Skip to content
This repository has been archived by the owner on Jan 10, 2024. It is now read-only.

Commit

Permalink
file tree: open relevant folders on nav
Browse files Browse the repository at this point in the history
includes: upgrade react-vtree to v3
  • Loading branch information
Wattenberger committed Dec 7, 2022
1 parent 93837a5 commit 22c9911
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 98 deletions.
159 changes: 75 additions & 84 deletions components/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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(() => {
Expand All @@ -106,80 +109,67 @@ 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);
}
}
},
[
filteredFiles,
updatedContents,
numberOfFiles < 20,
activeFilePath,
query,
query.branchPath.join("/"),
branchName,
]
);

if (!files.map) return null;

return (
Expand All @@ -202,6 +192,7 @@ export const Sidebar = ({
height={dimensions[1]}
width={dimensions[0]}
className="pb-10"
ref={tree}
>
{Node}
</Tree>
Expand All @@ -218,7 +209,6 @@ export const Sidebar = ({

const Node = ({
data: {
isLeaf,
name,
path,
activeFilePath,
Expand All @@ -228,10 +218,11 @@ const Node = ({
currentPathname,
currentQuery,
currentBranch,
isLeaf,
},
isOpen,
style,
toggle,
setOpen,
}) => {
return (
<div style={style} key={path || "/"}>
Expand All @@ -245,7 +236,7 @@ const Node = ({
currentPathname={currentPathname}
currentQuery={currentQuery}
currentBranch={currentBranch}
toggle={toggle}
setOpen={setOpen}
isOpen={isOpen}
isFolder={!isLeaf}
/>
Expand All @@ -265,7 +256,7 @@ type ItemProps = {
canCollapse?: boolean;
isOpen: boolean;
updatedContents: Record<string, unknown>;
toggle: () => void;
setOpen: (newState: boolean) => void;
};

const Item = ({
Expand All @@ -279,7 +270,7 @@ const Item = ({
currentBranch,
isOpen,
updatedContents,
toggle,
setOpen,
}: ItemProps) => {
const isActive = activeFilePath === path;

Expand Down Expand Up @@ -312,7 +303,7 @@ const Item = ({
// don't select the folder on toggle
e.stopPropagation();
e.preventDefault();
toggle();
setOpen(!isOpen);
}}
>
{depth > 0 && (
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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"
Expand Down
23 changes: 11 additions & 12 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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==
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down

0 comments on commit 22c9911

Please sign in to comment.