From a504b3e34f2a40807ca7ee56773004c595cce448 Mon Sep 17 00:00:00 2001 From: Ian Gulliver Date: Sat, 21 Feb 2026 16:52:10 -0700 Subject: [PATCH] Replace boolean cell flags with CellType enum (event, title, continuation, gap, chain, signal) --- cmd/qrunproxy/static/index.html | 25 +++++++++-------- cmd/qrunproxy/timeline.go | 50 ++++++++++++++++++--------------- 2 files changed, 42 insertions(+), 33 deletions(-) diff --git a/cmd/qrunproxy/static/index.html b/cmd/qrunproxy/static/index.html index 2ae4eb3..bc14fb8 100644 --- a/cmd/qrunproxy/static/index.html +++ b/cmd/qrunproxy/static/index.html @@ -177,28 +177,28 @@ function render(data) { for (let r = 0; r < numRows; r++) { const cells = data.tracks.map(t => t.cells[r] || {}); - const hasCue = cells.some(c => c.block_id && c.event && (data.blocks[c.block_id] || {}).type === 'cue'); - const hasSignal = !hasCue && cells.some(c => c.event && c.is_signal); + const hasCue = cells.some(c => (c.type === 'event' || c.type === 'signal') && c.block_id && (data.blocks[c.block_id] || {}).type === 'cue'); + const hasSignal = !hasCue && cells.some(c => c.type === 'signal'); const rowCls = hasCue ? ' cue-row' : (hasSignal ? ' sig-row' : ''); cells.forEach((c, ti) => { const div = document.createElement('div'); div.className = 'cell' + rowCls; - if (c.is_title) { + if (c.type === 'title') { const block = data.blocks[c.block_id] || {}; const loop = block.loop ? ' \u21A9' : ''; div.innerHTML = `
${block.name || ''}${loop}
`; - } else if (c.is_chain) { + } else if (c.type === 'chain') { const nextCell = data.tracks[ti]?.cells[r+1] || {}; - const sym = nextCell.is_start ? '\u2193' : '\u2502'; + const sym = nextCell.event === 'START' ? '\u2193' : '\u2502'; div.innerHTML = `
${sym}
`; - } else if (c.block_id) { + } else if (c.type === 'event' || c.type === 'signal') { const block = data.blocks[c.block_id] || {}; - const isInfinity = r === numRows - 1 && !c.is_end && !c.is_title; + const isInfinity = r === numRows - 1 && c.event !== 'END' && c.event !== 'GO'; let seg = 'mid'; - if (c.is_start && c.is_end) seg = 'single'; - else if (c.is_start) seg = 'start'; - else if (c.is_end) seg = 'end'; + if (c.event === 'GO') seg = 'single'; + else if (c.event === 'START') seg = 'start'; + else if (c.event === 'END') seg = 'end'; div.className += isInfinity ? ' infinity-cell' : ''; let inner = `
`; @@ -206,12 +206,15 @@ function render(data) { inner += `
${block.name || ''}
`; } else if (c.event) { let hCls = 'hook'; - if (c.is_signal) hCls += ' sig'; + if (c.type === 'signal') hCls += ' sig'; inner += `
${c.event.replace('_', ' ')}
`; } inner += `
`; if (isInfinity) inner += `
∿∿∿
`; div.innerHTML = inner; + } else if (c.type === 'continuation') { + const block = data.blocks[c.block_id] || {}; + div.innerHTML = `
`; } timeline.appendChild(div); }); diff --git a/cmd/qrunproxy/timeline.go b/cmd/qrunproxy/timeline.go index 97947e9..d79e43c 100644 --- a/cmd/qrunproxy/timeline.go +++ b/cmd/qrunproxy/timeline.go @@ -20,15 +20,21 @@ type Timeline struct { exclusives []exclusiveGroup `json:"-"` } +type CellType string + +const ( + CellEvent CellType = "event" + CellTitle CellType = "title" + CellContinuation CellType = "continuation" + CellGap CellType = "gap" + CellChain CellType = "chain" + CellSignal CellType = "signal" +) + type TimelineCell struct { + Type CellType `json:"type"` BlockID string `json:"block_id,omitempty"` - IsStart bool `json:"is_start,omitempty"` - IsEnd bool `json:"is_end,omitempty"` Event string `json:"event,omitempty"` - IsTitle bool `json:"is_title,omitempty"` - IsSignal bool `json:"is_signal,omitempty"` - IsGap bool `json:"-"` - IsChain bool `json:"is_chain,omitempty"` row int `json:"-"` track *TimelineTrack `json:"-"` } @@ -84,7 +90,7 @@ func (g exclusiveGroup) satisfied(tracks []*TimelineTrack) bool { continue } c := t.Cells[row] - if c.IsGap || c.IsChain || c.BlockID == "" { + if c.Type != CellEvent && c.Type != CellTitle && c.Type != CellSignal { continue } return false @@ -167,19 +173,18 @@ func (track *TimelineTrack) appendCells(cells ...*TimelineCell) { func getCueCells(block *Block) []*TimelineCell { return []*TimelineCell{{ + Type: CellEvent, BlockID: block.ID, - IsStart: true, - IsEnd: true, Event: "GO", }} } func getBlockCells(block *Block) []*TimelineCell { return []*TimelineCell{ - {BlockID: block.ID, IsStart: true, Event: "START"}, - {BlockID: block.ID, IsTitle: true}, - {BlockID: block.ID, Event: "FADE_OUT"}, - {BlockID: block.ID, IsEnd: true, Event: "END"}, + {Type: CellEvent, BlockID: block.ID, Event: "START"}, + {Type: CellTitle, BlockID: block.ID}, + {Type: CellEvent, BlockID: block.ID, Event: "FADE_OUT"}, + {Type: CellEvent, BlockID: block.ID, Event: "END"}, } } @@ -214,9 +219,9 @@ func (tl *Timeline) buildCells(endChains map[string]bool) { } if block.Type != "cue" && lastOnTrack[block.Track] != block { if endChains[block.ID] { - track.appendCells(&TimelineCell{IsChain: true}) + track.appendCells(&TimelineCell{Type: CellChain}) } else { - track.appendCells(&TimelineCell{IsGap: true}) + track.appendCells(&TimelineCell{Type: CellGap}) } } } @@ -232,7 +237,7 @@ func (tl *Timeline) buildConstraints() { t := tl.findCell(target.Block, target.Hook) if source.track != t.track { tl.addConstraint("same_row", source, t) - source.IsSignal = true + source.Type = CellSignal } group.members = append(group.members, t) } @@ -301,7 +306,7 @@ func (tl *Timeline) enforceExclusives() bool { continue } c := t.Cells[row] - if c.IsGap || c.IsChain || c.BlockID == "" { + if c.Type != CellEvent && c.Type != CellTitle && c.Type != CellSignal { continue } tl.insertGap(t, row) @@ -320,11 +325,11 @@ func (tl *Timeline) isAllRemovableGapRow(row int, except *TimelineTrack) bool { continue } c := t.Cells[row] - if !c.IsGap && !c.IsChain { + if c.Type != CellGap && c.Type != CellChain && c.Type != CellContinuation { return false } - hasBefore := row > 0 && t.Cells[row-1].BlockID != "" && !t.Cells[row-1].IsGap && !t.Cells[row-1].IsChain - hasAfter := row+1 < len(t.Cells) && t.Cells[row+1].BlockID != "" && !t.Cells[row+1].IsGap && !t.Cells[row+1].IsChain + hasBefore := row > 0 && (t.Cells[row-1].Type == CellEvent || t.Cells[row-1].Type == CellTitle || t.Cells[row-1].Type == CellSignal) + hasAfter := row+1 < len(t.Cells) && (t.Cells[row+1].Type == CellEvent || t.Cells[row+1].Type == CellTitle || t.Cells[row+1].Type == CellSignal) if hasBefore && hasAfter { return false } @@ -358,10 +363,11 @@ func (tl *Timeline) insertGap(track *TimelineTrack, beforeIndex int) { return } - gap := &TimelineCell{IsGap: true, row: beforeIndex, track: track} + gap := &TimelineCell{Type: CellGap, row: beforeIndex, track: track} if beforeIndex > 0 { prev := track.Cells[beforeIndex-1] - if prev.BlockID != "" && !prev.IsEnd { + if prev.BlockID != "" && prev.Event != "END" && prev.Event != "GO" { + gap.Type = CellContinuation gap.BlockID = prev.BlockID } }