mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-11-10 09:43:54 +01:00
Implement Monaco theme editor
This commit is contained in:
parent
b3f37b2583
commit
16cf227498
@ -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 (
|
||||
<Modal open={props.open} onClose={props.onClose}>
|
||||
<ThemeEditorModal
|
||||
open={themeEditorOpen}
|
||||
onClose={() => setThemeEditorOpen(false)}
|
||||
/>
|
||||
<Box display="flex" flexDirection="row" alignItems="center">
|
||||
<Typography>Theme: </Typography>
|
||||
<Select onChange={(event) => setTheme(event.target.value)} value={theme}>
|
||||
@ -53,7 +62,11 @@ export function OptionsModal(props: IProps): React.ReactElement {
|
||||
<MenuItem value="light">light</MenuItem>
|
||||
<MenuItem value="dracula">dracula</MenuItem>
|
||||
<MenuItem value="one-dark">one-dark</MenuItem>
|
||||
<MenuItem value="customTheme">Custom theme</MenuItem>
|
||||
</Select>
|
||||
<Button onClick={() => setThemeEditorOpen(true)} sx={{ mx: 1 }} startIcon={<EditIcon />}>
|
||||
Edit custom theme
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<Box display="flex" flexDirection="row" alignItems="center">
|
||||
@ -80,7 +93,7 @@ export function OptionsModal(props: IProps): React.ReactElement {
|
||||
<TextField type="number" label="Font size" value={fontSize} onChange={onFontChange} />
|
||||
</Box>
|
||||
<br />
|
||||
<Button onClick={save}>Save</Button>
|
||||
<Button onClick={save} startIcon={<SaveIcon />}>Save</Button>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
@ -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";
|
||||
@ -362,6 +362,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
|
||||
@ -1002,6 +1003,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;
|
||||
|
268
src/ScriptEditor/ui/ThemeEditorModal.tsx
Normal file
268
src/ScriptEditor/ui/ThemeEditorModal.tsx
Normal file
@ -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 (
|
||||
<>
|
||||
<Tooltip title={label}>
|
||||
<span>
|
||||
<TextField
|
||||
label={themePath}
|
||||
value={"#" + color}
|
||||
sx={{ display: "block", my: 1 }}
|
||||
InputProps={{
|
||||
startAdornment: (
|
||||
<>
|
||||
<ColorPicker
|
||||
hideTextfield
|
||||
deferred
|
||||
value={"#" + color}
|
||||
onChange={(newColor: Color) => onColorChange(themePath, newColor.hex)}
|
||||
disableAlpha
|
||||
/>
|
||||
</>
|
||||
),
|
||||
endAdornment: (
|
||||
<>
|
||||
<IconButton onClick={() => onColorChange(themePath, defaultColor)}>
|
||||
<ReplyIcon color="primary" />
|
||||
</IconButton>
|
||||
</>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
</Tooltip>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
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<HTMLInputElement>): void {
|
||||
try {
|
||||
const importedTheme = JSON.parse(event.target.value);
|
||||
if (typeof importedTheme !== "object") return;
|
||||
setThemeCopy(importedTheme);
|
||||
} catch (err) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal open={props.open} onClose={props.onClose}>
|
||||
<Typography variant="h4">Customize Editor theme</Typography>
|
||||
<Typography>Hover over input boxes for more information</Typography>
|
||||
<OptionSwitch
|
||||
checked={themeCopy.base === "vs"}
|
||||
onChange={(val) => {
|
||||
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.
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Box display="grid" sx={{ gridTemplateColumns: "1fr 1fr", width: "fit-content", gap: 1 }}>
|
||||
<Box>
|
||||
<Typography variant="h6">UI</Typography>
|
||||
<ColorEditor
|
||||
label="Background color"
|
||||
themePath="common.bg"
|
||||
onColorChange={onColorChange}
|
||||
color={themeCopy.common.bg}
|
||||
defaultColor={defaultMonacoTheme.common.bg}
|
||||
/>
|
||||
<ColorEditor
|
||||
label="Current line and minimap background color"
|
||||
themePath="ui.line"
|
||||
onColorChange={onColorChange}
|
||||
color={themeCopy.ui.line}
|
||||
defaultColor={defaultMonacoTheme.ui.line}
|
||||
/>
|
||||
<ColorEditor
|
||||
label="Base text color"
|
||||
themePath="common.fg"
|
||||
onColorChange={onColorChange}
|
||||
color={themeCopy.common.fg}
|
||||
defaultColor={defaultMonacoTheme.common.fg}
|
||||
/>
|
||||
<ColorEditor
|
||||
label="Popup background color"
|
||||
themePath="ui.panel.bg"
|
||||
onColorChange={onColorChange}
|
||||
color={themeCopy.ui.panel.bg}
|
||||
defaultColor={defaultMonacoTheme.ui.panel.bg}
|
||||
/>
|
||||
<ColorEditor
|
||||
label="Background color for selected item in popup"
|
||||
themePath="ui.panel.selected"
|
||||
onColorChange={onColorChange}
|
||||
color={themeCopy.ui.panel.selected}
|
||||
defaultColor={defaultMonacoTheme.ui.panel.selected}
|
||||
/>
|
||||
<ColorEditor
|
||||
label="Popup border color"
|
||||
themePath="ui.panel.border"
|
||||
onColorChange={onColorChange}
|
||||
color={themeCopy.ui.panel.border}
|
||||
defaultColor={defaultMonacoTheme.ui.panel.border}
|
||||
/>
|
||||
<ColorEditor
|
||||
label="Background color of highlighted text"
|
||||
themePath="ui.selection.bg"
|
||||
onColorChange={onColorChange}
|
||||
color={themeCopy.ui.selection.bg}
|
||||
defaultColor={defaultMonacoTheme.ui.selection.bg}
|
||||
/>
|
||||
</Box>
|
||||
<Box>
|
||||
<Typography variant="h6">Syntax</Typography>
|
||||
<ColorEditor
|
||||
label="Numbers, func names, and other key vars"
|
||||
themePath="common.accent"
|
||||
onColorChange={onColorChange}
|
||||
color={themeCopy.common.accent}
|
||||
defaultColor={defaultMonacoTheme.common.accent}
|
||||
/>
|
||||
<ColorEditor
|
||||
label="Keywords"
|
||||
themePath="syntax.keyword"
|
||||
onColorChange={onColorChange}
|
||||
color={themeCopy.syntax.keyword}
|
||||
defaultColor={defaultMonacoTheme.syntax.keyword}
|
||||
/>
|
||||
<ColorEditor
|
||||
label="Strings"
|
||||
themePath="syntax.string"
|
||||
onColorChange={onColorChange}
|
||||
color={themeCopy.syntax.string}
|
||||
defaultColor={defaultMonacoTheme.syntax.string}
|
||||
/>
|
||||
<ColorEditor
|
||||
label="Regexp literals as well as escapes within strings"
|
||||
themePath="syntax.regexp"
|
||||
onColorChange={onColorChange}
|
||||
color={themeCopy.syntax.regexp}
|
||||
defaultColor={defaultMonacoTheme.syntax.regexp}
|
||||
/>
|
||||
<ColorEditor
|
||||
label="Constants"
|
||||
themePath="syntax.constant"
|
||||
onColorChange={onColorChange}
|
||||
color={themeCopy.syntax.constant}
|
||||
defaultColor={defaultMonacoTheme.syntax.constant}
|
||||
/>
|
||||
<ColorEditor
|
||||
label="Entities"
|
||||
themePath="syntax.entity"
|
||||
onColorChange={onColorChange}
|
||||
color={themeCopy.syntax.entity}
|
||||
defaultColor={defaultMonacoTheme.syntax.entity}
|
||||
/>
|
||||
<ColorEditor
|
||||
label="'this', 'ns', types, and tags"
|
||||
themePath="syntax.tag"
|
||||
onColorChange={onColorChange}
|
||||
color={themeCopy.syntax.tag}
|
||||
defaultColor={defaultMonacoTheme.syntax.tag}
|
||||
/>
|
||||
<ColorEditor
|
||||
label="Netscript functions and constructors"
|
||||
themePath="syntax.markup"
|
||||
onColorChange={onColorChange}
|
||||
color={themeCopy.syntax.markup}
|
||||
defaultColor={defaultMonacoTheme.syntax.markup}
|
||||
/>
|
||||
<ColorEditor
|
||||
label="Errors"
|
||||
themePath="syntax.error"
|
||||
onColorChange={onColorChange}
|
||||
color={themeCopy.syntax.error}
|
||||
defaultColor={defaultMonacoTheme.syntax.error}
|
||||
/>
|
||||
<ColorEditor
|
||||
label="Comments"
|
||||
themePath="syntax.comment"
|
||||
onColorChange={onColorChange}
|
||||
color={themeCopy.syntax.comment}
|
||||
defaultColor={defaultMonacoTheme.syntax.comment}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
<TextField
|
||||
multiline
|
||||
fullWidth
|
||||
maxRows={10}
|
||||
label={"import / export theme"}
|
||||
value={JSON.stringify(themeCopy, undefined, 2)}
|
||||
onChange={onThemeChange}
|
||||
/>
|
||||
<Box sx={{ my: 1 }}>
|
||||
<Button onClick={() => {
|
||||
Settings.EditorTheme = { ...themeCopy };
|
||||
props.onClose()
|
||||
}} startIcon={<SaveIcon />}>Save</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setThemeCopy(defaultMonacoTheme);
|
||||
rerender();
|
||||
}}
|
||||
startIcon={<HistoryIcon />}
|
||||
>Reset to default</Button>
|
||||
</Box>
|
||||
</Modal>
|
||||
)
|
||||
}
|
Loading…
Reference in New Issue
Block a user