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