@hyperframes/studio 0.8.25 → 0.8.26
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-CTP_00ix.js → hyperframes-player-C-CArn13.js} +1 -1
- package/dist/assets/{index-Bjb4YqS_.js → index-BIXyJgK5.js} +1 -1
- package/dist/assets/{index-DSk_ALFr.js → index-D5yni1t6.js} +144 -144
- package/dist/assets/{index-Di-8Jxqc.js → index-pfeP2Y63.js} +1 -1
- package/dist/index.html +1 -1
- package/dist/index.js +27 -11
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
- package/src/hooks/timelineTrackVisibility.test.ts +7 -1
- package/src/hooks/timelineTrackVisibility.ts +5 -5
- package/src/hooks/useDomEditCommits.test.tsx +36 -0
- package/src/hooks/useDomEditCommits.ts +24 -9
- package/src/player/hooks/timelineSyncHydration.ts +10 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperframes/studio",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.26",
|
|
4
4
|
"description": "",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -48,11 +48,11 @@
|
|
|
48
48
|
"gsap": "^3.13.0",
|
|
49
49
|
"marked": "^14.1.4",
|
|
50
50
|
"mediabunny": "^1.45.3",
|
|
51
|
-
"@hyperframes/
|
|
52
|
-
"@hyperframes/
|
|
53
|
-
"@hyperframes/
|
|
54
|
-
"@hyperframes/
|
|
55
|
-
"@hyperframes/
|
|
51
|
+
"@hyperframes/core": "0.8.26",
|
|
52
|
+
"@hyperframes/parsers": "0.8.26",
|
|
53
|
+
"@hyperframes/studio-server": "0.8.26",
|
|
54
|
+
"@hyperframes/sdk": "0.8.26",
|
|
55
|
+
"@hyperframes/player": "0.8.26"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
58
|
"@types/react": "19",
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
"vite": "^6.4.2",
|
|
70
70
|
"vitest": "^3.2.4",
|
|
71
71
|
"zustand": "^5.0.0",
|
|
72
|
-
"@hyperframes/producer": "0.8.
|
|
72
|
+
"@hyperframes/producer": "0.8.26"
|
|
73
73
|
},
|
|
74
74
|
"peerDependencies": {
|
|
75
75
|
"react": "19",
|
|
@@ -316,10 +316,15 @@ describe("toggleTimelineElementHidden", () => {
|
|
|
316
316
|
const iframe = document.createElement("iframe");
|
|
317
317
|
document.body.append(iframe);
|
|
318
318
|
const seek = vi.fn();
|
|
319
|
+
const forceTimelineRebind = vi.fn();
|
|
319
320
|
const win = iframe.contentWindow;
|
|
320
321
|
if (!win) throw new Error("Expected iframe contentWindow");
|
|
321
|
-
const playerWindow: Window & {
|
|
322
|
+
const playerWindow: Window & {
|
|
323
|
+
__player?: { seek?: (time: number) => void };
|
|
324
|
+
__hfForceTimelineRebind?: () => void;
|
|
325
|
+
} = win;
|
|
322
326
|
playerWindow.__player = { seek };
|
|
327
|
+
playerWindow.__hfForceTimelineRebind = forceTimelineRebind;
|
|
323
328
|
|
|
324
329
|
const files = new Map([
|
|
325
330
|
[
|
|
@@ -369,6 +374,7 @@ describe("toggleTimelineElementHidden", () => {
|
|
|
369
374
|
expect(recordEdit).toHaveBeenCalledTimes(1);
|
|
370
375
|
expect(recordEdit.mock.calls[0]?.[0]?.label).toBe("Hide element");
|
|
371
376
|
expect(seek).toHaveBeenCalledWith(1.25);
|
|
377
|
+
expect(forceTimelineRebind).toHaveBeenCalledTimes(1);
|
|
372
378
|
expect(
|
|
373
379
|
usePlayerStore.getState().elements.find((el) => el.key === "index.html:#hero")?.hidden,
|
|
374
380
|
).toBe(true);
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { useCallback } from "react";
|
|
2
2
|
import { usePlayerStore, type TimelineElement } from "../player";
|
|
3
|
+
import { reseekPreviewAtTime } from "../player/hooks/timelineSyncHydration";
|
|
3
4
|
import { useExpandedTimelineElements } from "../player/hooks/useExpandedTimelineElements";
|
|
5
|
+
import { applySoftReloadFinalization } from "../utils/gsapSoftReload";
|
|
4
6
|
import {
|
|
5
7
|
timelineTrackOrder,
|
|
6
8
|
trackDisplayNumber,
|
|
@@ -106,11 +108,9 @@ function patchLiveHiddenState(
|
|
|
106
108
|
}
|
|
107
109
|
|
|
108
110
|
export function reseekPreviewRuntime(iframe: HTMLIFrameElement | null): void {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
win?.__player?.seek?.(usePlayerStore.getState().currentTime);
|
|
113
|
-
} catch {}
|
|
111
|
+
const store = usePlayerStore.getState();
|
|
112
|
+
if (applySoftReloadFinalization(iframe, store.currentTime)) return;
|
|
113
|
+
reseekPreviewAtTime({ seek: store.requestSeek }, store.currentTime);
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
export function groupElementsByTargetPath(
|
|
@@ -981,6 +981,42 @@ describe("useDomEditCommits style persist handling", () => {
|
|
|
981
981
|
}
|
|
982
982
|
});
|
|
983
983
|
|
|
984
|
+
it("rebinds the paused preview after a saved optimistic style commit", async () => {
|
|
985
|
+
stubPatchFetch({
|
|
986
|
+
ok: true,
|
|
987
|
+
changed: true,
|
|
988
|
+
matched: true,
|
|
989
|
+
path: "index.html",
|
|
990
|
+
version: '"sha256:changed"',
|
|
991
|
+
});
|
|
992
|
+
const { iframe, element } = createPreviewElement();
|
|
993
|
+
const seek = vi.fn();
|
|
994
|
+
const forceTimelineRebind = vi.fn();
|
|
995
|
+
Object.defineProperty(iframe.contentWindow, "__player", {
|
|
996
|
+
configurable: true,
|
|
997
|
+
value: { seek },
|
|
998
|
+
});
|
|
999
|
+
Object.defineProperty(iframe.contentWindow, "__hfForceTimelineRebind", {
|
|
1000
|
+
configurable: true,
|
|
1001
|
+
value: forceTimelineRebind,
|
|
1002
|
+
});
|
|
1003
|
+
usePlayerStore.setState({ currentTime: 2.4 });
|
|
1004
|
+
const rendered = renderDomEditCommits(createSelection(element), iframe);
|
|
1005
|
+
|
|
1006
|
+
try {
|
|
1007
|
+
await act(async () => {
|
|
1008
|
+
await rendered.hook.handleDomStyleCommit("color", "blue");
|
|
1009
|
+
});
|
|
1010
|
+
|
|
1011
|
+
expect(seek).toHaveBeenCalledWith(2.4);
|
|
1012
|
+
expect(forceTimelineRebind).toHaveBeenCalledTimes(1);
|
|
1013
|
+
expect(rendered.reloadPreview).not.toHaveBeenCalled();
|
|
1014
|
+
} finally {
|
|
1015
|
+
rendered.cleanup();
|
|
1016
|
+
usePlayerStore.getState().reset();
|
|
1017
|
+
}
|
|
1018
|
+
});
|
|
1019
|
+
|
|
984
1020
|
it("toasts and reverts a style commit when the patch request rejects", async () => {
|
|
985
1021
|
const { element, rendered, cleanup } = await commitStyleAgainst(new Error("network down"));
|
|
986
1022
|
|
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
} from "./useDomEditCommitsHelpers";
|
|
35
35
|
import type { CutoverResult } from "../utils/sdkCutover";
|
|
36
36
|
import { studioWriteHeaders } from "../utils/studioFileVersion";
|
|
37
|
+
import { reseekPreviewRuntime } from "./timelineTrackVisibility";
|
|
37
38
|
interface RecordEditInput {
|
|
38
39
|
label: string;
|
|
39
40
|
kind: EditHistoryKind;
|
|
@@ -154,6 +155,10 @@ export function useDomEditCommits({
|
|
|
154
155
|
if (options?.shouldSave && !options.shouldSave()) return;
|
|
155
156
|
|
|
156
157
|
const targetPath = selection.sourceFile || activeCompPath || "index.html";
|
|
158
|
+
const completePersistence = <T>(result: T, changed: boolean): T => {
|
|
159
|
+
if (options?.skipRefresh && changed) reseekPreviewRuntime(previewIframeRef.current);
|
|
160
|
+
return result;
|
|
161
|
+
};
|
|
157
162
|
|
|
158
163
|
const readResponse = await fetch(
|
|
159
164
|
`/api/projects/${pid}/files/${encodeURIComponent(targetPath)}`,
|
|
@@ -201,7 +206,10 @@ export function useDomEditCommits({
|
|
|
201
206
|
if (cutover.status === "committed") {
|
|
202
207
|
// SDK handled it — its in-memory doc is already current, so do NOT
|
|
203
208
|
// forceReload (that would echo-reload the session we just wrote).
|
|
204
|
-
return
|
|
209
|
+
return completePersistence(
|
|
210
|
+
{ sourceFile: targetPath, version: cutover.version, changed: true },
|
|
211
|
+
true,
|
|
212
|
+
);
|
|
205
213
|
}
|
|
206
214
|
}
|
|
207
215
|
|
|
@@ -249,9 +257,12 @@ export function useDomEditCommits({
|
|
|
249
257
|
throw new DomEditPersistUnresolvableError(targetPath);
|
|
250
258
|
}
|
|
251
259
|
warnDomEditPersistNoOp(selection, operations);
|
|
252
|
-
return
|
|
253
|
-
|
|
254
|
-
|
|
260
|
+
return completePersistence(
|
|
261
|
+
typeof patchData.path === "string" && typeof patchData.version === "string"
|
|
262
|
+
? { sourceFile: patchData.path, version: patchData.version, changed: false }
|
|
263
|
+
: undefined,
|
|
264
|
+
false,
|
|
265
|
+
);
|
|
255
266
|
}
|
|
256
267
|
|
|
257
268
|
const patchedContent =
|
|
@@ -289,11 +300,14 @@ export function useDomEditCommits({
|
|
|
289
300
|
if (!options?.skipRefresh) {
|
|
290
301
|
reloadPreview();
|
|
291
302
|
}
|
|
292
|
-
return
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
303
|
+
return completePersistence(
|
|
304
|
+
finalContent === patchedContent &&
|
|
305
|
+
typeof patchData.path === "string" &&
|
|
306
|
+
typeof patchData.version === "string"
|
|
307
|
+
? { sourceFile: patchData.path, version: patchData.version, changed: true }
|
|
308
|
+
: undefined,
|
|
309
|
+
true,
|
|
310
|
+
);
|
|
297
311
|
},
|
|
298
312
|
[
|
|
299
313
|
activeCompPath,
|
|
@@ -305,6 +319,7 @@ export function useDomEditCommits({
|
|
|
305
319
|
showToast,
|
|
306
320
|
forceReloadSdkSession,
|
|
307
321
|
onTrySdkPersist,
|
|
322
|
+
previewIframeRef,
|
|
308
323
|
],
|
|
309
324
|
);
|
|
310
325
|
|
|
@@ -290,6 +290,15 @@ export function resolveReloadSeekTime(input: {
|
|
|
290
290
|
return Math.min(target, input.duration);
|
|
291
291
|
}
|
|
292
292
|
|
|
293
|
+
type SeekablePreview = Pick<PlaybackAdapter, "seek">;
|
|
294
|
+
|
|
295
|
+
/** Re-run the current frame even when the runtime already reports that time. */
|
|
296
|
+
export function reseekPreviewAtTime(preview: SeekablePreview, time: number): void {
|
|
297
|
+
const target = Number.isFinite(time) && time > 0 ? time : 0;
|
|
298
|
+
preview.seek(target > 0.001 ? Math.max(0, target - 0.001) : 0.001);
|
|
299
|
+
preview.seek(target);
|
|
300
|
+
}
|
|
301
|
+
|
|
293
302
|
export function seekAdapterToRestorePoint(
|
|
294
303
|
adapter: PlaybackAdapter,
|
|
295
304
|
pendingSeekRef: { current: number | null },
|
|
@@ -303,8 +312,7 @@ export function seekAdapterToRestorePoint(
|
|
|
303
312
|
});
|
|
304
313
|
pendingSeekRef.current = null;
|
|
305
314
|
if (storeSeek != null) usePlayerStore.getState().clearSeekRequest();
|
|
306
|
-
adapter
|
|
307
|
-
adapter.seek(startTime);
|
|
315
|
+
reseekPreviewAtTime(adapter, startTime);
|
|
308
316
|
return startTime;
|
|
309
317
|
}
|
|
310
318
|
|