UI: Correct behavior of "back" button on faction augs page

Plus router refactoring
This commit is contained in:
Aleksei Bezrodnov 2023-06-26 10:24:37 +02:00 committed by GitHub
parent 9a0a843ffc
commit 1af01401d9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 173 additions and 201 deletions

@ -1,6 +1,7 @@
import React, { useState, useEffect } from "react";
import { Modal } from "../../ui/React/Modal";
import { Router } from "../../ui/GameRoot";
import { Page } from "../../ui/Router";
import { EventEmitter } from "../../utils/EventEmitter";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
@ -10,7 +11,7 @@ export const BitFlumeEvent = new EventEmitter<[]>();
export function BitFlumeModal(): React.ReactElement {
const [open, setOpen] = useState(false);
function flume(): void {
Router.toBitVerse(true, false);
Router.toPage(Page.BitVerse, { flume: true, quick: false });
setOpen(false);
}

@ -4,6 +4,7 @@ import { BlackOperationName, FactionName } from "@enums";
import { BlackOpList } from "./BlackOpList";
import { Bladeburner } from "../Bladeburner";
import { Router } from "../../ui/GameRoot";
import { Page } from "../../ui/Router";
import { CorruptableText } from "../../ui/React/CorruptableText";
interface IProps {
@ -28,7 +29,7 @@ export function BlackOpPage(props: IProps): React.ReactElement {
losses.
</Typography>
{props.bladeburner.blackops[BlackOperationName.OperationDaedalus] ? (
<Button sx={{ my: 1, p: 1 }} onClick={() => Router.toBitVerse(false, false)}>
<Button sx={{ my: 1, p: 1 }} onClick={() => Router.toPage(Page.BitVerse, { flume: false, quick: false })}>
<CorruptableText content="Destroy w0rld_d34mon"></CorruptableText>
</Button>
) : (

@ -8,6 +8,7 @@ import { Money } from "../../ui/React/Money";
import { formatNumberNoSuffix, formatPopulation, formatBigNumber } from "../../ui/formatNumber";
import { Factions } from "../../Faction/Factions";
import { Router } from "../../ui/GameRoot";
import { Page } from "../../ui/Router";
import { joinFaction } from "../../Faction/FactionHelpers";
import { Bladeburner } from "../Bladeburner";
@ -34,7 +35,7 @@ export function Stats(props: IProps): React.ReactElement {
joinFaction(faction);
}
Router.toFaction(faction);
Router.toPage(Page.Faction, { faction });
}
return (

@ -16,6 +16,7 @@ import { Player } from "@player";
import { FactionName } from "@enums";
import { Money } from "../../ui/React/Money";
import { Router } from "../../ui/GameRoot";
import { Page } from "../../ui/Router";
import { Bladeburner } from "../../Bladeburner/Bladeburner";
import { GangConstants } from "../../Gang/data/Constants";
import { checkForMessagesToSend } from "../../Message/MessageHelpers";
@ -37,10 +38,10 @@ export function General(): React.ReactElement {
const upgradeRam = () => (Player.getHomeComputer().maxRam *= 2);
// Node-clearing functions
const quickB1tFlum3 = () => Router.toBitVerse(true, true);
const b1tflum3 = () => Router.toBitVerse(true, false);
const quickHackW0r1dD43m0n = () => Router.toBitVerse(false, true);
const hackW0r1dD43m0n = () => Router.toBitVerse(false, false);
const quickB1tFlum3 = () => Router.toPage(Page.BitVerse, { flume: true, quick: true });
const b1tflum3 = () => Router.toPage(Page.BitVerse, { flume: true, quick: false });
const quickHackW0r1dD43m0n = () => Router.toPage(Page.BitVerse, { flume: false, quick: true });
const hackW0r1dD43m0n = () => Router.toPage(Page.BitVerse, { flume: false, quick: false });
// Corp functions
const createCorporation = () => {

@ -1,5 +1,6 @@
import { Player } from "@player";
import { Router } from "./ui/GameRoot";
import { Page } from "./ui/Router";
import { Terminal } from "./Terminal";
import { SnackbarEvents } from "./ui/React/Snackbar";
import { ToastVariant } from "@enums";
@ -28,8 +29,8 @@ declare global {
triggerGameExport: () => void;
triggerScriptsExport: () => void;
getSaveData: () => { save: string; fileName: string };
getSaveInfo: (base64save: string) => Promise<ImportPlayerData | undefined>;
pushSaveData: (base64save: string, automatic?: boolean) => void;
getSaveInfo: (base64Save: string) => Promise<ImportPlayerData | undefined>;
pushSaveData: (base64Save: string, automatic?: boolean) => void;
};
electronBridge: {
send: (channel: string, data?: unknown) => void;
@ -137,16 +138,17 @@ function initSaveFunctions(): void {
fileName: saveObject.getSaveFileName(),
};
},
getSaveInfo: async (base64save: string): Promise<ImportPlayerData | undefined> => {
getSaveInfo: async (base64Save: string): Promise<ImportPlayerData | undefined> => {
try {
const data = await saveObject.getImportDataFromString(base64save);
const data = await saveObject.getImportDataFromString(base64Save);
return data.playerData;
} catch (error) {
console.error(error);
return;
}
},
pushSaveData: (base64save: string, automatic = false): void => Router.toImportSave(base64save, automatic),
pushSaveData: (base64Save: string, automatic = false): void =>
Router.toPage(Page.ImportSave, { base64Save, automatic }),
};
// Will be consumed by the electron wrapper.

@ -1,5 +1,5 @@
import { Box, Button, Tooltip, Typography, Paper, Container } from "@mui/material";
import React from "react";
import { Box, Button, Tooltip, Typography, Paper, Container } from "@mui/material";
import { Augmentations } from "../../Augmentation/Augmentations";
import { getAugCost, getGenericAugmentationPriceMultiplier } from "../../Augmentation/AugmentationHelpers";
@ -11,22 +11,18 @@ import { Player } from "@player";
import { formatBigNumber } from "../../ui/formatNumber";
import { Favor } from "../../ui/React/Favor";
import { Reputation } from "../../ui/React/Reputation";
import { Router } from "../../ui/GameRoot";
import { Faction } from "../Faction";
import { getFactionAugmentationsFiltered, hasAugmentationPrereqs, purchaseAugmentation } from "../FactionHelpers";
import { CONSTANTS } from "../../Constants";
import { useRerender } from "../../ui/React/hooks";
interface IProps {
faction: Faction;
routeToMainPage: () => void;
}
/** Root React Component for displaying a faction's "Purchase Augmentations" page */
export function AugmentationsPage(props: IProps): React.ReactElement {
export function AugmentationsPage({ faction }: { faction: Faction }): React.ReactElement {
const rerender = useRerender();
function getAugs(): AugmentationName[] {
return getFactionAugmentationsFiltered(props.faction);
return getFactionAugmentationsFiltered(faction);
}
function getAugsSorted(): AugmentationName[] {
@ -66,7 +62,7 @@ export function AugmentationsPage(props: IProps): React.ReactElement {
const aug = Augmentations[augName];
const augCosts = getAugCost(aug);
const repCost = augCosts.repCost;
const hasReq = props.faction.playerReputation >= repCost;
const hasReq = faction.playerReputation >= repCost;
const hasRep = hasAugmentationPrereqs(aug);
const hasCost = augCosts.moneyCost !== 0 && Player.money > augCosts.moneyCost;
return hasCost && hasReq && hasRep;
@ -126,7 +122,7 @@ export function AugmentationsPage(props: IProps): React.ReactElement {
const owned = augs.filter((aug) => !purchasable.includes(aug));
const multiplierComponent =
props.faction.name !== FactionName.ShadowsOfAnarchy ? (
faction.name !== FactionName.ShadowsOfAnarchy ? (
<Tooltip
title={
<Typography>
@ -171,27 +167,27 @@ export function AugmentationsPage(props: IProps): React.ReactElement {
return (
<>
<Container disableGutters maxWidth="lg" sx={{ mx: 0 }}>
<Button onClick={props.routeToMainPage}>Back</Button>
<Typography variant="h4">Faction Augmentations - {props.faction.name}</Typography>
<Button onClick={() => Router.back()}>Back</Button>
<Typography variant="h4">Faction Augmentations - {faction.name}</Typography>
<Paper sx={{ p: 1, mb: 1 }}>
<Typography>
These are all of the Augmentations that are available to purchase from <b>{props.faction.name}</b>.
Augmentations are powerful upgrades that will enhance your abilities.
These are all of the Augmentations that are available to purchase from <b>{faction.name}</b>. Augmentations
are powerful upgrades that will enhance your abilities.
<br />
</Typography>
<Box
sx={{
display: "grid",
gridTemplateColumns: `repeat(${props.faction.name === FactionName.ShadowsOfAnarchy ? "2" : "3"}, 1fr)`,
gridTemplateColumns: `repeat(${faction.name === FactionName.ShadowsOfAnarchy ? "2" : "3"}, 1fr)`,
justifyItems: "center",
my: 1,
}}
>
<>{multiplierComponent}</>
<Typography>
<b>Reputation:</b> <Reputation reputation={props.faction.playerReputation} />
<b>Reputation:</b> <Reputation reputation={faction.playerReputation} />
<br />
<b>Favor:</b> <Favor favor={Math.floor(props.faction.favor)} />
<b>Favor:</b> <Favor favor={Math.floor(faction.favor)} />
</Typography>
</Box>
<Box sx={{ display: "grid", gridTemplateColumns: "repeat(4, 1fr)" }}>
@ -216,7 +212,7 @@ export function AugmentationsPage(props: IProps): React.ReactElement {
const costs = getAugCost(aug);
return (
hasAugmentationPrereqs(aug) &&
props.faction.playerReputation >= costs.repCost &&
faction.playerReputation >= costs.repCost &&
(costs.moneyCost === 0 || Player.money > costs.moneyCost)
);
}}
@ -224,12 +220,12 @@ export function AugmentationsPage(props: IProps): React.ReactElement {
if (!Settings.SuppressBuyAugmentationConfirmation) {
showModal(true);
} else {
purchaseAugmentation(aug, props.faction);
purchaseAugmentation(aug, faction);
rerender();
}
}}
rep={props.faction.playerReputation}
faction={props.faction}
rep={faction.playerReputation}
faction={faction}
/>
</>
);

@ -5,7 +5,6 @@
*/
import React, { useState } from "react";
import { AugmentationsPage } from "./AugmentationsPage";
import { DonateOption } from "./DonateOption";
import { Info } from "./Info";
import { Option } from "./Option";
@ -24,10 +23,9 @@ import { FactionWork } from "../../Work/FactionWork";
import { useRerender } from "../../ui/React/hooks";
import { repNeededToDonate } from "../formulas/donation";
interface IProps {
type FactionRootProps = {
faction: Faction;
augPage: boolean;
}
};
// Info text for all options on the UI
const hackingContractsInfo =
@ -148,11 +146,8 @@ function MainPage({ faction, rerender, onAugmentations }: IMainProps): React.Rea
);
}
export function FactionRoot(props: IProps): React.ReactElement {
export function FactionRoot({ faction }: FactionRootProps): React.ReactElement {
const rerender = useRerender(200);
const [purchasingAugs, setPurchasingAugs] = useState(props.augPage);
const faction = props.faction;
if (!Player.factions.includes(faction.name)) {
return (
@ -165,9 +160,11 @@ export function FactionRoot(props: IProps): React.ReactElement {
);
}
return purchasingAugs ? (
<AugmentationsPage faction={faction} routeToMainPage={() => setPurchasingAugs(false)} />
) : (
<MainPage rerender={rerender} faction={faction} onAugmentations={() => setPurchasingAugs(true)} />
return (
<MainPage
rerender={rerender}
faction={faction}
onAugmentations={() => Router.toPage(Page.FactionAugmentations, { faction })}
/>
);
}

@ -1,16 +1,18 @@
import type { Faction } from "../Faction";
import React, { useEffect } from "react";
import { Explore, Info, LastPage, LocalPolice, NewReleases, Report, SportsMma } from "@mui/icons-material";
import { Box, Button, Container, Paper, Tooltip, Typography, useTheme } from "@mui/material";
import { Player } from "@player";
import { FactionName } from "@enums";
import { Settings } from "../../Settings/Settings";
import { formatFavor, formatReputation } from "../../ui/formatNumber";
import { Router } from "../../ui/GameRoot";
import { FactionName } from "@enums";
import { Page } from "../../ui/Router";
import { useRerender } from "../../ui/React/hooks";
import { Faction } from "../Faction";
import { getFactionAugmentationsFiltered, joinFaction } from "../FactionHelpers";
import { Factions } from "../Factions";
import { useRerender } from "../../ui/React/hooks";
export const InvitationsSeen: string[] = [];
@ -53,11 +55,11 @@ const FactionElement = (props: FactionElementProps): React.ReactElement => {
const augsLeft = getFactionAugmentationsFiltered(props.faction).filter((aug) => !Player.hasAugmentation(aug)).length;
function openFaction(faction: Faction): void {
Router.toFaction(faction);
Router.toPage(Page.Faction, { faction });
}
function openFactionAugPage(faction: Faction): void {
Router.toFaction(faction, true);
Router.toPage(Page.FactionAugmentations, { faction });
}
function acceptInvitation(event: React.MouseEvent<HTMLButtonElement>, faction: FactionName): void {

@ -95,7 +95,7 @@ export const GameOptionsSidebar = (props: IProps): React.ReactElement => {
function compareSaveGame(): void {
if (!importData) return;
Router.toImportSave(importData.base64);
Router.toPage(Page.ImportSave, { base64Save: importData.base64 });
setImportSaveOpen(false);
setImportData(null);
}

@ -43,7 +43,7 @@ function toLocation(location: Location): void {
} else if (location.name === LocationName.WorldStockExchange) {
Router.toPage(Page.StockMarket);
} else {
Router.toLocation(location);
Router.toPage(Page.Location, { location });
}
}

@ -151,11 +151,10 @@ export function CompanyLocation(props: IProps): React.ReactElement {
if (!e.isTrusted) {
return;
}
const loc = location;
if (!loc.infiltrationData)
if (!location.infiltrationData)
throw new Error(`trying to start infiltration at ${props.locName} but the infiltrationData is null`);
Router.toInfiltration(loc);
Router.toPage(Page.Infiltration, { location });
}
function work(e: React.MouseEvent<HTMLElement>): void {

@ -23,8 +23,8 @@ import { findCrime } from "../Crime/CrimeHelpers";
import { CompanyPositions } from "../Company/CompanyPositions";
import { DarkWebItems } from "../DarkWeb/DarkWebItems";
import { Router } from "../ui/GameRoot";
import { SpecialServers } from "../Server/data/SpecialServers";
import { Page } from "../ui/Router";
import { SpecialServers } from "../Server/data/SpecialServers";
import { Locations } from "../Locations/Locations";
import { GetServer } from "../Server/AllServers";
import { Programs } from "../Programs/Programs";
@ -231,7 +231,7 @@ export function NetscriptSingularity(): InternalAPI<ISingularity> {
} else if (location.name === LocationName.WorldStockExchange) {
Router.toPage(Page.StockMarket);
} else {
Router.toLocation(location);
Router.toPage(Page.Location, { location });
}
Player.gainIntelligenceExp(CONSTANTS.IntelligenceSingFnBaseExpGain / 50000);
return true;
@ -559,7 +559,7 @@ export function NetscriptSingularity(): InternalAPI<ISingularity> {
server.backdoorInstalled = true;
if (SpecialServers.WorldDaemon === server.hostname) {
return Router.toBitVerse(false, false);
return Router.toPage(Page.BitVerse, { flume: false, quick: false });
}
// Manunally check for faction invites
Engine.Counters.checkFactionInvitations = 0;

@ -1,7 +1,7 @@
import type { Augmentation } from "../../../Augmentation/Augmentation";
import { Player } from "@player";
import { AugmentationName, LocationName } from "@enums";
import { AugmentationName } from "@enums";
import React, { useState } from "react";
import { CheckBox, CheckBoxOutlineBlank, Construction } from "@mui/icons-material";
@ -11,7 +11,6 @@ import { GraftingWork } from "../../../Work/GraftingWork";
import { Augmentations } from "../../../Augmentation/Augmentations";
import { CONSTANTS } from "../../../Constants";
import { hasAugmentationPrereqs } from "../../../Faction/FactionHelpers";
import { Locations } from "../../../Locations/Locations";
import { PurchaseAugmentationsOrderSetting } from "../../../Settings/SettingEnums";
import { Settings } from "../../../Settings/Settings";
import { Router } from "../../../ui/GameRoot";
@ -87,7 +86,7 @@ export const GraftingRoot = (): React.ReactElement => {
return (
<Container disableGutters maxWidth="lg" sx={{ mx: 0 }}>
<Button onClick={() => Router.toLocation(Locations[LocationName.NewTokyoVitaLife])}>Back</Button>
<Button onClick={() => Router.back()}>Back</Button>
<Typography variant="h4">Grafting Laboratory</Typography>
<Typography>
You find yourself in a secret laboratory, owned by a mysterious researcher.

@ -41,7 +41,7 @@ import PublicIcon from "@mui/icons-material/Public";
import LiveHelpIcon from "@mui/icons-material/LiveHelp";
import { Router } from "../../ui/GameRoot";
import { Page, SimplePage } from "../../ui/Router";
import { Page, isSimplePage } from "../../ui/Router";
import { SidebarAccordion } from "./SidebarAccordion";
import { Player } from "@player";
import { CONSTANTS } from "../../Constants";
@ -104,11 +104,7 @@ const useStyles = makeStyles((theme: Theme) =>
}),
);
interface IProps {
page: Page;
}
export function SidebarRoot(props: IProps): React.ReactElement {
export function SidebarRoot(props: { page: Page }): React.ReactElement {
useRerender(200);
let flash: Page | null = null;
@ -239,11 +235,11 @@ export function SidebarRoot(props: IProps): React.ReactElement {
const clickPage = useCallback(
(page: Page) => {
if (page === Page.Job) {
Router.toJob(Locations[Object.keys(Player.jobs)[0]]);
Router.toPage(page, { location: Locations[Object.keys(Player.jobs)[0]] });
} else if (page == Page.ScriptEditor) {
Router.toScriptEditor();
} else if ((Object.values(SimplePage) as Page[]).includes(page)) {
Router.toPage(page as SimplePage);
Router.toPage(page, {});
} else if (isSimplePage(page)) {
Router.toPage(page);
} else {
throw new Error("Can't handle click on Page " + page);
}

@ -1,5 +1,6 @@
import { Output, Link, RawOutput, TTimer } from "./OutputTypes";
import { Router } from "../ui/GameRoot";
import { Page } from "../ui/Router";
import { Player } from "@player";
import { HacknetServer } from "../Hacknet/HacknetServer";
import { BaseServer } from "../Server/BaseServer";
@ -206,7 +207,7 @@ export class Terminal {
// Success!
server.backdoorInstalled = true;
if (SpecialServers.WorldDaemon === server.hostname) {
Router.toBitVerse(false, false);
Router.toPage(Page.BitVerse, { flume: false, quick: false });
return;
}
// Manunally check for faction invites
@ -301,7 +302,7 @@ export class Terminal {
if (Player.bitNodeN == null) {
Player.bitNodeN = 1;
}
Router.toBitVerse(false, false);
Router.toPage(Page.BitVerse, { flume: false, quick: false });
return;
}
// Manunally check for faction invites

@ -1,5 +1,5 @@
import { Terminal } from "../../../Terminal";
import { ScriptEditorRouteOptions } from "../../../ui/Router";
import { ScriptEditorRouteOptions, Page } from "../../../ui/Router";
import { Router } from "../../../ui/GameRoot";
import { BaseServer } from "../../../Server/BaseServer";
import { CursorPositions } from "../../../ScriptEditor/CursorPositions";
@ -26,17 +26,17 @@ export async function main(ns) {
export function commonEditor(
command: string,
{ args, server }: EditorParameters,
scriptEditorRouteOptions?: ScriptEditorRouteOptions,
options?: ScriptEditorRouteOptions,
): void {
if (args.length < 1) return Terminal.error(`Incorrect usage of ${command} command. Usage: ${command} [scriptname]`);
const filesToOpen = new Map<ScriptFilePath | TextFilePath, string>();
const files = new Map<ScriptFilePath | TextFilePath, string>();
for (const arg of args) {
const pattern = String(arg);
// Glob of existing files
if (pattern.includes("*") || pattern.includes("?")) {
for (const [path, file] of getGlobbedFileMap(pattern, server, Terminal.currDir)) {
filesToOpen.set(path, file.content);
files.set(path, file.content);
}
continue;
}
@ -49,8 +49,8 @@ export function commonEditor(
}
const file = server.getContentFile(path);
const content = file ? file.content : isNs2(path) ? newNs2Template : "";
filesToOpen.set(path, content);
files.set(path, content);
if (content === newNs2Template) CursorPositions.saveCursor(path, { row: 3, column: 5 });
}
Router.toScriptEditor(filesToOpen, scriptEditorRouteOptions);
Router.toPage(Page.ScriptEditor, { files, options });
}

@ -1,15 +1,17 @@
import React from "react";
import { Theme } from "@mui/material/styles";
import type { TextFilePath } from "../../Paths/TextFilePath";
import type { ContractFilePath } from "../../Paths/ContractFilePath";
import type { ProgramFilePath } from "../../Paths/ProgramFilePath";
import type { ContentFilePath } from "../../Paths/ContentFile";
import type { ScriptFilePath } from "../../Paths/ScriptFilePath";
import { Theme } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles";
import makeStyles from "@mui/styles/makeStyles";
import React from "react";
import { BaseServer } from "../../Server/BaseServer";
import { Router } from "../../ui/GameRoot";
import { Page } from "../../ui/Router";
import { Terminal } from "../../Terminal";
import libarg from "arg";
import { showLiterature } from "../../Literature/LiteratureHelpers";
@ -133,9 +135,9 @@ export function ls(args: (string | number | boolean)[], server: BaseServer): voi
const fullPath = combinePath(baseDirectory, props.path);
function onClick() {
const code = server.scripts.get(fullPath)?.content ?? "";
const map = new Map<ContentFilePath, string>();
map.set(fullPath, code);
Router.toScriptEditor(map);
const files = new Map<ContentFilePath, string>();
files.set(fullPath, code);
Router.toPage(Page.ScriptEditor, { files });
}
return (
<span>

@ -1,6 +1,3 @@
import type { ScriptFilePath } from "../Paths/ScriptFilePath";
import type { TextFilePath } from "../Paths/TextFilePath";
import React, { useState, useEffect } from "react";
import { createStyles, makeStyles } from "@mui/styles";
import { Box, Typography } from "@mui/material";
@ -11,19 +8,16 @@ import { installAugmentations } from "../Augmentation/AugmentationHelpers";
import { saveObject } from "../SaveObject";
import { onExport } from "../ExportBonus";
import { LocationName } from "@enums";
import { Location } from "../Locations/Location";
import { ITutorial, iTutorialStart } from "../InteractiveTutorial";
import { InteractiveTutorialRoot } from "./InteractiveTutorial/InteractiveTutorialRoot";
import { ITutorialEvents } from "./InteractiveTutorial/ITutorialEvents";
import { Faction } from "../Faction/Faction";
import { prestigeAugmentation } from "../Prestige";
import { dialogBoxCreate } from "./React/DialogBox";
import { GetAllServers } from "../Server/AllServers";
import { Factions } from "../Faction/Factions";
import { StockMarket } from "../StockMarket/StockMarket";
import { Page, SimplePage, IRouter } from "./Router";
import { Page, PageWithContext, IRouter, ComplexPage, PageContext } from "./Router";
import { Overview } from "./React/Overview";
import { SidebarRoot } from "../Sidebar/ui/SidebarRoot";
import { AugmentationsRoot } from "../Augmentation/ui/AugmentationsRoot";
@ -47,6 +41,7 @@ import { TutorialRoot } from "../Tutorial/ui/TutorialRoot";
import { ActiveScriptsRoot } from "./ActiveScripts/ActiveScriptsRoot";
import { FactionsRoot } from "../Faction/ui/FactionsRoot";
import { FactionRoot } from "../Faction/ui/FactionRoot";
import { AugmentationsPage as FactionAugmentations } from "../Faction/ui/AugmentationsPage";
import { CharacterStats } from "./CharacterStats";
import { TravelAgencyRoot } from "../Locations/ui/TravelAgencyRoot";
import { StockMarketRoot } from "../StockMarket/ui/StockMarketRoot";
@ -72,7 +67,6 @@ import { ImportSave } from "./React/ImportSave";
import { BypassWrapper } from "./React/BypassWrapper";
import { Apr1 } from "./Apr1";
import { isFactionWork } from "../Work/FactionWork";
import { V2Modal } from "../utils/V2Modal";
import { MathJaxContext } from "better-react-mathjax";
import { useRerender } from "./React/hooks";
@ -100,6 +94,8 @@ const uninitialized = (): void => {
throw new Error("Router called before initialization");
};
const MAX_PAGES_IN_HISTORY = 10;
export let Router: IRouter = {
isInitialized: false,
page: () => {
@ -109,16 +105,12 @@ export let Router: IRouter = {
toPage: () => {
throw new Error("Router called before initialization");
},
toBitVerse: uninitialized,
toFaction: uninitialized,
toInfiltration: uninitialized,
toJob: uninitialized,
toScriptEditor: uninitialized,
toLocation: uninitialized,
toImportSave: uninitialized,
back: () => {
throw new Error("Router called before initialization");
},
};
function determineStartPage(): Page {
function determineStartPage() {
if (RecoveryMode) return Page.Recovery;
if (Player.currentWork !== null) return Page.Work;
return Page.Terminal;
@ -126,36 +118,21 @@ function determineStartPage(): Page {
export function GameRoot(): React.ReactElement {
const classes = useStyles();
const [{ files, vim }, setEditorOptions] = useState<{
files: Map<ScriptFilePath | TextFilePath, string>;
vim: boolean;
}>({
files: new Map(),
vim: false,
});
const [page, setPage] = useState(determineStartPage());
const [pages, setPages] = useState<PageWithContext[]>(() => [{ page: determineStartPage() }]);
const pageWithContext = pages[0];
const setNextPage = (pageWithContext: PageWithContext) =>
setPages((prev) => {
const next = [pageWithContext, ...prev];
next.length = Math.min(next.length, MAX_PAGES_IN_HISTORY);
return next;
});
const rerender = useRerender();
const [augPage, setAugPage] = useState<boolean>(false);
const [faction, setFaction] = useState<Faction>(
isFactionWork(Player.currentWork) ? Factions[Player.currentWork.factionName] : (undefined as unknown as Faction),
);
if (faction === undefined && page === Page.Faction)
throw new Error("Trying to go to a page without the proper setup");
const [flume, setFlume] = useState<boolean>(false);
const [quick, setQuick] = useState<boolean>(false);
const [location, setLocation] = useState<Location>(undefined as unknown as Location);
if (location === undefined && (page === Page.Infiltration || page === Page.Location || page === Page.Job))
throw new Error("Trying to go to a page without the proper setup");
const [cinematicText, setCinematicText] = useState("");
const [errorBoundaryKey, setErrorBoundaryKey] = useState<number>(0);
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");
const [allowRoutingCalls, setAllowRoutingCalls] = useState(true);
function resetErrorBoundary(): void {
@ -180,67 +157,28 @@ export function GameRoot(): React.ReactElement {
Router = {
isInitialized: true,
page: () => page,
page: () => pageWithContext.page,
allowRouting: (value: boolean) => setAllowRoutingCalls(value),
toPage: (page: SimplePage) => {
toPage: (page: Page, context?: PageContext<ComplexPage>) => {
if (!allowRoutingCalls) return attemptedForbiddenRouting("toPage");
switch (page) {
case Page.Travel:
Player.gotoLocation(LocationName.TravelAgency);
break;
case Page.BladeburnerCinematic:
setPage(page);
setCinematicText(cinematicText);
return;
case Page.BitVerse:
calculateAchievements();
break;
}
setPage(page);
setNextPage({ page, ...context } as PageWithContext);
},
toFaction: (faction: Faction, augPage = false) => {
if (!allowRoutingCalls) return attemptedForbiddenRouting("toFaction");
setAugPage(augPage);
setPage(Page.Faction);
if (faction) setFaction(faction);
},
toScriptEditor: (files = new Map(), options) => {
if (!allowRoutingCalls) return attemptedForbiddenRouting("toScriptEditor");
setEditorOptions({
files,
vim: !!options?.vim,
});
setPage(Page.ScriptEditor);
},
toJob: (location: Location) => {
if (!allowRoutingCalls) return attemptedForbiddenRouting("toJob");
setLocation(location);
setPage(Page.Job);
},
toBitVerse: (flume: boolean, quick: boolean) => {
if (!allowRoutingCalls) return attemptedForbiddenRouting("toBitVerse");
setFlume(flume);
setQuick(quick);
calculateAchievements();
setPage(Page.BitVerse);
},
toInfiltration: (location: Location) => {
if (!allowRoutingCalls) return attemptedForbiddenRouting("toInfiltration");
setLocation(location);
setPage(Page.Infiltration);
},
toLocation: (location: Location) => {
if (!allowRoutingCalls) return attemptedForbiddenRouting("toLocation");
setLocation(location);
setPage(Page.Location);
},
toImportSave: (base64save: string, automatic = false) => {
if (!allowRoutingCalls) return attemptedForbiddenRouting("toImportSave");
setImportString(base64save);
setImportAutomatic(automatic);
setPage(Page.ImportSave);
back: () => {
if (!allowRoutingCalls) return attemptedForbiddenRouting("back");
setPages((pages) => pages.slice(1));
},
};
useEffect(() => {
if (page !== Page.Terminal) window.scrollTo(0, 0);
if (pageWithContext.page !== Page.Terminal) window.scrollTo(0, 0);
});
function softReset(): void {
@ -254,7 +192,7 @@ export function GameRoot(): React.ReactElement {
let withSidebar = true;
let withPopups = true;
let bypassGame = false;
switch (page) {
switch (pageWithContext.page) {
case Page.Recovery: {
mainPage = <RecoveryRoot softReset={softReset} />;
withSidebar = false;
@ -263,13 +201,13 @@ export function GameRoot(): React.ReactElement {
break;
}
case Page.BitVerse: {
mainPage = <BitverseRoot flume={flume} quick={quick} />;
mainPage = <BitverseRoot flume={pageWithContext.flume} quick={pageWithContext.quick} />;
withSidebar = false;
withPopups = false;
break;
}
case Page.Infiltration: {
mainPage = <InfiltrationRoot location={location} />;
mainPage = <InfiltrationRoot location={pageWithContext.location} />;
withSidebar = false;
withPopups = false;
break;
@ -302,7 +240,13 @@ export function GameRoot(): React.ReactElement {
break;
}
case Page.ScriptEditor: {
mainPage = <ScriptEditorRoot files={files} hostname={Player.getCurrentServer().hostname} vim={vim} />;
mainPage = (
<ScriptEditorRoot
files={pageWithContext.files ?? new Map()}
hostname={Player.getCurrentServer().hostname}
vim={!!pageWithContext.options?.vim}
/>
);
break;
}
case Page.ActiveScripts: {
@ -322,7 +266,11 @@ export function GameRoot(): React.ReactElement {
break;
}
case Page.Faction: {
mainPage = <FactionRoot faction={faction} augPage={augPage} />;
mainPage = <FactionRoot faction={pageWithContext.faction} />;
break;
}
case Page.FactionAugmentations: {
mainPage = <FactionAugmentations faction={pageWithContext.faction} />;
break;
}
case Page.Milestones: {
@ -375,7 +323,7 @@ export function GameRoot(): React.ReactElement {
}
case Page.Job:
case Page.Location: {
mainPage = <GenericLocation loc={location} />;
mainPage = <GenericLocation loc={pageWithContext.location} />;
break;
}
case Page.Options: {
@ -417,7 +365,7 @@ export function GameRoot(): React.ReactElement {
break;
}
case Page.ImportSave: {
mainPage = <ImportSave importString={importString} automatic={importAutomatic} />;
mainPage = <ImportSave importString={pageWithContext.base64Save} automatic={!!pageWithContext.automatic} />;
withSidebar = false;
withPopups = false;
bypassGame = true;
@ -444,7 +392,7 @@ export function GameRoot(): React.ReactElement {
</Overview>
{withSidebar ? (
<Box display="flex" flexDirection="row" width="100%">
<SidebarRoot page={page} />
<SidebarRoot page={pageWithContext.page} />
<Box className={classes.root}>{mainPage}</Box>
</Box>
) : (

@ -3,7 +3,7 @@ import type { TextFilePath } from "../Paths/TextFilePath";
import type { Faction } from "../Faction/Faction";
import type { Location } from "../Locations/Location";
// These enums don't need enum helper support for now
// This enum doesn't need enum helper support for now
/**
* The full-screen page the player is currently be on.
* These are "simple" pages that don't require any extra parameters to
@ -38,14 +38,12 @@ export enum SimplePage {
ThemeBrowser = "Theme Browser",
}
/**
* "Complex" pages that need a custom transition function.
*/
export enum ComplexPage {
BitVerse = "BitVerse",
Faction = "Faction",
Infiltration = "Infiltration",
Job = "Job",
Faction = "Faction",
FactionAugmentations = "Faction Augmentations",
ScriptEditor = "Script Editor",
Location = "Location",
ImportSave = "Import Save",
@ -56,6 +54,35 @@ export enum ComplexPage {
export type Page = SimplePage | ComplexPage;
export const Page = { ...SimplePage, ...ComplexPage };
export type PageContext<T extends Page> = T extends ComplexPage.BitVerse
? { flume: boolean; quick: boolean }
: T extends ComplexPage.Infiltration
? { location: Location }
: T extends ComplexPage.Job
? { location: Location }
: T extends ComplexPage.Faction
? { faction: Faction }
: T extends ComplexPage.FactionAugmentations
? { faction: Faction }
: T extends ComplexPage.ScriptEditor
? { files?: Map<ScriptFilePath | TextFilePath, string>; options?: ScriptEditorRouteOptions }
: T extends ComplexPage.Location
? { location: Location }
: T extends ComplexPage.ImportSave
? { base64Save: string; automatic?: boolean }
: never;
export type PageWithContext =
| ({ page: ComplexPage.BitVerse } & PageContext<ComplexPage.BitVerse>)
| ({ page: ComplexPage.Infiltration } & PageContext<ComplexPage.Infiltration>)
| ({ page: ComplexPage.Job } & PageContext<ComplexPage.Job>)
| ({ page: ComplexPage.Faction } & PageContext<ComplexPage.Faction>)
| ({ page: ComplexPage.FactionAugmentations } & PageContext<ComplexPage.FactionAugmentations>)
| ({ page: ComplexPage.ScriptEditor } & PageContext<ComplexPage.ScriptEditor>)
| ({ page: ComplexPage.Location } & PageContext<ComplexPage.Location>)
| ({ page: ComplexPage.ImportSave } & PageContext<ComplexPage.ImportSave>)
| { page: SimplePage };
export interface ScriptEditorRouteOptions {
vim: boolean;
}
@ -66,11 +93,10 @@ export interface IRouter {
page(): Page;
allowRouting(value: boolean): void;
toPage(page: SimplePage): void;
toBitVerse(flume: boolean, quick: boolean): void;
toFaction(faction: Faction, augPage?: boolean): void; // faction name
toInfiltration(location: Location): void;
toJob(location: Location): void;
toScriptEditor(files?: Map<ScriptFilePath | TextFilePath, string>, options?: ScriptEditorRouteOptions): void;
toLocation(location: Location): void;
toImportSave(base64Save: string, automatic?: boolean): void;
toPage<T extends ComplexPage>(page: T, context: PageContext<T>): void;
/** go to a preveious page (if any) */
back(): void;
}
const simplePages = Object.values(SimplePage);
export const isSimplePage = (page: Page): page is SimplePage => simplePages.includes(page as SimplePage);

@ -206,7 +206,7 @@ export function WorkInProgressRoot(): React.ReactElement {
workInfo = {
buttons: {
cancel: () => {
Router.toLocation(Locations[LocationName.Slums]);
Router.toPage(Page.Location, { location: Locations[LocationName.Slums] });
Player.finishWork(true);
},
unfocus: () => {
@ -374,11 +374,11 @@ export function WorkInProgressRoot(): React.ReactElement {
workInfo = {
buttons: {
cancel: () => {
Router.toFaction(faction);
Router.toPage(Page.Faction, { faction });
Player.finishWork(true);
},
unfocus: () => {
Router.toFaction(faction);
Router.toPage(Page.Faction, { faction });
Player.stopFocusing();
},
},
@ -426,11 +426,11 @@ export function WorkInProgressRoot(): React.ReactElement {
buttons: {
cancel: () => {
Player.finishWork(true);
Router.toJob(Locations[comp.name]);
Router.toPage(Page.Job, { location: Locations[comp.name] });
},
unfocus: () => {
Player.stopFocusing();
Router.toJob(Locations[comp.name]);
Router.toPage(Page.Job, { location: Locations[comp.name] });
},
},
title: (