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
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

View File

@ -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;

View File

@ -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()}

View File

@ -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>
);
}

View File

@ -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}>

View File

@ -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;
}

View File

@ -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;

View File

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

View File

@ -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
View File

@ -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;

View File

@ -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 };

View File

@ -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()) {

View File

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

View File

@ -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;

View File

@ -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);
},

View File

@ -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();

View File

@ -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;
}
}

View File

@ -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) {

View File

@ -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");

View File

@ -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);
})();

View File

@ -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";

View File

@ -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>
);
}

View File

@ -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
const useStyles = makeStyles((theme: Theme) =>
createStyles({
root: {
backgroundColor: colors.well,
color: colors.primarydark,
backgroundColor: theme.palette.background.paper,
color: theme.palette.primary.dark,
margin: "5px",
padding: "3px 5px",
"&:after": {
backgroundColor: colors.well,
backgroundColor: theme.palette.background.paper,
},
borderRadius: 0,
},
});
}),
);
export const Select: React.FC<SelectProps> = (props: SelectProps) => {
return (

View File

@ -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();

View File

@ -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>
);
}