From 7e9b024fefbadcb36640e5757f33b9f83ed369be Mon Sep 17 00:00:00 2001 From: Guillermo Puente Date: Mon, 1 Jul 2024 16:54:33 -0400 Subject: [PATCH] feat: replaced react-window by tanstack/react-virtual --- package-lock.json | 72 +++++------- package.json | 4 +- src/components/file-content-view.tsx | 166 ++++++++++++++++++--------- src/hooks/useWindowSize.ts | 25 ++++ 4 files changed, 167 insertions(+), 100 deletions(-) create mode 100644 src/hooks/useWindowSize.ts diff --git a/package-lock.json b/package-lock.json index 9078e381..be8a46cc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,16 +1,17 @@ { "name": "@powerhousedao/connect", - "version": "1.0.0-dev.21", + "version": "1.0.0-dev.23", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@powerhousedao/connect", - "version": "1.0.0-dev.21", + "version": "1.0.0-dev.23", "license": "AGPL-3.0-only", "dependencies": { "@powerhousedao/design-system": "1.0.0-alpha.141", "@sentry/react": "^7.109.0", + "@tanstack/react-virtual": "^3.8.1", "did-key-creator": "^1.2.0", "document-drive": "1.0.0-alpha.80", "document-model": "1.7.0", @@ -28,8 +29,6 @@ "react-i18next": "^13.5.0", "react-router-dom": "^6.11.2", "react-stately": "^3.31.0", - "react-virtualized-auto-sizer": "^1.0.24", - "react-window": "^1.8.10", "tailwind-merge": "^1.14.0", "uuid": "^9.0.1", "viem": "^2.8.13" @@ -53,7 +52,6 @@ "@total-typescript/ts-reset": "^0.5.1", "@types/react": "^18.2.79", "@types/react-dom": "^18.2.18", - "@types/react-window": "^1.8.8", "@types/uuid": "^9.0.7", "@types/wicg-file-system-access": "^2020.9.6", "@typescript-eslint/eslint-plugin": "^6.18.1", @@ -7909,6 +7907,31 @@ "node": ">=10" } }, + "node_modules/@tanstack/react-virtual": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.8.1.tgz", + "integrity": "sha512-dP5a7giEM4BQWLJ7K07ToZv8rF51mzbrBMkf0scg1QNYuFx3utnPUBPUHdzaowZhIez1K2XS78amuzD+YGRA5Q==", + "dependencies": { + "@tanstack/virtual-core": "3.8.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@tanstack/virtual-core": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.8.1.tgz", + "integrity": "sha512-uNtAwenT276M9QYCjTBoHZ8X3MUeCRoGK59zPi92hMIxdfS9AyHjkDWJ94WroDxnv48UE+hIeo21BU84jKc8aQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@testing-library/dom": { "version": "9.3.4", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.4.tgz", @@ -8257,15 +8280,6 @@ "@types/react": "*" } }, - "node_modules/@types/react-window": { - "version": "1.8.8", - "resolved": "https://registry.npmjs.org/@types/react-window/-/react-window-1.8.8.tgz", - "integrity": "sha512-8Ls660bHR1AUA2kuRvVG9D/4XpRC6wjAaPT9dil7Ckc76eP9TKWZwwmgfq8Q1LANX3QNDnoU4Zp48A3w+zK69Q==", - "dev": true, - "dependencies": { - "@types/react": "*" - } - }, "node_modules/@types/responselike": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", @@ -22592,36 +22606,6 @@ "react-dom": ">=16.6.0" } }, - "node_modules/react-virtualized-auto-sizer": { - "version": "1.0.24", - "resolved": "https://registry.npmjs.org/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.24.tgz", - "integrity": "sha512-3kCn7N9NEb3FlvJrSHWGQ4iVl+ydQObq2fHMn12i5wbtm74zHOPhz/i64OL3c1S1vi9i2GXtZqNqUJTQ+BnNfg==", - "peerDependencies": { - "react": "^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0", - "react-dom": "^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/react-window": { - "version": "1.8.10", - "resolved": "https://registry.npmjs.org/react-window/-/react-window-1.8.10.tgz", - "integrity": "sha512-Y0Cx+dnU6NLa5/EvoHukUD0BklJ8qITCtVEPY1C/nL8wwoZ0b5aEw8Ff1dOVHw7fCzMt55XfJDd8S8W8LCaUCg==", - "dependencies": { - "@babel/runtime": "^7.0.0", - "memoize-one": ">=3.1.1 <6" - }, - "engines": { - "node": ">8.0.0" - }, - "peerDependencies": { - "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/react-window/node_modules/memoize-one": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", - "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==" - }, "node_modules/read-binary-file-arch": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/read-binary-file-arch/-/read-binary-file-arch-1.0.6.tgz", diff --git a/package.json b/package.json index fc0bfcfa..59da59ba 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,6 @@ "@total-typescript/ts-reset": "^0.5.1", "@types/react": "^18.2.79", "@types/react-dom": "^18.2.18", - "@types/react-window": "^1.8.8", "@types/uuid": "^9.0.7", "@types/wicg-file-system-access": "^2020.9.6", "@typescript-eslint/eslint-plugin": "^6.18.1", @@ -96,6 +95,7 @@ "dependencies": { "@powerhousedao/design-system": "1.0.0-alpha.141", "@sentry/react": "^7.109.0", + "@tanstack/react-virtual": "^3.8.1", "did-key-creator": "^1.2.0", "document-drive": "1.0.0-alpha.80", "document-model": "1.7.0", @@ -113,8 +113,6 @@ "react-i18next": "^13.5.0", "react-router-dom": "^6.11.2", "react-stately": "^3.31.0", - "react-virtualized-auto-sizer": "^1.0.24", - "react-window": "^1.8.10", "tailwind-merge": "^1.14.0", "uuid": "^9.0.1", "viem": "^2.8.13" diff --git a/src/components/file-content-view.tsx b/src/components/file-content-view.tsx index ccaec8f8..d90ad79f 100644 --- a/src/components/file-content-view.tsx +++ b/src/components/file-content-view.tsx @@ -2,12 +2,13 @@ import { ConnectDropdownMenuItem, TreeItem, } from '@powerhousedao/design-system'; +import React, { useRef } from 'react'; import { useTranslation } from 'react-i18next'; -import AutoSizer from 'react-virtualized-auto-sizer'; -import { VariableSizeGrid as Grid } from 'react-window'; import { FileItem } from 'src/components/file-item'; +import { useVirtualizer } from '@tanstack/react-virtual'; import { SetIsWriteMode } from 'src/hooks/useFileOptions'; +import { useWindowSize } from 'src/hooks/useWindowSize'; interface IProps { decodedDriveID: string; @@ -28,6 +29,8 @@ const GAP = 8; const ITEM_WIDTH = 256; const ITEM_HEIGHT = 48; +const USED_SPACE = 420; + export const FileContentView: React.FC = ({ decodedDriveID, onFileDeleted, @@ -38,7 +41,50 @@ export const FileContentView: React.FC = ({ onFileOptionsClick, isAllowedToCreateDocuments, }) => { + const parentRef = useRef(null); const { t } = useTranslation(); + const windowSize = useWindowSize(); + + const availableWidth = windowSize.innerWidth - USED_SPACE; + + const columnCount = Math.floor(availableWidth / (ITEM_WIDTH + GAP)) || 1; + const rowCount = Math.ceil(files.length / columnCount); + + const rowVirtualizer = useVirtualizer({ + count: rowCount, + getScrollElement: () => parentRef.current, + estimateSize: index => { + if (index > 0) { + return ITEM_HEIGHT + GAP; + } + return ITEM_HEIGHT; + }, + overscan: 5, + }); + + const columnVirtualizer = useVirtualizer({ + horizontal: true, + count: columnCount, + getScrollElement: () => parentRef.current, + estimateSize: index => { + if (index > 0) { + return ITEM_WIDTH + GAP; + } + return ITEM_WIDTH; + }, + overscan: 5, + }); + + const getItemIndex = (rowIndex: number, columnIndex: number) => + rowIndex * columnCount + columnIndex; + + const getItem = ( + rowIndex: number, + columnIndex: number, + ): TreeItem | null => { + const index = getItemIndex(rowIndex, columnIndex); + return files[index] || null; + }; if (files.length < 1) { return ( @@ -48,63 +94,77 @@ export const FileContentView: React.FC = ({ ); } + const renderItem = (rowIndex: number, columnIndex: number) => { + const file = getItem(rowIndex, columnIndex); + + if (!file) { + return null; + } + + return ( +
+ +
+ ); + }; + return ( - - {({ height, width }) => { - const columnCount = Math.floor(width / (ITEM_WIDTH + GAP)) || 1; - const rowCount = Math.ceil(files.length / columnCount); - - return ( - - index === columnCount - 1 - ? ITEM_WIDTH - : ITEM_WIDTH + GAP - } - rowHeight={index => - index === rowCount ? ITEM_HEIGHT : ITEM_HEIGHT + GAP - } - rowCount={rowCount} - > - {({ columnIndex, rowIndex, style }) => { - const itemIndex = - rowIndex * columnCount + columnIndex; - const file = files[itemIndex] || null; - - if (!file) return null; - - return ( +
+
+ {rowVirtualizer.getVirtualItems().map(virtualRow => ( + + {columnVirtualizer + .getVirtualItems() + .map(virtualColumn => (
- + {renderItem( + virtualRow.index, + virtualColumn.index, + )}
- ); - }} - - ); - }} - + ))} +
+ ))} +
+
); }; diff --git a/src/hooks/useWindowSize.ts b/src/hooks/useWindowSize.ts new file mode 100644 index 00000000..cb9521e6 --- /dev/null +++ b/src/hooks/useWindowSize.ts @@ -0,0 +1,25 @@ +import { useEffect, useState } from 'react'; + +export const useWindowSize = () => { + const [windowSize, setWindowSize] = useState({ + innerWidth: window.innerWidth, + innerHeight: window.innerHeight, + }); + + useEffect(() => { + const windowSizeHandler = () => { + setWindowSize({ + innerWidth: window.innerWidth, + innerHeight: window.innerHeight, + }); + }; + + window.addEventListener('resize', windowSizeHandler); + + return () => { + window.removeEventListener('resize', windowSizeHandler); + }; + }, []); + + return windowSize; +};