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 {
- +
); } 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