From f9f61cf3978f23a54950c7bc142c95a4506489c1 Mon Sep 17 00:00:00 2001 From: jearhe <0verme7753@gmail.com> Date: Wed, 2 Sep 2026 14:11:39 +0800 Subject: [PATCH] fix(sidebar): keep object browser scroll after sql generation --- .../src/components/objects/ObjectBrowser.vue | 11 ++++++++ .../ObjectBrowserViewportPersistence.spec.ts | 28 +++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 apps/desktop/src/components/objects/__tests__/ObjectBrowserViewportPersistence.spec.ts diff --git a/apps/desktop/src/components/objects/ObjectBrowser.vue b/apps/desktop/src/components/objects/ObjectBrowser.vue index f38de80f8d..b07e02486f 100644 --- a/apps/desktop/src/components/objects/ObjectBrowser.vue +++ b/apps/desktop/src/components/objects/ObjectBrowser.vue @@ -445,6 +445,16 @@ function onObjectsScroll() { }); } +function flushObjectBrowserViewport() { + if (viewportFrame) { + window.cancelAnimationFrame(viewportFrame); + viewportFrame = 0; + } + const el = scrollerElement(); + if (!el) return; + emitViewportChange(el.scrollTop); +} + function applyObjectBrowserScrollTop(scrollTop: number) { const scroller = activeScroller(); if (scroller && !(scroller instanceof HTMLElement)) { @@ -1436,6 +1446,7 @@ async function onEventSaved(savedName: string) { } async function openNewQuery(row: ObjectBrowserRow) { + flushObjectBrowserViewport(); const schema = row.schema || selectedSchema.value; const tabId = queryStore.createTab(props.connection.id, props.database, row.name, "query", schema, undefined, props.catalog); queryStore.updateSql( diff --git a/apps/desktop/src/components/objects/__tests__/ObjectBrowserViewportPersistence.spec.ts b/apps/desktop/src/components/objects/__tests__/ObjectBrowserViewportPersistence.spec.ts new file mode 100644 index 0000000000..a41514bb42 --- /dev/null +++ b/apps/desktop/src/components/objects/__tests__/ObjectBrowserViewportPersistence.spec.ts @@ -0,0 +1,28 @@ +import { readFileSync } from "node:fs"; +import { describe, expect, it } from "vitest"; + +const source = readFileSync(new URL("../ObjectBrowser.vue", import.meta.url), "utf8"); + +function functionBody(name: string): string { + const signature = new RegExp(`(?:async\\s+)?function\\s+${name}\\s*\\([^)]*\\)\\s*(?::\\s*[^\\{]+)?\\{`, "m").exec(source); + if (!signature) throw new Error(`Missing function ${name}`); + const bodyStart = signature.index + signature[0].length; + let depth = 1; + for (let index = bodyStart; index < source.length; index += 1) { + if (source[index] === "{") depth += 1; + else if (source[index] === "}") depth -= 1; + if (depth === 0) return source.slice(bodyStart, index); + } + throw new Error(`Unclosed function ${name}`); +} + +describe("ObjectBrowser viewport persistence", () => { + it("flushes the latest scroll position before generating SQL", () => { + const body = functionBody("openNewQuery"); + const flushIndex = body.indexOf("flushObjectBrowserViewport();"); + const createTabIndex = body.indexOf("queryStore.createTab"); + + expect(flushIndex).toBeGreaterThanOrEqual(0); + expect(createTabIndex).toBeGreaterThan(flushIndex); + }); +});