feat(sidebar): add nested virtual table groups with drag and batch move

This commit is contained in:
ReadMe.md authored and GitHub committed 2026-09-19 09:24:34 +08:00
1 parent 0db9756a57
commit 1f43d184aa
31 files changed
+1714 -20

No files matched your search

@@ -64,6 +64,7 @@ import SidebarLocateButton from "./SidebarLocateButton.vue";
import SidebarRegexToggleButton from "./SidebarRegexToggleButton.vue";
import SidebarTreeRuntimeHost from "./SidebarTreeRuntimeHost.vue";
import SidebarTreeItemDialogs from "./SidebarTreeItemDialogs.vue";
import SidebarTableVGroupDialog from "./SidebarTableVGroupDialog.vue";
import InstallExtensionDialog from "@/components/objects/InstallExtensionDialog.vue";
import ExtensionDetailsDialog from "@/components/objects/ExtensionDetailsDialog.vue";
import { RecycleScroller } from "vue-virtual-scroller";
@@ -1483,6 +1484,12 @@ function topOcclusionHeightForSidebarNode(nodeId: string): number {
return SIDEBAR_TREE_ROW_HEIGHT;
}
/** Select and reveal a freshly created table group. */
async function focusCreatedTableVGroup(groupId: string) {
store.selectedTreeNodeId = groupId;
await scrollToSidebarNode(groupId);
}
async function scrollToSidebarNode(nodeId: string, options?: { align?: SidebarNodeScrollAlign }) {
await nextTick();
@@ -1655,6 +1662,14 @@ async function locateTabInSidebar(tab: QueryTab | undefined | null, align: Sideb
}
}
// 表分组行同样是投影出的合成节点(不登记已加载子节点,上面的守卫会跳过),
// 折叠状态存在布局里,必须经布局 op 展开,否则下次投影又把它折叠回去。
for (const node of nodePath) {
if (node.type !== "table-vgroup" || !node.vgroupId) continue;
const group = store.tableVGroupLayoutFor(node)?.groups.find((current) => current.id === node.vgroupId);
if (group?.collapsed) store.toggleTableVGroupCollapsed(node, node.vgroupId);
}
// Connection groups never register loaded tree children, so the guard above
// skips them and a collapsed group keeps the target out of the visible flat
// tree. Reopen them through the layout op so the expansion is persisted and
@@ -2855,6 +2870,7 @@ defineExpose({ focusSearch, createNewGroup, collapseAllTreeNodes, locateTabInSid
</template>
</SidebarDangerConfirmDialog>
<SidebarTreeItemDialogs v-if="sidebarTreeItemDialogController" :key="sidebarTreeItemDialogController.node?.id" :controller="sidebarTreeItemDialogController" @closed="sidebarTreeItemDialogController = null" />
<SidebarTableVGroupDialog @created="focusCreatedTableVGroup" />
<InstallExtensionDialog v-if="sidebarInstallExtensionTarget" ref="sidebarInstallExtensionDialogRef" :node="sidebarInstallExtensionTarget" @close="refreshSidebarActionTarget" @changed="refreshSidebarActionTarget" />
<ExtensionDetailsDialog v-if="sidebarExtensionDetailsTarget" ref="sidebarExtensionDetailsDialogRef" :node="sidebarExtensionDetailsTarget" />
<div v-if="store.treeNodes.length === 0" class="px-3 py-8 text-center text-muted-foreground text-xs">
@@ -0,0 +1,71 @@
<script setup lang="ts">
import { computed } from "vue";
import { useI18n } from "vue-i18n";
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { useConnectionStore } from "@/stores/connectionStore";
import { useToast } from "@/composables/useToast";
import { tableVGroupNodeId } from "@/lib/table/tableVGroup";
import { showTableVGroupDialog, tableVGroupName, tableVGroupDialogScope, tableVGroupDialogParentGroupId, tableVGroupDialogTableNames, showTableVGroupDeleteConfirm, tableVGroupDeleteTarget } from "./sidebarTreeDialogState";
const { t } = useI18n();
const { toast } = useToast();
const connectionStore = useConnectionStore();
const emit = defineEmits<{ created: [groupId: string] }>();
const deleteConfirmMessage = computed(() => t("tableVGroup.deleteGroupConfirmMessage", { name: tableVGroupDeleteTarget.value?.name ?? "" }));
function confirmCreate() {
const scope = tableVGroupDialogScope.value;
const name = tableVGroupName.value.trim();
if (!scope || !name) return;
const groupId = connectionStore.createTableVGroup(scope, name, tableVGroupDialogParentGroupId.value);
if (groupId) {
for (const tableName of tableVGroupDialogTableNames.value) {
connectionStore.moveTableToVGroup(scope, tableName, groupId);
}
emit("created", tableVGroupNodeId(groupId));
}
showTableVGroupDialog.value = false;
tableVGroupName.value = "";
}
function confirmDelete() {
const target = tableVGroupDeleteTarget.value;
showTableVGroupDeleteConfirm.value = false;
tableVGroupDeleteTarget.value = null;
if (!target) return;
connectionStore.deleteTableVGroups(target.scope, [target.groupId]);
toast(t("tableVGroup.groupDeleted"), 2000);
}
</script>
<template>
<Dialog v-model:open="showTableVGroupDialog">
<DialogContent class="max-w-sm">
<DialogHeader>
<DialogTitle>{{ tableVGroupDialogTableNames.length ? t("tableVGroup.moveToNewGroup") : t("tableVGroup.newSubgroup") }}</DialogTitle>
</DialogHeader>
<Input v-model="tableVGroupName" :placeholder="t('connectionGroup.groupNamePlaceholder')" @keydown.enter.prevent="confirmCreate" />
<DialogFooter>
<Button variant="outline" @click="showTableVGroupDialog = false">{{ t("dangerDialog.cancel") }}</Button>
<Button :disabled="!tableVGroupName.trim()" @click="confirmCreate">{{ t("connectionGroup.createGroup") }}</Button>
</DialogFooter>
</DialogContent>
</Dialog>
<Dialog v-model:open="showTableVGroupDeleteConfirm">
<DialogContent class="max-w-sm">
<DialogHeader>
<DialogTitle>{{ t("tableVGroup.deleteGroupConfirmTitle") }}</DialogTitle>
</DialogHeader>
<p class="text-sm text-muted-foreground">{{ deleteConfirmMessage }}</p>
<DialogFooter>
<Button variant="outline" @click="showTableVGroupDeleteConfirm = false">{{ t("dangerDialog.cancel") }}</Button>
<Button variant="destructive" @click="confirmDelete">{{ t("tableVGroup.deleteGroup") }}</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</template>
@@ -84,6 +84,7 @@ import { canTreeNodePin, canTreeNodeShowExpander } from "@/lib/sidebar/sidebarTr
import { sidebarConnectionVisibleFilterMenu } from "@/lib/sidebar/sidebarVisibleFilterMenu";
import { supportsSidebarObjectNameFilter } from "@/lib/sidebar/sidebarObjectNameFilter";
import { connectionGroupDestinationRows } from "@/lib/sidebar/sidebarLayout";
import { hasTableVGroupEntries, selectedTableVGroupMoveTargets, tableVGroupDestinationRows, tableVGroupPathForTable, tableVGroupsEnabled } from "@/lib/table/tableVGroup";
import { objectTypesForGroupNode } from "@/lib/table/tableTree";
import { loadSidebarObjectGroup } from "@/lib/sidebar/sidebarObjectGroupRouting";
import { requestObjectBrowserSearchFocus } from "@/lib/tabs/objectBrowserSearchFocus";
@@ -219,6 +220,13 @@ import {
sidebarDangerRunningCancel,
sidebarFormTarget,
showDeleteConfirm,
showTableVGroupDialog,
showTableVGroupDeleteConfirm,
tableVGroupDeleteTarget,
tableVGroupName,
tableVGroupDialogScope,
tableVGroupDialogParentGroupId,
tableVGroupDialogTableNames,
showDropTableConfirm,
showDropTableChildObjectConfirm,
showBatchDropConfirm,
@@ -819,6 +827,13 @@ async function toggle(requestId = beginNavigationRequest()) {
return;
}
if (node.type === "table-vgroup") {
node.isExpanded = !node.isExpanded;
if (node.vgroupId) connectionStore.toggleTableVGroupCollapsed(node, node.vgroupId);
emitNodeToggled(node, wasExpanded);
return;
}
if (node.type === "type" && customTypeCapabilities(currentDatabaseType()).details && node.children !== undefined) {
node.isExpanded = node.children.length > 0 ? !node.isExpanded : false;
emitNodeToggled(node, wasExpanded);
@@ -1402,6 +1417,10 @@ function requestRenameSelectedNode(): boolean {
startRenameGroup();
return true;
}
if (activeNode.value.type === "table-vgroup" && activeNode.value.vgroupId) {
emit("request-group-rename", activeNode.value.id);
return true;
}
if (activeNode.value.type === "saved-sql-file" && activeNode.value.savedSqlId) {
emit("request-saved-sql-rename", activeNode.value.id);
return true;
@@ -1463,6 +1482,10 @@ function requestDeleteSelectedNode(): boolean {
deleteConnectionGroup();
return true;
}
if (activeNode.value.type === "table-vgroup") {
requestTableVGroupDelete(activeNode.value);
return true;
}
if (canDropDatabase.value) {
dropDatabase();
return true;
@@ -5707,6 +5730,7 @@ function buildConnectionSidebarMenu(context: SidebarMenuFactoryContext): boolean
function buildDatabaseSidebarMenu(context: SidebarMenuFactoryContext): boolean {
const { node, items } = context;
appendTableVGroupContainerItems(node, items);
// 4. Database / Schema
if (node.type === "database" || node.type === "schema") {
if (isXuguSyntheticTreeNode(currentDatabaseType(), node.type, node.schema)) {
@@ -6104,6 +6128,10 @@ function buildObjectSidebarMenu(context: SidebarMenuFactoryContext): boolean {
}
const destructiveActions: ContextMenuItem[] = [];
items.push(copyNameMenuItem());
if (node.type === "table") {
const vgroupMoveItems = buildTableVGroupMoveMenuItems(node);
if (vgroupMoveItems.length) items.push({ label: t("tableVGroup.moveToGroup"), icon: FolderInput, children: vgroupMoveItems });
}
items.push({ label: t("contextMenu.newQuery"), action: newQuery, icon: TerminalSquare });
if (node.type === "table" && supportsAiAssistantContext(currentDatabaseType())) {
items.push(addToAiMenuItem(node));
@@ -6435,6 +6463,7 @@ function treeTableClipboardMenuItems(node: TreeNode): ContextMenuItem[] {
function buildObjectGroupSidebarMenu(context: SidebarMenuFactoryContext): boolean {
const { node, items } = context;
appendTableVGroupContainerItems(node, items);
// 9. Group Labels (group-columns, group-tables, etc.)
if (isGroupLabel(node)) {
const mysqlObjectTemplate = node.connectionId ? mysqlObjectTemplateForGroup(connectionStore.getConfig(node.connectionId), node) : null;
@@ -6506,7 +6535,89 @@ function buildObjectGroupSidebarMenu(context: SidebarMenuFactoryContext): boolea
return false;
}
const sidebarMenuFactories: readonly SidebarMenuFactory[] = [buildConnectionSidebarMenu, buildDatabaseSidebarMenu, buildSpecialSidebarMenu, buildObjectSidebarMenu, buildObjectGroupSidebarMenu];
function buildTableVGroupMoveMenuItems(node: TreeNode): ContextMenuItem[] {
const layout = connectionStore.tableVGroupLayoutFor(node);
const targets = selectedTableVGroupMoveTargets(node, selectedTreeNodesInVisibleOrder());
const targetNames = targets.map((target) => target.label);
const targetsInGroup = (groupId: string) => targetNames.every((name) => tableVGroupPathForTable(layout, name).includes(groupId));
const items: ContextMenuItem[] = tableVGroupDestinationRows(layout).map((row) => ({
label: row.name,
title: row.path.join(" / "),
disabled: targetNames.length > 0 && targetsInGroup(row.id),
action: () => {
for (const name of targetNames) connectionStore.moveTableToVGroup(node, name, row.id);
},
}));
if (targetNames.some((name) => tableVGroupPathForTable(layout, name).length > 0)) {
items.push({
label: t("tableVGroup.removeFromGroup"),
action: () => {
for (const name of targetNames) connectionStore.moveTableToVGroup(node, name, null);
},
});
}
items.push({ label: "", separator: true });
items.push({
label: t("tableVGroup.moveToNewGroup"),
action: () => {
tableVGroupDialogScope.value = node;
tableVGroupDialogParentGroupId.value = null;
tableVGroupDialogTableNames.value = targetNames;
tableVGroupName.value = "";
showTableVGroupDialog.value = true;
},
icon: FolderPlus,
});
return items;
}
function appendTableVGroupContainerItems(node: TreeNode, items: ContextMenuItem[]) {
if (node.type !== "database" && node.type !== "schema" && node.type !== "linked-server-schema" && node.type !== "group-tables") return;
const layout = connectionStore.tableVGroupLayoutFor(node);
if (!hasTableVGroupEntries(layout)) return;
const enabled = tableVGroupsEnabled(layout);
items.push({
label: enabled ? t("tableVGroup.disableGroups") : t("tableVGroup.enableGroups"),
action: () => connectionStore.setTableVGroupsEnabled(node, !enabled),
icon: FolderInput,
});
items.push({ label: "", separator: true });
}
function buildTableVGroupSidebarMenu(context: SidebarMenuFactoryContext): boolean {
const { node, items } = context;
if (node.type !== "table-vgroup" || !node.vgroupId) return false;
const groupId = node.vgroupId;
items.push({
label: t("tableVGroup.newSubgroup"),
action: () => {
tableVGroupDialogScope.value = node;
tableVGroupDialogParentGroupId.value = groupId;
tableVGroupDialogTableNames.value = [];
tableVGroupName.value = "";
showTableVGroupDialog.value = true;
},
icon: FolderPlus,
});
items.push({ label: t("connectionGroup.renameGroup"), action: () => emit("request-group-rename", node.id), icon: Pencil, shortcut: shortcutRename });
items.push({
label: t("tableVGroup.deleteGroup"),
action: () => requestTableVGroupDelete(node),
icon: Trash2,
variant: "destructive" as const,
shortcut: shortcutDelete,
});
return true;
}
/** Open the shared confirmation before removing a group (its tables stay). */
function requestTableVGroupDelete(node: TreeNode) {
if (node.type !== "table-vgroup" || !node.vgroupId) return;
tableVGroupDeleteTarget.value = { scope: node, groupId: node.vgroupId, name: node.label };
showTableVGroupDeleteConfirm.value = true;
}
const sidebarMenuFactories: readonly SidebarMenuFactory[] = [buildConnectionSidebarMenu, buildDatabaseSidebarMenu, buildSpecialSidebarMenu, buildTableVGroupSidebarMenu, buildObjectSidebarMenu, buildObjectGroupSidebarMenu];
function treeItemMenuItems(): ContextMenuItem[] {
const node = activeNode.value;
@@ -74,6 +74,9 @@ import { formatSidebarObjectStorage } from "@/lib/sidebar/sidebarDatabaseStorage
import { effectiveRedisDatabaseIndex } from "@/lib/redis/redisDatabaseIndex";
import { dataTabOpenModeFromTreeClick } from "@/lib/sidebar/dataTabOpenPolicy";
import { effectiveDatabaseTypeForConnection } from "@/lib/database/jdbcDialect";
import { selectedTableVGroupMoveTargets, tableVGroupIdFromNodeId } from "@/lib/table/tableVGroup";
import { findTreeNodeById } from "@/lib/sql/newQueryContext";
import { resolveTableVGroupDropTarget, setTableVGroupDropTargetNodeId, tableVGroupDropTargetNodeId } from "@/lib/sidebar/sidebarTableVGroupDrag";
import { connectionDisplayUrlScheme } from "@/lib/connection/connectionPresentation";
import { isFocusSearchShortcut } from "@/lib/editor/keyboardShortcuts";
import { encodeSpannerResourcePath } from "@/lib/connection/spannerResourcePath";
@@ -248,6 +251,8 @@ function getIconInfo(node: TreeNode): { icon: any; colorClass: string } | null {
return null;
case "connection-group":
return { icon: node.isExpanded ? FolderOpen : FolderClosed, colorClass: "text-amber-500" };
case "table-vgroup":
return { icon: node.isExpanded ? FolderOpen : FolderClosed, colorClass: "text-emerald-500" };
case "database":
return { icon: Database, colorClass: "text-yellow-500" };
case "tablespace":
@@ -1075,7 +1080,11 @@ const renameInput = ref("");
const renameInputRef = ref<HTMLInputElement>();
/** Snapshot of the row being renamed: reprojecting the tree mid-rename recycles activeNode. */
const renameTargetNode = shallowRef<TreeNode | null>(null);
function startRenameGroup() {
renameTargetNode.value = activeNode.value;
renameInput.value = activeNode.value.label;
isRenamingGroup.value = true;
emit("rename-started");
@@ -1105,6 +1114,7 @@ watch(
if (activeNode.value.type === "connection-group") startRenameGroup();
else if (activeNode.value.type === "saved-sql-file") startRenameSavedSql();
else if (activeNode.value.type === "connection") startRenameConnection();
else if (activeNode.value.type === "table-vgroup") startRenameGroup();
},
{ immediate: true },
);
@@ -1126,11 +1136,17 @@ function finishRenameGroup() {
// groups (issue #681).
if (!isRenamingGroup.value) return;
isRenamingGroup.value = false;
const target = renameTargetNode.value ?? activeNode.value;
renameTargetNode.value = null;
const trimmed = renameInput.value.trim();
// An empty name cancels the rename and keeps the group as-is — never delete
// here. Deleting a group is done explicitly via the context menu (issue #681).
if (!trimmed || trimmed === activeNode.value.label) return;
connectionStore.renameConnectionGroup(activeNode.value.id, trimmed);
if (!trimmed || trimmed === target.label) return;
if (target.type === "table-vgroup" && target.vgroupId) {
connectionStore.renameTableVGroup(target, target.vgroupId, trimmed);
return;
}
connectionStore.renameConnectionGroup(target.id, trimmed);
}
async function finishRenameSavedSql() {
@@ -1193,6 +1209,19 @@ const {
return;
}
if (dragState.draggedType === "table-vgroup") {
const draggedGroupId = tableVGroupIdFromNodeId(draggedId);
const targetGroupId = tableVGroupIdFromNodeId(targetId);
// 落点回调是模块级单例,activeNode 未必是同容器的落点行,须用 targetId 现查。
const targetNode = findTreeNodeById(connectionStore.treeNodes, targetId);
if (draggedGroupId && targetGroupId && targetNode) connectionStore.reorderTableVGroupEntry(targetNode, draggedGroupId, targetGroupId, position);
return;
}
// 分组行只在拖动分组自身时才是合法落点:否则 targetId 不在侧边栏布局里,
// reorderSidebarEntries 找不到目标会把连接追加到根列表末尾。
if (tableVGroupIdFromNodeId(targetId)) return;
// If the grabbed row is part of a multi-selection, move all selected rows
// together; otherwise just the grabbed one (issue #681).
const selected = connectionStore.selectedTreeNodeIds;
@@ -1202,7 +1231,7 @@ const {
const canReorderTreeNode = computed(() => {
if (props.reorderDisabled) return false;
return activeNode.value.type === "connection" || activeNode.value.type === "connection-group";
return activeNode.value.type === "connection" || activeNode.value.type === "connection-group" || activeNode.value.type === "table-vgroup";
});
function isPinnedOrderDrag(): boolean {
@@ -1211,7 +1240,7 @@ function isPinnedOrderDrag(): boolean {
const dragVisual = computed(() => {
const targetId = isPinnedOrderDrag() ? pinnedSortKey() : activeNode.value.id;
const isDropTarget = isPinnedOrderDrag() ? connectionStore.isPinnedTreeNodeReorderTarget(pinnedSortKey()) : activeNode.value.type === "connection" || activeNode.value.type === "connection-group";
const isDropTarget = isPinnedOrderDrag() ? connectionStore.isPinnedTreeNodeReorderTarget(pinnedSortKey()) : activeNode.value.type === "connection" || activeNode.value.type === "connection-group" || activeNode.value.type === "table-vgroup";
return {
isDropTarget,
@@ -1346,6 +1375,10 @@ function tableReferenceDragPayload(): QueryEditorTableReferencePayload | null {
function startTableReferenceDrag(payload: QueryEditorTableReferencePayload) {
draggingTableReferencePayload = payload;
// 分组成员名单只收真实表:视图/物化视图投影不识别,入组会产生隐形脏数据。
vgroupDragTableNames = selectedTableVGroupMoveTargets(activeNode.value, selectedTreeNodesInVisibleOrder())
.filter((node) => node.type === "table")
.map((node) => node.label);
setActiveTableReferencePayload(payload);
document.getSelection()?.removeAllRanges();
referenceDragFeedback = beginTableReferenceDragFeedback(tableReferenceDragLabel(payload));
@@ -1355,6 +1388,8 @@ function finishTableReferenceDrag() {
clearActiveTableReferencePayload(draggingTableReferencePayload);
pendingTableReferenceDrag = null;
draggingTableReferencePayload = null;
vgroupDragTableNames = [];
setTableVGroupDropTargetNodeId(null);
referenceDragFeedback?.end();
referenceDragFeedback = null;
window.dispatchEvent(createTableReferenceDragEndEvent());
@@ -1362,6 +1397,14 @@ function finishTableReferenceDrag() {
document.removeEventListener("mouseup", onTableReferenceMouseUp, true);
}
/** 本次拖拽要移动进分组的表名(拖拽开始时按选中区解析,见 startTableReferenceDrag)。 */
let vgroupDragTableNames: string[] = [];
function tableVGroupDropTargetFor(payload: QueryEditorTableReferencePayload, event: MouseEvent) {
if (!vgroupDragTableNames.length) return null;
return resolveTableVGroupDropTarget(event.clientX, event.clientY, connectionStore.treeNodes, payload);
}
function onTableReferenceMouseMove(event: MouseEvent) {
if (!pendingTableReferenceDrag && !draggingTableReferencePayload) return;
if (pendingTableReferenceDrag && !draggingTableReferencePayload) {
@@ -1374,6 +1417,7 @@ function onTableReferenceMouseMove(event: MouseEvent) {
event.preventDefault();
document.getSelection()?.removeAllRanges();
referenceDragFeedback?.update(event.clientX, event.clientY);
setTableVGroupDropTargetNodeId(tableVGroupDropTargetFor(draggingTableReferencePayload, event)?.node.id ?? null);
// 仅查询编辑器消费 hover 光标线事件;AI 面板不监听。命中判定含覆盖层拦截时的几何回退。
if (isOverSqlEditorTarget(event.clientX, event.clientY)) {
window.dispatchEvent(createTableReferenceHoverEvent({ clientX: event.clientX, clientY: event.clientY }));
@@ -1385,15 +1429,20 @@ function onTableReferenceMouseUp(event: MouseEvent) {
const payload = draggingTableReferencePayload;
if (payload) {
suppressNextTableReferenceClick = true;
const target = document.elementFromPoint(event.clientX, event.clientY);
if (target instanceof Element && target.closest(`[data-query-editor-root], ${AI_ASSISTANT_TABLE_DROP_ROOT_SELECTOR}`)) {
window.dispatchEvent(
createTableReferenceDropEvent({
payload,
clientX: event.clientX,
clientY: event.clientY,
}),
);
const dropTarget = tableVGroupDropTargetFor(payload, event);
if (dropTarget) {
for (const tableName of vgroupDragTableNames) connectionStore.moveTableToVGroup(dropTarget.node, tableName, dropTarget.groupId);
} else {
const target = document.elementFromPoint(event.clientX, event.clientY);
if (target instanceof Element && target.closest(`[data-query-editor-root], ${AI_ASSISTANT_TABLE_DROP_ROOT_SELECTOR}`)) {
window.dispatchEvent(
createTableReferenceDropEvent({
payload,
clientX: event.clientX,
clientY: event.clientY,
}),
);
}
}
}
finishTableReferenceDrag();
@@ -1566,7 +1615,6 @@ function onKeydown(event: KeyboardEvent) {
rowWidthClass,
{
'group/sidebar-row': true,
'ring-1 ring-primary/50 bg-primary/5': dragVisual.showInside,
'opacity-50': dragVisual.dragging,
'tree-item-connection-tint': connectionColor,
'hover:bg-accent': node.type !== 'connection',
@@ -1574,6 +1622,7 @@ function onKeydown(event: KeyboardEvent) {
'tree-item-active': selectionVisual.rowSelected,
'tree-item-active--selection-set': selectionVisual.usesSelectionSetHighlight && selectionVisual.rowSelected,
'tree-item-highlight': highlighted,
'ring-1 ring-primary/50 bg-primary/5': dragVisual.showInside || tableVGroupDropTargetNodeId === node.id,
},
]"
:tabindex="selectionVisual.selected || selectionVisual.multiSelected ? 0 : -1"
@@ -5,6 +5,7 @@ import { fallbackCreateDatabaseCharsetMetadata } from "@/lib/database/createData
import type { DatabaseUserIdentity } from "@/lib/database/databaseUserAdmin";
import type { AuthorizationPlan, AuthorizationStepResult } from "@/lib/database/databaseAuthorizationPlan";
import type { MongoCreateIndexForm, MongoIndexRow } from "@/lib/sidebar/mongoCollectionMutation";
import type { TableVGroupScope } from "@/lib/table/tableVGroup";
export type DuplicateStructureSource = TreeNode & { connectionId: string; database: string };
type ConnectionDeleteTarget = TreeNode & { connectionId: string };
@@ -20,6 +21,15 @@ export const sidebarFormTarget = shallowRef<TreeNode | null>(null);
export const connectionDeleteTargetSnapshot = ref<ConnectionDeleteTarget[]>([]);
export const connectionGroupDeleteTargetSnapshot = ref<ConnectionGroupDeleteTarget[]>([]);
export const deleteConnectionsWithGroup = ref(false);
export const showTableVGroupDialog = ref(false);
export const tableVGroupName = ref("");
/** Scope snapshot + creation payload for the table vgroup naming dialog. */
export const tableVGroupDialogScope = shallowRef<TableVGroupScope | null>(null);
export const tableVGroupDialogParentGroupId = ref<string | null>(null);
export const tableVGroupDialogTableNames = ref<string[]>([]);
export const showTableVGroupDeleteConfirm = ref(false);
/** Scope + group snapshot for the table vgroup delete confirmation. */
export const tableVGroupDeleteTarget = shallowRef<{ scope: TableVGroupScope; groupId: string; name: string } | null>(null);
export const showDeleteConfirm = ref(false);
export const showDropTableConfirm = ref(false);
export const showDropTableChildObjectConfirm = ref(false);
@@ -235,6 +245,8 @@ const openFlags = [
showCompileErrorDialog,
showDeleteGroupConfirm,
showMoveToNewGroupDialog,
showTableVGroupDialog,
showTableVGroupDeleteConfirm,
];
export function resetSidebarTreeDialogState() {
@@ -266,4 +278,7 @@ export function resetSidebarTreeDialogState() {
connectionDeleteTargetSnapshot.value = [];
connectionGroupDeleteTargetSnapshot.value = [];
deleteConnectionsWithGroup.value = false;
tableVGroupDialogScope.value = null;
tableVGroupDialogParentGroupId.value = null;
tableVGroupDialogTableNames.value = [];
}