mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-12-18 12:15:44 +01:00
stock market in Mui
This commit is contained in:
parent
c05be66c60
commit
73d0dd98f2
36
dist/vendor.bundle.js
vendored
36
dist/vendor.bundle.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -3,17 +3,22 @@
|
||||
* general information about the stock market, buttons for the various purchases,
|
||||
* and a link to the documentation (Investopedia)
|
||||
*/
|
||||
import * as React from "react";
|
||||
import React, { useState } from "react";
|
||||
|
||||
import { getStockMarket4SDataCost, getStockMarket4STixApiCost } from "../StockMarketCosts";
|
||||
|
||||
import { CONSTANTS } from "../../Constants";
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
import { StdButton } from "../../ui/React/StdButton";
|
||||
import { StdButtonPurchased } from "../../ui/React/StdButtonPurchased";
|
||||
import { Money } from "../../ui/React/Money";
|
||||
|
||||
import { dialogBoxCreate } from "../../ui/React/DialogBox";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Link from "@mui/material/Link";
|
||||
import Button from "@mui/material/Button";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import HelpIcon from "@mui/icons-material/Help";
|
||||
import CheckIcon from "@mui/icons-material/Check";
|
||||
import { StaticModal } from "../../ui/React/StaticModal";
|
||||
|
||||
type IProps = {
|
||||
initStockMarket: () => void;
|
||||
@ -21,228 +26,210 @@ type IProps = {
|
||||
rerender: () => void;
|
||||
};
|
||||
|
||||
const blockStyleMarkup = {
|
||||
display: "block",
|
||||
};
|
||||
|
||||
export class InfoAndPurchases extends React.Component<IProps, any> {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.handleClick4SMarketDataHelpTip = this.handleClick4SMarketDataHelpTip.bind(this);
|
||||
this.purchaseWseAccount = this.purchaseWseAccount.bind(this);
|
||||
this.purchaseTixApiAccess = this.purchaseTixApiAccess.bind(this);
|
||||
this.purchase4SMarketData = this.purchase4SMarketData.bind(this);
|
||||
this.purchase4SMarketDataTixApiAccess = this.purchase4SMarketDataTixApiAccess.bind(this);
|
||||
}
|
||||
|
||||
handleClick4SMarketDataHelpTip(): void {
|
||||
dialogBoxCreate(
|
||||
"Access to the 4S Market Data feed will display two additional pieces " +
|
||||
"of information about each stock: Price Forecast & Volatility<br><br>" +
|
||||
"Price Forecast indicates the probability the stock has of increasing or " +
|
||||
"decreasing. A '+' forecast means the stock has a higher chance of increasing " +
|
||||
"than decreasing, and a '-' means the opposite. The number of '+/-' symbols " +
|
||||
"is used to illustrate the magnitude of these probabilities. For example, " +
|
||||
"'+++' means that the stock has a significantly higher chance of increasing " +
|
||||
"than decreasing, while '+' means that the stock only has a slightly higher chance " +
|
||||
"of increasing than decreasing.<br><br>" +
|
||||
"Volatility represents the maximum percentage by which a stock's price " +
|
||||
"can change every tick (a tick occurs every few seconds while the game " +
|
||||
"is running).<br><br>" +
|
||||
"A stock's price forecast can change over time. This is also affected by volatility. " +
|
||||
"The more volatile a stock is, the more its price forecast will change.",
|
||||
);
|
||||
}
|
||||
|
||||
purchaseWseAccount(): void {
|
||||
if (this.props.p.hasWseAccount) {
|
||||
function Purchase4SMarketDataTixApiAccessButton(props: IProps): React.ReactElement {
|
||||
function purchase4SMarketDataTixApiAccess(): void {
|
||||
if (props.p.has4SDataTixApi) {
|
||||
return;
|
||||
}
|
||||
if (!this.props.p.canAfford(CONSTANTS.WSEAccountCost)) {
|
||||
if (!props.p.canAfford(getStockMarket4STixApiCost())) {
|
||||
return;
|
||||
}
|
||||
this.props.p.hasWseAccount = true;
|
||||
this.props.initStockMarket();
|
||||
this.props.p.loseMoney(CONSTANTS.WSEAccountCost);
|
||||
this.props.rerender();
|
||||
|
||||
const worldHeader = document.getElementById("world-menu-header");
|
||||
if (worldHeader instanceof HTMLElement) {
|
||||
worldHeader.click();
|
||||
worldHeader.click();
|
||||
}
|
||||
props.p.has4SDataTixApi = true;
|
||||
props.p.loseMoney(getStockMarket4STixApiCost());
|
||||
props.rerender();
|
||||
}
|
||||
|
||||
purchaseTixApiAccess(): void {
|
||||
if (this.props.p.hasTixApiAccess) {
|
||||
return;
|
||||
}
|
||||
if (!this.props.p.canAfford(CONSTANTS.TIXAPICost)) {
|
||||
return;
|
||||
}
|
||||
this.props.p.hasTixApiAccess = true;
|
||||
this.props.p.loseMoney(CONSTANTS.TIXAPICost);
|
||||
this.props.rerender();
|
||||
}
|
||||
|
||||
purchase4SMarketData(): void {
|
||||
if (this.props.p.has4SData) {
|
||||
return;
|
||||
}
|
||||
if (!this.props.p.canAfford(getStockMarket4SDataCost())) {
|
||||
return;
|
||||
}
|
||||
this.props.p.has4SData = true;
|
||||
this.props.p.loseMoney(getStockMarket4SDataCost());
|
||||
this.props.rerender();
|
||||
}
|
||||
|
||||
purchase4SMarketDataTixApiAccess(): void {
|
||||
if (this.props.p.has4SDataTixApi) {
|
||||
return;
|
||||
}
|
||||
if (!this.props.p.canAfford(getStockMarket4STixApiCost())) {
|
||||
return;
|
||||
}
|
||||
this.props.p.has4SDataTixApi = true;
|
||||
this.props.p.loseMoney(getStockMarket4STixApiCost());
|
||||
this.props.rerender();
|
||||
}
|
||||
|
||||
renderPurchaseWseAccountButton(): React.ReactElement {
|
||||
if (this.props.p.hasWseAccount) {
|
||||
return <StdButtonPurchased text={"WSE Account - Purchased"} />;
|
||||
} else {
|
||||
const cost = CONSTANTS.WSEAccountCost;
|
||||
return (
|
||||
<StdButton
|
||||
disabled={!this.props.p.canAfford(cost)}
|
||||
onClick={this.purchaseWseAccount}
|
||||
text={
|
||||
<>
|
||||
Buy WSE Account - <Money money={cost} player={this.props.p} />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
renderPurchaseTixApiAccessButton(): React.ReactElement {
|
||||
if (this.props.p.hasTixApiAccess) {
|
||||
return <StdButtonPurchased text={"TIX API Access - Purchased"} />;
|
||||
} else {
|
||||
const cost = CONSTANTS.TIXAPICost;
|
||||
return (
|
||||
<StdButton
|
||||
disabled={!this.props.p.canAfford(cost) || !this.props.p.hasWseAccount}
|
||||
onClick={this.purchaseTixApiAccess}
|
||||
style={blockStyleMarkup}
|
||||
text={
|
||||
<>
|
||||
Buy Trade Information eXchange (TIX) API Access - <Money money={cost} player={this.props.p} />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
renderPurchase4SMarketDataButton(): React.ReactElement {
|
||||
if (this.props.p.has4SData) {
|
||||
return (
|
||||
<StdButtonPurchased
|
||||
text={"4S Market Data - Purchased"}
|
||||
tooltip={"Lets you view additional pricing and volatility information about stocks"}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
const cost = getStockMarket4SDataCost();
|
||||
return (
|
||||
<StdButton
|
||||
disabled={!this.props.p.canAfford(cost) || !this.props.p.hasWseAccount}
|
||||
onClick={this.purchase4SMarketData}
|
||||
text={
|
||||
<>
|
||||
Buy 4S Market Data Access - <Money money={cost} player={this.props.p} />
|
||||
</>
|
||||
}
|
||||
tooltip={"Lets you view additional pricing and volatility information about stocks"}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
renderPurchase4SMarketDataTixApiAccessButton(): React.ReactElement {
|
||||
if (!this.props.p.hasTixApiAccess) {
|
||||
return (
|
||||
<StdButton disabled={true} text={`Buy 4S Market Data TIX API Access`} tooltip={"Requires TIX API Access"} />
|
||||
);
|
||||
} else if (this.props.p.has4SDataTixApi) {
|
||||
return (
|
||||
<StdButtonPurchased
|
||||
text={"4S Market Data TIX API - Purchased"}
|
||||
tooltip={"Let you access 4S Market Data through Netscript"}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
const cost = getStockMarket4STixApiCost();
|
||||
return (
|
||||
<StdButton
|
||||
disabled={!this.props.p.canAfford(cost)}
|
||||
onClick={this.purchase4SMarketDataTixApiAccess}
|
||||
text={
|
||||
<>
|
||||
Buy 4S Market Data TIX API Access - <Money money={cost} player={this.props.p} />
|
||||
</>
|
||||
}
|
||||
tooltip={"Let you access 4S Market Data through Netscript"}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
render(): React.ReactNode {
|
||||
const documentationLink = "https://bitburner.readthedocs.io/en/latest/basicgameplay/stockmarket.html";
|
||||
if (props.p.has4SDataTixApi) {
|
||||
return (
|
||||
<div className={"stock-market-info-and-purchases"}>
|
||||
<p>Welcome to the World Stock Exchange (WSE)!</p>
|
||||
<button className={"std-button"}>
|
||||
<a href={documentationLink} target={"_blank"}>
|
||||
Investopedia
|
||||
</a>
|
||||
</button>
|
||||
<br />
|
||||
<p>To begin trading, you must first purchase an account:</p>
|
||||
{this.renderPurchaseWseAccountButton()}
|
||||
|
||||
<h2>Trade Information eXchange (TIX) API</h2>
|
||||
<p>
|
||||
TIX, short for Trade Information eXchange, is the communications protocol used by the WSE. Purchasing access
|
||||
to the TIX API lets you write code to create your own algorithmic/automated trading strategies.
|
||||
</p>
|
||||
{this.renderPurchaseTixApiAccessButton()}
|
||||
<h2>Four Sigma (4S) Market Data Feed</h2>
|
||||
<p>
|
||||
Four Sigma's (4S) Market Data Feed provides information about stocks that will help your trading strategies.
|
||||
</p>
|
||||
{this.renderPurchase4SMarketDataButton()}
|
||||
<button className={"help-tip-big"} onClick={this.handleClick4SMarketDataHelpTip}>
|
||||
?
|
||||
</button>
|
||||
{this.renderPurchase4SMarketDataTixApiAccessButton()}
|
||||
<p>
|
||||
Commission Fees: Every transaction you make has a{" "}
|
||||
<Money money={CONSTANTS.StockMarketCommission} player={this.props.p} /> commission fee.
|
||||
</p>
|
||||
<br />
|
||||
<p>
|
||||
WARNING: When you reset after installing Augmentations, the Stock Market is reset. You will retain your WSE
|
||||
Account, access to the TIX API, and 4S Market Data access. However, all of your stock positions are lost, so
|
||||
make sure to sell your stocks before installing Augmentations!
|
||||
</p>
|
||||
</div>
|
||||
<Typography>
|
||||
Market Data TIX API Access <CheckIcon />
|
||||
</Typography>
|
||||
);
|
||||
} else {
|
||||
const cost = getStockMarket4STixApiCost();
|
||||
return (
|
||||
<Tooltip
|
||||
title={
|
||||
!props.p.hasTixApiAccess ? (
|
||||
<Typography>Requires TIX API Access</Typography>
|
||||
) : (
|
||||
<Typography>Let you access 4S Market Data through Netscript</Typography>
|
||||
)
|
||||
}
|
||||
>
|
||||
<span>
|
||||
<Button
|
||||
disabled={!props.p.hasTixApiAccess || !props.p.canAfford(cost)}
|
||||
onClick={purchase4SMarketDataTixApiAccess}
|
||||
>
|
||||
Buy 4S Market Data TIX API Access -
|
||||
<Money money={cost} player={props.p} />
|
||||
</Button>
|
||||
</span>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function PurchaseWseAccountButton(props: IProps): React.ReactElement {
|
||||
if (props.p.hasWseAccount) {
|
||||
return (
|
||||
<Typography>
|
||||
WSE Account <CheckIcon />
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
function purchaseWseAccount(): void {
|
||||
if (props.p.hasWseAccount) {
|
||||
return;
|
||||
}
|
||||
if (!props.p.canAfford(CONSTANTS.WSEAccountCost)) {
|
||||
return;
|
||||
}
|
||||
props.p.hasWseAccount = true;
|
||||
props.initStockMarket();
|
||||
props.p.loseMoney(CONSTANTS.WSEAccountCost);
|
||||
props.rerender();
|
||||
}
|
||||
|
||||
const cost = CONSTANTS.WSEAccountCost;
|
||||
return (
|
||||
<>
|
||||
<Typography>To begin trading, you must first purchase an account:</Typography>
|
||||
<Button disabled={!props.p.canAfford(cost)} onClick={purchaseWseAccount}>
|
||||
Buy WSE Account -
|
||||
<Money money={cost} player={props.p} />
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function PurchaseTixApiAccessButton(props: IProps): React.ReactElement {
|
||||
function purchaseTixApiAccess(): void {
|
||||
if (props.p.hasTixApiAccess) {
|
||||
return;
|
||||
}
|
||||
if (!props.p.canAfford(CONSTANTS.TIXAPICost)) {
|
||||
return;
|
||||
}
|
||||
props.p.hasTixApiAccess = true;
|
||||
props.p.loseMoney(CONSTANTS.TIXAPICost);
|
||||
props.rerender();
|
||||
}
|
||||
|
||||
if (props.p.hasTixApiAccess) {
|
||||
return (
|
||||
<Typography>
|
||||
TIX API Access <CheckIcon />
|
||||
</Typography>
|
||||
);
|
||||
} else {
|
||||
const cost = CONSTANTS.TIXAPICost;
|
||||
return (
|
||||
<Button disabled={!props.p.canAfford(cost) || !props.p.hasWseAccount} onClick={purchaseTixApiAccess}>
|
||||
Buy Trade Information eXchange (TIX) API Access -
|
||||
<Money money={cost} player={props.p} />
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function Purchase4SMarketDataButton(props: IProps): React.ReactElement {
|
||||
function purchase4SMarketData(): void {
|
||||
if (props.p.has4SData) {
|
||||
return;
|
||||
}
|
||||
if (!props.p.canAfford(getStockMarket4SDataCost())) {
|
||||
return;
|
||||
}
|
||||
props.p.has4SData = true;
|
||||
props.p.loseMoney(getStockMarket4SDataCost());
|
||||
props.rerender();
|
||||
}
|
||||
if (props.p.has4SData) {
|
||||
return (
|
||||
<Typography>
|
||||
4S Market Data Access <CheckIcon />
|
||||
</Typography>
|
||||
);
|
||||
} else {
|
||||
const cost = getStockMarket4SDataCost();
|
||||
return (
|
||||
<Tooltip
|
||||
title={<Typography>Lets you view additional pricing and volatility information about stocks</Typography>}
|
||||
>
|
||||
<span>
|
||||
<Button disabled={!props.p.canAfford(cost) || !props.p.hasWseAccount} onClick={purchase4SMarketData}>
|
||||
Buy 4S Market Data Access -
|
||||
<Money money={cost} player={props.p} />
|
||||
</Button>
|
||||
</span>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function InfoAndPurchases(props: IProps): React.ReactElement {
|
||||
const [helpOpen, setHelpOpen] = useState(false);
|
||||
const documentationLink = "https://bitburner.readthedocs.io/en/latest/basicgameplay/stockmarket.html";
|
||||
return (
|
||||
<>
|
||||
<Typography>Welcome to the World Stock Exchange (WSE)!</Typography>
|
||||
<Link href={documentationLink} target={"_blank"}>
|
||||
Investopedia
|
||||
</Link>
|
||||
<br />
|
||||
<PurchaseWseAccountButton {...props} />
|
||||
|
||||
<Typography variant="h5" color="primary">
|
||||
Trade Information eXchange (TIX) API
|
||||
</Typography>
|
||||
<Typography>
|
||||
TIX, short for Trade Information eXchange, is the communications protocol used by the WSE. Purchasing access to
|
||||
the TIX API lets you write code to create your own algorithmic/automated trading strategies.
|
||||
</Typography>
|
||||
<PurchaseTixApiAccessButton {...props} />
|
||||
<Typography variant="h5" color="primary">
|
||||
Four Sigma (4S) Market Data Feed
|
||||
</Typography>
|
||||
<Typography>
|
||||
Four Sigma's (4S) Market Data Feed provides information about stocks that will help your trading strategies.
|
||||
<IconButton onClick={() => setHelpOpen(true)}>
|
||||
<HelpIcon />
|
||||
</IconButton>
|
||||
</Typography>
|
||||
<Purchase4SMarketDataTixApiAccessButton {...props} />
|
||||
<Purchase4SMarketDataButton {...props} />
|
||||
<Typography>
|
||||
Commission Fees: Every transaction you make has a{" "}
|
||||
<Money money={CONSTANTS.StockMarketCommission} player={props.p} /> commission fee.
|
||||
</Typography>
|
||||
<br />
|
||||
<Typography>
|
||||
WARNING: When you reset after installing Augmentations, the Stock Market is reset. You will retain your WSE
|
||||
Account, access to the TIX API, and 4S Market Data access. However, all of your stock positions are lost, so
|
||||
make sure to sell your stocks before installing Augmentations!
|
||||
</Typography>
|
||||
<StaticModal open={helpOpen} onClose={() => setHelpOpen(false)}>
|
||||
<Typography>
|
||||
Access to the 4S Market Data feed will display two additional pieces of information about each stock: Price
|
||||
Forecast & Volatility
|
||||
<br />
|
||||
<br />
|
||||
Price Forecast indicates the probability the stock has of increasing or decreasing. A '+' forecast means the
|
||||
stock has a higher chance of increasing than decreasing, and a '-' means the opposite. The number of '+/-'
|
||||
symbols is used to illustrate the magnitude of these probabilities. For example, '+++' means that the stock
|
||||
has a significantly higher chance of increasing than decreasing, while '+' means that the stock only has a
|
||||
slightly higher chance of increasing than decreasing.
|
||||
<br />
|
||||
<br />
|
||||
Volatility represents the maximum percentage by which a stock's price can change every tick (a tick occurs
|
||||
every few seconds while the game is running).
|
||||
<br />
|
||||
<br />A stock's price forecast can change over time. This is also affected by volatility. The more volatile a
|
||||
stock is, the more its price forecast will change.
|
||||
</Typography>
|
||||
</StaticModal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
43
src/StockMarket/ui/PlaceOrderModal.tsx
Normal file
43
src/StockMarket/ui/PlaceOrderModal.tsx
Normal file
@ -0,0 +1,43 @@
|
||||
import React, { useState } from "react";
|
||||
|
||||
import { Modal } from "../../ui/React/Modal";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import TextField from "@mui/material/TextField";
|
||||
import Button from "@mui/material/Button";
|
||||
|
||||
interface IProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
text: string;
|
||||
placeText: string;
|
||||
place: (price: number) => void;
|
||||
}
|
||||
|
||||
export function PlaceOrderModal(props: IProps): React.ReactElement {
|
||||
const [price, setPrice] = useState<number | null>(null);
|
||||
function onClick(): void {
|
||||
if (price === null) return;
|
||||
if (isNaN(price)) return;
|
||||
props.place(price);
|
||||
props.onClose();
|
||||
}
|
||||
|
||||
function onChange(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||
if (event.target.value === "") setPrice(null);
|
||||
else setPrice(parseFloat(event.target.value));
|
||||
}
|
||||
return (
|
||||
<Modal open={props.open} onClose={props.onClose}>
|
||||
<Typography>{props.text}</Typography>
|
||||
<TextField
|
||||
autoFocus
|
||||
type="number"
|
||||
onChange={onChange}
|
||||
placeholder="price"
|
||||
InputProps={{
|
||||
endAdornment: <Button onClick={onClick}>{props.placeText}</Button>,
|
||||
}}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
import React, { useState } from "react";
|
||||
|
||||
import { removePopup } from "../../ui/React/createPopup";
|
||||
|
||||
interface IProps {
|
||||
text: string;
|
||||
placeText: string;
|
||||
place: (price: number) => void;
|
||||
popupId: string;
|
||||
}
|
||||
|
||||
export function PlaceOrderPopup(props: IProps): React.ReactElement {
|
||||
const [price, setPrice] = useState<number | null>(null);
|
||||
function onClick(): void {
|
||||
if (price === null) return;
|
||||
if (isNaN(price)) return;
|
||||
props.place(price);
|
||||
removePopup(props.popupId);
|
||||
}
|
||||
|
||||
function onChange(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||
if (event.target.value === "") setPrice(null);
|
||||
else setPrice(parseFloat(event.target.value));
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<p>{props.text}</p>
|
||||
<input autoFocus={true} className="text-input" type="number" onChange={onChange} placeholder="price" />
|
||||
<button className="std-button" onClick={onClick}>
|
||||
{props.placeText}
|
||||
</button>
|
||||
</>
|
||||
);
|
||||
}
|
@ -47,7 +47,7 @@ export function StockMarketRoot(props: IProps): React.ReactElement {
|
||||
return () => clearInterval(id);
|
||||
}, []);
|
||||
return (
|
||||
<div className="stock-market-container">
|
||||
<>
|
||||
<InfoAndPurchases initStockMarket={props.initStockMarket} p={props.p} rerender={rerender} />
|
||||
{props.p.hasWseAccount && (
|
||||
<StockTickers
|
||||
@ -62,6 +62,6 @@ export function StockMarketRoot(props: IProps): React.ReactElement {
|
||||
stockMarket={props.stockMarket}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -1,13 +1,13 @@
|
||||
/**
|
||||
* React Component for a single stock ticker in the Stock Market UI
|
||||
*/
|
||||
import * as React from "react";
|
||||
import React, { useState } from "react";
|
||||
|
||||
import { StockTickerHeaderText } from "./StockTickerHeaderText";
|
||||
import { StockTickerOrderList } from "./StockTickerOrderList";
|
||||
import { StockTickerPositionText } from "./StockTickerPositionText";
|
||||
import { StockTickerTxButton } from "./StockTickerTxButton";
|
||||
import { PlaceOrderPopup } from "./PlaceOrderPopup";
|
||||
import { PlaceOrderModal } from "./PlaceOrderModal";
|
||||
|
||||
import { Order } from "../Order";
|
||||
import { Stock } from "../Stock";
|
||||
@ -18,11 +18,21 @@ import { PositionTypes } from "../data/PositionTypes";
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
import { SourceFileFlags } from "../../SourceFile/SourceFileFlags";
|
||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||
import { BBAccordion } from "../../ui/React/BBAccordion";
|
||||
import { Money } from "../../ui/React/Money";
|
||||
import { createPopup } from "../../ui/React/createPopup";
|
||||
|
||||
import { dialogBoxCreate } from "../../ui/React/DialogBox";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Box from "@mui/material/Box";
|
||||
import TextField from "@mui/material/TextField";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
import Select, { SelectChangeEvent } from "@mui/material/Select";
|
||||
|
||||
import ListItemButton from "@mui/material/ListItemButton";
|
||||
import ListItemText from "@mui/material/ListItemText";
|
||||
import Paper from "@mui/material/Paper";
|
||||
import Collapse from "@mui/material/Collapse";
|
||||
import ExpandMore from "@mui/icons-material/ExpandMore";
|
||||
import ExpandLess from "@mui/icons-material/ExpandLess";
|
||||
|
||||
enum SelectorOrderType {
|
||||
Market = "Market Order",
|
||||
@ -52,66 +62,55 @@ type IProps = {
|
||||
stock: Stock;
|
||||
};
|
||||
|
||||
type IState = {
|
||||
orderType: SelectorOrderType;
|
||||
position: PositionTypes;
|
||||
qty: string;
|
||||
};
|
||||
export function StockTicker(props: IProps): React.ReactElement {
|
||||
const [orderType, setOrderType] = useState(SelectorOrderType.Market);
|
||||
const [position, setPosition] = useState(PositionTypes.Long);
|
||||
const [qty, setQty] = useState("");
|
||||
const [open, setOpen] = useState(false);
|
||||
const [tickerOpen, setTicketOpen] = useState(false);
|
||||
|
||||
export class StockTicker extends React.Component<IProps, IState> {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
const [modalProps, setModalProps] = useState<{
|
||||
text: string;
|
||||
placeText: string;
|
||||
place: (n: number) => boolean;
|
||||
}>({
|
||||
text: "",
|
||||
placeText: "",
|
||||
place: (n: number) => false,
|
||||
});
|
||||
|
||||
this.state = {
|
||||
orderType: SelectorOrderType.Market,
|
||||
position: PositionTypes.Long,
|
||||
qty: "",
|
||||
};
|
||||
|
||||
this.getBuyTransactionCostContent = this.getBuyTransactionCostContent.bind(this);
|
||||
this.getSellTransactionCostContent = this.getSellTransactionCostContent.bind(this);
|
||||
this.handleBuyButtonClick = this.handleBuyButtonClick.bind(this);
|
||||
this.handleBuyMaxButtonClick = this.handleBuyMaxButtonClick.bind(this);
|
||||
this.handleHeaderClick = this.handleHeaderClick.bind(this);
|
||||
this.handleOrderTypeChange = this.handleOrderTypeChange.bind(this);
|
||||
this.handlePositionTypeChange = this.handlePositionTypeChange.bind(this);
|
||||
this.handleQuantityChange = this.handleQuantityChange.bind(this);
|
||||
this.handleSellButtonClick = this.handleSellButtonClick.bind(this);
|
||||
this.handleSellAllButtonClick = this.handleSellAllButtonClick.bind(this);
|
||||
}
|
||||
|
||||
getBuyTransactionCostContent(): JSX.Element | null {
|
||||
const stock = this.props.stock;
|
||||
const qty: number = this.getQuantity();
|
||||
function getBuyTransactionCostContent(): JSX.Element | null {
|
||||
const stock = props.stock;
|
||||
const qty: number = getQuantity();
|
||||
if (isNaN(qty)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const cost = getBuyTransactionCost(stock, qty, this.state.position);
|
||||
const cost = getBuyTransactionCost(stock, qty, position);
|
||||
if (cost == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
Purchasing {numeralWrapper.formatShares(qty)} shares (
|
||||
{this.state.position === PositionTypes.Long ? "Long" : "Short"}) will cost <Money money={cost} />.
|
||||
Purchasing {numeralWrapper.formatShares(qty)} shares ({position === PositionTypes.Long ? "Long" : "Short"}
|
||||
) will cost <Money money={cost} />.
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
getQuantity(): number {
|
||||
return Math.round(parseFloat(this.state.qty));
|
||||
function getQuantity(): number {
|
||||
return Math.round(parseFloat(qty));
|
||||
}
|
||||
|
||||
getSellTransactionCostContent(): JSX.Element | null {
|
||||
const stock = this.props.stock;
|
||||
const qty: number = this.getQuantity();
|
||||
function getSellTransactionCostContent(): JSX.Element | null {
|
||||
const stock = props.stock;
|
||||
const qty: number = getQuantity();
|
||||
if (isNaN(qty)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this.state.position === PositionTypes.Long) {
|
||||
if (position === PositionTypes.Long) {
|
||||
if (qty > stock.playerShares) {
|
||||
return <>You do not have this many shares in the Long position</>;
|
||||
}
|
||||
@ -121,56 +120,51 @@ export class StockTicker extends React.Component<IProps, IState> {
|
||||
}
|
||||
}
|
||||
|
||||
const cost = getSellTransactionGain(stock, qty, this.state.position);
|
||||
const cost = getSellTransactionGain(stock, qty, position);
|
||||
if (cost == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
Selling {numeralWrapper.formatShares(qty)} shares (
|
||||
{this.state.position === PositionTypes.Long ? "Long" : "Short"}) will result in a gain of <Money money={cost} />
|
||||
.
|
||||
Selling {numeralWrapper.formatShares(qty)} shares ({position === PositionTypes.Long ? "Long" : "Short"}) will
|
||||
result in a gain of <Money money={cost} />.
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
handleBuyButtonClick(): void {
|
||||
const shares = this.getQuantity();
|
||||
function handleBuyButtonClick(): void {
|
||||
const shares = getQuantity();
|
||||
if (isNaN(shares)) {
|
||||
dialogBoxCreate(`Invalid input for quantity (number of shares): ${this.state.qty}`);
|
||||
dialogBoxCreate(`Invalid input for quantity (number of shares): ${qty}`);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (this.state.orderType) {
|
||||
switch (orderType) {
|
||||
case SelectorOrderType.Market: {
|
||||
if (this.state.position === PositionTypes.Short) {
|
||||
this.props.buyStockShort(this.props.stock, shares);
|
||||
if (position === PositionTypes.Short) {
|
||||
props.buyStockShort(props.stock, shares);
|
||||
} else {
|
||||
this.props.buyStockLong(this.props.stock, shares);
|
||||
props.buyStockLong(props.stock, shares);
|
||||
}
|
||||
this.props.rerenderAllTickers();
|
||||
props.rerenderAllTickers();
|
||||
break;
|
||||
}
|
||||
case SelectorOrderType.Limit: {
|
||||
const popupId = `place-order-popup`;
|
||||
createPopup(popupId, PlaceOrderPopup, {
|
||||
setOpen(true);
|
||||
setModalProps({
|
||||
text: "Enter the price for your Limit Order",
|
||||
placeText: "Place Buy Limit Order",
|
||||
place: (price: number) =>
|
||||
this.props.placeOrder(this.props.stock, shares, price, OrderTypes.LimitBuy, this.state.position),
|
||||
popupId: popupId,
|
||||
place: (price: number) => props.placeOrder(props.stock, shares, price, OrderTypes.LimitBuy, position),
|
||||
});
|
||||
break;
|
||||
}
|
||||
case SelectorOrderType.Stop: {
|
||||
const popupId = `place-order-popup`;
|
||||
createPopup(popupId, PlaceOrderPopup, {
|
||||
setOpen(true);
|
||||
setModalProps({
|
||||
text: "Enter the price for your Stop Order",
|
||||
placeText: "Place Buy Stop Order",
|
||||
place: (price: number) =>
|
||||
this.props.placeOrder(this.props.stock, shares, price, OrderTypes.StopBuy, this.state.position),
|
||||
popupId: popupId,
|
||||
place: (price: number) => props.placeOrder(props.stock, shares, price, OrderTypes.StopBuy, position),
|
||||
});
|
||||
break;
|
||||
}
|
||||
@ -179,21 +173,21 @@ export class StockTicker extends React.Component<IProps, IState> {
|
||||
}
|
||||
}
|
||||
|
||||
handleBuyMaxButtonClick(): void {
|
||||
const playerMoney: number = this.props.p.money.toNumber();
|
||||
function handleBuyMaxButtonClick(): void {
|
||||
const playerMoney: number = props.p.money.toNumber();
|
||||
|
||||
const stock = this.props.stock;
|
||||
let maxShares = calculateBuyMaxAmount(stock, this.state.position, playerMoney);
|
||||
const stock = props.stock;
|
||||
let maxShares = calculateBuyMaxAmount(stock, position, playerMoney);
|
||||
maxShares = Math.min(maxShares, Math.round(stock.maxShares - stock.playerShares - stock.playerShortShares));
|
||||
|
||||
switch (this.state.orderType) {
|
||||
switch (orderType) {
|
||||
case SelectorOrderType.Market: {
|
||||
if (this.state.position === PositionTypes.Short) {
|
||||
this.props.buyStockShort(stock, maxShares);
|
||||
if (position === PositionTypes.Short) {
|
||||
props.buyStockShort(stock, maxShares);
|
||||
} else {
|
||||
this.props.buyStockLong(stock, maxShares);
|
||||
props.buyStockLong(stock, maxShares);
|
||||
}
|
||||
this.props.rerenderAllTickers();
|
||||
props.rerenderAllTickers();
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
@ -203,98 +197,70 @@ export class StockTicker extends React.Component<IProps, IState> {
|
||||
}
|
||||
}
|
||||
|
||||
handleHeaderClick(e: React.MouseEvent<HTMLButtonElement>): void {
|
||||
const elem = e.currentTarget;
|
||||
elem.classList.toggle("active");
|
||||
|
||||
const panel: HTMLElement = elem.nextElementSibling as HTMLElement;
|
||||
if (panel.style.display === "block") {
|
||||
panel.style.display = "none";
|
||||
} else {
|
||||
panel.style.display = "block";
|
||||
}
|
||||
}
|
||||
|
||||
handleOrderTypeChange(e: React.ChangeEvent<HTMLSelectElement>): void {
|
||||
function handleOrderTypeChange(e: SelectChangeEvent<string>): void {
|
||||
const val = e.target.value;
|
||||
|
||||
// The select value returns a string. Afaik TypeScript doesnt make it easy
|
||||
// to convert that string back to an enum type so we'll just do this for now
|
||||
switch (val) {
|
||||
case SelectorOrderType.Limit:
|
||||
this.setState({
|
||||
orderType: SelectorOrderType.Limit,
|
||||
});
|
||||
setOrderType(SelectorOrderType.Limit);
|
||||
break;
|
||||
case SelectorOrderType.Stop:
|
||||
this.setState({
|
||||
orderType: SelectorOrderType.Stop,
|
||||
});
|
||||
setOrderType(SelectorOrderType.Stop);
|
||||
break;
|
||||
case SelectorOrderType.Market:
|
||||
default:
|
||||
this.setState({
|
||||
orderType: SelectorOrderType.Market,
|
||||
});
|
||||
setOrderType(SelectorOrderType.Market);
|
||||
}
|
||||
}
|
||||
|
||||
handlePositionTypeChange(e: React.ChangeEvent<HTMLSelectElement>): void {
|
||||
function handlePositionTypeChange(e: SelectChangeEvent<string>): void {
|
||||
const val = e.target.value;
|
||||
|
||||
if (val === PositionTypes.Short) {
|
||||
this.setState({
|
||||
position: PositionTypes.Short,
|
||||
});
|
||||
setPosition(PositionTypes.Short);
|
||||
} else {
|
||||
this.setState({
|
||||
position: PositionTypes.Long,
|
||||
});
|
||||
setPosition(PositionTypes.Long);
|
||||
}
|
||||
}
|
||||
|
||||
handleQuantityChange(e: React.ChangeEvent<HTMLInputElement>): void {
|
||||
this.setState({
|
||||
qty: e.target.value,
|
||||
});
|
||||
function handleQuantityChange(e: React.ChangeEvent<HTMLInputElement>): void {
|
||||
setQty(e.target.value);
|
||||
}
|
||||
|
||||
handleSellButtonClick(): void {
|
||||
const shares = this.getQuantity();
|
||||
function handleSellButtonClick(): void {
|
||||
const shares = getQuantity();
|
||||
if (isNaN(shares)) {
|
||||
dialogBoxCreate(`Invalid input for quantity (number of shares): ${this.state.qty}`);
|
||||
dialogBoxCreate(`Invalid input for quantity (number of shares): ${qty}`);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (this.state.orderType) {
|
||||
switch (orderType) {
|
||||
case SelectorOrderType.Market: {
|
||||
if (this.state.position === PositionTypes.Short) {
|
||||
this.props.sellStockShort(this.props.stock, shares);
|
||||
if (position === PositionTypes.Short) {
|
||||
props.sellStockShort(props.stock, shares);
|
||||
} else {
|
||||
this.props.sellStockLong(this.props.stock, shares);
|
||||
props.sellStockLong(props.stock, shares);
|
||||
}
|
||||
this.props.rerenderAllTickers();
|
||||
props.rerenderAllTickers();
|
||||
break;
|
||||
}
|
||||
case SelectorOrderType.Limit: {
|
||||
const popupId = `place-order-popup`;
|
||||
createPopup(popupId, PlaceOrderPopup, {
|
||||
setOpen(true);
|
||||
setModalProps({
|
||||
text: "Enter the price for your Limit Order",
|
||||
placeText: "Place Sell Limit Order",
|
||||
place: (price: number) =>
|
||||
this.props.placeOrder(this.props.stock, shares, price, OrderTypes.LimitSell, this.state.position),
|
||||
popupId: popupId,
|
||||
place: (price: number) => props.placeOrder(props.stock, shares, price, OrderTypes.LimitSell, position),
|
||||
});
|
||||
break;
|
||||
}
|
||||
case SelectorOrderType.Stop: {
|
||||
const popupId = `place-order-popup`;
|
||||
createPopup(popupId, PlaceOrderPopup, {
|
||||
setOpen(true);
|
||||
setModalProps({
|
||||
text: "Enter the price for your Stop Order",
|
||||
placeText: "Place Sell Stop Order",
|
||||
place: (price: number) =>
|
||||
this.props.placeOrder(this.props.stock, shares, price, OrderTypes.StopSell, this.state.position),
|
||||
popupId: popupId,
|
||||
place: (price: number) => props.placeOrder(props.stock, shares, price, OrderTypes.StopSell, position),
|
||||
});
|
||||
break;
|
||||
}
|
||||
@ -303,17 +269,17 @@ export class StockTicker extends React.Component<IProps, IState> {
|
||||
}
|
||||
}
|
||||
|
||||
handleSellAllButtonClick(): void {
|
||||
const stock = this.props.stock;
|
||||
function handleSellAllButtonClick(): void {
|
||||
const stock = props.stock;
|
||||
|
||||
switch (this.state.orderType) {
|
||||
switch (orderType) {
|
||||
case SelectorOrderType.Market: {
|
||||
if (this.state.position === PositionTypes.Short) {
|
||||
this.props.sellStockShort(stock, stock.playerShortShares);
|
||||
if (position === PositionTypes.Short) {
|
||||
props.sellStockShort(stock, stock.playerShortShares);
|
||||
} else {
|
||||
this.props.sellStockLong(stock, stock.playerShares);
|
||||
props.sellStockLong(stock, stock.playerShares);
|
||||
}
|
||||
this.props.rerenderAllTickers();
|
||||
props.rerenderAllTickers();
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
@ -324,69 +290,56 @@ export class StockTicker extends React.Component<IProps, IState> {
|
||||
}
|
||||
|
||||
// Whether the player has access to orders besides market orders (limit/stop)
|
||||
hasOrderAccess(): boolean {
|
||||
return this.props.p.bitNodeN === 8 || SourceFileFlags[8] >= 3;
|
||||
function hasOrderAccess(): boolean {
|
||||
return props.p.bitNodeN === 8 || SourceFileFlags[8] >= 3;
|
||||
}
|
||||
|
||||
// Whether the player has access to shorting stocks
|
||||
hasShortAccess(): boolean {
|
||||
return this.props.p.bitNodeN === 8 || SourceFileFlags[8] >= 2;
|
||||
function hasShortAccess(): boolean {
|
||||
return props.p.bitNodeN === 8 || SourceFileFlags[8] >= 2;
|
||||
}
|
||||
|
||||
render(): React.ReactNode {
|
||||
return (
|
||||
<li>
|
||||
<BBAccordion
|
||||
headerContent={<StockTickerHeaderText p={this.props.p} stock={this.props.stock} />}
|
||||
panelContent={
|
||||
<div>
|
||||
<input
|
||||
className="stock-market-input"
|
||||
onChange={this.handleQuantityChange}
|
||||
placeholder="Quantity (Shares)"
|
||||
value={this.state.qty}
|
||||
/>
|
||||
<select
|
||||
className="stock-market-input dropdown"
|
||||
onChange={this.handlePositionTypeChange}
|
||||
value={this.state.position}
|
||||
>
|
||||
<option value={PositionTypes.Long}>Long</option>
|
||||
{this.hasShortAccess() && <option value={PositionTypes.Short}>Short</option>}
|
||||
</select>
|
||||
<select
|
||||
className="stock-market-input dropdown"
|
||||
onChange={this.handleOrderTypeChange}
|
||||
value={this.state.orderType}
|
||||
>
|
||||
<option value={SelectorOrderType.Market}>{SelectorOrderType.Market}</option>
|
||||
{this.hasOrderAccess() && <option value={SelectorOrderType.Limit}>{SelectorOrderType.Limit}</option>}
|
||||
{this.hasOrderAccess() && <option value={SelectorOrderType.Stop}>{SelectorOrderType.Stop}</option>}
|
||||
</select>
|
||||
return (
|
||||
<Box component={Paper}>
|
||||
<ListItemButton onClick={() => setTicketOpen((old) => !old)}>
|
||||
<ListItemText primary={<StockTickerHeaderText p={props.p} stock={props.stock} />} />
|
||||
{tickerOpen ? <ExpandLess color="primary" /> : <ExpandMore color="primary" />}
|
||||
</ListItemButton>
|
||||
<Collapse in={tickerOpen} unmountOnExit>
|
||||
<Box sx={{ mx: 4 }}>
|
||||
<Box display="flex" alignItems="center">
|
||||
<TextField onChange={handleQuantityChange} placeholder="Quantity (Shares)" value={qty} />
|
||||
<Select onChange={handlePositionTypeChange} value={position}>
|
||||
<MenuItem value={PositionTypes.Long}>Long</MenuItem>
|
||||
{hasShortAccess() && <MenuItem value={PositionTypes.Short}>Short</MenuItem>}
|
||||
</Select>
|
||||
<Select onChange={handleOrderTypeChange} value={orderType}>
|
||||
<MenuItem value={SelectorOrderType.Market}>{SelectorOrderType.Market}</MenuItem>
|
||||
{hasOrderAccess() && <MenuItem value={SelectorOrderType.Limit}>{SelectorOrderType.Limit}</MenuItem>}
|
||||
{hasOrderAccess() && <MenuItem value={SelectorOrderType.Stop}>{SelectorOrderType.Stop}</MenuItem>}
|
||||
</Select>
|
||||
|
||||
<StockTickerTxButton
|
||||
onClick={this.handleBuyButtonClick}
|
||||
text={"Buy"}
|
||||
tooltip={this.getBuyTransactionCostContent()}
|
||||
/>
|
||||
<StockTickerTxButton
|
||||
onClick={this.handleSellButtonClick}
|
||||
text={"Sell"}
|
||||
tooltip={this.getSellTransactionCostContent()}
|
||||
/>
|
||||
<StockTickerTxButton onClick={this.handleBuyMaxButtonClick} text={"Buy MAX"} />
|
||||
<StockTickerTxButton onClick={this.handleSellAllButtonClick} text={"Sell ALL"} />
|
||||
<StockTickerPositionText p={this.props.p} stock={this.props.stock} />
|
||||
<StockTickerOrderList
|
||||
cancelOrder={this.props.cancelOrder}
|
||||
orders={this.props.orders}
|
||||
p={this.props.p}
|
||||
stock={this.props.stock}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
<StockTickerTxButton onClick={handleBuyButtonClick} text={"Buy"} tooltip={getBuyTransactionCostContent()} />
|
||||
<StockTickerTxButton
|
||||
onClick={handleSellButtonClick}
|
||||
text={"Sell"}
|
||||
tooltip={getSellTransactionCostContent()}
|
||||
/>
|
||||
<StockTickerTxButton onClick={handleBuyMaxButtonClick} text={"Buy MAX"} />
|
||||
<StockTickerTxButton onClick={handleSellAllButtonClick} text={"Sell ALL"} />
|
||||
</Box>
|
||||
<StockTickerPositionText p={props.p} stock={props.stock} />
|
||||
<StockTickerOrderList cancelOrder={props.cancelOrder} orders={props.orders} p={props.p} stock={props.stock} />
|
||||
|
||||
<PlaceOrderModal
|
||||
text={modalProps.text}
|
||||
placeText={modalProps.placeText}
|
||||
place={modalProps.place}
|
||||
open={open}
|
||||
onClose={() => setOpen(false)}
|
||||
/>
|
||||
</Box>
|
||||
</Collapse>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import { TickerHeaderFormatData } from "../data/TickerHeaderFormatData";
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
import { Settings } from "../../Settings/Settings";
|
||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||
import Typography from "@mui/material/Typography";
|
||||
|
||||
type IProps = {
|
||||
p: IPlayer;
|
||||
@ -45,14 +46,16 @@ export function StockTickerHeaderText(props: IProps): React.ReactElement {
|
||||
// hdrText += ` - ${stock.getAbsoluteForecast()} / ${stock.otlkMagForecast}`;
|
||||
}
|
||||
|
||||
const styleMarkup = {
|
||||
color: "#66ff33",
|
||||
};
|
||||
let color = "primary";
|
||||
if (stock.lastPrice === stock.price) {
|
||||
styleMarkup.color = "white";
|
||||
color = "secondary";
|
||||
} else if (stock.lastPrice > stock.price) {
|
||||
styleMarkup.color = "red";
|
||||
color = "error";
|
||||
}
|
||||
|
||||
return <pre style={styleMarkup}>{hdrText}</pre>;
|
||||
return (
|
||||
<Typography style={{ whiteSpace: "pre" }} color={color}>
|
||||
{hdrText}
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
|
@ -8,40 +8,33 @@ import { PositionTypes } from "../data/PositionTypes";
|
||||
|
||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||
import { Money } from "../../ui/React/Money";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Button from "@mui/material/Button";
|
||||
import Box from "@mui/material/Box";
|
||||
|
||||
type IProps = {
|
||||
cancelOrder: (params: any) => void;
|
||||
order: Order;
|
||||
};
|
||||
|
||||
export class StockTickerOrder extends React.Component<IProps, any> {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.handleCancelOrderClick = this.handleCancelOrderClick.bind(this);
|
||||
export function StockTickerOrder(props: IProps): React.ReactElement {
|
||||
function handleCancelOrderClick(): void {
|
||||
props.cancelOrder({ order: props.order });
|
||||
}
|
||||
|
||||
handleCancelOrderClick(): void {
|
||||
this.props.cancelOrder({ order: this.props.order });
|
||||
}
|
||||
const order = props.order;
|
||||
|
||||
render(): React.ReactNode {
|
||||
const order = this.props.order;
|
||||
const posTxt = order.pos === PositionTypes.Long ? "Long Position" : "Short Position";
|
||||
const txt = (
|
||||
<>
|
||||
{order.type} - {posTxt} - {numeralWrapper.formatShares(order.shares)} @ <Money money={order.price} />
|
||||
</>
|
||||
);
|
||||
|
||||
const posTxt = order.pos === PositionTypes.Long ? "Long Position" : "Short Position";
|
||||
const txt = (
|
||||
<>
|
||||
{order.type} - {posTxt} - {numeralWrapper.formatShares(order.shares)} @ <Money money={order.price} />
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<li>
|
||||
{txt}
|
||||
<button className={"std-button stock-market-order-cancel-btn"} onClick={this.handleCancelOrderClick}>
|
||||
Cancel Order
|
||||
</button>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Box display="flex" alignItems="center">
|
||||
<Typography>{txt}</Typography>
|
||||
<Button onClick={handleCancelOrderClick}>Cancel Order</Button>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
@ -18,14 +18,12 @@ type IProps = {
|
||||
stock: Stock;
|
||||
};
|
||||
|
||||
export class StockTickerOrderList extends React.Component<IProps, any> {
|
||||
render(): React.ReactNode {
|
||||
const orders: React.ReactElement[] = [];
|
||||
for (let i = 0; i < this.props.orders.length; ++i) {
|
||||
const o = this.props.orders[i];
|
||||
orders.push(<StockTickerOrder cancelOrder={this.props.cancelOrder} order={o} key={i} />);
|
||||
}
|
||||
|
||||
return <ul className={"stock-market-order-list"}>{orders}</ul>;
|
||||
export function StockTickerOrderList(props: IProps): React.ReactElement {
|
||||
const orders: React.ReactElement[] = [];
|
||||
for (let i = 0; i < props.orders.length; ++i) {
|
||||
const o = props.orders[i];
|
||||
orders.push(<StockTickerOrder cancelOrder={props.cancelOrder} order={o} key={i} />);
|
||||
}
|
||||
|
||||
return <>{orders}</>;
|
||||
}
|
||||
|
@ -10,107 +10,109 @@ import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||
import { Money } from "../../ui/React/Money";
|
||||
import { SourceFileFlags } from "../../SourceFile/SourceFileFlags";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import Box from "@mui/material/Box";
|
||||
|
||||
type IProps = {
|
||||
p: IPlayer;
|
||||
stock: Stock;
|
||||
};
|
||||
|
||||
const blockStyleMarkup = {
|
||||
display: "block",
|
||||
};
|
||||
function LongPosition(props: IProps): React.ReactElement {
|
||||
const stock = props.stock;
|
||||
|
||||
export class StockTickerPositionText extends React.Component<IProps, any> {
|
||||
renderLongPosition(): React.ReactElement {
|
||||
const stock = this.props.stock;
|
||||
// Caculate total returns
|
||||
const totalCost = stock.playerShares * stock.playerAvgPx;
|
||||
const gains = (stock.getBidPrice() - stock.playerAvgPx) * stock.playerShares;
|
||||
let percentageGains = gains / totalCost;
|
||||
if (isNaN(percentageGains)) {
|
||||
percentageGains = 0;
|
||||
}
|
||||
|
||||
// Caculate total returns
|
||||
const totalCost = stock.playerShares * stock.playerAvgPx;
|
||||
const gains = (stock.getBidPrice() - stock.playerAvgPx) * stock.playerShares;
|
||||
let percentageGains = gains / totalCost;
|
||||
if (isNaN(percentageGains)) {
|
||||
percentageGains = 0;
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<Box display="flex">
|
||||
<Tooltip
|
||||
title={
|
||||
<Typography>
|
||||
Shares in the long position will increase in value if the price of the corresponding stock increases
|
||||
</Typography>
|
||||
}
|
||||
>
|
||||
<Typography variant="h5" color="primary">
|
||||
Long Position:
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
<Typography>Shares: {numeralWrapper.formatShares(stock.playerShares)}</Typography>
|
||||
<Typography>
|
||||
Average Price: <Money money={stock.playerAvgPx} /> (Total Cost: <Money money={totalCost} />
|
||||
</Typography>
|
||||
<Typography>
|
||||
Profit: <Money money={gains} /> ({numeralWrapper.formatPercentage(percentageGains)})
|
||||
</Typography>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function ShortPosition(props: IProps): React.ReactElement {
|
||||
const stock = props.stock;
|
||||
|
||||
// Caculate total returns
|
||||
const totalCost = stock.playerShortShares * stock.playerAvgShortPx;
|
||||
const gains = (stock.playerAvgShortPx - stock.getAskPrice()) * stock.playerShortShares;
|
||||
let percentageGains = gains / totalCost;
|
||||
if (isNaN(percentageGains)) {
|
||||
percentageGains = 0;
|
||||
}
|
||||
|
||||
if (props.p.bitNodeN === 8 || SourceFileFlags[8] >= 2) {
|
||||
return (
|
||||
<div>
|
||||
<h3 className={"tooltip"}>
|
||||
Long Position:
|
||||
<span className={"tooltiptext"}>
|
||||
Shares in the long position will increase in value if the price of the corresponding stock increases
|
||||
</span>
|
||||
</h3>
|
||||
<br />
|
||||
<p>Shares: {numeralWrapper.formatShares(stock.playerShares)}</p>
|
||||
<br />
|
||||
<p>
|
||||
Average Price: <Money money={stock.playerAvgPx} /> (Total Cost: <Money money={totalCost} />
|
||||
</p>
|
||||
<br />
|
||||
<p>
|
||||
<>
|
||||
<Box display="flex">
|
||||
<Tooltip
|
||||
title={
|
||||
<Typography>
|
||||
Shares in the short position will increase in value if the price of the corresponding stock decreases
|
||||
</Typography>
|
||||
}
|
||||
>
|
||||
<Typography variant="h5" color="primary">
|
||||
Short Position:
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
|
||||
<Typography>Shares: {numeralWrapper.formatShares(stock.playerShortShares)}</Typography>
|
||||
<Typography>
|
||||
Average Price: <Money money={stock.playerAvgShortPx} /> (Total Cost: <Money money={totalCost} />)
|
||||
</Typography>
|
||||
<Typography>
|
||||
Profit: <Money money={gains} /> ({numeralWrapper.formatPercentage(percentageGains)})
|
||||
</p>
|
||||
<br />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderShortPosition(): React.ReactElement | null {
|
||||
const stock = this.props.stock;
|
||||
|
||||
// Caculate total returns
|
||||
const totalCost = stock.playerShortShares * stock.playerAvgShortPx;
|
||||
const gains = (stock.playerAvgShortPx - stock.getAskPrice()) * stock.playerShortShares;
|
||||
let percentageGains = gains / totalCost;
|
||||
if (isNaN(percentageGains)) {
|
||||
percentageGains = 0;
|
||||
}
|
||||
|
||||
if (this.props.p.bitNodeN === 8 || SourceFileFlags[8] >= 2) {
|
||||
return (
|
||||
<div>
|
||||
<h3 className={"tooltip"}>
|
||||
Short Position:
|
||||
<span className={"tooltiptext"}>
|
||||
Shares in the short position will increase in value if the price of the corresponding stock decreases
|
||||
</span>
|
||||
</h3>
|
||||
<br />
|
||||
<p>Shares: {numeralWrapper.formatShares(stock.playerShortShares)}</p>
|
||||
<br />
|
||||
<p>
|
||||
Average Price: <Money money={stock.playerAvgShortPx} /> (Total Cost: <Money money={totalCost} />)
|
||||
</p>
|
||||
<br />
|
||||
<p>
|
||||
Profit: <Money money={gains} /> ({numeralWrapper.formatPercentage(percentageGains)})
|
||||
</p>
|
||||
<br />
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
render(): React.ReactNode {
|
||||
const stock = this.props.stock;
|
||||
|
||||
return (
|
||||
<div className={"stock-market-position-text"}>
|
||||
<p style={blockStyleMarkup}>Max Shares: {numeralWrapper.formatShares(stock.maxShares)}</p>
|
||||
<p className={"tooltip"}>
|
||||
Ask Price: <Money money={stock.getAskPrice()} />
|
||||
<span className={"tooltiptext"}>See Investopedia for details on what this is</span>
|
||||
</p>
|
||||
<br />
|
||||
<p className={"tooltip"}>
|
||||
Bid Price: <Money money={stock.getBidPrice()} />
|
||||
<span className={"tooltiptext"}>See Investopedia for details on what this is</span>
|
||||
</p>
|
||||
{this.renderLongPosition()}
|
||||
{this.renderShortPosition()}
|
||||
</div>
|
||||
</Typography>
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
return <></>;
|
||||
}
|
||||
}
|
||||
|
||||
export function StockTickerPositionText(props: IProps): React.ReactElement {
|
||||
const stock = props.stock;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Typography>Max Shares: {numeralWrapper.formatShares(stock.maxShares)}</Typography>
|
||||
<Typography className={"tooltip"}>
|
||||
Ask Price: <Money money={stock.getAskPrice()} />
|
||||
</Typography>
|
||||
<br />
|
||||
<Typography className={"tooltip"}>
|
||||
Bid Price: <Money money={stock.getBidPrice()} />
|
||||
</Typography>
|
||||
<LongPosition {...props} />
|
||||
<ShortPosition {...props} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -4,6 +4,9 @@
|
||||
*/
|
||||
import * as React from "react";
|
||||
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import Button from "@mui/material/Button";
|
||||
type IProps = {
|
||||
onClick: () => void;
|
||||
text: string;
|
||||
@ -11,17 +14,9 @@ type IProps = {
|
||||
};
|
||||
|
||||
export function StockTickerTxButton(props: IProps): React.ReactElement {
|
||||
let className = "stock-market-input std-button";
|
||||
|
||||
const hasTooltip = props.tooltip != null;
|
||||
if (hasTooltip) {
|
||||
className += " tooltip";
|
||||
}
|
||||
|
||||
return (
|
||||
<button className={className} onClick={props.onClick}>
|
||||
{props.text}
|
||||
{props.tooltip != null && <span className={"tooltiptext"}>{props.tooltip}</span>}
|
||||
</button>
|
||||
<Tooltip title={props.tooltip != null ? <Typography>{props.tooltip}</Typography> : ""}>
|
||||
<Button onClick={props.onClick}>{props.text}</Button>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
* of the stock tickers. It also contains the configuration for the
|
||||
* stock ticker UI (watchlist filter, portfolio vs all mode, etc.)
|
||||
*/
|
||||
import * as React from "react";
|
||||
import React, { useState } from "react";
|
||||
|
||||
import { StockTicker } from "./StockTicker";
|
||||
import { StockTickersConfig, TickerDisplayMode } from "./StockTickersConfig";
|
||||
@ -16,8 +16,6 @@ import { PositionTypes } from "../data/PositionTypes";
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
import { EventEmitter } from "../../utils/EventEmitter";
|
||||
|
||||
import { ErrorBoundary } from "../../ui/React/ErrorBoundary";
|
||||
|
||||
export type txFn = (stock: Stock, shares: number) => boolean;
|
||||
export type placeOrderFn = (
|
||||
stock: Stock,
|
||||
@ -39,169 +37,85 @@ type IProps = {
|
||||
stockMarket: IStockMarket;
|
||||
};
|
||||
|
||||
type IState = {
|
||||
rerenderFlag: boolean;
|
||||
tickerDisplayMode: TickerDisplayMode;
|
||||
watchlistFilter: string;
|
||||
watchlistSymbols: string[];
|
||||
};
|
||||
export function StockTickers(props: IProps): React.ReactElement {
|
||||
const setRerender = useState(false)[1];
|
||||
const [tickerDisplayMode, setTickerDisplayMode] = useState(TickerDisplayMode.AllStocks);
|
||||
const [watchlistFilter, setWatchlistFilter] = useState("");
|
||||
const [watchlistSymbols, setWatchlistSymbols] = useState<string[]>([]);
|
||||
|
||||
export class StockTickers extends React.Component<IProps, IState> {
|
||||
listRef: React.RefObject<HTMLUListElement>;
|
||||
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
rerenderFlag: false,
|
||||
tickerDisplayMode: TickerDisplayMode.AllStocks,
|
||||
watchlistFilter: "",
|
||||
watchlistSymbols: [],
|
||||
};
|
||||
|
||||
this.changeDisplayMode = this.changeDisplayMode.bind(this);
|
||||
this.changeWatchlistFilter = this.changeWatchlistFilter.bind(this);
|
||||
this.collapseAllTickers = this.collapseAllTickers.bind(this);
|
||||
this.expandAllTickers = this.expandAllTickers.bind(this);
|
||||
this.rerender = this.rerender.bind(this);
|
||||
|
||||
this.listRef = React.createRef();
|
||||
}
|
||||
|
||||
changeDisplayMode(): void {
|
||||
if (this.state.tickerDisplayMode === TickerDisplayMode.AllStocks) {
|
||||
this.setState({
|
||||
tickerDisplayMode: TickerDisplayMode.Portfolio,
|
||||
});
|
||||
function changeDisplayMode(): void {
|
||||
if (tickerDisplayMode === TickerDisplayMode.AllStocks) {
|
||||
setTickerDisplayMode(TickerDisplayMode.Portfolio);
|
||||
} else {
|
||||
this.setState({
|
||||
tickerDisplayMode: TickerDisplayMode.AllStocks,
|
||||
});
|
||||
setTickerDisplayMode(TickerDisplayMode.AllStocks);
|
||||
}
|
||||
}
|
||||
|
||||
changeWatchlistFilter(e: React.ChangeEvent<HTMLInputElement>): void {
|
||||
function changeWatchlistFilter(e: React.ChangeEvent<HTMLInputElement>): void {
|
||||
const watchlist = e.target.value;
|
||||
const sanitizedWatchlist = watchlist.replace(/\s/g, "");
|
||||
|
||||
this.setState({
|
||||
watchlistFilter: watchlist,
|
||||
});
|
||||
setWatchlistFilter(watchlist);
|
||||
|
||||
if (sanitizedWatchlist !== "") {
|
||||
this.setState({
|
||||
watchlistSymbols: sanitizedWatchlist.split(","),
|
||||
});
|
||||
setWatchlistSymbols(sanitizedWatchlist.split(","));
|
||||
} else {
|
||||
this.setState({
|
||||
watchlistSymbols: [],
|
||||
});
|
||||
setWatchlistSymbols([]);
|
||||
}
|
||||
}
|
||||
|
||||
collapseAllTickers(): void {
|
||||
const ul = this.listRef.current;
|
||||
if (ul == null) {
|
||||
return;
|
||||
}
|
||||
const tickers = ul.getElementsByClassName("accordion-header");
|
||||
for (let i = 0; i < tickers.length; ++i) {
|
||||
const ticker = tickers[i];
|
||||
if (!(ticker instanceof HTMLButtonElement)) {
|
||||
function rerender(): void {
|
||||
setRerender((old) => !old);
|
||||
}
|
||||
|
||||
const tickers: React.ReactElement[] = [];
|
||||
for (const stockMarketProp in props.stockMarket) {
|
||||
const val = props.stockMarket[stockMarketProp];
|
||||
if (val instanceof Stock) {
|
||||
// Skip if there's a filter and the stock isnt in that filter
|
||||
if (watchlistSymbols.length > 0 && !watchlistSymbols.includes(val.symbol)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ticker.classList.contains("active")) {
|
||||
ticker.click();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
expandAllTickers(): void {
|
||||
const ul = this.listRef.current;
|
||||
if (ul == null) {
|
||||
return;
|
||||
}
|
||||
const tickers = ul.getElementsByClassName("accordion-header");
|
||||
for (let i = 0; i < tickers.length; ++i) {
|
||||
const ticker = tickers[i];
|
||||
if (!(ticker instanceof HTMLButtonElement)) {
|
||||
continue;
|
||||
let orders = props.stockMarket.Orders[val.symbol];
|
||||
if (orders == null) {
|
||||
orders = [];
|
||||
}
|
||||
|
||||
if (!ticker.classList.contains("active")) {
|
||||
ticker.click();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rerender(): void {
|
||||
this.setState((prevState) => {
|
||||
return {
|
||||
rerenderFlag: !prevState.rerenderFlag,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
render(): React.ReactNode {
|
||||
const tickers: React.ReactElement[] = [];
|
||||
for (const stockMarketProp in this.props.stockMarket) {
|
||||
const val = this.props.stockMarket[stockMarketProp];
|
||||
if (val instanceof Stock) {
|
||||
// Skip if there's a filter and the stock isnt in that filter
|
||||
if (this.state.watchlistSymbols.length > 0 && !this.state.watchlistSymbols.includes(val.symbol)) {
|
||||
// Skip if we're in portfolio mode and the player doesnt own this or have any active orders
|
||||
if (tickerDisplayMode === TickerDisplayMode.Portfolio) {
|
||||
if (val.playerShares === 0 && val.playerShortShares === 0 && orders.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let orders = this.props.stockMarket.Orders[val.symbol];
|
||||
if (orders == null) {
|
||||
orders = [];
|
||||
}
|
||||
|
||||
// Skip if we're in portfolio mode and the player doesnt own this or have any active orders
|
||||
if (this.state.tickerDisplayMode === TickerDisplayMode.Portfolio) {
|
||||
if (val.playerShares === 0 && val.playerShortShares === 0 && orders.length === 0) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
tickers.push(
|
||||
<StockTicker
|
||||
buyStockLong={this.props.buyStockLong}
|
||||
buyStockShort={this.props.buyStockShort}
|
||||
cancelOrder={this.props.cancelOrder}
|
||||
key={val.symbol}
|
||||
orders={orders}
|
||||
p={this.props.p}
|
||||
placeOrder={this.props.placeOrder}
|
||||
rerenderAllTickers={this.rerender}
|
||||
sellStockLong={this.props.sellStockLong}
|
||||
sellStockShort={this.props.sellStockShort}
|
||||
stock={val}
|
||||
/>,
|
||||
);
|
||||
}
|
||||
|
||||
tickers.push(
|
||||
<StockTicker
|
||||
buyStockLong={props.buyStockLong}
|
||||
buyStockShort={props.buyStockShort}
|
||||
cancelOrder={props.cancelOrder}
|
||||
key={val.symbol}
|
||||
orders={orders}
|
||||
p={props.p}
|
||||
placeOrder={props.placeOrder}
|
||||
rerenderAllTickers={rerender}
|
||||
sellStockLong={props.sellStockLong}
|
||||
sellStockShort={props.sellStockShort}
|
||||
stock={val}
|
||||
/>,
|
||||
);
|
||||
}
|
||||
|
||||
const errorBoundaryProps = {
|
||||
eventEmitterForReset: this.props.eventEmitterForReset,
|
||||
id: "StockTickersErrorBoundary",
|
||||
};
|
||||
|
||||
return (
|
||||
<ErrorBoundary {...errorBoundaryProps}>
|
||||
<StockTickersConfig
|
||||
changeDisplayMode={this.changeDisplayMode}
|
||||
changeWatchlistFilter={this.changeWatchlistFilter}
|
||||
collapseAllTickers={this.collapseAllTickers}
|
||||
expandAllTickers={this.expandAllTickers}
|
||||
tickerDisplayMode={this.state.tickerDisplayMode}
|
||||
/>
|
||||
|
||||
<ul id="stock-market-list" ref={this.listRef}>
|
||||
{tickers}
|
||||
</ul>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<StockTickersConfig
|
||||
changeDisplayMode={changeDisplayMode}
|
||||
changeWatchlistFilter={changeWatchlistFilter}
|
||||
tickerDisplayMode={tickerDisplayMode}
|
||||
/>
|
||||
|
||||
{tickers}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -4,8 +4,10 @@
|
||||
* all/portoflio mode, etc)
|
||||
*/
|
||||
import * as React from "react";
|
||||
|
||||
import { StdButton } from "../../ui/React/StdButton";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import Button from "@mui/material/Button";
|
||||
import TextField from "@mui/material/TextField";
|
||||
|
||||
export enum TickerDisplayMode {
|
||||
AllStocks,
|
||||
@ -15,45 +17,38 @@ export enum TickerDisplayMode {
|
||||
type IProps = {
|
||||
changeDisplayMode: () => void;
|
||||
changeWatchlistFilter: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
collapseAllTickers: () => void;
|
||||
expandAllTickers: () => void;
|
||||
tickerDisplayMode: TickerDisplayMode;
|
||||
};
|
||||
|
||||
export class StockTickersConfig extends React.Component<IProps, any> {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
function DisplayModeButton(props: IProps): React.ReactElement {
|
||||
let txt = "";
|
||||
let tooltip = "";
|
||||
if (props.tickerDisplayMode === TickerDisplayMode.Portfolio) {
|
||||
txt = "Switch to 'All Stocks' Mode";
|
||||
tooltip = "Displays all stocks on the WSE";
|
||||
} else {
|
||||
txt = "Switch to 'Portfolio' Mode";
|
||||
tooltip = "Displays only the stocks for which you have shares or orders";
|
||||
}
|
||||
|
||||
renderDisplayModeButton(): React.ReactNode {
|
||||
let txt = "";
|
||||
let tooltip = "";
|
||||
if (this.props.tickerDisplayMode === TickerDisplayMode.Portfolio) {
|
||||
txt = "Switch to 'All Stocks' Mode";
|
||||
tooltip = "Displays all stocks on the WSE";
|
||||
} else {
|
||||
txt = "Switch to 'Portfolio' Mode";
|
||||
tooltip = "Displays only the stocks for which you have shares or orders";
|
||||
}
|
||||
|
||||
return <StdButton onClick={this.props.changeDisplayMode} text={txt} tooltip={tooltip} />;
|
||||
}
|
||||
|
||||
render(): React.ReactNode {
|
||||
return (
|
||||
<div>
|
||||
{this.renderDisplayModeButton()}
|
||||
<StdButton onClick={this.props.expandAllTickers} text="Expand Tickers" />
|
||||
<StdButton onClick={this.props.collapseAllTickers} text="Collapse Tickers" />
|
||||
|
||||
<input
|
||||
className="text-input"
|
||||
id="stock-market-watchlist-filter"
|
||||
onChange={this.props.changeWatchlistFilter}
|
||||
placeholder="Filter Stocks by symbol (comma-separated list)"
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Tooltip title={<Typography>{tooltip}</Typography>}>
|
||||
<Button onClick={props.changeDisplayMode}>{txt}</Button>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
export function StockTickersConfig(props: IProps): React.ReactElement {
|
||||
return (
|
||||
<div>
|
||||
<DisplayModeButton {...props} />
|
||||
<br />
|
||||
<TextField
|
||||
sx={{ width: "100%" }}
|
||||
onChange={props.changeWatchlistFilter}
|
||||
placeholder="Filter Stocks by symbol (comma-separated list)"
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -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,
|
||||
};
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user