Files
gui-video-clipper/src/lib/timeline/interactions.ts
cottongin 8ad2f1c800 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

78 lines
2.0 KiB
TypeScript

import { type TimelineState, xToTime } from './renderer';
export type { TimelineState };
const MIN_VISIBLE_RANGE = 0.1;
const MIN_ZOOM = 1;
export function clampZoom(zoom: number, duration: number): number {
const maxZoom = duration / MIN_VISIBLE_RANGE;
return Math.max(MIN_ZOOM, Math.min(zoom, maxZoom));
}
export function computeZoom(
state: TimelineState,
anchorTime: number,
factor: number,
duration: number
): { visibleStart: number; visibleEnd: number; zoom: number } {
const currentRange = state.visibleEnd - state.visibleStart;
if (currentRange <= 0) {
return {
visibleStart: state.visibleStart,
visibleEnd: state.visibleEnd,
zoom: state.zoom,
};
}
const newRange = currentRange / factor;
const clampedRange = Math.max(MIN_VISIBLE_RANGE, Math.min(newRange, duration));
const anchorRatio = (anchorTime - state.visibleStart) / currentRange;
let newStart = anchorTime - anchorRatio * clampedRange;
let newEnd = newStart + clampedRange;
if (newStart < 0) {
newStart = 0;
newEnd = clampedRange;
}
if (newEnd > duration) {
newEnd = duration;
newStart = Math.max(0, newEnd - clampedRange);
}
const newZoom = clampZoom(duration / clampedRange, duration);
return { visibleStart: newStart, visibleEnd: newEnd, zoom: newZoom };
}
export function panBy(
state: TimelineState,
deltaPixels: number,
duration: number
): { visibleStart: number; visibleEnd: number } {
const range = state.visibleEnd - state.visibleStart;
const deltaTime = (deltaPixels / state.width) * range;
let newStart = state.visibleStart + deltaTime;
let newEnd = newStart + range;
if (newStart < 0) {
newStart = 0;
newEnd = range;
}
if (newEnd > duration) {
newEnd = duration;
newStart = Math.max(0, newEnd - range);
}
return { visibleStart: newStart, visibleEnd: newEnd };
}
export function handleClick(
x: number,
state: TimelineState,
duration: number
): number {
const time = xToTime(x, state);
return Math.max(0, Math.min(time, duration));
}