Skip to content

Commit 1c105ce

Browse files
committed
feat: only show globally searched tables in the sidebar
1 parent 4855e2d commit 1c105ce

3 files changed

Lines changed: 69 additions & 53 deletions

File tree

Website/components/datamodelview/List.tsx

Lines changed: 58 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import { useEffect, useMemo, useRef, useCallback, useState } from "react";
22
import { useDatamodelView, useDatamodelViewDispatch } from "@/contexts/DatamodelViewContext";
33
import React from "react";
4-
import { elementScroll, useVirtualizer, VirtualItem, VirtualizerOptions } from '@tanstack/react-virtual';
4+
import { elementScroll, useVirtualizer, VirtualizerOptions } from '@tanstack/react-virtual';
55
import { Section } from "./Section";
66
import { useDatamodelData } from "@/contexts/DatamodelDataContext";
77
import { AttributeType, EntityType, GroupType } from "@/lib/Types";
88
import { updateURL } from "@/lib/url-utils";
99
import { copyToClipboard, generateGroupLink } from "@/lib/clipboard-utils";
1010
import { useSnackbar } from "@/contexts/SnackbarContext";
11-
import { Tooltip } from '@mui/material';
11+
import { debounce, Tooltip } from '@mui/material';
1212

1313
interface IListProps {
1414
}
@@ -106,6 +106,61 @@ export const List = ({ }: IListProps) => {
106106
requestAnimationFrame(run)
107107
}, [])
108108

109+
const debouncedOnChange = debounce((instance, sync) => {
110+
if (!sync) {
111+
dispatch({ type: 'SET_LOADING_SECTION', payload: null });
112+
}
113+
114+
const virtualItems = instance.getVirtualItems();
115+
if (virtualItems.length === 0) return;
116+
117+
const scrollOffset = instance.scrollOffset;
118+
const scrollRect = instance.scrollRect;
119+
if (!scrollOffset || !scrollRect) return;
120+
121+
const viewportTop = scrollOffset;
122+
const viewportBottom = scrollOffset + scrollRect.height;
123+
124+
let mostVisibleEntity: {
125+
entity: EntityType;
126+
group: GroupType;
127+
visibleArea: number;
128+
} | null = null;
129+
130+
for (const vi of virtualItems) {
131+
const item = flatItems[vi.index];
132+
if (!item || item.type !== 'entity') continue;
133+
134+
const itemTop = vi.start;
135+
const itemBottom = vi.end;
136+
137+
// Calculate intersection
138+
const intersectionTop = Math.max(itemTop, viewportTop);
139+
const intersectionBottom = Math.min(itemBottom, viewportBottom);
140+
141+
// Skip if no intersection
142+
if (intersectionTop >= intersectionBottom) continue;
143+
144+
const visibleArea = intersectionBottom - intersectionTop;
145+
146+
// Update most visible entity without array operations
147+
if (!mostVisibleEntity || visibleArea > mostVisibleEntity.visibleArea) {
148+
mostVisibleEntity = {
149+
entity: item.entity,
150+
group: item.group,
151+
visibleArea
152+
};
153+
}
154+
}
155+
156+
if (mostVisibleEntity && currentSection !== mostVisibleEntity.entity.SchemaName) {
157+
setSectionVirtualItem(mostVisibleEntity.entity.SchemaName);
158+
updateURL({ query: { group: mostVisibleEntity.group.Name, section: mostVisibleEntity.entity.SchemaName } });
159+
dispatch({ type: "SET_CURRENT_GROUP", payload: mostVisibleEntity.group.Name });
160+
dispatch({ type: "SET_CURRENT_SECTION", payload: mostVisibleEntity.entity.SchemaName });
161+
}
162+
}, 100);
163+
109164
const rowVirtualizer = useVirtualizer({
110165
count: flatItems.length,
111166
getScrollElement: () => parentRef.current,
@@ -116,49 +171,7 @@ export const List = ({ }: IListProps) => {
116171
return item.type === 'group' ? 100 : 500;
117172
},
118173
scrollToFn,
119-
onChange: (instance, sync) => {
120-
// Only update during actual scrolling (sync = true)
121-
if (!sync) {
122-
dispatch({ type: 'SET_LOADING_SECTION', payload: null });
123-
return;
124-
}
125-
126-
const virtualItems = instance.getVirtualItems();
127-
if (virtualItems.length === 0) return;
128-
129-
const scrollOffset = instance.scrollOffset;
130-
const scrollRect = instance.scrollRect;
131-
if (!scrollOffset || !scrollRect) return;
132-
133-
// Find the first entity item that's currently visible
134-
const firstVisibleEntity = virtualItems.find(vi => {
135-
const item = flatItems[vi.index];
136-
if (!item || item.type !== 'entity') return false;
137-
138-
// Check if this virtual item is actually visible in the viewport
139-
// vi.start is the top position of the item, vi.end would be vi.start + vi.size
140-
const itemTop = vi.start;
141-
const itemBottom = vi.end;
142-
const offset = 80;
143-
144-
// An item is visible if its bottom is below the scroll position
145-
// and its top is above the scroll position + viewport height
146-
return itemBottom - offset > scrollOffset && itemTop < scrollOffset + scrollRect.height;
147-
});
148-
149-
if (firstVisibleEntity) {
150-
const item = flatItems[firstVisibleEntity.index];
151-
if (item && item.type === 'entity') {
152-
// Only update if the section has actually changed
153-
if (currentSection !== item.entity.SchemaName) {
154-
setSectionVirtualItem(item.entity.SchemaName);
155-
updateURL({ query: { group: item.group.Name, section: item.entity.SchemaName } });
156-
dispatch({ type: "SET_CURRENT_GROUP", payload: item.group.Name });
157-
dispatch({ type: "SET_CURRENT_SECTION", payload: item.entity.SchemaName });
158-
}
159-
}
160-
}
161-
},
174+
onChange: debouncedOnChange,
162175
});
163176

164177
const scrollToSection = useCallback((sectionId: string) => {
@@ -201,7 +214,6 @@ export const List = ({ }: IListProps) => {
201214
}, [flatItems]);
202215

203216
const restoreSection = useCallback(() => {
204-
console.log(sectionVirtualItem)
205217
if (sectionVirtualItem) {
206218
scrollToSection(sectionVirtualItem);
207219
}

Website/components/datamodelview/SidebarDatamodelView.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,24 +27,25 @@ export const SidebarDatamodelView = ({ }: ISidebarDatamodelViewProps) => {
2727

2828
const dataModelDispatch = useDatamodelViewDispatch();
2929

30-
const { groups } = useDatamodelData();
30+
const { groups, filtered, search } = useDatamodelData();
3131

3232
const [searchTerm, setSearchTerm] = useState("");
3333
const [displaySearchTerm, setDisplaySearchTerm] = useState("");
3434
const [expandedGroups, setExpandedGroups] = useState<Set<string>>(new Set());
3535

3636
// Memoize search results to prevent recalculation on every render
3737
const filteredGroups = useMemo(() => {
38-
if (!searchTerm.trim()) return groups;
38+
if (!searchTerm.trim() && !search) return groups;
3939

4040
return groups.map(group => ({
4141
...group,
4242
Entities: group.Entities.filter(entity =>
43-
entity.SchemaName.toLowerCase().includes(searchTerm.toLowerCase()) ||
44-
entity.DisplayName.toLowerCase().includes(searchTerm.toLowerCase())
43+
(entity.SchemaName.toLowerCase().includes(searchTerm.toLowerCase()) ||
44+
entity.DisplayName.toLowerCase().includes(searchTerm.toLowerCase())) &&
45+
filtered.some(f => f.type === 'entity' && f.entity.SchemaName === entity.SchemaName)
4546
)
4647
})).filter(group => group.Entities.length > 0);
47-
}, [groups, searchTerm]);
48+
}, [groups, searchTerm, filtered]);
4849

4950
// Debounced search to reduce performance impact
5051
useEffect(() => {

Website/contexts/DatamodelDataContext.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
'use client'
22

33
import React, { createContext, useContext, useReducer, ReactNode } from "react";
4-
import { GroupType, SolutionWarningType } from "@/lib/Types";
4+
import { EntityType, GroupType, SolutionWarningType } from "@/lib/Types";
55
import { useSearchParams } from "next/navigation";
66

77
interface DatamodelDataState {
88
groups: GroupType[];
99
warnings: SolutionWarningType[];
1010
search: string;
11-
filtered: any[];
11+
filtered: Array<
12+
| { type: 'group'; group: GroupType }
13+
| { type: 'entity'; group: GroupType; entity: EntityType }
14+
>;
1215
}
1316

1417
const initialState: DatamodelDataState = {

0 commit comments

Comments
 (0)