Enable safe multi-connection cleanup from the sidebar (#816)

The sidebar already supports multi-select, so deletion now resolves the context-clicked selected connection set before confirming. Connection removal gained a batched path to persist once while pruning sidebar layout, pins, active state, connection errors, and stale selection for every removed connection.

Constraint: No new dependencies and existing single-connection delete behavior must remain available.\nRejected: Delete every selected tree item regardless of type | mixed tree selections could unexpectedly remove connections while table/object deletion has separate SQL confirmation behavior.\nConfidence: high\nScope-risk: narrow\nTested: pnpm fmt; pnpm test; pnpm typecheck; pnpm lint; pnpm build\nNot-tested: Manual desktop UI interaction against a running Tauri window

Co-authored-by: caisin <caisin@caisins-Mac-mini.local>
This commit is contained in:
Caisinandcaisin authored and GitHub committed 2026-06-07 16:58:49 +08:00
1 parent 74e19ec6a3
commit 13363b9094
10 files changed
+137 -19

No files matched your search

@@ -131,6 +131,7 @@ import {
treeSelectionRangeIdsByIndex,
treeSelectionRangeIds,
} from "@/lib/sidebarTreeSelection";
import { selectedConnectionDeleteTargets } from "@/lib/sidebarConnectionSelection";
import { sidebarTreeContextKey } from "@/lib/sidebarTreeContext";
import DangerConfirmDialog from "@/components/editor/DangerConfirmDialog.vue";
import ProcedureExecutionDialog from "@/components/objects/ProcedureExecutionDialog.vue";
@@ -904,20 +905,41 @@ async function refresh() {
const showDeleteConfirm = ref(false);
function connectionDeleteTargets() {
return selectedConnectionDeleteTargets(props.node, selectedTreeNodesInVisibleOrder());
}
function connectionDeleteMenuLabel(): string {
const count = connectionDeleteTargets().length;
return count > 1 ? t("contextMenu.deleteSelectedConnections", { count }) : t("contextMenu.deleteConnection");
}
function connectionDeleteConfirmMessage(): string {
const targets = connectionDeleteTargets();
return targets.length > 1
? t("contextMenu.confirmDeleteSelectedMessage", { count: targets.length })
: t("contextMenu.confirmDeleteMessage", { name: props.node.label });
}
function deleteConnection() {
if (!connectionDeleteTargets().length) return;
showDeleteConfirm.value = true;
}
async function confirmDelete() {
const node = props.node;
if (node.connectionId) {
try {
await connectionStore.disconnect(node.connectionId);
await connectionStore.removeConnection(node.connectionId);
toast(t("connection.deleted"), 2000);
} catch (e: any) {
toast(t("connection.saveFailed", { message: e?.message || String(e) }), 5000);
const targets = connectionDeleteTargets();
if (!targets.length) return;
try {
for (const target of targets) {
await connectionStore.disconnect(target.connectionId);
}
await connectionStore.removeConnections(targets.map((target) => target.connectionId));
toast(
targets.length > 1 ? t("connection.deletedSelected", { count: targets.length }) : t("connection.deleted"),
2000,
);
} catch (e: any) {
toast(t("connection.saveFailed", { message: e?.message || String(e) }), 5000);
}
}
@@ -2913,7 +2935,7 @@ function treeItemMenuItems(): ContextMenuItem[] {
items.push({ label: t("contextMenu.duplicateConnection"), action: duplicateConnection, icon: CopyPlus });
items.push({ label: "", separator: true });
items.push({
label: t("contextMenu.deleteConnection"),
label: connectionDeleteMenuLabel(),
action: deleteConnection,
icon: Trash2,
shortcut: shortcutDelete,
@@ -3383,7 +3405,7 @@ function treeItemMenuItems(): ContextMenuItem[] {
<DialogTitle>{{ t("contextMenu.confirmDeleteTitle") }}</DialogTitle>
</DialogHeader>
<p class="text-sm text-muted-foreground">
{{ t("contextMenu.confirmDeleteMessage", { name: node.label }) }}
{{ connectionDeleteConfirmMessage() }}
</p>
<DialogFooter>
<Button variant="outline" @click="showDeleteConfirm = false">{{ t("dangerDialog.cancel") }}</Button>
@@ -3393,7 +3415,7 @@ function treeItemMenuItems(): ContextMenuItem[] {
showDeleteConfirm = false;
confirmDelete();
"
>{{ t("contextMenu.deleteConnection") }}</Button
>{{ connectionDeleteMenuLabel() }}</Button
>
</DialogFooter>
</DialogContent>