Replace boolean cell flags with CellType enum (event, title, continuation, gap, chain, signal)

This commit is contained in:
Ian Gulliver
2026-02-21 16:52:10 -07:00
parent 50d4b12299
commit a504b3e34f
2 changed files with 42 additions and 33 deletions

View File

@@ -177,28 +177,28 @@ function render(data) {
for (let r = 0; r < numRows; r++) { for (let r = 0; r < numRows; r++) {
const cells = data.tracks.map(t => t.cells[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 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.event && c.is_signal); const hasSignal = !hasCue && cells.some(c => c.type === 'signal');
const rowCls = hasCue ? ' cue-row' : (hasSignal ? ' sig-row' : ''); const rowCls = hasCue ? ' cue-row' : (hasSignal ? ' sig-row' : '');
cells.forEach((c, ti) => { cells.forEach((c, ti) => {
const div = document.createElement('div'); const div = document.createElement('div');
div.className = 'cell' + rowCls; div.className = 'cell' + rowCls;
if (c.is_title) { if (c.type === 'title') {
const block = data.blocks[c.block_id] || {}; const block = data.blocks[c.block_id] || {};
const loop = block.loop ? ' \u21A9' : ''; const loop = block.loop ? ' \u21A9' : '';
div.innerHTML = `<div class="block block-mid ${block.type || ''}"><div class="title">${block.name || ''}${loop}</div></div>`; div.innerHTML = `<div class="block block-mid ${block.type || ''}"><div class="title">${block.name || ''}${loop}</div></div>`;
} else if (c.is_chain) { } else if (c.type === 'chain') {
const nextCell = data.tracks[ti]?.cells[r+1] || {}; const nextCell = data.tracks[ti]?.cells[r+1] || {};
const sym = nextCell.is_start ? '\u2193' : '\u2502'; const sym = nextCell.event === 'START' ? '\u2193' : '\u2502';
div.innerHTML = `<div style="text-align:center;color:var(--fg-dim);font-size:14px;line-height:24px">${sym}</div>`; div.innerHTML = `<div style="text-align:center;color:var(--fg-dim);font-size:14px;line-height:24px">${sym}</div>`;
} else if (c.block_id) { } else if (c.type === 'event' || c.type === 'signal') {
const block = data.blocks[c.block_id] || {}; 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'; let seg = 'mid';
if (c.is_start && c.is_end) seg = 'single'; if (c.event === 'GO') seg = 'single';
else if (c.is_start) seg = 'start'; else if (c.event === 'START') seg = 'start';
else if (c.is_end) seg = 'end'; else if (c.event === 'END') seg = 'end';
div.className += isInfinity ? ' infinity-cell' : ''; div.className += isInfinity ? ' infinity-cell' : '';
let inner = `<div class="block block-${seg} ${block.type || ''}">`; let inner = `<div class="block block-${seg} ${block.type || ''}">`;
@@ -206,12 +206,15 @@ function render(data) {
inner += `<div class="cue-label">${block.name || ''}</div>`; inner += `<div class="cue-label">${block.name || ''}</div>`;
} else if (c.event) { } else if (c.event) {
let hCls = 'hook'; let hCls = 'hook';
if (c.is_signal) hCls += ' sig'; if (c.type === 'signal') hCls += ' sig';
inner += `<div class="${hCls}">${c.event.replace('_', ' ')}</div>`; inner += `<div class="${hCls}">${c.event.replace('_', ' ')}</div>`;
} }
inner += `</div>`; inner += `</div>`;
if (isInfinity) inner += `<div class="infinity-marker">&#x223F;&#x223F;&#x223F;</div>`; if (isInfinity) inner += `<div class="infinity-marker">&#x223F;&#x223F;&#x223F;</div>`;
div.innerHTML = inner; div.innerHTML = inner;
} else if (c.type === 'continuation') {
const block = data.blocks[c.block_id] || {};
div.innerHTML = `<div class="block block-mid ${block.type || ''}"></div>`;
} }
timeline.appendChild(div); timeline.appendChild(div);
}); });

View File

@@ -20,15 +20,21 @@ type Timeline struct {
exclusives []exclusiveGroup `json:"-"` 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 TimelineCell struct {
Type CellType `json:"type"`
BlockID string `json:"block_id,omitempty"` BlockID string `json:"block_id,omitempty"`
IsStart bool `json:"is_start,omitempty"`
IsEnd bool `json:"is_end,omitempty"`
Event string `json:"event,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:"-"` row int `json:"-"`
track *TimelineTrack `json:"-"` track *TimelineTrack `json:"-"`
} }
@@ -84,7 +90,7 @@ func (g exclusiveGroup) satisfied(tracks []*TimelineTrack) bool {
continue continue
} }
c := t.Cells[row] c := t.Cells[row]
if c.IsGap || c.IsChain || c.BlockID == "" { if c.Type != CellEvent && c.Type != CellTitle && c.Type != CellSignal {
continue continue
} }
return false return false
@@ -167,19 +173,18 @@ func (track *TimelineTrack) appendCells(cells ...*TimelineCell) {
func getCueCells(block *Block) []*TimelineCell { func getCueCells(block *Block) []*TimelineCell {
return []*TimelineCell{{ return []*TimelineCell{{
Type: CellEvent,
BlockID: block.ID, BlockID: block.ID,
IsStart: true,
IsEnd: true,
Event: "GO", Event: "GO",
}} }}
} }
func getBlockCells(block *Block) []*TimelineCell { func getBlockCells(block *Block) []*TimelineCell {
return []*TimelineCell{ return []*TimelineCell{
{BlockID: block.ID, IsStart: true, Event: "START"}, {Type: CellEvent, BlockID: block.ID, Event: "START"},
{BlockID: block.ID, IsTitle: true}, {Type: CellTitle, BlockID: block.ID},
{BlockID: block.ID, Event: "FADE_OUT"}, {Type: CellEvent, BlockID: block.ID, Event: "FADE_OUT"},
{BlockID: block.ID, IsEnd: true, Event: "END"}, {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 block.Type != "cue" && lastOnTrack[block.Track] != block {
if endChains[block.ID] { if endChains[block.ID] {
track.appendCells(&TimelineCell{IsChain: true}) track.appendCells(&TimelineCell{Type: CellChain})
} else { } 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) t := tl.findCell(target.Block, target.Hook)
if source.track != t.track { if source.track != t.track {
tl.addConstraint("same_row", source, t) tl.addConstraint("same_row", source, t)
source.IsSignal = true source.Type = CellSignal
} }
group.members = append(group.members, t) group.members = append(group.members, t)
} }
@@ -301,7 +306,7 @@ func (tl *Timeline) enforceExclusives() bool {
continue continue
} }
c := t.Cells[row] c := t.Cells[row]
if c.IsGap || c.IsChain || c.BlockID == "" { if c.Type != CellEvent && c.Type != CellTitle && c.Type != CellSignal {
continue continue
} }
tl.insertGap(t, row) tl.insertGap(t, row)
@@ -320,11 +325,11 @@ func (tl *Timeline) isAllRemovableGapRow(row int, except *TimelineTrack) bool {
continue continue
} }
c := t.Cells[row] c := t.Cells[row]
if !c.IsGap && !c.IsChain { if c.Type != CellGap && c.Type != CellChain && c.Type != CellContinuation {
return false return false
} }
hasBefore := row > 0 && 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].BlockID != "" && !t.Cells[row+1].IsGap && !t.Cells[row+1].IsChain 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 { if hasBefore && hasAfter {
return false return false
} }
@@ -358,10 +363,11 @@ func (tl *Timeline) insertGap(track *TimelineTrack, beforeIndex int) {
return return
} }
gap := &TimelineCell{IsGap: true, row: beforeIndex, track: track} gap := &TimelineCell{Type: CellGap, row: beforeIndex, track: track}
if beforeIndex > 0 { if beforeIndex > 0 {
prev := track.Cells[beforeIndex-1] 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 gap.BlockID = prev.BlockID
} }
} }