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