aboutsummaryrefslogtreecommitdiffstats
path: root/packages/main/src/stores/SharedStore.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/main/src/stores/SharedStore.ts')
-rw-r--r--packages/main/src/stores/SharedStore.ts130
1 files changed, 95 insertions, 35 deletions
diff --git a/packages/main/src/stores/SharedStore.ts b/packages/main/src/stores/SharedStore.ts
index 861c8ce..9ec7963 100644
--- a/packages/main/src/stores/SharedStore.ts
+++ b/packages/main/src/stores/SharedStore.ts
@@ -22,12 +22,16 @@ import { sharedStore as originalSharedStore } from '@sophie/shared';
22import { 22import {
23 applySnapshot, 23 applySnapshot,
24 getSnapshot, 24 getSnapshot,
25 IMSTArray,
26 IMSTMap,
25 Instance, 27 Instance,
28 IReferenceType,
29 IStateTreeNode,
30 IType,
26 resolveIdentifier, 31 resolveIdentifier,
27 types, 32 types,
28} from 'mobx-state-tree'; 33} from 'mobx-state-tree';
29 34
30import SettingsWithId from '../utils/SettingsWithId';
31import { getLogger } from '../utils/log'; 35import { getLogger } from '../utils/log';
32import overrideProps from '../utils/overrideProps'; 36import overrideProps from '../utils/overrideProps';
33 37
@@ -51,27 +55,51 @@ function getConfigs<T>(models: { config: T }[]): T[] | undefined {
51 return models.length === 0 ? undefined : models.map((model) => model.config); 55 return models.length === 0 ? undefined : models.map((model) => model.config);
52} 56}
53 57
54function reconcileSettings<T>( 58type TypeWithSettings<C> = IType<
55 originalSnapshots: SettingsWithId<T>[], 59 { id: string; settings: C },
56 settingsSnapshotsWithId: SettingsWithId<T>[], 60 unknown,
57): SettingsWithId<T>[] { 61 { settings: IStateTreeNode<IType<C, unknown, unknown>> }
58 const idToOriginalSnapshots = new Map( 62>;
59 originalSnapshots.map((originalSnapshot) => [ 63
60 originalSnapshot.id, 64function deleteStaleModels<C, D extends TypeWithSettings<C>>(
61 originalSnapshot, 65 currentById: IMSTMap<D>,
62 ]), 66 toApplyById: Map<string, C>,
63 ); 67): void {
64 return settingsSnapshotsWithId.map(({ id, settings }) => ({ 68 const toDelete = new Set(currentById.keys());
65 ...idToOriginalSnapshots.get(id), 69 toApplyById.forEach((_settings, id) => toDelete.delete(id));
66 id, 70 toDelete.forEach((id) => currentById.delete(id));
67 settings, 71}
68 })); 72
73function applySettings<C, D extends TypeWithSettings<C>>(
74 currentById: IMSTMap<D>,
75 toApplyById: Map<string, C>,
76): void {
77 toApplyById.forEach((settingsSnapshot, id) => {
78 const model = currentById.get(id);
79 if (model === undefined) {
80 currentById.set(id, {
81 id,
82 settings: settingsSnapshot,
83 });
84 } else {
85 applySnapshot(model.settings, settingsSnapshot);
86 }
87 });
88}
89
90function pushReferences<C, D extends TypeWithSettings<C>>(
91 list: IMSTArray<IReferenceType<D>>,
92 toApply: [string, C][],
93): void {
94 list.push(...toApply.map(([id]) => id));
69} 95}
70 96
71export const sharedStore = overrideProps(originalSharedStore, { 97export const sharedStore = overrideProps(originalSharedStore, {
72 settings: types.optional(globalSettings, {}), 98 settings: types.optional(globalSettings, {}),
73 profiles: types.array(profile), 99 profilesById: types.map(profile),
74 services: types.array(service), 100 profiles: types.array(types.reference(profile)),
101 servicesById: types.map(service),
102 services: types.array(types.reference(service)),
75 selectedService: types.safeReference(service), 103 selectedService: types.safeReference(service),
76}) 104})
77 .views((self) => ({ 105 .views((self) => ({
@@ -87,25 +115,57 @@ export const sharedStore = overrideProps(originalSharedStore, {
87 })) 115 }))
88 .actions((self) => ({ 116 .actions((self) => ({
89 loadConfig(config: Config): void { 117 loadConfig(config: Config): void {
90 const snapshot = getSnapshot(self); 118 // `onPatch` will send store changes piecemeal without any attention to
91 const { profiles, services, ...settings } = config; 119 // transaction boundaries. We must make sure that any state communicated to the
92 const profileSettingsSnapshots = addMissingProfileIds(profiles); 120 // renderer process is actually valid.
93 const serviceSettingsSnapshots = addMissingServiceIdsAndProfiles( 121 const {
122 profiles,
123 profilesById,
124 selectedService,
94 services, 125 services,
95 profileSettingsSnapshots, 126 servicesById,
96 );
97 applySnapshot(self, {
98 ...snapshot,
99 settings, 127 settings,
100 profiles: reconcileSettings( 128 } = self;
101 snapshot.profiles, 129 const { id: selectedServiceId } = selectedService ?? { id: undefined };
102 profileSettingsSnapshots, 130 const {
103 ), 131 profiles: profilesConfig,
104 services: reconcileSettings( 132 services: servicesConfig,
105 snapshot.services, 133 ...settingsToApply
106 serviceSettingsSnapshots, 134 } = config;
107 ), 135 const profilesToApply = addMissingProfileIds(profilesConfig);
108 }); 136 const servicesToApply = addMissingServiceIdsAndProfiles(
137 servicesConfig,
138 profilesToApply,
139 );
140 const profilesToApplyById = new Map(profilesToApply);
141 const servicesToApplyById = new Map(servicesToApply);
142 // First remove any references to profiles and services that might be deleted.
143 self.selectedService = undefined;
144 services.clear();
145 profiles.clear();
146 // Delete all services that may depend on profiles that will be delete.
147 deleteStaleModels(servicesById, servicesToApplyById);
148 // Update existing profiles and add new profiles.
149 applySettings(profilesById, profilesToApplyById);
150 // Update existing services and add new services. This will make sure that no service
151 // depends on a profile that will be deleted.
152 applySettings(servicesById, servicesToApplyById);
153 // Now it's safe to delete stale profiles.
154 deleteStaleModels(profilesById, profilesToApplyById);
155 // We are ready to build new profile and service lists from the new models.
156 pushReferences(profiles, profilesToApply);
157 pushReferences(services, servicesToApply);
158 // Global settings may refer to particular profiles or services.
159 applySnapshot(settings, settingsToApply);
160 // Restore service selection (if applicable).
161 let newSelectedService;
162 if (selectedServiceId !== undefined) {
163 newSelectedService = servicesById.get(selectedServiceId);
164 }
165 if (newSelectedService === undefined && services.length > 0) {
166 [newSelectedService] = services;
167 }
168 self.selectedService = newSelectedService;
109 }, 169 },
110 setShouldUseDarkColors(shouldUseDarkColors: boolean): void { 170 setShouldUseDarkColors(shouldUseDarkColors: boolean): void {
111 self.shouldUseDarkColors = shouldUseDarkColors; 171 self.shouldUseDarkColors = shouldUseDarkColors;