Add validation & confirm prompt for save import

This commit is contained in:
Martin Fournier 2021-12-31 05:32:52 -05:00
parent b578e09986
commit 2b7464ebb7

@ -28,6 +28,7 @@ import { FileDiagnosticModal } from "../../Diagnostic/FileDiagnosticModal";
import { dialogBoxCreate } from "./DialogBox"; import { dialogBoxCreate } from "./DialogBox";
import { ConfirmationModal } from "./ConfirmationModal"; import { ConfirmationModal } from "./ConfirmationModal";
import { ThemeEditorModal } from "./ThemeEditorModal"; import { ThemeEditorModal } from "./ThemeEditorModal";
import { SnackbarEvents } from "./Snackbar";
import { Settings } from "../../Settings/Settings"; import { Settings } from "../../Settings/Settings";
import { save, deleteGame } from "../../db"; import { save, deleteGame } from "../../db";
@ -51,6 +52,12 @@ interface IProps {
softReset: () => void; softReset: () => void;
} }
interface ImportData {
base64: string;
parsed: any;
exportDate?: Date;
}
export function GameOptionsRoot(props: IProps): React.ReactElement { export function GameOptionsRoot(props: IProps): React.ReactElement {
const classes = useStyles(); const classes = useStyles();
const importInput = useRef<HTMLInputElement>(null); const importInput = useRef<HTMLInputElement>(null);
@ -84,6 +91,8 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
const [deleteGameOpen, setDeleteOpen] = useState(false); const [deleteGameOpen, setDeleteOpen] = useState(false);
const [themeEditorOpen, setThemeEditorOpen] = useState(false); const [themeEditorOpen, setThemeEditorOpen] = useState(false);
const [softResetOpen, setSoftResetOpen] = useState(false); const [softResetOpen, setSoftResetOpen] = useState(false);
const [importSaveOpen, setImportSaveOpen] = useState(false);
const [importData, setImportData] = useState<ImportData | null>(null);
function handleExecTimeChange(event: any, newValue: number | number[]): void { function handleExecTimeChange(event: any, newValue: number | number[]): void {
setExecTime(newValue as number); setExecTime(newValue as number);
@ -206,11 +215,67 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
return; return;
} }
const contents = result; const contents = result;
save(contents).then(() => setTimeout(() => location.reload(), 1000));
// https://stackoverflow.com/a/35002237
const base64regex = /^([0-9a-zA-Z+/]{4})*(([0-9a-zA-Z+/]{2}==)|([0-9a-zA-Z+/]{3}=))?$/;
if (!base64regex.test(contents)) {
SnackbarEvents.emit("Save game was not a base64 string", "error", 5000);
return;
}
let newSave;
try {
newSave = window.atob(contents);
newSave = newSave.trim();
} catch (error) {
console.log(error); // We'll handle below
}
if (!newSave || newSave === '') {
SnackbarEvents.emit("Save game had not content", "error", 5000);
return;
}
let parsedSave;
try {
parsedSave = JSON.parse(newSave);
} catch (error) {
console.log(error); // We'll handle below
}
if (!parsedSave || parsedSave.ctor !== 'BitburnerSaveObject' || !parsedSave.data) {
SnackbarEvents.emit("Save game did not seem valid", "error", 5000);
return;
}
const data: ImportData = {
base64: contents,
parsed: parsedSave,
}
// We don't always seem to have this value in the save file. Exporting from the option menu does not set the bonus I think.
const exportTimestamp = parsedSave.data.LastExportBonus;
if (exportTimestamp && exportTimestamp !== '0') {
data.exportDate = new Date(parseInt(exportTimestamp, 10))
}
setImportData(data)
setImportSaveOpen(true);
}; };
reader.readAsText(file); reader.readAsText(file);
} }
function confirmedImportGame(): void {
if (!importData) return;
setImportSaveOpen(false);
save(importData.base64).then(() => {
setImportData(null);
setTimeout(() => location.reload(), 1000)
});
}
function doSoftReset(): void { function doSoftReset(): void {
if (!Settings.SuppressBuyAugmentationConfirmation) { if (!Settings.SuppressBuyAugmentationConfirmation) {
setSoftResetOpen(true); setSoftResetOpen(true);
@ -618,19 +683,41 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
<Button onClick={() => setDeleteOpen(true)}>Delete Game</Button> <Button onClick={() => setDeleteOpen(true)}>Delete Game</Button>
</Box> </Box>
<Box> <Box>
<Tooltip title={<Typography>export</Typography>}> <Tooltip title={<Typography>Export your game to a text file.</Typography>}>
<Button onClick={() => props.export()}> <Button onClick={() => props.export()}>
<DownloadIcon color="primary" /> <DownloadIcon color="primary" />
Export Export Game
</Button> </Button>
</Tooltip> </Tooltip>
<Tooltip title={<Typography>import</Typography>}> <Tooltip title={<Typography>Import your game from a text file.<br/>This will <strong>overwrite</strong> your current game. Back it up first!</Typography>}>
<Button onClick={startImport}> <Button onClick={startImport}>
<UploadIcon color="primary" /> <UploadIcon color="primary" />
Import Import Game
<input ref={importInput} id="import-game-file-selector" type="file" hidden onChange={onImport} /> <input ref={importInput} id="import-game-file-selector" type="file" hidden onChange={onImport} />
</Button> </Button>
</Tooltip> </Tooltip>
<ConfirmationModal
open={importSaveOpen}
onClose={() => setImportSaveOpen(false)}
onConfirm={() => confirmedImportGame()}
confirmationText={
<>
Importing a new game will <strong>completely wipe</strong> the current data!
<br />
<br />
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 && (<>
The export date of the save file is <strong>{importData?.exportDate.toString()}</strong>
<br />
<br />
</>)}
</>
}
/>
</Box> </Box>
<Box> <Box>
<Tooltip <Tooltip