mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-11 14:50:02 +00:00
fix(studio): restore golden-branch timeline behaviors dropped by the stack rebuild
The Studio stack rebuild (#2291) landed the remaining NLE layers but dropped or regressed several final-wave behaviors from the reviewed studio-dnd stack, and never repaired the stale timelineZones.ts that #2279 introduced. Restores: - TimelineRuler: sticky under vertical scroll, full-height gridlines removed (beat lines only), frame-number tick labels via a persisted timeDisplayMode store preference (PlayerControls toggle now store-backed) - timelineZones: stable track lanes — lane = authored data-track-index ascending; z is paint order only (replaces the stale z-driven lane pack, which broke track insert-band commits that contractually depend on it) - persistTimelineBatchEdit: a batch member whose patch is a no-op (attributes already at target values, e.g. in a track-insert renumber) is skipped instead of aborting and rolling back the whole batch — this alone made new-track creation (incl. the top insert band) fail silently - useTimelineStackingSync: unresolvable clips read as NaN again so timelineStackingSync's Number.isFinite exclusion contract holds (z=0 fabrications skewed stacking boundaries) - timelineAssetDrop: drops land on the drop track (no overlap bump to max-track+1), data-hf-id stamped, audio gets data-volume - timing edits: soft-reload the server's rewritten GSAP script instead of a full iframe remount (no all-clips flash on move/resize); full reload only when no scriptText or the soft path can't apply, and one full reload when a group edit touches non-active files (new hooks/timelineTimingSync.ts) - duration: content-driven grow-AND-shrink on move/resize/delete, synced optimistically to the store and the live root data-duration at release (was a grow-only ratchet; shrink never updated the readout) New UX: sidebar asset click opens a compact non-modal preview over the canvas (dismiss on outside click, Escape, playback, or seek), and clicking an already-added asset reveals its clip in the timeline (smooth minimal scroll to its time and lane; vertical-only in fit zoom). Verified by pointer-driving a real project: sticky ruler + gridline removal, no iframe remount on move/resize (marker survives, GSAP tween positions rewritten in place), duration readout 40->37->40 on shrink/stretch, and top-insert-band track creation renumbering lanes correctly on disk.
This commit is contained in:
@@ -133,6 +133,7 @@ export function AssetCard({
|
||||
const pointerDownRef = useRef<{ x: number; y: number } | null>(null);
|
||||
|
||||
const setSelectedElementId = usePlayerStore((s) => s.setSelectedElementId);
|
||||
const requestClipReveal = usePlayerStore((s) => s.requestClipReveal);
|
||||
const elements = usePlayerStore((s) => s.elements);
|
||||
const setPreviewAsset = useAssetPreviewStore((s) => s.setPreviewAsset);
|
||||
|
||||
@@ -150,14 +151,17 @@ export function AssetCard({
|
||||
if (used) {
|
||||
const clip = findClipForAsset(elements, asset);
|
||||
if (clip) {
|
||||
setSelectedElementId(clip.key ?? clip.id);
|
||||
const clipKey = clip.key ?? clip.id;
|
||||
setSelectedElementId(clipKey);
|
||||
// Scroll the timeline so the selected clip is actually visible.
|
||||
requestClipReveal(clipKey);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Not added (or no matching clip found) → preview overlay
|
||||
setPreviewAsset(asset, projectId);
|
||||
},
|
||||
[used, elements, asset, projectId, setSelectedElementId, setPreviewAsset],
|
||||
[used, elements, asset, projectId, setSelectedElementId, requestClipReveal, setPreviewAsset],
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -43,6 +43,7 @@ export function AudioRow({
|
||||
// CapCut-style click behavior: drag-threshold gate.
|
||||
const pointerDownRef = useRef<{ x: number; y: number } | null>(null);
|
||||
const setSelectedElementId = usePlayerStore((s) => s.setSelectedElementId);
|
||||
const requestClipReveal = usePlayerStore((s) => s.requestClipReveal);
|
||||
const elements = usePlayerStore((s) => s.elements);
|
||||
const setPreviewAsset = useAssetPreviewStore((s) => s.setPreviewAsset);
|
||||
|
||||
@@ -59,14 +60,17 @@ export function AudioRow({
|
||||
if (used) {
|
||||
const clip = findClipForAsset(elements, asset);
|
||||
if (clip) {
|
||||
setSelectedElementId(clip.key ?? clip.id);
|
||||
const clipKey = clip.key ?? clip.id;
|
||||
setSelectedElementId(clipKey);
|
||||
// Scroll the timeline so the selected clip is actually visible.
|
||||
requestClipReveal(clipKey);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Not added → preview overlay (audio player)
|
||||
setPreviewAsset(asset, projectId);
|
||||
},
|
||||
[used, elements, asset, projectId, setSelectedElementId, setPreviewAsset],
|
||||
[used, elements, asset, projectId, setSelectedElementId, requestClipReveal, setPreviewAsset],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
Reference in New Issue
Block a user