mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-30 00:19:35 +00:00
feat: improve connection and table tree UX
This commit is contained in:
+17
-18
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch, onMounted, onUnmounted, nextTick, defineAsyncComponent, type Ref } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { DatabaseZap, FilePlus2, Play, Loader2, Square, X, Globe, Moon, Sun, Upload, Download, Plus, History, Server, Table2, Database, Search, ShieldCheck, Bot, Pin, AlignLeft, CloudDownload, ArrowLeftRight, FileCode, Settings, Sparkles, GitBranch } from "lucide-vue-next";
|
||||
import { DatabaseZap, FilePlus2, Play, Loader2, Square, X, Globe, Moon, Sun, Upload, Download, Plus, History, Table2, Database, Search, ShieldCheck, Bot, Pin, AlignLeft, CloudDownload, ArrowLeftRight, FileCode, Settings, Sparkles, GitBranch } from "lucide-vue-next";
|
||||
import { Splitpanes, Pane } from "splitpanes";
|
||||
import "splitpanes/dist/splitpanes.css";
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -55,6 +55,7 @@ import { getCurrentWebview } from "@tauri-apps/api/webview";
|
||||
import { getVersion } from "@tauri-apps/api/app";
|
||||
import * as api from "@/lib/tauri";
|
||||
import { canCancelQueryExecution, queryExecutionLabelKey } from "@/lib/queryExecutionState";
|
||||
import { connectionDriverLabel, connectionIconType, connectionOptionSubtitle } from "@/lib/connectionPresentation";
|
||||
import { resolveExecutableSql } from "@/lib/sqlExecutionTarget";
|
||||
import { buildTableSelectSql, quoteTableIdentifier } from "@/lib/tableSelectSql";
|
||||
import { isTauriRuntime } from "@/lib/tauriRuntime";
|
||||
@@ -438,14 +439,6 @@ function connectionDisplayName(connectionId: string): string {
|
||||
return connectionStore.getConfig(connectionId)?.name || connectionId;
|
||||
}
|
||||
|
||||
function connectionDriverLabel(connection?: ConnectionConfig): string {
|
||||
return connection?.driver_label || connection?.db_type.toUpperCase() || "";
|
||||
}
|
||||
|
||||
function connectionIconType(connection?: ConnectionConfig): string {
|
||||
return connection?.driver_profile || connection?.db_type || "postgres";
|
||||
}
|
||||
|
||||
function connectionColor(connectionId: string): string {
|
||||
return connectionStore.getConfig(connectionId)?.color || "";
|
||||
}
|
||||
@@ -1229,25 +1222,31 @@ async function setupFileDrop() {
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="flex items-center gap-1">
|
||||
<span v-if="activeConnection?.color" class="h-4 w-1 rounded-full shrink-0" :style="{ backgroundColor: activeConnection.color }" />
|
||||
<Server class="h-3.5 w-3.5 shrink-0" />
|
||||
<Select
|
||||
:model-value="activeConnectionValue"
|
||||
@update:model-value="changeActiveConnection"
|
||||
>
|
||||
<SelectTrigger class="h-6 w-auto max-w-48 border-0 bg-transparent px-1 text-xs font-medium text-foreground shadow-none focus:ring-0">
|
||||
<SelectValue :placeholder="t('editor.selectConnection')">
|
||||
{{ connectionDisplayName(activeConnectionValue) }}
|
||||
</SelectValue>
|
||||
<SelectTrigger class="h-6 w-auto max-w-56 border-0 bg-transparent px-1 text-xs font-medium text-foreground shadow-none focus:ring-0">
|
||||
<div v-if="activeConnection" class="flex min-w-0 items-center gap-1.5">
|
||||
<DatabaseIcon :db-type="connectionIconType(activeConnection)" class="h-3.5 w-3.5 shrink-0" />
|
||||
<span class="truncate">{{ connectionDisplayName(activeConnectionValue) }}</span>
|
||||
</div>
|
||||
<SelectValue v-else :placeholder="t('editor.selectConnection')" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectContent class="min-w-64">
|
||||
<SelectItem
|
||||
v-for="connection in connectionStore.connections"
|
||||
:key="connection.id"
|
||||
:value="connection.id"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="flex min-w-0 items-center gap-2">
|
||||
<span v-if="connection.color" class="h-3.5 w-1 rounded-full shrink-0" :style="{ backgroundColor: connection.color }" />
|
||||
<span>{{ connection.name }}</span>
|
||||
<span v-else class="h-3.5 w-1 shrink-0" />
|
||||
<DatabaseIcon :db-type="connectionIconType(connection)" class="h-3.5 w-3.5 shrink-0" />
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="truncate">{{ connection.name }}</div>
|
||||
<div class="truncate text-[11px] font-normal text-muted-foreground">{{ connectionOptionSubtitle(connection) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
@@ -1497,7 +1496,7 @@ async function setupFileDrop() {
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="truncate text-sm font-medium">{{ connection.name }}</div>
|
||||
<div class="truncate text-xs text-muted-foreground">
|
||||
{{ connectionDriverLabel(connection) }} · {{ connection.host || connection.database || 'local' }}{{ connection.port ? ':' + connection.port : '' }}
|
||||
{{ connectionOptionSubtitle(connection) || connectionDriverLabel(connection) }}
|
||||
</div>
|
||||
</div>
|
||||
<FilePlus2 class="h-4 w-4 text-muted-foreground" />
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
type ExportedTableSql,
|
||||
} from "@/lib/databaseExport";
|
||||
import { qualifiedTableName as buildQualifiedTableName, quoteTableIdentifier } from "@/lib/tableSelectSql";
|
||||
import { treeNodeRowAction } from "@/lib/treeNodeClick";
|
||||
import DatabaseIcon from "@/components/icons/DatabaseIcon.vue";
|
||||
import {
|
||||
Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter,
|
||||
@@ -178,14 +179,10 @@ async function toggle() {
|
||||
|
||||
function onClick() {
|
||||
const node = props.node;
|
||||
if (node.type === "table" || node.type === "view") {
|
||||
const action = treeNodeRowAction(node.type, canExpand);
|
||||
if (action === "open-data") {
|
||||
openData();
|
||||
toggle();
|
||||
} else if (node.type === "redis-db") {
|
||||
toggle();
|
||||
} else if (node.type === "mongo-collection") {
|
||||
toggle();
|
||||
} else if (canExpand) {
|
||||
} else if (action === "toggle") {
|
||||
toggle();
|
||||
}
|
||||
}
|
||||
@@ -629,9 +626,15 @@ async function showMore() {
|
||||
@click="onClick"
|
||||
>
|
||||
<template v-if="canExpand">
|
||||
<Loader2 v-if="node.isLoading" class="w-3.5 h-3.5 shrink-0 animate-spin text-muted-foreground" />
|
||||
<ChevronDown v-else-if="node.isExpanded" class="w-3.5 h-3.5 shrink-0 text-muted-foreground" />
|
||||
<ChevronRight v-else class="w-3.5 h-3.5 shrink-0 text-muted-foreground" />
|
||||
<button
|
||||
type="button"
|
||||
class="-m-0.5 flex h-4 w-4 shrink-0 items-center justify-center rounded-sm text-muted-foreground hover:bg-muted hover:text-foreground"
|
||||
@click.stop="toggle"
|
||||
>
|
||||
<Loader2 v-if="node.isLoading" class="w-3.5 h-3.5 animate-spin" />
|
||||
<ChevronDown v-else-if="node.isExpanded" class="w-3.5 h-3.5" />
|
||||
<ChevronRight v-else class="w-3.5 h-3.5" />
|
||||
</button>
|
||||
</template>
|
||||
<span v-else class="w-3.5 h-3.5 shrink-0" />
|
||||
<DatabaseIcon v-if="node.type === 'connection'" :db-type="connectionIconType(node.connectionId)" class="w-3.5 h-3.5 shrink-0" />
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import type { ConnectionConfig } from "@/types/database";
|
||||
|
||||
type ConnectionPresentationConfig = Pick<
|
||||
ConnectionConfig,
|
||||
"db_type" | "driver_profile" | "driver_label" | "host" | "port" | "database"
|
||||
>;
|
||||
|
||||
const LOCAL_DATABASE_TYPES = new Set(["sqlite", "duckdb"]);
|
||||
|
||||
export function connectionIconType(connection?: Pick<ConnectionConfig, "db_type" | "driver_profile">): string {
|
||||
return connection?.driver_profile || connection?.db_type || "postgres";
|
||||
}
|
||||
|
||||
export function connectionDriverLabel(connection?: Pick<ConnectionConfig, "db_type" | "driver_label">): string {
|
||||
return connection?.driver_label || connection?.db_type.toUpperCase() || "";
|
||||
}
|
||||
|
||||
export function connectionEndpointLabel(connection?: ConnectionPresentationConfig): string {
|
||||
if (!connection) return "";
|
||||
if (LOCAL_DATABASE_TYPES.has(connection.db_type)) {
|
||||
return connection.host || connection.database || "local";
|
||||
}
|
||||
if (connection.host && connection.port) return `${connection.host}:${connection.port}`;
|
||||
return connection.host || connection.database || "";
|
||||
}
|
||||
|
||||
export function connectionOptionSubtitle(connection?: ConnectionPresentationConfig): string {
|
||||
return [connectionDriverLabel(connection), connectionEndpointLabel(connection)].filter(Boolean).join(" · ");
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import type { TreeNodeType } from "@/types/database";
|
||||
|
||||
export type TreeNodeRowAction = "open-data" | "toggle" | "none";
|
||||
|
||||
const dataNodeTypes = new Set<TreeNodeType>(["table", "view"]);
|
||||
const toggleLeafNodeTypes = new Set<TreeNodeType>(["redis-db", "mongo-collection"]);
|
||||
|
||||
export function treeNodeRowAction(type: TreeNodeType, canExpand: boolean): TreeNodeRowAction {
|
||||
if (dataNodeTypes.has(type)) return "open-data";
|
||||
if (toggleLeafNodeTypes.has(type)) return "toggle";
|
||||
if (canExpand) return "toggle";
|
||||
return "none";
|
||||
}
|
||||
Reference in New Issue
Block a user