summaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
authorskal <pascal.massimino@gmail.com>2026-02-05 21:54:38 +0100
committerskal <pascal.massimino@gmail.com>2026-02-05 21:54:38 +0100
commit1b57c0d7bfbdddfb9060a54df220ee51d9246f05 (patch)
tree3db2e69946fa3fe2a57f68564d93cd036568bf64 /tools
parent58ad5c16d6f1b2afc51ec5b36ed08643643e5fd0 (diff)
feat(timeline-editor): Add re-order button and 10% viewport slack
Feature #1: Re-order Sequences by Time - Added "🔄 Re-order by Time" button to controls - Sorts sequences by startTime (ascending order) - Preserves focus on currently active sequence - Algorithm: 1. Store reference to current active sequence (lastActiveSeqIndex) 2. Sort sequences array: sequences.sort((a, b) => a.startTime - b.startTime) 3. Re-render timeline (recalculates _yPosition for all) 4. Find new index of previously active sequence 5. Scroll to its new Y position 6. Update lastActiveSeqIndex to new index - Button enabled when file is loaded - Shows success message after re-ordering - Useful when sequences are added/moved out of order Use Case: Before: Seq2(10s), Seq0(0s), Seq1(5s) ← Out of order After: Seq0(0s), Seq1(5s), Seq2(10s) ← Chronological order Feature #2: 10% Viewport Slack for Visual Comfort - Added headroom when calculating current time during scroll - Previously: currentTime = scrollLeft / pixelsPerSecond - Now: currentTime = (scrollLeft / pixelsPerSecond) + slack - Slack = 10% of viewport width in time units Benefits: - Sequences don't get targeted when right at left edge - More comfortable visual positioning - Sequences become active when ~10% into viewport - Prevents "edge hugging" during diagonal scroll Example: Viewport width: 1000px, pixelsPerSecond: 100 Slack = (1000 / 100) * 0.1 = 1.0 second scrollLeft = 500px → currentTime = 5.0s + 1.0s = 6.0s Sequence at 6.0s becomes active (not 5.0s) Technical Details: - viewportWidth = timelineContainer.clientWidth - slack = (viewportWidth / pixelsPerSecond) * 0.1 - Applied before finding target sequence - Works with any zoom level (pixelsPerSecond changes) - Dynamic calculation on every wheel event
Diffstat (limited to 'tools')
-rw-r--r--tools/timeline_editor/index.html35
1 files changed, 32 insertions, 3 deletions
diff --git a/tools/timeline_editor/index.html b/tools/timeline_editor/index.html
index 94e88f6..d450522 100644
--- a/tools/timeline_editor/index.html
+++ b/tools/timeline_editor/index.html
@@ -372,6 +372,7 @@
<button id="saveBtn" disabled>💾 Save demo.seq</button>
<button id="addSequenceBtn" disabled>➕ Add Sequence</button>
<button id="deleteBtn" disabled>🗑️ Delete Selected</button>
+ <button id="reorderBtn" disabled>🔄 Re-order by Time</button>
</div>
<div class="zoom-controls">
@@ -423,6 +424,7 @@
const saveBtn = document.getElementById('saveBtn');
const addSequenceBtn = document.getElementById('addSequenceBtn');
const deleteBtn = document.getElementById('deleteBtn');
+ const reorderBtn = document.getElementById('reorderBtn');
const propertiesPanel = document.getElementById('propertiesPanel');
const propertiesContent = document.getElementById('propertiesContent');
const messageArea = document.getElementById('messageArea');
@@ -868,6 +870,7 @@
renderTimeline();
saveBtn.disabled = false;
addSequenceBtn.disabled = false;
+ reorderBtn.disabled = false;
showMessage(`Loaded ${currentFile} - ${sequences.length} sequences`, 'success');
} catch (err) {
showMessage(`Error parsing file: ${err.message}`, 'error');
@@ -916,6 +919,30 @@
showMessage('Item deleted', 'success');
});
+ // Re-order sequences by time
+ reorderBtn.addEventListener('click', () => {
+ // Store current active sequence (if any)
+ const currentActiveSeq = lastActiveSeqIndex >= 0 ? sequences[lastActiveSeqIndex] : null;
+
+ // Sort sequences by start time (ascending)
+ sequences.sort((a, b) => a.startTime - b.startTime);
+
+ // Re-render timeline
+ renderTimeline();
+
+ // Restore focus on previously active sequence
+ if (currentActiveSeq) {
+ const newIndex = sequences.indexOf(currentActiveSeq);
+ if (newIndex >= 0 && sequences[newIndex]._yPosition !== undefined) {
+ // Scroll to keep it in view
+ timelineContainer.scrollTop = sequences[newIndex]._yPosition;
+ lastActiveSeqIndex = newIndex;
+ }
+ }
+
+ showMessage('Sequences re-ordered by start time', 'success');
+ });
+
// Zoom
zoomSlider.addEventListener('input', (e) => {
const zoom = parseInt(e.target.value);
@@ -963,12 +990,14 @@
// Horizontal scroll
timelineContainer.scrollLeft += e.deltaY;
- // Calculate current time position (left edge of viewport)
+ // Calculate current time position with 10% headroom for visual comfort
const currentScrollLeft = timelineContainer.scrollLeft;
- const currentTime = currentScrollLeft / pixelsPerSecond;
+ const viewportWidth = timelineContainer.clientWidth;
+ const slack = (viewportWidth / pixelsPerSecond) * 0.1; // 10% of viewport width in seconds
+ const currentTime = (currentScrollLeft / pixelsPerSecond) + slack;
// Find the closest sequence that should be visible at current time
- // (the last sequence that starts before or at current time)
+ // (the last sequence that starts before or at current time + slack)
let targetSeqIndex = 0;
for (let i = 0; i < sequences.length; i++) {
if (sequences[i].startTime <= currentTime) {