aboutsummaryrefslogtreecommitdiffstats
path: root/packages/renderer
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2022-03-28 23:37:15 +0200
committerLibravatar Kristóf Marussy <kristof@marussy.com>2022-05-16 00:54:56 +0200
commit04555cc62c9cded08c3090288fa372d961c50737 (patch)
tree4566893892216446dfe24490c98881316b97cb41 /packages/renderer
parentdesign: Increase location bar UI density (diff)
downloadsophie-04555cc62c9cded08c3090288fa372d961c50737.tar.gz
sophie-04555cc62c9cded08c3090288fa372d961c50737.tar.zst
sophie-04555cc62c9cded08c3090288fa372d961c50737.zip
feat: New window banner
* Add renderer code for notification banners with buttons * Handle new window open requests by denying them and displaying a notification Signed-off-by: Kristóf Marussy <kristof@marussy.com>
Diffstat (limited to 'packages/renderer')
-rw-r--r--packages/renderer/src/components/App.tsx2
-rw-r--r--packages/renderer/src/components/NewWindowBanner.tsx110
-rw-r--r--packages/renderer/src/components/NotificationBanner.tsx99
-rw-r--r--packages/renderer/src/stores/Service.ts28
4 files changed, 239 insertions, 0 deletions
diff --git a/packages/renderer/src/components/App.tsx b/packages/renderer/src/components/App.tsx
index b647a80..49c50af 100644
--- a/packages/renderer/src/components/App.tsx
+++ b/packages/renderer/src/components/App.tsx
@@ -24,6 +24,7 @@ import { observer } from 'mobx-react-lite';
24import React, { useCallback } from 'react'; 24import React, { useCallback } from 'react';
25 25
26import BrowserViewPlaceholder from './BrowserViewPlaceholder'; 26import BrowserViewPlaceholder from './BrowserViewPlaceholder';
27import NewWindowBanner from './NewWindowBanner';
27import { useStore } from './StoreProvider'; 28import { useStore } from './StoreProvider';
28import LocationBar from './locationBar/LocationBar'; 29import LocationBar from './locationBar/LocationBar';
29import Sidebar from './sidebar/Sidebar'; 30import Sidebar from './sidebar/Sidebar';
@@ -77,6 +78,7 @@ function App(): JSX.Element {
77 }} 78 }}
78 > 79 >
79 <LocationBar /> 80 <LocationBar />
81 <NewWindowBanner service={selectedService} />
80 <BrowserViewPlaceholder> 82 <BrowserViewPlaceholder>
81 <p>{JSON.stringify(selectedService?.state)}</p> 83 <p>{JSON.stringify(selectedService?.state)}</p>
82 {selectedService?.state.type === 'certificateError' && ( 84 {selectedService?.state.type === 'certificateError' && (
diff --git a/packages/renderer/src/components/NewWindowBanner.tsx b/packages/renderer/src/components/NewWindowBanner.tsx
new file mode 100644
index 0000000..a49b4b1
--- /dev/null
+++ b/packages/renderer/src/components/NewWindowBanner.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 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';
26
27import type Service from '../stores/Service';
28
29import NotificationBanner from './NotificationBanner';
30
31function NewWindowBanner({
32 service,
33}: {
34 service: Service | undefined;
35}): JSX.Element | null {
36 if (service === undefined) {
37 // eslint-disable-next-line unicorn/no-null -- React requires `null` to skip rendering.
38 return null;
39 }
40
41 const {
42 popups,
43 settings: { name },
44 } = service;
45 const { length: count } = popups;
46
47 if (count === 0) {
48 // eslint-disable-next-line unicorn/no-null -- React requires `null` to skip rendering.
49 return null;
50 }
51
52 const url = popups[count - 1];
53
54 return (
55 <NotificationBanner
56 severity="warning"
57 icon={<IconOpenInNew fontSize="inherit" />}
58 onClose={() => service.dismissAllPopups()}
59 buttons={
60 <>
61 <Button
62 onClick={() => service.followPopup(url)}
63 color="inherit"
64 size="small"
65 >
66 Follow link in this window
67 </Button>
68 <Button
69 onClick={() => service.openPopupInExternalBrowser(url)}
70 color="inherit"
71 size="small"
72 startIcon={<IconOpenInBrowser />}
73 >
74 Open in browser
75 </Button>
76 {count > 1 && (
77 <>
78 <Button
79 onClick={() => service.openAllPopupsInExternalBrowser()}
80 color="inherit"
81 size="small"
82 startIcon={<IconOpenInBrowser />}
83 >
84 Open all
85 </Button>
86 <Button
87 onClick={() => service.dismissPopup(url)}
88 color="inherit"
89 size="small"
90 >
91 Ignore
92 </Button>
93 </>
94 )}
95 </>
96 }
97 >
98 {name} wants to open <b>{url}</b>{' '}
99 {count === 1 ? (
100 <>in a new window</>
101 ) : (
102 <>
103 and <b>{count}</b> other links in new windows
104 </>
105 )}
106 </NotificationBanner>
107 );
108}
109
110export default observer(NewWindowBanner);
diff --git a/packages/renderer/src/components/NotificationBanner.tsx b/packages/renderer/src/components/NotificationBanner.tsx
new file mode 100644
index 0000000..d591e14
--- /dev/null
+++ b/packages/renderer/src/components/NotificationBanner.tsx
@@ -0,0 +1,99 @@
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';
26
27const NotificationBannerRoot = styled(Alert)(({ theme }) => ({
28 paddingTop: 7,
29 paddingBottom: 6,
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 paddingInlineStart: 0,
44 },
45}));
46
47const NotificationBannerText = styled(Typography)(({ theme }) => ({
48 fontSize: 'inherit',
49 paddingTop: theme.spacing(1),
50 paddingInlineEnd: theme.spacing(2),
51 flexGrow: 9999,
52}));
53
54const NotificationBannerButtons = styled(Box)(({ theme }) => ({
55 fontSize: 'inherit',
56 paddingTop: theme.spacing(0.5),
57 paddingInlineEnd: theme.spacing(0.5),
58 display: 'flex',
59 flexWrap: 'wrap',
60 flexGrow: 1,
61 gap: theme.spacing(1),
62 '.MuiButton-root': {
63 flexGrow: 1,
64 },
65}));
66
67export default function NotificationBanner({
68 severity,
69 icon,
70 onClose,
71 buttons,
72 children,
73}: {
74 severity?: AlertColor;
75 icon?: ReactNode;
76 onClose: () => void;
77 buttons?: ReactNode;
78 children?: ReactNode;
79}): JSX.Element {
80 return (
81 <NotificationBannerRoot
82 severity={severity ?? 'success'}
83 icon={icon ?? false}
84 onClose={onClose}
85 >
86 <NotificationBannerText>{children}</NotificationBannerText>
87 {buttons && (
88 <NotificationBannerButtons>{buttons}</NotificationBannerButtons>
89 )}
90 </NotificationBannerRoot>
91 );
92}
93
94NotificationBanner.defaultProps = {
95 severity: 'success',
96 icon: false,
97 buttons: undefined,
98 children: undefined,
99};
diff --git a/packages/renderer/src/stores/Service.ts b/packages/renderer/src/stores/Service.ts
index c50e5bd..e14d80b 100644
--- a/packages/renderer/src/stores/Service.ts
+++ b/packages/renderer/src/stores/Service.ts
@@ -80,6 +80,34 @@ const Service = defineServiceModel(ServiceSettings).actions((self) => {
80 action: 'open-current-url-in-external-browser', 80 action: 'open-current-url-in-external-browser',
81 }); 81 });
82 }, 82 },
83 followPopup(url: string): void {
84 dispatch({
85 action: 'follow-popup',
86 url,
87 });
88 },
89 openPopupInExternalBrowser(url: string): void {
90 dispatch({
91 action: 'open-popup-in-external-browser',
92 url,
93 });
94 },
95 openAllPopupsInExternalBrowser(): void {
96 dispatch({
97 action: 'open-all-popups-in-external-browser',
98 });
99 },
100 dismissPopup(url: string): void {
101 dispatch({
102 action: 'dismiss-popup',
103 url,
104 });
105 },
106 dismissAllPopups(): void {
107 dispatch({
108 action: 'dismiss-all-popups',
109 });
110 },
83 }; 111 };
84}); 112});
85 113