@hyperframes/studio 0.6.42 → 0.6.44
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-DmiO2Ufp.js → index-CaRE7VOD.js} +32 -32
- package/dist/index.html +1 -1
- package/package.json +4 -4
- package/src/components/sidebar/CompositionsTab.tsx +31 -18
- package/src/hooks/useAppHotkeys.ts +70 -29
- package/src/main.tsx +21 -3
- package/src/player/hooks/usePlaybackKeyboard.ts +21 -6
package/dist/index.html
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
|
|
6
6
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
7
7
|
<title>HyperFrames Studio</title>
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-CaRE7VOD.js"></script>
|
|
9
9
|
<link rel="stylesheet" crossorigin href="/assets/index-SKRp8mGz.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperframes/studio",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.44",
|
|
4
4
|
"description": "",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
"@codemirror/view": "6.40.0",
|
|
32
32
|
"@phosphor-icons/react": "^2.1.10",
|
|
33
33
|
"mediabunny": "^1.45.3",
|
|
34
|
-
"@hyperframes/core": "0.6.
|
|
35
|
-
"@hyperframes/player": "0.6.
|
|
34
|
+
"@hyperframes/core": "0.6.44",
|
|
35
|
+
"@hyperframes/player": "0.6.44"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@types/react": "19",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"vite": "^6.4.2",
|
|
47
47
|
"vitest": "^3.2.4",
|
|
48
48
|
"zustand": "^5.0.0",
|
|
49
|
-
"@hyperframes/producer": "0.6.
|
|
49
|
+
"@hyperframes/producer": "0.6.44"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
|
52
52
|
"react": "19",
|
|
@@ -62,33 +62,46 @@ function parsePositiveNumber(value: string | null): number | null {
|
|
|
62
62
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
// fallow-ignore-next-line complexity
|
|
65
66
|
function resolveIframeDuration(iframe: HTMLIFrameElement | null): number | null {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
try {
|
|
68
|
+
const win = iframe?.contentWindow as PreviewWindow | null;
|
|
69
|
+
const playerDuration = win?.__player?.getDuration?.();
|
|
70
|
+
if (Number.isFinite(playerDuration) && playerDuration != null && playerDuration > 0) {
|
|
71
|
+
return playerDuration;
|
|
72
|
+
}
|
|
73
|
+
} catch {
|
|
74
|
+
/* cross-origin iframe */
|
|
70
75
|
}
|
|
71
76
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
77
|
+
try {
|
|
78
|
+
const doc = iframe?.contentDocument;
|
|
79
|
+
const root = doc?.querySelector("[data-composition-id]") ?? doc?.documentElement ?? null;
|
|
80
|
+
return (
|
|
81
|
+
parsePositiveNumber(root?.getAttribute("data-composition-duration") ?? null) ??
|
|
82
|
+
parsePositiveNumber(root?.getAttribute("data-duration") ?? null)
|
|
83
|
+
);
|
|
84
|
+
} catch {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
78
87
|
}
|
|
79
88
|
|
|
80
89
|
function syncIframePlayback(iframe: HTMLIFrameElement | null, shouldPlay: boolean): boolean {
|
|
81
|
-
|
|
82
|
-
|
|
90
|
+
try {
|
|
91
|
+
const player = (iframe?.contentWindow as PreviewWindow | null)?.__player;
|
|
92
|
+
if (!player) return false;
|
|
93
|
+
|
|
94
|
+
if (shouldPlay) {
|
|
95
|
+
player.play?.();
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
83
98
|
|
|
84
|
-
|
|
85
|
-
player.
|
|
99
|
+
player.pause?.();
|
|
100
|
+
player.seek?.(resolveThumbnailSeekTime(resolveIframeDuration(iframe)));
|
|
86
101
|
return true;
|
|
102
|
+
} catch {
|
|
103
|
+
return false;
|
|
87
104
|
}
|
|
88
|
-
|
|
89
|
-
player.pause?.();
|
|
90
|
-
player.seek?.(resolveThumbnailSeekTime(resolveIframeDuration(iframe)));
|
|
91
|
-
return true;
|
|
92
105
|
}
|
|
93
106
|
|
|
94
107
|
function CompCard({
|
|
@@ -7,6 +7,35 @@ import { STUDIO_MOTION_PATH } from "../components/editor/studioMotion";
|
|
|
7
7
|
import { shouldHandleTimelineToggleHotkey, isEditableTarget } from "../utils/timelineDiscovery";
|
|
8
8
|
import { shouldIgnoreHistoryShortcut } from "../utils/studioHelpers";
|
|
9
9
|
|
|
10
|
+
/** Safely resolves contentWindow for a potentially cross-origin iframe. */
|
|
11
|
+
function iframeContentWindow(iframe: HTMLIFrameElement | null): Window | null {
|
|
12
|
+
try {
|
|
13
|
+
return iframe?.contentWindow ?? null;
|
|
14
|
+
} catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Handles Cmd/Ctrl+Z (undo) and Cmd/Ctrl+Shift+Z / Ctrl+Y (redo) key events.
|
|
21
|
+
* Returns true if the event was handled, false otherwise.
|
|
22
|
+
*/
|
|
23
|
+
// fallow-ignore-next-line complexity
|
|
24
|
+
function handleUndoRedoKey(event: KeyboardEvent, onUndo: () => void, onRedo: () => void): boolean {
|
|
25
|
+
const key = event.key.toLowerCase();
|
|
26
|
+
if (key === "z" && !event.shiftKey) {
|
|
27
|
+
event.preventDefault();
|
|
28
|
+
onUndo();
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
if ((key === "z" && event.shiftKey) || (event.ctrlKey && !event.metaKey && key === "y")) {
|
|
32
|
+
event.preventDefault();
|
|
33
|
+
onRedo();
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
|
|
10
39
|
// ── Types ──
|
|
11
40
|
|
|
12
41
|
interface EditHistoryHandle {
|
|
@@ -177,18 +206,15 @@ export function useAppHotkeys({
|
|
|
177
206
|
|
|
178
207
|
// Cmd/Ctrl+Z — undo, Cmd/Ctrl+Shift+Z or Ctrl+Y — redo
|
|
179
208
|
if (event.metaKey || event.ctrlKey) {
|
|
180
|
-
if (
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
event
|
|
184
|
-
void handleUndoRef.current()
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
void handleRedoRef.current();
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
209
|
+
if (
|
|
210
|
+
!shouldIgnoreHistoryShortcut(event.target) &&
|
|
211
|
+
handleUndoRedoKey(
|
|
212
|
+
event,
|
|
213
|
+
() => void handleUndoRef.current(),
|
|
214
|
+
() => void handleRedoRef.current(),
|
|
215
|
+
)
|
|
216
|
+
) {
|
|
217
|
+
return;
|
|
192
218
|
}
|
|
193
219
|
|
|
194
220
|
// Cmd/Ctrl+1 — sidebar: Compositions tab
|
|
@@ -310,13 +336,21 @@ export function useAppHotkeys({
|
|
|
310
336
|
|
|
311
337
|
const syncPreviewTimelineHotkey = useCallback(
|
|
312
338
|
(iframe: HTMLIFrameElement | null) => {
|
|
313
|
-
const nextWindow = iframe
|
|
339
|
+
const nextWindow = iframeContentWindow(iframe);
|
|
314
340
|
if (previewHotkeyWindowRef.current === nextWindow) return;
|
|
315
341
|
if (previewHotkeyWindowRef.current) {
|
|
316
|
-
|
|
342
|
+
try {
|
|
343
|
+
previewHotkeyWindowRef.current.removeEventListener("keydown", previewAppKeyDownHandler);
|
|
344
|
+
} catch {
|
|
345
|
+
/* cross-origin iframe */
|
|
346
|
+
}
|
|
317
347
|
}
|
|
318
348
|
previewHotkeyWindowRef.current = nextWindow;
|
|
319
|
-
|
|
349
|
+
try {
|
|
350
|
+
nextWindow?.addEventListener("keydown", previewAppKeyDownHandler, true);
|
|
351
|
+
} catch {
|
|
352
|
+
/* cross-origin iframe */
|
|
353
|
+
}
|
|
320
354
|
},
|
|
321
355
|
[previewAppKeyDownHandler],
|
|
322
356
|
);
|
|
@@ -324,7 +358,11 @@ export function useAppHotkeys({
|
|
|
324
358
|
useEffect(
|
|
325
359
|
() => () => {
|
|
326
360
|
if (previewHotkeyWindowRef.current) {
|
|
327
|
-
|
|
361
|
+
try {
|
|
362
|
+
previewHotkeyWindowRef.current.removeEventListener("keydown", previewAppKeyDownHandler);
|
|
363
|
+
} catch {
|
|
364
|
+
/* cross-origin iframe */
|
|
365
|
+
}
|
|
328
366
|
previewHotkeyWindowRef.current = null;
|
|
329
367
|
}
|
|
330
368
|
},
|
|
@@ -336,16 +374,11 @@ export function useAppHotkeys({
|
|
|
336
374
|
const handleHistoryHotkey = useCallback((event: KeyboardEvent) => {
|
|
337
375
|
if (!(event.metaKey || event.ctrlKey)) return;
|
|
338
376
|
if (shouldIgnoreHistoryShortcut(event.target)) return;
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
void
|
|
343
|
-
|
|
344
|
-
}
|
|
345
|
-
if ((key === "z" && event.shiftKey) || (event.ctrlKey && !event.metaKey && key === "y")) {
|
|
346
|
-
event.preventDefault();
|
|
347
|
-
void handleRedoRef.current();
|
|
348
|
-
}
|
|
377
|
+
handleUndoRedoKey(
|
|
378
|
+
event,
|
|
379
|
+
() => void handleUndoRef.current(),
|
|
380
|
+
() => void handleRedoRef.current(),
|
|
381
|
+
);
|
|
349
382
|
}, []);
|
|
350
383
|
|
|
351
384
|
const syncPreviewHistoryHotkey = useCallback(
|
|
@@ -353,7 +386,7 @@ export function useAppHotkeys({
|
|
|
353
386
|
previewHistoryHotkeyCleanupRef.current?.();
|
|
354
387
|
previewHistoryHotkeyCleanupRef.current = null;
|
|
355
388
|
|
|
356
|
-
const win = iframe
|
|
389
|
+
const win = iframeContentWindow(iframe);
|
|
357
390
|
let doc: Document | null = null;
|
|
358
391
|
try {
|
|
359
392
|
doc = iframe?.contentDocument ?? null;
|
|
@@ -362,10 +395,18 @@ export function useAppHotkeys({
|
|
|
362
395
|
}
|
|
363
396
|
if (!win && !doc) return;
|
|
364
397
|
|
|
365
|
-
|
|
398
|
+
try {
|
|
399
|
+
win?.addEventListener("keydown", handleHistoryHotkey, true);
|
|
400
|
+
} catch {
|
|
401
|
+
/* cross-origin */
|
|
402
|
+
}
|
|
366
403
|
doc?.addEventListener("keydown", handleHistoryHotkey, true);
|
|
367
404
|
previewHistoryHotkeyCleanupRef.current = () => {
|
|
368
|
-
|
|
405
|
+
try {
|
|
406
|
+
win?.removeEventListener("keydown", handleHistoryHotkey, true);
|
|
407
|
+
} catch {
|
|
408
|
+
/* cross-origin */
|
|
409
|
+
}
|
|
369
410
|
doc?.removeEventListener("keydown", handleHistoryHotkey, true);
|
|
370
411
|
};
|
|
371
412
|
},
|
package/src/main.tsx
CHANGED
|
@@ -22,8 +22,13 @@ function errorProps(value: unknown): {
|
|
|
22
22
|
return { error_message: String(value), error_name: null, stack_trace: null };
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
// fallow-ignore-next-line complexity
|
|
26
|
+
function isCompositionAssetError(msg: string, name: string | null): boolean {
|
|
27
|
+
if (msg.includes("Error fetching") && (msg.includes("404") || msg.includes("Not Found")))
|
|
28
|
+
return true;
|
|
29
|
+
if (name === "EncodingError" || msg.includes("unsupported or unrecognizable format")) return true;
|
|
30
|
+
if (msg.includes("MEDIA_ERR_SRC_NOT_SUPPORTED")) return true;
|
|
31
|
+
return false;
|
|
27
32
|
}
|
|
28
33
|
|
|
29
34
|
const ERROR_CAP = 50;
|
|
@@ -57,9 +62,22 @@ window.addEventListener("error", (event) => {
|
|
|
57
62
|
});
|
|
58
63
|
});
|
|
59
64
|
|
|
65
|
+
let filteredAssetErrorCount = 0;
|
|
66
|
+
|
|
67
|
+
// fallow-ignore-next-line complexity
|
|
60
68
|
window.addEventListener("unhandledrejection", (event) => {
|
|
61
69
|
const props = errorProps(event.reason);
|
|
62
|
-
if (isCompositionAssetError(props.error_message))
|
|
70
|
+
if (isCompositionAssetError(props.error_message, props.error_name)) {
|
|
71
|
+
filteredAssetErrorCount++;
|
|
72
|
+
if (filteredAssetErrorCount === 1 || filteredAssetErrorCount % 100 === 0) {
|
|
73
|
+
trackStudioEvent("composition_asset_error_filtered", {
|
|
74
|
+
error_message: props.error_message.slice(0, 200),
|
|
75
|
+
error_name: props.error_name,
|
|
76
|
+
total_filtered: filteredAssetErrorCount,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
63
81
|
|
|
64
82
|
rejectionCount++;
|
|
65
83
|
if (rejectionCount > ERROR_CAP) {
|
|
@@ -182,23 +182,38 @@ export function usePlaybackKeyboard({
|
|
|
182
182
|
playbackKeyDownRef.current = handlePlaybackKeyDown;
|
|
183
183
|
playbackKeyUpRef.current = handlePlaybackKeyUp;
|
|
184
184
|
|
|
185
|
+
// fallow-ignore-next-line complexity
|
|
185
186
|
const attachIframeShortcutListeners = useCallback(() => {
|
|
186
187
|
iframeShortcutCleanupRef.current?.();
|
|
187
188
|
iframeShortcutCleanupRef.current = null;
|
|
188
189
|
|
|
189
|
-
|
|
190
|
-
|
|
190
|
+
let iframeWin: Window | null = null;
|
|
191
|
+
let iframeDoc: Document | null = null;
|
|
192
|
+
try {
|
|
193
|
+
iframeWin = iframeRef.current?.contentWindow ?? null;
|
|
194
|
+
iframeDoc = iframeRef.current?.contentDocument ?? null;
|
|
195
|
+
} catch {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
191
198
|
if (!iframeWin && !iframeDoc) return;
|
|
192
199
|
|
|
193
200
|
const handleIframeKeyDown = (e: KeyboardEvent) => playbackKeyDownRef.current(e);
|
|
194
201
|
const handleIframeKeyUp = (e: KeyboardEvent) => playbackKeyUpRef.current(e);
|
|
195
|
-
|
|
196
|
-
|
|
202
|
+
try {
|
|
203
|
+
iframeWin?.addEventListener("keydown", handleIframeKeyDown, true);
|
|
204
|
+
iframeWin?.addEventListener("keyup", handleIframeKeyUp, true);
|
|
205
|
+
} catch {
|
|
206
|
+
/* cross-origin iframe */
|
|
207
|
+
}
|
|
197
208
|
iframeDoc?.addEventListener("keydown", handleIframeKeyDown, true);
|
|
198
209
|
iframeDoc?.addEventListener("keyup", handleIframeKeyUp, true);
|
|
199
210
|
iframeShortcutCleanupRef.current = () => {
|
|
200
|
-
|
|
201
|
-
|
|
211
|
+
try {
|
|
212
|
+
iframeWin?.removeEventListener("keydown", handleIframeKeyDown, true);
|
|
213
|
+
iframeWin?.removeEventListener("keyup", handleIframeKeyUp, true);
|
|
214
|
+
} catch {
|
|
215
|
+
/* cross-origin iframe */
|
|
216
|
+
}
|
|
202
217
|
iframeDoc?.removeEventListener("keydown", handleIframeKeyDown, true);
|
|
203
218
|
iframeDoc?.removeEventListener("keyup", handleIframeKeyUp, true);
|
|
204
219
|
};
|