Files
MyGoNavi/frontend/src/components/MonacoEditor.tsx
Syngnat 307bcc95d1 🐛 fix(ui): 统一新版界面字体并调整左侧快捷操作布局
- 统一新版界面字体变量在侧边栏、AI 面板、日志、DDL、Redis 与数据视图中的落地使用
- 调整 v2 左侧 rail 布局,将新建组、批量操作表、批量操作库、运行外部 SQL 文件、定位当前打开表迁移到顶部主操作区
- 收敛新版侧边栏树节点、连接信息、字段表头与字段描述的字体与字重表现
- 让 Monaco 编辑器、数据预览和代码展示区域跟随新的 UI/Mono 字体配置
- Refs #510
2026-05-29 14:43:32 +08:00

168 lines
4.7 KiB
TypeScript

import React, { useCallback, useEffect, useMemo, useState } from 'react';
import Editor, { loader, type BeforeMount, type EditorProps } from '@monaco-editor/react';
import { useStore } from '../store';
import { sanitizeDataTableFontSize } from '../utils/dataGridDisplay';
import { DEFAULT_MONO_FONT_FAMILY } from '../utils/fontFamilies';
export type { BeforeMount, OnMount } from '@monaco-editor/react';
export type GonaviMonacoTypography = 'code' | 'data';
const DEFAULT_FONT_SIZE = 14;
const MIN_FONT_SIZE = 12;
const MAX_FONT_SIZE = 20;
let monacoConfiguredPromise: Promise<void> | null = null;
let transparentThemesRegistered = false;
const isTestRuntime = (): boolean => {
const env = (import.meta as unknown as { env?: Record<string, unknown> }).env || {};
return env.MODE === 'test' || env.VITEST === true || env.VITEST === 'true';
};
export const registerGonaviMonacoThemes: BeforeMount = (monaco) => {
if (transparentThemesRegistered) {
return;
}
monaco.editor.defineTheme('transparent-dark', {
base: 'vs-dark',
inherit: true,
rules: [],
colors: {
'editor.background': '#00000000',
'editor.lineHighlightBackground': '#ffffff10',
'editorGutter.background': '#00000000',
'editorStickyScroll.background': '#1e1e1e',
'editorStickyScrollHover.background': '#2a2a2a',
},
});
monaco.editor.defineTheme('transparent-light', {
base: 'vs',
inherit: true,
rules: [],
colors: {
'editor.background': '#00000000',
'editor.lineHighlightBackground': '#00000010',
'editorGutter.background': '#00000000',
'editorStickyScroll.background': '#ffffff',
'editorStickyScrollHover.background': '#f5f5f5',
},
});
transparentThemesRegistered = true;
};
const ensureMonacoConfigured = (): Promise<void> => {
if (isTestRuntime()) {
return Promise.resolve();
}
if (!monacoConfiguredPromise) {
monacoConfiguredPromise = import('monaco-editor/esm/nls.messages.zh-cn')
.then(() => import('monaco-editor'))
.then((monaco) => {
loader.config({ monaco });
});
}
return monacoConfiguredPromise;
};
interface MonacoEditorProps extends EditorProps {
gonaviTypography?: GonaviMonacoTypography;
}
const MonacoEditor: React.FC<MonacoEditorProps> = ({
beforeMount,
gonaviTypography = 'code',
loading,
options,
...props
}) => {
const [ready, setReady] = useState(isTestRuntime);
const uiVersion = useStore((state) => state.appearance.uiVersion);
const dataTableFontSize = useStore((state) => state.appearance.dataTableFontSize);
const dataTableFontSizeFollowGlobal = useStore((state) => state.appearance.dataTableFontSizeFollowGlobal);
const monoFontFamily = useStore((state) => state.appearance.customMonoFontFamily);
const globalFontSize = useStore((state) => state.fontSize);
useEffect(() => {
let cancelled = false;
void ensureMonacoConfigured()
.then(() => {
if (!cancelled) {
setReady(true);
}
})
.catch((error) => {
console.error('Failed to configure Monaco Editor', error);
if (!cancelled) {
setReady(true);
}
});
return () => {
cancelled = true;
};
}, []);
const handleBeforeMount: BeforeMount = useCallback((monaco) => {
registerGonaviMonacoThemes(monaco);
beforeMount?.(monaco);
}, [beforeMount]);
const resolvedOptions = useMemo(() => {
if (uiVersion !== 'v2') {
return options;
}
const effectiveGlobalFontSize = Math.min(
MAX_FONT_SIZE,
Math.max(MIN_FONT_SIZE, Math.round(Number(globalFontSize) || DEFAULT_FONT_SIZE)),
);
const effectiveDataTableFontSize = dataTableFontSizeFollowGlobal !== false
? effectiveGlobalFontSize
: (sanitizeDataTableFontSize(dataTableFontSize) ?? effectiveGlobalFontSize);
const resolvedFontSize = gonaviTypography === 'data'
? effectiveDataTableFontSize
: Math.max(10, Math.round(effectiveDataTableFontSize * 0.92));
return {
...options,
fontFamily: options?.fontFamily ?? monoFontFamily ?? DEFAULT_MONO_FONT_FAMILY,
fontSize: options?.fontSize ?? resolvedFontSize,
lineHeight: options?.lineHeight ?? Math.max(18, Math.round(resolvedFontSize * 1.62)),
};
}, [
dataTableFontSize,
dataTableFontSizeFollowGlobal,
globalFontSize,
gonaviTypography,
monoFontFamily,
options,
uiVersion,
]);
if (!ready) {
return (
<div
data-monaco-editor-loading="true"
style={{ height: props.height || '100%', width: props.width || '100%' }}
>
{loading || null}
</div>
);
}
return (
<Editor
{...props}
options={resolvedOptions}
loading={loading}
beforeMount={handleBeforeMount}
/>
);
};
export default MonacoEditor;