mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-11-18 05:33:54 +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,
|
* general information about the stock market, buttons for the various purchases,
|
||||||
* and a link to the documentation (Investopedia)
|
* and a link to the documentation (Investopedia)
|
||||||
*/
|
*/
|
||||||
import * as React from "react";
|
import React, { useState } from "react";
|
||||||
|
|
||||||
import { getStockMarket4SDataCost, getStockMarket4STixApiCost } from "../StockMarketCosts";
|
import { getStockMarket4SDataCost, getStockMarket4STixApiCost } from "../StockMarketCosts";
|
||||||
|
|
||||||
import { CONSTANTS } from "../../Constants";
|
import { CONSTANTS } from "../../Constants";
|
||||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||||
import { StdButton } from "../../ui/React/StdButton";
|
|
||||||
import { StdButtonPurchased } from "../../ui/React/StdButtonPurchased";
|
|
||||||
import { Money } from "../../ui/React/Money";
|
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 = {
|
type IProps = {
|
||||||
initStockMarket: () => void;
|
initStockMarket: () => void;
|
||||||
@ -21,228 +26,210 @@ type IProps = {
|
|||||||
rerender: () => void;
|
rerender: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const blockStyleMarkup = {
|
function Purchase4SMarketDataTixApiAccessButton(props: IProps): React.ReactElement {
|
||||||
display: "block",
|
function purchase4SMarketDataTixApiAccess(): void {
|
||||||
};
|
if (props.p.has4SDataTixApi) {
|
||||||
|
|
||||||
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) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!this.props.p.canAfford(CONSTANTS.WSEAccountCost)) {
|
if (!props.p.canAfford(getStockMarket4STixApiCost())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.props.p.hasWseAccount = true;
|
props.p.has4SDataTixApi = true;
|
||||||
this.props.initStockMarket();
|
props.p.loseMoney(getStockMarket4STixApiCost());
|
||||||
this.props.p.loseMoney(CONSTANTS.WSEAccountCost);
|
props.rerender();
|
||||||
this.props.rerender();
|
|
||||||
|
|
||||||
const worldHeader = document.getElementById("world-menu-header");
|
|
||||||
if (worldHeader instanceof HTMLElement) {
|
|
||||||
worldHeader.click();
|
|
||||||
worldHeader.click();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
purchaseTixApiAccess(): void {
|
if (props.p.has4SDataTixApi) {
|
||||||
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 (
|
return (
|
||||||
<StdButton
|
<Typography>
|
||||||
disabled={!this.props.p.canAfford(cost)}
|
Market Data TIX API Access <CheckIcon />
|
||||||
onClick={this.purchaseWseAccount}
|
</Typography>
|
||||||
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 {
|
} else {
|
||||||
const cost = getStockMarket4STixApiCost();
|
const cost = getStockMarket4STixApiCost();
|
||||||
return (
|
return (
|
||||||
<StdButton
|
<Tooltip
|
||||||
disabled={!this.props.p.canAfford(cost)}
|
title={
|
||||||
onClick={this.purchase4SMarketDataTixApiAccess}
|
!props.p.hasTixApiAccess ? (
|
||||||
text={
|
<Typography>Requires TIX API Access</Typography>
|
||||||
<>
|
) : (
|
||||||
Buy 4S Market Data TIX API Access - <Money money={cost} player={this.props.p} />
|
<Typography>Let you access 4S Market Data through Netscript</Typography>
|
||||||
</>
|
)
|
||||||
}
|
}
|
||||||
tooltip={"Let you access 4S Market Data through Netscript"}
|
>
|
||||||
/>
|
<span>
|
||||||
);
|
<Button
|
||||||
}
|
disabled={!props.p.hasTixApiAccess || !props.p.canAfford(cost)}
|
||||||
}
|
onClick={purchase4SMarketDataTixApiAccess}
|
||||||
|
>
|
||||||
render(): React.ReactNode {
|
Buy 4S Market Data TIX API Access -
|
||||||
const documentationLink = "https://bitburner.readthedocs.io/en/latest/basicgameplay/stockmarket.html";
|
<Money money={cost} player={props.p} />
|
||||||
return (
|
</Button>
|
||||||
<div className={"stock-market-info-and-purchases"}>
|
</span>
|
||||||
<p>Welcome to the World Stock Exchange (WSE)!</p>
|
</Tooltip>
|
||||||
<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>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 () => clearInterval(id);
|
||||||
}, []);
|
}, []);
|
||||||
return (
|
return (
|
||||||
<div className="stock-market-container">
|
<>
|
||||||
<InfoAndPurchases initStockMarket={props.initStockMarket} p={props.p} rerender={rerender} />
|
<InfoAndPurchases initStockMarket={props.initStockMarket} p={props.p} rerender={rerender} />
|
||||||
{props.p.hasWseAccount && (
|
{props.p.hasWseAccount && (
|
||||||
<StockTickers
|
<StockTickers
|
||||||
@ -62,6 +62,6 @@ export function StockMarketRoot(props: IProps): React.ReactElement {
|
|||||||
stockMarket={props.stockMarket}
|
stockMarket={props.stockMarket}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
/**
|
/**
|
||||||
* React Component for a single stock ticker in the Stock Market UI
|
* 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 { StockTickerHeaderText } from "./StockTickerHeaderText";
|
||||||
import { StockTickerOrderList } from "./StockTickerOrderList";
|
import { StockTickerOrderList } from "./StockTickerOrderList";
|
||||||
import { StockTickerPositionText } from "./StockTickerPositionText";
|
import { StockTickerPositionText } from "./StockTickerPositionText";
|
||||||
import { StockTickerTxButton } from "./StockTickerTxButton";
|
import { StockTickerTxButton } from "./StockTickerTxButton";
|
||||||
import { PlaceOrderPopup } from "./PlaceOrderPopup";
|
import { PlaceOrderModal } from "./PlaceOrderModal";
|
||||||
|
|
||||||
import { Order } from "../Order";
|
import { Order } from "../Order";
|
||||||
import { Stock } from "../Stock";
|
import { Stock } from "../Stock";
|
||||||
@ -18,11 +18,21 @@ import { PositionTypes } from "../data/PositionTypes";
|
|||||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||||
import { SourceFileFlags } from "../../SourceFile/SourceFileFlags";
|
import { SourceFileFlags } from "../../SourceFile/SourceFileFlags";
|
||||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||||
import { BBAccordion } from "../../ui/React/BBAccordion";
|
|
||||||
import { Money } from "../../ui/React/Money";
|
import { Money } from "../../ui/React/Money";
|
||||||
import { createPopup } from "../../ui/React/createPopup";
|
|
||||||
|
|
||||||
import { dialogBoxCreate } from "../../ui/React/DialogBox";
|
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 {
|
enum SelectorOrderType {
|
||||||
Market = "Market Order",
|
Market = "Market Order",
|
||||||
@ -52,66 +62,55 @@ type IProps = {
|
|||||||
stock: Stock;
|
stock: Stock;
|
||||||
};
|
};
|
||||||
|
|
||||||
type IState = {
|
export function StockTicker(props: IProps): React.ReactElement {
|
||||||
orderType: SelectorOrderType;
|
const [orderType, setOrderType] = useState(SelectorOrderType.Market);
|
||||||
position: PositionTypes;
|
const [position, setPosition] = useState(PositionTypes.Long);
|
||||||
qty: string;
|
const [qty, setQty] = useState("");
|
||||||
};
|
const [open, setOpen] = useState(false);
|
||||||
|
const [tickerOpen, setTicketOpen] = useState(false);
|
||||||
|
|
||||||
export class StockTicker extends React.Component<IProps, IState> {
|
const [modalProps, setModalProps] = useState<{
|
||||||
constructor(props: IProps) {
|
text: string;
|
||||||
super(props);
|
placeText: string;
|
||||||
|
place: (n: number) => boolean;
|
||||||
|
}>({
|
||||||
|
text: "",
|
||||||
|
placeText: "",
|
||||||
|
place: (n: number) => false,
|
||||||
|
});
|
||||||
|
|
||||||
this.state = {
|
function getBuyTransactionCostContent(): JSX.Element | null {
|
||||||
orderType: SelectorOrderType.Market,
|
const stock = props.stock;
|
||||||
position: PositionTypes.Long,
|
const qty: number = getQuantity();
|
||||||
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();
|
|
||||||
if (isNaN(qty)) {
|
if (isNaN(qty)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const cost = getBuyTransactionCost(stock, qty, this.state.position);
|
const cost = getBuyTransactionCost(stock, qty, position);
|
||||||
if (cost == null) {
|
if (cost == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
Purchasing {numeralWrapper.formatShares(qty)} shares (
|
Purchasing {numeralWrapper.formatShares(qty)} shares ({position === PositionTypes.Long ? "Long" : "Short"}
|
||||||
{this.state.position === PositionTypes.Long ? "Long" : "Short"}) will cost <Money money={cost} />.
|
) will cost <Money money={cost} />.
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getQuantity(): number {
|
function getQuantity(): number {
|
||||||
return Math.round(parseFloat(this.state.qty));
|
return Math.round(parseFloat(qty));
|
||||||
}
|
}
|
||||||
|
|
||||||
getSellTransactionCostContent(): JSX.Element | null {
|
function getSellTransactionCostContent(): JSX.Element | null {
|
||||||
const stock = this.props.stock;
|
const stock = props.stock;
|
||||||
const qty: number = this.getQuantity();
|
const qty: number = getQuantity();
|
||||||
if (isNaN(qty)) {
|
if (isNaN(qty)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.state.position === PositionTypes.Long) {
|
if (position === PositionTypes.Long) {
|
||||||
if (qty > stock.playerShares) {
|
if (qty > stock.playerShares) {
|
||||||
return <>You do not have this many shares in the Long position</>;
|
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) {
|
if (cost == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
Selling {numeralWrapper.formatShares(qty)} shares (
|
Selling {numeralWrapper.formatShares(qty)} shares ({position === PositionTypes.Long ? "Long" : "Short"}) will
|
||||||
{this.state.position === PositionTypes.Long ? "Long" : "Short"}) will result in a gain of <Money money={cost} />
|
result in a gain of <Money money={cost} />.
|
||||||
.
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleBuyButtonClick(): void {
|
function handleBuyButtonClick(): void {
|
||||||
const shares = this.getQuantity();
|
const shares = getQuantity();
|
||||||
if (isNaN(shares)) {
|
if (isNaN(shares)) {
|
||||||
dialogBoxCreate(`Invalid input for quantity (number of shares): ${this.state.qty}`);
|
dialogBoxCreate(`Invalid input for quantity (number of shares): ${qty}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (this.state.orderType) {
|
switch (orderType) {
|
||||||
case SelectorOrderType.Market: {
|
case SelectorOrderType.Market: {
|
||||||
if (this.state.position === PositionTypes.Short) {
|
if (position === PositionTypes.Short) {
|
||||||
this.props.buyStockShort(this.props.stock, shares);
|
props.buyStockShort(props.stock, shares);
|
||||||
} else {
|
} else {
|
||||||
this.props.buyStockLong(this.props.stock, shares);
|
props.buyStockLong(props.stock, shares);
|
||||||
}
|
}
|
||||||
this.props.rerenderAllTickers();
|
props.rerenderAllTickers();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case SelectorOrderType.Limit: {
|
case SelectorOrderType.Limit: {
|
||||||
const popupId = `place-order-popup`;
|
setOpen(true);
|
||||||
createPopup(popupId, PlaceOrderPopup, {
|
setModalProps({
|
||||||
text: "Enter the price for your Limit Order",
|
text: "Enter the price for your Limit Order",
|
||||||
placeText: "Place Buy Limit Order",
|
placeText: "Place Buy Limit Order",
|
||||||
place: (price: number) =>
|
place: (price: number) => props.placeOrder(props.stock, shares, price, OrderTypes.LimitBuy, position),
|
||||||
this.props.placeOrder(this.props.stock, shares, price, OrderTypes.LimitBuy, this.state.position),
|
|
||||||
popupId: popupId,
|
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case SelectorOrderType.Stop: {
|
case SelectorOrderType.Stop: {
|
||||||
const popupId = `place-order-popup`;
|
setOpen(true);
|
||||||
createPopup(popupId, PlaceOrderPopup, {
|
setModalProps({
|
||||||
text: "Enter the price for your Stop Order",
|
text: "Enter the price for your Stop Order",
|
||||||
placeText: "Place Buy Stop Order",
|
placeText: "Place Buy Stop Order",
|
||||||
place: (price: number) =>
|
place: (price: number) => props.placeOrder(props.stock, shares, price, OrderTypes.StopBuy, position),
|
||||||
this.props.placeOrder(this.props.stock, shares, price, OrderTypes.StopBuy, this.state.position),
|
|
||||||
popupId: popupId,
|
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -179,21 +173,21 @@ export class StockTicker extends React.Component<IProps, IState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleBuyMaxButtonClick(): void {
|
function handleBuyMaxButtonClick(): void {
|
||||||
const playerMoney: number = this.props.p.money.toNumber();
|
const playerMoney: number = props.p.money.toNumber();
|
||||||
|
|
||||||
const stock = this.props.stock;
|
const stock = props.stock;
|
||||||
let maxShares = calculateBuyMaxAmount(stock, this.state.position, playerMoney);
|
let maxShares = calculateBuyMaxAmount(stock, position, playerMoney);
|
||||||
maxShares = Math.min(maxShares, Math.round(stock.maxShares - stock.playerShares - stock.playerShortShares));
|
maxShares = Math.min(maxShares, Math.round(stock.maxShares - stock.playerShares - stock.playerShortShares));
|
||||||
|
|
||||||
switch (this.state.orderType) {
|
switch (orderType) {
|
||||||
case SelectorOrderType.Market: {
|
case SelectorOrderType.Market: {
|
||||||
if (this.state.position === PositionTypes.Short) {
|
if (position === PositionTypes.Short) {
|
||||||
this.props.buyStockShort(stock, maxShares);
|
props.buyStockShort(stock, maxShares);
|
||||||
} else {
|
} else {
|
||||||
this.props.buyStockLong(stock, maxShares);
|
props.buyStockLong(stock, maxShares);
|
||||||
}
|
}
|
||||||
this.props.rerenderAllTickers();
|
props.rerenderAllTickers();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
@ -203,98 +197,70 @@ export class StockTicker extends React.Component<IProps, IState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleHeaderClick(e: React.MouseEvent<HTMLButtonElement>): void {
|
function handleOrderTypeChange(e: SelectChangeEvent<string>): 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 {
|
|
||||||
const val = e.target.value;
|
const val = e.target.value;
|
||||||
|
|
||||||
// The select value returns a string. Afaik TypeScript doesnt make it easy
|
// 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
|
// to convert that string back to an enum type so we'll just do this for now
|
||||||
switch (val) {
|
switch (val) {
|
||||||
case SelectorOrderType.Limit:
|
case SelectorOrderType.Limit:
|
||||||
this.setState({
|
setOrderType(SelectorOrderType.Limit);
|
||||||
orderType: SelectorOrderType.Limit,
|
|
||||||
});
|
|
||||||
break;
|
break;
|
||||||
case SelectorOrderType.Stop:
|
case SelectorOrderType.Stop:
|
||||||
this.setState({
|
setOrderType(SelectorOrderType.Stop);
|
||||||
orderType: SelectorOrderType.Stop,
|
|
||||||
});
|
|
||||||
break;
|
break;
|
||||||
case SelectorOrderType.Market:
|
case SelectorOrderType.Market:
|
||||||
default:
|
default:
|
||||||
this.setState({
|
setOrderType(SelectorOrderType.Market);
|
||||||
orderType: SelectorOrderType.Market,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePositionTypeChange(e: React.ChangeEvent<HTMLSelectElement>): void {
|
function handlePositionTypeChange(e: SelectChangeEvent<string>): void {
|
||||||
const val = e.target.value;
|
const val = e.target.value;
|
||||||
|
|
||||||
if (val === PositionTypes.Short) {
|
if (val === PositionTypes.Short) {
|
||||||
this.setState({
|
setPosition(PositionTypes.Short);
|
||||||
position: PositionTypes.Short,
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
this.setState({
|
setPosition(PositionTypes.Long);
|
||||||
position: PositionTypes.Long,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleQuantityChange(e: React.ChangeEvent<HTMLInputElement>): void {
|
function handleQuantityChange(e: React.ChangeEvent<HTMLInputElement>): void {
|
||||||
this.setState({
|
setQty(e.target.value);
|
||||||
qty: e.target.value,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSellButtonClick(): void {
|
function handleSellButtonClick(): void {
|
||||||
const shares = this.getQuantity();
|
const shares = getQuantity();
|
||||||
if (isNaN(shares)) {
|
if (isNaN(shares)) {
|
||||||
dialogBoxCreate(`Invalid input for quantity (number of shares): ${this.state.qty}`);
|
dialogBoxCreate(`Invalid input for quantity (number of shares): ${qty}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (this.state.orderType) {
|
switch (orderType) {
|
||||||
case SelectorOrderType.Market: {
|
case SelectorOrderType.Market: {
|
||||||
if (this.state.position === PositionTypes.Short) {
|
if (position === PositionTypes.Short) {
|
||||||
this.props.sellStockShort(this.props.stock, shares);
|
props.sellStockShort(props.stock, shares);
|
||||||
} else {
|
} else {
|
||||||
this.props.sellStockLong(this.props.stock, shares);
|
props.sellStockLong(props.stock, shares);
|
||||||
}
|
}
|
||||||
this.props.rerenderAllTickers();
|
props.rerenderAllTickers();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case SelectorOrderType.Limit: {
|
case SelectorOrderType.Limit: {
|
||||||
const popupId = `place-order-popup`;
|
setOpen(true);
|
||||||
createPopup(popupId, PlaceOrderPopup, {
|
setModalProps({
|
||||||
text: "Enter the price for your Limit Order",
|
text: "Enter the price for your Limit Order",
|
||||||
placeText: "Place Sell Limit Order",
|
placeText: "Place Sell Limit Order",
|
||||||
place: (price: number) =>
|
place: (price: number) => props.placeOrder(props.stock, shares, price, OrderTypes.LimitSell, position),
|
||||||
this.props.placeOrder(this.props.stock, shares, price, OrderTypes.LimitSell, this.state.position),
|
|
||||||
popupId: popupId,
|
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case SelectorOrderType.Stop: {
|
case SelectorOrderType.Stop: {
|
||||||
const popupId = `place-order-popup`;
|
setOpen(true);
|
||||||
createPopup(popupId, PlaceOrderPopup, {
|
setModalProps({
|
||||||
text: "Enter the price for your Stop Order",
|
text: "Enter the price for your Stop Order",
|
||||||
placeText: "Place Sell Stop Order",
|
placeText: "Place Sell Stop Order",
|
||||||
place: (price: number) =>
|
place: (price: number) => props.placeOrder(props.stock, shares, price, OrderTypes.StopSell, position),
|
||||||
this.props.placeOrder(this.props.stock, shares, price, OrderTypes.StopSell, this.state.position),
|
|
||||||
popupId: popupId,
|
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -303,17 +269,17 @@ export class StockTicker extends React.Component<IProps, IState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSellAllButtonClick(): void {
|
function handleSellAllButtonClick(): void {
|
||||||
const stock = this.props.stock;
|
const stock = props.stock;
|
||||||
|
|
||||||
switch (this.state.orderType) {
|
switch (orderType) {
|
||||||
case SelectorOrderType.Market: {
|
case SelectorOrderType.Market: {
|
||||||
if (this.state.position === PositionTypes.Short) {
|
if (position === PositionTypes.Short) {
|
||||||
this.props.sellStockShort(stock, stock.playerShortShares);
|
props.sellStockShort(stock, stock.playerShortShares);
|
||||||
} else {
|
} else {
|
||||||
this.props.sellStockLong(stock, stock.playerShares);
|
props.sellStockLong(stock, stock.playerShares);
|
||||||
}
|
}
|
||||||
this.props.rerenderAllTickers();
|
props.rerenderAllTickers();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default: {
|
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)
|
// Whether the player has access to orders besides market orders (limit/stop)
|
||||||
hasOrderAccess(): boolean {
|
function hasOrderAccess(): boolean {
|
||||||
return this.props.p.bitNodeN === 8 || SourceFileFlags[8] >= 3;
|
return props.p.bitNodeN === 8 || SourceFileFlags[8] >= 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Whether the player has access to shorting stocks
|
// Whether the player has access to shorting stocks
|
||||||
hasShortAccess(): boolean {
|
function hasShortAccess(): boolean {
|
||||||
return this.props.p.bitNodeN === 8 || SourceFileFlags[8] >= 2;
|
return props.p.bitNodeN === 8 || SourceFileFlags[8] >= 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
render(): React.ReactNode {
|
|
||||||
return (
|
return (
|
||||||
<li>
|
<Box component={Paper}>
|
||||||
<BBAccordion
|
<ListItemButton onClick={() => setTicketOpen((old) => !old)}>
|
||||||
headerContent={<StockTickerHeaderText p={this.props.p} stock={this.props.stock} />}
|
<ListItemText primary={<StockTickerHeaderText p={props.p} stock={props.stock} />} />
|
||||||
panelContent={
|
{tickerOpen ? <ExpandLess color="primary" /> : <ExpandMore color="primary" />}
|
||||||
<div>
|
</ListItemButton>
|
||||||
<input
|
<Collapse in={tickerOpen} unmountOnExit>
|
||||||
className="stock-market-input"
|
<Box sx={{ mx: 4 }}>
|
||||||
onChange={this.handleQuantityChange}
|
<Box display="flex" alignItems="center">
|
||||||
placeholder="Quantity (Shares)"
|
<TextField onChange={handleQuantityChange} placeholder="Quantity (Shares)" value={qty} />
|
||||||
value={this.state.qty}
|
<Select onChange={handlePositionTypeChange} value={position}>
|
||||||
/>
|
<MenuItem value={PositionTypes.Long}>Long</MenuItem>
|
||||||
<select
|
{hasShortAccess() && <MenuItem value={PositionTypes.Short}>Short</MenuItem>}
|
||||||
className="stock-market-input dropdown"
|
</Select>
|
||||||
onChange={this.handlePositionTypeChange}
|
<Select onChange={handleOrderTypeChange} value={orderType}>
|
||||||
value={this.state.position}
|
<MenuItem value={SelectorOrderType.Market}>{SelectorOrderType.Market}</MenuItem>
|
||||||
>
|
{hasOrderAccess() && <MenuItem value={SelectorOrderType.Limit}>{SelectorOrderType.Limit}</MenuItem>}
|
||||||
<option value={PositionTypes.Long}>Long</option>
|
{hasOrderAccess() && <MenuItem value={SelectorOrderType.Stop}>{SelectorOrderType.Stop}</MenuItem>}
|
||||||
{this.hasShortAccess() && <option value={PositionTypes.Short}>Short</option>}
|
</Select>
|
||||||
</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>
|
|
||||||
|
|
||||||
|
<StockTickerTxButton onClick={handleBuyButtonClick} text={"Buy"} tooltip={getBuyTransactionCostContent()} />
|
||||||
<StockTickerTxButton
|
<StockTickerTxButton
|
||||||
onClick={this.handleBuyButtonClick}
|
onClick={handleSellButtonClick}
|
||||||
text={"Buy"}
|
|
||||||
tooltip={this.getBuyTransactionCostContent()}
|
|
||||||
/>
|
|
||||||
<StockTickerTxButton
|
|
||||||
onClick={this.handleSellButtonClick}
|
|
||||||
text={"Sell"}
|
text={"Sell"}
|
||||||
tooltip={this.getSellTransactionCostContent()}
|
tooltip={getSellTransactionCostContent()}
|
||||||
/>
|
/>
|
||||||
<StockTickerTxButton onClick={this.handleBuyMaxButtonClick} text={"Buy MAX"} />
|
<StockTickerTxButton onClick={handleBuyMaxButtonClick} text={"Buy MAX"} />
|
||||||
<StockTickerTxButton onClick={this.handleSellAllButtonClick} text={"Sell ALL"} />
|
<StockTickerTxButton onClick={handleSellAllButtonClick} text={"Sell ALL"} />
|
||||||
<StockTickerPositionText p={this.props.p} stock={this.props.stock} />
|
</Box>
|
||||||
<StockTickerOrderList
|
<StockTickerPositionText p={props.p} stock={props.stock} />
|
||||||
cancelOrder={this.props.cancelOrder}
|
<StockTickerOrderList cancelOrder={props.cancelOrder} orders={props.orders} p={props.p} stock={props.stock} />
|
||||||
orders={this.props.orders}
|
|
||||||
p={this.props.p}
|
<PlaceOrderModal
|
||||||
stock={this.props.stock}
|
text={modalProps.text}
|
||||||
|
placeText={modalProps.placeText}
|
||||||
|
place={modalProps.place}
|
||||||
|
open={open}
|
||||||
|
onClose={() => setOpen(false)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</Box>
|
||||||
}
|
</Collapse>
|
||||||
/>
|
</Box>
|
||||||
</li>
|
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ import { TickerHeaderFormatData } from "../data/TickerHeaderFormatData";
|
|||||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||||
import { Settings } from "../../Settings/Settings";
|
import { Settings } from "../../Settings/Settings";
|
||||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||||
|
import Typography from "@mui/material/Typography";
|
||||||
|
|
||||||
type IProps = {
|
type IProps = {
|
||||||
p: IPlayer;
|
p: IPlayer;
|
||||||
@ -45,14 +46,16 @@ export function StockTickerHeaderText(props: IProps): React.ReactElement {
|
|||||||
// hdrText += ` - ${stock.getAbsoluteForecast()} / ${stock.otlkMagForecast}`;
|
// hdrText += ` - ${stock.getAbsoluteForecast()} / ${stock.otlkMagForecast}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const styleMarkup = {
|
let color = "primary";
|
||||||
color: "#66ff33",
|
|
||||||
};
|
|
||||||
if (stock.lastPrice === stock.price) {
|
if (stock.lastPrice === stock.price) {
|
||||||
styleMarkup.color = "white";
|
color = "secondary";
|
||||||
} else if (stock.lastPrice > stock.price) {
|
} 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,25 +8,21 @@ import { PositionTypes } from "../data/PositionTypes";
|
|||||||
|
|
||||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||||
import { Money } from "../../ui/React/Money";
|
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 = {
|
type IProps = {
|
||||||
cancelOrder: (params: any) => void;
|
cancelOrder: (params: any) => void;
|
||||||
order: Order;
|
order: Order;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class StockTickerOrder extends React.Component<IProps, any> {
|
export function StockTickerOrder(props: IProps): React.ReactElement {
|
||||||
constructor(props: IProps) {
|
function handleCancelOrderClick(): void {
|
||||||
super(props);
|
props.cancelOrder({ order: props.order });
|
||||||
|
|
||||||
this.handleCancelOrderClick = this.handleCancelOrderClick.bind(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCancelOrderClick(): void {
|
const order = props.order;
|
||||||
this.props.cancelOrder({ order: this.props.order });
|
|
||||||
}
|
|
||||||
|
|
||||||
render(): React.ReactNode {
|
|
||||||
const order = this.props.order;
|
|
||||||
|
|
||||||
const posTxt = order.pos === PositionTypes.Long ? "Long Position" : "Short Position";
|
const posTxt = order.pos === PositionTypes.Long ? "Long Position" : "Short Position";
|
||||||
const txt = (
|
const txt = (
|
||||||
@ -36,12 +32,9 @@ export class StockTickerOrder extends React.Component<IProps, any> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li>
|
<Box display="flex" alignItems="center">
|
||||||
{txt}
|
<Typography>{txt}</Typography>
|
||||||
<button className={"std-button stock-market-order-cancel-btn"} onClick={this.handleCancelOrderClick}>
|
<Button onClick={handleCancelOrderClick}>Cancel Order</Button>
|
||||||
Cancel Order
|
</Box>
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -18,14 +18,12 @@ type IProps = {
|
|||||||
stock: Stock;
|
stock: Stock;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class StockTickerOrderList extends React.Component<IProps, any> {
|
export function StockTickerOrderList(props: IProps): React.ReactElement {
|
||||||
render(): React.ReactNode {
|
|
||||||
const orders: React.ReactElement[] = [];
|
const orders: React.ReactElement[] = [];
|
||||||
for (let i = 0; i < this.props.orders.length; ++i) {
|
for (let i = 0; i < props.orders.length; ++i) {
|
||||||
const o = this.props.orders[i];
|
const o = props.orders[i];
|
||||||
orders.push(<StockTickerOrder cancelOrder={this.props.cancelOrder} order={o} key={i} />);
|
orders.push(<StockTickerOrder cancelOrder={props.cancelOrder} order={o} key={i} />);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <ul className={"stock-market-order-list"}>{orders}</ul>;
|
return <>{orders}</>;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -10,19 +10,17 @@ import { IPlayer } from "../../PersonObjects/IPlayer";
|
|||||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||||
import { Money } from "../../ui/React/Money";
|
import { Money } from "../../ui/React/Money";
|
||||||
import { SourceFileFlags } from "../../SourceFile/SourceFileFlags";
|
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 = {
|
type IProps = {
|
||||||
p: IPlayer;
|
p: IPlayer;
|
||||||
stock: Stock;
|
stock: Stock;
|
||||||
};
|
};
|
||||||
|
|
||||||
const blockStyleMarkup = {
|
function LongPosition(props: IProps): React.ReactElement {
|
||||||
display: "block",
|
const stock = props.stock;
|
||||||
};
|
|
||||||
|
|
||||||
export class StockTickerPositionText extends React.Component<IProps, any> {
|
|
||||||
renderLongPosition(): React.ReactElement {
|
|
||||||
const stock = this.props.stock;
|
|
||||||
|
|
||||||
// Caculate total returns
|
// Caculate total returns
|
||||||
const totalCost = stock.playerShares * stock.playerAvgPx;
|
const totalCost = stock.playerShares * stock.playerAvgPx;
|
||||||
@ -33,30 +31,33 @@ export class StockTickerPositionText extends React.Component<IProps, any> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<>
|
||||||
<h3 className={"tooltip"}>
|
<Box display="flex">
|
||||||
Long Position:
|
<Tooltip
|
||||||
<span className={"tooltiptext"}>
|
title={
|
||||||
|
<Typography>
|
||||||
Shares in the long position will increase in value if the price of the corresponding stock increases
|
Shares in the long position will increase in value if the price of the corresponding stock increases
|
||||||
</span>
|
</Typography>
|
||||||
</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>
|
|
||||||
Profit: <Money money={gains} /> ({numeralWrapper.formatPercentage(percentageGains)})
|
|
||||||
</p>
|
|
||||||
<br />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
>
|
||||||
|
<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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
renderShortPosition(): React.ReactElement | null {
|
function ShortPosition(props: IProps): React.ReactElement {
|
||||||
const stock = this.props.stock;
|
const stock = props.stock;
|
||||||
|
|
||||||
// Caculate total returns
|
// Caculate total returns
|
||||||
const totalCost = stock.playerShortShares * stock.playerAvgShortPx;
|
const totalCost = stock.playerShortShares * stock.playerAvgShortPx;
|
||||||
@ -66,51 +67,52 @@ export class StockTickerPositionText extends React.Component<IProps, any> {
|
|||||||
percentageGains = 0;
|
percentageGains = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.props.p.bitNodeN === 8 || SourceFileFlags[8] >= 2) {
|
if (props.p.bitNodeN === 8 || SourceFileFlags[8] >= 2) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<>
|
||||||
<h3 className={"tooltip"}>
|
<Box display="flex">
|
||||||
Short Position:
|
<Tooltip
|
||||||
<span className={"tooltiptext"}>
|
title={
|
||||||
|
<Typography>
|
||||||
Shares in the short position will increase in value if the price of the corresponding stock decreases
|
Shares in the short position will increase in value if the price of the corresponding stock decreases
|
||||||
</span>
|
</Typography>
|
||||||
</h3>
|
}
|
||||||
<br />
|
>
|
||||||
<p>Shares: {numeralWrapper.formatShares(stock.playerShortShares)}</p>
|
<Typography variant="h5" color="primary">
|
||||||
<br />
|
Short Position:
|
||||||
<p>
|
</Typography>
|
||||||
|
</Tooltip>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Typography>Shares: {numeralWrapper.formatShares(stock.playerShortShares)}</Typography>
|
||||||
|
<Typography>
|
||||||
Average Price: <Money money={stock.playerAvgShortPx} /> (Total Cost: <Money money={totalCost} />)
|
Average Price: <Money money={stock.playerAvgShortPx} /> (Total Cost: <Money money={totalCost} />)
|
||||||
</p>
|
</Typography>
|
||||||
<br />
|
<Typography>
|
||||||
<p>
|
|
||||||
Profit: <Money money={gains} /> ({numeralWrapper.formatPercentage(percentageGains)})
|
Profit: <Money money={gains} /> ({numeralWrapper.formatPercentage(percentageGains)})
|
||||||
</p>
|
</Typography>
|
||||||
<br />
|
</>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return <></>;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 * as React from "react";
|
||||||
|
|
||||||
|
import Typography from "@mui/material/Typography";
|
||||||
|
import Tooltip from "@mui/material/Tooltip";
|
||||||
|
import Button from "@mui/material/Button";
|
||||||
type IProps = {
|
type IProps = {
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
text: string;
|
text: string;
|
||||||
@ -11,17 +14,9 @@ type IProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function StockTickerTxButton(props: IProps): React.ReactElement {
|
export function StockTickerTxButton(props: IProps): React.ReactElement {
|
||||||
let className = "stock-market-input std-button";
|
|
||||||
|
|
||||||
const hasTooltip = props.tooltip != null;
|
|
||||||
if (hasTooltip) {
|
|
||||||
className += " tooltip";
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button className={className} onClick={props.onClick}>
|
<Tooltip title={props.tooltip != null ? <Typography>{props.tooltip}</Typography> : ""}>
|
||||||
{props.text}
|
<Button onClick={props.onClick}>{props.text}</Button>
|
||||||
{props.tooltip != null && <span className={"tooltiptext"}>{props.tooltip}</span>}
|
</Tooltip>
|
||||||
</button>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
* of the stock tickers. It also contains the configuration for the
|
* of the stock tickers. It also contains the configuration for the
|
||||||
* stock ticker UI (watchlist filter, portfolio vs all mode, etc.)
|
* 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 { StockTicker } from "./StockTicker";
|
||||||
import { StockTickersConfig, TickerDisplayMode } from "./StockTickersConfig";
|
import { StockTickersConfig, TickerDisplayMode } from "./StockTickersConfig";
|
||||||
@ -16,8 +16,6 @@ import { PositionTypes } from "../data/PositionTypes";
|
|||||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||||
import { EventEmitter } from "../../utils/EventEmitter";
|
import { EventEmitter } from "../../utils/EventEmitter";
|
||||||
|
|
||||||
import { ErrorBoundary } from "../../ui/React/ErrorBoundary";
|
|
||||||
|
|
||||||
export type txFn = (stock: Stock, shares: number) => boolean;
|
export type txFn = (stock: Stock, shares: number) => boolean;
|
||||||
export type placeOrderFn = (
|
export type placeOrderFn = (
|
||||||
stock: Stock,
|
stock: Stock,
|
||||||
@ -39,127 +37,53 @@ type IProps = {
|
|||||||
stockMarket: IStockMarket;
|
stockMarket: IStockMarket;
|
||||||
};
|
};
|
||||||
|
|
||||||
type IState = {
|
export function StockTickers(props: IProps): React.ReactElement {
|
||||||
rerenderFlag: boolean;
|
const setRerender = useState(false)[1];
|
||||||
tickerDisplayMode: TickerDisplayMode;
|
const [tickerDisplayMode, setTickerDisplayMode] = useState(TickerDisplayMode.AllStocks);
|
||||||
watchlistFilter: string;
|
const [watchlistFilter, setWatchlistFilter] = useState("");
|
||||||
watchlistSymbols: string[];
|
const [watchlistSymbols, setWatchlistSymbols] = useState<string[]>([]);
|
||||||
};
|
|
||||||
|
|
||||||
export class StockTickers extends React.Component<IProps, IState> {
|
function changeDisplayMode(): void {
|
||||||
listRef: React.RefObject<HTMLUListElement>;
|
if (tickerDisplayMode === TickerDisplayMode.AllStocks) {
|
||||||
|
setTickerDisplayMode(TickerDisplayMode.Portfolio);
|
||||||
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,
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
this.setState({
|
setTickerDisplayMode(TickerDisplayMode.AllStocks);
|
||||||
tickerDisplayMode: TickerDisplayMode.AllStocks,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
changeWatchlistFilter(e: React.ChangeEvent<HTMLInputElement>): void {
|
function changeWatchlistFilter(e: React.ChangeEvent<HTMLInputElement>): void {
|
||||||
const watchlist = e.target.value;
|
const watchlist = e.target.value;
|
||||||
const sanitizedWatchlist = watchlist.replace(/\s/g, "");
|
const sanitizedWatchlist = watchlist.replace(/\s/g, "");
|
||||||
|
|
||||||
this.setState({
|
setWatchlistFilter(watchlist);
|
||||||
watchlistFilter: watchlist,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (sanitizedWatchlist !== "") {
|
if (sanitizedWatchlist !== "") {
|
||||||
this.setState({
|
setWatchlistSymbols(sanitizedWatchlist.split(","));
|
||||||
watchlistSymbols: sanitizedWatchlist.split(","),
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
this.setState({
|
setWatchlistSymbols([]);
|
||||||
watchlistSymbols: [],
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
collapseAllTickers(): void {
|
function rerender(): void {
|
||||||
const ul = this.listRef.current;
|
setRerender((old) => !old);
|
||||||
if (ul == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const tickers = ul.getElementsByClassName("accordion-header");
|
|
||||||
for (let i = 0; i < tickers.length; ++i) {
|
|
||||||
const ticker = tickers[i];
|
|
||||||
if (!(ticker instanceof HTMLButtonElement)) {
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ticker.classList.contains("active")) {
|
|
||||||
ticker.click();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ticker.classList.contains("active")) {
|
|
||||||
ticker.click();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rerender(): void {
|
|
||||||
this.setState((prevState) => {
|
|
||||||
return {
|
|
||||||
rerenderFlag: !prevState.rerenderFlag,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
render(): React.ReactNode {
|
|
||||||
const tickers: React.ReactElement[] = [];
|
const tickers: React.ReactElement[] = [];
|
||||||
for (const stockMarketProp in this.props.stockMarket) {
|
for (const stockMarketProp in props.stockMarket) {
|
||||||
const val = this.props.stockMarket[stockMarketProp];
|
const val = props.stockMarket[stockMarketProp];
|
||||||
if (val instanceof Stock) {
|
if (val instanceof Stock) {
|
||||||
// Skip if there's a filter and the stock isnt in that filter
|
// 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)) {
|
if (watchlistSymbols.length > 0 && !watchlistSymbols.includes(val.symbol)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let orders = this.props.stockMarket.Orders[val.symbol];
|
let orders = props.stockMarket.Orders[val.symbol];
|
||||||
if (orders == null) {
|
if (orders == null) {
|
||||||
orders = [];
|
orders = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip if we're in portfolio mode and the player doesnt own this or have any active 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 (tickerDisplayMode === TickerDisplayMode.Portfolio) {
|
||||||
if (val.playerShares === 0 && val.playerShortShares === 0 && orders.length === 0) {
|
if (val.playerShares === 0 && val.playerShortShares === 0 && orders.length === 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -167,41 +91,31 @@ export class StockTickers extends React.Component<IProps, IState> {
|
|||||||
|
|
||||||
tickers.push(
|
tickers.push(
|
||||||
<StockTicker
|
<StockTicker
|
||||||
buyStockLong={this.props.buyStockLong}
|
buyStockLong={props.buyStockLong}
|
||||||
buyStockShort={this.props.buyStockShort}
|
buyStockShort={props.buyStockShort}
|
||||||
cancelOrder={this.props.cancelOrder}
|
cancelOrder={props.cancelOrder}
|
||||||
key={val.symbol}
|
key={val.symbol}
|
||||||
orders={orders}
|
orders={orders}
|
||||||
p={this.props.p}
|
p={props.p}
|
||||||
placeOrder={this.props.placeOrder}
|
placeOrder={props.placeOrder}
|
||||||
rerenderAllTickers={this.rerender}
|
rerenderAllTickers={rerender}
|
||||||
sellStockLong={this.props.sellStockLong}
|
sellStockLong={props.sellStockLong}
|
||||||
sellStockShort={this.props.sellStockShort}
|
sellStockShort={props.sellStockShort}
|
||||||
stock={val}
|
stock={val}
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const errorBoundaryProps = {
|
|
||||||
eventEmitterForReset: this.props.eventEmitterForReset,
|
|
||||||
id: "StockTickersErrorBoundary",
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ErrorBoundary {...errorBoundaryProps}>
|
<>
|
||||||
<StockTickersConfig
|
<StockTickersConfig
|
||||||
changeDisplayMode={this.changeDisplayMode}
|
changeDisplayMode={changeDisplayMode}
|
||||||
changeWatchlistFilter={this.changeWatchlistFilter}
|
changeWatchlistFilter={changeWatchlistFilter}
|
||||||
collapseAllTickers={this.collapseAllTickers}
|
tickerDisplayMode={tickerDisplayMode}
|
||||||
expandAllTickers={this.expandAllTickers}
|
|
||||||
tickerDisplayMode={this.state.tickerDisplayMode}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ul id="stock-market-list" ref={this.listRef}>
|
|
||||||
{tickers}
|
{tickers}
|
||||||
</ul>
|
</>
|
||||||
</ErrorBoundary>
|
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -4,8 +4,10 @@
|
|||||||
* all/portoflio mode, etc)
|
* all/portoflio mode, etc)
|
||||||
*/
|
*/
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
import Typography from "@mui/material/Typography";
|
||||||
import { StdButton } from "../../ui/React/StdButton";
|
import Tooltip from "@mui/material/Tooltip";
|
||||||
|
import Button from "@mui/material/Button";
|
||||||
|
import TextField from "@mui/material/TextField";
|
||||||
|
|
||||||
export enum TickerDisplayMode {
|
export enum TickerDisplayMode {
|
||||||
AllStocks,
|
AllStocks,
|
||||||
@ -15,20 +17,13 @@ export enum TickerDisplayMode {
|
|||||||
type IProps = {
|
type IProps = {
|
||||||
changeDisplayMode: () => void;
|
changeDisplayMode: () => void;
|
||||||
changeWatchlistFilter: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
changeWatchlistFilter: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||||
collapseAllTickers: () => void;
|
|
||||||
expandAllTickers: () => void;
|
|
||||||
tickerDisplayMode: TickerDisplayMode;
|
tickerDisplayMode: TickerDisplayMode;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class StockTickersConfig extends React.Component<IProps, any> {
|
function DisplayModeButton(props: IProps): React.ReactElement {
|
||||||
constructor(props: IProps) {
|
|
||||||
super(props);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderDisplayModeButton(): React.ReactNode {
|
|
||||||
let txt = "";
|
let txt = "";
|
||||||
let tooltip = "";
|
let tooltip = "";
|
||||||
if (this.props.tickerDisplayMode === TickerDisplayMode.Portfolio) {
|
if (props.tickerDisplayMode === TickerDisplayMode.Portfolio) {
|
||||||
txt = "Switch to 'All Stocks' Mode";
|
txt = "Switch to 'All Stocks' Mode";
|
||||||
tooltip = "Displays all stocks on the WSE";
|
tooltip = "Displays all stocks on the WSE";
|
||||||
} else {
|
} else {
|
||||||
@ -36,24 +31,24 @@ export class StockTickersConfig extends React.Component<IProps, any> {
|
|||||||
tooltip = "Displays only the stocks for which you have shares or orders";
|
tooltip = "Displays only the stocks for which you have shares or orders";
|
||||||
}
|
}
|
||||||
|
|
||||||
return <StdButton onClick={this.props.changeDisplayMode} text={txt} tooltip={tooltip} />;
|
return (
|
||||||
}
|
<Tooltip title={<Typography>{tooltip}</Typography>}>
|
||||||
|
<Button onClick={props.changeDisplayMode}>{txt}</Button>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
render(): React.ReactNode {
|
export function StockTickersConfig(props: IProps): React.ReactElement {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{this.renderDisplayModeButton()}
|
<DisplayModeButton {...props} />
|
||||||
<StdButton onClick={this.props.expandAllTickers} text="Expand Tickers" />
|
<br />
|
||||||
<StdButton onClick={this.props.collapseAllTickers} text="Collapse Tickers" />
|
<TextField
|
||||||
|
sx={{ width: "100%" }}
|
||||||
<input
|
onChange={props.changeWatchlistFilter}
|
||||||
className="text-input"
|
|
||||||
id="stock-market-watchlist-filter"
|
|
||||||
onChange={this.props.changeWatchlistFilter}
|
|
||||||
placeholder="Filter Stocks by symbol (comma-separated list)"
|
placeholder="Filter Stocks by symbol (comma-separated list)"
|
||||||
type="text"
|
type="text"
|
||||||
/>
|
/>
|
||||||
</div>
|
</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