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}