diff options
author | Kristóf Marussy <kristof@marussy.com> | 2022-03-04 02:01:20 +0100 |
---|---|---|
committer | Kristóf Marussy <kristof@marussy.com> | 2022-03-06 18:56:49 +0100 |
commit | fc048a5fbef87deac4f36c63e71f969bc6301bee (patch) | |
tree | 9e2a3158f52d92d8b099d5b47630513d8257ff16 /packages | |
parent | feat: Handle service load failures (diff) | |
download | sophie-fc048a5fbef87deac4f36c63e71f969bc6301bee.tar.gz sophie-fc048a5fbef87deac4f36c63e71f969bc6301bee.tar.zst sophie-fc048a5fbef87deac4f36c63e71f969bc6301bee.zip |
refactor(renderer): Location bar security label
Since electron doesn't have an API to extract the current certificate,
except in the case of certificate errors, there's no point in making the
security warning / padlock icon clickable.
We instead display a plain icon and a warning message.
We will later display the certificate details in case of a validation
error (when we have access to the certificate) as part of an error
message.
Also makes the security labels more visible to people with protanopia
who use the dark mode of the UI.
Signed-off-by: Kristóf Marussy <kristof@marussy.com>
Diffstat (limited to 'packages')
-rw-r--r-- | packages/renderer/src/components/locationBar/ButtonAdornment.tsx | 69 | ||||
-rw-r--r-- | packages/renderer/src/components/locationBar/GoButton.tsx (renamed from packages/renderer/src/components/locationBar/GoAdornment.tsx) | 26 | ||||
-rw-r--r-- | packages/renderer/src/components/locationBar/LocationInputAdornment.tsx (renamed from packages/renderer/src/components/locationBar/IconAdornment.tsx) | 28 | ||||
-rw-r--r-- | packages/renderer/src/components/locationBar/LocationTextField.tsx | 14 | ||||
-rw-r--r-- | packages/renderer/src/components/locationBar/SecurityLabel.tsx (renamed from packages/renderer/src/components/locationBar/UrlAdornment.tsx) | 77 | ||||
-rw-r--r-- | packages/renderer/src/components/locationBar/UrlOverlay.tsx | 3 | ||||
-rw-r--r-- | packages/renderer/src/components/locationBar/getAlertColor.ts | 28 |
7 files changed, 115 insertions, 130 deletions
diff --git a/packages/renderer/src/components/locationBar/ButtonAdornment.tsx b/packages/renderer/src/components/locationBar/ButtonAdornment.tsx deleted file mode 100644 index 2cf230b..0000000 --- a/packages/renderer/src/components/locationBar/ButtonAdornment.tsx +++ /dev/null | |||
@@ -1,69 +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 | |||
21 | import InputAdornment from '@mui/material/InputAdornment'; | ||
22 | import { styled } from '@mui/material/styles'; | ||
23 | |||
24 | export const NO_LABEL_BUTTON_CLASS_NAME = 'ButtonAdornment-NoLabel'; | ||
25 | |||
26 | const ButtonAdornment = styled(InputAdornment, { | ||
27 | name: 'ButtonAdornment', | ||
28 | })(({ theme, position }) => { | ||
29 | const { direction } = theme; | ||
30 | const left = direction === 'ltr' ? 'start' : 'end'; | ||
31 | return { | ||
32 | ...(position === left | ||
33 | ? { | ||
34 | marginRight: 2, | ||
35 | marginLeft: -8, | ||
36 | } | ||
37 | : { | ||
38 | marginLeft: 2, | ||
39 | marginRight: -8, | ||
40 | }), | ||
41 | '.MuiButton-root': { | ||
42 | minWidth: 32, | ||
43 | height: 32, | ||
44 | paddingLeft: 6, | ||
45 | paddingRight: 6, | ||
46 | borderRadius: 16, | ||
47 | }, | ||
48 | ...(direction === 'ltr' | ||
49 | ? { | ||
50 | '.MuiButton-startIcon': { | ||
51 | marginLeft: 0, | ||
52 | }, | ||
53 | [`.${NO_LABEL_BUTTON_CLASS_NAME} .MuiButton-startIcon`]: { | ||
54 | marginRight: 0, | ||
55 | }, | ||
56 | } | ||
57 | : { | ||
58 | '.MuiButton-startIcon': { | ||
59 | marginRight: 0, | ||
60 | marginLeft: theme.spacing(1), | ||
61 | }, | ||
62 | [`.${NO_LABEL_BUTTON_CLASS_NAME} .MuiButton-startIcon`]: { | ||
63 | marginLeft: 0, | ||
64 | }, | ||
65 | }), | ||
66 | }; | ||
67 | }); | ||
68 | |||
69 | export default ButtonAdornment; | ||
diff --git a/packages/renderer/src/components/locationBar/GoAdornment.tsx b/packages/renderer/src/components/locationBar/GoButton.tsx index f049b8e..32f715e 100644 --- a/packages/renderer/src/components/locationBar/GoAdornment.tsx +++ b/packages/renderer/src/components/locationBar/GoButton.tsx | |||
@@ -19,25 +19,33 @@ | |||
19 | */ | 19 | */ |
20 | 20 | ||
21 | import IconGo from '@mui/icons-material/Send'; | 21 | import IconGo from '@mui/icons-material/Send'; |
22 | import Button from '@mui/material/Button'; | 22 | import IconButton from '@mui/material/IconButton'; |
23 | import React, { MouseEventHandler } from 'react'; | 23 | import React, { MouseEventHandler } from 'react'; |
24 | 24 | ||
25 | import ButtonAdornment, { NO_LABEL_BUTTON_CLASS_NAME } from './ButtonAdornment'; | 25 | import LocationInputAdornment from './LocationInputAdornment'; |
26 | 26 | ||
27 | export default function GoAdornment({ | 27 | export default function GoButton({ |
28 | onClick, | 28 | onClick, |
29 | position, | ||
29 | }: { | 30 | }: { |
30 | onClick: MouseEventHandler<HTMLButtonElement>; | 31 | onClick: MouseEventHandler<HTMLButtonElement>; |
32 | position: 'start' | 'end'; | ||
31 | }): JSX.Element { | 33 | }): JSX.Element { |
32 | return ( | 34 | return ( |
33 | <ButtonAdornment position="end"> | 35 | <LocationInputAdornment position={position}> |
34 | <Button | 36 | <IconButton |
35 | aria-label="Go" | 37 | aria-label="Go" |
36 | color="inherit" | 38 | color="inherit" |
37 | startIcon={<IconGo />} | ||
38 | className={NO_LABEL_BUTTON_CLASS_NAME} | ||
39 | onClick={onClick} | 39 | onClick={onClick} |
40 | /> | 40 | sx={{ |
41 | </ButtonAdornment> | 41 | minWidth: '32px', |
42 | height: '32px', | ||
43 | paddingX: '6px', | ||
44 | borderRadius: '16px', | ||
45 | }} | ||
46 | > | ||
47 | <IconGo fontSize="small" /> | ||
48 | </IconButton> | ||
49 | </LocationInputAdornment> | ||
42 | ); | 50 | ); |
43 | } | 51 | } |
diff --git a/packages/renderer/src/components/locationBar/IconAdornment.tsx b/packages/renderer/src/components/locationBar/LocationInputAdornment.tsx index 1a2fa83..a3cd2c5 100644 --- a/packages/renderer/src/components/locationBar/IconAdornment.tsx +++ b/packages/renderer/src/components/locationBar/LocationInputAdornment.tsx | |||
@@ -21,21 +21,21 @@ | |||
21 | import InputAdornment from '@mui/material/InputAdornment'; | 21 | import InputAdornment from '@mui/material/InputAdornment'; |
22 | import { styled } from '@mui/material/styles'; | 22 | import { styled } from '@mui/material/styles'; |
23 | 23 | ||
24 | const IconAdornment = styled(InputAdornment, { | 24 | const LocationInputAdornment = styled(InputAdornment, { |
25 | name: 'IconAdornment', | 25 | name: 'LocationInputAdornment', |
26 | })(({ theme: { direction }, position }) => { | 26 | })(({ theme: { direction }, position }) => { |
27 | const left = direction === 'ltr' ? 'start' : 'end'; | 27 | const left = direction === 'ltr' ? 'start' : 'end'; |
28 | return { | 28 | const marginStart = -8; |
29 | ...(position === left | 29 | const marginEnd = 2; |
30 | ? { | 30 | return position === left |
31 | marginLeft: -2, | 31 | ? { |
32 | marginRight: 8, | 32 | marginLeft: marginStart, |
33 | } | 33 | marginRight: marginEnd, |
34 | : { | 34 | } |
35 | marginLeft: 8, | 35 | : { |
36 | marginRight: -2, | 36 | marginLeft: marginEnd, |
37 | }), | 37 | marginRight: marginStart, |
38 | }; | 38 | }; |
39 | }); | 39 | }); |
40 | 40 | ||
41 | export default IconAdornment; | 41 | export default LocationInputAdornment; |
diff --git a/packages/renderer/src/components/locationBar/LocationTextField.tsx b/packages/renderer/src/components/locationBar/LocationTextField.tsx index e711abc..3a4f7f5 100644 --- a/packages/renderer/src/components/locationBar/LocationTextField.tsx +++ b/packages/renderer/src/components/locationBar/LocationTextField.tsx | |||
@@ -26,9 +26,9 @@ import React, { useCallback, useEffect, useState } from 'react'; | |||
26 | 26 | ||
27 | import Service from '../../stores/Service'; | 27 | import Service from '../../stores/Service'; |
28 | 28 | ||
29 | import GoAdornment from './GoAdornment'; | 29 | import GoButton from './GoButton'; |
30 | import LocationOverlayInput from './LocationOverlayInput'; | 30 | import LocationOverlayInput from './LocationOverlayInput'; |
31 | import UrlAdornment from './UrlAdornment'; | 31 | import SecurityLabel from './SecurityLabel'; |
32 | import UrlOverlay from './UrlOverlay'; | 32 | import UrlOverlay from './UrlOverlay'; |
33 | import splitUrl from './splitUrl'; | 33 | import splitUrl from './splitUrl'; |
34 | 34 | ||
@@ -113,10 +113,16 @@ function LocationTextField({ | |||
113 | hiddenLabel | 113 | hiddenLabel |
114 | disableUnderline | 114 | disableUnderline |
115 | startAdornment={ | 115 | startAdornment={ |
116 | <UrlAdornment changed={changed} splitResult={splitResult} /> | 116 | <SecurityLabel |
117 | changed={changed} | ||
118 | splitResult={splitResult} | ||
119 | position="start" | ||
120 | /> | ||
117 | } | 121 | } |
118 | endAdornment={ | 122 | endAdornment={ |
119 | changed ? <GoAdornment onClick={() => service?.go(value)} /> : undefined | 123 | changed ? ( |
124 | <GoButton onClick={() => service?.go(value)} position="end" /> | ||
125 | ) : undefined | ||
120 | } | 126 | } |
121 | value={value} | 127 | value={value} |
122 | /> | 128 | /> |
diff --git a/packages/renderer/src/components/locationBar/UrlAdornment.tsx b/packages/renderer/src/components/locationBar/SecurityLabel.tsx index 6ede378..6e27e6b 100644 --- a/packages/renderer/src/components/locationBar/UrlAdornment.tsx +++ b/packages/renderer/src/components/locationBar/SecurityLabel.tsx | |||
@@ -19,68 +19,79 @@ | |||
19 | */ | 19 | */ |
20 | 20 | ||
21 | import IconHttps from '@mui/icons-material/HttpsOutlined'; | 21 | import IconHttps from '@mui/icons-material/HttpsOutlined'; |
22 | import IconHttp from '@mui/icons-material/NoEncryption'; | ||
22 | import IconGlobe from '@mui/icons-material/Public'; | 23 | import IconGlobe from '@mui/icons-material/Public'; |
23 | import IconWarning from '@mui/icons-material/Warning'; | 24 | import IconWarning from '@mui/icons-material/Warning'; |
24 | import { styled } from '@mui/material'; | 25 | import { styled } from '@mui/material/styles'; |
25 | import Button from '@mui/material/Button'; | ||
26 | import React from 'react'; | 26 | import React from 'react'; |
27 | 27 | ||
28 | import ButtonAdornment, { NO_LABEL_BUTTON_CLASS_NAME } from './ButtonAdornment'; | 28 | import LocationInputAdornment from './LocationInputAdornment'; |
29 | import IconAdornment from './IconAdornment'; | 29 | import getAlertColor from './getAlertColor'; |
30 | import type { SplitResult } from './splitUrl'; | 30 | import type { SplitResult } from './splitUrl'; |
31 | 31 | ||
32 | const FastColorChangingButton = styled(Button)(({ theme }) => ({ | 32 | const SecurityLabelRoot = styled(LocationInputAdornment, { |
33 | transition: theme.transitions.create( | 33 | name: 'SecurityLabel', |
34 | ['background-color', 'box-shadow', 'border-color'], | 34 | slot: 'Root', |
35 | { | 35 | shouldForwardProp: (prop) => prop !== 'alert', |
36 | duration: theme.transitions.duration.short, | 36 | })<{ alert: boolean }>(({ theme, alert }) => ({ |
37 | easing: theme.transitions.easing.easeInOut, | 37 | padding: 6, |
38 | }, | 38 | color: getAlertColor(theme, alert), |
39 | ), | 39 | // Clicking on the security label should focus the text field instead. |
40 | pointerEvents: 'none', | ||
40 | })); | 41 | })); |
41 | 42 | ||
42 | export default function UrlAdornment({ | 43 | const SecurityLabelText = styled('span', { |
44 | name: 'SecurityLabel', | ||
45 | slot: 'Text', | ||
46 | })(({ theme }) => ({ | ||
47 | marginInlineStart: theme.spacing(1), | ||
48 | // Keep the same baseline as the input box text. | ||
49 | paddingBottom: 1, | ||
50 | fontWeight: theme.typography.fontWeightMedium, | ||
51 | userSelect: 'none', | ||
52 | })); | ||
53 | |||
54 | export default function SecurityLabel({ | ||
43 | splitResult, | 55 | splitResult, |
44 | changed, | 56 | changed, |
57 | position, | ||
45 | }: { | 58 | }: { |
46 | splitResult: SplitResult; | 59 | splitResult: SplitResult; |
47 | changed: boolean; | 60 | changed: boolean; |
61 | position: 'start' | 'end'; | ||
48 | }): JSX.Element { | 62 | }): JSX.Element { |
49 | const { type } = splitResult; | 63 | const { type } = splitResult; |
50 | if (changed || type === 'empty') { | 64 | if (changed || type === 'empty') { |
51 | return ( | 65 | return ( |
52 | <IconAdornment position="start"> | 66 | <SecurityLabelRoot alert={false} position={position} aria-hidden> |
53 | <IconGlobe fontSize="small" /> | 67 | <IconGlobe fontSize="small" /> |
54 | </IconAdornment> | 68 | </SecurityLabelRoot> |
55 | ); | 69 | ); |
56 | } | 70 | } |
57 | switch (type) { | 71 | switch (type) { |
58 | case 'valid': { | 72 | case 'valid': { |
59 | const { secure } = splitResult; | 73 | const { secure } = splitResult; |
60 | return secure ? ( | 74 | return secure ? ( |
61 | <ButtonAdornment position="start"> | 75 | <SecurityLabelRoot |
62 | <FastColorChangingButton | 76 | alert={false} |
63 | aria-label="Show certificate" | 77 | position={position} |
64 | color="inherit" | 78 | aria-label="Secure connection" |
65 | className={NO_LABEL_BUTTON_CLASS_NAME} | 79 | > |
66 | startIcon={<IconHttps />} | 80 | <IconHttps fontSize="small" /> |
67 | /> | 81 | </SecurityLabelRoot> |
68 | </ButtonAdornment> | ||
69 | ) : ( | 82 | ) : ( |
70 | <ButtonAdornment position="start"> | 83 | <SecurityLabelRoot alert position={position}> |
71 | <FastColorChangingButton color="error" startIcon={<IconWarning />}> | 84 | <IconHttp fontSize="small" /> |
72 | Not secure | 85 | <SecurityLabelText>Not secure</SecurityLabelText> |
73 | </FastColorChangingButton> | 86 | </SecurityLabelRoot> |
74 | </ButtonAdornment> | ||
75 | ); | 87 | ); |
76 | } | 88 | } |
77 | case 'invalid': | 89 | case 'invalid': |
78 | return ( | 90 | return ( |
79 | <ButtonAdornment position="start"> | 91 | <SecurityLabelRoot alert position={position}> |
80 | <FastColorChangingButton color="error" startIcon={<IconWarning />}> | 92 | <IconWarning fontSize="small" /> |
81 | Unknown site | 93 | <SecurityLabelText>Unknown site</SecurityLabelText> |
82 | </FastColorChangingButton> | 94 | </SecurityLabelRoot> |
83 | </ButtonAdornment> | ||
84 | ); | 95 | ); |
85 | default: | 96 | default: |
86 | /* eslint-disable-next-line @typescript-eslint/restrict-template-expressions -- | 97 | /* eslint-disable-next-line @typescript-eslint/restrict-template-expressions -- |
diff --git a/packages/renderer/src/components/locationBar/UrlOverlay.tsx b/packages/renderer/src/components/locationBar/UrlOverlay.tsx index f7a3c4c..d590709 100644 --- a/packages/renderer/src/components/locationBar/UrlOverlay.tsx +++ b/packages/renderer/src/components/locationBar/UrlOverlay.tsx | |||
@@ -21,6 +21,7 @@ | |||
21 | import { styled } from '@mui/material/styles'; | 21 | import { styled } from '@mui/material/styles'; |
22 | import React from 'react'; | 22 | import React from 'react'; |
23 | 23 | ||
24 | import getAlertColor from './getAlertColor'; | ||
24 | import type { SplitResult } from './splitUrl'; | 25 | import type { SplitResult } from './splitUrl'; |
25 | 26 | ||
26 | const PrimaryFragment = styled('span', { | 27 | const PrimaryFragment = styled('span', { |
@@ -28,7 +29,7 @@ const PrimaryFragment = styled('span', { | |||
28 | slot: 'PrimaryFragment', | 29 | slot: 'PrimaryFragment', |
29 | shouldForwardProp: (prop) => prop !== 'alert', | 30 | shouldForwardProp: (prop) => prop !== 'alert', |
30 | })<{ alert: boolean }>(({ theme, alert }) => ({ | 31 | })<{ alert: boolean }>(({ theme, alert }) => ({ |
31 | color: alert ? theme.palette.error.main : theme.palette.text.primary, | 32 | color: getAlertColor(theme, alert), |
32 | })); | 33 | })); |
33 | 34 | ||
34 | const SecondaryFragment = styled('span', { | 35 | const SecondaryFragment = styled('span', { |
diff --git a/packages/renderer/src/components/locationBar/getAlertColor.ts b/packages/renderer/src/components/locationBar/getAlertColor.ts new file mode 100644 index 0000000..82b5f55 --- /dev/null +++ b/packages/renderer/src/components/locationBar/getAlertColor.ts | |||
@@ -0,0 +1,28 @@ | |||
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 | |||
21 | import { Theme } from '@mui/material/styles'; | ||
22 | |||
23 | export default function getAlertColor({ palette }: Theme, alert: boolean) { | ||
24 | if (alert) { | ||
25 | return palette.mode === 'dark' ? palette.error.light : palette.error.main; | ||
26 | } | ||
27 | return palette.text.primary; | ||
28 | } | ||