aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2022-03-04 02:01:20 +0100
committerLibravatar Kristóf Marussy <kristof@marussy.com>2022-03-06 18:56:49 +0100
commitfc048a5fbef87deac4f36c63e71f969bc6301bee (patch)
tree9e2a3158f52d92d8b099d5b47630513d8257ff16
parentfeat: Handle service load failures (diff)
downloadsophie-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>
-rw-r--r--packages/renderer/src/components/locationBar/ButtonAdornment.tsx69
-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.tsx14
-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.tsx3
-rw-r--r--packages/renderer/src/components/locationBar/getAlertColor.ts28
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
21import InputAdornment from '@mui/material/InputAdornment';
22import { styled } from '@mui/material/styles';
23
24export const NO_LABEL_BUTTON_CLASS_NAME = 'ButtonAdornment-NoLabel';
25
26const 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
69export 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
21import IconGo from '@mui/icons-material/Send'; 21import IconGo from '@mui/icons-material/Send';
22import Button from '@mui/material/Button'; 22import IconButton from '@mui/material/IconButton';
23import React, { MouseEventHandler } from 'react'; 23import React, { MouseEventHandler } from 'react';
24 24
25import ButtonAdornment, { NO_LABEL_BUTTON_CLASS_NAME } from './ButtonAdornment'; 25import LocationInputAdornment from './LocationInputAdornment';
26 26
27export default function GoAdornment({ 27export 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 @@
21import InputAdornment from '@mui/material/InputAdornment'; 21import InputAdornment from '@mui/material/InputAdornment';
22import { styled } from '@mui/material/styles'; 22import { styled } from '@mui/material/styles';
23 23
24const IconAdornment = styled(InputAdornment, { 24const 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
41export default IconAdornment; 41export 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
27import Service from '../../stores/Service'; 27import Service from '../../stores/Service';
28 28
29import GoAdornment from './GoAdornment'; 29import GoButton from './GoButton';
30import LocationOverlayInput from './LocationOverlayInput'; 30import LocationOverlayInput from './LocationOverlayInput';
31import UrlAdornment from './UrlAdornment'; 31import SecurityLabel from './SecurityLabel';
32import UrlOverlay from './UrlOverlay'; 32import UrlOverlay from './UrlOverlay';
33import splitUrl from './splitUrl'; 33import 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
21import IconHttps from '@mui/icons-material/HttpsOutlined'; 21import IconHttps from '@mui/icons-material/HttpsOutlined';
22import IconHttp from '@mui/icons-material/NoEncryption';
22import IconGlobe from '@mui/icons-material/Public'; 23import IconGlobe from '@mui/icons-material/Public';
23import IconWarning from '@mui/icons-material/Warning'; 24import IconWarning from '@mui/icons-material/Warning';
24import { styled } from '@mui/material'; 25import { styled } from '@mui/material/styles';
25import Button from '@mui/material/Button';
26import React from 'react'; 26import React from 'react';
27 27
28import ButtonAdornment, { NO_LABEL_BUTTON_CLASS_NAME } from './ButtonAdornment'; 28import LocationInputAdornment from './LocationInputAdornment';
29import IconAdornment from './IconAdornment'; 29import getAlertColor from './getAlertColor';
30import type { SplitResult } from './splitUrl'; 30import type { SplitResult } from './splitUrl';
31 31
32const FastColorChangingButton = styled(Button)(({ theme }) => ({ 32const 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
42export default function UrlAdornment({ 43const 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
54export 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 @@
21import { styled } from '@mui/material/styles'; 21import { styled } from '@mui/material/styles';
22import React from 'react'; 22import React from 'react';
23 23
24import getAlertColor from './getAlertColor';
24import type { SplitResult } from './splitUrl'; 25import type { SplitResult } from './splitUrl';
25 26
26const PrimaryFragment = styled('span', { 27const 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
34const SecondaryFragment = styled('span', { 35const 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
21import { Theme } from '@mui/material/styles';
22
23export 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}