mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-10 22:20:14 +00:00
Re-architect the timeline row model from data-track-index rows to stacking layers. Rows represent stacking layers per context: explicit-z clips merge onto one track when they share a z and don't overlap in time; auto-z clips stay one row each (DOM order); audio is pulled into its own bottom lanes. Rows keyed by a stable layer id, not data-track-index. Vertical drag always writes z-index, never track: drop onto a layer joins it (same z), between layers interpolates a new z, past the ends creates a new front/back layer. data-track-index is never rewritten; #958 holds. Adds hasExplicitZIndex capture (computed z != auto). L1: hasExplicitZIndex on the element model L2: buildStackingTimelineLayers (layer-based rows) L3: layer-aware vertical drag (join / interpolate / new-extreme)
108 lines
3.6 KiB
TypeScript
108 lines
3.6 KiB
TypeScript
import { useCallback, useState, type RefObject } from "react";
|
|
import { TIMELINE_ASSET_MIME, TIMELINE_BLOCK_MIME } from "../../utils/timelineAssetDrop";
|
|
import { TRACK_H, resolveTimelineAssetDrop } from "./timelineLayout";
|
|
import type { TimelineDropCallbacks } from "./timelineCallbacks";
|
|
import type { StackingTimelineLayer, TimelineLayerId } from "./timelineTrackOrder";
|
|
|
|
interface UseTimelineAssetDropOptions extends TimelineDropCallbacks {
|
|
scrollRef: RefObject<HTMLDivElement | null>;
|
|
ppsRef: RefObject<number>;
|
|
durationRef: RefObject<number>;
|
|
trackOrderRef: RefObject<TimelineLayerId[]>;
|
|
timelineLayersRef: RefObject<StackingTimelineLayer[]>;
|
|
}
|
|
|
|
export function useTimelineAssetDrop({
|
|
scrollRef,
|
|
ppsRef,
|
|
durationRef,
|
|
trackOrderRef,
|
|
timelineLayersRef,
|
|
onFileDrop,
|
|
onAssetDrop,
|
|
onBlockDrop,
|
|
}: UseTimelineAssetDropOptions) {
|
|
const [isDragOver, setIsDragOver] = useState(false);
|
|
|
|
const handleAssetDragOver = useCallback((e: React.DragEvent) => {
|
|
const hasFiles = e.dataTransfer.files.length > 0;
|
|
const types = Array.from(e.dataTransfer.types);
|
|
const hasAsset = types.includes(TIMELINE_ASSET_MIME);
|
|
const hasBlock = types.includes(TIMELINE_BLOCK_MIME);
|
|
if (!hasFiles && !hasAsset && !hasBlock) return;
|
|
e.preventDefault();
|
|
if (hasAsset || hasBlock) e.dataTransfer.dropEffect = "copy";
|
|
setIsDragOver(true);
|
|
}, []);
|
|
|
|
const handleAssetDrop = useCallback(
|
|
// fallow-ignore-next-line complexity
|
|
(e: React.DragEvent) => {
|
|
e.preventDefault();
|
|
setIsDragOver(false);
|
|
const scroll = scrollRef.current;
|
|
const rect = scroll?.getBoundingClientRect();
|
|
const layerById = new Map(timelineLayersRef.current.map((layer) => [layer.id, layer]));
|
|
const trackOrder = trackOrderRef.current
|
|
.map((id) => layerById.get(id)?.placementTrack)
|
|
.filter((track): track is number => track != null);
|
|
const dropInput = {
|
|
rectLeft: rect?.left ?? 0,
|
|
rectTop: rect?.top ?? 0,
|
|
scrollLeft: scroll?.scrollLeft ?? 0,
|
|
scrollTop: scroll?.scrollTop ?? 0,
|
|
pixelsPerSecond: ppsRef.current,
|
|
duration: durationRef.current,
|
|
trackHeight: TRACK_H,
|
|
trackOrder,
|
|
};
|
|
if (onFileDrop && e.dataTransfer.files.length > 0) {
|
|
void onFileDrop(
|
|
Array.from(e.dataTransfer.files),
|
|
scroll && rect ? resolveTimelineAssetDrop(dropInput, e.clientX, e.clientY) : undefined,
|
|
);
|
|
return;
|
|
}
|
|
const assetPayload = e.dataTransfer.getData(TIMELINE_ASSET_MIME);
|
|
if (assetPayload && onAssetDrop && scroll && rect) {
|
|
try {
|
|
const parsed = JSON.parse(assetPayload) as { path?: string };
|
|
if (parsed.path)
|
|
void onAssetDrop(
|
|
parsed.path,
|
|
resolveTimelineAssetDrop(dropInput, e.clientX, e.clientY),
|
|
);
|
|
} catch {
|
|
/* ignore malformed drag payloads */
|
|
}
|
|
return;
|
|
}
|
|
const blockPayload = e.dataTransfer.getData(TIMELINE_BLOCK_MIME);
|
|
if (blockPayload && onBlockDrop && scroll && rect) {
|
|
try {
|
|
const parsed = JSON.parse(blockPayload) as { name?: string };
|
|
if (parsed.name)
|
|
void onBlockDrop(
|
|
parsed.name,
|
|
resolveTimelineAssetDrop(dropInput, e.clientX, e.clientY),
|
|
);
|
|
} catch {
|
|
/* ignore malformed drag payloads */
|
|
}
|
|
}
|
|
},
|
|
[
|
|
onAssetDrop,
|
|
onBlockDrop,
|
|
onFileDrop,
|
|
scrollRef,
|
|
ppsRef,
|
|
durationRef,
|
|
trackOrderRef,
|
|
timelineLayersRef,
|
|
],
|
|
);
|
|
|
|
return { isDragOver, setIsDragOver, handleAssetDragOver, handleAssetDrop };
|
|
}
|