mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-11-26 17:43:48 +01:00
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:
parent
658df9fb01
commit
dc928828e2
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
103
src/ui/React/ErrorBoundary.tsx
Normal file
103
src/ui/React/ErrorBoundary.tsx
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user