@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
|
@@ -3,35 +3,27 @@ import { useCallback, useRef } from "react";
|
|
|
3
3
|
import type { TimelineElement } from "../player";
|
|
4
4
|
import { usePlayerStore } from "../player";
|
|
5
5
|
import { useRazorSplit } from "./useRazorSplit";
|
|
6
|
-
import {
|
|
7
|
-
buildTimelineAssetId,
|
|
8
|
-
buildTimelineAssetInsertHtml,
|
|
9
|
-
buildTimelineFileDropPlacements,
|
|
10
|
-
getTimelineAssetKind,
|
|
11
|
-
insertTimelineAssetIntoSource,
|
|
12
|
-
resolveTimelineAssetInitialGeometry,
|
|
13
|
-
resolveTimelineAssetSrc,
|
|
14
|
-
} from "../utils/timelineAssetDrop";
|
|
6
|
+
import { useTimelineAssetDropOps } from "./useTimelineAssetDropOps";
|
|
15
7
|
import { saveProjectFilesWithHistory } from "../utils/studioFileHistory";
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
resolveDroppedAssetDuration,
|
|
20
|
-
} from "../utils/studioHelpers";
|
|
8
|
+
import { setCompositionDurationToContent } from "../utils/timelineAssetDrop";
|
|
9
|
+
import { furthestClipEndFromSource } from "../player/lib/timelineElementHelpers";
|
|
10
|
+
import { getTimelineElementLabel } from "../utils/studioHelpers";
|
|
21
11
|
import {
|
|
22
12
|
applyTimelineStackingReorder,
|
|
23
13
|
buildPatchTarget,
|
|
24
14
|
patchIframeDomTiming,
|
|
25
15
|
persistTimelineEdit,
|
|
26
|
-
readFileContent,
|
|
27
|
-
foldedShiftGsapMutation,
|
|
28
|
-
foldedScaleGsapMutation,
|
|
29
16
|
formatTimelineAttributeNumber,
|
|
30
|
-
finishTimelineTimingFallback,
|
|
31
17
|
extendRootDurationIfNeeded,
|
|
32
18
|
buildTimelineMoveTimingPatch,
|
|
33
19
|
buildTimelineResizeTimingPatch,
|
|
34
20
|
} from "./timelineEditingHelpers";
|
|
21
|
+
import {
|
|
22
|
+
captureDurationRollback,
|
|
23
|
+
finishClipTimingFallback,
|
|
24
|
+
readFileContent,
|
|
25
|
+
syncPreviewContentDuration,
|
|
26
|
+
} from "./timelineTimingSync";
|
|
35
27
|
import type { PersistTimelineEditInput } from "./timelineEditingHelpers";
|
|
36
28
|
import type { TimelineStackingReorderIntent } from "../player/components/timelineEditing";
|
|
37
29
|
import {
|
|
@@ -39,6 +31,7 @@ import {
|
|
|
39
31
|
useTimelineTrackVisibilityEditing,
|
|
40
32
|
} from "./timelineTrackVisibility";
|
|
41
33
|
import { useTimelineGroupEditing } from "./useTimelineGroupEditing";
|
|
34
|
+
import { serializeZLaneGesture } from "../components/nle/zLaneGesture";
|
|
42
35
|
import { sdkTimingPersist } from "../utils/sdkCutover";
|
|
43
36
|
import type { UseTimelineEditingOptions } from "./useTimelineEditingTypes";
|
|
44
37
|
|
|
@@ -135,80 +128,108 @@ export function useTimelineEditing({
|
|
|
135
128
|
const handleTimelineElementMove = useCallback(
|
|
136
129
|
// fallow-ignore-next-line complexity
|
|
137
130
|
(element: TimelineElement, updates: TimelineMoveUpdates) => {
|
|
138
|
-
const
|
|
139
|
-
|
|
131
|
+
const commitMove = () => {
|
|
132
|
+
const targetPath = element.sourceFile || activeCompPath || "index.html";
|
|
133
|
+
const startChanged = updates.start !== element.start;
|
|
134
|
+
// A vertical-only lane move arrives with start unchanged but track changed
|
|
135
|
+
// (on this single-element path the drag commit has already folded the
|
|
136
|
+
// AUTHORED persist track into updates.track). It must persist like any
|
|
137
|
+
// other move — early-returning on !startChanged alone silently dropped
|
|
138
|
+
// the file write, so the lane snapped back on reload.
|
|
139
|
+
const trackChanged = updates.track !== element.track;
|
|
140
140
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
141
|
+
if (startChanged || trackChanged) {
|
|
142
|
+
const liveAttrs: Array<[string, string]> = [];
|
|
143
|
+
if (startChanged) {
|
|
144
|
+
liveAttrs.push(["data-start", formatTimelineAttributeNumber(updates.start)]);
|
|
145
|
+
}
|
|
146
|
+
if (trackChanged) {
|
|
147
|
+
liveAttrs.push(["data-track-index", formatTimelineAttributeNumber(updates.track)]);
|
|
148
|
+
}
|
|
149
|
+
patchIframeDomTiming(previewIframeRef.current, element, liveAttrs, activeCompPath);
|
|
150
|
+
}
|
|
146
151
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
152
|
+
const reorderDone = applyTimelineStackingReorder({
|
|
153
|
+
element,
|
|
154
|
+
stackingReorder: updates.stackingReorder,
|
|
155
|
+
timelineElements,
|
|
156
|
+
iframe: previewIframeRef.current,
|
|
157
|
+
activeCompPath,
|
|
158
|
+
commit: handleDomZIndexReorderCommitRef?.current,
|
|
159
|
+
});
|
|
155
160
|
|
|
156
|
-
|
|
161
|
+
if (!startChanged && !trackChanged) return reorderDone;
|
|
157
162
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
onGsapError: (err) => console.error("[Timeline] Failed to shift GSAP positions", err),
|
|
185
|
-
});
|
|
186
|
-
});
|
|
187
|
-
const needsExtension = extendRootDurationIfNeeded(updates.start + element.duration);
|
|
188
|
-
return reorderDone.then(() => {
|
|
189
|
-
if (sdkSession && element.hfId && !needsExtension) {
|
|
190
|
-
return sdkTimingPersist(
|
|
191
|
-
element.hfId,
|
|
192
|
-
targetPath,
|
|
193
|
-
{ start: updates.start },
|
|
194
|
-
sdkSession,
|
|
195
|
-
{
|
|
196
|
-
editHistory: { recordEdit },
|
|
197
|
-
writeProjectFile,
|
|
163
|
+
// Snapshot the duration BEFORE the optimistic updates below so a failed
|
|
164
|
+
// persist can roll the readout + live root back (see captureDurationRollback).
|
|
165
|
+
const rollbackDuration = captureDurationRollback(previewIframeRef.current);
|
|
166
|
+
// needsExtension gates the SDK path (setTiming can't grow the root duration), so read the store BEFORE the readout sync below optimistically updates it.
|
|
167
|
+
const needsExtension = extendRootDurationIfNeeded(updates.start + element.duration);
|
|
168
|
+
// Optimistic duration readout: content-driven (grow AND shrink), from the just-patched live DOM. See syncPreviewContentDuration.
|
|
169
|
+
syncPreviewContentDuration(previewIframeRef.current);
|
|
170
|
+
|
|
171
|
+
const buildMovePatches: PersistTimelineEditInput["buildPatches"] = (original, target) => {
|
|
172
|
+
// Persist lane changes too — data-start-only writes let reload snap the lane back.
|
|
173
|
+
const track = trackChanged ? updates.track : undefined;
|
|
174
|
+
return buildTimelineMoveTimingPatch(
|
|
175
|
+
original,
|
|
176
|
+
target,
|
|
177
|
+
updates.start,
|
|
178
|
+
element.duration,
|
|
179
|
+
track,
|
|
180
|
+
);
|
|
181
|
+
};
|
|
182
|
+
const coalesceKey = `timeline-move:${element.hfId ?? element.id}`;
|
|
183
|
+
const moveFallback = () =>
|
|
184
|
+
enqueueEdit(element, "Move timeline clip", buildMovePatches, coalesceKey).then(() =>
|
|
185
|
+
// Soft-reload with the server's rewritten GSAP script — the timing-only move already patched
|
|
186
|
+
// DOM + store, so swapping the script avoids the all-clips flash; falls back to reloadPreview().
|
|
187
|
+
finishClipTimingFallback({
|
|
188
|
+
iframe: previewIframeRef.current,
|
|
198
189
|
reloadPreview,
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
190
|
+
projectId: projectIdRef.current,
|
|
191
|
+
targetPath,
|
|
192
|
+
domId: element.domId,
|
|
193
|
+
label: "Move timeline clip",
|
|
194
|
+
coalesceKey,
|
|
195
|
+
recordEdit,
|
|
196
|
+
edit: { kind: "shift", delta: updates.start - element.start },
|
|
197
|
+
}),
|
|
198
|
+
);
|
|
199
|
+
return reorderDone
|
|
200
|
+
.then(() => {
|
|
201
|
+
// The SDK setTiming path writes start only — a lane change must take
|
|
202
|
+
// the fallback, whose patch builder writes data-track-index too.
|
|
203
|
+
if (sdkSession && element.hfId && !needsExtension && !trackChanged) {
|
|
204
|
+
return sdkTimingPersist(
|
|
205
|
+
element.hfId,
|
|
206
|
+
targetPath,
|
|
207
|
+
{ start: updates.start },
|
|
208
|
+
sdkSession,
|
|
209
|
+
{
|
|
210
|
+
editHistory: { recordEdit },
|
|
211
|
+
writeProjectFile,
|
|
212
|
+
reloadPreview,
|
|
213
|
+
domEditSaveTimestampRef,
|
|
214
|
+
compositionPath: activeCompPath,
|
|
215
|
+
// Capture on-disk bytes as the undo `before` so undoing a timing move
|
|
216
|
+
// restores the file verbatim, not a normalized full-DOM re-emit.
|
|
217
|
+
readProjectFile: (path) => readFileContent(projectIdRef.current ?? "", path),
|
|
218
|
+
},
|
|
219
|
+
{ label: "Move timeline clip", coalesceKey },
|
|
220
|
+
).then((handled) => {
|
|
221
|
+
if (!handled) return moveFallback();
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
return moveFallback();
|
|
225
|
+
})
|
|
226
|
+
.catch((error) => {
|
|
227
|
+
// Failed persist: revert the optimistic duration readout + live root.
|
|
228
|
+
rollbackDuration();
|
|
229
|
+
throw error;
|
|
208
230
|
});
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
});
|
|
231
|
+
};
|
|
232
|
+
return updates.stackingReorder ? serializeZLaneGesture(commitMove) : commitMove();
|
|
212
233
|
},
|
|
213
234
|
[
|
|
214
235
|
previewIframeRef,
|
|
@@ -244,7 +265,14 @@ export function useTimelineEditing({
|
|
|
244
265
|
: "data-media-start";
|
|
245
266
|
liveAttrs.push([liveAttr, formatTimelineAttributeNumber(updates.playbackStart)]);
|
|
246
267
|
}
|
|
247
|
-
patchIframeDomTiming(previewIframeRef.current, element, liveAttrs);
|
|
268
|
+
patchIframeDomTiming(previewIframeRef.current, element, liveAttrs, activeCompPath);
|
|
269
|
+
// Snapshot the duration BEFORE the optimistic updates below so a failed
|
|
270
|
+
// persist can roll the readout + live root back (see captureDurationRollback).
|
|
271
|
+
const rollbackDuration = captureDurationRollback(previewIframeRef.current);
|
|
272
|
+
// needsExtension gates the SDK path (setTiming can't grow the root duration), so read the store BEFORE the readout sync below optimistically updates it.
|
|
273
|
+
const needsExtension = extendRootDurationIfNeeded(updates.start + updates.duration);
|
|
274
|
+
// Optimistic duration readout: content-driven (grow AND shrink), from the just-patched live DOM. See syncPreviewContentDuration.
|
|
275
|
+
syncPreviewContentDuration(previewIframeRef.current);
|
|
248
276
|
const targetPath = element.sourceFile || activeCompPath || "index.html";
|
|
249
277
|
const buildResizePatches: PersistTimelineEditInput["buildPatches"] = (original, target) => {
|
|
250
278
|
return buildTimelineResizeTimingPatch(original, target, element, updates);
|
|
@@ -253,59 +281,55 @@ export function useTimelineEditing({
|
|
|
253
281
|
updates.playbackStart != null ||
|
|
254
282
|
(updates.start !== element.start && element.playbackStart != null);
|
|
255
283
|
// Server-path fallback: after persisting the attr patch, scale GSAP tween
|
|
256
|
-
// positions/durations on the server
|
|
257
|
-
//
|
|
284
|
+
// positions/durations on the server, then soft-reload with the rewritten
|
|
285
|
+
// script (timing-only resize) — same no-flash path as move; full reload is
|
|
286
|
+
// the fallback.
|
|
258
287
|
const coalesceKey = `timeline-resize:${element.hfId ?? element.id}`;
|
|
259
|
-
const timingChanged =
|
|
260
|
-
updates.start !== element.start || updates.duration !== element.duration;
|
|
261
|
-
const needsExtension = extendRootDurationIfNeeded(updates.start + updates.duration);
|
|
262
288
|
const resizeFallback = () =>
|
|
263
|
-
enqueueEdit(element, "Resize timeline clip", buildResizePatches, coalesceKey).then(() =>
|
|
264
|
-
|
|
265
|
-
const domId = element.domId;
|
|
266
|
-
return finishTimelineTimingFallback({
|
|
289
|
+
enqueueEdit(element, "Resize timeline clip", buildResizePatches, coalesceKey).then(() =>
|
|
290
|
+
finishClipTimingFallback({
|
|
267
291
|
iframe: previewIframeRef.current,
|
|
268
|
-
needsExtension,
|
|
269
|
-
rootDurationSeconds: updates.start + updates.duration,
|
|
270
|
-
reloadPreview,
|
|
271
|
-
gsapMutation:
|
|
272
|
-
timingChanged && domId && pid
|
|
273
|
-
? foldedScaleGsapMutation({
|
|
274
|
-
projectId: pid,
|
|
275
|
-
targetPath,
|
|
276
|
-
domId,
|
|
277
|
-
from: { start: element.start, duration: element.duration },
|
|
278
|
-
to: { start: updates.start, duration: updates.duration },
|
|
279
|
-
label: "Resize timeline clip",
|
|
280
|
-
coalesceKey,
|
|
281
|
-
recordEdit,
|
|
282
|
-
})
|
|
283
|
-
: undefined,
|
|
284
|
-
onGsapError: (err) => console.error("[Timeline] Failed to scale GSAP positions", err),
|
|
285
|
-
});
|
|
286
|
-
});
|
|
287
|
-
if (sdkSession && element.hfId && !hasPbsAdjustment && !needsExtension) {
|
|
288
|
-
return sdkTimingPersist(
|
|
289
|
-
element.hfId,
|
|
290
|
-
targetPath,
|
|
291
|
-
{ start: updates.start, duration: updates.duration },
|
|
292
|
-
sdkSession,
|
|
293
|
-
{
|
|
294
|
-
editHistory: { recordEdit },
|
|
295
|
-
writeProjectFile,
|
|
296
292
|
reloadPreview,
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
293
|
+
projectId: projectIdRef.current,
|
|
294
|
+
targetPath,
|
|
295
|
+
domId: element.domId,
|
|
296
|
+
label: "Resize timeline clip",
|
|
297
|
+
coalesceKey,
|
|
298
|
+
recordEdit,
|
|
299
|
+
edit: {
|
|
300
|
+
kind: "scale",
|
|
301
|
+
from: { start: element.start, duration: element.duration },
|
|
302
|
+
to: { start: updates.start, duration: updates.duration },
|
|
303
|
+
},
|
|
304
|
+
}),
|
|
305
|
+
);
|
|
306
|
+
const persistDone =
|
|
307
|
+
sdkSession && element.hfId && !hasPbsAdjustment && !needsExtension
|
|
308
|
+
? sdkTimingPersist(
|
|
309
|
+
element.hfId,
|
|
310
|
+
targetPath,
|
|
311
|
+
{ start: updates.start, duration: updates.duration },
|
|
312
|
+
sdkSession,
|
|
313
|
+
{
|
|
314
|
+
editHistory: { recordEdit },
|
|
315
|
+
writeProjectFile,
|
|
316
|
+
reloadPreview,
|
|
317
|
+
domEditSaveTimestampRef,
|
|
318
|
+
compositionPath: activeCompPath,
|
|
319
|
+
// Capture on-disk bytes as the undo `before` so undoing a timing
|
|
320
|
+
// resize restores the file verbatim, not a normalized full-DOM re-emit.
|
|
321
|
+
readProjectFile: (path) => readFileContent(projectIdRef.current ?? "", path),
|
|
322
|
+
},
|
|
323
|
+
{ label: "Resize timeline clip", coalesceKey },
|
|
324
|
+
).then((handled) => {
|
|
325
|
+
if (!handled) return resizeFallback();
|
|
326
|
+
})
|
|
327
|
+
: resizeFallback();
|
|
328
|
+
return persistDone.catch((error) => {
|
|
329
|
+
// Failed persist: revert the optimistic duration readout + live root.
|
|
330
|
+
rollbackDuration();
|
|
331
|
+
throw error;
|
|
332
|
+
});
|
|
309
333
|
},
|
|
310
334
|
[
|
|
311
335
|
previewIframeRef,
|
|
@@ -384,19 +408,37 @@ export function useTimelineEditing({
|
|
|
384
408
|
changed?: boolean;
|
|
385
409
|
content?: string;
|
|
386
410
|
};
|
|
387
|
-
const
|
|
411
|
+
const removedContent =
|
|
388
412
|
typeof removeData.content === "string" ? removeData.content : originalContent;
|
|
413
|
+
// Content-driven duration: shrink the composition to the furthest
|
|
414
|
+
// remaining clip end, read from the post-removal SOURCE (raw
|
|
415
|
+
// data-duration), so deleting the last/longest clip removes trailing
|
|
416
|
+
// empty space. Measured from the source, not the store, whose
|
|
417
|
+
// durations are runtime-truncated.
|
|
418
|
+
const deleteContentEnd = furthestClipEndFromSource(removedContent);
|
|
419
|
+
const patchedContent = setCompositionDurationToContent(removedContent, deleteContentEnd);
|
|
420
|
+
// Optimistically reflect the shrunk length in the readout/seek bar,
|
|
421
|
+
// rolling it back if the persist below fails (see captureDurationRollback).
|
|
422
|
+
const rollbackDuration = captureDurationRollback(previewIframeRef.current);
|
|
423
|
+
if (deleteContentEnd > 0 && targetPath === (activeCompPath || "index.html")) {
|
|
424
|
+
usePlayerStore.getState().setDuration(deleteContentEnd);
|
|
425
|
+
}
|
|
389
426
|
|
|
390
427
|
domEditSaveTimestampRef.current = Date.now();
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
428
|
+
try {
|
|
429
|
+
await saveProjectFilesWithHistory({
|
|
430
|
+
projectId: pid,
|
|
431
|
+
label: "Delete timeline clip",
|
|
432
|
+
kind: "timeline",
|
|
433
|
+
files: { [targetPath]: patchedContent },
|
|
434
|
+
readFile: async () => originalContent,
|
|
435
|
+
writeFile: writeProjectFile,
|
|
436
|
+
recordEdit,
|
|
437
|
+
});
|
|
438
|
+
} catch (error) {
|
|
439
|
+
rollbackDuration();
|
|
440
|
+
throw error;
|
|
441
|
+
}
|
|
400
442
|
|
|
401
443
|
usePlayerStore
|
|
402
444
|
.getState()
|
|
@@ -422,145 +464,23 @@ export function useTimelineEditing({
|
|
|
422
464
|
reloadPreview,
|
|
423
465
|
isRecordingRef,
|
|
424
466
|
forceReloadSdkSession,
|
|
467
|
+
previewIframeRef,
|
|
425
468
|
],
|
|
426
469
|
);
|
|
427
470
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
if (!pid) throw new Error("No active project");
|
|
442
|
-
|
|
443
|
-
const kind = getTimelineAssetKind(assetPath);
|
|
444
|
-
if (!kind) {
|
|
445
|
-
showToast("Only image, video, and audio assets can be dropped onto the timeline.");
|
|
446
|
-
return;
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
const targetPath = activeCompPath || "index.html";
|
|
450
|
-
try {
|
|
451
|
-
const originalContent = await readFileContent(pid, targetPath);
|
|
452
|
-
|
|
453
|
-
const normalizedStart = Number(formatTimelineAttributeNumber(placement.start));
|
|
454
|
-
const duration =
|
|
455
|
-
Number.isFinite(durationOverride) && durationOverride != null && durationOverride > 0
|
|
456
|
-
? durationOverride
|
|
457
|
-
: await resolveDroppedAssetDuration(pid, assetPath, kind);
|
|
458
|
-
const normalizedDuration = Number(formatTimelineAttributeNumber(duration));
|
|
459
|
-
const newId = buildTimelineAssetId(assetPath, collectHtmlIds(originalContent));
|
|
460
|
-
const resolvedAssetSrc = resolveTimelineAssetSrc(targetPath, assetPath);
|
|
461
|
-
|
|
462
|
-
const resolvedTargetPath = targetPath || "index.html";
|
|
463
|
-
const relevantElements = timelineElements.filter(
|
|
464
|
-
(te) => (te.sourceFile || activeCompPath || "index.html") === resolvedTargetPath,
|
|
465
|
-
);
|
|
466
|
-
const newElementZIndex = Math.max(1, relevantElements.length + 1);
|
|
467
|
-
|
|
468
|
-
const patchedContent = insertTimelineAssetIntoSource(
|
|
469
|
-
originalContent,
|
|
470
|
-
buildTimelineAssetInsertHtml({
|
|
471
|
-
id: newId,
|
|
472
|
-
assetPath: resolvedAssetSrc,
|
|
473
|
-
kind,
|
|
474
|
-
start: normalizedStart,
|
|
475
|
-
duration: normalizedDuration,
|
|
476
|
-
track: placement.track,
|
|
477
|
-
zIndex: newElementZIndex,
|
|
478
|
-
geometry: resolveTimelineAssetInitialGeometry(originalContent),
|
|
479
|
-
}),
|
|
480
|
-
);
|
|
481
|
-
|
|
482
|
-
domEditSaveTimestampRef.current = Date.now();
|
|
483
|
-
await saveProjectFilesWithHistory({
|
|
484
|
-
projectId: pid,
|
|
485
|
-
label: "Add timeline asset",
|
|
486
|
-
kind: "timeline",
|
|
487
|
-
files: { [targetPath]: patchedContent },
|
|
488
|
-
readFile: async () => originalContent,
|
|
489
|
-
writeFile: writeProjectFile,
|
|
490
|
-
recordEdit,
|
|
491
|
-
});
|
|
492
|
-
|
|
493
|
-
forceReloadSdkSession?.();
|
|
494
|
-
reloadPreview();
|
|
495
|
-
} catch (error) {
|
|
496
|
-
const message =
|
|
497
|
-
error instanceof Error ? error.message : "Failed to drop asset onto timeline";
|
|
498
|
-
showToast(message);
|
|
499
|
-
}
|
|
500
|
-
},
|
|
501
|
-
[
|
|
502
|
-
activeCompPath,
|
|
503
|
-
recordEdit,
|
|
504
|
-
showToast,
|
|
505
|
-
timelineElements,
|
|
506
|
-
writeProjectFile,
|
|
507
|
-
domEditSaveTimestampRef,
|
|
508
|
-
reloadPreview,
|
|
509
|
-
isRecordingRef,
|
|
510
|
-
forceReloadSdkSession,
|
|
511
|
-
],
|
|
512
|
-
);
|
|
513
|
-
|
|
514
|
-
// fallow-ignore-next-line complexity
|
|
515
|
-
const handleTimelineFileDrop = useCallback(
|
|
516
|
-
// fallow-ignore-next-line complexity
|
|
517
|
-
async (files: File[], placement?: Pick<TimelineElement, "start" | "track">) => {
|
|
518
|
-
if (isRecordingRef?.current) {
|
|
519
|
-
showToast("Cannot edit timeline while recording", "error");
|
|
520
|
-
return;
|
|
521
|
-
}
|
|
522
|
-
const pid = projectIdRef.current;
|
|
523
|
-
if (!pid) return;
|
|
524
|
-
const uploaded = await uploadProjectFiles(files);
|
|
525
|
-
if (uploaded.length === 0) return;
|
|
526
|
-
const durations: number[] = [];
|
|
527
|
-
for (const assetPath of uploaded) {
|
|
528
|
-
const kind = getTimelineAssetKind(assetPath);
|
|
529
|
-
const duration = kind ? await resolveDroppedAssetDuration(pid, assetPath, kind) : 0;
|
|
530
|
-
durations.push(Number(formatTimelineAttributeNumber(duration)));
|
|
531
|
-
}
|
|
532
|
-
const placements = buildTimelineFileDropPlacements(
|
|
533
|
-
placement ?? { start: 0, track: 0 },
|
|
534
|
-
durations,
|
|
535
|
-
timelineElements
|
|
536
|
-
.filter(
|
|
537
|
-
(te) =>
|
|
538
|
-
(te.sourceFile || activeCompPath || "index.html") ===
|
|
539
|
-
(activeCompPath || "index.html"),
|
|
540
|
-
)
|
|
541
|
-
.map((te) => ({
|
|
542
|
-
start: te.start,
|
|
543
|
-
duration: te.duration,
|
|
544
|
-
track: te.track,
|
|
545
|
-
})),
|
|
546
|
-
);
|
|
547
|
-
for (const [index, assetPath] of uploaded.entries()) {
|
|
548
|
-
await handleTimelineAssetDrop(
|
|
549
|
-
assetPath,
|
|
550
|
-
placements[index] ?? placements[0],
|
|
551
|
-
durations[index],
|
|
552
|
-
);
|
|
553
|
-
}
|
|
554
|
-
},
|
|
555
|
-
[
|
|
556
|
-
activeCompPath,
|
|
557
|
-
handleTimelineAssetDrop,
|
|
558
|
-
timelineElements,
|
|
559
|
-
uploadProjectFiles,
|
|
560
|
-
isRecordingRef,
|
|
561
|
-
showToast,
|
|
562
|
-
],
|
|
563
|
-
);
|
|
471
|
+
const { handleTimelineAssetDrop, handleTimelineFileDrop } = useTimelineAssetDropOps({
|
|
472
|
+
projectIdRef,
|
|
473
|
+
activeCompPath,
|
|
474
|
+
timelineElements,
|
|
475
|
+
showToast,
|
|
476
|
+
writeProjectFile,
|
|
477
|
+
recordEdit,
|
|
478
|
+
domEditSaveTimestampRef,
|
|
479
|
+
reloadPreview,
|
|
480
|
+
uploadProjectFiles,
|
|
481
|
+
isRecordingRef,
|
|
482
|
+
forceReloadSdkSession,
|
|
483
|
+
});
|
|
564
484
|
|
|
565
485
|
const handleBlockedTimelineEdit = useCallback(
|
|
566
486
|
(_element: TimelineElement) => {
|
|
@@ -23,7 +23,7 @@ export type TimelineZIndexReorderCommit = (
|
|
|
23
23
|
key?: string;
|
|
24
24
|
}>,
|
|
25
25
|
coalesceKey?: string,
|
|
26
|
-
) => Promise<void>;
|
|
26
|
+
) => Promise<import("./domEditCommitTypes").DomEditPatchBatchesResult | undefined | void>;
|
|
27
27
|
|
|
28
28
|
export interface UseTimelineEditingOptions {
|
|
29
29
|
projectId: string | null;
|