diff --git a/src/App.svelte b/src/App.svelte index e23ebba..f400c3b 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -62,6 +62,9 @@ // Caption state for PlayerControls let captionsEnabled = $state(true); + // Pending timeline zoom for cut-clip feature + let pendingTimelineZoom = $state<{ start: number; end: number } | null>(null); + const CONTROLS_HIDE_DELAY = 2500; const TOOLBAR_PROXIMITY = 50; @@ -191,6 +194,7 @@ const range = computeQuickClipRange(session.currentTime, session.duration, position); if (!range) return; addClip(range.start, range.end); + pendingTimelineZoom = { start: range.start, end: range.end }; setAppMode('clipper'); } @@ -462,7 +466,10 @@ }} >
- + { pendingTimelineZoom = null; }} + />
diff --git a/src/lib/components/Timeline.svelte b/src/lib/components/Timeline.svelte index 43d210e..591745e 100644 --- a/src/lib/components/Timeline.svelte +++ b/src/lib/components/Timeline.svelte @@ -22,6 +22,14 @@ type ClipDragTarget = { clipId: string; edge: 'start' | 'end' }; + let { + zoomRegion, + onZoomApplied, + }: { + zoomRegion?: { start: number; end: number } | null; + onZoomApplied?: () => void; + } = $props(); + let canvas = $state(null); let minimapCanvas = $state(null); let containerEl = $state(null); @@ -189,6 +197,19 @@ } }); + $effect(() => { + if (zoomRegion && session.duration > 0) { + const clipLen = zoomRegion.end - zoomRegion.start; + const pad = Math.max(clipLen * 0.2, 2); + const vs = Math.max(0, zoomRegion.start - pad); + const ve = Math.min(session.duration, zoomRegion.end + pad); + timelineState.visibleStart = vs; + timelineState.visibleEnd = ve; + timelineState.zoom = session.duration / (ve - vs); + onZoomApplied?.(); + } + }); + $effect(() => { if (!containerEl) return; const observer = new ResizeObserver((entries) => {