mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-11-29 19:13:49 +01:00
Add validation & confirm prompt for save import
This commit is contained in:
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
|
||||||
|
Loading…
Reference in New Issue
Block a user