Fix GH Issue #616: Stock Market UI throws error for certain locales because the price format length is too high

This commit is contained in:
danielyxie 2019-07-01 22:28:24 -07:00 committed by danielyxie
parent 658df9fb01
commit dc928828e2
5 changed files with 135 additions and 3 deletions

@ -20,6 +20,7 @@ import { CONSTANTS } from "../Constants";
import { WorkerScript } from "../Netscript/WorkerScript"; import { WorkerScript } from "../Netscript/WorkerScript";
import { Player } from "../Player"; import { Player } from "../Player";
import { IMap } from "../types"; import { IMap } from "../types";
import { EventEmitter } from "../utils/EventEmitter";
import { Page, routing } from ".././ui/navigationTracking"; import { Page, routing } from ".././ui/navigationTracking";
import { numeralWrapper } from ".././ui/numeralFormat"; import { numeralWrapper } from ".././ui/numeralFormat";
@ -290,11 +291,15 @@ function initStockMarketFnForReact() {
initSymbolToStockMap(); initSymbolToStockMap();
} }
const eventEmitterForUiReset = new EventEmitter();
export function displayStockMarketContent() { export function displayStockMarketContent() {
if (!routing.isOn(Page.StockMarket)) { if (!routing.isOn(Page.StockMarket)) {
return; return;
} }
eventEmitterForUiReset.emitEvent();
if (stockMarketContainer instanceof HTMLElement) { if (stockMarketContainer instanceof HTMLElement) {
const castedStockMarket = StockMarket as IStockMarket; const castedStockMarket = StockMarket as IStockMarket;
ReactDOM.render( ReactDOM.render(
@ -302,6 +307,7 @@ export function displayStockMarketContent() {
buyStockLong={buyStock} buyStockLong={buyStock}
buyStockShort={shortStock} buyStockShort={shortStock}
cancelOrder={cancelOrder} cancelOrder={cancelOrder}
eventEmitterForReset={eventEmitterForUiReset}
initStockMarket={initStockMarketFnForReact} initStockMarket={initStockMarketFnForReact}
p={Player} p={Player}
placeOrder={placeOrder} placeOrder={placeOrder}

@ -12,6 +12,7 @@ import { OrderTypes } from "../data/OrderTypes";
import { PositionTypes } from "../data/PositionTypes"; import { PositionTypes } from "../data/PositionTypes";
import { IPlayer } from "../../PersonObjects/IPlayer"; import { IPlayer } from "../../PersonObjects/IPlayer";
import { EventEmitter } from "../../utils/EventEmitter";
type txFn = (stock: Stock, shares: number) => boolean; type txFn = (stock: Stock, shares: number) => boolean;
export type placeOrderFn = (stock: Stock, shares: number, price: number, ordType: OrderTypes, posType: PositionTypes) => boolean; export type placeOrderFn = (stock: Stock, shares: number, price: number, ordType: OrderTypes, posType: PositionTypes) => boolean;
@ -20,6 +21,7 @@ type IProps = {
buyStockLong: txFn; buyStockLong: txFn;
buyStockShort: txFn; buyStockShort: txFn;
cancelOrder: (params: object) => void; cancelOrder: (params: object) => void;
eventEmitterForReset?: EventEmitter;
initStockMarket: () => void; initStockMarket: () => void;
p: IPlayer; p: IPlayer;
placeOrder: placeOrderFn; placeOrder: placeOrderFn;
@ -65,6 +67,7 @@ export class StockMarketRoot extends React.Component<IProps, IState> {
buyStockLong={this.props.buyStockLong} buyStockLong={this.props.buyStockLong}
buyStockShort={this.props.buyStockShort} buyStockShort={this.props.buyStockShort}
cancelOrder={this.props.cancelOrder} cancelOrder={this.props.cancelOrder}
eventEmitterForReset={this.props.eventEmitterForReset}
p={this.props.p} p={this.props.p}
placeOrder={this.props.placeOrder} placeOrder={this.props.placeOrder}
sellStockLong={this.props.sellStockLong} sellStockLong={this.props.sellStockLong}

@ -9,6 +9,7 @@ import { Stock } from "../Stock";
import { TickerHeaderFormatData } from "../data/TickerHeaderFormatData"; import { TickerHeaderFormatData } from "../data/TickerHeaderFormatData";
import { IPlayer } from "../../PersonObjects/IPlayer"; import { IPlayer } from "../../PersonObjects/IPlayer";
import { Settings } from "../../Settings/Settings";
import { numeralWrapper } from "../../ui/numeralFormat"; import { numeralWrapper } from "../../ui/numeralFormat";
type IProps = { type IProps = {
@ -16,12 +17,22 @@ type IProps = {
stock: Stock; stock: Stock;
} }
const localesWithLongPriceFormat = [
"cs",
"lv",
"pl",
"ru",
];
export function StockTickerHeaderText(props: IProps): React.ReactElement { export function StockTickerHeaderText(props: IProps): React.ReactElement {
const stock = props.stock; const stock = props.stock;
const stockPriceFormat = numeralWrapper.formatMoney(stock.price); const stockPriceFormat = numeralWrapper.formatMoney(stock.price);
const spacesAllottedForStockPrice = localesWithLongPriceFormat.includes(Settings.Locale) ? 15 : 12;
const spacesAfterStockName = " ".repeat(1 + TickerHeaderFormatData.longestName - stock.name.length + (TickerHeaderFormatData.longestSymbol - stock.symbol.length));
const spacesBeforePrice = " ".repeat(spacesAllottedForStockPrice - stockPriceFormat.length);
let hdrText = `${stock.name}${" ".repeat(1 + TickerHeaderFormatData.longestName - stock.name.length + (TickerHeaderFormatData.longestSymbol - stock.symbol.length))}${stock.symbol} -${" ".repeat(10 - stockPriceFormat.length)}${stockPriceFormat}`; let hdrText = `${stock.name}${spacesAfterStockName}${stock.symbol} -${spacesBeforePrice}${stockPriceFormat}`;
if (props.p.has4SData) { if (props.p.has4SData) {
hdrText += ` - Volatility: ${numeralWrapper.format(stock.mv, '0,0.00')}% - Price Forecast: `; hdrText += ` - Volatility: ${numeralWrapper.format(stock.mv, '0,0.00')}% - Price Forecast: `;
let plusOrMinus = stock.b; // True for "+", false for "-" let plusOrMinus = stock.b; // True for "+", false for "-"

@ -14,6 +14,9 @@ import { OrderTypes } from "../data/OrderTypes";
import { PositionTypes } from "../data/PositionTypes"; import { PositionTypes } from "../data/PositionTypes";
import { IPlayer } from "../../PersonObjects/IPlayer"; import { IPlayer } from "../../PersonObjects/IPlayer";
import { EventEmitter } from "../../utils/EventEmitter";
import { ErrorBoundary } from "../../ui/React/ErrorBoundary";
export type txFn = (stock: Stock, shares: number) => boolean; export type txFn = (stock: Stock, shares: number) => boolean;
export type placeOrderFn = (stock: Stock, shares: number, price: number, ordType: OrderTypes, posType: PositionTypes) => boolean; export type placeOrderFn = (stock: Stock, shares: number, price: number, ordType: OrderTypes, posType: PositionTypes) => boolean;
@ -22,6 +25,7 @@ type IProps = {
buyStockLong: txFn; buyStockLong: txFn;
buyStockShort: txFn; buyStockShort: txFn;
cancelOrder: (params: object) => void; cancelOrder: (params: object) => void;
eventEmitterForReset?: EventEmitter;
p: IPlayer; p: IPlayer;
placeOrder: placeOrderFn; placeOrder: placeOrderFn;
sellStockLong: txFn; sellStockLong: txFn;
@ -169,8 +173,13 @@ export class StockTickers extends React.Component<IProps, IState> {
} }
} }
const errorBoundaryProps = {
eventEmitterForReset: this.props.eventEmitterForReset,
id: "StockTickersErrorBoundary",
}
return ( return (
<div> <ErrorBoundary {...errorBoundaryProps}>
<StockTickersConfig <StockTickersConfig
changeDisplayMode={this.changeDisplayMode} changeDisplayMode={this.changeDisplayMode}
changeWatchlistFilter={this.changeWatchlistFilter} changeWatchlistFilter={this.changeWatchlistFilter}
@ -182,7 +191,7 @@ export class StockTickers extends React.Component<IProps, IState> {
<ul id="stock-market-list" ref={this.listRef}> <ul id="stock-market-list" ref={this.listRef}>
{tickers} {tickers}
</ul> </ul>
</div> </ErrorBoundary>
) )
} }
} }

@ -0,0 +1,103 @@
/**
* 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> {
constructor(props: IProps) {
super(props);
this.state = {
errorInfo: "",
hasError: false,
}
}
static getDerivedStateFromError(error: Error) {
return {
errorInfo: error.message,
hasError: true,
};
}
componentDidCatch(error: Error, info: IErrorInfo) {
console.error(`Caught error in React ErrorBoundary. Component stack:`);
console.error(info.componentStack);
}
componentDidMount() {
const cb = () => {
this.setState({
hasError: false,
});
}
if (this.hasEventEmitter()) {
this.props.eventEmitterForReset!.addSubscriber({
cb: cb,
id: this.props.id!,
});
}
}
componentWillUnmount() {
if (this.hasEventEmitter()) {
this.props.eventEmitterForReset!.removeSubscriber(this.props.id!);
}
}
hasEventEmitter(): boolean {
return this.props.eventEmitterForReset instanceof EventEmitter
&& typeof this.props.id === "string";
}
render() {
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;
}
}