diff options
author | Kristóf Marussy <kristof@marussy.com> | 2021-12-23 21:29:26 +0100 |
---|---|---|
committer | Kristóf Marussy <kristof@marussy.com> | 2021-12-23 21:29:26 +0100 |
commit | d303f2e3415237e1a519db21ad4e089c2ba7e9f9 (patch) | |
tree | 5d562dcaf7bb9c83c1930c7d7cf1b2c9de75e15b /packages/renderer/src | |
parent | build: Enable asar (diff) | |
download | sophie-d303f2e3415237e1a519db21ad4e089c2ba7e9f9.tar.gz sophie-d303f2e3415237e1a519db21ad4e089c2ba7e9f9.tar.zst sophie-d303f2e3415237e1a519db21ad4e089c2ba7e9f9.zip |
feat: Add BrowserView and synchronize its position
Diffstat (limited to 'packages/renderer/src')
-rw-r--r-- | packages/renderer/src/components/App.tsx | 42 | ||||
-rw-r--r-- | packages/renderer/src/components/BrowserViewPlaceholder.tsx | 128 | ||||
-rw-r--r-- | packages/renderer/src/components/Sidebar.tsx | 42 | ||||
-rw-r--r-- | packages/renderer/src/components/ThemeProvider.tsx | 4 | ||||
-rw-r--r-- | packages/renderer/src/components/ToggleDarkModeButton.tsx | 40 | ||||
-rw-r--r-- | packages/renderer/src/index.tsx | 20 | ||||
-rw-r--r-- | packages/renderer/src/stores/RootStore.ts | 26 |
7 files changed, 278 insertions, 24 deletions
diff --git a/packages/renderer/src/components/App.tsx b/packages/renderer/src/components/App.tsx new file mode 100644 index 0000000..b627fa7 --- /dev/null +++ b/packages/renderer/src/components/App.tsx | |||
@@ -0,0 +1,42 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2021-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 Box from '@mui/material/Box'; | ||
22 | import React from 'react'; | ||
23 | |||
24 | import { BrowserViewPlaceholder } from './BrowserViewPlaceholder'; | ||
25 | import { Sidebar } from './Sidebar'; | ||
26 | |||
27 | export function App(): JSX.Element { | ||
28 | return ( | ||
29 | <Box | ||
30 | sx={{ | ||
31 | display: 'flex', | ||
32 | flexDirection: 'row', | ||
33 | alignItems: 'stretch', | ||
34 | height: '100vh', | ||
35 | width: '100vw', | ||
36 | }} | ||
37 | > | ||
38 | <Sidebar /> | ||
39 | <BrowserViewPlaceholder /> | ||
40 | </Box> | ||
41 | ) | ||
42 | } | ||
diff --git a/packages/renderer/src/components/BrowserViewPlaceholder.tsx b/packages/renderer/src/components/BrowserViewPlaceholder.tsx new file mode 100644 index 0000000..06dc7fe --- /dev/null +++ b/packages/renderer/src/components/BrowserViewPlaceholder.tsx | |||
@@ -0,0 +1,128 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2021-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 { throttle } from 'lodash'; | ||
22 | import { observer } from 'mobx-react-lite'; | ||
23 | import Box from '@mui/material/Box'; | ||
24 | import React, { | ||
25 | useCallback, | ||
26 | useEffect, | ||
27 | useRef, | ||
28 | useState, | ||
29 | } from 'react'; | ||
30 | import type { BrowserViewBounds } from '@sophie/shared'; | ||
31 | |||
32 | import { useStore } from './StoreProvider'; | ||
33 | |||
34 | export const BrowserViewPlaceholder = observer(function BrowserViewPlaceholder() { | ||
35 | const { | ||
36 | shared: { | ||
37 | browserViewBounds: { | ||
38 | x: storeX, | ||
39 | y: storeY, | ||
40 | width: storeWidth, | ||
41 | height: storeHeight, | ||
42 | }, | ||
43 | }, | ||
44 | setBrowserViewBounds, | ||
45 | } = useStore(); | ||
46 | |||
47 | const [ | ||
48 | { | ||
49 | x: currentX, | ||
50 | y: currentY, | ||
51 | width: currentWidth, | ||
52 | height: currentHeight, | ||
53 | }, | ||
54 | setBounds, | ||
55 | ] = useState<BrowserViewBounds>({ | ||
56 | x: 0, | ||
57 | y: 0, | ||
58 | width: 0, | ||
59 | height: 0, | ||
60 | }); | ||
61 | |||
62 | useEffect(() => { | ||
63 | if (storeX !== currentX | ||
64 | || storeY !== currentY | ||
65 | || storeWidth !== currentWidth | ||
66 | || storeHeight !== currentHeight) { | ||
67 | setBrowserViewBounds({ | ||
68 | x: currentX, | ||
69 | y: currentY, | ||
70 | width: currentWidth, | ||
71 | height: currentHeight, | ||
72 | }); | ||
73 | } | ||
74 | }, [ | ||
75 | storeX, | ||
76 | storeY, | ||
77 | storeWidth, | ||
78 | storeHeight, | ||
79 | setBrowserViewBounds, | ||
80 | currentX, | ||
81 | currentY, | ||
82 | currentWidth, | ||
83 | currentHeight, | ||
84 | ]); | ||
85 | |||
86 | const onResize = useCallback(throttle(([boxEntry]: ResizeObserverEntry[]) => { | ||
87 | if (boxEntry) { | ||
88 | const { | ||
89 | x, | ||
90 | y, | ||
91 | width, | ||
92 | height, | ||
93 | } = boxEntry.target.getBoundingClientRect(); | ||
94 | setBounds({ | ||
95 | x, | ||
96 | y, | ||
97 | width, | ||
98 | height, | ||
99 | }); | ||
100 | } | ||
101 | }, 100), [setBounds]); | ||
102 | |||
103 | const resizeObserverRef = useRef<ResizeObserver | null>(null); | ||
104 | |||
105 | const ref = useCallback((box: HTMLElement | null) => { | ||
106 | if (resizeObserverRef.current !== null) { | ||
107 | resizeObserverRef.current.disconnect(); | ||
108 | } | ||
109 | if (box === null) { | ||
110 | resizeObserverRef.current = null; | ||
111 | return; | ||
112 | } | ||
113 | resizeObserverRef.current = new ResizeObserver(onResize); | ||
114 | resizeObserverRef.current.observe(box); | ||
115 | }, [onResize, resizeObserverRef]); | ||
116 | |||
117 | return ( | ||
118 | <Box | ||
119 | sx={{ | ||
120 | flex: 1, | ||
121 | // Workaround: display a plain white background if we fail to set the BrowserView background color. | ||
122 | // https://github.com/electron/electron/issues/31019 | ||
123 | background: '#fff', | ||
124 | }} | ||
125 | ref={ref} | ||
126 | /> | ||
127 | ) | ||
128 | }); | ||
diff --git a/packages/renderer/src/components/Sidebar.tsx b/packages/renderer/src/components/Sidebar.tsx new file mode 100644 index 0000000..6c79932 --- /dev/null +++ b/packages/renderer/src/components/Sidebar.tsx | |||
@@ -0,0 +1,42 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2021-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 Box from '@mui/material/Box'; | ||
22 | import React from 'react'; | ||
23 | |||
24 | import { ToggleDarkModeButton } from './ToggleDarkModeButton'; | ||
25 | |||
26 | export function Sidebar(): JSX.Element { | ||
27 | return ( | ||
28 | <Box | ||
29 | sx={(theme) => ({ | ||
30 | background: theme.palette.divider, | ||
31 | flex: 0, | ||
32 | display: 'flex', | ||
33 | flexDirection: 'column', | ||
34 | alignItems: 'center', | ||
35 | justifyContent: 'flex-end', | ||
36 | padding: 1, | ||
37 | })} | ||
38 | > | ||
39 | <ToggleDarkModeButton /> | ||
40 | </Box> | ||
41 | ); | ||
42 | } | ||
diff --git a/packages/renderer/src/components/ThemeProvider.tsx b/packages/renderer/src/components/ThemeProvider.tsx index 7173a9d..9215f5c 100644 --- a/packages/renderer/src/components/ThemeProvider.tsx +++ b/packages/renderer/src/components/ThemeProvider.tsx | |||
@@ -27,9 +27,9 @@ import React from 'react'; | |||
27 | 27 | ||
28 | import { useStore } from './StoreProvider'; | 28 | import { useStore } from './StoreProvider'; |
29 | 29 | ||
30 | export const ThemeProvider = observer(({ children }: { | 30 | export const ThemeProvider = observer(function ThemeProvider({ children }: { |
31 | children: JSX.Element | JSX.Element[], | 31 | children: JSX.Element | JSX.Element[], |
32 | }): JSX.Element => { | 32 | }) { |
33 | const { shared: { shouldUseDarkColors } } = useStore(); | 33 | const { shared: { shouldUseDarkColors } } = useStore(); |
34 | 34 | ||
35 | const theme = createTheme({ | 35 | const theme = createTheme({ |
diff --git a/packages/renderer/src/components/ToggleDarkModeButton.tsx b/packages/renderer/src/components/ToggleDarkModeButton.tsx new file mode 100644 index 0000000..1b6757e --- /dev/null +++ b/packages/renderer/src/components/ToggleDarkModeButton.tsx | |||
@@ -0,0 +1,40 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2021-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 { observer } from 'mobx-react-lite'; | ||
22 | import DarkModeIcon from '@mui/icons-material/DarkMode'; | ||
23 | import LightModeIcon from '@mui/icons-material/LightMode'; | ||
24 | import IconButton from '@mui/material/IconButton'; | ||
25 | import React from 'react'; | ||
26 | |||
27 | import { useStore } from './StoreProvider'; | ||
28 | |||
29 | export const ToggleDarkModeButton = observer(function ToggleDarkModeButton() { | ||
30 | const { shared: { shouldUseDarkColors }, toggleDarkMode } = useStore(); | ||
31 | |||
32 | return ( | ||
33 | <IconButton | ||
34 | aria-label="Toggle dark mode" | ||
35 | onClick={() => toggleDarkMode()} | ||
36 | > | ||
37 | {shouldUseDarkColors ? <LightModeIcon /> : <DarkModeIcon />} | ||
38 | </IconButton> | ||
39 | ); | ||
40 | }); | ||
diff --git a/packages/renderer/src/index.tsx b/packages/renderer/src/index.tsx index 901d137..90cba2c 100644 --- a/packages/renderer/src/index.tsx +++ b/packages/renderer/src/index.tsx | |||
@@ -22,13 +22,12 @@ import '@fontsource/roboto/300.css'; | |||
22 | import '@fontsource/roboto/400.css'; | 22 | import '@fontsource/roboto/400.css'; |
23 | import '@fontsource/roboto/500.css'; | 23 | import '@fontsource/roboto/500.css'; |
24 | import '@fontsource/roboto/700.css'; | 24 | import '@fontsource/roboto/700.css'; |
25 | import { observer } from 'mobx-react-lite'; | ||
26 | import Button from "@mui/material/Button"; | ||
27 | import CssBaseline from "@mui/material/CssBaseline"; | 25 | import CssBaseline from "@mui/material/CssBaseline"; |
28 | import React from 'react'; | 26 | import React from 'react'; |
29 | import { render } from 'react-dom'; | 27 | import { render } from 'react-dom'; |
30 | 28 | ||
31 | import { StoreProvider, useStore } from './components/StoreProvider'; | 29 | import { App } from './components/App'; |
30 | import { StoreProvider } from './components/StoreProvider'; | ||
32 | import { ThemeProvider } from './components/ThemeProvider'; | 31 | import { ThemeProvider } from './components/ThemeProvider'; |
33 | import { exposeToReduxDevtools } from './devTools'; | 32 | import { exposeToReduxDevtools } from './devTools'; |
34 | import { createAndConnectRootStore } from './stores/RootStore'; | 33 | import { createAndConnectRootStore } from './stores/RootStore'; |
@@ -41,26 +40,13 @@ if (isDevelopment) { | |||
41 | exposeToReduxDevtools(store); | 40 | exposeToReduxDevtools(store); |
42 | } | 41 | } |
43 | 42 | ||
44 | const Example = observer(() => { | ||
45 | const { shared: { clickCount } } = useStore(); | ||
46 | |||
47 | return ( | ||
48 | <Button | ||
49 | variant="contained" | ||
50 | onClick={() => store.buttonClick()} | ||
51 | > | ||
52 | Hello Sophie! {clickCount} | ||
53 | </Button> | ||
54 | ); | ||
55 | }); | ||
56 | |||
57 | function Root(): JSX.Element { | 43 | function Root(): JSX.Element { |
58 | return ( | 44 | return ( |
59 | <React.StrictMode> | 45 | <React.StrictMode> |
60 | <StoreProvider store={store}> | 46 | <StoreProvider store={store}> |
61 | <ThemeProvider> | 47 | <ThemeProvider> |
62 | <CssBaseline enableColorScheme /> | 48 | <CssBaseline enableColorScheme /> |
63 | <Example /> | 49 | <App /> |
64 | </ThemeProvider> | 50 | </ThemeProvider> |
65 | </StoreProvider> | 51 | </StoreProvider> |
66 | </React.StrictMode> | 52 | </React.StrictMode> |
diff --git a/packages/renderer/src/stores/RootStore.ts b/packages/renderer/src/stores/RootStore.ts index f24ea4d..c6533ba 100644 --- a/packages/renderer/src/stores/RootStore.ts +++ b/packages/renderer/src/stores/RootStore.ts | |||
@@ -26,10 +26,16 @@ import { | |||
26 | Instance, | 26 | Instance, |
27 | types | 27 | types |
28 | } from 'mobx-state-tree'; | 28 | } from 'mobx-state-tree'; |
29 | import { sharedStore, SophieRenderer } from '@sophie/shared'; | 29 | import { |
30 | BrowserViewBounds, | ||
31 | emptySharedStore, | ||
32 | PaletteMode, | ||
33 | sharedStore, | ||
34 | SophieRenderer, | ||
35 | } from '@sophie/shared'; | ||
30 | 36 | ||
31 | export interface RootEnv { | 37 | export interface RootEnv { |
32 | ipc: SophieRenderer; | 38 | ipc: Omit<SophieRenderer, 'setSharedStoreListener'>; |
33 | } | 39 | } |
34 | 40 | ||
35 | /** | 41 | /** |
@@ -46,8 +52,18 @@ export function getEnv(model: IAnyStateTreeNode): RootEnv { | |||
46 | export const rootStore = types.model('RootStore', { | 52 | export const rootStore = types.model('RootStore', { |
47 | shared: sharedStore, | 53 | shared: sharedStore, |
48 | }).actions((self) => ({ | 54 | }).actions((self) => ({ |
49 | buttonClick() { | 55 | setBrowserViewBounds(bounds: BrowserViewBounds) { |
50 | getEnv(self).ipc.buttonClick(); | 56 | getEnv(self).ipc.setBrowserViewBounds(bounds); |
57 | }, | ||
58 | setPaletteMode(mode: PaletteMode) { | ||
59 | getEnv(self).ipc.setPaletteMode(mode); | ||
60 | }, | ||
61 | toggleDarkMode() { | ||
62 | if (self.shared.shouldUseDarkColors) { | ||
63 | this.setPaletteMode('light'); | ||
64 | } else { | ||
65 | this.setPaletteMode('dark'); | ||
66 | } | ||
51 | }, | 67 | }, |
52 | })); | 68 | })); |
53 | 69 | ||
@@ -63,7 +79,7 @@ export interface RootStore extends Instance<typeof rootStore> {} | |||
63 | */ | 79 | */ |
64 | export function createAndConnectRootStore(ipc: SophieRenderer): RootStore { | 80 | export function createAndConnectRootStore(ipc: SophieRenderer): RootStore { |
65 | const store = rootStore.create({ | 81 | const store = rootStore.create({ |
66 | shared: {}, | 82 | shared: emptySharedStore, |
67 | }, { | 83 | }, { |
68 | ipc, | 84 | ipc, |
69 | }); | 85 | }); |