refactor: move timeline element context menu into timeline-element.tsx

This commit is contained in:
Maze Winther
2025-07-08 23:51:46 +02:00
parent 66da1e20d3
commit 813dbcb9c2
2 changed files with 113 additions and 81 deletions

View File

@ -11,6 +11,7 @@ import {
ChevronRight, ChevronRight,
ChevronLeft, ChevronLeft,
Type, Type,
Copy,
} from "lucide-react"; } from "lucide-react";
import { useMediaStore } from "@/stores/media-store"; import { useMediaStore } from "@/stores/media-store";
import { useTimelineStore } from "@/stores/timeline-store"; import { useTimelineStore } from "@/stores/timeline-store";
@ -33,6 +34,13 @@ import {
DropdownMenuSubContent, DropdownMenuSubContent,
DropdownMenuSubTrigger, DropdownMenuSubTrigger,
} from "../ui/dropdown-menu"; } from "../ui/dropdown-menu";
import {
ContextMenu,
ContextMenuContent,
ContextMenuItem,
ContextMenuSeparator,
ContextMenuTrigger,
} from "../ui/context-menu";
export function TimelineElement({ export function TimelineElement({
element, element,
@ -52,6 +60,7 @@ export function TimelineElement({
splitAndKeepLeft, splitAndKeepLeft,
splitAndKeepRight, splitAndKeepRight,
separateAudio, separateAudio,
addElementToTrack,
} = useTimelineStore(); } = useTimelineStore();
const { currentTime } = usePlaybackStore(); const { currentTime } = usePlaybackStore();
@ -172,6 +181,38 @@ export function TimelineElement({
return mediaItem?.type === "video" && track.type === "media"; 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 = () => { const renderElementContent = () => {
if (element.type === "text") { if (element.type === "text") {
return ( return (
@ -255,6 +296,8 @@ export function TimelineElement({
}; };
return ( return (
<ContextMenu>
<ContextMenuTrigger asChild>
<div <div
className={`absolute top-0 h-full select-none timeline-element ${ className={`absolute top-0 h-full select-none timeline-element ${
isBeingDragged ? "z-50" : "z-10" isBeingDragged ? "z-50" : "z-10"
@ -297,5 +340,25 @@ export function TimelineElement({
)} )}
</div> </div>
</div> </div>
</ContextMenuTrigger>
<ContextMenuContent>
<ContextMenuItem onClick={handleElementSplitContext}>
<Scissors className="h-4 w-4 mr-2" />
Split at playhead
</ContextMenuItem>
<ContextMenuItem onClick={handleElementDuplicateContext}>
<Copy className="h-4 w-4 mr-2" />
Duplicate {element.type === "text" ? "text" : "clip"}
</ContextMenuItem>
<ContextMenuSeparator />
<ContextMenuItem
onClick={handleElementDeleteContext}
className="text-destructive focus:text-destructive"
>
<Trash2 className="h-4 w-4 mr-2" />
Delete {element.type === "text" ? "text" : "clip"}
</ContextMenuItem>
</ContextMenuContent>
</ContextMenu>
); );
} }

View File

@ -4,15 +4,7 @@ import { useRef, useState, useEffect } from "react";
import { useTimelineStore } from "@/stores/timeline-store"; import { useTimelineStore } from "@/stores/timeline-store";
import { useMediaStore } from "@/stores/media-store"; import { useMediaStore } from "@/stores/media-store";
import { toast } from "sonner"; import { toast } from "sonner";
import { Copy, Scissors, Trash2 } from "lucide-react";
import { TimelineElement } from "./timeline-element"; import { TimelineElement } from "./timeline-element";
import {
ContextMenu,
ContextMenuContent,
ContextMenuItem,
ContextMenuSeparator,
ContextMenuTrigger,
} from "../ui/context-menu";
import { import {
TimelineTrack, TimelineTrack,
sortTracksByOrder, sortTracksByOrder,
@ -882,10 +874,8 @@ export function TimelineTrackContent({
}; };
return ( return (
<ContextMenu key={element.id}>
<ContextMenuTrigger asChild>
<div>
<TimelineElement <TimelineElement
key={element.id}
element={element} element={element}
track={track} track={track}
zoomLevel={zoomLevel} zoomLevel={zoomLevel}
@ -893,27 +883,6 @@ export function TimelineTrackContent({
onElementMouseDown={handleElementMouseDown} onElementMouseDown={handleElementMouseDown}
onElementClick={handleElementClick} onElementClick={handleElementClick}
/> />
</div>
</ContextMenuTrigger>
<ContextMenuContent>
<ContextMenuItem onClick={handleElementSplit}>
<Scissors className="h-4 w-4 mr-2" />
Split at playhead
</ContextMenuItem>
<ContextMenuItem onClick={handleElementDuplicate}>
<Copy className="h-4 w-4 mr-2" />
Duplicate {element.type === "text" ? "text" : "clip"}
</ContextMenuItem>
<ContextMenuSeparator />
<ContextMenuItem
onClick={handleElementDelete}
className="text-destructive focus:text-destructive"
>
<Trash2 className="h-4 w-4 mr-2" />
Delete {element.type === "text" ? "text" : "clip"}
</ContextMenuItem>
</ContextMenuContent>
</ContextMenu>
); );
})} })}
</> </>