hyperframes 0.8.127 → 0.8.129
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/{add-7CE5XYK2.js → add-MFBQ4JQ3.js} +16 -16
- package/dist/{animatedGifPrep-4VAIRX7D.js → animatedGifPrep-P2T6YM7Z.js} +7 -7
- package/dist/{asset-22WQELZE.js → asset-LOZG7JHC.js} +2 -2
- package/dist/{assetCataloger-A2AITAHA.js → assetCataloger-VBLPHX24.js} +3 -3
- package/dist/{auth-33GV2LO2.js → auth-JKCXKQRS.js} +2 -2
- package/dist/{autoUpdate-OIXGTNLS.js → autoUpdate-5KHORE4C.js} +2 -2
- package/dist/{backgroundChecks-BVP36PXK.js → backgroundChecks-DTVWBC74.js} +3 -3
- package/dist/backgroundChecksWorker.js +3 -3
- package/dist/{batchRender-URT4LT44.js → batchRender-FT7AP7XH.js} +4 -4
- package/dist/{beats-U34XHTAA.js → beats-ZLLBJMPB.js} +15 -15
- package/dist/{benchmark-FNGERKUQ.js → benchmark-RU2FBIWR.js} +13 -13
- package/dist/{browser-35LEQMCO.js → browser-RQV6NSLN.js} +10 -10
- package/dist/{bundleWithLocalizedFonts-4CGUDHCA.js → bundleWithLocalizedFonts-5QOPOB5D.js} +2 -2
- package/dist/{capture-M2JJS4F2.js → capture-RLZA6EMR.js} +10 -10
- package/dist/{capture-GZKMVNKK.js → capture-SZZ4IYJK.js} +2 -2
- package/dist/{catalog-OVTLU2MC.js → catalog-LYPHWJDJ.js} +16 -16
- package/dist/{check-DBVGHGKO.js → check-R4UNOR5Z.js} +36 -36
- package/dist/{checkBrowser-2SQIUOXA.js → checkBrowser-RSUTDBXE.js} +37 -37
- package/dist/{chunk-AGNZ2QLE.js → chunk-2YN64IN5.js} +2 -2
- package/dist/{chunk-ZZAHIKFK.js → chunk-3ZP5DXE7.js} +1 -1
- package/dist/{chunk-RUYSBG3F.js → chunk-4NWOGZZS.js} +32 -30
- package/dist/{chunk-G33WTKF3.js → chunk-557OWRHF.js} +2 -2
- package/dist/{chunk-ZOUDTLLH.js → chunk-6CTIGJY7.js} +2 -2
- package/dist/{chunk-CSHKZ3SL.js → chunk-7HG4BFIN.js} +1 -1
- package/dist/{chunk-7YJ7CCKW.js → chunk-7JZ4FXP2.js} +1 -1
- package/dist/{chunk-6252EFFW.js → chunk-A2Z4Z4VL.js} +1 -1
- package/dist/{chunk-7DPBFAYT.js → chunk-AVUUN2XD.js} +33 -31
- package/dist/{chunk-HXGPETEM.js → chunk-B6YD6JAS.js} +1 -1
- package/dist/{chunk-RHBM33JF.js → chunk-BI6TVCDI.js} +7 -7
- package/dist/{chunk-F4WYYNNG.js → chunk-CFKU7HTK.js} +1 -1
- package/dist/{chunk-NDPSG5WQ.js → chunk-CWPVM2QV.js} +2 -2
- package/dist/{chunk-YSCN66NT.js → chunk-FCSIGXXP.js} +1 -1
- package/dist/{chunk-ZLJXATSQ.js → chunk-FI4I7CME.js} +1 -1
- package/dist/{chunk-KPY6JX4K.js → chunk-GO74M65A.js} +11 -11
- package/dist/{chunk-TL7Y6IBY.js → chunk-GOAUWA46.js} +3 -3
- package/dist/{chunk-6ACXAUBT.js → chunk-H64TB7NB.js} +4 -4
- package/dist/{chunk-S2WNVYLQ.js → chunk-HRUGC23L.js} +1 -1
- package/dist/chunk-HWKCIWQT.js +3578 -0
- package/dist/{chunk-MK2HBL6E.js → chunk-IUZFXWSV.js} +1 -1
- package/dist/{chunk-YII7JZ2C.js → chunk-IXCQU5RO.js} +1 -1
- package/dist/{chunk-TP4PR3SL.js → chunk-JO2J4S2L.js} +3 -3
- package/dist/{chunk-OE66T7R2.js → chunk-K2EQN4VU.js} +1 -1
- package/dist/{chunk-D4NPKZFY.js → chunk-K2FTXJYH.js} +3 -3
- package/dist/{chunk-CX7NQZDE.js → chunk-KBJUXVOJ.js} +3 -3
- package/dist/{chunk-QR7IRRUX.js → chunk-KHSCFD7K.js} +5 -5
- package/dist/{chunk-BYI2K4RN.js → chunk-LHSR3M4M.js} +2 -2
- package/dist/{chunk-PVVUMILD.js → chunk-LYNI3FOL.js} +1 -1
- package/dist/{chunk-HZHXHO4V.js → chunk-MWB3ZIFM.js} +1 -1
- package/dist/{chunk-OQN3AUOV.js → chunk-NTIV6QFJ.js} +12 -12
- package/dist/{chunk-7TSIAYXZ.js → chunk-PDAMK4Q5.js} +1 -1
- package/dist/{chunk-LLU2BJYY.js → chunk-Q7IU7MOR.js} +2 -2
- package/dist/{chunk-BE6X3G5C.js → chunk-QC4FJVLM.js} +1 -1
- package/dist/{chunk-EKGD52A2.js → chunk-QZDMFVY7.js} +2 -2
- package/dist/{chunk-IAKZWKNV.js → chunk-RMHUQGRK.js} +4 -4
- package/dist/{chunk-V6LLKVWS.js → chunk-RZAAVJ2F.js} +1 -1
- package/dist/{chunk-Q3I4JEGM.js → chunk-S7NPLFWV.js} +286 -143
- package/dist/{chunk-PZSNUSBM.js → chunk-TCLGPL65.js} +2 -2
- package/dist/{chunk-L32XKBOF.js → chunk-UBC3SMKR.js} +1 -1
- package/dist/{chunk-SC4T53X4.js → chunk-UDO4A3HY.js} +3 -3
- package/dist/{chunk-T6HJCRJC.js → chunk-UEZXMWG3.js} +2 -2
- package/dist/{chunk-32MY4AZN.js → chunk-WPHMNO44.js} +1345 -23
- package/dist/{chunk-GBTGTS35.js → chunk-WWTEA2BG.js} +1 -1
- package/dist/{chunk-P4PMYDLR.js → chunk-XS4EJSKU.js} +1 -1
- package/dist/{chunk-S4AMCVAK.js → chunk-YQZNILVH.js} +5 -5
- package/dist/{chunk-OLVR6P6R.js → chunk-Z6B37SNM.js} +2 -2
- package/dist/{chunk-TPWWGKU7.js → chunk-ZJUKFZ2P.js} +2 -2
- package/dist/{chunk-K3ZB5A3K.js → chunk-ZMXUQLI2.js} +55 -14
- package/dist/{clean-STQ2XPVL.js → clean-RDKBYQYS.js} +20 -20
- package/dist/cli.js +52 -52
- package/dist/{cloud-OQNTIR2N.js → cloud-DRCSH3RO.js} +4 -4
- package/dist/{cloudrun-ELOBBQHC.js → cloudrun-AJUTHUEA.js} +5 -5
- package/dist/{compare-7PHURXFI.js → compare-EJNCWA7A.js} +28 -28
- package/dist/{compiler-A3FX5DBC.js → compiler-QO36EXLS.js} +3 -3
- package/dist/{component-56DVF6XU.js → component-55TKGKMP.js} +3 -3
- package/dist/{compositions-2DMUUOFR.js → compositions-LDU2NXG3.js} +12 -12
- package/dist/{delete-EECQVAVJ.js → delete-M5FEYAQW.js} +3 -3
- package/dist/{deterministicFonts-64U7A25I.js → deterministicFonts-HAEMV4TM.js} +5 -4
- package/dist/{dist-OJYO32WN.js → dist-FNNGCKFS.js} +1 -1
- package/dist/{dist-IPEAV427.js → dist-S3PJNPXQ.js} +3 -3
- package/dist/{doctor-F3CXFLFV.js → doctor-AGGUO5NJ.js} +11 -11
- package/dist/{events-2LJGIZKG.js → events-CIWUMG4Z.js} +10 -10
- package/dist/{events-H5RGIYVK.js → events-LBZKC7L7.js} +9 -9
- package/dist/{feedback-VCMOOK2O.js → feedback-J7I7VS4Q.js} +13 -13
- package/dist/{figma-BL4HCJ5P.js → figma-2AIFPR3T.js} +3 -3
- package/dist/{fontData.generated-BW5QEPUT.js → fontData.generated-NXAOI4GH.js} +1 -1
- package/dist/fontLocalizeCli.js +11 -12
- package/dist/{get-HWN5Z3BL.js → get-ZJBDIWZJ.js} +3 -3
- package/dist/{gpuPolicy-37CE57HN.js → gpuPolicy-2ZUZE5D6.js} +7 -7
- package/dist/{grade-compare-3OPZYNNZ.js → grade-compare-XVYJT6ZR.js} +28 -28
- package/dist/{gsapParser-LOSAMHNR.js → gsapParser-DIYYX6JT.js} +63 -41
- package/dist/{gsapParserAcorn-ZO2LPEZY.js → gsapParserAcorn-E3GEIRZB.js} +1 -1
- package/dist/{help-X3EPTRKV.js → help-TBVVVEDS.js} +1 -1
- package/dist/{history-YR6YTOLT.js → history-NZPEQU4L.js} +20 -20
- package/dist/hyperframe-runtime.js +30 -30
- package/dist/hyperframe.manifest.json +1 -1
- package/dist/hyperframe.runtime.iife.js +30 -30
- package/dist/hyperframes-player.global.js +3 -3
- package/dist/{info-QG7OXNW6.js → info-T6BT2MFN.js} +12 -12
- package/dist/{init-JUHW2ZN3.js → init-4EPQ7SDV.js} +23 -23
- package/dist/{inspect-LIDYSXAG.js → inspect-BPZFMWWW.js} +29 -29
- package/dist/{installer-YH76H5ML.js → installer-K6O5WF44.js} +5 -5
- package/dist/{keyframes-ZLHVLMRA.js → keyframes-UL3XY52K.js} +45 -43
- package/dist/{lambda-T33I4AX6.js → lambda-D7UMN4DJ.js} +6 -6
- package/dist/{layout-VD36CWWV.js → layout-YYGOEQKI.js} +29 -29
- package/dist/{lint-JAWDGLYW.js → lint-OW2KOTEE.js} +15 -15
- package/dist/{lintProject-IOQ4DBS4.js → lintProject-OHBBM3DJ.js} +1 -1
- package/dist/{list-24TGFCB3.js → list-EIDAPK3I.js} +3 -3
- package/dist/{login-E2PE6CAD.js → login-DWU6OUCZ.js} +4 -4
- package/dist/{media-treatment-2BMUTUGD.js → media-treatment-I7BTOWX4.js} +15 -15
- package/dist/{models-TS5PWNPS.js → models-C4E4IWJD.js} +8 -8
- package/dist/{motionShot-RU4MTVOO.js → motionShot-S4MIGMT5.js} +13 -13
- package/dist/{normalize-audio-245DPF4X.js → normalize-audio-X2M4VF2O.js} +15 -15
- package/dist/{open-RH7YWUVH.js → open-35JKHN2G.js} +11 -11
- package/dist/{pipeline-ZK3JBXX5.js → pipeline-TNGWUZQC.js} +7 -7
- package/dist/{play-VK6IF765.js → play-F2LMNJEH.js} +31 -31
- package/dist/{preflight-H7YMOAYQ.js → preflight-SMNV6RW2.js} +8 -8
- package/dist/{present-CLKL6QSK.js → present-AD4N25LI.js} +21 -21
- package/dist/{preview-GEPWX2GX.js → preview-QWMTUNE2.js} +21 -21
- package/dist/{producer-HBP36IDM.js → producer-ECLEN5RP.js} +1 -1
- package/dist/{projectConfig-FPSBKFQO.js → projectConfig-HYJMJ5BY.js} +7 -7
- package/dist/{publication-ACSV54DD.js → publication-HQ5Y3KBM.js} +3 -3
- package/dist/{publish-L2NGI7MV.js → publish-VHDYQASC.js} +12 -12
- package/dist/{remove-background-JRRBJN4U.js → remove-background-W2YX2NVH.js} +8 -8
- package/dist/{render-RXMA4QYX.js → render-2HZ6WDVA.js} +18 -18
- package/dist/{render-LZEOB6JN.js → render-AO5VX3YI.js} +4 -4
- package/dist/{render-TDSGRTQQ.js → render-JJVVEVIZ.js} +27 -27
- package/dist/{render-batch-ZJQ5VHY6.js → render-batch-YH7CTWNY.js} +4 -4
- package/dist/{renderCancellation-MS5MAATT.js → renderCancellation-JPIHE47X.js} +7 -7
- package/dist/renderSetupWorker.js +1 -1
- package/dist/{resolver-WODAPAQC.js → resolver-IZ647GBZ.js} +5 -5
- package/dist/{sherpa-B3IHJHTK.js → sherpa-KKLNW6U2.js} +8 -8
- package/dist/{skills-56DFZVPS.js → skills-O7QZLKAG.js} +12 -12
- package/dist/{skillsUpdateCheck-AXKUBY44.js → skillsUpdateCheck-W563PHIX.js} +3 -3
- package/dist/{snapshot-NYQJL43J.js → snapshot-4M37T4WP.js} +34 -34
- package/dist/{src-UQ72QBPU.js → src-6YQBJGWR.js} +12 -13
- package/dist/{src-4K2RSSO5.js → src-PVYUYV3M.js} +6 -6
- package/dist/{staticProjectServer-OJH6TFA3.js → staticProjectServer-7EU666GS.js} +16 -16
- package/dist/{status-MZT3S6U2.js → status-YM52BQ6S.js} +7 -7
- package/dist/studio/assets/{hyperframes-player-uqzDaxVL.js → hyperframes-player-DfEcBl9r.js} +1 -1
- package/dist/studio/assets/{index-DXSYlqmV.js → index-9ZaZFCMZ.js} +1 -1
- package/dist/studio/assets/{index-CLBNYlDY.js → index-Bmy-KVib.js} +1 -1
- package/dist/studio/assets/{index-COhbL1xq.js → index-C9YkEyN4.js} +189 -189
- package/dist/studio/{chunk-3F47EFCT.js → chunk-7UQWVN52.js} +17 -10
- package/dist/studio/chunk-7UQWVN52.js.map +1 -0
- package/dist/studio/{chunk-246VB2G3.js → chunk-DRVWEPO5.js} +35 -7
- package/dist/studio/chunk-DRVWEPO5.js.map +1 -0
- package/dist/studio/{chunk-5EKWJZLM.js → chunk-TDWGXBUT.js} +1 -1
- package/dist/studio/chunk-TDWGXBUT.js.map +1 -0
- package/dist/studio/{domEditingLayers-OL7GLSS4.js → domEditingLayers-IEVLO54Z.js} +3 -3
- package/dist/studio/index.d.ts +8 -9
- package/dist/studio/index.html +1 -1
- package/dist/studio/index.js +1396 -917
- package/dist/studio/index.js.map +1 -1
- package/dist/studio/player.js +2 -2
- package/dist/{studio-api-IT5SYLXJ.js → studio-api-PNQZRPX5.js} +10 -10
- package/dist/{studioServer-KHYH2K3Q.js → studioServer-PTUEDJ4R.js} +56 -50
- package/dist/{system-4KER4Y62.js → system-EZYZNUFD.js} +7 -7
- package/dist/{telemetry-G3NYQGWI.js → telemetry-DTSHY4NX.js} +10 -10
- package/dist/{timeline-CXK4RVZE.js → timeline-OQEMNGIW.js} +20 -20
- package/dist/{tokens-RLU7WBIC.js → tokens-IGPEYHDJ.js} +2 -2
- package/dist/{transcribe-SOY2USBA.js → transcribe-BNSJRR4M.js} +8 -8
- package/dist/{transcribe-Y37DWCAV.js → transcribe-LC5JLRDF.js} +13 -13
- package/dist/{tts-CGUXR7R4.js → tts-P3LIPIL5.js} +1 -1
- package/dist/{updateCheck-EJ5SAGDC.js → updateCheck-OJAVDMMV.js} +2 -2
- package/dist/{upgrade-MBWWBNGK.js → upgrade-2G4MARM4.js} +2 -2
- package/dist/{urlDownloader-ZJ2TZQ7C.js → urlDownloader-H6WRI2UE.js} +6 -6
- package/dist/{validate-4J3OCFET.js → validate-MOUGAHM2.js} +17 -17
- package/dist/{video-43437E6M.js → video-FGSGKRSV.js} +7 -7
- package/package.json +1 -1
- package/dist/chunk-4C656PVL.js +0 -1393
- package/dist/chunk-AQIS2UNW.js +0 -3578
- package/dist/fontCompression-ISNK7STS.js +0 -15
- package/dist/studio/chunk-246VB2G3.js.map +0 -1
- package/dist/studio/chunk-3F47EFCT.js.map +0 -1
- package/dist/studio/chunk-5EKWJZLM.js.map +0 -1
- package/dist/studio/{domEditingLayers-OL7GLSS4.js.map → domEditingLayers-IEVLO54Z.js.map} +0 -0
- package/dist/{synthesize-BDXNRYZN.js → synthesize-K62TIZWI.js} +4 -4
package/dist/studio/index.js
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
STUDIO_PREVIEW_FPS,
|
|
10
10
|
applyPreviewVariablesToUrl,
|
|
11
11
|
clampToDuration,
|
|
12
|
+
collectElementPatches,
|
|
12
13
|
computePinnedZoomPercent,
|
|
13
14
|
createTimelineResetState,
|
|
14
15
|
effectiveThumbnailMode,
|
|
@@ -26,6 +27,7 @@ import {
|
|
|
26
27
|
liveTime,
|
|
27
28
|
panelsInZone,
|
|
28
29
|
parseDockLayout,
|
|
30
|
+
patchElements,
|
|
29
31
|
readStudioUiPreferences,
|
|
30
32
|
secondsToFrame,
|
|
31
33
|
stepFrameTime,
|
|
@@ -37,7 +39,7 @@ import {
|
|
|
37
39
|
usePreviewVariablesStore,
|
|
38
40
|
whenPreviewBooted,
|
|
39
41
|
writeStudioUiPreferences
|
|
40
|
-
} from "./chunk-
|
|
42
|
+
} from "./chunk-DRVWEPO5.js";
|
|
41
43
|
import {
|
|
42
44
|
Button,
|
|
43
45
|
ContextMenu,
|
|
@@ -102,7 +104,7 @@ import {
|
|
|
102
104
|
resolveDomEditSelection,
|
|
103
105
|
serializeDomEditTextFields,
|
|
104
106
|
studioApiFetch
|
|
105
|
-
} from "./chunk-
|
|
107
|
+
} from "./chunk-7UQWVN52.js";
|
|
106
108
|
import {
|
|
107
109
|
LAYER_REVEAL_PRIOR_POSITION_ATTR,
|
|
108
110
|
LAYER_REVEAL_PRIOR_Z_ATTR,
|
|
@@ -162,7 +164,7 @@ import {
|
|
|
162
164
|
splitTimelineElementKey,
|
|
163
165
|
toHexColor,
|
|
164
166
|
wrapTimeline
|
|
165
|
-
} from "./chunk-
|
|
167
|
+
} from "./chunk-TDWGXBUT.js";
|
|
166
168
|
import "./chunk-PZ5AY32C.js";
|
|
167
169
|
|
|
168
170
|
// src/components/EditorShell.tsx
|
|
@@ -2779,7 +2781,6 @@ function resolveTimelineResize(input, edge, clientX) {
|
|
|
2779
2781
|
const clampedDelta = clamp2(deltaTime, minDelta, maxDelta);
|
|
2780
2782
|
return applyClipStartTrimDelta(input, clampedDelta);
|
|
2781
2783
|
}
|
|
2782
|
-
var MAX_HAND_EDIT_CLIPS = 3;
|
|
2783
2784
|
function resolveBlockedTimelineEditIntent(input) {
|
|
2784
2785
|
if (input.capabilities.canMove) {
|
|
2785
2786
|
return null;
|
|
@@ -4348,52 +4349,6 @@ var useLinkedClipPreferences = create2((set) => ({
|
|
|
4348
4349
|
}));
|
|
4349
4350
|
var isLinkedSelectionOn = () => useLinkedClipPreferences.getState().linkedSelection;
|
|
4350
4351
|
|
|
4351
|
-
// src/player/components/timelineLinkSelection.ts
|
|
4352
|
-
function selectClipWithLinks(key2, altKey, setSelectedElementId) {
|
|
4353
|
-
const state = usePlayerStore.getState();
|
|
4354
|
-
if (altKey || !isLinkedSelectionOn()) {
|
|
4355
|
-
state.setSelection([key2], key2);
|
|
4356
|
-
return;
|
|
4357
|
-
}
|
|
4358
|
-
setSelectedElementId(key2);
|
|
4359
|
-
const { selectedElementIds, elements } = usePlayerStore.getState();
|
|
4360
|
-
const expanded = expandToLinkedMembers(selectedElementIds, elements);
|
|
4361
|
-
if (expanded.size > selectedElementIds.size) state.setSelection(expanded, key2);
|
|
4362
|
-
}
|
|
4363
|
-
function toggleClipWithLinks(key2, altKey) {
|
|
4364
|
-
const state = usePlayerStore.getState();
|
|
4365
|
-
const element = state.elements.find((el) => (el.key ?? el.id) === key2);
|
|
4366
|
-
const members = element ? linkedMembersOf(element, state.elements, !altKey && isLinkedSelectionOn()) : [];
|
|
4367
|
-
const memberKeys = members.length > 0 ? members.map((el) => el.key ?? el.id) : [key2];
|
|
4368
|
-
const next = new Set(state.selectedElementIds);
|
|
4369
|
-
const removing = next.has(key2);
|
|
4370
|
-
for (const memberKey of memberKeys) {
|
|
4371
|
-
if (removing) next.delete(memberKey);
|
|
4372
|
-
else next.add(memberKey);
|
|
4373
|
-
}
|
|
4374
|
-
state.setSelection(next, removing ? state.selectedElementId : key2);
|
|
4375
|
-
const primary = usePlayerStore.getState().selectedElementId;
|
|
4376
|
-
return state.elements.find((el) => (el.key ?? el.id) === primary) ?? null;
|
|
4377
|
-
}
|
|
4378
|
-
function exceedsHandEditLimit(grabbed, altKey, edge) {
|
|
4379
|
-
const { selectedElementIds, elements } = usePlayerStore.getState();
|
|
4380
|
-
const grabbedKey = grabbed.key ?? grabbed.id;
|
|
4381
|
-
const keys = linkedGestureKeys(
|
|
4382
|
-
selectedElementIds,
|
|
4383
|
-
grabbed,
|
|
4384
|
-
elements,
|
|
4385
|
-
altKey,
|
|
4386
|
-
isLinkedSelectionOn()
|
|
4387
|
-
);
|
|
4388
|
-
const changed = edge ? buildTimelineGroupResizeMembers(
|
|
4389
|
-
elements,
|
|
4390
|
-
dropMisalignedTrimPartners(keys, grabbed, elements, edge),
|
|
4391
|
-
grabbedKey,
|
|
4392
|
-
edge
|
|
4393
|
-
) : resolveGroupMovers(elements, keys, grabbedKey);
|
|
4394
|
-
return (changed?.length ?? 1) > MAX_HAND_EDIT_CLIPS;
|
|
4395
|
-
}
|
|
4396
|
-
|
|
4397
4352
|
// src/player/components/timelineReadOnly.ts
|
|
4398
4353
|
import { createContext as createContext2, useContext as useContext2, useEffect as useEffect7, useEffectEvent } from "react";
|
|
4399
4354
|
var TimelineReadOnlyContext = createContext2(null);
|
|
@@ -4504,20 +4459,86 @@ function timelineEaseFocusId(elementId2, target) {
|
|
|
4504
4459
|
import { useCallback as useCallback8, useMemo as useMemo6 } from "react";
|
|
4505
4460
|
|
|
4506
4461
|
// src/player/components/TimelinePropertyLanes.tsx
|
|
4507
|
-
import {
|
|
4462
|
+
import {
|
|
4463
|
+
useMemo as useMemo3
|
|
4464
|
+
} from "react";
|
|
4508
4465
|
import {
|
|
4509
4466
|
classifyPropertyGroup
|
|
4510
4467
|
} from "@hyperframes/core/gsap-parser";
|
|
4511
4468
|
|
|
4512
4469
|
// src/hooks/gsapShared.ts
|
|
4513
4470
|
import { isHtmlElement as isHtmlElement2 } from "@hyperframes/core/runtime/dom-realm";
|
|
4471
|
+
import { getObjectArrayKeyframeTiming } from "@hyperframes/parsers/gsap-parser";
|
|
4472
|
+
|
|
4473
|
+
// src/utils/gsapKeyframeEases.ts
|
|
4474
|
+
import gsap2 from "gsap";
|
|
4475
|
+
var PERCENTAGE_SEGMENT_EASE = "power1.inOut";
|
|
4476
|
+
var ARRAY_STEP_EASE = "none";
|
|
4477
|
+
var MOTION_PATH_RUN_EASE = "power1.out";
|
|
4478
|
+
function keyframedTweenEases(anim) {
|
|
4479
|
+
const data = anim.keyframes;
|
|
4480
|
+
const fallback = data?.format === "object-array" ? ARRAY_STEP_EASE : data?.easeEach ?? PERCENTAGE_SEGMENT_EASE;
|
|
4481
|
+
return {
|
|
4482
|
+
run: data ? data.ease ?? anim.ease ?? (data.fromMotionPath ? MOTION_PATH_RUN_EASE : void 0) : void 0,
|
|
4483
|
+
segment: (arriving) => arriving.ease ?? fallback
|
|
4484
|
+
};
|
|
4485
|
+
}
|
|
4486
|
+
function easeFunction(name) {
|
|
4487
|
+
if (!name || name === "none" || name === "linear") return null;
|
|
4488
|
+
return gsap2.parseEase(name) ?? null;
|
|
4489
|
+
}
|
|
4490
|
+
var runEaseOf = (anim) => keyframedTweenEases(anim).run;
|
|
4491
|
+
var SCAN_STEPS = 200;
|
|
4492
|
+
var BISECT_STEPS = 40;
|
|
4493
|
+
var timingEases = /* @__PURE__ */ new Map();
|
|
4494
|
+
function timingEase(runEase) {
|
|
4495
|
+
if (!runEase) return null;
|
|
4496
|
+
if (!timingEases.has(runEase)) {
|
|
4497
|
+
const run = easeFunction(runEase);
|
|
4498
|
+
const samples = Array.from(
|
|
4499
|
+
{ length: SCAN_STEPS + 1 },
|
|
4500
|
+
(_, step) => run?.(step / SCAN_STEPS) ?? 0
|
|
4501
|
+
);
|
|
4502
|
+
const oneToOne = samples.every((v, i) => v >= 0 && v <= 1 && (i === 0 || v > samples[i - 1]));
|
|
4503
|
+
timingEases.set(runEase, run && oneToOne ? run : null);
|
|
4504
|
+
}
|
|
4505
|
+
return timingEases.get(runEase) ?? null;
|
|
4506
|
+
}
|
|
4507
|
+
function progressAtTime(runEase, timePercentage) {
|
|
4508
|
+
const run = timingEase(runEase);
|
|
4509
|
+
return run ? run(timePercentage / 100) * 100 : timePercentage;
|
|
4510
|
+
}
|
|
4511
|
+
function timeAtProgress(runEase, percentage) {
|
|
4512
|
+
const run = timingEase(runEase);
|
|
4513
|
+
if (!run || percentage <= 0 || percentage >= 100) return percentage;
|
|
4514
|
+
const target = percentage / 100;
|
|
4515
|
+
let low = 0;
|
|
4516
|
+
let high = 1;
|
|
4517
|
+
for (let step = 1; step <= SCAN_STEPS; step++) {
|
|
4518
|
+
if (run(step / SCAN_STEPS) >= target) {
|
|
4519
|
+
low = (step - 1) / SCAN_STEPS;
|
|
4520
|
+
high = step / SCAN_STEPS;
|
|
4521
|
+
break;
|
|
4522
|
+
}
|
|
4523
|
+
}
|
|
4524
|
+
for (let step = 0; step < BISECT_STEPS; step++) {
|
|
4525
|
+
const mid = (low + high) / 2;
|
|
4526
|
+
if (run(mid) >= target) high = mid;
|
|
4527
|
+
else low = mid;
|
|
4528
|
+
}
|
|
4529
|
+
return high * 100;
|
|
4530
|
+
}
|
|
4514
4531
|
|
|
4515
4532
|
// src/utils/globalTimeCompiler.ts
|
|
4533
|
+
import { GSAP_DEFAULT_DURATION } from "@hyperframes/parsers/gsap-parser";
|
|
4516
4534
|
function absoluteToPercentage(time, tweenStart, tweenDuration) {
|
|
4517
4535
|
if (tweenDuration <= 0) return 0;
|
|
4518
4536
|
const raw = (time - tweenStart) / tweenDuration * 100;
|
|
4519
4537
|
return Math.max(0, Math.min(100, Math.round(raw * 1e3) / 1e3));
|
|
4520
4538
|
}
|
|
4539
|
+
function percentageToAbsolute(pct, tweenStart, tweenDuration) {
|
|
4540
|
+
return tweenStart + pct / 100 * tweenDuration;
|
|
4541
|
+
}
|
|
4521
4542
|
function isTimeWithinTween(time, tweenStart, tweenDuration) {
|
|
4522
4543
|
return time >= tweenStart && time <= tweenStart + tweenDuration;
|
|
4523
4544
|
}
|
|
@@ -4528,8 +4549,8 @@ function resolveTweenStart(animation) {
|
|
|
4528
4549
|
if (!Number.isNaN(parsed)) return parsed;
|
|
4529
4550
|
return null;
|
|
4530
4551
|
}
|
|
4531
|
-
function resolveTweenDuration(animation
|
|
4532
|
-
return animation.duration ??
|
|
4552
|
+
function resolveTweenDuration(animation) {
|
|
4553
|
+
return animation.duration ?? GSAP_DEFAULT_DURATION;
|
|
4533
4554
|
}
|
|
4534
4555
|
function findTweenAtTime(time, animations, selector) {
|
|
4535
4556
|
for (const anim of animations) {
|
|
@@ -4545,7 +4566,13 @@ function absoluteToPercentageForAnimation(time, animation) {
|
|
|
4545
4566
|
const start = resolveTweenStart(animation);
|
|
4546
4567
|
if (start === null) return null;
|
|
4547
4568
|
const duration = resolveTweenDuration(animation);
|
|
4548
|
-
return absoluteToPercentage(time, start, duration);
|
|
4569
|
+
return progressAtTime(runEaseOf(animation), absoluteToPercentage(time, start, duration));
|
|
4570
|
+
}
|
|
4571
|
+
function percentageToAbsoluteForAnimation(pct, animation) {
|
|
4572
|
+
const start = resolveTweenStart(animation);
|
|
4573
|
+
if (start === null) return null;
|
|
4574
|
+
const duration = resolveTweenDuration(animation);
|
|
4575
|
+
return percentageToAbsolute(timeAtProgress(runEaseOf(animation), pct), start, duration);
|
|
4549
4576
|
}
|
|
4550
4577
|
function matchesSelector(tweenSelector, querySelector) {
|
|
4551
4578
|
return tweenSelector.split(",").some((part) => part.trim() === querySelector);
|
|
@@ -4572,6 +4599,11 @@ function isInstantHold(animation) {
|
|
|
4572
4599
|
}
|
|
4573
4600
|
var SAFE_HASH_ID = /^-?[A-Za-z_][\w-]*$/;
|
|
4574
4601
|
var KEYFRAME_PCT_MATCH = 1;
|
|
4602
|
+
function playsNear(anim, percentage, progress) {
|
|
4603
|
+
const runEase = runEaseOf(anim);
|
|
4604
|
+
const gap = timeAtProgress(runEase, percentage) - timeAtProgress(runEase, progress);
|
|
4605
|
+
return Math.abs(gap) <= KEYFRAME_PCT_MATCH;
|
|
4606
|
+
}
|
|
4575
4607
|
function idSelector(id) {
|
|
4576
4608
|
return SAFE_HASH_ID.test(id) ? `#${id}` : `[id="${id.replace(/(["\\])/g, "\\$1")}"]`;
|
|
4577
4609
|
}
|
|
@@ -4682,20 +4714,16 @@ function tweenTargetsElement(targetSelector, selector, element) {
|
|
|
4682
4714
|
if (!element || !doc) return false;
|
|
4683
4715
|
return matchesExactlyOne(doc, targetSelector, element);
|
|
4684
4716
|
}
|
|
4685
|
-
function resolveEditableTweenDuration(animation, selection) {
|
|
4686
|
-
const clipDuration2 = Number.parseFloat(selection.dataAttributes?.duration ?? "");
|
|
4687
|
-
return resolveTweenDuration(
|
|
4688
|
-
animation,
|
|
4689
|
-
Number.isFinite(clipDuration2) && clipDuration2 > 0 ? clipDuration2 : 0.5
|
|
4690
|
-
);
|
|
4691
|
-
}
|
|
4692
4717
|
function computeElementPercentage(currentTime, selection, animation) {
|
|
4693
4718
|
if (animation) {
|
|
4694
4719
|
const start = resolveTweenStart(animation);
|
|
4695
|
-
const duration =
|
|
4720
|
+
const duration = resolveTweenDuration(animation);
|
|
4696
4721
|
if (duration <= 0) return 0;
|
|
4697
4722
|
if (start !== null) {
|
|
4698
|
-
return
|
|
4723
|
+
return progressAtTime(
|
|
4724
|
+
runEaseOf(animation),
|
|
4725
|
+
absoluteToPercentage(currentTime, start, duration)
|
|
4726
|
+
);
|
|
4699
4727
|
}
|
|
4700
4728
|
}
|
|
4701
4729
|
const elStart = Number.parseFloat(selection.dataAttributes?.start ?? "0") || 0;
|
|
@@ -4732,11 +4760,16 @@ function parsePercentageKeyframes(kfObj) {
|
|
|
4732
4760
|
let easeEach;
|
|
4733
4761
|
if (Array.isArray(kfObj)) {
|
|
4734
4762
|
const steps = kfObj;
|
|
4763
|
+
const timing = getObjectArrayKeyframeTiming(
|
|
4764
|
+
steps.map((entry2) => entry2?.duration)
|
|
4765
|
+
);
|
|
4766
|
+
if (!timing) return null;
|
|
4735
4767
|
steps.forEach((entry2, i) => {
|
|
4736
4768
|
if (!entry2 || typeof entry2 !== "object") return;
|
|
4737
|
-
const percentage = steps.length > 1 ? Math.round(i / (steps.length - 1) * 1e3) / 10 : 0;
|
|
4738
4769
|
const properties = collectAnimatableKeyframeProperties(entry2);
|
|
4739
|
-
if (Object.keys(properties).length > 0)
|
|
4770
|
+
if (Object.keys(properties).length > 0) {
|
|
4771
|
+
keyframes.push({ percentage: timing.percentages[i], properties, step: i });
|
|
4772
|
+
}
|
|
4740
4773
|
});
|
|
4741
4774
|
return keyframes.length > 0 ? { keyframes } : null;
|
|
4742
4775
|
}
|
|
@@ -4783,11 +4816,15 @@ function toClipPercentage(absoluteTime, clipStart2, clipDuration2, fallbackPerce
|
|
|
4783
4816
|
}
|
|
4784
4817
|
function toClipKeyframes(source, anim, clipStart2, clipDuration2) {
|
|
4785
4818
|
const tweenStart = anim.resolvedStart ?? (typeof anim.position === "number" ? anim.position : 0);
|
|
4786
|
-
const tweenDuration = anim
|
|
4819
|
+
const tweenDuration = resolveTweenDuration(anim);
|
|
4787
4820
|
return source.map((keyframe) => ({
|
|
4788
4821
|
...keyframe,
|
|
4789
4822
|
percentage: toClipPercentage(
|
|
4790
|
-
toAbsoluteTime(
|
|
4823
|
+
toAbsoluteTime(
|
|
4824
|
+
tweenStart,
|
|
4825
|
+
tweenDuration,
|
|
4826
|
+
timeAtProgress(runEaseOf(anim), keyframe.percentage)
|
|
4827
|
+
),
|
|
4791
4828
|
clipStart2,
|
|
4792
4829
|
clipDuration2,
|
|
4793
4830
|
keyframe.percentage
|
|
@@ -7109,10 +7146,22 @@ function subscribeTimelineKeyframeRetimePreview(source, listener2) {
|
|
|
7109
7146
|
listener2(coordinator.preview);
|
|
7110
7147
|
return () => coordinator.previewListeners.delete(listener2);
|
|
7111
7148
|
}
|
|
7149
|
+
function retimedRunEase(actor) {
|
|
7150
|
+
const id = actor.target.animationId;
|
|
7151
|
+
if (id === void 0) return void 0;
|
|
7152
|
+
const { gsapAnimations } = usePlayerStore.getState();
|
|
7153
|
+
const animations = gsapAnimations.get(actor.elementId) ?? [...gsapAnimations.values()].flat();
|
|
7154
|
+
const animation = animations.find((candidate) => candidate.id === id);
|
|
7155
|
+
return animation ? runEaseOf(animation) : void 0;
|
|
7156
|
+
}
|
|
7112
7157
|
function resolveRetimeTweenPercentage(actor, toClipPercentage2) {
|
|
7113
7158
|
const animationKeyframes2 = actor.target.animationId === void 0 ? actor.keyframes : actor.keyframes.filter((keyframe) => keyframe.animationId === actor.target.animationId);
|
|
7114
7159
|
const tweenPercentages = animationKeyframes2.map((keyframe) => keyframe.tweenPercentage).filter((value) => typeof value === "number");
|
|
7115
|
-
const
|
|
7160
|
+
const runEase = retimedRunEase(actor);
|
|
7161
|
+
const timed = animationKeyframes2.map(
|
|
7162
|
+
(keyframe) => keyframe.tweenPercentage === void 0 ? keyframe : { ...keyframe, tweenPercentage: timeAtProgress(runEase, keyframe.tweenPercentage) }
|
|
7163
|
+
);
|
|
7164
|
+
const mapped = progressAtTime(runEase, clipToTweenPercentage(timed, toClipPercentage2));
|
|
7116
7165
|
if (tweenPercentages.length === 0) return mapped;
|
|
7117
7166
|
return Math.max(Math.min(...tweenPercentages), Math.min(Math.max(...tweenPercentages), mapped));
|
|
7118
7167
|
}
|
|
@@ -7819,6 +7868,7 @@ function getTimelinePropertyLanes(animations, clipStart2, clipDuration2) {
|
|
|
7819
7868
|
return Array.from(sourceGroups(animations), ([group, groupAnimations]) => ({
|
|
7820
7869
|
group,
|
|
7821
7870
|
animations: groupAnimations,
|
|
7871
|
+
elementAnimations: animations,
|
|
7822
7872
|
keyframes: groupKeyframes(groupAnimations, group, clipStart2, clipDuration2)
|
|
7823
7873
|
})).filter((lane) => lane.keyframes.length > 0);
|
|
7824
7874
|
}
|
|
@@ -7829,6 +7879,7 @@ function TimelinePropertyLanes({
|
|
|
7829
7879
|
clipDuration: clipDuration2,
|
|
7830
7880
|
clipLeftPx,
|
|
7831
7881
|
clipWidthPx,
|
|
7882
|
+
passengerStyle,
|
|
7832
7883
|
accentColor,
|
|
7833
7884
|
isSelected: isSelected2,
|
|
7834
7885
|
currentPercentage,
|
|
@@ -7868,7 +7919,8 @@ function TimelinePropertyLanes({
|
|
|
7868
7919
|
left: clipLeftPx,
|
|
7869
7920
|
top: getTimelineLaneTop(laneIndex),
|
|
7870
7921
|
width: clipWidthPx,
|
|
7871
|
-
height: LANE_H
|
|
7922
|
+
height: LANE_H,
|
|
7923
|
+
...passengerStyle
|
|
7872
7924
|
},
|
|
7873
7925
|
children: /* @__PURE__ */ jsx16(
|
|
7874
7926
|
TimelineDiamondLane,
|
|
@@ -9145,6 +9197,13 @@ function computeStackingPatches(elements, editedKeys) {
|
|
|
9145
9197
|
return patches;
|
|
9146
9198
|
}
|
|
9147
9199
|
|
|
9200
|
+
// src/player/store/batchElementUpdates.ts
|
|
9201
|
+
function batchElementUpdates(run) {
|
|
9202
|
+
const patches = collectElementPatches(run);
|
|
9203
|
+
if (!patches || patches.size === 0) return;
|
|
9204
|
+
usePlayerStore.setState((state) => ({ elements: patchElements(state.elements, patches) }));
|
|
9205
|
+
}
|
|
9206
|
+
|
|
9148
9207
|
// src/player/components/timelineOptimisticRevision.ts
|
|
9149
9208
|
var revisionsByUpdater = /* @__PURE__ */ new WeakMap();
|
|
9150
9209
|
function beginTimelineOptimisticGesture(updateElement, keys, scope = "timing") {
|
|
@@ -9170,11 +9229,13 @@ function isLatestTimelineOptimisticGesture(updateElement, gesture, key2, scope =
|
|
|
9170
9229
|
return gesture.has(key2) && revisionsByUpdater.get(updateElement)?.get(scope)?.get(key2) === gesture.get(key2);
|
|
9171
9230
|
}
|
|
9172
9231
|
function rollbackLatestTimelineOptimisticGesture(updateElement, gesture, rollbacks) {
|
|
9173
|
-
|
|
9174
|
-
|
|
9175
|
-
updateElement
|
|
9232
|
+
batchElementUpdates(() => {
|
|
9233
|
+
for (const rollback of rollbacks) {
|
|
9234
|
+
if (isLatestTimelineOptimisticGesture(updateElement, gesture, rollback.key)) {
|
|
9235
|
+
updateElement(rollback.key, rollback.updates);
|
|
9236
|
+
}
|
|
9176
9237
|
}
|
|
9177
|
-
}
|
|
9238
|
+
});
|
|
9178
9239
|
}
|
|
9179
9240
|
|
|
9180
9241
|
// src/components/nle/zLaneGesture.ts
|
|
@@ -9225,6 +9286,400 @@ function refreshAfterDurableLaneMove(pending2, deps) {
|
|
|
9225
9286
|
});
|
|
9226
9287
|
}
|
|
9227
9288
|
|
|
9289
|
+
// src/utils/studioSaveDiagnostics.ts
|
|
9290
|
+
var STUDIO_SAVE_ATTEMPT_PROPERTY = "__studioSaveAttempt";
|
|
9291
|
+
var StudioSaveHttpError = class extends Error {
|
|
9292
|
+
statusCode;
|
|
9293
|
+
alreadyToasted;
|
|
9294
|
+
constructor(message, statusCode, options = {}) {
|
|
9295
|
+
super(message);
|
|
9296
|
+
this.name = "StudioSaveHttpError";
|
|
9297
|
+
this.statusCode = statusCode;
|
|
9298
|
+
this.alreadyToasted = options.alreadyToasted ?? false;
|
|
9299
|
+
}
|
|
9300
|
+
};
|
|
9301
|
+
var StudioSaveNetworkError = class extends Error {
|
|
9302
|
+
constructor(message, options) {
|
|
9303
|
+
super(message, options);
|
|
9304
|
+
this.name = "StudioSaveNetworkError";
|
|
9305
|
+
}
|
|
9306
|
+
};
|
|
9307
|
+
var StudioFileConflictError = class extends StudioSaveHttpError {
|
|
9308
|
+
filePath;
|
|
9309
|
+
currentVersion;
|
|
9310
|
+
currentContent;
|
|
9311
|
+
attemptedContent;
|
|
9312
|
+
constructor(input) {
|
|
9313
|
+
super(`Save conflict: ${input.filePath} changed outside this Studio session`, 409);
|
|
9314
|
+
this.name = "StudioFileConflictError";
|
|
9315
|
+
this.filePath = input.filePath;
|
|
9316
|
+
this.currentVersion = input.currentVersion;
|
|
9317
|
+
this.currentContent = input.currentContent;
|
|
9318
|
+
this.attemptedContent = input.attemptedContent;
|
|
9319
|
+
}
|
|
9320
|
+
};
|
|
9321
|
+
function readNumericProperty(value, key2) {
|
|
9322
|
+
const record = value;
|
|
9323
|
+
const property = record[key2];
|
|
9324
|
+
return typeof property === "number" && Number.isFinite(property) ? property : void 0;
|
|
9325
|
+
}
|
|
9326
|
+
function createStudioSaveAbortError() {
|
|
9327
|
+
if (typeof DOMException !== "undefined") return new DOMException("Save aborted", "AbortError");
|
|
9328
|
+
const error = new Error("Save aborted");
|
|
9329
|
+
error.name = "AbortError";
|
|
9330
|
+
return error;
|
|
9331
|
+
}
|
|
9332
|
+
function throwIfStudioSaveAborted(signal) {
|
|
9333
|
+
if (signal?.aborted) throw createStudioSaveAbortError();
|
|
9334
|
+
}
|
|
9335
|
+
function attachStudioSaveAttempt(error, attempt) {
|
|
9336
|
+
if (!error || typeof error !== "object") return error;
|
|
9337
|
+
try {
|
|
9338
|
+
Object.defineProperty(error, STUDIO_SAVE_ATTEMPT_PROPERTY, {
|
|
9339
|
+
value: attempt,
|
|
9340
|
+
configurable: true
|
|
9341
|
+
});
|
|
9342
|
+
} catch {
|
|
9343
|
+
}
|
|
9344
|
+
return error;
|
|
9345
|
+
}
|
|
9346
|
+
function getStudioSaveErrorMessage(error) {
|
|
9347
|
+
if (error instanceof Error && error.message) return error.message;
|
|
9348
|
+
if (typeof error === "string" && error.trim()) return error;
|
|
9349
|
+
return "Unknown save failure";
|
|
9350
|
+
}
|
|
9351
|
+
function markStudioSaveErrorAlreadyToasted(error) {
|
|
9352
|
+
if (!error || typeof error !== "object") return error;
|
|
9353
|
+
try {
|
|
9354
|
+
Object.defineProperty(error, "alreadyToasted", { value: true, configurable: true });
|
|
9355
|
+
} catch {
|
|
9356
|
+
}
|
|
9357
|
+
return error;
|
|
9358
|
+
}
|
|
9359
|
+
function isStudioSaveErrorAlreadyToasted(error) {
|
|
9360
|
+
if (!error || typeof error !== "object") return false;
|
|
9361
|
+
if (error.alreadyToasted === true) return true;
|
|
9362
|
+
const cause = error.cause;
|
|
9363
|
+
return cause !== error && isStudioSaveErrorAlreadyToasted(cause);
|
|
9364
|
+
}
|
|
9365
|
+
function getStudioSaveStatusCode(error) {
|
|
9366
|
+
if (!error || typeof error !== "object") return void 0;
|
|
9367
|
+
const direct = readNumericProperty(error, "statusCode") ?? readNumericProperty(error, "status") ?? readNumericProperty(error, "status_code");
|
|
9368
|
+
if (direct != null) return direct;
|
|
9369
|
+
const cause = error.cause;
|
|
9370
|
+
if (cause && cause !== error) return getStudioSaveStatusCode(cause);
|
|
9371
|
+
return void 0;
|
|
9372
|
+
}
|
|
9373
|
+
function getStudioSaveAttempt(error) {
|
|
9374
|
+
if (!error || typeof error !== "object") return void 0;
|
|
9375
|
+
const direct = readNumericProperty(error, STUDIO_SAVE_ATTEMPT_PROPERTY);
|
|
9376
|
+
if (direct != null) return direct;
|
|
9377
|
+
const cause = error.cause;
|
|
9378
|
+
if (cause && cause !== error) return getStudioSaveAttempt(cause);
|
|
9379
|
+
return void 0;
|
|
9380
|
+
}
|
|
9381
|
+
function isStudioSaveAbortError(error) {
|
|
9382
|
+
return error instanceof Error && error.name === "AbortError";
|
|
9383
|
+
}
|
|
9384
|
+
function isRetryableStudioSaveError(error) {
|
|
9385
|
+
if (isStudioSaveAbortError(error)) return false;
|
|
9386
|
+
if (error instanceof StudioSaveNetworkError) return true;
|
|
9387
|
+
const statusCode = getStudioSaveStatusCode(error);
|
|
9388
|
+
if (statusCode == null) return false;
|
|
9389
|
+
return statusCode === 408 || statusCode === 425 || statusCode === 429 || statusCode >= 500;
|
|
9390
|
+
}
|
|
9391
|
+
function buildStudioSaveFailureProperties(input) {
|
|
9392
|
+
const statusCode = input.statusCode ?? getStudioSaveStatusCode(input.error) ?? null;
|
|
9393
|
+
const attempt = input.attempt ?? getStudioSaveAttempt(input.error) ?? void 0;
|
|
9394
|
+
return {
|
|
9395
|
+
source: input.source,
|
|
9396
|
+
error_message: getStudioSaveErrorMessage(input.error),
|
|
9397
|
+
status_code: statusCode,
|
|
9398
|
+
file_path: input.filePath ?? input.targetSourceFile ?? void 0,
|
|
9399
|
+
mutation_type: input.mutationType ?? void 0,
|
|
9400
|
+
attempt,
|
|
9401
|
+
label: input.label ?? void 0,
|
|
9402
|
+
target_id: input.targetId ?? void 0,
|
|
9403
|
+
target_selector: input.targetSelector ?? void 0,
|
|
9404
|
+
target_source_file: input.targetSourceFile ?? void 0,
|
|
9405
|
+
block_detail: gsapBlockDetail(input.error)
|
|
9406
|
+
};
|
|
9407
|
+
}
|
|
9408
|
+
function gsapBlockDetail(error) {
|
|
9409
|
+
if (!(error instanceof Error) || error.name !== "GsapEditBlockedError") return void 0;
|
|
9410
|
+
const detail = error.detail;
|
|
9411
|
+
return typeof detail === "string" ? detail : void 0;
|
|
9412
|
+
}
|
|
9413
|
+
function trackStudioSaveFailure(input) {
|
|
9414
|
+
trackStudioEvent("save_failure", buildStudioSaveFailureProperties(input));
|
|
9415
|
+
}
|
|
9416
|
+
function trackStudioEditBlocked(input) {
|
|
9417
|
+
trackStudioEvent("edit_blocked", buildStudioSaveFailureProperties(input));
|
|
9418
|
+
}
|
|
9419
|
+
async function createStudioSaveHttpError(response, fallbackMessage, options = {}) {
|
|
9420
|
+
let body = "";
|
|
9421
|
+
try {
|
|
9422
|
+
body = await response.text();
|
|
9423
|
+
} catch {
|
|
9424
|
+
body = "";
|
|
9425
|
+
}
|
|
9426
|
+
const detail = body.trim().slice(0, 300);
|
|
9427
|
+
const message = detail ? `${fallbackMessage} (${response.status}): ${detail}` : `${fallbackMessage} (${response.status})`;
|
|
9428
|
+
return new StudioSaveHttpError(message, response.status, options);
|
|
9429
|
+
}
|
|
9430
|
+
async function retryStudioSave(operation, options = {}) {
|
|
9431
|
+
const retries = options.retries ?? 3;
|
|
9432
|
+
const baseDelayMs = options.baseDelayMs ?? 500;
|
|
9433
|
+
const maxDelayMs = options.maxDelayMs ?? 8e3;
|
|
9434
|
+
const jitterRatio = options.jitterRatio ?? 0.25;
|
|
9435
|
+
const random = options.random ?? Math.random;
|
|
9436
|
+
const shouldRetry = options.shouldRetry ?? isRetryableStudioSaveError;
|
|
9437
|
+
const sleep = options.sleep ?? ((delayMs, signal) => new Promise((resolve, reject) => {
|
|
9438
|
+
throwIfStudioSaveAborted(signal);
|
|
9439
|
+
const onAbort = () => {
|
|
9440
|
+
globalThis.clearTimeout(timeout);
|
|
9441
|
+
reject(createStudioSaveAbortError());
|
|
9442
|
+
};
|
|
9443
|
+
const timeout = globalThis.setTimeout(() => {
|
|
9444
|
+
signal?.removeEventListener("abort", onAbort);
|
|
9445
|
+
resolve();
|
|
9446
|
+
}, delayMs);
|
|
9447
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
9448
|
+
}));
|
|
9449
|
+
const maxAttempts = retries + 1;
|
|
9450
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
9451
|
+
try {
|
|
9452
|
+
throwIfStudioSaveAborted(options.signal);
|
|
9453
|
+
return await operation(attempt);
|
|
9454
|
+
} catch (error) {
|
|
9455
|
+
const failure = attachStudioSaveAttempt(error, attempt);
|
|
9456
|
+
if (attempt >= maxAttempts || !shouldRetry(failure, attempt)) throw failure;
|
|
9457
|
+
const retryIndex = attempt - 1;
|
|
9458
|
+
const exponentialDelay = Math.min(baseDelayMs * 2 ** retryIndex, maxDelayMs);
|
|
9459
|
+
const jitterSpan = exponentialDelay * jitterRatio;
|
|
9460
|
+
const jitteredDelay = Math.round(exponentialDelay + (random() * 2 - 1) * jitterSpan);
|
|
9461
|
+
const delayMs = Math.max(0, Math.min(maxDelayMs, jitteredDelay));
|
|
9462
|
+
await sleep(delayMs, options.signal);
|
|
9463
|
+
}
|
|
9464
|
+
}
|
|
9465
|
+
throw new Error("Save retry loop exited unexpectedly");
|
|
9466
|
+
}
|
|
9467
|
+
|
|
9468
|
+
// src/utils/typingTarget.ts
|
|
9469
|
+
function isTypingTarget(target) {
|
|
9470
|
+
const element = asElement(target);
|
|
9471
|
+
if (!element) return false;
|
|
9472
|
+
if (element.isContentEditable) return true;
|
|
9473
|
+
return element.closest(TYPING_SELECTOR) !== null;
|
|
9474
|
+
}
|
|
9475
|
+
function ownsPlainKeys(target) {
|
|
9476
|
+
return isTypingTarget(target) || asElement(target)?.closest(KEY_OWNING_CONTROLS) != null;
|
|
9477
|
+
}
|
|
9478
|
+
var KEY_OWNING_CONTROLS = "[role='slider'],video[controls],audio[controls]";
|
|
9479
|
+
var TYPING_SELECTOR = [
|
|
9480
|
+
"input",
|
|
9481
|
+
"textarea",
|
|
9482
|
+
"select",
|
|
9483
|
+
"[contenteditable]:not([contenteditable='false'])",
|
|
9484
|
+
"[role='textbox']",
|
|
9485
|
+
"[role='searchbox']",
|
|
9486
|
+
"[role='combobox']",
|
|
9487
|
+
// A switch is a button in the DOM, but the native checkbox it replaces was an
|
|
9488
|
+
// `input` and matched above. Without this row the shared Toggle would let a
|
|
9489
|
+
// global shortcut claim Space instead of flipping the control (KTD13).
|
|
9490
|
+
"[role='switch']",
|
|
9491
|
+
"[role='dialog'][aria-modal='true']",
|
|
9492
|
+
".cm-editor"
|
|
9493
|
+
].join(",");
|
|
9494
|
+
function asElement(target) {
|
|
9495
|
+
if (!target || typeof target !== "object") return null;
|
|
9496
|
+
const candidate = target;
|
|
9497
|
+
return typeof candidate.closest === "function" ? target : null;
|
|
9498
|
+
}
|
|
9499
|
+
|
|
9500
|
+
// src/utils/studioPendingEdits.ts
|
|
9501
|
+
var STUDIO_FLUSH_PENDING_EDITS_EVENT = "hf-studio-flush-pending-edits";
|
|
9502
|
+
var pendingEdits = /* @__PURE__ */ new Map();
|
|
9503
|
+
var historyClaims = () => 0;
|
|
9504
|
+
function setStudioPendingEditClaimClock(read2) {
|
|
9505
|
+
historyClaims = read2 ?? (() => 0);
|
|
9506
|
+
}
|
|
9507
|
+
var NOT_SAVED = () => Promise.resolve(false);
|
|
9508
|
+
var adopting = null;
|
|
9509
|
+
function adoptingStudioPendingEdit() {
|
|
9510
|
+
return adopting;
|
|
9511
|
+
}
|
|
9512
|
+
function waitForPostBlurEffects() {
|
|
9513
|
+
return new Promise((resolve) => setTimeout(resolve, 0));
|
|
9514
|
+
}
|
|
9515
|
+
function inspectDrainFailures(results) {
|
|
9516
|
+
let firstFailure;
|
|
9517
|
+
for (const result of results) {
|
|
9518
|
+
if (result.status !== "rejected") continue;
|
|
9519
|
+
if (result.reason instanceof StudioFileConflictError) return { conflict: result.reason };
|
|
9520
|
+
firstFailure ??= result;
|
|
9521
|
+
}
|
|
9522
|
+
return { firstFailure };
|
|
9523
|
+
}
|
|
9524
|
+
function focusedField() {
|
|
9525
|
+
const active = document.activeElement;
|
|
9526
|
+
return active instanceof HTMLElement && isTypingTarget(active) ? active : null;
|
|
9527
|
+
}
|
|
9528
|
+
function hasStudioPendingEdits() {
|
|
9529
|
+
return pendingEdits.size > 0 || focusedField() !== null;
|
|
9530
|
+
}
|
|
9531
|
+
function isStudioEditSaving() {
|
|
9532
|
+
return pendingEdits.size > 0;
|
|
9533
|
+
}
|
|
9534
|
+
function afterStudioPendingEdits(run) {
|
|
9535
|
+
let waiting = true;
|
|
9536
|
+
const check = () => {
|
|
9537
|
+
if (!waiting) return;
|
|
9538
|
+
if (isStudioEditSaving()) {
|
|
9539
|
+
void Promise.allSettled([...pendingEdits.keys()]).then(check);
|
|
9540
|
+
return;
|
|
9541
|
+
}
|
|
9542
|
+
waiting = false;
|
|
9543
|
+
run();
|
|
9544
|
+
};
|
|
9545
|
+
check();
|
|
9546
|
+
return () => {
|
|
9547
|
+
waiting = false;
|
|
9548
|
+
};
|
|
9549
|
+
}
|
|
9550
|
+
function trackStudioPendingEdit(result) {
|
|
9551
|
+
if (!result) return void 0;
|
|
9552
|
+
const promise = Promise.resolve(result);
|
|
9553
|
+
if (adopting) return promise;
|
|
9554
|
+
pendingEdits.set(promise, {
|
|
9555
|
+
revert: null,
|
|
9556
|
+
landed: NOT_SAVED,
|
|
9557
|
+
redraws: [],
|
|
9558
|
+
showAgain: null,
|
|
9559
|
+
claimsAtBegin: historyClaims()
|
|
9560
|
+
});
|
|
9561
|
+
promise.then(
|
|
9562
|
+
() => pendingEdits.delete(promise),
|
|
9563
|
+
() => pendingEdits.delete(promise)
|
|
9564
|
+
);
|
|
9565
|
+
return promise;
|
|
9566
|
+
}
|
|
9567
|
+
function trackedStudioEdit(edit) {
|
|
9568
|
+
return (...args) => {
|
|
9569
|
+
const result = edit(...args);
|
|
9570
|
+
if (result instanceof Promise) trackStudioPendingEdit(result);
|
|
9571
|
+
return result;
|
|
9572
|
+
};
|
|
9573
|
+
}
|
|
9574
|
+
function beginStudioPendingEdit(revert) {
|
|
9575
|
+
let settle;
|
|
9576
|
+
const promise = trackStudioPendingEdit(new Promise((resolve) => settle = resolve));
|
|
9577
|
+
const entry2 = pendingEdits.get(promise);
|
|
9578
|
+
entry2.revert = revert;
|
|
9579
|
+
let landed = Promise.resolve(false);
|
|
9580
|
+
let saved2 = false;
|
|
9581
|
+
entry2.landed = () => landed;
|
|
9582
|
+
const inFlight = {
|
|
9583
|
+
reverted: () => entry2.revert === null && revert !== null,
|
|
9584
|
+
within(run) {
|
|
9585
|
+
const outer = adopting;
|
|
9586
|
+
adopting = inFlight;
|
|
9587
|
+
try {
|
|
9588
|
+
return run();
|
|
9589
|
+
} finally {
|
|
9590
|
+
adopting = outer;
|
|
9591
|
+
}
|
|
9592
|
+
},
|
|
9593
|
+
drawUnlessUndone(draw) {
|
|
9594
|
+
if (inFlight.reverted()) entry2.redraws.push(draw);
|
|
9595
|
+
else draw();
|
|
9596
|
+
},
|
|
9597
|
+
drawKeepingUndone(draw) {
|
|
9598
|
+
if (!inFlight.reverted()) return draw();
|
|
9599
|
+
entry2.showAgain?.();
|
|
9600
|
+
try {
|
|
9601
|
+
return draw();
|
|
9602
|
+
} finally {
|
|
9603
|
+
entry2.showAgain = revert();
|
|
9604
|
+
}
|
|
9605
|
+
},
|
|
9606
|
+
markSaved: () => void (saved2 = true)
|
|
9607
|
+
};
|
|
9608
|
+
return {
|
|
9609
|
+
settle,
|
|
9610
|
+
reverted: inFlight.reverted,
|
|
9611
|
+
// Only what `start` registers synchronously is adopted; a later write joins only through `within`.
|
|
9612
|
+
adopt(start) {
|
|
9613
|
+
try {
|
|
9614
|
+
const committed = inFlight.within(start);
|
|
9615
|
+
landed = Promise.resolve(committed).then(
|
|
9616
|
+
() => true,
|
|
9617
|
+
() => saved2
|
|
9618
|
+
);
|
|
9619
|
+
return committed;
|
|
9620
|
+
} catch (error) {
|
|
9621
|
+
settle();
|
|
9622
|
+
throw error;
|
|
9623
|
+
}
|
|
9624
|
+
}
|
|
9625
|
+
};
|
|
9626
|
+
}
|
|
9627
|
+
function paintBackNewestStudioPendingEdit() {
|
|
9628
|
+
const newest = [...pendingEdits.values()].at(-1);
|
|
9629
|
+
const revert = newest?.revert;
|
|
9630
|
+
if (!newest || !revert) return null;
|
|
9631
|
+
newest.revert = null;
|
|
9632
|
+
newest.showAgain = revert();
|
|
9633
|
+
return {
|
|
9634
|
+
showAgain: () => {
|
|
9635
|
+
newest.showAgain?.();
|
|
9636
|
+
for (const redraw of newest.redraws.splice(0)) redraw();
|
|
9637
|
+
},
|
|
9638
|
+
landed: newest.landed,
|
|
9639
|
+
claimsAtBegin: newest.claimsAtBegin
|
|
9640
|
+
};
|
|
9641
|
+
}
|
|
9642
|
+
function revertNewestStudioPendingEdit() {
|
|
9643
|
+
return paintBackNewestStudioPendingEdit()?.showAgain ?? null;
|
|
9644
|
+
}
|
|
9645
|
+
async function flushStudioPendingEdits({
|
|
9646
|
+
onlyCurrent = false
|
|
9647
|
+
} = {}) {
|
|
9648
|
+
const active = focusedField();
|
|
9649
|
+
if (active) {
|
|
9650
|
+
active.blur();
|
|
9651
|
+
await Promise.resolve();
|
|
9652
|
+
await waitForPostBlurEffects();
|
|
9653
|
+
}
|
|
9654
|
+
const detail = { promises: [] };
|
|
9655
|
+
window.dispatchEvent(
|
|
9656
|
+
new CustomEvent(STUDIO_FLUSH_PENDING_EDITS_EVENT, { detail })
|
|
9657
|
+
);
|
|
9658
|
+
const current = onlyCurrent ? new Set(pendingEdits.keys()) : null;
|
|
9659
|
+
const waiting = () => [...pendingEdits.keys()].filter((edit) => !current || current.has(edit));
|
|
9660
|
+
let conflict;
|
|
9661
|
+
let firstFailure;
|
|
9662
|
+
while (detail.promises.length > 0 || waiting().length > 0) {
|
|
9663
|
+
const promises = [...detail.promises, ...waiting()];
|
|
9664
|
+
detail.promises = [];
|
|
9665
|
+
const batchFailures = inspectDrainFailures(await Promise.allSettled(promises));
|
|
9666
|
+
conflict ??= batchFailures.conflict;
|
|
9667
|
+
firstFailure ??= batchFailures.firstFailure;
|
|
9668
|
+
}
|
|
9669
|
+
if (conflict) return { status: "conflict", error: conflict };
|
|
9670
|
+
return firstFailure ? { status: "failed", error: firstFailure.reason } : { status: "clean" };
|
|
9671
|
+
}
|
|
9672
|
+
function addStudioPendingEditFlushListener(handler) {
|
|
9673
|
+
const listener2 = (event) => {
|
|
9674
|
+
const detail = event.detail;
|
|
9675
|
+
if (!detail?.promises) return;
|
|
9676
|
+
const promise = trackStudioPendingEdit(handler());
|
|
9677
|
+
if (promise) detail.promises.push(promise);
|
|
9678
|
+
};
|
|
9679
|
+
window.addEventListener(STUDIO_FLUSH_PENDING_EDITS_EVENT, listener2);
|
|
9680
|
+
return () => window.removeEventListener(STUDIO_FLUSH_PENDING_EDITS_EVENT, listener2);
|
|
9681
|
+
}
|
|
9682
|
+
|
|
9228
9683
|
// src/player/components/timelineClipDragCommit.ts
|
|
9229
9684
|
var keyOf6 = (e) => e.key ?? e.id;
|
|
9230
9685
|
var round3 = (v) => Math.round(v * 1e3) / 1e3;
|
|
@@ -9237,11 +9692,11 @@ function moveStoreUpdates(edit) {
|
|
|
9237
9692
|
detach: audioGroup === null
|
|
9238
9693
|
};
|
|
9239
9694
|
}
|
|
9240
|
-
function persistMoveEdits(edits, deps, coalesceKey, operation = "timing"
|
|
9695
|
+
function persistMoveEdits(edits, deps, coalesceKey, operation = "timing") {
|
|
9241
9696
|
if (edits.length === 0) return Promise.resolve(true);
|
|
9242
9697
|
edits = edits.map((e) => {
|
|
9243
|
-
const
|
|
9244
|
-
return
|
|
9698
|
+
const start2 = clampToHostStart(e.element, e.updates.start);
|
|
9699
|
+
return start2 === e.updates.start ? e : { ...e, updates: { ...e.updates, start: start2 } };
|
|
9245
9700
|
});
|
|
9246
9701
|
const { updateElement, onMoveElement, onMoveElements } = deps;
|
|
9247
9702
|
if (!onMoveElements) {
|
|
@@ -9279,31 +9734,44 @@ function persistMoveEdits(edits, deps, coalesceKey, operation = "timing", coales
|
|
|
9279
9734
|
}
|
|
9280
9735
|
if (Object.keys(updates).length) updateElement(key2, updates);
|
|
9281
9736
|
};
|
|
9282
|
-
|
|
9737
|
+
const applyEdits = (reassert = false) => batchElementUpdates(() => edits.forEach((e) => applyEdit(e, reassert)));
|
|
9738
|
+
applyEdits();
|
|
9283
9739
|
const persistEdits = edits.map(
|
|
9284
9740
|
(e) => e.persistTrack == null || e.persistTrack === e.updates.track ? e : { element: e.element, updates: { ...e.updates, track: e.persistTrack } }
|
|
9285
9741
|
);
|
|
9286
|
-
const
|
|
9287
|
-
|
|
9742
|
+
const restorePrev = () => batchElementUpdates(() => {
|
|
9743
|
+
for (const p of prev) {
|
|
9744
|
+
const { audioGroup, ...timing } = p.updates;
|
|
9745
|
+
const updates = {};
|
|
9746
|
+
if (isLatestTimelineOptimisticGesture(updateElement, revision, p.key)) {
|
|
9747
|
+
Object.assign(updates, timing);
|
|
9748
|
+
}
|
|
9749
|
+
if (isLatestTimelineOptimisticGesture(updateElement, membershipRevision, p.key, "membership"))
|
|
9750
|
+
updates.audioGroup = audioGroup;
|
|
9751
|
+
if (Object.keys(updates).length) updateElement(p.key, updates);
|
|
9752
|
+
}
|
|
9753
|
+
});
|
|
9754
|
+
const saving = onMoveElements ? beginStudioPendingEdit(() => {
|
|
9755
|
+
restorePrev();
|
|
9756
|
+
return () => applyEdits(true);
|
|
9757
|
+
}) : null;
|
|
9758
|
+
const start = () => onMoveElements ? onMoveElements(persistEdits, coalesceKey, operation) : Promise.all(
|
|
9759
|
+
persistEdits.map((e) => Promise.resolve(onMoveElement?.(e.element, e.updates)))
|
|
9760
|
+
);
|
|
9761
|
+
const persisted = saving ? saving.adopt(start) : start();
|
|
9762
|
+
const done = Promise.resolve(persisted).then(
|
|
9288
9763
|
() => {
|
|
9289
|
-
|
|
9764
|
+
if (!saving?.reverted()) applyEdits(true);
|
|
9290
9765
|
return true;
|
|
9291
9766
|
},
|
|
9292
9767
|
(error) => {
|
|
9293
|
-
|
|
9294
|
-
const { audioGroup, ...timing } = p.updates;
|
|
9295
|
-
const updates = {};
|
|
9296
|
-
if (isLatestTimelineOptimisticGesture(updateElement, revision, p.key)) {
|
|
9297
|
-
Object.assign(updates, timing);
|
|
9298
|
-
}
|
|
9299
|
-
if (isLatestTimelineOptimisticGesture(updateElement, membershipRevision, p.key, "membership"))
|
|
9300
|
-
updates.audioGroup = audioGroup;
|
|
9301
|
-
if (Object.keys(updates).length) updateElement(p.key, updates);
|
|
9302
|
-
}
|
|
9768
|
+
if (!saving?.reverted()) restorePrev();
|
|
9303
9769
|
console.error("[Timeline] Failed to persist clip edits", error);
|
|
9304
9770
|
return false;
|
|
9305
9771
|
}
|
|
9306
9772
|
);
|
|
9773
|
+
saving?.settle(done);
|
|
9774
|
+
return done;
|
|
9307
9775
|
}
|
|
9308
9776
|
function resolveMultiSelection(drag, deps) {
|
|
9309
9777
|
const dragKey = keyOf6(drag.element);
|
|
@@ -9488,7 +9956,7 @@ function commitTrackInsert(drag, deps, multi) {
|
|
|
9488
9956
|
deps
|
|
9489
9957
|
).catch(() => void 0);
|
|
9490
9958
|
}
|
|
9491
|
-
function commitZMirrorLaneMove(element, move, deps, coalesceKey
|
|
9959
|
+
function commitZMirrorLaneMove(element, move, deps, coalesceKey) {
|
|
9492
9960
|
if (move.kind === "move") {
|
|
9493
9961
|
const edit = {
|
|
9494
9962
|
element,
|
|
@@ -9496,14 +9964,14 @@ function commitZMirrorLaneMove(element, move, deps, coalesceKey, coalesceMs) {
|
|
|
9496
9964
|
persistTrack: move.persistTrack
|
|
9497
9965
|
};
|
|
9498
9966
|
return refreshAfterDurableLaneMove(
|
|
9499
|
-
persistMoveEdits([edit], deps, coalesceKey, "lane-reorder"
|
|
9967
|
+
persistMoveEdits([edit], deps, coalesceKey, "lane-reorder"),
|
|
9500
9968
|
deps
|
|
9501
9969
|
);
|
|
9502
9970
|
}
|
|
9503
9971
|
const built = buildTrackInsertEdits(element, element.start, move.insertRow, null, deps);
|
|
9504
9972
|
if (!built || built.edits.length === 0) return Promise.resolve(false);
|
|
9505
9973
|
return refreshAfterDurableLaneMove(
|
|
9506
|
-
persistMoveEdits(built.edits, deps, coalesceKey, "track-insert"
|
|
9974
|
+
persistMoveEdits(built.edits, deps, coalesceKey, "track-insert"),
|
|
9507
9975
|
deps
|
|
9508
9976
|
);
|
|
9509
9977
|
}
|
|
@@ -9539,13 +10007,15 @@ function commitTimelineGroupResize(session, updateElement, persist2) {
|
|
|
9539
10007
|
updateElement,
|
|
9540
10008
|
changes.map((change) => change.key)
|
|
9541
10009
|
);
|
|
9542
|
-
|
|
9543
|
-
|
|
9544
|
-
|
|
9545
|
-
|
|
9546
|
-
|
|
9547
|
-
|
|
9548
|
-
|
|
10010
|
+
batchElementUpdates(() => {
|
|
10011
|
+
for (const change of changes) {
|
|
10012
|
+
updateElement(change.key, {
|
|
10013
|
+
start: change.start,
|
|
10014
|
+
duration: change.duration,
|
|
10015
|
+
playbackStart: change.playbackStart
|
|
10016
|
+
});
|
|
10017
|
+
}
|
|
10018
|
+
});
|
|
9549
10019
|
if (!persist2) {
|
|
9550
10020
|
rollbackLatestTimelineOptimisticGesture(
|
|
9551
10021
|
updateElement,
|
|
@@ -9554,7 +10024,6 @@ function commitTimelineGroupResize(session, updateElement, persist2) {
|
|
|
9554
10024
|
);
|
|
9555
10025
|
return;
|
|
9556
10026
|
}
|
|
9557
|
-
const coalesceKey = `clip-group-resize:${changes.map((change) => change.key).join(":")}`;
|
|
9558
10027
|
Promise.resolve(
|
|
9559
10028
|
persist2(
|
|
9560
10029
|
changes.map((change) => ({
|
|
@@ -9562,8 +10031,7 @@ function commitTimelineGroupResize(session, updateElement, persist2) {
|
|
|
9562
10031
|
start: change.start,
|
|
9563
10032
|
duration: change.duration,
|
|
9564
10033
|
playbackStart: change.playbackStart
|
|
9565
|
-
}))
|
|
9566
|
-
{ coalesceKey }
|
|
10034
|
+
}))
|
|
9567
10035
|
)
|
|
9568
10036
|
).catch((error) => {
|
|
9569
10037
|
rollbackLatestTimelineOptimisticGesture(
|
|
@@ -9684,7 +10152,7 @@ function mountTimelineClipDragGestureLifecycle({
|
|
|
9684
10152
|
event.clientX - blocked.originClientX,
|
|
9685
10153
|
event.clientY - blocked.originClientY
|
|
9686
10154
|
);
|
|
9687
|
-
const threshold = blocked.intent === "move"
|
|
10155
|
+
const threshold = blocked.intent === "move" ? 4 : 2;
|
|
9688
10156
|
if (!blocked.started && distance < threshold) return;
|
|
9689
10157
|
if (!blocked.started) {
|
|
9690
10158
|
blocked.started = true;
|
|
@@ -10273,8 +10741,6 @@ function useTimelineClipDrag({
|
|
|
10273
10741
|
if (!element || !getClipCapabilities(element).canMove) return;
|
|
10274
10742
|
event.preventDefault();
|
|
10275
10743
|
event.stopImmediatePropagation();
|
|
10276
|
-
if (exceedsHandEditLimit(element, false, null))
|
|
10277
|
-
return void onBlockedEditAttemptRef.current?.(element, "edit-many");
|
|
10278
10744
|
setShowPopover(false);
|
|
10279
10745
|
setRangeSelectionRef.current?.(null);
|
|
10280
10746
|
const row = keyboardPickupInsertRow(
|
|
@@ -10544,6 +11010,36 @@ function useTimelineDragPreviewState(draggedClip, resizingClip, selectedElementI
|
|
|
10544
11010
|
|
|
10545
11011
|
// src/player/components/useTimelineClipContextMenu.ts
|
|
10546
11012
|
import { useCallback as useCallback12 } from "react";
|
|
11013
|
+
|
|
11014
|
+
// src/player/components/timelineLinkSelection.ts
|
|
11015
|
+
function selectClipWithLinks(key2, altKey, setSelectedElementId) {
|
|
11016
|
+
const state = usePlayerStore.getState();
|
|
11017
|
+
if (altKey || !isLinkedSelectionOn()) {
|
|
11018
|
+
state.setSelection([key2], key2);
|
|
11019
|
+
return;
|
|
11020
|
+
}
|
|
11021
|
+
setSelectedElementId(key2);
|
|
11022
|
+
const { selectedElementIds, elements } = usePlayerStore.getState();
|
|
11023
|
+
const expanded = expandToLinkedMembers(selectedElementIds, elements);
|
|
11024
|
+
if (expanded.size > selectedElementIds.size) state.setSelection(expanded, key2);
|
|
11025
|
+
}
|
|
11026
|
+
function toggleClipWithLinks(key2, altKey) {
|
|
11027
|
+
const state = usePlayerStore.getState();
|
|
11028
|
+
const element = state.elements.find((el) => (el.key ?? el.id) === key2);
|
|
11029
|
+
const members = element ? linkedMembersOf(element, state.elements, !altKey && isLinkedSelectionOn()) : [];
|
|
11030
|
+
const memberKeys = members.length > 0 ? members.map((el) => el.key ?? el.id) : [key2];
|
|
11031
|
+
const next = new Set(state.selectedElementIds);
|
|
11032
|
+
const removing = next.has(key2);
|
|
11033
|
+
for (const memberKey of memberKeys) {
|
|
11034
|
+
if (removing) next.delete(memberKey);
|
|
11035
|
+
else next.add(memberKey);
|
|
11036
|
+
}
|
|
11037
|
+
state.setSelection(next, removing ? state.selectedElementId : key2);
|
|
11038
|
+
const primary = usePlayerStore.getState().selectedElementId;
|
|
11039
|
+
return state.elements.find((el) => (el.key ?? el.id) === primary) ?? null;
|
|
11040
|
+
}
|
|
11041
|
+
|
|
11042
|
+
// src/player/components/useTimelineClipContextMenu.ts
|
|
10547
11043
|
function useClipContextMenu(onSelectElement, dismissGapMenu, setClipContextMenu) {
|
|
10548
11044
|
const selectedElementIds = usePlayerStore((s) => s.selectedElementIds);
|
|
10549
11045
|
const setSelectedElementId = usePlayerStore((s) => s.setSelectedElementId);
|
|
@@ -11620,391 +12116,6 @@ import { useCallback as useCallback17 } from "react";
|
|
|
11620
12116
|
|
|
11621
12117
|
// src/contexts/DomEditContext.tsx
|
|
11622
12118
|
import { useCallback as useCallback16, useContext as useContext3, useMemo as useMemo12, useRef as useRef17 } from "react";
|
|
11623
|
-
|
|
11624
|
-
// src/utils/studioSaveDiagnostics.ts
|
|
11625
|
-
var STUDIO_SAVE_ATTEMPT_PROPERTY = "__studioSaveAttempt";
|
|
11626
|
-
var StudioSaveHttpError = class extends Error {
|
|
11627
|
-
statusCode;
|
|
11628
|
-
alreadyToasted;
|
|
11629
|
-
constructor(message, statusCode, options = {}) {
|
|
11630
|
-
super(message);
|
|
11631
|
-
this.name = "StudioSaveHttpError";
|
|
11632
|
-
this.statusCode = statusCode;
|
|
11633
|
-
this.alreadyToasted = options.alreadyToasted ?? false;
|
|
11634
|
-
}
|
|
11635
|
-
};
|
|
11636
|
-
var StudioSaveNetworkError = class extends Error {
|
|
11637
|
-
constructor(message, options) {
|
|
11638
|
-
super(message, options);
|
|
11639
|
-
this.name = "StudioSaveNetworkError";
|
|
11640
|
-
}
|
|
11641
|
-
};
|
|
11642
|
-
var StudioFileConflictError = class extends StudioSaveHttpError {
|
|
11643
|
-
filePath;
|
|
11644
|
-
currentVersion;
|
|
11645
|
-
currentContent;
|
|
11646
|
-
attemptedContent;
|
|
11647
|
-
constructor(input) {
|
|
11648
|
-
super(`Save conflict: ${input.filePath} changed outside this Studio session`, 409);
|
|
11649
|
-
this.name = "StudioFileConflictError";
|
|
11650
|
-
this.filePath = input.filePath;
|
|
11651
|
-
this.currentVersion = input.currentVersion;
|
|
11652
|
-
this.currentContent = input.currentContent;
|
|
11653
|
-
this.attemptedContent = input.attemptedContent;
|
|
11654
|
-
}
|
|
11655
|
-
};
|
|
11656
|
-
function readNumericProperty(value, key2) {
|
|
11657
|
-
const record = value;
|
|
11658
|
-
const property = record[key2];
|
|
11659
|
-
return typeof property === "number" && Number.isFinite(property) ? property : void 0;
|
|
11660
|
-
}
|
|
11661
|
-
function createStudioSaveAbortError() {
|
|
11662
|
-
if (typeof DOMException !== "undefined") return new DOMException("Save aborted", "AbortError");
|
|
11663
|
-
const error = new Error("Save aborted");
|
|
11664
|
-
error.name = "AbortError";
|
|
11665
|
-
return error;
|
|
11666
|
-
}
|
|
11667
|
-
function throwIfStudioSaveAborted(signal) {
|
|
11668
|
-
if (signal?.aborted) throw createStudioSaveAbortError();
|
|
11669
|
-
}
|
|
11670
|
-
function attachStudioSaveAttempt(error, attempt) {
|
|
11671
|
-
if (!error || typeof error !== "object") return error;
|
|
11672
|
-
try {
|
|
11673
|
-
Object.defineProperty(error, STUDIO_SAVE_ATTEMPT_PROPERTY, {
|
|
11674
|
-
value: attempt,
|
|
11675
|
-
configurable: true
|
|
11676
|
-
});
|
|
11677
|
-
} catch {
|
|
11678
|
-
}
|
|
11679
|
-
return error;
|
|
11680
|
-
}
|
|
11681
|
-
function getStudioSaveErrorMessage(error) {
|
|
11682
|
-
if (error instanceof Error && error.message) return error.message;
|
|
11683
|
-
if (typeof error === "string" && error.trim()) return error;
|
|
11684
|
-
return "Unknown save failure";
|
|
11685
|
-
}
|
|
11686
|
-
function markStudioSaveErrorAlreadyToasted(error) {
|
|
11687
|
-
if (!error || typeof error !== "object") return error;
|
|
11688
|
-
try {
|
|
11689
|
-
Object.defineProperty(error, "alreadyToasted", { value: true, configurable: true });
|
|
11690
|
-
} catch {
|
|
11691
|
-
}
|
|
11692
|
-
return error;
|
|
11693
|
-
}
|
|
11694
|
-
function isStudioSaveErrorAlreadyToasted(error) {
|
|
11695
|
-
if (!error || typeof error !== "object") return false;
|
|
11696
|
-
if (error.alreadyToasted === true) return true;
|
|
11697
|
-
const cause = error.cause;
|
|
11698
|
-
return cause !== error && isStudioSaveErrorAlreadyToasted(cause);
|
|
11699
|
-
}
|
|
11700
|
-
function getStudioSaveStatusCode(error) {
|
|
11701
|
-
if (!error || typeof error !== "object") return void 0;
|
|
11702
|
-
const direct = readNumericProperty(error, "statusCode") ?? readNumericProperty(error, "status") ?? readNumericProperty(error, "status_code");
|
|
11703
|
-
if (direct != null) return direct;
|
|
11704
|
-
const cause = error.cause;
|
|
11705
|
-
if (cause && cause !== error) return getStudioSaveStatusCode(cause);
|
|
11706
|
-
return void 0;
|
|
11707
|
-
}
|
|
11708
|
-
function getStudioSaveAttempt(error) {
|
|
11709
|
-
if (!error || typeof error !== "object") return void 0;
|
|
11710
|
-
const direct = readNumericProperty(error, STUDIO_SAVE_ATTEMPT_PROPERTY);
|
|
11711
|
-
if (direct != null) return direct;
|
|
11712
|
-
const cause = error.cause;
|
|
11713
|
-
if (cause && cause !== error) return getStudioSaveAttempt(cause);
|
|
11714
|
-
return void 0;
|
|
11715
|
-
}
|
|
11716
|
-
function isStudioSaveAbortError(error) {
|
|
11717
|
-
return error instanceof Error && error.name === "AbortError";
|
|
11718
|
-
}
|
|
11719
|
-
function isRetryableStudioSaveError(error) {
|
|
11720
|
-
if (isStudioSaveAbortError(error)) return false;
|
|
11721
|
-
if (error instanceof StudioSaveNetworkError) return true;
|
|
11722
|
-
const statusCode = getStudioSaveStatusCode(error);
|
|
11723
|
-
if (statusCode == null) return false;
|
|
11724
|
-
return statusCode === 408 || statusCode === 425 || statusCode === 429 || statusCode >= 500;
|
|
11725
|
-
}
|
|
11726
|
-
function buildStudioSaveFailureProperties(input) {
|
|
11727
|
-
const statusCode = input.statusCode ?? getStudioSaveStatusCode(input.error) ?? null;
|
|
11728
|
-
const attempt = input.attempt ?? getStudioSaveAttempt(input.error) ?? void 0;
|
|
11729
|
-
return {
|
|
11730
|
-
source: input.source,
|
|
11731
|
-
error_message: getStudioSaveErrorMessage(input.error),
|
|
11732
|
-
status_code: statusCode,
|
|
11733
|
-
file_path: input.filePath ?? input.targetSourceFile ?? void 0,
|
|
11734
|
-
mutation_type: input.mutationType ?? void 0,
|
|
11735
|
-
attempt,
|
|
11736
|
-
label: input.label ?? void 0,
|
|
11737
|
-
target_id: input.targetId ?? void 0,
|
|
11738
|
-
target_selector: input.targetSelector ?? void 0,
|
|
11739
|
-
target_source_file: input.targetSourceFile ?? void 0,
|
|
11740
|
-
block_detail: gsapBlockDetail(input.error)
|
|
11741
|
-
};
|
|
11742
|
-
}
|
|
11743
|
-
function gsapBlockDetail(error) {
|
|
11744
|
-
if (!(error instanceof Error) || error.name !== "GsapEditBlockedError") return void 0;
|
|
11745
|
-
const detail = error.detail;
|
|
11746
|
-
return typeof detail === "string" ? detail : void 0;
|
|
11747
|
-
}
|
|
11748
|
-
function trackStudioSaveFailure(input) {
|
|
11749
|
-
trackStudioEvent("save_failure", buildStudioSaveFailureProperties(input));
|
|
11750
|
-
}
|
|
11751
|
-
function trackStudioEditBlocked(input) {
|
|
11752
|
-
trackStudioEvent("edit_blocked", buildStudioSaveFailureProperties(input));
|
|
11753
|
-
}
|
|
11754
|
-
async function createStudioSaveHttpError(response, fallbackMessage, options = {}) {
|
|
11755
|
-
let body = "";
|
|
11756
|
-
try {
|
|
11757
|
-
body = await response.text();
|
|
11758
|
-
} catch {
|
|
11759
|
-
body = "";
|
|
11760
|
-
}
|
|
11761
|
-
const detail = body.trim().slice(0, 300);
|
|
11762
|
-
const message = detail ? `${fallbackMessage} (${response.status}): ${detail}` : `${fallbackMessage} (${response.status})`;
|
|
11763
|
-
return new StudioSaveHttpError(message, response.status, options);
|
|
11764
|
-
}
|
|
11765
|
-
async function retryStudioSave(operation, options = {}) {
|
|
11766
|
-
const retries = options.retries ?? 3;
|
|
11767
|
-
const baseDelayMs = options.baseDelayMs ?? 500;
|
|
11768
|
-
const maxDelayMs = options.maxDelayMs ?? 8e3;
|
|
11769
|
-
const jitterRatio = options.jitterRatio ?? 0.25;
|
|
11770
|
-
const random = options.random ?? Math.random;
|
|
11771
|
-
const shouldRetry = options.shouldRetry ?? isRetryableStudioSaveError;
|
|
11772
|
-
const sleep = options.sleep ?? ((delayMs, signal) => new Promise((resolve, reject) => {
|
|
11773
|
-
throwIfStudioSaveAborted(signal);
|
|
11774
|
-
const onAbort = () => {
|
|
11775
|
-
globalThis.clearTimeout(timeout);
|
|
11776
|
-
reject(createStudioSaveAbortError());
|
|
11777
|
-
};
|
|
11778
|
-
const timeout = globalThis.setTimeout(() => {
|
|
11779
|
-
signal?.removeEventListener("abort", onAbort);
|
|
11780
|
-
resolve();
|
|
11781
|
-
}, delayMs);
|
|
11782
|
-
signal?.addEventListener("abort", onAbort, { once: true });
|
|
11783
|
-
}));
|
|
11784
|
-
const maxAttempts = retries + 1;
|
|
11785
|
-
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
11786
|
-
try {
|
|
11787
|
-
throwIfStudioSaveAborted(options.signal);
|
|
11788
|
-
return await operation(attempt);
|
|
11789
|
-
} catch (error) {
|
|
11790
|
-
const failure = attachStudioSaveAttempt(error, attempt);
|
|
11791
|
-
if (attempt >= maxAttempts || !shouldRetry(failure, attempt)) throw failure;
|
|
11792
|
-
const retryIndex = attempt - 1;
|
|
11793
|
-
const exponentialDelay = Math.min(baseDelayMs * 2 ** retryIndex, maxDelayMs);
|
|
11794
|
-
const jitterSpan = exponentialDelay * jitterRatio;
|
|
11795
|
-
const jitteredDelay = Math.round(exponentialDelay + (random() * 2 - 1) * jitterSpan);
|
|
11796
|
-
const delayMs = Math.max(0, Math.min(maxDelayMs, jitteredDelay));
|
|
11797
|
-
await sleep(delayMs, options.signal);
|
|
11798
|
-
}
|
|
11799
|
-
}
|
|
11800
|
-
throw new Error("Save retry loop exited unexpectedly");
|
|
11801
|
-
}
|
|
11802
|
-
|
|
11803
|
-
// src/utils/typingTarget.ts
|
|
11804
|
-
function isTypingTarget(target) {
|
|
11805
|
-
const element = asElement(target);
|
|
11806
|
-
if (!element) return false;
|
|
11807
|
-
if (element.isContentEditable) return true;
|
|
11808
|
-
return element.closest(TYPING_SELECTOR) !== null;
|
|
11809
|
-
}
|
|
11810
|
-
function ownsPlainKeys(target) {
|
|
11811
|
-
return isTypingTarget(target) || asElement(target)?.closest(KEY_OWNING_CONTROLS) != null;
|
|
11812
|
-
}
|
|
11813
|
-
var KEY_OWNING_CONTROLS = "[role='slider'],video[controls],audio[controls]";
|
|
11814
|
-
var TYPING_SELECTOR = [
|
|
11815
|
-
"input",
|
|
11816
|
-
"textarea",
|
|
11817
|
-
"select",
|
|
11818
|
-
"[contenteditable]:not([contenteditable='false'])",
|
|
11819
|
-
"[role='textbox']",
|
|
11820
|
-
"[role='searchbox']",
|
|
11821
|
-
"[role='combobox']",
|
|
11822
|
-
// A switch is a button in the DOM, but the native checkbox it replaces was an
|
|
11823
|
-
// `input` and matched above. Without this row the shared Toggle would let a
|
|
11824
|
-
// global shortcut claim Space instead of flipping the control (KTD13).
|
|
11825
|
-
"[role='switch']",
|
|
11826
|
-
"[role='dialog'][aria-modal='true']",
|
|
11827
|
-
".cm-editor"
|
|
11828
|
-
].join(",");
|
|
11829
|
-
function asElement(target) {
|
|
11830
|
-
if (!target || typeof target !== "object") return null;
|
|
11831
|
-
const candidate = target;
|
|
11832
|
-
return typeof candidate.closest === "function" ? target : null;
|
|
11833
|
-
}
|
|
11834
|
-
|
|
11835
|
-
// src/utils/studioPendingEdits.ts
|
|
11836
|
-
var STUDIO_FLUSH_PENDING_EDITS_EVENT = "hf-studio-flush-pending-edits";
|
|
11837
|
-
var pendingEdits = /* @__PURE__ */ new Map();
|
|
11838
|
-
var NOT_SAVED = () => Promise.resolve(false);
|
|
11839
|
-
var adopting = null;
|
|
11840
|
-
function adoptingStudioPendingEdit() {
|
|
11841
|
-
return adopting;
|
|
11842
|
-
}
|
|
11843
|
-
function waitForPostBlurEffects() {
|
|
11844
|
-
return new Promise((resolve) => setTimeout(resolve, 0));
|
|
11845
|
-
}
|
|
11846
|
-
function inspectDrainFailures(results) {
|
|
11847
|
-
let firstFailure;
|
|
11848
|
-
for (const result of results) {
|
|
11849
|
-
if (result.status !== "rejected") continue;
|
|
11850
|
-
if (result.reason instanceof StudioFileConflictError) return { conflict: result.reason };
|
|
11851
|
-
firstFailure ??= result;
|
|
11852
|
-
}
|
|
11853
|
-
return { firstFailure };
|
|
11854
|
-
}
|
|
11855
|
-
function focusedField() {
|
|
11856
|
-
const active = document.activeElement;
|
|
11857
|
-
return active instanceof HTMLElement && isTypingTarget(active) ? active : null;
|
|
11858
|
-
}
|
|
11859
|
-
function hasStudioPendingEdits() {
|
|
11860
|
-
return pendingEdits.size > 0 || focusedField() !== null;
|
|
11861
|
-
}
|
|
11862
|
-
function isStudioEditSaving() {
|
|
11863
|
-
return pendingEdits.size > 0;
|
|
11864
|
-
}
|
|
11865
|
-
function afterStudioPendingEdits(run) {
|
|
11866
|
-
let waiting = true;
|
|
11867
|
-
const check = () => {
|
|
11868
|
-
if (!waiting) return;
|
|
11869
|
-
if (isStudioEditSaving()) {
|
|
11870
|
-
void Promise.allSettled([...pendingEdits.keys()]).then(check);
|
|
11871
|
-
return;
|
|
11872
|
-
}
|
|
11873
|
-
waiting = false;
|
|
11874
|
-
run();
|
|
11875
|
-
};
|
|
11876
|
-
check();
|
|
11877
|
-
return () => {
|
|
11878
|
-
waiting = false;
|
|
11879
|
-
};
|
|
11880
|
-
}
|
|
11881
|
-
function trackStudioPendingEdit(result) {
|
|
11882
|
-
if (!result) return void 0;
|
|
11883
|
-
const promise = Promise.resolve(result);
|
|
11884
|
-
if (adopting) return promise;
|
|
11885
|
-
pendingEdits.set(promise, { revert: null, landed: NOT_SAVED, redraws: [], showAgain: null });
|
|
11886
|
-
promise.then(
|
|
11887
|
-
() => pendingEdits.delete(promise),
|
|
11888
|
-
() => pendingEdits.delete(promise)
|
|
11889
|
-
);
|
|
11890
|
-
return promise;
|
|
11891
|
-
}
|
|
11892
|
-
function trackedStudioEdit(edit) {
|
|
11893
|
-
return (...args) => {
|
|
11894
|
-
const result = edit(...args);
|
|
11895
|
-
if (result instanceof Promise) trackStudioPendingEdit(result);
|
|
11896
|
-
return result;
|
|
11897
|
-
};
|
|
11898
|
-
}
|
|
11899
|
-
function beginStudioPendingEdit(revert) {
|
|
11900
|
-
let settle;
|
|
11901
|
-
const promise = trackStudioPendingEdit(new Promise((resolve) => settle = resolve));
|
|
11902
|
-
const entry2 = pendingEdits.get(promise);
|
|
11903
|
-
entry2.revert = revert;
|
|
11904
|
-
let landed = Promise.resolve(false);
|
|
11905
|
-
let saved2 = false;
|
|
11906
|
-
entry2.landed = () => landed;
|
|
11907
|
-
const inFlight = {
|
|
11908
|
-
reverted: () => entry2.revert === null && revert !== null,
|
|
11909
|
-
within(run) {
|
|
11910
|
-
const outer = adopting;
|
|
11911
|
-
adopting = inFlight;
|
|
11912
|
-
try {
|
|
11913
|
-
return run();
|
|
11914
|
-
} finally {
|
|
11915
|
-
adopting = outer;
|
|
11916
|
-
}
|
|
11917
|
-
},
|
|
11918
|
-
drawUnlessUndone(draw) {
|
|
11919
|
-
if (inFlight.reverted()) entry2.redraws.push(draw);
|
|
11920
|
-
else draw();
|
|
11921
|
-
},
|
|
11922
|
-
drawKeepingUndone(draw) {
|
|
11923
|
-
if (!inFlight.reverted()) return draw();
|
|
11924
|
-
entry2.showAgain?.();
|
|
11925
|
-
try {
|
|
11926
|
-
return draw();
|
|
11927
|
-
} finally {
|
|
11928
|
-
entry2.showAgain = revert();
|
|
11929
|
-
}
|
|
11930
|
-
},
|
|
11931
|
-
markSaved: () => void (saved2 = true)
|
|
11932
|
-
};
|
|
11933
|
-
return {
|
|
11934
|
-
settle,
|
|
11935
|
-
reverted: inFlight.reverted,
|
|
11936
|
-
// Only what `start` registers synchronously is adopted; a later write joins only through `within`.
|
|
11937
|
-
adopt(start) {
|
|
11938
|
-
try {
|
|
11939
|
-
const committed = inFlight.within(start);
|
|
11940
|
-
landed = Promise.resolve(committed).then(
|
|
11941
|
-
() => true,
|
|
11942
|
-
() => saved2
|
|
11943
|
-
);
|
|
11944
|
-
return committed;
|
|
11945
|
-
} catch (error) {
|
|
11946
|
-
settle();
|
|
11947
|
-
throw error;
|
|
11948
|
-
}
|
|
11949
|
-
}
|
|
11950
|
-
};
|
|
11951
|
-
}
|
|
11952
|
-
function paintBackNewestStudioPendingEdit() {
|
|
11953
|
-
const newest = [...pendingEdits.values()].at(-1);
|
|
11954
|
-
const revert = newest?.revert;
|
|
11955
|
-
if (!newest || !revert) return null;
|
|
11956
|
-
newest.revert = null;
|
|
11957
|
-
newest.showAgain = revert();
|
|
11958
|
-
return {
|
|
11959
|
-
showAgain: () => {
|
|
11960
|
-
newest.showAgain?.();
|
|
11961
|
-
for (const redraw of newest.redraws.splice(0)) redraw();
|
|
11962
|
-
},
|
|
11963
|
-
landed: newest.landed
|
|
11964
|
-
};
|
|
11965
|
-
}
|
|
11966
|
-
function revertNewestStudioPendingEdit() {
|
|
11967
|
-
return paintBackNewestStudioPendingEdit()?.showAgain ?? null;
|
|
11968
|
-
}
|
|
11969
|
-
async function flushStudioPendingEdits({
|
|
11970
|
-
onlyCurrent = false
|
|
11971
|
-
} = {}) {
|
|
11972
|
-
const active = focusedField();
|
|
11973
|
-
if (active) {
|
|
11974
|
-
active.blur();
|
|
11975
|
-
await Promise.resolve();
|
|
11976
|
-
await waitForPostBlurEffects();
|
|
11977
|
-
}
|
|
11978
|
-
const detail = { promises: [] };
|
|
11979
|
-
window.dispatchEvent(
|
|
11980
|
-
new CustomEvent(STUDIO_FLUSH_PENDING_EDITS_EVENT, { detail })
|
|
11981
|
-
);
|
|
11982
|
-
const current = onlyCurrent ? new Set(pendingEdits.keys()) : null;
|
|
11983
|
-
const waiting = () => [...pendingEdits.keys()].filter((edit) => !current || current.has(edit));
|
|
11984
|
-
let conflict;
|
|
11985
|
-
let firstFailure;
|
|
11986
|
-
while (detail.promises.length > 0 || waiting().length > 0) {
|
|
11987
|
-
const promises = [...detail.promises, ...waiting()];
|
|
11988
|
-
detail.promises = [];
|
|
11989
|
-
const batchFailures = inspectDrainFailures(await Promise.allSettled(promises));
|
|
11990
|
-
conflict ??= batchFailures.conflict;
|
|
11991
|
-
firstFailure ??= batchFailures.firstFailure;
|
|
11992
|
-
}
|
|
11993
|
-
if (conflict) return { status: "conflict", error: conflict };
|
|
11994
|
-
return firstFailure ? { status: "failed", error: firstFailure.reason } : { status: "clean" };
|
|
11995
|
-
}
|
|
11996
|
-
function addStudioPendingEditFlushListener(handler) {
|
|
11997
|
-
const listener2 = (event) => {
|
|
11998
|
-
const detail = event.detail;
|
|
11999
|
-
if (!detail?.promises) return;
|
|
12000
|
-
const promise = trackStudioPendingEdit(handler());
|
|
12001
|
-
if (promise) detail.promises.push(promise);
|
|
12002
|
-
};
|
|
12003
|
-
window.addEventListener(STUDIO_FLUSH_PENDING_EDITS_EVENT, listener2);
|
|
12004
|
-
return () => window.removeEventListener(STUDIO_FLUSH_PENDING_EDITS_EVENT, listener2);
|
|
12005
|
-
}
|
|
12006
|
-
|
|
12007
|
-
// src/contexts/DomEditContext.tsx
|
|
12008
12119
|
import { jsx as jsx19 } from "react/jsx-runtime";
|
|
12009
12120
|
var SAVES_AN_EDIT = {
|
|
12010
12121
|
handleTimelineElementSelect: false,
|
|
@@ -17284,6 +17395,14 @@ function applySoftReloadFinalization(iframe, currentTime) {
|
|
|
17284
17395
|
function timelineKeys(scriptText) {
|
|
17285
17396
|
return [...scriptText.matchAll(/__timelines\s*\[\s*["'`]([^"'`]+)["'`]\s*\]/g)].map((m) => m[1]).filter((key2) => key2 !== "__proxied");
|
|
17286
17397
|
}
|
|
17398
|
+
function scriptsRegistering(scripts, keys) {
|
|
17399
|
+
return scripts.filter((script) => {
|
|
17400
|
+
const text = script.textContent || "";
|
|
17401
|
+
return keys.some(
|
|
17402
|
+
(key2) => text.includes(`__timelines["${key2}"]`) || text.includes(`__timelines['${key2}']`)
|
|
17403
|
+
);
|
|
17404
|
+
});
|
|
17405
|
+
}
|
|
17287
17406
|
function applySoftReload(iframe, scriptText, options = {}) {
|
|
17288
17407
|
const { onAsyncFailure, currentTimeOverride, authoredHtml } = options;
|
|
17289
17408
|
if (!iframe || !scriptText) return "cannot-soft-reload";
|
|
@@ -17296,12 +17415,7 @@ function applySoftReload(iframe, scriptText, options = {}) {
|
|
|
17296
17415
|
if (targetKeys.length === 0) return "cannot-soft-reload";
|
|
17297
17416
|
const gsapScripts = findGsapScriptElements(doc);
|
|
17298
17417
|
if (gsapScripts.length === 0) return "cannot-soft-reload";
|
|
17299
|
-
const staleScripts = gsapScripts
|
|
17300
|
-
(script) => targetKeys.some((key2) => {
|
|
17301
|
-
const text = script.textContent || "";
|
|
17302
|
-
return text.includes(`__timelines["${key2}"]`) || text.includes(`__timelines['${key2}']`);
|
|
17303
|
-
})
|
|
17304
|
-
);
|
|
17418
|
+
const staleScripts = scriptsRegistering(gsapScripts, targetKeys);
|
|
17305
17419
|
if (gsapScripts.length > 1 && staleScripts.length === 0) return "cannot-soft-reload";
|
|
17306
17420
|
const currentTime = currentTimeOverride ?? win.__player?.getTime?.() ?? 0;
|
|
17307
17421
|
let deferredToAsync = false;
|
|
@@ -17540,25 +17654,190 @@ function requestGsapMutation(projectId, route, filePath, body) {
|
|
|
17540
17654
|
}
|
|
17541
17655
|
);
|
|
17542
17656
|
}
|
|
17543
|
-
async function
|
|
17657
|
+
async function postOwnedGsapMutation(route, projectId, filePath, body, fallback) {
|
|
17544
17658
|
let response;
|
|
17545
17659
|
try {
|
|
17546
|
-
response = await requestGsapMutation(projectId,
|
|
17660
|
+
response = await requestGsapMutation(projectId, route, filePath, body);
|
|
17547
17661
|
} catch (error) {
|
|
17548
17662
|
throw new GsapPreviewConvergenceError(`${fallback}: mutation outcome unknown`, {
|
|
17549
17663
|
cause: error
|
|
17550
17664
|
});
|
|
17551
17665
|
}
|
|
17552
|
-
const
|
|
17666
|
+
const payload = await response.json().catch(() => null);
|
|
17553
17667
|
if (!response.ok) {
|
|
17554
|
-
throw new GsapPreviewConvergenceError(readMutationError(
|
|
17668
|
+
throw new GsapPreviewConvergenceError(readMutationError(payload, fallback));
|
|
17555
17669
|
}
|
|
17556
|
-
return readMutationStatus(
|
|
17670
|
+
return readMutationStatus(payload);
|
|
17671
|
+
}
|
|
17672
|
+
function postGsapMutation(projectId, filePath, mutation, fallback) {
|
|
17673
|
+
return postOwnedGsapMutation("gsap-mutations", projectId, filePath, mutation, fallback);
|
|
17674
|
+
}
|
|
17675
|
+
function postGsapMutations(projectId, filePath, mutations, fallback) {
|
|
17676
|
+
return postOwnedGsapMutation(
|
|
17677
|
+
"gsap-mutations-batch",
|
|
17678
|
+
projectId,
|
|
17679
|
+
filePath,
|
|
17680
|
+
{ mutations },
|
|
17681
|
+
fallback
|
|
17682
|
+
);
|
|
17557
17683
|
}
|
|
17558
17684
|
|
|
17559
17685
|
// src/hooks/timelineTimingSync.ts
|
|
17560
17686
|
import { findTimelineScript } from "@hyperframes/core/gsap-parser-acorn";
|
|
17561
17687
|
import { walkCompositionDescendants } from "@hyperframes/parsers/hf-ids";
|
|
17688
|
+
|
|
17689
|
+
// src/utils/gsapLiveRetime.ts
|
|
17690
|
+
import { parseGsapScriptAcorn } from "@hyperframes/core/gsap-parser-acorn";
|
|
17691
|
+
import { hasExplicitTime } from "@hyperframes/parsers/gsap-writer-acorn";
|
|
17692
|
+
import { RUNTIME_FILLER } from "@hyperframes/core/runtime/protocol";
|
|
17693
|
+
var RERUN = { kind: "rerun" };
|
|
17694
|
+
var withoutLayout = (code) => code.replace(/\s+/g, " ").replace(/ ?([^\w$ ]) ?/g, "$1").trim();
|
|
17695
|
+
function untimedShape(animation) {
|
|
17696
|
+
const { id: _id, position: _p, resolvedStart: _s, duration: _d, ...rest } = animation;
|
|
17697
|
+
const provenance = rest.provenance && { ...rest.provenance, sourceRange: void 0 };
|
|
17698
|
+
return JSON.stringify(
|
|
17699
|
+
{ ...rest, provenance },
|
|
17700
|
+
(_key, value) => typeof value === "string" && value.startsWith("__raw:") ? withoutLayout(value) : value
|
|
17701
|
+
);
|
|
17702
|
+
}
|
|
17703
|
+
var onTimeline = (animation) => !animation.global;
|
|
17704
|
+
var placedAlone = (animation) => hasExplicitTime(animation) && typeof animation.resolvedStart === "number";
|
|
17705
|
+
var lengthOf = (animation) => animation.method === "set" ? 0 : animation.durationUnresolved ? void 0 : animation.duration;
|
|
17706
|
+
var withoutNumbers = (script) => withoutLayout(script.replace(/(?<![\w$#.])\d*\.?\d+/g, "0"));
|
|
17707
|
+
function planLiveRetime(before, after) {
|
|
17708
|
+
if (withoutNumbers(before) !== withoutNumbers(after)) return RERUN;
|
|
17709
|
+
const keys = timelineKeys(after);
|
|
17710
|
+
if (keys.length !== 1 || timelineKeys(before).join() !== keys.join()) return RERUN;
|
|
17711
|
+
const was = parseGsapScriptAcorn(before).animations.filter(onTimeline);
|
|
17712
|
+
const now2 = parseGsapScriptAcorn(after).animations.filter(onTimeline);
|
|
17713
|
+
if (was.length !== now2.length) return RERUN;
|
|
17714
|
+
const tweens = [];
|
|
17715
|
+
for (const [index, next] of now2.entries()) {
|
|
17716
|
+
const prev = was[index];
|
|
17717
|
+
if (untimedShape(prev) !== untimedShape(next)) return RERUN;
|
|
17718
|
+
if (!placedAlone(next) || !placedAlone(prev)) return RERUN;
|
|
17719
|
+
const duration = lengthOf(next);
|
|
17720
|
+
const wasDuration = lengthOf(prev);
|
|
17721
|
+
if (duration === void 0 || wasDuration === void 0) return RERUN;
|
|
17722
|
+
tweens.push({
|
|
17723
|
+
selector: next.targetSelector,
|
|
17724
|
+
start: next.resolvedStart,
|
|
17725
|
+
duration,
|
|
17726
|
+
wasStart: prev.resolvedStart,
|
|
17727
|
+
wasDuration,
|
|
17728
|
+
moved: next.resolvedStart !== prev.resolvedStart || duration !== wasDuration,
|
|
17729
|
+
source: index
|
|
17730
|
+
});
|
|
17731
|
+
}
|
|
17732
|
+
tweens.sort((x, y) => x.wasStart - y.wasStart || x.source - y.source);
|
|
17733
|
+
return { kind: "retime", key: keys[0], after, tweens };
|
|
17734
|
+
}
|
|
17735
|
+
function planLiveRetimeFromPreview(iframe, after) {
|
|
17736
|
+
const doc = iframe?.contentDocument;
|
|
17737
|
+
const [key2] = timelineKeys(after);
|
|
17738
|
+
if (!doc || !key2) return RERUN;
|
|
17739
|
+
const [script, ...extra] = scriptsRegistering(findGsapScriptElements(doc), [key2]);
|
|
17740
|
+
if (!script || extra.length > 0) return RERUN;
|
|
17741
|
+
const plan = planLiveRetime(script.textContent ?? "", after);
|
|
17742
|
+
return plan.kind === "retime" ? { ...plan, script } : RERUN;
|
|
17743
|
+
}
|
|
17744
|
+
var same = (a, b) => Math.abs(a - b) < 1e-6;
|
|
17745
|
+
var targetsMatch = (tween, selector) => {
|
|
17746
|
+
const targets = tween.targets?.() ?? [];
|
|
17747
|
+
try {
|
|
17748
|
+
return targets.length > 0 && targets.every((t2) => t2.matches?.(selector) === true);
|
|
17749
|
+
} catch {
|
|
17750
|
+
return false;
|
|
17751
|
+
}
|
|
17752
|
+
};
|
|
17753
|
+
function pairedLiveTweens(timeline, plan) {
|
|
17754
|
+
const children = timeline.getChildren(false, true, false).filter((tween) => tween.data !== RUNTIME_FILLER);
|
|
17755
|
+
if (children.length !== plan.tweens.length) return null;
|
|
17756
|
+
const paired = plan.tweens.every((t2, i) => {
|
|
17757
|
+
const live = children[i];
|
|
17758
|
+
return targetsMatch(live, t2.selector) && same(live.startTime() - live.delay(), t2.wasStart) && same(live.duration(), t2.wasDuration);
|
|
17759
|
+
});
|
|
17760
|
+
return paired ? children : null;
|
|
17761
|
+
}
|
|
17762
|
+
var near2 = (a, b) => Math.abs(a - b) <= 2e-3;
|
|
17763
|
+
function sharesOneStretch(tweens) {
|
|
17764
|
+
const first = tweens[0];
|
|
17765
|
+
const last = tweens.at(-1);
|
|
17766
|
+
const longest = tweens.reduce((x, y) => y.wasDuration > x.wasDuration ? y : x);
|
|
17767
|
+
const spread = last.wasStart - first.wasStart;
|
|
17768
|
+
const stretch = spread > longest.wasDuration ? (last.start - first.start) / spread : longest.wasDuration > 0 ? longest.duration / longest.wasDuration : 1;
|
|
17769
|
+
const fits = tweens.every(
|
|
17770
|
+
(t2) => near2(t2.start, first.start + stretch * (t2.wasStart - first.wasStart)) && near2(t2.duration, stretch * t2.wasDuration)
|
|
17771
|
+
);
|
|
17772
|
+
return stretch > 0 && fits;
|
|
17773
|
+
}
|
|
17774
|
+
function inOrHostsSubComposition(target, ownId) {
|
|
17775
|
+
const element = target;
|
|
17776
|
+
const owner = element.closest?.("[data-composition-id]");
|
|
17777
|
+
return owner != null && owner.getAttribute("data-composition-id") !== ownId || element.querySelector?.("[data-composition-id]") != null;
|
|
17778
|
+
}
|
|
17779
|
+
var keepsOrder = (tweens) => tweens.every(
|
|
17780
|
+
(t2, k) => k === 0 || (tweens[k - 1].start - t2.start || tweens[k - 1].source - t2.source) < 0
|
|
17781
|
+
);
|
|
17782
|
+
function movesKeepEachElementsHistory(timeline, plan, children) {
|
|
17783
|
+
const byElement = /* @__PURE__ */ new Map();
|
|
17784
|
+
children.forEach((child, i) => {
|
|
17785
|
+
for (const target of child.targets?.() ?? []) {
|
|
17786
|
+
const indexes = byElement.get(target);
|
|
17787
|
+
if (indexes) indexes.push(i);
|
|
17788
|
+
else byElement.set(target, [i]);
|
|
17789
|
+
}
|
|
17790
|
+
});
|
|
17791
|
+
const moved = /* @__PURE__ */ new Set();
|
|
17792
|
+
for (const [target, indexes] of byElement) {
|
|
17793
|
+
if (!indexes.some((i) => plan.tweens[i].moved)) continue;
|
|
17794
|
+
if (inOrHostsSubComposition(target, plan.key)) return false;
|
|
17795
|
+
const tweens = indexes.map((i) => plan.tweens[i]);
|
|
17796
|
+
const shifted = tweens.every((t2) => t2.duration === t2.wasDuration);
|
|
17797
|
+
const undelayed = indexes.every((i) => !children[i].delay() && !children[i].repeatDelay?.());
|
|
17798
|
+
if (!keepsOrder(tweens) || !sharesOneStretch(tweens) || !(shifted || undelayed)) return false;
|
|
17799
|
+
moved.add(target);
|
|
17800
|
+
}
|
|
17801
|
+
return !timeline.getChildren(false, false, true).some(
|
|
17802
|
+
(sub) => sub.getChildren(true, true, false).some((tween) => tween.targets?.().some((t2) => moved.has(t2)))
|
|
17803
|
+
);
|
|
17804
|
+
}
|
|
17805
|
+
function applyLiveRetime(iframe, plan) {
|
|
17806
|
+
const win = iframe?.contentWindow;
|
|
17807
|
+
const timeline = win?.__timelines?.[plan.key];
|
|
17808
|
+
if (typeof timeline?.getChildren !== "function") return false;
|
|
17809
|
+
const children = pairedLiveTweens(timeline, plan);
|
|
17810
|
+
if (!children) return false;
|
|
17811
|
+
if (plan.tweens.some((t2) => t2.moved)) {
|
|
17812
|
+
if (!movesKeepEachElementsHistory(timeline, plan, children)) return false;
|
|
17813
|
+
const bySource = plan.tweens.map((t2, i) => ({ ...t2, tween: children[i] })).sort((a, b) => a.source - b.source);
|
|
17814
|
+
for (const { tween } of bySource) timeline.remove(tween);
|
|
17815
|
+
for (const { tween, start, duration } of bySource) {
|
|
17816
|
+
if (tween.duration() !== duration) tween.duration(duration);
|
|
17817
|
+
timeline.add(tween, start);
|
|
17818
|
+
}
|
|
17819
|
+
const at = timeline.time();
|
|
17820
|
+
timeline.progress(1e-4, true);
|
|
17821
|
+
timeline.totalTime(at, false);
|
|
17822
|
+
}
|
|
17823
|
+
plan.script.textContent = plan.after;
|
|
17824
|
+
return true;
|
|
17825
|
+
}
|
|
17826
|
+
function moveLiveTweens(iframe, scriptText, currentTime, reloadPreview) {
|
|
17827
|
+
const plan = planLiveRetimeFromPreview(iframe, scriptText);
|
|
17828
|
+
if (plan.kind !== "retime") return false;
|
|
17829
|
+
try {
|
|
17830
|
+
if (!applyLiveRetime(iframe, plan)) return false;
|
|
17831
|
+
} catch (error) {
|
|
17832
|
+
console.error("[Studio] Moving the live tweens threw; reloading the preview", error);
|
|
17833
|
+
reloadPreview();
|
|
17834
|
+
return true;
|
|
17835
|
+
}
|
|
17836
|
+
if (!applySoftReloadFinalization(iframe, currentTime)) reloadPreview();
|
|
17837
|
+
return true;
|
|
17838
|
+
}
|
|
17839
|
+
|
|
17840
|
+
// src/hooks/timelineTimingSync.ts
|
|
17562
17841
|
async function readFileContent(projectId, targetPath) {
|
|
17563
17842
|
if (targetPath.includes("\0") || targetPath.includes("..")) {
|
|
17564
17843
|
throw new Error(`Unsafe path: ${targetPath}`);
|
|
@@ -17599,25 +17878,32 @@ function captureDurationRollback(iframe) {
|
|
|
17599
17878
|
function rebindPreviewTiming(iframe, currentTime) {
|
|
17600
17879
|
return applySoftReloadFinalization(iframe, currentTime);
|
|
17601
17880
|
}
|
|
17602
|
-
function syncTimingEditPreview(iframe, outcome,
|
|
17881
|
+
async function syncTimingEditPreview(iframe, outcome, projectId, reloadPreview, rebindWhenUnmutated) {
|
|
17882
|
+
const currentTime = () => usePlayerStore.getState().currentTime;
|
|
17603
17883
|
if (!outcome.mutated && rebindWhenUnmutated) {
|
|
17604
|
-
if (!rebindPreviewTiming(iframe, currentTime)) reloadPreview();
|
|
17884
|
+
if (!rebindPreviewTiming(iframe, currentTime())) reloadPreview();
|
|
17605
17885
|
return;
|
|
17606
17886
|
}
|
|
17607
|
-
|
|
17887
|
+
const { scriptText } = outcome;
|
|
17888
|
+
if (!iframe || !scriptText) {
|
|
17608
17889
|
reloadPreview();
|
|
17609
17890
|
return;
|
|
17610
17891
|
}
|
|
17611
|
-
|
|
17892
|
+
if (moveLiveTweens(iframe, scriptText, currentTime(), reloadPreview)) return;
|
|
17893
|
+
const nestedFiles = await settleNestedReads(
|
|
17894
|
+
projectId ? readNestedFiles(iframe, scriptText, (path) => readFileContent(projectId, path)) : null
|
|
17895
|
+
);
|
|
17896
|
+
const result = applySoftReload(iframe, scriptText, {
|
|
17612
17897
|
onAsyncFailure: reloadPreview,
|
|
17613
|
-
currentTimeOverride: currentTime,
|
|
17898
|
+
currentTimeOverride: currentTime(),
|
|
17614
17899
|
authoredHtml: outcome.after,
|
|
17615
17900
|
nestedFiles
|
|
17616
17901
|
});
|
|
17617
17902
|
if (result === "cannot-soft-reload") reloadPreview();
|
|
17618
17903
|
}
|
|
17904
|
+
var UNMUTATED = { mutated: false, scriptText: null };
|
|
17619
17905
|
async function finishTimelineTimingFallback(input) {
|
|
17620
|
-
let outcome =
|
|
17906
|
+
let outcome = UNMUTATED;
|
|
17621
17907
|
if (input.gsapMutation) {
|
|
17622
17908
|
try {
|
|
17623
17909
|
outcome = await input.gsapMutation();
|
|
@@ -17627,24 +17913,22 @@ async function finishTimelineTimingFallback(input) {
|
|
|
17627
17913
|
return;
|
|
17628
17914
|
}
|
|
17629
17915
|
}
|
|
17630
|
-
|
|
17631
|
-
const nestedFiles = await settleNestedReads(
|
|
17632
|
-
projectId && outcome.scriptText ? readNestedFiles(
|
|
17633
|
-
input.iframe,
|
|
17634
|
-
outcome.scriptText,
|
|
17635
|
-
(path) => readFileContent(projectId, path)
|
|
17636
|
-
) : null
|
|
17637
|
-
);
|
|
17638
|
-
syncTimingEditPreview(
|
|
17916
|
+
await syncTimingEditPreview(
|
|
17639
17917
|
input.iframe,
|
|
17640
17918
|
outcome,
|
|
17641
|
-
|
|
17919
|
+
input.projectId,
|
|
17642
17920
|
input.reloadPreview,
|
|
17643
|
-
input.rebindWhenUnmutated
|
|
17644
|
-
nestedFiles
|
|
17921
|
+
input.rebindWhenUnmutated
|
|
17645
17922
|
);
|
|
17646
17923
|
}
|
|
17647
|
-
var
|
|
17924
|
+
var UNTIL_NEXT_EDIT = Number.POSITIVE_INFINITY;
|
|
17925
|
+
var timingGestures = 0;
|
|
17926
|
+
function timingGestureStep(kind, given) {
|
|
17927
|
+
return {
|
|
17928
|
+
coalesceKey: given?.coalesceKey ?? `${kind}:${++timingGestures}`,
|
|
17929
|
+
coalesceMs: UNTIL_NEXT_EDIT
|
|
17930
|
+
};
|
|
17931
|
+
}
|
|
17648
17932
|
async function rollbackMutatedFiles(projectId, ownedSteps, onError) {
|
|
17649
17933
|
let convergenceRequired = false;
|
|
17650
17934
|
for (let index = ownedSteps.length - 1; index >= 0; index -= 1) {
|
|
@@ -17675,7 +17959,7 @@ async function foldGsapMutationInQueue(input) {
|
|
|
17675
17959
|
const ownedSteps = [];
|
|
17676
17960
|
const runOwnedMutation = async (path, mutation) => {
|
|
17677
17961
|
const pending2 = mutation();
|
|
17678
|
-
if (!pending2) return
|
|
17962
|
+
if (!pending2) return UNMUTATED;
|
|
17679
17963
|
const status2 = await pending2;
|
|
17680
17964
|
if (!status2.mutated) return status2;
|
|
17681
17965
|
if (status2.before === void 0 || status2.after === void 0) {
|
|
@@ -17720,7 +18004,7 @@ async function foldGsapMutationInQueue(input) {
|
|
|
17720
18004
|
await input.recordEdit({
|
|
17721
18005
|
label: input.label,
|
|
17722
18006
|
coalesceKey: input.coalesceKey,
|
|
17723
|
-
coalesceMs:
|
|
18007
|
+
coalesceMs: UNTIL_NEXT_EDIT,
|
|
17724
18008
|
files
|
|
17725
18009
|
});
|
|
17726
18010
|
}
|
|
@@ -17737,37 +18021,31 @@ function foldGsapMutationIntoHistory(input) {
|
|
|
17737
18021
|
() => foldGsapMutationInQueue(input)
|
|
17738
18022
|
);
|
|
17739
18023
|
}
|
|
18024
|
+
function shiftGsapMutation(elementId2, delta) {
|
|
18025
|
+
if (delta === 0 || !elementId2) return null;
|
|
18026
|
+
return { type: "shift-positions", targetSelector: `#${elementId2}`, delta };
|
|
18027
|
+
}
|
|
18028
|
+
function scaleGsapMutation(elementId2, oldStart, oldDuration, newStart, newDuration) {
|
|
18029
|
+
if (!elementId2 || oldDuration <= 0 || newDuration <= 0) return null;
|
|
18030
|
+
if (oldStart === newStart && oldDuration === newDuration) return null;
|
|
18031
|
+
return {
|
|
18032
|
+
type: "scale-positions",
|
|
18033
|
+
targetSelector: `#${elementId2}`,
|
|
18034
|
+
oldStart,
|
|
18035
|
+
oldDuration,
|
|
18036
|
+
newStart,
|
|
18037
|
+
newDuration
|
|
18038
|
+
};
|
|
18039
|
+
}
|
|
17740
18040
|
async function shiftGsapPositions(projectId, filePath, elementId2, delta) {
|
|
17741
|
-
|
|
17742
|
-
return
|
|
17743
|
-
|
|
17744
|
-
filePath,
|
|
17745
|
-
{
|
|
17746
|
-
type: "shift-positions",
|
|
17747
|
-
targetSelector: `#${elementId2}`,
|
|
17748
|
-
delta
|
|
17749
|
-
},
|
|
17750
|
-
"shift-positions failed"
|
|
17751
|
-
);
|
|
18041
|
+
const mutation = shiftGsapMutation(elementId2, delta);
|
|
18042
|
+
if (!mutation) return UNMUTATED;
|
|
18043
|
+
return postGsapMutation(projectId, filePath, mutation, "shift-positions failed");
|
|
17752
18044
|
}
|
|
17753
18045
|
async function scaleGsapPositions(projectId, filePath, elementId2, oldStart, oldDuration, newStart, newDuration) {
|
|
17754
|
-
|
|
17755
|
-
|
|
17756
|
-
|
|
17757
|
-
return { mutated: false, scriptText: null };
|
|
17758
|
-
return postGsapMutation(
|
|
17759
|
-
projectId,
|
|
17760
|
-
filePath,
|
|
17761
|
-
{
|
|
17762
|
-
type: "scale-positions",
|
|
17763
|
-
targetSelector: `#${elementId2}`,
|
|
17764
|
-
oldStart,
|
|
17765
|
-
oldDuration,
|
|
17766
|
-
newStart,
|
|
17767
|
-
newDuration
|
|
17768
|
-
},
|
|
17769
|
-
"scale-positions failed"
|
|
17770
|
-
);
|
|
18046
|
+
const mutation = scaleGsapMutation(elementId2, oldStart, oldDuration, newStart, newDuration);
|
|
18047
|
+
if (!mutation) return UNMUTATED;
|
|
18048
|
+
return postGsapMutation(projectId, filePath, mutation, "scale-positions failed");
|
|
17771
18049
|
}
|
|
17772
18050
|
function sdkTimingGsapSync(result) {
|
|
17773
18051
|
const timelineText = (html2) => {
|
|
@@ -17844,11 +18122,17 @@ async function finishGroupTimingGsapFallback(input) {
|
|
|
17844
18122
|
let mutated = false;
|
|
17845
18123
|
let scriptText = null;
|
|
17846
18124
|
let after;
|
|
18125
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
17847
18126
|
for (const change of input.changes) {
|
|
18127
|
+
const mutation = input.mutationFor(change);
|
|
18128
|
+
if (!mutation) continue;
|
|
17848
18129
|
const changePath = input.resolveChangePath(change.element);
|
|
18130
|
+
byPath.set(changePath, [...byPath.get(changePath) ?? [], mutation]);
|
|
18131
|
+
}
|
|
18132
|
+
for (const [changePath, mutations] of byPath) {
|
|
17849
18133
|
const status = await runOwnedMutation(
|
|
17850
18134
|
changePath,
|
|
17851
|
-
() => input.
|
|
18135
|
+
() => postGsapMutations(input.projectId, changePath, mutations, input.errorLabel)
|
|
17852
18136
|
);
|
|
17853
18137
|
mutated = mutated || status.mutated;
|
|
17854
18138
|
if (changePath === activePath) ({ scriptText, after } = status);
|
|
@@ -18015,10 +18299,10 @@ function resolveResizePlaybackStart(original, target, element, updates) {
|
|
|
18015
18299
|
)
|
|
18016
18300
|
};
|
|
18017
18301
|
}
|
|
18018
|
-
function
|
|
18302
|
+
function applyTimelineMoveAttributes(original, target, start, duration, track2, audioGroup) {
|
|
18019
18303
|
if (!Number.isFinite(start) || !Number.isFinite(duration)) {
|
|
18020
18304
|
console.warn(
|
|
18021
|
-
`[Timeline]
|
|
18305
|
+
`[Timeline] applyTimelineMoveAttributes: non-finite timing (start=${start}, duration=${duration}) \u2014 patch skipped`
|
|
18022
18306
|
);
|
|
18023
18307
|
return original;
|
|
18024
18308
|
}
|
|
@@ -18040,9 +18324,16 @@ function buildTimelineMoveTimingPatch(original, target, start, duration, track2,
|
|
|
18040
18324
|
property: "data-audio-group",
|
|
18041
18325
|
value: null
|
|
18042
18326
|
});
|
|
18043
|
-
return
|
|
18327
|
+
return patched;
|
|
18044
18328
|
}
|
|
18045
|
-
function
|
|
18329
|
+
function syncCompositionDurationToContent(source) {
|
|
18330
|
+
return setCompositionDurationToContent(source, furthestClipEndFromSource(source));
|
|
18331
|
+
}
|
|
18332
|
+
function buildTimelineMoveTimingPatch(original, target, start, duration, track2, audioGroup) {
|
|
18333
|
+
const patched = applyTimelineMoveAttributes(original, target, start, duration, track2, audioGroup);
|
|
18334
|
+
return !(Number.isFinite(start) && Number.isFinite(duration)) ? patched : syncCompositionDurationToContent(patched);
|
|
18335
|
+
}
|
|
18336
|
+
function applyTimelineResizeAttributes(original, target, element, updates) {
|
|
18046
18337
|
const pbs = resolveResizePlaybackStart(original, target, element, updates);
|
|
18047
18338
|
let patched = applyPatchByTarget(original, target, {
|
|
18048
18339
|
type: "attribute",
|
|
@@ -18061,7 +18352,10 @@ function buildTimelineResizeTimingPatch(original, target, element, updates) {
|
|
|
18061
18352
|
value: formatTimelineMediaOffset(pbs.value)
|
|
18062
18353
|
});
|
|
18063
18354
|
}
|
|
18064
|
-
return
|
|
18355
|
+
return patched;
|
|
18356
|
+
}
|
|
18357
|
+
function buildTimelineResizeTimingPatch(...args) {
|
|
18358
|
+
return syncCompositionDurationToContent(applyTimelineResizeAttributes(...args));
|
|
18065
18359
|
}
|
|
18066
18360
|
async function persistTimelineEdit(input) {
|
|
18067
18361
|
const targetPath = input.element.sourceFile || input.activeCompPath || "index.html";
|
|
@@ -18074,6 +18368,7 @@ async function persistTimelineEdit(input) {
|
|
|
18074
18368
|
projectId: input.projectId,
|
|
18075
18369
|
label: input.label,
|
|
18076
18370
|
coalesceKey: input.coalesceKey,
|
|
18371
|
+
coalesceMs: input.coalesceMs,
|
|
18077
18372
|
files: {
|
|
18078
18373
|
[targetPath]: (current) => {
|
|
18079
18374
|
const patched = input.buildPatches(current, patchTarget);
|
|
@@ -18113,7 +18408,12 @@ async function persistTimelineBatchEdit(input) {
|
|
|
18113
18408
|
changesByPath.set(targetPath, [...changesByPath.get(targetPath) ?? [], change]);
|
|
18114
18409
|
}
|
|
18115
18410
|
const buildFile = (targetPath) => (original) => {
|
|
18116
|
-
const
|
|
18411
|
+
const patched = patchTimelineChangesInSource(
|
|
18412
|
+
original,
|
|
18413
|
+
targetPath,
|
|
18414
|
+
changesByPath.get(targetPath)
|
|
18415
|
+
);
|
|
18416
|
+
const next = syncCompositionDurationToContent(patched);
|
|
18117
18417
|
if (next !== original) input.pendingTimelineEditPathRef.current.add(targetPath);
|
|
18118
18418
|
return next;
|
|
18119
18419
|
};
|
|
@@ -18521,6 +18821,7 @@ import { jsx as jsx33, jsxs as jsxs20 } from "react/jsx-runtime";
|
|
|
18521
18821
|
var TimelineClip = memo13(function TimelineClip2({
|
|
18522
18822
|
el,
|
|
18523
18823
|
pps,
|
|
18824
|
+
passengerStyle,
|
|
18524
18825
|
clipY,
|
|
18525
18826
|
clipHeight,
|
|
18526
18827
|
isSelected: isSelected2,
|
|
@@ -18592,7 +18893,8 @@ var TimelineClip = memo13(function TimelineClip2({
|
|
|
18592
18893
|
font: "inherit",
|
|
18593
18894
|
padding: 0,
|
|
18594
18895
|
textAlign: "left",
|
|
18595
|
-
transform: isDragging ? "translateY(-1px)" : void 0
|
|
18896
|
+
transform: isDragging ? "translateY(-1px)" : void 0,
|
|
18897
|
+
...passengerStyle
|
|
18596
18898
|
};
|
|
18597
18899
|
return /* @__PURE__ */ jsxs20(
|
|
18598
18900
|
"button",
|
|
@@ -18728,6 +19030,7 @@ function TimelineCompactDiamonds({
|
|
|
18728
19030
|
beatsActive,
|
|
18729
19031
|
accentColor,
|
|
18730
19032
|
isSelected: isSelected2,
|
|
19033
|
+
passengerStyle,
|
|
18731
19034
|
currentTime,
|
|
18732
19035
|
selectedKeyframes,
|
|
18733
19036
|
rovingTargetId,
|
|
@@ -18748,7 +19051,8 @@ function TimelineCompactDiamonds({
|
|
|
18748
19051
|
top: CLIP_Y,
|
|
18749
19052
|
width,
|
|
18750
19053
|
height: rowHeight - 2 * CLIP_Y,
|
|
18751
|
-
zIndex: isSelected2 ? 11 : 6
|
|
19054
|
+
zIndex: isSelected2 ? 11 : 6,
|
|
19055
|
+
...passengerStyle
|
|
18752
19056
|
},
|
|
18753
19057
|
children: /* @__PURE__ */ jsx34(
|
|
18754
19058
|
TimelineClipDiamonds,
|
|
@@ -21850,7 +22154,6 @@ function LayerDisclosureRow({
|
|
|
21850
22154
|
}
|
|
21851
22155
|
|
|
21852
22156
|
// src/player/components/trackHeaderLaneValues.ts
|
|
21853
|
-
import gsap2 from "gsap";
|
|
21854
22157
|
import {
|
|
21855
22158
|
classifyPropertyGroup as classifyPropertyGroup2
|
|
21856
22159
|
} from "@hyperframes/core/gsap-parser";
|
|
@@ -21858,24 +22161,25 @@ function roundValue(value) {
|
|
|
21858
22161
|
return String(Math.round(value * 100) / 100);
|
|
21859
22162
|
}
|
|
21860
22163
|
function easedProgress(progress, animation, ease) {
|
|
21861
|
-
|
|
21862
|
-
if (!resolved2 || resolved2 === "none") return progress;
|
|
21863
|
-
return gsap2.parseEase(resolved2)?.(progress) ?? progress;
|
|
22164
|
+
return easeFunction(keyframedTweenEases(animation).segment({ ease }))?.(progress) ?? progress;
|
|
21864
22165
|
}
|
|
21865
|
-
function propertyStops(animation, property) {
|
|
21866
|
-
|
|
22166
|
+
function propertyStops(animation, property, start) {
|
|
22167
|
+
const stops = (animation.keyframes?.keyframes ?? []).filter((keyframe) => property in keyframe.properties).map((keyframe) => ({
|
|
21867
22168
|
percentage: keyframe.percentage,
|
|
21868
22169
|
value: keyframe.properties[property],
|
|
21869
22170
|
ease: keyframe.ease
|
|
21870
22171
|
}));
|
|
22172
|
+
const first = stops[0];
|
|
22173
|
+
if (!first || first.percentage <= 0 || typeof first.value !== "number") return stops;
|
|
22174
|
+
return [{ percentage: 0, value: start ?? PROPERTY_DEFAULTS[property] ?? 0 }, ...stops];
|
|
21871
22175
|
}
|
|
21872
22176
|
function interpolableEnds(before, after) {
|
|
21873
22177
|
if (typeof before.value !== "number" || typeof after.value !== "number") return null;
|
|
21874
22178
|
if (before.percentage === after.percentage) return null;
|
|
21875
22179
|
return { from: before.value, to: after.value };
|
|
21876
22180
|
}
|
|
21877
|
-
function propertyValueAt(animation, property, tweenPercentage) {
|
|
21878
|
-
const stops = propertyStops(animation, property);
|
|
22181
|
+
function propertyValueAt(animation, property, tweenPercentage, start) {
|
|
22182
|
+
const stops = propertyStops(animation, property, start);
|
|
21879
22183
|
const before = stops.filter((stop2) => stop2.percentage <= tweenPercentage).at(-1);
|
|
21880
22184
|
const after = stops.find((stop2) => stop2.percentage >= tweenPercentage);
|
|
21881
22185
|
if (!before) return after?.value;
|
|
@@ -21885,7 +22189,7 @@ function propertyValueAt(animation, property, tweenPercentage) {
|
|
|
21885
22189
|
const progress = (tweenPercentage - before.percentage) / (after.percentage - before.percentage);
|
|
21886
22190
|
return ends.from + (ends.to - ends.from) * easedProgress(progress, animation, after.ease);
|
|
21887
22191
|
}
|
|
21888
|
-
function valuesAt(animation, group, tweenPercentage) {
|
|
22192
|
+
function valuesAt(animation, group, tweenPercentage, startValues = {}) {
|
|
21889
22193
|
const propertyNames = /* @__PURE__ */ new Set();
|
|
21890
22194
|
for (const keyframe of animation.keyframes?.keyframes ?? []) {
|
|
21891
22195
|
for (const property of Object.keys(keyframe.properties)) {
|
|
@@ -21894,11 +22198,34 @@ function valuesAt(animation, group, tweenPercentage) {
|
|
|
21894
22198
|
}
|
|
21895
22199
|
const values = {};
|
|
21896
22200
|
for (const property of propertyNames) {
|
|
21897
|
-
const value = propertyValueAt(animation, property, tweenPercentage);
|
|
22201
|
+
const value = propertyValueAt(animation, property, tweenPercentage, startValues[property]);
|
|
21898
22202
|
if (value !== void 0) values[property] = value;
|
|
21899
22203
|
}
|
|
21900
22204
|
return values;
|
|
21901
22205
|
}
|
|
22206
|
+
var endValue = (animation, property) => animation.keyframes?.keyframes.findLast((keyframe) => property in keyframe.properties)?.properties[property] ?? animation.properties[property];
|
|
22207
|
+
function valuesBefore(animation, animations) {
|
|
22208
|
+
const start = resolveTweenStart(animation);
|
|
22209
|
+
if (start == null) return {};
|
|
22210
|
+
const endOf2 = (other) => (resolveTweenStart(other) ?? Infinity) + (other.method === "set" ? 0 : resolveTweenDuration(other));
|
|
22211
|
+
const index = animations.indexOf(animation);
|
|
22212
|
+
const finished = animations.filter((other, i) => {
|
|
22213
|
+
if (other.method === "from" || i === index) return false;
|
|
22214
|
+
return endOf2(other) < start || endOf2(other) === start && i < index;
|
|
22215
|
+
}).sort((a, b) => endOf2(a) - endOf2(b));
|
|
22216
|
+
const values = {};
|
|
22217
|
+
for (const other of finished) {
|
|
22218
|
+
const keyframes = other.keyframes?.keyframes ?? [{ properties: other.properties }];
|
|
22219
|
+
for (const property of new Set(
|
|
22220
|
+
keyframes.flatMap((keyframe) => Object.keys(keyframe.properties))
|
|
22221
|
+
)) {
|
|
22222
|
+
const value = endValue(other, property);
|
|
22223
|
+
if (typeof value === "number") values[property] = value;
|
|
22224
|
+
else delete values[property];
|
|
22225
|
+
}
|
|
22226
|
+
}
|
|
22227
|
+
return values;
|
|
22228
|
+
}
|
|
21902
22229
|
function groupLabel(group, properties) {
|
|
21903
22230
|
if (group === "visual" && ("opacity" in properties || "autoAlpha" in properties)) {
|
|
21904
22231
|
return "Opacity";
|
|
@@ -21975,7 +22302,12 @@ function resolveLaneHeaderState(lane, currentTime, clipPercentage) {
|
|
|
21975
22302
|
currentTime,
|
|
21976
22303
|
clipPercentage
|
|
21977
22304
|
);
|
|
21978
|
-
const values = animation ? valuesAt(
|
|
22305
|
+
const values = animation ? valuesAt(
|
|
22306
|
+
animation,
|
|
22307
|
+
lane.group,
|
|
22308
|
+
tweenPercentage,
|
|
22309
|
+
valuesBefore(animation, lane.elementAnimations)
|
|
22310
|
+
) : {};
|
|
21979
22311
|
return {
|
|
21980
22312
|
navigation,
|
|
21981
22313
|
values,
|
|
@@ -23078,7 +23410,7 @@ function isIntentBlocked(intent, capabilities, onResizeElement, onMoveElement) {
|
|
|
23078
23410
|
if (capabilities.readOnly) return true;
|
|
23079
23411
|
return intent === "move" ? Boolean(onMoveElement) : Boolean(onResizeElement);
|
|
23080
23412
|
}
|
|
23081
|
-
function resolvePointerDownAction(e,
|
|
23413
|
+
function resolvePointerDownAction(e, capabilities, onResizeElement, onMoveElement) {
|
|
23082
23414
|
if (e.button !== 0) return { kind: "ignore" };
|
|
23083
23415
|
if (usePlayerStore.getState().activeTool === "razor") return { kind: "ignore" };
|
|
23084
23416
|
if (isAdditiveClick(e)) return { kind: "ignore" };
|
|
@@ -23093,8 +23425,6 @@ function resolvePointerDownAction(e, grabbed, capabilities, onResizeElement, onM
|
|
|
23093
23425
|
return { kind: "block", intent, rect };
|
|
23094
23426
|
}
|
|
23095
23427
|
if (!onMoveElement || !capabilities.canMove) return { kind: "ignore" };
|
|
23096
|
-
if (exceedsHandEditLimit(grabbed, e.altKey, null))
|
|
23097
|
-
return { kind: "block", intent: "edit-many", rect };
|
|
23098
23428
|
return { kind: "move", rect };
|
|
23099
23429
|
}
|
|
23100
23430
|
function createClipGestureHandlers(el, elementKey3, previewElement, capabilities, deps) {
|
|
@@ -23117,17 +23447,6 @@ function createClipGestureHandlers(el, elementKey3, previewElement, capabilities
|
|
|
23117
23447
|
const onResizeStart = (edge, e) => {
|
|
23118
23448
|
if (!canStartResize(edge, e, capabilities, onResizeElement)) return;
|
|
23119
23449
|
e.stopPropagation();
|
|
23120
|
-
if (exceedsHandEditLimit(el, e.altKey, edge)) {
|
|
23121
|
-
blockedClipRef.current = {
|
|
23122
|
-
pointerId: e.pointerId,
|
|
23123
|
-
element: el,
|
|
23124
|
-
intent: "edit-many",
|
|
23125
|
-
originClientX: e.clientX,
|
|
23126
|
-
originClientY: e.clientY,
|
|
23127
|
-
started: false
|
|
23128
|
-
};
|
|
23129
|
-
return;
|
|
23130
|
-
}
|
|
23131
23450
|
blockedClipRef.current = null;
|
|
23132
23451
|
setShowPopover(false);
|
|
23133
23452
|
setRangeSelection(null);
|
|
@@ -23145,7 +23464,7 @@ function createClipGestureHandlers(el, elementKey3, previewElement, capabilities
|
|
|
23145
23464
|
});
|
|
23146
23465
|
};
|
|
23147
23466
|
const onPointerDown = (e) => {
|
|
23148
|
-
const action = resolvePointerDownAction(e,
|
|
23467
|
+
const action = resolvePointerDownAction(e, capabilities, onResizeElement, onMoveElement);
|
|
23149
23468
|
if (action.kind === "ignore") return;
|
|
23150
23469
|
if (action.kind === "block") {
|
|
23151
23470
|
blockedClipRef.current = {
|
|
@@ -23352,6 +23671,14 @@ function TimelineTransitionOverlays({
|
|
|
23352
23671
|
|
|
23353
23672
|
// src/player/components/TimelineLanes.tsx
|
|
23354
23673
|
import { jsx as jsx57, jsxs as jsxs37 } from "react/jsx-runtime";
|
|
23674
|
+
function passengerStyleAt(offsetPx) {
|
|
23675
|
+
return {
|
|
23676
|
+
transform: `translateX(${offsetPx}px)`,
|
|
23677
|
+
opacity: 0.85,
|
|
23678
|
+
zIndex: 20,
|
|
23679
|
+
pointerEvents: "none"
|
|
23680
|
+
};
|
|
23681
|
+
}
|
|
23355
23682
|
function TimelineLanes({
|
|
23356
23683
|
pps,
|
|
23357
23684
|
contentOrigin,
|
|
@@ -23658,7 +23985,7 @@ function TimelineLanes({
|
|
|
23658
23985
|
isSelected2 || hoveredClip === clipKey || pinnedClipIdentities.has(clipKey)
|
|
23659
23986
|
);
|
|
23660
23987
|
const isPassenger = multiDragPreview != null && isMultiDragPassenger(clipKey, multiDragPreview);
|
|
23661
|
-
const
|
|
23988
|
+
const passengerStyle = isPassenger ? passengerStyleAt(multiDragPassengerOffsetPx(clipKey, pps, multiDragPreview)) : void 0;
|
|
23662
23989
|
const clipGestures = createClipGestureHandlers(
|
|
23663
23990
|
el,
|
|
23664
23991
|
elementKey3,
|
|
@@ -23690,6 +24017,7 @@ function TimelineLanes({
|
|
|
23690
24017
|
},
|
|
23691
24018
|
el: previewElement,
|
|
23692
24019
|
pps,
|
|
24020
|
+
passengerStyle,
|
|
23693
24021
|
clipY: CLIP_Y,
|
|
23694
24022
|
clipHeight: clipBarHeight,
|
|
23695
24023
|
isSelected: isSelected2,
|
|
@@ -23733,6 +24061,7 @@ function TimelineLanes({
|
|
|
23733
24061
|
beatsActive: beatStripOnTrack,
|
|
23734
24062
|
accentColor: clipStyle.accent,
|
|
23735
24063
|
isSelected: isSelected2,
|
|
24064
|
+
passengerStyle,
|
|
23736
24065
|
currentTime,
|
|
23737
24066
|
selectedKeyframes,
|
|
23738
24067
|
rovingTargetId: keyboard.rovingTargetId,
|
|
@@ -23754,6 +24083,7 @@ function TimelineLanes({
|
|
|
23754
24083
|
clipDuration: previewElement.duration,
|
|
23755
24084
|
clipLeftPx: previewElement.start * pps,
|
|
23756
24085
|
clipWidthPx: Math.max(previewElement.duration * pps, 4),
|
|
24086
|
+
passengerStyle,
|
|
23757
24087
|
accentColor: clipStyle.accent,
|
|
23758
24088
|
isSelected: isSelected2,
|
|
23759
24089
|
currentPercentage: previewElement.duration > 0 ? (currentTime - previewElement.start) / previewElement.duration * 100 : 0,
|
|
@@ -23769,31 +24099,11 @@ function TimelineLanes({
|
|
|
23769
24099
|
},
|
|
23770
24100
|
`${clipKey}-property-lanes`
|
|
23771
24101
|
);
|
|
23772
|
-
|
|
23773
|
-
|
|
23774
|
-
|
|
23775
|
-
|
|
23776
|
-
|
|
23777
|
-
] }, clipKey);
|
|
23778
|
-
}
|
|
23779
|
-
return /* @__PURE__ */ jsxs37(
|
|
23780
|
-
"div",
|
|
23781
|
-
{
|
|
23782
|
-
className: "absolute inset-0",
|
|
23783
|
-
style: {
|
|
23784
|
-
transform: `translateX(${passengerOffsetPx}px)`,
|
|
23785
|
-
opacity: 0.85,
|
|
23786
|
-
zIndex: 20,
|
|
23787
|
-
pointerEvents: "none"
|
|
23788
|
-
},
|
|
23789
|
-
children: [
|
|
23790
|
-
clip,
|
|
23791
|
-
compactDiamonds,
|
|
23792
|
-
propertyLanes
|
|
23793
|
-
]
|
|
23794
|
-
},
|
|
23795
|
-
clipKey
|
|
23796
|
-
);
|
|
24102
|
+
return /* @__PURE__ */ jsxs37(Fragment18, { children: [
|
|
24103
|
+
clip,
|
|
24104
|
+
compactDiamonds,
|
|
24105
|
+
propertyLanes
|
|
24106
|
+
] }, clipKey);
|
|
23797
24107
|
}),
|
|
23798
24108
|
/* @__PURE__ */ jsx57(
|
|
23799
24109
|
TimelineTransitionOverlays,
|
|
@@ -26149,9 +26459,9 @@ function mergeAudioInSource(source, input) {
|
|
|
26149
26459
|
}
|
|
26150
26460
|
var tagOf2 = (el) => el.tag.trim().toLowerCase();
|
|
26151
26461
|
var keyOf11 = (el) => el.key ?? el.id;
|
|
26152
|
-
var
|
|
26462
|
+
var near3 = (a, b) => Math.abs(a - b) <= SYNC_TOLERANCE_S;
|
|
26153
26463
|
function hasIdenticalTiming(a, b) {
|
|
26154
|
-
return
|
|
26464
|
+
return near3(a.start, b.start) && near3(a.duration, b.duration) && near3(a.playbackStart ?? 0, b.playbackStart ?? 0) && near3(a.playbackRate ?? 1, b.playbackRate ?? 1);
|
|
26155
26465
|
}
|
|
26156
26466
|
var sameAssetInScope = (a, b) => {
|
|
26157
26467
|
const asset = mediaAssetIdentity(a);
|
|
@@ -26192,7 +26502,7 @@ function isPairInSync(a, b) {
|
|
|
26192
26502
|
playbackRate: el.playbackRate ?? 1
|
|
26193
26503
|
});
|
|
26194
26504
|
const [x, y] = [timing(a), timing(b)];
|
|
26195
|
-
return
|
|
26505
|
+
return near3(x.playbackRate, y.playbackRate) && near3(sourceZeroTime(x), sourceZeroTime(y));
|
|
26196
26506
|
}
|
|
26197
26507
|
function pickDetachedAudioTrack(elements, window2) {
|
|
26198
26508
|
const trackOf = (el) => el.authoredTrack ?? el.track;
|
|
@@ -27268,8 +27578,8 @@ async function decodeVideoThumbnail(request, signal, budgets = TIMELINE_VIEWPORT
|
|
|
27268
27578
|
for (const time of timestamps) {
|
|
27269
27579
|
const key2 = await keys.getKeyPacket(time, { metadataOnly: true });
|
|
27270
27580
|
if (signal.aborted) return;
|
|
27271
|
-
const
|
|
27272
|
-
yield
|
|
27581
|
+
const near4 = key2 && key2.timestamp >= sourceStart && time - key2.timestamp <= maxKeyframeLead;
|
|
27582
|
+
yield near4 ? key2.timestamp : time;
|
|
27273
27583
|
}
|
|
27274
27584
|
}
|
|
27275
27585
|
const aspect = displayWidth / displayHeight;
|
|
@@ -29383,8 +29693,75 @@ var STUDIO_MANUAL_EDITS_WRAPPED_PROP = "__hfStudioManualEditsWrapped";
|
|
|
29383
29693
|
var STUDIO_MANUAL_EDITS_PLAYBACK_FRAME_PROP = "__hfStudioManualEditsPlaybackFrame";
|
|
29384
29694
|
var STUDIO_ROTATION_TRANSFORM_ORIGIN = "center center";
|
|
29385
29695
|
|
|
29386
|
-
// src/hooks/
|
|
29696
|
+
// src/hooks/gsapRuntimeMotionPath.ts
|
|
29387
29697
|
import { buildArcPath } from "@hyperframes/core/gsap-parser-acorn";
|
|
29698
|
+
function isXY(p) {
|
|
29699
|
+
return !!p && typeof p.x === "number" && typeof p.y === "number";
|
|
29700
|
+
}
|
|
29701
|
+
function coordsFromMotionPath(mp) {
|
|
29702
|
+
if (!mp || typeof mp !== "object") return null;
|
|
29703
|
+
const obj = mp;
|
|
29704
|
+
const pathVal = Array.isArray(mp) ? mp : obj.path;
|
|
29705
|
+
if (!Array.isArray(pathVal)) return null;
|
|
29706
|
+
const coords = pathVal.filter(isXY).map((p) => ({ x: p.x, y: p.y }));
|
|
29707
|
+
if (coords.length < 2) return null;
|
|
29708
|
+
const curviness = typeof obj.curviness === "number" ? obj.curviness : 1;
|
|
29709
|
+
const autoRotate = typeof obj.autoRotate === "number" ? obj.autoRotate : obj.autoRotate === true;
|
|
29710
|
+
return { coords, curviness, autoRotate, isCubic: obj.type === "cubic" };
|
|
29711
|
+
}
|
|
29712
|
+
function readMotionPathTween(vars, runEase) {
|
|
29713
|
+
const mp = coordsFromMotionPath(vars.motionPath);
|
|
29714
|
+
const shape = mp && buildArcPath(mp.coords, mp.curviness, mp.autoRotate, mp.isCubic);
|
|
29715
|
+
if (!shape) return null;
|
|
29716
|
+
const n = shape.waypoints.length;
|
|
29717
|
+
const keyframes = shape.waypoints.map((wp, i) => ({
|
|
29718
|
+
percentage: n > 1 ? Math.round(i / (n - 1) * 100) : 0,
|
|
29719
|
+
properties: { x: wp.x, y: wp.y }
|
|
29720
|
+
}));
|
|
29721
|
+
return { keyframes, arcPath: shape.arcPath, runEase: runEase ?? MOTION_PATH_RUN_EASE };
|
|
29722
|
+
}
|
|
29723
|
+
|
|
29724
|
+
// src/hooks/gsapRuntimeTweenIndex.ts
|
|
29725
|
+
function matchesElement(tween, el) {
|
|
29726
|
+
if (!tween.targets) return false;
|
|
29727
|
+
for (const t2 of tween.targets()) {
|
|
29728
|
+
if (t2 === el || el.id && t2.id === el.id) return true;
|
|
29729
|
+
}
|
|
29730
|
+
return false;
|
|
29731
|
+
}
|
|
29732
|
+
var tweenIndexes = null;
|
|
29733
|
+
function withTweenIndex(run) {
|
|
29734
|
+
const outer = tweenIndexes;
|
|
29735
|
+
tweenIndexes ??= /* @__PURE__ */ new WeakMap();
|
|
29736
|
+
try {
|
|
29737
|
+
return run();
|
|
29738
|
+
} finally {
|
|
29739
|
+
tweenIndexes = outer;
|
|
29740
|
+
}
|
|
29741
|
+
}
|
|
29742
|
+
function indexTweens(children) {
|
|
29743
|
+
const index = { byElement: /* @__PURE__ */ new Map(), byId: /* @__PURE__ */ new Map() };
|
|
29744
|
+
const add = (map, key2, tween) => {
|
|
29745
|
+
const list = map.get(key2);
|
|
29746
|
+
if (!list) map.set(key2, [tween]);
|
|
29747
|
+
else if (list.at(-1) !== tween) list.push(tween);
|
|
29748
|
+
};
|
|
29749
|
+
for (const tween of children) {
|
|
29750
|
+
for (const target of tween.targets?.() ?? []) {
|
|
29751
|
+
add(index.byElement, target, tween);
|
|
29752
|
+
const id = target.id;
|
|
29753
|
+
if (id) add(index.byId, id, tween);
|
|
29754
|
+
}
|
|
29755
|
+
}
|
|
29756
|
+
return index;
|
|
29757
|
+
}
|
|
29758
|
+
function tweensTargeting(timeline, el) {
|
|
29759
|
+
if (!timeline || typeof timeline !== "object" || !tweenIndexes)
|
|
29760
|
+
return (timeline?.getChildren?.(true) ?? []).filter((tween) => matchesElement(tween, el));
|
|
29761
|
+
let index = tweenIndexes.get(timeline);
|
|
29762
|
+
if (!index) tweenIndexes.set(timeline, index = indexTweens(timeline.getChildren?.(true) ?? []));
|
|
29763
|
+
return (el.id ? index.byId.get(el.id) : index.byElement.get(el)) ?? [];
|
|
29764
|
+
}
|
|
29388
29765
|
|
|
29389
29766
|
// src/hooks/gsapParsedTween.ts
|
|
29390
29767
|
var PARSED_NAME = { scale: "scaleX", rotate: "rotation" };
|
|
@@ -29547,14 +29924,13 @@ var BUILT_IN_EASES = [
|
|
|
29547
29924
|
function withExactStepTimes(anim, tween) {
|
|
29548
29925
|
const data = anim.keyframes;
|
|
29549
29926
|
const parts = tween?.timeline?.getChildren?.() ?? [];
|
|
29550
|
-
|
|
29551
|
-
|
|
29552
|
-
|
|
29553
|
-
|
|
29554
|
-
if (ends.some((end) => !Number.isFinite(end))) return anim;
|
|
29927
|
+
if (data?.format !== "object-array" || parts.length !== data.keyframes.length) return anim;
|
|
29928
|
+
const ends = parts.map((part) => (part.startTime?.() ?? 0) + (part.duration?.() ?? 0));
|
|
29929
|
+
const total = Math.max(...ends);
|
|
29930
|
+
if (!(total > 0) || ends.some((end) => !Number.isFinite(end))) return anim;
|
|
29555
29931
|
const keyframes = data.keyframes.map((kf, i) => ({
|
|
29556
29932
|
...kf,
|
|
29557
|
-
percentage: Math.round(ends[i] * 1e5) / 1e3
|
|
29933
|
+
percentage: Math.round(ends[i] / total * 1e5) / 1e3
|
|
29558
29934
|
}));
|
|
29559
29935
|
return { ...anim, keyframes: { ...data, keyframes } };
|
|
29560
29936
|
}
|
|
@@ -29716,23 +30092,9 @@ function timelinesOf(iframe) {
|
|
|
29716
30092
|
return null;
|
|
29717
30093
|
}
|
|
29718
30094
|
}
|
|
29719
|
-
function isXY(p) {
|
|
29720
|
-
return !!p && typeof p.x === "number" && typeof p.y === "number";
|
|
29721
|
-
}
|
|
29722
30095
|
function isZeroDurationSet(duration) {
|
|
29723
30096
|
return !(duration > 0);
|
|
29724
30097
|
}
|
|
29725
|
-
function coordsFromMotionPath(mp) {
|
|
29726
|
-
if (!mp || typeof mp !== "object") return null;
|
|
29727
|
-
const obj = mp;
|
|
29728
|
-
const pathVal = Array.isArray(mp) ? mp : obj.path;
|
|
29729
|
-
if (!Array.isArray(pathVal)) return null;
|
|
29730
|
-
const coords = pathVal.filter(isXY).map((p) => ({ x: p.x, y: p.y }));
|
|
29731
|
-
if (coords.length < 2) return null;
|
|
29732
|
-
const curviness = typeof obj.curviness === "number" ? obj.curviness : 1;
|
|
29733
|
-
const autoRotate = typeof obj.autoRotate === "number" ? obj.autoRotate : obj.autoRotate === true;
|
|
29734
|
-
return { coords, curviness, autoRotate, isCubic: obj.type === "cubic" };
|
|
29735
|
-
}
|
|
29736
30098
|
function flatTweenKeyframes(vars) {
|
|
29737
30099
|
const properties = {};
|
|
29738
30100
|
for (const [k, v] of Object.entries(vars)) {
|
|
@@ -29746,33 +30108,19 @@ function flatTweenKeyframes(vars) {
|
|
|
29746
30108
|
{ percentage: 100, properties }
|
|
29747
30109
|
];
|
|
29748
30110
|
}
|
|
30111
|
+
var stringOr = (value) => typeof value === "string" ? value : void 0;
|
|
29749
30112
|
function readTween(vars) {
|
|
30113
|
+
const runEase = stringOr(vars.ease);
|
|
29750
30114
|
if (vars.keyframes && typeof vars.keyframes === "object") {
|
|
29751
30115
|
const parsed = parsePercentageKeyframes(vars.keyframes);
|
|
29752
|
-
|
|
29753
|
-
|
|
29754
|
-
const mp = coordsFromMotionPath(vars.motionPath);
|
|
29755
|
-
if (mp) {
|
|
29756
|
-
const shape = buildArcPath(mp.coords, mp.curviness, mp.autoRotate, mp.isCubic);
|
|
29757
|
-
if (shape) {
|
|
29758
|
-
const n = shape.waypoints.length;
|
|
29759
|
-
const keyframes = shape.waypoints.map((wp, i) => ({
|
|
29760
|
-
percentage: n > 1 ? Math.round(i / (n - 1) * 100) : 0,
|
|
29761
|
-
properties: { x: wp.x, y: wp.y }
|
|
29762
|
-
}));
|
|
29763
|
-
return { keyframes, arcPath: shape.arcPath };
|
|
29764
|
-
}
|
|
30116
|
+
const keyframesEase = stringOr(vars.keyframes.ease);
|
|
30117
|
+
if (parsed) return { ...parsed, runEase: keyframesEase ?? runEase };
|
|
29765
30118
|
}
|
|
30119
|
+
const path = readMotionPathTween(vars, runEase);
|
|
30120
|
+
if (path) return path;
|
|
29766
30121
|
const flat = flatTweenKeyframes(vars);
|
|
29767
30122
|
return flat ? { keyframes: flat } : null;
|
|
29768
30123
|
}
|
|
29769
|
-
function matchesElement(tween, el) {
|
|
29770
|
-
if (!tween.targets) return false;
|
|
29771
|
-
for (const t2 of tween.targets()) {
|
|
29772
|
-
if (t2 === el || el.id && t2.id === el.id) return true;
|
|
29773
|
-
}
|
|
29774
|
-
return false;
|
|
29775
|
-
}
|
|
29776
30124
|
function tweenTiming(tween) {
|
|
29777
30125
|
const rawStart = typeof tween.startTime === "function" ? tween.startTime() : 0;
|
|
29778
30126
|
const rawDur = typeof tween.duration === "function" ? tween.duration() : 0;
|
|
@@ -29904,8 +30252,8 @@ var GSAP_TRANSFORM_KEYS = new Set(
|
|
|
29904
30252
|
function gsapWritesChannels(el, channels) {
|
|
29905
30253
|
const win = el.ownerDocument.defaultView;
|
|
29906
30254
|
return Object.values(win?.__timelines ?? {}).some(
|
|
29907
|
-
(tl) => (tl
|
|
29908
|
-
(tween) => !!tween.vars &&
|
|
30255
|
+
(tl) => tweensTargeting(tl, el).some(
|
|
30256
|
+
(tween) => !!tween.vars && (channels.some((ch) => ch in tween.vars) || keyframeVarsCarryChannel(tween.vars, channels))
|
|
29909
30257
|
)
|
|
29910
30258
|
);
|
|
29911
30259
|
}
|
|
@@ -29950,8 +30298,8 @@ function elementHasNonHoldTween(el, channels) {
|
|
|
29950
30298
|
function hasNonHoldTween(timelines, targetEl, channels, compositionId) {
|
|
29951
30299
|
if (!timelines) return false;
|
|
29952
30300
|
for (const tlId of compositionId ? [compositionId] : Object.keys(timelines)) {
|
|
29953
|
-
for (const tween of timelines[tlId]
|
|
29954
|
-
if (!tween.vars
|
|
30301
|
+
for (const tween of tweensTargeting(timelines[tlId], targetEl)) {
|
|
30302
|
+
if (!tween.vars) continue;
|
|
29955
30303
|
const dur = typeof tween.duration === "function" ? tween.duration() : 0;
|
|
29956
30304
|
if (isZeroDurationSet(dur)) continue;
|
|
29957
30305
|
if (channels && keyframeVarsCarryChannel(tween.vars, channels)) return true;
|
|
@@ -29961,16 +30309,17 @@ function hasNonHoldTween(timelines, targetEl, channels, compositionId) {
|
|
|
29961
30309
|
}
|
|
29962
30310
|
return false;
|
|
29963
30311
|
}
|
|
29964
|
-
function toClipRelative(
|
|
30312
|
+
function toClipRelative(read2, tweenStart, tweenDuration, clip) {
|
|
30313
|
+
const { keyframes, runEase } = read2;
|
|
29965
30314
|
if (!clip || clip.duration <= 0) return keyframes;
|
|
29966
30315
|
return keyframes.map((kf) => {
|
|
29967
|
-
const abs = toAbsoluteTime(tweenStart, tweenDuration, kf.percentage);
|
|
30316
|
+
const abs = toAbsoluteTime(tweenStart, tweenDuration, timeAtProgress(runEase, kf.percentage));
|
|
29968
30317
|
return { ...kf, percentage: Math.round((abs - clip.start) / clip.duration * 1e5) / 1e3 };
|
|
29969
30318
|
});
|
|
29970
30319
|
}
|
|
29971
30320
|
function buildEntry(read2, start, duration, clip) {
|
|
29972
30321
|
return {
|
|
29973
|
-
keyframes: toClipRelative(read2
|
|
30322
|
+
keyframes: toClipRelative(read2, start, duration, clip),
|
|
29974
30323
|
tweenStart: start,
|
|
29975
30324
|
tweenDuration: duration,
|
|
29976
30325
|
...read2.easeEach ? { easeEach: read2.easeEach } : {},
|
|
@@ -31971,7 +32320,7 @@ function NLEProvider({
|
|
|
31971
32320
|
}, [projectId, setCompIdToSrc]);
|
|
31972
32321
|
useEffect37(() => {
|
|
31973
32322
|
if (compIdToSrc.size === 0) return;
|
|
31974
|
-
const
|
|
32323
|
+
const patchElements2 = (elements) => {
|
|
31975
32324
|
const map = compIdToSrcRef.current;
|
|
31976
32325
|
if (map.size === 0) return null;
|
|
31977
32326
|
let patched2 = false;
|
|
@@ -31986,7 +32335,7 @@ function NLEProvider({
|
|
|
31986
32335
|
});
|
|
31987
32336
|
return patched2 ? updated : null;
|
|
31988
32337
|
};
|
|
31989
|
-
const patched =
|
|
32338
|
+
const patched = patchElements2(usePlayerStore.getState().elements);
|
|
31990
32339
|
if (patched) usePlayerStore.getState().setElements(patched);
|
|
31991
32340
|
let patching = false;
|
|
31992
32341
|
return usePlayerStore.subscribe((state, prev) => {
|
|
@@ -31994,7 +32343,7 @@ function NLEProvider({
|
|
|
31994
32343
|
if (state.elements === prev.elements || state.elements.length === 0) return;
|
|
31995
32344
|
if (state.elements.every((el) => el.compositionSrc)) return;
|
|
31996
32345
|
patching = true;
|
|
31997
|
-
const result =
|
|
32346
|
+
const result = patchElements2(state.elements);
|
|
31998
32347
|
if (result) state.setElements(result);
|
|
31999
32348
|
patching = false;
|
|
32000
32349
|
});
|
|
@@ -36477,7 +36826,7 @@ function OffCanvasIndicators({
|
|
|
36477
36826
|
const selectOffCanvas = async () => {
|
|
36478
36827
|
const el = elements.current.get(r.key);
|
|
36479
36828
|
if (!el) return;
|
|
36480
|
-
const { resolveDomEditSelection: resolveDomEditSelection2 } = await import("./domEditingLayers-
|
|
36829
|
+
const { resolveDomEditSelection: resolveDomEditSelection2 } = await import("./domEditingLayers-IEVLO54Z.js");
|
|
36481
36830
|
const acp = activeCompositionPathRef.current ?? "index.html";
|
|
36482
36831
|
const sel = await resolveDomEditSelection2(el, {
|
|
36483
36832
|
activeCompositionPath: acp,
|
|
@@ -43729,14 +44078,7 @@ function useMirrorLaneMoveCommit() {
|
|
|
43729
44078
|
// NO readZIndex / onStackingPatches: see the hook doc — the lane→z
|
|
43730
44079
|
// stacking sync must not re-trigger and fight the just-set z values.
|
|
43731
44080
|
},
|
|
43732
|
-
coalesceKey
|
|
43733
|
-
// Unbounded fold window: this record lands only AFTER the z persist's
|
|
43734
|
-
// server round-trip resolved, so the gap between the gesture's two
|
|
43735
|
-
// records exceeds editHistory's 300ms default under real latency and
|
|
43736
|
-
// the fold would silently split into two undo entries. The shared key
|
|
43737
|
-
// is unique per gesture (zReorderCoalesceKey's gesture seq), so the
|
|
43738
|
-
// unbounded window can never merge two distinct user actions.
|
|
43739
|
-
Number.POSITIVE_INFINITY
|
|
44081
|
+
coalesceKey
|
|
43740
44082
|
);
|
|
43741
44083
|
},
|
|
43742
44084
|
[onMoveElements]
|
|
@@ -44869,7 +45211,23 @@ function TopologyLens({ iframeRef, activeCompositionPath }) {
|
|
|
44869
45211
|
);
|
|
44870
45212
|
}
|
|
44871
45213
|
|
|
45214
|
+
// src/utils/sameData.ts
|
|
45215
|
+
function sameData(a, b) {
|
|
45216
|
+
if (Object.is(a, b)) return true;
|
|
45217
|
+
if (!a || !b || typeof a !== "object" || typeof b !== "object") return false;
|
|
45218
|
+
if ("nodeType" in a || "nodeType" in b || Array.isArray(a) !== Array.isArray(b)) return false;
|
|
45219
|
+
const keys = Object.keys(a);
|
|
45220
|
+
if (keys.length !== Object.keys(b).length) return false;
|
|
45221
|
+
return keys.every((key2) => sameData(a[key2], b[key2]));
|
|
45222
|
+
}
|
|
45223
|
+
|
|
44872
45224
|
// src/hooks/gsapKeyframeCacheHelpers.ts
|
|
45225
|
+
function keepEqualEntries(before, draft) {
|
|
45226
|
+
for (const [key2, value] of draft) {
|
|
45227
|
+
const shown2 = before.get(key2);
|
|
45228
|
+
if (shown2 !== void 0 && shown2 !== value && sameData(shown2, value)) draft.set(key2, shown2);
|
|
45229
|
+
}
|
|
45230
|
+
}
|
|
44873
45231
|
function sameEntries(before, after) {
|
|
44874
45232
|
if (before.size !== after.size) return false;
|
|
44875
45233
|
for (const [key2, value] of before) {
|
|
@@ -44884,6 +45242,8 @@ function publishKeyframeCache(edit) {
|
|
|
44884
45242
|
gsapAnimations: new Map(gsapAnimations)
|
|
44885
45243
|
};
|
|
44886
45244
|
edit(draft);
|
|
45245
|
+
keepEqualEntries(keyframeCache, draft.keyframeCache);
|
|
45246
|
+
keepEqualEntries(gsapAnimations, draft.gsapAnimations);
|
|
44887
45247
|
if (sameEntries(keyframeCache, draft.keyframeCache) && sameEntries(gsapAnimations, draft.gsapAnimations))
|
|
44888
45248
|
return;
|
|
44889
45249
|
usePlayerStore.setState({
|
|
@@ -44953,8 +45313,15 @@ function cachedElementIdsForFile(sourceFile, keyframeCache, gsapAnimations) {
|
|
|
44953
45313
|
const sfPrefix = `${sourceFile}#`;
|
|
44954
45314
|
const ids = /* @__PURE__ */ new Set();
|
|
44955
45315
|
for (const key2 of [...keyframeCache.keys(), ...gsapAnimations.keys()]) {
|
|
44956
|
-
if (
|
|
44957
|
-
|
|
45316
|
+
if (key2.startsWith(sfPrefix)) ids.add(key2.slice(sfPrefix.length));
|
|
45317
|
+
}
|
|
45318
|
+
if (sourceFile !== "index.html") return ids;
|
|
45319
|
+
for (const [key2, entry2] of keyframeCache) {
|
|
45320
|
+
const hash = key2.indexOf("#");
|
|
45321
|
+
const id = key2.slice(hash + 1);
|
|
45322
|
+
if (hash > 0 && !key2.startsWith(sfPrefix) && keyframeCache.get(sfPrefix + id) === entry2) {
|
|
45323
|
+
ids.delete(id);
|
|
45324
|
+
}
|
|
44958
45325
|
}
|
|
44959
45326
|
return ids;
|
|
44960
45327
|
}
|
|
@@ -45362,10 +45729,9 @@ function computeCurrentPercentage(selection, animation) {
|
|
|
45362
45729
|
return computeElementPercentage(usePlayerStore.getState().currentTime, selection, animation);
|
|
45363
45730
|
}
|
|
45364
45731
|
function parkPlayheadOnKeyframe(anim, pct) {
|
|
45365
|
-
const
|
|
45366
|
-
|
|
45367
|
-
|
|
45368
|
-
usePlayerStore.getState().requestSeek(roundTo3(ts + pct / 100 * td));
|
|
45732
|
+
const time = percentageToAbsoluteForAnimation(pct, anim);
|
|
45733
|
+
if (time === null || resolveTweenDuration(anim) <= 0) return;
|
|
45734
|
+
usePlayerStore.getState().requestSeek(roundTo3(time));
|
|
45369
45735
|
}
|
|
45370
45736
|
async function replaceKeyframedPositionHold(selection, existingSet, properties, commitMutation) {
|
|
45371
45737
|
const target = newTweenTarget(selection);
|
|
@@ -45740,7 +46106,7 @@ function findGsapPositionAnimation(animations, selector) {
|
|
|
45740
46106
|
}
|
|
45741
46107
|
function playheadProximity(a, currentTime) {
|
|
45742
46108
|
const pos = a.resolvedStart ?? (typeof a.position === "number" ? a.position : 0);
|
|
45743
|
-
const dur = a
|
|
46109
|
+
const dur = resolveTweenDuration(a);
|
|
45744
46110
|
if (currentTime >= pos - 0.05 && currentTime <= pos + dur + 0.05) return 50;
|
|
45745
46111
|
return -Math.round(Math.min(Math.abs(currentTime - pos), Math.abs(currentTime - pos - dur)) * 5);
|
|
45746
46112
|
}
|
|
@@ -45748,10 +46114,12 @@ function statesValueAt(anim, time) {
|
|
|
45748
46114
|
const duration = resolveTweenDuration(anim);
|
|
45749
46115
|
if (!(duration > 0)) return false;
|
|
45750
46116
|
const pct = (time - (resolveTweenStart(anim) ?? 0)) / duration * 100;
|
|
45751
|
-
const
|
|
45752
|
-
|
|
45753
|
-
if (anim.
|
|
45754
|
-
|
|
46117
|
+
const near4 = (keyframe) => Math.abs(keyframe - pct) <= KEYFRAME_PCT_MATCH;
|
|
46118
|
+
const runEase = runEaseOf(anim);
|
|
46119
|
+
if (anim.keyframes)
|
|
46120
|
+
return anim.keyframes.keyframes.some((kf) => near4(timeAtProgress(runEase, kf.percentage)));
|
|
46121
|
+
if (anim.method === "from") return near4(0);
|
|
46122
|
+
return near4(100) || anim.method === "fromTo" && near4(0);
|
|
45755
46123
|
}
|
|
45756
46124
|
function pickClosestToPlayhead(anims) {
|
|
45757
46125
|
if (anims.length <= 1) return anims[0] ?? null;
|
|
@@ -46054,8 +46422,6 @@ function tweensForThisElement(selection, animations) {
|
|
|
46054
46422
|
}
|
|
46055
46423
|
|
|
46056
46424
|
// src/hooks/gsapValueAtPlayhead.ts
|
|
46057
|
-
var PERCENTAGE_SEGMENT_EASE = "power1.inOut";
|
|
46058
|
-
var ARRAY_STEP_EASE = "none";
|
|
46059
46425
|
var roundPct2 = (pct) => Math.round(pct * 1e3) / 1e3;
|
|
46060
46426
|
var isLinear = (ease) => !ease || ease === "none" || ease === "linear";
|
|
46061
46427
|
function refuse(reason) {
|
|
@@ -46070,7 +46436,7 @@ function normalize(edit) {
|
|
|
46070
46436
|
const blocked = stepListBlock(anim);
|
|
46071
46437
|
if (blocked) return { reason: blocked };
|
|
46072
46438
|
return {
|
|
46073
|
-
runEase:
|
|
46439
|
+
runEase: runEaseOf(anim),
|
|
46074
46440
|
keyframes: data.keyframes.map((kf) => ({
|
|
46075
46441
|
...kf,
|
|
46076
46442
|
properties: { ...kf.properties },
|
|
@@ -47451,7 +47817,7 @@ function readAllAnimatedProperties(iframe, selector, anim, group) {
|
|
|
47451
47817
|
// src/hooks/gsapDragPositionCommit.ts
|
|
47452
47818
|
function buildTemporalArcKeyframes(anim, percentage, properties) {
|
|
47453
47819
|
return [
|
|
47454
|
-
...(anim
|
|
47820
|
+
...linearArcKeyframes(anim).filter((keyframe) => Math.abs(keyframe.percentage - percentage) > KEYFRAME_PCT_MATCH).map((keyframe) => ({
|
|
47455
47821
|
percentage: keyframe.percentage,
|
|
47456
47822
|
properties: { ...keyframe.properties },
|
|
47457
47823
|
...keyframe.ease ? { ease: keyframe.ease } : {}
|
|
@@ -47459,6 +47825,35 @@ function buildTemporalArcKeyframes(anim, percentage, properties) {
|
|
|
47459
47825
|
{ percentage, properties }
|
|
47460
47826
|
].sort((a, b) => a.percentage - b.percentage);
|
|
47461
47827
|
}
|
|
47828
|
+
function linearArcKeyframes(anim) {
|
|
47829
|
+
const runEase = runEaseOf(anim);
|
|
47830
|
+
return (anim.keyframes?.keyframes ?? []).map((keyframe) => ({
|
|
47831
|
+
...keyframe,
|
|
47832
|
+
percentage: roundTo3(timeAtProgress(runEase, keyframe.percentage))
|
|
47833
|
+
}));
|
|
47834
|
+
}
|
|
47835
|
+
function buildExtendedKeyframes(anim, currentTime, position, sourceDuration = resolveTweenDuration(anim), writeEase = runEaseOf(anim)) {
|
|
47836
|
+
const oldStart = resolveTweenStart(anim) ?? 0;
|
|
47837
|
+
const oldDuration = sourceDuration;
|
|
47838
|
+
const newStart = Math.min(oldStart, currentTime);
|
|
47839
|
+
const newEnd = Math.max(oldStart + oldDuration, currentTime);
|
|
47840
|
+
const newDuration = roundTo3(newEnd - newStart);
|
|
47841
|
+
const oldRunEase = runEaseOf(anim);
|
|
47842
|
+
const toPct = (absoluteTime) => {
|
|
47843
|
+
if (newDuration <= 0) return 0;
|
|
47844
|
+
const progress = progressAtTime(writeEase, (absoluteTime - newStart) / newDuration * 100);
|
|
47845
|
+
return Math.max(0, Math.min(100, Math.round(progress * 10) / 10));
|
|
47846
|
+
};
|
|
47847
|
+
const stops = anim.keyframes?.keyframes ?? [];
|
|
47848
|
+
const rescaled = stops.map((stop2) => ({
|
|
47849
|
+
percentage: toPct(oldStart + timeAtProgress(oldRunEase, stop2.percentage) / 100 * oldDuration),
|
|
47850
|
+
properties: stop2.properties,
|
|
47851
|
+
...stop2.ease ? { ease: stop2.ease } : {}
|
|
47852
|
+
}));
|
|
47853
|
+
const added = { percentage: toPct(currentTime), properties: position };
|
|
47854
|
+
const keyframes = [...rescaled, added].sort((a, b) => a.percentage - b.percentage);
|
|
47855
|
+
return { position: roundTo3(newStart), duration: newDuration, keyframes };
|
|
47856
|
+
}
|
|
47462
47857
|
function gsapPositionFromDragOutcome(selection, anim, studioOffset, gsapPos, iframe) {
|
|
47463
47858
|
if (anim.arcPath?.enabled) return { status: "persisted" };
|
|
47464
47859
|
const { newX, newY, baseGsapX, baseGsapY } = computeDraggedGsapPosition(
|
|
@@ -47487,12 +47882,38 @@ async function commitGsapPositionFromDrag(selection, anim, studioOffset, gsapPos
|
|
|
47487
47882
|
el.removeAttribute("data-hf-drag-initial-offset-y");
|
|
47488
47883
|
};
|
|
47489
47884
|
if (anim.arcPath?.enabled) {
|
|
47490
|
-
const { activeKeyframePct, setActiveKeyframePct } = usePlayerStore.getState();
|
|
47885
|
+
const { activeKeyframePct, currentTime, setActiveKeyframePct } = usePlayerStore.getState();
|
|
47886
|
+
const tweenStart = resolveTweenStart(anim);
|
|
47887
|
+
const tweenDuration = resolveTweenDuration(anim);
|
|
47888
|
+
if (activeKeyframePct === null && tweenStart !== null && !isTimeWithinTween(currentTime, tweenStart, tweenDuration)) {
|
|
47889
|
+
const extended = buildExtendedKeyframes(
|
|
47890
|
+
anim,
|
|
47891
|
+
currentTime,
|
|
47892
|
+
{ x: newX, y: newY },
|
|
47893
|
+
tweenDuration,
|
|
47894
|
+
"none"
|
|
47895
|
+
);
|
|
47896
|
+
await callbacks.commitMutation(
|
|
47897
|
+
selection,
|
|
47898
|
+
{
|
|
47899
|
+
type: "replace-with-keyframes",
|
|
47900
|
+
animationId: anim.id,
|
|
47901
|
+
targetSelector: anim.targetSelector,
|
|
47902
|
+
...extended,
|
|
47903
|
+
ease: "none"
|
|
47904
|
+
},
|
|
47905
|
+
{
|
|
47906
|
+
label: "Move layer (new keyframe)",
|
|
47907
|
+
keyframeAction: "add",
|
|
47908
|
+
softReload: true,
|
|
47909
|
+
beforeReload: restoreOffset
|
|
47910
|
+
}
|
|
47911
|
+
);
|
|
47912
|
+
return { status: "persisted" };
|
|
47913
|
+
}
|
|
47491
47914
|
const pct = activeKeyframePct ?? computeCurrentPercentage(selection, anim);
|
|
47492
47915
|
const keyframes = anim.keyframes?.keyframes ?? [];
|
|
47493
|
-
const pointIndex = keyframes.findIndex(
|
|
47494
|
-
(kf) => Math.abs(kf.percentage - pct) <= KEYFRAME_PCT_MATCH
|
|
47495
|
-
);
|
|
47916
|
+
const pointIndex = keyframes.findIndex((kf) => playsNear(anim, kf.percentage, pct));
|
|
47496
47917
|
if (pointIndex >= 0) {
|
|
47497
47918
|
await callbacks.commitMutation(
|
|
47498
47919
|
selection,
|
|
@@ -47509,11 +47930,10 @@ async function commitGsapPositionFromDrag(selection, anim, studioOffset, gsapPos
|
|
|
47509
47930
|
parkPlayheadOnKeyframe(anim, pct);
|
|
47510
47931
|
return { status: "persisted" };
|
|
47511
47932
|
}
|
|
47512
|
-
const tweenStart = resolveTweenStart(anim);
|
|
47513
|
-
const tweenDuration = resolveEditableTweenDuration(anim, selection);
|
|
47514
47933
|
if (tweenStart === null || tweenDuration <= 0 || keyframes.length < 2)
|
|
47515
47934
|
return { status: "persisted" };
|
|
47516
|
-
const
|
|
47935
|
+
const timePct = roundTo3(timeAtProgress(runEaseOf(anim), pct));
|
|
47936
|
+
const temporalKeyframes = buildTemporalArcKeyframes(anim, timePct, { x: newX, y: newY });
|
|
47517
47937
|
await callbacks.commitMutation(
|
|
47518
47938
|
selection,
|
|
47519
47939
|
{
|
|
@@ -48010,19 +48430,7 @@ function useGsapAnimationsForElement(projectId, sourceFile, target, version, ifr
|
|
|
48010
48430
|
if (isStaticPositionHold(anim)) continue;
|
|
48011
48431
|
const kf = anim.keyframes ?? synthesizeFlatTweenKeyframes(anim);
|
|
48012
48432
|
if (!kf) continue;
|
|
48013
|
-
|
|
48014
|
-
const tweenDur = anim.duration ?? elDuration;
|
|
48015
|
-
for (const k of kf.keyframes) {
|
|
48016
|
-
const absTime = toAbsoluteTime(tweenPos, tweenDur, k.percentage);
|
|
48017
|
-
const clipPct = toClipPercentage(absTime, elStart, elDuration, k.percentage);
|
|
48018
|
-
allKeyframes.push({
|
|
48019
|
-
...k,
|
|
48020
|
-
percentage: clipPct,
|
|
48021
|
-
tweenPercentage: k.percentage,
|
|
48022
|
-
propertyGroup: anim.propertyGroup,
|
|
48023
|
-
animationId: anim.id
|
|
48024
|
-
});
|
|
48025
|
-
}
|
|
48433
|
+
allKeyframes.push(...toClipKeyframes(kf.keyframes, anim, elStart, elDuration));
|
|
48026
48434
|
format = kf.format;
|
|
48027
48435
|
if (kf.ease) ease = kf.ease;
|
|
48028
48436
|
if (kf.easeEach) easeEach = kf.easeEach;
|
|
@@ -48074,11 +48482,11 @@ function usePopulateKeyframeCacheForFile(projectId, sourceFile, version, iframeR
|
|
|
48074
48482
|
const files = Array.from(
|
|
48075
48483
|
/* @__PURE__ */ new Set([sourceFile, ...compositionSrcKey ? compositionSrcKey.split("|") : []])
|
|
48076
48484
|
);
|
|
48077
|
-
const
|
|
48078
|
-
const stale =
|
|
48079
|
-
const covered =
|
|
48485
|
+
const sameData2 = loadedRef.current.dataKey === dataKey;
|
|
48486
|
+
const stale = sameData2 ? files.filter((sf) => !loadedRef.current.files.has(sf)) : files;
|
|
48487
|
+
const covered = sameData2 ? /* @__PURE__ */ new Set([...loadedRef.current.files, ...files]) : new Set(files);
|
|
48080
48488
|
loadedRef.current = { dataKey, files: covered };
|
|
48081
|
-
if (!
|
|
48489
|
+
if (!sameData2) pruneKeyframeCacheToFiles(files);
|
|
48082
48490
|
if (stale.length === 0) return;
|
|
48083
48491
|
runtimeScanDoneRef.current = "";
|
|
48084
48492
|
const doc = iframeRef?.current?.contentDocument;
|
|
@@ -48155,7 +48563,7 @@ function resolveFlatTweenBoundaryRetime(opts) {
|
|
|
48155
48563
|
};
|
|
48156
48564
|
}
|
|
48157
48565
|
function resolveKeyframeRetime(opts) {
|
|
48158
|
-
const { keyframes, draggedTweenPct, tweenStart, tweenDuration, dropAbsTime } = opts;
|
|
48566
|
+
const { keyframes, draggedTweenPct, tweenStart, tweenDuration, dropAbsTime, runEase } = opts;
|
|
48159
48567
|
if (tweenDuration <= 0) return { kind: "noop" };
|
|
48160
48568
|
const tweenEnd = tweenStart + tweenDuration;
|
|
48161
48569
|
const flatBoundary = resolveFlatTweenBoundaryRetime({
|
|
@@ -48167,7 +48575,8 @@ function resolveKeyframeRetime(opts) {
|
|
|
48167
48575
|
});
|
|
48168
48576
|
if (flatBoundary) return flatBoundary;
|
|
48169
48577
|
if (dropAbsTime >= tweenStart - EPSILON_TIME && dropAbsTime <= tweenEnd + EPSILON_TIME) {
|
|
48170
|
-
const
|
|
48578
|
+
const dropTimePct = clamp5((dropAbsTime - tweenStart) / tweenDuration * 100, 0, 100);
|
|
48579
|
+
const toTweenPct = round33(progressAtTime(runEase, dropTimePct));
|
|
48171
48580
|
if (Math.abs(toTweenPct - draggedTweenPct) < NOOP_EPSILON_PCT2) return { kind: "noop" };
|
|
48172
48581
|
return { kind: "move", toTweenPct };
|
|
48173
48582
|
}
|
|
@@ -48185,8 +48594,9 @@ function resolveKeyframeRetime(opts) {
|
|
|
48185
48594
|
}
|
|
48186
48595
|
});
|
|
48187
48596
|
const pctRemap = keyframes.map((kf, i) => {
|
|
48188
|
-
const absTime = i === draggedIdx ? dropAbsTime : tweenStart + kf.percentage / 100 * tweenDuration;
|
|
48189
|
-
|
|
48597
|
+
const absTime = i === draggedIdx ? dropAbsTime : tweenStart + timeAtProgress(runEase, kf.percentage) / 100 * tweenDuration;
|
|
48598
|
+
const newTimePct = (absTime - newStart) / newDuration * 100;
|
|
48599
|
+
return { from: kf.percentage, to: round33(progressAtTime(runEase, newTimePct)) };
|
|
48190
48600
|
});
|
|
48191
48601
|
return {
|
|
48192
48602
|
kind: "resize",
|
|
@@ -48365,14 +48775,15 @@ function useTimelineEditCallbacks({
|
|
|
48365
48775
|
elements,
|
|
48366
48776
|
domClipChildren
|
|
48367
48777
|
);
|
|
48368
|
-
const tweenDuration = resolveTweenDuration(anim
|
|
48778
|
+
const tweenDuration = resolveTweenDuration(anim);
|
|
48369
48779
|
const dropAbsTime = elStart + toClipPct / 100 * elDuration;
|
|
48370
48780
|
const decision = resolveKeyframeRetime({
|
|
48371
48781
|
keyframes: anim.keyframes?.keyframes ?? [],
|
|
48372
48782
|
draggedTweenPct: target.tweenPct,
|
|
48373
48783
|
tweenStart,
|
|
48374
48784
|
tweenDuration,
|
|
48375
|
-
dropAbsTime
|
|
48785
|
+
dropAbsTime,
|
|
48786
|
+
runEase: runEaseOf(anim)
|
|
48376
48787
|
});
|
|
48377
48788
|
if (decision.kind === "move" && decision.toTweenPct != null) {
|
|
48378
48789
|
return handleGsapMoveKeyframe(target.animId, target.tweenPct, decision.toTweenPct, sel);
|
|
@@ -73362,6 +73773,7 @@ function usePersistentEditHistory({ projectId }) {
|
|
|
73362
73773
|
const projectIdRef = useRef125(projectId);
|
|
73363
73774
|
projectIdRef.current = projectId;
|
|
73364
73775
|
const [own] = useState111(createOwnHistory);
|
|
73776
|
+
const [pageKeyScope] = useState111(generateId);
|
|
73365
73777
|
const refresh2 = useCallback100(async () => {
|
|
73366
73778
|
if (!projectId) return;
|
|
73367
73779
|
const seen = own.changes();
|
|
@@ -73379,6 +73791,10 @@ function usePersistentEditHistory({ projectId }) {
|
|
|
73379
73791
|
own.clear();
|
|
73380
73792
|
void refresh2().finally(() => setLoaded(true));
|
|
73381
73793
|
}, [refresh2, own]);
|
|
73794
|
+
useEffect111(() => {
|
|
73795
|
+
setStudioPendingEditClaimClock(own.claimCount);
|
|
73796
|
+
return () => setStudioPendingEditClaimClock(null);
|
|
73797
|
+
}, [own]);
|
|
73382
73798
|
const recordEdit = useCallback100(
|
|
73383
73799
|
async ({ label, coalesceKey, coalesceMs, files, created = [] }) => {
|
|
73384
73800
|
if (!projectId) return;
|
|
@@ -73388,7 +73804,10 @@ function usePersistentEditHistory({ projectId }) {
|
|
|
73388
73804
|
label,
|
|
73389
73805
|
paths,
|
|
73390
73806
|
overwrote: await overwroteVersions(files),
|
|
73391
|
-
...coalesceKey && {
|
|
73807
|
+
...coalesceKey && {
|
|
73808
|
+
coalesceKey: `${pageKeyScope}:${coalesceKey}`,
|
|
73809
|
+
idleMs: coalesceMs ?? DEFAULT_COALESCE_MS
|
|
73810
|
+
}
|
|
73392
73811
|
});
|
|
73393
73812
|
const claimed = claimHeld(reply, label);
|
|
73394
73813
|
if (claimed) {
|
|
@@ -73398,7 +73817,7 @@ function usePersistentEditHistory({ projectId }) {
|
|
|
73398
73817
|
heldClaimRef.current = claimed && coalesceKey ? { paths, at: Date.now() } : null;
|
|
73399
73818
|
await refresh2();
|
|
73400
73819
|
},
|
|
73401
|
-
[projectId, refresh2, own]
|
|
73820
|
+
[projectId, refresh2, own, pageKeyScope]
|
|
73402
73821
|
);
|
|
73403
73822
|
const step = useCallback100(
|
|
73404
73823
|
async (direction, callbacks) => {
|
|
@@ -74247,7 +74666,7 @@ function useFileManager({
|
|
|
74247
74666
|
import { useCallback as useCallback106, useRef as useRef130, useState as useState114 } from "react";
|
|
74248
74667
|
|
|
74249
74668
|
// src/utils/gsapUndoRestore.ts
|
|
74250
|
-
import { isCompositionTemplate as isCompositionTemplate2 } from "@hyperframes/parsers/hf-ids";
|
|
74669
|
+
import { ensureHfIds as ensureHfIds2, isCompositionTemplate as isCompositionTemplate2 } from "@hyperframes/parsers/hf-ids";
|
|
74251
74670
|
function elementIdentityKey(el) {
|
|
74252
74671
|
const hfId = el.getAttribute("data-hf-id");
|
|
74253
74672
|
if (hfId) return `hf:${hfId}`;
|
|
@@ -74383,8 +74802,10 @@ function scopeTargets(scope, keys, restoredByKey, previousByKey) {
|
|
|
74383
74802
|
return targets;
|
|
74384
74803
|
}
|
|
74385
74804
|
function fileTargets(doc, path, isActive, file) {
|
|
74386
|
-
const
|
|
74387
|
-
const
|
|
74805
|
+
const previewStamped = doc.querySelector("[data-hf-id]") !== null;
|
|
74806
|
+
const stampedLikeThePreview = (html2) => previewStamped && !html2.includes("data-hf-id") ? ensureHfIds2(html2) : html2;
|
|
74807
|
+
const prevDoc = parseRestoreSource(stampedLikeThePreview(file.previous));
|
|
74808
|
+
const nextDoc = parseRestoreSource(stampedLikeThePreview(file.restored));
|
|
74388
74809
|
const scripted = restoreScripted(prevDoc, nextDoc, isActive);
|
|
74389
74810
|
if (scripted === null) return null;
|
|
74390
74811
|
const keys = diffRestoreDocs(prevDoc, nextDoc);
|
|
@@ -74536,11 +74957,11 @@ function syncStoredElementAttribute(target, attr, value) {
|
|
|
74536
74957
|
const fields = STORED_FIELD[attr]?.(value);
|
|
74537
74958
|
if (!fields) return;
|
|
74538
74959
|
const key2 = getTimelineElementIdentity(target);
|
|
74539
|
-
const
|
|
74960
|
+
const same2 = (element) => Object.keys(fields).every((k) => element[k] === fields[k]);
|
|
74540
74961
|
usePlayerStore.setState((state) => {
|
|
74541
74962
|
let changed = false;
|
|
74542
74963
|
const elements = state.elements.map((element) => {
|
|
74543
|
-
if (getTimelineElementIdentity(element) !== key2 ||
|
|
74964
|
+
if (getTimelineElementIdentity(element) !== key2 || same2(element)) return element;
|
|
74544
74965
|
changed = true;
|
|
74545
74966
|
return { ...element, ...fields };
|
|
74546
74967
|
});
|
|
@@ -74665,6 +75086,10 @@ function usePreviewPersistence({
|
|
|
74665
75086
|
[applyCurrentStudioManualEditsToPreview, previewIframeRef]
|
|
74666
75087
|
);
|
|
74667
75088
|
applyStudioManualEditsToPreviewRef.current = applyStudioManualEditsToPreview;
|
|
75089
|
+
const heldPreviewDoc = useCallback106(() => {
|
|
75090
|
+
const doc = previewIframeRef.current?.contentDocument;
|
|
75091
|
+
return doc && isStudioManualEditGestureLiveIn(doc) ? doc : null;
|
|
75092
|
+
}, [previewIframeRef]);
|
|
74668
75093
|
const syncHistoryPreviewAfterApply = useCallback106(
|
|
74669
75094
|
async (restore2) => {
|
|
74670
75095
|
const projectId = usePlayerStore.getState().timelineProjectId;
|
|
@@ -74676,6 +75101,15 @@ function usePreviewPersistence({
|
|
|
74676
75101
|
(path) => projectId ? readProjectFileContent(projectId, path) : Promise.reject(new Error("No project is open to read nested files from."))
|
|
74677
75102
|
)
|
|
74678
75103
|
);
|
|
75104
|
+
const held = heldPreviewDoc();
|
|
75105
|
+
if (held) {
|
|
75106
|
+
const paths = restore2.paths ?? Object.keys(restore2.files ?? {});
|
|
75107
|
+
afterStudioManualEditGestures(held, () => {
|
|
75108
|
+
markScenesStale(previewIframeRef.current, paths);
|
|
75109
|
+
reloadPreview();
|
|
75110
|
+
});
|
|
75111
|
+
return;
|
|
75112
|
+
}
|
|
74679
75113
|
const strategy = applyUndoRestoreToPreview(
|
|
74680
75114
|
previewIframeRef.current,
|
|
74681
75115
|
activeCompPathRef.current,
|
|
@@ -74690,21 +75124,23 @@ function usePreviewPersistence({
|
|
|
74690
75124
|
}
|
|
74691
75125
|
syncStoredAutomationFromPreview(previewIframeRef.current?.contentDocument ?? null);
|
|
74692
75126
|
},
|
|
74693
|
-
[previewIframeRef, activeCompPathRef, reloadPreview]
|
|
75127
|
+
[previewIframeRef, activeCompPathRef, reloadPreview, heldPreviewDoc]
|
|
74694
75128
|
);
|
|
74695
75129
|
const showHistoryRestoreNow = useCallback106(
|
|
74696
75130
|
(files) => {
|
|
74697
|
-
if (!domEditSaveQueueRef.current?.isIdle() || hasStudioPendingEdits()
|
|
75131
|
+
if (!domEditSaveQueueRef.current?.isIdle() || hasStudioPendingEdits() || heldPreviewDoc())
|
|
75132
|
+
return null;
|
|
74698
75133
|
const iframe = previewIframeRef.current;
|
|
74699
75134
|
const now2 = () => usePlayerStore.getState().currentTime;
|
|
74700
75135
|
const putBack = showRestoreInPlace(iframe, activeCompPathRef.current, files, now2());
|
|
74701
75136
|
if (!putBack) return null;
|
|
74702
75137
|
return () => {
|
|
74703
|
-
if (putBack(now2()))
|
|
75138
|
+
if (!heldPreviewDoc() && putBack(now2()))
|
|
75139
|
+
syncStoredAutomationFromPreview(iframe?.contentDocument ?? null);
|
|
74704
75140
|
else void syncHistoryPreviewAfterApply({ paths: Object.keys(files) });
|
|
74705
75141
|
};
|
|
74706
75142
|
},
|
|
74707
|
-
[previewIframeRef, activeCompPathRef, syncHistoryPreviewAfterApply]
|
|
75143
|
+
[previewIframeRef, activeCompPathRef, syncHistoryPreviewAfterApply, heldPreviewDoc]
|
|
74708
75144
|
);
|
|
74709
75145
|
useMountEffect(() => {
|
|
74710
75146
|
void clearLegacyStudioMotionFile(_readOptionalProjectFile, _writeProjectFile);
|
|
@@ -74750,7 +75186,7 @@ function usePreviewDocumentVersion() {
|
|
|
74750
75186
|
}
|
|
74751
75187
|
|
|
74752
75188
|
// src/hooks/useTimelineEditing.ts
|
|
74753
|
-
import {
|
|
75189
|
+
import { useRef as useRef139 } from "react";
|
|
74754
75190
|
|
|
74755
75191
|
// src/hooks/useRazorSplit.ts
|
|
74756
75192
|
import { useCallback as useCallback109, useRef as useRef133 } from "react";
|
|
@@ -76050,7 +76486,6 @@ function applyRippleShifts(survivors, changes) {
|
|
|
76050
76486
|
return newStart != null ? { ...te, start: newStart } : te;
|
|
76051
76487
|
});
|
|
76052
76488
|
}
|
|
76053
|
-
var deleteGestureSeq = 0;
|
|
76054
76489
|
function unlinkInSource(source, survivors) {
|
|
76055
76490
|
const targets = survivors.map(buildPatchTarget).filter((target) => target !== null);
|
|
76056
76491
|
return targets.length > 0 ? setLinkInSource(source, targets, null) : source;
|
|
@@ -76086,7 +76521,7 @@ function useTimelineDeleteOps({
|
|
|
76086
76521
|
(candidate) => (candidate.sourceFile || activeCompPath || "index.html") === targetPath
|
|
76087
76522
|
);
|
|
76088
76523
|
try {
|
|
76089
|
-
const
|
|
76524
|
+
const step = timingGestureStep("main-track-ripple-delete");
|
|
76090
76525
|
const deleteHistoryLabel = "Delete timeline clip";
|
|
76091
76526
|
let rollbackDuration = () => {
|
|
76092
76527
|
};
|
|
@@ -76095,7 +76530,7 @@ function useTimelineDeleteOps({
|
|
|
76095
76530
|
projectId: pid,
|
|
76096
76531
|
path: targetPath,
|
|
76097
76532
|
label: deleteHistoryLabel,
|
|
76098
|
-
|
|
76533
|
+
...step,
|
|
76099
76534
|
writeFile: writeProjectFile,
|
|
76100
76535
|
recordEdit,
|
|
76101
76536
|
rewrite: async (originalContent) => {
|
|
@@ -76152,8 +76587,7 @@ function useTimelineDeleteOps({
|
|
|
76152
76587
|
if (rippleChanges) {
|
|
76153
76588
|
try {
|
|
76154
76589
|
await handleTimelineGroupMove(rippleChanges, {
|
|
76155
|
-
coalesceKey,
|
|
76156
|
-
coalesceMs: Number.POSITIVE_INFINITY,
|
|
76590
|
+
coalesceKey: step.coalesceKey,
|
|
76157
76591
|
// Coalescing keeps the LAST entry's label; without this the undo
|
|
76158
76592
|
// toast reads "Undid Move timeline clips" after a delete, naming
|
|
76159
76593
|
// the ripple's mechanics instead of what the user actually did.
|
|
@@ -76281,7 +76715,7 @@ function planSyncEdit(edit) {
|
|
|
76281
76715
|
property: "start",
|
|
76282
76716
|
value: value2
|
|
76283
76717
|
});
|
|
76284
|
-
return
|
|
76718
|
+
return syncCompositionDurationToContent(moved);
|
|
76285
76719
|
}
|
|
76286
76720
|
};
|
|
76287
76721
|
}
|
|
@@ -76925,12 +77359,6 @@ function allChangesSharePath(changes, activeCompPath) {
|
|
|
76925
77359
|
if (!firstPath) return null;
|
|
76926
77360
|
return changes.every((change) => targetPathFor(change.element, activeCompPath) === firstPath) ? firstPath : null;
|
|
76927
77361
|
}
|
|
76928
|
-
function moveCoalesceKey(changes) {
|
|
76929
|
-
return `timeline-group-move:${changes.map((change) => change.element.hfId ?? change.element.id).join(",")}`;
|
|
76930
|
-
}
|
|
76931
|
-
function resizeCoalesceKey(changes) {
|
|
76932
|
-
return `timeline-group-resize:${changes.map((change) => change.element.hfId ?? change.element.id).join(",")}`;
|
|
76933
|
-
}
|
|
76934
77362
|
function toSdkTimingChanges(changes, timingUpdate) {
|
|
76935
77363
|
return changes.map(
|
|
76936
77364
|
(change) => change.element.hfId ? { hfId: change.element.hfId, timingUpdate: timingUpdate(change) } : null
|
|
@@ -77053,8 +77481,7 @@ function useTimelineGroupEditing({
|
|
|
77053
77481
|
const rollbackDuration = captureDurationRollback(previewIframeRef.current);
|
|
77054
77482
|
const needsExtension = extendRootDurationIfNeeded(maxEnd);
|
|
77055
77483
|
syncPreviewContentDuration(previewIframeRef.current);
|
|
77056
|
-
const coalesceKey
|
|
77057
|
-
const coalesceMs = options?.coalesceMs;
|
|
77484
|
+
const { coalesceKey, coalesceMs } = timingGestureStep("timeline-group-move", options);
|
|
77058
77485
|
const label = options?.label ?? "Move timeline clips";
|
|
77059
77486
|
return enqueueGroupOperation(label, async (projectId) => {
|
|
77060
77487
|
await options?.beforeTiming;
|
|
@@ -77077,7 +77504,7 @@ function useTimelineGroupEditing({
|
|
|
77077
77504
|
label,
|
|
77078
77505
|
changes.map((change) => ({
|
|
77079
77506
|
element: change.element,
|
|
77080
|
-
buildPatches: (original, target) =>
|
|
77507
|
+
buildPatches: (original, target) => applyTimelineMoveAttributes(
|
|
77081
77508
|
original,
|
|
77082
77509
|
target,
|
|
77083
77510
|
toAuthoredStart(change.element, change.start),
|
|
@@ -77105,12 +77532,7 @@ function useTimelineGroupEditing({
|
|
|
77105
77532
|
changes,
|
|
77106
77533
|
sdkGsap: sdk?.sdkGsap,
|
|
77107
77534
|
resolveChangePath: (element) => targetPathFor(element, activeCompPath),
|
|
77108
|
-
|
|
77109
|
-
const delta = change.start - change.element.start;
|
|
77110
|
-
const domId = change.element.domId;
|
|
77111
|
-
if (delta === 0 || !domId) return null;
|
|
77112
|
-
return shiftGsapPositions(projectId, changePath, domId, delta);
|
|
77113
|
-
}
|
|
77535
|
+
mutationFor: (change) => shiftGsapMutation(change.element.domId, change.start - change.element.start)
|
|
77114
77536
|
});
|
|
77115
77537
|
} finally {
|
|
77116
77538
|
invalidateGsapCache?.();
|
|
@@ -77159,8 +77581,7 @@ function useTimelineGroupEditing({
|
|
|
77159
77581
|
const rollbackDuration = captureDurationRollback(previewIframeRef.current);
|
|
77160
77582
|
const needsExtension = extendRootDurationIfNeeded(maxEnd);
|
|
77161
77583
|
syncPreviewContentDuration(previewIframeRef.current);
|
|
77162
|
-
const coalesceKey
|
|
77163
|
-
const coalesceMs = options?.coalesceMs;
|
|
77584
|
+
const { coalesceKey, coalesceMs } = timingGestureStep("timeline-group-resize", options);
|
|
77164
77585
|
return enqueueGroupOperation("Resize timeline clips", async (projectId) => {
|
|
77165
77586
|
await options?.beforeTiming;
|
|
77166
77587
|
const sdk = await trySdkBatchPersist({
|
|
@@ -77181,7 +77602,7 @@ function useTimelineGroupEditing({
|
|
|
77181
77602
|
"Resize timeline clips",
|
|
77182
77603
|
changes.map((change) => ({
|
|
77183
77604
|
element: change.element,
|
|
77184
|
-
buildPatches: (original, target) =>
|
|
77605
|
+
buildPatches: (original, target) => applyTimelineResizeAttributes(original, target, change.element, {
|
|
77185
77606
|
start: change.start,
|
|
77186
77607
|
duration: change.duration,
|
|
77187
77608
|
playbackStart: change.playbackStart
|
|
@@ -77205,20 +77626,13 @@ function useTimelineGroupEditing({
|
|
|
77205
77626
|
changes,
|
|
77206
77627
|
sdkGsap: sdk?.sdkGsap,
|
|
77207
77628
|
resolveChangePath: (element) => targetPathFor(element, activeCompPath),
|
|
77208
|
-
|
|
77209
|
-
|
|
77210
|
-
|
|
77211
|
-
|
|
77212
|
-
|
|
77213
|
-
|
|
77214
|
-
|
|
77215
|
-
domId,
|
|
77216
|
-
toCompositionTime(change.element, change.element.start),
|
|
77217
|
-
change.element.duration,
|
|
77218
|
-
toCompositionTime(change.element, change.start),
|
|
77219
|
-
change.duration
|
|
77220
|
-
);
|
|
77221
|
-
}
|
|
77629
|
+
mutationFor: (change) => scaleGsapMutation(
|
|
77630
|
+
change.element.domId,
|
|
77631
|
+
toCompositionTime(change.element, change.element.start),
|
|
77632
|
+
change.element.duration,
|
|
77633
|
+
toCompositionTime(change.element, change.start),
|
|
77634
|
+
change.duration
|
|
77635
|
+
)
|
|
77222
77636
|
});
|
|
77223
77637
|
} finally {
|
|
77224
77638
|
invalidateGsapCache?.();
|
|
@@ -77245,54 +77659,28 @@ function useTimelineGroupEditing({
|
|
|
77245
77659
|
return { handleTimelineGroupMove, handleTimelineGroupResize };
|
|
77246
77660
|
}
|
|
77247
77661
|
|
|
77248
|
-
// src/hooks/
|
|
77249
|
-
import { useCallback as useCallback122
|
|
77250
|
-
|
|
77251
|
-
function useBlockedTimelineEditToast(showToast) {
|
|
77252
|
-
const lastAtRef = useRef138(0);
|
|
77253
|
-
return useCallback122(
|
|
77254
|
-
(_element, intent) => {
|
|
77255
|
-
const now2 = Date.now();
|
|
77256
|
-
if (now2 - lastAtRef.current < BLOCKED_TOAST_INTERVAL_MS) return;
|
|
77257
|
-
lastAtRef.current = now2;
|
|
77258
|
-
showToast(
|
|
77259
|
-
intent === "edit-many" ? `Select ${MAX_HAND_EDIT_CLIPS} or fewer clips to move or resize by hand, or describe the change.` : "This clip can't be moved or resized from the timeline yet.",
|
|
77260
|
-
"info"
|
|
77261
|
-
);
|
|
77262
|
-
},
|
|
77263
|
-
[showToast]
|
|
77264
|
-
);
|
|
77265
|
-
}
|
|
77266
|
-
|
|
77267
|
-
// src/hooks/useTimelineEditing.ts
|
|
77268
|
-
function useTimelineEditing({
|
|
77269
|
-
projectId,
|
|
77662
|
+
// src/hooks/useTimelineClipTimingEditing.ts
|
|
77663
|
+
import { useCallback as useCallback122 } from "react";
|
|
77664
|
+
function useTimelineClipTimingEditing({
|
|
77270
77665
|
activeCompPath,
|
|
77271
77666
|
timelineElements,
|
|
77272
77667
|
showToast,
|
|
77273
77668
|
writeProjectFile,
|
|
77274
|
-
observeProjectFileVersion,
|
|
77275
77669
|
recordEdit,
|
|
77276
77670
|
reloadPreview,
|
|
77277
77671
|
previewIframeRef,
|
|
77278
77672
|
pendingTimelineEditPathRef,
|
|
77279
|
-
uploadProjectFiles,
|
|
77280
77673
|
isRecordingRef,
|
|
77281
77674
|
sdkSession,
|
|
77282
77675
|
publishSdkSession,
|
|
77283
77676
|
forceReloadSdkSession,
|
|
77284
77677
|
invalidateGsapCache,
|
|
77285
77678
|
handleDomZIndexReorderCommitRef,
|
|
77286
|
-
|
|
77679
|
+
editQueueRef,
|
|
77680
|
+
projectIdRef
|
|
77287
77681
|
}) {
|
|
77288
|
-
const
|
|
77289
|
-
|
|
77290
|
-
const editQueueRef = useRef139(Promise.resolve());
|
|
77291
|
-
const track2 = useTrackPendingTimelineEdit();
|
|
77292
|
-
const checkEditable = useTimelineEditGate(canEdit, showToast);
|
|
77293
|
-
const guard = useTimelineEditGuard(canEdit, showToast);
|
|
77294
|
-
const enqueueEdit = useCallback123(
|
|
77295
|
-
(element, label, buildPatches, coalesceKey) => {
|
|
77682
|
+
const enqueueEdit = useCallback122(
|
|
77683
|
+
(element, label, buildPatches, step) => {
|
|
77296
77684
|
if (isRecordingRef?.current) {
|
|
77297
77685
|
showToast("Cannot edit timeline while recording", "error");
|
|
77298
77686
|
return Promise.resolve();
|
|
@@ -77309,7 +77697,7 @@ function useTimelineEditing({
|
|
|
77309
77697
|
writeProjectFile,
|
|
77310
77698
|
recordEdit,
|
|
77311
77699
|
pendingTimelineEditPathRef,
|
|
77312
|
-
|
|
77700
|
+
...step
|
|
77313
77701
|
})
|
|
77314
77702
|
).then(() => {
|
|
77315
77703
|
forceReloadSdkSession?.();
|
|
@@ -77326,26 +77714,12 @@ function useTimelineEditing({
|
|
|
77326
77714
|
pendingTimelineEditPathRef,
|
|
77327
77715
|
showToast,
|
|
77328
77716
|
isRecordingRef,
|
|
77329
|
-
forceReloadSdkSession
|
|
77717
|
+
forceReloadSdkSession,
|
|
77718
|
+
editQueueRef,
|
|
77719
|
+
projectIdRef
|
|
77330
77720
|
]
|
|
77331
77721
|
);
|
|
77332
|
-
const
|
|
77333
|
-
activeCompPath,
|
|
77334
|
-
editQueueRef,
|
|
77335
|
-
forceReloadSdkSession,
|
|
77336
|
-
invalidateGsapCache,
|
|
77337
|
-
isRecordingRef,
|
|
77338
|
-
pendingTimelineEditPathRef,
|
|
77339
|
-
previewIframeRef,
|
|
77340
|
-
projectIdRef,
|
|
77341
|
-
recordEdit,
|
|
77342
|
-
reloadPreview,
|
|
77343
|
-
sdkSession,
|
|
77344
|
-
publishSdkSession,
|
|
77345
|
-
showToast,
|
|
77346
|
-
writeProjectFile
|
|
77347
|
-
});
|
|
77348
|
-
const handleTimelineElementMove = useCallback123(
|
|
77722
|
+
const handleTimelineElementMove = useCallback122(
|
|
77349
77723
|
// fallow-ignore-next-line complexity
|
|
77350
77724
|
(element, updates) => {
|
|
77351
77725
|
const commitMove = () => {
|
|
@@ -77375,16 +77749,16 @@ function useTimelineEditing({
|
|
|
77375
77749
|
const needsExtension = extendRootDurationIfNeeded(updates.start + element.duration);
|
|
77376
77750
|
syncPreviewContentDuration(previewIframeRef.current);
|
|
77377
77751
|
const buildMovePatches = (original, target) => {
|
|
77378
|
-
const
|
|
77752
|
+
const track2 = trackChanged ? updates.track : void 0;
|
|
77379
77753
|
return buildTimelineMoveTimingPatch(
|
|
77380
77754
|
original,
|
|
77381
77755
|
target,
|
|
77382
77756
|
authoredStart,
|
|
77383
77757
|
element.duration,
|
|
77384
|
-
|
|
77758
|
+
track2
|
|
77385
77759
|
);
|
|
77386
77760
|
};
|
|
77387
|
-
const
|
|
77761
|
+
const step = timingGestureStep("timeline-move");
|
|
77388
77762
|
const finishMoveGsapSync = (sdkGsap) => (
|
|
77389
77763
|
// One GSAP sync per edit: the SDK commit's own (sdkGsap), else the server rewrite here.
|
|
77390
77764
|
finishClipTimingFallback({
|
|
@@ -77394,14 +77768,14 @@ function useTimelineEditing({
|
|
|
77394
77768
|
targetPath,
|
|
77395
77769
|
domId: element.domId,
|
|
77396
77770
|
label: "Move timeline clip",
|
|
77397
|
-
coalesceKey,
|
|
77771
|
+
coalesceKey: step.coalesceKey,
|
|
77398
77772
|
recordEdit,
|
|
77399
77773
|
writeProjectFile,
|
|
77400
77774
|
edit: { kind: "shift", delta: updates.start - element.start },
|
|
77401
77775
|
sdkGsap
|
|
77402
77776
|
}).finally(() => invalidateGsapCache?.())
|
|
77403
77777
|
);
|
|
77404
|
-
const moveFallback = () => enqueueEdit(element, "Move timeline clip", buildMovePatches,
|
|
77778
|
+
const moveFallback = () => enqueueEdit(element, "Move timeline clip", buildMovePatches, step).then(
|
|
77405
77779
|
() => finishMoveGsapSync()
|
|
77406
77780
|
);
|
|
77407
77781
|
return reorderDone.then(() => {
|
|
@@ -77421,7 +77795,7 @@ function useTimelineEditing({
|
|
|
77421
77795
|
readProjectFile: (path) => readFileContent(projectIdRef.current ?? "", path),
|
|
77422
77796
|
publishSession: publishSdkSession
|
|
77423
77797
|
},
|
|
77424
|
-
{ label: "Move timeline clip",
|
|
77798
|
+
{ label: "Move timeline clip", ...step, skipRefresh: true }
|
|
77425
77799
|
).then((result) => {
|
|
77426
77800
|
if (!cutoverCommittedOrThrow(result)) return moveFallback();
|
|
77427
77801
|
return finishMoveGsapSync(sdkTimingGsapSync(result));
|
|
@@ -77448,10 +77822,11 @@ function useTimelineEditing({
|
|
|
77448
77822
|
timelineElements,
|
|
77449
77823
|
handleDomZIndexReorderCommitRef,
|
|
77450
77824
|
showToast,
|
|
77451
|
-
invalidateGsapCache
|
|
77825
|
+
invalidateGsapCache,
|
|
77826
|
+
projectIdRef
|
|
77452
77827
|
]
|
|
77453
77828
|
);
|
|
77454
|
-
const handleTimelineElementResize =
|
|
77829
|
+
const handleTimelineElementResize = useCallback122(
|
|
77455
77830
|
// fallow-ignore-next-line complexity
|
|
77456
77831
|
(element, updates) => {
|
|
77457
77832
|
const authoredStart = toAuthoredStart(element, updates.start);
|
|
@@ -77472,7 +77847,7 @@ function useTimelineEditing({
|
|
|
77472
77847
|
return buildTimelineResizeTimingPatch(original, target, element, updates);
|
|
77473
77848
|
};
|
|
77474
77849
|
const hasPbsAdjustment = updates.playbackStart != null || updates.start !== element.start && element.playbackStart != null;
|
|
77475
|
-
const
|
|
77850
|
+
const step = timingGestureStep("timeline-resize");
|
|
77476
77851
|
const finishResizeGsapSync = (sdkGsap) => finishClipTimingFallback({
|
|
77477
77852
|
iframe: previewIframeRef.current,
|
|
77478
77853
|
reloadPreview,
|
|
@@ -77480,7 +77855,7 @@ function useTimelineEditing({
|
|
|
77480
77855
|
targetPath,
|
|
77481
77856
|
domId: element.domId,
|
|
77482
77857
|
label: "Resize timeline clip",
|
|
77483
|
-
coalesceKey,
|
|
77858
|
+
coalesceKey: step.coalesceKey,
|
|
77484
77859
|
recordEdit,
|
|
77485
77860
|
writeProjectFile,
|
|
77486
77861
|
edit: {
|
|
@@ -77490,7 +77865,7 @@ function useTimelineEditing({
|
|
|
77490
77865
|
},
|
|
77491
77866
|
sdkGsap
|
|
77492
77867
|
}).finally(() => invalidateGsapCache?.());
|
|
77493
|
-
const resizeFallback = () => enqueueEdit(element, "Resize timeline clip", buildResizePatches,
|
|
77868
|
+
const resizeFallback = () => enqueueEdit(element, "Resize timeline clip", buildResizePatches, step).then(
|
|
77494
77869
|
() => finishResizeGsapSync()
|
|
77495
77870
|
);
|
|
77496
77871
|
const persistDone = sdkSession && element.hfId && !element.link && !hasPbsAdjustment && !needsExtension ? sdkTimingPersist(
|
|
@@ -77508,7 +77883,7 @@ function useTimelineEditing({
|
|
|
77508
77883
|
readProjectFile: (path) => readFileContent(projectIdRef.current ?? "", path),
|
|
77509
77884
|
publishSession: publishSdkSession
|
|
77510
77885
|
},
|
|
77511
|
-
{ label: "Resize timeline clip",
|
|
77886
|
+
{ label: "Resize timeline clip", ...step, skipRefresh: true }
|
|
77512
77887
|
).then((result) => {
|
|
77513
77888
|
if (!cutoverCommittedOrThrow(result)) return resizeFallback();
|
|
77514
77889
|
return finishResizeGsapSync(sdkTimingGsapSync(result));
|
|
@@ -77529,9 +77904,90 @@ function useTimelineEditing({
|
|
|
77529
77904
|
writeProjectFile,
|
|
77530
77905
|
reloadPreview,
|
|
77531
77906
|
showToast,
|
|
77532
|
-
invalidateGsapCache
|
|
77907
|
+
invalidateGsapCache,
|
|
77908
|
+
projectIdRef
|
|
77533
77909
|
]
|
|
77534
77910
|
);
|
|
77911
|
+
return { handleTimelineElementMove, handleTimelineElementResize };
|
|
77912
|
+
}
|
|
77913
|
+
|
|
77914
|
+
// src/hooks/useBlockedTimelineEditToast.ts
|
|
77915
|
+
import { useCallback as useCallback123, useRef as useRef138 } from "react";
|
|
77916
|
+
var BLOCKED_TOAST_INTERVAL_MS = 1500;
|
|
77917
|
+
function useBlockedTimelineEditToast(showToast) {
|
|
77918
|
+
const lastAtRef = useRef138(0);
|
|
77919
|
+
return useCallback123(
|
|
77920
|
+
(_element) => {
|
|
77921
|
+
const now2 = Date.now();
|
|
77922
|
+
if (now2 - lastAtRef.current < BLOCKED_TOAST_INTERVAL_MS) return;
|
|
77923
|
+
lastAtRef.current = now2;
|
|
77924
|
+
showToast("This clip can't be moved or resized from the timeline yet.", "info");
|
|
77925
|
+
},
|
|
77926
|
+
[showToast]
|
|
77927
|
+
);
|
|
77928
|
+
}
|
|
77929
|
+
|
|
77930
|
+
// src/hooks/useTimelineEditing.ts
|
|
77931
|
+
function useTimelineEditing({
|
|
77932
|
+
projectId,
|
|
77933
|
+
activeCompPath,
|
|
77934
|
+
timelineElements,
|
|
77935
|
+
showToast,
|
|
77936
|
+
writeProjectFile,
|
|
77937
|
+
observeProjectFileVersion,
|
|
77938
|
+
recordEdit,
|
|
77939
|
+
reloadPreview,
|
|
77940
|
+
previewIframeRef,
|
|
77941
|
+
pendingTimelineEditPathRef,
|
|
77942
|
+
uploadProjectFiles,
|
|
77943
|
+
isRecordingRef,
|
|
77944
|
+
sdkSession,
|
|
77945
|
+
publishSdkSession,
|
|
77946
|
+
forceReloadSdkSession,
|
|
77947
|
+
invalidateGsapCache,
|
|
77948
|
+
handleDomZIndexReorderCommitRef,
|
|
77949
|
+
canEdit
|
|
77950
|
+
}) {
|
|
77951
|
+
const projectIdRef = useRef139(projectId);
|
|
77952
|
+
projectIdRef.current = projectId;
|
|
77953
|
+
const editQueueRef = useRef139(Promise.resolve());
|
|
77954
|
+
const track2 = useTrackPendingTimelineEdit();
|
|
77955
|
+
const checkEditable = useTimelineEditGate(canEdit, showToast);
|
|
77956
|
+
const guard = useTimelineEditGuard(canEdit, showToast);
|
|
77957
|
+
const groupEditing = useTimelineGroupEditing({
|
|
77958
|
+
activeCompPath,
|
|
77959
|
+
editQueueRef,
|
|
77960
|
+
forceReloadSdkSession,
|
|
77961
|
+
invalidateGsapCache,
|
|
77962
|
+
isRecordingRef,
|
|
77963
|
+
pendingTimelineEditPathRef,
|
|
77964
|
+
previewIframeRef,
|
|
77965
|
+
projectIdRef,
|
|
77966
|
+
recordEdit,
|
|
77967
|
+
reloadPreview,
|
|
77968
|
+
sdkSession,
|
|
77969
|
+
publishSdkSession,
|
|
77970
|
+
showToast,
|
|
77971
|
+
writeProjectFile
|
|
77972
|
+
});
|
|
77973
|
+
const { handleTimelineElementMove, handleTimelineElementResize } = useTimelineClipTimingEditing({
|
|
77974
|
+
activeCompPath,
|
|
77975
|
+
timelineElements,
|
|
77976
|
+
showToast,
|
|
77977
|
+
writeProjectFile,
|
|
77978
|
+
recordEdit,
|
|
77979
|
+
reloadPreview,
|
|
77980
|
+
previewIframeRef,
|
|
77981
|
+
pendingTimelineEditPathRef,
|
|
77982
|
+
isRecordingRef,
|
|
77983
|
+
sdkSession,
|
|
77984
|
+
publishSdkSession,
|
|
77985
|
+
forceReloadSdkSession,
|
|
77986
|
+
invalidateGsapCache,
|
|
77987
|
+
handleDomZIndexReorderCommitRef,
|
|
77988
|
+
editQueueRef,
|
|
77989
|
+
projectIdRef
|
|
77990
|
+
});
|
|
77535
77991
|
const handleToggleTrackHidden = useTimelineTrackVisibilityEditing({
|
|
77536
77992
|
projectIdRef,
|
|
77537
77993
|
activeCompPath,
|
|
@@ -77753,7 +78209,7 @@ function useTimelineEditing({
|
|
|
77753
78209
|
|
|
77754
78210
|
// src/hooks/timelineMoveAdapter.ts
|
|
77755
78211
|
import { useCallback as useCallback124 } from "react";
|
|
77756
|
-
function persistTimelineMoveEditsAtomically(edits, coalesceKey, operation, deps
|
|
78212
|
+
function persistTimelineMoveEditsAtomically(edits, coalesceKey, operation, deps) {
|
|
77757
78213
|
return deps.handleTimelineGroupMove(
|
|
77758
78214
|
edits.map(({ element, updates }) => ({
|
|
77759
78215
|
element,
|
|
@@ -77765,14 +78221,14 @@ function persistTimelineMoveEditsAtomically(edits, coalesceKey, operation, deps,
|
|
|
77765
78221
|
// ("timing") omit it so they stay eligible for the SDK fast path.
|
|
77766
78222
|
track: operation === "timing" ? void 0 : updates.track
|
|
77767
78223
|
})),
|
|
77768
|
-
{ coalesceKey
|
|
78224
|
+
{ coalesceKey }
|
|
77769
78225
|
);
|
|
77770
78226
|
}
|
|
77771
78227
|
function useTimelineMoveEditsHandler(handleTimelineGroupMove) {
|
|
77772
78228
|
return useCallback124(
|
|
77773
|
-
async (edits, coalesceKey, operation = "timing"
|
|
78229
|
+
async (edits, coalesceKey, operation = "timing") => {
|
|
77774
78230
|
const deps = { handleTimelineGroupMove };
|
|
77775
|
-
await persistTimelineMoveEditsAtomically(edits, coalesceKey, operation, deps
|
|
78231
|
+
await persistTimelineMoveEditsAtomically(edits, coalesceKey, operation, deps);
|
|
77776
78232
|
},
|
|
77777
78233
|
[handleTimelineGroupMove]
|
|
77778
78234
|
);
|
|
@@ -77902,6 +78358,9 @@ function domEditSelectionsTargetSame(a, b) {
|
|
|
77902
78358
|
if (!a || !b) return false;
|
|
77903
78359
|
return getDomEditTargetKey(a) === getDomEditTargetKey(b);
|
|
77904
78360
|
}
|
|
78361
|
+
function domEditSelectionsEqual(a, b) {
|
|
78362
|
+
return sameData(a, b);
|
|
78363
|
+
}
|
|
77905
78364
|
function domEditSelectionInGroup(group, selection) {
|
|
77906
78365
|
if (!selection) return false;
|
|
77907
78366
|
return group.some((entry2) => domEditSelectionsTargetSame(entry2, selection));
|
|
@@ -78168,6 +78627,8 @@ function useDomSelection({
|
|
|
78168
78627
|
const currentSelection = domEditSelectionRef.current;
|
|
78169
78628
|
const previousGroup = domEditGroupSelectionsRef.current;
|
|
78170
78629
|
const isRepeatedSingleSelection = !isAdditiveSelection && !options?.preserveGroup && previousGroup.length === 1 && domEditSelectionsTargetSame(currentSelection, selection) && domEditSelectionsTargetSame(previousGroup[0], selection);
|
|
78630
|
+
const isUnchangedRefresh = options?.preserveGroup && currentSelection !== null && (previousGroup.length === 0 || previousGroup.includes(currentSelection)) && domEditSelectionsEqual(currentSelection, selection);
|
|
78631
|
+
if (isUnchangedRefresh) return;
|
|
78171
78632
|
if (isRepeatedSingleSelection) {
|
|
78172
78633
|
if (options?.revealPanel !== false) {
|
|
78173
78634
|
setRightCollapsed(false);
|
|
@@ -78229,6 +78690,7 @@ function useDomSelection({
|
|
|
78229
78690
|
preferClipAncestor: options?.preferClipAncestor,
|
|
78230
78691
|
skipSourceProbe: options?.skipSourceProbe,
|
|
78231
78692
|
exactTarget: options?.exactTarget,
|
|
78693
|
+
previous: options?.previous,
|
|
78232
78694
|
activeGroupElement: options && "activeGroupElement" in options ? options.activeGroupElement : activeGroupElementRef.current,
|
|
78233
78695
|
projectId
|
|
78234
78696
|
});
|
|
@@ -78351,7 +78813,7 @@ function useDomSelection({
|
|
|
78351
78813
|
applyDomSelection(null, { revealPanel: false });
|
|
78352
78814
|
return;
|
|
78353
78815
|
}
|
|
78354
|
-
const nextSelection = await buildDomSelectionFromTarget(element);
|
|
78816
|
+
const nextSelection = await buildDomSelectionFromTarget(element, { previous: selection });
|
|
78355
78817
|
if (nextSelection) {
|
|
78356
78818
|
applyDomSelection(nextSelection, {
|
|
78357
78819
|
revealPanel: false,
|
|
@@ -78376,7 +78838,7 @@ function useDomSelection({
|
|
|
78376
78838
|
for (const selection of selections) {
|
|
78377
78839
|
const element = findElementForSelection(doc, selection, activeCompPath);
|
|
78378
78840
|
if (!element) continue;
|
|
78379
|
-
const nextSelection2 = await buildDomSelectionFromTarget(element);
|
|
78841
|
+
const nextSelection2 = await buildDomSelectionFromTarget(element, { previous: selection });
|
|
78380
78842
|
if (nextSelection2) nextGroup.push(nextSelection2);
|
|
78381
78843
|
}
|
|
78382
78844
|
if (nextGroup.length === 0) return;
|
|
@@ -81368,13 +81830,13 @@ function useGsapKeyframeOps({
|
|
|
81368
81830
|
|
|
81369
81831
|
// src/hooks/useGsapPropertyDebounce.ts
|
|
81370
81832
|
import { useCallback as useCallback138, useEffect as useEffect118, useRef as useRef146 } from "react";
|
|
81371
|
-
import { parseGsapScriptAcorn } from "@hyperframes/core/gsap-parser-acorn";
|
|
81833
|
+
import { parseGsapScriptAcorn as parseGsapScriptAcorn2 } from "@hyperframes/core/gsap-parser-acorn";
|
|
81372
81834
|
var DEBOUNCE_MS = 150;
|
|
81373
81835
|
function mergeTweenProperties(sdkSession, animationId, edited, kind) {
|
|
81374
81836
|
try {
|
|
81375
81837
|
const script = extractGsapScriptText(sdkSession.serialize());
|
|
81376
81838
|
if (!script) return { ...edited };
|
|
81377
|
-
const anim =
|
|
81839
|
+
const anim = parseGsapScriptAcorn2(script).animations.find((a) => a.id === animationId);
|
|
81378
81840
|
if (!anim) return { ...edited };
|
|
81379
81841
|
const existing = kind === "from" ? anim.fromProperties ?? {} : anim.properties;
|
|
81380
81842
|
return { ...existing, ...edited };
|
|
@@ -82052,7 +82514,9 @@ function useDomEditPreviewSync({
|
|
|
82052
82514
|
applyDomSelection(null, { revealPanel: false });
|
|
82053
82515
|
return;
|
|
82054
82516
|
}
|
|
82055
|
-
const nextSelection = await buildDomSelectionFromTarget(nextElement
|
|
82517
|
+
const nextSelection = await buildDomSelectionFromTarget(nextElement, {
|
|
82518
|
+
previous: currentSelection
|
|
82519
|
+
});
|
|
82056
82520
|
if (nextSelection) {
|
|
82057
82521
|
applyDomSelection(nextSelection, { revealPanel: false, preserveGroup: true });
|
|
82058
82522
|
}
|
|
@@ -83223,19 +83687,17 @@ async function commitKeyframeProps(selection, anim, props, propEntries, primaryP
|
|
|
83223
83687
|
const newStart = Math.min(ct, ts);
|
|
83224
83688
|
const newEnd = Math.max(ct, ts + td);
|
|
83225
83689
|
const newDuration = Math.max(0.01, newEnd - newStart);
|
|
83690
|
+
const runEase = runEaseOf(anim);
|
|
83691
|
+
const toNewPct = (absTime) => Math.round(progressAtTime(runEase, (absTime - newStart) / newDuration * 100) * 10) / 10;
|
|
83226
83692
|
const remapped = kfs.map((kf) => {
|
|
83227
|
-
const
|
|
83228
|
-
const newPct = Math.round((absTime - newStart) / newDuration * 1e3) / 10;
|
|
83693
|
+
const newPct = toNewPct(ts + timeAtProgress(runEase, kf.percentage) / 100 * td);
|
|
83229
83694
|
const p = { ...kf.properties };
|
|
83230
83695
|
for (const k of Object.keys(properties)) {
|
|
83231
83696
|
if (!(k in p) && backfillDefaults[k] != null) p[k] = backfillDefaults[k];
|
|
83232
83697
|
}
|
|
83233
83698
|
return { percentage: newPct, properties: p };
|
|
83234
83699
|
});
|
|
83235
|
-
remapped.push({
|
|
83236
|
-
percentage: Math.round((ct - newStart) / newDuration * 1e3) / 10,
|
|
83237
|
-
properties
|
|
83238
|
-
});
|
|
83700
|
+
remapped.push({ percentage: toNewPct(ct), properties });
|
|
83239
83701
|
remapped.sort((a, b) => a.percentage - b.percentage);
|
|
83240
83702
|
await commit2(
|
|
83241
83703
|
selection,
|
|
@@ -83924,6 +84386,19 @@ function runCommitPreflights(selection, fileAnimations, iframe, group) {
|
|
|
83924
84386
|
};
|
|
83925
84387
|
}
|
|
83926
84388
|
var resolvedSelections = /* @__PURE__ */ new WeakMap();
|
|
84389
|
+
var checkedSelections = /* @__PURE__ */ new WeakMap();
|
|
84390
|
+
function checkedPreflight(resolved2, inputs) {
|
|
84391
|
+
const known = checkedSelections.get(resolved2);
|
|
84392
|
+
const same2 = known?.animations === inputs.animations && known.iframe === inputs.iframe && known.group === inputs.group && known.version === inputs.version;
|
|
84393
|
+
if (same2) return known;
|
|
84394
|
+
const { animations, iframe, group } = inputs;
|
|
84395
|
+
const checked = {
|
|
84396
|
+
...inputs,
|
|
84397
|
+
preflight: runCommitPreflights(resolved2, animations, iframe, group)
|
|
84398
|
+
};
|
|
84399
|
+
checkedSelections.set(resolved2, checked);
|
|
84400
|
+
return checked;
|
|
84401
|
+
}
|
|
83927
84402
|
var resolvedOf = (selection) => resolvedSelections.get(selection) ?? selection;
|
|
83928
84403
|
var MANUAL_FLAGS = [
|
|
83929
84404
|
["canApplyManualOffset", "offset"],
|
|
@@ -83986,17 +84461,21 @@ function useCommitPreflightCapabilities({
|
|
|
83986
84461
|
void parseTick;
|
|
83987
84462
|
if (!enabled || !projectId) return { selection, groupSelections };
|
|
83988
84463
|
const group = groupSelections.length > 1;
|
|
84464
|
+
const iframe = previewIframeRef.current;
|
|
83989
84465
|
const narrow = (target) => {
|
|
83990
84466
|
const file = gsapSourceFileForSelection(target);
|
|
83991
84467
|
const animations = parsesRef.current.get(parseCacheKey(projectId, file))?.animations;
|
|
83992
|
-
|
|
83993
|
-
|
|
84468
|
+
if (!animations) return narrowCapabilities(target, null);
|
|
84469
|
+
const inputs = { animations, iframe, group, version };
|
|
84470
|
+
const checked = checkedPreflight(resolvedOf(target), inputs);
|
|
84471
|
+
checked.narrowed ??= narrowCapabilities(target, checked.preflight);
|
|
84472
|
+
return checked.narrowed;
|
|
83994
84473
|
};
|
|
83995
|
-
return {
|
|
84474
|
+
return withTweenIndex(() => ({
|
|
83996
84475
|
selection: selection && narrow(selection),
|
|
83997
84476
|
groupSelections: groupSelections.map(narrow)
|
|
83998
|
-
};
|
|
83999
|
-
}, [enabled, projectId, selection, groupSelections, parseTick, previewIframeRef]);
|
|
84477
|
+
}));
|
|
84478
|
+
}, [enabled, projectId, selection, groupSelections, parseTick, previewIframeRef, version]);
|
|
84000
84479
|
}
|
|
84001
84480
|
|
|
84002
84481
|
// src/hooks/domEditDeleteMembers.ts
|
|
@@ -85956,11 +86435,11 @@ function useEditHistoryActions({
|
|
|
85956
86435
|
const apply = useCallback153(
|
|
85957
86436
|
async (direction) => {
|
|
85958
86437
|
const noun = direction === "undo" ? "Undo" : "Redo";
|
|
85959
|
-
const
|
|
86438
|
+
const pendingEditShown = direction === "undo" ? paintBackNewestStudioPendingEdit() : null;
|
|
86439
|
+
const predicted = pendingEditShown ? null : editHistory.predict?.(direction) ?? null;
|
|
85960
86440
|
const predictedShown = predicted ? showHistoryRestoreNow?.(predicted.files) ?? null : null;
|
|
85961
|
-
const pendingEditShown = !predictedShown && direction === "undo" ? paintBackNewestStudioPendingEdit() : null;
|
|
85962
86441
|
const putBack = predictedShown ?? pendingEditShown?.showAgain;
|
|
85963
|
-
const claimedAfter = direction === "undo" && hasStudioPendingEdits() ? editHistory.claims?.() : void 0;
|
|
86442
|
+
const claimedAfter = pendingEditShown?.claimsAtBegin ?? (direction === "undo" && hasStudioPendingEdits() ? editHistory.claims?.() : void 0);
|
|
85964
86443
|
let result = { ok: false, reason: "failed" };
|
|
85965
86444
|
let serverSteppedShown = false;
|
|
85966
86445
|
let revertIsTheUndo = false;
|
|
@@ -85974,7 +86453,8 @@ function useEditHistoryActions({
|
|
|
85974
86453
|
claimedAfter
|
|
85975
86454
|
});
|
|
85976
86455
|
const stepped = Boolean(result.ok && result.label);
|
|
85977
|
-
|
|
86456
|
+
const editClaimed = pendingEditShown !== null && (editHistory.claims?.() ?? 0) > pendingEditShown.claimsAtBegin;
|
|
86457
|
+
serverSteppedShown = predictedShown ? stepped && result.undoes === predicted?.id : stepped && editClaimed;
|
|
85978
86458
|
} finally {
|
|
85979
86459
|
if (putBack && !serverSteppedShown && !revertIsTheUndo) putBack();
|
|
85980
86460
|
}
|
|
@@ -88727,16 +89207,16 @@ function averageSpeed(samples, from, to) {
|
|
|
88727
89207
|
return total / (seg.length - 1);
|
|
88728
89208
|
}
|
|
88729
89209
|
function speedAtEdge(samples, t2, window2, side) {
|
|
88730
|
-
const
|
|
89210
|
+
const near4 = samples.filter(
|
|
88731
89211
|
(s) => side === "start" ? s.time >= t2 && s.time <= t2 + window2 : s.time >= t2 - window2 && s.time <= t2
|
|
88732
89212
|
);
|
|
88733
|
-
if (
|
|
89213
|
+
if (near4.length < 2) return 0;
|
|
88734
89214
|
let total = 0;
|
|
88735
|
-
for (let i = 1; i <
|
|
88736
|
-
const dt =
|
|
88737
|
-
if (dt > 0) total += Math.abs(
|
|
89215
|
+
for (let i = 1; i < near4.length; i++) {
|
|
89216
|
+
const dt = near4[i].time - near4[i - 1].time;
|
|
89217
|
+
if (dt > 0) total += Math.abs(near4[i].value - near4[i - 1].value) / dt;
|
|
88738
89218
|
}
|
|
88739
|
-
return total / (
|
|
89219
|
+
return total / (near4.length - 1);
|
|
88740
89220
|
}
|
|
88741
89221
|
function fitEasesFromVelocity(keyframes, rawSamples, totalDuration) {
|
|
88742
89222
|
if (keyframes.length < 2 || rawSamples.length < 3) return keyframes;
|
|
@@ -88972,7 +89452,7 @@ function useGestureCommit({
|
|
|
88972
89452
|
);
|
|
88973
89453
|
} else {
|
|
88974
89454
|
const tweenStart = existingPositionTween.resolvedStart ?? 0;
|
|
88975
|
-
const tweenDur = existingPositionTween
|
|
89455
|
+
const tweenDur = resolveTweenDuration(existingPositionTween);
|
|
88976
89456
|
const tweenEnd = tweenStart + tweenDur;
|
|
88977
89457
|
const recEnd = recStart + duration;
|
|
88978
89458
|
const overlaps3 = recStart < tweenEnd + 0.05 && recEnd > tweenStart - 0.05;
|
|
@@ -91367,9 +91847,9 @@ function useLastFrame(request, snapshot3) {
|
|
|
91367
91847
|
}
|
|
91368
91848
|
function useNearViewport() {
|
|
91369
91849
|
const [element, setElement] = useState141(null);
|
|
91370
|
-
const [
|
|
91850
|
+
const [near4, setNear] = useState141(false);
|
|
91371
91851
|
useEffect144(() => {
|
|
91372
|
-
if (!element ||
|
|
91852
|
+
if (!element || near4) return;
|
|
91373
91853
|
if (typeof IntersectionObserver === "undefined") return setNear(true);
|
|
91374
91854
|
const observer = new IntersectionObserver(
|
|
91375
91855
|
(entries) => entries.some((entry2) => entry2.isIntersecting) && setNear(true),
|
|
@@ -91377,8 +91857,8 @@ function useNearViewport() {
|
|
|
91377
91857
|
);
|
|
91378
91858
|
observer.observe(element);
|
|
91379
91859
|
return () => observer.disconnect();
|
|
91380
|
-
}, [element,
|
|
91381
|
-
return [setElement,
|
|
91860
|
+
}, [element, near4]);
|
|
91861
|
+
return [setElement, near4];
|
|
91382
91862
|
}
|
|
91383
91863
|
function CompCard({
|
|
91384
91864
|
projectId,
|
|
@@ -96737,31 +97217,10 @@ function animatedProps(anim) {
|
|
|
96737
97217
|
}
|
|
96738
97218
|
return ["x", "y"];
|
|
96739
97219
|
}
|
|
96740
|
-
function isPlayheadWithinTween(anim, currentTime
|
|
97220
|
+
function isPlayheadWithinTween(anim, currentTime) {
|
|
96741
97221
|
const start = resolveTweenStart(anim);
|
|
96742
97222
|
if (start === null) return true;
|
|
96743
|
-
|
|
96744
|
-
return isTimeWithinTween(currentTime, start, duration);
|
|
96745
|
-
}
|
|
96746
|
-
function buildExtendedKeyframes(anim, currentTime, position, sourceDuration = resolveTweenDuration(anim)) {
|
|
96747
|
-
const oldStart = resolveTweenStart(anim) ?? 0;
|
|
96748
|
-
const oldDuration = sourceDuration;
|
|
96749
|
-
const newStart = Math.min(oldStart, currentTime);
|
|
96750
|
-
const newEnd = Math.max(oldStart + oldDuration, currentTime);
|
|
96751
|
-
const newDuration = roundTo3(newEnd - newStart);
|
|
96752
|
-
const toPct = (absoluteTime) => newDuration > 0 ? Math.max(
|
|
96753
|
-
0,
|
|
96754
|
-
Math.min(100, Math.round((absoluteTime - newStart) / newDuration * 1e3) / 10)
|
|
96755
|
-
) : 0;
|
|
96756
|
-
const stops = anim.keyframes?.keyframes ?? [];
|
|
96757
|
-
const rescaled = stops.map((stop2) => ({
|
|
96758
|
-
percentage: toPct(oldStart + stop2.percentage / 100 * oldDuration),
|
|
96759
|
-
properties: stop2.properties,
|
|
96760
|
-
...stop2.ease ? { ease: stop2.ease } : {}
|
|
96761
|
-
}));
|
|
96762
|
-
const added = { percentage: toPct(currentTime), properties: position };
|
|
96763
|
-
const keyframes = [...rescaled, added].sort((a, b) => a.percentage - b.percentage);
|
|
96764
|
-
return { position: roundTo3(newStart), duration: newDuration, keyframes };
|
|
97223
|
+
return isTimeWithinTween(currentTime, start, resolveTweenDuration(anim));
|
|
96765
97224
|
}
|
|
96766
97225
|
async function replaceSetWithSingleKeyframe(session, sel, setAnim, t2, iframe, selector) {
|
|
96767
97226
|
const position = readElementPosition(iframe, sel, setAnim);
|
|
@@ -96852,10 +97311,16 @@ async function extendKeyframedTweenToPlayhead(session, sel, anim, currentTime, d
|
|
|
96852
97311
|
}
|
|
96853
97312
|
);
|
|
96854
97313
|
}
|
|
96855
|
-
async function applyKeyframeAtPlayhead(session, sel, kfAnim, t2, iframe, commitOverrides) {
|
|
96856
|
-
const
|
|
97314
|
+
async function applyKeyframeAtPlayhead(session, sel, kfAnim, t2, iframe, commitOverrides, liveTween) {
|
|
97315
|
+
const keepAuthoredDuration = kfAnim.durationUnresolved === true;
|
|
97316
|
+
if (keepAuthoredDuration) {
|
|
97317
|
+
kfAnim = withLiveTiming(kfAnim, liveTween ?? findParsedTween(iframe, sel.element, kfAnim));
|
|
97318
|
+
}
|
|
97319
|
+
if (keepAuthoredDuration && kfAnim.duration == null) return;
|
|
97320
|
+
const duration = resolveTweenDuration(kfAnim);
|
|
96857
97321
|
const start = resolveTweenStart(kfAnim);
|
|
96858
97322
|
if (start !== null && !isTimeWithinTween(t2, start, duration)) {
|
|
97323
|
+
if (keepAuthoredDuration) return;
|
|
96859
97324
|
await extendKeyframedTweenToPlayhead(
|
|
96860
97325
|
session,
|
|
96861
97326
|
sel,
|
|
@@ -96867,10 +97332,8 @@ async function applyKeyframeAtPlayhead(session, sel, kfAnim, t2, iframe, commitO
|
|
|
96867
97332
|
);
|
|
96868
97333
|
return;
|
|
96869
97334
|
}
|
|
96870
|
-
const pct = start === null ? computeElementPercentage(t2, sel) :
|
|
96871
|
-
const existing = kfAnim.keyframes?.keyframes.find(
|
|
96872
|
-
(k) => Math.abs(k.percentage - pct) <= KEYFRAME_PCT_MATCH
|
|
96873
|
-
);
|
|
97335
|
+
const pct = start === null ? computeElementPercentage(t2, sel) : absoluteToPercentageForAnimation(t2, kfAnim) ?? 0;
|
|
97336
|
+
const existing = kfAnim.keyframes?.keyframes.find((k) => playsNear(kfAnim, k.percentage, pct));
|
|
96874
97337
|
if (existing) {
|
|
96875
97338
|
session.handleGsapRemoveKeyframe(kfAnim.id, existing.percentage);
|
|
96876
97339
|
return;
|
|
@@ -96922,21 +97385,26 @@ async function applyArcKeyframeAtPlayhead(session, sel, arcAnim, t2, iframe) {
|
|
|
96922
97385
|
const targetSelector = existingTweenTargetSelector(arcAnim, sel);
|
|
96923
97386
|
if (!targetSelector) return;
|
|
96924
97387
|
const start = resolveTweenStart(arcAnim) ?? 0;
|
|
96925
|
-
const duration =
|
|
97388
|
+
const duration = resolveTweenDuration(arcAnim);
|
|
96926
97389
|
if (!isTimeWithinTween(t2, start, duration)) {
|
|
96927
|
-
|
|
96928
|
-
|
|
96929
|
-
|
|
96930
|
-
|
|
96931
|
-
|
|
96932
|
-
|
|
96933
|
-
|
|
96934
|
-
|
|
96935
|
-
|
|
96936
|
-
|
|
97390
|
+
const held = readElementPosition(iframe, sel, arcAnim);
|
|
97391
|
+
if (t2 <= start || typeof held.x !== "number" || typeof held.y !== "number") return;
|
|
97392
|
+
const extended = buildExtendedKeyframes(arcAnim, t2, { x: held.x, y: held.y }, duration, "none");
|
|
97393
|
+
await session.commitMutation(
|
|
97394
|
+
{
|
|
97395
|
+
type: "replace-with-keyframes",
|
|
97396
|
+
animationId: arcAnim.id,
|
|
97397
|
+
targetSelector,
|
|
97398
|
+
position: extended.position,
|
|
97399
|
+
duration: extended.duration,
|
|
97400
|
+
keyframes: extended.keyframes,
|
|
97401
|
+
ease: "none"
|
|
97402
|
+
},
|
|
97403
|
+
{ label: "Add keyframe", keyframeAction: "add", softReload: true }
|
|
97404
|
+
);
|
|
96937
97405
|
return;
|
|
96938
97406
|
}
|
|
96939
|
-
const nodes = arcAnim
|
|
97407
|
+
const nodes = linearArcKeyframes(arcAnim);
|
|
96940
97408
|
const playheadPercentage = absoluteToPercentage(t2, start, duration);
|
|
96941
97409
|
const timedNodeIndex = nodes.findIndex(
|
|
96942
97410
|
(node) => Math.abs(node.percentage - playheadPercentage) <= KEYFRAME_PCT_MATCH
|
|
@@ -96997,6 +97465,7 @@ function useEnableKeyframes(sessionRef) {
|
|
|
96997
97465
|
} else if (setAnim) {
|
|
96998
97466
|
await promoteSetToKeyframes(session, sel, setAnim, t2, iframe);
|
|
96999
97467
|
} else if (flatAnim) {
|
|
97468
|
+
const flatLive = flatAnim.durationUnresolved ? findParsedTween(iframe, sel.element, flatAnim) : void 0;
|
|
97000
97469
|
enableKeyframesTransactionCounter += 1;
|
|
97001
97470
|
const coalesceKey = `enable-keyframes:${flatAnim.id}:${enableKeyframesTransactionCounter}`;
|
|
97002
97471
|
const convertCommitOverrides = {
|
|
@@ -97022,7 +97491,15 @@ function useEnableKeyframes(sessionRef) {
|
|
|
97022
97491
|
// conversion splits into two undo entries under real latency.
|
|
97023
97492
|
coalesceMs: Number.POSITIVE_INFINITY
|
|
97024
97493
|
};
|
|
97025
|
-
await applyKeyframeAtPlayhead(
|
|
97494
|
+
await applyKeyframeAtPlayhead(
|
|
97495
|
+
session,
|
|
97496
|
+
sel,
|
|
97497
|
+
converted,
|
|
97498
|
+
t2,
|
|
97499
|
+
iframe,
|
|
97500
|
+
applyCommitOverrides,
|
|
97501
|
+
flatLive
|
|
97502
|
+
);
|
|
97026
97503
|
}
|
|
97027
97504
|
} else {
|
|
97028
97505
|
const position = readElementPosition(iframe, sel, null);
|
|
@@ -97331,7 +97808,9 @@ var NO_KEYFRAME_TOGGLE = {
|
|
|
97331
97808
|
function isMotionPathEndpoint(animation, percentage) {
|
|
97332
97809
|
if (!animation?.keyframes) return false;
|
|
97333
97810
|
const keyframes = animation.keyframes.keyframes;
|
|
97334
|
-
return
|
|
97811
|
+
return [keyframes[0], keyframes.at(-1)].some(
|
|
97812
|
+
(keyframe) => keyframe && playsNear(animation, keyframe.percentage, percentage)
|
|
97813
|
+
);
|
|
97335
97814
|
}
|
|
97336
97815
|
function resolveKeyframeToggleState(session, currentTime) {
|
|
97337
97816
|
if (!session?.domEditSelection) return NO_KEYFRAME_TOGGLE;
|
|
@@ -97341,13 +97820,13 @@ function resolveKeyframeToggleState(session, currentTime) {
|
|
|
97341
97820
|
const animation = arcAnimation ?? session.selectedGsapAnimations.find((candidate) => candidate.keyframes && !candidate.arcPath);
|
|
97342
97821
|
if (!animation?.keyframes) return NO_KEYFRAME_TOGGLE;
|
|
97343
97822
|
const isMotionPath = Boolean(arcAnimation);
|
|
97344
|
-
if (!isPlayheadWithinTween(animation, currentTime
|
|
97823
|
+
if (!isPlayheadWithinTween(animation, currentTime)) {
|
|
97345
97824
|
return { state: "inactive", isMotionPath, pathEndpoint: false, willExtend: true };
|
|
97346
97825
|
}
|
|
97347
97826
|
const percentage = computeElementPercentage(currentTime, session.domEditSelection, animation);
|
|
97348
97827
|
const pathEndpoint = isMotionPathEndpoint(arcAnimation, percentage);
|
|
97349
97828
|
const active = animation.keyframes.keyframes.some(
|
|
97350
|
-
(keyframe) =>
|
|
97829
|
+
(keyframe) => playsNear(animation, keyframe.percentage, percentage)
|
|
97351
97830
|
);
|
|
97352
97831
|
return {
|
|
97353
97832
|
state: pathEndpoint ? "none" : active ? "active" : "inactive",
|