Augmentations in mui

This commit is contained in:
Olivier Gagnon 2021-09-25 01:06:17 -04:00
parent 5170c0e004
commit 5c6c472b64
19 changed files with 637 additions and 574 deletions

@ -12,7 +12,7 @@ import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviv
export interface IConstructorParams { export interface IConstructorParams {
info: string | JSX.Element; info: string | JSX.Element;
stats?: JSX.Element; stats?: JSX.Element | null;
isSpecial?: boolean; isSpecial?: boolean;
moneyCost: number; moneyCost: number;
name: string; name: string;
@ -369,7 +369,7 @@ export class Augmentation {
info: string | JSX.Element; info: string | JSX.Element;
// Description of the stats, often autogenerated, sometimes manually written. // Description of the stats, often autogenerated, sometimes manually written.
stats: JSX.Element; stats: JSX.Element | null;
// Any Augmentation not immediately available in BitNode-1 is special (e.g. Bladeburner augs) // Any Augmentation not immediately available in BitNode-1 is special (e.g. Bladeburner augs)
isSpecial = false; isSpecial = false;
@ -507,8 +507,9 @@ export class Augmentation {
this.mults.bladeburner_success_chance_mult = params.bladeburner_success_chance_mult; this.mults.bladeburner_success_chance_mult = params.bladeburner_success_chance_mult;
} }
if (params.stats) this.stats = params.stats; if (params.stats === undefined)
else this.stats = generateStatsDescription(this.mults, params.programs, params.startingMoney); this.stats = generateStatsDescription(this.mults, params.programs, params.startingMoney);
else this.stats = params.stats;
} }
// Adds this Augmentation to the specified Factions // Adds this Augmentation to the specified Factions

@ -1555,7 +1555,7 @@ function initAugmentations() {
repCost: 2.5e6, repCost: 2.5e6,
moneyCost: 0, moneyCost: 0,
info: "It's time to leave the cave.", info: "It's time to leave the cave.",
stats: <></>, stats: null,
}); });
RedPill.addToFactions(["Daedalus"]); RedPill.addToFactions(["Daedalus"]);
if (augmentationExists(AugmentationNames.TheRedPill)) { if (augmentationExists(AugmentationNames.TheRedPill)) {
@ -1595,7 +1595,7 @@ function initAugmentations() {
"exactly the implant does, but they promise that it will greatly " + "exactly the implant does, but they promise that it will greatly " +
"enhance your abilities.", "enhance your abilities.",
hacking_grow_mult: 3, hacking_grow_mult: 3,
stats: <></>, stats: null,
}); });
HiveMind.addToFactions(["ECorp"]); HiveMind.addToFactions(["ECorp"]);
if (augmentationExists(AugmentationNames.HiveMind)) { if (augmentationExists(AugmentationNames.HiveMind)) {

@ -2,92 +2,78 @@
* Root React component for the Augmentations UI page that display all of your * Root React component for the Augmentations UI page that display all of your
* owned and purchased Augmentations and Source-Files. * owned and purchased Augmentations and Source-Files.
*/ */
import * as React from "react"; import React, { useState } from "react";
import { InstalledAugmentationsAndSourceFiles } from "./InstalledAugmentationsAndSourceFiles"; import { InstalledAugmentations } from "./InstalledAugmentations";
import { PlayerMultipliers } from "./PlayerMultipliers"; import { PlayerMultipliers } from "./PlayerMultipliers";
import { PurchasedAugmentations } from "./PurchasedAugmentations"; import { PurchasedAugmentations } from "./PurchasedAugmentations";
import { SourceFiles } from "./SourceFiles";
import { Player } from "../../Player"; import { Player } from "../../Player";
import { StdButton } from "../../ui/React/StdButton"; import { StdButton } from "../../ui/React/StdButton";
import { canGetBonus } from "../../ExportBonus"; import { canGetBonus } from "../../ExportBonus";
type IProps = { import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
import Tooltip from "@mui/material/Tooltip";
interface IProps {
exportGameFn: () => void; exportGameFn: () => void;
installAugmentationsFn: () => void; installAugmentationsFn: () => void;
}; }
type IState = { export function AugmentationsRoot(props: IProps): React.ReactElement {
rerender: boolean; const setRerender = useState(false)[1];
};
function doExport(): void {
export class AugmentationsRoot extends React.Component<IProps, IState> { props.exportGameFn();
constructor(props: IProps) { setRerender((o) => !o);
super(props); }
this.state = {
rerender: false, function exportBonusStr(): string {
}; if (canGetBonus()) return "(+1 favor to all factions)";
this.export = this.export.bind(this); return "";
} }
export(): void { return (
this.props.exportGameFn(); <>
this.setState({ <Typography variant="h4">Augmentations</Typography>
rerender: !this.state.rerender, <Typography>
}); Below is a list of all Augmentations you have purchased but not yet installed. Click the button below to install
} them.
</Typography>
render(): React.ReactNode { <Typography>WARNING: Installing your Augmentations resets most of your progress, including:</Typography>
function exportBonusStr(): string { <br />
if (canGetBonus()) return "(+1 favor to all factions)"; <Typography>- Stats/Skill levels and Experience</Typography>
return ""; <Typography>- Money</Typography>
} <Typography>- Scripts on every computer but your home computer</Typography>
<Typography>- Purchased servers</Typography>
return ( <Typography>- Hacknet Nodes</Typography>
<> <Typography>- Faction/Company reputation</Typography>
<div className="augmentations-content"> <Typography>- Stocks</Typography>
<h1>Purchased Augmentations</h1> <br />
<p> <Typography>
Below is a list of all Augmentations you have purchased but not yet installed. Click the button below to Installing Augmentations lets you start over with the perks and benefits granted by all of the Augmentations you
install them. have ever installed. Also, you will keep any scripts and RAM/Core upgrades on your home computer (but you will
</p> lose all programs besides NUKE.exe)
<p>WARNING: Installing your Augmentations resets most of your progress, including:</p> </Typography>
<br /> <Tooltip title={"'I never asked for this'"}>
<p>- Stats/Skill levels and Experience</p> <Button onClick={props.installAugmentationsFn}>
<p>- Money</p> <Typography>Install Augmentations</Typography>
<p>- Scripts on every computer but your home computer</p> </Button>
<p>- Purchased servers</p> </Tooltip>
<p>- Hacknet Nodes</p> <Tooltip title={"It's always a good idea to backup/export your save!"}>
<p>- Faction/Company reputation</p> <Button sx={{ mx: 2 }} onClick={doExport}>
<p>- Stocks</p> <Typography color="error">Backup Save {exportBonusStr()}</Typography>
<br /> </Button>
<p> </Tooltip>
Installing Augmentations lets you start over with the perks and benefits granted by all of the Augmentations <PurchasedAugmentations />
you have ever installed. Also, you will keep any scripts and RAM/Core upgrades on your home computer (but <Typography variant="h4">Installed Augmentations</Typography>
you will lose all programs besides NUKE.exe) <Typography>List of all Augmentations that have been installed. You have gained the effects of these.</Typography>
</p> <InstalledAugmentations />
<StdButton <br /> <br />
onClick={this.props.installAugmentationsFn} <PlayerMultipliers />
text="Install Augmentations" <SourceFiles />
tooltip="'I never asked for this'" </>
/> );
<StdButton
addClasses="flashing-button"
onClick={this.export}
text={`Backup Save ${exportBonusStr()}`}
tooltip="It's always a good idea to backup/export your save!"
/>
<PurchasedAugmentations />
<h1>Installed Augmentations</h1>
<p>
{`List of all Augmentations ${Player.sourceFiles.length > 0 ? "and Source Files " : ""} ` +
`that have been installed. You have gained the effects of these.`}
</p>
<InstalledAugmentationsAndSourceFiles />
<br /> <br />
<PlayerMultipliers />
</div>
</>
);
}
} }

@ -1,19 +1,31 @@
/** /**
* React Component for displaying a list of the player's installed Augmentations * React Component for displaying all of the player's installed Augmentations and
* on the Augmentations UI * Source-Files.
*
* It also contains 'configuration' buttons that allow you to change how the
* Augs/SF's are displayed
*/ */
import * as React from "react"; import React, { useState } from "react";
import { Player } from "../../Player"; import { OwnedSourceFiles } from "./OwnedSourceFiles";
import { SourceFileMinus1 } from "./SourceFileMinus1";
import { AugmentationAccordion } from "../../ui/React/AugmentationAccordion";
import { Augmentations } from "../../Augmentation/Augmentations"; import { Augmentations } from "../../Augmentation/Augmentations";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames"; import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { Settings } from "../../Settings/Settings";
import { OwnedAugmentationsOrderSetting } from "../../Settings/SettingEnums";
import { AugmentationAccordion } from "../../ui/React/AugmentationAccordion"; import { Settings } from "../../Settings/Settings";
import { use } from "../../ui/Context";
import { OwnedAugmentationsOrderSetting } from "../../Settings/SettingEnums";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
import Tooltip from "@mui/material/Tooltip";
import List from "@mui/material/List";
export function InstalledAugmentations(): React.ReactElement { export function InstalledAugmentations(): React.ReactElement {
const sourceAugs = Player.augmentations.slice(); const setRerender = useState(true)[1];
const player = use.Player();
const sourceAugs = player.augmentations.slice();
if (Settings.OwnedAugmentationsOrder === OwnedAugmentationsOrderSetting.Alphabetically) { if (Settings.OwnedAugmentationsOrder === OwnedAugmentationsOrderSetting.Alphabetically) {
sourceAugs.sort((aug1, aug2) => { sourceAugs.sort((aug1, aug2) => {
@ -21,20 +33,42 @@ export function InstalledAugmentations(): React.ReactElement {
}); });
} }
const augs = sourceAugs.map((e) => { function rerender(): void {
const aug = Augmentations[e.name]; setRerender((old) => !old);
}
let level = null; function sortByAcquirementTime(): void {
if (e.name === AugmentationNames.NeuroFluxGovernor) { Settings.OwnedAugmentationsOrder = OwnedAugmentationsOrderSetting.AcquirementTime;
level = e.level; rerender();
} }
return ( function sortInOrder(): void {
<li key={e.name}> Settings.OwnedAugmentationsOrder = OwnedAugmentationsOrderSetting.Alphabetically;
<AugmentationAccordion aug={aug} level={level} /> rerender();
</li> }
);
});
return <>{augs}</>; return (
<>
<Tooltip title={"Sorts the Augmentations alphabetically in numeral order"}>
<Button onClick={sortInOrder}>Sort in Order</Button>
</Tooltip>
<Tooltip title={"Sorts the Augmentations based on when you acquired them (same as default)"}>
<Button sx={{ mx: 2 }} onClick={sortByAcquirementTime}>
Sort by Acquirement Time
</Button>
</Tooltip>
<List dense>
{sourceAugs.map((e) => {
const aug = Augmentations[e.name];
let level = null;
if (e.name === AugmentationNames.NeuroFluxGovernor) {
level = e.level;
}
return <AugmentationAccordion key={aug.name} aug={aug} level={level} />;
})}
</List>
</>
);
} }

@ -1,115 +0,0 @@
/**
* React Component for displaying all of the player's installed Augmentations and
* Source-Files.
*
* It also contains 'configuration' buttons that allow you to change how the
* Augs/SF's are displayed
*/
import * as React from "react";
import { InstalledAugmentations } from "./InstalledAugmentations";
import { ListConfiguration } from "./ListConfiguration";
import { OwnedSourceFiles } from "./OwnedSourceFiles";
import { SourceFileMinus1 } from "./SourceFileMinus1";
import { Settings } from "../../Settings/Settings";
import { OwnedAugmentationsOrderSetting } from "../../Settings/SettingEnums";
type IProps = {
// nothing special.
};
type IState = {
rerenderFlag: boolean;
};
export class InstalledAugmentationsAndSourceFiles extends React.Component<IProps, IState> {
listRef: React.RefObject<HTMLUListElement>;
constructor(props: IProps) {
super(props);
this.state = {
rerenderFlag: false,
};
this.collapseAllHeaders = this.collapseAllHeaders.bind(this);
this.expandAllHeaders = this.expandAllHeaders.bind(this);
this.sortByAcquirementTime = this.sortByAcquirementTime.bind(this);
this.sortInOrder = this.sortInOrder.bind(this);
this.listRef = React.createRef();
}
collapseAllHeaders(): void {
const ul = this.listRef.current;
if (ul == null) {
return;
}
const tickers = ul.getElementsByClassName("accordion-header");
for (let i = 0; i < tickers.length; ++i) {
const ticker = tickers[i];
if (!(ticker instanceof HTMLButtonElement)) {
continue;
}
if (ticker.classList.contains("active")) {
ticker.click();
}
}
}
expandAllHeaders(): void {
const ul = this.listRef.current;
if (ul == null) {
return;
}
const tickers = ul.getElementsByClassName("accordion-header");
for (let i = 0; i < tickers.length; ++i) {
const ticker = tickers[i];
if (!(ticker instanceof HTMLButtonElement)) {
continue;
}
if (!ticker.classList.contains("active")) {
ticker.click();
}
}
}
rerender(): void {
this.setState((prevState) => {
return {
rerenderFlag: !prevState.rerenderFlag,
};
});
}
sortByAcquirementTime(): void {
Settings.OwnedAugmentationsOrder = OwnedAugmentationsOrderSetting.AcquirementTime;
this.rerender();
}
sortInOrder(): void {
Settings.OwnedAugmentationsOrder = OwnedAugmentationsOrderSetting.Alphabetically;
this.rerender();
}
render(): React.ReactNode {
return (
<>
<ListConfiguration
collapseAllButtonsFn={this.collapseAllHeaders}
expandAllButtonsFn={this.expandAllHeaders}
sortByAcquirementTimeFn={this.sortByAcquirementTime}
sortInOrderFn={this.sortInOrder}
/>
<ul className="augmentations-list" ref={this.listRef}>
<SourceFileMinus1 />
<OwnedSourceFiles />
<InstalledAugmentations />
</ul>
</>
);
}
}

@ -1,33 +0,0 @@
/**
* React Component for configuring the way installed augmentations and
* Source-Files are displayed in the Augmentations UI
*/
import * as React from "react";
import { StdButton } from "../../ui/React/StdButton";
type IProps = {
collapseAllButtonsFn: () => void;
expandAllButtonsFn: () => void;
sortByAcquirementTimeFn: () => void;
sortInOrderFn: () => void;
};
export function ListConfiguration(props: IProps): React.ReactElement {
return (
<>
<StdButton onClick={props.expandAllButtonsFn} text="Expand All" />
<StdButton onClick={props.collapseAllButtonsFn} text="Collapse All" />
<StdButton
onClick={props.sortInOrderFn}
text="Sort in Order"
tooltip="Sorts the Augmentations alphabetically and Source-Files in numeral order"
/>
<StdButton
onClick={props.sortByAcquirementTimeFn}
text="Sort by Acquirement Time"
tooltip="Sorts the Augmentations and Source-Files based on when you acquired them (same as default)"
/>
</>
);
}

@ -20,20 +20,18 @@ export function OwnedSourceFiles(): React.ReactElement {
}); });
} }
const sfs = sourceSfs.map((e) => { return (
const srcFileKey = "SourceFile" + e.n; <>
const sfObj = SourceFiles[srcFileKey]; {sourceSfs.map((e) => {
if (sfObj == null) { const srcFileKey = "SourceFile" + e.n;
console.error(`Invalid source file number: ${e.n}`); const sfObj = SourceFiles[srcFileKey];
return null; if (sfObj == null) {
} console.error(`Invalid source file number: ${e.n}`);
return null;
}
return ( return <SourceFileAccordion key={e.n} level={e.lvl} sf={sfObj} />;
<li key={e.n}> })}
<SourceFileAccordion level={e.lvl} sf={sfObj} /> </>
</li> );
);
});
return <>{sfs}</>;
} }

@ -6,6 +6,11 @@ import * as React from "react";
import { Player } from "../../Player"; import { Player } from "../../Player";
import { numeralWrapper } from "../../ui/numeralFormat"; import { numeralWrapper } from "../../ui/numeralFormat";
import { Augmentations } from "../Augmentations"; import { Augmentations } from "../Augmentations";
import { Table, TableCell } from "../../ui/React/Table";
import TableBody from "@mui/material/TableBody";
import { Table as MuiTable } from "@mui/material";
import TableRow from "@mui/material/TableRow";
import Typography from "@mui/material/Typography";
function calculateAugmentedStats(): any { function calculateAugmentedStats(): any {
const augP: any = {}; const augP: any = {};
@ -19,62 +24,74 @@ function calculateAugmentedStats(): any {
return augP; return augP;
} }
export function PlayerMultipliers(): React.ReactElement { function Improvements({ r }: { r: number }): React.ReactElement {
const mults = calculateAugmentedStats(); if (r) {
function MultiplierTable(rows: any[]): React.ReactElement { console.log(r);
function improvements(r: number): JSX.Element[] {
let elems: JSX.Element[] = [];
if (r) {
elems = [<td key="2">&nbsp;{"=>"}&nbsp;</td>, <td key="3">{numeralWrapper.formatPercentage(r)}</td>];
}
return elems;
}
return ( return (
<table> <>
<tbody> <TableCell key="2">
{rows.map((r: any) => ( <Typography>&nbsp;{"=>"}&nbsp;</Typography>
<tr key={r[0]}> </TableCell>
<td key="0"> <TableCell key="3">
<span>{r[0]} multiplier:&nbsp;</span> <Typography>{numeralWrapper.formatPercentage(r)}</Typography>
</td> </TableCell>
<td key="1" style={{ textAlign: "right" }}> </>
{numeralWrapper.formatPercentage(r[1])}
</td>
{improvements(r[2])}
</tr>
))}
</tbody>
</table>
); );
} }
return <></>;
}
function MultiplierTable({ rows }: { rows: [string, number, number][] }): React.ReactElement {
return (
<Table size="small" padding="none">
<TableBody>
{rows.map((r: any) => (
<TableRow key={r[0]}>
<TableCell key="0">
<Typography noWrap>{r[0]} multiplier:&nbsp;</Typography>
</TableCell>
<TableCell key="1" style={{ textAlign: "right" }}>
<Typography noWrap>{numeralWrapper.formatPercentage(r[1])}</Typography>
</TableCell>
<Improvements r={r[2]} />
</TableRow>
))}
</TableBody>
</Table>
);
}
export function PlayerMultipliers(): React.ReactElement {
const mults = calculateAugmentedStats();
function BladeburnerMults(): React.ReactElement { function BladeburnerMults(): React.ReactElement {
if (!Player.canAccessBladeburner()) return <></>; if (!Player.canAccessBladeburner()) return <></>;
return ( return (
<> <>
{MultiplierTable([ <MultiplierTable
[ rows={[
"Bladeburner Success Chance", [
Player.bladeburner_success_chance_mult, "Bladeburner Success Chance",
Player.bladeburner_success_chance_mult * mults.bladeburner_success_chance_mult, Player.bladeburner_success_chance_mult,
], Player.bladeburner_success_chance_mult * mults.bladeburner_success_chance_mult,
[ ],
"Bladeburner Max Stamina", [
Player.bladeburner_max_stamina_mult, "Bladeburner Max Stamina",
Player.bladeburner_max_stamina_mult * mults.bladeburner_max_stamina_mult, Player.bladeburner_max_stamina_mult,
], Player.bladeburner_max_stamina_mult * mults.bladeburner_max_stamina_mult,
[ ],
"Bladeburner Stamina Gain", [
Player.bladeburner_stamina_gain_mult, "Bladeburner Stamina Gain",
Player.bladeburner_stamina_gain_mult * mults.bladeburner_stamina_gain_mult, Player.bladeburner_stamina_gain_mult,
], Player.bladeburner_stamina_gain_mult * mults.bladeburner_stamina_gain_mult,
[ ],
"Bladeburner Field Analysis", [
Player.bladeburner_analysis_mult, "Bladeburner Field Analysis",
Player.bladeburner_analysis_mult * mults.bladeburner_analysis_mult, Player.bladeburner_analysis_mult,
], Player.bladeburner_analysis_mult * mults.bladeburner_analysis_mult,
])} ],
]}
/>
<br /> <br />
</> </>
); );
@ -88,90 +105,110 @@ export function PlayerMultipliers(): React.ReactElement {
</strong> </strong>
</p> </p>
<br /> <br />
{MultiplierTable([ <MultiplierTable
["Hacking Chance ", Player.hacking_chance_mult, Player.hacking_chance_mult * mults.hacking_chance_mult], rows={[
["Hacking Speed ", Player.hacking_speed_mult, Player.hacking_speed_mult * mults.hacking_speed_mult], ["Hacking Chance ", Player.hacking_chance_mult, Player.hacking_chance_mult * mults.hacking_chance_mult],
["Hacking Money ", Player.hacking_money_mult, Player.hacking_money_mult * mults.hacking_money_mult], ["Hacking Speed ", Player.hacking_speed_mult, Player.hacking_speed_mult * mults.hacking_speed_mult],
["Hacking Growth ", Player.hacking_grow_mult, Player.hacking_grow_mult * mults.hacking_grow_mult], ["Hacking Money ", Player.hacking_money_mult, Player.hacking_money_mult * mults.hacking_money_mult],
])} ["Hacking Growth ", Player.hacking_grow_mult, Player.hacking_grow_mult * mults.hacking_grow_mult],
]}
/>
<br /> <br />
{MultiplierTable([ <MultiplierTable
["Hacking Level ", Player.hacking_mult, Player.hacking_mult * mults.hacking_mult], rows={[
["Hacking Experience ", Player.hacking_exp_mult, Player.hacking_exp_mult * mults.hacking_exp_mult], ["Hacking Level ", Player.hacking_mult, Player.hacking_mult * mults.hacking_mult],
])} ["Hacking Experience ", Player.hacking_exp_mult, Player.hacking_exp_mult * mults.hacking_exp_mult],
]}
/>
<br /> <br />
{MultiplierTable([ <MultiplierTable
["Strength Level ", Player.strength_mult, Player.strength_mult * mults.strength_mult], rows={[
["Strength Experience ", Player.strength_exp_mult, Player.strength_exp_mult * mults.strength_exp_mult], ["Strength Level ", Player.strength_mult, Player.strength_mult * mults.strength_mult],
])} ["Strength Experience ", Player.strength_exp_mult, Player.strength_exp_mult * mults.strength_exp_mult],
]}
/>
<br /> <br />
{MultiplierTable([ <MultiplierTable
["Defense Level ", Player.defense_mult, Player.defense_mult * mults.defense_mult], rows={[
["Defense Experience ", Player.defense_exp_mult, Player.defense_exp_mult * mults.defense_exp_mult], ["Defense Level ", Player.defense_mult, Player.defense_mult * mults.defense_mult],
])} ["Defense Experience ", Player.defense_exp_mult, Player.defense_exp_mult * mults.defense_exp_mult],
]}
/>
<br /> <br />
{MultiplierTable([ <MultiplierTable
["Dexterity Level ", Player.dexterity_mult, Player.dexterity_mult * mults.dexterity_mult], rows={[
["Dexterity Experience ", Player.dexterity_exp_mult, Player.dexterity_exp_mult * mults.dexterity_exp_mult], ["Dexterity Level ", Player.dexterity_mult, Player.dexterity_mult * mults.dexterity_mult],
])} ["Dexterity Experience ", Player.dexterity_exp_mult, Player.dexterity_exp_mult * mults.dexterity_exp_mult],
]}
/>
<br /> <br />
{MultiplierTable([ <MultiplierTable
["Agility Level ", Player.agility_mult, Player.agility_mult * mults.agility_mult], rows={[
["Agility Experience ", Player.agility_exp_mult, Player.agility_exp_mult * mults.agility_exp_mult], ["Agility Level ", Player.agility_mult, Player.agility_mult * mults.agility_mult],
])} ["Agility Experience ", Player.agility_exp_mult, Player.agility_exp_mult * mults.agility_exp_mult],
]}
/>
<br /> <br />
{MultiplierTable([ <MultiplierTable
["Charisma Level ", Player.charisma_mult, Player.charisma_mult * mults.charisma_mult], rows={[
["Charisma Experience ", Player.charisma_exp_mult, Player.charisma_exp_mult * mults.charisma_exp_mult], ["Charisma Level ", Player.charisma_mult, Player.charisma_mult * mults.charisma_mult],
])} ["Charisma Experience ", Player.charisma_exp_mult, Player.charisma_exp_mult * mults.charisma_exp_mult],
]}
/>
<br /> <br />
{MultiplierTable([ <MultiplierTable
[ rows={[
"Hacknet Node production ", [
Player.hacknet_node_money_mult, "Hacknet Node production ",
Player.hacknet_node_money_mult * mults.hacknet_node_money_mult, Player.hacknet_node_money_mult,
], Player.hacknet_node_money_mult * mults.hacknet_node_money_mult,
[ ],
"Hacknet Node purchase cost ", [
Player.hacknet_node_purchase_cost_mult, "Hacknet Node purchase cost ",
Player.hacknet_node_purchase_cost_mult * mults.hacknet_node_purchase_cost_mult, Player.hacknet_node_purchase_cost_mult,
], Player.hacknet_node_purchase_cost_mult * mults.hacknet_node_purchase_cost_mult,
[ ],
"Hacknet Node RAM upgrade cost ", [
Player.hacknet_node_ram_cost_mult, "Hacknet Node RAM upgrade cost ",
Player.hacknet_node_ram_cost_mult * mults.hacknet_node_ram_cost_mult, Player.hacknet_node_ram_cost_mult,
], Player.hacknet_node_ram_cost_mult * mults.hacknet_node_ram_cost_mult,
[ ],
"Hacknet Node Core purchase cost ", [
Player.hacknet_node_core_cost_mult, "Hacknet Node Core purchase cost ",
Player.hacknet_node_core_cost_mult * mults.hacknet_node_core_cost_mult, Player.hacknet_node_core_cost_mult,
], Player.hacknet_node_core_cost_mult * mults.hacknet_node_core_cost_mult,
[ ],
"Hacknet Node level upgrade cost ", [
Player.hacknet_node_level_cost_mult, "Hacknet Node level upgrade cost ",
Player.hacknet_node_level_cost_mult * mults.hacknet_node_level_cost_mult, Player.hacknet_node_level_cost_mult,
], Player.hacknet_node_level_cost_mult * mults.hacknet_node_level_cost_mult,
])} ],
]}
/>
<br /> <br />
{MultiplierTable([ <MultiplierTable
["Company reputation gain ", Player.company_rep_mult, Player.company_rep_mult * mults.company_rep_mult], rows={[
["Faction reputation gain ", Player.faction_rep_mult, Player.faction_rep_mult * mults.faction_rep_mult], ["Company reputation gain ", Player.company_rep_mult, Player.company_rep_mult * mults.company_rep_mult],
["Salary ", Player.work_money_mult, Player.work_money_mult * mults.work_money_mult], ["Faction reputation gain ", Player.faction_rep_mult, Player.faction_rep_mult * mults.faction_rep_mult],
])} ["Salary ", Player.work_money_mult, Player.work_money_mult * mults.work_money_mult],
]}
/>
<br /> <br />
{MultiplierTable([ <MultiplierTable
["Crime success ", Player.crime_success_mult, Player.crime_success_mult * mults.crime_success_mult], rows={[
["Crime money ", Player.crime_money_mult, Player.crime_money_mult * mults.crime_money_mult], ["Crime success ", Player.crime_success_mult, Player.crime_success_mult * mults.crime_success_mult],
])} ["Crime money ", Player.crime_money_mult, Player.crime_money_mult * mults.crime_money_mult],
]}
/>
<br /> <br />
<BladeburnerMults /> <BladeburnerMults />

@ -9,6 +9,7 @@ import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { Player } from "../../Player"; import { Player } from "../../Player";
import { AugmentationAccordion } from "../../ui/React/AugmentationAccordion"; import { AugmentationAccordion } from "../../ui/React/AugmentationAccordion";
import List from "@mui/material/List";
export function PurchasedAugmentations(): React.ReactElement { export function PurchasedAugmentations(): React.ReactElement {
const augs: React.ReactElement[] = []; const augs: React.ReactElement[] = [];
@ -29,12 +30,8 @@ export function PurchasedAugmentations(): React.ReactElement {
level = ownedAug.level; level = ownedAug.level;
} }
augs.push( augs.push(<AugmentationAccordion key={aug.name} aug={aug} level={level} />);
<li key={`${ownedAug.name}${ownedAug.level}`}>
<AugmentationAccordion aug={aug} level={level} />
</li>,
);
} }
return <ul className="augmentations-list">{augs}</ul>; return <List dense>{augs}</List>;
} }

@ -2,14 +2,22 @@
* React Component for displaying a list of the player's Source-Files * React Component for displaying a list of the player's Source-Files
* on the Augmentations UI * on the Augmentations UI
*/ */
import * as React from "react"; import React, { useState } from "react";
import { Player } from "../../Player"; import { Player } from "../../Player";
import { Exploit, ExploitName } from "../../Exploits/Exploit"; import { Exploit, ExploitName } from "../../Exploits/Exploit";
import { BBAccordion } from "../../ui/React/BBAccordion"; import ListItemButton from "@mui/material/ListItemButton";
import ListItemText from "@mui/material/ListItemText";
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
import Paper from "@mui/material/Paper";
import Collapse from "@mui/material/Collapse";
import ExpandMore from "@mui/icons-material/ExpandMore";
import ExpandLess from "@mui/icons-material/ExpandLess";
export function SourceFileMinus1(): React.ReactElement { export function SourceFileMinus1(): React.ReactElement {
const [open, setOpen] = useState(false);
const exploits = Player.exploits; const exploits = Player.exploits;
if (exploits.length === 0) { if (exploits.length === 0) {
@ -17,33 +25,35 @@ export function SourceFileMinus1(): React.ReactElement {
} }
return ( return (
<li key={-1}> <Box component={Paper}>
<BBAccordion <ListItemButton onClick={() => setOpen((old) => !old)}>
headerContent={ <ListItemText
<> primary={
Source-File -1: Exploits in the BitNodes <Typography style={{ whiteSpace: "pre-wrap" }}>
<br /> Source-File -1: Exploits in the BitNodes
Level {exploits.length} / ? <br />
</> Level {exploits.length} / ?
} </Typography>
panelContent={ }
<> />
<p> {open ? <ExpandLess color="primary" /> : <ExpandMore color="primary" />}
This Source-File can only be acquired with obscure knowledge of the game, javascript, and the web </ListItemButton>
ecosystem. <Collapse in={open} unmountOnExit>
</p> <Box m={4}>
<p>It increases all of the player's multipliers by 0.1%</p> <Typography>
<br /> This Source-File can only be acquired with obscure knowledge of the game, javascript, and the web ecosystem.
</Typography>
<Typography>It increases all of the player's multipliers by 0.1%</Typography>
<br />
<p>You have found the following exploits:</p> <Typography>You have found the following exploits:</Typography>
<ul> <Box mx={2}>
{exploits.map((c: Exploit) => ( {exploits.map((c: Exploit) => (
<li key={c}>* {ExploitName(c)}</li> <Typography key={c}>* {ExploitName(c)}</Typography>
))} ))}
</ul> </Box>
</> </Box>
} </Collapse>
/> </Box>
</li>
); );
} }

@ -0,0 +1,20 @@
import React from "react";
import { use } from "../../ui/Context";
import { SourceFileMinus1 } from "./SourceFileMinus1";
import { OwnedSourceFiles } from "./OwnedSourceFiles";
import List from "@mui/material/List";
import Typography from "@mui/material/Typography";
export function SourceFiles(): React.ReactElement {
const player = use.Player();
return (
<>
<Typography variant="h4">Source Files</Typography>
<List dense>
<SourceFileMinus1 />
<OwnedSourceFiles />
</List>
</>
);
}

@ -47,7 +47,7 @@ export function SleeveAugmentationsPopup(props: IProps): React.ReactElement {
tooltip = renderToStaticMarkup(tooltip); tooltip = renderToStaticMarkup(tooltip);
} }
tooltip += "<br /><br />"; tooltip += "<br /><br />";
tooltip += renderToStaticMarkup(aug.stats); tooltip += renderToStaticMarkup(aug.stats || <></>);
return ( return (
<div key={augName} className="gang-owned-upgrade tooltip"> <div key={augName} className="gang-owned-upgrade tooltip">
{augName} {augName}
@ -70,7 +70,7 @@ export function SleeveAugmentationsPopup(props: IProps): React.ReactElement {
info = renderToStaticMarkup(info); info = renderToStaticMarkup(info);
} }
info += "<br /><br />"; info += "<br /><br />";
info += renderToStaticMarkup(aug.stats); info += renderToStaticMarkup(aug.stats || <></>);
return ( return (
<div key={aug.name} className="cmpy-mgmt-upgrade-div" onClick={() => purchaseAugmentation(aug)}> <div key={aug.name} className="cmpy-mgmt-upgrade-div" onClick={() => purchaseAugmentation(aug)}>

@ -91,16 +91,11 @@ export class Script {
* @param {string} code - The new contents of the script * @param {string} code - The new contents of the script
* @param {Script[]} otherScripts - Other scripts on the server. Used to process imports * @param {Script[]} otherScripts - Other scripts on the server. Used to process imports
*/ */
saveScript(code: string, serverIp: string, otherScripts: Script[]): void { saveScript(filename: string, code: string, serverIp: string, otherScripts: Script[]): void {
// Update code and filename // Update code and filename
this.code = code.replace(/^\s+|\s+$/g, ""); this.code = code.replace(/^\s+|\s+$/g, "");
const filenameElem: HTMLInputElement | null = document.getElementById("script-editor-filename") as HTMLInputElement; this.filename = filename;
if (filenameElem == null) {
console.error(`Failed to get Script filename DOM element`);
return;
}
this.filename = filenameElem.value;
this.server = serverIp; this.server = serverIp;
this.updateRamUsage(otherScripts); this.updateRamUsage(otherScripts);
this.markUpdated(); this.markUpdated();

@ -130,14 +130,14 @@ export function Root(props: IProps): React.ReactElement {
let found = false; let found = false;
for (let i = 0; i < server.scripts.length; i++) { for (let i = 0; i < server.scripts.length; i++) {
if (filename == server.scripts[i].filename) { if (filename == server.scripts[i].filename) {
server.scripts[i].saveScript(code, props.player.currentServer, server.scripts); server.scripts[i].saveScript(filename, code, props.player.currentServer, server.scripts);
found = true; found = true;
} }
} }
if (!found) { if (!found) {
const script = new Script(); const script = new Script();
script.saveScript(code, props.player.currentServer, server.scripts); script.saveScript(filename, code, props.player.currentServer, server.scripts);
server.scripts.push(script); server.scripts.push(script);
} }
@ -165,7 +165,7 @@ export function Root(props: IProps): React.ReactElement {
//If the current script already exists on the server, overwrite it //If the current script already exists on the server, overwrite it
for (let i = 0; i < server.scripts.length; i++) { for (let i = 0; i < server.scripts.length; i++) {
if (filename == server.scripts[i].filename) { if (filename == server.scripts[i].filename) {
server.scripts[i].saveScript(code, props.player.currentServer, server.scripts); server.scripts[i].saveScript(filename, code, props.player.currentServer, server.scripts);
props.router.toTerminal(); props.router.toTerminal();
return; return;
} }
@ -173,7 +173,7 @@ export function Root(props: IProps): React.ReactElement {
//If the current script does NOT exist, create a new one //If the current script does NOT exist, create a new one
const script = new Script(); const script = new Script();
script.saveScript(code, props.player.currentServer, server.scripts); script.saveScript(filename, code, props.player.currentServer, server.scripts);
server.scripts.push(script); server.scripts.push(script);
} else if (filename.endsWith(".txt")) { } else if (filename.endsWith(".txt")) {
for (let i = 0; i < server.textFiles.length; ++i) { for (let i = 0; i < server.textFiles.length; ++i) {

@ -1,13 +1,13 @@
import { BitNodes } from "../BitNode/BitNode"; import { BitNodes } from "../BitNode/BitNode";
export class SourceFile { export class SourceFile {
info: string; info: JSX.Element;
lvl = 1; lvl = 1;
n: number; n: number;
name: string; name: string;
owned = false; owned = false;
constructor(number: number, info = "") { constructor(number: number, info: JSX.Element) {
const bitnodeKey = "BitNode" + number; const bitnodeKey = "BitNode" + number;
const bitnode = BitNodes[bitnodeKey]; const bitnode = BitNodes[bitnodeKey];
if (bitnode == null) { if (bitnode == null) {

@ -1,104 +0,0 @@
import { SourceFile } from "./SourceFile";
import { IMap } from "../types";
export const SourceFiles: IMap<SourceFile> = {};
SourceFiles["SourceFile1"] = new SourceFile(
1,
"This Source-File lets the player start with 32GB of RAM on his/her " +
"home computer. It also increases all of the player's multipliers by:<br><br>" +
"Level 1: 16%<br>" +
"Level 2: 24%<br>" +
"Level 3: 28%",
);
SourceFiles["SourceFile2"] = new SourceFile(
2,
"This Source-File allows you to form gangs in other BitNodes " +
"once your karma decreases to a certain value. It also increases the player's " +
"crime success rate, crime money, and charisma multipliers by:<br><br>" +
"Level 1: 24%<br>" +
"Level 2: 36%<br>" +
"Level 3: 42%",
);
SourceFiles["SourceFile3"] = new SourceFile(
3,
"This Source-File lets you create corporations on other BitNodes (although " +
"some BitNodes will disable this mechanic). This Source-File also increases your charisma and company salary multipliers by:<br>" +
"Level 1: 8%<br>" +
"Level 2: 12%<br>" +
"Level 3: 14%",
);
SourceFiles["SourceFile4"] = new SourceFile(
4,
"This Source-File lets you access and use the Singularity Functions in every BitNode. Every " +
"level of this Source-File opens up more of the Singularity Functions you can use.",
);
SourceFiles["SourceFile5"] = new SourceFile(
5,
"This Source-File grants a special new stat called Intelligence. Intelligence " +
"is unique because it is permanent and persistent (it never gets reset back to 1). However, " +
"gaining Intelligence experience is much slower than other stats, and it is also hidden (you won't " +
"know when you gain experience and how much). Higher Intelligence levels will boost your production " +
"for many actions in the game. In addition, this Source-File will unlock the getBitNodeMultipliers() " +
"and getServer() Netscript functions, as well as the formulas API, and will raise all of your " +
"hacking-related multipliers by:<br><br> " +
"Level 1: 8%<br>" +
"Level 2: 12%<br>" +
"Level 3: 14%",
);
SourceFiles["SourceFile6"] = new SourceFile(
6,
"This Source-File allows you to access the NSA's Bladeburner Division in other " +
"BitNodes. In addition, this Source-File will raise both the level and experience gain rate of all your combat stats by:<br><br>" +
"Level 1: 8%<br>" +
"Level 2: 12%<br>" +
"Level 3: 14%",
);
SourceFiles["SourceFile7"] = new SourceFile(
7,
"This Source-File allows you to access the Bladeburner Netscript API in other " +
"BitNodes. In addition, this Source-File will increase all of your Bladeburner multipliers by:<br><br>" +
"Level 1: 8%<br>" +
"Level 2: 12%<br>" +
"Level 3: 14%",
);
SourceFiles["SourceFile8"] = new SourceFile(
8,
"This Source-File grants the following benefits:<br><br>" +
"Level 1: Permanent access to WSE and TIX API<br>" +
"Level 2: Ability to short stocks in other BitNodes<br>" +
"Level 3: Ability to use limit/stop orders in other BitNodes<br><br>" +
"This Source-File also increases your hacking growth multipliers by: " +
"<br>Level 1: 12%<br>Level 2: 18%<br>Level 3: 21%",
);
SourceFiles["SourceFile9"] = new SourceFile(
9,
"This Source-File grants the following benefits:<br><br>" +
"Level 1: Permanently unlocks the Hacknet Server in other BitNodes<br>" +
"Level 2: You start with 128GB of RAM on your home computer when entering a new BitNode<br>" +
"Level 3: Grants a highly-upgraded Hacknet Server when entering a new BitNode<br><br>" +
"(Note that the Level 3 effect of this Source-File only applies when entering a new BitNode, NOT " +
"when installing Augmentations)",
);
SourceFiles["SourceFile10"] = new SourceFile(
10,
"This Source-File unlocks Sleeve technology in other BitNodes. Each level of this " +
"Source-File also grants you a Duplicate Sleeve",
);
SourceFiles["SourceFile11"] = new SourceFile(
11,
"This Source-File makes it so that company favor increases BOTH the player's salary and reputation gain rate " +
"at that company by 1% per favor (rather than just the reputation gain). This Source-File also " +
" increases the player's company salary and reputation gain multipliers by:<br><br>" +
"Level 1: 32%<br>" +
"Level 2: 48%<br>" +
"Level 3: 56%<br><br>" +
"It also reduces the price increase for every aug bought by:<br><br>" +
"Level 1: 4%<br>" +
"Level 2: 6%<br>" +
"Level 3: 7%",
);
SourceFiles["SourceFile12"] = new SourceFile(
12,
"This Source-File lets the player start with Neuroflux Governor equal to the level of this Source-File.",
);

@ -0,0 +1,197 @@
import React from "react";
import { SourceFile } from "./SourceFile";
import { IMap } from "../types";
export const SourceFiles: IMap<SourceFile> = {};
SourceFiles["SourceFile1"] = new SourceFile(
1,
(
<>
This Source-File lets the player start with 32GB of RAM on his/her home computer. It also increases all of the
player's multipliers by:
<br />
<br />
Level 1: 16%
<br />
Level 2: 24%
<br />
Level 3: 28%
</>
),
);
SourceFiles["SourceFile2"] = new SourceFile(
2,
(
<>
This Source-File allows you to form gangs in other BitNodes once your karma decreases to a certain value. It also
increases the player's crime success rate, crime money, and charisma multipliers by:
<br />
<br />
Level 1: 24%
<br />
Level 2: 36%
<br />
Level 3: 42%
</>
),
);
SourceFiles["SourceFile3"] = new SourceFile(
3,
(
<>
This Source-File lets you create corporations on other BitNodes (although some BitNodes will disable this
mechanic). This Source-File also increases your charisma and company salary multipliers by:
<br />
Level 1: 8%
<br />
Level 2: 12%
<br />
Level 3: 14%
</>
),
);
SourceFiles["SourceFile4"] = new SourceFile(
4,
(
<>
This Source-File lets you access and use the Singularity Functions in every BitNode. Every level of this
Source-File opens up more of the Singularity Functions you can use.
</>
),
);
SourceFiles["SourceFile5"] = new SourceFile(
5,
(
<>
This Source-File grants a special new stat called Intelligence. Intelligence is unique because it is permanent and
persistent (it never gets reset back to 1). However, gaining Intelligence experience is much slower than other
stats, and it is also hidden (you won't know when you gain experience and how much). Higher Intelligence levels
will boost your production for many actions in the game. In addition, this Source-File will unlock the
getBitNodeMultipliers() and getServer() Netscript functions, as well as the formulas API, and will raise all of
your hacking-related multipliers by:
<br />
<br />
Level 1: 8%
<br />
Level 2: 12%
<br />
Level 3: 14%
</>
),
);
SourceFiles["SourceFile6"] = new SourceFile(
6,
(
<>
This Source-File allows you to access the NSA's Bladeburner Division in other BitNodes. In addition, this
Source-File will raise both the level and experience gain rate of all your combat stats by:
<br />
<br />
Level 1: 8%
<br />
Level 2: 12%
<br />
Level 3: 14%
</>
),
);
SourceFiles["SourceFile7"] = new SourceFile(
7,
(
<>
This Source-File allows you to access the Bladeburner Netscript API in other BitNodes. In addition, this
Source-File will increase all of your Bladeburner multipliers by:
<br />
<br />
Level 1: 8%
<br />
Level 2: 12%
<br />
Level 3: 14%
</>
),
);
SourceFiles["SourceFile8"] = new SourceFile(
8,
(
<>
This Source-File grants the following benefits:
<br />
<br />
Level 1: Permanent access to WSE and TIX API
<br />
Level 2: Ability to short stocks in other BitNodes
<br />
Level 3: Ability to use limit/stop orders in other BitNodes
<br />
<br />
This Source-File also increases your hacking growth multipliers by:
<br />
Level 1: 12%
<br />
Level 2: 18%
<br />
Level 3: 21%
</>
),
);
SourceFiles["SourceFile9"] = new SourceFile(
9,
(
<>
This Source-File grants the following benefits:
<br />
<br />
Level 1: Permanently unlocks the Hacknet Server in other BitNodes
<br />
Level 2: You start with 128GB of RAM on your home computer when entering a new BitNode
<br />
Level 3: Grants a highly-upgraded Hacknet Server when entering a new BitNode
<br />
<br />
(Note that the Level 3 effect of this Source-File only applies when entering a new BitNode, NOT when installing
Augmentations)
</>
),
);
SourceFiles["SourceFile10"] = new SourceFile(
10,
(
<>
This Source-File unlocks Sleeve technology in other BitNodes. Each level of this Source-File also grants you a
Duplicate Sleeve
</>
),
);
SourceFiles["SourceFile11"] = new SourceFile(
11,
(
<>
This Source-File makes it so that company favor increases BOTH the player's salary and reputation gain rate at
that company by 1% per favor (rather than just the reputation gain). This Source-File also increases the player's
company salary and reputation gain multipliers by:
<br />
<br />
Level 1: 32%
<br />
Level 2: 48%
<br />
Level 3: 56%
<br />
<br />
It also reduces the price increase for every aug bought by:
<br />
<br />
Level 1: 4%
<br />
Level 2: 6%
<br />
Level 3: 7%
</>
),
);
SourceFiles["SourceFile12"] = new SourceFile(
12,
<>This Source-File lets the player start with Neuroflux Governor equal to the level of this Source-File.</>,
);

@ -4,12 +4,18 @@
* The header of the accordion contains the Augmentation's name (and level, if * The header of the accordion contains the Augmentation's name (and level, if
* applicable), and the accordion's panel contains the Augmentation's description. * applicable), and the accordion's panel contains the Augmentation's description.
*/ */
import * as React from "react"; import React, { useState } from "react";
import { BBAccordion } from "./BBAccordion";
import { Augmentation } from "../../Augmentation/Augmentation"; import { Augmentation } from "../../Augmentation/Augmentation";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames"; import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import ListItemButton from "@mui/material/ListItemButton";
import ListItemText from "@mui/material/ListItemText";
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
import Paper from "@mui/material/Paper";
import Collapse from "@mui/material/Collapse";
import ExpandMore from "@mui/icons-material/ExpandMore";
import ExpandLess from "@mui/icons-material/ExpandLess";
type IProps = { type IProps = {
aug: Augmentation; aug: Augmentation;
@ -17,6 +23,7 @@ type IProps = {
}; };
export function AugmentationAccordion(props: IProps): React.ReactElement { export function AugmentationAccordion(props: IProps): React.ReactElement {
const [open, setOpen] = useState(false);
let displayName = props.aug.name; let displayName = props.aug.name;
if (props.level != null) { if (props.level != null) {
if (props.aug.name === AugmentationNames.NeuroFluxGovernor) { if (props.aug.name === AugmentationNames.NeuroFluxGovernor) {
@ -26,31 +33,47 @@ export function AugmentationAccordion(props: IProps): React.ReactElement {
if (typeof props.aug.info === "string") { if (typeof props.aug.info === "string") {
return ( return (
<BBAccordion <Box component={Paper}>
headerContent={<>{displayName}</>} <ListItemButton onClick={() => setOpen((old) => !old)}>
panelContent={ <ListItemText primary={<Typography style={{ whiteSpace: "pre-wrap" }}>{displayName}</Typography>} />
<p> {open ? <ExpandLess color="primary" /> : <ExpandMore color="primary" />}
<span dangerouslySetInnerHTML={{ __html: props.aug.info }} /> </ListItemButton>
<br /> <Collapse in={open} unmountOnExit>
<br /> <Box m={4}>
{props.aug.stats} <Typography dangerouslySetInnerHTML={{ __html: props.aug.info }} />
</p> {props.aug.stats && (
} <>
/> <br />
<br />
<Typography>{props.aug.stats}</Typography>
</>
)}
</Box>
</Collapse>
</Box>
); );
} }
return ( return (
<BBAccordion <Box component={Paper}>
headerContent={<>{displayName}</>} <ListItemButton onClick={() => setOpen((old) => !old)}>
panelContent={ <ListItemText primary={<Typography style={{ whiteSpace: "pre-wrap" }}>{displayName}</Typography>} />
<p> {open ? <ExpandLess color="primary" /> : <ExpandMore color="primary" />}
{props.aug.info} </ListItemButton>
<br /> <Collapse in={open} unmountOnExit>
<br /> <Box m={4}>
{props.aug.stats} <Typography>
</p> {props.aug.info}
} {props.aug.stats && (
/> <>
<br />
<br />
{props.aug.stats}
</>
)}
</Typography>
</Box>
</Collapse>
</Box>
); );
} }

@ -4,30 +4,47 @@
* The header of the accordion contains the Source-Files's name and level, * The header of the accordion contains the Source-Files's name and level,
* and the accordion's panel contains the Source-File's description. * and the accordion's panel contains the Source-File's description.
*/ */
import * as React from "react"; import React, { useState } from "react";
import { BBAccordion } from "./BBAccordion";
import { SourceFile } from "../../SourceFile/SourceFile"; import { SourceFile } from "../../SourceFile/SourceFile";
import ListItemButton from "@mui/material/ListItemButton";
import ListItemText from "@mui/material/ListItemText";
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
import Paper from "@mui/material/Paper";
import Collapse from "@mui/material/Collapse";
import ExpandMore from "@mui/icons-material/ExpandMore";
import ExpandLess from "@mui/icons-material/ExpandLess";
type IProps = { type IProps = {
level: number; level: number;
sf: SourceFile; sf: SourceFile;
}; };
export function SourceFileAccordion(props: IProps): React.ReactElement { export function SourceFileAccordion(props: IProps): React.ReactElement {
const [open, setOpen] = useState(false);
const maxLevel = props.sf.n === 12 ? "∞" : "3"; const maxLevel = props.sf.n === 12 ? "∞" : "3";
return ( return (
<BBAccordion <Box component={Paper}>
headerContent={ <ListItemButton onClick={() => setOpen((old) => !old)}>
<> <ListItemText
{props.sf.name} primary={
<br /> <Typography style={{ whiteSpace: "pre-wrap" }}>
{`Level ${props.level} / ${maxLevel}`} {props.sf.name}
</> <br />
} {`Level ${props.level} / ${maxLevel}`}
panelContent={<p dangerouslySetInnerHTML={{ __html: props.sf.info }}></p>} </Typography>
/> }
/>
{open ? <ExpandLess color="primary" /> : <ExpandMore color="primary" />}
</ListItemButton>
<Collapse in={open} unmountOnExit>
<Box m={4}>
<Typography>{props.sf.info}</Typography>
</Box>
</Collapse>
</Box>
); );
} }