@hyperframes/studio 0.6.73 → 0.6.75
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/index-DcyZuBcU.css +1 -0
- package/dist/assets/index-uB_W2GDl.js +140 -0
- package/dist/index.html +2 -2
- package/package.json +4 -4
- package/src/App.tsx +30 -24
- package/src/components/StudioPreviewArea.tsx +101 -26
- package/src/components/StudioRightPanel.tsx +3 -0
- package/src/components/StudioToast.tsx +18 -0
- package/src/components/TimelineToolbar.tsx +230 -4
- package/src/components/editor/AnimationCard.tsx +68 -4
- package/src/components/editor/DomEditOverlay.tsx +70 -1
- package/src/components/editor/GridOverlay.tsx +50 -0
- package/src/components/editor/KeyframeDiamond.tsx +49 -0
- package/src/components/editor/KeyframeNavigation.tsx +139 -0
- package/src/components/editor/LayersPanel.test.ts +135 -0
- package/src/components/editor/LayersPanel.tsx +151 -15
- package/src/components/editor/PropertyPanel.tsx +293 -140
- package/src/components/editor/SnapGuideOverlay.tsx +166 -0
- package/src/components/editor/SnapToolbar.tsx +163 -0
- package/src/components/editor/SpringEaseEditor.tsx +256 -0
- package/src/components/editor/domEditOverlayGestures.ts +7 -0
- package/src/components/editor/domEditOverlayStartGesture.ts +28 -0
- package/src/components/editor/gsapAnimationConstants.ts +42 -0
- package/src/components/editor/gsapAnimationHelpers.ts +2 -1
- package/src/components/editor/manualEditingAvailability.ts +6 -0
- package/src/components/editor/manualEditsDom.ts +56 -2
- package/src/components/editor/manualOffsetDrag.ts +19 -3
- package/src/components/editor/propertyPanelHelpers.ts +90 -0
- package/src/components/editor/propertyPanelTimingSection.tsx +64 -0
- package/src/components/editor/snapEngine.test.ts +657 -0
- package/src/components/editor/snapEngine.ts +575 -0
- package/src/components/editor/snapTargetCollection.ts +147 -0
- package/src/components/editor/useDomEditOverlayGestures.ts +137 -10
- package/src/components/editor/useLayerDrag.ts +213 -0
- package/src/components/nle/NLELayout.tsx +18 -0
- package/src/contexts/DomEditContext.tsx +27 -0
- package/src/hooks/gsapRuntimeBridge.ts +585 -0
- package/src/hooks/gsapRuntimeKeyframes.ts +170 -0
- package/src/hooks/useAnimatedPropertyCommit.ts +131 -0
- package/src/hooks/useAppHotkeys.ts +63 -1
- package/src/hooks/useDomEditCommits.ts +88 -4
- package/src/hooks/useDomEditSession.ts +179 -65
- package/src/hooks/useGsapScriptCommits.ts +144 -7
- package/src/hooks/useGsapSelectionHandlers.ts +202 -0
- package/src/hooks/useGsapTweenCache.ts +174 -3
- package/src/hooks/useTimelineEditing.ts +93 -0
- package/src/icons/SystemIcons.tsx +2 -0
- package/src/player/components/ClipContextMenu.tsx +99 -0
- package/src/player/components/KeyframeDiamondContextMenu.tsx +164 -0
- package/src/player/components/Timeline.test.ts +2 -1
- package/src/player/components/Timeline.tsx +108 -68
- package/src/player/components/TimelineCanvas.tsx +47 -1
- package/src/player/components/TimelineClip.tsx +8 -3
- package/src/player/components/TimelineClipDiamonds.tsx +174 -0
- package/src/player/components/timelineDragDrop.ts +103 -0
- package/src/player/components/timelineLayout.ts +1 -1
- package/src/player/store/playerStore.ts +42 -0
- package/src/utils/editHistory.ts +1 -1
- package/src/utils/optimisticUpdate.test.ts +53 -0
- package/src/utils/optimisticUpdate.ts +18 -0
- package/src/utils/studioUiPreferences.ts +17 -0
- package/dist/assets/index-CrxThtSJ.css +0 -1
- package/dist/assets/index-Dc2HfqON.js +0 -140
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useCallback, useEffect, useRef } from "react";
|
|
2
2
|
import type { TimelineElement } from "../player";
|
|
3
|
+
import { usePlayerStore } from "../player";
|
|
3
4
|
import {
|
|
4
5
|
STUDIO_INSPECTOR_PANELS_ENABLED,
|
|
5
6
|
STUDIO_GSAP_PANEL_ENABLED,
|
|
@@ -16,7 +17,20 @@ import { useDomSelection } from "./useDomSelection";
|
|
|
16
17
|
import { usePreviewInteraction } from "./usePreviewInteraction";
|
|
17
18
|
import { useDomEditCommits } from "./useDomEditCommits";
|
|
18
19
|
import { useGsapScriptCommits } from "./useGsapScriptCommits";
|
|
19
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
useGsapAnimationsForElement,
|
|
22
|
+
useGsapCacheVersion,
|
|
23
|
+
usePopulateKeyframeCacheForFile,
|
|
24
|
+
fetchParsedAnimations,
|
|
25
|
+
getAnimationsForElement,
|
|
26
|
+
} from "./useGsapTweenCache";
|
|
27
|
+
import {
|
|
28
|
+
tryGsapDragIntercept,
|
|
29
|
+
tryGsapResizeIntercept,
|
|
30
|
+
tryGsapRotationIntercept,
|
|
31
|
+
} from "./gsapRuntimeBridge";
|
|
32
|
+
import { useAnimatedPropertyCommit } from "./useAnimatedPropertyCommit";
|
|
33
|
+
import { useGsapSelectionHandlers } from "./useGsapSelectionHandlers";
|
|
20
34
|
|
|
21
35
|
// ── Types ──
|
|
22
36
|
|
|
@@ -194,17 +208,37 @@ export function useDomEditSession({
|
|
|
194
208
|
onClickToSource,
|
|
195
209
|
});
|
|
196
210
|
|
|
211
|
+
// Sync DOM selection → timeline selectedElementId so that clip selection
|
|
212
|
+
// highlights and diamond playhead fills work on cold-load URL restore.
|
|
213
|
+
useEffect(() => {
|
|
214
|
+
if (!domEditSelection?.id) return;
|
|
215
|
+
const { selectedElementId, elements, setSelectedElementId } = usePlayerStore.getState();
|
|
216
|
+
const matchKey = elements.find(
|
|
217
|
+
(el) => el.domId === domEditSelection.id || el.id === domEditSelection.id,
|
|
218
|
+
);
|
|
219
|
+
const key = matchKey ? (matchKey.key ?? matchKey.id) : null;
|
|
220
|
+
if (key && key !== selectedElementId) setSelectedElementId(key);
|
|
221
|
+
}, [domEditSelection?.id]);
|
|
222
|
+
|
|
197
223
|
// ── GSAP script editing ──
|
|
198
224
|
|
|
199
225
|
const { version: gsapCacheVersion, bump: bumpGsapCache } = useGsapCacheVersion();
|
|
200
226
|
|
|
227
|
+
const gsapSourceFile = domEditSelection?.sourceFile || activeCompPath || "index.html";
|
|
228
|
+
|
|
229
|
+
usePopulateKeyframeCacheForFile(
|
|
230
|
+
STUDIO_GSAP_PANEL_ENABLED ? (projectId ?? null) : null,
|
|
231
|
+
gsapSourceFile,
|
|
232
|
+
gsapCacheVersion,
|
|
233
|
+
);
|
|
234
|
+
|
|
201
235
|
const {
|
|
202
236
|
animations: selectedGsapAnimations,
|
|
203
237
|
multipleTimelines: gsapMultipleTimelines,
|
|
204
238
|
unsupportedTimelinePattern: gsapUnsupportedTimelinePattern,
|
|
205
239
|
} = useGsapAnimationsForElement(
|
|
206
240
|
STUDIO_GSAP_PANEL_ENABLED ? (projectId ?? null) : null,
|
|
207
|
-
|
|
241
|
+
gsapSourceFile,
|
|
208
242
|
domEditSelection
|
|
209
243
|
? { id: domEditSelection.id ?? null, selector: domEditSelection.selector ?? null }
|
|
210
244
|
: null,
|
|
@@ -212,6 +246,7 @@ export function useDomEditSession({
|
|
|
212
246
|
);
|
|
213
247
|
|
|
214
248
|
const {
|
|
249
|
+
commitMutation: gsapCommitMutation,
|
|
215
250
|
updateGsapProperty,
|
|
216
251
|
updateGsapMeta,
|
|
217
252
|
deleteGsapAnimation,
|
|
@@ -221,6 +256,10 @@ export function useDomEditSession({
|
|
|
221
256
|
updateGsapFromProperty,
|
|
222
257
|
addGsapFromProperty,
|
|
223
258
|
removeGsapFromProperty,
|
|
259
|
+
addKeyframe,
|
|
260
|
+
removeKeyframe,
|
|
261
|
+
convertToKeyframes,
|
|
262
|
+
removeAllKeyframes,
|
|
224
263
|
} = useGsapScriptCommits({
|
|
225
264
|
projectIdRef,
|
|
226
265
|
activeCompPath,
|
|
@@ -250,6 +289,7 @@ export function useDomEditSession({
|
|
|
250
289
|
handleDomMotionCommit,
|
|
251
290
|
handleDomMotionClear,
|
|
252
291
|
handleDomEditElementDelete,
|
|
292
|
+
handleDomZIndexReorderCommit,
|
|
253
293
|
} = useDomEditCommits({
|
|
254
294
|
activeCompPath,
|
|
255
295
|
previewIframeRef,
|
|
@@ -270,77 +310,144 @@ export function useDomEditSession({
|
|
|
270
310
|
buildDomSelectionFromTarget,
|
|
271
311
|
});
|
|
272
312
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
);
|
|
296
|
-
|
|
297
|
-
const handleGsapAddAnimation = useCallback(
|
|
298
|
-
(method: "to" | "from" | "set" | "fromTo") => {
|
|
299
|
-
if (!domEditSelection) return;
|
|
300
|
-
addGsapAnimation(domEditSelection, method, currentTime);
|
|
313
|
+
// GSAP-aware: intercept offset/resize/rotation to commit via script mutation when animated.
|
|
314
|
+
const handleGsapAwarePathOffsetCommit = useCallback(
|
|
315
|
+
async (selection: DomEditSelection, next: { x: number; y: number }) => {
|
|
316
|
+
if (gsapCommitMutation) {
|
|
317
|
+
const handled = await tryGsapDragIntercept(
|
|
318
|
+
selection,
|
|
319
|
+
next,
|
|
320
|
+
selectedGsapAnimations,
|
|
321
|
+
previewIframeRef.current,
|
|
322
|
+
gsapCommitMutation,
|
|
323
|
+
async () => {
|
|
324
|
+
const pid = projectId;
|
|
325
|
+
if (!pid) return [];
|
|
326
|
+
const parsed = await fetchParsedAnimations(pid, gsapSourceFile);
|
|
327
|
+
if (!parsed) return [];
|
|
328
|
+
const target = { id: selection.id ?? null, selector: selection.selector ?? null };
|
|
329
|
+
return getAnimationsForElement(parsed.animations, target);
|
|
330
|
+
},
|
|
331
|
+
);
|
|
332
|
+
if (handled) return;
|
|
333
|
+
}
|
|
334
|
+
handleDomPathOffsetCommit(selection, next);
|
|
301
335
|
},
|
|
302
|
-
[
|
|
336
|
+
[
|
|
337
|
+
handleDomPathOffsetCommit,
|
|
338
|
+
selectedGsapAnimations,
|
|
339
|
+
gsapCommitMutation,
|
|
340
|
+
previewIframeRef,
|
|
341
|
+
projectId,
|
|
342
|
+
gsapSourceFile,
|
|
343
|
+
],
|
|
303
344
|
);
|
|
304
345
|
|
|
305
|
-
const
|
|
306
|
-
(
|
|
307
|
-
|
|
308
|
-
|
|
346
|
+
const makeFetchFallback = useCallback(
|
|
347
|
+
(selection: DomEditSelection) => async () => {
|
|
348
|
+
const pid = projectId;
|
|
349
|
+
if (!pid) return [];
|
|
350
|
+
const parsed = await fetchParsedAnimations(pid, gsapSourceFile);
|
|
351
|
+
if (!parsed) return [];
|
|
352
|
+
return getAnimationsForElement(parsed.animations, {
|
|
353
|
+
id: selection.id ?? null,
|
|
354
|
+
selector: selection.selector ?? null,
|
|
355
|
+
});
|
|
309
356
|
},
|
|
310
|
-
[
|
|
357
|
+
[projectId, gsapSourceFile],
|
|
311
358
|
);
|
|
312
359
|
|
|
313
|
-
const
|
|
314
|
-
(
|
|
315
|
-
if (
|
|
316
|
-
|
|
360
|
+
const handleGsapAwareBoxSizeCommit = useCallback(
|
|
361
|
+
async (selection: DomEditSelection, next: { width: number; height: number }) => {
|
|
362
|
+
if (gsapCommitMutation) {
|
|
363
|
+
const handled = await tryGsapResizeIntercept(
|
|
364
|
+
selection,
|
|
365
|
+
next,
|
|
366
|
+
selectedGsapAnimations,
|
|
367
|
+
previewIframeRef.current,
|
|
368
|
+
gsapCommitMutation,
|
|
369
|
+
makeFetchFallback(selection),
|
|
370
|
+
);
|
|
371
|
+
if (handled) return;
|
|
372
|
+
}
|
|
373
|
+
handleDomBoxSizeCommit(selection, next);
|
|
317
374
|
},
|
|
318
|
-
[
|
|
375
|
+
[
|
|
376
|
+
handleDomBoxSizeCommit,
|
|
377
|
+
selectedGsapAnimations,
|
|
378
|
+
gsapCommitMutation,
|
|
379
|
+
previewIframeRef,
|
|
380
|
+
makeFetchFallback,
|
|
381
|
+
],
|
|
319
382
|
);
|
|
320
383
|
|
|
321
|
-
const
|
|
322
|
-
(
|
|
323
|
-
if (
|
|
324
|
-
|
|
384
|
+
const handleGsapAwareRotationCommit = useCallback(
|
|
385
|
+
async (selection: DomEditSelection, next: { angle: number }) => {
|
|
386
|
+
if (gsapCommitMutation) {
|
|
387
|
+
const handled = await tryGsapRotationIntercept(
|
|
388
|
+
selection,
|
|
389
|
+
next.angle,
|
|
390
|
+
selectedGsapAnimations,
|
|
391
|
+
previewIframeRef.current,
|
|
392
|
+
gsapCommitMutation,
|
|
393
|
+
makeFetchFallback(selection),
|
|
394
|
+
);
|
|
395
|
+
if (handled) return;
|
|
396
|
+
}
|
|
397
|
+
handleDomRotationCommit(selection, next);
|
|
325
398
|
},
|
|
326
|
-
[
|
|
399
|
+
[
|
|
400
|
+
handleDomRotationCommit,
|
|
401
|
+
selectedGsapAnimations,
|
|
402
|
+
gsapCommitMutation,
|
|
403
|
+
previewIframeRef,
|
|
404
|
+
makeFetchFallback,
|
|
405
|
+
],
|
|
327
406
|
);
|
|
328
407
|
|
|
329
|
-
const
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
408
|
+
const {
|
|
409
|
+
handleGsapUpdateProperty,
|
|
410
|
+
handleGsapUpdateMeta,
|
|
411
|
+
handleGsapDeleteAnimation,
|
|
412
|
+
handleGsapAddAnimation,
|
|
413
|
+
handleGsapAddProperty,
|
|
414
|
+
handleGsapRemoveProperty,
|
|
415
|
+
handleGsapUpdateFromProperty,
|
|
416
|
+
handleGsapAddFromProperty,
|
|
417
|
+
handleGsapRemoveFromProperty,
|
|
418
|
+
handleGsapAddKeyframe,
|
|
419
|
+
handleGsapRemoveKeyframe,
|
|
420
|
+
handleGsapConvertToKeyframes,
|
|
421
|
+
handleGsapRemoveAllKeyframes,
|
|
422
|
+
handleResetSelectedElementKeyframes,
|
|
423
|
+
} = useGsapSelectionHandlers({
|
|
424
|
+
domEditSelection,
|
|
425
|
+
updateGsapProperty,
|
|
426
|
+
updateGsapMeta,
|
|
427
|
+
deleteGsapAnimation,
|
|
428
|
+
addGsapAnimation,
|
|
429
|
+
addGsapProperty,
|
|
430
|
+
removeGsapProperty,
|
|
431
|
+
updateGsapFromProperty,
|
|
432
|
+
addGsapFromProperty,
|
|
433
|
+
removeGsapFromProperty,
|
|
434
|
+
addKeyframe,
|
|
435
|
+
removeKeyframe,
|
|
436
|
+
convertToKeyframes,
|
|
437
|
+
removeAllKeyframes,
|
|
438
|
+
currentTime,
|
|
439
|
+
handleDomManualEditsReset,
|
|
440
|
+
selectedGsapAnimations,
|
|
441
|
+
});
|
|
336
442
|
|
|
337
|
-
const
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
443
|
+
const commitAnimatedProperty = useAnimatedPropertyCommit({
|
|
444
|
+
selectedGsapAnimations,
|
|
445
|
+
gsapCommitMutation,
|
|
446
|
+
addGsapAnimation: (sel, method, time) => addGsapAnimation(sel, method, time),
|
|
447
|
+
convertToKeyframes: (sel, animId) => convertToKeyframes(sel, animId),
|
|
448
|
+
previewIframeRef,
|
|
449
|
+
bumpGsapCache,
|
|
450
|
+
});
|
|
344
451
|
|
|
345
452
|
// Sync selection from preview document on load / refresh
|
|
346
453
|
// eslint-disable-next-line no-restricted-syntax
|
|
@@ -431,10 +538,8 @@ export function useDomEditSession({
|
|
|
431
538
|
agentModalAnchorPoint,
|
|
432
539
|
copiedAgentPrompt,
|
|
433
540
|
agentPromptSelectionContext,
|
|
434
|
-
|
|
435
541
|
// Refs
|
|
436
542
|
domEditSelectionRef,
|
|
437
|
-
|
|
438
543
|
// Callbacks
|
|
439
544
|
handleTimelineElementSelect,
|
|
440
545
|
handlePreviewCanvasMouseDown,
|
|
@@ -445,10 +550,11 @@ export function useDomEditSession({
|
|
|
445
550
|
handleDomStyleCommit,
|
|
446
551
|
handleDomAttributeCommit,
|
|
447
552
|
handleDomHtmlAttributeCommit,
|
|
448
|
-
handleDomPathOffsetCommit,
|
|
553
|
+
handleDomPathOffsetCommit: handleGsapAwarePathOffsetCommit,
|
|
449
554
|
handleDomGroupPathOffsetCommit,
|
|
450
|
-
|
|
451
|
-
|
|
555
|
+
handleDomZIndexReorderCommit,
|
|
556
|
+
handleDomBoxSizeCommit: handleGsapAwareBoxSizeCommit,
|
|
557
|
+
handleDomRotationCommit: handleGsapAwareRotationCommit,
|
|
452
558
|
handleDomManualEditsReset,
|
|
453
559
|
handleDomMotionCommit,
|
|
454
560
|
handleDomMotionClear,
|
|
@@ -482,5 +588,13 @@ export function useDomEditSession({
|
|
|
482
588
|
handleGsapUpdateFromProperty,
|
|
483
589
|
handleGsapAddFromProperty,
|
|
484
590
|
handleGsapRemoveFromProperty,
|
|
591
|
+
handleGsapAddKeyframe,
|
|
592
|
+
handleGsapRemoveKeyframe,
|
|
593
|
+
handleGsapConvertToKeyframes,
|
|
594
|
+
handleGsapRemoveAllKeyframes,
|
|
595
|
+
handleResetSelectedElementKeyframes,
|
|
596
|
+
commitAnimatedProperty,
|
|
597
|
+
invalidateGsapCache: bumpGsapCache,
|
|
598
|
+
previewIframeRef,
|
|
485
599
|
};
|
|
486
600
|
}
|
|
@@ -3,6 +3,8 @@ import type { ParsedGsap } from "@hyperframes/core/gsap-parser";
|
|
|
3
3
|
import type { DomEditSelection } from "../components/editor/domEditingTypes";
|
|
4
4
|
import type { EditHistoryKind } from "../utils/editHistory";
|
|
5
5
|
import { applySoftReload } from "../utils/gsapSoftReload";
|
|
6
|
+
import { executeOptimistic } from "../utils/optimisticUpdate";
|
|
7
|
+
import { usePlayerStore, type KeyframeCacheEntry } from "../player/store/playerStore";
|
|
6
8
|
|
|
7
9
|
const PROPERTY_DEFAULTS: Record<string, number> = {
|
|
8
10
|
opacity: 1,
|
|
@@ -70,6 +72,27 @@ async function mutateGsapScript(
|
|
|
70
72
|
}
|
|
71
73
|
}
|
|
72
74
|
|
|
75
|
+
function buildCacheKey(sourceFile: string, elementId: string): string {
|
|
76
|
+
return `${sourceFile}#${elementId}`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function readKeyframeSnapshot(
|
|
80
|
+
sourceFile: string,
|
|
81
|
+
elementId: string | null | undefined,
|
|
82
|
+
): KeyframeCacheEntry | undefined {
|
|
83
|
+
if (!elementId) return undefined;
|
|
84
|
+
return usePlayerStore.getState().keyframeCache.get(buildCacheKey(sourceFile, elementId));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function writeKeyframeCache(
|
|
88
|
+
sourceFile: string,
|
|
89
|
+
elementId: string | null | undefined,
|
|
90
|
+
data: KeyframeCacheEntry | undefined,
|
|
91
|
+
): void {
|
|
92
|
+
if (!elementId) return;
|
|
93
|
+
usePlayerStore.getState().setKeyframeCache(buildCacheKey(sourceFile, elementId), data);
|
|
94
|
+
}
|
|
95
|
+
|
|
73
96
|
interface GsapScriptCommitsParams {
|
|
74
97
|
projectIdRef: React.MutableRefObject<string | null>;
|
|
75
98
|
activeCompPath: string | null;
|
|
@@ -113,7 +136,13 @@ export function useGsapScriptCommits({
|
|
|
113
136
|
async (
|
|
114
137
|
selection: DomEditSelection,
|
|
115
138
|
mutation: Record<string, unknown>,
|
|
116
|
-
options: {
|
|
139
|
+
options: {
|
|
140
|
+
label: string;
|
|
141
|
+
coalesceKey?: string;
|
|
142
|
+
softReload?: boolean;
|
|
143
|
+
skipReload?: boolean;
|
|
144
|
+
beforeReload?: () => void;
|
|
145
|
+
},
|
|
117
146
|
) => {
|
|
118
147
|
const pid = projectIdRef.current;
|
|
119
148
|
if (!pid) return;
|
|
@@ -135,6 +164,21 @@ export function useGsapScriptCommits({
|
|
|
135
164
|
|
|
136
165
|
onCacheInvalidate();
|
|
137
166
|
|
|
167
|
+
if (result.parsed?.animations) {
|
|
168
|
+
const { setKeyframeCache } = usePlayerStore.getState();
|
|
169
|
+
for (const anim of result.parsed.animations) {
|
|
170
|
+
if (!anim.keyframes) continue;
|
|
171
|
+
const id = anim.targetSelector.match(/^#([\w-]+)/)?.[1];
|
|
172
|
+
if (!id) continue;
|
|
173
|
+
setKeyframeCache(`${targetPath}#${id}`, anim.keyframes);
|
|
174
|
+
if (targetPath !== "index.html") setKeyframeCache(`index.html#${id}`, anim.keyframes);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (options.skipReload) return;
|
|
179
|
+
|
|
180
|
+
options.beforeReload?.();
|
|
181
|
+
|
|
138
182
|
if (options.softReload && result.scriptText) {
|
|
139
183
|
if (!applySoftReload(previewIframeRef.current, result.scriptText)) {
|
|
140
184
|
reloadPreview();
|
|
@@ -225,7 +269,7 @@ export function useGsapScriptCommits({
|
|
|
225
269
|
async (
|
|
226
270
|
selection: DomEditSelection,
|
|
227
271
|
method: "to" | "from" | "set" | "fromTo",
|
|
228
|
-
|
|
272
|
+
_currentTime?: number,
|
|
229
273
|
) => {
|
|
230
274
|
const { selector, autoId } = ensureElementAddressable(selection);
|
|
231
275
|
|
|
@@ -253,12 +297,15 @@ export function useGsapScriptCommits({
|
|
|
253
297
|
if (!data.changed) return;
|
|
254
298
|
}
|
|
255
299
|
|
|
256
|
-
const
|
|
300
|
+
const elStart = Number.parseFloat(selection.dataAttributes?.start ?? "0") || 0;
|
|
301
|
+
const elDuration = Number.parseFloat(selection.dataAttributes?.duration ?? "1") || 1;
|
|
302
|
+
const position = Math.round(elStart * 1000) / 1000;
|
|
303
|
+
const duration = Math.round(elDuration * 1000) / 1000;
|
|
257
304
|
const toDefaults: Record<string, Record<string, number>> = {
|
|
258
305
|
from: { opacity: 0 },
|
|
259
|
-
to: { opacity: 1 },
|
|
306
|
+
to: { x: 0, y: 0, opacity: 1 },
|
|
260
307
|
set: { opacity: 1 },
|
|
261
|
-
fromTo: { opacity: 1 },
|
|
308
|
+
fromTo: { x: 0, y: 0, opacity: 1 },
|
|
262
309
|
};
|
|
263
310
|
|
|
264
311
|
await commitMutation(
|
|
@@ -267,8 +314,8 @@ export function useGsapScriptCommits({
|
|
|
267
314
|
type: "add",
|
|
268
315
|
targetSelector: selector,
|
|
269
316
|
method,
|
|
270
|
-
position
|
|
271
|
-
duration: method === "set" ? undefined :
|
|
317
|
+
position,
|
|
318
|
+
duration: method === "set" ? undefined : duration,
|
|
272
319
|
ease: method === "set" ? undefined : "power2.out",
|
|
273
320
|
properties: toDefaults[method] ?? { opacity: 1 },
|
|
274
321
|
fromProperties: method === "fromTo" ? { opacity: 0 } : undefined,
|
|
@@ -353,7 +400,93 @@ export function useGsapScriptCommits({
|
|
|
353
400
|
[commitMutation],
|
|
354
401
|
);
|
|
355
402
|
|
|
403
|
+
const addKeyframe = useCallback(
|
|
404
|
+
(
|
|
405
|
+
selection: DomEditSelection,
|
|
406
|
+
animationId: string,
|
|
407
|
+
percentage: number,
|
|
408
|
+
property: string,
|
|
409
|
+
value: number | string,
|
|
410
|
+
) => {
|
|
411
|
+
const sf = selection.sourceFile || activeCompPath || "index.html";
|
|
412
|
+
const elementId = selection.id;
|
|
413
|
+
void executeOptimistic<KeyframeCacheEntry | undefined>({
|
|
414
|
+
apply: () => {
|
|
415
|
+
const prev = readKeyframeSnapshot(sf, elementId);
|
|
416
|
+
if (prev) {
|
|
417
|
+
const newKeyframes = [
|
|
418
|
+
...prev.keyframes,
|
|
419
|
+
{ percentage, properties: { [property]: value } },
|
|
420
|
+
].sort((a, b) => a.percentage - b.percentage);
|
|
421
|
+
writeKeyframeCache(sf, elementId, { ...prev, keyframes: newKeyframes });
|
|
422
|
+
}
|
|
423
|
+
return prev;
|
|
424
|
+
},
|
|
425
|
+
persist: () =>
|
|
426
|
+
commitMutation(
|
|
427
|
+
selection,
|
|
428
|
+
{ type: "add-keyframe", animationId, percentage, properties: { [property]: value } },
|
|
429
|
+
{ label: `Add keyframe at ${percentage}%`, softReload: true },
|
|
430
|
+
),
|
|
431
|
+
rollback: (prev) => {
|
|
432
|
+
writeKeyframeCache(sf, elementId, prev);
|
|
433
|
+
},
|
|
434
|
+
});
|
|
435
|
+
},
|
|
436
|
+
[commitMutation, activeCompPath],
|
|
437
|
+
);
|
|
438
|
+
|
|
439
|
+
const removeKeyframe = useCallback(
|
|
440
|
+
(selection: DomEditSelection, animationId: string, percentage: number) => {
|
|
441
|
+
const sf = selection.sourceFile || activeCompPath || "index.html";
|
|
442
|
+
const elementId = selection.id;
|
|
443
|
+
void executeOptimistic<KeyframeCacheEntry | undefined>({
|
|
444
|
+
apply: () => {
|
|
445
|
+
const prev = readKeyframeSnapshot(sf, elementId);
|
|
446
|
+
if (prev) {
|
|
447
|
+
const newKeyframes = prev.keyframes.filter((kf) => kf.percentage !== percentage);
|
|
448
|
+
writeKeyframeCache(sf, elementId, { ...prev, keyframes: newKeyframes });
|
|
449
|
+
}
|
|
450
|
+
return prev;
|
|
451
|
+
},
|
|
452
|
+
persist: () =>
|
|
453
|
+
commitMutation(
|
|
454
|
+
selection,
|
|
455
|
+
{ type: "remove-keyframe", animationId, percentage },
|
|
456
|
+
{ label: `Remove keyframe at ${percentage}%`, softReload: true },
|
|
457
|
+
),
|
|
458
|
+
rollback: (prev) => {
|
|
459
|
+
writeKeyframeCache(sf, elementId, prev);
|
|
460
|
+
},
|
|
461
|
+
});
|
|
462
|
+
},
|
|
463
|
+
[commitMutation, activeCompPath],
|
|
464
|
+
);
|
|
465
|
+
|
|
466
|
+
const convertToKeyframes = useCallback(
|
|
467
|
+
(selection: DomEditSelection, animationId: string) => {
|
|
468
|
+
void commitMutation(
|
|
469
|
+
selection,
|
|
470
|
+
{ type: "convert-to-keyframes", animationId },
|
|
471
|
+
{ label: "Convert to keyframes" },
|
|
472
|
+
);
|
|
473
|
+
},
|
|
474
|
+
[commitMutation],
|
|
475
|
+
);
|
|
476
|
+
|
|
477
|
+
const removeAllKeyframes = useCallback(
|
|
478
|
+
(selection: DomEditSelection, animationId: string) => {
|
|
479
|
+
void commitMutation(
|
|
480
|
+
selection,
|
|
481
|
+
{ type: "remove-all-keyframes", animationId },
|
|
482
|
+
{ label: "Remove all keyframes", softReload: true },
|
|
483
|
+
);
|
|
484
|
+
},
|
|
485
|
+
[commitMutation],
|
|
486
|
+
);
|
|
487
|
+
|
|
356
488
|
return {
|
|
489
|
+
commitMutation,
|
|
357
490
|
updateGsapProperty,
|
|
358
491
|
updateGsapMeta,
|
|
359
492
|
deleteGsapAnimation,
|
|
@@ -363,5 +496,9 @@ export function useGsapScriptCommits({
|
|
|
363
496
|
updateGsapFromProperty,
|
|
364
497
|
addGsapFromProperty,
|
|
365
498
|
removeGsapFromProperty,
|
|
499
|
+
addKeyframe,
|
|
500
|
+
removeKeyframe,
|
|
501
|
+
convertToKeyframes,
|
|
502
|
+
removeAllKeyframes,
|
|
366
503
|
};
|
|
367
504
|
}
|