@hyperframes/studio 0.8.99 → 0.8.101
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-Rml34FRW.js → hyperframes-player-CFJLrHRF.js} +1 -1
- package/dist/assets/{index-CVDArg3m.js → index-BBNVnnyH.js} +179 -179
- package/dist/assets/{index-D9YzDuBx.js → index-C33EDBL2.js} +1 -1
- package/dist/assets/{index-C_fi-jTe.js → index-CJOzbslX.js} +1 -1
- package/dist/{chunk-U3EE4ACX.js → chunk-7X4XRP3L.js} +14 -13
- package/dist/chunk-7X4XRP3L.js.map +1 -0
- package/dist/index.d.ts +91 -21
- package/dist/index.html +1 -1
- package/dist/index.js +826 -538
- package/dist/index.js.map +1 -1
- package/dist/player.js +1 -1
- package/package.json +8 -8
- package/src/App.tsx +2 -0
- package/src/components/dock/Dock.test.tsx +78 -3
- package/src/components/dock/Dock.tsx +45 -18
- package/src/components/dock/dockLayout.ts +27 -14
- package/src/components/dock/dockLayoutSchema.ts +11 -4
- package/src/components/dock/dockLayoutStore.ts +4 -2
- package/src/components/editor/PropertyPanel.tsx +3 -3
- package/src/components/editor/domEditOverlayStartGesture.test.ts +83 -0
- package/src/components/editor/domEditOverlayStartGesture.ts +6 -4
- package/src/components/editor/manualOffsetDrag.test.ts +70 -0
- package/src/components/editor/manualOffsetDrag.ts +50 -40
- package/src/components/editor/plainTranslate.test.ts +62 -0
- package/src/components/editor/plainTranslate.ts +85 -0
- package/src/components/editor/propertyPanelTransformCommit.ts +3 -2
- package/src/components/editor/useDomEditNudge.ts +1 -1
- package/src/hooks/appHotkeysDispatch.ts +1 -7
- package/src/hooks/elementOffsetStager.ts +33 -2
- package/src/hooks/gsapRuntimeBridge.ts +1 -17
- package/src/hooks/gsapRuntimeKeyframes.ts +35 -0
- package/src/hooks/useAppHotkeys.previewForwarding.test.tsx +67 -2
- package/src/hooks/useAppHotkeys.ts +18 -45
- package/src/hooks/useDomEditCommits.test.tsx +12 -3
- package/src/hooks/useDomEditCommits.ts +2 -1
- package/src/hooks/useDomEditTextCommits.test.tsx +62 -5
- package/src/hooks/useDomEditTextCommits.ts +35 -35
- package/src/hooks/useDomGeometryCommit.test.tsx +62 -4
- package/src/hooks/useDomGeometryCommits.test.tsx +4 -2
- package/src/hooks/useEditHistoryActions.paint.test.tsx +166 -0
- package/src/hooks/useEditHistoryActions.test.tsx +67 -9
- package/src/hooks/useEditHistoryActions.ts +36 -8
- package/src/hooks/useGsapAwareEditing.test.tsx +46 -18
- package/src/hooks/useGsapAwareEditing.ts +11 -11
- package/src/hooks/usePersistentEditHistory.test.ts +46 -0
- package/src/hooks/usePersistentEditHistory.ts +101 -19
- package/src/hooks/usePreviewPersistence.ts +25 -2
- package/src/hooks/useStudioExternalFileChanges.ts +7 -3
- package/src/index.ts +4 -0
- package/src/player/components/timelineDragDrop.test.tsx +61 -5
- package/src/player/components/timelineDragDrop.ts +29 -8
- package/src/player/components/useTimelineProviderState.tsx +1 -0
- package/src/utils/domEditSaveQueue.ts +7 -2
- package/src/utils/gsapSoftReload.ts +1 -1
- package/src/utils/gsapUndoRestore.test.ts +46 -0
- package/src/utils/gsapUndoRestore.ts +166 -91
- package/src/utils/studioPendingEdits.ts +14 -5
- package/src/utils/studioUiPreferences.ts +8 -6
- package/dist/chunk-U3EE4ACX.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -43,6 +43,7 @@ import {
|
|
|
43
43
|
isTimelineIgnoredElement,
|
|
44
44
|
liveTime,
|
|
45
45
|
panelsInZone,
|
|
46
|
+
parseDockLayout,
|
|
46
47
|
playbackStartAttributeForElement,
|
|
47
48
|
previewElementFinder,
|
|
48
49
|
readLayerRevealPriorZ,
|
|
@@ -66,7 +67,7 @@ import {
|
|
|
66
67
|
whenPreviewBooted,
|
|
67
68
|
wrapTimeline,
|
|
68
69
|
writeStudioUiPreferences
|
|
69
|
-
} from "./chunk-
|
|
70
|
+
} from "./chunk-7X4XRP3L.js";
|
|
70
71
|
import {
|
|
71
72
|
Button,
|
|
72
73
|
ContextMenu,
|
|
@@ -3338,6 +3339,73 @@ function isLaneFree(elements, track2, start, end, excludeKey) {
|
|
|
3338
3339
|
);
|
|
3339
3340
|
}
|
|
3340
3341
|
|
|
3342
|
+
// src/player/components/timelineSnapping.ts
|
|
3343
|
+
function resolveSnapGuide(moving, trimming) {
|
|
3344
|
+
const source = moving?.started ? moving : trimming;
|
|
3345
|
+
if (source?.snapTime == null || source.snapType == null) return null;
|
|
3346
|
+
return { time: source.snapTime, type: source.snapType };
|
|
3347
|
+
}
|
|
3348
|
+
var TIMELINE_SNAP_PX = 8;
|
|
3349
|
+
var TYPE_PRIORITY = {
|
|
3350
|
+
playhead: 0,
|
|
3351
|
+
"clip-edge": 1,
|
|
3352
|
+
beat: 2
|
|
3353
|
+
};
|
|
3354
|
+
function collectTimelineSnapTargets(input) {
|
|
3355
|
+
const byTime = /* @__PURE__ */ new Map();
|
|
3356
|
+
const add = (time, type) => {
|
|
3357
|
+
if (!Number.isFinite(time) || time < 0) return;
|
|
3358
|
+
const rounded2 = Math.round(time * 1e3) / 1e3;
|
|
3359
|
+
const existing = byTime.get(rounded2);
|
|
3360
|
+
if (!existing || TYPE_PRIORITY[type] < TYPE_PRIORITY[existing.type]) {
|
|
3361
|
+
byTime.set(rounded2, { time: rounded2, type });
|
|
3362
|
+
}
|
|
3363
|
+
};
|
|
3364
|
+
for (const beat of input.beatTimes) add(beat, "beat");
|
|
3365
|
+
for (const el of input.elements) {
|
|
3366
|
+
if (input.excludeElementKey != null && (el.key ?? el.id) === input.excludeElementKey) continue;
|
|
3367
|
+
add(el.start, "clip-edge");
|
|
3368
|
+
add(el.start + el.duration, "clip-edge");
|
|
3369
|
+
}
|
|
3370
|
+
if (input.playheadTime != null && input.includePlayhead !== false)
|
|
3371
|
+
add(input.playheadTime, "playhead");
|
|
3372
|
+
return Array.from(byTime.values()).sort((a, b) => a.time - b.time);
|
|
3373
|
+
}
|
|
3374
|
+
function snapTimelineTime(time, targets, thresholdSecs) {
|
|
3375
|
+
let best = null;
|
|
3376
|
+
let bestDist = thresholdSecs;
|
|
3377
|
+
for (const target of targets) {
|
|
3378
|
+
const d = Math.abs(target.time - time);
|
|
3379
|
+
if (d < bestDist || d === bestDist && best && TYPE_PRIORITY[target.type] < TYPE_PRIORITY[best.type]) {
|
|
3380
|
+
bestDist = d;
|
|
3381
|
+
best = target;
|
|
3382
|
+
}
|
|
3383
|
+
}
|
|
3384
|
+
return best ? { time: best.time, target: best } : { time, target: null };
|
|
3385
|
+
}
|
|
3386
|
+
function snapMoveToTargets(start, duration, targets, pixelsPerSecond, timelineDuration2) {
|
|
3387
|
+
if (targets.length === 0) return { start, snapTime: null, snapType: null };
|
|
3388
|
+
const thresholdSecs = TIMELINE_SNAP_PX / Math.max(pixelsPerSecond, 1);
|
|
3389
|
+
const startSnap = snapTimelineTime(start, targets, thresholdSecs);
|
|
3390
|
+
const endSnap = snapTimelineTime(start + duration, targets, thresholdSecs);
|
|
3391
|
+
const startMoved = startSnap.target !== null;
|
|
3392
|
+
const endMoved = endSnap.target !== null;
|
|
3393
|
+
let candidate = start;
|
|
3394
|
+
let target = null;
|
|
3395
|
+
if (startMoved && (!endMoved || Math.abs(startSnap.time - start) <= Math.abs(endSnap.time - (start + duration)))) {
|
|
3396
|
+
candidate = startSnap.time;
|
|
3397
|
+
target = startSnap.target;
|
|
3398
|
+
} else if (endMoved) {
|
|
3399
|
+
candidate = endSnap.time - duration;
|
|
3400
|
+
target = endSnap.target;
|
|
3401
|
+
}
|
|
3402
|
+
const maxStart = Math.max(0, timelineDuration2 - duration);
|
|
3403
|
+
const roundedCandidate = Math.round(candidate * 1e3) / 1e3;
|
|
3404
|
+
const clamped = Math.max(0, Math.min(maxStart, roundedCandidate));
|
|
3405
|
+
if (target && Math.abs(clamped - roundedCandidate) > 1e-6) target = null;
|
|
3406
|
+
return { start: clamped, snapTime: target?.time ?? null, snapType: target?.type ?? null };
|
|
3407
|
+
}
|
|
3408
|
+
|
|
3341
3409
|
// src/player/components/timelineDragDrop.ts
|
|
3342
3410
|
function applyJsonDropPayload(raw, pick, apply, placement) {
|
|
3343
3411
|
try {
|
|
@@ -3358,12 +3426,20 @@ function resolveDropInsertRow(contentY, rowHeights, trackCount) {
|
|
|
3358
3426
|
if (rowFloat >= trackCount) return null;
|
|
3359
3427
|
return resolveInsertRow(rowFloat, trackCount);
|
|
3360
3428
|
}
|
|
3361
|
-
function
|
|
3429
|
+
function alignDropStart(placement, elements, pixelsPerSecond) {
|
|
3430
|
+
const laneHasClips = placement.insertRow == null && elements.some((el) => el.track === placement.track);
|
|
3431
|
+
if (!laneHasClips) return { ...placement, start: 0 };
|
|
3432
|
+
if (!usePlayerStore.getState().timelineSnapEnabled) return placement;
|
|
3433
|
+
const targets = collectTimelineSnapTargets({ elements, playheadTime: null, beatTimes: [] });
|
|
3434
|
+
const threshold = TIMELINE_SNAP_PX / Math.max(pixelsPerSecond, 1);
|
|
3435
|
+
return { ...placement, start: snapTimelineTime(placement.start, targets, threshold).time };
|
|
3436
|
+
}
|
|
3437
|
+
function placeDrop(geometry, elements, clientX, clientY, canInsertTrack) {
|
|
3362
3438
|
const placement = resolveTimelineAssetDrop(geometry, clientX, clientY);
|
|
3363
|
-
if (!canInsertTrack) return placement;
|
|
3364
3439
|
const contentY = clientY - geometry.rectTop + geometry.scrollTop;
|
|
3365
|
-
const insertRow = resolveDropInsertRow(contentY, geometry.rowHeights, geometry.trackOrder.length);
|
|
3366
|
-
|
|
3440
|
+
const insertRow = canInsertTrack ? resolveDropInsertRow(contentY, geometry.rowHeights, geometry.trackOrder.length) : null;
|
|
3441
|
+
const aimed = insertRow == null ? placement : { ...placement, insertRow, trackOrder: geometry.trackOrder };
|
|
3442
|
+
return alignDropStart(aimed, elements, geometry.pixelsPerSecond);
|
|
3367
3443
|
}
|
|
3368
3444
|
function invokeDropCallback(callback) {
|
|
3369
3445
|
try {
|
|
@@ -3391,6 +3467,7 @@ function useTimelineAssetDrop({
|
|
|
3391
3467
|
scrollRef,
|
|
3392
3468
|
ppsRef,
|
|
3393
3469
|
trackOrderRef,
|
|
3470
|
+
elementsRef,
|
|
3394
3471
|
rowGeometryRef,
|
|
3395
3472
|
contentOrigin,
|
|
3396
3473
|
onFileDrop,
|
|
@@ -3458,12 +3535,13 @@ function useTimelineAssetDrop({
|
|
|
3458
3535
|
rowHeights: rowGeometryRef.current.rowHeights,
|
|
3459
3536
|
trackOrder: trackOrderRef.current
|
|
3460
3537
|
},
|
|
3538
|
+
elementsRef.current,
|
|
3461
3539
|
clientX,
|
|
3462
3540
|
clientY,
|
|
3463
3541
|
canInsertTrack
|
|
3464
3542
|
);
|
|
3465
3543
|
},
|
|
3466
|
-
[scrollRef, ppsRef, trackOrderRef, rowGeometryRef, contentOrigin]
|
|
3544
|
+
[scrollRef, ppsRef, trackOrderRef, elementsRef, rowGeometryRef, contentOrigin]
|
|
3467
3545
|
);
|
|
3468
3546
|
const handleAssetDragOver = useCallback6(
|
|
3469
3547
|
(e) => {
|
|
@@ -3558,73 +3636,6 @@ function useTimelineAssetDrop({
|
|
|
3558
3636
|
// src/player/components/useTimelineClipDrag.ts
|
|
3559
3637
|
import { useRef as useRef7, useState as useState4, useCallback as useCallback7, useMemo as useMemo3, useEffect as useEffect7 } from "react";
|
|
3560
3638
|
|
|
3561
|
-
// src/player/components/timelineSnapping.ts
|
|
3562
|
-
function resolveSnapGuide(moving, trimming) {
|
|
3563
|
-
const source = moving?.started ? moving : trimming;
|
|
3564
|
-
if (source?.snapTime == null || source.snapType == null) return null;
|
|
3565
|
-
return { time: source.snapTime, type: source.snapType };
|
|
3566
|
-
}
|
|
3567
|
-
var TIMELINE_SNAP_PX = 8;
|
|
3568
|
-
var TYPE_PRIORITY = {
|
|
3569
|
-
playhead: 0,
|
|
3570
|
-
"clip-edge": 1,
|
|
3571
|
-
beat: 2
|
|
3572
|
-
};
|
|
3573
|
-
function collectTimelineSnapTargets(input) {
|
|
3574
|
-
const byTime = /* @__PURE__ */ new Map();
|
|
3575
|
-
const add = (time, type) => {
|
|
3576
|
-
if (!Number.isFinite(time) || time < 0) return;
|
|
3577
|
-
const rounded2 = Math.round(time * 1e3) / 1e3;
|
|
3578
|
-
const existing = byTime.get(rounded2);
|
|
3579
|
-
if (!existing || TYPE_PRIORITY[type] < TYPE_PRIORITY[existing.type]) {
|
|
3580
|
-
byTime.set(rounded2, { time: rounded2, type });
|
|
3581
|
-
}
|
|
3582
|
-
};
|
|
3583
|
-
for (const beat of input.beatTimes) add(beat, "beat");
|
|
3584
|
-
for (const el of input.elements) {
|
|
3585
|
-
if (input.excludeElementKey != null && (el.key ?? el.id) === input.excludeElementKey) continue;
|
|
3586
|
-
add(el.start, "clip-edge");
|
|
3587
|
-
add(el.start + el.duration, "clip-edge");
|
|
3588
|
-
}
|
|
3589
|
-
if (input.playheadTime != null && input.includePlayhead !== false)
|
|
3590
|
-
add(input.playheadTime, "playhead");
|
|
3591
|
-
return Array.from(byTime.values()).sort((a, b) => a.time - b.time);
|
|
3592
|
-
}
|
|
3593
|
-
function snapTimelineTime(time, targets, thresholdSecs) {
|
|
3594
|
-
let best = null;
|
|
3595
|
-
let bestDist = thresholdSecs;
|
|
3596
|
-
for (const target of targets) {
|
|
3597
|
-
const d = Math.abs(target.time - time);
|
|
3598
|
-
if (d < bestDist || d === bestDist && best && TYPE_PRIORITY[target.type] < TYPE_PRIORITY[best.type]) {
|
|
3599
|
-
bestDist = d;
|
|
3600
|
-
best = target;
|
|
3601
|
-
}
|
|
3602
|
-
}
|
|
3603
|
-
return best ? { time: best.time, target: best } : { time, target: null };
|
|
3604
|
-
}
|
|
3605
|
-
function snapMoveToTargets(start, duration, targets, pixelsPerSecond, timelineDuration2) {
|
|
3606
|
-
if (targets.length === 0) return { start, snapTime: null, snapType: null };
|
|
3607
|
-
const thresholdSecs = TIMELINE_SNAP_PX / Math.max(pixelsPerSecond, 1);
|
|
3608
|
-
const startSnap = snapTimelineTime(start, targets, thresholdSecs);
|
|
3609
|
-
const endSnap = snapTimelineTime(start + duration, targets, thresholdSecs);
|
|
3610
|
-
const startMoved = startSnap.target !== null;
|
|
3611
|
-
const endMoved = endSnap.target !== null;
|
|
3612
|
-
let candidate = start;
|
|
3613
|
-
let target = null;
|
|
3614
|
-
if (startMoved && (!endMoved || Math.abs(startSnap.time - start) <= Math.abs(endSnap.time - (start + duration)))) {
|
|
3615
|
-
candidate = startSnap.time;
|
|
3616
|
-
target = startSnap.target;
|
|
3617
|
-
} else if (endMoved) {
|
|
3618
|
-
candidate = endSnap.time - duration;
|
|
3619
|
-
target = endSnap.target;
|
|
3620
|
-
}
|
|
3621
|
-
const maxStart = Math.max(0, timelineDuration2 - duration);
|
|
3622
|
-
const roundedCandidate = Math.round(candidate * 1e3) / 1e3;
|
|
3623
|
-
const clamped = Math.max(0, Math.min(maxStart, roundedCandidate));
|
|
3624
|
-
if (target && Math.abs(clamped - roundedCandidate) > 1e-6) target = null;
|
|
3625
|
-
return { start: clamped, snapTime: target?.time ?? null, snapType: target?.type ?? null };
|
|
3626
|
-
}
|
|
3627
|
-
|
|
3628
3639
|
// src/player/components/timelineAuthoredMoveTarget.ts
|
|
3629
3640
|
function canMoveTimelineElement(element) {
|
|
3630
3641
|
return getTimelineEditCapabilities({
|
|
@@ -13033,6 +13044,7 @@ function useTimelineProviderState({
|
|
|
13033
13044
|
scrollRef,
|
|
13034
13045
|
ppsRef,
|
|
13035
13046
|
trackOrderRef,
|
|
13047
|
+
elementsRef: timelineElementsRef,
|
|
13036
13048
|
rowGeometryRef,
|
|
13037
13049
|
contentOrigin,
|
|
13038
13050
|
onFileDrop: pinnedOnFileDrop,
|
|
@@ -32383,6 +32395,334 @@ function computeDraggedGsapPosition(element, studioOffset, fallbackBase) {
|
|
|
32383
32395
|
};
|
|
32384
32396
|
}
|
|
32385
32397
|
|
|
32398
|
+
// src/hooks/gsapRuntimeKeyframes.ts
|
|
32399
|
+
import { buildArcPath } from "@hyperframes/core/gsap-parser-acorn";
|
|
32400
|
+
var FLAT_SKIP_KEYS = /* @__PURE__ */ new Set([
|
|
32401
|
+
"ease",
|
|
32402
|
+
"duration",
|
|
32403
|
+
"delay",
|
|
32404
|
+
"stagger",
|
|
32405
|
+
"motionPath",
|
|
32406
|
+
"overwrite",
|
|
32407
|
+
"immediateRender",
|
|
32408
|
+
"onComplete",
|
|
32409
|
+
"onUpdate",
|
|
32410
|
+
"onStart",
|
|
32411
|
+
"keyframes"
|
|
32412
|
+
]);
|
|
32413
|
+
function timelinesOf(iframe) {
|
|
32414
|
+
if (!iframe?.contentWindow) return null;
|
|
32415
|
+
try {
|
|
32416
|
+
return iframe.contentWindow.__timelines ?? null;
|
|
32417
|
+
} catch {
|
|
32418
|
+
return null;
|
|
32419
|
+
}
|
|
32420
|
+
}
|
|
32421
|
+
function isXY(p) {
|
|
32422
|
+
return !!p && typeof p.x === "number" && typeof p.y === "number";
|
|
32423
|
+
}
|
|
32424
|
+
function isZeroDurationSet(duration) {
|
|
32425
|
+
return !(duration > 0);
|
|
32426
|
+
}
|
|
32427
|
+
function coordsFromMotionPath(mp) {
|
|
32428
|
+
if (!mp || typeof mp !== "object") return null;
|
|
32429
|
+
const obj = mp;
|
|
32430
|
+
const pathVal = Array.isArray(mp) ? mp : obj.path;
|
|
32431
|
+
if (!Array.isArray(pathVal)) return null;
|
|
32432
|
+
const coords = pathVal.filter(isXY).map((p) => ({ x: p.x, y: p.y }));
|
|
32433
|
+
if (coords.length < 2) return null;
|
|
32434
|
+
const curviness = typeof obj.curviness === "number" ? obj.curviness : 1;
|
|
32435
|
+
const autoRotate = typeof obj.autoRotate === "number" ? obj.autoRotate : obj.autoRotate === true;
|
|
32436
|
+
return { coords, curviness, autoRotate, isCubic: obj.type === "cubic" };
|
|
32437
|
+
}
|
|
32438
|
+
function flatTweenKeyframes(vars) {
|
|
32439
|
+
const properties = {};
|
|
32440
|
+
for (const [k, v] of Object.entries(vars)) {
|
|
32441
|
+
if (FLAT_SKIP_KEYS.has(k)) continue;
|
|
32442
|
+
if (typeof v === "number") properties[k] = roundTo3(v);
|
|
32443
|
+
else if (typeof v === "string") properties[k] = v;
|
|
32444
|
+
}
|
|
32445
|
+
if (Object.keys(properties).length === 0) return null;
|
|
32446
|
+
return [
|
|
32447
|
+
{ percentage: 0, properties },
|
|
32448
|
+
{ percentage: 100, properties }
|
|
32449
|
+
];
|
|
32450
|
+
}
|
|
32451
|
+
function readTween(vars) {
|
|
32452
|
+
if (vars.keyframes && typeof vars.keyframes === "object") {
|
|
32453
|
+
const parsed = parsePercentageKeyframes(vars.keyframes);
|
|
32454
|
+
if (parsed) return parsed;
|
|
32455
|
+
}
|
|
32456
|
+
const mp = coordsFromMotionPath(vars.motionPath);
|
|
32457
|
+
if (mp) {
|
|
32458
|
+
const shape = buildArcPath(mp.coords, mp.curviness, mp.autoRotate, mp.isCubic);
|
|
32459
|
+
if (shape) {
|
|
32460
|
+
const n = shape.waypoints.length;
|
|
32461
|
+
const keyframes = shape.waypoints.map((wp, i) => ({
|
|
32462
|
+
percentage: n > 1 ? Math.round(i / (n - 1) * 100) : 0,
|
|
32463
|
+
properties: { x: wp.x, y: wp.y }
|
|
32464
|
+
}));
|
|
32465
|
+
return { keyframes, arcPath: shape.arcPath };
|
|
32466
|
+
}
|
|
32467
|
+
}
|
|
32468
|
+
const flat = flatTweenKeyframes(vars);
|
|
32469
|
+
return flat ? { keyframes: flat } : null;
|
|
32470
|
+
}
|
|
32471
|
+
function matchesElement(tween, el) {
|
|
32472
|
+
if (!tween.targets) return false;
|
|
32473
|
+
for (const t of tween.targets()) {
|
|
32474
|
+
if (t === el || el.id && t.id === el.id) return true;
|
|
32475
|
+
}
|
|
32476
|
+
return false;
|
|
32477
|
+
}
|
|
32478
|
+
function tweenTiming(tween) {
|
|
32479
|
+
const rawStart = typeof tween.startTime === "function" ? tween.startTime() : 0;
|
|
32480
|
+
const rawDur = typeof tween.duration === "function" ? tween.duration() : 0;
|
|
32481
|
+
return {
|
|
32482
|
+
start: Number.isFinite(rawStart) ? rawStart : 0,
|
|
32483
|
+
duration: Number.isFinite(rawDur) ? rawDur : 0
|
|
32484
|
+
};
|
|
32485
|
+
}
|
|
32486
|
+
function varsCarryChannel(vars, channels) {
|
|
32487
|
+
if (!vars) return false;
|
|
32488
|
+
for (const ch of channels) {
|
|
32489
|
+
if (Object.prototype.hasOwnProperty.call(vars, ch)) return true;
|
|
32490
|
+
}
|
|
32491
|
+
return false;
|
|
32492
|
+
}
|
|
32493
|
+
function keyframeVarsCarryChannel(vars, channels) {
|
|
32494
|
+
const kf = vars?.keyframes;
|
|
32495
|
+
if (!kf || typeof kf !== "object") return false;
|
|
32496
|
+
if (!Array.isArray(kf) && channels.some((ch) => Object.prototype.hasOwnProperty.call(kf, ch)))
|
|
32497
|
+
return true;
|
|
32498
|
+
const steps = Array.isArray(kf) ? kf : Object.values(kf);
|
|
32499
|
+
return steps.some(
|
|
32500
|
+
(step) => step != null && typeof step === "object" && channels.some((ch) => Object.prototype.hasOwnProperty.call(step, ch))
|
|
32501
|
+
);
|
|
32502
|
+
}
|
|
32503
|
+
function resolveRuntimeTween(iframe, selector, kind, compositionId, channels) {
|
|
32504
|
+
const timelines = timelinesOf(iframe);
|
|
32505
|
+
if (!timelines) return null;
|
|
32506
|
+
let targetEl = null;
|
|
32507
|
+
try {
|
|
32508
|
+
targetEl = iframe?.contentDocument?.querySelector(selector) ?? null;
|
|
32509
|
+
} catch {
|
|
32510
|
+
return null;
|
|
32511
|
+
}
|
|
32512
|
+
if (!targetEl) return null;
|
|
32513
|
+
const tlIds = compositionId ? [compositionId] : Object.keys(timelines).filter((k) => typeof timelines[k]?.getChildren === "function");
|
|
32514
|
+
const wantChannels = channels && channels.length > 0 ? channels : null;
|
|
32515
|
+
let first = null;
|
|
32516
|
+
let channelMatch = null;
|
|
32517
|
+
for (const tlId of tlIds) {
|
|
32518
|
+
const timeline = timelines[tlId];
|
|
32519
|
+
if (!timeline?.getChildren) continue;
|
|
32520
|
+
const now2 = typeof timeline.time === "function" ? timeline.time() : null;
|
|
32521
|
+
for (const tween of timeline.getChildren(true)) {
|
|
32522
|
+
if (!tween.vars || !matchesElement(tween, targetEl)) continue;
|
|
32523
|
+
const dur = typeof tween.duration === "function" ? tween.duration() : 0;
|
|
32524
|
+
const isSet = !(dur > 0);
|
|
32525
|
+
if (kind === "set" ? !isSet : isSet) continue;
|
|
32526
|
+
if (wantChannels) {
|
|
32527
|
+
const carries = kind === "set" ? varsCarryChannel(tween.vars, wantChannels) : keyframeVarsCarryChannel(tween.vars, wantChannels);
|
|
32528
|
+
if (carries) {
|
|
32529
|
+
if (channelMatch === null) channelMatch = { tween, timeline };
|
|
32530
|
+
} else if (first === null) {
|
|
32531
|
+
first = { tween, timeline };
|
|
32532
|
+
}
|
|
32533
|
+
continue;
|
|
32534
|
+
}
|
|
32535
|
+
if (first === null) first = { tween, timeline };
|
|
32536
|
+
if (kind === "keyframe" && now2 != null) {
|
|
32537
|
+
const start = typeof tween.startTime === "function" ? tween.startTime() : 0;
|
|
32538
|
+
if (now2 >= start - 1e-3 && now2 <= start + dur + 1e-3) return { tween, timeline };
|
|
32539
|
+
}
|
|
32540
|
+
}
|
|
32541
|
+
}
|
|
32542
|
+
return channelMatch ?? first;
|
|
32543
|
+
}
|
|
32544
|
+
function readCarriesChannel(read, channels) {
|
|
32545
|
+
return read.keyframes.some((kf) => channels.some((c) => kf.properties[c] != null));
|
|
32546
|
+
}
|
|
32547
|
+
function readRuntimeKeyframes(iframe, selector, compositionId, requireChannels) {
|
|
32548
|
+
const timelines = timelinesOf(iframe);
|
|
32549
|
+
if (!timelines) return null;
|
|
32550
|
+
let targetEl = null;
|
|
32551
|
+
try {
|
|
32552
|
+
targetEl = iframe?.contentDocument?.querySelector(selector) ?? null;
|
|
32553
|
+
} catch {
|
|
32554
|
+
return null;
|
|
32555
|
+
}
|
|
32556
|
+
if (!targetEl) return null;
|
|
32557
|
+
const tlIds = compositionId ? [compositionId] : Object.keys(timelines).filter((k) => typeof timelines[k]?.getChildren === "function");
|
|
32558
|
+
if (tlIds.length === 0) return null;
|
|
32559
|
+
let firstRead = null;
|
|
32560
|
+
for (const tlId of tlIds) {
|
|
32561
|
+
const timeline = timelines[tlId];
|
|
32562
|
+
if (!timeline?.getChildren) continue;
|
|
32563
|
+
const now2 = typeof timeline.time === "function" ? timeline.time() : null;
|
|
32564
|
+
for (const tween of timeline.getChildren(true)) {
|
|
32565
|
+
if (!tween.vars || !matchesElement(tween, targetEl)) continue;
|
|
32566
|
+
const dur = typeof tween.duration === "function" ? tween.duration() : 0;
|
|
32567
|
+
if (isZeroDurationSet(dur)) continue;
|
|
32568
|
+
const read = readTween(tween.vars);
|
|
32569
|
+
if (!read) continue;
|
|
32570
|
+
if (requireChannels && !readCarriesChannel(read, requireChannels)) continue;
|
|
32571
|
+
if (firstRead === null) firstRead = read;
|
|
32572
|
+
if (now2 != null) {
|
|
32573
|
+
const start = typeof tween.startTime === "function" ? tween.startTime() : 0;
|
|
32574
|
+
if (now2 >= start - 1e-3 && now2 <= start + dur + 1e-3) return read;
|
|
32575
|
+
}
|
|
32576
|
+
}
|
|
32577
|
+
}
|
|
32578
|
+
return firstRead;
|
|
32579
|
+
}
|
|
32580
|
+
function hasNonHoldTweenForElement(iframe, selector, compositionId, channels) {
|
|
32581
|
+
let targetEl = null;
|
|
32582
|
+
try {
|
|
32583
|
+
targetEl = iframe?.contentDocument?.querySelector(selector) ?? null;
|
|
32584
|
+
} catch {
|
|
32585
|
+
return false;
|
|
32586
|
+
}
|
|
32587
|
+
return !!targetEl && hasNonHoldTween(timelinesOf(iframe), targetEl, channels, compositionId);
|
|
32588
|
+
}
|
|
32589
|
+
var POSITION_CHANNELS = [
|
|
32590
|
+
"x",
|
|
32591
|
+
"y",
|
|
32592
|
+
"xPercent",
|
|
32593
|
+
"yPercent",
|
|
32594
|
+
"left",
|
|
32595
|
+
"top",
|
|
32596
|
+
// readTween reads the authored translateX/Y; GSAP normalizes them to x/y only at play time.
|
|
32597
|
+
"translateX",
|
|
32598
|
+
"translateY"
|
|
32599
|
+
];
|
|
32600
|
+
var MOVE_CHANNELS = [...POSITION_CHANNELS, "motionPath"];
|
|
32601
|
+
function gsapWritesChannels(el, channels) {
|
|
32602
|
+
const win = el.ownerDocument.defaultView;
|
|
32603
|
+
return Object.values(win?.__timelines ?? {}).some(
|
|
32604
|
+
(tl) => (tl?.getChildren?.(true) ?? []).some(
|
|
32605
|
+
(tween) => !!tween.vars && matchesElement(tween, el) && (channels.some((ch) => ch in tween.vars) || keyframeVarsCarryChannel(tween.vars, channels))
|
|
32606
|
+
)
|
|
32607
|
+
);
|
|
32608
|
+
}
|
|
32609
|
+
function gsapWritesPosition(el) {
|
|
32610
|
+
const cache2 = el._gsap;
|
|
32611
|
+
return !!cache2?.renderTransform || gsapWritesChannels(el, MOVE_CHANNELS);
|
|
32612
|
+
}
|
|
32613
|
+
function elementHasNonHoldTween(el, channels) {
|
|
32614
|
+
const win = el.ownerDocument.defaultView;
|
|
32615
|
+
return hasNonHoldTween(win?.__timelines ?? null, el, channels);
|
|
32616
|
+
}
|
|
32617
|
+
function hasNonHoldTween(timelines, targetEl, channels, compositionId) {
|
|
32618
|
+
if (!timelines) return false;
|
|
32619
|
+
for (const tlId of compositionId ? [compositionId] : Object.keys(timelines)) {
|
|
32620
|
+
for (const tween of timelines[tlId]?.getChildren?.(true) ?? []) {
|
|
32621
|
+
if (!tween.vars || !matchesElement(tween, targetEl)) continue;
|
|
32622
|
+
const dur = typeof tween.duration === "function" ? tween.duration() : 0;
|
|
32623
|
+
if (isZeroDurationSet(dur)) continue;
|
|
32624
|
+
if (channels && keyframeVarsCarryChannel(tween.vars, channels)) return true;
|
|
32625
|
+
const read = readTween(tween.vars);
|
|
32626
|
+
if (read && (!channels || readCarriesChannel(read, channels))) return true;
|
|
32627
|
+
}
|
|
32628
|
+
}
|
|
32629
|
+
return false;
|
|
32630
|
+
}
|
|
32631
|
+
function toClipRelative(keyframes, tweenStart, tweenDuration, clip) {
|
|
32632
|
+
if (!clip || clip.duration <= 0) return keyframes;
|
|
32633
|
+
return keyframes.map((kf) => {
|
|
32634
|
+
const abs = toAbsoluteTime(tweenStart, tweenDuration, kf.percentage);
|
|
32635
|
+
return { ...kf, percentage: Math.round((abs - clip.start) / clip.duration * 1e5) / 1e3 };
|
|
32636
|
+
});
|
|
32637
|
+
}
|
|
32638
|
+
function buildEntry(read, start, duration, clip) {
|
|
32639
|
+
return {
|
|
32640
|
+
keyframes: toClipRelative(read.keyframes, start, duration, clip),
|
|
32641
|
+
tweenStart: start,
|
|
32642
|
+
tweenDuration: duration,
|
|
32643
|
+
...read.easeEach ? { easeEach: read.easeEach } : {},
|
|
32644
|
+
...read.arcPath ? { arcPath: read.arcPath } : {}
|
|
32645
|
+
};
|
|
32646
|
+
}
|
|
32647
|
+
function addScanEntry(result, tween, clipById) {
|
|
32648
|
+
if (!tween.targets || !tween.vars) return;
|
|
32649
|
+
const { start, duration } = tweenTiming(tween);
|
|
32650
|
+
if (isZeroDurationSet(duration)) return;
|
|
32651
|
+
const read = readTween(tween.vars);
|
|
32652
|
+
if (!read) return;
|
|
32653
|
+
for (const target of tween.targets()) {
|
|
32654
|
+
const id = target.id;
|
|
32655
|
+
if (id && !result.has(id)) result.set(id, buildEntry(read, start, duration, clipById?.get(id)));
|
|
32656
|
+
}
|
|
32657
|
+
}
|
|
32658
|
+
function scanAllRuntimeKeyframes(iframe, clipById) {
|
|
32659
|
+
const result = /* @__PURE__ */ new Map();
|
|
32660
|
+
const timelines = timelinesOf(iframe);
|
|
32661
|
+
if (!timelines) return result;
|
|
32662
|
+
for (const timeline of Object.values(timelines)) {
|
|
32663
|
+
if (!timeline?.getChildren) continue;
|
|
32664
|
+
for (const tween of timeline.getChildren(true)) addScanEntry(result, tween, clipById);
|
|
32665
|
+
}
|
|
32666
|
+
return result;
|
|
32667
|
+
}
|
|
32668
|
+
|
|
32669
|
+
// src/components/editor/plainTranslate.ts
|
|
32670
|
+
var TERM = /^(-?\d*\.?\d+(?:e[-+]?\d+)?)(px|%)$/i;
|
|
32671
|
+
var SIGN = { "+": 1, "-": -1 };
|
|
32672
|
+
function termPx(term, side) {
|
|
32673
|
+
const match = TERM.exec(term ?? "");
|
|
32674
|
+
return match ? Number(match[1]) * (match[2] === "%" ? side / 100 : 1) : null;
|
|
32675
|
+
}
|
|
32676
|
+
function resolveLength(value, side) {
|
|
32677
|
+
const parts = (/^calc\((.*)\)$/.exec(value)?.[1] ?? value).split(" ");
|
|
32678
|
+
if (parts.length % 2 === 0) return null;
|
|
32679
|
+
let total = termPx(parts[0], side);
|
|
32680
|
+
for (let i = 1; i < parts.length && total !== null; i += 2) {
|
|
32681
|
+
const term = termPx(parts[i + 1], side);
|
|
32682
|
+
const sign = SIGN[parts[i] ?? ""];
|
|
32683
|
+
total = term === null || !sign ? null : total + sign * term;
|
|
32684
|
+
}
|
|
32685
|
+
return total;
|
|
32686
|
+
}
|
|
32687
|
+
function borderBox(cs, size, a, b) {
|
|
32688
|
+
const n = (prop) => Number.parseFloat(cs.getPropertyValue(prop)) || 0;
|
|
32689
|
+
const pad = n(`padding-${a}`) + n(`padding-${b}`) + n(`border-${a}-width`) + n(`border-${b}-width`);
|
|
32690
|
+
return n(size) + (cs.boxSizing === "border-box" ? 0 : pad);
|
|
32691
|
+
}
|
|
32692
|
+
function resolveInTransform(el, x, y) {
|
|
32693
|
+
const style = el.style;
|
|
32694
|
+
const saved2 = [style.getPropertyValue("transform"), style.getPropertyPriority("transform")];
|
|
32695
|
+
style.setProperty("transform", `translate(${x}, ${y})`, "important");
|
|
32696
|
+
try {
|
|
32697
|
+
const resolved2 = el.ownerDocument.defaultView?.getComputedStyle(el).transform ?? "";
|
|
32698
|
+
const matrix = new DOMMatrixReadOnly(resolved2.startsWith("matrix") ? resolved2 : void 0);
|
|
32699
|
+
return { x: matrix.m41, y: matrix.m42 };
|
|
32700
|
+
} finally {
|
|
32701
|
+
style.setProperty("transform", saved2[0] ?? "", saved2[1] ?? "");
|
|
32702
|
+
}
|
|
32703
|
+
}
|
|
32704
|
+
function readTranslatePx(el) {
|
|
32705
|
+
const cs = el.ownerDocument.defaultView?.getComputedStyle(el);
|
|
32706
|
+
const value = el.style.getPropertyValue("translate") || cs?.translate || "none";
|
|
32707
|
+
if (value === "none") return { x: 0, y: 0 };
|
|
32708
|
+
const [x = "0px", y = "0px"] = splitTopLevelWhitespace(value);
|
|
32709
|
+
const px = cs && resolveLength(x, borderBox(cs, "width", "left", "right"));
|
|
32710
|
+
const py = cs && resolveLength(y, borderBox(cs, "height", "top", "bottom"));
|
|
32711
|
+
return px != null && py != null ? { x: px, y: py } : resolveInTransform(el, x, y);
|
|
32712
|
+
}
|
|
32713
|
+
function formatTranslatePx(p) {
|
|
32714
|
+
return `${roundTo3(p.x)}px ${roundTo3(p.y)}px`;
|
|
32715
|
+
}
|
|
32716
|
+
function translatePatch(p) {
|
|
32717
|
+
return { type: "inline-style", property: "translate", value: formatTranslatePx(p) };
|
|
32718
|
+
}
|
|
32719
|
+
function writeTranslatePx(el, p) {
|
|
32720
|
+
el.style.setProperty("translate", formatTranslatePx(p));
|
|
32721
|
+
}
|
|
32722
|
+
function readMoveOffset(el) {
|
|
32723
|
+
return gsapWritesPosition(el) ? readStudioPathOffset(el) : readTranslatePx(el);
|
|
32724
|
+
}
|
|
32725
|
+
|
|
32386
32726
|
// src/components/editor/manualOffsetDrag.ts
|
|
32387
32727
|
function getOffsetDragGsap(element) {
|
|
32388
32728
|
const win = element.ownerDocument.defaultView;
|
|
@@ -32494,14 +32834,15 @@ function measureManualOffsetDragScreenToOffsetMatrix(element, initialOffset, opt
|
|
|
32494
32834
|
if (!Number.isFinite(probeSize) || probeSize <= 0) {
|
|
32495
32835
|
return { ok: false, reason: "Invalid movement probe size." };
|
|
32496
32836
|
}
|
|
32837
|
+
const draw = options.plainTranslate ? writeTranslatePx : applyStudioPathOffsetDraft;
|
|
32497
32838
|
const snapshot3 = captureStudioPathOffset(element);
|
|
32498
32839
|
try {
|
|
32499
|
-
|
|
32840
|
+
draw(element, initialOffset);
|
|
32500
32841
|
const origin = getRectCenter(element);
|
|
32501
32842
|
if (!origin) {
|
|
32502
32843
|
return { ok: false, reason: "Element has no measurable box." };
|
|
32503
32844
|
}
|
|
32504
|
-
|
|
32845
|
+
draw(element, {
|
|
32505
32846
|
x: initialOffset.x + probeSize,
|
|
32506
32847
|
y: initialOffset.y
|
|
32507
32848
|
});
|
|
@@ -32509,7 +32850,7 @@ function measureManualOffsetDragScreenToOffsetMatrix(element, initialOffset, opt
|
|
|
32509
32850
|
if (!probeX) {
|
|
32510
32851
|
return { ok: false, reason: "Element X movement could not be measured." };
|
|
32511
32852
|
}
|
|
32512
|
-
|
|
32853
|
+
draw(element, {
|
|
32513
32854
|
x: initialOffset.x,
|
|
32514
32855
|
y: initialOffset.y + probeSize
|
|
32515
32856
|
});
|
|
@@ -32554,14 +32895,13 @@ function resolveManualOffsetForPointerDelta(input) {
|
|
|
32554
32895
|
};
|
|
32555
32896
|
}
|
|
32556
32897
|
function createManualOffsetDragMember(input) {
|
|
32557
|
-
const
|
|
32558
|
-
input.element
|
|
32559
|
-
input.element.setAttribute("data-hf-drag-initial-offset-y", String(initialOffset.y));
|
|
32898
|
+
const plainTranslate = !!input.gesture && !gsapWritesPosition(input.element);
|
|
32899
|
+
const initialOffset = plainTranslate ? readTranslatePx(input.element) : readAppliedStudioPathOffset(input.element);
|
|
32560
32900
|
const win = input.element.ownerDocument.defaultView;
|
|
32561
|
-
const gsapX = win?.gsap?.getProperty?.(input.element, "x") || 0;
|
|
32562
|
-
const gsapY = win?.gsap?.getProperty?.(input.element, "y") || 0;
|
|
32563
|
-
|
|
32564
|
-
input.element
|
|
32901
|
+
const gsapX = !plainTranslate && win?.gsap?.getProperty?.(input.element, "x") || 0;
|
|
32902
|
+
const gsapY = !plainTranslate && win?.gsap?.getProperty?.(input.element, "y") || 0;
|
|
32903
|
+
const baseGsap = { x: gsapX, y: gsapY };
|
|
32904
|
+
if (!plainTranslate) stampGestureBase(input.element, initialOffset, baseGsap);
|
|
32565
32905
|
if (win?.__timelines) {
|
|
32566
32906
|
const paused = [];
|
|
32567
32907
|
for (const [id, tl] of Object.entries(win.__timelines)) {
|
|
@@ -32581,9 +32921,9 @@ function createManualOffsetDragMember(input) {
|
|
|
32581
32921
|
const gestureToken = beginStudioManualEditGesture(input.element);
|
|
32582
32922
|
const measured = measureManualOffsetDragScreenToOffsetMatrix(input.element, initialOffset, {
|
|
32583
32923
|
scaleX: input.rect.editScaleX,
|
|
32584
|
-
scaleY: input.rect.editScaleY
|
|
32924
|
+
scaleY: input.rect.editScaleY,
|
|
32925
|
+
plainTranslate
|
|
32585
32926
|
});
|
|
32586
|
-
const baseGsap = { x: gsapX, y: gsapY };
|
|
32587
32927
|
if (!measured.ok) {
|
|
32588
32928
|
const scaleX = input.rect.editScaleX || 1;
|
|
32589
32929
|
const scaleY = input.rect.editScaleY || 1;
|
|
@@ -32595,6 +32935,7 @@ function createManualOffsetDragMember(input) {
|
|
|
32595
32935
|
selection: input.selection,
|
|
32596
32936
|
element: input.element,
|
|
32597
32937
|
initialOffset,
|
|
32938
|
+
plainTranslate,
|
|
32598
32939
|
baseGsap,
|
|
32599
32940
|
initialPathOffset,
|
|
32600
32941
|
gestureToken,
|
|
@@ -32610,6 +32951,7 @@ function createManualOffsetDragMember(input) {
|
|
|
32610
32951
|
selection: input.selection,
|
|
32611
32952
|
element: input.element,
|
|
32612
32953
|
initialOffset,
|
|
32954
|
+
plainTranslate,
|
|
32613
32955
|
baseGsap,
|
|
32614
32956
|
initialPathOffset,
|
|
32615
32957
|
gestureToken,
|
|
@@ -32626,24 +32968,27 @@ function resolveManualOffsetDragMemberOffset(member, dx, dy) {
|
|
|
32626
32968
|
dy
|
|
32627
32969
|
});
|
|
32628
32970
|
}
|
|
32971
|
+
function drawMemberOffset(member, offset, commit) {
|
|
32972
|
+
if (member.plainTranslate) writeTranslatePx(member.element, offset);
|
|
32973
|
+
else if (applyOffsetDragDraftViaGsap(member.element, offset, member.baseGsap)) return;
|
|
32974
|
+
else if (commit) applyStudioPathOffset(member.element, offset);
|
|
32975
|
+
else applyStudioPathOffsetDraft(member.element, offset);
|
|
32976
|
+
}
|
|
32629
32977
|
function applyManualOffsetDragDraft(member, dx, dy) {
|
|
32630
32978
|
const offset = resolveManualOffsetDragMemberOffset(member, dx, dy);
|
|
32631
|
-
|
|
32632
|
-
applyStudioPathOffsetDraft(member.element, offset);
|
|
32633
|
-
}
|
|
32979
|
+
drawMemberOffset(member, offset, false);
|
|
32634
32980
|
return offset;
|
|
32635
32981
|
}
|
|
32636
|
-
function
|
|
32637
|
-
|
|
32638
|
-
|
|
32639
|
-
|
|
32640
|
-
|
|
32982
|
+
function stampGestureBase(el, initialOffset, baseGsap) {
|
|
32983
|
+
el.setAttribute("data-hf-drag-gsap-base-x", String(baseGsap.x));
|
|
32984
|
+
el.setAttribute("data-hf-drag-gsap-base-y", String(baseGsap.y));
|
|
32985
|
+
el.setAttribute("data-hf-drag-initial-offset-x", String(initialOffset.x));
|
|
32986
|
+
el.setAttribute("data-hf-drag-initial-offset-y", String(initialOffset.y));
|
|
32641
32987
|
}
|
|
32642
32988
|
function applyManualOffsetCommitValue(member, offset) {
|
|
32643
|
-
|
|
32644
|
-
|
|
32645
|
-
|
|
32646
|
-
}
|
|
32989
|
+
if (!member.plainTranslate)
|
|
32990
|
+
stampGestureBase(member.element, member.initialOffset, member.baseGsap);
|
|
32991
|
+
drawMemberOffset(member, offset, true);
|
|
32647
32992
|
return offset;
|
|
32648
32993
|
}
|
|
32649
32994
|
function applyManualOffsetDragCommit(member, dx, dy) {
|
|
@@ -32654,9 +32999,7 @@ function applyManualOffsetNudgeDraft(member, delta) {
|
|
|
32654
32999
|
x: member.initialOffset.x + delta.x,
|
|
32655
33000
|
y: member.initialOffset.y + delta.y
|
|
32656
33001
|
};
|
|
32657
|
-
|
|
32658
|
-
applyStudioPathOffsetDraft(member.element, offset);
|
|
32659
|
-
}
|
|
33002
|
+
drawMemberOffset(member, offset, false);
|
|
32660
33003
|
return offset;
|
|
32661
33004
|
}
|
|
32662
33005
|
function applyManualOffsetNudgeCommit(member, delta) {
|
|
@@ -32667,7 +33010,9 @@ function applyManualOffsetNudgeCommit(member, delta) {
|
|
|
32667
33010
|
}
|
|
32668
33011
|
function restoreManualOffsetDragMember(member) {
|
|
32669
33012
|
restoreStudioPathOffset(member.element, member.initialPathOffset);
|
|
32670
|
-
|
|
33013
|
+
if (!member.plainTranslate) {
|
|
33014
|
+
getOffsetDragGsap(member.element)?.set(member.element, { ...member.baseGsap });
|
|
33015
|
+
}
|
|
32671
33016
|
endStudioManualEditGesture(member.element, member.gestureToken);
|
|
32672
33017
|
}
|
|
32673
33018
|
function restoreManualOffsetDragMembers(members) {
|
|
@@ -32692,7 +33037,7 @@ function endManualOffsetDragMembers(members) {
|
|
|
32692
33037
|
member.element.removeAttribute("data-hf-drag-initial-offset-y");
|
|
32693
33038
|
member.element.removeAttribute("data-hf-drag-gsap-base-x");
|
|
32694
33039
|
member.element.removeAttribute("data-hf-drag-gsap-base-y");
|
|
32695
|
-
const gsapOwnsPosition = Boolean(getOffsetDragGsap(member.element));
|
|
33040
|
+
const gsapOwnsPosition = !member.plainTranslate && Boolean(getOffsetDragGsap(member.element));
|
|
32696
33041
|
if (gsapOwnsPosition) clearStudioPathOffset(member.element);
|
|
32697
33042
|
settleDraftTranslate(member.element, gsapOwnsPosition);
|
|
32698
33043
|
resumeGsapTimelines(member.element);
|
|
@@ -33345,7 +33690,8 @@ function startGroupDrag(e, opts) {
|
|
|
33345
33690
|
key: item.key,
|
|
33346
33691
|
selection: item.selection,
|
|
33347
33692
|
element: item.element,
|
|
33348
|
-
rect: item.rect
|
|
33693
|
+
rect: item.rect,
|
|
33694
|
+
gesture: "drag"
|
|
33349
33695
|
});
|
|
33350
33696
|
if (!result.ok) {
|
|
33351
33697
|
restoreManualOffsetDragMembers(members);
|
|
@@ -33409,7 +33755,6 @@ function startGesture(kind, e, opts, options) {
|
|
|
33409
33755
|
if (kind === "resize" && (!Number.isFinite(rect.width) || !Number.isFinite(rect.height)))
|
|
33410
33756
|
return false;
|
|
33411
33757
|
const size = readStudioBoxSize(sel.element);
|
|
33412
|
-
const rotation = { angle: readGsapRotation(sel.element) + readStudioRotation(sel.element).angle };
|
|
33413
33758
|
const layoutWidth = sel.element.offsetWidth;
|
|
33414
33759
|
const layoutHeight = sel.element.offsetHeight;
|
|
33415
33760
|
const actualWidth = size.width > 0 ? size.width : layoutWidth > 0 ? layoutWidth : rect.width / rect.editScaleX;
|
|
@@ -33440,7 +33785,8 @@ function startGesture(kind, e, opts, options) {
|
|
|
33440
33785
|
key: selectionCacheKey(sel),
|
|
33441
33786
|
selection: sel,
|
|
33442
33787
|
element: sel.element,
|
|
33443
|
-
rect
|
|
33788
|
+
rect,
|
|
33789
|
+
gesture: "drag"
|
|
33444
33790
|
});
|
|
33445
33791
|
if (!result.ok) {
|
|
33446
33792
|
opts.onBlockedMoveRef.current(result.selection);
|
|
@@ -33469,6 +33815,8 @@ function startGesture(kind, e, opts, options) {
|
|
|
33469
33815
|
manualEditDragToken = beginStudioManualEditGesture(sel.element);
|
|
33470
33816
|
}
|
|
33471
33817
|
}
|
|
33818
|
+
const gsapRotation = pathOffsetMember?.plainTranslate ? 0 : readGsapRotation(sel.element);
|
|
33819
|
+
const rotation = { angle: gsapRotation + readStudioRotation(sel.element).angle };
|
|
33472
33820
|
const overlayBounds = overlayEl?.getBoundingClientRect();
|
|
33473
33821
|
const centerX = (overlayBounds?.left ?? 0) + rect.left + rect.width / 2;
|
|
33474
33822
|
const centerY = (overlayBounds?.top ?? 0) + rect.top + rect.height / 2;
|
|
@@ -33619,6 +33967,7 @@ var DomEditSaveQueueOpenError = class extends Error {
|
|
|
33619
33967
|
function createDomEditSaveQueue(options = {}) {
|
|
33620
33968
|
const failureThreshold = options.failureThreshold ?? DEFAULT_FAILURE_THRESHOLD;
|
|
33621
33969
|
let tail = Promise.resolve();
|
|
33970
|
+
let queuedCount = 0;
|
|
33622
33971
|
let consecutiveFailures = 0;
|
|
33623
33972
|
let breakerOpen = false;
|
|
33624
33973
|
let drainError = null;
|
|
@@ -33657,13 +34006,14 @@ function createDomEditSaveQueue(options = {}) {
|
|
|
33657
34006
|
return {
|
|
33658
34007
|
enqueue(save) {
|
|
33659
34008
|
if (breakerOpen) return Promise.reject(new DomEditSaveQueueOpenError());
|
|
34009
|
+
queuedCount += 1;
|
|
33660
34010
|
const queued = tail.catch(() => void 0).then(() => {
|
|
33661
34011
|
if (breakerOpen) throw new DomEditSaveQueueOpenError();
|
|
33662
34012
|
return run(save);
|
|
33663
34013
|
});
|
|
33664
34014
|
tail = queued.then(
|
|
33665
|
-
() => void
|
|
33666
|
-
() => void
|
|
34015
|
+
() => void (queuedCount -= 1),
|
|
34016
|
+
() => void (queuedCount -= 1)
|
|
33667
34017
|
);
|
|
33668
34018
|
return queued;
|
|
33669
34019
|
},
|
|
@@ -33675,6 +34025,7 @@ function createDomEditSaveQueue(options = {}) {
|
|
|
33675
34025
|
if (drainError != null) return { status: "failed", error: drainError };
|
|
33676
34026
|
return { status: "clean" };
|
|
33677
34027
|
},
|
|
34028
|
+
isIdle: () => queuedCount === 0,
|
|
33678
34029
|
reset,
|
|
33679
34030
|
destroy() {
|
|
33680
34031
|
reset(false);
|
|
@@ -34185,7 +34536,7 @@ function useDomEditNudge(params) {
|
|
|
34185
34536
|
if (!targets) return null;
|
|
34186
34537
|
const members = [];
|
|
34187
34538
|
for (const target of targets) {
|
|
34188
|
-
const result = createManualOffsetDragMember(target);
|
|
34539
|
+
const result = createManualOffsetDragMember({ ...target, gesture: "nudge" });
|
|
34189
34540
|
if (!result.ok) {
|
|
34190
34541
|
restoreManualOffsetDragMembers(members);
|
|
34191
34542
|
return null;
|
|
@@ -34375,253 +34726,6 @@ var SnapGuideOverlay = memo23(function SnapGuideOverlay2({
|
|
|
34375
34726
|
// src/components/editor/DomEditCropHandles.tsx
|
|
34376
34727
|
import { useEffect as useEffect44, useRef as useRef65, useState as useState40 } from "react";
|
|
34377
34728
|
|
|
34378
|
-
// src/hooks/gsapRuntimeKeyframes.ts
|
|
34379
|
-
import { buildArcPath } from "@hyperframes/core/gsap-parser-acorn";
|
|
34380
|
-
var FLAT_SKIP_KEYS = /* @__PURE__ */ new Set([
|
|
34381
|
-
"ease",
|
|
34382
|
-
"duration",
|
|
34383
|
-
"delay",
|
|
34384
|
-
"stagger",
|
|
34385
|
-
"motionPath",
|
|
34386
|
-
"overwrite",
|
|
34387
|
-
"immediateRender",
|
|
34388
|
-
"onComplete",
|
|
34389
|
-
"onUpdate",
|
|
34390
|
-
"onStart",
|
|
34391
|
-
"keyframes"
|
|
34392
|
-
]);
|
|
34393
|
-
function timelinesOf(iframe) {
|
|
34394
|
-
if (!iframe?.contentWindow) return null;
|
|
34395
|
-
try {
|
|
34396
|
-
return iframe.contentWindow.__timelines ?? null;
|
|
34397
|
-
} catch {
|
|
34398
|
-
return null;
|
|
34399
|
-
}
|
|
34400
|
-
}
|
|
34401
|
-
function isXY(p) {
|
|
34402
|
-
return !!p && typeof p.x === "number" && typeof p.y === "number";
|
|
34403
|
-
}
|
|
34404
|
-
function isZeroDurationSet(duration) {
|
|
34405
|
-
return !(duration > 0);
|
|
34406
|
-
}
|
|
34407
|
-
function coordsFromMotionPath(mp) {
|
|
34408
|
-
if (!mp || typeof mp !== "object") return null;
|
|
34409
|
-
const obj = mp;
|
|
34410
|
-
const pathVal = Array.isArray(mp) ? mp : obj.path;
|
|
34411
|
-
if (!Array.isArray(pathVal)) return null;
|
|
34412
|
-
const coords = pathVal.filter(isXY).map((p) => ({ x: p.x, y: p.y }));
|
|
34413
|
-
if (coords.length < 2) return null;
|
|
34414
|
-
const curviness = typeof obj.curviness === "number" ? obj.curviness : 1;
|
|
34415
|
-
const autoRotate = typeof obj.autoRotate === "number" ? obj.autoRotate : obj.autoRotate === true;
|
|
34416
|
-
return { coords, curviness, autoRotate, isCubic: obj.type === "cubic" };
|
|
34417
|
-
}
|
|
34418
|
-
function flatTweenKeyframes(vars) {
|
|
34419
|
-
const properties = {};
|
|
34420
|
-
for (const [k, v] of Object.entries(vars)) {
|
|
34421
|
-
if (FLAT_SKIP_KEYS.has(k)) continue;
|
|
34422
|
-
if (typeof v === "number") properties[k] = roundTo3(v);
|
|
34423
|
-
else if (typeof v === "string") properties[k] = v;
|
|
34424
|
-
}
|
|
34425
|
-
if (Object.keys(properties).length === 0) return null;
|
|
34426
|
-
return [
|
|
34427
|
-
{ percentage: 0, properties },
|
|
34428
|
-
{ percentage: 100, properties }
|
|
34429
|
-
];
|
|
34430
|
-
}
|
|
34431
|
-
function readTween(vars) {
|
|
34432
|
-
if (vars.keyframes && typeof vars.keyframes === "object") {
|
|
34433
|
-
const parsed = parsePercentageKeyframes(vars.keyframes);
|
|
34434
|
-
if (parsed) return parsed;
|
|
34435
|
-
}
|
|
34436
|
-
const mp = coordsFromMotionPath(vars.motionPath);
|
|
34437
|
-
if (mp) {
|
|
34438
|
-
const shape = buildArcPath(mp.coords, mp.curviness, mp.autoRotate, mp.isCubic);
|
|
34439
|
-
if (shape) {
|
|
34440
|
-
const n = shape.waypoints.length;
|
|
34441
|
-
const keyframes = shape.waypoints.map((wp, i) => ({
|
|
34442
|
-
percentage: n > 1 ? Math.round(i / (n - 1) * 100) : 0,
|
|
34443
|
-
properties: { x: wp.x, y: wp.y }
|
|
34444
|
-
}));
|
|
34445
|
-
return { keyframes, arcPath: shape.arcPath };
|
|
34446
|
-
}
|
|
34447
|
-
}
|
|
34448
|
-
const flat = flatTweenKeyframes(vars);
|
|
34449
|
-
return flat ? { keyframes: flat } : null;
|
|
34450
|
-
}
|
|
34451
|
-
function matchesElement(tween, el) {
|
|
34452
|
-
if (!tween.targets) return false;
|
|
34453
|
-
for (const t of tween.targets()) {
|
|
34454
|
-
if (t === el || el.id && t.id === el.id) return true;
|
|
34455
|
-
}
|
|
34456
|
-
return false;
|
|
34457
|
-
}
|
|
34458
|
-
function tweenTiming(tween) {
|
|
34459
|
-
const rawStart = typeof tween.startTime === "function" ? tween.startTime() : 0;
|
|
34460
|
-
const rawDur = typeof tween.duration === "function" ? tween.duration() : 0;
|
|
34461
|
-
return {
|
|
34462
|
-
start: Number.isFinite(rawStart) ? rawStart : 0,
|
|
34463
|
-
duration: Number.isFinite(rawDur) ? rawDur : 0
|
|
34464
|
-
};
|
|
34465
|
-
}
|
|
34466
|
-
function varsCarryChannel(vars, channels) {
|
|
34467
|
-
if (!vars) return false;
|
|
34468
|
-
for (const ch of channels) {
|
|
34469
|
-
if (Object.prototype.hasOwnProperty.call(vars, ch)) return true;
|
|
34470
|
-
}
|
|
34471
|
-
return false;
|
|
34472
|
-
}
|
|
34473
|
-
function keyframeVarsCarryChannel(vars, channels) {
|
|
34474
|
-
const kf = vars?.keyframes;
|
|
34475
|
-
if (!kf || typeof kf !== "object") return false;
|
|
34476
|
-
if (!Array.isArray(kf) && channels.some((ch) => Object.prototype.hasOwnProperty.call(kf, ch)))
|
|
34477
|
-
return true;
|
|
34478
|
-
const steps = Array.isArray(kf) ? kf : Object.values(kf);
|
|
34479
|
-
return steps.some(
|
|
34480
|
-
(step) => step != null && typeof step === "object" && channels.some((ch) => Object.prototype.hasOwnProperty.call(step, ch))
|
|
34481
|
-
);
|
|
34482
|
-
}
|
|
34483
|
-
function resolveRuntimeTween(iframe, selector, kind, compositionId, channels) {
|
|
34484
|
-
const timelines = timelinesOf(iframe);
|
|
34485
|
-
if (!timelines) return null;
|
|
34486
|
-
let targetEl = null;
|
|
34487
|
-
try {
|
|
34488
|
-
targetEl = iframe?.contentDocument?.querySelector(selector) ?? null;
|
|
34489
|
-
} catch {
|
|
34490
|
-
return null;
|
|
34491
|
-
}
|
|
34492
|
-
if (!targetEl) return null;
|
|
34493
|
-
const tlIds = compositionId ? [compositionId] : Object.keys(timelines).filter((k) => typeof timelines[k]?.getChildren === "function");
|
|
34494
|
-
const wantChannels = channels && channels.length > 0 ? channels : null;
|
|
34495
|
-
let first = null;
|
|
34496
|
-
let channelMatch = null;
|
|
34497
|
-
for (const tlId of tlIds) {
|
|
34498
|
-
const timeline = timelines[tlId];
|
|
34499
|
-
if (!timeline?.getChildren) continue;
|
|
34500
|
-
const now2 = typeof timeline.time === "function" ? timeline.time() : null;
|
|
34501
|
-
for (const tween of timeline.getChildren(true)) {
|
|
34502
|
-
if (!tween.vars || !matchesElement(tween, targetEl)) continue;
|
|
34503
|
-
const dur = typeof tween.duration === "function" ? tween.duration() : 0;
|
|
34504
|
-
const isSet = !(dur > 0);
|
|
34505
|
-
if (kind === "set" ? !isSet : isSet) continue;
|
|
34506
|
-
if (wantChannels) {
|
|
34507
|
-
const carries = kind === "set" ? varsCarryChannel(tween.vars, wantChannels) : keyframeVarsCarryChannel(tween.vars, wantChannels);
|
|
34508
|
-
if (carries) {
|
|
34509
|
-
if (channelMatch === null) channelMatch = { tween, timeline };
|
|
34510
|
-
} else if (first === null) {
|
|
34511
|
-
first = { tween, timeline };
|
|
34512
|
-
}
|
|
34513
|
-
continue;
|
|
34514
|
-
}
|
|
34515
|
-
if (first === null) first = { tween, timeline };
|
|
34516
|
-
if (kind === "keyframe" && now2 != null) {
|
|
34517
|
-
const start = typeof tween.startTime === "function" ? tween.startTime() : 0;
|
|
34518
|
-
if (now2 >= start - 1e-3 && now2 <= start + dur + 1e-3) return { tween, timeline };
|
|
34519
|
-
}
|
|
34520
|
-
}
|
|
34521
|
-
}
|
|
34522
|
-
return channelMatch ?? first;
|
|
34523
|
-
}
|
|
34524
|
-
function readCarriesChannel(read, channels) {
|
|
34525
|
-
return read.keyframes.some((kf) => channels.some((c) => kf.properties[c] != null));
|
|
34526
|
-
}
|
|
34527
|
-
function readRuntimeKeyframes(iframe, selector, compositionId, requireChannels) {
|
|
34528
|
-
const timelines = timelinesOf(iframe);
|
|
34529
|
-
if (!timelines) return null;
|
|
34530
|
-
let targetEl = null;
|
|
34531
|
-
try {
|
|
34532
|
-
targetEl = iframe?.contentDocument?.querySelector(selector) ?? null;
|
|
34533
|
-
} catch {
|
|
34534
|
-
return null;
|
|
34535
|
-
}
|
|
34536
|
-
if (!targetEl) return null;
|
|
34537
|
-
const tlIds = compositionId ? [compositionId] : Object.keys(timelines).filter((k) => typeof timelines[k]?.getChildren === "function");
|
|
34538
|
-
if (tlIds.length === 0) return null;
|
|
34539
|
-
let firstRead = null;
|
|
34540
|
-
for (const tlId of tlIds) {
|
|
34541
|
-
const timeline = timelines[tlId];
|
|
34542
|
-
if (!timeline?.getChildren) continue;
|
|
34543
|
-
const now2 = typeof timeline.time === "function" ? timeline.time() : null;
|
|
34544
|
-
for (const tween of timeline.getChildren(true)) {
|
|
34545
|
-
if (!tween.vars || !matchesElement(tween, targetEl)) continue;
|
|
34546
|
-
const dur = typeof tween.duration === "function" ? tween.duration() : 0;
|
|
34547
|
-
if (isZeroDurationSet(dur)) continue;
|
|
34548
|
-
const read = readTween(tween.vars);
|
|
34549
|
-
if (!read) continue;
|
|
34550
|
-
if (requireChannels && !readCarriesChannel(read, requireChannels)) continue;
|
|
34551
|
-
if (firstRead === null) firstRead = read;
|
|
34552
|
-
if (now2 != null) {
|
|
34553
|
-
const start = typeof tween.startTime === "function" ? tween.startTime() : 0;
|
|
34554
|
-
if (now2 >= start - 1e-3 && now2 <= start + dur + 1e-3) return read;
|
|
34555
|
-
}
|
|
34556
|
-
}
|
|
34557
|
-
}
|
|
34558
|
-
return firstRead;
|
|
34559
|
-
}
|
|
34560
|
-
function hasNonHoldTweenForElement(iframe, selector, compositionId, channels) {
|
|
34561
|
-
let targetEl = null;
|
|
34562
|
-
try {
|
|
34563
|
-
targetEl = iframe?.contentDocument?.querySelector(selector) ?? null;
|
|
34564
|
-
} catch {
|
|
34565
|
-
return false;
|
|
34566
|
-
}
|
|
34567
|
-
return !!targetEl && hasNonHoldTween(timelinesOf(iframe), targetEl, channels, compositionId);
|
|
34568
|
-
}
|
|
34569
|
-
function elementHasNonHoldTween(el, channels) {
|
|
34570
|
-
const win = el.ownerDocument.defaultView;
|
|
34571
|
-
return hasNonHoldTween(win?.__timelines ?? null, el, channels);
|
|
34572
|
-
}
|
|
34573
|
-
function hasNonHoldTween(timelines, targetEl, channels, compositionId) {
|
|
34574
|
-
if (!timelines) return false;
|
|
34575
|
-
for (const tlId of compositionId ? [compositionId] : Object.keys(timelines)) {
|
|
34576
|
-
for (const tween of timelines[tlId]?.getChildren?.(true) ?? []) {
|
|
34577
|
-
if (!tween.vars || !matchesElement(tween, targetEl)) continue;
|
|
34578
|
-
const dur = typeof tween.duration === "function" ? tween.duration() : 0;
|
|
34579
|
-
if (isZeroDurationSet(dur)) continue;
|
|
34580
|
-
if (channels && keyframeVarsCarryChannel(tween.vars, channels)) return true;
|
|
34581
|
-
const read = readTween(tween.vars);
|
|
34582
|
-
if (read && (!channels || readCarriesChannel(read, channels))) return true;
|
|
34583
|
-
}
|
|
34584
|
-
}
|
|
34585
|
-
return false;
|
|
34586
|
-
}
|
|
34587
|
-
function toClipRelative(keyframes, tweenStart, tweenDuration, clip) {
|
|
34588
|
-
if (!clip || clip.duration <= 0) return keyframes;
|
|
34589
|
-
return keyframes.map((kf) => {
|
|
34590
|
-
const abs = toAbsoluteTime(tweenStart, tweenDuration, kf.percentage);
|
|
34591
|
-
return { ...kf, percentage: Math.round((abs - clip.start) / clip.duration * 1e5) / 1e3 };
|
|
34592
|
-
});
|
|
34593
|
-
}
|
|
34594
|
-
function buildEntry(read, start, duration, clip) {
|
|
34595
|
-
return {
|
|
34596
|
-
keyframes: toClipRelative(read.keyframes, start, duration, clip),
|
|
34597
|
-
tweenStart: start,
|
|
34598
|
-
tweenDuration: duration,
|
|
34599
|
-
...read.easeEach ? { easeEach: read.easeEach } : {},
|
|
34600
|
-
...read.arcPath ? { arcPath: read.arcPath } : {}
|
|
34601
|
-
};
|
|
34602
|
-
}
|
|
34603
|
-
function addScanEntry(result, tween, clipById) {
|
|
34604
|
-
if (!tween.targets || !tween.vars) return;
|
|
34605
|
-
const { start, duration } = tweenTiming(tween);
|
|
34606
|
-
if (isZeroDurationSet(duration)) return;
|
|
34607
|
-
const read = readTween(tween.vars);
|
|
34608
|
-
if (!read) return;
|
|
34609
|
-
for (const target of tween.targets()) {
|
|
34610
|
-
const id = target.id;
|
|
34611
|
-
if (id && !result.has(id)) result.set(id, buildEntry(read, start, duration, clipById?.get(id)));
|
|
34612
|
-
}
|
|
34613
|
-
}
|
|
34614
|
-
function scanAllRuntimeKeyframes(iframe, clipById) {
|
|
34615
|
-
const result = /* @__PURE__ */ new Map();
|
|
34616
|
-
const timelines = timelinesOf(iframe);
|
|
34617
|
-
if (!timelines) return result;
|
|
34618
|
-
for (const timeline of Object.values(timelines)) {
|
|
34619
|
-
if (!timeline?.getChildren) continue;
|
|
34620
|
-
for (const tween of timeline.getChildren(true)) addScanEntry(result, tween, clipById);
|
|
34621
|
-
}
|
|
34622
|
-
return result;
|
|
34623
|
-
}
|
|
34624
|
-
|
|
34625
34729
|
// src/components/editor/cropResize.ts
|
|
34626
34730
|
var boxOf = (element) => ({
|
|
34627
34731
|
width: element.offsetWidth,
|
|
@@ -42384,19 +42488,6 @@ function getAnimationsForElement(animations, target, element) {
|
|
|
42384
42488
|
}
|
|
42385
42489
|
|
|
42386
42490
|
// src/hooks/gsapRuntimeBridge.ts
|
|
42387
|
-
var POSITION_CHANNELS = [
|
|
42388
|
-
"x",
|
|
42389
|
-
"y",
|
|
42390
|
-
"xPercent",
|
|
42391
|
-
"yPercent",
|
|
42392
|
-
"left",
|
|
42393
|
-
"top",
|
|
42394
|
-
// GSAP normalizes translateX/Y to x/y at play time, but readTween reads the
|
|
42395
|
-
// AUTHORED shape — include them so a hand-authored translateX/Y position tween
|
|
42396
|
-
// still counts as a live position tween.
|
|
42397
|
-
"translateX",
|
|
42398
|
-
"translateY"
|
|
42399
|
-
];
|
|
42400
42491
|
var POSITION_CHANNEL_SET = new Set(POSITION_CHANNELS);
|
|
42401
42492
|
var ROTATION_CHANNELS = ["rotation", "rotationX", "rotationY", "rotationZ"];
|
|
42402
42493
|
var ROTATION_CHANNEL_SET = new Set(ROTATION_CHANNELS);
|
|
@@ -45044,12 +45135,13 @@ import { CornersIn, CornersOut, DotsThree, X as X2 } from "@phosphor-icons/react
|
|
|
45044
45135
|
import { create as create6 } from "zustand";
|
|
45045
45136
|
var useDockLayoutStore = create6((set, get) => ({
|
|
45046
45137
|
controller: null,
|
|
45138
|
+
panels: PANEL_IDS,
|
|
45047
45139
|
openPanels: new Set(PANEL_IDS),
|
|
45048
45140
|
visiblePanels: new Set(PANEL_IDS),
|
|
45049
45141
|
activePanel: null,
|
|
45050
45142
|
lastActive: {},
|
|
45051
45143
|
pendingActivation: null,
|
|
45052
|
-
attach: (controller) => set({ controller }),
|
|
45144
|
+
attach: (controller, panels = PANEL_IDS) => set({ controller, panels }),
|
|
45053
45145
|
detach: () => set({ controller: null, lastActive: {} }),
|
|
45054
45146
|
sync: (snapshot3) => set((state) => {
|
|
45055
45147
|
const lastActive = { ...state.lastActive };
|
|
@@ -45325,6 +45417,8 @@ var MIN_TIMELINE_H = 100;
|
|
|
45325
45417
|
var MIN_SIDE_W = 200;
|
|
45326
45418
|
var MIN_SIDE_W_FLOOR = 120;
|
|
45327
45419
|
var DEFAULT_TIMELINE_H = 360;
|
|
45420
|
+
var DEFAULT_LEFT = ["compositions", "assets", "code", "catalog"];
|
|
45421
|
+
var DEFAULT_RIGHT = ["design", "layers", "renders", "variables"];
|
|
45328
45422
|
function defaultSideWidths(viewportWidth) {
|
|
45329
45423
|
const floor = sideMinimumWidth(viewportWidth);
|
|
45330
45424
|
const left = Math.max(floor, Math.min(384, Math.round(viewportWidth * 0.257)));
|
|
@@ -45380,24 +45474,25 @@ function addRegisteredPanel(api, id, position) {
|
|
|
45380
45474
|
...position ? { position } : {}
|
|
45381
45475
|
});
|
|
45382
45476
|
}
|
|
45383
|
-
function
|
|
45477
|
+
function addSideColumn(api, ids, panels, direction) {
|
|
45478
|
+
const [first, ...rest] = ids.filter((id) => panels.includes(id));
|
|
45479
|
+
if (!first) return void 0;
|
|
45480
|
+
addRegisteredPanel(api, first, { referencePanel: "preview", direction });
|
|
45481
|
+
for (const id of rest)
|
|
45482
|
+
addRegisteredPanel(api, id, { referencePanel: first, direction: "within" });
|
|
45483
|
+
api.getPanel(first)?.api.setActive();
|
|
45484
|
+
return first;
|
|
45485
|
+
}
|
|
45486
|
+
function buildEditLayout(api, viewportWidth, panels = PANEL_IDS) {
|
|
45384
45487
|
api.clear();
|
|
45385
45488
|
const widths = defaultSideWidths(viewportWidth);
|
|
45386
45489
|
addRegisteredPanel(api, "preview");
|
|
45387
45490
|
addRegisteredPanel(api, "timeline", { referencePanel: "preview", direction: "below" });
|
|
45388
|
-
|
|
45389
|
-
|
|
45390
|
-
addRegisteredPanel(api, id, { referencePanel: "compositions", direction: "within" });
|
|
45391
|
-
}
|
|
45392
|
-
addRegisteredPanel(api, "design", { referencePanel: "preview", direction: "right" });
|
|
45393
|
-
for (const id of ["layers", "renders", "variables"]) {
|
|
45394
|
-
addRegisteredPanel(api, id, { referencePanel: "design", direction: "within" });
|
|
45395
|
-
}
|
|
45396
|
-
api.getPanel("compositions")?.api.setActive();
|
|
45397
|
-
api.getPanel("design")?.api.setActive();
|
|
45491
|
+
const left = addSideColumn(api, DEFAULT_LEFT, panels, "left");
|
|
45492
|
+
const right = addSideColumn(api, DEFAULT_RIGHT, panels, "right");
|
|
45398
45493
|
applySideMinimums(api, viewportWidth);
|
|
45399
|
-
api.getPanel(
|
|
45400
|
-
api.getPanel(
|
|
45494
|
+
if (left) api.getPanel(left)?.group.api.setSize({ width: widths.left });
|
|
45495
|
+
if (right) api.getPanel(right)?.group.api.setSize({ width: widths.right });
|
|
45401
45496
|
api.getPanel("timeline")?.group.api.setSize({ height: DEFAULT_TIMELINE_H });
|
|
45402
45497
|
}
|
|
45403
45498
|
|
|
@@ -45442,10 +45537,11 @@ function snapshot(api) {
|
|
|
45442
45537
|
groupActivePanels
|
|
45443
45538
|
};
|
|
45444
45539
|
}
|
|
45445
|
-
function createController(api) {
|
|
45540
|
+
function createController(api, options) {
|
|
45446
45541
|
const open = (id) => {
|
|
45447
45542
|
if (api.getPanel(id)) return;
|
|
45448
|
-
const { zone, reopen } = PANEL_DEFINITIONS[id];
|
|
45543
|
+
const { zone, reopen: preferred } = PANEL_DEFINITIONS[id];
|
|
45544
|
+
const reopen = zone === "center" || options.panels.includes(preferred.near) ? preferred : { near: "preview", direction: zone };
|
|
45449
45545
|
const sibling = zone === "center" ? void 0 : panelsInZone(zone).find((other) => other !== id && api.getPanel(other));
|
|
45450
45546
|
if (sibling) {
|
|
45451
45547
|
addRegisteredPanel(api, id, { referencePanel: sibling, direction: "within" });
|
|
@@ -45467,11 +45563,12 @@ function createController(api) {
|
|
|
45467
45563
|
},
|
|
45468
45564
|
setTitle: (id, title) => api.getPanel(id)?.api.setTitle(title),
|
|
45469
45565
|
setGroupVisible: (id, visible) => api.getPanel(id)?.group.api.setVisible(visible),
|
|
45470
|
-
reset: () => buildEditLayout(api,
|
|
45566
|
+
reset: () => buildEditLayout(api, options.dockWidth(), options.panels)
|
|
45471
45567
|
};
|
|
45472
45568
|
}
|
|
45473
|
-
function restoreOrBuild(api, projectId) {
|
|
45474
|
-
const
|
|
45569
|
+
function restoreOrBuild(api, projectId, options) {
|
|
45570
|
+
const { dockLayout } = readStudioUiPreferences(void 0, projectId, options.storageKey);
|
|
45571
|
+
const stored = parseDockLayout(dockLayout, options.panels);
|
|
45475
45572
|
if (stored) {
|
|
45476
45573
|
try {
|
|
45477
45574
|
api.fromJSON(stored);
|
|
@@ -45479,9 +45576,16 @@ function restoreOrBuild(api, projectId) {
|
|
|
45479
45576
|
} catch {
|
|
45480
45577
|
}
|
|
45481
45578
|
}
|
|
45482
|
-
buildEditLayout(api,
|
|
45579
|
+
buildEditLayout(api, options.dockWidth(), options.panels);
|
|
45483
45580
|
}
|
|
45484
|
-
|
|
45581
|
+
var windowWidth = () => window.innerWidth;
|
|
45582
|
+
function Root({
|
|
45583
|
+
projectId,
|
|
45584
|
+
panels = PANEL_IDS,
|
|
45585
|
+
storageKey,
|
|
45586
|
+
dockWidth = windowWidth,
|
|
45587
|
+
children
|
|
45588
|
+
}) {
|
|
45485
45589
|
const [slots, setSlots] = useState50({});
|
|
45486
45590
|
const registerSlot = useCallback70((id, element) => {
|
|
45487
45591
|
setSlots((prev) => {
|
|
@@ -45498,17 +45602,18 @@ function Root({ projectId, children }) {
|
|
|
45498
45602
|
const onReady = useCallback70(
|
|
45499
45603
|
({ api }) => {
|
|
45500
45604
|
disposeRef.current();
|
|
45501
|
-
|
|
45502
|
-
|
|
45605
|
+
const options = { panels, storageKey, dockWidth };
|
|
45606
|
+
restoreOrBuild(api, projectId, options);
|
|
45607
|
+
applySideMinimums(api, dockWidth());
|
|
45503
45608
|
const root = api.groups[0]?.element.closest(".hf-dock");
|
|
45504
45609
|
const disposeAccessibility = root ? installDockAccessibility(api, root) : () => {
|
|
45505
45610
|
};
|
|
45506
45611
|
const disposeTabFill = root ? installTabFill(api, root) : () => {
|
|
45507
45612
|
};
|
|
45508
|
-
const resizeObserver = new ResizeObserver(() => applySideMinimums(api,
|
|
45613
|
+
const resizeObserver = new ResizeObserver(() => applySideMinimums(api, dockWidth()));
|
|
45509
45614
|
if (root) resizeObserver.observe(root);
|
|
45510
45615
|
const store = useDockLayoutStore.getState();
|
|
45511
|
-
store.attach(createController(api));
|
|
45616
|
+
store.attach(createController(api, options), panels);
|
|
45512
45617
|
store.sync(snapshot(api));
|
|
45513
45618
|
const pending2 = store.takePendingActivation();
|
|
45514
45619
|
if (pending2) api.getPanel(pending2)?.api.setActive();
|
|
@@ -45516,7 +45621,7 @@ function Root({ projectId, children }) {
|
|
|
45516
45621
|
const persist2 = () => {
|
|
45517
45622
|
clearTimeout(timer);
|
|
45518
45623
|
timer = setTimeout(() => {
|
|
45519
|
-
writeStudioUiPreferences({ dockLayout: api.toJSON() }, void 0, projectId);
|
|
45624
|
+
writeStudioUiPreferences({ dockLayout: api.toJSON() }, void 0, projectId, storageKey);
|
|
45520
45625
|
}, PERSIST_DEBOUNCE_MS2);
|
|
45521
45626
|
};
|
|
45522
45627
|
const onDockChange = () => {
|
|
@@ -45525,11 +45630,11 @@ function Root({ projectId, children }) {
|
|
|
45525
45630
|
};
|
|
45526
45631
|
const subscriptions = [
|
|
45527
45632
|
api.onDidAddPanel(() => {
|
|
45528
|
-
applySideMinimums(api);
|
|
45633
|
+
applySideMinimums(api, dockWidth());
|
|
45529
45634
|
onDockChange();
|
|
45530
45635
|
}),
|
|
45531
45636
|
api.onDidMovePanel(() => {
|
|
45532
|
-
applySideMinimums(api);
|
|
45637
|
+
applySideMinimums(api, dockWidth());
|
|
45533
45638
|
onDockChange();
|
|
45534
45639
|
}),
|
|
45535
45640
|
api.onDidRemovePanel(onDockChange),
|
|
@@ -45545,7 +45650,7 @@ function Root({ projectId, children }) {
|
|
|
45545
45650
|
useDockLayoutStore.getState().detach();
|
|
45546
45651
|
};
|
|
45547
45652
|
},
|
|
45548
|
-
[projectId]
|
|
45653
|
+
[projectId, panels, storageKey, dockWidth]
|
|
45549
45654
|
);
|
|
45550
45655
|
return /* @__PURE__ */ jsxs71(SlotsContext.Provider, { value: { slots, registerSlot }, children: [
|
|
45551
45656
|
/* @__PURE__ */ jsx101(
|
|
@@ -45580,6 +45685,7 @@ function Panel({ id, title, children }) {
|
|
|
45580
45685
|
}
|
|
45581
45686
|
function WindowMenu() {
|
|
45582
45687
|
const openPanels = useDockLayoutStore((state) => state.openPanels);
|
|
45688
|
+
const panels = useDockLayoutStore((state) => state.panels);
|
|
45583
45689
|
const togglePanel = useDockLayoutStore((state) => state.togglePanel);
|
|
45584
45690
|
const resetLayout = useDockLayoutStore((state) => state.resetLayout);
|
|
45585
45691
|
const [open, setOpen] = useState50(false);
|
|
@@ -45610,7 +45716,7 @@ function WindowMenu() {
|
|
|
45610
45716
|
role: "menu",
|
|
45611
45717
|
className: "absolute right-0 top-8 z-50 w-44 rounded-md border border-neutral-800 bg-neutral-900 py-1 shadow-lg",
|
|
45612
45718
|
children: [
|
|
45613
|
-
|
|
45719
|
+
panels.map((id) => /* @__PURE__ */ jsxs71(
|
|
45614
45720
|
"button",
|
|
45615
45721
|
{
|
|
45616
45722
|
type: "button",
|
|
@@ -47838,7 +47944,7 @@ function createTransformCommitHandlers({
|
|
|
47838
47944
|
"This element's position can't be edited here yet \u2014 it is driven by its animation"
|
|
47839
47945
|
))
|
|
47840
47946
|
return;
|
|
47841
|
-
const current =
|
|
47947
|
+
const current = readMoveOffset(element.element);
|
|
47842
47948
|
await Promise.resolve(
|
|
47843
47949
|
onSetManualOffset(element, {
|
|
47844
47950
|
x: axis === "x" ? parsed : current.x,
|
|
@@ -48988,6 +49094,13 @@ function inspectDrainFailures(results) {
|
|
|
48988
49094
|
}
|
|
48989
49095
|
return { firstFailure };
|
|
48990
49096
|
}
|
|
49097
|
+
function focusedField() {
|
|
49098
|
+
const active = document.activeElement;
|
|
49099
|
+
return active instanceof HTMLElement && active.matches('input, textarea, select, [contenteditable="true"], [role="textbox"]') ? active : null;
|
|
49100
|
+
}
|
|
49101
|
+
function hasStudioPendingEdits() {
|
|
49102
|
+
return pendingEditPromises.size > 0 || focusedField() !== null;
|
|
49103
|
+
}
|
|
48991
49104
|
function trackStudioPendingEdit(result) {
|
|
48992
49105
|
if (!result) return void 0;
|
|
48993
49106
|
const promise = Promise.resolve(result);
|
|
@@ -48999,8 +49112,8 @@ function trackStudioPendingEdit(result) {
|
|
|
48999
49112
|
return promise;
|
|
49000
49113
|
}
|
|
49001
49114
|
async function flushStudioPendingEdits() {
|
|
49002
|
-
const active =
|
|
49003
|
-
if (active
|
|
49115
|
+
const active = focusedField();
|
|
49116
|
+
if (active) {
|
|
49004
49117
|
active.blur();
|
|
49005
49118
|
await Promise.resolve();
|
|
49006
49119
|
await waitForPostBlurEffects();
|
|
@@ -64921,7 +65034,7 @@ var PropertyPanel = memo36(function PropertyPanel2(props) {
|
|
|
64921
65034
|
const sourceLabel = element.id ? `#${element.id}` : element.selector ?? "";
|
|
64922
65035
|
const sections = resolveEditingSections(domEditSelectionToFacts(element, gsapAnimations.length));
|
|
64923
65036
|
const showEditableSections = element.capabilities.canEditStyles && sections.style;
|
|
64924
|
-
const manualOffset =
|
|
65037
|
+
const manualOffset = readMoveOffset(element.element);
|
|
64925
65038
|
const manualSize = readStudioBoxSize(element.element);
|
|
64926
65039
|
const resolvedWidth = manualSize.width > 0 ? manualSize.width : parsePxMetricValue(styles.width ?? "") ?? element.boundingBox.width;
|
|
64927
65040
|
const resolvedHeight = manualSize.height > 0 ? manualSize.height : parsePxMetricValue(styles.height ?? "") ?? element.boundingBox.height;
|
|
@@ -68027,6 +68140,60 @@ function useCaptionSync(projectId) {
|
|
|
68027
68140
|
// src/hooks/usePersistentEditHistory.ts
|
|
68028
68141
|
import { useCallback as useCallback97, useEffect as useEffect105, useMemo as useMemo65, useRef as useRef124, useState as useState104 } from "react";
|
|
68029
68142
|
var EMPTY3 = { entries: [], back: null, forward: null };
|
|
68143
|
+
var OWN_ENTRIES_KEPT = 100;
|
|
68144
|
+
function createOwnHistory() {
|
|
68145
|
+
const own = /* @__PURE__ */ new Map();
|
|
68146
|
+
let next = null;
|
|
68147
|
+
let changes = 0;
|
|
68148
|
+
const remember = (id, files) => {
|
|
68149
|
+
const known = own.get(id) ?? {};
|
|
68150
|
+
for (const [path, { before, after }] of Object.entries(files)) {
|
|
68151
|
+
known[path] = { before: known[path]?.before ?? before, after };
|
|
68152
|
+
}
|
|
68153
|
+
own.delete(id);
|
|
68154
|
+
own.set(id, known);
|
|
68155
|
+
if (own.size > OWN_ENTRIES_KEPT) own.delete(own.keys().next().value);
|
|
68156
|
+
};
|
|
68157
|
+
return {
|
|
68158
|
+
remember,
|
|
68159
|
+
overtake: () => {
|
|
68160
|
+
changes += 1;
|
|
68161
|
+
next = null;
|
|
68162
|
+
return changes;
|
|
68163
|
+
},
|
|
68164
|
+
offered: (seen, view) => {
|
|
68165
|
+
if (seen === changes) next = { undo: view.back, redo: view.forward };
|
|
68166
|
+
},
|
|
68167
|
+
changes: () => changes,
|
|
68168
|
+
stepped: (entry2) => {
|
|
68169
|
+
const undone = entry2.undoes ? own.get(entry2.undoes) : void 0;
|
|
68170
|
+
if (!undone) return;
|
|
68171
|
+
const swapped = Object.entries(undone).map(([path, f]) => [
|
|
68172
|
+
path,
|
|
68173
|
+
{ before: f.after, after: f.before }
|
|
68174
|
+
]);
|
|
68175
|
+
remember(entry2.id, Object.fromEntries(swapped));
|
|
68176
|
+
},
|
|
68177
|
+
afterOf: (id) => Object.fromEntries(Object.entries(id && own.get(id) || {}).map(([p, f]) => [p, f.after])),
|
|
68178
|
+
predict: (direction) => {
|
|
68179
|
+
const step = next?.[direction];
|
|
68180
|
+
const files = step ? own.get(step.id) : void 0;
|
|
68181
|
+
const paths = files ? Object.keys(files) : [];
|
|
68182
|
+
if (!files || paths.length !== step.paths.length || !paths.every((p) => step.paths.includes(p)))
|
|
68183
|
+
return null;
|
|
68184
|
+
const restore = paths.map((path) => [
|
|
68185
|
+
path,
|
|
68186
|
+
{ previous: files[path].after, restored: files[path].before }
|
|
68187
|
+
]);
|
|
68188
|
+
return { id: step.id, files: Object.fromEntries(restore) };
|
|
68189
|
+
},
|
|
68190
|
+
clear: () => {
|
|
68191
|
+
own.clear();
|
|
68192
|
+
next = null;
|
|
68193
|
+
changes += 1;
|
|
68194
|
+
}
|
|
68195
|
+
};
|
|
68196
|
+
}
|
|
68030
68197
|
var DEFAULT_COALESCE_MS = 300;
|
|
68031
68198
|
function historyUrl(projectId, path = "") {
|
|
68032
68199
|
return `/api/projects/${encodeURIComponent(projectId)}/history${path}`;
|
|
@@ -68045,10 +68212,11 @@ async function post(url, body, headers = {}) {
|
|
|
68045
68212
|
return { ok: false, status: response.status, error: reply?.error ?? `HTTP ${response.status}` };
|
|
68046
68213
|
}
|
|
68047
68214
|
function claimHeld(reply, label) {
|
|
68048
|
-
if (reply.ok)
|
|
68215
|
+
if (reply.ok)
|
|
68216
|
+
return reply.body?.claimed?.id ?? null;
|
|
68049
68217
|
if (reply.status !== 404)
|
|
68050
68218
|
console.error(`"${label}" was not recorded as your edit: ${reply.error}`);
|
|
68051
|
-
return
|
|
68219
|
+
return null;
|
|
68052
68220
|
}
|
|
68053
68221
|
async function overwroteVersions(files) {
|
|
68054
68222
|
const pairs = await Promise.all(
|
|
@@ -68086,38 +68254,47 @@ function usePersistentEditHistory({ projectId }) {
|
|
|
68086
68254
|
const heldClaimRef = useRef124(null);
|
|
68087
68255
|
const projectIdRef = useRef124(projectId);
|
|
68088
68256
|
projectIdRef.current = projectId;
|
|
68257
|
+
const [own] = useState104(createOwnHistory);
|
|
68089
68258
|
const refresh = useCallback97(async () => {
|
|
68090
68259
|
if (!projectId) return;
|
|
68260
|
+
const seen = own.changes();
|
|
68091
68261
|
const response = await fetch(historyUrl(projectId)).catch(() => null);
|
|
68092
68262
|
const next = response?.ok ? await response.json() : EMPTY3;
|
|
68093
|
-
if (projectIdRef.current
|
|
68094
|
-
|
|
68263
|
+
if (projectIdRef.current !== projectId) return;
|
|
68264
|
+
setView(next);
|
|
68265
|
+
own.offered(seen, next);
|
|
68266
|
+
}, [projectId, own]);
|
|
68095
68267
|
useEffect105(() => {
|
|
68096
68268
|
setView(EMPTY3);
|
|
68097
68269
|
setLoaded(false);
|
|
68098
68270
|
heldClaimRef.current = null;
|
|
68271
|
+
own.clear();
|
|
68099
68272
|
void refresh().finally(() => setLoaded(true));
|
|
68100
|
-
}, [refresh]);
|
|
68273
|
+
}, [refresh, own]);
|
|
68101
68274
|
const recordEdit = useCallback97(
|
|
68102
68275
|
async ({ label, coalesceKey, coalesceMs, files }) => {
|
|
68103
68276
|
if (!projectId) return;
|
|
68104
68277
|
const paths = Object.keys(files);
|
|
68278
|
+
own.overtake();
|
|
68105
68279
|
const reply = await post(historyUrl(projectId, "/claim"), {
|
|
68106
68280
|
label,
|
|
68107
68281
|
paths,
|
|
68108
68282
|
overwrote: await overwroteVersions(files),
|
|
68109
68283
|
...coalesceKey && { coalesceKey, idleMs: coalesceMs ?? DEFAULT_COALESCE_MS }
|
|
68110
68284
|
});
|
|
68111
|
-
|
|
68285
|
+
const claimed = claimHeld(reply, label);
|
|
68286
|
+
if (claimed) own.remember(claimed, files);
|
|
68287
|
+
heldClaimRef.current = claimed && coalesceKey ? { paths, at: Date.now() } : null;
|
|
68112
68288
|
void refresh();
|
|
68113
68289
|
},
|
|
68114
|
-
[projectId, refresh]
|
|
68290
|
+
[projectId, refresh, own]
|
|
68115
68291
|
);
|
|
68116
68292
|
const step = useCallback97(
|
|
68117
68293
|
async (direction, callbacks) => {
|
|
68118
68294
|
if (!projectId) return { ok: false, reason: "empty" };
|
|
68119
68295
|
const next = direction === "undo" ? view.back : view.forward;
|
|
68120
68296
|
const paths = [.../* @__PURE__ */ new Set([...next?.paths ?? [], ...heldClaimRef.current?.paths ?? []])];
|
|
68297
|
+
own.overtake();
|
|
68121
68298
|
const run = async () => {
|
|
68122
68299
|
const previous = await readAll(paths, callbacks.readFile);
|
|
68123
68300
|
const posted = await post(
|
|
@@ -68135,18 +68312,28 @@ function usePersistentEditHistory({ projectId }) {
|
|
|
68135
68312
|
return { ok: false, reason: "content-mismatch", paths: files };
|
|
68136
68313
|
}
|
|
68137
68314
|
if (!reply.entry) return { ok: false, reason: "empty" };
|
|
68315
|
+
own.stepped(reply.entry);
|
|
68138
68316
|
const changed = reply.entry.files.map((file) => file.path);
|
|
68139
68317
|
return {
|
|
68140
68318
|
ok: true,
|
|
68141
68319
|
label: reply.entry.label,
|
|
68320
|
+
undoes: reply.entry.undoes,
|
|
68142
68321
|
paths: changed,
|
|
68143
|
-
files: await restoredFiles(
|
|
68322
|
+
files: await restoredFiles(
|
|
68323
|
+
changed,
|
|
68324
|
+
{ ...own.afterOf(reply.entry.undoes), ...previous },
|
|
68325
|
+
callbacks.readFile
|
|
68326
|
+
)
|
|
68144
68327
|
};
|
|
68145
68328
|
};
|
|
68146
68329
|
return callbacks.serialize ? callbacks.serialize(paths, run) : run();
|
|
68147
68330
|
},
|
|
68148
|
-
[projectId, view, refresh]
|
|
68331
|
+
[projectId, view, refresh, own]
|
|
68149
68332
|
);
|
|
68333
|
+
const noteOutsideChange = useCallback97(() => {
|
|
68334
|
+
own.overtake();
|
|
68335
|
+
void refresh();
|
|
68336
|
+
}, [own, refresh]);
|
|
68150
68337
|
const undo = useCallback97((callbacks) => step("undo", callbacks), [step]);
|
|
68151
68338
|
const redo = useCallback97((callbacks) => step("redo", callbacks), [step]);
|
|
68152
68339
|
const state = useMemo65(() => {
|
|
@@ -68168,7 +68355,9 @@ function usePersistentEditHistory({ projectId }) {
|
|
|
68168
68355
|
state,
|
|
68169
68356
|
recordEdit,
|
|
68170
68357
|
undo,
|
|
68171
|
-
redo
|
|
68358
|
+
redo,
|
|
68359
|
+
predict: own.predict,
|
|
68360
|
+
noteOutsideChange
|
|
68172
68361
|
};
|
|
68173
68362
|
}
|
|
68174
68363
|
|
|
@@ -68934,6 +69123,7 @@ function useFileManager({
|
|
|
68934
69123
|
import { useCallback as useCallback103, useRef as useRef129, useState as useState107 } from "react";
|
|
68935
69124
|
|
|
68936
69125
|
// src/utils/gsapUndoRestore.ts
|
|
69126
|
+
import { isCompositionTemplate as isCompositionTemplate2 } from "@hyperframes/parsers/hf-ids";
|
|
68937
69127
|
function elementIdentityKey(el) {
|
|
68938
69128
|
const hfId = el.getAttribute("data-hf-id");
|
|
68939
69129
|
if (hfId) return `hf:${hfId}`;
|
|
@@ -68942,9 +69132,9 @@ function elementIdentityKey(el) {
|
|
|
68942
69132
|
return null;
|
|
68943
69133
|
}
|
|
68944
69134
|
var IDENTITY_SELECTOR = "[id], [data-hf-id]";
|
|
68945
|
-
function identityElementMap(
|
|
69135
|
+
function identityElementMap(root) {
|
|
68946
69136
|
const map = /* @__PURE__ */ new Map();
|
|
68947
|
-
for (const el of
|
|
69137
|
+
for (const el of root.querySelectorAll(IDENTITY_SELECTOR)) {
|
|
68948
69138
|
const key2 = elementIdentityKey(el);
|
|
68949
69139
|
if (!key2) continue;
|
|
68950
69140
|
if (map.has(key2)) return null;
|
|
@@ -68952,8 +69142,18 @@ function identityElementMap(doc) {
|
|
|
68952
69142
|
}
|
|
68953
69143
|
return map;
|
|
68954
69144
|
}
|
|
68955
|
-
function
|
|
68956
|
-
|
|
69145
|
+
function parseRestoreSource(html2) {
|
|
69146
|
+
const doc = new DOMParser().parseFromString(html2, "text/html");
|
|
69147
|
+
for (const template of Array.from(doc.querySelectorAll("template")).filter(
|
|
69148
|
+
isCompositionTemplate2
|
|
69149
|
+
)) {
|
|
69150
|
+
template.replaceWith(template.content);
|
|
69151
|
+
}
|
|
69152
|
+
return doc;
|
|
69153
|
+
}
|
|
69154
|
+
function normalizeSoftResidual(root) {
|
|
69155
|
+
const self = root.matches(IDENTITY_SELECTOR) ? [root] : [];
|
|
69156
|
+
for (const el of [...self, ...root.querySelectorAll(IDENTITY_SELECTOR)]) {
|
|
68957
69157
|
const id = el.getAttribute("id");
|
|
68958
69158
|
const hfId = el.getAttribute("data-hf-id");
|
|
68959
69159
|
for (const name of [...el.getAttributeNames()]) {
|
|
@@ -68962,7 +69162,7 @@ function normalizeSoftResidual(doc) {
|
|
|
68962
69162
|
if (id) el.setAttribute("id", id);
|
|
68963
69163
|
if (hfId) el.setAttribute("data-hf-id", hfId);
|
|
68964
69164
|
}
|
|
68965
|
-
for (const script of findGsapScriptElements(
|
|
69165
|
+
for (const script of findGsapScriptElements(root)) script.textContent = "";
|
|
68966
69166
|
}
|
|
68967
69167
|
function attributesEqual(a, b) {
|
|
68968
69168
|
const aNames = a.getAttributeNames();
|
|
@@ -68972,15 +69172,7 @@ function attributesEqual(a, b) {
|
|
|
68972
69172
|
}
|
|
68973
69173
|
return true;
|
|
68974
69174
|
}
|
|
68975
|
-
function
|
|
68976
|
-
let prevDoc;
|
|
68977
|
-
let nextDoc;
|
|
68978
|
-
try {
|
|
68979
|
-
prevDoc = new DOMParser().parseFromString(previous, "text/html");
|
|
68980
|
-
nextDoc = new DOMParser().parseFromString(restored, "text/html");
|
|
68981
|
-
} catch {
|
|
68982
|
-
return null;
|
|
68983
|
-
}
|
|
69175
|
+
function diffRestoreDocs(prevDoc, nextDoc) {
|
|
68984
69176
|
const prevByKey = identityElementMap(prevDoc);
|
|
68985
69177
|
const nextByKey = identityElementMap(nextDoc);
|
|
68986
69178
|
if (!prevByKey || !nextByKey) return null;
|
|
@@ -68991,10 +69183,12 @@ function diffSoftReloadableRestore(previous, restored) {
|
|
|
68991
69183
|
if (!prevEl || prevEl.tagName !== nextEl.tagName) return null;
|
|
68992
69184
|
if (!attributesEqual(prevEl, nextEl)) changedElementKeys.push(key2);
|
|
68993
69185
|
}
|
|
68994
|
-
|
|
68995
|
-
|
|
68996
|
-
|
|
68997
|
-
|
|
69186
|
+
const [prevRest, nextRest] = [prevDoc, nextDoc].map((doc) => {
|
|
69187
|
+
const root = doc.documentElement.cloneNode(true);
|
|
69188
|
+
normalizeSoftResidual(root);
|
|
69189
|
+
return root.outerHTML;
|
|
69190
|
+
});
|
|
69191
|
+
return prevRest === nextRest ? changedElementKeys : null;
|
|
68998
69192
|
}
|
|
68999
69193
|
function syncElementAttributes(target, source) {
|
|
69000
69194
|
for (const name of [...target.getAttributeNames()]) {
|
|
@@ -69013,16 +69207,84 @@ function syncStaleEditMarks(doc, restored) {
|
|
|
69013
69207
|
}
|
|
69014
69208
|
}
|
|
69015
69209
|
}
|
|
69016
|
-
function
|
|
69017
|
-
const doc = new DOMParser().parseFromString(html2, "text/html");
|
|
69018
|
-
return findGsapScriptElements(doc).map((script) => script.textContent ?? "");
|
|
69019
|
-
}
|
|
69020
|
-
function hasAmbiguousGsapScriptChange(previous, restored) {
|
|
69021
|
-
const previousScripts = readGsapScriptTexts(previous);
|
|
69022
|
-
const restoredScripts = readGsapScriptTexts(restored);
|
|
69210
|
+
function hasAmbiguousGsapScriptChange(previousScripts, restoredScripts) {
|
|
69023
69211
|
if (previousScripts.length <= 1 && restoredScripts.length <= 1) return false;
|
|
69024
69212
|
return previousScripts.length !== restoredScripts.length || previousScripts.some((script, index) => script !== restoredScripts[index]);
|
|
69025
69213
|
}
|
|
69214
|
+
function restoreScripted(prevDoc, nextDoc, isActive) {
|
|
69215
|
+
const [before, after] = [prevDoc, nextDoc].map(
|
|
69216
|
+
(doc) => findGsapScriptElements(doc).map((script) => script.textContent ?? "")
|
|
69217
|
+
);
|
|
69218
|
+
const scripted = before.length > 0 || after.length > 0;
|
|
69219
|
+
if (scripted && !isActive) return null;
|
|
69220
|
+
return hasAmbiguousGsapScriptChange(before, after) ? null : scripted;
|
|
69221
|
+
}
|
|
69222
|
+
function liveScopes(doc, path, isActive) {
|
|
69223
|
+
if (isActive) return [doc];
|
|
69224
|
+
return Array.from(doc.querySelectorAll(`[data-composition-file="${CSS.escape(path)}"]`));
|
|
69225
|
+
}
|
|
69226
|
+
function scopeTargets(scope, keys, restoredByKey) {
|
|
69227
|
+
const liveByKey = identityElementMap(scope);
|
|
69228
|
+
if (!liveByKey) return null;
|
|
69229
|
+
const targets = [];
|
|
69230
|
+
for (const key2 of keys) {
|
|
69231
|
+
const live = liveByKey.get(key2);
|
|
69232
|
+
const restored = restoredByKey.get(key2);
|
|
69233
|
+
if (!live || !restored || live.hasAttribute("data-hf-inner-root")) return null;
|
|
69234
|
+
targets.push({ live, restored });
|
|
69235
|
+
}
|
|
69236
|
+
return targets;
|
|
69237
|
+
}
|
|
69238
|
+
function fileTargets(doc, path, isActive, file) {
|
|
69239
|
+
const prevDoc = parseRestoreSource(file.previous);
|
|
69240
|
+
const nextDoc = parseRestoreSource(file.restored);
|
|
69241
|
+
const scripted = restoreScripted(prevDoc, nextDoc, isActive);
|
|
69242
|
+
if (scripted === null) return null;
|
|
69243
|
+
const keys = diffRestoreDocs(prevDoc, nextDoc);
|
|
69244
|
+
const restoredByKey = identityElementMap(nextDoc);
|
|
69245
|
+
if (!keys || !restoredByKey) return null;
|
|
69246
|
+
const found = liveScopes(doc, path, isActive).map(
|
|
69247
|
+
(scope) => scopeTargets(scope, keys, restoredByKey)
|
|
69248
|
+
);
|
|
69249
|
+
if (!found.length || found.includes(null)) return null;
|
|
69250
|
+
return { targets: found.flat(), scripted };
|
|
69251
|
+
}
|
|
69252
|
+
function planRestoreTargets(doc, activeDocPath, files) {
|
|
69253
|
+
const plan = { targets: [], scripted: false };
|
|
69254
|
+
for (const [path, file] of Object.entries(files)) {
|
|
69255
|
+
const found = fileTargets(doc, path, path === activeDocPath, file);
|
|
69256
|
+
if (!found) return null;
|
|
69257
|
+
plan.targets.push(...found.targets);
|
|
69258
|
+
plan.scripted ||= found.scripted;
|
|
69259
|
+
}
|
|
69260
|
+
return plan;
|
|
69261
|
+
}
|
|
69262
|
+
function finalizeInPlace(iframe, win, currentTime) {
|
|
69263
|
+
if (applySoftReloadFinalization(iframe, currentTime)) return true;
|
|
69264
|
+
try {
|
|
69265
|
+
win.__player?.seek?.(currentTime);
|
|
69266
|
+
win.__hfStudioManualEditsApply?.();
|
|
69267
|
+
return true;
|
|
69268
|
+
} catch {
|
|
69269
|
+
return false;
|
|
69270
|
+
}
|
|
69271
|
+
}
|
|
69272
|
+
function showRestoreInPlace(iframe, activeCompPath, files, currentTime) {
|
|
69273
|
+
const doc = iframe?.contentDocument;
|
|
69274
|
+
const win = iframe?.contentWindow;
|
|
69275
|
+
const plan = doc && win ? planRestoreTargets(doc, activeCompPath ?? "index.html", files) : null;
|
|
69276
|
+
if (!iframe || !win || !plan || plan.scripted) return null;
|
|
69277
|
+
const before = plan.targets.map(({ live }) => [live, live.cloneNode(false)]);
|
|
69278
|
+
for (const { live, restored } of plan.targets) syncElementAttributes(live, restored);
|
|
69279
|
+
const putBack = (time) => {
|
|
69280
|
+
if (iframe.contentDocument !== doc) return false;
|
|
69281
|
+
for (const [live, attributes] of before) syncElementAttributes(live, attributes);
|
|
69282
|
+
return finalizeInPlace(iframe, win, time);
|
|
69283
|
+
};
|
|
69284
|
+
if (finalizeInPlace(iframe, win, currentTime)) return putBack;
|
|
69285
|
+
putBack(currentTime);
|
|
69286
|
+
return null;
|
|
69287
|
+
}
|
|
69026
69288
|
function applyUndoRestoreToPreview(iframe, activeCompPath, files, currentTime, reload) {
|
|
69027
69289
|
const activeDocPath = activeCompPath ?? "index.html";
|
|
69028
69290
|
const paths = files ? Object.keys(files) : [];
|
|
@@ -69030,51 +69292,23 @@ function applyUndoRestoreToPreview(iframe, activeCompPath, files, currentTime, r
|
|
|
69030
69292
|
markScenesStale(iframe, paths);
|
|
69031
69293
|
reload();
|
|
69032
69294
|
};
|
|
69033
|
-
|
|
69034
|
-
|
|
69035
|
-
|
|
69036
|
-
|
|
69037
|
-
const doc = iframe.contentDocument;
|
|
69038
|
-
const win = iframe.contentWindow;
|
|
69039
|
-
if (!doc || !win) {
|
|
69040
|
-
reloadPreview();
|
|
69041
|
-
return "full";
|
|
69042
|
-
}
|
|
69043
|
-
const { previous, restored } = files[activeDocPath];
|
|
69044
|
-
const diff = diffSoftReloadableRestore(previous, restored);
|
|
69045
|
-
if (!diff) {
|
|
69046
|
-
reloadPreview();
|
|
69047
|
-
return "full";
|
|
69048
|
-
}
|
|
69049
|
-
if (hasAmbiguousGsapScriptChange(previous, restored)) {
|
|
69050
|
-
reloadPreview();
|
|
69051
|
-
return "full";
|
|
69052
|
-
}
|
|
69053
|
-
const liveByKey = identityElementMap(doc);
|
|
69054
|
-
const restoredByKey = identityElementMap(new DOMParser().parseFromString(restored, "text/html"));
|
|
69055
|
-
if (!liveByKey || !restoredByKey) {
|
|
69295
|
+
const doc = iframe?.contentDocument;
|
|
69296
|
+
const win = iframe?.contentWindow;
|
|
69297
|
+
const plan = doc && files && win ? planRestoreTargets(doc, activeDocPath, files) : null;
|
|
69298
|
+
if (!iframe || !doc || !files || !win || !plan) {
|
|
69056
69299
|
reloadPreview();
|
|
69057
69300
|
return "full";
|
|
69058
69301
|
}
|
|
69059
|
-
const
|
|
69060
|
-
|
|
69061
|
-
|
|
69062
|
-
|
|
69063
|
-
if (!liveEl || !restoredEl) {
|
|
69064
|
-
reloadPreview();
|
|
69065
|
-
return "full";
|
|
69066
|
-
}
|
|
69067
|
-
changedTargets.push({ live: liveEl, restored: restoredEl });
|
|
69068
|
-
}
|
|
69069
|
-
for (const target of changedTargets) syncElementAttributes(target.live, target.restored);
|
|
69070
|
-
const restoredScript = extractGsapScriptText(restored);
|
|
69071
|
-
const previousScript = extractGsapScriptText(previous);
|
|
69302
|
+
for (const target of plan.targets) syncElementAttributes(target.live, target.restored);
|
|
69303
|
+
const active = files[activeDocPath];
|
|
69304
|
+
const restoredScript = active ? extractGsapScriptText(active.restored) : null;
|
|
69305
|
+
const previousScript = active ? extractGsapScriptText(active.previous) : null;
|
|
69072
69306
|
if (restoredScript && restoredScript !== previousScript) {
|
|
69073
|
-
syncStaleEditMarks(doc, restored);
|
|
69307
|
+
syncStaleEditMarks(doc, active.restored);
|
|
69074
69308
|
const result = applySoftReload(iframe, restoredScript, {
|
|
69075
69309
|
onAsyncFailure: reloadPreview,
|
|
69076
69310
|
currentTimeOverride: currentTime,
|
|
69077
|
-
authoredHtml: restored
|
|
69311
|
+
authoredHtml: active.restored
|
|
69078
69312
|
});
|
|
69079
69313
|
if (result === "cannot-soft-reload") {
|
|
69080
69314
|
reloadPreview();
|
|
@@ -69082,15 +69316,9 @@ function applyUndoRestoreToPreview(iframe, activeCompPath, files, currentTime, r
|
|
|
69082
69316
|
}
|
|
69083
69317
|
return "soft";
|
|
69084
69318
|
}
|
|
69085
|
-
if (
|
|
69086
|
-
|
|
69087
|
-
|
|
69088
|
-
win.__hfStudioManualEditsApply?.();
|
|
69089
|
-
} catch {
|
|
69090
|
-
reloadPreview();
|
|
69091
|
-
return "full";
|
|
69092
|
-
}
|
|
69093
|
-
return "soft";
|
|
69319
|
+
if (finalizeInPlace(iframe, win, currentTime)) return "soft";
|
|
69320
|
+
reloadPreview();
|
|
69321
|
+
return "full";
|
|
69094
69322
|
}
|
|
69095
69323
|
|
|
69096
69324
|
// src/player/lib/automationStoreSync.ts
|
|
@@ -69274,6 +69502,20 @@ function usePreviewPersistence({
|
|
|
69274
69502
|
},
|
|
69275
69503
|
[previewIframeRef, activeCompPathRef, reloadPreview]
|
|
69276
69504
|
);
|
|
69505
|
+
const showHistoryRestoreNow = useCallback103(
|
|
69506
|
+
(files) => {
|
|
69507
|
+
if (!domEditSaveQueueRef.current?.isIdle() || hasStudioPendingEdits()) return null;
|
|
69508
|
+
const iframe = previewIframeRef.current;
|
|
69509
|
+
const now2 = () => usePlayerStore.getState().currentTime;
|
|
69510
|
+
const putBack = showRestoreInPlace(iframe, activeCompPathRef.current, files, now2());
|
|
69511
|
+
if (!putBack) return null;
|
|
69512
|
+
return () => {
|
|
69513
|
+
if (putBack(now2())) syncStoredAutomationFromPreview(iframe?.contentDocument ?? null);
|
|
69514
|
+
else void syncHistoryPreviewAfterApply({ paths: Object.keys(files) });
|
|
69515
|
+
};
|
|
69516
|
+
},
|
|
69517
|
+
[previewIframeRef, activeCompPathRef, syncHistoryPreviewAfterApply]
|
|
69518
|
+
);
|
|
69277
69519
|
useMountEffect(() => {
|
|
69278
69520
|
void clearLegacyStudioMotionFile(_readOptionalProjectFile, _writeProjectFile);
|
|
69279
69521
|
});
|
|
@@ -69288,7 +69530,8 @@ function usePreviewPersistence({
|
|
|
69288
69530
|
resetDomEditSaveQueueBreaker,
|
|
69289
69531
|
applyCurrentStudioManualEditsToPreview,
|
|
69290
69532
|
applyStudioManualEditsToPreview,
|
|
69291
|
-
syncHistoryPreviewAfterApply
|
|
69533
|
+
syncHistoryPreviewAfterApply,
|
|
69534
|
+
showHistoryRestoreNow
|
|
69292
69535
|
};
|
|
69293
69536
|
}
|
|
69294
69537
|
|
|
@@ -73716,10 +73959,6 @@ function canCommitInlineTextSelection(selection, element) {
|
|
|
73716
73959
|
if (selection.isCompositionHost || selection.isInsideLockedComposition) return false;
|
|
73717
73960
|
return canEditElementTextInline(element);
|
|
73718
73961
|
}
|
|
73719
|
-
function ownsCurrentPreviewElement(selection, element, document2) {
|
|
73720
|
-
if (!document2 || !element.isConnected) return false;
|
|
73721
|
-
return element === selection.element && element.ownerDocument === document2;
|
|
73722
|
-
}
|
|
73723
73962
|
async function resyncDomTextSelectionFromPreview(doc, selection, activeCompPath, buildDomSelectionFromTarget, applyDomSelection) {
|
|
73724
73963
|
if (!doc) return;
|
|
73725
73964
|
const refreshed = findElementForSelection(doc, selection, activeCompPath);
|
|
@@ -73742,6 +73981,8 @@ function useDomEditTextCommits({
|
|
|
73742
73981
|
}) {
|
|
73743
73982
|
const latestReadOnlyPreviewRef = useRef142(readOnlyPreview);
|
|
73744
73983
|
latestReadOnlyPreviewRef.current = readOnlyPreview;
|
|
73984
|
+
const latestSelectionRef = useRef142(domEditSelection);
|
|
73985
|
+
latestSelectionRef.current = domEditSelection;
|
|
73745
73986
|
const domTextCommitVersionRef = useRef142(/* @__PURE__ */ new Map());
|
|
73746
73987
|
const domStyleCommitVersionRef = useRef142(/* @__PURE__ */ new Map());
|
|
73747
73988
|
const {
|
|
@@ -73853,18 +74094,32 @@ function useDomEditTextCommits({
|
|
|
73853
74094
|
);
|
|
73854
74095
|
const handleDomRichTextCommit = useCallback123(
|
|
73855
74096
|
async ({ element, html: html2, previousHtml }) => {
|
|
73856
|
-
|
|
73857
|
-
if (latestReadOnlyPreviewRef.current) {
|
|
74097
|
+
const putBack = () => {
|
|
73858
74098
|
if (element.isConnected && element.innerHTML === html2) element.innerHTML = previousHtml;
|
|
73859
|
-
|
|
74099
|
+
};
|
|
74100
|
+
if (latestReadOnlyPreviewRef.current) return putBack();
|
|
74101
|
+
const refuse = (reason) => {
|
|
74102
|
+
console.error("[Studio] text edit not saved:", reason, element);
|
|
74103
|
+
showToast(`Couldn't save the text edit: ${reason}`, "error");
|
|
74104
|
+
putBack();
|
|
74105
|
+
};
|
|
74106
|
+
const doc = previewIframeRef.current?.contentDocument;
|
|
74107
|
+
if (!doc || !element.isConnected || element.ownerDocument !== doc) {
|
|
74108
|
+
return refuse("the text's element is gone from the preview");
|
|
74109
|
+
}
|
|
74110
|
+
const selection = await buildDomSelectionFromTarget(element, {
|
|
74111
|
+
exactTarget: true,
|
|
74112
|
+
skipSourceProbe: true
|
|
74113
|
+
});
|
|
74114
|
+
if (selection?.element !== element) {
|
|
74115
|
+
return refuse("this text was not found in the composition's source");
|
|
74116
|
+
}
|
|
74117
|
+
if (!canCommitInlineTextSelection(selection, element)) {
|
|
74118
|
+
return refuse("this text can't be edited in place");
|
|
73860
74119
|
}
|
|
73861
|
-
if (!canCommitInlineTextSelection(domEditSelection, element)) return;
|
|
73862
|
-
const iframe = previewIframeRef.current;
|
|
73863
|
-
const doc = iframe?.contentDocument;
|
|
73864
|
-
if (!ownsCurrentPreviewElement(domEditSelection, element, doc)) return;
|
|
73865
74120
|
const isLatestTextCommit = bumpDomEditCommitMapVersion(
|
|
73866
74121
|
domTextCommitVersionRef.current,
|
|
73867
|
-
getDomEditTargetKey(
|
|
74122
|
+
getDomEditTargetKey(selection)
|
|
73868
74123
|
);
|
|
73869
74124
|
const operations = [buildDomEditRichTextPatchOperation(html2)];
|
|
73870
74125
|
let appliedHtml = "";
|
|
@@ -73876,7 +74131,7 @@ function useDomEditTextCommits({
|
|
|
73876
74131
|
appliedHtml = element.innerHTML;
|
|
73877
74132
|
},
|
|
73878
74133
|
persist: async () => {
|
|
73879
|
-
await persistDomEditOperations(
|
|
74134
|
+
await persistDomEditOperations(selection, operations, {
|
|
73880
74135
|
label: "Edit text",
|
|
73881
74136
|
skipRefresh: true,
|
|
73882
74137
|
shouldSave: isLatestTextCommit
|
|
@@ -73888,11 +74143,12 @@ function useDomEditTextCommits({
|
|
|
73888
74143
|
element.innerHTML = previousHtml;
|
|
73889
74144
|
}
|
|
73890
74145
|
},
|
|
73891
|
-
onError: (error) => reportDomEditPersistFailure(
|
|
73892
|
-
|
|
74146
|
+
onError: (error) => reportDomEditPersistFailure(selection, operations, error, showToast),
|
|
74147
|
+
// Re-select only what is still selected: the selection may have moved on, or a host cleared it.
|
|
74148
|
+
shouldResync: () => isLatestTextCommit() && latestSelectionRef.current?.element === element,
|
|
73893
74149
|
resync: () => resyncDomTextSelectionFromPreview(
|
|
73894
74150
|
doc,
|
|
73895
|
-
|
|
74151
|
+
selection,
|
|
73896
74152
|
activeCompPath,
|
|
73897
74153
|
buildDomSelectionFromTarget,
|
|
73898
74154
|
applyDomSelection
|
|
@@ -73904,7 +74160,6 @@ function useDomEditTextCommits({
|
|
|
73904
74160
|
activeCompPath,
|
|
73905
74161
|
applyDomSelection,
|
|
73906
74162
|
buildDomSelectionFromTarget,
|
|
73907
|
-
domEditSelection,
|
|
73908
74163
|
persistDomEditOperations,
|
|
73909
74164
|
previewIframeRef,
|
|
73910
74165
|
showToast
|
|
@@ -74122,9 +74377,29 @@ function settleGsapDraftAtGestureStart(el) {
|
|
|
74122
74377
|
const y = Number.parseFloat(el.getAttribute("data-hf-drag-gsap-base-y") ?? "");
|
|
74123
74378
|
if (gsap3 && Number.isFinite(x) && Number.isFinite(y)) gsap3.set(el, { x, y });
|
|
74124
74379
|
}
|
|
74380
|
+
var plainMoveCounter = 0;
|
|
74381
|
+
function stagePlainTranslate(commitPositionPatchToHtml, selection, next, coalesceKey) {
|
|
74382
|
+
const style = selection.element.style;
|
|
74383
|
+
const before = style.getPropertyValue("translate");
|
|
74384
|
+
const patch = translatePatch(next);
|
|
74385
|
+
style.setProperty("translate", patch.value);
|
|
74386
|
+
const rollback = () => {
|
|
74387
|
+
if (style.getPropertyValue("translate") === patch.value) style.setProperty("translate", before);
|
|
74388
|
+
};
|
|
74389
|
+
const key2 = coalesceKey ?? `move:${++plainMoveCounter}`;
|
|
74390
|
+
const options = { label: "Move layer", coalesceKey: key2, coalesceMs: Number.POSITIVE_INFINITY };
|
|
74391
|
+
const save = () => commitPositionPatchToHtml(selection, [patch], options).catch((error) => {
|
|
74392
|
+
rollback();
|
|
74393
|
+
throw error;
|
|
74394
|
+
});
|
|
74395
|
+
return { save, rollback };
|
|
74396
|
+
}
|
|
74125
74397
|
function stageElementOffset({ commitPositionPatchToHtml, showToast, readOnlyPreview }, selection, next, coalesceKey) {
|
|
74126
74398
|
const el = selection.element;
|
|
74127
74399
|
if (readOnlyPreview) return { save: () => Promise.resolve(), rollback: () => void 0 };
|
|
74400
|
+
if (!gsapWritesPosition(el)) {
|
|
74401
|
+
return stagePlainTranslate(commitPositionPatchToHtml, selection, next, coalesceKey);
|
|
74402
|
+
}
|
|
74128
74403
|
const previous = { position: el.style.position, left: el.style.left, top: el.style.top };
|
|
74129
74404
|
const liftMarker = el.getAttribute(LAYER_REVEAL_PRIOR_POSITION_ATTR);
|
|
74130
74405
|
const result = applyElementPositionOffset(el, next);
|
|
@@ -77602,6 +77877,8 @@ function useGsapAwareEditing({
|
|
|
77602
77877
|
);
|
|
77603
77878
|
const handleGsapAwarePathOffsetCommit = useCallback138(
|
|
77604
77879
|
async (selection, next, modifiers) => {
|
|
77880
|
+
if (!gsapWritesPosition(selection.element))
|
|
77881
|
+
return stageElementPositionOffset(selection, next).save();
|
|
77605
77882
|
if (gsapCommitMutation) {
|
|
77606
77883
|
try {
|
|
77607
77884
|
const ownedAnimations = getGsapAnimationsForSelection(selection);
|
|
@@ -77667,6 +77944,7 @@ function useGsapAwareEditing({
|
|
|
77667
77944
|
const offsetMembers = /* @__PURE__ */ new Set();
|
|
77668
77945
|
const preflightResults = await Promise.allSettled(
|
|
77669
77946
|
updates.map(async ({ selection }) => {
|
|
77947
|
+
if (!gsapWritesPosition(selection.element)) return void offsetMembers.add(selection);
|
|
77670
77948
|
const animations = await makeFetchFallback(selection, { failOnFetchError: true })();
|
|
77671
77949
|
preflightAnimations.set(selection, animations);
|
|
77672
77950
|
const outcome = await tryGsapDragIntercept(
|
|
@@ -79519,14 +79797,18 @@ function useStudioExternalFileChanges({
|
|
|
79519
79797
|
fileManager,
|
|
79520
79798
|
previewPersistence,
|
|
79521
79799
|
pendingTimelineEditPathRef,
|
|
79522
|
-
reloadPreview
|
|
79800
|
+
reloadPreview,
|
|
79801
|
+
onOutsideChange
|
|
79523
79802
|
}) {
|
|
79524
79803
|
const { flushPendingSourceSave, discardPendingSourceSave } = fileManager;
|
|
79525
79804
|
const { drainPendingDomEditSaves, resetDomEditSaveQueueBreaker } = previewPersistence;
|
|
79526
79805
|
const bumpThumbnailRevisions = usePlayerStore((state) => state.bumpThumbnailRevisions);
|
|
79527
79806
|
const onAcceptedPersistedFileChange = useCallback143(
|
|
79528
|
-
(_path, affectedCompositions) =>
|
|
79529
|
-
|
|
79807
|
+
(_path, affectedCompositions) => {
|
|
79808
|
+
onOutsideChange?.();
|
|
79809
|
+
bumpThumbnailRevisions(affectedCompositions);
|
|
79810
|
+
},
|
|
79811
|
+
[bumpThumbnailRevisions, onOutsideChange]
|
|
79530
79812
|
);
|
|
79531
79813
|
const drainPendingChanges = useCallback143(async () => {
|
|
79532
79814
|
const source = await flushPendingSourceSave();
|
|
@@ -80093,6 +80375,7 @@ function useEditHistoryActions({
|
|
|
80093
80375
|
writeProjectFile,
|
|
80094
80376
|
showToast,
|
|
80095
80377
|
syncHistoryPreviewAfterApply,
|
|
80378
|
+
showHistoryRestoreNow,
|
|
80096
80379
|
waitForPendingDomEditSaves,
|
|
80097
80380
|
onAfterUndoRedo,
|
|
80098
80381
|
activeCompPath,
|
|
@@ -80109,11 +80392,20 @@ function useEditHistoryActions({
|
|
|
80109
80392
|
const apply = useCallback145(
|
|
80110
80393
|
async (direction) => {
|
|
80111
80394
|
const noun = direction === "undo" ? "Undo" : "Redo";
|
|
80112
|
-
|
|
80113
|
-
const
|
|
80114
|
-
|
|
80115
|
-
|
|
80116
|
-
|
|
80395
|
+
const predicted = editHistory.predict?.(direction) ?? null;
|
|
80396
|
+
const putBack = predicted ? showHistoryRestoreNow?.(predicted.files) ?? null : null;
|
|
80397
|
+
let result = { ok: false, reason: "failed" };
|
|
80398
|
+
let serverSteppedShown = false;
|
|
80399
|
+
try {
|
|
80400
|
+
await waitForPendingDomEditSaves();
|
|
80401
|
+
result = await editHistory[direction]({
|
|
80402
|
+
readFile: readHistoryFile,
|
|
80403
|
+
serialize: serializeHistoryFiles
|
|
80404
|
+
});
|
|
80405
|
+
serverSteppedShown = Boolean(putBack && result.ok && result.label) && result.undoes === predicted?.id;
|
|
80406
|
+
} finally {
|
|
80407
|
+
if (putBack && !serverSteppedShown) putBack();
|
|
80408
|
+
}
|
|
80117
80409
|
if (!result.ok && result.reason === "content-mismatch") {
|
|
80118
80410
|
showToast(
|
|
80119
80411
|
`Can't ${direction}: ${result.paths?.join(", ")} changed since that edit.`,
|
|
@@ -80126,7 +80418,8 @@ function useEditHistoryActions({
|
|
|
80126
80418
|
return;
|
|
80127
80419
|
}
|
|
80128
80420
|
if (result.ok && result.label) {
|
|
80129
|
-
const
|
|
80421
|
+
const files = serverSteppedShown ? fromShown(result.files, predicted.files) : result.files;
|
|
80422
|
+
const restore = { paths: result.paths, files };
|
|
80130
80423
|
onAfterUndoRedo?.(restore);
|
|
80131
80424
|
if (activeCompPath && result.paths?.includes(activeCompPath)) {
|
|
80132
80425
|
forceReloadSdkSession?.();
|
|
@@ -80140,6 +80433,7 @@ function useEditHistoryActions({
|
|
|
80140
80433
|
readHistoryFile,
|
|
80141
80434
|
showToast,
|
|
80142
80435
|
syncHistoryPreviewAfterApply,
|
|
80436
|
+
showHistoryRestoreNow,
|
|
80143
80437
|
waitForPendingDomEditSaves,
|
|
80144
80438
|
serializeHistoryFiles,
|
|
80145
80439
|
onAfterUndoRedo,
|
|
@@ -80151,6 +80445,15 @@ function useEditHistoryActions({
|
|
|
80151
80445
|
const redo = useCallback145(() => apply("redo"), [apply]);
|
|
80152
80446
|
return useMemo71(() => ({ undo, redo }), [undo, redo]);
|
|
80153
80447
|
}
|
|
80448
|
+
function fromShown(files, shown2) {
|
|
80449
|
+
if (!files) return files;
|
|
80450
|
+
return Object.fromEntries(
|
|
80451
|
+
Object.entries(files).map(([path, f]) => [
|
|
80452
|
+
path,
|
|
80453
|
+
{ previous: shown2[path]?.restored ?? f.previous, restored: f.restored }
|
|
80454
|
+
])
|
|
80455
|
+
);
|
|
80456
|
+
}
|
|
80154
80457
|
|
|
80155
80458
|
// src/hooks/useAppHotkeys.ts
|
|
80156
80459
|
function iframeContentWindow(iframe) {
|
|
@@ -80166,9 +80469,9 @@ function safeAddListener(t, type, h, capture = false) {
|
|
|
80166
80469
|
} catch {
|
|
80167
80470
|
}
|
|
80168
80471
|
}
|
|
80169
|
-
function safeRemoveListener(t, type, h) {
|
|
80472
|
+
function safeRemoveListener(t, type, h, capture = false) {
|
|
80170
80473
|
try {
|
|
80171
|
-
t?.removeEventListener(type, h);
|
|
80474
|
+
t?.removeEventListener(type, h, capture);
|
|
80172
80475
|
} catch {
|
|
80173
80476
|
}
|
|
80174
80477
|
}
|
|
@@ -80195,6 +80498,7 @@ function useAppHotkeys({
|
|
|
80195
80498
|
writeProjectFile,
|
|
80196
80499
|
showToast,
|
|
80197
80500
|
syncHistoryPreviewAfterApply,
|
|
80501
|
+
showHistoryRestoreNow,
|
|
80198
80502
|
waitForPendingDomEditSaves,
|
|
80199
80503
|
handleCopy: handleCopy2,
|
|
80200
80504
|
handlePaste: handlePaste2,
|
|
@@ -80218,6 +80522,7 @@ function useAppHotkeys({
|
|
|
80218
80522
|
writeProjectFile,
|
|
80219
80523
|
showToast,
|
|
80220
80524
|
syncHistoryPreviewAfterApply,
|
|
80525
|
+
showHistoryRestoreNow,
|
|
80221
80526
|
waitForPendingDomEditSaves,
|
|
80222
80527
|
onAfterUndoRedo,
|
|
80223
80528
|
activeCompPath,
|
|
@@ -80274,38 +80579,17 @@ function useAppHotkeys({
|
|
|
80274
80579
|
window.addEventListener("keydown", handleAppKeyDown, true);
|
|
80275
80580
|
return () => window.removeEventListener("keydown", handleAppKeyDown, true);
|
|
80276
80581
|
}, [handleAppKeyDown]);
|
|
80277
|
-
const handleHistoryHotkey = useCallback146((event) => {
|
|
80278
|
-
if (!(event.metaKey || event.ctrlKey) || shouldIgnoreHistoryShortcut(event.target)) return;
|
|
80279
|
-
handleUndoRedoKey(
|
|
80280
|
-
event,
|
|
80281
|
-
() => void cbRef.current.handleUndo(),
|
|
80282
|
-
() => void cbRef.current.handleRedo()
|
|
80283
|
-
);
|
|
80284
|
-
}, []);
|
|
80285
80582
|
const syncPreviewHotkeys = useCallback146(
|
|
80286
80583
|
(iframe) => {
|
|
80287
80584
|
previewHistoryCleanupRef.current?.();
|
|
80288
80585
|
previewHistoryCleanupRef.current = null;
|
|
80289
80586
|
const win = iframeContentWindow(iframe);
|
|
80290
|
-
|
|
80291
|
-
try {
|
|
80292
|
-
doc = iframe?.contentDocument ?? null;
|
|
80293
|
-
} catch {
|
|
80294
|
-
doc = null;
|
|
80295
|
-
}
|
|
80296
|
-
if (!win && !doc) return;
|
|
80297
|
-
const handler = handleHistoryHotkey;
|
|
80587
|
+
if (!win) return;
|
|
80298
80588
|
const appHandler = handleAppKeyDown;
|
|
80299
|
-
safeAddListener(win, "keydown", handler, true);
|
|
80300
80589
|
safeAddListener(win, "keydown", appHandler, true);
|
|
80301
|
-
|
|
80302
|
-
previewHistoryCleanupRef.current = () => {
|
|
80303
|
-
safeRemoveListener(win, "keydown", handler);
|
|
80304
|
-
safeRemoveListener(win, "keydown", appHandler);
|
|
80305
|
-
doc?.removeEventListener("keydown", handleHistoryHotkey, true);
|
|
80306
|
-
};
|
|
80590
|
+
previewHistoryCleanupRef.current = () => safeRemoveListener(win, "keydown", appHandler, true);
|
|
80307
80591
|
},
|
|
80308
|
-
[handleAppKeyDown
|
|
80592
|
+
[handleAppKeyDown]
|
|
80309
80593
|
);
|
|
80310
80594
|
useEffect121(
|
|
80311
80595
|
() => () => {
|
|
@@ -91691,7 +91975,8 @@ function StudioApp({ readOnlyPreview = false, readOnlyPreviewReason } = {}) {
|
|
|
91691
91975
|
fileManager,
|
|
91692
91976
|
previewPersistence,
|
|
91693
91977
|
pendingTimelineEditPathRef,
|
|
91694
|
-
reloadPreview
|
|
91978
|
+
reloadPreview,
|
|
91979
|
+
onOutsideChange: editHistory.noteOutsideChange
|
|
91695
91980
|
});
|
|
91696
91981
|
const invalidateGsapCacheRef = useRef181(() => {
|
|
91697
91982
|
});
|
|
@@ -91793,6 +92078,7 @@ function StudioApp({ readOnlyPreview = false, readOnlyPreviewReason } = {}) {
|
|
|
91793
92078
|
writeProjectFile: fileManager.writeProjectFile,
|
|
91794
92079
|
showToast,
|
|
91795
92080
|
syncHistoryPreviewAfterApply: previewPersistence.syncHistoryPreviewAfterApply,
|
|
92081
|
+
showHistoryRestoreNow: previewPersistence.showHistoryRestoreNow,
|
|
91796
92082
|
waitForPendingDomEditSaves: previewPersistence.waitForPendingDomEditSaves,
|
|
91797
92083
|
handleCopy: handleCopy2,
|
|
91798
92084
|
handlePaste: handlePaste2,
|
|
@@ -92681,6 +92967,7 @@ export {
|
|
|
92681
92967
|
ConnectedDomEditOverlay,
|
|
92682
92968
|
ContextMenu,
|
|
92683
92969
|
DEFAULT_SHORTCUT_SECTIONS,
|
|
92970
|
+
Dock,
|
|
92684
92971
|
DomEditOverlay,
|
|
92685
92972
|
DomEditProvider,
|
|
92686
92973
|
EditorShell,
|
|
@@ -92776,6 +93063,7 @@ export {
|
|
|
92776
93063
|
snapEngagedForTravel,
|
|
92777
93064
|
useClipboard,
|
|
92778
93065
|
useCompositionStack,
|
|
93066
|
+
useDockLayoutStore,
|
|
92779
93067
|
useDomEditSelectionContext,
|
|
92780
93068
|
useDomEditSession,
|
|
92781
93069
|
useDomGeometryCommit,
|