From f3a2eefe25d1340c01b78a96d3f9b0109a5e4cc7 Mon Sep 17 00:00:00 2001 From: nikec <43032218+niikeec@users.noreply.github.com> Date: Mon, 30 Oct 2023 16:56:03 +0100 Subject: [PATCH] [ENG-1384] Replace GridList with @virtual-grid/react (#1707) replace GridList with @virtual-grid/react --- .../app/$libraryId/Explorer/View/GridList.tsx | 26 +- interface/components/GridList.tsx | 277 ------------------ interface/components/index.ts | 1 - interface/package.json | 5 +- pnpm-lock.yaml | Bin 790806 -> 791911 bytes 5 files changed, 14 insertions(+), 295 deletions(-) delete mode 100644 interface/components/GridList.tsx diff --git a/interface/app/$libraryId/Explorer/View/GridList.tsx b/interface/app/$libraryId/Explorer/View/GridList.tsx index 109d447fa..7fcefd21b 100644 --- a/interface/app/$libraryId/Explorer/View/GridList.tsx +++ b/interface/app/$libraryId/Explorer/View/GridList.tsx @@ -1,3 +1,4 @@ +import { Grid, useGrid } from '@virtual-grid/react'; import { createContext, useCallback, @@ -11,7 +12,6 @@ import { import Selecto from 'react-selecto'; import { useKey } from 'rooks'; import { type ExplorerItem } from '@sd/client'; -import { GridList, useGridList } from '~/components'; import { useMouseNavigate, useOperatingSystem } from '~/hooks'; import { useExplorerContext } from '../Context'; @@ -111,7 +111,6 @@ export default ({ children }: { children: RenderItem }) => { const explorer = useExplorerContext(); const settings = explorer.useSettingsSnapshot(); - const explorerStore = useExplorerStore(); const explorerView = useExplorerViewContext(); const selecto = useRef(null); @@ -127,17 +126,15 @@ export default ({ children }: { children: RenderItem }) => { const padding = settings.layoutMode === 'grid' ? 12 : 0; - const grid = useGridList({ - ref: explorerView.ref, + const grid = useGrid({ + scrollRef: explorer.scrollRef, count: explorer.items?.length ?? 0, totalCount: explorer.count, - overscan: explorer.overscan, + ...(settings.layoutMode === 'grid' + ? { size: { width: settings.gridItemSize, height: itemHeight } } + : { columns: settings.mediaColumns }), + rowVirtualizer: { overscan: explorer.overscan ?? 5 }, onLoadMore: explorer.loadMore, - size: - settings.layoutMode === 'grid' - ? { width: settings.gridItemSize, height: itemHeight } - : undefined, - columns: settings.layoutMode === 'media' ? settings.mediaColumns : undefined, getItemId: useCallback( (index: number) => { const item = explorer.items?.[index]; @@ -154,8 +151,7 @@ export default ({ children }: { children: RenderItem }) => { x: padding, y: padding }, - gap: explorerView.gap || (settings.layoutMode === 'grid' ? settings.gridGap : undefined), - top: explorerView.top + gap: explorerView.gap || (settings.layoutMode === 'grid' ? settings.gridGap : undefined) }); function getElementId(element: Element) { @@ -552,7 +548,7 @@ export default ({ children }: { children: RenderItem }) => { let itemsInDragCount = (dragHeight - grid.gap.y) / - (grid.virtualItemHeight + grid.gap.y); + (grid.virtualItemSize.height + grid.gap.y); if (itemsInDragCount > 1) { itemsInDragCount = Math.ceil(itemsInDragCount); @@ -619,7 +615,7 @@ export default ({ children }: { children: RenderItem }) => { /> )} - + {(index) => { const item = explorer.items?.[index]; if (!item) return null; @@ -653,7 +649,7 @@ export default ({ children }: { children: RenderItem }) => { ); }} - + ); }; diff --git a/interface/components/GridList.tsx b/interface/components/GridList.tsx deleted file mode 100644 index 65a5db873..000000000 --- a/interface/components/GridList.tsx +++ /dev/null @@ -1,277 +0,0 @@ -import { useVirtualizer } from '@tanstack/react-virtual'; -import React, { - ReactNode, - RefObject, - useCallback, - useEffect, - useLayoutEffect, - useMemo, - useRef, - useState -} from 'react'; -import { useMutationObserver } from 'rooks'; -import useResizeObserver from 'use-resize-observer'; -import { ExplorerViewPadding } from '~/app/$libraryId/Explorer/View'; -import { useExplorerViewPadding } from '~/app/$libraryId/Explorer/View/util'; - -type ItemData = any | undefined; -type ItemId = number | string; - -export interface GridListItem { - index: number; - id: IdT; - row: number; - column: number; - rect: Omit; - data: DataT; -} - -export interface UseGridListProps { - count: number; - totalCount?: number; - ref: RefObject; - padding?: number | ExplorerViewPadding; - gap?: number | { x?: number; y?: number }; - overscan?: number; - top?: number; - onLoadMore?: () => void; - getItemId?: (index: number) => IdT | undefined; - getItemData?: (index: number) => DataT; - size?: number | { width: number; height: number }; - columns?: number; -} - -export const useGridList = ({ - padding, - gap, - size, - columns, - ref, - getItemId, - getItemData, - ...props -}: UseGridListProps) => { - const { width } = useResizeObserver({ ref }); - - const count = !props.totalCount ? props.count : Math.max(props.count, props.totalCount); - - const gridPadding = useExplorerViewPadding(padding); - - const paddingTop = gridPadding.top ?? 0; - const paddingBottom = gridPadding.bottom ?? 0; - const paddingLeft = gridPadding.left ?? 0; - const paddingRight = gridPadding.right ?? 0; - - const gapX = (typeof gap === 'object' ? gap.x : gap) || 0; - const gapY = (typeof gap === 'object' ? gap.y : gap) || 0; - - const itemWidth = size ? (typeof size === 'object' ? size.width : size) : undefined; - const itemHeight = size ? (typeof size === 'object' ? size.height : size) : undefined; - - const gridWidth = width ? width - (paddingLeft + paddingRight) : 0; - - let columnCount = columns || 0; - - if (!columns && itemWidth) { - let columns = Math.floor(gridWidth / itemWidth); - if (gapX) columns = Math.floor((gridWidth - (columns - 1) * gapX) / itemWidth); - columnCount = columns; - } - - const rowCount = columnCount > 0 ? Math.ceil(props.count / columnCount) : 0; - const totalRowCount = columnCount > 0 ? Math.ceil(count / columnCount) : 0; - - const virtualItemWidth = - columnCount > 0 ? (gridWidth - (columnCount - 1) * gapX) / columnCount : 0; - - const virtualItemHeight = itemHeight || virtualItemWidth; - - const getItem = useCallback( - (index: number) => { - if (index < 0 || index >= count) return; - - const id = getItemId?.(index) || index; - - const data = getItemData?.(index) as DataT; - - const column = index % columnCount; - const row = Math.floor(index / columnCount); - - const x = paddingLeft + (column !== 0 ? gapX : 0) * column + virtualItemWidth * column; - const y = paddingTop + (row !== 0 ? gapY : 0) * row + virtualItemHeight * row; - - const item: GridListItem = { - index, - id, - data, - row, - column, - rect: { - height: virtualItemHeight, - width: virtualItemWidth, - x, - y, - top: y, - bottom: y + virtualItemHeight, - left: x, - right: x + virtualItemWidth - } - }; - - return item; - }, - [ - columnCount, - count, - gapX, - gapY, - getItemId, - getItemData, - paddingLeft, - paddingTop, - virtualItemHeight, - virtualItemWidth - ] - ); - - return { - columnCount, - rowCount, - totalRowCount, - width: gridWidth, - padding: { top: paddingTop, bottom: paddingBottom, left: paddingLeft, right: paddingRight }, - gap: { x: gapX, y: gapY }, - itemHeight, - itemWidth, - virtualItemHeight, - virtualItemWidth, - getItem, - ...props - }; -}; - -export interface GridListProps { - grid: ReturnType; - scrollRef: RefObject; - children: (index: number) => ReactNode; -} - -export const GridList = ({ grid, children, scrollRef }: GridListProps) => { - const ref = useRef(null); - - const [listOffset, setListOffset] = useState(0); - - const getHeight = useCallback( - (index: number) => grid.virtualItemHeight + (index !== 0 ? grid.gap.y : 0), - [grid.virtualItemHeight, grid.gap.y] - ); - - const getWidth = useCallback( - (index: number) => grid.virtualItemWidth + (index !== 0 ? grid.gap.x : 0), - [grid.virtualItemWidth, grid.gap.x] - ); - - const rowVirtualizer = useVirtualizer({ - count: grid.totalRowCount, - getScrollElement: () => scrollRef.current, - estimateSize: getHeight, - paddingStart: grid.padding.top, - paddingEnd: grid.padding.bottom, - overscan: grid.overscan ?? 5, - scrollMargin: listOffset - }); - - const columnVirtualizer = useVirtualizer({ - horizontal: true, - count: grid.columnCount, - getScrollElement: () => scrollRef.current, - estimateSize: getWidth, - paddingStart: grid.padding.left, - paddingEnd: grid.padding.right - }); - - const virtualRows = rowVirtualizer.getVirtualItems(); - const virtualColumns = columnVirtualizer.getVirtualItems(); - - // Measure virtual item on size change - useEffect(() => { - rowVirtualizer.measure(); - columnVirtualizer.measure(); - }, [rowVirtualizer, columnVirtualizer, grid.virtualItemWidth, grid.virtualItemHeight]); - - // Force recalculate range - // https://github.com/TanStack/virtual/issues/485 - useMemo(() => { - rowVirtualizer.calculateRange(); - columnVirtualizer.calculateRange(); - - return null; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [rowVirtualizer, columnVirtualizer, grid.columnCount, grid.rowCount]); - - useEffect(() => { - if (!grid.onLoadMore) return; - - const lastRow = virtualRows[virtualRows.length - 1]; - if (!lastRow) return; - - const loadMoreFromRow = Math.ceil(grid.rowCount * 0.75); - - if (lastRow.index >= loadMoreFromRow - 1) grid.onLoadMore(); - }, [virtualRows, grid.rowCount, grid.onLoadMore, grid]); - - useMutationObserver(scrollRef, () => setListOffset(ref.current?.offsetTop ?? 0)); - - useLayoutEffect(() => setListOffset(ref.current?.offsetTop ?? 0), []); - - return ( -
- {grid.width > 0 && - virtualRows.map((virtualRow) => ( - - {virtualColumns.map((virtualColumn) => { - const index = virtualRow.index * grid.columnCount + virtualColumn.index; - - if (index >= grid.count) return null; - - return ( -
-
- {children(index)} -
-
- ); - })} -
- ))} -
- ); -}; diff --git a/interface/components/index.ts b/interface/components/index.ts index 9eabd7867..d614cb501 100644 --- a/interface/components/index.ts +++ b/interface/components/index.ts @@ -4,7 +4,6 @@ export * from './ColorPicker'; export * from './DismissibleNotice'; export * from './DragRegion'; export * from './Folder'; -export * from './GridList'; export * from './Icon'; export * from './Loader'; export * from './PDFViewer'; diff --git a/interface/package.json b/interface/package.json index 2932e7730..4a9000cbb 100644 --- a/interface/package.json +++ b/interface/package.json @@ -22,6 +22,7 @@ "@tanstack/react-query-devtools": "^4.36.1", "@tanstack/react-table": "^8.10.7", "@tanstack/react-virtual": "3.0.0-beta.66", + "@virtual-grid/react": "^1.0.2", "class-variance-authority": "^0.7.0", "clsx": "^2.0.0", "crypto-random-string": "^5.0.0", @@ -53,14 +54,14 @@ "valtio": "^1.11.2" }, "devDependencies": { - "tailwindcss": "^3.3.3", - "type-fest": "^4.5.0", "@sd/config": "workspace:*", "@types/node": "~18.17.19", "@types/react": "^18.2.31", "@types/react-dom": "^18.2.14", "@types/uuid": "^9.0.6", "@vitejs/plugin-react": "^4.1.0", + "tailwindcss": "^3.3.3", + "type-fest": "^4.5.0", "typescript": "^5.2.2", "vite": "^4.5.0", "vite-plugin-svgr": "^3.3.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6670e1e828fea99e5efb1dd05c15abd80996059e..7ab3cb05b3cb070cb54eb1d5c1662459ebb51de1 100644 GIT binary patch delta 791 zcmZvZZ%7ky7{}S|ZhBie(Sc?7XEhqRyxso#C$P=dY*V+}=H2EXXghCn-tD&CPUi&4 zs8|t^a?tN(){9;gfnjhEfxQjFCKcGb=uJf;gizqCmIW#F;sXy44}8DR_j~sN{`nEU z@&%T*+}c$I(%`8Lhor6a3uzrvNeezi8qg+J1O6SP!RVUF2;y{t>rM-q35h7RLl`bC z!8(NnEa{Xc2|g8)-Zl+ftrC?gp^&QtbW5k^m}1do$E+A$$uW?eW0a4-s{Sm&_n#;R zsyCG=sOq5>sc{hj3mmKg+1qfPd?yI+n)hAe@~g`*cTfAzf|Ploy&{S0yo9a zMdv)cpClbKZmwvJ#{&)!x>0|m`M}p^fl1IJ=-7sa$GmZGWYFvITf{(bUWk*KP*RK+ z(xbg*cb=u(r3f=V++zuO6VZ6t zSOCji%H!bUHiUweA1JJ_f~RZBli=7Yj7p2Bs{U^SMyZW396eabUK+;!P78*Ef(bs+ zn&Cx8C^EE|$?@H}WRVeO7@@zj4|7O~Xp7W0t^?06!Y0t!jT!*ej<(6x4x|Cv7dZxq zCo1h;FSZUuCqL99y9(gaBcs3-tZM?sZbVx_3`h-Cjk+53GGai>Z(9GS?W##*R&aj} zvXSSrIm+oYmuJ|bpB_qBgo!B5djhoGX9;Igo+vx!z|*mamnTNXL-tuRFHUC8xD_cw~s4C1PGK$g-pvTtVS3Q2m6S1&ycGP2kc8g%-R#tX5U#cTjnL qNBwN`xBCG(^;&Zs0l7_0Ggw~NsO8-ajY$coaTf?11*!0gT&c2QW#APG6(O zD%O55m1+CIROXd0L0r4(4XrHt+t2#5Fd2aP+nF0!^5$>%R$}F50txs|Ur@}*x_zDs zD<9MJ^XIq)r_Z>=Z9eS|Gk-geIx7&f0Wmueb8P2P=WIXUe(XEv_G90P;