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 };