aboutsummaryrefslogtreecommitdiffstats
path: root/packages/renderer
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2021-12-23 15:48:58 +0100
committerLibravatar Kristóf Marussy <kristof@marussy.com>2021-12-23 15:48:58 +0100
commitb5d16a8068ed55b784f8e10f19a99cc7f11b8bc7 (patch)
treed819b4bb607f5b046451e15385c16804d5f43fcf /packages/renderer
parentfeat: Main to renderer store synchronization (diff)
downloadsophie-b5d16a8068ed55b784f8e10f19a99cc7f11b8bc7.tar.gz
sophie-b5d16a8068ed55b784f8e10f19a99cc7f11b8bc7.tar.zst
sophie-b5d16a8068ed55b784f8e10f19a99cc7f11b8bc7.zip
feat: Add react and mobx integration
Diffstat (limited to 'packages/renderer')
-rw-r--r--packages/renderer/package.json1
-rw-r--r--packages/renderer/src/components/StoreProvider.tsx24
-rw-r--r--packages/renderer/src/components/ThemeProvider.tsx26
-rw-r--r--packages/renderer/src/index.tsx53
-rw-r--r--packages/renderer/src/stores/RootStore.ts61
5 files changed, 135 insertions, 30 deletions
diff --git a/packages/renderer/package.json b/packages/renderer/package.json
index ee627b4..ceec3a5 100644
--- a/packages/renderer/package.json
+++ b/packages/renderer/package.json
@@ -15,6 +15,7 @@
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", 17 "mobx": "^6.3.10",
18 "mobx-react-lite": "^3.2.2",
18 "mobx-state-tree": "^5.1.0", 19 "mobx-state-tree": "^5.1.0",
19 "react": "^17.0.2", 20 "react": "^17.0.2",
20 "react-dom": "^17.0.2" 21 "react-dom": "^17.0.2"
diff --git a/packages/renderer/src/components/StoreProvider.tsx b/packages/renderer/src/components/StoreProvider.tsx
new file mode 100644
index 0000000..51c39f7
--- /dev/null
+++ b/packages/renderer/src/components/StoreProvider.tsx
@@ -0,0 +1,24 @@
1import React, { createContext, useContext } from 'react';
2
3import type { RootStore } from '../stores/RootStore';
4
5const StoreContext = createContext<RootStore | null>(null);
6
7export function useStore(): RootStore {
8 const store = useContext(StoreContext);
9 if (store === null) {
10 throw new Error('useStore can only be called inside of StoreProvider');
11 }
12 return store;
13}
14
15export function StoreProvider({ children, store }: {
16 children: JSX.Element | JSX.Element[],
17 store: RootStore,
18}): JSX.Element {
19 return (
20 <StoreContext.Provider value={store}>
21 {children}
22 </StoreContext.Provider>
23 );
24}
diff --git a/packages/renderer/src/components/ThemeProvider.tsx b/packages/renderer/src/components/ThemeProvider.tsx
new file mode 100644
index 0000000..8be4f6a
--- /dev/null
+++ b/packages/renderer/src/components/ThemeProvider.tsx
@@ -0,0 +1,26 @@
1import { observer } from 'mobx-react-lite';
2import {
3 unstable_createMuiStrictModeTheme as createTheme,
4 ThemeProvider as MuiThemeProvider,
5} from '@mui/material/styles';
6import React from 'react';
7
8import { useStore } from './StoreProvider';
9
10export const ThemeProvider = observer(({ children }: {
11 children: JSX.Element | JSX.Element[],
12}): JSX.Element => {
13 const { shared: { shouldUseDarkColors } } = useStore();
14
15 const theme = createTheme({
16 palette: {
17 mode: shouldUseDarkColors ? 'dark' : 'light',
18 },
19 });
20
21 return (
22 <MuiThemeProvider theme={theme}>
23 {children}
24 </MuiThemeProvider>
25 );
26});
diff --git a/packages/renderer/src/index.tsx b/packages/renderer/src/index.tsx
index 37daaa6..34b21de 100644
--- a/packages/renderer/src/index.tsx
+++ b/packages/renderer/src/index.tsx
@@ -2,56 +2,49 @@ import '@fontsource/roboto/300.css';
2import '@fontsource/roboto/400.css'; 2import '@fontsource/roboto/400.css';
3import '@fontsource/roboto/500.css'; 3import '@fontsource/roboto/500.css';
4import '@fontsource/roboto/700.css'; 4import '@fontsource/roboto/700.css';
5import { applyPatch, applySnapshot } from 'mobx-state-tree'; 5import { observer } from 'mobx-react-lite';
6import Button from "@mui/material/Button"; 6import Button from "@mui/material/Button";
7import CssBaseline from "@mui/material/CssBaseline"; 7import CssBaseline from "@mui/material/CssBaseline";
8import {
9 unstable_createMuiStrictModeTheme as createTheme,
10 ThemeProvider,
11} from '@mui/material/styles';
12import React from 'react'; 8import React from 'react';
13import { render } from 'react-dom'; 9import { render } from 'react-dom';
14import { sharedStore } from '@sophie/shared';
15 10
11import { StoreProvider, useStore } from './components/StoreProvider';
12import { ThemeProvider } from './components/ThemeProvider';
16import { exposeToReduxDevtools } from './devTools'; 13import { exposeToReduxDevtools } from './devTools';
14import { createAndConnectRootStore } from './stores/RootStore';
17 15
18const isDevelopment = import.meta.env.MODE === 'development'; 16const isDevelopment = import.meta.env.MODE === 'development';
19 17
20const store = sharedStore.create(); 18const store = createAndConnectRootStore(window.sophieRenderer);
21 19
22if (isDevelopment) { 20if (isDevelopment) {
23 exposeToReduxDevtools(store); 21 exposeToReduxDevtools(store);
24} 22}
25 23
26window.sophieRenderer.setSharedStoreListener({ 24const Example = observer(() => {
27 onSnapshot(snapshot) { 25 const { shared: { clickCount } } = useStore();
28 applySnapshot(store, snapshot);
29 },
30 onPatch(patch) {
31 applyPatch(store, patch);
32 },
33});
34 26
35const theme = createTheme({ 27 return (
36 palette: { 28 <Button
37 mode: 'dark', 29 variant="contained"
38 }, 30 onClick={() => store.buttonClick()}
31 >
32 Hello Sophie! {clickCount}
33 </Button>
34 );
39}); 35});
40 36
41function App() { 37function Root(): JSX.Element {
42 return ( 38 return (
43 <React.StrictMode> 39 <React.StrictMode>
44 <ThemeProvider theme={theme}> 40 <StoreProvider store={store}>
45 <CssBaseline enableColorScheme /> 41 <ThemeProvider>
46 <Button 42 <CssBaseline enableColorScheme />
47 variant="contained" 43 <Example />
48 onClick={window.sophieRenderer.buttonClick} 44 </ThemeProvider>
49 > 45 </StoreProvider>
50 Hello Sophie!
51 </Button>
52 </ThemeProvider>
53 </React.StrictMode> 46 </React.StrictMode>
54 ); 47 );
55} 48}
56 49
57render(<App />, document.querySelector('#app')); 50render(<Root />, document.querySelector('#app'));
diff --git a/packages/renderer/src/stores/RootStore.ts b/packages/renderer/src/stores/RootStore.ts
new file mode 100644
index 0000000..86efac6
--- /dev/null
+++ b/packages/renderer/src/stores/RootStore.ts
@@ -0,0 +1,61 @@
1import {
2 applySnapshot,
3 applyPatch,
4 getEnv as getAnyEnv,
5 IAnyStateTreeNode,
6 Instance,
7 types
8} from 'mobx-state-tree';
9import { sharedStore, SophieRenderer } from '@sophie/shared';
10
11export interface RootEnv {
12 ipc: SophieRenderer;
13}
14
15/**
16 * Gets a well-typed environment from `model`.
17 *
18 * Only useable inside state trees created by `createAndConnectRootStore`.
19 *
20 * @param model The state tree node.
21 */
22export function getEnv(model: IAnyStateTreeNode): RootEnv {
23 return getAnyEnv<RootEnv>(model);
24}
25
26export const rootStore = types.model('RootStore', {
27 shared: sharedStore,
28}).actions((self) => ({
29 buttonClick() {
30 getEnv(self).ipc.buttonClick();
31 },
32}));
33
34export interface RootStore extends Instance<typeof rootStore> {}
35
36/**
37 * Creates a new `RootStore` with a new environment and connects it to `ipc`.
38 *
39 * Changes to the `shared` store in the main process will be propagated to
40 * the newly created store via `ipc`.
41 *
42 * @param ipc The `sophieRenderer` context bridge.
43 */
44export function createAndConnectRootStore(ipc: SophieRenderer): RootStore {
45 const store = rootStore.create({
46 shared: {},
47 }, {
48 ipc,
49 });
50
51 ipc.setSharedStoreListener({
52 onSnapshot(snapshot) {
53 applySnapshot(store.shared, snapshot);
54 },
55 onPatch(patch) {
56 applyPatch(store.shared, patch);
57 },
58 });
59
60 return store;
61}