swarm-ui: the refresh picker shows a spinner in place of its clock while its page is fetching
usePolled reports when a request is in flight (through a new onBusy of the poller) and holds it on for at least 0.4 seconds, so a quick answer still reads as a refresh. The picker swaps its clock for the spinner, in the same box so its width stays, and the spinner stands still where motion is off. Every page with a picker passes it; a page with several polled requests shows it while any of them runs. The Jobs page fetches through shared components and has none yet.
This commit is contained in:
parent
89bbeb9757
commit
47c447409b
9 changed files with 296 additions and 23 deletions
|
|
@ -75,7 +75,11 @@ function AlertsBody() {
|
|||
const [showAll, setShowAll] = useState(false);
|
||||
const [intervalMs, setIntervalMs] =
|
||||
useState<RefreshIntervalMs>(DEFAULT_INTERVAL_MS);
|
||||
const { data: alerts, error } = usePolled(intervalMs, (signal) =>
|
||||
const {
|
||||
data: alerts,
|
||||
error,
|
||||
refreshing,
|
||||
} = usePolled(intervalMs, (signal) =>
|
||||
fetchJson<Alert[]>("/api/alerts", signal),
|
||||
);
|
||||
const listed = alerts ? listedAlerts(alerts, showAll) : null;
|
||||
|
|
@ -96,6 +100,7 @@ function AlertsBody() {
|
|||
id="alerts-refresh"
|
||||
value={intervalMs}
|
||||
onChange={setIntervalMs}
|
||||
refreshing={refreshing}
|
||||
/>
|
||||
</div>
|
||||
{error ? (
|
||||
|
|
|
|||
|
|
@ -510,6 +510,14 @@ export function ComponentsPage() {
|
|||
<Sample label="editable">
|
||||
<RefreshIntervalPickerSample />
|
||||
</Sample>
|
||||
<Sample label="refreshing">
|
||||
<RefreshIntervalPicker
|
||||
id="sample-refresh-interval-busy"
|
||||
value={30_000}
|
||||
onChange={() => undefined}
|
||||
refreshing
|
||||
/>
|
||||
</Sample>
|
||||
</Section>
|
||||
|
||||
<Section title="TextField">
|
||||
|
|
|
|||
|
|
@ -98,6 +98,7 @@ export function AgentsPage() {
|
|||
const {
|
||||
data: rows,
|
||||
error,
|
||||
refreshing,
|
||||
setData: setRows,
|
||||
} = usePolled(intervalMs, (signal) =>
|
||||
fetchJson<AgentRow[]>("/api/agents/status", signal),
|
||||
|
|
@ -131,6 +132,7 @@ export function AgentsPage() {
|
|||
id="agents-refresh"
|
||||
value={intervalMs}
|
||||
onChange={setIntervalMs}
|
||||
refreshing={refreshing}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -60,7 +60,11 @@ function storedFilter(): AgentFilter {
|
|||
export function BroadcastComposer() {
|
||||
const [intervalMs, setIntervalMs] =
|
||||
useState<RefreshIntervalMs>(DEFAULT_INTERVAL_MS);
|
||||
const { data: agents, error: loadError } = usePolled(intervalMs, (signal) =>
|
||||
const {
|
||||
data: agents,
|
||||
error: loadError,
|
||||
refreshing,
|
||||
} = usePolled(intervalMs, (signal) =>
|
||||
fetchJson<BroadcastAgent[]>("/api/agents/status", signal),
|
||||
);
|
||||
const [filter, setFilterState] = useState<AgentFilter>(storedFilter);
|
||||
|
|
@ -173,6 +177,7 @@ export function BroadcastComposer() {
|
|||
id="broadcast-refresh"
|
||||
value={intervalMs}
|
||||
onChange={setIntervalMs}
|
||||
refreshing={refreshing}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -96,9 +96,11 @@ function BuildsBody() {
|
|||
const wanted = wantedRun(useSearch());
|
||||
const [intervalMs, setIntervalMs] =
|
||||
useState<RefreshIntervalMs>(DEFAULT_INTERVAL_MS);
|
||||
const { data: runs, error } = usePolled(intervalMs, (signal) =>
|
||||
fetchJson<BuildRun[]>(RUNS, signal),
|
||||
);
|
||||
const {
|
||||
data: runs,
|
||||
error,
|
||||
refreshing,
|
||||
} = usePolled(intervalMs, (signal) => fetchJson<BuildRun[]>(RUNS, signal));
|
||||
// Which cards are open. Survives a refresh, so a list that updates under
|
||||
// you does not fold the card you are reading. A linked build starts open.
|
||||
const [open, setOpen] = useState<ReadonlySet<string>>(
|
||||
|
|
@ -134,6 +136,7 @@ function BuildsBody() {
|
|||
id="builds-refresh"
|
||||
value={intervalMs}
|
||||
onChange={setIntervalMs}
|
||||
refreshing={refreshing}
|
||||
/>
|
||||
</div>
|
||||
<p>
|
||||
|
|
|
|||
|
|
@ -41,18 +41,24 @@ export function NotificationRouter() {
|
|||
const {
|
||||
data: view,
|
||||
error,
|
||||
refreshing: flowsRefreshing,
|
||||
refresh,
|
||||
} = usePolled(intervalMs, (signal) =>
|
||||
fetchJson<FlowsView>("/api/notify/flows", signal),
|
||||
);
|
||||
// The router's own answers. Either is 503 while the running router predates
|
||||
// it, which the page says in place of the section.
|
||||
const { data: dry, error: dryError } = usePolled(intervalMs, (signal) =>
|
||||
fetchJson<DryRun>(DRY_RUN_URL, signal),
|
||||
);
|
||||
const { data: deliveries, error: deliveriesError } = usePolled(
|
||||
intervalMs,
|
||||
(signal) => fetchJson<DeliveriesView>(DELIVERIES_URL, signal),
|
||||
const {
|
||||
data: dry,
|
||||
error: dryError,
|
||||
refreshing: dryRefreshing,
|
||||
} = usePolled(intervalMs, (signal) => fetchJson<DryRun>(DRY_RUN_URL, signal));
|
||||
const {
|
||||
data: deliveries,
|
||||
error: deliveriesError,
|
||||
refreshing: deliveriesRefreshing,
|
||||
} = usePolled(intervalMs, (signal) =>
|
||||
fetchJson<DeliveriesView>(DELIVERIES_URL, signal),
|
||||
);
|
||||
// A failed poll keeps the last answer; the counts are shown only while the
|
||||
// latest poll worked.
|
||||
|
|
@ -102,6 +108,7 @@ export function NotificationRouter() {
|
|||
id="notifications-refresh"
|
||||
value={intervalMs}
|
||||
onChange={setIntervalMs}
|
||||
refreshing={flowsRefreshing || dryRefreshing || deliveriesRefreshing}
|
||||
/>
|
||||
<Button variant="primary" onClick={() => draftFrom()}>
|
||||
create flow
|
||||
|
|
|
|||
|
|
@ -93,7 +93,11 @@ function RunCard({
|
|||
export function ReviewRuns() {
|
||||
const [intervalMs, setIntervalMs] =
|
||||
useState<RefreshIntervalMs>(DEFAULT_INTERVAL_MS);
|
||||
const { data: runs, error } = usePolled(intervalMs, (signal) =>
|
||||
const {
|
||||
data: runs,
|
||||
error,
|
||||
refreshing,
|
||||
} = usePolled(intervalMs, (signal) =>
|
||||
fetchJson<ClassifierRun[]>(RUNS, signal),
|
||||
);
|
||||
// Which cards are expanded. Survives a refresh, so a list that updates
|
||||
|
|
@ -117,6 +121,7 @@ export function ReviewRuns() {
|
|||
id="review-runs-refresh"
|
||||
value={intervalMs}
|
||||
onChange={setIntervalMs}
|
||||
refreshing={refreshing}
|
||||
/>
|
||||
}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ function SchedulesBody() {
|
|||
const {
|
||||
data: schedules,
|
||||
error,
|
||||
refreshing: schedulesRefreshing,
|
||||
refresh,
|
||||
} = usePolled(intervalMs, (signal) =>
|
||||
fetchJson<Schedule[]>("/api/schedules", signal),
|
||||
|
|
@ -72,7 +73,11 @@ function SchedulesBody() {
|
|||
// lists, polled on the same cadence so an agent created after this page
|
||||
// opened shows up. A failed load does not block the page, the cards do not
|
||||
// need it; the form says the list could not load.
|
||||
const { data: roster, error: rosterError } = usePolled(intervalMs, (signal) =>
|
||||
const {
|
||||
data: roster,
|
||||
error: rosterError,
|
||||
refreshing: rosterRefreshing,
|
||||
} = usePolled(intervalMs, (signal) =>
|
||||
fetchJson<AgentRow[]>("/api/agents/status", signal),
|
||||
);
|
||||
const agentNames = useMemo(() => agentNamesFrom(roster), [roster]);
|
||||
|
|
@ -159,6 +164,7 @@ function SchedulesBody() {
|
|||
id="schedules-refresh"
|
||||
value={intervalMs}
|
||||
onChange={setIntervalMs}
|
||||
refreshing={schedulesRefreshing || rosterRefreshing}
|
||||
/>
|
||||
</div>
|
||||
{error ? (
|
||||
|
|
|
|||
|
|
@ -116,7 +116,11 @@ const DEFAULT_INTERVAL_MS: RefreshIntervalMs = 30_000;
|
|||
function HiveRoster() {
|
||||
const [intervalMs, setIntervalMs] =
|
||||
useState<RefreshIntervalMs>(DEFAULT_INTERVAL_MS);
|
||||
const { data: hives, error } = usePolled(intervalMs, (signal) =>
|
||||
const {
|
||||
data: hives,
|
||||
error,
|
||||
refreshing,
|
||||
} = usePolled(intervalMs, (signal) =>
|
||||
fetchJson<HiveStatus[]>("/api/hives/status", signal),
|
||||
);
|
||||
|
||||
|
|
@ -127,6 +131,7 @@ function HiveRoster() {
|
|||
id="hives-refresh"
|
||||
value={intervalMs}
|
||||
onChange={setIntervalMs}
|
||||
refreshing={refreshing}
|
||||
/>
|
||||
</div>
|
||||
{error ? (
|
||||
|
|
|
|||
Loading…
Reference in a new issue