diff options
-rw-r--r-- | packages/main/package.json | 6 | ||||
-rw-r--r-- | packages/main/src/index.ts | 117 | ||||
-rw-r--r-- | packages/main/src/services/ConfigPersistence.ts | 36 | ||||
-rw-r--r-- | packages/main/src/services/MainEnv.ts | 38 | ||||
-rw-r--r-- | packages/main/src/services/impl/ConfigPersistenceImpl.ts | 102 | ||||
-rw-r--r-- | packages/main/src/stores/Config.ts | 96 | ||||
-rw-r--r-- | packages/main/src/stores/MainStore.ts (renamed from packages/main/src/stores/RootStore.ts) | 23 | ||||
-rw-r--r-- | yarn.lock | 8 |
8 files changed, 302 insertions, 124 deletions
diff --git a/packages/main/package.json b/packages/main/package.json index 96cd52c..024fba8 100644 --- a/packages/main/package.json +++ b/packages/main/package.json | |||
@@ -14,11 +14,15 @@ | |||
14 | "@sophie/shared": "workspace:*", | 14 | "@sophie/shared": "workspace:*", |
15 | "electron": "16.0.5", | 15 | "electron": "16.0.5", |
16 | "json5": "^2.2.0", | 16 | "json5": "^2.2.0", |
17 | "lodash": "^4.17.21", | ||
17 | "mobx": "^6.3.10", | 18 | "mobx": "^6.3.10", |
18 | "mobx-state-tree": "^5.1.0" | 19 | "mobx-state-tree": "^5.1.0", |
20 | "ms": "^2.1.3" | ||
19 | }, | 21 | }, |
20 | "devDependencies": { | 22 | "devDependencies": { |
21 | "@types/electron-devtools-installer": "^2.2.1", | 23 | "@types/electron-devtools-installer": "^2.2.1", |
24 | "@types/lodash": "^4.14.178", | ||
25 | "@types/ms": "^0.7.31", | ||
22 | "@types/node": "^17.0.4", | 26 | "@types/node": "^17.0.4", |
23 | "electron-devtools-installer": "^3.2.0", | 27 | "electron-devtools-installer": "^3.2.0", |
24 | "rimraf": "^3.0.2", | 28 | "rimraf": "^3.0.2", |
diff --git a/packages/main/src/index.ts b/packages/main/src/index.ts index 8055ce2..8297ff5 100644 --- a/packages/main/src/index.ts +++ b/packages/main/src/index.ts | |||
@@ -26,20 +26,9 @@ import { | |||
26 | nativeTheme, | 26 | nativeTheme, |
27 | } from 'electron'; | 27 | } from 'electron'; |
28 | import { readFileSync } from 'fs'; | 28 | import { readFileSync } from 'fs'; |
29 | import { | 29 | import { readFile } from 'fs/promises'; |
30 | readFile, | ||
31 | stat, | ||
32 | watch, | ||
33 | writeFile, | ||
34 | } from 'fs/promises'; | ||
35 | import JSON5 from 'json5'; | ||
36 | import { autorun } from 'mobx'; | 30 | import { autorun } from 'mobx'; |
37 | import { | 31 | import { getSnapshot, onPatch } from 'mobx-state-tree'; |
38 | applySnapshot, | ||
39 | getSnapshot, | ||
40 | onPatch, | ||
41 | onSnapshot, | ||
42 | } from 'mobx-state-tree'; | ||
43 | import { join } from 'path'; | 32 | import { join } from 'path'; |
44 | import { | 33 | import { |
45 | ServiceToMainIpcMessage, | 34 | ServiceToMainIpcMessage, |
@@ -58,8 +47,8 @@ import { | |||
58 | installDevToolsExtensions, | 47 | installDevToolsExtensions, |
59 | openDevToolsWhenReady, | 48 | openDevToolsWhenReady, |
60 | } from './devTools'; | 49 | } from './devTools'; |
61 | import { ConfigSnapshotOut } from './stores/Config'; | 50 | import { ConfigPersistenceImpl } from './services/impl/ConfigPersistenceImpl'; |
62 | import { createRootStore } from './stores/RootStore'; | 51 | import { createMainStore } from './stores/MainStore'; |
63 | 52 | ||
64 | const isDevelopment = import.meta.env.MODE === 'development'; | 53 | const isDevelopment = import.meta.env.MODE === 'development'; |
65 | 54 | ||
@@ -116,7 +105,12 @@ if (isDevelopment) { | |||
116 | 105 | ||
117 | let mainWindow: BrowserWindow | null = null; | 106 | let mainWindow: BrowserWindow | null = null; |
118 | 107 | ||
119 | const store = createRootStore(); | 108 | const store = createMainStore({ |
109 | configPersistence: new ConfigPersistenceImpl( | ||
110 | app.getPath('userData'), | ||
111 | 'config.json5', | ||
112 | ), | ||
113 | }); | ||
120 | 114 | ||
121 | autorun(() => { | 115 | autorun(() => { |
122 | nativeTheme.themeSource = store.config.themeSource; | 116 | nativeTheme.themeSource = store.config.themeSource; |
@@ -127,96 +121,7 @@ nativeTheme.on('updated', () => { | |||
127 | store.setShouldUseDarkColors(nativeTheme.shouldUseDarkColors); | 121 | store.setShouldUseDarkColors(nativeTheme.shouldUseDarkColors); |
128 | }); | 122 | }); |
129 | 123 | ||
130 | const userDataDir = app.getPath('userData'); | 124 | store.config.initConfig(); |
131 | const configFileName = 'config.json5'; | ||
132 | const configPath = join(userDataDir, configFileName); | ||
133 | let loadingConfig = false; | ||
134 | let savingConfig = false; | ||
135 | let configMtime: Date | null = null; | ||
136 | |||
137 | async function loadConfig(): Promise<void> { | ||
138 | let configStr: string; | ||
139 | try { | ||
140 | configStr = await readFile(configPath, 'utf8'); | ||
141 | } catch (err) { | ||
142 | if ((err as NodeJS.ErrnoException).code === 'ENOENT') { | ||
143 | console.log('Creating config', configPath); | ||
144 | return saveConfig(getSnapshot(store.config)); | ||
145 | } | ||
146 | throw err; | ||
147 | } | ||
148 | let configSnapshot: unknown; | ||
149 | try { | ||
150 | configSnapshot = JSON5.parse(configStr); | ||
151 | } catch (err) { | ||
152 | console.error('Invalid config file', configPath, err); | ||
153 | return; | ||
154 | } | ||
155 | loadingConfig = true; | ||
156 | try { | ||
157 | applySnapshot(store.config, configSnapshot); | ||
158 | } catch (err) { | ||
159 | console.error('Falied to apply snaphot', configPath, configSnapshot, err); | ||
160 | } finally { | ||
161 | loadingConfig = false; | ||
162 | } | ||
163 | } | ||
164 | |||
165 | async function saveConfig(configSnapshot: ConfigSnapshotOut): Promise<void> { | ||
166 | const configJson = JSON5.stringify(configSnapshot, { | ||
167 | space: 2, | ||
168 | }); | ||
169 | savingConfig = true; | ||
170 | try { | ||
171 | await writeFile(configPath, configJson, 'utf8'); | ||
172 | const stats = await stat(configPath); | ||
173 | configMtime = stats.mtime; | ||
174 | } finally { | ||
175 | savingConfig = false; | ||
176 | } | ||
177 | console.log('Wrote config', configPath); | ||
178 | } | ||
179 | |||
180 | onSnapshot(store.config, (snapshot) => { | ||
181 | if (!loadingConfig) { | ||
182 | saveConfig(snapshot).catch((err) => { | ||
183 | console.error('Failed to save config', configPath, err); | ||
184 | }); | ||
185 | } | ||
186 | }); | ||
187 | |||
188 | async function watchConfig(): Promise<void> { | ||
189 | const configWatcher = watch(userDataDir, { | ||
190 | persistent: false, | ||
191 | }); | ||
192 | for await (const { eventType, filename } of configWatcher) { | ||
193 | if (eventType !== 'change' | ||
194 | && (filename !== configFileName || filename !== null)) { | ||
195 | continue; | ||
196 | } | ||
197 | let mtime: Date; | ||
198 | try { | ||
199 | const stats = await stat(configPath); | ||
200 | mtime = stats.mtime; | ||
201 | } catch (err) { | ||
202 | if ((err as NodeJS.ErrnoException).code === 'ENOENT') { | ||
203 | continue; | ||
204 | } | ||
205 | throw err; | ||
206 | } | ||
207 | if (!savingConfig && (configMtime === null || mtime > configMtime)) { | ||
208 | await loadConfig(); | ||
209 | configMtime = mtime; | ||
210 | console.log('Reloaded config', configPath); | ||
211 | } | ||
212 | } | ||
213 | } | ||
214 | |||
215 | loadConfig().catch((err) => { | ||
216 | console.error('Failed to load config', configPath, err); | ||
217 | }).then(watchConfig).catch((err) => { | ||
218 | console.error('Error when watching for config changes', configPath, err); | ||
219 | }); | ||
220 | 125 | ||
221 | const rendererBaseUrl = getResourceUrl('../renderer/'); | 126 | const rendererBaseUrl = getResourceUrl('../renderer/'); |
222 | function shouldCancelMainWindowRequest(url: string, method: string): boolean { | 127 | function shouldCancelMainWindowRequest(url: string, method: string): boolean { |
diff --git a/packages/main/src/services/ConfigPersistence.ts b/packages/main/src/services/ConfigPersistence.ts new file mode 100644 index 0000000..f9a82de --- /dev/null +++ b/packages/main/src/services/ConfigPersistence.ts | |||
@@ -0,0 +1,36 @@ | |||
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 { IDisposer } from 'mobx-state-tree'; | ||
22 | import ms from 'ms'; | ||
23 | |||
24 | import type { ConfigSnapshotOut } from '../stores/Config'; | ||
25 | |||
26 | export const CONFIG_DEBOUNCE_TIME: number = ms('1s'); | ||
27 | |||
28 | export type ReadConfigResult = { found: true; data: unknown; } | { found: false; }; | ||
29 | |||
30 | export interface ConfigPersistence { | ||
31 | readConfig(): Promise<ReadConfigResult>; | ||
32 | |||
33 | writeConfig(configSnapshot: ConfigSnapshotOut): Promise<Date>; | ||
34 | |||
35 | watchConfig(callback: (mtime: Date) => Promise<void>): IDisposer; | ||
36 | } | ||
diff --git a/packages/main/src/services/MainEnv.ts b/packages/main/src/services/MainEnv.ts new file mode 100644 index 0000000..23ee9a1 --- /dev/null +++ b/packages/main/src/services/MainEnv.ts | |||
@@ -0,0 +1,38 @@ | |||
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 { IAnyStateTreeNode, getEnv as getAnyEnv } from 'mobx-state-tree'; | ||
22 | |||
23 | import type { ConfigPersistence } from './ConfigPersistence'; | ||
24 | |||
25 | export interface MainEnv { | ||
26 | configPersistence: ConfigPersistence; | ||
27 | } | ||
28 | |||
29 | /** | ||
30 | * Gets a well-typed environment from `model`. | ||
31 | * | ||
32 | * Only useable inside state trees created by `createAndConnectRootStore`. | ||
33 | * | ||
34 | * @param model The state tree node. | ||
35 | */ | ||
36 | export function getEnv(model: IAnyStateTreeNode): MainEnv { | ||
37 | return getAnyEnv<MainEnv>(model); | ||
38 | } | ||
diff --git a/packages/main/src/services/impl/ConfigPersistenceImpl.ts b/packages/main/src/services/impl/ConfigPersistenceImpl.ts new file mode 100644 index 0000000..097ab74 --- /dev/null +++ b/packages/main/src/services/impl/ConfigPersistenceImpl.ts | |||
@@ -0,0 +1,102 @@ | |||
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 { watch } from 'fs'; | ||
22 | import { readFile, stat, writeFile } from 'fs/promises'; | ||
23 | import JSON5 from 'json5'; | ||
24 | import { throttle } from 'lodash'; | ||
25 | import { IDisposer } from 'mobx-state-tree'; | ||
26 | import { join } from 'path'; | ||
27 | |||
28 | import { CONFIG_DEBOUNCE_TIME, ConfigPersistence, ReadConfigResult } from '../ConfigPersistence'; | ||
29 | import type { ConfigSnapshotOut } from '../../stores/Config'; | ||
30 | |||
31 | export class ConfigPersistenceImpl implements ConfigPersistence { | ||
32 | readonly #userDataDir: string; | ||
33 | |||
34 | readonly #configFileName: string; | ||
35 | |||
36 | readonly #configFilePath: string; | ||
37 | |||
38 | constructor( | ||
39 | userDataDir: string, | ||
40 | configFileName: string, | ||
41 | ) { | ||
42 | this.#userDataDir = userDataDir; | ||
43 | this.#configFileName = configFileName; | ||
44 | this.#configFilePath = join(this.#userDataDir, this.#configFileName); | ||
45 | } | ||
46 | |||
47 | async readConfig(): Promise<ReadConfigResult> { | ||
48 | let configStr; | ||
49 | try { | ||
50 | configStr = await readFile(this.#configFilePath, 'utf8'); | ||
51 | } catch (err) { | ||
52 | if ((err as NodeJS.ErrnoException).code === 'ENOENT') { | ||
53 | return { found: false }; | ||
54 | } | ||
55 | throw err; | ||
56 | } | ||
57 | return { | ||
58 | found: true, | ||
59 | data: JSON5.parse(configStr), | ||
60 | }; | ||
61 | } | ||
62 | |||
63 | async writeConfig(configSnapshot: ConfigSnapshotOut): Promise<Date> { | ||
64 | const configJson = JSON5.stringify(configSnapshot, { | ||
65 | space: 2, | ||
66 | }); | ||
67 | await writeFile(this.#configFilePath, configJson, 'utf8'); | ||
68 | const { mtime } = await stat(this.#configFilePath); | ||
69 | return mtime; | ||
70 | } | ||
71 | |||
72 | watchConfig(callback: (mtime: Date) => Promise<void>): IDisposer { | ||
73 | const configChanged = throttle(async () => { | ||
74 | let mtime: Date; | ||
75 | try { | ||
76 | const stats = await stat(this.#configFilePath); | ||
77 | mtime = stats.mtime; | ||
78 | } catch (err) { | ||
79 | if ((err as NodeJS.ErrnoException).code === 'ENOENT') { | ||
80 | return; | ||
81 | } | ||
82 | throw err; | ||
83 | } | ||
84 | return callback(mtime); | ||
85 | }, CONFIG_DEBOUNCE_TIME); | ||
86 | |||
87 | const watcher = watch(this.#userDataDir, { | ||
88 | persistent: false, | ||
89 | }); | ||
90 | |||
91 | watcher.on('change', (eventType, filename) => { | ||
92 | if (eventType === 'change' | ||
93 | && (filename === this.#configFileName || filename === null)) { | ||
94 | configChanged()?.catch((err) => { | ||
95 | console.log('Unhandled error while listening for config changes', err); | ||
96 | }); | ||
97 | } | ||
98 | }); | ||
99 | |||
100 | return () => watcher.close(); | ||
101 | } | ||
102 | } | ||
diff --git a/packages/main/src/stores/Config.ts b/packages/main/src/stores/Config.ts index 483a491..eb53635 100644 --- a/packages/main/src/stores/Config.ts +++ b/packages/main/src/stores/Config.ts | |||
@@ -18,15 +18,103 @@ | |||
18 | * SPDX-License-Identifier: AGPL-3.0-only | 18 | * SPDX-License-Identifier: AGPL-3.0-only |
19 | */ | 19 | */ |
20 | 20 | ||
21 | import type { Instance } from 'mobx-state-tree'; | 21 | import { debounce } from 'lodash'; |
22 | import { config as originalConfig, ThemeSource } from '@sophie/shared'; | 22 | import { |
23 | applySnapshot, | ||
24 | flow, | ||
25 | getSnapshot, | ||
26 | IDisposer, | ||
27 | Instance, | ||
28 | onSnapshot, | ||
29 | } from 'mobx-state-tree'; | ||
30 | import { | ||
31 | config as originalConfig, | ||
32 | ConfigSnapshotIn, | ||
33 | ConfigSnapshotOut, | ||
34 | ThemeSource, | ||
35 | } from '@sophie/shared'; | ||
23 | 36 | ||
24 | export type { ConfigSnapshotIn, ConfigSnapshotOut } from '@sophie/shared'; | 37 | import { CONFIG_DEBOUNCE_TIME, ReadConfigResult } from '../services/ConfigPersistence'; |
38 | import { getEnv } from '../services/MainEnv'; | ||
25 | 39 | ||
26 | export const config = originalConfig.actions((self) => ({ | 40 | export const config = originalConfig.actions((self) => ({ |
27 | setThemeSource(mode: ThemeSource) { | 41 | setThemeSource(mode: ThemeSource) { |
28 | self.themeSource = mode; | 42 | self.themeSource = mode; |
29 | }, | 43 | }, |
30 | })); | 44 | })).actions((self) => { |
45 | let lastSnapshotOnDisk: ConfigSnapshotOut | null = null; | ||
46 | let writingConfig = false; | ||
47 | let configMtime: Date | null = null; | ||
48 | let onSnapshotDisposer: IDisposer | null = null; | ||
49 | let watcherDisposer: IDisposer | null = null; | ||
50 | |||
51 | function dispose() { | ||
52 | onSnapshotDisposer?.(); | ||
53 | watcherDisposer?.(); | ||
54 | } | ||
55 | |||
56 | const actions: { | ||
57 | beforeDetach(): void, | ||
58 | readConfig(): Promise<boolean>; | ||
59 | writeConfig(): Promise<void>; | ||
60 | initConfig(): Promise<void>; | ||
61 | } = { | ||
62 | beforeDetach() { | ||
63 | dispose(); | ||
64 | }, | ||
65 | readConfig: flow(function*() { | ||
66 | const result: ReadConfigResult = yield getEnv(self).configPersistence.readConfig(); | ||
67 | if (result.found) { | ||
68 | try { | ||
69 | applySnapshot(self, result.data); | ||
70 | lastSnapshotOnDisk = getSnapshot(self); | ||
71 | console.log('Loaded config'); | ||
72 | } catch (err) { | ||
73 | console.error('Failed to read config', result.data, err); | ||
74 | } | ||
75 | } | ||
76 | return result.found; | ||
77 | }), | ||
78 | writeConfig: flow(function*() { | ||
79 | const snapshot = getSnapshot(self); | ||
80 | writingConfig = true; | ||
81 | try { | ||
82 | configMtime = yield getEnv(self).configPersistence.writeConfig(snapshot); | ||
83 | lastSnapshotOnDisk = snapshot; | ||
84 | console.log('Wrote config'); | ||
85 | } finally { | ||
86 | writingConfig = false; | ||
87 | } | ||
88 | }), | ||
89 | initConfig: flow(function*() { | ||
90 | dispose(); | ||
91 | const foundConfig: boolean = yield actions.readConfig(); | ||
92 | if (!foundConfig) { | ||
93 | console.log('Creating new config file'); | ||
94 | try { | ||
95 | yield actions.writeConfig(); | ||
96 | } catch (err) { | ||
97 | console.error('Failed to initialize config'); | ||
98 | } | ||
99 | } | ||
100 | onSnapshotDisposer = onSnapshot(self, debounce((snapshot) => { | ||
101 | // We can compare snapshots by reference, since it is only recreated on store changes. | ||
102 | if (lastSnapshotOnDisk !== snapshot) { | ||
103 | actions.writeConfig().catch((err) => { | ||
104 | console.log('Failed to write config on config change', err); | ||
105 | }) | ||
106 | } | ||
107 | }, CONFIG_DEBOUNCE_TIME)); | ||
108 | watcherDisposer = getEnv(self).configPersistence.watchConfig(async (mtime) => { | ||
109 | if (!writingConfig && (configMtime === null || mtime > configMtime)) { | ||
110 | await actions.readConfig(); | ||
111 | } | ||
112 | }); | ||
113 | }), | ||
114 | }; | ||
115 | return actions; | ||
116 | }); | ||
31 | 117 | ||
32 | export interface Config extends Instance<typeof config> {} | 118 | export interface Config extends Instance<typeof config> {} |
119 | |||
120 | export type { ConfigSnapshotIn, ConfigSnapshotOut }; | ||
diff --git a/packages/main/src/stores/RootStore.ts b/packages/main/src/stores/MainStore.ts index 31e2b71..ee215a7 100644 --- a/packages/main/src/stores/RootStore.ts +++ b/packages/main/src/stores/MainStore.ts | |||
@@ -19,15 +19,13 @@ | |||
19 | */ | 19 | */ |
20 | 20 | ||
21 | import { applySnapshot, Instance, types } from 'mobx-state-tree'; | 21 | import { applySnapshot, Instance, types } from 'mobx-state-tree'; |
22 | import { | 22 | import { BrowserViewBounds, emptySharedStore } from '@sophie/shared'; |
23 | BrowserViewBounds, | ||
24 | emptySharedStore, | ||
25 | } from '@sophie/shared'; | ||
26 | 23 | ||
27 | import type { Config } from './Config'; | 24 | import type { Config } from './Config'; |
25 | import { MainEnv } from '../services/MainEnv'; | ||
28 | import { sharedStore } from './SharedStore'; | 26 | import { sharedStore } from './SharedStore'; |
29 | 27 | ||
30 | export const rootStore = types.model('RootStore', { | 28 | export const mainStore = types.model('MainStore', { |
31 | browserViewBounds: types.model('BrowserViewBounds', { | 29 | browserViewBounds: types.model('BrowserViewBounds', { |
32 | x: 0, | 30 | x: 0, |
33 | y: 0, | 31 | y: 0, |
@@ -48,11 +46,14 @@ export const rootStore = types.model('RootStore', { | |||
48 | } | 46 | } |
49 | })); | 47 | })); |
50 | 48 | ||
51 | export interface RootStore extends Instance<typeof rootStore> {} | 49 | export interface RootStore extends Instance<typeof mainStore> {} |
52 | 50 | ||
53 | export function createRootStore(): RootStore { | 51 | export function createMainStore(env: MainEnv): RootStore { |
54 | return rootStore.create({ | 52 | return mainStore.create( |
55 | browserViewBounds: {}, | 53 | { |
56 | shared: emptySharedStore, | 54 | browserViewBounds: {}, |
57 | }); | 55 | shared: emptySharedStore, |
56 | }, | ||
57 | env, | ||
58 | ); | ||
58 | } | 59 | } |
@@ -838,12 +838,16 @@ __metadata: | |||
838 | "@sophie/service-shared": "workspace:*" | 838 | "@sophie/service-shared": "workspace:*" |
839 | "@sophie/shared": "workspace:*" | 839 | "@sophie/shared": "workspace:*" |
840 | "@types/electron-devtools-installer": ^2.2.1 | 840 | "@types/electron-devtools-installer": ^2.2.1 |
841 | "@types/lodash": ^4.14.178 | ||
842 | "@types/ms": ^0.7.31 | ||
841 | "@types/node": ^17.0.4 | 843 | "@types/node": ^17.0.4 |
842 | electron: 16.0.5 | 844 | electron: 16.0.5 |
843 | electron-devtools-installer: ^3.2.0 | 845 | electron-devtools-installer: ^3.2.0 |
844 | json5: ^2.2.0 | 846 | json5: ^2.2.0 |
847 | lodash: ^4.17.21 | ||
845 | mobx: ^6.3.10 | 848 | mobx: ^6.3.10 |
846 | mobx-state-tree: ^5.1.0 | 849 | mobx-state-tree: ^5.1.0 |
850 | ms: ^2.1.3 | ||
847 | rimraf: ^3.0.2 | 851 | rimraf: ^3.0.2 |
848 | typescript: ^4.5.4 | 852 | typescript: ^4.5.4 |
849 | vite: ^2.7.6 | 853 | vite: ^2.7.6 |
@@ -1027,7 +1031,7 @@ __metadata: | |||
1027 | languageName: node | 1031 | languageName: node |
1028 | linkType: hard | 1032 | linkType: hard |
1029 | 1033 | ||
1030 | "@types/ms@npm:*": | 1034 | "@types/ms@npm:*, @types/ms@npm:^0.7.31": |
1031 | version: 0.7.31 | 1035 | version: 0.7.31 |
1032 | resolution: "@types/ms@npm:0.7.31" | 1036 | resolution: "@types/ms@npm:0.7.31" |
1033 | checksum: daadd354aedde024cce6f5aa873fefe7b71b22cd0e28632a69e8b677aeb48ae8caa1c60e5919bb781df040d116b01cb4316335167a3fc0ef6a63fa3614c0f6da | 1037 | checksum: daadd354aedde024cce6f5aa873fefe7b71b22cd0e28632a69e8b677aeb48ae8caa1c60e5919bb781df040d116b01cb4316335167a3fc0ef6a63fa3614c0f6da |
@@ -4088,7 +4092,7 @@ __metadata: | |||
4088 | languageName: node | 4092 | languageName: node |
4089 | linkType: hard | 4093 | linkType: hard |
4090 | 4094 | ||
4091 | "ms@npm:^2.0.0": | 4095 | "ms@npm:^2.0.0, ms@npm:^2.1.3": |
4092 | version: 2.1.3 | 4096 | version: 2.1.3 |
4093 | resolution: "ms@npm:2.1.3" | 4097 | resolution: "ms@npm:2.1.3" |
4094 | checksum: aa92de608021b242401676e35cfa5aa42dd70cbdc082b916da7fb925c542173e36bce97ea3e804923fe92c0ad991434e4a38327e15a1b5b5f945d66df615ae6d | 4098 | checksum: aa92de608021b242401676e35cfa5aa42dd70cbdc082b916da7fb925c542173e36bce97ea3e804923fe92c0ad991434e4a38327e15a1b5b5f945d66df615ae6d |