@hyperframes/studio 0.6.0 → 0.6.2
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-CzwFysqv.js +418 -0
- package/dist/assets/index-hYc4aP7M.js +117 -0
- package/dist/index.html +1 -1
- package/package.json +4 -4
- package/src/App.tsx +2 -13
- package/src/captions/components/CaptionOverlay.tsx +13 -246
- package/src/captions/components/CaptionOverlayUtils.ts +221 -0
- package/src/components/StudioPreviewArea.tsx +6 -2
- package/src/components/editor/DomEditOverlay.tsx +88 -1007
- package/src/components/editor/EaseCurveEditor.tsx +221 -0
- package/src/components/editor/FileTree.tsx +13 -621
- package/src/components/editor/FileTreeIcons.tsx +128 -0
- package/src/components/editor/FileTreeNodes.tsx +496 -0
- package/src/components/editor/MotionPanel.tsx +16 -390
- package/src/components/editor/MotionPanelFields.tsx +185 -0
- package/src/components/editor/domEditOverlayGeometry.ts +211 -0
- package/src/components/editor/domEditOverlayGestures.ts +138 -0
- package/src/components/editor/domEditOverlayStartGesture.ts +155 -0
- package/src/components/editor/domEditing.ts +44 -1150
- package/src/components/editor/domEditingAgentPrompt.ts +97 -0
- package/src/components/editor/domEditingDom.ts +266 -0
- package/src/components/editor/domEditingElement.ts +329 -0
- package/src/components/editor/domEditingLayers.ts +460 -0
- package/src/components/editor/domEditingTypes.ts +125 -0
- package/src/components/editor/manualEdits.ts +84 -1081
- package/src/components/editor/manualEditsDom.ts +436 -0
- package/src/components/editor/manualEditsParsing.ts +280 -0
- package/src/components/editor/manualEditsSnapshot.ts +333 -0
- package/src/components/editor/manualEditsTypes.ts +141 -0
- package/src/components/editor/studioMotion.ts +47 -434
- package/src/components/editor/studioMotionOps.ts +299 -0
- package/src/components/editor/studioMotionTypes.ts +168 -0
- package/src/components/editor/useDomEditOverlayGestures.ts +393 -0
- package/src/components/editor/useDomEditOverlayRects.ts +207 -0
- package/src/components/nle/NLELayout.tsx +60 -144
- package/src/components/nle/useCompositionStack.ts +126 -0
- package/src/hooks/useToast.ts +20 -0
- package/src/player/components/Timeline.tsx +189 -1418
- package/src/player/components/TimelineCanvas.tsx +434 -0
- package/src/player/components/TimelineEmptyState.tsx +102 -0
- package/src/player/components/TimelineRuler.tsx +90 -0
- package/src/player/components/timelineIcons.tsx +49 -0
- package/src/player/components/timelineLayout.ts +215 -0
- package/src/player/components/timelineUtils.ts +211 -0
- package/src/player/components/useTimelineClipDrag.ts +388 -0
- package/src/player/components/useTimelinePlayhead.ts +200 -0
- package/src/player/components/useTimelineRangeSelection.ts +135 -0
- package/src/player/hooks/usePlaybackKeyboard.ts +171 -0
- package/src/player/hooks/useTimelinePlayer.ts +69 -1372
- package/src/player/hooks/useTimelineSyncCallbacks.ts +288 -0
- package/src/player/lib/playbackAdapter.ts +145 -0
- package/src/player/lib/playbackShortcuts.ts +68 -0
- package/src/player/lib/playbackTypes.ts +60 -0
- package/src/player/lib/timelineDOM.ts +373 -0
- package/src/player/lib/timelineElementHelpers.ts +303 -0
- package/src/player/lib/timelineIframeHelpers.ts +269 -0
- package/dist/assets/hyperframes-player-DOFETgjy.js +0 -418
- package/dist/assets/index-DUqUmaoH.js +0 -117
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { useState, useRef, useEffect, type PointerEvent } from "react";
|
|
2
|
+
import { RotateCcw } from "../../icons/SystemIcons";
|
|
3
|
+
import {
|
|
4
|
+
clampStudioCustomEasePoints,
|
|
5
|
+
controlPointsForGsapEase,
|
|
6
|
+
serializeStudioCustomEaseData,
|
|
7
|
+
type StudioCustomEaseControlPoints,
|
|
8
|
+
} from "./studioMotion";
|
|
9
|
+
import { LABEL } from "./MotionPanelFields";
|
|
10
|
+
|
|
11
|
+
function formatNumericValue(value: number): string {
|
|
12
|
+
const rounded = Math.round(value * 100) / 100;
|
|
13
|
+
return Number.isInteger(rounded)
|
|
14
|
+
? `${rounded}`
|
|
15
|
+
: rounded.toFixed(2).replace(/0+$/, "").replace(/\.$/, "");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function cubicBezierPoint(t: number, p1: StudioCustomEaseControlPoints): { x: number; y: number } {
|
|
19
|
+
const inv = 1 - t;
|
|
20
|
+
const inv2 = inv * inv;
|
|
21
|
+
const t2 = t * t;
|
|
22
|
+
return {
|
|
23
|
+
x: 3 * inv2 * t * p1.x1 + 3 * inv * t2 * p1.x2 + t2 * t,
|
|
24
|
+
y: 3 * inv2 * t * p1.y1 + 3 * inv * t2 * p1.y2 + t2 * t,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function buildCurvePath(
|
|
29
|
+
points: StudioCustomEaseControlPoints,
|
|
30
|
+
map: (point: { x: number; y: number }) => { x: number; y: number },
|
|
31
|
+
): string {
|
|
32
|
+
const commands: string[] = [];
|
|
33
|
+
for (let index = 0; index <= 48; index += 1) {
|
|
34
|
+
const point = map(cubicBezierPoint(index / 48, points));
|
|
35
|
+
commands.push(`${index === 0 ? "M" : "L"}${point.x.toFixed(2)},${point.y.toFixed(2)}`);
|
|
36
|
+
}
|
|
37
|
+
return commands.join(" ");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function EaseCurveEditor({
|
|
41
|
+
points,
|
|
42
|
+
onCommit,
|
|
43
|
+
}: {
|
|
44
|
+
points: StudioCustomEaseControlPoints;
|
|
45
|
+
onCommit: (points: StudioCustomEaseControlPoints) => void;
|
|
46
|
+
}) {
|
|
47
|
+
const svgRef = useRef<SVGSVGElement | null>(null);
|
|
48
|
+
const [draft, setDraft] = useState(points);
|
|
49
|
+
const draggingRef = useRef<"p1" | "p2" | null>(null);
|
|
50
|
+
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
setDraft(points);
|
|
53
|
+
}, [points]);
|
|
54
|
+
|
|
55
|
+
const width = 324;
|
|
56
|
+
const height = 214;
|
|
57
|
+
const plot = { left: 46, top: 24, width: 242, height: 146 };
|
|
58
|
+
const yMin = -0.4;
|
|
59
|
+
const yMax = 1.4;
|
|
60
|
+
|
|
61
|
+
const mapPoint = (point: { x: number; y: number }) => ({
|
|
62
|
+
x: plot.left + point.x * plot.width,
|
|
63
|
+
y: plot.top + ((yMax - point.y) / (yMax - yMin)) * plot.height,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const unmapPointer = (event: PointerEvent<SVGSVGElement>) => {
|
|
67
|
+
const rect = svgRef.current?.getBoundingClientRect();
|
|
68
|
+
if (!rect) return null;
|
|
69
|
+
const x = ((event.clientX - rect.left) / rect.width) * width;
|
|
70
|
+
const y = ((event.clientY - rect.top) / rect.height) * height;
|
|
71
|
+
return clampStudioCustomEasePoints({
|
|
72
|
+
x1: draggingRef.current === "p1" ? (x - plot.left) / plot.width : draft.x1,
|
|
73
|
+
y1:
|
|
74
|
+
draggingRef.current === "p1"
|
|
75
|
+
? yMax - ((y - plot.top) / plot.height) * (yMax - yMin)
|
|
76
|
+
: draft.y1,
|
|
77
|
+
x2: draggingRef.current === "p2" ? (x - plot.left) / plot.width : draft.x2,
|
|
78
|
+
y2:
|
|
79
|
+
draggingRef.current === "p2"
|
|
80
|
+
? yMax - ((y - plot.top) / plot.height) * (yMax - yMin)
|
|
81
|
+
: draft.y2,
|
|
82
|
+
});
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const start = mapPoint({ x: 0, y: 0 });
|
|
86
|
+
const end = mapPoint({ x: 1, y: 1 });
|
|
87
|
+
const p1 = mapPoint({ x: draft.x1, y: draft.y1 });
|
|
88
|
+
const p2 = mapPoint({ x: draft.x2, y: draft.y2 });
|
|
89
|
+
const curvePath = buildCurvePath(draft, mapPoint);
|
|
90
|
+
|
|
91
|
+
const handlePointerMove = (event: PointerEvent<SVGSVGElement>) => {
|
|
92
|
+
if (!draggingRef.current) return;
|
|
93
|
+
event.preventDefault();
|
|
94
|
+
const next = unmapPointer(event);
|
|
95
|
+
if (next) setDraft(next);
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const endDrag = () => {
|
|
99
|
+
if (!draggingRef.current) return;
|
|
100
|
+
draggingRef.current = null;
|
|
101
|
+
onCommit(draft);
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const startDrag = (handle: "p1" | "p2", event: PointerEvent<SVGCircleElement>) => {
|
|
105
|
+
event.preventDefault();
|
|
106
|
+
event.stopPropagation();
|
|
107
|
+
draggingRef.current = handle;
|
|
108
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<div className="overflow-hidden rounded-2xl border border-neutral-800 bg-black/40">
|
|
113
|
+
<div className="flex items-center justify-between gap-3 border-b border-neutral-800 px-3 py-2">
|
|
114
|
+
<div>
|
|
115
|
+
<div className={LABEL}>CustomEase</div>
|
|
116
|
+
<div className="mt-1 font-mono text-[10px] text-neutral-500">
|
|
117
|
+
{serializeStudioCustomEaseData(draft)}
|
|
118
|
+
</div>
|
|
119
|
+
</div>
|
|
120
|
+
<button
|
|
121
|
+
type="button"
|
|
122
|
+
onClick={() => {
|
|
123
|
+
const reset = controlPointsForGsapEase("power3.out");
|
|
124
|
+
setDraft(reset);
|
|
125
|
+
onCommit(reset);
|
|
126
|
+
}}
|
|
127
|
+
className="inline-flex h-8 items-center justify-center gap-2 rounded-xl border border-neutral-800 bg-neutral-950 px-3 text-[10px] font-semibold uppercase tracking-[0.14em] text-neutral-400 transition-colors hover:border-neutral-700 hover:text-neutral-100"
|
|
128
|
+
>
|
|
129
|
+
<RotateCcw size={13} />
|
|
130
|
+
Reset
|
|
131
|
+
</button>
|
|
132
|
+
</div>
|
|
133
|
+
<svg
|
|
134
|
+
ref={svgRef}
|
|
135
|
+
viewBox={`0 0 ${width} ${height}`}
|
|
136
|
+
className="block w-full select-none touch-none"
|
|
137
|
+
onPointerMove={handlePointerMove}
|
|
138
|
+
onPointerUp={endDrag}
|
|
139
|
+
onPointerCancel={endDrag}
|
|
140
|
+
>
|
|
141
|
+
<rect x="0" y="0" width={width} height={height} fill="transparent" />
|
|
142
|
+
{[0, 0.5, 1].map((value) => {
|
|
143
|
+
const mapped = mapPoint({ x: 0, y: value });
|
|
144
|
+
return (
|
|
145
|
+
<g key={value}>
|
|
146
|
+
<line
|
|
147
|
+
x1={plot.left}
|
|
148
|
+
x2={plot.left + plot.width}
|
|
149
|
+
y1={mapped.y}
|
|
150
|
+
y2={mapped.y}
|
|
151
|
+
stroke="rgba(255,255,255,0.12)"
|
|
152
|
+
strokeDasharray="5 8"
|
|
153
|
+
/>
|
|
154
|
+
<text
|
|
155
|
+
x={plot.left - 12}
|
|
156
|
+
y={mapped.y + 4}
|
|
157
|
+
textAnchor="end"
|
|
158
|
+
className="fill-neutral-500 text-[10px] font-semibold"
|
|
159
|
+
>
|
|
160
|
+
{value}
|
|
161
|
+
</text>
|
|
162
|
+
</g>
|
|
163
|
+
);
|
|
164
|
+
})}
|
|
165
|
+
<line
|
|
166
|
+
x1={plot.left}
|
|
167
|
+
x2={plot.left + plot.width}
|
|
168
|
+
y1={plot.top + plot.height}
|
|
169
|
+
y2={plot.top + plot.height}
|
|
170
|
+
stroke="rgba(255,255,255,0.18)"
|
|
171
|
+
/>
|
|
172
|
+
<line
|
|
173
|
+
x1={plot.left}
|
|
174
|
+
x2={plot.left}
|
|
175
|
+
y1={plot.top}
|
|
176
|
+
y2={plot.top + plot.height}
|
|
177
|
+
stroke="rgba(255,255,255,0.18)"
|
|
178
|
+
/>
|
|
179
|
+
<line x1={start.x} y1={start.y} x2={p1.x} y2={p1.y} stroke="rgba(255,221,87,0.34)" />
|
|
180
|
+
<line x1={end.x} y1={end.y} x2={p2.x} y2={p2.y} stroke="rgba(255,221,87,0.34)" />
|
|
181
|
+
<path d={curvePath} fill="none" stroke="#ffdd57" strokeWidth="4" strokeLinecap="round" />
|
|
182
|
+
<circle cx={start.x} cy={start.y} r="5" fill="#ffdd57" />
|
|
183
|
+
<circle cx={end.x} cy={end.y} r="5" fill="#ffdd57" />
|
|
184
|
+
<circle
|
|
185
|
+
cx={p1.x}
|
|
186
|
+
cy={p1.y}
|
|
187
|
+
r="9"
|
|
188
|
+
fill="#141414"
|
|
189
|
+
stroke="#ffdd57"
|
|
190
|
+
strokeWidth="4"
|
|
191
|
+
className="cursor-grab active:cursor-grabbing"
|
|
192
|
+
onPointerDown={(event) => startDrag("p1", event)}
|
|
193
|
+
/>
|
|
194
|
+
<circle
|
|
195
|
+
cx={p2.x}
|
|
196
|
+
cy={p2.y}
|
|
197
|
+
r="9"
|
|
198
|
+
fill="#141414"
|
|
199
|
+
stroke="#ffdd57"
|
|
200
|
+
strokeWidth="4"
|
|
201
|
+
className="cursor-grab active:cursor-grabbing"
|
|
202
|
+
onPointerDown={(event) => startDrag("p2", event)}
|
|
203
|
+
/>
|
|
204
|
+
<text x={p1.x + 12} y={p1.y - 10} className="fill-neutral-400 text-[10px] font-semibold">
|
|
205
|
+
P1
|
|
206
|
+
</text>
|
|
207
|
+
<text x={p2.x + 12} y={p2.y - 10} className="fill-neutral-400 text-[10px] font-semibold">
|
|
208
|
+
P2
|
|
209
|
+
</text>
|
|
210
|
+
</svg>
|
|
211
|
+
<div className="grid grid-cols-2 gap-2 border-t border-neutral-800 p-3">
|
|
212
|
+
<div className="rounded-xl border border-neutral-800 bg-neutral-950 px-3 py-2 font-mono text-[10px] text-neutral-400">
|
|
213
|
+
P1 {formatNumericValue(draft.x1)}, {formatNumericValue(draft.y1)}
|
|
214
|
+
</div>
|
|
215
|
+
<div className="rounded-xl border border-neutral-800 bg-neutral-950 px-3 py-2 font-mono text-[10px] text-neutral-400">
|
|
216
|
+
P2 {formatNumericValue(draft.x2)}, {formatNumericValue(draft.y2)}
|
|
217
|
+
</div>
|
|
218
|
+
</div>
|
|
219
|
+
</div>
|
|
220
|
+
);
|
|
221
|
+
}
|