From f7f5758c85a3d5d1fb08dfc8397c6ea76cbead47 Mon Sep 17 00:00:00 2001 From: t8y2 <1156263951@qq.com> Date: Thu, 28 May 2026 17:43:06 +0800 Subject: [PATCH] feat(desktop): add global UI zoom settings and shortcuts --- apps/desktop/src/App.vue | 73 +++++++++++++++++++ .../editor/EditorSettingsDialog.vue | 31 ++++++++ .../src/components/editor/QueryEditor.vue | 28 ------- apps/desktop/src/i18n/locales/en.ts | 6 ++ apps/desktop/src/i18n/locales/es.ts | 6 ++ apps/desktop/src/i18n/locales/zh-CN.ts | 5 ++ apps/desktop/src/lib/keyboardShortcuts.ts | 21 ++++++ apps/desktop/src/lib/shortcutRegistry.ts | 21 ++++++ apps/desktop/src/stores/settingsStore.ts | 11 +++ .../desktopUiScaleCapability.test.ts | 19 +++++ packages/app-tests/keyboardShortcuts.test.ts | 24 ++++++ .../queryEditorSearchReplace.test.ts | 17 +++++ packages/app-tests/settingsStore.test.ts | 20 +++++ src-tauri/capabilities/default.json | 1 + 14 files changed, 255 insertions(+), 28 deletions(-) create mode 100644 packages/app-tests/desktopUiScaleCapability.test.ts diff --git a/apps/desktop/src/App.vue b/apps/desktop/src/App.vue index f17548852..8d392f082 100644 --- a/apps/desktop/src/App.vue +++ b/apps/desktop/src/App.vue @@ -43,8 +43,11 @@ import { isModRShortcut, isNewQueryShortcut, isObjectSourceSaveShortcutTarget, + isResetZoomShortcut, isRefreshDataShortcut, isSaveShortcut, + isZoomInShortcut, + isZoomOutShortcut, } from "@/lib/keyboardShortcuts"; import { isPreviewTab } from "@/lib/tabPresentation"; import { supportsSqlFileExecution } from "@/lib/databaseCapabilities"; @@ -224,6 +227,47 @@ const saveSqlFolders = computed(() => { return tab ? savedSqlStore.listFolders(tab.connectionId) : []; }); +async function applyUiScale(scale: number) { + if (!isDesktop) return; + try { + const { getCurrentWebview } = await import("@tauri-apps/api/webview"); + await getCurrentWebview().setZoom(scale); + } catch (error) { + console.warn("[DBX] Failed to apply UI scale", { scale, error }); + } +} + +function setGlobalUiScale(scale: number) { + settingsStore.updateEditorSettings({ uiScale: scale }); +} + +function zoomInUi() { + setGlobalUiScale(settingsStore.editorSettings.uiScale + 0.1); +} + +function zoomOutUi() { + setGlobalUiScale(settingsStore.editorSettings.uiScale - 0.1); +} + +function resetUiZoom() { + setGlobalUiScale(1); +} + +function isGlobalUiZoomTarget(target: EventTarget | null): target is Element { + if (!(target instanceof Element)) return false; + if (target.closest("[data-query-editor-root], [data-cell-detail-editor-root], [data-object-source-editor]")) { + return true; + } + if ( + target instanceof HTMLInputElement || + target instanceof HTMLTextAreaElement || + (target instanceof HTMLElement && target.isContentEditable) + ) { + return false; + } + return !target.closest("[contenteditable='true']"); +} + watch( () => queryStore.activeTabId, (id) => { @@ -242,6 +286,14 @@ watch( }, ); +watch( + () => settingsStore.editorSettings.uiScale, + (scale) => { + void applyUiScale(scale); + }, + { immediate: true }, +); + function toggleAiPanel() { showAiPanel.value = !showAiPanel.value; localStorage.setItem("dbx-ai-panel-open", String(showAiPanel.value)); @@ -667,6 +719,26 @@ function handleKeydown(e: KeyboardEvent) { e.stopPropagation(); return; } + if (isDesktop && isGlobalUiZoomTarget(e.target)) { + if (isZoomInShortcut(e, shortcuts)) { + e.preventDefault(); + e.stopPropagation(); + zoomInUi(); + return; + } + if (isZoomOutShortcut(e, shortcuts)) { + e.preventDefault(); + e.stopPropagation(); + zoomOutUi(); + return; + } + if (isResetZoomShortcut(e, shortcuts)) { + e.preventDefault(); + e.stopPropagation(); + resetUiZoom(); + return; + } + } if (isDesktop && isBrowserReloadShortcut(e)) { e.preventDefault(); e.stopPropagation(); @@ -733,6 +805,7 @@ onMounted(async () => { aiPanelReady.value = true; }); applyTheme(); + void applyUiScale(settingsStore.editorSettings.uiScale); window.addEventListener("keydown", handleKeydown); window.addEventListener("dbx-open-driver-store", openDriverStoreFromEvent); if (isDesktop) { diff --git a/apps/desktop/src/components/editor/EditorSettingsDialog.vue b/apps/desktop/src/components/editor/EditorSettingsDialog.vue index 2e7e1cc71..eb6fe023e 100644 --- a/apps/desktop/src/components/editor/EditorSettingsDialog.vue +++ b/apps/desktop/src/components/editor/EditorSettingsDialog.vue @@ -87,6 +87,7 @@ const emit = defineEmits<{ // Local edit state const editFontFamily = ref(settingsStore.editorSettings.fontFamily); const editFontSize = ref(settingsStore.editorSettings.fontSize); +const editUiScale = ref(settingsStore.editorSettings.uiScale); const editTheme = ref(settingsStore.editorSettings.theme); const editExecuteMode = ref(settingsStore.editorSettings.executeMode); const editWordWrap = ref(settingsStore.editorSettings.wordWrap); @@ -105,6 +106,7 @@ const redisScanPageSizeOptions = [200, 1000, 5000, 10000]; const systemFonts = ref([]); const systemFontsLoading = ref(false); const systemFontsLoaded = ref(false); +const uiScaleOptions = [0.75, 0.9, 1, 1.1, 1.25, 1.5, 1.75, 2]; // --- Snippet state --- const editSnippets = ref(settingsStore.editorSettings.snippets.map((s) => ({ ...s }))); @@ -219,6 +221,7 @@ watch( if (open) { editFontFamily.value = settingsStore.editorSettings.fontFamily; editFontSize.value = settingsStore.editorSettings.fontSize; + editUiScale.value = settingsStore.editorSettings.uiScale; editTheme.value = settingsStore.editorSettings.theme; editExecuteMode.value = settingsStore.editorSettings.executeMode; editWordWrap.value = settingsStore.editorSettings.wordWrap; @@ -256,6 +259,7 @@ function hasChanges(): boolean { return ( editFontFamily.value !== settingsStore.editorSettings.fontFamily || editFontSize.value !== settingsStore.editorSettings.fontSize || + editUiScale.value !== settingsStore.editorSettings.uiScale || editTheme.value !== settingsStore.editorSettings.theme || editExecuteMode.value !== settingsStore.editorSettings.executeMode || editWordWrap.value !== settingsStore.editorSettings.wordWrap || @@ -280,6 +284,7 @@ async function applySettings() { settingsStore.updateEditorSettings({ fontFamily: editFontFamily.value, fontSize: editFontSize.value, + uiScale: editUiScale.value, theme: editTheme.value, executeMode: editExecuteMode.value, wordWrap: editWordWrap.value, @@ -304,6 +309,7 @@ async function applySettings() { function resetDefaults() { editFontFamily.value = DEFAULT_EDITOR_SETTINGS.fontFamily; editFontSize.value = DEFAULT_EDITOR_SETTINGS.fontSize; + editUiScale.value = DEFAULT_EDITOR_SETTINGS.uiScale; editTheme.value = DEFAULT_EDITOR_SETTINGS.theme; editExecuteMode.value = DEFAULT_EDITOR_SETTINGS.executeMode; editWordWrap.value = DEFAULT_EDITOR_SETTINGS.wordWrap; @@ -1106,6 +1112,31 @@ watch(
+
+ + +

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

+
+ + +
diff --git a/apps/desktop/src/components/editor/QueryEditor.vue b/apps/desktop/src/components/editor/QueryEditor.vue index 215d2a01b..b662a7ec9 100644 --- a/apps/desktop/src/components/editor/QueryEditor.vue +++ b/apps/desktop/src/components/editor/QueryEditor.vue @@ -383,34 +383,6 @@ function runKeymapExtension(codeMirrorKeymap: (typeof import("@codemirror/view") ]), ) ?? [], codeMirrorKeymap.of([ - { - key: "Mod-=", - run: () => { - zoomIn(); - return true; - }, - }, - { - key: "Mod-+", - run: () => { - zoomIn(); - return true; - }, - }, - { - key: "Mod--", - run: () => { - zoomOut(); - return true; - }, - }, - { - key: "Mod-0", - run: () => { - resetZoom(); - return true; - }, - }, { key: shortcutToCodeMirrorKey(shortcuts.acceptCompletion), run: (view) => codeMirrorAcceptCompletion?.(view) ?? false, diff --git a/apps/desktop/src/i18n/locales/en.ts b/apps/desktop/src/i18n/locales/en.ts index cb435b49a..19243bcb1 100644 --- a/apps/desktop/src/i18n/locales/en.ts +++ b/apps/desktop/src/i18n/locales/en.ts @@ -1386,6 +1386,9 @@ export default { noFontsFound: "No fonts found", useCustomFont: "Use “{font}”", fontSize: "Font Size", + uiScale: "UI Scale", + uiScaleDescription: + "Scale the entire desktop UI for high-DPI displays. Changes apply immediately and are restored on next launch.", theme: "Theme", selectTheme: "Select theme...", followAppTheme: "Follow app theme", @@ -1480,6 +1483,9 @@ export default { shortcutNewQuery: "New query", shortcutCloseTab: "Close tab", shortcutFocusSearch: "Focus search", + shortcutZoomInUi: "Zoom in UI", + shortcutZoomOutUi: "Zoom out UI", + shortcutResetUiZoom: "Reset UI zoom", shortcutRefreshData: "Refresh data", shortcutToggleTranspose: "Toggle transpose view", shortcutCancelSearch: "Cancel search", diff --git a/apps/desktop/src/i18n/locales/es.ts b/apps/desktop/src/i18n/locales/es.ts index 2d379bc36..28d53d60c 100644 --- a/apps/desktop/src/i18n/locales/es.ts +++ b/apps/desktop/src/i18n/locales/es.ts @@ -1280,6 +1280,9 @@ export default { noFontsFound: "No se encontraron fuentes", useCustomFont: "Usar “{font}”", fontSize: "Tamaño de fuente", + uiScale: "Escala de interfaz", + uiScaleDescription: + "Escala toda la interfaz de escritorio para pantallas de alta densidad. Los cambios se aplican al instante y se restauran al volver a abrir.", theme: "Tema", selectTheme: "Seleccionar tema...", appLayout: "Diseño de la interfaz", @@ -1373,6 +1376,9 @@ export default { shortcutNewQuery: "Nueva consulta", shortcutCloseTab: "Cerrar pestaña", shortcutFocusSearch: "Enfocar búsqueda", + shortcutZoomInUi: "Ampliar interfaz", + shortcutZoomOutUi: "Reducir interfaz", + shortcutResetUiZoom: "Restablecer zoom de interfaz", shortcutRefreshData: "Actualizar datos", shortcutToggleTranspose: "Alternar vista transpuesta", shortcutCancelSearch: "Cancelar búsqueda", diff --git a/apps/desktop/src/i18n/locales/zh-CN.ts b/apps/desktop/src/i18n/locales/zh-CN.ts index 237f0075e..6f9335cc8 100644 --- a/apps/desktop/src/i18n/locales/zh-CN.ts +++ b/apps/desktop/src/i18n/locales/zh-CN.ts @@ -1361,6 +1361,8 @@ export default { noFontsFound: "未找到字体", useCustomFont: "使用“{font}”", fontSize: "字号", + uiScale: "界面缩放", + uiScaleDescription: "按比例缩放整个桌面端界面,适合高清屏;修改后立即生效,并在下次启动时恢复。", theme: "主题", selectTheme: "选择主题...", followAppTheme: "跟随应用主题", @@ -1447,6 +1449,9 @@ export default { shortcutNewQuery: "新建查询", shortcutCloseTab: "关闭标签页", shortcutFocusSearch: "聚焦搜索", + shortcutZoomInUi: "放大全局界面", + shortcutZoomOutUi: "缩小全局界面", + shortcutResetUiZoom: "重置全局界面缩放", shortcutRefreshData: "刷新数据", shortcutToggleTranspose: "切换转置视图", shortcutCancelSearch: "取消搜索", diff --git a/apps/desktop/src/lib/keyboardShortcuts.ts b/apps/desktop/src/lib/keyboardShortcuts.ts index 5da2d734c..0b0651b55 100644 --- a/apps/desktop/src/lib/keyboardShortcuts.ts +++ b/apps/desktop/src/lib/keyboardShortcuts.ts @@ -92,6 +92,27 @@ export function isModRShortcut(event: ShortcutLikeEvent): boolean { return matchesShortcut(event, "Mod+R"); } +export function isZoomInShortcut(event: ShortcutLikeEvent, shortcuts?: Partial): boolean { + if (matchesShortcut(event, actionShortcut("zoomInUi", shortcuts))) return true; + if (event.isComposing || event.altKey) return false; + if (!event.metaKey && !event.ctrlKey) return false; + return normalizeKey(event.key) === "NumpadAdd" && !event.shiftKey; +} + +export function isZoomOutShortcut(event: ShortcutLikeEvent, shortcuts?: Partial): boolean { + if (matchesShortcut(event, actionShortcut("zoomOutUi", shortcuts))) return true; + if (event.isComposing || event.altKey) return false; + if (!event.metaKey && !event.ctrlKey) return false; + return normalizeKey(event.key) === "NumpadSubtract" && !event.shiftKey; +} + +export function isResetZoomShortcut(event: ShortcutLikeEvent, shortcuts?: Partial): boolean { + if (matchesShortcut(event, actionShortcut("resetUiZoom", shortcuts))) return true; + if (event.isComposing || event.altKey || event.shiftKey) return false; + if (!event.metaKey && !event.ctrlKey) return false; + return normalizeKey(event.key) === "Numpad0"; +} + export function isToggleTransposeShortcut(event: ShortcutLikeEvent, shortcuts?: Partial): boolean { return matchesShortcut(event, actionShortcut("toggleTranspose", shortcuts)); } diff --git a/apps/desktop/src/lib/shortcutRegistry.ts b/apps/desktop/src/lib/shortcutRegistry.ts index d40a9981e..b0b4f372d 100644 --- a/apps/desktop/src/lib/shortcutRegistry.ts +++ b/apps/desktop/src/lib/shortcutRegistry.ts @@ -7,6 +7,9 @@ export type ShortcutActionId = | "newQuery" | "closeTab" | "focusSearch" + | "zoomInUi" + | "zoomOutUi" + | "resetUiZoom" | "find" | "replace" | "refreshData" @@ -73,6 +76,24 @@ export const SHORTCUT_DEFINITIONS: ShortcutDefinition[] = [ scope: "global", defaultShortcut: "Mod+F", }, + { + id: "zoomInUi", + labelKey: "settings.shortcutZoomInUi", + scope: "global", + defaultShortcut: "Mod+=", + }, + { + id: "zoomOutUi", + labelKey: "settings.shortcutZoomOutUi", + scope: "global", + defaultShortcut: "Mod+-", + }, + { + id: "resetUiZoom", + labelKey: "settings.shortcutResetUiZoom", + scope: "global", + defaultShortcut: "Mod+0", + }, { id: "find", labelKey: "settings.shortcutFind", diff --git a/apps/desktop/src/stores/settingsStore.ts b/apps/desktop/src/stores/settingsStore.ts index 9dc5c0259..8519f7a7c 100644 --- a/apps/desktop/src/stores/settingsStore.ts +++ b/apps/desktop/src/stores/settingsStore.ts @@ -172,6 +172,7 @@ export type EditorTheme = export interface EditorSettings { fontFamily: string; fontSize: number; + uiScale: number; theme: EditorTheme; executeMode: "all" | "current"; wordWrap: boolean; @@ -222,6 +223,7 @@ export const FONT_FAMILIES: { value: string; label: string }[] = [ export const DEFAULT_EDITOR_SETTINGS: EditorSettings = { fontFamily: "'JetBrains Mono', 'Fira Code', monospace", fontSize: 13, + uiScale: 1, theme: "app", executeMode: "all", wordWrap: false, @@ -245,6 +247,13 @@ export const DEFAULT_EDITOR_SETTINGS: EditorSettings = { export const STORAGE_KEY = "dbx-editor-settings"; const OLD_FONT_SIZE_KEY = "dbx-query-editor-font-size"; +const MIN_UI_SCALE = 0.75; +const MAX_UI_SCALE = 2; + +function normalizeUiScale(value: unknown): number { + if (typeof value !== "number" || !Number.isFinite(value)) return DEFAULT_EDITOR_SETTINGS.uiScale; + return Math.min(MAX_UI_SCALE, Math.max(MIN_UI_SCALE, Math.round(value * 100) / 100)); +} function normalizeColumnFormatters(value: unknown): Record { if (!value || typeof value !== "object" || Array.isArray(value)) return {}; @@ -301,6 +310,7 @@ export function normalizeEditorSettings(settings: Partial, exist return { fontFamily: settings.fontFamily ?? DEFAULT_EDITOR_SETTINGS.fontFamily, fontSize: settings.fontSize ?? DEFAULT_EDITOR_SETTINGS.fontSize, + uiScale: normalizeUiScale(settings.uiScale), theme: settings.theme && EDITOR_THEME_VALUES.has(settings.theme) ? settings.theme : DEFAULT_EDITOR_SETTINGS.theme, executeMode: settings.executeMode ?? DEFAULT_EDITOR_SETTINGS.executeMode, wordWrap: settings.wordWrap ?? DEFAULT_EDITOR_SETTINGS.wordWrap, @@ -427,6 +437,7 @@ export const useSettingsStore = defineStore("settings", () => { function updateEditorSettings(partial: Partial) { if (partial.fontFamily !== undefined) editorSettings.value.fontFamily = partial.fontFamily; if (partial.fontSize !== undefined) editorSettings.value.fontSize = partial.fontSize; + if (partial.uiScale !== undefined) editorSettings.value.uiScale = normalizeUiScale(partial.uiScale); if (partial.theme !== undefined) editorSettings.value.theme = partial.theme; if (partial.executeMode !== undefined) editorSettings.value.executeMode = partial.executeMode; if (partial.wordWrap !== undefined) editorSettings.value.wordWrap = partial.wordWrap; diff --git a/packages/app-tests/desktopUiScaleCapability.test.ts b/packages/app-tests/desktopUiScaleCapability.test.ts new file mode 100644 index 000000000..b9f7f0c3f --- /dev/null +++ b/packages/app-tests/desktopUiScaleCapability.test.ts @@ -0,0 +1,19 @@ +import { strict as assert } from "node:assert"; +import { readFileSync } from "node:fs"; +import test from "node:test"; + +test("desktop capability allows setting the webview zoom", () => { + const capability = JSON.parse(readFileSync("src-tauri/capabilities/default.json", "utf8")) as { + permissions: string[]; + }; + + assert.equal(capability.permissions.includes("core:webview:allow-set-webview-zoom"), true); +}); + +test("app logs webview zoom failures instead of swallowing them silently", () => { + const source = readFileSync("apps/desktop/src/App.vue", "utf8"); + + assert.match(source, /getCurrentWebview\(\)\.setZoom\(scale\)/); + assert.match(source, /console\.warn\("\[DBX\] Failed to apply UI scale"/); + assert.match(source, /applyUiScale\(settingsStore\.editorSettings\.uiScale\)/); +}); diff --git a/packages/app-tests/keyboardShortcuts.test.ts b/packages/app-tests/keyboardShortcuts.test.ts index d873f1126..c951bae17 100644 --- a/packages/app-tests/keyboardShortcuts.test.ts +++ b/packages/app-tests/keyboardShortcuts.test.ts @@ -10,11 +10,14 @@ import { isModRShortcut, isNewQueryShortcut, isObjectSourceSaveShortcutTarget, + isResetZoomShortcut, isRefreshDataShortcut, isSaveShortcut, isCopyCurrentRowShortcut, isDeleteCurrentRowShortcut, isToggleTransposeShortcut, + isZoomInShortcut, + isZoomOutShortcut, } from "../../apps/desktop/src/lib/keyboardShortcuts.ts"; import { shortcutToCodeMirrorKey } from "../../apps/desktop/src/lib/shortcutRegistry.ts"; @@ -119,6 +122,27 @@ test("matches Mod-R without shift or alt for scoped refresh and replace", () => assert.equal(isModRShortcut({ key: "r", ctrlKey: true, altKey: true }), false); }); +test("matches desktop UI zoom shortcuts", () => { + assert.equal(isZoomInShortcut({ key: "=", ctrlKey: true }), true); + assert.equal(isZoomInShortcut({ key: "NumpadAdd", ctrlKey: true }), true); + assert.equal(isZoomOutShortcut({ key: "-", ctrlKey: true }), true); + assert.equal(isZoomOutShortcut({ key: "NumpadSubtract", metaKey: true }), true); + assert.equal(isResetZoomShortcut({ key: "0", ctrlKey: true }), true); + assert.equal(isResetZoomShortcut({ key: "Numpad0", metaKey: true }), true); +}); + +test("matches configurable desktop UI zoom shortcuts", () => { + assert.equal(isZoomInShortcut({ key: "i", ctrlKey: true }, { zoomInUi: "Mod+I" } as any), true); + assert.equal(isZoomOutShortcut({ key: "o", metaKey: true }, { zoomOutUi: "Mod+O" } as any), true); + assert.equal(isResetZoomShortcut({ key: "9", ctrlKey: true }, { resetUiZoom: "Mod+9" } as any), true); +}); + +test("ignores desktop UI zoom shortcuts with the wrong modifiers", () => { + assert.equal(isZoomInShortcut({ key: "=", ctrlKey: true, altKey: true }), false); + assert.equal(isZoomOutShortcut({ key: "-", isComposing: true, ctrlKey: true }), false); + assert.equal(isResetZoomShortcut({ key: "0", metaKey: true, shiftKey: true }), false); +}); + test("ignores focus search shortcut while composing", () => { assert.equal(isFocusSearchShortcut({ key: "f", ctrlKey: true, isComposing: true }), false); }); diff --git a/packages/app-tests/queryEditorSearchReplace.test.ts b/packages/app-tests/queryEditorSearchReplace.test.ts index e788bb314..27d0acbbf 100644 --- a/packages/app-tests/queryEditorSearchReplace.test.ts +++ b/packages/app-tests/queryEditorSearchReplace.test.ts @@ -25,6 +25,13 @@ test("query editor localizes the replace all button", () => { assert.doesNotMatch(searchPanelSource, />\s*全部\s* { + assert.doesNotMatch(source, /key:\s*"Mod-="/); + assert.doesNotMatch(source, /key:\s*"Mod-\+"/); + assert.doesNotMatch(source, /key:\s*"Mod--"/); + assert.doesNotMatch(source, /key:\s*"Mod-0"/); +}); + test("query editor exposes a context menu for executing selected SQL", () => { assert.match(source, /ContextMenuContent/); assert.match(source, /data-context-menu/); @@ -67,3 +74,13 @@ test("app keydown routes Mod-R directly before browser reload handling", () => { assert.match(contentAreaSource, /dataGridRef\.value\?\.openCellDetailSearch\(\)/); assert.match(contentAreaSource, /if \(target\.closest\("\[data-grid-root\]"\)\) return refreshData\(\)/); }); + +test("app routes global UI zoom shortcuts across editor surfaces", () => { + assert.match(appSource, /const shortcuts = settingsStore\.editorSettings\.shortcuts;/); + assert.match(appSource, /isZoomInShortcut\(e, shortcuts\)/); + assert.match(appSource, /isZoomOutShortcut\(e, shortcuts\)/); + assert.match(appSource, /isResetZoomShortcut\(e, shortcuts\)/); + assert.match(appSource, /isGlobalUiZoomTarget\(e\.target\)/); + assert.match(appSource, /settingsStore\.updateEditorSettings\(\{\s*uiScale:\s*scale\s*\}\)/); + assert.match(appSource, /\[data-query-editor-root\], \[data-cell-detail-editor-root\], \[data-object-source-editor\]/); +}); diff --git a/packages/app-tests/settingsStore.test.ts b/packages/app-tests/settingsStore.test.ts index 584f0cfc5..75d26563d 100644 --- a/packages/app-tests/settingsStore.test.ts +++ b/packages/app-tests/settingsStore.test.ts @@ -40,6 +40,9 @@ test("defaults shortcut settings", () => { assert.equal(settings.shortcuts.deleteCurrentRow, "Delete"); assert.equal(settings.shortcuts.newQuery, "Mod+T"); assert.equal(settings.shortcuts.focusSearch, "Mod+F"); + assert.equal(settings.shortcuts.zoomInUi, "Mod+="); + assert.equal(settings.shortcuts.zoomOutUi, "Mod+-"); + assert.equal(settings.shortcuts.resetUiZoom, "Mod+0"); assert.equal(settings.shortcuts.refreshData, "F5"); assert.equal(settings.shortcuts.toggleTranspose, "Tab"); }); @@ -51,6 +54,7 @@ test("keeps saved shortcut overrides", () => { copyCurrentRow: "Alt+Shift+D", deleteCurrentRow: "Backspace", newQuery: "Shift+Mod+N", + zoomInUi: "Alt+Mod+=", } as any, }); @@ -58,6 +62,7 @@ test("keeps saved shortcut overrides", () => { assert.equal(settings.shortcuts.copyCurrentRow, "Alt+Shift+D"); assert.equal(settings.shortcuts.deleteCurrentRow, "Backspace"); assert.equal(settings.shortcuts.newQuery, "Shift+Mod+N"); + assert.equal(settings.shortcuts.zoomInUi, "Alt+Mod+="); assert.equal(settings.shortcuts.saveSql, "Mod+S"); }); @@ -180,3 +185,18 @@ test("infers legacy AI provider from saved endpoint and model", () => { assert.equal(deepseek.endpoint, "https://api.deepseek.com/anthropic/v1/messages"); assert.equal(deepseek.model, "deepseek-v4-pro"); }); + +test("normalizeEditorSettings falls back to the default UI scale", () => { + const settings = normalizeEditorSettings({}); + + assert.equal(settings.uiScale, DEFAULT_EDITOR_SETTINGS.uiScale); +}); + +test("normalizeEditorSettings clamps UI scale into the supported range", () => { + assert.equal(normalizeEditorSettings({ uiScale: 0.2 }).uiScale, 0.75); + assert.equal(normalizeEditorSettings({ uiScale: 2.8 }).uiScale, 2); +}); + +test("normalizeEditorSettings keeps valid UI scales with two-decimal precision", () => { + assert.equal(normalizeEditorSettings({ uiScale: 1.125 }).uiScale, 1.13); +}); diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index c8e9a0ed9..2787f8273 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -17,6 +17,7 @@ "core:window:allow-is-maximized", "core:window:allow-close", "core:window:allow-start-dragging", + "core:webview:allow-set-webview-zoom", "dialog:default", "dialog:allow-save", "dialog:allow-open",