aboutsummaryrefslogtreecommitdiffstats
path: root/packages
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2021-12-23 21:29:26 +0100
committerLibravatar Kristóf Marussy <kristof@marussy.com>2021-12-23 21:29:26 +0100
commitd303f2e3415237e1a519db21ad4e089c2ba7e9f9 (patch)
tree5d562dcaf7bb9c83c1930c7d7cf1b2c9de75e15b /packages
parentbuild: Enable asar (diff)
downloadsophie-d303f2e3415237e1a519db21ad4e089c2ba7e9f9.tar.gz
sophie-d303f2e3415237e1a519db21ad4e089c2ba7e9f9.tar.zst
sophie-d303f2e3415237e1a519db21ad4e089c2ba7e9f9.zip
feat: Add BrowserView and synchronize its position
Diffstat (limited to 'packages')
-rw-r--r--packages/main/package.json2
-rw-r--r--packages/main/src/index.ts63
-rw-r--r--packages/main/src/stores/RootStore.ts22
-rw-r--r--packages/preload/package.json2
-rw-r--r--packages/preload/src/SophieRendererImpl.ts21
-rw-r--r--packages/renderer/package.json2
-rw-r--r--packages/renderer/src/components/App.tsx42
-rw-r--r--packages/renderer/src/components/BrowserViewPlaceholder.tsx128
-rw-r--r--packages/renderer/src/components/Sidebar.tsx42
-rw-r--r--packages/renderer/src/components/ThemeProvider.tsx4
-rw-r--r--packages/renderer/src/components/ToggleDarkModeButton.tsx40
-rw-r--r--packages/renderer/src/index.tsx20
-rw-r--r--packages/renderer/src/stores/RootStore.ts26
-rw-r--r--packages/shared/package.json3
-rw-r--r--packages/shared/src/contextBridge/SophieRenderer.ts6
-rw-r--r--packages/shared/src/index.ts11
-rw-r--r--packages/shared/src/ipc/MainToRendererIpcMessage.ts4
-rw-r--r--packages/shared/src/ipc/RendererToMainIpcMessage.ts5
-rw-r--r--packages/shared/src/schemas.ts34
-rw-r--r--packages/shared/src/stores/SharedStore.ts12
-rw-r--r--packages/shared/vite.config.js1
21 files changed, 427 insertions, 63 deletions
diff --git a/packages/main/package.json b/packages/main/package.json
index 509acdc..014c511 100644
--- a/packages/main/package.json
+++ b/packages/main/package.json
@@ -11,7 +11,7 @@
11 }, 11 },
12 "dependencies": { 12 "dependencies": {
13 "@sophie/shared": "workspace:*", 13 "@sophie/shared": "workspace:*",
14 "electron": "^16.0.5", 14 "electron": "16.0.5",
15 "mobx": "^6.3.10", 15 "mobx": "^6.3.10",
16 "mobx-state-tree": "^5.1.0" 16 "mobx-state-tree": "^5.1.0"
17 }, 17 },
diff --git a/packages/main/src/index.ts b/packages/main/src/index.ts
index 90102a7..59b9ecb 100644
--- a/packages/main/src/index.ts
+++ b/packages/main/src/index.ts
@@ -18,11 +18,14 @@
18 * SPDX-License-Identifier: AGPL-3.0-only 18 * SPDX-License-Identifier: AGPL-3.0-only
19 */ 19 */
20 20
21import { app, BrowserWindow } from 'electron'; 21import { app, BrowserView, BrowserWindow } from 'electron';
22import { autorun } from 'mobx';
22import { getSnapshot, onPatch } from 'mobx-state-tree'; 23import { getSnapshot, onPatch } from 'mobx-state-tree';
23import { join } from 'path'; 24import { join } from 'path';
24import { 25import {
26 browserViewBounds,
25 MainToRendererIpcMessage, 27 MainToRendererIpcMessage,
28 paletteMode,
26 RendererToMainIpcMessage, 29 RendererToMainIpcMessage,
27} from '@sophie/shared'; 30} from '@sophie/shared';
28import { URL } from 'url'; 31import { URL } from 'url';
@@ -31,7 +34,7 @@ import {
31 installDevToolsExtensions, 34 installDevToolsExtensions,
32 openDevToolsWhenReady, 35 openDevToolsWhenReady,
33} from './devTools'; 36} from './devTools';
34import { rootStore } from './stores/RootStore'; 37import { createRootStore } from './stores/RootStore';
35 38
36const isSingleInstance = app.requestSingleInstanceLock(); 39const isSingleInstance = app.requestSingleInstanceLock();
37const isDevelopment = import.meta.env.MODE === 'development'; 40const isDevelopment = import.meta.env.MODE === 'development';
@@ -49,19 +52,13 @@ if (isDevelopment) {
49 52
50let mainWindow: BrowserWindow | null = null; 53let mainWindow: BrowserWindow | null = null;
51 54
52const store = rootStore.create({ 55const store = createRootStore();
53 shared: {
54 clickCount: 1,
55 },
56});
57 56
58function createWindow(): Promise<void> { 57async function createWindow(): Promise<void> {
59 mainWindow = new BrowserWindow({ 58 mainWindow = new BrowserWindow({
60 show: false, 59 show: false,
61 autoHideMenuBar: true, 60 autoHideMenuBar: true,
62 webPreferences: { 61 webPreferences: {
63 nativeWindowOpen: true,
64 webviewTag: false,
65 sandbox: true, 62 sandbox: true,
66 preload: join(__dirname, '../../preload/dist/index.cjs'), 63 preload: join(__dirname, '../../preload/dist/index.cjs'),
67 }, 64 },
@@ -78,16 +75,23 @@ function createWindow(): Promise<void> {
78 const { webContents } = mainWindow; 75 const { webContents } = mainWindow;
79 76
80 webContents.on('ipc-message', (_event, channel, ...args) => { 77 webContents.on('ipc-message', (_event, channel, ...args) => {
81 switch (channel) { 78 try {
82 case RendererToMainIpcMessage.SharedStoreSnapshotRequest: 79 switch (channel) {
83 webContents.send(MainToRendererIpcMessage.SharedStoreSnapshot, getSnapshot(store.shared)); 80 case RendererToMainIpcMessage.SharedStoreSnapshotRequest:
84 break; 81 webContents.send(MainToRendererIpcMessage.SharedStoreSnapshot, getSnapshot(store.shared));
85 case RendererToMainIpcMessage.ButtonClick: 82 break;
86 store.buttonClick(); 83 case RendererToMainIpcMessage.SetBrowserViewBounds:
87 break; 84 store.setBrowserViewBounds(browserViewBounds.parse(args[0]));
88 default: 85 break;
89 console.warn('Unknown IPC message:', channel, args); 86 case RendererToMainIpcMessage.SetPaletteMode:
90 break; 87 store.setPaletteMode(paletteMode.parse(args[0]))
88 break;
89 default:
90 console.warn('Unknown IPC message:', channel, args);
91 break;
92 }
93 } catch (err) {
94 console.error('Error while processing IPC message:', channel, args, err);
91 } 95 }
92 }); 96 });
93 97
@@ -99,7 +103,24 @@ function createWindow(): Promise<void> {
99 ? import.meta.env.VITE_DEV_SERVER_URL 103 ? import.meta.env.VITE_DEV_SERVER_URL
100 : new URL('../renderer/dist/index.html', `file://${__dirname}`).toString(); 104 : new URL('../renderer/dist/index.html', `file://${__dirname}`).toString();
101 105
102 return mainWindow.loadURL(pageUrl); 106 const browserView = new BrowserView({
107 webPreferences: {
108 sandbox: true,
109 partition: 'service',
110 },
111 });
112
113 browserView.setBackgroundColor('#fff');
114
115 autorun(() => {
116 browserView.setBounds(store.shared.browserViewBounds);
117 });
118
119 await mainWindow.loadURL(pageUrl);
120
121 mainWindow.addBrowserView(browserView);
122
123 return browserView.webContents.loadURL('https://git.marussy.com/sophie/about');
103} 124}
104 125
105app.on('second-instance', () => { 126app.on('second-instance', () => {
diff --git a/packages/main/src/stores/RootStore.ts b/packages/main/src/stores/RootStore.ts
index edc740c..9d138ce 100644
--- a/packages/main/src/stores/RootStore.ts
+++ b/packages/main/src/stores/RootStore.ts
@@ -18,15 +18,29 @@
18 * SPDX-License-Identifier: AGPL-3.0-only 18 * SPDX-License-Identifier: AGPL-3.0-only
19 */ 19 */
20 20
21import { Instance, types } from 'mobx-state-tree'; 21import { applySnapshot, Instance, types } from 'mobx-state-tree';
22import { sharedStore } from '@sophie/shared'; 22import {
23 BrowserViewBounds,
24 emptySharedStore,
25 PaletteMode,
26 sharedStore,
27} from '@sophie/shared';
23 28
24export const rootStore = types.model('RootStore', { 29export const rootStore = types.model('RootStore', {
25 shared: sharedStore, 30 shared: sharedStore,
26}).actions((self) => ({ 31}).actions((self) => ({
27 buttonClick() { 32 setBrowserViewBounds(bounds: BrowserViewBounds) {
28 self.shared.clickCount += 1; 33 applySnapshot(self.shared.browserViewBounds, bounds);
34 },
35 setPaletteMode(mode: PaletteMode) {
36 self.shared.shouldUseDarkColors = mode === 'dark';
29 }, 37 },
30})); 38}));
31 39
32export interface RootStore extends Instance<typeof rootStore> {} 40export interface RootStore extends Instance<typeof rootStore> {}
41
42export function createRootStore(): RootStore {
43 return rootStore.create({
44 shared: emptySharedStore,
45 });
46}
diff --git a/packages/preload/package.json b/packages/preload/package.json
index e050b9a..bd2b412 100644
--- a/packages/preload/package.json
+++ b/packages/preload/package.json
@@ -12,7 +12,7 @@
12 }, 12 },
13 "dependencies": { 13 "dependencies": {
14 "@sophie/shared": "workspace:*", 14 "@sophie/shared": "workspace:*",
15 "electron": "^16.0.5", 15 "electron": "16.0.5",
16 "mobx": "^6.3.10", 16 "mobx": "^6.3.10",
17 "mobx-state-tree": "^5.1.0" 17 "mobx-state-tree": "^5.1.0"
18 }, 18 },
diff --git a/packages/preload/src/SophieRendererImpl.ts b/packages/preload/src/SophieRendererImpl.ts
index 1ac8b65..6bffeae 100644
--- a/packages/preload/src/SophieRendererImpl.ts
+++ b/packages/preload/src/SophieRendererImpl.ts
@@ -21,12 +21,16 @@
21import { ipcRenderer } from 'electron'; 21import { ipcRenderer } from 'electron';
22import type { IJsonPatch } from 'mobx-state-tree'; 22import type { IJsonPatch } from 'mobx-state-tree';
23import { 23import {
24 BrowserViewBounds,
25 browserViewBounds,
24 MainToRendererIpcMessage, 26 MainToRendererIpcMessage,
27 PaletteMode,
28 paletteMode,
25 RendererToMainIpcMessage, 29 RendererToMainIpcMessage,
26 sharedStore, 30 sharedStore,
27 SharedStoreListener, 31 SharedStoreListener,
28 SharedStoreSnapshotIn, 32 SharedStoreSnapshotIn,
29 SophieRenderer 33 SophieRenderer,
30} from '@sophie/shared'; 34} from '@sophie/shared';
31 35
32export type MessageSender = (channel: RendererToMainIpcMessage, ...args: unknown[]) => void; 36export type MessageSender = (channel: RendererToMainIpcMessage, ...args: unknown[]) => void;
@@ -71,8 +75,16 @@ export class SophieRendererImpl implements SophieRenderer {
71 this.#send(RendererToMainIpcMessage.SharedStoreSnapshotRequest); 75 this.#send(RendererToMainIpcMessage.SharedStoreSnapshotRequest);
72 } 76 }
73 77
74 buttonClick(): void { 78 setBrowserViewBounds(bounds: BrowserViewBounds): void {
75 this.#send(RendererToMainIpcMessage.ButtonClick); 79 if (browserViewBounds.safeParse(bounds).success) {
80 this.#send(RendererToMainIpcMessage.SetBrowserViewBounds, bounds);
81 }
82 }
83
84 setPaletteMode(mode: PaletteMode): void {
85 if (paletteMode.safeParse(mode).success) {
86 this.#send(RendererToMainIpcMessage.SetPaletteMode, mode);
87 }
76 } 88 }
77} 89}
78 90
@@ -89,6 +101,7 @@ export function createSophieRenderer(): SophieRenderer {
89 101
90 return { 102 return {
91 setSharedStoreListener: impl.setSharedStoreListener.bind(impl), 103 setSharedStoreListener: impl.setSharedStoreListener.bind(impl),
92 buttonClick: impl.buttonClick.bind(impl), 104 setBrowserViewBounds: impl.setBrowserViewBounds.bind(impl),
105 setPaletteMode: impl.setPaletteMode.bind(impl),
93 }; 106 };
94} 107}
diff --git a/packages/renderer/package.json b/packages/renderer/package.json
index 5f9bd57..4948f2b 100644
--- a/packages/renderer/package.json
+++ b/packages/renderer/package.json
@@ -16,6 +16,7 @@
16 "@mui/icons-material": "^5.2.5", 16 "@mui/icons-material": "^5.2.5",
17 "@mui/material": "^5.2.5", 17 "@mui/material": "^5.2.5",
18 "@sophie/shared": "workspace:*", 18 "@sophie/shared": "workspace:*",
19 "lodash": "^4.17.21",
19 "mobx": "^6.3.10", 20 "mobx": "^6.3.10",
20 "mobx-react-lite": "^3.2.2", 21 "mobx-react-lite": "^3.2.2",
21 "mobx-state-tree": "^5.1.0", 22 "mobx-state-tree": "^5.1.0",
@@ -23,6 +24,7 @@
23 "react-dom": "^17.0.2" 24 "react-dom": "^17.0.2"
24 }, 25 },
25 "devDependencies": { 26 "devDependencies": {
27 "@types/lodash": "^4.14.178",
26 "@types/react": "^17.0.38", 28 "@types/react": "^17.0.38",
27 "@types/react-dom": "^17.0.11", 29 "@types/react-dom": "^17.0.11",
28 "@vitejs/plugin-react": "^1.1.3", 30 "@vitejs/plugin-react": "^1.1.3",
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
21import Box from '@mui/material/Box';
22import React from 'react';
23
24import { BrowserViewPlaceholder } from './BrowserViewPlaceholder';
25import { Sidebar } from './Sidebar';
26
27export 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
21import { throttle } from 'lodash';
22import { observer } from 'mobx-react-lite';
23import Box from '@mui/material/Box';
24import React, {
25 useCallback,
26 useEffect,
27 useRef,
28 useState,
29} from 'react';
30import type { BrowserViewBounds } from '@sophie/shared';
31
32import { useStore } from './StoreProvider';
33
34export 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
21import Box from '@mui/material/Box';
22import React from 'react';
23
24import { ToggleDarkModeButton } from './ToggleDarkModeButton';
25
26export 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
28import { useStore } from './StoreProvider'; 28import { useStore } from './StoreProvider';
29 29
30export const ThemeProvider = observer(({ children }: { 30export 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
21import { observer } from 'mobx-react-lite';
22import DarkModeIcon from '@mui/icons-material/DarkMode';
23import LightModeIcon from '@mui/icons-material/LightMode';
24import IconButton from '@mui/material/IconButton';
25import React from 'react';
26
27import { useStore } from './StoreProvider';
28
29export 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';
22import '@fontsource/roboto/400.css'; 22import '@fontsource/roboto/400.css';
23import '@fontsource/roboto/500.css'; 23import '@fontsource/roboto/500.css';
24import '@fontsource/roboto/700.css'; 24import '@fontsource/roboto/700.css';
25import { observer } from 'mobx-react-lite';
26import Button from "@mui/material/Button";
27import CssBaseline from "@mui/material/CssBaseline"; 25import CssBaseline from "@mui/material/CssBaseline";
28import React from 'react'; 26import React from 'react';
29import { render } from 'react-dom'; 27import { render } from 'react-dom';
30 28
31import { StoreProvider, useStore } from './components/StoreProvider'; 29import { App } from './components/App';
30import { StoreProvider } from './components/StoreProvider';
32import { ThemeProvider } from './components/ThemeProvider'; 31import { ThemeProvider } from './components/ThemeProvider';
33import { exposeToReduxDevtools } from './devTools'; 32import { exposeToReduxDevtools } from './devTools';
34import { createAndConnectRootStore } from './stores/RootStore'; 33import { createAndConnectRootStore } from './stores/RootStore';
@@ -41,26 +40,13 @@ if (isDevelopment) {
41 exposeToReduxDevtools(store); 40 exposeToReduxDevtools(store);
42} 41}
43 42
44const 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
57function Root(): JSX.Element { 43function 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';
29import { sharedStore, SophieRenderer } from '@sophie/shared'; 29import {
30 BrowserViewBounds,
31 emptySharedStore,
32 PaletteMode,
33 sharedStore,
34 SophieRenderer,
35} from '@sophie/shared';
30 36
31export interface RootEnv { 37export 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 {
46export const rootStore = types.model('RootStore', { 52export 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 */
64export function createAndConnectRootStore(ipc: SophieRenderer): RootStore { 80export 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 });
diff --git a/packages/shared/package.json b/packages/shared/package.json
index 83aec54..f5d4f90 100644
--- a/packages/shared/package.json
+++ b/packages/shared/package.json
@@ -17,6 +17,7 @@
17 }, 17 },
18 "dependencies": { 18 "dependencies": {
19 "mobx": "^6.3.10", 19 "mobx": "^6.3.10",
20 "mobx-state-tree": "^5.1.0" 20 "mobx-state-tree": "^5.1.0",
21 "zod": "^3.11.6"
21 } 22 }
22} 23}
diff --git a/packages/shared/src/contextBridge/SophieRenderer.ts b/packages/shared/src/contextBridge/SophieRenderer.ts
index 0d82d56..f2e2180 100644
--- a/packages/shared/src/contextBridge/SophieRenderer.ts
+++ b/packages/shared/src/contextBridge/SophieRenderer.ts
@@ -20,8 +20,12 @@
20 20
21import { SharedStoreListener } from '../stores/SharedStore'; 21import { SharedStoreListener } from '../stores/SharedStore';
22 22
23import { BrowserViewBounds, PaletteMode } from '../schemas';
24
23export interface SophieRenderer { 25export interface SophieRenderer {
24 setSharedStoreListener(listener: SharedStoreListener): void; 26 setSharedStoreListener(listener: SharedStoreListener): void;
25 27
26 buttonClick(): void; 28 setBrowserViewBounds(bounds: BrowserViewBounds): void;
29
30 setPaletteMode(mode: PaletteMode): void;
27} 31}
diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts
index 250700f..0a1ec40 100644
--- a/packages/shared/src/index.ts
+++ b/packages/shared/src/index.ts
@@ -24,9 +24,18 @@ export { MainToRendererIpcMessage } from './ipc/MainToRendererIpcMessage';
24export { RendererToMainIpcMessage } from './ipc/RendererToMainIpcMessage'; 24export { RendererToMainIpcMessage } from './ipc/RendererToMainIpcMessage';
25 25
26export type { 26export type {
27 BrowserViewBounds,
28 PaletteMode,
29} from './schemas';
30export {
31 browserViewBounds,
32 paletteMode
33} from './schemas';
34
35export type {
27 SharedStore, 36 SharedStore,
28 SharedStoreListener, 37 SharedStoreListener,
29 SharedStoreSnapshotIn, 38 SharedStoreSnapshotIn,
30 SharedStoreSnapshotOut, 39 SharedStoreSnapshotOut,
31} from './stores/SharedStore'; 40} from './stores/SharedStore';
32export { sharedStore } from './stores/SharedStore'; 41export { emptySharedStore, sharedStore } from './stores/SharedStore';
diff --git a/packages/shared/src/ipc/MainToRendererIpcMessage.ts b/packages/shared/src/ipc/MainToRendererIpcMessage.ts
index e68d676..92da489 100644
--- a/packages/shared/src/ipc/MainToRendererIpcMessage.ts
+++ b/packages/shared/src/ipc/MainToRendererIpcMessage.ts
@@ -19,6 +19,6 @@
19 */ 19 */
20 20
21export enum MainToRendererIpcMessage { 21export enum MainToRendererIpcMessage {
22 SharedStoreSnapshot = "sophie-shared-store-snapshot", 22 SharedStoreSnapshot = 'sophie-shared-store-snapshot',
23 SharedStorePatch = "sophie-shared-store-patch", 23 SharedStorePatch = 'sophie-shared-store-patch',
24} 24}
diff --git a/packages/shared/src/ipc/RendererToMainIpcMessage.ts b/packages/shared/src/ipc/RendererToMainIpcMessage.ts
index a7aa664..ba354d1 100644
--- a/packages/shared/src/ipc/RendererToMainIpcMessage.ts
+++ b/packages/shared/src/ipc/RendererToMainIpcMessage.ts
@@ -19,6 +19,7 @@
19 */ 19 */
20 20
21export enum RendererToMainIpcMessage { 21export enum RendererToMainIpcMessage {
22 SharedStoreSnapshotRequest = "sophie-shared-store-snapshot-request", 22 SharedStoreSnapshotRequest = 'sophie-shared-store-snapshot-request',
23 ButtonClick = "sophie-button-click" 23 SetBrowserViewBounds = 'sophie-set-browser-view-bounds',
24 SetPaletteMode = 'sophie-set-palette-mode',
24} 25}
diff --git a/packages/shared/src/schemas.ts b/packages/shared/src/schemas.ts
new file mode 100644
index 0000000..8827467
--- /dev/null
+++ b/packages/shared/src/schemas.ts
@@ -0,0 +1,34 @@
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
21import { z } from 'zod';
22
23export const browserViewBounds = z.object({
24 x: z.number().nonnegative(),
25 y: z.number().nonnegative(),
26 width: z.number().nonnegative(),
27 height: z.number().nonnegative(),
28});
29
30export type BrowserViewBounds = z.infer<typeof browserViewBounds>;
31
32export const paletteMode = z.enum(['light', 'dark']);
33
34export type PaletteMode = z.infer<typeof paletteMode>;
diff --git a/packages/shared/src/stores/SharedStore.ts b/packages/shared/src/stores/SharedStore.ts
index 7fd65b7..7c2b742 100644
--- a/packages/shared/src/stores/SharedStore.ts
+++ b/packages/shared/src/stores/SharedStore.ts
@@ -27,10 +27,20 @@ import {
27} from 'mobx-state-tree'; 27} from 'mobx-state-tree';
28 28
29export const sharedStore = types.model("SharedStore", { 29export const sharedStore = types.model("SharedStore", {
30 browserViewBounds: types.model("BrowserViewBoundsStore", {
31 x: 0,
32 y: 0,
33 width: 0,
34 height: 0,
35 }),
30 shouldUseDarkColors: true, 36 shouldUseDarkColors: true,
31 clickCount: 0
32}); 37});
33 38
39export const emptySharedStore: SharedStoreSnapshotIn = {
40 browserViewBounds: {
41 },
42};
43
34export interface SharedStore extends Instance<typeof sharedStore> {} 44export interface SharedStore extends Instance<typeof sharedStore> {}
35 45
36export interface SharedStoreSnapshotIn extends SnapshotIn<typeof sharedStore> {} 46export interface SharedStoreSnapshotIn extends SnapshotIn<typeof sharedStore> {}
diff --git a/packages/shared/vite.config.js b/packages/shared/vite.config.js
index 76bd965..2867541 100644
--- a/packages/shared/vite.config.js
+++ b/packages/shared/vite.config.js
@@ -29,6 +29,7 @@ const config = makeConfig({
29 external: [ 29 external: [
30 'mobx', 30 'mobx',
31 'mobx-state-tree', 31 'mobx-state-tree',
32 'zod',
32 ...builtinModules, 33 ...builtinModules,
33 ], 34 ],
34 }, 35 },