This commit is contained in:
Olivier Gagnon
2021-10-03 20:34:36 -04:00
368 changed files with 9561 additions and 23690 deletions
@@ -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
View File
@@ -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>
);
-46
View File
@@ -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>
);
}
+49
View File
@@ -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>
)}
</>
);
}
+13 -5
View File
@@ -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>;
}
-67
View File
@@ -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>
);
}
}
-80
View File
@@ -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>
);
}
}
-84
View File
@@ -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>
);
}
}
+3 -1
View File
@@ -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>
+3 -2
View File
@@ -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>&#9608;</span>}
</pre>
</Typography>
</>
);
}
+6 -8
View File
@@ -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>
);
}
+33 -68
View File
@@ -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>
);
}
+5 -37
View File
@@ -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 }} />);
}
}
-93
View File
@@ -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
View File
@@ -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>;
}
+3 -2
View File
@@ -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
View File
@@ -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>
);
}
-134
View File
@@ -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);
}
+163
View File
@@ -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
View File
@@ -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
View File
@@ -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>
);
-46
View File
@@ -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,
}}
/>
);
};
-32
View File
@@ -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,
}}
/>
);
};
-81
View File
@@ -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,
}}
/>
);
};
+1 -2
View File
@@ -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" />;
-21
View File
@@ -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>
);
}
}
-71
View File
@@ -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>
);
}
}
-56
View File
@@ -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>
);
}
}
+52
View File
@@ -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>
)}
</>
);
}
+14 -2
View File
@@ -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>
);
+3 -2
View File
@@ -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`} />;
}
-38
View File
@@ -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,
}}
/>
);
};
+1 -1
View File
@@ -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>
);
+16
View File
@@ -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>
);
}
+2 -2
View File
@@ -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>
-60
View File
@@ -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>
);
}
-56
View File
@@ -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>
);
}
}
+29
View File
@@ -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,
},
},
},
},
},
});
}
+10 -2
View File
@@ -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} />
+4 -4
View File
@@ -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>
</>
);
}
-95
View File
@@ -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 = "";
}
-2
View File
@@ -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;
}
+13 -12
View File
@@ -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>
-36
View File
@@ -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);
}
-161
View File
@@ -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();
-29
View File
@@ -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;
}
}
-308
View File
@@ -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;
}
-37
View File
@@ -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;
}
-14
View File
@@ -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;
}
-25
View File
@@ -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;
}
}
-26
View File
@@ -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);
}
}
-15
View File
@@ -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.
}
}