UI: Break SidebarRoot into smaller components, and memoize (#246)

This commit is contained in:
David Walker 2022-12-04 18:05:55 -08:00 committed by GitHub
parent 8d793ea271
commit a46d34bd60
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 280 additions and 544 deletions

@ -0,0 +1,90 @@
import React, { useMemo, useState } from "react";
import Collapse from "@mui/material/Collapse";
import ListItem from "@mui/material/ListItem";
import ListItemIcon from "@mui/material/ListItemIcon";
import ListItemText from "@mui/material/ListItemText";
import Tooltip from "@mui/material/Tooltip";
import Typography from "@mui/material/Typography";
import ExpandLessIcon from "@mui/icons-material/ExpandLess";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import { SidebarItem, ICreateProps as IItemProps } from "./SidebarItem";
import type { Page } from "../../ui/Router";
interface IProps {
key_: string;
page: Page;
clickPage: (page: Page) => void;
flash: Page | null;
items: (IItemProps | boolean)[];
icon: React.ReactElement;
sidebarOpen: boolean;
classes: any;
}
// We can't useCallback for this, because in the items map it would be
// called a changing number of times, and hooks can't be called in loops. So
// we set up this explicit cache of function objects instead.
// This is at module scope, because it's fine for all Accordions to share the
// same cache.
// WeakMap prevents memory leaks. We won't drop slices of the cache too soon,
// because the fn keys are themselves memoized elsewhere, which keeps them
// alive and thus keeps the WeakMap entries alive.
const clickFnCache = new WeakMap();
function getClickFn(toWrap: (page: Page) => void, page: Page) {
let first = clickFnCache.get(toWrap);
if (first === undefined) {
first = {};
clickFnCache.set(toWrap, first);
}
// Short-circuit: Avoid assign/eval of function on found
return (first[page] ??= () => toWrap(page));
}
// This can't be usefully memoized, because props.items is a new array every time.
export function SidebarAccordion(props: IProps): React.ReactElement {
const [open, setOpen] = useState(true);
// Obnoxious, because we can't modify props at all.
const li_classes = useMemo(() => ({ root: props.classes.listitem }), [props.classes.listitem]);
const icon = Object.assign({}, props.icon);
icon.props = Object.assign({ color: "primary" }, icon.props);
// Explicitily useMemo() to save rerendering deep chunks of this tree.
// memo() can't be (easily) used on components like <List>, because the
// props.children array will be a different object every time.
return (
<>
{useMemo(
() => (
<ListItem classes={li_classes} button onClick={() => setOpen((open) => !open)}>
<ListItemIcon>
<Tooltip title={!props.sidebarOpen ? props.key_ : ""} children={icon} />
</ListItemIcon>
<ListItemText primary={<Typography>{props.key_}</Typography>} />
{open ? <ExpandLessIcon color="primary" /> : <ExpandMoreIcon color="primary" />}
</ListItem>
),
[li_classes, props.sidebarOpen, props.key_, open, props.icon.type],
)}
<Collapse in={open} timeout="auto" unmountOnExit>
{props.items.map((x) => {
if (typeof x !== "object") return null;
const { key_, icon, count, active } = x;
return (
<SidebarItem
key={key_}
key_={key_}
icon={icon}
count={count}
active={active ?? props.page === key_}
clickFn={getClickFn(props.clickPage, key_)}
flash={props.flash === key_}
classes={props.classes}
sidebarOpen={props.sidebarOpen}
/>
);
})}
</Collapse>
</>
);
}

@ -0,0 +1,53 @@
import React, { memo } from "react";
import Badge from "@mui/material/Badge";
import ListItem from "@mui/material/ListItem";
import ListItemIcon from "@mui/material/ListItemIcon";
import ListItemText from "@mui/material/ListItemText";
import Tooltip from "@mui/material/Tooltip";
import Typography from "@mui/material/Typography";
import type { Page } from "../../ui/Router";
export interface ICreateProps {
key_: Page;
icon: React.ReactElement;
count?: number;
active?: boolean;
}
export interface IProps extends ICreateProps {
clickFn: () => void;
flash: boolean;
classes: any;
sidebarOpen: boolean;
}
export const SidebarItem = memo(function (props: IProps): React.ReactElement {
// Use icon as a template. (We can't modify props)
const icon: React.ReactElement = {
type: props.icon.type,
key: props.icon.key,
props: {
color: props.flash ? "error" : !props.active ? "secondary" : "primary",
...props.icon.props,
},
};
return (
<ListItem
classes={{ root: props.classes.listitem }}
button
key={props.key_}
className={props.active ? props.classes.active : ""}
onClick={props.clickFn}
>
<ListItemIcon>
<Badge badgeContent={(props.count ?? 0) > 0 ? props.count : undefined} color="error">
<Tooltip title={!props.sidebarOpen ? props.key_ : ""} children={icon} />
</Badge>
</ListItemIcon>
<ListItemText>
<Typography color={props.flash ? "error" : !props.active ? "secondary" : "primary"} children={props.key_} />
</ListItemText>
</ListItem>
);
});

@ -1,6 +1,5 @@
import React, { useCallback, useState, useEffect } from "react"; import React, { useMemo, useCallback, useState, useEffect } from "react";
import { KEYCODE } from "../../utils/helpers/keyCodes"; import { KEYCODE } from "../../utils/helpers/keyCodes";
import clsx from "clsx";
import { styled, Theme, CSSObject } from "@mui/material/styles"; import { styled, Theme, CSSObject } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles"; import createStyles from "@mui/styles/createStyles";
import makeStyles from "@mui/styles/makeStyles"; import makeStyles from "@mui/styles/makeStyles";
@ -14,8 +13,6 @@ import ListItem from "@mui/material/ListItem";
import ListItemIcon from "@mui/material/ListItemIcon"; import ListItemIcon from "@mui/material/ListItemIcon";
import ListItemText from "@mui/material/ListItemText"; import ListItemText from "@mui/material/ListItemText";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import Collapse from "@mui/material/Collapse";
import Badge from "@mui/material/Badge";
import ComputerIcon from "@mui/icons-material/Computer"; import ComputerIcon from "@mui/icons-material/Computer";
import LastPageIcon from "@mui/icons-material/LastPage"; // Terminal import LastPageIcon from "@mui/icons-material/LastPage"; // Terminal
@ -42,11 +39,10 @@ import EmojiEventsIcon from "@mui/icons-material/EmojiEvents"; // Achievements
import AccountBoxIcon from "@mui/icons-material/AccountBox"; import AccountBoxIcon from "@mui/icons-material/AccountBox";
import PublicIcon from "@mui/icons-material/Public"; import PublicIcon from "@mui/icons-material/Public";
import LiveHelpIcon from "@mui/icons-material/LiveHelp"; import LiveHelpIcon from "@mui/icons-material/LiveHelp";
import ExpandLessIcon from "@mui/icons-material/ExpandLess";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import { Router } from "../../ui/GameRoot"; import { Router } from "../../ui/GameRoot";
import { Page, SimplePage } from "../../ui/Router"; import { Page, SimplePage } from "../../ui/Router";
import { SidebarAccordion } from "./SidebarAccordion";
import { Player } from "@player"; import { Player } from "@player";
import { CONSTANTS } from "../../Constants"; import { CONSTANTS } from "../../Constants";
import { iTutorialSteps, iTutorialNextStep, ITutorial } from "../../InteractiveTutorial"; import { iTutorialSteps, iTutorialNextStep, ITutorial } from "../../InteractiveTutorial";
@ -59,6 +55,39 @@ import { InvitationsSeen } from "../../Faction/ui/FactionsRoot";
import { hash } from "../../hash/hash"; import { hash } from "../../hash/hash";
import { Locations } from "../../Locations/Locations"; import { Locations } from "../../Locations/Locations";
// All icon instances need to be constant, so they have stable object identity.
// Otherwise, the memoization of all the higher-level components doesn't work.
const computerIcon = <ComputerIcon />;
const lastPageIcon = <LastPageIcon />;
const createIcon = <CreateIcon />;
const storageIcon = <StorageIcon />;
const bugReportIcon = <BugReportIcon />;
const equalizerIcon = <EqualizerIcon />;
const contactsIcon = <ContactsIcon />;
const doubleArrowIcon = <DoubleArrowIcon style={{ transform: "rotate(-90deg)" }} />;
const accountTreeIcon = <AccountTreeIcon />;
const peopleAltIcon = <PeopleAltIcon />;
const locationCityIcon = <LocationCityIcon />;
const airplanemodeActiveIcon = <AirplanemodeActiveIcon />;
const workIcon = <WorkIcon />;
const trendingUpIcon = <TrendingUpIcon />;
const formatBoldIcon = <FormatBoldIcon />;
const businessIcon = <BusinessIcon />;
const sportsMmaIcon = <SportsMmaIcon />;
const checkIcon = <CheckIcon />;
const helpIcon = <HelpIcon />;
const settingsIcon = <SettingsIcon />;
const developerBoardIcon = <DeveloperBoardIcon />;
const emojiEventsIcon = <EmojiEventsIcon />;
const accountBoxIcon = <AccountBoxIcon />;
const publicIcon = <PublicIcon />;
const liveHelpIcon = <LiveHelpIcon />;
const chevronLeftIcon = <ChevronLeftIcon color="primary" />;
const chevronRightIcon = <ChevronRightIcon color="primary" />;
// Use constant Dividers just for performance
const divider = <Divider />;
const openedMixin = (theme: Theme): CSSObject => ({ const openedMixin = (theme: Theme): CSSObject => ({
width: theme.spacing(31), width: theme.spacing(31),
transition: theme.transitions.create("width", { transition: theme.transitions.create("width", {
@ -118,11 +147,6 @@ export function SidebarRoot(props: IProps): React.ReactElement {
return () => clearInterval(id); return () => clearInterval(id);
}, []); }, []);
const [hackingOpen, setHackingOpen] = useState(true);
const [characterOpen, setCharacterOpen] = useState(true);
const [worldOpen, setWorldOpen] = useState(true);
const [helpOpen, setHelpOpen] = useState(true);
let flash: Page | null = null; let flash: Page | null = null;
switch (ITutorial.currStep) { switch (ITutorial.currStep) {
case iTutorialSteps.CharacterGoToTerminalPage: case iTutorialSteps.CharacterGoToTerminalPage:
@ -270,13 +294,17 @@ export function SidebarRoot(props: IProps): React.ReactElement {
Settings.IsSidebarOpened = !old; Settings.IsSidebarOpened = !old;
return !old; return !old;
}); });
const li_classes = useMemo(() => ({ root: classes.listitem }), [classes.listitem]);
// Explicitily useMemo() to save rerendering deep chunks of this tree.
// memo() can't be (easily) used on components like <List>, because the
// props.children array will be a different object every time.
return ( return (
<Drawer open={open} anchor="left" variant="permanent"> <Drawer open={open} anchor="left" variant="permanent">
<ListItem classes={{ root: classes.listitem }} button onClick={toggleDrawer}> {useMemo(
<ListItemIcon> () => (
{!open ? <ChevronRightIcon color="primary" /> : <ChevronLeftIcon color="primary" />} <ListItem classes={li_classes} button onClick={toggleDrawer}>
</ListItemIcon> <ListItemIcon>{!open ? chevronRightIcon : chevronLeftIcon}</ListItemIcon>
<ListItemText <ListItemText
primary={ primary={
<Tooltip title={hash()}> <Tooltip title={hash()}>
@ -285,528 +313,93 @@ export function SidebarRoot(props: IProps): React.ReactElement {
} }
/> />
</ListItem> </ListItem>
<Divider /> ),
[li_classes, open],
)}
{divider}
<List> <List>
<ListItem classes={{ root: classes.listitem }} button onClick={() => setHackingOpen((old) => !old)}> <SidebarAccordion
<ListItemIcon> key_="Hacking"
<Tooltip title={!open ? "Hacking" : ""}> page={props.page}
<ComputerIcon color="primary" /> clickPage={clickPage}
</Tooltip> flash={flash}
</ListItemIcon> icon={computerIcon}
<ListItemText primary={<Typography>Hacking</Typography>} /> sidebarOpen={open}
{hackingOpen ? <ExpandLessIcon color="primary" /> : <ExpandMoreIcon color="primary" />} classes={classes}
</ListItem> items={[
<Collapse in={hackingOpen} timeout="auto" unmountOnExit> { key_: Page.Terminal, icon: lastPageIcon },
<List> { key_: Page.ScriptEditor, icon: createIcon },
<ListItem { key_: Page.ActiveScripts, icon: storageIcon },
classes={{ root: classes.listitem }} { key_: Page.CreateProgram, icon: bugReportIcon, count: programCount },
button canStaneksGift && { key_: Page.StaneksGift, icon: developerBoardIcon },
key={"Terminal"} ]}
className={clsx({
[classes.active]: props.page === Page.Terminal,
})}
onClick={() => clickPage(Page.Terminal)}
>
<ListItemIcon>
<Tooltip title={!open ? "Terminal" : ""}>
<LastPageIcon
color={flash === Page.Terminal ? "error" : props.page !== Page.Terminal ? "secondary" : "primary"}
/> />
</Tooltip> {divider}
</ListItemIcon> <SidebarAccordion
<ListItemText> key_="Character"
<Typography page={props.page}
color={flash === Page.Terminal ? "error" : props.page !== Page.Terminal ? "secondary" : "primary"} clickPage={clickPage}
> flash={flash}
Terminal icon={accountBoxIcon}
</Typography> sidebarOpen={open}
</ListItemText> classes={classes}
</ListItem> items={[
<ListItem { key_: Page.Stats, icon: equalizerIcon },
classes={{ root: classes.listitem }} canOpenFactions && {
button key_: Page.Factions,
key={"Script Editor"} icon: contactsIcon,
className={clsx({ active: [Page.Factions as Page, Page.Faction].includes(props.page),
[classes.active]: props.page === Page.ScriptEditor, count: invitationsCount,
})} },
onClick={() => clickPage(Page.ScriptEditor)} canOpenAugmentations && {
> key_: Page.Augmentations,
<ListItemIcon> icon: doubleArrowIcon,
<Tooltip title={!open ? "Script Editor" : ""}> count: augmentationCount,
<CreateIcon color={props.page !== Page.ScriptEditor ? "secondary" : "primary"} /> },
</Tooltip> { key_: Page.Hacknet, icon: accountTreeIcon },
</ListItemIcon> canOpenSleeves && { key_: Page.Sleeves, icon: peopleAltIcon },
<ListItemText> ]}
<Typography color={props.page !== Page.ScriptEditor ? "secondary" : "primary"}>
Script Editor
</Typography>
</ListItemText>
</ListItem>
<ListItem
classes={{ root: classes.listitem }}
button
key={"Active Scripts"}
className={clsx({
[classes.active]: props.page === Page.ActiveScripts,
})}
onClick={() => clickPage(Page.ActiveScripts)}
>
<ListItemIcon>
<Tooltip title={!open ? "Active Scripts" : ""}>
<StorageIcon
color={
flash === Page.ActiveScripts
? "error"
: props.page !== Page.ActiveScripts
? "secondary"
: "primary"
}
/> />
</Tooltip> {divider}
</ListItemIcon> <SidebarAccordion
<ListItemText> key_="World"
<Typography page={props.page}
color={ clickPage={clickPage}
flash === Page.ActiveScripts ? "error" : props.page !== Page.ActiveScripts ? "secondary" : "primary" flash={flash}
} icon={publicIcon}
> sidebarOpen={open}
Active Scripts classes={classes}
</Typography> items={[
</ListItemText> {
</ListItem> key_: Page.City,
<ListItem icon: locationCityIcon,
button active: [Page.City as Page, Page.Grafting, Page.Location].includes(props.page),
key={"Create Program"} },
className={clsx({ { key_: Page.Travel, icon: airplanemodeActiveIcon },
[classes.active]: props.page === Page.CreateProgram, canJob && { key_: Page.Job, icon: workIcon },
})} canStockMarket && { key_: Page.StockMarket, icon: trendingUpIcon },
onClick={() => clickPage(Page.CreateProgram)} canBladeburner && { key_: Page.Bladeburner, icon: formatBoldIcon },
> canCorporation && { key_: Page.Corporation, icon: businessIcon },
<ListItemIcon> canGang && { key_: Page.Gang, icon: sportsMmaIcon },
<Badge badgeContent={programCount > 0 ? programCount : undefined} color="error"> ]}
<Tooltip title={!open ? "Create Program" : ""}>
<BugReportIcon color={props.page !== Page.CreateProgram ? "secondary" : "primary"} />
</Tooltip>
</Badge>
</ListItemIcon>
<ListItemText>
<Typography color={props.page !== Page.CreateProgram ? "secondary" : "primary"}>
Create Program
</Typography>
</ListItemText>
</ListItem>
{canStaneksGift && (
<ListItem
button
key={"Staneks Gift"}
className={clsx({
[classes.active]: props.page === Page.StaneksGift,
})}
onClick={() => clickPage(Page.StaneksGift)}
>
<ListItemIcon>
<Tooltip title={!open ? "Stanek's Gift" : ""}>
<DeveloperBoardIcon color={props.page !== Page.StaneksGift ? "secondary" : "primary"} />
</Tooltip>
</ListItemIcon>
<ListItemText>
<Typography color={props.page !== Page.StaneksGift ? "secondary" : "primary"}>
Stanek's Gift
</Typography>
</ListItemText>
</ListItem>
)}
</List>
</Collapse>
<Divider />
<ListItem classes={{ root: classes.listitem }} button onClick={() => setCharacterOpen((old) => !old)}>
<ListItemIcon>
<Tooltip title={!open ? "Character" : ""}>
<AccountBoxIcon color="primary" />
</Tooltip>
</ListItemIcon>
<ListItemText primary={<Typography>Character</Typography>} />
{characterOpen ? <ExpandLessIcon color="primary" /> : <ExpandMoreIcon color="primary" />}
</ListItem>
<Collapse in={characterOpen} timeout="auto" unmountOnExit>
<ListItem
button
key={"Stats"}
className={clsx({
[classes.active]: props.page === Page.Stats,
})}
onClick={() => clickPage(Page.Stats)}
>
<ListItemIcon>
<Tooltip title={!open ? "Stats" : ""}>
<EqualizerIcon
color={flash === Page.Stats ? "error" : props.page !== Page.Stats ? "secondary" : "primary"}
/> />
</Tooltip> {divider}
</ListItemIcon> <SidebarAccordion
<ListItemText> key_="Help"
<Typography color={flash === Page.Stats ? "error" : props.page !== Page.Stats ? "secondary" : "primary"}> page={props.page}
Stats clickPage={clickPage}
</Typography> flash={flash}
</ListItemText> icon={liveHelpIcon}
</ListItem> sidebarOpen={open}
{canOpenFactions && ( classes={classes}
<ListItem items={[
classes={{ root: classes.listitem }} { key_: Page.Milestones, icon: checkIcon },
button { key_: Page.Tutorial, icon: helpIcon },
key={"Factions"} { key_: Page.Achievements, icon: emojiEventsIcon },
className={clsx({ { key_: Page.Options, icon: settingsIcon },
[classes.active]: [Page.Factions, Page.Faction].includes(props.page), process.env.NODE_ENV === "development" && { key_: Page.DevMenu, icon: developerBoardIcon },
})} ]}
onClick={() => clickPage(Page.Factions)}
>
<ListItemIcon>
<Badge badgeContent={invitationsCount !== 0 ? invitationsCount : undefined} color="error">
<Tooltip title={!open ? "Factions" : ""}>
<ContactsIcon
color={![Page.Factions, Page.Faction].includes(props.page) ? "secondary" : "primary"}
/> />
</Tooltip>
</Badge>
</ListItemIcon>
<ListItemText>
<Typography color={![Page.Factions, Page.Faction].includes(props.page) ? "secondary" : "primary"}>
Factions
</Typography>
</ListItemText>
</ListItem>
)}
{canOpenAugmentations && (
<ListItem
classes={{ root: classes.listitem }}
button
key={"Augmentations"}
className={clsx({
[classes.active]: props.page === Page.Augmentations,
})}
onClick={() => clickPage(Page.Augmentations)}
>
<ListItemIcon>
<Badge badgeContent={augmentationCount !== 0 ? augmentationCount : undefined} color="error">
<Tooltip title={!open ? "Augmentations" : ""}>
<DoubleArrowIcon
style={{ transform: "rotate(-90deg)" }}
color={props.page !== Page.Augmentations ? "secondary" : "primary"}
/>
</Tooltip>
</Badge>
</ListItemIcon>
<ListItemText>
<Typography color={props.page !== Page.Augmentations ? "secondary" : "primary"}>
Augmentations
</Typography>
</ListItemText>
</ListItem>
)}
<ListItem
button
key={"Hacknet"}
className={clsx({
[classes.active]: props.page === Page.Hacknet,
})}
onClick={() => clickPage(Page.Hacknet)}
>
<ListItemIcon>
<Tooltip title={!open ? "Hacknet" : ""}>
<AccountTreeIcon
color={flash === Page.Hacknet ? "error" : props.page !== Page.Hacknet ? "secondary" : "primary"}
/>
</Tooltip>
</ListItemIcon>
<ListItemText>
<Typography
color={flash === Page.Hacknet ? "error" : props.page !== Page.Hacknet ? "secondary" : "primary"}
>
Hacknet
</Typography>
</ListItemText>
</ListItem>
{canOpenSleeves && (
<ListItem
classes={{ root: classes.listitem }}
button
key={"Sleeves"}
className={clsx({
[classes.active]: props.page === Page.Sleeves,
})}
onClick={() => clickPage(Page.Sleeves)}
>
<ListItemIcon>
<Tooltip title={!open ? "Sleeves" : ""}>
<PeopleAltIcon color={props.page !== Page.Sleeves ? "secondary" : "primary"} />
</Tooltip>
</ListItemIcon>
<ListItemText>
<Typography color={props.page !== Page.Sleeves ? "secondary" : "primary"}>Sleeves</Typography>
</ListItemText>
</ListItem>
)}
</Collapse>
<Divider />
<ListItem classes={{ root: classes.listitem }} button onClick={() => setWorldOpen((old) => !old)}>
<ListItemIcon>
<Tooltip title={!open ? "World" : ""}>
<PublicIcon color="primary" />
</Tooltip>
</ListItemIcon>
<ListItemText primary={<Typography>World</Typography>} />
{worldOpen ? <ExpandLessIcon color="primary" /> : <ExpandMoreIcon color="primary" />}
</ListItem>
<Collapse in={worldOpen} timeout="auto" unmountOnExit>
<ListItem
button
key={"City"}
className={clsx({
[classes.active]:
props.page === Page.City || props.page === Page.Grafting || props.page === Page.Location,
})}
onClick={() => clickPage(Page.City)}
>
<ListItemIcon>
<Tooltip title={!open ? "City" : ""}>
<LocationCityIcon
color={flash === Page.City ? "error" : props.page !== Page.City ? "secondary" : "primary"}
/>
</Tooltip>
</ListItemIcon>
<ListItemText>
<Typography color={flash === Page.City ? "error" : props.page !== Page.City ? "secondary" : "primary"}>
City
</Typography>
</ListItemText>
</ListItem>
<ListItem
button
key={"Travel"}
className={clsx({
[classes.active]: props.page === Page.Travel,
})}
onClick={() => clickPage(Page.Travel)}
>
<ListItemIcon>
<Tooltip title={!open ? "Travel" : ""}>
<AirplanemodeActiveIcon color={props.page !== Page.Travel ? "secondary" : "primary"} />
</Tooltip>
</ListItemIcon>
<ListItemText>
<Typography color={props.page !== Page.Travel ? "secondary" : "primary"}>Travel</Typography>
</ListItemText>
</ListItem>
{canJob && (
<ListItem
classes={{ root: classes.listitem }}
button
key={"Job"}
className={clsx({
[classes.active]: props.page === Page.Job,
})}
onClick={() => clickPage(Page.Job)}
>
<ListItemIcon>
<Tooltip title={!open ? "Job" : ""}>
<WorkIcon color={props.page !== Page.Job ? "secondary" : "primary"} />
</Tooltip>
</ListItemIcon>
<ListItemText>
<Typography color={props.page !== Page.Job ? "secondary" : "primary"}>Job</Typography>
</ListItemText>
</ListItem>
)}
{canStockMarket && (
<ListItem
classes={{ root: classes.listitem }}
button
key={"Stock Market"}
className={clsx({
[classes.active]: props.page === Page.StockMarket,
})}
onClick={() => clickPage(Page.StockMarket)}
>
<ListItemIcon>
<Tooltip title={!open ? "Stock Market" : ""}>
<TrendingUpIcon color={props.page !== Page.StockMarket ? "secondary" : "primary"} />
</Tooltip>
</ListItemIcon>
<ListItemText>
<Typography color={props.page !== Page.StockMarket ? "secondary" : "primary"}>Stock Market</Typography>
</ListItemText>
</ListItem>
)}
{canBladeburner && (
<ListItem
classes={{ root: classes.listitem }}
button
key={"Bladeburner"}
className={clsx({
[classes.active]: props.page === Page.Bladeburner,
})}
onClick={() => clickPage(Page.Bladeburner)}
>
<ListItemIcon>
<Tooltip title={!open ? "Bladeburner" : ""}>
<FormatBoldIcon color={props.page !== Page.Bladeburner ? "secondary" : "primary"} />
</Tooltip>
</ListItemIcon>
<ListItemText>
<Typography color={props.page !== Page.Bladeburner ? "secondary" : "primary"}>Bladeburner</Typography>
</ListItemText>
</ListItem>
)}
{canCorporation && (
<ListItem
classes={{ root: classes.listitem }}
button
key={"Corp"}
className={clsx({
[classes.active]: props.page === Page.Corporation,
})}
onClick={() => clickPage(Page.Corporation)}
>
<ListItemIcon>
<Tooltip title={!open ? "Corp" : ""}>
<BusinessIcon color={props.page !== Page.Corporation ? "secondary" : "primary"} />
</Tooltip>
</ListItemIcon>
<ListItemText>
<Typography color={props.page !== Page.Corporation ? "secondary" : "primary"}>Corp</Typography>
</ListItemText>
</ListItem>
)}
{canGang && (
<ListItem
classes={{ root: classes.listitem }}
button
key={"Gang"}
className={clsx({
[classes.active]: props.page === Page.Gang,
})}
onClick={() => clickPage(Page.Gang)}
>
<ListItemIcon>
<Tooltip title={!open ? "Gang" : ""}>
<SportsMmaIcon color={props.page !== Page.Gang ? "secondary" : "primary"} />
</Tooltip>
</ListItemIcon>
<ListItemText>
<Typography color={props.page !== Page.Gang ? "secondary" : "primary"}>Gang</Typography>
</ListItemText>
</ListItem>
)}
</Collapse>
<Divider />
<ListItem classes={{ root: classes.listitem }} button onClick={() => setHelpOpen((old) => !old)}>
<ListItemIcon>
<Tooltip title={!open ? "Help" : ""}>
<LiveHelpIcon color="primary" />
</Tooltip>
</ListItemIcon>
<ListItemText primary={<Typography>Help</Typography>} />
{helpOpen ? <ExpandLessIcon color="primary" /> : <ExpandMoreIcon color="primary" />}
</ListItem>
<Collapse in={helpOpen} timeout="auto" unmountOnExit>
<ListItem
button
key={"Milestones"}
className={clsx({
[classes.active]: props.page === Page.Milestones,
})}
onClick={() => clickPage(Page.Milestones)}
>
<ListItemIcon>
<Tooltip title={!open ? "Milestones" : ""}>
<CheckIcon color={props.page !== Page.Milestones ? "secondary" : "primary"} />
</Tooltip>
</ListItemIcon>
<ListItemText>
<Typography color={props.page !== Page.Milestones ? "secondary" : "primary"}>Milestones</Typography>
</ListItemText>
</ListItem>
<ListItem
button
key={"Tutorial"}
className={clsx({
[classes.active]: props.page === Page.Tutorial,
})}
onClick={() => clickPage(Page.Tutorial)}
>
<ListItemIcon>
<Tooltip title={!open ? "Tutorial" : ""}>
<HelpIcon
color={flash === Page.Tutorial ? "error" : props.page !== Page.Tutorial ? "secondary" : "primary"}
/>
</Tooltip>
</ListItemIcon>
<ListItemText>
<Typography
color={flash === Page.Tutorial ? "error" : props.page !== Page.Tutorial ? "secondary" : "primary"}
>
Tutorial
</Typography>
</ListItemText>
</ListItem>
<ListItem
button
key={"Achievements"}
className={clsx({
[classes.active]: props.page === Page.Achievements,
})}
onClick={() => clickPage(Page.Achievements)}
>
<ListItemIcon>
<Tooltip title={!open ? "Achievements" : ""}>
<EmojiEventsIcon color={props.page !== Page.Achievements ? "secondary" : "primary"} />
</Tooltip>
</ListItemIcon>
<ListItemText>
<Typography color={props.page !== Page.Achievements ? "secondary" : "primary"}>Achievements</Typography>
</ListItemText>
</ListItem>
<ListItem
button
key={"Options"}
className={clsx({
[classes.active]: props.page === Page.Options,
})}
onClick={() => clickPage(Page.Options)}
>
<ListItemIcon>
<Tooltip title={!open ? "Options" : ""}>
<SettingsIcon color={props.page !== Page.Options ? "secondary" : "primary"} />
</Tooltip>
</ListItemIcon>
<ListItemText>
<Typography color={props.page !== Page.Options ? "secondary" : "primary"}>Options</Typography>
</ListItemText>
</ListItem>
{process.env.NODE_ENV === "development" && (
<ListItem
classes={{ root: classes.listitem }}
button
key={"Dev"}
className={clsx({
[classes.active]: props.page === Page.DevMenu,
})}
onClick={() => clickPage(Page.DevMenu)}
>
<ListItemIcon>
<Tooltip title={!open ? "Dev" : ""}>
<DeveloperBoardIcon color={props.page !== Page.DevMenu ? "secondary" : "primary"} />
</Tooltip>
</ListItemIcon>
<ListItemText>
<Typography color={props.page !== Page.DevMenu ? "secondary" : "primary"}>Dev</Typography>
</ListItemText>
</ListItem>
)}
</Collapse>
</List> </List>
</Drawer> </Drawer>
); );