fix(downloads): GIF review round 3 — every length readout, live regions in place
A refused GIF's length reads rounded up everywhere the editor shows it (the segment and the kept summary of one), and the GIF notes and estimates sit in live regions that are there before GIF is picked, so a note already true then is said. Tests hold the clip panel's readout and which segment a note names; DESIGN.md claims only what the contract test holds.
This commit is contained in:
@@ -599,7 +599,7 @@ export default function VideoEditor({ job, onClose }: { job: DownloadJob; onClos
|
||||
/>
|
||||
</label>
|
||||
<span className="text-xs text-muted tabular-nums w-16 text-right">
|
||||
{tc(sel.end - sel.start)}
|
||||
{tc(gif ? gifShownLength(sel.start, sel.end) : sel.end - sel.start)}
|
||||
</span>
|
||||
{(inField.invalid || outField.invalid) && (
|
||||
<div className="basis-full">
|
||||
@@ -633,7 +633,13 @@ export default function VideoEditor({ job, onClose }: { job: DownloadJob; onClos
|
||||
})}
|
||||
<div className="flex-1" />
|
||||
<span className="text-xs text-muted">
|
||||
{t("editor.keptSummary", { n: kept.length, dur: tc(keptDur) })}
|
||||
{t("editor.keptSummary", {
|
||||
n: kept.length,
|
||||
// One GIF: its length as the notes say it (a refused one never reads as allowed).
|
||||
dur: tc(
|
||||
gif && kept.length === 1 ? gifShownLength(kept[0]!.start, kept[0]!.end) : keptDur,
|
||||
),
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -664,37 +670,38 @@ export default function VideoEditor({ job, onClose }: { job: DownloadJob; onClos
|
||||
label: t("clip.gif.side", { n: formatNumber(s, i18n.language) }),
|
||||
}))}
|
||||
/>
|
||||
{kept.length > 0 && (
|
||||
<span className="text-xs text-muted tabular-nums" aria-live="polite">
|
||||
{t(
|
||||
gifTotalText.single ? "editor.format.estimateOne" : "editor.format.estimate",
|
||||
gifTotalText,
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{/* The live regions stay mounted whatever the format (laid out as their content
|
||||
alone): a live region announces what changes inside it, not what arrives together
|
||||
with it, so a note already true when GIF is picked is said too. */}
|
||||
<span className="contents" aria-live="polite">
|
||||
{gif && kept.length > 0 && (
|
||||
<span className="text-xs text-muted tabular-nums">
|
||||
{t(
|
||||
gifTotalText.single ? "editor.format.estimateOne" : "editor.format.estimate",
|
||||
gifTotalText,
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{/* Mounted for as long as GIF is chosen: a live region announces what changes inside it,
|
||||
not what arrives together with it, and the first note is the one that counts. */}
|
||||
{gif && (
|
||||
<div id={gifNoteId} aria-live="polite">
|
||||
{gifNotes.length > 0 && (
|
||||
<ul className="space-y-0.5 text-xs">
|
||||
{gifNotes.map((g) => (
|
||||
<li key={g.n} className={g.verdict === "too_long" ? "text-danger" : "text-warn"}>
|
||||
{t(GIF_NOTE_KEY[g.verdict as keyof typeof GIF_NOTE_KEY], {
|
||||
n: g.n,
|
||||
length: tc(g.length),
|
||||
seconds: GIF_MAX_S,
|
||||
})}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div id={gifNoteId} className="contents" aria-live="polite">
|
||||
{gif && gifNotes.length > 0 && (
|
||||
<ul className="space-y-0.5 text-xs">
|
||||
{gifNotes.map((g) => (
|
||||
<li key={g.n} className={g.verdict === "too_long" ? "text-danger" : "text-warn"}>
|
||||
{t(GIF_NOTE_KEY[g.verdict as keyof typeof GIF_NOTE_KEY], {
|
||||
n: g.n,
|
||||
length: tc(g.length),
|
||||
seconds: GIF_MAX_S,
|
||||
})}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* crop + precision */}
|
||||
<div className="grid sm:grid-cols-2 gap-3">
|
||||
|
||||
Reference in New Issue
Block a user