File
Blob: src/client/pages/TreePage.tsx
| 1 | import type { FileIconName } from "@/shared/web"; |
| 2 | import { Breadcrumbs } from "@/client/components/Breadcrumbs"; |
| 3 | import { EmptyState } from "@/client/components/EmptyState"; |
| 4 | import { FileIcon } from "@/client/components/FileIcon"; |
| 5 | import { type Progress, ProgressBanner } from "@/client/components/ProgressBanner"; |
| 6 | import { RepoNav } from "@/client/components/RepoNav"; |
| 7 | |
| 8 | type TreeEntry = { |
| 9 | name: string; |
| 10 | href: string; |
| 11 | isDir: boolean; |
| 12 | isSymlink: boolean; |
| 13 | iconName: FileIconName; |
| 14 | shortOid: string; |
| 15 | }; |
| 16 | |
| 17 | type Breadcrumb = { |
| 18 | name: string; |
| 19 | href: string | null; |
| 20 | }; |
| 21 | |
| 22 | export type TreePageProps = { |
| 23 | owner: string; |
| 24 | repo: string; |
| 25 | refEnc: string; |
| 26 | entries: TreeEntry[]; |
| 27 | breadcrumbs?: Breadcrumb[]; |
| 28 | parentHref?: string | null; |
| 29 | progress?: Progress; |
| 30 | }; |
| 31 | |
| 32 | function getTreeEntryIconClass(entry: TreeEntry): string { |
| 33 | if (entry.isDir) { |
| 34 | return "text-amber-600 dark:text-amber-400"; |
| 35 | } |
| 36 | if (entry.isSymlink) { |
| 37 | return "text-accent-500 dark:text-accent-400"; |
| 38 | } |
| 39 | return "text-zinc-500 dark:text-zinc-400"; |
| 40 | } |
| 41 | |
| 42 | export function TreePage({ |
| 43 | owner, |
| 44 | repo, |
| 45 | refEnc, |
| 46 | entries, |
| 47 | breadcrumbs, |
| 48 | parentHref, |
| 49 | progress, |
| 50 | }: TreePageProps) { |
| 51 | return ( |
| 52 | <> |
| 53 | <RepoNav owner={owner} repo={repo} refEnc={refEnc} currentTab="browse" /> |
| 54 | <ProgressBanner progress={progress} /> |
| 55 | <div> |
| 56 | <span className="mb-1 inline-block text-xs font-semibold uppercase tracking-wider text-accent-500 dark:text-accent-400"> |
| 57 | Browse |
| 58 | </span> |
| 59 | <h2 className="font-display tracking-tight">Tree</h2> |
| 60 | <Breadcrumbs items={breadcrumbs} parentHref={parentHref} /> |
| 61 | <table className="mt-4 overflow-hidden rounded-2xl border border-zinc-200 shadow-xs dark:border-zinc-800/60"> |
| 62 | <thead> |
| 63 | <tr> |
| 64 | <th>Name</th> |
| 65 | <th>OID</th> |
| 66 | </tr> |
| 67 | </thead> |
| 68 | <tbody> |
| 69 | {entries.length ? ( |
| 70 | entries.map((entry) => ( |
| 71 | <tr key={`${entry.href}-${entry.name}`}> |
| 72 | <td> |
| 73 | <FileIcon |
| 74 | name={entry.iconName} |
| 75 | className={`mr-1.5 inline-block h-4 w-4 align-[-2px] ${getTreeEntryIconClass(entry)}`} |
| 76 | />{" "} |
| 77 | <a href={entry.href}>{entry.name}</a> |
| 78 | </td> |
| 79 | <td className="text-zinc-500 dark:text-zinc-400">{entry.shortOid}</td> |
| 80 | </tr> |
| 81 | )) |
| 82 | ) : ( |
| 83 | <tr> |
| 84 | <td colSpan={2}> |
| 85 | <EmptyState |
| 86 | icon={ |
| 87 | <FileIcon |
| 88 | name="folder" |
| 89 | className="h-5 w-5 text-zinc-500 dark:text-zinc-400" |
| 90 | /> |
| 91 | } |
| 92 | title="This tree is empty" |
| 93 | /> |
| 94 | </td> |
| 95 | </tr> |
| 96 | )} |
| 97 | </tbody> |
| 98 | </table> |
| 99 | </div> |
| 100 | </> |
| 101 | ); |
| 102 | } |