Skip to content

Commit 8a3577d

Browse files
committed
review: fixes
1 parent ac43a5a commit 8a3577d

2 files changed

Lines changed: 154 additions & 39 deletions

File tree

apps/web/src/hooks/timeline/element/use-element-interaction.ts

Lines changed: 50 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ export function useElementInteraction({
191191
const [isPendingDrag, setIsPendingDrag] = useState(false);
192192
const pendingDragRef = useRef<PendingDragState | null>(null);
193193
const moveGroupRef = useRef<MoveGroup | null>(null);
194+
const newTrackIdsRef = useRef<string[]>([]);
194195
const groupMoveResultRef = useRef<GroupMoveResult | null>(null);
195196
const lastMouseXRef = useRef(0);
196197
const mouseDownLocationRef = useRef<{ x: number; y: number } | null>(null);
@@ -227,6 +228,7 @@ export function useElementInteraction({
227228

228229
const endDrag = useCallback(() => {
229230
moveGroupRef.current = null;
231+
newTrackIdsRef.current = [];
230232
groupMoveResultRef.current = null;
231233
setDragState(initialDragState);
232234
setDragDropTarget(null);
@@ -262,7 +264,7 @@ export function useElementInteraction({
262264
target: {
263265
kind: "newTracks",
264266
anchorInsertIndex: dropTarget.trackIndex,
265-
newTrackIds: group.members.map(() => generateUUID()),
267+
newTrackIds: newTrackIdsRef.current,
266268
},
267269
});
268270
}
@@ -297,7 +299,7 @@ export function useElementInteraction({
297299
target: {
298300
kind: "newTracks",
299301
anchorInsertIndex: dropTarget.trackIndex,
300-
newTrackIds: group.members.map(() => generateUUID()),
302+
newTrackIds: newTrackIdsRef.current,
301303
},
302304
});
303305
},
@@ -386,18 +388,61 @@ export function useElementInteraction({
386388
}
387389

388390
moveGroupRef.current = moveGroup;
389-
groupMoveResultRef.current = null;
391+
newTrackIdsRef.current = moveGroup.members.map(() => generateUUID());
390392
const dragTimeOffsets: Record<string, number> = {};
391393
for (const member of moveGroup.members) {
392394
dragTimeOffsets[member.elementId] = member.timeOffset;
393395
}
396+
const {
397+
snappedTime: initialSnappedTime,
398+
snapPoint: initialSnapPoint,
399+
} = getDragSnapResult({
400+
frameSnappedTime: snappedTime,
401+
group: moveGroup,
402+
});
403+
const verticalDragDirection = getVerticalDragDirection({
404+
startMouseY: pendingDragRef.current.startMouseY,
405+
currentMouseY: clientY,
406+
});
407+
const anchorDropTarget = getDragDropTarget({
408+
clientX,
409+
clientY,
410+
elementId: pendingDragRef.current.elementId,
411+
trackId: pendingDragRef.current.trackId,
412+
tracks: sceneTracks,
413+
tracksContainerRef,
414+
tracksScrollRef,
415+
headerRef,
416+
zoomLevel,
417+
snappedTime: initialSnappedTime,
418+
verticalDragDirection,
419+
});
420+
const nextGroupMoveResult =
421+
anchorDropTarget != null
422+
? resolveGroupDragMove({
423+
group: moveGroup,
424+
snappedTime: initialSnappedTime,
425+
dropTarget: anchorDropTarget,
426+
})
427+
: null;
428+
groupMoveResultRef.current = nextGroupMoveResult;
429+
setDragDropTarget(
430+
anchorDropTarget &&
431+
(anchorDropTarget.isNewTrack || !nextGroupMoveResult)
432+
? {
433+
...anchorDropTarget,
434+
isNewTrack: true,
435+
}
436+
: null,
437+
);
394438
startDrag({
395439
...pendingDragRef.current,
396440
dragElementIds: moveGroup.members.map((member) => member.elementId),
397441
dragTimeOffsets,
398-
initialCurrentTime: snappedTime,
442+
initialCurrentTime: initialSnappedTime,
399443
initialCurrentMouseY: clientY,
400444
});
445+
onSnapPointChange?.(initialSnapPoint);
401446
startedDragThisEvent = true;
402447
pendingDragRef.current = null;
403448
setIsPendingDrag(false);
@@ -533,40 +578,14 @@ export function useElementInteraction({
533578
}
534579
}
535580

536-
const dropTarget = getDragDropTarget({
537-
clientX,
538-
clientY,
539-
elementId: dragState.elementId,
540-
trackId: dragState.trackId,
541-
tracks: sceneTracks,
542-
tracksContainerRef,
543-
tracksScrollRef,
544-
headerRef,
545-
zoomLevel,
546-
snappedTime: dragState.currentTime,
547-
verticalDragDirection: getVerticalDragDirection({
548-
startMouseY: dragState.startMouseY,
549-
currentMouseY: clientY,
550-
}),
551-
});
552-
if (!dropTarget) {
553-
endDrag();
554-
onSnapPointChange?.(null);
555-
return;
556-
}
557-
const snappedTime = dragState.currentTime;
558581
const moveGroup = moveGroupRef.current;
559582
if (!moveGroup) {
560583
endDrag();
561584
onSnapPointChange?.(null);
562585
return;
563586
}
564587

565-
const groupMoveResult = resolveGroupDragMove({
566-
group: moveGroup,
567-
snappedTime,
568-
dropTarget,
569-
});
588+
const groupMoveResult = groupMoveResultRef.current;
570589
if (!groupMoveResult) {
571590
endDrag();
572591
onSnapPointChange?.(null);
@@ -604,18 +623,10 @@ export function useElementInteraction({
604623
dragState.isDragging,
605624
dragState.elementId,
606625
dragState.startElementTime,
607-
dragState.startMouseY,
608626
dragState.trackId,
609-
dragState.currentTime,
610-
zoomLevel,
611627
endDrag,
612628
onSnapPointChange,
613629
editor.timeline,
614-
tracksContainerRef,
615-
tracksScrollRef,
616-
headerRef,
617-
resolveGroupDragMove,
618-
sceneTracks,
619630
]);
620631

621632
useEffect(() => {

apps/web/src/lib/timeline/group-move/__tests__/group-move.test.ts

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import type {
1111
import type { Transform } from "@/lib/rendering";
1212
import { buildMoveGroup } from "@/lib/timeline/group-move/build-group";
1313
import { resolveGroupMove } from "@/lib/timeline/group-move/resolve-move";
14+
import { snapGroupEdges } from "@/lib/timeline/group-move/snap";
1415

1516
function buildTransform(): Transform {
1617
return {
@@ -284,4 +285,107 @@ describe("group move", () => {
284285
},
285286
]);
286287
});
288+
289+
test("snapGroupEdges snaps the closest group edge and preserves offsets", () => {
290+
const tracks = buildTracks({
291+
overlay: [
292+
buildVideoTrack({
293+
id: "overlay-video",
294+
elements: [
295+
buildVideoElement({ id: "video-1", startTime: 5, duration: 2 }),
296+
],
297+
}),
298+
buildTextTrack({
299+
id: "overlay-text",
300+
elements: [
301+
buildTextElement({ id: "text-1", startTime: 8, duration: 3 }),
302+
],
303+
}),
304+
],
305+
main: buildVideoTrack({
306+
id: "main",
307+
elements: [
308+
buildVideoElement({ id: "video-snap", startTime: 10, duration: 4 }),
309+
],
310+
}),
311+
});
312+
const group = buildMoveGroup({
313+
anchorRef: { trackId: "overlay-video", elementId: "video-1" },
314+
selectedElements: [
315+
{ trackId: "overlay-video", elementId: "video-1" },
316+
{ trackId: "overlay-text", elementId: "text-1" },
317+
],
318+
tracks,
319+
});
320+
if (!group) {
321+
throw new Error("Expected group");
322+
}
323+
324+
const result = snapGroupEdges({
325+
group,
326+
anchorStartTime: 6,
327+
tracks,
328+
playheadTime: 100,
329+
zoomLevel: 1,
330+
});
331+
332+
expect(result).toEqual({
333+
snappedAnchorStartTime: 7,
334+
snapPoint: {
335+
time: 10,
336+
type: "element-start",
337+
elementId: "video-snap",
338+
trackId: "main",
339+
},
340+
});
341+
});
342+
343+
test("snapGroupEdges returns the raw anchor time when nothing is within threshold", () => {
344+
const tracks = buildTracks({
345+
overlay: [
346+
buildVideoTrack({
347+
id: "overlay-video",
348+
elements: [
349+
buildVideoElement({ id: "video-1", startTime: 5, duration: 2 }),
350+
],
351+
}),
352+
buildTextTrack({
353+
id: "overlay-text",
354+
elements: [
355+
buildTextElement({ id: "text-1", startTime: 8, duration: 3 }),
356+
],
357+
}),
358+
],
359+
main: buildVideoTrack({
360+
id: "main",
361+
elements: [
362+
buildVideoElement({ id: "video-snap", startTime: 100, duration: 4 }),
363+
],
364+
}),
365+
});
366+
const group = buildMoveGroup({
367+
anchorRef: { trackId: "overlay-video", elementId: "video-1" },
368+
selectedElements: [
369+
{ trackId: "overlay-video", elementId: "video-1" },
370+
{ trackId: "overlay-text", elementId: "text-1" },
371+
],
372+
tracks,
373+
});
374+
if (!group) {
375+
throw new Error("Expected group");
376+
}
377+
378+
const result = snapGroupEdges({
379+
group,
380+
anchorStartTime: 6,
381+
tracks,
382+
playheadTime: 200,
383+
zoomLevel: 1000,
384+
});
385+
386+
expect(result).toEqual({
387+
snappedAnchorStartTime: 6,
388+
snapPoint: null,
389+
});
390+
});
287391
});

0 commit comments

Comments
 (0)