mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-11 06:30:03 +00:00
refactor(studio): single-source the timeline stacking key + guard audio reorder
The element stacking key (element.key ?? id) was recomputed in four places (reorder-intent generation, row ordering, the commit-time sibling lookup via a threaded keyOf param, and resolveTimelineMove). Any drift would silently break the sibling lookup and no-op the reorder. Route all of them through the existing getTimelineElementIdentity owner, share one toStackingOrderItem mapper between row ordering and reorder intent, and drop the keyOf parameter. Also enforce the audio side-effect invariant in the single mutation owner (applyTimelineStackingReorder): dragging an audio clip has no visual layer to restack, so it never writes z-index. Covered by a new hook test.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { formatTime } from "../lib/time";
|
||||
import { roundToCenti } from "../../utils/rounding";
|
||||
import { resolveContextOrder, resolveStackingContextKey } from "../lib/layerOrdering";
|
||||
import { getTimelineElementIdentity } from "../lib/timelineElementHelpers";
|
||||
|
||||
const roundToCentiseconds = roundToCenti;
|
||||
|
||||
@@ -32,7 +33,7 @@ export interface TimelineStackingReorderIntent {
|
||||
siblingKeys: string[];
|
||||
}
|
||||
|
||||
interface TimelineStackingOrderItem {
|
||||
export interface TimelineStackingOrderItem {
|
||||
key: string;
|
||||
track: number;
|
||||
zIndex: number;
|
||||
@@ -41,9 +42,9 @@ interface TimelineStackingOrderItem {
|
||||
compositionAncestors: readonly string[];
|
||||
}
|
||||
|
||||
function toStackingOrderItem(element: TimelineStackingElement): TimelineStackingOrderItem {
|
||||
export function toStackingOrderItem(element: TimelineStackingElement): TimelineStackingOrderItem {
|
||||
return {
|
||||
key: element.key ?? element.id,
|
||||
key: getTimelineElementIdentity(element),
|
||||
track: element.track,
|
||||
zIndex: element.zIndex ?? 0,
|
||||
stackingContextId: element.stackingContextId ?? null,
|
||||
@@ -77,8 +78,9 @@ export function resolveTimelineStackingReorderByTargetTrack(args: {
|
||||
}): TimelineStackingReorderIntent | null {
|
||||
const orderedSiblings = resolveContextSiblings(args.element, args.elements);
|
||||
if (orderedSiblings.length <= 1) return null;
|
||||
const draggedKey = args.element.key ?? args.element.id;
|
||||
const fromIndex = orderedSiblings.findIndex((sibling) => sibling.key === draggedKey);
|
||||
const fromIndex = orderedSiblings.findIndex(
|
||||
(sibling) => sibling.key === getTimelineElementIdentity(args.element),
|
||||
);
|
||||
if (fromIndex < 0) return null;
|
||||
const toIndex = orderedSiblings.findIndex((sibling) => sibling.track === args.targetTrack);
|
||||
if (toIndex < 0) return null;
|
||||
@@ -178,7 +180,7 @@ export function resolveTimelineMove(
|
||||
// stacking context (top = front), rather than changing the raw track number.
|
||||
if (input.stackingElement && input.stackingElements) {
|
||||
const orderedSiblings = resolveContextSiblings(input.stackingElement, input.stackingElements);
|
||||
const draggedKey = input.stackingElement.key ?? input.stackingElement.id;
|
||||
const draggedKey = getTimelineElementIdentity(input.stackingElement);
|
||||
const fromIndex = orderedSiblings.findIndex((sibling) => sibling.key === draggedKey);
|
||||
if (fromIndex >= 0 && orderedSiblings.length > 1) {
|
||||
const toIndex = clamp(fromIndex + deltaTrack, 0, orderedSiblings.length - 1);
|
||||
|
||||
@@ -1,39 +1,23 @@
|
||||
import { type TimelineElement } from "../store/playerStore";
|
||||
import {
|
||||
resolveContextOrder,
|
||||
resolveStackingContextKey,
|
||||
type ContextOrderItem,
|
||||
} from "../lib/layerOrdering";
|
||||
import { resolveContextOrder, resolveStackingContextKey } from "../lib/layerOrdering";
|
||||
import { getTimelineElementIdentity } from "../lib/timelineElementHelpers";
|
||||
import { toStackingOrderItem, type TimelineStackingOrderItem } from "./timelineEditing";
|
||||
|
||||
/**
|
||||
* Pure timeline track-ordering logic. Timeline rows are ordered by scoped
|
||||
* stacking (z-index per stacking context, top = front), with data-track-index
|
||||
* used only to split time-overlapping clips of equal rank onto separate rows.
|
||||
* Extracted from Timeline.tsx to keep the component under the studio 600-LOC cap.
|
||||
*
|
||||
* Key derivation and stacking-descriptor mapping are owned by timelineEditing so
|
||||
* the row order here and the reorder intent there interpret every element the
|
||||
* same way.
|
||||
*/
|
||||
|
||||
interface TimelineTrackOrderItem extends ContextOrderItem {
|
||||
key: string;
|
||||
track: number;
|
||||
start: number;
|
||||
duration: number;
|
||||
}
|
||||
|
||||
function getTimelineElementKey(element: TimelineElement): string {
|
||||
return element.key ?? element.id;
|
||||
}
|
||||
type TimelineTrackOrderItem = TimelineStackingOrderItem & { start: number; duration: number };
|
||||
|
||||
function toTimelineTrackOrderItem(element: TimelineElement): TimelineTrackOrderItem {
|
||||
return {
|
||||
key: getTimelineElementKey(element),
|
||||
track: element.track,
|
||||
start: element.start,
|
||||
duration: element.duration,
|
||||
zIndex: element.zIndex ?? 0,
|
||||
stackingContextId: element.stackingContextId ?? null,
|
||||
parentCompositionId: element.parentCompositionId ?? null,
|
||||
compositionAncestors: element.compositionAncestors ?? [],
|
||||
};
|
||||
return { ...toStackingOrderItem(element), start: element.start, duration: element.duration };
|
||||
}
|
||||
|
||||
function timelineElementsOverlap(
|
||||
@@ -51,7 +35,7 @@ function trackFrontOrderIndex(
|
||||
for (const element of elements) {
|
||||
orderIndex = Math.min(
|
||||
orderIndex,
|
||||
orderIndexByKey.get(getTimelineElementKey(element)) ?? Number.POSITIVE_INFINITY,
|
||||
orderIndexByKey.get(getTimelineElementIdentity(element)) ?? Number.POSITIVE_INFINITY,
|
||||
);
|
||||
}
|
||||
return orderIndex;
|
||||
|
||||
Reference in New Issue
Block a user