From 041dc8b8566e141d5f9425aef6d42496632f77be Mon Sep 17 00:00:00 2001 From: lizzzzz777 <72645313+lizzzzz777@users.noreply.github.com> Date: Fri, 14 Aug 2026 15:30:31 +0800 Subject: [PATCH] feat(sidebar): browse objects on database single click --- .../editor/EditorSettingsDialog.vue | 14 +++++++ .../sidebar/SidebarTreeRuntimeHost.vue | 9 +++- .../src/components/sidebar/TreeItem.vue | 6 ++- apps/desktop/src/i18n/locales/en.ts | 2 + apps/desktop/src/i18n/locales/es.ts | 2 + apps/desktop/src/i18n/locales/it.ts | 2 + apps/desktop/src/i18n/locales/ja.ts | 2 + apps/desktop/src/i18n/locales/ko.ts | 2 + apps/desktop/src/i18n/locales/pt-BR.ts | 2 + apps/desktop/src/i18n/locales/zh-CN.ts | 2 + apps/desktop/src/i18n/locales/zh-TW.ts | 2 + .../__tests__/sidebar/treeNodeClick.spec.ts | 41 ++++++++++++++++++- .../src/lib/settings/editorSettingsDraft.ts | 1 + .../src/lib/settings/settingsSearch.ts | 1 + apps/desktop/src/lib/sidebar/treeNodeClick.ts | 16 ++++++-- apps/desktop/src/stores/settingsStore.ts | 4 ++ 16 files changed, 101 insertions(+), 7 deletions(-) diff --git a/apps/desktop/src/components/editor/EditorSettingsDialog.vue b/apps/desktop/src/components/editor/EditorSettingsDialog.vue index 9a58d8953e..880b78d937 100644 --- a/apps/desktop/src/components/editor/EditorSettingsDialog.vue +++ b/apps/desktop/src/components/editor/EditorSettingsDialog.vue @@ -394,6 +394,7 @@ const dataTabReuseModeHelp = ref(null); const editRoutineSourceOpenMode = ref(settingsStore.editorSettings.routineSourceOpenMode); const editSidebarTableSearchEnabled = ref(settingsStore.editorSettings.sidebarTableSearchEnabled); const editAutoSelectActiveSidebarNode = ref(settingsStore.editorSettings.autoSelectActiveSidebarNode); +const editSidebarOpenDatabaseOnSingleClick = ref(settingsStore.editorSettings.sidebarOpenDatabaseOnSingleClick); const editOpenTabsRestoreMode = ref(settingsStore.editorSettings.openTabsRestoreMode); const editDisconnectTabHandlingMode = ref(settingsStore.editorSettings.disconnectTabHandlingMode); const editDataTabReuseMode = ref(settingsStore.editorSettings.dataTabReuseMode); @@ -514,6 +515,7 @@ function currentEditorSettingsDraft(): EditorSettingsDraft { routineSourceOpenMode: editRoutineSourceOpenMode.value, sidebarTableSearchEnabled: editSidebarTableSearchEnabled.value, autoSelectActiveSidebarNode: editAutoSelectActiveSidebarNode.value, + sidebarOpenDatabaseOnSingleClick: editSidebarOpenDatabaseOnSingleClick.value, openTabsRestoreMode: editOpenTabsRestoreMode.value, disconnectTabHandlingMode: editDisconnectTabHandlingMode.value, dataTabReuseMode: editDataTabReuseMode.value, @@ -791,6 +793,7 @@ function syncEditorSettingsDraftFromStore() { editRoutineSourceOpenMode.value = settingsStore.editorSettings.routineSourceOpenMode; editSidebarTableSearchEnabled.value = settingsStore.editorSettings.sidebarTableSearchEnabled; editAutoSelectActiveSidebarNode.value = settingsStore.editorSettings.autoSelectActiveSidebarNode; + editSidebarOpenDatabaseOnSingleClick.value = settingsStore.editorSettings.sidebarOpenDatabaseOnSingleClick; editOpenTabsRestoreMode.value = settingsStore.editorSettings.openTabsRestoreMode; editDisconnectTabHandlingMode.value = settingsStore.editorSettings.disconnectTabHandlingMode; editDataTabReuseMode.value = settingsStore.editorSettings.dataTabReuseMode; @@ -1003,6 +1006,7 @@ function resetDefaultsForTab(tab: SettingsCategory) { editRoutineSourceOpenMode.value = DEFAULT_EDITOR_SETTINGS.routineSourceOpenMode; editSidebarTableSearchEnabled.value = DEFAULT_EDITOR_SETTINGS.sidebarTableSearchEnabled; editAutoSelectActiveSidebarNode.value = DEFAULT_EDITOR_SETTINGS.autoSelectActiveSidebarNode; + editSidebarOpenDatabaseOnSingleClick.value = DEFAULT_EDITOR_SETTINGS.sidebarOpenDatabaseOnSingleClick; editOpenTabsRestoreMode.value = DEFAULT_EDITOR_SETTINGS.openTabsRestoreMode; editDisconnectTabHandlingMode.value = DEFAULT_EDITOR_SETTINGS.disconnectTabHandlingMode; editDataTabReuseMode.value = DEFAULT_EDITOR_SETTINGS.dataTabReuseMode; @@ -1110,6 +1114,7 @@ function resetAllDefaults() { editRoutineSourceOpenMode.value = DEFAULT_EDITOR_SETTINGS.routineSourceOpenMode; editSidebarTableSearchEnabled.value = DEFAULT_EDITOR_SETTINGS.sidebarTableSearchEnabled; editAutoSelectActiveSidebarNode.value = DEFAULT_EDITOR_SETTINGS.autoSelectActiveSidebarNode; + editSidebarOpenDatabaseOnSingleClick.value = DEFAULT_EDITOR_SETTINGS.sidebarOpenDatabaseOnSingleClick; editOpenTabsRestoreMode.value = DEFAULT_EDITOR_SETTINGS.openTabsRestoreMode; editDisconnectTabHandlingMode.value = DEFAULT_EDITOR_SETTINGS.disconnectTabHandlingMode; editDataTabReuseMode.value = DEFAULT_EDITOR_SETTINGS.dataTabReuseMode; @@ -4633,6 +4638,15 @@ onUnmounted(() => { +
+
+ + + {{ t("settings.sidebarOpenDatabaseOnSingleClickDescription") }} + +
+ +
diff --git a/apps/desktop/src/components/sidebar/SidebarTreeRuntimeHost.vue b/apps/desktop/src/components/sidebar/SidebarTreeRuntimeHost.vue index 0204b7c9dd..ed0b505d99 100644 --- a/apps/desktop/src/components/sidebar/SidebarTreeRuntimeHost.vue +++ b/apps/desktop/src/components/sidebar/SidebarTreeRuntimeHost.vue @@ -894,10 +894,15 @@ function runRowClickAction(clickDetail: number) { openMongoTreeData(node); return; } - const action = treeNodeRowAction(node.type, canExpand.value, settingsStore.editorSettings.sidebarActivation, currentDatabaseType()); + const action = treeNodeRowAction(node.type, canExpand.value, settingsStore.editorSettings.sidebarActivation, currentDatabaseType(), settingsStore.editorSettings.sidebarOpenDatabaseOnSingleClick, canOpenObjectBrowser.value); if (!shouldRunTreeNodeRowAction(action, clickDetail, isGroupLabel(node))) return; if (action === "open-data") { scheduleOpenData(node); + } else if (action === "open-object-browser") { + void openObjectBrowser(); + } else if (action === "open-object-browser-and-expand") { + void openObjectBrowser(); + if (!node.isExpanded) void toggle(); } else if (action === "open-source") { openObjectSourceDialog(false); } else if (action === "open-extension-details") { @@ -1231,7 +1236,7 @@ function requestDeleteSelectedNode(): boolean { function onDoubleClick(event: MouseEvent) { if (dataTabOpenModeFromTreeClick(activeNode.value.type, event, settingsStore.editorSettings.shortcuts.openDataInNewTab) === "new-tab") return; - const action = treeNodeRowDoubleClickAction(activeNode.value.type, canOpenObjectBrowser.value, settingsStore.editorSettings.sidebarActivation, canExpand.value, currentDatabaseType(), canOpenConnectionDatabaseBrowser.value); + const action = treeNodeRowDoubleClickAction(activeNode.value.type, canOpenObjectBrowser.value, settingsStore.editorSettings.sidebarActivation, canExpand.value, currentDatabaseType(), canOpenConnectionDatabaseBrowser.value, settingsStore.editorSettings.sidebarOpenDatabaseOnSingleClick); if (action === "open-database-browser") { void openDatabaseBrowser(); } else if (action === "open-object-browser") { diff --git a/apps/desktop/src/components/sidebar/TreeItem.vue b/apps/desktop/src/components/sidebar/TreeItem.vue index 68ea54113f..88f8e06ec7 100644 --- a/apps/desktop/src/components/sidebar/TreeItem.vue +++ b/apps/desktop/src/components/sidebar/TreeItem.vue @@ -77,7 +77,7 @@ import { sidebarTreeRuntimeKey } from "@/lib/sidebar/sidebarTreeRuntime"; import { treeNodePinKey } from "@/lib/app/pinnedItems"; import { isTreeGroupNodeType } from "@/lib/sidebar/treeNodeGroup"; import { customTypeCapabilities } from "@/lib/database/databaseObjectCapabilities"; -import { shouldActivateTreeNodeOnSingleClick } from "@/lib/sidebar/treeNodeClick"; +import { shouldActivateTreeNodeOnSingleClick, shouldOpenObjectBrowserOnSingleClick } from "@/lib/sidebar/treeNodeClick"; const { t } = useI18n(); @@ -1188,6 +1188,10 @@ function onClick(event: MouseEvent) { } selectSingleTreeNode(props.node); rowRef.value?.focus({ preventScroll: true }); + if (shouldOpenObjectBrowserOnSingleClick(props.node.type, settingsStore.editorSettings.sidebarOpenDatabaseOnSingleClick)) { + treeRuntime.handleRowClick(props.node, event.detail); + return; + } if (!shouldActivateTreeNodeOnSingleClick(props.node.type, settingsStore.editorSettings.sidebarActivation) && props.node.type !== "load-more") return; treeRuntime.handleRowClick(props.node, event.detail); } diff --git a/apps/desktop/src/i18n/locales/en.ts b/apps/desktop/src/i18n/locales/en.ts index 854bb13a2c..d438f8aa71 100644 --- a/apps/desktop/src/i18n/locales/en.ts +++ b/apps/desktop/src/i18n/locales/en.ts @@ -5737,6 +5737,8 @@ export default { routineSourceOpenModeDialogDescription: "Open source in a popup dialog for quick viewing and editing.", autoSelectActiveSidebarNode: "Always select opened item", autoSelectActiveSidebarNodeDescription: "When switching tabs, select the matching visible table, collection, or SQL file in the sidebar.", + sidebarOpenDatabaseOnSingleClick: "Browse objects on database single click", + sidebarOpenDatabaseOnSingleClickDescription: 'When enabled, single-clicking a database or schema node also expands the sidebar and opens the "Browse Objects" tab.', openTabsRestoreMode: "Restore tabs on launch", openTabsRestoreModeDescription: "Choose how DBX restores tabs that were open last time.", openTabsRestoreModeAll: "Keep all tabs", diff --git a/apps/desktop/src/i18n/locales/es.ts b/apps/desktop/src/i18n/locales/es.ts index c1c67d89d5..41f200dec8 100644 --- a/apps/desktop/src/i18n/locales/es.ts +++ b/apps/desktop/src/i18n/locales/es.ts @@ -5481,6 +5481,8 @@ export default withEnglishFallback({ sidebarObjectDisplaySimpleDescription: "Muestra tablas y vistas directamente bajo cada base de datos o esquema para reducir niveles intermedios.", autoSelectActiveSidebarNode: "Seleccionar siempre el elemento abierto", autoSelectActiveSidebarNodeDescription: "Al cambiar de pestaña, selecciona la tabla, colección o archivo SQL visible correspondiente en la barra lateral.", + sidebarOpenDatabaseOnSingleClick: "Explorar objetos al hacer clic en la base de datos", + sidebarOpenDatabaseOnSingleClickDescription: 'Si está activado, al hacer un solo clic en un nodo de base de datos o esquema se expande la barra lateral y se abre la pestaña "Explorar objetos".', openTabsRestoreMode: "Restaurar pestañas al iniciar", openTabsRestoreModeDescription: "Elige cómo DBX restaura las pestañas que estaban abiertas la última vez.", openTabsRestoreModeAll: "Conservar todas las pestañas", diff --git a/apps/desktop/src/i18n/locales/it.ts b/apps/desktop/src/i18n/locales/it.ts index a5701f5637..42eeb39536 100644 --- a/apps/desktop/src/i18n/locales/it.ts +++ b/apps/desktop/src/i18n/locales/it.ts @@ -5481,6 +5481,8 @@ export default withEnglishFallback({ sidebarObjectDisplaySimpleDescription: "Elenca tabelle e viste direttamente sotto ogni database o schema per ridurre i livelli intermedi.", autoSelectActiveSidebarNode: "Seleziona sempre l'elemento aperto", autoSelectActiveSidebarNodeDescription: "Quando passi da una scheda all'altra, seleziona la tabella, la collezione o il file SQL visibile corrispondente nella barra laterale.", + sidebarOpenDatabaseOnSingleClick: "Sfoglia oggetti al clic sul database", + sidebarOpenDatabaseOnSingleClickDescription: 'Se abilitato, un singolo clic su un nodo database o schema espande la barra laterale e apre la scheda "Esplora Oggetti".', openTabsRestoreMode: "Ripristina schede all'avvio", openTabsRestoreModeDescription: "Scegli come DBX ripristina le schede aperte l'ultima volta.", openTabsRestoreModeAll: "Mantieni tutte le schede", diff --git a/apps/desktop/src/i18n/locales/ja.ts b/apps/desktop/src/i18n/locales/ja.ts index de31dea169..94f063c090 100644 --- a/apps/desktop/src/i18n/locales/ja.ts +++ b/apps/desktop/src/i18n/locales/ja.ts @@ -5508,6 +5508,8 @@ export default withEnglishFallback({ sidebarObjectDisplaySimpleDescription: "中間レベルを減らすために、各データベースまたはスキーマの直下にテーブルとビューを一覧表示します。", autoSelectActiveSidebarNode: "開いた項目を常に選択", autoSelectActiveSidebarNodeDescription: "タブ切替時に、サイドバーで一致する表示中のテーブル、コレクション、またはSQLファイルを選択します。", + sidebarOpenDatabaseOnSingleClick: "データベースのシングルクリックでオブジェクトを参照", + sidebarOpenDatabaseOnSingleClickDescription: "有効にすると、データベースまたはスキーマノードをシングルクリックした際に、サイドバーを展開すると同時に「オブジェクトを参照」タブを開きます。", openTabsRestoreMode: "起動時にタブを復元", openTabsRestoreModeDescription: "DBX 起動時に前回開いていたタブをどのように復元するかを選択します。", openTabsRestoreModeAll: "すべてのタブを保持", diff --git a/apps/desktop/src/i18n/locales/ko.ts b/apps/desktop/src/i18n/locales/ko.ts index b2e70e82a9..7d65a786c1 100644 --- a/apps/desktop/src/i18n/locales/ko.ts +++ b/apps/desktop/src/i18n/locales/ko.ts @@ -5220,6 +5220,8 @@ export default withEnglishFallback({ sidebarObjectDisplaySimpleDescription: "중간 수준을 줄이기 위해 각 데이터베이스나 스키마 아래에 테이블과 뷰를 직접 나열합니다.", autoSelectActiveSidebarNode: "열린 항목 항상 선택", autoSelectActiveSidebarNodeDescription: "탭을 전환할 때 사이드바에서 일치하는 표시된 테이블, 컬렉션 또는 SQL 파일을 선택합니다.", + sidebarOpenDatabaseOnSingleClick: "데이터베이스 클릭 시 객체 탐색", + sidebarOpenDatabaseOnSingleClickDescription: '활성화하면 데이터베이스 또는 스키마 노드를 한 번 클릭할 때 사이드바를 펼치면서 "객체 탐색" 탭을 엽니다.', openTabsRestoreMode: "실행 시 탭 복원", openTabsRestoreModeDescription: "DBX가 지난번에 열려 있던 탭을 복원하는 방법을 선택하세요.", openTabsRestoreModeAll: "모든 탭 유지", diff --git a/apps/desktop/src/i18n/locales/pt-BR.ts b/apps/desktop/src/i18n/locales/pt-BR.ts index 4dc338b25f..19b21b2c5c 100644 --- a/apps/desktop/src/i18n/locales/pt-BR.ts +++ b/apps/desktop/src/i18n/locales/pt-BR.ts @@ -5483,6 +5483,8 @@ export default withEnglishFallback({ sidebarObjectDisplaySimpleDescription: "Listar tabelas e views diretamente sob cada banco de dados ou schema para reduzir os níveis intermediários.", autoSelectActiveSidebarNode: "Sempre selecionar o item aberto", autoSelectActiveSidebarNodeDescription: "Ao alternar abas, selecionar a tabela, coleção ou arquivo SQL correspondente visível na barra lateral.", + sidebarOpenDatabaseOnSingleClick: "Explorar objetos ao clicar no banco de dados", + sidebarOpenDatabaseOnSingleClickDescription: 'Quando ativado, um clique único em um nó de banco de dados ou esquema expande a barra lateral e abre a aba "Explorar Objetos".', openTabsRestoreMode: "Restaurar abas ao iniciar", openTabsRestoreModeDescription: "Escolha como o DBX restaura as abas que estavam abertas na última vez.", openTabsRestoreModeAll: "Manter todas as abas", diff --git a/apps/desktop/src/i18n/locales/zh-CN.ts b/apps/desktop/src/i18n/locales/zh-CN.ts index 80da6cc15a..dff48734c1 100644 --- a/apps/desktop/src/i18n/locales/zh-CN.ts +++ b/apps/desktop/src/i18n/locales/zh-CN.ts @@ -5724,6 +5724,8 @@ export default withEnglishFallback({ routineSourceOpenModeDialogDescription: "在弹出对话框中打开源码,方便快速查看和编辑。", autoSelectActiveSidebarNode: "始终选中已打开项目", autoSelectActiveSidebarNodeDescription: "切换标签页时,在侧边栏选中匹配的可见表、集合或 SQL 文件。", + sidebarOpenDatabaseOnSingleClick: "单击数据库时浏览对象", + sidebarOpenDatabaseOnSingleClickDescription: "开启后,单击数据库或模式节点时,会同时展开侧边栏并打开“浏览对象”标签页。", openTabsRestoreMode: "启动时恢复标签页", openTabsRestoreModeDescription: "选择 DBX 启动时如何恢复上次打开的标签页。", openTabsRestoreModeAll: "保留所有标签", diff --git a/apps/desktop/src/i18n/locales/zh-TW.ts b/apps/desktop/src/i18n/locales/zh-TW.ts index 36ab794b48..c4a8333b32 100644 --- a/apps/desktop/src/i18n/locales/zh-TW.ts +++ b/apps/desktop/src/i18n/locales/zh-TW.ts @@ -4818,6 +4818,8 @@ export default withEnglishFallback({ sidebarObjectDisplaySimpleDescription: "直接在每個資料庫或 schema 下列出資料表與檢視,減少中間層級。", autoSelectActiveSidebarNode: "一律選取已開啟項目", autoSelectActiveSidebarNodeDescription: "切換分頁時,在側邊欄選取相符的可見資料表、集合或 SQL 檔案。", + sidebarOpenDatabaseOnSingleClick: "單擊資料庫時瀏覽物件", + sidebarOpenDatabaseOnSingleClickDescription: "開啟後,單擊資料庫或結構描述節點時,會同時展開側邊欄並開啟「瀏覽物件」分頁。", openTabsRestoreMode: "啟動時還原分頁", openTabsRestoreModeDescription: "選擇 DBX 啟動時如何還原上次開啟的分頁。", openTabsRestoreModeAll: "保留所有分頁", diff --git a/apps/desktop/src/lib/__tests__/sidebar/treeNodeClick.spec.ts b/apps/desktop/src/lib/__tests__/sidebar/treeNodeClick.spec.ts index ed7c8a6b9a..e706be2cdd 100644 --- a/apps/desktop/src/lib/__tests__/sidebar/treeNodeClick.spec.ts +++ b/apps/desktop/src/lib/__tests__/sidebar/treeNodeClick.spec.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { isDirectNavigationTreeNode, objectSourceKindForTreeNode, objectSourceTargetForTreeNode, shouldActivateTreeNodeOnSingleClick, treeNodeRowAction, treeNodeRowDoubleClickAction } from "@/lib/sidebar/treeNodeClick"; +import { isDirectNavigationTreeNode, objectSourceKindForTreeNode, objectSourceTargetForTreeNode, shouldActivateTreeNodeOnSingleClick, shouldOpenObjectBrowserOnSingleClick, treeNodeRowAction, treeNodeRowDoubleClickAction } from "@/lib/sidebar/treeNodeClick"; describe("treeNodeClick", () => { it("opens synonym nodes as synonym source", () => { @@ -49,6 +49,45 @@ describe("treeNodeClick", () => { expect(treeNodeRowDoubleClickAction("connection", false, "double", true, "postgres", false)).toBe("toggle"); }); + it("opens the object browser on single click when the database single-click switch is on", () => { + expect(treeNodeRowAction("database", true, "single", "postgres", true, true)).toBe("open-object-browser-and-expand"); + expect(treeNodeRowAction("database", false, "single", "postgres", true, true)).toBe("open-object-browser"); + expect(treeNodeRowAction("schema", true, "single", "postgres", true, true)).toBe("open-object-browser-and-expand"); + expect(treeNodeRowAction("object-browser", false, "single", "postgres", true, true)).toBe("open-object-browser"); + }); + + it("lets the database single-click switch override double-click activation", () => { + expect(treeNodeRowAction("database", true, "double", "postgres", true, true)).toBe("open-object-browser-and-expand"); + expect(treeNodeRowAction("schema", false, "double", "postgres", true, true)).toBe("open-object-browser"); + }); + + it("does not reopen the object browser when the single-click switch already handled the gesture", () => { + expect(treeNodeRowDoubleClickAction("database", true, "single", true, "postgres", false, true)).toBe("none"); + expect(treeNodeRowDoubleClickAction("schema", true, "double", true, "postgres", false, true)).toBe("none"); + expect(treeNodeRowDoubleClickAction("object-browser", true, "single", false, "postgres", false, true)).toBe("none"); + }); + + it("keeps the existing double-click fallback when single-click browsing is unavailable", () => { + expect(treeNodeRowDoubleClickAction("database", true, "single", true, "postgres", false, false)).toBe("open-object-browser"); + expect(treeNodeRowDoubleClickAction("database", false, "double", true, "postgres", false, true)).toBe("toggle"); + }); + + it("keeps the original single-click behavior when the database single-click switch is off or unsupported", () => { + expect(treeNodeRowAction("database", true, "single", "postgres", false, true)).toBe("toggle"); + expect(treeNodeRowAction("database", true, "double", "postgres", false, true)).toBe("none"); + // Capability off (e.g. unsupported database type) falls back to the previous behavior. + expect(treeNodeRowAction("database", true, "single", "postgres", true, false)).toBe("toggle"); + expect(treeNodeRowAction("database", true, "double", "postgres", true, false)).toBe("none"); + }); + + it("does not route non-database nodes through the database single-click switch", () => { + expect(treeNodeRowAction("table", false, "single", "postgres", true, true)).toBe("open-data"); + expect(treeNodeRowAction("table", false, "double", "postgres", true, true)).toBe("none"); + expect(shouldOpenObjectBrowserOnSingleClick("table", true)).toBe(false); + expect(shouldOpenObjectBrowserOnSingleClick("database", true)).toBe(true); + expect(shouldOpenObjectBrowserOnSingleClick("database", false)).toBe(false); + }); + it("expands package containers while preserving source behavior for leaf packages", () => { expect(treeNodeRowAction("package", true)).toBe("toggle"); expect(treeNodeRowAction("package", false)).toBe("open-source"); diff --git a/apps/desktop/src/lib/settings/editorSettingsDraft.ts b/apps/desktop/src/lib/settings/editorSettingsDraft.ts index f967ce2ca3..0326ba0974 100644 --- a/apps/desktop/src/lib/settings/editorSettingsDraft.ts +++ b/apps/desktop/src/lib/settings/editorSettingsDraft.ts @@ -54,6 +54,7 @@ export const EDITOR_SETTINGS_DRAFT_KEYS = [ "routineSourceOpenMode", "sidebarTableSearchEnabled", "autoSelectActiveSidebarNode", + "sidebarOpenDatabaseOnSingleClick", "openTabsRestoreMode", "disconnectTabHandlingMode", "dataTabReuseMode", diff --git a/apps/desktop/src/lib/settings/settingsSearch.ts b/apps/desktop/src/lib/settings/settingsSearch.ts index ef88d74d77..66263e36a5 100644 --- a/apps/desktop/src/lib/settings/settingsSearch.ts +++ b/apps/desktop/src/lib/settings/settingsSearch.ts @@ -163,6 +163,7 @@ export const SETTINGS_SEARCH_DEFINITIONS: readonly SettingsSearchDefinition[] = { id: "navigation-object-info", category: "navigation", titleKey: "settings.sidebarObjectInfoMode", descriptionKey: "settings.sidebarObjectInfoModeDescription", targetId: "navigation" }, { id: "navigation-table-search", category: "navigation", titleKey: "settings.sidebarTableSearchEnabled", descriptionKey: "settings.sidebarTableSearchEnabledDescription", targetId: "navigation" }, { id: "navigation-active-node", category: "navigation", titleKey: "settings.autoSelectActiveSidebarNode", descriptionKey: "settings.autoSelectActiveSidebarNodeDescription", targetId: "navigation" }, + { id: "navigation-open-database-on-single-click", category: "navigation", titleKey: "settings.sidebarOpenDatabaseOnSingleClick", descriptionKey: "settings.sidebarOpenDatabaseOnSingleClickDescription", targetId: "navigation" }, { id: "navigation-tabs-restore", category: "navigation", titleKey: "settings.openTabsRestoreMode", descriptionKey: "settings.openTabsRestoreModeDescription", targetId: "navigation" }, { id: "navigation-sidebar-scroll", category: "navigation", titleKey: "settings.sidebarAllowHorizontalScroll", descriptionKey: "settings.sidebarAllowHorizontalScrollDescription", targetId: "navigation" }, { id: "navigation-hidden-tables", category: "navigation", titleKey: "settings.sidebarHiddenTablePrefixes", descriptionKey: "settings.sidebarHiddenTablePrefixesDescription", targetId: "navigation" }, diff --git a/apps/desktop/src/lib/sidebar/treeNodeClick.ts b/apps/desktop/src/lib/sidebar/treeNodeClick.ts index e1a933f027..14af688a2f 100644 --- a/apps/desktop/src/lib/sidebar/treeNodeClick.ts +++ b/apps/desktop/src/lib/sidebar/treeNodeClick.ts @@ -2,7 +2,7 @@ import type { DatabaseType, ObjectSourceKind, TreeNode, TreeNodeType } from "@/t import { customTypeCapabilities, supportsTypeObjectSource } from "@/lib/database/databaseObjectCapabilities"; import { matchesShortcut, type ShortcutLikeEvent } from "@/lib/editor/keyboardShortcuts"; -export type TreeNodeRowAction = "open-data" | "open-source" | "open-extension-details" | "open-saved-sql" | "toggle" | "none"; +export type TreeNodeRowAction = "open-data" | "open-source" | "open-extension-details" | "open-saved-sql" | "open-object-browser" | "open-object-browser-and-expand" | "toggle" | "none"; export type TreeNodeRowDoubleClickAction = "open-data" | "activate-data" | "open-database-browser" | "open-object-browser" | "open-object-browser-and-expand" | "open-source" | "open-extension-details" | "open-saved-sql" | "toggle" | "none"; export type SidebarSelectionCopyAction = "copy-name" | "none"; export type SidebarActivation = "single" | "double"; @@ -41,6 +41,11 @@ export function shouldActivateTreeNodeOnSingleClick(type: TreeNodeType, activati return activation !== "double" || isDirectNavigationTreeNode(type); } const objectBrowserNodeTypes = new Set(["database", "schema", "object-browser"]); + +/** 开关“单击数据库同时打开数据库项目”命中时,单击数据库类节点直接打开对象浏览器标签页。 */ +export function shouldOpenObjectBrowserOnSingleClick(type: TreeNodeType, enabled: boolean): boolean { + return enabled && objectBrowserNodeTypes.has(type); +} const sourceNodeTypes = new Set(["materialized_view", "procedure", "function", "trigger", "sequence", "synonym", "package", "package-body", "type", "type-body"]); const savedSqlNodeTypes = new Set(["saved-sql-file"]); const tableChildGroupNodeTypes = new Set(["group-columns", "group-indexes", "group-fkeys", "group-triggers", "group-constraints", "group-partitions", "group-table-partitions", "group-table-subpartitions"]); @@ -102,7 +107,11 @@ function canOpenTreeNodeSource(type: TreeNodeType, dbType?: DatabaseType): boole return true; } -export function treeNodeRowAction(type: TreeNodeType, canExpand: boolean, activation: SidebarActivation = "single", dbType?: DatabaseType): TreeNodeRowAction { +export function treeNodeRowAction(type: TreeNodeType, canExpand: boolean, activation: SidebarActivation = "single", dbType?: DatabaseType, openDatabaseOnSingleClick = false, canOpenObjectBrowser = false): TreeNodeRowAction { + // “单击数据库同时打开数据库项目”开关优先于激活方式:单击即等效双击的打开行为。 + if (openDatabaseOnSingleClick && canOpenObjectBrowser && shouldOpenObjectBrowserOnSingleClick(type, true)) { + return canExpand ? "open-object-browser-and-expand" : "open-object-browser"; + } if (!shouldActivateTreeNodeOnSingleClick(type, activation)) return "none"; if (type === "extension") return "open-extension-details"; if (savedSqlNodeTypes.has(type)) return "open-saved-sql"; @@ -124,10 +133,11 @@ export function shouldRunTreeNodeRowAction(action: TreeNodeRowAction, clickDetai return action !== "none" && clickDetail <= 1; } -export function treeNodeRowDoubleClickAction(type: TreeNodeType, canOpenObjectBrowser: boolean, activation: SidebarActivation = "single", canExpand = false, dbType?: DatabaseType, canOpenDatabaseBrowser = false): TreeNodeRowDoubleClickAction { +export function treeNodeRowDoubleClickAction(type: TreeNodeType, canOpenObjectBrowser: boolean, activation: SidebarActivation = "single", canExpand = false, dbType?: DatabaseType, canOpenDatabaseBrowser = false, openDatabaseOnSingleClick = false): TreeNodeRowDoubleClickAction { // Single-click activation already handles the first click in a dblclick // sequence. Only double-click activation needs a second-stage table action. if (type === "table") return activation === "double" ? "activate-data" : "none"; + if (openDatabaseOnSingleClick && canOpenObjectBrowser && shouldOpenObjectBrowserOnSingleClick(type, true)) return "none"; if (type === "connection" && canOpenDatabaseBrowser) return "open-database-browser"; if (activation === "double") { if (type === "extension") return "open-extension-details"; diff --git a/apps/desktop/src/stores/settingsStore.ts b/apps/desktop/src/stores/settingsStore.ts index 7744f19a36..b4d6f1e3d6 100644 --- a/apps/desktop/src/stores/settingsStore.ts +++ b/apps/desktop/src/stores/settingsStore.ts @@ -576,6 +576,7 @@ export interface EditorSettings { sidebarTableSearchLocal: boolean; sidebarGlobalSearchLocal: boolean; autoSelectActiveSidebarNode: boolean; + sidebarOpenDatabaseOnSingleClick: boolean; openTabsRestoreMode: OpenTabsRestoreMode; disconnectTabHandlingMode: DisconnectTabHandlingMode; dataTabReuseMode: DataTabReuseMode; @@ -772,6 +773,7 @@ export const DEFAULT_EDITOR_SETTINGS: EditorSettings = { sidebarTableSearchLocal: true, sidebarGlobalSearchLocal: false, autoSelectActiveSidebarNode: false, + sidebarOpenDatabaseOnSingleClick: false, openTabsRestoreMode: "all", disconnectTabHandlingMode: "close-tabs", dataTabReuseMode: DEFAULT_DATA_TAB_REUSE_MODE, @@ -1113,6 +1115,7 @@ export function normalizeEditorSettings(settings: Partial, exist sidebarTableSearchLocal: typeof settings.sidebarTableSearchLocal === "boolean" ? settings.sidebarTableSearchLocal : DEFAULT_EDITOR_SETTINGS.sidebarTableSearchLocal, sidebarGlobalSearchLocal: typeof settings.sidebarGlobalSearchLocal === "boolean" ? settings.sidebarGlobalSearchLocal : DEFAULT_EDITOR_SETTINGS.sidebarGlobalSearchLocal, autoSelectActiveSidebarNode: settings.autoSelectActiveSidebarNode ?? DEFAULT_EDITOR_SETTINGS.autoSelectActiveSidebarNode, + sidebarOpenDatabaseOnSingleClick: typeof settings.sidebarOpenDatabaseOnSingleClick === "boolean" ? settings.sidebarOpenDatabaseOnSingleClick : DEFAULT_EDITOR_SETTINGS.sidebarOpenDatabaseOnSingleClick, openTabsRestoreMode: normalizeOpenTabsRestoreMode( (settings as Partial).openTabsRestoreMode, ( @@ -1692,6 +1695,7 @@ export const useSettingsStore = defineStore("settings", () => { if (partial.sidebarTableSearchLocal !== undefined) editorSettings.value.sidebarTableSearchLocal = partial.sidebarTableSearchLocal; if (partial.sidebarGlobalSearchLocal !== undefined) editorSettings.value.sidebarGlobalSearchLocal = partial.sidebarGlobalSearchLocal; if (partial.autoSelectActiveSidebarNode !== undefined) editorSettings.value.autoSelectActiveSidebarNode = partial.autoSelectActiveSidebarNode; + if (partial.sidebarOpenDatabaseOnSingleClick !== undefined) editorSettings.value.sidebarOpenDatabaseOnSingleClick = partial.sidebarOpenDatabaseOnSingleClick === true; if (partial.openTabsRestoreMode !== undefined) editorSettings.value.openTabsRestoreMode = normalizeOpenTabsRestoreMode(partial.openTabsRestoreMode); if (partial.disconnectTabHandlingMode !== undefined) editorSettings.value.disconnectTabHandlingMode = normalizeDisconnectTabHandlingMode(partial.disconnectTabHandlingMode); if (partial.dataTabReuseMode !== undefined) editorSettings.value.dataTabReuseMode = normalizeDataTabReuseMode(partial.dataTabReuseMode);