mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-19 16:16:45 +00:00
feat(sidebar): collapse child nodes recursively when folding a parent
This commit is contained in:
@@ -2043,8 +2043,18 @@ function findSchemaNode(nodes: TreeNode[], connId: string, database: string, sch
|
||||
function onSearchToggle(node: TreeNode) {
|
||||
if (!isTreeSearchFiltering.value || !node.children) return;
|
||||
const next = new Set(searchCollapsedIds.value);
|
||||
if (node.isExpanded) next.add(node.id);
|
||||
else next.delete(node.id);
|
||||
if (node.isExpanded) {
|
||||
next.add(node.id);
|
||||
const collectDescendants = (children: readonly TreeNode[]) => {
|
||||
for (const child of children) {
|
||||
next.add(child.id);
|
||||
if (child.children?.length) collectDescendants(child.children);
|
||||
}
|
||||
};
|
||||
collectDescendants(node.children);
|
||||
} else {
|
||||
next.delete(node.id);
|
||||
}
|
||||
searchCollapsedIds.value = next;
|
||||
}
|
||||
|
||||
|
||||
@@ -246,6 +246,7 @@ import { buildXuguCompileSql } from "@/lib/database/xuguCompileSql";
|
||||
import { buildDamengCompileViewSql } from "@/lib/database/damengCompileSql";
|
||||
import type { SidebarDataOpenRequest } from "@/lib/sidebar/sidebarDataOpenCoordinator";
|
||||
import { createSidebarActionTarget, findSidebarActionTarget, releaseRemovedSidebarActionTarget, type SidebarActionTarget } from "@/lib/sidebar/sidebarActionTarget";
|
||||
import { collapseSubtreeDescendants } from "@/lib/sidebar/sidebarTreeCollapse";
|
||||
import { createSidebarMenuContext, normalizeSidebarMenuDescriptors } from "@/lib/sidebar/sidebarTreeMenuDescriptors";
|
||||
import { driverProfileDatabaseWorkspace } from "@/lib/database/driverProfileExtensions";
|
||||
import type { SidebarDangerDialogRequest } from "@/lib/sidebar/sidebarDangerDialog";
|
||||
@@ -874,6 +875,7 @@ async function toggle(requestId = beginNavigationRequest()) {
|
||||
if (node.isLoading) {
|
||||
if (node.isExpanded) {
|
||||
node.isExpanded = false;
|
||||
collapseSubtreeDescendants(node);
|
||||
if (shouldReleaseCollapsedTreeNodeChildren()) connectionStore.releaseCollapsedTreeNodeChildren(node.id);
|
||||
connectionStore.cancelTreeNodeLoad(node.id);
|
||||
emitNodeToggled(node, true, false);
|
||||
@@ -888,6 +890,7 @@ async function toggle(requestId = beginNavigationRequest()) {
|
||||
|
||||
if (node.type === "connection-group") {
|
||||
node.isExpanded = !node.isExpanded;
|
||||
if (!node.isExpanded) collapseSubtreeDescendants(node);
|
||||
connectionStore.toggleConnectionGroupCollapsed(node.id);
|
||||
emitNodeToggled(node, wasExpanded);
|
||||
return;
|
||||
@@ -895,12 +898,14 @@ async function toggle(requestId = beginNavigationRequest()) {
|
||||
|
||||
if (node.type === "group-partitions") {
|
||||
node.isExpanded = !node.isExpanded;
|
||||
if (!node.isExpanded) collapseSubtreeDescendants(node);
|
||||
emitNodeToggled(node, wasExpanded);
|
||||
return;
|
||||
}
|
||||
|
||||
if (node.type === "table-vgroup") {
|
||||
node.isExpanded = !node.isExpanded;
|
||||
if (!node.isExpanded) collapseSubtreeDescendants(node);
|
||||
if (node.vgroupId) connectionStore.toggleTableVGroupCollapsed(node, node.vgroupId);
|
||||
emitNodeToggled(node, wasExpanded);
|
||||
return;
|
||||
@@ -908,6 +913,7 @@ async function toggle(requestId = beginNavigationRequest()) {
|
||||
|
||||
if (node.type === "type" && customTypeCapabilities(currentDatabaseType()).details && node.children !== undefined) {
|
||||
node.isExpanded = node.children.length > 0 ? !node.isExpanded : false;
|
||||
if (!node.isExpanded) collapseSubtreeDescendants(node);
|
||||
emitNodeToggled(node, wasExpanded);
|
||||
return;
|
||||
}
|
||||
@@ -919,6 +925,7 @@ async function toggle(requestId = beginNavigationRequest()) {
|
||||
// schema and replace the package's member list with unrelated objects.
|
||||
if (currentDatabaseType() === "xugu" && (node.type === "group-procedures" || node.type === "group-functions") && node.parentType === "package") {
|
||||
node.isExpanded = !node.isExpanded;
|
||||
if (!node.isExpanded) collapseSubtreeDescendants(node);
|
||||
// Package member groups have no group-level reload path: their children
|
||||
// are hydrated together with the owning package. Releasing a large group
|
||||
// here would leave it empty on the next local-only expand.
|
||||
@@ -933,6 +940,7 @@ async function toggle(requestId = beginNavigationRequest()) {
|
||||
const needsConfiguredTablePageDrain = node.type === "group-tables" && connectionStore.getConfig(node.connectionId || "")?.sidebar_auto_load_all_tables === true && hasTableTreeLoadMore(node.children ?? []);
|
||||
if (databaseObjectGroup && connectionStore.canUseLoadedTreeNodeToggle(node) && !needsConfiguredTablePageDrain) {
|
||||
node.isExpanded = !node.isExpanded;
|
||||
if (!node.isExpanded) collapseSubtreeDescendants(node);
|
||||
if (wasExpanded && shouldReleaseCollapsedTreeNodeChildren()) connectionStore.releaseCollapsedTreeNodeChildren(node.id);
|
||||
emitNodeToggled(node, wasExpanded);
|
||||
return;
|
||||
@@ -940,18 +948,21 @@ async function toggle(requestId = beginNavigationRequest()) {
|
||||
|
||||
if (node.type === "type-attributes" || node.type === "type-methods") {
|
||||
node.isExpanded = !node.isExpanded;
|
||||
if (!node.isExpanded) collapseSubtreeDescendants(node);
|
||||
emitNodeToggled(node, wasExpanded);
|
||||
return;
|
||||
}
|
||||
|
||||
if (node.type === "saved-sql-root" || node.type === "saved-sql-folder") {
|
||||
node.isExpanded = !node.isExpanded;
|
||||
if (!node.isExpanded) collapseSubtreeDescendants(node);
|
||||
emitNodeToggled(node, wasExpanded);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((node.type === "group-extensions" || node.type === "group-event-triggers") && connectionStore.canUseLoadedTreeNodeToggle(node)) {
|
||||
node.isExpanded = !node.isExpanded;
|
||||
if (!node.isExpanded) collapseSubtreeDescendants(node);
|
||||
if (wasExpanded && shouldReleaseCollapsedTreeNodeChildren()) connectionStore.releaseCollapsedTreeNodeChildren(node.id);
|
||||
emitNodeToggled(node, wasExpanded);
|
||||
return;
|
||||
@@ -961,6 +972,7 @@ async function toggle(requestId = beginNavigationRequest()) {
|
||||
// with the tablespace listing; expanding them is a pure local toggle.
|
||||
if (node.type === "tablespace" || node.type === "group-datafiles") {
|
||||
node.isExpanded = !node.isExpanded;
|
||||
if (!node.isExpanded) collapseSubtreeDescendants(node);
|
||||
emitNodeToggled(node, wasExpanded);
|
||||
return;
|
||||
}
|
||||
@@ -968,6 +980,7 @@ async function toggle(requestId = beginNavigationRequest()) {
|
||||
if (node.type === "group-tablespaces") {
|
||||
if (connectionStore.canUseLoadedTreeNodeToggle(node)) {
|
||||
node.isExpanded = !node.isExpanded;
|
||||
if (!node.isExpanded) collapseSubtreeDescendants(node);
|
||||
if (wasExpanded && shouldReleaseCollapsedTreeNodeChildren()) connectionStore.releaseCollapsedTreeNodeChildren(node.id);
|
||||
emitNodeToggled(node, wasExpanded);
|
||||
return;
|
||||
@@ -977,7 +990,10 @@ async function toggle(requestId = beginNavigationRequest()) {
|
||||
emitNodeToggled(node, wasExpanded);
|
||||
} catch (e: any) {
|
||||
if (!isCurrentNavigationRequest(requestId)) return;
|
||||
if (!wasExpanded) node.isExpanded = false;
|
||||
if (!wasExpanded) {
|
||||
node.isExpanded = false;
|
||||
collapseSubtreeDescendants(node);
|
||||
}
|
||||
const errMsg = e?.message || String(e);
|
||||
if (errMsg.includes(CONNECTION_ATTEMPT_CANCELLED_MESSAGE)) return;
|
||||
toast(t("connection.connectFailed", { message: translateBackendError(t, e) }), 5000);
|
||||
@@ -994,6 +1010,7 @@ async function toggle(requestId = beginNavigationRequest()) {
|
||||
|
||||
if (node.isExpanded) {
|
||||
node.isExpanded = false;
|
||||
collapseSubtreeDescendants(node);
|
||||
if (shouldReleaseCollapsedTreeNodeChildren()) connectionStore.releaseCollapsedTreeNodeChildren(node.id);
|
||||
emitNodeToggled(node, wasExpanded, false);
|
||||
return;
|
||||
@@ -7304,7 +7321,14 @@ function activateRuntimeNode(node: TreeNode) {
|
||||
function emitNodeToggled(node: TreeNode, wasExpanded: boolean, expandedOverride?: boolean) {
|
||||
const liveNode = findSidebarActionTarget(connectionStore.treeNodes, createSidebarActionTarget(node)) ?? node;
|
||||
activeNode.value = liveNode;
|
||||
emit("node-toggled", node, expandedOverride ?? !wasExpanded);
|
||||
const isExpanded = expandedOverride ?? !wasExpanded;
|
||||
if (!isExpanded) {
|
||||
collapseSubtreeDescendants(node);
|
||||
if (liveNode !== node) {
|
||||
collapseSubtreeDescendants(liveNode);
|
||||
}
|
||||
}
|
||||
emit("node-toggled", node, isExpanded);
|
||||
}
|
||||
|
||||
function activateActionTarget(target: SidebarActionTarget) {
|
||||
|
||||
+131
@@ -911,4 +911,135 @@ describe("SidebarTreeRuntimeHost expansion", () => {
|
||||
expect(connectionStore.loadXuguTablespaces).not.toHaveBeenCalled();
|
||||
expect(toggled).toHaveBeenCalledWith(datafileGroup, true);
|
||||
});
|
||||
|
||||
it("collapses all child and descendant nodes when a parent node is collapsed", async () => {
|
||||
const columnsGroup: TreeNode = {
|
||||
id: "mysql:db:table:users:group-columns",
|
||||
label: "Columns",
|
||||
type: "group-columns",
|
||||
connectionId: "mysql",
|
||||
database: "db",
|
||||
tableName: "users",
|
||||
isExpanded: true,
|
||||
children: [{ id: "mysql:db:table:users:col:id", label: "id", type: "column", connectionId: "mysql", database: "db", tableName: "users" }],
|
||||
};
|
||||
const tableNode: TreeNode = {
|
||||
id: "mysql:db:table:users",
|
||||
label: "users",
|
||||
type: "table",
|
||||
connectionId: "mysql",
|
||||
database: "db",
|
||||
isExpanded: true,
|
||||
children: [columnsGroup],
|
||||
};
|
||||
const tablesGroup: TreeNode = {
|
||||
id: "mysql:db:group-tables",
|
||||
label: "Tables",
|
||||
type: "group-tables",
|
||||
connectionId: "mysql",
|
||||
database: "db",
|
||||
isExpanded: true,
|
||||
children: [tableNode],
|
||||
};
|
||||
const databaseNode: TreeNode = {
|
||||
id: "mysql:db",
|
||||
label: "db",
|
||||
type: "database",
|
||||
connectionId: "mysql",
|
||||
database: "db",
|
||||
isExpanded: true,
|
||||
children: [tablesGroup],
|
||||
};
|
||||
|
||||
connectionStore.treeNodes = [databaseNode];
|
||||
connectionStore.loadTables.mockImplementation(async () => {
|
||||
databaseNode.isExpanded = true;
|
||||
});
|
||||
const host = ref<InstanceType<typeof SidebarTreeRuntimeHost> | null>(null);
|
||||
const toggled = vi.fn((node: TreeNode, expanded: boolean) => {
|
||||
syncSidebarTreeNodeExpansion(connectionStore.treeNodes, node, expanded);
|
||||
});
|
||||
const app = createApp(
|
||||
defineComponent({
|
||||
setup: () => () => h(SidebarTreeRuntimeHost, { ref: host, node: databaseNode, depth: 0, onNodeToggled: toggled }),
|
||||
}),
|
||||
);
|
||||
mountedApps.push(app);
|
||||
const container = document.createElement("div");
|
||||
document.body.append(container);
|
||||
app.use(i18n);
|
||||
app.mount(container);
|
||||
|
||||
// Collapsing databaseNode
|
||||
host.value?.toggleNode(databaseNode);
|
||||
await nextTick();
|
||||
|
||||
expect(databaseNode.isExpanded).toBe(false);
|
||||
expect(tablesGroup.isExpanded).toBe(false);
|
||||
expect(tableNode.isExpanded).toBe(false);
|
||||
expect(columnsGroup.isExpanded).toBe(false);
|
||||
expect(toggled).toHaveBeenCalledWith(databaseNode, false);
|
||||
|
||||
// Re-expanding databaseNode
|
||||
host.value?.toggleNode(databaseNode);
|
||||
await nextTick();
|
||||
|
||||
expect(databaseNode.isExpanded).toBe(true);
|
||||
// Descendants must remain collapsed after parent is re-expanded
|
||||
expect(tablesGroup.isExpanded).toBe(false);
|
||||
expect(tableNode.isExpanded).toBe(false);
|
||||
expect(columnsGroup.isExpanded).toBe(false);
|
||||
});
|
||||
|
||||
it("collapses all child connections and their descendants when a connection-group collapses", async () => {
|
||||
const databaseNode: TreeNode = {
|
||||
id: "mysql:db",
|
||||
label: "db",
|
||||
type: "database",
|
||||
connectionId: "mysql",
|
||||
database: "db",
|
||||
isExpanded: true,
|
||||
children: [],
|
||||
};
|
||||
const connectionNode: TreeNode = {
|
||||
id: "mysql",
|
||||
label: "MySQL",
|
||||
type: "connection",
|
||||
connectionId: "mysql",
|
||||
isExpanded: true,
|
||||
children: [databaseNode],
|
||||
};
|
||||
const groupNode: TreeNode = {
|
||||
id: "group-1",
|
||||
label: "Dev Group",
|
||||
type: "connection-group",
|
||||
isExpanded: true,
|
||||
children: [connectionNode],
|
||||
};
|
||||
|
||||
connectionStore.treeNodes = [groupNode];
|
||||
connectionStore.toggleConnectionGroupCollapsed = vi.fn();
|
||||
|
||||
const host = ref<InstanceType<typeof SidebarTreeRuntimeHost> | null>(null);
|
||||
const toggled = vi.fn((node: TreeNode, expanded: boolean) => {
|
||||
syncSidebarTreeNodeExpansion(connectionStore.treeNodes, node, expanded);
|
||||
});
|
||||
const app = createApp(
|
||||
defineComponent({
|
||||
setup: () => () => h(SidebarTreeRuntimeHost, { ref: host, node: groupNode, depth: 0, onNodeToggled: toggled }),
|
||||
}),
|
||||
);
|
||||
mountedApps.push(app);
|
||||
const container = document.createElement("div");
|
||||
document.body.append(container);
|
||||
app.use(i18n);
|
||||
app.mount(container);
|
||||
|
||||
host.value?.toggleNode(groupNode);
|
||||
await nextTick();
|
||||
|
||||
expect(groupNode.isExpanded).toBe(false);
|
||||
expect(connectionNode.isExpanded).toBe(false);
|
||||
expect(databaseNode.isExpanded).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { TreeNode } from "@/types/database";
|
||||
import { collapseExpandedTreeNodes, collapseSubtreeDescendants } from "@/lib/sidebar/sidebarTreeCollapse";
|
||||
import { toggleGroupCollapsed, emptyLayout, createGroup } from "@/lib/sidebar/sidebarLayout";
|
||||
import { toggleTableVGroupCollapsed, emptyTableVGroupLayout, createTableVGroup } from "@/lib/table/tableVGroup";
|
||||
|
||||
describe("sidebarTreeCollapse", () => {
|
||||
it("collapseSubtreeDescendants collapses all child and descendant nodes while keeping parent untouched", () => {
|
||||
const parent: TreeNode = {
|
||||
id: "parent",
|
||||
label: "Parent",
|
||||
type: "database",
|
||||
isExpanded: true,
|
||||
children: [
|
||||
{
|
||||
id: "child-1",
|
||||
label: "Child 1",
|
||||
type: "schema",
|
||||
isExpanded: true,
|
||||
children: [
|
||||
{
|
||||
id: "grandchild-1",
|
||||
label: "Grandchild 1",
|
||||
type: "group-tables",
|
||||
isExpanded: true,
|
||||
children: [
|
||||
{
|
||||
id: "table-1",
|
||||
label: "Table 1",
|
||||
type: "table",
|
||||
isExpanded: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "grandchild-2",
|
||||
label: "Grandchild 2",
|
||||
type: "group-views",
|
||||
isExpanded: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "child-2",
|
||||
label: "Child 2",
|
||||
type: "schema",
|
||||
isExpanded: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const count = collapseSubtreeDescendants(parent);
|
||||
expect(count).toBe(4);
|
||||
// Parent itself is untouched
|
||||
expect(parent.isExpanded).toBe(true);
|
||||
// Children and descendants are collapsed
|
||||
expect(parent.children![0].isExpanded).toBe(false);
|
||||
expect(parent.children![0].children![0].isExpanded).toBe(false);
|
||||
expect(parent.children![0].children![0].children![0].isExpanded).toBe(false);
|
||||
expect(parent.children![0].children![1].isExpanded).toBe(false);
|
||||
expect(parent.children![1].isExpanded).toBe(false);
|
||||
});
|
||||
|
||||
it("collapseSubtreeDescendants returns 0 when node has no children", () => {
|
||||
const leaf: TreeNode = {
|
||||
id: "leaf",
|
||||
label: "Leaf",
|
||||
type: "table",
|
||||
isExpanded: true,
|
||||
};
|
||||
expect(collapseSubtreeDescendants(leaf)).toBe(0);
|
||||
expect(leaf.isExpanded).toBe(true);
|
||||
});
|
||||
|
||||
it("toggleGroupCollapsed collapses all descendant connection groups when parent group collapses", () => {
|
||||
let layout = emptyLayout();
|
||||
const g1 = createGroup(layout, "Parent Group");
|
||||
layout = g1.layout;
|
||||
const g2 = createGroup(layout, "Child Group", g1.groupId);
|
||||
layout = g2.layout;
|
||||
const g3 = createGroup(layout, "Grandchild Group", g2.groupId);
|
||||
layout = g3.layout;
|
||||
|
||||
// Initially all 3 groups are expanded (collapsed = false)
|
||||
expect(layout.groups.find((g) => g.id === g1.groupId)?.collapsed).toBe(false);
|
||||
expect(layout.groups.find((g) => g.id === g2.groupId)?.collapsed).toBe(false);
|
||||
expect(layout.groups.find((g) => g.id === g3.groupId)?.collapsed).toBe(false);
|
||||
|
||||
// Collapsing Parent Group collapses itself and all its descendant groups
|
||||
const collapsed = toggleGroupCollapsed(layout, g1.groupId);
|
||||
expect(collapsed.groups.find((g) => g.id === g1.groupId)?.collapsed).toBe(true);
|
||||
expect(collapsed.groups.find((g) => g.id === g2.groupId)?.collapsed).toBe(true);
|
||||
expect(collapsed.groups.find((g) => g.id === g3.groupId)?.collapsed).toBe(true);
|
||||
|
||||
// Expanding Parent Group reopens Parent Group only; descendant groups stay collapsed
|
||||
const reExpanded = toggleGroupCollapsed(collapsed, g1.groupId);
|
||||
expect(reExpanded.groups.find((g) => g.id === g1.groupId)?.collapsed).toBe(false);
|
||||
expect(reExpanded.groups.find((g) => g.id === g2.groupId)?.collapsed).toBe(true);
|
||||
expect(reExpanded.groups.find((g) => g.id === g3.groupId)?.collapsed).toBe(true);
|
||||
});
|
||||
|
||||
it("toggleTableVGroupCollapsed collapses all descendant virtual groups when parent group collapses", () => {
|
||||
let layout = emptyTableVGroupLayout();
|
||||
const g1 = createTableVGroup(layout, "Parent VGroup");
|
||||
layout = g1.layout;
|
||||
const g2 = createTableVGroup(layout, "Child VGroup", g1.groupId);
|
||||
layout = g2.layout;
|
||||
|
||||
expect(layout.groups.find((g) => g.id === g1.groupId)?.collapsed).toBe(false);
|
||||
expect(layout.groups.find((g) => g.id === g2.groupId)?.collapsed).toBe(false);
|
||||
|
||||
const collapsed = toggleTableVGroupCollapsed(layout, g1.groupId);
|
||||
expect(collapsed.groups.find((g) => g.id === g1.groupId)?.collapsed).toBe(true);
|
||||
expect(collapsed.groups.find((g) => g.id === g2.groupId)?.collapsed).toBe(true);
|
||||
|
||||
const reExpanded = toggleTableVGroupCollapsed(collapsed, g1.groupId);
|
||||
expect(reExpanded.groups.find((g) => g.id === g1.groupId)?.collapsed).toBe(false);
|
||||
expect(reExpanded.groups.find((g) => g.id === g2.groupId)?.collapsed).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -647,9 +647,30 @@ export function deleteGroup(layout: SidebarLayout, groupId: string): SidebarLayo
|
||||
}
|
||||
|
||||
export function toggleGroupCollapsed(layout: SidebarLayout, groupId: string): SidebarLayout {
|
||||
const targetGroup = layout.groups.find((group) => group.id === groupId);
|
||||
const willCollapse = targetGroup ? !targetGroup.collapsed : false;
|
||||
if (!willCollapse) {
|
||||
return {
|
||||
...layout,
|
||||
groups: layout.groups.map((group) => (group.id === groupId ? { ...group, collapsed: false } : group)),
|
||||
};
|
||||
}
|
||||
|
||||
const groupEntry = findGroupEntry(layout.order, groupId);
|
||||
const descendantGroupIds = new Set<string>();
|
||||
const collectDescendants = (entry: Extract<SidebarOrderEntry, { type: "group" }>) => {
|
||||
for (const child of entryChildren(entry)) {
|
||||
if (child.type === "group") {
|
||||
descendantGroupIds.add(child.id);
|
||||
collectDescendants(child);
|
||||
}
|
||||
}
|
||||
};
|
||||
if (groupEntry) collectDescendants(groupEntry);
|
||||
|
||||
return {
|
||||
...layout,
|
||||
groups: layout.groups.map((group) => (group.id === groupId ? { ...group, collapsed: !group.collapsed } : group)),
|
||||
groups: layout.groups.map((group) => (group.id === groupId || descendantGroupIds.has(group.id) ? { ...group, collapsed: true } : group)),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -15,3 +15,7 @@ export function collapseExpandedTreeNodes(nodes: TreeNode[]): number {
|
||||
|
||||
return collapsedCount;
|
||||
}
|
||||
export function collapseSubtreeDescendants(node: TreeNode): number {
|
||||
if (!node.children?.length) return 0;
|
||||
return collapseExpandedTreeNodes(node.children);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
import type { TreeNode } from "@/types/database";
|
||||
import { findSidebarActionTarget } from "@/lib/sidebar/sidebarActionTarget";
|
||||
import { collapseSubtreeDescendants } from "@/lib/sidebar/sidebarTreeCollapse";
|
||||
|
||||
export { collapseSubtreeDescendants } from "@/lib/sidebar/sidebarTreeCollapse";
|
||||
|
||||
export function syncSidebarTreeNodeExpansion(nodes: readonly TreeNode[], renderedNode: TreeNode, expanded: boolean): boolean {
|
||||
const liveNode = findSidebarActionTarget(nodes, renderedNode);
|
||||
if (!liveNode || liveNode === renderedNode || liveNode.isExpanded === expanded) return false;
|
||||
if (!liveNode) return false;
|
||||
if (!expanded) {
|
||||
collapseSubtreeDescendants(liveNode);
|
||||
}
|
||||
if (liveNode === renderedNode || liveNode.isExpanded === expanded) return false;
|
||||
liveNode.isExpanded = expanded;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -340,9 +340,30 @@ export function deleteTableVGroups(layout: TableVGroupLayout, groupIds: Iterable
|
||||
}
|
||||
|
||||
export function toggleTableVGroupCollapsed(layout: TableVGroupLayout, groupId: string): TableVGroupLayout {
|
||||
const targetGroup = layout.groups.find((group) => group.id === groupId);
|
||||
const willCollapse = targetGroup ? !targetGroup.collapsed : false;
|
||||
if (!willCollapse) {
|
||||
return {
|
||||
...layout,
|
||||
groups: layout.groups.map((group) => (group.id === groupId ? { ...group, collapsed: false } : group)),
|
||||
};
|
||||
}
|
||||
|
||||
const groupEntry = findGroupEntry(layout.order, groupId);
|
||||
const descendantGroupIds = new Set<string>();
|
||||
const collectDescendants = (entry: TableVGroupEntry) => {
|
||||
for (const child of entryChildren(entry)) {
|
||||
if (child.type === "group") {
|
||||
descendantGroupIds.add(child.id);
|
||||
collectDescendants(child);
|
||||
}
|
||||
}
|
||||
};
|
||||
if (groupEntry && groupEntry.type === "group") collectDescendants(groupEntry);
|
||||
|
||||
return {
|
||||
...layout,
|
||||
groups: layout.groups.map((group) => (group.id === groupId ? { ...group, collapsed: !group.collapsed } : group)),
|
||||
groups: layout.groups.map((group) => (group.id === groupId || descendantGroupIds.has(group.id) ? { ...group, collapsed: true } : group)),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -126,7 +126,7 @@ import {
|
||||
import { buildDatabaseTreeNodes, buildDuckDbConnectionTreeNodes, compareSidebarNames, sortSidebarDatabases, sortSidebarNames, shouldIncludeDefaultDatabaseNode } from "@/lib/database/databaseTree";
|
||||
import { spannerDisplayDatabase, spannerSchemaDisplayName } from "@/lib/connection/spannerResourcePath";
|
||||
import { buildSqlServerDatabaseTreeNodes } from "@/lib/database/sqlServerTree";
|
||||
import { collapseExpandedTreeNodes } from "@/lib/sidebar/sidebarTreeCollapse";
|
||||
import { collapseExpandedTreeNodes, collapseSubtreeDescendants } from "@/lib/sidebar/sidebarTreeCollapse";
|
||||
import { findNodePathByIdentity, nodeMatchesRegexScopeIdentity, type SidebarRegexScopeIdentity } from "@/lib/sidebar/sidebarSearchTree";
|
||||
import { findDatabaseTreeNode } from "@/lib/sidebar/treeRefreshTarget";
|
||||
import { simpleModeEmptyShellNeedsConfirmedLoad, treeNodeLoadedChildrenContentPresent } from "@/lib/sidebar/treeLoadedChildrenMarker";
|
||||
@@ -8225,6 +8225,7 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
if (!connectionId || !database) return;
|
||||
if (node.isExpanded && !options?.force) {
|
||||
node.isExpanded = false;
|
||||
collapseSubtreeDescendants(node);
|
||||
if (!sidebarSearchQuery.value && !options?.preserveCollapsedChildren) releaseCollapsedTreeNodeChildren(node.id);
|
||||
return;
|
||||
}
|
||||
@@ -10599,6 +10600,10 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
return connectionIdsInGroupsOp(sidebarLayout.value, groupIds);
|
||||
},
|
||||
toggleConnectionGroupCollapsed(groupId: string) {
|
||||
const groupNode = findNode(treeNodes.value, groupId);
|
||||
if (groupNode) {
|
||||
collapseSubtreeDescendants(groupNode);
|
||||
}
|
||||
updateLayoutAndRebuild(toggleGroupCollapsedOp(sidebarLayout.value, groupId));
|
||||
},
|
||||
expandConnectionGroups(groupIds: Iterable<string>) {
|
||||
|
||||
Reference in New Issue
Block a user