From 4b36bb11de62c048113fece2d54fe6d82cd9e3ec Mon Sep 17 00:00:00 2001 From: Nick Skriabin Date: Wed, 2 Jul 2025 14:14:23 +0100 Subject: [PATCH 1/4] Grid scroll fix attempt 1 --- .../DataManager/Toolbar/GridWidthButton.jsx | 7 ++++++- .../src/components/MainView/GridView/GridView.jsx | 12 +++++++----- web/package.json | 3 ++- web/yarn.lock | 8 ++++---- 4 files changed, 19 insertions(+), 11 deletions(-) diff --git a/web/libs/datamanager/src/components/DataManager/Toolbar/GridWidthButton.jsx b/web/libs/datamanager/src/components/DataManager/Toolbar/GridWidthButton.jsx index b7d6980d3f49..7d5741c3dec9 100644 --- a/web/libs/datamanager/src/components/DataManager/Toolbar/GridWidthButton.jsx +++ b/web/libs/datamanager/src/components/DataManager/Toolbar/GridWidthButton.jsx @@ -4,6 +4,7 @@ import { Button } from "../../Common/Button/Button"; import { Dropdown } from "../../Common/Dropdown/DropdownComponent"; import { Toggle } from "../../Common/Form"; import { IconSettings, IconMinus, IconPlus } from "@humansignal/icons"; +import debounce from "lodash.debounce"; const injector = inject(({ store }) => { const view = store?.currentView; @@ -23,12 +24,16 @@ const injector = inject(({ store }) => { export const GridWidthButton = injector(({ view, isGrid, gridWidth, fitImagesToWidth, hasImage, size }) => { const [width, setWidth] = useState(gridWidth); + const setGridWidthStore = debounce((value) => { + view.setGridWidth(value); + }, 200); + const setGridWidth = useCallback( (width) => { const newWidth = Math.max(1, Math.min(width, 10)); setWidth(newWidth); - view.setGridWidth(newWidth); + setGridWidthStore(newWidth); }, [view], ); diff --git a/web/libs/datamanager/src/components/MainView/GridView/GridView.jsx b/web/libs/datamanager/src/components/MainView/GridView/GridView.jsx index 0c0f14377a68..ec11f383ea6a 100644 --- a/web/libs/datamanager/src/components/MainView/GridView/GridView.jsx +++ b/web/libs/datamanager/src/components/MainView/GridView/GridView.jsx @@ -151,8 +151,8 @@ export const GridView = observer(({ data, view, loadMore, fields, onChange, hidd const renderItem = useCallback( ({ style, rowIndex, columnIndex }) => { const index = getCellIndex(rowIndex, columnIndex); - if (!data || !(index in data)) return null; const row = data[index]; + if (!row) return null; const props = { style: { @@ -187,17 +187,19 @@ export const GridView = observer(({ data, view, loadMore, fields, onChange, hidd }); }; - const itemCount = Math.ceil(data.length / columnCount); + const itemCount = Math.floor(data.length / columnCount); const isItemLoaded = useCallback( (index) => { const rowIndex = index * columnCount; const rowFullfilled = data.slice(rowIndex, columnCount).length === columnCount; + console.log(rowIndex, rowFullfilled); - return !view.dataStore.hasNextPage || rowFullfilled; + return rowFullfilled; }, [columnCount, data, view.dataStore.hasNextPage], ); + console.log({ itemCount }); return ( @@ -205,7 +207,7 @@ export const GridView = observer(({ data, view, loadMore, fields, onChange, hidd {({ width, height }) => ( diff --git a/web/package.json b/web/package.json index 23d3754cf8ab..72e7f48c7596 100644 --- a/web/package.json +++ b/web/package.json @@ -79,6 +79,7 @@ "js-base64": "^3.7.7", "keymaster": "^1.6.2", "konva": "^8.1.3", + "lodash.debounce": "^4.0.8", "lodash.get": "^4.4.0", "lodash.ismatch": "^4.4.0", "lodash.throttle": "^4.1.1", @@ -103,7 +104,7 @@ "react-router-dom": "^5.2.0", "react-singleton-hook": "^3.1.1", "react-virtualized-auto-sizer": "^1.0.20", - "react-window": "^1.8.9", + "react-window": "^1.8.11", "react-window-infinite-loader": "^1.0.5", "sanitize-html": "^2.14.0", "shadcn": "^2.1.8", diff --git a/web/yarn.lock b/web/yarn.lock index 399c6c17a909..e1d10a94e353 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -17895,10 +17895,10 @@ react-window-infinite-loader@^1.0.5: resolved "https://registry.yarnpkg.com/react-window-infinite-loader/-/react-window-infinite-loader-1.0.9.tgz#d861c03d5cbc550e2f185371af820fd22d46c099" integrity sha512-5Hg89IdU4Vrp0RT8kZYKeTIxWZYhNkVXeI1HbKo01Vm/Z7qztDvXljwx16sMzsa9yapRJQW3ODZfMUw38SOWHw== -react-window@^1.8.9: - version "1.8.10" - resolved "https://registry.yarnpkg.com/react-window/-/react-window-1.8.10.tgz#9e6b08548316814b443f7002b1cf8fd3a1bdde03" - integrity sha512-Y0Cx+dnU6NLa5/EvoHukUD0BklJ8qITCtVEPY1C/nL8wwoZ0b5aEw8Ff1dOVHw7fCzMt55XfJDd8S8W8LCaUCg== +react-window@^1.8.11: + version "1.8.11" + resolved "https://registry.yarnpkg.com/react-window/-/react-window-1.8.11.tgz#a857b48fa85bd77042d59cc460964ff2e0648525" + integrity sha512-+SRbUVT2scadgFSWx+R1P754xHPEqvcfSfVX10QYg6POOz+WNgkN48pS+BtZNIMGiL1HYrSEiCkwsMS15QogEQ== dependencies: "@babel/runtime" "^7.0.0" memoize-one ">=3.1.1 <6" From 75e2b3d4b71e799da079bc0fdb41a08e0e9490c2 Mon Sep 17 00:00:00 2001 From: Nick Skriabin Date: Thu, 3 Jul 2025 15:55:05 +0100 Subject: [PATCH 2/4] Fix grid resize and infinite scroll --- .../components/MainView/GridView/GridView.jsx | 92 +++++++++++++++---- 1 file changed, 74 insertions(+), 18 deletions(-) diff --git a/web/libs/datamanager/src/components/MainView/GridView/GridView.jsx b/web/libs/datamanager/src/components/MainView/GridView/GridView.jsx index ec11f383ea6a..82fdf1e09d43 100644 --- a/web/libs/datamanager/src/components/MainView/GridView/GridView.jsx +++ b/web/libs/datamanager/src/components/MainView/GridView/GridView.jsx @@ -129,7 +129,7 @@ export const GridCell = observer(({ view, selected, row, fields, onClick, column export const GridView = observer(({ data, view, loadMore, fields, onChange, hiddenFields }) => { const columnCount = view.gridWidth ?? 4; - const getCellIndex = (row, column) => columnCount * row + column; + const getCellIndex = useCallback((row, column) => columnCount * row + column, [columnCount]); const fieldsData = useMemo(() => { return prepareColumns(fields, hiddenFields); @@ -148,6 +148,10 @@ export const GridView = observer(({ data, view, loadMore, fields, onChange, hidd const finalRowHeight = CELL_HEADER_HEIGHT + rowHeight * (hasImage ? Math.max(1, (IMAGE_SIZE_COEFFICIENT - columnCount) * 0.5) : 1); + // Calculate the total number of rows needed to display all items + const itemCount = view.dataStore.total || data.length; + const totalRows = Math.ceil(itemCount / columnCount); + const renderItem = useCallback( ({ style, rowIndex, columnIndex }) => { const index = getCellIndex(rowIndex, columnIndex); @@ -173,33 +177,85 @@ export const GridView = observer(({ data, view, loadMore, fields, onChange, hidd /> ); }, - [data, columnCount, fieldsData, view.selected, view, view.selected.list, view.selected.all, getCellIndex], + [data, columnCount, fieldsData, view.selected, view, view.selected.list, view.selected.all, getCellIndex, onChange], ); - const onItemsRenderedWrap = + const onItemsRenderedWrap = useCallback( (cb) => ({ visibleRowStartIndex, visibleRowStopIndex, overscanRowStopIndex, overscanRowStartIndex }) => { + // Check if we're near the end and need to load more + const visibleItemStartIndex = getCellIndex(visibleRowStartIndex, 0); + const visibleItemStopIndex = getCellIndex(visibleRowStopIndex, columnCount - 1); + + // If we're showing items near the end of our loaded data, trigger loading + if (visibleItemStopIndex >= data.length - (columnCount * 2) && view.dataStore.hasNextPage && !view.dataStore.loading) { + console.log('GridView: Near end of loaded data, triggering load', { + visibleItemStopIndex, + dataLength: data.length, + columnCount, + hasNextPage: view.dataStore.hasNextPage, + loading: view.dataStore.loading + }); + loadMore?.(); + } + cb({ overscanStartIndex: overscanRowStartIndex, overscanStopIndex: overscanRowStopIndex, visibleStartIndex: visibleRowStartIndex, visibleStopIndex: visibleRowStopIndex, }); - }; - - const itemCount = Math.floor(data.length / columnCount); + }, + [data.length, columnCount, view.dataStore.hasNextPage, view.dataStore.loading, loadMore, getCellIndex], + ); + // Check if a specific item index is loaded const isItemLoaded = useCallback( (index) => { - const rowIndex = index * columnCount; - const rowFullfilled = data.slice(rowIndex, columnCount).length === columnCount; - console.log(rowIndex, rowFullfilled); + const rowExists = index < data.length && !!data[index]; + const hasNextPage = view.dataStore.hasNextPage; + const result = !hasNextPage || rowExists; + + // Only log when we're near the end of loaded data + if (index >= data.length - 5) { + console.log("GridView: isItemLoaded check", { + index, + dataLength: data.length, + rowExists, + hasNextPage, + result, + total: view.dataStore.total, + }); + } + + return result; + }, + [data.length, view.dataStore.hasNextPage, view.dataStore.total], + ); - return rowFullfilled; + // Wrap loadMore to ensure it handles the grid's item indexing correctly + const loadMoreWrapper = useCallback( + async (startIndex, stopIndex) => { + const threshold = Math.max(1, Math.floor(view.dataStore.pageSize / 4)); + const batchSize = Math.max(1, Math.floor(view.dataStore.pageSize / 2)); + + console.log("GridView: Loading more items", { + startIndex, + stopIndex, + currentDataLength: data.length, + total: view.dataStore.total, + threshold, + batchSize, + hasNextPage: view.dataStore.hasNextPage, + pageSize: view.dataStore.pageSize, + }); + + if (loadMore) { + await loadMore(); + } }, - [columnCount, data, view.dataStore.hasNextPage], + [loadMore, data.length, view.dataStore.pageSize, view.dataStore.total, view.dataStore.hasNextPage], ); - console.log({ itemCount }); return ( @@ -207,11 +263,11 @@ export const GridView = observer(({ data, view, loadMore, fields, onChange, hidd {({ width, height }) => ( {({ onItemsRendered, ref }) => ( Date: Thu, 3 Jul 2025 16:00:58 +0100 Subject: [PATCH 3/4] Cleanup --- .../components/MainView/GridView/GridView.jsx | 86 +++++-------------- 1 file changed, 22 insertions(+), 64 deletions(-) diff --git a/web/libs/datamanager/src/components/MainView/GridView/GridView.jsx b/web/libs/datamanager/src/components/MainView/GridView/GridView.jsx index 82fdf1e09d43..f441d0c5a584 100644 --- a/web/libs/datamanager/src/components/MainView/GridView/GridView.jsx +++ b/web/libs/datamanager/src/components/MainView/GridView/GridView.jsx @@ -182,30 +182,26 @@ export const GridView = observer(({ data, view, loadMore, fields, onChange, hidd const onItemsRenderedWrap = useCallback( (cb) => - ({ visibleRowStartIndex, visibleRowStopIndex, overscanRowStopIndex, overscanRowStartIndex }) => { - // Check if we're near the end and need to load more - const visibleItemStartIndex = getCellIndex(visibleRowStartIndex, 0); - const visibleItemStopIndex = getCellIndex(visibleRowStopIndex, columnCount - 1); - - // If we're showing items near the end of our loaded data, trigger loading - if (visibleItemStopIndex >= data.length - (columnCount * 2) && view.dataStore.hasNextPage && !view.dataStore.loading) { - console.log('GridView: Near end of loaded data, triggering load', { - visibleItemStopIndex, - dataLength: data.length, - columnCount, - hasNextPage: view.dataStore.hasNextPage, - loading: view.dataStore.loading + ({ visibleRowStartIndex, visibleRowStopIndex, overscanRowStopIndex, overscanRowStartIndex }) => { + // Check if we're near the end and need to load more + const visibleItemStopIndex = getCellIndex(visibleRowStopIndex, columnCount - 1); + + // If we're showing items near the end of our loaded data, trigger loading + if ( + visibleItemStopIndex >= data.length - columnCount * 2 && + view.dataStore.hasNextPage && + !view.dataStore.loading + ) { + loadMore?.(); + } + + cb({ + overscanStartIndex: overscanRowStartIndex, + overscanStopIndex: overscanRowStopIndex, + visibleStartIndex: visibleRowStartIndex, + visibleStopIndex: visibleRowStopIndex, }); - loadMore?.(); - } - - cb({ - overscanStartIndex: overscanRowStartIndex, - overscanStopIndex: overscanRowStopIndex, - visibleStartIndex: visibleRowStartIndex, - visibleStopIndex: visibleRowStopIndex, - }); - }, + }, [data.length, columnCount, view.dataStore.hasNextPage, view.dataStore.loading, loadMore, getCellIndex], ); @@ -214,47 +210,9 @@ export const GridView = observer(({ data, view, loadMore, fields, onChange, hidd (index) => { const rowExists = index < data.length && !!data[index]; const hasNextPage = view.dataStore.hasNextPage; - const result = !hasNextPage || rowExists; - - // Only log when we're near the end of loaded data - if (index >= data.length - 5) { - console.log("GridView: isItemLoaded check", { - index, - dataLength: data.length, - rowExists, - hasNextPage, - result, - total: view.dataStore.total, - }); - } - - return result; - }, - [data.length, view.dataStore.hasNextPage, view.dataStore.total], - ); - - // Wrap loadMore to ensure it handles the grid's item indexing correctly - const loadMoreWrapper = useCallback( - async (startIndex, stopIndex) => { - const threshold = Math.max(1, Math.floor(view.dataStore.pageSize / 4)); - const batchSize = Math.max(1, Math.floor(view.dataStore.pageSize / 2)); - - console.log("GridView: Loading more items", { - startIndex, - stopIndex, - currentDataLength: data.length, - total: view.dataStore.total, - threshold, - batchSize, - hasNextPage: view.dataStore.hasNextPage, - pageSize: view.dataStore.pageSize, - }); - - if (loadMore) { - await loadMore(); - } + return !hasNextPage || rowExists; }, - [loadMore, data.length, view.dataStore.pageSize, view.dataStore.total, view.dataStore.hasNextPage], + [data.length, view.dataStore.hasNextPage], ); return ( @@ -265,7 +223,7 @@ export const GridView = observer(({ data, view, loadMore, fields, onChange, hidd From 2f955813007fb29f052d37860b80efee2659425f Mon Sep 17 00:00:00 2001 From: Nick Skriabin Date: Thu, 3 Jul 2025 16:04:31 +0100 Subject: [PATCH 4/4] Cleanup --- .../datamanager/src/components/MainView/GridView/GridView.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/libs/datamanager/src/components/MainView/GridView/GridView.jsx b/web/libs/datamanager/src/components/MainView/GridView/GridView.jsx index f441d0c5a584..2d8e93673e54 100644 --- a/web/libs/datamanager/src/components/MainView/GridView/GridView.jsx +++ b/web/libs/datamanager/src/components/MainView/GridView/GridView.jsx @@ -177,7 +177,7 @@ export const GridView = observer(({ data, view, loadMore, fields, onChange, hidd /> ); }, - [data, columnCount, fieldsData, view.selected, view, view.selected.list, view.selected.all, getCellIndex, onChange], + [data, columnCount, fieldsData, view, onChange, getCellIndex], ); const onItemsRenderedWrap = useCallback(