bitburner-src/src/ui/React/ImportSaveRoot.tsx
Martin Fournier a47eae8b25 Add function to enable/disable routing
Wraps the toX() functions of the Router to add a check to only run the
function when it is allowed.

The check is used in the import page to prevent user scripts from
redirecting away to a crime or something else.
2022-01-26 03:56:19 -05:00

352 lines
12 KiB
TypeScript

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";
import { pushImportResult } from "../../Electron";
import { IRouter } from "../Router";
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 IProps {
importString: string;
automatic: boolean;
router: IRouter;
}
let initialAutosave = 0;
export function ImportSaveRoot(props: IProps): JSX.Element {
const classes = useStyles();
const [importData, setImportData] = useState<ImportData | undefined>();
const [currentData, setCurrentData] = useState<ImportData | undefined>();
const [importModalOpen, setImportModalOpen] = useState(false);
const [headback, setHeadback] = useState(false);
function handleGoBack(): void {
Settings.AutosaveInterval = initialAutosave;
pushImportResult(false);
props.router.allowRouting(true);
setHeadback(true)
}
async function handleImport(): Promise<void> {
await saveObject.importGame(props.importString, true);
pushImportResult(true);
}
useEffect(() => {
// We want to disable autosave while we're in this mode
initialAutosave = Settings.AutosaveInterval;
Settings.AutosaveInterval = 0;
props.router.allowRouting(false);
}, []);
useEffect(() => {
if (headback) props.router.toTerminal();
}, [headback]);
useEffect(() => {
async function fetchData(): Promise<void> {
const dataBeingImported = await saveObject.getImportDataFromString(props.importString);
const dataCurrentlyInGame = await saveObject.getImportDataFromString(saveObject.getSaveString(true));
setImportData(dataBeingImported);
setCurrentData(dataCurrentlyInGame);
return Promise.resolve();
}
if (props.importString) fetchData();
}, [props.importString]);
if (!importData || !currentData) return <></>;
return (
<Box className={classes.root}>
<Typography variant="h4" sx={{ mb: 2 }}>
Import Save Comparison
</Typography>
{props.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>
{(currentData.playerData?.lastSave ?? 0) > 0 ?
new Date(currentData.playerData?.lastSave ?? 0).toLocaleString() : 'n/a'}
</TableCell>
<TableCell>
{(importData.playerData?.lastSave ?? 0) > 0 ?
new Date(importData.playerData?.lastSave ?? 0).toLocaleString() : 'n/a'}
</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>
);
}