diff options
author | Kristóf Marussy <kristof@marussy.com> | 2022-04-24 17:01:25 +0200 |
---|---|---|
committer | Kristóf Marussy <kristof@marussy.com> | 2022-05-16 00:55:02 +0200 |
commit | 0b632445a933644c7eb10015eb04b7c21d562900 (patch) | |
tree | 5bd1fe200cc747086220dbcba90097bfe2cc4cdc /packages | |
parent | chore(deps): upgrade dependencies (diff) | |
download | sophie-0b632445a933644c7eb10015eb04b7c21d562900.tar.gz sophie-0b632445a933644c7eb10015eb04b7c21d562900.tar.zst sophie-0b632445a933644c7eb10015eb04b7c21d562900.zip |
refactor: reduce service switcher tearing
We render the location bar and notification banners separately for each
service and keep track of the BrowserView size separately for each
service to reduce the tearing that appears when people switch services.
The tearing cannot be eliminated completely, because it comes from the
separation between the main and renderer processes. But we can at least
try and reduce the IPC round-tripping and layout calculations required
to accurately position the services.
This approach has an overhead compared to the single
BrowserViewPlaceholder approach, because the renderer process has to
layout the location bar and notification for all services, not only the
selected one. The number of IPC messages during windows resize is also
increased. To compensate, we increase the throttle interval for resize
IPC messages and let electron itself resize the BrowserView between IPC
updates. (We must still keep pumping IPC messages during window resize,
because, e.g., changes in notification banner size due to re-layouting
will still affect the required BrowserView size).
If further reduction of IPC traffic is needed, we could implement
batching for resize IPC messages and more intelligent throttling via a
token bucker mechanism.
Signed-off-by: Kristóf Marussy <kristof@marussy.com>
Diffstat (limited to 'packages')
23 files changed, 209 insertions, 141 deletions
diff --git a/packages/main/src/infrastructure/electron/impl/ElectronMainWindow.ts b/packages/main/src/infrastructure/electron/impl/ElectronMainWindow.ts index edc6592..b0db115 100644 --- a/packages/main/src/infrastructure/electron/impl/ElectronMainWindow.ts +++ b/packages/main/src/infrastructure/electron/impl/ElectronMainWindow.ts | |||
@@ -153,6 +153,9 @@ export default class ElectronMainWindow implements MainWindow { | |||
153 | } | 153 | } |
154 | if (serviceView instanceof ElectronServiceView) { | 154 | if (serviceView instanceof ElectronServiceView) { |
155 | this.browserWindow.setBrowserView(serviceView.browserView); | 155 | this.browserWindow.setBrowserView(serviceView.browserView); |
156 | // If this `BrowserView` hasn't been attached previously, | ||
157 | // we must update its bounds _after_ attaching for the resizing to take effect. | ||
158 | serviceView.updateBounds(); | ||
156 | return; | 159 | return; |
157 | } | 160 | } |
158 | throw new TypeError( | 161 | throw new TypeError( |
diff --git a/packages/main/src/infrastructure/electron/impl/ElectronServiceView.ts b/packages/main/src/infrastructure/electron/impl/ElectronServiceView.ts index 2e64269..3118efc 100644 --- a/packages/main/src/infrastructure/electron/impl/ElectronServiceView.ts +++ b/packages/main/src/infrastructure/electron/impl/ElectronServiceView.ts | |||
@@ -18,7 +18,6 @@ | |||
18 | * SPDX-License-Identifier: AGPL-3.0-only | 18 | * SPDX-License-Identifier: AGPL-3.0-only |
19 | */ | 19 | */ |
20 | 20 | ||
21 | import type { BrowserViewBounds } from '@sophie/shared'; | ||
22 | import { BrowserView } from 'electron'; | 21 | import { BrowserView } from 'electron'; |
23 | 22 | ||
24 | import type Service from '../../../stores/Service'; | 23 | import type Service from '../../../stores/Service'; |
@@ -39,7 +38,7 @@ export default class ElectronServiceView implements ServiceView { | |||
39 | readonly browserView: BrowserView; | 38 | readonly browserView: BrowserView; |
40 | 39 | ||
41 | constructor( | 40 | constructor( |
42 | service: Service, | 41 | private readonly service: Service, |
43 | resources: Resources, | 42 | resources: Resources, |
44 | partition: ElectronPartition, | 43 | partition: ElectronPartition, |
45 | private readonly parent: ElectronViewFactory, | 44 | private readonly parent: ElectronViewFactory, |
@@ -56,6 +55,13 @@ export default class ElectronServiceView implements ServiceView { | |||
56 | }); | 55 | }); |
57 | 56 | ||
58 | this.browserView.setBackgroundColor('#fff'); | 57 | this.browserView.setBackgroundColor('#fff'); |
58 | this.browserView.setAutoResize({ | ||
59 | width: true, | ||
60 | height: true, | ||
61 | }); | ||
62 | // Util we first attach `browserView` to a `BrowserWindow`, | ||
63 | // `setBounds` calls will be ignored, so there's no point in callind `updateBounds` here. | ||
64 | // It will be called by `ElectronMainWindow` when we first attach this service to it. | ||
59 | 65 | ||
60 | const { webContents } = this.browserView; | 66 | const { webContents } = this.browserView; |
61 | 67 | ||
@@ -191,13 +197,17 @@ export default class ElectronServiceView implements ServiceView { | |||
191 | this.browserView.webContents.toggleDevTools(); | 197 | this.browserView.webContents.toggleDevTools(); |
192 | } | 198 | } |
193 | 199 | ||
194 | setBounds(bounds: BrowserViewBounds): void { | 200 | updateBounds(): void { |
195 | this.browserView.setBounds(bounds); | 201 | const { x, y, width, height, hasBounds } = this.service; |
202 | if (!hasBounds) { | ||
203 | return; | ||
204 | } | ||
205 | this.browserView.setBounds({ x, y, width, height }); | ||
196 | } | 206 | } |
197 | 207 | ||
198 | dispose(): void { | 208 | dispose(): void { |
199 | this.parent.unregisterServiceView(this.webContentsId); | ||
200 | setImmediate(() => { | 209 | setImmediate(() => { |
210 | this.parent.unregisterServiceView(this.webContentsId); | ||
201 | // Undocumented electron API, see e.g., https://github.com/electron/electron/issues/29626 | 211 | // Undocumented electron API, see e.g., https://github.com/electron/electron/issues/29626 |
202 | ( | 212 | ( |
203 | this.browserView.webContents as unknown as { destroy(): void } | 213 | this.browserView.webContents as unknown as { destroy(): void } |
diff --git a/packages/main/src/infrastructure/electron/types.ts b/packages/main/src/infrastructure/electron/types.ts index 1321048..92ca9ad 100644 --- a/packages/main/src/infrastructure/electron/types.ts +++ b/packages/main/src/infrastructure/electron/types.ts | |||
@@ -18,8 +18,6 @@ | |||
18 | * SPDX-License-Identifier: AGPL-3.0-only | 18 | * SPDX-License-Identifier: AGPL-3.0-only |
19 | */ | 19 | */ |
20 | 20 | ||
21 | import type { BrowserViewBounds } from '@sophie/shared'; | ||
22 | |||
23 | import type MainStore from '../../stores/MainStore'; | 21 | import type MainStore from '../../stores/MainStore'; |
24 | import type Profile from '../../stores/Profile'; | 22 | import type Profile from '../../stores/Profile'; |
25 | import type Service from '../../stores/Service'; | 23 | import type Service from '../../stores/Service'; |
@@ -67,7 +65,7 @@ export interface ServiceView { | |||
67 | 65 | ||
68 | toggleDeveloperTools(): void; | 66 | toggleDeveloperTools(): void; |
69 | 67 | ||
70 | setBounds(bounds: BrowserViewBounds): void; | 68 | updateBounds(): void; |
71 | 69 | ||
72 | dispose(): void; | 70 | dispose(): void; |
73 | } | 71 | } |
diff --git a/packages/main/src/reactions/loadServices.ts b/packages/main/src/reactions/loadServices.ts index 4ef6131..f56ac62 100644 --- a/packages/main/src/reactions/loadServices.ts +++ b/packages/main/src/reactions/loadServices.ts | |||
@@ -18,7 +18,7 @@ | |||
18 | * SPDX-License-Identifier: AGPL-3.0-only | 18 | * SPDX-License-Identifier: AGPL-3.0-only |
19 | */ | 19 | */ |
20 | 20 | ||
21 | import { autorun, reaction } from 'mobx'; | 21 | import { reaction } from 'mobx'; |
22 | import { addDisposer } from 'mobx-state-tree'; | 22 | import { addDisposer } from 'mobx-state-tree'; |
23 | 23 | ||
24 | import type { | 24 | import type { |
@@ -94,7 +94,6 @@ export default function loadServices( | |||
94 | throw new Error(`Missing Partition ${profileId}`); | 94 | throw new Error(`Missing Partition ${profileId}`); |
95 | } | 95 | } |
96 | view = viewFactory.createServiceView(service, partition); | 96 | view = viewFactory.createServiceView(service, partition); |
97 | view.setBounds(store.browserViewBounds); | ||
98 | servicesToViews.set(serviceId, view); | 97 | servicesToViews.set(serviceId, view); |
99 | service.setServiceView(view); | 98 | service.setServiceView(view); |
100 | const { urlToLoad } = service; | 99 | const { urlToLoad } = service; |
@@ -133,12 +132,7 @@ export default function loadServices( | |||
133 | }, | 132 | }, |
134 | ); | 133 | ); |
135 | 134 | ||
136 | const resizeDisposer = autorun(() => { | ||
137 | store.visibleService?.serviceView?.setBounds(store.browserViewBounds); | ||
138 | }); | ||
139 | |||
140 | addDisposer(store, () => { | 135 | addDisposer(store, () => { |
141 | resizeDisposer(); | ||
142 | disposer(); | 136 | disposer(); |
143 | store.mainWindow?.setServiceView(undefined); | 137 | store.mainWindow?.setServiceView(undefined); |
144 | servicesToViews.forEach((serviceView, serviceId) => { | 138 | servicesToViews.forEach((serviceView, serviceId) => { |
diff --git a/packages/main/src/stores/MainStore.ts b/packages/main/src/stores/MainStore.ts index d717bed..9affbd0 100644 --- a/packages/main/src/stores/MainStore.ts +++ b/packages/main/src/stores/MainStore.ts | |||
@@ -18,9 +18,9 @@ | |||
18 | * SPDX-License-Identifier: AGPL-3.0-only | 18 | * SPDX-License-Identifier: AGPL-3.0-only |
19 | */ | 19 | */ |
20 | 20 | ||
21 | import type { Action, BrowserViewBounds } from '@sophie/shared'; | 21 | import type { Action } from '@sophie/shared'; |
22 | import type { ResourceKey } from 'i18next'; | 22 | import type { ResourceKey } from 'i18next'; |
23 | import { applySnapshot, flow, Instance, types } from 'mobx-state-tree'; | 23 | import { flow, Instance, types } from 'mobx-state-tree'; |
24 | 24 | ||
25 | import type I18nStore from '../i18n/I18nStore'; | 25 | import type I18nStore from '../i18n/I18nStore'; |
26 | import type { UseTranslationResult } from '../i18n/I18nStore'; | 26 | import type { UseTranslationResult } from '../i18n/I18nStore'; |
@@ -37,15 +37,6 @@ const log = getLogger('MainStore'); | |||
37 | 37 | ||
38 | const MainStore = types | 38 | const MainStore = types |
39 | .model('MainStore', { | 39 | .model('MainStore', { |
40 | browserViewBounds: types.optional( | ||
41 | types.model('BrowserViewBounds', { | ||
42 | x: 0, | ||
43 | y: 0, | ||
44 | width: 0, | ||
45 | height: 0, | ||
46 | }), | ||
47 | {}, | ||
48 | ), | ||
49 | shared: types.optional(SharedStore, {}), | 40 | shared: types.optional(SharedStore, {}), |
50 | i18n: types.frozen<I18nStore | undefined>(), | 41 | i18n: types.frozen<I18nStore | undefined>(), |
51 | }) | 42 | }) |
@@ -83,9 +74,6 @@ const MainStore = types | |||
83 | }), | 74 | }), |
84 | ) | 75 | ) |
85 | .actions((self) => ({ | 76 | .actions((self) => ({ |
86 | setBrowserViewBounds(bounds: BrowserViewBounds): void { | ||
87 | applySnapshot(self.browserViewBounds, bounds); | ||
88 | }, | ||
89 | setMainWindow(mainWindow: MainWindow | undefined): void { | 77 | setMainWindow(mainWindow: MainWindow | undefined): void { |
90 | self.mainWindow = mainWindow; | 78 | self.mainWindow = mainWindow; |
91 | }, | 79 | }, |
@@ -121,9 +109,6 @@ const MainStore = types | |||
121 | .actions((self) => ({ | 109 | .actions((self) => ({ |
122 | dispatch(action: Action): void { | 110 | dispatch(action: Action): void { |
123 | switch (action.action) { | 111 | switch (action.action) { |
124 | case 'set-browser-view-bounds': | ||
125 | self.setBrowserViewBounds(action.browserViewBounds); | ||
126 | break; | ||
127 | case 'set-selected-service-id': | 112 | case 'set-selected-service-id': |
128 | self.settings.setSelectedServiceId(action.serviceId); | 113 | self.settings.setSelectedServiceId(action.serviceId); |
129 | break; | 114 | break; |
diff --git a/packages/main/src/stores/Service.ts b/packages/main/src/stores/Service.ts index 1d46dc9..8ba8098 100644 --- a/packages/main/src/stores/Service.ts +++ b/packages/main/src/stores/Service.ts | |||
@@ -25,6 +25,7 @@ import { | |||
25 | defineServiceModel, | 25 | defineServiceModel, |
26 | ServiceAction, | 26 | ServiceAction, |
27 | type ServiceStateSnapshotIn, | 27 | type ServiceStateSnapshotIn, |
28 | type BrowserViewBounds, | ||
28 | } from '@sophie/shared'; | 29 | } from '@sophie/shared'; |
29 | import { type Instance, getSnapshot, cast, flow } from 'mobx-state-tree'; | 30 | import { type Instance, getSnapshot, cast, flow } from 'mobx-state-tree'; |
30 | 31 | ||
@@ -41,8 +42,18 @@ const Service = defineServiceModel(ServiceSettings) | |||
41 | .volatile( | 42 | .volatile( |
42 | (): { | 43 | (): { |
43 | serviceView: ServiceView | undefined; | 44 | serviceView: ServiceView | undefined; |
45 | x: number; | ||
46 | y: number; | ||
47 | width: number; | ||
48 | height: number; | ||
49 | hasBounds: boolean; | ||
44 | } => ({ | 50 | } => ({ |
45 | serviceView: undefined, | 51 | serviceView: undefined, |
52 | x: 0, | ||
53 | y: 0, | ||
54 | width: 0, | ||
55 | height: 0, | ||
56 | hasBounds: false, | ||
46 | }), | 57 | }), |
47 | ) | 58 | ) |
48 | .views((self) => ({ | 59 | .views((self) => ({ |
@@ -59,10 +70,20 @@ const Service = defineServiceModel(ServiceSettings) | |||
59 | })) | 70 | })) |
60 | .views((self) => ({ | 71 | .views((self) => ({ |
61 | get shouldBeVisible(): boolean { | 72 | get shouldBeVisible(): boolean { |
62 | return self.shouldBeLoaded && !self.hasError; | 73 | // Do not attach service views for which we don't know the appropriate frame size, |
74 | // because they will just appear in a random location until the frame size is determined. | ||
75 | return self.shouldBeLoaded && !self.hasError && self.hasBounds; | ||
63 | }, | 76 | }, |
64 | })) | 77 | })) |
65 | .actions((self) => ({ | 78 | .actions((self) => ({ |
79 | setBrowserViewBounds(bounds: BrowserViewBounds): void { | ||
80 | self.x = bounds.x; | ||
81 | self.y = bounds.y; | ||
82 | self.width = bounds.width; | ||
83 | self.height = bounds.height; | ||
84 | self.hasBounds = true; | ||
85 | self.serviceView?.updateBounds(); | ||
86 | }, | ||
66 | setServiceView(serviceView: ServiceView | undefined): void { | 87 | setServiceView(serviceView: ServiceView | undefined): void { |
67 | self.serviceView = serviceView; | 88 | self.serviceView = serviceView; |
68 | }, | 89 | }, |
@@ -263,6 +284,9 @@ const Service = defineServiceModel(ServiceSettings) | |||
263 | .actions((self) => ({ | 284 | .actions((self) => ({ |
264 | dispatch(action: ServiceAction): void { | 285 | dispatch(action: ServiceAction): void { |
265 | switch (action.action) { | 286 | switch (action.action) { |
287 | case 'set-browser-view-bounds': | ||
288 | self.setBrowserViewBounds(action.browserViewBounds); | ||
289 | break; | ||
266 | case 'back': | 290 | case 'back': |
267 | self.goBack(); | 291 | self.goBack(); |
268 | break; | 292 | break; |
diff --git a/packages/renderer/src/components/App.tsx b/packages/renderer/src/components/App.tsx index d381abf..2f728e7 100644 --- a/packages/renderer/src/components/App.tsx +++ b/packages/renderer/src/components/App.tsx | |||
@@ -23,12 +23,8 @@ import { observer } from 'mobx-react-lite'; | |||
23 | import React, { useCallback, useEffect } from 'react'; | 23 | import React, { useCallback, useEffect } from 'react'; |
24 | import { useTranslation } from 'react-i18next'; | 24 | import { useTranslation } from 'react-i18next'; |
25 | 25 | ||
26 | import BrowserViewPlaceholder from './BrowserViewPlaceholder'; | 26 | import ServicePanel from './ServicePanel'; |
27 | import { useStore } from './StoreProvider'; | 27 | import { useStore } from './StoreProvider'; |
28 | import InsecureConnectionBanner from './banner/InsecureConnectionBanner'; | ||
29 | import NewWindowBanner from './banner/NewWindowBanner'; | ||
30 | import ErrorPage from './errorPage/ErrorPage'; | ||
31 | import LocationBar from './locationBar/LocationBar'; | ||
32 | import Sidebar from './sidebar/Sidebar'; | 28 | import Sidebar from './sidebar/Sidebar'; |
33 | 29 | ||
34 | function App({ devMode }: { devMode: boolean }): JSX.Element { | 30 | function App({ devMode }: { devMode: boolean }): JSX.Element { |
@@ -37,6 +33,7 @@ function App({ devMode }: { devMode: boolean }): JSX.Element { | |||
37 | }); | 33 | }); |
38 | const { | 34 | const { |
39 | settings: { selectedService }, | 35 | settings: { selectedService }, |
36 | shared: { services }, | ||
40 | } = useStore(); | 37 | } = useStore(); |
41 | const { | 38 | const { |
42 | settings: { name: serviceName }, | 39 | settings: { name: serviceName }, |
@@ -95,20 +92,13 @@ function App({ devMode }: { devMode: boolean }): JSX.Element { | |||
95 | <Box | 92 | <Box |
96 | sx={{ | 93 | sx={{ |
97 | flex: 1, | 94 | flex: 1, |
98 | display: 'flex', | ||
99 | overflow: 'hidden', | ||
100 | flexDirection: 'column', | ||
101 | alignItems: 'stretch', | ||
102 | height: '100%', | 95 | height: '100%', |
103 | zIndex: 100, | 96 | position: 'relative', |
104 | }} | 97 | }} |
105 | > | 98 | > |
106 | <LocationBar /> | 99 | {services.map((service) => ( |
107 | <InsecureConnectionBanner service={selectedService} /> | 100 | <ServicePanel key={service.id} service={service} /> |
108 | <NewWindowBanner service={selectedService} /> | 101 | ))} |
109 | <BrowserViewPlaceholder> | ||
110 | <ErrorPage service={selectedService} /> | ||
111 | </BrowserViewPlaceholder> | ||
112 | </Box> | 102 | </Box> |
113 | </Box> | 103 | </Box> |
114 | ); | 104 | ); |
diff --git a/packages/renderer/src/components/BrowserViewPlaceholder.tsx b/packages/renderer/src/components/BrowserViewPlaceholder.tsx index 9bd1176..2bfc9b0 100644 --- a/packages/renderer/src/components/BrowserViewPlaceholder.tsx +++ b/packages/renderer/src/components/BrowserViewPlaceholder.tsx | |||
@@ -22,29 +22,29 @@ import Box from '@mui/material/Box'; | |||
22 | import throttle from 'lodash-es/throttle'; | 22 | import throttle from 'lodash-es/throttle'; |
23 | import React, { ReactNode, useCallback, useRef } from 'react'; | 23 | import React, { ReactNode, useCallback, useRef } from 'react'; |
24 | 24 | ||
25 | import { useStore } from './StoreProvider'; | 25 | import Service from '../stores/Service'; |
26 | 26 | ||
27 | function BrowserViewPlaceholder({ | 27 | function BrowserViewPlaceholder({ |
28 | service, | ||
28 | children, | 29 | children, |
29 | }: { | 30 | }: { |
31 | service: Service; | ||
30 | children?: ReactNode; | 32 | children?: ReactNode; |
31 | }): JSX.Element { | 33 | }): JSX.Element { |
32 | const store = useStore(); | ||
33 | |||
34 | // eslint-disable-next-line react-hooks/exhaustive-deps -- react-hooks doesn't support `throttle`. | 34 | // eslint-disable-next-line react-hooks/exhaustive-deps -- react-hooks doesn't support `throttle`. |
35 | const onResize = useCallback( | 35 | const onResize = useCallback( |
36 | throttle(([entry]: ResizeObserverEntry[]) => { | 36 | throttle(([entry]: ResizeObserverEntry[]) => { |
37 | if (entry) { | 37 | if (entry) { |
38 | const { x, y, width, height } = entry.target.getBoundingClientRect(); | 38 | const { x, y, width, height } = entry.target.getBoundingClientRect(); |
39 | store.setBrowserViewBounds({ | 39 | service.setBrowserViewBounds({ |
40 | x: Math.round(x), | 40 | x: Math.round(x), |
41 | y: Math.round(y), | 41 | y: Math.round(y), |
42 | width: Math.round(width), | 42 | width: Math.round(width), |
43 | height: Math.round(height), | 43 | height: Math.round(height), |
44 | }); | 44 | }); |
45 | } | 45 | } |
46 | }, 40), | 46 | }, 100), |
47 | [store], | 47 | [service], |
48 | ); | 48 | ); |
49 | 49 | ||
50 | const resizeObserverRef = useRef<ResizeObserver | undefined>(); | 50 | const resizeObserverRef = useRef<ResizeObserver | undefined>(); |
@@ -61,7 +61,7 @@ function BrowserViewPlaceholder({ | |||
61 | resizeObserverRef.current = new ResizeObserver(onResize); | 61 | resizeObserverRef.current = new ResizeObserver(onResize); |
62 | resizeObserverRef.current.observe(element); | 62 | resizeObserverRef.current.observe(element); |
63 | }, | 63 | }, |
64 | [onResize, resizeObserverRef], | 64 | [onResize], |
65 | ); | 65 | ); |
66 | 66 | ||
67 | return ( | 67 | return ( |
diff --git a/packages/renderer/src/components/ServicePanel.tsx b/packages/renderer/src/components/ServicePanel.tsx new file mode 100644 index 0000000..de58d24 --- /dev/null +++ b/packages/renderer/src/components/ServicePanel.tsx | |||
@@ -0,0 +1,76 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2021-2022 Kristóf Marussy <kristof@marussy.com> | ||
3 | * | ||
4 | * This file is part of Sophie. | ||
5 | * | ||
6 | * Sophie is free software: you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU Affero General Public License as | ||
8 | * published by the Free Software Foundation, version 3. | ||
9 | * | ||
10 | * This program is distributed in the hope that it will be useful, | ||
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
13 | * GNU Affero General Public License for more details. | ||
14 | * | ||
15 | * You should have received a copy of the GNU Affero General Public License | ||
16 | * along with this program. If not, see <https://www.gnu.org/licenses/>. | ||
17 | * | ||
18 | * SPDX-License-Identifier: AGPL-3.0-only | ||
19 | */ | ||
20 | |||
21 | import Box from '@mui/material/Box'; | ||
22 | import { observer } from 'mobx-react-lite'; | ||
23 | import React from 'react'; | ||
24 | |||
25 | import Service from '../stores/Service'; | ||
26 | |||
27 | import BrowserViewPlaceholder from './BrowserViewPlaceholder'; | ||
28 | import { useStore } from './StoreProvider'; | ||
29 | import InsecureConnectionBanner from './banner/InsecureConnectionBanner'; | ||
30 | import NewWindowBanner from './banner/NewWindowBanner'; | ||
31 | import ErrorPage from './errorPage/ErrorPage'; | ||
32 | import LocationBar from './locationBar/LocationBar'; | ||
33 | |||
34 | export function getServicePanelID(service: Service): string { | ||
35 | return `Sophie-${service.id}-ServicePanel`; | ||
36 | } | ||
37 | |||
38 | function ServicePanel({ service }: { service: Service }): JSX.Element { | ||
39 | const { | ||
40 | settings: { selectedService }, | ||
41 | } = useStore(); | ||
42 | |||
43 | const { | ||
44 | settings: { name }, | ||
45 | } = service; | ||
46 | const visible = service === selectedService; | ||
47 | |||
48 | return ( | ||
49 | <Box | ||
50 | id={getServicePanelID(service)} | ||
51 | role="tabpanel" | ||
52 | aria-label={name} | ||
53 | sx={{ | ||
54 | position: 'absolute', | ||
55 | top: 0, | ||
56 | left: 0, | ||
57 | bottom: 0, | ||
58 | right: 0, | ||
59 | display: 'flex', | ||
60 | overflow: 'hidden', | ||
61 | visibility: visible ? 'visible' : 'hidden', | ||
62 | flexDirection: 'column', | ||
63 | alignItems: 'stretch', | ||
64 | }} | ||
65 | > | ||
66 | <LocationBar service={service} /> | ||
67 | <InsecureConnectionBanner service={service} /> | ||
68 | <NewWindowBanner service={service} /> | ||
69 | <BrowserViewPlaceholder service={service}> | ||
70 | <ErrorPage service={service} /> | ||
71 | </BrowserViewPlaceholder> | ||
72 | </Box> | ||
73 | ); | ||
74 | } | ||
75 | |||
76 | export default observer(ServicePanel); | ||
diff --git a/packages/renderer/src/components/banner/InsecureConnectionBanner.tsx b/packages/renderer/src/components/banner/InsecureConnectionBanner.tsx index 7a03fce..0b70db6 100644 --- a/packages/renderer/src/components/banner/InsecureConnectionBanner.tsx +++ b/packages/renderer/src/components/banner/InsecureConnectionBanner.tsx | |||
@@ -34,17 +34,12 @@ import NotificationBanner from './NotificationBanner'; | |||
34 | function InsecureConnectionBanner({ | 34 | function InsecureConnectionBanner({ |
35 | service, | 35 | service, |
36 | }: { | 36 | }: { |
37 | service: Service | undefined; | 37 | service: Service; |
38 | }): JSX.Element | null { | 38 | }): JSX.Element | null { |
39 | const { t } = useTranslation(undefined, { | 39 | const { t } = useTranslation(undefined, { |
40 | keyPrefix: 'banner.insecureConnection', | 40 | keyPrefix: 'banner.insecureConnection', |
41 | }); | 41 | }); |
42 | 42 | ||
43 | if (service === undefined) { | ||
44 | // eslint-disable-next-line unicorn/no-null -- React requires `null` to skip rendering. | ||
45 | return null; | ||
46 | } | ||
47 | |||
48 | const { | 43 | const { |
49 | canReconnectSecurely, | 44 | canReconnectSecurely, |
50 | hasError, | 45 | hasError, |
diff --git a/packages/renderer/src/components/banner/NewWindowBanner.tsx b/packages/renderer/src/components/banner/NewWindowBanner.tsx index 478de8e..07fafda 100644 --- a/packages/renderer/src/components/banner/NewWindowBanner.tsx +++ b/packages/renderer/src/components/banner/NewWindowBanner.tsx | |||
@@ -32,17 +32,12 @@ import NotificationBanner from './NotificationBanner'; | |||
32 | function NewWindowBanner({ | 32 | function NewWindowBanner({ |
33 | service, | 33 | service, |
34 | }: { | 34 | }: { |
35 | service: Service | undefined; | 35 | service: Service; |
36 | }): JSX.Element | null { | 36 | }): JSX.Element | null { |
37 | const { t } = useTranslation(undefined, { | 37 | const { t } = useTranslation(undefined, { |
38 | keyPrefix: 'banner.newWindow', | 38 | keyPrefix: 'banner.newWindow', |
39 | }); | 39 | }); |
40 | 40 | ||
41 | if (service === undefined) { | ||
42 | // eslint-disable-next-line unicorn/no-null -- React requires `null` to skip rendering. | ||
43 | return null; | ||
44 | } | ||
45 | |||
46 | const { | 41 | const { |
47 | popups, | 42 | popups, |
48 | settings: { name }, | 43 | settings: { name }, |
diff --git a/packages/renderer/src/components/errorPage/ErrorPage.tsx b/packages/renderer/src/components/errorPage/ErrorPage.tsx index 99ca020..10f54cd 100644 --- a/packages/renderer/src/components/errorPage/ErrorPage.tsx +++ b/packages/renderer/src/components/errorPage/ErrorPage.tsx | |||
@@ -123,14 +123,10 @@ function formatError(service: Service, t: TFunction): ErrorDetails { | |||
123 | } | 123 | } |
124 | } | 124 | } |
125 | 125 | ||
126 | function ErrorPage({ | 126 | function ErrorPage({ service }: { service: Service }): JSX.Element | null { |
127 | service, | ||
128 | }: { | ||
129 | service: Service | undefined; | ||
130 | }): JSX.Element | null { | ||
131 | const { t } = useTranslation(undefined); | 127 | const { t } = useTranslation(undefined); |
132 | 128 | ||
133 | if (service === undefined || !service.hasError) { | 129 | if (!service.hasError) { |
134 | // eslint-disable-next-line unicorn/no-null -- React requires `null` to skip rendering. | 130 | // eslint-disable-next-line unicorn/no-null -- React requires `null` to skip rendering. |
135 | return null; | 131 | return null; |
136 | } | 132 | } |
diff --git a/packages/renderer/src/components/locationBar/ExtraButtons.tsx b/packages/renderer/src/components/locationBar/ExtraButtons.tsx index ef90199..4d4c3c4 100644 --- a/packages/renderer/src/components/locationBar/ExtraButtons.tsx +++ b/packages/renderer/src/components/locationBar/ExtraButtons.tsx | |||
@@ -27,11 +27,7 @@ import { useTranslation } from 'react-i18next'; | |||
27 | 27 | ||
28 | import type Service from '../../stores/Service'; | 28 | import type Service from '../../stores/Service'; |
29 | 29 | ||
30 | function ExtraButtons({ | 30 | function ExtraButtons({ service }: { service: Service }): JSX.Element { |
31 | service, | ||
32 | }: { | ||
33 | service: Service | undefined; | ||
34 | }): JSX.Element { | ||
35 | const { t } = useTranslation(undefined, { | 31 | const { t } = useTranslation(undefined, { |
36 | keyPrefix: 'toolbar', | 32 | keyPrefix: 'toolbar', |
37 | }); | 33 | }); |
@@ -40,8 +36,8 @@ function ExtraButtons({ | |||
40 | <Box display="flex"> | 36 | <Box display="flex"> |
41 | <IconButton | 37 | <IconButton |
42 | aria-label={t('openInBrowser')} | 38 | aria-label={t('openInBrowser')} |
43 | disabled={service?.currentUrl === undefined} | 39 | disabled={service.currentUrl === undefined} |
44 | onClick={() => service?.openCurrentURLInExternalBrowser()} | 40 | onClick={() => service.openCurrentURLInExternalBrowser()} |
45 | > | 41 | > |
46 | <IconOpenInBrowser /> | 42 | <IconOpenInBrowser /> |
47 | </IconButton> | 43 | </IconButton> |
diff --git a/packages/renderer/src/components/locationBar/LocationBar.tsx b/packages/renderer/src/components/locationBar/LocationBar.tsx index 54ead8e..c290722 100644 --- a/packages/renderer/src/components/locationBar/LocationBar.tsx +++ b/packages/renderer/src/components/locationBar/LocationBar.tsx | |||
@@ -22,13 +22,16 @@ import { styled } from '@mui/material/styles'; | |||
22 | import { observer } from 'mobx-react-lite'; | 22 | import { observer } from 'mobx-react-lite'; |
23 | import React from 'react'; | 23 | import React from 'react'; |
24 | 24 | ||
25 | import type Service from '../../stores/Service'; | ||
25 | import { useStore } from '../StoreProvider'; | 26 | import { useStore } from '../StoreProvider'; |
26 | 27 | ||
27 | import ExtraButtons from './ExtraButtons'; | 28 | import ExtraButtons from './ExtraButtons'; |
28 | import LocationTextField from './LocationTextField'; | 29 | import LocationTextField from './LocationTextField'; |
29 | import NavigationButtons from './NavigationButtons'; | 30 | import NavigationButtons from './NavigationButtons'; |
30 | 31 | ||
31 | export const LOCATION_BAR_ID = 'Sophie-LocationBar'; | 32 | export function getLocaltionBarID(service: Service): string { |
33 | return `Sophie-${service.id}-LocationBar`; | ||
34 | } | ||
32 | 35 | ||
33 | const LocationBarRoot = styled('header', { | 36 | const LocationBarRoot = styled('header', { |
34 | name: 'LocationBar', | 37 | name: 'LocationBar', |
@@ -41,17 +44,22 @@ const LocationBarRoot = styled('header', { | |||
41 | borderBottom: `1px solid ${theme.palette.divider}`, | 44 | borderBottom: `1px solid ${theme.palette.divider}`, |
42 | })); | 45 | })); |
43 | 46 | ||
44 | function LocationBar(): JSX.Element { | 47 | function LocationBar({ service }: { service: Service }): JSX.Element { |
45 | const { | 48 | const { |
46 | shared: { locationBarVisible }, | 49 | settings: { showLocationBar }, |
47 | settings: { selectedService }, | ||
48 | } = useStore(); | 50 | } = useStore(); |
49 | 51 | ||
52 | const { alwaysShowLocationBar } = service; | ||
53 | const locationBarVisible = showLocationBar || alwaysShowLocationBar; | ||
54 | |||
50 | return ( | 55 | return ( |
51 | <LocationBarRoot id={LOCATION_BAR_ID} hidden={!locationBarVisible}> | 56 | <LocationBarRoot |
52 | <NavigationButtons service={selectedService} /> | 57 | id={getLocaltionBarID(service)} |
53 | <LocationTextField service={selectedService} /> | 58 | hidden={!locationBarVisible} |
54 | <ExtraButtons service={selectedService} /> | 59 | > |
60 | <NavigationButtons service={service} /> | ||
61 | <LocationTextField service={service} /> | ||
62 | <ExtraButtons service={service} /> | ||
55 | </LocationBarRoot> | 63 | </LocationBarRoot> |
56 | ); | 64 | ); |
57 | } | 65 | } |
diff --git a/packages/renderer/src/components/locationBar/LocationTextField.tsx b/packages/renderer/src/components/locationBar/LocationTextField.tsx index 85cf794..1d6b561 100644 --- a/packages/renderer/src/components/locationBar/LocationTextField.tsx +++ b/packages/renderer/src/components/locationBar/LocationTextField.tsx | |||
@@ -20,7 +20,6 @@ | |||
20 | 20 | ||
21 | import FilledInput from '@mui/material/FilledInput'; | 21 | import FilledInput from '@mui/material/FilledInput'; |
22 | import { styled } from '@mui/material/styles'; | 22 | import { styled } from '@mui/material/styles'; |
23 | import { SecurityLabelKind } from '@sophie/shared'; | ||
24 | import { autorun } from 'mobx'; | 23 | import { autorun } from 'mobx'; |
25 | import { observer } from 'mobx-react-lite'; | 24 | import { observer } from 'mobx-react-lite'; |
26 | import React, { useCallback, useEffect, useState } from 'react'; | 25 | import React, { useCallback, useEffect, useState } from 'react'; |
@@ -44,19 +43,15 @@ const LocationTextFieldRoot = styled(FilledInput, { | |||
44 | }, | 43 | }, |
45 | })); | 44 | })); |
46 | 45 | ||
47 | function LocationTextField({ | 46 | function LocationTextField({ service }: { service: Service }): JSX.Element { |
48 | service, | ||
49 | }: { | ||
50 | service: Service | undefined; | ||
51 | }): JSX.Element { | ||
52 | const [inputFocused, setInputFocused] = useState(false); | 47 | const [inputFocused, setInputFocused] = useState(false); |
53 | const [changed, setChanged] = useState(false); | 48 | const [changed, setChanged] = useState(false); |
54 | const [value, setValue] = useState(''); | 49 | const [value, setValue] = useState(''); |
55 | 50 | ||
56 | const resetValue = useCallback(() => { | 51 | const resetValue = useCallback(() => { |
57 | setValue(service?.currentUrl ?? ''); | 52 | setValue(service.currentUrl ?? ''); |
58 | setChanged(false); | 53 | setChanged(false); |
59 | }, [service, setChanged, setValue]); | 54 | }, [service]); |
60 | 55 | ||
61 | useEffect( | 56 | useEffect( |
62 | () => | 57 | () => |
@@ -84,8 +79,8 @@ function LocationTextField({ | |||
84 | overlayVisible: !inputFocused && !changed, | 79 | overlayVisible: !inputFocused && !changed, |
85 | overlay: ( | 80 | overlay: ( |
86 | <UrlOverlay | 81 | <UrlOverlay |
87 | url={service?.currentUrl ?? ''} | 82 | url={service.currentUrl ?? ''} |
88 | alert={service?.hasSecurityLabelWarning ?? false} | 83 | alert={service.hasSecurityLabelWarning ?? false} |
89 | /> | 84 | /> |
90 | ), | 85 | ), |
91 | }} | 86 | }} |
@@ -102,7 +97,7 @@ function LocationTextField({ | |||
102 | resetValue(); | 97 | resetValue(); |
103 | break; | 98 | break; |
104 | case 'Enter': | 99 | case 'Enter': |
105 | service?.go(value); | 100 | service.go(value); |
106 | break; | 101 | break; |
107 | default: | 102 | default: |
108 | // Nothing to do, let the key event through. | 103 | // Nothing to do, let the key event through. |
@@ -117,14 +112,14 @@ function LocationTextField({ | |||
117 | disableUnderline | 112 | disableUnderline |
118 | startAdornment={ | 113 | startAdornment={ |
119 | <SecurityLabel | 114 | <SecurityLabel |
120 | kind={service?.securityLabel ?? SecurityLabelKind.Empty} | 115 | kind={service.securityLabel} |
121 | changed={changed} | 116 | changed={changed} |
122 | position="start" | 117 | position="start" |
123 | /> | 118 | /> |
124 | } | 119 | } |
125 | endAdornment={ | 120 | endAdornment={ |
126 | changed ? ( | 121 | changed ? ( |
127 | <GoButton onClick={() => service?.go(value)} position="end" /> | 122 | <GoButton onClick={() => service.go(value)} position="end" /> |
128 | ) : undefined | 123 | ) : undefined |
129 | } | 124 | } |
130 | value={value} | 125 | value={value} |
diff --git a/packages/renderer/src/components/locationBar/NavigationButtons.tsx b/packages/renderer/src/components/locationBar/NavigationButtons.tsx index 9c4ebdb..96e40e7 100644 --- a/packages/renderer/src/components/locationBar/NavigationButtons.tsx +++ b/packages/renderer/src/components/locationBar/NavigationButtons.tsx | |||
@@ -32,11 +32,7 @@ import { useTranslation } from 'react-i18next'; | |||
32 | 32 | ||
33 | import type Service from '../../stores/Service'; | 33 | import type Service from '../../stores/Service'; |
34 | 34 | ||
35 | function NavigationButtons({ | 35 | function NavigationButtons({ service }: { service: Service }): JSX.Element { |
36 | service, | ||
37 | }: { | ||
38 | service: Service | undefined; | ||
39 | }): JSX.Element { | ||
40 | const { t } = useTranslation(undefined, { | 36 | const { t } = useTranslation(undefined, { |
41 | keyPrefix: 'toolbar', | 37 | keyPrefix: 'toolbar', |
42 | }); | 38 | }); |
@@ -46,36 +42,31 @@ function NavigationButtons({ | |||
46 | <Box display="flex"> | 42 | <Box display="flex"> |
47 | <IconButton | 43 | <IconButton |
48 | aria-label={t('back')} | 44 | aria-label={t('back')} |
49 | disabled={service === undefined || !service.canGoBack} | 45 | disabled={!service.canGoBack} |
50 | onClick={() => service?.goBack()} | 46 | onClick={() => service.goBack()} |
51 | > | 47 | > |
52 | {direction === 'ltr' ? <IconArrowBack /> : <IconArrowForward />} | 48 | {direction === 'ltr' ? <IconArrowBack /> : <IconArrowForward />} |
53 | </IconButton> | 49 | </IconButton> |
54 | <IconButton | 50 | <IconButton |
55 | aria-label={t('forward')} | 51 | aria-label={t('forward')} |
56 | disabled={service === undefined || !service.canGoForward} | 52 | disabled={!service.canGoForward} |
57 | onClick={() => service?.goForward()} | 53 | onClick={() => service.goForward()} |
58 | > | 54 | > |
59 | {direction === 'ltr' ? <IconArrowForward /> : <IconArrowBack />} | 55 | {direction === 'ltr' ? <IconArrowForward /> : <IconArrowBack />} |
60 | </IconButton> | 56 | </IconButton> |
61 | {service?.loading ?? false ? ( | 57 | {service.loading ? ( |
62 | <IconButton aria-label={t('stop')} onClick={() => service?.stop()}> | 58 | <IconButton aria-label={t('stop')} onClick={() => service.stop()}> |
63 | <IconStop /> | 59 | <IconStop /> |
64 | </IconButton> | 60 | </IconButton> |
65 | ) : ( | 61 | ) : ( |
66 | <IconButton | 62 | <IconButton |
67 | aria-label={t('reload')} | 63 | aria-label={t('reload')} |
68 | disabled={service === undefined} | 64 | onClick={(event) => service.reload(event.shiftKey)} |
69 | onClick={(event) => service?.reload(event.shiftKey)} | ||
70 | > | 65 | > |
71 | <IconRefresh /> | 66 | <IconRefresh /> |
72 | </IconButton> | 67 | </IconButton> |
73 | )} | 68 | )} |
74 | <IconButton | 69 | <IconButton aria-label={t('home')} onClick={() => service.goHome()}> |
75 | aria-label={t('home')} | ||
76 | disabled={service === undefined} | ||
77 | onClick={() => service?.goHome()} | ||
78 | > | ||
79 | <IconHome /> | 70 | <IconHome /> |
80 | </IconButton> | 71 | </IconButton> |
81 | </Box> | 72 | </Box> |
diff --git a/packages/renderer/src/components/sidebar/ServiceSwitcher.tsx b/packages/renderer/src/components/sidebar/ServiceSwitcher.tsx index 0ebd359..24cfd0c 100644 --- a/packages/renderer/src/components/sidebar/ServiceSwitcher.tsx +++ b/packages/renderer/src/components/sidebar/ServiceSwitcher.tsx | |||
@@ -27,6 +27,7 @@ import React from 'react'; | |||
27 | import { useTranslation } from 'react-i18next'; | 27 | import { useTranslation } from 'react-i18next'; |
28 | 28 | ||
29 | import type Service from '../../stores/Service'; | 29 | import type Service from '../../stores/Service'; |
30 | import { getServicePanelID } from '../ServicePanel'; | ||
30 | import { useStore } from '../StoreProvider'; | 31 | import { useStore } from '../StoreProvider'; |
31 | 32 | ||
32 | import ServiceIcon from './ServiceIcon'; | 33 | import ServiceIcon from './ServiceIcon'; |
@@ -112,6 +113,7 @@ function ServiceSwitcher(): JSX.Element { | |||
112 | value={service.id} | 113 | value={service.id} |
113 | icon={<ServiceIcon service={service} />} | 114 | icon={<ServiceIcon service={service} />} |
114 | aria-label={getServiceTitle(service, t)} | 115 | aria-label={getServiceTitle(service, t)} |
116 | aria-controls={getServicePanelID(service)} | ||
115 | /> | 117 | /> |
116 | ))} | 118 | ))} |
117 | </ServiceSwitcherRoot> | 119 | </ServiceSwitcherRoot> |
diff --git a/packages/renderer/src/components/sidebar/ToggleLocationBarButton.tsx b/packages/renderer/src/components/sidebar/ToggleLocationBarButton.tsx index 7fc559d..c697170 100644 --- a/packages/renderer/src/components/sidebar/ToggleLocationBarButton.tsx +++ b/packages/renderer/src/components/sidebar/ToggleLocationBarButton.tsx | |||
@@ -28,7 +28,7 @@ import React from 'react'; | |||
28 | import { useTranslation } from 'react-i18next'; | 28 | import { useTranslation } from 'react-i18next'; |
29 | 29 | ||
30 | import { useStore } from '../StoreProvider'; | 30 | import { useStore } from '../StoreProvider'; |
31 | import { LOCATION_BAR_ID } from '../locationBar/LocationBar'; | 31 | import { getLocaltionBarID } from '../locationBar/LocationBar'; |
32 | 32 | ||
33 | function ToggleLocationBarIcon({ | 33 | function ToggleLocationBarIcon({ |
34 | loading, | 34 | loading, |
@@ -54,13 +54,17 @@ function ToggleLocationBarButton(): JSX.Element { | |||
54 | const { selectedService } = settings; | 54 | const { selectedService } = settings; |
55 | 55 | ||
56 | return ( | 56 | return ( |
57 | /* eslint-disable react/jsx-props-no-spreading -- Conditionally set the aria-controls prop. */ | ||
57 | <IconButton | 58 | <IconButton |
58 | disabled={!canToggleLocationBar} | 59 | disabled={!canToggleLocationBar} |
59 | aria-pressed={locationBarVisible} | 60 | aria-pressed={locationBarVisible} |
60 | aria-controls={LOCATION_BAR_ID} | 61 | {...(selectedService === undefined |
62 | ? {} | ||
63 | : { 'aria-controls': getLocaltionBarID(selectedService) })} | ||
61 | aria-label={t('toolbar.toggleLocationBar')} | 64 | aria-label={t('toolbar.toggleLocationBar')} |
62 | onClick={() => settings.toggleLocationBar()} | 65 | onClick={() => settings.toggleLocationBar()} |
63 | > | 66 | > |
67 | {/* eslint-enable react/jsx-props-no-spreading */} | ||
64 | <ToggleLocationBarIcon | 68 | <ToggleLocationBarIcon |
65 | loading={selectedService?.loading ?? false} | 69 | loading={selectedService?.loading ?? false} |
66 | show={locationBarVisible} | 70 | show={locationBarVisible} |
diff --git a/packages/renderer/src/stores/RendererStore.ts b/packages/renderer/src/stores/RendererStore.ts index 7052162..a3983ca 100644 --- a/packages/renderer/src/stores/RendererStore.ts +++ b/packages/renderer/src/stores/RendererStore.ts | |||
@@ -18,14 +18,14 @@ | |||
18 | * SPDX-License-Identifier: AGPL-3.0-only | 18 | * SPDX-License-Identifier: AGPL-3.0-only |
19 | */ | 19 | */ |
20 | 20 | ||
21 | import { BrowserViewBounds, SophieRenderer } from '@sophie/shared'; | 21 | import type { SophieRenderer } from '@sophie/shared'; |
22 | import { applySnapshot, applyPatch, Instance, types } from 'mobx-state-tree'; | 22 | import { applySnapshot, applyPatch, Instance, types } from 'mobx-state-tree'; |
23 | 23 | ||
24 | import { getLogger } from '../utils/log'; | 24 | import { getLogger } from '../utils/log'; |
25 | 25 | ||
26 | import GlobalSettings from './GlobalSettings'; | 26 | import type GlobalSettings from './GlobalSettings'; |
27 | import RendererEnv, { getEnv } from './RendererEnv'; | 27 | import type RendererEnv from './RendererEnv'; |
28 | import Service from './Service'; | 28 | import type Service from './Service'; |
29 | import SharedStore from './SharedStore'; | 29 | import SharedStore from './SharedStore'; |
30 | 30 | ||
31 | const log = getLogger('RendererStore'); | 31 | const log = getLogger('RendererStore'); |
@@ -41,14 +41,6 @@ const RendererStore = types | |||
41 | get services(): Service[] { | 41 | get services(): Service[] { |
42 | return self.shared.services; | 42 | return self.shared.services; |
43 | }, | 43 | }, |
44 | })) | ||
45 | .actions((self) => ({ | ||
46 | setBrowserViewBounds(browserViewBounds: BrowserViewBounds): void { | ||
47 | getEnv(self).dispatchMainAction({ | ||
48 | action: 'set-browser-view-bounds', | ||
49 | browserViewBounds, | ||
50 | }); | ||
51 | }, | ||
52 | })); | 44 | })); |
53 | 45 | ||
54 | /* | 46 | /* |
diff --git a/packages/renderer/src/stores/Service.ts b/packages/renderer/src/stores/Service.ts index dcaf96e..c8d513f 100644 --- a/packages/renderer/src/stores/Service.ts +++ b/packages/renderer/src/stores/Service.ts | |||
@@ -18,7 +18,11 @@ | |||
18 | * SPDX-License-Identifier: AGPL-3.0-only | 18 | * SPDX-License-Identifier: AGPL-3.0-only |
19 | */ | 19 | */ |
20 | 20 | ||
21 | import { defineServiceModel, ServiceAction } from '@sophie/shared'; | 21 | import { |
22 | BrowserViewBounds, | ||
23 | defineServiceModel, | ||
24 | ServiceAction, | ||
25 | } from '@sophie/shared'; | ||
22 | import { Instance } from 'mobx-state-tree'; | 26 | import { Instance } from 'mobx-state-tree'; |
23 | 27 | ||
24 | import { getEnv } from './RendererEnv'; | 28 | import { getEnv } from './RendererEnv'; |
@@ -49,6 +53,12 @@ const Service = defineServiceModel(ServiceSettings) | |||
49 | } | 53 | } |
50 | 54 | ||
51 | return { | 55 | return { |
56 | setBrowserViewBounds(browserViewBounds: BrowserViewBounds): void { | ||
57 | dispatch({ | ||
58 | action: 'set-browser-view-bounds', | ||
59 | browserViewBounds, | ||
60 | }); | ||
61 | }, | ||
52 | goBack(): void { | 62 | goBack(): void { |
53 | dispatch({ | 63 | dispatch({ |
54 | action: 'back', | 64 | action: 'back', |
diff --git a/packages/shared/src/schemas/Action.ts b/packages/shared/src/schemas/Action.ts index ce983fa..7ece27a 100644 --- a/packages/shared/src/schemas/Action.ts +++ b/packages/shared/src/schemas/Action.ts | |||
@@ -20,7 +20,6 @@ | |||
20 | 20 | ||
21 | import { z } from 'zod'; | 21 | import { z } from 'zod'; |
22 | 22 | ||
23 | import { BrowserViewBounds } from './BrowserViewBounds'; | ||
24 | import { ServiceAction } from './ServiceAction'; | 23 | import { ServiceAction } from './ServiceAction'; |
25 | import { ThemeSource } from './ThemeSource'; | 24 | import { ThemeSource } from './ThemeSource'; |
26 | 25 | ||
@@ -31,10 +30,6 @@ export const Action = /* @__PURE__ */ (() => | |||
31 | serviceId: z.string(), | 30 | serviceId: z.string(), |
32 | }), | 31 | }), |
33 | z.object({ | 32 | z.object({ |
34 | action: z.literal('set-browser-view-bounds'), | ||
35 | browserViewBounds: BrowserViewBounds, | ||
36 | }), | ||
37 | z.object({ | ||
38 | action: z.literal('set-theme-source'), | 33 | action: z.literal('set-theme-source'), |
39 | themeSource: ThemeSource, | 34 | themeSource: ThemeSource, |
40 | }), | 35 | }), |
diff --git a/packages/shared/src/schemas/ServiceAction.ts b/packages/shared/src/schemas/ServiceAction.ts index c0f07d6..9486aaf 100644 --- a/packages/shared/src/schemas/ServiceAction.ts +++ b/packages/shared/src/schemas/ServiceAction.ts | |||
@@ -20,9 +20,15 @@ | |||
20 | 20 | ||
21 | import { z } from 'zod'; | 21 | import { z } from 'zod'; |
22 | 22 | ||
23 | import { BrowserViewBounds } from './BrowserViewBounds'; | ||
24 | |||
23 | export const ServiceAction = /* @__PURE__ */ (() => | 25 | export const ServiceAction = /* @__PURE__ */ (() => |
24 | z.union([ | 26 | z.union([ |
25 | z.object({ | 27 | z.object({ |
28 | action: z.literal('set-browser-view-bounds'), | ||
29 | browserViewBounds: BrowserViewBounds, | ||
30 | }), | ||
31 | z.object({ | ||
26 | action: z.literal('back'), | 32 | action: z.literal('back'), |
27 | }), | 33 | }), |
28 | z.object({ | 34 | z.object({ |
diff --git a/packages/shared/src/stores/SharedStoreBase.ts b/packages/shared/src/stores/SharedStoreBase.ts index e4b3a38..949ef6a 100644 --- a/packages/shared/src/stores/SharedStoreBase.ts +++ b/packages/shared/src/stores/SharedStoreBase.ts | |||
@@ -65,7 +65,10 @@ export function defineSharedStoreModel< | |||
65 | return settings.showLocationBar || self.alwaysShowLocationBar; | 65 | return settings.showLocationBar || self.alwaysShowLocationBar; |
66 | }, | 66 | }, |
67 | get canToggleLocationBar(): boolean { | 67 | get canToggleLocationBar(): boolean { |
68 | return !self.alwaysShowLocationBar; | 68 | return ( |
69 | !self.alwaysShowLocationBar && | ||
70 | self.settings.selectedService !== undefined | ||
71 | ); | ||
69 | }, | 72 | }, |
70 | })); | 73 | })); |
71 | } | 74 | } |