@hyperframes/studio 0.7.56 → 0.7.58
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/hyperframes-player-CtTDO63S.js +459 -0
- package/dist/assets/{index-CmVCjZjp.js → index-B_UvTX3E.js} +205 -205
- package/dist/assets/{index-BWnOxAiH.js → index-C47jAC3Q.js} +1 -1
- package/dist/assets/{index-C4csZims.js → index-DeQPzqwH.js} +1 -1
- package/dist/assets/index-uahwWkgw.css +1 -0
- package/dist/{chunk-5QSIMBEJ.js → chunk-OBAG3GWK.js} +33 -21
- package/dist/chunk-OBAG3GWK.js.map +1 -0
- package/dist/{domEditingLayers-6LQGKPOI.js → domEditingLayers-2CKWGEHS.js} +2 -2
- package/dist/index.d.ts +41 -4
- package/dist/index.html +2 -2
- package/dist/index.js +7436 -5423
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
- package/src/App.tsx +3 -3
- package/src/components/StudioFeedbackBar.tsx +2 -2
- package/src/components/editor/CanvasContextMenu.test.tsx +166 -2
- package/src/components/editor/CanvasContextMenu.tsx +100 -20
- package/src/components/editor/DomEditOverlay.tsx +34 -62
- package/src/components/editor/LayersPanel.tsx +111 -91
- package/src/components/editor/canvasContextMenuZOrder.test.ts +243 -2
- package/src/components/editor/canvasContextMenuZOrder.ts +184 -23
- package/src/components/editor/domEditingDom.ts +5 -6
- package/src/components/editor/domEditingElement.ts +15 -32
- package/src/components/editor/layersPanelSort.ts +80 -0
- package/src/components/editor/propertyPanelColorGradingSection.tsx +15 -21
- package/src/components/editor/useCanvasContextMenuState.ts +92 -0
- package/src/components/editor/useLayerRevealOverride.test.ts +169 -0
- package/src/components/editor/useLayerRevealOverride.ts +389 -0
- package/src/components/editor/useZOrderCrossedFlash.tsx +68 -0
- package/src/components/nle/AssetPreviewOverlay.test.tsx +106 -0
- package/src/components/nle/AssetPreviewOverlay.tsx +61 -37
- package/src/components/nle/PreviewOverlays.tsx +61 -16
- package/src/components/nle/TimelinePane.test.ts +3 -0
- package/src/components/nle/TimelinePane.tsx +11 -1
- package/src/components/nle/useCanvasZOrderTimelineMirror.test.tsx +440 -0
- package/src/components/nle/useCanvasZOrderTimelineMirror.ts +191 -0
- package/src/components/nle/useTimelineEditCallbacks.ts +1 -0
- package/src/components/nle/zLaneGesture.test.ts +176 -0
- package/src/components/nle/zLaneGesture.ts +77 -0
- package/src/components/sidebar/AssetCard.test.tsx +106 -0
- package/src/components/sidebar/AssetCard.tsx +19 -2
- package/src/components/sidebar/AudioRow.tsx +19 -2
- package/src/hooks/domEditCommitTypes.ts +29 -2
- package/src/hooks/elementLifecycleOpsTestUtils.ts +25 -0
- package/src/hooks/fetchStubTestUtils.ts +16 -0
- package/src/hooks/timelineEditingHelpers.test.ts +183 -1
- package/src/hooks/timelineEditingHelpers.ts +63 -259
- package/src/hooks/timelineMoveAdapter.test.ts +44 -40
- package/src/hooks/timelineMoveAdapter.ts +7 -4
- package/src/hooks/timelineTimingSync.test.ts +820 -0
- package/src/hooks/timelineTimingSync.ts +597 -0
- package/src/hooks/timelineTrackVisibility.test.ts +3 -1
- package/src/hooks/timelineTrackVisibility.ts +4 -3
- package/src/hooks/useCaptionDetection.ts +2 -0
- package/src/hooks/useClipboard.ts +19 -17
- package/src/hooks/useCompositionDimensions.ts +32 -5
- package/src/hooks/useDomEditCommits.test.tsx +392 -23
- package/src/hooks/useDomEditCommits.ts +180 -53
- package/src/hooks/useDomEditSession.test.tsx +3 -1
- package/src/hooks/useDomEditSession.ts +1 -1
- package/src/hooks/useElementLifecycleOps.test.tsx +559 -28
- package/src/hooks/useElementLifecycleOps.ts +175 -69
- package/src/hooks/useElementPicker.ts +7 -8
- package/src/hooks/usePreviewPersistence.ts +2 -2
- package/src/hooks/useTimelineAssetDropOps.ts +175 -0
- package/src/hooks/useTimelineEditing.test.tsx +591 -277
- package/src/hooks/useTimelineEditing.ts +202 -282
- package/src/hooks/useTimelineEditingTypes.ts +1 -1
- package/src/hooks/useTimelineGroupEditing.ts +187 -134
- package/src/player/components/PlayerControls.tsx +5 -5
- package/src/player/components/PlayheadIndicator.tsx +7 -2
- package/src/player/components/Timeline.test.ts +105 -18
- package/src/player/components/Timeline.tsx +69 -98
- package/src/player/components/TimelineCanvas.tsx +47 -7
- package/src/player/components/TimelineLanes.tsx +32 -10
- package/src/player/components/TimelineOverlays.tsx +36 -0
- package/src/player/components/TimelineRuler.tsx +70 -47
- package/src/player/components/TrackGapContextMenu.tsx +116 -0
- package/src/player/components/timelineCallbacks.ts +4 -1
- package/src/player/components/timelineClipDragCommit.test.ts +370 -11
- package/src/player/components/timelineClipDragCommit.ts +267 -68
- package/src/player/components/timelineGapCommit.test.ts +163 -0
- package/src/player/components/timelineGapCommit.ts +103 -0
- package/src/player/components/timelineGaps.test.ts +221 -0
- package/src/player/components/timelineGaps.ts +182 -0
- package/src/player/components/timelineLayout.test.ts +2 -1
- package/src/player/components/timelineLayout.ts +119 -30
- package/src/player/components/timelineMarquee.test.ts +20 -10
- package/src/player/components/timelineMarquee.ts +9 -2
- package/src/player/components/timelineRevealScroll.test.ts +133 -0
- package/src/player/components/timelineRevealScroll.ts +91 -0
- package/src/player/components/timelineStackingSync.test.ts +102 -1
- package/src/player/components/timelineStackingSync.ts +45 -5
- package/src/player/components/timelineTrackPersistPipeline.test.ts +168 -0
- package/src/player/components/timelineZMirror.test.ts +667 -0
- package/src/player/components/timelineZMirror.ts +393 -0
- package/src/player/components/timelineZones.test.ts +122 -310
- package/src/player/components/timelineZones.ts +82 -135
- package/src/player/components/timelineZoom.test.ts +2 -2
- package/src/player/components/timelineZoom.ts +7 -3
- package/src/player/components/useTimelineClipDrag.ts +1 -1
- package/src/player/components/useTimelineGapHighlights.test.ts +119 -0
- package/src/player/components/useTimelineGapHighlights.ts +110 -0
- package/src/player/components/useTimelinePlayhead.ts +4 -3
- package/src/player/components/useTimelineRangeSelection.ts +6 -4
- package/src/player/components/useTimelineRevealClip.ts +56 -0
- package/src/player/components/useTimelineScrollViewport.ts +73 -0
- package/src/player/components/useTimelineStackingSync.test.tsx +11 -2
- package/src/player/components/useTimelineStackingSync.ts +17 -4
- package/src/player/components/useTimelineTrackDerivations.ts +38 -0
- package/src/player/components/useTrackGapMenu.ts +155 -0
- package/src/player/hooks/useExpandedTimelineElements.test.ts +67 -4
- package/src/player/hooks/useExpandedTimelineElements.ts +19 -1
- package/src/player/hooks/useTimelinePlayer.seek.test.ts +55 -0
- package/src/player/hooks/useTimelinePlayer.test.ts +36 -3
- package/src/player/hooks/useTimelinePlayer.ts +4 -0
- package/src/player/hooks/useTimelineSyncCallbacks.ts +11 -1
- package/src/player/index.ts +3 -1
- package/src/player/lib/layerOrdering.test.ts +1 -15
- package/src/player/lib/layerOrdering.ts +5 -28
- package/src/player/lib/runtimeProtocol.test.ts +48 -0
- package/src/player/lib/runtimeProtocol.ts +65 -0
- package/src/player/lib/timelineDOM.test.ts +72 -0
- package/src/player/lib/timelineDOM.ts +12 -0
- package/src/player/lib/timelineElementHelpers.ts +54 -11
- package/src/player/lib/timelineIframeHelpers.ts +2 -4
- package/src/player/store/playerStore.test.ts +38 -13
- package/src/player/store/playerStore.ts +49 -1
- package/src/telemetry/events.test.ts +10 -0
- package/src/telemetry/events.ts +1 -0
- package/src/utils/assetPreviewDismiss.test.ts +27 -0
- package/src/utils/assetPreviewDismiss.ts +29 -0
- package/src/utils/domEditSaveQueue.ts +4 -3
- package/src/utils/editHistory.test.ts +82 -0
- package/src/utils/editHistory.ts +14 -2
- package/src/utils/gsapSoftReload.test.ts +54 -115
- package/src/utils/gsapSoftReload.ts +42 -152
- package/src/utils/gsapUndoRestore.test.ts +269 -0
- package/src/utils/gsapUndoRestore.ts +269 -0
- package/src/utils/sourceScopedSelectorIndex.ts +29 -0
- package/src/utils/timelineAssetDrop.test.ts +144 -52
- package/src/utils/timelineAssetDrop.ts +62 -86
- package/dist/assets/hyperframes-player-BBrKzTGd.js +0 -459
- package/dist/assets/index-D-GyYi2d.css +0 -1
- package/dist/chunk-5QSIMBEJ.js.map +0 -1
- /package/dist/{domEditingLayers-6LQGKPOI.js.map → domEditingLayers-2CKWGEHS.js.map} +0 -0
|
@@ -43,6 +43,15 @@ export interface StackingElement {
|
|
|
43
43
|
zIndex: number;
|
|
44
44
|
/** Audio clips have no visual stacking and are excluded from the computation. */
|
|
45
45
|
isAudio: boolean;
|
|
46
|
+
/** Source document. Leaf z-indexes are comparable only inside this file. */
|
|
47
|
+
sourceFile?: string;
|
|
48
|
+
/**
|
|
49
|
+
* CSS stacking context the clip's node lives in (TimelineElement.stackingContextId).
|
|
50
|
+
* Leaf z-indexes are only comparable WITHIN one context — across contexts the
|
|
51
|
+
* ancestors' z decides paint order — so the sync partitions by this key and
|
|
52
|
+
* never patches across contexts. Null/undefined ⇒ the root context.
|
|
53
|
+
*/
|
|
54
|
+
stackingContextId?: string | null;
|
|
46
55
|
/**
|
|
47
56
|
* Discovery / DOM document position (optional). Two clips with EQUAL z paint by
|
|
48
57
|
* DOM order — the one LATER in the DOM paints ON TOP. When supplied, "is A above
|
|
@@ -62,6 +71,22 @@ export interface StackingPatch {
|
|
|
62
71
|
|
|
63
72
|
const EPS = 1e-6;
|
|
64
73
|
|
|
74
|
+
/**
|
|
75
|
+
* Canonical paint-scope key: leaf z-indexes are comparable only within the same
|
|
76
|
+
* source document and CSS stacking context. The ONLY place this normalization
|
|
77
|
+
* lives — partitioning, membership checks, and pairwise equality all use it.
|
|
78
|
+
*/
|
|
79
|
+
const paintScopeKey = (el: { sourceFile?: string; stackingContextId?: string | null }): string =>
|
|
80
|
+
JSON.stringify([el.sourceFile ?? null, el.stackingContextId ?? null]);
|
|
81
|
+
|
|
82
|
+
/** Canonical paint-scope equality for stacking sync and its inverse mirror. */
|
|
83
|
+
export function samePaintScope(
|
|
84
|
+
a: { sourceFile?: string; stackingContextId?: string | null },
|
|
85
|
+
b: { sourceFile?: string; stackingContextId?: string | null },
|
|
86
|
+
): boolean {
|
|
87
|
+
return paintScopeKey(a) === paintScopeKey(b);
|
|
88
|
+
}
|
|
89
|
+
|
|
65
90
|
/**
|
|
66
91
|
* Two clips overlap in time when their half-open [start, end) intervals intersect.
|
|
67
92
|
*
|
|
@@ -71,7 +96,10 @@ const EPS = 1e-6;
|
|
|
71
96
|
* epsilon guards against float fuzz (e.g. 5.0000001 vs 5) spuriously overlapping two
|
|
72
97
|
* abutting clips and shuffling lanes. The two are intended to differ, not align.
|
|
73
98
|
*/
|
|
74
|
-
function overlapsInTime(
|
|
99
|
+
function overlapsInTime(
|
|
100
|
+
a: Pick<StackingElement, "start" | "duration">,
|
|
101
|
+
b: Pick<StackingElement, "start" | "duration">,
|
|
102
|
+
): boolean {
|
|
75
103
|
return a.start < b.start + b.duration - EPS && b.start < a.start + a.duration - EPS;
|
|
76
104
|
}
|
|
77
105
|
|
|
@@ -99,9 +127,13 @@ interface MutZ extends StackingElement {
|
|
|
99
127
|
* Does `a` currently paint ON TOP of `b`? Higher z wins; equal z breaks by DOM
|
|
100
128
|
* order (later in DOM paints on top). When either domIndex is absent, equal z is
|
|
101
129
|
* treated as "not strictly above" (ambiguous) — callers should supply domIndex to
|
|
102
|
-
* disambiguate (see StackingElement.domIndex).
|
|
130
|
+
* disambiguate (see StackingElement.domIndex). Exported (like laneIsAbove) as the
|
|
131
|
+
* ONE paint-order predicate so every consumer agrees on what "paints above" means.
|
|
103
132
|
*/
|
|
104
|
-
function paintsAbove(
|
|
133
|
+
function paintsAbove(
|
|
134
|
+
a: Pick<StackingElement, "zIndex" | "domIndex">,
|
|
135
|
+
b: Pick<StackingElement, "zIndex" | "domIndex">,
|
|
136
|
+
): boolean {
|
|
105
137
|
if (a.zIndex !== b.zIndex) return a.zIndex > b.zIndex;
|
|
106
138
|
if (a.domIndex != null && b.domIndex != null) return a.domIndex > b.domIndex;
|
|
107
139
|
return false;
|
|
@@ -284,7 +316,13 @@ export function computeStackingPatches(
|
|
|
284
316
|
// z=0 would enter the boundary math as a phantom neighbour at the z-floor. An
|
|
285
317
|
// unresolved clip is neither a neighbour nor resolvable as an edit, so it is
|
|
286
318
|
// excluded outright (item 13).
|
|
287
|
-
const
|
|
319
|
+
const allResolved = elements.filter((e) => Number.isFinite(e.zIndex));
|
|
320
|
+
|
|
321
|
+
// Leaf z is only meaningful within ONE source document and stacking context:
|
|
322
|
+
// across either boundary the ancestor composition/context decides paint order.
|
|
323
|
+
// Restrict the computation to the edited clips' own paint scope(s).
|
|
324
|
+
const editedScopes = new Set(allResolved.filter((e) => editedSet.has(e.key)).map(paintScopeKey));
|
|
325
|
+
const resolved = allResolved.filter((e) => editedScopes.has(paintScopeKey(e)));
|
|
288
326
|
|
|
289
327
|
// Mutable z snapshot so edits + cascaded bumps see each other's applied z.
|
|
290
328
|
const byKey = new Map<string, MutZ>(resolved.map((e) => [e.key, { ...e }]));
|
|
@@ -305,7 +343,9 @@ export function computeStackingPatches(
|
|
|
305
343
|
// LIFTED neighbour without overlapping the edited clip itself (#2198).
|
|
306
344
|
const all = [...byKey.values()];
|
|
307
345
|
const overlappersOf = (clip: MutZ): MutZ[] =>
|
|
308
|
-
all.filter(
|
|
346
|
+
all.filter(
|
|
347
|
+
(o) => o.key !== clip.key && !o.isAudio && samePaintScope(clip, o) && overlapsInTime(clip, o),
|
|
348
|
+
);
|
|
309
349
|
|
|
310
350
|
for (const clip of edited) {
|
|
311
351
|
resolveEditedZ(clip, overlappersOf(clip), overlappersOf, patchZ);
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import type { TimelineElement } from "../store/playerStore";
|
|
3
|
+
import type { ClipManifestClip } from "../lib/playbackTypes";
|
|
4
|
+
import { createTimelineElementFromManifestClip } from "../lib/timelineDOM";
|
|
5
|
+
import { buildExpandedElements } from "../hooks/useExpandedTimelineElements";
|
|
6
|
+
import { normalizeToZones } from "./timelineZones";
|
|
7
|
+
import { commitDraggedClipMove, type TimelineMoveEdit } from "./timelineClipDragCommit";
|
|
8
|
+
import type { DraggedClipState } from "./useTimelineClipDrag";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Pipeline test across the REAL manifest→element boundary: no hand-injected
|
|
12
|
+
* `authoredTrack` / `stackingContextId`. A runtime-manifest-shaped payload with
|
|
13
|
+
* SPARSE authored tracks flows through createTimelineElementFromManifestClip →
|
|
14
|
+
* (normalizeToZones | buildChildElements) → commitDraggedClipMove, and the
|
|
15
|
+
* persisted data-track-index must be the AUTHORED target, never the display lane.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const manifestClip = (over: Partial<ClipManifestClip>): ClipManifestClip => ({
|
|
19
|
+
id: "x",
|
|
20
|
+
label: "x",
|
|
21
|
+
start: 0,
|
|
22
|
+
duration: 2,
|
|
23
|
+
track: 0,
|
|
24
|
+
stackingContextId: "root",
|
|
25
|
+
kind: "element",
|
|
26
|
+
tagName: "div",
|
|
27
|
+
compositionId: null,
|
|
28
|
+
parentCompositionId: null,
|
|
29
|
+
compositionSrc: null,
|
|
30
|
+
assetUrl: null,
|
|
31
|
+
...over,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
function fromManifest(clips: ClipManifestClip[]): TimelineElement[] {
|
|
35
|
+
return clips.map((clip, index) =>
|
|
36
|
+
createTimelineElementFromManifestClip({ clip, fallbackIndex: index }),
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function drag(
|
|
41
|
+
element: TimelineElement,
|
|
42
|
+
opts: { previewStart: number; previewTrack: number },
|
|
43
|
+
): DraggedClipState {
|
|
44
|
+
return {
|
|
45
|
+
element,
|
|
46
|
+
originClientX: 0,
|
|
47
|
+
originClientY: 0,
|
|
48
|
+
originScrollLeft: 0,
|
|
49
|
+
originScrollTop: 0,
|
|
50
|
+
pointerClientX: 0,
|
|
51
|
+
pointerClientY: 0,
|
|
52
|
+
pointerOffsetX: 0,
|
|
53
|
+
pointerOffsetY: 0,
|
|
54
|
+
previewStart: opts.previewStart,
|
|
55
|
+
previewTrack: opts.previewTrack,
|
|
56
|
+
desiredTrack: opts.previewTrack,
|
|
57
|
+
insertRow: null,
|
|
58
|
+
snapTime: null,
|
|
59
|
+
snapType: null,
|
|
60
|
+
started: true,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Commit a lane-change drag and return the single persisted edit batch. */
|
|
65
|
+
function commitLaneChange(
|
|
66
|
+
element: TimelineElement,
|
|
67
|
+
previewTrack: number,
|
|
68
|
+
elements: TimelineElement[],
|
|
69
|
+
trackOrder: number[],
|
|
70
|
+
): TimelineMoveEdit[] {
|
|
71
|
+
const onMoveElements = vi.fn();
|
|
72
|
+
commitDraggedClipMove(drag(element, { previewStart: element.start, previewTrack }), {
|
|
73
|
+
elements,
|
|
74
|
+
trackOrder,
|
|
75
|
+
updateElement: vi.fn(),
|
|
76
|
+
onMoveElements,
|
|
77
|
+
});
|
|
78
|
+
expect(onMoveElements).toHaveBeenCalledTimes(1);
|
|
79
|
+
return onMoveElements.mock.calls[0][0] as TimelineMoveEdit[];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
describe("track persist pipeline (manifest → factory → lanes → drag commit)", () => {
|
|
83
|
+
// Sparse authored tracks 3 and 7 (mixed kinds), plus audio on 5, exactly as a
|
|
84
|
+
// runtime manifest would ship them (clip.track is the verbatim data-track-index).
|
|
85
|
+
const sparseManifest = [
|
|
86
|
+
manifestClip({ id: "v", kind: "video", tagName: "video", track: 3, start: 0 }),
|
|
87
|
+
manifestClip({ id: "g", kind: "element", tagName: "div", track: 7, start: 10 }),
|
|
88
|
+
manifestClip({ id: "m", kind: "audio", tagName: "audio", track: 5, start: 0 }),
|
|
89
|
+
];
|
|
90
|
+
|
|
91
|
+
it("factory records the authored track and stacking context from the manifest clip", () => {
|
|
92
|
+
const [v, g, m] = fromManifest(sparseManifest);
|
|
93
|
+
expect([v.authoredTrack, g.authoredTrack, m.authoredTrack]).toEqual([3, 7, 5]);
|
|
94
|
+
expect(v.stackingContextId).toBe("root");
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("a lane change on a sparse file persists the AUTHORED target track, not the display lane", () => {
|
|
98
|
+
// normalizeToZones packs visual tracks {3, 7} onto display lanes {0, 1} and
|
|
99
|
+
// the audio track 5 onto lane 2, preserving the factory-set authoredTrack.
|
|
100
|
+
const elements = normalizeToZones(fromManifest(sparseManifest));
|
|
101
|
+
const byId = new Map(elements.map((e) => [e.id, e]));
|
|
102
|
+
expect(byId.get("v")).toMatchObject({ track: 0, authoredTrack: 3 });
|
|
103
|
+
expect(byId.get("g")).toMatchObject({ track: 1, authoredTrack: 7 });
|
|
104
|
+
expect(byId.get("m")).toMatchObject({ track: 2, authoredTrack: 5 });
|
|
105
|
+
|
|
106
|
+
// Drag the video (lane 0) onto the div's lane (display 1, authored 7).
|
|
107
|
+
const down = commitLaneChange(byId.get("v")!, 1, elements, [0, 1, 2]);
|
|
108
|
+
expect(down).toHaveLength(1);
|
|
109
|
+
expect(down[0].updates.track).toBe(7); // authored, NOT display lane 1
|
|
110
|
+
expect(down[0].updates.track).not.toBe(1);
|
|
111
|
+
|
|
112
|
+
// And the reverse: the div (lane 1) onto the video's lane (display 0, authored 3).
|
|
113
|
+
const up = commitLaneChange(byId.get("g")!, 0, elements, [0, 1, 2]);
|
|
114
|
+
expect(up[0].updates.track).toBe(3); // authored, NOT display lane 0
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("an expanded sub-comp child's lane change persists the sibling's authored track from ITS file", () => {
|
|
118
|
+
// Host timeline: the sub-comp host plus a root clip, discovered through the
|
|
119
|
+
// factory and lane-normalized like the store does.
|
|
120
|
+
const hostManifest = [
|
|
121
|
+
manifestClip({
|
|
122
|
+
id: "scene",
|
|
123
|
+
kind: "composition",
|
|
124
|
+
tagName: "div",
|
|
125
|
+
track: 0,
|
|
126
|
+
start: 0,
|
|
127
|
+
duration: 10,
|
|
128
|
+
compositionId: "scene",
|
|
129
|
+
compositionSrc: "scene.html",
|
|
130
|
+
}),
|
|
131
|
+
manifestClip({ id: "root-clip", kind: "video", tagName: "video", track: 1, start: 0 }),
|
|
132
|
+
];
|
|
133
|
+
// scene.html has SPARSE authored tracks 3 and 7.
|
|
134
|
+
const childClips = [
|
|
135
|
+
manifestClip({ id: "c3", track: 3, start: 1, duration: 2, parentCompositionId: "scene" }),
|
|
136
|
+
manifestClip({ id: "c7", track: 7, start: 4, duration: 2, parentCompositionId: "scene" }),
|
|
137
|
+
];
|
|
138
|
+
const storeElements = normalizeToZones(fromManifest(hostManifest));
|
|
139
|
+
const parentMap = new Map([
|
|
140
|
+
["c3", "scene"],
|
|
141
|
+
["c7", "scene"],
|
|
142
|
+
]);
|
|
143
|
+
const expanded = buildExpandedElements(
|
|
144
|
+
storeElements,
|
|
145
|
+
[...hostManifest, ...childClips],
|
|
146
|
+
parentMap,
|
|
147
|
+
"scene",
|
|
148
|
+
"scene",
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
// The children replaced the host row: synthetic display lanes, but the
|
|
152
|
+
// authored track (in scene.html's coordinate space) survived the expansion.
|
|
153
|
+
const c3 = expanded.find((e) => e.domId === "c3")!;
|
|
154
|
+
const c7 = expanded.find((e) => e.domId === "c7")!;
|
|
155
|
+
expect(c3).toMatchObject({ authoredTrack: 3, sourceFile: "scene.html" });
|
|
156
|
+
expect(c7).toMatchObject({ authoredTrack: 7, sourceFile: "scene.html" });
|
|
157
|
+
expect(c3.stackingContextId).toBe("root");
|
|
158
|
+
expect(c3.track).not.toBe(3); // display row is synthetic
|
|
159
|
+
|
|
160
|
+
// Drag c3 onto c7's display lane: the persist target is c3's OWN file, so
|
|
161
|
+
// the written track must be c7's authored 7 — not the display-lane integer.
|
|
162
|
+
const trackOrder = [...new Set(expanded.map((e) => e.track))].sort((a, b) => a - b);
|
|
163
|
+
const edits = commitLaneChange(c3, c7.track, expanded, trackOrder);
|
|
164
|
+
expect(edits).toHaveLength(1);
|
|
165
|
+
expect(edits[0].updates.track).toBe(7);
|
|
166
|
+
expect(edits[0].updates.track).not.toBe(c7.track);
|
|
167
|
+
});
|
|
168
|
+
});
|