import type { Marker, SwitchType } from './types'; import { ZOOM_DEFAULT } from './types'; export const app = $state({ markers: [] as Marker[], selectedIds: new Set(), isPlaying: false, currentTime: 0, duration: 0, fps: 30, pixelsPerSecond: ZOOM_DEFAULT, scrollLeft: 0, activeSwitch: 'mxbrown' as SwitchType, videoUrl: null as string | null, videoName: null as string | null, timelineWidth: 800, previewSpeed: 1, }); let markerCounter = 0; export function createMarkerId(): string { markerCounter += 1; return `m${markerCounter}`; } export function setMarkers(value: Marker[]) { app.markers = value; const maxId = value.reduce((max, m) => { const num = parseInt(m.id.replace(/\D/g, ''), 10); return Number.isFinite(num) ? Math.max(max, num) : max; }, 0); markerCounter = maxId; } export function setSelectedIds(ids: Set) { app.selectedIds = ids; } export function setCurrentTime(value: number) { app.currentTime = value; } export function setDuration(value: number) { app.duration = value; } export function setIsPlaying(value: boolean) { app.isPlaying = value; } export function setFps(value: number) { app.fps = value; } export function setPreviewSpeed(value: number) { app.previewSpeed = Math.max(0.25, Math.min(2, value)); } export function setPixelsPerSecond(value: number) { app.pixelsPerSecond = value; } export function setScrollLeft(value: number) { app.scrollLeft = value; } export function setActiveSwitch(value: SwitchType) { app.activeSwitch = value; } export function setTimelineWidth(value: number) { app.timelineWidth = value; } export function addMarker(time: number, key: string, code?: string): Marker { const marker: Marker = { id: createMarkerId(), time, key, code }; app.markers = [...app.markers, marker].sort((a, b) => a.time - b.time); return marker; } export function updateMarker( id: string, patch: Partial>, ) { app.markers = app.markers .map((m) => (m.id === id ? { ...m, ...patch } : m)) .sort((a, b) => a.time - b.time); } export function setMarkerTimes(updates: Map) { app.markers = app.markers .map((m) => { const time = updates.get(m.id); return time === undefined ? m : { ...m, time }; }) .sort((a, b) => a.time - b.time); } export function deleteMarkers(ids: Set) { app.markers = app.markers.filter((m) => !ids.has(m.id)); const next = new Set(app.selectedIds); for (const id of ids) next.delete(id); app.selectedIds = next; } export function selectAll() { app.selectedIds = new Set(app.markers.map((m) => m.id)); } export function toggleSelection(id: string, additive: boolean) { const next = additive ? new Set(app.selectedIds) : new Set(); if (next.has(id)) next.delete(id); else next.add(id); app.selectedIds = next; } export function setVideoUrl(url: string | null, name: string | null = null) { if (app.videoUrl) URL.revokeObjectURL(app.videoUrl); app.videoUrl = url; app.videoName = name; } export function snapToFrame(time: number): number { const frameDuration = 1 / app.fps; return Math.round(time / frameDuration) * frameDuration; } export function nudgeSelected(deltaFrames: number) { if (app.selectedIds.size === 0) return; const frameDuration = 1 / app.fps; const delta = deltaFrames * frameDuration; app.markers = app.markers .map((m) => { if (!app.selectedIds.has(m.id)) return m; const next = Math.max(0, Math.min(app.duration || Infinity, m.time + delta)); return { ...m, time: snapToFrame(next) }; }) .sort((a, b) => a.time - b.time); }