Replace renderRows with per-track cells API, fill continuations in insertGap

This commit is contained in:
Ian Gulliver
2026-02-20 09:04:14 -07:00
parent 64e76445cf
commit 616f2cfb4e
2 changed files with 36 additions and 54 deletions

View File

@@ -164,7 +164,10 @@ function render(data) {
`<span><span class="status-dot"></span>QLab Connected</span>`; `<span><span class="status-dot"></span>QLab Connected</span>`;
const timeline = document.getElementById('timeline'); const timeline = document.getElementById('timeline');
timeline.style.gridTemplateColumns = `repeat(${data.tracks.length}, 140px)`; const numTracks = data.tracks.length;
const numRows = Math.max(...data.tracks.map(t => t.cells.length));
timeline.style.gridTemplateColumns = `repeat(${numTracks}, 140px)`;
data.tracks.forEach(track => { data.tracks.forEach(track => {
const el = document.createElement('div'); const el = document.createElement('div');
el.className = 'track-header'; el.className = 'track-header';
@@ -172,12 +175,13 @@ function render(data) {
timeline.appendChild(el); timeline.appendChild(el);
}); });
data.rows.forEach((row, rowIndex) => { for (let r = 0; r < numRows; r++) {
const hasCue = row.cells.some(c => c.block_id && c.event && (data.blocks[c.block_id] || {}).type === 'cue'); const cells = data.tracks.map(t => t.cells[r] || {});
const hasSignal = !hasCue && row.cells.some(c => c.event && c.is_signal); 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 rowCls = hasCue ? ' cue-row' : (hasSignal ? ' sig-row' : ''); const rowCls = hasCue ? ' cue-row' : (hasSignal ? ' sig-row' : '');
row.cells.forEach(c => { cells.forEach(c => {
const div = document.createElement('div'); const div = document.createElement('div');
div.className = 'cell' + rowCls; div.className = 'cell' + rowCls;
if (c.is_title) { if (c.is_title) {
@@ -185,7 +189,7 @@ function render(data) {
div.innerHTML = `<div class="block block-mid ${block.type || ''}"><div class="title">${block.name || ''}</div></div>`; div.innerHTML = `<div class="block block-mid ${block.type || ''}"><div class="title">${block.name || ''}</div></div>`;
} else if (c.block_id) { } else if (c.block_id) {
const block = data.blocks[c.block_id] || {}; const block = data.blocks[c.block_id] || {};
const isInfinity = rowIndex === data.rows.length - 1 && !c.is_end && !c.is_title; const isInfinity = r === numRows - 1 && !c.is_end && !c.is_title;
let seg = 'mid'; let seg = 'mid';
if (c.is_start && c.is_end) seg = 'single'; if (c.is_start && c.is_end) seg = 'single';
else if (c.is_start) seg = 'start'; else if (c.is_start) seg = 'start';
@@ -206,7 +210,7 @@ function render(data) {
} }
timeline.appendChild(div); timeline.appendChild(div);
}); });
}); }
} }
</script> </script>
</body> </body>

View File

@@ -40,14 +40,14 @@ type TriggerTarget struct {
Hook string `json:"hook"` Hook string `json:"hook"`
} }
type Timeline struct { type TimelineTrack struct {
Tracks []Track `json:"tracks"` Track
Blocks map[string]Block `json:"blocks"` Cells []TimelineCell `json:"cells"`
Rows []TimelineRow `json:"rows"`
} }
type TimelineRow struct { type Timeline struct {
Cells []TimelineCell `json:"cells"` Tracks []TimelineTrack `json:"tracks"`
Blocks map[string]Block `json:"blocks"`
} }
type TimelineCell struct { type TimelineCell struct {
@@ -150,10 +150,14 @@ func BuildTimeline(show Show) (Timeline, error) {
b.assignRows() b.assignRows()
b.collapseEmptyRows() b.collapseEmptyRows()
tracks := make([]TimelineTrack, len(b.tracks))
for i, t := range b.tracks {
tracks[i] = TimelineTrack{Track: t, Cells: b.trackCells[i]}
}
return Timeline{ return Timeline{
Tracks: b.tracks, Tracks: tracks,
Blocks: b.blocks, Blocks: b.blocks,
Rows: b.renderRows(),
}, nil }, nil
} }
@@ -340,10 +344,22 @@ func (b *timelineBuilder) insertGap(track, beforeIndex int) {
} }
} }
gap := TimelineCell{IsGap: true}
for i := beforeIndex - 1; i >= 0; i-- {
c := b.trackCells[track][i]
if c.IsGap {
continue
}
if c.BlockID != "" && !c.IsEnd {
gap.BlockID = c.BlockID
}
break
}
cells := b.trackCells[track] cells := b.trackCells[track]
newCells := make([]TimelineCell, 0, len(cells)+1) newCells := make([]TimelineCell, 0, len(cells)+1)
newCells = append(newCells, cells[:beforeIndex]...) newCells = append(newCells, cells[:beforeIndex]...)
newCells = append(newCells, TimelineCell{IsGap: true}) newCells = append(newCells, gap)
newCells = append(newCells, cells[beforeIndex:]...) newCells = append(newCells, cells[beforeIndex:]...)
b.trackCells[track] = newCells b.trackCells[track] = newCells
@@ -435,41 +451,3 @@ func (b *timelineBuilder) collapseEmptyRows() {
} }
} }
func (b *timelineBuilder) renderRows() []TimelineRow {
maxLen := 0
for _, cells := range b.trackCells {
if len(cells) > maxLen {
maxLen = len(cells)
}
}
rows := make([]TimelineRow, maxLen)
for r := range rows {
rows[r].Cells = make([]TimelineCell, len(b.tracks))
}
for trackIdx, cells := range b.trackCells {
activeBlock := ""
for r := 0; r < maxLen; r++ {
if r < len(cells) {
c := cells[r]
if c.IsGap {
if activeBlock != "" {
rows[r].Cells[trackIdx] = TimelineCell{BlockID: activeBlock}
}
} else {
rows[r].Cells[trackIdx] = c
if c.BlockID != "" && !c.IsEnd {
activeBlock = c.BlockID
} else if c.IsEnd {
activeBlock = ""
}
}
} else if activeBlock != "" {
rows[r].Cells[trackIdx] = TimelineCell{BlockID: activeBlock}
}
}
}
return rows
}