fix(web-ui): playout fmtDuration no longer clobbers global asset duration formatter

screens-playout.jsx declared a top-level function fmtDuration(secs) that, in
the shared global script scope, overwrote data.jsx's fmtDuration(ms). After
the playout redesign loaded, normalizeAsset(duration_ms) hit the seconds-based
version, rendering every asset duration x1000 (15000ms shown as 4:10:00).
Rename the playout-local helpers to playoutFmtDur/playoutFmtTC.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Zac Gaetano 2026-05-31 22:02:46 -04:00
parent 7b878d48c9
commit b92a5bc7f7

View file

@ -36,7 +36,7 @@ async function poFetch(path, opts) {
// Helpers
function fmtDuration(secs) {
function playoutFmtDur(secs) {
if (!secs || secs < 0) return '—';
const s = Math.floor(secs);
const h = Math.floor(s / 3600);
@ -47,7 +47,7 @@ function fmtDuration(secs) {
return h > 0 ? `${h}:${mm}:${ssStr}` : `${m}:${ssStr}`;
}
function fmtTimecode(secs) {
function playoutFmtTC(secs) {
// HH:MM:SS:FF at 29.97 (rounded)
const s = Math.floor(secs);
const h = Math.floor(s / 3600);
@ -317,7 +317,7 @@ function Playlist({ channel, playlistId, items, activeIndex, onReload }) {
<div className="po-playlist-head">
<span className="po-section-label">Playlist</span>
<span className="mono muted" style={{ fontSize: 11, marginLeft: 'auto' }}>
{items.length} clip{items.length !== 1 ? 's' : ''} · {fmtDuration(totalSecs)}
{items.length} clip{items.length !== 1 ? 's' : ''} · {playoutFmtDur(totalSecs)}
</span>
{dropErr && <span className="po-drop-err">{dropErr}</span>}
</div>
@ -338,7 +338,7 @@ function Playlist({ channel, playlistId, items, activeIndex, onReload }) {
{isActive ? <span className="po-pl-onair"></span> : index + 1}
</span>
<span className="po-pl-name">{it.clip_name || it.asset_id}</span>
<span className="mono po-pl-dur">{fmtDuration(dur)}</span>
<span className="mono po-pl-dur">{playoutFmtDur(dur)}</span>
<span className={'badge po-pl-badge ' + (
it.media_status === 'ready' ? 'success' :
it.media_status === 'staging' ? 'warn' :
@ -552,7 +552,7 @@ function ProgramMonitor({ channel, engine, elapsed }) {
)}
{onAir && (
<div className="po-tc-overlay mono">{fmtTimecode(elapsed)}</div>
<div className="po-tc-overlay mono">{playoutFmtTC(elapsed)}</div>
)}
<div className="po-meters-wrap">
@ -580,7 +580,7 @@ function ProgramMonitor({ channel, engine, elapsed }) {
</span>
{timeRemaining > 0 && (
<span className="po-clip-remain" title="Time remaining in clip">
-{fmtDuration(timeRemaining)}
-{playoutFmtDur(timeRemaining)}
</span>
)}
{engine.loop && <span title="Loop"></span>}
@ -761,8 +761,8 @@ function NowPlayingCard({ engine, elapsed, items }) {
<div className="po-nowplaying-fill" style={{ width: (progress * 100) + '%' }} />
</div>
<div className="po-nowplaying-times mono">
<span className="po-nowplaying-elapsed">{fmtDuration(elapsed)}</span>
<span className="po-nowplaying-remain muted">-{fmtDuration(timeRemaining)}</span>
<span className="po-nowplaying-elapsed">{playoutFmtDur(elapsed)}</span>
<span className="po-nowplaying-remain muted">-{playoutFmtDur(timeRemaining)}</span>
</div>
</div>
{nextItem && (
@ -813,7 +813,7 @@ function Timeline({ items, activeIndex, elapsed, breaks }) {
<div className="po-tl">
<div className="po-tl-head">
<span className="po-section-label">Timeline</span>
<span className="mono muted" style={{ fontSize: 11 }}>{fmtDuration(totalSecs)} total</span>
<span className="mono muted" style={{ fontSize: 11 }}>{playoutFmtDur(totalSecs)} total</span>
</div>
<div className="po-tl-track-wrap">
{activeIndex >= 0 && (
@ -833,9 +833,9 @@ function Timeline({ items, activeIndex, elapsed, breaks }) {
<div key={it.id}
className={'po-tl-clip' + (isActive ? ' po-tl-clip--active' : '')}
style={{ width: pct + '%', '--clip-color': color }}
title={`${it.clip_name || it.asset_id} · ${fmtDuration(dur)}`}>
title={`${it.clip_name || it.asset_id} · ${playoutFmtDur(dur)}`}>
<span className="po-tl-clip-name">{it.clip_name || it.asset_id}</span>
<span className="po-tl-clip-dur mono">{fmtDuration(dur)}</span>
<span className="po-tl-clip-dur mono">{playoutFmtDur(dur)}</span>
{it.media_status === 'staging' && (
<span className="po-tl-staging-dot" title="Staging…" />
)}
@ -849,7 +849,7 @@ function Timeline({ items, activeIndex, elapsed, breaks }) {
<div className="po-tl-ruler">
{totalSecs > 0 && Array.from({ length: 5 }).map((_, i) => (
<span key={i} className="po-tl-ruler-mark mono" style={{ left: (i * 25) + '%' }}>
{fmtDuration((totalSecs * i) / 4)}
{playoutFmtDur((totalSecs * i) / 4)}
</span>
))}
</div>
@ -907,7 +907,7 @@ function AsRunDrawer({ channel, refreshKey, open, onClose }) {
<td>{r.clip_name || r.item_id || '—'}</td>
<td className="mono">
{r.duration_s != null
? fmtDuration(Number(r.duration_s))
? playoutFmtDur(Number(r.duration_s))
: (r.ended_at ? '—' : 'on air')}
</td>
<td className={'po-asrun-result po-asrun-' + (r.result || 'played')}>