fix: improve element dragging (can drag outside timeline without it breaking)

This commit is contained in:
Maze Winther
2025-07-10 20:10:01 +02:00
parent 0f175b232f
commit 1376bee16d
2 changed files with 113 additions and 43 deletions

View File

@ -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>
);

View File

@ -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 (!hasOverlap) {
if (dragState.trackId === track.id) {
if (movingElement) {
const movingElementDuration =
movingElement.duration -
movingElement.trimStart -
movingElement.trimEnd;
const movingElementEnd = finalTime + movingElementDuration;
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);