From 4a7eb4aa2a7877fde5254ff61f2ed3a1e17337a7 Mon Sep 17 00:00:00 2001 From: Ian Gulliver Date: Wed, 18 Feb 2026 22:33:16 -0700 Subject: [PATCH] Fold START signal targets into the row that starts the block --- cmd/qrunweb/static/index.html | 80 ++++++++++++++++++++++------------- 1 file changed, 50 insertions(+), 30 deletions(-) diff --git a/cmd/qrunweb/static/index.html b/cmd/qrunweb/static/index.html index b0d7fb5..56983aa 100644 --- a/cmd/qrunweb/static/index.html +++ b/cmd/qrunweb/static/index.html @@ -169,7 +169,11 @@ function render(data) { function ref(b, s) { return b + ':' + s; } const triggerSourceSet = new Set(); - data.triggers.forEach(t => triggerSourceSet.add(ref(t.source.block, t.source.signal))); + const startSignalMap = new Map(); + data.triggers.forEach(t => { + triggerSourceSet.add(ref(t.source.block, t.source.signal)); + if (t.source.signal === 'START') startSignalMap.set(t.source.block, t.targets); + }); function blockTrack(bid) { const b = blockMap.get(bid); @@ -233,8 +237,35 @@ function render(data) { toEnd.forEach(bid => { active.delete(blockTrack(bid)); pendingEnds.delete(bid); }); } + function expandTargets(targetMap) { + const toExpand = [...targetMap.entries()].filter(([_, hook]) => hook === 'START'); + toExpand.forEach(([bid]) => { + const extras = startSignalMap.get(bid); + if (extras) extras.forEach(et => { if (!targetMap.has(et.block)) targetMap.set(et.block, et.hook); }); + }); + } + + function applyTargets(targetMap, tid, isCue) { + const entry = [...targetMap.entries()].find(([bid]) => blockTrack(bid) === tid); + if (!entry) return null; + const [bid, hook] = entry; + const isHook = !isCue; + if (hook === 'START') { + active.set(tid, bid); + wantTitle(bid); + return { blockId: bid, segment: 'start', event: 'START', isHook }; + } + if (hook === 'END') return { blockId: bid, segment: 'end', event: 'END', isHook, directEnd: true }; + if (hook === 'FADE_OUT') { + pendingEnds.add(bid); + return { blockId: bid, segment: 'mid', event: 'FADE_OUT' }; + } + return null; + } + function processTrigger(trigger) { const src = trigger.source; + if (src.signal === 'START') return; const isCue = src.signal === 'GO'; flush(); @@ -252,23 +283,30 @@ function render(data) { } active.set(tid, tgt); wantTitle(tgt); - const startCells = trackIds.map(t => - t === tid ? { blockId: tgt, segment: 'start', event: 'START' } : mid(t) - ); - addRow(startCells); + const targetMap = new Map(); + const extras = startSignalMap.get(tgt); + if (extras) extras.forEach(et => targetMap.set(et.block, et.hook)); + expandTargets(targetMap); + const hasTargets = targetMap.size > 0; + const directEnds = []; + const startCells = trackIds.map(t => { + if (t === tid) return { blockId: tgt, segment: 'start', event: 'START', isSignal: hasTargets }; + const cell = applyTargets(targetMap, t, false); + if (cell) { if (cell.directEnd) { directEnds.push(cell.blockId); delete cell.directEnd; } return cell; } + return mid(t); + }); + addRow(startCells, hasTargets ? 'sig-row' : ''); + directEnds.forEach(bid => active.delete(blockTrack(bid))); return; } - if (!isCue && src.signal === 'START' && active.get(blockTrack(src.block)) !== src.block) { - active.set(blockTrack(src.block), src.block); - } - emitTitles(); const rowClass = isCue ? 'cue-row' : 'sig-row'; const targetMap = new Map(); trigger.targets.forEach(t => targetMap.set(t.block, t.hook)); + expandTargets(targetMap); const directEnds = []; @@ -277,29 +315,12 @@ function render(data) { return { cueLabel: blockMap.get(src.block).name }; if (!isCue && blockTrack(src.block) === tid) { - const alreadyActive = active.get(tid) === src.block; - const seg = alreadyActive ? 'mid' : src.signal === 'START' ? 'start' : src.signal === 'END' ? 'end' : 'mid'; + const seg = src.signal === 'END' ? 'end' : 'mid'; return { blockId: src.block, segment: seg, event: src.signal, isSignal: true }; } - const entry = [...targetMap.entries()].find(([bid]) => blockTrack(bid) === tid); - if (entry) { - const [bid, hook] = entry; - const isHook = !isCue; - if (hook === 'START') { - active.set(tid, bid); - wantTitle(bid); - return { blockId: bid, segment: 'start', event: 'START', isHook }; - } - if (hook === 'END') { - directEnds.push(bid); - return { blockId: bid, segment: 'end', event: 'END', isHook }; - } - if (hook === 'FADE_OUT') { - pendingEnds.add(bid); - return { blockId: bid, segment: 'mid', event: 'FADE_OUT' }; - } - } + const cell = applyTargets(targetMap, tid, isCue); + if (cell) { if (cell.directEnd) { directEnds.push(cell.blockId); delete cell.directEnd; } return cell; } return mid(tid); }); @@ -309,7 +330,6 @@ function render(data) { directEnds.forEach(bid => active.delete(blockTrack(bid))); if (!isCue) { - if (src.signal === 'START' && !pendingTitles.has(src.block)) wantTitle(src.block); if (src.signal === 'FADE_OUT') pendingEnds.add(src.block); if (src.signal === 'END') { active.delete(blockTrack(src.block));