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