1import { type Params, useParams } from "@solidjs/router";
2import { createMemo, createResource, onMount, Show } from "solid-js";
3import { figureOutDid } from "../../../util/handle";
4import type { Compare, DID } from "../../../util/types";
5import { Header } from "../components/header";
6import { useDid } from "../context";
7import { getRepoCommit, getRepoCompare } from "../main.data";
8import { buildTree, type TreeNode } from "./data";
9import { DiffView, Sidebar } from "./generic";
10
11export async function preloadRepoCompare({ params }: { params: Params }) {
12 const did = await figureOutDid(params.user!);
13 if (!did) return;
14 getRepoCommit(did, params.repo!, params.ref!);
15}
16
17export default function RepoCompare() {
18 const params = useParams() as {
19 user: string;
20 repo: string;
21 base: string;
22 compare: string;
23 };
24 const did = useDid();
25
26 const [compare] = createResource(
27 () => {
28 const d = did();
29 if (!d) return;
30 return [d, params.repo, params.base, params.compare] as const;
31 },
32 async ([did, repo, base, compare]) => {
33 const res = await getRepoCompare(did as DID, repo, base, compare);
34 if (!res.ok) return;
35 return res.data as Compare;
36 },
37 );
38
39 const compareDiffViewInput = createMemo(() => {
40 if (!compare()?.combined_patch) return;
41 return compare()!.combined_patch!.map((diff) => ({
42 oldName: diff.OldName,
43 newName: diff.NewName,
44 textFragments: diff.TextFragments || [],
45 }));
46 });
47
48 const sidebar = createMemo(() => {
49 if (!compare()?.combined_patch)
50 return { name: "", fullPath: "", type: "directory" } as TreeNode;
51 const set = new Set(
52 compare()!.combined_patch!.flatMap((file) => [
53 file.NewName,
54 file.OldName,
55 ]),
56 );
57 set.delete("");
58 return buildTree(set);
59 });
60
61 const compareNumbers = createMemo(() => {
62 if (!compare()?.combined_patch) return { insertions: 0, deletions: 0 };
63 let [insertions, deletions] = [0, 0];
64 compare()!.combined_patch!.forEach((file) => {
65 if (!file.TextFragments) return;
66 file.TextFragments.forEach((fragment) => {
67 insertions += fragment.LinesAdded;
68 deletions += fragment.LinesDeleted;
69 });
70 });
71 return { insertions, deletions };
72 });
73
74 onMount(() => {
75 if (window.location.hash) {
76 const element = document.getElementById(window.location.hash.slice(1));
77 if (element)
78 element.scrollIntoView({ behavior: "instant", block: "start" });
79 }
80 });
81
82 return (
83 <div class="mx-auto max-w-10xl">
84 <Show when={compare()}>
85 <CommitHeader user={params.user} repo={params.repo} />
86 </Show>
87 <Show when={sidebar()?.children && compare() && compareDiffViewInput()}>
88 <div class="flex flex-col gap-1 md:flex-row">
89 <Sidebar
90 sidebar={sidebar()}
91 insertions={compareNumbers().insertions}
92 deletions={compareNumbers().deletions}
93 />
94 <div class="min-w-0 flex-1 flex-col gap-1 p-1 max-md:pt-0 md:pl-0">
95 <DiffView diff={compareDiffViewInput()!} />
96 </div>
97 </div>
98 </Show>
99 </div>
100 );
101}
102
103function CommitHeader(props: { user: string; repo: string }) {
104 return (
105 <div>
106 <Header user={props.user} repo={props.repo} />
107 </div>
108 );
109}