@hyperframes/studio 0.8.114 → 0.8.116
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-aXNJKiIg.js → hyperframes-player-mGYvPVpw.js} +1 -1
- package/dist/assets/{index-Bn2RJNTJ.js → index-BWHt0-ir.js} +1 -1
- package/dist/assets/{index-Dz7doz9l.js → index-DYNaXPVf.js} +203 -203
- package/dist/assets/{index-C9N-DGjC.js → index-Dr3hNpvS.js} +1 -1
- package/dist/assets/{index-BYnsJnJV.css → index-t1LH6H5H.css} +1 -1
- package/dist/{chunk-BIUXP3PG.js → chunk-3F47EFCT.js} +142 -277
- package/dist/chunk-3F47EFCT.js.map +1 -0
- package/dist/{chunk-K47E2ZJA.js → chunk-5EKWJZLM.js} +504 -17
- package/dist/chunk-5EKWJZLM.js.map +1 -0
- package/dist/{chunk-VWT7STY3.js → chunk-J6Q572FA.js} +2 -2
- package/dist/{domEditingLayers-V67MS4WP.js → domEditingLayers-OL7GLSS4.js} +3 -5
- package/dist/index.d.ts +146 -40
- package/dist/index.html +2 -2
- package/dist/index.js +4021 -3591
- package/dist/index.js.map +1 -1
- package/dist/player.js +2 -2
- package/dist/styles.css +1 -1
- package/package.json +9 -8
- package/src/App.tsx +2 -2
- package/src/captions/hooks/useCaptionSync.ts +10 -6
- package/src/components/LinkedSelectionToggle.tsx +3 -2
- package/src/components/ProjectUnreachableBanner.tsx +3 -3
- package/src/components/dock/Dock.tsx +5 -7
- package/src/components/editor/CanvasContextMenu.tsx +3 -2
- package/src/components/editor/DomEditCropHandles.test.tsx +27 -0
- package/src/components/editor/DomEditCropHandles.tsx +7 -4
- package/src/components/editor/DomEditOverlay.tsx +5 -1
- package/src/components/editor/FileTreeNodes.tsx +13 -10
- package/src/components/editor/MotionPathOverlay.tsx +7 -2
- package/src/components/editor/PropertyPanelEmptyState.tsx +12 -2
- package/src/components/editor/SnapToolbar.test.tsx +100 -1
- package/src/components/editor/SnapToolbar.tsx +43 -5
- package/src/components/editor/anchoredResizeCommitFeedsOffset.test.ts +48 -1
- package/src/components/editor/audioFxTelemetry.ts +2 -3
- package/src/components/editor/commitPreflightHandles.test.tsx +2 -2
- package/src/components/editor/cropResize.ts +4 -3
- package/src/components/editor/domEditingDom.ts +4 -0
- package/src/components/editor/domEditingLayers.ts +1 -5
- package/src/components/editor/domGroupMoveBy.test.tsx +252 -0
- package/src/components/editor/domGroupMoveBy.ts +95 -0
- package/src/components/editor/dragUndoPaint.test.ts +5 -5
- package/src/components/editor/gestureHoldsPreviewReload.test.tsx +33 -0
- package/src/components/editor/manualEditingAvailability.ts +8 -0
- package/src/components/editor/manualEdits.ts +1 -0
- package/src/components/editor/manualEditsParsing.ts +7 -0
- package/src/components/editor/manualOffsetDrag.ts +5 -6
- package/src/components/editor/marqueeCommit.test.tsx +106 -37
- package/src/components/editor/marqueeCommit.ts +24 -8
- package/src/components/editor/motionPathCommit.test.ts +29 -1
- package/src/components/editor/motionPathCommit.ts +21 -9
- package/src/components/editor/motionPathSelection.test.ts +6 -31
- package/src/components/editor/probeSourceElement.test.ts +45 -0
- package/src/components/editor/probeSourceElement.ts +41 -16
- package/src/components/editor/propertyPanelFlatTextSection.tsx +10 -7
- package/src/components/editor/propertyPanelFont.tsx +3 -2
- package/src/components/editor/propertyPanelFxAddMenu.tsx +11 -2
- package/src/components/editor/propertyPanelFxCarveModule.tsx +15 -5
- package/src/components/editor/propertyPanelFxNodeOpenBody.tsx +3 -0
- package/src/components/editor/propertyPanelFxNodeParams.tsx +15 -2
- package/src/components/editor/propertyPanelFxNodeRow.tsx +3 -0
- package/src/components/editor/propertyPanelFxPresetRun.tsx +10 -2
- package/src/components/editor/propertyPanelFxSection.test.tsx +56 -0
- package/src/components/editor/propertyPanelFxSection.tsx +30 -28
- package/src/components/editor/propertyPanelTypes.ts +6 -3
- package/src/components/editor/useColorGradingController.ts +2 -1
- package/src/components/editor/useDomEditNudge.test.tsx +37 -0
- package/src/components/editor/useDomEditNudge.ts +2 -0
- package/src/components/editor/useDomEditOverlayGestures.ts +5 -0
- package/src/components/editor/useDomEditZOrder.ts +12 -2
- package/src/components/editor/useFxCarveNodes.ts +2 -1
- package/src/components/editor/useFxLevelling.ts +2 -1
- package/src/components/feedback/projectProvenance.ts +2 -1
- package/src/components/nle/NLEContext.test.ts +1 -0
- package/src/components/nle/NLEContext.tsx +2 -1
- package/src/components/nle/NLEPreview.tsx +12 -10
- package/src/components/nle/PreviewPoster.tsx +2 -1
- package/src/components/nle/useTimelineEditCallbacks.test.tsx +1 -0
- package/src/components/nle/useTimelineEditCallbacks.ts +7 -1
- package/src/components/renders/useFfmpegStatus.ts +2 -1
- package/src/components/renders/useRenderQueue.ts +5 -4
- package/src/components/sidebar/AssetContextMenu.tsx +4 -4
- package/src/components/sidebar/AssetsTab.tsx +2 -1
- package/src/components/sidebar/GlobalAssetsView.tsx +2 -1
- package/src/components/ui/menuStyle.test.ts +61 -0
- package/src/components/ui/menuStyle.ts +10 -0
- package/src/contexts/DomEditContext.tsx +5 -1
- package/src/contexts/TimelineEditContext.tsx +1 -0
- package/src/domEditExports.test.tsx +9 -1
- package/src/hooks/domEditCommitRunner.ts +1 -1
- package/src/hooks/domSelectionTestHarness.ts +23 -0
- package/src/hooks/domStyleCommit.propertyNames.test.ts +73 -0
- package/src/hooks/domStyleCommit.ts +8 -1
- package/src/hooks/draggedGsapPosition.ts +10 -0
- package/src/hooks/elementOffsetStager.ts +5 -4
- package/src/hooks/gsapDragCommit.test.ts +75 -164
- package/src/hooks/gsapDragCommit.ts +4 -125
- package/src/hooks/gsapDragPositionCommit.ts +36 -384
- package/src/hooks/gsapEditOutcome.ts +22 -2
- package/src/hooks/gsapGestureOutcome.test.ts +161 -0
- package/src/hooks/gsapGestureOutcome.ts +64 -0
- package/src/hooks/gsapGroupPreflight.ts +34 -0
- package/src/hooks/gsapKeyframeCommit.ts +3 -12
- package/src/hooks/gsapMutationClient.ts +4 -3
- package/src/hooks/gsapParsedTween.test-helpers.ts +75 -0
- package/src/hooks/gsapParsedTween.test.ts +62 -0
- package/src/hooks/gsapParsedTween.ts +243 -0
- package/src/hooks/gsapPositionDetection.test.ts +78 -0
- package/src/hooks/gsapPositionDetection.ts +52 -33
- package/src/hooks/gsapResizeDropPoint.test.ts +4 -3
- package/src/hooks/gsapResizeElementSize.test.tsx +1 -0
- package/src/hooks/gsapResizeGeometrySweep.test.ts +13 -25
- package/src/hooks/gsapResizeIntercept.test.ts +96 -11
- package/src/hooks/gsapResizeIntercept.ts +79 -168
- package/src/hooks/gsapResizeSweep.test.ts +16 -33
- package/src/hooks/gsapRuntimeBridge.sharedTween.test.ts +6 -8
- package/src/hooks/gsapRuntimeBridge.test.ts +41 -6
- package/src/hooks/gsapRuntimeBridge.ts +105 -131
- package/src/hooks/gsapScriptCommitHelpers.ts +2 -1
- package/src/hooks/gsapScriptCommitTypes.ts +13 -0
- package/src/hooks/gsapShared.ts +7 -0
- package/src/hooks/gsapShared.writeTarget.test.ts +45 -57
- package/src/hooks/gsapValueAtPlayhead.browser.test.ts +83 -0
- package/src/hooks/gsapValueAtPlayhead.realGsap.test.ts +179 -0
- package/src/hooks/gsapValueAtPlayhead.test.ts +552 -0
- package/src/hooks/gsapValueAtPlayhead.ts +292 -0
- package/src/hooks/gsapWholePropertyOffsetCommit.test.ts +43 -0
- package/src/hooks/gsapWholePropertyOffsetCommit.ts +2 -3
- package/src/hooks/keyframeCacheAstLoad.ts +8 -6
- package/src/hooks/newTweenTarget.test.ts +41 -76
- package/src/hooks/plainRotation.ts +3 -2
- package/src/hooks/previewPromotionSelection.test.tsx +1 -1
- package/src/hooks/refreshTimelineRowText.test.ts +65 -0
- package/src/hooks/refreshTimelineRowText.ts +29 -0
- package/src/hooks/timelineTimingSync.test.ts +1 -0
- package/src/hooks/timelineTimingSync.ts +2 -1
- package/src/hooks/useAnimatedPropertyCommit.test.tsx +46 -5
- package/src/hooks/useAnimatedPropertyCommit.ts +2 -2
- package/src/hooks/useAskAgentModal.ts +2 -1
- package/src/hooks/useBlockCatalog.ts +2 -1
- package/src/hooks/useBlockedTimelineEditToast.ts +12 -3
- package/src/hooks/useCaptionDetection.ts +2 -1
- package/src/hooks/useCompositionContentLoader.ts +2 -1
- package/src/hooks/useDomEditCommitsHelpers.ts +3 -2
- package/src/hooks/useDomEditPositionPatchCommit.ts +18 -23
- package/src/hooks/useDomEditSession.test.tsx +30 -1
- package/src/hooks/useDomEditSession.ts +10 -0
- package/src/hooks/useDomEditTextCommits.test.tsx +41 -0
- package/src/hooks/useDomEditTextCommits.ts +13 -3
- package/src/hooks/useDomEditWiring.ts +4 -2
- package/src/hooks/useDomGeometryCommit.test.tsx +25 -6
- package/src/hooks/useDomGeometryCommit.ts +8 -4
- package/src/hooks/useDomGeometryCommits.test.tsx +1 -0
- package/src/hooks/useDomGeometryCommits.ts +3 -2
- package/src/hooks/useDomSelection.test.ts +26 -0
- package/src/hooks/useDomSelection.ts +10 -3
- package/src/hooks/useDomSelectionTypes.ts +6 -1
- package/src/hooks/useEditHistoryActions.paint.test.tsx +82 -20
- package/src/hooks/useEditHistoryActions.test.tsx +15 -0
- package/src/hooks/useEditHistoryActions.ts +16 -5
- package/src/hooks/useElementLifecycleOps.ts +2 -1
- package/src/hooks/useEnableKeyframes.test.ts +34 -3
- package/src/hooks/useEnableKeyframes.ts +7 -4
- package/src/hooks/useExternalFileChangeCoordinator.test.tsx +121 -0
- package/src/hooks/useExternalFileChangeCoordinator.ts +35 -8
- package/src/hooks/useFileManager.projectOwnership.test.tsx +6 -1
- package/src/hooks/useFileManager.ts +14 -10
- package/src/hooks/useFileTree.ts +3 -2
- package/src/hooks/useFrameCapture.ts +5 -1
- package/src/hooks/useFreezeFrame.ts +3 -2
- package/src/hooks/useGestureCommit.test.tsx +225 -55
- package/src/hooks/useGestureCommit.ts +86 -55
- package/src/hooks/useGroupCommits.ts +2 -1
- package/src/hooks/useGsapAwareEditing.groupPlan.test.tsx +160 -0
- package/src/hooks/useGsapAwareEditing.test.tsx +28 -35
- package/src/hooks/useGsapAwareEditing.testHelpers.tsx +42 -0
- package/src/hooks/useGsapAwareEditing.ts +101 -195
- package/src/hooks/useGsapAwareGroupMove.ts +179 -0
- package/src/hooks/useGsapInteractionFailureTelemetry.ts +10 -4
- package/src/hooks/useGsapKeyframeOps.sdkUsage.test.tsx +114 -0
- package/src/hooks/useGsapKeyframeOps.ts +36 -10
- package/src/hooks/useGsapScriptCommits.test.tsx +60 -1
- package/src/hooks/useGsapScriptCommits.ts +15 -10
- package/src/hooks/useGsapSelectionHandlers.test.tsx +1 -1
- package/src/hooks/useGsapSelectionHandlers.ts +24 -18
- package/src/hooks/useGsapTweenCache.ts +18 -12
- package/src/hooks/useLintModal.ts +2 -1
- package/src/hooks/usePersistentEditHistory.test.ts +277 -1
- package/src/hooks/usePersistentEditHistory.ts +47 -8
- package/src/hooks/usePopulateKeyframeCacheForFile.test.tsx +76 -2
- package/src/hooks/usePreviewInteraction.test.ts +21 -0
- package/src/hooks/usePreviewInteraction.ts +2 -0
- package/src/hooks/usePreviewPersistence.history.test.tsx +14 -3
- package/src/hooks/usePreviewPersistence.ts +13 -11
- package/src/hooks/useProjectFileWriter.ts +20 -19
- package/src/hooks/useRemoveBackground.ts +2 -1
- package/src/hooks/useRenderClipContent.test.ts +18 -0
- package/src/hooks/useRenderClipContent.ts +4 -5
- package/src/hooks/useSdkSession.ts +2 -1
- package/src/hooks/useServerConnection.ts +4 -3
- package/src/hooks/useStudioSelectionPublisher.ts +2 -1
- package/src/hooks/useThumbnailStill.test.ts +100 -0
- package/src/hooks/useThumbnailStill.ts +37 -0
- package/src/hooks/useTimelineDeleteOps.test.tsx +30 -22
- package/src/hooks/useTimelineDeleteOps.ts +22 -27
- package/src/hooks/useTimelineEditing.test.tsx +1 -1
- package/src/index.ts +4 -0
- package/src/player/components/AudioWaveform.tsx +3 -2
- package/src/player/components/AutomationSelectionMenu.tsx +3 -2
- package/src/player/components/ClipContextMenu.test.tsx +73 -0
- package/src/player/components/ClipContextMenu.tsx +33 -31
- package/src/player/components/CompositionThumbnail.tsx +2 -1
- package/src/player/components/KeyframeDiamondContextMenu.tsx +8 -9
- package/src/player/components/OutOfSyncBadge.tsx +5 -5
- package/src/player/components/TextClipContent.test.ts +19 -0
- package/src/player/components/TextClipContent.tsx +27 -0
- package/src/player/components/TrackGapContextMenu.tsx +3 -6
- package/src/player/components/clipMenuNormalize.ts +2 -1
- package/src/player/components/clipMenuToolItems.test.tsx +23 -0
- package/src/player/components/clipMenuToolItems.tsx +13 -9
- package/src/player/components/clipPeakMap.ts +2 -1
- package/src/player/components/timelineCallbacks.ts +1 -0
- package/src/player/components/timelineClipDragGestureLifecycle.ts +1 -1
- package/src/player/components/timelineClipGestureHandlers.test.ts +94 -0
- package/src/player/components/timelineClipGestureHandlers.ts +20 -2
- package/src/player/components/timelineEditing.ts +3 -1
- package/src/player/components/timelineLinkSelection.ts +35 -1
- package/src/player/components/useTimelineClipDrag.keyboard.test.tsx +14 -0
- package/src/player/components/useTimelineClipDrag.ts +6 -7
- package/src/player/hooks/timelinePlayerSync.test.ts +6 -0
- package/src/player/hooks/timelinePlayerSync.ts +6 -1
- package/src/player/hooks/useTimelinePlayer.ts +5 -1
- package/src/player/lib/thumbnailScheduler.test.ts +16 -0
- package/src/player/lib/thumbnailScheduler.ts +10 -2
- package/src/player/lib/timelineElementHelpers.test.ts +87 -1
- package/src/player/lib/timelineElementHelpers.ts +6 -15
- package/src/player/lib/timelineText.ts +40 -0
- package/src/player/sceneSwap.ts +2 -1
- package/src/player/store/timelineElement.ts +12 -0
- package/src/styles/theme.css +3 -0
- package/src/telemetry/client.ts +2 -1
- package/src/utils/blockInstaller.ts +2 -1
- package/src/utils/elementGsap.test.ts +9 -0
- package/src/utils/elementGsap.ts +7 -0
- package/src/utils/gsapSoftReload.ts +2 -3
- package/src/utils/gsapUndoRestore.browser.test.ts +51 -45
- package/src/utils/gsapUndoRestore.ts +3 -6
- package/src/utils/keyframeUsage.ts +79 -0
- package/src/utils/previewFeatureUsage.test.ts +32 -0
- package/src/utils/previewFeatureUsage.ts +46 -0
- package/src/utils/previewPrefetch.ts +2 -1
- package/src/utils/razorSplitTransaction.ts +12 -8
- package/src/utils/softReloadTargets.ts +1 -3
- package/src/utils/studioApiFetch.test.ts +75 -0
- package/src/utils/studioApiFetch.ts +13 -0
- package/src/utils/studioFileHistory.ts +4 -1
- package/src/utils/studioHelpers.ts +7 -1
- package/src/utils/studioPendingEdits.test.ts +23 -6
- package/src/utils/studioPendingEdits.ts +32 -8
- package/src/utils/studioTelemetry.test.ts +107 -0
- package/src/utils/studioTelemetry.ts +51 -5
- package/src/utils/timelineCompositionInsert.ts +3 -2
- package/src/webmcp/StudioAgentTools.tsx +4 -3
- package/dist/chunk-BIUXP3PG.js.map +0 -1
- package/dist/chunk-K47E2ZJA.js.map +0 -1
- /package/dist/{chunk-VWT7STY3.js.map → chunk-J6Q572FA.js.map} +0 -0
- /package/dist/{domEditingLayers-V67MS4WP.js.map → domEditingLayers-OL7GLSS4.js.map} +0 -0
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
// @vitest-environment jsdom
|
|
2
|
-
import { act } from "react";
|
|
2
|
+
import { act, useLayoutEffect } from "react";
|
|
3
3
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
4
|
+
import type { GsapAnimation } from "@hyperframes/core/gsap-parser";
|
|
5
|
+
import { trackStudioEvent } from "../utils/studioTelemetry";
|
|
6
|
+
import { trackKeyframeCommit } from "../utils/keyframeUsage";
|
|
4
7
|
import type { DomEditSelection } from "../components/editor/domEditing";
|
|
5
8
|
import { usePlayerStore } from "../player";
|
|
6
9
|
import { mountReactHarness } from "./domSelectionTestHarness";
|
|
7
10
|
import type { CommitMutationOptions } from "./gsapScriptCommitTypes";
|
|
11
|
+
import { useGsapAwareEditing } from "./useGsapAwareEditing";
|
|
8
12
|
import { useGestureCommit } from "./useGestureCommit";
|
|
9
13
|
|
|
14
|
+
vi.mock("../utils/studioTelemetry", () => ({ trackStudioEvent: vi.fn() }));
|
|
15
|
+
|
|
10
16
|
const gestureRecording = vi.hoisted(() => ({
|
|
11
17
|
startRecording: vi.fn(),
|
|
12
18
|
stopRecording: vi.fn(() => [
|
|
@@ -84,44 +90,88 @@ function makeSelection(element: HTMLElement): DomEditSelection {
|
|
|
84
90
|
};
|
|
85
91
|
}
|
|
86
92
|
|
|
93
|
+
function mountRecording(
|
|
94
|
+
writer: (
|
|
95
|
+
mutation: Record<string, unknown>,
|
|
96
|
+
options: CommitMutationOptions,
|
|
97
|
+
selection?: DomEditSelection,
|
|
98
|
+
) => Promise<void>,
|
|
99
|
+
animations: GsapAnimation[] = [],
|
|
100
|
+
readOnlyPreview = false,
|
|
101
|
+
) {
|
|
102
|
+
const iframe = document.createElement("iframe");
|
|
103
|
+
document.body.append(iframe);
|
|
104
|
+
const element = document.createElement("div");
|
|
105
|
+
element.id = "card";
|
|
106
|
+
const session = {
|
|
107
|
+
current: {
|
|
108
|
+
domEditSelection: makeSelection(element),
|
|
109
|
+
selectedGsapAnimations: animations,
|
|
110
|
+
commitMutation: writer,
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
let hook: ReturnType<typeof useGestureCommit> | null = null;
|
|
114
|
+
function Probe() {
|
|
115
|
+
const editing = useGsapAwareEditing({
|
|
116
|
+
domEditSelection: session.current.domEditSelection,
|
|
117
|
+
selectedGsapAnimations: session.current.selectedGsapAnimations,
|
|
118
|
+
gsapCommitMutation: (selection, mutation, options) => writer(mutation, options, selection),
|
|
119
|
+
previewIframeRef: { current: iframe },
|
|
120
|
+
showToast: vi.fn(),
|
|
121
|
+
bumpGsapCache: vi.fn(),
|
|
122
|
+
makeFetchFallback: () => vi.fn(),
|
|
123
|
+
trackGsapInteractionFailure: vi.fn(),
|
|
124
|
+
stageElementPositionOffset: vi.fn(),
|
|
125
|
+
handleDomBoxSizeCommit: vi.fn(),
|
|
126
|
+
handleDomRotationCommit: vi.fn(),
|
|
127
|
+
commitPositionPatchToHtml: vi.fn(),
|
|
128
|
+
addGsapAnimation: vi.fn(),
|
|
129
|
+
convertToKeyframes: vi.fn(),
|
|
130
|
+
setArcPath: vi.fn(),
|
|
131
|
+
updateArcSegment: vi.fn(),
|
|
132
|
+
});
|
|
133
|
+
const currentHook = useGestureCommit({
|
|
134
|
+
domEditSessionRef: session,
|
|
135
|
+
previewIframeRef: { current: iframe },
|
|
136
|
+
showToast: vi.fn(),
|
|
137
|
+
isGestureRecordingRef: { current: false },
|
|
138
|
+
readOnlyPreview,
|
|
139
|
+
});
|
|
140
|
+
useLayoutEffect(() => {
|
|
141
|
+
session.current.commitMutation = editing.commitMutation;
|
|
142
|
+
hook = currentHook;
|
|
143
|
+
});
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
const root = mountReactHarness(<Probe />);
|
|
147
|
+
cleanup = () => act(() => root.unmount());
|
|
148
|
+
return Object.assign(
|
|
149
|
+
() => {
|
|
150
|
+
if (!hook) throw new Error("hook did not initialize");
|
|
151
|
+
return hook;
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
select: (selection: DomEditSelection, nextAnimations: GsapAnimation[]) => {
|
|
155
|
+
session.current.domEditSelection = selection;
|
|
156
|
+
session.current.selectedGsapAnimations = nextAnimations;
|
|
157
|
+
act(() => root.render(<Probe />));
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
87
163
|
describe("useGestureCommit", () => {
|
|
88
164
|
it("coalesces property-group commits and reloads only the terminal group", async () => {
|
|
89
|
-
const
|
|
90
|
-
document.body.append(iframe);
|
|
91
|
-
const element = document.createElement("div");
|
|
92
|
-
element.id = "card";
|
|
93
|
-
const commitMutation = vi.fn<
|
|
165
|
+
const writer = vi.fn<
|
|
94
166
|
(mutation: Record<string, unknown>, options: CommitMutationOptions) => Promise<void>
|
|
95
167
|
>(async () => {});
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
selectedGsapAnimations: [],
|
|
100
|
-
commitMutation,
|
|
101
|
-
},
|
|
102
|
-
};
|
|
103
|
-
const captured: { hook: ReturnType<typeof useGestureCommit> | null } = { hook: null };
|
|
104
|
-
function Probe() {
|
|
105
|
-
captured.hook = useGestureCommit({
|
|
106
|
-
domEditSessionRef: sessionRef,
|
|
107
|
-
previewIframeRef: { current: iframe },
|
|
108
|
-
showToast: vi.fn(),
|
|
109
|
-
isGestureRecordingRef: { current: false },
|
|
110
|
-
readOnlyPreview: false,
|
|
111
|
-
});
|
|
112
|
-
return null;
|
|
113
|
-
}
|
|
114
|
-
const root = mountReactHarness(<Probe />);
|
|
115
|
-
cleanup = () => act(() => root.unmount());
|
|
116
|
-
if (!captured.hook) throw new Error("hook did not initialize");
|
|
117
|
-
|
|
118
|
-
act(() => captured.hook?.handleToggleRecording());
|
|
119
|
-
act(() => captured.hook?.handleToggleRecording());
|
|
168
|
+
const hook = mountRecording(writer);
|
|
169
|
+
act(() => hook().handleToggleRecording());
|
|
170
|
+
act(() => hook().handleToggleRecording());
|
|
120
171
|
await act(async () => {
|
|
121
|
-
await vi.waitFor(() => expect(
|
|
172
|
+
await vi.waitFor(() => expect(writer).toHaveBeenCalledTimes(2));
|
|
122
173
|
});
|
|
123
|
-
|
|
124
|
-
const options = commitMutation.mock.calls.map((call) => call[1]);
|
|
174
|
+
const options = writer.mock.calls.map((call) => call[1]);
|
|
125
175
|
expect(new Set(options.map((entry) => entry.coalesceKey)).size).toBe(1);
|
|
126
176
|
expect(options[0]).toEqual(expect.objectContaining({ coalesceMs: Infinity, skipReload: true }));
|
|
127
177
|
expect(options[0]).not.toHaveProperty("softReload");
|
|
@@ -130,30 +180,150 @@ describe("useGestureCommit", () => {
|
|
|
130
180
|
});
|
|
131
181
|
|
|
132
182
|
it("does not start a recording while the preview is read-only", () => {
|
|
133
|
-
const
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
183
|
+
const writer = vi.fn(async () => {});
|
|
184
|
+
const hook = mountRecording(writer, [], true);
|
|
185
|
+
act(() => hook().handleToggleRecording());
|
|
186
|
+
expect(gestureRecording.startRecording).not.toHaveBeenCalled();
|
|
187
|
+
expect(writer).not.toHaveBeenCalled();
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it.each([
|
|
192
|
+
["new mixed-property recording", undefined, true, true, 1, "button"],
|
|
193
|
+
["keyboard recording", undefined, true, true, 1, "keyboard"],
|
|
194
|
+
["static hold replacement", "set", true, true, 1, "button"],
|
|
195
|
+
["overlapping tween replacement", "to", true, true, 1, "button"],
|
|
196
|
+
["unchanged recording", undefined, true, false, 0, "button"],
|
|
197
|
+
["refused recording", undefined, false, true, 0, "button"],
|
|
198
|
+
] as const)(
|
|
199
|
+
"counts writer receipts once for %s",
|
|
200
|
+
async (_name, method, ok, changed, count, inputMethod) => {
|
|
201
|
+
const animations = method
|
|
202
|
+
? [
|
|
203
|
+
{
|
|
204
|
+
id: "card-position",
|
|
205
|
+
targetSelector: "#card",
|
|
206
|
+
propertyGroup: "position",
|
|
207
|
+
method,
|
|
208
|
+
properties: { x: 0, y: 0 },
|
|
209
|
+
resolvedStart: 0,
|
|
210
|
+
position: 0,
|
|
211
|
+
duration: method === "set" ? 0 : 2,
|
|
212
|
+
keyframes: {
|
|
213
|
+
keyframes: [
|
|
214
|
+
{ percentage: 0, properties: { x: 0 } },
|
|
215
|
+
{ percentage: 100, properties: { x: 100 } },
|
|
216
|
+
],
|
|
217
|
+
},
|
|
218
|
+
} as unknown as GsapAnimation,
|
|
219
|
+
]
|
|
220
|
+
: [];
|
|
221
|
+
const writer = vi.fn(
|
|
222
|
+
async (mutation: Record<string, unknown>, options: CommitMutationOptions) => {
|
|
223
|
+
const result = { ok, changed };
|
|
224
|
+
trackKeyframeCommit([mutation], result, options);
|
|
225
|
+
options.onResult?.(result);
|
|
226
|
+
},
|
|
227
|
+
);
|
|
228
|
+
const hook = mountRecording(writer, animations);
|
|
229
|
+
act(() => hook().handleToggleRecording(inputMethod));
|
|
230
|
+
await act(async () => {
|
|
231
|
+
hook().handleToggleRecording();
|
|
232
|
+
await vi.waitFor(() => expect(writer).toHaveBeenCalled());
|
|
233
|
+
});
|
|
234
|
+
expect(trackStudioEvent).toHaveBeenCalledTimes(count * 2);
|
|
235
|
+
if (count) {
|
|
236
|
+
expect(trackStudioEvent).toHaveBeenCalledWith("keyframe", { action: "add" });
|
|
237
|
+
expect(trackStudioEvent).toHaveBeenCalledWith("feature_used", {
|
|
238
|
+
feature: "gesture_recording",
|
|
239
|
+
surface: "preview",
|
|
240
|
+
method: inputMethod,
|
|
149
241
|
});
|
|
150
|
-
return null;
|
|
151
242
|
}
|
|
152
|
-
|
|
153
|
-
|
|
243
|
+
},
|
|
244
|
+
);
|
|
154
245
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
246
|
+
it("keeps a pending recording isolated until its writer settles", async () => {
|
|
247
|
+
let release!: () => void;
|
|
248
|
+
const pending = new Promise<void>((resolve) => {
|
|
249
|
+
release = resolve;
|
|
250
|
+
});
|
|
251
|
+
const writer = vi.fn(
|
|
252
|
+
async (_mutation: Record<string, unknown>, options: CommitMutationOptions) => {
|
|
253
|
+
await pending;
|
|
254
|
+
options.onResult?.({ ok: true, changed: true });
|
|
255
|
+
},
|
|
256
|
+
);
|
|
257
|
+
const hook = mountRecording(writer);
|
|
258
|
+
act(() => hook().handleToggleRecording("keyboard"));
|
|
259
|
+
act(() => hook().handleToggleRecording());
|
|
260
|
+
await vi.waitFor(() => expect(writer).toHaveBeenCalledTimes(1));
|
|
261
|
+
act(() => hook().handleToggleRecording("button"));
|
|
262
|
+
expect(gestureRecording.startRecording).toHaveBeenCalledTimes(1);
|
|
263
|
+
await act(async () => {
|
|
264
|
+
release();
|
|
265
|
+
await vi.waitFor(() =>
|
|
266
|
+
expect(trackStudioEvent).toHaveBeenCalledWith("feature_used", {
|
|
267
|
+
feature: "gesture_recording",
|
|
268
|
+
surface: "preview",
|
|
269
|
+
method: "keyboard",
|
|
270
|
+
}),
|
|
271
|
+
);
|
|
272
|
+
});
|
|
273
|
+
expect(gestureRecording.clearSamples).toHaveBeenCalledTimes(1);
|
|
274
|
+
act(() => hook().handleToggleRecording("button"));
|
|
275
|
+
expect(gestureRecording.startRecording).toHaveBeenCalledTimes(2);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it("writes one recording to its original selection after selecting another source", async () => {
|
|
279
|
+
const original = {
|
|
280
|
+
id: "original-position",
|
|
281
|
+
targetSelector: "#card",
|
|
282
|
+
propertyGroup: "position",
|
|
283
|
+
method: "set",
|
|
284
|
+
properties: { x: 0, y: 0 },
|
|
285
|
+
resolvedStart: 0,
|
|
286
|
+
position: 0,
|
|
287
|
+
duration: 0,
|
|
288
|
+
} as GsapAnimation;
|
|
289
|
+
const writer = vi.fn(
|
|
290
|
+
async (
|
|
291
|
+
_mutation: Record<string, unknown>,
|
|
292
|
+
options: CommitMutationOptions,
|
|
293
|
+
_selection?: DomEditSelection,
|
|
294
|
+
) => {
|
|
295
|
+
options.onResult?.({ ok: true, changed: true });
|
|
296
|
+
},
|
|
297
|
+
);
|
|
298
|
+
const hook = mountRecording(writer, [original]);
|
|
299
|
+
act(() => hook().handleToggleRecording("keyboard"));
|
|
300
|
+
const other = document.createElement("div");
|
|
301
|
+
other.id = "other";
|
|
302
|
+
hook.select(
|
|
303
|
+
{ ...makeSelection(other), sourceFile: "other.html", compositionPath: "other.html" },
|
|
304
|
+
[],
|
|
305
|
+
);
|
|
306
|
+
await act(async () => {
|
|
307
|
+
hook().handleToggleRecording();
|
|
308
|
+
await vi.waitFor(() => expect(trackStudioEvent).toHaveBeenCalledTimes(2));
|
|
309
|
+
});
|
|
310
|
+
expect(writer).toHaveBeenCalledExactlyOnceWith(
|
|
311
|
+
expect.objectContaining({
|
|
312
|
+
type: "replace-with-keyframes",
|
|
313
|
+
animationId: "original-position",
|
|
314
|
+
targetSelector: "#card",
|
|
315
|
+
}),
|
|
316
|
+
expect.objectContaining({ keyframeAction: "add" }),
|
|
317
|
+
expect.objectContaining({
|
|
318
|
+
id: "card",
|
|
319
|
+
sourceFile: "index.html",
|
|
320
|
+
compositionPath: "index.html",
|
|
321
|
+
}),
|
|
322
|
+
);
|
|
323
|
+
expect(trackStudioEvent).toHaveBeenCalledWith("keyframe", { action: "add" });
|
|
324
|
+
expect(trackStudioEvent).toHaveBeenCalledWith("feature_used", {
|
|
325
|
+
feature: "gesture_recording",
|
|
326
|
+
surface: "preview",
|
|
327
|
+
method: "keyboard",
|
|
158
328
|
});
|
|
159
329
|
});
|
|
@@ -11,6 +11,8 @@ import { usePlayerStore } from "../player";
|
|
|
11
11
|
import type { DomEditSelection } from "../components/editor/domEditing";
|
|
12
12
|
import type { GsapAnimation } from "@hyperframes/core/gsap-parser";
|
|
13
13
|
import type { CommitMutationOptions } from "./gsapScriptCommitTypes";
|
|
14
|
+
import { trackPreviewEditResult } from "../utils/previewFeatureUsage";
|
|
15
|
+
import { observeGsapGesture } from "./gsapGestureOutcome";
|
|
14
16
|
import { roundTo3 } from "../utils/rounding";
|
|
15
17
|
import { classifyPropertyGroup } from "@hyperframes/core/gsap-parser";
|
|
16
18
|
import { isInstantHold, idSelector, writeTargetSelector, tweenTargetsElement } from "./gsapShared";
|
|
@@ -72,6 +74,7 @@ interface GestureSessionRef {
|
|
|
72
74
|
commitMutation?: (
|
|
73
75
|
mutation: Record<string, unknown>,
|
|
74
76
|
options: CommitMutationOptions,
|
|
77
|
+
selection?: DomEditSelection,
|
|
75
78
|
) => Promise<void>;
|
|
76
79
|
}
|
|
77
80
|
|
|
@@ -81,6 +84,12 @@ function reloadOnlyLast(index: number, count: number): Partial<CommitMutationOpt
|
|
|
81
84
|
return index === count - 1 ? { softReload: true } : { skipReload: true };
|
|
82
85
|
}
|
|
83
86
|
|
|
87
|
+
function recordingEnd(selection: DomEditSelection): number | undefined {
|
|
88
|
+
const start = Number.parseFloat(selection.dataAttributes?.start ?? "0") || 0;
|
|
89
|
+
const duration = Number.parseFloat(selection.dataAttributes?.duration ?? "0") || 0;
|
|
90
|
+
return duration > 0 ? start + duration : undefined;
|
|
91
|
+
}
|
|
92
|
+
|
|
84
93
|
let gestureRecordingCommitCounter = 0;
|
|
85
94
|
|
|
86
95
|
interface UseGestureCommitParams {
|
|
@@ -95,7 +104,7 @@ interface UseGestureCommitParams {
|
|
|
95
104
|
export interface UseGestureCommitResult {
|
|
96
105
|
gestureState: "idle" | "recording";
|
|
97
106
|
gestureRecording: ReturnType<typeof useGestureRecording>;
|
|
98
|
-
handleToggleRecording: () => void;
|
|
107
|
+
handleToggleRecording: (method?: "button" | "keyboard") => void;
|
|
99
108
|
}
|
|
100
109
|
|
|
101
110
|
// fallow-ignore-next-line complexity
|
|
@@ -111,11 +120,13 @@ export function useGestureCommit({
|
|
|
111
120
|
const [gestureState, setGestureState] = useState<"idle" | "recording">("idle");
|
|
112
121
|
const gestureStateRef = useRef<"idle" | "recording">("idle");
|
|
113
122
|
const recordingAutoStopRef = useRef<ReturnType<typeof setInterval>>(undefined);
|
|
123
|
+
const recordingMethodRef = useRef<"button" | "keyboard">("button");
|
|
114
124
|
const recordingStartTimeRef = useRef(0);
|
|
115
125
|
const commitInFlightRef = useRef(false);
|
|
116
126
|
// Capture selection at recording start so commit always targets the recorded element,
|
|
117
127
|
// even if the user's selection changes mid-recording.
|
|
118
128
|
const capturedSelectionRef = useRef<DomEditSelection | null>(null);
|
|
129
|
+
const capturedAnimationsRef = useRef<GsapAnimation[]>([]);
|
|
119
130
|
|
|
120
131
|
// Unmount: clear auto-stop interval
|
|
121
132
|
useEffect(() => () => clearInterval(recordingAutoStopRef.current), []);
|
|
@@ -206,6 +217,12 @@ export function useGestureCommit({
|
|
|
206
217
|
return;
|
|
207
218
|
}
|
|
208
219
|
if (liveSession.commitMutation) {
|
|
220
|
+
const writer = liveSession.commitMutation;
|
|
221
|
+
const outcome = observeGsapGesture((_selection, mutation, options) =>
|
|
222
|
+
writer(mutation, options, sel),
|
|
223
|
+
);
|
|
224
|
+
const commit = (mutation: Record<string, unknown>, options: CommitMutationOptions) =>
|
|
225
|
+
outcome.commit!(sel, mutation, options);
|
|
209
226
|
const recStart = recordingStartTimeRef.current;
|
|
210
227
|
const rawKeyframes = sortedPcts.map((pct) => ({
|
|
211
228
|
percentage: pct,
|
|
@@ -216,7 +233,7 @@ export function useGestureCommit({
|
|
|
216
233
|
const hasPositionProps = keyframes.some((kf) =>
|
|
217
234
|
Object.keys(kf.properties).some((k) => classifyPropertyGroup(k) === "position"),
|
|
218
235
|
);
|
|
219
|
-
const allAnims =
|
|
236
|
+
const allAnims = capturedAnimationsRef.current;
|
|
220
237
|
const existingPositionTween = hasPositionProps
|
|
221
238
|
? allAnims.find(
|
|
222
239
|
(a) =>
|
|
@@ -228,7 +245,7 @@ export function useGestureCommit({
|
|
|
228
245
|
if (isInstantHold(existingPositionTween)) {
|
|
229
246
|
// An instant hold is not a tween to merge into — replace it with the
|
|
230
247
|
// recorded motion (which already starts from the held position).
|
|
231
|
-
await
|
|
248
|
+
await commit(
|
|
232
249
|
{
|
|
233
250
|
type: "replace-with-keyframes",
|
|
234
251
|
animationId: existingPositionTween.id,
|
|
@@ -237,7 +254,7 @@ export function useGestureCommit({
|
|
|
237
254
|
duration: roundTo3(duration),
|
|
238
255
|
keyframes,
|
|
239
256
|
},
|
|
240
|
-
{ label: "Gesture recording (replace set)", softReload: true },
|
|
257
|
+
{ label: "Gesture recording (replace set)", softReload: true, keyframeAction: "add" },
|
|
241
258
|
);
|
|
242
259
|
} else {
|
|
243
260
|
const tweenStart = existingPositionTween.resolvedStart ?? 0;
|
|
@@ -274,7 +291,7 @@ export function useGestureCommit({
|
|
|
274
291
|
|
|
275
292
|
const merged = [...preserved, ...mapped].sort((a, b) => a.percentage - b.percentage);
|
|
276
293
|
|
|
277
|
-
await
|
|
294
|
+
await commit(
|
|
278
295
|
{
|
|
279
296
|
type: "replace-with-keyframes",
|
|
280
297
|
animationId: existingPositionTween.id,
|
|
@@ -286,7 +303,7 @@ export function useGestureCommit({
|
|
|
286
303
|
duration: tweenDur,
|
|
287
304
|
keyframes: merged,
|
|
288
305
|
},
|
|
289
|
-
{ label: "Gesture recording (merge)", softReload: true },
|
|
306
|
+
{ label: "Gesture recording (merge)", softReload: true, keyframeAction: "add" },
|
|
290
307
|
);
|
|
291
308
|
} else {
|
|
292
309
|
// Emit one tween per property group so a mixed-prop gesture (e.g.
|
|
@@ -294,7 +311,7 @@ export function useGestureCommit({
|
|
|
294
311
|
// tween that the position-only drag intercept can't edit.
|
|
295
312
|
const keyframeGroups = partitionKeyframesByGroup(keyframes);
|
|
296
313
|
for (const [index, groupKfs] of keyframeGroups.entries()) {
|
|
297
|
-
await
|
|
314
|
+
await commit(
|
|
298
315
|
{
|
|
299
316
|
type: "add-with-keyframes",
|
|
300
317
|
targetSelector: writeSelector,
|
|
@@ -320,7 +337,7 @@ export function useGestureCommit({
|
|
|
320
337
|
// No existing tween — same per-group split as the new-range branch above.
|
|
321
338
|
const keyframeGroups = partitionKeyframesByGroup(keyframes);
|
|
322
339
|
for (const [index, groupKfs] of keyframeGroups.entries()) {
|
|
323
|
-
await
|
|
340
|
+
await commit(
|
|
324
341
|
{
|
|
325
342
|
type: "add-with-keyframes",
|
|
326
343
|
targetSelector: writeSelector,
|
|
@@ -338,6 +355,7 @@ export function useGestureCommit({
|
|
|
338
355
|
);
|
|
339
356
|
}
|
|
340
357
|
}
|
|
358
|
+
trackPreviewEditResult("gesture_recording", recordingMethodRef.current, outcome.finish());
|
|
341
359
|
}
|
|
342
360
|
showToast(`Recorded ${sortedPcts.length} keyframes`, "info");
|
|
343
361
|
} catch (err) {
|
|
@@ -351,58 +369,71 @@ export function useGestureCommit({
|
|
|
351
369
|
}
|
|
352
370
|
}, [gestureRecording, showToast, isGestureRecordingRef, domEditSessionRef]);
|
|
353
371
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
372
|
+
const armAutoStop = useCallback(
|
|
373
|
+
(elementEnd: number | undefined) => {
|
|
374
|
+
clearInterval(recordingAutoStopRef.current);
|
|
375
|
+
const autoStopAt = elementEnd ?? Infinity;
|
|
376
|
+
recordingAutoStopRef.current = setInterval(() => {
|
|
377
|
+
const { currentTime: t, duration: d } = usePlayerStore.getState();
|
|
378
|
+
const limit = Math.min(autoStopAt, d);
|
|
379
|
+
if (limit > 0 && t >= limit - 0.05) {
|
|
380
|
+
void stopAndCommitRecording();
|
|
381
|
+
}
|
|
382
|
+
}, 100);
|
|
383
|
+
},
|
|
384
|
+
[stopAndCommitRecording],
|
|
385
|
+
);
|
|
386
|
+
|
|
387
|
+
const startRecording = useCallback(
|
|
388
|
+
(method: "button" | "keyboard") => {
|
|
389
|
+
if (readOnlyPreview) return;
|
|
390
|
+
const sel = domEditSessionRef.current.domEditSelection;
|
|
391
|
+
if (!sel) {
|
|
392
|
+
showToast("Select an element first", "error");
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
const iframe = previewIframeRef.current;
|
|
396
|
+
if (!iframe) {
|
|
397
|
+
showToast("Preview not ready — try again", "error");
|
|
359
398
|
return;
|
|
360
399
|
}
|
|
361
|
-
void stopAndCommitRecording();
|
|
362
|
-
return;
|
|
363
|
-
}
|
|
364
|
-
if (readOnlyPreview) return;
|
|
365
|
-
const sel = domEditSessionRef.current.domEditSelection;
|
|
366
|
-
if (!sel) {
|
|
367
|
-
showToast("Select an element first", "error");
|
|
368
|
-
return;
|
|
369
|
-
}
|
|
370
|
-
const iframe = previewIframeRef.current;
|
|
371
|
-
if (!iframe) {
|
|
372
|
-
showToast("Preview not ready — try again", "error");
|
|
373
|
-
return;
|
|
374
|
-
}
|
|
375
400
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
401
|
+
const store = usePlayerStore.getState();
|
|
402
|
+
recordingMethodRef.current = method;
|
|
403
|
+
recordingStartTimeRef.current = store.currentTime;
|
|
404
|
+
const elementEnd = recordingEnd(sel);
|
|
405
|
+
capturedSelectionRef.current = sel;
|
|
406
|
+
capturedAnimationsRef.current = domEditSessionRef.current.selectedGsapAnimations ?? [];
|
|
407
|
+
gestureRecording.startRecording(sel.element, iframe, elementEnd);
|
|
408
|
+
gestureStateRef.current = "recording";
|
|
409
|
+
isGestureRecordingRef.current = true;
|
|
410
|
+
setGestureState("recording");
|
|
386
411
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
412
|
+
armAutoStop(elementEnd);
|
|
413
|
+
},
|
|
414
|
+
[
|
|
415
|
+
gestureRecording,
|
|
416
|
+
showToast,
|
|
417
|
+
previewIframeRef,
|
|
418
|
+
domEditSessionRef,
|
|
419
|
+
isGestureRecordingRef,
|
|
420
|
+
readOnlyPreview,
|
|
421
|
+
armAutoStop,
|
|
422
|
+
],
|
|
423
|
+
);
|
|
424
|
+
|
|
425
|
+
const handleToggleRecording = useCallback(
|
|
426
|
+
(method: "button" | "keyboard" = "button") => {
|
|
427
|
+
if (commitInFlightRef.current) return;
|
|
428
|
+
if (gestureStateRef.current === "recording") {
|
|
429
|
+
if (readOnlyPreview) cancelRecording();
|
|
430
|
+
else void stopAndCommitRecording();
|
|
431
|
+
return;
|
|
394
432
|
}
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
stopAndCommitRecording,
|
|
400
|
-
cancelRecording,
|
|
401
|
-
previewIframeRef,
|
|
402
|
-
domEditSessionRef,
|
|
403
|
-
isGestureRecordingRef,
|
|
404
|
-
readOnlyPreview,
|
|
405
|
-
]);
|
|
433
|
+
startRecording(method);
|
|
434
|
+
},
|
|
435
|
+
[cancelRecording, readOnlyPreview, startRecording, stopAndCommitRecording],
|
|
436
|
+
);
|
|
406
437
|
|
|
407
438
|
return useStableHandlers({ gestureState, gestureRecording, handleToggleRecording }, projectId);
|
|
408
439
|
}
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
type ElementMatchSelection,
|
|
17
17
|
} from "../utils/studioHelpers";
|
|
18
18
|
import type { TimelineElement } from "../player";
|
|
19
|
+
import { studioApiFetch } from "../utils/studioApiFetch";
|
|
19
20
|
|
|
20
21
|
interface UseGroupCommitsParams extends DomEditCommitBaseParams {
|
|
21
22
|
/** Resync the SDK session after a server-side write (the wrapper/unwrap changes
|
|
@@ -132,7 +133,7 @@ async function commitStructuralMutation(
|
|
|
132
133
|
writeFile: deps.writeProjectFile,
|
|
133
134
|
recordEdit: deps.editHistory.recordEdit,
|
|
134
135
|
rewrite: async (originalContent) => {
|
|
135
|
-
const mutateResponse = await
|
|
136
|
+
const mutateResponse = await studioApiFetch(
|
|
136
137
|
buildProjectApiPath(pid, `/file-mutations/${route}/${encodeURIComponent(targetPath)}`),
|
|
137
138
|
{
|
|
138
139
|
method: "POST",
|