Merge pull request #1226 from threehams/corp-overview-perf

Stop remounting components on every render on corp overview
This commit is contained in:
hydroflame 2021-09-13 00:22:01 -04:00 committed by GitHub
commit 38cf4bd3cd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 327 additions and 309 deletions

@ -63,7 +63,6 @@ module.exports = {
"id-blacklist": ["error"],
"id-length": ["off"],
"id-match": ["error"],
"implicit-arrow-linebreak": ["error", "beside"],
indent: ["off"],
"indent-legacy": ["off"],
"init-declarations": ["off"],

@ -133,6 +133,7 @@
}
.cmpy-mgmt-upgrade-div {
text-align: left;
display: inline-block;
border: 1px solid #fff;
margin: 2px;

@ -40,9 +40,9 @@ export function LevelableUpgrade(props: IProps): React.ReactElement {
}
return (
<div className={"cmpy-mgmt-upgrade-div tooltip"} style={{ width: "45%" }} onClick={onClick}>
<button className={"cmpy-mgmt-upgrade-div tooltip"} style={{ width: "45%" }} onClick={onClick}>
{text}
<span className={"tooltiptext"}>{tooltip}</span>
</div>
</button>
);
}

@ -27,330 +27,52 @@ interface IProps {
player: IPlayer;
rerender: () => void;
}
export function Overview(props: IProps): React.ReactElement {
// Generic Function for Creating a button
interface ICreateButtonProps {
text: string;
class?: string;
className?: string;
display?: string;
tooltip?: string;
onClick?: (event: React.MouseEvent) => void;
}
function Button(props: ICreateButtonProps): React.ReactElement {
let className = props.className ? props.className : "std-button";
const hasTooltip = props.tooltip != null;
if (hasTooltip) className += " tooltip";
return (
<a className={className} onClick={props.onClick} style={{ display: props.display ? props.display : "block" }}>
{props.text}
{hasTooltip && <span className={"tooltiptext"}>{props.tooltip}</span>}
</a>
);
}
function openBribeFactionPopup(): void {
const popupId = "corp-bribe-popup";
createPopup(popupId, BribeFactionPopup, {
player: props.player,
popupId: popupId,
corp: props.corp,
});
}
const profit: number = props.corp.revenue.minus(props.corp.expenses).toNumber();
function DividendsStats(): React.ReactElement {
if (props.corp.dividendPercentage <= 0 || profit <= 0) return <></>;
const totalDividends = (props.corp.dividendPercentage / 100) * profit;
const retainedEarnings = profit - totalDividends;
const dividendsPerShare = totalDividends / props.corp.totalShares;
const playerEarnings = props.corp.numShares * dividendsPerShare;
return (
<>
Retained Profits (after dividends): <Money money={retainedEarnings} /> / s<br />
<br />
Dividend Percentage: {numeralWrapper.format(props.corp.dividendPercentage / 100, "0%")}
<br />
Dividends per share: <Money money={dividendsPerShare} /> / s<br />
Your earnings as a shareholder (Pre-Tax): <Money money={playerEarnings} /> / s<br />
Dividend Tax Rate: {props.corp.dividendTaxPercentage}%<br />
Your earnings as a shareholder (Post-Tax):{" "}
<Money money={playerEarnings * (1 - props.corp.dividendTaxPercentage / 100)} /> / s<br />
<br />
</>
);
}
function Mult(props: { name: string; mult: number }): React.ReactElement {
if (props.mult <= 1) return <></>;
return (
<p>
{props.name}
{numeralWrapper.format(props.mult, "0.000")}
<br />
</p>
);
}
// Returns a string with general information about Corporation
function BonusTime(): React.ReactElement {
const storedTime = props.corp.storedCycles * CONSTANTS.MilliPerCycle;
if (storedTime <= 15000) return <></>;
return (
<p>
Bonus time: {convertTimeMsToTimeElapsedString(storedTime)}
<br />
<br />
</p>
);
}
function BribeButton(): React.ReactElement {
const canBribe = props.corp.determineValuation() >= CorporationConstants.BribeThreshold || true;
const bribeFactionsClass = canBribe ? "a-link-button" : "a-link-button-inactive";
return (
<Button
className={bribeFactionsClass}
display="inline-block"
onClick={openBribeFactionPopup}
text="Bribe Factions"
tooltip={
canBribe
? "Use your Corporations power and influence to bribe Faction leaders in exchange for reputation"
: "Your Corporation is not powerful enough to bribe Faction leaders"
}
/>
);
}
function openFindInvestorsPopup(): void {
const popupId = "cmpy-mgmt-find-investors-popup";
createPopup(popupId, FindInvestorsPopup, {
rerender: props.rerender,
player: props.player,
popupId: popupId,
corp: props.corp,
});
}
function openGoPublicPopup(): void {
const popupId = "cmpy-mgmt-go-public-popup";
createPopup(popupId, GoPublicPopup, {
rerender: props.rerender,
player: props.player,
popupId: popupId,
corp: props.corp,
});
}
// Render the buttons for when your Corporation is still private
function PrivateButtons(): React.ReactElement {
const fundingAvailable = props.corp.fundingRound < 4;
const findInvestorsClassName = fundingAvailable ? "std-button" : "a-link-button-inactive";
const findInvestorsTooltip = fundingAvailable
? "Search for private investors who will give you startup funding in exchangefor equity (stock shares) in your company"
: undefined;
return (
<>
<Button
className={findInvestorsClassName}
onClick={openFindInvestorsPopup}
text="Find Investors"
tooltip={findInvestorsTooltip}
display="inline-block"
/>
<Button
className="std-button"
onClick={openGoPublicPopup}
display="inline-block"
text="Go Public"
tooltip={
"Become a publicly traded and owned entity. Going public " +
"involves issuing shares for an IPO. Once you are a public " +
"company, your shares will be traded on the stock market."
}
/>
<br />
</>
);
}
function openSellSharesPopup(): void {
const popupId = "cmpy-mgmt-sell-shares-popup";
createPopup(popupId, SellSharesPopup, {
corp: props.corp,
player: props.player,
popupId: popupId,
rerender: props.rerender,
});
}
function openBuybackSharesPopup(): void {
const popupId = "corp-buyback-shares-popup";
createPopup(popupId, BuybackSharesPopup, {
rerender: props.rerender,
player: props.player,
popupId: popupId,
corp: props.corp,
});
}
function openIssueNewSharesPopup(): void {
const popupId = "cmpy-mgmt-issue-new-shares-popup";
createPopup(popupId, IssueNewSharesPopup, {
popupId: popupId,
corp: props.corp,
});
}
function openIssueDividendsPopup(): void {
const popupId = "cmpy-mgmt-issue-dividends-popup";
createPopup(popupId, IssueDividendsPopup, {
popupId: popupId,
corp: props.corp,
});
}
// Render the buttons for when your Corporation has gone public
function PublicButtons(): React.ReactElement {
const corp = props.corp;
const sellSharesOnCd = corp.shareSaleCooldown > 0;
const sellSharesClass = sellSharesOnCd ? "a-link-button-inactive" : "std-button";
const sellSharesTooltip = sellSharesOnCd
? "Cannot sell shares for " + corp.convertCooldownToString(corp.shareSaleCooldown)
: "Sell your shares in the company. The money earned from selling your " +
"shares goes into your personal account, not the Corporation's. " +
"This is one of the only ways to profit from your business venture.";
const issueNewSharesOnCd = corp.issueNewSharesCooldown > 0;
const issueNewSharesClass = issueNewSharesOnCd ? "a-link-button-inactive" : "std-button";
const issueNewSharesTooltip = issueNewSharesOnCd
? "Cannot issue new shares for " + corp.convertCooldownToString(corp.issueNewSharesCooldown)
: "Issue new equity shares to raise capital.";
return (
<>
<Button
className={sellSharesClass}
display="inline-block"
onClick={openSellSharesPopup}
text="Sell Shares"
tooltip={sellSharesTooltip}
/>
<Button
className="std-button"
display="inline-block"
onClick={openBuybackSharesPopup}
text="Buyback shares"
tooltip="Buy back shares you that previously issued or sold at market price."
/>
<br />
<Button
className={issueNewSharesClass}
display="inline-block"
onClick={openIssueNewSharesPopup}
text="Issue New Shares"
tooltip={issueNewSharesTooltip}
/>
<Button
className="std-button"
display="inline-block"
onClick={openIssueDividendsPopup}
text="Issue Dividends"
tooltip="Manage the dividends that are paid out to shareholders (including yourself)"
/>
<br />
</>
);
}
// Render the UI for Corporation upgrades
function Upgrades(): React.ReactElement {
// Don't show upgrades
if (props.corp.divisions.length <= 0) {
return <h1>Unlock upgrades after creating your first division</h1>;
}
return (
<div className={"cmpy-mgmt-upgrade-container"}>
<h1 className={"cmpy-mgmt-upgrade-header"}> Unlocks </h1>
{Object.values(CorporationUnlockUpgrades)
.filter((upgrade: CorporationUnlockUpgrade) => props.corp.unlockUpgrades[upgrade[0]] === 0)
.map((upgrade: CorporationUnlockUpgrade) => (
<UnlockUpgrade
rerender={props.rerender}
player={props.player}
corp={props.corp}
upgradeData={upgrade}
key={upgrade[0]}
/>
))}
<h1 className={"cmpy-mgmt-upgrade-header"}> Upgrades </h1>
{props.corp.upgrades
.map((level: number, i: number) => CorporationUpgrades[i])
.map((upgrade: CorporationUpgrade) => (
<LevelableUpgrade
rerender={props.rerender}
player={props.player}
corp={props.corp}
upgrade={upgrade}
key={upgrade[0]}
/>
))}
</div>
);
}
export function Overview({ corp, player, rerender }: IProps): React.ReactElement {
const profit: number = corp.revenue.minus(corp.expenses).toNumber();
return (
<div>
<p>
Total Funds: <Money money={props.corp.funds.toNumber()} />
Total Funds: <Money money={corp.funds.toNumber()} />
<br />
Total Revenue: <Money money={props.corp.revenue.toNumber()} /> / s<br />
Total Expenses: <Money money={props.corp.expenses.toNumber()} /> / s
Total Revenue: <Money money={corp.revenue.toNumber()} /> / s<br />
Total Expenses: <Money money={corp.expenses.toNumber()} /> / s
<br />
Total Profits: <Money money={profit} /> / s<br />
<DividendsStats />
Publicly Traded: {props.corp.public ? "Yes" : "No"}
<DividendsStats corp={corp} profit={profit} />
Publicly Traded: {corp.public ? "Yes" : "No"}
<br />
Owned Stock Shares: {numeralWrapper.format(props.corp.numShares, "0.000a")}
Owned Stock Shares: {numeralWrapper.format(corp.numShares, "0.000a")}
<br />
Stock Price: {props.corp.public ? <Money money={props.corp.sharePrice} /> : "N/A"}
Stock Price: {corp.public ? <Money money={corp.sharePrice} /> : "N/A"}
<br />
</p>
<p className="tooltip">
Total Stock Shares: {numeralWrapper.format(props.corp.totalShares, "0.000a")}
Total Stock Shares: {numeralWrapper.format(corp.totalShares, "0.000a")}
<span className="tooltiptext">
Outstanding Shares: {numeralWrapper.format(props.corp.issuedShares, "0.000a")}
Outstanding Shares: {numeralWrapper.format(corp.issuedShares, "0.000a")}
<br />
Private Shares:{" "}
{numeralWrapper.format(props.corp.totalShares - props.corp.issuedShares - props.corp.numShares, "0.000a")}
Private Shares: {numeralWrapper.format(corp.totalShares - corp.issuedShares - corp.numShares, "0.000a")}
</span>
</p>
<br />
<br />
<Mult name="Production Multiplier: " mult={props.corp.getProductionMultiplier()} />
<Mult name="Storage Multiplier: " mult={props.corp.getStorageMultiplier()} />
<Mult name="Advertising Multiplier: " mult={props.corp.getAdvertisingMultiplier()} />
<Mult name="Empl. Creativity Multiplier: " mult={props.corp.getEmployeeCreMultiplier()} />
<Mult name="Empl. Charisma Multiplier: " mult={props.corp.getEmployeeChaMultiplier()} />
<Mult name="Empl. Intelligence Multiplier: " mult={props.corp.getEmployeeIntMultiplier()} />
<Mult name="Empl. Efficiency Multiplier: " mult={props.corp.getEmployeeEffMultiplier()} />
<Mult name="Sales Multiplier: " mult={props.corp.getSalesMultiplier()} />
<Mult name="Scientific Research Multiplier: " mult={props.corp.getScientificResearchMultiplier()} />
<Mult name="Production Multiplier: " mult={corp.getProductionMultiplier()} />
<Mult name="Storage Multiplier: " mult={corp.getStorageMultiplier()} />
<Mult name="Advertising Multiplier: " mult={corp.getAdvertisingMultiplier()} />
<Mult name="Empl. Creativity Multiplier: " mult={corp.getEmployeeCreMultiplier()} />
<Mult name="Empl. Charisma Multiplier: " mult={corp.getEmployeeChaMultiplier()} />
<Mult name="Empl. Intelligence Multiplier: " mult={corp.getEmployeeIntMultiplier()} />
<Mult name="Empl. Efficiency Multiplier: " mult={corp.getEmployeeEffMultiplier()} />
<Mult name="Sales Multiplier: " mult={corp.getSalesMultiplier()} />
<Mult name="Scientific Research Multiplier: " mult={corp.getScientificResearchMultiplier()} />
<br />
<BonusTime />
<BonusTime corp={corp} />
<div>
<Button
className="a-link-button"
display="inline-block"
onClick={() => props.corp.getStarterGuide(props.player)}
onClick={() => corp.getStarterGuide(player)}
text="Getting Started Guide"
tooltip={
"Get a copy of and read 'The Complete Handbook for Creating a Successful Corporation.' " +
@ -358,11 +80,307 @@ export function Overview(props: IProps): React.ReactElement {
"provides some tips/pointers for helping you get started with managing it."
}
/>
{props.corp.public ? <PublicButtons /> : <PrivateButtons />}
<BribeButton />
{corp.public ? (
<PublicButtons corp={corp} player={player} rerender={rerender} />
) : (
<PrivateButtons corp={corp} player={player} rerender={rerender} />
)}
<BribeButton corp={corp} player={player} />
</div>
<br />
<Upgrades />
<Upgrades corp={corp} player={player} rerender={rerender} />
</div>
);
}
interface IPrivateButtonsProps {
corp: ICorporation;
player: IPlayer;
rerender: () => void;
}
// Render the buttons for when your Corporation is still private
function PrivateButtons({ corp, player, rerender }: IPrivateButtonsProps): React.ReactElement {
function openFindInvestorsPopup(): void {
const popupId = "cmpy-mgmt-find-investors-popup";
createPopup(popupId, FindInvestorsPopup, {
rerender,
player,
popupId,
corp: corp,
});
}
function openGoPublicPopup(): void {
const popupId = "cmpy-mgmt-go-public-popup";
createPopup(popupId, GoPublicPopup, {
rerender,
player,
popupId,
corp: corp,
});
}
const fundingAvailable = corp.fundingRound < 4;
const findInvestorsClassName = fundingAvailable ? "std-button" : "a-link-button-inactive";
const findInvestorsTooltip = fundingAvailable
? "Search for private investors who will give you startup funding in exchangefor equity (stock shares) in your company"
: undefined;
return (
<>
<Button
className={findInvestorsClassName}
onClick={openFindInvestorsPopup}
text="Find Investors"
tooltip={findInvestorsTooltip}
display="inline-block"
/>
<Button
className="std-button"
onClick={openGoPublicPopup}
display="inline-block"
text="Go Public"
tooltip={
"Become a publicly traded and owned entity. Going public " +
"involves issuing shares for an IPO. Once you are a public " +
"company, your shares will be traded on the stock market."
}
/>
<br />
</>
);
}
interface IUpgradeProps {
corp: ICorporation;
player: IPlayer;
rerender: () => void;
}
// Render the UI for Corporation upgrades
function Upgrades({ corp, player, rerender }: IUpgradeProps): React.ReactElement {
// Don't show upgrades
if (corp.divisions.length <= 0) {
return <h1>Unlock upgrades after creating your first division</h1>;
}
return (
<div className={"cmpy-mgmt-upgrade-container"}>
<h1 className={"cmpy-mgmt-upgrade-header"}> Unlocks </h1>
{Object.values(CorporationUnlockUpgrades)
.filter((upgrade: CorporationUnlockUpgrade) => corp.unlockUpgrades[upgrade[0]] === 0)
.map((upgrade: CorporationUnlockUpgrade) => (
<UnlockUpgrade rerender={rerender} player={player} corp={corp} upgradeData={upgrade} key={upgrade[0]} />
))}
<h1 className={"cmpy-mgmt-upgrade-header"}> Upgrades </h1>
{corp.upgrades
.map((level: number, i: number) => CorporationUpgrades[i])
.map((upgrade: CorporationUpgrade) => (
<LevelableUpgrade rerender={rerender} player={player} corp={corp} upgrade={upgrade} key={upgrade[0]} />
))}
</div>
);
}
interface IPublicButtonsProps {
corp: ICorporation;
player: IPlayer;
rerender: () => void;
}
// Render the buttons for when your Corporation has gone public
function PublicButtons({ corp, player, rerender }: IPublicButtonsProps): React.ReactElement {
const sellSharesOnCd = corp.shareSaleCooldown > 0;
const sellSharesClass = sellSharesOnCd ? "a-link-button-inactive" : "std-button";
const sellSharesTooltip = sellSharesOnCd
? "Cannot sell shares for " + corp.convertCooldownToString(corp.shareSaleCooldown)
: "Sell your shares in the company. The money earned from selling your " +
"shares goes into your personal account, not the Corporation's. " +
"This is one of the only ways to profit from your business venture.";
const issueNewSharesOnCd = corp.issueNewSharesCooldown > 0;
const issueNewSharesClass = issueNewSharesOnCd ? "a-link-button-inactive" : "std-button";
const issueNewSharesTooltip = issueNewSharesOnCd
? "Cannot issue new shares for " + corp.convertCooldownToString(corp.issueNewSharesCooldown)
: "Issue new equity shares to raise capital.";
function openSellSharesPopup(): void {
const popupId = "cmpy-mgmt-sell-shares-popup";
createPopup(popupId, SellSharesPopup, {
corp: corp,
player,
popupId,
rerender,
});
}
function openBuybackSharesPopup(): void {
const popupId = "corp-buyback-shares-popup";
createPopup(popupId, BuybackSharesPopup, {
rerender,
player,
popupId,
corp: corp,
});
}
function openIssueNewSharesPopup(): void {
const popupId = "cmpy-mgmt-issue-new-shares-popup";
createPopup(popupId, IssueNewSharesPopup, {
popupId,
corp: corp,
});
}
function openIssueDividendsPopup(): void {
const popupId = "cmpy-mgmt-issue-dividends-popup";
createPopup(popupId, IssueDividendsPopup, {
popupId,
corp: corp,
});
}
return (
<>
<Button
className={sellSharesClass}
display="inline-block"
onClick={openSellSharesPopup}
text="Sell Shares"
tooltip={sellSharesTooltip}
/>
<Button
className="std-button"
display="inline-block"
onClick={openBuybackSharesPopup}
text="Buyback shares"
tooltip="Buy back shares you that previously issued or sold at market price."
/>
<br />
<Button
className={issueNewSharesClass}
display="inline-block"
onClick={openIssueNewSharesPopup}
text="Issue New Shares"
tooltip={issueNewSharesTooltip}
/>
<Button
className="std-button"
display="inline-block"
onClick={openIssueDividendsPopup}
text="Issue Dividends"
tooltip="Manage the dividends that are paid out to shareholders (including yourself)"
/>
<br />
</>
);
}
// Generic Function for Creating a button
interface ICreateButtonProps {
text: string;
className?: string;
display?: string;
tooltip?: string;
onClick?: (event: React.MouseEvent) => void;
}
function Button({ className = "std-button", text, display, tooltip, onClick }: ICreateButtonProps): React.ReactElement {
const hasTooltip = tooltip != null;
if (hasTooltip) className += " tooltip";
return (
<button className={className} onClick={onClick} style={{ display: display ? display : "block" }}>
{text}
{hasTooltip && <span className={"tooltiptext"}>{tooltip}</span>}
</button>
);
}
interface IBribeButtonProps {
player: IPlayer;
corp: ICorporation;
}
function BribeButton({ player, corp }: IBribeButtonProps): React.ReactElement {
function openBribeFactionPopup(): void {
const popupId = "corp-bribe-popup";
createPopup(popupId, BribeFactionPopup, {
player,
popupId,
corp: corp,
});
}
const canBribe = corp.determineValuation() >= CorporationConstants.BribeThreshold || true;
const bribeFactionsClass = canBribe ? "a-link-button" : "a-link-button-inactive";
return (
<Button
className={bribeFactionsClass}
display="inline-block"
onClick={openBribeFactionPopup}
text="Bribe Factions"
tooltip={
canBribe
? "Use your Corporations power and influence to bribe Faction leaders in exchange for reputation"
: "Your Corporation is not powerful enough to bribe Faction leaders"
}
/>
);
}
interface IDividendsStatsProps {
corp: ICorporation;
profit: number;
}
function DividendsStats({ corp, profit }: IDividendsStatsProps): React.ReactElement {
if (corp.dividendPercentage <= 0 || profit <= 0) return <></>;
const totalDividends = (corp.dividendPercentage / 100) * profit;
const retainedEarnings = profit - totalDividends;
const dividendsPerShare = totalDividends / corp.totalShares;
const playerEarnings = corp.numShares * dividendsPerShare;
return (
<>
Retained Profits (after dividends): <Money money={retainedEarnings} /> / s
<br />
<br />
Dividend Percentage: {numeralWrapper.format(corp.dividendPercentage / 100, "0%")}
<br />
Dividends per share: <Money money={dividendsPerShare} /> / s<br />
Your earnings as a shareholder (Pre-Tax): <Money money={playerEarnings} /> / s<br />
Dividend Tax Rate: {corp.dividendTaxPercentage}%<br />
Your earnings as a shareholder (Post-Tax):{" "}
<Money money={playerEarnings * (1 - corp.dividendTaxPercentage / 100)} /> / s<br />
<br />
</>
);
}
interface IMultProps {
name: string;
mult: number;
}
function Mult({ name, mult }: IMultProps): React.ReactElement {
if (mult <= 1) return <></>;
return (
<p>
{name}
{numeralWrapper.format(mult, "0.000")}
<br />
</p>
);
}
interface IBonusTimeProps {
corp: ICorporation;
}
// Returns a string with general information about Corporation
function BonusTime({ corp }: IBonusTimeProps): React.ReactElement {
const storedTime = corp.storedCycles * CONSTANTS.MilliPerCycle;
if (storedTime <= 15000) return <></>;
return (
<p>
Bonus time: {convertTimeMsToTimeElapsedString(storedTime)}
<br />
<br />
</p>
);
}

@ -34,9 +34,9 @@ export function UnlockUpgrade(props: IProps): React.ReactElement {
}
return (
<div className={"cmpy-mgmt-upgrade-div tooltip"} style={{ width: "45%" }} onClick={onClick}>
<button className={"cmpy-mgmt-upgrade-div tooltip"} style={{ width: "45%" }} onClick={onClick}>
{text}
<span className={"tooltiptext"}>{tooltip}</span>
</div>
</button>
);
}