@hyperframes/studio 0.6.88 → 0.6.90
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-BKuDHMYl.js +146 -0
- package/dist/assets/index-D2NkPomd.css +1 -0
- package/dist/index.html +2 -2
- package/package.json +4 -4
- package/src/App.tsx +33 -193
- package/src/components/StudioLeftSidebar.tsx +6 -0
- package/src/components/StudioRightPanel.tsx +8 -0
- package/src/components/TimelineToolbar.tsx +54 -31
- package/src/components/editor/AnimationCard.tsx +15 -3
- package/src/components/editor/DomEditOverlay.test.ts +34 -1
- package/src/components/editor/FileTree.tsx +5 -1
- package/src/components/editor/FileTreeNodes.tsx +17 -3
- package/src/components/editor/LayersPanel.tsx +19 -4
- package/src/components/editor/PropertyPanel.tsx +82 -170
- package/src/components/editor/domEditOverlayStartGesture.ts +1 -0
- package/src/components/editor/gsapAnimatesProperty.ts +52 -0
- package/src/components/editor/manualEditsDom.ts +11 -57
- package/src/components/editor/manualOffsetDrag.test.ts +18 -1
- package/src/components/editor/manualOffsetDrag.ts +16 -10
- package/src/components/editor/propertyPanel3dTransform.tsx +133 -0
- package/src/components/editor/propertyPanelHelpers.ts +76 -0
- package/src/components/editor/propertyPanelStyleSections.tsx +1 -9
- package/src/components/editor/useDomEditOverlayGestures.ts +3 -0
- package/src/components/editor/useLayerDrag.ts +6 -3
- package/src/components/renders/RenderQueueItem.tsx +47 -46
- package/src/components/sidebar/CompositionsTab.tsx +15 -2
- package/src/components/sidebar/LeftSidebar.tsx +11 -0
- package/src/hooks/gsapDragCommit.ts +294 -0
- package/src/hooks/gsapKeyframeCacheHelpers.ts +88 -0
- package/src/hooks/gsapRuntimeBridge.ts +49 -402
- package/src/hooks/gsapRuntimeReaders.ts +201 -0
- package/src/hooks/timelineEditingHelpers.ts +148 -0
- package/src/hooks/useAnimatedPropertyCommit.ts +54 -12
- package/src/hooks/useBlockHandlers.ts +150 -0
- package/src/hooks/useClipboard.ts +1 -10
- package/src/hooks/useDomEditPreviewSync.ts +126 -0
- package/src/hooks/useDomEditSession.ts +11 -79
- package/src/hooks/useGestureCommit.ts +166 -0
- package/src/hooks/useGestureRecording.ts +271 -169
- package/src/hooks/useGsapScriptCommits.ts +7 -80
- package/src/hooks/useLintModal.ts +97 -25
- package/src/hooks/useTimelineEditing.ts +10 -132
- package/src/player/components/TimelineCanvas.tsx +24 -7
- package/src/player/components/useTimelinePlayhead.ts +2 -1
- package/src/player/store/playerStore.ts +12 -0
- package/src/utils/gsapSoftReload.ts +18 -1
- package/src/utils/studioUrlState.test.ts +9 -0
- package/dist/assets/index-B9_ctmee.js +0 -143
- package/dist/assets/index-CGlIm_-E.css +0 -1
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Manages gesture recording state and commit logic for the Studio.
|
|
3
|
+
* Extracted from App.tsx to keep file sizes under the 600-line limit.
|
|
4
|
+
*/
|
|
5
|
+
import { useState, useCallback, useRef, useEffect } from "react";
|
|
6
|
+
import { useGestureRecording } from "./useGestureRecording";
|
|
7
|
+
import { simplifyGestureSamples } from "../utils/rdpSimplify";
|
|
8
|
+
import { usePlayerStore } from "../player";
|
|
9
|
+
import type { DomEditSelection } from "../components/editor/domEditing";
|
|
10
|
+
|
|
11
|
+
// Minimal subset of the session used by gesture commit
|
|
12
|
+
interface GestureSessionRef {
|
|
13
|
+
domEditSelection: DomEditSelection | null;
|
|
14
|
+
commitMutation?: (
|
|
15
|
+
mutation: Record<string, unknown>,
|
|
16
|
+
options: { label: string; softReload?: boolean },
|
|
17
|
+
) => Promise<void>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface UseGestureCommitParams {
|
|
21
|
+
domEditSessionRef: React.MutableRefObject<GestureSessionRef>;
|
|
22
|
+
previewIframeRef: React.RefObject<HTMLIFrameElement | null>;
|
|
23
|
+
showToast: (message: string, tone?: "error" | "info") => void;
|
|
24
|
+
isGestureRecordingRef: React.MutableRefObject<boolean>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface UseGestureCommitResult {
|
|
28
|
+
gestureState: "idle" | "recording";
|
|
29
|
+
gestureRecording: ReturnType<typeof useGestureRecording>;
|
|
30
|
+
handleToggleRecording: () => void;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// fallow-ignore-next-line complexity
|
|
34
|
+
export function useGestureCommit({
|
|
35
|
+
domEditSessionRef,
|
|
36
|
+
previewIframeRef,
|
|
37
|
+
showToast,
|
|
38
|
+
isGestureRecordingRef,
|
|
39
|
+
}: UseGestureCommitParams): UseGestureCommitResult {
|
|
40
|
+
const gestureRecording = useGestureRecording();
|
|
41
|
+
const [gestureState, setGestureState] = useState<"idle" | "recording">("idle");
|
|
42
|
+
const gestureStateRef = useRef<"idle" | "recording">("idle");
|
|
43
|
+
const recordingAutoStopRef = useRef<ReturnType<typeof setInterval>>(undefined);
|
|
44
|
+
const recordingStartTimeRef = useRef(0);
|
|
45
|
+
const commitInFlightRef = useRef(false);
|
|
46
|
+
|
|
47
|
+
// Unmount: clear auto-stop interval
|
|
48
|
+
useEffect(() => () => clearInterval(recordingAutoStopRef.current), []);
|
|
49
|
+
|
|
50
|
+
// fallow-ignore-next-line complexity
|
|
51
|
+
const stopAndCommitRecording = useCallback(async () => {
|
|
52
|
+
clearInterval(recordingAutoStopRef.current);
|
|
53
|
+
if (commitInFlightRef.current) return;
|
|
54
|
+
commitInFlightRef.current = true;
|
|
55
|
+
gestureStateRef.current = "idle";
|
|
56
|
+
isGestureRecordingRef.current = false;
|
|
57
|
+
const frozenSamples = gestureRecording.stopRecording();
|
|
58
|
+
const store = usePlayerStore.getState();
|
|
59
|
+
store.setIsPlaying(false);
|
|
60
|
+
try {
|
|
61
|
+
const liveSession = domEditSessionRef.current;
|
|
62
|
+
const sel = liveSession.domEditSelection;
|
|
63
|
+
if (!sel) {
|
|
64
|
+
if (frozenSamples.length > 2) {
|
|
65
|
+
showToast("Selection lost during recording", "error");
|
|
66
|
+
}
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const duration = frozenSamples.length > 0 ? frozenSamples[frozenSamples.length - 1]!.time : 0;
|
|
70
|
+
|
|
71
|
+
if (frozenSamples.length <= 2) {
|
|
72
|
+
showToast("No gesture detected — move the pointer while recording", "error");
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (duration <= 0) {
|
|
76
|
+
showToast("Recording too short — try again", "error");
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const simplified = simplifyGestureSamples(frozenSamples, duration, 5);
|
|
81
|
+
const sortedPcts = Array.from(simplified.keys()).sort((a, b) => a - b);
|
|
82
|
+
|
|
83
|
+
// Ensure a 0% keyframe exists with the element's start-of-recording position
|
|
84
|
+
if (!simplified.has(0) && frozenSamples.length > 0) {
|
|
85
|
+
simplified.set(0, frozenSamples[0]!.properties);
|
|
86
|
+
if (!sortedPcts.includes(0)) sortedPcts.unshift(0);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const selector = sel.id ? `#${sel.id}` : sel.selector;
|
|
90
|
+
if (!selector) {
|
|
91
|
+
showToast("Cannot save — element has no selector", "error");
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (liveSession.commitMutation) {
|
|
95
|
+
const recStart = recordingStartTimeRef.current;
|
|
96
|
+
const keyframes = sortedPcts.map((pct) => ({
|
|
97
|
+
percentage: pct,
|
|
98
|
+
properties: simplified.get(pct) as Record<string, number | string>,
|
|
99
|
+
}));
|
|
100
|
+
|
|
101
|
+
await liveSession.commitMutation(
|
|
102
|
+
{
|
|
103
|
+
type: "add-with-keyframes",
|
|
104
|
+
targetSelector: selector,
|
|
105
|
+
position: Math.round(recStart * 1000) / 1000,
|
|
106
|
+
duration: Math.round(duration * 1000) / 1000,
|
|
107
|
+
keyframes,
|
|
108
|
+
},
|
|
109
|
+
{ label: "Gesture recording", softReload: true },
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
showToast(`Recorded ${sortedPcts.length} keyframes`, "info");
|
|
113
|
+
} finally {
|
|
114
|
+
store.requestSeek(recordingStartTimeRef.current);
|
|
115
|
+
gestureRecording.clearSamples();
|
|
116
|
+
setGestureState("idle");
|
|
117
|
+
commitInFlightRef.current = false;
|
|
118
|
+
}
|
|
119
|
+
}, [gestureRecording, showToast, isGestureRecordingRef, domEditSessionRef]);
|
|
120
|
+
|
|
121
|
+
const handleToggleRecording = useCallback(() => {
|
|
122
|
+
if (gestureStateRef.current === "recording") {
|
|
123
|
+
void stopAndCommitRecording();
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const sel = domEditSessionRef.current.domEditSelection;
|
|
127
|
+
if (!sel) {
|
|
128
|
+
showToast("Select an element first", "error");
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const iframe = previewIframeRef.current;
|
|
132
|
+
if (!iframe) {
|
|
133
|
+
showToast("Preview not ready — try again", "error");
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const store = usePlayerStore.getState();
|
|
138
|
+
recordingStartTimeRef.current = store.currentTime;
|
|
139
|
+
const elStart = Number.parseFloat(sel.dataAttributes?.start ?? "0") || 0;
|
|
140
|
+
const elDur = Number.parseFloat(sel.dataAttributes?.duration ?? "0") || 0;
|
|
141
|
+
const elementEnd = elDur > 0 ? elStart + elDur : undefined;
|
|
142
|
+
gestureRecording.startRecording(sel.element, iframe, elementEnd);
|
|
143
|
+
gestureStateRef.current = "recording";
|
|
144
|
+
isGestureRecordingRef.current = true;
|
|
145
|
+
setGestureState("recording");
|
|
146
|
+
|
|
147
|
+
clearInterval(recordingAutoStopRef.current);
|
|
148
|
+
const autoStopAt = elementEnd ?? Infinity;
|
|
149
|
+
recordingAutoStopRef.current = setInterval(() => {
|
|
150
|
+
const { currentTime: t, duration: d } = usePlayerStore.getState();
|
|
151
|
+
const limit = Math.min(autoStopAt, d);
|
|
152
|
+
if (limit > 0 && t >= limit - 0.05) {
|
|
153
|
+
void stopAndCommitRecording();
|
|
154
|
+
}
|
|
155
|
+
}, 100);
|
|
156
|
+
}, [
|
|
157
|
+
gestureRecording,
|
|
158
|
+
showToast,
|
|
159
|
+
stopAndCommitRecording,
|
|
160
|
+
previewIframeRef,
|
|
161
|
+
domEditSessionRef,
|
|
162
|
+
isGestureRecordingRef,
|
|
163
|
+
]);
|
|
164
|
+
|
|
165
|
+
return { gestureState, gestureRecording, handleToggleRecording };
|
|
166
|
+
}
|