mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-19 17:38:19 +08:00
ui: theme changer
This commit is contained in:
1 parent
8e02eb5bf9
commit
7dd2d82eb8
9 files changed
+103
-65
No files matched your search
@@ -198,6 +198,7 @@ export default function (env = {}) {
|
|||||||
patterns: [
|
patterns: [
|
||||||
{ from: root('static') },
|
{ from: root('static') },
|
||||||
{ from: root(`${dirname(require.resolve('vditor/package.json'))}`), to: 'vditor/' },
|
{ 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({
|
new webpack.DefinePlugin({
|
||||||
|
|||||||
@@ -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();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -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));
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -49,6 +49,7 @@ export async function load(features = ['markdown']) {
|
|||||||
console.log('Loaded monaco feature:', feat, 'in', Date.now() - s, 'ms');
|
console.log('Loaded monaco feature:', feat, 'in', Date.now() - s, 'ms');
|
||||||
loaded.push(feat);
|
loaded.push(feat);
|
||||||
}
|
}
|
||||||
|
await res.loadThemePromise;
|
||||||
return { monaco: res.default, registerAction: res.registerAction };
|
return { monaco: res.default, registerAction: res.registerAction };
|
||||||
}
|
}
|
||||||
export default load;
|
export default load;
|
||||||
@@ -4,7 +4,6 @@ import { connect } from 'react-redux';
|
|||||||
const mapStateToProps = (state) => ({
|
const mapStateToProps = (state) => ({
|
||||||
value: state.editor.code,
|
value: state.editor.code,
|
||||||
language: window.LANGS[state.editor.lang]?.monaco,
|
language: window.LANGS[state.editor.lang]?.monaco,
|
||||||
theme: 'vs-light',
|
|
||||||
mainSize: state.ui.main.size,
|
mainSize: state.ui.main.size,
|
||||||
pretestSize: state.ui.pretest.size,
|
pretestSize: state.ui.pretest.size,
|
||||||
recordSize: state.ui.records.size,
|
recordSize: state.ui.records.size,
|
||||||
@@ -23,21 +22,20 @@ export default connect(mapStateToProps, mapDispatchToProps)(class MonacoEditor e
|
|||||||
|
|
||||||
async componentDidMount() {
|
async componentDidMount() {
|
||||||
const value = this.props.value || '';
|
const value = this.props.value || '';
|
||||||
const { language, theme } = this.props;
|
const { language } = this.props;
|
||||||
const { load } = await import('vj/components/monaco/loader');
|
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}`));
|
this.model = monaco.editor.createModel(value, language, monaco.Uri.parse(`file:///${UiContext.pdoc.pid || UiContext.pdoc.docId}.${language}`));
|
||||||
if (this.containerElement) {
|
if (this.containerElement) {
|
||||||
/** @type {monaco.editor.IStandaloneEditorConstructionOptions} */
|
/** @type {monaco.editor.IStandaloneEditorConstructionOptions} */
|
||||||
const config = {
|
const config = {
|
||||||
theme,
|
theme: 'vs-light',
|
||||||
|
...customOptions,
|
||||||
lineNumbers: true,
|
lineNumbers: true,
|
||||||
glyphMargin: true,
|
glyphMargin: true,
|
||||||
lightbulb: { enabled: true },
|
lightbulb: { enabled: true },
|
||||||
model: this.model,
|
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);
|
this.editor = monaco.editor.create(this.containerElement, config);
|
||||||
registerAction(this.editor, this.model);
|
registerAction(this.editor, this.model);
|
||||||
this.disposable.push(
|
this.disposable.push(
|
||||||
@@ -46,10 +44,6 @@ export default connect(mapStateToProps, mapDispatchToProps)(class MonacoEditor e
|
|||||||
this.props.handleUpdateCode(this.editor.getValue(), event);
|
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);
|
monaco.editor.setModelLanguage(model, language);
|
||||||
editor.updateOptions({ mode: language });
|
editor.updateOptions({ mode: language });
|
||||||
}
|
}
|
||||||
if (prevProps.theme !== theme) monaco.editor.setTheme(theme);
|
|
||||||
if (editor) {
|
if (editor) {
|
||||||
if (prevProps.mainSize !== mainSize
|
if (prevProps.mainSize !== mainSize
|
||||||
|| prevProps.recordSize !== recordSize
|
|| prevProps.recordSize !== recordSize
|
||||||
|
|||||||
@@ -64,9 +64,6 @@ const mapDispatchToProps = (dispatch) => ({
|
|||||||
payload: request.get(UiContext.getSubmissionsUrl),
|
payload: request.get(UiContext.getSubmissionsUrl),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
handleClickRefresh() {
|
|
||||||
if (this.props.recordsVisible) this.loadSubmissions();
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(class ScratchpadToolbarContainer extends React.PureComponent {
|
export default connect(mapStateToProps, mapDispatchToProps)(class ScratchpadToolbarContainer extends React.PureComponent {
|
||||||
@@ -121,18 +118,6 @@ export default connect(mapStateToProps, mapDispatchToProps)(class ScratchpadTool
|
|||||||
{' '}
|
{' '}
|
||||||
(F10)
|
(F10)
|
||||||
</ToolbarButton>
|
</ToolbarButton>
|
||||||
<ToolbarButton
|
|
||||||
data-tooltip={i18n('Refresh Records')}
|
|
||||||
className="scratchpad__toolbar__refresh"
|
|
||||||
data-global-hotkey="alt+f"
|
|
||||||
onClick={() => this.props.handleClickRefresh()}
|
|
||||||
>
|
|
||||||
<Icon name="refresh" />
|
|
||||||
{' '}
|
|
||||||
{i18n('Refresh')}
|
|
||||||
{' '}
|
|
||||||
(Alt+F)
|
|
||||||
</ToolbarButton>
|
|
||||||
<ToolbarButton
|
<ToolbarButton
|
||||||
data-global-hotkey="alt+q"
|
data-global-hotkey="alt+q"
|
||||||
data-tooltip={`${i18n('Quit Scratchpad')} (Alt+Q)`}
|
data-tooltip={`${i18n('Quit Scratchpad')} (Alt+Q)`}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export default function reducer(state = {
|
|||||||
},
|
},
|
||||||
records: {
|
records: {
|
||||||
visible: UiContext.canViewRecord && localStorage.getItem('scratchpad/records') === 'true',
|
visible: UiContext.canViewRecord && localStorage.getItem('scratchpad/records') === 'true',
|
||||||
size: 200,
|
size: 100,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
},
|
},
|
||||||
isPosting: false,
|
isPosting: false,
|
||||||
|
|||||||
@@ -33,12 +33,12 @@ __langname: 简体中文
|
|||||||
'Join {0}': '加入 {0}'
|
'Join {0}': '加入 {0}'
|
||||||
'No': 否
|
'No': 否
|
||||||
'Note: Problem title may not be hidden.': 注意:题目标题可能不会被隐藏。
|
'Note: Problem title may not be hidden.': 注意:题目标题可能不会被隐藏。
|
||||||
|
'Packages have new version: {0}': '以下组件有新版本: {0}'
|
||||||
'Role {1} already exists in domain {0}.': '域 {0} 中已存在角色 {1}。'
|
'Role {1} already exists in domain {0}.': '域 {0} 中已存在角色 {1}。'
|
||||||
'Solved {0} problems, RP: {1} (No. {2})': '解决了 {0} 道题目,RP: {1} (No. {2})'
|
'Solved {0} problems, RP: {1} (No. {2})': '解决了 {0} 道题目,RP: {1} (No. {2})'
|
||||||
'The value `{1}` of {0} already exists.': '{0} 的值 `{1}` 已经存在。'
|
'The value `{1}` of {0} already exists.': '{0} 的值 `{1}` 已经存在。'
|
||||||
'Yes': 是
|
'Yes': 是
|
||||||
"Effects only when Difficulty is not 'Use algorithm calculated'.": 仅当难度不为“使用算法计算”时才起效。
|
"Effects only when Difficulty is not 'Use algorithm calculated'.": 仅当难度不为“使用算法计算”时才起效。
|
||||||
'Packages have new version: {0}': '以下组件有新版本: {0}'
|
|
||||||
"Split by ', '.": 由“, ”或“,”分隔。
|
"Split by ', '.": 由“, ”或“,”分隔。
|
||||||
"What's file?": 什么是文件?
|
"What's file?": 什么是文件?
|
||||||
(All Problems): (所有题目)
|
(All Problems): (所有题目)
|
||||||
@@ -110,6 +110,7 @@ Categories: 分类
|
|||||||
Category: 分类
|
Category: 分类
|
||||||
Change Email: 修改电子邮件
|
Change Email: 修改电子邮件
|
||||||
Change Password: 修改密码
|
Change Password: 修改密码
|
||||||
|
Change Theme: 切换主题
|
||||||
Check In: 签到
|
Check In: 签到
|
||||||
Chinese: 中文
|
Chinese: 中文
|
||||||
Choose the background image in your profile page.: 选择您资料页面的背景图片。
|
Choose the background image in your profile page.: 选择您资料页面的背景图片。
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@hydrooj/ui-default",
|
"name": "@hydrooj/ui-default",
|
||||||
"version": "4.29.9",
|
"version": "4.30.0",
|
||||||
"author": "undefined <i@undefined.moe>",
|
"author": "undefined <i@undefined.moe>",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"main": "hydro.js",
|
"main": "hydro.js",
|
||||||
@@ -57,6 +57,7 @@
|
|||||||
"monaco-editor": "^0.31.1",
|
"monaco-editor": "^0.31.1",
|
||||||
"monaco-editor-nls": "^2.0.0",
|
"monaco-editor-nls": "^2.0.0",
|
||||||
"monaco-editor-webpack-plugin": "^7.0.1",
|
"monaco-editor-webpack-plugin": "^7.0.1",
|
||||||
|
"monaco-themes": "^0.4.0",
|
||||||
"nanoid": "^3.1.30",
|
"nanoid": "^3.1.30",
|
||||||
"normalize.css": "^8.0.1",
|
"normalize.css": "^8.0.1",
|
||||||
"nprogress": "^0.2.0",
|
"nprogress": "^0.2.0",
|
||||||
|
|||||||
Reference in new issue
Block a user