mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2025-03-15 06:42:30 +01:00
Fixed Stock Market UI issues. Added warnings for price movements
This commit is contained in:
@ -7,13 +7,27 @@
|
||||
p {
|
||||
font-size: $defaultFontSize * 0.8125;
|
||||
}
|
||||
|
||||
a {
|
||||
font-size: $defaultFontSize * 0.875;
|
||||
}
|
||||
h2 {
|
||||
}
|
||||
|
||||
.stock-market-info-and-purchases {
|
||||
> h2 {
|
||||
display: block;
|
||||
margin-top: 10px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
> p {
|
||||
display: block;
|
||||
margin-left: 10px;
|
||||
width: 70%;
|
||||
}
|
||||
|
||||
> a, > button {
|
||||
margin: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
@ -27,19 +41,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
#stock-market-container p {
|
||||
padding: 6px;
|
||||
margin: 6px;
|
||||
width: 70%;
|
||||
}
|
||||
|
||||
#stock-market-container a {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
#stock-market-watchlist-filter {
|
||||
margin: 5px 5px 5px 10px;
|
||||
padding: 4px;
|
||||
width: 50%;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.stock-market-input {
|
||||
@ -51,13 +56,25 @@
|
||||
color: var(--my-font-color);
|
||||
}
|
||||
|
||||
.stock-market-price-movement-warning {
|
||||
border: 1px solid white;
|
||||
color: red;
|
||||
margin: 2px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.stock-market-position-text {
|
||||
color: #fff;
|
||||
display: inline-block;
|
||||
display: block;
|
||||
|
||||
p {
|
||||
color: #fff;
|
||||
display: block;
|
||||
display: inline-block;
|
||||
margin: 4px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,14 +1,12 @@
|
||||
import {
|
||||
Order,
|
||||
OrderTypes,
|
||||
PositionTypes
|
||||
} from "./Order";
|
||||
import { Order } from "./Order";
|
||||
import { Stock } from "./Stock";
|
||||
import {
|
||||
getStockMarket4SDataCost,
|
||||
getStockMarket4STixApiCost
|
||||
} from "./StockMarketCosts";
|
||||
import { InitStockMetadata } from "./data/InitStockMetadata";
|
||||
import { OrderTypes } from "./data/OrderTypes";
|
||||
import { PositionTypes } from "./data/PositionTypes";
|
||||
import { StockSymbols } from "./data/StockSymbols";
|
||||
import { StockMarketRoot } from "./ui/Root";
|
||||
|
||||
@ -23,7 +21,7 @@ import { dialogBoxCreate } from "../../utils/DialogBox";
|
||||
import { Reviver } from "../../utils/JSONReviver";
|
||||
|
||||
import React from "react";
|
||||
import ReactDOm from "react-dom";
|
||||
import ReactDOM from "react-dom";
|
||||
|
||||
export function placeOrder(stock, shares, price, type, position, workerScript=null) {
|
||||
var tixApi = (workerScript instanceof WorkerScript);
|
||||
@ -60,7 +58,7 @@ export function cancelOrder(params, workerScript=null) {
|
||||
if (StockMarket["Orders"] == null) {return false;}
|
||||
if (params.order && params.order instanceof Order) {
|
||||
var order = params.order;
|
||||
//An 'Order' object is passed in
|
||||
// An 'Order' object is passed in
|
||||
var stockOrders = StockMarket["Orders"][order.stock.symbol];
|
||||
for (var i = 0; i < stockOrders.length; ++i) {
|
||||
if (order == stockOrders[i]) {
|
||||
@ -72,7 +70,7 @@ export function cancelOrder(params, workerScript=null) {
|
||||
return false;
|
||||
} else if (params.stock && params.shares && params.price && params.type &&
|
||||
params.pos && params.stock instanceof Stock) {
|
||||
//Order properties are passed in. Need to look for the order
|
||||
// Order properties are passed in. Need to look for the order
|
||||
var stockOrders = StockMarket["Orders"][params.stock.symbol];
|
||||
var orderTxt = params.stock.symbol + " - " + params.shares + " @ " +
|
||||
numeralWrapper.format(params.price, '$0.000a');
|
||||
@ -124,7 +122,7 @@ function executeOrder(order) {
|
||||
break;
|
||||
}
|
||||
if (res) {
|
||||
//Remove order from order book
|
||||
// Remove order from order book
|
||||
for (var i = 0; i < stockOrders.length; ++i) {
|
||||
if (order == stockOrders[i]) {
|
||||
stockOrders.splice(i, 1);
|
||||
@ -509,14 +507,12 @@ export function processStockPrices(numCycles=1) {
|
||||
processOrders(stock, OrderTypes.LimitSell, PositionTypes.Long);
|
||||
processOrders(stock, OrderTypes.StopBuy, PositionTypes.Long);
|
||||
processOrders(stock, OrderTypes.StopSell, PositionTypes.Short);
|
||||
displayStockMarketContent();
|
||||
} else {
|
||||
stock.price /= (1 + av);
|
||||
processOrders(stock, OrderTypes.LimitBuy, PositionTypes.Long);
|
||||
processOrders(stock, OrderTypes.LimitSell, PositionTypes.Short);
|
||||
processOrders(stock, OrderTypes.StopBuy, PositionTypes.Short);
|
||||
processOrders(stock, OrderTypes.StopSell, PositionTypes.Long);
|
||||
displayStockMarketContent();
|
||||
}
|
||||
|
||||
var otlkMagChange = stock.otlkMag * av;
|
||||
@ -533,9 +529,10 @@ export function processStockPrices(numCycles=1) {
|
||||
stock.otlkMag *= -1;
|
||||
stock.b = !stock.b;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
displayStockMarketContent();
|
||||
}
|
||||
|
||||
//Checks and triggers any orders for the specified stock
|
||||
|
@ -13,6 +13,10 @@ import { CONSTANTS } from "../Constants";
|
||||
export function getBuyTransactionCost(stock: Stock, shares: number, posType: PositionTypes): number | null {
|
||||
if (isNaN(shares) || shares <= 0 || !(stock instanceof Stock)) { return null; }
|
||||
|
||||
// Cap the 'shares' arg at the stock's maximum shares. This'll prevent
|
||||
// hanging in the case when a really big number is passed in
|
||||
shares = Math.min(shares, stock.maxShares);
|
||||
|
||||
const isLong = (posType === PositionTypes.Long);
|
||||
|
||||
// If the number of shares doesn't trigger a price movement, its a simple calculation
|
||||
@ -58,6 +62,10 @@ export function getBuyTransactionCost(stock: Stock, shares: number, posType: Pos
|
||||
export function getSellTransactionGain(stock: Stock, shares: number, posType: PositionTypes): number | null {
|
||||
if (isNaN(shares) || shares <= 0 || !(stock instanceof Stock)) { return null; }
|
||||
|
||||
// Cap the 'shares' arg at the stock's maximum shares. This'll prevent
|
||||
// hanging in the case when a really big number is passed in
|
||||
shares = Math.min(shares, stock.maxShares);
|
||||
|
||||
const isLong = (posType === PositionTypes.Long);
|
||||
|
||||
// If the number of shares doesn't trigger a price mvoement, its a simple calculation
|
||||
|
@ -39,6 +39,16 @@ export class InfoAndPurchases extends React.Component<IProps, any> {
|
||||
this.purchase4SMarketDataTixApiAccess = this.purchase4SMarketDataTixApiAccess.bind(this);
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps: IProps) {
|
||||
// This only need to rerender if the player has purchased something new
|
||||
if (this.props.p.hasWseAccount !== nextProps.p.hasWseAccount) { return true; }
|
||||
if (this.props.p.hasTixApiAccess !== nextProps.p.hasTixApiAccess) { return true; }
|
||||
if (this.props.p.has4SData !== nextProps.p.has4SData) { return true; }
|
||||
if (this.props.p.has4SDataTixApi !== nextProps.p.has4SDataTixApi) { return true; }
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
handleClick4SMarketDataHelpTip() {
|
||||
dialogBoxCreate(
|
||||
"Access to the 4S Market Data feed will display two additional pieces " +
|
||||
@ -179,15 +189,19 @@ export class InfoAndPurchases extends React.Component<IProps, any> {
|
||||
render() {
|
||||
const documentationLink = "https://bitburner.readthedocs.io/en/latest/basicgameplay/stockmarket.html";
|
||||
return (
|
||||
<div>
|
||||
<p>Welcome to the World Stock Exchange (WSE)!</p><br /><br />
|
||||
<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.
|
||||
To begin trading, you must first purchase an account:
|
||||
</p>
|
||||
{this.renderPurchaseWseAccountButton()}
|
||||
<a className={"std-button"} href={documentationLink} target={"_blank"}>
|
||||
Investopedia
|
||||
</a>
|
||||
|
||||
<h2>Trade Information eXchange (TIX) API</h2>
|
||||
<p>
|
||||
TIX, short for Trade Information eXchange, is the communications protocol
|
||||
@ -208,7 +222,7 @@ export class InfoAndPurchases extends React.Component<IProps, any> {
|
||||
<p>
|
||||
Commission Fees: Every transaction you make has
|
||||
a {numeralWrapper.formatMoney(CONSTANTS.StockMarketCommission)} commission fee.
|
||||
</p>
|
||||
</p><br />
|
||||
<p>
|
||||
WARNING: When you reset after installing Augmentations, the Stock
|
||||
Market is reset. You will retain your WSE Account, access to the
|
||||
|
@ -10,12 +10,18 @@ import { StockTickerTxButton } from "./StockTickerTxButton";
|
||||
|
||||
import { Order } from "../Order";
|
||||
import { Stock } from "../Stock";
|
||||
import {
|
||||
getBuyTransactionCost,
|
||||
getSellTransactionGain,
|
||||
} from "../StockMarketHelpers";
|
||||
import { OrderTypes } from "../data/OrderTypes";
|
||||
import { PositionTypes } from "../data/PositionTypes";
|
||||
|
||||
import { CONSTANTS } from "../../Constants";
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
import { SourceFileFlags } from "../../SourceFile/SourceFileFlags";
|
||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||
import { Accordion } from "../../ui/React/Accordion";
|
||||
|
||||
import { dialogBoxCreate } from "../../../utils/DialogBox";
|
||||
import {
|
||||
@ -63,8 +69,11 @@ export class StockTicker extends React.Component<IProps, IState> {
|
||||
qty: "",
|
||||
}
|
||||
|
||||
this.getBuyTransactionCostText = this.getBuyTransactionCostText.bind(this);
|
||||
this.getSellTransactionCostText = this.getSellTransactionCostText.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);
|
||||
@ -96,8 +105,46 @@ export class StockTicker extends React.Component<IProps, IState> {
|
||||
yesNoTxtInpBoxCreate(popupTxt);
|
||||
}
|
||||
|
||||
getBuyTransactionCostText(): string {
|
||||
const stock = this.props.stock;
|
||||
const qty: number = this.getQuantity();
|
||||
if (isNaN(qty)) { return ""; }
|
||||
const cost = getBuyTransactionCost(this.props.stock, qty, this.state.position);
|
||||
if (cost == null) { return ""; }
|
||||
|
||||
let costTxt = `Purchasing ${numeralWrapper.formatBigNumber(qty)} shares will cost ${numeralWrapper.formatMoney(cost)}. `;
|
||||
|
||||
const causesMovement = qty > stock.shareTxUntilMovement;
|
||||
if (causesMovement) {
|
||||
costTxt += `WARNING: Purchasing this many shares will influence the stock price`;
|
||||
}
|
||||
|
||||
return costTxt;
|
||||
}
|
||||
|
||||
getQuantity(): number {
|
||||
return Math.round(parseFloat(this.state.qty));
|
||||
}
|
||||
|
||||
getSellTransactionCostText(): string {
|
||||
const stock = this.props.stock;
|
||||
const qty: number = this.getQuantity();
|
||||
if (isNaN(qty)) { return ""; }
|
||||
const cost = getSellTransactionGain(this.props.stock, qty, this.state.position);
|
||||
if (cost == null) { return ""; }
|
||||
|
||||
let costTxt = `Selling ${numeralWrapper.formatBigNumber(qty)} shares will result in a gain of ${numeralWrapper.formatMoney(cost)}. `;
|
||||
|
||||
const causesMovement = qty > stock.shareTxUntilMovement;
|
||||
if (causesMovement) {
|
||||
costTxt += `WARNING: Selling this many shares will influence the stock price`;
|
||||
}
|
||||
|
||||
return costTxt;
|
||||
}
|
||||
|
||||
handleBuyButtonClick() {
|
||||
const shares = parseInt(this.state.qty);
|
||||
const shares = this.getQuantity();
|
||||
if (isNaN(shares)) {
|
||||
dialogBoxCreate(`Invalid input for quantity (number of shares): ${this.state.qty}`);
|
||||
return;
|
||||
@ -178,6 +225,18 @@ export class StockTicker extends React.Component<IProps, IState> {
|
||||
}
|
||||
}
|
||||
|
||||
handleHeaderClick(e: React.MouseEvent<HTMLButtonElement>) {
|
||||
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>) {
|
||||
const val = e.target.value;
|
||||
|
||||
@ -224,7 +283,7 @@ export class StockTicker extends React.Component<IProps, IState> {
|
||||
}
|
||||
|
||||
handleSellButtonClick() {
|
||||
const shares = parseInt(this.state.qty);
|
||||
const shares = this.getQuantity();
|
||||
if (isNaN(shares)) {
|
||||
dialogBoxCreate(`Invalid input for quantity (number of shares): ${this.state.qty}`);
|
||||
return;
|
||||
@ -294,49 +353,68 @@ export class StockTicker extends React.Component<IProps, IState> {
|
||||
}
|
||||
|
||||
render() {
|
||||
// Determine if the player's intended transaction will cause a price movement
|
||||
let causesMovement: boolean = false;
|
||||
const qty = this.getQuantity();
|
||||
if (!isNaN(qty)) {
|
||||
causesMovement = qty > this.props.stock.shareTxUntilMovement;
|
||||
}
|
||||
|
||||
return (
|
||||
<li>
|
||||
<button className={"accordion-header"}>
|
||||
<StockTickerHeaderText p={this.props.p} stock={this.props.stock} />
|
||||
</button>
|
||||
<div className={"accordion-panel"}>
|
||||
<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={"Long"}>Long</option>
|
||||
{
|
||||
this.hasShortAccess() &&
|
||||
<option value={"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>
|
||||
<Accordion
|
||||
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={"Long"}>Long</option>
|
||||
{
|
||||
this.hasShortAccess() &&
|
||||
<option value={"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>
|
||||
|
||||
<StockTickerTxButton onClick={this.handleBuyButtonClick} text={"Buy"} />
|
||||
<StockTickerTxButton onClick={this.handleSellButtonClick} text={"Sell"} />
|
||||
<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>
|
||||
<StockTickerTxButton onClick={this.handleBuyButtonClick} text={"Buy"} tooltip={this.getBuyTransactionCostText()} />
|
||||
<StockTickerTxButton onClick={this.handleSellButtonClick} text={"Sell"} tooltip={this.getSellTransactionCostText()} />
|
||||
<StockTickerTxButton onClick={this.handleBuyMaxButtonClick} text={"Buy MAX"} />
|
||||
<StockTickerTxButton onClick={this.handleSellAllButtonClick} text={"Sell ALL"} />
|
||||
{
|
||||
causesMovement &&
|
||||
<p className="stock-market-price-movement-warning">
|
||||
WARNING: Buying/Selling {numeralWrapper.formatBigNumber(qty)} shares will affect
|
||||
the stock's price. This applies during the transaction itself as well. See Investopedia
|
||||
for more details.
|
||||
</p>
|
||||
}
|
||||
<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>
|
||||
)
|
||||
}
|
||||
|
@ -37,18 +37,16 @@ export class StockTickerPositionText extends React.Component<IProps, any> {
|
||||
Shares in the long position will increase in value if the price
|
||||
of the corresponding stock increases
|
||||
</span>
|
||||
</h3>
|
||||
</h3><br />
|
||||
<p>
|
||||
Shares: {numeralWrapper.format(stock.playerShares, "0,0")}
|
||||
</p>
|
||||
</p><br />
|
||||
<p>
|
||||
Average Price: {numeralWrapper.formatMoney(stock.playerAvgPx)}
|
||||
(Total Cost: {numeralWrapper.formatMoney(totalCost)})
|
||||
</p>
|
||||
Average Price: {numeralWrapper.formatMoney(stock.playerAvgPx)} (Total Cost: {numeralWrapper.formatMoney(totalCost)})
|
||||
</p><br />
|
||||
<p>
|
||||
Profit: {numeralWrapper.formatMoney(gains)}
|
||||
({numeralWrapper.formatPercentage(percentageGains)})
|
||||
</p>
|
||||
Profit: {numeralWrapper.formatMoney(gains)} ({numeralWrapper.formatPercentage(percentageGains)})
|
||||
</p><br />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -71,18 +69,16 @@ export class StockTickerPositionText extends React.Component<IProps, any> {
|
||||
Shares in the short position will increase in value if the
|
||||
price of the corresponding stock decreases
|
||||
</span>
|
||||
</h3>
|
||||
</h3><br />
|
||||
<p>
|
||||
Shares: {numeralWrapper.format(stock.playerShortShares, "0,0")}
|
||||
</p>
|
||||
</p><br />
|
||||
<p>
|
||||
Average Price: {numeralWrapper.formatMoney(stock.playerAvgShortPx)}
|
||||
(Total Cost: {numeralWrapper.formatMoney(totalCost)})
|
||||
</p>
|
||||
Average Price: {numeralWrapper.formatMoney(stock.playerAvgShortPx)} (Total Cost: {numeralWrapper.formatMoney(totalCost)})
|
||||
</p><br />
|
||||
<p>
|
||||
Profit: {numeralWrapper.formatMoney(gains)}
|
||||
({numeralWrapper.formatPercentage(percentageGains)})
|
||||
</p>
|
||||
Profit: {numeralWrapper.formatMoney(gains)} ({numeralWrapper.formatPercentage(percentageGains)})
|
||||
</p><br />
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
@ -96,15 +92,16 @@ export class StockTickerPositionText extends React.Component<IProps, any> {
|
||||
return (
|
||||
<div className={"stock-market-position-text"}>
|
||||
<p style={blockStyleMarkup}>
|
||||
Max Shares: ${numeralWrapper.formatMoney(stock.maxShares)}
|
||||
Max Shares: {numeralWrapper.formatBigNumber(stock.maxShares)}
|
||||
</p>
|
||||
<p className={"tooltip"} >
|
||||
Ask Price: {numeralWrapper.formatMoney(stock.getAskPrice())}
|
||||
<span className={"tooltiptext"}>
|
||||
See Investopedia for details on what this is
|
||||
</span>
|
||||
</p>
|
||||
</p><br />
|
||||
<p className={"tooltip"} >
|
||||
Bid Price: {numeralWrapper.formatMoney(stock.getBidPrice())}
|
||||
<span className={"tooltiptext"}>
|
||||
See Investopedia for details on what this is
|
||||
</span>
|
||||
|
@ -7,12 +7,35 @@ import * as React from "react";
|
||||
type IProps = {
|
||||
onClick: () => void;
|
||||
text: string;
|
||||
tooltip?: string;
|
||||
}
|
||||
|
||||
type IInnerHTMLMarkup = {
|
||||
__html: string;
|
||||
}
|
||||
|
||||
export function StockTickerTxButton(props: IProps): React.ReactElement {
|
||||
let className = "stock-market-input std-button";
|
||||
|
||||
const hasTooltip = (typeof props.tooltip === "string" && props.tooltip !== "");
|
||||
if (hasTooltip) {
|
||||
className += " tooltip";
|
||||
}
|
||||
|
||||
let tooltipMarkup: IInnerHTMLMarkup | null;
|
||||
if (hasTooltip) {
|
||||
tooltipMarkup = {
|
||||
__html: props.tooltip!
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<button className={"stock-market-input std-button"} onClick={props.onClick}>
|
||||
<button className={className} onClick={props.onClick}>
|
||||
{props.text}
|
||||
{
|
||||
hasTooltip &&
|
||||
<span className={"tooltiptext"} dangerouslySetInnerHTML={tooltipMarkup!}></span>
|
||||
}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
@ -75,6 +75,10 @@ export class StockTickers extends React.Component<IProps, IState> {
|
||||
this.setState({
|
||||
watchlistSymbols: sanitizedWatchlist.split(","),
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
watchlistSymbols: [],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -91,8 +95,9 @@ export class StockTickers extends React.Component<IProps, IState> {
|
||||
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)) {
|
||||
continue; // Not in watchlist
|
||||
continue;
|
||||
}
|
||||
|
||||
let orders = this.props.stockMarket.Orders[val.symbol];
|
||||
@ -100,11 +105,19 @@ export class StockTickers extends React.Component<IProps, IState> {
|
||||
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}
|
||||
|
@ -82,7 +82,6 @@ import {
|
||||
} from "./Server/SpecialServerIps";
|
||||
import {
|
||||
StockMarket,
|
||||
StockSymbols,
|
||||
SymbolToStockMap,
|
||||
initSymbolToStockMap,
|
||||
stockMarketCycle,
|
||||
|
73
src/ui/React/Accordion.tsx
Normal file
73
src/ui/React/Accordion.tsx
Normal file
@ -0,0 +1,73 @@
|
||||
/**
|
||||
* React component to create an accordion element
|
||||
*/
|
||||
import * as React from "react";
|
||||
|
||||
type IProps = {
|
||||
headerContent: React.ReactElement;
|
||||
panelContent: React.ReactElement;
|
||||
}
|
||||
|
||||
type IState = {
|
||||
panelOpened: boolean;
|
||||
}
|
||||
|
||||
export class Accordion extends React.Component<IProps, IState> {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.handleHeaderClick = this.handleHeaderClick.bind(this);
|
||||
|
||||
this.state = {
|
||||
panelOpened: false,
|
||||
}
|
||||
}
|
||||
|
||||
handleHeaderClick(e: React.MouseEvent<HTMLButtonElement>) {
|
||||
const elem = e.currentTarget;
|
||||
elem.classList.toggle("active");
|
||||
|
||||
const panel: HTMLElement = elem.nextElementSibling as HTMLElement;
|
||||
if (panel!.style.display === "block") {
|
||||
panel!.style.display = "none";
|
||||
this.setState({
|
||||
panelOpened: false,
|
||||
});
|
||||
} else {
|
||||
panel!.style.display = "block";
|
||||
this.setState({
|
||||
panelOpened: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<button className={"accordion-header"} onClick={this.handleHeaderClick}>
|
||||
{this.props.headerContent}
|
||||
</button>
|
||||
<AccordionPanel opened={this.state.panelOpened} panelContent={this.props.panelContent} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
type IPanelProps = {
|
||||
opened: boolean;
|
||||
panelContent: React.ReactElement;
|
||||
}
|
||||
|
||||
class AccordionPanel extends React.Component<IPanelProps, any> {
|
||||
shouldComponentUpdate(nextProps: IPanelProps) {
|
||||
return this.props.opened || nextProps.opened;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className={"accordion-panel"}>
|
||||
{this.props.panelContent}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user