Fix editor timeline interactions

This commit is contained in:
Zac Gaetano 2026-05-25 01:10:45 -04:00
parent de895dd7f8
commit 78539ec8b0

View file

@ -28,8 +28,11 @@
playheadFrames: 0,
activeTool: 'select', // 'select' | 'razor' | 'hand'
selectedId: null,
contextMenu: null,
onClipsChanged: null, // callback(clips[])
onPlayheadMoved: null, // callback(frames)
onClipContextMenu: null, // callback({clip, x, y})
onExternalDrop: null, // callback({track, timelineFrames, dataTransfer})
};
let _uid = 0;
@ -46,6 +49,8 @@
s.scale = options.scale || 100;
s.onClipsChanged = options.onClipsChanged || null;
s.onPlayheadMoved = options.onPlayheadMoved || null;
s.onClipContextMenu = options.onClipContextMenu || null;
s.onExternalDrop = options.onExternalDrop || null;
container.innerHTML = '';
container.style.cssText = [
@ -96,6 +101,8 @@
area.dataset.trackId = t.id;
area.style.cssText = 'flex:1;position:relative;overflow:visible;';
area.addEventListener('click', _onAreaClick);
area.addEventListener('dragover', _onAreaDragOver);
area.addEventListener('drop', _onAreaDrop);
row.appendChild(area);
s.tracksEl.appendChild(row);
@ -241,7 +248,7 @@
// Clip label
var lbl = document.createElement('span');
lbl.textContent = clip.display_name || 'Clip';
lbl.textContent = clip.display_name || clip.name || 'Clip';
lbl.style.cssText = [
'font-size:9px', 'font-weight:500',
'color:var(--text-secondary)',
@ -250,6 +257,14 @@
].join(';');
el.appendChild(lbl);
el.addEventListener('contextmenu', function (e) {
e.preventDefault();
e.stopPropagation();
s.selectedId = clip._id;
_renderClips();
if (s.onClipContextMenu) s.onClipContextMenu({ clip: Object.assign({}, clip), x: e.clientX, y: e.clientY });
});
if (s.activeTool === 'select') {
// Trim handles (shown on hover)
var lh = _makeHandle('left');
@ -268,6 +283,7 @@
// Drag to move (body only)
el.addEventListener('mousedown', function (e) {
if (e.button !== 0) return;
if (e.target === lh || e.target === rh) return;
e.preventDefault();
s.selectedId = clip._id;
@ -279,10 +295,12 @@
});
lh.addEventListener('mousedown', function (e) {
if (e.button !== 0) return;
e.stopPropagation();
_onTrimStart(e, clip, 'left');
});
rh.addEventListener('mousedown', function (e) {
if (e.button !== 0) return;
e.stopPropagation();
_onTrimStart(e, clip, 'right');
});
@ -411,7 +429,7 @@
if (s.onClipsChanged) s.onClipsChanged(s.clips.slice());
}
// ── Area click (deselect) ───────────────────────────────────────────────────
// ── Area click/drop ─────────────────────────────────────────────────────────
function _onAreaClick(e) {
if (e.target !== e.currentTarget) return; // hit a clip, not empty space
@ -421,6 +439,23 @@
}
}
function _onAreaDragOver(e) {
if (!s.onExternalDrop) return;
e.preventDefault();
e.dataTransfer.dropEffect = 'copy';
}
function _onAreaDrop(e) {
if (!s.onExternalDrop) return;
e.preventDefault();
var area = e.currentTarget;
var rect = area.getBoundingClientRect();
var x = e.clientX - rect.left + s.container.scrollLeft;
var track = Number(area.dataset.trackId);
var timelineFrames = Math.max(0, pxToFrames(x));
s.onExternalDrop({ track: track, timelineFrames: timelineFrames, dataTransfer: e.dataTransfer });
}
// ── Keyboard ────────────────────────────────────────────────────────────────
function _onKeyDown(e) {
@ -515,6 +550,7 @@
s.tracksEl.addEventListener('mousedown', function (e) {
if (s.activeTool !== 'hand') return;
if (e.button !== 0) return;
dragging = true;
startX = e.clientX;
startScroll = s.container.scrollLeft;
@ -537,7 +573,7 @@
// ── Add clip at playhead ─────────────────────────────────────────────────────
function addClip(asset, srcIn, srcOut, track) {
function addClip(asset, srcIn, srcOut, track, timelineInFrames) {
track = track !== undefined ? track : 0;
srcIn = srcIn || 0;
srcOut = srcOut || (asset.duration_ms ? asset.duration_ms / 1000 : 10);
@ -546,13 +582,13 @@
// regardless of sequence frame rate (not hardcoded to 59.94).
var srcInFr = Math.round(srcIn * s.fps);
var srcOutFr = Math.round(srcOut * s.fps);
var tlInFr = s.playheadFrames;
var tlInFr = timelineInFrames !== undefined ? Math.max(0, Math.round(timelineInFrames)) : s.playheadFrames;
var tlOutFr = tlInFr + (srcOutFr - srcInFr);
var clip = {
_id: uid(),
asset_id: asset.id || asset.asset_id,
display_name: asset.display_name || asset.filename || 'Clip',
display_name: asset.display_name || asset.filename || asset.name || 'Clip',
duration_ms: asset.duration_ms || null,
streamUrl: asset.streamUrl || null,
track: track,