@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,3 +1,6 @@
|
|
|
1
|
+
import { useGsapAwareGroupMove } from "./useGsapAwareGroupMove";
|
|
2
|
+
import { observeGsapGesture } from "./gsapGestureOutcome";
|
|
3
|
+
import type { DomEditPersistOutcome } from "./domEditCommitTypes";
|
|
1
4
|
/**
|
|
2
5
|
* GSAP-aware move/resize/rotation wrappers that intercept geometry commits
|
|
3
6
|
* for animated elements and route them through script mutation instead of
|
|
@@ -21,14 +24,9 @@ import {
|
|
|
21
24
|
useGsapSaveFailureTelemetry,
|
|
22
25
|
useSafeGsapCommitMutation,
|
|
23
26
|
} from "./useSafeGsapCommitMutation";
|
|
24
|
-
import type {
|
|
25
|
-
CommitMutation,
|
|
26
|
-
CommitMutationCall,
|
|
27
|
-
CommitMutationOptions,
|
|
28
|
-
} from "./gsapScriptCommitTypes";
|
|
27
|
+
import type { CommitMutation, CommitMutationOptions } from "./gsapScriptCommitTypes";
|
|
29
28
|
import { setElementGsapPosition } from "../utils/elementGsap";
|
|
30
29
|
import { logResize, logResizeSettle } from "../utils/resizeDebug";
|
|
31
|
-
import type { DomEditGroupPathOffsetCommit } from "../components/editor/DomEditOverlay";
|
|
32
30
|
import type { MoveCommitOptions } from "../components/editor/domEditOverlayGestures";
|
|
33
31
|
import { runGestureTransaction } from "./gestureTransaction";
|
|
34
32
|
import {
|
|
@@ -40,33 +38,18 @@ import {
|
|
|
40
38
|
} from "./gsapRuntimeKeyframes";
|
|
41
39
|
import { assertGsapEditPersisted, saveMove } from "./gsapEditOutcome";
|
|
42
40
|
import type { GsapAnimationFetchOptions } from "./useGsapAnimationFetchFallback";
|
|
43
|
-
import {
|
|
41
|
+
import { type ElementOffsetStagerDeps } from "./elementOffsetStager";
|
|
44
42
|
import {
|
|
45
43
|
prepareCropResize,
|
|
46
44
|
saveCropResize,
|
|
47
45
|
writeSizeWithCrop,
|
|
48
46
|
} from "../components/editor/cropResize";
|
|
49
47
|
|
|
50
|
-
// Distinct coalesceKey per group drag so consecutive group drags don't fold
|
|
51
|
-
// into one another's undo entry (module-local counter, not Date.now()).
|
|
52
|
-
let groupDragCommitCounter = 0;
|
|
53
|
-
|
|
54
|
-
function firstPreflightFailure(
|
|
55
|
-
results: PromiseSettledResult<void>[],
|
|
56
|
-
updates: DomEditGroupPathOffsetCommit[],
|
|
57
|
-
): { error: unknown; selection: DomEditSelection } | null {
|
|
58
|
-
for (const [index, result] of results.entries()) {
|
|
59
|
-
if (result.status !== "rejected") continue;
|
|
60
|
-
const selection = updates[index]?.selection;
|
|
61
|
-
if (selection) return { error: result.reason, selection };
|
|
62
|
-
}
|
|
63
|
-
return null;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
48
|
export interface UseGsapAwareEditingParams {
|
|
67
49
|
domEditSelection: DomEditSelection | null;
|
|
68
50
|
selectedGsapAnimations: GsapAnimation[];
|
|
69
51
|
gsapCommitMutation: CommitMutation | null;
|
|
52
|
+
activeCompPath?: string | null;
|
|
70
53
|
previewIframeRef: React.RefObject<HTMLIFrameElement | null>;
|
|
71
54
|
showToast: (message: string, tone?: "error" | "info") => void;
|
|
72
55
|
bumpGsapCache: () => void;
|
|
@@ -79,6 +62,7 @@ export interface UseGsapAwareEditingParams {
|
|
|
79
62
|
selection: DomEditSelection | null,
|
|
80
63
|
mutationType: string,
|
|
81
64
|
label: string,
|
|
65
|
+
toast?: boolean,
|
|
82
66
|
) => void;
|
|
83
67
|
// DOM fallbacks (from useDomEditCommits)
|
|
84
68
|
stageElementPositionOffset: (
|
|
@@ -86,15 +70,18 @@ export interface UseGsapAwareEditingParams {
|
|
|
86
70
|
next: { x: number; y: number },
|
|
87
71
|
plainTranslate: boolean,
|
|
88
72
|
coalesceKey?: string,
|
|
89
|
-
) => { save: () => Promise<
|
|
90
|
-
handleDomRotationCommit: (
|
|
73
|
+
) => { save: () => Promise<DomEditPersistOutcome | undefined>; rollback: () => void };
|
|
74
|
+
handleDomRotationCommit: (
|
|
75
|
+
selection: DomEditSelection,
|
|
76
|
+
next: RotationCommit,
|
|
77
|
+
) => Promise<DomEditPersistOutcome | undefined>;
|
|
91
78
|
handleDomBoxSizeCommit: (
|
|
92
79
|
selection: DomEditSelection,
|
|
93
80
|
next: { width: number; height: number },
|
|
94
81
|
offset?: { x: number; y: number },
|
|
95
82
|
restore?: () => void,
|
|
96
83
|
undoKey?: string,
|
|
97
|
-
) => Promise<
|
|
84
|
+
) => Promise<DomEditPersistOutcome | undefined>;
|
|
98
85
|
commitPositionPatchToHtml: ElementOffsetStagerDeps["commitPositionPatchToHtml"];
|
|
99
86
|
// GSAP script commit ops (from useGsapScriptCommits)
|
|
100
87
|
addGsapAnimation: (
|
|
@@ -132,6 +119,7 @@ export function useGsapAwareEditing({
|
|
|
132
119
|
domEditSelection,
|
|
133
120
|
selectedGsapAnimations,
|
|
134
121
|
gsapCommitMutation,
|
|
122
|
+
activeCompPath,
|
|
135
123
|
previewIframeRef,
|
|
136
124
|
showToast,
|
|
137
125
|
bumpGsapCache,
|
|
@@ -162,10 +150,12 @@ export function useGsapAwareEditing({
|
|
|
162
150
|
next: { x: number; y: number },
|
|
163
151
|
modifiers?: MoveCommitOptions,
|
|
164
152
|
) => {
|
|
165
|
-
|
|
166
|
-
if (modifiers?.plainTranslate ?? !gsapWritesPosition(selection.element))
|
|
167
|
-
|
|
168
|
-
|
|
153
|
+
const writes = observeGsapGesture(gsapCommitMutation);
|
|
154
|
+
if (modifiers?.plainTranslate ?? !gsapWritesPosition(selection.element)) {
|
|
155
|
+
const result = await stageElementPositionOffset(selection, next, true).save();
|
|
156
|
+
return writes.finish(result?.changed === true);
|
|
157
|
+
}
|
|
158
|
+
if (writes.commit) {
|
|
169
159
|
try {
|
|
170
160
|
const ownedAnimations = getGsapAnimationsForSelection(selection);
|
|
171
161
|
const targetAnimations = Array.isArray(ownedAnimations)
|
|
@@ -176,11 +166,15 @@ export function useGsapAwareEditing({
|
|
|
176
166
|
next,
|
|
177
167
|
targetAnimations,
|
|
178
168
|
previewIframeRef.current,
|
|
179
|
-
|
|
169
|
+
writes.commit,
|
|
180
170
|
makeFetchFallback(selection),
|
|
181
171
|
modifiers,
|
|
182
172
|
);
|
|
183
|
-
await saveMove(outcome, () =>
|
|
173
|
+
await saveMove(outcome, async () => {
|
|
174
|
+
const result = await stageElementPositionOffset(selection, next, false).save();
|
|
175
|
+
writes.recordDomResult(result);
|
|
176
|
+
});
|
|
177
|
+
return writes.finish();
|
|
184
178
|
} catch (error) {
|
|
185
179
|
trackGsapInteractionFailure(error, selection, "drag", "Move animated layer");
|
|
186
180
|
throw error;
|
|
@@ -197,137 +191,15 @@ export function useGsapAwareEditing({
|
|
|
197
191
|
],
|
|
198
192
|
);
|
|
199
193
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
const coalesceKey = `group-drag:${++groupDragCommitCounter}`;
|
|
210
|
-
// Members are written one at a time, and a re-render re-runs the script with the OLD
|
|
211
|
-
// position of every member not yet written, so they snap back until their own write
|
|
212
|
-
// lands. The drafts are already on screen: hold the render until the last member.
|
|
213
|
-
let renderOnCommit = false;
|
|
214
|
-
const previewFallbackLatch = { pending: false };
|
|
215
|
-
const withGroupOptions = (options: CommitMutationOptions): CommitMutationOptions => ({
|
|
216
|
-
...options,
|
|
217
|
-
coalesceKey,
|
|
218
|
-
coalesceMs: Number.POSITIVE_INFINITY,
|
|
219
|
-
deferPreviewSync: !renderOnCommit,
|
|
220
|
-
previewFallbackLatch,
|
|
221
|
-
});
|
|
222
|
-
// Every member writes the same file. Queue their mutations and send them as
|
|
223
|
-
// ONE request instead of one round trip per member: the server reads, parses
|
|
224
|
-
// and writes the composition once, and the preview patches once.
|
|
225
|
-
const queued: CommitMutationCall[] = [];
|
|
226
|
-
const flushQueued = async () => {
|
|
227
|
-
if (queued.length === 0) return;
|
|
228
|
-
const calls = queued.splice(0, queued.length);
|
|
229
|
-
if (!gsapCommitMutation.batch) {
|
|
230
|
-
for (const call of calls) {
|
|
231
|
-
await gsapCommitMutation(call.selection, call.mutation, call.options);
|
|
232
|
-
}
|
|
233
|
-
return;
|
|
234
|
-
}
|
|
235
|
-
await gsapCommitMutation.batch(calls, {
|
|
236
|
-
...(calls.at(-1)?.options ?? { label: "Move animated layer (group)" }),
|
|
237
|
-
label: "Move animated layer (group)",
|
|
238
|
-
});
|
|
239
|
-
};
|
|
240
|
-
const coalescedCommit: typeof gsapCommitMutation = (selection, mutation, options) => {
|
|
241
|
-
queued.push({ selection, mutation, options: withGroupOptions(options) });
|
|
242
|
-
return Promise.resolve();
|
|
243
|
-
};
|
|
244
|
-
const preflightAnimations = new Map<DomEditSelection, GsapAnimation[]>();
|
|
245
|
-
// Members saved on themselves, each with its route: true for its CSS translate.
|
|
246
|
-
const offsetMembers = new Map<DomEditSelection, boolean>();
|
|
247
|
-
// Editability is user-atomic: prove every member can be written before the first source
|
|
248
|
-
// mutation, so a blocked member never leaves earlier siblings partially moved. Preflights
|
|
249
|
-
// write nothing and share one in-flight parse per file, so they run together.
|
|
250
|
-
const preflightResults = await Promise.allSettled(
|
|
251
|
-
updates.map(async ({ selection, plainTranslate }) => {
|
|
252
|
-
if (plainTranslate ?? !gsapWritesPosition(selection.element)) {
|
|
253
|
-
refuseGsapTakeover(selection.element, showToast);
|
|
254
|
-
return void offsetMembers.set(selection, true);
|
|
255
|
-
}
|
|
256
|
-
const animations = await makeFetchFallback(selection, { failOnFetchError: true })();
|
|
257
|
-
preflightAnimations.set(selection, animations);
|
|
258
|
-
const outcome = await tryGsapDragIntercept(
|
|
259
|
-
selection,
|
|
260
|
-
{ x: 0, y: 0 },
|
|
261
|
-
animations,
|
|
262
|
-
previewIframeRef.current,
|
|
263
|
-
coalescedCommit,
|
|
264
|
-
undefined,
|
|
265
|
-
{ preflightOnly: true, group: true },
|
|
266
|
-
);
|
|
267
|
-
if (outcome.status === "element-offset") offsetMembers.set(selection, false);
|
|
268
|
-
assertGsapEditPersisted(outcome);
|
|
269
|
-
}),
|
|
270
|
-
);
|
|
271
|
-
const preflightFailure = firstPreflightFailure(preflightResults, updates);
|
|
272
|
-
if (preflightFailure) {
|
|
273
|
-
trackGsapInteractionFailure(
|
|
274
|
-
preflightFailure.error,
|
|
275
|
-
preflightFailure.selection,
|
|
276
|
-
"drag",
|
|
277
|
-
"Move animated layer (group)",
|
|
278
|
-
);
|
|
279
|
-
throw preflightFailure.error;
|
|
280
|
-
}
|
|
281
|
-
const lastScriptWrite = updates.findLastIndex(
|
|
282
|
-
({ selection }) => !offsetMembers.has(selection),
|
|
283
|
-
);
|
|
284
|
-
for (const [index, { selection, next }] of updates.entries()) {
|
|
285
|
-
renderOnCommit = index === lastScriptWrite;
|
|
286
|
-
const plain = offsetMembers.get(selection);
|
|
287
|
-
if (plain !== undefined) {
|
|
288
|
-
await stageElementPositionOffset(selection, next, plain, coalesceKey).save();
|
|
289
|
-
continue;
|
|
290
|
-
}
|
|
291
|
-
try {
|
|
292
|
-
const outcome = await tryGsapDragIntercept(
|
|
293
|
-
selection,
|
|
294
|
-
next,
|
|
295
|
-
preflightAnimations.get(selection) ?? [],
|
|
296
|
-
previewIframeRef.current,
|
|
297
|
-
coalescedCommit,
|
|
298
|
-
// The intercept re-reads the file to resolve a stale or shared tween.
|
|
299
|
-
// Anything already queued has to be on disk before that read, or it
|
|
300
|
-
// resolves against a file missing writes it is about to build on.
|
|
301
|
-
async () => {
|
|
302
|
-
await flushQueued();
|
|
303
|
-
return makeFetchFallback(selection, { fresh: true })();
|
|
304
|
-
},
|
|
305
|
-
{ preflightPassed: true },
|
|
306
|
-
);
|
|
307
|
-
assertGsapEditPersisted(outcome);
|
|
308
|
-
} catch (error) {
|
|
309
|
-
trackGsapInteractionFailure(error, selection, "drag", "Move animated layer (group)");
|
|
310
|
-
throw error;
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
try {
|
|
314
|
-
await flushQueued();
|
|
315
|
-
} catch (error) {
|
|
316
|
-
// The aggregate write has no uniquely failing member; do not misattribute
|
|
317
|
-
// its telemetry to whichever member happened to be last in the array.
|
|
318
|
-
trackGsapInteractionFailure(error, null, "drag", "Move animated layer (group)");
|
|
319
|
-
throw error;
|
|
320
|
-
}
|
|
321
|
-
},
|
|
322
|
-
[
|
|
323
|
-
gsapCommitMutation,
|
|
324
|
-
previewIframeRef,
|
|
325
|
-
makeFetchFallback,
|
|
326
|
-
trackGsapInteractionFailure,
|
|
327
|
-
stageElementPositionOffset,
|
|
328
|
-
showToast,
|
|
329
|
-
],
|
|
330
|
-
);
|
|
194
|
+
const handleGsapAwareGroupPathOffsetCommit = useGsapAwareGroupMove({
|
|
195
|
+
gsapCommitMutation,
|
|
196
|
+
activeCompPath,
|
|
197
|
+
previewIframeRef,
|
|
198
|
+
makeFetchFallback,
|
|
199
|
+
trackGsapInteractionFailure,
|
|
200
|
+
stageElementPositionOffset,
|
|
201
|
+
showToast,
|
|
202
|
+
});
|
|
331
203
|
|
|
332
204
|
const handleGsapAwareBoxSizeCommit = useCallback(
|
|
333
205
|
async (
|
|
@@ -336,8 +208,11 @@ export function useGsapAwareEditing({
|
|
|
336
208
|
offset?: { x: number; y: number },
|
|
337
209
|
restore: () => void = () => undefined,
|
|
338
210
|
) => {
|
|
339
|
-
|
|
340
|
-
|
|
211
|
+
const writes = observeGsapGesture(gsapCommitMutation);
|
|
212
|
+
if (!gsapWritesBox(selection.element)) {
|
|
213
|
+
const result = await handleDomBoxSizeCommit(selection, next, offset, restore);
|
|
214
|
+
return writes.finish(result?.changed === true);
|
|
215
|
+
}
|
|
341
216
|
let targetAnimations: GsapAnimation[];
|
|
342
217
|
try {
|
|
343
218
|
const ownedAnimations = getGsapAnimationsForSelection(selection);
|
|
@@ -367,7 +242,28 @@ export function useGsapAwareEditing({
|
|
|
367
242
|
let anchorMove: ReturnType<typeof stageElementPositionOffset> | null = null;
|
|
368
243
|
const stageCrop = prepareCropResize(selection.element);
|
|
369
244
|
let cropUndoKey: string | null = null;
|
|
370
|
-
|
|
245
|
+
const saveResizeAnchor = async (
|
|
246
|
+
ownsDragOffset: boolean,
|
|
247
|
+
commitMutation: CommitMutation,
|
|
248
|
+
coalesceKey: string,
|
|
249
|
+
) => {
|
|
250
|
+
if (offset && !ownsDragOffset) {
|
|
251
|
+
const dragOutcome = await tryGsapDragIntercept(
|
|
252
|
+
selection,
|
|
253
|
+
offset,
|
|
254
|
+
targetAnimations,
|
|
255
|
+
previewIframeRef.current,
|
|
256
|
+
commitMutation,
|
|
257
|
+
makeFetchFallback(selection),
|
|
258
|
+
);
|
|
259
|
+
// Saved after the size, under its undo key, so the two are one step.
|
|
260
|
+
await saveMove(dragOutcome, async () => {
|
|
261
|
+
const plain = !gsapWritesPosition(selection.element);
|
|
262
|
+
anchorMove = stageElementPositionOffset(selection, offset, plain, coalesceKey);
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
await runGestureTransaction({
|
|
371
267
|
element: selection.element,
|
|
372
268
|
label: "Resize layer",
|
|
373
269
|
settle: () => {
|
|
@@ -383,8 +279,8 @@ export function useGsapAwareEditing({
|
|
|
383
279
|
setElementGsapPosition(selection.element, newX, newY);
|
|
384
280
|
},
|
|
385
281
|
persist: async (commit, coalesceKey) => {
|
|
386
|
-
if (
|
|
387
|
-
const commitMutation = commit(
|
|
282
|
+
if (writes.commit) {
|
|
283
|
+
const commitMutation = commit(writes.commit);
|
|
388
284
|
try {
|
|
389
285
|
const outcome = await tryGsapResizeIntercept(
|
|
390
286
|
selection,
|
|
@@ -393,12 +289,20 @@ export function useGsapAwareEditing({
|
|
|
393
289
|
previewIframeRef.current,
|
|
394
290
|
commitMutation,
|
|
395
291
|
makeFetchFallback(selection),
|
|
292
|
+
offset,
|
|
396
293
|
);
|
|
397
294
|
assertGsapEditPersisted(outcome);
|
|
398
295
|
// Saved before the buffered GSAP writes, so their reload stays the gesture's last render.
|
|
399
|
-
if (outcome.status === "element-size")
|
|
400
|
-
await handleDomBoxSizeCommit(
|
|
401
|
-
|
|
296
|
+
if (outcome.status === "element-size") {
|
|
297
|
+
const result = await handleDomBoxSizeCommit(
|
|
298
|
+
selection,
|
|
299
|
+
next,
|
|
300
|
+
undefined,
|
|
301
|
+
undefined,
|
|
302
|
+
coalesceKey,
|
|
303
|
+
);
|
|
304
|
+
writes.recordDomResult(result);
|
|
305
|
+
} else cropUndoKey = coalesceKey;
|
|
402
306
|
// What the resize did, not what its tweens suggest: a scale hold still commits a size.
|
|
403
307
|
const ownsDragOffset =
|
|
404
308
|
outcome.status === "persisted" && outcome.ownsDragOffset === true;
|
|
@@ -409,21 +313,7 @@ export function useGsapAwareEditing({
|
|
|
409
313
|
});
|
|
410
314
|
// A resize that moved the element itself has already written
|
|
411
315
|
// where it landed. Everything else leaves the anchor to the drag.
|
|
412
|
-
|
|
413
|
-
const dragOutcome = await tryGsapDragIntercept(
|
|
414
|
-
selection,
|
|
415
|
-
offset,
|
|
416
|
-
targetAnimations,
|
|
417
|
-
previewIframeRef.current,
|
|
418
|
-
commitMutation,
|
|
419
|
-
makeFetchFallback(selection),
|
|
420
|
-
);
|
|
421
|
-
// Saved after the size, under its undo key, so the two are one step.
|
|
422
|
-
await saveMove(dragOutcome, async () => {
|
|
423
|
-
const plain = !gsapWritesPosition(selection.element);
|
|
424
|
-
anchorMove = stageElementPositionOffset(selection, offset, plain, coalesceKey);
|
|
425
|
-
});
|
|
426
|
-
}
|
|
316
|
+
await saveResizeAnchor(ownsDragOffset, commitMutation, coalesceKey);
|
|
427
317
|
logResizeSettle(selection.element, ownsDragOffset ? "gsap-scale" : "gsap-size");
|
|
428
318
|
return;
|
|
429
319
|
} catch (error) {
|
|
@@ -434,10 +324,17 @@ export function useGsapAwareEditing({
|
|
|
434
324
|
throw new Error("Resize of a GSAP-owned box has no GSAP writer");
|
|
435
325
|
},
|
|
436
326
|
afterBufferedCommitsSaved: async () => {
|
|
437
|
-
await anchorMove?.save();
|
|
327
|
+
const anchorResult = await anchorMove?.save();
|
|
328
|
+
writes.recordDomResult(anchorResult);
|
|
438
329
|
// Only now is the size live for every caller, drag or not.
|
|
439
330
|
if (cropUndoKey) {
|
|
440
|
-
await saveCropResize(
|
|
331
|
+
const cropResult = await saveCropResize(
|
|
332
|
+
stageCrop,
|
|
333
|
+
selection,
|
|
334
|
+
commitPositionPatchToHtml,
|
|
335
|
+
cropUndoKey,
|
|
336
|
+
);
|
|
337
|
+
writes.recordDomResult(cropResult);
|
|
441
338
|
}
|
|
442
339
|
},
|
|
443
340
|
restore: () => {
|
|
@@ -446,6 +343,7 @@ export function useGsapAwareEditing({
|
|
|
446
343
|
},
|
|
447
344
|
skipPixelAssert: hasLivePositionTween,
|
|
448
345
|
});
|
|
346
|
+
return writes.finish();
|
|
449
347
|
},
|
|
450
348
|
[
|
|
451
349
|
handleDomBoxSizeCommit,
|
|
@@ -461,9 +359,12 @@ export function useGsapAwareEditing({
|
|
|
461
359
|
|
|
462
360
|
const handleGsapAwareRotationCommit = useCallback(
|
|
463
361
|
async (selection: DomEditSelection, next: RotationCommit) => {
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
362
|
+
const writes = observeGsapGesture(gsapCommitMutation);
|
|
363
|
+
if (next.plain || !gsapWritesRotation(selection.element)) {
|
|
364
|
+
const result = await handleDomRotationCommit(selection, next);
|
|
365
|
+
return writes.finish(result?.changed === true);
|
|
366
|
+
}
|
|
367
|
+
if (writes.commit) {
|
|
467
368
|
try {
|
|
468
369
|
const targetAnimations = await getGsapAnimationsForSelection(selection);
|
|
469
370
|
// A keyframe or a tl.set; a computed source rejects, so the gesture restores its draft.
|
|
@@ -472,10 +373,11 @@ export function useGsapAwareEditing({
|
|
|
472
373
|
next.angle,
|
|
473
374
|
targetAnimations,
|
|
474
375
|
previewIframeRef.current,
|
|
475
|
-
|
|
376
|
+
writes.commit,
|
|
476
377
|
makeFetchFallback(selection),
|
|
477
378
|
);
|
|
478
379
|
assertGsapEditPersisted(outcome);
|
|
380
|
+
return writes.finish();
|
|
479
381
|
} catch (error) {
|
|
480
382
|
trackGsapInteractionFailure(error, selection, "rotation", "Rotate animated layer");
|
|
481
383
|
throw error;
|
|
@@ -564,12 +466,16 @@ export function useGsapAwareEditing({
|
|
|
564
466
|
);
|
|
565
467
|
|
|
566
468
|
const commitMutation = useCallback(
|
|
567
|
-
async (
|
|
568
|
-
|
|
469
|
+
async (
|
|
470
|
+
mutation: Record<string, unknown>,
|
|
471
|
+
options: CommitMutationOptions,
|
|
472
|
+
selection = domEditSelection,
|
|
473
|
+
) => {
|
|
474
|
+
if (!selection) return;
|
|
569
475
|
// Return (await) the safe-commit chain so consumers that `await
|
|
570
476
|
// session.commitMutation(...)` (gesture recording, enable-keyframes) run
|
|
571
477
|
// their post-actions only after the server save has settled.
|
|
572
|
-
await safeGsapCommit(
|
|
478
|
+
await safeGsapCommit(selection, mutation, options);
|
|
573
479
|
},
|
|
574
480
|
[domEditSelection, safeGsapCommit],
|
|
575
481
|
);
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { useCallback } from "react";
|
|
2
|
+
import type { DomEditSelection } from "../components/editor/domEditingTypes";
|
|
3
|
+
import type { DomEditGroupPathOffsetCommit } from "../components/editor/DomEditOverlay";
|
|
4
|
+
import type { GsapAnimation } from "@hyperframes/core/gsap-parser";
|
|
5
|
+
import type { CommitMutationCall, CommitMutationOptions } from "./gsapScriptCommitTypes";
|
|
6
|
+
import type { UseGsapAwareEditingParams } from "./useGsapAwareEditing";
|
|
7
|
+
import { observeGsapGesture } from "./gsapGestureOutcome";
|
|
8
|
+
import { gsapWritesPosition } from "./gsapRuntimeKeyframes";
|
|
9
|
+
import { refuseGsapTakeover } from "./elementOffsetStager";
|
|
10
|
+
import { tryGsapDragIntercept } from "./gsapRuntimeBridge";
|
|
11
|
+
import { assertGsapEditPersisted } from "./gsapEditOutcome";
|
|
12
|
+
import { firstPreflightFailure } from "./gsapGroupPreflight";
|
|
13
|
+
|
|
14
|
+
// A distinct key keeps consecutive group drags in separate undo entries.
|
|
15
|
+
let groupDragCommitCounter = 0;
|
|
16
|
+
|
|
17
|
+
export function useGsapAwareGroupMove({
|
|
18
|
+
gsapCommitMutation,
|
|
19
|
+
activeCompPath,
|
|
20
|
+
previewIframeRef,
|
|
21
|
+
makeFetchFallback,
|
|
22
|
+
trackGsapInteractionFailure,
|
|
23
|
+
stageElementPositionOffset,
|
|
24
|
+
showToast,
|
|
25
|
+
}: Pick<
|
|
26
|
+
UseGsapAwareEditingParams,
|
|
27
|
+
| "gsapCommitMutation"
|
|
28
|
+
| "activeCompPath"
|
|
29
|
+
| "previewIframeRef"
|
|
30
|
+
| "makeFetchFallback"
|
|
31
|
+
| "trackGsapInteractionFailure"
|
|
32
|
+
| "stageElementPositionOffset"
|
|
33
|
+
| "showToast"
|
|
34
|
+
>) {
|
|
35
|
+
const handleGsapAwareGroupPathOffsetCommit = useCallback(
|
|
36
|
+
async (
|
|
37
|
+
updates: DomEditGroupPathOffsetCommit[],
|
|
38
|
+
options: { refusalToast?: boolean } = {},
|
|
39
|
+
): Promise<import("../utils/previewFeatureUsage").GeometryCommitResult> => {
|
|
40
|
+
const writes = observeGsapGesture(gsapCommitMutation);
|
|
41
|
+
const writer = writes.commit;
|
|
42
|
+
if (!writer) return { ok: true, changed: false };
|
|
43
|
+
const toastRefusal = options.refusalToast !== false;
|
|
44
|
+
// One coalesce key across slow writes keeps the group drag in one undo entry.
|
|
45
|
+
const coalesceKey = `group-drag:${++groupDragCommitCounter}`;
|
|
46
|
+
// Hold the render until every member is saved, or unwritten members snap back.
|
|
47
|
+
let renderOnCommit = false;
|
|
48
|
+
const previewFallbackLatch = { pending: false };
|
|
49
|
+
const withGroupOptions = (options: CommitMutationOptions): CommitMutationOptions => ({
|
|
50
|
+
...options,
|
|
51
|
+
coalesceKey,
|
|
52
|
+
coalesceMs: Number.POSITIVE_INFINITY,
|
|
53
|
+
deferPreviewSync: !renderOnCommit,
|
|
54
|
+
previewFallbackLatch,
|
|
55
|
+
});
|
|
56
|
+
// Batch members in the same file to avoid repeating its read, parse, write and preview patch.
|
|
57
|
+
const queued: CommitMutationCall[] = [];
|
|
58
|
+
const flushQueued = async () => {
|
|
59
|
+
if (queued.length === 0) return;
|
|
60
|
+
const calls = queued.splice(0, queued.length);
|
|
61
|
+
if (!writer.batch) {
|
|
62
|
+
for (const call of calls) {
|
|
63
|
+
await writer(call.selection, call.mutation, call.options);
|
|
64
|
+
}
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
await writer.batch(calls, {
|
|
68
|
+
...(calls.at(-1)?.options ?? { label: "Move animated layer (group)" }),
|
|
69
|
+
label: "Move animated layer (group)",
|
|
70
|
+
});
|
|
71
|
+
};
|
|
72
|
+
const coalescedCommit: typeof gsapCommitMutation = (selection, mutation, options) => {
|
|
73
|
+
queued.push({ selection, mutation, options: withGroupOptions(options) });
|
|
74
|
+
return Promise.resolve();
|
|
75
|
+
};
|
|
76
|
+
const preflightAnimations = new Map<DomEditSelection, GsapAnimation[]>();
|
|
77
|
+
// Members saved on themselves, each with its route: true for its CSS translate.
|
|
78
|
+
const offsetMembers = new Map<DomEditSelection, boolean>();
|
|
79
|
+
// Editability is user-atomic: prove every member can be written before the first source
|
|
80
|
+
// mutation, so a blocked member never leaves earlier siblings partially moved. Preflights
|
|
81
|
+
// write nothing and share one in-flight parse per file, so they run together.
|
|
82
|
+
const preflightResults = await Promise.allSettled(
|
|
83
|
+
updates.map(async ({ selection, next, plainTranslate }) => {
|
|
84
|
+
if (plainTranslate ?? !gsapWritesPosition(selection.element)) {
|
|
85
|
+
refuseGsapTakeover(selection.element, toastRefusal ? showToast : () => {});
|
|
86
|
+
return void offsetMembers.set(selection, true);
|
|
87
|
+
}
|
|
88
|
+
const animations = await makeFetchFallback(selection, { failOnFetchError: true })();
|
|
89
|
+
preflightAnimations.set(selection, animations);
|
|
90
|
+
const outcome = await tryGsapDragIntercept(
|
|
91
|
+
selection,
|
|
92
|
+
next,
|
|
93
|
+
animations,
|
|
94
|
+
previewIframeRef.current,
|
|
95
|
+
coalescedCommit,
|
|
96
|
+
undefined,
|
|
97
|
+
{ preflightOnly: true, group: true },
|
|
98
|
+
);
|
|
99
|
+
if (outcome.status === "element-offset") offsetMembers.set(selection, false);
|
|
100
|
+
assertGsapEditPersisted(outcome);
|
|
101
|
+
}),
|
|
102
|
+
);
|
|
103
|
+
const preflightFailure = firstPreflightFailure(
|
|
104
|
+
preflightResults,
|
|
105
|
+
updates,
|
|
106
|
+
offsetMembers,
|
|
107
|
+
activeCompPath,
|
|
108
|
+
);
|
|
109
|
+
if (preflightFailure) {
|
|
110
|
+
trackGsapInteractionFailure(
|
|
111
|
+
preflightFailure.error,
|
|
112
|
+
preflightFailure.selection,
|
|
113
|
+
"drag",
|
|
114
|
+
"Move animated layer (group)",
|
|
115
|
+
toastRefusal,
|
|
116
|
+
);
|
|
117
|
+
throw preflightFailure.error;
|
|
118
|
+
}
|
|
119
|
+
const lastScriptWrite = updates.findLastIndex(
|
|
120
|
+
({ selection }) => !offsetMembers.has(selection),
|
|
121
|
+
);
|
|
122
|
+
for (const [index, { selection, next }] of updates.entries()) {
|
|
123
|
+
renderOnCommit = index === lastScriptWrite;
|
|
124
|
+
const plain = offsetMembers.get(selection);
|
|
125
|
+
if (plain !== undefined) {
|
|
126
|
+
const result = await stageElementPositionOffset(
|
|
127
|
+
selection,
|
|
128
|
+
next,
|
|
129
|
+
plain,
|
|
130
|
+
coalesceKey,
|
|
131
|
+
).save();
|
|
132
|
+
writes.recordDomResult(result);
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
try {
|
|
136
|
+
const outcome = await tryGsapDragIntercept(
|
|
137
|
+
selection,
|
|
138
|
+
next,
|
|
139
|
+
preflightAnimations.get(selection) ?? [],
|
|
140
|
+
previewIframeRef.current,
|
|
141
|
+
coalescedCommit,
|
|
142
|
+
// The intercept re-reads the file to resolve a stale or shared tween.
|
|
143
|
+
// Anything already queued has to be on disk before that read, or it
|
|
144
|
+
// resolves against a file missing writes it is about to build on.
|
|
145
|
+
async () => {
|
|
146
|
+
await flushQueued();
|
|
147
|
+
return makeFetchFallback(selection, { fresh: true })();
|
|
148
|
+
},
|
|
149
|
+
{ preflightPassed: true },
|
|
150
|
+
);
|
|
151
|
+
assertGsapEditPersisted(outcome);
|
|
152
|
+
} catch (error) {
|
|
153
|
+
trackGsapInteractionFailure(error, selection, "drag", "Move animated layer (group)");
|
|
154
|
+
throw error;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
try {
|
|
158
|
+
await flushQueued();
|
|
159
|
+
return writes.finish();
|
|
160
|
+
} catch (error) {
|
|
161
|
+
// The aggregate write has no uniquely failing member; do not misattribute
|
|
162
|
+
// its telemetry to whichever member happened to be last in the array.
|
|
163
|
+
trackGsapInteractionFailure(error, null, "drag", "Move animated layer (group)");
|
|
164
|
+
throw error;
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
[
|
|
168
|
+
gsapCommitMutation,
|
|
169
|
+
activeCompPath,
|
|
170
|
+
previewIframeRef,
|
|
171
|
+
makeFetchFallback,
|
|
172
|
+
trackGsapInteractionFailure,
|
|
173
|
+
stageElementPositionOffset,
|
|
174
|
+
showToast,
|
|
175
|
+
],
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
return handleGsapAwareGroupPathOffsetCommit;
|
|
179
|
+
}
|
|
@@ -4,8 +4,8 @@ import { trackStudioEditBlocked, trackStudioSaveFailure } from "../utils/studioS
|
|
|
4
4
|
import { isGsapEditBlockedError } from "./gsapEditOutcome";
|
|
5
5
|
import { wasAlreadyToasted } from "./domEditPersistFailure";
|
|
6
6
|
|
|
7
|
-
function failureToast(error: unknown): string | null {
|
|
8
|
-
if (wasAlreadyToasted(error)) return null;
|
|
7
|
+
function failureToast(error: unknown, toast: boolean): string | null {
|
|
8
|
+
if (!toast || wasAlreadyToasted(error)) return null;
|
|
9
9
|
return isGsapEditBlockedError(error) ? error.message : "Failed to save animated edit.";
|
|
10
10
|
}
|
|
11
11
|
|
|
@@ -14,7 +14,13 @@ export function useGsapInteractionFailureTelemetry(
|
|
|
14
14
|
showToast: (message: string, tone?: "error" | "info") => void,
|
|
15
15
|
) {
|
|
16
16
|
return useCallback(
|
|
17
|
-
(
|
|
17
|
+
(
|
|
18
|
+
error: unknown,
|
|
19
|
+
selection: DomEditSelection | null,
|
|
20
|
+
mutationType: string,
|
|
21
|
+
label: string,
|
|
22
|
+
toast?: boolean,
|
|
23
|
+
) => {
|
|
18
24
|
const report = isGsapEditBlockedError(error)
|
|
19
25
|
? trackStudioEditBlocked
|
|
20
26
|
: trackStudioSaveFailure;
|
|
@@ -28,7 +34,7 @@ export function useGsapInteractionFailureTelemetry(
|
|
|
28
34
|
targetSelector: selection?.selector,
|
|
29
35
|
targetSourceFile: selection?.sourceFile,
|
|
30
36
|
});
|
|
31
|
-
const message = failureToast(error);
|
|
37
|
+
const message = failureToast(error, toast !== false);
|
|
32
38
|
if (message) showToast(message, "error");
|
|
33
39
|
},
|
|
34
40
|
[activeCompPath, showToast],
|