Fix: keep simple sidebar table pagination bounded (#2092)
This commit is contained in:
parent
b951d3a50a
commit
267768ecbd
|
|
@ -208,10 +208,12 @@ const activeTab = computed(() => queryStore.tabs.find((tab) => tab.id === queryS
|
|||
// ancestor. The overlay reuses <TreeItem>, so collapse/expand comes for free.
|
||||
const stickyScrollTop = ref(0);
|
||||
const sidebarScrollMetrics = ref({ scrollTop: 0, clientHeight: 0, scrollHeight: 0 });
|
||||
const isScrollingSidebar = ref(false);
|
||||
const isDraggingSidebarScrollbar = ref(false);
|
||||
let sidebarScrollbarResizeObserver: ResizeObserver | null = null;
|
||||
let sidebarScrollbarAnimationFrame = 0;
|
||||
let sidebarScrollbarDragOffset = 0;
|
||||
let sidebarScrollingTimer = 0;
|
||||
|
||||
function updateSidebarScrollMetrics() {
|
||||
const scroller = currentTreeScroller();
|
||||
|
|
@ -234,6 +236,11 @@ function scheduleSidebarScrollMetricsUpdate() {
|
|||
}
|
||||
|
||||
function onTreeScroll() {
|
||||
isScrollingSidebar.value = true;
|
||||
window.clearTimeout(sidebarScrollingTimer);
|
||||
sidebarScrollingTimer = window.setTimeout(() => {
|
||||
isScrollingSidebar.value = false;
|
||||
}, 700);
|
||||
scheduleSidebarScrollMetricsUpdate();
|
||||
}
|
||||
|
||||
|
|
@ -780,6 +787,7 @@ onUnmounted(() => {
|
|||
stopSidebarScrollbarDrag();
|
||||
sidebarScrollbarResizeObserver?.disconnect();
|
||||
window.cancelAnimationFrame(sidebarScrollbarAnimationFrame);
|
||||
window.clearTimeout(sidebarScrollingTimer);
|
||||
});
|
||||
|
||||
defineExpose({ focusSearch, createNewGroup });
|
||||
|
|
@ -853,7 +861,7 @@ defineExpose({ focusSearch, createNewGroup });
|
|||
<div v-if="stickyNode" class="sticky-database-header pointer-events-auto absolute inset-x-0 top-0 z-[5] border-b border-border/60" :style="stickyHeaderStyle">
|
||||
<TreeItem :node="stickyNode.node" :depth="stickyNode.depth" :drag-disabled="true" @search-toggle="onSearchToggle" />
|
||||
</div>
|
||||
<div v-if="hasSidebarVerticalOverflow" ref="sidebarScrollbarTrackRef" class="sidebar-tree-scrollbar" :class="{ 'sidebar-tree-scrollbar--dragging': isDraggingSidebarScrollbar }" @pointerdown="onSidebarScrollbarTrackPointerDown">
|
||||
<div v-if="hasSidebarVerticalOverflow" ref="sidebarScrollbarTrackRef" class="sidebar-tree-scrollbar" :class="{ 'sidebar-tree-scrollbar--scrolling': isScrollingSidebar, 'sidebar-tree-scrollbar--dragging': isDraggingSidebarScrollbar }" @pointerdown="onSidebarScrollbarTrackPointerDown">
|
||||
<div class="sidebar-tree-scrollbar__thumb" :style="sidebarScrollbarThumbStyle" @pointerdown.stop="onSidebarScrollbarThumbPointerDown" />
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -871,7 +879,7 @@ defineExpose({ focusSearch, createNewGroup });
|
|||
@rename-started="pendingRenameGroupId = null"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="hasSidebarVerticalOverflow" ref="sidebarScrollbarTrackRef" class="sidebar-tree-scrollbar" :class="{ 'sidebar-tree-scrollbar--dragging': isDraggingSidebarScrollbar }" @pointerdown="onSidebarScrollbarTrackPointerDown">
|
||||
<div v-if="hasSidebarVerticalOverflow" ref="sidebarScrollbarTrackRef" class="sidebar-tree-scrollbar" :class="{ 'sidebar-tree-scrollbar--scrolling': isScrollingSidebar, 'sidebar-tree-scrollbar--dragging': isDraggingSidebarScrollbar }" @pointerdown="onSidebarScrollbarTrackPointerDown">
|
||||
<div class="sidebar-tree-scrollbar__thumb" :style="sidebarScrollbarThumbStyle" @pointerdown.stop="onSidebarScrollbarThumbPointerDown" />
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -889,10 +897,13 @@ defineExpose({ focusSearch, createNewGroup });
|
|||
.connection-tree-scroller {
|
||||
will-change: scroll-position;
|
||||
contain: content;
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
|
||||
.connection-tree-scroller::-webkit-scrollbar:vertical {
|
||||
.connection-tree-scroller::-webkit-scrollbar {
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.connection-tree-scroller :deep(.vue-recycle-scroller__item-view) {
|
||||
|
|
@ -906,9 +917,9 @@ defineExpose({ focusSearch, createNewGroup });
|
|||
|
||||
.sidebar-tree-scrollbar {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 4px;
|
||||
bottom: 0;
|
||||
z-index: 10;
|
||||
width: 12px;
|
||||
cursor: default;
|
||||
|
|
@ -916,6 +927,7 @@ defineExpose({ focusSearch, createNewGroup });
|
|||
transition: opacity 120ms ease;
|
||||
}
|
||||
|
||||
.sidebar-tree-scrollbar--scrolling,
|
||||
.sidebar-tree-scrollbar:hover,
|
||||
.sidebar-tree-scrollbar--dragging {
|
||||
opacity: 1;
|
||||
|
|
|
|||
|
|
@ -254,6 +254,13 @@ export function mergeTableInfosIntoObjects(objects: readonly ObjectInfo[], table
|
|||
return merged;
|
||||
}
|
||||
|
||||
export function filterSimpleSidebarSupplementalObjects(objects: readonly ObjectInfo[]): ObjectInfo[] {
|
||||
return objects.filter((object) => {
|
||||
const objectType = normalizeObjectType(object.object_type);
|
||||
return objectType !== "TABLE" && objectType !== "VIEW" && objectType !== "MATERIALIZED_VIEW";
|
||||
});
|
||||
}
|
||||
|
||||
function buildPartitionTree(entries: TableTreeEntry[], connectionId: string, database: string): TreeNode[] {
|
||||
const orderedEntries = sortDatabaseObjectsByName(entries, (entry) => entry.node.label);
|
||||
const byKey = new Map<string, TableTreeEntry>();
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ import {
|
|||
buildSimpleObjectTreeNodes,
|
||||
buildTableTreeNodes,
|
||||
expandCachedObjectBrowserNodes,
|
||||
filterSimpleSidebarSupplementalObjects,
|
||||
mergeTableInfosIntoObjects,
|
||||
mergeTableTreePageChildren,
|
||||
objectGroupRefreshParentId,
|
||||
|
|
@ -770,7 +771,7 @@ export const useConnectionStore = defineStore("connection", () => {
|
|||
connectionId: options.connectionId,
|
||||
database: options.database,
|
||||
schema: options.effectiveSchema,
|
||||
objects: mergeTableInfosIntoObjects(objects, pageTables, options.effectiveSchema),
|
||||
objects: mergeTableInfosIntoObjects(filterSimpleSidebarSupplementalObjects(objects), pageTables, options.effectiveSchema),
|
||||
});
|
||||
return {
|
||||
children,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { test } from "vitest";
|
||||
import assert from "node:assert/strict";
|
||||
import { buildGroupedObjectTreeNodes, buildObjectGroupPlaceholderNodes, buildSimpleObjectTreeNodes, buildTableTreeNodes, mergeTableInfosIntoObjects, mergeTableTreePageChildren } from "../../apps/desktop/src/lib/tableTree.ts";
|
||||
import { buildGroupedObjectTreeNodes, buildObjectGroupPlaceholderNodes, buildSimpleObjectTreeNodes, buildTableTreeNodes, filterSimpleSidebarSupplementalObjects, mergeTableInfosIntoObjects, mergeTableTreePageChildren } from "../../apps/desktop/src/lib/tableTree.ts";
|
||||
import type { ObjectInfo, TableInfo, TreeNode } from "../../apps/desktop/src/types/database.ts";
|
||||
|
||||
function table(name: string, parent?: string): TableInfo {
|
||||
|
|
@ -296,6 +296,32 @@ test("mergeTableInfosIntoObjects restores views missing from object metadata", (
|
|||
);
|
||||
});
|
||||
|
||||
test("filterSimpleSidebarSupplementalObjects leaves paged tables and views to listTables", () => {
|
||||
const supplemental = filterSimpleSidebarSupplementalObjects([
|
||||
{ name: "orders", object_type: "TABLE", schema: "public" },
|
||||
{ name: "active_orders", object_type: "VIEW", schema: "public" },
|
||||
{ name: "order_summary", object_type: "MATERIALIZED VIEW", schema: "public" },
|
||||
{ name: "refresh_stats", object_type: "PROCEDURE", schema: "public" },
|
||||
{ name: "total_due", object_type: "FUNCTION", schema: "public" },
|
||||
]);
|
||||
const nodes = buildSimpleObjectTreeNodes({
|
||||
nodeId: "conn:app:public",
|
||||
connectionId: "conn",
|
||||
database: "app",
|
||||
schema: "public",
|
||||
objects: mergeTableInfosIntoObjects(supplemental, [table("orders")], "public"),
|
||||
});
|
||||
|
||||
assert.deepEqual(
|
||||
nodes.map((node) => ({ label: node.label, type: node.type })),
|
||||
[
|
||||
{ label: "orders", type: "table" },
|
||||
{ label: "refresh_stats", type: "procedure" },
|
||||
{ label: "total_due", type: "function" },
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
test("mergeTableInfosIntoObjects dedupes MySQL tables when object metadata carries database as schema", () => {
|
||||
const merged = mergeTableInfosIntoObjects(
|
||||
[
|
||||
|
|
|
|||
Loading…
Reference in New Issue