@hyperframes/studio 0.6.97 → 0.6.99
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/hyperframes-player-DgsMQSvV.js +418 -0
- package/dist/assets/index-B62bDCQv.css +1 -0
- package/dist/assets/{index-HveJ0MuV.js → index-C52IT_lp.js} +1 -1
- package/dist/assets/index-DOh7E1uj.js +1 -0
- package/dist/assets/index-DrwSRbsl.js +252 -0
- package/dist/index.html +2 -2
- package/package.json +7 -5
- package/src/App.tsx +182 -177
- package/src/captions/store.ts +11 -11
- package/src/components/StudioHeader.tsx +4 -4
- package/src/components/StudioLeftSidebar.tsx +2 -2
- package/src/components/StudioPreviewArea.tsx +225 -183
- package/src/components/StudioRightPanel.tsx +3 -3
- package/src/components/TimelineToolbar.tsx +25 -0
- package/src/components/editor/DomEditOverlay.tsx +2 -5
- package/src/components/editor/EaseCurveSection.tsx +2 -3
- package/src/components/editor/GestureTrailOverlay.tsx +4 -3
- package/src/components/editor/LayersPanel.tsx +3 -9
- package/src/components/editor/PropertyPanel.tsx +20 -61
- package/src/components/editor/colorValue.ts +3 -1
- package/src/components/editor/domEditOverlayGestures.ts +54 -1
- package/src/components/editor/domEditOverlayStartGesture.ts +5 -2
- package/src/components/editor/gradientValue.ts +3 -3
- package/src/components/editor/keyframeMove.test.ts +101 -0
- package/src/components/editor/keyframeMove.ts +151 -0
- package/src/components/editor/manualEditsDom.ts +0 -12
- package/src/components/editor/propertyPanelHelpers.ts +10 -38
- package/src/components/editor/propertyPanelMediaSection.tsx +1 -5
- package/src/components/editor/propertyPanelTimingSection.tsx +1 -6
- package/src/components/editor/propertyPanelTransformCommit.ts +129 -0
- package/src/components/editor/studioMotionOps.test.ts +1 -1
- package/src/components/editor/studioMotionOps.ts +2 -1
- package/src/components/editor/useDomEditOverlayGestures.ts +1 -46
- package/src/components/nle/NLELayout.tsx +1 -24
- package/src/components/sidebar/BlocksTab.tsx +2 -2
- package/src/contexts/DomEditContext.tsx +134 -31
- package/src/contexts/StudioContext.tsx +90 -40
- package/src/contexts/TimelineEditContext.tsx +47 -0
- package/src/hooks/domEditCommitTypes.ts +14 -0
- package/src/hooks/gsapDragCommit.ts +9 -24
- package/src/hooks/gsapKeyframeCacheHelpers.ts +2 -1
- package/src/hooks/gsapKeyframeCommit.ts +5 -15
- package/src/hooks/gsapRuntimeBridge.ts +18 -52
- package/src/hooks/gsapRuntimeKeyframes.ts +8 -57
- package/src/hooks/gsapRuntimeReaders.ts +19 -26
- package/src/hooks/gsapScriptCommitHelpers.ts +1 -11
- package/src/hooks/gsapScriptCommitTypes.ts +58 -0
- package/src/hooks/gsapShared.ts +157 -0
- package/src/hooks/timelineEditingHelpers.ts +63 -2
- package/src/hooks/useAnimatedPropertyCommit.ts +3 -25
- package/src/hooks/useAppHotkeys.ts +299 -377
- package/src/hooks/useConsoleErrorCapture.ts +33 -5
- package/src/hooks/useDomEditCommits.ts +35 -293
- package/src/hooks/useDomEditPositionPatchCommit.ts +1 -1
- package/src/hooks/useDomEditSession.ts +78 -249
- package/src/hooks/useDomEditTextCommits.ts +1 -1
- package/src/hooks/useDomEditWiring.ts +255 -0
- package/src/hooks/useDomGeometryCommits.ts +181 -0
- package/src/hooks/useDomSelection.ts +10 -27
- package/src/hooks/useEditorSave.ts +82 -0
- package/src/hooks/useElementLifecycleOps.ts +177 -0
- package/src/hooks/useEnableKeyframes.ts +10 -15
- package/src/hooks/useFileManager.ts +32 -114
- package/src/hooks/useFileTree.ts +80 -0
- package/src/hooks/useGestureCommit.ts +7 -5
- package/src/hooks/useGestureRecording.ts +1 -1
- package/src/hooks/useGsapAnimationOps.ts +122 -0
- package/src/hooks/useGsapArcPathOps.ts +61 -0
- package/src/hooks/useGsapAwareEditing.ts +242 -0
- package/src/hooks/useGsapKeyframeOps.ts +167 -0
- package/src/hooks/useGsapPropertyDebounce.ts +135 -0
- package/src/hooks/useGsapScriptCommits.ts +58 -570
- package/src/hooks/useGsapSelectionHandlers.ts +22 -9
- package/src/hooks/useGsapTweenCache.ts +35 -29
- package/src/hooks/useLintModal.ts +7 -0
- package/src/hooks/useMusicBeatAnalysis.ts +152 -0
- package/src/hooks/useRazorSplit.ts +1 -1
- package/src/hooks/useRenderClipContent.ts +46 -21
- package/src/hooks/useTimelineEditing.ts +48 -4
- package/src/player/components/AudioWaveform.tsx +29 -4
- package/src/player/components/BeatStrip.tsx +166 -0
- package/src/player/components/Timeline.tsx +39 -18
- package/src/player/components/TimelineCanvas.tsx +52 -12
- package/src/player/components/TimelineClipDiamonds.tsx +130 -20
- package/src/player/components/TimelinePropertyRows.tsx +8 -2
- package/src/player/components/TimelineRuler.tsx +36 -2
- package/src/player/components/timelineEditing.ts +30 -5
- package/src/player/components/useTimelineClipDrag.ts +155 -4
- package/src/player/components/useTimelinePlayhead.ts +30 -1
- package/src/player/hooks/useTimelinePlayer.ts +47 -45
- package/src/player/lib/mediaProbe.ts +46 -3
- package/src/player/lib/playbackScrub.ts +16 -0
- package/src/player/lib/timelineDOM.ts +10 -2
- package/src/player/lib/timelineIframeHelpers.ts +89 -0
- package/src/player/store/playerStore.ts +92 -33
- package/src/utils/beatEditActions.ts +109 -0
- package/src/utils/beatEditing.ts +136 -0
- package/src/utils/clipboardPayload.ts +3 -2
- package/src/utils/compositionPatterns.ts +2 -0
- package/src/utils/keyframeSelection.test.ts +45 -0
- package/src/utils/keyframeSelection.ts +29 -0
- package/src/utils/rounding.ts +9 -0
- package/src/utils/studioHelpers.ts +5 -2
- package/src/utils/studioUrlState.ts +2 -1
- package/src/utils/timelineAssetDrop.ts +6 -5
- package/src/utils/timelineInspector.ts +15 -100
- package/dist/assets/hyperframes-player-Daj5djxa.js +0 -418
- package/dist/assets/index-B0twsRu0.css +0 -1
- package/dist/assets/index-Cfye9xzo.js +0 -251
- package/src/components/editor/DopesheetStrip.tsx +0 -141
- package/src/components/editor/StaggerControls.tsx +0 -61
- package/src/components/editor/TimelineLayerPanel.test.ts +0 -42
- package/src/components/editor/TimelineLayerPanel.tsx +0 -15
- package/src/components/nle/TimelineEditorNotice.tsx +0 -133
- package/src/hooks/gsapRuntimePreview.ts +0 -19
- package/src/player/components/timelineUtils.ts +0 -211
- package/src/utils/audioBeatDetection.ts +0 -58
- package/src/utils/keyframeSnapping.test.ts +0 -74
- package/src/utils/keyframeSnapping.ts +0 -63
- package/src/utils/timelineInspector.test.ts +0 -79
|
@@ -1,38 +1,26 @@
|
|
|
1
|
-
import { useCallback
|
|
2
|
-
import type { GsapAnimation, ParsedGsap } from "@hyperframes/core/gsap-parser";
|
|
1
|
+
import { useCallback } from "react";
|
|
3
2
|
import { findUnsafeMutationValues } from "@hyperframes/core/studio-api/finite-mutation";
|
|
4
3
|
import type { DomEditSelection } from "../components/editor/domEditingTypes";
|
|
5
|
-
import type { EditHistoryKind } from "../utils/editHistory";
|
|
6
4
|
import { applySoftReload } from "../utils/gsapSoftReload";
|
|
7
|
-
import {
|
|
8
|
-
import type { KeyframeCacheEntry } from "../player/store/playerStore";
|
|
9
|
-
import { commitKeyframeAtTimeImpl } from "./gsapKeyframeCommit";
|
|
10
|
-
import {
|
|
11
|
-
updateKeyframeCacheFromParsed,
|
|
12
|
-
readKeyframeSnapshot,
|
|
13
|
-
writeKeyframeCache,
|
|
14
|
-
} from "./gsapKeyframeCacheHelpers";
|
|
15
|
-
import {
|
|
16
|
-
useGsapSaveFailureTelemetry,
|
|
17
|
-
useSafeGsapCommitMutation,
|
|
18
|
-
} from "./useSafeGsapCommitMutation";
|
|
5
|
+
import { updateKeyframeCacheFromParsed } from "./gsapKeyframeCacheHelpers";
|
|
19
6
|
import {
|
|
20
7
|
GsapMutationHttpError,
|
|
21
|
-
assignGsapTargetAutoIdIfNeeded,
|
|
22
|
-
ensureElementAddressable,
|
|
23
8
|
formatGsapMutationRejectionToast,
|
|
24
|
-
PROPERTY_DEFAULTS,
|
|
25
9
|
readJsonResponseBody,
|
|
26
10
|
} from "./gsapScriptCommitHelpers";
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
11
|
+
import type {
|
|
12
|
+
CommitMutationOptions,
|
|
13
|
+
GsapScriptCommitsParams,
|
|
14
|
+
MutationResult,
|
|
15
|
+
} from "./gsapScriptCommitTypes";
|
|
16
|
+
import { useGsapAnimationOps } from "./useGsapAnimationOps";
|
|
17
|
+
import { useGsapArcPathOps } from "./useGsapArcPathOps";
|
|
18
|
+
import { useGsapKeyframeOps } from "./useGsapKeyframeOps";
|
|
19
|
+
import { useGsapPropertyDebounce } from "./useGsapPropertyDebounce";
|
|
20
|
+
import {
|
|
21
|
+
useGsapSaveFailureTelemetry,
|
|
22
|
+
useSafeGsapCommitMutation,
|
|
23
|
+
} from "./useSafeGsapCommitMutation";
|
|
36
24
|
|
|
37
25
|
async function mutateGsapScript(
|
|
38
26
|
projectId: string,
|
|
@@ -47,554 +35,54 @@ async function mutateGsapScript(
|
|
|
47
35
|
body: JSON.stringify(mutation),
|
|
48
36
|
},
|
|
49
37
|
);
|
|
50
|
-
if (!res.ok)
|
|
51
|
-
throw new GsapMutationHttpError(res.status, await readJsonResponseBody(res));
|
|
52
|
-
}
|
|
38
|
+
if (!res.ok) throw new GsapMutationHttpError(res.status, await readJsonResponseBody(res));
|
|
53
39
|
const result = (await res.json()) as MutationResult;
|
|
54
|
-
if (!result.ok) {
|
|
55
|
-
throw new Error(`Failed to update GSAP in ${sourceFile}`);
|
|
56
|
-
}
|
|
40
|
+
if (!result.ok) throw new Error(`Failed to update GSAP in ${sourceFile}`);
|
|
57
41
|
return result;
|
|
58
42
|
}
|
|
59
43
|
|
|
60
|
-
|
|
61
|
-
sourceFile: string;
|
|
62
|
-
elementId: string | null | undefined;
|
|
63
|
-
apply: (entry: KeyframeCacheEntry) => KeyframeCacheEntry;
|
|
64
|
-
persist: () => Promise<void>;
|
|
65
|
-
}): Promise<void> {
|
|
66
|
-
return executeOptimistic<KeyframeCacheEntry | undefined>({
|
|
67
|
-
apply: () => {
|
|
68
|
-
const prev = readKeyframeSnapshot(options.sourceFile, options.elementId);
|
|
69
|
-
if (prev) writeKeyframeCache(options.sourceFile, options.elementId, options.apply(prev));
|
|
70
|
-
return prev;
|
|
71
|
-
},
|
|
72
|
-
persist: options.persist,
|
|
73
|
-
rollback: (prev) => {
|
|
74
|
-
writeKeyframeCache(options.sourceFile, options.elementId, prev);
|
|
75
|
-
},
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
interface GsapScriptCommitsParams {
|
|
80
|
-
projectIdRef: React.MutableRefObject<string | null>;
|
|
81
|
-
activeCompPath: string | null;
|
|
82
|
-
previewIframeRef: React.RefObject<HTMLIFrameElement | null>;
|
|
83
|
-
editHistory: {
|
|
84
|
-
recordEdit: (entry: {
|
|
85
|
-
label: string;
|
|
86
|
-
kind: EditHistoryKind;
|
|
87
|
-
coalesceKey?: string;
|
|
88
|
-
files: Record<string, { before: string; after: string }>;
|
|
89
|
-
}) => Promise<void>;
|
|
90
|
-
};
|
|
91
|
-
domEditSaveTimestampRef: React.MutableRefObject<number>;
|
|
92
|
-
reloadPreview: () => void;
|
|
93
|
-
onCacheInvalidate: () => void;
|
|
94
|
-
onFileContentChanged?: (path: string, content: string) => void;
|
|
95
|
-
showToast: (message: string, tone?: "error" | "info") => void;
|
|
96
|
-
}
|
|
97
|
-
const DEBOUNCE_MS = 150;
|
|
98
|
-
|
|
44
|
+
// oxfmt-ignore
|
|
99
45
|
// fallow-ignore-next-line complexity
|
|
100
|
-
export function useGsapScriptCommits({
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
onCacheInvalidate,
|
|
108
|
-
onFileContentChanged,
|
|
109
|
-
showToast,
|
|
110
|
-
}: GsapScriptCommitsParams) {
|
|
111
|
-
const pendingPropertyEditRef = useRef<{
|
|
112
|
-
selection: DomEditSelection;
|
|
113
|
-
animationId: string;
|
|
114
|
-
property: string;
|
|
115
|
-
value: number | string;
|
|
116
|
-
} | null>(null);
|
|
117
|
-
const debounceTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
118
|
-
/** Send a mutation and record the edit in undo history. */
|
|
119
|
-
const commitMutation = useCallback(
|
|
120
|
-
// fallow-ignore-next-line complexity
|
|
121
|
-
async (
|
|
122
|
-
selection: DomEditSelection,
|
|
123
|
-
mutation: Record<string, unknown>,
|
|
124
|
-
options: {
|
|
125
|
-
label: string;
|
|
126
|
-
coalesceKey?: string;
|
|
127
|
-
softReload?: boolean;
|
|
128
|
-
skipReload?: boolean;
|
|
129
|
-
beforeReload?: () => void;
|
|
130
|
-
},
|
|
131
|
-
) => {
|
|
132
|
-
const pid = projectIdRef.current;
|
|
133
|
-
if (!pid) return;
|
|
134
|
-
const unsafeFields = findUnsafeMutationValues(mutation);
|
|
135
|
-
if (unsafeFields.length > 0) {
|
|
136
|
-
showToast?.(
|
|
137
|
-
"Couldn't read element layout — try again at a different playhead time",
|
|
138
|
-
"error",
|
|
139
|
-
);
|
|
140
|
-
if (options.skipReload) return;
|
|
141
|
-
throw new Error(
|
|
142
|
-
`Mutation contains unsafe values: ${unsafeFields.map((field) => field.path).join(", ")}`,
|
|
143
|
-
);
|
|
144
|
-
}
|
|
145
|
-
const targetPath = selection.sourceFile || activeCompPath || "index.html";
|
|
146
|
-
let result: MutationResult;
|
|
147
|
-
try {
|
|
148
|
-
result = await mutateGsapScript(pid, targetPath, mutation);
|
|
149
|
-
} catch (error) {
|
|
150
|
-
if (error instanceof GsapMutationHttpError) {
|
|
151
|
-
showToast?.(formatGsapMutationRejectionToast(error), "error");
|
|
152
|
-
}
|
|
153
|
-
if (options.skipReload) return;
|
|
154
|
-
throw error;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
if (result.changed === false) {
|
|
158
|
-
if (options.skipReload) return;
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
domEditSaveTimestampRef.current = Date.now();
|
|
163
|
-
|
|
164
|
-
if (result.before != null && result.after != null) {
|
|
165
|
-
await editHistory.recordEdit({
|
|
166
|
-
label: options.label,
|
|
167
|
-
kind: "manual",
|
|
168
|
-
coalesceKey: options.coalesceKey,
|
|
169
|
-
files: { [targetPath]: { before: result.before, after: result.after } },
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
if (result.after != null) {
|
|
174
|
-
onFileContentChanged?.(targetPath, result.after);
|
|
175
|
-
}
|
|
176
|
-
|
|
46
|
+
export function useGsapScriptCommits({ projectIdRef, activeCompPath, previewIframeRef, editHistory, domEditSaveTimestampRef, reloadPreview, onCacheInvalidate, onFileContentChanged, showToast }: GsapScriptCommitsParams) {
|
|
47
|
+
const commitMutation = useCallback(async (selection: DomEditSelection, mutation: Record<string, unknown>, options: CommitMutationOptions) => {
|
|
48
|
+
const pid = projectIdRef.current;
|
|
49
|
+
if (!pid) return;
|
|
50
|
+
const unsafeFields = findUnsafeMutationValues(mutation);
|
|
51
|
+
if (unsafeFields.length > 0) {
|
|
52
|
+
showToast?.("Couldn't read element layout — try again at a different playhead time", "error");
|
|
177
53
|
if (options.skipReload) return;
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
[
|
|
206
|
-
projectIdRef,
|
|
207
|
-
activeCompPath,
|
|
208
|
-
previewIframeRef,
|
|
209
|
-
editHistory,
|
|
210
|
-
domEditSaveTimestampRef,
|
|
211
|
-
reloadPreview,
|
|
212
|
-
onCacheInvalidate,
|
|
213
|
-
onFileContentChanged,
|
|
214
|
-
showToast,
|
|
215
|
-
],
|
|
216
|
-
);
|
|
217
|
-
|
|
54
|
+
throw new Error(`Mutation contains unsafe values: ${unsafeFields.map((field) => field.path).join(", ")}`);
|
|
55
|
+
}
|
|
56
|
+
const targetPath = selection.sourceFile || activeCompPath || "index.html";
|
|
57
|
+
let result: MutationResult;
|
|
58
|
+
try {
|
|
59
|
+
result = await mutateGsapScript(pid, targetPath, mutation);
|
|
60
|
+
} catch (error) {
|
|
61
|
+
if (error instanceof GsapMutationHttpError) showToast?.(formatGsapMutationRejectionToast(error), "error");
|
|
62
|
+
if (options.skipReload) return;
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
if (result.changed === false) return;
|
|
66
|
+
domEditSaveTimestampRef.current = Date.now();
|
|
67
|
+
if (result.before != null && result.after != null) {
|
|
68
|
+
await editHistory.recordEdit({ label: options.label, kind: "manual", coalesceKey: options.coalesceKey, files: { [targetPath]: { before: result.before, after: result.after } } });
|
|
69
|
+
}
|
|
70
|
+
if (result.after != null) onFileContentChanged?.(targetPath, result.after);
|
|
71
|
+
if (options.skipReload) return;
|
|
72
|
+
if (result.parsed?.animations) updateKeyframeCacheFromParsed(result.parsed.animations, targetPath, selection.id ?? undefined, mutation);
|
|
73
|
+
options.beforeReload?.();
|
|
74
|
+
if (options.softReload && result.scriptText) {
|
|
75
|
+
if (!applySoftReload(previewIframeRef.current, result.scriptText)) reloadPreview();
|
|
76
|
+
} else {
|
|
77
|
+
reloadPreview();
|
|
78
|
+
}
|
|
79
|
+
onCacheInvalidate();
|
|
80
|
+
}, [projectIdRef, activeCompPath, previewIframeRef, editHistory, domEditSaveTimestampRef, reloadPreview, onCacheInvalidate, onFileContentChanged, showToast]);
|
|
218
81
|
const trackGsapSaveFailure = useGsapSaveFailureTelemetry(activeCompPath);
|
|
219
|
-
const commitMutationSafely = useSafeGsapCommitMutation(
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
);
|
|
224
|
-
|
|
225
|
-
const flushPendingPropertyEdit = useCallback(() => {
|
|
226
|
-
const pending = pendingPropertyEditRef.current;
|
|
227
|
-
if (!pending) return;
|
|
228
|
-
pendingPropertyEditRef.current = null;
|
|
229
|
-
const { selection, animationId, property, value } = pending;
|
|
230
|
-
commitMutationSafely(
|
|
231
|
-
selection,
|
|
232
|
-
{ type: "update-property", animationId, property, value },
|
|
233
|
-
{
|
|
234
|
-
label: `Edit GSAP ${property}`,
|
|
235
|
-
coalesceKey: `gsap:${animationId}:${property}`,
|
|
236
|
-
softReload: true,
|
|
237
|
-
},
|
|
238
|
-
);
|
|
239
|
-
}, [commitMutationSafely]);
|
|
240
|
-
|
|
241
|
-
const updateGsapProperty = useCallback(
|
|
242
|
-
(
|
|
243
|
-
selection: DomEditSelection,
|
|
244
|
-
animationId: string,
|
|
245
|
-
property: string,
|
|
246
|
-
value: number | string,
|
|
247
|
-
) => {
|
|
248
|
-
pendingPropertyEditRef.current = { selection, animationId, property, value };
|
|
249
|
-
if (debounceTimerRef.current) clearTimeout(debounceTimerRef.current);
|
|
250
|
-
debounceTimerRef.current = setTimeout(flushPendingPropertyEdit, DEBOUNCE_MS);
|
|
251
|
-
},
|
|
252
|
-
[flushPendingPropertyEdit],
|
|
253
|
-
);
|
|
254
|
-
useEffect(() => {
|
|
255
|
-
return () => {
|
|
256
|
-
if (debounceTimerRef.current) clearTimeout(debounceTimerRef.current);
|
|
257
|
-
flushPendingPropertyEdit();
|
|
258
|
-
};
|
|
259
|
-
}, [flushPendingPropertyEdit]);
|
|
260
|
-
|
|
261
|
-
const updateGsapMeta = useCallback(
|
|
262
|
-
(
|
|
263
|
-
selection: DomEditSelection,
|
|
264
|
-
animationId: string,
|
|
265
|
-
updates: { duration?: number; ease?: string; position?: number },
|
|
266
|
-
) => {
|
|
267
|
-
commitMutationSafely(
|
|
268
|
-
selection,
|
|
269
|
-
{ type: "update-meta", animationId, updates },
|
|
270
|
-
{
|
|
271
|
-
label: "Edit GSAP animation",
|
|
272
|
-
coalesceKey: `gsap:${animationId}:meta`,
|
|
273
|
-
},
|
|
274
|
-
);
|
|
275
|
-
},
|
|
276
|
-
[commitMutationSafely],
|
|
277
|
-
);
|
|
278
|
-
const deleteGsapAnimation = useCallback(
|
|
279
|
-
(selection: DomEditSelection, animationId: string) => {
|
|
280
|
-
commitMutationSafely(
|
|
281
|
-
selection,
|
|
282
|
-
{ type: "delete", animationId, stripStudioEdits: true },
|
|
283
|
-
{ label: "Delete GSAP animation" },
|
|
284
|
-
);
|
|
285
|
-
},
|
|
286
|
-
[commitMutationSafely],
|
|
287
|
-
);
|
|
288
|
-
const deleteAllForSelector = useCallback(
|
|
289
|
-
(selection: DomEditSelection, targetSelector: string) => {
|
|
290
|
-
void commitMutation(
|
|
291
|
-
selection,
|
|
292
|
-
{ type: "delete-all-for-selector", targetSelector },
|
|
293
|
-
{ label: "Delete all animations for element" },
|
|
294
|
-
);
|
|
295
|
-
},
|
|
296
|
-
[commitMutation],
|
|
297
|
-
);
|
|
298
|
-
const addGsapAnimation = useCallback(
|
|
299
|
-
// fallow-ignore-next-line complexity
|
|
300
|
-
async (
|
|
301
|
-
selection: DomEditSelection,
|
|
302
|
-
method: "to" | "from" | "set" | "fromTo",
|
|
303
|
-
_currentTime?: number,
|
|
304
|
-
) => {
|
|
305
|
-
const { selector, autoId } = ensureElementAddressable(selection);
|
|
306
|
-
|
|
307
|
-
if (autoId) {
|
|
308
|
-
const pid = projectIdRef.current;
|
|
309
|
-
const targetPath = selection.sourceFile || activeCompPath || "index.html";
|
|
310
|
-
if (!pid) return;
|
|
311
|
-
const assigned = await assignGsapTargetAutoIdIfNeeded({
|
|
312
|
-
projectId: pid,
|
|
313
|
-
targetPath,
|
|
314
|
-
selection,
|
|
315
|
-
autoId,
|
|
316
|
-
showToast,
|
|
317
|
-
});
|
|
318
|
-
if (!assigned) return;
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
const elStart = Number.parseFloat(selection.dataAttributes?.start ?? "0") || 0;
|
|
322
|
-
const elDuration = Number.parseFloat(selection.dataAttributes?.duration ?? "1") || 1;
|
|
323
|
-
const position = Math.round(elStart * 1000) / 1000;
|
|
324
|
-
const duration = Math.round(elDuration * 1000) / 1000;
|
|
325
|
-
const toDefaults: Record<string, Record<string, number>> = {
|
|
326
|
-
from: { opacity: 0 },
|
|
327
|
-
to: { x: 0, y: 0, opacity: 1 },
|
|
328
|
-
set: { opacity: 1 },
|
|
329
|
-
fromTo: { x: 0, y: 0, opacity: 1 },
|
|
330
|
-
};
|
|
331
|
-
|
|
332
|
-
await commitMutation(
|
|
333
|
-
selection,
|
|
334
|
-
{
|
|
335
|
-
type: "add",
|
|
336
|
-
targetSelector: selector,
|
|
337
|
-
method,
|
|
338
|
-
position,
|
|
339
|
-
duration: method === "set" ? undefined : duration,
|
|
340
|
-
ease: method === "set" ? undefined : "power2.out",
|
|
341
|
-
properties: toDefaults[method] ?? { opacity: 1 },
|
|
342
|
-
fromProperties: method === "fromTo" ? { opacity: 0 } : undefined,
|
|
343
|
-
},
|
|
344
|
-
{ label: `Add GSAP ${method} animation` },
|
|
345
|
-
);
|
|
346
|
-
},
|
|
347
|
-
[commitMutation, projectIdRef, activeCompPath, showToast],
|
|
348
|
-
);
|
|
349
|
-
const addGsapProperty = useCallback(
|
|
350
|
-
// fallow-ignore-next-line complexity
|
|
351
|
-
(selection: DomEditSelection, animationId: string, property: string) => {
|
|
352
|
-
let defaultValue = PROPERTY_DEFAULTS[property] ?? 0;
|
|
353
|
-
const el = selection.element;
|
|
354
|
-
if (property === "width" || property === "height") {
|
|
355
|
-
const rect = el.getBoundingClientRect();
|
|
356
|
-
defaultValue = Math.round(property === "width" ? rect.width : rect.height);
|
|
357
|
-
} else if (property === "opacity" || property === "autoAlpha") {
|
|
358
|
-
const cs = el.ownerDocument.defaultView?.getComputedStyle(el);
|
|
359
|
-
defaultValue = cs ? Number.parseFloat(cs.opacity) || 1 : 1;
|
|
360
|
-
}
|
|
361
|
-
commitMutationSafely(
|
|
362
|
-
selection,
|
|
363
|
-
{ type: "add-property", animationId, property, defaultValue },
|
|
364
|
-
{ label: `Add GSAP ${property}` },
|
|
365
|
-
);
|
|
366
|
-
},
|
|
367
|
-
[commitMutationSafely],
|
|
368
|
-
);
|
|
369
|
-
const removeGsapProperty = useCallback(
|
|
370
|
-
(selection: DomEditSelection, animationId: string, property: string) => {
|
|
371
|
-
commitMutationSafely(
|
|
372
|
-
selection,
|
|
373
|
-
{ type: "remove-property", animationId, property },
|
|
374
|
-
{ label: `Remove GSAP ${property}` },
|
|
375
|
-
);
|
|
376
|
-
},
|
|
377
|
-
[commitMutationSafely],
|
|
378
|
-
);
|
|
379
|
-
const updateGsapFromProperty = useCallback(
|
|
380
|
-
(
|
|
381
|
-
selection: DomEditSelection,
|
|
382
|
-
animationId: string,
|
|
383
|
-
property: string,
|
|
384
|
-
value: number | string,
|
|
385
|
-
) => {
|
|
386
|
-
commitMutationSafely(
|
|
387
|
-
selection,
|
|
388
|
-
{ type: "update-from-property", animationId, property, value },
|
|
389
|
-
{
|
|
390
|
-
label: `Edit GSAP from-${property}`,
|
|
391
|
-
coalesceKey: `gsap:${animationId}:from:${property}`,
|
|
392
|
-
},
|
|
393
|
-
);
|
|
394
|
-
},
|
|
395
|
-
[commitMutationSafely],
|
|
396
|
-
);
|
|
397
|
-
const addGsapFromProperty = useCallback(
|
|
398
|
-
(selection: DomEditSelection, animationId: string, property: string) => {
|
|
399
|
-
const defaultValue = PROPERTY_DEFAULTS[property] ?? 0;
|
|
400
|
-
commitMutationSafely(
|
|
401
|
-
selection,
|
|
402
|
-
{ type: "add-from-property", animationId, property, defaultValue },
|
|
403
|
-
{ label: `Add GSAP from-${property}` },
|
|
404
|
-
);
|
|
405
|
-
},
|
|
406
|
-
[commitMutationSafely],
|
|
407
|
-
);
|
|
408
|
-
const removeGsapFromProperty = useCallback(
|
|
409
|
-
(selection: DomEditSelection, animationId: string, property: string) => {
|
|
410
|
-
commitMutationSafely(
|
|
411
|
-
selection,
|
|
412
|
-
{ type: "remove-from-property", animationId, property },
|
|
413
|
-
{ label: `Remove GSAP from-${property}` },
|
|
414
|
-
);
|
|
415
|
-
},
|
|
416
|
-
[commitMutationSafely],
|
|
417
|
-
);
|
|
418
|
-
const addKeyframe = useCallback(
|
|
419
|
-
(
|
|
420
|
-
selection: DomEditSelection,
|
|
421
|
-
animationId: string,
|
|
422
|
-
percentage: number,
|
|
423
|
-
property: string,
|
|
424
|
-
value: number | string,
|
|
425
|
-
) => {
|
|
426
|
-
const sf = selection.sourceFile || activeCompPath || "index.html";
|
|
427
|
-
const elementId = selection.id;
|
|
428
|
-
const mutation = {
|
|
429
|
-
type: "add-keyframe",
|
|
430
|
-
animationId,
|
|
431
|
-
percentage,
|
|
432
|
-
properties: { [property]: value },
|
|
433
|
-
};
|
|
434
|
-
void executeOptimisticKeyframeCacheUpdate({
|
|
435
|
-
sourceFile: sf,
|
|
436
|
-
elementId,
|
|
437
|
-
apply: (prev) => ({
|
|
438
|
-
...prev,
|
|
439
|
-
keyframes: [...prev.keyframes, { percentage, properties: { [property]: value } }].sort(
|
|
440
|
-
(a, b) => a.percentage - b.percentage,
|
|
441
|
-
),
|
|
442
|
-
}),
|
|
443
|
-
persist: () =>
|
|
444
|
-
commitMutation(selection, mutation, {
|
|
445
|
-
label: `Add keyframe at ${percentage}%`,
|
|
446
|
-
softReload: true,
|
|
447
|
-
}),
|
|
448
|
-
}).catch((error) => {
|
|
449
|
-
trackGsapSaveFailure(error, selection, mutation, `Add keyframe at ${percentage}%`);
|
|
450
|
-
});
|
|
451
|
-
},
|
|
452
|
-
[commitMutation, activeCompPath, trackGsapSaveFailure],
|
|
453
|
-
);
|
|
454
|
-
const addKeyframeBatch = useCallback(
|
|
455
|
-
(
|
|
456
|
-
selection: DomEditSelection,
|
|
457
|
-
animationId: string,
|
|
458
|
-
percentage: number,
|
|
459
|
-
properties: Record<string, number | string>,
|
|
460
|
-
) => {
|
|
461
|
-
return commitMutation(
|
|
462
|
-
selection,
|
|
463
|
-
{ type: "add-keyframe", animationId, percentage, properties },
|
|
464
|
-
{ label: `Add keyframe at ${percentage}%`, softReload: true },
|
|
465
|
-
);
|
|
466
|
-
},
|
|
467
|
-
[commitMutation],
|
|
468
|
-
);
|
|
469
|
-
const removeKeyframe = useCallback(
|
|
470
|
-
(selection: DomEditSelection, animationId: string, percentage: number) => {
|
|
471
|
-
const sf = selection.sourceFile || activeCompPath || "index.html";
|
|
472
|
-
const elementId = selection.id;
|
|
473
|
-
const mutation = { type: "remove-keyframe", animationId, percentage };
|
|
474
|
-
void executeOptimisticKeyframeCacheUpdate({
|
|
475
|
-
sourceFile: sf,
|
|
476
|
-
elementId,
|
|
477
|
-
apply: (prev) => ({
|
|
478
|
-
...prev,
|
|
479
|
-
keyframes: prev.keyframes.filter(
|
|
480
|
-
(kf) => Math.abs((kf.tweenPercentage ?? kf.percentage) - percentage) > 0.2,
|
|
481
|
-
),
|
|
482
|
-
}),
|
|
483
|
-
persist: () =>
|
|
484
|
-
commitMutation(selection, mutation, {
|
|
485
|
-
label: `Remove keyframe at ${percentage}%`,
|
|
486
|
-
softReload: true,
|
|
487
|
-
}),
|
|
488
|
-
}).catch((error) => {
|
|
489
|
-
trackGsapSaveFailure(error, selection, mutation, `Remove keyframe at ${percentage}%`);
|
|
490
|
-
});
|
|
491
|
-
},
|
|
492
|
-
[commitMutation, activeCompPath, trackGsapSaveFailure],
|
|
493
|
-
);
|
|
494
|
-
const convertToKeyframes = useCallback(
|
|
495
|
-
(
|
|
496
|
-
selection: DomEditSelection,
|
|
497
|
-
animationId: string,
|
|
498
|
-
resolvedFromValues?: Record<string, number | string>,
|
|
499
|
-
) => {
|
|
500
|
-
return commitMutation(
|
|
501
|
-
selection,
|
|
502
|
-
{ type: "convert-to-keyframes", animationId, resolvedFromValues },
|
|
503
|
-
{ label: "Convert to keyframes" },
|
|
504
|
-
);
|
|
505
|
-
},
|
|
506
|
-
[commitMutation],
|
|
507
|
-
);
|
|
508
|
-
const removeAllKeyframes = useCallback(
|
|
509
|
-
(selection: DomEditSelection, animationId: string) => {
|
|
510
|
-
commitMutationSafely(
|
|
511
|
-
selection,
|
|
512
|
-
{ type: "remove-all-keyframes", animationId },
|
|
513
|
-
{ label: "Remove all keyframes", softReload: true },
|
|
514
|
-
);
|
|
515
|
-
},
|
|
516
|
-
[commitMutationSafely],
|
|
517
|
-
);
|
|
518
|
-
const setArcPath = useCallback(
|
|
519
|
-
(
|
|
520
|
-
selection: DomEditSelection,
|
|
521
|
-
animationId: string,
|
|
522
|
-
config: {
|
|
523
|
-
enabled: boolean;
|
|
524
|
-
autoRotate?: boolean | number;
|
|
525
|
-
segments?: Array<{
|
|
526
|
-
curviness: number;
|
|
527
|
-
cp1?: { x: number; y: number };
|
|
528
|
-
cp2?: { x: number; y: number };
|
|
529
|
-
}>;
|
|
530
|
-
},
|
|
531
|
-
) => {
|
|
532
|
-
commitMutationSafely(
|
|
533
|
-
selection,
|
|
534
|
-
{ type: "set-arc-path" as const, animationId, ...config },
|
|
535
|
-
{ label: config.enabled ? "Enable arc path" : "Disable arc path", softReload: true },
|
|
536
|
-
);
|
|
537
|
-
},
|
|
538
|
-
[commitMutationSafely],
|
|
539
|
-
);
|
|
540
|
-
const updateArcSegment = useCallback(
|
|
541
|
-
(
|
|
542
|
-
selection: DomEditSelection,
|
|
543
|
-
animationId: string,
|
|
544
|
-
segmentIndex: number,
|
|
545
|
-
update: {
|
|
546
|
-
curviness?: number;
|
|
547
|
-
cp1?: { x: number; y: number };
|
|
548
|
-
cp2?: { x: number; y: number };
|
|
549
|
-
},
|
|
550
|
-
) => {
|
|
551
|
-
commitMutationSafely(
|
|
552
|
-
selection,
|
|
553
|
-
{ type: "update-arc-segment" as const, animationId, segmentIndex, ...update },
|
|
554
|
-
{ label: "Update arc segment", softReload: true },
|
|
555
|
-
);
|
|
556
|
-
},
|
|
557
|
-
[commitMutationSafely],
|
|
558
|
-
);
|
|
559
|
-
const removeArcPath = useCallback(
|
|
560
|
-
(selection: DomEditSelection, animationId: string) => {
|
|
561
|
-
commitMutationSafely(
|
|
562
|
-
selection,
|
|
563
|
-
{ type: "remove-arc-path" as const, animationId },
|
|
564
|
-
{ label: "Remove arc path", softReload: true },
|
|
565
|
-
);
|
|
566
|
-
},
|
|
567
|
-
[commitMutationSafely],
|
|
568
|
-
);
|
|
569
|
-
const commitKeyframeAtTime = useCallback(
|
|
570
|
-
(
|
|
571
|
-
selection: DomEditSelection,
|
|
572
|
-
absoluteTime: number,
|
|
573
|
-
animations: GsapAnimation[],
|
|
574
|
-
properties: Record<string, number | string>,
|
|
575
|
-
) => commitKeyframeAtTimeImpl(selection, absoluteTime, animations, properties, commitMutation),
|
|
576
|
-
[commitMutation],
|
|
577
|
-
);
|
|
578
|
-
return {
|
|
579
|
-
commitMutation,
|
|
580
|
-
updateGsapProperty,
|
|
581
|
-
updateGsapMeta,
|
|
582
|
-
deleteGsapAnimation,
|
|
583
|
-
deleteAllForSelector,
|
|
584
|
-
addGsapAnimation,
|
|
585
|
-
addGsapProperty,
|
|
586
|
-
removeGsapProperty,
|
|
587
|
-
updateGsapFromProperty,
|
|
588
|
-
addGsapFromProperty,
|
|
589
|
-
removeGsapFromProperty,
|
|
590
|
-
addKeyframe,
|
|
591
|
-
addKeyframeBatch,
|
|
592
|
-
removeKeyframe,
|
|
593
|
-
convertToKeyframes,
|
|
594
|
-
removeAllKeyframes,
|
|
595
|
-
setArcPath,
|
|
596
|
-
updateArcSegment,
|
|
597
|
-
removeArcPath,
|
|
598
|
-
commitKeyframeAtTime,
|
|
599
|
-
};
|
|
82
|
+
const commitMutationSafely = useSafeGsapCommitMutation(commitMutation, trackGsapSaveFailure, showToast);
|
|
83
|
+
const propertyOps = useGsapPropertyDebounce(commitMutationSafely);
|
|
84
|
+
const animationOps = useGsapAnimationOps({ projectIdRef, activeCompPath, commitMutation, commitMutationSafely, showToast });
|
|
85
|
+
const keyframeOps = useGsapKeyframeOps({ activeCompPath, commitMutation, commitMutationSafely, trackGsapSaveFailure });
|
|
86
|
+
const arcPathOps = useGsapArcPathOps(commitMutationSafely);
|
|
87
|
+
return { commitMutation, ...propertyOps, ...animationOps, ...keyframeOps, ...arcPathOps };
|
|
600
88
|
}
|
|
@@ -110,9 +110,14 @@ export function useGsapSelectionHandlers({
|
|
|
110
110
|
);
|
|
111
111
|
|
|
112
112
|
const handleGsapUpdateMeta = useCallback(
|
|
113
|
-
(
|
|
114
|
-
|
|
115
|
-
|
|
113
|
+
(
|
|
114
|
+
animId: string,
|
|
115
|
+
updates: { duration?: number; ease?: string; position?: number },
|
|
116
|
+
selectionOverride?: DomEditSelection | null,
|
|
117
|
+
) => {
|
|
118
|
+
const sel = selectionOverride ?? domEditSelection ?? lastSelectionRef.current;
|
|
119
|
+
if (!sel) return;
|
|
120
|
+
updateGsapMeta(sel, animId, updates);
|
|
116
121
|
},
|
|
117
122
|
[domEditSelection, updateGsapMeta],
|
|
118
123
|
);
|
|
@@ -191,9 +196,16 @@ export function useGsapSelectionHandlers({
|
|
|
191
196
|
);
|
|
192
197
|
|
|
193
198
|
const handleGsapAddKeyframe = useCallback(
|
|
194
|
-
(
|
|
195
|
-
|
|
196
|
-
|
|
199
|
+
(
|
|
200
|
+
animId: string,
|
|
201
|
+
percentage: number,
|
|
202
|
+
property: string,
|
|
203
|
+
value: number | string,
|
|
204
|
+
selectionOverride?: DomEditSelection | null,
|
|
205
|
+
) => {
|
|
206
|
+
const sel = selectionOverride ?? domEditSelection ?? lastSelectionRef.current;
|
|
207
|
+
if (!sel) return;
|
|
208
|
+
addKeyframe(sel, animId, percentage, property, value);
|
|
197
209
|
},
|
|
198
210
|
[domEditSelection, addKeyframe],
|
|
199
211
|
);
|
|
@@ -208,9 +220,10 @@ export function useGsapSelectionHandlers({
|
|
|
208
220
|
[domEditSelection, addKeyframeBatch, trackGsapHandlerFailure],
|
|
209
221
|
);
|
|
210
222
|
const handleGsapRemoveKeyframe = useCallback(
|
|
211
|
-
(animId: string, percentage: number) => {
|
|
212
|
-
|
|
213
|
-
|
|
223
|
+
(animId: string, percentage: number, selectionOverride?: DomEditSelection | null) => {
|
|
224
|
+
const sel = selectionOverride ?? domEditSelection ?? lastSelectionRef.current;
|
|
225
|
+
if (!sel) return;
|
|
226
|
+
removeKeyframe(sel, animId, percentage);
|
|
214
227
|
},
|
|
215
228
|
[domEditSelection, removeKeyframe],
|
|
216
229
|
);
|