@hyperframes/studio 0.7.54 → 0.7.56
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/{index-uBY329wb.js → index-BWnOxAiH.js} +1 -1
- package/dist/assets/{index-CMHYjEZ5.js → index-C4csZims.js} +1 -1
- package/dist/assets/index-CmVCjZjp.js +423 -0
- package/dist/assets/index-D-GyYi2d.css +1 -0
- package/dist/{chunk-SOTCF4DF.js → chunk-5QSIMBEJ.js} +2 -1
- package/dist/chunk-5QSIMBEJ.js.map +1 -0
- package/dist/{domEditingLayers-2ECJK24D.js → domEditingLayers-6LQGKPOI.js} +2 -2
- package/dist/index.d.ts +146 -144
- package/dist/index.html +2 -2
- package/dist/index.js +40582 -37803
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
- package/src/App.tsx +106 -96
- package/src/components/EditorShell.tsx +253 -0
- package/src/components/StudioGlobalDragOverlay.tsx +1 -3
- package/src/components/StudioLeftSidebar.tsx +13 -3
- package/src/components/StudioRightPanel.tsx +8 -3
- package/src/components/TimelineToolbar.test.tsx +2 -2
- package/src/components/TimelineToolbar.tsx +179 -121
- package/src/components/editor/CanvasContextMenu.test.tsx +115 -0
- package/src/components/editor/CanvasContextMenu.tsx +198 -0
- package/src/components/editor/DomEditCropHandles.test.tsx +74 -3
- package/src/components/editor/DomEditCropHandles.tsx +64 -29
- package/src/components/editor/DomEditOverlay.test.ts +245 -279
- package/src/components/editor/DomEditOverlay.tsx +158 -126
- package/src/components/editor/DomEditOverlayHover.test.tsx +90 -0
- package/src/components/editor/DomEditRotateHandle.tsx +38 -18
- package/src/components/editor/DomEditSelectionChrome.test.tsx +80 -0
- package/src/components/editor/DomEditSelectionChrome.tsx +257 -0
- package/src/components/editor/LayersPanel.test.ts +66 -2
- package/src/components/editor/LayersPanel.tsx +43 -1
- package/src/components/editor/OffCanvasIndicators.test.tsx +34 -0
- package/src/components/editor/OffCanvasIndicators.tsx +49 -10
- package/src/components/editor/SnapGuideOverlay.tsx +28 -23
- package/src/components/editor/SnapToolbar.test.tsx +0 -1
- package/src/components/editor/anchoredResizeCommitFeedsOffset.test.ts +214 -0
- package/src/components/editor/anchoredResizeReleaseShift.test.ts +208 -0
- package/src/components/editor/canvasContextMenuZOrder.test.ts +435 -0
- package/src/components/editor/canvasContextMenuZOrder.ts +352 -0
- package/src/components/editor/domEditNudge.test.ts +80 -0
- package/src/components/editor/domEditNudge.ts +44 -0
- package/src/components/editor/domEditOverlayGeometry.test.ts +167 -1
- package/src/components/editor/domEditOverlayGeometry.ts +280 -20
- package/src/components/editor/domEditOverlayGestures.ts +44 -41
- package/src/components/editor/domEditOverlayStartGesture.ts +62 -2
- package/src/components/editor/domEditResizeLocal.test.ts +131 -0
- package/src/components/editor/domEditResizeLocal.ts +125 -0
- package/src/components/editor/domEditingDom.ts +1 -1
- package/src/components/editor/manualEditsDomPatches.test.ts +47 -0
- package/src/components/editor/manualOffsetDrag.ts +55 -12
- package/src/components/editor/offCanvasIndicatorGeometry.ts +31 -10
- package/src/components/editor/offCanvasIndicatorRefresh.test.tsx +25 -1
- package/src/components/editor/resizeDraft.ts +108 -0
- package/src/components/editor/snapEngine.test.ts +12 -78
- package/src/components/editor/snapEngine.ts +13 -53
- package/src/components/editor/useDomEditNudge.test.tsx +228 -0
- package/src/components/editor/useDomEditNudge.ts +254 -0
- package/src/components/editor/useDomEditOverlayGestures.ts +115 -116
- package/src/components/editor/useDomEditOverlayRects.ts +9 -2
- package/src/components/nle/AssetPreviewOverlay.tsx +147 -0
- package/src/components/nle/NLEContext.test.ts +144 -0
- package/src/components/nle/NLEContext.tsx +328 -0
- package/src/components/nle/NLEPreview.tsx +1 -1
- package/src/components/nle/PreviewOverlays.tsx +240 -0
- package/src/components/nle/PreviewPane.tsx +163 -0
- package/src/components/nle/TimelinePane.test.ts +60 -0
- package/src/components/nle/TimelinePane.tsx +293 -0
- package/src/components/nle/TimelineResizeDivider.tsx +8 -2
- package/src/components/nle/useCompositionStack.test.tsx +44 -0
- package/src/components/nle/useCompositionStack.ts +17 -9
- package/src/components/nle/useTimelineEditCallbacks.ts +217 -0
- package/src/components/sidebar/AssetCard.tsx +326 -0
- package/src/components/sidebar/AssetContextMenu.tsx +15 -30
- package/src/components/sidebar/AssetsTab.test.ts +96 -0
- package/src/components/sidebar/AssetsTab.tsx +71 -206
- package/src/components/sidebar/AudioRow.tsx +39 -2
- package/src/components/sidebar/BlocksTab.tsx +8 -1
- package/src/components/sidebar/LeftSidebar.tsx +4 -1
- package/src/components/sidebar/assetHelpers.ts +29 -0
- package/src/contexts/DomEditContext.tsx +9 -0
- package/src/contexts/StudioContext.tsx +9 -8
- package/src/contexts/TimelineEditContext.tsx +3 -7
- package/src/hooks/deleteSelectedKeyframes.ts +35 -0
- package/src/hooks/domEditCommitTypes.ts +12 -1
- package/src/hooks/domSelectionTestHarness.ts +14 -0
- package/src/hooks/gestureTransaction.test.ts +311 -0
- package/src/hooks/gestureTransaction.ts +199 -0
- package/src/hooks/gsapDragCommit.test.ts +141 -41
- package/src/hooks/gsapDragCommit.ts +64 -74
- package/src/hooks/gsapDragStaticSetHelpers.ts +9 -21
- package/src/hooks/gsapResizeIntercept.test.ts +69 -0
- package/src/hooks/gsapResizeIntercept.ts +38 -10
- package/src/hooks/gsapRuntimeBridge.test.ts +49 -13
- package/src/hooks/gsapRuntimeBridge.ts +4 -4
- package/src/hooks/gsapScriptCommitTypes.ts +16 -5
- package/src/hooks/gsapShared.test.ts +19 -1
- package/src/hooks/gsapShared.ts +14 -0
- package/src/hooks/patchDocumentRootDuration.test.ts +71 -0
- package/src/hooks/timelineEditingGsap.ts +14 -0
- package/src/hooks/timelineEditingHelpers.test.ts +34 -1
- package/src/hooks/timelineEditingHelpers.ts +22 -48
- package/src/hooks/timelineMoveAdapter.test.ts +77 -0
- package/src/hooks/timelineMoveAdapter.ts +37 -0
- package/src/hooks/useAnimatedPropertyCommit.test.tsx +36 -8
- package/src/hooks/useAnimatedPropertyCommit.ts +31 -26
- package/src/hooks/useAppHotkeys.ts +16 -25
- package/src/hooks/useBlockHandlers.ts +55 -27
- package/src/hooks/useContextMenuDismiss.ts +31 -7
- package/src/hooks/useDomEditCommits.test.tsx +154 -1
- package/src/hooks/useDomEditCommits.ts +100 -3
- package/src/hooks/useDomEditPositionPatchCommit.ts +2 -0
- package/src/hooks/useDomEditSession.ts +2 -2
- package/src/hooks/useDomEditTextCommits.test.tsx +135 -0
- package/src/hooks/useDomEditTextCommits.ts +8 -3
- package/src/hooks/useDomEditWiring.ts +7 -9
- package/src/hooks/useDomGeometryCommits.ts +17 -2
- package/src/hooks/useDomSelection.test.ts +2 -36
- package/src/hooks/useDomSelection.ts +81 -51
- package/src/hooks/useDomSelectionSelectionGuards.test.ts +276 -0
- package/src/hooks/useElementLifecycleOps.test.tsx +258 -0
- package/src/hooks/useElementLifecycleOps.ts +40 -71
- package/src/hooks/useEnableKeyframes.test.ts +142 -1
- package/src/hooks/useEnableKeyframes.ts +73 -29
- package/src/hooks/useGestureCommit.test.tsx +130 -0
- package/src/hooks/useGestureCommit.ts +32 -8
- package/src/hooks/useGroupCommits.ts +7 -28
- package/src/hooks/useGsapAwareEditing.test.tsx +239 -0
- package/src/hooks/useGsapAwareEditing.ts +115 -20
- package/src/hooks/useGsapKeyframeOps.test.tsx +59 -0
- package/src/hooks/useGsapKeyframeOps.ts +38 -10
- package/src/hooks/useGsapScriptCommits.test.tsx +40 -1
- package/src/hooks/useGsapScriptCommits.ts +134 -50
- package/src/hooks/useGsapSelectionHandlers.ts +49 -16
- package/src/hooks/useMusicBeatAnalysis.ts +72 -36
- package/src/hooks/usePersistentEditHistory.test.ts +33 -0
- package/src/hooks/usePersistentEditHistory.ts +92 -58
- package/src/hooks/usePreviewPersistence.ts +25 -7
- package/src/hooks/useRazorSplit.history.test.tsx +303 -0
- package/src/hooks/useRazorSplit.test.ts +269 -0
- package/src/hooks/useRazorSplit.testHelpers.ts +64 -0
- package/src/hooks/useRazorSplit.ts +107 -36
- package/src/hooks/useRenderClipContent.ts +24 -6
- package/src/hooks/useStudioContextValue.ts +15 -5
- package/src/hooks/useStudioUrlState.ts +2 -4
- package/src/hooks/useTimelineEditing.test.tsx +20 -29
- package/src/hooks/useTimelineEditingTypes.ts +6 -0
- package/src/hooks/useTimelineGroupEditing.ts +18 -4
- package/src/index.ts +1 -1
- package/src/player/components/ImageThumbnail.test.tsx +173 -0
- package/src/player/components/ImageThumbnail.tsx +160 -0
- package/src/player/components/PlayheadIndicator.tsx +50 -5
- package/src/player/components/Timeline.test.ts +102 -19
- package/src/player/components/Timeline.tsx +194 -194
- package/src/player/components/TimelineCanvas.tsx +207 -521
- package/src/player/components/TimelineClip.tsx +4 -1
- package/src/player/components/TimelineClipDiamonds.tsx +9 -40
- package/src/player/components/TimelineEmptyState.tsx +3 -1
- package/src/player/components/TimelineLanes.tsx +487 -0
- package/src/player/components/TimelineOverlays.tsx +122 -0
- package/src/player/components/VideoThumbnail.test.tsx +152 -0
- package/src/player/components/VideoThumbnail.tsx +28 -7
- package/src/player/components/thumbnailUtils.test.ts +127 -0
- package/src/player/components/thumbnailUtils.ts +54 -0
- package/src/player/components/timelineCallbacks.ts +18 -18
- package/src/player/components/timelineClipChildren.tsx +39 -0
- package/src/player/components/timelineClipDragCommit.test.ts +943 -0
- package/src/player/components/timelineClipDragCommit.ts +401 -0
- package/src/player/components/timelineClipDragPreview.test.ts +144 -0
- package/src/player/components/timelineClipDragPreview.ts +323 -38
- package/src/player/components/timelineClipDragTypes.ts +65 -0
- package/src/player/components/timelineCollision.test.ts +477 -0
- package/src/player/components/timelineCollision.ts +263 -0
- package/src/player/components/timelineDragDrop.ts +69 -60
- package/src/player/components/timelineEditCapabilities.ts +52 -0
- package/src/player/components/timelineEditing.test.ts +1 -1
- package/src/player/components/timelineEditing.ts +76 -59
- package/src/player/components/timelineGroupEditing.test.ts +126 -0
- package/src/player/components/timelineGroupEditing.ts +133 -0
- package/src/player/components/timelineGroupResizeCommit.ts +54 -0
- package/src/player/components/timelineLayout.test.ts +95 -23
- package/src/player/components/timelineLayout.ts +120 -61
- package/src/player/components/timelineMarquee.test.ts +197 -0
- package/src/player/components/timelineMarquee.ts +112 -0
- package/src/player/components/timelineMultiDragPreview.test.ts +127 -0
- package/src/player/components/timelineMultiDragPreview.ts +106 -0
- package/src/player/components/timelineOptimisticRevision.ts +42 -0
- package/src/player/components/timelineSnapping.test.ts +134 -0
- package/src/player/components/timelineSnapping.ts +110 -0
- package/src/player/components/timelineStackingSync.test.ts +244 -0
- package/src/player/components/timelineStackingSync.ts +331 -0
- package/src/player/components/timelineTheme.ts +6 -1
- package/src/player/components/timelineZones.test.ts +425 -0
- package/src/player/components/timelineZones.ts +198 -0
- package/src/player/components/timelineZoom.test.ts +67 -0
- package/src/player/components/timelineZoom.ts +51 -0
- package/src/player/components/useResolvedTimelineEditCallbacks.ts +25 -3
- package/src/player/components/useTimelineClipDrag.resize.test.tsx +241 -0
- package/src/player/components/useTimelineClipDrag.ts +380 -386
- package/src/player/components/useTimelineEditPinning.ts +121 -0
- package/src/player/components/useTimelineGeometry.ts +129 -0
- package/src/player/components/useTimelinePlayhead.ts +4 -16
- package/src/player/components/useTimelineRangeSelection.ts +341 -50
- package/src/player/components/useTimelineStackingSync.test.tsx +67 -0
- package/src/player/components/useTimelineStackingSync.ts +83 -0
- package/src/player/hooks/useExpandedTimelineElements.ts +0 -3
- package/src/player/hooks/usePlaybackKeyboard.ts +5 -0
- package/src/player/hooks/useTimelinePlayer.ts +48 -53
- package/src/player/hooks/useTimelinePlayerLoop.ts +85 -0
- package/src/player/hooks/useTimelineSyncCallbacks.test.ts +219 -0
- package/src/player/hooks/useTimelineSyncCallbacks.ts +104 -4
- package/src/player/index.ts +3 -2
- package/src/player/lib/time.test.ts +19 -19
- package/src/player/lib/time.ts +6 -2
- package/src/player/lib/timelineDOM.test.ts +25 -98
- package/src/player/lib/timelineDOM.ts +12 -81
- package/src/player/lib/timelineElementHelpers.ts +54 -5
- package/src/player/lib/timelineIframeHelpers.ts +2 -0
- package/src/player/store/playerStore.ts +62 -3
- package/src/styles/studio.css +27 -0
- package/src/utils/assetClickBehavior.test.ts +104 -0
- package/src/utils/assetClickBehavior.ts +75 -0
- package/src/utils/assetPreviewStore.ts +33 -0
- package/src/utils/blockInstaller.ts +10 -2
- package/src/utils/canvasNudgeGate.test.ts +31 -0
- package/src/utils/canvasNudgeGate.ts +32 -0
- package/src/utils/editHistory.test.ts +35 -0
- package/src/utils/gsapSoftReload.test.ts +119 -1
- package/src/utils/gsapSoftReload.ts +153 -0
- package/src/utils/mediaTypes.ts +3 -2
- package/src/utils/resizeDebug.ts +55 -0
- package/src/utils/rootDuration.test.ts +90 -1
- package/src/utils/rootDuration.ts +76 -13
- package/src/utils/sdkCutover.ts +4 -0
- package/src/utils/studioFileHistory.ts +37 -7
- package/src/utils/studioHelpers.ts +62 -0
- package/src/utils/studioPreviewHelpers.test.ts +65 -8
- package/src/utils/studioPreviewHelpers.ts +10 -0
- package/src/utils/studioTelemetry.ts +4 -1
- package/src/utils/studioUiPreferences.test.ts +38 -0
- package/src/utils/studioUiPreferences.ts +27 -0
- package/src/utils/studioUrlState.test.ts +0 -1
- package/src/utils/timelineAssetDrop.ts +62 -0
- package/src/utils/timelineDiscovery.ts +0 -17
- package/src/utils/timelineInspector.test.ts +121 -0
- package/src/utils/timelineInspector.ts +32 -1
- package/dist/assets/index-Dq7FEg0K.css +0 -1
- package/dist/assets/index-pRhCpGPz.js +0 -423
- package/dist/chunk-SOTCF4DF.js.map +0 -1
- package/src/components/StudioPreviewArea.tsx +0 -500
- package/src/components/nle/NLELayout.test.ts +0 -12
- package/src/components/nle/NLELayout.tsx +0 -591
- package/src/player/components/TimelineLayerGutter.tsx +0 -61
- package/src/player/components/TimelineSelectionOverlays.tsx +0 -55
- package/src/player/components/timelineMarqueeSelection.test.ts +0 -87
- package/src/player/components/timelineSnapTargets.test.ts +0 -144
- package/src/player/components/timelineSnapTargets.ts +0 -164
- package/src/player/components/useTimelineClipDrag.test.tsx +0 -506
- package/src/player/components/useTimelineClipGroupDrag.ts +0 -417
- package/src/player/components/useTimelineMarqueeSelection.test.tsx +0 -231
- package/src/player/components/useTimelineMarqueeSelection.ts +0 -276
- package/src/utils/timelineDiscovery.test.ts +0 -90
- /package/dist/{domEditingLayers-2ECJK24D.js.map → domEditingLayers-6LQGKPOI.js.map} +0 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Block drop/add handlers for the Studio.
|
|
3
3
|
* Extracted from App.tsx to keep file sizes under the 600-line limit.
|
|
4
4
|
*/
|
|
5
|
-
import { useCallback, useMemo, useState } from "react";
|
|
5
|
+
import { useCallback, useMemo, useRef, useState } from "react";
|
|
6
6
|
import type { TimelineElement } from "../player";
|
|
7
7
|
import { usePlayerStore } from "../player";
|
|
8
8
|
import { addBlockToProject } from "../utils/blockInstaller";
|
|
@@ -83,17 +83,41 @@ export function useBlockHandlers({
|
|
|
83
83
|
],
|
|
84
84
|
);
|
|
85
85
|
|
|
86
|
+
// Block installs hit the server and end in a full preview reload; without a
|
|
87
|
+
// guard, repeat drops while one is in flight stack duplicate installs.
|
|
88
|
+
const installingBlockRef = useRef(false);
|
|
89
|
+
const runBlockInstall = useCallback(
|
|
90
|
+
async <T>(blockName: string, install: () => Promise<T>): Promise<T | null> => {
|
|
91
|
+
if (installingBlockRef.current) {
|
|
92
|
+
blockCtx.showToast("A block is already installing — one moment…", "info");
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
installingBlockRef.current = true;
|
|
96
|
+
blockCtx.showToast(`Adding ${blockName}…`, "info");
|
|
97
|
+
try {
|
|
98
|
+
return await install();
|
|
99
|
+
} finally {
|
|
100
|
+
installingBlockRef.current = false;
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
[blockCtx],
|
|
104
|
+
);
|
|
105
|
+
|
|
86
106
|
const handleAddBlock = useCallback(
|
|
87
107
|
(blockName: string) => {
|
|
88
108
|
if (!projectId) return;
|
|
109
|
+
// fallow-ignore-next-line complexity
|
|
89
110
|
void (async () => {
|
|
90
|
-
const result = await
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
111
|
+
const result = await runBlockInstall(blockName, () =>
|
|
112
|
+
addBlockToProject({
|
|
113
|
+
projectId,
|
|
114
|
+
blockName,
|
|
115
|
+
...blockCtx,
|
|
116
|
+
previewIframe: previewIframeRef.current,
|
|
117
|
+
currentTime: usePlayerStore.getState().currentTime,
|
|
118
|
+
}),
|
|
119
|
+
);
|
|
120
|
+
if (result === null) return;
|
|
97
121
|
const params = result?.block.type === "hyperframes:block" ? result.block.params : undefined;
|
|
98
122
|
if (params?.length) {
|
|
99
123
|
setActiveBlockParams({
|
|
@@ -107,37 +131,41 @@ export function useBlockHandlers({
|
|
|
107
131
|
}
|
|
108
132
|
})();
|
|
109
133
|
},
|
|
110
|
-
[projectId, blockCtx, previewIframeRef, setRightCollapsed, setRightPanelTab],
|
|
134
|
+
[projectId, blockCtx, previewIframeRef, runBlockInstall, setRightCollapsed, setRightPanelTab],
|
|
111
135
|
);
|
|
112
136
|
|
|
113
137
|
const handleTimelineBlockDrop = useCallback(
|
|
114
138
|
(blockName: string, placement: { start: number; track: number }) => {
|
|
115
139
|
if (!projectId) return;
|
|
116
|
-
void
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
140
|
+
void runBlockInstall(blockName, () =>
|
|
141
|
+
addBlockToProject({
|
|
142
|
+
projectId,
|
|
143
|
+
blockName,
|
|
144
|
+
placement,
|
|
145
|
+
...blockCtx,
|
|
146
|
+
previewIframe: previewIframeRef.current,
|
|
147
|
+
currentTime: usePlayerStore.getState().currentTime,
|
|
148
|
+
}),
|
|
149
|
+
);
|
|
124
150
|
},
|
|
125
|
-
[projectId, blockCtx, previewIframeRef],
|
|
151
|
+
[projectId, blockCtx, previewIframeRef, runBlockInstall],
|
|
126
152
|
);
|
|
127
153
|
|
|
128
154
|
const handlePreviewBlockDrop = useCallback(
|
|
129
155
|
(blockName: string, position: { left: number; top: number }) => {
|
|
130
156
|
if (!projectId) return;
|
|
131
|
-
void
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
157
|
+
void runBlockInstall(blockName, () =>
|
|
158
|
+
addBlockToProject({
|
|
159
|
+
projectId,
|
|
160
|
+
blockName,
|
|
161
|
+
visualPosition: position,
|
|
162
|
+
...blockCtx,
|
|
163
|
+
previewIframe: previewIframeRef.current,
|
|
164
|
+
currentTime: usePlayerStore.getState().currentTime,
|
|
165
|
+
}),
|
|
166
|
+
);
|
|
139
167
|
},
|
|
140
|
-
[projectId, blockCtx, previewIframeRef],
|
|
168
|
+
[projectId, blockCtx, previewIframeRef, runBlockInstall],
|
|
141
169
|
);
|
|
142
170
|
|
|
143
171
|
return {
|
|
@@ -1,26 +1,50 @@
|
|
|
1
1
|
import { useCallback, useEffect, useRef, type RefObject } from "react";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Shared dismiss logic for context menus: closes on
|
|
5
|
-
*
|
|
4
|
+
* Shared dismiss logic for context menus: closes on ANY pointerdown outside the
|
|
5
|
+
* menu (mouse, pen, or touch), or Escape.
|
|
6
|
+
*
|
|
7
|
+
* Two failure modes this guards against, both seen in the canvas editor:
|
|
8
|
+
*
|
|
9
|
+
* 1. The menu lives inside DomEditOverlay, whose own pointer handlers call
|
|
10
|
+
* `event.stopPropagation()` on several branches (marquee start, shift-select).
|
|
11
|
+
* A bubble-phase `mousedown`/`pointerdown` listener on `document` never fires
|
|
12
|
+
* for those events — the overlay eats them first — so the menu stayed open.
|
|
13
|
+
* Listening in the CAPTURE phase runs this dismiss BEFORE any bubble-phase
|
|
14
|
+
* stopPropagation, so an outside press always closes the menu.
|
|
15
|
+
*
|
|
16
|
+
* 2. `mousedown` alone misses pointer/touch-only gestures. `pointerdown` is the
|
|
17
|
+
* superset (fires for mouse, pen, and touch) and is exactly the event the
|
|
18
|
+
* overlay itself acts on, so hooking it here dismisses on the same press that
|
|
19
|
+
* starts a canvas gesture — no second click required.
|
|
6
20
|
*/
|
|
7
21
|
export function useContextMenuDismiss(onClose: () => void): RefObject<HTMLDivElement | null> {
|
|
8
22
|
const menuRef = useRef<HTMLDivElement>(null);
|
|
9
23
|
|
|
10
24
|
const dismiss = useCallback(
|
|
11
|
-
(e: MouseEvent | KeyboardEvent) => {
|
|
12
|
-
if (e instanceof KeyboardEvent
|
|
13
|
-
|
|
25
|
+
(e: PointerEvent | MouseEvent | KeyboardEvent) => {
|
|
26
|
+
if (e instanceof KeyboardEvent) {
|
|
27
|
+
if (e.key === "Escape") onClose();
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
// Any press inside the menu is a menu interaction — leave it open (the
|
|
31
|
+
// item's own handler will close it after acting).
|
|
32
|
+
if (menuRef.current?.contains(e.target as Node)) return;
|
|
14
33
|
onClose();
|
|
15
34
|
},
|
|
16
35
|
[onClose],
|
|
17
36
|
);
|
|
18
37
|
|
|
19
38
|
useEffect(() => {
|
|
20
|
-
|
|
39
|
+
// Capture phase so overlay/iframe-side handlers that stopPropagation on the
|
|
40
|
+
// bubble phase can't swallow the dismiss. `pointerdown` covers mouse + touch
|
|
41
|
+
// + pen; keep `mousedown` too for any synthetic-mouse path that skips it.
|
|
42
|
+
document.addEventListener("pointerdown", dismiss, true);
|
|
43
|
+
document.addEventListener("mousedown", dismiss, true);
|
|
21
44
|
document.addEventListener("keydown", dismiss);
|
|
22
45
|
return () => {
|
|
23
|
-
document.removeEventListener("
|
|
46
|
+
document.removeEventListener("pointerdown", dismiss, true);
|
|
47
|
+
document.removeEventListener("mousedown", dismiss, true);
|
|
24
48
|
document.removeEventListener("keydown", dismiss);
|
|
25
49
|
};
|
|
26
50
|
}, [dismiss]);
|
|
@@ -6,6 +6,7 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
6
6
|
import type { MutableRefObject } from "react";
|
|
7
7
|
import type { DomEditSelection, DomEditTextField } from "../components/editor/domEditing";
|
|
8
8
|
import type { ImportedFontAsset } from "../components/editor/fontAssets";
|
|
9
|
+
import { usePlayerStore } from "../player";
|
|
9
10
|
import { StudioSaveHttpError } from "../utils/studioSaveDiagnostics";
|
|
10
11
|
import { trackStudioEvent } from "../utils/studioTelemetry";
|
|
11
12
|
import { useDomEditCommits } from "./useDomEditCommits";
|
|
@@ -27,6 +28,7 @@ interface RenderedDomEditCommits {
|
|
|
27
28
|
hook: ReturnType<typeof useDomEditCommits>;
|
|
28
29
|
showToast: ReturnType<typeof makeShowToast>;
|
|
29
30
|
recordEdit: ReturnType<typeof vi.fn<() => Promise<void>>>;
|
|
31
|
+
reloadPreview: ReturnType<typeof vi.fn>;
|
|
30
32
|
cleanup: () => void;
|
|
31
33
|
}
|
|
32
34
|
|
|
@@ -209,6 +211,7 @@ function renderDomEditCommits(
|
|
|
209
211
|
const previewIframeRef: MutableRefObject<HTMLIFrameElement | null> = { current: iframe };
|
|
210
212
|
const projectIdRef: MutableRefObject<string | null> = { current: "p1" };
|
|
211
213
|
const domEditSaveTimestampRef: MutableRefObject<number> = { current: 0 };
|
|
214
|
+
const reloadPreview = vi.fn();
|
|
212
215
|
|
|
213
216
|
function Probe() {
|
|
214
217
|
captured.current = useDomEditCommits({
|
|
@@ -223,7 +226,7 @@ function renderDomEditCommits(
|
|
|
223
226
|
importedFontAssetsRef: { current: options.importedFontAssets ?? [] },
|
|
224
227
|
projectId: "p1",
|
|
225
228
|
projectIdRef,
|
|
226
|
-
reloadPreview
|
|
229
|
+
reloadPreview,
|
|
227
230
|
domEditSelection: selection,
|
|
228
231
|
applyDomSelection: vi.fn(),
|
|
229
232
|
clearDomSelection: vi.fn(),
|
|
@@ -244,6 +247,7 @@ function renderDomEditCommits(
|
|
|
244
247
|
hook: captured.current,
|
|
245
248
|
showToast,
|
|
246
249
|
recordEdit,
|
|
250
|
+
reloadPreview,
|
|
247
251
|
cleanup: () => {
|
|
248
252
|
act(() => {
|
|
249
253
|
root.unmount();
|
|
@@ -253,6 +257,155 @@ function renderDomEditCommits(
|
|
|
253
257
|
} satisfies RenderedDomEditCommits;
|
|
254
258
|
}
|
|
255
259
|
|
|
260
|
+
describe("useDomEditCommits z-index reorder persistence", () => {
|
|
261
|
+
beforeEach(() => {
|
|
262
|
+
ensureCssEscape();
|
|
263
|
+
vi.clearAllMocks();
|
|
264
|
+
vi.unstubAllGlobals();
|
|
265
|
+
document.body.replaceChildren();
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it("persists an N-element reorder with one batch POST, one undo entry, and one reload", async () => {
|
|
269
|
+
const original =
|
|
270
|
+
'<div id="a" style="z-index: 1"></div><div id="b" style="z-index: 2"></div><div id="c" style="z-index: 3"></div>';
|
|
271
|
+
const after =
|
|
272
|
+
'<div id="a" style="z-index: 3"></div><div id="b" style="z-index: 2"></div><div id="c" style="z-index: 1"></div>';
|
|
273
|
+
const fetchMock = vi.fn(
|
|
274
|
+
async (
|
|
275
|
+
input: Parameters<typeof fetch>[0],
|
|
276
|
+
_init?: Parameters<typeof fetch>[1],
|
|
277
|
+
): Promise<Response> => {
|
|
278
|
+
const url = requestUrl(input);
|
|
279
|
+
if (url.includes("/api/projects/p1/files/")) return jsonResponse({ content: original });
|
|
280
|
+
if (url.includes("/file-mutations/patch-elements-batch/")) {
|
|
281
|
+
return jsonResponse({
|
|
282
|
+
ok: true,
|
|
283
|
+
changed: true,
|
|
284
|
+
matched: [true, true, true],
|
|
285
|
+
content: after,
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
if (url.includes("/file-mutations/patch-element/")) {
|
|
289
|
+
return jsonResponse({ ok: true, changed: true, matched: true, content: after });
|
|
290
|
+
}
|
|
291
|
+
throw new Error(`Unexpected fetch: ${url}`);
|
|
292
|
+
},
|
|
293
|
+
);
|
|
294
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
295
|
+
const { iframe, element } = createPreviewElement(
|
|
296
|
+
'<div data-hf-id="hf-card"></div><div id="b"></div><div id="c"></div>',
|
|
297
|
+
);
|
|
298
|
+
element.id = "a";
|
|
299
|
+
const elements = [
|
|
300
|
+
element,
|
|
301
|
+
iframe.contentDocument!.getElementById("b")!,
|
|
302
|
+
iframe.contentDocument!.getElementById("c")!,
|
|
303
|
+
];
|
|
304
|
+
const rendered = renderDomEditCommits(createSelection(element), iframe);
|
|
305
|
+
|
|
306
|
+
try {
|
|
307
|
+
await act(async () => {
|
|
308
|
+
await rendered.hook.handleDomZIndexReorderCommit(
|
|
309
|
+
elements.map((item, index) => ({
|
|
310
|
+
element: item,
|
|
311
|
+
zIndex: 3 - index,
|
|
312
|
+
id: item.id,
|
|
313
|
+
sourceFile: "index.html",
|
|
314
|
+
})),
|
|
315
|
+
"z-reorder:test",
|
|
316
|
+
);
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
const batchPosts = fetchMock.mock.calls.filter(([input]) =>
|
|
320
|
+
requestUrl(input).includes("/file-mutations/patch-elements-batch/"),
|
|
321
|
+
);
|
|
322
|
+
const singlePosts = fetchMock.mock.calls.filter(([input]) =>
|
|
323
|
+
requestUrl(input).includes("/file-mutations/patch-element/"),
|
|
324
|
+
);
|
|
325
|
+
expect(batchPosts).toHaveLength(1);
|
|
326
|
+
expect(singlePosts).toHaveLength(0);
|
|
327
|
+
expect(JSON.parse(String(batchPosts[0]?.[1]?.body))).toEqual({
|
|
328
|
+
patches: expect.arrayContaining([
|
|
329
|
+
expect.objectContaining({ target: expect.objectContaining({ id: "a" }) }),
|
|
330
|
+
expect.objectContaining({ target: expect.objectContaining({ id: "b" }) }),
|
|
331
|
+
expect.objectContaining({ target: expect.objectContaining({ id: "c" }) }),
|
|
332
|
+
]),
|
|
333
|
+
});
|
|
334
|
+
expect(rendered.recordEdit).toHaveBeenCalledTimes(1);
|
|
335
|
+
expect(rendered.recordEdit).toHaveBeenCalledWith({
|
|
336
|
+
label: "Reorder layers",
|
|
337
|
+
kind: "manual",
|
|
338
|
+
coalesceKey: "z-reorder:test",
|
|
339
|
+
files: { "index.html": { before: original, after } },
|
|
340
|
+
});
|
|
341
|
+
expect(rendered.reloadPreview).toHaveBeenCalledTimes(1);
|
|
342
|
+
} finally {
|
|
343
|
+
rendered.cleanup();
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
it("rolls back live state after a failed batch POST without a disk write-back", async () => {
|
|
348
|
+
const original = '<div id="a" style="z-index: 7"></div><div id="b"></div>';
|
|
349
|
+
const fetchMock = vi.fn(async (input: Parameters<typeof fetch>[0]): Promise<Response> => {
|
|
350
|
+
const url = requestUrl(input);
|
|
351
|
+
if (url.includes("/api/projects/p1/files/")) return jsonResponse({ content: original });
|
|
352
|
+
if (url.includes("/file-mutations/patch-elements-batch/")) {
|
|
353
|
+
return jsonResponse({ error: "batch rejected" }, 500);
|
|
354
|
+
}
|
|
355
|
+
throw new Error(`Unexpected fetch: ${url}`);
|
|
356
|
+
});
|
|
357
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
358
|
+
const writeProjectFile = vi.fn(async () => {});
|
|
359
|
+
const { iframe, element } = createPreviewElement(
|
|
360
|
+
'<div data-hf-id="hf-card" style="z-index: 7"></div><div id="b"></div>',
|
|
361
|
+
);
|
|
362
|
+
element.id = "a";
|
|
363
|
+
const second = iframe.contentDocument!.getElementById("b")!;
|
|
364
|
+
usePlayerStore.getState().setElements([
|
|
365
|
+
{ id: "a", tag: "div", start: 0, duration: 1, track: 0, zIndex: 7, hasExplicitZIndex: true },
|
|
366
|
+
{ id: "b", tag: "div", start: 0, duration: 1, track: 1, zIndex: 0, hasExplicitZIndex: false },
|
|
367
|
+
]);
|
|
368
|
+
const rendered = renderDomEditCommits(createSelection(element), iframe, { writeProjectFile });
|
|
369
|
+
|
|
370
|
+
try {
|
|
371
|
+
let rejection: unknown;
|
|
372
|
+
await act(async () => {
|
|
373
|
+
try {
|
|
374
|
+
await rendered.hook.handleDomZIndexReorderCommit([
|
|
375
|
+
{ element, zIndex: 2, id: "a", sourceFile: "index.html", key: "a" },
|
|
376
|
+
{ element: second, zIndex: 1, id: "b", sourceFile: "index.html", key: "b" },
|
|
377
|
+
]);
|
|
378
|
+
} catch (error) {
|
|
379
|
+
rejection = error;
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
expect(rejection).toBeInstanceOf(Error);
|
|
384
|
+
expect((rejection as Error).message).toContain("batch rejected");
|
|
385
|
+
expect(element.style.zIndex).toBe("7");
|
|
386
|
+
expect(second.style.zIndex).toBe("");
|
|
387
|
+
expect(
|
|
388
|
+
usePlayerStore
|
|
389
|
+
.getState()
|
|
390
|
+
.elements.map(({ zIndex, hasExplicitZIndex }) => ({ zIndex, hasExplicitZIndex })),
|
|
391
|
+
).toEqual([
|
|
392
|
+
{ zIndex: 7, hasExplicitZIndex: true },
|
|
393
|
+
{ zIndex: 0, hasExplicitZIndex: false },
|
|
394
|
+
]);
|
|
395
|
+
expect(writeProjectFile).not.toHaveBeenCalled();
|
|
396
|
+
expect(rendered.recordEdit).not.toHaveBeenCalled();
|
|
397
|
+
expect(rendered.reloadPreview).not.toHaveBeenCalled();
|
|
398
|
+
expect(
|
|
399
|
+
fetchMock.mock.calls.filter(([input]) =>
|
|
400
|
+
requestUrl(input).includes("/file-mutations/patch-elements-batch/"),
|
|
401
|
+
),
|
|
402
|
+
).toHaveLength(1);
|
|
403
|
+
} finally {
|
|
404
|
+
rendered.cleanup();
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
});
|
|
408
|
+
|
|
256
409
|
async function commitStyleAgainst(response: Parameters<typeof stubPatchFetch>[0]) {
|
|
257
410
|
stubPatchFetch(response);
|
|
258
411
|
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
@@ -4,11 +4,19 @@ import { FONT_EXT } from "../utils/mediaTypes";
|
|
|
4
4
|
|
|
5
5
|
import { trackStudioEvent } from "../utils/studioTelemetry";
|
|
6
6
|
import { primaryFontFamilyValue } from "../utils/studioFontHelpers";
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
createStudioSaveHttpError,
|
|
9
|
+
StudioSaveHttpError,
|
|
10
|
+
trackStudioSaveFailure,
|
|
11
|
+
} from "../utils/studioSaveDiagnostics";
|
|
8
12
|
import { buildDomEditPatchTarget, type DomEditSelection } from "../components/editor/domEditing";
|
|
9
13
|
import { fontFamilyFromAssetPath, type ImportedFontAsset } from "../components/editor/fontAssets";
|
|
10
14
|
import type { EditHistoryKind } from "../utils/editHistory";
|
|
11
|
-
import type {
|
|
15
|
+
import type {
|
|
16
|
+
CommitDomEditPatchBatches,
|
|
17
|
+
DomEditPatchBatch,
|
|
18
|
+
PersistDomEditOperations,
|
|
19
|
+
} from "./domEditCommitTypes";
|
|
12
20
|
import type { PatchOperation } from "../utils/sourcePatcher";
|
|
13
21
|
import {
|
|
14
22
|
DomEditPersistUnsafeValueError,
|
|
@@ -20,6 +28,7 @@ import { useDomEditTextCommits } from "./useDomEditTextCommits";
|
|
|
20
28
|
import { useDomGeometryCommits } from "./useDomGeometryCommits";
|
|
21
29
|
import { useElementLifecycleOps } from "./useElementLifecycleOps";
|
|
22
30
|
import { formatFieldsSuffix } from "./gsapScriptCommitHelpers";
|
|
31
|
+
import { readProjectFileContent } from "../utils/studioFileHistory";
|
|
23
32
|
|
|
24
33
|
// ── Helpers ──
|
|
25
34
|
|
|
@@ -48,9 +57,36 @@ interface RecordEditInput {
|
|
|
48
57
|
label: string;
|
|
49
58
|
kind: EditHistoryKind;
|
|
50
59
|
coalesceKey?: string;
|
|
60
|
+
coalesceMs?: number;
|
|
51
61
|
files: Record<string, { before: string; after: string }>;
|
|
52
62
|
}
|
|
53
63
|
|
|
64
|
+
async function patchElementBatch(projectId: string, batch: DomEditPatchBatch) {
|
|
65
|
+
const before = await readProjectFileContent(projectId, batch.sourceFile);
|
|
66
|
+
const response = await fetch(
|
|
67
|
+
`/api/projects/${encodeURIComponent(projectId)}/file-mutations/patch-elements-batch/${encodeURIComponent(batch.sourceFile)}`,
|
|
68
|
+
{
|
|
69
|
+
method: "POST",
|
|
70
|
+
headers: { "Content-Type": "application/json" },
|
|
71
|
+
body: JSON.stringify({ patches: batch.patches }),
|
|
72
|
+
},
|
|
73
|
+
);
|
|
74
|
+
if (!response.ok) {
|
|
75
|
+
const rejection = await readErrorResponseBody(response);
|
|
76
|
+
throw new StudioSaveHttpError(formatPatchRejectionMessage(rejection), response.status);
|
|
77
|
+
}
|
|
78
|
+
const result = (await response.json()) as {
|
|
79
|
+
changed?: boolean;
|
|
80
|
+
content?: string;
|
|
81
|
+
};
|
|
82
|
+
return {
|
|
83
|
+
sourceFile: batch.sourceFile,
|
|
84
|
+
changed: result.changed === true,
|
|
85
|
+
before,
|
|
86
|
+
after: typeof result.content === "string" ? result.content : before,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
54
90
|
export interface UseDomEditCommitsParams {
|
|
55
91
|
activeCompPath: string | null;
|
|
56
92
|
previewIframeRef: React.MutableRefObject<HTMLIFrameElement | null>;
|
|
@@ -271,6 +307,7 @@ export function useDomEditCommits({
|
|
|
271
307
|
label: options?.label ?? "Edit layer",
|
|
272
308
|
kind: "manual",
|
|
273
309
|
coalesceKey: options?.coalesceKey,
|
|
310
|
+
coalesceMs: options?.coalesceMs,
|
|
274
311
|
files: { [targetPath]: { before: originalContent, after: finalContent } },
|
|
275
312
|
});
|
|
276
313
|
forceReloadSdkSession?.();
|
|
@@ -292,6 +329,66 @@ export function useDomEditCommits({
|
|
|
292
329
|
],
|
|
293
330
|
);
|
|
294
331
|
|
|
332
|
+
const commitDomEditPatchBatches: CommitDomEditPatchBatches = useCallback(
|
|
333
|
+
(batches, options) =>
|
|
334
|
+
queueDomEditSave(async () => {
|
|
335
|
+
const pid = projectIdRef.current;
|
|
336
|
+
if (!pid) throw new Error("No active project");
|
|
337
|
+
const unsafeFields = batches.flatMap((batch) =>
|
|
338
|
+
batch.patches.flatMap((patch) => findUnsafeDomPatchValues(patch)),
|
|
339
|
+
);
|
|
340
|
+
if (unsafeFields.length > 0) {
|
|
341
|
+
showToast("Couldn't save edit because it contains invalid layout values", "error");
|
|
342
|
+
throw new DomEditPersistUnsafeValueError(
|
|
343
|
+
`DOM patch contains unsafe values: ${formatUnsafeFieldList(unsafeFields)}`,
|
|
344
|
+
{ alreadyToasted: true },
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
domEditSaveTimestampRef.current = Date.now();
|
|
349
|
+
const results = await Promise.all(batches.map((batch) => patchElementBatch(pid, batch)));
|
|
350
|
+
const files = Object.fromEntries(
|
|
351
|
+
results
|
|
352
|
+
.filter((result) => result.changed)
|
|
353
|
+
.map((result) => [result.sourceFile, { before: result.before, after: result.after }]),
|
|
354
|
+
);
|
|
355
|
+
if (Object.keys(files).length === 0) return;
|
|
356
|
+
await editHistory.recordEdit({
|
|
357
|
+
label: options.label,
|
|
358
|
+
kind: "manual",
|
|
359
|
+
coalesceKey: options.coalesceKey,
|
|
360
|
+
files,
|
|
361
|
+
});
|
|
362
|
+
forceReloadSdkSession?.();
|
|
363
|
+
reloadPreview();
|
|
364
|
+
}).catch((error) => {
|
|
365
|
+
const alreadyToasted =
|
|
366
|
+
(error instanceof StudioSaveHttpError ||
|
|
367
|
+
error instanceof DomEditPersistUnsafeValueError) &&
|
|
368
|
+
error.alreadyToasted;
|
|
369
|
+
if (!alreadyToasted) {
|
|
370
|
+
showToast(error instanceof Error ? error.message : "Failed to reorder layers", "error");
|
|
371
|
+
}
|
|
372
|
+
trackStudioSaveFailure({
|
|
373
|
+
source: "dom_edit",
|
|
374
|
+
error,
|
|
375
|
+
filePath: batches.map((batch) => batch.sourceFile).join(","),
|
|
376
|
+
mutationType: "z-reorder",
|
|
377
|
+
label: options.label,
|
|
378
|
+
});
|
|
379
|
+
throw error;
|
|
380
|
+
}),
|
|
381
|
+
[
|
|
382
|
+
domEditSaveTimestampRef,
|
|
383
|
+
editHistory,
|
|
384
|
+
forceReloadSdkSession,
|
|
385
|
+
projectIdRef,
|
|
386
|
+
queueDomEditSave,
|
|
387
|
+
reloadPreview,
|
|
388
|
+
showToast,
|
|
389
|
+
],
|
|
390
|
+
);
|
|
391
|
+
|
|
295
392
|
// ── Text & style commits (delegated to useDomEditTextCommits) ──
|
|
296
393
|
|
|
297
394
|
const {
|
|
@@ -352,7 +449,7 @@ export function useDomEditCommits({
|
|
|
352
449
|
onTrySdkDelete,
|
|
353
450
|
onReorderShadow,
|
|
354
451
|
forceReloadSdkSession,
|
|
355
|
-
|
|
452
|
+
commitDomEditPatchBatches,
|
|
356
453
|
});
|
|
357
454
|
|
|
358
455
|
return {
|
|
@@ -15,6 +15,7 @@ interface UseDomEditPositionPatchCommitParams {
|
|
|
15
15
|
interface PositionPatchOptions {
|
|
16
16
|
label: string;
|
|
17
17
|
coalesceKey: string;
|
|
18
|
+
coalesceMs?: number;
|
|
18
19
|
skipRefresh?: boolean;
|
|
19
20
|
}
|
|
20
21
|
|
|
@@ -30,6 +31,7 @@ export function useDomEditPositionPatchCommit({
|
|
|
30
31
|
await persistDomEditOperations(selection, patches, {
|
|
31
32
|
label: options.label,
|
|
32
33
|
coalesceKey: options.coalesceKey,
|
|
34
|
+
coalesceMs: options.coalesceMs,
|
|
33
35
|
skipRefresh: options.skipRefresh ?? true,
|
|
34
36
|
});
|
|
35
37
|
}).catch((error) => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useCallback } from "react";
|
|
2
2
|
import { trackStudioEvent } from "../utils/studioTelemetry";
|
|
3
|
-
import type { TimelineElement } from "../player";
|
|
3
|
+
import type { SelectElementOptions, TimelineElement } from "../player";
|
|
4
4
|
import type { ImportedFontAsset } from "../components/editor/fontAssets";
|
|
5
5
|
import type { EditHistoryKind } from "../utils/editHistory";
|
|
6
6
|
import type { RightPanelTab } from "../utils/studioHelpers";
|
|
@@ -38,7 +38,7 @@ export interface UseDomEditSessionParams {
|
|
|
38
38
|
compositionLoading: boolean;
|
|
39
39
|
previewIframeRef: React.MutableRefObject<HTMLIFrameElement | null>;
|
|
40
40
|
timelineElements: TimelineElement[];
|
|
41
|
-
setSelectedTimelineElementId: (id: string | null) => void;
|
|
41
|
+
setSelectedTimelineElementId: (id: string | null, options?: SelectElementOptions) => void;
|
|
42
42
|
setRightCollapsed: (collapsed: boolean) => void;
|
|
43
43
|
setRightPanelTab: (tab: RightPanelTab) => void;
|
|
44
44
|
showToast: (message: string, tone?: "error" | "info") => void;
|