feat: timeline right-click context menu with mark/seek/delete actions

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-22 16:53:43 -04:00
parent 27c199e090
commit 8c271773bd
2 changed files with 273 additions and 0 deletions

View File

@@ -1,9 +1,14 @@
<script lang="ts">
import TimelineContextMenu from '$lib/components/TimelineContextMenu.svelte';
import type { ContextMenuAction } from '$lib/components/TimelineContextMenu.svelte';
import { session } from '$lib/stores/videoSession.svelte';
import {
clipStore,
selectClip,
updateClip,
markInPoint,
markOutPoint,
removeClip,
} from '$lib/stores/clips.svelte';
import { drawTimeline, type TimelineState } from '$lib/timeline/renderer';
import {
@@ -37,6 +42,11 @@
let minimapDragStartX = $state(0);
let minimapDragStartVisibleStart = $state(0);
let mouseX = $state<number | null>(null);
let contextMenu = $state<{
x: number;
y: number;
time: number;
} | null>(null);
let isZoomed = $derived(timelineState.zoom > 1.01);
@@ -235,7 +245,49 @@
}
}
function handleContextMenu(e: MouseEvent) {
e.preventDefault();
const x = getCanvasX(e);
const time = computeClickTime(x, timelineState, session.duration);
seekTo(time);
contextMenu = {
x: e.clientX,
y: e.clientY,
time,
};
}
function handleContextMenuAction(action: ContextMenuAction) {
if (!contextMenu) return;
switch (action.type) {
case 'mark-in':
markInPoint(contextMenu.time);
break;
case 'mark-out':
markOutPoint(contextMenu.time);
break;
case 'go-to-clip-start': {
const clip = clipStore.clips.find((c) => c.id === clipStore.selectedClipId);
if (clip) seekTo(clip.startTime);
break;
}
case 'go-to-clip-end': {
const clip = clipStore.clips.find((c) => c.id === clipStore.selectedClipId);
if (clip) seekTo(clip.endTime);
break;
}
case 'delete-clip':
if (clipStore.selectedClipId) removeClip(clipStore.selectedClipId);
break;
default: {
const _exhaustive: never = action;
void _exhaustive;
}
}
}
function handleMouseDown(e: MouseEvent) {
contextMenu = null;
const x = getCanvasX(e);
const y = getCanvasY(e);
@@ -298,6 +350,7 @@
function handleWheel(e: WheelEvent) {
e.preventDefault();
contextMenu = null;
if (e.deltaX !== 0) {
const result = panBy(timelineState, e.deltaX, session.duration);
@@ -395,10 +448,24 @@
onmouseup={handleMouseUp}
onmouseleave={handleMouseLeave}
onwheel={handleWheel}
oncontextmenu={handleContextMenu}
></canvas>
</div>
</div>
{#if contextMenu}
<TimelineContextMenu
x={contextMenu.x}
y={contextMenu.y}
time={contextMenu.time}
selectedClipId={clipStore.selectedClipId}
onAction={handleContextMenuAction}
onClose={() => {
contextMenu = null;
}}
/>
{/if}
<svelte:window
onmousemove={handleMinimapMouseMove}
onmouseup={handleMinimapMouseUp}