<script lang="ts">
// Every project in the workshop and the worktrees of each, and the
// worktrees outside every project.
//
// Picking a worktree makes it the context: the right sidebar works in it.
// Picking a project opens its page. The rows say only what tells worktrees
// apart at a glance — the branch and whether there are changes — and the
// detail lives on the project's page.
import { onMount } from "svelte";
import { Badge, EmptyState, IconButton, Notice, Panel, Tree, TreeItem } from "@wsb/uikit/components";
import { keyed } from "@wsb/uikit/handles";
import type { Worktree } from "../../api/types/levels.js";
import { context } from "../../stores/context.svelte.js";
import { levels } from "../../stores/levels.svelte.js";
import { workbench } from "../../stores/workbench.svelte.js";
let folded = $state<Record<string, boolean>>({});
onMount(() => {
void levels.ensureLoaded();
});
function detailOf(worktree: Worktree): string {
if (levels.opening === worktree.id) return "opening…";
const branch = worktree.branch ?? "detached";
return worktree.dirty > 0 ? `${branch} · ${worktree.dirty} changed` : branch;
}
</script>
{#snippet actions()}
<IconButton handle="workshop.projects.refresh" icon="refresh" label="Read the projects again" busy={levels.loading} onpress={() => void levels.load()} />
<IconButton handle="workshop.projects.open" icon="open-external" label="Open every project" onpress={() => workbench.openProjects()} />
{/snippet}
{#snippet worktreeRow(worktree: Worktree, depth: number)}
<TreeItem
handle={keyed("workshop.worktree", worktree.id)}
label={worktree.name}
{depth}
icon="worktree"
detail={detailOf(worktree)}
current={context.worktree === worktree.id}
onpress={() => void context.enter(worktree.id)}
/>
{/snippet}
<Panel title="Projects" handle="workshop.projects" {actions}>
{#if levels.error}
<Notice tone="danger" title="The projects could not be read" detail={levels.error} />
{/if}
{#if levels.openFailure}
<Notice tone="danger" title={`${levels.openFailure.worktree} could not be opened`} detail={levels.openFailure.message} />
{/if}
{#if levels.loaded && levels.projects.length === 0 && levels.standalone.length === 0}
<EmptyState icon="projects" title="No projects" detail="Clone a project into the workshop to see it here." />
{:else}
<Tree ariaLabel="Projects">
{#each levels.projects as project (project.name)}
{@const open = !(folded[project.name] ?? false)}
{#snippet count()}<Badge label={String(project.worktrees.length)} title={`${project.worktrees.length} worktrees`} />{/snippet}
<TreeItem
handle={keyed("workshop.project", project.name)}
label={project.name}
depth={0}
icon="project"
expanded={open}
current={context.project === project.name}
meta={count}
ontoggle={(expanded) => { folded = { ...folded, [project.name]: !expanded }; }}
onpress={() => workbench.openProject(project.name, { transient: true })}
/>
{#if open}
{#each project.worktrees as worktree (worktree.id)}
{@render worktreeRow(worktree, 1)}
{/each}
{/if}
{/each}
{#each levels.standalone as worktree (worktree.id)}
{@render worktreeRow(worktree, 0)}
{/each}
</Tree>
{/if}
</Panel>