review: fixes
This commit is contained in:
parent
ac43a5a73f
commit
8a3577d2e7
|
|
@ -191,6 +191,7 @@ export function useElementInteraction({
|
||||||
const [isPendingDrag, setIsPendingDrag] = useState(false);
|
const [isPendingDrag, setIsPendingDrag] = useState(false);
|
||||||
const pendingDragRef = useRef<PendingDragState | null>(null);
|
const pendingDragRef = useRef<PendingDragState | null>(null);
|
||||||
const moveGroupRef = useRef<MoveGroup | null>(null);
|
const moveGroupRef = useRef<MoveGroup | null>(null);
|
||||||
|
const newTrackIdsRef = useRef<string[]>([]);
|
||||||
const groupMoveResultRef = useRef<GroupMoveResult | null>(null);
|
const groupMoveResultRef = useRef<GroupMoveResult | null>(null);
|
||||||
const lastMouseXRef = useRef(0);
|
const lastMouseXRef = useRef(0);
|
||||||
const mouseDownLocationRef = useRef<{ x: number; y: number } | null>(null);
|
const mouseDownLocationRef = useRef<{ x: number; y: number } | null>(null);
|
||||||
|
|
@ -227,6 +228,7 @@ export function useElementInteraction({
|
||||||
|
|
||||||
const endDrag = useCallback(() => {
|
const endDrag = useCallback(() => {
|
||||||
moveGroupRef.current = null;
|
moveGroupRef.current = null;
|
||||||
|
newTrackIdsRef.current = [];
|
||||||
groupMoveResultRef.current = null;
|
groupMoveResultRef.current = null;
|
||||||
setDragState(initialDragState);
|
setDragState(initialDragState);
|
||||||
setDragDropTarget(null);
|
setDragDropTarget(null);
|
||||||
|
|
@ -262,7 +264,7 @@ export function useElementInteraction({
|
||||||
target: {
|
target: {
|
||||||
kind: "newTracks",
|
kind: "newTracks",
|
||||||
anchorInsertIndex: dropTarget.trackIndex,
|
anchorInsertIndex: dropTarget.trackIndex,
|
||||||
newTrackIds: group.members.map(() => generateUUID()),
|
newTrackIds: newTrackIdsRef.current,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -297,7 +299,7 @@ export function useElementInteraction({
|
||||||
target: {
|
target: {
|
||||||
kind: "newTracks",
|
kind: "newTracks",
|
||||||
anchorInsertIndex: dropTarget.trackIndex,
|
anchorInsertIndex: dropTarget.trackIndex,
|
||||||
newTrackIds: group.members.map(() => generateUUID()),
|
newTrackIds: newTrackIdsRef.current,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
@ -386,18 +388,61 @@ export function useElementInteraction({
|
||||||
}
|
}
|
||||||
|
|
||||||
moveGroupRef.current = moveGroup;
|
moveGroupRef.current = moveGroup;
|
||||||
groupMoveResultRef.current = null;
|
newTrackIdsRef.current = moveGroup.members.map(() => generateUUID());
|
||||||
const dragTimeOffsets: Record<string, number> = {};
|
const dragTimeOffsets: Record<string, number> = {};
|
||||||
for (const member of moveGroup.members) {
|
for (const member of moveGroup.members) {
|
||||||
dragTimeOffsets[member.elementId] = member.timeOffset;
|
dragTimeOffsets[member.elementId] = member.timeOffset;
|
||||||
}
|
}
|
||||||
|
const {
|
||||||
|
snappedTime: initialSnappedTime,
|
||||||
|
snapPoint: initialSnapPoint,
|
||||||
|
} = getDragSnapResult({
|
||||||
|
frameSnappedTime: snappedTime,
|
||||||
|
group: moveGroup,
|
||||||
|
});
|
||||||
|
const verticalDragDirection = getVerticalDragDirection({
|
||||||
|
startMouseY: pendingDragRef.current.startMouseY,
|
||||||
|
currentMouseY: clientY,
|
||||||
|
});
|
||||||
|
const anchorDropTarget = getDragDropTarget({
|
||||||
|
clientX,
|
||||||
|
clientY,
|
||||||
|
elementId: pendingDragRef.current.elementId,
|
||||||
|
trackId: pendingDragRef.current.trackId,
|
||||||
|
tracks: sceneTracks,
|
||||||
|
tracksContainerRef,
|
||||||
|
tracksScrollRef,
|
||||||
|
headerRef,
|
||||||
|
zoomLevel,
|
||||||
|
snappedTime: initialSnappedTime,
|
||||||
|
verticalDragDirection,
|
||||||
|
});
|
||||||
|
const nextGroupMoveResult =
|
||||||
|
anchorDropTarget != null
|
||||||
|
? resolveGroupDragMove({
|
||||||
|
group: moveGroup,
|
||||||
|
snappedTime: initialSnappedTime,
|
||||||
|
dropTarget: anchorDropTarget,
|
||||||
|
})
|
||||||
|
: null;
|
||||||
|
groupMoveResultRef.current = nextGroupMoveResult;
|
||||||
|
setDragDropTarget(
|
||||||
|
anchorDropTarget &&
|
||||||
|
(anchorDropTarget.isNewTrack || !nextGroupMoveResult)
|
||||||
|
? {
|
||||||
|
...anchorDropTarget,
|
||||||
|
isNewTrack: true,
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
);
|
||||||
startDrag({
|
startDrag({
|
||||||
...pendingDragRef.current,
|
...pendingDragRef.current,
|
||||||
dragElementIds: moveGroup.members.map((member) => member.elementId),
|
dragElementIds: moveGroup.members.map((member) => member.elementId),
|
||||||
dragTimeOffsets,
|
dragTimeOffsets,
|
||||||
initialCurrentTime: snappedTime,
|
initialCurrentTime: initialSnappedTime,
|
||||||
initialCurrentMouseY: clientY,
|
initialCurrentMouseY: clientY,
|
||||||
});
|
});
|
||||||
|
onSnapPointChange?.(initialSnapPoint);
|
||||||
startedDragThisEvent = true;
|
startedDragThisEvent = true;
|
||||||
pendingDragRef.current = null;
|
pendingDragRef.current = null;
|
||||||
setIsPendingDrag(false);
|
setIsPendingDrag(false);
|
||||||
|
|
@ -533,28 +578,6 @@ export function useElementInteraction({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const dropTarget = getDragDropTarget({
|
|
||||||
clientX,
|
|
||||||
clientY,
|
|
||||||
elementId: dragState.elementId,
|
|
||||||
trackId: dragState.trackId,
|
|
||||||
tracks: sceneTracks,
|
|
||||||
tracksContainerRef,
|
|
||||||
tracksScrollRef,
|
|
||||||
headerRef,
|
|
||||||
zoomLevel,
|
|
||||||
snappedTime: dragState.currentTime,
|
|
||||||
verticalDragDirection: getVerticalDragDirection({
|
|
||||||
startMouseY: dragState.startMouseY,
|
|
||||||
currentMouseY: clientY,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
if (!dropTarget) {
|
|
||||||
endDrag();
|
|
||||||
onSnapPointChange?.(null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const snappedTime = dragState.currentTime;
|
|
||||||
const moveGroup = moveGroupRef.current;
|
const moveGroup = moveGroupRef.current;
|
||||||
if (!moveGroup) {
|
if (!moveGroup) {
|
||||||
endDrag();
|
endDrag();
|
||||||
|
|
@ -562,11 +585,7 @@ export function useElementInteraction({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const groupMoveResult = resolveGroupDragMove({
|
const groupMoveResult = groupMoveResultRef.current;
|
||||||
group: moveGroup,
|
|
||||||
snappedTime,
|
|
||||||
dropTarget,
|
|
||||||
});
|
|
||||||
if (!groupMoveResult) {
|
if (!groupMoveResult) {
|
||||||
endDrag();
|
endDrag();
|
||||||
onSnapPointChange?.(null);
|
onSnapPointChange?.(null);
|
||||||
|
|
@ -604,18 +623,10 @@ export function useElementInteraction({
|
||||||
dragState.isDragging,
|
dragState.isDragging,
|
||||||
dragState.elementId,
|
dragState.elementId,
|
||||||
dragState.startElementTime,
|
dragState.startElementTime,
|
||||||
dragState.startMouseY,
|
|
||||||
dragState.trackId,
|
dragState.trackId,
|
||||||
dragState.currentTime,
|
|
||||||
zoomLevel,
|
|
||||||
endDrag,
|
endDrag,
|
||||||
onSnapPointChange,
|
onSnapPointChange,
|
||||||
editor.timeline,
|
editor.timeline,
|
||||||
tracksContainerRef,
|
|
||||||
tracksScrollRef,
|
|
||||||
headerRef,
|
|
||||||
resolveGroupDragMove,
|
|
||||||
sceneTracks,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import type {
|
||||||
import type { Transform } from "@/lib/rendering";
|
import type { Transform } from "@/lib/rendering";
|
||||||
import { buildMoveGroup } from "@/lib/timeline/group-move/build-group";
|
import { buildMoveGroup } from "@/lib/timeline/group-move/build-group";
|
||||||
import { resolveGroupMove } from "@/lib/timeline/group-move/resolve-move";
|
import { resolveGroupMove } from "@/lib/timeline/group-move/resolve-move";
|
||||||
|
import { snapGroupEdges } from "@/lib/timeline/group-move/snap";
|
||||||
|
|
||||||
function buildTransform(): Transform {
|
function buildTransform(): Transform {
|
||||||
return {
|
return {
|
||||||
|
|
@ -284,4 +285,107 @@ describe("group move", () => {
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("snapGroupEdges snaps the closest group edge and preserves offsets", () => {
|
||||||
|
const tracks = buildTracks({
|
||||||
|
overlay: [
|
||||||
|
buildVideoTrack({
|
||||||
|
id: "overlay-video",
|
||||||
|
elements: [
|
||||||
|
buildVideoElement({ id: "video-1", startTime: 5, duration: 2 }),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
buildTextTrack({
|
||||||
|
id: "overlay-text",
|
||||||
|
elements: [
|
||||||
|
buildTextElement({ id: "text-1", startTime: 8, duration: 3 }),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
main: buildVideoTrack({
|
||||||
|
id: "main",
|
||||||
|
elements: [
|
||||||
|
buildVideoElement({ id: "video-snap", startTime: 10, duration: 4 }),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
const group = buildMoveGroup({
|
||||||
|
anchorRef: { trackId: "overlay-video", elementId: "video-1" },
|
||||||
|
selectedElements: [
|
||||||
|
{ trackId: "overlay-video", elementId: "video-1" },
|
||||||
|
{ trackId: "overlay-text", elementId: "text-1" },
|
||||||
|
],
|
||||||
|
tracks,
|
||||||
|
});
|
||||||
|
if (!group) {
|
||||||
|
throw new Error("Expected group");
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = snapGroupEdges({
|
||||||
|
group,
|
||||||
|
anchorStartTime: 6,
|
||||||
|
tracks,
|
||||||
|
playheadTime: 100,
|
||||||
|
zoomLevel: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
snappedAnchorStartTime: 7,
|
||||||
|
snapPoint: {
|
||||||
|
time: 10,
|
||||||
|
type: "element-start",
|
||||||
|
elementId: "video-snap",
|
||||||
|
trackId: "main",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("snapGroupEdges returns the raw anchor time when nothing is within threshold", () => {
|
||||||
|
const tracks = buildTracks({
|
||||||
|
overlay: [
|
||||||
|
buildVideoTrack({
|
||||||
|
id: "overlay-video",
|
||||||
|
elements: [
|
||||||
|
buildVideoElement({ id: "video-1", startTime: 5, duration: 2 }),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
buildTextTrack({
|
||||||
|
id: "overlay-text",
|
||||||
|
elements: [
|
||||||
|
buildTextElement({ id: "text-1", startTime: 8, duration: 3 }),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
main: buildVideoTrack({
|
||||||
|
id: "main",
|
||||||
|
elements: [
|
||||||
|
buildVideoElement({ id: "video-snap", startTime: 100, duration: 4 }),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
const group = buildMoveGroup({
|
||||||
|
anchorRef: { trackId: "overlay-video", elementId: "video-1" },
|
||||||
|
selectedElements: [
|
||||||
|
{ trackId: "overlay-video", elementId: "video-1" },
|
||||||
|
{ trackId: "overlay-text", elementId: "text-1" },
|
||||||
|
],
|
||||||
|
tracks,
|
||||||
|
});
|
||||||
|
if (!group) {
|
||||||
|
throw new Error("Expected group");
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = snapGroupEdges({
|
||||||
|
group,
|
||||||
|
anchorStartTime: 6,
|
||||||
|
tracks,
|
||||||
|
playheadTime: 200,
|
||||||
|
zoomLevel: 1000,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
snappedAnchorStartTime: 6,
|
||||||
|
snapPoint: null,
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue