Merge pull request #1351 from danielyxie/dev

Theme editor
This commit is contained in:
hydroflame 2021-09-22 02:20:56 -04:00 committed by GitHub
commit 46d9ad8419
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 486 additions and 232 deletions

34
dist/vendor.bundle.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -2,7 +2,7 @@ import React, { useState } from "react";
import { SourceFileFlags } from "../../SourceFile/SourceFileFlags";
import { IRouter } from "../../ui/Router";
import { BitNodes } from "../BitNode";
import { enterBitNode } from "../../RedPill";
import { enterBitNode, setRedPillFlag } from "../../RedPill";
import { PortalPopup } from "./PortalPopup";
import { createPopup } from "../../ui/React/createPopup";
import { CinematicText } from "../../ui/React/CinematicText";
@ -71,6 +71,7 @@ interface IProps {
}
export function BitverseRoot(props: IProps): React.ReactElement {
setRedPillFlag(true);
const player = use.Player();
const enter = enterBitNode;
const destroyed = player.bitNodeN;

@ -77,8 +77,9 @@ export function ResearchPopup(props: IProps): React.ReactElement {
return (
<div id={props.popupId}>
<div id={props.popupId + "outer-box"}></div>
<div>
Research points: {props.industry.sciResearch.qty}
<br />
Multipliers from research:
<br />* Advertising Multiplier: x{researchTree.getAdvertisingMultiplier()}
<br />* Employee Charisma Multiplier: x{researchTree.getEmployeeChaMultiplier()}

@ -1,12 +1,16 @@
import LinearProgress from "@mui/material/LinearProgress";
import React, { useState, useEffect } from "react";
import withStyles from '@mui/styles/withStyles';
import withStyles from "@mui/styles/withStyles";
import { Theme } from "@mui/material/styles";
import Grid from "@mui/material/Grid";
const TimerProgress = withStyles(() => ({
const TimerProgress = withStyles((theme: Theme) => ({
root: {
backgroundColor: theme.palette.background.paper,
},
bar: {
transition: "none",
backgroundColor: "#adff2f",
backgroundColor: theme.palette.primary.main,
},
}))(LinearProgress);
@ -36,7 +40,7 @@ export function GameTimer(props: IProps): React.ReactElement {
// bar physically reaches the end
return (
<Grid item xs={12}>
<TimerProgress variant="determinate" value={v} />
<TimerProgress variant="determinate" value={v} color="primary" />
</Grid>
);
}

@ -48,7 +48,6 @@ function coloredArrow(difficulty: number): JSX.Element {
}
export function Intro(props: IProps): React.ReactElement {
console.log(props);
return (
<>
<Grid container spacing={3}>

@ -48,17 +48,18 @@ function addMessageToServer(msg, serverHostname) {
//Checks if any of the 'timed' messages should be sent
function checkForMessagesToSend() {
var jumper0 = Messages[MessageFilenames.Jumper0];
var jumper1 = Messages[MessageFilenames.Jumper1];
var jumper2 = Messages[MessageFilenames.Jumper2];
var jumper3 = Messages[MessageFilenames.Jumper3];
var jumper4 = Messages[MessageFilenames.Jumper4];
var cybersecTest = Messages[MessageFilenames.CyberSecTest];
var nitesecTest = Messages[MessageFilenames.NiteSecTest];
var bitrunnersTest = Messages[MessageFilenames.BitRunnersTest];
var redpill = Messages[MessageFilenames.RedPill];
if (redPillFlag) return;
const jumper0 = Messages[MessageFilenames.Jumper0];
const jumper1 = Messages[MessageFilenames.Jumper1];
const jumper2 = Messages[MessageFilenames.Jumper2];
const jumper3 = Messages[MessageFilenames.Jumper3];
const jumper4 = Messages[MessageFilenames.Jumper4];
const cybersecTest = Messages[MessageFilenames.CyberSecTest];
const nitesecTest = Messages[MessageFilenames.NiteSecTest];
const bitrunnersTest = Messages[MessageFilenames.BitRunnersTest];
const redpill = Messages[MessageFilenames.RedPill];
var redpillOwned = false;
const redpillOwned = false;
if (Augmentations[AugmentationNames.TheRedPill].owned) {
redpillOwned = true;
}

@ -2354,7 +2354,7 @@ function NetscriptFunctions(workerScript) {
);
}
var port = NetscriptPorts[port - 1];
if (port == null || !(port instanceof NetscriptPort)) {
if (port == null || !(port instanceof Object)) {
throw makeRuntimeErrorMsg("write", `Could not find port: ${port}. This is a bug. Report to dev.`);
}
return port.write(data);
@ -2421,7 +2421,7 @@ function NetscriptFunctions(workerScript) {
);
}
var port = NetscriptPorts[port - 1];
if (port == null || !(port instanceof NetscriptPort)) {
if (port == null || !(port instanceof Object)) {
throw makeRuntimeErrorMsg("tryWrite", `Could not find port: ${port}. This is a bug. Report to dev.`);
}
return port.tryWrite(data);
@ -2442,7 +2442,7 @@ function NetscriptFunctions(workerScript) {
);
}
var port = NetscriptPorts[port - 1];
if (port == null || !(port instanceof NetscriptPort)) {
if (port == null || !(port instanceof Object)) {
throw makeRuntimeErrorMsg("read", `Could not find port: ${port}. This is a bug. Report to dev.`);
}
return port.read();
@ -2489,7 +2489,7 @@ function NetscriptFunctions(workerScript) {
);
}
var port = NetscriptPorts[port - 1];
if (port == null || !(port instanceof NetscriptPort)) {
if (port == null || !(port instanceof Object)) {
throw makeRuntimeErrorMsg("peek", `Could not find port: ${port}. This is a bug. Report to dev.`);
}
return port.peek();
@ -2506,7 +2506,7 @@ function NetscriptFunctions(workerScript) {
);
}
var port = NetscriptPorts[port - 1];
if (port == null || !(port instanceof NetscriptPort)) {
if (port == null || !(port instanceof Object)) {
throw makeRuntimeErrorMsg("clear", `Could not find port: ${port}. This is a bug. Report to dev.`);
}
return port.clear();
@ -2542,7 +2542,7 @@ function NetscriptFunctions(workerScript) {
);
}
var port = NetscriptPorts[port - 1];
if (port == null || !(port instanceof NetscriptPort)) {
if (port == null || !(port instanceof Object)) {
throw makeRuntimeErrorMsg("getPortHandle", `Could not find port: ${port}. This is a bug. Report to dev.`);
}
return port;

@ -1,51 +1,55 @@
import { Settings } from "./Settings/Settings";
export class NetscriptPort {
data: any[] = [];
interface IPort {}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
write(data: any): any {
this.data.push(data);
if (this.data.length > Settings.MaxPortCapacity) {
return this.data.shift();
}
return null;
}
export function NetscriptPort(): IPort {
const data: any[] = [];
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
tryWrite(data: any): boolean {
if (this.data.length >= Settings.MaxPortCapacity) {
return false;
}
this.data.push(data);
return true;
}
return {
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
write: (value: any): any => {
data.push(value);
if (data.length > Settings.MaxPortCapacity) {
return data.shift();
}
return null;
},
read(): any {
if (this.data.length === 0) {
return "NULL PORT DATA";
}
return this.data.shift();
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
tryWrite: (value: any): boolean => {
if (data.length >= Settings.MaxPortCapacity) {
return false;
}
data.push(value);
return true;
},
peek(): any {
if (this.data.length === 0) {
return "NULL PORT DATA";
} else {
const foo = this.data.slice();
return foo[0];
}
}
read: (): any => {
if (data.length === 0) {
return "NULL PORT DATA";
}
return data.shift();
},
full(): boolean {
return this.data.length == Settings.MaxPortCapacity;
}
peek: (): any => {
if (data.length === 0) {
return "NULL PORT DATA";
} else {
const foo = data.slice();
return foo[0];
}
},
empty(): boolean {
return this.data.length === 0;
}
full: (): boolean => {
return data.length == Settings.MaxPortCapacity;
},
clear(): void {
this.data.length = 0;
}
empty: (): boolean => {
return data.length === 0;
},
clear: (): void => {
data.length = 0;
},
};
}

@ -35,7 +35,7 @@ import { simple as walksimple } from "acorn-walk";
// Netscript Ports are instantiated here
export const NetscriptPorts = [];
for (var i = 0; i < CONSTANTS.NumNetscriptPorts; ++i) {
NetscriptPorts.push(new NetscriptPort());
NetscriptPorts.push(NetscriptPort());
}
export function prestigeWorkerScripts() {

1
src/RedPill.d.ts vendored

@ -1,2 +1,3 @@
export declare let redPillFlag: boolean;
export declare function enterBitNode(router: IRouter, flume: boolean, destroyedBitNode: number, newBitNode: number);
export declare function setRedPillFlag(b: boolean): void;

@ -9,7 +9,11 @@ import { SourceFiles } from "./SourceFile/SourceFiles";
import { dialogBoxCreate } from "../utils/DialogBox";
let redPillFlag = false;
export let redPillFlag = false;
export function setRedPillFlag(b) {
redPillFlag = b;
}
function giveSourceFile(bitNodeNumber) {
var sourceFileKey = "SourceFile" + bitNodeNumber.toString();
@ -81,5 +85,3 @@ export function enterBitNode(router, flume, destroyedBitNode, newBitNode) {
}
prestigeSourceFile(flume);
}
export { redPillFlag };

@ -3,8 +3,6 @@ import { Companies, loadCompanies } from "./Company/Companies";
import { CONSTANTS } from "./Constants";
import { Engine } from "./engine";
import { Factions, loadFactions } from "./Faction/Factions";
import { loadFconf } from "./Fconf/Fconf";
import { FconfSettings } from "./Fconf/FconfSettings";
import { loadAllGangs, AllGangs } from "./Gang/AllGangs";
import { loadMessages, initMessages, Messages } from "./Message/MessageHelpers";
import { Player, loadPlayer } from "./Player";
@ -42,7 +40,6 @@ function BitburnerSaveObject() {
this.MessagesSave = "";
this.StockMarketSave = "";
this.SettingsSave = "";
this.FconfSettingsSave = "";
this.VersionSave = "";
this.AllGangsSave = "";
this.LastExportBonus = "";
@ -74,7 +71,6 @@ BitburnerSaveObject.prototype.getSaveString = function () {
this.MessagesSave = JSON.stringify(Messages);
this.StockMarketSave = JSON.stringify(StockMarket);
this.SettingsSave = JSON.stringify(Settings);
this.FconfSettingsSave = JSON.stringify(FconfSettings);
this.VersionSave = JSON.stringify(CONSTANTS.Version);
this.LastExportBonus = JSON.stringify(ExportBonus.LastExportBonus);
if (Player.inGang()) {

@ -75,7 +75,7 @@ export class RunningScript {
}
let logEntry = txt;
if (FconfSettings.ENABLE_TIMESTAMPS) {
if (Settings.EnableTimestamps) {
logEntry = "[" + getTimestamp() + "] " + logEntry;
}

@ -76,7 +76,7 @@ let lastPosition: monaco.Position | null = null;
export function Root(props: IProps): React.ReactElement {
const editorRef = useRef<IStandaloneCodeEditor | null>(null);
const [filename, setFilename] = useState(props.filename ? props.filename : lastFilename);
const [code, setCode] = useState<string>(props.code ? props.code : lastCode);
const [code, setCode] = useState<string>(props.filename ? props.code : lastCode);
const [ram, setRAM] = useState("RAM: ???");
const [options, setOptions] = useState<Options>({
theme: Settings.MonacoTheme,
@ -86,7 +86,6 @@ export function Root(props: IProps): React.ReactElement {
// store the last known state in case we need to restart without nano.
useEffect(() => {
if (props.filename === undefined) return;
console.log("setting to " + props.filename);
lastFilename = props.filename;
lastCode = props.code;
lastPosition = null;

@ -38,6 +38,16 @@ interface IDefaultSettings {
*/
DisableTextEffects: boolean;
/**
* Enable bash hotkeys
*/
EnableBashHotkeys: boolean;
/**
* Enable timestamps
*/
EnableTimestamps: boolean;
/**
* Locale used for display numbers
*/
@ -87,6 +97,39 @@ interface IDefaultSettings {
* Whether the user should be displayed a popup message when his Bladeburner actions are cancelled.
*/
SuppressBladeburnerPopup: boolean;
/*
* Theme colors
*/
theme: {
[key: string]: string | undefined;
primarylight: string;
primary: string;
primarydark: string;
errorlight: string;
error: string;
errordark: string;
secondarylight: string;
secondary: string;
secondarydark: string;
warninglight: string;
warning: string;
warningdark: string;
infolight: string;
info: string;
infodark: string;
welllight: string;
well: string;
white: string;
black: string;
hp: string;
money: string;
hack: string;
combat: string;
cha: string;
int: string;
rep: string;
};
}
/**
@ -108,7 +151,7 @@ interface ISettings extends IDefaultSettings {
MonacoInsertSpaces: boolean;
}
const defaultSettings: IDefaultSettings = {
export const defaultSettings: IDefaultSettings = {
ActiveScriptsServerPageSize: 10,
ActiveScriptsScriptPageSize: 10,
AutosaveInterval: 60,
@ -116,6 +159,8 @@ const defaultSettings: IDefaultSettings = {
DisableASCIIArt: false,
DisableHotkeys: false,
DisableTextEffects: false,
EnableBashHotkeys: false,
EnableTimestamps: false,
Locale: "en",
MaxLogCapacity: 50,
MaxPortCapacity: 50,
@ -126,6 +171,35 @@ const defaultSettings: IDefaultSettings = {
SuppressMessages: false,
SuppressTravelConfirmation: false,
SuppressBladeburnerPopup: false,
theme: {
primarylight: "#0f0",
primary: "#0c0",
primarydark: "#090",
errorlight: "#f00",
error: "#c00",
errordark: "#900",
secondarylight: "#AAA",
secondary: "#888",
secondarydark: "#666",
warninglight: "#ff0",
warning: "#cc0",
warningdark: "#990",
infolight: "#69f",
info: "#36c",
infodark: "#039",
welllight: "#444",
well: "#222",
white: "#fff",
black: "#000",
hp: "#dd3434",
money: "#ffd700",
hack: "#adff2f",
combat: "#faffdf",
cha: "#a671d1",
int: "#6495ed",
rep: "#faffdf",
},
};
/**
@ -140,6 +214,8 @@ export const Settings: ISettings & ISelfInitializer & ISelfLoading = {
DisableASCIIArt: defaultSettings.DisableASCIIArt,
DisableHotkeys: defaultSettings.DisableHotkeys,
DisableTextEffects: defaultSettings.DisableTextEffects,
EnableBashHotkeys: defaultSettings.EnableBashHotkeys,
EnableTimestamps: defaultSettings.EnableTimestamps,
Locale: "en",
MaxLogCapacity: defaultSettings.MaxLogCapacity,
MaxPortCapacity: defaultSettings.MaxPortCapacity,
@ -154,6 +230,35 @@ export const Settings: ISettings & ISelfInitializer & ISelfLoading = {
SuppressBladeburnerPopup: defaultSettings.SuppressBladeburnerPopup,
MonacoTheme: "vs-dark",
MonacoInsertSpaces: false,
theme: {
primarylight: defaultSettings.theme.primarylight,
primary: defaultSettings.theme.primary,
primarydark: defaultSettings.theme.primarydark,
errorlight: defaultSettings.theme.errorlight,
error: defaultSettings.theme.error,
errordark: defaultSettings.theme.errordark,
secondarylight: defaultSettings.theme.secondarylight,
secondary: defaultSettings.theme.secondary,
secondarydark: defaultSettings.theme.secondarydark,
warninglight: defaultSettings.theme.warninglight,
warning: defaultSettings.theme.warning,
warningdark: defaultSettings.theme.warningdark,
infolight: defaultSettings.theme.infolight,
info: defaultSettings.theme.info,
infodark: defaultSettings.theme.infodark,
welllight: defaultSettings.theme.welllight,
well: defaultSettings.theme.well,
white: defaultSettings.theme.white,
black: defaultSettings.theme.black,
hp: defaultSettings.theme.hp,
money: defaultSettings.theme.money,
hack: defaultSettings.theme.hack,
combat: defaultSettings.theme.combat,
cha: defaultSettings.theme.cha,
int: defaultSettings.theme.int,
rep: defaultSettings.theme.rep,
},
init() {
Object.assign(Settings, defaultSettings);
},

@ -52,7 +52,6 @@ import { redPillFlag } from "../../RedPill";
import { inMission } from "../../Missions";
import { KEY } from "../../../utils/helpers/keyCodes";
import { FconfSettings } from "../../Fconf/FconfSettings";
const openedMixin = (theme: Theme): CSSObject => ({
width: theme.spacing(31),
@ -297,7 +296,7 @@ export function SidebarRoot(props: IProps): React.ReactElement {
clickCreateProgram();
} else if (event.keyCode === KEY.F && event.altKey) {
// Overriden by Fconf
if (props.page == Page.Terminal && FconfSettings.ENABLE_BASH_HOTKEYS) {
if (props.page == Page.Terminal && Settings.EnableBashHotkeys) {
return;
}
event.preventDefault();

@ -2,6 +2,8 @@ import { TextFile } from "../TextFile";
import { Script } from "../Script/Script";
import { IPlayer } from "../PersonObjects/IPlayer";
import { IRouter } from "../ui/Router";
import { Settings } from "../Settings/Settings";
import { getTimestamp } from "../../utils/helpers/getTimestamp";
export class Output {
text: string;
@ -10,6 +12,7 @@ export class Output {
text: string,
color: "inherit" | "initial" | "primary" | "secondary" | "error" | "textPrimary" | "textSecondary" | undefined,
) {
if (Settings.EnableTimestamps) text = "[" + getTimestamp() + "] " + text;
this.text = text;
this.color = color;
}
@ -18,6 +21,7 @@ export class Output {
export class Link {
hostname: string;
constructor(hostname: string) {
if (Settings.EnableTimestamps) hostname = "[" + getTimestamp() + "] " + hostname;
this.hostname = hostname;
}
}

@ -39,7 +39,7 @@ export function ls(
}
// If the second argument is not a pipe, then it must be for listing a directory
if (numArgs >= 2 && args[0] !== "|") {
if (numArgs >= 1 && args[0] !== "|") {
const newPath = evaluateDirectoryPath(args[0] + "", terminal.cwd());
prefix = newPath ? newPath : "";
if (prefix != null) {

@ -12,7 +12,7 @@ import { IRouter } from "../../ui/Router";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { determineAllPossibilitiesForTabCompletion } from "../determineAllPossibilitiesForTabCompletion";
import { tabCompletion } from "../tabCompletion";
import { FconfSettings } from "../../Fconf/FconfSettings";
import { Settings } from "../../Settings/Settings";
const useStyles = makeStyles((theme: Theme) =>
createStyles({
@ -149,12 +149,15 @@ export function TerminalInput({ terminal, router, player }: IProps): React.React
useEffect(() => {
function keyDown(this: Document, event: KeyboardEvent): void {
if (terminal.contractOpen) return;
const ref = terminalInput.current;
if (ref) ref.focus();
// Cancel action
if (event.keyCode === KEY.C && event.ctrlKey) {
if (terminal.action !== null && event.keyCode === KEY.C && event.ctrlKey) {
terminal.finishAction(router, player, true);
return;
}
const ref = terminalInput.current;
if (event.ctrlKey || event.metaKey) return;
if (event.keyCode === KEY.C && (event.ctrlKey || event.metaKey)) return; // trying to copy
if (ref) ref.focus();
}
document.addEventListener("keydown", keyDown);
return () => document.removeEventListener("keydown", keyDown);
@ -227,11 +230,8 @@ export function TerminalInput({ terminal, router, player }: IProps): React.React
}
// Select previous command.
if (
event.keyCode === KEY.UPARROW ||
(FconfSettings.ENABLE_BASH_HOTKEYS && event.keyCode === KEY.P && event.ctrlKey)
) {
if (FconfSettings.ENABLE_BASH_HOTKEYS) {
if (event.keyCode === KEY.UPARROW || (Settings.EnableBashHotkeys && event.keyCode === KEY.P && event.ctrlKey)) {
if (Settings.EnableBashHotkeys) {
event.preventDefault();
}
const i = terminal.commandHistoryIndex;
@ -258,11 +258,8 @@ export function TerminalInput({ terminal, router, player }: IProps): React.React
}
// Select next command
if (
event.keyCode === KEY.DOWNARROW ||
(FconfSettings.ENABLE_BASH_HOTKEYS && event.keyCode === KEY.M && event.ctrlKey)
) {
if (FconfSettings.ENABLE_BASH_HOTKEYS) {
if (event.keyCode === KEY.DOWNARROW || (Settings.EnableBashHotkeys && event.keyCode === KEY.M && event.ctrlKey)) {
if (Settings.EnableBashHotkeys) {
event.preventDefault();
}
const i = terminal.commandHistoryIndex;
@ -287,7 +284,7 @@ export function TerminalInput({ terminal, router, player }: IProps): React.React
}
// Extra Bash Emulation Hotkeys, must be enabled through .fconf
if (FconfSettings.ENABLE_BASH_HOTKEYS) {
if (Settings.EnableBashHotkeys) {
if (event.keyCode === KEY.A && event.ctrlKey) {
event.preventDefault();
moveTextCursor("home");

@ -1,7 +1,7 @@
import React from "react";
import ReactDOM from "react-dom";
import { TTheme as Theme, colors } from "./ui/React/Theme";
import { TTheme as Theme, ThemeEvents, refreshTheme } from "./ui/React/Theme";
import { LoadingScreen } from "./ui/LoadingScreen";
import "./engineStyle";
@ -12,13 +12,16 @@ ReactDOM.render(
document.getElementById("mainmenu-container"),
);
// setTimeout(() => {
// colors.primary = "#fff";
// refreshTheme();
// ReactDOM.render(
// <Theme>
// <LoadingScreen />
// </Theme>,
// document.getElementById("mainmenu-container"),
// );
// }, 5000);
function rerender() {
refreshTheme();
ReactDOM.render(
<Theme>
<LoadingScreen />
</Theme>,
document.getElementById("mainmenu-container"),
);
}
(function () {
ThemeEvents.subscribe(rerender);
})();

@ -18,7 +18,6 @@ import Button from "@mui/material/Button";
import IconButton from "@mui/material/IconButton";
import SaveIcon from "@mui/icons-material/Save";
import { colors } from "./Theme";
import { Settings } from "../../Settings/Settings";
import { use } from "../Context";

@ -26,6 +26,7 @@ import UploadIcon from "@mui/icons-material/Upload";
import { FileDiagnosticModal } from "../../Diagnostic/FileDiagnosticModal";
import { dialogBoxCreate } from "../../../utils/DialogBox";
import { ConfirmationModal } from "./ConfirmationModal";
import { ThemeEditorModal } from "./ThemeEditorModal";
import { Settings } from "../../Settings/Settings";
import { save, deleteGame } from "../../db";
@ -74,9 +75,13 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
const [disableHotkeys, setDisableHotkeys] = useState(Settings.DisableHotkeys);
const [disableASCIIArt, setDisableASCIIArt] = useState(Settings.DisableASCIIArt);
const [disableTextEffects, setDisableTextEffects] = useState(Settings.DisableTextEffects);
const [enableBashHotkeys, setEnableBashHotkeys] = useState(Settings.EnableBashHotkeys);
const [enableTimestamps, setEnableTimestamps] = useState(Settings.EnableTimestamps);
const [locale, setLocale] = useState(Settings.Locale);
const [diagnosticOpen, setDiagnosticOpen] = useState(false);
const [deleteGameOpen, setDeleteOpen] = useState(false);
const [themeEditorOpen, setThemeEditorOpen] = useState(false);
function handleExecTimeChange(event: any, newValue: number | number[]): void {
setExecTime(newValue as number);
@ -152,6 +157,15 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
Settings.Locale = event.target.value as string;
}
function handleEnableBashHotkeysChange(event: React.ChangeEvent<HTMLInputElement>): void {
setEnableBashHotkeys(event.target.checked);
Settings.EnableBashHotkeys = event.target.checked;
}
function handleEnableTimestampsChange(event: React.ChangeEvent<HTMLInputElement>): void {
setEnableTimestamps(event.target.checked);
Settings.EnableTimestamps = event.target.checked;
}
function startImport(): void {
if (!window.File || !window.FileReader || !window.FileList || !window.Blob) return;
const ii = importInput.current;
@ -454,6 +468,43 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
}
/>
</ListItem>
<ListItem>
<FormControlLabel
control={<Switch checked={enableBashHotkeys} onChange={handleEnableBashHotkeysChange} />}
label={
<Tooltip
title={
<Typography>
Improved Bash emulation mode. Setting this to 1 enables several new Terminal shortcuts and
features that more closely resemble a real Bash-style shell. Note that when this mode is
enabled, the default browser shortcuts are overriden by the new Bash shortcuts.
</Typography>
}
>
<Typography>Enable bash hotkeys</Typography>
</Tooltip>
}
/>
</ListItem>
<ListItem>
<FormControlLabel
control={<Switch checked={enableTimestamps} onChange={handleEnableTimestampsChange} />}
label={
<Tooltip
title={
<Typography>
Terminal commands and log entries will be timestamped. The timestamp will have the format: M/D
h:m
</Typography>
}
>
<Typography>Enable timestamps</Typography>
</Tooltip>
}
/>
</ListItem>
<ListItem>
<Tooltip title={<Typography>Sets the locale for displaying numbers.</Typography>}>
<Typography>Locale&nbsp;</Typography>
@ -551,6 +602,7 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
>
<Button onClick={() => setDiagnosticOpen(true)}>Diagnose files</Button>
</Tooltip>
<Button onClick={() => setThemeEditorOpen(true)}>Theme editor</Button>
</Box>
<Box>
<Link href="https://github.com/danielyxie/bitburner/issues/new" target="_blank">
@ -583,6 +635,7 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
onClose={() => setDeleteOpen(false)}
confirmationText={"Really delete your game? (It's permanent!)"}
/>
<ThemeEditorModal open={themeEditorOpen} onClose={() => setThemeEditorOpen(false)} />
</div>
);
}

@ -5,23 +5,25 @@
import React from "react";
import { Select as MuiSelect, SelectProps } from "@mui/material";
import makeStyles from '@mui/styles/makeStyles';
import { colors } from "./Theme";
import { Theme } from "@mui/material/styles";
import makeStyles from "@mui/styles/makeStyles";
import createStyles from "@mui/styles/createStyles";
const useStyles = makeStyles({
// Tries to emulate StdButton in buttons.scss
root: {
backgroundColor: colors.well,
color: colors.primarydark,
margin: "5px",
padding: "3px 5px",
"&:after": {
backgroundColor: colors.well,
const useStyles = makeStyles((theme: Theme) =>
createStyles({
root: {
backgroundColor: theme.palette.background.paper,
color: theme.palette.primary.dark,
margin: "5px",
padding: "3px 5px",
"&:after": {
backgroundColor: theme.palette.background.paper,
},
borderRadius: 0,
},
borderRadius: 0,
},
});
}),
);
export const Select: React.FC<SelectProps> = (props: SelectProps) => {
return (

@ -1,5 +1,9 @@
import React from "react";
import { createTheme, ThemeProvider, Theme, StyledEngineProvider } from "@mui/material/styles";
import { EventEmitter } from "../../utils/EventEmitter";
import { Settings } from "../../Settings/Settings";
export const ThemeEvents = new EventEmitter<[]>();
declare module "@mui/material/styles" {
interface Theme {
@ -25,83 +29,49 @@ declare module "@mui/material/styles" {
};
}
}
export let colors = {
primarylight: "#0f0",
primary: "#0c0",
primarydark: "#090",
errorlight: "#f00",
error: "#c00",
errordark: "#900",
secondarylight: "#AAA",
secondary: "#888",
secondarydark: "#666",
warninglight: "#ff0",
warning: "#cc0",
warningdark: "#990",
infolight: "#69f",
info: "#36c",
infodark: "#039",
welllight: "#444",
well: "#222",
white: "#fff",
black: "#000",
hp: "#dd3434",
money: "#ffd700",
hack: "#adff2f",
combat: "#faffdf",
cha: "#a671d1",
int: "#6495ed",
rep: "#faffdf",
};
let theme: Theme;
function refreshTheme() {
export function refreshTheme() {
theme = createTheme({
colors: {
hp: "#dd3434",
money: "#ffd700",
hack: "#adff2f",
combat: "#faffdf",
cha: "#a671d1",
int: "#6495ed",
rep: "#faffdf",
hp: Settings.theme.hp,
money: Settings.theme.money,
hack: Settings.theme.hack,
combat: Settings.theme.combat,
cha: Settings.theme.cha,
int: Settings.theme.int,
rep: Settings.theme.rep,
},
palette: {
primary: {
light: colors.primarylight,
main: colors.primary,
dark: colors.primarydark,
light: Settings.theme.primarylight,
main: Settings.theme.primary,
dark: Settings.theme.primarydark,
},
secondary: {
light: colors.secondarylight,
main: colors.secondary,
dark: colors.secondarydark,
light: Settings.theme.secondarylight,
main: Settings.theme.secondary,
dark: Settings.theme.secondarydark,
},
error: {
light: colors.errorlight,
main: colors.error,
dark: colors.errordark,
light: Settings.theme.errorlight,
main: Settings.theme.error,
dark: Settings.theme.errordark,
},
info: {
light: colors.infolight,
main: colors.info,
dark: colors.infodark,
light: Settings.theme.infolight,
main: Settings.theme.info,
dark: Settings.theme.infodark,
},
warning: {
light: colors.warninglight,
main: colors.warning,
dark: colors.warningdark,
light: Settings.theme.warninglight,
main: Settings.theme.warning,
dark: Settings.theme.warningdark,
},
background: {
default: colors.black,
paper: colors.well,
default: Settings.theme.black,
paper: Settings.theme.well,
},
},
typography: {
@ -114,13 +84,13 @@ function refreshTheme() {
MuiInputBase: {
styleOverrides: {
root: {
backgroundColor: colors.well,
color: colors.primary,
backgroundColor: Settings.theme.well,
color: Settings.theme.primary,
},
input: {
"&::placeholder": {
userSelect: "none",
color: colors.primarydark,
color: Settings.theme.primarydark,
},
},
},
@ -129,18 +99,18 @@ function refreshTheme() {
MuiInput: {
styleOverrides: {
root: {
backgroundColor: colors.well,
backgroundColor: Settings.theme.well,
borderBottomColor: "#fff",
},
underline: {
"&:hover": {
borderBottomColor: colors.primarydark,
borderBottomColor: Settings.theme.primarydark,
},
"&:before": {
borderBottomColor: colors.primary,
borderBottomColor: Settings.theme.primary,
},
"&:after": {
borderBottomColor: colors.primarylight,
borderBottomColor: Settings.theme.primarylight,
},
},
},
@ -149,10 +119,10 @@ function refreshTheme() {
MuiInputLabel: {
styleOverrides: {
root: {
color: colors.primarydark, // why is this switched?
color: Settings.theme.primarydark, // why is this switched?
userSelect: "none",
"&:before": {
color: colors.primarylight,
color: Settings.theme.primarylight,
},
},
},
@ -161,10 +131,10 @@ function refreshTheme() {
styleOverrides: {
root: {
backgroundColor: "#333",
border: "1px solid " + colors.well,
// color: colors.primary,
border: "1px solid " + Settings.theme.well,
// color: Settings.theme.primary,
"&:hover": {
backgroundColor: colors.black,
backgroundColor: Settings.theme.black,
},
borderRadius: 0,
@ -174,21 +144,21 @@ function refreshTheme() {
MuiSelect: {
styleOverrides: {
icon: {
color: colors.primary,
color: Settings.theme.primary,
},
},
},
MuiMenu: {
styleOverrides: {
list: {
backgroundColor: colors.well,
backgroundColor: Settings.theme.well,
},
},
},
MuiMenuItem: {
styleOverrides: {
root: {
color: colors.primary,
color: Settings.theme.primary,
},
},
},
@ -202,14 +172,14 @@ function refreshTheme() {
MuiAccordionDetails: {
styleOverrides: {
root: {
backgroundColor: colors.black,
backgroundColor: Settings.theme.black,
},
},
},
MuiIconButton: {
styleOverrides: {
root: {
color: colors.primary,
color: Settings.theme.primary,
},
},
},
@ -217,8 +187,8 @@ function refreshTheme() {
styleOverrides: {
tooltip: {
fontSize: "1em",
color: colors.primary,
backgroundColor: colors.well,
color: Settings.theme.primary,
backgroundColor: Settings.theme.well,
borderRadius: 0,
border: "2px solid white",
maxWidth: "100vh",
@ -228,8 +198,8 @@ function refreshTheme() {
MuiSlider: {
styleOverrides: {
valueLabel: {
color: colors.primary,
backgroundColor: colors.well,
color: Settings.theme.primary,
backgroundColor: Settings.theme.well,
},
},
},
@ -241,34 +211,34 @@ function refreshTheme() {
display: "none",
},
scrollbarWidth: "none", // firefox
backgroundColor: colors.black,
backgroundColor: Settings.theme.black,
},
paperAnchorDockedLeft: {
borderRight: "1px solid " + colors.welllight,
borderRight: "1px solid " + Settings.theme.welllight,
},
},
},
MuiDivider: {
styleOverrides: {
root: {
backgroundColor: colors.welllight,
backgroundColor: Settings.theme.welllight,
},
},
},
MuiFormControlLabel: {
styleOverrides: {
root: {
color: colors.primary,
color: Settings.theme.primary,
},
},
},
MuiSwitch: {
styleOverrides: {
switchBase: {
color: colors.primarydark,
color: Settings.theme.primarydark,
},
track: {
backgroundColor: colors.welllight,
backgroundColor: Settings.theme.welllight,
},
},
},
@ -276,21 +246,20 @@ function refreshTheme() {
styleOverrides: {
root: {
borderRadius: 0,
backgroundColor: colors.black,
border: "1px solid " + colors.welllight,
backgroundColor: Settings.theme.black,
border: "1px solid " + Settings.theme.welllight,
},
},
},
MuiTablePagination: {
styleOverrides: {
select: {
color: colors.primary,
color: Settings.theme.primary,
},
},
},
},
});
console.log("refreshed");
}
refreshTheme();

@ -0,0 +1,115 @@
import React, { useState } from "react";
import { Modal } from "./Modal";
import Button from "@mui/material/Button";
import Typography from "@mui/material/Typography";
import TextField from "@mui/material/TextField";
import DoneIcon from "@mui/icons-material/Done";
import IconButton from "@mui/material/IconButton";
import ReplyIcon from "@mui/icons-material/Reply";
import { ThemeEvents } from "./Theme";
import { Settings, defaultSettings } from "../../Settings/Settings";
interface IProps {
open: boolean;
onClose: () => void;
}
function ColorEditor({ name }: { name: string }): React.ReactElement {
const [color, setColor] = useState(Settings.theme[name]);
if (color === undefined) return <></>;
const valid = color.match(/#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})/g);
function set(): void {
if (!valid) return;
Settings.theme[name] = color;
ThemeEvents.emit();
}
function revert(): void {
Settings.theme[name] = defaultSettings.theme[name];
setColor(defaultSettings.theme[name]);
ThemeEvents.emit();
}
function onChange(event: React.ChangeEvent<HTMLInputElement>): void {
setColor(event.target.value);
}
return (
<>
<TextField
sx={{ mx: 1 }}
label={name}
value={color}
onChange={onChange}
variant="standard"
InputProps={{
endAdornment: (
<>
<IconButton onClick={set} disabled={!valid}>
<DoneIcon color={valid ? "primary" : "error"} />
</IconButton>
<IconButton onClick={revert}>
<ReplyIcon color="primary" />
</IconButton>
</>
),
}}
/>
</>
);
}
export function ThemeEditorModal(props: IProps): React.ReactElement {
return (
<Modal open={props.open} onClose={props.onClose}>
<Button color="primary">primary</Button>
<Button color="secondary">secondary</Button>
<Button color="warning">warning</Button>
<Button color="info">info</Button>
<Button color="error">error</Button>
<Typography color="primary">primary</Typography>
<Typography color="secondary">secondary</Typography>
<Typography color="warning">warning</Typography>
<Typography color="info">info</Typography>
<Typography color="error">error</Typography>
<br />
<ColorEditor name="primarylight" />
<ColorEditor name="primary" />
<ColorEditor name="primarydark" />
<br />
<ColorEditor name="errorlight" />
<ColorEditor name="error" />
<ColorEditor name="errordark" />
<br />
<ColorEditor name="secondarylight" />
<ColorEditor name="secondary" />
<ColorEditor name="secondarydark" />
<br />
<ColorEditor name="warninglight" />
<ColorEditor name="warning" />
<ColorEditor name="warningdark" />
<br />
<ColorEditor name="infolight" />
<ColorEditor name="info" />
<ColorEditor name="infodark" />
<br />
<ColorEditor name="welllight" />
<ColorEditor name="well" />
<ColorEditor name="white" />
<ColorEditor name="black" />
<br />
<ColorEditor name="hp" />
<ColorEditor name="money" />
<ColorEditor name="hack" />
<ColorEditor name="combat" />
<ColorEditor name="cha" />
<ColorEditor name="int" />
<ColorEditor name="rep" />
</Modal>
);
}