feat(web): three-phase progress with live download % on The Floor
Replace the misleading 6-micro-step progress bar with three honest
phases (Searching -> Downloading -> Finishing). Downloading now shows
a live determinate percentage sourced from job.downloadProgress (Slice
A), with a "NN% · done/total tracks" caption when the track count is
known. Searching and Finishing stay indeterminate.
- status.ts: describeJob collapses to {label, kind}; drop step/totalSteps
and the STAGES array.
- progress-bar.tsx: ProgressBar takes {kind, indeterminate?, percent?,
caption?} instead of step/total/stageLabel.
- job-row.tsx: JobRow takes a `bar?: ReactNode` slot instead of
step/total/indeterminate/stageLabel props.
- queue.tsx: builds the per-state bar + meta; downloading meta drops
the track count (now shown in the bar). Retry button + error note
from the retry feature preserved exactly.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+24
-9
@@ -5,6 +5,7 @@ import { describeJob, timeAgo } from "./_ui/status";
|
||||
import { StatTiles } from "./_ui/stat-tiles";
|
||||
import { SectionHeader } from "./_ui/section-header";
|
||||
import { JobRow } from "./_ui/job-row";
|
||||
import { ProgressBar } from "./_ui/progress-bar";
|
||||
import { PressedList } from "./_ui/pressed-list";
|
||||
import { toast } from "./_ui/toast";
|
||||
|
||||
@@ -24,6 +25,7 @@ type Row = {
|
||||
updatedAt: string | null;
|
||||
candidateCount: number;
|
||||
chosen: { source: string; format: string; trackCount: number } | null;
|
||||
downloadProgress: number;
|
||||
} | null;
|
||||
};
|
||||
|
||||
@@ -38,6 +40,7 @@ function jobOf(r: Row) {
|
||||
updatedAt: null,
|
||||
candidateCount: 0,
|
||||
chosen: null,
|
||||
downloadProgress: 0,
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -139,7 +142,7 @@ export function Queue() {
|
||||
let meta: string | undefined;
|
||||
if (j.state === "downloading" || j.state === "tagging") {
|
||||
const parts: string[] = [];
|
||||
if (j.chosen) parts.push(`${j.chosen.source} · ${j.chosen.format} · ${j.chosen.trackCount} tracks`);
|
||||
if (j.chosen) parts.push(`${j.chosen.source} · ${j.chosen.format}`);
|
||||
const elapsed = timeAgo(j.claimedAt ?? j.updatedAt, now);
|
||||
if (elapsed) parts.push(elapsed);
|
||||
meta = parts.join(" · ") || undefined;
|
||||
@@ -151,8 +154,6 @@ export function Queue() {
|
||||
} else if (j.state === "requested") {
|
||||
const elapsed = timeAgo(r.createdAt, now);
|
||||
meta = elapsed ? `In the queue · ${elapsed}` : "In the queue";
|
||||
} else if (j.state === "needs_attention") {
|
||||
meta = undefined;
|
||||
} else {
|
||||
meta = undefined;
|
||||
}
|
||||
@@ -166,8 +167,25 @@ export function Queue() {
|
||||
</>
|
||||
) : undefined;
|
||||
|
||||
const stageLabel =
|
||||
attention || j.state === "matching" || j.state === "matched" ? undefined : j.currentStage;
|
||||
let bar: React.ReactNode;
|
||||
if (j.state === "matching" || j.state === "matched") {
|
||||
bar = <ProgressBar kind="working" indeterminate caption="searching" />;
|
||||
} else if (j.state === "downloading") {
|
||||
const pct = Math.round((j.downloadProgress || 0) * 100);
|
||||
const total = j.chosen?.trackCount ?? 0;
|
||||
const done = total ? Math.round((pct / 100) * total) : 0;
|
||||
bar = (
|
||||
<ProgressBar
|
||||
kind="working"
|
||||
percent={pct}
|
||||
caption={total ? `${pct}% · ${done}/${total} tracks` : `${pct}%`}
|
||||
/>
|
||||
);
|
||||
} else if (j.state === "tagging") {
|
||||
bar = <ProgressBar kind="verify" indeterminate caption="finishing" />;
|
||||
} else {
|
||||
bar = undefined;
|
||||
}
|
||||
|
||||
return (
|
||||
<JobRow
|
||||
@@ -176,12 +194,9 @@ export function Queue() {
|
||||
album={r.album}
|
||||
kind={d.kind}
|
||||
label={d.label}
|
||||
step={d.step}
|
||||
total={d.totalSteps}
|
||||
indeterminate={j.state === "matching" || j.state === "matched"}
|
||||
meta={meta}
|
||||
note={note}
|
||||
stageLabel={stageLabel}
|
||||
bar={bar}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user