@hyperframes/studio 0.8.56 → 0.8.57
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-c23wnIfc.js +464 -0
- package/dist/assets/{index-ZmBtVrEm.css → index-DKJpHYYd.css} +1 -1
- package/dist/assets/{index-DFlILHJe.js → index-DgkAzx3n.js} +1 -1
- package/dist/assets/{index-CxR7F2YQ.js → index-IDNr3X1H.js} +1 -1
- package/dist/assets/{index-BlkJ_Axe.js → index-gbOCDCuZ.js} +194 -194
- package/dist/index.d.ts +95 -9
- package/dist/index.html +2 -2
- package/dist/index.js +15573 -14835
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +7 -7
- package/src/App.tsx +4 -0
- package/src/components/EditorShell.tsx +3 -1
- package/src/components/ProjectUnreachableBanner.test.tsx +113 -0
- package/src/components/ProjectUnreachableBanner.tsx +89 -0
- package/src/components/editor/DomEditOverlay.tsx +0 -15
- package/src/components/editor/GridOverlay.tsx +12 -23
- package/src/components/editor/MotionPathOverlay.test.tsx +66 -0
- package/src/components/editor/MotionPathOverlay.tsx +3 -1
- package/src/components/editor/PreviewGuides.test.tsx +6 -1
- package/src/components/editor/PreviewGuides.tsx +8 -10
- package/src/components/editor/PreviewOverlayProvider.test.tsx +90 -0
- package/src/components/editor/PreviewOverlayProvider.tsx +86 -0
- package/src/components/editor/SnapToolbar.test.tsx +25 -12
- package/src/components/editor/SnapToolbar.tsx +30 -59
- package/src/components/editor/propertyPanelFlatMediaSection.test.tsx +2 -2
- package/src/components/editor/propertyPanelFlatMediaSection.tsx +46 -12
- package/src/components/editor/usePreviewCompositionRect.test.tsx +19 -13
- package/src/components/editor/usePreviewCompositionRect.ts +12 -12
- package/src/components/editor/useVolumeAutomation.test.tsx +68 -0
- package/src/components/editor/useVolumeAutomation.ts +57 -16
- package/src/components/nle/NLEContext.tsx +40 -4
- package/src/components/nle/NLEPreview.test.ts +66 -1
- package/src/components/nle/NLEPreview.tsx +88 -44
- package/src/components/nle/PreviewOverlays.tsx +4 -16
- package/src/components/nle/PreviewPane.tsx +17 -2
- package/src/components/nle/previewReadOnly.test.tsx +1 -0
- package/src/hooks/useCompositionDimensions.test.tsx +38 -0
- package/src/hooks/useCompositionDimensions.ts +17 -1
- package/src/hooks/useDomEditAttributeCommits.test.ts +23 -0
- package/src/hooks/useDomEditAttributeCommits.ts +14 -3
- package/src/hooks/useFileTree.test.tsx +56 -0
- package/src/hooks/useFileTree.ts +8 -1
- package/src/hooks/useSdkSession.lifecycle.test.tsx +80 -0
- package/src/hooks/useSdkSession.ts +46 -10
- package/src/hooks/useServerConnection.hashProject.test.tsx +303 -0
- package/src/hooks/useServerConnection.ts +81 -8
- package/src/index.ts +12 -0
- package/src/player/components/Player.test.ts +141 -13
- package/src/player/components/Player.tsx +58 -2
- package/src/player/components/ShortcutsPanel.test.tsx +4 -10
- package/src/player/components/Timeline.tsx +23 -572
- package/src/player/components/TimelineCanvas.tsx +9 -30
- package/src/player/components/TimelineOverlays.test.ts +67 -32
- package/src/player/components/TimelineOverlays.tsx +46 -107
- package/src/player/components/TimelineProvider.tsx +191 -0
- package/src/player/components/testRootHarness.ts +18 -0
- package/src/player/components/timelineProviderStateBuilders.test.ts +109 -0
- package/src/player/components/timelineProviderStateBuilders.ts +83 -0
- package/src/player/components/useTimelineClipContextMenu.ts +1 -1
- package/src/player/components/useTimelineOverlaysState.ts +100 -0
- package/src/player/components/useTimelineProviderState.tsx +583 -0
- package/src/player/hooks/playbackAdapterResolution.ts +98 -0
- package/src/player/hooks/timelinePlayerTestHarness.ts +120 -0
- package/src/player/hooks/usePreviewFirstFrameTelemetry.ts +51 -0
- package/src/player/hooks/useShadowPreviewReload.ts +183 -0
- package/src/player/hooks/useTimelinePlayer.seek.test.ts +8 -88
- package/src/player/hooks/useTimelinePlayer.shadowReload.test.ts +457 -0
- package/src/player/hooks/useTimelinePlayer.ts +65 -99
- package/src/player/hooks/useTimelineSyncCallbacks.ts +159 -88
- package/src/telemetry/events.test.ts +18 -0
- package/src/telemetry/events.ts +10 -0
- package/src/telemetry/system.test.ts +49 -0
- package/src/telemetry/system.ts +6 -0
- package/src/telemetry/tabId.test.ts +43 -0
- package/src/telemetry/tabId.ts +44 -0
- package/src/utils/previewCompositionSize.ts +26 -0
- package/src/utils/reloadDebug.ts +2 -4
- package/src/utils/sourcePatcher.ts +1 -1
- package/src/utils/studioTelemetry.test.ts +14 -0
- package/src/utils/studioTelemetry.ts +4 -0
- package/dist/assets/hyperframes-player-k8LzMtXX.js +0 -464
|
@@ -26,7 +26,11 @@ interface PlayerProps {
|
|
|
26
26
|
projectId?: string;
|
|
27
27
|
directUrl?: string;
|
|
28
28
|
onLoad: () => void;
|
|
29
|
+
/** Fires once the loaded document is painted and every loader (shader, assets) has cleared. */
|
|
30
|
+
onReadyToShowChange?: (ready: boolean) => void;
|
|
31
|
+
onPreviewError?: (message: string) => void;
|
|
29
32
|
onCompositionLoadingChange?: (loading: boolean) => void;
|
|
33
|
+
onPainted?: (details: { iframe: HTMLIFrameElement; startedAt: number; loadId: number }) => void;
|
|
30
34
|
portrait?: boolean;
|
|
31
35
|
style?: React.CSSProperties;
|
|
32
36
|
suppressLoadingOverlay?: boolean;
|
|
@@ -134,7 +138,10 @@ export const Player = forwardRef<HTMLIFrameElement, PlayerProps>(
|
|
|
134
138
|
projectId,
|
|
135
139
|
directUrl,
|
|
136
140
|
onLoad,
|
|
141
|
+
onReadyToShowChange,
|
|
142
|
+
onPreviewError,
|
|
137
143
|
onCompositionLoadingChange,
|
|
144
|
+
onPainted,
|
|
138
145
|
portrait,
|
|
139
146
|
style,
|
|
140
147
|
suppressLoadingOverlay,
|
|
@@ -146,13 +153,25 @@ export const Player = forwardRef<HTMLIFrameElement, PlayerProps>(
|
|
|
146
153
|
const assetPollRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
|
147
154
|
const assetFadeRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
148
155
|
const retryPreviewRef = useRef<(() => void) | null>(null);
|
|
156
|
+
const loadStartedAtRef = useRef(0);
|
|
157
|
+
const loadIdRef = useRef(0);
|
|
149
158
|
const retryCountRef = useRef(0);
|
|
159
|
+
// Read at call time: this element outlives its first props (a shadow preview is
|
|
160
|
+
// promoted in place), so mount-time closures would go stale.
|
|
161
|
+
const onLoadRef = useRef(onLoad);
|
|
162
|
+
onLoadRef.current = onLoad;
|
|
163
|
+
const onReadyToShowChangeRef = useRef(onReadyToShowChange);
|
|
164
|
+
onReadyToShowChangeRef.current = onReadyToShowChange;
|
|
165
|
+
const onPreviewErrorRef = useRef(onPreviewError);
|
|
166
|
+
onPreviewErrorRef.current = onPreviewError;
|
|
167
|
+
const [loaded, setLoaded] = useState(false);
|
|
150
168
|
const [assetsLoading, setAssetsLoading] = useState(false);
|
|
151
169
|
const [assetOverlayVisible, setAssetOverlayVisible] = useState(false);
|
|
152
170
|
const [assetOverlayFading, setAssetOverlayFading] = useState(false);
|
|
153
171
|
const [assetWaitLong, setAssetWaitLong] = useState(false);
|
|
154
172
|
const [shaderTransitionLoading, setShaderTransitionLoading] = useState(false);
|
|
155
173
|
const [compositionLoading, setCompositionLoading] = useState(true);
|
|
174
|
+
const [painted, setPainted] = useState(false);
|
|
156
175
|
const [compositionOverlayDeferred, setCompositionOverlayDeferred] = useState(true);
|
|
157
176
|
const [previewError, setPreviewError] = useState<string | null>(null);
|
|
158
177
|
|
|
@@ -194,6 +213,8 @@ export const Player = forwardRef<HTMLIFrameElement, PlayerProps>(
|
|
|
194
213
|
retryUrl.searchParams.set("_hfStudioRetry", String(retryCountRef.current));
|
|
195
214
|
setPreviewError(null);
|
|
196
215
|
setCompositionLoading(true);
|
|
216
|
+
loadStartedAtRef.current = performance.now();
|
|
217
|
+
loadIdRef.current += 1;
|
|
197
218
|
player.setAttribute("src", retryUrl.pathname + retryUrl.search);
|
|
198
219
|
};
|
|
199
220
|
retryPreviewRef.current = retryPreview;
|
|
@@ -207,15 +228,23 @@ export const Player = forwardRef<HTMLIFrameElement, PlayerProps>(
|
|
|
207
228
|
setPreviewError(null);
|
|
208
229
|
setCompositionLoading(false);
|
|
209
230
|
};
|
|
231
|
+
const handlePainted = () => {
|
|
232
|
+
setPainted(true);
|
|
233
|
+
onPainted?.({ iframe, startedAt: loadStartedAtRef.current, loadId: loadIdRef.current });
|
|
234
|
+
};
|
|
210
235
|
const handleError = (event: Event) => {
|
|
211
|
-
|
|
236
|
+
const message = readPreviewErrorMessage(event);
|
|
237
|
+
onPreviewErrorRef.current?.(message);
|
|
238
|
+
setPreviewError(message);
|
|
212
239
|
setCompositionLoading(false);
|
|
213
240
|
};
|
|
214
241
|
const handleLoad = () => {
|
|
215
242
|
loadCountRef.current++;
|
|
243
|
+
setLoaded(true);
|
|
216
244
|
setPreviewError(null);
|
|
217
245
|
setShaderTransitionLoading(false);
|
|
218
246
|
setCompositionLoading(true);
|
|
247
|
+
setPainted(false);
|
|
219
248
|
// Reveal animation on reload (hot-reload, composition switch)
|
|
220
249
|
if (loadCountRef.current > 1) {
|
|
221
250
|
container.classList.remove("preview-revealing");
|
|
@@ -224,7 +253,7 @@ export const Player = forwardRef<HTMLIFrameElement, PlayerProps>(
|
|
|
224
253
|
const onEnd = () => container.classList.remove("preview-revealing");
|
|
225
254
|
container.addEventListener("animationend", onEnd, { once: true });
|
|
226
255
|
}
|
|
227
|
-
|
|
256
|
+
onLoadRef.current();
|
|
228
257
|
|
|
229
258
|
// Show a loading overlay until every `<video>`/`<audio>` and Lottie
|
|
230
259
|
// asset is ready. Without this users can click play before audio has
|
|
@@ -274,6 +303,7 @@ export const Player = forwardRef<HTMLIFrameElement, PlayerProps>(
|
|
|
274
303
|
player.addEventListener("click", preventToggle, { capture: true });
|
|
275
304
|
player.addEventListener("shadertransitionstate", handleShaderTransitionState);
|
|
276
305
|
player.addEventListener("ready", handleReady);
|
|
306
|
+
player.addEventListener("painted", handlePainted);
|
|
277
307
|
player.addEventListener("error", handleError);
|
|
278
308
|
|
|
279
309
|
// Bridge the inner iframe to the forwarded ref for useTimelinePlayer.
|
|
@@ -291,6 +321,8 @@ export const Player = forwardRef<HTMLIFrameElement, PlayerProps>(
|
|
|
291
321
|
player.style.height = "100%";
|
|
292
322
|
player.style.display = "block";
|
|
293
323
|
player.style.background = "transparent";
|
|
324
|
+
loadStartedAtRef.current = performance.now();
|
|
325
|
+
loadIdRef.current += 1;
|
|
294
326
|
player.setAttribute("src", src);
|
|
295
327
|
container.appendChild(player);
|
|
296
328
|
|
|
@@ -313,6 +345,7 @@ export const Player = forwardRef<HTMLIFrameElement, PlayerProps>(
|
|
|
313
345
|
player.removeEventListener("click", preventToggle, { capture: true });
|
|
314
346
|
player.removeEventListener("shadertransitionstate", handleShaderTransitionState);
|
|
315
347
|
player.removeEventListener("ready", handleReady);
|
|
348
|
+
player.removeEventListener("painted", handlePainted);
|
|
316
349
|
player.removeEventListener("error", handleError);
|
|
317
350
|
if (assetPollRef.current) clearInterval(assetPollRef.current);
|
|
318
351
|
assetPollRef.current = null;
|
|
@@ -394,6 +427,29 @@ export const Player = forwardRef<HTMLIFrameElement, PlayerProps>(
|
|
|
394
427
|
setAssetsLoading(false);
|
|
395
428
|
};
|
|
396
429
|
|
|
430
|
+
const readyToShow =
|
|
431
|
+
loaded &&
|
|
432
|
+
painted &&
|
|
433
|
+
!compositionLoading &&
|
|
434
|
+
!shaderTransitionLoading &&
|
|
435
|
+
!assetsLoading &&
|
|
436
|
+
!previewError;
|
|
437
|
+
// `painted` means the player's own loader has finished fading; two frames of grace on top.
|
|
438
|
+
useEffect(() => {
|
|
439
|
+
if (!readyToShow) {
|
|
440
|
+
onReadyToShowChangeRef.current?.(false);
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
let second = 0;
|
|
444
|
+
const first = requestAnimationFrame(() => {
|
|
445
|
+
second = requestAnimationFrame(() => onReadyToShowChangeRef.current?.(true));
|
|
446
|
+
});
|
|
447
|
+
return () => {
|
|
448
|
+
cancelAnimationFrame(first);
|
|
449
|
+
cancelAnimationFrame(second);
|
|
450
|
+
};
|
|
451
|
+
}, [readyToShow]);
|
|
452
|
+
|
|
397
453
|
const showCompositionOverlay =
|
|
398
454
|
!suppressLoadingOverlay &&
|
|
399
455
|
!compositionOverlayDeferred &&
|
|
@@ -1,24 +1,18 @@
|
|
|
1
1
|
// @vitest-environment happy-dom
|
|
2
2
|
|
|
3
3
|
import React, { act, Profiler } from "react";
|
|
4
|
-
import {
|
|
5
|
-
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
4
|
+
import { describe, expect, it, vi } from "vitest";
|
|
6
5
|
import { ShortcutsPanel } from "./ShortcutsPanel";
|
|
6
|
+
import { createHappyDomRootHarness } from "./testRootHarness";
|
|
7
7
|
|
|
8
8
|
(globalThis as unknown as { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
|
9
9
|
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
afterEach(() => {
|
|
13
|
-
for (const root of roots.splice(0)) act(() => root.unmount());
|
|
14
|
-
document.body.innerHTML = "";
|
|
15
|
-
});
|
|
10
|
+
const { mount } = createHappyDomRootHarness();
|
|
16
11
|
|
|
17
12
|
function renderPanel(onRender = vi.fn()) {
|
|
18
13
|
const host = document.createElement("div");
|
|
19
14
|
document.body.append(host);
|
|
20
|
-
const root =
|
|
21
|
-
roots.push(root);
|
|
15
|
+
const root = mount(host);
|
|
22
16
|
act(() => {
|
|
23
17
|
root.render(
|
|
24
18
|
<Profiler id="shortcuts-panel" onRender={onRender}>
|