From bbf9aae234ac0813b925b12c2533aac536267340 Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Tue, 24 Jun 2025 17:54:41 +0200 Subject: Revert "refactor: overhaul timeline component, separate into different files" This reverts commit 2aa37920d5f183f9c0de2c42076fa57c5b48320e. --- .../editor/timeline/timeline-track-content.tsx | 593 --------------------- 1 file changed, 593 deletions(-) delete mode 100644 apps/web/src/components/editor/timeline/timeline-track-content.tsx (limited to 'apps/web/src/components/editor/timeline/timeline-track-content.tsx') diff --git a/apps/web/src/components/editor/timeline/timeline-track-content.tsx b/apps/web/src/components/editor/timeline/timeline-track-content.tsx deleted file mode 100644 index a33fb3ee..00000000 --- a/apps/web/src/components/editor/timeline/timeline-track-content.tsx +++ /dev/null @@ -1,593 +0,0 @@ -"use client"; - -import { useState, useRef } from "react"; -import { toast } from "sonner"; -import { TimelineClip } from "./timeline-clip"; -import { useMediaStore } from "@/stores/media-store"; -import { useTimelineStore, type TimelineTrack } from "@/stores/timeline-store"; -import { usePlaybackStore } from "@/stores/playback-store"; - -interface TimelineTrackContentProps { - track: TimelineTrack; - zoomLevel: number; - setContextMenu: ( - menu: { - type: "track" | "clip"; - trackId: string; - clipId?: string; - x: number; - y: number; - } | null - ) => void; -} - -export function TimelineTrackContent({ - track, - zoomLevel, - setContextMenu, -}: TimelineTrackContentProps) { - const { mediaItems } = useMediaStore(); - const { - tracks, - moveClipToTrack, - updateClipTrim, - updateClipStartTime, - addClipToTrack, - removeClipFromTrack, - toggleTrackMute, - selectedClips, - selectClip, - deselectClip, - } = useTimelineStore(); - const { currentTime } = usePlaybackStore(); - const [isDropping, setIsDropping] = useState(false); - const [dropPosition, setDropPosition] = useState(null); - const [isDraggedOver, setIsDraggedOver] = useState(false); - const [wouldOverlap, setWouldOverlap] = useState(false); - const [resizing, setResizing] = useState<{ - clipId: string; - side: "left" | "right"; - startX: number; - initialTrimStart: number; - initialTrimEnd: number; - } | null>(null); - const dragCounterRef = useRef(0); - - const handleResizeStart = ( - e: React.MouseEvent, - clipId: string, - side: "left" | "right" - ) => { - e.stopPropagation(); - e.preventDefault(); - - const clip = track.clips.find((c) => c.id === clipId); - if (!clip) return; - - setResizing({ - clipId, - side, - startX: e.clientX, - initialTrimStart: clip.trimStart, - initialTrimEnd: clip.trimEnd, - }); - }; - - const updateTrimFromMouseMove = (e: { clientX: number }) => { - if (!resizing) return; - - const clip = track.clips.find((c) => c.id === resizing.clipId); - if (!clip) return; - - const deltaX = e.clientX - resizing.startX; - const deltaTime = deltaX / (50 * zoomLevel); - - if (resizing.side === "left") { - const newTrimStart = Math.max( - 0, - Math.min( - clip.duration - clip.trimEnd - 0.1, - resizing.initialTrimStart + deltaTime - ) - ); - updateClipTrim(track.id, clip.id, newTrimStart, clip.trimEnd); - } else { - const newTrimEnd = Math.max( - 0, - Math.min( - clip.duration - clip.trimStart - 0.1, - resizing.initialTrimEnd - deltaTime - ) - ); - updateClipTrim(track.id, clip.id, clip.trimStart, newTrimEnd); - } - }; - - const handleResizeMove = (e: React.MouseEvent) => { - updateTrimFromMouseMove(e); - }; - - const handleResizeEnd = () => { - setResizing(null); - }; - - const handleClipDragStart = (e: React.DragEvent, clip: any) => { - const dragData = { clipId: clip.id, trackId: track.id, name: clip.name }; - - e.dataTransfer.setData( - "application/x-timeline-clip", - JSON.stringify(dragData) - ); - e.dataTransfer.effectAllowed = "move"; - - // Add visual feedback to the dragged element - const target = e.currentTarget.parentElement as HTMLElement; - target.style.opacity = "0.5"; - target.style.transform = "scale(0.95)"; - }; - - const handleClipDragEnd = (e: React.DragEvent) => { - // Reset visual feedback - const target = e.currentTarget.parentElement as HTMLElement; - target.style.opacity = ""; - target.style.transform = ""; - }; - - const handleTrackDragOver = (e: React.DragEvent) => { - e.preventDefault(); - - // Handle both timeline clips and media items - const hasTimelineClip = e.dataTransfer.types.includes( - "application/x-timeline-clip" - ); - const hasMediaItem = e.dataTransfer.types.includes( - "application/x-media-item" - ); - - if (!hasTimelineClip && !hasMediaItem) return; - - if (hasMediaItem) { - try { - const mediaItemData = e.dataTransfer.getData( - "application/x-media-item" - ); - if (mediaItemData) { - const { type } = JSON.parse(mediaItemData); - const isCompatible = - (track.type === "video" && - (type === "video" || type === "image")) || - (track.type === "audio" && type === "audio"); - - if (!isCompatible) { - e.dataTransfer.dropEffect = "none"; - return; - } - } - } catch (error) {} - } - - // Calculate drop position for overlap checking - const trackContainer = e.currentTarget.querySelector( - ".track-clips-container" - ) as HTMLElement; - let dropTime = 0; - if (trackContainer) { - const rect = trackContainer.getBoundingClientRect(); - const mouseX = Math.max(0, e.clientX - rect.left); - dropTime = mouseX / (50 * zoomLevel); - } - - // Check for potential overlaps and show appropriate feedback - let wouldOverlap = false; - - if (hasMediaItem) { - try { - const mediaItemData = e.dataTransfer.getData( - "application/x-media-item" - ); - if (mediaItemData) { - const { id } = JSON.parse(mediaItemData); - const mediaItem = mediaItems.find((item) => item.id === id); - if (mediaItem) { - const newClipDuration = mediaItem.duration || 5; - const snappedTime = Math.round(dropTime * 10) / 10; - const newClipEnd = snappedTime + newClipDuration; - - wouldOverlap = track.clips.some((existingClip) => { - const existingStart = existingClip.startTime; - const existingEnd = - existingClip.startTime + - (existingClip.duration - - existingClip.trimStart - - existingClip.trimEnd); - return snappedTime < existingEnd && newClipEnd > existingStart; - }); - } - } - } catch (error) { - // Continue with default behavior - } - } else if (hasTimelineClip) { - try { - const timelineClipData = e.dataTransfer.getData( - "application/x-timeline-clip" - ); - if (timelineClipData) { - const { clipId, trackId: fromTrackId } = JSON.parse(timelineClipData); - const sourceTrack = tracks.find( - (t: TimelineTrack) => t.id === fromTrackId - ); - const movingClip = sourceTrack?.clips.find( - (c: any) => c.id === clipId - ); - - if (movingClip) { - const movingClipDuration = - movingClip.duration - movingClip.trimStart - movingClip.trimEnd; - const snappedTime = Math.round(dropTime * 10) / 10; - const movingClipEnd = snappedTime + movingClipDuration; - - wouldOverlap = track.clips.some((existingClip) => { - if (fromTrackId === track.id && existingClip.id === clipId) - return false; - - const existingStart = existingClip.startTime; - const existingEnd = - existingClip.startTime + - (existingClip.duration - - existingClip.trimStart - - existingClip.trimEnd); - return snappedTime < existingEnd && movingClipEnd > existingStart; - }); - } - } - } catch (error) { - // Continue with default behavior - } - } - - if (wouldOverlap) { - e.dataTransfer.dropEffect = "none"; - setIsDraggedOver(true); - setWouldOverlap(true); - setDropPosition(Math.round(dropTime * 10) / 10); - return; - } - - e.dataTransfer.dropEffect = hasTimelineClip ? "move" : "copy"; - setIsDraggedOver(true); - setWouldOverlap(false); - setDropPosition(Math.round(dropTime * 10) / 10); - }; - - const handleTrackDragEnter = (e: React.DragEvent) => { - e.preventDefault(); - - const hasTimelineClip = e.dataTransfer.types.includes( - "application/x-timeline-clip" - ); - const hasMediaItem = e.dataTransfer.types.includes( - "application/x-media-item" - ); - - if (!hasTimelineClip && !hasMediaItem) return; - - dragCounterRef.current++; - setIsDropping(true); - setIsDraggedOver(true); - }; - - const handleTrackDragLeave = (e: React.DragEvent) => { - e.preventDefault(); - - const hasTimelineClip = e.dataTransfer.types.includes( - "application/x-timeline-clip" - ); - const hasMediaItem = e.dataTransfer.types.includes( - "application/x-media-item" - ); - - if (!hasTimelineClip && !hasMediaItem) return; - - dragCounterRef.current--; - - if (dragCounterRef.current === 0) { - setIsDropping(false); - setIsDraggedOver(false); - setWouldOverlap(false); - setDropPosition(null); - } - }; - - const handleTrackDrop = (e: React.DragEvent) => { - e.preventDefault(); - e.stopPropagation(); - - // Reset all drag states - dragCounterRef.current = 0; - setIsDropping(false); - setIsDraggedOver(false); - setWouldOverlap(false); - const currentDropPosition = dropPosition; - setDropPosition(null); - - const hasTimelineClip = e.dataTransfer.types.includes( - "application/x-timeline-clip" - ); - const hasMediaItem = e.dataTransfer.types.includes( - "application/x-media-item" - ); - - if (!hasTimelineClip && !hasMediaItem) return; - - const trackContainer = e.currentTarget.querySelector( - ".track-clips-container" - ) as HTMLElement; - if (!trackContainer) return; - - const rect = trackContainer.getBoundingClientRect(); - const mouseX = Math.max(0, e.clientX - rect.left); - const newStartTime = mouseX / (50 * zoomLevel); - const snappedTime = Math.round(newStartTime * 10) / 10; - - try { - if (hasTimelineClip) { - // Handle timeline clip movement - const timelineClipData = e.dataTransfer.getData( - "application/x-timeline-clip" - ); - if (!timelineClipData) return; - - const { clipId, trackId: fromTrackId } = JSON.parse(timelineClipData); - - // Find the clip being moved - const sourceTrack = tracks.find( - (t: TimelineTrack) => t.id === fromTrackId - ); - const movingClip = sourceTrack?.clips.find((c: any) => c.id === clipId); - - if (!movingClip) { - toast.error("Clip not found"); - return; - } - - // Check for overlaps with existing clips (excluding the moving clip itself) - const movingClipDuration = - movingClip.duration - movingClip.trimStart - movingClip.trimEnd; - const movingClipEnd = snappedTime + movingClipDuration; - - const hasOverlap = track.clips.some((existingClip) => { - // Skip the clip being moved if it's on the same track - if (fromTrackId === track.id && existingClip.id === clipId) - return false; - - const existingStart = existingClip.startTime; - const existingEnd = - existingClip.startTime + - (existingClip.duration - - existingClip.trimStart - - existingClip.trimEnd); - - // Check if clips overlap - return snappedTime < existingEnd && movingClipEnd > existingStart; - }); - - if (hasOverlap) { - toast.error( - "Cannot move clip here - it would overlap with existing clips" - ); - return; - } - - if (fromTrackId === track.id) { - // Moving within same track - updateClipStartTime(track.id, clipId, snappedTime); - } else { - // Moving to different track - moveClipToTrack(fromTrackId, track.id, clipId); - requestAnimationFrame(() => { - updateClipStartTime(track.id, clipId, snappedTime); - }); - } - } else if (hasMediaItem) { - // Handle media item drop - const mediaItemData = e.dataTransfer.getData( - "application/x-media-item" - ); - if (!mediaItemData) return; - - const { id, type } = JSON.parse(mediaItemData); - const mediaItem = mediaItems.find((item) => item.id === id); - - if (!mediaItem) { - toast.error("Media item not found"); - return; - } - - // Check if track type is compatible - const isCompatible = - (track.type === "video" && (type === "video" || type === "image")) || - (track.type === "audio" && type === "audio"); - - if (!isCompatible) { - toast.error(`Cannot add ${type} to ${track.type} track`); - return; - } - - // Check for overlaps with existing clips - const newClipDuration = mediaItem.duration || 5; - const newClipEnd = snappedTime + newClipDuration; - - const hasOverlap = track.clips.some((existingClip) => { - const existingStart = existingClip.startTime; - const existingEnd = - existingClip.startTime + - (existingClip.duration - - existingClip.trimStart - - existingClip.trimEnd); - - // Check if clips overlap - return snappedTime < existingEnd && newClipEnd > existingStart; - }); - - if (hasOverlap) { - toast.error( - "Cannot place clip here - it would overlap with existing clips" - ); - return; - } - - addClipToTrack(track.id, { - mediaId: mediaItem.id, - name: mediaItem.name, - duration: mediaItem.duration || 5, - startTime: snappedTime, - trimStart: 0, - trimEnd: 0, - }); - - toast.success(`Added ${mediaItem.name} to ${track.name}`); - } - } catch (error) { - console.error("Error handling drop:", error); - toast.error("Failed to add media to track"); - } - }; - - const getTrackColor = (type: string) => { - switch (type) { - case "video": - return "bg-blue-500/20 border-blue-500/30"; - case "audio": - return "bg-green-500/20 border-green-500/30"; - case "effects": - return "bg-purple-500/20 border-purple-500/30"; - default: - return "bg-gray-500/20 border-gray-500/30"; - } - }; - - return ( -
{ - e.preventDefault(); - // Only show track menu if we didn't click on a clip - if (!(e.target as HTMLElement).closest(".timeline-clip")) { - setContextMenu({ - type: "track", - trackId: track.id, - x: e.clientX, - y: e.clientY, - }); - } - }} - onDragOver={handleTrackDragOver} - onDragEnter={handleTrackDragEnter} - onDragLeave={handleTrackDragLeave} - onDrop={handleTrackDrop} - onMouseMove={handleResizeMove} - onMouseUp={handleResizeEnd} - onMouseLeave={handleResizeEnd} - > -
- {track.clips.length === 0 ? ( -
- {isDropping - ? wouldOverlap - ? "Cannot drop - would overlap" - : "Drop clip here" - : "Drop media here"} -
- ) : ( - <> - {track.clips.map((clip) => { - const isSelected = selectedClips.some( - (c) => c.trackId === track.id && c.clipId === clip.id - ); - return ( - { - e.stopPropagation(); - const isSelected = selectedClips.some( - (c) => c.trackId === track.id && c.clipId === clip.id - ); - - if (e.metaKey || e.ctrlKey || e.shiftKey) { - // Multi-selection mode: toggle the clip - selectClip(track.id, clip.id, true); - } else if (isSelected) { - // If clip is already selected, deselect it - deselectClip(track.id, clip.id); - } else { - // If clip is not selected, select it (replacing other selections) - selectClip(track.id, clip.id, false); - } - }} - onContextMenu={(e) => { - e.preventDefault(); - e.stopPropagation(); - setContextMenu({ - type: "clip", - trackId: track.id, - clipId: clip.id, - x: e.clientX, - y: e.clientY, - }); - }} - getTrackColor={getTrackColor} - /> - ); - })} - - {/* Drop position indicator */} - {isDraggedOver && dropPosition !== null && ( -
-
-
-
- {wouldOverlap ? "⚠️" : ""} - {dropPosition.toFixed(1)}s -
-
- )} - - )} -
-
- ); -} -- cgit v1.3.1