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++) {
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 = `<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 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>`;
} 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 = `<div class="block block-${seg} ${block.type || ''}">`;
@@ -206,12 +206,15 @@ function render(data) {
inner += `<div class="cue-label">${block.name || ''}</div>`;
} else if (c.event) {
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>`;
if (isInfinity) inner += `<div class="infinity-marker">&#x223F;&#x223F;&#x223F;</div>`;
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);
});

View File

@@ -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
}
}