@hyperframes/studio 0.8.192 → 0.8.194
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-ChN3cOr0.js → hyperframes-player-DLOUZUct.js} +1 -1
- package/dist/assets/{index-CHu83hHM.js → index-CAQ7OcNJ.js} +163 -163
- package/dist/assets/{index-BYbFcRR0.js → index-D9kA6JUX.js} +1 -1
- package/dist/{chunk-6SXUUBPD.js → chunk-ORLUDHTU.js} +3 -1
- package/dist/index.html +1 -1
- package/dist/index.js +11 -5
- package/dist/player.js +1 -1
- package/package.json +8 -8
- package/src/components/panels/MotionBlurPanel.test.tsx +12 -3
- package/src/components/panels/motionBlurPanelModel.test.ts +13 -3
- package/src/components/panels/motionBlurPanelModel.ts +4 -2
- package/src/player/sceneSwap.test.ts +22 -0
- package/src/player/sceneSwap.ts +3 -1
- package/src/utils/gsapSoftReload.test.ts +42 -0
- package/src/utils/gsapSoftReload.ts +10 -3
|
@@ -1028,7 +1028,8 @@ function sceneSwapFor(iframe) {
|
|
|
1028
1028
|
() => deadline.abort(new Error("the preview took too long to swap")),
|
|
1029
1029
|
SCENE_SWAP_MS
|
|
1030
1030
|
);
|
|
1031
|
-
|
|
1031
|
+
const onCancel = () => deadline.abort(cancel?.reason);
|
|
1032
|
+
cancel?.addEventListener("abort", onCancel, { once: true });
|
|
1032
1033
|
const swapping = (async () => {
|
|
1033
1034
|
const response = await studioApiFetch(url, { signal: deadline.signal });
|
|
1034
1035
|
if (!response.ok) throw new Error(`preview request failed with ${response.status}`);
|
|
@@ -1043,6 +1044,7 @@ function sceneSwapFor(iframe) {
|
|
|
1043
1044
|
await Promise.race([swapping, expired]);
|
|
1044
1045
|
} finally {
|
|
1045
1046
|
clearTimeout(timer);
|
|
1047
|
+
cancel?.removeEventListener("abort", onCancel);
|
|
1046
1048
|
}
|
|
1047
1049
|
iframe.dispatchEvent(new Event(SCENES_SWAPPED));
|
|
1048
1050
|
};
|
package/dist/index.html
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
document.documentElement.dataset.theme = "paper";
|
|
15
15
|
}
|
|
16
16
|
</script>
|
|
17
|
-
<script type="module" crossorigin src="/assets/index-
|
|
17
|
+
<script type="module" crossorigin src="/assets/index-CAQ7OcNJ.js"></script>
|
|
18
18
|
<link rel="stylesheet" crossorigin href="/assets/index-CYbvOVp-.css">
|
|
19
19
|
</head>
|
|
20
20
|
<body>
|
package/dist/index.js
CHANGED
|
@@ -48,7 +48,7 @@ import {
|
|
|
48
48
|
usePreviewVariablesStore,
|
|
49
49
|
whenPreviewBooted,
|
|
50
50
|
writeStudioUiPreferences
|
|
51
|
-
} from "./chunk-
|
|
51
|
+
} from "./chunk-ORLUDHTU.js";
|
|
52
52
|
import {
|
|
53
53
|
Button,
|
|
54
54
|
ContextMenu,
|
|
@@ -17608,7 +17608,7 @@ async function writeProjectFilesWithHistoryInQueue({
|
|
|
17608
17608
|
|
|
17609
17609
|
// src/utils/gsapSoftReload.ts
|
|
17610
17610
|
import { COLOR_GRADING_SOURCE_HIDDEN_ATTR as COLOR_GRADING_SOURCE_HIDDEN_ATTR2 } from "@hyperframes/core/color-grading";
|
|
17611
|
-
import { motionPathPluginUrl } from "@hyperframes/core/gsap-cdn";
|
|
17611
|
+
import { GSAP_BASE_URL_META, motionPathPluginUrl } from "@hyperframes/core/gsap-cdn";
|
|
17612
17612
|
|
|
17613
17613
|
// src/utils/authoredSource.ts
|
|
17614
17614
|
import { ensureHfIds, isCompositionTemplate } from "@hyperframes/parsers/hf-ids";
|
|
@@ -17870,6 +17870,12 @@ function fileDocs(ownFile, written, nestedFiles) {
|
|
|
17870
17870
|
|
|
17871
17871
|
// src/utils/gsapSoftReload.ts
|
|
17872
17872
|
var pendingReadyRetry = null;
|
|
17873
|
+
function previewMotionPathUrl(doc, version) {
|
|
17874
|
+
const baseUrl = doc.head.querySelector(
|
|
17875
|
+
`meta[name="${GSAP_BASE_URL_META}"]`
|
|
17876
|
+
)?.content;
|
|
17877
|
+
return motionPathPluginUrl(version, baseUrl);
|
|
17878
|
+
}
|
|
17873
17879
|
function ensureMotionPathPluginLoaded(iframe) {
|
|
17874
17880
|
if (pendingReadyRetry) window.removeEventListener("message", pendingReadyRetry);
|
|
17875
17881
|
pendingReadyRetry = null;
|
|
@@ -17900,7 +17906,7 @@ function ensureMotionPathPluginLoaded(iframe) {
|
|
|
17900
17906
|
try {
|
|
17901
17907
|
win.__hfMotionPathPluginLoading = true;
|
|
17902
17908
|
const pluginScript = doc.createElement("script");
|
|
17903
|
-
pluginScript.src =
|
|
17909
|
+
pluginScript.src = previewMotionPathUrl(doc, win.gsap?.version);
|
|
17904
17910
|
const finalize = () => {
|
|
17905
17911
|
win.__hfMotionPathPluginLoading = false;
|
|
17906
17912
|
try {
|
|
@@ -18086,7 +18092,7 @@ function applySoftReload(iframe, scriptText, options = {}) {
|
|
|
18086
18092
|
}
|
|
18087
18093
|
win.__hfMotionPathPluginLoading = true;
|
|
18088
18094
|
const pluginScript = doc.createElement("script");
|
|
18089
|
-
pluginScript.src =
|
|
18095
|
+
pluginScript.src = previewMotionPathUrl(doc, win.gsap?.version);
|
|
18090
18096
|
pluginScript.onload = () => {
|
|
18091
18097
|
win.__hfMotionPathPluginLoading = false;
|
|
18092
18098
|
executeScript();
|
|
@@ -98684,7 +98690,7 @@ function levelOf(settings) {
|
|
|
98684
98690
|
return settings.shutterDeg > STRONG_ABOVE ? "strong" : "subtle";
|
|
98685
98691
|
}
|
|
98686
98692
|
function patchForLevel(level) {
|
|
98687
|
-
return level === "off" ? { enabled: false } : { enabled: true, ...LEVEL_SETTINGS[level] };
|
|
98693
|
+
return level === "off" ? { enabled: false, events: false } : { enabled: true, events: true, ...LEVEL_SETTINGS[level] };
|
|
98688
98694
|
}
|
|
98689
98695
|
|
|
98690
98696
|
// src/components/panels/MotionBlurPanel.tsx
|
package/dist/player.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperframes/studio",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.194",
|
|
4
4
|
"description": "",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -60,12 +60,12 @@
|
|
|
60
60
|
"dockview-react": "8.3.1",
|
|
61
61
|
"gsap": "^3.13.0",
|
|
62
62
|
"mediabunny": "^1.45.3",
|
|
63
|
-
"@hyperframes/
|
|
64
|
-
"@hyperframes/
|
|
65
|
-
"@hyperframes/
|
|
66
|
-
"@hyperframes/
|
|
67
|
-
"@hyperframes/
|
|
68
|
-
"@hyperframes/studio-server": "0.8.
|
|
63
|
+
"@hyperframes/parsers": "0.8.194",
|
|
64
|
+
"@hyperframes/core": "0.8.194",
|
|
65
|
+
"@hyperframes/sdk": "0.8.194",
|
|
66
|
+
"@hyperframes/shader-transitions": "0.8.194",
|
|
67
|
+
"@hyperframes/player": "0.8.194",
|
|
68
|
+
"@hyperframes/studio-server": "0.8.194"
|
|
69
69
|
},
|
|
70
70
|
"devDependencies": {
|
|
71
71
|
"@tailwindcss/vite": "^4.3.3",
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
"vite": "^6.4.2",
|
|
83
83
|
"vitest": "^4.1.11",
|
|
84
84
|
"zustand": "^5.0.0",
|
|
85
|
-
"@hyperframes/producer": "0.8.
|
|
85
|
+
"@hyperframes/producer": "0.8.194"
|
|
86
86
|
},
|
|
87
87
|
"peerDependencies": {
|
|
88
88
|
"react": "19",
|
|
@@ -130,20 +130,29 @@ describe("MotionBlurPanel", () => {
|
|
|
130
130
|
expect(html).toContain('data-motion-blur-samples="64"');
|
|
131
131
|
});
|
|
132
132
|
|
|
133
|
-
it("Off removes data-motion-blur and leaves
|
|
133
|
+
it("Off removes data-motion-blur and its events, and leaves the other settings on the root", async () => {
|
|
134
134
|
const host = await mount(
|
|
135
135
|
' data-motion-blur="on" data-motion-blur-shutter="720" data-motion-blur-samples="64" data-motion-blur-tier="filter" data-motion-blur-events="on"',
|
|
136
136
|
);
|
|
137
137
|
await click(radio(host, "Off"));
|
|
138
138
|
const html = files["index.html"] ?? "";
|
|
139
139
|
expect(html).not.toContain('data-motion-blur="on"');
|
|
140
|
-
|
|
140
|
+
expect(html).not.toContain("data-motion-blur-events");
|
|
141
|
+
for (const kept of ['shutter="720"', 'samples="64"', 'tier="filter"']) {
|
|
141
142
|
expect(html).toContain(`data-motion-blur-${kept}`);
|
|
142
143
|
}
|
|
143
144
|
});
|
|
144
145
|
|
|
146
|
+
it("turns the events attribute on with Subtle and Strong, so callback-driven motion is found", async () => {
|
|
147
|
+
const host = await mount();
|
|
148
|
+
await click(radio(host, "Subtle"));
|
|
149
|
+
expect(files["index.html"]).toContain('data-motion-blur-events="on"');
|
|
150
|
+
await click(radio(host, "Strong"));
|
|
151
|
+
expect(files["index.html"]).toContain('data-motion-blur-events="on"');
|
|
152
|
+
});
|
|
153
|
+
|
|
145
154
|
it("writes nothing when the chosen level is already set", async () => {
|
|
146
|
-
const host = await mount(' data-motion-blur="on"');
|
|
155
|
+
const host = await mount(' data-motion-blur="on" data-motion-blur-events="on"');
|
|
147
156
|
await click(radio(host, "Subtle"));
|
|
148
157
|
expect(writeProjectFile).not.toHaveBeenCalled();
|
|
149
158
|
});
|
|
@@ -130,9 +130,19 @@ describe("levelOf and patchForLevel", () => {
|
|
|
130
130
|
});
|
|
131
131
|
|
|
132
132
|
it("sets Subtle to the default blur and Strong to the maximum, and leaves the rest alone for Off", () => {
|
|
133
|
-
expect(patchForLevel("subtle")).toEqual({
|
|
134
|
-
|
|
135
|
-
|
|
133
|
+
expect(patchForLevel("subtle")).toEqual({
|
|
134
|
+
enabled: true,
|
|
135
|
+
events: true,
|
|
136
|
+
shutterDeg: 240,
|
|
137
|
+
samples: null,
|
|
138
|
+
});
|
|
139
|
+
expect(patchForLevel("strong")).toEqual({
|
|
140
|
+
enabled: true,
|
|
141
|
+
events: true,
|
|
142
|
+
shutterDeg: 720,
|
|
143
|
+
samples: 64,
|
|
144
|
+
});
|
|
145
|
+
expect(patchForLevel("off")).toEqual({ enabled: false, events: false });
|
|
136
146
|
});
|
|
137
147
|
|
|
138
148
|
it("reads back as the level that was chosen", () => {
|
|
@@ -92,7 +92,9 @@ export function levelOf(settings: MotionBlurSettings): BlurLevel {
|
|
|
92
92
|
return settings.shutterDeg > STRONG_ABOVE ? "strong" : "subtle";
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
/** What choosing `level` changes; Off leaves the shutter, samples and
|
|
95
|
+
/** What choosing `level` changes; blur on turns events on too, and Off leaves the shutter, samples and the rest alone. */
|
|
96
96
|
export function patchForLevel(level: BlurLevel): Partial<MotionBlurSettings> {
|
|
97
|
-
return level === "off"
|
|
97
|
+
return level === "off"
|
|
98
|
+
? { enabled: false, events: false }
|
|
99
|
+
: { enabled: true, events: true, ...LEVEL_SETTINGS[level] };
|
|
98
100
|
}
|
|
@@ -98,6 +98,28 @@ describe("sceneSwapFor", () => {
|
|
|
98
98
|
expect(swap).not.toHaveBeenCalled();
|
|
99
99
|
});
|
|
100
100
|
|
|
101
|
+
it("leaves no listener on the caller's cancel signal once swaps settle, so a swapped film can be freed", async () => {
|
|
102
|
+
vi.spyOn(globalThis, "fetch").mockImplementation(async () => new Response("<html></html>"));
|
|
103
|
+
const cancel = new AbortController();
|
|
104
|
+
const live = new Set<unknown>();
|
|
105
|
+
const { signal } = cancel;
|
|
106
|
+
const add = signal.addEventListener.bind(signal);
|
|
107
|
+
const remove = signal.removeEventListener.bind(signal);
|
|
108
|
+
signal.addEventListener = ((type: string, listener: EventListener, options?: unknown) => {
|
|
109
|
+
live.add(listener);
|
|
110
|
+
add(type, listener, options as AddEventListenerOptions);
|
|
111
|
+
}) as AbortSignal["addEventListener"];
|
|
112
|
+
signal.removeEventListener = ((type: string, listener: EventListener, options?: unknown) => {
|
|
113
|
+
live.delete(listener);
|
|
114
|
+
remove(type, listener, options as EventListenerOptions);
|
|
115
|
+
}) as AbortSignal["removeEventListener"];
|
|
116
|
+
const { iframe } = swappableIframe();
|
|
117
|
+
const refused = swappableIframe(vi.fn(async () => Promise.reject(new Error("refused")))).iframe;
|
|
118
|
+
for (let n = 0; n < 3; n++) await sceneSwapFor(iframe)!("/preview", () => true, signal);
|
|
119
|
+
await expect(sceneSwapFor(refused)!("/preview", () => true, signal)).rejects.toThrow("refused");
|
|
120
|
+
expect(live.size).toBe(0);
|
|
121
|
+
});
|
|
122
|
+
|
|
101
123
|
it("swaps a document that arrives in time", async () => {
|
|
102
124
|
vi.spyOn(globalThis, "fetch").mockImplementation(async () => new Response("<html></html>"));
|
|
103
125
|
const { iframe, swap } = swappableIframe();
|
package/src/player/sceneSwap.ts
CHANGED
|
@@ -88,7 +88,8 @@ export function sceneSwapFor(
|
|
|
88
88
|
() => deadline.abort(new Error("the preview took too long to swap")),
|
|
89
89
|
SCENE_SWAP_MS,
|
|
90
90
|
);
|
|
91
|
-
|
|
91
|
+
const onCancel = () => deadline.abort(cancel?.reason);
|
|
92
|
+
cancel?.addEventListener("abort", onCancel, { once: true });
|
|
92
93
|
const swapping = (async () => {
|
|
93
94
|
const response = await studioApiFetch(url, { signal: deadline.signal });
|
|
94
95
|
if (!response.ok) throw new Error(`preview request failed with ${response.status}`);
|
|
@@ -104,6 +105,7 @@ export function sceneSwapFor(
|
|
|
104
105
|
await Promise.race([swapping, expired]);
|
|
105
106
|
} finally {
|
|
106
107
|
clearTimeout(timer);
|
|
108
|
+
cancel?.removeEventListener("abort", onCancel);
|
|
107
109
|
}
|
|
108
110
|
iframe.dispatchEvent(new Event(SCENES_SWAPPED));
|
|
109
111
|
};
|
|
@@ -318,6 +318,28 @@ window.__timelines["root"] = tl;`;
|
|
|
318
318
|
expect(contentWindow.__hfMotionPathPluginLoading).toBe(false);
|
|
319
319
|
});
|
|
320
320
|
|
|
321
|
+
it("loads a missing soft-reload plugin from the iframe mirror and reports its failure", () => {
|
|
322
|
+
const { iframe, contentWindow } = buildMockIframe({
|
|
323
|
+
gsap: { timeline: vi.fn(), registerPlugin: vi.fn(), version: "3.14.2" },
|
|
324
|
+
});
|
|
325
|
+
const head = iframe.contentDocument!.head;
|
|
326
|
+
const meta = document.createElement("meta");
|
|
327
|
+
meta.name = "hyperframes-gsap-base-url";
|
|
328
|
+
meta.content = "http://localhost:8080/vendor/npm";
|
|
329
|
+
head.appendChild(meta);
|
|
330
|
+
const onAsyncFailure = vi.fn();
|
|
331
|
+
expect(applySoftReload(iframe, MOTION_PATH_SCRIPT_TEXT, { onAsyncFailure })).toBe("applied");
|
|
332
|
+
const plugin = head.querySelector("script");
|
|
333
|
+
expect(plugin?.src).toBe(
|
|
334
|
+
"http://localhost:8080/vendor/npm/gsap@3.14.2/dist/MotionPathPlugin.min.js",
|
|
335
|
+
);
|
|
336
|
+
plugin?.onerror?.(new Event("error"));
|
|
337
|
+
expect(onAsyncFailure).toHaveBeenCalledTimes(1);
|
|
338
|
+
expect(contentWindow.__hfForceTimelineRebind).not.toHaveBeenCalled();
|
|
339
|
+
expect(contentWindow.__hfMotionPathPluginLoading).toBe(false);
|
|
340
|
+
expect(head.querySelectorAll("script")).toHaveLength(1);
|
|
341
|
+
});
|
|
342
|
+
|
|
321
343
|
it('returns "cannot-soft-reload" when multiple GSAP scripts exist (ambiguous)', () => {
|
|
322
344
|
const script1 = document.createElement("script");
|
|
323
345
|
script1.textContent = "const tl = gsap.timeline({ paused: true });";
|
|
@@ -490,6 +512,26 @@ describe("ensureMotionPathPluginLoaded", () => {
|
|
|
490
512
|
);
|
|
491
513
|
});
|
|
492
514
|
|
|
515
|
+
it("preloads from the iframe mirror once, retaining the runtime version", () => {
|
|
516
|
+
const { iframe, appendedScripts, contentWindow, registerPlugin } = buildBootstrapIframe({
|
|
517
|
+
gsap: { version: "3.14.2", registerPlugin: vi.fn() },
|
|
518
|
+
});
|
|
519
|
+
const meta = document.createElement("meta");
|
|
520
|
+
meta.name = "hyperframes-gsap-base-url";
|
|
521
|
+
meta.content = "http://localhost:8080/vendor/npm";
|
|
522
|
+
iframe.contentDocument!.head.appendChild(meta);
|
|
523
|
+
ensureMotionPathPluginLoaded(iframe);
|
|
524
|
+
ensureMotionPathPluginLoaded(iframe);
|
|
525
|
+
expect(appendedScripts).toHaveLength(1);
|
|
526
|
+
expect(appendedScripts[0]!.src).toBe(
|
|
527
|
+
"http://localhost:8080/vendor/npm/gsap@3.14.2/dist/MotionPathPlugin.min.js",
|
|
528
|
+
);
|
|
529
|
+
appendedScripts[0]!.onerror?.(new Event("error"));
|
|
530
|
+
expect(contentWindow.__hfMotionPathPluginLoading).toBe(false);
|
|
531
|
+
expect(registerPlugin).not.toHaveBeenCalled();
|
|
532
|
+
expect(appendedScripts).toHaveLength(1);
|
|
533
|
+
});
|
|
534
|
+
|
|
493
535
|
it("appends the plugin script once and registers it on load", () => {
|
|
494
536
|
const { iframe, contentWindow, appendedScripts, registerPlugin } = buildBootstrapIframe();
|
|
495
537
|
ensureMotionPathPluginLoaded(iframe);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { COLOR_GRADING_SOURCE_HIDDEN_ATTR } from "@hyperframes/core/color-grading";
|
|
2
|
-
import { motionPathPluginUrl } from "@hyperframes/core/gsap-cdn";
|
|
2
|
+
import { GSAP_BASE_URL_META, motionPathPluginUrl } from "@hyperframes/core/gsap-cdn";
|
|
3
3
|
import { findAuthoredElement } from "./authoredSource";
|
|
4
4
|
import { applyAuthoredInlineOpacity, readStampedAuthoredOpacity } from "./authoredOpacity";
|
|
5
5
|
import {
|
|
@@ -50,6 +50,13 @@ type IframeWindow = Window & {
|
|
|
50
50
|
// At most one preview waits for its runtime; a newer load replaces it, so a frame that never boots is not kept alive.
|
|
51
51
|
let pendingReadyRetry: ((event: MessageEvent) => void) | null = null;
|
|
52
52
|
|
|
53
|
+
function previewMotionPathUrl(doc: Document, version?: string): string {
|
|
54
|
+
const baseUrl = doc.head.querySelector<HTMLMetaElement>(
|
|
55
|
+
`meta[name="${GSAP_BASE_URL_META}"]`,
|
|
56
|
+
)?.content;
|
|
57
|
+
return motionPathPluginUrl(version, baseUrl);
|
|
58
|
+
}
|
|
59
|
+
|
|
53
60
|
export function ensureMotionPathPluginLoaded(iframe: HTMLIFrameElement | null): void {
|
|
54
61
|
if (pendingReadyRetry) window.removeEventListener("message", pendingReadyRetry);
|
|
55
62
|
pendingReadyRetry = null;
|
|
@@ -86,7 +93,7 @@ export function ensureMotionPathPluginLoaded(iframe: HTMLIFrameElement | null):
|
|
|
86
93
|
try {
|
|
87
94
|
win.__hfMotionPathPluginLoading = true;
|
|
88
95
|
const pluginScript = doc.createElement("script");
|
|
89
|
-
pluginScript.src =
|
|
96
|
+
pluginScript.src = previewMotionPathUrl(doc, win.gsap?.version);
|
|
90
97
|
const finalize = () => {
|
|
91
98
|
win.__hfMotionPathPluginLoading = false;
|
|
92
99
|
try {
|
|
@@ -480,7 +487,7 @@ export function applySoftReload(
|
|
|
480
487
|
}
|
|
481
488
|
win.__hfMotionPathPluginLoading = true;
|
|
482
489
|
const pluginScript = doc.createElement("script");
|
|
483
|
-
pluginScript.src =
|
|
490
|
+
pluginScript.src = previewMotionPathUrl(doc, win.gsap?.version);
|
|
484
491
|
pluginScript.onload = () => {
|
|
485
492
|
win.__hfMotionPathPluginLoading = false;
|
|
486
493
|
executeScript();
|