@hyperframes/studio 0.7.58 → 0.7.60
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-3XTTaVNf.js} +1 -1
- package/dist/assets/{index-C47jAC3Q.js → index-D6etaey-.js} +1 -1
- package/dist/assets/index-DXbu6IPT.css +1 -0
- package/dist/assets/{index-DeQPzqwH.js → index-Dh_WhagG.js} +1 -1
- package/dist/assets/index-cH6NfVV_.js +426 -0
- package/dist/index.d.ts +12 -3
- package/dist/index.html +2 -2
- package/dist/index.js +13755 -8627
- 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 +4 -4
- package/src/components/DesignPanelPromoteProvider.tsx +15 -2
- package/src/components/StudioRightPanel.tsx +42 -53
- 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 +107 -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/FileManagerContext.tsx +3 -0
- package/src/contexts/PanelLayoutContext.tsx +3 -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/timelineEditingHelpers.ts +2 -2
- 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.test.tsx +41 -1
- package/src/hooks/useDomEditCommits.ts +31 -177
- package/src/hooks/useDomEditCommitsHelpers.ts +158 -0
- package/src/hooks/useDomEditSession.ts +18 -6
- package/src/hooks/useDomEditTextCommits.ts +14 -9
- package/src/hooks/useDomEditWiring.ts +10 -9
- package/src/hooks/useEditorSave.ts +1 -1
- package/src/hooks/useElementLifecycleOps.ts +8 -3
- package/src/hooks/useFileManager.projectOwnership.test.tsx +109 -0
- package/src/hooks/useFileManager.ts +151 -65
- 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/usePanelLayout.test.ts +118 -0
- package/src/hooks/usePanelLayout.ts +24 -1
- 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/usePreviewPersistence.ts +4 -1
- 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/useRazorSplit.history.test.tsx +12 -4
- package/src/hooks/useRazorSplit.test.tsx +88 -0
- package/src/hooks/useRazorSplit.testHelpers.ts +3 -2
- package/src/hooks/useRazorSplit.ts +50 -16
- 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 +18 -10
- package/src/hooks/useTimelineEditingTypes.ts +5 -1
- package/src/hooks/useTimelineGroupEditing.ts +17 -3
- 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/domEditSaveQueue.test.ts +19 -0
- package/src/utils/domEditSaveQueue.ts +2 -1
- package/src/utils/sdkCutover.gate.test.ts +7 -7
- package/src/utils/sdkCutover.test.ts +511 -62
- package/src/utils/sdkCutover.ts +181 -219
- package/src/utils/sdkEditTransaction.ts +293 -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 +34 -29
- package/src/utils/studioFileMutationCoordinator.ts +45 -0
- package/src/utils/studioFileVersion.test.ts +36 -0
- package/src/utils/studioFileVersion.ts +23 -0
- package/src/utils/studioHelpers.test.ts +45 -0
- package/src/utils/studioHelpers.ts +45 -14
- package/src/utils/studioSaveDiagnostics.test.ts +18 -0
- package/src/utils/studioSaveDiagnostics.ts +38 -0
- package/dist/assets/index-B_UvTX3E.js +0 -423
- package/dist/assets/index-uahwWkgw.css +0 -1
|
@@ -12,11 +12,14 @@ declare namespace studioPreset {
|
|
|
12
12
|
}
|
|
13
13
|
let panel: {
|
|
14
14
|
bg: string;
|
|
15
|
+
"bg-inset": string;
|
|
15
16
|
input: string;
|
|
16
17
|
surface: string;
|
|
17
18
|
hover: string;
|
|
18
19
|
border: string;
|
|
19
20
|
"border-input": string;
|
|
21
|
+
hairline: string;
|
|
22
|
+
"text-0": string;
|
|
20
23
|
"text-1": string;
|
|
21
24
|
"text-2": string;
|
|
22
25
|
"text-3": string;
|
|
@@ -24,6 +27,8 @@ declare namespace studioPreset {
|
|
|
24
27
|
"text-5": string;
|
|
25
28
|
accent: string;
|
|
26
29
|
danger: string;
|
|
30
|
+
media: string;
|
|
31
|
+
container: string;
|
|
27
32
|
};
|
|
28
33
|
}
|
|
29
34
|
}
|
|
@@ -13,18 +13,25 @@ var studioPreset = {
|
|
|
13
13
|
},
|
|
14
14
|
panel: {
|
|
15
15
|
bg: "#0C0C0E",
|
|
16
|
+
// Open inspector-section body — slightly lighter than headers (bg)
|
|
17
|
+
// so the recessed scrollable region reads distinct.
|
|
18
|
+
"bg-inset": "#121214",
|
|
16
19
|
input: "#161618",
|
|
17
20
|
surface: "#18181B",
|
|
18
21
|
hover: "#27272A",
|
|
19
22
|
border: "#1E1E1E",
|
|
20
23
|
"border-input": "#27272A",
|
|
24
|
+
hairline: "#1A1A1C",
|
|
25
|
+
"text-0": "#FAFAFA",
|
|
21
26
|
"text-1": "#E4E4E7",
|
|
22
27
|
"text-2": "#A1A1AA",
|
|
23
28
|
"text-3": "#71717A",
|
|
24
29
|
"text-4": "#52525B",
|
|
25
30
|
"text-5": "#3F3F46",
|
|
26
31
|
accent: "#3CE6AC",
|
|
27
|
-
danger: "#EF4444"
|
|
32
|
+
danger: "#EF4444",
|
|
33
|
+
media: "#00E3FF",
|
|
34
|
+
container: "#F5A623"
|
|
28
35
|
}
|
|
29
36
|
}
|
|
30
37
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/styles/tailwind-preset.shared.js","../../src/styles/tailwind-preset.ts"],"sourcesContent":["const studioPreset = {\n theme: {\n extend: {\n colors: {\n studio: {\n bg: \"#0a0a0a\",\n surface: \"#141414\",\n border: \"#262626\",\n text: \"#e5e5e5\",\n muted: \"#737373\",\n accent: \"#3CE6AC\",\n },\n panel: {\n bg: \"#0C0C0E\",\n input: \"#161618\",\n surface: \"#18181B\",\n hover: \"#27272A\",\n border: \"#1E1E1E\",\n \"border-input\": \"#27272A\",\n \"text-1\": \"#E4E4E7\",\n \"text-2\": \"#A1A1AA\",\n \"text-3\": \"#71717A\",\n \"text-4\": \"#52525B\",\n \"text-5\": \"#3F3F46\",\n accent: \"#3CE6AC\",\n danger: \"#EF4444\",\n },\n },\n },\n },\n plugins: [],\n};\n\nexport default studioPreset;\n","import studioPreset from \"./tailwind-preset.shared.js\";\n\nexport default studioPreset;\n"],"mappings":";AAAA,IAAM,eAAe;AAAA,EACnB,OAAO;AAAA,IACL,QAAQ;AAAA,MACN,QAAQ;AAAA,QACN,QAAQ;AAAA,UACN,IAAI;AAAA,UACJ,SAAS;AAAA,UACT,QAAQ;AAAA,UACR,MAAM;AAAA,UACN,OAAO;AAAA,UACP,QAAQ;AAAA,QACV;AAAA,QACA,OAAO;AAAA,UACL,IAAI;AAAA,
|
|
1
|
+
{"version":3,"sources":["../../src/styles/tailwind-preset.shared.js","../../src/styles/tailwind-preset.ts"],"sourcesContent":["const studioPreset = {\n theme: {\n extend: {\n colors: {\n studio: {\n bg: \"#0a0a0a\",\n surface: \"#141414\",\n border: \"#262626\",\n text: \"#e5e5e5\",\n muted: \"#737373\",\n accent: \"#3CE6AC\",\n },\n panel: {\n bg: \"#0C0C0E\",\n // Open inspector-section body — slightly lighter than headers (bg)\n // so the recessed scrollable region reads distinct.\n \"bg-inset\": \"#121214\",\n input: \"#161618\",\n surface: \"#18181B\",\n hover: \"#27272A\",\n border: \"#1E1E1E\",\n \"border-input\": \"#27272A\",\n hairline: \"#1A1A1C\",\n \"text-0\": \"#FAFAFA\",\n \"text-1\": \"#E4E4E7\",\n \"text-2\": \"#A1A1AA\",\n \"text-3\": \"#71717A\",\n \"text-4\": \"#52525B\",\n \"text-5\": \"#3F3F46\",\n accent: \"#3CE6AC\",\n danger: \"#EF4444\",\n media: \"#00E3FF\",\n container: \"#F5A623\",\n },\n },\n },\n },\n plugins: [],\n};\n\nexport default studioPreset;\n","import studioPreset from \"./tailwind-preset.shared.js\";\n\nexport default studioPreset;\n"],"mappings":";AAAA,IAAM,eAAe;AAAA,EACnB,OAAO;AAAA,IACL,QAAQ;AAAA,MACN,QAAQ;AAAA,QACN,QAAQ;AAAA,UACN,IAAI;AAAA,UACJ,SAAS;AAAA,UACT,QAAQ;AAAA,UACR,MAAM;AAAA,UACN,OAAO;AAAA,UACP,QAAQ;AAAA,QACV;AAAA,QACA,OAAO;AAAA,UACL,IAAI;AAAA;AAAA;AAAA,UAGJ,YAAY;AAAA,UACZ,OAAO;AAAA,UACP,SAAS;AAAA,UACT,OAAO;AAAA,UACP,QAAQ;AAAA,UACR,gBAAgB;AAAA,UAChB,UAAU;AAAA,UACV,UAAU;AAAA,UACV,UAAU;AAAA,UACV,UAAU;AAAA,UACV,UAAU;AAAA,UACV,UAAU;AAAA,UACV,UAAU;AAAA,UACV,QAAQ;AAAA,UACR,QAAQ;AAAA,UACR,OAAO;AAAA,UACP,WAAW;AAAA,QACb;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EACA,SAAS,CAAC;AACZ;AAEA,IAAO,iCAAQ;;;ACtCf,IAAO,0BAAQ;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperframes/studio",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.60",
|
|
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/core": "0.7.
|
|
50
|
-
"@hyperframes/
|
|
51
|
-
"@hyperframes/
|
|
52
|
-
"@hyperframes/
|
|
53
|
-
"@hyperframes/studio-server": "0.7.
|
|
49
|
+
"@hyperframes/core": "0.7.60",
|
|
50
|
+
"@hyperframes/player": "0.7.60",
|
|
51
|
+
"@hyperframes/sdk": "0.7.60",
|
|
52
|
+
"@hyperframes/parsers": "0.7.60",
|
|
53
|
+
"@hyperframes/studio-server": "0.7.60"
|
|
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.60"
|
|
69
69
|
},
|
|
70
70
|
"peerDependencies": {
|
|
71
71
|
"react": "19",
|
package/src/App.tsx
CHANGED
|
@@ -64,21 +64,18 @@ import {
|
|
|
64
64
|
} from "./utils/studioUrlState";
|
|
65
65
|
import { trackStudioSessionStart } from "./telemetry/events";
|
|
66
66
|
import { hasFiredSessionStart, markSessionStartFired } from "./telemetry/config";
|
|
67
|
-
|
|
68
67
|
type CanvasRect = { left: number; top: number; width: number; height: number };
|
|
69
68
|
// fallow-ignore-next-line complexity
|
|
70
69
|
export function StudioApp() {
|
|
71
70
|
const { projectId, resolving, waitingForServer } = useServerConnection();
|
|
72
71
|
const initialUrlStateRef = useRef(readStudioUrlStateFromWindow());
|
|
73
72
|
const viewModeValue = useViewModeState();
|
|
74
|
-
|
|
75
73
|
useEffect(() => {
|
|
76
74
|
if (resolving || waitingForServer) return;
|
|
77
75
|
if (hasFiredSessionStart()) return;
|
|
78
76
|
markSessionStartFired();
|
|
79
77
|
trackStudioSessionStart({ has_project: projectId != null });
|
|
80
78
|
}, [projectId, resolving, waitingForServer]);
|
|
81
|
-
|
|
82
79
|
const [activeCompPath, setActiveCompPath] = useState<string | null>(null);
|
|
83
80
|
const [activeCompPathHydrated, setActiveCompPathHydrated] = useState(
|
|
84
81
|
() => initialUrlStateRef.current.activeCompPath == null,
|
|
@@ -168,6 +165,7 @@ export function StudioApp() {
|
|
|
168
165
|
timelineElements,
|
|
169
166
|
showToast,
|
|
170
167
|
writeProjectFile: fileManager.writeProjectFile,
|
|
168
|
+
observeProjectFileVersion: fileManager.observeProjectFileVersion,
|
|
171
169
|
recordEdit: editHistory.recordEdit,
|
|
172
170
|
domEditSaveTimestampRef,
|
|
173
171
|
reloadPreview,
|
|
@@ -176,6 +174,7 @@ export function StudioApp() {
|
|
|
176
174
|
uploadProjectFiles: fileManager.uploadProjectFiles,
|
|
177
175
|
isRecordingRef: isGestureRecordingRef,
|
|
178
176
|
sdkSession: editFlowSdkSession,
|
|
177
|
+
publishSdkSession: sdkHandle.publish,
|
|
179
178
|
forceReloadSdkSession: sdkHandle.forceReload,
|
|
180
179
|
handleDomZIndexReorderCommitRef,
|
|
181
180
|
});
|
|
@@ -317,6 +316,7 @@ export function StudioApp() {
|
|
|
317
316
|
selectSidebarTab: sidebarTabRef.current.select,
|
|
318
317
|
getSidebarTab: sidebarTabRef.current.get,
|
|
319
318
|
sdkSession: editFlowSdkSession,
|
|
319
|
+
publishSdkSession: sdkHandle.publish,
|
|
320
320
|
forceReloadSdkSession: sdkHandle.forceReload,
|
|
321
321
|
});
|
|
322
322
|
domEditSelectionBridgeRef.current = domEditSession.domEditSelection;
|
|
@@ -331,7 +331,6 @@ export function StudioApp() {
|
|
|
331
331
|
domEditSession.domEditSelection,
|
|
332
332
|
domEditSession.domEditGroupSelections,
|
|
333
333
|
);
|
|
334
|
-
|
|
335
334
|
useCaptionDetection({
|
|
336
335
|
projectId,
|
|
337
336
|
activeCompPath,
|
|
@@ -533,6 +532,7 @@ export function StudioApp() {
|
|
|
533
532
|
recordingDuration={gestureRecording.recordingDuration}
|
|
534
533
|
onToggleRecording={recordingToggle}
|
|
535
534
|
sdkSession={sdkHandle.session}
|
|
535
|
+
publishSdkSession={sdkHandle.publish}
|
|
536
536
|
reloadPreview={reloadPreview}
|
|
537
537
|
domEditSaveTimestampRef={domEditSaveTimestampRef}
|
|
538
538
|
recordEdit={editHistory.recordEdit}
|
|
@@ -9,11 +9,12 @@
|
|
|
9
9
|
* composition, so behavior there is unchanged.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import type
|
|
12
|
+
import { useCallback, type ReactNode } from "react";
|
|
13
13
|
import type { DomEditSelection } from "./editor/domEditingTypes";
|
|
14
14
|
import { useSdkSession } from "../hooks/useSdkSession";
|
|
15
15
|
import { useVariablesPersist, type UseVariablesPersistParams } from "../hooks/useVariablesPersist";
|
|
16
16
|
import { VariablePromoteProvider } from "../contexts/VariablePromoteContext";
|
|
17
|
+
import { getStudioSaveErrorMessage } from "../utils/studioSaveDiagnostics";
|
|
17
18
|
|
|
18
19
|
/** Persist wiring minus the target — this provider derives the target from the selection. */
|
|
19
20
|
type PersistDeps = Omit<UseVariablesPersistParams, "sdkSession" | "activeCompPath">;
|
|
@@ -22,12 +23,14 @@ export function DesignPanelPromoteProvider({
|
|
|
22
23
|
selection,
|
|
23
24
|
projectId,
|
|
24
25
|
activeCompPath,
|
|
26
|
+
showToast,
|
|
25
27
|
children,
|
|
26
28
|
...persistDeps
|
|
27
29
|
}: PersistDeps & {
|
|
28
30
|
selection: DomEditSelection | null;
|
|
29
31
|
projectId: string | null;
|
|
30
32
|
activeCompPath: string | null;
|
|
33
|
+
showToast: (message: string, tone?: "error" | "info") => void;
|
|
31
34
|
children: ReactNode;
|
|
32
35
|
}) {
|
|
33
36
|
const targetPath = selection?.sourceFile || activeCompPath || "index.html";
|
|
@@ -35,10 +38,20 @@ export function DesignPanelPromoteProvider({
|
|
|
35
38
|
const persist = useVariablesPersist({
|
|
36
39
|
...persistDeps,
|
|
37
40
|
sdkSession: handle.session,
|
|
41
|
+
publishSdkSession: handle.publish,
|
|
38
42
|
activeCompPath: targetPath,
|
|
39
43
|
});
|
|
44
|
+
const handlePersistError = useCallback(
|
|
45
|
+
(error: unknown) => showToast(getStudioSaveErrorMessage(error), "error"),
|
|
46
|
+
[showToast],
|
|
47
|
+
);
|
|
40
48
|
return (
|
|
41
|
-
<VariablePromoteProvider
|
|
49
|
+
<VariablePromoteProvider
|
|
50
|
+
session={handle.session}
|
|
51
|
+
selection={selection}
|
|
52
|
+
persist={persist}
|
|
53
|
+
onPersistError={handlePersistError}
|
|
54
|
+
>
|
|
42
55
|
{children}
|
|
43
56
|
</VariablePromoteProvider>
|
|
44
57
|
);
|
|
@@ -1,12 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
useCallback,
|
|
3
|
-
useEffect,
|
|
4
|
-
useMemo,
|
|
5
|
-
useRef,
|
|
6
|
-
useState,
|
|
7
|
-
type MutableRefObject,
|
|
8
|
-
type PointerEvent as ReactPointerEvent,
|
|
9
|
-
} from "react";
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef, type MutableRefObject } from "react";
|
|
10
2
|
import { PropertyPanel } from "./editor/PropertyPanel";
|
|
11
3
|
import { LayersPanel } from "./editor/LayersPanel";
|
|
12
4
|
import { CaptionPropertyPanel } from "../captions/components/CaptionPropertyPanel";
|
|
@@ -20,10 +12,13 @@ import { usePreviewVariablesStore } from "../hooks/previewVariablesStore";
|
|
|
20
12
|
import type { RenderJob } from "./renders/useRenderQueue";
|
|
21
13
|
import type { BlockParam } from "@hyperframes/core/registry";
|
|
22
14
|
import type { IframeWindow } from "../player/lib/playbackTypes";
|
|
23
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
STUDIO_FLAT_INSPECTOR_ENABLED,
|
|
17
|
+
STUDIO_INSPECTOR_PANELS_ENABLED,
|
|
18
|
+
} from "./editor/manualEditingAvailability";
|
|
24
19
|
import type { Composition } from "@hyperframes/sdk";
|
|
25
20
|
import type { EditHistoryKind } from "../utils/editHistory";
|
|
26
|
-
import { useSlideshowPersist } from "../hooks/useSlideshowPersist";
|
|
21
|
+
import { useSlideshowPersist, type UseSlideshowPersistParams } from "../hooks/useSlideshowPersist";
|
|
27
22
|
import { DesignPanelPromoteProvider } from "./DesignPanelPromoteProvider";
|
|
28
23
|
|
|
29
24
|
import { useStudioPlaybackContext, useStudioShellContext } from "../contexts/StudioContext";
|
|
@@ -38,9 +33,8 @@ import {
|
|
|
38
33
|
type ColorGradingScope,
|
|
39
34
|
} from "./studioColorGradingScope";
|
|
40
35
|
import type { BackgroundRemovalProgress } from "./editor/propertyPanelTypes";
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const MAX_INSPECTOR_SPLIT_PERCENT = 75;
|
|
36
|
+
import { timelineKeysForSelections, type ToggleHiddenHandler } from "../utils/studioHelpers";
|
|
37
|
+
import { useInspectorSplitResize } from "../hooks/useInspectorSplitResize";
|
|
44
38
|
|
|
45
39
|
export interface StudioRightPanelProps {
|
|
46
40
|
designPanelActive: boolean;
|
|
@@ -56,6 +50,7 @@ export interface StudioRightPanelProps {
|
|
|
56
50
|
onToggleRecording?: () => void;
|
|
57
51
|
/** Dependencies for the Slideshow persist callback, threaded from App.tsx. */
|
|
58
52
|
sdkSession: Composition | null;
|
|
53
|
+
publishSdkSession: NonNullable<UseSlideshowPersistParams["publishSdkSession"]>;
|
|
59
54
|
reloadPreview: () => void;
|
|
60
55
|
domEditSaveTimestampRef: MutableRefObject<number>;
|
|
61
56
|
recordEdit: (entry: {
|
|
@@ -63,7 +58,7 @@ export interface StudioRightPanelProps {
|
|
|
63
58
|
kind: EditHistoryKind;
|
|
64
59
|
files: Record<string, { before: string; after: string }>;
|
|
65
60
|
}) => Promise<void>;
|
|
66
|
-
onToggleElementHidden?:
|
|
61
|
+
onToggleElementHidden?: ToggleHiddenHandler;
|
|
67
62
|
}
|
|
68
63
|
|
|
69
64
|
// fallow-ignore-next-line complexity
|
|
@@ -75,6 +70,7 @@ export function StudioRightPanel({
|
|
|
75
70
|
recordingDuration,
|
|
76
71
|
onToggleRecording,
|
|
77
72
|
sdkSession,
|
|
73
|
+
publishSdkSession,
|
|
78
74
|
reloadPreview,
|
|
79
75
|
domEditSaveTimestampRef,
|
|
80
76
|
recordEdit,
|
|
@@ -87,6 +83,7 @@ export function StudioRightPanel({
|
|
|
87
83
|
setRightPanelTab,
|
|
88
84
|
rightInspectorPanes,
|
|
89
85
|
toggleRightInspectorPane,
|
|
86
|
+
setExclusiveRightInspectorPane,
|
|
90
87
|
handlePanelResizeStart,
|
|
91
88
|
handlePanelResizeMove,
|
|
92
89
|
handlePanelResizeEnd,
|
|
@@ -109,10 +106,12 @@ export function StudioRightPanel({
|
|
|
109
106
|
copiedAgentPrompt,
|
|
110
107
|
clearDomSelection,
|
|
111
108
|
handleUngroupSelection,
|
|
109
|
+
handleGroupSelection,
|
|
112
110
|
handleDomStyleCommit,
|
|
113
111
|
handleDomAttributeCommit,
|
|
114
112
|
handleDomAttributeLiveCommit,
|
|
115
113
|
handleDomHtmlAttributeCommit,
|
|
114
|
+
handleDomAttributesCommit,
|
|
116
115
|
handleDomPathOffsetCommit,
|
|
117
116
|
handleDomBoxSizeCommit,
|
|
118
117
|
handleDomRotationCommit,
|
|
@@ -167,6 +166,7 @@ export function StudioRightPanel({
|
|
|
167
166
|
recordEdit,
|
|
168
167
|
reloadPreview,
|
|
169
168
|
domEditSaveTimestampRef,
|
|
169
|
+
publishSdkSession,
|
|
170
170
|
});
|
|
171
171
|
|
|
172
172
|
// Notes path: persists are debounced in SlideshowPanel; coalesceKey ensures
|
|
@@ -179,16 +179,17 @@ export function StudioRightPanel({
|
|
|
179
179
|
recordEdit,
|
|
180
180
|
reloadPreview,
|
|
181
181
|
domEditSaveTimestampRef,
|
|
182
|
+
publishSdkSession,
|
|
182
183
|
coalesceKey: activeCompPath ? `slideshow-notes:${activeCompPath}` : "slideshow-notes",
|
|
183
184
|
});
|
|
184
185
|
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
}
|
|
186
|
+
const {
|
|
187
|
+
layersPanePercent,
|
|
188
|
+
splitContainerRef,
|
|
189
|
+
handleInspectorSplitResizeStart,
|
|
190
|
+
handleInspectorSplitResizeMove,
|
|
191
|
+
handleInspectorSplitResizeEnd,
|
|
192
|
+
} = useInspectorSplitResize();
|
|
192
193
|
const backgroundRemovalAbortRef = useRef<AbortController | null>(null);
|
|
193
194
|
|
|
194
195
|
useEffect(
|
|
@@ -226,38 +227,16 @@ export function StudioRightPanel({
|
|
|
226
227
|
setRightPanelTab(pane);
|
|
227
228
|
return;
|
|
228
229
|
}
|
|
230
|
+
// Flat inspector: Layers always renders full-height by itself (see the
|
|
231
|
+
// render branch below), so the two panes are mutually exclusive here —
|
|
232
|
+
// otherwise both tabs could show "active" while only one actually shows.
|
|
233
|
+
if (STUDIO_FLAT_INSPECTOR_ENABLED) {
|
|
234
|
+
setExclusiveRightInspectorPane(pane);
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
229
237
|
toggleRightInspectorPane(pane);
|
|
230
238
|
};
|
|
231
239
|
|
|
232
|
-
const handleInspectorSplitResizeStart = useCallback(
|
|
233
|
-
(event: ReactPointerEvent<HTMLDivElement>) => {
|
|
234
|
-
event.preventDefault();
|
|
235
|
-
event.currentTarget.setPointerCapture(event.pointerId);
|
|
236
|
-
const height = splitContainerRef.current?.getBoundingClientRect().height ?? 0;
|
|
237
|
-
splitDragRef.current = {
|
|
238
|
-
startY: event.clientY,
|
|
239
|
-
startPercent: layersPanePercent,
|
|
240
|
-
height,
|
|
241
|
-
};
|
|
242
|
-
},
|
|
243
|
-
[layersPanePercent],
|
|
244
|
-
);
|
|
245
|
-
|
|
246
|
-
const handleInspectorSplitResizeMove = useCallback((event: ReactPointerEvent<HTMLDivElement>) => {
|
|
247
|
-
const drag = splitDragRef.current;
|
|
248
|
-
if (!drag || drag.height <= 0) return;
|
|
249
|
-
const deltaPercent = ((event.clientY - drag.startY) / drag.height) * 100;
|
|
250
|
-
const next = Math.min(
|
|
251
|
-
MAX_INSPECTOR_SPLIT_PERCENT,
|
|
252
|
-
Math.max(MIN_INSPECTOR_SPLIT_PERCENT, drag.startPercent + deltaPercent),
|
|
253
|
-
);
|
|
254
|
-
setLayersPanePercent(next);
|
|
255
|
-
}, []);
|
|
256
|
-
|
|
257
|
-
const handleInspectorSplitResizeEnd = useCallback(() => {
|
|
258
|
-
splitDragRef.current = null;
|
|
259
|
-
}, []);
|
|
260
|
-
|
|
261
240
|
const handleApplyColorGradingScope = useCallback(
|
|
262
241
|
async (scope: ColorGradingScope, value: string | null) =>
|
|
263
242
|
applyColorGradingScopeUpdate({
|
|
@@ -341,12 +320,17 @@ export function StudioRightPanel({
|
|
|
341
320
|
},
|
|
342
321
|
[projectId, refreshFileTree, showToast],
|
|
343
322
|
);
|
|
344
|
-
|
|
323
|
+
const handleHideAllSelected = () => {
|
|
324
|
+
const { elements } = usePlayerStore.getState();
|
|
325
|
+
const keys = timelineKeysForSelections(domEditGroupSelections, elements, activeCompPath);
|
|
326
|
+
if (keys.length > 0) void onToggleElementHidden?.(keys, true);
|
|
327
|
+
};
|
|
345
328
|
const propertyPanel = (
|
|
346
329
|
<DesignPanelPromoteProvider
|
|
347
330
|
selection={domEditGroupSelections.length > 1 ? null : domEditSelection}
|
|
348
331
|
projectId={projectId}
|
|
349
332
|
activeCompPath={activeCompPath}
|
|
333
|
+
showToast={showToast}
|
|
350
334
|
readProjectFile={readProjectFile}
|
|
351
335
|
writeProjectFile={writeProjectFile}
|
|
352
336
|
recordEdit={recordEdit}
|
|
@@ -359,12 +343,16 @@ export function StudioRightPanel({
|
|
|
359
343
|
assets={assets}
|
|
360
344
|
element={domEditGroupSelections.length > 1 ? null : domEditSelection}
|
|
361
345
|
multiSelectCount={domEditGroupSelections.length}
|
|
346
|
+
multiSelectedElements={domEditGroupSelections}
|
|
347
|
+
onGroupSelection={handleGroupSelection}
|
|
348
|
+
onHideAllSelected={handleHideAllSelected}
|
|
362
349
|
copiedAgentPrompt={copiedAgentPrompt}
|
|
363
350
|
onClearSelection={clearDomSelection}
|
|
364
351
|
onToggleElementHidden={onToggleElementHidden}
|
|
365
352
|
onUngroup={handleUngroupSelection}
|
|
366
353
|
onSetStyle={handleDomStyleCommit}
|
|
367
354
|
onSetAttribute={handleDomAttributeCommit}
|
|
355
|
+
onSetAttributes={handleDomAttributesCommit}
|
|
368
356
|
onSetAttributeLive={handleDomAttributeLiveCommit}
|
|
369
357
|
onApplyColorGradingScope={handleApplyColorGradingScope}
|
|
370
358
|
onSetHtmlAttribute={handleDomHtmlAttributeCommit}
|
|
@@ -534,11 +522,12 @@ export function StudioRightPanel({
|
|
|
534
522
|
) : rightPanelTab === "variables" ? (
|
|
535
523
|
<VariablesPanel
|
|
536
524
|
sdkSession={sdkSession}
|
|
525
|
+
publishSdkSession={publishSdkSession}
|
|
537
526
|
reloadPreview={reloadPreview}
|
|
538
527
|
domEditSaveTimestampRef={domEditSaveTimestampRef}
|
|
539
528
|
recordEdit={recordEdit}
|
|
540
529
|
/>
|
|
541
|
-
) : layersPaneOpen && designPaneOpen ? (
|
|
530
|
+
) : layersPaneOpen && designPaneOpen && !STUDIO_FLAT_INSPECTOR_ENABLED ? (
|
|
542
531
|
<div ref={splitContainerRef} className="flex h-full min-h-0 min-w-0 flex-col">
|
|
543
532
|
<div
|
|
544
533
|
className="min-h-[120px] overflow-hidden"
|
|
@@ -0,0 +1,134 @@
|
|
|
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 { AnimationCard } from "./AnimationCard";
|
|
7
|
+
import type { GsapAnimation } from "@hyperframes/core/gsap-parser";
|
|
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 baseAnimation(overrides: Partial<GsapAnimation> = {}): GsapAnimation {
|
|
16
|
+
return {
|
|
17
|
+
id: "anim-1",
|
|
18
|
+
method: "to",
|
|
19
|
+
position: 0.8,
|
|
20
|
+
duration: 1.2,
|
|
21
|
+
ease: "power2.out",
|
|
22
|
+
properties: { opacity: 1 },
|
|
23
|
+
...overrides,
|
|
24
|
+
} as GsapAnimation;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const noop = () => {};
|
|
28
|
+
|
|
29
|
+
describe("AnimationCard flat branch", () => {
|
|
30
|
+
it("renders a mint border-left and panel-token colors when flat", () => {
|
|
31
|
+
const host = document.createElement("div");
|
|
32
|
+
document.body.append(host);
|
|
33
|
+
const root = createRoot(host);
|
|
34
|
+
act(() => {
|
|
35
|
+
root.render(
|
|
36
|
+
<AnimationCard
|
|
37
|
+
animation={baseAnimation()}
|
|
38
|
+
defaultExpanded={false}
|
|
39
|
+
flat
|
|
40
|
+
onUpdateProperty={noop}
|
|
41
|
+
onUpdateMeta={noop}
|
|
42
|
+
onDeleteAnimation={noop}
|
|
43
|
+
onAddProperty={noop}
|
|
44
|
+
onRemoveProperty={noop}
|
|
45
|
+
/>,
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
const card = host.querySelector('[data-flat-effect-card="true"]');
|
|
49
|
+
expect(card).not.toBeNull();
|
|
50
|
+
expect(card?.className).toContain("border-panel-accent");
|
|
51
|
+
act(() => root.unmount());
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("still renders the legacy (non-flat) appearance when flat is omitted", () => {
|
|
55
|
+
const host = document.createElement("div");
|
|
56
|
+
document.body.append(host);
|
|
57
|
+
const root = createRoot(host);
|
|
58
|
+
act(() => {
|
|
59
|
+
root.render(
|
|
60
|
+
<AnimationCard
|
|
61
|
+
animation={baseAnimation()}
|
|
62
|
+
defaultExpanded={false}
|
|
63
|
+
onUpdateProperty={noop}
|
|
64
|
+
onUpdateMeta={noop}
|
|
65
|
+
onDeleteAnimation={noop}
|
|
66
|
+
onAddProperty={noop}
|
|
67
|
+
onRemoveProperty={noop}
|
|
68
|
+
/>,
|
|
69
|
+
);
|
|
70
|
+
});
|
|
71
|
+
expect(host.querySelector('[data-flat-effect-card="true"]')).toBeNull();
|
|
72
|
+
expect(host.textContent).toContain("power2.out");
|
|
73
|
+
act(() => root.unmount());
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("toggles expanded state when the collapsed header button is clicked, in both modes", () => {
|
|
77
|
+
for (const flat of [false, true]) {
|
|
78
|
+
const host = document.createElement("div");
|
|
79
|
+
document.body.append(host);
|
|
80
|
+
const root = createRoot(host);
|
|
81
|
+
act(() => {
|
|
82
|
+
root.render(
|
|
83
|
+
<AnimationCard
|
|
84
|
+
animation={baseAnimation()}
|
|
85
|
+
defaultExpanded={false}
|
|
86
|
+
flat={flat || undefined}
|
|
87
|
+
onUpdateProperty={noop}
|
|
88
|
+
onUpdateMeta={noop}
|
|
89
|
+
onDeleteAnimation={noop}
|
|
90
|
+
onAddProperty={noop}
|
|
91
|
+
onRemoveProperty={noop}
|
|
92
|
+
/>,
|
|
93
|
+
);
|
|
94
|
+
});
|
|
95
|
+
expect(host.textContent).not.toContain("Remove");
|
|
96
|
+
const button = host.querySelector("button");
|
|
97
|
+
expect(button).not.toBeNull();
|
|
98
|
+
act(() => {
|
|
99
|
+
button?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
|
100
|
+
});
|
|
101
|
+
expect(host.textContent).toContain("Remove");
|
|
102
|
+
act(() => root.unmount());
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("invokes onDeleteAnimation with the animation id when Remove is clicked, in flat mode", () => {
|
|
107
|
+
const onDeleteAnimation = vi.fn();
|
|
108
|
+
const host = document.createElement("div");
|
|
109
|
+
document.body.append(host);
|
|
110
|
+
const root = createRoot(host);
|
|
111
|
+
act(() => {
|
|
112
|
+
root.render(
|
|
113
|
+
<AnimationCard
|
|
114
|
+
animation={baseAnimation()}
|
|
115
|
+
defaultExpanded={true}
|
|
116
|
+
flat
|
|
117
|
+
onUpdateProperty={noop}
|
|
118
|
+
onUpdateMeta={noop}
|
|
119
|
+
onDeleteAnimation={onDeleteAnimation}
|
|
120
|
+
onAddProperty={noop}
|
|
121
|
+
onRemoveProperty={noop}
|
|
122
|
+
/>,
|
|
123
|
+
);
|
|
124
|
+
});
|
|
125
|
+
const buttons = Array.from(host.querySelectorAll("button"));
|
|
126
|
+
const removeButton = buttons.find((b) => b.textContent === "Remove");
|
|
127
|
+
expect(removeButton).not.toBeUndefined();
|
|
128
|
+
act(() => {
|
|
129
|
+
removeButton?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
|
130
|
+
});
|
|
131
|
+
expect(onDeleteAnimation).toHaveBeenCalledWith("anim-1");
|
|
132
|
+
act(() => root.unmount());
|
|
133
|
+
});
|
|
134
|
+
});
|
|
@@ -21,12 +21,14 @@ import {
|
|
|
21
21
|
interface AnimationCardProps extends GsapAnimationEditCallbacks {
|
|
22
22
|
animation: GsapAnimation;
|
|
23
23
|
defaultExpanded: boolean;
|
|
24
|
+
flat?: boolean;
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
// fallow-ignore-next-line complexity
|
|
27
28
|
export const AnimationCard = memo(function AnimationCard({
|
|
28
29
|
animation,
|
|
29
30
|
defaultExpanded,
|
|
31
|
+
flat,
|
|
30
32
|
onUpdateProperty,
|
|
31
33
|
onUpdateMeta,
|
|
32
34
|
onDeleteAnimation,
|
|
@@ -150,7 +152,14 @@ export const AnimationCard = memo(function AnimationCard({
|
|
|
150
152
|
);
|
|
151
153
|
|
|
152
154
|
return (
|
|
153
|
-
<div
|
|
155
|
+
<div
|
|
156
|
+
data-flat-effect-card={flat ? "true" : undefined}
|
|
157
|
+
className={
|
|
158
|
+
flat
|
|
159
|
+
? "border-b border-l-2 border-panel-accent/40 border-b-panel-hairline pb-3 pl-2"
|
|
160
|
+
: "border-b border-neutral-800 pb-3"
|
|
161
|
+
}
|
|
162
|
+
>
|
|
154
163
|
<button
|
|
155
164
|
type="button"
|
|
156
165
|
onClick={() => setExpanded((v) => !v)}
|
|
@@ -162,13 +171,19 @@ export const AnimationCard = memo(function AnimationCard({
|
|
|
162
171
|
>
|
|
163
172
|
{methodLabel}
|
|
164
173
|
</span>
|
|
165
|
-
<span
|
|
174
|
+
<span
|
|
175
|
+
className={`text-[11px] font-medium ${flat ? "text-panel-text-3" : "text-neutral-400"}`}
|
|
176
|
+
title="When this effect plays"
|
|
177
|
+
>
|
|
166
178
|
{typeof animation.position === "number"
|
|
167
179
|
? `${parseFloat(animation.position.toFixed(3))}s`
|
|
168
180
|
: animation.position}{" "}
|
|
169
181
|
– {typeof endTime === "number" ? `${parseFloat(endTime.toFixed(3))}s` : endTime}
|
|
170
182
|
</span>
|
|
171
|
-
<span
|
|
183
|
+
<span
|
|
184
|
+
className={`ml-auto text-[10px] ${flat ? "text-panel-text-3" : "text-neutral-500"}`}
|
|
185
|
+
title={easeName}
|
|
186
|
+
>
|
|
172
187
|
{easeLabel}
|
|
173
188
|
</span>
|
|
174
189
|
<svg
|
|
@@ -176,7 +191,7 @@ export const AnimationCard = memo(function AnimationCard({
|
|
|
176
191
|
height="10"
|
|
177
192
|
viewBox="0 0 10 10"
|
|
178
193
|
fill="currentColor"
|
|
179
|
-
className={`flex-shrink-0 text-neutral-500
|
|
194
|
+
className={`flex-shrink-0 transition-transform ${flat ? "text-panel-text-5" : "text-neutral-500"} ${expanded ? "" : "-rotate-90"}`}
|
|
180
195
|
>
|
|
181
196
|
<path d="M2 3l3 4 3-4z" />
|
|
182
197
|
</svg>
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { useTrackDesignInput } from "../../contexts/DesignPanelInputContext";
|
|
2
|
+
|
|
1
3
|
export type GestureRecordingState = "idle" | "recording" | "preview";
|
|
2
4
|
|
|
3
5
|
interface GestureRecordIconProps {
|
|
@@ -28,13 +30,17 @@ export function GestureRecordPanelButton({
|
|
|
28
30
|
onToggleRecording,
|
|
29
31
|
}: GestureRecordPanelButtonProps) {
|
|
30
32
|
const recording = recordingState === "recording";
|
|
33
|
+
const track = useTrackDesignInput();
|
|
31
34
|
|
|
32
35
|
return (
|
|
33
36
|
<div className="px-4 pb-3">
|
|
34
37
|
<button
|
|
35
38
|
type="button"
|
|
36
39
|
onMouseDown={(e) => e.preventDefault()}
|
|
37
|
-
onClick={
|
|
40
|
+
onClick={() => {
|
|
41
|
+
track("button", "Gesture recording");
|
|
42
|
+
onToggleRecording();
|
|
43
|
+
}}
|
|
38
44
|
className={`w-full flex items-center justify-center gap-2 rounded-lg py-2 text-[11px] font-medium transition-colors ${
|
|
39
45
|
recording
|
|
40
46
|
? "bg-red-500/15 text-red-400 border border-red-500/30 animate-pulse"
|