diff options
author | Kristóf Marussy <kristof@marussy.com> | 2022-01-23 17:12:47 +0100 |
---|---|---|
committer | Kristóf Marussy <kristof@marussy.com> | 2022-02-08 21:43:17 +0100 |
commit | 044b2de8c7861504704468ba441d4a6a37eed8f7 (patch) | |
tree | 940d2b7946d07a0c69b5e3ad46c25c599cf2aca7 /packages | |
parent | feat: Add selected service field to SharedStore (diff) | |
download | sophie-044b2de8c7861504704468ba441d4a6a37eed8f7.tar.gz sophie-044b2de8c7861504704468ba441d4a6a37eed8f7.tar.zst sophie-044b2de8c7861504704468ba441d4a6a37eed8f7.zip |
refactor: Move runtime state into shared models
Now the runtime state lives inside the model (instead of being
associated to the static settings via a map), which simplifies state
management. Static settings are now located inside the runtime models,
so we must create tests to make sure that the settings are being
persisted correctly. The contents of the config file are now generated
as a view of store (instead of a snapshot), which adds flexibility.
Signed-off-by: Kristóf Marussy <kristof@marussy.com>
Diffstat (limited to 'packages')
33 files changed, 496 insertions, 622 deletions
diff --git a/packages/main/src/controllers/__tests__/initConfig.spec.ts b/packages/main/src/controllers/__tests__/initConfig.spec.ts index dc00b9d..fdd22c9 100644 --- a/packages/main/src/controllers/__tests__/initConfig.spec.ts +++ b/packages/main/src/controllers/__tests__/initConfig.spec.ts | |||
@@ -20,16 +20,15 @@ | |||
20 | 20 | ||
21 | import { jest } from '@jest/globals'; | 21 | import { jest } from '@jest/globals'; |
22 | import { mocked } from 'jest-mock'; | 22 | import { mocked } from 'jest-mock'; |
23 | import { getSnapshot } from 'mobx-state-tree'; | ||
24 | import ms from 'ms'; | 23 | import ms from 'ms'; |
25 | 24 | ||
26 | import type ConfigPersistence from '../../infrastructure/ConfigPersistence'; | 25 | import type ConfigPersistence from '../../infrastructure/ConfigPersistence'; |
27 | import { Config, config as configModel } from '../../stores/Config'; | 26 | import { sharedStore, SharedStore } from '../../stores/SharedStore'; |
28 | import type Disposer from '../../utils/Disposer'; | 27 | import type Disposer from '../../utils/Disposer'; |
29 | import { silenceLogger } from '../../utils/log'; | 28 | import { silenceLogger } from '../../utils/log'; |
30 | import initConfig from '../initConfig'; | 29 | import initConfig from '../initConfig'; |
31 | 30 | ||
32 | let config: Config; | 31 | let store: SharedStore; |
33 | const persistenceService: ConfigPersistence = { | 32 | const persistenceService: ConfigPersistence = { |
34 | readConfig: jest.fn(), | 33 | readConfig: jest.fn(), |
35 | writeConfig: jest.fn(), | 34 | writeConfig: jest.fn(), |
@@ -44,7 +43,7 @@ beforeAll(() => { | |||
44 | }); | 43 | }); |
45 | 44 | ||
46 | beforeEach(() => { | 45 | beforeEach(() => { |
47 | config = configModel.create(); | 46 | store = sharedStore.create(); |
48 | }); | 47 | }); |
49 | 48 | ||
50 | describe('when initializing', () => { | 49 | describe('when initializing', () => { |
@@ -56,7 +55,7 @@ describe('when initializing', () => { | |||
56 | }); | 55 | }); |
57 | 56 | ||
58 | it('should create a new config file', async () => { | 57 | it('should create a new config file', async () => { |
59 | await initConfig(config, persistenceService); | 58 | await initConfig(store, persistenceService); |
60 | expect(persistenceService.writeConfig).toHaveBeenCalledTimes(1); | 59 | expect(persistenceService.writeConfig).toHaveBeenCalledTimes(1); |
61 | }); | 60 | }); |
62 | 61 | ||
@@ -65,7 +64,7 @@ describe('when initializing', () => { | |||
65 | new Error('boo'), | 64 | new Error('boo'), |
66 | ); | 65 | ); |
67 | await expect(() => | 66 | await expect(() => |
68 | initConfig(config, persistenceService), | 67 | initConfig(store, persistenceService), |
69 | ).rejects.toBeInstanceOf(Error); | 68 | ).rejects.toBeInstanceOf(Error); |
70 | }); | 69 | }); |
71 | }); | 70 | }); |
@@ -76,16 +75,16 @@ describe('when initializing', () => { | |||
76 | found: true, | 75 | found: true, |
77 | data: { | 76 | data: { |
78 | // Use a default empty config file to not trigger config rewrite. | 77 | // Use a default empty config file to not trigger config rewrite. |
79 | ...getSnapshot(config), | 78 | ...store.config, |
80 | themeSource: 'dark', | 79 | themeSource: 'dark', |
81 | }, | 80 | }, |
82 | }); | 81 | }); |
83 | }); | 82 | }); |
84 | 83 | ||
85 | it('should read the existing config file is there is one', async () => { | 84 | it('should read the existing config file is there is one', async () => { |
86 | await initConfig(config, persistenceService); | 85 | await initConfig(store, persistenceService); |
87 | expect(persistenceService.writeConfig).not.toHaveBeenCalled(); | 86 | expect(persistenceService.writeConfig).not.toHaveBeenCalled(); |
88 | expect(config.themeSource).toBe('dark'); | 87 | expect(store.settings.themeSource).toBe('dark'); |
89 | }); | 88 | }); |
90 | 89 | ||
91 | it('should bail if it cannot set up a watcher', async () => { | 90 | it('should bail if it cannot set up a watcher', async () => { |
@@ -93,7 +92,7 @@ describe('when initializing', () => { | |||
93 | throw new Error('boo'); | 92 | throw new Error('boo'); |
94 | }); | 93 | }); |
95 | await expect(() => | 94 | await expect(() => |
96 | initConfig(config, persistenceService), | 95 | initConfig(store, persistenceService), |
97 | ).rejects.toBeInstanceOf(Error); | 96 | ).rejects.toBeInstanceOf(Error); |
98 | }); | 97 | }); |
99 | }); | 98 | }); |
@@ -108,7 +107,7 @@ describe('when initializing', () => { | |||
108 | }, | 107 | }, |
109 | }, | 108 | }, |
110 | }); | 109 | }); |
111 | await initConfig(config, persistenceService); | 110 | await initConfig(store, persistenceService); |
112 | expect(persistenceService.writeConfig).toHaveBeenCalledTimes(1); | 111 | expect(persistenceService.writeConfig).toHaveBeenCalledTimes(1); |
113 | }); | 112 | }); |
114 | 113 | ||
@@ -119,15 +118,15 @@ describe('when initializing', () => { | |||
119 | themeSource: -1, | 118 | themeSource: -1, |
120 | }, | 119 | }, |
121 | }); | 120 | }); |
122 | await initConfig(config, persistenceService); | 121 | await initConfig(store, persistenceService); |
123 | expect(config.themeSource).not.toBe(-1); | 122 | expect(store.settings.themeSource).not.toBe(-1); |
124 | expect(persistenceService.writeConfig).not.toHaveBeenCalled(); | 123 | expect(persistenceService.writeConfig).not.toHaveBeenCalled(); |
125 | }); | 124 | }); |
126 | 125 | ||
127 | it('should bail if it cannot determine whether there is a config file', async () => { | 126 | it('should bail if it cannot determine whether there is a config file', async () => { |
128 | mocked(persistenceService.readConfig).mockRejectedValue(new Error('boo')); | 127 | mocked(persistenceService.readConfig).mockRejectedValue(new Error('boo')); |
129 | await expect(() => | 128 | await expect(() => |
130 | initConfig(config, persistenceService), | 129 | initConfig(store, persistenceService), |
131 | ).rejects.toBeInstanceOf(Error); | 130 | ).rejects.toBeInstanceOf(Error); |
132 | }); | 131 | }); |
133 | }); | 132 | }); |
@@ -140,10 +139,10 @@ describe('when it has loaded the config', () => { | |||
140 | beforeEach(async () => { | 139 | beforeEach(async () => { |
141 | mocked(persistenceService.readConfig).mockResolvedValueOnce({ | 140 | mocked(persistenceService.readConfig).mockResolvedValueOnce({ |
142 | found: true, | 141 | found: true, |
143 | data: getSnapshot(config), | 142 | data: store.config, |
144 | }); | 143 | }); |
145 | mocked(persistenceService.watchConfig).mockReturnValueOnce(watcherDisposer); | 144 | mocked(persistenceService.watchConfig).mockReturnValueOnce(watcherDisposer); |
146 | sutDisposer = await initConfig(config, persistenceService, throttleMs); | 145 | sutDisposer = await initConfig(store, persistenceService, throttleMs); |
147 | [[configChangedCallback]] = mocked( | 146 | [[configChangedCallback]] = mocked( |
148 | persistenceService.watchConfig, | 147 | persistenceService.watchConfig, |
149 | ).mock.calls; | 148 | ).mock.calls; |
@@ -152,16 +151,16 @@ describe('when it has loaded the config', () => { | |||
152 | 151 | ||
153 | it('should throttle saving changes to the config file', () => { | 152 | it('should throttle saving changes to the config file', () => { |
154 | mocked(persistenceService.writeConfig).mockResolvedValue(); | 153 | mocked(persistenceService.writeConfig).mockResolvedValue(); |
155 | config.setThemeSource('dark'); | 154 | store.settings.setThemeSource('dark'); |
156 | jest.advanceTimersByTime(lessThanThrottleMs); | 155 | jest.advanceTimersByTime(lessThanThrottleMs); |
157 | config.setThemeSource('light'); | 156 | store.settings.setThemeSource('light'); |
158 | jest.advanceTimersByTime(throttleMs); | 157 | jest.advanceTimersByTime(throttleMs); |
159 | expect(persistenceService.writeConfig).toHaveBeenCalledTimes(1); | 158 | expect(persistenceService.writeConfig).toHaveBeenCalledTimes(1); |
160 | }); | 159 | }); |
161 | 160 | ||
162 | it('should handle config writing errors gracefully', () => { | 161 | it('should handle config writing errors gracefully', () => { |
163 | mocked(persistenceService.writeConfig).mockRejectedValue(new Error('boo')); | 162 | mocked(persistenceService.writeConfig).mockRejectedValue(new Error('boo')); |
164 | config.setThemeSource('dark'); | 163 | store.settings.setThemeSource('dark'); |
165 | jest.advanceTimersByTime(throttleMs); | 164 | jest.advanceTimersByTime(throttleMs); |
166 | expect(persistenceService.writeConfig).toHaveBeenCalledTimes(1); | 165 | expect(persistenceService.writeConfig).toHaveBeenCalledTimes(1); |
167 | }); | 166 | }); |
@@ -171,14 +170,14 @@ describe('when it has loaded the config', () => { | |||
171 | found: true, | 170 | found: true, |
172 | data: { | 171 | data: { |
173 | // Use a default empty config file to not trigger config rewrite. | 172 | // Use a default empty config file to not trigger config rewrite. |
174 | ...getSnapshot(config), | 173 | ...store.config, |
175 | themeSource: 'dark', | 174 | themeSource: 'dark', |
176 | }, | 175 | }, |
177 | }); | 176 | }); |
178 | await configChangedCallback(); | 177 | await configChangedCallback(); |
179 | // Do not write back the changes we have just read. | 178 | // Do not write back the changes we have just read. |
180 | expect(persistenceService.writeConfig).not.toHaveBeenCalled(); | 179 | expect(persistenceService.writeConfig).not.toHaveBeenCalled(); |
181 | expect(config.themeSource).toBe('dark'); | 180 | expect(store.settings.themeSource).toBe('dark'); |
182 | }); | 181 | }); |
183 | 182 | ||
184 | it('should update the config file if new details are added', async () => { | 183 | it('should update the config file if new details are added', async () => { |
@@ -203,7 +202,7 @@ describe('when it has loaded the config', () => { | |||
203 | }, | 202 | }, |
204 | }); | 203 | }); |
205 | await configChangedCallback(); | 204 | await configChangedCallback(); |
206 | expect(config.themeSource).not.toBe(-1); | 205 | expect(store.settings.themeSource).not.toBe(-1); |
207 | expect(persistenceService.writeConfig).not.toHaveBeenCalled(); | 206 | expect(persistenceService.writeConfig).not.toHaveBeenCalled(); |
208 | }); | 207 | }); |
209 | 208 | ||
@@ -222,7 +221,7 @@ describe('when it has loaded the config', () => { | |||
222 | }); | 221 | }); |
223 | 222 | ||
224 | it('should not listen to store changes any more', () => { | 223 | it('should not listen to store changes any more', () => { |
225 | config.setThemeSource('dark'); | 224 | store.settings.setThemeSource('dark'); |
226 | jest.advanceTimersByTime(2 * throttleMs); | 225 | jest.advanceTimersByTime(2 * throttleMs); |
227 | expect(persistenceService.writeConfig).not.toHaveBeenCalled(); | 226 | expect(persistenceService.writeConfig).not.toHaveBeenCalled(); |
228 | }); | 227 | }); |
diff --git a/packages/main/src/controllers/__tests__/initNativeTheme.spec.ts b/packages/main/src/controllers/__tests__/initNativeTheme.spec.ts index 16acca5..9107c78 100644 --- a/packages/main/src/controllers/__tests__/initNativeTheme.spec.ts +++ b/packages/main/src/controllers/__tests__/initNativeTheme.spec.ts | |||
@@ -21,7 +21,7 @@ | |||
21 | import { jest } from '@jest/globals'; | 21 | import { jest } from '@jest/globals'; |
22 | import { mocked } from 'jest-mock'; | 22 | import { mocked } from 'jest-mock'; |
23 | 23 | ||
24 | import { createMainStore, MainStore } from '../../stores/MainStore'; | 24 | import { sharedStore, SharedStore } from '../../stores/SharedStore'; |
25 | import type Disposer from '../../utils/Disposer'; | 25 | import type Disposer from '../../utils/Disposer'; |
26 | 26 | ||
27 | let shouldUseDarkColors = false; | 27 | let shouldUseDarkColors = false; |
@@ -40,11 +40,11 @@ jest.unstable_mockModule('electron', () => ({ | |||
40 | const { nativeTheme } = await import('electron'); | 40 | const { nativeTheme } = await import('electron'); |
41 | const { default: initNativeTheme } = await import('../initNativeTheme'); | 41 | const { default: initNativeTheme } = await import('../initNativeTheme'); |
42 | 42 | ||
43 | let store: MainStore; | 43 | let store: SharedStore; |
44 | let disposeSut: Disposer; | 44 | let disposeSut: Disposer; |
45 | 45 | ||
46 | beforeEach(() => { | 46 | beforeEach(() => { |
47 | store = createMainStore(); | 47 | store = sharedStore.create(); |
48 | disposeSut = initNativeTheme(store); | 48 | disposeSut = initNativeTheme(store); |
49 | }); | 49 | }); |
50 | 50 | ||
@@ -53,7 +53,7 @@ it('should register a nativeTheme updated listener', () => { | |||
53 | }); | 53 | }); |
54 | 54 | ||
55 | it('should synchronize themeSource changes to the nativeTheme', () => { | 55 | it('should synchronize themeSource changes to the nativeTheme', () => { |
56 | store.config.setThemeSource('dark'); | 56 | store.settings.setThemeSource('dark'); |
57 | expect(nativeTheme.themeSource).toBe('dark'); | 57 | expect(nativeTheme.themeSource).toBe('dark'); |
58 | }); | 58 | }); |
59 | 59 | ||
@@ -63,7 +63,7 @@ it('should synchronize shouldUseDarkColors changes to the store', () => { | |||
63 | )![1]; | 63 | )![1]; |
64 | shouldUseDarkColors = true; | 64 | shouldUseDarkColors = true; |
65 | listener(); | 65 | listener(); |
66 | expect(store.shared.shouldUseDarkColors).toBe(true); | 66 | expect(store.shouldUseDarkColors).toBe(true); |
67 | }); | 67 | }); |
68 | 68 | ||
69 | it('should remove the listener on dispose', () => { | 69 | it('should remove the listener on dispose', () => { |
diff --git a/packages/main/src/controllers/initConfig.ts b/packages/main/src/controllers/initConfig.ts index c8cd335..55bf6df 100644 --- a/packages/main/src/controllers/initConfig.ts +++ b/packages/main/src/controllers/initConfig.ts | |||
@@ -20,11 +20,11 @@ | |||
20 | 20 | ||
21 | import deepEqual from 'deep-equal'; | 21 | import deepEqual from 'deep-equal'; |
22 | import { debounce } from 'lodash-es'; | 22 | import { debounce } from 'lodash-es'; |
23 | import { getSnapshot, onSnapshot } from 'mobx-state-tree'; | 23 | import { reaction } from 'mobx'; |
24 | import ms from 'ms'; | 24 | import ms from 'ms'; |
25 | 25 | ||
26 | import type ConfigPersistence from '../infrastructure/ConfigPersistence'; | 26 | import type ConfigPersistence from '../infrastructure/ConfigPersistence'; |
27 | import { Config, ConfigFileIn, ConfigSnapshotOut } from '../stores/Config'; | 27 | import { Config, SharedStore } from '../stores/SharedStore'; |
28 | import type Disposer from '../utils/Disposer'; | 28 | import type Disposer from '../utils/Disposer'; |
29 | import { getLogger } from '../utils/log'; | 29 | import { getLogger } from '../utils/log'; |
30 | 30 | ||
@@ -33,18 +33,18 @@ const DEFAULT_CONFIG_DEBOUNCE_TIME = ms('1s'); | |||
33 | const log = getLogger('config'); | 33 | const log = getLogger('config'); |
34 | 34 | ||
35 | export default async function initConfig( | 35 | export default async function initConfig( |
36 | config: Config, | 36 | sharedStore: SharedStore, |
37 | persistenceService: ConfigPersistence, | 37 | persistenceService: ConfigPersistence, |
38 | debounceTime: number = DEFAULT_CONFIG_DEBOUNCE_TIME, | 38 | debounceTime: number = DEFAULT_CONFIG_DEBOUNCE_TIME, |
39 | ): Promise<Disposer> { | 39 | ): Promise<Disposer> { |
40 | log.trace('Initializing config controller'); | 40 | log.trace('Initializing config controller'); |
41 | 41 | ||
42 | let lastSnapshotOnDisk: ConfigSnapshotOut | undefined; | 42 | let lastConfigOnDisk: Config | undefined; |
43 | 43 | ||
44 | async function writeConfig(): Promise<void> { | 44 | async function writeConfig(): Promise<void> { |
45 | const snapshot = getSnapshot(config); | 45 | const { config } = sharedStore; |
46 | await persistenceService.writeConfig(snapshot); | 46 | await persistenceService.writeConfig(config); |
47 | lastSnapshotOnDisk = snapshot; | 47 | lastConfigOnDisk = config; |
48 | } | 48 | } |
49 | 49 | ||
50 | async function readConfig(): Promise<boolean> { | 50 | async function readConfig(): Promise<boolean> { |
@@ -53,13 +53,13 @@ export default async function initConfig( | |||
53 | try { | 53 | try { |
54 | // This cast is unsound if the config file is invalid, | 54 | // This cast is unsound if the config file is invalid, |
55 | // but we'll throw an error in the end anyways. | 55 | // but we'll throw an error in the end anyways. |
56 | config.loadFromConfigFile(result.data as ConfigFileIn); | 56 | sharedStore.loadConfig(result.data as Config); |
57 | } catch (error) { | 57 | } catch (error) { |
58 | log.error('Failed to apply config snapshot', result.data, error); | 58 | log.error('Failed to apply config snapshot', result.data, error); |
59 | return true; | 59 | return true; |
60 | } | 60 | } |
61 | lastSnapshotOnDisk = getSnapshot(config); | 61 | lastConfigOnDisk = sharedStore.config; |
62 | if (!deepEqual(result.data, lastSnapshotOnDisk, { strict: true })) { | 62 | if (!deepEqual(result.data, lastConfigOnDisk, { strict: true })) { |
63 | await writeConfig(); | 63 | await writeConfig(); |
64 | } | 64 | } |
65 | } | 65 | } |
@@ -72,11 +72,11 @@ export default async function initConfig( | |||
72 | log.info('Created config file'); | 72 | log.info('Created config file'); |
73 | } | 73 | } |
74 | 74 | ||
75 | const disposeOnSnapshot = onSnapshot( | 75 | const disposeReaction = reaction( |
76 | config, | 76 | () => sharedStore.config, |
77 | debounce((snapshot) => { | 77 | debounce((config) => { |
78 | // We can compare snapshots by reference, since it is only recreated on store changes. | 78 | // We can compare snapshots by reference, since it is only recreated on store changes. |
79 | if (lastSnapshotOnDisk !== snapshot) { | 79 | if (lastConfigOnDisk !== config) { |
80 | writeConfig().catch((error) => { | 80 | writeConfig().catch((error) => { |
81 | log.error('Failed to write config on config change', error); | 81 | log.error('Failed to write config on config change', error); |
82 | }); | 82 | }); |
@@ -95,6 +95,6 @@ export default async function initConfig( | |||
95 | return () => { | 95 | return () => { |
96 | log.trace('Disposing config controller'); | 96 | log.trace('Disposing config controller'); |
97 | disposeWatcher(); | 97 | disposeWatcher(); |
98 | disposeOnSnapshot(); | 98 | disposeReaction(); |
99 | }; | 99 | }; |
100 | } | 100 | } |
diff --git a/packages/main/src/controllers/initNativeTheme.ts b/packages/main/src/controllers/initNativeTheme.ts index d2074ab..ce7972a 100644 --- a/packages/main/src/controllers/initNativeTheme.ts +++ b/packages/main/src/controllers/initNativeTheme.ts | |||
@@ -21,18 +21,18 @@ | |||
21 | import { nativeTheme } from 'electron'; | 21 | import { nativeTheme } from 'electron'; |
22 | import { autorun } from 'mobx'; | 22 | import { autorun } from 'mobx'; |
23 | 23 | ||
24 | import type { MainStore } from '../stores/MainStore'; | 24 | import type { SharedStore } from '../stores/SharedStore'; |
25 | import type Disposer from '../utils/Disposer'; | 25 | import type Disposer from '../utils/Disposer'; |
26 | import { getLogger } from '../utils/log'; | 26 | import { getLogger } from '../utils/log'; |
27 | 27 | ||
28 | const log = getLogger('nativeTheme'); | 28 | const log = getLogger('nativeTheme'); |
29 | 29 | ||
30 | export default function initNativeTheme(store: MainStore): Disposer { | 30 | export default function initNativeTheme(store: SharedStore): Disposer { |
31 | log.trace('Initializing nativeTheme controller'); | 31 | log.trace('Initializing nativeTheme controller'); |
32 | 32 | ||
33 | const disposeThemeSourceReaction = autorun(() => { | 33 | const disposeThemeSourceReaction = autorun(() => { |
34 | nativeTheme.themeSource = store.config.themeSource; | 34 | nativeTheme.themeSource = store.settings.themeSource; |
35 | log.debug('Set theme source:', store.config.themeSource); | 35 | log.debug('Set theme source:', store.settings.themeSource); |
36 | }); | 36 | }); |
37 | 37 | ||
38 | store.setShouldUseDarkColors(nativeTheme.shouldUseDarkColors); | 38 | store.setShouldUseDarkColors(nativeTheme.shouldUseDarkColors); |
diff --git a/packages/main/src/index.ts b/packages/main/src/index.ts index 66405ea..0978cd9 100644 --- a/packages/main/src/index.ts +++ b/packages/main/src/index.ts | |||
@@ -270,13 +270,13 @@ async function createWindow(): Promise<unknown> { | |||
270 | const actionToDispatch = action.parse(rawAction); | 270 | const actionToDispatch = action.parse(rawAction); |
271 | switch (actionToDispatch.action) { | 271 | switch (actionToDispatch.action) { |
272 | case 'set-selected-service-id': | 272 | case 'set-selected-service-id': |
273 | store.setSelectedServiceId(actionToDispatch.serviceId); | 273 | store.shared.setSelectedServiceId(actionToDispatch.serviceId); |
274 | break; | 274 | break; |
275 | case 'set-browser-view-bounds': | 275 | case 'set-browser-view-bounds': |
276 | store.setBrowserViewBounds(actionToDispatch.browserViewBounds); | 276 | store.setBrowserViewBounds(actionToDispatch.browserViewBounds); |
277 | break; | 277 | break; |
278 | case 'set-theme-source': | 278 | case 'set-theme-source': |
279 | store.config.setThemeSource(actionToDispatch.themeSource); | 279 | store.settings.setThemeSource(actionToDispatch.themeSource); |
280 | break; | 280 | break; |
281 | case 'reload-all-services': | 281 | case 'reload-all-services': |
282 | reloadServiceInject().catch((error) => { | 282 | reloadServiceInject().catch((error) => { |
diff --git a/packages/main/src/infrastructure/ConfigPersistence.ts b/packages/main/src/infrastructure/ConfigPersistence.ts index 4b96f01..184fa8d 100644 --- a/packages/main/src/infrastructure/ConfigPersistence.ts +++ b/packages/main/src/infrastructure/ConfigPersistence.ts | |||
@@ -18,7 +18,7 @@ | |||
18 | * SPDX-License-Identifier: AGPL-3.0-only | 18 | * SPDX-License-Identifier: AGPL-3.0-only |
19 | */ | 19 | */ |
20 | 20 | ||
21 | import type { ConfigSnapshotOut } from '../stores/Config'; | 21 | import type { Config } from '../stores/SharedStore'; |
22 | import type Disposer from '../utils/Disposer'; | 22 | import type Disposer from '../utils/Disposer'; |
23 | 23 | ||
24 | export type ReadConfigResult = | 24 | export type ReadConfigResult = |
@@ -28,7 +28,7 @@ export type ReadConfigResult = | |||
28 | export default interface ConfigPersistence { | 28 | export default interface ConfigPersistence { |
29 | readConfig(): Promise<ReadConfigResult>; | 29 | readConfig(): Promise<ReadConfigResult>; |
30 | 30 | ||
31 | writeConfig(configSnapshot: ConfigSnapshotOut): Promise<void>; | 31 | writeConfig(config: Config): Promise<void>; |
32 | 32 | ||
33 | watchConfig(callback: () => Promise<void>, throttleMs: number): Disposer; | 33 | watchConfig(callback: () => Promise<void>, throttleMs: number): Disposer; |
34 | } | 34 | } |
diff --git a/packages/main/src/infrastructure/impl/FileBasedConfigPersistence.ts b/packages/main/src/infrastructure/impl/FileBasedConfigPersistence.ts index 06e3fab..88d8bf8 100644 --- a/packages/main/src/infrastructure/impl/FileBasedConfigPersistence.ts +++ b/packages/main/src/infrastructure/impl/FileBasedConfigPersistence.ts | |||
@@ -24,7 +24,7 @@ import path from 'node:path'; | |||
24 | import JSON5 from 'json5'; | 24 | import JSON5 from 'json5'; |
25 | import throttle from 'lodash-es/throttle'; | 25 | import throttle from 'lodash-es/throttle'; |
26 | 26 | ||
27 | import type { ConfigSnapshotOut } from '../../stores/Config'; | 27 | import type { Config } from '../../stores/SharedStore'; |
28 | import type Disposer from '../../utils/Disposer'; | 28 | import type Disposer from '../../utils/Disposer'; |
29 | import { getLogger } from '../../utils/log'; | 29 | import { getLogger } from '../../utils/log'; |
30 | import type ConfigPersistence from '../ConfigPersistence'; | 30 | import type ConfigPersistence from '../ConfigPersistence'; |
@@ -65,7 +65,7 @@ export default class FileBasedConfigPersistence implements ConfigPersistence { | |||
65 | }; | 65 | }; |
66 | } | 66 | } |
67 | 67 | ||
68 | async writeConfig(configSnapshot: ConfigSnapshotOut): Promise<void> { | 68 | async writeConfig(configSnapshot: Config): Promise<void> { |
69 | const configJson = JSON5.stringify(configSnapshot, { | 69 | const configJson = JSON5.stringify(configSnapshot, { |
70 | space: 2, | 70 | space: 2, |
71 | }); | 71 | }); |
diff --git a/packages/main/src/init.ts b/packages/main/src/init.ts index 236a075..fd8dd94 100644 --- a/packages/main/src/init.ts +++ b/packages/main/src/init.ts | |||
@@ -31,10 +31,10 @@ export default async function init(store: MainStore): Promise<Disposer> { | |||
31 | app.getPath('userData'), | 31 | app.getPath('userData'), |
32 | ); | 32 | ); |
33 | const disposeConfigController = await initConfig( | 33 | const disposeConfigController = await initConfig( |
34 | store.config, | 34 | store.shared, |
35 | configPersistenceService, | 35 | configPersistenceService, |
36 | ); | 36 | ); |
37 | const disposeNativeThemeController = initNativeTheme(store); | 37 | const disposeNativeThemeController = initNativeTheme(store.shared); |
38 | 38 | ||
39 | return () => { | 39 | return () => { |
40 | disposeNativeThemeController(); | 40 | disposeNativeThemeController(); |
diff --git a/packages/main/src/stores/Config.ts b/packages/main/src/stores/Config.ts deleted file mode 100644 index e7fc360..0000000 --- a/packages/main/src/stores/Config.ts +++ /dev/null | |||
@@ -1,58 +0,0 @@ | |||
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 { config as originalConfig, ThemeSource } from '@sophie/shared'; | ||
22 | import { applySnapshot, Instance, SnapshotIn } from 'mobx-state-tree'; | ||
23 | |||
24 | import { addMissingProfileIds, PartialProfileSnapshotIn } from './Profile'; | ||
25 | import { | ||
26 | addMissingServiceIdsAndProfiles, | ||
27 | PartialServiceSnapshotIn, | ||
28 | } from './Service'; | ||
29 | |||
30 | export const config = originalConfig.actions((self) => ({ | ||
31 | loadFromConfigFile(snapshot: ConfigFileIn): void { | ||
32 | const profiles = addMissingProfileIds(snapshot.profiles); | ||
33 | const services = addMissingServiceIdsAndProfiles( | ||
34 | snapshot.services, | ||
35 | profiles, | ||
36 | ); | ||
37 | applySnapshot(self, { | ||
38 | ...snapshot, | ||
39 | profiles, | ||
40 | services, | ||
41 | }); | ||
42 | }, | ||
43 | setThemeSource(mode: ThemeSource): void { | ||
44 | self.themeSource = mode; | ||
45 | }, | ||
46 | })); | ||
47 | |||
48 | export interface Config extends Instance<typeof config> {} | ||
49 | |||
50 | export interface ConfigSnapshotIn extends SnapshotIn<typeof config> {} | ||
51 | |||
52 | export interface ConfigFileIn | ||
53 | extends Omit<ConfigSnapshotIn, 'profiles' | 'services'> { | ||
54 | profiles?: PartialProfileSnapshotIn[] | undefined; | ||
55 | services?: PartialServiceSnapshotIn[] | undefined; | ||
56 | } | ||
57 | |||
58 | export type { ConfigSnapshotOut } from '@sophie/shared'; | ||
diff --git a/packages/renderer/src/stores/Service.ts b/packages/main/src/stores/GlobalSettings.ts index 2f45106..1eb13b3 100644 --- a/packages/renderer/src/stores/Service.ts +++ b/packages/main/src/stores/GlobalSettings.ts | |||
@@ -19,20 +19,20 @@ | |||
19 | */ | 19 | */ |
20 | 20 | ||
21 | import { | 21 | import { |
22 | runtimeService, | 22 | globalSettings as originalGlobalSettings, |
23 | RuntimeService, | 23 | ThemeSource, |
24 | service as originalService, | ||
25 | } from '@sophie/shared'; | 24 | } from '@sophie/shared'; |
26 | import { Instance } from 'mobx-state-tree'; | 25 | import { Instance } from 'mobx-state-tree'; |
27 | 26 | ||
28 | import getEnv from '../env/getEnv'; | 27 | export const globalSettings = originalGlobalSettings.actions((self) => ({ |
29 | 28 | setThemeSource(mode: ThemeSource): void { | |
30 | export const service = originalService.views((self) => ({ | 29 | self.themeSource = mode; |
31 | get runtime(): RuntimeService { | ||
32 | return getEnv(self).getRuntimeService(self.id) ?? runtimeService.create(); | ||
33 | }, | 30 | }, |
34 | })); | 31 | })); |
35 | 32 | ||
36 | export interface Service extends Instance<typeof service> {} | 33 | export interface GlobalSettings extends Instance<typeof globalSettings> {} |
37 | 34 | ||
38 | export type { ServiceSnapshotIn, ServiceSnapshotOut } from '@sophie/shared'; | 35 | export type { |
36 | GlobalSettingsSnapshotIn, | ||
37 | GlobalSettingsSnapshotOut, | ||
38 | } from '@sophie/shared'; | ||
diff --git a/packages/main/src/stores/MainStore.ts b/packages/main/src/stores/MainStore.ts index f0d6472..18f5bf9 100644 --- a/packages/main/src/stores/MainStore.ts +++ b/packages/main/src/stores/MainStore.ts | |||
@@ -18,21 +18,14 @@ | |||
18 | * SPDX-License-Identifier: AGPL-3.0-only | 18 | * SPDX-License-Identifier: AGPL-3.0-only |
19 | */ | 19 | */ |
20 | 20 | ||
21 | import { BrowserViewBounds, service } from '@sophie/shared'; | 21 | import type { BrowserViewBounds } from '@sophie/shared'; |
22 | import { | 22 | import { applySnapshot, Instance, types } from 'mobx-state-tree'; |
23 | applySnapshot, | ||
24 | Instance, | ||
25 | resolveIdentifier, | ||
26 | types, | ||
27 | } from 'mobx-state-tree'; | ||
28 | 23 | ||
29 | import { getLogger } from '../utils/log'; | 24 | import { GlobalSettings } from './GlobalSettings'; |
30 | 25 | import { Profile } from './Profile'; | |
31 | import type { Config } from './Config.js'; | 26 | import { Service } from './Service'; |
32 | import { sharedStore } from './SharedStore'; | 27 | import { sharedStore } from './SharedStore'; |
33 | 28 | ||
34 | const log = getLogger('mainStore'); | ||
35 | |||
36 | export const mainStore = types | 29 | export const mainStore = types |
37 | .model('MainStore', { | 30 | .model('MainStore', { |
38 | browserViewBounds: types.optional( | 31 | browserViewBounds: types.optional( |
@@ -47,26 +40,20 @@ export const mainStore = types | |||
47 | shared: types.optional(sharedStore, {}), | 40 | shared: types.optional(sharedStore, {}), |
48 | }) | 41 | }) |
49 | .views((self) => ({ | 42 | .views((self) => ({ |
50 | get config(): Config { | 43 | get settings(): GlobalSettings { |
51 | return self.shared.config; | 44 | return self.shared.settings; |
45 | }, | ||
46 | get profiles(): Profile[] { | ||
47 | return self.shared.profiles; | ||
48 | }, | ||
49 | get services(): Service[] { | ||
50 | return self.shared.services; | ||
52 | }, | 51 | }, |
53 | })) | 52 | })) |
54 | .actions((self) => ({ | 53 | .actions((self) => ({ |
55 | setSelectedServiceId(serviceId: string): void { | ||
56 | const serviceInstance = resolveIdentifier(service, self, serviceId); | ||
57 | if (serviceInstance === undefined) { | ||
58 | log.warn('Trying to select unknown service', serviceId); | ||
59 | return; | ||
60 | } | ||
61 | self.shared.selectedService = serviceInstance; | ||
62 | log.debug('Selected service', serviceId); | ||
63 | }, | ||
64 | setBrowserViewBounds(bounds: BrowserViewBounds): void { | 54 | setBrowserViewBounds(bounds: BrowserViewBounds): void { |
65 | applySnapshot(self.browserViewBounds, bounds); | 55 | applySnapshot(self.browserViewBounds, bounds); |
66 | }, | 56 | }, |
67 | setShouldUseDarkColors(shouldUseDarkColors: boolean): void { | ||
68 | self.shared.shouldUseDarkColors = shouldUseDarkColors; | ||
69 | }, | ||
70 | })); | 57 | })); |
71 | 58 | ||
72 | export interface MainStore extends Instance<typeof mainStore> {} | 59 | export interface MainStore extends Instance<typeof mainStore> {} |
diff --git a/packages/main/src/stores/Profile.ts b/packages/main/src/stores/Profile.ts index 4705862..eaf23c4 100644 --- a/packages/main/src/stores/Profile.ts +++ b/packages/main/src/stores/Profile.ts | |||
@@ -18,34 +18,39 @@ | |||
18 | * SPDX-License-Identifier: AGPL-3.0-only | 18 | * SPDX-License-Identifier: AGPL-3.0-only |
19 | */ | 19 | */ |
20 | 20 | ||
21 | import type { ProfileSnapshotIn } from '@sophie/shared'; | 21 | import { |
22 | profile as originalProfile, | ||
23 | ProfileSettingsSnapshotIn, | ||
24 | } from '@sophie/shared'; | ||
25 | import { getSnapshot, Instance } from 'mobx-state-tree'; | ||
22 | 26 | ||
27 | import SettingsWithId from '../utils/SettingsWithId'; | ||
23 | import generateId from '../utils/generateId'; | 28 | import generateId from '../utils/generateId'; |
24 | 29 | ||
25 | export interface PartialProfileSnapshotIn | 30 | export interface ProfileConfig extends ProfileSettingsSnapshotIn { |
26 | extends Omit<ProfileSnapshotIn, 'id'> { | ||
27 | id?: string | undefined; | 31 | id?: string | undefined; |
28 | } | 32 | } |
29 | 33 | ||
34 | export const profile = originalProfile.views((self) => ({ | ||
35 | get config(): ProfileConfig { | ||
36 | const { id, settings } = self; | ||
37 | return { ...getSnapshot(settings), id }; | ||
38 | }, | ||
39 | })); | ||
40 | |||
41 | export interface Profile extends Instance<typeof profile> {} | ||
42 | |||
43 | export type ProfileSettingsSnapshotWithId = | ||
44 | SettingsWithId<ProfileSettingsSnapshotIn>; | ||
45 | |||
30 | export function addMissingProfileIds( | 46 | export function addMissingProfileIds( |
31 | partialProfiles: PartialProfileSnapshotIn[] | undefined, | 47 | profileConfigs: ProfileConfig[] | undefined, |
32 | ): ProfileSnapshotIn[] { | 48 | ): ProfileSettingsSnapshotWithId[] { |
33 | return (partialProfiles ?? []).map((profile) => { | 49 | return (profileConfigs ?? []).map((profileConfig) => { |
34 | const { name } = profile; | 50 | const { id, ...settings } = profileConfig; |
35 | let { id } = profile; | ||
36 | if (typeof id === 'undefined') { | ||
37 | id = generateId(name); | ||
38 | } | ||
39 | return { | 51 | return { |
40 | ...profile, | 52 | id: typeof id === 'undefined' ? generateId(settings.name) : id, |
41 | id, | 53 | settings, |
42 | }; | 54 | }; |
43 | }); | 55 | }); |
44 | } | 56 | } |
45 | |||
46 | export type { | ||
47 | Profile, | ||
48 | ProfileSnapshotOut, | ||
49 | ProfileSnapshotIn, | ||
50 | } from '@sophie/shared'; | ||
51 | export { profile } from '@sophie/shared'; | ||
diff --git a/packages/main/src/stores/RuntimeService.ts b/packages/main/src/stores/RuntimeService.ts deleted file mode 100644 index ecb1942..0000000 --- a/packages/main/src/stores/RuntimeService.ts +++ /dev/null | |||
@@ -1,74 +0,0 @@ | |||
1 | /* | ||
2 | * Copyright (C) 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 { UnreadCount } from '@sophie/service-shared'; | ||
22 | import { runtimeService as originalRuntimeService } from '@sophie/shared'; | ||
23 | import { Instance } from 'mobx-state-tree'; | ||
24 | |||
25 | export const runtimeService = originalRuntimeService.actions((self) => ({ | ||
26 | setLocation({ | ||
27 | url, | ||
28 | canGoBack, | ||
29 | canGoForward, | ||
30 | }: { | ||
31 | url: string; | ||
32 | canGoBack: boolean; | ||
33 | canGoForward: boolean; | ||
34 | }): void { | ||
35 | self.url = url; | ||
36 | self.canGoBack = canGoBack; | ||
37 | self.canGoForward = canGoForward; | ||
38 | }, | ||
39 | setTitle(title: string): void { | ||
40 | self.title = title; | ||
41 | }, | ||
42 | hibernated(): void { | ||
43 | self.canGoBack = false; | ||
44 | self.canGoForward = false; | ||
45 | self.state = 'hibernated'; | ||
46 | }, | ||
47 | startedLoading(): void { | ||
48 | self.state = 'loading'; | ||
49 | }, | ||
50 | finishedLoading(): void { | ||
51 | if (self.state === 'loading') { | ||
52 | // Do not overwrite crashed state if the service haven't been reloaded yet. | ||
53 | self.state = 'loaded'; | ||
54 | } | ||
55 | }, | ||
56 | crashed(): void { | ||
57 | self.state = 'crashed'; | ||
58 | }, | ||
59 | setUnreadCount({ direct, indirect }: UnreadCount): void { | ||
60 | if (direct !== undefined) { | ||
61 | self.directMessageCount = direct; | ||
62 | } | ||
63 | if (indirect !== undefined) { | ||
64 | self.indirectMessageCount = indirect; | ||
65 | } | ||
66 | }, | ||
67 | })); | ||
68 | |||
69 | export interface RuntimeService extends Instance<typeof runtimeService> {} | ||
70 | |||
71 | export type { | ||
72 | RuntimeServiceSnapshotIn, | ||
73 | RuntimeServiceSnapshotOut, | ||
74 | } from '@sophie/shared'; | ||
diff --git a/packages/main/src/stores/Service.ts b/packages/main/src/stores/Service.ts index 9bc6a43..78c57cb 100644 --- a/packages/main/src/stores/Service.ts +++ b/packages/main/src/stores/Service.ts | |||
@@ -18,46 +18,95 @@ | |||
18 | * SPDX-License-Identifier: AGPL-3.0-only | 18 | * SPDX-License-Identifier: AGPL-3.0-only |
19 | */ | 19 | */ |
20 | 20 | ||
21 | import type { ServiceSnapshotIn } from '@sophie/shared'; | 21 | import type { UnreadCount } from '@sophie/service-shared'; |
22 | import { | ||
23 | service as originalService, | ||
24 | ServiceSettingsSnapshotIn, | ||
25 | } from '@sophie/shared'; | ||
26 | import { Instance, getSnapshot, ReferenceIdentifier } from 'mobx-state-tree'; | ||
22 | 27 | ||
28 | import SettingsWithId from '../utils/SettingsWithId'; | ||
23 | import generateId from '../utils/generateId'; | 29 | import generateId from '../utils/generateId'; |
30 | import overrideProps from '../utils/overrideProps'; | ||
24 | 31 | ||
25 | import type { ProfileSnapshotIn } from './Profile'; | 32 | import { ProfileSettingsSnapshotWithId } from './Profile'; |
33 | import { serviceSettings } from './ServiceSettings'; | ||
26 | 34 | ||
27 | export interface PartialServiceSnapshotIn | 35 | export interface ServiceConfig |
28 | extends Omit<ServiceSnapshotIn, 'id' | 'profile'> { | 36 | extends Omit<ServiceSettingsSnapshotIn, 'profile'> { |
29 | id?: string | undefined; | 37 | id?: string | undefined; |
30 | profile?: string | undefined; | 38 | |
39 | profile?: ReferenceIdentifier | undefined; | ||
31 | } | 40 | } |
32 | 41 | ||
42 | export const service = overrideProps(originalService, { | ||
43 | settings: serviceSettings, | ||
44 | }) | ||
45 | .views((self) => ({ | ||
46 | get config(): ServiceConfig { | ||
47 | const { id, settings } = self; | ||
48 | return { ...getSnapshot(settings), id }; | ||
49 | }, | ||
50 | })) | ||
51 | .actions((self) => ({ | ||
52 | setLocation({ | ||
53 | url, | ||
54 | canGoBack, | ||
55 | canGoForward, | ||
56 | }: { | ||
57 | url: string; | ||
58 | canGoBack: boolean; | ||
59 | canGoForward: boolean; | ||
60 | }): void { | ||
61 | self.currentUrl = url; | ||
62 | self.canGoBack = canGoBack; | ||
63 | self.canGoForward = canGoForward; | ||
64 | }, | ||
65 | setTitle(title: string): void { | ||
66 | self.title = title; | ||
67 | }, | ||
68 | startedLoading(): void { | ||
69 | self.state = 'loading'; | ||
70 | }, | ||
71 | finishedLoading(): void { | ||
72 | if (self.state === 'loading') { | ||
73 | // Do not overwrite crashed state if the service haven't been reloaded yet. | ||
74 | self.state = 'loaded'; | ||
75 | } | ||
76 | }, | ||
77 | crashed(): void { | ||
78 | self.state = 'crashed'; | ||
79 | }, | ||
80 | setUnreadCount({ direct, indirect }: UnreadCount): void { | ||
81 | if (direct !== undefined) { | ||
82 | self.directMessageCount = direct; | ||
83 | } | ||
84 | if (indirect !== undefined) { | ||
85 | self.indirectMessageCount = indirect; | ||
86 | } | ||
87 | }, | ||
88 | })); | ||
89 | |||
90 | export interface Service extends Instance<typeof service> {} | ||
91 | |||
92 | export type ServiceSettingsSnapshotWithId = | ||
93 | SettingsWithId<ServiceSettingsSnapshotIn>; | ||
94 | |||
33 | export function addMissingServiceIdsAndProfiles( | 95 | export function addMissingServiceIdsAndProfiles( |
34 | partialServices: PartialServiceSnapshotIn[] | undefined, | 96 | serviceConfigs: ServiceConfig[] | undefined, |
35 | profiles: ProfileSnapshotIn[], | 97 | profiles: ProfileSettingsSnapshotWithId[], |
36 | ): ServiceSnapshotIn[] { | 98 | ): ServiceSettingsSnapshotWithId[] { |
37 | return (partialServices ?? []).map((service) => { | 99 | return (serviceConfigs ?? []).map((serviceConfig) => { |
38 | const { name } = service; | 100 | const { id, ...settings } = serviceConfig; |
39 | let { id, profile } = service; | 101 | const { name } = settings; |
40 | if (typeof id === 'undefined') { | 102 | let { profile: profileId } = settings; |
41 | id = generateId(name); | 103 | if (profileId === undefined) { |
42 | } | 104 | profileId = generateId(name); |
43 | if (typeof profile === 'undefined') { | 105 | profiles.push({ id: profileId, settings: { name } }); |
44 | profile = generateId(name); | ||
45 | profiles.push({ | ||
46 | id: profile, | ||
47 | name: service.name, | ||
48 | }); | ||
49 | } | 106 | } |
50 | return { | 107 | return { |
51 | ...service, | 108 | id: id === undefined ? generateId(name) : id, |
52 | id, | 109 | settings: { ...settings, profile: profileId }, |
53 | profile, | ||
54 | }; | 110 | }; |
55 | }); | 111 | }); |
56 | } | 112 | } |
57 | |||
58 | export type { | ||
59 | Service, | ||
60 | ServiceSnapshotOut, | ||
61 | ServiceSnapshotIn, | ||
62 | } from '@sophie/shared'; | ||
63 | export { service } from '@sophie/shared'; | ||
diff --git a/packages/renderer/src/stores/SharedStore.ts b/packages/main/src/stores/ServiceSettings.ts index 962f7e2..960de9b 100644 --- a/packages/renderer/src/stores/SharedStore.ts +++ b/packages/main/src/stores/ServiceSettings.ts | |||
@@ -18,20 +18,20 @@ | |||
18 | * SPDX-License-Identifier: AGPL-3.0-only | 18 | * SPDX-License-Identifier: AGPL-3.0-only |
19 | */ | 19 | */ |
20 | 20 | ||
21 | import { sharedStore as originalSharedStore } from '@sophie/shared'; | 21 | import { serviceSettings as originalServiceSettings } from '@sophie/shared'; |
22 | import { Instance, types } from 'mobx-state-tree'; | 22 | import { Instance, types } from 'mobx-state-tree'; |
23 | 23 | ||
24 | import { config } from './Config'; | 24 | import overrideProps from '../utils/overrideProps'; |
25 | import { service } from './Service'; | ||
26 | 25 | ||
27 | export const sharedStore = originalSharedStore.props({ | 26 | import { profile } from './Profile'; |
28 | config: types.optional(config, {}), | 27 | |
29 | selectedService: types.safeReference(service), | 28 | export const serviceSettings = overrideProps(originalServiceSettings, { |
29 | profile: types.reference(profile), | ||
30 | }); | 30 | }); |
31 | 31 | ||
32 | export interface SharedStore extends Instance<typeof sharedStore> {} | 32 | export interface ServiceSettings extends Instance<typeof serviceSettings> {} |
33 | 33 | ||
34 | export type { | 34 | export type { |
35 | SharedStoreSnapshotIn, | 35 | ServiceSettingsSnapshotIn, |
36 | SharedStoreSnapshotOut, | 36 | ServiceSettingsSnapshotOut, |
37 | } from '@sophie/shared'; | 37 | } from '@sophie/shared'; |
diff --git a/packages/main/src/stores/SharedStore.ts b/packages/main/src/stores/SharedStore.ts index 76ce05c..861c8ce 100644 --- a/packages/main/src/stores/SharedStore.ts +++ b/packages/main/src/stores/SharedStore.ts | |||
@@ -19,19 +19,111 @@ | |||
19 | */ | 19 | */ |
20 | 20 | ||
21 | import { sharedStore as originalSharedStore } from '@sophie/shared'; | 21 | import { sharedStore as originalSharedStore } from '@sophie/shared'; |
22 | import { Instance, types } from 'mobx-state-tree'; | 22 | import { |
23 | applySnapshot, | ||
24 | getSnapshot, | ||
25 | Instance, | ||
26 | resolveIdentifier, | ||
27 | types, | ||
28 | } from 'mobx-state-tree'; | ||
23 | 29 | ||
24 | import { config } from './Config'; | 30 | import SettingsWithId from '../utils/SettingsWithId'; |
25 | import { runtimeService } from './RuntimeService'; | 31 | import { getLogger } from '../utils/log'; |
32 | import overrideProps from '../utils/overrideProps'; | ||
33 | |||
34 | import { globalSettings, GlobalSettingsSnapshotIn } from './GlobalSettings'; | ||
35 | import { addMissingProfileIds, profile, ProfileConfig } from './Profile'; | ||
36 | import { | ||
37 | addMissingServiceIdsAndProfiles, | ||
38 | service, | ||
39 | ServiceConfig, | ||
40 | } from './Service'; | ||
41 | |||
42 | const log = getLogger('sharedStore'); | ||
43 | |||
44 | export interface Config extends GlobalSettingsSnapshotIn { | ||
45 | profiles?: ProfileConfig[] | undefined; | ||
46 | |||
47 | services?: ServiceConfig[] | undefined; | ||
48 | } | ||
49 | |||
50 | function getConfigs<T>(models: { config: T }[]): T[] | undefined { | ||
51 | return models.length === 0 ? undefined : models.map((model) => model.config); | ||
52 | } | ||
53 | |||
54 | function reconcileSettings<T>( | ||
55 | originalSnapshots: SettingsWithId<T>[], | ||
56 | settingsSnapshotsWithId: SettingsWithId<T>[], | ||
57 | ): SettingsWithId<T>[] { | ||
58 | const idToOriginalSnapshots = new Map( | ||
59 | originalSnapshots.map((originalSnapshot) => [ | ||
60 | originalSnapshot.id, | ||
61 | originalSnapshot, | ||
62 | ]), | ||
63 | ); | ||
64 | return settingsSnapshotsWithId.map(({ id, settings }) => ({ | ||
65 | ...idToOriginalSnapshots.get(id), | ||
66 | id, | ||
67 | settings, | ||
68 | })); | ||
69 | } | ||
70 | |||
71 | export const sharedStore = overrideProps(originalSharedStore, { | ||
72 | settings: types.optional(globalSettings, {}), | ||
73 | profiles: types.array(profile), | ||
74 | services: types.array(service), | ||
75 | selectedService: types.safeReference(service), | ||
76 | }) | ||
77 | .views((self) => ({ | ||
78 | get config(): Config { | ||
79 | const { settings, profiles, services } = self; | ||
80 | const globalSettingsConfig = getSnapshot(settings); | ||
81 | return { | ||
82 | ...globalSettingsConfig, | ||
83 | profiles: getConfigs(profiles), | ||
84 | services: getConfigs(services), | ||
85 | }; | ||
86 | }, | ||
87 | })) | ||
88 | .actions((self) => ({ | ||
89 | loadConfig(config: Config): void { | ||
90 | const snapshot = getSnapshot(self); | ||
91 | const { profiles, services, ...settings } = config; | ||
92 | const profileSettingsSnapshots = addMissingProfileIds(profiles); | ||
93 | const serviceSettingsSnapshots = addMissingServiceIdsAndProfiles( | ||
94 | services, | ||
95 | profileSettingsSnapshots, | ||
96 | ); | ||
97 | applySnapshot(self, { | ||
98 | ...snapshot, | ||
99 | settings, | ||
100 | profiles: reconcileSettings( | ||
101 | snapshot.profiles, | ||
102 | profileSettingsSnapshots, | ||
103 | ), | ||
104 | services: reconcileSettings( | ||
105 | snapshot.services, | ||
106 | serviceSettingsSnapshots, | ||
107 | ), | ||
108 | }); | ||
109 | }, | ||
110 | setShouldUseDarkColors(shouldUseDarkColors: boolean): void { | ||
111 | self.shouldUseDarkColors = shouldUseDarkColors; | ||
112 | }, | ||
113 | setSelectedServiceId(serviceId: string): void { | ||
114 | const serviceInstance = resolveIdentifier(service, self, serviceId); | ||
115 | if (serviceInstance === undefined) { | ||
116 | log.warn('Trying to select unknown service', serviceId); | ||
117 | return; | ||
118 | } | ||
119 | self.selectedService = serviceInstance; | ||
120 | log.debug('Selected service', serviceId); | ||
121 | }, | ||
122 | })); | ||
123 | |||
124 | export interface SharedStore extends Instance<typeof sharedStore> {} | ||
26 | 125 | ||
27 | export type { | 126 | export type { |
28 | SharedStoreSnapshotIn, | 127 | SharedStoreSnapshotIn, |
29 | SharedStoreSnapshotOut, | 128 | SharedStoreSnapshotOut, |
30 | } from '@sophie/shared'; | 129 | } from '@sophie/shared'; |
31 | |||
32 | export const sharedStore = originalSharedStore.props({ | ||
33 | config: types.optional(config, {}), | ||
34 | runtimeServices: types.map(runtimeService), | ||
35 | }); | ||
36 | |||
37 | export interface SharedStore extends Instance<typeof sharedStore> {} | ||
diff --git a/packages/main/src/stores/__tests__/Config.spec.ts b/packages/main/src/stores/__tests__/SharedStore.spec.ts index 22ccbc7..3ea187c 100644 --- a/packages/main/src/stores/__tests__/Config.spec.ts +++ b/packages/main/src/stores/__tests__/SharedStore.spec.ts | |||
@@ -18,28 +18,28 @@ | |||
18 | * SPDX-License-Identifier: AGPL-3.0-only | 18 | * SPDX-License-Identifier: AGPL-3.0-only |
19 | */ | 19 | */ |
20 | 20 | ||
21 | import { config, Config, ConfigFileIn } from '../Config'; | 21 | import type { ProfileConfig } from '../Profile'; |
22 | import type { PartialProfileSnapshotIn } from '../Profile'; | 22 | import type { ServiceConfig } from '../Service'; |
23 | import type { PartialServiceSnapshotIn } from '../Service'; | 23 | import { Config, sharedStore, SharedStore } from '../SharedStore'; |
24 | 24 | ||
25 | const profileProps: PartialProfileSnapshotIn = { | 25 | const profileProps: ProfileConfig = { |
26 | name: 'Test profile', | 26 | name: 'Test profile', |
27 | }; | 27 | }; |
28 | 28 | ||
29 | const serviceProps: PartialServiceSnapshotIn = { | 29 | const serviceProps: ServiceConfig = { |
30 | name: 'Test service', | 30 | name: 'Test service', |
31 | url: 'https://example.com', | 31 | url: 'https://example.com', |
32 | }; | 32 | }; |
33 | 33 | ||
34 | let sut: Config; | 34 | let sut: SharedStore; |
35 | 35 | ||
36 | beforeEach(() => { | 36 | beforeEach(() => { |
37 | sut = config.create(); | 37 | sut = sharedStore.create(); |
38 | }); | 38 | }); |
39 | 39 | ||
40 | describe('preprocessConfigFile', () => { | 40 | describe('loadConfig', () => { |
41 | it('should load profiles with an ID', () => { | 41 | it('should load profiles with an ID', () => { |
42 | sut.loadFromConfigFile({ | 42 | sut.loadConfig({ |
43 | profiles: [ | 43 | profiles: [ |
44 | { | 44 | { |
45 | id: 'someId', | 45 | id: 'someId', |
@@ -51,14 +51,14 @@ describe('preprocessConfigFile', () => { | |||
51 | }); | 51 | }); |
52 | 52 | ||
53 | it('should generate an ID for profiles without and ID', () => { | 53 | it('should generate an ID for profiles without and ID', () => { |
54 | sut.loadFromConfigFile({ | 54 | sut.loadConfig({ |
55 | profiles: [profileProps], | 55 | profiles: [profileProps], |
56 | }); | 56 | }); |
57 | expect(sut.profiles[0].id).toBeDefined(); | 57 | expect(sut.profiles[0].id).toBeDefined(); |
58 | }); | 58 | }); |
59 | 59 | ||
60 | it('should load services with an ID and a profile', () => { | 60 | it('should load services with an ID and a profile', () => { |
61 | sut.loadFromConfigFile({ | 61 | sut.loadConfig({ |
62 | profiles: [ | 62 | profiles: [ |
63 | { | 63 | { |
64 | id: 'someProfileId', | 64 | id: 'someProfileId', |
@@ -74,12 +74,12 @@ describe('preprocessConfigFile', () => { | |||
74 | ], | 74 | ], |
75 | }); | 75 | }); |
76 | expect(sut.services[0].id).toBe('someServiceId'); | 76 | expect(sut.services[0].id).toBe('someServiceId'); |
77 | expect(sut.services[0].profile).toBe(sut.profiles[0]); | 77 | expect(sut.services[0].settings.profile).toBe(sut.profiles[0]); |
78 | }); | 78 | }); |
79 | 79 | ||
80 | it('should refuse to load a profile without a name', () => { | 80 | it('should refuse to load a profile without a name', () => { |
81 | expect(() => { | 81 | expect(() => { |
82 | sut.loadFromConfigFile({ | 82 | sut.loadConfig({ |
83 | profiles: [ | 83 | profiles: [ |
84 | { | 84 | { |
85 | id: 'someProfileId', | 85 | id: 'someProfileId', |
@@ -87,13 +87,13 @@ describe('preprocessConfigFile', () => { | |||
87 | name: undefined, | 87 | name: undefined, |
88 | }, | 88 | }, |
89 | ], | 89 | ], |
90 | } as unknown as ConfigFileIn); | 90 | } as unknown as Config); |
91 | }).toThrow(); | 91 | }).toThrow(); |
92 | expect(sut.profiles).toHaveLength(0); | 92 | expect(sut.profiles).toHaveLength(0); |
93 | }); | 93 | }); |
94 | 94 | ||
95 | it('should load services without an ID but with a profile', () => { | 95 | it('should load services without an ID but with a profile', () => { |
96 | sut.loadFromConfigFile({ | 96 | sut.loadConfig({ |
97 | profiles: [ | 97 | profiles: [ |
98 | { | 98 | { |
99 | id: 'someProfileId', | 99 | id: 'someProfileId', |
@@ -108,11 +108,11 @@ describe('preprocessConfigFile', () => { | |||
108 | ], | 108 | ], |
109 | }); | 109 | }); |
110 | expect(sut.services[0].id).toBeDefined(); | 110 | expect(sut.services[0].id).toBeDefined(); |
111 | expect(sut.services[0].profile).toBe(sut.profiles[0]); | 111 | expect(sut.services[0].settings.profile).toBe(sut.profiles[0]); |
112 | }); | 112 | }); |
113 | 113 | ||
114 | it('should create a profile for a service with an ID but no profile', () => { | 114 | it('should create a profile for a service with an ID but no profile', () => { |
115 | sut.loadFromConfigFile({ | 115 | sut.loadConfig({ |
116 | services: [ | 116 | services: [ |
117 | { | 117 | { |
118 | id: 'someServiceId', | 118 | id: 'someServiceId', |
@@ -121,12 +121,14 @@ describe('preprocessConfigFile', () => { | |||
121 | ], | 121 | ], |
122 | }); | 122 | }); |
123 | expect(sut.services[0].id).toBe('someServiceId'); | 123 | expect(sut.services[0].id).toBe('someServiceId'); |
124 | expect(sut.services[0].profile).toBeDefined(); | 124 | expect(sut.services[0].settings.profile).toBeDefined(); |
125 | expect(sut.services[0].profile.name).toBe(serviceProps.name); | 125 | expect(sut.services[0].settings.profile.settings.name).toBe( |
126 | serviceProps.name, | ||
127 | ); | ||
126 | }); | 128 | }); |
127 | 129 | ||
128 | it('should create a profile for a service without an ID or profile', () => { | 130 | it('should create a profile for a service without an ID or profile', () => { |
129 | sut.loadFromConfigFile({ | 131 | sut.loadConfig({ |
130 | services: [ | 132 | services: [ |
131 | { | 133 | { |
132 | ...serviceProps, | 134 | ...serviceProps, |
@@ -134,13 +136,15 @@ describe('preprocessConfigFile', () => { | |||
134 | ], | 136 | ], |
135 | }); | 137 | }); |
136 | expect(sut.services[0].id).toBeDefined(); | 138 | expect(sut.services[0].id).toBeDefined(); |
137 | expect(sut.services[0].profile).toBeDefined(); | 139 | expect(sut.services[0].settings.profile).toBeDefined(); |
138 | expect(sut.services[0].profile.name).toBe(serviceProps.name); | 140 | expect(sut.services[0].settings.profile.settings.name).toBe( |
141 | serviceProps.name, | ||
142 | ); | ||
139 | }); | 143 | }); |
140 | 144 | ||
141 | it('should refuse to load a service without a name', () => { | 145 | it('should refuse to load a service without a name', () => { |
142 | expect(() => { | 146 | expect(() => { |
143 | sut.loadFromConfigFile({ | 147 | sut.loadConfig({ |
144 | services: [ | 148 | services: [ |
145 | { | 149 | { |
146 | id: 'someServiceId', | 150 | id: 'someServiceId', |
@@ -148,7 +152,7 @@ describe('preprocessConfigFile', () => { | |||
148 | name: undefined, | 152 | name: undefined, |
149 | }, | 153 | }, |
150 | ], | 154 | ], |
151 | } as unknown as ConfigFileIn); | 155 | } as unknown as Config); |
152 | }).toThrow(); | 156 | }).toThrow(); |
153 | expect(sut.profiles).toHaveLength(0); | 157 | expect(sut.profiles).toHaveLength(0); |
154 | expect(sut.services).toHaveLength(0); | 158 | expect(sut.services).toHaveLength(0); |
diff --git a/packages/renderer/src/stores/Config.ts b/packages/main/src/utils/SettingsWithId.ts index 070c4ec..fde3e86 100644 --- a/packages/renderer/src/stores/Config.ts +++ b/packages/main/src/utils/SettingsWithId.ts | |||
@@ -18,15 +18,8 @@ | |||
18 | * SPDX-License-Identifier: AGPL-3.0-only | 18 | * SPDX-License-Identifier: AGPL-3.0-only |
19 | */ | 19 | */ |
20 | 20 | ||
21 | import { config as originalConfig } from '@sophie/shared'; | 21 | export default interface SettingsWithId<T> { |
22 | import { Instance, types } from 'mobx-state-tree'; | 22 | id: string; |
23 | 23 | ||
24 | import { service } from './Service'; | 24 | settings: T; |
25 | 25 | } | |
26 | export const config = originalConfig.props({ | ||
27 | services: types.array(service), | ||
28 | }); | ||
29 | |||
30 | export interface Config extends Instance<typeof config> {} | ||
31 | |||
32 | export type { ConfigSnapshotIn, ConfigSnapshotOut } from '@sophie/shared'; | ||
diff --git a/packages/main/src/utils/overrideProps.ts b/packages/main/src/utils/overrideProps.ts new file mode 100644 index 0000000..c626408 --- /dev/null +++ b/packages/main/src/utils/overrideProps.ts | |||
@@ -0,0 +1,62 @@ | |||
1 | /* | ||
2 | * Copyright (C) 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 | /** | ||
22 | * @file This file implements a technique to force-override properties of a model. | ||
23 | * | ||
24 | * The overridden properties must conform to the SnapshotIt and SnapshotOut format | ||
25 | * of the original model. Essentially, this means that only views and actions can | ||
26 | * be added safely. | ||
27 | * | ||
28 | * @see https://github.com/mobxjs/mobx-state-tree/issues/1403#issuecomment-940843087 | ||
29 | */ | ||
30 | |||
31 | import { | ||
32 | IAnyModelType, | ||
33 | IModelType, | ||
34 | ModelProperties, | ||
35 | SnapshotIn, | ||
36 | SnapshotOut, | ||
37 | } from 'mobx-state-tree'; | ||
38 | |||
39 | export type IUnsafeOverriddenModelType< | ||
40 | BASE extends IAnyModelType, | ||
41 | PROPS extends ModelProperties, | ||
42 | > = BASE extends IModelType<infer P, infer O, infer CC, infer CS> | ||
43 | ? IModelType<Omit<P, keyof PROPS> & PROPS, O, CC, CS> | ||
44 | : never; | ||
45 | |||
46 | export type IOverriddenModelType< | ||
47 | BASE extends IAnyModelType, | ||
48 | PROPS extends ModelProperties, | ||
49 | > = SnapshotIn<BASE> extends SnapshotIn<IUnsafeOverriddenModelType<BASE, PROPS>> | ||
50 | ? SnapshotOut< | ||
51 | IUnsafeOverriddenModelType<BASE, PROPS> | ||
52 | > extends SnapshotOut<BASE> | ||
53 | ? IUnsafeOverriddenModelType<BASE, PROPS> | ||
54 | : never | ||
55 | : never; | ||
56 | |||
57 | export default function overrideProps< | ||
58 | BASE extends IAnyModelType, | ||
59 | PROPS extends ModelProperties, | ||
60 | >(base: BASE, props: PROPS): IOverriddenModelType<BASE, PROPS> { | ||
61 | return base.props(props) as IOverriddenModelType<BASE, PROPS>; | ||
62 | } | ||
diff --git a/packages/renderer/src/components/ServiceSwitcher.tsx b/packages/renderer/src/components/ServiceSwitcher.tsx index 0786b71..167153f 100644 --- a/packages/renderer/src/components/ServiceSwitcher.tsx +++ b/packages/renderer/src/components/ServiceSwitcher.tsx | |||
@@ -78,8 +78,8 @@ export default observer(() => { | |||
78 | <ServiceSwitcherTab | 78 | <ServiceSwitcherTab |
79 | key={service.id} | 79 | key={service.id} |
80 | value={service.id} | 80 | value={service.id} |
81 | icon={<ServiceIcon name={service.name} />} | 81 | icon={<ServiceIcon name={service.settings.name} />} |
82 | aria-label={service.name} | 82 | aria-label={service.settings.name} |
83 | /> | 83 | /> |
84 | ))} | 84 | ))} |
85 | </ServiceSwitcherRoot> | 85 | </ServiceSwitcherRoot> |
diff --git a/packages/renderer/src/env/RendererEnv.ts b/packages/renderer/src/env/RendererEnv.ts index 5ca2978..ba4c43e 100644 --- a/packages/renderer/src/env/RendererEnv.ts +++ b/packages/renderer/src/env/RendererEnv.ts | |||
@@ -18,10 +18,8 @@ | |||
18 | * SPDX-License-Identifier: AGPL-3.0-only | 18 | * SPDX-License-Identifier: AGPL-3.0-only |
19 | */ | 19 | */ |
20 | 20 | ||
21 | import type { Action, RuntimeService } from '@sophie/shared'; | 21 | import type { Action } from '@sophie/shared'; |
22 | 22 | ||
23 | export default interface RendererEnv { | 23 | export default interface RendererEnv { |
24 | dispatchMainAction(action: Action): void; | 24 | dispatchMainAction(action: Action): void; |
25 | |||
26 | getRuntimeService(serviceId: string): RuntimeService | undefined; | ||
27 | } | 25 | } |
diff --git a/packages/renderer/src/env/impl/RendererEnvImpl.ts b/packages/renderer/src/env/impl/RendererEnvImpl.ts deleted file mode 100644 index 184d31b..0000000 --- a/packages/renderer/src/env/impl/RendererEnvImpl.ts +++ /dev/null | |||
@@ -1,55 +0,0 @@ | |||
1 | /* | ||
2 | * Copyright (C) 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 { | ||
22 | Action, | ||
23 | runtimeService, | ||
24 | RuntimeService, | ||
25 | SophieRenderer, | ||
26 | } from '@sophie/shared'; | ||
27 | import type { IMSTMap } from 'mobx-state-tree'; | ||
28 | |||
29 | import type { RendererStore } from '../../stores/RendererStore'; | ||
30 | import type RendererEnv from '../RendererEnv'; | ||
31 | |||
32 | export default class RendererEnvImpl implements RendererEnv { | ||
33 | readonly #ipc: SophieRenderer; | ||
34 | |||
35 | #runtimeServices: IMSTMap<typeof runtimeService> | undefined; | ||
36 | |||
37 | constructor(ipc: SophieRenderer) { | ||
38 | this.#ipc = ipc; | ||
39 | } | ||
40 | |||
41 | setStore(store: RendererStore): void { | ||
42 | this.#runtimeServices = store.shared.runtimeServices; | ||
43 | } | ||
44 | |||
45 | dispatchMainAction(action: Action): void { | ||
46 | this.#ipc.dispatchAction(action); | ||
47 | } | ||
48 | |||
49 | getRuntimeService(serviceId: string): RuntimeService | undefined { | ||
50 | if (this.#runtimeServices === undefined) { | ||
51 | throw new Error('runtime services map is not yet set'); | ||
52 | } | ||
53 | return this.#runtimeServices.get(serviceId); | ||
54 | } | ||
55 | } | ||
diff --git a/packages/renderer/src/env/impl/__tests__/RendererEnvImpl.spec.ts b/packages/renderer/src/env/impl/__tests__/RendererEnvImpl.spec.ts deleted file mode 100644 index d36462c..0000000 --- a/packages/renderer/src/env/impl/__tests__/RendererEnvImpl.spec.ts +++ /dev/null | |||
@@ -1,87 +0,0 @@ | |||
1 | /* | ||
2 | * Copyright (C) 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 { jest } from '@jest/globals'; | ||
22 | import { | ||
23 | Action, | ||
24 | runtimeService, | ||
25 | RuntimeService, | ||
26 | SophieRenderer, | ||
27 | } from '@sophie/shared'; | ||
28 | |||
29 | import { rendererStore } from '../../../stores/RendererStore'; | ||
30 | import RendererEnvImpl from '../RendererEnvImpl'; | ||
31 | |||
32 | const ipc: SophieRenderer = { | ||
33 | dispatchAction: jest.fn(), | ||
34 | onSharedStoreChange: jest.fn(), | ||
35 | }; | ||
36 | let sut: RendererEnvImpl; | ||
37 | |||
38 | beforeEach(() => { | ||
39 | sut = new RendererEnvImpl(ipc); | ||
40 | }); | ||
41 | |||
42 | describe('dispatchMainAction', () => { | ||
43 | it('should dispatch actions via the IPC', () => { | ||
44 | const action: Action = { | ||
45 | action: 'set-theme-source', | ||
46 | themeSource: 'dark', | ||
47 | }; | ||
48 | sut.dispatchMainAction(action); | ||
49 | expect(ipc.dispatchAction).toHaveBeenCalledWith(action); | ||
50 | }); | ||
51 | }); | ||
52 | |||
53 | describe('getRuntimeService', () => { | ||
54 | describe('when no store was set', () => { | ||
55 | it('should throw an error', () => { | ||
56 | expect(() => sut.getRuntimeService('someId')).toThrow(); | ||
57 | }); | ||
58 | }); | ||
59 | |||
60 | describe('when the store was set', () => { | ||
61 | let runtimeServiceStore: RuntimeService; | ||
62 | |||
63 | beforeEach(() => { | ||
64 | runtimeServiceStore = runtimeService.create({ | ||
65 | state: 'loaded', | ||
66 | }); | ||
67 | const store = rendererStore.create({ | ||
68 | shared: { | ||
69 | runtimeServices: { | ||
70 | someId: runtimeServiceStore, | ||
71 | }, | ||
72 | }, | ||
73 | }); | ||
74 | sut.setStore(store); | ||
75 | }); | ||
76 | |||
77 | it('should return the runtime service for the given ID', () => { | ||
78 | const returnedStore = sut.getRuntimeService('someId'); | ||
79 | expect(returnedStore).toBe(runtimeServiceStore); | ||
80 | }); | ||
81 | |||
82 | it('should return undefined for an unknown ID', () => { | ||
83 | const returnedStore = sut.getRuntimeService('unknownId'); | ||
84 | expect(returnedStore).toBeUndefined(); | ||
85 | }); | ||
86 | }); | ||
87 | }); | ||
diff --git a/packages/renderer/src/stores/RendererStore.ts b/packages/renderer/src/stores/RendererStore.ts index 731ca28..a4e6197 100644 --- a/packages/renderer/src/stores/RendererStore.ts +++ b/packages/renderer/src/stores/RendererStore.ts | |||
@@ -18,17 +18,19 @@ | |||
18 | * SPDX-License-Identifier: AGPL-3.0-only | 18 | * SPDX-License-Identifier: AGPL-3.0-only |
19 | */ | 19 | */ |
20 | 20 | ||
21 | import { BrowserViewBounds, SophieRenderer, ThemeSource } from '@sophie/shared'; | 21 | import { |
22 | BrowserViewBounds, | ||
23 | sharedStore, | ||
24 | Service, | ||
25 | SophieRenderer, | ||
26 | ThemeSource, | ||
27 | } from '@sophie/shared'; | ||
22 | import { applySnapshot, applyPatch, Instance, types } from 'mobx-state-tree'; | 28 | import { applySnapshot, applyPatch, Instance, types } from 'mobx-state-tree'; |
23 | 29 | ||
30 | import RendererEnv from '../env/RendererEnv'; | ||
24 | import getEnv from '../env/getEnv'; | 31 | import getEnv from '../env/getEnv'; |
25 | import RendererEnvImpl from '../env/impl/RendererEnvImpl'; | ||
26 | import { getLogger } from '../utils/log'; | 32 | import { getLogger } from '../utils/log'; |
27 | 33 | ||
28 | import type { Config } from './Config'; | ||
29 | import type { Service } from './Service'; | ||
30 | import { sharedStore } from './SharedStore'; | ||
31 | |||
32 | const log = getLogger('RendererStore'); | 34 | const log = getLogger('RendererStore'); |
33 | 35 | ||
34 | export const rendererStore = types | 36 | export const rendererStore = types |
@@ -36,11 +38,8 @@ export const rendererStore = types | |||
36 | shared: types.optional(sharedStore, {}), | 38 | shared: types.optional(sharedStore, {}), |
37 | }) | 39 | }) |
38 | .views((self) => ({ | 40 | .views((self) => ({ |
39 | get config(): Config { | ||
40 | return self.shared.config; | ||
41 | }, | ||
42 | get services(): Service[] { | 41 | get services(): Service[] { |
43 | return this.config.services; | 42 | return self.shared.services; |
44 | }, | 43 | }, |
45 | get selectedService(): Service | undefined { | 44 | get selectedService(): Service | undefined { |
46 | return self.shared.selectedService; | 45 | return self.shared.selectedService; |
@@ -87,9 +86,10 @@ export interface RendererStore extends Instance<typeof rendererStore> {} | |||
87 | export function createAndConnectRendererStore( | 86 | export function createAndConnectRendererStore( |
88 | ipc: SophieRenderer, | 87 | ipc: SophieRenderer, |
89 | ): RendererStore { | 88 | ): RendererStore { |
90 | const env = new RendererEnvImpl(ipc); | 89 | const env: RendererEnv = { |
90 | dispatchMainAction: ipc.dispatchAction, | ||
91 | }; | ||
91 | const store = rendererStore.create({}, env); | 92 | const store = rendererStore.create({}, env); |
92 | env.setStore(store); | ||
93 | 93 | ||
94 | ipc | 94 | ipc |
95 | .onSharedStoreChange({ | 95 | .onSharedStoreChange({ |
diff --git a/packages/renderer/src/stores/__tests__/Service.spec.ts b/packages/renderer/src/stores/__tests__/Service.spec.ts deleted file mode 100644 index f835d41..0000000 --- a/packages/renderer/src/stores/__tests__/Service.spec.ts +++ /dev/null | |||
@@ -1,63 +0,0 @@ | |||
1 | /* | ||
2 | * Copyright (C) 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 { jest } from '@jest/globals'; | ||
22 | import { runtimeService } from '@sophie/shared'; | ||
23 | import { mocked } from 'jest-mock'; | ||
24 | |||
25 | import type RendererEnv from '../../env/RendererEnv'; | ||
26 | import { service, Service } from '../Service'; | ||
27 | |||
28 | const env: RendererEnv = { | ||
29 | dispatchMainAction: jest.fn(), | ||
30 | getRuntimeService: jest.fn(), | ||
31 | }; | ||
32 | let sut: Service; | ||
33 | |||
34 | beforeEach(() => { | ||
35 | sut = service.create( | ||
36 | { | ||
37 | id: 'serviceId', | ||
38 | name: 'Foo', | ||
39 | url: 'https://example.com', | ||
40 | profile: 'profileId', | ||
41 | }, | ||
42 | env, | ||
43 | ); | ||
44 | }); | ||
45 | |||
46 | describe('runtime', () => { | ||
47 | it('should return the runtime service with for the service ID', () => { | ||
48 | const runtimeServiceStore = runtimeService.create({}, env); | ||
49 | mocked(env.getRuntimeService).mockReturnValueOnce(runtimeServiceStore); | ||
50 | const returnedStore = sut.runtime; | ||
51 | expect(env.getRuntimeService).toHaveBeenCalledWith('serviceId'); | ||
52 | expect(returnedStore).toBe(runtimeServiceStore); | ||
53 | }); | ||
54 | |||
55 | it('should return a valid runtime service even if none exists in the environment', () => { | ||
56 | /* | ||
57 | eslint-disable-next-line unicorn/no-useless-undefined -- | ||
58 | `mockReturnValueOnce` expects 1 parameter. | ||
59 | */ | ||
60 | mocked(env.getRuntimeService).mockReturnValueOnce(undefined); | ||
61 | expect(sut.runtime).toHaveProperty('state', 'hibernated'); | ||
62 | }); | ||
63 | }); | ||
diff --git a/packages/shared/src/contextBridge/SophieRenderer.ts b/packages/shared/src/contextBridge/SophieRenderer.ts index 9858aa9..28dc0b7 100644 --- a/packages/shared/src/contextBridge/SophieRenderer.ts +++ b/packages/shared/src/contextBridge/SophieRenderer.ts | |||
@@ -21,7 +21,7 @@ | |||
21 | import { Action } from '../schemas'; | 21 | import { Action } from '../schemas'; |
22 | import { SharedStoreListener } from '../stores/SharedStore'; | 22 | import { SharedStoreListener } from '../stores/SharedStore'; |
23 | 23 | ||
24 | export interface SophieRenderer { | 24 | export default interface SophieRenderer { |
25 | onSharedStoreChange(this: void, listener: SharedStoreListener): Promise<void>; | 25 | onSharedStoreChange(this: void, listener: SharedStoreListener): Promise<void>; |
26 | 26 | ||
27 | dispatchAction(this: void, action: Action): void; | 27 | dispatchAction(this: void, action: Action): void; |
diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index df02854..55cf5ce 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts | |||
@@ -18,7 +18,7 @@ | |||
18 | * SPDX-License-Identifier: AGPL-3.0-only | 18 | * SPDX-License-Identifier: AGPL-3.0-only |
19 | */ | 19 | */ |
20 | 20 | ||
21 | export type { SophieRenderer } from './contextBridge/SophieRenderer'; | 21 | export type { default as SophieRenderer } from './contextBridge/SophieRenderer'; |
22 | 22 | ||
23 | export { MainToRendererIpcMessage, RendererToMainIpcMessage } from './ipc'; | 23 | export { MainToRendererIpcMessage, RendererToMainIpcMessage } from './ipc'; |
24 | 24 | ||
@@ -26,34 +26,33 @@ export type { Action, BrowserViewBounds, ThemeSource } from './schemas'; | |||
26 | export { action, browserViewBounds, themeSource } from './schemas'; | 26 | export { action, browserViewBounds, themeSource } from './schemas'; |
27 | 27 | ||
28 | export type { | 28 | export type { |
29 | Config, | 29 | GlobalSettings, |
30 | ConfigSnapshotIn, | 30 | GlobalSettingsSnapshotIn, |
31 | ConfigSnapshotOut, | 31 | GlobalSettingsSnapshotOut, |
32 | } from './stores/Config'; | 32 | } from './stores/GlobalSettings'; |
33 | export { config } from './stores/Config'; | 33 | export { globalSettings } from './stores/GlobalSettings'; |
34 | 34 | ||
35 | export type { | 35 | export type { Profile } from './stores/Profile'; |
36 | Profile, | ||
37 | ProfileSnapshotIn, | ||
38 | ProfileSnapshotOut, | ||
39 | } from './stores/Profile'; | ||
40 | export { profile } from './stores/Profile'; | 36 | export { profile } from './stores/Profile'; |
41 | 37 | ||
42 | export type { | 38 | export type { |
43 | RuntimeService, | 39 | ProfileSettings, |
44 | RuntimeServiceSnapshotIn, | 40 | ProfileSettingsSnapshotIn, |
45 | RuntimeServiceSnapshotOut, | 41 | ProfileSettingsSnapshotOut, |
46 | } from './stores/RuntimeService'; | 42 | } from './stores/ProfileSettings'; |
47 | export { runtimeService } from './stores/RuntimeService'; | 43 | export { profileSettings } from './stores/ProfileSettings'; |
48 | 44 | ||
49 | export type { | 45 | export type { Service } from './stores/Service'; |
50 | Service, | ||
51 | ServiceSnapshotIn, | ||
52 | ServiceSnapshotOut, | ||
53 | } from './stores/Service'; | ||
54 | export { service } from './stores/Service'; | 46 | export { service } from './stores/Service'; |
55 | 47 | ||
56 | export type { | 48 | export type { |
49 | ServiceSettings, | ||
50 | ServiceSettingsSnapshotIn, | ||
51 | ServiceSettingsSnapshotOut, | ||
52 | } from './stores/ServiceSettings'; | ||
53 | export { serviceSettings } from './stores/ServiceSettings'; | ||
54 | |||
55 | export type { | ||
57 | SharedStore, | 56 | SharedStore, |
58 | SharedStoreListener, | 57 | SharedStoreListener, |
59 | SharedStoreSnapshotIn, | 58 | SharedStoreSnapshotIn, |
diff --git a/packages/shared/src/stores/Config.ts b/packages/shared/src/stores/GlobalSettings.ts index 1d97e7d..bd0155a 100644 --- a/packages/shared/src/stores/Config.ts +++ b/packages/shared/src/stores/GlobalSettings.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | /* | 1 | /* |
2 | * Copyright (C) 2021-2022 Kristóf Marussy <kristof@marussy.com> | 2 | * Copyright (C) 2022 Kristóf Marussy <kristof@marussy.com> |
3 | * | 3 | * |
4 | * This file is part of Sophie. | 4 | * This file is part of Sophie. |
5 | * | 5 | * |
@@ -22,17 +22,14 @@ import { Instance, types, SnapshotIn, SnapshotOut } from 'mobx-state-tree'; | |||
22 | 22 | ||
23 | import { themeSource } from '../schemas'; | 23 | import { themeSource } from '../schemas'; |
24 | 24 | ||
25 | import { profile } from './Profile'; | 25 | export const globalSettings = types.model('GlobalSettings', { |
26 | import { service } from './Service'; | ||
27 | |||
28 | export const config = types.model('Config', { | ||
29 | profiles: types.array(profile), | ||
30 | services: types.array(service), | ||
31 | themeSource: types.optional(types.enumeration(themeSource.options), 'system'), | 26 | themeSource: types.optional(types.enumeration(themeSource.options), 'system'), |
32 | }); | 27 | }); |
33 | 28 | ||
34 | export interface Config extends Instance<typeof config> {} | 29 | export interface GlobalSettings extends Instance<typeof globalSettings> {} |
35 | 30 | ||
36 | export interface ConfigSnapshotIn extends SnapshotIn<typeof config> {} | 31 | export interface GlobalSettingsSnapshotIn |
32 | extends SnapshotIn<typeof globalSettings> {} | ||
37 | 33 | ||
38 | export interface ConfigSnapshotOut extends SnapshotOut<typeof config> {} | 34 | export interface GlobalSettingsSnapshotOut |
35 | extends SnapshotOut<typeof globalSettings> {} | ||
diff --git a/packages/shared/src/stores/Profile.ts b/packages/shared/src/stores/Profile.ts index 88a0f4d..bb058f6 100644 --- a/packages/shared/src/stores/Profile.ts +++ b/packages/shared/src/stores/Profile.ts | |||
@@ -18,15 +18,13 @@ | |||
18 | * SPDX-License-Identifier: AGPL-3.0-only | 18 | * SPDX-License-Identifier: AGPL-3.0-only |
19 | */ | 19 | */ |
20 | 20 | ||
21 | import { Instance, types, SnapshotIn, SnapshotOut } from 'mobx-state-tree'; | 21 | import { Instance, types } from 'mobx-state-tree'; |
22 | |||
23 | import { profileSettings } from './ProfileSettings'; | ||
22 | 24 | ||
23 | export const profile = types.model('Profile', { | 25 | export const profile = types.model('Profile', { |
24 | id: types.identifier, | 26 | id: types.identifier, |
25 | name: types.string, | 27 | settings: profileSettings, |
26 | }); | 28 | }); |
27 | 29 | ||
28 | export interface Profile extends Instance<typeof profile> {} | 30 | export interface Profile extends Instance<typeof profile> {} |
29 | |||
30 | export interface ProfileSnapshotIn extends SnapshotIn<typeof profile> {} | ||
31 | |||
32 | export interface ProfileSnapshotOut extends SnapshotOut<typeof profile> {} | ||
diff --git a/packages/shared/src/stores/ProfileSettings.ts b/packages/shared/src/stores/ProfileSettings.ts new file mode 100644 index 0000000..ec8da5f --- /dev/null +++ b/packages/shared/src/stores/ProfileSettings.ts | |||
@@ -0,0 +1,33 @@ | |||
1 | /* | ||
2 | * Copyright (C) 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 { Instance, types, SnapshotIn, SnapshotOut } from 'mobx-state-tree'; | ||
22 | |||
23 | export const profileSettings = types.model('ProfileSettings', { | ||
24 | name: types.string, | ||
25 | }); | ||
26 | |||
27 | export interface ProfileSettings extends Instance<typeof profileSettings> {} | ||
28 | |||
29 | export interface ProfileSettingsSnapshotIn | ||
30 | extends SnapshotIn<typeof profileSettings> {} | ||
31 | |||
32 | export interface ProfileSettingsSnapshotOut | ||
33 | extends SnapshotOut<typeof profileSettings> {} | ||
diff --git a/packages/shared/src/stores/Service.ts b/packages/shared/src/stores/Service.ts index ed2cd9a..36acd3d 100644 --- a/packages/shared/src/stores/Service.ts +++ b/packages/shared/src/stores/Service.ts | |||
@@ -18,20 +18,23 @@ | |||
18 | * SPDX-License-Identifier: AGPL-3.0-only | 18 | * SPDX-License-Identifier: AGPL-3.0-only |
19 | */ | 19 | */ |
20 | 20 | ||
21 | import { Instance, types, SnapshotIn, SnapshotOut } from 'mobx-state-tree'; | 21 | import { Instance, types } from 'mobx-state-tree'; |
22 | 22 | ||
23 | import { profile } from './Profile'; | 23 | import { serviceSettings } from './ServiceSettings'; |
24 | 24 | ||
25 | export const service = types.model('Service', { | 25 | export const service = types.model('Service', { |
26 | id: types.identifier, | 26 | id: types.identifier, |
27 | name: types.string, | 27 | settings: serviceSettings, |
28 | profile: types.reference(profile), | 28 | currentUrl: types.maybe(types.string), |
29 | // TODO: Remove this once recipes are added. | 29 | canGoBack: false, |
30 | url: types.string, | 30 | canGoForward: false, |
31 | title: types.maybe(types.string), | ||
32 | state: types.optional( | ||
33 | types.enumeration('ServiceState', ['loading', 'loaded', 'crashed']), | ||
34 | 'loading', | ||
35 | ), | ||
36 | directMessageCount: 0, | ||
37 | indirectMessageCount: 0, | ||
31 | }); | 38 | }); |
32 | 39 | ||
33 | export interface Service extends Instance<typeof service> {} | 40 | export interface Service extends Instance<typeof service> {} |
34 | |||
35 | export interface ServiceSnapshotIn extends SnapshotIn<typeof service> {} | ||
36 | |||
37 | export interface ServiceSnapshotOut extends SnapshotOut<typeof service> {} | ||
diff --git a/packages/shared/src/stores/RuntimeService.ts b/packages/shared/src/stores/ServiceSettings.ts index c5b9031..54cd7eb 100644 --- a/packages/shared/src/stores/RuntimeService.ts +++ b/packages/shared/src/stores/ServiceSettings.ts | |||
@@ -20,28 +20,19 @@ | |||
20 | 20 | ||
21 | import { Instance, types, SnapshotIn, SnapshotOut } from 'mobx-state-tree'; | 21 | import { Instance, types, SnapshotIn, SnapshotOut } from 'mobx-state-tree'; |
22 | 22 | ||
23 | export const runtimeService = types.model({ | 23 | import { profile } from './Profile'; |
24 | url: types.maybe(types.string), | 24 | |
25 | canGoBack: false, | 25 | export const serviceSettings = types.model('ServiceSettings', { |
26 | canGoForward: false, | 26 | name: types.string, |
27 | title: types.maybe(types.string), | 27 | profile: types.reference(profile), |
28 | state: types.optional( | 28 | // TODO: Remove this once recipes are added. |
29 | types.enumeration('ServiceState', [ | 29 | url: types.string, |
30 | 'hibernated', | ||
31 | 'loading', | ||
32 | 'loaded', | ||
33 | 'crashed', | ||
34 | ]), | ||
35 | 'hibernated', | ||
36 | ), | ||
37 | directMessageCount: 0, | ||
38 | indirectMessageCount: 0, | ||
39 | }); | 30 | }); |
40 | 31 | ||
41 | export interface RuntimeService extends Instance<typeof runtimeService> {} | 32 | export interface ServiceSettings extends Instance<typeof serviceSettings> {} |
42 | 33 | ||
43 | export interface RuntimeServiceSnapshotIn | 34 | export interface ServiceSettingsSnapshotIn |
44 | extends SnapshotIn<typeof runtimeService> {} | 35 | extends SnapshotIn<typeof serviceSettings> {} |
45 | 36 | ||
46 | export interface RuntimeServiceSnapshotOut | 37 | export interface ServiceSettingsSnapshotOut |
47 | extends SnapshotOut<typeof runtimeService> {} | 38 | extends SnapshotOut<typeof serviceSettings> {} |
diff --git a/packages/shared/src/stores/SharedStore.ts b/packages/shared/src/stores/SharedStore.ts index e6e2cad..a04f4bf 100644 --- a/packages/shared/src/stores/SharedStore.ts +++ b/packages/shared/src/stores/SharedStore.ts | |||
@@ -26,13 +26,14 @@ import { | |||
26 | SnapshotOut, | 26 | SnapshotOut, |
27 | } from 'mobx-state-tree'; | 27 | } from 'mobx-state-tree'; |
28 | 28 | ||
29 | import { config } from './Config'; | 29 | import { globalSettings } from './GlobalSettings'; |
30 | import { runtimeService } from './RuntimeService'; | 30 | import { profile } from './Profile'; |
31 | import { service } from './Service'; | 31 | import { service } from './Service'; |
32 | 32 | ||
33 | export const sharedStore = types.model('SharedStore', { | 33 | export const sharedStore = types.model('SharedStore', { |
34 | config: types.optional(config, {}), | 34 | settings: types.optional(globalSettings, {}), |
35 | runtimeServices: types.map(runtimeService), | 35 | profiles: types.array(profile), |
36 | services: types.array(service), | ||
36 | selectedService: types.safeReference(service), | 37 | selectedService: types.safeReference(service), |
37 | shouldUseDarkColors: false, | 38 | shouldUseDarkColors: false, |
38 | }); | 39 | }); |