diff options
author | Kristóf Marussy <kristof@marussy.com> | 2021-12-23 15:48:58 +0100 |
---|---|---|
committer | Kristóf Marussy <kristof@marussy.com> | 2021-12-23 15:48:58 +0100 |
commit | b5d16a8068ed55b784f8e10f19a99cc7f11b8bc7 (patch) | |
tree | d819b4bb607f5b046451e15385c16804d5f43fcf /packages/renderer/src | |
parent | feat: Main to renderer store synchronization (diff) | |
download | sophie-b5d16a8068ed55b784f8e10f19a99cc7f11b8bc7.tar.gz sophie-b5d16a8068ed55b784f8e10f19a99cc7f11b8bc7.tar.zst sophie-b5d16a8068ed55b784f8e10f19a99cc7f11b8bc7.zip |
feat: Add react and mobx integration
Diffstat (limited to 'packages/renderer/src')
-rw-r--r-- | packages/renderer/src/components/StoreProvider.tsx | 24 | ||||
-rw-r--r-- | packages/renderer/src/components/ThemeProvider.tsx | 26 | ||||
-rw-r--r-- | packages/renderer/src/index.tsx | 53 | ||||
-rw-r--r-- | packages/renderer/src/stores/RootStore.ts | 61 |
4 files changed, 134 insertions, 30 deletions
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 @@ | |||
1 | import React, { createContext, useContext } from 'react'; | ||
2 | |||
3 | import type { RootStore } from '../stores/RootStore'; | ||
4 | |||
5 | const StoreContext = createContext<RootStore | null>(null); | ||
6 | |||
7 | export 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 | |||
15 | export 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 @@ | |||
1 | import { observer } from 'mobx-react-lite'; | ||
2 | import { | ||
3 | unstable_createMuiStrictModeTheme as createTheme, | ||
4 | ThemeProvider as MuiThemeProvider, | ||
5 | } from '@mui/material/styles'; | ||
6 | import React from 'react'; | ||
7 | |||
8 | import { useStore } from './StoreProvider'; | ||
9 | |||
10 | export 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'; | |||
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 { observer } from 'mobx-react-lite'; |
6 | import Button from "@mui/material/Button"; | 6 | import Button from "@mui/material/Button"; |
7 | import CssBaseline from "@mui/material/CssBaseline"; | 7 | import CssBaseline from "@mui/material/CssBaseline"; |
8 | import { | ||
9 | unstable_createMuiStrictModeTheme as createTheme, | ||
10 | ThemeProvider, | ||
11 | } from '@mui/material/styles'; | ||
12 | import React from 'react'; | 8 | import React from 'react'; |
13 | import { render } from 'react-dom'; | 9 | import { render } from 'react-dom'; |
14 | import { sharedStore } from '@sophie/shared'; | ||
15 | 10 | ||
11 | import { StoreProvider, useStore } from './components/StoreProvider'; | ||
12 | import { ThemeProvider } from './components/ThemeProvider'; | ||
16 | import { exposeToReduxDevtools } from './devTools'; | 13 | import { exposeToReduxDevtools } from './devTools'; |
14 | import { createAndConnectRootStore } from './stores/RootStore'; | ||
17 | 15 | ||
18 | const isDevelopment = import.meta.env.MODE === 'development'; | 16 | const isDevelopment = import.meta.env.MODE === 'development'; |
19 | 17 | ||
20 | const store = sharedStore.create(); | 18 | const store = createAndConnectRootStore(window.sophieRenderer); |
21 | 19 | ||
22 | if (isDevelopment) { | 20 | if (isDevelopment) { |
23 | exposeToReduxDevtools(store); | 21 | exposeToReduxDevtools(store); |
24 | } | 22 | } |
25 | 23 | ||
26 | window.sophieRenderer.setSharedStoreListener({ | 24 | const 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 | ||
35 | const 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 | ||
41 | function App() { | 37 | function 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 | ||
57 | render(<App />, document.querySelector('#app')); | 50 | render(<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 @@ | |||
1 | import { | ||
2 | applySnapshot, | ||
3 | applyPatch, | ||
4 | getEnv as getAnyEnv, | ||
5 | IAnyStateTreeNode, | ||
6 | Instance, | ||
7 | types | ||
8 | } from 'mobx-state-tree'; | ||
9 | import { sharedStore, SophieRenderer } from '@sophie/shared'; | ||
10 | |||
11 | export 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 | */ | ||
22 | export function getEnv(model: IAnyStateTreeNode): RootEnv { | ||
23 | return getAnyEnv<RootEnv>(model); | ||
24 | } | ||
25 | |||
26 | export const rootStore = types.model('RootStore', { | ||
27 | shared: sharedStore, | ||
28 | }).actions((self) => ({ | ||
29 | buttonClick() { | ||
30 | getEnv(self).ipc.buttonClick(); | ||
31 | }, | ||
32 | })); | ||
33 | |||
34 | export 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 | */ | ||
44 | export 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 | } | ||