summaryrefslogtreecommitdiff
path: root/apps/web/src/components/editor/timeline/timeline-drag-handlers.tsx
blob: 60cd7122fdc53241b7145e589d09696f35687509 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
import { useState, useRef } from "react";
import { toast } from "sonner";
import { useTimelineStore } from "@/stores/timeline-store";
import { useMediaStore } from "@/stores/media-store";
import { useProjectStore } from "@/stores/project-store";
import { processMediaFiles } from "@/lib/media-processing";
import type { DragData } from "@/types/timeline";

export interface TimelineDragHandlersProps {
	isDragOver: boolean;
	setIsDragOver: (isDragOver: boolean) => void;
	isProcessing: boolean;
	setIsProcessing: (isProcessing: boolean) => void;
	progress: number;
	setProgress: (progress: number) => void;
}

export function useTimelineDragHandlers({
	isDragOver,
	setIsDragOver,
	isProcessing,
	setIsProcessing,
	progress,
	setProgress,
}: TimelineDragHandlersProps) {
	const { mediaItems, addMediaItem } = useMediaStore();
	const { activeProject } = useProjectStore();
	const dragCounterRef = useRef(0);

	const handleDragEnter = (e: React.DragEvent) => {
		// When something is dragged over the timeline, show overlay
		e.preventDefault();
		// Don't show overlay for timeline elements - they're handled by tracks
		if (e.dataTransfer.types.includes("application/x-timeline-element")) {
			return;
		}
		dragCounterRef.current += 1;
		if (!isDragOver) {
			setIsDragOver(true);
		}
	};

	const handleDragOver = (e: React.DragEvent) => {
		e.preventDefault();
	};

	const handleDragLeave = (e: React.DragEvent) => {
		e.preventDefault();

		// Don't update state for timeline elements - they're handled by tracks
		if (e.dataTransfer.types.includes("application/x-timeline-element")) {
			return;
		}

		dragCounterRef.current -= 1;
		if (dragCounterRef.current === 0) {
			setIsDragOver(false);
		}
	};

	const handleDrop = async (e: React.DragEvent) => {
		// When media is dropped, add it as a new track/element
		e.preventDefault();
		setIsDragOver(false);
		dragCounterRef.current = 0;

		// Ignore timeline element drags - they're handled by track-specific handlers
		const hasTimelineElement = e.dataTransfer.types.includes(
			"application/x-timeline-element",
		);
		if (hasTimelineElement) {
			return;
		}

		const itemData = e.dataTransfer.getData("application/x-media-item");
		if (itemData) {
			try {
				const dragData: DragData = JSON.parse(itemData);

				if (dragData.type === "text") {
					// Always create new text track to avoid overlaps
					useTimelineStore.getState().addTextToNewTrack(dragData);
				} else {
					// Handle media items
					const mediaItem = mediaItems.find(
						(item: any) => item.id === dragData.id,
					);
					if (!mediaItem) {
						toast.error("Media item not found");
						return;
					}

					useTimelineStore.getState().addMediaToNewTrack(mediaItem);
				}
			} catch (error) {
				console.error("Error parsing dropped item data:", error);
				toast.error("Failed to add item to timeline");
			}
		} else if (e.dataTransfer.files?.length > 0) {
			// Handle file drops by creating new tracks
			if (!activeProject) {
				toast.error("No active project");
				return;
			}

			setIsProcessing(true);
			setProgress(0);
			try {
				const processedItems = await processMediaFiles(
					e.dataTransfer.files,
					(p) => setProgress(p),
				);
				for (const processedItem of processedItems) {
					await addMediaItem(activeProject.id, processedItem);
					const currentMediaItems = useMediaStore.getState().mediaItems;
					const addedItem = currentMediaItems.find(
						(item) =>
							item.name === processedItem.name &&
							item.url === processedItem.url,
					);
					if (addedItem) {
						useTimelineStore.getState().addMediaToNewTrack(addedItem);
					}
				}
			} catch (error) {
				// Show error if file processing fails
				console.error("Error processing external files:", error);
				toast.error("Failed to process dropped files");
			} finally {
				setIsProcessing(false);
				setProgress(0);
			}
		}
	};

	const dragProps = {
		onDragEnter: handleDragEnter,
		onDragOver: handleDragOver,
		onDragLeave: handleDragLeave,
		onDrop: handleDrop,
	};

	return { dragProps };
}