diff --git a/components/Sidebar.tsx b/components/Sidebar.tsx index d044bff7..98d30e55 100644 --- a/components/Sidebar.tsx +++ b/components/Sidebar.tsx @@ -67,13 +67,7 @@ export const Sidebar = ({ if (!tree.current) return; tree.current.scrollToItem(activeFilePath, "center"); }, 100); - }, []); - useEffect(() => { - if (!tree.current) return; - tree.current.recomputeTree({ - useDefaultOpenness: true, - }); - }, [searchTerm]); + }, [activeFilePath]); const nestedFiles = useMemo(() => { try { @@ -82,18 +76,27 @@ export const Sidebar = ({ }, [files]); const filteredFiles = useMemo(() => { - if (!searchTerm) return nestedFiles; const lowerSearchTerm = searchTerm.toLowerCase(); - return nestedFiles - .map((file) => { - const children = file.children.filter((child) => - child.name.toLowerCase().includes(lowerSearchTerm) - ); - const isMatch = file.name.toLowerCase().includes(lowerSearchTerm); - if (!children.length && !isMatch) return null; - return { ...file, children }; - }) - .filter(Boolean) as NestedFileTree[]; + return [ + { + name: `${owner}/${repo}`, + path: "", + children: !searchTerm + ? nestedFiles + : (nestedFiles + .map((file) => { + const children = file.children.filter((child) => + child.name.toLowerCase().includes(lowerSearchTerm) + ); + const isMatch = file.name + .toLowerCase() + .includes(lowerSearchTerm); + if (!children.length && !isMatch) return null; + return { ...file, children }; + }) + .filter(Boolean) as NestedFileTree[]), + }, + ]; }, [nestedFiles, searchTerm]); const numberOfFiles = useMemo(() => { @@ -106,68 +109,55 @@ export const Sidebar = ({ return runningCount; }, [filteredFiles]); + // This function prepares an object for yielding. We can yield an object + // that has `data` object with `id` and `isOpenByDefault` fields. + // We can also add any other data here. + const getNodeData = (node, nestingLevel) => { + const id = node.path || "/"; + // don't close open folders + const existingIsOpen = tree.current?.state.records.get(id)?.public.isOpen; + return { + data: { + id, + isLeaf: node.children?.length === 0, + isOpenByDefault: + existingIsOpen || + activeFilePath === id || + activeFilePath.startsWith(`${id}/`) || + numberOfFiles < 20 || + nestingLevel < 1, + name: node.name, + depth: nestingLevel, + path: node.path, + activeFilePath, + canCollapse: nestingLevel > 0, + updatedContents, + currentPathname: router.pathname, + currentQuery: query, + currentBranch: branchName, + }, + nestingLevel, + node, + }; + }; const treeWalker = useMemo( () => - function* treeWalker(refresh) { - const stack = []; - - // Remember all the necessary data of the first node in the stack. - stack.push({ - depth: 0, - node: { - id: "/", - name: `${owner}/${repo}`, - path: "", - children: filteredFiles, - }, - }); - - // Walk through the tree until we have no nodes available. - while (stack.length !== 0) { - const { - node: { children = [], path, name }, - depth, - } = stack.pop(); - const canCollapse = depth > 0; - const id = path || "/"; + function* treeWalker() { + // Step [1]: Define the root node of our tree. There can be one or + // multiple nodes. + for (let i = 0; i < filteredFiles.length; i++) { + yield getNodeData(filteredFiles[i], 0); + } - // Here we are sending the information about the node to the Tree component - // and receive an information about the openness state from it. The - // `refresh` parameter tells us if the full update of the tree is requested; - // basing on it we decide to return the full node data or only the node - // id to update the nodes order. - const isOpened = yield refresh - ? { - id, - isLeaf: children.length === 0, - isOpenByDefault: - activeFilePath === id || - activeFilePath.startsWith(`${id}/`) || - numberOfFiles < 20 || - depth < 1, - name, - depth, - path, - activeFilePath, - canCollapse, - updatedContents, - currentPathname: router.pathname, - currentQuery: query, - currentBranch: branchName, - } - : id; + while (true) { + // Step [2]: Get the parent component back. It will be the object + // the `getNodeData` function constructed, so you can read any data from it. + const parent = yield; - // Basing on the node openness state we are deciding if we need to render - // the child nodes (if they exist). - if (children.length !== 0 && isOpened) { - // Since it is a stack structure, we need to put nodes we want to render - // first to the end of the stack. - for (let i = children.length - 1; i >= 0; i--) { - stack.push({ - depth: depth + 1, - node: children[i], - }); - } + for (let i = 0; i < parent.node.children.length; i++) { + // Step [3]: Yielding all the children of the provided component. Then we + // will return for the step [2] with the first children. + yield getNodeData(parent.node.children[i], parent.nestingLevel + 1); } } }, @@ -175,11 +165,11 @@ export const Sidebar = ({ filteredFiles, updatedContents, numberOfFiles < 20, - activeFilePath, - query, + query.branchPath.join("/"), branchName, ] ); + if (!files.map) return null; return ( @@ -202,6 +192,7 @@ export const Sidebar = ({ height={dimensions[1]} width={dimensions[0]} className="pb-10" + ref={tree} > {Node} @@ -218,7 +209,6 @@ export const Sidebar = ({ const Node = ({ data: { - isLeaf, name, path, activeFilePath, @@ -228,10 +218,11 @@ const Node = ({ currentPathname, currentQuery, currentBranch, + isLeaf, }, isOpen, style, - toggle, + setOpen, }) => { return (
@@ -245,7 +236,7 @@ const Node = ({ currentPathname={currentPathname} currentQuery={currentQuery} currentBranch={currentBranch} - toggle={toggle} + setOpen={setOpen} isOpen={isOpen} isFolder={!isLeaf} /> @@ -265,7 +256,7 @@ type ItemProps = { canCollapse?: boolean; isOpen: boolean; updatedContents: Record; - toggle: () => void; + setOpen: (newState: boolean) => void; }; const Item = ({ @@ -279,7 +270,7 @@ const Item = ({ currentBranch, isOpen, updatedContents, - toggle, + setOpen, }: ItemProps) => { const isActive = activeFilePath === path; @@ -312,7 +303,7 @@ const Item = ({ // don't select the folder on toggle e.stopPropagation(); e.preventDefault(); - toggle(); + setOpen(!isOpen); }} > {depth > 0 && ( diff --git a/package.json b/package.json index 72930953..56e58767 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "react-dom": "^18.1.0", "react-icons": "^4.3.1", "react-query": "^3.34.16", - "react-vtree": "^2.0.4", + "react-vtree": "3.0.0-beta.3", "react-window": "^1.8.7", "streamifier": "^0.1.1", "styled-components": "^5.3.3", @@ -77,7 +77,8 @@ "resolutions": { "trim": "0.0.3", "diff": "3.5.0", - "prismjs": "1.27.0" + "prismjs": "1.27.0", + "@octokit/types": "8.0.0" }, "volta": { "node": "16.18.1" diff --git a/yarn.lock b/yarn.lock index 135b84bc..0cf62a63 100644 --- a/yarn.lock +++ b/yarn.lock @@ -849,14 +849,7 @@ "@octokit/plugin-request-log" "^1.0.4" "@octokit/plugin-rest-endpoint-methods" "^5.12.0" -"@octokit/types@^6.0.3", "@octokit/types@^6.10.0", "@octokit/types@^6.12.2", "@octokit/types@^6.16.1", "@octokit/types@^6.34.0": - version "6.34.0" - resolved "https://registry.yarnpkg.com/@octokit/types/-/types-6.34.0.tgz#c6021333334d1ecfb5d370a8798162ddf1ae8218" - integrity sha512-s1zLBjWhdEI2zwaoSgyOFoKSl109CUcVBCc7biPJ3aAf6LGLU6szDvi31JPU7bxfla2lqfhjbbg/5DdFNxOwHw== - dependencies: - "@octokit/openapi-types" "^11.2.0" - -"@octokit/types@^8.0.0": +"@octokit/types@8.0.0", "@octokit/types@^6.0.3", "@octokit/types@^6.10.0", "@octokit/types@^6.12.2", "@octokit/types@^6.16.1", "@octokit/types@^6.34.0", "@octokit/types@^8.0.0": version "8.0.0" resolved "https://registry.yarnpkg.com/@octokit/types/-/types-8.0.0.tgz#93f0b865786c4153f0f6924da067fe0bb7426a9f" integrity sha512-65/TPpOJP1i3K4lBJMnWqPUJ6zuOtzhtagDvydAWbEXpbFYA0oMKKyLb95NFZZP0lSh/4b6K+DQlzvYQJQQePg== @@ -4091,6 +4084,11 @@ react-is@^16.12.0, react-is@^16.13.1, react-is@^16.7.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== +react-merge-refs@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/react-merge-refs/-/react-merge-refs-1.1.0.tgz#73d88b892c6c68cbb7a66e0800faa374f4c38b06" + integrity sha512-alTKsjEL0dKH/ru1Iyn7vliS2QRcBp9zZPGoWxUOvRGWPUYgjo+V01is7p04It6KhgrzhJGnIj9GgX8W4bZoCQ== + react-query@^3.34.16: version "3.34.19" resolved "https://registry.yarnpkg.com/react-query/-/react-query-3.34.19.tgz#0ff049b6e0d2ed148e9abfdd346625d0e88dc229" @@ -4142,12 +4140,13 @@ react-style-singleton@^2.1.0: invariant "^2.2.4" tslib "^1.0.0" -react-vtree@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/react-vtree/-/react-vtree-2.0.4.tgz#340e64255f5f4ec6f4c35dc44a7036f7fcd98bc5" - integrity sha512-UOld0VqyAZrryF06K753X4bcEVN6/wW831exvVlMZeZAVHk9KXnlHs4rpqDAeoiBgUwJqoW/rtn0hwsokRRxPA== +react-vtree@3.0.0-beta.3: + version "3.0.0-beta.3" + resolved "https://registry.yarnpkg.com/react-vtree/-/react-vtree-3.0.0-beta.3.tgz#9a2dfc31fa730c39d19b0dff7a9df81ead816bd5" + integrity sha512-BGC8kOT2Ti3rne0Nwu+n90TAo8lbYiWT36Cu47aj6bz+Bs7k5p3EVgBTinyuCdU5+n4a9wJOXHAdop/zsR1RAA== dependencies: "@babel/runtime" "^7.11.0" + react-merge-refs "^1.1.0" react-window@^1.8.7: version "1.8.7"