johnandersen777.bsky.social/knotless
git that lives in your Atmosphere account
Forked from chadtmiller.com/knotless
0
0
<script lang="ts">
import type { FileDiffView } from '#lib/lexicons/dev/knotless/defs.ts';
import { pathOf } from '#lib/diffs.ts';
import Icon from '#lib/atoms/Icon.svelte';
/**
* A diff's files as a tree of their directories, each a link to its
* file's diff (`href` of its index). Each directory lists what it holds
* in the order the diffs come, so the tree read down is the diffs read
* down. A directory with one directory in it, and nothing else, goes by
* both their names. A file with comments on its lines says how many, as
* `comments` counts them. `onpick` hears which file was clicked.
*/
let {
files,
href,
onpick,
comments
}: {
files: FileDiffView[];
href: (i: number) => string;
onpick?: (i: number) => void;
comments?: (f: FileDiffView) => number;
} = $props();
type File = { name: string; i: number; f: FileDiffView };
type Dir = { name: string; entries: (Dir | File)[] };
const isDir = (e: Dir | File): e is Dir => 'entries' in e;
const tree = $derived.by(() => {
const root: Dir = { name: '', entries: [] };
for (const [i, f] of files.entries()) {
const parts = pathOf(f).split('/');
const name = parts.pop() ?? '';
let at = root;
for (const part of parts) {
let next = at.entries.find((e): e is Dir => isDir(e) && e.name === part);
if (!next) at.entries.push((next = { name: part, entries: [] }));
at = next;
}
at.entries.push({ name, i, f });
}
const folded = (e: Dir | File): Dir | File => {
if (!isDir(e)) return e;
let d = e;
while (d.entries.length === 1 && isDir(d.entries[0]))
d = { ...d.entries[0], name: `${d.name}/${d.entries[0].name}` };
return { ...d, entries: d.entries.map(folded) };
};
return { ...root, entries: root.entries.map(folded) };
});
</script>
{#snippet dir(d: Dir)}
{#each d.entries as e (isDir(e) ? `d:${e.name}` : e.i)}
<li>
{#if isDir(e)}
<details open>
<summary><Icon name="dir" /><span class="name">{e.name}</span></summary>
<ul>{@render dir(e)}</ul>
</details>
{:else}
<a href={href(e.i)} title={pathOf(e.f)} onclick={() => onpick?.(e.i)}
><Icon name="file" /><span class="name">{e.name}</span>{#if comments?.(e.f)}{@const n =
comments(e.f)}<span class="said" aria-label="{n} {n === 1 ? 'comment' : 'comments'}"
><Icon name="chat" />{n}</span
>{/if}<span class="add">+{e.f.added}</span><span class="rem">−{e.f.removed}</span></a
>
{/if}
</li>
{/each}
{/snippet}
<nav class="file-tree" aria-label="Changed files"><ul>{@render dir(tree)}</ul></nav>