@hyperframes/studio 0.6.0-alpha.12 → 0.6.0-alpha.13
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-DjsVzYFP.js → hyperframes-player-DMgdgHZd.js} +1 -1
- package/dist/assets/index-B0OzpJPU.css +1 -0
- package/dist/assets/index-SEkerIt9.js +110 -0
- package/dist/index.html +2 -2
- package/package.json +4 -4
- package/src/App.tsx +35 -302
- package/src/components/editor/DomEditOverlay.tsx +15 -1
- package/src/components/editor/PropertyPanel.tsx +158 -27
- package/src/components/editor/domEditing.ts +38 -5
- package/src/components/editor/manualEditingAvailability.test.ts +2 -2
- package/src/components/editor/manualEditingAvailability.ts +1 -1
- package/src/components/nle/NLELayout.tsx +0 -10
- package/src/player/components/Player.tsx +19 -1
- package/src/player/components/Timeline.test.ts +0 -8
- package/src/player/components/Timeline.tsx +2 -83
- package/src/player/components/TimelineClip.tsx +9 -244
- package/dist/assets/index-FWg79aJz.css +0 -1
- package/dist/assets/index-xdyn_qRZ.js +0 -110
- package/src/player/components/TimelineClip.test.ts +0 -92
package/dist/index.html
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
|
|
6
6
|
<title>HyperFrames Studio</title>
|
|
7
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/assets/index-SEkerIt9.js"></script>
|
|
8
|
+
<link rel="stylesheet" crossorigin href="/assets/index-B0OzpJPU.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
11
11
|
<div id="root"></div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperframes/studio",
|
|
3
|
-
"version": "0.6.0-alpha.
|
|
3
|
+
"version": "0.6.0-alpha.13",
|
|
4
4
|
"description": "",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
"@phosphor-icons/react": "^2.1.10",
|
|
33
33
|
"codemirror": "^6.0.1",
|
|
34
34
|
"motion": "^12.38.0",
|
|
35
|
-
"@hyperframes/core": "0.6.0-alpha.
|
|
36
|
-
"@hyperframes/player": "0.6.0-alpha.
|
|
35
|
+
"@hyperframes/core": "0.6.0-alpha.13",
|
|
36
|
+
"@hyperframes/player": "0.6.0-alpha.13"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@types/react": "^19.0.0",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"vite": "^6.4.2",
|
|
48
48
|
"vitest": "^3.2.4",
|
|
49
49
|
"zustand": "^5.0.0",
|
|
50
|
-
"@hyperframes/producer": "0.6.0-alpha.
|
|
50
|
+
"@hyperframes/producer": "0.6.0-alpha.13"
|
|
51
51
|
},
|
|
52
52
|
"peerDependencies": {
|
|
53
53
|
"react": "^18.0.0 || ^19.0.0",
|
package/src/App.tsx
CHANGED
|
@@ -74,24 +74,19 @@ import {
|
|
|
74
74
|
DomEditOverlay,
|
|
75
75
|
type DomEditGroupPathOffsetCommit,
|
|
76
76
|
} from "./components/editor/DomEditOverlay";
|
|
77
|
-
import { TimelineLayerPanel } from "./components/editor/TimelineLayerPanel";
|
|
78
77
|
import {
|
|
79
78
|
STUDIO_INSPECTOR_PANELS_ENABLED,
|
|
80
79
|
STUDIO_MANUAL_EDITING_DISABLED_TITLE,
|
|
81
80
|
STUDIO_MOTION_PANEL_ENABLED,
|
|
82
81
|
STUDIO_PREVIEW_MANUAL_EDITING_ENABLED,
|
|
83
82
|
STUDIO_PREVIEW_SELECTION_ENABLED,
|
|
84
|
-
STUDIO_TIMELINE_LAYER_INSPECTOR_ENABLED,
|
|
85
83
|
} from "./components/editor/manualEditingAvailability";
|
|
86
84
|
import {
|
|
87
85
|
buildDomEditStylePatchOperation,
|
|
88
86
|
buildDomEditTextPatchOperation,
|
|
89
87
|
buildElementAgentPrompt,
|
|
90
|
-
collectDomEditLayerItems,
|
|
91
|
-
countDomEditChildLayers,
|
|
92
88
|
findElementForSelection,
|
|
93
89
|
findElementForTimelineElement,
|
|
94
|
-
getDomEditLayerKey,
|
|
95
90
|
getDomEditTargetKey,
|
|
96
91
|
isLargeRasterDomEditSelection,
|
|
97
92
|
isTextEditableSelection,
|
|
@@ -99,7 +94,6 @@ import {
|
|
|
99
94
|
serializeDomEditTextFields,
|
|
100
95
|
resolveDomEditSelection,
|
|
101
96
|
type DomEditViewport,
|
|
102
|
-
type DomEditLayerItem,
|
|
103
97
|
type DomEditTextField,
|
|
104
98
|
type DomEditSelection,
|
|
105
99
|
buildDefaultDomEditTextField,
|
|
@@ -134,14 +128,7 @@ import {
|
|
|
134
128
|
upsertStudioGsapMotion,
|
|
135
129
|
} from "./components/editor/studioMotion";
|
|
136
130
|
import { saveProjectFilesWithHistory } from "./utils/studioFileHistory";
|
|
137
|
-
import {
|
|
138
|
-
canInspectTimelineElement,
|
|
139
|
-
getTimelineElementKey,
|
|
140
|
-
getTimelineLayerVisibilityInPreview,
|
|
141
|
-
isTimelineElementActiveAtTime,
|
|
142
|
-
isTimelineLayerVisibleInPreview,
|
|
143
|
-
shouldShowTimelineInspectorBounds,
|
|
144
|
-
} from "./utils/timelineInspector";
|
|
131
|
+
import { getTimelineElementKey, isTimelineElementActiveAtTime } from "./utils/timelineInspector";
|
|
145
132
|
|
|
146
133
|
interface EditingFile {
|
|
147
134
|
path: string;
|
|
@@ -543,105 +530,6 @@ function readPlaybackTime(target: object | null, key: string): number | null {
|
|
|
543
530
|
}
|
|
544
531
|
}
|
|
545
532
|
|
|
546
|
-
interface PreviewPlayerCompat {
|
|
547
|
-
getTime: () => number;
|
|
548
|
-
renderSeek: (timeSeconds: number) => void;
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
function getPreviewPlayer(win: Window | null | undefined): PreviewPlayerCompat | null {
|
|
552
|
-
const player = objectLike(win ? Reflect.get(win, "__player") : null);
|
|
553
|
-
if (!player) return null;
|
|
554
|
-
const getTime = Reflect.get(player, "getTime");
|
|
555
|
-
const renderSeek = Reflect.get(player, "renderSeek");
|
|
556
|
-
if (typeof getTime !== "function" || typeof renderSeek !== "function") return null;
|
|
557
|
-
return {
|
|
558
|
-
getTime: () => {
|
|
559
|
-
const value = getTime.call(player);
|
|
560
|
-
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
561
|
-
},
|
|
562
|
-
renderSeek: (timeSeconds: number) => {
|
|
563
|
-
renderSeek.call(player, timeSeconds);
|
|
564
|
-
},
|
|
565
|
-
};
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
function seekStudioPreview(iframe: HTMLIFrameElement | null, timeSeconds: number): boolean {
|
|
569
|
-
const player = getPreviewPlayer(iframe?.contentWindow);
|
|
570
|
-
if (!player) return false;
|
|
571
|
-
const nextTime = Math.max(0, timeSeconds);
|
|
572
|
-
player.renderSeek(nextTime);
|
|
573
|
-
usePlayerStore.getState().setCurrentTime(nextTime);
|
|
574
|
-
liveTime.notify(nextTime);
|
|
575
|
-
return true;
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
function parseFiniteSeconds(value: string | null): number | null {
|
|
579
|
-
if (value == null || value.trim() === "") return null;
|
|
580
|
-
const parsed = Number.parseFloat(value);
|
|
581
|
-
return Number.isFinite(parsed) ? parsed : null;
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
function resolveLayerVisibleSeekTime(
|
|
585
|
-
layerElement: HTMLElement,
|
|
586
|
-
timelineElement: TimelineElement | null,
|
|
587
|
-
player: PreviewPlayerCompat | null,
|
|
588
|
-
): number | null {
|
|
589
|
-
if (!timelineElement || !player) return null;
|
|
590
|
-
const originalTime = player.getTime();
|
|
591
|
-
|
|
592
|
-
const clipStart = Math.max(0, timelineElement.start);
|
|
593
|
-
const clipEnd = Math.max(clipStart, clipStart + Math.max(0, timelineElement.duration));
|
|
594
|
-
const authoredStart = parseFiniteSeconds(
|
|
595
|
-
layerElement.getAttribute("data-start") ??
|
|
596
|
-
layerElement.closest<HTMLElement>("[data-start]")?.getAttribute("data-start") ??
|
|
597
|
-
null,
|
|
598
|
-
);
|
|
599
|
-
const preferredTime =
|
|
600
|
-
authoredStart == null
|
|
601
|
-
? clipStart
|
|
602
|
-
: Math.min(clipEnd, Math.max(clipStart, clipStart + authoredStart));
|
|
603
|
-
const candidates = [preferredTime, clipStart];
|
|
604
|
-
const duration = clipEnd - clipStart;
|
|
605
|
-
if (duration > 0) {
|
|
606
|
-
const maxSamples = 24;
|
|
607
|
-
const frameStep = 1 / 24;
|
|
608
|
-
const step = Math.max(frameStep, duration / maxSamples);
|
|
609
|
-
for (let time = clipStart; time <= clipEnd + 0.0001; time += step) {
|
|
610
|
-
candidates.push(Math.min(clipEnd, time));
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
candidates.push(clipEnd);
|
|
614
|
-
|
|
615
|
-
let lastTried = preferredTime;
|
|
616
|
-
let clearestVisibleTime: number | null = null;
|
|
617
|
-
let clearestVisibleOpacity = 0;
|
|
618
|
-
let resolvedTime: number | null = null;
|
|
619
|
-
const seen = new Set<string>();
|
|
620
|
-
try {
|
|
621
|
-
for (const candidate of candidates) {
|
|
622
|
-
const time = Math.min(clipEnd, Math.max(clipStart, candidate));
|
|
623
|
-
const key = time.toFixed(4);
|
|
624
|
-
if (seen.has(key)) continue;
|
|
625
|
-
seen.add(key);
|
|
626
|
-
lastTried = time;
|
|
627
|
-
player.renderSeek(time);
|
|
628
|
-
const visibility = getTimelineLayerVisibilityInPreview(layerElement);
|
|
629
|
-
if (visibility.visible && visibility.compositeOpacity > clearestVisibleOpacity) {
|
|
630
|
-
clearestVisibleTime = time;
|
|
631
|
-
clearestVisibleOpacity = visibility.compositeOpacity;
|
|
632
|
-
}
|
|
633
|
-
if (isTimelineLayerVisibleInPreview(layerElement, { minCompositeOpacity: 0.9 })) {
|
|
634
|
-
resolvedTime = time;
|
|
635
|
-
break;
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
} finally {
|
|
639
|
-
player.renderSeek(originalTime);
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
return resolvedTime ?? clearestVisibleTime ?? lastTried;
|
|
643
|
-
}
|
|
644
|
-
|
|
645
533
|
function pauseStudioPreviewPlayback(iframe: HTMLIFrameElement | null): number | null {
|
|
646
534
|
const win = iframe?.contentWindow;
|
|
647
535
|
if (!win) return null;
|
|
@@ -901,9 +789,8 @@ export function StudioApp() {
|
|
|
901
789
|
const [copiedAgentPrompt, setCopiedAgentPrompt] = useState(false);
|
|
902
790
|
const [agentModalOpen, setAgentModalOpen] = useState(false);
|
|
903
791
|
const [previewIframe, setPreviewIframe] = useState<HTMLIFrameElement | null>(null);
|
|
904
|
-
const [inspectedTimelineElementId, setInspectedTimelineElementId] = useState<string | null>(null);
|
|
905
792
|
const [compositionLoading, setCompositionLoading] = useState(true);
|
|
906
|
-
const [
|
|
793
|
+
const [, setPreviewDocumentVersion] = useState(0);
|
|
907
794
|
const refreshPreviewDocumentVersion = useCallback(() => {
|
|
908
795
|
setPreviewDocumentVersion((version) => version + 1);
|
|
909
796
|
window.setTimeout(() => setPreviewDocumentVersion((version) => version + 1), 80);
|
|
@@ -2156,10 +2043,8 @@ export function StudioApp() {
|
|
|
2156
2043
|
|
|
2157
2044
|
const writeHistoryProjectFile = useCallback(
|
|
2158
2045
|
async (path: string, content: string): Promise<void> => {
|
|
2046
|
+
domEditSaveTimestampRef.current = Date.now();
|
|
2159
2047
|
await writeProjectFile(path, content);
|
|
2160
|
-
if (path === STUDIO_MANUAL_EDITS_PATH || path === STUDIO_MOTION_PATH) {
|
|
2161
|
-
domEditSaveTimestampRef.current = Date.now();
|
|
2162
|
-
}
|
|
2163
2048
|
},
|
|
2164
2049
|
[writeProjectFile],
|
|
2165
2050
|
);
|
|
@@ -2207,11 +2092,12 @@ export function StudioApp() {
|
|
|
2207
2092
|
iframe: HTMLIFrameElement | null = previewIframeRef.current,
|
|
2208
2093
|
options?: { forceFromDisk?: boolean; readFromDiskFirst?: boolean },
|
|
2209
2094
|
) => {
|
|
2210
|
-
const readRevision = studioManualEditRevisionRef.current;
|
|
2211
2095
|
const readFromDiskFirst = Boolean(options?.forceFromDisk || options?.readFromDiskFirst);
|
|
2212
2096
|
if (!readFromDiskFirst) {
|
|
2213
2097
|
applyCurrentStudioManualEditsToPreview(iframe);
|
|
2098
|
+
return;
|
|
2214
2099
|
}
|
|
2100
|
+
const readRevision = studioManualEditRevisionRef.current;
|
|
2215
2101
|
let content: string;
|
|
2216
2102
|
try {
|
|
2217
2103
|
content = await readOptionalProjectFile(STUDIO_MANUAL_EDITS_PATH);
|
|
@@ -2219,31 +2105,19 @@ export function StudioApp() {
|
|
|
2219
2105
|
const message =
|
|
2220
2106
|
error instanceof Error ? error.message : "Failed to read manual edit manifest";
|
|
2221
2107
|
showToast(message);
|
|
2222
|
-
|
|
2223
|
-
applyCurrentStudioManualEditsToPreview(iframe);
|
|
2224
|
-
}
|
|
2108
|
+
applyCurrentStudioManualEditsToPreview(iframe);
|
|
2225
2109
|
return;
|
|
2226
2110
|
}
|
|
2227
2111
|
if (options?.forceFromDisk || readRevision === studioManualEditRevisionRef.current) {
|
|
2228
2112
|
studioManualEditManifestRef.current = parseStudioManualEditManifest(content);
|
|
2229
2113
|
if (options?.forceFromDisk) studioManualEditRevisionRef.current += 1;
|
|
2230
|
-
applyCurrentStudioManualEditsToPreview(iframe);
|
|
2231
|
-
return;
|
|
2232
|
-
}
|
|
2233
|
-
if (readFromDiskFirst) {
|
|
2234
|
-
applyCurrentStudioManualEditsToPreview(iframe);
|
|
2235
2114
|
}
|
|
2115
|
+
applyCurrentStudioManualEditsToPreview(iframe);
|
|
2236
2116
|
},
|
|
2237
2117
|
[applyCurrentStudioManualEditsToPreview, readOptionalProjectFile, showToast],
|
|
2238
2118
|
);
|
|
2239
2119
|
applyStudioManualEditsToPreviewRef.current = applyStudioManualEditsToPreview;
|
|
2240
2120
|
|
|
2241
|
-
const applyStudioManualEditsToPreviewAfterRefresh = useCallback(
|
|
2242
|
-
(iframe: HTMLIFrameElement | null = previewIframeRef.current) =>
|
|
2243
|
-
applyStudioManualEditsToPreview(iframe, { readFromDiskFirst: true }),
|
|
2244
|
-
[applyStudioManualEditsToPreview],
|
|
2245
|
-
);
|
|
2246
|
-
|
|
2247
2121
|
const applyCurrentStudioMotionToPreview = useCallback(
|
|
2248
2122
|
(iframe: HTMLIFrameElement | null = previewIframeRef.current) => {
|
|
2249
2123
|
if (!iframe) return;
|
|
@@ -2282,43 +2156,32 @@ export function StudioApp() {
|
|
|
2282
2156
|
iframe: HTMLIFrameElement | null = previewIframeRef.current,
|
|
2283
2157
|
options?: { forceFromDisk?: boolean; readFromDiskFirst?: boolean },
|
|
2284
2158
|
) => {
|
|
2285
|
-
const readRevision = studioMotionRevisionRef.current;
|
|
2286
2159
|
const readFromDiskFirst = Boolean(options?.forceFromDisk || options?.readFromDiskFirst);
|
|
2287
2160
|
if (!readFromDiskFirst) {
|
|
2288
2161
|
applyCurrentStudioMotionToPreview(iframe);
|
|
2162
|
+
return;
|
|
2289
2163
|
}
|
|
2164
|
+
const readRevision = studioMotionRevisionRef.current;
|
|
2290
2165
|
let content: string;
|
|
2291
2166
|
try {
|
|
2292
2167
|
content = await readOptionalProjectFile(STUDIO_MOTION_PATH);
|
|
2293
2168
|
} catch (error) {
|
|
2294
2169
|
const message = error instanceof Error ? error.message : "Failed to read motion manifest";
|
|
2295
2170
|
showToast(message);
|
|
2296
|
-
|
|
2297
|
-
applyCurrentStudioMotionToPreview(iframe);
|
|
2298
|
-
}
|
|
2171
|
+
applyCurrentStudioMotionToPreview(iframe);
|
|
2299
2172
|
return;
|
|
2300
2173
|
}
|
|
2301
2174
|
if (options?.forceFromDisk || readRevision === studioMotionRevisionRef.current) {
|
|
2302
2175
|
studioMotionManifestRef.current = parseStudioMotionManifest(content);
|
|
2303
2176
|
if (options?.forceFromDisk) studioMotionRevisionRef.current += 1;
|
|
2304
2177
|
setStudioMotionRevision((revision) => revision + 1);
|
|
2305
|
-
applyCurrentStudioMotionToPreview(iframe);
|
|
2306
|
-
return;
|
|
2307
|
-
}
|
|
2308
|
-
if (readFromDiskFirst) {
|
|
2309
|
-
applyCurrentStudioMotionToPreview(iframe);
|
|
2310
2178
|
}
|
|
2179
|
+
applyCurrentStudioMotionToPreview(iframe);
|
|
2311
2180
|
},
|
|
2312
2181
|
[applyCurrentStudioMotionToPreview, readOptionalProjectFile, showToast],
|
|
2313
2182
|
);
|
|
2314
2183
|
applyStudioMotionToPreviewRef.current = applyStudioMotionToPreview;
|
|
2315
2184
|
|
|
2316
|
-
const applyStudioMotionToPreviewAfterRefresh = useCallback(
|
|
2317
|
-
(iframe: HTMLIFrameElement | null = previewIframeRef.current) =>
|
|
2318
|
-
applyStudioMotionToPreview(iframe, { readFromDiskFirst: true }),
|
|
2319
|
-
[applyStudioMotionToPreview],
|
|
2320
|
-
);
|
|
2321
|
-
|
|
2322
2185
|
const commitStudioManualEditManifestOptimistically = useCallback(
|
|
2323
2186
|
(
|
|
2324
2187
|
updateManifest: (manifest: StudioManualEditManifest) => StudioManualEditManifest,
|
|
@@ -2475,6 +2338,19 @@ export function StudioApp() {
|
|
|
2475
2338
|
return;
|
|
2476
2339
|
}
|
|
2477
2340
|
|
|
2341
|
+
// Reload the iframe in-place rather than recreating the Player component.
|
|
2342
|
+
// This preserves the <hyperframes-player> web component and its shader
|
|
2343
|
+
// transition cache — only the iframe document reloads, so transitions that
|
|
2344
|
+
// weren't touched by the undo/redo don't need to rebuild from scratch.
|
|
2345
|
+
const iframe = previewIframeRef.current;
|
|
2346
|
+
if (iframe?.contentWindow) {
|
|
2347
|
+
try {
|
|
2348
|
+
iframe.contentWindow.location.reload();
|
|
2349
|
+
return;
|
|
2350
|
+
} catch {
|
|
2351
|
+
// Cross-origin or detached — fall through to full refresh
|
|
2352
|
+
}
|
|
2353
|
+
}
|
|
2478
2354
|
setRefreshKey((key) => key + 1);
|
|
2479
2355
|
},
|
|
2480
2356
|
[applyStudioManualEditsToPreview, applyStudioMotionToPreview],
|
|
@@ -2637,148 +2513,20 @@ export function StudioApp() {
|
|
|
2637
2513
|
[activeCompPath, buildDomSelectionFromTarget, compIdToSrc, isMasterView],
|
|
2638
2514
|
);
|
|
2639
2515
|
|
|
2640
|
-
const inspectedTimelineElement = useMemo(
|
|
2641
|
-
() =>
|
|
2642
|
-
timelineElements.find(
|
|
2643
|
-
(element) => getTimelineElementKey(element) === inspectedTimelineElementId,
|
|
2644
|
-
) ?? null,
|
|
2645
|
-
[inspectedTimelineElementId, timelineElements],
|
|
2646
|
-
);
|
|
2647
|
-
|
|
2648
|
-
const timelineLayerChildCounts = useMemo(() => {
|
|
2649
|
-
void previewDocumentVersion;
|
|
2650
|
-
const counts = new Map<string, number>();
|
|
2651
|
-
if (!STUDIO_TIMELINE_LAYER_INSPECTOR_ENABLED || !inspectedTimelineElement) return counts;
|
|
2652
|
-
|
|
2653
|
-
const key = getTimelineElementKey(inspectedTimelineElement);
|
|
2654
|
-
if (key) {
|
|
2655
|
-
const selection = buildDomSelectionForTimelineElement(inspectedTimelineElement);
|
|
2656
|
-
const count = countDomEditChildLayers(selection?.element, {
|
|
2657
|
-
activeCompositionPath: activeCompPath,
|
|
2658
|
-
isMasterView,
|
|
2659
|
-
});
|
|
2660
|
-
if (count > 0) counts.set(key, count);
|
|
2661
|
-
}
|
|
2662
|
-
|
|
2663
|
-
return counts;
|
|
2664
|
-
}, [
|
|
2665
|
-
activeCompPath,
|
|
2666
|
-
buildDomSelectionForTimelineElement,
|
|
2667
|
-
inspectedTimelineElement,
|
|
2668
|
-
isMasterView,
|
|
2669
|
-
previewDocumentVersion,
|
|
2670
|
-
]);
|
|
2671
|
-
|
|
2672
|
-
const inspectedTimelineLayers = useMemo(() => {
|
|
2673
|
-
void previewDocumentVersion;
|
|
2674
|
-
if (!STUDIO_TIMELINE_LAYER_INSPECTOR_ENABLED || !inspectedTimelineElement) return [];
|
|
2675
|
-
const selection = buildDomSelectionForTimelineElement(inspectedTimelineElement);
|
|
2676
|
-
return collectDomEditLayerItems(selection?.element, {
|
|
2677
|
-
activeCompositionPath: activeCompPath,
|
|
2678
|
-
isMasterView,
|
|
2679
|
-
});
|
|
2680
|
-
}, [
|
|
2681
|
-
activeCompPath,
|
|
2682
|
-
buildDomSelectionForTimelineElement,
|
|
2683
|
-
inspectedTimelineElement,
|
|
2684
|
-
isMasterView,
|
|
2685
|
-
previewDocumentVersion,
|
|
2686
|
-
]);
|
|
2687
|
-
|
|
2688
|
-
const selectedTimelineLayerKey = useMemo(
|
|
2689
|
-
() => (domEditSelection ? getDomEditLayerKey(domEditSelection) : null),
|
|
2690
|
-
[domEditSelection],
|
|
2691
|
-
);
|
|
2692
|
-
|
|
2693
2516
|
const handleTimelineElementSelect = useCallback(
|
|
2694
2517
|
(element: TimelineElement | null) => {
|
|
2695
2518
|
if (!STUDIO_INSPECTOR_PANELS_ENABLED) return;
|
|
2696
2519
|
if (!element) {
|
|
2697
2520
|
applyDomSelection(null, { revealPanel: false });
|
|
2698
|
-
setInspectedTimelineElementId(null);
|
|
2699
|
-
return;
|
|
2700
|
-
}
|
|
2701
|
-
|
|
2702
|
-
const selection = buildDomSelectionForTimelineElement(element);
|
|
2703
|
-
if (selection) applyDomSelection(selection);
|
|
2704
|
-
|
|
2705
|
-
const key = getTimelineElementKey(element);
|
|
2706
|
-
if (STUDIO_TIMELINE_LAYER_INSPECTOR_ENABLED && key && canInspectTimelineElement(element)) {
|
|
2707
|
-
setInspectedTimelineElementId(key);
|
|
2708
|
-
setLeftCollapsed(false);
|
|
2709
|
-
|
|
2710
|
-
const iframe = previewIframeRef.current;
|
|
2711
|
-
if (!shouldShowTimelineInspectorBounds(currentTime, element)) {
|
|
2712
|
-
seekStudioPreview(iframe, element.start);
|
|
2713
|
-
}
|
|
2714
|
-
} else {
|
|
2715
|
-
setInspectedTimelineElementId(null);
|
|
2716
|
-
}
|
|
2717
|
-
},
|
|
2718
|
-
[applyDomSelection, buildDomSelectionForTimelineElement, currentTime],
|
|
2719
|
-
);
|
|
2720
|
-
|
|
2721
|
-
const handleTimelineElementInspect = useCallback(
|
|
2722
|
-
(element: TimelineElement) => {
|
|
2723
|
-
if (!STUDIO_TIMELINE_LAYER_INSPECTOR_ENABLED || !STUDIO_INSPECTOR_PANELS_ENABLED) return;
|
|
2724
|
-
if (!canInspectTimelineElement(element)) {
|
|
2725
|
-
showToast("Audio clips do not have visual layers.", "info");
|
|
2726
2521
|
return;
|
|
2727
2522
|
}
|
|
2728
2523
|
|
|
2729
|
-
const key = getTimelineElementKey(element);
|
|
2730
|
-
if (!key) return;
|
|
2731
|
-
setInspectedTimelineElementId((current) => (current === key ? null : key));
|
|
2732
|
-
setLeftCollapsed(false);
|
|
2733
|
-
|
|
2734
|
-
const iframe = previewIframeRef.current;
|
|
2735
|
-
if (!shouldShowTimelineInspectorBounds(currentTime, element)) {
|
|
2736
|
-
seekStudioPreview(iframe, element.start);
|
|
2737
|
-
}
|
|
2738
|
-
|
|
2739
2524
|
const selection = buildDomSelectionForTimelineElement(element);
|
|
2740
2525
|
if (selection) applyDomSelection(selection);
|
|
2741
2526
|
},
|
|
2742
|
-
[applyDomSelection, buildDomSelectionForTimelineElement
|
|
2743
|
-
);
|
|
2744
|
-
|
|
2745
|
-
const handleTimelineLayerSelect = useCallback(
|
|
2746
|
-
(layer: DomEditLayerItem) => {
|
|
2747
|
-
if (!STUDIO_INSPECTOR_PANELS_ENABLED) return;
|
|
2748
|
-
|
|
2749
|
-
const iframe = previewIframeRef.current;
|
|
2750
|
-
const player = getPreviewPlayer(iframe?.contentWindow);
|
|
2751
|
-
const visibleTime = resolveLayerVisibleSeekTime(
|
|
2752
|
-
layer.element,
|
|
2753
|
-
inspectedTimelineElement,
|
|
2754
|
-
player,
|
|
2755
|
-
);
|
|
2756
|
-
if (visibleTime != null) {
|
|
2757
|
-
seekStudioPreview(iframe, visibleTime);
|
|
2758
|
-
}
|
|
2759
|
-
|
|
2760
|
-
const selection = buildDomSelectionFromTarget(layer.element, { preferClipAncestor: false });
|
|
2761
|
-
if (!selection) {
|
|
2762
|
-
showToast("Studio could not resolve this nested layer.", "error");
|
|
2763
|
-
return;
|
|
2764
|
-
}
|
|
2765
|
-
|
|
2766
|
-
applyDomSelection(selection);
|
|
2767
|
-
requestAnimationFrame(refreshPreviewDocumentVersion);
|
|
2768
|
-
},
|
|
2769
|
-
[
|
|
2770
|
-
applyDomSelection,
|
|
2771
|
-
buildDomSelectionFromTarget,
|
|
2772
|
-
inspectedTimelineElement,
|
|
2773
|
-
refreshPreviewDocumentVersion,
|
|
2774
|
-
showToast,
|
|
2775
|
-
],
|
|
2527
|
+
[applyDomSelection, buildDomSelectionForTimelineElement],
|
|
2776
2528
|
);
|
|
2777
2529
|
|
|
2778
|
-
const handleTimelineLayerPanelClose = useCallback(() => {
|
|
2779
|
-
setInspectedTimelineElementId(null);
|
|
2780
|
-
}, []);
|
|
2781
|
-
|
|
2782
2530
|
const preloadAgentPromptSnippet = useCallback(
|
|
2783
2531
|
async (selection: DomEditSelection) => {
|
|
2784
2532
|
const pid = projectIdRef.current;
|
|
@@ -3558,8 +3306,8 @@ export function StudioApp() {
|
|
|
3558
3306
|
attachErrorCapture();
|
|
3559
3307
|
syncPreviewHistoryHotkey(previewIframe);
|
|
3560
3308
|
void (async () => {
|
|
3561
|
-
await
|
|
3562
|
-
await
|
|
3309
|
+
await applyStudioManualEditsToPreviewRef.current(previewIframe);
|
|
3310
|
+
await applyStudioMotionToPreviewRef.current(previewIframe);
|
|
3563
3311
|
})();
|
|
3564
3312
|
syncSelectionFromDocument();
|
|
3565
3313
|
refreshPreviewDocumentVersion();
|
|
@@ -3570,8 +3318,8 @@ export function StudioApp() {
|
|
|
3570
3318
|
attachErrorCapture();
|
|
3571
3319
|
syncPreviewHistoryHotkey(previewIframe);
|
|
3572
3320
|
void (async () => {
|
|
3573
|
-
await
|
|
3574
|
-
await
|
|
3321
|
+
await applyStudioManualEditsToPreviewRef.current(previewIframe);
|
|
3322
|
+
await applyStudioMotionToPreviewRef.current(previewIframe);
|
|
3575
3323
|
})();
|
|
3576
3324
|
syncSelectionFromDocument();
|
|
3577
3325
|
refreshPreviewDocumentVersion();
|
|
@@ -3584,8 +3332,6 @@ export function StudioApp() {
|
|
|
3584
3332
|
}, [
|
|
3585
3333
|
activeCompPath,
|
|
3586
3334
|
applyDomSelection,
|
|
3587
|
-
applyStudioManualEditsToPreviewAfterRefresh,
|
|
3588
|
-
applyStudioMotionToPreviewAfterRefresh,
|
|
3589
3335
|
buildDomSelectionFromTarget,
|
|
3590
3336
|
captionEditMode,
|
|
3591
3337
|
previewIframe,
|
|
@@ -4054,26 +3800,15 @@ export function StudioApp() {
|
|
|
4054
3800
|
const motionPanelActive =
|
|
4055
3801
|
STUDIO_INSPECTOR_PANELS_ENABLED && STUDIO_MOTION_PANEL_ENABLED && rightPanelTab === "motion";
|
|
4056
3802
|
const inspectorPanelActive = designPanelActive || motionPanelActive;
|
|
3803
|
+
const isPlaying = usePlayerStore((s) => s.isPlaying);
|
|
4057
3804
|
const shouldShowSelectedDomBounds =
|
|
4058
3805
|
inspectorPanelActive &&
|
|
4059
3806
|
!rightCollapsed &&
|
|
3807
|
+
!isPlaying &&
|
|
4060
3808
|
(!selectedTimelineElement ||
|
|
4061
3809
|
isTimelineElementActiveAtTime(currentTime, selectedTimelineElement));
|
|
4062
3810
|
const inspectorButtonActive =
|
|
4063
3811
|
STUDIO_INSPECTOR_PANELS_ENABLED && !rightCollapsed && inspectorPanelActive;
|
|
4064
|
-
const timelineLayerPanel =
|
|
4065
|
-
STUDIO_TIMELINE_LAYER_INSPECTOR_ENABLED &&
|
|
4066
|
-
inspectedTimelineElement &&
|
|
4067
|
-
inspectedTimelineLayers.length > 0 ? (
|
|
4068
|
-
<TimelineLayerPanel
|
|
4069
|
-
clipLabel={getTimelineElementLabel(inspectedTimelineElement)}
|
|
4070
|
-
layers={inspectedTimelineLayers}
|
|
4071
|
-
selectedLayerKey={selectedTimelineLayerKey}
|
|
4072
|
-
onSelectLayer={handleTimelineLayerSelect}
|
|
4073
|
-
onClose={handleTimelineLayerPanelClose}
|
|
4074
|
-
/>
|
|
4075
|
-
) : null;
|
|
4076
|
-
|
|
4077
3812
|
if (resolving || !projectId) {
|
|
4078
3813
|
return (
|
|
4079
3814
|
<div className="h-full w-full bg-neutral-950 flex items-center justify-center">
|
|
@@ -4287,7 +4022,6 @@ export function StudioApp() {
|
|
|
4287
4022
|
onLint={handleLint}
|
|
4288
4023
|
linting={linting}
|
|
4289
4024
|
onToggleCollapse={toggleLeftSidebar}
|
|
4290
|
-
takeoverContent={timelineLayerPanel}
|
|
4291
4025
|
/>
|
|
4292
4026
|
)}
|
|
4293
4027
|
|
|
@@ -4319,16 +4053,12 @@ export function StudioApp() {
|
|
|
4319
4053
|
onResizeElement={handleTimelineElementResize}
|
|
4320
4054
|
onBlockedEditAttempt={handleBlockedTimelineEdit}
|
|
4321
4055
|
onSelectTimelineElement={handleTimelineElementSelect}
|
|
4322
|
-
onInspectTimelineElement={handleTimelineElementInspect}
|
|
4323
|
-
inspectedTimelineElementId={inspectedTimelineElementId}
|
|
4324
|
-
timelineLayerChildCounts={timelineLayerChildCounts}
|
|
4325
4056
|
onCompIdToSrcChange={setCompIdToSrc}
|
|
4326
4057
|
onCompositionLoadingChange={setCompositionLoading}
|
|
4327
4058
|
onCompositionChange={(compPath) => {
|
|
4328
4059
|
// Sync activeCompPath when user drills down via timeline double-click
|
|
4329
4060
|
// or navigates back via breadcrumb — keeps sidebar + thumbnails in sync.
|
|
4330
4061
|
setActiveCompPath(compPath);
|
|
4331
|
-
setInspectedTimelineElementId(null);
|
|
4332
4062
|
refreshPreviewDocumentVersion();
|
|
4333
4063
|
}}
|
|
4334
4064
|
onIframeRef={handlePreviewIframeRef}
|
|
@@ -4340,7 +4070,10 @@ export function StudioApp() {
|
|
|
4340
4070
|
iframeRef={previewIframeRef}
|
|
4341
4071
|
activeCompositionPath={activeCompPath}
|
|
4342
4072
|
hoverSelection={
|
|
4343
|
-
STUDIO_PREVIEW_SELECTION_ENABLED &&
|
|
4073
|
+
STUDIO_PREVIEW_SELECTION_ENABLED &&
|
|
4074
|
+
!captionEditMode &&
|
|
4075
|
+
!compositionLoading &&
|
|
4076
|
+
!isPlaying
|
|
4344
4077
|
? domEditHoverSelection
|
|
4345
4078
|
: null
|
|
4346
4079
|
}
|
|
@@ -85,6 +85,20 @@ interface DomEditOverlayProps {
|
|
|
85
85
|
onRotationCommit: (selection: DomEditSelection, next: { angle: number }) => Promise<void> | void;
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
function isElementVisibleForOverlay(el: HTMLElement): boolean {
|
|
89
|
+
const win = el.ownerDocument.defaultView;
|
|
90
|
+
if (!win) return true;
|
|
91
|
+
let current: HTMLElement | null = el;
|
|
92
|
+
while (current) {
|
|
93
|
+
const computed = win.getComputedStyle(current);
|
|
94
|
+
if (computed.display === "none" || computed.visibility === "hidden") return false;
|
|
95
|
+
const opacity = Number.parseFloat(computed.opacity);
|
|
96
|
+
if (Number.isFinite(opacity) && opacity <= 0.01) return false;
|
|
97
|
+
current = current.parentElement;
|
|
98
|
+
}
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
|
|
88
102
|
function toOverlayRect(
|
|
89
103
|
overlayEl: HTMLDivElement,
|
|
90
104
|
iframe: HTMLIFrameElement,
|
|
@@ -534,7 +548,7 @@ export const DomEditOverlay = memo(function DomEditOverlay({
|
|
|
534
548
|
|
|
535
549
|
if (sel) {
|
|
536
550
|
const el = resolveElement(doc, sel, resolvedElementRef);
|
|
537
|
-
if (el) {
|
|
551
|
+
if (el && isElementVisibleForOverlay(el)) {
|
|
538
552
|
setNextOverlayRect(toOverlayRect(overlayEl, iframe, el));
|
|
539
553
|
} else {
|
|
540
554
|
clearOverlayRect();
|