@hyperframes/studio 0.6.72 → 0.6.74
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-BcJO6Ej5.js +140 -0
- package/dist/assets/index-C2gBZ2km.css +1 -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/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/nle/NLELayout.tsx +18 -0
- package/src/contexts/DomEditContext.tsx +24 -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 +39 -4
- package/src/hooks/useDomEditSession.ts +177 -63
- 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-CveQve6o.js +0 -140
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// fallow-ignore-file code-duplication
|
|
1
2
|
/**
|
|
2
3
|
* Gesture handling for DomEditOverlay.
|
|
3
4
|
* Owns: onPointerMove, onPointerUp, clearPointerState.
|
|
@@ -23,7 +24,12 @@ import {
|
|
|
23
24
|
restoreStudioPathOffset,
|
|
24
25
|
restoreStudioRotation,
|
|
25
26
|
} from "./manualEdits";
|
|
26
|
-
import {
|
|
27
|
+
import {
|
|
28
|
+
type GroupOverlayItem,
|
|
29
|
+
type OverlayRect,
|
|
30
|
+
resolveDomEditGroupOverlayRect,
|
|
31
|
+
toOverlayRect,
|
|
32
|
+
} from "./domEditOverlayGeometry";
|
|
27
33
|
import {
|
|
28
34
|
BLOCKED_MOVE_THRESHOLD_PX,
|
|
29
35
|
type BlockedMoveState,
|
|
@@ -39,6 +45,13 @@ import {
|
|
|
39
45
|
startGesture as _startGesture,
|
|
40
46
|
startGroupDrag as _startGroupDrag,
|
|
41
47
|
} from "./domEditOverlayStartGesture";
|
|
48
|
+
import {
|
|
49
|
+
resolveSnapAdjustment,
|
|
50
|
+
resolveResizeSnapAdjustment,
|
|
51
|
+
resolveEquidistanceGuides,
|
|
52
|
+
SNAP_THRESHOLD_PX,
|
|
53
|
+
} from "./snapEngine";
|
|
54
|
+
import type { SnapGuidesState } from "./SnapGuideOverlay";
|
|
42
55
|
|
|
43
56
|
// Refs are stable across renders; values are read via .current.
|
|
44
57
|
export type UseDomEditOverlayGesturesOptions = {
|
|
@@ -79,6 +92,7 @@ export type UseDomEditOverlayGesturesOptions = {
|
|
|
79
92
|
e: React.MouseEvent<HTMLDivElement>,
|
|
80
93
|
o?: { preferClipAncestor?: boolean },
|
|
81
94
|
) => void;
|
|
95
|
+
snapGuidesRef: RefObject<SnapGuidesState | null>;
|
|
82
96
|
};
|
|
83
97
|
|
|
84
98
|
export function createDomEditOverlayGestureHandlers(opts: UseDomEditOverlayGesturesOptions) {
|
|
@@ -111,6 +125,7 @@ export function createDomEditOverlayGestureHandlers(opts: UseDomEditOverlayGestu
|
|
|
111
125
|
options?: { selection?: DomEditSelection; rect?: OverlayRect | null },
|
|
112
126
|
) => _startGesture(kind, e, opts, options);
|
|
113
127
|
|
|
128
|
+
// fallow-ignore-next-line complexity
|
|
114
129
|
const onPointerMove = (e: React.PointerEvent<HTMLDivElement>) => {
|
|
115
130
|
const g = opts.gestureRef.current;
|
|
116
131
|
const groupG = opts.groupGestureRef.current;
|
|
@@ -133,8 +148,48 @@ export function createDomEditOverlayGestureHandlers(opts: UseDomEditOverlayGestu
|
|
|
133
148
|
}
|
|
134
149
|
|
|
135
150
|
if (groupG) {
|
|
136
|
-
|
|
137
|
-
|
|
151
|
+
let dx = e.clientX - groupG.startX;
|
|
152
|
+
let dy = e.clientY - groupG.startY;
|
|
153
|
+
|
|
154
|
+
const sc = groupG.snapContext;
|
|
155
|
+
if (sc?.snapEnabled && sc.targets.length > 0) {
|
|
156
|
+
const groupBounds = resolveDomEditGroupOverlayRect(
|
|
157
|
+
groupG.originItems.map((item) => item.rect),
|
|
158
|
+
);
|
|
159
|
+
if (groupBounds) {
|
|
160
|
+
const allTargets = sc.compositionTarget
|
|
161
|
+
? [...sc.targets, sc.compositionTarget]
|
|
162
|
+
: sc.targets;
|
|
163
|
+
const snap = resolveSnapAdjustment({
|
|
164
|
+
movingRect: groupBounds,
|
|
165
|
+
proposedDx: dx,
|
|
166
|
+
proposedDy: dy,
|
|
167
|
+
targets: allTargets,
|
|
168
|
+
gridEdges: sc.gridEdges ?? undefined,
|
|
169
|
+
threshold: SNAP_THRESHOLD_PX,
|
|
170
|
+
disabled: e.altKey,
|
|
171
|
+
});
|
|
172
|
+
dx = snap.dx;
|
|
173
|
+
dy = snap.dy;
|
|
174
|
+
const movedRect = {
|
|
175
|
+
left: groupBounds.left + dx,
|
|
176
|
+
top: groupBounds.top + dy,
|
|
177
|
+
width: groupBounds.width,
|
|
178
|
+
height: groupBounds.height,
|
|
179
|
+
};
|
|
180
|
+
const spacingGuides = e.altKey
|
|
181
|
+
? []
|
|
182
|
+
: resolveEquidistanceGuides({
|
|
183
|
+
movingRect: movedRect,
|
|
184
|
+
targets: allTargets,
|
|
185
|
+
threshold: SNAP_THRESHOLD_PX,
|
|
186
|
+
});
|
|
187
|
+
opts.snapGuidesRef.current = { guides: snap.guides, spacingGuides };
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
groupG.lastSnappedDx = dx;
|
|
191
|
+
groupG.lastSnappedDy = dy;
|
|
192
|
+
|
|
138
193
|
setDraftGroupOverlayItems(
|
|
139
194
|
groupG.originItems.map((item) => ({
|
|
140
195
|
...item,
|
|
@@ -146,8 +201,8 @@ export function createDomEditOverlayGestureHandlers(opts: UseDomEditOverlayGestu
|
|
|
146
201
|
}
|
|
147
202
|
|
|
148
203
|
if (!g || !sel) return;
|
|
149
|
-
|
|
150
|
-
|
|
204
|
+
let dx = e.clientX - g.startX;
|
|
205
|
+
let dy = e.clientY - g.startY;
|
|
151
206
|
|
|
152
207
|
if (g.kind === "rotate") {
|
|
153
208
|
applyStudioRotationDraft(
|
|
@@ -167,6 +222,46 @@ export function createDomEditOverlayGestureHandlers(opts: UseDomEditOverlayGestu
|
|
|
167
222
|
}
|
|
168
223
|
|
|
169
224
|
if (g.kind === "drag") {
|
|
225
|
+
const sc = g.snapContext;
|
|
226
|
+
if (sc?.snapEnabled && sc.targets.length > 0) {
|
|
227
|
+
const movingRect = {
|
|
228
|
+
left: g.originLeft,
|
|
229
|
+
top: g.originTop,
|
|
230
|
+
width: g.originWidth,
|
|
231
|
+
height: g.originHeight,
|
|
232
|
+
};
|
|
233
|
+
const allTargets = sc.compositionTarget
|
|
234
|
+
? [...sc.targets, sc.compositionTarget]
|
|
235
|
+
: sc.targets;
|
|
236
|
+
const snap = resolveSnapAdjustment({
|
|
237
|
+
movingRect,
|
|
238
|
+
proposedDx: dx,
|
|
239
|
+
proposedDy: dy,
|
|
240
|
+
targets: allTargets,
|
|
241
|
+
gridEdges: sc.gridEdges ?? undefined,
|
|
242
|
+
threshold: SNAP_THRESHOLD_PX,
|
|
243
|
+
disabled: e.altKey,
|
|
244
|
+
});
|
|
245
|
+
dx = snap.dx;
|
|
246
|
+
dy = snap.dy;
|
|
247
|
+
const movedRect = {
|
|
248
|
+
left: movingRect.left + dx,
|
|
249
|
+
top: movingRect.top + dy,
|
|
250
|
+
width: movingRect.width,
|
|
251
|
+
height: movingRect.height,
|
|
252
|
+
};
|
|
253
|
+
const spacingGuides = e.altKey
|
|
254
|
+
? []
|
|
255
|
+
: resolveEquidistanceGuides({
|
|
256
|
+
movingRect: movedRect,
|
|
257
|
+
targets: allTargets,
|
|
258
|
+
threshold: SNAP_THRESHOLD_PX,
|
|
259
|
+
});
|
|
260
|
+
opts.snapGuidesRef.current = { guides: snap.guides, spacingGuides };
|
|
261
|
+
}
|
|
262
|
+
g.lastSnappedDx = dx;
|
|
263
|
+
g.lastSnappedDy = dy;
|
|
264
|
+
|
|
170
265
|
const nextBoxLeft = g.originLeft + dx;
|
|
171
266
|
const nextBoxTop = g.originTop + dy;
|
|
172
267
|
setDraftOverlayRect({
|
|
@@ -184,6 +279,32 @@ export function createDomEditOverlayGestureHandlers(opts: UseDomEditOverlayGestu
|
|
|
184
279
|
if (g.pathOffsetMember) applyManualOffsetDragDraft(g.pathOffsetMember, dx, dy);
|
|
185
280
|
} else {
|
|
186
281
|
if (!box) return;
|
|
282
|
+
|
|
283
|
+
const sc = g.snapContext;
|
|
284
|
+
if (sc?.snapEnabled && sc.targets.length > 0) {
|
|
285
|
+
const movingRect = {
|
|
286
|
+
left: g.originLeft,
|
|
287
|
+
top: g.originTop,
|
|
288
|
+
width: g.originWidth,
|
|
289
|
+
height: g.originHeight,
|
|
290
|
+
};
|
|
291
|
+
const allTargets = sc.compositionTarget
|
|
292
|
+
? [...sc.targets, sc.compositionTarget]
|
|
293
|
+
: sc.targets;
|
|
294
|
+
const snap = resolveResizeSnapAdjustment({
|
|
295
|
+
movingRect,
|
|
296
|
+
proposedDx: dx,
|
|
297
|
+
proposedDy: dy,
|
|
298
|
+
targets: allTargets,
|
|
299
|
+
gridEdges: sc.gridEdges ?? undefined,
|
|
300
|
+
threshold: SNAP_THRESHOLD_PX,
|
|
301
|
+
disabled: e.altKey,
|
|
302
|
+
});
|
|
303
|
+
dx = snap.dx;
|
|
304
|
+
dy = snap.dy;
|
|
305
|
+
opts.snapGuidesRef.current = { guides: snap.guides, spacingGuides: [] };
|
|
306
|
+
}
|
|
307
|
+
|
|
187
308
|
const nextSize = resolveDomEditResizeGesture({
|
|
188
309
|
originWidth: g.originWidth,
|
|
189
310
|
originHeight: g.originHeight,
|
|
@@ -223,7 +344,9 @@ export function createDomEditOverlayGestureHandlers(opts: UseDomEditOverlayGestu
|
|
|
223
344
|
}
|
|
224
345
|
};
|
|
225
346
|
|
|
347
|
+
// fallow-ignore-next-line complexity
|
|
226
348
|
const onPointerUp = (e: React.PointerEvent<HTMLDivElement>) => {
|
|
349
|
+
opts.snapGuidesRef.current = null;
|
|
227
350
|
const g = opts.gestureRef.current;
|
|
228
351
|
const groupG = opts.groupGestureRef.current;
|
|
229
352
|
const sel = g?.selection ?? opts.selectionRef.current;
|
|
@@ -233,13 +356,15 @@ export function createDomEditOverlayGestureHandlers(opts: UseDomEditOverlayGestu
|
|
|
233
356
|
if (groupG) {
|
|
234
357
|
opts.groupGestureRef.current = null;
|
|
235
358
|
opts.rafPausedRef.current = false;
|
|
236
|
-
const
|
|
237
|
-
const
|
|
238
|
-
if (Math.hypot(
|
|
359
|
+
const rawDx = e.clientX - groupG.startX;
|
|
360
|
+
const rawDy = e.clientY - groupG.startY;
|
|
361
|
+
if (Math.hypot(rawDx, rawDy) < BLOCKED_MOVE_THRESHOLD_PX) {
|
|
239
362
|
restoreGroupPathOffsets(groupG);
|
|
240
363
|
opts.suppressNextBoxClickRef.current = true;
|
|
241
364
|
return;
|
|
242
365
|
}
|
|
366
|
+
const dx = groupG.lastSnappedDx ?? rawDx;
|
|
367
|
+
const dy = groupG.lastSnappedDy ?? rawDy;
|
|
243
368
|
setDraftGroupOverlayItems(
|
|
244
369
|
groupG.originItems.map((item) => ({
|
|
245
370
|
...item,
|
|
@@ -327,8 +452,8 @@ export function createDomEditOverlayGestureHandlers(opts: UseDomEditOverlayGestu
|
|
|
327
452
|
})
|
|
328
453
|
.finally(() => endStudioManualEditGesture(sel.element, g.manualEditDragToken));
|
|
329
454
|
} else if (g.kind === "drag") {
|
|
330
|
-
const dx = e.clientX - g.startX;
|
|
331
|
-
const dy = e.clientY - g.startY;
|
|
455
|
+
const dx = g.lastSnappedDx ?? e.clientX - g.startX;
|
|
456
|
+
const dy = g.lastSnappedDy ?? e.clientY - g.startY;
|
|
332
457
|
if (!g.pathOffsetMember) return;
|
|
333
458
|
const finalOffset = applyManualOffsetDragCommit(g.pathOffsetMember, dx, dy);
|
|
334
459
|
const nextBoxLeft = g.originLeft + dx;
|
|
@@ -372,7 +497,9 @@ export function createDomEditOverlayGestureHandlers(opts: UseDomEditOverlayGestu
|
|
|
372
497
|
}
|
|
373
498
|
};
|
|
374
499
|
|
|
500
|
+
// fallow-ignore-next-line complexity
|
|
375
501
|
const clearPointerState = (selectionRef: RefObject<DomEditSelection | null>) => {
|
|
502
|
+
opts.snapGuidesRef.current = null;
|
|
376
503
|
const groupG = opts.groupGestureRef.current;
|
|
377
504
|
if (groupG) restoreGroupPathOffsets(groupG);
|
|
378
505
|
const g = opts.gestureRef.current;
|
|
@@ -69,7 +69,13 @@ interface NLELayoutProps {
|
|
|
69
69
|
updates: Pick<TimelineElement, "start" | "duration" | "playbackStart">,
|
|
70
70
|
) => Promise<void> | void;
|
|
71
71
|
onBlockedEditAttempt?: (element: TimelineElement, intent: BlockedTimelineEditIntent) => void;
|
|
72
|
+
onSplitElement?: (element: TimelineElement, splitTime: number) => Promise<void> | void;
|
|
72
73
|
onSelectTimelineElement?: (element: TimelineElement | null) => void;
|
|
74
|
+
onDeleteKeyframe?: (elementId: string, percentage: number) => void;
|
|
75
|
+
onDeleteAllKeyframes?: (elementId: string) => void;
|
|
76
|
+
onChangeKeyframeEase?: (elementId: string, percentage: number, ease: string) => void;
|
|
77
|
+
onMoveKeyframe?: (element: TimelineElement, oldPct: number, newPct: number) => void;
|
|
78
|
+
onToggleKeyframeAtPlayhead?: (element: TimelineElement) => void;
|
|
73
79
|
/** Exposes the compIdToSrc map for parent components (e.g., useRenderClipContent) */
|
|
74
80
|
onCompIdToSrcChange?: (map: Map<string, string>) => void;
|
|
75
81
|
/** Whether the timeline panel is visible (default: true) */
|
|
@@ -117,7 +123,13 @@ export const NLELayout = memo(function NLELayout({
|
|
|
117
123
|
onMoveElement,
|
|
118
124
|
onResizeElement,
|
|
119
125
|
onBlockedEditAttempt,
|
|
126
|
+
onSplitElement,
|
|
120
127
|
onSelectTimelineElement,
|
|
128
|
+
onDeleteKeyframe,
|
|
129
|
+
onDeleteAllKeyframes,
|
|
130
|
+
onChangeKeyframeEase,
|
|
131
|
+
onMoveKeyframe,
|
|
132
|
+
onToggleKeyframeAtPlayhead,
|
|
121
133
|
onCompIdToSrcChange,
|
|
122
134
|
timelineVisible,
|
|
123
135
|
onToggleTimeline,
|
|
@@ -447,7 +459,13 @@ export const NLELayout = memo(function NLELayout({
|
|
|
447
459
|
onMoveElement={onMoveElement}
|
|
448
460
|
onResizeElement={onResizeElement}
|
|
449
461
|
onBlockedEditAttempt={onBlockedEditAttempt}
|
|
462
|
+
onSplitElement={onSplitElement}
|
|
450
463
|
onSelectElement={onSelectTimelineElement}
|
|
464
|
+
onDeleteKeyframe={onDeleteKeyframe}
|
|
465
|
+
onDeleteAllKeyframes={onDeleteAllKeyframes}
|
|
466
|
+
onChangeKeyframeEase={onChangeKeyframeEase}
|
|
467
|
+
onMoveKeyframe={onMoveKeyframe}
|
|
468
|
+
onToggleKeyframeAtPlayhead={onToggleKeyframeAtPlayhead}
|
|
451
469
|
/>
|
|
452
470
|
</div>
|
|
453
471
|
{timelineFooter && <div className="flex-shrink-0">{timelineFooter}</div>}
|
|
@@ -65,6 +65,14 @@ export function DomEditProvider({
|
|
|
65
65
|
handleGsapUpdateFromProperty,
|
|
66
66
|
handleGsapAddFromProperty,
|
|
67
67
|
handleGsapRemoveFromProperty,
|
|
68
|
+
handleGsapAddKeyframe,
|
|
69
|
+
handleGsapRemoveKeyframe,
|
|
70
|
+
handleGsapConvertToKeyframes,
|
|
71
|
+
handleGsapRemoveAllKeyframes,
|
|
72
|
+
handleResetSelectedElementKeyframes,
|
|
73
|
+
commitAnimatedProperty,
|
|
74
|
+
invalidateGsapCache,
|
|
75
|
+
previewIframeRef,
|
|
68
76
|
},
|
|
69
77
|
children,
|
|
70
78
|
}: {
|
|
@@ -125,6 +133,14 @@ export function DomEditProvider({
|
|
|
125
133
|
handleGsapUpdateFromProperty,
|
|
126
134
|
handleGsapAddFromProperty,
|
|
127
135
|
handleGsapRemoveFromProperty,
|
|
136
|
+
handleGsapAddKeyframe,
|
|
137
|
+
handleGsapRemoveKeyframe,
|
|
138
|
+
handleGsapConvertToKeyframes,
|
|
139
|
+
handleGsapRemoveAllKeyframes,
|
|
140
|
+
handleResetSelectedElementKeyframes,
|
|
141
|
+
commitAnimatedProperty,
|
|
142
|
+
invalidateGsapCache,
|
|
143
|
+
previewIframeRef,
|
|
128
144
|
}),
|
|
129
145
|
[
|
|
130
146
|
domEditSelection,
|
|
@@ -179,6 +195,14 @@ export function DomEditProvider({
|
|
|
179
195
|
handleGsapUpdateFromProperty,
|
|
180
196
|
handleGsapAddFromProperty,
|
|
181
197
|
handleGsapRemoveFromProperty,
|
|
198
|
+
handleGsapAddKeyframe,
|
|
199
|
+
handleGsapRemoveKeyframe,
|
|
200
|
+
handleGsapConvertToKeyframes,
|
|
201
|
+
handleGsapRemoveAllKeyframes,
|
|
202
|
+
handleResetSelectedElementKeyframes,
|
|
203
|
+
commitAnimatedProperty,
|
|
204
|
+
invalidateGsapCache,
|
|
205
|
+
previewIframeRef,
|
|
182
206
|
],
|
|
183
207
|
);
|
|
184
208
|
return <DomEditContext value={stable}>{children}</DomEditContext>;
|