diff options
| author | Maze Winther <mazewinther@gmail.com> | 2025-07-10 20:10:01 +0200 |
|---|---|---|
| committer | Maze Winther <mazewinther@gmail.com> | 2025-07-10 20:10:01 +0200 |
| commit | 1376bee16db8933cc65b803fb53306b5c561d808 (patch) | |
| tree | 22d739b33657d8a47c4f8274b65489ec85a7d5ec /apps/web/src/components/editor | |
| parent | 0f175b232feedb5238f5a4fecf5388f44318388d (diff) | |
| download | opencut-1376bee16db8933cc65b803fb53306b5c561d808.tar.gz | |
fix: improve element dragging (can drag outside timeline without it breaking)
Diffstat (limited to 'apps/web/src/components/editor')
| -rw-r--r-- | apps/web/src/components/editor/properties-panel.tsx | 22 | ||||
| -rw-r--r-- | apps/web/src/components/editor/timeline-track.tsx | 134 |
2 files changed, 113 insertions, 43 deletions
diff --git a/apps/web/src/components/editor/properties-panel.tsx b/apps/web/src/components/editor/properties-panel.tsx index 490afa4d..e9b651bc 100644 --- a/apps/web/src/components/editor/properties-panel.tsx +++ b/apps/web/src/components/editor/properties-panel.tsx @@ -8,6 +8,13 @@ import { useTimelineStore } from "@/stores/timeline-store"; import { Textarea } from "../ui/textarea"; import { MediaElement, TextElement } from "@/types/timeline"; import { useMediaStore } from "@/stores/media-store"; +import { + Select, + SelectContent, + SelectTrigger, + SelectValue, + SelectItem, +} from "../ui/select"; export function PropertiesPanel() { const { activeProject } = useProjectStore(); @@ -31,7 +38,7 @@ export function PropertiesPanel() { ); const TextProperties = (element: TextElement, trackId: string) => ( - <div className="space-y-4 p-5"> + <div className="space-y-6 p-5"> <Textarea placeholder="Name" defaultValue={element.content} @@ -40,6 +47,19 @@ export function PropertiesPanel() { updateTextElement(trackId, element.id, { content: e.target.value }) } /> + <div className="flex items-center justify-between gap-6"> + <Label className="text-xs">Font</Label> + <Select> + <SelectTrigger className="w-full text-xs"> + <SelectValue placeholder="Select a font" /> + </SelectTrigger> + <SelectContent> + <SelectItem value="Arial">Arial</SelectItem> + <SelectItem value="Helvetica">Helvetica</SelectItem> + <SelectItem value="Times New Roman">Times New Roman</SelectItem> + </SelectContent> + </Select> + </div> </div> ); diff --git a/apps/web/src/components/editor/timeline-track.tsx b/apps/web/src/components/editor/timeline-track.tsx index 008768d4..8734d0fb 100644 --- a/apps/web/src/components/editor/timeline-track.tsx +++ b/apps/web/src/components/editor/timeline-track.tsx @@ -88,65 +88,115 @@ export function TimelineTrackContent({ const handleMouseUp = (e: MouseEvent) => { if (!dragState.elementId || !dragState.trackId) return; - // Check if the mouse is actually over this track + // If this track initiated the drag, we should handle the mouse up regardless of where it occurs + const isTrackThatStartedDrag = dragState.trackId === track.id; + const timelineRect = timelineRef.current?.getBoundingClientRect(); - if (!timelineRect) return; + if (!timelineRect) { + if (isTrackThatStartedDrag) { + updateElementStartTime( + track.id, + dragState.elementId, + dragState.currentTime + ); + endDragAction(); + } + return; + } const isMouseOverThisTrack = e.clientY >= timelineRect.top && e.clientY <= timelineRect.bottom; - // Only handle if mouse is over this track - if (!isMouseOverThisTrack) return; + if (!isMouseOverThisTrack && !isTrackThatStartedDrag) return; const finalTime = dragState.currentTime; - // Check for overlaps and update position - const sourceTrack = tracks.find((t) => t.id === dragState.trackId); - const movingElement = sourceTrack?.elements.find( - (c) => c.id === dragState.elementId - ); + if (isMouseOverThisTrack) { + const sourceTrack = tracks.find((t) => t.id === dragState.trackId); + const movingElement = sourceTrack?.elements.find( + (c) => c.id === dragState.elementId + ); - if (movingElement) { - const movingElementDuration = - movingElement.duration - - movingElement.trimStart - - movingElement.trimEnd; - const movingElementEnd = finalTime + movingElementDuration; + if (movingElement) { + const movingElementDuration = + movingElement.duration - + movingElement.trimStart - + movingElement.trimEnd; + const movingElementEnd = finalTime + movingElementDuration; - const targetTrack = tracks.find((t) => t.id === track.id); - const hasOverlap = targetTrack?.elements.some((existingElement) => { - if ( - dragState.trackId === track.id && - existingElement.id === dragState.elementId - ) { - return false; + const targetTrack = tracks.find((t) => t.id === track.id); + const hasOverlap = targetTrack?.elements.some((existingElement) => { + if ( + dragState.trackId === track.id && + existingElement.id === dragState.elementId + ) { + return false; + } + const existingStart = existingElement.startTime; + const existingEnd = + existingElement.startTime + + (existingElement.duration - + existingElement.trimStart - + existingElement.trimEnd); + return finalTime < existingEnd && movingElementEnd > existingStart; + }); + + if (!hasOverlap) { + if (dragState.trackId === track.id) { + updateElementStartTime(track.id, dragState.elementId, finalTime); + } else { + moveElementToTrack( + dragState.trackId, + track.id, + dragState.elementId + ); + requestAnimationFrame(() => { + updateElementStartTime( + track.id, + dragState.elementId!, + finalTime + ); + }); + } } - const existingStart = existingElement.startTime; - const existingEnd = - existingElement.startTime + - (existingElement.duration - - existingElement.trimStart - - existingElement.trimEnd); - return finalTime < existingEnd && movingElementEnd > existingStart; - }); + } + } else if (isTrackThatStartedDrag) { + // Mouse is not over this track, but this track started the drag + // This means user released over ruler/outside - update position within same track + const sourceTrack = tracks.find((t) => t.id === dragState.trackId); + const movingElement = sourceTrack?.elements.find( + (c) => c.id === dragState.elementId + ); + + if (movingElement) { + const movingElementDuration = + movingElement.duration - + movingElement.trimStart - + movingElement.trimEnd; + const movingElementEnd = finalTime + movingElementDuration; - if (!hasOverlap) { - if (dragState.trackId === track.id) { + const hasOverlap = track.elements.some((existingElement) => { + if (existingElement.id === dragState.elementId) { + return false; + } + const existingStart = existingElement.startTime; + const existingEnd = + existingElement.startTime + + (existingElement.duration - + existingElement.trimStart - + existingElement.trimEnd); + return finalTime < existingEnd && movingElementEnd > existingStart; + }); + + if (!hasOverlap) { updateElementStartTime(track.id, dragState.elementId, finalTime); - } else { - moveElementToTrack( - dragState.trackId, - track.id, - dragState.elementId - ); - requestAnimationFrame(() => { - updateElementStartTime(track.id, dragState.elementId!, finalTime); - }); } } } - endDragAction(); + if (isTrackThatStartedDrag) { + endDragAction(); + } }; document.addEventListener("mousemove", handleMouseMove); |
