-
Notifications
You must be signed in to change notification settings - Fork 253
Expand file tree
/
Copy pathpureFileTreePanel.tsx
More file actions
101 lines (92 loc) · 4.68 KB
/
pureFileTreePanel.tsx
File metadata and controls
101 lines (92 loc) · 4.68 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
'use client';
import { FileTreeNode } from "../types";
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
import React, { useCallback, useMemo, useRef } from "react";
import { FileTreeItemComponent } from "./fileTreeItemComponent";
import { getBrowsePath } from "@/app/[domain]/browse/hooks/utils";
import { useBrowseParams } from "@/app/[domain]/browse/hooks/useBrowseParams";
import { useDomain } from "@/hooks/useDomain";
const renderLoadingSkeleton = (depth: number) => {
return (
<div className="flex items-center gap-1 p-0.5 text-sm text-muted-foreground" style={{ paddingLeft: `${depth * 16}px` }}>
<div className="w-5 h-4" />
<div className="h-3 w-3 rounded-full border-2 border-muted-foreground/30 border-t-muted-foreground animate-spin" />
<span>Loading...</span>
</div>
);
}
interface PureFileTreePanelProps {
tree: FileTreeNode;
openPaths: Set<string>;
path: string;
onTreeNodeClicked: (node: FileTreeNode) => void;
}
export const PureFileTreePanel = ({ tree, openPaths, path, onTreeNodeClicked }: PureFileTreePanelProps) => {
const scrollAreaRef = useRef<HTMLDivElement>(null);
const { repoName, revisionName } = useBrowseParams();
const domain = useDomain();
const renderTree = useCallback((nodes: FileTreeNode, depth = 0): React.ReactNode => {
if (!nodes.children || nodes.children.length === 0) {
return null;
}
return (
<>
{nodes.children.map((node) => {
return (
<React.Fragment key={node.path}>
<FileTreeItemComponent
href={getBrowsePath({
repoName,
revisionName,
path: node.path,
pathType: node.type === 'tree' ? 'tree' : 'blob',
domain,
})}
key={node.path}
node={node}
isActive={node.path === path}
depth={depth}
isCollapsed={!openPaths.has(node.path)}
isCollapseChevronVisible={node.type === 'tree'}
// Only collapse the tree when a regular click happens.
// (i.e., not ctrl/cmd click).
onClick={(e) => {
const isMetaOrCtrlKey = e.metaKey || e.ctrlKey;
if (node.type === 'tree' && !isMetaOrCtrlKey) {
onTreeNodeClicked(node);
}
}}
// @note: onNavigate _won't_ be called when the user ctrl/cmd clicks on a tree node.
// So when a regular click happens, we want to prevent the navigation from happening
// and instead collapse the tree.
onNavigate={(e) => {
if (node.type === 'tree') {
e.preventDefault();
}
}}
parentRef={scrollAreaRef}
/>
{node.type === 'tree' && node.children.length > 0 && openPaths.has(node.path) && renderTree(node, depth + 1)}
{/*
@note: a empty tree indicates that the contents are beaing loaded. Render a loading skeleton to indicate that.
This relies on the fact that you cannot have empty tress in git.
@see: https://archive.kernel.org/oldwiki/git.wiki.kernel.org/index.php/GitFaq.html#Can_I_add_empty_directories.3F
*/}
{node.type === 'tree' && node.children.length === 0 && openPaths.has(node.path) && renderLoadingSkeleton(depth)}
</React.Fragment>
);
})}
</>
);
}, [domain, onTreeNodeClicked, path, repoName, revisionName, openPaths]);
const renderedTree = useMemo(() => renderTree(tree), [tree, renderTree]);
return (
<ScrollArea
className="h-full w-full overflow-auto p-0.5"
ref={scrollAreaRef}
>
{renderedTree}
<ScrollBar orientation="horizontal" />
</ScrollArea>
)
}