2.6 KB84 lines
Blame
1import type { Metadata } from "next";
2import Link from "next/link";
3import { FileIcon } from "@/app/components/file-icon";
4import { encodePath } from "@/lib/utils";
5import { getRepoTree } from "@/lib/grove-api";
6
7interface Props {
8 params: Promise<{ owner: string; repo: string; path: string[] }>;
9}
10
11export async function generateMetadata({ params }: Props): Promise<Metadata> {
12 const { repo, path: pathParts } = await params;
13 const path = pathParts.slice(1).join("/");
14 return { title: `${path || "/"} · ${repo}` };
15}
16
17
18function sortEntries(entries: any[]) {
19 return [...entries].sort((a, b) => {
20 if (a.type === b.type) return a.name.localeCompare(b.name);
21 return a.type === "tree" ? -1 : 1;
22 });
23}
24
25export default async function TreePage({ params }: Props) {
26 const { owner, repo, path: pathParts } = await params;
27 const ref = pathParts[0] ?? "main";
28 const path = pathParts.slice(1).join("/");
29
30 const tree = await getRepoTree(owner, repo, ref, path);
31
32 if (!tree) {
33 return (
34 <div className="max-w-3xl mx-auto px-4 py-16">
35 <h1 className="text-lg" style={{ color: "var(--text-secondary)" }}>
36 Path not found
37 </h1>
38 </div>
39 );
40 }
41
42 const parts = path.split("/");
43
44 return (
45 <div className="px-4 py-6">
46 <div className="text-sm" style={{ border: "1px solid var(--border-subtle)" }}>
47 <Link
48 href={
49 parts.length > 1
50 ? `/${owner}/${repo}/tree/${ref}/${encodePath(parts.slice(0, -1).join("/"))}`
51 : `/${owner}/${repo}`
52 }
53 className="flex items-center gap-2 py-1.5 pl-3 pr-3 hover-row"
54 style={{
55 borderBottom: "1px solid var(--divide)",
56 color: "var(--text-muted)",
57 }}
58 >
59 <FileIcon type="tree" />
60 <span className="hover:underline">..</span>
61 </Link>
62 {sortEntries(tree.entries).map((entry: any) => (
63 <Link
64 key={entry.name}
65 href={
66 entry.type === "tree"
67 ? `/${owner}/${repo}/tree/${ref}/${encodePath(path)}/${encodePath(entry.name)}`
68 : `/${owner}/${repo}/blob/${ref}/${encodePath(path)}/${encodePath(entry.name)}`
69 }
70 className="flex items-center gap-2 py-1.5 pl-3 pr-3 hover-row"
71 style={{
72 borderTop: "1px solid var(--divide)",
73 color: "var(--accent)",
74 }}
75 >
76 <FileIcon type={entry.type} name={entry.name} />
77 <span className="hover:underline">{entry.name}</span>
78 </Link>
79 ))}
80 </div>
81 </div>
82 );
83}
84