Skip to content

Commit

Permalink
Merge pull request #114 from moonlight-mod/cyn/moonbase-crashScreen
Browse files Browse the repository at this point in the history
moonbase: Crash screen hijack with updater
  • Loading branch information
NotNite authored Oct 17, 2024
2 parents 77cf282 + 1e3b9dd commit bc242e9
Show file tree
Hide file tree
Showing 5 changed files with 351 additions and 9 deletions.
34 changes: 33 additions & 1 deletion packages/core-extensions/src/moonbase/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,32 @@
import { ExtensionWebpackModule } from "@moonlight-mod/types";
import { ExtensionWebpackModule, Patch } from "@moonlight-mod/types";

export const patches: Patch[] = [
{
find: "window.DiscordErrors=",
replace: [
// replace reporting line with update status
{
match: /,(\(0,.\.jsx\))\("p",{children:.\.[a-zA-Z]+\.Messages.ERRORS_ACTION_TO_TAKE}\)/,
replacement: (_, createElement) =>
`,${createElement}(require("moonbase_crashScreen").UpdateText,{state:this.state,setState:this.setState.bind(this)})`
},

// wrap actions field to display error details
{
match: /(?<=return(\(0,.\.jsx\))\(.+?,)action:(.),className:/,
replacement: (_, createElement, action) =>
`action:${createElement}(require("moonbase_crashScreen").wrapAction,{action:${action},state:this.state}),className:`
},

// add update button
{
match: /(?<=\.ERRORS_RELOAD}\),(\(0,.\.jsx\))\(.,{}\))/,
replacement: (_, createElement) =>
`,${createElement}(require("moonbase_crashScreen").UpdateButton,{state:this.state,setState:this.setState.bind(this)})`
}
]
}
];

export const webpackModules: Record<string, ExtensionWebpackModule> = {
stores: {
Expand Down Expand Up @@ -43,5 +71,9 @@ export const webpackModules: Record<string, ExtensionWebpackModule> = {

moonbase: {
dependencies: [{ ext: "moonbase", id: "stores" }]
},

crashScreen: {
dependencies: [{ id: "react" }]
}
};
128 changes: 127 additions & 1 deletion packages/core-extensions/src/moonbase/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
--moonbase-fg: #fffba6;
}

.moonbase-settings > :first-child {
.moonbase-settings> :first-child {
margin-top: 0px;
}

Expand Down Expand Up @@ -53,3 +53,129 @@ textarea.moonbase-resizeable {
flex-direction: row;
gap: 8px;
}

/* crash screen */
.moonbase-crash-wrapper>[class^="buttons_"] {
gap: 1rem;
}

.moonbase-crash-wrapper {
display: flex;
flex-direction: column;
align-items: center;
gap: 1rem;
height: 50%;
width: 50vw;
max-height: 50%;
max-width: 50vw;
}

.moonbase-crash-tabs {
width: 100%;
}

.moonbase-crash-details-wrapper {
overflow-y: scroll;
color: var(--text-normal);
background: var(--background-secondary);
border: 1px solid var(--background-tertiary);
border-radius: 4px;
padding: 0.5em;

&::-webkit-scrollbar {
width: 8px;
height: 8px;
}

&::-webkit-scrollbar-thumb {
background-clip: padding-box;
border: 2px solid transparent;
border-radius: 4px;
background-color: var(--scrollbar-thin-thumb);
min-height: 40px;
}

&::-webkit-scrollbar-track {
border: 2px solid var(--scrollbar-thin-track);
background-color: var(--scrollbar-thin-track);
border-color: var(--scrollbar-thin-track);
}
}

.moonbase-crash-details {
box-sizing: border-box;
padding: 0;
font-family: var(--font-code);
font-size: .75rem;
line-height: 1rem;
margin: 6px;
white-space: pre-wrap;
background-clip: border-box;

&>code {
font-size: .875rem;
line-height: 1.125rem;
text-indent: 0;
white-space: pre-wrap;
text-size-adjust: none;
display: block;
user-select: text;
}
}

.moonbase-crash-extensions {
overflow-y: scroll;
display: grid;
grid-auto-columns: 25vw;
gap: 8px;

&::-webkit-scrollbar {
width: 8px;
height: 8px;
}

&::-webkit-scrollbar-thumb {
background-clip: padding-box;
border: 2px solid transparent;
border-radius: 4px;
background-color: var(--scrollbar-thin-thumb);
min-height: 40px;
}

&::-webkit-scrollbar-track {
border: 2px solid var(--scrollbar-thin-track);
background-color: var(--scrollbar-thin-track);
border-color: var(--scrollbar-thin-track);
}
}

.moonbase-crash-extensionCard {
color: var(--text-normal);
background: var(--background-secondary);
border: 1px solid var(--background-tertiary);
border-radius: 4px;
padding: 0.5em;
display: flex;
}

.moonbase-crash-extensionCard-meta {
display: flex;
flex-direction: column;
flex-grow: 1;
}

.moonbase-crash-extensionCard-title {
color: var(--text-normal);
font-family: var(--font-primary);
font-size: 16px;
line-height: 1.25;
font-weight: 600;
}

.moonbase-crash-extensionCard-version {
color: var(--text-muted);
font-family: var(--font-primary);
font-size: 14px;
line-height: 1.286;
font-weight: 400;
}
7 changes: 7 additions & 0 deletions packages/core-extensions/src/moonbase/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,10 @@ export type MoonbaseExtension = {
compat: ExtensionCompat;
hasUpdate: boolean;
};

export enum UpdateState {
Ready,
Working,
Installed,
Failed
}
183 changes: 183 additions & 0 deletions packages/core-extensions/src/moonbase/webpackModules/crashScreen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import React from "@moonlight-mod/wp/react";
import * as Components from "@moonlight-mod/wp/discord/components/common/index";
import { useStateFromStores, useStateFromStoresObject } from "@moonlight-mod/wp/discord/packages/flux";
import spacepack from "@moonlight-mod/wp/spacepack_spacepack";
import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores";
import { RepositoryManifest, UpdateState } from "../types";

const { Button, TabBar } = Components;
const TabBarClasses = spacepack.findByCode(/\.exports={tabBar:"tabBar_[a-z0-9]+",tabBarItem:"tabBarItem_[a-z0-9]+"}/)[0]
.exports;

const logger = moonlight.getLogger("moonbase/crashScreen");

type ErrorState = {
error: Error;
info: {
componentStack: string;
};
__moonlight_update?: UpdateState;
};

type WrapperProps = {
action: React.ReactNode;
state: ErrorState;
};

type UpdateCardProps = {
id: number;
ext: {
version: string;
download: string;
updateManifest: RepositoryManifest;
};
};

const updateStrings: Record<UpdateState, string> = {
[UpdateState.Ready]: "A new version of moonlight is available.",
[UpdateState.Working]: "Updating moonlight...",
[UpdateState.Installed]: "Updated moonlight. Click Reload to apply changes.",
[UpdateState.Failed]: "Failed to update moonlight. Please use the installer."
};
const buttonStrings: Record<UpdateState, string> = {
[UpdateState.Ready]: "Update moonlight",
[UpdateState.Working]: "Updating moonlight...",
[UpdateState.Installed]: "",
[UpdateState.Failed]: "Update failed"
};
const extensionButtonStrings: Record<UpdateState, string> = {
[UpdateState.Ready]: "Update",
[UpdateState.Working]: "Updating...",
[UpdateState.Installed]: "Updated",
[UpdateState.Failed]: "Update failed"
};

function ExtensionUpdateCard({ id, ext }: UpdateCardProps) {
const [state, setState] = React.useState(UpdateState.Ready);
const installed = useStateFromStores([MoonbaseSettingsStore], () => MoonbaseSettingsStore.getExtension(id), [id]);

return (
<div className="moonbase-crash-extensionCard">
<div className="moonbase-crash-extensionCard-meta">
<div className="moonbase-crash-extensionCard-title">
{ext.updateManifest.meta?.name ?? ext.updateManifest.id}
</div>
<div className="moonbase-crash-extensionCard-version">{`v${installed?.manifest?.version ?? "???"} -> v${
ext.version
}`}</div>
</div>
<div className="moonbase-crash-extensionCard-button">
<Button
color={Button.Colors.GREEN}
disabled={state !== UpdateState.Ready}
onClick={() => {
setState(UpdateState.Working);
MoonbaseSettingsStore.installExtension(id)
.then(() => setState(UpdateState.Installed))
.catch(() => setState(UpdateState.Failed));
}}
>
{extensionButtonStrings[state]}
</Button>
</div>
</div>
);
}

export function wrapAction({ action, state }: WrapperProps) {
const [tab, setTab] = React.useState("crash");

const { updates, updateCount } = useStateFromStoresObject([MoonbaseSettingsStore], () => {
const { updates } = MoonbaseSettingsStore;
return {
updates: Object.entries(updates),
updateCount: Object.keys(updates).length
};
});

return (
<div className="moonbase-crash-wrapper">
{action}
<TabBar
className={`${TabBarClasses.tabBar} moonbase-crash-tabs`}
type="top"
selectedItem={tab}
onItemSelect={(v) => setTab(v)}
>
<TabBar.Item className={TabBarClasses.tabBarItem} id="crash">
Crash Details
</TabBar.Item>
<TabBar.Item className={TabBarClasses.tabBarItem} id="extensions" disabled={updateCount === 0}>
{`Extension Updates (${updateCount})`}
</TabBar.Item>
</TabBar>
{tab === "crash" ? (
<div className="moonbase-crash-details-wrapper">
<pre className="moonbase-crash-details">
<code>
{state.error.stack}
{"\n\nComponent stack:"}
{state.info.componentStack}
</code>
</pre>
</div>
) : null}
{tab === "extensions" ? (
<div className="moonbase-crash-extensions">
{updates.map(([id, ext]) => (
<ExtensionUpdateCard id={Number(id)} ext={ext} />
))}
</div>
) : null}
</div>
);
}

export function UpdateText({ state, setState }: { state: ErrorState; setState: (state: ErrorState) => void }) {
if (!state.__moonlight_update) {
setState({
...state,
__moonlight_update: UpdateState.Ready
});
}
const newVersion = useStateFromStores([MoonbaseSettingsStore], () => MoonbaseSettingsStore.newVersion);

return newVersion == null ? null : (
<p>{state.__moonlight_update !== undefined ? updateStrings[state.__moonlight_update] : ""}</p>
);
}

export function UpdateButton({ state, setState }: { state: ErrorState; setState: (state: ErrorState) => void }) {
const newVersion = useStateFromStores([MoonbaseSettingsStore], () => MoonbaseSettingsStore.newVersion);
return newVersion == null ||
state.__moonlight_update === UpdateState.Installed ||
state.__moonlight_update === undefined ? null : (
<Button
size={Button.Sizes.LARGE}
disabled={state.__moonlight_update !== UpdateState.Ready}
onClick={() => {
setState({
...state,
__moonlight_update: UpdateState.Working
});

MoonbaseSettingsStore.updateMoonlight()
.then(() => {
setState({
...state,
__moonlight_update: UpdateState.Installed
});
})
.catch((e) => {
logger.error(e);
setState({
...state,
__moonlight_update: UpdateState.Failed
});
});
}}
>
{state.__moonlight_update !== undefined ? buttonStrings[state.__moonlight_update] : ""}
</Button>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,7 @@ import * as Components from "@moonlight-mod/wp/discord/components/common/index";
import React from "@moonlight-mod/wp/react";
import spacepack from "@moonlight-mod/wp/spacepack_spacepack";
import Flex from "@moonlight-mod/wp/discord/uikit/Flex";

enum UpdateState {
Ready,
Working,
Installed,
Failed
}
import { UpdateState } from "../../types";

const { ThemeDarkIcon, Text, Button } = Components;
const Margins = spacepack.require("discord/styles/shared/Margins.css");
Expand Down

0 comments on commit bc242e9

Please sign in to comment.