@hyperframes/studio 0.8.90 → 0.8.92
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-DDjER_ii.js → hyperframes-player-DKJPDHUp.js} +1 -1
- package/dist/assets/{index-BKE3_N4v.js → index-C5_j5-3u.js} +178 -178
- package/dist/assets/{index-5SFeUXP8.js → index-CHZ4kFWm.js} +1 -1
- package/dist/assets/{index-CVsCxnlz.js → index-DpzpqJ_j.js} +1 -1
- package/dist/{chunk-KU4F7QZ3.js → chunk-QPSOTK6M.js} +8 -1
- package/dist/chunk-QPSOTK6M.js.map +1 -0
- package/dist/index.d.ts +18 -14
- package/dist/index.html +1 -1
- package/dist/index.js +9190 -8916
- package/dist/index.js.map +1 -1
- package/dist/player.js +1 -1
- package/package.json +8 -8
- package/src/components/TimelineToolbar.test.tsx +12 -0
- package/src/components/editor/MotionPathOverlay.tsx +5 -1
- package/src/components/editor/commitPreflightHandles.test.tsx +7 -20
- package/src/components/editor/elementPositionOffset.test.ts +62 -0
- package/src/components/editor/elementPositionOffset.ts +64 -0
- package/src/components/editor/motionPathCommit.test.ts +25 -5
- package/src/components/editor/motionPathSelection.ts +10 -3
- package/src/components/editor/useLayerRevealOverride.test.ts +8 -0
- package/src/components/editor/useLayerRevealOverride.ts +2 -1
- package/src/components/nle/NLEContext.tsx +1 -1
- package/src/components/nle/useTimelineEditCallbacks.test.tsx +53 -0
- package/src/hooks/domEditPersistFailure.ts +8 -6
- package/src/hooks/elementOffsetStager.ts +65 -0
- package/src/hooks/gestureTransaction.ts +4 -2
- package/src/hooks/gsapDragCommit.ts +1 -0
- package/src/hooks/gsapEditOutcome.ts +2 -1
- package/src/hooks/gsapRuntimeBridge.sharedTween.test.ts +157 -0
- package/src/hooks/gsapRuntimeBridge.ts +50 -14
- package/src/hooks/gsapShared.test.ts +1 -1
- package/src/hooks/gsapShared.ts +16 -12
- package/src/hooks/gsapTweenReach.ts +30 -0
- package/src/hooks/timelineEditingHelpers.ts +2 -1
- package/src/hooks/useClipboard.source.test.ts +58 -0
- package/src/hooks/useClipboard.ts +12 -3
- package/src/hooks/useCommitPreflightCapabilities.ts +2 -2
- package/src/hooks/useDomEditCommits.ts +2 -0
- package/src/hooks/useDomEditPositionPatchCommit.ts +4 -1
- package/src/hooks/useDomEditSession.ts +2 -0
- package/src/hooks/useDomGeometryCommit.test.tsx +102 -0
- package/src/hooks/useDomGeometryCommit.ts +42 -1
- package/src/hooks/useDomGeometryCommits.test.tsx +27 -0
- package/src/hooks/useDomGeometryCommits.ts +14 -1
- package/src/hooks/useGsapAwareEditing.test.tsx +113 -5
- package/src/hooks/useGsapAwareEditing.ts +44 -7
- package/src/hooks/useGsapInteractionFailureTelemetry.test.tsx +2 -1
- package/src/hooks/useGsapInteractionFailureTelemetry.ts +8 -4
- package/src/hooks/useRazorSplit.test.ts +5 -5
- package/src/hooks/useTimelineAssetDropOps.test.tsx +138 -0
- package/src/hooks/useTimelineAssetDropOps.ts +40 -18
- package/src/hooks/useTimelineEditing.test.tsx +384 -0
- package/src/hooks/useTimelineEditing.ts +12 -12
- package/src/hooks/useTimelineGroupEditing.ts +16 -7
- package/src/player/components/Timeline.rulerSeek.test.tsx +111 -0
- package/src/player/components/Timeline.showKeyframes.test.tsx +28 -2
- package/src/player/components/TimelinePropertyLanes.test.tsx +1 -1
- package/src/player/components/TimelineProvider.tsx +1 -0
- package/src/player/components/TimelineTrackHeader.test.tsx +1 -1
- package/src/player/components/TimelineTypes.ts +1 -1
- package/src/player/components/timelineClipDragCommit.test.ts +32 -66
- package/src/player/components/timelineClipDragCommit.ts +19 -37
- package/src/player/components/timelineClipDragPreview.test.ts +283 -24
- package/src/player/components/timelineClipDragPreview.ts +51 -83
- package/src/player/components/timelineClipDragTypes.ts +3 -8
- package/src/player/components/timelineCollision.test.ts +100 -302
- package/src/player/components/timelineCollision.ts +73 -160
- package/src/player/components/timelineDragDrop.test.tsx +19 -5
- package/src/player/components/timelineDragDrop.ts +7 -9
- package/src/player/components/timelineDragLanding.ts +0 -49
- package/src/player/components/timelineEditing.ts +2 -1
- package/src/player/components/timelineGroupEditing.ts +7 -2
- package/src/player/components/timelineLayout.test.ts +0 -25
- package/src/player/components/timelineLayout.ts +0 -16
- package/src/player/components/timelineMultiDragPreview.test.ts +34 -23
- package/src/player/components/timelineMultiDragPreview.ts +26 -25
- package/src/player/components/timelineProviderStateBuilders.test.ts +1 -0
- package/src/player/components/timelineProviderStateBuilders.ts +14 -6
- package/src/player/components/timelineShellMeta.tsx +99 -0
- package/src/player/components/timelineZMirror.test.ts +2 -2
- package/src/player/components/timelineZMirror.ts +1 -1
- package/src/player/components/useTimelineClipDrag.resize.test.tsx +8 -5
- package/src/player/components/useTimelineClipDrag.ts +3 -2
- package/src/player/components/useTimelineDragPreviewState.ts +34 -0
- package/src/player/components/useTimelinePlayhead.test.tsx +215 -26
- package/src/player/components/useTimelinePlayhead.ts +56 -27
- package/src/player/components/useTimelineProviderState.tsx +29 -66
- package/src/player/components/useTimelineZoom.ts +9 -1
- package/src/player/hooks/useTimelinePlayer.seek.test.ts +44 -0
- package/src/player/hooks/useTimelinePlayer.ts +7 -4
- package/src/player/lib/timelineDOM.test.ts +79 -0
- package/src/player/lib/timelineDOM.ts +16 -7
- package/src/player/lib/timelineIframeHelpers.test.ts +36 -0
- package/src/player/lib/timelineIframeHelpers.ts +12 -3
- package/src/player/store/liveTime.ts +6 -0
- package/src/player/store/playerStore.ts +2 -0
- package/src/player/store/timelineElement.ts +28 -3
- package/src/utils/razorSplitTransaction.test.ts +1 -1
- package/src/utils/razorSplitTransaction.ts +3 -3
- package/src/utils/timelineDropTrackInsert.test.ts +60 -1
- package/src/utils/timelineDropTrackInsert.ts +36 -5
- package/dist/chunk-KU4F7QZ3.js.map +0 -1
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
import type { TimelineElement } from "../store/playerStore";
|
|
14
14
|
import type { IframeWindow } from "./playbackTypes";
|
|
15
15
|
import { readClipTiming } from "@hyperframes/core/composition-contract";
|
|
16
|
+
import { createRuntimeStartTimeResolver } from "@hyperframes/core/runtime/start-resolver";
|
|
16
17
|
import {
|
|
17
18
|
getTimelineElementSelector,
|
|
18
19
|
getTimelineElementSourceFile,
|
|
@@ -414,15 +415,16 @@ function buildMissingCompositionEntry(params: {
|
|
|
414
415
|
rootDuration: number;
|
|
415
416
|
fallbackIndex: number;
|
|
416
417
|
resolveEnd: (refId: string, visiting: ReadonlySet<string>) => number | null;
|
|
418
|
+
masterTime: ReturnType<typeof createRuntimeStartTimeResolver>;
|
|
417
419
|
}): TimelineElement | null {
|
|
418
|
-
const { doc, iframeWin, element, compositionId, rootDuration, fallbackIndex
|
|
419
|
-
|
|
420
|
+
const { doc, iframeWin, element, compositionId, rootDuration, fallbackIndex } = params;
|
|
421
|
+
const { resolveEnd, masterTime } = params;
|
|
420
422
|
const transitionLabels = transitionLabelsForDocument(doc, iframeWin.__timelines);
|
|
421
423
|
const timing = readClipTiming(element, {
|
|
422
424
|
resolveReferenceEnd: (refId) => resolveEnd(refId, new Set([compositionId])),
|
|
423
425
|
});
|
|
424
426
|
const window = clampCompositionWindow(
|
|
425
|
-
|
|
427
|
+
masterTime.resolveStartForElement(element),
|
|
426
428
|
timing.duration ?? timelineDuration(iframeWin, compositionId),
|
|
427
429
|
rootDuration,
|
|
428
430
|
);
|
|
@@ -456,6 +458,7 @@ function buildMissingCompositionEntry(params: {
|
|
|
456
458
|
key: identity.key,
|
|
457
459
|
tag: element.tagName.toLowerCase(),
|
|
458
460
|
start: window.start,
|
|
461
|
+
parentCompositionStart: masterTime.resolveHostStartForElement(element),
|
|
459
462
|
duration: window.duration,
|
|
460
463
|
track: timing.trackIndex,
|
|
461
464
|
authoredTrack: timing.trackIndex,
|
|
@@ -494,6 +497,11 @@ export function buildMissingCompositionElements(
|
|
|
494
497
|
const missing: TimelineElement[] = [];
|
|
495
498
|
|
|
496
499
|
const resolveEnd = createReferenceEndResolver(createTimedElementLookup(doc), iframeWin);
|
|
500
|
+
const masterTime = createRuntimeStartTimeResolver({
|
|
501
|
+
timelineRegistry: iframeWin.__timelines,
|
|
502
|
+
includeAuthoredTimingAttrs: true,
|
|
503
|
+
documentRef: doc,
|
|
504
|
+
});
|
|
497
505
|
|
|
498
506
|
for (const host of hosts) {
|
|
499
507
|
const el = host as HTMLElement;
|
|
@@ -508,6 +516,7 @@ export function buildMissingCompositionElements(
|
|
|
508
516
|
rootDuration,
|
|
509
517
|
fallbackIndex: missing.length,
|
|
510
518
|
resolveEnd,
|
|
519
|
+
masterTime,
|
|
511
520
|
});
|
|
512
521
|
if (entry) missing.push(entry);
|
|
513
522
|
}
|
|
@@ -2,12 +2,18 @@
|
|
|
2
2
|
type TimeListener = (time: number) => void;
|
|
3
3
|
const timeListeners = new Set<TimeListener>();
|
|
4
4
|
let latestTime = 0;
|
|
5
|
+
let seekCount = 0;
|
|
5
6
|
|
|
6
7
|
export const liveTime = {
|
|
7
8
|
notify: (time: number) => {
|
|
8
9
|
latestTime = time;
|
|
9
10
|
timeListeners.forEach((listener) => listener(time));
|
|
10
11
|
},
|
|
12
|
+
notifySeek: (time: number) => {
|
|
13
|
+
seekCount += 1;
|
|
14
|
+
liveTime.notify(time);
|
|
15
|
+
},
|
|
16
|
+
seekCount: () => seekCount,
|
|
11
17
|
latest: () => latestTime,
|
|
12
18
|
subscribe: (listener: TimeListener) => {
|
|
13
19
|
timeListeners.add(listener);
|
|
@@ -83,6 +83,7 @@ interface PlayerState extends PlayerStoreSlices {
|
|
|
83
83
|
zoomMode: ZoomMode;
|
|
84
84
|
/** Timeline zoom percent relative to the fit width when in manual mode */
|
|
85
85
|
manualZoomPercent: number;
|
|
86
|
+
userZoomCount: number;
|
|
86
87
|
/**
|
|
87
88
|
* Bumped on every live z-index edit (handleDomZIndexReorderCommit apply AND
|
|
88
89
|
* rollback). Flashless z commits (skipReload) never reload the iframe or
|
|
@@ -279,6 +280,7 @@ export const usePlayerStore = create<PlayerState>((set, get) => ({
|
|
|
279
280
|
loopEnabled: false,
|
|
280
281
|
zoomMode: "fit",
|
|
281
282
|
manualZoomPercent: 100,
|
|
283
|
+
userZoomCount: 0,
|
|
282
284
|
zEditVersion: 0,
|
|
283
285
|
timelinePps: 100,
|
|
284
286
|
timelineFitPps: 100,
|
|
@@ -84,13 +84,38 @@ export interface TimelineElement {
|
|
|
84
84
|
audioGroupFxChain?: string;
|
|
85
85
|
audioGroupAutomation?: string;
|
|
86
86
|
/**
|
|
87
|
-
*
|
|
88
|
-
*
|
|
87
|
+
* Master start of the composition this row runs in, which its tweens and its
|
|
88
|
+
* `data-start` are local to; 0 at the root. Writes go through toAuthoredStart.
|
|
89
89
|
*/
|
|
90
|
-
|
|
90
|
+
parentCompositionStart?: number;
|
|
91
|
+
/** A legacy root-global media start: its `data-start` is already master time. */
|
|
92
|
+
authoredStartIsMasterTime?: boolean;
|
|
93
|
+
/** Legacy marker for an inline sub-composition child; current rows never set it. */
|
|
91
94
|
expandedHostKey?: string;
|
|
92
95
|
}
|
|
93
96
|
|
|
97
|
+
type RowClock = Pick<
|
|
98
|
+
TimelineElement,
|
|
99
|
+
"start" | "parentCompositionStart" | "authoredStartIsMasterTime"
|
|
100
|
+
>;
|
|
101
|
+
const authoredOffset = (element: RowClock) =>
|
|
102
|
+
element.authoredStartIsMasterTime ? 0 : (element.parentCompositionStart ?? 0);
|
|
103
|
+
|
|
104
|
+
/** The earliest master start this row can take: its host's start, or its own if already earlier. */
|
|
105
|
+
export function clampToHostStart(element: RowClock, masterTime: number): number {
|
|
106
|
+
return Math.max(Math.min(authoredOffset(element), element.start), masterTime);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** A master-time position on this row, as the `data-start` its source file stores. */
|
|
110
|
+
export function toAuthoredStart(element: RowClock, masterTime: number): number {
|
|
111
|
+
return Math.max(0, clampToHostStart(element, masterTime) - authoredOffset(element));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** A master-time position on the clock this row's tweens run on. */
|
|
115
|
+
export function toCompositionTime(element: RowClock, masterTime: number): number {
|
|
116
|
+
return masterTime - (element.parentCompositionStart ?? 0);
|
|
117
|
+
}
|
|
118
|
+
|
|
94
119
|
/**
|
|
95
120
|
* The fields `updateElement` may write.
|
|
96
121
|
*
|
|
@@ -48,7 +48,7 @@ describe("buildAtomicCutIntents", () => {
|
|
|
48
48
|
|
|
49
49
|
it("rebases each nested target into its own source-file coordinates", () => {
|
|
50
50
|
const intents = buildAtomicCutIntents(
|
|
51
|
-
[element({ start: 8, duration: 4,
|
|
51
|
+
[element({ start: 8, duration: 4, parentCompositionStart: 6, sourceFile: "scene.html" })],
|
|
52
52
|
10,
|
|
53
53
|
"index.html",
|
|
54
54
|
);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { TimelineElement } from "../player";
|
|
2
|
+
import { toAuthoredStart } from "../player/store/timelineElement";
|
|
2
3
|
import type { RecordEditInput } from "../hooks/timelineEditingHelpers";
|
|
3
4
|
import { buildPatchTarget } from "./timelineElementSplit";
|
|
4
5
|
import { serializeStudioFileMutations } from "./studioFileMutationCoordinator";
|
|
@@ -61,12 +62,11 @@ function buildCutTarget(
|
|
|
61
62
|
target: CutTarget["target"],
|
|
62
63
|
splitTime: number,
|
|
63
64
|
): CutTarget {
|
|
64
|
-
const basis = element.expandedParentStart;
|
|
65
65
|
return {
|
|
66
66
|
target,
|
|
67
67
|
...(element.domId ? { originalId: element.domId } : {}),
|
|
68
|
-
splitTime:
|
|
69
|
-
elementStart:
|
|
68
|
+
splitTime: Math.max(0, toAuthoredStart(element, splitTime)),
|
|
69
|
+
elementStart: toAuthoredStart(element, element.start),
|
|
70
70
|
elementDuration: element.duration,
|
|
71
71
|
...(element.playbackStart != null ? { playbackStart: element.playbackStart } : {}),
|
|
72
72
|
...(element.playbackRate != null ? { playbackRate: element.playbackRate } : {}),
|
|
@@ -99,6 +99,54 @@ describe("applyTrackRenumbers", () => {
|
|
|
99
99
|
});
|
|
100
100
|
|
|
101
101
|
describe("resolveDropTrack", () => {
|
|
102
|
+
// Title shows on row 0; Music is written on track 0 too but shows on audio row 1.
|
|
103
|
+
const shown = (
|
|
104
|
+
id: string,
|
|
105
|
+
tag: string,
|
|
106
|
+
row: number,
|
|
107
|
+
track: number,
|
|
108
|
+
start: number,
|
|
109
|
+
length: number,
|
|
110
|
+
) => ({
|
|
111
|
+
...clip(id, row, start),
|
|
112
|
+
tag,
|
|
113
|
+
duration: length,
|
|
114
|
+
authoredTrack: track,
|
|
115
|
+
});
|
|
116
|
+
const title = shown("title", "div", 0, 0, 0, 10);
|
|
117
|
+
const music = shown("music", "audio", 1, 0, 2, 3);
|
|
118
|
+
const song = { id: "song", tag: "audio", start: 2, duration: 3 };
|
|
119
|
+
const drop = (elements: TimelineElement[], row: number, dropped = song) =>
|
|
120
|
+
resolveDropTrack({ source, elements, placement: { track: row }, dropped });
|
|
121
|
+
|
|
122
|
+
it("joins an audio row's file track at the nearest free time, not a new track", () => {
|
|
123
|
+
expect(drop([title, music], 1)).toMatchObject({ track: 0, start: 5 });
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("keeps an audio file dropped on a visual row off the audio clip on the track it is written to", () => {
|
|
127
|
+
expect(drop([title, music], 0)).toMatchObject({ track: 0, start: 5 });
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("writes a drop on a shown row to that row's file track and checks the clips there", () => {
|
|
131
|
+
const top = shown("top", "div", 0, 0, 0, 4);
|
|
132
|
+
const lower = shown("lower", "div", 1, 2, 0, 4); // file track 2, shown as row 1
|
|
133
|
+
const image = { id: "image", tag: "img", start: 1, duration: 3 };
|
|
134
|
+
expect(drop([top, lower], 1, image)).toMatchObject({ track: 2, start: 4 });
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("lets only clips of the dropped file's kind block it on the row", () => {
|
|
138
|
+
const title = clip("title", 0, 0);
|
|
139
|
+
const music = { ...clip("music", 0, 0), tag: "audio" };
|
|
140
|
+
const song = { id: "song", tag: "audio", start: 1, duration: 3 };
|
|
141
|
+
// An audio file shows in the audio rows wherever it is written, so a visual clip never blocks it.
|
|
142
|
+
expect(
|
|
143
|
+
resolveDropTrack({ source, elements: [title], placement: { track: 0 }, dropped: song }).start,
|
|
144
|
+
).toBe(1);
|
|
145
|
+
expect(
|
|
146
|
+
resolveDropTrack({ source, elements: [music], placement: { track: 0 }, dropped: song }).start,
|
|
147
|
+
).toBe(2);
|
|
148
|
+
});
|
|
149
|
+
|
|
102
150
|
it("keeps the aimed lane and the source when no insert is asked for", () => {
|
|
103
151
|
const out = resolveDropTrack({
|
|
104
152
|
source,
|
|
@@ -106,7 +154,17 @@ describe("resolveDropTrack", () => {
|
|
|
106
154
|
placement: { track: 2 },
|
|
107
155
|
dropped,
|
|
108
156
|
});
|
|
109
|
-
expect(out).toEqual({ source, track: 2 });
|
|
157
|
+
expect(out).toEqual({ source, track: 2, start: 5 });
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("moves a drop on an occupied row to that row's nearest free time, with no new track", () => {
|
|
161
|
+
const out = resolveDropTrack({
|
|
162
|
+
source,
|
|
163
|
+
elements,
|
|
164
|
+
placement: { track: 1 },
|
|
165
|
+
dropped: { ...dropped, start: 1 },
|
|
166
|
+
});
|
|
167
|
+
expect(out).toEqual({ source, track: 1, start: 2 });
|
|
110
168
|
});
|
|
111
169
|
|
|
112
170
|
it("returns the new lane and the source with the lanes below pushed down", () => {
|
|
@@ -117,6 +175,7 @@ describe("resolveDropTrack", () => {
|
|
|
117
175
|
dropped,
|
|
118
176
|
});
|
|
119
177
|
expect(out.track).toBe(1);
|
|
178
|
+
expect(out.start).toBe(5);
|
|
120
179
|
expect(out.source).toContain('id="b" data-start="0" data-track-index="2"');
|
|
121
180
|
});
|
|
122
181
|
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import type { TimelineElement } from "../player";
|
|
2
2
|
import { layoutAfterTrackInsert } from "../player/components/timelineDragLanding";
|
|
3
3
|
import { canMoveTimelineElement } from "../player/components/timelineAuthoredMoveTarget";
|
|
4
|
+
import { resolveNearestFreeStart } from "../player/components/timelineCollision";
|
|
5
|
+
import { authoredTrackForLane } from "../player/components/timelineAuthoredTrack";
|
|
6
|
+
import { isAudioTimelineElement } from "./timelineInspector";
|
|
4
7
|
import type { TimelineDropPlacement } from "../player/components/timelineCallbacks";
|
|
5
8
|
import { applyPatchByTarget, readAttributeByTarget } from "./sourcePatcher";
|
|
6
9
|
import { buildPatchTarget } from "../hooks/timelineEditingHelpers";
|
|
@@ -67,17 +70,45 @@ export function applyTrackRenumbers(source: string, plan: DropTrackInsertPlan):
|
|
|
67
70
|
return out;
|
|
68
71
|
}
|
|
69
72
|
|
|
70
|
-
|
|
73
|
+
type DroppedClip = Pick<TimelineElement, "id" | "tag" | "start" | "duration">;
|
|
74
|
+
|
|
75
|
+
/** The file track a drop on display row `lane` is written to, and its nearest free start there.
|
|
76
|
+
* The timeline's own clips decide the track; `placed` clips only block, like same-kind clips there. */
|
|
77
|
+
function resolveRowDrop(
|
|
78
|
+
elements: TimelineElement[],
|
|
79
|
+
placed: readonly TimelineElement[],
|
|
80
|
+
lane: number,
|
|
81
|
+
dropped: DroppedClip,
|
|
82
|
+
): { track: number; start: number } {
|
|
83
|
+
const audio = isAudioTimelineElement(dropped);
|
|
84
|
+
const row = elements.filter((e) => e.track === lane);
|
|
85
|
+
const otherKindRow = row.length > 0 && !row.some((e) => isAudioTimelineElement(e) === audio);
|
|
86
|
+
const asClip = { ...dropped, key: dropped.id, track: lane, sourceFile: elements[0]?.sourceFile };
|
|
87
|
+
const track = otherKindRow ? lane : authoredTrackForLane(lane, elements, asClip);
|
|
88
|
+
const onTrack = [...elements, ...placed]
|
|
89
|
+
.filter((e) => isAudioTimelineElement(e) === audio && (e.authoredTrack ?? e.track) === track)
|
|
90
|
+
.map((e) => ({ ...e, track }));
|
|
91
|
+
return {
|
|
92
|
+
track,
|
|
93
|
+
start: resolveNearestFreeStart(onTrack, track, dropped.start, dropped.duration, null),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Where a dropped clip is written: the aimed row's file track at its nearest free time, or a new track. */
|
|
71
98
|
export function resolveDropTrack(input: {
|
|
72
99
|
source: string;
|
|
73
100
|
elements: TimelineElement[];
|
|
101
|
+
/** Clips this drop gesture already wrote, which `elements` does not hold yet. */
|
|
102
|
+
placed?: readonly TimelineElement[];
|
|
74
103
|
placement: TimelineDropPlacement;
|
|
75
|
-
dropped:
|
|
76
|
-
}): { source: string; track: number } {
|
|
104
|
+
dropped: DroppedClip;
|
|
105
|
+
}): { source: string; track: number; start: number } {
|
|
77
106
|
const { source, elements, placement, dropped } = input;
|
|
78
|
-
if (placement.insertRow == null)
|
|
107
|
+
if (placement.insertRow == null) {
|
|
108
|
+
return { source, ...resolveRowDrop(elements, input.placed ?? [], placement.track, dropped) };
|
|
109
|
+
}
|
|
79
110
|
const { insertRow, trackOrder } = placement;
|
|
80
111
|
const plan = planDropTrackInsert({ elements, trackOrder, insertRow, dropped });
|
|
81
112
|
if (!plan) throw new Error("Cannot open a new track here: a locked clip would have to move.");
|
|
82
|
-
return { source: applyTrackRenumbers(source, plan), track: plan.track };
|
|
113
|
+
return { source: applyTrackRenumbers(source, plan), track: plan.track, start: dropped.start };
|
|
83
114
|
}
|