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