diff --git a/apps/desktop/src/components/editor/EditorSettingsDialog.vue b/apps/desktop/src/components/editor/EditorSettingsDialog.vue index 7b9d80cf48..b94925915c 100644 --- a/apps/desktop/src/components/editor/EditorSettingsDialog.vue +++ b/apps/desktop/src/components/editor/EditorSettingsDialog.vue @@ -264,6 +264,7 @@ function createEmptyTableColumnTemplateRow(): TableColumnTemplateGridRow { // Local edit state const editFontFamily = ref(settingsStore.editorSettings.fontFamily); const editFontSize = ref(settingsStore.editorSettings.fontSize); +const editTableFontFamily = ref(settingsStore.editorSettings.tableFontFamily); const editUiFontFamily = ref(settingsStore.editorSettings.uiFontFamily); const editUiScale = ref(settingsStore.editorSettings.uiScale); const editTheme = ref(settingsStore.editorSettings.theme); @@ -418,6 +419,7 @@ function currentEditorSettingsDraft(): EditorSettingsDraft { return { fontFamily: editFontFamily.value, fontSize: editFontSize.value, + tableFontFamily: editTableFontFamily.value, uiFontFamily: editUiFontFamily.value, uiScale: editUiScale.value, theme: editTheme.value, @@ -651,6 +653,7 @@ const systemFontOptions = computed(() => { const options = new Set(FONT_FAMILIES.map((font) => font.value)); for (const font of systemFonts.value) options.add(cssFontFamilyForName(font)); if (editFontFamily.value) options.add(editFontFamily.value); + if (editTableFontFamily.value) options.add(editTableFontFamily.value); return [...options]; }); @@ -698,6 +701,7 @@ async function loadSystemFontOptions() { function syncEditorSettingsDraftFromStore() { editFontFamily.value = settingsStore.editorSettings.fontFamily; editFontSize.value = settingsStore.editorSettings.fontSize; + editTableFontFamily.value = settingsStore.editorSettings.tableFontFamily; editUiFontFamily.value = settingsStore.editorSettings.uiFontFamily; editUiScale.value = settingsStore.editorSettings.uiScale; editTheme.value = settingsStore.editorSettings.theme; @@ -915,6 +919,7 @@ function resetDefaultsForTab(tab: SettingsCategory) { editSqlFormatter.value = normalizeSqlFormatterSettings(DEFAULT_EDITOR_SETTINGS.sqlFormatter); sqlFormatterConfigValid.value = true; } else if (tab === "appearance") { + editTableFontFamily.value = DEFAULT_EDITOR_SETTINGS.tableFontFamily; editUiFontFamily.value = DEFAULT_EDITOR_SETTINGS.uiFontFamily; editUiScale.value = DEFAULT_EDITOR_SETTINGS.uiScale; editTheme.value = DEFAULT_EDITOR_SETTINGS.theme; @@ -973,6 +978,7 @@ function resetDefaultsForTab(tab: SettingsCategory) { function resetAllDefaults() { editFontFamily.value = DEFAULT_EDITOR_SETTINGS.fontFamily; editFontSize.value = DEFAULT_EDITOR_SETTINGS.fontSize; + editTableFontFamily.value = DEFAULT_EDITOR_SETTINGS.tableFontFamily; editUiFontFamily.value = DEFAULT_EDITOR_SETTINGS.uiFontFamily; editUiScale.value = DEFAULT_EDITOR_SETTINGS.uiScale; editTheme.value = DEFAULT_EDITOR_SETTINGS.theme; @@ -1174,6 +1180,10 @@ function onFontFamilyChange(v: any) { if (typeof v === "string") editFontFamily.value = v; } +function onTableFontFamilyChange(v: any) { + if (typeof v === "string") editTableFontFamily.value = v; +} + function onUiFontFamilyChange(v: any) { if (typeof v === "string") editUiFontFamily.value = v; } @@ -3430,7 +3440,7 @@ onUnmounted(cleanupPreviewEditor); -
+
@@ -3485,7 +3495,36 @@ onUnmounted(cleanupPreviewEditor);
-
+
+
+ + +

{{ t("settings.uiScaleDescription") }}

+
+
+
+ +
+
+ +
+
+

{{ t("settings.uiFontFamilyDescription") }}

@@ -3524,28 +3563,42 @@ onUnmounted(cleanupPreviewEditor);
-
- - -

{{ t("settings.uiScaleDescription") }}

+
+ + +

{{ t("settings.dataGridFontFamilyDescription") }}

- + + + +
@@ -5712,13 +5765,9 @@ onUnmounted(cleanupPreviewEditor); box-shadow: 0 0 0 2px rgba(96, 165, 250, 0.45) !important; } -.settings-appearance-section > * + * { - margin-top: 1.25rem; -} - .settings-appearance-top-grid { display: grid; - grid-template-columns: repeat(4, minmax(0, 1fr)); + grid-template-columns: repeat(3, minmax(0, 1fr)); column-gap: 0.75rem; row-gap: 1rem; } diff --git a/apps/desktop/src/components/grid/DataGrid.vue b/apps/desktop/src/components/grid/DataGrid.vue index 6aa6a3126e..1e2e9a7c60 100644 --- a/apps/desktop/src/components/grid/DataGrid.vue +++ b/apps/desktop/src/components/grid/DataGrid.vue @@ -1785,9 +1785,10 @@ function scrollToColumnIndex(columnIndex: number) { // --- Column resize composable --- const columnWidthDensity = computed(() => settingsStore.editorSettings.columnWidthDensity); +const tableFontFamily = computed(() => settingsStore.editorSettings.tableFontFamily); const columnWidthCacheKey = computed(() => props.cacheKey?.trim() || undefined); const columnStructureSignature = computed(() => createDataGridColumnStructureSignature(props.result.columns, props.result.column_types)); -const columnHeaderMeasurementKey = computed(() => [tableFontSize.value, settingsStore.editorSettings.fontFamily]); +const columnHeaderMeasurementKey = computed(() => [tableFontSize.value, tableFontFamily.value]); let columnHeaderMeasureContext: CanvasRenderingContext2D | null | undefined; function measureColumnHeaderText(text: string): number | undefined { @@ -1795,8 +1796,7 @@ function measureColumnHeaderText(text: string): number | undefined { if (columnHeaderMeasureContext === undefined) columnHeaderMeasureContext = document.createElement("canvas").getContext("2d"); if (!columnHeaderMeasureContext) return undefined; // Match the rendered semibold header font instead of estimating proportional glyphs by character count. - const fontFamily = getComputedStyle(gridRef.value ?? document.body).fontFamily || "sans-serif"; - columnHeaderMeasureContext.font = `600 ${tableFontSize.value}px ${fontFamily}`; + columnHeaderMeasureContext.font = `600 ${tableFontSize.value}px ${tableFontFamily.value}`; return Math.ceil(columnHeaderMeasureContext.measureText(text).width); } @@ -1816,6 +1816,7 @@ const gridStyle = computed(() => ({ "--header-total-w": dataGridHeaderContentWidth("var(--total-w)", gridScrollbarGutter.value), "--grid-scrollbar-gutter": `${gridScrollbarGutter.value}px`, [EDITOR_FONT_FAMILY_CSS_VAR]: settingsStore.editorSettings.fontFamily, + "--dbx-data-grid-font-family": tableFontFamily.value, "--dbx-table-font-size": `${tableFontSize.value}px`, })); const gridHorizontalScrollLeft = ref(0); @@ -4384,7 +4385,7 @@ const canvasSurfaceWidth = computed(() => { if (vw <= 0) return total; return Math.min(vw, total); }); -const canvasRenderStyleKey = computed(() => `${settingsStore.editorSettings.theme}:${settingsStore.editorSettings.uiScale}:${canvasBackingPixelRatio.value}:${isDark.value}:${themePalette.value}:${settingsStore.editorSettings.fontFamily}:${tableFontSize.value}`); +const canvasRenderStyleKey = computed(() => `${settingsStore.editorSettings.theme}:${settingsStore.editorSettings.uiScale}:${canvasBackingPixelRatio.value}:${isDark.value}:${themePalette.value}:${tableFontFamily.value}:${tableFontSize.value}`); const CANVAS_MOUSE_WHEEL_SCROLL_MULTIPLIER = 1.5; const CANVAS_TRACKPAD_DELTA_THRESHOLD = 40; let canvasPixelRatioMediaQuery: MediaQueryList | null = null; diff --git a/apps/desktop/src/i18n/locales/en.ts b/apps/desktop/src/i18n/locales/en.ts index e86533ef63..59bb585a0f 100644 --- a/apps/desktop/src/i18n/locales/en.ts +++ b/apps/desktop/src/i18n/locales/en.ts @@ -3640,6 +3640,8 @@ export default { securityTab: "Security", aboutTab: "About", fontFamily: "Font Family", + dataGridFontFamily: "Result Grid Font", + dataGridFontFamilyDescription: "Applies to query results, table data, column headers, and inline cell editors.", uiFontFamily: "Interface Font", uiFontAppDefault: "DBX Default", uiFontSystemDefault: "System Default", diff --git a/apps/desktop/src/i18n/locales/es.ts b/apps/desktop/src/i18n/locales/es.ts index e1db1efdce..2540882034 100644 --- a/apps/desktop/src/i18n/locales/es.ts +++ b/apps/desktop/src/i18n/locales/es.ts @@ -3418,6 +3418,8 @@ export default withEnglishFallback({ securityTab: "Seguridad", aboutTab: "Acerca de", fontFamily: "Familia tipográfica", + dataGridFontFamily: "Fuente de la cuadrícula de resultados", + dataGridFontFamilyDescription: "Se aplica a resultados de consultas, datos de tablas, encabezados de columnas y editores dentro de las celdas.", uiFontFamily: "Fuente de la interfaz", uiFontAppDefault: "Predeterminada de DBX", uiFontSystemDefault: "Predeterminada del sistema", diff --git a/apps/desktop/src/i18n/locales/it.ts b/apps/desktop/src/i18n/locales/it.ts index 89faf435b8..b5ddd87a39 100644 --- a/apps/desktop/src/i18n/locales/it.ts +++ b/apps/desktop/src/i18n/locales/it.ts @@ -3416,6 +3416,8 @@ export default withEnglishFallback({ securityTab: "Sicurezza", aboutTab: "Informazioni", fontFamily: "Famiglia Carattere (Font)", + dataGridFontFamily: "Font della griglia risultati", + dataGridFontFamilyDescription: "Si applica ai risultati delle query, ai dati delle tabelle, alle intestazioni delle colonne e agli editor nelle celle.", uiFontFamily: "Carattere interfaccia", uiFontAppDefault: "Predefinito DBX", uiFontSystemDefault: "Predefinito di sistema", diff --git a/apps/desktop/src/i18n/locales/ja.ts b/apps/desktop/src/i18n/locales/ja.ts index 8f72fda5ab..b5d9a46cc2 100644 --- a/apps/desktop/src/i18n/locales/ja.ts +++ b/apps/desktop/src/i18n/locales/ja.ts @@ -3417,6 +3417,8 @@ export default withEnglishFallback({ securityTab: "セキュリティ", aboutTab: "バージョン情報", fontFamily: "フォントファミリー", + dataGridFontFamily: "結果グリッドのフォント", + dataGridFontFamilyDescription: "クエリ結果、テーブルデータ、列ヘッダー、セル内エディターに適用します。", uiFontFamily: "インターフェイスフォント", uiFontAppDefault: "DBX デフォルト", uiFontSystemDefault: "システムデフォルト", diff --git a/apps/desktop/src/i18n/locales/pt-BR.ts b/apps/desktop/src/i18n/locales/pt-BR.ts index 8505a0425e..159b390fc8 100644 --- a/apps/desktop/src/i18n/locales/pt-BR.ts +++ b/apps/desktop/src/i18n/locales/pt-BR.ts @@ -3418,6 +3418,8 @@ export default withEnglishFallback({ securityTab: "Segurança", aboutTab: "Sobre", fontFamily: "Família da fonte", + dataGridFontFamily: "Fonte da grade de resultados", + dataGridFontFamilyDescription: "Aplica-se aos resultados de consultas, dados de tabelas, cabeçalhos de colunas e editores dentro das células.", uiFontFamily: "Fonte da interface", uiFontAppDefault: "Padrão do DBX", uiFontSystemDefault: "Padrão do sistema", diff --git a/apps/desktop/src/i18n/locales/zh-CN.ts b/apps/desktop/src/i18n/locales/zh-CN.ts index 74d97afea5..03e59624ba 100644 --- a/apps/desktop/src/i18n/locales/zh-CN.ts +++ b/apps/desktop/src/i18n/locales/zh-CN.ts @@ -3630,6 +3630,8 @@ export default withEnglishFallback({ securityTab: "安全", aboutTab: "关于我们", fontFamily: "字体", + dataGridFontFamily: "查询结果网格字体", + dataGridFontFamilyDescription: "用于查询结果、表数据、列标题和单元格内编辑器。", uiFontFamily: "界面字体", uiFontAppDefault: "DBX 默认", uiFontSystemDefault: "系统默认", diff --git a/apps/desktop/src/i18n/locales/zh-TW.ts b/apps/desktop/src/i18n/locales/zh-TW.ts index 98da272eba..95cbd516eb 100644 --- a/apps/desktop/src/i18n/locales/zh-TW.ts +++ b/apps/desktop/src/i18n/locales/zh-TW.ts @@ -3234,6 +3234,8 @@ export default withEnglishFallback({ tunnelsSocksConnectRejected: "SOCKS 代理連線被拒(代碼 {code})", tunnelsSocksUnsupportedAddrType: "不支援的 SOCKS 綁定位址類型:{type}", fontFamily: "字型", + dataGridFontFamily: "查詢結果網格字型", + dataGridFontFamilyDescription: "套用於查詢結果、資料表資料、欄位標題及儲存格內編輯器。", uiFontFamily: "介面字型", uiFontAppDefault: "DBX 預設", uiFontSystemDefault: "系統預設", diff --git a/apps/desktop/src/lib/app/appFonts.ts b/apps/desktop/src/lib/app/appFonts.ts index 47bd7584bc..baa8469a91 100644 --- a/apps/desktop/src/lib/app/appFonts.ts +++ b/apps/desktop/src/lib/app/appFonts.ts @@ -2,5 +2,7 @@ export const APP_FONT_SANS_CSS_VAR = "--font-sans"; export const DEFAULT_UI_FONT_FAMILY = `"Geist Variable", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Segoe UI", system-ui, sans-serif`; +export const DEFAULT_DATA_GRID_FONT_FAMILY = `"Geist Variable Tabular", "Geist Variable", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif`; + // Native-feeling UI option without DBX's bundled/brand font at the front of the stack. export const SYSTEM_UI_FONT_FAMILY = `system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif`; diff --git a/apps/desktop/src/lib/settings/editorSettingsDraft.ts b/apps/desktop/src/lib/settings/editorSettingsDraft.ts index 7aa8984cf0..df29d2f8ee 100644 --- a/apps/desktop/src/lib/settings/editorSettingsDraft.ts +++ b/apps/desktop/src/lib/settings/editorSettingsDraft.ts @@ -4,6 +4,7 @@ import { normalizeResultPageSize } from "@/lib/dataGrid/paginationPageSize"; export const EDITOR_SETTINGS_DRAFT_KEYS = [ "fontFamily", "fontSize", + "tableFontFamily", "uiFontFamily", "uiScale", "theme", diff --git a/apps/desktop/src/stores/settingsStore.ts b/apps/desktop/src/stores/settingsStore.ts index 8ab0f839fe..18dbef47ea 100644 --- a/apps/desktop/src/stores/settingsStore.ts +++ b/apps/desktop/src/stores/settingsStore.ts @@ -14,7 +14,7 @@ import type { SqlSnippet } from "@/types/database"; import { DEFAULT_SQL_SNIPPETS } from "@/lib/sql/sqlCompletion"; import { setDebugLoggingEnabled } from "@/lib/backend/debugLog"; import { DEFAULT_TABLE_COLUMN_TEMPLATE_FIELDS, normalizeTableColumnTemplateFields } from "@/lib/table/tableColumnTemplates"; -import { DEFAULT_UI_FONT_FAMILY } from "@/lib/app/appFonts"; +import { DEFAULT_DATA_GRID_FONT_FAMILY, DEFAULT_UI_FONT_FAMILY } from "@/lib/app/appFonts"; import { safeLocalStorageGet, safeLocalStorageRemove } from "@/lib/backend/safeStorage"; import type { AiProvider, AiApiStyle, AiAuthMethod, AiEffortLevel, AiReasoningLevel, AiConfiguredModel, AiConfig, AiTestConnectionResult, AiConfigItem } from "@/types/ai"; @@ -417,6 +417,7 @@ export interface EditorSettings { dataGridSearchMode: DataGridSearchMode; dataGridMultiRowTranspose: boolean; dataGridHideNullColumns: boolean; + tableFontFamily: string; tableFontSize: number; structureEditorDensity: StructureEditorDensity; tableInfoDrawerWidth: number; @@ -587,6 +588,7 @@ export const DEFAULT_EDITOR_SETTINGS: EditorSettings = { dataGridSearchMode: "filter", dataGridMultiRowTranspose: false, dataGridHideNullColumns: false, + tableFontFamily: DEFAULT_DATA_GRID_FONT_FAMILY, tableFontSize: TABLE_FONT_SIZE_DEFAULT, structureEditorDensity: "compact", tableInfoDrawerWidth: 320, @@ -848,6 +850,7 @@ export function normalizeEditorSettings(settings: Partial, exist dataGridSearchMode: normalizeDataGridSearchMode(settings.dataGridSearchMode), dataGridMultiRowTranspose: settings.dataGridMultiRowTranspose === true, dataGridHideNullColumns: settings.dataGridHideNullColumns === true, + tableFontFamily: normalizeFontFamily(settings.tableFontFamily, DEFAULT_EDITOR_SETTINGS.tableFontFamily), tableFontSize: normalizeTableFontSize(settings.tableFontSize), structureEditorDensity: normalizeStructureEditorDensity(settings.structureEditorDensity), tableInfoDrawerWidth: normalizeDrawerWidth(settings.tableInfoDrawerWidth, 240, DEFAULT_EDITOR_SETTINGS.tableInfoDrawerWidth), @@ -1207,6 +1210,7 @@ export const useSettingsStore = defineStore("settings", () => { if (partial.dataGridSearchMode !== undefined) editorSettings.value.dataGridSearchMode = normalizeDataGridSearchMode(partial.dataGridSearchMode); if (partial.dataGridMultiRowTranspose !== undefined) editorSettings.value.dataGridMultiRowTranspose = partial.dataGridMultiRowTranspose === true; if (partial.dataGridHideNullColumns !== undefined) editorSettings.value.dataGridHideNullColumns = partial.dataGridHideNullColumns === true; + if (partial.tableFontFamily !== undefined) editorSettings.value.tableFontFamily = normalizeFontFamily(partial.tableFontFamily, DEFAULT_EDITOR_SETTINGS.tableFontFamily); if (partial.tableFontSize !== undefined) editorSettings.value.tableFontSize = normalizeTableFontSize(partial.tableFontSize); if (partial.structureEditorDensity !== undefined) editorSettings.value.structureEditorDensity = normalizeStructureEditorDensity(partial.structureEditorDensity); if (partial.tableInfoDrawerWidth !== undefined) editorSettings.value.tableInfoDrawerWidth = normalizeDrawerWidth(partial.tableInfoDrawerWidth, 240, DEFAULT_EDITOR_SETTINGS.tableInfoDrawerWidth); diff --git a/packages/app-tests/dataGridFontSettings.test.ts b/packages/app-tests/dataGridFontSettings.test.ts new file mode 100644 index 0000000000..31a5663a25 --- /dev/null +++ b/packages/app-tests/dataGridFontSettings.test.ts @@ -0,0 +1,36 @@ +import { strict as assert } from "node:assert"; +import { readFileSync } from "node:fs"; +import { test } from "vitest"; + +const dataGridSource = readFileSync("apps/desktop/src/components/grid/DataGrid.vue", "utf8"); +const settingsDialogSource = readFileSync("apps/desktop/src/components/editor/EditorSettingsDialog.vue", "utf8"); + +test("applies the configured result grid font to DOM and canvas renderers", () => { + assert.match(dataGridSource, /const tableFontFamily = computed\(\(\) => settingsStore\.editorSettings\.tableFontFamily\)/); + assert.match(dataGridSource, /"--dbx-data-grid-font-family": tableFontFamily\.value/); + assert.match(dataGridSource, /class="canvas-grid-surface dbx-data-grid-font-family/); + assert.match(dataGridSource, /class="data-grid-scroller dbx-data-grid-font-family/); +}); + +test("invalidates grid measurements and canvas rendering when the result font changes", () => { + assert.match(dataGridSource, /columnHeaderMeasurementKey = computed\(\(\) => \[tableFontSize\.value, tableFontFamily\.value\]\)/); + assert.match(dataGridSource, /columnHeaderMeasureContext\.font = `600 \$\{tableFontSize\.value\}px \$\{tableFontFamily\.value\}`/); + assert.match(dataGridSource, /canvasRenderStyleKey = computed\(\(\) => `[^`]*\$\{tableFontFamily\.value\}:\$\{tableFontSize\.value\}`\)/); +}); + +test("places the result grid font beside the interface font in appearance settings", () => { + const editorSectionStart = settingsDialogSource.indexOf(`activeSettingsTab === 'editor'`); + const appearanceSectionStart = settingsDialogSource.indexOf(`activeSettingsTab === 'appearance'`); + const resultGridFontField = settingsDialogSource.indexOf(``); + + assert.ok(editorSectionStart >= 0); + assert.ok(appearanceSectionStart > editorSectionStart); + assert.ok(resultGridFontField > appearanceSectionStart); + assert.equal(settingsDialogSource.slice(editorSectionStart, appearanceSectionStart).includes(`settings.dataGridFontFamily`), false); +}); + +test("keeps appearance section spacing and help icons aligned without stacked margins", () => { + assert.doesNotMatch(settingsDialogSource, /\.settings-appearance-section > \* \+ \*/); + assert.match(settingsDialogSource, /activeSettingsTab === 'appearance'" class="settings-appearance-section flex flex-col gap-4 py-2"/); + assert.match(settingsDialogSource, /
\s*