@hyperframes/studio 0.8.24 → 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-CTQCs_KG.js → hyperframes-player-C-CArn13.js} +1 -1
- package/dist/assets/{index-okdE01ky.js → index-BIXyJgK5.js} +1 -1
- package/dist/assets/{index-Bq3M0sjr.js → index-D5yni1t6.js} +218 -218
- package/dist/assets/index-DnRfAiK2.css +1 -0
- package/dist/assets/{index-9WJFcXTP.js → index-pfeP2Y63.js} +1 -1
- package/dist/{chunk-6BT6DTB4.js → chunk-A7S5SGNA.js} +2 -2
- package/dist/chunk-A7S5SGNA.js.map +1 -0
- package/dist/{domEditingLayers-URA7BLWE.js → domEditingLayers-JMMVOGTO.js} +2 -2
- package/dist/index.d.ts +4 -0
- package/dist/index.html +2 -2
- package/dist/index.js +6230 -5019
- package/dist/index.js.map +1 -1
- package/package.json +8 -7
- package/src/components/editor/TopologyLens.test.tsx +294 -0
- package/src/components/editor/TopologyLens.tsx +206 -0
- package/src/components/editor/domEditOverlayGeometry.ts +14 -0
- package/src/components/editor/domEditOverlayGeometryBatch.test.ts +18 -0
- package/src/components/editor/domEditingLayers.test.ts +9 -0
- package/src/components/editor/domEditingLayers.ts +8 -8
- package/src/components/editor/topologyLensGeometry.test.ts +44 -0
- package/src/components/editor/topologyLensGeometry.ts +119 -0
- package/src/components/editor/topologyLensState.test.ts +116 -0
- package/src/components/editor/topologyLensState.ts +72 -0
- package/src/components/nle/PreviewOverlays.topologyLens.test.tsx +126 -0
- package/src/components/nle/PreviewOverlays.tsx +24 -18
- package/src/components/sidebar/CompositionsTab.drag.test.tsx +16 -0
- package/src/components/sidebar/CompositionsTab.tsx +10 -3
- package/src/components/ui/HyperframesLoader.tsx +3 -48
- package/src/components/ui/HyperframesMark.tsx +53 -0
- package/src/contexts/DomEditContext.tsx +12 -0
- package/src/hooks/domEditCommitRunner.test.ts +59 -0
- package/src/hooks/domEditCommitRunner.ts +55 -20
- package/src/hooks/domEditCommitTypes.ts +8 -1
- package/src/hooks/domEditTextCommitPlan.ts +47 -0
- package/src/hooks/timelineTrackVisibility.test.ts +7 -1
- package/src/hooks/timelineTrackVisibility.ts +5 -5
- package/src/hooks/useDomEditAttributeCommits.ts +4 -1
- package/src/hooks/useDomEditCommits.test.tsx +243 -8
- package/src/hooks/useDomEditCommits.ts +69 -14
- package/src/hooks/useDomEditPositionPatchCommit.test.tsx +3 -4
- package/src/hooks/useDomEditPositionPatchCommit.ts +27 -29
- package/src/hooks/useDomEditSession.ts +6 -2
- package/src/hooks/useDomEditTextCommits.test.tsx +86 -2
- package/src/hooks/useDomEditTextCommits.ts +64 -86
- package/src/hooks/useDomEditWiring.ts +1 -1
- package/src/hooks/useDomSelection.ts +2 -0
- package/src/hooks/useDomSelectionTypes.ts +2 -0
- package/src/hooks/useExternalFileChangeCoordinator.test.tsx +71 -4
- package/src/hooks/useExternalFileChangeCoordinator.ts +18 -9
- package/src/hooks/useGsapAnimationFetchFallback.test.ts +13 -1
- package/src/hooks/useGsapAnimationFetchFallback.ts +7 -3
- package/src/hooks/useGsapAnimationOps.test.tsx +101 -0
- package/src/hooks/useGsapAnimationOps.ts +4 -4
- package/src/hooks/useGsapAwareEditing.test.tsx +30 -2
- package/src/hooks/useGsapAwareEditing.ts +31 -10
- package/src/hooks/useGsapSelectionHandlers.test.tsx +3 -1
- package/src/hooks/useGsapSelectionHandlers.ts +21 -11
- package/src/hooks/useRenderClipContent.test.ts +27 -0
- package/src/hooks/useRenderClipContent.ts +5 -0
- package/src/hooks/useStudioExternalFileChanges.ts +5 -0
- package/src/player/components/CompositionThumbnail.test.ts +66 -1
- package/src/player/components/CompositionThumbnail.tsx +6 -0
- package/src/player/hooks/timelineSyncHydration.ts +10 -2
- package/src/player/store/playerStore.test.ts +24 -0
- package/src/player/store/thumbnailSlice.ts +6 -0
- package/src/styles/studio.css +179 -0
- package/src/utils/domEditSaveQueue.test.ts +5 -3
- package/src/utils/domEditSaveQueue.ts +6 -1
- package/src/webmcp/StudioAgentTools.test.ts +70 -0
- package/src/webmcp/StudioAgentTools.tsx +89 -21
- package/src/webmcp/handles.test.ts +172 -15
- package/src/webmcp/handles.ts +152 -35
- package/src/webmcp/tools/animationTools.test.ts +241 -60
- package/src/webmcp/tools/animationTools.ts +191 -104
- package/src/webmcp/tools/contentTools.test.ts +103 -41
- package/src/webmcp/tools/contentTools.ts +149 -105
- package/src/webmcp/tools/inspectTools.test.ts +47 -5
- package/src/webmcp/tools/inspectTools.ts +59 -19
- package/src/webmcp/tools/lookTools.test.ts +185 -38
- package/src/webmcp/tools/lookTools.ts +134 -21
- package/src/webmcp/tools/selectionTools.test.ts +105 -9
- package/src/webmcp/tools/selectionTools.ts +34 -19
- package/src/webmcp/tools/targetedWriteTools.test.ts +125 -0
- package/src/webmcp/tools/transformTools.test.ts +140 -54
- package/src/webmcp/tools/transformTools.ts +215 -56
- package/src/webmcp/useStudioAgentTools.test.tsx +310 -10
- package/src/webmcp/useStudioAgentTools.ts +27 -15
- package/src/webmcp/webmcpTestUtils.ts +37 -0
- package/src/webmcp/writeCoordinator.test.ts +375 -0
- package/src/webmcp/writeCoordinator.ts +473 -0
- package/dist/assets/index-yGhfxxoL.css +0 -1
- package/dist/chunk-6BT6DTB4.js.map +0 -1
- /package/dist/{domEditingLayers-URA7BLWE.js.map → domEditingLayers-JMMVOGTO.js.map} +0 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { useId, type SVGProps } from "react";
|
|
2
|
+
|
|
3
|
+
export function HyperframesMark({ viewBox = "0 0 100 100", ...props }: SVGProps<SVGSVGElement>) {
|
|
4
|
+
const gradientId = useId();
|
|
5
|
+
const leftGradient = `${gradientId}-left`;
|
|
6
|
+
const rightGradient = `${gradientId}-right`;
|
|
7
|
+
return (
|
|
8
|
+
<svg
|
|
9
|
+
viewBox={viewBox}
|
|
10
|
+
fill="none"
|
|
11
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
12
|
+
aria-hidden="true"
|
|
13
|
+
{...props}
|
|
14
|
+
>
|
|
15
|
+
<g transform="translate(50 50)" opacity=".92">
|
|
16
|
+
<g transform="translate(-50 -50)">
|
|
17
|
+
<path
|
|
18
|
+
d="M10.1851 57.8021L33.1145 73.8313C36.2202 75.9978 41.5173 73.5433 42.4816 69.4984L51.7611 30.4271C52.7253 26.3822 48.5802 23.9277 44.4602 26.0942L13.917 42.1235C6.96677 45.7676 4.97564 54.1579 10.1851 57.8021Z"
|
|
19
|
+
fill={`url(#${leftGradient})`}
|
|
20
|
+
/>
|
|
21
|
+
<path
|
|
22
|
+
d="M87.5129 57.5141L56.9696 73.5433C52.8371 75.7098 48.7046 73.2553 49.6688 69.2104L58.9483 30.1391C59.9125 26.0942 65.2097 23.6397 68.3154 25.8062L91.2447 41.8354C96.4668 45.4796 94.4631 53.8699 87.5129 57.5141Z"
|
|
23
|
+
fill={`url(#${rightGradient})`}
|
|
24
|
+
/>
|
|
25
|
+
</g>
|
|
26
|
+
</g>
|
|
27
|
+
<defs>
|
|
28
|
+
<linearGradient
|
|
29
|
+
id={leftGradient}
|
|
30
|
+
x1="48.5676"
|
|
31
|
+
y1="25"
|
|
32
|
+
x2="44.7804"
|
|
33
|
+
y2="71.9384"
|
|
34
|
+
gradientUnits="userSpaceOnUse"
|
|
35
|
+
>
|
|
36
|
+
<stop stopColor="#06E3FA" />
|
|
37
|
+
<stop offset="1" stopColor="#4FDB5E" />
|
|
38
|
+
</linearGradient>
|
|
39
|
+
<linearGradient
|
|
40
|
+
id={rightGradient}
|
|
41
|
+
x1="54.8282"
|
|
42
|
+
y1="73.8392"
|
|
43
|
+
x2="72.0989"
|
|
44
|
+
y2="32.8932"
|
|
45
|
+
gradientUnits="userSpaceOnUse"
|
|
46
|
+
>
|
|
47
|
+
<stop stopColor="#06E3FA" />
|
|
48
|
+
<stop offset="1" stopColor="#4FDB5E" />
|
|
49
|
+
</linearGradient>
|
|
50
|
+
</defs>
|
|
51
|
+
</svg>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
@@ -14,6 +14,7 @@ export interface DomEditActionsValue extends Pick<
|
|
|
14
14
|
| "applyDomSelection"
|
|
15
15
|
| "clearDomSelection"
|
|
16
16
|
| "handleDomStyleCommit"
|
|
17
|
+
| "handleDomStyleCommitForSelection"
|
|
17
18
|
| "handleDomAttributeCommit"
|
|
18
19
|
| "handleDomAttributeLiveCommit"
|
|
19
20
|
| "handleDomAttributeQuietCommit"
|
|
@@ -26,10 +27,12 @@ export interface DomEditActionsValue extends Pick<
|
|
|
26
27
|
| "handleDomRotationCommit"
|
|
27
28
|
| "handleDomManualEditsReset"
|
|
28
29
|
| "handleDomTextCommit"
|
|
30
|
+
| "handleDomTextCommitForSelection"
|
|
29
31
|
| "handleDomRichTextCommit"
|
|
30
32
|
| "handleDomTextFieldStyleCommit"
|
|
31
33
|
| "handleDomAddTextField"
|
|
32
34
|
| "handleDomRemoveTextField"
|
|
35
|
+
| "getGsapAnimationsForSelection"
|
|
33
36
|
| "handleAskAgent"
|
|
34
37
|
| "handleAgentModalSubmit"
|
|
35
38
|
| "handleBlockedDomMove"
|
|
@@ -153,6 +156,7 @@ export function DomEditProvider({
|
|
|
153
156
|
applyDomSelection,
|
|
154
157
|
clearDomSelection,
|
|
155
158
|
handleDomStyleCommit,
|
|
159
|
+
handleDomStyleCommitForSelection,
|
|
156
160
|
handleDomAttributeCommit,
|
|
157
161
|
handleDomAttributeLiveCommit,
|
|
158
162
|
handleDomAttributeQuietCommit,
|
|
@@ -166,10 +170,12 @@ export function DomEditProvider({
|
|
|
166
170
|
handleDomManualEditsReset,
|
|
167
171
|
|
|
168
172
|
handleDomTextCommit,
|
|
173
|
+
handleDomTextCommitForSelection,
|
|
169
174
|
handleDomRichTextCommit,
|
|
170
175
|
handleDomTextFieldStyleCommit,
|
|
171
176
|
handleDomAddTextField,
|
|
172
177
|
handleDomRemoveTextField,
|
|
178
|
+
getGsapAnimationsForSelection,
|
|
173
179
|
handleAskAgent,
|
|
174
180
|
handleAgentModalSubmit,
|
|
175
181
|
handleBlockedDomMove,
|
|
@@ -243,6 +249,7 @@ export function DomEditProvider({
|
|
|
243
249
|
applyDomSelection,
|
|
244
250
|
clearDomSelection,
|
|
245
251
|
handleDomStyleCommit,
|
|
252
|
+
handleDomStyleCommitForSelection,
|
|
246
253
|
handleDomAttributeCommit,
|
|
247
254
|
handleDomAttributeLiveCommit,
|
|
248
255
|
handleDomAttributeQuietCommit,
|
|
@@ -255,10 +262,12 @@ export function DomEditProvider({
|
|
|
255
262
|
handleDomRotationCommit,
|
|
256
263
|
handleDomManualEditsReset,
|
|
257
264
|
handleDomTextCommit,
|
|
265
|
+
handleDomTextCommitForSelection,
|
|
258
266
|
handleDomRichTextCommit,
|
|
259
267
|
handleDomTextFieldStyleCommit,
|
|
260
268
|
handleDomAddTextField,
|
|
261
269
|
handleDomRemoveTextField,
|
|
270
|
+
getGsapAnimationsForSelection,
|
|
262
271
|
handleAskAgent,
|
|
263
272
|
handleAgentModalSubmit,
|
|
264
273
|
handleBlockedDomMove,
|
|
@@ -314,6 +323,7 @@ export function DomEditProvider({
|
|
|
314
323
|
applyDomSelection,
|
|
315
324
|
clearDomSelection,
|
|
316
325
|
handleDomStyleCommit,
|
|
326
|
+
handleDomStyleCommitForSelection,
|
|
317
327
|
handleDomAttributeCommit,
|
|
318
328
|
handleDomAttributeLiveCommit,
|
|
319
329
|
handleDomAttributeQuietCommit,
|
|
@@ -326,10 +336,12 @@ export function DomEditProvider({
|
|
|
326
336
|
handleDomRotationCommit,
|
|
327
337
|
handleDomManualEditsReset,
|
|
328
338
|
handleDomTextCommit,
|
|
339
|
+
handleDomTextCommitForSelection,
|
|
329
340
|
handleDomRichTextCommit,
|
|
330
341
|
handleDomTextFieldStyleCommit,
|
|
331
342
|
handleDomAddTextField,
|
|
332
343
|
handleDomRemoveTextField,
|
|
344
|
+
getGsapAnimationsForSelection,
|
|
333
345
|
handleAskAgent,
|
|
334
346
|
handleAgentModalSubmit,
|
|
335
347
|
handleBlockedDomMove,
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { bumpDomEditCommitMapVersion, runDomEditCommit } from "./domEditCommitRunner";
|
|
3
|
+
|
|
4
|
+
function commitConfig(overrides: Partial<Parameters<typeof runDomEditCommit>[0]> = {}) {
|
|
5
|
+
return {
|
|
6
|
+
capture: vi.fn(),
|
|
7
|
+
apply: vi.fn(),
|
|
8
|
+
persist: vi.fn(async () => undefined),
|
|
9
|
+
shouldRevert: vi.fn(() => true),
|
|
10
|
+
revert: vi.fn(),
|
|
11
|
+
onError: vi.fn(),
|
|
12
|
+
shouldResync: vi.fn(() => false),
|
|
13
|
+
resync: vi.fn(),
|
|
14
|
+
...overrides,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
describe("DOM edit commit version cleanup", () => {
|
|
19
|
+
it("releases a settled target without deleting a newer target version", () => {
|
|
20
|
+
const versions = new Map<string, symbol>();
|
|
21
|
+
const first = bumpDomEditCommitMapVersion(versions, "headline");
|
|
22
|
+
const second = bumpDomEditCommitMapVersion(versions, "headline");
|
|
23
|
+
|
|
24
|
+
first.release();
|
|
25
|
+
expect(versions.size).toBe(1);
|
|
26
|
+
expect(second()).toBe(true);
|
|
27
|
+
|
|
28
|
+
second.release();
|
|
29
|
+
expect(versions.size).toBe(0);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("never lets an old commit become latest again after cleanup and reuse", () => {
|
|
33
|
+
const versions = new Map<string, symbol>();
|
|
34
|
+
const first = bumpDomEditCommitMapVersion(versions, "headline");
|
|
35
|
+
const second = bumpDomEditCommitMapVersion(versions, "headline");
|
|
36
|
+
second.release();
|
|
37
|
+
|
|
38
|
+
const third = bumpDomEditCommitMapVersion(versions, "headline");
|
|
39
|
+
|
|
40
|
+
expect(first()).toBe(false);
|
|
41
|
+
expect(third()).toBe(true);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("runs settlement cleanup even when capture throws", async () => {
|
|
45
|
+
const onFinally = vi.fn();
|
|
46
|
+
await expect(
|
|
47
|
+
runDomEditCommit(
|
|
48
|
+
commitConfig({
|
|
49
|
+
capture: () => {
|
|
50
|
+
throw new Error("capture failed");
|
|
51
|
+
},
|
|
52
|
+
onFinally,
|
|
53
|
+
}),
|
|
54
|
+
),
|
|
55
|
+
).rejects.toThrow("capture failed");
|
|
56
|
+
|
|
57
|
+
expect(onFinally).toHaveBeenCalledOnce();
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import type { DomEditPersistOutcome } from "./domEditCommitTypes";
|
|
2
|
+
|
|
1
3
|
interface DomEditCommitRunnerConfig {
|
|
2
4
|
capture: () => void;
|
|
3
5
|
apply: () => void;
|
|
4
|
-
persist: () => Promise<
|
|
6
|
+
persist: () => Promise<unknown>;
|
|
5
7
|
shouldRevert: (error: unknown) => boolean;
|
|
6
8
|
revert: () => void;
|
|
7
9
|
onError: (error: unknown) => void;
|
|
@@ -16,6 +18,8 @@ interface DomEditCommitRunnerConfig {
|
|
|
16
18
|
* instead of relying on a rejection that will never come.
|
|
17
19
|
*/
|
|
18
20
|
onSettled?: (ok: boolean) => void;
|
|
21
|
+
/** Always runs once after persistence, resync, or an unexpected runner error. */
|
|
22
|
+
onFinally?: () => void;
|
|
19
23
|
}
|
|
20
24
|
|
|
21
25
|
interface CommitVersionRef {
|
|
@@ -28,32 +32,46 @@ export function bumpDomEditCommitVersion(versionRef: CommitVersionRef): () => bo
|
|
|
28
32
|
return () => versionRef.current === commitVersion;
|
|
29
33
|
}
|
|
30
34
|
|
|
35
|
+
export interface DomEditCommitVersionGuard {
|
|
36
|
+
(): boolean;
|
|
37
|
+
release: () => void;
|
|
38
|
+
}
|
|
39
|
+
|
|
31
40
|
export function bumpDomEditCommitMapVersion<TKey>(
|
|
32
|
-
versionMap: Map<TKey,
|
|
41
|
+
versionMap: Map<TKey, symbol>,
|
|
33
42
|
versionKey: TKey,
|
|
34
|
-
):
|
|
35
|
-
const commitVersion = (
|
|
43
|
+
): DomEditCommitVersionGuard {
|
|
44
|
+
const commitVersion = Symbol("dom-edit-commit");
|
|
36
45
|
versionMap.set(versionKey, commitVersion);
|
|
37
|
-
|
|
46
|
+
const isLatest = () => versionMap.get(versionKey) === commitVersion;
|
|
47
|
+
return Object.assign(isLatest, {
|
|
48
|
+
release: () => {
|
|
49
|
+
if (isLatest()) versionMap.delete(versionKey);
|
|
50
|
+
},
|
|
51
|
+
});
|
|
38
52
|
}
|
|
39
53
|
|
|
40
54
|
export async function runDomEditCommit(config: DomEditCommitRunnerConfig): Promise<void> {
|
|
41
|
-
config.capture();
|
|
42
|
-
config.apply();
|
|
43
|
-
|
|
44
55
|
try {
|
|
45
|
-
|
|
46
|
-
config.
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
config.
|
|
56
|
+
config.capture();
|
|
57
|
+
config.apply();
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
await config.persist();
|
|
61
|
+
config.onSettled?.(true);
|
|
62
|
+
} catch (error) {
|
|
63
|
+
if (config.shouldRevert(error)) {
|
|
64
|
+
config.revert();
|
|
65
|
+
}
|
|
66
|
+
config.onError(error);
|
|
67
|
+
config.onSettled?.(false);
|
|
50
68
|
}
|
|
51
|
-
config.onError(error);
|
|
52
|
-
config.onSettled?.(false);
|
|
53
|
-
}
|
|
54
69
|
|
|
55
|
-
|
|
56
|
-
|
|
70
|
+
if (!config.shouldResync()) return;
|
|
71
|
+
await config.resync();
|
|
72
|
+
} finally {
|
|
73
|
+
config.onFinally?.();
|
|
74
|
+
}
|
|
57
75
|
}
|
|
58
76
|
|
|
59
77
|
/**
|
|
@@ -75,7 +93,9 @@ export type DomEditCommitDeclineReason =
|
|
|
75
93
|
| "preview-stale"
|
|
76
94
|
| "persist-failed";
|
|
77
95
|
|
|
78
|
-
export type DomEditCommitOutcome =
|
|
96
|
+
export type DomEditCommitOutcome =
|
|
97
|
+
| { ok: true; persistence?: DomEditPersistOutcome }
|
|
98
|
+
| { ok: false; reason: DomEditCommitDeclineReason };
|
|
79
99
|
|
|
80
100
|
export function domEditCommitDeclined(reason: DomEditCommitDeclineReason): DomEditCommitOutcome {
|
|
81
101
|
return { ok: false, reason };
|
|
@@ -93,12 +113,27 @@ export async function runReportedDomEditCommit(
|
|
|
93
113
|
config: DomEditCommitRunnerConfig,
|
|
94
114
|
): Promise<DomEditCommitOutcome> {
|
|
95
115
|
let landed = false;
|
|
116
|
+
let persistence: DomEditPersistOutcome | undefined;
|
|
96
117
|
await runDomEditCommit({
|
|
97
118
|
...config,
|
|
119
|
+
persist: async () => {
|
|
120
|
+
const result = await config.persist();
|
|
121
|
+
if (isDomEditPersistOutcome(result)) persistence = result;
|
|
122
|
+
},
|
|
98
123
|
onSettled: (ok) => {
|
|
99
124
|
landed = ok;
|
|
100
125
|
config.onSettled?.(ok);
|
|
101
126
|
},
|
|
102
127
|
});
|
|
103
|
-
|
|
128
|
+
if (!landed) return domEditCommitDeclined("persist-failed");
|
|
129
|
+
return persistence ? { ok: true, persistence } : { ok: true };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function isDomEditPersistOutcome(value: unknown): value is DomEditPersistOutcome {
|
|
133
|
+
if (typeof value !== "object" || value === null) return false;
|
|
134
|
+
return (
|
|
135
|
+
typeof Reflect.get(value, "sourceFile") === "string" &&
|
|
136
|
+
typeof Reflect.get(value, "version") === "string" &&
|
|
137
|
+
typeof Reflect.get(value, "changed") === "boolean"
|
|
138
|
+
);
|
|
104
139
|
}
|
|
@@ -6,6 +6,13 @@ export interface DomEditPatchBatch {
|
|
|
6
6
|
patches: Array<{ target: PatchTarget; operations: PatchOperation[] }>;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
+
/** Durable evidence returned by the one Studio DOM persistence actor. */
|
|
10
|
+
export interface DomEditPersistOutcome {
|
|
11
|
+
sourceFile: string;
|
|
12
|
+
version: string;
|
|
13
|
+
changed: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
9
16
|
export type CommitDomEditPatchBatches = (
|
|
10
17
|
batches: DomEditPatchBatch[],
|
|
11
18
|
options: {
|
|
@@ -49,4 +56,4 @@ export type PersistDomEditOperations = (
|
|
|
49
56
|
prepareContent?: (html: string, sourceFile: string) => string;
|
|
50
57
|
shouldSave?: () => boolean;
|
|
51
58
|
},
|
|
52
|
-
) => Promise<
|
|
59
|
+
) => Promise<DomEditPersistOutcome | undefined>;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildDomEditRichTextPatchOperation,
|
|
3
|
+
buildDomEditTextPatchOperation,
|
|
4
|
+
serializeDomEditTextFields,
|
|
5
|
+
type DomEditTextField,
|
|
6
|
+
} from "../components/editor/domEditing";
|
|
7
|
+
import type { PatchOperation } from "../utils/sourcePatcher";
|
|
8
|
+
import { buildTextFieldChildOperations } from "./domEditTextFieldCommitOps";
|
|
9
|
+
|
|
10
|
+
interface DomTextCommitPlan {
|
|
11
|
+
usesSerializedTextFields: boolean;
|
|
12
|
+
nextContent: string;
|
|
13
|
+
operations: PatchOperation[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function buildNextDomTextFields(
|
|
17
|
+
textFields: DomEditTextField[],
|
|
18
|
+
value: string,
|
|
19
|
+
fieldKey?: string,
|
|
20
|
+
): DomEditTextField[] {
|
|
21
|
+
if (textFields.length === 0) return [];
|
|
22
|
+
return textFields.map((field) => (field.key === fieldKey ? { ...field, value } : field));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function planDomTextCommit(
|
|
26
|
+
originalTextFields: DomEditTextField[],
|
|
27
|
+
nextTextFields: DomEditTextField[],
|
|
28
|
+
plainTextContent: string,
|
|
29
|
+
): DomTextCommitPlan {
|
|
30
|
+
const usesSerializedTextFields =
|
|
31
|
+
nextTextFields.length > 1 || nextTextFields.some((field) => field.source === "child");
|
|
32
|
+
const nextContent = usesSerializedTextFields
|
|
33
|
+
? serializeDomEditTextFields(nextTextFields)
|
|
34
|
+
: plainTextContent;
|
|
35
|
+
const childOperations = usesSerializedTextFields
|
|
36
|
+
? buildTextFieldChildOperations(originalTextFields, nextTextFields)
|
|
37
|
+
: null;
|
|
38
|
+
// Per-child operations when the layers still line up one-for-one. Structure
|
|
39
|
+
// changes write the element's markup because no stable child address exists.
|
|
40
|
+
const operations =
|
|
41
|
+
childOperations ??
|
|
42
|
+
(usesSerializedTextFields
|
|
43
|
+
? [buildDomEditRichTextPatchOperation(nextContent)]
|
|
44
|
+
: [buildDomEditTextPatchOperation(nextContent)]);
|
|
45
|
+
|
|
46
|
+
return { usesSerializedTextFields, nextContent, operations };
|
|
47
|
+
}
|
|
@@ -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(
|
|
@@ -135,7 +135,7 @@ export function useDomEditAttributeCommits({
|
|
|
135
135
|
refreshDomEditSelectionFromPreview,
|
|
136
136
|
persistDomEditOperations,
|
|
137
137
|
}: UseDomEditAttributeCommitsParams) {
|
|
138
|
-
const domAttributeCommitVersionRef = useRef(new Map<string,
|
|
138
|
+
const domAttributeCommitVersionRef = useRef(new Map<string, symbol>());
|
|
139
139
|
|
|
140
140
|
const commitDataAttribute = useCallback(
|
|
141
141
|
async (attr: string, value: string | null, options: DataAttributeCommitOptions) => {
|
|
@@ -200,6 +200,7 @@ export function useDomEditAttributeCommits({
|
|
|
200
200
|
syncStoredAutomationFromPreview(previewIframeRef.current?.contentDocument ?? null);
|
|
201
201
|
},
|
|
202
202
|
onSettled: options.onSettled,
|
|
203
|
+
onFinally: isLatestCommit.release,
|
|
203
204
|
});
|
|
204
205
|
},
|
|
205
206
|
[
|
|
@@ -288,6 +289,7 @@ export function useDomEditAttributeCommits({
|
|
|
288
289
|
shouldResync: () => isLatestCommit() && !!options.refreshAfter,
|
|
289
290
|
resync: () => refreshDomEditSelectionFromPreview(selection),
|
|
290
291
|
onSettled: options.onSettled,
|
|
292
|
+
onFinally: isLatestCommit.release,
|
|
291
293
|
});
|
|
292
294
|
},
|
|
293
295
|
[
|
|
@@ -420,6 +422,7 @@ export function useDomEditAttributeCommits({
|
|
|
420
422
|
// shipped without one.
|
|
421
423
|
syncStoredAutomationFromPreview(previewIframeRef.current?.contentDocument ?? null);
|
|
422
424
|
},
|
|
425
|
+
onFinally: isLatestCommit.release,
|
|
423
426
|
});
|
|
424
427
|
},
|
|
425
428
|
[
|