g1t/apps/web/app/components/repo-view.tsx

146 lines4,658 bytesCodeBlame

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Initial g1t: services, event bus, intents and attempts1import { Link } from "react-router";
2
3import type { BlobView as Blob, TreeView as Tree } from "@g1t/contracts";
4
5function encodePath(path: string): string {
6 return path.split("/").map(encodeURIComponent).join("/");
7}
8
9function Breadcrumbs({
10 base,
11 repo,
12 gitRef,
13 path,
14}: {
15 base: string;
16 repo: string;
17 gitRef: string;
18 path: string;
19}) {
20 const segments = path.split("/").filter(Boolean);
21 if (segments.length === 0) return null;
22 return (
23 <p className="mb-4 font-mono text-sm text-muted">
24 <Link to={`${base}/tree/${gitRef}`} className="hover:text-fg">
25 {repo}
26 </Link>
27 {segments.map((segment, i) => {
28 const to = encodePath(segments.slice(0, i + 1).join("/"));
29 const last = i === segments.length - 1;
30 return (
31 <span key={to}>
32 {" / "}
33 {last ? (
34 <span className="text-fg">{segment}</span>
35 ) : (
36 <Link to={`${base}/tree/${gitRef}/${to}`} className="hover:text-fg">
37 {segment}
38 </Link>
39 )}
40 </span>
41 );
42 })}
43 </p>
44 );
45}
46
47export function TreeView({ tree }: { tree: Tree }) {
48 const { repo, ref, path, head, entries, readme } = tree;
49 const base = `/${repo.namespace}/${repo.name}`;
50 const prefix = path ? `${encodePath(path)}/` : "";
51 const cloneUrl = `https://g1t.sh${base}.git`;
52
53 if (!head) {
54 return (
55 <div className="mt-6 rounded-md border border-line bg-surface p-6">
56 <p className="text-muted">This repository is empty. Push to it:</p>
57 <pre className="mt-3 overflow-x-auto font-mono text-sm">
58 git remote add origin {cloneUrl}
59 {"\n"}git push -u origin {ref}
60 </pre>
61 </div>
62 );
63 }
64
65 return (
66 <div className="mt-6">
67 <Breadcrumbs base={base} repo={repo.name} gitRef={ref} path={path} />
68 <div className="overflow-hidden rounded-md border border-line">
69 <div className="flex items-baseline gap-3 border-b border-line bg-surface px-4 py-2 text-sm">
70 <span className="font-mono text-accent">{ref}</span>
71 <span className="truncate">{head.message.split("\n")[0]}</span>
72 <span className="ml-auto shrink-0 font-mono text-muted">
73 {head.author.name} · {head.hash.slice(0, 7)}
74 </span>
75 </div>
76 <ul className="divide-y divide-line font-mono text-sm">
77 {entries.map((entry) => {
78 const isTree = entry.kind === "tree";
79 const kind = isTree ? "tree" : "blob";
80 return (
81 <li key={entry.name}>
82 <Link
83 to={`${base}/${kind}/${ref}/${prefix}${encodeURIComponent(entry.name)}`}
84 className="block px-4 py-1.5 hover:bg-surface"
85 >
86 {entry.name}
87 {isTree && <span className="text-muted">/</span>}
88 </Link>
89 </li>
90 );
91 })}
92 </ul>
93 </div>
94
95 {!path && (
96 <pre className="mt-4 overflow-x-auto rounded-md border border-line bg-surface px-4 py-2 font-mono text-sm">
97 git clone {cloneUrl}
98 </pre>
99 )}
100
101 {readme?.text != null && (
102 <section className="mt-6 rounded-md border border-line">
103 <h2 className="border-b border-line bg-surface px-4 py-2 font-mono text-sm text-muted">
104 {readme.name}
105 </h2>
106 <pre className="whitespace-pre-wrap p-4 text-sm">{readme.text}</pre>
107 </section>
108 )}
109 </div>
110 );
111}
112
113export function BlobView({ blob }: { blob: Blob }) {
114 const { repo, ref, path, size, text } = blob;
115 const lines = text?.replace(/\n$/, "").split("\n");
116 return (
117 <div className="mt-6">
118 <Breadcrumbs
119 base={`/${repo.namespace}/${repo.name}`}
120 repo={repo.name}
121 gitRef={ref}
122 path={path}
123 />
124 <div className="overflow-hidden rounded-md border border-line">
125 <div className="border-b border-line bg-surface px-4 py-2 font-mono text-sm text-muted">
126 {size.toLocaleString("en-US")} bytes
127 </div>
128 {lines ? (
129 <div className="flex overflow-x-auto font-mono text-sm leading-6">
130 <pre
131 aria-hidden="true"
132 className="select-none px-4 py-3 text-right text-muted"
133 >
134 {lines.map((_, i) => i + 1).join("\n")}
135 </pre>
136 <pre className="py-3 pr-4">{lines.join("\n")}</pre>
137 </div>
138 ) : (
139 <p className="p-4 text-sm text-muted">
140 Binary or large file not shown.
141 </p>
142 )}
143 </div>
144 </div>
145 );
146}