diff options
Diffstat (limited to 'packages/renderer/src/components')
12 files changed, 137 insertions, 89 deletions
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} |