diff --git a/apps/web/src/components/editor/timeline-element.tsx b/apps/web/src/components/editor/timeline-element.tsx index 4a909ce..1e1674b 100644 --- a/apps/web/src/components/editor/timeline-element.tsx +++ b/apps/web/src/components/editor/timeline-element.tsx @@ -11,6 +11,7 @@ import { ChevronRight, ChevronLeft, Type, + Copy, } from "lucide-react"; import { useMediaStore } from "@/stores/media-store"; import { useTimelineStore } from "@/stores/timeline-store"; @@ -33,6 +34,13 @@ import { DropdownMenuSubContent, DropdownMenuSubTrigger, } from "../ui/dropdown-menu"; +import { + ContextMenu, + ContextMenuContent, + ContextMenuItem, + ContextMenuSeparator, + ContextMenuTrigger, +} from "../ui/context-menu"; export function TimelineElement({ element, @@ -52,6 +60,7 @@ export function TimelineElement({ splitAndKeepLeft, splitAndKeepRight, separateAudio, + addElementToTrack, } = useTimelineStore(); const { currentTime } = usePlaybackStore(); @@ -172,6 +181,38 @@ export function TimelineElement({ return mediaItem?.type === "video" && track.type === "media"; }; + const handleElementSplitContext = () => { + const effectiveStart = element.startTime; + const effectiveEnd = + element.startTime + + (element.duration - element.trimStart - element.trimEnd); + + if (currentTime > effectiveStart && currentTime < effectiveEnd) { + const secondElementId = splitElement(track.id, element.id, currentTime); + if (!secondElementId) { + toast.error("Failed to split element"); + } + } else { + toast.error("Playhead must be within element to split"); + } + }; + + const handleElementDuplicateContext = () => { + const { id, ...elementWithoutId } = element; + addElementToTrack(track.id, { + ...elementWithoutId, + name: element.name + " (copy)", + startTime: + element.startTime + + (element.duration - element.trimStart - element.trimEnd) + + 0.1, + }); + }; + + const handleElementDeleteContext = () => { + removeElementFromTrack(track.id, element.id); + }; + const renderElementContent = () => { if (element.type === "text") { return ( @@ -255,47 +296,69 @@ export function TimelineElement({ }; return ( -
-
onElementClick && onElementClick(e, element)} - onMouseDown={handleElementMouseDown} - onContextMenu={(e) => - onElementMouseDown && onElementMouseDown(e, element) - } - > -
- {renderElementContent()} -
+ + +
+
onElementClick && onElementClick(e, element)} + onMouseDown={handleElementMouseDown} + onContextMenu={(e) => + onElementMouseDown && onElementMouseDown(e, element) + } + > +
+ {renderElementContent()} +
- {isSelected && ( - <> -
handleResizeStart(e, element.id, "left")} - /> -
handleResizeStart(e, element.id, "right")} - /> - - )} -
-
+ {isSelected && ( + <> +
handleResizeStart(e, element.id, "left")} + /> +
handleResizeStart(e, element.id, "right")} + /> + + )} +
+
+ + + + + Split at playhead + + + + Duplicate {element.type === "text" ? "text" : "clip"} + + + + + Delete {element.type === "text" ? "text" : "clip"} + + + ); } diff --git a/apps/web/src/components/editor/timeline-track.tsx b/apps/web/src/components/editor/timeline-track.tsx index f176659..008768d 100644 --- a/apps/web/src/components/editor/timeline-track.tsx +++ b/apps/web/src/components/editor/timeline-track.tsx @@ -4,15 +4,7 @@ import { useRef, useState, useEffect } from "react"; import { useTimelineStore } from "@/stores/timeline-store"; import { useMediaStore } from "@/stores/media-store"; import { toast } from "sonner"; -import { Copy, Scissors, Trash2 } from "lucide-react"; import { TimelineElement } from "./timeline-element"; -import { - ContextMenu, - ContextMenuContent, - ContextMenuItem, - ContextMenuSeparator, - ContextMenuTrigger, -} from "../ui/context-menu"; import { TimelineTrack, sortTracksByOrder, @@ -882,38 +874,15 @@ export function TimelineTrackContent({ }; return ( - - -
- -
-
- - - - Split at playhead - - - - Duplicate {element.type === "text" ? "text" : "clip"} - - - - - Delete {element.type === "text" ? "text" : "clip"} - - -
+ ); })}