this repo has no description
1import { Config, ExtensionLoadSource } from "@moonlight-mod/types";
2import {
3 ExtensionState,
4 MoonbaseExtension,
5 MoonbaseNatives,
6 RepositoryManifest
7} from "../types";
8import Flux from "@moonlight-mod/wp/common_flux";
9import Dispatcher from "@moonlight-mod/wp/common_fluxDispatcher";
10
11const natives: MoonbaseNatives = moonlight.getNatives("moonbase");
12const logger = moonlight.getLogger("moonbase");
13
14class MoonbaseSettingsStore extends Flux.Store<any> {
15 private origConfig: Config;
16 private config: Config;
17
18 modified: boolean;
19 submitting: boolean;
20 installing: boolean;
21
22 extensions: { [id: string]: MoonbaseExtension };
23 updates: { [id: string]: { version: string; download: string } };
24
25 constructor() {
26 super(Dispatcher);
27
28 this.origConfig = moonlightNode.config;
29 this.config = this.clone(this.origConfig);
30
31 this.modified = false;
32 this.submitting = false;
33 this.installing = false;
34
35 this.extensions = {};
36 this.updates = {};
37 for (const ext of moonlightNode.extensions) {
38 const existingExtension = this.extensions[ext.id];
39 if (existingExtension != null) continue;
40
41 this.extensions[ext.id] = {
42 ...ext,
43 state: moonlight.enabledExtensions.has(ext.id)
44 ? ExtensionState.Enabled
45 : ExtensionState.Disabled
46 };
47 }
48
49 natives.fetchRepositories(this.config.repositories).then((ret) => {
50 for (const [repo, exts] of Object.entries(ret)) {
51 try {
52 for (const ext of exts) {
53 try {
54 const existingExtension = this.extensions[ext.id];
55 if (existingExtension !== undefined) {
56 if (this.hasUpdate(repo, ext, existingExtension)) {
57 this.updates[ext.id] = {
58 version: ext.version!,
59 download: ext.download
60 };
61 }
62
63 this.extensions[ext.id].manifest = ext;
64 this.extensions[ext.id].source = {
65 type: ExtensionLoadSource.Normal,
66 url: repo
67 };
68
69 continue;
70 }
71
72 this.extensions[ext.id] = {
73 id: ext.id,
74 manifest: ext,
75 source: { type: ExtensionLoadSource.Normal, url: repo },
76 state: ExtensionState.NotDownloaded
77 };
78 } catch (e) {
79 logger.error(`Error processing extension ${ext.id}`, e);
80 }
81 }
82 } catch (e) {
83 logger.error(`Error processing repository ${repo}`, e);
84 }
85 }
86
87 this.emitChange();
88 });
89 }
90
91 // this logic sucks so bad lol
92 private hasUpdate(
93 repo: string,
94 repoExt: RepositoryManifest,
95 existing: MoonbaseExtension
96 ) {
97 return (
98 existing.source.type === ExtensionLoadSource.Normal &&
99 existing.source.url != null &&
100 existing.source.url === repo &&
101 repoExt.version != null &&
102 existing.manifest.version !== repoExt.version
103 );
104 }
105
106 // Jank
107 private isModified() {
108 const orig = JSON.stringify(this.origConfig);
109 const curr = JSON.stringify(this.config);
110 return orig !== curr;
111 }
112
113 get busy() {
114 return this.submitting || this.installing;
115 }
116
117 showNotice() {
118 return this.modified;
119 }
120
121 getExtension(id: string) {
122 return this.extensions[id];
123 }
124
125 getExtensionName(id: string) {
126 return Object.prototype.hasOwnProperty.call(this.extensions, id)
127 ? this.extensions[id].manifest.meta?.name ?? id
128 : id;
129 }
130
131 getExtensionUpdate(id: string) {
132 return Object.prototype.hasOwnProperty.call(this.updates, id)
133 ? this.updates[id]
134 : null;
135 }
136
137 getExtensionEnabled(id: string) {
138 const val = this.config.extensions[id];
139 if (val == null) return false;
140 return typeof val === "boolean" ? val : val.enabled;
141 }
142
143 getExtensionConfig<T>(id: string, key: string): T | undefined {
144 const defaultValue = this.extensions[id].manifest.settings?.[key]?.default;
145 const clonedDefaultValue = this.clone(defaultValue);
146 const cfg = this.config.extensions[id];
147
148 if (cfg == null || typeof cfg === "boolean") return clonedDefaultValue;
149 return cfg.config?.[key] ?? clonedDefaultValue;
150 }
151
152 getExtensionConfigName(id: string, key: string) {
153 return this.extensions[id].manifest.settings?.[key]?.displayName ?? key;
154 }
155
156 getExtensionConfigDescription(id: string, key: string) {
157 return this.extensions[id].manifest.settings?.[key]?.description;
158 }
159
160 setExtensionConfig(id: string, key: string, value: any) {
161 const oldConfig = this.config.extensions[id];
162 const newConfig =
163 typeof oldConfig === "boolean"
164 ? {
165 enabled: oldConfig,
166 config: { [key]: value }
167 }
168 : {
169 ...oldConfig,
170 config: { ...(oldConfig?.config ?? {}), [key]: value }
171 };
172
173 this.config.extensions[id] = newConfig;
174 this.modified = this.isModified();
175 this.emitChange();
176 }
177
178 setExtensionEnabled(id: string, enabled: boolean) {
179 let val = this.config.extensions[id];
180
181 if (val == null) {
182 this.config.extensions[id] = { enabled };
183 this.modified = this.isModified();
184 this.emitChange();
185 return;
186 }
187
188 if (typeof val === "boolean") {
189 val = enabled;
190 } else {
191 val.enabled = enabled;
192 }
193
194 this.config.extensions[id] = val;
195 this.modified = this.isModified();
196 this.emitChange();
197 }
198
199 async installExtension(id: string) {
200 const ext = this.getExtension(id);
201 if (!("download" in ext.manifest)) {
202 throw new Error("Extension has no download URL");
203 }
204
205 this.installing = true;
206 try {
207 const url = this.updates[id]?.download ?? ext.manifest.download;
208 await natives.installExtension(ext.manifest, url, ext.source.url!);
209 if (ext.state === ExtensionState.NotDownloaded) {
210 this.extensions[id].state = ExtensionState.Disabled;
211 }
212
213 delete this.updates[id];
214 } catch (e) {
215 logger.error("Error installing extension:", e);
216 }
217
218 this.installing = false;
219 this.emitChange();
220 }
221
222 async deleteExtension(id: string) {
223 const ext = this.getExtension(id);
224 if (ext == null) return;
225
226 this.installing = true;
227 try {
228 await natives.deleteExtension(ext.id);
229 this.extensions[id].state = ExtensionState.NotDownloaded;
230 } catch (e) {
231 logger.error("Error deleting extension:", e);
232 }
233
234 this.installing = false;
235 this.emitChange();
236 }
237
238 getConfigOption<K extends keyof Config>(key: K): Config[K] {
239 return this.config[key];
240 }
241
242 setConfigOption<K extends keyof Config>(key: K, value: Config[K]) {
243 this.config[key] = value;
244 this.modified = this.isModified();
245 this.emitChange();
246 }
247
248 writeConfig() {
249 this.submitting = true;
250
251 try {
252 moonlightNode.writeConfig(this.config);
253 this.origConfig = this.clone(this.config);
254 } catch (e) {
255 logger.error("Error writing config", e);
256 }
257
258 this.submitting = false;
259 this.modified = false;
260 this.emitChange();
261 }
262
263 reset() {
264 this.submitting = false;
265 this.modified = false;
266 this.config = this.clone(this.origConfig);
267 this.emitChange();
268 }
269
270 // Required because electron likes to make it immutable sometimes.
271 // This sucks.
272 private clone<T>(obj: T): T {
273 return structuredClone(obj);
274 }
275}
276
277const settingsStore = new MoonbaseSettingsStore();
278export { settingsStore as MoonbaseSettingsStore };