From d101abe74b0ed062f3fba0689bd6a8370cac8988 Mon Sep 17 00:00:00 2001 From: haipengno1 Date: Tue, 7 Jul 2026 02:05:34 +0800 Subject: [PATCH] fix(sidebar): pin schema row for Dameng/Oracle-style trees --- .../src/components/sidebar/ConnectionTree.vue | 58 +++++++++++++++---- 1 file changed, 46 insertions(+), 12 deletions(-) diff --git a/apps/desktop/src/components/sidebar/ConnectionTree.vue b/apps/desktop/src/components/sidebar/ConnectionTree.vue index 50710d2ba..1666ff643 100644 --- a/apps/desktop/src/components/sidebar/ConnectionTree.vue +++ b/apps/desktop/src/components/sidebar/ConnectionTree.vue @@ -153,11 +153,19 @@ const SEARCH_SCOPE_TO_NODE_TYPES: Record = { view: ["view"], }; -// Database-level container types. When browsing a large number of children -// under one of these (e.g. hundreds of tables) and scrolling down, the row is -// kept pinned at the top of the tree so the active database stays visible and -// can be collapsed with one click. Mirrors the `database` search scope above. +// Sticky-row container types. When browsing a large number of children (e.g. +// hundreds of tables) under one of these and scrolling down, the row is kept +// pinned at the top so the active container stays identifiable and can be +// collapsed with one click. +// +// Database-level containers are always preferred. Schema is only a fallback, +// used when the upward path has NO database-level ancestor at all: Dameng / +// Oracle / oceanbase-oracle expose `connection -> schema -> tables` (no database +// node, via connectionUsesVisibleSchemaFilter). For Postgres/SQLServer, whose +// tree is `connection -> database -> schema -> tables`, the sticky walk prefers +// the database node, so schema never shadows it. const DATABASE_LEVEL_TYPES = new Set(SEARCH_SCOPE_TO_NODE_TYPES.database); +const SCHEMA_LEVEL_TYPES = new Set(["schema"]); const searchScopeOptions = computed(() => { return [ @@ -373,16 +381,29 @@ const stickyNode = computed(() => { if (len === 0) return null; const topIndex = Math.min(Math.floor(stickyScrollTop.value / SIDEBAR_TREE_ROW_HEIGHT), len - 1); - // Walk UP from the topmost visible row to the nearest database-level ancestor. - // Show the overlay as soon as that database row starts crossing the top edge, - // instead of waiting for it to fully scroll out by one row. + // flatNodes is a DFS preorder spanning ALL connections, so walking up from a + // leaf visits `... -> schema -> database -> connection -> `. + // Stop at the connection boundary so the sticky row never leaks across into a + // different connection's subtree (e.g. MySQL's last database sticking while + // scrolling Dameng). Within one connection: track both candidates and prefer + // database-level; only fall back to schema when the whole path has no + // database-level container (Dameng/Oracle-style trees). + let schemaCandidate: FlatTreeNode | null = null; + let schemaCandidateTop = 0; for (let i = topIndex; i >= 0; i--) { const item = nodes[i]; - if (!DATABASE_LEVEL_TYPES.has(item.type)) continue; - const rowTop = i * SIDEBAR_TREE_ROW_HEIGHT; - return stickyScrollTop.value > rowTop ? item : null; + if (item.type === "connection" || item.type === "connection-group") break; + if (DATABASE_LEVEL_TYPES.has(item.type)) { + const rowTop = i * SIDEBAR_TREE_ROW_HEIGHT; + return stickyScrollTop.value > rowTop ? item : null; + } + if (item.type === "schema" && !schemaCandidate) { + schemaCandidate = item; + schemaCandidateTop = i * SIDEBAR_TREE_ROW_HEIGHT; + } } - return null; + if (!schemaCandidate) return null; + return stickyScrollTop.value > schemaCandidateTop ? schemaCandidate : null; }); const stickyHeaderStyle = computed(() => { @@ -391,7 +412,20 @@ const stickyHeaderStyle = computed(() => { const nodes = flatNodes.value; const currentIndex = nodes.findIndex((item) => item.id === node.id); if (currentIndex < 0) return {}; - const nextDatabaseIndex = nodes.findIndex((item, index) => index > currentIndex && DATABASE_LEVEL_TYPES.has(item.type)); + // Look forward for the next sibling container at the SAME level as the sticky + // node so the push-up only fires when a peer scrolls in (database-to-database, + // or schema-to-schema for Dameng/Oracle), never schema-into-database. Stop at + // the connection boundary so we never reach into the next connection's rows. + const nextTypes = SCHEMA_LEVEL_TYPES.has(node.type) ? SCHEMA_LEVEL_TYPES : DATABASE_LEVEL_TYPES; + let nextDatabaseIndex = -1; + for (let i = currentIndex + 1; i < nodes.length; i++) { + const item = nodes[i]; + if (item.type === "connection" || item.type === "connection-group") break; + if (nextTypes.has(item.type)) { + nextDatabaseIndex = i; + break; + } + } if (nextDatabaseIndex < 0) return {}; const distanceToNext = nextDatabaseIndex * SIDEBAR_TREE_ROW_HEIGHT - stickyScrollTop.value; if (distanceToNext >= SIDEBAR_TREE_ROW_HEIGHT) return {};