Watch
0
0
Fork
You've already forked hyperhive
0

swarm-ui: the schedules, notification flows and flow editor show placeholders while they load

The schedules card, the notification router card (flows and recent
deliveries) and the flow editor showed a bare 'loading…' line. They now show
grey placeholders in the layout of what is coming: schedule cards with the
real card's row heights, flow cards with a block for the graph, the
deliveries table's header over blank rows, and lines for the editor.
This commit is contained in:
iris 2026-11-06 19:26:20 +01:00 • committed by mara
commit 5eef1f4a3d
4 changed files with 111 additions and 7 deletions

View file

@ -4,6 +4,7 @@
import { EmptyState } from "../../ui/empty-state/EmptyState.js";
import { RelativeTime } from "../../ui/relative-time/RelativeTime.js";
import { Table, type TableColumn } from "../../ui/table/Table.js";
import { TableSkeleton } from "../../ui/table/TableSkeleton.js";
import { issueRef, outcomeText, type Delivery } from "./RouterLive.js";
// A delivery with its place in the answer, which is what tells two identical rows
@ -48,6 +49,11 @@ const COLUMNS: TableColumn<Row>[] = [
},
];
/** The table's twin while the deliveries load. */
export function DeliveriesSkeleton() {
return <TableSkeleton columns={COLUMNS} label="the deliveries" />;
}
export function Deliveries({ rows }: { rows: Delivery[] }) {
if (rows.length === 0) return <EmptyState>no deliveries yet</EmptyState>;
const placed = rows.map((d, place) => ({ ...d, place }));

View file

@ -26,6 +26,7 @@ import { ActionRow } from "../../ui/action-row/ActionRow.js";
import { ConfirmDialog } from "../../ui/confirm-dialog/ConfirmDialog.js";
import { Hint } from "../../ui/hint/Hint.js";
import { Panel } from "../../ui/panel/Panel.js";
import { SkeletonLines } from "../../ui/skeleton/Skeleton.js";
import { TextField } from "../../ui/text-field/TextField.js";
import { fetchJson, ProblemError } from "../../ui/polled/usePolled.js";
import { FlowCanvas } from "./FlowCanvas.js";
@ -248,7 +249,7 @@ export function FlowEditorPage({
if (!view) {
return (
<Panel title="notification flow" icon="🔔" actions={back}>
<p>loading…</p>
<SkeletonLines label="the flow" />
</Panel>
);
}

View file

@ -0,0 +1,30 @@
// The twin of the flow cards for a list that is loading: the same card and
// head layout (`notify-flow-head`), a placeholder for the name and the
// buttons, and a block where the flow's graph goes. The list is in the
// container the real one uses (`notify-flows`), so the gap is the same.
import { Card } from "../../ui/card/Card.js";
import { Skeleton, SkeletonRegion } from "../../ui/skeleton/Skeleton.js";
function FlowCardSkeleton() {
return (
<Card>
<div class="notify-flow-head">
<Skeleton width="12em" class="notify-flow-name" />
<Skeleton width="9em" height="2.4em" />
</div>
<Skeleton shape="block" width="100%" height="6em" />
</Card>
);
}
export function FlowListSkeleton({ count = 3 }: { count?: number }) {
return (
<SkeletonRegion label="the flows">
<div class="notify-flows">
{Array.from({ length: count }, (_, i) => (
<FlowCardSkeleton key={i} />
))}
</div>
</SkeletonRegion>
);
}

View file

@ -20,7 +20,8 @@ import {
type RefreshIntervalMs,
} from "../../ui/refresh-interval/RefreshInterval.js";
import { fetchJson, usePolled } from "../../ui/polled/usePolled.js";
import { Deliveries } from "./Deliveries.js";
import { Deliveries, DeliveriesSkeleton } from "./Deliveries.js";
import { FlowListSkeleton } from "./FlowListSkeleton.js";
import { NEW_FLOW_PATH } from "./FlowDraft.js";
import { FlowGraph } from "./FlowGraph.js";
import { withBuiltin, type FlowsView } from "./FlowTypes.js";
@ -124,7 +125,7 @@ export function NotificationRouter() {
problem={actionError}
/>
) : null}
{!error && view === null ? <p>loading…</p> : null}
{!error && view === null ? <FlowListSkeleton /> : null}
{view ? (
<div class="notify-flows">
{dryError ? (
@ -191,7 +192,7 @@ export function NotificationRouter() {
) : deliveries ? (
<Deliveries rows={deliveries.rows} />
) : (
<p>loading…</p>
<DeliveriesSkeleton />
)}
</div>
) : null}