From dfb0529a0e017d0bedd79d949e827e51c622056b Mon Sep 17 00:00:00 2001 From: skal Date: Thu, 5 Feb 2026 22:17:05 +0100 Subject: feat(timeline-editor): Final UI polish - auto-apply, stack-order priority Implemented final UI improvements: - Removed sequence info text (Start:, Priority) - Removed 'Properties updated' message - Auto-apply properties with oninput handlers - Stack-order-based priority with Up/Down buttons - Added 'Same as Above' toggle for = priority modifier New functions: autoApplyProperties(), moveEffectUp(), moveEffectDown(), toggleSamePriority() Task #57 Phase 1 complete: Interactive editor with drag-drop, resize handles, snap-to-beat, and stack-based priority. handoff(Claude): Timeline editor Phase 1 feature-complete. All dragging bugs fixed, dynamic bounds working, stack-order priority operational. Ready for Phase 2 planning. --- tools/timeline_editor/index.html | 98 +++++++++++++++++++++++++--------------- 1 file changed, 61 insertions(+), 37 deletions(-) (limited to 'tools') diff --git a/tools/timeline_editor/index.html b/tools/timeline_editor/index.html index c6c54bd..5ab8ed0 100644 --- a/tools/timeline_editor/index.html +++ b/tools/timeline_editor/index.html @@ -663,28 +663,12 @@ seq._yPosition = cumulativeY; cumulativeY += seqHeight + sequenceGap; - // Format time display based on mode (show visual start, not seq.startTime) - let seqTimeDisplay; - if (showBeats) { - const beatDuration = 60.0 / bpm; - const startBeat = (seqVisualStart / beatDuration).toFixed(1); - seqTimeDisplay = `Start: ${startBeat}b`; - } else { - seqTimeDisplay = `Start: ${seqVisualStart.toFixed(2)}s`; - } - // Create sequence name overlay (large, centered, fades on hover) const seqNameDiv = document.createElement('div'); seqNameDiv.className = 'sequence-name'; seqNameDiv.textContent = seq.name || `Sequence ${seqIndex + 1}`; - // Create small info text in corner - const seqInfoDiv = document.createElement('div'); - seqInfoDiv.className = 'sequence-info'; - seqInfoDiv.innerHTML = `${seqTimeDisplay} | Priority: ${seq.priority}`; - seqDiv.appendChild(seqNameDiv); - seqDiv.appendChild(seqInfoDiv); if (selectedItem && selectedItem.type === 'sequence' && selectedItem.index === seqIndex) { seqDiv.classList.add('selected'); @@ -912,70 +896,110 @@ propertiesContent.innerHTML = `
- +
- -
-
- - +
- `; } else if (selectedItem.type === 'effect') { const effect = sequences[selectedItem.seqIndex].effects[selectedItem.effectIndex]; + const effects = sequences[selectedItem.seqIndex].effects; + const canMoveUp = selectedItem.effectIndex < effects.length - 1; + const canMoveDown = selectedItem.effectIndex > 0; + const samePriority = effect.priorityModifier === '='; + propertiesContent.innerHTML = `
- +
- +
- +
- - + +
- - + +
+ + +
+
- `; } } - function applyProperties() { + // Auto-apply properties on input change (no Apply button needed) + function autoApplyProperties() { if (!selectedItem) return; if (selectedItem.type === 'sequence') { const seq = sequences[selectedItem.index]; seq.name = document.getElementById('propName').value; seq.startTime = parseFloat(document.getElementById('propStartTime').value); - seq.priority = parseInt(document.getElementById('propPriority').value); } else if (selectedItem.type === 'effect') { const effect = sequences[selectedItem.seqIndex].effects[selectedItem.effectIndex]; effect.className = document.getElementById('propClassName').value; effect.startTime = parseFloat(document.getElementById('propStartTime').value); effect.endTime = parseFloat(document.getElementById('propEndTime').value); - effect.priority = parseInt(document.getElementById('propPriority').value); effect.args = document.getElementById('propArgs').value; } // Re-render timeline (recalculates sequence bounds) renderTimeline(); + } - // Update properties panel to reflect any calculated changes - updateProperties(); + // Move effect up in stack (higher priority) + function moveEffectUp() { + if (!selectedItem || selectedItem.type !== 'effect') return; - showMessage('Properties updated', 'success'); + const effects = sequences[selectedItem.seqIndex].effects; + const index = selectedItem.effectIndex; + + if (index < effects.length - 1) { + // Swap with effect above + [effects[index], effects[index + 1]] = [effects[index + 1], effects[index]]; + selectedItem.effectIndex = index + 1; + renderTimeline(); + updateProperties(); + } + } + + // Move effect down in stack (lower priority) + function moveEffectDown() { + if (!selectedItem || selectedItem.type !== 'effect') return; + + const effects = sequences[selectedItem.seqIndex].effects; + const index = selectedItem.effectIndex; + + if (index > 0) { + // Swap with effect below + [effects[index], effects[index - 1]] = [effects[index - 1], effects[index]]; + selectedItem.effectIndex = index - 1; + renderTimeline(); + updateProperties(); + } + } + + // Toggle same priority as previous effect (= modifier) + function toggleSamePriority() { + if (!selectedItem || selectedItem.type !== 'effect') return; + + const effect = sequences[selectedItem.seqIndex].effects[selectedItem.effectIndex]; + effect.priorityModifier = effect.priorityModifier === '=' ? '+' : '='; + updateProperties(); } // File operations -- cgit v1.2.3