mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-12 07:09:59 +00:00
fix(studio): harden composition timeline reliability (#2615)
* fix(studio): preserve composition playback continuity * feat(studio): drag compositions into the timeline * fix(studio): collapse expanded composition move aliases * fix(studio): make timeline cuts atomic * fix(studio): group inspector gesture history * test(studio): cover masked text selection * fix(studio): harden composition timeline reliability * fix(studio): satisfy CI source gates * fix(studio): harden composition mutation requests
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
import { useCallback, useState, type RefObject } from "react";
|
||||
import { TIMELINE_ASSET_MIME, TIMELINE_BLOCK_MIME } from "../../utils/timelineAssetDrop";
|
||||
import {
|
||||
parseTimelineCompositionPayload,
|
||||
TIMELINE_COMPOSITION_MIME,
|
||||
} from "../../utils/timelineCompositionDrop";
|
||||
import { usePlayerStore } from "../store/playerStore";
|
||||
import { TRACK_H, resolveTimelineAssetDrop } from "./timelineLayout";
|
||||
import type { TimelineDropCallbacks } from "./timelineCallbacks";
|
||||
@@ -32,6 +36,11 @@ function applyJsonDropPayload(
|
||||
}
|
||||
}
|
||||
|
||||
function resolveDropStart(usePointerStart: boolean, pointerStart: number): number {
|
||||
if (usePointerStart) return pointerStart;
|
||||
return Math.max(0, usePlayerStore.getState().currentTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dropping an asset/file/block onto the timeline places it at the PLAYHEAD —
|
||||
* start is the current playhead time, only the track comes from the drop y.
|
||||
@@ -48,6 +57,7 @@ export function useTimelineAssetDrop({
|
||||
onFileDrop,
|
||||
onAssetDrop,
|
||||
onBlockDrop,
|
||||
onCompositionDrop,
|
||||
}: UseTimelineAssetDropOptions) {
|
||||
const [isDragOver, setIsDragOver] = useState(false);
|
||||
|
||||
@@ -56,7 +66,8 @@ export function useTimelineAssetDrop({
|
||||
const hasFiles = types.includes("Files");
|
||||
const hasAsset = types.includes(TIMELINE_ASSET_MIME);
|
||||
const hasBlock = types.includes(TIMELINE_BLOCK_MIME);
|
||||
if (!hasFiles && !hasAsset && !hasBlock) return;
|
||||
const hasComposition = types.includes(TIMELINE_COMPOSITION_MIME);
|
||||
if (!hasFiles && !hasAsset && !hasBlock && !hasComposition) return;
|
||||
e.preventDefault();
|
||||
e.dataTransfer.dropEffect = "copy";
|
||||
setIsDragOver(true);
|
||||
@@ -65,11 +76,10 @@ export function useTimelineAssetDrop({
|
||||
const clearDropPreview = useCallback(() => setIsDragOver(false), []);
|
||||
|
||||
const resolveDropPlacement = useCallback(
|
||||
(clientX: number, clientY: number): TimelinePlacement => {
|
||||
(clientX: number, clientY: number, usePointerStart = false): TimelinePlacement => {
|
||||
const scroll = scrollRef.current;
|
||||
const rect = scroll?.getBoundingClientRect();
|
||||
// Track comes from the vertical drop position; start is the playhead.
|
||||
const { track } = resolveTimelineAssetDrop(
|
||||
const pointer = resolveTimelineAssetDrop(
|
||||
{
|
||||
rectLeft: rect?.left ?? 0,
|
||||
rectTop: rect?.top ?? 0,
|
||||
@@ -77,14 +87,17 @@ export function useTimelineAssetDrop({
|
||||
scrollTop: scroll?.scrollTop ?? 0,
|
||||
pixelsPerSecond: ppsRef.current,
|
||||
duration: durationRef.current,
|
||||
clampStartToDuration: !usePointerStart,
|
||||
trackHeight: TRACK_H,
|
||||
trackOrder: trackOrderRef.current,
|
||||
},
|
||||
clientX,
|
||||
clientY,
|
||||
);
|
||||
const start = Math.max(0, usePlayerStore.getState().currentTime);
|
||||
return { start, track };
|
||||
return {
|
||||
start: resolveDropStart(usePointerStart, pointer.start),
|
||||
track: pointer.track,
|
||||
};
|
||||
},
|
||||
[scrollRef, ppsRef, durationRef, trackOrderRef],
|
||||
);
|
||||
@@ -93,6 +106,14 @@ export function useTimelineAssetDrop({
|
||||
(e: React.DragEvent) => {
|
||||
e.preventDefault();
|
||||
setIsDragOver(false);
|
||||
const compositionPayload = parseTimelineCompositionPayload(
|
||||
e.dataTransfer.getData(TIMELINE_COMPOSITION_MIME),
|
||||
);
|
||||
if (compositionPayload && onCompositionDrop) {
|
||||
const placement = resolveDropPlacement(e.clientX, e.clientY, true);
|
||||
void onCompositionDrop(compositionPayload.sourcePath, placement);
|
||||
return;
|
||||
}
|
||||
const placement = resolveDropPlacement(e.clientX, e.clientY);
|
||||
|
||||
if (onFileDrop && e.dataTransfer.files.length > 0) {
|
||||
@@ -109,7 +130,7 @@ export function useTimelineAssetDrop({
|
||||
applyJsonDropPayload(blockPayload, (p) => p.name, onBlockDrop, placement);
|
||||
}
|
||||
},
|
||||
[resolveDropPlacement, onFileDrop, onAssetDrop, onBlockDrop],
|
||||
[resolveDropPlacement, onFileDrop, onAssetDrop, onBlockDrop, onCompositionDrop],
|
||||
);
|
||||
|
||||
return { isDragOver, handleAssetDragOver, handleAssetDrop, clearDropPreview };
|
||||
|
||||
Reference in New Issue
Block a user