From 5a5fbf04fe918ad736309371b35ca52923c25f25 Mon Sep 17 00:00:00 2001 From: Syngnat Date: Thu, 23 Jul 2026 11:24:44 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(settings):=20=E6=8B=86?= =?UTF-8?q?=E5=88=86=20SQL=20=E7=BC=96=E8=BE=91=E5=99=A8=E4=B8=8E=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2=E7=BB=93=E6=9E=9C=E5=AD=97=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 SQL 编辑器独立字号与跟随全局配置 - 将查询编辑器和 DDL 预览接入 SQL 专用排版 - 迁移旧版耦合字号设置并保留原有视觉大小 - 更新六语种文案并补充持久化与组件测试 --- frontend/src/App.tool-center.test.ts | 6 ++ frontend/src/App.tsx | 83 ++++++++++++++++++ .../src/components/DataGridV2DdlWorkspace.tsx | 2 +- frontend/src/components/MonacoEditor.tsx | 16 +++- .../MonacoEditor.typography.test.tsx | 44 +++++++--- .../QueryEditor.results-and-drop.test.tsx | 29 +++++-- frontend/src/components/QueryEditor.tsx | 27 ++++-- frontend/src/store.test.ts | 34 +++++++- frontend/src/store.ts | 19 ++++- .../src/utils/sqlEditorTypography.test.ts | 42 ++++++++++ frontend/src/utils/sqlEditorTypography.ts | 84 +++++++++++++++++++ shared/i18n/de-DE.json | 5 +- shared/i18n/en-US.json | 5 +- shared/i18n/ja-JP.json | 5 +- shared/i18n/ru-RU.json | 5 +- shared/i18n/zh-CN.json | 5 +- shared/i18n/zh-TW.json | 5 +- 17 files changed, 373 insertions(+), 43 deletions(-) create mode 100644 frontend/src/utils/sqlEditorTypography.test.ts create mode 100644 frontend/src/utils/sqlEditorTypography.ts diff --git a/frontend/src/App.tool-center.test.ts b/frontend/src/App.tool-center.test.ts index 23d0ac95..4e5e9584 100644 --- a/frontend/src/App.tool-center.test.ts +++ b/frontend/src/App.tool-center.test.ts @@ -553,6 +553,7 @@ describe('global appearance tokens', () => { expect(appSource).toContain("t('app.theme.appearance.sidebar_rail_scale_hint')"); expect(appSource).toContain('v2SidebarRailScale: sanitizeV2SidebarRailScale(value)'); expect(appSource).toContain("t('app.theme.data_table.font_size')"); + expect(appSource).toContain("t('app.theme.data_table.sql_editor_font_size')"); expect(appSource).toContain("t('app.theme.data_table.sidebar_tree_font_size')"); expect(v2ThemeCss).toContain('--gn-sidebar-rail-scale'); expect(v2ThemeCss).toContain('font-size: calc(var(--gn-font-size-sm, 12px) * var(--gn-sidebar-rail-scale, 1));'); @@ -595,14 +596,19 @@ describe('global appearance tokens', () => { expect(appSource).toContain('matchFontFamilyOption'); expect(appSource).toContain('showSearch'); expect(appSource).toContain('const dataTableFontSizeFollowsGlobal = appearance.dataTableFontSizeFollowGlobal !== false;'); + expect(appSource).toContain('const sqlEditorFontSizeFollowsGlobal = appearance.sqlEditorFontSizeFollowGlobal !== false;'); expect(appSource).toContain('const sidebarTreeFontSizeFollowsGlobal = appearance.sidebarTreeFontSizeFollowGlobal !== false;'); expect(appSource).toContain('disabled={dataTableFontSizeFollowsGlobal}'); + expect(appSource).toContain('disabled={sqlEditorFontSizeFollowsGlobal}'); expect(appSource).toContain('disabled={sidebarTreeFontSizeFollowsGlobal}'); expect(appSource).toContain("type={dataTableFontSizeFollowsGlobal ? 'primary' : 'default'}"); + expect(appSource).toContain("type={sqlEditorFontSizeFollowsGlobal ? 'primary' : 'default'}"); expect(appSource).toContain("type={sidebarTreeFontSizeFollowsGlobal ? 'primary' : 'default'}"); expect(appSource).toContain('dataTableFontSizeFollowGlobal: !dataTableFontSizeFollowsGlobal'); + expect(appSource).toContain('sqlEditorFontSizeFollowGlobal: !sqlEditorFontSizeFollowsGlobal'); expect(appSource).toContain('sidebarTreeFontSizeFollowGlobal: !sidebarTreeFontSizeFollowsGlobal'); expect(appSource).toContain('dataTableFontSize: dataTableFontSizeFollowsGlobal'); + expect(appSource).toContain('sqlEditorFontSize: sqlEditorFontSizeFollowsGlobal'); expect(appSource).toContain('sidebarTreeFontSize: sidebarTreeFontSizeFollowsGlobal'); }); diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index ddd1c1c1..36f3c9de 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -58,6 +58,12 @@ import { sanitizeDataTableFontSize, sanitizeSidebarTreeFontSize, } from './utils/dataGridDisplay'; +import { + MAX_SQL_EDITOR_FONT_SIZE, + MIN_SQL_EDITOR_FONT_SIZE, + resolveSqlEditorFontSize, + sanitizeSqlEditorFontSize, +} from './utils/sqlEditorTypography'; import { TAB_DISPLAY_SECONDARY_DEFAULT_KEYS, TAB_DISPLAY_ELEMENT_META, @@ -272,6 +278,10 @@ const DATA_TABLE_FONT_SLIDER_MARKS: Record = { 16: '16', 18: '18', }; +const SQL_EDITOR_FONT_SLIDER_MARKS: Record = { + ...DATA_TABLE_FONT_SLIDER_MARKS, + 20: '20', +}; const DEFAULT_UI_SCALE = 1.0; const DEFAULT_FONT_SIZE = 14; const EMPTY_INSTALLED_FONT_FAMILIES: InstalledFontFamily[] = []; @@ -765,10 +775,16 @@ function App() { const tokenControlHeightSM = Math.max(20, Math.round(24 * effectiveUiScale)); const tokenControlHeightLG = Math.max(30, Math.round(40 * effectiveUiScale)); const dataTableFontSizeFollowsGlobal = appearance.dataTableFontSizeFollowGlobal !== false; + const sqlEditorFontSizeFollowsGlobal = appearance.sqlEditorFontSizeFollowGlobal !== false; const sidebarTreeFontSizeFollowsGlobal = appearance.sidebarTreeFontSizeFollowGlobal !== false; const effectiveDataTableFontSize = dataTableFontSizeFollowsGlobal ? effectiveFontSize : (sanitizeDataTableFontSize(appearance.dataTableFontSize) ?? effectiveFontSize); + const effectiveSqlEditorFontSize = resolveSqlEditorFontSize({ + globalFontSize: effectiveFontSize, + sqlEditorFontSize: appearance.sqlEditorFontSize, + sqlEditorFontSizeFollowGlobal: appearance.sqlEditorFontSizeFollowGlobal, + }); const effectiveSidebarTreeFontSize = sidebarTreeFontSizeFollowsGlobal ? effectiveFontSize : (sanitizeSidebarTreeFontSize(appearance.sidebarTreeFontSize) ?? effectiveFontSize); @@ -5941,6 +5957,41 @@ function App() { /> ), })} + {renderThemeSettingsRow({ + label: ( + + {t('app.theme.data_table.sql_editor_font_size')} + + + ), + stacked: true, + control: ( + setAppearance({ + sqlEditorFontSize: sanitizeSqlEditorFontSize(value), + sqlEditorFontSizeFollowGlobal: false, + })} + /> + ), + })} {renderThemeSettingsRow({ label: ( @@ -6682,6 +6733,38 @@ function App() { {t('app.theme.data_table.density_hint')} +
+
+
{t('app.theme.data_table.sql_editor_font_size')}
+ +
+
+ setAppearance({ + sqlEditorFontSize: sanitizeSqlEditorFontSize(value), + sqlEditorFontSizeFollowGlobal: false, + })} + style={{ flex: 1 }} + /> + {effectiveSqlEditorFontSize}px +
+
{t('app.theme.data_table.font_size')}
diff --git a/frontend/src/components/DataGridV2DdlWorkspace.tsx b/frontend/src/components/DataGridV2DdlWorkspace.tsx index 6122cd92..1d9cee69 100644 --- a/frontend/src/components/DataGridV2DdlWorkspace.tsx +++ b/frontend/src/components/DataGridV2DdlWorkspace.tsx @@ -137,7 +137,7 @@ export const DataGridV2DdlView: React.FC = ({
= ({ const uiVersion = useStore((state) => state.appearance.uiVersion); const dataTableFontSize = useStore((state) => state.appearance.dataTableFontSize); const dataTableFontSizeFollowGlobal = useStore((state) => state.appearance.dataTableFontSizeFollowGlobal); + const sqlEditorFontSize = useStore((state) => state.appearance.sqlEditorFontSize); + const sqlEditorFontSizeFollowGlobal = useStore((state) => state.appearance.sqlEditorFontSizeFollowGlobal); const monoFontFamily = useStore((state) => state.appearance.customMonoFontFamily); const globalFontSize = useStore((state) => state.fontSize); @@ -917,9 +920,16 @@ const MonacoEditor: React.FC = ({ const effectiveDataTableFontSize = dataTableFontSizeFollowGlobal !== false ? effectiveGlobalFontSize : (sanitizeDataTableFontSize(dataTableFontSize) ?? effectiveGlobalFontSize); + const effectiveSqlEditorFontSize = resolveSqlEditorFontSize({ + globalFontSize: effectiveGlobalFontSize, + sqlEditorFontSize, + sqlEditorFontSizeFollowGlobal, + }); const resolvedFontSize = gonaviTypography === 'data' ? effectiveDataTableFontSize - : Math.max(10, Math.round(effectiveDataTableFontSize * 0.92)); + : gonaviTypography === 'sql' + ? effectiveSqlEditorFontSize + : Math.max(10, Math.round(effectiveDataTableFontSize * 0.92)); const effectiveEditorFontSize = Math.max( 10, Math.round(Number(options?.fontSize) || resolvedFontSize), @@ -939,6 +949,8 @@ const MonacoEditor: React.FC = ({ gonaviTypography, monoFontFamily, options, + sqlEditorFontSize, + sqlEditorFontSizeFollowGlobal, uiVersion, ]); diff --git a/frontend/src/components/MonacoEditor.typography.test.tsx b/frontend/src/components/MonacoEditor.typography.test.tsx index 52cb1c17..90510612 100644 --- a/frontend/src/components/MonacoEditor.typography.test.tsx +++ b/frontend/src/components/MonacoEditor.typography.test.tsx @@ -6,17 +6,19 @@ import MonacoEditor from './MonacoEditor'; const storeState = vi.hoisted(() => ({ fontSize: 14, - appearance: { - enabled: true, - opacity: 1, - blur: 0, - uiVersion: 'v2' as 'legacy' | 'v2', - customUIFontFamily: null as string | null, - customMonoFontFamily: null as string | null, - showDataTableVerticalBorders: false, - dataTableDensity: 'comfortable' as const, - dataTableFontSize: null as number | null, + appearance: { + enabled: true, + opacity: 1, + blur: 0, + uiVersion: 'v2' as 'legacy' | 'v2', + customUIFontFamily: null as string | null, + customMonoFontFamily: null as string | null, + showDataTableVerticalBorders: false, + dataTableDensity: 'comfortable' as const, + dataTableFontSize: null as number | null, dataTableFontSizeFollowGlobal: true, + sqlEditorFontSize: null as number | null, + sqlEditorFontSizeFollowGlobal: true, sidebarTreeFontSize: null as number | null, sidebarTreeFontSizeFollowGlobal: true, }, @@ -47,6 +49,8 @@ describe('MonacoEditor typography', () => { dataTableDensity: 'comfortable', dataTableFontSize: null, dataTableFontSizeFollowGlobal: true, + sqlEditorFontSize: null, + sqlEditorFontSizeFollowGlobal: true, sidebarTreeFontSize: null, sidebarTreeFontSizeFollowGlobal: true, }; @@ -86,6 +90,26 @@ describe('MonacoEditor typography', () => { expect(markup).toContain('"lineHeight":24'); }); + it('keeps SQL-editor and data-editor font sizes independent in v2', () => { + storeState.fontSize = 16; + storeState.appearance.sqlEditorFontSizeFollowGlobal = false; + storeState.appearance.sqlEditorFontSize = 18; + storeState.appearance.dataTableFontSizeFollowGlobal = false; + storeState.appearance.dataTableFontSize = 11; + + const sqlMarkup = renderToStaticMarkup( + , + ); + const dataMarkup = renderToStaticMarkup( + , + ); + + expect(sqlMarkup).toContain('"fontSize":18'); + expect(sqlMarkup).toContain('"lineHeight":29'); + expect(dataMarkup).toContain('"fontSize":11'); + expect(dataMarkup).toContain('"lineHeight":18'); + }); + it('keeps legacy editors on their explicit font settings', () => { storeState.appearance.uiVersion = 'legacy'; diff --git a/frontend/src/components/QueryEditor.results-and-drop.test.tsx b/frontend/src/components/QueryEditor.results-and-drop.test.tsx index 0527aa95..6067e366 100644 --- a/frontend/src/components/QueryEditor.results-and-drop.test.tsx +++ b/frontend/src/components/QueryEditor.results-and-drop.test.tsx @@ -55,8 +55,16 @@ const storeState = vi.hoisted(() => ({ savedQueries: [] as SavedQuery[], saveQuery: vi.fn(), theme: 'light', + fontSize: 14, languagePreference: 'zh-CN' as 'zh-CN' | 'en-US', - appearance: { uiVersion: 'legacy' as 'legacy' | 'v2' }, + appearance: { + uiVersion: 'legacy' as 'legacy' | 'v2', + customMonoFontFamily: null as string | null, + dataTableFontSize: null as number | null, + dataTableFontSizeFollowGlobal: true, + sqlEditorFontSize: null as number | null, + sqlEditorFontSizeFollowGlobal: true, + }, sqlFormatOptions: { keywordCase: 'upper' as const }, setSqlFormatOptions: vi.fn(), queryOptions: { @@ -813,7 +821,13 @@ describe('QueryEditor external SQL save', () => { storeState.clearSqlLogs.mockReset(); storeState.connections[0].config.type = 'mysql'; storeState.connections[0].config.database = 'main'; + storeState.fontSize = 14; storeState.appearance.uiVersion = 'legacy'; + storeState.appearance.customMonoFontFamily = null; + storeState.appearance.dataTableFontSize = null; + storeState.appearance.dataTableFontSizeFollowGlobal = true; + storeState.appearance.sqlEditorFontSize = null; + storeState.appearance.sqlEditorFontSizeFollowGlobal = true; autoFetchState.visible = false; dataGridState.latestProps = null; tabsState.activeKey = undefined; @@ -968,7 +982,12 @@ describe('QueryEditor external SQL save', () => { renderer?.unmount(); }); - it('disables sticky scroll for object-edit query tabs', async () => { + it('disables sticky scroll for object-edit query tabs without overriding shared typography', async () => { + storeState.appearance.uiVersion = 'v2'; + storeState.appearance.dataTableFontSize = 11; + storeState.appearance.dataTableFontSizeFollowGlobal = false; + storeState.appearance.sqlEditorFontSize = 18; + storeState.appearance.sqlEditorFontSizeFollowGlobal = false; storeState.connections[0].config.type = 'oracle'; storeState.connections[0].config.database = 'ORCLPDB1'; const plsql = [ @@ -985,12 +1004,10 @@ describe('QueryEditor external SQL save', () => { }); expect(editorState.latestOptions?.stickyScroll?.enabled).toBe(false); - expect(editorState.latestOptions?.fontSize).toBe(14); - expect(editorState.latestOptions?.lineHeight).toBe(24); + expect(editorState.latestOptions?.fontSize).toBe(18); + expect(editorState.latestOptions?.lineHeight).toBe(29); expect(editorState.latestOptions?.lineNumbersMinChars).toBe(4); expect(editorState.editor.updateOptions).toHaveBeenCalledWith(expect.objectContaining({ - fontSize: 14, - lineHeight: 24, lineNumbersMinChars: 4, stickyScroll: { enabled: false }, })); diff --git a/frontend/src/components/QueryEditor.tsx b/frontend/src/components/QueryEditor.tsx index 75e139fb..190e3fac 100644 --- a/frontend/src/components/QueryEditor.tsx +++ b/frontend/src/components/QueryEditor.tsx @@ -312,7 +312,11 @@ const buildQueryEditorInlineMemoryEntries = ({ .map((entry) => ({ sql: entry.sql })); }; -const buildQueryEditorMonacoOptions = (isObjectEditQueryTab: boolean, wordWrapEnabled = false) => ({ +const buildQueryEditorMonacoOptions = ( + isObjectEditQueryTab: boolean, + wordWrapEnabled = false, + preserveLegacyObjectEditTypography = false, +) => ({ minimap: { enabled: false }, automaticLayout: true, fixedOverflowWidgets: true, @@ -332,8 +336,9 @@ const buildQueryEditorMonacoOptions = (isObjectEditQueryTab: boolean, wordWrapEn inlineSuggest: buildQueryEditorAiInlineSuggestOptions(), ...(isObjectEditQueryTab ? { - fontSize: 14, - lineHeight: 24, + ...(preserveLegacyObjectEditTypography + ? { fontSize: 14, lineHeight: 24 } + : {}), lineNumbersMinChars: 4, stickyScroll: { enabled: false }, } @@ -1261,8 +1266,12 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc const isExternalSQLFileTab = Boolean(String(tab.filePath || '').trim()); const isObjectEditQueryTab = tab.type === 'query' && tab.queryMode === 'object-edit'; const queryEditorMonacoOptions = useMemo( - () => buildQueryEditorMonacoOptions(isObjectEditQueryTab, wordWrapEnabled), - [isObjectEditQueryTab, wordWrapEnabled], + () => buildQueryEditorMonacoOptions( + isObjectEditQueryTab, + wordWrapEnabled, + appearance.uiVersion !== 'v2', + ), + [appearance.uiVersion, isObjectEditQueryTab, wordWrapEnabled], ); type ResultSet = QueryEditorResultSet; @@ -3721,7 +3730,11 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc if (mountedModel && typeof monaco?.editor?.setModelLanguage === 'function') { monaco.editor.setModelLanguage(mountedModel, 'sql'); } - editor.updateOptions?.(buildQueryEditorMonacoOptions(isObjectEditQueryTab, wordWrapEnabled)); + editor.updateOptions?.(buildQueryEditorMonacoOptions( + isObjectEditQueryTab, + wordWrapEnabled, + !isV2Ui, + )); aiInlineGhostVisibleContextKeyRef.current = editor.createContextKey?.( QUERY_EDITOR_AI_INLINE_CONTEXT_KEY, @@ -8948,7 +8961,7 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc > { expect(appearance.dataTableDensity).toBe('comfortable'); expect(appearance.dataTableFontSize).toBeNull(); expect(appearance.dataTableFontSizeFollowGlobal).toBe(true); + expect(appearance.sqlEditorFontSize).toBeNull(); + expect(appearance.sqlEditorFontSizeFollowGlobal).toBe(true); expect(appearance.sidebarTreeFontSize).toBeNull(); expect(appearance.sidebarTreeFontSizeFollowGlobal).toBe(true); expect(appearance.customUIFontFamily).toBeNull(); @@ -99,6 +101,32 @@ describe('store appearance persistence', () => { }); }); + it('migrates the coupled data-table font into an independent SQL editor font', async () => { + storage.setItem('lite-db-storage', JSON.stringify({ + state: { + appearance: { + uiVersion: 'v2', + dataTableFontSize: 18, + dataTableFontSizeFollowGlobal: false, + }, + }, + version: 18, + })); + + const { useStore } = await importStore(); + const appearance = useStore.getState().appearance; + + expect(appearance.dataTableFontSize).toBe(18); + expect(appearance.dataTableFontSizeFollowGlobal).toBe(false); + expect(appearance.sqlEditorFontSize).toBe(17); + expect(appearance.sqlEditorFontSizeFollowGlobal).toBe(false); + + const persisted = JSON.parse(storage.getItem('lite-db-storage') || '{}'); + expect(persisted.version).toBe(19); + expect(persisted.state.appearance.sqlEditorFontSize).toBe(17); + expect(persisted.state.appearance.sqlEditorFontSizeFollowGlobal).toBe(false); + }); + it('migrates an existing legacy UI selection to V2', async () => { storage.setItem('lite-db-storage', JSON.stringify({ state: { @@ -113,7 +141,7 @@ describe('store appearance persistence', () => { expect(useStore.getState().appearance.uiVersion).toBe('v2'); const persisted = JSON.parse(storage.getItem('lite-db-storage') || '{}'); - expect(persisted.version).toBe(18); + expect(persisted.version).toBe(19); expect(persisted.state.appearance.uiVersion).toBe('v2'); }); @@ -1293,7 +1321,7 @@ describe('store appearance persistence', () => { )).toEqual(legacyTag?.childOrder); const persisted = JSON.parse(storage.getItem('lite-db-storage') || '{}'); - expect(persisted.version).toBe(18); + expect(persisted.version).toBe(19); expect(persisted.state.connectionTags[0].childOrder).toEqual([ 'connection:conn-a', 'connection:conn-b', @@ -3094,7 +3122,7 @@ describe('store appearance persistence', () => { mac: { combo: 'Meta+K', enabled: false }, windows: { combo: 'Ctrl+K', enabled: true }, }); - expect(JSON.parse(storage.getItem('lite-db-storage') || '{}').version).toBe(18); + expect(JSON.parse(storage.getItem('lite-db-storage') || '{}').version).toBe(19); storage.setItem('lite-db-storage', JSON.stringify({ state: { diff --git a/frontend/src/store.ts b/frontend/src/store.ts index 7658dc4e..28554796 100644 --- a/frontend/src/store.ts +++ b/frontend/src/store.ts @@ -48,6 +48,12 @@ import { sanitizeDataGridDisplaySettings, type DataGridDisplaySettings, } from "./utils/dataGridDisplay"; +import { + DEFAULT_SQL_EDITOR_TYPOGRAPHY_SETTINGS, + migrateLegacySqlEditorTypographySettings, + sanitizeSqlEditorTypographySettings, + type SqlEditorTypographySettings, +} from "./utils/sqlEditorTypography"; import { normalizeOceanBaseProtocol, resolveOceanBaseProtocolFromConfig, @@ -144,7 +150,8 @@ export type ThemePreference = ThemeMode | "system"; /** AI 聊天默认打开形态:侧栏 / 独立浮动窗 */ export type AIChatOpenMode = "dock" | "detached"; -export interface AppearanceSettings extends DataGridDisplaySettings { +export interface AppearanceSettings + extends DataGridDisplaySettings, SqlEditorTypographySettings { uiVersion: "legacy" | "v2"; enabled: boolean; opacity: number; @@ -185,6 +192,7 @@ export const DEFAULT_APPEARANCE: AppearanceSettings = { tabDisplay: DEFAULT_TAB_DISPLAY_SETTINGS, redisDbAliases: DEFAULT_REDIS_DB_ALIASES, ...DEFAULT_DATA_GRID_DISPLAY_SETTINGS, + ...DEFAULT_SQL_EDITOR_TYPOGRAPHY_SETTINGS, }; const DEFAULT_UI_SCALE = 1.0; const MIN_UI_SCALE = 0.8; @@ -258,7 +266,8 @@ const MIN_KEEPALIVE_INTERVAL_MINUTES = 1; const MAX_KEEPALIVE_INTERVAL_MINUTES = 1440; const DEFAULT_DIAGNOSTIC_TIMEOUT_SECONDS = 15; const MAX_DIAGNOSTIC_TIMEOUT_SECONDS = 300; -const PERSIST_VERSION = 18; +const PERSIST_VERSION = 19; +const SQL_EDITOR_FONT_SIZE_SPLIT_VERSION = 19; const UI_VERSION_V2_MIGRATION_VERSION = 14; const SIDEBAR_SEARCH_SHORTCUT_MIGRATION_VERSION = 18; const PERSIST_STORAGE_KEY = "lite-db-storage"; @@ -2901,6 +2910,9 @@ const sanitizeAppearance = ( return { ...DEFAULT_APPEARANCE }; } const dataGridDisplaySettings = sanitizeDataGridDisplaySettings(appearance); + const sqlEditorTypographySettings = version < SQL_EDITOR_FONT_SIZE_SPLIT_VERSION + ? migrateLegacySqlEditorTypographySettings(dataGridDisplaySettings) + : sanitizeSqlEditorTypographySettings(appearance); const nextAppearance = { uiVersion: appearance.uiVersion === "v2" || appearance.uiVersion === "legacy" @@ -2953,6 +2965,9 @@ const sanitizeAppearance = ( dataTableFontSize: dataGridDisplaySettings.dataTableFontSize, dataTableFontSizeFollowGlobal: dataGridDisplaySettings.dataTableFontSizeFollowGlobal, + sqlEditorFontSize: sqlEditorTypographySettings.sqlEditorFontSize, + sqlEditorFontSizeFollowGlobal: + sqlEditorTypographySettings.sqlEditorFontSizeFollowGlobal, sidebarTreeFontSize: dataGridDisplaySettings.sidebarTreeFontSize, sidebarTreeFontSizeFollowGlobal: dataGridDisplaySettings.sidebarTreeFontSizeFollowGlobal, diff --git a/frontend/src/utils/sqlEditorTypography.test.ts b/frontend/src/utils/sqlEditorTypography.test.ts new file mode 100644 index 00000000..735da856 --- /dev/null +++ b/frontend/src/utils/sqlEditorTypography.test.ts @@ -0,0 +1,42 @@ +import { describe, expect, it } from 'vitest'; + +import { + migrateLegacySqlEditorTypographySettings, + resolveSqlEditorFontSize, + sanitizeSqlEditorTypographySettings, +} from './sqlEditorTypography'; + +describe('SQL editor typography', () => { + it('derives a code-sized value while following the global font size', () => { + expect(resolveSqlEditorFontSize({ + globalFontSize: 14, + sqlEditorFontSize: 20, + sqlEditorFontSizeFollowGlobal: true, + })).toBe(13); + }); + + it('uses and clamps an independent SQL editor font size', () => { + expect(resolveSqlEditorFontSize({ + globalFontSize: 14, + sqlEditorFontSize: 99, + sqlEditorFontSizeFollowGlobal: false, + })).toBe(20); + expect(sanitizeSqlEditorTypographySettings({ + sqlEditorFontSize: 9, + sqlEditorFontSizeFollowGlobal: false, + })).toEqual({ + sqlEditorFontSize: 10, + sqlEditorFontSizeFollowGlobal: false, + }); + }); + + it('preserves the legacy editor size derived from a custom data-table font', () => { + expect(migrateLegacySqlEditorTypographySettings({ + dataTableFontSize: 18, + dataTableFontSizeFollowGlobal: false, + })).toEqual({ + sqlEditorFontSize: 17, + sqlEditorFontSizeFollowGlobal: false, + }); + }); +}); diff --git a/frontend/src/utils/sqlEditorTypography.ts b/frontend/src/utils/sqlEditorTypography.ts new file mode 100644 index 00000000..b5c9cb14 --- /dev/null +++ b/frontend/src/utils/sqlEditorTypography.ts @@ -0,0 +1,84 @@ +export interface SqlEditorTypographySettings { + sqlEditorFontSize: number | null; + sqlEditorFontSizeFollowGlobal: boolean; +} + +export const MIN_SQL_EDITOR_FONT_SIZE = 10; +export const MAX_SQL_EDITOR_FONT_SIZE = 20; +export const DEFAULT_SQL_EDITOR_FONT_SCALE = 0.92; + +export const DEFAULT_SQL_EDITOR_TYPOGRAPHY_SETTINGS: SqlEditorTypographySettings = { + sqlEditorFontSize: null, + sqlEditorFontSizeFollowGlobal: true, +}; + +export const sanitizeSqlEditorFontSize = (value: unknown): number | null => { + if (value === null || value === undefined || value === '') return null; + const numeric = Number(value); + if (!Number.isFinite(numeric)) return null; + return Math.min( + MAX_SQL_EDITOR_FONT_SIZE, + Math.max(MIN_SQL_EDITOR_FONT_SIZE, Math.round(numeric)), + ); +}; + +export const resolveSqlEditorFontSize = ({ + globalFontSize, + sqlEditorFontSize, + sqlEditorFontSizeFollowGlobal, +}: { + globalFontSize: number; + sqlEditorFontSize: unknown; + sqlEditorFontSizeFollowGlobal: unknown; +}): number => { + const normalizedGlobalFontSize = Number.isFinite(Number(globalFontSize)) + ? Number(globalFontSize) + : 14; + const globalDerivedFontSize = sanitizeSqlEditorFontSize( + Math.round(normalizedGlobalFontSize * DEFAULT_SQL_EDITOR_FONT_SCALE), + ) ?? 13; + + if (sqlEditorFontSizeFollowGlobal !== false) { + return globalDerivedFontSize; + } + + return sanitizeSqlEditorFontSize(sqlEditorFontSize) ?? globalDerivedFontSize; +}; + +export const sanitizeSqlEditorTypographySettings = ( + value: Partial | undefined, +): SqlEditorTypographySettings => { + if (!value || typeof value !== 'object') { + return { ...DEFAULT_SQL_EDITOR_TYPOGRAPHY_SETTINGS }; + } + + const sqlEditorFontSize = sanitizeSqlEditorFontSize(value.sqlEditorFontSize); + return { + sqlEditorFontSize, + sqlEditorFontSizeFollowGlobal: typeof value.sqlEditorFontSizeFollowGlobal === 'boolean' + ? value.sqlEditorFontSizeFollowGlobal + : sqlEditorFontSize === null, + }; +}; + +export const migrateLegacySqlEditorTypographySettings = ({ + dataTableFontSize, + dataTableFontSizeFollowGlobal, +}: { + dataTableFontSize: unknown; + dataTableFontSizeFollowGlobal: unknown; +}): SqlEditorTypographySettings => { + if (dataTableFontSizeFollowGlobal !== false) { + return { ...DEFAULT_SQL_EDITOR_TYPOGRAPHY_SETTINGS }; + } + + const sanitizedDataTableFontSize = sanitizeSqlEditorFontSize(dataTableFontSize); + return { + sqlEditorFontSize: sanitizedDataTableFontSize === null + ? null + : sanitizeSqlEditorFontSize( + Math.round(sanitizedDataTableFontSize * DEFAULT_SQL_EDITOR_FONT_SCALE), + ), + sqlEditorFontSizeFollowGlobal: false, + }; +}; diff --git a/shared/i18n/de-DE.json b/shared/i18n/de-DE.json index 22347aa4..02ebeb9b 100644 --- a/shared/i18n/de-DE.json +++ b/shared/i18n/de-DE.json @@ -2930,13 +2930,14 @@ "app.theme.data_table.density.standard": "Standard", "app.theme.data_table.density_hint": "Steuert Zeilenhöhe, Spaltenbreite und Innenabstand. Komfortabel eignet sich für große Bildschirme; kompakt maximiert die Informationsdichte. Manuell gezogene Spaltenbreiten bleiben vorrangig erhalten.", "app.theme.data_table.follow_global": "Global folgen", - "app.theme.data_table.font_size": "Schriftgröße der Datentabelle", + "app.theme.data_table.font_size": "Schriftgröße für Datentabellen und Abfrageergebnisse", "app.theme.data_table.sidebar_tree_font_size": "Schriftgröße des linken Schemasbaums", + "app.theme.data_table.sql_editor_font_size": "Schriftgröße des SQL-Editors", "app.theme.data_table.table_double_click_action": "Doppelklick-Aktion für Tabellen", "app.theme.data_table.table_double_click_action.open_data": "Daten öffnen", "app.theme.data_table.table_double_click_action.open_design": "Objektdesign öffnen", "app.theme.data_table.table_double_click_action_hint": "Steuert die Standardaktion beim Doppelklick auf Tabellenknoten im linken Schemasbaum. Standardmäßig werden Tabellendaten geöffnet; optional kann direkt das Objektdesign geöffnet werden.", - "app.theme.data_table.title": "Datentabellenanzeige", + "app.theme.data_table.title": "Editor- und Datentabellenanzeige", "app.theme.data_table.vertical_borders": "Vertikale Trennlinien der Datentabelle anzeigen", "app.theme.data_table.vertical_borders_hint": "Wirkt nur auf DataGrid auf Datentabellenseiten und nicht auf andere Tabellenkomponenten.", "app.theme.data_table.row_number": "Zeilennummern der Datentabelle anzeigen", diff --git a/shared/i18n/en-US.json b/shared/i18n/en-US.json index 83ad8ca3..a1367abc 100644 --- a/shared/i18n/en-US.json +++ b/shared/i18n/en-US.json @@ -2930,13 +2930,14 @@ "app.theme.data_table.density.standard": "Standard", "app.theme.data_table.density_hint": "Controls row height, column width, and padding. Comfortable is better for large-screen review; compact maximizes information density. Manually dragged column widths are preserved first.", "app.theme.data_table.follow_global": "Follow Global", - "app.theme.data_table.font_size": "Data Table Font Size", + "app.theme.data_table.font_size": "Data Table and Query Result Font Size", "app.theme.data_table.sidebar_tree_font_size": "Sidebar Schema Tree Font Size", + "app.theme.data_table.sql_editor_font_size": "SQL Editor Font Size", "app.theme.data_table.table_double_click_action": "Table Double-click Action", "app.theme.data_table.table_double_click_action.open_data": "Open data", "app.theme.data_table.table_double_click_action.open_design": "Open object design", "app.theme.data_table.table_double_click_action_hint": "Controls the default action when double-clicking table nodes in the left schema tree. The default opens table data; switch it to open object design directly.", - "app.theme.data_table.title": "Data Table Display", + "app.theme.data_table.title": "Editor and Data Table Display", "app.theme.data_table.vertical_borders": "Show Data Table Vertical Separators", "app.theme.data_table.vertical_borders_hint": "Only affects DataGrid on data table pages, not other table components.", "app.theme.data_table.row_number": "Show Data Table Row Numbers", diff --git a/shared/i18n/ja-JP.json b/shared/i18n/ja-JP.json index 632fad0a..6a4b21ca 100644 --- a/shared/i18n/ja-JP.json +++ b/shared/i18n/ja-JP.json @@ -2930,13 +2930,14 @@ "app.theme.data_table.density.standard": "標準", "app.theme.data_table.density_hint": "行の高さ、列幅、余白を制御します。ゆったりは大画面での確認に適し、コンパクトは情報密度を最大化します。手動でドラッグした列幅は優先して保持されます。", "app.theme.data_table.follow_global": "全体設定に従う", - "app.theme.data_table.font_size": "データテーブルのフォントサイズ", + "app.theme.data_table.font_size": "データテーブルとクエリ結果のフォントサイズ", "app.theme.data_table.sidebar_tree_font_size": "左側スキーマツリーのフォントサイズ", + "app.theme.data_table.sql_editor_font_size": "SQL エディターのフォントサイズ", "app.theme.data_table.table_double_click_action": "テーブルのダブルクリック動作", "app.theme.data_table.table_double_click_action.open_data": "データを開く", "app.theme.data_table.table_double_click_action.open_design": "オブジェクト設計を開く", "app.theme.data_table.table_double_click_action_hint": "左側スキーマツリーでテーブルノードをダブルクリックしたときの既定動作を制御します。既定ではテーブルデータを開き、直接オブジェクト設計を開くよう切り替えられます。", - "app.theme.data_table.title": "データテーブル表示", + "app.theme.data_table.title": "エディターとデータテーブル表示", "app.theme.data_table.vertical_borders": "データテーブルの縦区切り線を表示", "app.theme.data_table.vertical_borders_hint": "データテーブルページの DataGrid のみに作用し、他のテーブルコンポーネントには影響しません。", "app.theme.data_table.row_number": "データテーブルの行番号を表示", diff --git a/shared/i18n/ru-RU.json b/shared/i18n/ru-RU.json index b20954e1..c2931ea8 100644 --- a/shared/i18n/ru-RU.json +++ b/shared/i18n/ru-RU.json @@ -2930,13 +2930,14 @@ "app.theme.data_table.density.standard": "Стандартная", "app.theme.data_table.density_hint": "Управляет высотой строк, шириной столбцов и отступами. Комфортная плотность удобна для больших экранов; компактная увеличивает плотность информации. Ширина столбцов, измененная вручную, сохраняется в приоритете.", "app.theme.data_table.follow_global": "Следовать глобально", - "app.theme.data_table.font_size": "Размер шрифта таблицы данных", + "app.theme.data_table.font_size": "Размер шрифта таблиц данных и результатов запросов", "app.theme.data_table.sidebar_tree_font_size": "Размер шрифта левого дерева схем", + "app.theme.data_table.sql_editor_font_size": "Размер шрифта редактора SQL", "app.theme.data_table.table_double_click_action": "Действие двойного щелчка по таблице", "app.theme.data_table.table_double_click_action.open_data": "Открыть данные", "app.theme.data_table.table_double_click_action.open_design": "Открыть дизайн объекта", "app.theme.data_table.table_double_click_action_hint": "Управляет действием по умолчанию при двойном щелчке по таблице в левом дереве схем. По умолчанию открываются данные таблицы; можно переключить на прямое открытие дизайна объекта.", - "app.theme.data_table.title": "Отображение таблицы данных", + "app.theme.data_table.title": "Отображение редактора и таблиц данных", "app.theme.data_table.vertical_borders": "Показывать вертикальные разделители таблицы данных", "app.theme.data_table.vertical_borders_hint": "Влияет только на DataGrid на страницах таблиц данных и не затрагивает другие табличные компоненты.", "app.theme.data_table.row_number": "Показывать номера строк таблицы данных", diff --git a/shared/i18n/zh-CN.json b/shared/i18n/zh-CN.json index 9ff35793..c5580718 100644 --- a/shared/i18n/zh-CN.json +++ b/shared/i18n/zh-CN.json @@ -2930,13 +2930,14 @@ "app.theme.data_table.density.standard": "标准", "app.theme.data_table.density_hint": "控制行高、列宽和内边距。舒适适合大屏细看;紧凑适合最大化信息密度。已手动拖拽的列宽优先保留。", "app.theme.data_table.follow_global": "跟随全局", - "app.theme.data_table.font_size": "数据表字体大小", + "app.theme.data_table.font_size": "数据表与查询结果字体大小", "app.theme.data_table.sidebar_tree_font_size": "左侧库表字体大小", + "app.theme.data_table.sql_editor_font_size": "SQL 编辑器字体大小", "app.theme.data_table.table_double_click_action": "双击表名行为", "app.theme.data_table.table_double_click_action.open_data": "打开表数据", "app.theme.data_table.table_double_click_action.open_design": "打开对象设计", "app.theme.data_table.table_double_click_action_hint": "控制左侧库表树中双击表节点的默认动作。默认打开表数据,可切换为直接打开表对象设计。", - "app.theme.data_table.title": "数据表显示", + "app.theme.data_table.title": "编辑器与数据表显示", "app.theme.data_table.vertical_borders": "显示数据表竖向分隔线", "app.theme.data_table.vertical_borders_hint": "仅作用于数据表页面 DataGrid,不影响其他表格组件。", "app.theme.data_table.row_number": "显示数据表行号", diff --git a/shared/i18n/zh-TW.json b/shared/i18n/zh-TW.json index eb1877f8..749953ef 100644 --- a/shared/i18n/zh-TW.json +++ b/shared/i18n/zh-TW.json @@ -2930,13 +2930,14 @@ "app.theme.data_table.density.standard": "標準", "app.theme.data_table.density_hint": "控制列高、欄寬和內距。舒適適合大螢幕細看;緊湊適合最大化資訊密度。已手動拖曳的欄寬會優先保留。", "app.theme.data_table.follow_global": "跟隨全域", - "app.theme.data_table.font_size": "資料表字型大小", + "app.theme.data_table.font_size": "資料表與查詢結果字型大小", "app.theme.data_table.sidebar_tree_font_size": "左側庫表字型大小", + "app.theme.data_table.sql_editor_font_size": "SQL 編輯器字型大小", "app.theme.data_table.table_double_click_action": "雙擊表名行為", "app.theme.data_table.table_double_click_action.open_data": "開啟表資料", "app.theme.data_table.table_double_click_action.open_design": "開啟物件設計", "app.theme.data_table.table_double_click_action_hint": "控制左側庫表樹中雙擊表節點的預設動作。預設開啟表資料,可切換為直接開啟表物件設計。", - "app.theme.data_table.title": "資料表显示", + "app.theme.data_table.title": "編輯器與資料表顯示", "app.theme.data_table.vertical_borders": "显示資料表竖向分隔线", "app.theme.data_table.vertical_borders_hint": "仅作用于資料表页面 DataGrid,不影响其他表格组件。", "app.theme.data_table.row_number": "顯示資料表行號",