import { usePlaybackStore } from "@/stores/playback-store"; import { useProjectStore } from "@/stores/project-store"; import { useTimelineStore } from "@/stores/timeline-store"; import { toast } from "sonner"; import { TooltipProvider, Tooltip, TooltipTrigger, TooltipContent, } from "@/components/ui/tooltip"; import { Button } from "@/components/ui/button"; import { Pause, Play, SkipBack, Bookmark, Magnet, Link, ZoomOut, ZoomIn, Copy, Trash2, Snowflake, ArrowLeftToLine, ArrowRightToLine, SplitSquareHorizontal, Scissors, } from "lucide-react"; import { Slider } from "@/components/ui/slider"; import { DEFAULT_FPS } from "@/stores/project-store"; import { formatTimeCode } from "@/lib/time"; import { TIMELINE_CONSTANTS } from "@/constants/timeline-constants"; import { EditableTimecode } from "@/components/ui/editable-timecode"; export function TimelineToolbar({ zoomLevel, setZoomLevel, }: { zoomLevel: number; setZoomLevel: (zoom: number) => void; }) { const { tracks, addTrack, addElementToTrack, removeElementFromTrack, removeElementFromTrackWithRipple, selectedElements, clearSelectedElements, splitElement, splitAndKeepLeft, splitAndKeepRight, separateAudio, snappingEnabled, toggleSnapping, rippleEditingEnabled, toggleRippleEditing, } = useTimelineStore(); const { currentTime, duration, isPlaying, toggle, seek } = usePlaybackStore(); const { toggleBookmark, isBookmarked, activeProject } = useProjectStore(); const handleSplitSelected = () => { if (selectedElements.length === 0) return; let splitCount = 0; selectedElements.forEach(({ trackId, elementId }) => { const track = tracks.find((t) => t.id === trackId); const element = track?.elements.find((c) => c.id === elementId); if (element && track) { const effectiveStart = element.startTime; const effectiveEnd = element.startTime + (element.duration - element.trimStart - element.trimEnd); if (currentTime > effectiveStart && currentTime < effectiveEnd) { const newElementId = splitElement(trackId, elementId, currentTime); if (newElementId) splitCount++; } } }); if (splitCount === 0) { toast.error("Playhead must be within selected elements to split"); } }; const handleDuplicateSelected = () => { if (selectedElements.length === 0) return; const canDuplicate = selectedElements.length === 1; if (!canDuplicate) return; selectedElements.forEach(({ trackId, elementId }) => { const track = tracks.find((t) => t.id === trackId); const element = track?.elements.find((el) => el.id === elementId); if (element) { const newStartTime = element.startTime + (element.duration - element.trimStart - element.trimEnd) + 0.1; const { id, ...elementWithoutId } = element; addElementToTrack(trackId, { ...elementWithoutId, startTime: newStartTime, }); } }); clearSelectedElements(); }; const handleFreezeSelected = () => { toast.info("Freeze frame functionality coming soon!"); }; const handleSplitAndKeepLeft = () => { if (selectedElements.length !== 1) { toast.error("Select exactly one element"); return; } const { trackId, elementId } = selectedElements[0]; const track = tracks.find((t) => t.id === trackId); const element = track?.elements.find((c) => c.id === elementId); if (!element) return; const effectiveStart = element.startTime; const effectiveEnd = element.startTime + (element.duration - element.trimStart - element.trimEnd); if (currentTime <= effectiveStart || currentTime >= effectiveEnd) { toast.error("Playhead must be within selected element"); return; } splitAndKeepLeft(trackId, elementId, currentTime); }; const handleSplitAndKeepRight = () => { if (selectedElements.length !== 1) { toast.error("Select exactly one element"); return; } const { trackId, elementId } = selectedElements[0]; const track = tracks.find((t) => t.id === trackId); const element = track?.elements.find((c) => c.id === elementId); if (!element) return; const effectiveStart = element.startTime; const effectiveEnd = element.startTime + (element.duration - element.trimStart - element.trimEnd); if (currentTime <= effectiveStart || currentTime >= effectiveEnd) { toast.error("Playhead must be within selected element"); return; } splitAndKeepRight(trackId, elementId, currentTime); }; const handleSeparateAudio = () => { if (selectedElements.length !== 1) { toast.error("Select exactly one media element to separate audio"); return; } const { trackId, elementId } = selectedElements[0]; const track = tracks.find((t) => t.id === trackId); if (!track || track.type !== "media") { toast.error("Select a media element to separate audio"); return; } separateAudio(trackId, elementId); }; const handleDeleteSelected = () => { if (selectedElements.length === 0) return; selectedElements.forEach(({ trackId, elementId }) => { if (rippleEditingEnabled) { removeElementFromTrackWithRipple(trackId, elementId); } else { removeElementFromTrack(trackId, elementId); } }); clearSelectedElements(); }; const handleZoomIn = () => { setZoomLevel(Math.min(4, zoomLevel + 0.25)); }; const handleZoomOut = () => { setZoomLevel(Math.max(0.25, zoomLevel - 0.25)); }; const handleZoomSliderChange = (values: number[]) => { setZoomLevel(values[0]); }; const handleToggleBookmark = async () => { await toggleBookmark(currentTime); }; const currentBookmarked = isBookmarked(currentTime); return (