From 2dabe3194e9bb89357e7bd192573efc547022b63 Mon Sep 17 00:00:00 2001 From: undefined Date: Wed, 26 Jul 2023 03:39:12 +0800 Subject: [PATCH] ui: add editor setting (#622) --- .../scratchpad/ScratchpadEditorContainer.tsx | 21 +++++++- .../scratchpad/ScratchpadSettings.tsx | 48 +++++++++++++++++++ .../components/scratchpad/index.tsx | 6 +++ .../components/scratchpad/reducers/ui.ts | 26 ++++++++++ 4 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 packages/ui-default/components/scratchpad/ScratchpadSettings.tsx diff --git a/packages/ui-default/components/scratchpad/ScratchpadEditorContainer.tsx b/packages/ui-default/components/scratchpad/ScratchpadEditorContainer.tsx index ceac926cb..52c8e3a05 100644 --- a/packages/ui-default/components/scratchpad/ScratchpadEditorContainer.tsx +++ b/packages/ui-default/components/scratchpad/ScratchpadEditorContainer.tsx @@ -8,11 +8,16 @@ interface ScratchpadOptions { value?: string; language?: string; handleUpdateCode?: (str: string, event: monaco.editor.IModelContentChangedEvent) => void; + settings?: any; + pendingCommand?: string; + commandDone?: () => void; } export default connect((state: any) => ({ value: state.editor.code, language: window.LANGS[state.editor.lang]?.monaco, + settings: state.ui.settings.config, + pendingCommand: state.ui.pendingCommand, }), (dispatch) => ({ handleUpdateCode: (code: string) => { dispatch({ @@ -20,6 +25,12 @@ export default connect((state: any) => ({ payload: code, }); }, + commandDone: () => { + dispatch({ + type: 'SCRATCHPAD_TRIGGER_EDITOR_COMMAND', + payload: { command: '' }, + }); + }, }))(class MonacoEditor extends React.PureComponent { disposable: monaco.IDisposable[] = []; __prevent_trigger_change_event = false; @@ -36,8 +47,8 @@ export default connect((state: any) => ({ if (this.containerElement) { const config: monaco.editor.IStandaloneEditorConstructionOptions = { theme: 'vs-light', - ...customOptions, fontFamily: UserContext.codeFontFamily, + ...customOptions, lineNumbers: 'on', glyphMargin: true, lightbulb: { enabled: true }, @@ -88,6 +99,14 @@ export default connect((state: any) => ({ this.model = monaco.editor.getModel(uri) || monaco.editor.createModel(val, language, uri); editor.setModel(this.model); } + if (editor && this.props.settings) { + editor.updateOptions(this.props.settings); + } + if (this.props.pendingCommand) { + editor.focus(); + editor.getAction(this.props.pendingCommand)?.run(); + this.props.commandDone(); + } } componentWillUnmount() { diff --git a/packages/ui-default/components/scratchpad/ScratchpadSettings.tsx b/packages/ui-default/components/scratchpad/ScratchpadSettings.tsx new file mode 100644 index 000000000..5ec35d45d --- /dev/null +++ b/packages/ui-default/components/scratchpad/ScratchpadSettings.tsx @@ -0,0 +1,48 @@ +import React from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { i18n } from 'vj/utils'; +import { RootState } from './reducers'; + +export default function ScratchpadSettings() { + const config = useSelector((state: RootState) => state.ui.settings.config); + const parsed = React.useMemo(() => { + const settings = { fontSize: 14, tabSize: 4 }; + try { + Object.assign(settings, config); + } catch (e) { } + return settings; + }, [config]); + const dispatch = useDispatch(); + function dispatcher(setting: string, numeric = false) { + return (ev) => { + const val = ev?.target?.value || ev; + dispatch({ type: 'SCRATCHPAD_SETTING_UPDATE', payload: { setting, value: numeric ? +val : val } }); + }; + } + function openThemeSelect() { + dispatch({ type: 'SCRATCHPAD_TRIGGER_EDITOR_COMMAND', payload: { command: 'hydro.changeEditorTheme' } }); + } + // TODO update style + return
+
+ +
+
+ +
+
+

{i18n('Theme')}:

+ +
+
; +} diff --git a/packages/ui-default/components/scratchpad/index.tsx b/packages/ui-default/components/scratchpad/index.tsx index 535982c59..c10f35af0 100644 --- a/packages/ui-default/components/scratchpad/index.tsx +++ b/packages/ui-default/components/scratchpad/index.tsx @@ -1,4 +1,5 @@ import ProblemIcon from '@vscode/codicons/src/icons/file.svg?react'; +import SettingsIcon from '@vscode/codicons/src/icons/settings-gear.svg?react'; import { Allotment } from 'allotment'; import $ from 'jquery'; import _ from 'lodash'; @@ -10,6 +11,7 @@ import { Context, ctx, Service } from 'vj/context'; import ScratchpadEditor from './ScratchpadEditorContainer'; import ScratchpadPretest from './ScratchpadPretestContainer'; import ScratchpadRecords from './ScratchpadRecordsContainer'; +import ScratchpadSettings from './ScratchpadSettings'; import ScratchpadToolbar from './ScratchpadToolbarContainer'; const pages = { @@ -17,6 +19,10 @@ const pages = { icon: () => , component: () => , }, + settings: { + icon: () => , + component: () => , + }, }; let rerenderCallback = null; diff --git a/packages/ui-default/components/scratchpad/reducers/ui.ts b/packages/ui-default/components/scratchpad/reducers/ui.ts index aa7029c87..a6240535b 100644 --- a/packages/ui-default/components/scratchpad/reducers/ui.ts +++ b/packages/ui-default/components/scratchpad/reducers/ui.ts @@ -11,11 +11,16 @@ export default function reducer(state = { visible: UiContext.canViewRecord && localStorage.getItem('scratchpad/records') === 'true', isLoading: false, }, + settings: { + visible: false, + config: JSON.parse(localStorage.getItem('editor.config') || '{}'), + }, isPosting: false, pretestWaitSec: 0, submitWaitSec: 0, lastTick: 0, activePage: 'problem', + pendingCommand: '', }, action: any = {}) { switch (action.type) { case 'SCRATCHPAD_UI_SET_VISIBILITY': { @@ -40,6 +45,27 @@ export default function reducer(state = { }, }; } + case 'SCRATCHPAD_SETTING_UPDATE': { + const { setting, value } = action.payload; + const config = { + ...state.settings.config, + [setting]: value, + }; + localStorage.setItem('editor.config', JSON.stringify(config)); + return { + ...state, + settings: { + ...state.settings, + config, + }, + }; + } + case 'SCRATCHPAD_TRIGGER_EDITOR_COMMAND': { + return { + ...state, + pendingCommand: action.payload.command, + }; + } case 'SCRATCHPAD_POST_PRETEST_PENDING': case 'SCRATCHPAD_POST_SUBMIT_PENDING': { return {