Something went wrong. Try again.
Monorepo for Tangled
Something went wrong. Try again.
Svelte
at icy/tmrrpn
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596<script module lang="ts"> interface TreeDir { type: "dir"; name: string; path: string; children: TreeNode[]; }
interface TreeFile { type: "file"; name: string; path: string; }
type TreeNode = TreeDir | TreeFile;
// first-seen order (the diff's file order), no re-sorting const buildTree = (paths: string[]): TreeNode[] => { const roots: TreeNode[] = []; const dirs: Record<string, TreeDir> = Object.create(null); for (const path of paths) { const parts = path.split("/"); let siblings = roots; let prefix = ""; for (const dir of parts.slice(0, -1)) { prefix = prefix ? `${prefix}/${dir}` : dir; let node = dirs[prefix]; if (!node) { node = { type: "dir", name: dir, path: prefix, children: [] }; dirs[prefix] = node; siblings.push(node); } siblings = node.children; } siblings.push({ type: "file", name: parts.at(-1) ?? path, path }); } return roots; };</script>
<script lang="ts"> import File from "$icon/file"; import Folder from "$icon/folder"; import FolderOpen from "$icon/folder-open";
interface Props { // full slash-separated paths, anchors point at `#file-<path>` files: { name: string }[]; }
let { files }: Props = $props();
const tree = $derived(buildTree(files.map((file) => file.name)));
// the appview names a tailwind group per nesting level (`group/level-3`) so a // folder icon only follows its own <details>; tracking open state per path is // the same result without the twelve-deep safelist let closed = $state<Record<string, boolean>>({});</script>
{#snippet nodes(list: TreeNode[])} {#each list as node (node.path)} {#if node.type === "dir"} {@const open = !closed[node.path]} <details {open} ontoggle={(e) => (closed[node.path] = !e.currentTarget.open)}> <summary class="cursor-pointer list-none pt-1"> <span class="tree-directory inline-flex items-center gap-2"> {#if open} <FolderOpen class="size-4 shrink-0" aria-hidden="true" /> {:else} <Folder class="size-4 shrink-0" aria-hidden="true" /> {/if} <span class="filename truncate text-foreground-default">{node.name}</span> </span> </summary> <div class="ml-1 border-l border-border-default pl-2"> {@render nodes(node.children)} </div> </details> {:else} <div class="tree-file flex items-center gap-2 pt-1"> <File class="size-4 shrink-0" aria-hidden="true" /> <a href="#file-{node.path}" data-path={node.path} class="filetree-link filename truncate text-foreground-default no-underline hover:underline" > {node.name} </a> </div> {/if} {/each}{/snippet}
{@render nodes(tree)}