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