mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-12-22 14:12:27 +01:00
Add an import save comparison page
This adds a new page reachable from the import save file options menu. It shows the difference between the current save and the data that is being imported, for confirmation. Includes an "automatic" variant, which has different wording for when Electron decides it has access to a newer version of the game. While in this screen, the autosave is disabled. This also adds a new BypassWrapper component around the game's tree. It allows for content to be displayed without rendering the nested pages (import, recovery). This prevents player scripts from messing with the screen.
This commit is contained in:
parent
974344545d
commit
26432082e2
@ -81,6 +81,8 @@ import { AchievementsRoot } from "../Achievements/AchievementsRoot";
|
||||
import { ErrorBoundary } from "./ErrorBoundary";
|
||||
import { Settings } from "../Settings/Settings";
|
||||
import { ThemeBrowser } from "../Themes/ui/ThemeBrowser";
|
||||
import { ImportSaveRoot } from "./React/ImportSaveRoot";
|
||||
import { BypassWrapper } from "./React/BypassWrapper";
|
||||
|
||||
const htmlLocation = location;
|
||||
|
||||
@ -199,6 +201,9 @@ export let Router: IRouter = {
|
||||
toThemeBrowser: () => {
|
||||
throw new Error("Router called before initialization");
|
||||
},
|
||||
toImportSave: () => {
|
||||
throw new Error("Router called before initialization");
|
||||
},
|
||||
};
|
||||
|
||||
function determineStartPage(player: IPlayer): Page {
|
||||
@ -228,6 +233,11 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
|
||||
const [errorBoundaryKey, setErrorBoundaryKey] = useState<number>(0);
|
||||
const [sidebarOpened, setSideBarOpened] = useState(Settings.IsSidebarOpened);
|
||||
|
||||
const [importString, setImportString] = useState<string>(undefined as unknown as string);
|
||||
const [importAutomatic, setImportAutomatic] = useState<boolean>(false);
|
||||
if (importString === undefined && page === Page.ImportSave)
|
||||
throw new Error("Trying to go to a page without the proper setup");
|
||||
|
||||
function resetErrorBoundary(): void {
|
||||
setErrorBoundaryKey(errorBoundaryKey + 1);
|
||||
}
|
||||
@ -315,7 +325,12 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
|
||||
},
|
||||
toThemeBrowser: () => {
|
||||
setPage(Page.ThemeBrowser);
|
||||
}
|
||||
},
|
||||
toImportSave: (base64save: string, automatic = false) => {
|
||||
setImportString(base64save);
|
||||
setImportAutomatic(automatic);
|
||||
setPage(Page.ImportSave);
|
||||
},
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@ -332,11 +347,13 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
|
||||
let mainPage = <Typography>Cannot load</Typography>;
|
||||
let withSidebar = true;
|
||||
let withPopups = true;
|
||||
let bypassGame = false;
|
||||
switch (page) {
|
||||
case Page.Recovery: {
|
||||
mainPage = <RecoveryRoot router={Router} softReset={softReset} />;
|
||||
withSidebar = false;
|
||||
withPopups = false;
|
||||
bypassGame = true;
|
||||
break;
|
||||
}
|
||||
case Page.BitVerse: {
|
||||
@ -517,44 +534,62 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
|
||||
mainPage = <ThemeBrowser router={Router} />;
|
||||
break;
|
||||
}
|
||||
case Page.ImportSave: {
|
||||
mainPage = (
|
||||
<ImportSaveRoot
|
||||
importString={importString}
|
||||
automatic={importAutomatic}
|
||||
onReturning={() => Router.toTerminal()}
|
||||
/>
|
||||
);
|
||||
withSidebar = false;
|
||||
withPopups = false;
|
||||
bypassGame = true;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Context.Player.Provider value={player}>
|
||||
<Context.Router.Provider value={Router}>
|
||||
<ErrorBoundary key={errorBoundaryKey} router={Router} softReset={softReset}>
|
||||
<SnackbarProvider>
|
||||
<Overview mode={ITutorial.isRunning ? "tutorial" : "overview"}>
|
||||
{!ITutorial.isRunning ? (
|
||||
<CharacterOverview save={() => saveObject.saveGame()} killScripts={killAllScripts} />
|
||||
<BypassWrapper content={bypassGame ? mainPage : null}>
|
||||
<SnackbarProvider>
|
||||
<Overview mode={ITutorial.isRunning ? "tutorial" : "overview"}>
|
||||
{!ITutorial.isRunning ? (
|
||||
<CharacterOverview save={() => saveObject.saveGame()} killScripts={killAllScripts} />
|
||||
) : (
|
||||
<InteractiveTutorialRoot />
|
||||
)}
|
||||
</Overview>
|
||||
{withSidebar ? (
|
||||
<Box display="flex" flexDirection="row" width="100%">
|
||||
<SidebarRoot
|
||||
player={player}
|
||||
router={Router}
|
||||
page={page}
|
||||
opened={sidebarOpened}
|
||||
onToggled={(isOpened) => {
|
||||
setSideBarOpened(isOpened);
|
||||
Settings.IsSidebarOpened = isOpened;
|
||||
}}
|
||||
/>
|
||||
<Box className={classes.root}>{mainPage}</Box>
|
||||
</Box>
|
||||
) : (
|
||||
<InteractiveTutorialRoot />
|
||||
)}
|
||||
</Overview>
|
||||
{withSidebar ? (
|
||||
<Box display="flex" flexDirection="row" width="100%">
|
||||
<SidebarRoot player={player} router={Router} page={page}
|
||||
opened={sidebarOpened}
|
||||
onToggled={(isOpened) => {
|
||||
setSideBarOpened(isOpened);
|
||||
Settings.IsSidebarOpened = isOpened;
|
||||
}} />
|
||||
<Box className={classes.root}>{mainPage}</Box>
|
||||
</Box>
|
||||
) : (
|
||||
<Box className={classes.root}>{mainPage}</Box>
|
||||
)}
|
||||
<Unclickable />
|
||||
{withPopups && (
|
||||
<>
|
||||
<LogBoxManager />
|
||||
<AlertManager />
|
||||
<PromptManager />
|
||||
<InvitationModal />
|
||||
<Snackbar />
|
||||
</>
|
||||
)}
|
||||
</SnackbarProvider>
|
||||
)}
|
||||
<Unclickable />
|
||||
{withPopups && (
|
||||
<>
|
||||
<LogBoxManager />
|
||||
<AlertManager />
|
||||
<PromptManager />
|
||||
<InvitationModal />
|
||||
<Snackbar />
|
||||
</>
|
||||
)}
|
||||
</SnackbarProvider>
|
||||
</BypassWrapper>
|
||||
</ErrorBoundary>
|
||||
</Context.Router.Provider>
|
||||
</Context.Player.Provider>
|
||||
|
11
src/ui/React/BypassWrapper.tsx
Normal file
11
src/ui/React/BypassWrapper.tsx
Normal file
@ -0,0 +1,11 @@
|
||||
import React from "react";
|
||||
|
||||
interface IProps {
|
||||
children: React.ReactNode;
|
||||
content: React.ReactNode;
|
||||
}
|
||||
|
||||
export function BypassWrapper(props: IProps): React.ReactElement {
|
||||
if (!props.content) return <>{props.children}</>;
|
||||
return <>{props.content}</>;
|
||||
}
|
@ -9,6 +9,7 @@ interface IProps {
|
||||
onClose: () => void;
|
||||
onConfirm: () => void;
|
||||
confirmationText: string | React.ReactNode;
|
||||
additionalButton?: React.ReactNode;
|
||||
}
|
||||
|
||||
export function ConfirmationModal(props: IProps): React.ReactElement {
|
||||
@ -23,6 +24,7 @@ export function ConfirmationModal(props: IProps): React.ReactElement {
|
||||
>
|
||||
Confirm
|
||||
</Button>
|
||||
{props.additionalButton && <>{props.additionalButton}</>}
|
||||
</>
|
||||
</Modal>
|
||||
);
|
||||
|
@ -37,7 +37,8 @@ import { ThemeEditorButton } from "../../Themes/ui/ThemeEditorButton";
|
||||
import { StyleEditorButton } from "../../Themes/ui/StyleEditorButton";
|
||||
import { formatTime } from "../../utils/helpers/formatTime";
|
||||
import { OptionSwitch } from "./OptionSwitch";
|
||||
import { saveObject } from "../../SaveObject";
|
||||
import { ImportData, saveObject } from "../../SaveObject";
|
||||
import { convertTimeMsToTimeElapsedString } from "../../utils/StringHelperFunctions";
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
@ -58,12 +59,6 @@ interface IProps {
|
||||
softReset: () => void;
|
||||
}
|
||||
|
||||
interface ImportData {
|
||||
base64: string;
|
||||
parsed: any;
|
||||
exportDate?: Date;
|
||||
}
|
||||
|
||||
export function GameOptionsRoot(props: IProps): React.ReactElement {
|
||||
const classes = useStyles();
|
||||
const importInput = useRef<HTMLInputElement>(null);
|
||||
@ -125,7 +120,7 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
|
||||
try {
|
||||
const base64Save = await saveObject.getImportStringFromFile(event.target.files);
|
||||
const data = await saveObject.getImportDataFromString(base64Save);
|
||||
setImportData(data)
|
||||
setImportData(data);
|
||||
setImportSaveOpen(true);
|
||||
} catch (ex: any) {
|
||||
SnackbarEvents.emit(ex.toString(), "error", 5000);
|
||||
@ -145,6 +140,13 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
|
||||
setImportData(null);
|
||||
}
|
||||
|
||||
function compareSaveGame(): void {
|
||||
if (!importData) return;
|
||||
props.router.toImportSave(importData.base64);
|
||||
setImportSaveOpen(false);
|
||||
setImportData(null);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classes.root} style={{ width: "90%" }}>
|
||||
<Typography variant="h4" gutterBottom>
|
||||
@ -534,6 +536,7 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
|
||||
open={importSaveOpen}
|
||||
onClose={() => setImportSaveOpen(false)}
|
||||
onConfirm={() => confirmedImportGame()}
|
||||
additionalButton={<Button onClick={compareSaveGame}>Compare Save</Button>}
|
||||
confirmationText={
|
||||
<>
|
||||
Importing a new game will <strong>completely wipe</strong> the current data!
|
||||
@ -542,15 +545,24 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
|
||||
Make sure to have a backup of your current save file before importing.
|
||||
<br />
|
||||
The file you are attempting to import seems valid.
|
||||
<br />
|
||||
<br />
|
||||
{importData?.exportDate && (
|
||||
{(importData?.playerData?.lastSave ?? 0) > 0 && (
|
||||
<>
|
||||
The export date of the save file is <strong>{importData?.exportDate.toString()}</strong>
|
||||
<br />
|
||||
<br />
|
||||
The export date of the save file is{" "}
|
||||
<strong>{new Date(importData?.playerData?.lastSave ?? 0).toLocaleString()}</strong>
|
||||
</>
|
||||
)}
|
||||
{(importData?.playerData?.totalPlaytime ?? 0) > 0 && (
|
||||
<>
|
||||
<br />
|
||||
<br />
|
||||
Total play time of imported game:{" "}
|
||||
{convertTimeMsToTimeElapsedString(importData?.playerData?.totalPlaytime ?? 0)}
|
||||
</>
|
||||
)}
|
||||
<br />
|
||||
<br />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
|
335
src/ui/React/ImportSaveRoot.tsx
Normal file
335
src/ui/React/ImportSaveRoot.tsx
Normal file
@ -0,0 +1,335 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
import {
|
||||
Paper,
|
||||
Table,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TableBody,
|
||||
TableContainer,
|
||||
TableCell,
|
||||
Typography,
|
||||
Tooltip,
|
||||
Box,
|
||||
Button,
|
||||
ButtonGroup,
|
||||
} from "@mui/material";
|
||||
|
||||
import makeStyles from "@mui/styles/makeStyles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import { Theme } from "@mui/material/styles";
|
||||
|
||||
import ThumbUpAlt from "@mui/icons-material/ThumbUpAlt";
|
||||
import ThumbDownAlt from "@mui/icons-material/ThumbDownAlt";
|
||||
import DirectionsRunIcon from "@mui/icons-material/DirectionsRun";
|
||||
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
|
||||
import WarningIcon from "@mui/icons-material/Warning";
|
||||
|
||||
import { ImportData, saveObject } from "../../SaveObject";
|
||||
import { Settings } from "../../Settings/Settings";
|
||||
import { convertTimeMsToTimeElapsedString } from "../../utils/StringHelperFunctions";
|
||||
import { numeralWrapper } from "../numeralFormat";
|
||||
import { ConfirmationModal } from "./ConfirmationModal";
|
||||
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
root: {
|
||||
padding: theme.spacing(2),
|
||||
maxWidth: "1000px",
|
||||
|
||||
"& .MuiTable-root": {
|
||||
"& .MuiTableCell-root": {
|
||||
borderBottom: `1px solid ${Settings.theme.welllight}`,
|
||||
},
|
||||
|
||||
"& .MuiTableHead-root .MuiTableRow-root": {
|
||||
backgroundColor: Settings.theme.backgroundsecondary,
|
||||
|
||||
"& .MuiTableCell-root": {
|
||||
color: Settings.theme.primary,
|
||||
fontWeight: "bold",
|
||||
},
|
||||
},
|
||||
|
||||
"& .MuiTableBody-root": {
|
||||
"& .MuiTableRow-root:nth-of-type(odd)": {
|
||||
backgroundColor: Settings.theme.well,
|
||||
|
||||
"& .MuiTableCell-root": {
|
||||
color: Settings.theme.primarylight,
|
||||
},
|
||||
},
|
||||
"& .MuiTableRow-root:nth-of-type(even)": {
|
||||
backgroundColor: Settings.theme.backgroundsecondary,
|
||||
|
||||
"& .MuiTableCell-root": {
|
||||
color: Settings.theme.primarylight,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
function ComparisonIcon({ isBetter }: { isBetter: boolean }): JSX.Element {
|
||||
if (isBetter) {
|
||||
return (
|
||||
<Tooltip
|
||||
title={
|
||||
<>
|
||||
Imported value is <b>larger</b>!
|
||||
</>
|
||||
}
|
||||
>
|
||||
<ThumbUpAlt color="success" />
|
||||
</Tooltip>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<Tooltip
|
||||
title={
|
||||
<>
|
||||
Imported value is <b>smaller</b>!
|
||||
</>
|
||||
}
|
||||
>
|
||||
<ThumbDownAlt color="error" />
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export interface ImportSaveProps {
|
||||
importString: string;
|
||||
automatic: boolean;
|
||||
onReturning: () => void;
|
||||
}
|
||||
|
||||
let initialAutosave = 0;
|
||||
|
||||
export function ImportSaveRoot({ importString, automatic, onReturning }: ImportSaveProps): JSX.Element {
|
||||
const classes = useStyles();
|
||||
const [importData, setImportData] = useState<ImportData | undefined>();
|
||||
const [currentData, setCurrentData] = useState<ImportData | undefined>();
|
||||
const [importModalOpen, setImportModalOpen] = useState(false);
|
||||
|
||||
function handleGoBack(): void {
|
||||
Settings.AutosaveInterval = initialAutosave;
|
||||
onReturning();
|
||||
}
|
||||
|
||||
async function handleImport(): Promise<void> {
|
||||
await saveObject.importGame(importString, true);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
// We want to disable autosave while we're in this mode
|
||||
initialAutosave = Settings.AutosaveInterval;
|
||||
Settings.AutosaveInterval = 0;
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchData(): Promise<void> {
|
||||
const dataBeingImported = await saveObject.getImportDataFromString(importString);
|
||||
const dataCurrentlyInGame = await saveObject.getImportDataFromString(saveObject.getSaveString(true));
|
||||
|
||||
setImportData(dataBeingImported);
|
||||
setCurrentData(dataCurrentlyInGame);
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
if (importString) fetchData();
|
||||
}, [importString]);
|
||||
|
||||
if (!importData || !currentData) return <></>;
|
||||
return (
|
||||
<Box className={classes.root}>
|
||||
<Typography variant="h4" sx={{ mb: 2 }}>
|
||||
Import Save Comparison
|
||||
</Typography>
|
||||
{automatic && (
|
||||
<Typography sx={{ mb: 2 }}>
|
||||
We've found a <b>NEWER save</b> that you may want to use instead.
|
||||
</Typography>
|
||||
)}
|
||||
<Typography variant="body1" sx={{ mb: 2 }}>
|
||||
Your current game's data is on the left and the data that will be imported is on the right.
|
||||
<br />
|
||||
Please double check everything is fine before proceeding!
|
||||
</Typography>
|
||||
<TableContainer color="secondary" component={Paper}>
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell></TableCell>
|
||||
<TableCell>Current Game</TableCell>
|
||||
<TableCell>Being Imported</TableCell>
|
||||
<TableCell></TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell>Game Identifier</TableCell>
|
||||
<TableCell>{currentData.playerData?.identifier ?? "n/a"}</TableCell>
|
||||
<TableCell>{importData.playerData?.identifier ?? "n/a"}</TableCell>
|
||||
<TableCell>
|
||||
{importData.playerData?.identifier !== currentData.playerData?.identifier && (
|
||||
<Tooltip title="These are two different games!">
|
||||
<WarningIcon color="warning" />
|
||||
</Tooltip>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>Playtime</TableCell>
|
||||
<TableCell>{convertTimeMsToTimeElapsedString(currentData.playerData?.totalPlaytime ?? 0)}</TableCell>
|
||||
<TableCell>{convertTimeMsToTimeElapsedString(importData.playerData?.totalPlaytime ?? 0)}</TableCell>
|
||||
<TableCell>
|
||||
{importData.playerData?.totalPlaytime !== currentData.playerData?.totalPlaytime && (
|
||||
<ComparisonIcon
|
||||
isBetter={
|
||||
(importData.playerData?.totalPlaytime ?? 0) > (currentData.playerData?.totalPlaytime ?? 0)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
<TableRow>
|
||||
<TableCell>Saved On</TableCell>
|
||||
<TableCell>{new Date(currentData.playerData?.lastSave ?? 0).toLocaleString()}</TableCell>
|
||||
<TableCell>{new Date(importData.playerData?.lastSave ?? 0).toLocaleString()}</TableCell>
|
||||
<TableCell>
|
||||
{importData.playerData?.lastSave !== currentData.playerData?.lastSave && (
|
||||
<ComparisonIcon
|
||||
isBetter={(importData.playerData?.lastSave ?? 0) > (currentData.playerData?.lastSave ?? 0)}
|
||||
/>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
<TableRow>
|
||||
<TableCell>Money</TableCell>
|
||||
<TableCell>{numeralWrapper.formatMoney(currentData.playerData?.money ?? 0)}</TableCell>
|
||||
<TableCell>{numeralWrapper.formatMoney(importData.playerData?.money ?? 0)}</TableCell>
|
||||
<TableCell>
|
||||
{importData.playerData?.money !== currentData.playerData?.money && (
|
||||
<ComparisonIcon
|
||||
isBetter={(importData.playerData?.money ?? 0) > (currentData.playerData?.money ?? 0)}
|
||||
/>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
<TableRow>
|
||||
<TableCell>Hacking</TableCell>
|
||||
<TableCell>{numeralWrapper.formatSkill(currentData.playerData?.hacking ?? 0)}</TableCell>
|
||||
<TableCell>{numeralWrapper.formatSkill(importData.playerData?.hacking ?? 0)}</TableCell>
|
||||
<TableCell>
|
||||
{importData.playerData?.hacking !== currentData.playerData?.hacking && (
|
||||
<ComparisonIcon
|
||||
isBetter={(importData.playerData?.hacking ?? 0) > (currentData.playerData?.hacking ?? 0)}
|
||||
/>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
<TableRow>
|
||||
<TableCell>Augmentations</TableCell>
|
||||
<TableCell>{currentData.playerData?.augmentations}</TableCell>
|
||||
<TableCell>{importData.playerData?.augmentations}</TableCell>
|
||||
<TableCell>
|
||||
{importData.playerData?.augmentations !== currentData.playerData?.augmentations && (
|
||||
<ComparisonIcon
|
||||
isBetter={
|
||||
(importData.playerData?.augmentations ?? 0) > (currentData.playerData?.augmentations ?? 0)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
<TableRow>
|
||||
<TableCell>Factions</TableCell>
|
||||
<TableCell>{currentData.playerData?.factions}</TableCell>
|
||||
<TableCell>{importData.playerData?.factions}</TableCell>
|
||||
<TableCell>
|
||||
{importData.playerData?.factions !== currentData.playerData?.factions && (
|
||||
<ComparisonIcon
|
||||
isBetter={(importData.playerData?.factions ?? 0) > (currentData.playerData?.factions ?? 0)}
|
||||
/>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>Achievements</TableCell>
|
||||
<TableCell>{currentData.playerData?.achievements}</TableCell>
|
||||
<TableCell>{importData.playerData?.achievements}</TableCell>
|
||||
<TableCell>
|
||||
{importData.playerData?.achievements !== currentData.playerData?.achievements && (
|
||||
<ComparisonIcon
|
||||
isBetter={(importData.playerData?.achievements ?? 0) > (currentData.playerData?.achievements ?? 0)}
|
||||
/>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
<TableRow>
|
||||
<TableCell>Source Files</TableCell>
|
||||
<TableCell>{currentData.playerData?.sourceFiles}</TableCell>
|
||||
<TableCell>{importData.playerData?.sourceFiles}</TableCell>
|
||||
<TableCell>
|
||||
{importData.playerData?.sourceFiles !== currentData.playerData?.sourceFiles && (
|
||||
<ComparisonIcon
|
||||
isBetter={(importData.playerData?.sourceFiles ?? 0) > (currentData.playerData?.sourceFiles ?? 0)}
|
||||
/>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
<TableRow>
|
||||
<TableCell>BitNode</TableCell>
|
||||
<TableCell>
|
||||
{currentData.playerData?.bitNode}-{currentData.playerData?.bitNodeLevel}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{importData.playerData?.bitNode}-{importData.playerData?.bitNodeLevel}
|
||||
</TableCell>
|
||||
<TableCell></TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
|
||||
<Box sx={{ display: "flex", justifyContent: "flex-end" }}>
|
||||
<ButtonGroup>
|
||||
<Button onClick={handleGoBack} sx={{ my: 2 }} startIcon={<ArrowBackIcon />} color="secondary">
|
||||
Take me back!
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => setImportModalOpen(true)}
|
||||
sx={{ my: 2 }}
|
||||
startIcon={<DirectionsRunIcon />}
|
||||
color="warning"
|
||||
>
|
||||
Proceed with import
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
<ConfirmationModal
|
||||
open={importModalOpen}
|
||||
onClose={() => setImportModalOpen(false)}
|
||||
onConfirm={handleImport}
|
||||
confirmationText={
|
||||
<>
|
||||
Importing new save game data will <strong>completely wipe</strong> the current game data!
|
||||
<br />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
@ -38,6 +38,7 @@ export enum Page {
|
||||
Recovery,
|
||||
Achievements,
|
||||
ThemeBrowser,
|
||||
ImportSave,
|
||||
}
|
||||
|
||||
export interface ScriptEditorRouteOptions {
|
||||
@ -84,4 +85,5 @@ export interface IRouter {
|
||||
toStaneksGift(): void;
|
||||
toAchievements(): void;
|
||||
toThemeBrowser(): void;
|
||||
toImportSave(base64Save: string, automatic?: boolean): void;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user