2026-09-21 11:03:24 -04:00
|
|
|
<script lang="ts">
|
2026-09-22 16:53:43 -04:00
|
|
|
import TimelineContextMenu from '$lib/components/TimelineContextMenu.svelte';
|
|
|
|
|
import type { ContextMenuAction } from '$lib/components/TimelineContextMenu.svelte';
|
2026-09-21 11:03:24 -04:00
|
|
|
import { session } from '$lib/stores/videoSession.svelte';
|
2026-09-21 11:05:45 -04:00
|
|
|
import {
|
|
|
|
|
clipStore,
|
|
|
|
|
selectClip,
|
|
|
|
|
updateClip,
|
2026-09-22 16:53:43 -04:00
|
|
|
markInPoint,
|
|
|
|
|
markOutPoint,
|
|
|
|
|
removeClip,
|
2026-09-21 11:05:45 -04:00
|
|
|
} from '$lib/stores/clips.svelte';
|
2026-09-21 11:03:24 -04:00
|
|
|
import { drawTimeline, type TimelineState } from '$lib/timeline/renderer';
|
|
|
|
|
import {
|
|
|
|
|
computeZoom,
|
chore: stage all pending work — caption styling, media server, processing modal, docs, summaries
Includes:
- Extended caption styling (font, shadow, dimmed color, bg toggle)
- Media server, subtitle downloader, VTT parser, processing modal
- Waveform tiers, thumbnail/timeline improvements, transport controls
- Hybrid download model, dependency management, clip export enhancements
- 21 chat summaries, 2 implementation plans, 2 design specs
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-22 10:48:16 -04:00
|
|
|
panBy,
|
2026-09-21 11:03:24 -04:00
|
|
|
handleClick as computeClickTime,
|
|
|
|
|
} from '$lib/timeline/interactions';
|
2026-09-21 11:05:45 -04:00
|
|
|
import { hitTestClip } from '$lib/timeline/clipRenderer';
|
chore: stage all pending work — caption styling, media server, processing modal, docs, summaries
Includes:
- Extended caption styling (font, shadow, dimmed color, bg toggle)
- Media server, subtitle downloader, VTT parser, processing modal
- Waveform tiers, thumbnail/timeline improvements, transport controls
- Hybrid download model, dependency management, clip export enhancements
- 21 chat summaries, 2 implementation plans, 2 design specs
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-22 10:48:16 -04:00
|
|
|
import { drawWaveform, type WaveformData } from '$lib/timeline/waveformRenderer';
|
|
|
|
|
import { setThumbnailLoadCallback } from '$lib/timeline/thumbnailRenderer';
|
2026-09-21 11:05:45 -04:00
|
|
|
|
|
|
|
|
type ClipDragTarget = { clipId: string; edge: 'start' | 'end' };
|
2026-09-21 11:03:24 -04:00
|
|
|
|
|
|
|
|
let canvas = $state<HTMLCanvasElement | null>(null);
|
chore: stage all pending work — caption styling, media server, processing modal, docs, summaries
Includes:
- Extended caption styling (font, shadow, dimmed color, bg toggle)
- Media server, subtitle downloader, VTT parser, processing modal
- Waveform tiers, thumbnail/timeline improvements, transport controls
- Hybrid download model, dependency management, clip export enhancements
- 21 chat summaries, 2 implementation plans, 2 design specs
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-22 10:48:16 -04:00
|
|
|
let minimapCanvas = $state<HTMLCanvasElement | null>(null);
|
2026-09-21 11:03:24 -04:00
|
|
|
let containerEl = $state<HTMLDivElement | null>(null);
|
chore: stage all pending work — caption styling, media server, processing modal, docs, summaries
Includes:
- Extended caption styling (font, shadow, dimmed color, bg toggle)
- Media server, subtitle downloader, VTT parser, processing modal
- Waveform tiers, thumbnail/timeline improvements, transport controls
- Hybrid download model, dependency management, clip export enhancements
- 21 chat summaries, 2 implementation plans, 2 design specs
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-22 10:48:16 -04:00
|
|
|
let pendingDraw = 0;
|
|
|
|
|
let pendingMinimapDraw = 0;
|
2026-09-21 11:03:24 -04:00
|
|
|
|
|
|
|
|
let timelineState = $state<TimelineState>({
|
|
|
|
|
visibleStart: 0,
|
|
|
|
|
visibleEnd: 60,
|
|
|
|
|
zoom: 1,
|
|
|
|
|
width: 600,
|
|
|
|
|
height: 160,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let isDragging = $state(false);
|
2026-09-21 11:05:45 -04:00
|
|
|
let dragTarget = $state<ClipDragTarget | null>(null);
|
chore: stage all pending work — caption styling, media server, processing modal, docs, summaries
Includes:
- Extended caption styling (font, shadow, dimmed color, bg toggle)
- Media server, subtitle downloader, VTT parser, processing modal
- Waveform tiers, thumbnail/timeline improvements, transport controls
- Hybrid download model, dependency management, clip export enhancements
- 21 chat summaries, 2 implementation plans, 2 design specs
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-22 10:48:16 -04:00
|
|
|
let isMinimapDragging = $state(false);
|
|
|
|
|
let minimapDragStartX = $state(0);
|
|
|
|
|
let minimapDragStartVisibleStart = $state(0);
|
2026-09-22 16:51:56 -04:00
|
|
|
let mouseX = $state<number | null>(null);
|
2026-09-22 16:53:43 -04:00
|
|
|
let contextMenu = $state<{
|
|
|
|
|
x: number;
|
|
|
|
|
y: number;
|
|
|
|
|
time: number;
|
|
|
|
|
} | null>(null);
|
chore: stage all pending work — caption styling, media server, processing modal, docs, summaries
Includes:
- Extended caption styling (font, shadow, dimmed color, bg toggle)
- Media server, subtitle downloader, VTT parser, processing modal
- Waveform tiers, thumbnail/timeline improvements, transport controls
- Hybrid download model, dependency management, clip export enhancements
- 21 chat summaries, 2 implementation plans, 2 design specs
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-22 10:48:16 -04:00
|
|
|
|
|
|
|
|
let isZoomed = $derived(timelineState.zoom > 1.01);
|
|
|
|
|
|
|
|
|
|
// Construct waveform data from pre-computed tiers — pure reactive, no IPC
|
|
|
|
|
let waveformData = $derived<WaveformData>({
|
|
|
|
|
tiers: session.waveformTiers,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Register thumbnail load callback
|
|
|
|
|
$effect(() => {
|
|
|
|
|
setThumbnailLoadCallback(() => requestDraw());
|
|
|
|
|
return () => setThumbnailLoadCallback(null);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
function requestDraw() {
|
|
|
|
|
if (!pendingDraw) {
|
|
|
|
|
pendingDraw = requestAnimationFrame(() => {
|
|
|
|
|
drawMainCanvas();
|
|
|
|
|
pendingDraw = 0;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function requestMinimapDraw() {
|
|
|
|
|
if (!pendingMinimapDraw) {
|
|
|
|
|
pendingMinimapDraw = requestAnimationFrame(() => {
|
|
|
|
|
drawMinimapCanvas();
|
|
|
|
|
pendingMinimapDraw = 0;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function drawMainCanvas() {
|
|
|
|
|
if (!canvas) return;
|
|
|
|
|
const ctx = canvas.getContext('2d');
|
|
|
|
|
if (!ctx) return;
|
|
|
|
|
ctx.save();
|
|
|
|
|
ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
|
|
|
|
|
drawTimeline(
|
|
|
|
|
ctx,
|
|
|
|
|
timelineState,
|
|
|
|
|
session.currentTime,
|
|
|
|
|
session.duration,
|
|
|
|
|
clipStore.clips,
|
|
|
|
|
clipStore.selectedClipId,
|
|
|
|
|
clipStore.pendingInPoint,
|
|
|
|
|
waveformData,
|
2026-09-22 14:34:58 -04:00
|
|
|
session.thumbnailSpritesheets,
|
2026-09-22 16:51:56 -04:00
|
|
|
session.status === 'ready',
|
|
|
|
|
mouseX
|
chore: stage all pending work — caption styling, media server, processing modal, docs, summaries
Includes:
- Extended caption styling (font, shadow, dimmed color, bg toggle)
- Media server, subtitle downloader, VTT parser, processing modal
- Waveform tiers, thumbnail/timeline improvements, transport controls
- Hybrid download model, dependency management, clip export enhancements
- 21 chat summaries, 2 implementation plans, 2 design specs
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-22 10:48:16 -04:00
|
|
|
);
|
|
|
|
|
ctx.restore();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function drawMinimapCanvas() {
|
|
|
|
|
if (!minimapCanvas || !isZoomed || session.duration <= 0) return;
|
|
|
|
|
const ctx = minimapCanvas.getContext('2d');
|
|
|
|
|
if (!ctx) return;
|
|
|
|
|
const w = minimapCanvas.width / window.devicePixelRatio;
|
|
|
|
|
const h = minimapCanvas.height / window.devicePixelRatio;
|
|
|
|
|
|
|
|
|
|
ctx.save();
|
|
|
|
|
ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
|
|
|
|
|
ctx.clearRect(0, 0, w, h);
|
|
|
|
|
|
|
|
|
|
ctx.fillStyle = '#12121e';
|
|
|
|
|
ctx.fillRect(0, 0, w, h);
|
|
|
|
|
|
|
|
|
|
// Minimap always shows the overview tier at full duration
|
|
|
|
|
if (session.waveformTiers) {
|
|
|
|
|
const fullState: TimelineState = {
|
|
|
|
|
visibleStart: 0,
|
|
|
|
|
visibleEnd: session.duration,
|
|
|
|
|
zoom: 1,
|
|
|
|
|
width: w,
|
|
|
|
|
height: h,
|
|
|
|
|
};
|
|
|
|
|
const minimapWaveform: WaveformData = {
|
|
|
|
|
tiers: session.waveformTiers,
|
|
|
|
|
};
|
|
|
|
|
drawWaveform(ctx, fullState, minimapWaveform, session.duration, 0, h);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const vpLeft = (timelineState.visibleStart / session.duration) * w;
|
|
|
|
|
const vpRight = (timelineState.visibleEnd / session.duration) * w;
|
|
|
|
|
|
|
|
|
|
ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
|
|
|
|
|
ctx.fillRect(0, 0, vpLeft, h);
|
|
|
|
|
ctx.fillRect(vpRight, 0, w - vpRight, h);
|
|
|
|
|
|
|
|
|
|
ctx.strokeStyle = '#89b4fa';
|
|
|
|
|
ctx.lineWidth = 1.5;
|
|
|
|
|
ctx.strokeRect(vpLeft, 0, vpRight - vpLeft, h);
|
|
|
|
|
|
|
|
|
|
const phX = (session.currentTime / session.duration) * w;
|
|
|
|
|
ctx.strokeStyle = '#ffffff';
|
|
|
|
|
ctx.lineWidth = 1;
|
|
|
|
|
ctx.beginPath();
|
|
|
|
|
ctx.moveTo(phX, 0);
|
|
|
|
|
ctx.lineTo(phX, h);
|
|
|
|
|
ctx.stroke();
|
|
|
|
|
|
|
|
|
|
ctx.restore();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Single unified redraw effect — coalesced by requestAnimationFrame
|
|
|
|
|
$effect(() => {
|
|
|
|
|
void session.currentTime;
|
|
|
|
|
void session.duration;
|
|
|
|
|
void session.waveformTiers;
|
|
|
|
|
void session.thumbnailSpritesheets;
|
|
|
|
|
void clipStore.clips;
|
|
|
|
|
void clipStore.selectedClipId;
|
|
|
|
|
void clipStore.pendingInPoint;
|
|
|
|
|
void timelineState.visibleStart;
|
|
|
|
|
void timelineState.visibleEnd;
|
|
|
|
|
void timelineState.width;
|
|
|
|
|
void timelineState.height;
|
|
|
|
|
void canvas;
|
2026-09-22 16:51:56 -04:00
|
|
|
void mouseX;
|
chore: stage all pending work — caption styling, media server, processing modal, docs, summaries
Includes:
- Extended caption styling (font, shadow, dimmed color, bg toggle)
- Media server, subtitle downloader, VTT parser, processing modal
- Waveform tiers, thumbnail/timeline improvements, transport controls
- Hybrid download model, dependency management, clip export enhancements
- 21 chat summaries, 2 implementation plans, 2 design specs
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-22 10:48:16 -04:00
|
|
|
requestDraw();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Minimap redraws
|
|
|
|
|
$effect(() => {
|
|
|
|
|
void session.currentTime;
|
|
|
|
|
void session.waveformTiers;
|
|
|
|
|
void timelineState.visibleStart;
|
|
|
|
|
void timelineState.visibleEnd;
|
|
|
|
|
void isZoomed;
|
|
|
|
|
void minimapCanvas;
|
|
|
|
|
requestMinimapDraw();
|
|
|
|
|
});
|
2026-09-21 11:03:24 -04:00
|
|
|
|
|
|
|
|
$effect(() => {
|
|
|
|
|
if (session.duration > 0) {
|
2026-09-21 11:25:28 -04:00
|
|
|
timelineState.visibleStart = 0;
|
2026-09-21 11:03:24 -04:00
|
|
|
timelineState.visibleEnd = session.duration;
|
2026-09-21 11:25:28 -04:00
|
|
|
timelineState.zoom = 1;
|
2026-09-21 11:03:24 -04:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
$effect(() => {
|
|
|
|
|
if (!containerEl) return;
|
|
|
|
|
const observer = new ResizeObserver((entries) => {
|
|
|
|
|
for (const entry of entries) {
|
|
|
|
|
timelineState.width = entry.contentRect.width;
|
|
|
|
|
timelineState.height = entry.contentRect.height;
|
|
|
|
|
if (canvas) {
|
|
|
|
|
canvas.width = entry.contentRect.width * window.devicePixelRatio;
|
|
|
|
|
canvas.height = entry.contentRect.height * window.devicePixelRatio;
|
|
|
|
|
canvas.style.width = `${entry.contentRect.width}px`;
|
|
|
|
|
canvas.style.height = `${entry.contentRect.height}px`;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
observer.observe(containerEl);
|
|
|
|
|
return () => observer.disconnect();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
$effect(() => {
|
chore: stage all pending work — caption styling, media server, processing modal, docs, summaries
Includes:
- Extended caption styling (font, shadow, dimmed color, bg toggle)
- Media server, subtitle downloader, VTT parser, processing modal
- Waveform tiers, thumbnail/timeline improvements, transport controls
- Hybrid download model, dependency management, clip export enhancements
- 21 chat summaries, 2 implementation plans, 2 design specs
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-22 10:48:16 -04:00
|
|
|
if (!minimapCanvas || !containerEl) return;
|
|
|
|
|
const observer = new ResizeObserver((entries) => {
|
|
|
|
|
for (const entry of entries) {
|
|
|
|
|
if (minimapCanvas) {
|
|
|
|
|
const w = entry.contentRect.width;
|
|
|
|
|
const h = 28;
|
|
|
|
|
minimapCanvas.width = w * window.devicePixelRatio;
|
|
|
|
|
minimapCanvas.height = h * window.devicePixelRatio;
|
|
|
|
|
minimapCanvas.style.width = `${w}px`;
|
|
|
|
|
minimapCanvas.style.height = `${h}px`;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
observer.observe(containerEl);
|
|
|
|
|
return () => observer.disconnect();
|
2026-09-21 11:03:24 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
function getCanvasX(e: MouseEvent): number {
|
|
|
|
|
if (!canvas) return 0;
|
|
|
|
|
const rect = canvas.getBoundingClientRect();
|
|
|
|
|
return e.clientX - rect.left;
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-21 11:05:45 -04:00
|
|
|
function getCanvasY(e: MouseEvent): number {
|
|
|
|
|
if (!canvas) return 0;
|
|
|
|
|
const rect = canvas.getBoundingClientRect();
|
|
|
|
|
return e.clientY - rect.top;
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-21 11:03:24 -04:00
|
|
|
function seekTo(time: number) {
|
|
|
|
|
session.currentTime = time;
|
|
|
|
|
const videoEl = document.querySelector('video');
|
|
|
|
|
if (videoEl) {
|
|
|
|
|
videoEl.currentTime = time;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-22 16:53:43 -04:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-21 11:03:24 -04:00
|
|
|
function handleMouseDown(e: MouseEvent) {
|
2026-09-22 16:53:43 -04:00
|
|
|
contextMenu = null;
|
2026-09-21 11:03:24 -04:00
|
|
|
const x = getCanvasX(e);
|
2026-09-21 11:05:45 -04:00
|
|
|
const y = getCanvasY(e);
|
|
|
|
|
|
|
|
|
|
const hit = hitTestClip(x, y, timelineState, clipStore.clips);
|
|
|
|
|
if (hit) {
|
|
|
|
|
if (hit.edge === 'body') {
|
|
|
|
|
selectClip(hit.clipId);
|
|
|
|
|
} else if (hit.edge === 'start' || hit.edge === 'end') {
|
|
|
|
|
dragTarget = { clipId: hit.clipId, edge: hit.edge };
|
|
|
|
|
selectClip(hit.clipId);
|
|
|
|
|
}
|
|
|
|
|
isDragging = true;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
chore: stage all pending work — caption styling, media server, processing modal, docs, summaries
Includes:
- Extended caption styling (font, shadow, dimmed color, bg toggle)
- Media server, subtitle downloader, VTT parser, processing modal
- Waveform tiers, thumbnail/timeline improvements, transport controls
- Hybrid download model, dependency management, clip export enhancements
- 21 chat summaries, 2 implementation plans, 2 design specs
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-22 10:48:16 -04:00
|
|
|
selectClip(null);
|
2026-09-21 11:05:45 -04:00
|
|
|
isDragging = true;
|
2026-09-21 11:03:24 -04:00
|
|
|
const targetTime = computeClickTime(x, timelineState, session.duration);
|
|
|
|
|
seekTo(targetTime);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleMouseMove(e: MouseEvent) {
|
|
|
|
|
if (!isDragging) return;
|
|
|
|
|
const x = getCanvasX(e);
|
2026-09-21 11:05:45 -04:00
|
|
|
|
|
|
|
|
if (dragTarget) {
|
|
|
|
|
const time = computeClickTime(x, timelineState, session.duration);
|
|
|
|
|
switch (dragTarget.edge) {
|
|
|
|
|
case 'start':
|
|
|
|
|
updateClip(dragTarget.clipId, { startTime: time });
|
|
|
|
|
break;
|
|
|
|
|
case 'end':
|
|
|
|
|
updateClip(dragTarget.clipId, { endTime: time });
|
|
|
|
|
break;
|
|
|
|
|
default: {
|
|
|
|
|
const _exhaustive: never = dragTarget.edge;
|
|
|
|
|
void _exhaustive;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-21 11:03:24 -04:00
|
|
|
const targetTime = computeClickTime(x, timelineState, session.duration);
|
|
|
|
|
seekTo(targetTime);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleMouseUp() {
|
|
|
|
|
isDragging = false;
|
2026-09-21 11:05:45 -04:00
|
|
|
dragTarget = null;
|
2026-09-21 11:03:24 -04:00
|
|
|
}
|
|
|
|
|
|
2026-09-22 16:51:56 -04:00
|
|
|
function handleMouseTrack(e: MouseEvent) {
|
|
|
|
|
mouseX = getCanvasX(e);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleMouseLeave() {
|
|
|
|
|
handleMouseUp();
|
|
|
|
|
mouseX = null;
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-21 11:03:24 -04:00
|
|
|
function handleWheel(e: WheelEvent) {
|
|
|
|
|
e.preventDefault();
|
2026-09-22 16:53:43 -04:00
|
|
|
contextMenu = null;
|
chore: stage all pending work — caption styling, media server, processing modal, docs, summaries
Includes:
- Extended caption styling (font, shadow, dimmed color, bg toggle)
- Media server, subtitle downloader, VTT parser, processing modal
- Waveform tiers, thumbnail/timeline improvements, transport controls
- Hybrid download model, dependency management, clip export enhancements
- 21 chat summaries, 2 implementation plans, 2 design specs
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-22 10:48:16 -04:00
|
|
|
|
|
|
|
|
if (e.deltaX !== 0) {
|
|
|
|
|
const result = panBy(timelineState, e.deltaX, session.duration);
|
|
|
|
|
timelineState.visibleStart = result.visibleStart;
|
|
|
|
|
timelineState.visibleEnd = result.visibleEnd;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-21 11:03:24 -04:00
|
|
|
const x = getCanvasX(e);
|
|
|
|
|
const anchorTime = computeClickTime(x, timelineState, session.duration);
|
|
|
|
|
const factor = e.deltaY < 0 ? 1.15 : 0.87;
|
|
|
|
|
const result = computeZoom(timelineState, anchorTime, factor, session.duration);
|
|
|
|
|
timelineState.visibleStart = result.visibleStart;
|
|
|
|
|
timelineState.visibleEnd = result.visibleEnd;
|
|
|
|
|
timelineState.zoom = result.zoom;
|
|
|
|
|
}
|
chore: stage all pending work — caption styling, media server, processing modal, docs, summaries
Includes:
- Extended caption styling (font, shadow, dimmed color, bg toggle)
- Media server, subtitle downloader, VTT parser, processing modal
- Waveform tiers, thumbnail/timeline improvements, transport controls
- Hybrid download model, dependency management, clip export enhancements
- 21 chat summaries, 2 implementation plans, 2 design specs
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-22 10:48:16 -04:00
|
|
|
|
|
|
|
|
function handleMinimapMouseDown(e: MouseEvent) {
|
|
|
|
|
if (!minimapCanvas || session.duration <= 0) return;
|
|
|
|
|
const rect = minimapCanvas.getBoundingClientRect();
|
|
|
|
|
const x = e.clientX - rect.left;
|
|
|
|
|
isMinimapDragging = true;
|
|
|
|
|
minimapDragStartX = x;
|
|
|
|
|
minimapDragStartVisibleStart = timelineState.visibleStart;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleMinimapMouseMove(e: MouseEvent) {
|
|
|
|
|
if (!isMinimapDragging || !minimapCanvas || session.duration <= 0) return;
|
|
|
|
|
const rect = minimapCanvas.getBoundingClientRect();
|
|
|
|
|
const x = e.clientX - rect.left;
|
|
|
|
|
const deltaX = x - minimapDragStartX;
|
|
|
|
|
const deltaTime = (deltaX / rect.width) * session.duration;
|
|
|
|
|
const range = timelineState.visibleEnd - timelineState.visibleStart;
|
|
|
|
|
|
|
|
|
|
let newStart = minimapDragStartVisibleStart + deltaTime;
|
|
|
|
|
let newEnd = newStart + range;
|
|
|
|
|
|
|
|
|
|
if (newStart < 0) {
|
|
|
|
|
newStart = 0;
|
|
|
|
|
newEnd = range;
|
|
|
|
|
}
|
|
|
|
|
if (newEnd > session.duration) {
|
|
|
|
|
newEnd = session.duration;
|
|
|
|
|
newStart = Math.max(0, newEnd - range);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
timelineState.visibleStart = newStart;
|
|
|
|
|
timelineState.visibleEnd = newEnd;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleMinimapMouseUp() {
|
|
|
|
|
isMinimapDragging = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleMinimapClick(e: MouseEvent) {
|
|
|
|
|
if (!minimapCanvas || session.duration <= 0) return;
|
|
|
|
|
const rect = minimapCanvas.getBoundingClientRect();
|
|
|
|
|
const x = e.clientX - rect.left;
|
|
|
|
|
const clickTime = (x / rect.width) * session.duration;
|
|
|
|
|
const range = timelineState.visibleEnd - timelineState.visibleStart;
|
|
|
|
|
|
|
|
|
|
let newStart = clickTime - range / 2;
|
|
|
|
|
let newEnd = newStart + range;
|
|
|
|
|
if (newStart < 0) {
|
|
|
|
|
newStart = 0;
|
|
|
|
|
newEnd = range;
|
|
|
|
|
}
|
|
|
|
|
if (newEnd > session.duration) {
|
|
|
|
|
newEnd = session.duration;
|
|
|
|
|
newStart = Math.max(0, newEnd - range);
|
|
|
|
|
}
|
|
|
|
|
timelineState.visibleStart = newStart;
|
|
|
|
|
timelineState.visibleEnd = newEnd;
|
|
|
|
|
}
|
2026-09-21 11:03:24 -04:00
|
|
|
</script>
|
|
|
|
|
|
chore: stage all pending work — caption styling, media server, processing modal, docs, summaries
Includes:
- Extended caption styling (font, shadow, dimmed color, bg toggle)
- Media server, subtitle downloader, VTT parser, processing modal
- Waveform tiers, thumbnail/timeline improvements, transport controls
- Hybrid download model, dependency management, clip export enhancements
- 21 chat summaries, 2 implementation plans, 2 design specs
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-22 10:48:16 -04:00
|
|
|
<div class="timeline-wrapper">
|
|
|
|
|
{#if isZoomed}
|
|
|
|
|
<div class="minimap">
|
|
|
|
|
<canvas
|
|
|
|
|
bind:this={minimapCanvas}
|
|
|
|
|
onmousedown={handleMinimapMouseDown}
|
|
|
|
|
onclick={handleMinimapClick}
|
|
|
|
|
></canvas>
|
|
|
|
|
</div>
|
|
|
|
|
{/if}
|
|
|
|
|
<div class="timeline-container" bind:this={containerEl}>
|
|
|
|
|
<canvas
|
|
|
|
|
bind:this={canvas}
|
|
|
|
|
onmousedown={handleMouseDown}
|
2026-09-22 16:51:56 -04:00
|
|
|
onmousemove={(e) => {
|
|
|
|
|
handleMouseMove(e);
|
|
|
|
|
handleMouseTrack(e);
|
|
|
|
|
}}
|
chore: stage all pending work — caption styling, media server, processing modal, docs, summaries
Includes:
- Extended caption styling (font, shadow, dimmed color, bg toggle)
- Media server, subtitle downloader, VTT parser, processing modal
- Waveform tiers, thumbnail/timeline improvements, transport controls
- Hybrid download model, dependency management, clip export enhancements
- 21 chat summaries, 2 implementation plans, 2 design specs
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-22 10:48:16 -04:00
|
|
|
onmouseup={handleMouseUp}
|
2026-09-22 16:51:56 -04:00
|
|
|
onmouseleave={handleMouseLeave}
|
chore: stage all pending work — caption styling, media server, processing modal, docs, summaries
Includes:
- Extended caption styling (font, shadow, dimmed color, bg toggle)
- Media server, subtitle downloader, VTT parser, processing modal
- Waveform tiers, thumbnail/timeline improvements, transport controls
- Hybrid download model, dependency management, clip export enhancements
- 21 chat summaries, 2 implementation plans, 2 design specs
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-22 10:48:16 -04:00
|
|
|
onwheel={handleWheel}
|
2026-09-22 16:53:43 -04:00
|
|
|
oncontextmenu={handleContextMenu}
|
chore: stage all pending work — caption styling, media server, processing modal, docs, summaries
Includes:
- Extended caption styling (font, shadow, dimmed color, bg toggle)
- Media server, subtitle downloader, VTT parser, processing modal
- Waveform tiers, thumbnail/timeline improvements, transport controls
- Hybrid download model, dependency management, clip export enhancements
- 21 chat summaries, 2 implementation plans, 2 design specs
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-22 10:48:16 -04:00
|
|
|
></canvas>
|
|
|
|
|
</div>
|
2026-09-21 10:45:53 -04:00
|
|
|
</div>
|
|
|
|
|
|
2026-09-22 16:53:43 -04:00
|
|
|
{#if contextMenu}
|
|
|
|
|
<TimelineContextMenu
|
|
|
|
|
x={contextMenu.x}
|
|
|
|
|
y={contextMenu.y}
|
|
|
|
|
time={contextMenu.time}
|
|
|
|
|
selectedClipId={clipStore.selectedClipId}
|
|
|
|
|
onAction={handleContextMenuAction}
|
|
|
|
|
onClose={() => {
|
|
|
|
|
contextMenu = null;
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
{/if}
|
|
|
|
|
|
chore: stage all pending work — caption styling, media server, processing modal, docs, summaries
Includes:
- Extended caption styling (font, shadow, dimmed color, bg toggle)
- Media server, subtitle downloader, VTT parser, processing modal
- Waveform tiers, thumbnail/timeline improvements, transport controls
- Hybrid download model, dependency management, clip export enhancements
- 21 chat summaries, 2 implementation plans, 2 design specs
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-22 10:48:16 -04:00
|
|
|
<svelte:window
|
|
|
|
|
onmousemove={handleMinimapMouseMove}
|
|
|
|
|
onmouseup={handleMinimapMouseUp}
|
|
|
|
|
/>
|
|
|
|
|
|
2026-09-21 10:45:53 -04:00
|
|
|
<style>
|
chore: stage all pending work — caption styling, media server, processing modal, docs, summaries
Includes:
- Extended caption styling (font, shadow, dimmed color, bg toggle)
- Media server, subtitle downloader, VTT parser, processing modal
- Waveform tiers, thumbnail/timeline improvements, transport controls
- Hybrid download model, dependency management, clip export enhancements
- 21 chat summaries, 2 implementation plans, 2 design specs
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-22 10:48:16 -04:00
|
|
|
.timeline-wrapper {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
height: 100%;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.minimap {
|
|
|
|
|
height: 28px;
|
|
|
|
|
background: #12121e;
|
|
|
|
|
border-bottom: 1px solid var(--border, #353550);
|
|
|
|
|
cursor: grab;
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.minimap:active {
|
|
|
|
|
cursor: grabbing;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.minimap canvas {
|
|
|
|
|
display: block;
|
|
|
|
|
width: 100%;
|
|
|
|
|
height: 100%;
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-21 11:03:24 -04:00
|
|
|
.timeline-container {
|
chore: stage all pending work — caption styling, media server, processing modal, docs, summaries
Includes:
- Extended caption styling (font, shadow, dimmed color, bg toggle)
- Media server, subtitle downloader, VTT parser, processing modal
- Waveform tiers, thumbnail/timeline improvements, transport controls
- Hybrid download model, dependency management, clip export enhancements
- 21 chat summaries, 2 implementation plans, 2 design specs
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-22 10:48:16 -04:00
|
|
|
flex: 1;
|
|
|
|
|
min-height: 80px;
|
2026-09-21 10:45:53 -04:00
|
|
|
background: var(--timeline-bg);
|
2026-09-21 11:03:24 -04:00
|
|
|
position: relative;
|
|
|
|
|
cursor: crosshair;
|
2026-09-21 10:45:53 -04:00
|
|
|
}
|
2026-09-21 11:03:24 -04:00
|
|
|
|
|
|
|
|
canvas {
|
|
|
|
|
display: block;
|
|
|
|
|
width: 100%;
|
|
|
|
|
height: 100%;
|
2026-09-21 10:45:53 -04:00
|
|
|
}
|
|
|
|
|
</style>
|