@hyperframes/studio 0.8.127 → 0.8.128
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-uqzDaxVL.js → hyperframes-player-CKCx0CFA.js} +1 -1
- package/dist/assets/{index-DXSYlqmV.js → index-CF31Pq1E.js} +1 -1
- package/dist/assets/{index-COhbL1xq.js → index-ClLzyuwb.js} +171 -171
- package/dist/assets/{index-CLBNYlDY.js → index-CwS08gWW.js} +1 -1
- package/dist/{chunk-3F47EFCT.js → chunk-7UQWVN52.js} +17 -10
- package/dist/chunk-7UQWVN52.js.map +1 -0
- package/dist/{chunk-246VB2G3.js → chunk-DRVWEPO5.js} +35 -7
- package/dist/chunk-DRVWEPO5.js.map +1 -0
- package/dist/{chunk-5EKWJZLM.js → chunk-TDWGXBUT.js} +1 -1
- package/dist/chunk-TDWGXBUT.js.map +1 -0
- package/dist/{domEditingLayers-OL7GLSS4.js → domEditingLayers-IEVLO54Z.js} +3 -3
- package/dist/index.d.ts +8 -9
- package/dist/index.html +1 -1
- package/dist/index.js +1193 -869
- package/dist/index.js.map +1 -1
- package/dist/player.js +2 -2
- package/package.json +8 -8
- package/src/components/TimelineToolbar.tsx +6 -7
- package/src/components/editor/commitPreflightHandles.test.tsx +32 -0
- package/src/components/editor/domEditingLayers.test.ts +45 -18
- package/src/components/editor/domEditingLayers.ts +11 -9
- package/src/components/editor/domEditingTypes.ts +1 -0
- package/src/components/editor/keyframeRetime.ts +11 -4
- package/src/components/editor/probeSourceElement.ts +17 -1
- package/src/components/nle/useCanvasZOrderTimelineMirror.test.tsx +4 -14
- package/src/components/nle/useCanvasZOrderTimelineMirror.ts +0 -7
- package/src/components/nle/useTimelineEditCallbacks.test.tsx +27 -42
- package/src/components/nle/useTimelineEditCallbacks.ts +3 -2
- package/src/hooks/gsapDragCommit.ts +8 -5
- package/src/hooks/gsapDragPositionCommit.ts +90 -17
- package/src/hooks/gsapKeyframeCacheHelpers.test.ts +50 -0
- package/src/hooks/gsapKeyframeCacheHelpers.ts +21 -2
- package/src/hooks/gsapMutationClient.ts +35 -7
- package/src/hooks/gsapOvershootEase.test.ts +67 -0
- package/src/hooks/gsapParsedTween.ts +5 -6
- package/src/hooks/gsapPositionDetection.ts +5 -2
- package/src/hooks/gsapRuntimeBridge.test.ts +35 -0
- package/src/hooks/gsapRuntimeKeyframes.test.ts +80 -6
- package/src/hooks/gsapRuntimeKeyframes.ts +19 -59
- package/src/hooks/gsapRuntimeMotionPath.ts +47 -0
- package/src/hooks/gsapRuntimeTweenIndex.ts +53 -0
- package/src/hooks/gsapShared.test.ts +48 -35
- package/src/hooks/gsapShared.ts +30 -32
- package/src/hooks/gsapValueAtPlayhead.realGsap.test.ts +93 -3
- package/src/hooks/gsapValueAtPlayhead.test.ts +13 -0
- package/src/hooks/gsapValueAtPlayhead.ts +3 -4
- package/src/hooks/timelineEditingHelpers.test.ts +21 -0
- package/src/hooks/timelineEditingHelpers.ts +40 -14
- package/src/hooks/timelineLinkEditPlan.ts +6 -4
- package/src/hooks/timelineMoveAdapter.ts +3 -5
- package/src/hooks/timelineTimingSync.test.ts +83 -125
- package/src/hooks/timelineTimingSync.ts +62 -42
- package/src/hooks/useAnimatedPropertyCommit.ts +6 -6
- package/src/hooks/useBlockedTimelineEditToast.ts +3 -12
- package/src/hooks/useCommitPreflightCapabilities.ts +42 -7
- package/src/hooks/useDomEditPreviewSync.ts +8 -2
- package/src/hooks/useDomSelection.test.ts +27 -0
- package/src/hooks/useDomSelection.ts +12 -13
- package/src/hooks/useDomSelectionTypes.ts +1 -0
- package/src/hooks/useEditHistoryActions.test.tsx +54 -3
- package/src/hooks/useEditHistoryActions.ts +9 -6
- package/src/hooks/useEnableKeyframes.test.ts +20 -15
- package/src/hooks/useEnableKeyframes.ts +54 -71
- package/src/hooks/useGestureCommit.ts +2 -1
- package/src/hooks/useGsapTweenCache.ts +2 -17
- package/src/hooks/usePersistentEditHistory.test.ts +45 -0
- package/src/hooks/usePersistentEditHistory.ts +13 -2
- package/src/hooks/usePreviewPersistence.history.test.tsx +83 -0
- package/src/hooks/usePreviewPersistence.ts +26 -4
- package/src/hooks/useTimelineClipTimingEditing.ts +343 -0
- package/src/hooks/useTimelineDeleteOps.test.tsx +20 -1
- package/src/hooks/useTimelineDeleteOps.ts +5 -13
- package/src/hooks/useTimelineEditing.test.tsx +63 -4
- package/src/hooks/useTimelineEditing.ts +21 -295
- package/src/hooks/useTimelineGroupEditing.test.tsx +91 -0
- package/src/hooks/useTimelineGroupEditing.ts +15 -38
- package/src/player/components/Timeline.test.ts +12 -4
- package/src/player/components/TimelineClip.tsx +3 -0
- package/src/player/components/TimelineCompactDiamonds.tsx +4 -0
- package/src/player/components/TimelineLanes.test.tsx +61 -5
- package/src/player/components/TimelineLanes.tsx +20 -28
- package/src/player/components/TimelinePropertyLanes.tsx +10 -1
- package/src/player/components/timelineCallbacks.ts +1 -4
- package/src/player/components/timelineClipDragCommit.test.ts +31 -0
- package/src/player/components/timelineClipDragCommit.ts +39 -28
- package/src/player/components/timelineClipDragGestureLifecycle.ts +1 -1
- package/src/player/components/timelineClipGestureHandlers.test.ts +6 -34
- package/src/player/components/timelineClipGestureHandlers.ts +2 -20
- package/src/player/components/timelineEditing.ts +1 -3
- package/src/player/components/timelineGroupResizeCommit.ts +10 -9
- package/src/player/components/timelineLinkSelection.ts +1 -35
- package/src/player/components/timelineOptimisticRevision.ts +7 -4
- package/src/player/components/trackHeaderLaneState.ts +9 -2
- package/src/player/components/trackHeaderLaneValues.test.ts +89 -0
- package/src/player/components/trackHeaderLaneValues.ts +54 -13
- package/src/player/components/useTimelineClipDrag.keyboard.test.tsx +4 -6
- package/src/player/components/useTimelineClipDrag.resize.test.tsx +4 -7
- package/src/player/components/useTimelineClipDrag.ts +0 -3
- package/src/player/components/useTimelineKeyframeHandlers.ts +17 -1
- package/src/player/store/batchElementUpdates.test.ts +43 -0
- package/src/player/store/batchElementUpdates.ts +9 -0
- package/src/player/store/elementPatchQueue.ts +35 -0
- package/src/player/store/playerStore.ts +5 -6
- package/src/utils/domEditHelpers.ts +5 -0
- package/src/utils/globalTimeCompiler.test.ts +0 -4
- package/src/utils/globalTimeCompiler.ts +6 -4
- package/src/utils/gsapKeyframeEases.ts +79 -0
- package/src/utils/sameData.ts +9 -0
- package/src/utils/studioFileHistory.ts +1 -0
- package/src/utils/studioPendingEdits.ts +15 -1
- package/dist/chunk-246VB2G3.js.map +0 -1
- package/dist/chunk-3F47EFCT.js.map +0 -1
- package/dist/chunk-5EKWJZLM.js.map +0 -1
- /package/dist/{domEditingLayers-OL7GLSS4.js.map → domEditingLayers-IEVLO54Z.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
STUDIO_PREVIEW_FPS,
|
|
10
10
|
applyPreviewVariablesToUrl,
|
|
11
11
|
clampToDuration,
|
|
12
|
+
collectElementPatches,
|
|
12
13
|
computePinnedZoomPercent,
|
|
13
14
|
createTimelineResetState,
|
|
14
15
|
effectiveThumbnailMode,
|
|
@@ -26,6 +27,7 @@ import {
|
|
|
26
27
|
liveTime,
|
|
27
28
|
panelsInZone,
|
|
28
29
|
parseDockLayout,
|
|
30
|
+
patchElements,
|
|
29
31
|
readStudioUiPreferences,
|
|
30
32
|
secondsToFrame,
|
|
31
33
|
stepFrameTime,
|
|
@@ -37,7 +39,7 @@ import {
|
|
|
37
39
|
usePreviewVariablesStore,
|
|
38
40
|
whenPreviewBooted,
|
|
39
41
|
writeStudioUiPreferences
|
|
40
|
-
} from "./chunk-
|
|
42
|
+
} from "./chunk-DRVWEPO5.js";
|
|
41
43
|
import {
|
|
42
44
|
Button,
|
|
43
45
|
ContextMenu,
|
|
@@ -102,7 +104,7 @@ import {
|
|
|
102
104
|
resolveDomEditSelection,
|
|
103
105
|
serializeDomEditTextFields,
|
|
104
106
|
studioApiFetch
|
|
105
|
-
} from "./chunk-
|
|
107
|
+
} from "./chunk-7UQWVN52.js";
|
|
106
108
|
import {
|
|
107
109
|
LAYER_REVEAL_PRIOR_POSITION_ATTR,
|
|
108
110
|
LAYER_REVEAL_PRIOR_Z_ATTR,
|
|
@@ -162,7 +164,7 @@ import {
|
|
|
162
164
|
splitTimelineElementKey,
|
|
163
165
|
toHexColor,
|
|
164
166
|
wrapTimeline
|
|
165
|
-
} from "./chunk-
|
|
167
|
+
} from "./chunk-TDWGXBUT.js";
|
|
166
168
|
import "./chunk-PZ5AY32C.js";
|
|
167
169
|
|
|
168
170
|
// src/components/EditorShell.tsx
|
|
@@ -2779,7 +2781,6 @@ function resolveTimelineResize(input, edge, clientX) {
|
|
|
2779
2781
|
const clampedDelta = clamp2(deltaTime, minDelta, maxDelta);
|
|
2780
2782
|
return applyClipStartTrimDelta(input, clampedDelta);
|
|
2781
2783
|
}
|
|
2782
|
-
var MAX_HAND_EDIT_CLIPS = 3;
|
|
2783
2784
|
function resolveBlockedTimelineEditIntent(input) {
|
|
2784
2785
|
if (input.capabilities.canMove) {
|
|
2785
2786
|
return null;
|
|
@@ -4348,52 +4349,6 @@ var useLinkedClipPreferences = create2((set) => ({
|
|
|
4348
4349
|
}));
|
|
4349
4350
|
var isLinkedSelectionOn = () => useLinkedClipPreferences.getState().linkedSelection;
|
|
4350
4351
|
|
|
4351
|
-
// src/player/components/timelineLinkSelection.ts
|
|
4352
|
-
function selectClipWithLinks(key2, altKey, setSelectedElementId) {
|
|
4353
|
-
const state = usePlayerStore.getState();
|
|
4354
|
-
if (altKey || !isLinkedSelectionOn()) {
|
|
4355
|
-
state.setSelection([key2], key2);
|
|
4356
|
-
return;
|
|
4357
|
-
}
|
|
4358
|
-
setSelectedElementId(key2);
|
|
4359
|
-
const { selectedElementIds, elements } = usePlayerStore.getState();
|
|
4360
|
-
const expanded = expandToLinkedMembers(selectedElementIds, elements);
|
|
4361
|
-
if (expanded.size > selectedElementIds.size) state.setSelection(expanded, key2);
|
|
4362
|
-
}
|
|
4363
|
-
function toggleClipWithLinks(key2, altKey) {
|
|
4364
|
-
const state = usePlayerStore.getState();
|
|
4365
|
-
const element = state.elements.find((el) => (el.key ?? el.id) === key2);
|
|
4366
|
-
const members = element ? linkedMembersOf(element, state.elements, !altKey && isLinkedSelectionOn()) : [];
|
|
4367
|
-
const memberKeys = members.length > 0 ? members.map((el) => el.key ?? el.id) : [key2];
|
|
4368
|
-
const next = new Set(state.selectedElementIds);
|
|
4369
|
-
const removing = next.has(key2);
|
|
4370
|
-
for (const memberKey of memberKeys) {
|
|
4371
|
-
if (removing) next.delete(memberKey);
|
|
4372
|
-
else next.add(memberKey);
|
|
4373
|
-
}
|
|
4374
|
-
state.setSelection(next, removing ? state.selectedElementId : key2);
|
|
4375
|
-
const primary = usePlayerStore.getState().selectedElementId;
|
|
4376
|
-
return state.elements.find((el) => (el.key ?? el.id) === primary) ?? null;
|
|
4377
|
-
}
|
|
4378
|
-
function exceedsHandEditLimit(grabbed, altKey, edge) {
|
|
4379
|
-
const { selectedElementIds, elements } = usePlayerStore.getState();
|
|
4380
|
-
const grabbedKey = grabbed.key ?? grabbed.id;
|
|
4381
|
-
const keys = linkedGestureKeys(
|
|
4382
|
-
selectedElementIds,
|
|
4383
|
-
grabbed,
|
|
4384
|
-
elements,
|
|
4385
|
-
altKey,
|
|
4386
|
-
isLinkedSelectionOn()
|
|
4387
|
-
);
|
|
4388
|
-
const changed = edge ? buildTimelineGroupResizeMembers(
|
|
4389
|
-
elements,
|
|
4390
|
-
dropMisalignedTrimPartners(keys, grabbed, elements, edge),
|
|
4391
|
-
grabbedKey,
|
|
4392
|
-
edge
|
|
4393
|
-
) : resolveGroupMovers(elements, keys, grabbedKey);
|
|
4394
|
-
return (changed?.length ?? 1) > MAX_HAND_EDIT_CLIPS;
|
|
4395
|
-
}
|
|
4396
|
-
|
|
4397
4352
|
// src/player/components/timelineReadOnly.ts
|
|
4398
4353
|
import { createContext as createContext2, useContext as useContext2, useEffect as useEffect7, useEffectEvent } from "react";
|
|
4399
4354
|
var TimelineReadOnlyContext = createContext2(null);
|
|
@@ -4504,20 +4459,86 @@ function timelineEaseFocusId(elementId2, target) {
|
|
|
4504
4459
|
import { useCallback as useCallback8, useMemo as useMemo6 } from "react";
|
|
4505
4460
|
|
|
4506
4461
|
// src/player/components/TimelinePropertyLanes.tsx
|
|
4507
|
-
import {
|
|
4462
|
+
import {
|
|
4463
|
+
useMemo as useMemo3
|
|
4464
|
+
} from "react";
|
|
4508
4465
|
import {
|
|
4509
4466
|
classifyPropertyGroup
|
|
4510
4467
|
} from "@hyperframes/core/gsap-parser";
|
|
4511
4468
|
|
|
4512
4469
|
// src/hooks/gsapShared.ts
|
|
4513
4470
|
import { isHtmlElement as isHtmlElement2 } from "@hyperframes/core/runtime/dom-realm";
|
|
4471
|
+
import { getObjectArrayKeyframeTiming } from "@hyperframes/parsers/gsap-parser";
|
|
4472
|
+
|
|
4473
|
+
// src/utils/gsapKeyframeEases.ts
|
|
4474
|
+
import gsap2 from "gsap";
|
|
4475
|
+
var PERCENTAGE_SEGMENT_EASE = "power1.inOut";
|
|
4476
|
+
var ARRAY_STEP_EASE = "none";
|
|
4477
|
+
var MOTION_PATH_RUN_EASE = "power1.out";
|
|
4478
|
+
function keyframedTweenEases(anim) {
|
|
4479
|
+
const data = anim.keyframes;
|
|
4480
|
+
const fallback = data?.format === "object-array" ? ARRAY_STEP_EASE : data?.easeEach ?? PERCENTAGE_SEGMENT_EASE;
|
|
4481
|
+
return {
|
|
4482
|
+
run: data ? data.ease ?? anim.ease ?? (data.fromMotionPath ? MOTION_PATH_RUN_EASE : void 0) : void 0,
|
|
4483
|
+
segment: (arriving) => arriving.ease ?? fallback
|
|
4484
|
+
};
|
|
4485
|
+
}
|
|
4486
|
+
function easeFunction(name) {
|
|
4487
|
+
if (!name || name === "none" || name === "linear") return null;
|
|
4488
|
+
return gsap2.parseEase(name) ?? null;
|
|
4489
|
+
}
|
|
4490
|
+
var runEaseOf = (anim) => keyframedTweenEases(anim).run;
|
|
4491
|
+
var SCAN_STEPS = 200;
|
|
4492
|
+
var BISECT_STEPS = 40;
|
|
4493
|
+
var timingEases = /* @__PURE__ */ new Map();
|
|
4494
|
+
function timingEase(runEase) {
|
|
4495
|
+
if (!runEase) return null;
|
|
4496
|
+
if (!timingEases.has(runEase)) {
|
|
4497
|
+
const run = easeFunction(runEase);
|
|
4498
|
+
const samples = Array.from(
|
|
4499
|
+
{ length: SCAN_STEPS + 1 },
|
|
4500
|
+
(_, step) => run?.(step / SCAN_STEPS) ?? 0
|
|
4501
|
+
);
|
|
4502
|
+
const oneToOne = samples.every((v, i) => v >= 0 && v <= 1 && (i === 0 || v > samples[i - 1]));
|
|
4503
|
+
timingEases.set(runEase, run && oneToOne ? run : null);
|
|
4504
|
+
}
|
|
4505
|
+
return timingEases.get(runEase) ?? null;
|
|
4506
|
+
}
|
|
4507
|
+
function progressAtTime(runEase, timePercentage) {
|
|
4508
|
+
const run = timingEase(runEase);
|
|
4509
|
+
return run ? run(timePercentage / 100) * 100 : timePercentage;
|
|
4510
|
+
}
|
|
4511
|
+
function timeAtProgress(runEase, percentage) {
|
|
4512
|
+
const run = timingEase(runEase);
|
|
4513
|
+
if (!run || percentage <= 0 || percentage >= 100) return percentage;
|
|
4514
|
+
const target = percentage / 100;
|
|
4515
|
+
let low = 0;
|
|
4516
|
+
let high = 1;
|
|
4517
|
+
for (let step = 1; step <= SCAN_STEPS; step++) {
|
|
4518
|
+
if (run(step / SCAN_STEPS) >= target) {
|
|
4519
|
+
low = (step - 1) / SCAN_STEPS;
|
|
4520
|
+
high = step / SCAN_STEPS;
|
|
4521
|
+
break;
|
|
4522
|
+
}
|
|
4523
|
+
}
|
|
4524
|
+
for (let step = 0; step < BISECT_STEPS; step++) {
|
|
4525
|
+
const mid = (low + high) / 2;
|
|
4526
|
+
if (run(mid) >= target) high = mid;
|
|
4527
|
+
else low = mid;
|
|
4528
|
+
}
|
|
4529
|
+
return high * 100;
|
|
4530
|
+
}
|
|
4514
4531
|
|
|
4515
4532
|
// src/utils/globalTimeCompiler.ts
|
|
4533
|
+
import { GSAP_DEFAULT_DURATION } from "@hyperframes/parsers/gsap-parser";
|
|
4516
4534
|
function absoluteToPercentage(time, tweenStart, tweenDuration) {
|
|
4517
4535
|
if (tweenDuration <= 0) return 0;
|
|
4518
4536
|
const raw = (time - tweenStart) / tweenDuration * 100;
|
|
4519
4537
|
return Math.max(0, Math.min(100, Math.round(raw * 1e3) / 1e3));
|
|
4520
4538
|
}
|
|
4539
|
+
function percentageToAbsolute(pct, tweenStart, tweenDuration) {
|
|
4540
|
+
return tweenStart + pct / 100 * tweenDuration;
|
|
4541
|
+
}
|
|
4521
4542
|
function isTimeWithinTween(time, tweenStart, tweenDuration) {
|
|
4522
4543
|
return time >= tweenStart && time <= tweenStart + tweenDuration;
|
|
4523
4544
|
}
|
|
@@ -4528,8 +4549,8 @@ function resolveTweenStart(animation) {
|
|
|
4528
4549
|
if (!Number.isNaN(parsed)) return parsed;
|
|
4529
4550
|
return null;
|
|
4530
4551
|
}
|
|
4531
|
-
function resolveTweenDuration(animation
|
|
4532
|
-
return animation.duration ??
|
|
4552
|
+
function resolveTweenDuration(animation) {
|
|
4553
|
+
return animation.duration ?? GSAP_DEFAULT_DURATION;
|
|
4533
4554
|
}
|
|
4534
4555
|
function findTweenAtTime(time, animations, selector) {
|
|
4535
4556
|
for (const anim of animations) {
|
|
@@ -4545,7 +4566,13 @@ function absoluteToPercentageForAnimation(time, animation) {
|
|
|
4545
4566
|
const start = resolveTweenStart(animation);
|
|
4546
4567
|
if (start === null) return null;
|
|
4547
4568
|
const duration = resolveTweenDuration(animation);
|
|
4548
|
-
return absoluteToPercentage(time, start, duration);
|
|
4569
|
+
return progressAtTime(runEaseOf(animation), absoluteToPercentage(time, start, duration));
|
|
4570
|
+
}
|
|
4571
|
+
function percentageToAbsoluteForAnimation(pct, animation) {
|
|
4572
|
+
const start = resolveTweenStart(animation);
|
|
4573
|
+
if (start === null) return null;
|
|
4574
|
+
const duration = resolveTweenDuration(animation);
|
|
4575
|
+
return percentageToAbsolute(timeAtProgress(runEaseOf(animation), pct), start, duration);
|
|
4549
4576
|
}
|
|
4550
4577
|
function matchesSelector(tweenSelector, querySelector) {
|
|
4551
4578
|
return tweenSelector.split(",").some((part) => part.trim() === querySelector);
|
|
@@ -4572,6 +4599,11 @@ function isInstantHold(animation) {
|
|
|
4572
4599
|
}
|
|
4573
4600
|
var SAFE_HASH_ID = /^-?[A-Za-z_][\w-]*$/;
|
|
4574
4601
|
var KEYFRAME_PCT_MATCH = 1;
|
|
4602
|
+
function playsNear(anim, percentage, progress) {
|
|
4603
|
+
const runEase = runEaseOf(anim);
|
|
4604
|
+
const gap = timeAtProgress(runEase, percentage) - timeAtProgress(runEase, progress);
|
|
4605
|
+
return Math.abs(gap) <= KEYFRAME_PCT_MATCH;
|
|
4606
|
+
}
|
|
4575
4607
|
function idSelector(id) {
|
|
4576
4608
|
return SAFE_HASH_ID.test(id) ? `#${id}` : `[id="${id.replace(/(["\\])/g, "\\$1")}"]`;
|
|
4577
4609
|
}
|
|
@@ -4682,20 +4714,16 @@ function tweenTargetsElement(targetSelector, selector, element) {
|
|
|
4682
4714
|
if (!element || !doc) return false;
|
|
4683
4715
|
return matchesExactlyOne(doc, targetSelector, element);
|
|
4684
4716
|
}
|
|
4685
|
-
function resolveEditableTweenDuration(animation, selection) {
|
|
4686
|
-
const clipDuration2 = Number.parseFloat(selection.dataAttributes?.duration ?? "");
|
|
4687
|
-
return resolveTweenDuration(
|
|
4688
|
-
animation,
|
|
4689
|
-
Number.isFinite(clipDuration2) && clipDuration2 > 0 ? clipDuration2 : 0.5
|
|
4690
|
-
);
|
|
4691
|
-
}
|
|
4692
4717
|
function computeElementPercentage(currentTime, selection, animation) {
|
|
4693
4718
|
if (animation) {
|
|
4694
4719
|
const start = resolveTweenStart(animation);
|
|
4695
|
-
const duration =
|
|
4720
|
+
const duration = resolveTweenDuration(animation);
|
|
4696
4721
|
if (duration <= 0) return 0;
|
|
4697
4722
|
if (start !== null) {
|
|
4698
|
-
return
|
|
4723
|
+
return progressAtTime(
|
|
4724
|
+
runEaseOf(animation),
|
|
4725
|
+
absoluteToPercentage(currentTime, start, duration)
|
|
4726
|
+
);
|
|
4699
4727
|
}
|
|
4700
4728
|
}
|
|
4701
4729
|
const elStart = Number.parseFloat(selection.dataAttributes?.start ?? "0") || 0;
|
|
@@ -4732,11 +4760,16 @@ function parsePercentageKeyframes(kfObj) {
|
|
|
4732
4760
|
let easeEach;
|
|
4733
4761
|
if (Array.isArray(kfObj)) {
|
|
4734
4762
|
const steps = kfObj;
|
|
4763
|
+
const timing = getObjectArrayKeyframeTiming(
|
|
4764
|
+
steps.map((entry2) => entry2?.duration)
|
|
4765
|
+
);
|
|
4766
|
+
if (!timing) return null;
|
|
4735
4767
|
steps.forEach((entry2, i) => {
|
|
4736
4768
|
if (!entry2 || typeof entry2 !== "object") return;
|
|
4737
|
-
const percentage = steps.length > 1 ? Math.round(i / (steps.length - 1) * 1e3) / 10 : 0;
|
|
4738
4769
|
const properties = collectAnimatableKeyframeProperties(entry2);
|
|
4739
|
-
if (Object.keys(properties).length > 0)
|
|
4770
|
+
if (Object.keys(properties).length > 0) {
|
|
4771
|
+
keyframes.push({ percentage: timing.percentages[i], properties, step: i });
|
|
4772
|
+
}
|
|
4740
4773
|
});
|
|
4741
4774
|
return keyframes.length > 0 ? { keyframes } : null;
|
|
4742
4775
|
}
|
|
@@ -4783,11 +4816,15 @@ function toClipPercentage(absoluteTime, clipStart2, clipDuration2, fallbackPerce
|
|
|
4783
4816
|
}
|
|
4784
4817
|
function toClipKeyframes(source, anim, clipStart2, clipDuration2) {
|
|
4785
4818
|
const tweenStart = anim.resolvedStart ?? (typeof anim.position === "number" ? anim.position : 0);
|
|
4786
|
-
const tweenDuration = anim
|
|
4819
|
+
const tweenDuration = resolveTweenDuration(anim);
|
|
4787
4820
|
return source.map((keyframe) => ({
|
|
4788
4821
|
...keyframe,
|
|
4789
4822
|
percentage: toClipPercentage(
|
|
4790
|
-
toAbsoluteTime(
|
|
4823
|
+
toAbsoluteTime(
|
|
4824
|
+
tweenStart,
|
|
4825
|
+
tweenDuration,
|
|
4826
|
+
timeAtProgress(runEaseOf(anim), keyframe.percentage)
|
|
4827
|
+
),
|
|
4791
4828
|
clipStart2,
|
|
4792
4829
|
clipDuration2,
|
|
4793
4830
|
keyframe.percentage
|
|
@@ -7109,10 +7146,22 @@ function subscribeTimelineKeyframeRetimePreview(source, listener2) {
|
|
|
7109
7146
|
listener2(coordinator.preview);
|
|
7110
7147
|
return () => coordinator.previewListeners.delete(listener2);
|
|
7111
7148
|
}
|
|
7149
|
+
function retimedRunEase(actor) {
|
|
7150
|
+
const id = actor.target.animationId;
|
|
7151
|
+
if (id === void 0) return void 0;
|
|
7152
|
+
const { gsapAnimations } = usePlayerStore.getState();
|
|
7153
|
+
const animations = gsapAnimations.get(actor.elementId) ?? [...gsapAnimations.values()].flat();
|
|
7154
|
+
const animation = animations.find((candidate) => candidate.id === id);
|
|
7155
|
+
return animation ? runEaseOf(animation) : void 0;
|
|
7156
|
+
}
|
|
7112
7157
|
function resolveRetimeTweenPercentage(actor, toClipPercentage2) {
|
|
7113
7158
|
const animationKeyframes2 = actor.target.animationId === void 0 ? actor.keyframes : actor.keyframes.filter((keyframe) => keyframe.animationId === actor.target.animationId);
|
|
7114
7159
|
const tweenPercentages = animationKeyframes2.map((keyframe) => keyframe.tweenPercentage).filter((value) => typeof value === "number");
|
|
7115
|
-
const
|
|
7160
|
+
const runEase = retimedRunEase(actor);
|
|
7161
|
+
const timed = animationKeyframes2.map(
|
|
7162
|
+
(keyframe) => keyframe.tweenPercentage === void 0 ? keyframe : { ...keyframe, tweenPercentage: timeAtProgress(runEase, keyframe.tweenPercentage) }
|
|
7163
|
+
);
|
|
7164
|
+
const mapped = progressAtTime(runEase, clipToTweenPercentage(timed, toClipPercentage2));
|
|
7116
7165
|
if (tweenPercentages.length === 0) return mapped;
|
|
7117
7166
|
return Math.max(Math.min(...tweenPercentages), Math.min(Math.max(...tweenPercentages), mapped));
|
|
7118
7167
|
}
|
|
@@ -7819,6 +7868,7 @@ function getTimelinePropertyLanes(animations, clipStart2, clipDuration2) {
|
|
|
7819
7868
|
return Array.from(sourceGroups(animations), ([group, groupAnimations]) => ({
|
|
7820
7869
|
group,
|
|
7821
7870
|
animations: groupAnimations,
|
|
7871
|
+
elementAnimations: animations,
|
|
7822
7872
|
keyframes: groupKeyframes(groupAnimations, group, clipStart2, clipDuration2)
|
|
7823
7873
|
})).filter((lane) => lane.keyframes.length > 0);
|
|
7824
7874
|
}
|
|
@@ -7829,6 +7879,7 @@ function TimelinePropertyLanes({
|
|
|
7829
7879
|
clipDuration: clipDuration2,
|
|
7830
7880
|
clipLeftPx,
|
|
7831
7881
|
clipWidthPx,
|
|
7882
|
+
passengerStyle,
|
|
7832
7883
|
accentColor,
|
|
7833
7884
|
isSelected: isSelected2,
|
|
7834
7885
|
currentPercentage,
|
|
@@ -7868,7 +7919,8 @@ function TimelinePropertyLanes({
|
|
|
7868
7919
|
left: clipLeftPx,
|
|
7869
7920
|
top: getTimelineLaneTop(laneIndex),
|
|
7870
7921
|
width: clipWidthPx,
|
|
7871
|
-
height: LANE_H
|
|
7922
|
+
height: LANE_H,
|
|
7923
|
+
...passengerStyle
|
|
7872
7924
|
},
|
|
7873
7925
|
children: /* @__PURE__ */ jsx16(
|
|
7874
7926
|
TimelineDiamondLane,
|
|
@@ -9145,6 +9197,13 @@ function computeStackingPatches(elements, editedKeys) {
|
|
|
9145
9197
|
return patches;
|
|
9146
9198
|
}
|
|
9147
9199
|
|
|
9200
|
+
// src/player/store/batchElementUpdates.ts
|
|
9201
|
+
function batchElementUpdates(run) {
|
|
9202
|
+
const patches = collectElementPatches(run);
|
|
9203
|
+
if (!patches || patches.size === 0) return;
|
|
9204
|
+
usePlayerStore.setState((state) => ({ elements: patchElements(state.elements, patches) }));
|
|
9205
|
+
}
|
|
9206
|
+
|
|
9148
9207
|
// src/player/components/timelineOptimisticRevision.ts
|
|
9149
9208
|
var revisionsByUpdater = /* @__PURE__ */ new WeakMap();
|
|
9150
9209
|
function beginTimelineOptimisticGesture(updateElement, keys, scope = "timing") {
|
|
@@ -9170,11 +9229,13 @@ function isLatestTimelineOptimisticGesture(updateElement, gesture, key2, scope =
|
|
|
9170
9229
|
return gesture.has(key2) && revisionsByUpdater.get(updateElement)?.get(scope)?.get(key2) === gesture.get(key2);
|
|
9171
9230
|
}
|
|
9172
9231
|
function rollbackLatestTimelineOptimisticGesture(updateElement, gesture, rollbacks) {
|
|
9173
|
-
|
|
9174
|
-
|
|
9175
|
-
updateElement
|
|
9232
|
+
batchElementUpdates(() => {
|
|
9233
|
+
for (const rollback of rollbacks) {
|
|
9234
|
+
if (isLatestTimelineOptimisticGesture(updateElement, gesture, rollback.key)) {
|
|
9235
|
+
updateElement(rollback.key, rollback.updates);
|
|
9236
|
+
}
|
|
9176
9237
|
}
|
|
9177
|
-
}
|
|
9238
|
+
});
|
|
9178
9239
|
}
|
|
9179
9240
|
|
|
9180
9241
|
// src/components/nle/zLaneGesture.ts
|
|
@@ -9225,6 +9286,400 @@ function refreshAfterDurableLaneMove(pending2, deps) {
|
|
|
9225
9286
|
});
|
|
9226
9287
|
}
|
|
9227
9288
|
|
|
9289
|
+
// src/utils/studioSaveDiagnostics.ts
|
|
9290
|
+
var STUDIO_SAVE_ATTEMPT_PROPERTY = "__studioSaveAttempt";
|
|
9291
|
+
var StudioSaveHttpError = class extends Error {
|
|
9292
|
+
statusCode;
|
|
9293
|
+
alreadyToasted;
|
|
9294
|
+
constructor(message, statusCode, options = {}) {
|
|
9295
|
+
super(message);
|
|
9296
|
+
this.name = "StudioSaveHttpError";
|
|
9297
|
+
this.statusCode = statusCode;
|
|
9298
|
+
this.alreadyToasted = options.alreadyToasted ?? false;
|
|
9299
|
+
}
|
|
9300
|
+
};
|
|
9301
|
+
var StudioSaveNetworkError = class extends Error {
|
|
9302
|
+
constructor(message, options) {
|
|
9303
|
+
super(message, options);
|
|
9304
|
+
this.name = "StudioSaveNetworkError";
|
|
9305
|
+
}
|
|
9306
|
+
};
|
|
9307
|
+
var StudioFileConflictError = class extends StudioSaveHttpError {
|
|
9308
|
+
filePath;
|
|
9309
|
+
currentVersion;
|
|
9310
|
+
currentContent;
|
|
9311
|
+
attemptedContent;
|
|
9312
|
+
constructor(input) {
|
|
9313
|
+
super(`Save conflict: ${input.filePath} changed outside this Studio session`, 409);
|
|
9314
|
+
this.name = "StudioFileConflictError";
|
|
9315
|
+
this.filePath = input.filePath;
|
|
9316
|
+
this.currentVersion = input.currentVersion;
|
|
9317
|
+
this.currentContent = input.currentContent;
|
|
9318
|
+
this.attemptedContent = input.attemptedContent;
|
|
9319
|
+
}
|
|
9320
|
+
};
|
|
9321
|
+
function readNumericProperty(value, key2) {
|
|
9322
|
+
const record = value;
|
|
9323
|
+
const property = record[key2];
|
|
9324
|
+
return typeof property === "number" && Number.isFinite(property) ? property : void 0;
|
|
9325
|
+
}
|
|
9326
|
+
function createStudioSaveAbortError() {
|
|
9327
|
+
if (typeof DOMException !== "undefined") return new DOMException("Save aborted", "AbortError");
|
|
9328
|
+
const error = new Error("Save aborted");
|
|
9329
|
+
error.name = "AbortError";
|
|
9330
|
+
return error;
|
|
9331
|
+
}
|
|
9332
|
+
function throwIfStudioSaveAborted(signal) {
|
|
9333
|
+
if (signal?.aborted) throw createStudioSaveAbortError();
|
|
9334
|
+
}
|
|
9335
|
+
function attachStudioSaveAttempt(error, attempt) {
|
|
9336
|
+
if (!error || typeof error !== "object") return error;
|
|
9337
|
+
try {
|
|
9338
|
+
Object.defineProperty(error, STUDIO_SAVE_ATTEMPT_PROPERTY, {
|
|
9339
|
+
value: attempt,
|
|
9340
|
+
configurable: true
|
|
9341
|
+
});
|
|
9342
|
+
} catch {
|
|
9343
|
+
}
|
|
9344
|
+
return error;
|
|
9345
|
+
}
|
|
9346
|
+
function getStudioSaveErrorMessage(error) {
|
|
9347
|
+
if (error instanceof Error && error.message) return error.message;
|
|
9348
|
+
if (typeof error === "string" && error.trim()) return error;
|
|
9349
|
+
return "Unknown save failure";
|
|
9350
|
+
}
|
|
9351
|
+
function markStudioSaveErrorAlreadyToasted(error) {
|
|
9352
|
+
if (!error || typeof error !== "object") return error;
|
|
9353
|
+
try {
|
|
9354
|
+
Object.defineProperty(error, "alreadyToasted", { value: true, configurable: true });
|
|
9355
|
+
} catch {
|
|
9356
|
+
}
|
|
9357
|
+
return error;
|
|
9358
|
+
}
|
|
9359
|
+
function isStudioSaveErrorAlreadyToasted(error) {
|
|
9360
|
+
if (!error || typeof error !== "object") return false;
|
|
9361
|
+
if (error.alreadyToasted === true) return true;
|
|
9362
|
+
const cause = error.cause;
|
|
9363
|
+
return cause !== error && isStudioSaveErrorAlreadyToasted(cause);
|
|
9364
|
+
}
|
|
9365
|
+
function getStudioSaveStatusCode(error) {
|
|
9366
|
+
if (!error || typeof error !== "object") return void 0;
|
|
9367
|
+
const direct = readNumericProperty(error, "statusCode") ?? readNumericProperty(error, "status") ?? readNumericProperty(error, "status_code");
|
|
9368
|
+
if (direct != null) return direct;
|
|
9369
|
+
const cause = error.cause;
|
|
9370
|
+
if (cause && cause !== error) return getStudioSaveStatusCode(cause);
|
|
9371
|
+
return void 0;
|
|
9372
|
+
}
|
|
9373
|
+
function getStudioSaveAttempt(error) {
|
|
9374
|
+
if (!error || typeof error !== "object") return void 0;
|
|
9375
|
+
const direct = readNumericProperty(error, STUDIO_SAVE_ATTEMPT_PROPERTY);
|
|
9376
|
+
if (direct != null) return direct;
|
|
9377
|
+
const cause = error.cause;
|
|
9378
|
+
if (cause && cause !== error) return getStudioSaveAttempt(cause);
|
|
9379
|
+
return void 0;
|
|
9380
|
+
}
|
|
9381
|
+
function isStudioSaveAbortError(error) {
|
|
9382
|
+
return error instanceof Error && error.name === "AbortError";
|
|
9383
|
+
}
|
|
9384
|
+
function isRetryableStudioSaveError(error) {
|
|
9385
|
+
if (isStudioSaveAbortError(error)) return false;
|
|
9386
|
+
if (error instanceof StudioSaveNetworkError) return true;
|
|
9387
|
+
const statusCode = getStudioSaveStatusCode(error);
|
|
9388
|
+
if (statusCode == null) return false;
|
|
9389
|
+
return statusCode === 408 || statusCode === 425 || statusCode === 429 || statusCode >= 500;
|
|
9390
|
+
}
|
|
9391
|
+
function buildStudioSaveFailureProperties(input) {
|
|
9392
|
+
const statusCode = input.statusCode ?? getStudioSaveStatusCode(input.error) ?? null;
|
|
9393
|
+
const attempt = input.attempt ?? getStudioSaveAttempt(input.error) ?? void 0;
|
|
9394
|
+
return {
|
|
9395
|
+
source: input.source,
|
|
9396
|
+
error_message: getStudioSaveErrorMessage(input.error),
|
|
9397
|
+
status_code: statusCode,
|
|
9398
|
+
file_path: input.filePath ?? input.targetSourceFile ?? void 0,
|
|
9399
|
+
mutation_type: input.mutationType ?? void 0,
|
|
9400
|
+
attempt,
|
|
9401
|
+
label: input.label ?? void 0,
|
|
9402
|
+
target_id: input.targetId ?? void 0,
|
|
9403
|
+
target_selector: input.targetSelector ?? void 0,
|
|
9404
|
+
target_source_file: input.targetSourceFile ?? void 0,
|
|
9405
|
+
block_detail: gsapBlockDetail(input.error)
|
|
9406
|
+
};
|
|
9407
|
+
}
|
|
9408
|
+
function gsapBlockDetail(error) {
|
|
9409
|
+
if (!(error instanceof Error) || error.name !== "GsapEditBlockedError") return void 0;
|
|
9410
|
+
const detail = error.detail;
|
|
9411
|
+
return typeof detail === "string" ? detail : void 0;
|
|
9412
|
+
}
|
|
9413
|
+
function trackStudioSaveFailure(input) {
|
|
9414
|
+
trackStudioEvent("save_failure", buildStudioSaveFailureProperties(input));
|
|
9415
|
+
}
|
|
9416
|
+
function trackStudioEditBlocked(input) {
|
|
9417
|
+
trackStudioEvent("edit_blocked", buildStudioSaveFailureProperties(input));
|
|
9418
|
+
}
|
|
9419
|
+
async function createStudioSaveHttpError(response, fallbackMessage, options = {}) {
|
|
9420
|
+
let body = "";
|
|
9421
|
+
try {
|
|
9422
|
+
body = await response.text();
|
|
9423
|
+
} catch {
|
|
9424
|
+
body = "";
|
|
9425
|
+
}
|
|
9426
|
+
const detail = body.trim().slice(0, 300);
|
|
9427
|
+
const message = detail ? `${fallbackMessage} (${response.status}): ${detail}` : `${fallbackMessage} (${response.status})`;
|
|
9428
|
+
return new StudioSaveHttpError(message, response.status, options);
|
|
9429
|
+
}
|
|
9430
|
+
async function retryStudioSave(operation, options = {}) {
|
|
9431
|
+
const retries = options.retries ?? 3;
|
|
9432
|
+
const baseDelayMs = options.baseDelayMs ?? 500;
|
|
9433
|
+
const maxDelayMs = options.maxDelayMs ?? 8e3;
|
|
9434
|
+
const jitterRatio = options.jitterRatio ?? 0.25;
|
|
9435
|
+
const random = options.random ?? Math.random;
|
|
9436
|
+
const shouldRetry = options.shouldRetry ?? isRetryableStudioSaveError;
|
|
9437
|
+
const sleep = options.sleep ?? ((delayMs, signal) => new Promise((resolve, reject) => {
|
|
9438
|
+
throwIfStudioSaveAborted(signal);
|
|
9439
|
+
const onAbort = () => {
|
|
9440
|
+
globalThis.clearTimeout(timeout);
|
|
9441
|
+
reject(createStudioSaveAbortError());
|
|
9442
|
+
};
|
|
9443
|
+
const timeout = globalThis.setTimeout(() => {
|
|
9444
|
+
signal?.removeEventListener("abort", onAbort);
|
|
9445
|
+
resolve();
|
|
9446
|
+
}, delayMs);
|
|
9447
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
9448
|
+
}));
|
|
9449
|
+
const maxAttempts = retries + 1;
|
|
9450
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
9451
|
+
try {
|
|
9452
|
+
throwIfStudioSaveAborted(options.signal);
|
|
9453
|
+
return await operation(attempt);
|
|
9454
|
+
} catch (error) {
|
|
9455
|
+
const failure = attachStudioSaveAttempt(error, attempt);
|
|
9456
|
+
if (attempt >= maxAttempts || !shouldRetry(failure, attempt)) throw failure;
|
|
9457
|
+
const retryIndex = attempt - 1;
|
|
9458
|
+
const exponentialDelay = Math.min(baseDelayMs * 2 ** retryIndex, maxDelayMs);
|
|
9459
|
+
const jitterSpan = exponentialDelay * jitterRatio;
|
|
9460
|
+
const jitteredDelay = Math.round(exponentialDelay + (random() * 2 - 1) * jitterSpan);
|
|
9461
|
+
const delayMs = Math.max(0, Math.min(maxDelayMs, jitteredDelay));
|
|
9462
|
+
await sleep(delayMs, options.signal);
|
|
9463
|
+
}
|
|
9464
|
+
}
|
|
9465
|
+
throw new Error("Save retry loop exited unexpectedly");
|
|
9466
|
+
}
|
|
9467
|
+
|
|
9468
|
+
// src/utils/typingTarget.ts
|
|
9469
|
+
function isTypingTarget(target) {
|
|
9470
|
+
const element = asElement(target);
|
|
9471
|
+
if (!element) return false;
|
|
9472
|
+
if (element.isContentEditable) return true;
|
|
9473
|
+
return element.closest(TYPING_SELECTOR) !== null;
|
|
9474
|
+
}
|
|
9475
|
+
function ownsPlainKeys(target) {
|
|
9476
|
+
return isTypingTarget(target) || asElement(target)?.closest(KEY_OWNING_CONTROLS) != null;
|
|
9477
|
+
}
|
|
9478
|
+
var KEY_OWNING_CONTROLS = "[role='slider'],video[controls],audio[controls]";
|
|
9479
|
+
var TYPING_SELECTOR = [
|
|
9480
|
+
"input",
|
|
9481
|
+
"textarea",
|
|
9482
|
+
"select",
|
|
9483
|
+
"[contenteditable]:not([contenteditable='false'])",
|
|
9484
|
+
"[role='textbox']",
|
|
9485
|
+
"[role='searchbox']",
|
|
9486
|
+
"[role='combobox']",
|
|
9487
|
+
// A switch is a button in the DOM, but the native checkbox it replaces was an
|
|
9488
|
+
// `input` and matched above. Without this row the shared Toggle would let a
|
|
9489
|
+
// global shortcut claim Space instead of flipping the control (KTD13).
|
|
9490
|
+
"[role='switch']",
|
|
9491
|
+
"[role='dialog'][aria-modal='true']",
|
|
9492
|
+
".cm-editor"
|
|
9493
|
+
].join(",");
|
|
9494
|
+
function asElement(target) {
|
|
9495
|
+
if (!target || typeof target !== "object") return null;
|
|
9496
|
+
const candidate = target;
|
|
9497
|
+
return typeof candidate.closest === "function" ? target : null;
|
|
9498
|
+
}
|
|
9499
|
+
|
|
9500
|
+
// src/utils/studioPendingEdits.ts
|
|
9501
|
+
var STUDIO_FLUSH_PENDING_EDITS_EVENT = "hf-studio-flush-pending-edits";
|
|
9502
|
+
var pendingEdits = /* @__PURE__ */ new Map();
|
|
9503
|
+
var historyClaims = () => 0;
|
|
9504
|
+
function setStudioPendingEditClaimClock(read2) {
|
|
9505
|
+
historyClaims = read2 ?? (() => 0);
|
|
9506
|
+
}
|
|
9507
|
+
var NOT_SAVED = () => Promise.resolve(false);
|
|
9508
|
+
var adopting = null;
|
|
9509
|
+
function adoptingStudioPendingEdit() {
|
|
9510
|
+
return adopting;
|
|
9511
|
+
}
|
|
9512
|
+
function waitForPostBlurEffects() {
|
|
9513
|
+
return new Promise((resolve) => setTimeout(resolve, 0));
|
|
9514
|
+
}
|
|
9515
|
+
function inspectDrainFailures(results) {
|
|
9516
|
+
let firstFailure;
|
|
9517
|
+
for (const result of results) {
|
|
9518
|
+
if (result.status !== "rejected") continue;
|
|
9519
|
+
if (result.reason instanceof StudioFileConflictError) return { conflict: result.reason };
|
|
9520
|
+
firstFailure ??= result;
|
|
9521
|
+
}
|
|
9522
|
+
return { firstFailure };
|
|
9523
|
+
}
|
|
9524
|
+
function focusedField() {
|
|
9525
|
+
const active = document.activeElement;
|
|
9526
|
+
return active instanceof HTMLElement && isTypingTarget(active) ? active : null;
|
|
9527
|
+
}
|
|
9528
|
+
function hasStudioPendingEdits() {
|
|
9529
|
+
return pendingEdits.size > 0 || focusedField() !== null;
|
|
9530
|
+
}
|
|
9531
|
+
function isStudioEditSaving() {
|
|
9532
|
+
return pendingEdits.size > 0;
|
|
9533
|
+
}
|
|
9534
|
+
function afterStudioPendingEdits(run) {
|
|
9535
|
+
let waiting = true;
|
|
9536
|
+
const check = () => {
|
|
9537
|
+
if (!waiting) return;
|
|
9538
|
+
if (isStudioEditSaving()) {
|
|
9539
|
+
void Promise.allSettled([...pendingEdits.keys()]).then(check);
|
|
9540
|
+
return;
|
|
9541
|
+
}
|
|
9542
|
+
waiting = false;
|
|
9543
|
+
run();
|
|
9544
|
+
};
|
|
9545
|
+
check();
|
|
9546
|
+
return () => {
|
|
9547
|
+
waiting = false;
|
|
9548
|
+
};
|
|
9549
|
+
}
|
|
9550
|
+
function trackStudioPendingEdit(result) {
|
|
9551
|
+
if (!result) return void 0;
|
|
9552
|
+
const promise = Promise.resolve(result);
|
|
9553
|
+
if (adopting) return promise;
|
|
9554
|
+
pendingEdits.set(promise, {
|
|
9555
|
+
revert: null,
|
|
9556
|
+
landed: NOT_SAVED,
|
|
9557
|
+
redraws: [],
|
|
9558
|
+
showAgain: null,
|
|
9559
|
+
claimsAtBegin: historyClaims()
|
|
9560
|
+
});
|
|
9561
|
+
promise.then(
|
|
9562
|
+
() => pendingEdits.delete(promise),
|
|
9563
|
+
() => pendingEdits.delete(promise)
|
|
9564
|
+
);
|
|
9565
|
+
return promise;
|
|
9566
|
+
}
|
|
9567
|
+
function trackedStudioEdit(edit) {
|
|
9568
|
+
return (...args) => {
|
|
9569
|
+
const result = edit(...args);
|
|
9570
|
+
if (result instanceof Promise) trackStudioPendingEdit(result);
|
|
9571
|
+
return result;
|
|
9572
|
+
};
|
|
9573
|
+
}
|
|
9574
|
+
function beginStudioPendingEdit(revert) {
|
|
9575
|
+
let settle;
|
|
9576
|
+
const promise = trackStudioPendingEdit(new Promise((resolve) => settle = resolve));
|
|
9577
|
+
const entry2 = pendingEdits.get(promise);
|
|
9578
|
+
entry2.revert = revert;
|
|
9579
|
+
let landed = Promise.resolve(false);
|
|
9580
|
+
let saved2 = false;
|
|
9581
|
+
entry2.landed = () => landed;
|
|
9582
|
+
const inFlight = {
|
|
9583
|
+
reverted: () => entry2.revert === null && revert !== null,
|
|
9584
|
+
within(run) {
|
|
9585
|
+
const outer = adopting;
|
|
9586
|
+
adopting = inFlight;
|
|
9587
|
+
try {
|
|
9588
|
+
return run();
|
|
9589
|
+
} finally {
|
|
9590
|
+
adopting = outer;
|
|
9591
|
+
}
|
|
9592
|
+
},
|
|
9593
|
+
drawUnlessUndone(draw) {
|
|
9594
|
+
if (inFlight.reverted()) entry2.redraws.push(draw);
|
|
9595
|
+
else draw();
|
|
9596
|
+
},
|
|
9597
|
+
drawKeepingUndone(draw) {
|
|
9598
|
+
if (!inFlight.reverted()) return draw();
|
|
9599
|
+
entry2.showAgain?.();
|
|
9600
|
+
try {
|
|
9601
|
+
return draw();
|
|
9602
|
+
} finally {
|
|
9603
|
+
entry2.showAgain = revert();
|
|
9604
|
+
}
|
|
9605
|
+
},
|
|
9606
|
+
markSaved: () => void (saved2 = true)
|
|
9607
|
+
};
|
|
9608
|
+
return {
|
|
9609
|
+
settle,
|
|
9610
|
+
reverted: inFlight.reverted,
|
|
9611
|
+
// Only what `start` registers synchronously is adopted; a later write joins only through `within`.
|
|
9612
|
+
adopt(start) {
|
|
9613
|
+
try {
|
|
9614
|
+
const committed = inFlight.within(start);
|
|
9615
|
+
landed = Promise.resolve(committed).then(
|
|
9616
|
+
() => true,
|
|
9617
|
+
() => saved2
|
|
9618
|
+
);
|
|
9619
|
+
return committed;
|
|
9620
|
+
} catch (error) {
|
|
9621
|
+
settle();
|
|
9622
|
+
throw error;
|
|
9623
|
+
}
|
|
9624
|
+
}
|
|
9625
|
+
};
|
|
9626
|
+
}
|
|
9627
|
+
function paintBackNewestStudioPendingEdit() {
|
|
9628
|
+
const newest = [...pendingEdits.values()].at(-1);
|
|
9629
|
+
const revert = newest?.revert;
|
|
9630
|
+
if (!newest || !revert) return null;
|
|
9631
|
+
newest.revert = null;
|
|
9632
|
+
newest.showAgain = revert();
|
|
9633
|
+
return {
|
|
9634
|
+
showAgain: () => {
|
|
9635
|
+
newest.showAgain?.();
|
|
9636
|
+
for (const redraw of newest.redraws.splice(0)) redraw();
|
|
9637
|
+
},
|
|
9638
|
+
landed: newest.landed,
|
|
9639
|
+
claimsAtBegin: newest.claimsAtBegin
|
|
9640
|
+
};
|
|
9641
|
+
}
|
|
9642
|
+
function revertNewestStudioPendingEdit() {
|
|
9643
|
+
return paintBackNewestStudioPendingEdit()?.showAgain ?? null;
|
|
9644
|
+
}
|
|
9645
|
+
async function flushStudioPendingEdits({
|
|
9646
|
+
onlyCurrent = false
|
|
9647
|
+
} = {}) {
|
|
9648
|
+
const active = focusedField();
|
|
9649
|
+
if (active) {
|
|
9650
|
+
active.blur();
|
|
9651
|
+
await Promise.resolve();
|
|
9652
|
+
await waitForPostBlurEffects();
|
|
9653
|
+
}
|
|
9654
|
+
const detail = { promises: [] };
|
|
9655
|
+
window.dispatchEvent(
|
|
9656
|
+
new CustomEvent(STUDIO_FLUSH_PENDING_EDITS_EVENT, { detail })
|
|
9657
|
+
);
|
|
9658
|
+
const current = onlyCurrent ? new Set(pendingEdits.keys()) : null;
|
|
9659
|
+
const waiting = () => [...pendingEdits.keys()].filter((edit) => !current || current.has(edit));
|
|
9660
|
+
let conflict;
|
|
9661
|
+
let firstFailure;
|
|
9662
|
+
while (detail.promises.length > 0 || waiting().length > 0) {
|
|
9663
|
+
const promises = [...detail.promises, ...waiting()];
|
|
9664
|
+
detail.promises = [];
|
|
9665
|
+
const batchFailures = inspectDrainFailures(await Promise.allSettled(promises));
|
|
9666
|
+
conflict ??= batchFailures.conflict;
|
|
9667
|
+
firstFailure ??= batchFailures.firstFailure;
|
|
9668
|
+
}
|
|
9669
|
+
if (conflict) return { status: "conflict", error: conflict };
|
|
9670
|
+
return firstFailure ? { status: "failed", error: firstFailure.reason } : { status: "clean" };
|
|
9671
|
+
}
|
|
9672
|
+
function addStudioPendingEditFlushListener(handler) {
|
|
9673
|
+
const listener2 = (event) => {
|
|
9674
|
+
const detail = event.detail;
|
|
9675
|
+
if (!detail?.promises) return;
|
|
9676
|
+
const promise = trackStudioPendingEdit(handler());
|
|
9677
|
+
if (promise) detail.promises.push(promise);
|
|
9678
|
+
};
|
|
9679
|
+
window.addEventListener(STUDIO_FLUSH_PENDING_EDITS_EVENT, listener2);
|
|
9680
|
+
return () => window.removeEventListener(STUDIO_FLUSH_PENDING_EDITS_EVENT, listener2);
|
|
9681
|
+
}
|
|
9682
|
+
|
|
9228
9683
|
// src/player/components/timelineClipDragCommit.ts
|
|
9229
9684
|
var keyOf6 = (e) => e.key ?? e.id;
|
|
9230
9685
|
var round3 = (v) => Math.round(v * 1e3) / 1e3;
|
|
@@ -9237,11 +9692,11 @@ function moveStoreUpdates(edit) {
|
|
|
9237
9692
|
detach: audioGroup === null
|
|
9238
9693
|
};
|
|
9239
9694
|
}
|
|
9240
|
-
function persistMoveEdits(edits, deps, coalesceKey, operation = "timing"
|
|
9695
|
+
function persistMoveEdits(edits, deps, coalesceKey, operation = "timing") {
|
|
9241
9696
|
if (edits.length === 0) return Promise.resolve(true);
|
|
9242
9697
|
edits = edits.map((e) => {
|
|
9243
|
-
const
|
|
9244
|
-
return
|
|
9698
|
+
const start2 = clampToHostStart(e.element, e.updates.start);
|
|
9699
|
+
return start2 === e.updates.start ? e : { ...e, updates: { ...e.updates, start: start2 } };
|
|
9245
9700
|
});
|
|
9246
9701
|
const { updateElement, onMoveElement, onMoveElements } = deps;
|
|
9247
9702
|
if (!onMoveElements) {
|
|
@@ -9279,31 +9734,44 @@ function persistMoveEdits(edits, deps, coalesceKey, operation = "timing", coales
|
|
|
9279
9734
|
}
|
|
9280
9735
|
if (Object.keys(updates).length) updateElement(key2, updates);
|
|
9281
9736
|
};
|
|
9282
|
-
|
|
9737
|
+
const applyEdits = (reassert = false) => batchElementUpdates(() => edits.forEach((e) => applyEdit(e, reassert)));
|
|
9738
|
+
applyEdits();
|
|
9283
9739
|
const persistEdits = edits.map(
|
|
9284
9740
|
(e) => e.persistTrack == null || e.persistTrack === e.updates.track ? e : { element: e.element, updates: { ...e.updates, track: e.persistTrack } }
|
|
9285
9741
|
);
|
|
9286
|
-
const
|
|
9287
|
-
|
|
9742
|
+
const restorePrev = () => batchElementUpdates(() => {
|
|
9743
|
+
for (const p of prev) {
|
|
9744
|
+
const { audioGroup, ...timing } = p.updates;
|
|
9745
|
+
const updates = {};
|
|
9746
|
+
if (isLatestTimelineOptimisticGesture(updateElement, revision, p.key)) {
|
|
9747
|
+
Object.assign(updates, timing);
|
|
9748
|
+
}
|
|
9749
|
+
if (isLatestTimelineOptimisticGesture(updateElement, membershipRevision, p.key, "membership"))
|
|
9750
|
+
updates.audioGroup = audioGroup;
|
|
9751
|
+
if (Object.keys(updates).length) updateElement(p.key, updates);
|
|
9752
|
+
}
|
|
9753
|
+
});
|
|
9754
|
+
const saving = onMoveElements ? beginStudioPendingEdit(() => {
|
|
9755
|
+
restorePrev();
|
|
9756
|
+
return () => applyEdits(true);
|
|
9757
|
+
}) : null;
|
|
9758
|
+
const start = () => onMoveElements ? onMoveElements(persistEdits, coalesceKey, operation) : Promise.all(
|
|
9759
|
+
persistEdits.map((e) => Promise.resolve(onMoveElement?.(e.element, e.updates)))
|
|
9760
|
+
);
|
|
9761
|
+
const persisted = saving ? saving.adopt(start) : start();
|
|
9762
|
+
const done = Promise.resolve(persisted).then(
|
|
9288
9763
|
() => {
|
|
9289
|
-
|
|
9764
|
+
if (!saving?.reverted()) applyEdits(true);
|
|
9290
9765
|
return true;
|
|
9291
9766
|
},
|
|
9292
9767
|
(error) => {
|
|
9293
|
-
|
|
9294
|
-
const { audioGroup, ...timing } = p.updates;
|
|
9295
|
-
const updates = {};
|
|
9296
|
-
if (isLatestTimelineOptimisticGesture(updateElement, revision, p.key)) {
|
|
9297
|
-
Object.assign(updates, timing);
|
|
9298
|
-
}
|
|
9299
|
-
if (isLatestTimelineOptimisticGesture(updateElement, membershipRevision, p.key, "membership"))
|
|
9300
|
-
updates.audioGroup = audioGroup;
|
|
9301
|
-
if (Object.keys(updates).length) updateElement(p.key, updates);
|
|
9302
|
-
}
|
|
9768
|
+
if (!saving?.reverted()) restorePrev();
|
|
9303
9769
|
console.error("[Timeline] Failed to persist clip edits", error);
|
|
9304
9770
|
return false;
|
|
9305
9771
|
}
|
|
9306
9772
|
);
|
|
9773
|
+
saving?.settle(done);
|
|
9774
|
+
return done;
|
|
9307
9775
|
}
|
|
9308
9776
|
function resolveMultiSelection(drag, deps) {
|
|
9309
9777
|
const dragKey = keyOf6(drag.element);
|
|
@@ -9488,7 +9956,7 @@ function commitTrackInsert(drag, deps, multi) {
|
|
|
9488
9956
|
deps
|
|
9489
9957
|
).catch(() => void 0);
|
|
9490
9958
|
}
|
|
9491
|
-
function commitZMirrorLaneMove(element, move, deps, coalesceKey
|
|
9959
|
+
function commitZMirrorLaneMove(element, move, deps, coalesceKey) {
|
|
9492
9960
|
if (move.kind === "move") {
|
|
9493
9961
|
const edit = {
|
|
9494
9962
|
element,
|
|
@@ -9496,14 +9964,14 @@ function commitZMirrorLaneMove(element, move, deps, coalesceKey, coalesceMs) {
|
|
|
9496
9964
|
persistTrack: move.persistTrack
|
|
9497
9965
|
};
|
|
9498
9966
|
return refreshAfterDurableLaneMove(
|
|
9499
|
-
persistMoveEdits([edit], deps, coalesceKey, "lane-reorder"
|
|
9967
|
+
persistMoveEdits([edit], deps, coalesceKey, "lane-reorder"),
|
|
9500
9968
|
deps
|
|
9501
9969
|
);
|
|
9502
9970
|
}
|
|
9503
9971
|
const built = buildTrackInsertEdits(element, element.start, move.insertRow, null, deps);
|
|
9504
9972
|
if (!built || built.edits.length === 0) return Promise.resolve(false);
|
|
9505
9973
|
return refreshAfterDurableLaneMove(
|
|
9506
|
-
persistMoveEdits(built.edits, deps, coalesceKey, "track-insert"
|
|
9974
|
+
persistMoveEdits(built.edits, deps, coalesceKey, "track-insert"),
|
|
9507
9975
|
deps
|
|
9508
9976
|
);
|
|
9509
9977
|
}
|
|
@@ -9539,13 +10007,15 @@ function commitTimelineGroupResize(session, updateElement, persist2) {
|
|
|
9539
10007
|
updateElement,
|
|
9540
10008
|
changes.map((change) => change.key)
|
|
9541
10009
|
);
|
|
9542
|
-
|
|
9543
|
-
|
|
9544
|
-
|
|
9545
|
-
|
|
9546
|
-
|
|
9547
|
-
|
|
9548
|
-
|
|
10010
|
+
batchElementUpdates(() => {
|
|
10011
|
+
for (const change of changes) {
|
|
10012
|
+
updateElement(change.key, {
|
|
10013
|
+
start: change.start,
|
|
10014
|
+
duration: change.duration,
|
|
10015
|
+
playbackStart: change.playbackStart
|
|
10016
|
+
});
|
|
10017
|
+
}
|
|
10018
|
+
});
|
|
9549
10019
|
if (!persist2) {
|
|
9550
10020
|
rollbackLatestTimelineOptimisticGesture(
|
|
9551
10021
|
updateElement,
|
|
@@ -9554,7 +10024,6 @@ function commitTimelineGroupResize(session, updateElement, persist2) {
|
|
|
9554
10024
|
);
|
|
9555
10025
|
return;
|
|
9556
10026
|
}
|
|
9557
|
-
const coalesceKey = `clip-group-resize:${changes.map((change) => change.key).join(":")}`;
|
|
9558
10027
|
Promise.resolve(
|
|
9559
10028
|
persist2(
|
|
9560
10029
|
changes.map((change) => ({
|
|
@@ -9562,8 +10031,7 @@ function commitTimelineGroupResize(session, updateElement, persist2) {
|
|
|
9562
10031
|
start: change.start,
|
|
9563
10032
|
duration: change.duration,
|
|
9564
10033
|
playbackStart: change.playbackStart
|
|
9565
|
-
}))
|
|
9566
|
-
{ coalesceKey }
|
|
10034
|
+
}))
|
|
9567
10035
|
)
|
|
9568
10036
|
).catch((error) => {
|
|
9569
10037
|
rollbackLatestTimelineOptimisticGesture(
|
|
@@ -9684,7 +10152,7 @@ function mountTimelineClipDragGestureLifecycle({
|
|
|
9684
10152
|
event.clientX - blocked.originClientX,
|
|
9685
10153
|
event.clientY - blocked.originClientY
|
|
9686
10154
|
);
|
|
9687
|
-
const threshold = blocked.intent === "move"
|
|
10155
|
+
const threshold = blocked.intent === "move" ? 4 : 2;
|
|
9688
10156
|
if (!blocked.started && distance < threshold) return;
|
|
9689
10157
|
if (!blocked.started) {
|
|
9690
10158
|
blocked.started = true;
|
|
@@ -10273,8 +10741,6 @@ function useTimelineClipDrag({
|
|
|
10273
10741
|
if (!element || !getClipCapabilities(element).canMove) return;
|
|
10274
10742
|
event.preventDefault();
|
|
10275
10743
|
event.stopImmediatePropagation();
|
|
10276
|
-
if (exceedsHandEditLimit(element, false, null))
|
|
10277
|
-
return void onBlockedEditAttemptRef.current?.(element, "edit-many");
|
|
10278
10744
|
setShowPopover(false);
|
|
10279
10745
|
setRangeSelectionRef.current?.(null);
|
|
10280
10746
|
const row = keyboardPickupInsertRow(
|
|
@@ -10544,6 +11010,36 @@ function useTimelineDragPreviewState(draggedClip, resizingClip, selectedElementI
|
|
|
10544
11010
|
|
|
10545
11011
|
// src/player/components/useTimelineClipContextMenu.ts
|
|
10546
11012
|
import { useCallback as useCallback12 } from "react";
|
|
11013
|
+
|
|
11014
|
+
// src/player/components/timelineLinkSelection.ts
|
|
11015
|
+
function selectClipWithLinks(key2, altKey, setSelectedElementId) {
|
|
11016
|
+
const state = usePlayerStore.getState();
|
|
11017
|
+
if (altKey || !isLinkedSelectionOn()) {
|
|
11018
|
+
state.setSelection([key2], key2);
|
|
11019
|
+
return;
|
|
11020
|
+
}
|
|
11021
|
+
setSelectedElementId(key2);
|
|
11022
|
+
const { selectedElementIds, elements } = usePlayerStore.getState();
|
|
11023
|
+
const expanded = expandToLinkedMembers(selectedElementIds, elements);
|
|
11024
|
+
if (expanded.size > selectedElementIds.size) state.setSelection(expanded, key2);
|
|
11025
|
+
}
|
|
11026
|
+
function toggleClipWithLinks(key2, altKey) {
|
|
11027
|
+
const state = usePlayerStore.getState();
|
|
11028
|
+
const element = state.elements.find((el) => (el.key ?? el.id) === key2);
|
|
11029
|
+
const members = element ? linkedMembersOf(element, state.elements, !altKey && isLinkedSelectionOn()) : [];
|
|
11030
|
+
const memberKeys = members.length > 0 ? members.map((el) => el.key ?? el.id) : [key2];
|
|
11031
|
+
const next = new Set(state.selectedElementIds);
|
|
11032
|
+
const removing = next.has(key2);
|
|
11033
|
+
for (const memberKey of memberKeys) {
|
|
11034
|
+
if (removing) next.delete(memberKey);
|
|
11035
|
+
else next.add(memberKey);
|
|
11036
|
+
}
|
|
11037
|
+
state.setSelection(next, removing ? state.selectedElementId : key2);
|
|
11038
|
+
const primary = usePlayerStore.getState().selectedElementId;
|
|
11039
|
+
return state.elements.find((el) => (el.key ?? el.id) === primary) ?? null;
|
|
11040
|
+
}
|
|
11041
|
+
|
|
11042
|
+
// src/player/components/useTimelineClipContextMenu.ts
|
|
10547
11043
|
function useClipContextMenu(onSelectElement, dismissGapMenu, setClipContextMenu) {
|
|
10548
11044
|
const selectedElementIds = usePlayerStore((s) => s.selectedElementIds);
|
|
10549
11045
|
const setSelectedElementId = usePlayerStore((s) => s.setSelectedElementId);
|
|
@@ -11620,391 +12116,6 @@ import { useCallback as useCallback17 } from "react";
|
|
|
11620
12116
|
|
|
11621
12117
|
// src/contexts/DomEditContext.tsx
|
|
11622
12118
|
import { useCallback as useCallback16, useContext as useContext3, useMemo as useMemo12, useRef as useRef17 } from "react";
|
|
11623
|
-
|
|
11624
|
-
// src/utils/studioSaveDiagnostics.ts
|
|
11625
|
-
var STUDIO_SAVE_ATTEMPT_PROPERTY = "__studioSaveAttempt";
|
|
11626
|
-
var StudioSaveHttpError = class extends Error {
|
|
11627
|
-
statusCode;
|
|
11628
|
-
alreadyToasted;
|
|
11629
|
-
constructor(message, statusCode, options = {}) {
|
|
11630
|
-
super(message);
|
|
11631
|
-
this.name = "StudioSaveHttpError";
|
|
11632
|
-
this.statusCode = statusCode;
|
|
11633
|
-
this.alreadyToasted = options.alreadyToasted ?? false;
|
|
11634
|
-
}
|
|
11635
|
-
};
|
|
11636
|
-
var StudioSaveNetworkError = class extends Error {
|
|
11637
|
-
constructor(message, options) {
|
|
11638
|
-
super(message, options);
|
|
11639
|
-
this.name = "StudioSaveNetworkError";
|
|
11640
|
-
}
|
|
11641
|
-
};
|
|
11642
|
-
var StudioFileConflictError = class extends StudioSaveHttpError {
|
|
11643
|
-
filePath;
|
|
11644
|
-
currentVersion;
|
|
11645
|
-
currentContent;
|
|
11646
|
-
attemptedContent;
|
|
11647
|
-
constructor(input) {
|
|
11648
|
-
super(`Save conflict: ${input.filePath} changed outside this Studio session`, 409);
|
|
11649
|
-
this.name = "StudioFileConflictError";
|
|
11650
|
-
this.filePath = input.filePath;
|
|
11651
|
-
this.currentVersion = input.currentVersion;
|
|
11652
|
-
this.currentContent = input.currentContent;
|
|
11653
|
-
this.attemptedContent = input.attemptedContent;
|
|
11654
|
-
}
|
|
11655
|
-
};
|
|
11656
|
-
function readNumericProperty(value, key2) {
|
|
11657
|
-
const record = value;
|
|
11658
|
-
const property = record[key2];
|
|
11659
|
-
return typeof property === "number" && Number.isFinite(property) ? property : void 0;
|
|
11660
|
-
}
|
|
11661
|
-
function createStudioSaveAbortError() {
|
|
11662
|
-
if (typeof DOMException !== "undefined") return new DOMException("Save aborted", "AbortError");
|
|
11663
|
-
const error = new Error("Save aborted");
|
|
11664
|
-
error.name = "AbortError";
|
|
11665
|
-
return error;
|
|
11666
|
-
}
|
|
11667
|
-
function throwIfStudioSaveAborted(signal) {
|
|
11668
|
-
if (signal?.aborted) throw createStudioSaveAbortError();
|
|
11669
|
-
}
|
|
11670
|
-
function attachStudioSaveAttempt(error, attempt) {
|
|
11671
|
-
if (!error || typeof error !== "object") return error;
|
|
11672
|
-
try {
|
|
11673
|
-
Object.defineProperty(error, STUDIO_SAVE_ATTEMPT_PROPERTY, {
|
|
11674
|
-
value: attempt,
|
|
11675
|
-
configurable: true
|
|
11676
|
-
});
|
|
11677
|
-
} catch {
|
|
11678
|
-
}
|
|
11679
|
-
return error;
|
|
11680
|
-
}
|
|
11681
|
-
function getStudioSaveErrorMessage(error) {
|
|
11682
|
-
if (error instanceof Error && error.message) return error.message;
|
|
11683
|
-
if (typeof error === "string" && error.trim()) return error;
|
|
11684
|
-
return "Unknown save failure";
|
|
11685
|
-
}
|
|
11686
|
-
function markStudioSaveErrorAlreadyToasted(error) {
|
|
11687
|
-
if (!error || typeof error !== "object") return error;
|
|
11688
|
-
try {
|
|
11689
|
-
Object.defineProperty(error, "alreadyToasted", { value: true, configurable: true });
|
|
11690
|
-
} catch {
|
|
11691
|
-
}
|
|
11692
|
-
return error;
|
|
11693
|
-
}
|
|
11694
|
-
function isStudioSaveErrorAlreadyToasted(error) {
|
|
11695
|
-
if (!error || typeof error !== "object") return false;
|
|
11696
|
-
if (error.alreadyToasted === true) return true;
|
|
11697
|
-
const cause = error.cause;
|
|
11698
|
-
return cause !== error && isStudioSaveErrorAlreadyToasted(cause);
|
|
11699
|
-
}
|
|
11700
|
-
function getStudioSaveStatusCode(error) {
|
|
11701
|
-
if (!error || typeof error !== "object") return void 0;
|
|
11702
|
-
const direct = readNumericProperty(error, "statusCode") ?? readNumericProperty(error, "status") ?? readNumericProperty(error, "status_code");
|
|
11703
|
-
if (direct != null) return direct;
|
|
11704
|
-
const cause = error.cause;
|
|
11705
|
-
if (cause && cause !== error) return getStudioSaveStatusCode(cause);
|
|
11706
|
-
return void 0;
|
|
11707
|
-
}
|
|
11708
|
-
function getStudioSaveAttempt(error) {
|
|
11709
|
-
if (!error || typeof error !== "object") return void 0;
|
|
11710
|
-
const direct = readNumericProperty(error, STUDIO_SAVE_ATTEMPT_PROPERTY);
|
|
11711
|
-
if (direct != null) return direct;
|
|
11712
|
-
const cause = error.cause;
|
|
11713
|
-
if (cause && cause !== error) return getStudioSaveAttempt(cause);
|
|
11714
|
-
return void 0;
|
|
11715
|
-
}
|
|
11716
|
-
function isStudioSaveAbortError(error) {
|
|
11717
|
-
return error instanceof Error && error.name === "AbortError";
|
|
11718
|
-
}
|
|
11719
|
-
function isRetryableStudioSaveError(error) {
|
|
11720
|
-
if (isStudioSaveAbortError(error)) return false;
|
|
11721
|
-
if (error instanceof StudioSaveNetworkError) return true;
|
|
11722
|
-
const statusCode = getStudioSaveStatusCode(error);
|
|
11723
|
-
if (statusCode == null) return false;
|
|
11724
|
-
return statusCode === 408 || statusCode === 425 || statusCode === 429 || statusCode >= 500;
|
|
11725
|
-
}
|
|
11726
|
-
function buildStudioSaveFailureProperties(input) {
|
|
11727
|
-
const statusCode = input.statusCode ?? getStudioSaveStatusCode(input.error) ?? null;
|
|
11728
|
-
const attempt = input.attempt ?? getStudioSaveAttempt(input.error) ?? void 0;
|
|
11729
|
-
return {
|
|
11730
|
-
source: input.source,
|
|
11731
|
-
error_message: getStudioSaveErrorMessage(input.error),
|
|
11732
|
-
status_code: statusCode,
|
|
11733
|
-
file_path: input.filePath ?? input.targetSourceFile ?? void 0,
|
|
11734
|
-
mutation_type: input.mutationType ?? void 0,
|
|
11735
|
-
attempt,
|
|
11736
|
-
label: input.label ?? void 0,
|
|
11737
|
-
target_id: input.targetId ?? void 0,
|
|
11738
|
-
target_selector: input.targetSelector ?? void 0,
|
|
11739
|
-
target_source_file: input.targetSourceFile ?? void 0,
|
|
11740
|
-
block_detail: gsapBlockDetail(input.error)
|
|
11741
|
-
};
|
|
11742
|
-
}
|
|
11743
|
-
function gsapBlockDetail(error) {
|
|
11744
|
-
if (!(error instanceof Error) || error.name !== "GsapEditBlockedError") return void 0;
|
|
11745
|
-
const detail = error.detail;
|
|
11746
|
-
return typeof detail === "string" ? detail : void 0;
|
|
11747
|
-
}
|
|
11748
|
-
function trackStudioSaveFailure(input) {
|
|
11749
|
-
trackStudioEvent("save_failure", buildStudioSaveFailureProperties(input));
|
|
11750
|
-
}
|
|
11751
|
-
function trackStudioEditBlocked(input) {
|
|
11752
|
-
trackStudioEvent("edit_blocked", buildStudioSaveFailureProperties(input));
|
|
11753
|
-
}
|
|
11754
|
-
async function createStudioSaveHttpError(response, fallbackMessage, options = {}) {
|
|
11755
|
-
let body = "";
|
|
11756
|
-
try {
|
|
11757
|
-
body = await response.text();
|
|
11758
|
-
} catch {
|
|
11759
|
-
body = "";
|
|
11760
|
-
}
|
|
11761
|
-
const detail = body.trim().slice(0, 300);
|
|
11762
|
-
const message = detail ? `${fallbackMessage} (${response.status}): ${detail}` : `${fallbackMessage} (${response.status})`;
|
|
11763
|
-
return new StudioSaveHttpError(message, response.status, options);
|
|
11764
|
-
}
|
|
11765
|
-
async function retryStudioSave(operation, options = {}) {
|
|
11766
|
-
const retries = options.retries ?? 3;
|
|
11767
|
-
const baseDelayMs = options.baseDelayMs ?? 500;
|
|
11768
|
-
const maxDelayMs = options.maxDelayMs ?? 8e3;
|
|
11769
|
-
const jitterRatio = options.jitterRatio ?? 0.25;
|
|
11770
|
-
const random = options.random ?? Math.random;
|
|
11771
|
-
const shouldRetry = options.shouldRetry ?? isRetryableStudioSaveError;
|
|
11772
|
-
const sleep = options.sleep ?? ((delayMs, signal) => new Promise((resolve, reject) => {
|
|
11773
|
-
throwIfStudioSaveAborted(signal);
|
|
11774
|
-
const onAbort = () => {
|
|
11775
|
-
globalThis.clearTimeout(timeout);
|
|
11776
|
-
reject(createStudioSaveAbortError());
|
|
11777
|
-
};
|
|
11778
|
-
const timeout = globalThis.setTimeout(() => {
|
|
11779
|
-
signal?.removeEventListener("abort", onAbort);
|
|
11780
|
-
resolve();
|
|
11781
|
-
}, delayMs);
|
|
11782
|
-
signal?.addEventListener("abort", onAbort, { once: true });
|
|
11783
|
-
}));
|
|
11784
|
-
const maxAttempts = retries + 1;
|
|
11785
|
-
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
11786
|
-
try {
|
|
11787
|
-
throwIfStudioSaveAborted(options.signal);
|
|
11788
|
-
return await operation(attempt);
|
|
11789
|
-
} catch (error) {
|
|
11790
|
-
const failure = attachStudioSaveAttempt(error, attempt);
|
|
11791
|
-
if (attempt >= maxAttempts || !shouldRetry(failure, attempt)) throw failure;
|
|
11792
|
-
const retryIndex = attempt - 1;
|
|
11793
|
-
const exponentialDelay = Math.min(baseDelayMs * 2 ** retryIndex, maxDelayMs);
|
|
11794
|
-
const jitterSpan = exponentialDelay * jitterRatio;
|
|
11795
|
-
const jitteredDelay = Math.round(exponentialDelay + (random() * 2 - 1) * jitterSpan);
|
|
11796
|
-
const delayMs = Math.max(0, Math.min(maxDelayMs, jitteredDelay));
|
|
11797
|
-
await sleep(delayMs, options.signal);
|
|
11798
|
-
}
|
|
11799
|
-
}
|
|
11800
|
-
throw new Error("Save retry loop exited unexpectedly");
|
|
11801
|
-
}
|
|
11802
|
-
|
|
11803
|
-
// src/utils/typingTarget.ts
|
|
11804
|
-
function isTypingTarget(target) {
|
|
11805
|
-
const element = asElement(target);
|
|
11806
|
-
if (!element) return false;
|
|
11807
|
-
if (element.isContentEditable) return true;
|
|
11808
|
-
return element.closest(TYPING_SELECTOR) !== null;
|
|
11809
|
-
}
|
|
11810
|
-
function ownsPlainKeys(target) {
|
|
11811
|
-
return isTypingTarget(target) || asElement(target)?.closest(KEY_OWNING_CONTROLS) != null;
|
|
11812
|
-
}
|
|
11813
|
-
var KEY_OWNING_CONTROLS = "[role='slider'],video[controls],audio[controls]";
|
|
11814
|
-
var TYPING_SELECTOR = [
|
|
11815
|
-
"input",
|
|
11816
|
-
"textarea",
|
|
11817
|
-
"select",
|
|
11818
|
-
"[contenteditable]:not([contenteditable='false'])",
|
|
11819
|
-
"[role='textbox']",
|
|
11820
|
-
"[role='searchbox']",
|
|
11821
|
-
"[role='combobox']",
|
|
11822
|
-
// A switch is a button in the DOM, but the native checkbox it replaces was an
|
|
11823
|
-
// `input` and matched above. Without this row the shared Toggle would let a
|
|
11824
|
-
// global shortcut claim Space instead of flipping the control (KTD13).
|
|
11825
|
-
"[role='switch']",
|
|
11826
|
-
"[role='dialog'][aria-modal='true']",
|
|
11827
|
-
".cm-editor"
|
|
11828
|
-
].join(",");
|
|
11829
|
-
function asElement(target) {
|
|
11830
|
-
if (!target || typeof target !== "object") return null;
|
|
11831
|
-
const candidate = target;
|
|
11832
|
-
return typeof candidate.closest === "function" ? target : null;
|
|
11833
|
-
}
|
|
11834
|
-
|
|
11835
|
-
// src/utils/studioPendingEdits.ts
|
|
11836
|
-
var STUDIO_FLUSH_PENDING_EDITS_EVENT = "hf-studio-flush-pending-edits";
|
|
11837
|
-
var pendingEdits = /* @__PURE__ */ new Map();
|
|
11838
|
-
var NOT_SAVED = () => Promise.resolve(false);
|
|
11839
|
-
var adopting = null;
|
|
11840
|
-
function adoptingStudioPendingEdit() {
|
|
11841
|
-
return adopting;
|
|
11842
|
-
}
|
|
11843
|
-
function waitForPostBlurEffects() {
|
|
11844
|
-
return new Promise((resolve) => setTimeout(resolve, 0));
|
|
11845
|
-
}
|
|
11846
|
-
function inspectDrainFailures(results) {
|
|
11847
|
-
let firstFailure;
|
|
11848
|
-
for (const result of results) {
|
|
11849
|
-
if (result.status !== "rejected") continue;
|
|
11850
|
-
if (result.reason instanceof StudioFileConflictError) return { conflict: result.reason };
|
|
11851
|
-
firstFailure ??= result;
|
|
11852
|
-
}
|
|
11853
|
-
return { firstFailure };
|
|
11854
|
-
}
|
|
11855
|
-
function focusedField() {
|
|
11856
|
-
const active = document.activeElement;
|
|
11857
|
-
return active instanceof HTMLElement && isTypingTarget(active) ? active : null;
|
|
11858
|
-
}
|
|
11859
|
-
function hasStudioPendingEdits() {
|
|
11860
|
-
return pendingEdits.size > 0 || focusedField() !== null;
|
|
11861
|
-
}
|
|
11862
|
-
function isStudioEditSaving() {
|
|
11863
|
-
return pendingEdits.size > 0;
|
|
11864
|
-
}
|
|
11865
|
-
function afterStudioPendingEdits(run) {
|
|
11866
|
-
let waiting = true;
|
|
11867
|
-
const check = () => {
|
|
11868
|
-
if (!waiting) return;
|
|
11869
|
-
if (isStudioEditSaving()) {
|
|
11870
|
-
void Promise.allSettled([...pendingEdits.keys()]).then(check);
|
|
11871
|
-
return;
|
|
11872
|
-
}
|
|
11873
|
-
waiting = false;
|
|
11874
|
-
run();
|
|
11875
|
-
};
|
|
11876
|
-
check();
|
|
11877
|
-
return () => {
|
|
11878
|
-
waiting = false;
|
|
11879
|
-
};
|
|
11880
|
-
}
|
|
11881
|
-
function trackStudioPendingEdit(result) {
|
|
11882
|
-
if (!result) return void 0;
|
|
11883
|
-
const promise = Promise.resolve(result);
|
|
11884
|
-
if (adopting) return promise;
|
|
11885
|
-
pendingEdits.set(promise, { revert: null, landed: NOT_SAVED, redraws: [], showAgain: null });
|
|
11886
|
-
promise.then(
|
|
11887
|
-
() => pendingEdits.delete(promise),
|
|
11888
|
-
() => pendingEdits.delete(promise)
|
|
11889
|
-
);
|
|
11890
|
-
return promise;
|
|
11891
|
-
}
|
|
11892
|
-
function trackedStudioEdit(edit) {
|
|
11893
|
-
return (...args) => {
|
|
11894
|
-
const result = edit(...args);
|
|
11895
|
-
if (result instanceof Promise) trackStudioPendingEdit(result);
|
|
11896
|
-
return result;
|
|
11897
|
-
};
|
|
11898
|
-
}
|
|
11899
|
-
function beginStudioPendingEdit(revert) {
|
|
11900
|
-
let settle;
|
|
11901
|
-
const promise = trackStudioPendingEdit(new Promise((resolve) => settle = resolve));
|
|
11902
|
-
const entry2 = pendingEdits.get(promise);
|
|
11903
|
-
entry2.revert = revert;
|
|
11904
|
-
let landed = Promise.resolve(false);
|
|
11905
|
-
let saved2 = false;
|
|
11906
|
-
entry2.landed = () => landed;
|
|
11907
|
-
const inFlight = {
|
|
11908
|
-
reverted: () => entry2.revert === null && revert !== null,
|
|
11909
|
-
within(run) {
|
|
11910
|
-
const outer = adopting;
|
|
11911
|
-
adopting = inFlight;
|
|
11912
|
-
try {
|
|
11913
|
-
return run();
|
|
11914
|
-
} finally {
|
|
11915
|
-
adopting = outer;
|
|
11916
|
-
}
|
|
11917
|
-
},
|
|
11918
|
-
drawUnlessUndone(draw) {
|
|
11919
|
-
if (inFlight.reverted()) entry2.redraws.push(draw);
|
|
11920
|
-
else draw();
|
|
11921
|
-
},
|
|
11922
|
-
drawKeepingUndone(draw) {
|
|
11923
|
-
if (!inFlight.reverted()) return draw();
|
|
11924
|
-
entry2.showAgain?.();
|
|
11925
|
-
try {
|
|
11926
|
-
return draw();
|
|
11927
|
-
} finally {
|
|
11928
|
-
entry2.showAgain = revert();
|
|
11929
|
-
}
|
|
11930
|
-
},
|
|
11931
|
-
markSaved: () => void (saved2 = true)
|
|
11932
|
-
};
|
|
11933
|
-
return {
|
|
11934
|
-
settle,
|
|
11935
|
-
reverted: inFlight.reverted,
|
|
11936
|
-
// Only what `start` registers synchronously is adopted; a later write joins only through `within`.
|
|
11937
|
-
adopt(start) {
|
|
11938
|
-
try {
|
|
11939
|
-
const committed = inFlight.within(start);
|
|
11940
|
-
landed = Promise.resolve(committed).then(
|
|
11941
|
-
() => true,
|
|
11942
|
-
() => saved2
|
|
11943
|
-
);
|
|
11944
|
-
return committed;
|
|
11945
|
-
} catch (error) {
|
|
11946
|
-
settle();
|
|
11947
|
-
throw error;
|
|
11948
|
-
}
|
|
11949
|
-
}
|
|
11950
|
-
};
|
|
11951
|
-
}
|
|
11952
|
-
function paintBackNewestStudioPendingEdit() {
|
|
11953
|
-
const newest = [...pendingEdits.values()].at(-1);
|
|
11954
|
-
const revert = newest?.revert;
|
|
11955
|
-
if (!newest || !revert) return null;
|
|
11956
|
-
newest.revert = null;
|
|
11957
|
-
newest.showAgain = revert();
|
|
11958
|
-
return {
|
|
11959
|
-
showAgain: () => {
|
|
11960
|
-
newest.showAgain?.();
|
|
11961
|
-
for (const redraw of newest.redraws.splice(0)) redraw();
|
|
11962
|
-
},
|
|
11963
|
-
landed: newest.landed
|
|
11964
|
-
};
|
|
11965
|
-
}
|
|
11966
|
-
function revertNewestStudioPendingEdit() {
|
|
11967
|
-
return paintBackNewestStudioPendingEdit()?.showAgain ?? null;
|
|
11968
|
-
}
|
|
11969
|
-
async function flushStudioPendingEdits({
|
|
11970
|
-
onlyCurrent = false
|
|
11971
|
-
} = {}) {
|
|
11972
|
-
const active = focusedField();
|
|
11973
|
-
if (active) {
|
|
11974
|
-
active.blur();
|
|
11975
|
-
await Promise.resolve();
|
|
11976
|
-
await waitForPostBlurEffects();
|
|
11977
|
-
}
|
|
11978
|
-
const detail = { promises: [] };
|
|
11979
|
-
window.dispatchEvent(
|
|
11980
|
-
new CustomEvent(STUDIO_FLUSH_PENDING_EDITS_EVENT, { detail })
|
|
11981
|
-
);
|
|
11982
|
-
const current = onlyCurrent ? new Set(pendingEdits.keys()) : null;
|
|
11983
|
-
const waiting = () => [...pendingEdits.keys()].filter((edit) => !current || current.has(edit));
|
|
11984
|
-
let conflict;
|
|
11985
|
-
let firstFailure;
|
|
11986
|
-
while (detail.promises.length > 0 || waiting().length > 0) {
|
|
11987
|
-
const promises = [...detail.promises, ...waiting()];
|
|
11988
|
-
detail.promises = [];
|
|
11989
|
-
const batchFailures = inspectDrainFailures(await Promise.allSettled(promises));
|
|
11990
|
-
conflict ??= batchFailures.conflict;
|
|
11991
|
-
firstFailure ??= batchFailures.firstFailure;
|
|
11992
|
-
}
|
|
11993
|
-
if (conflict) return { status: "conflict", error: conflict };
|
|
11994
|
-
return firstFailure ? { status: "failed", error: firstFailure.reason } : { status: "clean" };
|
|
11995
|
-
}
|
|
11996
|
-
function addStudioPendingEditFlushListener(handler) {
|
|
11997
|
-
const listener2 = (event) => {
|
|
11998
|
-
const detail = event.detail;
|
|
11999
|
-
if (!detail?.promises) return;
|
|
12000
|
-
const promise = trackStudioPendingEdit(handler());
|
|
12001
|
-
if (promise) detail.promises.push(promise);
|
|
12002
|
-
};
|
|
12003
|
-
window.addEventListener(STUDIO_FLUSH_PENDING_EDITS_EVENT, listener2);
|
|
12004
|
-
return () => window.removeEventListener(STUDIO_FLUSH_PENDING_EDITS_EVENT, listener2);
|
|
12005
|
-
}
|
|
12006
|
-
|
|
12007
|
-
// src/contexts/DomEditContext.tsx
|
|
12008
12119
|
import { jsx as jsx19 } from "react/jsx-runtime";
|
|
12009
12120
|
var SAVES_AN_EDIT = {
|
|
12010
12121
|
handleTimelineElementSelect: false,
|
|
@@ -17540,20 +17651,32 @@ function requestGsapMutation(projectId, route, filePath, body) {
|
|
|
17540
17651
|
}
|
|
17541
17652
|
);
|
|
17542
17653
|
}
|
|
17543
|
-
async function
|
|
17654
|
+
async function postOwnedGsapMutation(route, projectId, filePath, body, fallback) {
|
|
17544
17655
|
let response;
|
|
17545
17656
|
try {
|
|
17546
|
-
response = await requestGsapMutation(projectId,
|
|
17657
|
+
response = await requestGsapMutation(projectId, route, filePath, body);
|
|
17547
17658
|
} catch (error) {
|
|
17548
17659
|
throw new GsapPreviewConvergenceError(`${fallback}: mutation outcome unknown`, {
|
|
17549
17660
|
cause: error
|
|
17550
17661
|
});
|
|
17551
17662
|
}
|
|
17552
|
-
const
|
|
17663
|
+
const payload = await response.json().catch(() => null);
|
|
17553
17664
|
if (!response.ok) {
|
|
17554
|
-
throw new GsapPreviewConvergenceError(readMutationError(
|
|
17665
|
+
throw new GsapPreviewConvergenceError(readMutationError(payload, fallback));
|
|
17555
17666
|
}
|
|
17556
|
-
return readMutationStatus(
|
|
17667
|
+
return readMutationStatus(payload);
|
|
17668
|
+
}
|
|
17669
|
+
function postGsapMutation(projectId, filePath, mutation, fallback) {
|
|
17670
|
+
return postOwnedGsapMutation("gsap-mutations", projectId, filePath, mutation, fallback);
|
|
17671
|
+
}
|
|
17672
|
+
function postGsapMutations(projectId, filePath, mutations, fallback) {
|
|
17673
|
+
return postOwnedGsapMutation(
|
|
17674
|
+
"gsap-mutations-batch",
|
|
17675
|
+
projectId,
|
|
17676
|
+
filePath,
|
|
17677
|
+
{ mutations },
|
|
17678
|
+
fallback
|
|
17679
|
+
);
|
|
17557
17680
|
}
|
|
17558
17681
|
|
|
17559
17682
|
// src/hooks/timelineTimingSync.ts
|
|
@@ -17616,8 +17739,9 @@ function syncTimingEditPreview(iframe, outcome, currentTime, reloadPreview, rebi
|
|
|
17616
17739
|
});
|
|
17617
17740
|
if (result === "cannot-soft-reload") reloadPreview();
|
|
17618
17741
|
}
|
|
17742
|
+
var UNMUTATED = { mutated: false, scriptText: null };
|
|
17619
17743
|
async function finishTimelineTimingFallback(input) {
|
|
17620
|
-
let outcome =
|
|
17744
|
+
let outcome = UNMUTATED;
|
|
17621
17745
|
if (input.gsapMutation) {
|
|
17622
17746
|
try {
|
|
17623
17747
|
outcome = await input.gsapMutation();
|
|
@@ -17644,7 +17768,14 @@ async function finishTimelineTimingFallback(input) {
|
|
|
17644
17768
|
nestedFiles
|
|
17645
17769
|
);
|
|
17646
17770
|
}
|
|
17647
|
-
var
|
|
17771
|
+
var UNTIL_NEXT_EDIT = Number.POSITIVE_INFINITY;
|
|
17772
|
+
var timingGestures = 0;
|
|
17773
|
+
function timingGestureStep(kind, given) {
|
|
17774
|
+
return {
|
|
17775
|
+
coalesceKey: given?.coalesceKey ?? `${kind}:${++timingGestures}`,
|
|
17776
|
+
coalesceMs: UNTIL_NEXT_EDIT
|
|
17777
|
+
};
|
|
17778
|
+
}
|
|
17648
17779
|
async function rollbackMutatedFiles(projectId, ownedSteps, onError) {
|
|
17649
17780
|
let convergenceRequired = false;
|
|
17650
17781
|
for (let index = ownedSteps.length - 1; index >= 0; index -= 1) {
|
|
@@ -17675,7 +17806,7 @@ async function foldGsapMutationInQueue(input) {
|
|
|
17675
17806
|
const ownedSteps = [];
|
|
17676
17807
|
const runOwnedMutation = async (path, mutation) => {
|
|
17677
17808
|
const pending2 = mutation();
|
|
17678
|
-
if (!pending2) return
|
|
17809
|
+
if (!pending2) return UNMUTATED;
|
|
17679
17810
|
const status2 = await pending2;
|
|
17680
17811
|
if (!status2.mutated) return status2;
|
|
17681
17812
|
if (status2.before === void 0 || status2.after === void 0) {
|
|
@@ -17720,7 +17851,7 @@ async function foldGsapMutationInQueue(input) {
|
|
|
17720
17851
|
await input.recordEdit({
|
|
17721
17852
|
label: input.label,
|
|
17722
17853
|
coalesceKey: input.coalesceKey,
|
|
17723
|
-
coalesceMs:
|
|
17854
|
+
coalesceMs: UNTIL_NEXT_EDIT,
|
|
17724
17855
|
files
|
|
17725
17856
|
});
|
|
17726
17857
|
}
|
|
@@ -17737,37 +17868,31 @@ function foldGsapMutationIntoHistory(input) {
|
|
|
17737
17868
|
() => foldGsapMutationInQueue(input)
|
|
17738
17869
|
);
|
|
17739
17870
|
}
|
|
17871
|
+
function shiftGsapMutation(elementId2, delta) {
|
|
17872
|
+
if (delta === 0 || !elementId2) return null;
|
|
17873
|
+
return { type: "shift-positions", targetSelector: `#${elementId2}`, delta };
|
|
17874
|
+
}
|
|
17875
|
+
function scaleGsapMutation(elementId2, oldStart, oldDuration, newStart, newDuration) {
|
|
17876
|
+
if (!elementId2 || oldDuration <= 0 || newDuration <= 0) return null;
|
|
17877
|
+
if (oldStart === newStart && oldDuration === newDuration) return null;
|
|
17878
|
+
return {
|
|
17879
|
+
type: "scale-positions",
|
|
17880
|
+
targetSelector: `#${elementId2}`,
|
|
17881
|
+
oldStart,
|
|
17882
|
+
oldDuration,
|
|
17883
|
+
newStart,
|
|
17884
|
+
newDuration
|
|
17885
|
+
};
|
|
17886
|
+
}
|
|
17740
17887
|
async function shiftGsapPositions(projectId, filePath, elementId2, delta) {
|
|
17741
|
-
|
|
17742
|
-
return
|
|
17743
|
-
|
|
17744
|
-
filePath,
|
|
17745
|
-
{
|
|
17746
|
-
type: "shift-positions",
|
|
17747
|
-
targetSelector: `#${elementId2}`,
|
|
17748
|
-
delta
|
|
17749
|
-
},
|
|
17750
|
-
"shift-positions failed"
|
|
17751
|
-
);
|
|
17888
|
+
const mutation = shiftGsapMutation(elementId2, delta);
|
|
17889
|
+
if (!mutation) return UNMUTATED;
|
|
17890
|
+
return postGsapMutation(projectId, filePath, mutation, "shift-positions failed");
|
|
17752
17891
|
}
|
|
17753
17892
|
async function scaleGsapPositions(projectId, filePath, elementId2, oldStart, oldDuration, newStart, newDuration) {
|
|
17754
|
-
|
|
17755
|
-
|
|
17756
|
-
|
|
17757
|
-
return { mutated: false, scriptText: null };
|
|
17758
|
-
return postGsapMutation(
|
|
17759
|
-
projectId,
|
|
17760
|
-
filePath,
|
|
17761
|
-
{
|
|
17762
|
-
type: "scale-positions",
|
|
17763
|
-
targetSelector: `#${elementId2}`,
|
|
17764
|
-
oldStart,
|
|
17765
|
-
oldDuration,
|
|
17766
|
-
newStart,
|
|
17767
|
-
newDuration
|
|
17768
|
-
},
|
|
17769
|
-
"scale-positions failed"
|
|
17770
|
-
);
|
|
17893
|
+
const mutation = scaleGsapMutation(elementId2, oldStart, oldDuration, newStart, newDuration);
|
|
17894
|
+
if (!mutation) return UNMUTATED;
|
|
17895
|
+
return postGsapMutation(projectId, filePath, mutation, "scale-positions failed");
|
|
17771
17896
|
}
|
|
17772
17897
|
function sdkTimingGsapSync(result) {
|
|
17773
17898
|
const timelineText = (html2) => {
|
|
@@ -17844,11 +17969,17 @@ async function finishGroupTimingGsapFallback(input) {
|
|
|
17844
17969
|
let mutated = false;
|
|
17845
17970
|
let scriptText = null;
|
|
17846
17971
|
let after;
|
|
17972
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
17847
17973
|
for (const change of input.changes) {
|
|
17974
|
+
const mutation = input.mutationFor(change);
|
|
17975
|
+
if (!mutation) continue;
|
|
17848
17976
|
const changePath = input.resolveChangePath(change.element);
|
|
17977
|
+
byPath.set(changePath, [...byPath.get(changePath) ?? [], mutation]);
|
|
17978
|
+
}
|
|
17979
|
+
for (const [changePath, mutations] of byPath) {
|
|
17849
17980
|
const status = await runOwnedMutation(
|
|
17850
17981
|
changePath,
|
|
17851
|
-
() => input.
|
|
17982
|
+
() => postGsapMutations(input.projectId, changePath, mutations, input.errorLabel)
|
|
17852
17983
|
);
|
|
17853
17984
|
mutated = mutated || status.mutated;
|
|
17854
17985
|
if (changePath === activePath) ({ scriptText, after } = status);
|
|
@@ -18015,10 +18146,10 @@ function resolveResizePlaybackStart(original, target, element, updates) {
|
|
|
18015
18146
|
)
|
|
18016
18147
|
};
|
|
18017
18148
|
}
|
|
18018
|
-
function
|
|
18149
|
+
function applyTimelineMoveAttributes(original, target, start, duration, track2, audioGroup) {
|
|
18019
18150
|
if (!Number.isFinite(start) || !Number.isFinite(duration)) {
|
|
18020
18151
|
console.warn(
|
|
18021
|
-
`[Timeline]
|
|
18152
|
+
`[Timeline] applyTimelineMoveAttributes: non-finite timing (start=${start}, duration=${duration}) \u2014 patch skipped`
|
|
18022
18153
|
);
|
|
18023
18154
|
return original;
|
|
18024
18155
|
}
|
|
@@ -18040,9 +18171,16 @@ function buildTimelineMoveTimingPatch(original, target, start, duration, track2,
|
|
|
18040
18171
|
property: "data-audio-group",
|
|
18041
18172
|
value: null
|
|
18042
18173
|
});
|
|
18043
|
-
return
|
|
18174
|
+
return patched;
|
|
18175
|
+
}
|
|
18176
|
+
function syncCompositionDurationToContent(source) {
|
|
18177
|
+
return setCompositionDurationToContent(source, furthestClipEndFromSource(source));
|
|
18178
|
+
}
|
|
18179
|
+
function buildTimelineMoveTimingPatch(original, target, start, duration, track2, audioGroup) {
|
|
18180
|
+
const patched = applyTimelineMoveAttributes(original, target, start, duration, track2, audioGroup);
|
|
18181
|
+
return !(Number.isFinite(start) && Number.isFinite(duration)) ? patched : syncCompositionDurationToContent(patched);
|
|
18044
18182
|
}
|
|
18045
|
-
function
|
|
18183
|
+
function applyTimelineResizeAttributes(original, target, element, updates) {
|
|
18046
18184
|
const pbs = resolveResizePlaybackStart(original, target, element, updates);
|
|
18047
18185
|
let patched = applyPatchByTarget(original, target, {
|
|
18048
18186
|
type: "attribute",
|
|
@@ -18061,7 +18199,10 @@ function buildTimelineResizeTimingPatch(original, target, element, updates) {
|
|
|
18061
18199
|
value: formatTimelineMediaOffset(pbs.value)
|
|
18062
18200
|
});
|
|
18063
18201
|
}
|
|
18064
|
-
return
|
|
18202
|
+
return patched;
|
|
18203
|
+
}
|
|
18204
|
+
function buildTimelineResizeTimingPatch(...args) {
|
|
18205
|
+
return syncCompositionDurationToContent(applyTimelineResizeAttributes(...args));
|
|
18065
18206
|
}
|
|
18066
18207
|
async function persistTimelineEdit(input) {
|
|
18067
18208
|
const targetPath = input.element.sourceFile || input.activeCompPath || "index.html";
|
|
@@ -18074,6 +18215,7 @@ async function persistTimelineEdit(input) {
|
|
|
18074
18215
|
projectId: input.projectId,
|
|
18075
18216
|
label: input.label,
|
|
18076
18217
|
coalesceKey: input.coalesceKey,
|
|
18218
|
+
coalesceMs: input.coalesceMs,
|
|
18077
18219
|
files: {
|
|
18078
18220
|
[targetPath]: (current) => {
|
|
18079
18221
|
const patched = input.buildPatches(current, patchTarget);
|
|
@@ -18113,7 +18255,12 @@ async function persistTimelineBatchEdit(input) {
|
|
|
18113
18255
|
changesByPath.set(targetPath, [...changesByPath.get(targetPath) ?? [], change]);
|
|
18114
18256
|
}
|
|
18115
18257
|
const buildFile = (targetPath) => (original) => {
|
|
18116
|
-
const
|
|
18258
|
+
const patched = patchTimelineChangesInSource(
|
|
18259
|
+
original,
|
|
18260
|
+
targetPath,
|
|
18261
|
+
changesByPath.get(targetPath)
|
|
18262
|
+
);
|
|
18263
|
+
const next = syncCompositionDurationToContent(patched);
|
|
18117
18264
|
if (next !== original) input.pendingTimelineEditPathRef.current.add(targetPath);
|
|
18118
18265
|
return next;
|
|
18119
18266
|
};
|
|
@@ -18521,6 +18668,7 @@ import { jsx as jsx33, jsxs as jsxs20 } from "react/jsx-runtime";
|
|
|
18521
18668
|
var TimelineClip = memo13(function TimelineClip2({
|
|
18522
18669
|
el,
|
|
18523
18670
|
pps,
|
|
18671
|
+
passengerStyle,
|
|
18524
18672
|
clipY,
|
|
18525
18673
|
clipHeight,
|
|
18526
18674
|
isSelected: isSelected2,
|
|
@@ -18592,7 +18740,8 @@ var TimelineClip = memo13(function TimelineClip2({
|
|
|
18592
18740
|
font: "inherit",
|
|
18593
18741
|
padding: 0,
|
|
18594
18742
|
textAlign: "left",
|
|
18595
|
-
transform: isDragging ? "translateY(-1px)" : void 0
|
|
18743
|
+
transform: isDragging ? "translateY(-1px)" : void 0,
|
|
18744
|
+
...passengerStyle
|
|
18596
18745
|
};
|
|
18597
18746
|
return /* @__PURE__ */ jsxs20(
|
|
18598
18747
|
"button",
|
|
@@ -18728,6 +18877,7 @@ function TimelineCompactDiamonds({
|
|
|
18728
18877
|
beatsActive,
|
|
18729
18878
|
accentColor,
|
|
18730
18879
|
isSelected: isSelected2,
|
|
18880
|
+
passengerStyle,
|
|
18731
18881
|
currentTime,
|
|
18732
18882
|
selectedKeyframes,
|
|
18733
18883
|
rovingTargetId,
|
|
@@ -18748,7 +18898,8 @@ function TimelineCompactDiamonds({
|
|
|
18748
18898
|
top: CLIP_Y,
|
|
18749
18899
|
width,
|
|
18750
18900
|
height: rowHeight - 2 * CLIP_Y,
|
|
18751
|
-
zIndex: isSelected2 ? 11 : 6
|
|
18901
|
+
zIndex: isSelected2 ? 11 : 6,
|
|
18902
|
+
...passengerStyle
|
|
18752
18903
|
},
|
|
18753
18904
|
children: /* @__PURE__ */ jsx34(
|
|
18754
18905
|
TimelineClipDiamonds,
|
|
@@ -21850,7 +22001,6 @@ function LayerDisclosureRow({
|
|
|
21850
22001
|
}
|
|
21851
22002
|
|
|
21852
22003
|
// src/player/components/trackHeaderLaneValues.ts
|
|
21853
|
-
import gsap2 from "gsap";
|
|
21854
22004
|
import {
|
|
21855
22005
|
classifyPropertyGroup as classifyPropertyGroup2
|
|
21856
22006
|
} from "@hyperframes/core/gsap-parser";
|
|
@@ -21858,24 +22008,25 @@ function roundValue(value) {
|
|
|
21858
22008
|
return String(Math.round(value * 100) / 100);
|
|
21859
22009
|
}
|
|
21860
22010
|
function easedProgress(progress, animation, ease) {
|
|
21861
|
-
|
|
21862
|
-
if (!resolved2 || resolved2 === "none") return progress;
|
|
21863
|
-
return gsap2.parseEase(resolved2)?.(progress) ?? progress;
|
|
22011
|
+
return easeFunction(keyframedTweenEases(animation).segment({ ease }))?.(progress) ?? progress;
|
|
21864
22012
|
}
|
|
21865
|
-
function propertyStops(animation, property) {
|
|
21866
|
-
|
|
22013
|
+
function propertyStops(animation, property, start) {
|
|
22014
|
+
const stops = (animation.keyframes?.keyframes ?? []).filter((keyframe) => property in keyframe.properties).map((keyframe) => ({
|
|
21867
22015
|
percentage: keyframe.percentage,
|
|
21868
22016
|
value: keyframe.properties[property],
|
|
21869
22017
|
ease: keyframe.ease
|
|
21870
22018
|
}));
|
|
22019
|
+
const first = stops[0];
|
|
22020
|
+
if (!first || first.percentage <= 0 || typeof first.value !== "number") return stops;
|
|
22021
|
+
return [{ percentage: 0, value: start ?? PROPERTY_DEFAULTS[property] ?? 0 }, ...stops];
|
|
21871
22022
|
}
|
|
21872
22023
|
function interpolableEnds(before, after) {
|
|
21873
22024
|
if (typeof before.value !== "number" || typeof after.value !== "number") return null;
|
|
21874
22025
|
if (before.percentage === after.percentage) return null;
|
|
21875
22026
|
return { from: before.value, to: after.value };
|
|
21876
22027
|
}
|
|
21877
|
-
function propertyValueAt(animation, property, tweenPercentage) {
|
|
21878
|
-
const stops = propertyStops(animation, property);
|
|
22028
|
+
function propertyValueAt(animation, property, tweenPercentage, start) {
|
|
22029
|
+
const stops = propertyStops(animation, property, start);
|
|
21879
22030
|
const before = stops.filter((stop2) => stop2.percentage <= tweenPercentage).at(-1);
|
|
21880
22031
|
const after = stops.find((stop2) => stop2.percentage >= tweenPercentage);
|
|
21881
22032
|
if (!before) return after?.value;
|
|
@@ -21885,7 +22036,7 @@ function propertyValueAt(animation, property, tweenPercentage) {
|
|
|
21885
22036
|
const progress = (tweenPercentage - before.percentage) / (after.percentage - before.percentage);
|
|
21886
22037
|
return ends.from + (ends.to - ends.from) * easedProgress(progress, animation, after.ease);
|
|
21887
22038
|
}
|
|
21888
|
-
function valuesAt(animation, group, tweenPercentage) {
|
|
22039
|
+
function valuesAt(animation, group, tweenPercentage, startValues = {}) {
|
|
21889
22040
|
const propertyNames = /* @__PURE__ */ new Set();
|
|
21890
22041
|
for (const keyframe of animation.keyframes?.keyframes ?? []) {
|
|
21891
22042
|
for (const property of Object.keys(keyframe.properties)) {
|
|
@@ -21894,11 +22045,34 @@ function valuesAt(animation, group, tweenPercentage) {
|
|
|
21894
22045
|
}
|
|
21895
22046
|
const values = {};
|
|
21896
22047
|
for (const property of propertyNames) {
|
|
21897
|
-
const value = propertyValueAt(animation, property, tweenPercentage);
|
|
22048
|
+
const value = propertyValueAt(animation, property, tweenPercentage, startValues[property]);
|
|
21898
22049
|
if (value !== void 0) values[property] = value;
|
|
21899
22050
|
}
|
|
21900
22051
|
return values;
|
|
21901
22052
|
}
|
|
22053
|
+
var endValue = (animation, property) => animation.keyframes?.keyframes.findLast((keyframe) => property in keyframe.properties)?.properties[property] ?? animation.properties[property];
|
|
22054
|
+
function valuesBefore(animation, animations) {
|
|
22055
|
+
const start = resolveTweenStart(animation);
|
|
22056
|
+
if (start == null) return {};
|
|
22057
|
+
const endOf2 = (other) => (resolveTweenStart(other) ?? Infinity) + (other.method === "set" ? 0 : resolveTweenDuration(other));
|
|
22058
|
+
const index = animations.indexOf(animation);
|
|
22059
|
+
const finished = animations.filter((other, i) => {
|
|
22060
|
+
if (other.method === "from" || i === index) return false;
|
|
22061
|
+
return endOf2(other) < start || endOf2(other) === start && i < index;
|
|
22062
|
+
}).sort((a, b) => endOf2(a) - endOf2(b));
|
|
22063
|
+
const values = {};
|
|
22064
|
+
for (const other of finished) {
|
|
22065
|
+
const keyframes = other.keyframes?.keyframes ?? [{ properties: other.properties }];
|
|
22066
|
+
for (const property of new Set(
|
|
22067
|
+
keyframes.flatMap((keyframe) => Object.keys(keyframe.properties))
|
|
22068
|
+
)) {
|
|
22069
|
+
const value = endValue(other, property);
|
|
22070
|
+
if (typeof value === "number") values[property] = value;
|
|
22071
|
+
else delete values[property];
|
|
22072
|
+
}
|
|
22073
|
+
}
|
|
22074
|
+
return values;
|
|
22075
|
+
}
|
|
21902
22076
|
function groupLabel(group, properties) {
|
|
21903
22077
|
if (group === "visual" && ("opacity" in properties || "autoAlpha" in properties)) {
|
|
21904
22078
|
return "Opacity";
|
|
@@ -21975,7 +22149,12 @@ function resolveLaneHeaderState(lane, currentTime, clipPercentage) {
|
|
|
21975
22149
|
currentTime,
|
|
21976
22150
|
clipPercentage
|
|
21977
22151
|
);
|
|
21978
|
-
const values = animation ? valuesAt(
|
|
22152
|
+
const values = animation ? valuesAt(
|
|
22153
|
+
animation,
|
|
22154
|
+
lane.group,
|
|
22155
|
+
tweenPercentage,
|
|
22156
|
+
valuesBefore(animation, lane.elementAnimations)
|
|
22157
|
+
) : {};
|
|
21979
22158
|
return {
|
|
21980
22159
|
navigation,
|
|
21981
22160
|
values,
|
|
@@ -23078,7 +23257,7 @@ function isIntentBlocked(intent, capabilities, onResizeElement, onMoveElement) {
|
|
|
23078
23257
|
if (capabilities.readOnly) return true;
|
|
23079
23258
|
return intent === "move" ? Boolean(onMoveElement) : Boolean(onResizeElement);
|
|
23080
23259
|
}
|
|
23081
|
-
function resolvePointerDownAction(e,
|
|
23260
|
+
function resolvePointerDownAction(e, capabilities, onResizeElement, onMoveElement) {
|
|
23082
23261
|
if (e.button !== 0) return { kind: "ignore" };
|
|
23083
23262
|
if (usePlayerStore.getState().activeTool === "razor") return { kind: "ignore" };
|
|
23084
23263
|
if (isAdditiveClick(e)) return { kind: "ignore" };
|
|
@@ -23093,8 +23272,6 @@ function resolvePointerDownAction(e, grabbed, capabilities, onResizeElement, onM
|
|
|
23093
23272
|
return { kind: "block", intent, rect };
|
|
23094
23273
|
}
|
|
23095
23274
|
if (!onMoveElement || !capabilities.canMove) return { kind: "ignore" };
|
|
23096
|
-
if (exceedsHandEditLimit(grabbed, e.altKey, null))
|
|
23097
|
-
return { kind: "block", intent: "edit-many", rect };
|
|
23098
23275
|
return { kind: "move", rect };
|
|
23099
23276
|
}
|
|
23100
23277
|
function createClipGestureHandlers(el, elementKey3, previewElement, capabilities, deps) {
|
|
@@ -23117,17 +23294,6 @@ function createClipGestureHandlers(el, elementKey3, previewElement, capabilities
|
|
|
23117
23294
|
const onResizeStart = (edge, e) => {
|
|
23118
23295
|
if (!canStartResize(edge, e, capabilities, onResizeElement)) return;
|
|
23119
23296
|
e.stopPropagation();
|
|
23120
|
-
if (exceedsHandEditLimit(el, e.altKey, edge)) {
|
|
23121
|
-
blockedClipRef.current = {
|
|
23122
|
-
pointerId: e.pointerId,
|
|
23123
|
-
element: el,
|
|
23124
|
-
intent: "edit-many",
|
|
23125
|
-
originClientX: e.clientX,
|
|
23126
|
-
originClientY: e.clientY,
|
|
23127
|
-
started: false
|
|
23128
|
-
};
|
|
23129
|
-
return;
|
|
23130
|
-
}
|
|
23131
23297
|
blockedClipRef.current = null;
|
|
23132
23298
|
setShowPopover(false);
|
|
23133
23299
|
setRangeSelection(null);
|
|
@@ -23145,7 +23311,7 @@ function createClipGestureHandlers(el, elementKey3, previewElement, capabilities
|
|
|
23145
23311
|
});
|
|
23146
23312
|
};
|
|
23147
23313
|
const onPointerDown = (e) => {
|
|
23148
|
-
const action = resolvePointerDownAction(e,
|
|
23314
|
+
const action = resolvePointerDownAction(e, capabilities, onResizeElement, onMoveElement);
|
|
23149
23315
|
if (action.kind === "ignore") return;
|
|
23150
23316
|
if (action.kind === "block") {
|
|
23151
23317
|
blockedClipRef.current = {
|
|
@@ -23352,6 +23518,14 @@ function TimelineTransitionOverlays({
|
|
|
23352
23518
|
|
|
23353
23519
|
// src/player/components/TimelineLanes.tsx
|
|
23354
23520
|
import { jsx as jsx57, jsxs as jsxs37 } from "react/jsx-runtime";
|
|
23521
|
+
function passengerStyleAt(offsetPx) {
|
|
23522
|
+
return {
|
|
23523
|
+
transform: `translateX(${offsetPx}px)`,
|
|
23524
|
+
opacity: 0.85,
|
|
23525
|
+
zIndex: 20,
|
|
23526
|
+
pointerEvents: "none"
|
|
23527
|
+
};
|
|
23528
|
+
}
|
|
23355
23529
|
function TimelineLanes({
|
|
23356
23530
|
pps,
|
|
23357
23531
|
contentOrigin,
|
|
@@ -23658,7 +23832,7 @@ function TimelineLanes({
|
|
|
23658
23832
|
isSelected2 || hoveredClip === clipKey || pinnedClipIdentities.has(clipKey)
|
|
23659
23833
|
);
|
|
23660
23834
|
const isPassenger = multiDragPreview != null && isMultiDragPassenger(clipKey, multiDragPreview);
|
|
23661
|
-
const
|
|
23835
|
+
const passengerStyle = isPassenger ? passengerStyleAt(multiDragPassengerOffsetPx(clipKey, pps, multiDragPreview)) : void 0;
|
|
23662
23836
|
const clipGestures = createClipGestureHandlers(
|
|
23663
23837
|
el,
|
|
23664
23838
|
elementKey3,
|
|
@@ -23690,6 +23864,7 @@ function TimelineLanes({
|
|
|
23690
23864
|
},
|
|
23691
23865
|
el: previewElement,
|
|
23692
23866
|
pps,
|
|
23867
|
+
passengerStyle,
|
|
23693
23868
|
clipY: CLIP_Y,
|
|
23694
23869
|
clipHeight: clipBarHeight,
|
|
23695
23870
|
isSelected: isSelected2,
|
|
@@ -23733,6 +23908,7 @@ function TimelineLanes({
|
|
|
23733
23908
|
beatsActive: beatStripOnTrack,
|
|
23734
23909
|
accentColor: clipStyle.accent,
|
|
23735
23910
|
isSelected: isSelected2,
|
|
23911
|
+
passengerStyle,
|
|
23736
23912
|
currentTime,
|
|
23737
23913
|
selectedKeyframes,
|
|
23738
23914
|
rovingTargetId: keyboard.rovingTargetId,
|
|
@@ -23754,6 +23930,7 @@ function TimelineLanes({
|
|
|
23754
23930
|
clipDuration: previewElement.duration,
|
|
23755
23931
|
clipLeftPx: previewElement.start * pps,
|
|
23756
23932
|
clipWidthPx: Math.max(previewElement.duration * pps, 4),
|
|
23933
|
+
passengerStyle,
|
|
23757
23934
|
accentColor: clipStyle.accent,
|
|
23758
23935
|
isSelected: isSelected2,
|
|
23759
23936
|
currentPercentage: previewElement.duration > 0 ? (currentTime - previewElement.start) / previewElement.duration * 100 : 0,
|
|
@@ -23769,31 +23946,11 @@ function TimelineLanes({
|
|
|
23769
23946
|
},
|
|
23770
23947
|
`${clipKey}-property-lanes`
|
|
23771
23948
|
);
|
|
23772
|
-
|
|
23773
|
-
|
|
23774
|
-
|
|
23775
|
-
|
|
23776
|
-
|
|
23777
|
-
] }, clipKey);
|
|
23778
|
-
}
|
|
23779
|
-
return /* @__PURE__ */ jsxs37(
|
|
23780
|
-
"div",
|
|
23781
|
-
{
|
|
23782
|
-
className: "absolute inset-0",
|
|
23783
|
-
style: {
|
|
23784
|
-
transform: `translateX(${passengerOffsetPx}px)`,
|
|
23785
|
-
opacity: 0.85,
|
|
23786
|
-
zIndex: 20,
|
|
23787
|
-
pointerEvents: "none"
|
|
23788
|
-
},
|
|
23789
|
-
children: [
|
|
23790
|
-
clip,
|
|
23791
|
-
compactDiamonds,
|
|
23792
|
-
propertyLanes
|
|
23793
|
-
]
|
|
23794
|
-
},
|
|
23795
|
-
clipKey
|
|
23796
|
-
);
|
|
23949
|
+
return /* @__PURE__ */ jsxs37(Fragment18, { children: [
|
|
23950
|
+
clip,
|
|
23951
|
+
compactDiamonds,
|
|
23952
|
+
propertyLanes
|
|
23953
|
+
] }, clipKey);
|
|
23797
23954
|
}),
|
|
23798
23955
|
/* @__PURE__ */ jsx57(
|
|
23799
23956
|
TimelineTransitionOverlays,
|
|
@@ -29383,8 +29540,75 @@ var STUDIO_MANUAL_EDITS_WRAPPED_PROP = "__hfStudioManualEditsWrapped";
|
|
|
29383
29540
|
var STUDIO_MANUAL_EDITS_PLAYBACK_FRAME_PROP = "__hfStudioManualEditsPlaybackFrame";
|
|
29384
29541
|
var STUDIO_ROTATION_TRANSFORM_ORIGIN = "center center";
|
|
29385
29542
|
|
|
29386
|
-
// src/hooks/
|
|
29543
|
+
// src/hooks/gsapRuntimeMotionPath.ts
|
|
29387
29544
|
import { buildArcPath } from "@hyperframes/core/gsap-parser-acorn";
|
|
29545
|
+
function isXY(p) {
|
|
29546
|
+
return !!p && typeof p.x === "number" && typeof p.y === "number";
|
|
29547
|
+
}
|
|
29548
|
+
function coordsFromMotionPath(mp) {
|
|
29549
|
+
if (!mp || typeof mp !== "object") return null;
|
|
29550
|
+
const obj = mp;
|
|
29551
|
+
const pathVal = Array.isArray(mp) ? mp : obj.path;
|
|
29552
|
+
if (!Array.isArray(pathVal)) return null;
|
|
29553
|
+
const coords = pathVal.filter(isXY).map((p) => ({ x: p.x, y: p.y }));
|
|
29554
|
+
if (coords.length < 2) return null;
|
|
29555
|
+
const curviness = typeof obj.curviness === "number" ? obj.curviness : 1;
|
|
29556
|
+
const autoRotate = typeof obj.autoRotate === "number" ? obj.autoRotate : obj.autoRotate === true;
|
|
29557
|
+
return { coords, curviness, autoRotate, isCubic: obj.type === "cubic" };
|
|
29558
|
+
}
|
|
29559
|
+
function readMotionPathTween(vars, runEase) {
|
|
29560
|
+
const mp = coordsFromMotionPath(vars.motionPath);
|
|
29561
|
+
const shape = mp && buildArcPath(mp.coords, mp.curviness, mp.autoRotate, mp.isCubic);
|
|
29562
|
+
if (!shape) return null;
|
|
29563
|
+
const n = shape.waypoints.length;
|
|
29564
|
+
const keyframes = shape.waypoints.map((wp, i) => ({
|
|
29565
|
+
percentage: n > 1 ? Math.round(i / (n - 1) * 100) : 0,
|
|
29566
|
+
properties: { x: wp.x, y: wp.y }
|
|
29567
|
+
}));
|
|
29568
|
+
return { keyframes, arcPath: shape.arcPath, runEase: runEase ?? MOTION_PATH_RUN_EASE };
|
|
29569
|
+
}
|
|
29570
|
+
|
|
29571
|
+
// src/hooks/gsapRuntimeTweenIndex.ts
|
|
29572
|
+
function matchesElement(tween, el) {
|
|
29573
|
+
if (!tween.targets) return false;
|
|
29574
|
+
for (const t2 of tween.targets()) {
|
|
29575
|
+
if (t2 === el || el.id && t2.id === el.id) return true;
|
|
29576
|
+
}
|
|
29577
|
+
return false;
|
|
29578
|
+
}
|
|
29579
|
+
var tweenIndexes = null;
|
|
29580
|
+
function withTweenIndex(run) {
|
|
29581
|
+
const outer = tweenIndexes;
|
|
29582
|
+
tweenIndexes ??= /* @__PURE__ */ new WeakMap();
|
|
29583
|
+
try {
|
|
29584
|
+
return run();
|
|
29585
|
+
} finally {
|
|
29586
|
+
tweenIndexes = outer;
|
|
29587
|
+
}
|
|
29588
|
+
}
|
|
29589
|
+
function indexTweens(children) {
|
|
29590
|
+
const index = { byElement: /* @__PURE__ */ new Map(), byId: /* @__PURE__ */ new Map() };
|
|
29591
|
+
const add = (map, key2, tween) => {
|
|
29592
|
+
const list = map.get(key2);
|
|
29593
|
+
if (!list) map.set(key2, [tween]);
|
|
29594
|
+
else if (list.at(-1) !== tween) list.push(tween);
|
|
29595
|
+
};
|
|
29596
|
+
for (const tween of children) {
|
|
29597
|
+
for (const target of tween.targets?.() ?? []) {
|
|
29598
|
+
add(index.byElement, target, tween);
|
|
29599
|
+
const id = target.id;
|
|
29600
|
+
if (id) add(index.byId, id, tween);
|
|
29601
|
+
}
|
|
29602
|
+
}
|
|
29603
|
+
return index;
|
|
29604
|
+
}
|
|
29605
|
+
function tweensTargeting(timeline, el) {
|
|
29606
|
+
if (!timeline || typeof timeline !== "object" || !tweenIndexes)
|
|
29607
|
+
return (timeline?.getChildren?.(true) ?? []).filter((tween) => matchesElement(tween, el));
|
|
29608
|
+
let index = tweenIndexes.get(timeline);
|
|
29609
|
+
if (!index) tweenIndexes.set(timeline, index = indexTweens(timeline.getChildren?.(true) ?? []));
|
|
29610
|
+
return (el.id ? index.byId.get(el.id) : index.byElement.get(el)) ?? [];
|
|
29611
|
+
}
|
|
29388
29612
|
|
|
29389
29613
|
// src/hooks/gsapParsedTween.ts
|
|
29390
29614
|
var PARSED_NAME = { scale: "scaleX", rotate: "rotation" };
|
|
@@ -29547,14 +29771,13 @@ var BUILT_IN_EASES = [
|
|
|
29547
29771
|
function withExactStepTimes(anim, tween) {
|
|
29548
29772
|
const data = anim.keyframes;
|
|
29549
29773
|
const parts = tween?.timeline?.getChildren?.() ?? [];
|
|
29550
|
-
|
|
29551
|
-
|
|
29552
|
-
|
|
29553
|
-
|
|
29554
|
-
if (ends.some((end) => !Number.isFinite(end))) return anim;
|
|
29774
|
+
if (data?.format !== "object-array" || parts.length !== data.keyframes.length) return anim;
|
|
29775
|
+
const ends = parts.map((part) => (part.startTime?.() ?? 0) + (part.duration?.() ?? 0));
|
|
29776
|
+
const total = Math.max(...ends);
|
|
29777
|
+
if (!(total > 0) || ends.some((end) => !Number.isFinite(end))) return anim;
|
|
29555
29778
|
const keyframes = data.keyframes.map((kf, i) => ({
|
|
29556
29779
|
...kf,
|
|
29557
|
-
percentage: Math.round(ends[i] * 1e5) / 1e3
|
|
29780
|
+
percentage: Math.round(ends[i] / total * 1e5) / 1e3
|
|
29558
29781
|
}));
|
|
29559
29782
|
return { ...anim, keyframes: { ...data, keyframes } };
|
|
29560
29783
|
}
|
|
@@ -29716,23 +29939,9 @@ function timelinesOf(iframe) {
|
|
|
29716
29939
|
return null;
|
|
29717
29940
|
}
|
|
29718
29941
|
}
|
|
29719
|
-
function isXY(p) {
|
|
29720
|
-
return !!p && typeof p.x === "number" && typeof p.y === "number";
|
|
29721
|
-
}
|
|
29722
29942
|
function isZeroDurationSet(duration) {
|
|
29723
29943
|
return !(duration > 0);
|
|
29724
29944
|
}
|
|
29725
|
-
function coordsFromMotionPath(mp) {
|
|
29726
|
-
if (!mp || typeof mp !== "object") return null;
|
|
29727
|
-
const obj = mp;
|
|
29728
|
-
const pathVal = Array.isArray(mp) ? mp : obj.path;
|
|
29729
|
-
if (!Array.isArray(pathVal)) return null;
|
|
29730
|
-
const coords = pathVal.filter(isXY).map((p) => ({ x: p.x, y: p.y }));
|
|
29731
|
-
if (coords.length < 2) return null;
|
|
29732
|
-
const curviness = typeof obj.curviness === "number" ? obj.curviness : 1;
|
|
29733
|
-
const autoRotate = typeof obj.autoRotate === "number" ? obj.autoRotate : obj.autoRotate === true;
|
|
29734
|
-
return { coords, curviness, autoRotate, isCubic: obj.type === "cubic" };
|
|
29735
|
-
}
|
|
29736
29945
|
function flatTweenKeyframes(vars) {
|
|
29737
29946
|
const properties = {};
|
|
29738
29947
|
for (const [k, v] of Object.entries(vars)) {
|
|
@@ -29746,33 +29955,19 @@ function flatTweenKeyframes(vars) {
|
|
|
29746
29955
|
{ percentage: 100, properties }
|
|
29747
29956
|
];
|
|
29748
29957
|
}
|
|
29958
|
+
var stringOr = (value) => typeof value === "string" ? value : void 0;
|
|
29749
29959
|
function readTween(vars) {
|
|
29960
|
+
const runEase = stringOr(vars.ease);
|
|
29750
29961
|
if (vars.keyframes && typeof vars.keyframes === "object") {
|
|
29751
29962
|
const parsed = parsePercentageKeyframes(vars.keyframes);
|
|
29752
|
-
|
|
29753
|
-
|
|
29754
|
-
const mp = coordsFromMotionPath(vars.motionPath);
|
|
29755
|
-
if (mp) {
|
|
29756
|
-
const shape = buildArcPath(mp.coords, mp.curviness, mp.autoRotate, mp.isCubic);
|
|
29757
|
-
if (shape) {
|
|
29758
|
-
const n = shape.waypoints.length;
|
|
29759
|
-
const keyframes = shape.waypoints.map((wp, i) => ({
|
|
29760
|
-
percentage: n > 1 ? Math.round(i / (n - 1) * 100) : 0,
|
|
29761
|
-
properties: { x: wp.x, y: wp.y }
|
|
29762
|
-
}));
|
|
29763
|
-
return { keyframes, arcPath: shape.arcPath };
|
|
29764
|
-
}
|
|
29963
|
+
const keyframesEase = stringOr(vars.keyframes.ease);
|
|
29964
|
+
if (parsed) return { ...parsed, runEase: keyframesEase ?? runEase };
|
|
29765
29965
|
}
|
|
29966
|
+
const path = readMotionPathTween(vars, runEase);
|
|
29967
|
+
if (path) return path;
|
|
29766
29968
|
const flat = flatTweenKeyframes(vars);
|
|
29767
29969
|
return flat ? { keyframes: flat } : null;
|
|
29768
29970
|
}
|
|
29769
|
-
function matchesElement(tween, el) {
|
|
29770
|
-
if (!tween.targets) return false;
|
|
29771
|
-
for (const t2 of tween.targets()) {
|
|
29772
|
-
if (t2 === el || el.id && t2.id === el.id) return true;
|
|
29773
|
-
}
|
|
29774
|
-
return false;
|
|
29775
|
-
}
|
|
29776
29971
|
function tweenTiming(tween) {
|
|
29777
29972
|
const rawStart = typeof tween.startTime === "function" ? tween.startTime() : 0;
|
|
29778
29973
|
const rawDur = typeof tween.duration === "function" ? tween.duration() : 0;
|
|
@@ -29904,8 +30099,8 @@ var GSAP_TRANSFORM_KEYS = new Set(
|
|
|
29904
30099
|
function gsapWritesChannels(el, channels) {
|
|
29905
30100
|
const win = el.ownerDocument.defaultView;
|
|
29906
30101
|
return Object.values(win?.__timelines ?? {}).some(
|
|
29907
|
-
(tl) => (tl
|
|
29908
|
-
(tween) => !!tween.vars &&
|
|
30102
|
+
(tl) => tweensTargeting(tl, el).some(
|
|
30103
|
+
(tween) => !!tween.vars && (channels.some((ch) => ch in tween.vars) || keyframeVarsCarryChannel(tween.vars, channels))
|
|
29909
30104
|
)
|
|
29910
30105
|
);
|
|
29911
30106
|
}
|
|
@@ -29950,8 +30145,8 @@ function elementHasNonHoldTween(el, channels) {
|
|
|
29950
30145
|
function hasNonHoldTween(timelines, targetEl, channels, compositionId) {
|
|
29951
30146
|
if (!timelines) return false;
|
|
29952
30147
|
for (const tlId of compositionId ? [compositionId] : Object.keys(timelines)) {
|
|
29953
|
-
for (const tween of timelines[tlId]
|
|
29954
|
-
if (!tween.vars
|
|
30148
|
+
for (const tween of tweensTargeting(timelines[tlId], targetEl)) {
|
|
30149
|
+
if (!tween.vars) continue;
|
|
29955
30150
|
const dur = typeof tween.duration === "function" ? tween.duration() : 0;
|
|
29956
30151
|
if (isZeroDurationSet(dur)) continue;
|
|
29957
30152
|
if (channels && keyframeVarsCarryChannel(tween.vars, channels)) return true;
|
|
@@ -29961,16 +30156,17 @@ function hasNonHoldTween(timelines, targetEl, channels, compositionId) {
|
|
|
29961
30156
|
}
|
|
29962
30157
|
return false;
|
|
29963
30158
|
}
|
|
29964
|
-
function toClipRelative(
|
|
30159
|
+
function toClipRelative(read2, tweenStart, tweenDuration, clip) {
|
|
30160
|
+
const { keyframes, runEase } = read2;
|
|
29965
30161
|
if (!clip || clip.duration <= 0) return keyframes;
|
|
29966
30162
|
return keyframes.map((kf) => {
|
|
29967
|
-
const abs = toAbsoluteTime(tweenStart, tweenDuration, kf.percentage);
|
|
30163
|
+
const abs = toAbsoluteTime(tweenStart, tweenDuration, timeAtProgress(runEase, kf.percentage));
|
|
29968
30164
|
return { ...kf, percentage: Math.round((abs - clip.start) / clip.duration * 1e5) / 1e3 };
|
|
29969
30165
|
});
|
|
29970
30166
|
}
|
|
29971
30167
|
function buildEntry(read2, start, duration, clip) {
|
|
29972
30168
|
return {
|
|
29973
|
-
keyframes: toClipRelative(read2
|
|
30169
|
+
keyframes: toClipRelative(read2, start, duration, clip),
|
|
29974
30170
|
tweenStart: start,
|
|
29975
30171
|
tweenDuration: duration,
|
|
29976
30172
|
...read2.easeEach ? { easeEach: read2.easeEach } : {},
|
|
@@ -31971,7 +32167,7 @@ function NLEProvider({
|
|
|
31971
32167
|
}, [projectId, setCompIdToSrc]);
|
|
31972
32168
|
useEffect37(() => {
|
|
31973
32169
|
if (compIdToSrc.size === 0) return;
|
|
31974
|
-
const
|
|
32170
|
+
const patchElements2 = (elements) => {
|
|
31975
32171
|
const map = compIdToSrcRef.current;
|
|
31976
32172
|
if (map.size === 0) return null;
|
|
31977
32173
|
let patched2 = false;
|
|
@@ -31986,7 +32182,7 @@ function NLEProvider({
|
|
|
31986
32182
|
});
|
|
31987
32183
|
return patched2 ? updated : null;
|
|
31988
32184
|
};
|
|
31989
|
-
const patched =
|
|
32185
|
+
const patched = patchElements2(usePlayerStore.getState().elements);
|
|
31990
32186
|
if (patched) usePlayerStore.getState().setElements(patched);
|
|
31991
32187
|
let patching = false;
|
|
31992
32188
|
return usePlayerStore.subscribe((state, prev) => {
|
|
@@ -31994,7 +32190,7 @@ function NLEProvider({
|
|
|
31994
32190
|
if (state.elements === prev.elements || state.elements.length === 0) return;
|
|
31995
32191
|
if (state.elements.every((el) => el.compositionSrc)) return;
|
|
31996
32192
|
patching = true;
|
|
31997
|
-
const result =
|
|
32193
|
+
const result = patchElements2(state.elements);
|
|
31998
32194
|
if (result) state.setElements(result);
|
|
31999
32195
|
patching = false;
|
|
32000
32196
|
});
|
|
@@ -36477,7 +36673,7 @@ function OffCanvasIndicators({
|
|
|
36477
36673
|
const selectOffCanvas = async () => {
|
|
36478
36674
|
const el = elements.current.get(r.key);
|
|
36479
36675
|
if (!el) return;
|
|
36480
|
-
const { resolveDomEditSelection: resolveDomEditSelection2 } = await import("./domEditingLayers-
|
|
36676
|
+
const { resolveDomEditSelection: resolveDomEditSelection2 } = await import("./domEditingLayers-IEVLO54Z.js");
|
|
36481
36677
|
const acp = activeCompositionPathRef.current ?? "index.html";
|
|
36482
36678
|
const sel = await resolveDomEditSelection2(el, {
|
|
36483
36679
|
activeCompositionPath: acp,
|
|
@@ -43729,14 +43925,7 @@ function useMirrorLaneMoveCommit() {
|
|
|
43729
43925
|
// NO readZIndex / onStackingPatches: see the hook doc — the lane→z
|
|
43730
43926
|
// stacking sync must not re-trigger and fight the just-set z values.
|
|
43731
43927
|
},
|
|
43732
|
-
coalesceKey
|
|
43733
|
-
// Unbounded fold window: this record lands only AFTER the z persist's
|
|
43734
|
-
// server round-trip resolved, so the gap between the gesture's two
|
|
43735
|
-
// records exceeds editHistory's 300ms default under real latency and
|
|
43736
|
-
// the fold would silently split into two undo entries. The shared key
|
|
43737
|
-
// is unique per gesture (zReorderCoalesceKey's gesture seq), so the
|
|
43738
|
-
// unbounded window can never merge two distinct user actions.
|
|
43739
|
-
Number.POSITIVE_INFINITY
|
|
43928
|
+
coalesceKey
|
|
43740
43929
|
);
|
|
43741
43930
|
},
|
|
43742
43931
|
[onMoveElements]
|
|
@@ -44869,7 +45058,23 @@ function TopologyLens({ iframeRef, activeCompositionPath }) {
|
|
|
44869
45058
|
);
|
|
44870
45059
|
}
|
|
44871
45060
|
|
|
45061
|
+
// src/utils/sameData.ts
|
|
45062
|
+
function sameData(a, b) {
|
|
45063
|
+
if (Object.is(a, b)) return true;
|
|
45064
|
+
if (!a || !b || typeof a !== "object" || typeof b !== "object") return false;
|
|
45065
|
+
if ("nodeType" in a || "nodeType" in b || Array.isArray(a) !== Array.isArray(b)) return false;
|
|
45066
|
+
const keys = Object.keys(a);
|
|
45067
|
+
if (keys.length !== Object.keys(b).length) return false;
|
|
45068
|
+
return keys.every((key2) => sameData(a[key2], b[key2]));
|
|
45069
|
+
}
|
|
45070
|
+
|
|
44872
45071
|
// src/hooks/gsapKeyframeCacheHelpers.ts
|
|
45072
|
+
function keepEqualEntries(before, draft) {
|
|
45073
|
+
for (const [key2, value] of draft) {
|
|
45074
|
+
const shown2 = before.get(key2);
|
|
45075
|
+
if (shown2 !== void 0 && shown2 !== value && sameData(shown2, value)) draft.set(key2, shown2);
|
|
45076
|
+
}
|
|
45077
|
+
}
|
|
44873
45078
|
function sameEntries(before, after) {
|
|
44874
45079
|
if (before.size !== after.size) return false;
|
|
44875
45080
|
for (const [key2, value] of before) {
|
|
@@ -44884,6 +45089,8 @@ function publishKeyframeCache(edit) {
|
|
|
44884
45089
|
gsapAnimations: new Map(gsapAnimations)
|
|
44885
45090
|
};
|
|
44886
45091
|
edit(draft);
|
|
45092
|
+
keepEqualEntries(keyframeCache, draft.keyframeCache);
|
|
45093
|
+
keepEqualEntries(gsapAnimations, draft.gsapAnimations);
|
|
44887
45094
|
if (sameEntries(keyframeCache, draft.keyframeCache) && sameEntries(gsapAnimations, draft.gsapAnimations))
|
|
44888
45095
|
return;
|
|
44889
45096
|
usePlayerStore.setState({
|
|
@@ -44953,8 +45160,15 @@ function cachedElementIdsForFile(sourceFile, keyframeCache, gsapAnimations) {
|
|
|
44953
45160
|
const sfPrefix = `${sourceFile}#`;
|
|
44954
45161
|
const ids = /* @__PURE__ */ new Set();
|
|
44955
45162
|
for (const key2 of [...keyframeCache.keys(), ...gsapAnimations.keys()]) {
|
|
44956
|
-
if (
|
|
44957
|
-
|
|
45163
|
+
if (key2.startsWith(sfPrefix)) ids.add(key2.slice(sfPrefix.length));
|
|
45164
|
+
}
|
|
45165
|
+
if (sourceFile !== "index.html") return ids;
|
|
45166
|
+
for (const [key2, entry2] of keyframeCache) {
|
|
45167
|
+
const hash = key2.indexOf("#");
|
|
45168
|
+
const id = key2.slice(hash + 1);
|
|
45169
|
+
if (hash > 0 && !key2.startsWith(sfPrefix) && keyframeCache.get(sfPrefix + id) === entry2) {
|
|
45170
|
+
ids.delete(id);
|
|
45171
|
+
}
|
|
44958
45172
|
}
|
|
44959
45173
|
return ids;
|
|
44960
45174
|
}
|
|
@@ -45362,10 +45576,9 @@ function computeCurrentPercentage(selection, animation) {
|
|
|
45362
45576
|
return computeElementPercentage(usePlayerStore.getState().currentTime, selection, animation);
|
|
45363
45577
|
}
|
|
45364
45578
|
function parkPlayheadOnKeyframe(anim, pct) {
|
|
45365
|
-
const
|
|
45366
|
-
|
|
45367
|
-
|
|
45368
|
-
usePlayerStore.getState().requestSeek(roundTo3(ts + pct / 100 * td));
|
|
45579
|
+
const time = percentageToAbsoluteForAnimation(pct, anim);
|
|
45580
|
+
if (time === null || resolveTweenDuration(anim) <= 0) return;
|
|
45581
|
+
usePlayerStore.getState().requestSeek(roundTo3(time));
|
|
45369
45582
|
}
|
|
45370
45583
|
async function replaceKeyframedPositionHold(selection, existingSet, properties, commitMutation) {
|
|
45371
45584
|
const target = newTweenTarget(selection);
|
|
@@ -45740,7 +45953,7 @@ function findGsapPositionAnimation(animations, selector) {
|
|
|
45740
45953
|
}
|
|
45741
45954
|
function playheadProximity(a, currentTime) {
|
|
45742
45955
|
const pos = a.resolvedStart ?? (typeof a.position === "number" ? a.position : 0);
|
|
45743
|
-
const dur = a
|
|
45956
|
+
const dur = resolveTweenDuration(a);
|
|
45744
45957
|
if (currentTime >= pos - 0.05 && currentTime <= pos + dur + 0.05) return 50;
|
|
45745
45958
|
return -Math.round(Math.min(Math.abs(currentTime - pos), Math.abs(currentTime - pos - dur)) * 5);
|
|
45746
45959
|
}
|
|
@@ -45749,7 +45962,9 @@ function statesValueAt(anim, time) {
|
|
|
45749
45962
|
if (!(duration > 0)) return false;
|
|
45750
45963
|
const pct = (time - (resolveTweenStart(anim) ?? 0)) / duration * 100;
|
|
45751
45964
|
const near3 = (keyframe) => Math.abs(keyframe - pct) <= KEYFRAME_PCT_MATCH;
|
|
45752
|
-
|
|
45965
|
+
const runEase = runEaseOf(anim);
|
|
45966
|
+
if (anim.keyframes)
|
|
45967
|
+
return anim.keyframes.keyframes.some((kf) => near3(timeAtProgress(runEase, kf.percentage)));
|
|
45753
45968
|
if (anim.method === "from") return near3(0);
|
|
45754
45969
|
return near3(100) || anim.method === "fromTo" && near3(0);
|
|
45755
45970
|
}
|
|
@@ -46054,8 +46269,6 @@ function tweensForThisElement(selection, animations) {
|
|
|
46054
46269
|
}
|
|
46055
46270
|
|
|
46056
46271
|
// src/hooks/gsapValueAtPlayhead.ts
|
|
46057
|
-
var PERCENTAGE_SEGMENT_EASE = "power1.inOut";
|
|
46058
|
-
var ARRAY_STEP_EASE = "none";
|
|
46059
46272
|
var roundPct2 = (pct) => Math.round(pct * 1e3) / 1e3;
|
|
46060
46273
|
var isLinear = (ease) => !ease || ease === "none" || ease === "linear";
|
|
46061
46274
|
function refuse(reason) {
|
|
@@ -46070,7 +46283,7 @@ function normalize(edit) {
|
|
|
46070
46283
|
const blocked = stepListBlock(anim);
|
|
46071
46284
|
if (blocked) return { reason: blocked };
|
|
46072
46285
|
return {
|
|
46073
|
-
runEase:
|
|
46286
|
+
runEase: runEaseOf(anim),
|
|
46074
46287
|
keyframes: data.keyframes.map((kf) => ({
|
|
46075
46288
|
...kf,
|
|
46076
46289
|
properties: { ...kf.properties },
|
|
@@ -47451,7 +47664,7 @@ function readAllAnimatedProperties(iframe, selector, anim, group) {
|
|
|
47451
47664
|
// src/hooks/gsapDragPositionCommit.ts
|
|
47452
47665
|
function buildTemporalArcKeyframes(anim, percentage, properties) {
|
|
47453
47666
|
return [
|
|
47454
|
-
...(anim
|
|
47667
|
+
...linearArcKeyframes(anim).filter((keyframe) => Math.abs(keyframe.percentage - percentage) > KEYFRAME_PCT_MATCH).map((keyframe) => ({
|
|
47455
47668
|
percentage: keyframe.percentage,
|
|
47456
47669
|
properties: { ...keyframe.properties },
|
|
47457
47670
|
...keyframe.ease ? { ease: keyframe.ease } : {}
|
|
@@ -47459,6 +47672,35 @@ function buildTemporalArcKeyframes(anim, percentage, properties) {
|
|
|
47459
47672
|
{ percentage, properties }
|
|
47460
47673
|
].sort((a, b) => a.percentage - b.percentage);
|
|
47461
47674
|
}
|
|
47675
|
+
function linearArcKeyframes(anim) {
|
|
47676
|
+
const runEase = runEaseOf(anim);
|
|
47677
|
+
return (anim.keyframes?.keyframes ?? []).map((keyframe) => ({
|
|
47678
|
+
...keyframe,
|
|
47679
|
+
percentage: roundTo3(timeAtProgress(runEase, keyframe.percentage))
|
|
47680
|
+
}));
|
|
47681
|
+
}
|
|
47682
|
+
function buildExtendedKeyframes(anim, currentTime, position, sourceDuration = resolveTweenDuration(anim), writeEase = runEaseOf(anim)) {
|
|
47683
|
+
const oldStart = resolveTweenStart(anim) ?? 0;
|
|
47684
|
+
const oldDuration = sourceDuration;
|
|
47685
|
+
const newStart = Math.min(oldStart, currentTime);
|
|
47686
|
+
const newEnd = Math.max(oldStart + oldDuration, currentTime);
|
|
47687
|
+
const newDuration = roundTo3(newEnd - newStart);
|
|
47688
|
+
const oldRunEase = runEaseOf(anim);
|
|
47689
|
+
const toPct = (absoluteTime) => {
|
|
47690
|
+
if (newDuration <= 0) return 0;
|
|
47691
|
+
const progress = progressAtTime(writeEase, (absoluteTime - newStart) / newDuration * 100);
|
|
47692
|
+
return Math.max(0, Math.min(100, Math.round(progress * 10) / 10));
|
|
47693
|
+
};
|
|
47694
|
+
const stops = anim.keyframes?.keyframes ?? [];
|
|
47695
|
+
const rescaled = stops.map((stop2) => ({
|
|
47696
|
+
percentage: toPct(oldStart + timeAtProgress(oldRunEase, stop2.percentage) / 100 * oldDuration),
|
|
47697
|
+
properties: stop2.properties,
|
|
47698
|
+
...stop2.ease ? { ease: stop2.ease } : {}
|
|
47699
|
+
}));
|
|
47700
|
+
const added = { percentage: toPct(currentTime), properties: position };
|
|
47701
|
+
const keyframes = [...rescaled, added].sort((a, b) => a.percentage - b.percentage);
|
|
47702
|
+
return { position: roundTo3(newStart), duration: newDuration, keyframes };
|
|
47703
|
+
}
|
|
47462
47704
|
function gsapPositionFromDragOutcome(selection, anim, studioOffset, gsapPos, iframe) {
|
|
47463
47705
|
if (anim.arcPath?.enabled) return { status: "persisted" };
|
|
47464
47706
|
const { newX, newY, baseGsapX, baseGsapY } = computeDraggedGsapPosition(
|
|
@@ -47487,12 +47729,38 @@ async function commitGsapPositionFromDrag(selection, anim, studioOffset, gsapPos
|
|
|
47487
47729
|
el.removeAttribute("data-hf-drag-initial-offset-y");
|
|
47488
47730
|
};
|
|
47489
47731
|
if (anim.arcPath?.enabled) {
|
|
47490
|
-
const { activeKeyframePct, setActiveKeyframePct } = usePlayerStore.getState();
|
|
47732
|
+
const { activeKeyframePct, currentTime, setActiveKeyframePct } = usePlayerStore.getState();
|
|
47733
|
+
const tweenStart = resolveTweenStart(anim);
|
|
47734
|
+
const tweenDuration = resolveTweenDuration(anim);
|
|
47735
|
+
if (activeKeyframePct === null && tweenStart !== null && !isTimeWithinTween(currentTime, tweenStart, tweenDuration)) {
|
|
47736
|
+
const extended = buildExtendedKeyframes(
|
|
47737
|
+
anim,
|
|
47738
|
+
currentTime,
|
|
47739
|
+
{ x: newX, y: newY },
|
|
47740
|
+
tweenDuration,
|
|
47741
|
+
"none"
|
|
47742
|
+
);
|
|
47743
|
+
await callbacks.commitMutation(
|
|
47744
|
+
selection,
|
|
47745
|
+
{
|
|
47746
|
+
type: "replace-with-keyframes",
|
|
47747
|
+
animationId: anim.id,
|
|
47748
|
+
targetSelector: anim.targetSelector,
|
|
47749
|
+
...extended,
|
|
47750
|
+
ease: "none"
|
|
47751
|
+
},
|
|
47752
|
+
{
|
|
47753
|
+
label: "Move layer (new keyframe)",
|
|
47754
|
+
keyframeAction: "add",
|
|
47755
|
+
softReload: true,
|
|
47756
|
+
beforeReload: restoreOffset
|
|
47757
|
+
}
|
|
47758
|
+
);
|
|
47759
|
+
return { status: "persisted" };
|
|
47760
|
+
}
|
|
47491
47761
|
const pct = activeKeyframePct ?? computeCurrentPercentage(selection, anim);
|
|
47492
47762
|
const keyframes = anim.keyframes?.keyframes ?? [];
|
|
47493
|
-
const pointIndex = keyframes.findIndex(
|
|
47494
|
-
(kf) => Math.abs(kf.percentage - pct) <= KEYFRAME_PCT_MATCH
|
|
47495
|
-
);
|
|
47763
|
+
const pointIndex = keyframes.findIndex((kf) => playsNear(anim, kf.percentage, pct));
|
|
47496
47764
|
if (pointIndex >= 0) {
|
|
47497
47765
|
await callbacks.commitMutation(
|
|
47498
47766
|
selection,
|
|
@@ -47509,11 +47777,10 @@ async function commitGsapPositionFromDrag(selection, anim, studioOffset, gsapPos
|
|
|
47509
47777
|
parkPlayheadOnKeyframe(anim, pct);
|
|
47510
47778
|
return { status: "persisted" };
|
|
47511
47779
|
}
|
|
47512
|
-
const tweenStart = resolveTweenStart(anim);
|
|
47513
|
-
const tweenDuration = resolveEditableTweenDuration(anim, selection);
|
|
47514
47780
|
if (tweenStart === null || tweenDuration <= 0 || keyframes.length < 2)
|
|
47515
47781
|
return { status: "persisted" };
|
|
47516
|
-
const
|
|
47782
|
+
const timePct = roundTo3(timeAtProgress(runEaseOf(anim), pct));
|
|
47783
|
+
const temporalKeyframes = buildTemporalArcKeyframes(anim, timePct, { x: newX, y: newY });
|
|
47517
47784
|
await callbacks.commitMutation(
|
|
47518
47785
|
selection,
|
|
47519
47786
|
{
|
|
@@ -48010,19 +48277,7 @@ function useGsapAnimationsForElement(projectId, sourceFile, target, version, ifr
|
|
|
48010
48277
|
if (isStaticPositionHold(anim)) continue;
|
|
48011
48278
|
const kf = anim.keyframes ?? synthesizeFlatTweenKeyframes(anim);
|
|
48012
48279
|
if (!kf) continue;
|
|
48013
|
-
|
|
48014
|
-
const tweenDur = anim.duration ?? elDuration;
|
|
48015
|
-
for (const k of kf.keyframes) {
|
|
48016
|
-
const absTime = toAbsoluteTime(tweenPos, tweenDur, k.percentage);
|
|
48017
|
-
const clipPct = toClipPercentage(absTime, elStart, elDuration, k.percentage);
|
|
48018
|
-
allKeyframes.push({
|
|
48019
|
-
...k,
|
|
48020
|
-
percentage: clipPct,
|
|
48021
|
-
tweenPercentage: k.percentage,
|
|
48022
|
-
propertyGroup: anim.propertyGroup,
|
|
48023
|
-
animationId: anim.id
|
|
48024
|
-
});
|
|
48025
|
-
}
|
|
48280
|
+
allKeyframes.push(...toClipKeyframes(kf.keyframes, anim, elStart, elDuration));
|
|
48026
48281
|
format = kf.format;
|
|
48027
48282
|
if (kf.ease) ease = kf.ease;
|
|
48028
48283
|
if (kf.easeEach) easeEach = kf.easeEach;
|
|
@@ -48074,11 +48329,11 @@ function usePopulateKeyframeCacheForFile(projectId, sourceFile, version, iframeR
|
|
|
48074
48329
|
const files = Array.from(
|
|
48075
48330
|
/* @__PURE__ */ new Set([sourceFile, ...compositionSrcKey ? compositionSrcKey.split("|") : []])
|
|
48076
48331
|
);
|
|
48077
|
-
const
|
|
48078
|
-
const stale =
|
|
48079
|
-
const covered =
|
|
48332
|
+
const sameData2 = loadedRef.current.dataKey === dataKey;
|
|
48333
|
+
const stale = sameData2 ? files.filter((sf) => !loadedRef.current.files.has(sf)) : files;
|
|
48334
|
+
const covered = sameData2 ? /* @__PURE__ */ new Set([...loadedRef.current.files, ...files]) : new Set(files);
|
|
48080
48335
|
loadedRef.current = { dataKey, files: covered };
|
|
48081
|
-
if (!
|
|
48336
|
+
if (!sameData2) pruneKeyframeCacheToFiles(files);
|
|
48082
48337
|
if (stale.length === 0) return;
|
|
48083
48338
|
runtimeScanDoneRef.current = "";
|
|
48084
48339
|
const doc = iframeRef?.current?.contentDocument;
|
|
@@ -48155,7 +48410,7 @@ function resolveFlatTweenBoundaryRetime(opts) {
|
|
|
48155
48410
|
};
|
|
48156
48411
|
}
|
|
48157
48412
|
function resolveKeyframeRetime(opts) {
|
|
48158
|
-
const { keyframes, draggedTweenPct, tweenStart, tweenDuration, dropAbsTime } = opts;
|
|
48413
|
+
const { keyframes, draggedTweenPct, tweenStart, tweenDuration, dropAbsTime, runEase } = opts;
|
|
48159
48414
|
if (tweenDuration <= 0) return { kind: "noop" };
|
|
48160
48415
|
const tweenEnd = tweenStart + tweenDuration;
|
|
48161
48416
|
const flatBoundary = resolveFlatTweenBoundaryRetime({
|
|
@@ -48167,7 +48422,8 @@ function resolveKeyframeRetime(opts) {
|
|
|
48167
48422
|
});
|
|
48168
48423
|
if (flatBoundary) return flatBoundary;
|
|
48169
48424
|
if (dropAbsTime >= tweenStart - EPSILON_TIME && dropAbsTime <= tweenEnd + EPSILON_TIME) {
|
|
48170
|
-
const
|
|
48425
|
+
const dropTimePct = clamp5((dropAbsTime - tweenStart) / tweenDuration * 100, 0, 100);
|
|
48426
|
+
const toTweenPct = round33(progressAtTime(runEase, dropTimePct));
|
|
48171
48427
|
if (Math.abs(toTweenPct - draggedTweenPct) < NOOP_EPSILON_PCT2) return { kind: "noop" };
|
|
48172
48428
|
return { kind: "move", toTweenPct };
|
|
48173
48429
|
}
|
|
@@ -48185,8 +48441,9 @@ function resolveKeyframeRetime(opts) {
|
|
|
48185
48441
|
}
|
|
48186
48442
|
});
|
|
48187
48443
|
const pctRemap = keyframes.map((kf, i) => {
|
|
48188
|
-
const absTime = i === draggedIdx ? dropAbsTime : tweenStart + kf.percentage / 100 * tweenDuration;
|
|
48189
|
-
|
|
48444
|
+
const absTime = i === draggedIdx ? dropAbsTime : tweenStart + timeAtProgress(runEase, kf.percentage) / 100 * tweenDuration;
|
|
48445
|
+
const newTimePct = (absTime - newStart) / newDuration * 100;
|
|
48446
|
+
return { from: kf.percentage, to: round33(progressAtTime(runEase, newTimePct)) };
|
|
48190
48447
|
});
|
|
48191
48448
|
return {
|
|
48192
48449
|
kind: "resize",
|
|
@@ -48365,14 +48622,15 @@ function useTimelineEditCallbacks({
|
|
|
48365
48622
|
elements,
|
|
48366
48623
|
domClipChildren
|
|
48367
48624
|
);
|
|
48368
|
-
const tweenDuration = resolveTweenDuration(anim
|
|
48625
|
+
const tweenDuration = resolveTweenDuration(anim);
|
|
48369
48626
|
const dropAbsTime = elStart + toClipPct / 100 * elDuration;
|
|
48370
48627
|
const decision = resolveKeyframeRetime({
|
|
48371
48628
|
keyframes: anim.keyframes?.keyframes ?? [],
|
|
48372
48629
|
draggedTweenPct: target.tweenPct,
|
|
48373
48630
|
tweenStart,
|
|
48374
48631
|
tweenDuration,
|
|
48375
|
-
dropAbsTime
|
|
48632
|
+
dropAbsTime,
|
|
48633
|
+
runEase: runEaseOf(anim)
|
|
48376
48634
|
});
|
|
48377
48635
|
if (decision.kind === "move" && decision.toTweenPct != null) {
|
|
48378
48636
|
return handleGsapMoveKeyframe(target.animId, target.tweenPct, decision.toTweenPct, sel);
|
|
@@ -73362,6 +73620,7 @@ function usePersistentEditHistory({ projectId }) {
|
|
|
73362
73620
|
const projectIdRef = useRef125(projectId);
|
|
73363
73621
|
projectIdRef.current = projectId;
|
|
73364
73622
|
const [own] = useState111(createOwnHistory);
|
|
73623
|
+
const [pageKeyScope] = useState111(generateId);
|
|
73365
73624
|
const refresh2 = useCallback100(async () => {
|
|
73366
73625
|
if (!projectId) return;
|
|
73367
73626
|
const seen = own.changes();
|
|
@@ -73379,6 +73638,10 @@ function usePersistentEditHistory({ projectId }) {
|
|
|
73379
73638
|
own.clear();
|
|
73380
73639
|
void refresh2().finally(() => setLoaded(true));
|
|
73381
73640
|
}, [refresh2, own]);
|
|
73641
|
+
useEffect111(() => {
|
|
73642
|
+
setStudioPendingEditClaimClock(own.claimCount);
|
|
73643
|
+
return () => setStudioPendingEditClaimClock(null);
|
|
73644
|
+
}, [own]);
|
|
73382
73645
|
const recordEdit = useCallback100(
|
|
73383
73646
|
async ({ label, coalesceKey, coalesceMs, files, created = [] }) => {
|
|
73384
73647
|
if (!projectId) return;
|
|
@@ -73388,7 +73651,10 @@ function usePersistentEditHistory({ projectId }) {
|
|
|
73388
73651
|
label,
|
|
73389
73652
|
paths,
|
|
73390
73653
|
overwrote: await overwroteVersions(files),
|
|
73391
|
-
...coalesceKey && {
|
|
73654
|
+
...coalesceKey && {
|
|
73655
|
+
coalesceKey: `${pageKeyScope}:${coalesceKey}`,
|
|
73656
|
+
idleMs: coalesceMs ?? DEFAULT_COALESCE_MS
|
|
73657
|
+
}
|
|
73392
73658
|
});
|
|
73393
73659
|
const claimed = claimHeld(reply, label);
|
|
73394
73660
|
if (claimed) {
|
|
@@ -73398,7 +73664,7 @@ function usePersistentEditHistory({ projectId }) {
|
|
|
73398
73664
|
heldClaimRef.current = claimed && coalesceKey ? { paths, at: Date.now() } : null;
|
|
73399
73665
|
await refresh2();
|
|
73400
73666
|
},
|
|
73401
|
-
[projectId, refresh2, own]
|
|
73667
|
+
[projectId, refresh2, own, pageKeyScope]
|
|
73402
73668
|
);
|
|
73403
73669
|
const step = useCallback100(
|
|
73404
73670
|
async (direction, callbacks) => {
|
|
@@ -74665,6 +74931,10 @@ function usePreviewPersistence({
|
|
|
74665
74931
|
[applyCurrentStudioManualEditsToPreview, previewIframeRef]
|
|
74666
74932
|
);
|
|
74667
74933
|
applyStudioManualEditsToPreviewRef.current = applyStudioManualEditsToPreview;
|
|
74934
|
+
const heldPreviewDoc = useCallback106(() => {
|
|
74935
|
+
const doc = previewIframeRef.current?.contentDocument;
|
|
74936
|
+
return doc && isStudioManualEditGestureLiveIn(doc) ? doc : null;
|
|
74937
|
+
}, [previewIframeRef]);
|
|
74668
74938
|
const syncHistoryPreviewAfterApply = useCallback106(
|
|
74669
74939
|
async (restore2) => {
|
|
74670
74940
|
const projectId = usePlayerStore.getState().timelineProjectId;
|
|
@@ -74676,6 +74946,15 @@ function usePreviewPersistence({
|
|
|
74676
74946
|
(path) => projectId ? readProjectFileContent(projectId, path) : Promise.reject(new Error("No project is open to read nested files from."))
|
|
74677
74947
|
)
|
|
74678
74948
|
);
|
|
74949
|
+
const held = heldPreviewDoc();
|
|
74950
|
+
if (held) {
|
|
74951
|
+
const paths = restore2.paths ?? Object.keys(restore2.files ?? {});
|
|
74952
|
+
afterStudioManualEditGestures(held, () => {
|
|
74953
|
+
markScenesStale(previewIframeRef.current, paths);
|
|
74954
|
+
reloadPreview();
|
|
74955
|
+
});
|
|
74956
|
+
return;
|
|
74957
|
+
}
|
|
74679
74958
|
const strategy = applyUndoRestoreToPreview(
|
|
74680
74959
|
previewIframeRef.current,
|
|
74681
74960
|
activeCompPathRef.current,
|
|
@@ -74690,21 +74969,23 @@ function usePreviewPersistence({
|
|
|
74690
74969
|
}
|
|
74691
74970
|
syncStoredAutomationFromPreview(previewIframeRef.current?.contentDocument ?? null);
|
|
74692
74971
|
},
|
|
74693
|
-
[previewIframeRef, activeCompPathRef, reloadPreview]
|
|
74972
|
+
[previewIframeRef, activeCompPathRef, reloadPreview, heldPreviewDoc]
|
|
74694
74973
|
);
|
|
74695
74974
|
const showHistoryRestoreNow = useCallback106(
|
|
74696
74975
|
(files) => {
|
|
74697
|
-
if (!domEditSaveQueueRef.current?.isIdle() || hasStudioPendingEdits()
|
|
74976
|
+
if (!domEditSaveQueueRef.current?.isIdle() || hasStudioPendingEdits() || heldPreviewDoc())
|
|
74977
|
+
return null;
|
|
74698
74978
|
const iframe = previewIframeRef.current;
|
|
74699
74979
|
const now2 = () => usePlayerStore.getState().currentTime;
|
|
74700
74980
|
const putBack = showRestoreInPlace(iframe, activeCompPathRef.current, files, now2());
|
|
74701
74981
|
if (!putBack) return null;
|
|
74702
74982
|
return () => {
|
|
74703
|
-
if (putBack(now2()))
|
|
74983
|
+
if (!heldPreviewDoc() && putBack(now2()))
|
|
74984
|
+
syncStoredAutomationFromPreview(iframe?.contentDocument ?? null);
|
|
74704
74985
|
else void syncHistoryPreviewAfterApply({ paths: Object.keys(files) });
|
|
74705
74986
|
};
|
|
74706
74987
|
},
|
|
74707
|
-
[previewIframeRef, activeCompPathRef, syncHistoryPreviewAfterApply]
|
|
74988
|
+
[previewIframeRef, activeCompPathRef, syncHistoryPreviewAfterApply, heldPreviewDoc]
|
|
74708
74989
|
);
|
|
74709
74990
|
useMountEffect(() => {
|
|
74710
74991
|
void clearLegacyStudioMotionFile(_readOptionalProjectFile, _writeProjectFile);
|
|
@@ -74750,7 +75031,7 @@ function usePreviewDocumentVersion() {
|
|
|
74750
75031
|
}
|
|
74751
75032
|
|
|
74752
75033
|
// src/hooks/useTimelineEditing.ts
|
|
74753
|
-
import {
|
|
75034
|
+
import { useRef as useRef139 } from "react";
|
|
74754
75035
|
|
|
74755
75036
|
// src/hooks/useRazorSplit.ts
|
|
74756
75037
|
import { useCallback as useCallback109, useRef as useRef133 } from "react";
|
|
@@ -76050,7 +76331,6 @@ function applyRippleShifts(survivors, changes) {
|
|
|
76050
76331
|
return newStart != null ? { ...te, start: newStart } : te;
|
|
76051
76332
|
});
|
|
76052
76333
|
}
|
|
76053
|
-
var deleteGestureSeq = 0;
|
|
76054
76334
|
function unlinkInSource(source, survivors) {
|
|
76055
76335
|
const targets = survivors.map(buildPatchTarget).filter((target) => target !== null);
|
|
76056
76336
|
return targets.length > 0 ? setLinkInSource(source, targets, null) : source;
|
|
@@ -76086,7 +76366,7 @@ function useTimelineDeleteOps({
|
|
|
76086
76366
|
(candidate) => (candidate.sourceFile || activeCompPath || "index.html") === targetPath
|
|
76087
76367
|
);
|
|
76088
76368
|
try {
|
|
76089
|
-
const
|
|
76369
|
+
const step = timingGestureStep("main-track-ripple-delete");
|
|
76090
76370
|
const deleteHistoryLabel = "Delete timeline clip";
|
|
76091
76371
|
let rollbackDuration = () => {
|
|
76092
76372
|
};
|
|
@@ -76095,7 +76375,7 @@ function useTimelineDeleteOps({
|
|
|
76095
76375
|
projectId: pid,
|
|
76096
76376
|
path: targetPath,
|
|
76097
76377
|
label: deleteHistoryLabel,
|
|
76098
|
-
|
|
76378
|
+
...step,
|
|
76099
76379
|
writeFile: writeProjectFile,
|
|
76100
76380
|
recordEdit,
|
|
76101
76381
|
rewrite: async (originalContent) => {
|
|
@@ -76152,8 +76432,7 @@ function useTimelineDeleteOps({
|
|
|
76152
76432
|
if (rippleChanges) {
|
|
76153
76433
|
try {
|
|
76154
76434
|
await handleTimelineGroupMove(rippleChanges, {
|
|
76155
|
-
coalesceKey,
|
|
76156
|
-
coalesceMs: Number.POSITIVE_INFINITY,
|
|
76435
|
+
coalesceKey: step.coalesceKey,
|
|
76157
76436
|
// Coalescing keeps the LAST entry's label; without this the undo
|
|
76158
76437
|
// toast reads "Undid Move timeline clips" after a delete, naming
|
|
76159
76438
|
// the ripple's mechanics instead of what the user actually did.
|
|
@@ -76281,7 +76560,7 @@ function planSyncEdit(edit) {
|
|
|
76281
76560
|
property: "start",
|
|
76282
76561
|
value: value2
|
|
76283
76562
|
});
|
|
76284
|
-
return
|
|
76563
|
+
return syncCompositionDurationToContent(moved);
|
|
76285
76564
|
}
|
|
76286
76565
|
};
|
|
76287
76566
|
}
|
|
@@ -76925,12 +77204,6 @@ function allChangesSharePath(changes, activeCompPath) {
|
|
|
76925
77204
|
if (!firstPath) return null;
|
|
76926
77205
|
return changes.every((change) => targetPathFor(change.element, activeCompPath) === firstPath) ? firstPath : null;
|
|
76927
77206
|
}
|
|
76928
|
-
function moveCoalesceKey(changes) {
|
|
76929
|
-
return `timeline-group-move:${changes.map((change) => change.element.hfId ?? change.element.id).join(",")}`;
|
|
76930
|
-
}
|
|
76931
|
-
function resizeCoalesceKey(changes) {
|
|
76932
|
-
return `timeline-group-resize:${changes.map((change) => change.element.hfId ?? change.element.id).join(",")}`;
|
|
76933
|
-
}
|
|
76934
77207
|
function toSdkTimingChanges(changes, timingUpdate) {
|
|
76935
77208
|
return changes.map(
|
|
76936
77209
|
(change) => change.element.hfId ? { hfId: change.element.hfId, timingUpdate: timingUpdate(change) } : null
|
|
@@ -77053,8 +77326,7 @@ function useTimelineGroupEditing({
|
|
|
77053
77326
|
const rollbackDuration = captureDurationRollback(previewIframeRef.current);
|
|
77054
77327
|
const needsExtension = extendRootDurationIfNeeded(maxEnd);
|
|
77055
77328
|
syncPreviewContentDuration(previewIframeRef.current);
|
|
77056
|
-
const coalesceKey
|
|
77057
|
-
const coalesceMs = options?.coalesceMs;
|
|
77329
|
+
const { coalesceKey, coalesceMs } = timingGestureStep("timeline-group-move", options);
|
|
77058
77330
|
const label = options?.label ?? "Move timeline clips";
|
|
77059
77331
|
return enqueueGroupOperation(label, async (projectId) => {
|
|
77060
77332
|
await options?.beforeTiming;
|
|
@@ -77077,7 +77349,7 @@ function useTimelineGroupEditing({
|
|
|
77077
77349
|
label,
|
|
77078
77350
|
changes.map((change) => ({
|
|
77079
77351
|
element: change.element,
|
|
77080
|
-
buildPatches: (original, target) =>
|
|
77352
|
+
buildPatches: (original, target) => applyTimelineMoveAttributes(
|
|
77081
77353
|
original,
|
|
77082
77354
|
target,
|
|
77083
77355
|
toAuthoredStart(change.element, change.start),
|
|
@@ -77105,12 +77377,7 @@ function useTimelineGroupEditing({
|
|
|
77105
77377
|
changes,
|
|
77106
77378
|
sdkGsap: sdk?.sdkGsap,
|
|
77107
77379
|
resolveChangePath: (element) => targetPathFor(element, activeCompPath),
|
|
77108
|
-
|
|
77109
|
-
const delta = change.start - change.element.start;
|
|
77110
|
-
const domId = change.element.domId;
|
|
77111
|
-
if (delta === 0 || !domId) return null;
|
|
77112
|
-
return shiftGsapPositions(projectId, changePath, domId, delta);
|
|
77113
|
-
}
|
|
77380
|
+
mutationFor: (change) => shiftGsapMutation(change.element.domId, change.start - change.element.start)
|
|
77114
77381
|
});
|
|
77115
77382
|
} finally {
|
|
77116
77383
|
invalidateGsapCache?.();
|
|
@@ -77159,8 +77426,7 @@ function useTimelineGroupEditing({
|
|
|
77159
77426
|
const rollbackDuration = captureDurationRollback(previewIframeRef.current);
|
|
77160
77427
|
const needsExtension = extendRootDurationIfNeeded(maxEnd);
|
|
77161
77428
|
syncPreviewContentDuration(previewIframeRef.current);
|
|
77162
|
-
const coalesceKey
|
|
77163
|
-
const coalesceMs = options?.coalesceMs;
|
|
77429
|
+
const { coalesceKey, coalesceMs } = timingGestureStep("timeline-group-resize", options);
|
|
77164
77430
|
return enqueueGroupOperation("Resize timeline clips", async (projectId) => {
|
|
77165
77431
|
await options?.beforeTiming;
|
|
77166
77432
|
const sdk = await trySdkBatchPersist({
|
|
@@ -77181,7 +77447,7 @@ function useTimelineGroupEditing({
|
|
|
77181
77447
|
"Resize timeline clips",
|
|
77182
77448
|
changes.map((change) => ({
|
|
77183
77449
|
element: change.element,
|
|
77184
|
-
buildPatches: (original, target) =>
|
|
77450
|
+
buildPatches: (original, target) => applyTimelineResizeAttributes(original, target, change.element, {
|
|
77185
77451
|
start: change.start,
|
|
77186
77452
|
duration: change.duration,
|
|
77187
77453
|
playbackStart: change.playbackStart
|
|
@@ -77205,20 +77471,13 @@ function useTimelineGroupEditing({
|
|
|
77205
77471
|
changes,
|
|
77206
77472
|
sdkGsap: sdk?.sdkGsap,
|
|
77207
77473
|
resolveChangePath: (element) => targetPathFor(element, activeCompPath),
|
|
77208
|
-
|
|
77209
|
-
|
|
77210
|
-
|
|
77211
|
-
|
|
77212
|
-
|
|
77213
|
-
|
|
77214
|
-
|
|
77215
|
-
domId,
|
|
77216
|
-
toCompositionTime(change.element, change.element.start),
|
|
77217
|
-
change.element.duration,
|
|
77218
|
-
toCompositionTime(change.element, change.start),
|
|
77219
|
-
change.duration
|
|
77220
|
-
);
|
|
77221
|
-
}
|
|
77474
|
+
mutationFor: (change) => scaleGsapMutation(
|
|
77475
|
+
change.element.domId,
|
|
77476
|
+
toCompositionTime(change.element, change.element.start),
|
|
77477
|
+
change.element.duration,
|
|
77478
|
+
toCompositionTime(change.element, change.start),
|
|
77479
|
+
change.duration
|
|
77480
|
+
)
|
|
77222
77481
|
});
|
|
77223
77482
|
} finally {
|
|
77224
77483
|
invalidateGsapCache?.();
|
|
@@ -77245,54 +77504,28 @@ function useTimelineGroupEditing({
|
|
|
77245
77504
|
return { handleTimelineGroupMove, handleTimelineGroupResize };
|
|
77246
77505
|
}
|
|
77247
77506
|
|
|
77248
|
-
// src/hooks/
|
|
77249
|
-
import { useCallback as useCallback122
|
|
77250
|
-
|
|
77251
|
-
function useBlockedTimelineEditToast(showToast) {
|
|
77252
|
-
const lastAtRef = useRef138(0);
|
|
77253
|
-
return useCallback122(
|
|
77254
|
-
(_element, intent) => {
|
|
77255
|
-
const now2 = Date.now();
|
|
77256
|
-
if (now2 - lastAtRef.current < BLOCKED_TOAST_INTERVAL_MS) return;
|
|
77257
|
-
lastAtRef.current = now2;
|
|
77258
|
-
showToast(
|
|
77259
|
-
intent === "edit-many" ? `Select ${MAX_HAND_EDIT_CLIPS} or fewer clips to move or resize by hand, or describe the change.` : "This clip can't be moved or resized from the timeline yet.",
|
|
77260
|
-
"info"
|
|
77261
|
-
);
|
|
77262
|
-
},
|
|
77263
|
-
[showToast]
|
|
77264
|
-
);
|
|
77265
|
-
}
|
|
77266
|
-
|
|
77267
|
-
// src/hooks/useTimelineEditing.ts
|
|
77268
|
-
function useTimelineEditing({
|
|
77269
|
-
projectId,
|
|
77507
|
+
// src/hooks/useTimelineClipTimingEditing.ts
|
|
77508
|
+
import { useCallback as useCallback122 } from "react";
|
|
77509
|
+
function useTimelineClipTimingEditing({
|
|
77270
77510
|
activeCompPath,
|
|
77271
77511
|
timelineElements,
|
|
77272
77512
|
showToast,
|
|
77273
77513
|
writeProjectFile,
|
|
77274
|
-
observeProjectFileVersion,
|
|
77275
77514
|
recordEdit,
|
|
77276
77515
|
reloadPreview,
|
|
77277
77516
|
previewIframeRef,
|
|
77278
77517
|
pendingTimelineEditPathRef,
|
|
77279
|
-
uploadProjectFiles,
|
|
77280
77518
|
isRecordingRef,
|
|
77281
77519
|
sdkSession,
|
|
77282
77520
|
publishSdkSession,
|
|
77283
77521
|
forceReloadSdkSession,
|
|
77284
77522
|
invalidateGsapCache,
|
|
77285
77523
|
handleDomZIndexReorderCommitRef,
|
|
77286
|
-
|
|
77524
|
+
editQueueRef,
|
|
77525
|
+
projectIdRef
|
|
77287
77526
|
}) {
|
|
77288
|
-
const
|
|
77289
|
-
|
|
77290
|
-
const editQueueRef = useRef139(Promise.resolve());
|
|
77291
|
-
const track2 = useTrackPendingTimelineEdit();
|
|
77292
|
-
const checkEditable = useTimelineEditGate(canEdit, showToast);
|
|
77293
|
-
const guard = useTimelineEditGuard(canEdit, showToast);
|
|
77294
|
-
const enqueueEdit = useCallback123(
|
|
77295
|
-
(element, label, buildPatches, coalesceKey) => {
|
|
77527
|
+
const enqueueEdit = useCallback122(
|
|
77528
|
+
(element, label, buildPatches, step) => {
|
|
77296
77529
|
if (isRecordingRef?.current) {
|
|
77297
77530
|
showToast("Cannot edit timeline while recording", "error");
|
|
77298
77531
|
return Promise.resolve();
|
|
@@ -77309,7 +77542,7 @@ function useTimelineEditing({
|
|
|
77309
77542
|
writeProjectFile,
|
|
77310
77543
|
recordEdit,
|
|
77311
77544
|
pendingTimelineEditPathRef,
|
|
77312
|
-
|
|
77545
|
+
...step
|
|
77313
77546
|
})
|
|
77314
77547
|
).then(() => {
|
|
77315
77548
|
forceReloadSdkSession?.();
|
|
@@ -77326,26 +77559,12 @@ function useTimelineEditing({
|
|
|
77326
77559
|
pendingTimelineEditPathRef,
|
|
77327
77560
|
showToast,
|
|
77328
77561
|
isRecordingRef,
|
|
77329
|
-
forceReloadSdkSession
|
|
77562
|
+
forceReloadSdkSession,
|
|
77563
|
+
editQueueRef,
|
|
77564
|
+
projectIdRef
|
|
77330
77565
|
]
|
|
77331
77566
|
);
|
|
77332
|
-
const
|
|
77333
|
-
activeCompPath,
|
|
77334
|
-
editQueueRef,
|
|
77335
|
-
forceReloadSdkSession,
|
|
77336
|
-
invalidateGsapCache,
|
|
77337
|
-
isRecordingRef,
|
|
77338
|
-
pendingTimelineEditPathRef,
|
|
77339
|
-
previewIframeRef,
|
|
77340
|
-
projectIdRef,
|
|
77341
|
-
recordEdit,
|
|
77342
|
-
reloadPreview,
|
|
77343
|
-
sdkSession,
|
|
77344
|
-
publishSdkSession,
|
|
77345
|
-
showToast,
|
|
77346
|
-
writeProjectFile
|
|
77347
|
-
});
|
|
77348
|
-
const handleTimelineElementMove = useCallback123(
|
|
77567
|
+
const handleTimelineElementMove = useCallback122(
|
|
77349
77568
|
// fallow-ignore-next-line complexity
|
|
77350
77569
|
(element, updates) => {
|
|
77351
77570
|
const commitMove = () => {
|
|
@@ -77375,16 +77594,16 @@ function useTimelineEditing({
|
|
|
77375
77594
|
const needsExtension = extendRootDurationIfNeeded(updates.start + element.duration);
|
|
77376
77595
|
syncPreviewContentDuration(previewIframeRef.current);
|
|
77377
77596
|
const buildMovePatches = (original, target) => {
|
|
77378
|
-
const
|
|
77597
|
+
const track2 = trackChanged ? updates.track : void 0;
|
|
77379
77598
|
return buildTimelineMoveTimingPatch(
|
|
77380
77599
|
original,
|
|
77381
77600
|
target,
|
|
77382
77601
|
authoredStart,
|
|
77383
77602
|
element.duration,
|
|
77384
|
-
|
|
77603
|
+
track2
|
|
77385
77604
|
);
|
|
77386
77605
|
};
|
|
77387
|
-
const
|
|
77606
|
+
const step = timingGestureStep("timeline-move");
|
|
77388
77607
|
const finishMoveGsapSync = (sdkGsap) => (
|
|
77389
77608
|
// One GSAP sync per edit: the SDK commit's own (sdkGsap), else the server rewrite here.
|
|
77390
77609
|
finishClipTimingFallback({
|
|
@@ -77394,14 +77613,14 @@ function useTimelineEditing({
|
|
|
77394
77613
|
targetPath,
|
|
77395
77614
|
domId: element.domId,
|
|
77396
77615
|
label: "Move timeline clip",
|
|
77397
|
-
coalesceKey,
|
|
77616
|
+
coalesceKey: step.coalesceKey,
|
|
77398
77617
|
recordEdit,
|
|
77399
77618
|
writeProjectFile,
|
|
77400
77619
|
edit: { kind: "shift", delta: updates.start - element.start },
|
|
77401
77620
|
sdkGsap
|
|
77402
77621
|
}).finally(() => invalidateGsapCache?.())
|
|
77403
77622
|
);
|
|
77404
|
-
const moveFallback = () => enqueueEdit(element, "Move timeline clip", buildMovePatches,
|
|
77623
|
+
const moveFallback = () => enqueueEdit(element, "Move timeline clip", buildMovePatches, step).then(
|
|
77405
77624
|
() => finishMoveGsapSync()
|
|
77406
77625
|
);
|
|
77407
77626
|
return reorderDone.then(() => {
|
|
@@ -77421,7 +77640,7 @@ function useTimelineEditing({
|
|
|
77421
77640
|
readProjectFile: (path) => readFileContent(projectIdRef.current ?? "", path),
|
|
77422
77641
|
publishSession: publishSdkSession
|
|
77423
77642
|
},
|
|
77424
|
-
{ label: "Move timeline clip",
|
|
77643
|
+
{ label: "Move timeline clip", ...step, skipRefresh: true }
|
|
77425
77644
|
).then((result) => {
|
|
77426
77645
|
if (!cutoverCommittedOrThrow(result)) return moveFallback();
|
|
77427
77646
|
return finishMoveGsapSync(sdkTimingGsapSync(result));
|
|
@@ -77448,10 +77667,11 @@ function useTimelineEditing({
|
|
|
77448
77667
|
timelineElements,
|
|
77449
77668
|
handleDomZIndexReorderCommitRef,
|
|
77450
77669
|
showToast,
|
|
77451
|
-
invalidateGsapCache
|
|
77670
|
+
invalidateGsapCache,
|
|
77671
|
+
projectIdRef
|
|
77452
77672
|
]
|
|
77453
77673
|
);
|
|
77454
|
-
const handleTimelineElementResize =
|
|
77674
|
+
const handleTimelineElementResize = useCallback122(
|
|
77455
77675
|
// fallow-ignore-next-line complexity
|
|
77456
77676
|
(element, updates) => {
|
|
77457
77677
|
const authoredStart = toAuthoredStart(element, updates.start);
|
|
@@ -77472,7 +77692,7 @@ function useTimelineEditing({
|
|
|
77472
77692
|
return buildTimelineResizeTimingPatch(original, target, element, updates);
|
|
77473
77693
|
};
|
|
77474
77694
|
const hasPbsAdjustment = updates.playbackStart != null || updates.start !== element.start && element.playbackStart != null;
|
|
77475
|
-
const
|
|
77695
|
+
const step = timingGestureStep("timeline-resize");
|
|
77476
77696
|
const finishResizeGsapSync = (sdkGsap) => finishClipTimingFallback({
|
|
77477
77697
|
iframe: previewIframeRef.current,
|
|
77478
77698
|
reloadPreview,
|
|
@@ -77480,7 +77700,7 @@ function useTimelineEditing({
|
|
|
77480
77700
|
targetPath,
|
|
77481
77701
|
domId: element.domId,
|
|
77482
77702
|
label: "Resize timeline clip",
|
|
77483
|
-
coalesceKey,
|
|
77703
|
+
coalesceKey: step.coalesceKey,
|
|
77484
77704
|
recordEdit,
|
|
77485
77705
|
writeProjectFile,
|
|
77486
77706
|
edit: {
|
|
@@ -77490,7 +77710,7 @@ function useTimelineEditing({
|
|
|
77490
77710
|
},
|
|
77491
77711
|
sdkGsap
|
|
77492
77712
|
}).finally(() => invalidateGsapCache?.());
|
|
77493
|
-
const resizeFallback = () => enqueueEdit(element, "Resize timeline clip", buildResizePatches,
|
|
77713
|
+
const resizeFallback = () => enqueueEdit(element, "Resize timeline clip", buildResizePatches, step).then(
|
|
77494
77714
|
() => finishResizeGsapSync()
|
|
77495
77715
|
);
|
|
77496
77716
|
const persistDone = sdkSession && element.hfId && !element.link && !hasPbsAdjustment && !needsExtension ? sdkTimingPersist(
|
|
@@ -77508,7 +77728,7 @@ function useTimelineEditing({
|
|
|
77508
77728
|
readProjectFile: (path) => readFileContent(projectIdRef.current ?? "", path),
|
|
77509
77729
|
publishSession: publishSdkSession
|
|
77510
77730
|
},
|
|
77511
|
-
{ label: "Resize timeline clip",
|
|
77731
|
+
{ label: "Resize timeline clip", ...step, skipRefresh: true }
|
|
77512
77732
|
).then((result) => {
|
|
77513
77733
|
if (!cutoverCommittedOrThrow(result)) return resizeFallback();
|
|
77514
77734
|
return finishResizeGsapSync(sdkTimingGsapSync(result));
|
|
@@ -77529,9 +77749,90 @@ function useTimelineEditing({
|
|
|
77529
77749
|
writeProjectFile,
|
|
77530
77750
|
reloadPreview,
|
|
77531
77751
|
showToast,
|
|
77532
|
-
invalidateGsapCache
|
|
77752
|
+
invalidateGsapCache,
|
|
77753
|
+
projectIdRef
|
|
77533
77754
|
]
|
|
77534
77755
|
);
|
|
77756
|
+
return { handleTimelineElementMove, handleTimelineElementResize };
|
|
77757
|
+
}
|
|
77758
|
+
|
|
77759
|
+
// src/hooks/useBlockedTimelineEditToast.ts
|
|
77760
|
+
import { useCallback as useCallback123, useRef as useRef138 } from "react";
|
|
77761
|
+
var BLOCKED_TOAST_INTERVAL_MS = 1500;
|
|
77762
|
+
function useBlockedTimelineEditToast(showToast) {
|
|
77763
|
+
const lastAtRef = useRef138(0);
|
|
77764
|
+
return useCallback123(
|
|
77765
|
+
(_element) => {
|
|
77766
|
+
const now2 = Date.now();
|
|
77767
|
+
if (now2 - lastAtRef.current < BLOCKED_TOAST_INTERVAL_MS) return;
|
|
77768
|
+
lastAtRef.current = now2;
|
|
77769
|
+
showToast("This clip can't be moved or resized from the timeline yet.", "info");
|
|
77770
|
+
},
|
|
77771
|
+
[showToast]
|
|
77772
|
+
);
|
|
77773
|
+
}
|
|
77774
|
+
|
|
77775
|
+
// src/hooks/useTimelineEditing.ts
|
|
77776
|
+
function useTimelineEditing({
|
|
77777
|
+
projectId,
|
|
77778
|
+
activeCompPath,
|
|
77779
|
+
timelineElements,
|
|
77780
|
+
showToast,
|
|
77781
|
+
writeProjectFile,
|
|
77782
|
+
observeProjectFileVersion,
|
|
77783
|
+
recordEdit,
|
|
77784
|
+
reloadPreview,
|
|
77785
|
+
previewIframeRef,
|
|
77786
|
+
pendingTimelineEditPathRef,
|
|
77787
|
+
uploadProjectFiles,
|
|
77788
|
+
isRecordingRef,
|
|
77789
|
+
sdkSession,
|
|
77790
|
+
publishSdkSession,
|
|
77791
|
+
forceReloadSdkSession,
|
|
77792
|
+
invalidateGsapCache,
|
|
77793
|
+
handleDomZIndexReorderCommitRef,
|
|
77794
|
+
canEdit
|
|
77795
|
+
}) {
|
|
77796
|
+
const projectIdRef = useRef139(projectId);
|
|
77797
|
+
projectIdRef.current = projectId;
|
|
77798
|
+
const editQueueRef = useRef139(Promise.resolve());
|
|
77799
|
+
const track2 = useTrackPendingTimelineEdit();
|
|
77800
|
+
const checkEditable = useTimelineEditGate(canEdit, showToast);
|
|
77801
|
+
const guard = useTimelineEditGuard(canEdit, showToast);
|
|
77802
|
+
const groupEditing = useTimelineGroupEditing({
|
|
77803
|
+
activeCompPath,
|
|
77804
|
+
editQueueRef,
|
|
77805
|
+
forceReloadSdkSession,
|
|
77806
|
+
invalidateGsapCache,
|
|
77807
|
+
isRecordingRef,
|
|
77808
|
+
pendingTimelineEditPathRef,
|
|
77809
|
+
previewIframeRef,
|
|
77810
|
+
projectIdRef,
|
|
77811
|
+
recordEdit,
|
|
77812
|
+
reloadPreview,
|
|
77813
|
+
sdkSession,
|
|
77814
|
+
publishSdkSession,
|
|
77815
|
+
showToast,
|
|
77816
|
+
writeProjectFile
|
|
77817
|
+
});
|
|
77818
|
+
const { handleTimelineElementMove, handleTimelineElementResize } = useTimelineClipTimingEditing({
|
|
77819
|
+
activeCompPath,
|
|
77820
|
+
timelineElements,
|
|
77821
|
+
showToast,
|
|
77822
|
+
writeProjectFile,
|
|
77823
|
+
recordEdit,
|
|
77824
|
+
reloadPreview,
|
|
77825
|
+
previewIframeRef,
|
|
77826
|
+
pendingTimelineEditPathRef,
|
|
77827
|
+
isRecordingRef,
|
|
77828
|
+
sdkSession,
|
|
77829
|
+
publishSdkSession,
|
|
77830
|
+
forceReloadSdkSession,
|
|
77831
|
+
invalidateGsapCache,
|
|
77832
|
+
handleDomZIndexReorderCommitRef,
|
|
77833
|
+
editQueueRef,
|
|
77834
|
+
projectIdRef
|
|
77835
|
+
});
|
|
77535
77836
|
const handleToggleTrackHidden = useTimelineTrackVisibilityEditing({
|
|
77536
77837
|
projectIdRef,
|
|
77537
77838
|
activeCompPath,
|
|
@@ -77753,7 +78054,7 @@ function useTimelineEditing({
|
|
|
77753
78054
|
|
|
77754
78055
|
// src/hooks/timelineMoveAdapter.ts
|
|
77755
78056
|
import { useCallback as useCallback124 } from "react";
|
|
77756
|
-
function persistTimelineMoveEditsAtomically(edits, coalesceKey, operation, deps
|
|
78057
|
+
function persistTimelineMoveEditsAtomically(edits, coalesceKey, operation, deps) {
|
|
77757
78058
|
return deps.handleTimelineGroupMove(
|
|
77758
78059
|
edits.map(({ element, updates }) => ({
|
|
77759
78060
|
element,
|
|
@@ -77765,14 +78066,14 @@ function persistTimelineMoveEditsAtomically(edits, coalesceKey, operation, deps,
|
|
|
77765
78066
|
// ("timing") omit it so they stay eligible for the SDK fast path.
|
|
77766
78067
|
track: operation === "timing" ? void 0 : updates.track
|
|
77767
78068
|
})),
|
|
77768
|
-
{ coalesceKey
|
|
78069
|
+
{ coalesceKey }
|
|
77769
78070
|
);
|
|
77770
78071
|
}
|
|
77771
78072
|
function useTimelineMoveEditsHandler(handleTimelineGroupMove) {
|
|
77772
78073
|
return useCallback124(
|
|
77773
|
-
async (edits, coalesceKey, operation = "timing"
|
|
78074
|
+
async (edits, coalesceKey, operation = "timing") => {
|
|
77774
78075
|
const deps = { handleTimelineGroupMove };
|
|
77775
|
-
await persistTimelineMoveEditsAtomically(edits, coalesceKey, operation, deps
|
|
78076
|
+
await persistTimelineMoveEditsAtomically(edits, coalesceKey, operation, deps);
|
|
77776
78077
|
},
|
|
77777
78078
|
[handleTimelineGroupMove]
|
|
77778
78079
|
);
|
|
@@ -77902,6 +78203,9 @@ function domEditSelectionsTargetSame(a, b) {
|
|
|
77902
78203
|
if (!a || !b) return false;
|
|
77903
78204
|
return getDomEditTargetKey(a) === getDomEditTargetKey(b);
|
|
77904
78205
|
}
|
|
78206
|
+
function domEditSelectionsEqual(a, b) {
|
|
78207
|
+
return sameData(a, b);
|
|
78208
|
+
}
|
|
77905
78209
|
function domEditSelectionInGroup(group, selection) {
|
|
77906
78210
|
if (!selection) return false;
|
|
77907
78211
|
return group.some((entry2) => domEditSelectionsTargetSame(entry2, selection));
|
|
@@ -78168,6 +78472,8 @@ function useDomSelection({
|
|
|
78168
78472
|
const currentSelection = domEditSelectionRef.current;
|
|
78169
78473
|
const previousGroup = domEditGroupSelectionsRef.current;
|
|
78170
78474
|
const isRepeatedSingleSelection = !isAdditiveSelection && !options?.preserveGroup && previousGroup.length === 1 && domEditSelectionsTargetSame(currentSelection, selection) && domEditSelectionsTargetSame(previousGroup[0], selection);
|
|
78475
|
+
const isUnchangedRefresh = options?.preserveGroup && currentSelection !== null && (previousGroup.length === 0 || previousGroup.includes(currentSelection)) && domEditSelectionsEqual(currentSelection, selection);
|
|
78476
|
+
if (isUnchangedRefresh) return;
|
|
78171
78477
|
if (isRepeatedSingleSelection) {
|
|
78172
78478
|
if (options?.revealPanel !== false) {
|
|
78173
78479
|
setRightCollapsed(false);
|
|
@@ -78229,6 +78535,7 @@ function useDomSelection({
|
|
|
78229
78535
|
preferClipAncestor: options?.preferClipAncestor,
|
|
78230
78536
|
skipSourceProbe: options?.skipSourceProbe,
|
|
78231
78537
|
exactTarget: options?.exactTarget,
|
|
78538
|
+
previous: options?.previous,
|
|
78232
78539
|
activeGroupElement: options && "activeGroupElement" in options ? options.activeGroupElement : activeGroupElementRef.current,
|
|
78233
78540
|
projectId
|
|
78234
78541
|
});
|
|
@@ -78351,7 +78658,7 @@ function useDomSelection({
|
|
|
78351
78658
|
applyDomSelection(null, { revealPanel: false });
|
|
78352
78659
|
return;
|
|
78353
78660
|
}
|
|
78354
|
-
const nextSelection = await buildDomSelectionFromTarget(element);
|
|
78661
|
+
const nextSelection = await buildDomSelectionFromTarget(element, { previous: selection });
|
|
78355
78662
|
if (nextSelection) {
|
|
78356
78663
|
applyDomSelection(nextSelection, {
|
|
78357
78664
|
revealPanel: false,
|
|
@@ -78376,7 +78683,7 @@ function useDomSelection({
|
|
|
78376
78683
|
for (const selection of selections) {
|
|
78377
78684
|
const element = findElementForSelection(doc, selection, activeCompPath);
|
|
78378
78685
|
if (!element) continue;
|
|
78379
|
-
const nextSelection2 = await buildDomSelectionFromTarget(element);
|
|
78686
|
+
const nextSelection2 = await buildDomSelectionFromTarget(element, { previous: selection });
|
|
78380
78687
|
if (nextSelection2) nextGroup.push(nextSelection2);
|
|
78381
78688
|
}
|
|
78382
78689
|
if (nextGroup.length === 0) return;
|
|
@@ -82052,7 +82359,9 @@ function useDomEditPreviewSync({
|
|
|
82052
82359
|
applyDomSelection(null, { revealPanel: false });
|
|
82053
82360
|
return;
|
|
82054
82361
|
}
|
|
82055
|
-
const nextSelection = await buildDomSelectionFromTarget(nextElement
|
|
82362
|
+
const nextSelection = await buildDomSelectionFromTarget(nextElement, {
|
|
82363
|
+
previous: currentSelection
|
|
82364
|
+
});
|
|
82056
82365
|
if (nextSelection) {
|
|
82057
82366
|
applyDomSelection(nextSelection, { revealPanel: false, preserveGroup: true });
|
|
82058
82367
|
}
|
|
@@ -83223,19 +83532,17 @@ async function commitKeyframeProps(selection, anim, props, propEntries, primaryP
|
|
|
83223
83532
|
const newStart = Math.min(ct, ts);
|
|
83224
83533
|
const newEnd = Math.max(ct, ts + td);
|
|
83225
83534
|
const newDuration = Math.max(0.01, newEnd - newStart);
|
|
83535
|
+
const runEase = runEaseOf(anim);
|
|
83536
|
+
const toNewPct = (absTime) => Math.round(progressAtTime(runEase, (absTime - newStart) / newDuration * 100) * 10) / 10;
|
|
83226
83537
|
const remapped = kfs.map((kf) => {
|
|
83227
|
-
const
|
|
83228
|
-
const newPct = Math.round((absTime - newStart) / newDuration * 1e3) / 10;
|
|
83538
|
+
const newPct = toNewPct(ts + timeAtProgress(runEase, kf.percentage) / 100 * td);
|
|
83229
83539
|
const p = { ...kf.properties };
|
|
83230
83540
|
for (const k of Object.keys(properties)) {
|
|
83231
83541
|
if (!(k in p) && backfillDefaults[k] != null) p[k] = backfillDefaults[k];
|
|
83232
83542
|
}
|
|
83233
83543
|
return { percentage: newPct, properties: p };
|
|
83234
83544
|
});
|
|
83235
|
-
remapped.push({
|
|
83236
|
-
percentage: Math.round((ct - newStart) / newDuration * 1e3) / 10,
|
|
83237
|
-
properties
|
|
83238
|
-
});
|
|
83545
|
+
remapped.push({ percentage: toNewPct(ct), properties });
|
|
83239
83546
|
remapped.sort((a, b) => a.percentage - b.percentage);
|
|
83240
83547
|
await commit2(
|
|
83241
83548
|
selection,
|
|
@@ -83924,6 +84231,19 @@ function runCommitPreflights(selection, fileAnimations, iframe, group) {
|
|
|
83924
84231
|
};
|
|
83925
84232
|
}
|
|
83926
84233
|
var resolvedSelections = /* @__PURE__ */ new WeakMap();
|
|
84234
|
+
var checkedSelections = /* @__PURE__ */ new WeakMap();
|
|
84235
|
+
function checkedPreflight(resolved2, inputs) {
|
|
84236
|
+
const known = checkedSelections.get(resolved2);
|
|
84237
|
+
const same = known?.animations === inputs.animations && known.iframe === inputs.iframe && known.group === inputs.group && known.version === inputs.version;
|
|
84238
|
+
if (same) return known;
|
|
84239
|
+
const { animations, iframe, group } = inputs;
|
|
84240
|
+
const checked = {
|
|
84241
|
+
...inputs,
|
|
84242
|
+
preflight: runCommitPreflights(resolved2, animations, iframe, group)
|
|
84243
|
+
};
|
|
84244
|
+
checkedSelections.set(resolved2, checked);
|
|
84245
|
+
return checked;
|
|
84246
|
+
}
|
|
83927
84247
|
var resolvedOf = (selection) => resolvedSelections.get(selection) ?? selection;
|
|
83928
84248
|
var MANUAL_FLAGS = [
|
|
83929
84249
|
["canApplyManualOffset", "offset"],
|
|
@@ -83986,17 +84306,21 @@ function useCommitPreflightCapabilities({
|
|
|
83986
84306
|
void parseTick;
|
|
83987
84307
|
if (!enabled || !projectId) return { selection, groupSelections };
|
|
83988
84308
|
const group = groupSelections.length > 1;
|
|
84309
|
+
const iframe = previewIframeRef.current;
|
|
83989
84310
|
const narrow = (target) => {
|
|
83990
84311
|
const file = gsapSourceFileForSelection(target);
|
|
83991
84312
|
const animations = parsesRef.current.get(parseCacheKey(projectId, file))?.animations;
|
|
83992
|
-
|
|
83993
|
-
|
|
84313
|
+
if (!animations) return narrowCapabilities(target, null);
|
|
84314
|
+
const inputs = { animations, iframe, group, version };
|
|
84315
|
+
const checked = checkedPreflight(resolvedOf(target), inputs);
|
|
84316
|
+
checked.narrowed ??= narrowCapabilities(target, checked.preflight);
|
|
84317
|
+
return checked.narrowed;
|
|
83994
84318
|
};
|
|
83995
|
-
return {
|
|
84319
|
+
return withTweenIndex(() => ({
|
|
83996
84320
|
selection: selection && narrow(selection),
|
|
83997
84321
|
groupSelections: groupSelections.map(narrow)
|
|
83998
|
-
};
|
|
83999
|
-
}, [enabled, projectId, selection, groupSelections, parseTick, previewIframeRef]);
|
|
84322
|
+
}));
|
|
84323
|
+
}, [enabled, projectId, selection, groupSelections, parseTick, previewIframeRef, version]);
|
|
84000
84324
|
}
|
|
84001
84325
|
|
|
84002
84326
|
// src/hooks/domEditDeleteMembers.ts
|
|
@@ -85956,11 +86280,11 @@ function useEditHistoryActions({
|
|
|
85956
86280
|
const apply = useCallback153(
|
|
85957
86281
|
async (direction) => {
|
|
85958
86282
|
const noun = direction === "undo" ? "Undo" : "Redo";
|
|
85959
|
-
const
|
|
86283
|
+
const pendingEditShown = direction === "undo" ? paintBackNewestStudioPendingEdit() : null;
|
|
86284
|
+
const predicted = pendingEditShown ? null : editHistory.predict?.(direction) ?? null;
|
|
85960
86285
|
const predictedShown = predicted ? showHistoryRestoreNow?.(predicted.files) ?? null : null;
|
|
85961
|
-
const pendingEditShown = !predictedShown && direction === "undo" ? paintBackNewestStudioPendingEdit() : null;
|
|
85962
86286
|
const putBack = predictedShown ?? pendingEditShown?.showAgain;
|
|
85963
|
-
const claimedAfter = direction === "undo" && hasStudioPendingEdits() ? editHistory.claims?.() : void 0;
|
|
86287
|
+
const claimedAfter = pendingEditShown?.claimsAtBegin ?? (direction === "undo" && hasStudioPendingEdits() ? editHistory.claims?.() : void 0);
|
|
85964
86288
|
let result = { ok: false, reason: "failed" };
|
|
85965
86289
|
let serverSteppedShown = false;
|
|
85966
86290
|
let revertIsTheUndo = false;
|
|
@@ -85974,7 +86298,8 @@ function useEditHistoryActions({
|
|
|
85974
86298
|
claimedAfter
|
|
85975
86299
|
});
|
|
85976
86300
|
const stepped = Boolean(result.ok && result.label);
|
|
85977
|
-
|
|
86301
|
+
const editClaimed = pendingEditShown !== null && (editHistory.claims?.() ?? 0) > pendingEditShown.claimsAtBegin;
|
|
86302
|
+
serverSteppedShown = predictedShown ? stepped && result.undoes === predicted?.id : stepped && editClaimed;
|
|
85978
86303
|
} finally {
|
|
85979
86304
|
if (putBack && !serverSteppedShown && !revertIsTheUndo) putBack();
|
|
85980
86305
|
}
|
|
@@ -88972,7 +89297,7 @@ function useGestureCommit({
|
|
|
88972
89297
|
);
|
|
88973
89298
|
} else {
|
|
88974
89299
|
const tweenStart = existingPositionTween.resolvedStart ?? 0;
|
|
88975
|
-
const tweenDur = existingPositionTween
|
|
89300
|
+
const tweenDur = resolveTweenDuration(existingPositionTween);
|
|
88976
89301
|
const tweenEnd = tweenStart + tweenDur;
|
|
88977
89302
|
const recEnd = recStart + duration;
|
|
88978
89303
|
const overlaps3 = recStart < tweenEnd + 0.05 && recEnd > tweenStart - 0.05;
|
|
@@ -96737,31 +97062,10 @@ function animatedProps(anim) {
|
|
|
96737
97062
|
}
|
|
96738
97063
|
return ["x", "y"];
|
|
96739
97064
|
}
|
|
96740
|
-
function isPlayheadWithinTween(anim, currentTime
|
|
97065
|
+
function isPlayheadWithinTween(anim, currentTime) {
|
|
96741
97066
|
const start = resolveTweenStart(anim);
|
|
96742
97067
|
if (start === null) return true;
|
|
96743
|
-
|
|
96744
|
-
return isTimeWithinTween(currentTime, start, duration);
|
|
96745
|
-
}
|
|
96746
|
-
function buildExtendedKeyframes(anim, currentTime, position, sourceDuration = resolveTweenDuration(anim)) {
|
|
96747
|
-
const oldStart = resolveTweenStart(anim) ?? 0;
|
|
96748
|
-
const oldDuration = sourceDuration;
|
|
96749
|
-
const newStart = Math.min(oldStart, currentTime);
|
|
96750
|
-
const newEnd = Math.max(oldStart + oldDuration, currentTime);
|
|
96751
|
-
const newDuration = roundTo3(newEnd - newStart);
|
|
96752
|
-
const toPct = (absoluteTime) => newDuration > 0 ? Math.max(
|
|
96753
|
-
0,
|
|
96754
|
-
Math.min(100, Math.round((absoluteTime - newStart) / newDuration * 1e3) / 10)
|
|
96755
|
-
) : 0;
|
|
96756
|
-
const stops = anim.keyframes?.keyframes ?? [];
|
|
96757
|
-
const rescaled = stops.map((stop2) => ({
|
|
96758
|
-
percentage: toPct(oldStart + stop2.percentage / 100 * oldDuration),
|
|
96759
|
-
properties: stop2.properties,
|
|
96760
|
-
...stop2.ease ? { ease: stop2.ease } : {}
|
|
96761
|
-
}));
|
|
96762
|
-
const added = { percentage: toPct(currentTime), properties: position };
|
|
96763
|
-
const keyframes = [...rescaled, added].sort((a, b) => a.percentage - b.percentage);
|
|
96764
|
-
return { position: roundTo3(newStart), duration: newDuration, keyframes };
|
|
97068
|
+
return isTimeWithinTween(currentTime, start, resolveTweenDuration(anim));
|
|
96765
97069
|
}
|
|
96766
97070
|
async function replaceSetWithSingleKeyframe(session, sel, setAnim, t2, iframe, selector) {
|
|
96767
97071
|
const position = readElementPosition(iframe, sel, setAnim);
|
|
@@ -96852,10 +97156,16 @@ async function extendKeyframedTweenToPlayhead(session, sel, anim, currentTime, d
|
|
|
96852
97156
|
}
|
|
96853
97157
|
);
|
|
96854
97158
|
}
|
|
96855
|
-
async function applyKeyframeAtPlayhead(session, sel, kfAnim, t2, iframe, commitOverrides) {
|
|
96856
|
-
const
|
|
97159
|
+
async function applyKeyframeAtPlayhead(session, sel, kfAnim, t2, iframe, commitOverrides, liveTween) {
|
|
97160
|
+
const keepAuthoredDuration = kfAnim.durationUnresolved === true;
|
|
97161
|
+
if (keepAuthoredDuration) {
|
|
97162
|
+
kfAnim = withLiveTiming(kfAnim, liveTween ?? findParsedTween(iframe, sel.element, kfAnim));
|
|
97163
|
+
}
|
|
97164
|
+
if (keepAuthoredDuration && kfAnim.duration == null) return;
|
|
97165
|
+
const duration = resolveTweenDuration(kfAnim);
|
|
96857
97166
|
const start = resolveTweenStart(kfAnim);
|
|
96858
97167
|
if (start !== null && !isTimeWithinTween(t2, start, duration)) {
|
|
97168
|
+
if (keepAuthoredDuration) return;
|
|
96859
97169
|
await extendKeyframedTweenToPlayhead(
|
|
96860
97170
|
session,
|
|
96861
97171
|
sel,
|
|
@@ -96867,10 +97177,8 @@ async function applyKeyframeAtPlayhead(session, sel, kfAnim, t2, iframe, commitO
|
|
|
96867
97177
|
);
|
|
96868
97178
|
return;
|
|
96869
97179
|
}
|
|
96870
|
-
const pct = start === null ? computeElementPercentage(t2, sel) :
|
|
96871
|
-
const existing = kfAnim.keyframes?.keyframes.find(
|
|
96872
|
-
(k) => Math.abs(k.percentage - pct) <= KEYFRAME_PCT_MATCH
|
|
96873
|
-
);
|
|
97180
|
+
const pct = start === null ? computeElementPercentage(t2, sel) : absoluteToPercentageForAnimation(t2, kfAnim) ?? 0;
|
|
97181
|
+
const existing = kfAnim.keyframes?.keyframes.find((k) => playsNear(kfAnim, k.percentage, pct));
|
|
96874
97182
|
if (existing) {
|
|
96875
97183
|
session.handleGsapRemoveKeyframe(kfAnim.id, existing.percentage);
|
|
96876
97184
|
return;
|
|
@@ -96922,21 +97230,26 @@ async function applyArcKeyframeAtPlayhead(session, sel, arcAnim, t2, iframe) {
|
|
|
96922
97230
|
const targetSelector = existingTweenTargetSelector(arcAnim, sel);
|
|
96923
97231
|
if (!targetSelector) return;
|
|
96924
97232
|
const start = resolveTweenStart(arcAnim) ?? 0;
|
|
96925
|
-
const duration =
|
|
97233
|
+
const duration = resolveTweenDuration(arcAnim);
|
|
96926
97234
|
if (!isTimeWithinTween(t2, start, duration)) {
|
|
96927
|
-
|
|
96928
|
-
|
|
96929
|
-
|
|
96930
|
-
|
|
96931
|
-
|
|
96932
|
-
|
|
96933
|
-
|
|
96934
|
-
|
|
96935
|
-
|
|
96936
|
-
|
|
97235
|
+
const held = readElementPosition(iframe, sel, arcAnim);
|
|
97236
|
+
if (t2 <= start || typeof held.x !== "number" || typeof held.y !== "number") return;
|
|
97237
|
+
const extended = buildExtendedKeyframes(arcAnim, t2, { x: held.x, y: held.y }, duration, "none");
|
|
97238
|
+
await session.commitMutation(
|
|
97239
|
+
{
|
|
97240
|
+
type: "replace-with-keyframes",
|
|
97241
|
+
animationId: arcAnim.id,
|
|
97242
|
+
targetSelector,
|
|
97243
|
+
position: extended.position,
|
|
97244
|
+
duration: extended.duration,
|
|
97245
|
+
keyframes: extended.keyframes,
|
|
97246
|
+
ease: "none"
|
|
97247
|
+
},
|
|
97248
|
+
{ label: "Add keyframe", keyframeAction: "add", softReload: true }
|
|
97249
|
+
);
|
|
96937
97250
|
return;
|
|
96938
97251
|
}
|
|
96939
|
-
const nodes = arcAnim
|
|
97252
|
+
const nodes = linearArcKeyframes(arcAnim);
|
|
96940
97253
|
const playheadPercentage = absoluteToPercentage(t2, start, duration);
|
|
96941
97254
|
const timedNodeIndex = nodes.findIndex(
|
|
96942
97255
|
(node) => Math.abs(node.percentage - playheadPercentage) <= KEYFRAME_PCT_MATCH
|
|
@@ -96997,6 +97310,7 @@ function useEnableKeyframes(sessionRef) {
|
|
|
96997
97310
|
} else if (setAnim) {
|
|
96998
97311
|
await promoteSetToKeyframes(session, sel, setAnim, t2, iframe);
|
|
96999
97312
|
} else if (flatAnim) {
|
|
97313
|
+
const flatLive = flatAnim.durationUnresolved ? findParsedTween(iframe, sel.element, flatAnim) : void 0;
|
|
97000
97314
|
enableKeyframesTransactionCounter += 1;
|
|
97001
97315
|
const coalesceKey = `enable-keyframes:${flatAnim.id}:${enableKeyframesTransactionCounter}`;
|
|
97002
97316
|
const convertCommitOverrides = {
|
|
@@ -97022,7 +97336,15 @@ function useEnableKeyframes(sessionRef) {
|
|
|
97022
97336
|
// conversion splits into two undo entries under real latency.
|
|
97023
97337
|
coalesceMs: Number.POSITIVE_INFINITY
|
|
97024
97338
|
};
|
|
97025
|
-
await applyKeyframeAtPlayhead(
|
|
97339
|
+
await applyKeyframeAtPlayhead(
|
|
97340
|
+
session,
|
|
97341
|
+
sel,
|
|
97342
|
+
converted,
|
|
97343
|
+
t2,
|
|
97344
|
+
iframe,
|
|
97345
|
+
applyCommitOverrides,
|
|
97346
|
+
flatLive
|
|
97347
|
+
);
|
|
97026
97348
|
}
|
|
97027
97349
|
} else {
|
|
97028
97350
|
const position = readElementPosition(iframe, sel, null);
|
|
@@ -97331,7 +97653,9 @@ var NO_KEYFRAME_TOGGLE = {
|
|
|
97331
97653
|
function isMotionPathEndpoint(animation, percentage) {
|
|
97332
97654
|
if (!animation?.keyframes) return false;
|
|
97333
97655
|
const keyframes = animation.keyframes.keyframes;
|
|
97334
|
-
return
|
|
97656
|
+
return [keyframes[0], keyframes.at(-1)].some(
|
|
97657
|
+
(keyframe) => keyframe && playsNear(animation, keyframe.percentage, percentage)
|
|
97658
|
+
);
|
|
97335
97659
|
}
|
|
97336
97660
|
function resolveKeyframeToggleState(session, currentTime) {
|
|
97337
97661
|
if (!session?.domEditSelection) return NO_KEYFRAME_TOGGLE;
|
|
@@ -97341,13 +97665,13 @@ function resolveKeyframeToggleState(session, currentTime) {
|
|
|
97341
97665
|
const animation = arcAnimation ?? session.selectedGsapAnimations.find((candidate) => candidate.keyframes && !candidate.arcPath);
|
|
97342
97666
|
if (!animation?.keyframes) return NO_KEYFRAME_TOGGLE;
|
|
97343
97667
|
const isMotionPath = Boolean(arcAnimation);
|
|
97344
|
-
if (!isPlayheadWithinTween(animation, currentTime
|
|
97668
|
+
if (!isPlayheadWithinTween(animation, currentTime)) {
|
|
97345
97669
|
return { state: "inactive", isMotionPath, pathEndpoint: false, willExtend: true };
|
|
97346
97670
|
}
|
|
97347
97671
|
const percentage = computeElementPercentage(currentTime, session.domEditSelection, animation);
|
|
97348
97672
|
const pathEndpoint = isMotionPathEndpoint(arcAnimation, percentage);
|
|
97349
97673
|
const active = animation.keyframes.keyframes.some(
|
|
97350
|
-
(keyframe) =>
|
|
97674
|
+
(keyframe) => playsNear(animation, keyframe.percentage, percentage)
|
|
97351
97675
|
);
|
|
97352
97676
|
return {
|
|
97353
97677
|
state: pathEndpoint ? "none" : active ? "active" : "inactive",
|