aboutsummaryrefslogtreecommitdiffstats
path: root/packages/renderer
diff options
context:
space:
mode:
Diffstat (limited to 'packages/renderer')
-rw-r--r--packages/renderer/src/components/locationBar/LocationBar.tsx5
-rw-r--r--packages/renderer/src/components/locationBar/LocationTextField.tsx13
-rw-r--r--packages/renderer/src/components/locationBar/SecurityLabel.tsx79
-rw-r--r--packages/renderer/src/components/locationBar/UrlOverlay.tsx61
-rw-r--r--packages/renderer/src/components/locationBar/splitUrl.ts62
-rw-r--r--packages/renderer/src/components/sidebar/ToggleLocationBarButton.tsx12
6 files changed, 107 insertions, 125 deletions
diff --git a/packages/renderer/src/components/locationBar/LocationBar.tsx b/packages/renderer/src/components/locationBar/LocationBar.tsx
index fc9c147..54ead8e 100644
--- a/packages/renderer/src/components/locationBar/LocationBar.tsx
+++ b/packages/renderer/src/components/locationBar/LocationBar.tsx
@@ -43,11 +43,12 @@ const LocationBarRoot = styled('header', {
43 43
44function LocationBar(): JSX.Element { 44function LocationBar(): JSX.Element {
45 const { 45 const {
46 settings: { selectedService, showLocationBar }, 46 shared: { locationBarVisible },
47 settings: { selectedService },
47 } = useStore(); 48 } = useStore();
48 49
49 return ( 50 return (
50 <LocationBarRoot id={LOCATION_BAR_ID} hidden={!showLocationBar}> 51 <LocationBarRoot id={LOCATION_BAR_ID} hidden={!locationBarVisible}>
51 <NavigationButtons service={selectedService} /> 52 <NavigationButtons service={selectedService} />
52 <LocationTextField service={selectedService} /> 53 <LocationTextField service={selectedService} />
53 <ExtraButtons service={selectedService} /> 54 <ExtraButtons service={selectedService} />
diff --git a/packages/renderer/src/components/locationBar/LocationTextField.tsx b/packages/renderer/src/components/locationBar/LocationTextField.tsx
index 30953de..85cf794 100644
--- a/packages/renderer/src/components/locationBar/LocationTextField.tsx
+++ b/packages/renderer/src/components/locationBar/LocationTextField.tsx
@@ -20,6 +20,7 @@
20 20
21import FilledInput from '@mui/material/FilledInput'; 21import FilledInput from '@mui/material/FilledInput';
22import { styled } from '@mui/material/styles'; 22import { styled } from '@mui/material/styles';
23import { SecurityLabelKind } from '@sophie/shared';
23import { autorun } from 'mobx'; 24import { autorun } from 'mobx';
24import { observer } from 'mobx-react-lite'; 25import { observer } from 'mobx-react-lite';
25import React, { useCallback, useEffect, useState } from 'react'; 26import React, { useCallback, useEffect, useState } from 'react';
@@ -30,7 +31,6 @@ import GoButton from './GoButton';
30import LocationOverlayInput from './LocationOverlayInput'; 31import LocationOverlayInput from './LocationOverlayInput';
31import SecurityLabel from './SecurityLabel'; 32import SecurityLabel from './SecurityLabel';
32import UrlOverlay from './UrlOverlay'; 33import UrlOverlay from './UrlOverlay';
33import splitUrl from './splitUrl';
34 34
35const LocationTextFieldRoot = styled(FilledInput, { 35const LocationTextFieldRoot = styled(FilledInput, {
36 name: 'LocationTextField', 36 name: 'LocationTextField',
@@ -75,8 +75,6 @@ function LocationTextField({
75 [setInputFocused], 75 [setInputFocused],
76 ); 76 );
77 77
78 const splitResult = splitUrl(service?.currentUrl);
79
80 return ( 78 return (
81 <LocationTextFieldRoot 79 <LocationTextFieldRoot
82 inputComponent={LocationOverlayInput} 80 inputComponent={LocationOverlayInput}
@@ -84,7 +82,12 @@ function LocationTextField({
84 'aria-label': 'Location', 82 'aria-label': 'Location',
85 spellCheck: false, 83 spellCheck: false,
86 overlayVisible: !inputFocused && !changed, 84 overlayVisible: !inputFocused && !changed,
87 overlay: <UrlOverlay splitResult={splitResult} />, 85 overlay: (
86 <UrlOverlay
87 url={service?.currentUrl ?? ''}
88 alert={service?.hasSecurityLabelWarning ?? false}
89 />
90 ),
88 }} 91 }}
89 inputRef={inputRefCallback} 92 inputRef={inputRefCallback}
90 onFocus={() => setInputFocused(true)} 93 onFocus={() => setInputFocused(true)}
@@ -114,8 +117,8 @@ function LocationTextField({
114 disableUnderline 117 disableUnderline
115 startAdornment={ 118 startAdornment={
116 <SecurityLabel 119 <SecurityLabel
120 kind={service?.securityLabel ?? SecurityLabelKind.Empty}
117 changed={changed} 121 changed={changed}
118 splitResult={splitResult}
119 position="start" 122 position="start"
120 /> 123 />
121 } 124 }
diff --git a/packages/renderer/src/components/locationBar/SecurityLabel.tsx b/packages/renderer/src/components/locationBar/SecurityLabel.tsx
index 0017f89..ac51cff 100644
--- a/packages/renderer/src/components/locationBar/SecurityLabel.tsx
+++ b/packages/renderer/src/components/locationBar/SecurityLabel.tsx
@@ -18,17 +18,17 @@
18 * SPDX-License-Identifier: AGPL-3.0-only 18 * SPDX-License-Identifier: AGPL-3.0-only
19 */ 19 */
20 20
21import IconHttps from '@mui/icons-material/HttpsOutlined'; 21import HttpsOutliedIcon from '@mui/icons-material/HttpsOutlined';
22import IconHttp from '@mui/icons-material/NoEncryption'; 22import NoEncrpytionIcon from '@mui/icons-material/NoEncryption';
23import IconGlobe from '@mui/icons-material/Public'; 23import PublicIcon from '@mui/icons-material/Public';
24import IconWarning from '@mui/icons-material/Warning'; 24import WarningIcon from '@mui/icons-material/Warning';
25import { styled } from '@mui/material/styles'; 25import { styled } from '@mui/material/styles';
26import { SecurityLabelKind } from '@sophie/shared';
26import React from 'react'; 27import React from 'react';
27import { useTranslation } from 'react-i18next'; 28import { useTranslation } from 'react-i18next';
28 29
29import LocationInputAdornment from './LocationInputAdornment'; 30import LocationInputAdornment from './LocationInputAdornment';
30import getAlertColor from './getAlertColor'; 31import getAlertColor from './getAlertColor';
31import type { SplitResult } from './splitUrl';
32 32
33const SecurityLabelRoot = styled(LocationInputAdornment, { 33const SecurityLabelRoot = styled(LocationInputAdornment, {
34 name: 'SecurityLabel', 34 name: 'SecurityLabel',
@@ -53,55 +53,48 @@ const SecurityLabelText = styled('span', {
53})); 53}));
54 54
55export default function SecurityLabel({ 55export default function SecurityLabel({
56 splitResult, 56 kind,
57 changed, 57 changed,
58 position, 58 position,
59}: { 59}: {
60 splitResult: SplitResult; 60 kind: SecurityLabelKind;
61 changed: boolean; 61 changed: boolean;
62 position: 'start' | 'end'; 62 position: 'start' | 'end';
63}): JSX.Element { 63}): JSX.Element {
64 const { t } = useTranslation(undefined, { 64 const { t } = useTranslation();
65 keyPrefix: 'securityLabel',
66 });
67 65
68 const { type } = splitResult; 66 if (changed || kind === SecurityLabelKind.Empty) {
69 if (changed || type === 'empty') {
70 return ( 67 return (
71 <SecurityLabelRoot alert={false} position={position} aria-hidden> 68 <SecurityLabelRoot alert={false} position={position} aria-hidden>
72 <IconGlobe fontSize="small" /> 69 <PublicIcon fontSize="small" />
73 </SecurityLabelRoot> 70 </SecurityLabelRoot>
74 ); 71 );
75 } 72 }
76 switch (type) { 73 if (kind === SecurityLabelKind.SecureConnection) {
77 case 'valid': { 74 return (
78 const { secure } = splitResult; 75 <SecurityLabelRoot
79 return secure ? ( 76 alert={false}
80 <SecurityLabelRoot 77 position={position}
81 alert={false} 78 aria-label={t('securityLabel.secureConnection')}
82 position={position} 79 >
83 aria-label={t('secureConnection')} 80 <HttpsOutliedIcon fontSize="small" />
84 > 81 </SecurityLabelRoot>
85 <IconHttps fontSize="small" /> 82 );
86 </SecurityLabelRoot>
87 ) : (
88 <SecurityLabelRoot alert position={position}>
89 <IconHttp fontSize="small" />
90 <SecurityLabelText>{t('notSecureConnection')}</SecurityLabelText>
91 </SecurityLabelRoot>
92 );
93 }
94 case 'invalid':
95 return (
96 <SecurityLabelRoot alert position={position}>
97 <IconWarning fontSize="small" />
98 <SecurityLabelText>{t('unknownSite')}</SecurityLabelText>
99 </SecurityLabelRoot>
100 );
101 default:
102 /* eslint-disable-next-line @typescript-eslint/restrict-template-expressions --
103 Error handling for impossible case.
104 */
105 throw new Error(`Unexpected SplitResult: ${type}`);
106 } 83 }
84 const tslError =
85 kind === SecurityLabelKind.NotSecureConnection ||
86 kind === SecurityLabelKind.CertificateError;
87 const icon = tslError ? (
88 <NoEncrpytionIcon fontSize="small" />
89 ) : (
90 <WarningIcon fontSize="small" />
91 );
92 return (
93 <SecurityLabelRoot alert position={position}>
94 {icon}
95 <SecurityLabelText>
96 {t([`securityLabel.${kind}`, 'securityLabel.unspecific'])}
97 </SecurityLabelText>
98 </SecurityLabelRoot>
99 );
107} 100}
diff --git a/packages/renderer/src/components/locationBar/UrlOverlay.tsx b/packages/renderer/src/components/locationBar/UrlOverlay.tsx
index d590709..a71fa4e 100644
--- a/packages/renderer/src/components/locationBar/UrlOverlay.tsx
+++ b/packages/renderer/src/components/locationBar/UrlOverlay.tsx
@@ -22,7 +22,47 @@ import { styled } from '@mui/material/styles';
22import React from 'react'; 22import React from 'react';
23 23
24import getAlertColor from './getAlertColor'; 24import getAlertColor from './getAlertColor';
25import type { SplitResult } from './splitUrl'; 25
26export type SplitResult =
27 | {
28 type: 'hostOnly';
29 prefix: string;
30 host: string;
31 suffix: string;
32 }
33 | {
34 type: 'wholeString';
35 value: string;
36 }
37 | {
38 type: 'empty';
39 };
40
41function splitUrl(urlString: string | undefined): SplitResult {
42 if (urlString === undefined || urlString === '') {
43 return { type: 'empty' };
44 }
45 let url: URL;
46 try {
47 url = new URL(urlString);
48 } catch {
49 return { type: 'wholeString', value: urlString };
50 }
51 const { protocol, host, username, password, pathname, search, hash } = url;
52 if (host !== '') {
53 return {
54 type: 'hostOnly',
55 prefix: `${protocol}//${
56 username === ''
57 ? ''
58 : `${username}${password === '' ? '' : `:${password}`}@`
59 }`,
60 host,
61 suffix: `${pathname}${search}${hash}`,
62 };
63 }
64 return { type: 'wholeString', value: urlString };
65}
26 66
27const PrimaryFragment = styled('span', { 67const PrimaryFragment = styled('span', {
28 name: 'LocationOverlayInput', 68 name: 'LocationOverlayInput',
@@ -40,28 +80,31 @@ const SecondaryFragment = styled('span', {
40})); 80}));
41 81
42export default function UrlOverlay({ 82export default function UrlOverlay({
43 splitResult, 83 url,
84 alert,
44}: { 85}: {
45 splitResult: SplitResult; 86 url: string | undefined;
87 alert: boolean;
46}): JSX.Element { 88}): JSX.Element {
89 const splitResult = splitUrl(url);
47 const { type } = splitResult; 90 const { type } = splitResult;
48 switch (type) { 91 switch (type) {
49 case 'valid': { 92 case 'hostOnly': {
50 const { secure, prefix, host, suffix } = splitResult; 93 const { prefix, host, suffix } = splitResult;
51 return ( 94 return (
52 <> 95 <>
53 <SecondaryFragment>{prefix}</SecondaryFragment> 96 <SecondaryFragment>{prefix}</SecondaryFragment>
54 <PrimaryFragment alert={!secure}>{host}</PrimaryFragment> 97 <PrimaryFragment alert={alert}>{host}</PrimaryFragment>
55 <SecondaryFragment>{suffix}</SecondaryFragment> 98 <SecondaryFragment>{suffix}</SecondaryFragment>
56 </> 99 </>
57 ); 100 );
58 } 101 }
59 case 'invalid': { 102 case 'wholeString': {
60 const { value } = splitResult; 103 const { value } = splitResult;
61 return <PrimaryFragment alert>{value}</PrimaryFragment>; 104 return <PrimaryFragment alert={alert}>{value}</PrimaryFragment>;
62 } 105 }
63 case 'empty': 106 case 'empty':
64 return <PrimaryFragment alert={false} />; 107 return <PrimaryFragment alert={alert} />;
65 default: 108 default:
66 /* eslint-disable-next-line @typescript-eslint/restrict-template-expressions -- 109 /* eslint-disable-next-line @typescript-eslint/restrict-template-expressions --
67 Error handling for impossible case. 110 Error handling for impossible case.
diff --git a/packages/renderer/src/components/locationBar/splitUrl.ts b/packages/renderer/src/components/locationBar/splitUrl.ts
deleted file mode 100644
index 6e95472..0000000
--- a/packages/renderer/src/components/locationBar/splitUrl.ts
+++ /dev/null
@@ -1,62 +0,0 @@
1/*
2 * Copyright (C) 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
21export type SplitResult =
22 | {
23 type: 'valid';
24 secure: boolean;
25 prefix: string;
26 host: string;
27 suffix: string;
28 }
29 | {
30 type: 'invalid';
31 value: string;
32 }
33 | {
34 type: 'empty';
35 };
36
37export default function splitUrl(urlString: string | undefined): SplitResult {
38 if (urlString === undefined || urlString === '') {
39 return { type: 'empty' };
40 }
41 let url: URL;
42 try {
43 url = new URL(urlString);
44 } catch {
45 return { type: 'invalid', value: urlString };
46 }
47 const { protocol, host, username, password, pathname, search, hash } = url;
48 if (protocol === 'http:' || protocol === 'https:') {
49 return {
50 type: 'valid',
51 secure: protocol === 'https:',
52 prefix: `${protocol}//${
53 username === ''
54 ? ''
55 : `${username}${password === '' ? '' : `:${password}`}@`
56 }`,
57 host,
58 suffix: `${pathname}${search}${hash}`,
59 };
60 }
61 return { type: 'invalid', value: urlString };
62}
diff --git a/packages/renderer/src/components/sidebar/ToggleLocationBarButton.tsx b/packages/renderer/src/components/sidebar/ToggleLocationBarButton.tsx
index 325160e..7fc559d 100644
--- a/packages/renderer/src/components/sidebar/ToggleLocationBarButton.tsx
+++ b/packages/renderer/src/components/sidebar/ToggleLocationBarButton.tsx
@@ -47,19 +47,23 @@ function ToggleLocationBarIcon({
47 47
48function ToggleLocationBarButton(): JSX.Element { 48function ToggleLocationBarButton(): JSX.Element {
49 const { t } = useTranslation(); 49 const { t } = useTranslation();
50 const { settings } = useStore(); 50 const {
51 const { selectedService, showLocationBar } = settings; 51 shared: { locationBarVisible, canToggleLocationBar },
52 settings,
53 } = useStore();
54 const { selectedService } = settings;
52 55
53 return ( 56 return (
54 <IconButton 57 <IconButton
55 aria-pressed={showLocationBar} 58 disabled={!canToggleLocationBar}
59 aria-pressed={locationBarVisible}
56 aria-controls={LOCATION_BAR_ID} 60 aria-controls={LOCATION_BAR_ID}
57 aria-label={t('toolbar.toggleLocationBar')} 61 aria-label={t('toolbar.toggleLocationBar')}
58 onClick={() => settings.toggleLocationBar()} 62 onClick={() => settings.toggleLocationBar()}
59 > 63 >
60 <ToggleLocationBarIcon 64 <ToggleLocationBarIcon
61 loading={selectedService?.loading ?? false} 65 loading={selectedService?.loading ?? false}
62 show={showLocationBar} 66 show={locationBarVisible}
63 /> 67 />
64 </IconButton> 68 </IconButton>
65 ); 69 );