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

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

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

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

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

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

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

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

1
src/RedPill.d.ts vendored

@ -1,2 +1,3 @@
export declare let redPillFlag: boolean; export declare let redPillFlag: boolean;
export declare function enterBitNode(router: IRouter, flume: boolean, destroyedBitNode: number, newBitNode: number); 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"; import { dialogBoxCreate } from "../utils/DialogBox";
let redPillFlag = false; export let redPillFlag = false;
export function setRedPillFlag(b) {
redPillFlag = b;
}
function giveSourceFile(bitNodeNumber) { function giveSourceFile(bitNodeNumber) {
var sourceFileKey = "SourceFile" + bitNodeNumber.toString(); var sourceFileKey = "SourceFile" + bitNodeNumber.toString();
@ -81,5 +85,3 @@ export function enterBitNode(router, flume, destroyedBitNode, newBitNode) {
} }
prestigeSourceFile(flume); prestigeSourceFile(flume);
} }
export { redPillFlag };

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

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

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

@ -38,6 +38,16 @@ interface IDefaultSettings {
*/ */
DisableTextEffects: boolean; DisableTextEffects: boolean;
/**
* Enable bash hotkeys
*/
EnableBashHotkeys: boolean;
/**
* Enable timestamps
*/
EnableTimestamps: boolean;
/** /**
* Locale used for display numbers * 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. * Whether the user should be displayed a popup message when his Bladeburner actions are cancelled.
*/ */
SuppressBladeburnerPopup: boolean; 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; MonacoInsertSpaces: boolean;
} }
const defaultSettings: IDefaultSettings = { export const defaultSettings: IDefaultSettings = {
ActiveScriptsServerPageSize: 10, ActiveScriptsServerPageSize: 10,
ActiveScriptsScriptPageSize: 10, ActiveScriptsScriptPageSize: 10,
AutosaveInterval: 60, AutosaveInterval: 60,
@ -116,6 +159,8 @@ const defaultSettings: IDefaultSettings = {
DisableASCIIArt: false, DisableASCIIArt: false,
DisableHotkeys: false, DisableHotkeys: false,
DisableTextEffects: false, DisableTextEffects: false,
EnableBashHotkeys: false,
EnableTimestamps: false,
Locale: "en", Locale: "en",
MaxLogCapacity: 50, MaxLogCapacity: 50,
MaxPortCapacity: 50, MaxPortCapacity: 50,
@ -126,6 +171,35 @@ const defaultSettings: IDefaultSettings = {
SuppressMessages: false, SuppressMessages: false,
SuppressTravelConfirmation: false, SuppressTravelConfirmation: false,
SuppressBladeburnerPopup: 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, DisableASCIIArt: defaultSettings.DisableASCIIArt,
DisableHotkeys: defaultSettings.DisableHotkeys, DisableHotkeys: defaultSettings.DisableHotkeys,
DisableTextEffects: defaultSettings.DisableTextEffects, DisableTextEffects: defaultSettings.DisableTextEffects,
EnableBashHotkeys: defaultSettings.EnableBashHotkeys,
EnableTimestamps: defaultSettings.EnableTimestamps,
Locale: "en", Locale: "en",
MaxLogCapacity: defaultSettings.MaxLogCapacity, MaxLogCapacity: defaultSettings.MaxLogCapacity,
MaxPortCapacity: defaultSettings.MaxPortCapacity, MaxPortCapacity: defaultSettings.MaxPortCapacity,
@ -154,6 +230,35 @@ export const Settings: ISettings & ISelfInitializer & ISelfLoading = {
SuppressBladeburnerPopup: defaultSettings.SuppressBladeburnerPopup, SuppressBladeburnerPopup: defaultSettings.SuppressBladeburnerPopup,
MonacoTheme: "vs-dark", MonacoTheme: "vs-dark",
MonacoInsertSpaces: false, 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() { init() {
Object.assign(Settings, defaultSettings); Object.assign(Settings, defaultSettings);
}, },

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

@ -2,6 +2,8 @@ import { TextFile } from "../TextFile";
import { Script } from "../Script/Script"; import { Script } from "../Script/Script";
import { IPlayer } from "../PersonObjects/IPlayer"; import { IPlayer } from "../PersonObjects/IPlayer";
import { IRouter } from "../ui/Router"; import { IRouter } from "../ui/Router";
import { Settings } from "../Settings/Settings";
import { getTimestamp } from "../../utils/helpers/getTimestamp";
export class Output { export class Output {
text: string; text: string;
@ -10,6 +12,7 @@ export class Output {
text: string, text: string,
color: "inherit" | "initial" | "primary" | "secondary" | "error" | "textPrimary" | "textSecondary" | undefined, color: "inherit" | "initial" | "primary" | "secondary" | "error" | "textPrimary" | "textSecondary" | undefined,
) { ) {
if (Settings.EnableTimestamps) text = "[" + getTimestamp() + "] " + text;
this.text = text; this.text = text;
this.color = color; this.color = color;
} }
@ -18,6 +21,7 @@ export class Output {
export class Link { export class Link {
hostname: string; hostname: string;
constructor(hostname: string) { constructor(hostname: string) {
if (Settings.EnableTimestamps) hostname = "[" + getTimestamp() + "] " + hostname;
this.hostname = 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 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()); const newPath = evaluateDirectoryPath(args[0] + "", terminal.cwd());
prefix = newPath ? newPath : ""; prefix = newPath ? newPath : "";
if (prefix != null) { if (prefix != null) {

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

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

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

@ -26,6 +26,7 @@ import UploadIcon from "@mui/icons-material/Upload";
import { FileDiagnosticModal } from "../../Diagnostic/FileDiagnosticModal"; import { FileDiagnosticModal } from "../../Diagnostic/FileDiagnosticModal";
import { dialogBoxCreate } from "../../../utils/DialogBox"; import { dialogBoxCreate } from "../../../utils/DialogBox";
import { ConfirmationModal } from "./ConfirmationModal"; import { ConfirmationModal } from "./ConfirmationModal";
import { ThemeEditorModal } from "./ThemeEditorModal";
import { Settings } from "../../Settings/Settings"; import { Settings } from "../../Settings/Settings";
import { save, deleteGame } from "../../db"; import { save, deleteGame } from "../../db";
@ -74,9 +75,13 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
const [disableHotkeys, setDisableHotkeys] = useState(Settings.DisableHotkeys); const [disableHotkeys, setDisableHotkeys] = useState(Settings.DisableHotkeys);
const [disableASCIIArt, setDisableASCIIArt] = useState(Settings.DisableASCIIArt); const [disableASCIIArt, setDisableASCIIArt] = useState(Settings.DisableASCIIArt);
const [disableTextEffects, setDisableTextEffects] = useState(Settings.DisableTextEffects); 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 [locale, setLocale] = useState(Settings.Locale);
const [diagnosticOpen, setDiagnosticOpen] = useState(false); const [diagnosticOpen, setDiagnosticOpen] = useState(false);
const [deleteGameOpen, setDeleteOpen] = useState(false); const [deleteGameOpen, setDeleteOpen] = useState(false);
const [themeEditorOpen, setThemeEditorOpen] = useState(false);
function handleExecTimeChange(event: any, newValue: number | number[]): void { function handleExecTimeChange(event: any, newValue: number | number[]): void {
setExecTime(newValue as number); setExecTime(newValue as number);
@ -152,6 +157,15 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
Settings.Locale = event.target.value as string; 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 { function startImport(): void {
if (!window.File || !window.FileReader || !window.FileList || !window.Blob) return; if (!window.File || !window.FileReader || !window.FileList || !window.Blob) return;
const ii = importInput.current; const ii = importInput.current;
@ -454,6 +468,43 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
} }
/> />
</ListItem> </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> <ListItem>
<Tooltip title={<Typography>Sets the locale for displaying numbers.</Typography>}> <Tooltip title={<Typography>Sets the locale for displaying numbers.</Typography>}>
<Typography>Locale&nbsp;</Typography> <Typography>Locale&nbsp;</Typography>
@ -551,6 +602,7 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
> >
<Button onClick={() => setDiagnosticOpen(true)}>Diagnose files</Button> <Button onClick={() => setDiagnosticOpen(true)}>Diagnose files</Button>
</Tooltip> </Tooltip>
<Button onClick={() => setThemeEditorOpen(true)}>Theme editor</Button>
</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">
@ -583,6 +635,7 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
onClose={() => setDeleteOpen(false)} onClose={() => setDeleteOpen(false)}
confirmationText={"Really delete your game? (It's permanent!)"} confirmationText={"Really delete your game? (It's permanent!)"}
/> />
<ThemeEditorModal open={themeEditorOpen} onClose={() => setThemeEditorOpen(false)} />
</div> </div>
); );
} }

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

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