@hyperframes/studio 0.7.58 → 0.7.59
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-CtTDO63S.js → hyperframes-player-Bm07FLkl.js} +1 -1
- package/dist/assets/{index-C47jAC3Q.js → index-B995FG46.js} +1 -1
- package/dist/assets/index-CrkAdJkb.js +426 -0
- package/dist/assets/index-Dj5p8U_A.css +1 -0
- package/dist/assets/{index-DeQPzqwH.js → index-FvzmPhfG.js} +1 -1
- package/dist/index.d.ts +12 -3
- package/dist/index.html +2 -2
- package/dist/index.js +13514 -8528
- package/dist/index.js.map +1 -1
- package/dist/styles/tailwind-preset.d.ts +5 -0
- package/dist/styles/tailwind-preset.js +8 -1
- package/dist/styles/tailwind-preset.js.map +1 -1
- package/package.json +7 -7
- package/src/App.tsx +3 -3
- package/src/components/DesignPanelPromoteProvider.tsx +15 -2
- package/src/components/StudioRightPanel.tsx +29 -51
- package/src/components/editor/AnimationCard.test.tsx +134 -0
- package/src/components/editor/AnimationCard.tsx +19 -4
- package/src/components/editor/ArcPathControls.tsx +1 -0
- package/src/components/editor/GestureRecordControl.tsx +7 -1
- package/src/components/editor/GsapAnimationSection.tsx +109 -14
- package/src/components/editor/InspectorHeaderActions.tsx +42 -4
- package/src/components/editor/PropertyPanel.test.tsx +948 -0
- package/src/components/editor/PropertyPanel.tsx +144 -148
- package/src/components/editor/PropertyPanelEmptyState.test.tsx +74 -0
- package/src/components/editor/PropertyPanelEmptyState.tsx +179 -1
- package/src/components/editor/PropertyPanelFlat.tsx +580 -0
- package/src/components/editor/PropertyPanelFlatFooter.test.tsx +83 -0
- package/src/components/editor/PropertyPanelFlatFooter.tsx +73 -0
- package/src/components/editor/PropertyPanelFlatHeader.test.tsx +79 -0
- package/src/components/editor/PropertyPanelFlatHeader.tsx +112 -0
- package/src/components/editor/gsapAnimationCallbacks.ts +27 -0
- package/src/components/editor/gsapLivePreview.ts +34 -0
- package/src/components/editor/manualEditingAvailability.test.ts +10 -0
- package/src/components/editor/manualEditingAvailability.ts +9 -0
- package/src/components/editor/propertyPanel3dTransform.tsx +5 -0
- package/src/components/editor/propertyPanelColor.test.tsx +28 -0
- package/src/components/editor/propertyPanelColor.tsx +31 -1
- package/src/components/editor/propertyPanelColorGradingControls.tsx +11 -2
- package/src/components/editor/propertyPanelColorGradingSection.tsx +45 -365
- package/src/components/editor/propertyPanelColorGradingSlider.tsx +19 -5
- package/src/components/editor/propertyPanelFill.tsx +22 -5
- package/src/components/editor/propertyPanelFlatColorGradingSection.test.tsx +622 -0
- package/src/components/editor/propertyPanelFlatColorGradingSection.tsx +570 -0
- package/src/components/editor/propertyPanelFlatLayoutSection.test.tsx +293 -0
- package/src/components/editor/propertyPanelFlatLayoutSection.tsx +383 -0
- package/src/components/editor/propertyPanelFlatMaskInsetRows.tsx +102 -0
- package/src/components/editor/propertyPanelFlatMediaSection.test.tsx +436 -0
- package/src/components/editor/propertyPanelFlatMediaSection.tsx +286 -0
- package/src/components/editor/propertyPanelFlatMotionSection.test.tsx +272 -0
- package/src/components/editor/propertyPanelFlatMotionSection.tsx +315 -0
- package/src/components/editor/propertyPanelFlatPrimitives.test.tsx +1174 -0
- package/src/components/editor/propertyPanelFlatPrimitives.tsx +563 -0
- package/src/components/editor/propertyPanelFlatSelectRow.tsx +101 -0
- package/src/components/editor/propertyPanelFlatStyleHelpers.test.ts +23 -0
- package/src/components/editor/propertyPanelFlatStyleHelpers.ts +27 -0
- package/src/components/editor/propertyPanelFlatStyleSections.test.tsx +598 -0
- package/src/components/editor/propertyPanelFlatStyleSections.tsx +516 -0
- package/src/components/editor/propertyPanelFlatTextSection.test.tsx +465 -0
- package/src/components/editor/propertyPanelFlatTextSection.tsx +407 -0
- package/src/components/editor/propertyPanelFlatTimingDerivation.test.ts +71 -0
- package/src/components/editor/propertyPanelFlatTimingDerivation.ts +59 -0
- package/src/components/editor/propertyPanelFlatToggle.test.tsx +63 -0
- package/src/components/editor/propertyPanelFlatToggle.tsx +53 -0
- package/src/components/editor/propertyPanelFont.test.tsx +30 -0
- package/src/components/editor/propertyPanelFont.tsx +136 -95
- package/src/components/editor/propertyPanelHelpers.ts +73 -0
- package/src/components/editor/propertyPanelInputCoverage.test.tsx +587 -0
- package/src/components/editor/propertyPanelMediaSection.tsx +10 -0
- package/src/components/editor/propertyPanelPrimitives.tsx +60 -29
- package/src/components/editor/propertyPanelSections.test.tsx +161 -0
- package/src/components/editor/propertyPanelSections.tsx +154 -90
- package/src/components/editor/propertyPanelStyleSections.tsx +12 -1
- package/src/components/editor/propertyPanelTimingSection.tsx +1 -1
- package/src/components/editor/propertyPanelTypes.ts +14 -1
- package/src/components/editor/propertyPanelValueTier.test.ts +32 -0
- package/src/components/editor/propertyPanelValueTier.ts +28 -0
- package/src/components/editor/useColorGradingController.test.ts +418 -0
- package/src/components/editor/useColorGradingController.ts +596 -0
- package/src/components/panels/VariablesPanel.tsx +4 -0
- package/src/components/sidebar/CompositionsTab.test.ts +28 -2
- package/src/components/sidebar/CompositionsTab.tsx +3 -1
- package/src/components/storyboard/FramePoster.tsx +24 -4
- package/src/components/storyboard/StoryboardFrameFocus.tsx +4 -0
- package/src/components/storyboard/StoryboardFrameTile.tsx +30 -1
- package/src/components/storyboard/StoryboardGrid.tsx +23 -1
- package/src/components/storyboard/StoryboardLoaded.tsx +58 -2
- package/src/components/storyboard/StoryboardView.tsx +9 -2
- package/src/components/storyboard/frameComments.test.ts +95 -0
- package/src/components/storyboard/frameComments.ts +115 -0
- package/src/components/storyboard/useFrameComments.ts +82 -0
- package/src/contexts/DesignPanelInputContext.test.tsx +81 -0
- package/src/contexts/DesignPanelInputContext.tsx +48 -0
- package/src/contexts/DomEditContext.tsx +4 -0
- package/src/contexts/VariablePromoteContext.test.tsx +67 -0
- package/src/contexts/VariablePromoteContext.tsx +10 -5
- package/src/hooks/domEditCommitRunner.ts +11 -0
- package/src/hooks/gsapScriptCommitTypes.ts +6 -4
- package/src/hooks/timelineTrackVisibility.test.ts +52 -0
- package/src/hooks/timelineTrackVisibility.ts +27 -10
- package/src/hooks/useAppHotkeys.ts +9 -0
- package/src/hooks/useDomEditAttributeCommits.ts +123 -1
- package/src/hooks/useDomEditCommits.ts +29 -175
- package/src/hooks/useDomEditCommitsHelpers.ts +158 -0
- package/src/hooks/useDomEditSession.ts +17 -5
- package/src/hooks/useDomEditTextCommits.ts +14 -9
- package/src/hooks/useDomEditWiring.ts +10 -9
- package/src/hooks/useElementLifecycleOps.ts +8 -3
- package/src/hooks/useFileManager.projectOwnership.test.tsx +98 -0
- package/src/hooks/useFileManager.ts +82 -60
- package/src/hooks/useGsapAnimationOps.ts +7 -6
- package/src/hooks/useGsapKeyframeOps.ts +6 -5
- package/src/hooks/useGsapPropertyDebounce.ts +43 -28
- package/src/hooks/useGsapPropertyDebounceFlush.test.ts +35 -0
- package/src/hooks/useGsapScriptCommits.test.tsx +48 -2
- package/src/hooks/useGsapScriptCommits.ts +150 -50
- package/src/hooks/useGsapSelectionHandlers.test.tsx +116 -0
- package/src/hooks/useGsapSelectionHandlers.ts +94 -31
- package/src/hooks/useInspectorSplitResize.ts +51 -0
- package/src/hooks/usePersistentEditHistory.projectOwnership.test.tsx +60 -0
- package/src/hooks/usePersistentEditHistory.test.ts +49 -1
- package/src/hooks/usePersistentEditHistory.ts +71 -31
- package/src/hooks/useProjectCompositionVariables.ts +10 -10
- package/src/hooks/useProjectSignaturePoll.test.tsx +135 -0
- package/src/hooks/useProjectSignaturePoll.ts +68 -0
- package/src/hooks/useSdkSession.lifecycle.test.tsx +137 -0
- package/src/hooks/useSdkSession.ts +155 -14
- package/src/hooks/useSlideshowPersist.ts +7 -1
- package/src/hooks/useStoryboard.ts +23 -4
- package/src/hooks/useTimelineEditing.test.tsx +4 -1
- package/src/hooks/useTimelineEditing.ts +16 -10
- package/src/hooks/useTimelineEditingTypes.ts +3 -0
- package/src/hooks/useTimelineGroupEditing.ts +16 -2
- package/src/hooks/useVariablesPersist.ts +9 -7
- package/src/icons/SystemIcons.tsx +2 -0
- package/src/styles/studio.css +22 -0
- package/src/styles/tailwind-preset.shared.js +7 -0
- package/src/utils/designInputTracking.test.ts +97 -0
- package/src/utils/designInputTracking.ts +80 -0
- package/src/utils/sdkCutover.gate.test.ts +7 -7
- package/src/utils/sdkCutover.test.ts +490 -57
- package/src/utils/sdkCutover.ts +181 -219
- package/src/utils/sdkEditTransaction.ts +292 -0
- package/src/utils/sdkResolverAttempts.ts +94 -0
- package/src/utils/sdkResolverShadow.test.ts +148 -0
- package/src/utils/sdkResolverShadow.ts +139 -106
- package/src/utils/setSlideshowManifest.test.ts +0 -9
- package/src/utils/setSlideshowManifest.ts +20 -27
- package/src/utils/studioFileHistory.test.ts +41 -0
- package/src/utils/studioFileHistory.ts +30 -27
- package/src/utils/studioFileMutationCoordinator.ts +45 -0
- package/src/utils/studioHelpers.test.ts +45 -0
- package/src/utils/studioHelpers.ts +45 -14
- package/src/utils/studioSaveDiagnostics.ts +17 -0
- package/dist/assets/index-B_UvTX3E.js +0 -423
- package/dist/assets/index-uahwWkgw.css +0 -1
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { memo, useEffect, useMemo, useRef, useState } from "react";
|
|
2
2
|
import { Move } from "../../icons/SystemIcons";
|
|
3
|
-
import { Eye, EyeSlash } from "@phosphor-icons/react";
|
|
4
3
|
import { InspectorHeaderActions } from "./InspectorHeaderActions";
|
|
5
4
|
import { useStudioShellContext } from "../../contexts/StudioContext";
|
|
6
5
|
import { readStudioBoxSize, readStudioPathOffset, readStudioRotation } from "./manualEdits";
|
|
7
6
|
import {
|
|
7
|
+
buildElementInfoText,
|
|
8
8
|
EMPTY_STYLES,
|
|
9
9
|
formatPxMetricValue,
|
|
10
10
|
parsePxMetricValue,
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
readGsapRuntimeValuesForPanel,
|
|
13
13
|
readGsapBorderRadiusForPanel,
|
|
14
14
|
isSelectedElementHidden,
|
|
15
|
+
selectionIdentityKey,
|
|
15
16
|
} from "./propertyPanelHelpers";
|
|
16
17
|
import { MetricField, Section } from "./propertyPanelPrimitives";
|
|
17
18
|
import { createTransformCommitHandlers } from "./propertyPanelTransformCommit";
|
|
@@ -24,12 +25,19 @@ import { TextSection, StyleSections } from "./propertyPanelSections";
|
|
|
24
25
|
import { GsapAnimationSection } from "./GsapAnimationSection";
|
|
25
26
|
import { PropertyPanel3dTransform } from "./propertyPanel3dTransform";
|
|
26
27
|
import { KeyframeNavigation } from "./KeyframeNavigation";
|
|
27
|
-
import {
|
|
28
|
+
import {
|
|
29
|
+
STUDIO_FLAT_INSPECTOR_ENABLED,
|
|
30
|
+
STUDIO_GSAP_PANEL_ENABLED,
|
|
31
|
+
STUDIO_KEYFRAMES_ENABLED,
|
|
32
|
+
} from "./manualEditingAvailability";
|
|
33
|
+
import { PropertyPanelFlat } from "./PropertyPanelFlat";
|
|
34
|
+
import { createGsapLivePreview } from "./gsapLivePreview";
|
|
28
35
|
import { usePlayerStore, liveTime } from "../../player";
|
|
29
36
|
import { TimingSection } from "./propertyPanelTimingSection";
|
|
30
37
|
import { type PropertyPanelProps } from "./propertyPanelHelpers";
|
|
31
38
|
import { GestureRecordPanelButton } from "./GestureRecordControl";
|
|
32
39
|
import { PropertyPanelEmptyState } from "./PropertyPanelEmptyState";
|
|
40
|
+
import { DesignPanelInputProvider } from "../../contexts/DesignPanelInputContext";
|
|
33
41
|
|
|
34
42
|
// Re-export helpers that external consumers import from this module
|
|
35
43
|
export {
|
|
@@ -46,61 +54,64 @@ export {
|
|
|
46
54
|
} from "./propertyPanelHelpers";
|
|
47
55
|
|
|
48
56
|
// fallow-ignore-next-line complexity
|
|
49
|
-
export const PropertyPanel = memo(function PropertyPanel({
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
57
|
+
export const PropertyPanel = memo(function PropertyPanel(props: PropertyPanelProps) {
|
|
58
|
+
const {
|
|
59
|
+
projectId,
|
|
60
|
+
projectDir,
|
|
61
|
+
assets,
|
|
62
|
+
element,
|
|
63
|
+
multiSelectCount = 0,
|
|
64
|
+
multiSelectedElements,
|
|
65
|
+
onGroupSelection,
|
|
66
|
+
onHideAllSelected,
|
|
67
|
+
copiedAgentPrompt: _copiedAgentPrompt,
|
|
68
|
+
onClearSelection,
|
|
69
|
+
onUngroup,
|
|
70
|
+
onSetStyle,
|
|
71
|
+
onSetAttribute,
|
|
72
|
+
onSetAttributeLive,
|
|
73
|
+
onApplyColorGradingScope,
|
|
74
|
+
onSetHtmlAttribute,
|
|
75
|
+
onRemoveBackground,
|
|
76
|
+
onSetManualOffset,
|
|
77
|
+
onSetManualSize,
|
|
78
|
+
onSetManualRotation,
|
|
79
|
+
onSetText,
|
|
80
|
+
onSetTextFieldStyle,
|
|
81
|
+
onAddTextField,
|
|
82
|
+
onRemoveTextField,
|
|
83
|
+
onToggleElementHidden,
|
|
84
|
+
onImportAssets,
|
|
85
|
+
fontAssets = [],
|
|
86
|
+
onImportFonts,
|
|
87
|
+
previewIframeRef,
|
|
88
|
+
gsapAnimations = [],
|
|
89
|
+
gsapMultipleTimelines,
|
|
90
|
+
gsapUnsupportedTimelinePattern,
|
|
91
|
+
onUpdateGsapProperty,
|
|
92
|
+
onUpdateGsapMeta,
|
|
93
|
+
onDeleteGsapAnimation,
|
|
94
|
+
onAddGsapProperty,
|
|
95
|
+
onRemoveGsapProperty,
|
|
96
|
+
onUpdateGsapFromProperty,
|
|
97
|
+
onAddGsapFromProperty,
|
|
98
|
+
onRemoveGsapFromProperty,
|
|
99
|
+
onAddGsapAnimation,
|
|
100
|
+
onSetArcPath,
|
|
101
|
+
onUpdateArcSegment,
|
|
102
|
+
onUnroll,
|
|
103
|
+
onUpdateKeyframeEase,
|
|
104
|
+
onSetAllKeyframeEases,
|
|
105
|
+
onAddKeyframe,
|
|
106
|
+
onRemoveKeyframe,
|
|
107
|
+
onConvertToKeyframes,
|
|
108
|
+
onCommitAnimatedProperty,
|
|
109
|
+
onCommitAnimatedProperties,
|
|
110
|
+
onSeekToTime,
|
|
111
|
+
recordingState,
|
|
112
|
+
recordingDuration,
|
|
113
|
+
onToggleRecording,
|
|
114
|
+
} = props;
|
|
104
115
|
const styles = element?.computedStyles ?? EMPTY_STYLES;
|
|
105
116
|
const { showToast } = useStudioShellContext();
|
|
106
117
|
const [clipboardCopied, setClipboardCopied] = useState(false);
|
|
@@ -170,13 +181,22 @@ export const PropertyPanel = memo(function PropertyPanel({
|
|
|
170
181
|
};
|
|
171
182
|
|
|
172
183
|
if (!element) {
|
|
173
|
-
return
|
|
184
|
+
return (
|
|
185
|
+
<PropertyPanelEmptyState
|
|
186
|
+
flat={STUDIO_FLAT_INSPECTOR_ENABLED}
|
|
187
|
+
multiSelectCount={multiSelectCount}
|
|
188
|
+
multiSelectedElements={multiSelectedElements}
|
|
189
|
+
onGroupSelection={onGroupSelection}
|
|
190
|
+
onHideAllSelected={onHideAllSelected}
|
|
191
|
+
onClearSelection={onClearSelection}
|
|
192
|
+
/>
|
|
193
|
+
);
|
|
174
194
|
}
|
|
175
195
|
|
|
176
196
|
const manualOffsetEditingDisabled = !element.capabilities.canApplyManualOffset;
|
|
177
197
|
const manualSizeEditingDisabled = !element.capabilities.canApplyManualSize;
|
|
178
198
|
const manualRotationEditingDisabled = !element.capabilities.canApplyManualRotation;
|
|
179
|
-
const sourceLabel = element.id ? `#${element.id}` : element.selector;
|
|
199
|
+
const sourceLabel = element.id ? `#${element.id}` : (element.selector ?? "");
|
|
180
200
|
const showEditableSections = element.capabilities.canEditStyles;
|
|
181
201
|
// Capabilities are already resolved on the selection; recompute only sections,
|
|
182
202
|
// feeding the live GSAP tween count (arrives on the gsapAnimations prop, not the
|
|
@@ -234,48 +254,8 @@ export const PropertyPanel = memo(function PropertyPanel({
|
|
|
234
254
|
const displayH = gsapRuntimeValues?.height ?? resolvedHeight;
|
|
235
255
|
const displayR = gsapRuntimeValues?.rotation ?? manualRotation.angle;
|
|
236
256
|
|
|
237
|
-
// fallow-ignore-next-line complexity
|
|
238
257
|
const handleCopyElementInfo = () => {
|
|
239
|
-
const
|
|
240
|
-
let lineNum: number | null = null;
|
|
241
|
-
try {
|
|
242
|
-
const src = previewIframeRef?.current?.contentDocument?.documentElement?.outerHTML ?? "";
|
|
243
|
-
if (src && element.id) {
|
|
244
|
-
const idx = src.indexOf(`id="${element.id}"`);
|
|
245
|
-
if (idx > -1) lineNum = src.slice(0, idx).split("\n").length;
|
|
246
|
-
}
|
|
247
|
-
if (!lineNum && element.selector) {
|
|
248
|
-
const tag = element.tagName.toLowerCase();
|
|
249
|
-
const cls = element.selector.startsWith(".")
|
|
250
|
-
? element.selector.slice(1).split(".")[0]
|
|
251
|
-
: null;
|
|
252
|
-
const search = cls ? `class="${cls}` : `<${tag}`;
|
|
253
|
-
const idx = src.indexOf(search);
|
|
254
|
-
if (idx > -1) lineNum = src.slice(0, idx).split("\n").length;
|
|
255
|
-
}
|
|
256
|
-
} catch {}
|
|
257
|
-
const fileLoc = lineNum ? `${file}:${lineNum}` : file;
|
|
258
|
-
const lines = [
|
|
259
|
-
`Element: ${element.label} (${sourceLabel})`,
|
|
260
|
-
`File: ${fileLoc}`,
|
|
261
|
-
`Position: x=${Math.round(element.boundingBox.x)}, y=${Math.round(element.boundingBox.y)}`,
|
|
262
|
-
`Size: ${Math.round(element.boundingBox.width)}×${Math.round(element.boundingBox.height)}`,
|
|
263
|
-
`Tag: <${element.tagName}>`,
|
|
264
|
-
];
|
|
265
|
-
if (element.computedStyles["z-index"] && element.computedStyles["z-index"] !== "auto") {
|
|
266
|
-
lines.push(`Z-index: ${element.computedStyles["z-index"]}`);
|
|
267
|
-
}
|
|
268
|
-
if (gsapAnimations.length > 0) {
|
|
269
|
-
const anim = gsapAnimations[0];
|
|
270
|
-
lines.push(
|
|
271
|
-
`Animation: ${anim.method}() ${anim.duration}s at ${anim.position}s, ease: ${anim.ease ?? "default"}`,
|
|
272
|
-
);
|
|
273
|
-
const props = Object.entries(anim.properties)
|
|
274
|
-
.map(([k, v]) => `${k}: ${v}`)
|
|
275
|
-
.join(", ");
|
|
276
|
-
if (props) lines.push(`Properties: ${props}`);
|
|
277
|
-
}
|
|
278
|
-
const text = lines.join("\n");
|
|
258
|
+
const text = buildElementInfoText(element, sourceLabel, gsapAnimations, previewIframeRef);
|
|
279
259
|
void navigator.clipboard.writeText(text);
|
|
280
260
|
showToast(`Copied element info for ${element.label} — paste into any AI agent`, "info");
|
|
281
261
|
setClipboardCopied(true);
|
|
@@ -283,51 +263,80 @@ export const PropertyPanel = memo(function PropertyPanel({
|
|
|
283
263
|
clipboardTimerRef.current = setTimeout(() => setClipboardCopied(false), 1500);
|
|
284
264
|
};
|
|
285
265
|
|
|
286
|
-
|
|
266
|
+
if (STUDIO_FLAT_INSPECTOR_ENABLED) {
|
|
267
|
+
// Forward the raw props (handlers, ids, assets, recording, fonts, etc.) and
|
|
268
|
+
// the values the legacy path already computed above (so they aren't derived
|
|
269
|
+
// twice). PropertyPanelFlat owns the one-open group state.
|
|
270
|
+
return (
|
|
271
|
+
<PropertyPanelFlat
|
|
272
|
+
{...props}
|
|
273
|
+
key={selectionIdentityKey(element)}
|
|
274
|
+
element={element}
|
|
275
|
+
styles={styles}
|
|
276
|
+
sections={sections}
|
|
277
|
+
sourceLabel={sourceLabel}
|
|
278
|
+
gsapBorderRadius={gsapBorderRadius}
|
|
279
|
+
showEditableSections={showEditableSections}
|
|
280
|
+
selectedElementHidden={selectedElementHidden}
|
|
281
|
+
selectedElementId={selectedElementId}
|
|
282
|
+
clipboardCopied={clipboardCopied}
|
|
283
|
+
onCopyElementInfo={handleCopyElementInfo}
|
|
284
|
+
displayX={displayX}
|
|
285
|
+
displayY={displayY}
|
|
286
|
+
displayW={displayW}
|
|
287
|
+
displayH={displayH}
|
|
288
|
+
displayR={displayR}
|
|
289
|
+
manualOffsetEditingDisabled={manualOffsetEditingDisabled}
|
|
290
|
+
manualSizeEditingDisabled={manualSizeEditingDisabled}
|
|
291
|
+
manualRotationEditingDisabled={manualRotationEditingDisabled}
|
|
292
|
+
commitManualOffset={commitManualOffset}
|
|
293
|
+
commitManualSize={commitManualSize}
|
|
294
|
+
commitManualRotation={commitManualRotation}
|
|
295
|
+
gsapAnimId={gsapAnimId}
|
|
296
|
+
navKeyframes={navKeyframes}
|
|
297
|
+
currentTime={currentTime}
|
|
298
|
+
animIdForProp={animIdForProp}
|
|
299
|
+
gsapRuntimeValues={gsap3dValues}
|
|
300
|
+
elStart={elStart}
|
|
301
|
+
elDuration={elDuration}
|
|
302
|
+
/>
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const classicPanel = (
|
|
287
307
|
<div className="flex h-full min-h-0 flex-col overflow-hidden bg-panel-bg text-panel-text-1">
|
|
288
|
-
<
|
|
289
|
-
<div className="
|
|
290
|
-
<div className="
|
|
291
|
-
<div className="
|
|
292
|
-
|
|
308
|
+
<DesignPanelInputProvider section="header">
|
|
309
|
+
<div className="px-4 py-3">
|
|
310
|
+
<div className="flex items-center justify-between gap-4">
|
|
311
|
+
<div className="min-w-0">
|
|
312
|
+
<div className="truncate text-[13px] font-semibold text-neutral-100">
|
|
313
|
+
{element.label}
|
|
314
|
+
</div>
|
|
315
|
+
<div className="mt-0.5 truncate text-[11px] text-neutral-500">{sourceLabel}</div>
|
|
293
316
|
</div>
|
|
294
|
-
<div className="mt-0.5 truncate text-[11px] text-neutral-500">{sourceLabel}</div>
|
|
295
|
-
</div>
|
|
296
|
-
<div className="flex items-center gap-1">
|
|
297
|
-
{selectedElementId && onToggleElementHidden && (
|
|
298
|
-
<button
|
|
299
|
-
type="button"
|
|
300
|
-
aria-label={visibilityToggleLabel}
|
|
301
|
-
title={visibilityToggleLabel}
|
|
302
|
-
onClick={() => {
|
|
303
|
-
void onToggleElementHidden(selectedElementId, !selectedElementHidden);
|
|
304
|
-
}}
|
|
305
|
-
className="flex h-6 w-6 items-center justify-center rounded text-neutral-500 transition-colors hover:bg-neutral-800 hover:text-neutral-300"
|
|
306
|
-
>
|
|
307
|
-
{selectedElementHidden ? (
|
|
308
|
-
<EyeSlash size={13} weight="bold" aria-hidden="true" />
|
|
309
|
-
) : (
|
|
310
|
-
<Eye size={13} weight="bold" aria-hidden="true" />
|
|
311
|
-
)}
|
|
312
|
-
</button>
|
|
313
|
-
)}
|
|
314
317
|
<InspectorHeaderActions
|
|
315
318
|
element={element}
|
|
316
319
|
copied={clipboardCopied}
|
|
317
320
|
onCopy={handleCopyElementInfo}
|
|
318
321
|
onClear={onClearSelection}
|
|
319
322
|
onUngroup={onUngroup}
|
|
323
|
+
selectedElementId={selectedElementId}
|
|
324
|
+
selectedElementHidden={selectedElementHidden}
|
|
325
|
+
visibilityLabel={visibilityToggleLabel}
|
|
326
|
+
onToggleHidden={onToggleElementHidden}
|
|
320
327
|
/>
|
|
321
328
|
</div>
|
|
322
329
|
</div>
|
|
323
|
-
</
|
|
330
|
+
</DesignPanelInputProvider>
|
|
324
331
|
<div className="flex-1 overflow-y-auto">
|
|
325
332
|
{onToggleRecording && (
|
|
326
|
-
<
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
333
|
+
<DesignPanelInputProvider section="footer">
|
|
334
|
+
<GestureRecordPanelButton
|
|
335
|
+
recordingState={recordingState}
|
|
336
|
+
recordingDuration={recordingDuration}
|
|
337
|
+
onToggleRecording={onToggleRecording}
|
|
338
|
+
/>
|
|
339
|
+
</DesignPanelInputProvider>
|
|
331
340
|
)}
|
|
332
341
|
|
|
333
342
|
<TextSection
|
|
@@ -353,12 +362,7 @@ export const PropertyPanel = memo(function PropertyPanel({
|
|
|
353
362
|
)}
|
|
354
363
|
{sections.colorGrading && (
|
|
355
364
|
<ColorGradingSection
|
|
356
|
-
key={
|
|
357
|
-
element.id ?? "",
|
|
358
|
-
element.hfId ?? "",
|
|
359
|
-
element.selector ?? "",
|
|
360
|
-
String(element.selectorIndex ?? ""),
|
|
361
|
-
].join("|")}
|
|
365
|
+
key={selectionIdentityKey(element)}
|
|
362
366
|
projectId={projectId}
|
|
363
367
|
element={element}
|
|
364
368
|
assets={assets}
|
|
@@ -522,16 +526,7 @@ export const PropertyPanel = memo(function PropertyPanel({
|
|
|
522
526
|
onSeekToTime={onSeekToTime}
|
|
523
527
|
onRemoveKeyframe={onRemoveKeyframe}
|
|
524
528
|
onConvertToKeyframes={onConvertToKeyframes}
|
|
525
|
-
onLivePreviewProps={(
|
|
526
|
-
const iframe = iframeRef.current;
|
|
527
|
-
const win = iframe?.contentWindow as
|
|
528
|
-
| { gsap?: { set: (t: Element, v: Record<string, number>) => void } }
|
|
529
|
-
| null
|
|
530
|
-
| undefined;
|
|
531
|
-
const sel = el.id ? `#${el.id}` : el.selector;
|
|
532
|
-
const node = sel ? iframe?.contentDocument?.querySelector(sel) : null;
|
|
533
|
-
if (win?.gsap && node) win.gsap.set(node, props);
|
|
534
|
-
}}
|
|
529
|
+
onLivePreviewProps={createGsapLivePreview(iframeRef)}
|
|
535
530
|
/>
|
|
536
531
|
<div className="mt-3">
|
|
537
532
|
<div className="mb-2 text-[10px] font-medium uppercase tracking-wider text-neutral-600">
|
|
@@ -587,4 +582,5 @@ export const PropertyPanel = memo(function PropertyPanel({
|
|
|
587
582
|
</div>
|
|
588
583
|
</div>
|
|
589
584
|
);
|
|
585
|
+
return <DesignPanelInputProvider ui="classic">{classicPanel}</DesignPanelInputProvider>;
|
|
590
586
|
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
|
|
3
|
+
import React, { act } from "react";
|
|
4
|
+
import { createRoot } from "react-dom/client";
|
|
5
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
6
|
+
import { PropertyPanelEmptyState } from "./PropertyPanelEmptyState";
|
|
7
|
+
import type { DomEditSelection } from "./domEditingTypes";
|
|
8
|
+
|
|
9
|
+
(globalThis as unknown as { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
|
10
|
+
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
document.body.innerHTML = "";
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
function renderInto(node: React.ReactElement) {
|
|
16
|
+
const host = document.createElement("div");
|
|
17
|
+
document.body.append(host);
|
|
18
|
+
const root = createRoot(host);
|
|
19
|
+
act(() => {
|
|
20
|
+
root.render(node);
|
|
21
|
+
});
|
|
22
|
+
return { host, root };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
describe("PropertyPanelEmptyState — flat empty", () => {
|
|
26
|
+
it("shows the cursor glyph, headline, and the two shortcut rows", () => {
|
|
27
|
+
const { host, root } = renderInto(<PropertyPanelEmptyState flat multiSelectCount={0} />);
|
|
28
|
+
expect(host.textContent).toContain("Nothing selected");
|
|
29
|
+
expect(host.textContent).toContain("Record a gesture");
|
|
30
|
+
expect(host.textContent).toContain("Describe a change to the agent");
|
|
31
|
+
act(() => root.unmount());
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe("PropertyPanelEmptyState — flat multi-select", () => {
|
|
36
|
+
const elements = [
|
|
37
|
+
{ id: "mono-label", selector: ".mono-label", label: "Mono Label", tagName: "div" },
|
|
38
|
+
{ id: null, selector: "#s2-chart", label: "S2 Chart", tagName: "div" },
|
|
39
|
+
] as unknown as DomEditSelection[];
|
|
40
|
+
|
|
41
|
+
it("lists each selected element and wires group/hide-all/clear actions", () => {
|
|
42
|
+
const onGroupSelection = vi.fn();
|
|
43
|
+
const onHideAllSelected = vi.fn();
|
|
44
|
+
const onClearSelection = vi.fn();
|
|
45
|
+
const { host, root } = renderInto(
|
|
46
|
+
<PropertyPanelEmptyState
|
|
47
|
+
flat
|
|
48
|
+
multiSelectCount={2}
|
|
49
|
+
multiSelectedElements={elements}
|
|
50
|
+
onGroupSelection={onGroupSelection}
|
|
51
|
+
onHideAllSelected={onHideAllSelected}
|
|
52
|
+
onClearSelection={onClearSelection}
|
|
53
|
+
/>,
|
|
54
|
+
);
|
|
55
|
+
expect(host.textContent).toContain("2 elements selected");
|
|
56
|
+
expect(host.textContent).toContain("Mono Label");
|
|
57
|
+
expect(host.textContent).toContain("S2 Chart");
|
|
58
|
+
|
|
59
|
+
const group = host.querySelector<HTMLButtonElement>('[data-flat-multiselect-group="true"]');
|
|
60
|
+
act(() => group?.dispatchEvent(new MouseEvent("click", { bubbles: true })));
|
|
61
|
+
expect(onGroupSelection).toHaveBeenCalledTimes(1);
|
|
62
|
+
|
|
63
|
+
const hideAll = host.querySelector<HTMLButtonElement>(
|
|
64
|
+
'[data-flat-multiselect-hide-all="true"]',
|
|
65
|
+
);
|
|
66
|
+
act(() => hideAll?.dispatchEvent(new MouseEvent("click", { bubbles: true })));
|
|
67
|
+
expect(onHideAllSelected).toHaveBeenCalledTimes(1);
|
|
68
|
+
|
|
69
|
+
const clear = host.querySelector<HTMLButtonElement>('[data-flat-multiselect-clear="true"]');
|
|
70
|
+
act(() => clear?.dispatchEvent(new MouseEvent("click", { bubbles: true })));
|
|
71
|
+
expect(onClearSelection).toHaveBeenCalledTimes(1);
|
|
72
|
+
act(() => root.unmount());
|
|
73
|
+
});
|
|
74
|
+
});
|
|
@@ -1,6 +1,184 @@
|
|
|
1
1
|
import { Eye, Layers } from "../../icons/SystemIcons";
|
|
2
|
+
import type { DomEditSelection } from "./domEditingTypes";
|
|
3
|
+
|
|
4
|
+
function FlatEmptyState() {
|
|
5
|
+
return (
|
|
6
|
+
<div className="flex h-full flex-col items-center justify-center gap-2.5 px-8 py-10 text-center">
|
|
7
|
+
<span className="flex h-11 w-11 items-center justify-center rounded-xl border border-panel-border-input bg-panel-input text-panel-text-3">
|
|
8
|
+
<svg
|
|
9
|
+
width="20"
|
|
10
|
+
height="20"
|
|
11
|
+
viewBox="0 0 20 20"
|
|
12
|
+
fill="none"
|
|
13
|
+
stroke="currentColor"
|
|
14
|
+
strokeWidth="1.4"
|
|
15
|
+
>
|
|
16
|
+
<path d="M4 3l6 14 2-6 6-2z" strokeLinejoin="round" />
|
|
17
|
+
</svg>
|
|
18
|
+
</span>
|
|
19
|
+
<div className="text-[13px] font-semibold text-panel-text-0">Nothing selected</div>
|
|
20
|
+
<div className="max-w-[250px] text-[11px] leading-[1.5] text-panel-text-3">
|
|
21
|
+
Click any element on the canvas to edit it, or drag to select several.
|
|
22
|
+
</div>
|
|
23
|
+
<div className="mt-2 flex w-full flex-col gap-1.5">
|
|
24
|
+
<span className="flex items-center justify-between rounded-lg border border-panel-border bg-panel-bg px-3 py-2">
|
|
25
|
+
<span className="flex items-center gap-2 text-[11px] text-panel-text-2">
|
|
26
|
+
<span className="text-panel-danger">●</span>
|
|
27
|
+
Record a gesture
|
|
28
|
+
</span>
|
|
29
|
+
<span className="rounded border border-panel-border-input px-[5px] py-px font-mono text-[9px] text-panel-text-5">
|
|
30
|
+
R
|
|
31
|
+
</span>
|
|
32
|
+
</span>
|
|
33
|
+
<span className="flex items-center justify-between rounded-lg border border-panel-border bg-panel-bg px-3 py-2">
|
|
34
|
+
<span className="flex items-center gap-2 text-[11px] text-panel-text-2">
|
|
35
|
+
<span className="text-panel-accent">✦</span>
|
|
36
|
+
Describe a change to the agent
|
|
37
|
+
</span>
|
|
38
|
+
<span className="rounded border border-panel-border-input px-[5px] py-px font-mono text-[9px] text-panel-text-5">
|
|
39
|
+
⌘K
|
|
40
|
+
</span>
|
|
41
|
+
</span>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function elementKindGlyph(element: DomEditSelection): { glyph: string; className: string } {
|
|
48
|
+
if (element.tagName === "video" || element.tagName === "audio" || element.tagName === "img") {
|
|
49
|
+
return { glyph: "◆", className: "bg-panel-media/10 text-panel-media" };
|
|
50
|
+
}
|
|
51
|
+
if (element.textFields?.length > 0) {
|
|
52
|
+
return { glyph: "T", className: "bg-panel-accent/10 text-panel-accent" };
|
|
53
|
+
}
|
|
54
|
+
return { glyph: "▦", className: "bg-panel-container/10 text-panel-container" };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function FlatMultiSelectState({
|
|
58
|
+
multiSelectCount,
|
|
59
|
+
multiSelectedElements = [],
|
|
60
|
+
onGroupSelection,
|
|
61
|
+
onHideAllSelected,
|
|
62
|
+
onClearSelection,
|
|
63
|
+
}: {
|
|
64
|
+
multiSelectCount: number;
|
|
65
|
+
multiSelectedElements?: DomEditSelection[];
|
|
66
|
+
onGroupSelection?: () => void;
|
|
67
|
+
onHideAllSelected?: () => void;
|
|
68
|
+
onClearSelection?: () => void;
|
|
69
|
+
}) {
|
|
70
|
+
return (
|
|
71
|
+
<div className="flex flex-col gap-3 px-4 py-3">
|
|
72
|
+
<div className="flex items-center gap-3 rounded-xl border border-panel-border bg-panel-surface p-3">
|
|
73
|
+
<span className="flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg bg-panel-accent/10 text-panel-accent">
|
|
74
|
+
<Layers size={16} />
|
|
75
|
+
</span>
|
|
76
|
+
<div className="min-w-0 flex-1">
|
|
77
|
+
<div className="text-[13px] font-semibold text-panel-text-0">
|
|
78
|
+
{multiSelectCount} elements selected
|
|
79
|
+
</div>
|
|
80
|
+
<div className="mt-px font-mono text-[10px] text-panel-text-3">
|
|
81
|
+
shift-click to add or remove
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
<button
|
|
85
|
+
type="button"
|
|
86
|
+
data-flat-multiselect-clear="true"
|
|
87
|
+
aria-label="Clear selection"
|
|
88
|
+
onClick={onClearSelection}
|
|
89
|
+
className="flex h-[26px] w-[26px] flex-shrink-0 items-center justify-center text-panel-text-3"
|
|
90
|
+
>
|
|
91
|
+
<svg
|
|
92
|
+
width="13"
|
|
93
|
+
height="13"
|
|
94
|
+
viewBox="0 0 16 16"
|
|
95
|
+
fill="none"
|
|
96
|
+
stroke="currentColor"
|
|
97
|
+
strokeWidth="1.5"
|
|
98
|
+
>
|
|
99
|
+
<path d="M3 3l10 10M13 3L3 13" />
|
|
100
|
+
</svg>
|
|
101
|
+
</button>
|
|
102
|
+
</div>
|
|
103
|
+
<div className="flex flex-col gap-1">
|
|
104
|
+
{multiSelectedElements.map((element) => {
|
|
105
|
+
const { glyph, className } = elementKindGlyph(element);
|
|
106
|
+
return (
|
|
107
|
+
<span
|
|
108
|
+
key={`${element.id ?? element.selector ?? ""}:${element.selectorIndex ?? 0}`}
|
|
109
|
+
className="flex items-center gap-2 rounded-lg border border-panel-border bg-panel-bg px-2.5 py-[7px]"
|
|
110
|
+
>
|
|
111
|
+
<span
|
|
112
|
+
className={`flex h-[18px] w-[18px] flex-shrink-0 items-center justify-center rounded text-[9px] font-bold ${className}`}
|
|
113
|
+
>
|
|
114
|
+
{glyph}
|
|
115
|
+
</span>
|
|
116
|
+
<span className="min-w-0 flex-1 truncate text-[11px] text-panel-text-1">
|
|
117
|
+
{element.label}
|
|
118
|
+
</span>
|
|
119
|
+
<span className="flex-shrink-0 font-mono text-[9px] text-panel-text-4">
|
|
120
|
+
{element.id ? `#${element.id}` : element.selector}
|
|
121
|
+
</span>
|
|
122
|
+
</span>
|
|
123
|
+
);
|
|
124
|
+
})}
|
|
125
|
+
</div>
|
|
126
|
+
<div className="flex gap-2">
|
|
127
|
+
<button
|
|
128
|
+
type="button"
|
|
129
|
+
data-flat-multiselect-group="true"
|
|
130
|
+
onClick={onGroupSelection}
|
|
131
|
+
className="flex h-[34px] flex-1 items-center justify-center gap-2 rounded-lg bg-panel-hover text-[11px] font-semibold text-panel-text-0"
|
|
132
|
+
>
|
|
133
|
+
<Layers size={13} />
|
|
134
|
+
Group selection
|
|
135
|
+
</button>
|
|
136
|
+
<button
|
|
137
|
+
type="button"
|
|
138
|
+
data-flat-multiselect-hide-all="true"
|
|
139
|
+
onClick={onHideAllSelected}
|
|
140
|
+
className="flex h-[34px] items-center gap-1.5 rounded-lg border border-panel-border-input bg-panel-input px-3 text-[11px] font-medium text-panel-text-2"
|
|
141
|
+
>
|
|
142
|
+
<Eye size={13} />
|
|
143
|
+
Hide all
|
|
144
|
+
</button>
|
|
145
|
+
</div>
|
|
146
|
+
<span className="text-center text-[10px] text-panel-text-5">
|
|
147
|
+
Select a single element to edit its properties
|
|
148
|
+
</span>
|
|
149
|
+
</div>
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function PropertyPanelEmptyState({
|
|
154
|
+
multiSelectCount,
|
|
155
|
+
flat,
|
|
156
|
+
multiSelectedElements,
|
|
157
|
+
onGroupSelection,
|
|
158
|
+
onHideAllSelected,
|
|
159
|
+
onClearSelection,
|
|
160
|
+
}: {
|
|
161
|
+
multiSelectCount: number;
|
|
162
|
+
flat?: boolean;
|
|
163
|
+
multiSelectedElements?: DomEditSelection[];
|
|
164
|
+
onGroupSelection?: () => void;
|
|
165
|
+
onHideAllSelected?: () => void;
|
|
166
|
+
onClearSelection?: () => void;
|
|
167
|
+
}) {
|
|
168
|
+
if (flat) {
|
|
169
|
+
return multiSelectCount > 1 ? (
|
|
170
|
+
<FlatMultiSelectState
|
|
171
|
+
multiSelectCount={multiSelectCount}
|
|
172
|
+
multiSelectedElements={multiSelectedElements}
|
|
173
|
+
onGroupSelection={onGroupSelection}
|
|
174
|
+
onHideAllSelected={onHideAllSelected}
|
|
175
|
+
onClearSelection={onClearSelection}
|
|
176
|
+
/>
|
|
177
|
+
) : (
|
|
178
|
+
<FlatEmptyState />
|
|
179
|
+
);
|
|
180
|
+
}
|
|
2
181
|
|
|
3
|
-
export function PropertyPanelEmptyState({ multiSelectCount }: { multiSelectCount: number }) {
|
|
4
182
|
return (
|
|
5
183
|
<div className="flex h-full flex-col bg-neutral-900">
|
|
6
184
|
<div className="flex flex-1 flex-col items-center justify-center px-6 text-center">
|