From 7dd2d82eb8543e5925600ec6b17ddda4c045cdfe Mon Sep 17 00:00:00 2001 From: undefined Date: Sat, 15 Jan 2022 15:57:44 +0800 Subject: [PATCH] ui: theme changer --- packages/ui-default/build/config/webpack.js | 1 + .../ui-default/components/monaco/index.js | 36 -------- .../ui-default/components/monaco/index.ts | 92 +++++++++++++++++++ .../ui-default/components/monaco/loader.ts | 1 + .../scratchpad/ScratchpadEditorContainer.jsx | 15 +-- .../scratchpad/ScratchpadToolbarContainer.jsx | 15 --- .../components/scratchpad/reducers/ui.js | 2 +- packages/ui-default/locales/zh.yaml | 3 +- packages/ui-default/package.json | 3 +- 9 files changed, 103 insertions(+), 65 deletions(-) delete mode 100644 packages/ui-default/components/monaco/index.js create mode 100644 packages/ui-default/components/monaco/index.ts diff --git a/packages/ui-default/build/config/webpack.js b/packages/ui-default/build/config/webpack.js index 74817675f..8746d5222 100644 --- a/packages/ui-default/build/config/webpack.js +++ b/packages/ui-default/build/config/webpack.js @@ -198,6 +198,7 @@ export default function (env = {}) { patterns: [ { from: root('static') }, { from: root(`${dirname(require.resolve('vditor/package.json'))}`), to: 'vditor/' }, + { from: `${dirname(require.resolve('monaco-themes/package.json'))}/themes`, to: 'monaco/themes/' }, ], }), new webpack.DefinePlugin({ diff --git a/packages/ui-default/components/monaco/index.js b/packages/ui-default/components/monaco/index.js deleted file mode 100644 index c274cefc3..000000000 --- a/packages/ui-default/components/monaco/index.js +++ /dev/null @@ -1,36 +0,0 @@ -import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; -import './monaco.css'; - -export default monaco; - -/** @param {import('monaco-editor').editor.IStandaloneCodeEditor} editor */ -export function registerAction(editor, model, element) { - editor.addAction({ - id: 'theme-dark', - label: 'Use dark theme', - run: () => monaco.editor.setTheme('vs-dark'), - }); - editor.addAction({ - id: 'theme-light', - label: 'Use light theme', - run: () => monaco.editor.setTheme('vs-light'), - }); - if (element) { - editor.addAction({ - id: 'submit-form', - label: 'Submit', - keybindings: [ - monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter, - monaco.KeyMod.WinCtrl | monaco.KeyCode.Enter, - ], - run: () => $(element).closest('form').submit(), - }); - } - editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.KeyP, () => { - editor.getAction('editor.action.quickCommand').run(); - }); - editor.addCommand(monaco.KeyMod.Alt | monaco.KeyMod.Shift | monaco.KeyCode.KeyF, () => { - const action = editor.getAction('editor.action.format') || editor.getAction('editor.action.formatDocument'); - if (action) action.run(); - }); -} diff --git a/packages/ui-default/components/monaco/index.ts b/packages/ui-default/components/monaco/index.ts new file mode 100644 index 000000000..80d9189d8 --- /dev/null +++ b/packages/ui-default/components/monaco/index.ts @@ -0,0 +1,92 @@ +import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; +import { EditorAction, registerEditorAction } from 'monaco-editor/esm/vs/editor/browser/editorExtensions'; +import { IQuickInputService } from 'monaco-editor/esm/vs/platform/quickinput/common/quickInput'; +import list from 'monaco-themes/themes/themelist.json'; +import i18n from 'vj/utils/i18n'; +import './monaco.css'; + +export default monaco; +export const customOptions: monaco.editor.IStandaloneDiffEditorConstructionOptions = JSON.parse(localStorage.getItem('editor.config') || '{}'); + +const loaded = {}; +async function fetchTheme(id: string, label: string) { + if (loaded[id]) return; + const res = await fetch(`${UiContext.cdn_prefix}monaco/themes/${label}.json`); + const data = await res.json(); + monaco.editor.defineTheme(id, data); + loaded[id] = true; +} +export const loadThemePromise = customOptions.theme + ? fetchTheme(customOptions.theme, list[customOptions.theme]) + : Promise.resolve(); + +// 这破烂 monaco 能不能给个 typings 啊 +// 我翻源码真的很累的好吧 +class ChangeThemeAction extends EditorAction { + constructor() { + super({ + id: 'hydro.changeEditorTheme', + label: i18n('Change Theme'), + alias: i18n('Change Theme'), + }); + } + + async run(accessor, editor: monaco.editor.IStandaloneCodeEditor) { + const items = Object.keys(list).map((key) => ({ + label: list[key], + command: key, + })); + const quickInputService = accessor.get(IQuickInputService); + const oldTheme = (editor as any)._themeService._theme; + let focus = ''; + const selected = await quickInputService.pick(items, { + canPickMany: false, + async onDidFocus(item) { + focus = item.command; + await fetchTheme(item.command, item.label); + if (focus === item.command) monaco.editor.setTheme(item.command); + }, + }); + if (!selected) { + const themeId = oldTheme.type ? `${oldTheme.base}-${oldTheme.type}` : oldTheme.id; + return monaco.editor.setTheme(themeId); + } + await fetchTheme(selected.command, selected.label); + customOptions.theme = selected.command; + return monaco.editor.setTheme(selected.command); + } +} + +registerEditorAction(ChangeThemeAction); + +export function registerAction( + editor: monaco.editor.IStandaloneCodeEditor, + model: monaco.editor.IModel, + element, +) { + if (element) { + editor.addAction({ + id: 'hydro.submitForm', + label: 'Submit', + keybindings: [ + monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter, + monaco.KeyMod.WinCtrl | monaco.KeyCode.Enter, + ], + run: () => { + $(element).closest('form').submit(); + }, + }); + } + editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.KeyP, () => { + editor.getAction('editor.action.quickCommand').run(); + }); + editor.addCommand(monaco.KeyMod.Alt | monaco.KeyMod.Shift | monaco.KeyCode.KeyF, () => { + const action = editor.getAction('editor.action.format') || editor.getAction('editor.action.formatDocument'); + if (action) action.run(); + }); + editor.onDidChangeConfiguration(() => { + const current = editor.getOption(monaco.editor.EditorOptions.fontSize.id); + customOptions.fontSize = current; + localStorage.setItem('editor.config', JSON.stringify(customOptions)); + }); +} diff --git a/packages/ui-default/components/monaco/loader.ts b/packages/ui-default/components/monaco/loader.ts index 383842160..8ddabdd14 100644 --- a/packages/ui-default/components/monaco/loader.ts +++ b/packages/ui-default/components/monaco/loader.ts @@ -49,6 +49,7 @@ export async function load(features = ['markdown']) { console.log('Loaded monaco feature:', feat, 'in', Date.now() - s, 'ms'); loaded.push(feat); } + await res.loadThemePromise; return { monaco: res.default, registerAction: res.registerAction }; } export default load; diff --git a/packages/ui-default/components/scratchpad/ScratchpadEditorContainer.jsx b/packages/ui-default/components/scratchpad/ScratchpadEditorContainer.jsx index d95e95190..475d1a7a0 100644 --- a/packages/ui-default/components/scratchpad/ScratchpadEditorContainer.jsx +++ b/packages/ui-default/components/scratchpad/ScratchpadEditorContainer.jsx @@ -4,7 +4,6 @@ import { connect } from 'react-redux'; const mapStateToProps = (state) => ({ value: state.editor.code, language: window.LANGS[state.editor.lang]?.monaco, - theme: 'vs-light', mainSize: state.ui.main.size, pretestSize: state.ui.pretest.size, recordSize: state.ui.records.size, @@ -23,21 +22,20 @@ export default connect(mapStateToProps, mapDispatchToProps)(class MonacoEditor e async componentDidMount() { const value = this.props.value || ''; - const { language, theme } = this.props; + const { language } = this.props; const { load } = await import('vj/components/monaco/loader'); - const { monaco, registerAction } = await load([language]); + const { monaco, registerAction, customOptions } = await load([language]); this.model = monaco.editor.createModel(value, language, monaco.Uri.parse(`file:///${UiContext.pdoc.pid || UiContext.pdoc.docId}.${language}`)); if (this.containerElement) { /** @type {monaco.editor.IStandaloneEditorConstructionOptions} */ const config = { - theme, + theme: 'vs-light', + ...customOptions, lineNumbers: true, glyphMargin: true, lightbulb: { enabled: true }, model: this.model, }; - const fontSize = localStorage.getItem('scratchpad.editor.fontSize'); - if (fontSize && !Number.isNaN(+fontSize)) config.fontSize = +fontSize; this.editor = monaco.editor.create(this.containerElement, config); registerAction(this.editor, this.model); this.disposable.push( @@ -46,10 +44,6 @@ export default connect(mapStateToProps, mapDispatchToProps)(class MonacoEditor e this.props.handleUpdateCode(this.editor.getValue(), event); } }), - this.editor.onDidChangeConfiguration(() => { - const current = this.editor.getOptions()._values[40].fontSize; - localStorage.setItem('scratchpad.editor.fontSize', current); - }), ); } } @@ -80,7 +74,6 @@ export default connect(mapStateToProps, mapDispatchToProps)(class MonacoEditor e monaco.editor.setModelLanguage(model, language); editor.updateOptions({ mode: language }); } - if (prevProps.theme !== theme) monaco.editor.setTheme(theme); if (editor) { if (prevProps.mainSize !== mainSize || prevProps.recordSize !== recordSize diff --git a/packages/ui-default/components/scratchpad/ScratchpadToolbarContainer.jsx b/packages/ui-default/components/scratchpad/ScratchpadToolbarContainer.jsx index 19a81e94c..342e6322f 100644 --- a/packages/ui-default/components/scratchpad/ScratchpadToolbarContainer.jsx +++ b/packages/ui-default/components/scratchpad/ScratchpadToolbarContainer.jsx @@ -64,9 +64,6 @@ const mapDispatchToProps = (dispatch) => ({ payload: request.get(UiContext.getSubmissionsUrl), }); }, - handleClickRefresh() { - if (this.props.recordsVisible) this.loadSubmissions(); - }, }); export default connect(mapStateToProps, mapDispatchToProps)(class ScratchpadToolbarContainer extends React.PureComponent { @@ -121,18 +118,6 @@ export default connect(mapStateToProps, mapDispatchToProps)(class ScratchpadTool {' '} (F10) - this.props.handleClickRefresh()} - > - - {' '} - {i18n('Refresh')} - {' '} - (Alt+F) - ", "license": "AGPL-3.0", "main": "hydro.js", @@ -57,6 +57,7 @@ "monaco-editor": "^0.31.1", "monaco-editor-nls": "^2.0.0", "monaco-editor-webpack-plugin": "^7.0.1", + "monaco-themes": "^0.4.0", "nanoid": "^3.1.30", "normalize.css": "^8.0.1", "nprogress": "^0.2.0",