@hyperframes/studio 0.5.0-alpha.8 → 0.5.0
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-CoI5h1xv.js +353 -0
- package/dist/assets/index-BKjcNNNd.css +1 -0
- package/dist/assets/index-CqiisJmo.js +93 -0
- package/dist/index.html +2 -2
- package/package.json +4 -4
- package/src/App.tsx +208 -1436
- package/src/captions/components/CaptionOverlay.tsx +2 -1
- package/src/captions/generator.test.ts +19 -0
- package/src/captions/generator.ts +9 -2
- package/src/captions/hooks/useCaptionSync.ts +6 -1
- package/src/captions/keyboard.test.ts +38 -0
- package/src/captions/keyboard.ts +8 -0
- package/src/captions/parser.test.ts +14 -0
- package/src/captions/parser.ts +1 -0
- package/src/components/LintModal.tsx +4 -3
- package/src/components/editor/PropertyPanel.tsx +206 -2462
- package/src/components/nle/NLELayout.tsx +47 -17
- package/src/components/nle/NLEPreview.tsx +9 -50
- package/src/components/sidebar/AssetsTab.tsx +4 -3
- package/src/components/sidebar/CompositionsTab.test.ts +1 -16
- package/src/components/sidebar/CompositionsTab.tsx +45 -117
- package/src/components/sidebar/LeftSidebar.tsx +55 -34
- package/src/components/ui/HyperframesLoader.tsx +104 -0
- package/src/components/ui/index.ts +2 -0
- package/src/icons/SystemIcons.tsx +2 -0
- package/src/player/components/CompositionThumbnail.tsx +10 -42
- package/src/player/components/EditModal.tsx +20 -5
- package/src/player/components/Player.tsx +129 -28
- package/src/player/components/PlayerControls.tsx +117 -49
- package/src/player/components/Timeline.test.ts +0 -12
- package/src/player/components/Timeline.tsx +25 -52
- package/src/player/components/TimelineClip.tsx +9 -21
- package/src/player/components/timelineEditing.test.ts +4 -2
- package/src/player/components/timelineEditing.ts +3 -1
- package/src/player/components/timelineTheme.test.ts +19 -0
- package/src/player/components/timelineTheme.ts +8 -4
- package/src/player/hooks/useTimelinePlayer.test.ts +219 -1
- package/src/player/hooks/useTimelinePlayer.ts +487 -106
- package/src/player/lib/time.test.ts +29 -1
- package/src/player/lib/time.ts +26 -0
- package/src/player/store/playerStore.test.ts +11 -1
- package/src/player/store/playerStore.ts +6 -1
- package/src/styles/studio.css +112 -0
- package/src/utils/frameCapture.test.ts +26 -0
- package/src/utils/frameCapture.ts +40 -0
- package/src/utils/mediaTypes.ts +1 -1
- package/src/utils/projectRouting.test.ts +87 -0
- package/src/utils/projectRouting.ts +27 -0
- package/src/utils/sourcePatcher.test.ts +1 -128
- package/src/utils/sourcePatcher.ts +18 -130
- package/src/utils/timelineAssetDrop.test.ts +11 -31
- package/src/utils/timelineAssetDrop.ts +2 -22
- package/dist/assets/hyperframes-player-vibA20NC.js +0 -198
- package/dist/assets/index-0Zt0t13W.css +0 -1
- package/dist/assets/index-C9f5eif8.js +0 -105
- package/src/components/editor/DomEditOverlay.tsx +0 -442
- package/src/components/editor/colorValue.test.ts +0 -82
- package/src/components/editor/colorValue.ts +0 -175
- package/src/components/editor/domEditing.test.ts +0 -537
- package/src/components/editor/domEditing.ts +0 -762
- package/src/components/editor/floatingPanel.test.ts +0 -34
- package/src/components/editor/floatingPanel.ts +0 -54
- package/src/components/editor/fontAssets.ts +0 -32
- package/src/components/editor/fontCatalog.ts +0 -126
- package/src/components/editor/gradientValue.test.ts +0 -89
- package/src/components/editor/gradientValue.ts +0 -445
- package/src/player/components/CompositionThumbnail.test.ts +0 -19
- package/src/utils/clipboard.test.ts +0 -88
- package/src/utils/clipboard.ts +0 -57
|
@@ -1,537 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { Window } from "happy-dom";
|
|
3
|
-
import {
|
|
4
|
-
buildDomEditMovePatchOperations,
|
|
5
|
-
buildDomEditResizePatchOperations,
|
|
6
|
-
buildDomEditStylePatchOperation,
|
|
7
|
-
buildElementAgentPrompt,
|
|
8
|
-
findElementForSelection,
|
|
9
|
-
isTextEditableSelection,
|
|
10
|
-
serializeDomEditTextFields,
|
|
11
|
-
type DomEditSelection,
|
|
12
|
-
resolveDomEditCapabilities,
|
|
13
|
-
resolveDomEditSelection,
|
|
14
|
-
} from "./domEditing";
|
|
15
|
-
|
|
16
|
-
function createDocument(markup: string): Document {
|
|
17
|
-
const window = new Window();
|
|
18
|
-
Object.assign(window, { SyntaxError });
|
|
19
|
-
window.document.body.innerHTML = markup;
|
|
20
|
-
return window.document;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
describe("resolveDomEditCapabilities", () => {
|
|
24
|
-
it("marks absolute px-positioned layers as movable and resizable", () => {
|
|
25
|
-
expect(
|
|
26
|
-
resolveDomEditCapabilities({
|
|
27
|
-
selector: "#card",
|
|
28
|
-
inlineStyles: {
|
|
29
|
-
left: "120px",
|
|
30
|
-
top: "80px",
|
|
31
|
-
width: "240px",
|
|
32
|
-
height: "140px",
|
|
33
|
-
},
|
|
34
|
-
computedStyles: {
|
|
35
|
-
position: "absolute",
|
|
36
|
-
left: "120px",
|
|
37
|
-
top: "80px",
|
|
38
|
-
width: "240px",
|
|
39
|
-
height: "140px",
|
|
40
|
-
transform: "none",
|
|
41
|
-
},
|
|
42
|
-
isCompositionHost: false,
|
|
43
|
-
isMasterView: false,
|
|
44
|
-
}),
|
|
45
|
-
).toEqual({
|
|
46
|
-
canSelect: true,
|
|
47
|
-
canEditStyles: true,
|
|
48
|
-
canMove: true,
|
|
49
|
-
canResize: true,
|
|
50
|
-
canDetachFromLayout: false,
|
|
51
|
-
reasonIfDisabled: undefined,
|
|
52
|
-
});
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it("rejects flex/grid children for move and resize", () => {
|
|
56
|
-
expect(
|
|
57
|
-
resolveDomEditCapabilities({
|
|
58
|
-
selector: "#chip",
|
|
59
|
-
tagName: "div",
|
|
60
|
-
inlineStyles: {},
|
|
61
|
-
computedStyles: {
|
|
62
|
-
position: "static",
|
|
63
|
-
display: "block",
|
|
64
|
-
left: "auto",
|
|
65
|
-
top: "auto",
|
|
66
|
-
width: "180px",
|
|
67
|
-
height: "64px",
|
|
68
|
-
transform: "none",
|
|
69
|
-
},
|
|
70
|
-
isCompositionHost: false,
|
|
71
|
-
isMasterView: false,
|
|
72
|
-
}),
|
|
73
|
-
).toMatchObject({
|
|
74
|
-
canSelect: true,
|
|
75
|
-
canEditStyles: true,
|
|
76
|
-
canMove: false,
|
|
77
|
-
canResize: false,
|
|
78
|
-
canDetachFromLayout: true,
|
|
79
|
-
reasonIfDisabled: "This layer is controlled by layout.",
|
|
80
|
-
});
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it("rejects transform-driven geometry", () => {
|
|
84
|
-
expect(
|
|
85
|
-
resolveDomEditCapabilities({
|
|
86
|
-
selector: "#card",
|
|
87
|
-
inlineStyles: {
|
|
88
|
-
left: "120px",
|
|
89
|
-
top: "80px",
|
|
90
|
-
width: "240px",
|
|
91
|
-
height: "140px",
|
|
92
|
-
},
|
|
93
|
-
computedStyles: {
|
|
94
|
-
position: "absolute",
|
|
95
|
-
left: "120px",
|
|
96
|
-
top: "80px",
|
|
97
|
-
width: "240px",
|
|
98
|
-
height: "140px",
|
|
99
|
-
transform: "matrix(1, 0, 0, 1, 12, 0)",
|
|
100
|
-
},
|
|
101
|
-
isCompositionHost: false,
|
|
102
|
-
isMasterView: false,
|
|
103
|
-
}),
|
|
104
|
-
).toMatchObject({
|
|
105
|
-
canMove: false,
|
|
106
|
-
canResize: false,
|
|
107
|
-
canDetachFromLayout: false,
|
|
108
|
-
});
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
it("treats identity transforms left behind by animation libraries as movable", () => {
|
|
112
|
-
expect(
|
|
113
|
-
resolveDomEditCapabilities({
|
|
114
|
-
selector: "#card",
|
|
115
|
-
inlineStyles: {
|
|
116
|
-
left: "120px",
|
|
117
|
-
top: "80px",
|
|
118
|
-
width: "240px",
|
|
119
|
-
height: "140px",
|
|
120
|
-
},
|
|
121
|
-
computedStyles: {
|
|
122
|
-
position: "absolute",
|
|
123
|
-
left: "120px",
|
|
124
|
-
top: "80px",
|
|
125
|
-
width: "240px",
|
|
126
|
-
height: "140px",
|
|
127
|
-
transform: "matrix(1, 0, 0, 1, 0, 0)",
|
|
128
|
-
},
|
|
129
|
-
isCompositionHost: false,
|
|
130
|
-
isMasterView: false,
|
|
131
|
-
}),
|
|
132
|
-
).toMatchObject({
|
|
133
|
-
canMove: true,
|
|
134
|
-
canResize: true,
|
|
135
|
-
canDetachFromLayout: false,
|
|
136
|
-
});
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
it("treats identity matrix3d transforms as movable", () => {
|
|
140
|
-
expect(
|
|
141
|
-
resolveDomEditCapabilities({
|
|
142
|
-
selector: "#card",
|
|
143
|
-
inlineStyles: {
|
|
144
|
-
left: "120px",
|
|
145
|
-
top: "80px",
|
|
146
|
-
width: "240px",
|
|
147
|
-
height: "140px",
|
|
148
|
-
},
|
|
149
|
-
computedStyles: {
|
|
150
|
-
position: "absolute",
|
|
151
|
-
left: "120px",
|
|
152
|
-
top: "80px",
|
|
153
|
-
width: "240px",
|
|
154
|
-
height: "140px",
|
|
155
|
-
transform: "matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1)",
|
|
156
|
-
},
|
|
157
|
-
isCompositionHost: false,
|
|
158
|
-
isMasterView: false,
|
|
159
|
-
}),
|
|
160
|
-
).toMatchObject({
|
|
161
|
-
canMove: true,
|
|
162
|
-
canResize: true,
|
|
163
|
-
});
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
it("allows imported absolute media to resize from computed px geometry", () => {
|
|
167
|
-
expect(
|
|
168
|
-
resolveDomEditCapabilities({
|
|
169
|
-
selector: "#photo",
|
|
170
|
-
inlineStyles: {
|
|
171
|
-
inset: "0",
|
|
172
|
-
width: "100%",
|
|
173
|
-
height: "100%",
|
|
174
|
-
},
|
|
175
|
-
computedStyles: {
|
|
176
|
-
position: "absolute",
|
|
177
|
-
left: "0px",
|
|
178
|
-
top: "0px",
|
|
179
|
-
width: "330px",
|
|
180
|
-
height: "228px",
|
|
181
|
-
transform: "none",
|
|
182
|
-
},
|
|
183
|
-
isCompositionHost: false,
|
|
184
|
-
isMasterView: false,
|
|
185
|
-
}),
|
|
186
|
-
).toMatchObject({
|
|
187
|
-
canMove: true,
|
|
188
|
-
canResize: true,
|
|
189
|
-
});
|
|
190
|
-
});
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
describe("resolveDomEditSelection", () => {
|
|
194
|
-
it("allows moving composition hosts in master view while keeping contents drill-down only", () => {
|
|
195
|
-
expect(
|
|
196
|
-
resolveDomEditCapabilities({
|
|
197
|
-
selector: "#detail-host",
|
|
198
|
-
inlineStyles: {
|
|
199
|
-
left: "80px",
|
|
200
|
-
top: "60px",
|
|
201
|
-
width: "320px",
|
|
202
|
-
height: "220px",
|
|
203
|
-
},
|
|
204
|
-
computedStyles: {
|
|
205
|
-
position: "absolute",
|
|
206
|
-
left: "80px",
|
|
207
|
-
top: "60px",
|
|
208
|
-
width: "320px",
|
|
209
|
-
height: "220px",
|
|
210
|
-
transform: "none",
|
|
211
|
-
},
|
|
212
|
-
isCompositionHost: true,
|
|
213
|
-
isMasterView: true,
|
|
214
|
-
}),
|
|
215
|
-
).toEqual({
|
|
216
|
-
canSelect: true,
|
|
217
|
-
canEditStyles: false,
|
|
218
|
-
canMove: true,
|
|
219
|
-
canResize: true,
|
|
220
|
-
canDetachFromLayout: false,
|
|
221
|
-
reasonIfDisabled: undefined,
|
|
222
|
-
});
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
it("resolves child clicks inside a composition host back to the host in master view", () => {
|
|
226
|
-
const document = createDocument(`
|
|
227
|
-
<div data-composition-id="main">
|
|
228
|
-
<div id="detail-host" data-composition-src="compositions/detail-card.html">
|
|
229
|
-
<span id="inner-copy">Nested scene</span>
|
|
230
|
-
</div>
|
|
231
|
-
</div>
|
|
232
|
-
`);
|
|
233
|
-
|
|
234
|
-
const child = document.getElementById("inner-copy") as HTMLElement;
|
|
235
|
-
const selection = resolveDomEditSelection(child, {
|
|
236
|
-
activeCompositionPath: null,
|
|
237
|
-
isMasterView: true,
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
expect(selection?.id).toBe("detail-host");
|
|
241
|
-
expect(selection?.isCompositionHost).toBe(true);
|
|
242
|
-
expect(selection?.capabilities.canMove).toBe(false);
|
|
243
|
-
expect(selection?.capabilities.canEditStyles).toBe(false);
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
it("scopes class selector indexing to the same source file", () => {
|
|
247
|
-
const document = createDocument(`
|
|
248
|
-
<div data-composition-id="main">
|
|
249
|
-
<div class="chip">Root chip</div>
|
|
250
|
-
<div data-composition-id="nested" data-composition-file="compositions/nested.html">
|
|
251
|
-
<div class="chip">Nested chip</div>
|
|
252
|
-
</div>
|
|
253
|
-
</div>
|
|
254
|
-
`);
|
|
255
|
-
|
|
256
|
-
const rootChip = document.getElementsByClassName("chip")[0] as HTMLElement;
|
|
257
|
-
const selection = resolveDomEditSelection(rootChip, {
|
|
258
|
-
activeCompositionPath: null,
|
|
259
|
-
isMasterView: true,
|
|
260
|
-
});
|
|
261
|
-
|
|
262
|
-
expect(selection?.sourceFile).toBe("index.html");
|
|
263
|
-
expect(selection?.selector).toBe(".chip");
|
|
264
|
-
expect(selection?.selectorIndex).toBe(0);
|
|
265
|
-
expect(findElementForSelection(document, selection!, null)).toBe(rootChip);
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
it("prefers the nearest clip ancestor on single-click style selection", () => {
|
|
269
|
-
const document = createDocument(`
|
|
270
|
-
<section id="card" class="clip" style="left: 10px; top: 20px; width: 200px; height: 100px; position: absolute;">
|
|
271
|
-
<p id="copy">Hello</p>
|
|
272
|
-
</section>
|
|
273
|
-
`);
|
|
274
|
-
|
|
275
|
-
const child = document.getElementById("copy") as HTMLElement;
|
|
276
|
-
const selection = resolveDomEditSelection(child, {
|
|
277
|
-
activeCompositionPath: null,
|
|
278
|
-
isMasterView: false,
|
|
279
|
-
preferClipAncestor: true,
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
expect(selection?.id).toBe("card");
|
|
283
|
-
expect(selection?.selector).toBe("#card");
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
it("can resolve the exact child when clip-ancestor preference is disabled", () => {
|
|
287
|
-
const document = createDocument(`
|
|
288
|
-
<section id="card" class="clip" style="left: 10px; top: 20px; width: 200px; height: 100px; position: absolute;">
|
|
289
|
-
<p id="copy">Hello</p>
|
|
290
|
-
</section>
|
|
291
|
-
`);
|
|
292
|
-
|
|
293
|
-
const child = document.getElementById("copy") as HTMLElement;
|
|
294
|
-
const selection = resolveDomEditSelection(child, {
|
|
295
|
-
activeCompositionPath: null,
|
|
296
|
-
isMasterView: false,
|
|
297
|
-
preferClipAncestor: false,
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
expect(selection?.id).toBe("copy");
|
|
301
|
-
expect(selection?.selector).toBe("#copy");
|
|
302
|
-
});
|
|
303
|
-
|
|
304
|
-
it("collects simple child text blocks as separate editable fields", () => {
|
|
305
|
-
const document = createDocument(`
|
|
306
|
-
<section id="card" class="clip" style="left: 10px; top: 20px; width: 200px; height: 100px; position: absolute;">
|
|
307
|
-
<strong>Headline</strong>
|
|
308
|
-
<span>Supporting copy</span>
|
|
309
|
-
</section>
|
|
310
|
-
`);
|
|
311
|
-
|
|
312
|
-
const selection = resolveDomEditSelection(document.getElementById("card") as HTMLElement, {
|
|
313
|
-
activeCompositionPath: null,
|
|
314
|
-
isMasterView: false,
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
expect(selection?.textFields.map((field) => field.label)).toEqual(["Text 1", "Text 2"]);
|
|
318
|
-
expect(selection?.textFields.map((field) => field.value)).toEqual([
|
|
319
|
-
"Headline",
|
|
320
|
-
"Supporting copy",
|
|
321
|
-
]);
|
|
322
|
-
});
|
|
323
|
-
|
|
324
|
-
it("preserves user-entered text spacing in editable text fields", () => {
|
|
325
|
-
const document = createDocument(`
|
|
326
|
-
<section id="card" class="clip" style="position: absolute;">
|
|
327
|
-
<strong>Headline with trailing space </strong>
|
|
328
|
-
</section>
|
|
329
|
-
`);
|
|
330
|
-
|
|
331
|
-
const selection = resolveDomEditSelection(document.getElementById("card") as HTMLElement, {
|
|
332
|
-
activeCompositionPath: null,
|
|
333
|
-
isMasterView: false,
|
|
334
|
-
});
|
|
335
|
-
|
|
336
|
-
expect(selection?.textFields[0]?.value).toBe("Headline with trailing space ");
|
|
337
|
-
});
|
|
338
|
-
|
|
339
|
-
it("keeps an emptied text layer editable so users can type into it again", () => {
|
|
340
|
-
const document = createDocument(`
|
|
341
|
-
<div id="card" class="clip" style="position: absolute;"></div>
|
|
342
|
-
`);
|
|
343
|
-
|
|
344
|
-
const selection = resolveDomEditSelection(document.getElementById("card") as HTMLElement, {
|
|
345
|
-
activeCompositionPath: null,
|
|
346
|
-
isMasterView: false,
|
|
347
|
-
});
|
|
348
|
-
|
|
349
|
-
expect(selection?.textFields).toMatchObject([
|
|
350
|
-
{
|
|
351
|
-
key: "self:0:div",
|
|
352
|
-
label: "Content",
|
|
353
|
-
value: "",
|
|
354
|
-
source: "self",
|
|
355
|
-
},
|
|
356
|
-
]);
|
|
357
|
-
expect(selection ? isTextEditableSelection(selection) : false).toBe(true);
|
|
358
|
-
});
|
|
359
|
-
|
|
360
|
-
it("keeps emptied child text layers editable after their content is cleared", () => {
|
|
361
|
-
const document = createDocument(`
|
|
362
|
-
<div id="card" class="clip" style="position: absolute;">
|
|
363
|
-
<strong></strong>
|
|
364
|
-
<span></span>
|
|
365
|
-
</div>
|
|
366
|
-
`);
|
|
367
|
-
|
|
368
|
-
const selection = resolveDomEditSelection(document.getElementById("card") as HTMLElement, {
|
|
369
|
-
activeCompositionPath: null,
|
|
370
|
-
isMasterView: false,
|
|
371
|
-
});
|
|
372
|
-
|
|
373
|
-
expect(selection?.textFields.map((field) => field.tagName)).toEqual(["strong", "span"]);
|
|
374
|
-
expect(selection?.textFields.map((field) => field.value)).toEqual(["", ""]);
|
|
375
|
-
});
|
|
376
|
-
});
|
|
377
|
-
|
|
378
|
-
describe("patch builders and prompt builder", () => {
|
|
379
|
-
it("builds move patch operations for left/top", () => {
|
|
380
|
-
expect(buildDomEditMovePatchOperations(140.4, 82.1)).toEqual([
|
|
381
|
-
{ type: "inline-style", property: "left", value: "140px" },
|
|
382
|
-
{ type: "inline-style", property: "top", value: "82px" },
|
|
383
|
-
]);
|
|
384
|
-
});
|
|
385
|
-
|
|
386
|
-
it("builds resize patch operations for width/height", () => {
|
|
387
|
-
expect(buildDomEditResizePatchOperations(301.6, 210.1)).toEqual([
|
|
388
|
-
{ type: "inline-style", property: "width", value: "302px" },
|
|
389
|
-
{ type: "inline-style", property: "height", value: "210px" },
|
|
390
|
-
]);
|
|
391
|
-
});
|
|
392
|
-
|
|
393
|
-
it("builds style patch operations", () => {
|
|
394
|
-
expect(buildDomEditStylePatchOperation("background-color", "rgb(15, 23, 42)")).toEqual({
|
|
395
|
-
type: "inline-style",
|
|
396
|
-
property: "background-color",
|
|
397
|
-
value: "rgb(15, 23, 42)",
|
|
398
|
-
});
|
|
399
|
-
});
|
|
400
|
-
|
|
401
|
-
it("builds an agent prompt with source and selector context", () => {
|
|
402
|
-
const selection = {
|
|
403
|
-
element: {} as HTMLElement,
|
|
404
|
-
id: "editable-card",
|
|
405
|
-
selector: "#editable-card",
|
|
406
|
-
selectorIndex: undefined,
|
|
407
|
-
sourceFile: "index.html",
|
|
408
|
-
compositionPath: "index.html",
|
|
409
|
-
compositionSrc: undefined,
|
|
410
|
-
isCompositionHost: false,
|
|
411
|
-
label: "Drag me first",
|
|
412
|
-
tagName: "div",
|
|
413
|
-
boundingBox: { x: 108, y: 112, width: 380, height: 196 },
|
|
414
|
-
textContent: "Drag me first",
|
|
415
|
-
dataAttributes: {},
|
|
416
|
-
inlineStyles: {
|
|
417
|
-
left: "108px",
|
|
418
|
-
top: "112px",
|
|
419
|
-
width: "380px",
|
|
420
|
-
height: "196px",
|
|
421
|
-
},
|
|
422
|
-
computedStyles: {
|
|
423
|
-
position: "absolute",
|
|
424
|
-
left: "108px",
|
|
425
|
-
top: "112px",
|
|
426
|
-
width: "380px",
|
|
427
|
-
height: "196px",
|
|
428
|
-
color: "rgb(248, 250, 252)",
|
|
429
|
-
},
|
|
430
|
-
textFields: [
|
|
431
|
-
{
|
|
432
|
-
key: "self:0:div",
|
|
433
|
-
label: "Content",
|
|
434
|
-
value: "Drag me first",
|
|
435
|
-
tagName: "div",
|
|
436
|
-
attributes: [],
|
|
437
|
-
inlineStyles: {},
|
|
438
|
-
computedStyles: {},
|
|
439
|
-
source: "self",
|
|
440
|
-
},
|
|
441
|
-
],
|
|
442
|
-
capabilities: {
|
|
443
|
-
canSelect: true,
|
|
444
|
-
canEditStyles: true,
|
|
445
|
-
canMove: true,
|
|
446
|
-
canResize: true,
|
|
447
|
-
},
|
|
448
|
-
} satisfies DomEditSelection;
|
|
449
|
-
|
|
450
|
-
const prompt = buildElementAgentPrompt({
|
|
451
|
-
selection,
|
|
452
|
-
currentTime: 1.25,
|
|
453
|
-
tagSnippet: `<div id="editable-card" style="position:absolute; left: 108px; top: 112px; width: 380px; height: 196px; color: rgb(248, 250, 252)"`,
|
|
454
|
-
});
|
|
455
|
-
|
|
456
|
-
expect(prompt).toContain("## HyperFrames element edit request v1");
|
|
457
|
-
expect(prompt).toContain("Schema version: 1");
|
|
458
|
-
expect(prompt).toContain("Source file: index.html");
|
|
459
|
-
expect(prompt).toContain("Selector: #editable-card");
|
|
460
|
-
expect(prompt).toContain("Playback time:");
|
|
461
|
-
expect(prompt).toContain("Text fields:");
|
|
462
|
-
expect(prompt).toContain('key=self:0:div; tag=<div>; source=self; text="Drag me first"');
|
|
463
|
-
expect(prompt).toContain("Inline styles:");
|
|
464
|
-
expect(prompt).toContain("Computed styles (browser-resolved):");
|
|
465
|
-
expect(prompt).toContain("Target HTML:");
|
|
466
|
-
expect(prompt).toContain("Guardrails:");
|
|
467
|
-
expect(prompt).toContain("Do not modify other elements' data-* attributes or positioning.");
|
|
468
|
-
});
|
|
469
|
-
|
|
470
|
-
it("uses an absolute source path in copied agent prompts when provided", () => {
|
|
471
|
-
const selection = {
|
|
472
|
-
element: {} as HTMLElement,
|
|
473
|
-
id: "editable-card",
|
|
474
|
-
selector: "#editable-card",
|
|
475
|
-
selectorIndex: undefined,
|
|
476
|
-
sourceFile: "index.html",
|
|
477
|
-
compositionPath: "index.html",
|
|
478
|
-
compositionSrc: undefined,
|
|
479
|
-
isCompositionHost: false,
|
|
480
|
-
label: "Drag me first",
|
|
481
|
-
tagName: "div",
|
|
482
|
-
boundingBox: { x: 108, y: 112, width: 380, height: 196 },
|
|
483
|
-
textContent: "Drag me first",
|
|
484
|
-
dataAttributes: {},
|
|
485
|
-
inlineStyles: {},
|
|
486
|
-
computedStyles: {},
|
|
487
|
-
textFields: [],
|
|
488
|
-
capabilities: {
|
|
489
|
-
canSelect: true,
|
|
490
|
-
canEditStyles: true,
|
|
491
|
-
canMove: true,
|
|
492
|
-
canResize: true,
|
|
493
|
-
canDetachFromLayout: false,
|
|
494
|
-
},
|
|
495
|
-
} satisfies DomEditSelection;
|
|
496
|
-
|
|
497
|
-
const prompt = buildElementAgentPrompt({
|
|
498
|
-
selection,
|
|
499
|
-
currentTime: 1.25,
|
|
500
|
-
sourceFilePath: "/tmp/hf-studio-project/index.html",
|
|
501
|
-
});
|
|
502
|
-
|
|
503
|
-
expect(prompt).toContain("Source file: /tmp/hf-studio-project/index.html");
|
|
504
|
-
expect(prompt).not.toContain("Source file: index.html");
|
|
505
|
-
});
|
|
506
|
-
|
|
507
|
-
it("serializes child text fields back into HTML", () => {
|
|
508
|
-
expect(
|
|
509
|
-
serializeDomEditTextFields([
|
|
510
|
-
{
|
|
511
|
-
key: "child:0:strong",
|
|
512
|
-
label: "Text 1",
|
|
513
|
-
value: "Headline <1>",
|
|
514
|
-
tagName: "strong",
|
|
515
|
-
attributes: [],
|
|
516
|
-
inlineStyles: {
|
|
517
|
-
"font-size": "22px",
|
|
518
|
-
},
|
|
519
|
-
computedStyles: {},
|
|
520
|
-
source: "child",
|
|
521
|
-
},
|
|
522
|
-
{
|
|
523
|
-
key: "child:1:span",
|
|
524
|
-
label: "Text 2",
|
|
525
|
-
value: "Details & more",
|
|
526
|
-
tagName: "span",
|
|
527
|
-
attributes: [],
|
|
528
|
-
inlineStyles: {},
|
|
529
|
-
computedStyles: {},
|
|
530
|
-
source: "child",
|
|
531
|
-
},
|
|
532
|
-
]),
|
|
533
|
-
).toBe(
|
|
534
|
-
'<strong data-hf-text-key="child:0:strong" style="font-size: 22px">Headline <1></strong><span data-hf-text-key="child:1:span">Details & more</span>',
|
|
535
|
-
);
|
|
536
|
-
});
|
|
537
|
-
});
|