import { useMemo } from "react"; import { toast } from "sonner"; import { useTimelineStore } from "@/stores/timeline-store"; import { usePlaybackStore } from "@/stores/playback-store"; export function useTimelineActionHandlers() { const { tracks, addElementToTrack, removeElementFromTrack, selectedElements, clearSelectedElements, splitElement, splitAndKeepLeft, splitAndKeepRight, separateAudio, } = useTimelineStore(); const { currentTime } = usePlaybackStore(); // Create optimized lookup maps for O(1) access instead of O(n) find operations const trackMap = useMemo(() => { const map = new Map(); tracks.forEach((track) => { map.set(track.id, track); // Also create element lookup for this track const elementMap = new Map(); track.elements.forEach((element) => { elementMap.set(element.id, element); }); map.set(`${track.id}_elements`, elementMap); }); return map; }, [tracks]); // Helper function for O(1) track/element lookup const findTrackAndElement = (trackId: string, elementId: string) => { const track = trackMap.get(trackId); const elementMap = trackMap.get(`${trackId}_elements`); const element = elementMap?.get(elementId); return { track, element }; }; // Action handlers for toolbar const handleSplitSelected = () => { if (selectedElements.length === 0) { toast.error("No elements selected"); return; } let splitCount = 0; selectedElements.forEach(({ trackId, elementId }) => { const { track, element } = findTrackAndElement(trackId, 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) { toast.error("No elements selected"); return; } const canDuplicate = selectedElements.length === 1; if (!canDuplicate) return; selectedElements.forEach(({ trackId, elementId }) => { const { element } = findTrackAndElement(trackId, elementId); if (element) { const newStartTime = element.startTime + (element.duration - element.trimStart - element.trimEnd) + 0.1; // Create element without id (will be generated by store) const { id, ...elementWithoutId } = element; addElementToTrack(trackId, { ...elementWithoutId, startTime: newStartTime, }); // We can't predict the new id, so just clear selection for now // TODO: addElementToTrack could return the new element id } }); 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 { element } = findTrackAndElement(trackId, 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 { element } = findTrackAndElement(trackId, 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 } = findTrackAndElement(trackId, elementId); if (!track || track.type !== "media") { toast.error("Select a media element to separate audio"); return; } separateAudio(trackId, elementId); }; const handleDeleteSelected = () => { if (selectedElements.length === 0) { toast.error("No elements selected"); return; } selectedElements.forEach(({ trackId, elementId }) => { removeElementFromTrack(trackId, elementId); }); clearSelectedElements(); }; return { handleSplitSelected, handleDuplicateSelected, handleFreezeSelected, handleSplitAndKeepLeft, handleSplitAndKeepRight, handleSeparateAudio, handleDeleteSelected, }; }