🐛 fix(ui): 统一新版界面字体并调整左侧快捷操作布局

- 统一新版界面字体变量在侧边栏、AI 面板、日志、DDL、Redis 与数据视图中的落地使用
- 调整 v2 左侧 rail 布局,将新建组、批量操作表、批量操作库、运行外部 SQL 文件、定位当前打开表迁移到顶部主操作区
- 收敛新版侧边栏树节点、连接信息、字段表头与字段描述的字体与字重表现
- 让 Monaco 编辑器、数据预览和代码展示区域跟随新的 UI/Mono 字体配置
- Refs #510
This commit is contained in:
Syngnat
2026-05-29 14:43:32 +08:00
parent a7f8ce36df
commit 307bcc95d1
22 changed files with 532 additions and 228 deletions

View File

@@ -1,4 +1,4 @@
import React, { useState, useEffect, useMemo, useCallback } from 'react';
import React, { useState, useEffect, useMemo, useCallback, useRef } from 'react';
import { Layout, Button, ConfigProvider, theme, message, Modal, Spin, Slider, Progress, Switch, Input, InputNumber, Select, Segmented, Tooltip } from 'antd';
import zhCN from 'antd/locale/zh_CN';
import { PlusOutlined, ConsoleSqlOutlined, UploadOutlined, DownloadOutlined, CloudDownloadOutlined, BugOutlined, ToolOutlined, GlobalOutlined, InfoCircleOutlined, GithubOutlined, SkinOutlined, CheckOutlined, MinusOutlined, BorderOutlined, CloseOutlined, SettingOutlined, LinkOutlined, BgColorsOutlined, AppstoreOutlined, RobotOutlined, FolderOpenOutlined, HddOutlined, SafetyCertificateOutlined, SwitcherOutlined, CodeOutlined } from '@ant-design/icons';
@@ -105,6 +105,7 @@ const MIN_FONT_SIZE = 12;
const MAX_FONT_SIZE = 20;
const DEFAULT_UI_SCALE = 1.0;
const DEFAULT_FONT_SIZE = 14;
const EMPTY_INSTALLED_FONT_FAMILIES: InstalledFontFamily[] = [];
type SidebarResizeBounds = { minWidth: number; maxWidth: number };
type SidebarResizeDragState = SidebarResizeBounds & {
startX: number;
@@ -229,9 +230,11 @@ class AIPanelErrorBoundary extends React.Component<
function App() {
const [isModalOpen, setIsModalOpen] = useState(false);
const [isConnectionModalMounted, setIsConnectionModalMounted] = useState(false);
const [isSyncModalOpen, setIsSyncModalOpen] = useState(false);
const [isDriverModalOpen, setIsDriverModalOpen] = useState(false);
const [editingConnection, setEditingConnection] = useState<SavedConnection | null>(null);
const connectionModalWarmupDoneRef = useRef(false);
const windowState = useStore(state => state.windowState);
const themeMode = useStore(state => state.theme);
const setTheme = useStore(state => state.setTheme);
@@ -2072,6 +2075,57 @@ function App() {
const [isShortcutModalOpen, setIsShortcutModalOpen] = useState(false);
const [isSnippetModalOpen, setIsSnippetModalOpen] = useState(false);
const [capturingShortcutAction, setCapturingShortcutAction] = useState<ShortcutAction | null>(null);
useEffect(() => {
if (!isThemeModalOpen || themeModalSection !== 'appearance') {
return;
}
if (hasLoadedInstalledFontsRef.current || isFontFamiliesLoading) {
return;
}
let cancelled = false;
hasLoadedInstalledFontsRef.current = true;
setIsFontFamiliesLoading(true);
setFontFamiliesLoadError(null);
ListInstalledFontFamilies()
.then((result) => {
if (cancelled) {
return;
}
if (!result?.success) {
throw new Error(String(result?.message || '加载系统字体失败'));
}
const nextFonts = Array.isArray(result?.data)
? result.data
.map((item) => ({
family: sanitizeFontFamilyInput((item as InstalledFontFamily | Record<string, unknown>)?.family) || '',
path: typeof (item as InstalledFontFamily | Record<string, unknown>)?.path === 'string'
? String((item as InstalledFontFamily | Record<string, unknown>).path)
: undefined,
}))
.filter((item) => item.family)
: EMPTY_INSTALLED_FONT_FAMILIES;
setInstalledFontFamilies(nextFonts);
})
.catch((error) => {
if (cancelled) {
return;
}
hasLoadedInstalledFontsRef.current = false;
setFontFamiliesLoadError(String(error instanceof Error ? error.message : error || '加载系统字体失败'));
})
.finally(() => {
if (!cancelled) {
setIsFontFamiliesLoading(false);
}
});
return () => {
cancelled = true;
};
}, [isThemeModalOpen, themeModalSection]);
const shortcutConflictMap = useMemo(() => {
const map: Partial<Record<ShortcutAction, ConflictInfo[]>> = {};
for (const action of SHORTCUT_ACTION_ORDER) {
@@ -2294,15 +2348,35 @@ function App() {
const handleCreateConnection = useCallback(() => {
setSecurityUpdateRepairSource(null);
setEditingConnection(null);
setIsConnectionModalMounted(true);
setIsModalOpen(true);
}, []);
const handleEditConnection = (conn: SavedConnection) => {
setSecurityUpdateRepairSource(null);
setEditingConnection(conn);
setIsConnectionModalMounted(true);
setIsModalOpen(true);
};
useEffect(() => {
if (connectionModalWarmupDoneRef.current) {
return;
}
connectionModalWarmupDoneRef.current = true;
const warmup = () => setIsConnectionModalMounted(true);
if (typeof window === 'undefined') {
warmup();
return;
}
if (typeof window.requestIdleCallback === 'function') {
const idleId = window.requestIdleCallback(() => warmup(), { timeout: 1200 });
return () => window.cancelIdleCallback?.(idleId);
}
const timerId = window.setTimeout(warmup, 300);
return () => window.clearTimeout(timerId);
}, []);
const handleConnectionSaved = useCallback(async (savedConnection: SavedConnection) => {
if (!shouldRetrySecurityUpdateAfterRepairSave(securityUpdateRepairSource)) {
return;
@@ -2643,8 +2717,13 @@ function App() {
document.body.style.color = darkMode ? '#ffffff' : '#000000';
document.body.setAttribute('data-theme', darkMode ? 'dark' : 'light');
document.body.setAttribute('data-ui-version', appearance.uiVersion);
document.body.setAttribute('data-platform', runtimePlatform || '');
document.body.style.fontSize = `${effectiveFontSize}px`;
document.body.style.setProperty('--gn-font-sans', resolvedUiFontFamily);
document.body.style.setProperty('--gn-font-mono', resolvedMonoFontFamily);
document.documentElement.style.setProperty('--gonavi-font-size', `${effectiveFontSize}px`);
document.documentElement.style.setProperty('--gn-font-sans', resolvedUiFontFamily);
document.documentElement.style.setProperty('--gn-font-mono', resolvedMonoFontFamily);
document.documentElement.style.setProperty('--gn-ui-scale', `${effectiveUiScale}`);
document.documentElement.style.setProperty('--gn-font-size', `${effectiveFontSize}px`);
document.documentElement.style.setProperty('--gn-font-size-sm', `${Math.max(10, Math.round(effectiveFontSize * 0.86))}px`);
@@ -3389,7 +3468,7 @@ function App() {
)}
</Content>
</Layout>
{isModalOpen && (
{isConnectionModalMounted && (
<ConnectionModal
open={isModalOpen}
onClose={handleCloseModal}

View File

@@ -5,6 +5,7 @@
overflow: hidden;
border-left: 1px solid rgba(128, 128, 128, 0.12);
position: relative;
font-family: var(--gn-font-sans, "Inter", "PingFang SC", -apple-system, BlinkMacSystemFont, "Helvetica Neue", "Segoe UI", sans-serif);
}
/* Resize Handle */
@@ -203,14 +204,14 @@
margin: 10px 0;
border-radius: 4px;
padding: 10px;
font-family: 'JetBrains Mono', 'Fira Code', 'Consolas', monospace;
font-family: var(--gn-font-mono, "JetBrains Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace);
font-size: 12px;
overflow-x: auto;
border: 1px solid rgba(128, 128, 128, 0.15);
background: rgba(0, 0, 0, 0.2);
}
.ai-markdown-content code {
font-family: 'JetBrains Mono', 'Fira Code', 'Consolas', monospace;
font-family: var(--gn-font-mono, "JetBrains Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace);
background: rgba(128, 128, 128, 0.15);
padding: 2px 4px;
border-radius: 3px;

View File

@@ -21,7 +21,6 @@ import {
import { resolveProviderSecretDraft } from '../utils/providerSecretDraft';
import { buildAddProviderEditorSession, buildClosedProviderEditorSession, buildEditProviderEditorSession, type ProviderEditorSession } from '../utils/aiProviderEditorState';
import type { OverlayWorkbenchTheme } from '../utils/overlayWorkbenchTheme';
interface AISettingsModalProps {
open: boolean;
onClose: () => void;
@@ -422,7 +421,7 @@ const AISettingsModal: React.FC<AISettingsModalProps> = ({ open, onClose, darkMo
<div style={{ fontSize: 12, color: overlayTheme.mutedText, marginTop: 4, display: 'flex', alignItems: 'center', gap: 6 }}>
<span>{matchedPreset.label}</span>
<span style={{ opacity: 0.4 }}>·</span>
<span style={{ fontFamily: 'monospace', fontSize: 12 }}>{p.model || '未选择模型'}</span>
<span style={{ fontFamily: 'var(--gn-font-mono)', fontSize: 12 }}>{p.model || '未选择模型'}</span>
</div>
</div>
<Space size={2}>
@@ -683,7 +682,7 @@ const AISettingsModal: React.FC<AISettingsModalProps> = ({ open, onClose, darkMo
<div style={{
background: darkMode ? 'rgba(0,0,0,0.2)' : 'rgba(255,255,255,0.8)',
padding: '10px 12px', borderRadius: 8, fontSize: 13, color: overlayTheme.mutedText,
whiteSpace: 'pre-wrap', fontFamily: 'monospace', lineHeight: 1.5,
whiteSpace: 'pre-wrap', fontFamily: 'var(--gn-font-mono)', lineHeight: 1.5,
userSelect: 'text', border: darkMode ? '1px solid rgba(255,255,255,0.03)' : '1px solid rgba(0,0,0,0.02)'
}}>
{promptText}
@@ -718,7 +717,7 @@ const AISettingsModal: React.FC<AISettingsModalProps> = ({ open, onClose, darkMo
<div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 8 }}>
<span style={{ fontSize: 20 }}>{tool.icon}</span>
<div>
<div style={{ fontWeight: 700, fontSize: 14, color: overlayTheme.titleText, fontFamily: 'monospace' }}>
<div style={{ fontWeight: 700, fontSize: 14, color: overlayTheme.titleText, fontFamily: 'var(--gn-font-mono)' }}>
{tool.name}
</div>
<div style={{ fontSize: 13, color: overlayTheme.mutedText, marginTop: 2 }}>{tool.desc}</div>
@@ -733,7 +732,7 @@ const AISettingsModal: React.FC<AISettingsModalProps> = ({ open, onClose, darkMo
<div style={{ marginTop: 8, fontSize: 12, color: overlayTheme.mutedText, opacity: 0.7, display: 'flex', alignItems: 'center', gap: 6 }}>
<ToolOutlined style={{ fontSize: 12 }} />
<span></span>
<code style={{ fontFamily: 'monospace', fontSize: 12, padding: '1px 6px', borderRadius: 4, background: darkMode ? 'rgba(255,255,255,0.06)' : 'rgba(0,0,0,0.04)' }}>
<code style={{ fontFamily: 'var(--gn-font-mono)', fontSize: 12, padding: '1px 6px', borderRadius: 4, background: darkMode ? 'rgba(255,255,255,0.06)' : 'rgba(0,0,0,0.04)' }}>
{tool.params}
</code>
</div>
@@ -835,6 +834,3 @@ export default AISettingsModal;

View File

@@ -7333,6 +7333,7 @@ const ConnectionModal: React.FC<{
wordBreak: "break-all",
lineHeight: "20px",
fontSize: 13,
fontFamily: "var(--gn-font-mono)",
}}
>
{String(testResult?.message || "暂无失败日志")}

View File

@@ -184,6 +184,7 @@ const DATE_TIME_CACHE_LIMIT = 2000;
const TABLE_CELL_PREVIEW_MAX_CHARS = 240;
const DATA_GRID_DISPLAY_RENDER_VERSION = Symbol('DATA_GRID_DISPLAY_RENDER_VERSION');
const DATA_GRID_VIRTUAL_EDIT_RENDER_VERSION = Symbol('DATA_GRID_VIRTUAL_EDIT_RENDER_VERSION');
const DEFAULT_GRID_MONO_FONT_FAMILY = '"JetBrains Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace';
const normalizedDateTimeCache = new Map<string, string>();
const objectCellPreviewCache = new WeakMap<object, string>();
const makeCellKey = (rowKey: string, colName: string) => `${rowKey}${CELL_KEY_SEP}${colName}`;
@@ -3990,7 +3991,7 @@ const DataGrid: React.FC<DataGridProps> = ({
const computed = window.getComputedStyle(element);
const weight = computed.fontWeight || '400';
const size = computed.fontSize || '13px';
const family = computed.fontFamily || 'sans-serif';
const family = computed.fontFamily || DEFAULT_GRID_MONO_FONT_FAMILY;
font = `${weight} ${size} ${family}`;
}
return (text: string) => measureTextWidth(text, font);
@@ -4001,7 +4002,7 @@ const DataGrid: React.FC<DataGridProps> = ({
if (displayColumnNames.length === 0 || displayData.length === 0) return;
const sig = displayColumnNames.join(',');
if (autoFitDoneRef.current === sig) return;
const font = `${densityParams.dataFontSize}px -apple-system, sans-serif`;
const font = `${densityParams.dataFontSize}px ${DEFAULT_GRID_MONO_FONT_FAMILY}`;
const newWidths: Record<string, number> = {};
displayColumnNames.forEach((key) => {
const autoWidth = calculateAutoFitColumnWidth({
@@ -4039,8 +4040,8 @@ const DataGrid: React.FC<DataGridProps> = ({
const nextWidth = calculateAutoFitColumnWidth({
headerTexts,
valueTexts: displayDataRef.current.slice(0, 200).map((row) => row?.[normalizedKey]),
measureHeaderText: buildAutoFitMeasurer(headerEl ?? null, `600 ${densityParams.dataFontSize}px -apple-system, sans-serif`),
measureCellText: buildAutoFitMeasurer(sampleCell ?? null, `400 ${densityParams.dataFontSize}px -apple-system, sans-serif`),
measureHeaderText: buildAutoFitMeasurer(headerEl ?? null, `600 ${densityParams.dataFontSize}px ${DEFAULT_GRID_MONO_FONT_FAMILY}`),
measureCellText: buildAutoFitMeasurer(sampleCell ?? null, `400 ${densityParams.dataFontSize}px ${DEFAULT_GRID_MONO_FONT_FAMILY}`),
defaultWidth,
minWidth: 80,
maxWidth: Math.max(720, Math.floor(containerWidth * 0.85)),

View File

@@ -86,6 +86,7 @@ const DataGridPreviewPanel: React.FC<DataGridPreviewPanelProps> = ({
{focusedCellInfo ? (
<Editor
height="100%"
gonaviTypography="data"
language={dataPanelIsJson ? 'json' : 'plaintext'}
theme={darkMode ? 'transparent-dark' : 'transparent-light'}
value={dataPanelValue}
@@ -98,7 +99,6 @@ const DataGridPreviewPanel: React.FC<DataGridPreviewPanelProps> = ({
minimap: { enabled: false },
scrollBeyondLastLine: false,
wordWrap: 'on',
fontSize: 13,
tabSize: 2,
automaticLayout: true,
readOnly: !focusedCellWritable,

View File

@@ -60,6 +60,7 @@ export const DataGridV2DdlView: React.FC<DataGridV2DdlViewProps> = ({
<div className="gn-v2-data-grid-ddl-code">
<Editor
height="100%"
gonaviTypography="code"
language="sql"
theme={darkMode ? 'transparent-dark' : 'transparent-light'}
value={ddlLoading ? '正在加载 DDL...' : ddlText}
@@ -68,7 +69,6 @@ export const DataGridV2DdlView: React.FC<DataGridV2DdlViewProps> = ({
minimap: { enabled: false },
scrollBeyondLastLine: false,
wordWrap: 'off',
fontSize: 12,
tabSize: 2,
automaticLayout: true,
}}

View File

@@ -9,7 +9,6 @@ import { isMacLikePlatform, normalizeOpacityForPlatform, resolveAppearanceValues
import { buildRpcConnectionConfig } from '../utils/connectionRpcConfig';
import { formatLocalDateTimeLiteral, normalizeTemporalLiteralText } from './dataGridCopyInsert';
import { buildDataSyncRequest, type SourceDatasetMode, validateDataSyncSelection } from './dataSyncRequest';
const { Title, Text } = Typography;
const { Step } = Steps;
const { Option } = Select;
@@ -1239,7 +1238,7 @@ const DataSyncModal: React.FC<{ open: boolean; onClose: () => void }> = ({ open,
padding: 12,
height: 300,
overflowY: 'auto',
fontFamily: 'SFMono-Regular, ui-monospace, Menlo, Consolas, monospace'
fontFamily: 'var(--gn-font-mono)'
}}
>
{syncLogs.map((item, i: number) => <div key={i}>{renderSyncLogItem(item)}</div>)}

View File

@@ -1674,7 +1674,7 @@ const DriverManagerModal: React.FC<{ open: boolean; onClose: () => void; onOpenG
</Paragraph>
) : null}
{activeDriverLogLines.length > 0 ? (
<pre style={{ margin: 0, maxHeight: 360, overflow: 'auto', padding: 12, background: logBlockBackground, color: logBlockTextColor, borderRadius: 8, border: `1px solid ${logBlockBorderColor}`, whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>
<pre style={{ margin: 0, maxHeight: 360, overflow: 'auto', padding: 12, background: logBlockBackground, color: logBlockTextColor, borderRadius: 8, border: `1px solid ${logBlockBorderColor}`, whiteSpace: 'pre-wrap', wordBreak: 'break-word', fontFamily: 'var(--gn-font-mono)' }}>
{activeDriverLogLines.join('\n')}
</pre>
) : (

View File

@@ -5,7 +5,6 @@ import { PreviewImportFile, ImportDataWithProgress } from '../../wailsjs/go/app/
import { EventsOn, EventsOff } from '../../wailsjs/runtime/runtime';
import { useStore } from '../store';
import { buildRpcConnectionConfig } from '../utils/connectionRpcConfig';
interface ImportPreviewModalProps {
visible: boolean;
filePath: string;
@@ -234,7 +233,7 @@ const ImportPreviewModal: React.FC<ImportPreviewModalProps> = ({
borderRadius: 4,
padding: 12,
fontSize: 12,
fontFamily: 'monospace'
fontFamily: 'var(--gn-font-mono)'
}}>
{importResult.errorLogs.map((log: string, idx: number) => (
<div key={idx} style={{ marginBottom: 4 }}>{log}</div>

View File

@@ -3,7 +3,6 @@ import { Table, Tag, Button, Tooltip, Empty } from 'antd';
import { ClearOutlined, CloseOutlined, BugOutlined, ClockCircleOutlined } from '@ant-design/icons';
import { useStore } from '../store';
import { normalizeOpacityForPlatform, resolveAppearanceValues } from '../utils/appearance';
interface LogPanelProps {
height: number;
onClose: () => void;
@@ -76,7 +75,7 @@ const LogPanel: React.FC<LogPanelProps> = ({ height, onClose, onResizeStart }) =
title: 'SQL / Message',
dataIndex: 'sql',
render: (text: string, record: any) => (
<div style={{ fontFamily: 'monospace', wordBreak: 'break-all', fontSize: '12px', lineHeight: '1.45' }}>
<div style={{ fontFamily: 'var(--gn-font-mono)', wordBreak: 'break-all', fontSize: '12px', lineHeight: '1.45' }}>
<div style={{ color: darkMode ? '#a6e22e' : '#005cc5' }}>{text}</div>
{record.message && <div style={{ color: '#ff4d4f', marginTop: 2 }}>{record.message}</div>}
{record.affectedRows !== undefined && <div style={{ color: panelMutedTextColor, marginTop: 1 }}>Affected: {record.affectedRows}</div>}

View File

@@ -1,8 +1,15 @@
import React, { useCallback, useEffect, useState } from 'react';
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;
@@ -60,8 +67,23 @@ const ensureMonacoConfigured = (): Promise<void> => {
return monacoConfiguredPromise;
};
const MonacoEditor: React.FC<EditorProps> = ({ beforeMount, loading, ...props }) => {
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;
@@ -89,6 +111,38 @@ const MonacoEditor: React.FC<EditorProps> = ({ beforeMount, loading, ...props })
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
@@ -100,7 +154,14 @@ const MonacoEditor: React.FC<EditorProps> = ({ beforeMount, loading, ...props })
);
}
return <Editor {...props} loading={loading} beforeMount={handleBeforeMount} />;
return (
<Editor
{...props}
options={resolvedOptions}
loading={loading}
beforeMount={handleBeforeMount}
/>
);
};
export default MonacoEditor;

View File

@@ -2972,6 +2972,7 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
<div className={isV2Ui ? 'gn-v2-query-monaco-shell' : undefined} style={{ height: editorHeight, minHeight: '100px' }}>
<Editor
height="100%"
gonaviTypography="code"
defaultLanguage="sql"
theme={darkMode ? "transparent-dark" : "transparent-light"}
defaultValue={query}
@@ -2981,7 +2982,6 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
minimap: { enabled: false },
automaticLayout: true,
scrollBeyondLastLine: false,
fontSize: 14
}}
/>
</div>
@@ -3076,7 +3076,7 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
<CloseOutlined />
<span></span>
</div>
<div className="custom-scrollbar" style={{ padding: 16, background: darkMode ? '#2d1a1a' : '#fff2f0', border: `1px solid ${darkMode ? '#5c2020' : '#ffccc7'}`, borderRadius: 6, color: darkMode ? '#ffa39e' : '#cf1322', fontFamily: 'monospace', whiteSpace: 'pre-wrap', wordBreak: 'break-all', maxHeight: '40vh', overflow: 'auto' }}>
<div className="custom-scrollbar" style={{ padding: 16, background: darkMode ? '#2d1a1a' : '#fff2f0', border: `1px solid ${darkMode ? '#5c2020' : '#ffccc7'}`, borderRadius: 6, color: darkMode ? '#ffa39e' : '#cf1322', fontFamily: 'var(--gn-font-mono)', fontSize: 'var(--gn-font-size-mono, 13px)', whiteSpace: 'pre-wrap', wordBreak: 'break-all', maxHeight: '40vh', overflow: 'auto' }}>
{executionError}
</div>
<div style={{ marginTop: 8 }}>

View File

@@ -111,6 +111,7 @@ const RedisCommandEditor: React.FC<RedisCommandEditorProps> = ({ connectionId, r
const appearance = useStore(state => state.appearance);
const connection = connections.find(c => c.id === connectionId);
const darkMode = theme === 'dark';
const isV2Ui = appearance.uiVersion === 'v2';
const resolvedAppearance = resolveAppearanceValues(appearance);
const opacity = normalizeOpacityForPlatform(resolvedAppearance.opacity);
const blur = normalizeBlurForPlatform(resolvedAppearance.blur);
@@ -406,6 +407,7 @@ const RedisCommandEditor: React.FC<RedisCommandEditorProps> = ({ connectionId, r
<div style={{ flex: 1, minHeight: 0, position: 'relative' }}>
<Editor
theme={darkMode ? 'transparent-dark' : 'transparent-light'}
gonaviTypography="code"
defaultLanguage="redis"
language="redis"
value={command}
@@ -414,7 +416,6 @@ const RedisCommandEditor: React.FC<RedisCommandEditorProps> = ({ connectionId, r
options={{
minimap: { enabled: false },
lineNumbers: 'on',
fontSize: 14,
wordWrap: 'on',
scrollBeyondLastLine: false,
automaticLayout: true,
@@ -469,8 +470,9 @@ const RedisCommandEditor: React.FC<RedisCommandEditorProps> = ({ connectionId, r
overflow: 'auto',
background: darkMode ? '#111418' : '#f8fafc',
color: darkMode ? '#d4d4d4' : '#0f172a',
fontFamily: '"Consolas", "Courier New", monospace',
fontSize: 13,
fontFamily: 'var(--gn-font-mono)',
fontSize: 'var(--gn-font-size-mono, 13px)',
lineHeight: '1.62',
padding: 12,
}}
>

View File

@@ -1012,6 +1012,7 @@ const RedisViewer: React.FC<RedisViewerProps> = ({ connectionId, redisDB }) => {
</div>
<Editor
height="calc(100% - 72px)"
gonaviTypography="data"
language={isJson ? 'json' : 'plaintext'}
theme={darkMode ? 'transparent-dark' : 'transparent-light'}
value={displayValue}
@@ -1024,7 +1025,6 @@ const RedisViewer: React.FC<RedisViewerProps> = ({ connectionId, redisDB }) => {
automaticLayout: true,
folding: true,
formatOnPaste: true,
fontFamily: isBinary ? 'monospace' : undefined
}}
/>
<div style={{ padding: '8px 0', flexShrink: 0 }}>
@@ -1135,7 +1135,7 @@ const RedisViewer: React.FC<RedisViewerProps> = ({ connectionId, redisDB }) => {
<Tooltip title={<pre style={{ maxHeight: 300, overflow: 'auto', margin: 0, fontSize: 12 }}>{tooltipContent}</pre>} styles={{ root: { maxWidth: 600 } }}>
<span style={{
color: record.isBinary ? '#d46b08' : (record.isJson ? jsonAccentColor : undefined),
fontFamily: record.isBinary ? 'monospace' : undefined,
fontFamily: record.isBinary ? 'var(--gn-font-mono)' : undefined,
fontSize: record.isBinary ? 11 : undefined
}}>
{text}
@@ -1285,7 +1285,7 @@ const RedisViewer: React.FC<RedisViewerProps> = ({ connectionId, redisDB }) => {
<Tooltip title={<pre style={{ maxHeight: 300, overflow: 'auto', margin: 0, fontSize: 12 }}>{tooltipContent}</pre>} styles={{ root: { maxWidth: 600 } }}>
<span style={{
color: record.isBinary ? '#d46b08' : (record.isJson ? jsonAccentColor : undefined),
fontFamily: record.isBinary ? 'monospace' : undefined,
fontFamily: record.isBinary ? 'var(--gn-font-mono)' : undefined,
fontSize: record.isBinary ? 11 : undefined
}}>
{text}
@@ -1411,7 +1411,7 @@ const RedisViewer: React.FC<RedisViewerProps> = ({ connectionId, redisDB }) => {
<Tooltip title={<pre style={{ maxHeight: 300, overflow: 'auto', margin: 0, fontSize: 12 }}>{tooltipContent}</pre>} styles={{ root: { maxWidth: 600 } }}>
<span style={{
color: record.isBinary ? '#d46b08' : (record.isJson ? jsonAccentColor : undefined),
fontFamily: record.isBinary ? 'monospace' : undefined,
fontFamily: record.isBinary ? 'var(--gn-font-mono)' : undefined,
fontSize: record.isBinary ? 11 : undefined
}}>
{text}
@@ -1536,7 +1536,7 @@ const RedisViewer: React.FC<RedisViewerProps> = ({ connectionId, redisDB }) => {
<Tooltip title={<pre style={{ maxHeight: 300, overflow: 'auto', margin: 0, fontSize: 12 }}>{tooltipContent}</pre>} styles={{ root: { maxWidth: 600 } }}>
<span style={{
color: record.isBinary ? '#d46b08' : (record.isJson ? jsonAccentColor : undefined),
fontFamily: record.isBinary ? 'monospace' : undefined,
fontFamily: record.isBinary ? 'var(--gn-font-mono)' : undefined,
fontSize: record.isBinary ? 11 : undefined
}}>
{text}
@@ -1723,7 +1723,7 @@ const RedisViewer: React.FC<RedisViewerProps> = ({ connectionId, redisDB }) => {
<Tooltip title={<pre style={{ maxHeight: 300, overflow: 'auto', margin: 0, fontSize: 12 }}>{tooltipContent}</pre>} styles={{ root: { maxWidth: 720 } }}>
<span style={{
color: record.isBinary ? '#d46b08' : (record.isJson ? jsonAccentColor : undefined),
fontFamily: record.isBinary ? 'monospace' : undefined,
fontFamily: record.isBinary ? 'var(--gn-font-mono)' : undefined,
fontSize: record.isBinary ? 11 : undefined
}}>
{text}
@@ -1960,6 +1960,7 @@ const RedisViewer: React.FC<RedisViewerProps> = ({ connectionId, redisDB }) => {
>
<Editor
height="450px"
gonaviTypography="data"
language={formatRedisStringValue(editValue).isJson ? 'json' : 'plaintext'}
theme={darkMode ? 'transparent-dark' : 'transparent-light'}
value={editValue}
@@ -2050,6 +2051,7 @@ const RedisViewer: React.FC<RedisViewerProps> = ({ connectionId, redisDB }) => {
>
<Editor
height="450px"
gonaviTypography="data"
language={jsonEditConfig?.isJson ? 'json' : 'plaintext'}
theme={darkMode ? 'transparent-dark' : 'transparent-light'}
defaultValue={jsonEditConfig?.value || ''}

View File

@@ -97,7 +97,6 @@ import {
} from './V2TableContextMenu';
const { Search } = Input;
type SidebarContextMenuState = {
x: number;
y: number;
@@ -7139,11 +7138,18 @@ const Sidebar: React.FC<{
return (
<span
title={hoverTitle}
style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8, width: '100%' }}
className="gn-v2-tree-external-root"
>
<span style={{ minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
{statusBadge}
{displayTitle}
<span
className="gn-v2-tree-title"
data-node-type={node.type}
data-sidebar-node-key={String(node.key || '')}
data-sidebar-node-type={String(node.type || '')}
>
<span className="gn-v2-tree-label">
{statusBadge}
{displayTitle}
</span>
</span>
<Button
size="small"
@@ -7156,7 +7162,7 @@ const Sidebar: React.FC<{
event.stopPropagation();
void handleAddExternalSQLDirectory(node);
}}
style={{ paddingInline: 4, height: 20 }}
className="gn-v2-tree-external-root-action"
/>
</span>
);
@@ -7372,7 +7378,7 @@ const Sidebar: React.FC<{
const rootToken = buildSidebarRootConnectionToken(conn.id);
return (
<Tooltip key={conn.id} title={title}>
<Tooltip key={conn.id} title={title} placement="right">
<button
type="button"
className={`gn-v2-rail-item${conn.id === activeConnectionId ? ' is-active' : ''}`}
@@ -7477,7 +7483,7 @@ const Sidebar: React.FC<{
}}
>
{hasV2RailConnectionGroups && (
<Tooltip title={`${groupTitle} · ${group.connections.length} 个连接`}>
<Tooltip title={`${groupTitle} · ${group.connections.length} 个连接`} placement="right">
<button
type="button"
className={`gn-v2-rail-group-header${group.isUngrouped ? ' is-ungrouped' : ''}`}
@@ -7520,96 +7526,94 @@ const Sidebar: React.FC<{
const renderV2ConnectionRail = () => (
<div className="gn-v2-connection-rail" aria-label="系统操作">
<div className="gn-v2-rail-footer">
<div className="gn-v2-rail-action-group" aria-label="对象区快捷操作">
<Tooltip title="新建组">
<div className="gn-v2-rail-primary-actions" aria-label="对象区快捷操作">
<Tooltip title="新建组" placement="right">
<button
type="button"
className="gn-v2-rail-tool gn-v2-rail-action"
onClick={() => { setRenameViewTarget(null); createTagForm.resetFields(); setIsCreateTagModalOpen(true); }}
aria-label="新建组"
data-sidebar-create-group-action="true"
>
<FolderOpenOutlined />
</button>
</Tooltip>
<Tooltip title="批量操作表" placement="right">
<button
type="button"
className="gn-v2-rail-tool gn-v2-rail-action"
onClick={() => openBatchOperationModal()}
aria-label="批量操作表"
data-sidebar-batch-table-action="true"
>
<TableOutlined />
</button>
</Tooltip>
<Tooltip title="批量操作库" placement="right">
<button
type="button"
className="gn-v2-rail-tool gn-v2-rail-action"
onClick={() => openBatchDatabaseModal()}
aria-label="批量操作库"
data-sidebar-batch-database-action="true"
>
<DatabaseOutlined />
</button>
</Tooltip>
<Tooltip title="运行外部SQL文件" placement="right">
<button
type="button"
className="gn-v2-rail-tool gn-v2-rail-action"
onClick={handleOpenSQLFileFromToolbar}
aria-label="运行外部 SQL 文件"
data-sidebar-open-external-sql-file-action="true"
>
<FileAddOutlined />
</button>
</Tooltip>
<Tooltip title={canLocateActiveTab ? '定位当前打开表' : '当前标签页没有可定位的表'} placement="right">
<span className="gn-v2-rail-action-wrap">
<button
type="button"
className="gn-v2-rail-tool gn-v2-rail-action"
onClick={() => { setRenameViewTarget(null); createTagForm.resetFields(); setIsCreateTagModalOpen(true); }}
aria-label="新建组"
data-sidebar-create-group-action="true"
onClick={handleLocateActiveTabInSidebar}
aria-label="定位当前打开表"
data-sidebar-locate-current-tab-action="true"
disabled={!canLocateActiveTab}
>
<FolderOpenOutlined />
<AimOutlined />
</button>
</Tooltip>
<Tooltip title="批量操作表">
<button
type="button"
className="gn-v2-rail-tool gn-v2-rail-action"
onClick={() => openBatchOperationModal()}
aria-label="批量操作表"
data-sidebar-batch-table-action="true"
>
<TableOutlined />
</button>
</Tooltip>
<Tooltip title="批量操作库">
<button
type="button"
className="gn-v2-rail-tool gn-v2-rail-action"
onClick={() => openBatchDatabaseModal()}
aria-label="批量操作库"
data-sidebar-batch-database-action="true"
>
<DatabaseOutlined />
</button>
</Tooltip>
<Tooltip title="运行外部SQL文件">
<button
type="button"
className="gn-v2-rail-tool gn-v2-rail-action"
onClick={handleOpenSQLFileFromToolbar}
aria-label="运行外部 SQL 文件"
data-sidebar-open-external-sql-file-action="true"
>
<FileAddOutlined />
</button>
</Tooltip>
<Tooltip title={canLocateActiveTab ? '定位当前打开表' : '当前标签页没有可定位的表'}>
<span className="gn-v2-rail-action-wrap">
<button
type="button"
className="gn-v2-rail-tool gn-v2-rail-action"
onClick={handleLocateActiveTabInSidebar}
aria-label="定位当前打开表"
data-sidebar-locate-current-tab-action="true"
disabled={!canLocateActiveTab}
>
<AimOutlined />
</button>
</span>
</Tooltip>
</div>
<div className="gn-v2-rail-system-actions" aria-label="系统操作">
<Tooltip title="AI 助手">
<button
type="button"
className="gn-v2-rail-tool"
onClick={onToggleAI}
aria-label="AI 助手"
data-gonavi-ai-entry-action="true"
>
<RobotOutlined />
</button>
</Tooltip>
<Tooltip title="工具">
<button
type="button"
className="gn-v2-rail-tool"
onClick={onOpenTools}
aria-label="工具"
data-gonavi-open-tools-action="true"
>
<ToolOutlined />
</button>
</Tooltip>
<Tooltip title="设置">
<button type="button" className="gn-v2-rail-tool" onClick={onOpenSettings} aria-label="设置">
<SettingOutlined />
</button>
</Tooltip>
</div>
</span>
</Tooltip>
</div>
<div className="gn-v2-rail-secondary-actions" aria-label="系统操作">
<Tooltip title="AI 助手" placement="right">
<button
type="button"
className="gn-v2-rail-tool"
onClick={onToggleAI}
aria-label="AI 助手"
data-gonavi-ai-entry-action="true"
>
<RobotOutlined />
</button>
</Tooltip>
<Tooltip title="工具" placement="right">
<button
type="button"
className="gn-v2-rail-tool"
onClick={onOpenTools}
aria-label="工具"
data-gonavi-open-tools-action="true"
>
<ToolOutlined />
</button>
</Tooltip>
<Tooltip title="设置" placement="right">
<button type="button" className="gn-v2-rail-tool" onClick={onOpenSettings} aria-label="设置">
<SettingOutlined />
</button>
</Tooltip>
</div>
</div>
);
@@ -8384,7 +8388,7 @@ const Sidebar: React.FC<{
<div><strong style={{ color: '#52c41a' }}>{sqlFileExecState.executed}</strong> | <strong style={{ color: sqlFileExecState.failed > 0 ? '#ff4d4f' : undefined }}>{sqlFileExecState.failed}</strong> </div>
</div>
{sqlFileExecState.currentSQL && sqlFileExecState.status === 'running' && (
<div style={{ fontSize: 12, color: 'rgba(128,128,128,0.8)', background: 'rgba(128,128,128,0.06)', borderRadius: 6, padding: '6px 10px', marginTop: 8, fontFamily: 'monospace', wordBreak: 'break-all', maxHeight: 60, overflow: 'hidden' }}>
<div style={{ fontSize: 12, color: 'rgba(128,128,128,0.8)', background: 'rgba(128,128,128,0.06)', borderRadius: 6, padding: '6px 10px', marginTop: 8, fontFamily: 'var(--gn-font-mono)', wordBreak: 'break-all', maxHeight: 60, overflow: 'hidden' }}>
{sqlFileExecState.currentSQL}
</div>
)}

View File

@@ -12,7 +12,6 @@ import type { SqlSnippet } from '../types';
import { useStore } from '../store';
import { BUILTIN_SNIPPET_MAP } from '../utils/sqlSnippetDefaults';
import type { OverlayWorkbenchTheme } from '../utils/overlayWorkbenchTheme';
interface SnippetSettingsModalProps {
open: boolean;
onClose: () => void;
@@ -163,7 +162,7 @@ export default function SnippetSettingsModal({
key: 'syntax',
label: '片段语法说明',
children: (
<div style={{ fontSize: 12, lineHeight: 1.8, color: mutedColor, fontFamily: 'monospace' }}>
<div style={{ fontSize: 12, lineHeight: 1.8, color: mutedColor, fontFamily: 'var(--gn-font-mono)' }}>
<div>{'${1:占位符} 第一个 Tab 位,占位符为提示文字'}</div>
<div>{'${2:默认值} 第二个 Tab 位,默认值可直接确认'}</div>
<div>{'$0 最终光标位置'}</div>
@@ -361,7 +360,7 @@ export default function SnippetSettingsModal({
style={{
flex: 1,
minHeight: 120,
fontFamily: 'monospace',
fontFamily: 'var(--gn-font-mono)',
fontSize: 13,
resize: 'none',
}}

View File

@@ -1,6 +1,5 @@
import { useCallback, useEffect, useMemo, useRef } from 'react';
import Editor, { type BeforeMount, type OnMount } from './MonacoEditor';
interface TableDesignerSqlPreviewProps {
sql: string;
darkMode?: boolean;
@@ -25,7 +24,6 @@ export interface SqlChangeHighlight {
const SQL_PREVIEW_LIGHT_THEME = 'gonavi-sql-preview-light';
const SQL_PREVIEW_DARK_THEME = 'gonavi-sql-preview-dark';
const CHANGE_LINE_RULES: Array<{
kind: SqlChangeHighlightKind;
label: string;
@@ -231,7 +229,7 @@ const TableDesignerSqlPreview: React.FC<TableDesignerSqlPreviewProps> = ({
onMount={handleEditorMount}
options={{
automaticLayout: true,
fontFamily: '"JetBrains Mono", "Cascadia Code", Consolas, monospace',
fontFamily: 'var(--gn-font-mono)',
fontSize: 13,
lineNumbers: 'on',
lineDecorationsWidth: 14,

View File

@@ -16,7 +16,6 @@ import {
resolveJVMDiagnosticPlanTargetTabId,
} from '../../utils/jvmDiagnosticPlan';
import { buildAIReadonlyPreviewSQL } from '../../utils/aiSqlLimit';
// 🔧 性能优化:将 ReactMarkdown 包装为 Memo 组件并提取固定的 plugins
const remarkPlugins = [remarkGfm];
@@ -92,11 +91,11 @@ const AIToolResultItem: React.FC<{ resultMsg: AIChatMessage, darkMode: boolean,
>
{toolExpanded ? <CaretDownOutlined /> : <CaretRightOutlined />}
<ApiOutlined style={{ color: '#1677ff' }} />
<span> (<span style={{ fontFamily: 'monospace', color: overlayTheme.iconColor }}>{resultMsg.tool_name || 'unknown'}</span>)</span>
<span> (<span style={{ fontFamily: 'var(--gn-font-mono)', color: overlayTheme.iconColor }}>{resultMsg.tool_name || 'unknown'}</span>)</span>
<span style={{ fontSize: 11, marginLeft: 8, opacity: 0.6 }}>{charCount > 0 ? `${charCount} 个字符` : '无数据'}</span>
</div>
{toolExpanded && (
<div style={{ marginTop: 8, fontSize: 12, color: overlayTheme.mutedText, fontFamily: 'monospace', whiteSpace: 'pre-wrap', wordBreak: 'break-all', maxHeight: 300, overflowY: 'auto', background: darkMode ? 'rgba(0,0,0,0.2)' : 'rgba(0,0,0,0.03)', padding: 8, borderRadius: 6 }}>
<div style={{ marginTop: 8, fontSize: 12, color: overlayTheme.mutedText, fontFamily: 'var(--gn-font-mono)', whiteSpace: 'pre-wrap', wordBreak: 'break-all', maxHeight: 300, overflowY: 'auto', background: darkMode ? 'rgba(0,0,0,0.2)' : 'rgba(0,0,0,0.03)', padding: 8, borderRadius: 6 }}>
{resultMsg.content}
</div>
)}
@@ -291,7 +290,7 @@ const AIBlockHashRender = ({ match, darkMode, overlayTheme, children, activeConn
padding: '6px 12px', background: darkMode ? 'rgba(255,255,255,0.06)' : 'rgba(0,0,0,0.04)',
fontSize: 12, color: overlayTheme.mutedText
}}>
<span style={{ fontFamily: 'monospace' }}>{match[1]}</span>
<span style={{ fontFamily: 'var(--gn-font-mono)' }}>{match[1]}</span>
<div style={{ display: 'flex', gap: 12, alignItems: 'center' }}>
{isSql && <CodeRunBtn text={codeText} connectionId={activeConnectionId} dbName={activeDbName} />}
{isSelectQuery && activeConnectionConfig && (
@@ -332,7 +331,7 @@ const AIBlockHashRender = ({ match, darkMode, overlayTheme, children, activeConn
codeTagProps={{
style: {
fontSize: '14px',
fontFamily: 'Menlo, Monaco, Consolas, "Courier New", monospace'
fontFamily: 'var(--gn-font-mono)'
}
}}
>
@@ -383,7 +382,7 @@ const AIBlockHashRender = ({ match, darkMode, overlayTheme, children, activeConn
<span style={{ fontSize: 11, color: overlayTheme.mutedText, cursor: 'pointer' }} onClick={() => setPreviewExpanded(false)}> </span>
</div>
<div style={{ overflowX: 'auto', maxHeight: 200, overflowY: 'auto' }}>
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 11, fontFamily: 'monospace' }}>
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 11, fontFamily: 'var(--gn-font-mono)' }}>
<thead>
<tr>
{previewCols.map(col => (

View File

@@ -12,7 +12,6 @@ import {
formatJVMDiagnosticPhaseLabel,
formatJVMDiagnosticTransportLabel,
} from "../../utils/jvmDiagnosticPresentation";
const { Text } = Typography;
type JVMDiagnosticHistoryProps = {
@@ -62,7 +61,7 @@ const JVMDiagnosticHistory: React.FC<JVMDiagnosticHistoryProps> = ({
style={{
whiteSpace: "pre-wrap",
wordBreak: "break-word",
fontFamily: "SFMono-Regular, Menlo, Monaco, Consolas, monospace",
fontFamily: "var(--gn-font-mono)",
}}
>
{record.command}

View File

@@ -7,7 +7,6 @@ import {
formatJVMDiagnosticEventLabel,
formatJVMDiagnosticPhaseLabel,
} from "../../utils/jvmDiagnosticPresentation";
const { Text } = Typography;
type JVMDiagnosticOutputProps = {
@@ -44,7 +43,7 @@ const JVMDiagnosticOutput: React.FC<JVMDiagnosticOutputProps> = ({
style={{
whiteSpace: "pre-wrap",
wordBreak: "break-word",
fontFamily: "SFMono-Regular, Menlo, Monaco, Consolas, monospace",
fontFamily: "var(--gn-font-mono)",
}}
>
{chunkTexts[index]}

View File

@@ -19,8 +19,6 @@
File is self-contained — safe to import once at app entry.
────────────────────────────────────────────────────────────────────── */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap');
/* ─── Light tokens ─────────────────────────────────────── */
body[data-ui-version="v2"][data-theme="light"] {
--gn-font-sans: "Inter", "PingFang SC", -apple-system, BlinkMacSystemFont, "Helvetica Neue", "Segoe UI", sans-serif;
@@ -111,9 +109,15 @@ body[data-ui-version="v2"] {
font-size: var(--gn-font-size, var(--gonavi-font-size, 14px));
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-synthesis: none;
font-feature-settings: "cv11", "ss01";
}
body[data-ui-version="v2"][data-platform="darwin"] {
-webkit-font-smoothing: subpixel-antialiased;
-moz-osx-font-smoothing: auto;
}
body[data-ui-version="v2"] code,
body[data-ui-version="v2"] pre,
body[data-ui-version="v2"] kbd,
@@ -183,8 +187,8 @@ body[data-ui-version="v2"] .ant-tabs-tab.ant-tabs-tab-active::after {
}
body[data-ui-version="v2"] .ant-tabs-tab .ant-tabs-tab-btn {
font-family: var(--gn-font-mono);
font-size: 12.5px;
font-family: var(--gn-font-sans);
font-size: var(--gn-font-size-sm, 12px);
}
body[data-ui-version="v2"] .ant-tabs-ink-bar {
@@ -216,7 +220,7 @@ body[data-ui-version="v2"] .ant-tree .ant-tree-node-content-wrapper:hover {
body[data-ui-version="v2"] .ant-tree .ant-tree-node-content-wrapper.ant-tree-node-selected {
background: var(--gn-bg-selected) !important;
color: var(--gn-fg-1) !important;
font-weight: 600;
font-weight: 400;
}
/* table names — mono */
@@ -420,8 +424,8 @@ body[data-ui-version="v2"] .ant-table-tbody > tr.ant-table-row-selected > td {
/* ─── Tags / Badges ────────────────────────────────────── */
body[data-ui-version="v2"] .ant-tag {
font-family: var(--gn-font-mono);
font-size: 10.5px !important;
font-family: var(--gn-font-sans);
font-size: var(--gn-font-size-xs, 10px) !important;
font-weight: 600;
padding: 1px 6px !important;
border-radius: 4px !important;
@@ -627,7 +631,7 @@ body[data-ui-version="v2"] .gn-v2-data-grid-alt-toolbar > div:first-child {
body[data-ui-version="v2"] .gn-v2-data-grid-fields-head span,
body[data-ui-version="v2"] .gn-v2-data-grid-alt-toolbar span {
color: var(--gn-fg-5);
font-family: var(--gn-font-mono);
font-family: var(--gn-font-sans);
font-size: 10.5px;
font-weight: 650;
}
@@ -637,7 +641,7 @@ body[data-ui-version="v2"] .gn-v2-data-grid-alt-toolbar strong {
max-width: 520px;
overflow: hidden;
color: var(--gn-fg-1);
font-family: var(--gn-font-mono);
font-family: var(--gn-font-sans);
font-size: 13px;
font-weight: 750;
text-overflow: ellipsis;
@@ -936,6 +940,15 @@ body[data-ui-version="v2"] .monaco-editor .margin {
background-color: var(--gn-bg-panel) !important;
}
body[data-ui-version="v2"] .monaco-editor,
body[data-ui-version="v2"] .monaco-editor .margin,
body[data-ui-version="v2"] .monaco-editor .view-lines,
body[data-ui-version="v2"] .monaco-editor .view-line,
body[data-ui-version="v2"] .monaco-editor textarea,
body[data-ui-version="v2"] .monaco-editor .inputarea {
font-family: var(--gn-font-mono) !important;
}
/* ─── Progress ────────────────────────────────────────── */
body[data-ui-version="v2"] .ant-progress-bg {
background: var(--gn-accent) !important;
@@ -1407,13 +1420,14 @@ body[data-ui-version="v2"] .gn-v2-sidebar-redesign {
}
body[data-ui-version="v2"] .gn-v2-connection-rail {
width: calc(54px * var(--gn-ui-scale, 1));
flex: 0 0 calc(54px * var(--gn-ui-scale, 1));
width: calc(38px * var(--gn-ui-scale, 1));
flex: 0 0 calc(38px * var(--gn-ui-scale, 1));
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
gap: calc(4px * var(--gn-ui-scale, 1));
padding: calc(12px * var(--gn-ui-scale, 1)) 0;
padding: calc(8px * var(--gn-ui-scale, 1)) 0;
border-right: 0.5px solid var(--gn-br-1);
background: transparent;
}
@@ -1458,32 +1472,31 @@ body[data-ui-version="v2"] .gn-v2-rail-group + .gn-v2-rail-group {
body[data-ui-version="v2"] .gn-v2-rail-group-header {
position: relative;
width: calc(38px * var(--gn-ui-scale, 1));
height: calc(24px * var(--gn-ui-scale, 1));
width: 100%;
min-height: calc(24px * var(--gn-ui-scale, 1));
overflow: visible;
border: none;
border-radius: calc(7px * var(--gn-ui-scale, 1));
border-radius: 0;
display: flex;
align-items: center;
justify-content: center;
padding: 0;
background: var(--gn-bg-panel);
color: var(--gn-fg-4);
box-shadow: inset 0 0 0 0.5px var(--gn-br-1);
justify-content: flex-start;
gap: 4px;
padding: 0 4px 0 6px;
background: transparent;
color: var(--gn-fg-5);
box-shadow: none;
}
body[data-ui-version="v2"] .gn-v2-rail-group-header:hover {
background: var(--gn-bg-hover);
color: var(--gn-fg-2);
background: transparent;
color: var(--gn-fg-3);
}
body[data-ui-version="v2"] .gn-v2-rail-group-chevron {
position: absolute;
left: 4px;
bottom: 3px;
position: static;
display: inline-flex;
color: var(--gn-fg-5);
font-size: 10px;
font-size: 9px;
line-height: 1;
transform: rotate(0deg);
transition: transform 120ms ease;
@@ -1493,16 +1506,15 @@ body[data-ui-version="v2"] .gn-v2-rail-group.is-collapsed .gn-v2-rail-group-chev
transform: rotate(-90deg);
}
body[data-ui-version="v2"] .gn-v2-rail-group-badge {
max-width: 26px;
overflow: hidden;
color: var(--gn-fg-3);
font-family: var(--gn-font-mono);
font-size: 9px;
font-weight: 800;
body[data-ui-version="v2"] .gn-v2-rail-group-title {
min-width: 0;
color: var(--gn-fg-4);
font-size: 12px;
font-weight: 700;
line-height: 1;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
body[data-ui-version="v2"] .gn-v2-rail-group-count {
@@ -1533,12 +1545,22 @@ body[data-ui-version="v2"] .gn-v2-rail-group-items {
gap: calc(4px * var(--gn-ui-scale, 1));
}
body[data-ui-version="v2"] .gn-v2-rail-footer {
margin-top: auto;
body[data-ui-version="v2"] .gn-v2-rail-primary-actions,
body[data-ui-version="v2"] .gn-v2-rail-secondary-actions {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
gap: calc(10px * var(--gn-ui-scale, 1));
gap: calc(6px * var(--gn-ui-scale, 1));
}
body[data-ui-version="v2"] .gn-v2-rail-primary-actions {
padding-top: calc(4px * var(--gn-ui-scale, 1));
}
body[data-ui-version="v2"] .gn-v2-rail-secondary-actions {
margin-top: auto;
padding-bottom: calc(4px * var(--gn-ui-scale, 1));
}
body[data-ui-version="v2"] .gn-v2-rail-action-group,
@@ -1571,7 +1593,7 @@ body[data-ui-version="v2"] .gn-v2-rail-action:disabled {
body[data-ui-version="v2"] .gn-v2-rail-item,
body[data-ui-version="v2"] .gn-v2-rail-tool {
position: relative;
width: calc(38px * var(--gn-ui-scale, 1));
width: calc(36px * var(--gn-ui-scale, 1));
height: calc(38px * var(--gn-ui-scale, 1));
border-radius: calc(9px * var(--gn-ui-scale, 1));
border: none;
@@ -1581,12 +1603,16 @@ body[data-ui-version="v2"] .gn-v2-rail-tool {
display: flex;
align-items: center;
justify-content: center;
gap: 0;
padding: 0;
}
body[data-ui-version="v2"] .gn-v2-rail-tool {
width: calc(24px * var(--gn-ui-scale, 1));
height: calc(32px * var(--gn-ui-scale, 1));
border-radius: calc(8px * var(--gn-ui-scale, 1));
justify-content: center;
padding: 0;
}
body[data-ui-version="v2"] .gn-v2-rail-item:hover,
@@ -1638,14 +1664,21 @@ body[data-ui-version="v2"] .gn-v2-rail-badge {
letter-spacing: 0;
}
body[data-ui-version="v2"] .gn-v2-rail-badge-wrap {
position: relative;
width: 22px;
height: 22px;
flex: 0 0 22px;
}
body[data-ui-version="v2"] .gn-v2-rail-fallback {
display: none;
}
body[data-ui-version="v2"] .gn-v2-rail-status {
position: absolute;
right: 3px;
bottom: 3px;
right: -2px;
bottom: -2px;
width: 8px;
height: 8px;
border-radius: 50%;
@@ -1661,22 +1694,53 @@ body[data-ui-version="v2"] .gn-v2-rail-status.is-error {
background: var(--gn-danger);
}
body[data-ui-version="v2"] .gn-v2-rail-add {
body[data-ui-version="v2"] .gn-v2-rail-create-button {
margin-top: 4px;
border: 1.5px dashed var(--gn-br-2);
color: var(--gn-fg-4);
width: calc(40px * var(--gn-ui-scale, 1));
height: calc(40px * var(--gn-ui-scale, 1));
border: 1px dashed var(--gn-br-2);
border-radius: calc(14px * var(--gn-ui-scale, 1));
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 6px;
padding: 0;
color: var(--gn-fg-5);
background: transparent;
}
body[data-ui-version="v2"] .gn-v2-rail-create-button:hover {
background: color-mix(in srgb, var(--gn-bg-hover) 54%, transparent);
}
body[data-ui-version="v2"] .gn-v2-rail-create-icon {
width: 24px;
height: 24px;
border-radius: 8px;
display: inline-flex;
align-items: center;
justify-content: center;
background: color-mix(in srgb, var(--gn-bg-panel) 72%, var(--gn-accent) 28%);
color: var(--gn-accent);
box-shadow: inset 0 0 0 0.5px color-mix(in srgb, var(--gn-accent) 28%, transparent);
}
body[data-ui-version="v2"] .gn-v2-rail-create-label {
color: var(--gn-fg-5);
font-size: 10px;
line-height: 1;
}
body[data-ui-version="v2"] .gn-v2-object-explorer {
min-width: 0;
flex: 1 1 calc(100% - 54px);
flex: 1 1 calc(100% - 38px);
background: var(--gn-bg-panel-2);
overflow: hidden;
}
body[data-ui-version="v2"] .gn-v2-active-connection-header {
padding: 12px 14px 10px;
padding: 10px 10px 8px 12px;
min-height: 52px;
border-bottom: 0.5px solid var(--gn-br-1);
display: flex;
@@ -1684,6 +1748,21 @@ body[data-ui-version="v2"] .gn-v2-active-connection-header {
gap: 8px;
}
body[data-ui-version="v2"] .gn-v2-active-connection-trigger {
min-width: 0;
flex: 1 1 auto;
height: 34px;
display: flex;
align-items: center;
gap: 8px;
padding: 0 9px;
border: 0;
border-radius: 0;
background: transparent;
color: inherit;
text-align: left;
}
body[data-ui-version="v2"] .gn-v2-live-dot {
width: 7px;
height: 7px;
@@ -1703,7 +1782,7 @@ body[data-ui-version="v2"] .gn-v2-live-dot.is-error {
body[data-ui-version="v2"] .gn-v2-active-connection-copy {
min-width: 0;
flex: 1;
flex: 1 1 auto;
}
body[data-ui-version="v2"] .gn-v2-active-connection-copy strong,
@@ -1713,23 +1792,32 @@ body[data-ui-version="v2"] .gn-v2-active-connection-copy span {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-family: var(--gn-font-sans);
letter-spacing: 0;
}
body[data-ui-version="v2"] .gn-v2-active-connection-copy strong {
color: var(--gn-fg-1);
font-size: 13px;
font-weight: 700;
font-weight: 400;
line-height: 1.2;
}
body[data-ui-version="v2"] .gn-v2-active-connection-copy span {
margin-top: 2px;
color: var(--gn-fg-4);
font-family: var(--gn-font-mono);
font-size: 10.5px;
font-size: 11px;
font-weight: 400;
line-height: 1.2;
}
body[data-ui-version="v2"] .gn-v2-active-connection-actions {
display: inline-flex;
align-items: center;
gap: 2px;
flex: 0 0 auto;
}
body[data-ui-version="v2"] .gn-v2-explorer-search {
padding: 10px 10px 6px !important;
border-bottom: none !important;
@@ -2106,6 +2194,7 @@ body[data-ui-version="v2"] .gn-v2-explorer-tree-shell .ant-tree-node-content-wra
body[data-ui-version="v2"] .gn-v2-explorer-tree-shell .ant-tree-node-content-wrapper.ant-tree-node-selected {
background: var(--gn-bg-selected) !important;
color: var(--gn-fg-1) !important;
font-weight: 400 !important;
}
body[data-ui-version="v2"] .gn-v2-explorer-tree-shell .ant-tree-iconEle {
@@ -2132,6 +2221,21 @@ body[data-ui-version="v2"] .gn-v2-explorer-tree-shell .ant-tree-iconEle .anticon
color: var(--gn-fg-4);
}
body[data-ui-version="v2"] .gn-v2-explorer-tree-shell .gn-v2-tree-folder-icon {
width: 22px;
height: 22px;
display: inline-flex;
align-items: center;
justify-content: center;
flex: 0 0 22px;
}
body[data-ui-version="v2"] .gn-v2-explorer-tree-shell .gn-v2-tree-folder-icon .anticon-folder,
body[data-ui-version="v2"] .gn-v2-explorer-tree-shell .gn-v2-tree-folder-icon .anticon-folder-open {
color: #faad14;
font-size: 18px;
}
body[data-ui-version="v2"] .gn-v2-explorer-tree-shell .ant-tree-title {
min-width: 0;
display: inline-flex;
@@ -2158,20 +2262,28 @@ body[data-ui-version="v2"] .gn-v2-tree-title {
align-items: center;
gap: 5px;
color: var(--gn-fg-2);
font-family: var(--gn-font-sans);
font-size: var(--gn-sidebar-tree-font-size, var(--gn-font-size-sm, 12px));
font-weight: 650;
font-weight: 400 !important;
line-height: 1.25;
}
body[data-ui-version="v2"] .gn-v2-tree-title.is-mono .gn-v2-tree-label {
font-family: var(--gn-font-mono);
font-size: clamp(9px, calc(var(--gn-sidebar-tree-font-size, var(--gn-font-size-sm, 12px)) - 1px), 17px);
font-family: inherit;
font-size: inherit;
font-weight: 400 !important;
}
body[data-ui-version="v2"] .gn-v2-tree-title.is-group {
font-weight: 800;
font-weight: 400 !important;
color: var(--gn-fg-1);
}
body[data-ui-version="v2"] .gn-v2-tree-title.is-connection {
align-items: center;
gap: 6px;
}
body[data-ui-version="v2"] .gn-v2-tree-status {
position: relative;
margin: 0 2px 0 0;
@@ -2210,15 +2322,25 @@ body[data-ui-version="v2"] .gn-v2-tree-label {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
letter-spacing: 0;
font-weight: 400 !important;
}
body[data-ui-version="v2"] .gn-v2-tree-connection-copy {
min-width: 0;
flex: 1 1 auto;
color: var(--gn-fg-1);
font-weight: 400 !important;
}
body[data-ui-version="v2"] .gn-v2-tree-count {
flex: 0 0 auto;
color: var(--gn-fg-5);
font-family: var(--gn-font-mono);
font-size: clamp(9px, calc(var(--gn-sidebar-tree-font-size, var(--gn-font-size-sm, 12px)) - 2px), 16px);
font-weight: 650;
opacity: 0.78;
font-family: var(--gn-font-sans);
font-variant-numeric: tabular-nums;
font-size: clamp(10px, calc(var(--gn-sidebar-tree-font-size, var(--gn-font-size-sm, 12px)) - 1px), 16px);
font-weight: 400 !important;
opacity: 0.82;
}
body[data-ui-version="v2"] .gn-v2-table-pin-action {
@@ -2259,19 +2381,40 @@ body[data-ui-version="v2"] .gn-v2-object-explorer .ant-tree .ant-tree-node-conte
}
body[data-ui-version="v2"] .gn-v2-tree-title[data-node-type="database"] {
font-weight: 800;
font-weight: 400 !important;
color: var(--gn-fg-1);
}
body[data-ui-version="v2"] .gn-v2-tree-title[data-node-type="queries-folder"],
body[data-ui-version="v2"] .gn-v2-tree-title[data-node-type="external-sql-root"] {
color: var(--gn-fg-4);
font-weight: 650;
color: var(--gn-fg-1);
font-weight: 400 !important;
}
body[data-ui-version="v2"] .gn-v2-tree-title[data-group-key="views"],
body[data-ui-version="v2"] .gn-v2-tree-title[data-group-key="routines"] {
color: var(--gn-fg-4);
body[data-ui-version="v2"] .gn-v2-tree-external-root {
width: 100%;
min-width: 0;
display: inline-flex;
align-items: center;
justify-content: space-between;
gap: 8px;
}
body[data-ui-version="v2"] .gn-v2-tree-external-root .gn-v2-tree-title {
min-width: 0;
flex: 1 1 auto;
}
body[data-ui-version="v2"] .gn-v2-tree-external-root .gn-v2-tree-label {
display: inline-flex;
align-items: center;
gap: 6px;
}
body[data-ui-version="v2"] .gn-v2-tree-external-root-action {
flex: 0 0 auto;
padding-inline: 4px !important;
height: 20px !important;
}
body[data-ui-version="v2"] .gn-v2-tree-title[data-group-key="triggers"] {
@@ -2285,9 +2428,10 @@ body[data-ui-version="v2"] .gn-v2-tree-section-title {
height: 22px;
padding: 7px 4px 3px 2px;
color: var(--gn-fg-5);
font-size: clamp(9px, calc(var(--gn-sidebar-tree-font-size, var(--gn-font-size-sm, 12px)) - 2px), 16px);
font-weight: 800;
line-height: 1;
font-family: var(--gn-font-sans);
font-size: clamp(10px, calc(var(--gn-sidebar-tree-font-size, var(--gn-font-size-sm, 12px)) - 1px), 16px);
font-weight: 400;
line-height: 1.1;
letter-spacing: 0;
pointer-events: none;
user-select: none;
@@ -2333,7 +2477,7 @@ body[data-ui-version="v2"] .gn-v2-sidebar-log-button {
gap: 6px;
padding: 0 10px;
font-size: 12.5px;
font-weight: 700;
font-weight: 400;
}
body[data-ui-version="v2"] .gn-v2-sidebar-log-button:hover {
@@ -2345,7 +2489,7 @@ body[data-ui-version="v2"] .gn-v2-sidebar-log-button small {
color: var(--gn-fg-4);
font-family: var(--gn-font-mono);
font-size: 10.5px;
font-weight: 650;
font-weight: 400;
}
body[data-ui-version="v2"] .gn-v2-empty-workbench {
@@ -2424,6 +2568,7 @@ body[data-ui-version="v2"] .gn-v2-empty-panel {
display: flex;
flex-direction: column;
gap: 10px;
font-family: var(--gn-font-sans);
}
body[data-ui-version="v2"] .gn-v2-panel-heading {
@@ -2431,8 +2576,11 @@ body[data-ui-version="v2"] .gn-v2-panel-heading {
justify-content: space-between;
align-items: center;
color: var(--gn-fg-1);
font-family: var(--gn-font-sans);
font-size: 13px;
font-weight: 700;
line-height: 1.3;
letter-spacing: 0;
margin-bottom: 4px;
}
@@ -2449,6 +2597,7 @@ body[data-ui-version="v2"] .gn-v2-empty-panel button {
background: var(--gn-bg-panel-2);
color: var(--gn-fg-1);
cursor: pointer;
font-family: var(--gn-font-sans);
}
body[data-ui-version="v2"] .gn-v2-empty-panel button:hover {
@@ -2469,11 +2618,23 @@ body[data-ui-version="v2"] .gn-v2-empty-panel button > .anticon {
body[data-ui-version="v2"] .gn-v2-empty-panel strong,
body[data-ui-version="v2"] .gn-v2-empty-panel small {
display: block;
font-family: var(--gn-font-sans);
letter-spacing: 0;
}
body[data-ui-version="v2"] .gn-v2-empty-panel strong {
color: var(--gn-fg-1);
font-size: 13px;
font-weight: 650;
line-height: 1.35;
}
body[data-ui-version="v2"] .gn-v2-empty-panel small {
color: var(--gn-fg-4);
font-size: 11px;
margin-top: 3px;
font-weight: 500;
line-height: 1.45;
}
/* ─── Full V2 workbench shell: app / topbar / workspace ─ */
@@ -2764,7 +2925,7 @@ body[data-ui-version="v2"] .gn-v2-data-grid-toolbar-title strong {
color: var(--gn-fg-1);
font-family: var(--gn-font-mono);
font-size: 13.5px;
font-weight: 750;
font-weight: 400;
text-overflow: ellipsis;
}
@@ -2774,7 +2935,7 @@ body[data-ui-version="v2"] .gn-v2-data-grid-toolbar-title small {
color: var(--gn-fg-4);
font-family: var(--gn-font-mono);
font-size: 10.5px;
font-weight: 600;
font-weight: 400;
text-overflow: ellipsis;
}
@@ -2897,12 +3058,13 @@ body[data-ui-version="v2"] .gn-v2-data-grid .ant-table-thead > tr > th {
background: var(--gn-bg-panel-2) !important;
color: var(--gn-fg-2) !important;
font-family: var(--gn-font-mono) !important;
font-weight: 400 !important;
vertical-align: top !important;
}
body[data-ui-version="v2"] .gn-v2-data-grid .ant-table-thead .ant-table-column-title {
font-size: var(--gn-data-table-font-size, var(--gn-font-size-mono, 12px));
font-weight: 750;
font-weight: 400 !important;
}
body[data-ui-version="v2"] .gn-v2-data-grid .ant-table-thead .ant-table-column-sorters {
@@ -3007,7 +3169,7 @@ body[data-ui-version="v2"] .gn-v2-column-title-name {
color: var(--gn-fg-1);
font-family: var(--gn-font-mono);
font-size: var(--gn-font-size-sm, 12px);
font-weight: 750;
font-weight: 400 !important;
text-overflow: ellipsis;
white-space: nowrap;
}
@@ -3049,14 +3211,14 @@ body[data-ui-version="v2"] .gn-v2-column-title-type {
color: var(--gn-fg-4);
font-family: var(--gn-font-mono);
font-size: var(--gn-font-size-xs, 10px);
font-weight: 600;
font-weight: 400 !important;
}
body[data-ui-version="v2"] .gn-v2-column-title-comment {
color: var(--gn-fg-4);
font-family: var(--gn-font-sans);
font-family: var(--gn-font-mono);
font-size: var(--gn-font-size-xs, 10px);
font-weight: 500;
font-weight: 400 !important;
}
body[data-ui-version="v2"] .gn-v2-data-grid .react-resizable-handle::after {
@@ -4416,19 +4578,23 @@ body[data-ui-version="v2"] .gn-v2-table-overview-icon {
body[data-ui-version="v2"] .gn-v2-table-overview-title {
color: var(--gn-fg-1) !important;
font-family: var(--gn-font-mono);
font-family: var(--gn-font-sans);
font-size: 14px !important;
font-weight: 750 !important;
font-weight: 700 !important;
letter-spacing: 0;
}
body[data-ui-version="v2"] .gn-v2-table-overview-summary {
color: var(--gn-fg-4) !important;
font-family: var(--gn-font-mono);
font-family: var(--gn-font-sans);
font-weight: 500;
}
body[data-ui-version="v2"] .gn-v2-table-overview-summary strong {
color: var(--gn-fg-2);
font-weight: 750;
font-family: var(--gn-font-mono);
font-weight: 600;
font-variant-numeric: tabular-nums;
}
body[data-ui-version="v2"] .gn-v2-table-overview-content {