From 1af01401d9ff0010d2e23fb1bf87506d20893ff2 Mon Sep 17 00:00:00 2001 From: Aleksei Bezrodnov Date: Mon, 26 Jun 2023 10:24:37 +0200 Subject: [PATCH] UI: Correct behavior of "back" button on faction augs page Plus router refactoring --- src/BitNode/ui/BitFlumeModal.tsx | 3 +- src/Bladeburner/ui/BlackOpPage.tsx | 3 +- src/Bladeburner/ui/Stats.tsx | 3 +- src/DevMenu/ui/General.tsx | 9 +- src/Electron.tsx | 12 +- src/Faction/ui/AugmentationsPage.tsx | 38 ++--- src/Faction/ui/FactionRoot.tsx | 21 ++- src/Faction/ui/FactionsRoot.tsx | 14 +- src/GameOptions/ui/GameOptionsSidebar.tsx | 2 +- src/Locations/ui/City.tsx | 2 +- src/Locations/ui/CompanyLocation.tsx | 5 +- src/NetscriptFunctions/Singularity.ts | 6 +- .../Grafting/ui/GraftingRoot.tsx | 5 +- src/Sidebar/ui/SidebarRoot.tsx | 16 +- src/Terminal/Terminal.ts | 5 +- src/Terminal/commands/common/editor.ts | 12 +- src/Terminal/commands/ls.tsx | 12 +- src/ui/GameRoot.tsx | 146 ++++++------------ src/ui/Router.ts | 50 ++++-- src/ui/WorkInProgressRoot.tsx | 10 +- 20 files changed, 173 insertions(+), 201 deletions(-) diff --git a/src/BitNode/ui/BitFlumeModal.tsx b/src/BitNode/ui/BitFlumeModal.tsx index b55038b57..f81573c2d 100644 --- a/src/BitNode/ui/BitFlumeModal.tsx +++ b/src/BitNode/ui/BitFlumeModal.tsx @@ -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); } diff --git a/src/Bladeburner/ui/BlackOpPage.tsx b/src/Bladeburner/ui/BlackOpPage.tsx index 6c6477f9a..99afa2ee5 100644 --- a/src/Bladeburner/ui/BlackOpPage.tsx +++ b/src/Bladeburner/ui/BlackOpPage.tsx @@ -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. {props.bladeburner.blackops[BlackOperationName.OperationDaedalus] ? ( - ) : ( diff --git a/src/Bladeburner/ui/Stats.tsx b/src/Bladeburner/ui/Stats.tsx index 69af2076a..e4d3fbaaa 100644 --- a/src/Bladeburner/ui/Stats.tsx +++ b/src/Bladeburner/ui/Stats.tsx @@ -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 ( diff --git a/src/DevMenu/ui/General.tsx b/src/DevMenu/ui/General.tsx index b19b508a0..1386dd9c0 100644 --- a/src/DevMenu/ui/General.tsx +++ b/src/DevMenu/ui/General.tsx @@ -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 = () => { diff --git a/src/Electron.tsx b/src/Electron.tsx index 2d6140bfe..d45f0cd00 100644 --- a/src/Electron.tsx +++ b/src/Electron.tsx @@ -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; - pushSaveData: (base64save: string, automatic?: boolean) => void; + getSaveInfo: (base64Save: string) => Promise; + 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 => { + getSaveInfo: async (base64Save: string): Promise => { 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. diff --git a/src/Faction/ui/AugmentationsPage.tsx b/src/Faction/ui/AugmentationsPage.tsx index 430e6cb6b..6a782481c 100644 --- a/src/Faction/ui/AugmentationsPage.tsx +++ b/src/Faction/ui/AugmentationsPage.tsx @@ -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 ? ( @@ -171,27 +167,27 @@ export function AugmentationsPage(props: IProps): React.ReactElement { return ( <> - - Faction Augmentations - {props.faction.name} + + Faction Augmentations - {faction.name} - These are all of the Augmentations that are available to purchase from {props.faction.name}. - Augmentations are powerful upgrades that will enhance your abilities. + These are all of the Augmentations that are available to purchase from {faction.name}. Augmentations + are powerful upgrades that will enhance your abilities.
<>{multiplierComponent} - Reputation: + Reputation:
- Favor: + Favor:
@@ -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} /> ); diff --git a/src/Faction/ui/FactionRoot.tsx b/src/Faction/ui/FactionRoot.tsx index 88e1a3c03..dfbb5abc1 100644 --- a/src/Faction/ui/FactionRoot.tsx +++ b/src/Faction/ui/FactionRoot.tsx @@ -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 ? ( - setPurchasingAugs(false)} /> - ) : ( - setPurchasingAugs(true)} /> + return ( + Router.toPage(Page.FactionAugmentations, { faction })} + /> ); } diff --git a/src/Faction/ui/FactionsRoot.tsx b/src/Faction/ui/FactionsRoot.tsx index d4eee5dff..24765b21f 100644 --- a/src/Faction/ui/FactionsRoot.tsx +++ b/src/Faction/ui/FactionsRoot.tsx @@ -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, faction: FactionName): void { diff --git a/src/GameOptions/ui/GameOptionsSidebar.tsx b/src/GameOptions/ui/GameOptionsSidebar.tsx index 0f7d43596..255c161b0 100644 --- a/src/GameOptions/ui/GameOptionsSidebar.tsx +++ b/src/GameOptions/ui/GameOptionsSidebar.tsx @@ -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); } diff --git a/src/Locations/ui/City.tsx b/src/Locations/ui/City.tsx index 4aad8a1c8..db71df300 100644 --- a/src/Locations/ui/City.tsx +++ b/src/Locations/ui/City.tsx @@ -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 }); } } diff --git a/src/Locations/ui/CompanyLocation.tsx b/src/Locations/ui/CompanyLocation.tsx index b24f06644..2a8f33027 100644 --- a/src/Locations/ui/CompanyLocation.tsx +++ b/src/Locations/ui/CompanyLocation.tsx @@ -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): void { diff --git a/src/NetscriptFunctions/Singularity.ts b/src/NetscriptFunctions/Singularity.ts index 232112214..f15480888 100644 --- a/src/NetscriptFunctions/Singularity.ts +++ b/src/NetscriptFunctions/Singularity.ts @@ -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 { } 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 { 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; diff --git a/src/PersonObjects/Grafting/ui/GraftingRoot.tsx b/src/PersonObjects/Grafting/ui/GraftingRoot.tsx index 5878ca5d7..ed26f70d1 100644 --- a/src/PersonObjects/Grafting/ui/GraftingRoot.tsx +++ b/src/PersonObjects/Grafting/ui/GraftingRoot.tsx @@ -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 ( - + Grafting Laboratory You find yourself in a secret laboratory, owned by a mysterious researcher. diff --git a/src/Sidebar/ui/SidebarRoot.tsx b/src/Sidebar/ui/SidebarRoot.tsx index 8ee241175..be1acc5f9 100644 --- a/src/Sidebar/ui/SidebarRoot.tsx +++ b/src/Sidebar/ui/SidebarRoot.tsx @@ -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); } diff --git a/src/Terminal/Terminal.ts b/src/Terminal/Terminal.ts index 5d986bee5..6ea929d4a 100644 --- a/src/Terminal/Terminal.ts +++ b/src/Terminal/Terminal.ts @@ -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 diff --git a/src/Terminal/commands/common/editor.ts b/src/Terminal/commands/common/editor.ts index 488b663fb..a7f2004ea 100644 --- a/src/Terminal/commands/common/editor.ts +++ b/src/Terminal/commands/common/editor.ts @@ -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(); + const files = new Map(); 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 }); } diff --git a/src/Terminal/commands/ls.tsx b/src/Terminal/commands/ls.tsx index fe3a999f2..202882605 100644 --- a/src/Terminal/commands/ls.tsx +++ b/src/Terminal/commands/ls.tsx @@ -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(); - map.set(fullPath, code); - Router.toScriptEditor(map); + const files = new Map(); + files.set(fullPath, code); + Router.toPage(Page.ScriptEditor, { files }); } return ( diff --git a/src/ui/GameRoot.tsx b/src/ui/GameRoot.tsx index 8878c3fb4..5804cccb2 100644 --- a/src/ui/GameRoot.tsx +++ b/src/ui/GameRoot.tsx @@ -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; - vim: boolean; - }>({ - files: new Map(), - vim: false, - }); - const [page, setPage] = useState(determineStartPage()); + + const [pages, setPages] = useState(() => [{ 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(false); - const [faction, setFaction] = useState( - 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(false); - const [quick, setQuick] = useState(false); - const [location, setLocation] = useState(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(0); - const [importString, setImportString] = useState(undefined as unknown as string); - const [importAutomatic, setImportAutomatic] = useState(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) => { 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 = ; withSidebar = false; @@ -263,13 +201,13 @@ export function GameRoot(): React.ReactElement { break; } case Page.BitVerse: { - mainPage = ; + mainPage = ; withSidebar = false; withPopups = false; break; } case Page.Infiltration: { - mainPage = ; + mainPage = ; withSidebar = false; withPopups = false; break; @@ -302,7 +240,13 @@ export function GameRoot(): React.ReactElement { break; } case Page.ScriptEditor: { - mainPage = ; + mainPage = ( + + ); break; } case Page.ActiveScripts: { @@ -322,7 +266,11 @@ export function GameRoot(): React.ReactElement { break; } case Page.Faction: { - mainPage = ; + mainPage = ; + break; + } + case Page.FactionAugmentations: { + mainPage = ; break; } case Page.Milestones: { @@ -375,7 +323,7 @@ export function GameRoot(): React.ReactElement { } case Page.Job: case Page.Location: { - mainPage = ; + mainPage = ; break; } case Page.Options: { @@ -417,7 +365,7 @@ export function GameRoot(): React.ReactElement { break; } case Page.ImportSave: { - mainPage = ; + mainPage = ; withSidebar = false; withPopups = false; bypassGame = true; @@ -444,7 +392,7 @@ export function GameRoot(): React.ReactElement { {withSidebar ? ( - + {mainPage} ) : ( diff --git a/src/ui/Router.ts b/src/ui/Router.ts index d4bba79c4..4c6b3bcd4 100644 --- a/src/ui/Router.ts +++ b/src/ui/Router.ts @@ -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 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; options?: ScriptEditorRouteOptions } + : T extends ComplexPage.Location + ? { location: Location } + : T extends ComplexPage.ImportSave + ? { base64Save: string; automatic?: boolean } + : never; + +export type PageWithContext = + | ({ page: ComplexPage.BitVerse } & PageContext) + | ({ page: ComplexPage.Infiltration } & PageContext) + | ({ page: ComplexPage.Job } & PageContext) + | ({ page: ComplexPage.Faction } & PageContext) + | ({ page: ComplexPage.FactionAugmentations } & PageContext) + | ({ page: ComplexPage.ScriptEditor } & PageContext) + | ({ page: ComplexPage.Location } & PageContext) + | ({ page: ComplexPage.ImportSave } & PageContext) + | { 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, options?: ScriptEditorRouteOptions): void; - toLocation(location: Location): void; - toImportSave(base64Save: string, automatic?: boolean): void; + toPage(page: T, context: PageContext): 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); diff --git a/src/ui/WorkInProgressRoot.tsx b/src/ui/WorkInProgressRoot.tsx index 8902438d0..9e3f0c09f 100644 --- a/src/ui/WorkInProgressRoot.tsx +++ b/src/ui/WorkInProgressRoot.tsx @@ -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: (