diff options
author | Kristóf Marussy <kristof@marussy.com> | 2021-12-23 13:40:47 +0100 |
---|---|---|
committer | Kristóf Marussy <kristof@marussy.com> | 2021-12-23 15:48:30 +0100 |
commit | 950fb9be8061e2a26e0536b98c6a3ee230618f54 (patch) | |
tree | b136dcc9add0d268a2e7a6288ec934a86d03b652 /packages/renderer | |
parent | feat: Add shared package for electron ipc (diff) | |
download | sophie-950fb9be8061e2a26e0536b98c6a3ee230618f54.tar.gz sophie-950fb9be8061e2a26e0536b98c6a3ee230618f54.tar.zst sophie-950fb9be8061e2a26e0536b98c6a3ee230618f54.zip |
feat: Main to renderer store synchronization
Patches are send in one direction only, from the main to the renderer,
so all actions have to go through the context bridge and the renderer
IPC to modify the store in the renderer. This makes the store in the
main process a single source of truth, which simplifies debugging and
state persistence.
The store in the renderer is connected to redux devtools for inspection,
but playing back the state in the devtools won't change the sotre in
main process.
Diffstat (limited to 'packages/renderer')
-rw-r--r-- | packages/renderer/package.json | 6 | ||||
-rw-r--r-- | packages/renderer/src/devTools.ts | 37 | ||||
-rw-r--r-- | packages/renderer/src/index.tsx | 23 | ||||
-rw-r--r-- | packages/renderer/tsconfig.json | 3 | ||||
-rw-r--r-- | packages/renderer/vite.config.js | 3 |
5 files changed, 69 insertions, 3 deletions
diff --git a/packages/renderer/package.json b/packages/renderer/package.json index 559e668..ee627b4 100644 --- a/packages/renderer/package.json +++ b/packages/renderer/package.json | |||
@@ -14,13 +14,17 @@ | |||
14 | "@mui/icons-material": "^5.2.5", | 14 | "@mui/icons-material": "^5.2.5", |
15 | "@mui/material": "^5.2.5", | 15 | "@mui/material": "^5.2.5", |
16 | "@sophie/shared": "workspace:*", | 16 | "@sophie/shared": "workspace:*", |
17 | "mobx": "^6.3.10", | ||
18 | "mobx-state-tree": "^5.1.0", | ||
17 | "react": "^17.0.2", | 19 | "react": "^17.0.2", |
18 | "react-dom": "^17.0.2" | 20 | "react-dom": "^17.0.2" |
19 | }, | 21 | }, |
20 | "devDependencies": { | 22 | "devDependencies": { |
21 | "@types/react": "^17.0.37", | 23 | "@types/react": "^17.0.38", |
22 | "@types/react-dom": "^17.0.11", | 24 | "@types/react-dom": "^17.0.11", |
23 | "@vitejs/plugin-react": "^1.1.3", | 25 | "@vitejs/plugin-react": "^1.1.3", |
26 | "mst-middlewares": "^5.1.0", | ||
27 | "remotedev": "^0.2.9", | ||
24 | "typescript": "^4.5.4", | 28 | "typescript": "^4.5.4", |
25 | "vite": "^2.7.6" | 29 | "vite": "^2.7.6" |
26 | } | 30 | } |
diff --git a/packages/renderer/src/devTools.ts b/packages/renderer/src/devTools.ts new file mode 100644 index 0000000..5930c48 --- /dev/null +++ b/packages/renderer/src/devTools.ts | |||
@@ -0,0 +1,37 @@ | |||
1 | import type { IAnyStateTreeNode } from 'mobx-state-tree'; | ||
2 | |||
3 | /** | ||
4 | * Connects the `model` to the redux devtools extension after loading the required | ||
5 | * dependencies asynchronously. | ||
6 | * | ||
7 | * We have to apply a workaround to load `remotedev` by shimming the `global` object, | ||
8 | * because `remotedev` uses an old version of `socketcluster-client` that refers to | ||
9 | * `global` instead of `globalThis`. | ||
10 | * | ||
11 | * Due to the old dependencies, this function is not safe to call in production. | ||
12 | * However, we don't bundle `remotedev` in production, so the call would fail anyways. | ||
13 | * | ||
14 | * @param model The store to connect to the redux devtools. | ||
15 | * @see https://github.com/SocketCluster/socketcluster-client/issues/118#issuecomment-469064682 | ||
16 | */ | ||
17 | async function exposeToReduxDevtoolsAsync(model: IAnyStateTreeNode): Promise<void> { | ||
18 | (window as { global?: unknown }).global = window; | ||
19 | |||
20 | const [remotedev, { connectReduxDevtools }] = await Promise.all([ | ||
21 | // @ts-ignore | ||
22 | import('remotedev'), | ||
23 | import('mst-middlewares'), | ||
24 | ]); | ||
25 | connectReduxDevtools(remotedev, model); | ||
26 | } | ||
27 | |||
28 | /** | ||
29 | * Connects the `model` to the redux devtools extension. | ||
30 | * | ||
31 | * @param model The store to connect to the redux devtools. | ||
32 | */ | ||
33 | export function exposeToReduxDevtools(model: IAnyStateTreeNode): void { | ||
34 | exposeToReduxDevtoolsAsync(model).catch((err) => { | ||
35 | console.error('Could not connect to Redux devtools', err); | ||
36 | }); | ||
37 | } | ||
diff --git a/packages/renderer/src/index.tsx b/packages/renderer/src/index.tsx index 452448c..37daaa6 100644 --- a/packages/renderer/src/index.tsx +++ b/packages/renderer/src/index.tsx | |||
@@ -2,6 +2,7 @@ import '@fontsource/roboto/300.css'; | |||
2 | import '@fontsource/roboto/400.css'; | 2 | import '@fontsource/roboto/400.css'; |
3 | import '@fontsource/roboto/500.css'; | 3 | import '@fontsource/roboto/500.css'; |
4 | import '@fontsource/roboto/700.css'; | 4 | import '@fontsource/roboto/700.css'; |
5 | import { applyPatch, applySnapshot } from 'mobx-state-tree'; | ||
5 | import Button from "@mui/material/Button"; | 6 | import Button from "@mui/material/Button"; |
6 | import CssBaseline from "@mui/material/CssBaseline"; | 7 | import CssBaseline from "@mui/material/CssBaseline"; |
7 | import { | 8 | import { |
@@ -10,6 +11,26 @@ import { | |||
10 | } from '@mui/material/styles'; | 11 | } from '@mui/material/styles'; |
11 | import React from 'react'; | 12 | import React from 'react'; |
12 | import { render } from 'react-dom'; | 13 | import { render } from 'react-dom'; |
14 | import { sharedStore } from '@sophie/shared'; | ||
15 | |||
16 | import { exposeToReduxDevtools } from './devTools'; | ||
17 | |||
18 | const isDevelopment = import.meta.env.MODE === 'development'; | ||
19 | |||
20 | const store = sharedStore.create(); | ||
21 | |||
22 | if (isDevelopment) { | ||
23 | exposeToReduxDevtools(store); | ||
24 | } | ||
25 | |||
26 | window.sophieRenderer.setSharedStoreListener({ | ||
27 | onSnapshot(snapshot) { | ||
28 | applySnapshot(store, snapshot); | ||
29 | }, | ||
30 | onPatch(patch) { | ||
31 | applyPatch(store, patch); | ||
32 | }, | ||
33 | }); | ||
13 | 34 | ||
14 | const theme = createTheme({ | 35 | const theme = createTheme({ |
15 | palette: { | 36 | palette: { |
@@ -24,7 +45,7 @@ function App() { | |||
24 | <CssBaseline enableColorScheme /> | 45 | <CssBaseline enableColorScheme /> |
25 | <Button | 46 | <Button |
26 | variant="contained" | 47 | variant="contained" |
27 | onClick={window.sophieRenderer.buttonClicked} | 48 | onClick={window.sophieRenderer.buttonClick} |
28 | > | 49 | > |
29 | Hello Sophie! | 50 | Hello Sophie! |
30 | </Button> | 51 | </Button> |
diff --git a/packages/renderer/tsconfig.json b/packages/renderer/tsconfig.json index 668356c..8746462 100644 --- a/packages/renderer/tsconfig.json +++ b/packages/renderer/tsconfig.json | |||
@@ -7,6 +7,9 @@ | |||
7 | "dom", | 7 | "dom", |
8 | "dom.iterable", | 8 | "dom.iterable", |
9 | "esnext" | 9 | "esnext" |
10 | ], | ||
11 | "types": [ | ||
12 | "vite/client" | ||
10 | ] | 13 | ] |
11 | }, | 14 | }, |
12 | "references": [ | 15 | "references": [ |
diff --git a/packages/renderer/vite.config.js b/packages/renderer/vite.config.js index ff34b6d..87c2d0c 100644 --- a/packages/renderer/vite.config.js +++ b/packages/renderer/vite.config.js | |||
@@ -3,7 +3,6 @@ | |||
3 | /* eslint-env node */ | 3 | /* eslint-env node */ |
4 | 4 | ||
5 | import { builtinModules } from 'module'; | 5 | import { builtinModules } from 'module'; |
6 | import { join } from 'path'; | ||
7 | import react from '@vitejs/plugin-react'; | 6 | import react from '@vitejs/plugin-react'; |
8 | 7 | ||
9 | // `resolveJsonModule` is disabled for this package, but vite will load the json nevertheless. | 8 | // `resolveJsonModule` is disabled for this package, but vite will load the json nevertheless. |
@@ -36,6 +35,8 @@ const config = { | |||
36 | assetsDir: '.', | 35 | assetsDir: '.', |
37 | rollupOptions: { | 36 | rollupOptions: { |
38 | external: [ | 37 | external: [ |
38 | 'mst-middlewares', | ||
39 | 'remotedev', | ||
39 | ...builtinModules, | 40 | ...builtinModules, |
40 | ], | 41 | ], |
41 | }, | 42 | }, |