diff --git a/package.json b/package.json index b0cd62e..2ba20b4 100644 --- a/package.json +++ b/package.json @@ -16,11 +16,13 @@ "dependencies": { "@primer/octicons": "^9.1.1", "@sentry/browser": "^5.5.0", + "@types/react-window": "^1.8.1", "ini": "^1.3.5", "js-base64": "^2.5.1", "nprogress": "^0.2.0", "react": "^16.8.6", "react-dom": "^16.8.6", + "react-window": "^1.8.5", "safe-touch": "^0.2.2" }, "devDependencies": { diff --git a/src/components/FileExplorer.tsx b/src/components/FileExplorer.tsx index 353fcde..c497ff8 100644 --- a/src/components/FileExplorer.tsx +++ b/src/components/FileExplorer.tsx @@ -1,4 +1,5 @@ import * as React from 'react' +import { FixedSizeList as List, ListChildComponentProps } from 'react-window' import connect from 'driver/connect' import { FileExplorer as FileExplorerCore } from 'driver/core' import SearchBar from 'components/SearchBar' @@ -9,6 +10,7 @@ import { ConnectorState } from 'driver/core/FileExplorer' import { TreeData, MetaData } from 'utils/GitHubHelper' import { VisibleNodes, TreeNode } from 'utils/VisibleNodesGenerator' import Icon from './Icon' +import SizeObserver from './SizeObserver' export type Props = { treeData?: TreeData @@ -50,27 +52,53 @@ class FileExplorer extends React.Component { execAfterRender() } - renderFiles(visibleNodes: VisibleNodes, onNodeClick: Node['props']['onClick']) { + VirtualNode = React.memo(({ index, style }) => { + const { visibleNodes, onNodeClick } = this.props + if (!visibleNodes) return null const { nodes, depths, focusedNode, expandedNodes } = visibleNodes - const { searchKey, searched } = this.props + const node = nodes[index] + return ( + + ) + }) + + renderFiles(visibleNodes: VisibleNodes) { + const { nodes } = visibleNodes + const { searchKey } = this.props const inSearch = searchKey !== '' if (inSearch && nodes.length === 0) { return } return ( -
- {nodes.map(node => ( - - ))} -
+ + {({ width, height }) => + height && + width && ( + { + const node = nodes[index] + return node && node.path + }} + itemData={{ nodes }} + height={height} + itemCount={nodes.length} + itemSize={35} + width={width} + > + {this.VirtualNode} + + ) + } + ) } @@ -129,7 +157,7 @@ class FileExplorer extends React.Component { onSearchKeyChange={handleSearchKeyChange} onFocus={onFocusSearchBar} /> - {this.renderFiles(visibleNodes, onNodeClick)} + {this.renderFiles(visibleNodes)} ) )} diff --git a/src/components/Node.tsx b/src/components/Node.tsx index 5075da2..46cd6c1 100644 --- a/src/components/Node.tsx +++ b/src/components/Node.tsx @@ -23,6 +23,7 @@ type Props = { expanded: boolean focused: boolean renderActions?(node: TreeNode): React.ReactNode + style?: React.CSSProperties } export default class Node extends React.PureComponent { onClick: React.MouseEventHandler = event => { @@ -39,13 +40,13 @@ export default class Node extends React.PureComponent { } render() { - const { node, depth, expanded, focused, renderActions } = this.props + const { node, depth, expanded, focused, renderActions, style } = this.props const { name, path, virtual } = node if (virtual) { // this is not a real node // for now, all virtual nodes are indicators for pending state return ( -
+
@@ -53,7 +54,11 @@ export default class Node extends React.PureComponent { ) } return ( -
+
=3.1.1 <6": + version "5.0.5" + resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.0.5.tgz#8cd3809555723a07684afafcd6f756072ac75d7e" + integrity sha512-ey6EpYv0tEaIbM/nTDOpHciXUvd+ackQrJgEzBwemhZZIWZjcyodqEcrmqDy2BKRTM3a65kKBV4WtLXJDt26SQ== + memory-fs@^0.4.0, memory-fs@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" @@ -4557,6 +4576,14 @@ react-is@^16.8.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.6.tgz#5bbc1e2d29141c9fbdfed456343fe2bc430a6a16" integrity sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA== +react-window@^1.8.5: + version "1.8.5" + resolved "https://registry.yarnpkg.com/react-window/-/react-window-1.8.5.tgz#a56b39307e79979721021f5d06a67742ecca52d1" + integrity sha512-HeTwlNa37AFa8MDZFZOKcNEkuF2YflA0hpGPiTT9vR7OawEt+GZbfM6wqkBahD3D3pUjIabQYzsnY/BSJbgq6Q== + dependencies: + "@babel/runtime" "^7.0.0" + memoize-one ">=3.1.1 <6" + react@^16.8.6: version "16.8.6" resolved "https://registry.yarnpkg.com/react/-/react-16.8.6.tgz#ad6c3a9614fd3a4e9ef51117f54d888da01f2bbe" @@ -4601,6 +4628,11 @@ regenerate@^1.4.0: resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11" integrity sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg== +regenerator-runtime@^0.13.2: + version "0.13.3" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz#7cf6a77d8f5c6f60eb73c5fc1955b2ceb01e6bf5" + integrity sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw== + regenerator-transform@^0.14.0: version "0.14.1" resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.1.tgz#3b2fce4e1ab7732c08f665dfdb314749c7ddd2fb"