diff --git a/src/DevMenu/ui/General.tsx b/src/DevMenu/ui/General.tsx
index 0f7a89441..c6a2532d7 100644
--- a/src/DevMenu/ui/General.tsx
+++ b/src/DevMenu/ui/General.tsx
@@ -1,4 +1,4 @@
-import React from "react";
+import React, { useEffect, useState } from "react";
import Accordion from "@mui/material/Accordion";
import AccordionSummary from "@mui/material/AccordionSummary";
@@ -17,6 +17,8 @@ interface IProps {
}
export function General(props: IProps): React.ReactElement {
+ const [error, setError] = useState(false);
+
function addMoney(n: number) {
return function () {
props.player.gainMoney(n, "other");
@@ -43,6 +45,10 @@ export function General(props: IProps): React.ReactElement {
props.router.toBitVerse(false, false);
}
+ useEffect(() => {
+ if (error) throw new ReferenceError('Manually thrown error');
+ }, [error]);
+
return (
}>
@@ -81,6 +87,7 @@ export function General(props: IProps): React.ReactElement {
+
);
diff --git a/src/ui/ErrorBoundary.tsx b/src/ui/ErrorBoundary.tsx
index ab555124e..41da024bf 100644
--- a/src/ui/ErrorBoundary.tsx
+++ b/src/ui/ErrorBoundary.tsx
@@ -1,29 +1,58 @@
import React, { ErrorInfo } from "react";
+
+import { IErrorData, getErrorForDisplay } from "../utils/ErrorHelper";
import { RecoveryRoot } from "./React/RecoveryRoot";
-import { IRouter } from "./Router";
+import { IRouter, Page } from "./Router";
interface IProps {
router: IRouter;
softReset: () => void;
}
+interface IState {
+ error?: Error;
+ errorInfo?: React.ErrorInfo;
+ page?: Page;
+ hasError: boolean;
+}
+
+export class ErrorBoundary extends React.Component {
+ state: IState
-export class ErrorBoundary extends React.Component {
- state: { hasError: boolean }
constructor(props: IProps) {
super(props);
- this.state = { hasError: false };
+ this.state = { hasError: false } as IState;
}
+
+ reset(): void {
+ this.setState( { hasError: false } as IState);
+ }
+
componentDidCatch(error: Error, errorInfo: ErrorInfo): void {
+ this.setState({
+ errorInfo,
+ page: this.props.router.page(),
+ });
console.error(error, errorInfo);
}
render(): React.ReactNode {
if (this.state.hasError) {
- return ;
+ let errorData: IErrorData | undefined;
+ if (this.state.error) {
+ try {
+ // We don't want recursive errors, so in case this fails, it's in a try catch.
+ errorData = getErrorForDisplay(this.state.error, this.state.errorInfo, this.state.page);
+ } catch (ex) {
+ console.error(ex);
+ }
+ }
+
+ return this.reset()} />;
}
return this.props.children;
}
- static getDerivedStateFromError(): { hasError: true} {
- return { hasError: true };
+ static getDerivedStateFromError(error: Error): IState {
+ return { hasError: true, error};
}
-}
\ No newline at end of file
+}
diff --git a/src/ui/React/DeleteGameButton.tsx b/src/ui/React/DeleteGameButton.tsx
new file mode 100644
index 000000000..a2d2c2d15
--- /dev/null
+++ b/src/ui/React/DeleteGameButton.tsx
@@ -0,0 +1,32 @@
+import React, { useState } from 'react';
+import { deleteGame } from "../../db";
+import { ConfirmationModal } from "./ConfirmationModal";
+import Button from "@mui/material/Button";
+import { Tooltip } from '@mui/material';
+
+import DeleteIcon from '@mui/icons-material/Delete';
+
+interface IProps {
+ color?: "primary" | "warning" | "error";
+}
+
+export function DeleteGameButton({ color = "primary" }: IProps): React.ReactElement {
+ const [modalOpened, setModalOpened] = useState(false);
+
+ return (<>
+
+ } color={color} onClick={() => setModalOpened(true)}>Delete Save
+
+ {
+ setModalOpened(false);
+ deleteGame()
+ .then(() => setTimeout(() => location.reload(), 1000))
+ .catch((r) => console.error(`Could not delete game: ${r}`));
+ }}
+ open={modalOpened}
+ onClose={() => setModalOpened(false)}
+ confirmationText={"Really delete your game? (It's permanent!)"}
+ />
+ >)
+}
diff --git a/src/ui/React/GameOptionsRoot.tsx b/src/ui/React/GameOptionsRoot.tsx
index 432f160e6..2761626e6 100644
--- a/src/ui/React/GameOptionsRoot.tsx
+++ b/src/ui/React/GameOptionsRoot.tsx
@@ -21,6 +21,7 @@ import TextField from "@mui/material/TextField";
import DownloadIcon from "@mui/icons-material/Download";
import UploadIcon from "@mui/icons-material/Upload";
+import SaveIcon from '@mui/icons-material/Save';
import { FileDiagnosticModal } from "../../Diagnostic/FileDiagnosticModal";
import { dialogBoxCreate } from "./DialogBox";
@@ -31,9 +32,11 @@ import { StyleEditorModal } from "./StyleEditorModal";
import { SnackbarEvents } from "./Snackbar";
import { Settings } from "../../Settings/Settings";
-import { save, deleteGame } from "../../db";
+import { save } from "../../db";
import { formatTime } from "../../utils/helpers/formatTime";
import { OptionSwitch } from "./OptionSwitch";
+import { DeleteGameButton } from "./DeleteGameButton";
+import { SoftResetButton } from "./SoftResetButton";
const useStyles = makeStyles((theme: Theme) =>
createStyles({
@@ -71,10 +74,8 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
const [timestampFormat, setTimestampFormat] = useState(Settings.TimestampsFormat);
const [locale, setLocale] = useState(Settings.Locale);
const [diagnosticOpen, setDiagnosticOpen] = useState(false);
- const [deleteGameOpen, setDeleteOpen] = useState(false);
const [themeEditorOpen, setThemeEditorOpen] = useState(false);
const [styleEditorOpen, setStyleEditorOpen] = useState(false);
- const [softResetOpen, setSoftResetOpen] = useState(false);
const [importSaveOpen, setImportSaveOpen] = useState(false);
const [importData, setImportData] = useState(null);
@@ -194,14 +195,6 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
});
}
- function doSoftReset(): void {
- if (!Settings.SuppressBuyAugmentationConfirmation) {
- setSoftResetOpen(true);
- } else {
- props.softReset();
- }
- }
-
return (
@@ -531,19 +524,19 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
-
-
+
+ Export your game to a text file.}>
-
diff --git a/src/ui/React/RecoveryRoot.tsx b/src/ui/React/RecoveryRoot.tsx
index 5c0e2e42c..207399b74 100644
--- a/src/ui/React/RecoveryRoot.tsx
+++ b/src/ui/React/RecoveryRoot.tsx
@@ -1,11 +1,16 @@
-import React from "react";
-import Typography from "@mui/material/Typography";
-import Link from "@mui/material/Link";
-import Button from "@mui/material/Button";
+import React, { useEffect } from "react";
+
+import { Typography, Link, Button, ButtonGroup, Tooltip, Box, Paper, TextField } from "@mui/material";
import { Settings } from "../../Settings/Settings";
import { load } from "../../db";
import { IRouter } from "../Router";
import { download } from "../../SaveObject";
+import { IErrorData, newIssueUrl } from "../../utils/ErrorHelper";
+import { DeleteGameButton } from "./DeleteGameButton";
+import { SoftResetButton } from "./SoftResetButton";
+
+import DirectionsRunIcon from '@mui/icons-material/DirectionsRun';
+import GitHubIcon from "@mui/icons-material/GitHub";
export let RecoveryMode = false;
@@ -16,40 +21,79 @@ export function ActivateRecoveryMode(): void {
interface IProps {
router: IRouter;
softReset: () => void;
+ errorData?: IErrorData;
+ resetError?: () => void;
}
-export function RecoveryRoot({ router, softReset }: IProps): React.ReactElement {
+export function RecoveryRoot({ router, softReset, errorData, resetError }: IProps): React.ReactElement {
function recover(): void {
+ if (resetError) resetError();
RecoveryMode = false;
router.toTerminal();
}
Settings.AutosaveInterval = 0;
- load().then((content) => {
- download("RECOVERY.json", content);
- });
+
+ useEffect(() => {
+ load().then((content) => {
+ const epochTime = Math.round(Date.now() / 1000);
+ const filename = `RECOVERY_BITBURNER_${epochTime}.json`;
+ download(filename, content);
+ });
+ }, []);
+
return (
- <>
+ RECOVERY MODE ACTIVATED
- There was an error loading your save file and the game went into recovery mode. In this mode saving is disabled
+ There was an error with your save file and the game went into recovery mode. In this mode saving is disabled
and the game will automatically export your save file (to prevent corruption).
At this point it is recommended to alert a developer.
-
- File an issue on github
-
-
- Make a reddit post
-
-
- Post in the #bug-report channel on Discord.
-
+
+ File an issue on github
+
+
+ Make a reddit post
+
+
+ Post in the #bug-report channel on Discord.
+ Please include your save file.