mirror of
https://github.com/OpenCut-app/OpenCut.git
synced 2026-07-13 21:52:53 +02:00
fix: ensure dragging/resizing an element on the timeline doesn't deselect it
This commit is contained in:
@@ -78,7 +78,6 @@ export function Timeline() {
|
|||||||
snappingEnabled,
|
snappingEnabled,
|
||||||
toggleSnapping,
|
toggleSnapping,
|
||||||
dragState,
|
dragState,
|
||||||
justFinishedDragging,
|
|
||||||
} = useTimelineStore();
|
} = useTimelineStore();
|
||||||
const { mediaItems, addMediaItem } = useMediaStore();
|
const { mediaItems, addMediaItem } = useMediaStore();
|
||||||
const { activeProject } = useProjectStore();
|
const { activeProject } = useProjectStore();
|
||||||
@@ -92,6 +91,14 @@ export function Timeline() {
|
|||||||
const rulerRef = useRef<HTMLDivElement>(null);
|
const rulerRef = useRef<HTMLDivElement>(null);
|
||||||
const [isInTimeline, setIsInTimeline] = useState(false);
|
const [isInTimeline, setIsInTimeline] = useState(false);
|
||||||
|
|
||||||
|
// Track mouse down/up for distinguishing clicks from drag/resize ends
|
||||||
|
const mouseTrackingRef = useRef({
|
||||||
|
isMouseDown: false,
|
||||||
|
downX: 0,
|
||||||
|
downY: 0,
|
||||||
|
downTime: 0,
|
||||||
|
});
|
||||||
|
|
||||||
// Timeline zoom functionality
|
// Timeline zoom functionality
|
||||||
const { zoomLevel, setZoomLevel, handleWheel } = useTimelineZoom({
|
const { zoomLevel, setZoomLevel, handleWheel } = useTimelineZoom({
|
||||||
containerRef: timelineRef,
|
containerRef: timelineRef,
|
||||||
@@ -158,16 +165,69 @@ export function Timeline() {
|
|||||||
setCurrentSnapPoint(snapPoint);
|
setCurrentSnapPoint(snapPoint);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// Track mouse down to distinguish real clicks from drag/resize ends
|
||||||
|
const handleTimelineMouseDown = useCallback((e: React.MouseEvent) => {
|
||||||
|
// Only track mouse down on timeline background areas (not elements)
|
||||||
|
const target = e.target as HTMLElement;
|
||||||
|
const isTimelineBackground =
|
||||||
|
!target.closest(".timeline-element") &&
|
||||||
|
!playheadRef.current?.contains(target) &&
|
||||||
|
!target.closest("[data-track-labels]");
|
||||||
|
|
||||||
|
if (isTimelineBackground) {
|
||||||
|
mouseTrackingRef.current = {
|
||||||
|
isMouseDown: true,
|
||||||
|
downX: e.clientX,
|
||||||
|
downY: e.clientY,
|
||||||
|
downTime: e.timeStamp,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
// Timeline content click to seek handler
|
// Timeline content click to seek handler
|
||||||
const handleTimelineContentClick = useCallback(
|
const handleTimelineContentClick = useCallback(
|
||||||
(e: React.MouseEvent) => {
|
(e: React.MouseEvent) => {
|
||||||
// Don't seek if this was a selection box operation
|
const { isMouseDown, downX, downY, downTime } = mouseTrackingRef.current;
|
||||||
if (isSelecting || justFinishedSelecting) {
|
|
||||||
|
// Reset mouse tracking
|
||||||
|
mouseTrackingRef.current = {
|
||||||
|
isMouseDown: false,
|
||||||
|
downX: 0,
|
||||||
|
downY: 0,
|
||||||
|
downTime: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Only process as click if we tracked a mouse down on timeline background
|
||||||
|
if (!isMouseDown) {
|
||||||
|
console.log(
|
||||||
|
JSON.stringify({
|
||||||
|
ignoredClickWithoutMouseDown: true,
|
||||||
|
timeStamp: e.timeStamp,
|
||||||
|
})
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't seek if we just finished dragging an element
|
// Check if mouse moved significantly (indicates drag, not click)
|
||||||
if (justFinishedDragging) {
|
const deltaX = Math.abs(e.clientX - downX);
|
||||||
|
const deltaY = Math.abs(e.clientY - downY);
|
||||||
|
const deltaTime = e.timeStamp - downTime;
|
||||||
|
|
||||||
|
if (deltaX > 5 || deltaY > 5 || deltaTime > 500) {
|
||||||
|
console.log(
|
||||||
|
JSON.stringify({
|
||||||
|
ignoredDragNotClick: true,
|
||||||
|
deltaX,
|
||||||
|
deltaY,
|
||||||
|
deltaTime,
|
||||||
|
timeStamp: e.timeStamp,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't seek if this was a selection box operation
|
||||||
|
if (isSelecting || justFinishedSelecting) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,19 +247,6 @@ export function Timeline() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// MAIN FIX: Only seek on direct clicks to timeline background areas
|
|
||||||
// Check if the click is on the actual timeline background, not on any interactive elements
|
|
||||||
const target = e.target as HTMLElement;
|
|
||||||
const isTimelineBackground =
|
|
||||||
target.classList.contains("track-elements-container") ||
|
|
||||||
target.closest("[data-ruler-area]") ||
|
|
||||||
target.classList.contains("timeline-track-background");
|
|
||||||
|
|
||||||
if (!isTimelineBackground) {
|
|
||||||
clearSelectedElements();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear selected elements when clicking empty timeline area
|
// Clear selected elements when clicking empty timeline area
|
||||||
console.log(JSON.stringify({ clearingSelectedElements: true }));
|
console.log(JSON.stringify({ clearingSelectedElements: true }));
|
||||||
clearSelectedElements();
|
clearSelectedElements();
|
||||||
@@ -256,7 +303,6 @@ export function Timeline() {
|
|||||||
clearSelectedElements,
|
clearSelectedElements,
|
||||||
isSelecting,
|
isSelecting,
|
||||||
justFinishedSelecting,
|
justFinishedSelecting,
|
||||||
justFinishedDragging,
|
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -931,7 +977,10 @@ export function Timeline() {
|
|||||||
<div
|
<div
|
||||||
className="flex-1 relative overflow-hidden"
|
className="flex-1 relative overflow-hidden"
|
||||||
onWheel={handleWheel}
|
onWheel={handleWheel}
|
||||||
onMouseDown={handleSelectionMouseDown}
|
onMouseDown={(e) => {
|
||||||
|
handleTimelineMouseDown(e);
|
||||||
|
handleSelectionMouseDown(e);
|
||||||
|
}}
|
||||||
onClick={handleTimelineContentClick}
|
onClick={handleTimelineContentClick}
|
||||||
ref={tracksContainerRef}
|
ref={tracksContainerRef}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -186,12 +186,6 @@ export function useTimelineElementResize({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleResizeEnd = () => {
|
const handleResizeEnd = () => {
|
||||||
console.log(
|
|
||||||
JSON.stringify({
|
|
||||||
resizeEnd: true,
|
|
||||||
timeStamp: Date.now(),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
setResizing(null);
|
setResizing(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -74,7 +74,6 @@ interface TimelineStore {
|
|||||||
clickOffsetTime: number;
|
clickOffsetTime: number;
|
||||||
currentTime: number;
|
currentTime: number;
|
||||||
};
|
};
|
||||||
justFinishedDragging: boolean;
|
|
||||||
setDragState: (dragState: Partial<TimelineStore["dragState"]>) => void;
|
setDragState: (dragState: Partial<TimelineStore["dragState"]>) => void;
|
||||||
startDrag: (
|
startDrag: (
|
||||||
elementId: string,
|
elementId: string,
|
||||||
@@ -231,8 +230,6 @@ export const useTimelineStore = create<TimelineStore>((set, get) => {
|
|||||||
// Snapping settings defaults
|
// Snapping settings defaults
|
||||||
snappingEnabled: true,
|
snappingEnabled: true,
|
||||||
|
|
||||||
justFinishedDragging: false,
|
|
||||||
|
|
||||||
getSortedTracks: () => {
|
getSortedTracks: () => {
|
||||||
const { _tracks } = get();
|
const { _tracks } = get();
|
||||||
const tracksWithMain = ensureMainTrack(_tracks);
|
const tracksWithMain = ensureMainTrack(_tracks);
|
||||||
@@ -941,12 +938,7 @@ export const useTimelineStore = create<TimelineStore>((set, get) => {
|
|||||||
clickOffsetTime: 0,
|
clickOffsetTime: 0,
|
||||||
currentTime: 0,
|
currentTime: 0,
|
||||||
},
|
},
|
||||||
justFinishedDragging: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
set({ justFinishedDragging: false });
|
|
||||||
}, 50);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Persistence methods
|
// Persistence methods
|
||||||
|
|||||||
Reference in New Issue
Block a user