mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-11-22 15:43:49 +01:00
MISC: enforce eslint react checks (#640)
This commit is contained in:
parent
91bfb154b6
commit
1d5a735941
@ -7,6 +7,8 @@ module.exports = {
|
||||
extends: [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:react/recommended",
|
||||
"plugin:react-hooks/recommended",
|
||||
//"plugin:@typescript-eslint/recommended-requiring-type-checking",
|
||||
//"plugin:@typescript-eslint/strict",
|
||||
],
|
||||
@ -31,5 +33,6 @@ module.exports = {
|
||||
],
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"react/no-unescaped-entities": "off",
|
||||
},
|
||||
};
|
||||
|
1707
package-lock.json
generated
1707
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -71,7 +71,9 @@
|
||||
"css-loader": "^6.7.3",
|
||||
"electron": "^22.2.1",
|
||||
"electron-packager": "^17.1.1",
|
||||
"eslint": "^8.31.0",
|
||||
"eslint": "^8.43.0",
|
||||
"eslint-plugin-react": "^7.32.2",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"file-loader": "^6.2.0",
|
||||
"fork-ts-checker-webpack-plugin": "^7.2.14",
|
||||
"html-webpack-plugin": "^5.5.0",
|
||||
|
@ -83,13 +83,16 @@ export function SourceFilesElement(): React.ReactElement {
|
||||
sfList.sort(([n1, __lvl1], [n2, __lvl2]) => n1 - n2);
|
||||
}
|
||||
|
||||
if (sfList.length === 0) {
|
||||
const [selectedSf, setSelectedSf] = useState(() => {
|
||||
if (sfList.length === 0) return null;
|
||||
const [n, lvl] = sfList[0];
|
||||
return { n, lvl };
|
||||
});
|
||||
|
||||
if (!selectedSf) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
const firstEle = sfList[0];
|
||||
const [selectedSf, setSelectedSf] = useState({ n: firstEle[0], lvl: firstEle[1] });
|
||||
|
||||
return (
|
||||
<Box sx={{ width: "100%", mt: 1 }}>
|
||||
<Paper sx={{ p: 1 }}>
|
||||
|
@ -1,10 +1,11 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import React, { useState } from "react";
|
||||
import { Box, Button, Paper, Tooltip, Typography } from "@mui/material";
|
||||
import { Player } from "@player";
|
||||
import { FactionName } from "@enums";
|
||||
import { convertTimeMsToTimeElapsedString } from "../../utils/StringHelperFunctions";
|
||||
import { BladeburnerConstants } from "../data/Constants";
|
||||
import { Money } from "../../ui/React/Money";
|
||||
import { useRerender } from "../../ui/React/hooks";
|
||||
import { formatNumberNoSuffix, formatPopulation, formatBigNumber } from "../../ui/formatNumber";
|
||||
import { Factions } from "../../Faction/Factions";
|
||||
import { Router } from "../../ui/GameRoot";
|
||||
@ -20,13 +21,9 @@ interface IProps {
|
||||
|
||||
export function Stats(props: IProps): React.ReactElement {
|
||||
const [travelOpen, setTravelOpen] = useState(false);
|
||||
const setRerender = useState(false)[1];
|
||||
useRerender(1000);
|
||||
|
||||
const inFaction = props.bladeburner.rank >= BladeburnerConstants.RankNeededForFaction;
|
||||
useEffect(() => {
|
||||
const id = setInterval(() => setRerender((old) => !old), 1000);
|
||||
return () => clearInterval(id);
|
||||
}, []);
|
||||
|
||||
function openFaction(): void {
|
||||
if (!inFaction) return;
|
||||
|
@ -151,7 +151,7 @@ export class CodingContract {
|
||||
/** Creates a popup to prompt the player to solve the problem */
|
||||
async prompt(): Promise<CodingContractResult> {
|
||||
return new Promise<CodingContractResult>((resolve) => {
|
||||
const props = {
|
||||
CodingContractEvent.emit({
|
||||
c: this,
|
||||
onClose: () => {
|
||||
resolve(CodingContractResult.Cancelled);
|
||||
@ -163,8 +163,7 @@ export class CodingContract {
|
||||
resolve(CodingContractResult.Failure);
|
||||
}
|
||||
},
|
||||
};
|
||||
CodingContractEvent.emit(props);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -14,10 +14,12 @@ import { useRerender } from "../../ui/React/hooks";
|
||||
|
||||
export function CorporationRoot(): React.ReactElement {
|
||||
const rerender = useRerender(200);
|
||||
const [divisionName, setDivisionName] = useState<string | number>("Overview");
|
||||
|
||||
const corporation = Player.corporation;
|
||||
if (corporation === null) return <></>;
|
||||
const [divisionName, setDivisionName] = useState<string | number>("Overview");
|
||||
function handleChange(event: React.SyntheticEvent, tab: string | number): void {
|
||||
|
||||
function handleChange(_event: React.SyntheticEvent, tab: string | number): void {
|
||||
setDivisionName(tab);
|
||||
}
|
||||
|
||||
|
@ -151,9 +151,9 @@ export function DivisionOverview(props: DivisionOverviewProps): React.ReactEleme
|
||||
<br />
|
||||
<StatsTable
|
||||
rows={[
|
||||
["Revenue:", <MoneyRate money={division.lastCycleRevenue} />],
|
||||
["Expenses:", <MoneyRate money={division.lastCycleExpenses} />],
|
||||
["Profit:", <MoneyRate money={profit} />],
|
||||
["Revenue:", <MoneyRate key="revenue" money={division.lastCycleRevenue} />],
|
||||
["Expenses:", <MoneyRate key="expenses" money={division.lastCycleExpenses} />],
|
||||
["Profit:", <MoneyRate key="profit" money={profit} />],
|
||||
]}
|
||||
/>
|
||||
<br />
|
||||
|
@ -40,6 +40,7 @@ const useStyles = makeStyles(() =>
|
||||
);
|
||||
|
||||
function WarehouseRoot(props: WarehouseProps): React.ReactElement {
|
||||
const classes = useStyles();
|
||||
const corp = useCorporation();
|
||||
const division = useDivision();
|
||||
const [smartSupplyOpen, setSmartSupplyOpen] = useState(false);
|
||||
@ -57,8 +58,6 @@ function WarehouseRoot(props: WarehouseProps): React.ReactElement {
|
||||
props.rerender();
|
||||
}
|
||||
|
||||
const classes = useStyles();
|
||||
|
||||
// Current State:
|
||||
let stateText;
|
||||
switch (division.state) {
|
||||
|
@ -61,13 +61,13 @@ export function Overview({ rerender }: IProps): React.ReactElement {
|
||||
<>
|
||||
<StatsTable
|
||||
rows={[
|
||||
["Total Funds:", <Money money={corp.funds} />],
|
||||
["Total Revenue:", <MoneyRate money={corp.revenue} />],
|
||||
["Total Expenses:", <MoneyRate money={corp.expenses} />],
|
||||
["Total Profit:", <MoneyRate money={corp.revenue - corp.expenses} />],
|
||||
["Total Funds:", <Money key="funds" money={corp.funds} />],
|
||||
["Total Revenue:", <MoneyRate key="revenue" money={corp.revenue} />],
|
||||
["Total Expenses:", <MoneyRate key="expenses" money={corp.expenses} />],
|
||||
["Total Profit:", <MoneyRate key="profit" money={corp.revenue - corp.expenses} />],
|
||||
["Publicly Traded:", corp.public ? "Yes" : "No"],
|
||||
["Owned Stock Shares:", formatShares(corp.numShares)],
|
||||
["Stock Price:", corp.public ? <Money money={corp.sharePrice} /> : "N/A"],
|
||||
["Stock Price:", corp.public ? <Money key="price" money={corp.sharePrice} /> : "N/A"],
|
||||
]}
|
||||
/>
|
||||
<br />
|
||||
@ -160,16 +160,16 @@ interface IUpgradeProps {
|
||||
}
|
||||
// Render the UI for Corporation upgrades
|
||||
function Upgrades({ rerender }: IUpgradeProps): React.ReactElement {
|
||||
const [purchaseMultiplier, setPurchaseMultiplier] = useState<PositiveInteger | "MAX">(
|
||||
corpConstants.PurchaseMultipliers.x1,
|
||||
);
|
||||
|
||||
const corp = useCorporation();
|
||||
// Don't show upgrades
|
||||
if (corp.divisions.size === 0) {
|
||||
return <Typography variant="h4">Upgrades are unlocked once you create an industry.</Typography>;
|
||||
}
|
||||
|
||||
const [purchaseMultiplier, setPurchaseMultiplier] = useState<PositiveInteger | "MAX">(
|
||||
corpConstants.PurchaseMultipliers.x1,
|
||||
);
|
||||
|
||||
const unlocksNotOwned = Object.values(CorpUnlocks)
|
||||
.filter((unlock) => !corp.unlocks.has(unlock.name))
|
||||
.map(({ name }) => <Unlock rerender={rerender} name={name} key={name} />);
|
||||
@ -332,10 +332,10 @@ function DividendsStats({ profit }: IDividendsStatsProps): React.ReactElement {
|
||||
return (
|
||||
<StatsTable
|
||||
rows={[
|
||||
["Retained Profits (after dividends):", <MoneyRate money={retainedEarnings} />],
|
||||
["Retained Profits (after dividends):", <MoneyRate key="profits" money={retainedEarnings} />],
|
||||
["Dividend Percentage:", formatPercent(corp.dividendRate, 0)],
|
||||
["Dividends per share:", <MoneyRate money={dividendsPerShare} />],
|
||||
["Your earnings as a shareholder:", <MoneyRate money={playerEarnings} />],
|
||||
["Dividends per share:", <MoneyRate key="dividends" money={dividendsPerShare} />],
|
||||
["Your earnings as a shareholder:", <MoneyRate key="earnings" money={playerEarnings} />],
|
||||
]}
|
||||
/>
|
||||
);
|
||||
|
@ -13,9 +13,10 @@ interface IMarketTA2Props {
|
||||
}
|
||||
|
||||
function MarketTA2(props: IMarketTA2Props): React.ReactElement {
|
||||
const rerender = useRerender();
|
||||
|
||||
const division = useDivision();
|
||||
if (!division.hasResearch("Market-TA.II")) return <></>;
|
||||
const rerender = useRerender();
|
||||
|
||||
function onMarketTA2(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||
props.mat.marketTa2 = event.target.checked;
|
||||
|
@ -13,9 +13,10 @@ interface ITa2Props {
|
||||
}
|
||||
|
||||
function MarketTA2(props: ITa2Props): React.ReactElement {
|
||||
const rerender = useRerender();
|
||||
|
||||
const division = useDivision();
|
||||
if (!division.hasResearch("Market-TA.II")) return <></>;
|
||||
const rerender = useRerender();
|
||||
|
||||
function onCheckedChange(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||
props.product.marketTa2 = event.target.checked;
|
||||
|
@ -19,7 +19,7 @@ interface IProps {
|
||||
|
||||
export function StaneksGiftRoot({ staneksGift }: IProps): React.ReactElement {
|
||||
const rerender = useRerender();
|
||||
useEffect(() => StaneksGiftEvents.subscribe(rerender), []);
|
||||
useEffect(() => StaneksGiftEvents.subscribe(rerender), [rerender]);
|
||||
return (
|
||||
<Container maxWidth="lg" disableGutters sx={{ mx: 0 }}>
|
||||
<Typography variant="h4">
|
||||
|
@ -1,28 +1,31 @@
|
||||
import React, { useEffect } from "react";
|
||||
|
||||
import Typography from "@mui/material/Typography";
|
||||
|
||||
import { Player } from "@player";
|
||||
import { AugmentationName } from "@enums";
|
||||
|
||||
import React, { useEffect } from "react";
|
||||
|
||||
import { General } from "./DevMenu/ui/General";
|
||||
import { Stats } from "./DevMenu/ui/Stats";
|
||||
import { FactionsDev } from "./DevMenu/ui/FactionsDev";
|
||||
import { Augmentations } from "./DevMenu/ui/Augmentations";
|
||||
import { SourceFiles } from "./DevMenu/ui/SourceFiles";
|
||||
import { Programs } from "./DevMenu/ui/Programs";
|
||||
import { Servers } from "./DevMenu/ui/Servers";
|
||||
import { Companies } from "./DevMenu/ui/Companies";
|
||||
import { Bladeburner as BladeburnerElem } from "./DevMenu/ui/Bladeburner";
|
||||
import { Gang } from "./DevMenu/ui/Gang";
|
||||
import { Corporation } from "./DevMenu/ui/Corporation";
|
||||
import { CodingContracts } from "./DevMenu/ui/CodingContracts";
|
||||
import { StockMarket } from "./DevMenu/ui/StockMarket";
|
||||
import { Sleeves } from "./DevMenu/ui/Sleeves";
|
||||
import { Stanek } from "./DevMenu/ui/Stanek";
|
||||
import { TimeSkip } from "./DevMenu/ui/TimeSkip";
|
||||
import { SaveFile } from "./DevMenu/ui/SaveFile";
|
||||
import { Achievements } from "./DevMenu/ui/Achievements";
|
||||
import { Entropy } from "./DevMenu/ui/Entropy";
|
||||
import Typography from "@mui/material/Typography";
|
||||
|
||||
import { StatsDev } from "./DevMenu/ui/StatsDev";
|
||||
import { FactionsDev } from "./DevMenu/ui/FactionsDev";
|
||||
import { AugmentationsDev } from "./DevMenu/ui/AugmentationsDev";
|
||||
import { SourceFilesDev } from "./DevMenu/ui/SourceFilesDev";
|
||||
import { ProgramsDev } from "./DevMenu/ui/ProgramsDev";
|
||||
import { ServersDev } from "./DevMenu/ui/ServersDev";
|
||||
import { CompaniesDev } from "./DevMenu/ui/CompaniesDev";
|
||||
import { BladeburnerDev } from "./DevMenu/ui/BladeburnerDev";
|
||||
import { GangDev } from "./DevMenu/ui/GangDev";
|
||||
import { CorporationDev } from "./DevMenu/ui/CorporationDev";
|
||||
import { CodingContractsDev } from "./DevMenu/ui/CodingContractsDev";
|
||||
import { StockMarketDev } from "./DevMenu/ui/StockMarketDev";
|
||||
import { SleevesDev } from "./DevMenu/ui/SleevesDev";
|
||||
import { StanekDev } from "./DevMenu/ui/StanekDev";
|
||||
import { SaveFileDev } from "./DevMenu/ui/SaveFileDev";
|
||||
import { AchievementsDev } from "./DevMenu/ui/AchievementsDev";
|
||||
import { EntropyDev } from "./DevMenu/ui/EntropyDev";
|
||||
|
||||
import { Exploit } from "./Exploits/Exploit";
|
||||
|
||||
export function DevMenuRoot(): React.ReactElement {
|
||||
@ -33,31 +36,31 @@ export function DevMenuRoot(): React.ReactElement {
|
||||
<>
|
||||
<Typography>Development Menu - Only meant to be used for testing/debugging</Typography>
|
||||
<General />
|
||||
<Stats />
|
||||
<StatsDev />
|
||||
<FactionsDev />
|
||||
<Augmentations />
|
||||
<SourceFiles />
|
||||
<Programs />
|
||||
<Servers />
|
||||
<Companies />
|
||||
<AugmentationsDev />
|
||||
<SourceFilesDev />
|
||||
<ProgramsDev />
|
||||
<ServersDev />
|
||||
<CompaniesDev />
|
||||
|
||||
{Player.bladeburner && <BladeburnerElem />}
|
||||
{Player.bladeburner && <BladeburnerDev bladeburner={Player.bladeburner} />}
|
||||
|
||||
{Player.gang && <Gang />}
|
||||
{Player.gang && <GangDev />}
|
||||
|
||||
{Player.corporation && <Corporation />}
|
||||
{Player.corporation && <CorporationDev />}
|
||||
|
||||
<CodingContracts />
|
||||
<CodingContractsDev />
|
||||
|
||||
{Player.hasWseAccount && <StockMarket />}
|
||||
{Player.hasWseAccount && <StockMarketDev />}
|
||||
|
||||
{Player.sleeves.length > 0 && <Sleeves />}
|
||||
{Player.augmentations.some((aug) => aug.name === AugmentationName.StaneksGift1) && <Stanek />}
|
||||
{Player.sleeves.length > 0 && <SleevesDev />}
|
||||
{Player.augmentations.some((aug) => aug.name === AugmentationName.StaneksGift1) && <StanekDev />}
|
||||
|
||||
<TimeSkip />
|
||||
<Achievements />
|
||||
<Entropy />
|
||||
<SaveFile />
|
||||
<AchievementsDev />
|
||||
<EntropyDev />
|
||||
<SaveFileDev />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ import { Player } from "@player";
|
||||
import { achievements } from "../../Achievements/Achievements";
|
||||
import { Engine } from "../../engine";
|
||||
|
||||
export function Achievements(): React.ReactElement {
|
||||
export function AchievementsDev(): React.ReactElement {
|
||||
const [playerAchievement, setPlayerAchievements] = useState(Player.achievements.map((m) => m.ID));
|
||||
|
||||
function grantAchievement(id: string): void {
|
@ -14,7 +14,7 @@ import {
|
||||
} from "@mui/material";
|
||||
import { AugmentationName } from "@enums";
|
||||
|
||||
export function Augmentations(): React.ReactElement {
|
||||
export function AugmentationsDev(): React.ReactElement {
|
||||
const [augmentation, setAugmentation] = useState(AugmentationName.Targeting1);
|
||||
|
||||
function setAugmentationDropdown(event: SelectChangeEvent): void {
|
@ -18,13 +18,11 @@ import { Player } from "@player";
|
||||
import { CityName } from "@enums";
|
||||
import { Skills as AllSkills } from "../../Bladeburner/Skills";
|
||||
import { SkillNames } from "../../Bladeburner/data/SkillNames";
|
||||
import { Bladeburner } from "../../Bladeburner/Bladeburner";
|
||||
|
||||
const bigNumber = 1e27;
|
||||
|
||||
export function Bladeburner(): React.ReactElement {
|
||||
if (!Player.bladeburner) return <></>;
|
||||
const bladeburner = Player.bladeburner;
|
||||
|
||||
export function BladeburnerDev({ bladeburner }: { bladeburner: Bladeburner }): React.ReactElement {
|
||||
// Rank functions
|
||||
const modifyBladeburnerRank = (modify: number) => (rank: number) => bladeburner.changeRank(Player, rank * modify);
|
||||
const resetBladeburnerRank = () => {
|
@ -12,7 +12,7 @@ import MenuItem from "@mui/material/MenuItem";
|
||||
import { generateContract, generateRandomContract, generateRandomContractOnHome } from "../../CodingContractGenerator";
|
||||
import { CodingContractTypes } from "../../CodingContracts";
|
||||
|
||||
export function CodingContracts(): React.ReactElement {
|
||||
export function CodingContractsDev(): React.ReactElement {
|
||||
const [codingcontract, setCodingcontract] = useState("Find Largest Prime Factor");
|
||||
function setCodingcontractDropdown(event: SelectChangeEvent): void {
|
||||
setCodingcontract(event.target.value);
|
@ -15,7 +15,7 @@ import { Adjuster } from "./Adjuster";
|
||||
|
||||
const bigNumber = 1e12;
|
||||
|
||||
export function Companies(): React.ReactElement {
|
||||
export function CompaniesDev(): React.ReactElement {
|
||||
const [company, setCompany] = useState(FactionName.ECorp as string);
|
||||
function setCompanyDropdown(event: SelectChangeEvent): void {
|
||||
setCompany(event.target.value);
|
@ -12,7 +12,7 @@ import { Player } from "@player";
|
||||
|
||||
const bigNumber = 1e27;
|
||||
|
||||
export function Corporation(): React.ReactElement {
|
||||
export function CorporationDev(): React.ReactElement {
|
||||
function addTonsCorporationFunds(): void {
|
||||
if (Player.corporation) {
|
||||
Player.corporation.funds = Player.corporation.funds + bigNumber;
|
@ -9,9 +9,9 @@ import Typography from "@mui/material/Typography";
|
||||
import { Player } from "@player";
|
||||
import { Adjuster } from "./Adjuster";
|
||||
|
||||
// Update as additional BitNodes get implemented
|
||||
// TODO: Update as additional BitNodes get implemented
|
||||
|
||||
export function Entropy(): React.ReactElement {
|
||||
export function EntropyDev(): React.ReactElement {
|
||||
return (
|
||||
<Accordion TransitionProps={{ unmountOnExit: true }}>
|
||||
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
@ -11,7 +11,7 @@ import { Player } from "@player";
|
||||
|
||||
const bigNumber = 1e27;
|
||||
|
||||
export function Gang(): React.ReactElement {
|
||||
export function GangDev(): React.ReactElement {
|
||||
function addTonsGangCycles(): void {
|
||||
if (Player.gang) {
|
||||
Player.gang.storedCycles = bigNumber;
|
@ -12,7 +12,7 @@ import { Player } from "@player";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
import { CompletedProgramName } from "@enums";
|
||||
|
||||
export function Programs(): React.ReactElement {
|
||||
export function ProgramsDev(): React.ReactElement {
|
||||
const [program, setProgram] = useState(CompletedProgramName.bruteSsh);
|
||||
function setProgramDropdown(event: SelectChangeEvent): void {
|
||||
setProgram(event.target.value as CompletedProgramName);
|
@ -13,7 +13,7 @@ import { Upload } from "@mui/icons-material";
|
||||
import { Button } from "@mui/material";
|
||||
import { OptionSwitch } from "../../ui/React/OptionSwitch";
|
||||
|
||||
export function SaveFile(): React.ReactElement {
|
||||
export function SaveFileDev(): React.ReactElement {
|
||||
const importInput = useRef<HTMLInputElement>(null);
|
||||
const [saveFile, setSaveFile] = useState("");
|
||||
const [restoreScripts, setRestoreScripts] = useState(true);
|
@ -12,7 +12,7 @@ import { GetServer, GetAllServers } from "../../Server/AllServers";
|
||||
import { Server } from "../../Server/Server";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
|
||||
export function Servers(): React.ReactElement {
|
||||
export function ServersDev(): React.ReactElement {
|
||||
const [server, setServer] = useState("home");
|
||||
function setServerDropdown(event: SelectChangeEvent): void {
|
||||
setServer(event.target.value);
|
@ -10,7 +10,7 @@ import Typography from "@mui/material/Typography";
|
||||
import { Player } from "@player";
|
||||
import { Adjuster } from "./Adjuster";
|
||||
|
||||
export function Sleeves(): React.ReactElement {
|
||||
export function SleevesDev(): React.ReactElement {
|
||||
function sleeveMaxAllShock(): void {
|
||||
for (let i = 0; i < Player.sleeves.length; ++i) {
|
||||
Player.sleeves[i].shock = 100;
|
@ -13,7 +13,7 @@ import ButtonGroup from "@mui/material/ButtonGroup";
|
||||
// Update as additional BitNodes get implemented
|
||||
const validSFN = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
|
||||
|
||||
export function SourceFiles(): React.ReactElement {
|
||||
export function SourceFilesDev(): React.ReactElement {
|
||||
function setSF(sfN: number, sfLvl: number) {
|
||||
return function () {
|
||||
if (sfN === 9) {
|
@ -10,7 +10,7 @@ import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import { Adjuster } from "./Adjuster";
|
||||
|
||||
export function Stanek(): React.ReactElement {
|
||||
export function StanekDev(): React.ReactElement {
|
||||
function addCycles(): void {
|
||||
staneksGift.storedCycles = 1e6;
|
||||
}
|
@ -12,7 +12,7 @@ import { Player } from "@player";
|
||||
|
||||
const bigNumber = 1e27;
|
||||
|
||||
export function Stats(): React.ReactElement {
|
||||
export function StatsDev(): React.ReactElement {
|
||||
function modifyExp(stat: string, modifier: number) {
|
||||
return function (exp: number) {
|
||||
switch (stat) {
|
@ -13,7 +13,7 @@ import { dialogBoxCreate } from "../../ui/React/DialogBox";
|
||||
import { StockMarket as SM } from "../../StockMarket/StockMarket";
|
||||
import { Stock } from "../../StockMarket/Stock";
|
||||
|
||||
export function StockMarket(): React.ReactElement {
|
||||
export function StockMarketDev(): React.ReactElement {
|
||||
const [stockPrice, setStockPrice] = useState(0);
|
||||
const [stockSymbol, setStockSymbol] = useState("");
|
||||
|
@ -14,7 +14,7 @@ import { Faction } from "../Faction";
|
||||
import { getFactionAugmentationsFiltered, joinFaction } from "../FactionHelpers";
|
||||
import { Factions } from "../Factions";
|
||||
|
||||
export const InvitationsSeen: string[] = [];
|
||||
export const InvitationsSeen = new Set<FactionName>();
|
||||
|
||||
const fontSize = "small";
|
||||
const marginRight = 0.5;
|
||||
@ -173,8 +173,7 @@ export function FactionsRoot(): React.ReactElement {
|
||||
const rerender = useRerender(200);
|
||||
useEffect(() => {
|
||||
Player.factionInvitations.forEach((faction) => {
|
||||
if (InvitationsSeen.includes(faction)) return;
|
||||
InvitationsSeen.push(faction);
|
||||
InvitationsSeen.add(faction);
|
||||
});
|
||||
}, []);
|
||||
|
||||
|
@ -1,31 +1,28 @@
|
||||
/**
|
||||
* React Component for the content of the popup before the player confirms the
|
||||
* ascension of a gang member.
|
||||
*/
|
||||
import React, { useState, useEffect } from "react";
|
||||
import React from "react";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Button from "@mui/material/Button";
|
||||
|
||||
import { GangMember } from "../GangMember";
|
||||
import { formatPreciseMultiplier, formatRespect } from "../../ui/formatNumber";
|
||||
import { dialogBoxCreate } from "../../ui/React/DialogBox";
|
||||
import { Modal } from "../../ui/React/Modal";
|
||||
import { useGang } from "./Context";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Button from "@mui/material/Button";
|
||||
import { useRerender } from "../../ui/React/hooks";
|
||||
|
||||
interface IProps {
|
||||
type AscensionModalProps = {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
member: GangMember;
|
||||
onAscend: () => void;
|
||||
}
|
||||
};
|
||||
|
||||
export function AscensionModal(props: IProps): React.ReactElement {
|
||||
/**
|
||||
* React Component for the content of the popup before the player confirms the
|
||||
* ascension of a gang member.
|
||||
*/
|
||||
export function AscensionModal(props: AscensionModalProps): React.ReactElement {
|
||||
const gang = useGang();
|
||||
const setRerender = useState(false)[1];
|
||||
|
||||
useEffect(() => {
|
||||
const id = setInterval(() => setRerender((old) => !old), 1000);
|
||||
return () => clearInterval(id);
|
||||
}, []);
|
||||
useRerender(1000);
|
||||
|
||||
function confirm(): void {
|
||||
props.onAscend();
|
||||
|
@ -14,7 +14,7 @@ import { GangMember } from "../GangMember";
|
||||
import { Settings } from "../../Settings/Settings";
|
||||
import { MoneyRate } from "../../ui/React/MoneyRate";
|
||||
import { StatsRow } from "../../ui/React/StatsRow";
|
||||
import { characterOverviewStyles as useStyles } from "../../ui/React/CharacterOverview";
|
||||
import { useStyles } from "../../ui/React/CharacterOverview";
|
||||
|
||||
interface IProps {
|
||||
member: GangMember;
|
||||
@ -34,7 +34,7 @@ export function GangMemberStats(props: IProps): React.ReactElement {
|
||||
|
||||
const gang = useGang();
|
||||
const data = [
|
||||
[`Money:`, <MoneyRate money={5 * props.member.calculateMoneyGain(gang)} />],
|
||||
[`Money:`, <MoneyRate key="money" money={5 * props.member.calculateMoneyGain(gang)} />],
|
||||
[`Respect:`, `${formatRespect(5 * props.member.calculateRespectGain(gang))} / sec`],
|
||||
[`Wanted Level:`, `${formatWanted(5 * props.member.calculateWantedLevelGain(gang))} / sec`],
|
||||
[`Total Respect:`, `${formatRespect(props.member.earnedRespect)}`],
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import React from "react";
|
||||
import { ManagementSubpage } from "./ManagementSubpage";
|
||||
import { TerritorySubpage } from "./TerritorySubpage";
|
||||
import { EquipmentsSubpage } from "./EquipmentsSubpage";
|
||||
@ -8,6 +8,8 @@ import { Context } from "./Context";
|
||||
import Tabs from "@mui/material/Tabs";
|
||||
import Tab from "@mui/material/Tab";
|
||||
|
||||
import { useRerender } from "../../ui/React/hooks";
|
||||
|
||||
/** React Component for all the gang stuff. */
|
||||
export function GangRoot(): React.ReactElement {
|
||||
const gang = (function () {
|
||||
@ -20,12 +22,7 @@ export function GangRoot(): React.ReactElement {
|
||||
setValue(tab);
|
||||
}
|
||||
|
||||
const setRerender = useState(false)[1];
|
||||
|
||||
useEffect(() => {
|
||||
const id = setInterval(() => setRerender((old) => !old), 200);
|
||||
return () => clearInterval(id);
|
||||
}, []);
|
||||
useRerender(200);
|
||||
|
||||
return (
|
||||
<Context.Gang.Provider value={gang}>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Button, Container, Paper, Typography } from "@mui/material";
|
||||
import React, { useState } from "react";
|
||||
import React, { useCallback, useState } from "react";
|
||||
import { AugmentationName } from "@enums";
|
||||
import { Router } from "../../ui/GameRoot";
|
||||
import { Page } from "../../ui/Router";
|
||||
@ -15,12 +15,12 @@ import { SlashGame } from "./SlashGame";
|
||||
import { Victory } from "./Victory";
|
||||
import { WireCuttingGame } from "./WireCuttingGame";
|
||||
|
||||
interface IProps {
|
||||
type GameProps = {
|
||||
StartingDifficulty: number;
|
||||
Difficulty: number;
|
||||
Reward: number;
|
||||
MaxLevel: number;
|
||||
}
|
||||
};
|
||||
|
||||
enum Stage {
|
||||
Countdown = 0,
|
||||
@ -40,7 +40,7 @@ const minigames = [
|
||||
WireCuttingGame,
|
||||
];
|
||||
|
||||
export function Game(props: IProps): React.ReactElement {
|
||||
export function Game(props: GameProps): React.ReactElement {
|
||||
const [level, setLevel] = useState(1);
|
||||
const [stage, setStage] = useState(Stage.Countdown);
|
||||
const [results, setResults] = useState("");
|
||||
@ -49,32 +49,21 @@ export function Game(props: IProps): React.ReactElement {
|
||||
id: Math.floor(Math.random() * minigames.length),
|
||||
});
|
||||
|
||||
function nextGameId(): number {
|
||||
let id = gameIds.lastGames[0];
|
||||
const ids = [gameIds.lastGames[0], gameIds.lastGames[1], gameIds.id];
|
||||
while (ids.includes(id)) {
|
||||
id = Math.floor(Math.random() * minigames.length);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
const setupNextGame = useCallback(() => {
|
||||
const nextGameId = () => {
|
||||
let id = gameIds.lastGames[0];
|
||||
const ids = [gameIds.lastGames[0], gameIds.lastGames[1], gameIds.id];
|
||||
while (ids.includes(id)) {
|
||||
id = Math.floor(Math.random() * minigames.length);
|
||||
}
|
||||
return id;
|
||||
};
|
||||
|
||||
function setupNextGame(): void {
|
||||
setGameIds({
|
||||
lastGames: [gameIds.lastGames[1], gameIds.id],
|
||||
id: nextGameId(),
|
||||
});
|
||||
}
|
||||
|
||||
function success(): void {
|
||||
pushResult(true);
|
||||
if (level === props.MaxLevel) {
|
||||
setStage(Stage.Sell);
|
||||
} else {
|
||||
setStage(Stage.Countdown);
|
||||
setLevel(level + 1);
|
||||
}
|
||||
setupNextGame();
|
||||
}
|
||||
}, [gameIds]);
|
||||
|
||||
function pushResult(win: boolean): void {
|
||||
setResults((old) => {
|
||||
@ -85,20 +74,34 @@ export function Game(props: IProps): React.ReactElement {
|
||||
});
|
||||
}
|
||||
|
||||
function failure(options?: { automated: boolean }): void {
|
||||
setStage(Stage.Countdown);
|
||||
pushResult(false);
|
||||
// Kill the player immediately if they use automation, so
|
||||
// it's clear they're not meant to
|
||||
const damage = options?.automated
|
||||
? Player.hp.current
|
||||
: props.StartingDifficulty * 3 * (Player.hasAugmentation(AugmentationName.WKSharmonizer, true) ? 0.5 : 1);
|
||||
if (Player.takeDamage(damage)) {
|
||||
Router.toPage(Page.City);
|
||||
return;
|
||||
const onSuccess = useCallback(() => {
|
||||
pushResult(true);
|
||||
if (level === props.MaxLevel) {
|
||||
setStage(Stage.Sell);
|
||||
} else {
|
||||
setStage(Stage.Countdown);
|
||||
setLevel(level + 1);
|
||||
}
|
||||
setupNextGame();
|
||||
}
|
||||
}, [level, props.MaxLevel, setupNextGame]);
|
||||
|
||||
const onFailure = useCallback(
|
||||
(options?: { automated: boolean }) => {
|
||||
setStage(Stage.Countdown);
|
||||
pushResult(false);
|
||||
// Kill the player immediately if they use automation, so
|
||||
// it's clear they're not meant to
|
||||
const damage = options?.automated
|
||||
? Player.hp.current
|
||||
: props.StartingDifficulty * 3 * (Player.hasAugmentation(AugmentationName.WKSharmonizer, true) ? 0.5 : 1);
|
||||
if (Player.takeDamage(damage)) {
|
||||
Router.toPage(Page.City);
|
||||
return;
|
||||
}
|
||||
setupNextGame();
|
||||
},
|
||||
[props.StartingDifficulty, setupNextGame],
|
||||
);
|
||||
|
||||
function cancel(): void {
|
||||
Router.toPage(Page.City);
|
||||
@ -112,7 +115,9 @@ export function Game(props: IProps): React.ReactElement {
|
||||
break;
|
||||
case Stage.Minigame: {
|
||||
const MiniGame = minigames[gameIds.id];
|
||||
stageComponent = <MiniGame onSuccess={success} onFailure={failure} difficulty={props.Difficulty + level / 50} />;
|
||||
stageComponent = (
|
||||
<MiniGame onSuccess={onSuccess} onFailure={onFailure} difficulty={props.Difficulty + level / 50} />
|
||||
);
|
||||
break;
|
||||
}
|
||||
case Stage.Sell:
|
||||
|
@ -4,36 +4,41 @@ import { AugmentationName } from "@enums";
|
||||
import { Player } from "@player";
|
||||
import { ProgressBar } from "../../ui/React/Progress";
|
||||
|
||||
interface IProps {
|
||||
type GameTimerProps = {
|
||||
millis: number;
|
||||
onExpire: () => void;
|
||||
noPaper?: boolean;
|
||||
ignoreAugment_WKSharmonizer?: boolean;
|
||||
}
|
||||
};
|
||||
|
||||
export function GameTimer(props: IProps): React.ReactElement {
|
||||
export function GameTimer({
|
||||
millis,
|
||||
onExpire,
|
||||
noPaper,
|
||||
ignoreAugment_WKSharmonizer,
|
||||
}: GameTimerProps): React.ReactElement {
|
||||
const [v, setV] = useState(100);
|
||||
const totalMillis =
|
||||
(!props.ignoreAugment_WKSharmonizer && Player.hasAugmentation(AugmentationName.WKSharmonizer, true) ? 1.3 : 1) *
|
||||
props.millis;
|
||||
(!ignoreAugment_WKSharmonizer && Player.hasAugmentation(AugmentationName.WKSharmonizer, true) ? 1.3 : 1) * millis;
|
||||
|
||||
const tick = 200;
|
||||
useEffect(() => {
|
||||
const intervalId = setInterval(() => {
|
||||
setV((old) => {
|
||||
if (old <= 0) props.onExpire();
|
||||
if (old <= 0) onExpire();
|
||||
return old - (tick / totalMillis) * 100;
|
||||
});
|
||||
}, tick);
|
||||
|
||||
return () => {
|
||||
clearInterval(intervalId);
|
||||
};
|
||||
}, []);
|
||||
}, [onExpire, totalMillis]);
|
||||
|
||||
// https://stackoverflow.com/questions/55593367/disable-material-uis-linearprogress-animation
|
||||
// TODO(hydroflame): there's like a bug where it triggers the end before the
|
||||
// bar physically reaches the end
|
||||
return props.noPaper ? (
|
||||
return noPaper ? (
|
||||
<ProgressBar variant="determinate" value={v} color="primary" />
|
||||
) : (
|
||||
<Paper sx={{ p: 1, mb: 1 }}>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Box, Paper, Typography } from "@mui/material";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { AugmentationName } from "@enums";
|
||||
import { Player } from "@player";
|
||||
import { KEY } from "../../utils/helpers/keyCodes";
|
||||
@ -25,48 +25,52 @@ const difficulties: {
|
||||
Impossible: { window: 150 },
|
||||
};
|
||||
|
||||
export function SlashGame(props: IMinigameProps): React.ReactElement {
|
||||
export function SlashGame({ difficulty: _difficulty, onSuccess, onFailure }: IMinigameProps): React.ReactElement {
|
||||
const difficulty: Difficulty = { window: 0 };
|
||||
interpolate(difficulties, props.difficulty, difficulty);
|
||||
interpolate(difficulties, _difficulty, difficulty);
|
||||
|
||||
const [phase, setPhase] = useState(0);
|
||||
|
||||
function press(this: Document, event: KeyboardEvent): void {
|
||||
event.preventDefault();
|
||||
if (event.key !== KEY.SPACE) return;
|
||||
if (phase !== 1) {
|
||||
props.onFailure();
|
||||
onFailure();
|
||||
} else {
|
||||
props.onSuccess();
|
||||
onSuccess();
|
||||
}
|
||||
}
|
||||
const hasAugment = Player.hasAugmentation(AugmentationName.MightOfAres, true);
|
||||
const guardingTime = Math.random() * 3250 + 1500 - (250 + difficulty.window);
|
||||
const preparingTime = difficulty.window;
|
||||
const attackingTime = 250;
|
||||
|
||||
const guardingTimeRef = useRef(Math.random() * 3250 + 1500 - (250 + difficulty.window));
|
||||
|
||||
useEffect(() => {
|
||||
const preparingTime = difficulty.window;
|
||||
const attackingTime = 250;
|
||||
|
||||
let id = window.setTimeout(() => {
|
||||
setPhase(1);
|
||||
id = window.setTimeout(() => {
|
||||
setPhase(2);
|
||||
id = window.setTimeout(() => props.onFailure(), attackingTime);
|
||||
id = window.setTimeout(() => onFailure(), attackingTime);
|
||||
}, preparingTime);
|
||||
}, guardingTime);
|
||||
}, guardingTimeRef.current);
|
||||
|
||||
return () => {
|
||||
clearInterval(id);
|
||||
};
|
||||
}, []);
|
||||
}, [difficulty.window, onFailure]);
|
||||
|
||||
const hasAugment = Player.hasAugmentation(AugmentationName.MightOfAres, true);
|
||||
return (
|
||||
<>
|
||||
<GameTimer millis={5000} onExpire={props.onFailure} />
|
||||
<GameTimer millis={5000} onExpire={onFailure} />
|
||||
<Paper sx={{ display: "grid", justifyItems: "center" }}>
|
||||
<Typography variant="h4">Attack when his guard is down!</Typography>
|
||||
|
||||
{hasAugment ? (
|
||||
<Box sx={{ my: 1 }}>
|
||||
<Typography variant="h5">Guard will drop in...</Typography>
|
||||
<GameTimer millis={guardingTime} onExpire={() => null} ignoreAugment_WKSharmonizer noPaper />
|
||||
<GameTimer millis={guardingTimeRef.current} onExpire={() => null} ignoreAugment_WKSharmonizer noPaper />
|
||||
</Box>
|
||||
) : (
|
||||
<></>
|
||||
@ -75,7 +79,7 @@ export function SlashGame(props: IMinigameProps): React.ReactElement {
|
||||
{phase === 0 && <Typography variant="h4">Guarding ...</Typography>}
|
||||
{phase === 1 && <Typography variant="h4">Preparing?</Typography>}
|
||||
{phase === 2 && <Typography variant="h4">ATTACKING!</Typography>}
|
||||
<KeyHandler onKeyDown={press} onFailure={props.onFailure} />
|
||||
<KeyHandler onKeyDown={press} onFailure={onFailure} />
|
||||
</Paper>
|
||||
</>
|
||||
);
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React, { useEffect, useState, useRef } from "react";
|
||||
|
||||
import { Box, Paper, Typography } from "@mui/material";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { AugmentationName } from "@enums";
|
||||
import { Player } from "@player";
|
||||
import { Settings } from "../../Settings/Settings";
|
||||
@ -51,49 +52,51 @@ interface Question {
|
||||
shouldCut: (wire: Wire, index: number) => boolean;
|
||||
}
|
||||
|
||||
export function WireCuttingGame(props: IMinigameProps): React.ReactElement {
|
||||
export function WireCuttingGame({ onSuccess, onFailure, ...otherProps }: IMinigameProps): React.ReactElement {
|
||||
const difficulty: Difficulty = {
|
||||
timer: 0,
|
||||
wiresmin: 0,
|
||||
wiresmax: 0,
|
||||
rules: 0,
|
||||
};
|
||||
interpolate(difficulties, props.difficulty, difficulty);
|
||||
interpolate(difficulties, otherProps.difficulty, difficulty);
|
||||
const timer = difficulty.timer;
|
||||
const [wires] = useState(generateWires(difficulty));
|
||||
const [cutWires, setCutWires] = useState(new Array(wires.length).fill(false));
|
||||
const [questions] = useState(generateQuestion(wires, difficulty));
|
||||
const wiresRef = useRef(generateWires(difficulty));
|
||||
const questionsRef = useRef(generateQuestion(wiresRef.current, difficulty));
|
||||
|
||||
const [cutWires, setCutWires] = useState(new Array(wiresRef.current.length).fill(false));
|
||||
const hasAugment = Player.hasAugmentation(AugmentationName.KnowledgeOfApollo, true);
|
||||
|
||||
function checkWire(wireNum: number): boolean {
|
||||
return questions.some((q) => q.shouldCut(wires[wireNum - 1], wireNum - 1));
|
||||
}
|
||||
|
||||
// TODO: refactor, move the code from this effect to a `press` function
|
||||
useEffect(() => {
|
||||
// check if we won
|
||||
const wiresToBeCut = [];
|
||||
for (let j = 0; j < wires.length; j++) {
|
||||
for (let j = 0; j < wiresRef.current.length; j++) {
|
||||
let shouldBeCut = false;
|
||||
for (let i = 0; i < questions.length; i++) {
|
||||
shouldBeCut = shouldBeCut || questions[i].shouldCut(wires[j], j);
|
||||
for (let i = 0; i < questionsRef.current.length; i++) {
|
||||
shouldBeCut = shouldBeCut || questionsRef.current[i].shouldCut(wiresRef.current[j], j);
|
||||
}
|
||||
wiresToBeCut.push(shouldBeCut);
|
||||
}
|
||||
if (wiresToBeCut.every((b, i) => b === cutWires[i])) {
|
||||
props.onSuccess();
|
||||
onSuccess();
|
||||
}
|
||||
}, [cutWires]);
|
||||
}, [cutWires, onSuccess]);
|
||||
|
||||
function checkWire(wireNum: number): boolean {
|
||||
return questionsRef.current.some((q) => q.shouldCut(wiresRef.current[wireNum - 1], wireNum - 1));
|
||||
}
|
||||
|
||||
function press(this: Document, event: KeyboardEvent): void {
|
||||
event.preventDefault();
|
||||
const wireNum = parseInt(event.key);
|
||||
|
||||
if (wireNum < 1 || wireNum > wires.length || isNaN(wireNum)) return;
|
||||
if (wireNum < 1 || wireNum > wiresRef.current.length || isNaN(wireNum)) return;
|
||||
setCutWires((old) => {
|
||||
const next = [...old];
|
||||
next[wireNum - 1] = true;
|
||||
if (!checkWire(wireNum)) {
|
||||
props.onFailure();
|
||||
onFailure();
|
||||
}
|
||||
|
||||
return next;
|
||||
@ -102,23 +105,23 @@ export function WireCuttingGame(props: IMinigameProps): React.ReactElement {
|
||||
|
||||
return (
|
||||
<>
|
||||
<GameTimer millis={timer} onExpire={props.onFailure} />
|
||||
<GameTimer millis={timer} onExpire={onFailure} />
|
||||
<Paper sx={{ display: "grid", justifyItems: "center", pb: 1 }}>
|
||||
<Typography variant="h4" sx={{ width: "75%", textAlign: "center" }}>
|
||||
Cut the wires with the following properties! (keyboard 1 to 9)
|
||||
</Typography>
|
||||
{questions.map((question, i) => (
|
||||
{questionsRef.current.map((question, i) => (
|
||||
<Typography key={i}>{question.toString()}</Typography>
|
||||
))}
|
||||
<Box
|
||||
sx={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: `repeat(${wires.length}, 1fr)`,
|
||||
gridTemplateColumns: `repeat(${wiresRef.current.length}, 1fr)`,
|
||||
columnGap: 3,
|
||||
justifyItems: "center",
|
||||
}}
|
||||
>
|
||||
{new Array(wires.length).fill(0).map((_, i) => {
|
||||
{Array.from({ length: wiresRef.current.length }).map((_, i) => {
|
||||
const isCorrectWire = checkWire(i + 1);
|
||||
const color = hasAugment && !isCorrectWire ? Settings.theme.disabled : Settings.theme.primary;
|
||||
return (
|
||||
@ -129,7 +132,7 @@ export function WireCuttingGame(props: IMinigameProps): React.ReactElement {
|
||||
})}
|
||||
{new Array(8).fill(0).map((_, i) => (
|
||||
<React.Fragment key={i}>
|
||||
{wires.map((wire, j) => {
|
||||
{wiresRef.current.map((wire, j) => {
|
||||
if ((i === 3 || i === 4) && cutWires[j]) {
|
||||
return <Typography key={j}></Typography>;
|
||||
}
|
||||
@ -145,7 +148,7 @@ export function WireCuttingGame(props: IMinigameProps): React.ReactElement {
|
||||
</React.Fragment>
|
||||
))}
|
||||
</Box>
|
||||
<KeyHandler onKeyDown={press} onFailure={props.onFailure} />
|
||||
<KeyHandler onKeyDown={press} onFailure={onFailure} />
|
||||
</Paper>
|
||||
</>
|
||||
);
|
||||
|
@ -31,7 +31,7 @@ export function SlumsLocation(): React.ReactElement {
|
||||
return (
|
||||
<Box sx={{ display: "grid", width: "fit-content" }}>
|
||||
{crimes.map((crime) => (
|
||||
<Tooltip title={crime.tooltipText}>
|
||||
<Tooltip key={crime.workName} title={crime.tooltipText}>
|
||||
<Button onClick={(e) => doCrime(e, crime)}>
|
||||
{crime.type} ({formatPercent(crime.successRate(Player))} chance of success)
|
||||
</Button>
|
||||
|
@ -801,5 +801,5 @@ let customElementKey = 0;
|
||||
* so the game won't crash and the user gets sensible messages.
|
||||
*/
|
||||
export function wrapUserNode(value: unknown) {
|
||||
return <CustomBoundary key={`PlayerContent${customElementKey++}`} children={value as React.ReactNode} />;
|
||||
return <CustomBoundary key={`PlayerContent${customElementKey++}`}>{value}</CustomBoundary>;
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ const AugPreReqsChecklist = (props: IProps): React.ReactElement => {
|
||||
<b>Pre-Requisites:</b>
|
||||
<br />
|
||||
{aug.prereqs.map((preAug) => (
|
||||
<span style={{ display: "flex", alignItems: "center" }}>
|
||||
<span key={preAug} style={{ display: "flex", alignItems: "center" }}>
|
||||
{Player.hasAugmentation(preAug) ? <CheckBox sx={{ mr: 1 }} /> : <CheckBoxOutlineBlank sx={{ mr: 1 }} />}
|
||||
{preAug}
|
||||
</span>
|
||||
|
@ -13,7 +13,7 @@ import {
|
||||
} from "../../../ui/formatNumber";
|
||||
import { Settings } from "../../../Settings/Settings";
|
||||
import { StatsRow } from "../../../ui/React/StatsRow";
|
||||
import { characterOverviewStyles as useStyles } from "../../../ui/React/CharacterOverview";
|
||||
import { useStyles } from "../../../ui/React/CharacterOverview";
|
||||
import { Money } from "../../../ui/React/Money";
|
||||
import { MoneyRate } from "../../../ui/React/MoneyRate";
|
||||
import { ReputationRate } from "../../../ui/React/ReputationRate";
|
||||
@ -106,7 +106,7 @@ export function EarningsElement(props: IProps): React.ReactElement {
|
||||
if (isSleeveCrimeWork(props.sleeve.currentWork)) {
|
||||
const gains = props.sleeve.currentWork.getExp(props.sleeve);
|
||||
data = [
|
||||
[`Money:`, <Money money={gains.money} />],
|
||||
[`Money:`, <Money key="money" money={gains.money} />],
|
||||
[`Hacking Exp:`, `${formatExp(gains.hackExp)}`],
|
||||
[`Strength Exp:`, `${formatExp(gains.strExp)}`],
|
||||
[`Defense Exp:`, `${formatExp(gains.defExp)}`],
|
||||
@ -118,7 +118,7 @@ export function EarningsElement(props: IProps): React.ReactElement {
|
||||
if (isSleeveClassWork(props.sleeve.currentWork)) {
|
||||
const rates = props.sleeve.currentWork.calculateRates(props.sleeve);
|
||||
data = [
|
||||
[`Money:`, <MoneyRate money={CYCLES_PER_SEC * rates.money} />],
|
||||
[`Money:`, <MoneyRate key="money-rate" money={CYCLES_PER_SEC * rates.money} />],
|
||||
[`Hacking Exp:`, `${formatExp(CYCLES_PER_SEC * rates.hackExp)} / sec`],
|
||||
[`Strength Exp:`, `${formatExp(CYCLES_PER_SEC * rates.strExp)} / sec`],
|
||||
[`Defense Exp:`, `${formatExp(CYCLES_PER_SEC * rates.defExp)} / sec`],
|
||||
@ -137,21 +137,21 @@ export function EarningsElement(props: IProps): React.ReactElement {
|
||||
[`Dexterity Exp:`, `${formatExp(CYCLES_PER_SEC * rates.dexExp)} / sec`],
|
||||
[`Agility Exp:`, `${formatExp(CYCLES_PER_SEC * rates.agiExp)} / sec`],
|
||||
[`Charisma Exp:`, `${formatExp(CYCLES_PER_SEC * rates.chaExp)} / sec`],
|
||||
[`Reputation:`, <ReputationRate reputation={CYCLES_PER_SEC * repGain} />],
|
||||
[`Reputation:`, <ReputationRate key="reputation-rate" reputation={CYCLES_PER_SEC * repGain} />],
|
||||
];
|
||||
}
|
||||
|
||||
if (isSleeveCompanyWork(props.sleeve.currentWork)) {
|
||||
const rates = props.sleeve.currentWork.getGainRates(props.sleeve);
|
||||
data = [
|
||||
[`Money:`, <MoneyRate money={CYCLES_PER_SEC * rates.money} />],
|
||||
[`Money:`, <MoneyRate key="money-rate" money={CYCLES_PER_SEC * rates.money} />],
|
||||
[`Hacking Exp:`, `${formatExp(CYCLES_PER_SEC * rates.hackExp)} / sec`],
|
||||
[`Strength Exp:`, `${formatExp(CYCLES_PER_SEC * rates.strExp)} / sec`],
|
||||
[`Defense Exp:`, `${formatExp(CYCLES_PER_SEC * rates.defExp)} / sec`],
|
||||
[`Dexterity Exp:`, `${formatExp(CYCLES_PER_SEC * rates.dexExp)} / sec`],
|
||||
[`Agility Exp:`, `${formatExp(CYCLES_PER_SEC * rates.agiExp)} / sec`],
|
||||
[`Charisma Exp:`, `${formatExp(CYCLES_PER_SEC * rates.chaExp)} / sec`],
|
||||
[`Reputation:`, <ReputationRate reputation={CYCLES_PER_SEC * rates.reputation} />],
|
||||
[`Reputation:`, <ReputationRate key="reputation-rate" reputation={CYCLES_PER_SEC * rates.reputation} />],
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -152,8 +152,8 @@ export function prestigeAugmentation(): void {
|
||||
staneksGift.prestigeAugmentation();
|
||||
|
||||
resetPidCounter();
|
||||
ProgramsSeen.splice(0, ProgramsSeen.length);
|
||||
InvitationsSeen.splice(0, InvitationsSeen.length);
|
||||
ProgramsSeen.clear();
|
||||
InvitationsSeen.clear();
|
||||
}
|
||||
|
||||
// Prestige by destroying Bit Node and gaining a Source File
|
||||
|
@ -13,7 +13,7 @@ import { Programs } from "../Programs";
|
||||
import { CreateProgramWork, isCreateProgramWork } from "../../Work/CreateProgramWork";
|
||||
import { useRerender } from "../../ui/React/hooks";
|
||||
|
||||
export const ProgramsSeen: string[] = [];
|
||||
export const ProgramsSeen = new Set<string>();
|
||||
|
||||
export function ProgramsRoot(): React.ReactElement {
|
||||
useRerender(200);
|
||||
@ -35,9 +35,9 @@ export function ProgramsRoot(): React.ReactElement {
|
||||
|
||||
useEffect(() => {
|
||||
programs.forEach((p) => {
|
||||
if (ProgramsSeen.includes(p.name)) return;
|
||||
ProgramsSeen.push(p.name);
|
||||
ProgramsSeen.add(p.name);
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const getHackingLevelRemaining = (lvl: number): number => {
|
||||
|
@ -46,6 +46,9 @@ export function Editor({ beforeMount, onMount, onChange }: EditorProps) {
|
||||
editorRef.current?.getModel()?.dispose();
|
||||
editorRef.current?.dispose();
|
||||
};
|
||||
// this eslint ignore instruction can potentially cause unobvious bugs
|
||||
// (e.g. if `onChange` starts using a prop or state in parent component).
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return <div ref={containerDiv} style={{ height: "1px", width: "100%", flexGrow: 1 }} />;
|
||||
|
@ -77,14 +77,6 @@ function Root(props: IProps): React.ReactElement {
|
||||
currentScript = openScripts[0] ?? null;
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (currentScript !== null) {
|
||||
const tabIndex = currentTabIndex();
|
||||
if (typeof tabIndex === "number") onTabClick(tabIndex);
|
||||
parseCode(currentScript.code);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
function keydown(event: KeyboardEvent): void {
|
||||
if (Settings.DisableHotkeys) return;
|
||||
@ -145,10 +137,10 @@ function Root(props: IProps): React.ReactElement {
|
||||
finishUpdatingRAM();
|
||||
}, 300);
|
||||
|
||||
function parseCode(newCode: string) {
|
||||
const parseCode = (newCode: string) => {
|
||||
startUpdatingRAM();
|
||||
debouncedCodeParsing(newCode);
|
||||
}
|
||||
};
|
||||
|
||||
// How to load function definition in monaco
|
||||
// https://github.com/Microsoft/monaco-editor/issues/1415
|
||||
@ -444,6 +436,16 @@ function Root(props: IProps): React.ReactElement {
|
||||
onOpenPreviousTab,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (currentScript !== null) {
|
||||
const tabIndex = currentTabIndex();
|
||||
if (typeof tabIndex === "number") onTabClick(tabIndex);
|
||||
parseCode(currentScript.code);
|
||||
}
|
||||
// disable eslint because we want to run this only once on mount
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
|
@ -23,6 +23,9 @@ export function useVimEditor({ editor, vim, onOpenNextTab, onOpenPreviousTab, on
|
||||
|
||||
const vimStatusRef = useRef<HTMLElement>(null);
|
||||
|
||||
const actionsRef = useRef({ save: onSave, openNextTab: onOpenNextTab, openPreviousTab: onOpenPreviousTab });
|
||||
actionsRef.current = { save: onSave, openNextTab: onOpenNextTab, openPreviousTab: onOpenPreviousTab };
|
||||
|
||||
useEffect(() => {
|
||||
// setup monaco-vim
|
||||
if (vim && editor && !vimEditor) {
|
||||
@ -31,14 +34,14 @@ export function useVimEditor({ editor, vim, onOpenNextTab, onOpenPreviousTab, on
|
||||
setVimEditor(MonacoVim.initVimMode(editor, vimStatusRef.current));
|
||||
MonacoVim.VimMode.Vim.defineEx("write", "w", function () {
|
||||
// your own implementation on what you want to do when :w is pressed
|
||||
onSave();
|
||||
actionsRef.current.save();
|
||||
});
|
||||
MonacoVim.VimMode.Vim.defineEx("quit", "q", function () {
|
||||
Router.toPage(Page.Terminal);
|
||||
});
|
||||
|
||||
const saveNQuit = (): void => {
|
||||
onSave();
|
||||
actionsRef.current.save();
|
||||
Router.toPage(Page.Terminal);
|
||||
};
|
||||
// "wqriteandquit" & "xriteandquit" are not typos, prefix must be found in full string
|
||||
@ -48,10 +51,10 @@ export function useVimEditor({ editor, vim, onOpenNextTab, onOpenPreviousTab, on
|
||||
// Setup "go to next tab" and "go to previous tab". This is a little more involved
|
||||
// since these aren't Ex commands (they run in normal mode, not after typing `:`)
|
||||
MonacoVim.VimMode.Vim.defineAction("nextTabs", function (_cm: any, { repeat = 1 }: { repeat?: number }) {
|
||||
onOpenNextTab(repeat);
|
||||
actionsRef.current.openNextTab(repeat);
|
||||
});
|
||||
MonacoVim.VimMode.Vim.defineAction("prevTabs", function (_cm: any, { repeat = 1 }: { repeat?: number }) {
|
||||
onOpenPreviousTab(repeat);
|
||||
actionsRef.current.openPreviousTab(repeat);
|
||||
});
|
||||
MonacoVim.VimMode.Vim.mapCommand("gt", "action", "nextTabs", {}, { context: "normal" });
|
||||
MonacoVim.VimMode.Vim.mapCommand("gT", "action", "prevTabs", {}, { context: "normal" });
|
||||
|
@ -11,7 +11,7 @@ import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
||||
import { SidebarItem, ICreateProps as IItemProps } from "./SidebarItem";
|
||||
import type { Page } from "../../ui/Router";
|
||||
|
||||
interface IProps {
|
||||
type SidebarAccordionProps = {
|
||||
key_: string;
|
||||
page: Page;
|
||||
clickPage: (page: Page) => void;
|
||||
@ -20,7 +20,7 @@ interface IProps {
|
||||
icon: React.ReactElement["type"];
|
||||
sidebarOpen: boolean;
|
||||
classes: any;
|
||||
}
|
||||
};
|
||||
|
||||
// We can't useCallback for this, because in the items map it would be
|
||||
// called a changing number of times, and hooks can't be called in loops. So
|
||||
@ -42,9 +42,18 @@ function getClickFn(toWrap: (page: Page) => void, page: Page) {
|
||||
}
|
||||
|
||||
// This can't be usefully memoized, because props.items is a new array every time.
|
||||
export function SidebarAccordion(props: IProps): React.ReactElement {
|
||||
export function SidebarAccordion({
|
||||
classes,
|
||||
icon: Icon,
|
||||
sidebarOpen,
|
||||
key_,
|
||||
items,
|
||||
page,
|
||||
clickPage,
|
||||
flash,
|
||||
}: SidebarAccordionProps): React.ReactElement {
|
||||
const [open, setOpen] = useState(true);
|
||||
const li_classes = useMemo(() => ({ root: props.classes.listitem }), [props.classes.listitem]);
|
||||
const li_classes = useMemo(() => ({ root: classes.listitem }), [classes.listitem]);
|
||||
|
||||
// Explicitily useMemo() to save rerendering deep chunks of this tree.
|
||||
// memo() can't be (easily) used on components like <List>, because the
|
||||
@ -55,18 +64,18 @@ export function SidebarAccordion(props: IProps): React.ReactElement {
|
||||
() => (
|
||||
<ListItem classes={li_classes} button onClick={() => setOpen((open) => !open)}>
|
||||
<ListItemIcon>
|
||||
<Tooltip title={!props.sidebarOpen ? props.key_ : ""}>
|
||||
<props.icon color={"primary"} />
|
||||
<Tooltip title={!sidebarOpen ? key_ : ""}>
|
||||
<Icon color={"primary"} />
|
||||
</Tooltip>
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={<Typography>{props.key_}</Typography>} />
|
||||
<ListItemText primary={<Typography>{key_}</Typography>} />
|
||||
{open ? <ExpandLessIcon color="primary" /> : <ExpandMoreIcon color="primary" />}
|
||||
</ListItem>
|
||||
),
|
||||
[li_classes, props.sidebarOpen, props.key_, open, props.icon],
|
||||
[li_classes, sidebarOpen, key_, open, Icon],
|
||||
)}
|
||||
<Collapse in={open} timeout="auto" unmountOnExit>
|
||||
{props.items.map((x) => {
|
||||
{items.map((x) => {
|
||||
if (typeof x !== "object") return null;
|
||||
const { key_, icon, count, active } = x;
|
||||
return (
|
||||
@ -75,11 +84,11 @@ export function SidebarAccordion(props: IProps): React.ReactElement {
|
||||
key_={key_}
|
||||
icon={icon}
|
||||
count={count}
|
||||
active={active ?? props.page === key_}
|
||||
clickFn={getClickFn(props.clickPage, key_)}
|
||||
flash={props.flash === key_}
|
||||
classes={props.classes}
|
||||
sidebarOpen={props.sidebarOpen}
|
||||
active={active ?? page === key_}
|
||||
clickFn={getClickFn(clickPage, key_)}
|
||||
flash={flash === key_}
|
||||
classes={classes}
|
||||
sidebarOpen={sidebarOpen}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
@ -15,14 +15,14 @@ export interface ICreateProps {
|
||||
active?: boolean;
|
||||
}
|
||||
|
||||
export interface IProps extends ICreateProps {
|
||||
export interface SidebarItemProps extends ICreateProps {
|
||||
clickFn: () => void;
|
||||
flash: boolean;
|
||||
classes: any;
|
||||
sidebarOpen: boolean;
|
||||
}
|
||||
|
||||
export const SidebarItem = memo(function (props: IProps): React.ReactElement {
|
||||
export const SidebarItem = memo(function SidebarItem(props: SidebarItemProps): React.ReactElement {
|
||||
const color = props.flash ? "error" : props.active ? "primary" : "secondary";
|
||||
return (
|
||||
<ListItem
|
||||
@ -40,7 +40,7 @@ export const SidebarItem = memo(function (props: IProps): React.ReactElement {
|
||||
</Badge>
|
||||
</ListItemIcon>
|
||||
<ListItemText>
|
||||
<Typography color={color} children={props.key_} />
|
||||
<Typography color={color}>{props.key_}</Typography>
|
||||
</ListItemText>
|
||||
</ListItem>
|
||||
);
|
||||
|
@ -56,9 +56,12 @@ import { hash } from "../../hash/hash";
|
||||
import { Locations } from "../../Locations/Locations";
|
||||
import { useRerender } from "../../ui/React/hooks";
|
||||
|
||||
const RotatedDoubleArrowIcon = React.forwardRef((props: { color: "primary" | "secondary" | "error" }, __ref) => (
|
||||
<DoubleArrowIcon color={props.color} style={{ transform: "rotate(-90deg)" }} />
|
||||
));
|
||||
const RotatedDoubleArrowIcon = React.forwardRef(function RotatedDoubleArrowIcon(
|
||||
props: { color: "primary" | "secondary" | "error" },
|
||||
__ref: React.ForwardedRef<SVGSVGElement>,
|
||||
) {
|
||||
return <DoubleArrowIcon color={props.color} style={{ transform: "rotate(-90deg)" }} ref={__ref} />;
|
||||
});
|
||||
|
||||
const openedMixin = (theme: Theme): CSSObject => ({
|
||||
width: theme.spacing(31),
|
||||
@ -131,8 +134,8 @@ export function SidebarRoot(props: { page: Page }): React.ReactElement {
|
||||
}
|
||||
|
||||
const augmentationCount = Player.queuedAugmentations.length;
|
||||
const invitationsCount = Player.factionInvitations.filter((f) => !InvitationsSeen.includes(f)).length;
|
||||
const programCount = getAvailableCreatePrograms().length - ProgramsSeen.length;
|
||||
const invitationsCount = Player.factionInvitations.filter((f) => !InvitationsSeen.has(f)).length;
|
||||
const programCount = getAvailableCreatePrograms().length - ProgramsSeen.size;
|
||||
|
||||
const canOpenFactions =
|
||||
Player.factionInvitations.length > 0 ||
|
||||
@ -156,6 +159,24 @@ export function SidebarRoot(props: { page: Page }): React.ReactElement {
|
||||
const canBladeburner = !!Player.bladeburner;
|
||||
const canStaneksGift = Player.augmentations.some((aug) => aug.name === AugmentationName.StaneksGift1);
|
||||
|
||||
const clickPage = useCallback(
|
||||
(page: Page) => {
|
||||
if (page === Page.Job) {
|
||||
Router.toPage(page, { location: Locations[Object.keys(Player.jobs)[0]] });
|
||||
} else if (page == Page.ScriptEditor) {
|
||||
Router.toPage(page, {});
|
||||
} else if (isSimplePage(page)) {
|
||||
Router.toPage(page);
|
||||
} else {
|
||||
throw new Error("Can't handle click on Page " + page);
|
||||
}
|
||||
if (flash === page) {
|
||||
iTutorialNextStep();
|
||||
}
|
||||
},
|
||||
[flash],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
// Shortcuts to navigate through the game
|
||||
// Alt-t - Terminal
|
||||
@ -230,25 +251,7 @@ export function SidebarRoot(props: { page: Page }): React.ReactElement {
|
||||
|
||||
document.addEventListener("keydown", handleShortcuts);
|
||||
return () => document.removeEventListener("keydown", handleShortcuts);
|
||||
}, []);
|
||||
|
||||
const clickPage = useCallback(
|
||||
(page: Page) => {
|
||||
if (page === Page.Job) {
|
||||
Router.toPage(page, { location: Locations[Object.keys(Player.jobs)[0]] });
|
||||
} else if (page == Page.ScriptEditor) {
|
||||
Router.toPage(page, {});
|
||||
} else if (isSimplePage(page)) {
|
||||
Router.toPage(page);
|
||||
} else {
|
||||
throw new Error("Can't handle click on Page " + page);
|
||||
}
|
||||
if (flash === page) {
|
||||
iTutorialNextStep();
|
||||
}
|
||||
},
|
||||
[flash],
|
||||
);
|
||||
}, [canJob, clickPage, props.page]);
|
||||
|
||||
const classes = useStyles();
|
||||
const [open, setOpen] = useState(Settings.IsSidebarOpened);
|
||||
@ -280,7 +283,7 @@ export function SidebarRoot(props: { page: Page }): React.ReactElement {
|
||||
/>
|
||||
</ListItem>
|
||||
),
|
||||
[li_classes, open],
|
||||
[ChevronOpenClose, li_classes],
|
||||
)}
|
||||
<Divider />
|
||||
<List>
|
||||
|
@ -44,10 +44,6 @@ export function TerminalRoot(): React.ReactElement {
|
||||
const rerender = useRerender();
|
||||
const [key, setKey] = useState(0);
|
||||
|
||||
function clear(): void {
|
||||
setKey((key) => key + 1);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const debounced = _.debounce(async () => rerender(), 25, { maxWait: 50 });
|
||||
const unsubscribe = TerminalEvents.subscribe(debounced);
|
||||
@ -55,9 +51,10 @@ export function TerminalRoot(): React.ReactElement {
|
||||
debounced.cancel();
|
||||
unsubscribe();
|
||||
};
|
||||
}, []);
|
||||
}, [rerender]);
|
||||
|
||||
useEffect(() => {
|
||||
const clear = () => setKey((key) => key + 1);
|
||||
const debounced = _.debounce(async () => clear(), 25, { maxWait: 50 });
|
||||
const unsubscribe = TerminalClearEvents.subscribe(debounced);
|
||||
return () => {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useState } from "react";
|
||||
import { Modal } from "../../ui/React/Modal";
|
||||
|
||||
import Button from "@mui/material/Button";
|
||||
@ -23,28 +23,18 @@ interface IProps {
|
||||
interface FontFamilyProps {
|
||||
value: React.CSSProperties["fontFamily"];
|
||||
onChange: (newValue: React.CSSProperties["fontFamily"], error?: string) => void;
|
||||
refreshId: number;
|
||||
}
|
||||
|
||||
function FontFamilyField({ value, onChange, refreshId }: FontFamilyProps): React.ReactElement {
|
||||
function FontFamilyField({ value, onChange }: FontFamilyProps): React.ReactElement {
|
||||
const [errorText, setErrorText] = useState<string | undefined>();
|
||||
const [fontFamily, setFontFamily] = useState<React.CSSProperties["fontFamily"]>(value);
|
||||
|
||||
function update(newValue: React.CSSProperties["fontFamily"]): void {
|
||||
const update = (newValue: React.CSSProperties["fontFamily"]) => {
|
||||
const errorText = newValue ? "" : "Must have a value";
|
||||
setFontFamily(newValue);
|
||||
if (!newValue) {
|
||||
setErrorText("Must have a value");
|
||||
} else {
|
||||
setErrorText("");
|
||||
}
|
||||
}
|
||||
|
||||
function onTextChange(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||
update(event.target.value);
|
||||
}
|
||||
|
||||
useEffect(() => onChange(fontFamily, errorText), [fontFamily]);
|
||||
useEffect(() => update(value), [refreshId]);
|
||||
setErrorText(errorText);
|
||||
onChange(newValue, errorText);
|
||||
};
|
||||
|
||||
return (
|
||||
<TextField
|
||||
@ -53,7 +43,7 @@ function FontFamilyField({ value, onChange, refreshId }: FontFamilyProps): React
|
||||
error={!!errorText}
|
||||
value={fontFamily}
|
||||
helperText={errorText}
|
||||
onChange={onTextChange}
|
||||
onChange={(event) => update(event.target.value)}
|
||||
fullWidth
|
||||
/>
|
||||
);
|
||||
@ -62,30 +52,19 @@ function FontFamilyField({ value, onChange, refreshId }: FontFamilyProps): React
|
||||
interface LineHeightProps {
|
||||
value: React.CSSProperties["lineHeight"];
|
||||
onChange: (newValue: React.CSSProperties["lineHeight"], error?: string) => void;
|
||||
refreshId: number;
|
||||
}
|
||||
|
||||
function LineHeightField({ value, onChange, refreshId }: LineHeightProps): React.ReactElement {
|
||||
function LineHeightField({ value, onChange }: LineHeightProps): React.ReactElement {
|
||||
const [errorText, setErrorText] = useState<string | undefined>();
|
||||
const [lineHeight, setLineHeight] = useState<React.CSSProperties["lineHeight"]>(value);
|
||||
|
||||
function update(newValue: React.CSSProperties["lineHeight"]): void {
|
||||
const update = (newValue: React.CSSProperties["lineHeight"]) => {
|
||||
const errorText = !newValue ? "Must have a value" : isNaN(Number(newValue)) ? "Must be a number" : "";
|
||||
|
||||
setLineHeight(newValue);
|
||||
if (!newValue) {
|
||||
setErrorText("Must have a value");
|
||||
} else if (isNaN(Number(newValue))) {
|
||||
setErrorText("Must be a number");
|
||||
} else {
|
||||
setErrorText("");
|
||||
}
|
||||
}
|
||||
|
||||
function onTextChange(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||
update(event.target.value);
|
||||
}
|
||||
|
||||
useEffect(() => onChange(lineHeight, errorText), [lineHeight]);
|
||||
useEffect(() => update(value), [refreshId]);
|
||||
setErrorText(errorText);
|
||||
onChange(newValue, errorText);
|
||||
};
|
||||
|
||||
return (
|
||||
<TextField
|
||||
@ -94,13 +73,12 @@ function LineHeightField({ value, onChange, refreshId }: LineHeightProps): React
|
||||
error={!!errorText}
|
||||
value={lineHeight}
|
||||
helperText={errorText}
|
||||
onChange={onTextChange}
|
||||
onChange={(event) => update(event.target.value)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function StyleEditorModal(props: IProps): React.ReactElement {
|
||||
const [refreshId, setRefreshId] = useState<number>(0);
|
||||
const [error, setError] = useState<string | undefined>();
|
||||
const [customStyle, setCustomStyle] = useState<IStyleSettings>({
|
||||
...Settings.styles,
|
||||
@ -119,7 +97,6 @@ export function StyleEditorModal(props: IProps): React.ReactElement {
|
||||
const styles = { ...defaultStyles };
|
||||
setCustomStyle(styles);
|
||||
persistToSettings(styles);
|
||||
setRefreshId(refreshId + 1);
|
||||
}
|
||||
|
||||
function update(styles: IStyleSettings, errorMessage?: string): void {
|
||||
@ -139,13 +116,11 @@ export function StyleEditorModal(props: IProps): React.ReactElement {
|
||||
<Paper sx={{ p: 2, my: 2 }}>
|
||||
<FontFamilyField
|
||||
value={customStyle.fontFamily}
|
||||
refreshId={refreshId}
|
||||
onChange={(value, error) => update({ ...customStyle, fontFamily: value ?? "" }, error)}
|
||||
/>
|
||||
<br />
|
||||
<LineHeightField
|
||||
value={customStyle.lineHeight}
|
||||
refreshId={refreshId}
|
||||
onChange={(value, error) => update({ ...customStyle, lineHeight: Number(value) ?? 0 }, error)}
|
||||
/>
|
||||
<br />
|
||||
|
@ -106,60 +106,60 @@ interface IMoneyModalProps {
|
||||
|
||||
function MoneyModal({ open, onClose }: IMoneyModalProps): React.ReactElement {
|
||||
function convertMoneySourceTrackerToString(src: MoneySourceTracker): React.ReactElement {
|
||||
const parts: [string, JSX.Element][] = [[`Total:`, <Money money={src.total} />]];
|
||||
const parts: [string, JSX.Element][] = [[`Total:`, <Money key="total" money={src.total} />]];
|
||||
if (src.augmentations) {
|
||||
parts.push([`Augmentations:`, <Money money={src.augmentations} />]);
|
||||
parts.push([`Augmentations:`, <Money key="aug" money={src.augmentations} />]);
|
||||
}
|
||||
if (src.bladeburner) {
|
||||
parts.push([`Bladeburner:`, <Money money={src.bladeburner} />]);
|
||||
parts.push([`Bladeburner:`, <Money key="blade" money={src.bladeburner} />]);
|
||||
}
|
||||
if (src.casino) {
|
||||
parts.push([`Casino:`, <Money money={src.casino} />]);
|
||||
parts.push([`Casino:`, <Money key="casino" money={src.casino} />]);
|
||||
}
|
||||
if (src.codingcontract) {
|
||||
parts.push([`Coding Contracts:`, <Money money={src.codingcontract} />]);
|
||||
parts.push([`Coding Contracts:`, <Money key="coding-contract" money={src.codingcontract} />]);
|
||||
}
|
||||
if (src.work) {
|
||||
parts.push([`Company Work:`, <Money money={src.work} />]);
|
||||
parts.push([`Company Work:`, <Money key="company-work" money={src.work} />]);
|
||||
}
|
||||
if (src.class) {
|
||||
parts.push([`Class:`, <Money money={src.class} />]);
|
||||
parts.push([`Class:`, <Money key="class" money={src.class} />]);
|
||||
}
|
||||
if (src.corporation) {
|
||||
parts.push([`Corporation:`, <Money money={src.corporation} />]);
|
||||
parts.push([`Corporation:`, <Money key="corp" money={src.corporation} />]);
|
||||
}
|
||||
if (src.crime) {
|
||||
parts.push([`Crimes:`, <Money money={src.crime} />]);
|
||||
parts.push([`Crimes:`, <Money key="crime" money={src.crime} />]);
|
||||
}
|
||||
if (src.gang) {
|
||||
parts.push([`Gang:`, <Money money={src.gang} />]);
|
||||
parts.push([`Gang:`, <Money key="gang" money={src.gang} />]);
|
||||
}
|
||||
if (src.hacking) {
|
||||
parts.push([`Hacking:`, <Money money={src.hacking} />]);
|
||||
parts.push([`Hacking:`, <Money key="hacking" money={src.hacking} />]);
|
||||
}
|
||||
if (src.hacknet) {
|
||||
parts.push([`Hacknet Nodes:`, <Money money={src.hacknet} />]);
|
||||
parts.push([`Hacknet Nodes:`, <Money key="hacknet" money={src.hacknet} />]);
|
||||
}
|
||||
if (src.hacknet_expenses) {
|
||||
parts.push([`Hacknet Nodes Expenses:`, <Money money={src.hacknet_expenses} />]);
|
||||
parts.push([`Hacknet Nodes Expenses:`, <Money key="hacknet-expenses" money={src.hacknet_expenses} />]);
|
||||
}
|
||||
if (src.hospitalization) {
|
||||
parts.push([`Hospitalization:`, <Money money={src.hospitalization} />]);
|
||||
parts.push([`Hospitalization:`, <Money key="hospital" money={src.hospitalization} />]);
|
||||
}
|
||||
if (src.infiltration) {
|
||||
parts.push([`Infiltration:`, <Money money={src.infiltration} />]);
|
||||
parts.push([`Infiltration:`, <Money key="infiltration" money={src.infiltration} />]);
|
||||
}
|
||||
if (src.servers) {
|
||||
parts.push([`Servers:`, <Money money={src.servers} />]);
|
||||
parts.push([`Servers:`, <Money key="servers" money={src.servers} />]);
|
||||
}
|
||||
if (src.stock) {
|
||||
parts.push([`Stock Market:`, <Money money={src.stock} />]);
|
||||
parts.push([`Stock Market:`, <Money key="market" money={src.stock} />]);
|
||||
}
|
||||
if (src.sleeves) {
|
||||
parts.push([`Sleeves:`, <Money money={src.sleeves} />]);
|
||||
parts.push([`Sleeves:`, <Money key="sleeves" money={src.sleeves} />]);
|
||||
}
|
||||
if (src.other) {
|
||||
parts.push([`Other:`, <Money money={src.other} />]);
|
||||
parts.push([`Other:`, <Money key="other" money={src.other} />]);
|
||||
}
|
||||
|
||||
return <StatsTable rows={parts} wide />;
|
||||
|
@ -32,7 +32,9 @@ export function ButtonWithTooltip({
|
||||
return (
|
||||
<Tooltip {...tooltipProps} title={tooltipText}>
|
||||
<span>
|
||||
<Button {...buttonProps} disabled={disabled} onClick={onClick} children={children} />
|
||||
<Button {...buttonProps} disabled={disabled} onClick={onClick}>
|
||||
{children}
|
||||
</Button>
|
||||
</span>
|
||||
</Tooltip>
|
||||
);
|
||||
|
@ -5,27 +5,26 @@ import { RecoveryRoot } from "./React/RecoveryRoot";
|
||||
import { Page } from "./Router";
|
||||
import { Router } from "./GameRoot";
|
||||
|
||||
interface IProps {
|
||||
type ErrorBoundaryProps = {
|
||||
softReset: () => void;
|
||||
}
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
interface IState {
|
||||
type ErrorBoundaryState = {
|
||||
error?: Error;
|
||||
errorInfo?: React.ErrorInfo;
|
||||
page?: Page;
|
||||
hasError: boolean;
|
||||
}
|
||||
};
|
||||
|
||||
export class ErrorBoundary extends React.Component<IProps, IState> {
|
||||
state: IState;
|
||||
|
||||
constructor(props: IProps) {
|
||||
export class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
||||
constructor(props: ErrorBoundaryProps) {
|
||||
super(props);
|
||||
this.state = { hasError: false } as IState;
|
||||
this.state = { hasError: false };
|
||||
}
|
||||
|
||||
reset(): void {
|
||||
this.setState({ hasError: false } as IState);
|
||||
this.setState({ hasError: false });
|
||||
}
|
||||
|
||||
componentDidCatch(error: Error, errorInfo: ErrorInfo): void {
|
||||
@ -35,6 +34,7 @@ export class ErrorBoundary extends React.Component<IProps, IState> {
|
||||
});
|
||||
console.error(error, errorInfo);
|
||||
}
|
||||
|
||||
render(): React.ReactNode {
|
||||
if (this.state.hasError) {
|
||||
let errorData: IErrorData | undefined;
|
||||
@ -51,7 +51,8 @@ export class ErrorBoundary extends React.Component<IProps, IState> {
|
||||
}
|
||||
return this.props.children;
|
||||
}
|
||||
static getDerivedStateFromError(error: Error): IState {
|
||||
|
||||
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
|
||||
return { hasError: true, error };
|
||||
}
|
||||
}
|
||||
|
@ -141,7 +141,7 @@ export function GameRoot(): React.ReactElement {
|
||||
|
||||
useEffect(() => {
|
||||
return ITutorialEvents.subscribe(rerender);
|
||||
}, []);
|
||||
}, [rerender]);
|
||||
|
||||
function killAllScripts(): void {
|
||||
for (const server of GetAllServers()) {
|
||||
|
@ -541,6 +541,7 @@ export function InteractiveTutorialRoot(): React.ReactElement {
|
||||
<a
|
||||
href="https://bitburner-official.readthedocs.io/en/latest/guidesandtips/gettingstartedguideforbeginnerprogrammers.html"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Getting Started
|
||||
</a>{" "}
|
||||
@ -560,7 +561,8 @@ export function InteractiveTutorialRoot(): React.ReactElement {
|
||||
|
||||
useEffect(() => {
|
||||
return ITutorialEvents.subscribe(rerender);
|
||||
}, []);
|
||||
}, [rerender]);
|
||||
|
||||
const step = ITutorial.currStep;
|
||||
const content = contents[step];
|
||||
if (content === undefined) throw new Error("error in the tutorial");
|
||||
|
@ -60,12 +60,12 @@ const lineClass = (classes: Record<string, string>, s: string): string => {
|
||||
return lineClassMap[s] || classes.primary;
|
||||
};
|
||||
|
||||
interface IProps {
|
||||
type ANSIITypographyProps = {
|
||||
text: unknown;
|
||||
color: "primary" | "error" | "success" | "info" | "warn";
|
||||
}
|
||||
};
|
||||
|
||||
export const ANSIITypography = React.memo((props: IProps): React.ReactElement => {
|
||||
export const ANSIITypography = React.memo(function ANSIITypography(props: ANSIITypographyProps): React.ReactElement {
|
||||
const text = String(props.text);
|
||||
const classes = useStyles();
|
||||
const parts = [];
|
||||
|
@ -40,6 +40,7 @@ import { ActionIdentifier } from "../../Bladeburner/ActionIdentifier";
|
||||
import { Skills } from "../../PersonObjects/Skills";
|
||||
import { calculateSkillProgress } from "../../PersonObjects/formulas/skill";
|
||||
import { EventEmitter } from "../../utils/EventEmitter";
|
||||
import { useRerender } from "./hooks";
|
||||
|
||||
type SkillRowName = "Hack" | "Str" | "Def" | "Dex" | "Agi" | "Cha" | "Int";
|
||||
type RowName = SkillRowName | "HP" | "Money";
|
||||
@ -103,8 +104,10 @@ function SkillBar({ name, color }: SkillBarProps): React.ReactElement {
|
||||
const mult = skillMultUpdaters[name]();
|
||||
setProgress(calculateSkillProgress(Player.exp[skillNameMap[name]], mult));
|
||||
});
|
||||
|
||||
return clearSubscription;
|
||||
}, []);
|
||||
}, [name]);
|
||||
|
||||
return (
|
||||
<TableRow>
|
||||
<StatsProgressOverviewCell progress={progress} color={color} />
|
||||
@ -118,11 +121,12 @@ interface ValProps {
|
||||
}
|
||||
export function Val({ name, color }: ValProps): React.ReactElement {
|
||||
//val isn't actually used here, the update of val just forces a refresh of the formattedVal that gets shown
|
||||
const setVal = useState(valUpdaters[name]())[1];
|
||||
const [__, setVal] = useState(valUpdaters[name]());
|
||||
useEffect(() => {
|
||||
const clearSubscription = OverviewEventEmitter.subscribe(() => setVal(valUpdaters[name]()));
|
||||
return clearSubscription;
|
||||
}, []);
|
||||
}, [name]);
|
||||
|
||||
return <Typography color={color}>{formattedVals[name]()}</Typography>;
|
||||
}
|
||||
|
||||
@ -249,11 +253,11 @@ function ActionText(props: { action: ActionIdentifier }): React.ReactElement {
|
||||
|
||||
function BladeburnerText(): React.ReactElement {
|
||||
const classes = useStyles();
|
||||
const setRerender = useState(false)[1];
|
||||
const rerender = useRerender();
|
||||
useEffect(() => {
|
||||
const clearSubscription = OverviewEventEmitter.subscribe(() => setRerender((old) => !old));
|
||||
const clearSubscription = OverviewEventEmitter.subscribe(rerender);
|
||||
return clearSubscription;
|
||||
}, []);
|
||||
}, [rerender]);
|
||||
|
||||
const action = Player.bladeburner?.action;
|
||||
return useMemo(
|
||||
@ -276,7 +280,7 @@ function BladeburnerText(): React.ReactElement {
|
||||
</TableRow>
|
||||
</>
|
||||
),
|
||||
[action?.type, action?.name, classes.cellNone],
|
||||
[action, classes.cellNone],
|
||||
);
|
||||
}
|
||||
|
||||
@ -325,11 +329,11 @@ function WorkInProgressOverview({ tooltip, children, header }: WorkInProgressOve
|
||||
}
|
||||
|
||||
function Work(): React.ReactElement {
|
||||
const setRerender = useState(false)[1];
|
||||
const rerender = useRerender();
|
||||
useEffect(() => {
|
||||
const clearSubscription = OverviewEventEmitter.subscribe(() => setRerender((old) => !old));
|
||||
const clearSubscription = OverviewEventEmitter.subscribe(rerender);
|
||||
return clearSubscription;
|
||||
}, []);
|
||||
}, [rerender]);
|
||||
|
||||
if (Player.currentWork === null || Player.focus) return <></>;
|
||||
|
||||
@ -461,4 +465,4 @@ const useStyles = makeStyles((theme: Theme) =>
|
||||
}),
|
||||
);
|
||||
|
||||
export { useStyles as characterOverviewStyles };
|
||||
export { useStyles };
|
||||
|
@ -9,57 +9,54 @@ import Typography from "@mui/material/Typography";
|
||||
import TextField from "@mui/material/TextField";
|
||||
import Button from "@mui/material/Button";
|
||||
|
||||
interface IProps {
|
||||
interface CodingContractProps {
|
||||
c: CodingContract;
|
||||
onClose: () => void;
|
||||
onAttempt: (answer: string) => void;
|
||||
}
|
||||
|
||||
export const CodingContractEvent = new EventEmitter<[IProps]>();
|
||||
export const CodingContractEvent = new EventEmitter<[CodingContractProps]>();
|
||||
|
||||
export function CodingContractModal(): React.ReactElement {
|
||||
const [props, setProps] = useState<IProps | null>(null);
|
||||
const [contract, setContract] = useState<CodingContractProps | null>(null);
|
||||
const [answer, setAnswer] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
CodingContractEvent.subscribe((props) => setProps(props));
|
||||
CodingContractEvent.subscribe((props) => setContract(props));
|
||||
});
|
||||
if (props === null) return <></>;
|
||||
if (contract === null) return <></>;
|
||||
|
||||
function onChange(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||
setAnswer(event.target.value);
|
||||
}
|
||||
|
||||
function onKeyDown(event: React.KeyboardEvent<HTMLInputElement>): void {
|
||||
if (props === null) return;
|
||||
// React just won't cooperate on this one.
|
||||
// "React.KeyboardEvent<HTMLInputElement>" seems like the right type but
|
||||
// whatever ...
|
||||
const value = (event.target as any).value;
|
||||
if (contract === null) return;
|
||||
const value = event.currentTarget.value;
|
||||
|
||||
if (event.key === KEY.ENTER && value !== "") {
|
||||
event.preventDefault();
|
||||
props.onAttempt(answer);
|
||||
contract.onAttempt(answer);
|
||||
setAnswer("");
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
function close(): void {
|
||||
if (props === null) return;
|
||||
props.onClose();
|
||||
setProps(null);
|
||||
if (contract === null) return;
|
||||
contract.onClose();
|
||||
setContract(null);
|
||||
}
|
||||
|
||||
const contractType = CodingContractTypes[props.c.type];
|
||||
const contractType = CodingContractTypes[contract.c.type];
|
||||
const description = [];
|
||||
for (const [i, value] of contractType.desc(props.c.data).split("\n").entries())
|
||||
for (const [i, value] of contractType.desc(contract.c.data).split("\n").entries())
|
||||
description.push(<span key={i} dangerouslySetInnerHTML={{ __html: value + "<br />" }}></span>);
|
||||
return (
|
||||
<Modal open={props !== null} onClose={close}>
|
||||
<CopyableText variant="h4" value={props.c.type} />
|
||||
<Modal open={contract !== null} onClose={close}>
|
||||
<CopyableText variant="h4" value={contract.c.type} />
|
||||
<Typography>
|
||||
You are attempting to solve a Coding Contract. You have {props.c.getMaxNumTries() - props.c.tries} tries
|
||||
You are attempting to solve a Coding Contract. You have {contract.c.getMaxNumTries() - contract.c.tries} tries
|
||||
remaining, after which the contract will self-destruct.
|
||||
</Typography>
|
||||
<br />
|
||||
@ -75,7 +72,7 @@ export function CodingContractModal(): React.ReactElement {
|
||||
endAdornment: (
|
||||
<Button
|
||||
onClick={() => {
|
||||
props.onAttempt(answer);
|
||||
contract.onAttempt(answer);
|
||||
setAnswer("");
|
||||
close();
|
||||
}}
|
||||
|
@ -4,7 +4,7 @@ function replace(str: string, i: number, char: string): string {
|
||||
return str.substring(0, i) + char + str.substring(i + 1);
|
||||
}
|
||||
|
||||
interface IProps {
|
||||
interface CorruptableTextProps {
|
||||
content: string;
|
||||
}
|
||||
|
||||
@ -20,7 +20,7 @@ function randomize(char: string): string {
|
||||
return randFrom(other);
|
||||
}
|
||||
|
||||
export function CorruptableText(props: IProps): JSX.Element {
|
||||
export function CorruptableText(props: CorruptableTextProps): JSX.Element {
|
||||
const [content, setContent] = useState(props.content);
|
||||
|
||||
useEffect(() => {
|
||||
@ -30,8 +30,8 @@ export function CorruptableText(props: IProps): JSX.Element {
|
||||
counter--;
|
||||
if (counter > 0) return;
|
||||
counter = Math.random() * 5;
|
||||
const index = Math.random() * content.length;
|
||||
const letter = content.charAt(index);
|
||||
const index = Math.random() * props.content.length;
|
||||
const letter = props.content.charAt(index);
|
||||
setContent((content) => replace(content, index, randomize(letter)));
|
||||
timers.push(
|
||||
window.setTimeout(() => {
|
||||
@ -44,7 +44,7 @@ export function CorruptableText(props: IProps): JSX.Element {
|
||||
clearInterval(intervalId);
|
||||
timers.forEach((timerId) => clearTimeout(timerId));
|
||||
};
|
||||
}, []);
|
||||
}, [props.content]);
|
||||
|
||||
return <span>{content}</span>;
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import React, { useState, useEffect, useRef, useCallback, useMemo } from "react";
|
||||
import { EventEmitter } from "../../utils/EventEmitter";
|
||||
import { RunningScript } from "../../Script/RunningScript";
|
||||
import { killWorkerScriptByPid } from "../../Netscript/killWorkerScript";
|
||||
@ -80,6 +80,16 @@ let logs: Log[] = [];
|
||||
|
||||
export function LogBoxManager(): React.ReactElement {
|
||||
const rerender = useRerender();
|
||||
|
||||
//Close tail windows by their pid.
|
||||
const closePid = useCallback(
|
||||
(pid: number) => {
|
||||
logs = logs.filter((log) => log.script.pid !== pid);
|
||||
rerender();
|
||||
},
|
||||
[rerender],
|
||||
);
|
||||
|
||||
useEffect(
|
||||
() =>
|
||||
LogBoxEvents.subscribe((script: RunningScript) => {
|
||||
@ -90,7 +100,7 @@ export function LogBoxManager(): React.ReactElement {
|
||||
});
|
||||
rerender();
|
||||
}),
|
||||
[],
|
||||
[rerender],
|
||||
);
|
||||
|
||||
//Event used by ns.closeTail to close tail windows
|
||||
@ -99,14 +109,16 @@ export function LogBoxManager(): React.ReactElement {
|
||||
LogBoxCloserEvents.subscribe((pid: number) => {
|
||||
closePid(pid);
|
||||
}),
|
||||
[],
|
||||
[closePid],
|
||||
);
|
||||
|
||||
useEffect(() =>
|
||||
LogBoxClearEvents.subscribe(() => {
|
||||
logs = [];
|
||||
rerender();
|
||||
}),
|
||||
useEffect(
|
||||
() =>
|
||||
LogBoxClearEvents.subscribe(() => {
|
||||
logs = [];
|
||||
rerender();
|
||||
}),
|
||||
[rerender],
|
||||
);
|
||||
|
||||
//Close tail windows by their id
|
||||
@ -115,12 +127,6 @@ export function LogBoxManager(): React.ReactElement {
|
||||
rerender();
|
||||
}
|
||||
|
||||
//Close tail windows by their pid.
|
||||
function closePid(pid: number): void {
|
||||
logs = logs.filter((log) => log.script.pid !== pid);
|
||||
rerender();
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{logs.map((log) => (
|
||||
@ -130,7 +136,7 @@ export function LogBoxManager(): React.ReactElement {
|
||||
);
|
||||
}
|
||||
|
||||
interface IProps {
|
||||
interface LogWindowProps {
|
||||
script: RunningScript;
|
||||
onClose: () => void;
|
||||
}
|
||||
@ -158,7 +164,7 @@ const useStyles = makeStyles(() =>
|
||||
|
||||
export const logBoxBaseZIndex = 1500;
|
||||
|
||||
function LogWindow(props: IProps): React.ReactElement {
|
||||
function LogWindow(props: LogWindowProps): React.ReactElement {
|
||||
const draggableRef = useRef<HTMLDivElement>(null);
|
||||
const rootRef = useRef<Draggable>(null);
|
||||
const script = props.script;
|
||||
@ -187,10 +193,18 @@ function LogWindow(props: IProps): React.ReactElement {
|
||||
propsRef.current.setSize(size.width, size.height);
|
||||
};
|
||||
|
||||
const updateLayer = useCallback(() => {
|
||||
const c = container.current;
|
||||
if (c === null) return;
|
||||
c.style.zIndex = logBoxBaseZIndex + layerCounter + "";
|
||||
layerCounter++;
|
||||
rerender();
|
||||
}, [rerender]);
|
||||
|
||||
useEffect(() => {
|
||||
propsRef.current.updateDOM();
|
||||
updateLayer();
|
||||
}, []);
|
||||
}, [updateLayer]);
|
||||
|
||||
function kill(): void {
|
||||
killWorkerScriptByPid(script.pid);
|
||||
@ -226,14 +240,6 @@ function LogWindow(props: IProps): React.ReactElement {
|
||||
}
|
||||
}
|
||||
|
||||
function updateLayer(): void {
|
||||
const c = container.current;
|
||||
if (c === null) return;
|
||||
c.style.zIndex = logBoxBaseZIndex + layerCounter + "";
|
||||
layerCounter++;
|
||||
rerender();
|
||||
}
|
||||
|
||||
function title(): React.ReactElement {
|
||||
const title_str = script.title === "string" ? script.title : `${script.filename} ${script.args.join(" ")}`;
|
||||
return (
|
||||
@ -267,22 +273,26 @@ function LogWindow(props: IProps): React.ReactElement {
|
||||
return "primary";
|
||||
}
|
||||
|
||||
const onWindowResize = useMemo(
|
||||
() =>
|
||||
debounce((): void => {
|
||||
const node = draggableRef.current;
|
||||
if (!node) return;
|
||||
|
||||
if (!isOnScreen(node)) {
|
||||
propsRef.current.setPosition(0, 0);
|
||||
}
|
||||
}, 100),
|
||||
[],
|
||||
);
|
||||
|
||||
// And trigger fakeDrag when the window is resized
|
||||
useEffect(() => {
|
||||
window.addEventListener("resize", onWindowResize);
|
||||
return () => {
|
||||
window.removeEventListener("resize", onWindowResize);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const onWindowResize = debounce((): void => {
|
||||
const node = draggableRef.current;
|
||||
if (!node) return;
|
||||
|
||||
if (!isOnScreen(node)) {
|
||||
propsRef.current.setPosition(0, 0);
|
||||
}
|
||||
}, 100);
|
||||
}, [onWindowResize]);
|
||||
|
||||
const isOnScreen = (node: HTMLDivElement): boolean => {
|
||||
const bounds = node.getBoundingClientRect();
|
||||
|
@ -1,22 +1,22 @@
|
||||
import { FormControlLabel, Switch, Tooltip, Typography } from "@mui/material";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useState } from "react";
|
||||
|
||||
interface IProps {
|
||||
type OptionSwitchProps = {
|
||||
checked: boolean;
|
||||
onChange: (newValue: boolean, error?: string) => void;
|
||||
text: React.ReactNode;
|
||||
tooltip: React.ReactNode;
|
||||
}
|
||||
};
|
||||
|
||||
export function OptionSwitch({ checked, onChange, text, tooltip }: IProps): React.ReactElement {
|
||||
export function OptionSwitch({ checked, onChange, text, tooltip }: OptionSwitchProps): React.ReactElement {
|
||||
const [value, setValue] = useState(checked);
|
||||
|
||||
function handleSwitchChange(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||
setValue(event.target.checked);
|
||||
const newValue = event.target.checked;
|
||||
setValue(newValue);
|
||||
onChange(newValue);
|
||||
}
|
||||
|
||||
useEffect(() => onChange(value), [value]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormControlLabel
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import React, { useState, useEffect, useRef, useMemo } from "react";
|
||||
import Draggable, { DraggableEventHandler } from "react-draggable";
|
||||
import makeStyles from "@mui/styles/makeStyles";
|
||||
import Collapse from "@mui/material/Collapse";
|
||||
@ -82,8 +82,25 @@ export function Overview({ children, mode }: IProps): React.ReactElement {
|
||||
Settings.overview = { x, y, opened: open };
|
||||
}, [open, x, y]);
|
||||
|
||||
const fakeDrag = useMemo(
|
||||
() =>
|
||||
debounce((): void => {
|
||||
const node = draggableRef.current;
|
||||
if (!node) return;
|
||||
|
||||
// No official way to trigger an onChange to recompute the bounds
|
||||
// See: https://github.com/react-grid-layout/react-draggable/issues/363#issuecomment-947751127
|
||||
triggerMouseEvent(node, "mouseover");
|
||||
triggerMouseEvent(node, "mousedown");
|
||||
triggerMouseEvent(document, "mousemove");
|
||||
triggerMouseEvent(node, "mouseup");
|
||||
triggerMouseEvent(node, "click");
|
||||
}, 100),
|
||||
[],
|
||||
);
|
||||
|
||||
// Trigger fakeDrag once to make sure loaded data is not outside bounds
|
||||
useEffect(() => fakeDrag(), []);
|
||||
useEffect(() => fakeDrag(), [fakeDrag]);
|
||||
|
||||
// And trigger fakeDrag when the window is resized
|
||||
useEffect(() => {
|
||||
@ -91,20 +108,7 @@ export function Overview({ children, mode }: IProps): React.ReactElement {
|
||||
return () => {
|
||||
window.removeEventListener("resize", fakeDrag);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const fakeDrag = debounce((): void => {
|
||||
const node = draggableRef.current;
|
||||
if (!node) return;
|
||||
|
||||
// No official way to trigger an onChange to recompute the bounds
|
||||
// See: https://github.com/react-grid-layout/react-draggable/issues/363#issuecomment-947751127
|
||||
triggerMouseEvent(node, "mouseover");
|
||||
triggerMouseEvent(node, "mousedown");
|
||||
triggerMouseEvent(document, "mousemove");
|
||||
triggerMouseEvent(node, "mouseup");
|
||||
triggerMouseEvent(node, "click");
|
||||
}, 100);
|
||||
}, [fakeDrag]);
|
||||
|
||||
const triggerMouseEvent = (node: HTMLDivElement | Document, eventType: string): void => {
|
||||
const clickEvent = document.createEvent("MouseEvents");
|
||||
|
@ -1,7 +1,7 @@
|
||||
import * as React from "react";
|
||||
import LinearProgress from "@mui/material/LinearProgress";
|
||||
import { TableCell, Tooltip, Typography } from "@mui/material";
|
||||
import { characterOverviewStyles } from "./CharacterOverview";
|
||||
import { useStyles } from "./CharacterOverview";
|
||||
import { ISkillProgress } from "../../PersonObjects/formulas/skill";
|
||||
import { formatExp } from "../formatNumber";
|
||||
|
||||
@ -54,7 +54,7 @@ export function StatsProgressBar({
|
||||
}
|
||||
|
||||
export function StatsProgressOverviewCell({ progress: skill, color }: IStatsOverviewCellProps): React.ReactElement {
|
||||
const classes = characterOverviewStyles();
|
||||
const classes = useStyles();
|
||||
return (
|
||||
<TableCell
|
||||
component="th"
|
||||
|
@ -3,8 +3,7 @@ import React from "react";
|
||||
import { Typography, TableCell, TableRow } from "@mui/material";
|
||||
|
||||
import { formatExp, formatNumberNoSuffix } from "../formatNumber";
|
||||
import { characterOverviewStyles as useStyles } from "./CharacterOverview";
|
||||
import { ClassNameMap } from "@material-ui/core/styles/withStyles";
|
||||
import { useStyles } from "./CharacterOverview";
|
||||
|
||||
interface ITableRowData {
|
||||
content?: string;
|
||||
@ -15,14 +14,14 @@ interface ITableRowData {
|
||||
interface IProps {
|
||||
name: string;
|
||||
color: string;
|
||||
classes?: ClassNameMap;
|
||||
data?: ITableRowData;
|
||||
children?: React.ReactElement;
|
||||
}
|
||||
|
||||
export const StatsRow = ({ name, color, classes = useStyles(), children, data }: IProps): React.ReactElement => {
|
||||
let content = "";
|
||||
export const StatsRow = ({ name, color, children, data }: IProps): React.ReactElement => {
|
||||
const classes = useStyles();
|
||||
|
||||
let content = "";
|
||||
if (data) {
|
||||
if (data.content !== undefined) {
|
||||
content = data.content;
|
||||
@ -39,7 +38,7 @@ export const StatsRow = ({ name, color, classes = useStyles(), children, data }:
|
||||
<Typography style={{ color: color }}>{name}</Typography>
|
||||
</TableCell>
|
||||
<TableCell align="right" classes={{ root: classes.cellNone }}>
|
||||
{content ? <Typography style={{ color: color }}>{content}</Typography> : <></>}
|
||||
{content && <Typography style={{ color: color }}>{content}</Typography>}
|
||||
{children}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
@ -4,13 +4,16 @@ import { useCallback, useEffect, useState } from "react";
|
||||
* @param autoRerenderTime: Optional. If provided and nonzero, used as the ms interval to automatically call the rerender function.
|
||||
*/
|
||||
export function useRerender(autoRerenderTime?: number) {
|
||||
const setRerender = useState(false)[1];
|
||||
const rerender = () => setRerender((old) => !old);
|
||||
const [__, setRerender] = useState(false);
|
||||
|
||||
const rerender = useCallback(() => setRerender((old) => !old), []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!autoRerenderTime) return;
|
||||
const intervalID = setInterval(rerender, autoRerenderTime);
|
||||
return () => clearInterval(intervalID);
|
||||
}, []);
|
||||
}, [rerender, autoRerenderTime]);
|
||||
|
||||
return rerender;
|
||||
}
|
||||
|
||||
|
@ -17,5 +17,5 @@
|
||||
"strict": true,
|
||||
"target": "es2022"
|
||||
},
|
||||
"include": ["src/**/*", "electron/**/*", ".eslintrc.js", "node_modules/monaco-editor/monaco.d.ts"]
|
||||
"include": ["src/**/*", "electron/**/*", "node_modules/monaco-editor/monaco.d.ts"]
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user