feat(sidebar): show connection group paths

This commit is contained in:
二丫讲梵 authored and GitHub committed 2026-07-14 19:03:27 +08:00
1 parent 69dd363417
commit c2b8a1795a
14 files changed
+251 -10

No files matched your search

@@ -14,6 +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 { isSingleDatabase, supportsSqlInListPaste, supportsTransaction as supportsTransactionFeature } from "@/lib/database/databaseCapabilities";
import { hexToRgba } from "@/lib/common/color";
import { productionContextForDatabase } from "@/lib/database/productionSafety";
@@ -66,6 +67,7 @@ const activeDatabaseOptions = computed(() => {
});
const connectionOptionIds = computed(() => connectionStore.connections.map((connection) => connection.id));
const connectionGroupPaths = computed(() => buildConnectionGroupPathMap(connectionStore.sidebarLayout));
const activeDatabaseValue = computed(() => props.activeTab.database || "");
const activeProductionContext = computed(() => productionContextForDatabase(props.activeConnection, props.activeTab.database));
const showConnectionProductionBadge = computed(() => activeProductionContext.value.reason === "connection");
@@ -154,6 +156,10 @@ 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";
@@ -326,6 +332,8 @@ function databaseOptionIsProduction(database: string): boolean {
:loading-text="t('common.loading')"
trigger-class="font-medium text-foreground"
:display-name="connectionDisplayName"
list-class="w-96 max-w-[calc(100vw-2rem)]"
item-class="h-9"
@update:model-value="(connectionId) => emit('changeConnection', connectionId)"
>
<template #trigger-label="{ label }">
@@ -339,7 +347,10 @@ function databaseOptionIsProduction(database: string): boolean {
<template #option-label="{ option, label }">
<div class="flex min-w-0 items-center gap-2">
<DatabaseIcon :db-type="connectionIconType(connectionById(option))" class="h-3.5 w-3.5 shrink-0" />
<TruncatedTextTooltip :text="label" class="min-w-0 flex-1" side="left" :side-offset="8" />
<div class="flex min-w-0 flex-1 items-center gap-2">
<TruncatedTextTooltip :text="connectionGroupLabel(option)" class="block min-w-0 max-w-48 shrink-0 rounded-sm bg-muted/70 px-1.5 py-0.5 text-[11px] text-muted-foreground" side="left" :side-offset="8" />
<TruncatedTextTooltip :text="label" class="block min-w-[7rem] flex-1 text-sm font-medium" side="left" :side-offset="8" />
</div>
</div>
</template>
</SearchableSelect>
@@ -27,6 +27,7 @@ const props = withDefaults(
triggerClass?: HTMLAttributes["class"];
triggerIconClass?: HTMLAttributes["class"];
contentClass?: HTMLAttributes["class"];
listClass?: HTMLAttributes["class"];
itemClass?: HTMLAttributes["class"];
displayName?: (option: string) => string;
optionTooltip?: (option: string) => string | undefined;
@@ -236,13 +237,13 @@ function handleKeydown(event: KeyboardEvent) {
</PopoverTrigger>
<PopoverContent :align="SEARCHABLE_SELECT_HELP_PANEL_ALIGN" :class="cn('w-auto max-w-[calc(100vw-1rem)] border-0 bg-transparent p-0 shadow-none ring-0', contentClass)">
<div class="flex min-w-0 flex-col gap-2 sm:flex-row sm:items-start">
<div ref="listCard" class="w-52 shrink-0 rounded-md border bg-popover p-1.5 shadow-md">
<div ref="listCard" :class="cn('w-52 shrink-0 rounded-md border bg-popover p-1.5 shadow-md', listClass)">
<div class="relative rounded-sm border bg-background">
<Search class="pointer-events-none absolute left-2 top-1/2 h-3 w-3 -translate-y-1/2 text-muted-foreground" />
<span v-if="!searchText" class="pointer-events-none absolute left-[25px] top-1/2 -translate-y-1/2 text-sm text-muted-foreground">{{ searchPlaceholder }}</span>
<Input ref="searchInput" :model-value="searchText" class="h-6 border-0 pl-6 pr-2 text-sm caret-foreground shadow-none focus-visible:ring-0" @update:model-value="(value) => (searchText = String(value))" @keydown="handleKeydown" />
</div>
<div ref="listContainer" class="max-h-64 overflow-y-auto py-1" @scroll="updateHelpPanelOffset">
<div ref="listContainer" class="dbx-searchable-select-list max-h-64 overflow-y-auto py-1" @scroll="updateHelpPanelOffset">
<div v-if="loading" class="px-2 py-2 text-sm text-muted-foreground">
{{ loadingText }}
</div>
@@ -348,4 +349,43 @@ function handleKeydown(event: KeyboardEvent) {
border-color: rgb(147, 197, 253) !important;
box-shadow: 0 0 0 2px rgba(147, 197, 253, 0.24) !important;
}
.dbx-searchable-select-list {
scrollbar-width: thin;
scrollbar-color: color-mix(in oklch, var(--foreground) 30%, transparent) transparent;
}
.dbx-searchable-select-list::-webkit-scrollbar {
width: 6px;
height: 6px;
}
.dbx-searchable-select-list::-webkit-scrollbar-track {
background: transparent;
}
.dbx-searchable-select-list::-webkit-scrollbar-thumb {
border: 1px solid transparent;
border-radius: 999px;
background: color-mix(in oklch, var(--foreground) 30%, transparent);
background-clip: padding-box;
}
.dbx-searchable-select-list:hover::-webkit-scrollbar-thumb {
border: 0;
background: color-mix(in oklch, var(--foreground) 48%, transparent);
}
.dark .dbx-searchable-select-list {
scrollbar-color: rgb(82, 82, 91) transparent;
}
.dark .dbx-searchable-select-list::-webkit-scrollbar-thumb {
background: rgb(82, 82, 91);
background-clip: padding-box;
}
.dark .dbx-searchable-select-list:hover::-webkit-scrollbar-thumb {
background: rgb(113, 113, 122);
}
</style>