this repo has no description
1import React from "@moonlight-mod/wp/react"; 2import * as Components from "@moonlight-mod/wp/discord/components/common/index"; 3import { useStateFromStores, useStateFromStoresObject } from "@moonlight-mod/wp/discord/packages/flux"; 4import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; 5import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores"; 6import { RepositoryManifest, UpdateState } from "../types"; 7import { ConfigExtension, DetectedExtension } from "@moonlight-mod/types"; 8 9const { Button, TabBar } = Components; 10const TabBarClasses = spacepack.findByCode(/tabBar:"tabBar_[a-z0-9]+",tabBarItem:"tabBarItem_[a-z0-9]+"/)[0].exports; 11 12const MODULE_REGEX = /Webpack-Module-(\d+)/g; 13 14const logger = moonlight.getLogger("moonbase/crashScreen"); 15 16type ErrorState = { 17 error: Error; 18 info: { 19 componentStack: string; 20 }; 21 __moonlight_update?: UpdateState; 22}; 23 24type WrapperProps = { 25 action: React.ReactNode; 26 state: ErrorState; 27}; 28 29type UpdateCardProps = { 30 id: number; 31 ext: { 32 version: string; 33 download: string; 34 updateManifest: RepositoryManifest; 35 }; 36}; 37 38const updateStrings: Record<UpdateState, string> = { 39 [UpdateState.Ready]: "A new version of moonlight is available.", 40 [UpdateState.Working]: "Updating moonlight...", 41 [UpdateState.Installed]: "Updated moonlight. Click Reload to apply changes.", 42 [UpdateState.Failed]: "Failed to update moonlight. Please use the installer." 43}; 44const buttonStrings: Record<UpdateState, string> = { 45 [UpdateState.Ready]: "Update moonlight", 46 [UpdateState.Working]: "Updating moonlight...", 47 [UpdateState.Installed]: "", 48 [UpdateState.Failed]: "Update failed" 49}; 50const extensionButtonStrings: Record<UpdateState, string> = { 51 [UpdateState.Ready]: "Update", 52 [UpdateState.Working]: "Updating...", 53 [UpdateState.Installed]: "Updated", 54 [UpdateState.Failed]: "Update failed" 55}; 56 57function ExtensionUpdateCard({ id, ext }: UpdateCardProps) { 58 const [state, setState] = React.useState(UpdateState.Ready); 59 const installed = useStateFromStores([MoonbaseSettingsStore], () => MoonbaseSettingsStore.getExtension(id), [id]); 60 61 return ( 62 <div className="moonbase-crash-extensionCard"> 63 <div className="moonbase-crash-extensionCard-meta"> 64 <div className="moonbase-crash-extensionCard-title"> 65 {ext.updateManifest.meta?.name ?? ext.updateManifest.id} 66 </div> 67 <div className="moonbase-crash-extensionCard-version">{`v${installed?.manifest?.version ?? "???"} -> v${ 68 ext.version 69 }`}</div> 70 </div> 71 <div className="moonbase-crash-extensionCard-button"> 72 <Button 73 color={Button.Colors.GREEN} 74 disabled={state !== UpdateState.Ready} 75 onClick={() => { 76 setState(UpdateState.Working); 77 MoonbaseSettingsStore.installExtension(id) 78 .then(() => setState(UpdateState.Installed)) 79 .catch(() => setState(UpdateState.Failed)); 80 }} 81 > 82 {extensionButtonStrings[state]} 83 </Button> 84 </div> 85 </div> 86 ); 87} 88 89function ExtensionDisableCard({ ext }: { ext: DetectedExtension }) { 90 function disableWithDependents() { 91 const disable = new Set<string>(); 92 disable.add(ext.id); 93 for (const [id, dependencies] of moonlightNode.processedExtensions.dependencyGraph) { 94 if (dependencies?.has(ext.id)) disable.add(id); 95 } 96 97 const config = structuredClone(moonlightNode.config); 98 for (const id in config.extensions) { 99 if (!disable.has(id)) continue; 100 if (typeof config.extensions[id] === "boolean") config.extensions[id] = false; 101 else (config.extensions[id] as ConfigExtension).enabled = false; 102 } 103 104 let msg = `Are you sure you want to disable "${ext.manifest.meta?.name ?? ext.id}"`; 105 if (disable.size > 1) { 106 msg += ` and its ${disable.size - 1} dependent${disable.size - 1 === 1 ? "" : "s"}`; 107 } 108 msg += "?"; 109 110 if (confirm(msg)) { 111 moonlightNode.writeConfig(config); 112 window.location.reload(); 113 } 114 } 115 116 return ( 117 <div className="moonbase-crash-extensionCard"> 118 <div className="moonbase-crash-extensionCard-meta"> 119 <div className="moonbase-crash-extensionCard-title">{ext.manifest.meta?.name ?? ext.id}</div> 120 <div className="moonbase-crash-extensionCard-version">{`v${ext.manifest.version ?? "???"}`}</div> 121 </div> 122 <div className="moonbase-crash-extensionCard-button"> 123 <Button color={Button.Colors.RED} onClick={disableWithDependents}> 124 Disable 125 </Button> 126 </div> 127 </div> 128 ); 129} 130 131export function wrapAction({ action, state }: WrapperProps) { 132 const [tab, setTab] = React.useState("crash"); 133 134 const { updates, updateCount } = useStateFromStoresObject([MoonbaseSettingsStore], () => { 135 const { updates } = MoonbaseSettingsStore; 136 return { 137 updates: Object.entries(updates), 138 updateCount: Object.keys(updates).length 139 }; 140 }); 141 142 const causes = React.useMemo(() => { 143 const causes = new Set<string>(); 144 if (state.error.stack) { 145 for (const [, id] of state.error.stack.matchAll(MODULE_REGEX)) 146 for (const ext of moonlight.patched.get(id) ?? []) causes.add(ext); 147 } 148 for (const [, id] of state.info.componentStack.matchAll(MODULE_REGEX)) 149 for (const ext of moonlight.patched.get(id) ?? []) causes.add(ext); 150 return [...causes]; 151 }, []); 152 153 return ( 154 <div className="moonbase-crash-wrapper"> 155 {action} 156 <TabBar 157 className={`${TabBarClasses.tabBar} moonbase-crash-tabs`} 158 type="top" 159 selectedItem={tab} 160 onItemSelect={(v) => setTab(v)} 161 > 162 <TabBar.Item className={TabBarClasses.tabBarItem} id="crash"> 163 Crash details 164 </TabBar.Item> 165 <TabBar.Item className={TabBarClasses.tabBarItem} id="extensions" disabled={updateCount === 0}> 166 {`Extension updates (${updateCount})`} 167 </TabBar.Item> 168 <TabBar.Item className={TabBarClasses.tabBarItem} id="causes" disabled={causes.length === 0}> 169 {`Possible causes (${causes.length})`} 170 </TabBar.Item> 171 </TabBar> 172 {tab === "crash" ? ( 173 <div className="moonbase-crash-details-wrapper"> 174 <pre className="moonbase-crash-details"> 175 <code> 176 {state.error.stack} 177 {"\n\nComponent stack:"} 178 {state.info.componentStack} 179 </code> 180 </pre> 181 </div> 182 ) : null} 183 {tab === "extensions" ? ( 184 <div className="moonbase-crash-extensions"> 185 {updates.map(([id, ext]) => ( 186 <ExtensionUpdateCard id={Number(id)} ext={ext} /> 187 ))} 188 </div> 189 ) : null} 190 {tab === "causes" ? ( 191 <div className="moonbase-crash-extensions"> 192 {causes 193 .map((ext) => moonlightNode.extensions.find((e) => e.id === ext)!) 194 .map((ext) => ( 195 <ExtensionDisableCard ext={ext} /> 196 ))} 197 </div> 198 ) : null} 199 </div> 200 ); 201} 202 203export function UpdateText({ state, setState }: { state: ErrorState; setState: (state: ErrorState) => void }) { 204 if (!state.__moonlight_update) { 205 setState({ 206 ...state, 207 __moonlight_update: UpdateState.Ready 208 }); 209 } 210 const newVersion = useStateFromStores([MoonbaseSettingsStore], () => MoonbaseSettingsStore.newVersion); 211 212 return newVersion == null ? null : ( 213 <p>{state.__moonlight_update !== undefined ? updateStrings[state.__moonlight_update] : ""}</p> 214 ); 215} 216 217export function UpdateButton({ state, setState }: { state: ErrorState; setState: (state: ErrorState) => void }) { 218 const newVersion = useStateFromStores([MoonbaseSettingsStore], () => MoonbaseSettingsStore.newVersion); 219 return newVersion == null || 220 state.__moonlight_update === UpdateState.Installed || 221 state.__moonlight_update === undefined ? null : ( 222 <Button 223 size={Button.Sizes.LARGE} 224 disabled={state.__moonlight_update !== UpdateState.Ready} 225 onClick={() => { 226 setState({ 227 ...state, 228 __moonlight_update: UpdateState.Working 229 }); 230 231 MoonbaseSettingsStore.updateMoonlight() 232 .then(() => { 233 setState({ 234 ...state, 235 __moonlight_update: UpdateState.Installed 236 }); 237 }) 238 .catch((e) => { 239 logger.error(e); 240 setState({ 241 ...state, 242 __moonlight_update: UpdateState.Failed 243 }); 244 }); 245 }} 246 > 247 {state.__moonlight_update !== undefined ? buttonStrings[state.__moonlight_update] : ""} 248 </Button> 249 ); 250}