@hyperframes/studio 0.6.25 → 0.6.27
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/{index-Cqq4uPvL.css → index-DVpLGNHi.css} +1 -1
- package/dist/assets/{index-BA9LlfxA.js → index-DYjmgXgg.js} +35 -36
- package/dist/index.html +2 -2
- package/package.json +4 -4
- package/src/components/editor/TimelineLayerPanel.tsx +0 -98
- package/src/components/editor/domEditing.test.ts +23 -0
- package/src/components/editor/domEditingElement.ts +19 -38
- package/src/components/editor/manualEditingAvailability.test.ts +1 -23
- package/src/components/editor/manualEditingAvailability.ts +3 -15
- package/src/components/editor/manualEditsSnapshot.ts +0 -34
- package/src/hooks/useDomEditSession.ts +0 -5
- package/src/hooks/usePreviewInteraction.ts +2 -40
- package/src/hooks/useTimelineEditing.ts +7 -73
- package/src/icons/SystemIcons.tsx +0 -75
- package/src/player/components/timelineEditing.test.ts +0 -24
- package/src/player/components/timelineEditing.ts +0 -6
- package/src/player/components/useTimelineRangeSelection.ts +0 -16
- package/src/player/hooks/useTimelineSyncCallbacks.ts +0 -6
- package/src/utils/blockInstaller.ts +2 -6
- package/src/utils/sourcePatcher.test.ts +22 -0
- package/src/utils/sourcePatcher.ts +3 -3
- package/src/utils/studioHelpers.ts +3 -9
- package/src/utils/studioPreviewHelpers.ts +3 -69
- package/src/utils/timelineDiscovery.ts +0 -16
- package/src/utils/timelineZIndexInjection.test.ts +155 -0
package/dist/index.html
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
|
|
6
6
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
7
7
|
<title>HyperFrames Studio</title>
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
9
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-DYjmgXgg.js"></script>
|
|
9
|
+
<link rel="stylesheet" crossorigin href="/assets/index-DVpLGNHi.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
|
12
12
|
<div id="root"></div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperframes/studio",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.27",
|
|
4
4
|
"description": "",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
"@codemirror/theme-one-dark": "^6.1.2",
|
|
31
31
|
"@codemirror/view": "6.40.0",
|
|
32
32
|
"@phosphor-icons/react": "^2.1.10",
|
|
33
|
-
"@hyperframes/
|
|
34
|
-
"@hyperframes/
|
|
33
|
+
"@hyperframes/core": "0.6.27",
|
|
34
|
+
"@hyperframes/player": "0.6.27"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@types/react": "19",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"vite": "^6.4.2",
|
|
46
46
|
"vitest": "^3.2.4",
|
|
47
47
|
"zustand": "^5.0.0",
|
|
48
|
-
"@hyperframes/producer": "0.6.
|
|
48
|
+
"@hyperframes/producer": "0.6.27"
|
|
49
49
|
},
|
|
50
50
|
"peerDependencies": {
|
|
51
51
|
"react": "19",
|
|
@@ -1,14 +1,5 @@
|
|
|
1
|
-
import { memo } from "react";
|
|
2
1
|
import type { DomEditLayerItem } from "./domEditing";
|
|
3
2
|
|
|
4
|
-
interface TimelineLayerPanelProps {
|
|
5
|
-
clipLabel: string;
|
|
6
|
-
layers: DomEditLayerItem[];
|
|
7
|
-
selectedLayerKey: string | null;
|
|
8
|
-
onSelectLayer: (layer: DomEditLayerItem) => void;
|
|
9
|
-
onClose: () => void;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
3
|
const MEDIA_LAYER_TAGS = new Set(["audio", "canvas", "img", "picture", "svg", "video"]);
|
|
13
4
|
|
|
14
5
|
export function getTimelineLayerPanelSummary(layers: readonly DomEditLayerItem[]): string {
|
|
@@ -22,92 +13,3 @@ export function getTimelineLayerPanelSummary(layers: readonly DomEditLayerItem[]
|
|
|
22
13
|
? "Single selectable media layer"
|
|
23
14
|
: "Single selectable layer";
|
|
24
15
|
}
|
|
25
|
-
|
|
26
|
-
export const TimelineLayerPanel = memo(function TimelineLayerPanel({
|
|
27
|
-
clipLabel,
|
|
28
|
-
layers,
|
|
29
|
-
selectedLayerKey,
|
|
30
|
-
onSelectLayer,
|
|
31
|
-
onClose,
|
|
32
|
-
}: TimelineLayerPanelProps) {
|
|
33
|
-
return (
|
|
34
|
-
<div className="flex h-full min-h-0 flex-col overflow-hidden bg-neutral-950">
|
|
35
|
-
<div className="flex items-start justify-between gap-3 border-b border-white/10 px-3 py-3">
|
|
36
|
-
<div className="min-w-0">
|
|
37
|
-
<div className="text-[9px] font-semibold uppercase tracking-[0.18em] text-neutral-500">
|
|
38
|
-
Clip layers
|
|
39
|
-
</div>
|
|
40
|
-
<div className="mt-1 truncate text-sm font-semibold text-neutral-100">{clipLabel}</div>
|
|
41
|
-
</div>
|
|
42
|
-
<button
|
|
43
|
-
type="button"
|
|
44
|
-
onPointerDown={(event) => {
|
|
45
|
-
event.stopPropagation();
|
|
46
|
-
}}
|
|
47
|
-
onClick={onClose}
|
|
48
|
-
className="flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-md border border-white/10 bg-black/20 text-neutral-500 transition-colors hover:border-white/20 hover:text-neutral-200"
|
|
49
|
-
aria-label="Close clip layers"
|
|
50
|
-
>
|
|
51
|
-
<svg
|
|
52
|
-
width="14"
|
|
53
|
-
height="14"
|
|
54
|
-
viewBox="0 0 24 24"
|
|
55
|
-
fill="none"
|
|
56
|
-
stroke="currentColor"
|
|
57
|
-
strokeWidth="1.8"
|
|
58
|
-
strokeLinecap="round"
|
|
59
|
-
aria-hidden="true"
|
|
60
|
-
>
|
|
61
|
-
<path d="M18 6 6 18" />
|
|
62
|
-
<path d="m6 6 12 12" />
|
|
63
|
-
</svg>
|
|
64
|
-
</button>
|
|
65
|
-
</div>
|
|
66
|
-
<div className="border-b border-white/10 px-3 py-2 text-[11px] text-neutral-500">
|
|
67
|
-
{getTimelineLayerPanelSummary(layers)}
|
|
68
|
-
</div>
|
|
69
|
-
<div className="min-h-0 flex-1 overflow-y-auto py-1">
|
|
70
|
-
{layers.map((layer) => {
|
|
71
|
-
const selected = layer.key === selectedLayerKey;
|
|
72
|
-
return (
|
|
73
|
-
<button
|
|
74
|
-
key={layer.key}
|
|
75
|
-
type="button"
|
|
76
|
-
data-timeline-layer-row={layer.key}
|
|
77
|
-
onPointerDown={(event) => {
|
|
78
|
-
event.stopPropagation();
|
|
79
|
-
onSelectLayer(layer);
|
|
80
|
-
}}
|
|
81
|
-
onClick={(event) => {
|
|
82
|
-
event.stopPropagation();
|
|
83
|
-
onSelectLayer(layer);
|
|
84
|
-
}}
|
|
85
|
-
className={`group flex w-full items-center gap-2 px-2.5 py-1.5 text-left transition-colors ${
|
|
86
|
-
selected
|
|
87
|
-
? "bg-studio-accent/14 text-studio-accent"
|
|
88
|
-
: "text-neutral-300 hover:bg-white/[0.04] hover:text-neutral-100"
|
|
89
|
-
}`}
|
|
90
|
-
style={{ paddingLeft: 10 + layer.depth * 14 }}
|
|
91
|
-
>
|
|
92
|
-
<span
|
|
93
|
-
className={`flex h-5 w-5 flex-shrink-0 items-center justify-center rounded-md border text-[9px] font-bold uppercase ${
|
|
94
|
-
selected
|
|
95
|
-
? "border-studio-accent/50 bg-studio-accent/18"
|
|
96
|
-
: "border-white/10 bg-black/20 text-neutral-500 group-hover:text-neutral-300"
|
|
97
|
-
}`}
|
|
98
|
-
>
|
|
99
|
-
{layer.tagName.slice(0, 2)}
|
|
100
|
-
</span>
|
|
101
|
-
<span className="min-w-0 flex-1 truncate text-xs font-medium">{layer.label}</span>
|
|
102
|
-
{layer.childCount > 0 && (
|
|
103
|
-
<span className="rounded-full border border-white/10 bg-black/25 px-1.5 py-0.5 text-[9px] font-semibold tabular-nums text-neutral-500">
|
|
104
|
-
{layer.childCount}
|
|
105
|
-
</span>
|
|
106
|
-
)}
|
|
107
|
-
</button>
|
|
108
|
-
);
|
|
109
|
-
})}
|
|
110
|
-
</div>
|
|
111
|
-
</div>
|
|
112
|
-
);
|
|
113
|
-
});
|
|
@@ -321,6 +321,29 @@ describe("resolveVisualDomEditSelectionTarget", () => {
|
|
|
321
321
|
expect(visualTarget).toBe(headline);
|
|
322
322
|
expect(explicitSelection?.id).toBe("container");
|
|
323
323
|
});
|
|
324
|
+
|
|
325
|
+
it("prefers the visually-on-top sibling over a deeper element in a separate visual layer", () => {
|
|
326
|
+
const document = createDocument(`
|
|
327
|
+
<div id="comp-root">
|
|
328
|
+
<div id="sub-comp" class="sub-comp">
|
|
329
|
+
<img id="sf-chrome" class="sf-chrome" style="width:100%;height:100%" />
|
|
330
|
+
</div>
|
|
331
|
+
<video id="pip-studio" class="pip-studio" style="position:absolute;z-index:15" />
|
|
332
|
+
</div>
|
|
333
|
+
`);
|
|
334
|
+
const pipStudio = document.getElementById("pip-studio") as HTMLElement;
|
|
335
|
+
const sfChrome = document.getElementById("sf-chrome") as HTMLElement;
|
|
336
|
+
const subComp = document.getElementById("sub-comp") as HTMLElement;
|
|
337
|
+
setElementRect(pipStudio, { left: 50, top: 50, width: 320, height: 320 });
|
|
338
|
+
setElementRect(sfChrome, { left: 0, top: 0, width: 1920, height: 1080 });
|
|
339
|
+
setElementRect(subComp, { left: 0, top: 0, width: 1920, height: 1080 });
|
|
340
|
+
|
|
341
|
+
expect(
|
|
342
|
+
resolveVisualDomEditSelectionTarget([pipStudio, subComp, sfChrome], {
|
|
343
|
+
activeCompositionPath: "index.html",
|
|
344
|
+
}),
|
|
345
|
+
).toBe(pipStudio);
|
|
346
|
+
});
|
|
324
347
|
});
|
|
325
348
|
|
|
326
349
|
describe("isLargeRasterDomEditSelection", () => {
|
|
@@ -12,13 +12,9 @@ import type {
|
|
|
12
12
|
import {
|
|
13
13
|
buildStableSelector,
|
|
14
14
|
escapeCssString,
|
|
15
|
-
findClosestByAttribute,
|
|
16
|
-
getElementDepth,
|
|
17
|
-
getPreferredClassSelector,
|
|
18
15
|
getSelectorIndex,
|
|
19
16
|
getSourceFileForElement,
|
|
20
17
|
isHtmlElement,
|
|
21
|
-
isTextBearingTag,
|
|
22
18
|
normalizeTimelineCompositionSource,
|
|
23
19
|
querySelectorAllSafely,
|
|
24
20
|
} from "./domEditingDom";
|
|
@@ -60,7 +56,7 @@ function isEmptyVisualContainer(el: HTMLElement): boolean {
|
|
|
60
56
|
return true;
|
|
61
57
|
}
|
|
62
58
|
|
|
63
|
-
|
|
59
|
+
function hasRenderedBox(el: HTMLElement): boolean {
|
|
64
60
|
const rect = el.getBoundingClientRect();
|
|
65
61
|
if (rect.width <= 1 || rect.height <= 1) return false;
|
|
66
62
|
if (!isElementComputedVisible(el)) return false;
|
|
@@ -70,23 +66,6 @@ export function hasRenderedBox(el: HTMLElement): boolean {
|
|
|
70
66
|
|
|
71
67
|
// ─── Visual scoring ──────────────────────────────────────────────────────────
|
|
72
68
|
|
|
73
|
-
function isEditableTextLeafForScoring(el: HTMLElement): boolean {
|
|
74
|
-
return isTextBearingTag(el.tagName.toLowerCase()) && el.children.length === 0;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function getVisualElementScore(el: HTMLElement, pointerStackIndex: number): number {
|
|
78
|
-
const tagName = el.tagName.toLowerCase();
|
|
79
|
-
const rect = el.getBoundingClientRect();
|
|
80
|
-
const area = Math.max(1, rect.width * rect.height);
|
|
81
|
-
const smallerElementBonus = Math.max(0, 1_000_000 - Math.min(area, 1_000_000)) / 1_000;
|
|
82
|
-
const visualLeafBonus =
|
|
83
|
-
isEditableTextLeafForScoring(el) || ["img", "video", "canvas", "svg"].includes(tagName)
|
|
84
|
-
? 2_000
|
|
85
|
-
: 0;
|
|
86
|
-
|
|
87
|
-
return getElementDepth(el) * 10_000 + visualLeafBonus + smallerElementBonus - pointerStackIndex;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
69
|
// ─── Layer patch target ──────────────────────────────────────────────────────
|
|
91
70
|
|
|
92
71
|
const DOM_LAYER_IGNORED_TAGS = new Set([
|
|
@@ -174,25 +153,31 @@ export function resolveVisualDomEditSelectionTarget(
|
|
|
174
153
|
elementsFromPoint: Iterable<Element | null | undefined>,
|
|
175
154
|
options: Pick<DomEditContextOptions, "activeCompositionPath">,
|
|
176
155
|
): HTMLElement | null {
|
|
177
|
-
|
|
178
|
-
let pointerStackIndex = 0;
|
|
156
|
+
const candidates: HTMLElement[] = [];
|
|
179
157
|
|
|
180
158
|
for (const entry of elementsFromPoint) {
|
|
181
|
-
if (!isHtmlElement(entry))
|
|
182
|
-
|
|
183
|
-
|
|
159
|
+
if (!isHtmlElement(entry)) continue;
|
|
160
|
+
if (hasRenderedBox(entry) && getDomLayerPatchTarget(entry, options.activeCompositionPath)) {
|
|
161
|
+
candidates.push(entry);
|
|
184
162
|
}
|
|
163
|
+
}
|
|
185
164
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
165
|
+
if (candidates.length === 0) return null;
|
|
166
|
+
|
|
167
|
+
// candidates are in visual stacking order (topmost first, from elementsFromPoint).
|
|
168
|
+
// Start with the topmost and only replace with a descendant that is more
|
|
169
|
+
// specific within the same visual subtree. Never jump to an unrelated
|
|
170
|
+
// element that happens to be painted behind the current pick.
|
|
171
|
+
let best = candidates[0];
|
|
172
|
+
|
|
173
|
+
for (let i = 1; i < candidates.length; i++) {
|
|
174
|
+
const candidate = candidates[i];
|
|
175
|
+
if (best.contains(candidate)) {
|
|
176
|
+
best = candidate;
|
|
191
177
|
}
|
|
192
|
-
pointerStackIndex += 1;
|
|
193
178
|
}
|
|
194
179
|
|
|
195
|
-
return best
|
|
180
|
+
return best;
|
|
196
181
|
}
|
|
197
182
|
|
|
198
183
|
// ─── Raster detection ────────────────────────────────────────────────────────
|
|
@@ -324,7 +309,3 @@ export function getDirectLayerChildren(
|
|
|
324
309
|
isHtmlElement(child) && getDomLayerPatchTarget(child, options.activeCompositionPath) !== null,
|
|
325
310
|
);
|
|
326
311
|
}
|
|
327
|
-
|
|
328
|
-
// ─── Composition source helpers ───────────────────────────────────────────────
|
|
329
|
-
|
|
330
|
-
export { findClosestByAttribute, getPreferredClassSelector, getSourceFileForElement };
|
|
@@ -23,37 +23,15 @@ describe("manual editing availability", () => {
|
|
|
23
23
|
expect(availability.STUDIO_PREVIEW_SELECTION_ENABLED).toBe(true);
|
|
24
24
|
expect(availability.STUDIO_INSPECTOR_PANELS_ENABLED).toBe(true);
|
|
25
25
|
expect(availability.STUDIO_MOTION_PANEL_ENABLED).toBe(false);
|
|
26
|
-
expect(availability.STUDIO_TIMELINE_LAYER_INSPECTOR_ENABLED).toBe(true);
|
|
27
26
|
});
|
|
28
27
|
|
|
29
|
-
it("
|
|
30
|
-
const availability = await loadAvailabilityWithEnv({
|
|
31
|
-
VITE_STUDIO_ENABLE_INSPECTOR_PANELS: "1",
|
|
32
|
-
VITE_STUDIO_ENABLE_TIMELINE_LAYER_INSPECTOR: "true",
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
expect(availability.STUDIO_INSPECTOR_PANELS_ENABLED).toBe(true);
|
|
36
|
-
expect(availability.STUDIO_TIMELINE_LAYER_INSPECTOR_ENABLED).toBe(true);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it("allows explicit env flags to disable default-on inspector layers", async () => {
|
|
40
|
-
const availability = await loadAvailabilityWithEnv({
|
|
41
|
-
VITE_STUDIO_ENABLE_TIMELINE_LAYER_INSPECTOR: "off",
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
expect(availability.STUDIO_INSPECTOR_PANELS_ENABLED).toBe(true);
|
|
45
|
-
expect(availability.STUDIO_TIMELINE_LAYER_INSPECTOR_ENABLED).toBe(false);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it("keeps timeline layer inspection off when the parent inspector flag is off", async () => {
|
|
28
|
+
it("disables preview selection when the inspector panel flag is explicitly off", async () => {
|
|
49
29
|
const availability = await loadAvailabilityWithEnv({
|
|
50
30
|
VITE_STUDIO_ENABLE_INSPECTOR_PANELS: "0",
|
|
51
|
-
VITE_STUDIO_ENABLE_TIMELINE_LAYER_INSPECTOR: "true",
|
|
52
31
|
});
|
|
53
32
|
|
|
54
33
|
expect(availability.STUDIO_INSPECTOR_PANELS_ENABLED).toBe(false);
|
|
55
34
|
expect(availability.STUDIO_PREVIEW_SELECTION_ENABLED).toBe(false);
|
|
56
|
-
expect(availability.STUDIO_TIMELINE_LAYER_INSPECTOR_ENABLED).toBe(false);
|
|
57
35
|
});
|
|
58
36
|
|
|
59
37
|
it("enables feature flags with explicit truthy env values", () => {
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
export type StudioFeatureFlagEnv = Record<string, boolean | string | undefined>;
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
export const STUDIO_TIMELINE_LAYER_INSPECTOR_ENV = "VITE_STUDIO_ENABLE_TIMELINE_LAYER_INSPECTOR";
|
|
7
|
-
|
|
3
|
+
const STUDIO_PREVIEW_MANUAL_DRAGGING_ENV = "VITE_STUDIO_ENABLE_PREVIEW_MANUAL_DRAGGING";
|
|
4
|
+
const STUDIO_INSPECTOR_PANELS_ENV = "VITE_STUDIO_ENABLE_INSPECTOR_PANELS";
|
|
5
|
+
const STUDIO_MOTION_PANEL_ENV = "VITE_STUDIO_ENABLE_MOTION_PANEL";
|
|
8
6
|
const TRUTHY_ENV_VALUES = new Set(["1", "true", "yes", "on", "enabled"]);
|
|
9
7
|
const FALSY_ENV_VALUES = new Set(["0", "false", "no", "off", "disabled"]);
|
|
10
8
|
|
|
@@ -52,14 +50,6 @@ export const STUDIO_MOTION_PANEL_ENABLED = resolveStudioBooleanEnvFlag(
|
|
|
52
50
|
false,
|
|
53
51
|
);
|
|
54
52
|
|
|
55
|
-
export const STUDIO_TIMELINE_LAYER_INSPECTOR_ENABLED =
|
|
56
|
-
STUDIO_INSPECTOR_PANELS_ENABLED &&
|
|
57
|
-
resolveStudioBooleanEnvFlag(
|
|
58
|
-
env,
|
|
59
|
-
[STUDIO_TIMELINE_LAYER_INSPECTOR_ENV, "VITE_STUDIO_TIMELINE_LAYER_INSPECTOR_ENABLED"],
|
|
60
|
-
true,
|
|
61
|
-
);
|
|
62
|
-
|
|
63
53
|
export const STUDIO_BLOCKS_PANEL_ENABLED = resolveStudioBooleanEnvFlag(
|
|
64
54
|
env,
|
|
65
55
|
["VITE_STUDIO_ENABLE_BLOCKS_PANEL", "VITE_STUDIO_BLOCKS_PANEL_ENABLED"],
|
|
@@ -68,6 +58,4 @@ export const STUDIO_BLOCKS_PANEL_ENABLED = resolveStudioBooleanEnvFlag(
|
|
|
68
58
|
|
|
69
59
|
export const STUDIO_PREVIEW_SELECTION_ENABLED = STUDIO_INSPECTOR_PANELS_ENABLED;
|
|
70
60
|
|
|
71
|
-
export const STUDIO_MANUAL_EDITING_ENABLED = STUDIO_PREVIEW_MANUAL_EDITING_ENABLED;
|
|
72
|
-
|
|
73
61
|
export const STUDIO_MANUAL_EDITING_DISABLED_TITLE = "Manual editing is temporarily disabled";
|
|
@@ -11,7 +11,6 @@ import {
|
|
|
11
11
|
STUDIO_HEIGHT_PROP,
|
|
12
12
|
STUDIO_ROTATION_PROP,
|
|
13
13
|
STUDIO_PATH_OFFSET_ATTR,
|
|
14
|
-
STUDIO_MANUAL_EDIT_GESTURE_ATTR,
|
|
15
14
|
STUDIO_BOX_SIZE_ATTR,
|
|
16
15
|
STUDIO_ROTATION_ATTR,
|
|
17
16
|
STUDIO_ORIGINAL_TRANSLATE_ATTR,
|
|
@@ -33,7 +32,6 @@ import {
|
|
|
33
32
|
STUDIO_ORIGINAL_INLINE_ROTATE_ATTR,
|
|
34
33
|
STUDIO_ORIGINAL_ROTATION_TRANSFORM_ORIGIN_ATTR,
|
|
35
34
|
STUDIO_ROTATION_DRAFT_ATTR,
|
|
36
|
-
STUDIO_ORIGINAL_TRANSFORM_DISPLAY_ATTR,
|
|
37
35
|
} from "./manualEditsTypes";
|
|
38
36
|
import type {
|
|
39
37
|
StudioBoxSizeSnapshot,
|
|
@@ -187,38 +185,6 @@ export function restoreStudioPathOffset(
|
|
|
187
185
|
);
|
|
188
186
|
}
|
|
189
187
|
|
|
190
|
-
/* ── DOM element collection ───────────────────────────────────────── */
|
|
191
|
-
export function collectStudioManualEditElements(doc: Document): HTMLElement[] {
|
|
192
|
-
const htmlElement = doc.defaultView?.HTMLElement;
|
|
193
|
-
if (!htmlElement) return [];
|
|
194
|
-
|
|
195
|
-
const elements = [doc.documentElement, ...Array.from(doc.getElementsByTagName("*"))].filter(
|
|
196
|
-
(element): element is HTMLElement => element instanceof htmlElement,
|
|
197
|
-
);
|
|
198
|
-
|
|
199
|
-
return elements.filter(
|
|
200
|
-
(element) =>
|
|
201
|
-
element.hasAttribute(STUDIO_PATH_OFFSET_ATTR) ||
|
|
202
|
-
element.hasAttribute(STUDIO_MANUAL_EDIT_GESTURE_ATTR) ||
|
|
203
|
-
element.hasAttribute(STUDIO_BOX_SIZE_ATTR) ||
|
|
204
|
-
element.hasAttribute(STUDIO_ROTATION_ATTR) ||
|
|
205
|
-
element.hasAttribute(STUDIO_ROTATION_DRAFT_ATTR) ||
|
|
206
|
-
element.hasAttribute(STUDIO_ORIGINAL_TRANSLATE_ATTR) ||
|
|
207
|
-
element.hasAttribute(STUDIO_ORIGINAL_INLINE_TRANSLATE_ATTR) ||
|
|
208
|
-
element.hasAttribute(STUDIO_ORIGINAL_TRANSFORM_DISPLAY_ATTR) ||
|
|
209
|
-
element.hasAttribute(STUDIO_ORIGINAL_MIN_WIDTH_ATTR) ||
|
|
210
|
-
element.hasAttribute(STUDIO_ORIGINAL_FLEX_BASIS_ATTR) ||
|
|
211
|
-
element.hasAttribute(STUDIO_ORIGINAL_SCALE_ATTR) ||
|
|
212
|
-
element.hasAttribute(STUDIO_ORIGINAL_ROTATE_ATTR) ||
|
|
213
|
-
element.hasAttribute(STUDIO_ORIGINAL_INLINE_ROTATE_ATTR) ||
|
|
214
|
-
Boolean(element.style.getPropertyValue(STUDIO_OFFSET_X_PROP)) ||
|
|
215
|
-
Boolean(element.style.getPropertyValue(STUDIO_OFFSET_Y_PROP)) ||
|
|
216
|
-
Boolean(element.style.getPropertyValue(STUDIO_WIDTH_PROP)) ||
|
|
217
|
-
Boolean(element.style.getPropertyValue(STUDIO_HEIGHT_PROP)) ||
|
|
218
|
-
Boolean(element.style.getPropertyValue(STUDIO_ROTATION_PROP)),
|
|
219
|
-
);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
188
|
/* ── Clear functions ──────────────────────────────────────────────── */
|
|
223
189
|
type BoxSizeProperty =
|
|
224
190
|
| "width"
|
|
@@ -151,7 +151,6 @@ export function useDomEditSession({
|
|
|
151
151
|
setAgentModalOpen,
|
|
152
152
|
setAgentPromptSelectionContext,
|
|
153
153
|
setAgentModalAnchorPoint,
|
|
154
|
-
preloadAgentPromptSnippet,
|
|
155
154
|
handleAskAgent,
|
|
156
155
|
handleAgentModalSubmit,
|
|
157
156
|
} = useAskAgentModal({
|
|
@@ -181,10 +180,6 @@ export function useDomEditSession({
|
|
|
181
180
|
applyDomSelection,
|
|
182
181
|
resolveDomSelectionFromPreviewPoint,
|
|
183
182
|
updateDomEditHoverSelection,
|
|
184
|
-
preloadAgentPromptSnippet,
|
|
185
|
-
setAgentPromptSelectionContext,
|
|
186
|
-
setAgentModalAnchorPoint,
|
|
187
|
-
setAgentModalOpen,
|
|
188
183
|
onClickToSource,
|
|
189
184
|
});
|
|
190
185
|
|
|
@@ -1,16 +1,8 @@
|
|
|
1
1
|
import { useCallback } from "react";
|
|
2
2
|
import { liveTime, usePlayerStore } from "../player";
|
|
3
|
-
import {
|
|
4
|
-
getPreviewLocalPointer,
|
|
5
|
-
buildRasterClickSelectionContext,
|
|
6
|
-
pauseStudioPreviewPlayback,
|
|
7
|
-
} from "../utils/studioPreviewHelpers";
|
|
3
|
+
import { pauseStudioPreviewPlayback } from "../utils/studioPreviewHelpers";
|
|
8
4
|
import { STUDIO_PREVIEW_SELECTION_ENABLED } from "../components/editor/manualEditingAvailability";
|
|
9
|
-
import {
|
|
10
|
-
isLargeRasterDomEditSelection,
|
|
11
|
-
type DomEditSelection,
|
|
12
|
-
} from "../components/editor/domEditing";
|
|
13
|
-
import type { AgentModalAnchorPoint } from "../utils/studioHelpers";
|
|
5
|
+
import { type DomEditSelection } from "../components/editor/domEditing";
|
|
14
6
|
|
|
15
7
|
// ── Types ──
|
|
16
8
|
|
|
@@ -32,12 +24,6 @@ export interface UsePreviewInteractionParams {
|
|
|
32
24
|
) => DomEditSelection | null;
|
|
33
25
|
updateDomEditHoverSelection: (selection: DomEditSelection | null) => void;
|
|
34
26
|
|
|
35
|
-
// From useAskAgentModal
|
|
36
|
-
preloadAgentPromptSnippet: (selection: DomEditSelection) => Promise<void>;
|
|
37
|
-
setAgentPromptSelectionContext: (context: string | undefined) => void;
|
|
38
|
-
setAgentModalAnchorPoint: (point: AgentModalAnchorPoint | null) => void;
|
|
39
|
-
setAgentModalOpen: (open: boolean) => void;
|
|
40
|
-
|
|
41
27
|
onClickToSource?: (selection: DomEditSelection) => void;
|
|
42
28
|
}
|
|
43
29
|
|
|
@@ -51,10 +37,6 @@ export function usePreviewInteraction({
|
|
|
51
37
|
applyDomSelection,
|
|
52
38
|
resolveDomSelectionFromPreviewPoint,
|
|
53
39
|
updateDomEditHoverSelection,
|
|
54
|
-
preloadAgentPromptSnippet,
|
|
55
|
-
setAgentPromptSelectionContext,
|
|
56
|
-
setAgentModalAnchorPoint,
|
|
57
|
-
setAgentModalOpen,
|
|
58
40
|
onClickToSource,
|
|
59
41
|
}: UsePreviewInteractionParams) {
|
|
60
42
|
const handlePreviewCanvasMouseDown = useCallback(
|
|
@@ -69,37 +51,17 @@ export function usePreviewInteraction({
|
|
|
69
51
|
}
|
|
70
52
|
e.preventDefault();
|
|
71
53
|
e.stopPropagation();
|
|
72
|
-
const localPointer = previewIframeRef.current
|
|
73
|
-
? getPreviewLocalPointer(previewIframeRef.current, e.clientX, e.clientY)
|
|
74
|
-
: null;
|
|
75
54
|
applyDomSelection(nextSelection, { additive: e.shiftKey });
|
|
76
55
|
if (!e.shiftKey && e.altKey && onClickToSource) {
|
|
77
56
|
onClickToSource(nextSelection);
|
|
78
57
|
}
|
|
79
|
-
if (
|
|
80
|
-
!e.shiftKey &&
|
|
81
|
-
localPointer &&
|
|
82
|
-
isLargeRasterDomEditSelection(nextSelection, localPointer.viewport)
|
|
83
|
-
) {
|
|
84
|
-
setAgentPromptSelectionContext(
|
|
85
|
-
buildRasterClickSelectionContext(nextSelection, localPointer),
|
|
86
|
-
);
|
|
87
|
-
setAgentModalAnchorPoint({ x: e.clientX, y: e.clientY });
|
|
88
|
-
void preloadAgentPromptSnippet(nextSelection);
|
|
89
|
-
setAgentModalOpen(true);
|
|
90
|
-
}
|
|
91
58
|
},
|
|
92
59
|
[
|
|
93
60
|
applyDomSelection,
|
|
94
61
|
captionEditMode,
|
|
95
62
|
compositionLoading,
|
|
96
63
|
onClickToSource,
|
|
97
|
-
preloadAgentPromptSnippet,
|
|
98
64
|
resolveDomSelectionFromPreviewPoint,
|
|
99
|
-
previewIframeRef,
|
|
100
|
-
setAgentModalAnchorPoint,
|
|
101
|
-
setAgentModalOpen,
|
|
102
|
-
setAgentPromptSelectionContext,
|
|
103
65
|
],
|
|
104
66
|
);
|
|
105
67
|
|
|
@@ -2,10 +2,7 @@ import { useCallback, useRef } from "react";
|
|
|
2
2
|
import type { TimelineElement } from "../player";
|
|
3
3
|
import { usePlayerStore } from "../player";
|
|
4
4
|
import { applyPatchByTarget, readAttributeByTarget } from "../utils/sourcePatcher";
|
|
5
|
-
import {
|
|
6
|
-
buildTrackZIndexMap,
|
|
7
|
-
formatTimelineAttributeNumber,
|
|
8
|
-
} from "../player/components/timelineEditing";
|
|
5
|
+
import { formatTimelineAttributeNumber } from "../player/components/timelineEditing";
|
|
9
6
|
import {
|
|
10
7
|
buildTimelineAssetId,
|
|
11
8
|
buildTimelineAssetInsertHtml,
|
|
@@ -101,16 +98,6 @@ export function useTimelineEditing({
|
|
|
101
98
|
throw new Error(`Timeline element ${element.id} is missing a patchable target`);
|
|
102
99
|
}
|
|
103
100
|
|
|
104
|
-
const resolvedTargetPath = targetPath || "index.html";
|
|
105
|
-
const relevantElements = timelineElements
|
|
106
|
-
.map((te) =>
|
|
107
|
-
(te.key ?? te.id) === (element.key ?? element.id)
|
|
108
|
-
? { ...te, start: updates.start, track: updates.track }
|
|
109
|
-
: te,
|
|
110
|
-
)
|
|
111
|
-
.filter((te) => (te.sourceFile || activeCompPath || "index.html") === resolvedTargetPath);
|
|
112
|
-
const trackZIndices = buildTrackZIndexMap(relevantElements.map((te) => te.track));
|
|
113
|
-
|
|
114
101
|
let patchedContent = applyPatchByTarget(originalContent, patchTarget, {
|
|
115
102
|
type: "attribute",
|
|
116
103
|
property: "start",
|
|
@@ -121,17 +108,6 @@ export function useTimelineEditing({
|
|
|
121
108
|
property: "track-index",
|
|
122
109
|
value: String(updates.track),
|
|
123
110
|
});
|
|
124
|
-
for (const te of relevantElements) {
|
|
125
|
-
const elementTarget = buildPatchTarget(te);
|
|
126
|
-
if (!elementTarget) continue;
|
|
127
|
-
const nextZIndex = trackZIndices.get(te.track);
|
|
128
|
-
if (nextZIndex == null) continue;
|
|
129
|
-
patchedContent = applyPatchByTarget(patchedContent, elementTarget, {
|
|
130
|
-
type: "inline-style",
|
|
131
|
-
property: "z-index",
|
|
132
|
-
value: String(nextZIndex),
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
111
|
|
|
136
112
|
if (patchedContent === originalContent) {
|
|
137
113
|
throw new Error(`Unable to patch timeline element ${element.id} in ${targetPath}`);
|
|
@@ -150,14 +126,7 @@ export function useTimelineEditing({
|
|
|
150
126
|
|
|
151
127
|
reloadPreview();
|
|
152
128
|
},
|
|
153
|
-
[
|
|
154
|
-
activeCompPath,
|
|
155
|
-
recordEdit,
|
|
156
|
-
timelineElements,
|
|
157
|
-
writeProjectFile,
|
|
158
|
-
domEditSaveTimestampRef,
|
|
159
|
-
reloadPreview,
|
|
160
|
-
],
|
|
129
|
+
[activeCompPath, recordEdit, writeProjectFile, domEditSaveTimestampRef, reloadPreview],
|
|
161
130
|
);
|
|
162
131
|
|
|
163
132
|
const handleTimelineElementResize = useCallback(
|
|
@@ -247,14 +216,6 @@ export function useTimelineEditing({
|
|
|
247
216
|
throw new Error(`Timeline element ${element.id} is missing a patchable target`);
|
|
248
217
|
}
|
|
249
218
|
|
|
250
|
-
const resolvedTargetPath = targetPath || "index.html";
|
|
251
|
-
const remainingElements = timelineElements.filter(
|
|
252
|
-
(te) =>
|
|
253
|
-
(te.key ?? te.id) !== (element.key ?? element.id) &&
|
|
254
|
-
(te.sourceFile || activeCompPath || "index.html") === resolvedTargetPath,
|
|
255
|
-
);
|
|
256
|
-
const trackZIndices = buildTrackZIndexMap(remainingElements.map((te) => te.track));
|
|
257
|
-
|
|
258
219
|
const removeResponse = await fetch(
|
|
259
220
|
`/api/projects/${pid}/file-mutations/remove-element/${encodeURIComponent(targetPath)}`,
|
|
260
221
|
{
|
|
@@ -271,19 +232,8 @@ export function useTimelineEditing({
|
|
|
271
232
|
changed?: boolean;
|
|
272
233
|
content?: string;
|
|
273
234
|
};
|
|
274
|
-
|
|
235
|
+
const patchedContent =
|
|
275
236
|
typeof removeData.content === "string" ? removeData.content : originalContent;
|
|
276
|
-
for (const te of remainingElements) {
|
|
277
|
-
const elementTarget = buildPatchTarget(te);
|
|
278
|
-
if (!elementTarget) continue;
|
|
279
|
-
const nextZIndex = trackZIndices.get(te.track);
|
|
280
|
-
if (nextZIndex == null) continue;
|
|
281
|
-
patchedContent = applyPatchByTarget(patchedContent, elementTarget, {
|
|
282
|
-
type: "inline-style",
|
|
283
|
-
property: "z-index",
|
|
284
|
-
value: String(nextZIndex),
|
|
285
|
-
});
|
|
286
|
-
}
|
|
287
237
|
|
|
288
238
|
domEditSaveTimestampRef.current = Date.now();
|
|
289
239
|
await saveProjectFilesWithHistory({
|
|
@@ -352,26 +302,10 @@ export function useTimelineEditing({
|
|
|
352
302
|
const relevantElements = timelineElements.filter(
|
|
353
303
|
(te) => (te.sourceFile || activeCompPath || "index.html") === resolvedTargetPath,
|
|
354
304
|
);
|
|
355
|
-
const
|
|
356
|
-
...relevantElements.map((te) => te.track),
|
|
357
|
-
placement.track,
|
|
358
|
-
]);
|
|
359
|
-
|
|
360
|
-
let patchedContent = originalContent;
|
|
361
|
-
for (const te of relevantElements) {
|
|
362
|
-
const elementTarget = buildPatchTarget(te);
|
|
363
|
-
if (!elementTarget) continue;
|
|
364
|
-
const nextZIndex = trackZIndices.get(te.track);
|
|
365
|
-
if (nextZIndex == null) continue;
|
|
366
|
-
patchedContent = applyPatchByTarget(patchedContent, elementTarget, {
|
|
367
|
-
type: "inline-style",
|
|
368
|
-
property: "z-index",
|
|
369
|
-
value: String(nextZIndex),
|
|
370
|
-
});
|
|
371
|
-
}
|
|
305
|
+
const newElementZIndex = Math.max(1, relevantElements.length + 1);
|
|
372
306
|
|
|
373
|
-
patchedContent = insertTimelineAssetIntoSource(
|
|
374
|
-
|
|
307
|
+
const patchedContent = insertTimelineAssetIntoSource(
|
|
308
|
+
originalContent,
|
|
375
309
|
buildTimelineAssetInsertHtml({
|
|
376
310
|
id: newId,
|
|
377
311
|
assetPath: resolvedAssetSrc,
|
|
@@ -379,7 +313,7 @@ export function useTimelineEditing({
|
|
|
379
313
|
start: normalizedStart,
|
|
380
314
|
duration: normalizedDuration,
|
|
381
315
|
track: placement.track,
|
|
382
|
-
zIndex:
|
|
316
|
+
zIndex: newElementZIndex,
|
|
383
317
|
geometry: resolveTimelineAssetInitialGeometry(originalContent),
|
|
384
318
|
}),
|
|
385
319
|
);
|