mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-11-26 17:43:48 +01:00
Add theme browser page accessible from game options
Removed the themes buttons that were in the ThemeEditorModal and only left a "Revert to Default" button along with a link to the ThemeBrowser page. Split off the buttons into reusable components since they are now used in two pages. Display the themes in big cards with a zoomable screenshot. Applying the theme now shows a toast with an option to undo the action. The snackbar now allows ReactNode instead of only strings. - Add link with details on how to create a new theme in the game. - Add link to the theme-sharing discord channel. - Add icons to the theme & style buttons in GameOptions - Add "Theme Editor" button to ThemeBrowser - Add "Style Editor" button to ThemeBrowser - Move Styles related files into Themes folder - Includes a modal that shows a bigger version of the screenshot. - Change Snackbar to allow for ReactNode as the message
This commit is contained in:
parent
61d6e43b37
commit
a26b9c8dcf
@ -6,7 +6,7 @@ import { GameInfo, IStyleSettings, UserInterface as IUserInterface, UserInterfac
|
|||||||
import { Settings } from "../Settings/Settings";
|
import { Settings } from "../Settings/Settings";
|
||||||
import { ThemeEvents } from "../Themes/ui/Theme";
|
import { ThemeEvents } from "../Themes/ui/Theme";
|
||||||
import { defaultTheme } from "../Themes/Themes";
|
import { defaultTheme } from "../Themes/Themes";
|
||||||
import { defaultStyles } from "../Settings/Styles";
|
import { defaultStyles } from "../Themes/Styles";
|
||||||
import { CONSTANTS } from "../Constants";
|
import { CONSTANTS } from "../Constants";
|
||||||
import { hash } from "../hash/hash";
|
import { hash } from "../hash/hash";
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { ISelfInitializer, ISelfLoading } from "../types";
|
import { ISelfInitializer, ISelfLoading } from "../types";
|
||||||
import { OwnedAugmentationsOrderSetting, PurchaseAugmentationsOrderSetting } from "./SettingEnums";
|
import { OwnedAugmentationsOrderSetting, PurchaseAugmentationsOrderSetting } from "./SettingEnums";
|
||||||
import { defaultTheme, ITheme } from "../Themes/Themes";
|
import { defaultTheme, ITheme } from "../Themes/Themes";
|
||||||
import { defaultStyles } from "./Styles";
|
import { defaultStyles } from "../Themes/Styles";
|
||||||
import { WordWrapOptions } from "../ScriptEditor/ui/Options";
|
import { WordWrapOptions } from "../ScriptEditor/ui/Options";
|
||||||
import { OverviewSettings } from "../ui/React/Overview";
|
import { OverviewSettings } from "../ui/React/Overview";
|
||||||
import { IStyleSettings } from "../ScriptEditor/NetscriptDefinitions";
|
import { IStyleSettings } from "../ScriptEditor/NetscriptDefinitions";
|
||||||
|
@ -8,7 +8,8 @@ See [CONTRIBUTING.md](/CONTRIBUTING.md) for details.
|
|||||||
|
|
||||||
1. Duplicate one of the folders in `/src/Themes/data` and give it a new name (keep the hyphenated format)
|
1. Duplicate one of the folders in `/src/Themes/data` and give it a new name (keep the hyphenated format)
|
||||||
2. Modify the data in the new `/src/Themes/data/new-folder/index.ts` file
|
2. Modify the data in the new `/src/Themes/data/new-folder/index.ts` file
|
||||||
3. Add the import/export into the `/src/Themes/data/index.ts` file
|
3. Replace the screenshot.png with one of your theme
|
||||||
|
4. Add the import/export into the `/src/Themes/data/index.ts` file
|
||||||
|
|
||||||
The themes are ordered according to the export order in `index.ts`
|
The themes are ordered according to the export order in `index.ts`
|
||||||
|
|
||||||
|
19
src/Themes/ui/StyleEditorButton.tsx
Normal file
19
src/Themes/ui/StyleEditorButton.tsx
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import Button from "@mui/material/Button";
|
||||||
|
import Tooltip from "@mui/material/Tooltip";
|
||||||
|
import TextFormatIcon from "@mui/icons-material/TextFormat";
|
||||||
|
import { StyleEditorModal } from "./StyleEditorModal";
|
||||||
|
|
||||||
|
export function StyleEditorButton(): React.ReactElement {
|
||||||
|
const [styleEditorOpen, setStyleEditorOpen] = useState(false);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Tooltip title="The style editor allows you to modify certain CSS rules used by the game.">
|
||||||
|
<Button startIcon={<TextFormatIcon />} onClick={() => setStyleEditorOpen(true)}>
|
||||||
|
Style Editor
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<StyleEditorModal open={styleEditorOpen} onClose={() => setStyleEditorOpen(false)} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { Modal } from "./Modal";
|
import { Modal } from "../../ui/React/Modal";
|
||||||
|
|
||||||
import Button from "@mui/material/Button";
|
import Button from "@mui/material/Button";
|
||||||
import ButtonGroup from "@mui/material/ButtonGroup";
|
import ButtonGroup from "@mui/material/ButtonGroup";
|
||||||
@ -9,9 +9,9 @@ import TextField from "@mui/material/TextField";
|
|||||||
import ReplyIcon from "@mui/icons-material/Reply";
|
import ReplyIcon from "@mui/icons-material/Reply";
|
||||||
import SaveIcon from "@mui/icons-material/Save";
|
import SaveIcon from "@mui/icons-material/Save";
|
||||||
|
|
||||||
import { ThemeEvents } from "../../Themes/ui/Theme";
|
import { ThemeEvents } from "./Theme";
|
||||||
import { Settings } from "../../Settings/Settings";
|
import { Settings } from "../../Settings/Settings";
|
||||||
import { defaultStyles } from "../../Settings/Styles";
|
import { defaultStyles } from "../Styles";
|
||||||
import { Tooltip } from "@mui/material";
|
import { Tooltip } from "@mui/material";
|
||||||
import { IStyleSettings } from "../../ScriptEditor/NetscriptDefinitions";
|
import { IStyleSettings } from "../../ScriptEditor/NetscriptDefinitions";
|
||||||
|
|
93
src/Themes/ui/ThemeBrowser.tsx
Normal file
93
src/Themes/ui/ThemeBrowser.tsx
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import Typography from "@mui/material/Typography";
|
||||||
|
import Paper from "@mui/material/Paper";
|
||||||
|
import { ThemeEvents } from "./Theme";
|
||||||
|
import { Settings } from "../../Settings/Settings";
|
||||||
|
import { getPredefinedThemes, IPredefinedTheme } from "../Themes";
|
||||||
|
import { Box, ButtonGroup, Button } from "@mui/material";
|
||||||
|
import { IRouter } from "../../ui/Router";
|
||||||
|
import { ThemeEditorButton } from "./ThemeEditorButton";
|
||||||
|
import { StyleEditorButton } from "./StyleEditorButton";
|
||||||
|
import { ThemeEntry } from "./ThemeEntry";
|
||||||
|
import { ThemeCollaborate } from "./ThemeCollaborate";
|
||||||
|
import { Modal } from "../../ui/React/Modal";
|
||||||
|
import { SnackbarEvents } from "../../ui/React/Snackbar";
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
router: IRouter;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Everything dies when the theme gets reloaded, so we'll keep the current scroll to not jump around.
|
||||||
|
let previousScrollY = 0;
|
||||||
|
|
||||||
|
export function ThemeBrowser({ router }: IProps): React.ReactElement {
|
||||||
|
const [modalOpen, setModalOpen] = useState(false);
|
||||||
|
const [modalImageSrc, setModalImageSrc] = useState<string | undefined>();
|
||||||
|
const predefinedThemes = getPredefinedThemes();
|
||||||
|
const themes = (predefinedThemes &&
|
||||||
|
Object.entries(predefinedThemes).map(([key, templateTheme]) => (
|
||||||
|
<ThemeEntry
|
||||||
|
key={key}
|
||||||
|
theme={templateTheme}
|
||||||
|
onActivated={() => setTheme(templateTheme)}
|
||||||
|
onImageClick={handleZoom}
|
||||||
|
/>
|
||||||
|
))) || <></>;
|
||||||
|
|
||||||
|
function setTheme(theme: IPredefinedTheme): void {
|
||||||
|
previousScrollY = window.scrollY;
|
||||||
|
const previousColors = { ...Settings.theme };
|
||||||
|
Object.assign(Settings.theme, theme.colors);
|
||||||
|
ThemeEvents.emit();
|
||||||
|
SnackbarEvents.emit(
|
||||||
|
<>
|
||||||
|
Updated theme to "<strong>{theme.name}</strong>"
|
||||||
|
<Button
|
||||||
|
sx={{ ml: 1 }}
|
||||||
|
color="secondary"
|
||||||
|
size="small"
|
||||||
|
onClick={() => {
|
||||||
|
Object.assign(Settings.theme, previousColors);
|
||||||
|
ThemeEvents.emit();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
UNDO
|
||||||
|
</Button>
|
||||||
|
</>,
|
||||||
|
"info",
|
||||||
|
30000,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleZoom(src: string): void {
|
||||||
|
previousScrollY = window.scrollY;
|
||||||
|
setModalImageSrc(src);
|
||||||
|
setModalOpen(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCloseZoom(): void {
|
||||||
|
previousScrollY = window.scrollY;
|
||||||
|
setModalOpen(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
requestAnimationFrame(() => window.scrollTo(0, previousScrollY));
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ mx: 2 }}>
|
||||||
|
<Typography variant="h4">Theme Browser</Typography>
|
||||||
|
<Paper sx={{ px: 2, py: 1, my: 1 }}>
|
||||||
|
<ThemeCollaborate />
|
||||||
|
<ButtonGroup sx={{ mb: 2, display: "block" }}>
|
||||||
|
<ThemeEditorButton router={router} />
|
||||||
|
<StyleEditorButton />
|
||||||
|
</ButtonGroup>
|
||||||
|
<Box sx={{ display: "flex", flexWrap: "wrap" }}>{themes}</Box>
|
||||||
|
<Modal open={modalOpen} onClose={handleCloseZoom}>
|
||||||
|
<img src={modalImageSrc} style={{ width: "100%" }} />
|
||||||
|
</Modal>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
24
src/Themes/ui/ThemeCollaborate.tsx
Normal file
24
src/Themes/ui/ThemeCollaborate.tsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import React from "react";
|
||||||
|
import Typography from "@mui/material/Typography";
|
||||||
|
import { Link } from "@mui/material";
|
||||||
|
|
||||||
|
export function ThemeCollaborate(): React.ReactElement {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Typography sx={{ my: 1 }}>
|
||||||
|
If you've created a theme that you believe should be added in game's theme browser, feel free to{" "}
|
||||||
|
<Link href="https://github.com/danielyxie/bitburner/tree/dev/src/Themes/README.md" target="_blank">
|
||||||
|
create a pull request
|
||||||
|
</Link>
|
||||||
|
.
|
||||||
|
</Typography>
|
||||||
|
<Typography sx={{ my: 1 }}>
|
||||||
|
Head over to the{" "}
|
||||||
|
<Link href="https://discord.com/channels/415207508303544321/921991895230611466" target="_blank">
|
||||||
|
theme-sharing
|
||||||
|
</Link>{" "}
|
||||||
|
discord channel for more.
|
||||||
|
</Typography>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
24
src/Themes/ui/ThemeEditorButton.tsx
Normal file
24
src/Themes/ui/ThemeEditorButton.tsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import Button from "@mui/material/Button";
|
||||||
|
import Tooltip from "@mui/material/Tooltip";
|
||||||
|
import { ThemeEditorModal } from "./ThemeEditorModal";
|
||||||
|
import { IRouter } from "../../ui/Router";
|
||||||
|
import ColorizeIcon from "@mui/icons-material/Colorize";
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
router: IRouter;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ThemeEditorButton({ router }: IProps): React.ReactElement {
|
||||||
|
const [themeEditorOpen, setThemeEditorOpen] = useState(false);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Tooltip title="The theme editor allows you to modify the colors the game uses.">
|
||||||
|
<Button startIcon={<ColorizeIcon />} onClick={() => setThemeEditorOpen(true)}>
|
||||||
|
Theme Editor
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<ThemeEditorModal open={themeEditorOpen} onClose={() => setThemeEditorOpen(false)} router={router} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { Modal } from "../../ui/React/Modal";
|
import { Modal } from "../../ui/React/Modal";
|
||||||
import Button from "@mui/material/Button";
|
import Button from "@mui/material/Button";
|
||||||
|
import ButtonGroup from "@mui/material/ButtonGroup";
|
||||||
import Typography from "@mui/material/Typography";
|
import Typography from "@mui/material/Typography";
|
||||||
import Tooltip from "@mui/material/Tooltip";
|
import Tooltip from "@mui/material/Tooltip";
|
||||||
import Paper from "@mui/material/Paper";
|
import Paper from "@mui/material/Paper";
|
||||||
@ -8,15 +9,19 @@ import TextField from "@mui/material/TextField";
|
|||||||
import IconButton from "@mui/material/IconButton";
|
import IconButton from "@mui/material/IconButton";
|
||||||
import ReplyIcon from "@mui/icons-material/Reply";
|
import ReplyIcon from "@mui/icons-material/Reply";
|
||||||
import PaletteSharpIcon from "@mui/icons-material/PaletteSharp";
|
import PaletteSharpIcon from "@mui/icons-material/PaletteSharp";
|
||||||
|
import HistoryIcon from '@mui/icons-material/History';
|
||||||
import { Color, ColorPicker } from "material-ui-color";
|
import { Color, ColorPicker } from "material-ui-color";
|
||||||
import { ThemeEvents } from "./Theme";
|
import { ThemeEvents } from "./Theme";
|
||||||
import { Settings, defaultSettings } from "../../Settings/Settings";
|
import { Settings, defaultSettings } from "../../Settings/Settings";
|
||||||
import { getPredefinedThemes } from "../Themes";
|
import { defaultTheme } from "../Themes";
|
||||||
import { UserInterfaceTheme } from "../../ScriptEditor/NetscriptDefinitions";
|
import { UserInterfaceTheme } from "../../ScriptEditor/NetscriptDefinitions";
|
||||||
|
import { IRouter } from "../../ui/Router";
|
||||||
|
import { ThemeCollaborate } from "./ThemeCollaborate";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
|
router: IRouter;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IColorEditorProps {
|
interface IColorEditorProps {
|
||||||
@ -68,28 +73,6 @@ export function ThemeEditorModal(props: IProps): React.ReactElement {
|
|||||||
...Settings.theme,
|
...Settings.theme,
|
||||||
});
|
});
|
||||||
|
|
||||||
const predefinedThemes = getPredefinedThemes();
|
|
||||||
const themes = predefinedThemes && Object.entries(predefinedThemes)
|
|
||||||
.map(([key, templateTheme]) => {
|
|
||||||
const name = templateTheme.name || key;
|
|
||||||
let inner = <Typography>{name}</Typography>;
|
|
||||||
let toolTipTitle;
|
|
||||||
if (templateTheme.credit) {
|
|
||||||
toolTipTitle = <Typography>{templateTheme.description || name} <em>by {templateTheme.credit}</em></Typography>;
|
|
||||||
} else if (templateTheme.description) {
|
|
||||||
toolTipTitle = <Typography>{templateTheme.description}</Typography>;
|
|
||||||
}
|
|
||||||
if (toolTipTitle) {
|
|
||||||
inner = <Tooltip title={toolTipTitle}>{inner}</Tooltip>
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<Button onClick={() => setTemplateTheme(templateTheme.colors)}
|
|
||||||
startIcon={<PaletteSharpIcon />} key={key} sx={{ mr: 1, mb: 1 }}>
|
|
||||||
{inner}
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
}) || <></>;
|
|
||||||
|
|
||||||
function setTheme(theme: UserInterfaceTheme): void {
|
function setTheme(theme: UserInterfaceTheme): void {
|
||||||
setCustomTheme(theme);
|
setCustomTheme(theme);
|
||||||
Object.assign(Settings.theme, theme);
|
Object.assign(Settings.theme, theme);
|
||||||
@ -372,8 +355,18 @@ export function ThemeEditorModal(props: IProps): React.ReactElement {
|
|||||||
/>
|
/>
|
||||||
<>
|
<>
|
||||||
<Typography sx={{ my: 1 }}>Backup your theme or share it with others by copying the string above.</Typography>
|
<Typography sx={{ my: 1 }}>Backup your theme or share it with others by copying the string above.</Typography>
|
||||||
<Typography sx={{ my: 1 }}>Replace the current theme with a pre-built template using the buttons below.</Typography>
|
<ThemeCollaborate />
|
||||||
{themes}
|
<ButtonGroup>
|
||||||
|
<Tooltip title="Reverts all modification back to the default theme. This is permanent.">
|
||||||
|
<Button onClick={() => setTemplateTheme(defaultTheme)}
|
||||||
|
startIcon={<HistoryIcon />}>
|
||||||
|
Revert to Default
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title="Move over to the theme browser's page to use one of our predefined themes.">
|
||||||
|
<Button startIcon={<PaletteSharpIcon />} onClick={() => props.router.toThemeBrowser()}>See more themes</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</ButtonGroup>
|
||||||
</>
|
</>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
77
src/Themes/ui/ThemeEntry.tsx
Normal file
77
src/Themes/ui/ThemeEntry.tsx
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
import React from "react";
|
||||||
|
import Typography from "@mui/material/Typography";
|
||||||
|
import Tooltip from "@mui/material/Tooltip";
|
||||||
|
import PaletteSharpIcon from "@mui/icons-material/PaletteSharp";
|
||||||
|
import { Settings } from "../../Settings/Settings";
|
||||||
|
import { IPredefinedTheme } from "../Themes";
|
||||||
|
import { Link, Card, CardHeader, CardContent, CardMedia, Button } from "@mui/material";
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
theme: IPredefinedTheme;
|
||||||
|
onActivated: () => void;
|
||||||
|
onImageClick: (src: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ThemeEntry({ theme, onActivated, onImageClick }: IProps): React.ReactElement {
|
||||||
|
if (!theme) return <></>;
|
||||||
|
return (
|
||||||
|
<Card key={theme.screenshot} sx={{ width: 400, mr: 1, mb: 1 }}>
|
||||||
|
<CardHeader
|
||||||
|
action={
|
||||||
|
<Tooltip title="Use this theme">
|
||||||
|
<Button startIcon={<PaletteSharpIcon />} onClick={onActivated} variant="outlined">
|
||||||
|
Use
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
}
|
||||||
|
title={theme.name}
|
||||||
|
subheader={
|
||||||
|
<>
|
||||||
|
by {theme.credit}{" "}
|
||||||
|
{theme.reference && (
|
||||||
|
<>
|
||||||
|
(
|
||||||
|
<Link href={theme.reference} target="_blank">
|
||||||
|
ref
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
sx={{
|
||||||
|
color: Settings.theme.primary,
|
||||||
|
"& .MuiCardHeader-subheader": {
|
||||||
|
color: Settings.theme.secondarydark,
|
||||||
|
},
|
||||||
|
"& .MuiButton-outlined": {
|
||||||
|
backgroundColor: "transparent",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<CardMedia
|
||||||
|
component="img"
|
||||||
|
width="400"
|
||||||
|
image={theme.screenshot}
|
||||||
|
alt={`Theme Screenshot of "${theme.name}"`}
|
||||||
|
sx={{
|
||||||
|
borderTop: `1px solid ${Settings.theme.welllight}`,
|
||||||
|
borderBottom: `1px solid ${Settings.theme.welllight}`,
|
||||||
|
cursor: "zoom-in",
|
||||||
|
}}
|
||||||
|
onClick={() => onImageClick(theme.screenshot)}
|
||||||
|
/>
|
||||||
|
<CardContent>
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
color="text.secondary"
|
||||||
|
sx={{
|
||||||
|
color: Settings.theme.primarydark,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{theme.description}
|
||||||
|
</Typography>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
@ -79,6 +79,7 @@ import { RecoveryMode, RecoveryRoot } from "./React/RecoveryRoot";
|
|||||||
import { AchievementsRoot } from "../Achievements/AchievementsRoot";
|
import { AchievementsRoot } from "../Achievements/AchievementsRoot";
|
||||||
import { ErrorBoundary } from "./ErrorBoundary";
|
import { ErrorBoundary } from "./ErrorBoundary";
|
||||||
import { Settings } from "../Settings/Settings";
|
import { Settings } from "../Settings/Settings";
|
||||||
|
import { ThemeBrowser } from "../Themes/ui/ThemeBrowser";
|
||||||
|
|
||||||
const htmlLocation = location;
|
const htmlLocation = location;
|
||||||
|
|
||||||
@ -194,6 +195,9 @@ export let Router: IRouter = {
|
|||||||
toAchievements: () => {
|
toAchievements: () => {
|
||||||
throw new Error("Router called before initialization");
|
throw new Error("Router called before initialization");
|
||||||
},
|
},
|
||||||
|
toThemeBrowser: () => {
|
||||||
|
throw new Error("Router called before initialization");
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
function determineStartPage(player: IPlayer): Page {
|
function determineStartPage(player: IPlayer): Page {
|
||||||
@ -307,6 +311,9 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
|
|||||||
toAchievements: () => {
|
toAchievements: () => {
|
||||||
setPage(Page.Achievements);
|
setPage(Page.Achievements);
|
||||||
},
|
},
|
||||||
|
toThemeBrowser: () => {
|
||||||
|
setPage(Page.ThemeBrowser);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -471,6 +478,7 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
|
|||||||
mainPage = (
|
mainPage = (
|
||||||
<GameOptionsRoot
|
<GameOptionsRoot
|
||||||
player={player}
|
player={player}
|
||||||
|
router={Router}
|
||||||
save={() => saveObject.saveGame()}
|
save={() => saveObject.saveGame()}
|
||||||
export={() => {
|
export={() => {
|
||||||
// Apply the export bonus before saving the game
|
// Apply the export bonus before saving the game
|
||||||
@ -503,6 +511,10 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
|
|||||||
mainPage = <AchievementsRoot />;
|
mainPage = <AchievementsRoot />;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case Page.ThemeBrowser: {
|
||||||
|
mainPage = <ThemeBrowser router={Router} />;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -22,12 +22,11 @@ import TextField from "@mui/material/TextField";
|
|||||||
import DownloadIcon from "@mui/icons-material/Download";
|
import DownloadIcon from "@mui/icons-material/Download";
|
||||||
import UploadIcon from "@mui/icons-material/Upload";
|
import UploadIcon from "@mui/icons-material/Upload";
|
||||||
import SaveIcon from "@mui/icons-material/Save";
|
import SaveIcon from "@mui/icons-material/Save";
|
||||||
|
import PaletteIcon from '@mui/icons-material/Palette';
|
||||||
|
|
||||||
import { FileDiagnosticModal } from "../../Diagnostic/FileDiagnosticModal";
|
import { FileDiagnosticModal } from "../../Diagnostic/FileDiagnosticModal";
|
||||||
import { dialogBoxCreate } from "./DialogBox";
|
import { dialogBoxCreate } from "./DialogBox";
|
||||||
import { ConfirmationModal } from "./ConfirmationModal";
|
import { ConfirmationModal } from "./ConfirmationModal";
|
||||||
import { ThemeEditorModal } from "../../Themes/ui/ThemeEditorModal";
|
|
||||||
import { StyleEditorModal } from "./StyleEditorModal";
|
|
||||||
|
|
||||||
import { SnackbarEvents } from "./Snackbar";
|
import { SnackbarEvents } from "./Snackbar";
|
||||||
|
|
||||||
@ -37,6 +36,9 @@ import { formatTime } from "../../utils/helpers/formatTime";
|
|||||||
import { OptionSwitch } from "./OptionSwitch";
|
import { OptionSwitch } from "./OptionSwitch";
|
||||||
import { DeleteGameButton } from "./DeleteGameButton";
|
import { DeleteGameButton } from "./DeleteGameButton";
|
||||||
import { SoftResetButton } from "./SoftResetButton";
|
import { SoftResetButton } from "./SoftResetButton";
|
||||||
|
import { IRouter } from "../Router";
|
||||||
|
import { ThemeEditorButton } from "../../Themes/ui/ThemeEditorButton";
|
||||||
|
import { StyleEditorButton } from "../../Themes/ui/StyleEditorButton";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme: Theme) =>
|
const useStyles = makeStyles((theme: Theme) =>
|
||||||
createStyles({
|
createStyles({
|
||||||
@ -50,6 +52,7 @@ const useStyles = makeStyles((theme: Theme) =>
|
|||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
player: IPlayer;
|
player: IPlayer;
|
||||||
|
router: IRouter;
|
||||||
save: () => void;
|
save: () => void;
|
||||||
export: () => void;
|
export: () => void;
|
||||||
forceKill: () => void;
|
forceKill: () => void;
|
||||||
@ -74,8 +77,6 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
|
|||||||
const [timestampFormat, setTimestampFormat] = useState(Settings.TimestampsFormat);
|
const [timestampFormat, setTimestampFormat] = useState(Settings.TimestampsFormat);
|
||||||
const [locale, setLocale] = useState(Settings.Locale);
|
const [locale, setLocale] = useState(Settings.Locale);
|
||||||
const [diagnosticOpen, setDiagnosticOpen] = useState(false);
|
const [diagnosticOpen, setDiagnosticOpen] = useState(false);
|
||||||
const [themeEditorOpen, setThemeEditorOpen] = useState(false);
|
|
||||||
const [styleEditorOpen, setStyleEditorOpen] = useState(false);
|
|
||||||
const [importSaveOpen, setImportSaveOpen] = useState(false);
|
const [importSaveOpen, setImportSaveOpen] = useState(false);
|
||||||
const [importData, setImportData] = useState<ImportData | null>(null);
|
const [importData, setImportData] = useState<ImportData | null>(null);
|
||||||
|
|
||||||
@ -642,9 +643,14 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
|
|||||||
<Button onClick={() => setDiagnosticOpen(true)}>Diagnose files</Button>
|
<Button onClick={() => setDiagnosticOpen(true)}>Diagnose files</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Box>
|
</Box>
|
||||||
<Box sx={{ display: "grid", gridTemplateColumns: "1fr 1fr" }}>
|
<Box sx={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr" }}>
|
||||||
<Button onClick={() => setThemeEditorOpen(true)}>Theme editor</Button>
|
<Tooltip title="Head to the theme browser to see a collection of prebuilt themes.">
|
||||||
<Button onClick={() => setStyleEditorOpen(true)}>Style editor</Button>
|
<Button startIcon={<PaletteIcon />} onClick={() => props.router.toThemeBrowser()}>
|
||||||
|
Theme Browser
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<ThemeEditorButton router={props.router} />
|
||||||
|
<StyleEditorButton />
|
||||||
</Box>
|
</Box>
|
||||||
<Box>
|
<Box>
|
||||||
<Link href="https://github.com/danielyxie/bitburner/issues/new" target="_blank">
|
<Link href="https://github.com/danielyxie/bitburner/issues/new" target="_blank">
|
||||||
@ -669,8 +675,6 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
|
|||||||
</Box>
|
</Box>
|
||||||
</Grid>
|
</Grid>
|
||||||
<FileDiagnosticModal open={diagnosticOpen} onClose={() => setDiagnosticOpen(false)} />
|
<FileDiagnosticModal open={diagnosticOpen} onClose={() => setDiagnosticOpen(false)} />
|
||||||
<ThemeEditorModal open={themeEditorOpen} onClose={() => setThemeEditorOpen(false)} />
|
|
||||||
<StyleEditorModal open={styleEditorOpen} onClose={() => setStyleEditorOpen(false)} />
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,10 @@ const useStyles = makeStyles(() => ({
|
|||||||
snackbar: {
|
snackbar: {
|
||||||
// Log popup z-index increments, so let's add a padding to be well above them.
|
// Log popup z-index increments, so let's add a padding to be well above them.
|
||||||
zIndex: `${logBoxBaseZIndex + 1000} !important` as any,
|
zIndex: `${logBoxBaseZIndex + 1000} !important` as any,
|
||||||
|
|
||||||
|
"& .MuiAlert-icon": {
|
||||||
|
alignSelf: 'center',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -27,7 +31,7 @@ export function SnackbarProvider(props: IProps): React.ReactElement {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SnackbarEvents = new EventEmitter<[string, "success" | "warning" | "error" | "info", number]>();
|
export const SnackbarEvents = new EventEmitter<[string | React.ReactNode, "success" | "warning" | "error" | "info", number]>();
|
||||||
|
|
||||||
export function Snackbar(): React.ReactElement {
|
export function Snackbar(): React.ReactElement {
|
||||||
const { enqueueSnackbar, closeSnackbar } = useSnackbar();
|
const { enqueueSnackbar, closeSnackbar } = useSnackbar();
|
||||||
|
@ -37,6 +37,7 @@ export enum Page {
|
|||||||
StaneksGift,
|
StaneksGift,
|
||||||
Recovery,
|
Recovery,
|
||||||
Achievements,
|
Achievements,
|
||||||
|
ThemeBrowser,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ScriptEditorRouteOptions {
|
export interface ScriptEditorRouteOptions {
|
||||||
@ -82,4 +83,5 @@ export interface IRouter {
|
|||||||
toLocation(location: Location): void;
|
toLocation(location: Location): void;
|
||||||
toStaneksGift(): void;
|
toStaneksGift(): void;
|
||||||
toAchievements(): void;
|
toAchievements(): void;
|
||||||
|
toThemeBrowser(): void;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user