@hyperframes/studio 0.7.21 → 0.7.23
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-BGuumSiM.js +459 -0
- package/dist/assets/{index-jbPe1Dih.js → index-B8XMgbWF.js} +157 -157
- package/dist/assets/{index-pAaVqALC.js → index-BRZTrsSs.js} +1 -1
- package/dist/assets/{index-gFA786gK.js → index-DPO5Gw_v.js} +1 -1
- package/dist/{chunk-SBGXX7WY.js → chunk-AN2EWWK3.js} +59 -108
- package/dist/chunk-AN2EWWK3.js.map +1 -0
- package/dist/{domEditingLayers-VZMLL4AP.js → domEditingLayers-EK7R7R4G.js} +4 -2
- package/dist/index.html +1 -1
- package/dist/index.js +163 -99
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
- package/src/App.tsx +2 -5
- package/src/components/StudioHeader.tsx +2 -3
- package/src/components/editor/PropertyPanel.tsx +11 -8
- package/src/components/editor/domEditingDom.ts +4 -33
- package/src/components/editor/domEditingLayers.ts +88 -108
- package/src/components/editor/manualEditingAvailability.ts +0 -10
- package/src/components/editor/propertyPanelColorGradingSection.tsx +0 -4
- package/src/components/editor/propertyPanelMediaSection.tsx +0 -6
- package/src/contexts/ViewModeContext.tsx +10 -19
- package/src/hooks/useDomEditSession.test.tsx +277 -0
- package/src/hooks/useDomEditSession.ts +7 -4
- package/src/telemetry/config.ts +10 -33
- package/src/telemetry/distinctId.test.ts +103 -0
- package/src/telemetry/distinctId.ts +125 -0
- package/src/utils/safeStorage.ts +20 -0
- package/src/utils/sdkCutover.ts +21 -112
- package/src/utils/sdkCutoverEligibility.ts +116 -0
- package/src/utils/sdkResolverShadow.test.ts +101 -5
- package/src/utils/sdkResolverShadow.ts +67 -4
- package/src/utils/studioTelemetry.ts +5 -19
- package/dist/assets/hyperframes-player-DNLS_l47.js +0 -459
- package/dist/chunk-SBGXX7WY.js.map +0 -1
- package/src/hooks/useDomEditSession.test.ts +0 -41
- /package/dist/{domEditingLayers-VZMLL4AP.js.map → domEditingLayers-EK7R7R4G.js.map} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperframes/studio",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.23",
|
|
4
4
|
"description": "",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -46,11 +46,11 @@
|
|
|
46
46
|
"gsap": "^3.13.0",
|
|
47
47
|
"marked": "^14.1.4",
|
|
48
48
|
"mediabunny": "^1.45.3",
|
|
49
|
-
"@hyperframes/
|
|
50
|
-
"@hyperframes/player": "0.7.
|
|
51
|
-
"@hyperframes/
|
|
52
|
-
"@hyperframes/
|
|
53
|
-
"@hyperframes/
|
|
49
|
+
"@hyperframes/core": "0.7.23",
|
|
50
|
+
"@hyperframes/player": "0.7.23",
|
|
51
|
+
"@hyperframes/sdk": "0.7.23",
|
|
52
|
+
"@hyperframes/studio-server": "0.7.23",
|
|
53
|
+
"@hyperframes/parsers": "0.7.23"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"@types/react": "19",
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"vite": "^6.4.2",
|
|
66
66
|
"vitest": "^3.2.4",
|
|
67
67
|
"zustand": "^5.0.0",
|
|
68
|
-
"@hyperframes/producer": "0.7.
|
|
68
|
+
"@hyperframes/producer": "0.7.23"
|
|
69
69
|
},
|
|
70
70
|
"peerDependencies": {
|
|
71
71
|
"react": "19",
|
package/src/App.tsx
CHANGED
|
@@ -36,10 +36,7 @@ import {
|
|
|
36
36
|
import type { DomEditSelection } from "./components/editor/domEditing";
|
|
37
37
|
import { StudioHeader } from "./components/StudioHeader";
|
|
38
38
|
import { useGestureCommit } from "./hooks/useGestureCommit";
|
|
39
|
-
import {
|
|
40
|
-
STUDIO_KEYFRAMES_ENABLED,
|
|
41
|
-
STUDIO_STORYBOARD_ENABLED,
|
|
42
|
-
} from "./components/editor/manualEditingAvailability";
|
|
39
|
+
import { STUDIO_KEYFRAMES_ENABLED } from "./components/editor/manualEditingAvailability";
|
|
43
40
|
import { GestureTrailOverlay } from "./components/editor/GestureTrailOverlay";
|
|
44
41
|
import { StudioLeftSidebar } from "./components/StudioLeftSidebar";
|
|
45
42
|
import { StudioPreviewArea } from "./components/StudioPreviewArea";
|
|
@@ -65,7 +62,7 @@ type CanvasRect = { left: number; top: number; width: number; height: number };
|
|
|
65
62
|
export function StudioApp() {
|
|
66
63
|
const { projectId, resolving, waitingForServer } = useServerConnection();
|
|
67
64
|
const initialUrlStateRef = useRef(readStudioUrlStateFromWindow());
|
|
68
|
-
const viewModeValue = useViewModeState(
|
|
65
|
+
const viewModeValue = useViewModeState();
|
|
69
66
|
|
|
70
67
|
// sessionStorage-backed: fires once per tab, survives HMR remounts
|
|
71
68
|
useEffect(() => {
|
|
@@ -3,7 +3,6 @@ import { RotateCcw, RotateCw, Camera } from "../icons/SystemIcons";
|
|
|
3
3
|
import {
|
|
4
4
|
STUDIO_INSPECTOR_PANELS_ENABLED,
|
|
5
5
|
STUDIO_MANUAL_EDITING_DISABLED_TITLE,
|
|
6
|
-
STUDIO_STORYBOARD_ENABLED,
|
|
7
6
|
} from "./editor/manualEditingAvailability";
|
|
8
7
|
import { getHistoryShortcutLabel } from "../utils/studioHelpers";
|
|
9
8
|
import { useStudioShellContext } from "../contexts/StudioContext";
|
|
@@ -204,8 +203,8 @@ export function StudioHeader({
|
|
|
204
203
|
</span>
|
|
205
204
|
<span className="text-[11px] font-medium text-neutral-300">{projectId}</span>
|
|
206
205
|
</div>
|
|
207
|
-
{/* Center: storyboard / preview toggle
|
|
208
|
-
|
|
206
|
+
{/* Center: storyboard / preview toggle */}
|
|
207
|
+
<ViewModeToggle />
|
|
209
208
|
{/* Right: toolbar buttons */}
|
|
210
209
|
<div className="flex items-center gap-1.5">
|
|
211
210
|
<button
|
|
@@ -14,11 +14,10 @@ import {
|
|
|
14
14
|
import { MetricField, Section } from "./propertyPanelPrimitives";
|
|
15
15
|
import { createTransformCommitHandlers } from "./propertyPanelTransformCommit";
|
|
16
16
|
import { classifyPropertyGroup } from "@hyperframes/core/gsap-parser";
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
} from "./propertyPanelColorGradingSection";
|
|
17
|
+
import { resolveEditingSections } from "@hyperframes/core/editing";
|
|
18
|
+
import { MediaSection } from "./propertyPanelMediaSection";
|
|
19
|
+
import { ColorGradingSection } from "./propertyPanelColorGradingSection";
|
|
20
|
+
import { domEditSelectionToFacts } from "./domEditingLayers";
|
|
22
21
|
import { TextSection, StyleSections } from "./propertyPanelSections";
|
|
23
22
|
import { GsapAnimationSection } from "./GsapAnimationSection";
|
|
24
23
|
import { PropertyPanel3dTransform } from "./propertyPanel3dTransform";
|
|
@@ -199,6 +198,10 @@ export const PropertyPanel = memo(function PropertyPanel({
|
|
|
199
198
|
const manualRotationEditingDisabled = !element.capabilities.canApplyManualRotation;
|
|
200
199
|
const sourceLabel = element.id ? `#${element.id}` : element.selector;
|
|
201
200
|
const showEditableSections = element.capabilities.canEditStyles;
|
|
201
|
+
// Capabilities are already resolved on the selection; recompute only sections,
|
|
202
|
+
// feeding the live GSAP tween count (arrives on the gsapAnimations prop, not the
|
|
203
|
+
// selection) so the Timing section shows for pure-GSAP elements with no data-start.
|
|
204
|
+
const sections = resolveEditingSections(domEditSelectionToFacts(element, gsapAnimations.length));
|
|
202
205
|
const manualOffset = readStudioPathOffset(element.element);
|
|
203
206
|
const manualSize = readStudioBoxSize(element.element);
|
|
204
207
|
const resolvedWidth =
|
|
@@ -339,7 +342,7 @@ export const PropertyPanel = memo(function PropertyPanel({
|
|
|
339
342
|
onRemoveTextField={onRemoveTextField}
|
|
340
343
|
/>
|
|
341
344
|
|
|
342
|
-
{
|
|
345
|
+
{sections.timing && (
|
|
343
346
|
// Render whenever there's an authored clip range OR animations to infer
|
|
344
347
|
// one from — a pure-GSAP element with no data-start still gets a Timing
|
|
345
348
|
// range (TimingSection derives it from its tweens).
|
|
@@ -349,7 +352,7 @@ export const PropertyPanel = memo(function PropertyPanel({
|
|
|
349
352
|
onSetAttribute={onSetAttribute}
|
|
350
353
|
/>
|
|
351
354
|
)}
|
|
352
|
-
{
|
|
355
|
+
{sections.media && (
|
|
353
356
|
<MediaSection
|
|
354
357
|
projectDir={projectDir}
|
|
355
358
|
element={element}
|
|
@@ -360,7 +363,7 @@ export const PropertyPanel = memo(function PropertyPanel({
|
|
|
360
363
|
/>
|
|
361
364
|
)}
|
|
362
365
|
|
|
363
|
-
{STUDIO_COLOR_GRADING_ENABLED &&
|
|
366
|
+
{STUDIO_COLOR_GRADING_ENABLED && sections.colorGrading && (
|
|
364
367
|
<ColorGradingSection
|
|
365
368
|
key={[
|
|
366
369
|
element.id ?? "",
|
|
@@ -19,39 +19,10 @@ export function isHtmlElement(value: unknown): value is HTMLElement {
|
|
|
19
19
|
|
|
20
20
|
// ─── Style parsing ────────────────────────────────────────────────────────────
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const parsed = parseFloat(trimmed);
|
|
27
|
-
return Number.isFinite(parsed) ? parsed : null;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function isIdentityTransform(value: string | undefined): boolean {
|
|
31
|
-
const transform = (value ?? "none").trim();
|
|
32
|
-
if (!transform || transform === "none") return true;
|
|
33
|
-
|
|
34
|
-
const matrix = transform.match(/^matrix\(([^)]+)\)$/i);
|
|
35
|
-
if (matrix) {
|
|
36
|
-
const values = matrix[1].split(",").map((part) => Number.parseFloat(part.trim()));
|
|
37
|
-
if (values.length !== 6 || values.some((part) => !Number.isFinite(part))) return false;
|
|
38
|
-
return (
|
|
39
|
-
Math.abs(values[0] - 1) < 0.0001 &&
|
|
40
|
-
Math.abs(values[1]) < 0.0001 &&
|
|
41
|
-
Math.abs(values[2]) < 0.0001 &&
|
|
42
|
-
Math.abs(values[3] - 1) < 0.0001 &&
|
|
43
|
-
Math.abs(values[4]) < 0.0001 &&
|
|
44
|
-
Math.abs(values[5]) < 0.0001
|
|
45
|
-
);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const matrix3d = transform.match(/^matrix3d\(([^)]+)\)$/i);
|
|
49
|
-
if (!matrix3d) return false;
|
|
50
|
-
const values = matrix3d[1].split(",").map((part) => Number.parseFloat(part.trim()));
|
|
51
|
-
if (values.length !== 16 || values.some((part) => !Number.isFinite(part))) return false;
|
|
52
|
-
const identity = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
|
|
53
|
-
return values.every((part, index) => Math.abs(part - identity[index]) < 0.0001);
|
|
54
|
-
}
|
|
22
|
+
// Single source of truth lives in @hyperframes/core/editing so the studio
|
|
23
|
+
// callers and the core resolver can't drift. Re-exported here to keep this
|
|
24
|
+
// module's public surface (6 studio callers import parsePx from it).
|
|
25
|
+
export { parsePx } from "@hyperframes/core/editing";
|
|
55
26
|
|
|
56
27
|
export function isTextBearingTag(tagName: string): boolean {
|
|
57
28
|
return ["div", "span", "p", "strong", "h1", "h2", "h3", "h4", "h5", "h6"].includes(tagName);
|
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
* for dom editing.
|
|
4
4
|
*/
|
|
5
5
|
import type { PatchOperation } from "../../utils/sourcePatcher";
|
|
6
|
+
import {
|
|
7
|
+
resolveEditingAffordances,
|
|
8
|
+
resolveEditingSections,
|
|
9
|
+
type EditableElementFacts,
|
|
10
|
+
} from "@hyperframes/core/editing";
|
|
6
11
|
import { groupScopedLayerRoots, resolveGroupCapture } from "./domEditingGroups";
|
|
7
12
|
import type {
|
|
8
13
|
DomEditCapabilities,
|
|
@@ -21,9 +26,7 @@ import {
|
|
|
21
26
|
getSelectorIndex,
|
|
22
27
|
getSourceFileForElement,
|
|
23
28
|
isHtmlElement,
|
|
24
|
-
isIdentityTransform,
|
|
25
29
|
isTextBearingTag,
|
|
26
|
-
parsePx,
|
|
27
30
|
} from "./domEditingDom";
|
|
28
31
|
import {
|
|
29
32
|
findElementForSelection,
|
|
@@ -171,12 +174,66 @@ export function buildDefaultDomEditTextField(base?: Partial<DomEditTextField>):
|
|
|
171
174
|
|
|
172
175
|
// ─── Capabilities ────────────────────────────────────────────────────────────
|
|
173
176
|
|
|
174
|
-
|
|
177
|
+
/**
|
|
178
|
+
* Build the geometry/capability half of EditableElementFacts. Section inputs
|
|
179
|
+
* (text/timing/animation) are irrelevant to capability resolution, so they are
|
|
180
|
+
* zeroed here. Shared by the wrapper and the live-selection path so the two
|
|
181
|
+
* fact-construction sites can't disagree.
|
|
182
|
+
*/
|
|
183
|
+
function capabilityFacts(geometry: {
|
|
184
|
+
hasStableTarget: boolean;
|
|
185
|
+
tag: string;
|
|
186
|
+
inlineStyles: Record<string, string>;
|
|
187
|
+
computedStyles: Record<string, string>;
|
|
188
|
+
isCompositionHost: boolean;
|
|
189
|
+
isCompositionRoot: boolean;
|
|
190
|
+
isInsideLockedComposition: boolean;
|
|
191
|
+
isMasterView: boolean;
|
|
192
|
+
existsInSource: boolean;
|
|
193
|
+
}): EditableElementFacts {
|
|
194
|
+
return {
|
|
195
|
+
...geometry,
|
|
196
|
+
hasEditableText: false,
|
|
197
|
+
hasTimingStart: false,
|
|
198
|
+
animationCount: 0,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Build core EditableElementFacts from a fully-resolved DomEditSelection.
|
|
204
|
+
* `animationCount` is supplied by the caller because live GSAP tweens arrive on
|
|
205
|
+
* a separate channel (the PropertyPanel `gsapAnimations` prop), not on the
|
|
206
|
+
* selection — `selection.gsapAnimations` is never populated.
|
|
207
|
+
*/
|
|
208
|
+
export function domEditSelectionToFacts(
|
|
209
|
+
selection: DomEditSelection,
|
|
210
|
+
animationCount = selection.gsapAnimations?.length ?? 0,
|
|
211
|
+
): EditableElementFacts {
|
|
212
|
+
return {
|
|
213
|
+
hasStableTarget: Boolean(selection.selector || selection.hfId),
|
|
214
|
+
tag: selection.tagName,
|
|
215
|
+
inlineStyles: selection.inlineStyles,
|
|
216
|
+
computedStyles: selection.computedStyles,
|
|
217
|
+
isCompositionHost: selection.isCompositionHost,
|
|
218
|
+
isCompositionRoot: false,
|
|
219
|
+
isInsideLockedComposition: selection.isInsideLockedComposition,
|
|
220
|
+
isMasterView: false,
|
|
221
|
+
existsInSource: true,
|
|
222
|
+
hasEditableText: selection.textFields.length > 0,
|
|
223
|
+
hasTimingStart: selection.dataAttributes.start != null,
|
|
224
|
+
animationCount,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Resolve DOM edit capabilities for a given element.
|
|
230
|
+
* Thin wrapper over core resolveEditingAffordances — kept for backward
|
|
231
|
+
* compatibility (tests and the barrel import this signature directly).
|
|
232
|
+
*/
|
|
175
233
|
export function resolveDomEditCapabilities(args: {
|
|
176
234
|
selector?: string;
|
|
177
235
|
hfId?: string;
|
|
178
236
|
tagName?: string;
|
|
179
|
-
className?: string;
|
|
180
237
|
inlineStyles: Record<string, string>;
|
|
181
238
|
computedStyles: Record<string, string>;
|
|
182
239
|
isCompositionHost: boolean;
|
|
@@ -185,92 +242,19 @@ export function resolveDomEditCapabilities(args: {
|
|
|
185
242
|
isMasterView: boolean;
|
|
186
243
|
existsInSource?: boolean;
|
|
187
244
|
}): DomEditCapabilities {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
if (args.existsInSource === false) {
|
|
204
|
-
return {
|
|
205
|
-
canSelect: true,
|
|
206
|
-
canEditStyles: false,
|
|
207
|
-
canMove: false,
|
|
208
|
-
canResize: false,
|
|
209
|
-
canApplyManualOffset: false,
|
|
210
|
-
canApplyManualSize: false,
|
|
211
|
-
canApplyManualRotation: false,
|
|
212
|
-
reasonIfDisabled: "This element is generated by a script and cannot be edited visually.",
|
|
213
|
-
};
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
if (args.isCompositionRoot) {
|
|
217
|
-
return {
|
|
218
|
-
canSelect: true,
|
|
219
|
-
canEditStyles: true,
|
|
220
|
-
canMove: false,
|
|
221
|
-
canResize: false,
|
|
222
|
-
canApplyManualOffset: false,
|
|
223
|
-
canApplyManualSize: false,
|
|
224
|
-
canApplyManualRotation: false,
|
|
225
|
-
reasonIfDisabled: "The root composition defines the preview bounds.",
|
|
226
|
-
};
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
const position = args.computedStyles.position;
|
|
230
|
-
const left = parsePx(args.inlineStyles.left) ?? parsePx(args.computedStyles.left);
|
|
231
|
-
const top = parsePx(args.inlineStyles.top) ?? parsePx(args.computedStyles.top);
|
|
232
|
-
const width = parsePx(args.inlineStyles.width) ?? parsePx(args.computedStyles.width);
|
|
233
|
-
const height = parsePx(args.inlineStyles.height) ?? parsePx(args.computedStyles.height);
|
|
234
|
-
const hasTransformDrivenGeometry = !isIdentityTransform(args.computedStyles.transform);
|
|
235
|
-
|
|
236
|
-
const canMove =
|
|
237
|
-
(position === "absolute" || position === "fixed") &&
|
|
238
|
-
left != null &&
|
|
239
|
-
top != null &&
|
|
240
|
-
!hasTransformDrivenGeometry;
|
|
241
|
-
|
|
242
|
-
const canResize = canMove && (width != null || height != null);
|
|
243
|
-
const canApplyManualGeometry = !args.isCompositionHost;
|
|
244
|
-
const canApplyManualOffset = canApplyManualGeometry;
|
|
245
|
-
const canApplyManualSize = canApplyManualGeometry;
|
|
246
|
-
const canApplyManualRotation = canApplyManualGeometry;
|
|
247
|
-
const reasonIfDisabled = canApplyManualGeometry
|
|
248
|
-
? undefined
|
|
249
|
-
: "Select an internal layer to transform it.";
|
|
250
|
-
|
|
251
|
-
if (args.isCompositionHost && args.isMasterView) {
|
|
252
|
-
return {
|
|
253
|
-
canSelect: true,
|
|
254
|
-
canEditStyles: false,
|
|
255
|
-
canMove,
|
|
256
|
-
canResize,
|
|
257
|
-
canApplyManualOffset,
|
|
258
|
-
canApplyManualSize,
|
|
259
|
-
canApplyManualRotation,
|
|
260
|
-
reasonIfDisabled,
|
|
261
|
-
};
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
return {
|
|
265
|
-
canSelect: true,
|
|
266
|
-
canEditStyles: true,
|
|
267
|
-
canMove,
|
|
268
|
-
canResize,
|
|
269
|
-
canApplyManualOffset,
|
|
270
|
-
canApplyManualSize,
|
|
271
|
-
canApplyManualRotation,
|
|
272
|
-
reasonIfDisabled,
|
|
273
|
-
};
|
|
245
|
+
return resolveEditingAffordances(
|
|
246
|
+
capabilityFacts({
|
|
247
|
+
hasStableTarget: Boolean(args.selector || args.hfId),
|
|
248
|
+
tag: (args.tagName ?? "div").toLowerCase(),
|
|
249
|
+
inlineStyles: args.inlineStyles,
|
|
250
|
+
computedStyles: args.computedStyles,
|
|
251
|
+
isCompositionHost: args.isCompositionHost,
|
|
252
|
+
isCompositionRoot: args.isCompositionRoot ?? false,
|
|
253
|
+
isInsideLockedComposition: args.isInsideLockedComposition ?? false,
|
|
254
|
+
isMasterView: args.isMasterView,
|
|
255
|
+
existsInSource: args.existsInSource ?? true,
|
|
256
|
+
}),
|
|
257
|
+
).capabilities;
|
|
274
258
|
}
|
|
275
259
|
|
|
276
260
|
// ─── Element label ────────────────────────────────────────────────────────────
|
|
@@ -354,19 +338,19 @@ export async function resolveDomEditSelection(
|
|
|
354
338
|
if (selectorIndex != null) probeTarget.selectorIndex = selectorIndex;
|
|
355
339
|
existsInSource = await probeSourceElement(options.projectId, sourceFile, probeTarget);
|
|
356
340
|
}
|
|
357
|
-
const capabilities =
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
341
|
+
const capabilities = resolveEditingAffordances(
|
|
342
|
+
capabilityFacts({
|
|
343
|
+
hasStableTarget: Boolean(selector || hfId),
|
|
344
|
+
tag: current.tagName.toLowerCase(),
|
|
345
|
+
inlineStyles,
|
|
346
|
+
computedStyles,
|
|
347
|
+
isCompositionHost: Boolean(compositionSrc),
|
|
348
|
+
isCompositionRoot,
|
|
349
|
+
isInsideLockedComposition: isInsideLocked,
|
|
350
|
+
isMasterView: options.isMasterView,
|
|
351
|
+
existsInSource: existsInSource ?? true,
|
|
352
|
+
}),
|
|
353
|
+
).capabilities;
|
|
370
354
|
const rect = current.getBoundingClientRect();
|
|
371
355
|
|
|
372
356
|
return {
|
|
@@ -554,11 +538,7 @@ export function getDomEditTargetKey(
|
|
|
554
538
|
}
|
|
555
539
|
|
|
556
540
|
export function isTextEditableSelection(selection: DomEditSelection): boolean {
|
|
557
|
-
return (
|
|
558
|
-
selection.textFields.length > 0 &&
|
|
559
|
-
!selection.isCompositionHost &&
|
|
560
|
-
!selection.isInsideLockedComposition
|
|
561
|
-
);
|
|
541
|
+
return resolveEditingSections(domEditSelectionToFacts(selection)).text;
|
|
562
542
|
}
|
|
563
543
|
|
|
564
544
|
// buildElementAgentPrompt is in domEditingAgentPrompt.ts
|
|
@@ -82,16 +82,6 @@ export const STUDIO_RAZOR_TOOL_ENABLED = resolveStudioBooleanEnvFlag(
|
|
|
82
82
|
true,
|
|
83
83
|
);
|
|
84
84
|
|
|
85
|
-
// Storyboard view: a top-level, toggleable view that renders STORYBOARD.md as a
|
|
86
|
-
// contact sheet of live HTML frame tiles, replacing the timeline/preview stage.
|
|
87
|
-
// Opt-in / off by default until the experience is ready for broad exposure.
|
|
88
|
-
// VITE_STUDIO_ENABLE_STORYBOARD=1 npx hyperframes preview
|
|
89
|
-
export const STUDIO_STORYBOARD_ENABLED = resolveStudioBooleanEnvFlag(
|
|
90
|
-
env,
|
|
91
|
-
["VITE_STUDIO_ENABLE_STORYBOARD", "VITE_STUDIO_STORYBOARD_ENABLED"],
|
|
92
|
-
false,
|
|
93
|
-
);
|
|
94
|
-
|
|
95
85
|
export const STUDIO_PREVIEW_SELECTION_ENABLED = STUDIO_INSPECTOR_PANELS_ENABLED;
|
|
96
86
|
|
|
97
87
|
// Stage 7 Step 3c: SDK cutover — routes inline-style ops through SDK dispatch
|
|
@@ -50,10 +50,6 @@ interface RuntimeColorGradingStatus {
|
|
|
50
50
|
message: string;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
export function isColorGradingCapableElement(element: DomEditSelection): boolean {
|
|
54
|
-
return element.tagName === "video" || element.tagName === "img";
|
|
55
|
-
}
|
|
56
|
-
|
|
57
53
|
function readColorGradingFromElement(element: DomEditSelection): NormalizedHfColorGrading {
|
|
58
54
|
const grading =
|
|
59
55
|
normalizeHfColorGrading(element.dataAttributes[COLOR_GRADING_DATA_KEY]) ??
|
|
@@ -10,12 +10,6 @@ import {
|
|
|
10
10
|
} from "./propertyPanelHelpers";
|
|
11
11
|
import { Section, SegmentedControl, SelectField, SliderControl } from "./propertyPanelPrimitives";
|
|
12
12
|
|
|
13
|
-
const MEDIA_TAGS = new Set(["video", "audio"]);
|
|
14
|
-
|
|
15
|
-
export function isMediaElement(element: DomEditSelection): boolean {
|
|
16
|
-
return MEDIA_TAGS.has(element.tagName);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
13
|
export function MediaSection({
|
|
20
14
|
projectDir,
|
|
21
15
|
element,
|
|
@@ -44,14 +44,11 @@ export interface ViewModeValue {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
/**
|
|
47
|
-
* Owns the view-mode state
|
|
48
|
-
* mode is
|
|
49
|
-
* fully inert until the flag is on.
|
|
47
|
+
* Owns the view-mode state — initial read from `?view=`, toggling, popstate sync.
|
|
48
|
+
* Storyboard mode is always available; no flag gating.
|
|
50
49
|
*/
|
|
51
|
-
export function useViewModeState(
|
|
52
|
-
const [viewMode, setMode] = useState<StudioViewMode>(() =>
|
|
53
|
-
enabled ? readViewModeFromUrl() : "timeline",
|
|
54
|
-
);
|
|
50
|
+
export function useViewModeState(): ViewModeValue {
|
|
51
|
+
const [viewMode, setMode] = useState<StudioViewMode>(() => readViewModeFromUrl());
|
|
55
52
|
|
|
56
53
|
// Reflect genuine browser back/forward between history entries with a different
|
|
57
54
|
// `?view=`. Note: our own writes use `replaceState` (below), which does NOT fire
|
|
@@ -60,23 +57,17 @@ export function useViewModeState(enabled: boolean): ViewModeValue {
|
|
|
60
57
|
// the mount-time read); a scripted `pushState`/`replaceState` to `?view=` would not be
|
|
61
58
|
// reflected here, by design.
|
|
62
59
|
useEffect(() => {
|
|
63
|
-
if (!enabled) return;
|
|
64
60
|
const onPopState = () => setMode(readViewModeFromUrl());
|
|
65
61
|
window.addEventListener("popstate", onPopState);
|
|
66
62
|
return () => window.removeEventListener("popstate", onPopState);
|
|
67
|
-
}, [
|
|
63
|
+
}, []);
|
|
68
64
|
|
|
69
|
-
const setViewMode = useCallback(
|
|
70
|
-
(mode
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
writeViewModeToUrl(mode);
|
|
74
|
-
},
|
|
75
|
-
[enabled],
|
|
76
|
-
);
|
|
65
|
+
const setViewMode = useCallback((mode: StudioViewMode) => {
|
|
66
|
+
setMode(mode);
|
|
67
|
+
writeViewModeToUrl(mode);
|
|
68
|
+
}, []);
|
|
77
69
|
|
|
78
|
-
|
|
79
|
-
return useMemo(() => ({ viewMode: effectiveMode, setViewMode }), [effectiveMode, setViewMode]);
|
|
70
|
+
return useMemo(() => ({ viewMode, setViewMode }), [viewMode, setViewMode]);
|
|
80
71
|
}
|
|
81
72
|
|
|
82
73
|
const ViewModeContext = createContext<ViewModeValue | null>(null);
|