diff --git a/src/ScriptEditor/ui/OptionsModal.tsx b/src/ScriptEditor/ui/OptionsModal.tsx
index 70fb9e361..65e12db1f 100644
--- a/src/ScriptEditor/ui/OptionsModal.tsx
+++ b/src/ScriptEditor/ui/OptionsModal.tsx
@@ -9,6 +9,10 @@ import Select from "@mui/material/Select";
import Switch from "@mui/material/Switch";
import MenuItem from "@mui/material/MenuItem";
import TextField from "@mui/material/TextField";
+import EditIcon from '@mui/icons-material/Edit';
+import SaveIcon from '@mui/icons-material/Save';
+
+import { ThemeEditorModal } from "./ThemeEditorModal";
interface IProps {
options: Options;
@@ -23,6 +27,7 @@ export function OptionsModal(props: IProps): React.ReactElement {
const [fontSize, setFontSize] = useState(props.options.fontSize);
const [wordWrap, setWordWrap] = useState(props.options.wordWrap);
const [vim, setVim] = useState(props.options.vim);
+ const [themeEditorOpen, setThemeEditorOpen] = useState(false);
function save(): void {
props.save({
@@ -43,6 +48,10 @@ export function OptionsModal(props: IProps): React.ReactElement {
return (
+ setThemeEditorOpen(false)}
+ />
Theme:
+
@@ -80,7 +93,7 @@ export function OptionsModal(props: IProps): React.ReactElement {
-
+ }>Save
);
}
diff --git a/src/ScriptEditor/ui/ScriptEditorRoot.tsx b/src/ScriptEditor/ui/ScriptEditorRoot.tsx
index a53823671..f62c85f87 100644
--- a/src/ScriptEditor/ui/ScriptEditorRoot.tsx
+++ b/src/ScriptEditor/ui/ScriptEditorRoot.tsx
@@ -25,7 +25,7 @@ import { Settings } from "../../Settings/Settings";
import { iTutorialNextStep, ITutorial, iTutorialSteps } from "../../InteractiveTutorial";
import { debounce } from "lodash";
import { saveObject } from "../../SaveObject";
-import { loadThemes } from "./themes";
+import { loadThemes, makeTheme } from "./themes";
import { GetServer } from "../../Server/AllServers";
import Button from "@mui/material/Button";
@@ -330,6 +330,7 @@ export function Root(props: IProps): React.ReactElement {
monaco.languages.typescript.javascriptDefaults.addExtraLib(source, "netscript.d.ts");
monaco.languages.typescript.typescriptDefaults.addExtraLib(source, "netscript.d.ts");
loadThemes(monaco);
+ monaco.editor.defineTheme("customTheme", makeTheme(Settings.EditorTheme));
}
// When the editor is mounted
@@ -902,6 +903,7 @@ export function Root(props: IProps): React.ReactElement {
vim: Settings.MonacoVim,
}}
save={(options: Options) => {
+ monacoRef.current?.editor.defineTheme("customTheme", makeTheme(Settings.EditorTheme));
setOptions(options);
Settings.MonacoTheme = options.theme;
Settings.MonacoInsertSpaces = options.insertSpaces;
diff --git a/src/ScriptEditor/ui/ThemeEditorModal.tsx b/src/ScriptEditor/ui/ThemeEditorModal.tsx
new file mode 100644
index 000000000..c3f105441
--- /dev/null
+++ b/src/ScriptEditor/ui/ThemeEditorModal.tsx
@@ -0,0 +1,268 @@
+import React, { useState } from "react";
+import { Modal } from "../../ui/React/Modal";
+import { defaultMonacoTheme } from "./themes";
+
+import Typography from "@mui/material/Typography";
+import Button from "@mui/material/Button";
+import Box from "@mui/material/Box";
+import Tooltip from "@mui/material/Tooltip";
+import TextField from "@mui/material/TextField";
+import IconButton from "@mui/material/IconButton";
+import ReplyIcon from "@mui/icons-material/Reply";
+import HistoryIcon from '@mui/icons-material/History';
+import SaveIcon from '@mui/icons-material/Save';
+
+import { Settings } from "../../Settings/Settings";
+import { OptionSwitch } from "../../ui/React/OptionSwitch";
+import _ from "lodash";
+
+import { Color, ColorPicker } from "material-ui-color";
+
+interface IProps {
+ onClose: () => void;
+ open: boolean;
+}
+
+interface IColorEditorProps {
+ label: string;
+ themePath: string;
+ color: string | undefined;
+ onColorChange: (name: string, value: string) => void;
+ defaultColor: string;
+}
+
+// Slightly tweaked version of the same function found in game options
+function ColorEditor({ label, themePath, onColorChange, color, defaultColor }: IColorEditorProps): React.ReactElement {
+ if (color === undefined) {
+ console.error(`color ${themePath} was undefined, reverting to default`);
+ color = defaultColor;
+ }
+
+ return (
+ <>
+
+
+
+ onColorChange(themePath, newColor.hex)}
+ disableAlpha
+ />
+ >
+ ),
+ endAdornment: (
+ <>
+ onColorChange(themePath, defaultColor)}>
+
+
+ >
+ ),
+ }}
+ />
+
+
+ >
+ );
+}
+
+export function ThemeEditorModal(props: IProps): React.ReactElement {
+ const setRerender = useState(false)[1];
+ function rerender(): void {
+ setRerender((o) => !o);
+ }
+
+ // Need to deep copy the object since it has nested attributes
+ const [themeCopy, setThemeCopy] = useState(JSON.parse(JSON.stringify(Settings.EditorTheme)));
+
+ function onColorChange(name: string, value: string): void {
+ setThemeCopy(_.set(themeCopy, name, value));
+ rerender();
+ }
+
+ function onThemeChange(event: React.ChangeEvent): void {
+ try {
+ const importedTheme = JSON.parse(event.target.value);
+ if (typeof importedTheme !== "object") return;
+ setThemeCopy(importedTheme);
+ } catch (err) {
+ // ignore
+ }
+ }
+
+ return (
+
+ Customize Editor theme
+ Hover over input boxes for more information
+ {
+ setThemeCopy(_.set(themeCopy, "base", val ? "vs" : "vs-dark"));
+ rerender();
+ }}
+ text="Use light theme as base"
+ tooltip={
+ <>
+ If enabled, the vs light theme will be used as the
+ theme base, otherwise, vs-dark will be used.
+ >
+ }
+ />
+
+
+ UI
+
+
+
+
+
+
+
+
+
+ Syntax
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
\ No newline at end of file