Center block titles between open/close events with compacted title rows
This commit is contained in:
@@ -192,8 +192,7 @@ function render(data) {
|
|||||||
|
|
||||||
const active = new Map();
|
const active = new Map();
|
||||||
const pendingEnds = new Set();
|
const pendingEnds = new Set();
|
||||||
const pendingTitles = new Set();
|
const noTitleAfter = new Set();
|
||||||
const titled = new Set();
|
|
||||||
const rows = [];
|
const rows = [];
|
||||||
|
|
||||||
function mid(tid) {
|
function mid(tid) {
|
||||||
@@ -205,23 +204,7 @@ function render(data) {
|
|||||||
rows.push({ cells, rowClass: rowClass || '' });
|
rows.push({ cells, rowClass: rowClass || '' });
|
||||||
}
|
}
|
||||||
|
|
||||||
function wantTitle(bid) {
|
|
||||||
if (!titled.has(bid)) pendingTitles.add(bid);
|
|
||||||
}
|
|
||||||
|
|
||||||
function emitTitles() {
|
|
||||||
if (pendingTitles.size === 0) return;
|
|
||||||
const cells = trackIds.map(tid => {
|
|
||||||
const t = [...pendingTitles].find(bid => blockTrack(bid) === tid);
|
|
||||||
return t ? { blockId: t, segment: 'mid', title: blockMap.get(t).name } : mid(tid);
|
|
||||||
});
|
|
||||||
addRow(cells);
|
|
||||||
pendingTitles.forEach(bid => titled.add(bid));
|
|
||||||
pendingTitles.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
function flush() {
|
function flush() {
|
||||||
emitTitles();
|
|
||||||
if (pendingEnds.size === 0) return;
|
if (pendingEnds.size === 0) return;
|
||||||
|
|
||||||
const toEnd = [...pendingEnds].filter(bid => !triggerSourceSet.has(ref(bid, 'END')));
|
const toEnd = [...pendingEnds].filter(bid => !triggerSourceSet.has(ref(bid, 'END')));
|
||||||
@@ -250,7 +233,6 @@ function render(data) {
|
|||||||
const isHook = !isCue;
|
const isHook = !isCue;
|
||||||
if (hook === 'START') {
|
if (hook === 'START') {
|
||||||
active.set(tid, bid);
|
active.set(tid, bid);
|
||||||
wantTitle(bid);
|
|
||||||
return { blockId: bid, segment: 'start', event: 'START', isHook };
|
return { blockId: bid, segment: 'start', event: 'START', isHook };
|
||||||
}
|
}
|
||||||
if (hook === 'END') return { blockId: bid, segment: 'end', event: 'END', isHook, directEnd: true };
|
if (hook === 'END') return { blockId: bid, segment: 'end', event: 'END', isHook, directEnd: true };
|
||||||
@@ -280,13 +262,13 @@ function render(data) {
|
|||||||
active.delete(tid);
|
active.delete(tid);
|
||||||
}
|
}
|
||||||
active.set(tid, tgt);
|
active.set(tid, tgt);
|
||||||
wantTitle(tgt);
|
|
||||||
const targetMap = new Map();
|
const targetMap = new Map();
|
||||||
const extras = startSignalMap.get(tgt);
|
const extras = startSignalMap.get(tgt);
|
||||||
if (extras) extras.forEach(et => targetMap.set(et.block, et.hook));
|
if (extras) extras.forEach(et => targetMap.set(et.block, et.hook));
|
||||||
expandTargets(targetMap);
|
expandTargets(targetMap);
|
||||||
const hasTargets = targetMap.size > 0;
|
const hasTargets = targetMap.size > 0;
|
||||||
const directEnds = [];
|
const directEnds = [];
|
||||||
|
noTitleAfter.add(rows.length - 1);
|
||||||
const startCells = trackIds.map(t => {
|
const startCells = trackIds.map(t => {
|
||||||
if (t === tid) return { blockId: tgt, segment: 'start', event: 'START', isSignal: hasTargets };
|
if (t === tid) return { blockId: tgt, segment: 'start', event: 'START', isSignal: hasTargets };
|
||||||
const cell = applyTargets(targetMap, t, false);
|
const cell = applyTargets(targetMap, t, false);
|
||||||
@@ -299,7 +281,6 @@ function render(data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
flush();
|
flush();
|
||||||
emitTitles();
|
|
||||||
|
|
||||||
const rowClass = isCue ? 'cue-row' : 'sig-row';
|
const rowClass = isCue ? 'cue-row' : 'sig-row';
|
||||||
|
|
||||||
@@ -339,7 +320,6 @@ function render(data) {
|
|||||||
|
|
||||||
data.triggers.forEach(t => processTrigger(t));
|
data.triggers.forEach(t => processTrigger(t));
|
||||||
flush();
|
flush();
|
||||||
emitTitles();
|
|
||||||
|
|
||||||
const stillActive = [];
|
const stillActive = [];
|
||||||
for (const [tid, bid] of active) {
|
for (const [tid, bid] of active) {
|
||||||
@@ -353,6 +333,102 @@ function render(data) {
|
|||||||
addRow(cells);
|
addRow(cells);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const blockOpenEnd = new Map();
|
||||||
|
const blockCloseStart = new Map();
|
||||||
|
const blockRange = new Map();
|
||||||
|
rows.forEach((row, idx) => {
|
||||||
|
row.cells.forEach(cell => {
|
||||||
|
if (!cell.blockId) return;
|
||||||
|
const bid = cell.blockId;
|
||||||
|
if (!blockRange.has(bid)) blockRange.set(bid, { first: idx, last: idx });
|
||||||
|
else blockRange.get(bid).last = idx;
|
||||||
|
if (cell.event === 'START') blockOpenEnd.set(bid, Math.max(blockOpenEnd.get(bid) || -1, idx));
|
||||||
|
if (cell.event === 'FADE_OUT' || cell.event === 'END') blockCloseStart.set(bid, Math.min(blockCloseStart.get(bid) || Infinity, idx));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const titlesToPlace = [];
|
||||||
|
for (const [bid, range] of blockRange) {
|
||||||
|
if (blockMap.get(bid).type === 'cue') continue;
|
||||||
|
const openEnd = blockOpenEnd.get(bid) || range.first;
|
||||||
|
const closeStart = blockCloseStart.has(bid) ? blockCloseStart.get(bid) : Infinity;
|
||||||
|
let afterRow;
|
||||||
|
if (closeStart === Infinity) {
|
||||||
|
afterRow = openEnd;
|
||||||
|
} else if (openEnd + 1 <= closeStart - 1) {
|
||||||
|
afterRow = Math.floor((openEnd + closeStart - 1) / 2);
|
||||||
|
} else {
|
||||||
|
afterRow = openEnd;
|
||||||
|
}
|
||||||
|
titlesToPlace.push({ bid, track: blockTrack(bid), afterRow, validFrom: openEnd, validTo: closeStart === Infinity ? range.last : closeStart - 1 });
|
||||||
|
}
|
||||||
|
titlesToPlace.sort((a, b) => a.afterRow - b.afterRow);
|
||||||
|
|
||||||
|
const titleGroups = [];
|
||||||
|
titlesToPlace.forEach(t => {
|
||||||
|
let best = -1;
|
||||||
|
let bestDist = Infinity;
|
||||||
|
for (let i = 0; i < titleGroups.length; i++) {
|
||||||
|
const group = titleGroups[i];
|
||||||
|
const iFrom = Math.max(group.validFrom, t.validFrom);
|
||||||
|
const iTo = Math.min(group.validTo, t.validTo);
|
||||||
|
if (iFrom > iTo) continue;
|
||||||
|
if (group.titles.some(gt => gt.track === t.track)) continue;
|
||||||
|
let candidate = group.afterRow;
|
||||||
|
if (candidate < iFrom || candidate > iTo) candidate = Math.floor((iFrom + iTo) / 2);
|
||||||
|
if (noTitleAfter.has(candidate)) {
|
||||||
|
let found = false;
|
||||||
|
for (let r = candidate + 1; r <= iTo; r++) {
|
||||||
|
if (!noTitleAfter.has(r)) { candidate = r; found = true; break; }
|
||||||
|
}
|
||||||
|
if (!found) continue;
|
||||||
|
}
|
||||||
|
const dist = Math.abs(candidate - t.afterRow);
|
||||||
|
if (dist < bestDist) { best = i; bestDist = dist; }
|
||||||
|
}
|
||||||
|
if (best >= 0) {
|
||||||
|
const group = titleGroups[best];
|
||||||
|
group.validFrom = Math.max(group.validFrom, t.validFrom);
|
||||||
|
group.validTo = Math.min(group.validTo, t.validTo);
|
||||||
|
if (group.afterRow < group.validFrom || group.afterRow > group.validTo) {
|
||||||
|
group.afterRow = Math.floor((group.validFrom + group.validTo) / 2);
|
||||||
|
}
|
||||||
|
if (noTitleAfter.has(group.afterRow)) {
|
||||||
|
for (let r = group.afterRow + 1; r <= group.validTo; r++) {
|
||||||
|
if (!noTitleAfter.has(r)) { group.afterRow = r; break; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
group.titles.push({ bid: t.bid, track: t.track });
|
||||||
|
} else {
|
||||||
|
let pos = t.afterRow;
|
||||||
|
if (noTitleAfter.has(pos)) {
|
||||||
|
for (let r = pos + 1; r <= t.validTo; r++) {
|
||||||
|
if (!noTitleAfter.has(r)) { pos = r; break; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
titleGroups.push({ afterRow: pos, validFrom: t.validFrom, validTo: t.validTo, titles: [{ bid: t.bid, track: t.track }] });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
titleGroups.sort((a, b) => a.afterRow - b.afterRow);
|
||||||
|
|
||||||
|
const finalRows = [];
|
||||||
|
let tgIdx = 0;
|
||||||
|
rows.forEach((row, idx) => {
|
||||||
|
finalRows.push(row);
|
||||||
|
while (tgIdx < titleGroups.length && titleGroups[tgIdx].afterRow === idx) {
|
||||||
|
const group = titleGroups[tgIdx];
|
||||||
|
const titleCells = trackIds.map((tid, colIdx) => {
|
||||||
|
const t = group.titles.find(gt => gt.track === tid);
|
||||||
|
if (t) return { blockId: t.bid, segment: 'mid', title: blockMap.get(t.bid).name };
|
||||||
|
const prev = row.cells[colIdx];
|
||||||
|
if (prev.blockId && prev.segment !== 'end') return { blockId: prev.blockId, segment: 'mid' };
|
||||||
|
return { empty: true };
|
||||||
|
});
|
||||||
|
finalRows.push({ cells: titleCells, rowClass: '' });
|
||||||
|
tgIdx++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const timeline = document.getElementById('timeline');
|
const timeline = document.getElementById('timeline');
|
||||||
const trackNames = { [CUE_TRACK]: 'Cue' };
|
const trackNames = { [CUE_TRACK]: 'Cue' };
|
||||||
data.tracks.forEach(t => { trackNames[t.id] = t.name; });
|
data.tracks.forEach(t => { trackNames[t.id] = t.name; });
|
||||||
@@ -365,7 +441,7 @@ function render(data) {
|
|||||||
timeline.appendChild(th);
|
timeline.appendChild(th);
|
||||||
});
|
});
|
||||||
|
|
||||||
rows.forEach(row => {
|
finalRows.forEach(row => {
|
||||||
row.cells.forEach(cell => {
|
row.cells.forEach(cell => {
|
||||||
const div = document.createElement('div');
|
const div = document.createElement('div');
|
||||||
let cls = 'cell';
|
let cls = 'cell';
|
||||||
|
|||||||
Reference in New Issue
Block a user