From 1c34b2b568d55472c61dc6aa6ab88c80a25bcee4 Mon Sep 17 00:00:00 2001 From: vrustx <279631638@qq.com> Date: Wed, 15 Jul 2026 19:29:38 +0800 Subject: [PATCH] fix(connection): share connection group path map --- .../connection/ConnectionGroupBadge.vue | 13 +++++ .../diagram/SchemaDiagramDialog.vue | 8 +-- .../src/components/diff/DataCompareDialog.vue | 3 ++ .../components/diff/SchemaDiffConfigStep.vue | 3 ++ .../src/components/editor/AiAssistant.vue | 2 + .../export/DatabaseExportDialog.vue | 8 +-- .../src/components/layout/EditorToolbar.vue | 8 +-- .../sql-file/SqlFileExecutionDialog.vue | 8 +-- .../transfer/DataTransferDialog.vue | 3 ++ .../composables/useConnectionGroupLabel.ts | 16 ++++++ .../connectionStore.groupPaths.spec.ts | 54 +++++++++++++++++++ apps/desktop/src/stores/connectionStore.ts | 3 ++ 12 files changed, 114 insertions(+), 15 deletions(-) create mode 100644 apps/desktop/src/components/connection/ConnectionGroupBadge.vue create mode 100644 apps/desktop/src/composables/useConnectionGroupLabel.ts create mode 100644 apps/desktop/src/stores/__tests__/connectionStore.groupPaths.spec.ts diff --git a/apps/desktop/src/components/connection/ConnectionGroupBadge.vue b/apps/desktop/src/components/connection/ConnectionGroupBadge.vue new file mode 100644 index 000000000..40527adc9 --- /dev/null +++ b/apps/desktop/src/components/connection/ConnectionGroupBadge.vue @@ -0,0 +1,13 @@ + + + diff --git a/apps/desktop/src/components/diagram/SchemaDiagramDialog.vue b/apps/desktop/src/components/diagram/SchemaDiagramDialog.vue index 7ed443420..5156e95f9 100644 --- a/apps/desktop/src/components/diagram/SchemaDiagramDialog.vue +++ b/apps/desktop/src/components/diagram/SchemaDiagramDialog.vue @@ -8,6 +8,7 @@ import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { useConnectionStore } from "@/stores/connectionStore"; import DatabaseIcon from "@/components/icons/DatabaseIcon.vue"; +import ConnectionGroupBadge from "@/components/connection/ConnectionGroupBadge.vue"; import * as api from "@/lib/backend/api"; import { DIAGRAM_SQL_TYPES, isSchemaAware as isSchemaAwareDatabase } from "@/lib/database/databaseCapabilities"; import { databaseOptionsForConnection } from "@/composables/useDatabaseOptions"; @@ -814,9 +815,10 @@ onUnmounted(stopDrag); -
- - {{ connection.name }} +
+ + + {{ connection.name }}
diff --git a/apps/desktop/src/components/diff/DataCompareDialog.vue b/apps/desktop/src/components/diff/DataCompareDialog.vue index ab1e5c0f7..2ec66599e 100644 --- a/apps/desktop/src/components/diff/DataCompareDialog.vue +++ b/apps/desktop/src/components/diff/DataCompareDialog.vue @@ -7,6 +7,7 @@ import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import SearchableSelect from "@/components/ui/searchable-select/SearchableSelect.vue"; +import ConnectionGroupBadge from "@/components/connection/ConnectionGroupBadge.vue"; import { useConnectionStore } from "@/stores/connectionStore"; import { useToast } from "@/composables/useToast"; import { databaseOptionsForConnection } from "@/composables/useDatabaseOptions"; @@ -915,6 +916,7 @@ watch( @@ -1001,6 +1003,7 @@ watch( diff --git a/apps/desktop/src/components/diff/SchemaDiffConfigStep.vue b/apps/desktop/src/components/diff/SchemaDiffConfigStep.vue index fa8c8b334..c47db733b 100644 --- a/apps/desktop/src/components/diff/SchemaDiffConfigStep.vue +++ b/apps/desktop/src/components/diff/SchemaDiffConfigStep.vue @@ -5,6 +5,7 @@ import { Label } from "@/components/ui/label"; import { Button } from "@/components/ui/button"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import SearchableSelect from "@/components/ui/searchable-select/SearchableSelect.vue"; +import ConnectionGroupBadge from "@/components/connection/ConnectionGroupBadge.vue"; import { useConnectionStore } from "@/stores/connectionStore"; import DatabaseIcon from "@/components/icons/DatabaseIcon.vue"; import * as api from "@/lib/backend/api"; @@ -269,6 +270,7 @@ async function fetchDbVersion(connectionId: string, database: string, schema: st @@ -353,6 +355,7 @@ async function fetchDbVersion(connectionId: string, database: string, schema: st diff --git a/apps/desktop/src/components/editor/AiAssistant.vue b/apps/desktop/src/components/editor/AiAssistant.vue index 59456d18c..4aaf8aa7f 100644 --- a/apps/desktop/src/components/editor/AiAssistant.vue +++ b/apps/desktop/src/components/editor/AiAssistant.vue @@ -50,6 +50,7 @@ import { useConnectionStore } from "@/stores/connectionStore"; import { useSavedSqlStore } from "@/stores/savedSqlStore"; import { connectionIconType } from "@/lib/connection/connectionPresentation"; import DatabaseIcon from "@/components/icons/DatabaseIcon.vue"; +import ConnectionGroupBadge from "@/components/connection/ConnectionGroupBadge.vue"; import { useQueryStore } from "@/stores/queryStore"; import { useToast } from "@/composables/useToast"; import { useNavigationTargets } from "@/composables/useNavigationTargets"; @@ -1995,6 +1996,7 @@ async function openExternalUrl(url: string) {
+ {{ conn.name }}
diff --git a/apps/desktop/src/components/export/DatabaseExportDialog.vue b/apps/desktop/src/components/export/DatabaseExportDialog.vue index 6e6293dda..4caa58e9b 100644 --- a/apps/desktop/src/components/export/DatabaseExportDialog.vue +++ b/apps/desktop/src/components/export/DatabaseExportDialog.vue @@ -7,6 +7,7 @@ import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { useConnectionStore } from "@/stores/connectionStore"; import DatabaseIcon from "@/components/icons/DatabaseIcon.vue"; +import ConnectionGroupBadge from "@/components/connection/ConnectionGroupBadge.vue"; import * as api from "@/lib/backend/api"; import type { ExportProgress } from "@/lib/backend/api"; import { isSchemaAware } from "@/lib/database/databaseFeatureSupport"; @@ -572,9 +573,10 @@ watch( -
- - {{ c.name }} +
+ + + {{ c.name }}
diff --git a/apps/desktop/src/components/layout/EditorToolbar.vue b/apps/desktop/src/components/layout/EditorToolbar.vue index 70b3bf4c4..f19c6c0ee 100644 --- a/apps/desktop/src/components/layout/EditorToolbar.vue +++ b/apps/desktop/src/components/layout/EditorToolbar.vue @@ -14,7 +14,7 @@ import { useSchemaOptions } from "@/composables/useSchemaOptions"; import { connectionIconType } from "@/lib/connection/connectionPresentation"; import { formatDatabaseLabel, isDefaultDatabase } from "@/lib/database/defaultDatabase"; import { connectionDisplayName } from "@/lib/tabs/tabPresentation"; -import { buildConnectionGroupPathMap } from "@/lib/sidebar/sidebarLayout"; +import { useConnectionGroupLabel } from "@/composables/useConnectionGroupLabel"; import { isSingleDatabase, supportsClearableQuerySchema, supportsSqlInListPaste, supportsTransaction as supportsTransactionFeature } from "@/lib/database/databaseCapabilities"; import { hexToRgba } from "@/lib/common/color"; import { productionContextForDatabase } from "@/lib/database/productionSafety"; @@ -67,7 +67,7 @@ const activeDatabaseOptions = computed(() => { }); const connectionOptionIds = computed(() => connectionStore.connections.map((connection) => connection.id)); -const connectionGroupPaths = computed(() => buildConnectionGroupPathMap(connectionStore.sidebarLayout)); +const { connectionGroupLabel } = useConnectionGroupLabel(); const activeDatabaseValue = computed(() => props.activeTab.database || ""); const activeProductionContext = computed(() => productionContextForDatabase(props.activeConnection, props.activeTab.database)); const showConnectionProductionBadge = computed(() => activeProductionContext.value.reason === "connection"); @@ -156,10 +156,6 @@ function connectionById(connectionId: string): ConnectionConfig | undefined { return connectionStore.getConfig(connectionId); } -function connectionGroupLabel(connectionId: string): string { - return connectionGroupPaths.value.get(connectionId)?.join(" / ") || t("connectionGroup.ungroupedLabel"); -} - function databaseOptionIsProduction(database: string): boolean { if (!database || props.activeConnection?.is_production) return false; return productionContextForDatabase(props.activeConnection, database).reason === "database"; diff --git a/apps/desktop/src/components/sql-file/SqlFileExecutionDialog.vue b/apps/desktop/src/components/sql-file/SqlFileExecutionDialog.vue index 81cd08cc2..ca586e713 100644 --- a/apps/desktop/src/components/sql-file/SqlFileExecutionDialog.vue +++ b/apps/desktop/src/components/sql-file/SqlFileExecutionDialog.vue @@ -10,6 +10,7 @@ import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import DatabaseIcon from "@/components/icons/DatabaseIcon.vue"; +import ConnectionGroupBadge from "@/components/connection/ConnectionGroupBadge.vue"; import { useToast } from "@/composables/useToast"; import { useConnectionStore } from "@/stores/connectionStore"; import { useProductionSafetyStore } from "@/stores/productionSafetyStore"; @@ -502,9 +503,10 @@ watch( -
- - {{ c.name }} +
+ + + {{ c.name }}
diff --git a/apps/desktop/src/components/transfer/DataTransferDialog.vue b/apps/desktop/src/components/transfer/DataTransferDialog.vue index f556d3f4a..e936c2371 100644 --- a/apps/desktop/src/components/transfer/DataTransferDialog.vue +++ b/apps/desktop/src/components/transfer/DataTransferDialog.vue @@ -8,6 +8,7 @@ import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import SearchableSelect from "@/components/ui/searchable-select/SearchableSelect.vue"; +import ConnectionGroupBadge from "@/components/connection/ConnectionGroupBadge.vue"; import { useConnectionStore } from "@/stores/connectionStore"; import DatabaseIcon from "@/components/icons/DatabaseIcon.vue"; import * as api from "@/lib/backend/api"; @@ -382,6 +383,7 @@ function getConnectionName(id: string) { @@ -445,6 +447,7 @@ function getConnectionName(id: string) { diff --git a/apps/desktop/src/composables/useConnectionGroupLabel.ts b/apps/desktop/src/composables/useConnectionGroupLabel.ts new file mode 100644 index 000000000..400ee6991 --- /dev/null +++ b/apps/desktop/src/composables/useConnectionGroupLabel.ts @@ -0,0 +1,16 @@ +import { storeToRefs } from "pinia"; +import { useI18n } from "vue-i18n"; +import { useConnectionStore } from "@/stores/connectionStore"; + +/** Resolves the sidebar group path label for connection dropdown options. */ +export function useConnectionGroupLabel() { + const connectionStore = useConnectionStore(); + const { connectionGroupPaths } = storeToRefs(connectionStore); + const { t } = useI18n(); + + function connectionGroupLabel(connectionId: string): string { + return connectionGroupPaths.value.get(connectionId)?.join(" / ") || t("connectionGroup.ungroupedLabel"); + } + + return { connectionGroupPaths, connectionGroupLabel }; +} diff --git a/apps/desktop/src/stores/__tests__/connectionStore.groupPaths.spec.ts b/apps/desktop/src/stores/__tests__/connectionStore.groupPaths.spec.ts new file mode 100644 index 000000000..d998d3bb1 --- /dev/null +++ b/apps/desktop/src/stores/__tests__/connectionStore.groupPaths.spec.ts @@ -0,0 +1,54 @@ +import { createPinia, setActivePinia, storeToRefs } from "pinia"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import type { SidebarLayout } from "@/types/database"; + +const groupedLayout: SidebarLayout = { + groups: [{ id: "project", name: "Project", collapsed: false }], + order: [ + { + type: "group", + id: "project", + children: [{ type: "connection", id: "grouped" }], + }, + { type: "connection", id: "root" }, + ], +}; + +describe("connectionStore connection group paths", () => { + beforeEach(() => { + vi.stubGlobal("localStorage", { + getItem: vi.fn(() => null), + setItem: vi.fn(), + removeItem: vi.fn(), + }); + setActivePinia(createPinia()); + }); + + it("shares one cached path map across store consumers", async () => { + const { useConnectionStore } = await import("@/stores/connectionStore"); + const store = useConnectionStore(); + store.sidebarLayout = groupedLayout; + + const firstConsumer = storeToRefs(store).connectionGroupPaths; + const secondConsumer = storeToRefs(store).connectionGroupPaths; + + expect(firstConsumer.value).toBe(secondConsumer.value); + expect(firstConsumer.value.get("grouped")).toEqual(["Project"]); + expect(firstConsumer.value.get("root")).toEqual([]); + }); + + it("rebuilds the shared map when the sidebar layout changes", async () => { + const { useConnectionStore } = await import("@/stores/connectionStore"); + const store = useConnectionStore(); + store.sidebarLayout = groupedLayout; + const previousPaths = store.connectionGroupPaths; + + store.sidebarLayout = { + groups: [], + order: [{ type: "connection", id: "grouped" }], + }; + + expect(store.connectionGroupPaths).not.toBe(previousPaths); + expect(store.connectionGroupPaths.get("grouped")).toEqual([]); + }); +}); diff --git a/apps/desktop/src/stores/connectionStore.ts b/apps/desktop/src/stores/connectionStore.ts index 4cdc37744..12ab61635 100644 --- a/apps/desktop/src/stores/connectionStore.ts +++ b/apps/desktop/src/stores/connectionStore.ts @@ -34,6 +34,7 @@ import { moveConnectionToGroup as moveConnectionToGroupOp, remapSidebarLayoutConnectionIds, reorderEntry as reorderEntryOp, + buildConnectionGroupPathMap, type DropPosition, } from "@/lib/sidebar/sidebarLayout"; import type { SqlCompletionColumn, SqlCompletionForeignKey, SqlCompletionObject, SqlCompletionTable } from "@/lib/sql/sqlCompletion"; @@ -327,6 +328,7 @@ export const useConnectionStore = defineStore("connection", () => { allDatabases?: boolean; } | null>(null); const sidebarLayout = ref(emptyLayout()); + const connectionGroupPaths = computed(() => buildConnectionGroupPathMap(sidebarLayout.value)); let layoutPersistTimer: ReturnType | null = null; const staleTreeRefreshIds = new Set(); const metadataLoadCoordinator = new MetadataLoadCoordinator((event) => { @@ -5318,6 +5320,7 @@ export const useConnectionStore = defineStore("connection", () => { markConnectionLost, recordConnectionLostError, sidebarLayout, + connectionGroupPaths, getConfig, connectionIdentifierQuote, isTreeNodePinned,