FileTreeTable.tsx
Raw
1import type { TreeEntry } from "../../services/git.ts";
2
3interface FileTreeTableProps {
4 repoName: string;
5 treeRef: string;
6 subpath: string;
7 entries: TreeEntry[];
8}
9
10export function FileTreeTable({
11 repoName,
12 treeRef,
13 subpath,
14 entries,
15}: FileTreeTableProps) {
16 const parts = subpath ? subpath.split("/") : [];
17 const parentPath = parts.slice(0, -1).join("/");
18 const parentHref = parentPath
19 ? `/${repoName}/tree/${treeRef}/${parentPath}`
20 : `/${repoName}/tree/${treeRef}`;
21
22 const sorted = [...entries].sort((a, b) => {
23 if (a.type !== b.type) return a.type === "tree" ? -1 : 1;
24 return a.name.localeCompare(b.name);
25 });
26
27 return (
28 <table class="file-tree">
29 <tbody>
30 {!!subpath && (
31 <tr class="file-tree-row file-tree-row-up">
32 <td class="file-icon file-icon-dir">{DirIcon()}</td>
33 <td class="file-name" colspan="2">
34 <a href={parentHref}>..</a>
35 </td>
36 </tr>
37 )}
38 {sorted.map((entry) => {
39 const entryPath = subpath
40 ? `${subpath}/${entry.name}`
41 : entry.name;
42 const href =
43 entry.type === "tree"
44 ? `/${repoName}/tree/${treeRef}/${entryPath}`
45 : `/${repoName}/blob/${treeRef}/${entryPath}`;
46 return (
47 <tr class="file-tree-row">
48 <td
49 class={`file-icon ${entry.type === "tree" ? "file-icon-dir" : "file-icon-file"}`}
50 >
51 {entry.type === "tree" ? DirIcon() : FileIcon()}
52 </td>
53 <td class="file-name">
54 <a
55 href={href}
56 class={
57 entry.type === "tree"
58 ? "file-name-dir"
59 : undefined
60 }
61 safe
62 >
63 {entry.name}
64 </a>
65 </td>
66 <td class="file-size">
67 {entry.type === "blob" && entry.size !== "-"
68 ? formatSize(parseInt(entry.size, 10))
69 : ""}
70 </td>
71 </tr>
72 );
73 })}
74 </tbody>
75 </table>
76 );
77}
78
79function formatSize(bytes: number): string {
80 if (bytes < 1024) return `${bytes} B`;
81 if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
82 return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
83}
84
85function DirIcon(): JSX.Element {
86 return `<svg class="tree-icon" width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true"><path d="M1.75 1A1.75 1.75 0 0 0 0 2.75v10.5C0 14.216.784 15 1.75 15h12.5A1.75 1.75 0 0 0 16 13.25v-8.5A1.75 1.75 0 0 0 14.25 3H7.5a.25.25 0 0 1-.2-.1l-.9-1.2C6.07 1.26 5.55 1 5 1H1.75Z"/></svg>`;
87}
88
89function FileIcon(): JSX.Element {
90 return `<svg class="tree-icon" width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true"><path d="M2 1.75C2 .784 2.784 0 3.75 0h6.586c.464 0 .909.184 1.237.513l2.914 2.914c.329.328.513.773.513 1.237v9.586A1.75 1.75 0 0 1 13.25 16h-9.5A1.75 1.75 0 0 1 2 14.25Zm1.75-.25a.25.25 0 0 0-.25.25v12.5c0 .138.112.25.25.25h9.5a.25.25 0 0 0 .25-.25V6h-2.75A1.75 1.75 0 0 1 9 4.25V1.5Zm6.75.062V4.25c0 .138.112.25.25.25h2.688l-.011-.013-2.914-2.914-.013-.011Z"/></svg>`;
91}
92