Merge pull request #2551 from TheMas3212/feature-catch-errors-and-softreset-recovery

Add ErrorBoundary component to catch rendering error and redirect to recovery page
This commit is contained in:
hydroflame 2022-01-15 18:00:39 -05:00 committed by GitHub
commit 8d3c366e0e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 62 additions and 29 deletions

29
src/ui/ErrorBoundary.tsx Normal file

@ -0,0 +1,29 @@
import React, { ErrorInfo } from "react";
import { RecoveryRoot } from "./React/RecoveryRoot";
import { IRouter } from "./Router";
interface IProps {
router: IRouter;
softReset: () => void;
}
export class ErrorBoundary extends React.Component<IProps> {
state: { hasError: boolean }
constructor(props: IProps) {
super(props);
this.state = { hasError: false };
}
componentDidCatch(error: Error, errorInfo: ErrorInfo): void {
console.error(error, errorInfo);
}
render(): React.ReactNode {
if (this.state.hasError) {
return <RecoveryRoot router={this.props.router} softReset={this.props.softReset} />;
}
return this.props.children;
}
static getDerivedStateFromError(): { hasError: true} {
return { hasError: true };
}
}

@ -77,6 +77,7 @@ import { enterBitNode } from "../RedPill";
import { Context } from "./Context"; import { Context } from "./Context";
import { RecoveryMode, RecoveryRoot } from "./React/RecoveryRoot"; import { RecoveryMode, RecoveryRoot } from "./React/RecoveryRoot";
import { AchievementsRoot } from "../Achievements/AchievementsRoot"; import { AchievementsRoot } from "../Achievements/AchievementsRoot";
import { ErrorBoundary } from "./ErrorBoundary";
const htmlLocation = location; const htmlLocation = location;
@ -218,6 +219,11 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
throw new Error("Trying to go to a page without the proper setup"); throw new Error("Trying to go to a page without the proper setup");
const [cinematicText, setCinematicText] = useState(""); const [cinematicText, setCinematicText] = useState("");
const [errorBoundaryKey, setErrorBoundaryKey] = useState<number>(0);
function resetErrorBoundary(): void {
setErrorBoundaryKey(errorBoundaryKey+1);
}
function rerender(): void { function rerender(): void {
setRerender((old) => old + 1); setRerender((old) => old + 1);
@ -305,12 +311,19 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
if (page !== Page.Terminal) window.scrollTo(0, 0); if (page !== Page.Terminal) window.scrollTo(0, 0);
}); });
function softReset(): void {
dialogBoxCreate("Soft Reset!");
prestigeAugmentation();
resetErrorBoundary();
Router.toTerminal();
}
let mainPage = <Typography>Cannot load</Typography>; let mainPage = <Typography>Cannot load</Typography>;
let withSidebar = true; let withSidebar = true;
let withPopups = true; let withPopups = true;
switch (page) { switch (page) {
case Page.Recovery: { case Page.Recovery: {
mainPage = <RecoveryRoot router={Router} />; mainPage = <RecoveryRoot router={Router} softReset={softReset} />;
withSidebar = false; withSidebar = false;
withPopups = false; withPopups = false;
break; break;
@ -463,11 +476,7 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
saveObject.exportGame(); saveObject.exportGame();
}} }}
forceKill={killAllScripts} forceKill={killAllScripts}
softReset={() => { softReset={softReset}
dialogBoxCreate("Soft Reset!");
prestigeAugmentation();
Router.toTerminal();
}}
/> />
); );
break; break;
@ -496,34 +505,27 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
return ( return (
<Context.Player.Provider value={player}> <Context.Player.Provider value={player}>
<Context.Router.Provider value={Router}> <Context.Router.Provider value={Router}>
<ErrorBoundary key={errorBoundaryKey} router={Router} softReset={softReset}>
<SnackbarProvider> <SnackbarProvider>
<Overview mode={ITutorial.isRunning ? "tutorial" : "overview"}> <Overview mode={ITutorial.isRunning ? "tutorial" : "overview"}>
{!ITutorial.isRunning ? ( {!ITutorial.isRunning ? (
<CharacterOverview save={() => saveObject.saveGame()} killScripts={killAllScripts} /> <CharacterOverview save={() => saveObject.saveGame()} killScripts={killAllScripts} />
) : ( ) : (
<InteractiveTutorialRoot />
)}
</Overview>
{withSidebar ? (
<Box display="flex" flexDirection="row" width="100%">
<SidebarRoot player={player} router={Router} page={page} />
<Box className={classes.root}>{mainPage}</Box> <Box className={classes.root}>{mainPage}</Box>
</Box> )}
) : ( <Unclickable />
<Box className={classes.root}>{mainPage}</Box> {withPopups && (
)} <>
<Unclickable /> <LogBoxManager />
{withPopups && ( <AlertManager />
<> <PromptManager />
<LogBoxManager /> <InvitationModal />
<AlertManager /> <Snackbar />
<PromptManager /> </>
<InvitationModal /> )}
<Snackbar /> </SnackbarProvider>
</> </ErrorBoundary>
)}
</SnackbarProvider>
</Context.Router.Provider> </Context.Router.Provider>
</Context.Player.Provider> </Context.Player.Provider>
); );

@ -15,9 +15,10 @@ export function ActivateRecoveryMode(): void {
interface IProps { interface IProps {
router: IRouter; router: IRouter;
softReset: () => void;
} }
export function RecoveryRoot({ router }: IProps): React.ReactElement { export function RecoveryRoot({ router, softReset }: IProps): React.ReactElement {
function recover(): void { function recover(): void {
RecoveryMode = false; RecoveryMode = false;
router.toTerminal(); router.toTerminal();
@ -48,6 +49,7 @@ export function RecoveryRoot({ router }: IProps): React.ReactElement {
<br /> <br />
<Typography>You can disable recovery mode now. But chances are the game will not work correctly.</Typography> <Typography>You can disable recovery mode now. But chances are the game will not work correctly.</Typography>
<Button onClick={recover}>DISABLE RECOVERY MODE</Button> <Button onClick={recover}>DISABLE RECOVERY MODE</Button>
<Button onClick={softReset}>PERFORM SOFT RESET</Button>
</> </>
); );
} }