mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-07-23 22:20:13 +08:00
🐛 fix(ui): 统一新版界面字体并调整左侧快捷操作布局
- 统一新版界面字体变量在侧边栏、AI 面板、日志、DDL、Redis 与数据视图中的落地使用 - 调整 v2 左侧 rail 布局,将新建组、批量操作表、批量操作库、运行外部 SQL 文件、定位当前打开表迁移到顶部主操作区 - 收敛新版侧边栏树节点、连接信息、字段表头与字段描述的字体与字重表现 - 让 Monaco 编辑器、数据预览和代码展示区域跟随新的 UI/Mono 字体配置 - Refs #510
This commit is contained in:
@@ -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}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -7333,6 +7333,7 @@ const ConnectionModal: React.FC<{
|
||||
wordBreak: "break-all",
|
||||
lineHeight: "20px",
|
||||
fontSize: 13,
|
||||
fontFamily: "var(--gn-font-mono)",
|
||||
}}
|
||||
>
|
||||
{String(testResult?.message || "暂无失败日志")}
|
||||
|
||||
@@ -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)),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
}}
|
||||
|
||||
@@ -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>)}
|
||||
|
||||
@@ -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>
|
||||
) : (
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 }}>
|
||||
|
||||
@@ -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,
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -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 || ''}
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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',
|
||||
}}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 => (
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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]}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user