From 2aa37920d5f183f9c0de2c42076fa57c5b48320e Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Tue, 24 Jun 2025 17:51:34 +0200 Subject: refactor: overhaul timeline component, separate into different files --- .../components/editor/timeline/timeline-clip.tsx | 192 +++++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 apps/web/src/components/editor/timeline/timeline-clip.tsx (limited to 'apps/web/src/components/editor/timeline/timeline-clip.tsx') diff --git a/apps/web/src/components/editor/timeline/timeline-clip.tsx b/apps/web/src/components/editor/timeline/timeline-clip.tsx new file mode 100644 index 00000000..69740cd7 --- /dev/null +++ b/apps/web/src/components/editor/timeline/timeline-clip.tsx @@ -0,0 +1,192 @@ +"use client"; + +import { Button } from "../../ui/button"; +import { MoreVertical, Scissors, Trash2 } from "lucide-react"; +import { useMediaStore } from "@/stores/media-store"; +import { useTimelineStore, type TimelineTrack } from "@/stores/timeline-store"; +import { usePlaybackStore } from "@/stores/playback-store"; +import { type TimelineClip } from "@/types/timeline"; +import { useState } from "react"; + +interface TimelineClipProps { + clip: TimelineClip; + track: TimelineTrack; + zoomLevel: number; + isSelected: boolean; + onResizeStart: ( + e: React.MouseEvent, + clipId: string, + side: "left" | "right" + ) => void; + onClipDragStart: (e: React.DragEvent, clip: any) => void; + onClipDragEnd: (e: React.DragEvent) => void; + onSelect: (e: React.MouseEvent) => void; + onContextMenu: (e: React.MouseEvent) => void; + getTrackColor: (type: string) => string; +} + +export function TimelineClip({ + clip, + track, + zoomLevel, + isSelected, + onResizeStart, + onClipDragStart, + onClipDragEnd, + onSelect, + onContextMenu, + getTrackColor, +}: TimelineClipProps) { + const { mediaItems } = useMediaStore(); + const { addClipToTrack, updateClipTrim, removeClipFromTrack } = + useTimelineStore(); + const { currentTime } = usePlaybackStore(); + const [clipMenuOpen, setClipMenuOpen] = useState(null); + + const effectiveDuration = clip.duration - clip.trimStart - clip.trimEnd; + const clipWidth = Math.max(80, effectiveDuration * 50 * zoomLevel); + const clipLeft = clip.startTime * 50 * zoomLevel; + + const renderClipContent = (clip: TimelineClip) => { + const mediaItem = mediaItems.find((item) => item.id === clip.mediaId); + + if (!mediaItem) { + return ( + {clip.name} + ); + } + + if (mediaItem.type === "image") { + return ( +
+ {mediaItem.name} +
+ ); + } + + if (mediaItem.type === "video" && mediaItem.thumbnailUrl) { + return ( +
+
+ {mediaItem.name} +
+ + {clip.name} + +
+ ); + } + + // Fallback for audio or videos without thumbnails + return ( + {clip.name} + ); + }; + + const handleSplitClip = (clip: TimelineClip) => { + // Use current playback time as split point + const splitTime = currentTime; + + // Only split if splitTime is within the clip's effective range + const effectiveStart = clip.startTime; + const effectiveEnd = + clip.startTime + (clip.duration - clip.trimStart - clip.trimEnd); + if (splitTime <= effectiveStart || splitTime >= effectiveEnd) return; + + const firstDuration = splitTime - effectiveStart; + const secondDuration = effectiveEnd - splitTime; + + // First part: adjust original clip + updateClipTrim( + track.id, + clip.id, + clip.trimStart, + clip.trimEnd + secondDuration + ); + + // Second part: add new clip after split + addClipToTrack(track.id, { + mediaId: clip.mediaId, + name: clip.name + " (cut)", + duration: clip.duration, + startTime: splitTime, + trimStart: clip.trimStart + firstDuration, + trimEnd: clip.trimEnd, + }); + }; + + const handleDeleteClip = (clipId: string) => { + removeClipFromTrack(track.id, clipId); + }; + + return ( +
+ {/* Left trim handle */} +
onResizeStart(e, clip.id, "left")} + /> + + {/* Clip content */} +
onClipDragStart(e, clip)} + onDragEnd={onClipDragEnd} + > + {renderClipContent(clip)} + + {/* Clip options menu */} +
+ + {clipMenuOpen === clip.id && ( +
+ + +
+ )} +
+
+ + {/* Right trim handle */} +
onResizeStart(e, clip.id, "right")} + /> +
+ ); +} -- cgit v1.3.1