@hyperframes/studio 0.6.86 → 0.6.87
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-BA19FAPN.js +143 -0
- package/dist/assets/index-CGlIm_-E.css +1 -0
- package/dist/index.html +2 -2
- package/package.json +4 -4
- package/src/App.tsx +159 -6
- package/src/components/StudioHeader.tsx +20 -7
- package/src/components/StudioPreviewArea.tsx +6 -1
- package/src/components/StudioRightPanel.tsx +13 -0
- package/src/components/StudioToast.tsx +47 -7
- package/src/components/TimelineToolbar.tsx +12 -122
- package/src/components/editor/AnimationCard.tsx +64 -10
- package/src/components/editor/ArcPathControls.tsx +131 -0
- package/src/components/editor/BorderRadiusEditor.tsx +209 -0
- package/src/components/editor/DomEditOverlay.tsx +70 -11
- package/src/components/editor/DopesheetStrip.tsx +141 -0
- package/src/components/editor/EaseCurveSection.tsx +82 -7
- package/src/components/editor/GestureTrailOverlay.tsx +132 -0
- package/src/components/editor/GsapAnimationSection.tsx +14 -1
- package/src/components/editor/KeyframeDiamond.tsx +27 -12
- package/src/components/editor/LayersPanel.tsx +14 -12
- package/src/components/editor/MotionPathOverlay.tsx +146 -0
- package/src/components/editor/PropertyPanel.tsx +196 -66
- package/src/components/editor/SourceEditor.tsx +0 -1
- package/src/components/editor/StaggerControls.tsx +61 -0
- package/src/components/editor/domEditOverlayGeometry.test.ts +13 -0
- package/src/components/editor/domEditOverlayGeometry.ts +2 -1
- package/src/components/editor/domEditing.test.ts +43 -0
- package/src/components/editor/domEditing.ts +2 -0
- package/src/components/editor/domEditingElement.ts +25 -2
- package/src/components/editor/domEditingLayers.test.ts +78 -0
- package/src/components/editor/domEditingLayers.ts +33 -13
- package/src/components/editor/domEditingTypes.ts +1 -0
- package/src/components/editor/manualEditingAvailability.ts +1 -1
- package/src/components/editor/manualEdits.ts +3 -0
- package/src/components/editor/manualEditsDom.ts +23 -5
- package/src/components/editor/manualOffsetDrag.ts +59 -0
- package/src/components/editor/panelTokens.ts +10 -0
- package/src/components/editor/propertyPanelColor.tsx +2 -2
- package/src/components/editor/propertyPanelFill.tsx +1 -1
- package/src/components/editor/propertyPanelHelpers.ts +18 -2
- package/src/components/editor/propertyPanelMediaSection.tsx +1 -1
- package/src/components/editor/propertyPanelPrimitives.tsx +38 -25
- package/src/components/editor/propertyPanelSections.tsx +4 -6
- package/src/components/editor/propertyPanelStyleSections.tsx +30 -8
- package/src/components/editor/useDomEditOverlayRects.ts +46 -2
- package/src/components/renders/RenderQueue.tsx +121 -100
- package/src/components/renders/RenderQueueItem.tsx +13 -13
- package/src/contexts/DomEditContext.tsx +12 -0
- package/src/contexts/FileManagerContext.tsx +3 -0
- package/src/contexts/StudioContext.tsx +0 -4
- package/src/hooks/gsapKeyframeCommit.ts +92 -0
- package/src/hooks/gsapRuntimeBridge.ts +147 -85
- package/src/hooks/gsapRuntimeKeyframes.ts +75 -24
- package/src/hooks/gsapRuntimePreview.ts +19 -0
- package/src/hooks/useAppHotkeys.ts +18 -0
- package/src/hooks/useAskAgentModal.ts +2 -4
- package/src/hooks/useDomEditCommits.ts +11 -17
- package/src/hooks/useDomEditSession.ts +47 -4
- package/src/hooks/useEnableKeyframes.ts +171 -0
- package/src/hooks/useFileManager.ts +7 -0
- package/src/hooks/useGestureRecording.ts +340 -0
- package/src/hooks/useGsapScriptCommits.ts +171 -35
- package/src/hooks/useGsapSelectionHandlers.ts +27 -8
- package/src/hooks/useGsapTweenCache.ts +169 -11
- package/src/hooks/useKeyframeKeyboard.ts +103 -0
- package/src/hooks/useStudioContextValue.ts +5 -4
- package/src/hooks/useStudioUrlState.ts +1 -2
- package/src/hooks/useTimelineEditing.ts +50 -3
- package/src/hooks/useToast.ts +6 -1
- package/src/player/components/ShortcutsPanel.tsx +40 -0
- package/src/player/components/TimelineClipDiamonds.tsx +3 -3
- package/src/player/components/TimelinePropertyRows.tsx +120 -0
- package/src/player/lib/timelineDOM.test.ts +55 -0
- package/src/player/lib/timelineDOM.ts +13 -0
- package/src/player/lib/timelineIframeHelpers.test.ts +51 -0
- package/src/player/lib/timelineIframeHelpers.ts +1 -0
- package/src/player/store/playerStore.ts +43 -0
- package/src/utils/audioBeatDetection.ts +58 -0
- package/src/utils/globalTimeCompiler.test.ts +169 -0
- package/src/utils/globalTimeCompiler.ts +77 -0
- package/src/utils/gsapSoftReload.ts +30 -10
- package/src/utils/keyframeSnapping.test.ts +74 -0
- package/src/utils/keyframeSnapping.ts +63 -0
- package/src/utils/rdpSimplify.ts +183 -0
- package/src/utils/sourcePatcher.ts +2 -0
- package/dist/assets/index-BT9VHgSy.js +0 -140
- package/dist/assets/index-DHcptK1_.css +0 -1
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { useCallback, useEffect, useRef } from "react";
|
|
2
|
-
import type { ParsedGsap } from "@hyperframes/core/gsap-parser";
|
|
2
|
+
import type { GsapAnimation, 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
6
|
import { executeOptimistic } from "../utils/optimisticUpdate";
|
|
7
7
|
import { usePlayerStore, type KeyframeCacheEntry } from "../player/store/playerStore";
|
|
8
|
+
import { commitKeyframeAtTimeImpl } from "./gsapKeyframeCommit";
|
|
8
9
|
|
|
9
10
|
const PROPERTY_DEFAULTS: Record<string, number> = {
|
|
10
11
|
opacity: 1,
|
|
@@ -71,11 +72,69 @@ async function mutateGsapScript(
|
|
|
71
72
|
return null;
|
|
72
73
|
}
|
|
73
74
|
}
|
|
74
|
-
|
|
75
|
+
function updateKeyframeCacheFromParsed(
|
|
76
|
+
animations: GsapAnimation[],
|
|
77
|
+
targetPath: string,
|
|
78
|
+
selectionId: string | undefined,
|
|
79
|
+
mutation: Record<string, unknown>,
|
|
80
|
+
): void {
|
|
81
|
+
const { setKeyframeCache, elements } = usePlayerStore.getState();
|
|
82
|
+
const idsWithKeyframes = new Set<string>();
|
|
83
|
+
const merged = new Map<string, KeyframeCacheEntry>();
|
|
84
|
+
for (const anim of animations) {
|
|
85
|
+
const id = anim.targetSelector.match(/^#([\w-]+)/)?.[1];
|
|
86
|
+
if (!id || !anim.keyframes) continue;
|
|
87
|
+
idsWithKeyframes.add(id);
|
|
88
|
+
|
|
89
|
+
// Convert tween-relative percentages to clip-relative so diamonds
|
|
90
|
+
// render at the correct position within the timeline clip.
|
|
91
|
+
const tweenPos = typeof anim.position === "number" ? anim.position : 0;
|
|
92
|
+
const tweenDur = anim.duration ?? 1;
|
|
93
|
+
const timelineEl = elements.find(
|
|
94
|
+
(el) => el.domId === id || (el.key ?? el.id) === `${targetPath}#${id}`,
|
|
95
|
+
);
|
|
96
|
+
const elStart = timelineEl?.start ?? 0;
|
|
97
|
+
const elDuration = timelineEl?.duration ?? 4;
|
|
98
|
+
const clipKeyframes = anim.keyframes.keyframes.map((kf) => {
|
|
99
|
+
const absTime = tweenPos + (kf.percentage / 100) * tweenDur;
|
|
100
|
+
const clipPct =
|
|
101
|
+
elDuration > 0 ? Math.round(((absTime - elStart) / elDuration) * 1000) / 10 : kf.percentage;
|
|
102
|
+
return { ...kf, percentage: clipPct };
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const existing = merged.get(id);
|
|
106
|
+
if (existing) {
|
|
107
|
+
const byPct = new Map<number, (typeof existing.keyframes)[0]>();
|
|
108
|
+
for (const kf of [...existing.keyframes, ...clipKeyframes]) {
|
|
109
|
+
const prev = byPct.get(kf.percentage);
|
|
110
|
+
if (prev) {
|
|
111
|
+
prev.properties = { ...prev.properties, ...kf.properties };
|
|
112
|
+
if (kf.ease) prev.ease = kf.ease;
|
|
113
|
+
} else {
|
|
114
|
+
byPct.set(kf.percentage, { ...kf, properties: { ...kf.properties } });
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
existing.keyframes = Array.from(byPct.values()).sort((a, b) => a.percentage - b.percentage);
|
|
118
|
+
} else {
|
|
119
|
+
merged.set(id, { ...anim.keyframes, keyframes: clipKeyframes });
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
for (const [id, entry] of merged) {
|
|
123
|
+
setKeyframeCache(`${targetPath}#${id}`, entry);
|
|
124
|
+
setKeyframeCache(id, entry);
|
|
125
|
+
if (targetPath !== "index.html") setKeyframeCache(`index.html#${id}`, entry);
|
|
126
|
+
}
|
|
127
|
+
const targetId =
|
|
128
|
+
(mutation as { targetSelector?: string }).targetSelector?.match(/^#([\w-]+)/)?.[1] ??
|
|
129
|
+
selectionId;
|
|
130
|
+
if (targetId && !idsWithKeyframes.has(targetId)) {
|
|
131
|
+
setKeyframeCache(`${targetPath}#${targetId}`, undefined);
|
|
132
|
+
if (targetPath !== "index.html") setKeyframeCache(`index.html#${targetId}`, undefined);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
75
135
|
function buildCacheKey(sourceFile: string, elementId: string): string {
|
|
76
136
|
return `${sourceFile}#${elementId}`;
|
|
77
137
|
}
|
|
78
|
-
|
|
79
138
|
function readKeyframeSnapshot(
|
|
80
139
|
sourceFile: string,
|
|
81
140
|
elementId: string | null | undefined,
|
|
@@ -83,7 +142,6 @@ function readKeyframeSnapshot(
|
|
|
83
142
|
if (!elementId) return undefined;
|
|
84
143
|
return usePlayerStore.getState().keyframeCache.get(buildCacheKey(sourceFile, elementId));
|
|
85
144
|
}
|
|
86
|
-
|
|
87
145
|
function writeKeyframeCache(
|
|
88
146
|
sourceFile: string,
|
|
89
147
|
elementId: string | null | undefined,
|
|
@@ -92,7 +150,6 @@ function writeKeyframeCache(
|
|
|
92
150
|
if (!elementId) return;
|
|
93
151
|
usePlayerStore.getState().setKeyframeCache(buildCacheKey(sourceFile, elementId), data);
|
|
94
152
|
}
|
|
95
|
-
|
|
96
153
|
interface GsapScriptCommitsParams {
|
|
97
154
|
projectIdRef: React.MutableRefObject<string | null>;
|
|
98
155
|
activeCompPath: string | null;
|
|
@@ -108,8 +165,8 @@ interface GsapScriptCommitsParams {
|
|
|
108
165
|
domEditSaveTimestampRef: React.MutableRefObject<number>;
|
|
109
166
|
reloadPreview: () => void;
|
|
110
167
|
onCacheInvalidate: () => void;
|
|
168
|
+
onFileContentChanged?: (path: string, content: string) => void;
|
|
111
169
|
}
|
|
112
|
-
|
|
113
170
|
const DEBOUNCE_MS = 150;
|
|
114
171
|
|
|
115
172
|
// fallow-ignore-next-line complexity unit-size
|
|
@@ -121,6 +178,7 @@ export function useGsapScriptCommits({
|
|
|
121
178
|
domEditSaveTimestampRef,
|
|
122
179
|
reloadPreview,
|
|
123
180
|
onCacheInvalidate,
|
|
181
|
+
onFileContentChanged,
|
|
124
182
|
}: GsapScriptCommitsParams) {
|
|
125
183
|
const pendingPropertyEditRef = useRef<{
|
|
126
184
|
selection: DomEditSelection;
|
|
@@ -129,7 +187,6 @@ export function useGsapScriptCommits({
|
|
|
129
187
|
value: number | string;
|
|
130
188
|
} | null>(null);
|
|
131
189
|
const debounceTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
132
|
-
|
|
133
190
|
/** Send a mutation and record the edit in undo history. */
|
|
134
191
|
const commitMutation = useCallback(
|
|
135
192
|
// fallow-ignore-next-line complexity
|
|
@@ -162,21 +219,23 @@ export function useGsapScriptCommits({
|
|
|
162
219
|
});
|
|
163
220
|
}
|
|
164
221
|
|
|
165
|
-
|
|
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
|
-
}
|
|
222
|
+
if (result.after != null) {
|
|
223
|
+
onFileContentChanged?.(targetPath, result.after);
|
|
176
224
|
}
|
|
177
225
|
|
|
178
226
|
if (options.skipReload) return;
|
|
179
227
|
|
|
228
|
+
// Write the keyframe cache immediately from the parsed response
|
|
229
|
+
// (synchronous — the timeline diamonds appear on the next render).
|
|
230
|
+
if (result.parsed?.animations) {
|
|
231
|
+
updateKeyframeCacheFromParsed(
|
|
232
|
+
result.parsed.animations,
|
|
233
|
+
targetPath,
|
|
234
|
+
selection.id ?? undefined,
|
|
235
|
+
mutation,
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
180
239
|
options.beforeReload?.();
|
|
181
240
|
|
|
182
241
|
if (options.softReload && result.scriptText) {
|
|
@@ -186,6 +245,11 @@ export function useGsapScriptCommits({
|
|
|
186
245
|
} else {
|
|
187
246
|
reloadPreview();
|
|
188
247
|
}
|
|
248
|
+
|
|
249
|
+
// Bump the cache version AFTER reload so the async re-fetch in
|
|
250
|
+
// useGsapAnimationsForElement reads the post-reload script, not
|
|
251
|
+
// the stale pre-reload version that would overwrite fresh data.
|
|
252
|
+
onCacheInvalidate();
|
|
189
253
|
},
|
|
190
254
|
[
|
|
191
255
|
projectIdRef,
|
|
@@ -195,9 +259,9 @@ export function useGsapScriptCommits({
|
|
|
195
259
|
domEditSaveTimestampRef,
|
|
196
260
|
reloadPreview,
|
|
197
261
|
onCacheInvalidate,
|
|
262
|
+
onFileContentChanged,
|
|
198
263
|
],
|
|
199
264
|
);
|
|
200
|
-
|
|
201
265
|
const flushPendingPropertyEdit = useCallback(() => {
|
|
202
266
|
const pending = pendingPropertyEditRef.current;
|
|
203
267
|
if (!pending) return;
|
|
@@ -227,7 +291,6 @@ export function useGsapScriptCommits({
|
|
|
227
291
|
},
|
|
228
292
|
[flushPendingPropertyEdit],
|
|
229
293
|
);
|
|
230
|
-
|
|
231
294
|
useEffect(() => {
|
|
232
295
|
return () => {
|
|
233
296
|
if (debounceTimerRef.current) clearTimeout(debounceTimerRef.current);
|
|
@@ -252,7 +315,6 @@ export function useGsapScriptCommits({
|
|
|
252
315
|
},
|
|
253
316
|
[commitMutation],
|
|
254
317
|
);
|
|
255
|
-
|
|
256
318
|
const deleteGsapAnimation = useCallback(
|
|
257
319
|
(selection: DomEditSelection, animationId: string) => {
|
|
258
320
|
void commitMutation(
|
|
@@ -263,7 +325,6 @@ export function useGsapScriptCommits({
|
|
|
263
325
|
},
|
|
264
326
|
[commitMutation],
|
|
265
327
|
);
|
|
266
|
-
|
|
267
328
|
const addGsapAnimation = useCallback(
|
|
268
329
|
// fallow-ignore-next-line complexity
|
|
269
330
|
async (
|
|
@@ -285,6 +346,7 @@ export function useGsapScriptCommits({
|
|
|
285
346
|
body: JSON.stringify({
|
|
286
347
|
target: {
|
|
287
348
|
id: selection.id,
|
|
349
|
+
hfId: selection.hfId,
|
|
288
350
|
selector: selection.selector,
|
|
289
351
|
selectorIndex: selection.selectorIndex,
|
|
290
352
|
},
|
|
@@ -325,7 +387,6 @@ export function useGsapScriptCommits({
|
|
|
325
387
|
},
|
|
326
388
|
[commitMutation, projectIdRef, activeCompPath],
|
|
327
389
|
);
|
|
328
|
-
|
|
329
390
|
const addGsapProperty = useCallback(
|
|
330
391
|
// fallow-ignore-next-line complexity
|
|
331
392
|
(selection: DomEditSelection, animationId: string, property: string) => {
|
|
@@ -346,7 +407,6 @@ export function useGsapScriptCommits({
|
|
|
346
407
|
},
|
|
347
408
|
[commitMutation],
|
|
348
409
|
);
|
|
349
|
-
|
|
350
410
|
const removeGsapProperty = useCallback(
|
|
351
411
|
(selection: DomEditSelection, animationId: string, property: string) => {
|
|
352
412
|
void commitMutation(
|
|
@@ -357,7 +417,6 @@ export function useGsapScriptCommits({
|
|
|
357
417
|
},
|
|
358
418
|
[commitMutation],
|
|
359
419
|
);
|
|
360
|
-
|
|
361
420
|
const updateGsapFromProperty = useCallback(
|
|
362
421
|
(
|
|
363
422
|
selection: DomEditSelection,
|
|
@@ -376,7 +435,6 @@ export function useGsapScriptCommits({
|
|
|
376
435
|
},
|
|
377
436
|
[commitMutation],
|
|
378
437
|
);
|
|
379
|
-
|
|
380
438
|
const addGsapFromProperty = useCallback(
|
|
381
439
|
(selection: DomEditSelection, animationId: string, property: string) => {
|
|
382
440
|
const defaultValue = PROPERTY_DEFAULTS[property] ?? 0;
|
|
@@ -388,7 +446,6 @@ export function useGsapScriptCommits({
|
|
|
388
446
|
},
|
|
389
447
|
[commitMutation],
|
|
390
448
|
);
|
|
391
|
-
|
|
392
449
|
const removeGsapFromProperty = useCallback(
|
|
393
450
|
(selection: DomEditSelection, animationId: string, property: string) => {
|
|
394
451
|
void commitMutation(
|
|
@@ -399,7 +456,6 @@ export function useGsapScriptCommits({
|
|
|
399
456
|
},
|
|
400
457
|
[commitMutation],
|
|
401
458
|
);
|
|
402
|
-
|
|
403
459
|
const addKeyframe = useCallback(
|
|
404
460
|
(
|
|
405
461
|
selection: DomEditSelection,
|
|
@@ -435,7 +491,21 @@ export function useGsapScriptCommits({
|
|
|
435
491
|
},
|
|
436
492
|
[commitMutation, activeCompPath],
|
|
437
493
|
);
|
|
438
|
-
|
|
494
|
+
const addKeyframeBatch = useCallback(
|
|
495
|
+
(
|
|
496
|
+
selection: DomEditSelection,
|
|
497
|
+
animationId: string,
|
|
498
|
+
percentage: number,
|
|
499
|
+
properties: Record<string, number | string>,
|
|
500
|
+
) => {
|
|
501
|
+
return commitMutation(
|
|
502
|
+
selection,
|
|
503
|
+
{ type: "add-keyframe", animationId, percentage, properties },
|
|
504
|
+
{ label: `Add keyframe at ${percentage}%`, softReload: true },
|
|
505
|
+
);
|
|
506
|
+
},
|
|
507
|
+
[commitMutation],
|
|
508
|
+
);
|
|
439
509
|
const removeKeyframe = useCallback(
|
|
440
510
|
(selection: DomEditSelection, animationId: string, percentage: number) => {
|
|
441
511
|
const sf = selection.sourceFile || activeCompPath || "index.html";
|
|
@@ -462,18 +532,20 @@ export function useGsapScriptCommits({
|
|
|
462
532
|
},
|
|
463
533
|
[commitMutation, activeCompPath],
|
|
464
534
|
);
|
|
465
|
-
|
|
466
535
|
const convertToKeyframes = useCallback(
|
|
467
|
-
(
|
|
468
|
-
|
|
536
|
+
(
|
|
537
|
+
selection: DomEditSelection,
|
|
538
|
+
animationId: string,
|
|
539
|
+
resolvedFromValues?: Record<string, number | string>,
|
|
540
|
+
) => {
|
|
541
|
+
return commitMutation(
|
|
469
542
|
selection,
|
|
470
|
-
{ type: "convert-to-keyframes", animationId },
|
|
543
|
+
{ type: "convert-to-keyframes", animationId, resolvedFromValues },
|
|
471
544
|
{ label: "Convert to keyframes" },
|
|
472
545
|
);
|
|
473
546
|
},
|
|
474
547
|
[commitMutation],
|
|
475
548
|
);
|
|
476
|
-
|
|
477
549
|
const removeAllKeyframes = useCallback(
|
|
478
550
|
(selection: DomEditSelection, animationId: string) => {
|
|
479
551
|
void commitMutation(
|
|
@@ -484,7 +556,66 @@ export function useGsapScriptCommits({
|
|
|
484
556
|
},
|
|
485
557
|
[commitMutation],
|
|
486
558
|
);
|
|
487
|
-
|
|
559
|
+
const setArcPath = useCallback(
|
|
560
|
+
(
|
|
561
|
+
selection: DomEditSelection,
|
|
562
|
+
animationId: string,
|
|
563
|
+
config: {
|
|
564
|
+
enabled: boolean;
|
|
565
|
+
autoRotate?: boolean | number;
|
|
566
|
+
segments?: Array<{
|
|
567
|
+
curviness: number;
|
|
568
|
+
cp1?: { x: number; y: number };
|
|
569
|
+
cp2?: { x: number; y: number };
|
|
570
|
+
}>;
|
|
571
|
+
},
|
|
572
|
+
) => {
|
|
573
|
+
void commitMutation(
|
|
574
|
+
selection,
|
|
575
|
+
{ type: "set-arc-path" as const, animationId, ...config },
|
|
576
|
+
{ label: config.enabled ? "Enable arc path" : "Disable arc path", softReload: true },
|
|
577
|
+
);
|
|
578
|
+
},
|
|
579
|
+
[commitMutation],
|
|
580
|
+
);
|
|
581
|
+
const updateArcSegment = useCallback(
|
|
582
|
+
(
|
|
583
|
+
selection: DomEditSelection,
|
|
584
|
+
animationId: string,
|
|
585
|
+
segmentIndex: number,
|
|
586
|
+
update: {
|
|
587
|
+
curviness?: number;
|
|
588
|
+
cp1?: { x: number; y: number };
|
|
589
|
+
cp2?: { x: number; y: number };
|
|
590
|
+
},
|
|
591
|
+
) => {
|
|
592
|
+
void commitMutation(
|
|
593
|
+
selection,
|
|
594
|
+
{ type: "update-arc-segment" as const, animationId, segmentIndex, ...update },
|
|
595
|
+
{ label: "Update arc segment", softReload: true },
|
|
596
|
+
);
|
|
597
|
+
},
|
|
598
|
+
[commitMutation],
|
|
599
|
+
);
|
|
600
|
+
const removeArcPath = useCallback(
|
|
601
|
+
(selection: DomEditSelection, animationId: string) => {
|
|
602
|
+
void commitMutation(
|
|
603
|
+
selection,
|
|
604
|
+
{ type: "remove-arc-path" as const, animationId },
|
|
605
|
+
{ label: "Remove arc path", softReload: true },
|
|
606
|
+
);
|
|
607
|
+
},
|
|
608
|
+
[commitMutation],
|
|
609
|
+
);
|
|
610
|
+
const commitKeyframeAtTime = useCallback(
|
|
611
|
+
(
|
|
612
|
+
selection: DomEditSelection,
|
|
613
|
+
absoluteTime: number,
|
|
614
|
+
animations: GsapAnimation[],
|
|
615
|
+
properties: Record<string, number | string>,
|
|
616
|
+
) => commitKeyframeAtTimeImpl(selection, absoluteTime, animations, properties, commitMutation),
|
|
617
|
+
[commitMutation],
|
|
618
|
+
);
|
|
488
619
|
return {
|
|
489
620
|
commitMutation,
|
|
490
621
|
updateGsapProperty,
|
|
@@ -497,8 +628,13 @@ export function useGsapScriptCommits({
|
|
|
497
628
|
addGsapFromProperty,
|
|
498
629
|
removeGsapFromProperty,
|
|
499
630
|
addKeyframe,
|
|
631
|
+
addKeyframeBatch,
|
|
500
632
|
removeKeyframe,
|
|
501
633
|
convertToKeyframes,
|
|
502
634
|
removeAllKeyframes,
|
|
635
|
+
setArcPath,
|
|
636
|
+
updateArcSegment,
|
|
637
|
+
removeArcPath,
|
|
638
|
+
commitKeyframeAtTime,
|
|
503
639
|
};
|
|
504
640
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useCallback } from "react";
|
|
2
2
|
import type { DomEditSelection } from "../components/editor/domEditing";
|
|
3
|
+
import { usePlayerStore } from "../player";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Thin useCallback wrappers that guard on `domEditSelection` before
|
|
@@ -19,10 +20,10 @@ export function useGsapSelectionHandlers({
|
|
|
19
20
|
addGsapFromProperty,
|
|
20
21
|
removeGsapFromProperty,
|
|
21
22
|
addKeyframe,
|
|
23
|
+
addKeyframeBatch,
|
|
22
24
|
removeKeyframe,
|
|
23
25
|
convertToKeyframes,
|
|
24
26
|
removeAllKeyframes,
|
|
25
|
-
currentTime,
|
|
26
27
|
handleDomManualEditsReset,
|
|
27
28
|
selectedGsapAnimations,
|
|
28
29
|
}: {
|
|
@@ -61,10 +62,20 @@ export function useGsapSelectionHandlers({
|
|
|
61
62
|
property: string,
|
|
62
63
|
value: number | string,
|
|
63
64
|
) => void;
|
|
65
|
+
addKeyframeBatch: (
|
|
66
|
+
sel: DomEditSelection,
|
|
67
|
+
animId: string,
|
|
68
|
+
percentage: number,
|
|
69
|
+
properties: Record<string, number | string>,
|
|
70
|
+
) => Promise<void>;
|
|
64
71
|
removeKeyframe: (sel: DomEditSelection, animId: string, percentage: number) => void;
|
|
65
|
-
convertToKeyframes: (
|
|
72
|
+
convertToKeyframes: (
|
|
73
|
+
sel: DomEditSelection,
|
|
74
|
+
animId: string,
|
|
75
|
+
resolvedFromValues?: Record<string, number | string>,
|
|
76
|
+
) => void;
|
|
66
77
|
removeAllKeyframes: (sel: DomEditSelection, animId: string) => void;
|
|
67
|
-
|
|
78
|
+
|
|
68
79
|
handleDomManualEditsReset: (sel: DomEditSelection) => void;
|
|
69
80
|
selectedGsapAnimations: { id: string; keyframes?: unknown }[];
|
|
70
81
|
}) {
|
|
@@ -95,12 +106,12 @@ export function useGsapSelectionHandlers({
|
|
|
95
106
|
const handleGsapAddAnimation = useCallback(
|
|
96
107
|
(method: "to" | "from" | "set" | "fromTo") => {
|
|
97
108
|
if (!domEditSelection) return;
|
|
98
|
-
addGsapAnimation(domEditSelection, method, currentTime);
|
|
109
|
+
addGsapAnimation(domEditSelection, method, usePlayerStore.getState().currentTime);
|
|
99
110
|
if (domEditSelection.element.hasAttribute("data-hf-studio-path-offset")) {
|
|
100
111
|
handleDomManualEditsReset(domEditSelection);
|
|
101
112
|
}
|
|
102
113
|
},
|
|
103
|
-
[domEditSelection, addGsapAnimation,
|
|
114
|
+
[domEditSelection, addGsapAnimation, handleDomManualEditsReset],
|
|
104
115
|
);
|
|
105
116
|
|
|
106
117
|
const handleGsapAddProperty = useCallback(
|
|
@@ -151,6 +162,13 @@ export function useGsapSelectionHandlers({
|
|
|
151
162
|
[domEditSelection, addKeyframe],
|
|
152
163
|
);
|
|
153
164
|
|
|
165
|
+
const handleGsapAddKeyframeBatch = useCallback(
|
|
166
|
+
(animId: string, percentage: number, properties: Record<string, number | string>) => {
|
|
167
|
+
if (!domEditSelection) return Promise.resolve();
|
|
168
|
+
return addKeyframeBatch(domEditSelection, animId, percentage, properties);
|
|
169
|
+
},
|
|
170
|
+
[domEditSelection, addKeyframeBatch],
|
|
171
|
+
);
|
|
154
172
|
const handleGsapRemoveKeyframe = useCallback(
|
|
155
173
|
(animId: string, percentage: number) => {
|
|
156
174
|
if (!domEditSelection) return;
|
|
@@ -160,9 +178,9 @@ export function useGsapSelectionHandlers({
|
|
|
160
178
|
);
|
|
161
179
|
|
|
162
180
|
const handleGsapConvertToKeyframes = useCallback(
|
|
163
|
-
(animId: string) => {
|
|
164
|
-
if (!domEditSelection) return;
|
|
165
|
-
convertToKeyframes(domEditSelection, animId);
|
|
181
|
+
(animId: string, resolvedFromValues?: Record<string, number | string>) => {
|
|
182
|
+
if (!domEditSelection) return Promise.resolve();
|
|
183
|
+
return convertToKeyframes(domEditSelection, animId, resolvedFromValues);
|
|
166
184
|
},
|
|
167
185
|
[domEditSelection, convertToKeyframes],
|
|
168
186
|
);
|
|
@@ -194,6 +212,7 @@ export function useGsapSelectionHandlers({
|
|
|
194
212
|
handleGsapAddFromProperty,
|
|
195
213
|
handleGsapRemoveFromProperty,
|
|
196
214
|
handleGsapAddKeyframe,
|
|
215
|
+
handleGsapAddKeyframeBatch,
|
|
197
216
|
handleGsapRemoveKeyframe,
|
|
198
217
|
handleGsapConvertToKeyframes,
|
|
199
218
|
handleGsapRemoveAllKeyframes,
|