aboutsummaryrefslogtreecommitdiffstats
path: root/packages/renderer/src/components/banner
diff options
context:
space:
mode:
Diffstat (limited to 'packages/renderer/src/components/banner')
-rw-r--r--packages/renderer/src/components/banner/InsecureConnectionBanner.tsx92
-rw-r--r--packages/renderer/src/components/banner/NewWindowBanner.tsx117
-rw-r--r--packages/renderer/src/components/banner/NotificationBanner.tsx110
3 files changed, 319 insertions, 0 deletions
diff --git a/packages/renderer/src/components/banner/InsecureConnectionBanner.tsx b/packages/renderer/src/components/banner/InsecureConnectionBanner.tsx
new file mode 100644
index 0000000..7a03fce
--- /dev/null
+++ b/packages/renderer/src/components/banner/InsecureConnectionBanner.tsx
@@ -0,0 +1,92 @@
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 CableIcon from '@mui/icons-material/Cable';
22import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline';
23import HomeIcon from '@mui/icons-material/HomeOutlined';
24import Button from '@mui/material/Button';
25import { SecurityLabelKind } from '@sophie/shared';
26import { observer } from 'mobx-react-lite';
27import React from 'react';
28import { useTranslation } from 'react-i18next';
29
30import type Service from '../../stores/Service';
31
32import NotificationBanner from './NotificationBanner';
33
34function InsecureConnectionBanner({
35 service,
36}: {
37 service: Service | undefined;
38}): JSX.Element | null {
39 const { t } = useTranslation(undefined, {
40 keyPrefix: 'banner.insecureConnection',
41 });
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 {
49 canReconnectSecurely,
50 hasError,
51 securityLabel,
52 settings: { name: serviceName },
53 } = service;
54
55 if (securityLabel !== SecurityLabelKind.NotSecureConnection || hasError) {
56 // eslint-disable-next-line unicorn/no-null -- React requires `null` to skip rendering.
57 return null;
58 }
59
60 return (
61 <NotificationBanner
62 severity="error"
63 icon={<ErrorOutlineIcon fontSize="inherit" />}
64 buttons={
65 <>
66 <Button
67 onClick={() => service.goHome()}
68 color="inherit"
69 size="small"
70 startIcon={<HomeIcon />}
71 >
72 {t('home', { serviceName })}
73 </Button>
74 {canReconnectSecurely && (
75 <Button
76 onClick={() => service.reconnectSecurely()}
77 color="inherit"
78 size="small"
79 startIcon={<CableIcon />}
80 >
81 {t('reconnectSecurely')}
82 </Button>
83 )}
84 </>
85 }
86 >
87 {t('message')}
88 </NotificationBanner>
89 );
90}
91
92export default observer(InsecureConnectionBanner);
diff --git a/packages/renderer/src/components/banner/NewWindowBanner.tsx b/packages/renderer/src/components/banner/NewWindowBanner.tsx
new file mode 100644
index 0000000..478de8e
--- /dev/null
+++ b/packages/renderer/src/components/banner/NewWindowBanner.tsx
@@ -0,0 +1,117 @@
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 IconOpenInBrowser from '@mui/icons-material/OpenInBrowser';
22import IconOpenInNew from '@mui/icons-material/OpenInNew';
23import Button from '@mui/material/Button';
24import { observer } from 'mobx-react-lite';
25import React from 'react';
26import { Trans, useTranslation } from 'react-i18next';
27
28import type Service from '../../stores/Service';
29
30import NotificationBanner from './NotificationBanner';
31
32function NewWindowBanner({
33 service,
34}: {
35 service: Service | undefined;
36}): JSX.Element | null {
37 const { t } = useTranslation(undefined, {
38 keyPrefix: 'banner.newWindow',
39 });
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 {
47 popups,
48 settings: { name },
49 } = service;
50 const { length: count } = popups;
51
52 if (count === 0) {
53 // eslint-disable-next-line unicorn/no-null -- React requires `null` to skip rendering.
54 return null;
55 }
56
57 const url = popups[count - 1];
58
59 return (
60 <NotificationBanner
61 severity="warning"
62 icon={<IconOpenInNew fontSize="inherit" />}
63 onClose={() => service.dismissAllPopups()}
64 buttons={
65 <>
66 <Button
67 onClick={() => service.followPopup(url)}
68 color="inherit"
69 size="small"
70 >
71 {t('followLink')}
72 </Button>
73 <Button
74 onClick={() => service.openPopupInExternalBrowser(url)}
75 color="inherit"
76 size="small"
77 startIcon={<IconOpenInBrowser />}
78 >
79 {t('openInExternalBrowser')}
80 </Button>
81 {count > 1 && (
82 <>
83 <Button
84 onClick={() => service.openAllPopupsInExternalBrowser()}
85 color="inherit"
86 size="small"
87 startIcon={<IconOpenInBrowser />}
88 >
89 {t('openAllInExternalBrowser')}
90 </Button>
91 <Button
92 onClick={() => service.dismissPopup(url)}
93 color="inherit"
94 size="small"
95 >
96 {t('dismiss')}
97 </Button>
98 </>
99 )}
100 </>
101 }
102 >
103 {count === 1 ? (
104 <Trans i18nKey="messageSingleLink" t={t}>
105 {{ name }} wants to open <strong>{{ url }}</strong> in a new window
106 </Trans>
107 ) : (
108 <Trans i18nKey="messageMultipleLinks" count={count - 1} t={t}>
109 {{ name }} wants to open <strong>{{ url }}</strong> and{' '}
110 <strong>{{ count: count - 1 }}</strong> other links in new windows
111 </Trans>
112 )}
113 </NotificationBanner>
114 );
115}
116
117export default observer(NewWindowBanner);
diff --git a/packages/renderer/src/components/banner/NotificationBanner.tsx b/packages/renderer/src/components/banner/NotificationBanner.tsx
new file mode 100644
index 0000000..818f498
--- /dev/null
+++ b/packages/renderer/src/components/banner/NotificationBanner.tsx
@@ -0,0 +1,110 @@
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 { Typography } from '@mui/material';
22import Alert, { AlertColor } from '@mui/material/Alert';
23import Box from '@mui/material/Box';
24import { styled } from '@mui/material/styles';
25import React, { ReactNode } from 'react';
26import { useTranslation } from 'react-i18next';
27
28const NotificationBannerRoot = styled(Alert)(({ theme }) => ({
29 padding: `7px ${theme.spacing(1)} 6px ${theme.spacing(2)}`,
30 // Match the height of the location bar.
31 minHeight: 53,
32 borderRadius: 0,
33 borderBottom: `1px solid ${theme.palette.divider}`,
34 '.MuiAlert-message': {
35 flexGrow: 1,
36 paddingTop: 0,
37 paddingBottom: 4,
38 display: 'flex',
39 flexWrap: 'wrap',
40 justifyContent: 'center',
41 },
42 '.MuiAlert-action': {
43 paddingLeft: 0,
44 paddingRight: theme.spacing(1),
45 },
46}));
47
48const NotificationBannerText = styled(Typography)(({ theme }) => ({
49 fontSize: 'inherit',
50 paddingTop: theme.spacing(1),
51 paddingRight: theme.spacing(2),
52 flexGrow: 9999,
53}));
54
55const NotificationBannerButtons = styled(Box, {
56 shouldForwardProp: (prop) => prop !== 'hasCloseButton',
57})<{ hasCloseButton: boolean }>(({ theme, hasCloseButton }) => ({
58 fontSize: 'inherit',
59 paddingTop: theme.spacing(0.5),
60 paddingRight: hasCloseButton ? theme.spacing(0.5) : 0,
61 display: 'flex',
62 flexWrap: 'wrap',
63 flexGrow: 1,
64 gap: theme.spacing(1),
65 '.MuiButton-root': {
66 flexGrow: 1,
67 },
68}));
69
70export default function NotificationBanner({
71 severity,
72 icon,
73 onClose,
74 buttons,
75 children,
76}: {
77 severity?: AlertColor;
78 icon?: ReactNode;
79 onClose?: () => void;
80 buttons?: ReactNode;
81 children?: ReactNode;
82}): JSX.Element {
83 const { t } = useTranslation();
84
85 return (
86 /* eslint-disable react/jsx-props-no-spreading -- Conditionally set the onClose prop. */
87 <NotificationBannerRoot
88 severity={severity ?? 'success'}
89 icon={icon ?? false}
90 {...(onClose === undefined ? {} : { onClose })}
91 closeText={t<string>('banner.close')}
92 >
93 {/* eslint-enable react/jsx-props-no-spreading */}
94 <NotificationBannerText>{children}</NotificationBannerText>
95 {buttons && (
96 <NotificationBannerButtons hasCloseButton={onClose !== undefined}>
97 {buttons}
98 </NotificationBannerButtons>
99 )}
100 </NotificationBannerRoot>
101 );
102}
103
104NotificationBanner.defaultProps = {
105 severity: 'success',
106 icon: false,
107 onClose: undefined,
108 buttons: undefined,
109 children: undefined,
110};