<!--
The boards — every GitHub Projects board of the accounts — the most
recently changed first, as many across as the group is wide. Each says what
is happening on it: its latest cards, the column each is in, and when it
changed, so a board need not be opened to see what moved.
A board belongs to no project; the projects that work from one are named on
it. The boards are the workshop's mirror, kept current by its GitHub stream,
so nothing here needs reloading.
-->
<script lang="ts">
import { onMount } from "svelte";
import { Button, Card, CardGrid, EmptyState, List, ListItem, Notice, Page, Spinner, Tag } from "@wsb/uikit/components";
import { keyed } from "@wsb/uikit/handles";
import { boards } from "../../stores/boards.svelte.js";
import { workbench } from "../../stores/workbench.svelte.js";
import { columnName, itemKind, type Project, type RecentCard } from "../../api/types/github.js";
import { formatRelativeTime } from "../../utils/format.js";
onMount(() => {
void boards.load();
return boards.watch();
});
const ICONS = { issue: "issue", "pull-request": "pull-request", draft: "notes" } as const;
function openBoard(board: Project, event: MouseEvent) {
workbench.openBoard(board.owner, board.number, { beside: event.altKey });
}
function openCard(board: Project, card: RecentCard, event: MouseEvent) {
boards.showCard(board.owner, board.number, card.id);
workbench.openBoard(board.owner, board.number, { beside: event.altKey });
}
/** Which card it is and when it changed: `#42 · 2h ago`, `draft · 2h ago`. */
function about(card: RecentCard): string {
const which = card.number !== null ? `#${card.number}` : itemKind(card) === "draft" ? "draft" : "";
return [which, formatRelativeTime(card.updated_at)].filter(Boolean).join(" · ");
}
</script>
{#snippet refresh()}
<Button
handle="page.boards.refresh"
label={boards.refreshing ? "Asking GitHub…" : "Refresh"}
variant="secondary"
busy={boards.refreshing}
onpress={() => void boards.refresh()}
/>
{/snippet}
<Page title="Boards" detail="Every board, the most recently changed first" handle="page.boards" actions={refresh}>
{#if boards.noToken}
<Notice
tone="warning"
title="There is no GitHub login on this host"
detail="The boards shown are the last ones read. Log in with `gh auth login`."
/>
{/if}
{#each boards.problems as problem (problem)}
<Notice tone="warning" title="A board could not be listed" detail={problem} />
{/each}
{#if boards.error}
<Notice tone="danger" title="The boards could not be read" detail={boards.error} />
{/if}
{#if boards.loading && boards.projects.length === 0}
<Spinner label="Reading the boards" />
{:else if boards.projects.length === 0}
<EmptyState
icon="boards"
title="No boards"
detail="They are read from GitHub with the read:project scope on the GitHub login (gh auth login, or GITHUB_TOKEN); a token without it sees none."
/>
{/if}
<CardGrid ariaLabel="Boards">
{#each boards.byActivity as { board, activity } (board.id)}
{@const using = boards.projectsUsing(board)}
{#snippet projects()}
{#each using as project (project)}<Tag label={project} icon="project" />{/each}
{/snippet}
<Card
handle={keyed("page.board", `${board.owner}/${board.number}`)}
title={board.title}
detail={[
`${board.owner} · #${board.number}`,
board.closed ? "closed" : null,
activity.newest_change ? `changed ${formatRelativeTime(activity.newest_change)}` : null,
]
.filter(Boolean)
.join(" · ")}
icon="board"
muted={board.closed}
meta={using.length > 0 ? projects : undefined}
onpress={(event) => openBoard(board, event)}
>
{#if activity.latest.length > 0}
<List ariaLabel={`Latest on ${board.title}`}>
{#each activity.latest as card (card.id)}
{@const column = columnName(card, board)}
{#snippet where()}{#if column}<Tag label={column} />{/if}{/snippet}
<ListItem
handle={keyed("page.board.card", card.id)}
title={card.title}
detail={about(card)}
icon={ICONS[itemKind(card)]}
meta={column ? where : undefined}
onpress={(event) => openCard(board, card, event)}
/>
{/each}
</List>
{:else}
<EmptyState title={activity.cards.ever_synced ? "No cards" : "Cards not read yet"} />
{/if}
</Card>
{/each}
</CardGrid>
</Page>