mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2026-05-03 06:17:04 +02:00
sg
This commit is contained in:
@@ -85,9 +85,7 @@ export function ServerAccordions(props: IProps): React.ReactElement {
|
||||
<TextField
|
||||
value={filter}
|
||||
onChange={handleFilterChange}
|
||||
color="primary"
|
||||
autoFocus
|
||||
variant="standard"
|
||||
InputProps={{
|
||||
startAdornment: <SearchIcon />,
|
||||
spellCheck: false,
|
||||
|
||||
@@ -28,7 +28,7 @@ import { killWorkerScript } from "../../Netscript/killWorkerScript";
|
||||
import { WorkerScript } from "../../Netscript/WorkerScript";
|
||||
|
||||
import { dialogBoxCreate } from "../React/DialogBox";
|
||||
import { logBoxCreate } from "../React/LogBox";
|
||||
import { LogBoxEvents } from "../React/LogBoxManager";
|
||||
import { convertTimeMsToTimeElapsedString } from "../../utils/StringHelperFunctions";
|
||||
import { arrayToString } from "../../utils/helpers/arrayToString";
|
||||
import { Money } from "../React/Money";
|
||||
@@ -50,7 +50,9 @@ export function WorkerScriptAccordion(props: IProps): React.ReactElement {
|
||||
const workerScript = props.workerScript;
|
||||
const scriptRef = workerScript.scriptRef;
|
||||
|
||||
const logClickHandler = logBoxCreate.bind(null, scriptRef);
|
||||
function logClickHandler(): void {
|
||||
LogBoxEvents.emit(scriptRef);
|
||||
}
|
||||
const killScript = killWorkerScript.bind(null, scriptRef as any, scriptRef.server);
|
||||
|
||||
function killScriptClickHandler(): void {
|
||||
|
||||
+10
-12
@@ -56,7 +56,6 @@ import { TerminalRoot } from "../Terminal/ui/TerminalRoot";
|
||||
import { TutorialRoot } from "../Tutorial/ui/TutorialRoot";
|
||||
import { ActiveScriptsRoot } from "../ui/ActiveScripts/ActiveScriptsRoot";
|
||||
import { FactionsRoot } from "../Faction/ui/FactionsRoot";
|
||||
import { HackingMissionRoot } from "../HackingMission/ui/HackingMissionRoot";
|
||||
import { FactionRoot } from "../Faction/ui/FactionRoot";
|
||||
import { CharacterStats } from "./CharacterStats";
|
||||
import { TravelAgencyRoot } from "../Locations/ui/TravelAgencyRoot";
|
||||
@@ -69,6 +68,10 @@ import { BladeburnerCinematic } from "../Bladeburner/ui/BladeburnerCinematic";
|
||||
import { workerScripts } from "../Netscript/WorkerScripts";
|
||||
import { Unclickable } from "../Exploits/Unclickable";
|
||||
import { Snackbar } from "./React/Snackbar";
|
||||
import { LogBoxManager } from "./React/LogBoxManager";
|
||||
import { AlertManager } from "./React/AlertManager";
|
||||
import { PromptManager } from "./React/PromptManager";
|
||||
import { InvitationModal } from "../Faction/ui/InvitationModal";
|
||||
|
||||
import { enterBitNode } from "../RedPill";
|
||||
import { Context } from "./Context";
|
||||
@@ -177,9 +180,6 @@ export let Router: IRouter = {
|
||||
toLocation: () => {
|
||||
throw new Error("Router called before initialization");
|
||||
},
|
||||
toHackingMission: () => {
|
||||
throw new Error("Router called before initialization");
|
||||
},
|
||||
toStaneksGift: () => {
|
||||
throw new Error("Router called before initialization");
|
||||
},
|
||||
@@ -272,10 +272,6 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
|
||||
setLocation(location);
|
||||
setPage(Page.Location);
|
||||
},
|
||||
toHackingMission: (faction: Faction) => {
|
||||
setPage(Page.HackingMission);
|
||||
setFaction(faction);
|
||||
},
|
||||
toStaneksGift: () => {
|
||||
setPage(Page.StaneksGift);
|
||||
},
|
||||
@@ -301,8 +297,6 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
|
||||
<BitverseRoot flume={flume} enter={enterBitNode} quick={quick} />
|
||||
) : page === Page.Infiltration ? (
|
||||
<InfiltrationRoot location={location} />
|
||||
) : page === Page.HackingMission ? (
|
||||
<HackingMissionRoot faction={faction} />
|
||||
) : page === Page.BladeburnerCinematic ? (
|
||||
<BladeburnerCinematic />
|
||||
) : page === Page.Work ? (
|
||||
@@ -314,7 +308,7 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
|
||||
{page === Page.Terminal ? (
|
||||
<TerminalRoot terminal={terminal} router={Router} player={player} />
|
||||
) : page === Page.Sleeves ? (
|
||||
<SleeveRoot player={player} />
|
||||
<SleeveRoot />
|
||||
) : page === Page.Stats ? (
|
||||
<CharacterStats />
|
||||
) : page === Page.StaneksGift ? (
|
||||
@@ -344,7 +338,7 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
|
||||
) : page === Page.Bladeburner ? (
|
||||
<BladeburnerRoot />
|
||||
) : page === Page.Resleeves ? (
|
||||
<ResleeveRoot player={player} />
|
||||
<ResleeveRoot />
|
||||
) : page === Page.Travel ? (
|
||||
<TravelAgencyRoot p={player} router={Router} />
|
||||
) : page === Page.StockMarket ? (
|
||||
@@ -404,6 +398,10 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
|
||||
)}
|
||||
<Unclickable />
|
||||
<Snackbar />
|
||||
<LogBoxManager />
|
||||
<AlertManager />
|
||||
<PromptManager />
|
||||
<InvitationModal />
|
||||
</Context.Router.Provider>
|
||||
</Context.Player.Provider>
|
||||
);
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
/**
|
||||
* Basic stateless button that uses the 'accordion-button' css class.
|
||||
* This class has a black background so that it does not clash with the default
|
||||
* accordion coloring
|
||||
*/
|
||||
import * as React from "react";
|
||||
|
||||
interface IProps {
|
||||
addClasses?: string;
|
||||
disabled?: boolean;
|
||||
id?: string;
|
||||
onClick?: (e: React.MouseEvent<HTMLElement>) => any;
|
||||
style?: any;
|
||||
text: string;
|
||||
tooltip?: string;
|
||||
}
|
||||
|
||||
type IInnerHTMLMarkup = {
|
||||
__html: string;
|
||||
};
|
||||
|
||||
export function AccordionButton(props: IProps): React.ReactElement {
|
||||
const hasTooltip = props.tooltip != null && props.tooltip !== "";
|
||||
|
||||
// TODO Add a disabled class for accordion buttons?
|
||||
let className = "accordion-button";
|
||||
if (hasTooltip) {
|
||||
className += " tooltip";
|
||||
}
|
||||
|
||||
if (typeof props.addClasses === "string") {
|
||||
className += ` ${props.addClasses}`;
|
||||
}
|
||||
|
||||
// Tooltip will be set using inner HTML
|
||||
const tooltipMarkup: IInnerHTMLMarkup = {
|
||||
__html: props.tooltip ? props.tooltip : "",
|
||||
};
|
||||
|
||||
return (
|
||||
<button className={className} id={props.id} onClick={props.onClick} style={props.style}>
|
||||
{props.text}
|
||||
{hasTooltip && <span className={"tooltiptext"} dangerouslySetInnerHTML={tooltipMarkup}></span>}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { EventEmitter } from "../../utils/EventEmitter";
|
||||
import { Modal } from "../../ui/React/Modal";
|
||||
import Typography from "@mui/material/Typography";
|
||||
|
||||
export const AlertEvents = new EventEmitter<[string | JSX.Element]>();
|
||||
|
||||
interface Alert {
|
||||
id: string;
|
||||
text: string | JSX.Element;
|
||||
}
|
||||
|
||||
let i = 0;
|
||||
export function AlertManager(): React.ReactElement {
|
||||
const [alerts, setAlerts] = useState<Alert[]>([]);
|
||||
useEffect(
|
||||
() =>
|
||||
AlertEvents.subscribe((text: string | JSX.Element) => {
|
||||
const id = i + "";
|
||||
i++;
|
||||
setAlerts((old) => {
|
||||
return [
|
||||
...old,
|
||||
{
|
||||
id: id,
|
||||
text: text,
|
||||
},
|
||||
];
|
||||
});
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
function close(): void {
|
||||
setAlerts((old) => {
|
||||
return old.slice(1, 1e99);
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{alerts.length > 0 && (
|
||||
<Modal open={true} onClose={close}>
|
||||
<Typography>{alerts[0].text}</Typography>
|
||||
</Modal>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,9 +1,17 @@
|
||||
import * as React from "react";
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import makeStyles from "@mui/styles/makeStyles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
aug: {
|
||||
color: theme.colors.combat,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
export function Augmentation({ name }: { name: string }): JSX.Element {
|
||||
return (
|
||||
<span className={"samefont"} style={{ color: "white" }}>
|
||||
{name}
|
||||
</span>
|
||||
);
|
||||
const classes = useStyles();
|
||||
return <span className={classes.aug}>{name}</span>;
|
||||
}
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
/**
|
||||
* Basic paragraph (p Element) that automatically re-renders itself every X seconds
|
||||
*
|
||||
* NOT recommended for usage - only if you really have to
|
||||
*/
|
||||
import * as React from "react";
|
||||
|
||||
interface IProps {
|
||||
intervalTime?: number;
|
||||
style?: any;
|
||||
getContent: () => JSX.Element;
|
||||
getTooltip?: () => JSX.Element;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
i: number;
|
||||
}
|
||||
|
||||
export class AutoupdatingParagraph extends React.Component<IProps, IState> {
|
||||
/**
|
||||
* Timer ID for auto-updating implementation (returned value from setInterval())
|
||||
*/
|
||||
interval = 0;
|
||||
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
i: 0,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount(): void {
|
||||
const time = this.props.intervalTime ? this.props.intervalTime : 1000;
|
||||
this.interval = window.setInterval(() => this.tick(), time);
|
||||
}
|
||||
|
||||
componentWillUnmount(): void {
|
||||
clearInterval(this.interval);
|
||||
}
|
||||
|
||||
tick(): void {
|
||||
this.setState((prevState) => ({
|
||||
i: prevState.i + 1,
|
||||
}));
|
||||
}
|
||||
|
||||
hasTooltip(): boolean {
|
||||
if (this.props.getTooltip != null) {
|
||||
return !!this.props.getTooltip();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
tooltip(): JSX.Element {
|
||||
if (!this.props.getTooltip) return <></>;
|
||||
return this.props.getTooltip();
|
||||
}
|
||||
|
||||
render(): React.ReactNode {
|
||||
return (
|
||||
<div className="tooltip" style={this.props.style}>
|
||||
<p>{this.props.getContent()}</p>
|
||||
{this.hasTooltip() && <span className={"tooltiptext"}>{this.tooltip()}</span>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
/**
|
||||
* Basic stateless button that automatically re-renders itself every X seconds
|
||||
* Uses the 'std-button' css class
|
||||
*
|
||||
* NOT recommended for usage - only if you really have to
|
||||
*/
|
||||
import * as React from "react";
|
||||
|
||||
interface IProps {
|
||||
label?: string;
|
||||
disabled?: boolean;
|
||||
intervalTime?: number;
|
||||
onClick?: (e: React.MouseEvent<HTMLElement>) => any;
|
||||
style?: any;
|
||||
text: string | JSX.Element;
|
||||
tooltip?: string;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
i: number;
|
||||
}
|
||||
|
||||
type IInnerHTMLMarkup = {
|
||||
__html: string;
|
||||
};
|
||||
|
||||
export class AutoupdatingStdButton extends React.Component<IProps, IState> {
|
||||
/**
|
||||
* Timer ID for auto-updating implementation (returned value from setInterval())
|
||||
*/
|
||||
interval = 0;
|
||||
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
i: 0,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount(): void {
|
||||
const time = this.props.intervalTime ? this.props.intervalTime : 1000;
|
||||
this.interval = window.setInterval(() => this.tick(), time);
|
||||
}
|
||||
|
||||
componentWillUnmount(): void {
|
||||
clearInterval(this.interval);
|
||||
}
|
||||
|
||||
tick(): void {
|
||||
this.setState((prevState) => ({
|
||||
i: prevState.i + 1,
|
||||
}));
|
||||
}
|
||||
|
||||
render(): React.ReactNode {
|
||||
const hasTooltip = this.props.tooltip != null && this.props.tooltip !== "";
|
||||
|
||||
let className = this.props.disabled ? "std-button-disabled" : "std-button";
|
||||
if (hasTooltip) {
|
||||
className += " tooltip";
|
||||
}
|
||||
|
||||
// Tooltip will eb set using inner HTML
|
||||
const tooltipMarkup: IInnerHTMLMarkup = {
|
||||
__html: this.props.tooltip ? this.props.tooltip : "",
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
aria-label={this.props.label || ""}
|
||||
className={className}
|
||||
onClick={this.props.onClick}
|
||||
style={this.props.style}
|
||||
>
|
||||
{this.props.text}
|
||||
{hasTooltip && <span className={"tooltiptext"} dangerouslySetInnerHTML={tooltipMarkup}></span>}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
/**
|
||||
* React component to create an accordion element
|
||||
*/
|
||||
import * as React from "react";
|
||||
|
||||
type IProps = {
|
||||
headerClass?: string; // Override default class
|
||||
headerContent: React.ReactElement;
|
||||
panelClass?: string; // Override default class
|
||||
panelContent: React.ReactElement;
|
||||
panelInitiallyOpened?: boolean;
|
||||
style?: string;
|
||||
};
|
||||
|
||||
type IState = {
|
||||
panelOpened: boolean;
|
||||
};
|
||||
|
||||
export class BBAccordion extends React.Component<IProps, IState> {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.handleHeaderClick = this.handleHeaderClick.bind(this);
|
||||
|
||||
this.state = {
|
||||
panelOpened: props.panelInitiallyOpened ? props.panelInitiallyOpened : false,
|
||||
};
|
||||
}
|
||||
|
||||
handleHeaderClick(): void {
|
||||
this.setState({
|
||||
panelOpened: !this.state.panelOpened,
|
||||
});
|
||||
}
|
||||
|
||||
render(): React.ReactNode {
|
||||
let className = "accordion-header";
|
||||
if (typeof this.props.headerClass === "string") {
|
||||
className = this.props.headerClass;
|
||||
}
|
||||
|
||||
if (this.state.panelOpened) className += " active";
|
||||
|
||||
return (
|
||||
<>
|
||||
<button className={className} onClick={this.handleHeaderClick}>
|
||||
{this.props.headerContent}
|
||||
</button>
|
||||
<AccordionPanel
|
||||
opened={this.state.panelOpened}
|
||||
panelClass={this.props.panelClass}
|
||||
panelContent={this.props.panelContent}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
type IPanelProps = {
|
||||
opened: boolean;
|
||||
panelClass?: string; // Override default class
|
||||
panelContent: React.ReactElement;
|
||||
};
|
||||
|
||||
class AccordionPanel extends React.Component<IPanelProps, any> {
|
||||
shouldComponentUpdate(nextProps: IPanelProps): boolean {
|
||||
return this.props.opened || nextProps.opened;
|
||||
}
|
||||
|
||||
render(): React.ReactNode {
|
||||
let className = "accordion-panel";
|
||||
if (typeof this.props.panelClass === "string") {
|
||||
className = this.props.panelClass;
|
||||
}
|
||||
|
||||
if (!this.props.opened) return <></>;
|
||||
|
||||
return (
|
||||
<div className={className} style={{ display: "block" }}>
|
||||
{this.props.panelContent}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -60,7 +60,9 @@ function Work(): React.ReactElement {
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell component="th" scope="row" colSpan={2} classes={{ root: classes.cellNone }}>
|
||||
<Typography>+{Reputation(player.workRepGained)} rep</Typography>
|
||||
<Typography>
|
||||
+<Reputation reputation={player.workRepGained} /> rep
|
||||
</Typography>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import Typography from "@mui/material/Typography";
|
||||
|
||||
interface IProps {
|
||||
text: string;
|
||||
@@ -34,10 +35,10 @@ export function CinematicLine(props: IProps): React.ReactElement {
|
||||
});
|
||||
return (
|
||||
<>
|
||||
<pre>
|
||||
<Typography>
|
||||
{props.text.slice(0, length)}
|
||||
{!done && <span>█</span>}
|
||||
</pre>
|
||||
</Typography>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import React, { useState } from "react";
|
||||
|
||||
import { CinematicLine } from "./CinematicLine";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Button from "@mui/material/Button";
|
||||
|
||||
interface IProps {
|
||||
lines: string[];
|
||||
@@ -22,16 +24,12 @@ export function CinematicText(props: IProps): React.ReactElement {
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<>
|
||||
{props.lines.slice(0, i).map((line, i) => (
|
||||
<pre key={i}>{line}</pre>
|
||||
<Typography key={i}>{line}</Typography>
|
||||
))}
|
||||
{props.lines.length > i && <CinematicLine key={i} text={props.lines[i]} onDone={advance} />}
|
||||
{!props.auto && props.onDone && done && (
|
||||
<button className="std-button" onClick={props.onDone}>
|
||||
Continue ...
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{!props.auto && props.onDone && done && <Button onClick={props.onDone}>Continue ...</Button>}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,24 +1,37 @@
|
||||
import React, { useState } from "react";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { KEY } from "../../utils/helpers/keyCodes";
|
||||
|
||||
import { CodingContract, CodingContractType, CodingContractTypes } from "../../CodingContracts";
|
||||
import { ClickableTag, CopyableText } from "./CopyableText";
|
||||
import { CopyableText } from "./CopyableText";
|
||||
import { Modal } from "./Modal";
|
||||
import { EventEmitter } from "../../utils/EventEmitter";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import TextField from "@mui/material/TextField";
|
||||
import Button from "@mui/material/Button";
|
||||
|
||||
type IProps = {
|
||||
interface IProps {
|
||||
c: CodingContract;
|
||||
popupId: string;
|
||||
onClose: () => void;
|
||||
onAttempt: (answer: string) => void;
|
||||
};
|
||||
}
|
||||
|
||||
export function CodingContractPopup(props: IProps): React.ReactElement {
|
||||
export const CodingContractEvent = new EventEmitter<[IProps]>();
|
||||
|
||||
export function CodingContractModal(): React.ReactElement {
|
||||
const [props, setProps] = useState<IProps | null>(null);
|
||||
const [answer, setAnswer] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
CodingContractEvent.subscribe((props) => setProps(props));
|
||||
});
|
||||
if (props === null) return <></>;
|
||||
|
||||
function onChange(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||
setAnswer(event.target.value);
|
||||
}
|
||||
|
||||
function onKeyDown(event: React.KeyboardEvent<HTMLInputElement>): void {
|
||||
if (props === null) return;
|
||||
// React just won't cooperate on this one.
|
||||
// "React.KeyboardEvent<HTMLInputElement>" seems like the right type but
|
||||
// whatever ...
|
||||
@@ -30,34 +43,45 @@ export function CodingContractPopup(props: IProps): React.ReactElement {
|
||||
}
|
||||
}
|
||||
|
||||
function close(): void {
|
||||
if (props === null) return;
|
||||
props.onClose();
|
||||
setProps(null);
|
||||
}
|
||||
|
||||
const contractType: CodingContractType = CodingContractTypes[props.c.type];
|
||||
const description = [];
|
||||
for (const [i, value] of contractType.desc(props.c.data).split("\n").entries())
|
||||
description.push(<span key={i} dangerouslySetInnerHTML={{ __html: value + "<br />" }}></span>);
|
||||
return (
|
||||
<div>
|
||||
<CopyableText value={props.c.type} tag={ClickableTag.Tag_h1} />
|
||||
<br />
|
||||
<br />
|
||||
<p>
|
||||
<Modal open={props !== null} onClose={close}>
|
||||
<CopyableText variant="h4" value={props.c.type} />
|
||||
<Typography>
|
||||
You are attempting to solve a Coding Contract. You have {props.c.getMaxNumTries() - props.c.tries} tries
|
||||
remaining, after which the contract will self-destruct.
|
||||
</p>
|
||||
</Typography>
|
||||
<br />
|
||||
<p>{description}</p>
|
||||
<Typography>{description}</Typography>
|
||||
<br />
|
||||
<input
|
||||
className="text-input"
|
||||
style={{ width: "50%", marginTop: "8px" }}
|
||||
autoFocus={true}
|
||||
<TextField
|
||||
autoFocus
|
||||
placeholder="Enter Solution here"
|
||||
value={answer}
|
||||
onChange={onChange}
|
||||
onKeyDown={onKeyDown}
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<Button
|
||||
onClick={() => {
|
||||
props.onAttempt(answer);
|
||||
close();
|
||||
}}
|
||||
>
|
||||
Solve
|
||||
</Button>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<button className={"std-button"} onClick={() => props.onAttempt(answer)}>
|
||||
Solve
|
||||
</button>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
@@ -1,83 +1,48 @@
|
||||
import * as React from "react";
|
||||
|
||||
export enum ClickableTag {
|
||||
Tag_span,
|
||||
Tag_h1,
|
||||
}
|
||||
import React, { useState } from "react";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
|
||||
type IProps = {
|
||||
value: string;
|
||||
tag: ClickableTag;
|
||||
color?: string;
|
||||
variant?:
|
||||
| "button"
|
||||
| "caption"
|
||||
| "h1"
|
||||
| "h2"
|
||||
| "h3"
|
||||
| "h4"
|
||||
| "h5"
|
||||
| "h6"
|
||||
| "subtitle1"
|
||||
| "subtitle2"
|
||||
| "body1"
|
||||
| "body2"
|
||||
| "overline"
|
||||
| "inherit"
|
||||
| undefined;
|
||||
};
|
||||
|
||||
type IState = {
|
||||
tooltipVisible: boolean;
|
||||
};
|
||||
export function CopyableText(props: IProps): React.ReactElement {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
export class CopyableText extends React.Component<IProps, IState> {
|
||||
public static defaultProps = {
|
||||
//Default span to prevent destroying current clickables
|
||||
tag: ClickableTag.Tag_span,
|
||||
};
|
||||
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.copy = this.copy.bind(this);
|
||||
this.tooltipClasses = this.tooltipClasses.bind(this);
|
||||
this.textClasses = this.textClasses.bind(this);
|
||||
|
||||
this.state = {
|
||||
tooltipVisible: false,
|
||||
};
|
||||
}
|
||||
|
||||
copy(): void {
|
||||
function copy(): void {
|
||||
const copyText = document.createElement("textarea");
|
||||
copyText.value = this.props.value;
|
||||
copyText.value = props.value;
|
||||
document.body.appendChild(copyText);
|
||||
copyText.select();
|
||||
copyText.setSelectionRange(0, 1e10);
|
||||
document.execCommand("copy");
|
||||
document.body.removeChild(copyText);
|
||||
this.setState({ tooltipVisible: true });
|
||||
setTimeout(() => this.setState({ tooltipVisible: false }), 1000);
|
||||
setOpen(true);
|
||||
setTimeout(() => setOpen(false), 1000);
|
||||
}
|
||||
|
||||
tooltipClasses(): string {
|
||||
let classes = "copy_tooltip_text";
|
||||
if (this.state.tooltipVisible) {
|
||||
classes += " copy_tooltip_text_visible";
|
||||
}
|
||||
|
||||
return classes;
|
||||
}
|
||||
|
||||
textClasses(): string {
|
||||
let classes = "copy_tooltip noselect text";
|
||||
if (this.state.tooltipVisible) {
|
||||
classes += " copy_tooltip_copied";
|
||||
}
|
||||
|
||||
return classes;
|
||||
}
|
||||
|
||||
render(): React.ReactNode {
|
||||
switch (this.props.tag) {
|
||||
case ClickableTag.Tag_h1:
|
||||
return (
|
||||
<h1 className={this.textClasses()} onClick={this.copy}>
|
||||
{this.props.value}
|
||||
<span className={this.tooltipClasses()}>Copied!</span>
|
||||
</h1>
|
||||
);
|
||||
case ClickableTag.Tag_span:
|
||||
return (
|
||||
<span className={this.textClasses()} onClick={this.copy}>
|
||||
<b>{this.props.value}</b>
|
||||
<span className={this.tooltipClasses()}>Copied!</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<Tooltip open={open} title={<Typography>Copied!</Typography>}>
|
||||
<Typography variant={props.variant} color={props.color} onClick={copy}>
|
||||
{props.value}
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,43 +1,11 @@
|
||||
import { createPopup } from "./createPopup";
|
||||
import { getRandomInt } from "../../utils/helpers/getRandomInt";
|
||||
import { AlertEvents } from "./AlertManager";
|
||||
|
||||
import React from "react";
|
||||
|
||||
interface IProps {
|
||||
content: JSX.Element;
|
||||
}
|
||||
|
||||
function MessagePopup(props: IProps): React.ReactElement {
|
||||
return <>{props.content}</>;
|
||||
}
|
||||
|
||||
export function dialogBoxCreate(txt: string | JSX.Element, preformatted = false): void {
|
||||
const popupId =
|
||||
`popup-` +
|
||||
Array.from(Array(16))
|
||||
.map(() => `${getRandomInt(0, 9)}`)
|
||||
.join("");
|
||||
if (typeof txt === "string") {
|
||||
if (preformatted) {
|
||||
// For text files as they are often computed data that
|
||||
// shouldn't be wrapped and should retain tabstops.
|
||||
createPopup(popupId, MessagePopup, {
|
||||
content: <pre dangerouslySetInnerHTML={{ __html: txt }} />,
|
||||
});
|
||||
} else {
|
||||
createPopup(popupId, MessagePopup, {
|
||||
content: (
|
||||
<p
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: txt.replace(/(?:\r\n|\r|\n)/g, "<br />"),
|
||||
}}
|
||||
/>
|
||||
),
|
||||
});
|
||||
}
|
||||
export function dialogBoxCreate(txt: string | JSX.Element): void {
|
||||
if (typeof txt !== "string") {
|
||||
AlertEvents.emit(txt);
|
||||
} else {
|
||||
createPopup(popupId, MessagePopup, {
|
||||
content: txt,
|
||||
});
|
||||
AlertEvents.emit(<pre dangerouslySetInnerHTML={{ __html: txt }} />);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
/**
|
||||
* React Component for a simple Error Boundary. The fallback UI for
|
||||
* this error boundary is simply a bordered text box
|
||||
*/
|
||||
import * as React from "react";
|
||||
|
||||
import { EventEmitter } from "../../utils/EventEmitter";
|
||||
|
||||
type IProps = {
|
||||
eventEmitterForReset?: EventEmitter<[]>;
|
||||
id?: string;
|
||||
};
|
||||
|
||||
type IState = {
|
||||
errorInfo: string;
|
||||
hasError: boolean;
|
||||
};
|
||||
|
||||
type IErrorInfo = {
|
||||
componentStack: string;
|
||||
};
|
||||
|
||||
// TODO: Move this out to a css file
|
||||
const styleMarkup = {
|
||||
border: "1px solid red",
|
||||
display: "inline-block",
|
||||
margin: "4px",
|
||||
padding: "4px",
|
||||
};
|
||||
|
||||
export class ErrorBoundary extends React.Component<IProps, IState> {
|
||||
unsubscribe: (() => void) | null = null;
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
errorInfo: "",
|
||||
hasError: false,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidCatch(error: Error, info: IErrorInfo): void {
|
||||
console.error(`Caught error in React ErrorBoundary. Component stack:`);
|
||||
console.error(info.componentStack);
|
||||
}
|
||||
|
||||
componentDidMount(): void {
|
||||
const cb = (): void => {
|
||||
this.setState({
|
||||
hasError: false,
|
||||
});
|
||||
};
|
||||
|
||||
if (this.hasEventEmitter()) {
|
||||
this.unsubscribe = (this.props.eventEmitterForReset as EventEmitter<[]>).subscribe(cb);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount(): void {
|
||||
if (this.unsubscribe !== null) {
|
||||
this.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
hasEventEmitter(): boolean {
|
||||
return (
|
||||
this.props.eventEmitterForReset != null &&
|
||||
this.props.eventEmitterForReset instanceof EventEmitter &&
|
||||
this.props.id != null &&
|
||||
typeof this.props.id === "string"
|
||||
);
|
||||
}
|
||||
|
||||
render(): React.ReactNode {
|
||||
if (this.state.hasError) {
|
||||
return (
|
||||
<div style={styleMarkup}>
|
||||
<p>{`Error rendering UI. This is (probably) a bug. Please report to game developer.`}</p>
|
||||
<p>{`In the meantime, try refreshing the game WITHOUT saving.`}</p>
|
||||
<p>{`Error info: ${this.state.errorInfo}`}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return this.props.children;
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(error: Error): IState {
|
||||
return {
|
||||
errorInfo: error.message,
|
||||
hasError: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
+14
-6
@@ -1,10 +1,18 @@
|
||||
import * as React from "react";
|
||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import makeStyles from "@mui/styles/makeStyles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
|
||||
export function Favor(favor: number | string): JSX.Element {
|
||||
return (
|
||||
<span className={"light-yellow samefont"}>
|
||||
{typeof favor === "number" ? numeralWrapper.formatFavor(favor) : favor}
|
||||
</span>
|
||||
);
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
favor: {
|
||||
color: theme.colors.rep,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
export function Favor({ favor }: { favor: number | string }): React.ReactElement {
|
||||
const classes = useStyles();
|
||||
return <span className={classes.favor}>{typeof favor === "number" ? numeralWrapper.formatFavor(favor) : favor}</span>;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from "react";
|
||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||
import { Hashes } from "../../ui/React/Hashes";
|
||||
|
||||
export function HashRate(hashes: number): JSX.Element {
|
||||
return Hashes(`${numeralWrapper.formatHashes(hashes)} / sec`);
|
||||
export function HashRate({ hashes }: { hashes: number }): React.ReactElement {
|
||||
return <Hashes hashes={`${numeralWrapper.formatHashes(hashes)} / sec`} />;
|
||||
}
|
||||
|
||||
+14
-4
@@ -1,10 +1,20 @@
|
||||
import * as React from "react";
|
||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import makeStyles from "@mui/styles/makeStyles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
|
||||
export function Hashes(hashes: number | string): JSX.Element {
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
money: {
|
||||
color: theme.colors.money,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
export function Hashes({ hashes }: { hashes: number | string }): React.ReactElement {
|
||||
const classes = useStyles();
|
||||
return (
|
||||
<span className={"money-gold samefont"}>
|
||||
{typeof hashes === "number" ? numeralWrapper.formatHashes(hashes) : hashes}
|
||||
</span>
|
||||
<span className={classes.money}>{typeof hashes === "number" ? numeralWrapper.formatHashes(hashes) : hashes}</span>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,134 +0,0 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import { killWorkerScript } from "../../Netscript/killWorkerScript";
|
||||
import { RunningScript } from "../../Script/RunningScript";
|
||||
|
||||
import { createElement } from "../uiHelpers/createElement";
|
||||
import { removeElementById } from "../uiHelpers/removeElementById";
|
||||
|
||||
let gameContainer: HTMLElement;
|
||||
|
||||
(function () {
|
||||
function getGameContainer(): void {
|
||||
const container = document.getElementById("root");
|
||||
if (container == null) {
|
||||
throw new Error(`Failed to find game container DOM element`);
|
||||
}
|
||||
|
||||
gameContainer = container;
|
||||
document.removeEventListener("DOMContentLoaded", getGameContainer);
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", getGameContainer);
|
||||
})();
|
||||
|
||||
interface IProps {
|
||||
script: RunningScript;
|
||||
container: HTMLElement;
|
||||
id: string;
|
||||
}
|
||||
|
||||
function ScriptLogPopup(props: IProps): React.ReactElement {
|
||||
const setRerender = useState(false)[1];
|
||||
|
||||
function rerender(): void {
|
||||
setRerender((old) => !old);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const id = setInterval(rerender, 1000);
|
||||
return () => clearInterval(id);
|
||||
}, []);
|
||||
|
||||
function close(): void {
|
||||
const content = document.getElementById(props.id);
|
||||
if (content == null) return;
|
||||
ReactDOM.unmountComponentAtNode(content);
|
||||
removeElementById(props.id);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
function closeHandler(event: KeyboardEvent): void {
|
||||
if (event.keyCode === 27) {
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("keydown", closeHandler);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener("keydown", closeHandler);
|
||||
};
|
||||
}, []);
|
||||
|
||||
function kill(): void {
|
||||
killWorkerScript(props.script, props.script.server, true);
|
||||
close();
|
||||
}
|
||||
|
||||
function drag(event: React.MouseEvent<HTMLElement, MouseEvent>): void {
|
||||
event.preventDefault();
|
||||
let x = event.clientX;
|
||||
let y = event.clientY;
|
||||
let left = props.container.offsetLeft + props.container.clientWidth / 2;
|
||||
let top = props.container.offsetTop + props.container.clientWidth / 5;
|
||||
function mouseMove(event: MouseEvent): void {
|
||||
left += event.clientX - x;
|
||||
top += event.clientY - y;
|
||||
props.container.style.left = left + "px";
|
||||
props.container.style.top = top + "px";
|
||||
// reset right and bottom to avoid the window stretching
|
||||
props.container.style.right = "";
|
||||
props.container.style.bottom = "";
|
||||
x = event.clientX;
|
||||
y = event.clientY;
|
||||
}
|
||||
function mouseUp(): void {
|
||||
document.removeEventListener("mouseup", mouseUp);
|
||||
document.removeEventListener("mousemove", mouseMove);
|
||||
}
|
||||
document.addEventListener("mouseup", mouseUp);
|
||||
document.addEventListener("mousemove", mouseMove);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="log-box-header" onMouseDown={drag}>
|
||||
<p>
|
||||
{props.script.filename} {props.script.args.map((x: any): string => `${x}`).join(" ")}
|
||||
</p>
|
||||
<div>
|
||||
<button className="log-box-button" onClick={kill}>
|
||||
Kill Script
|
||||
</button>
|
||||
<button className="log-box-button" onClick={close}>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="log-box-log-container">
|
||||
<p>
|
||||
{props.script.logs.map(
|
||||
(line: string, i: number): JSX.Element => (
|
||||
<span key={i} style={{ whiteSpace: "pre-line" }}>
|
||||
{line}
|
||||
<br />
|
||||
</span>
|
||||
),
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function logBoxCreate(script: RunningScript): void {
|
||||
const id = script.server + "-" + script.filename + script.args.map((x: any): string => `${x}`).join("-");
|
||||
if (document.getElementById(id) !== null) return;
|
||||
const container = createElement("div", {
|
||||
class: "log-box-container",
|
||||
id: id,
|
||||
});
|
||||
gameContainer.appendChild(container);
|
||||
ReactDOM.render(<ScriptLogPopup script={script} id={id} container={container} />, container);
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import { EventEmitter } from "../../utils/EventEmitter";
|
||||
import { RunningScript } from "../../Script/RunningScript";
|
||||
import { killWorkerScript } from "../../Netscript/killWorkerScript";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Box from "@mui/material/Box";
|
||||
import Button from "@mui/material/Button";
|
||||
import Paper from "@mui/material/Paper";
|
||||
|
||||
export const LogBoxEvents = new EventEmitter<[RunningScript]>();
|
||||
|
||||
interface Log {
|
||||
id: string;
|
||||
script: RunningScript;
|
||||
}
|
||||
|
||||
export function LogBoxManager(): React.ReactElement {
|
||||
const [logs, setLogs] = useState<Log[]>([]);
|
||||
useEffect(
|
||||
() =>
|
||||
LogBoxEvents.subscribe((script: RunningScript) => {
|
||||
const id = script.server + "-" + script.filename + script.args.map((x: any): string => `${x}`).join("-");
|
||||
setLogs((old) => {
|
||||
return [
|
||||
...old,
|
||||
{
|
||||
id: id,
|
||||
script: script,
|
||||
},
|
||||
];
|
||||
});
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
function close(id: string): void {
|
||||
setLogs((old) => old.filter((l) => l.id !== id));
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{logs.map((log) => (
|
||||
<LogWindow key={log.id} script={log.script} id={log.id} onClose={() => close(log.id)} />
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
interface IProps {
|
||||
script: RunningScript;
|
||||
id: string;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
function LogWindow(props: IProps): React.ReactElement {
|
||||
const container = useRef<HTMLDivElement>(null);
|
||||
const setRerender = useState(false)[1];
|
||||
function rerender(): void {
|
||||
setRerender((old) => !old);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const id = setInterval(rerender, 1000);
|
||||
return () => clearInterval(id);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
function closeHandler(event: KeyboardEvent): void {
|
||||
if (event.keyCode === 27) {
|
||||
props.onClose();
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("keydown", closeHandler);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener("keydown", closeHandler);
|
||||
};
|
||||
}, []);
|
||||
|
||||
function kill(): void {
|
||||
killWorkerScript(props.script, props.script.server, true);
|
||||
props.onClose();
|
||||
}
|
||||
|
||||
function drag(event: React.MouseEvent<HTMLElement, MouseEvent>): void {
|
||||
const c = container.current;
|
||||
if (c === null) return;
|
||||
event.preventDefault();
|
||||
let x = event.clientX;
|
||||
let y = event.clientY;
|
||||
let left = c.offsetLeft + c.clientWidth / 2;
|
||||
let top = c.offsetTop + c.clientWidth / 5;
|
||||
function mouseMove(event: MouseEvent): void {
|
||||
const c = container.current;
|
||||
if (c === null) return;
|
||||
left += event.clientX - x;
|
||||
top += event.clientY - y;
|
||||
c.style.left = left + "px";
|
||||
c.style.top = top + "px";
|
||||
// reset right and bottom to avoid the window stretching
|
||||
c.style.right = "";
|
||||
c.style.bottom = "";
|
||||
x = event.clientX;
|
||||
y = event.clientY;
|
||||
}
|
||||
function mouseUp(): void {
|
||||
document.removeEventListener("mouseup", mouseUp);
|
||||
document.removeEventListener("mousemove", mouseMove);
|
||||
}
|
||||
document.addEventListener("mouseup", mouseUp);
|
||||
document.addEventListener("mousemove", mouseMove);
|
||||
}
|
||||
|
||||
return (
|
||||
<Paper
|
||||
style={{
|
||||
display: "flex",
|
||||
flexFlow: "column",
|
||||
backgroundColor: "gray",
|
||||
width: "50%",
|
||||
position: "fixed",
|
||||
left: "50%",
|
||||
top: "40%",
|
||||
margin: "-10% 0 0 -25%",
|
||||
height: "auto",
|
||||
maxHeight: "50%",
|
||||
zIndex: 10,
|
||||
border: "2px solid $hacker-green",
|
||||
}}
|
||||
ref={container}
|
||||
>
|
||||
<Paper
|
||||
style={{
|
||||
cursor: "grab",
|
||||
}}
|
||||
>
|
||||
<Box display="flex" alignItems="center" onMouseDown={drag}>
|
||||
<Typography color="primary" variant="h6" noWrap component="div">
|
||||
{props.script.filename} {props.script.args.map((x: any): string => `${x}`).join(" ")}
|
||||
</Typography>
|
||||
|
||||
<Box display="flex" marginLeft="auto">
|
||||
<Button onClick={kill}>Kill Script</Button>
|
||||
<Button onClick={props.onClose}>Close</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Paper>
|
||||
<Paper>
|
||||
<Box maxHeight="25vh" overflow="scroll">
|
||||
{props.script.logs.map(
|
||||
(line: string, i: number): JSX.Element => (
|
||||
<Typography key={i} style={{ whiteSpace: "pre-line" }}>
|
||||
{line}
|
||||
<br />
|
||||
</Typography>
|
||||
),
|
||||
)}
|
||||
</Box>
|
||||
</Paper>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
+11
-2
@@ -33,13 +33,22 @@ const useStyles = makeStyles((theme: Theme) =>
|
||||
interface IProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
children: JSX.Element[] | JSX.Element | React.ReactElement[] | React.ReactElement;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const Modal = (props: IProps): React.ReactElement => {
|
||||
const classes = useStyles();
|
||||
return (
|
||||
<M open={props.open} onClose={props.onClose} closeAfterTransition className={classes.modal}>
|
||||
<M
|
||||
disableRestoreFocus
|
||||
disableScrollLock
|
||||
disableEnforceFocus
|
||||
disableAutoFocus
|
||||
open={props.open}
|
||||
onClose={props.onClose}
|
||||
closeAfterTransition
|
||||
className={classes.modal}
|
||||
>
|
||||
<Fade in={props.open}>
|
||||
<div className={classes.paper}>
|
||||
<Box sx={{ m: 2 }}>{props.children}</Box>
|
||||
|
||||
+18
-3
@@ -1,19 +1,34 @@
|
||||
import * as React from "react";
|
||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import makeStyles from "@mui/styles/makeStyles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
unbuyable: {
|
||||
color: theme.palette.action.disabled,
|
||||
},
|
||||
money: {
|
||||
color: theme.colors.money,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
interface IProps {
|
||||
money: number | string;
|
||||
player?: IPlayer;
|
||||
}
|
||||
export function Money(props: IProps): JSX.Element {
|
||||
export function Money(props: IProps): React.ReactElement {
|
||||
const classes = useStyles();
|
||||
if (props.player !== undefined) {
|
||||
if (typeof props.money !== "number") throw new Error("if player if provided, money should be number, contact dev");
|
||||
if (!props.player.canAfford(props.money))
|
||||
return <span className={"unbuyable samefont"}>{numeralWrapper.formatMoney(props.money)}</span>;
|
||||
return <span className={classes.unbuyable}>{numeralWrapper.formatMoney(props.money)}</span>;
|
||||
}
|
||||
return (
|
||||
<span className={"money-gold samefont"}>
|
||||
<span className={classes.money}>
|
||||
{typeof props.money === "number" ? numeralWrapper.formatMoney(props.money) : props.money}
|
||||
</span>
|
||||
);
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
/**
|
||||
* Wrapper around material-ui's Button component that styles it with
|
||||
* Bitburner's UI theme
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { Button, ButtonProps } from "@mui/material";
|
||||
import makeStyles from "@mui/styles/makeStyles";
|
||||
const useStyles = makeStyles({
|
||||
// Tries to emulate StdButton in buttons.scss
|
||||
root: {
|
||||
backgroundColor: "#555",
|
||||
border: "1px solid #333",
|
||||
color: "white",
|
||||
margin: "5px",
|
||||
padding: "3px 5px",
|
||||
"&:hover": {
|
||||
backgroundColor: "#666",
|
||||
},
|
||||
|
||||
borderRadius: 0,
|
||||
},
|
||||
textPrimary: {
|
||||
color: "rgb( 144, 202, 249)",
|
||||
},
|
||||
textSecondary: {
|
||||
color: "rgb(244, 143, 177)",
|
||||
},
|
||||
disabled: {
|
||||
backgroundColor: "#333",
|
||||
color: "#fff",
|
||||
cursor: "default",
|
||||
},
|
||||
});
|
||||
|
||||
export const MuiButton: React.FC<ButtonProps> = (props: ButtonProps) => {
|
||||
return (
|
||||
<Button
|
||||
{...props}
|
||||
classes={{
|
||||
...useStyles(),
|
||||
...props.classes,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -1,32 +0,0 @@
|
||||
/**
|
||||
* Wrapper around material-ui's Button component that styles it with
|
||||
* Bitburner's UI theme
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { Paper, PaperProps } from "@mui/material";
|
||||
|
||||
import makeStyles from "@mui/styles/makeStyles";
|
||||
|
||||
const useStyles = makeStyles({
|
||||
root: {
|
||||
backgroundColor: "rgb(30, 30, 30)",
|
||||
border: "2px solid #000",
|
||||
borderRadius: "10px",
|
||||
display: "inline-block",
|
||||
flexWrap: "wrap",
|
||||
padding: "10px",
|
||||
},
|
||||
});
|
||||
|
||||
export const MuiPaper: React.FC<PaperProps> = (props: PaperProps) => {
|
||||
return (
|
||||
<Paper
|
||||
{...props}
|
||||
classes={{
|
||||
...useStyles(),
|
||||
...props.classes,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -1,81 +0,0 @@
|
||||
/**
|
||||
* Wrapper around material-ui's TextField component that styles it with
|
||||
* Bitburner's UI theme
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { TextField, TextFieldProps } from "@mui/material";
|
||||
|
||||
import makeStyles from "@mui/styles/makeStyles";
|
||||
|
||||
const backgroundColorStyles = {
|
||||
backgroundColor: "rgba(57, 54, 54, 0.9)",
|
||||
"&:hover": {
|
||||
backgroundColor: "rgba(70, 70, 70, 0.9)",
|
||||
},
|
||||
};
|
||||
|
||||
const formControlStyles = {
|
||||
border: "1px solid #e2e2e1",
|
||||
overflow: "hidden",
|
||||
borderRadius: 4,
|
||||
color: "white",
|
||||
...backgroundColorStyles,
|
||||
};
|
||||
|
||||
const useStyles = makeStyles({
|
||||
root: {
|
||||
...formControlStyles,
|
||||
},
|
||||
});
|
||||
|
||||
const useInputStyles = makeStyles({
|
||||
root: {
|
||||
...backgroundColorStyles,
|
||||
color: "white",
|
||||
},
|
||||
focused: {
|
||||
backgroundColor: "rgba(70, 70, 70, 0.9)",
|
||||
},
|
||||
disabled: {
|
||||
color: "white",
|
||||
},
|
||||
});
|
||||
|
||||
const useLabelStyles = makeStyles({
|
||||
root: {
|
||||
color: "white",
|
||||
},
|
||||
focused: {
|
||||
color: "white !important", // Need important to override styles from FormLabel
|
||||
},
|
||||
disabled: {
|
||||
color: "white !important", // Need important to override styles from FormLabel
|
||||
},
|
||||
});
|
||||
|
||||
export const MuiTextField: React.FC<TextFieldProps> = (props: TextFieldProps) => {
|
||||
return (
|
||||
<TextField
|
||||
{...props}
|
||||
classes={{
|
||||
...useStyles(),
|
||||
...props.classes,
|
||||
}}
|
||||
InputProps={{
|
||||
classes: {
|
||||
...useInputStyles(),
|
||||
...props.InputProps?.classes,
|
||||
},
|
||||
...props.InputProps,
|
||||
}}
|
||||
InputLabelProps={{
|
||||
classes: {
|
||||
...useLabelStyles(),
|
||||
...props.InputLabelProps?.classes,
|
||||
},
|
||||
...props.InputLabelProps,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -23,8 +23,7 @@ export function Overview({ children }: IProps): React.ReactElement {
|
||||
const [open, setOpen] = useState(true);
|
||||
const classes = useStyles();
|
||||
const router = use.Router();
|
||||
if (router.page() === Page.BitVerse || router.page() === Page.HackingMission || router.page() === Page.Loading)
|
||||
return <></>;
|
||||
if (router.page() === Page.BitVerse || router.page() === Page.Loading) return <></>;
|
||||
let icon;
|
||||
if (open) {
|
||||
icon = <VisibilityOffIcon color="primary" />;
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
/**
|
||||
* Text (p Element) with Tooltip
|
||||
*/
|
||||
import * as React from "react";
|
||||
|
||||
export interface IParagraphWithTooltipProps {
|
||||
style?: any;
|
||||
content: JSX.Element;
|
||||
tooltip: string | React.ReactElement | JSX.Element;
|
||||
}
|
||||
|
||||
export class ParagraphWithTooltip extends React.Component<IParagraphWithTooltipProps, any> {
|
||||
render(): React.ReactNode {
|
||||
return (
|
||||
<div className={"tooltip"} style={this.props.style}>
|
||||
<p>{this.props.content}</p>
|
||||
<span className={"tooltiptext"}>{this.props.tooltip}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
/**
|
||||
* Basic button for popup dialog boxes
|
||||
* It creates an event handler such that pressing Esc will perform the click handler.
|
||||
*
|
||||
* Should only be used in other React components, otherwise it may not be properly
|
||||
* unmounted
|
||||
*/
|
||||
import * as React from "react";
|
||||
import * as ReactDOM from "react-dom";
|
||||
|
||||
import { KEY } from "../../utils/helpers/keyCodes";
|
||||
import { removeElement } from "../uiHelpers/removeElement";
|
||||
|
||||
export interface IPopupButtonProps {
|
||||
class?: string;
|
||||
popup: HTMLElement | string;
|
||||
style?: any;
|
||||
text: string;
|
||||
onClose?: () => void;
|
||||
}
|
||||
|
||||
export class PopupButton extends React.Component<IPopupButtonProps, any> {
|
||||
constructor(props: IPopupButtonProps) {
|
||||
super(props);
|
||||
this.handleClick = this.handleClick.bind(this);
|
||||
this.keyListener = this.keyListener.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount(): void {
|
||||
document.addEventListener("keydown", this.keyListener);
|
||||
}
|
||||
|
||||
componentWillUnmount(): void {
|
||||
document.removeEventListener("keydown", this.keyListener);
|
||||
}
|
||||
|
||||
handleClick(): void {
|
||||
if (this.props.onClose) this.props.onClose();
|
||||
//We might be able to remove this?
|
||||
//Clickhandler from the props will override this anyhow.
|
||||
let popup: HTMLElement | null;
|
||||
if (typeof this.props.popup === "string") {
|
||||
popup = document.getElementById(this.props.popup);
|
||||
} else {
|
||||
popup = this.props.popup;
|
||||
}
|
||||
// TODO Check if this is okay? This is essentially calling to unmount a parent component
|
||||
if (popup instanceof HTMLElement) {
|
||||
ReactDOM.unmountComponentAtNode(popup); // Removes everything inside the wrapper container
|
||||
removeElement(popup); // Removes the wrapper container
|
||||
}
|
||||
}
|
||||
|
||||
keyListener(e: KeyboardEvent): void {
|
||||
//This doesn't really make sense, a button doesnt have to listen to escape IMO
|
||||
//Too affraid to remove it since im not sure what it will break.. But yuck..
|
||||
if (e.keyCode === KEY.ESC) {
|
||||
this.handleClick();
|
||||
}
|
||||
}
|
||||
|
||||
render(): React.ReactNode {
|
||||
const className = this.props.class ? this.props.class : "std-button";
|
||||
|
||||
return (
|
||||
<button className={className} onClick={this.handleClick} style={this.props.style}>
|
||||
{this.props.text}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
/**
|
||||
* Close button for popup dialog boxes
|
||||
* It creates an event handler such that pressing Esc will close the binded popup
|
||||
*
|
||||
* Should only be used in other React components, otherwise it may not be properly
|
||||
* unmounted
|
||||
*/
|
||||
import * as React from "react";
|
||||
import * as ReactDOM from "react-dom";
|
||||
|
||||
import { removeElement } from "../uiHelpers/removeElement";
|
||||
import { IPopupButtonProps, PopupButton } from "./PopupButton";
|
||||
|
||||
export interface IPopupCloseButtonProps extends IPopupButtonProps {
|
||||
class?: string;
|
||||
popup: HTMLElement | string;
|
||||
style?: any;
|
||||
text: string;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export class PopupCloseButton extends PopupButton {
|
||||
constructor(props: IPopupCloseButtonProps) {
|
||||
super(props);
|
||||
|
||||
this.closePopup = this.closePopup.bind(this);
|
||||
}
|
||||
|
||||
closePopup(): void {
|
||||
if (this.props.onClose) this.props.onClose();
|
||||
let popup: HTMLElement | null;
|
||||
if (typeof this.props.popup === "string") {
|
||||
popup = document.getElementById(this.props.popup);
|
||||
} else {
|
||||
popup = this.props.popup;
|
||||
}
|
||||
|
||||
// TODO Check if this is okay? This is essentially calling to unmount a
|
||||
// parent component
|
||||
if (popup instanceof HTMLElement) {
|
||||
// Removes everything inside the wrapper container
|
||||
ReactDOM.unmountComponentAtNode(popup);
|
||||
removeElement(popup); // Removes the wrapper container
|
||||
}
|
||||
}
|
||||
|
||||
render(): React.ReactNode {
|
||||
const className = this.props.class ? this.props.class : "std-button";
|
||||
|
||||
return (
|
||||
<button className={className} onClick={this.closePopup} style={this.props.style}>
|
||||
{this.props.text}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { EventEmitter } from "../../utils/EventEmitter";
|
||||
import { Modal } from "../../ui/React/Modal";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Button from "@mui/material/Button";
|
||||
|
||||
export const PromptEvent = new EventEmitter<[Prompt]>();
|
||||
|
||||
interface Prompt {
|
||||
txt: string;
|
||||
resolve: (result: boolean) => void;
|
||||
}
|
||||
|
||||
export function PromptManager(): React.ReactElement {
|
||||
const [prompt, setPrompt] = useState<Prompt | null>(null);
|
||||
useEffect(
|
||||
() =>
|
||||
PromptEvent.subscribe((p: Prompt) => {
|
||||
setPrompt(p);
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
function close(): void {
|
||||
if (prompt === null) return;
|
||||
prompt.resolve(false);
|
||||
setPrompt(null);
|
||||
}
|
||||
|
||||
function yes(): void {
|
||||
if (prompt === null) return;
|
||||
prompt.resolve(true);
|
||||
setPrompt(null);
|
||||
}
|
||||
function no(): void {
|
||||
if (prompt === null) return;
|
||||
prompt.resolve(false);
|
||||
setPrompt(null);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{prompt != null && (
|
||||
<Modal open={true} onClose={close}>
|
||||
<Typography>{prompt.txt}</Typography>
|
||||
<Button onClick={yes}>Yes</Button>
|
||||
<Button onClick={no}>No</Button>
|
||||
</Modal>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,9 +1,21 @@
|
||||
import * as React from "react";
|
||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import makeStyles from "@mui/styles/makeStyles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
|
||||
export function Reputation(reputation: number | string): JSX.Element {
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
reputation: {
|
||||
color: theme.colors.rep,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
export function Reputation({ reputation }: { reputation: number | string }): React.ReactElement {
|
||||
const classes = useStyles();
|
||||
return (
|
||||
<span className={"reputation samefont"}>
|
||||
<span className={classes.reputation}>
|
||||
{typeof reputation === "number" ? numeralWrapper.formatReputation(reputation) : reputation}
|
||||
</span>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from "react";
|
||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||
import { Reputation } from "../../ui/React/Reputation";
|
||||
|
||||
export function ReputationRate(reputation: number): JSX.Element {
|
||||
return Reputation(`${numeralWrapper.formatReputation(reputation)} / sec`);
|
||||
export function ReputationRate({ reputation }: { reputation: number }): React.ReactElement {
|
||||
return <Reputation reputation={`${numeralWrapper.formatReputation(reputation)} / sec`} />;
|
||||
}
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
/**
|
||||
* Wrapper around material-ui's Button component that styles it with
|
||||
* Bitburner's UI theme
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { Select as MuiSelect, SelectProps } from "@mui/material";
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import makeStyles from "@mui/styles/makeStyles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
root: {
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
color: theme.palette.primary.dark,
|
||||
margin: "5px",
|
||||
padding: "3px 5px",
|
||||
"&:after": {
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
},
|
||||
|
||||
borderRadius: 0,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
export const Select: React.FC<SelectProps> = (props: SelectProps) => {
|
||||
return (
|
||||
<MuiSelect
|
||||
{...props}
|
||||
classes={{
|
||||
...useStyles(),
|
||||
...props.classes,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -61,7 +61,7 @@ export function ServerDropdown(props: IProps): React.ReactElement {
|
||||
}
|
||||
|
||||
return (
|
||||
<Select sx={{ mx: 1 }} variant="standard" value={props.value} onChange={props.onChange}>
|
||||
<Select sx={{ mx: 1 }} value={props.value} onChange={props.onChange}>
|
||||
{servers}
|
||||
</Select>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import React from "react";
|
||||
import { Modal } from "./Modal";
|
||||
|
||||
interface IProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
children: JSX.Element[] | JSX.Element | React.ReactElement[] | React.ReactElement;
|
||||
}
|
||||
|
||||
export function StaticModal(props: IProps): React.ReactElement {
|
||||
return (
|
||||
<Modal open={props.open} onClose={props.onClose}>
|
||||
{props.children}
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
@@ -19,8 +19,8 @@ export function StatsTable({ rows, title, wide }: IProps): React.ReactElement {
|
||||
{title && <Typography>{title}</Typography>}
|
||||
<T size="small" padding="none">
|
||||
<TableBody>
|
||||
{rows.map((row: any[]) => (
|
||||
<TableRow key={row[0]}>
|
||||
{rows.map((row: any[], i: number) => (
|
||||
<TableRow key={i}>
|
||||
{row.map((elem: any, i: number) => (
|
||||
<TableCell key={i} align={i !== 0 ? "right" : "left"}>
|
||||
<Typography noWrap>{elem}</Typography>
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
/**
|
||||
* Basic stateless button
|
||||
* Uses the 'std-button' css class
|
||||
*/
|
||||
import * as React from "react";
|
||||
|
||||
interface IStdButtonProps {
|
||||
addClasses?: string;
|
||||
autoFocus?: boolean;
|
||||
disabled?: boolean;
|
||||
id?: string;
|
||||
onClick?: (e: React.MouseEvent<HTMLElement>) => any;
|
||||
onKeyUp?: (e: React.KeyboardEvent<HTMLElement>) => any;
|
||||
style?: any;
|
||||
text: string | JSX.Element;
|
||||
tooltip?: string | JSX.Element;
|
||||
}
|
||||
|
||||
type IInnerHTMLMarkup = {
|
||||
__html: string;
|
||||
};
|
||||
|
||||
export function StdButton(props: IStdButtonProps): React.ReactElement {
|
||||
const hasTooltip = props.tooltip != null && props.tooltip !== "";
|
||||
let className = props.disabled ? "std-button-disabled" : "std-button";
|
||||
if (hasTooltip) {
|
||||
className += " tooltip";
|
||||
}
|
||||
|
||||
if (typeof props.addClasses === "string") {
|
||||
className += ` ${props.addClasses}`;
|
||||
}
|
||||
|
||||
// Tooltip will be set using inner HTML
|
||||
let tooltip;
|
||||
if (hasTooltip) {
|
||||
if (typeof props.tooltip === "string") {
|
||||
const tooltipMarkup: IInnerHTMLMarkup = {
|
||||
__html: props.tooltip,
|
||||
};
|
||||
tooltip = <span className={"tooltiptext"} dangerouslySetInnerHTML={tooltipMarkup}></span>;
|
||||
} else {
|
||||
tooltip = <span className={"tooltiptext"}>{props.tooltip}</span>;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
className={className}
|
||||
id={props.id}
|
||||
onClick={props.onClick}
|
||||
onKeyUp={props.onKeyUp}
|
||||
style={props.style}
|
||||
autoFocus={props.autoFocus}
|
||||
>
|
||||
{props.text}
|
||||
{hasTooltip && tooltip}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
/**
|
||||
* Stateless button that represents something that has been purchased.
|
||||
*/
|
||||
import * as React from "react";
|
||||
|
||||
interface IStdButtonPurchasedProps {
|
||||
onClick?: (e: React.MouseEvent<HTMLElement>) => any;
|
||||
style?: any;
|
||||
text: string;
|
||||
tooltip?: string;
|
||||
}
|
||||
|
||||
type IInnerHTMLMarkup = {
|
||||
__html: string;
|
||||
};
|
||||
|
||||
export class StdButtonPurchased extends React.Component<IStdButtonPurchasedProps, any> {
|
||||
constructor(props: IStdButtonPurchasedProps) {
|
||||
super(props);
|
||||
this.hasTooltip = this.hasTooltip.bind(this);
|
||||
this.tooltip = this.tooltip.bind(this);
|
||||
}
|
||||
|
||||
hasTooltip(): boolean {
|
||||
return this.props.tooltip != null && this.props.tooltip !== "";
|
||||
}
|
||||
|
||||
tooltip(): string {
|
||||
if (!this.props.tooltip) return "";
|
||||
return this.props.tooltip;
|
||||
}
|
||||
|
||||
render(): React.ReactNode {
|
||||
let className = "std-button-bought";
|
||||
if (this.hasTooltip()) {
|
||||
className += " tooltip";
|
||||
}
|
||||
|
||||
// Tooltip will be set using inner HTML
|
||||
let tooltipMarkup: IInnerHTMLMarkup = {
|
||||
__html: "",
|
||||
};
|
||||
if (this.hasTooltip()) {
|
||||
tooltipMarkup = {
|
||||
__html: this.tooltip(),
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<button className={className} onClick={this.props.onClick} style={this.props.style}>
|
||||
{this.props.text}
|
||||
{this.hasTooltip() && <span className={"tooltiptext"} dangerouslySetInnerHTML={tooltipMarkup}></span>}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -73,6 +73,9 @@ export function refreshTheme(): void {
|
||||
default: Settings.theme.black,
|
||||
paper: Settings.theme.well,
|
||||
},
|
||||
action: {
|
||||
disabled: Settings.theme.disabled,
|
||||
},
|
||||
},
|
||||
typography: {
|
||||
fontFamily: "monospace",
|
||||
@@ -147,6 +150,19 @@ export function refreshTheme(): void {
|
||||
color: Settings.theme.primary,
|
||||
},
|
||||
},
|
||||
defaultProps: {
|
||||
variant: "standard",
|
||||
},
|
||||
},
|
||||
MuiTextField: {
|
||||
defaultProps: {
|
||||
variant: "standard",
|
||||
},
|
||||
},
|
||||
MuiTypography: {
|
||||
defaultProps: {
|
||||
color: "primary",
|
||||
},
|
||||
},
|
||||
MuiMenu: {
|
||||
styleOverrides: {
|
||||
@@ -194,6 +210,9 @@ export function refreshTheme(): void {
|
||||
maxWidth: "100vh",
|
||||
},
|
||||
},
|
||||
defaultProps: {
|
||||
disableInteractive: true,
|
||||
},
|
||||
},
|
||||
MuiSlider: {
|
||||
styleOverrides: {
|
||||
@@ -258,6 +277,16 @@ export function refreshTheme(): void {
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiTab: {
|
||||
styleOverrides: {
|
||||
textColorPrimary: {
|
||||
color: Settings.theme.secondary,
|
||||
"&.Mui-selected": {
|
||||
color: Settings.theme.primary,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -22,7 +22,10 @@ interface IColorEditorProps {
|
||||
}
|
||||
|
||||
function ColorEditor({ name, onColorChange, color, defaultColor }: IColorEditorProps): React.ReactElement {
|
||||
if (color === undefined) throw new Error("should not happen");
|
||||
if (color === undefined) {
|
||||
console.error(`color ${name} was undefined, reverting to default`);
|
||||
color = defaultColor;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -30,7 +33,6 @@ function ColorEditor({ name, onColorChange, color, defaultColor }: IColorEditorP
|
||||
sx={{ mx: 1 }}
|
||||
label={name}
|
||||
value={color}
|
||||
variant="standard"
|
||||
InputProps={{
|
||||
startAdornment: (
|
||||
<>
|
||||
@@ -264,6 +266,12 @@ export function ThemeEditorModal(props: IProps): React.ReactElement {
|
||||
color={customTheme["rep"]}
|
||||
defaultColor={defaultSettings.theme["rep"]}
|
||||
/>
|
||||
<ColorEditor
|
||||
name="disabled"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["disabled"]}
|
||||
defaultColor={defaultSettings.theme["disabled"]}
|
||||
/>
|
||||
<br />
|
||||
<br />
|
||||
<TextField label={"import / export theme"} value={JSON.stringify(customTheme)} onChange={onThemeChange} />
|
||||
|
||||
@@ -12,10 +12,10 @@ interface ICityProps {
|
||||
function City(props: ICityProps): React.ReactElement {
|
||||
if (props.city !== props.currentCity) {
|
||||
return (
|
||||
<Tooltip title={props.city}>
|
||||
<Tooltip title={<Typography>{props.city}</Typography>}>
|
||||
<span
|
||||
onClick={() => props.onTravel(props.city)}
|
||||
style={{ color: "white", lineHeight: "1em", whiteSpace: "pre" }}
|
||||
style={{ color: "white", lineHeight: "1em", whiteSpace: "pre", cursor: "pointer" }}
|
||||
>
|
||||
{props.city[0]}
|
||||
</span>
|
||||
@@ -33,7 +33,7 @@ interface IProps {
|
||||
export function WorldMap(props: IProps): React.ReactElement {
|
||||
// prettier-ignore
|
||||
return (
|
||||
<div className="noselect">
|
||||
<>
|
||||
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> ,_ . ._. _. .</Typography>
|
||||
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> , _-\','|~\~ ~/ ;-'_ _-' ,;_;_, ~~-</Typography>
|
||||
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> /~~-\_/-'~'--' \~~| ', ,' / / ~|-_\_/~/~ ~~--~~~~'--_</Typography>
|
||||
@@ -56,6 +56,6 @@ export function WorldMap(props: IProps): React.ReactElement {
|
||||
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> / ,' ~</Typography>
|
||||
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> ',| ~</Typography>
|
||||
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> ~'</Typography>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
/**
|
||||
* Create a pop-up dialog box using React.
|
||||
*
|
||||
* Calling this function with the same ID and React Root Component will trigger a re-render
|
||||
*
|
||||
* @param id The (hopefully) unique identifier for the popup container
|
||||
* @param rootComponent Root React Component for the content (NOT the popup containers themselves)
|
||||
*/
|
||||
import * as React from "react";
|
||||
import * as ReactDOM from "react-dom";
|
||||
|
||||
import { Popup } from "./Popup";
|
||||
|
||||
import { createElement } from "../uiHelpers/createElement";
|
||||
import { removeElementById } from "../uiHelpers/removeElementById";
|
||||
|
||||
let gameContainer: HTMLElement;
|
||||
|
||||
(function () {
|
||||
function getGameContainer(): void {
|
||||
const container = document.getElementById("root");
|
||||
if (container == null) {
|
||||
throw new Error(`Failed to find game container DOM element`);
|
||||
}
|
||||
|
||||
gameContainer = container;
|
||||
document.removeEventListener("DOMContentLoaded", getGameContainer);
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", getGameContainer);
|
||||
})();
|
||||
|
||||
// This variable is used to avoid setting the semi-transparent background
|
||||
// several times on top of one another. Sometimes there's several popup at once.
|
||||
let deepestPopupId = "";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
export function createPopup<T>(
|
||||
id: string,
|
||||
rootComponent: (props: T) => React.ReactElement,
|
||||
props: T,
|
||||
onClose?: () => void,
|
||||
): HTMLElement | null {
|
||||
let container = document.getElementById(id);
|
||||
if (container == null) {
|
||||
function onClick(this: HTMLElement, event: MouseEvent): void {
|
||||
if (!event.srcElement) return;
|
||||
if (!(event.srcElement instanceof HTMLElement)) return;
|
||||
const clickedId = (event.srcElement as HTMLElement).id;
|
||||
if (clickedId !== id) return;
|
||||
removePopup(id);
|
||||
if (onClose) onClose();
|
||||
}
|
||||
const backgroundColor = deepestPopupId === "" ? "rgba(0,0,0,0.5)" : "rgba(0,0,0,0)";
|
||||
container = createElement("div", {
|
||||
class: "popup-box-container",
|
||||
display: "flex",
|
||||
id: id,
|
||||
backgroundColor: backgroundColor,
|
||||
mouseDown: onClick,
|
||||
});
|
||||
|
||||
gameContainer.appendChild(container);
|
||||
}
|
||||
|
||||
if (deepestPopupId === "") deepestPopupId = id;
|
||||
ReactDOM.render(
|
||||
<Popup
|
||||
content={rootComponent}
|
||||
id={id}
|
||||
props={props}
|
||||
removePopup={() => {
|
||||
removePopup(id);
|
||||
if (onClose) onClose();
|
||||
}}
|
||||
/>,
|
||||
container,
|
||||
);
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes a popup created with the createPopup() function above
|
||||
*/
|
||||
export function removePopup(id: string): void {
|
||||
const content = document.getElementById(`${id}`);
|
||||
if (content == null) return;
|
||||
|
||||
ReactDOM.unmountComponentAtNode(content);
|
||||
|
||||
removeElementById(id);
|
||||
removeElementById(`${id}-close`);
|
||||
if (id === deepestPopupId) deepestPopupId = "";
|
||||
}
|
||||
@@ -33,7 +33,6 @@ export enum Page {
|
||||
Work,
|
||||
BladeburnerCinematic,
|
||||
Location,
|
||||
HackingMission,
|
||||
Loading,
|
||||
StaneksGift,
|
||||
}
|
||||
@@ -75,6 +74,5 @@ export interface IRouter {
|
||||
toWork(): void;
|
||||
toBladeburnerCinematic(): void;
|
||||
toLocation(location: Location): void;
|
||||
toHackingMission(faction: Faction): void;
|
||||
toStaneksGift(): void;
|
||||
}
|
||||
|
||||
@@ -49,7 +49,8 @@ export function WorkInProgressRoot(): React.ReactElement {
|
||||
<Typography>
|
||||
You are currently {player.currentWorkFactionDescription} for your faction {faction.name}
|
||||
<br />
|
||||
(Current Faction Reputation: {Reputation(faction.playerReputation)}). <br />
|
||||
(Current Faction Reputation: <Reputation reputation={faction.playerReputation} />
|
||||
). <br />
|
||||
You have been doing this for {convertTimeMsToTimeElapsedString(player.timeWorked)}
|
||||
<br />
|
||||
<br />
|
||||
@@ -58,8 +59,8 @@ export function WorkInProgressRoot(): React.ReactElement {
|
||||
<Money money={player.workMoneyGained} /> (<MoneyRate money={player.workMoneyGainRate * CYCLES_PER_SEC} />){" "}
|
||||
<br />
|
||||
<br />
|
||||
{Reputation(player.workRepGained)} ({ReputationRate(player.workRepGainRate * CYCLES_PER_SEC)}) reputation
|
||||
for this faction <br />
|
||||
<Reputation reputation={player.workRepGained} /> (
|
||||
<ReputationRate reputation={player.workRepGainRate * CYCLES_PER_SEC} />) reputation for this faction <br />
|
||||
<br />
|
||||
{numeralWrapper.formatExp(player.workHackExpGained)} (
|
||||
{numeralWrapper.formatExp(player.workHackExpGainRate * CYCLES_PER_SEC)} / sec) hacking exp <br />
|
||||
@@ -171,7 +172,7 @@ export function WorkInProgressRoot(): React.ReactElement {
|
||||
<Grid item>
|
||||
<Typography>
|
||||
You are currently working as a {position} at {player.companyName} (Current Company Reputation:{" "}
|
||||
{Reputation(companyRep)})<br />
|
||||
<Reputation reputation={companyRep} />)<br />
|
||||
<br />
|
||||
You have been working for {convertTimeMsToTimeElapsedString(player.timeWorked)}
|
||||
<br />
|
||||
@@ -181,8 +182,8 @@ export function WorkInProgressRoot(): React.ReactElement {
|
||||
<Money money={player.workMoneyGained} /> (<MoneyRate money={player.workMoneyGainRate * CYCLES_PER_SEC} />){" "}
|
||||
<br />
|
||||
<br />
|
||||
{Reputation(player.workRepGained)} ({ReputationRate(player.workRepGainRate * CYCLES_PER_SEC)}) reputation
|
||||
for this company <br />
|
||||
<Reputation reputation={player.workRepGained} /> (
|
||||
<ReputationRate reputation={player.workRepGainRate * CYCLES_PER_SEC} />) reputation for this company <br />
|
||||
<br />
|
||||
{numeralWrapper.formatExp(player.workHackExpGained)} (
|
||||
{`${numeralWrapper.formatExp(player.workHackExpGainRate * CYCLES_PER_SEC)} / sec`}
|
||||
@@ -241,7 +242,7 @@ export function WorkInProgressRoot(): React.ReactElement {
|
||||
<Grid item>
|
||||
<Typography>
|
||||
You are currently working as a {position} at {player.companyName} (Current Company Reputation:{" "}
|
||||
{Reputation(companyRep)})<br />
|
||||
<Reputation reputation={companyRep} />)<br />
|
||||
<br />
|
||||
You have been working for {convertTimeMsToTimeElapsedString(player.timeWorked)}
|
||||
<br />
|
||||
@@ -251,8 +252,8 @@ export function WorkInProgressRoot(): React.ReactElement {
|
||||
<Money money={player.workMoneyGained} /> (<MoneyRate money={player.workMoneyGainRate * CYCLES_PER_SEC} />){" "}
|
||||
<br />
|
||||
<br />
|
||||
{Reputation(player.workRepGained)} (
|
||||
{Reputation(`${numeralWrapper.formatExp(player.workRepGainRate * CYCLES_PER_SEC)} / sec`)}
|
||||
<Reputation reputation={player.workRepGained} /> (
|
||||
<ReputationRate reputation={player.workRepGainRate * CYCLES_PER_SEC} />
|
||||
) reputation for this company <br />
|
||||
<br />
|
||||
{numeralWrapper.formatExp(player.workHackExpGained)} (
|
||||
@@ -306,12 +307,12 @@ export function WorkInProgressRoot(): React.ReactElement {
|
||||
<Grid container direction="column" justifyContent="center" alignItems="center" style={{ minHeight: "100vh" }}>
|
||||
<Grid item>
|
||||
<Typography>
|
||||
<p>You are attempting to {player.crimeType}.</p>
|
||||
<Typography>You are attempting to {player.crimeType}.</Typography>
|
||||
<br />
|
||||
|
||||
<p>
|
||||
<Typography>
|
||||
Time remaining: {convertTimeMsToTimeElapsedString(player.timeNeededToCompleteWork - player.timeWorked)}
|
||||
</p>
|
||||
</Typography>
|
||||
|
||||
<br />
|
||||
<pre>{progressBar}</pre>
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
import { setTimeoutRef } from "../utils/SetTimeoutRef";
|
||||
import { getElementById } from "./uiHelpers/getElementById";
|
||||
import { Action } from "../types";
|
||||
|
||||
const threeSeconds = 3000;
|
||||
let x: number | undefined;
|
||||
|
||||
/**
|
||||
* Displays a status message to the player for approximately 3 seconds.
|
||||
* @param text The status text to display
|
||||
*/
|
||||
export function createStatusText(text: string): void {
|
||||
const statusElement: HTMLElement = getElementById("status-text");
|
||||
const handler: Action = () => {
|
||||
statusElement.innerText = "";
|
||||
statusElement.style.display = "none";
|
||||
statusElement.classList.remove("status-text");
|
||||
};
|
||||
|
||||
if (x !== undefined) {
|
||||
clearTimeout(x);
|
||||
// Likely not needed due to clearTimeout, but just in case...
|
||||
x = undefined;
|
||||
// reset the element's animation
|
||||
statusElement.style.animation = "none";
|
||||
setTimeout(function () {
|
||||
statusElement.style.animation = "";
|
||||
}, 10);
|
||||
}
|
||||
|
||||
statusElement.style.display = "block";
|
||||
statusElement.classList.add("status-text");
|
||||
statusElement.innerText = text;
|
||||
|
||||
x = setTimeoutRef(handler, threeSeconds);
|
||||
}
|
||||
@@ -1,161 +0,0 @@
|
||||
/**
|
||||
* The full-screen page the player is currently be on.
|
||||
* These pages are mutually exclusive.
|
||||
*/
|
||||
export enum Page {
|
||||
/**
|
||||
* (Default) The terminal is where the player issues all commands, executes scripts, etc.
|
||||
*/
|
||||
Terminal = "Terminal",
|
||||
|
||||
/**
|
||||
* Displays most of the statistics about the player.
|
||||
*/
|
||||
CharacterInfo = "CharacterInfo",
|
||||
|
||||
/**
|
||||
* The console for editing Netscript files.
|
||||
*/
|
||||
ScriptEditor = "ScriptEditor",
|
||||
|
||||
/**
|
||||
* Monitor the scripts currently executing across the servers.
|
||||
*/
|
||||
ActiveScripts = "ActiveScripts",
|
||||
|
||||
/**
|
||||
* View, purchase, and upgrade Hacknet nodes.
|
||||
*/
|
||||
HacknetNodes = "HacknetNodes",
|
||||
|
||||
/**
|
||||
* The list of programs the player could potentially build.
|
||||
*/
|
||||
CreateProgram = "CreateProgram",
|
||||
|
||||
/**
|
||||
* The list of all factions, and invites, available to the player.
|
||||
*/
|
||||
Factions = "Factions",
|
||||
|
||||
/**
|
||||
* Information about a specific faction.
|
||||
*/
|
||||
Faction = "Faction",
|
||||
|
||||
/**
|
||||
* The list of installed, and yet-to-be installed, augmentations the player has purchased.
|
||||
*/
|
||||
Augmentations = "Augmentations",
|
||||
|
||||
/**
|
||||
* List of milestones that players should follow.
|
||||
*/
|
||||
Milestones = "Milestones",
|
||||
|
||||
/**
|
||||
* A collection of in-game material to learn about the game.
|
||||
*/
|
||||
Tutorial = "Tutorial",
|
||||
|
||||
/**
|
||||
* A collection of items to manipulate the state of the game. Useful for development.
|
||||
*/
|
||||
DevMenu = "Dev Menu",
|
||||
|
||||
/**
|
||||
* Visiting a location in the world
|
||||
*/
|
||||
Location = "Location",
|
||||
|
||||
/**
|
||||
* A blocking page to show the player they are currently doing some action (building a program, working, etc.).
|
||||
*/
|
||||
workInProgress = "WorkInProgress",
|
||||
|
||||
/**
|
||||
* A special screen to show the player they've reached a certain point in the game.
|
||||
*/
|
||||
RedPill = "RedPill",
|
||||
|
||||
/**
|
||||
* A special screen to show the player they've reached a certain point in the game.
|
||||
*/
|
||||
CinematicText = "CinematicText",
|
||||
|
||||
/**
|
||||
* Mini-game to infiltrate a company, gaining experience from successful progress.
|
||||
*/
|
||||
Infiltration = "Infiltration",
|
||||
|
||||
/**
|
||||
* View the in-game stock market.
|
||||
*/
|
||||
StockMarket = "StockMarket",
|
||||
|
||||
/**
|
||||
* Manage gang actions and members.
|
||||
*/
|
||||
Gang = "Gang",
|
||||
|
||||
/**
|
||||
* Perform missions for a Faction.
|
||||
*/
|
||||
Mission = "Mission",
|
||||
|
||||
/**
|
||||
* Manage a corporation.
|
||||
*/
|
||||
Corporation = "Corporation",
|
||||
|
||||
/**
|
||||
* Manage special Bladeburner activities.
|
||||
*/
|
||||
Bladeburner = "Bladeburner",
|
||||
|
||||
/**
|
||||
* Manage your Sleeves
|
||||
*/
|
||||
Sleeves = "Sleeves",
|
||||
|
||||
/**
|
||||
* Purchase Resleeves
|
||||
*/
|
||||
Resleeves = "Re-sleeving",
|
||||
|
||||
/**
|
||||
* Game options
|
||||
*/
|
||||
GameOptions = "GameOptions",
|
||||
}
|
||||
|
||||
/**
|
||||
* This class keeps track of player navigation/routing within the game.
|
||||
*/
|
||||
class Routing {
|
||||
/**
|
||||
* Tracking the what page the user is currently on.
|
||||
*/
|
||||
private currentPage: Page | null = null;
|
||||
|
||||
/**
|
||||
* Determines if the player is currently on the specified page.
|
||||
* @param page The page to compare against the current state.
|
||||
*/
|
||||
isOn(page: Page): boolean {
|
||||
return this.currentPage === page;
|
||||
}
|
||||
|
||||
/**
|
||||
* Routes the player to the appropriate page.
|
||||
* @param page The page to navigate to.
|
||||
*/
|
||||
navigateTo(page: Page): void {
|
||||
this.currentPage = page;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The routing instance for tracking page navigation.
|
||||
*/
|
||||
export const routing: Routing = new Routing();
|
||||
@@ -1,29 +0,0 @@
|
||||
import { getElementById } from "./getElementById";
|
||||
|
||||
/**
|
||||
* Given an element by its ID, removes all event listeners from that element by cloning and
|
||||
* replacing. Then returns the new cloned element.
|
||||
* @param elemId The HTML ID to retrieve the element by.
|
||||
*/
|
||||
export function clearEventListeners(elemId: string | HTMLElement): HTMLElement | null {
|
||||
try {
|
||||
let elem: HTMLElement;
|
||||
if (typeof elemId === "string") {
|
||||
elem = getElementById(elemId);
|
||||
} else {
|
||||
elem = elemId;
|
||||
}
|
||||
|
||||
const newElem: HTMLElement = elem.cloneNode(true) as HTMLElement;
|
||||
if (elem.parentNode !== null) {
|
||||
elem.parentNode.replaceChild(newElem, elem);
|
||||
}
|
||||
|
||||
return newElem;
|
||||
} catch (e) {
|
||||
// tslint:disable-next-line:no-console
|
||||
console.error(e);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,308 +0,0 @@
|
||||
/**
|
||||
* Options specific to creating an anchor ("<a>") element.
|
||||
*/
|
||||
interface ICreateElementAnchorOptions {
|
||||
href?: string;
|
||||
target?: string;
|
||||
text?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options specific to creating an input ("<input>") element.
|
||||
*/
|
||||
interface ICreateElementInputOptions {
|
||||
checked?: boolean;
|
||||
max?: string;
|
||||
maxLength?: number;
|
||||
min?: string;
|
||||
name?: string;
|
||||
pattern?: string;
|
||||
placeholder?: string;
|
||||
step?: string;
|
||||
type?: string;
|
||||
value?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options specific to creating a label ("<label>") element.
|
||||
*/
|
||||
interface ICreateElementLabelOptions {
|
||||
for?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for setting up event listeners on the element.
|
||||
*/
|
||||
interface ICreateElementListenerOptions {
|
||||
changeListener?(this: HTMLElement, ev: Event): any;
|
||||
clickListener?(this: HTMLElement, ev: MouseEvent): any;
|
||||
mouseDown?(this: HTMLElement, ev: MouseEvent): any;
|
||||
inputListener?(this: HTMLElement, ev: Event): any;
|
||||
onfocus?(this: HTMLElement, ev: FocusEvent): any;
|
||||
onkeydown?(this: HTMLElement, ev: KeyboardEvent): any;
|
||||
onkeyup?(this: HTMLElement, ev: KeyboardEvent): any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for setting up the inline-styling of element.
|
||||
* NOTE: Relying on CSS styling should be preferred over forcing the higher specificity via inline styles.
|
||||
*/
|
||||
interface ICreateElementStyleOptions {
|
||||
backgroundColor?: string;
|
||||
border?: string;
|
||||
color?: string;
|
||||
display?: string;
|
||||
float?: string;
|
||||
fontSize?: string;
|
||||
margin?: string;
|
||||
marginLeft?: string;
|
||||
marginTop?: string;
|
||||
overflow?: string;
|
||||
padding?: string;
|
||||
position?: string;
|
||||
visibility?: string;
|
||||
whiteSpace?: string;
|
||||
width?: string;
|
||||
height?: string;
|
||||
top?: string;
|
||||
left?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for adding an in-game tooltip to the element.
|
||||
*/
|
||||
interface ICreateElementTooltipOptions {
|
||||
tooltip?: string;
|
||||
tooltipleft?: string;
|
||||
tooltipsmall?: string;
|
||||
tooltiplow?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* All possible configuration options when creating an element.
|
||||
*/
|
||||
interface ICreateElementOptions
|
||||
extends ICreateElementStyleOptions,
|
||||
ICreateElementListenerOptions,
|
||||
ICreateElementInputOptions,
|
||||
ICreateElementAnchorOptions,
|
||||
ICreateElementLabelOptions,
|
||||
ICreateElementTooltipOptions {
|
||||
/**
|
||||
* CSS Class(es) to initially set.
|
||||
*/
|
||||
class?: string;
|
||||
|
||||
/**
|
||||
* A (hopefully) unique identifier for the element.
|
||||
*/
|
||||
id?: string;
|
||||
|
||||
innerHTML?: string;
|
||||
innerText?: string;
|
||||
tabIndex?: number;
|
||||
}
|
||||
|
||||
function setElementAnchor(el: HTMLAnchorElement, params: ICreateElementAnchorOptions): void {
|
||||
if (params.text !== undefined) {
|
||||
el.text = params.text;
|
||||
}
|
||||
if (params.href !== undefined) {
|
||||
el.href = params.href;
|
||||
}
|
||||
if (params.target !== undefined) {
|
||||
el.target = params.target;
|
||||
}
|
||||
}
|
||||
|
||||
function setElementInput(el: HTMLInputElement, params: ICreateElementInputOptions): void {
|
||||
if (params.name !== undefined) {
|
||||
el.name = params.name;
|
||||
}
|
||||
if (params.value !== undefined) {
|
||||
el.value = params.value;
|
||||
}
|
||||
if (params.type !== undefined) {
|
||||
el.type = params.type;
|
||||
}
|
||||
if (params.checked !== undefined) {
|
||||
el.checked = params.checked;
|
||||
}
|
||||
if (params.pattern !== undefined) {
|
||||
el.pattern = params.pattern;
|
||||
}
|
||||
if (params.maxLength !== undefined) {
|
||||
el.maxLength = params.maxLength;
|
||||
}
|
||||
if (params.placeholder !== undefined) {
|
||||
el.placeholder = params.placeholder;
|
||||
}
|
||||
if (params.max !== undefined) {
|
||||
el.max = params.max;
|
||||
}
|
||||
if (params.min !== undefined) {
|
||||
el.min = params.min;
|
||||
}
|
||||
if (params.step !== undefined) {
|
||||
el.step = params.step;
|
||||
}
|
||||
}
|
||||
|
||||
function setElementLabel(el: HTMLLabelElement, params: ICreateElementLabelOptions): void {
|
||||
if (params.for !== undefined) {
|
||||
el.htmlFor = params.for;
|
||||
}
|
||||
}
|
||||
|
||||
function setElementListeners(el: HTMLElement, params: ICreateElementListenerOptions): void {
|
||||
// tslint:disable:no-unbound-method
|
||||
if (params.clickListener !== undefined) {
|
||||
el.addEventListener("click", params.clickListener);
|
||||
}
|
||||
if (params.mouseDown !== undefined) {
|
||||
el.addEventListener("mousedown", params.mouseDown);
|
||||
}
|
||||
if (params.inputListener !== undefined) {
|
||||
el.addEventListener("input", params.inputListener);
|
||||
}
|
||||
if (params.changeListener !== undefined) {
|
||||
el.addEventListener("change", params.changeListener);
|
||||
}
|
||||
if (params.onkeyup !== undefined) {
|
||||
el.addEventListener("keyup", params.onkeyup);
|
||||
}
|
||||
if (params.onkeydown !== undefined) {
|
||||
el.addEventListener("keydown", params.onkeydown);
|
||||
}
|
||||
if (params.onfocus !== undefined) {
|
||||
el.addEventListener("focus", params.onfocus);
|
||||
}
|
||||
// tslint:enable:no-unbound-method
|
||||
}
|
||||
|
||||
function setElementStyle(el: HTMLElement, params: ICreateElementStyleOptions): void {
|
||||
if (params.display !== undefined) {
|
||||
el.style.display = params.display;
|
||||
}
|
||||
if (params.visibility !== undefined) {
|
||||
el.style.visibility = params.visibility;
|
||||
}
|
||||
if (params.margin !== undefined) {
|
||||
el.style.margin = params.margin;
|
||||
}
|
||||
if (params.marginLeft !== undefined) {
|
||||
el.style.marginLeft = params.marginLeft;
|
||||
}
|
||||
if (params.marginTop !== undefined) {
|
||||
el.style.marginTop = params.marginTop;
|
||||
}
|
||||
if (params.padding !== undefined) {
|
||||
el.style.padding = params.padding;
|
||||
}
|
||||
if (params.color !== undefined) {
|
||||
el.style.color = params.color;
|
||||
}
|
||||
if (params.border !== undefined) {
|
||||
el.style.border = params.border;
|
||||
}
|
||||
if (params.float !== undefined) {
|
||||
el.style.cssFloat = params.float;
|
||||
}
|
||||
if (params.fontSize !== undefined) {
|
||||
el.style.fontSize = params.fontSize;
|
||||
}
|
||||
if (params.whiteSpace !== undefined) {
|
||||
el.style.whiteSpace = params.whiteSpace;
|
||||
}
|
||||
if (params.width !== undefined) {
|
||||
el.style.width = params.width;
|
||||
}
|
||||
if (params.height !== undefined) {
|
||||
el.style.height = params.height;
|
||||
}
|
||||
if (params.top !== undefined) {
|
||||
el.style.top = params.top;
|
||||
}
|
||||
if (params.left !== undefined) {
|
||||
el.style.left = params.left;
|
||||
}
|
||||
if (params.backgroundColor !== undefined) {
|
||||
el.style.backgroundColor = params.backgroundColor;
|
||||
}
|
||||
if (params.position !== undefined) {
|
||||
el.style.position = params.position;
|
||||
}
|
||||
if (params.overflow !== undefined) {
|
||||
el.style.overflow = params.overflow;
|
||||
}
|
||||
}
|
||||
|
||||
function setElementTooltip(el: HTMLElement, params: ICreateElementTooltipOptions): void {
|
||||
if (params.tooltip !== undefined && params.tooltip !== "") {
|
||||
el.className += " tooltip";
|
||||
el.appendChild(
|
||||
createElement("span", {
|
||||
class: "tooltiptext",
|
||||
innerHTML: params.tooltip,
|
||||
}),
|
||||
);
|
||||
} else if (params.tooltipleft !== undefined) {
|
||||
el.className += " tooltip";
|
||||
el.appendChild(
|
||||
createElement("span", {
|
||||
class: "tooltiptextleft",
|
||||
innerHTML: params.tooltipleft,
|
||||
}),
|
||||
);
|
||||
} else if (params.tooltipsmall !== undefined) {
|
||||
el.className += " tooltip";
|
||||
el.appendChild(
|
||||
createElement("span", {
|
||||
class: "tooltiptext smallfont",
|
||||
innerHTML: params.tooltipsmall,
|
||||
}),
|
||||
);
|
||||
} else if (params.tooltiplow !== undefined) {
|
||||
el.className += "tooltip";
|
||||
el.appendChild(
|
||||
createElement("span", {
|
||||
class: "tooltiptextlow",
|
||||
innerHTML: params.tooltiplow,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An all-in-one-call way of creating an element to be added to the DOM at some point.
|
||||
* @param tagName The HTML tag/element name
|
||||
* @param params Additional parameters to set on the element
|
||||
*/
|
||||
export function createElement(tagName: string, params: ICreateElementOptions = {}): HTMLElement {
|
||||
const el: HTMLElement = document.createElement(tagName);
|
||||
|
||||
if (params.id !== undefined) {
|
||||
el.id = params.id;
|
||||
}
|
||||
if (params.class !== undefined) {
|
||||
el.className = params.class;
|
||||
}
|
||||
if (params.innerHTML !== undefined) {
|
||||
el.innerHTML = params.innerHTML;
|
||||
}
|
||||
if (params.innerText !== undefined) {
|
||||
el.innerText = params.innerText;
|
||||
}
|
||||
if (params.tabIndex !== undefined) {
|
||||
el.tabIndex = params.tabIndex;
|
||||
}
|
||||
|
||||
setElementAnchor(el as HTMLAnchorElement, params);
|
||||
setElementInput(el as HTMLInputElement, params);
|
||||
setElementLabel(el as HTMLLabelElement, params);
|
||||
setElementListeners(el, params);
|
||||
setElementStyle(el, params);
|
||||
setElementTooltip(el, params);
|
||||
|
||||
return el;
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
import { createElement } from "./createElement";
|
||||
import { getElementById } from "./getElementById";
|
||||
|
||||
interface ICreatePopupOptions {
|
||||
backgroundColor?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the necessary DOM elements to present an in-game popup to the player.
|
||||
* @param id The (hopefully) unique identifier for the popup container.
|
||||
* @param elems The collection of HTML Elements to show within the popup.
|
||||
*/
|
||||
export function createPopup(id: string, elems: HTMLElement[], options: ICreatePopupOptions = {}): HTMLDivElement {
|
||||
const container: HTMLDivElement = createElement("div", {
|
||||
class: "popup-box-container",
|
||||
display: "flex",
|
||||
id: id,
|
||||
}) as HTMLDivElement;
|
||||
const content: HTMLElement = createElement("div", {
|
||||
class: "popup-box-content",
|
||||
id: `${id}-content`,
|
||||
});
|
||||
|
||||
for (const elem of elems) {
|
||||
content.appendChild(elem);
|
||||
}
|
||||
|
||||
// Configurable Options
|
||||
if (options.backgroundColor) {
|
||||
content.style.backgroundColor = options.backgroundColor;
|
||||
}
|
||||
|
||||
container.appendChild(content);
|
||||
getElementById("root").appendChild(container);
|
||||
|
||||
return container;
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
/**
|
||||
* Returns a reference to the first object with the specified value of the ID or NAME attribute,
|
||||
* throwing an error if it is unable to find it.
|
||||
* @param elementId The HTML ID to retrieve the element by.
|
||||
* @throws {Error} When the 'elementId' cannot be found.
|
||||
*/
|
||||
export function getElementById(elementId: string): HTMLElement {
|
||||
const el: HTMLElement | null = document.getElementById(elementId);
|
||||
if (el === null) {
|
||||
throw new Error(`Unable to find element with id '${elementId}'`);
|
||||
}
|
||||
|
||||
return el;
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
export function getSelectValue(selector: HTMLSelectElement | null): string {
|
||||
if (selector == null) {
|
||||
return "";
|
||||
}
|
||||
if (selector.options.length <= 0) {
|
||||
return "";
|
||||
}
|
||||
if (selector.selectedIndex < 0) {
|
||||
return "";
|
||||
}
|
||||
return selector.options[selector.selectedIndex].value;
|
||||
}
|
||||
|
||||
export function getSelectText(selector: HTMLSelectElement | null): string {
|
||||
if (selector == null) {
|
||||
return "";
|
||||
}
|
||||
if (selector.options.length <= 0) {
|
||||
return "";
|
||||
}
|
||||
if (selector.selectedIndex < 0) {
|
||||
return "";
|
||||
}
|
||||
return selector.options[selector.selectedIndex].text;
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
import { isString } from "../../utils/helpers/isString";
|
||||
import { getElementById } from "./getElementById";
|
||||
|
||||
/**
|
||||
* Clears out all children from the provided element.
|
||||
* If a string is passed in, it will treat it as an ID and search for the element to delete all children from.
|
||||
* @param el The element or ID of an element to remove all children from.
|
||||
*/
|
||||
export function removeChildrenFromElement(el: string | null | Element): void {
|
||||
if (el === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const elem: HTMLElement | Element = isString(el) ? getElementById(el as string) : (el as Element);
|
||||
|
||||
if (elem instanceof Element) {
|
||||
while (elem.firstChild !== null) {
|
||||
elem.removeChild(elem.firstChild);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// tslint:disable-next-line:no-console
|
||||
console.debug(e);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
/**
|
||||
* For a given element, this function removes it AND its children
|
||||
* @param elem The element to remove.
|
||||
*/
|
||||
export function removeElement(elem: Element | null): void {
|
||||
if (elem === null) {
|
||||
// tslint:disable-next-line:no-console
|
||||
console.debug("The element passed into 'removeElement' was null.");
|
||||
|
||||
return;
|
||||
}
|
||||
if (!(elem instanceof Element)) {
|
||||
// tslint:disable-next-line:no-console
|
||||
console.debug("The element passed into 'removeElement' was not an instance of an Element.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
while (elem.firstChild !== null) {
|
||||
elem.removeChild(elem.firstChild);
|
||||
}
|
||||
|
||||
if (elem.parentNode !== null) {
|
||||
elem.parentNode.removeChild(elem);
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import { getElementById } from "./getElementById";
|
||||
import { removeElement } from "./removeElement";
|
||||
|
||||
/**
|
||||
* Given its id, this function removes an element AND its children
|
||||
* @param id The HTML identifier to search for and remove.
|
||||
*/
|
||||
export function removeElementById(id: string): void {
|
||||
try {
|
||||
const elem: HTMLElement = getElementById(id);
|
||||
removeElement(elem);
|
||||
} catch (e) {
|
||||
// Probably should log this as we're trying to remove elements that don't exist.
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user