diff --git a/src/ScriptEditor/ui/OptionsModal.tsx b/src/ScriptEditor/ui/OptionsModal.tsx index fba5f247d..3f797638c 100644 --- a/src/ScriptEditor/ui/OptionsModal.tsx +++ b/src/ScriptEditor/ui/OptionsModal.tsx @@ -1,6 +1,4 @@ -import React, { useState } from "react"; -import { Options, WordWrapOptions } from "./Options"; -import { Modal } from "../../ui/React/Modal"; +import React from "react"; import Button from "@mui/material/Button"; import Typography from "@mui/material/Typography"; @@ -9,62 +7,41 @@ 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 { useBoolean } from "../../ui/React/hooks"; +import { Modal } from "../../ui/React/Modal"; import { ThemeEditorModal } from "./ThemeEditorModal"; +import { Options } from "./Options"; -interface IProps { - options: Options; - save: (options: Options) => void; - onClose: () => void; +export type OptionsModalProps = { open: boolean; -} + options: Options; + onClose: () => void; + onOptionChange: (option: keyof Options, value: Options[keyof Options]) => void; + onThemeChange: () => void; +}; -export function OptionsModal(props: IProps): React.ReactElement { - const [theme, setTheme] = useState(props.options.theme); - const [insertSpaces, setInsertSpaces] = useState(props.options.insertSpaces); - const [tabSize, setTabSize] = useState(props.options.tabSize); - const [detectIndentation, setDetectIndentation] = useState(props.options.detectIndentation); - const [fontFamily, setFontFamily] = useState(props.options.fontFamily); - const [fontSize, setFontSize] = useState(props.options.fontSize); - const [fontLigatures, setFontLigatures] = useState(props.options.fontLigatures); - const [wordWrap, setWordWrap] = useState(props.options.wordWrap); - const [vim, setVim] = useState(props.options.vim); - const [themeEditorOpen, setThemeEditorOpen] = useState(false); +export function OptionsModal(props: OptionsModalProps): React.ReactElement { + const [themeEditorOpen, { on: openThemeEditor, off: closeThemeEditor }] = useBoolean(false); - function save(): void { - props.save({ - theme, - insertSpaces, - tabSize, - detectIndentation, - fontFamily, - fontSize, - fontLigatures, - wordWrap, - vim, - }); - props.onClose(); - } + const onFontSizeChange = (event: React.ChangeEvent) => { + const fontSize = parseInt(event.target.value); + if (!Number.isFinite(fontSize) || fontSize < 1) return; + props.onOptionChange("fontSize", fontSize); + }; - function onFontSizeChange(event: React.ChangeEvent): void { - const n = parseInt(event.target.value); - if (!Number.isFinite(n) || n < 1) return; - setFontSize(n); - } - - function onTabSizeChange(event: React.ChangeEvent): void { - const n = parseInt(event.target.value); - if (!Number.isFinite(n) || n < 1) return; - setTabSize(n); - } + const onTabSizeChange = (event: React.ChangeEvent) => { + const tabSize = parseInt(event.target.value); + if (!Number.isFinite(tabSize) || tabSize < 1) return; + props.onOptionChange("tabSize", tabSize); + }; return ( - setThemeEditorOpen(false)} /> +
Theme: - props.onOptionChange("theme", event.target.value)} value={props.options.theme}> monokai solarized-dark solarized-light @@ -74,29 +51,38 @@ export function OptionsModal(props: IProps): React.ReactElement { one-dark Custom theme -
Indent using tabs: - setInsertSpaces(e.target.checked)} checked={insertSpaces} /> + props.onOptionChange("insertSpaces", e.target.checked)} + checked={props.options.insertSpaces} + />
Tab size: - +
Auto-detect indentation: - setDetectIndentation(e.target.checked)} checked={detectIndentation} /> + props.onOptionChange("detectIndentation", e.target.checked)} + checked={props.options.detectIndentation} + />
Word wrap: - props.onOptionChange("wordWrap", event.target.value)} + value={props.options.wordWrap} + > Off On Bounded @@ -106,28 +92,30 @@ export function OptionsModal(props: IProps): React.ReactElement {
Enable vim mode: - setVim(e.target.checked)} checked={vim} /> + props.onOptionChange("vim", e.target.checked)} checked={props.options.vim} />
Font family: - setFontFamily(e.target.value)} /> + props.onOptionChange("fontFamily", e.target.value)} + />
Font size: - +
Enable font ligatures: - setFontLigatures(e.target.checked)} checked={fontLigatures} /> + props.onOptionChange("fontLigatures", e.target.checked)} + checked={props.options.fontLigatures} + />
- -
- ); } diff --git a/src/ScriptEditor/ui/ThemeEditorModal.tsx b/src/ScriptEditor/ui/ThemeEditorModal.tsx index 03d35f14d..11363b749 100644 --- a/src/ScriptEditor/ui/ThemeEditorModal.tsx +++ b/src/ScriptEditor/ui/ThemeEditorModal.tsx @@ -1,78 +1,74 @@ -import { History, Reply, Save } from "@mui/icons-material"; -import { Box, Button, Paper, TextField, Tooltip, Typography } from "@mui/material"; -import IconButton from "@mui/material/IconButton"; +import React from "react"; import _ from "lodash"; + +import { Grid, Box, Button, IconButton, Paper, TextField, Tooltip, Typography } from "@mui/material"; +import { History, Reply } from "@mui/icons-material"; import { Color, ColorPicker } from "material-ui-color"; -import React, { useState } from "react"; -import { useRerender } from "../../ui/React/hooks"; + import { Settings } from "../../Settings/Settings"; +import { useRerender } from "../../ui/React/hooks"; import { Modal } from "../../ui/React/Modal"; import { OptionSwitch } from "../../ui/React/OptionSwitch"; -import { defaultMonacoTheme, IScriptEditorTheme } from "./themes"; -interface IProps { - onClose: () => void; - open: boolean; -} +import { defaultMonacoTheme } from "./themes"; -interface IColorEditorProps { +type ColorEditorProps = { 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 { +function ColorEditor({ label, themePath, onColorChange, color, defaultColor }: ColorEditorProps): 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)}> - - - - ), - }} - /> - - - + + + onColorChange(themePath, newColor.hex)} + disableAlpha + /> + ), + endAdornment: ( + onColorChange(themePath, defaultColor)}> + + + ), + }} + /> + + ); } -export function ThemeEditorModal(props: IProps): React.ReactElement { +type ThemeEditorProps = { + onClose: () => void; + onChange: () => void; + open: boolean; +}; + +export function ThemeEditorModal(props: ThemeEditorProps): React.ReactElement { const rerender = useRerender(); - // 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)); + function onThemePropChange(prop: string, value: string): void { + _.set(Settings.EditorTheme, prop, value); + props.onChange(); rerender(); } @@ -80,28 +76,28 @@ export function ThemeEditorModal(props: IProps): React.ReactElement { try { const importedTheme = JSON.parse(event.target.value); if (typeof importedTheme !== "object") return; - setThemeCopy(importedTheme); + Settings.EditorTheme = importedTheme; + props.onChange(); } catch (err) { // ignore } } + const onResetToDefault = () => { + Settings.EditorTheme = defaultMonacoTheme; + props.onChange(); + rerender(); + }; + return ( - { - setThemeCopy(Settings.EditorTheme); - props.onClose(); - }} - > + Customize Editor theme Hover over input boxes for more information { - setThemeCopy(_.set(themeCopy, "base", val ? "vs" : "vs-dark")); - rerender(); + onThemePropChange("base", val ? "vs" : "vs-dark"); }} text="Use light theme as base" tooltip={ @@ -111,133 +107,133 @@ export function ThemeEditorModal(props: IProps): React.ReactElement { } /> - - + + UI - - + + Syntax - - + + - - diff --git a/src/ScriptEditor/ui/Toolbar.tsx b/src/ScriptEditor/ui/Toolbar.tsx index 8a9ae7255..1acb5860f 100644 --- a/src/ScriptEditor/ui/Toolbar.tsx +++ b/src/ScriptEditor/ui/Toolbar.tsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React from "react"; import * as monaco from "monaco-editor"; import Box from "@mui/material/Box"; @@ -17,9 +17,9 @@ import { makeTheme, sanitizeTheme } from "./themes"; import { Modal } from "../../ui/React/Modal"; import { Page } from "../../ui/Router"; import { Router } from "../../ui/GameRoot"; +import { useBoolean } from "../../ui/React/hooks"; import { Settings } from "../../Settings/Settings"; -import { OptionsModal } from "./OptionsModal"; -import { Options } from "./Options"; +import { OptionsModal, OptionsModalProps } from "./OptionsModal"; import { useScriptEditorContext } from "./ScriptEditorContext"; type IStandaloneCodeEditor = monaco.editor.IStandaloneCodeEditor; @@ -30,8 +30,8 @@ interface IProps { } export function Toolbar({ editor, onSave }: IProps) { - const [ramInfoOpen, setRamInfoOpen] = useState(false); - const [optionsOpen, setOptionsOpen] = useState(false); + const [ramInfoOpen, { on: openRAMInfo, off: closeRAMInfo }] = useBoolean(false); + const [optionsOpen, { on: openOptions, off: closeOptions }] = useBoolean(false); function beautify(): void { editor?.getAction("editor.action.formatDocument")?.run(); @@ -39,20 +39,24 @@ export function Toolbar({ editor, onSave }: IProps) { const { ram, ramEntries, isUpdatingRAM, options, saveOptions } = useScriptEditorContext(); + const onOptionChange: OptionsModalProps["onOptionChange"] = (option, value) => { + saveOptions({ ...options, [option]: value }); + editor?.updateOptions(options); + }; + + const onThemeChange = () => { + sanitizeTheme(Settings.EditorTheme); + monaco.editor.defineTheme("customTheme", makeTheme(Settings.EditorTheme)); + }; + return ( <> - - @@ -76,20 +80,12 @@ export function Toolbar({ editor, onSave }: IProps) { { - sanitizeTheme(Settings.EditorTheme); - monaco.editor.defineTheme("customTheme", makeTheme(Settings.EditorTheme)); - setOptionsOpen(false); - }} - options={{ ...options }} - save={(options: Options) => { - sanitizeTheme(Settings.EditorTheme); - monaco.editor.defineTheme("customTheme", makeTheme(Settings.EditorTheme)); - editor?.updateOptions(options); - saveOptions(options); - }} + options={options} + onClose={closeOptions} + onOptionChange={onOptionChange} + onThemeChange={onThemeChange} /> - setRamInfoOpen(false)}> + {ramEntries.map(([n, r]) => ( diff --git a/src/ui/GameRoot.tsx b/src/ui/GameRoot.tsx index 5a5e20e79..8878c3fb4 100644 --- a/src/ui/GameRoot.tsx +++ b/src/ui/GameRoot.tsx @@ -79,19 +79,21 @@ import { useRerender } from "./React/hooks"; const htmlLocation = location; -const useStyles = makeStyles((theme: Theme) => - createStyles({ - root: { - "-ms-overflow-style": "none" /* for Internet Explorer, Edge */, - "scrollbar-width": "none" /* for Firefox */, - margin: theme.spacing(0), - flexGrow: 1, - padding: "8px", - minHeight: "100vh", - boxSizing: "border-box", - width: "1px", - }, - }), +const useStyles = makeStyles( + (theme: Theme) => + createStyles({ + root: { + "-ms-overflow-style": "none" /* for Internet Explorer, Edge */, + "scrollbar-width": "none" /* for Firefox */, + margin: theme.spacing(0), + flexGrow: 1, + padding: "8px", + minHeight: "100vh", + boxSizing: "border-box", + width: "1px", + }, + }), + { name: "GameRoot" }, ); const uninitialized = (): void => {