@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
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
import { act } from "react";
|
|
3
|
+
import { createRoot, type Root } from "react-dom/client";
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
5
|
+
import { useGsapKeyframeOps } from "./useGsapKeyframeOps";
|
|
6
|
+
import type { CutoverResult } from "../utils/sdkCutover";
|
|
7
|
+
import type { DomEditSelection } from "../components/editor/domEditingTypes";
|
|
8
|
+
import { trackStudioEvent } from "../utils/studioTelemetry";
|
|
9
|
+
|
|
10
|
+
const sdkWrite = vi.hoisted(() => vi.fn<(...args: unknown[]) => Promise<CutoverResult>>());
|
|
11
|
+
vi.mock("../utils/sdkCutover", async (original) => ({
|
|
12
|
+
...(await original<typeof import("../utils/sdkCutover")>()),
|
|
13
|
+
sdkGsapKeyframePersist: sdkWrite,
|
|
14
|
+
sdkGsapConvertToKeyframesPersist: sdkWrite,
|
|
15
|
+
sdkGsapRemoveAllKeyframesPersist: sdkWrite,
|
|
16
|
+
}));
|
|
17
|
+
vi.mock("../utils/studioTelemetry", () => ({ trackStudioEvent: vi.fn() }));
|
|
18
|
+
Reflect.set(globalThis, "IS_REACT_ACT_ENVIRONMENT", true);
|
|
19
|
+
type Params = Parameters<typeof useGsapKeyframeOps>[0];
|
|
20
|
+
let root: Root;
|
|
21
|
+
function mount() {
|
|
22
|
+
let api: ReturnType<typeof useGsapKeyframeOps> | undefined;
|
|
23
|
+
const legacy = vi.fn(async () => undefined);
|
|
24
|
+
function Probe() {
|
|
25
|
+
api = useGsapKeyframeOps({
|
|
26
|
+
activeCompPath: "index.html",
|
|
27
|
+
commitMutation: legacy,
|
|
28
|
+
commitMutationSafely: legacy,
|
|
29
|
+
trackGsapSaveFailure: vi.fn(),
|
|
30
|
+
sdkSession: {} as NonNullable<Params["sdkSession"]>,
|
|
31
|
+
sdkDeps: {
|
|
32
|
+
editHistory: { recordEdit: vi.fn(async () => undefined) },
|
|
33
|
+
writeProjectFile: vi.fn(async () => undefined),
|
|
34
|
+
reloadPreview: vi.fn(),
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
root = createRoot(document.createElement("div"));
|
|
40
|
+
act(() => root.render(<Probe />));
|
|
41
|
+
if (!api) throw new Error("hook did not mount");
|
|
42
|
+
return { api, legacy };
|
|
43
|
+
}
|
|
44
|
+
const selection = { id: "private-target", sourceFile: "private.html" } as DomEditSelection;
|
|
45
|
+
beforeEach(() => {
|
|
46
|
+
vi.clearAllMocks();
|
|
47
|
+
sdkWrite.mockResolvedValue({
|
|
48
|
+
status: "committed",
|
|
49
|
+
version: "v2",
|
|
50
|
+
before: "before",
|
|
51
|
+
after: "after",
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
afterEach(() => act(() => root.unmount()));
|
|
55
|
+
describe("SDK keyframe usage", () => {
|
|
56
|
+
it.each(["add", "convert", "remove_all", "reset"] as const)(
|
|
57
|
+
"counts a changed committed %s operation once",
|
|
58
|
+
async (action) => {
|
|
59
|
+
const { api, legacy } = mount();
|
|
60
|
+
await act(async () => {
|
|
61
|
+
if (action === "add")
|
|
62
|
+
await api.addKeyframeBatch(selection, "private-animation", 50, { x: 12, y: 4 });
|
|
63
|
+
else if (action === "convert") await api.convertToKeyframes(selection, "private-animation");
|
|
64
|
+
else await api.removeAllKeyframes(selection, "private-animation", action);
|
|
65
|
+
});
|
|
66
|
+
expect(legacy).not.toHaveBeenCalled();
|
|
67
|
+
expect(trackStudioEvent).toHaveBeenCalledExactlyOnceWith("keyframe", { action });
|
|
68
|
+
},
|
|
69
|
+
);
|
|
70
|
+
it("preserves property metadata on a single-property SDK add", async () => {
|
|
71
|
+
const { api } = mount();
|
|
72
|
+
await act(async () => api.addKeyframe(selection, "private-animation", 50, "x", 12));
|
|
73
|
+
expect(trackStudioEvent).toHaveBeenCalledExactlyOnceWith("keyframe", {
|
|
74
|
+
action: "add",
|
|
75
|
+
property: "x",
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
it("does not count unchanged SDK source", async () => {
|
|
79
|
+
sdkWrite.mockResolvedValue({
|
|
80
|
+
status: "committed",
|
|
81
|
+
version: "v2",
|
|
82
|
+
before: "same",
|
|
83
|
+
after: "same",
|
|
84
|
+
});
|
|
85
|
+
const { api } = mount();
|
|
86
|
+
await api.addKeyframeBatch(selection, "private-animation", 50, { x: 12 });
|
|
87
|
+
expect(await api.removeAllKeyframes(selection, "private-animation")).toBe(false);
|
|
88
|
+
expect(trackStudioEvent).not.toHaveBeenCalled();
|
|
89
|
+
});
|
|
90
|
+
it("honors intermediate-write suppression in SDK paths", async () => {
|
|
91
|
+
const { api } = mount();
|
|
92
|
+
await api.convertToKeyframes(selection, "private-animation", undefined, undefined, {
|
|
93
|
+
keyframeTelemetry: false,
|
|
94
|
+
});
|
|
95
|
+
await api.addKeyframeBatch(
|
|
96
|
+
selection,
|
|
97
|
+
"private-animation",
|
|
98
|
+
50,
|
|
99
|
+
{ x: 12 },
|
|
100
|
+
{ keyframeTelemetry: false },
|
|
101
|
+
);
|
|
102
|
+
await api.removeAllKeyframes(selection, "private-animation", "remove_all", false);
|
|
103
|
+
expect(trackStudioEvent).not.toHaveBeenCalled();
|
|
104
|
+
});
|
|
105
|
+
it("does not fall back or count after a failed SDK write", async () => {
|
|
106
|
+
sdkWrite.mockResolvedValue({ status: "failed", error: new Error("refused") });
|
|
107
|
+
const { api, legacy } = mount();
|
|
108
|
+
await expect(
|
|
109
|
+
api.addKeyframeBatch(selection, "private-animation", 50, { x: 12 }),
|
|
110
|
+
).rejects.toThrow("refused");
|
|
111
|
+
expect(legacy).not.toHaveBeenCalled();
|
|
112
|
+
expect(trackStudioEvent).not.toHaveBeenCalled();
|
|
113
|
+
});
|
|
114
|
+
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { trackKeyframeUsage } from "../utils/keyframeUsage";
|
|
1
2
|
// fallow-ignore-file code-duplication
|
|
2
3
|
// Add/remove operation-family transaction shapes stay parallel until SDK graduation.
|
|
3
4
|
import { useCallback } from "react";
|
|
@@ -139,10 +140,14 @@ export function useGsapKeyframeOps({
|
|
|
139
140
|
coalesceKey: `gsap:${animationId}:kf:${percentage}`,
|
|
140
141
|
},
|
|
141
142
|
);
|
|
142
|
-
if (cutoverCommittedOrThrow(handled))
|
|
143
|
+
if (cutoverCommittedOrThrow(handled)) {
|
|
144
|
+
if (handled.before !== handled.after) trackKeyframeUsage("add", property);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
143
147
|
}
|
|
144
148
|
await commitMutation(selection, mutation, {
|
|
145
149
|
label: `Add keyframe at ${percentage}%`,
|
|
150
|
+
keyframeProperty: property,
|
|
146
151
|
softReload: true,
|
|
147
152
|
});
|
|
148
153
|
},
|
|
@@ -172,7 +177,11 @@ export function useGsapKeyframeOps({
|
|
|
172
177
|
sdkDeps,
|
|
173
178
|
toSdkPersistOptions(`Add keyframe at ${percentage}%`, commitOverrides),
|
|
174
179
|
);
|
|
175
|
-
if (cutoverCommittedOrThrow(handled))
|
|
180
|
+
if (cutoverCommittedOrThrow(handled)) {
|
|
181
|
+
if (handled.before !== handled.after && commitOverrides?.keyframeTelemetry !== false)
|
|
182
|
+
trackKeyframeUsage("add");
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
176
185
|
}
|
|
177
186
|
return commitMutation(
|
|
178
187
|
selection,
|
|
@@ -319,7 +328,11 @@ export function useGsapKeyframeOps({
|
|
|
319
328
|
sdkDeps,
|
|
320
329
|
toSdkPersistOptions("Convert to keyframes", commitOverrides),
|
|
321
330
|
);
|
|
322
|
-
if (cutoverCommittedOrThrow(handled))
|
|
331
|
+
if (cutoverCommittedOrThrow(handled)) {
|
|
332
|
+
if (handled.before !== handled.after && commitOverrides.keyframeTelemetry !== false)
|
|
333
|
+
trackKeyframeUsage("convert");
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
323
336
|
}
|
|
324
337
|
return commitMutation(
|
|
325
338
|
selection,
|
|
@@ -333,11 +346,19 @@ export function useGsapKeyframeOps({
|
|
|
333
346
|
);
|
|
334
347
|
|
|
335
348
|
const removeAllKeyframes = useCallback(
|
|
336
|
-
async (
|
|
349
|
+
async (
|
|
350
|
+
selection: DomEditSelection,
|
|
351
|
+
animationId: string,
|
|
352
|
+
action: "remove_all" | "reset" = "remove_all",
|
|
353
|
+
telemetry = true,
|
|
354
|
+
): Promise<boolean> => {
|
|
337
355
|
const targetPath = selection.sourceFile || activeCompPath || "index.html";
|
|
338
356
|
// A class/descendant selector can resolve a live element whose selection
|
|
339
357
|
// deliberately has no id. The cache is still keyed by that DOM id.
|
|
340
|
-
const
|
|
358
|
+
const clearCache = () => {
|
|
359
|
+
const cacheElementId = selection.id || selection.element?.id;
|
|
360
|
+
if (cacheElementId) clearKeyframeCacheForElement(targetPath, cacheElementId);
|
|
361
|
+
};
|
|
341
362
|
if (sdkSession && sdkDeps) {
|
|
342
363
|
const handled = await sdkGsapRemoveAllKeyframesPersist(
|
|
343
364
|
targetPath,
|
|
@@ -347,26 +368,31 @@ export function useGsapKeyframeOps({
|
|
|
347
368
|
{ label: "Remove all keyframes" },
|
|
348
369
|
);
|
|
349
370
|
if (cutoverCommittedOrThrow(handled)) {
|
|
350
|
-
|
|
351
|
-
|
|
371
|
+
clearCache();
|
|
372
|
+
const changed = handled.before !== handled.after;
|
|
373
|
+
if (changed && telemetry) trackKeyframeUsage(action);
|
|
374
|
+
return changed;
|
|
352
375
|
}
|
|
353
376
|
}
|
|
377
|
+
let changed = false;
|
|
354
378
|
await commitMutationSafely(
|
|
355
379
|
selection,
|
|
356
380
|
{ type: "remove-all-keyframes", animationId },
|
|
357
381
|
{
|
|
358
382
|
label: "Remove all keyframes",
|
|
383
|
+
keyframeAction: action,
|
|
384
|
+
keyframeTelemetry: telemetry,
|
|
359
385
|
softReload: true,
|
|
360
386
|
// The committed result is the single success boundary: clearing
|
|
361
387
|
// before it makes failed saves lie, while waiting for the reload leaves
|
|
362
388
|
// stale diamonds visible during the source round-trip.
|
|
363
389
|
onResult: (result) => {
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
}
|
|
390
|
+
changed = result.ok && result.changed === true;
|
|
391
|
+
if (changed) clearCache();
|
|
367
392
|
},
|
|
368
393
|
},
|
|
369
394
|
);
|
|
395
|
+
return changed;
|
|
370
396
|
},
|
|
371
397
|
[commitMutationSafely, activeCompPath, sdkSession, sdkDeps],
|
|
372
398
|
);
|
|
@@ -590,6 +590,63 @@ describe("runCommit — instantPatch wiring", () => {
|
|
|
590
590
|
vi.unstubAllGlobals();
|
|
591
591
|
});
|
|
592
592
|
|
|
593
|
+
it.each([
|
|
594
|
+
["add-keyframe", "add"],
|
|
595
|
+
["add-with-keyframes", "add"],
|
|
596
|
+
["convert-to-keyframes", "convert"],
|
|
597
|
+
["remove-all-keyframes", "remove_all"],
|
|
598
|
+
])("counts %s only after a changed durable write", async (type, action) => {
|
|
599
|
+
mockFetchResult();
|
|
600
|
+
const deps = renderCommitHook();
|
|
601
|
+
await deps.api.commitMutation(
|
|
602
|
+
selection,
|
|
603
|
+
{ type, properties: { text: "private-content" } },
|
|
604
|
+
{ label: "Edit" },
|
|
605
|
+
);
|
|
606
|
+
expect(trackStudioEvent.mock.calls.filter(([event]) => event === "keyframe")).toEqual([
|
|
607
|
+
["keyframe", { action }],
|
|
608
|
+
]);
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
it("counts reset with its explicit action and suppresses intermediate writes", async () => {
|
|
612
|
+
mockFetchResult();
|
|
613
|
+
const deps = renderCommitHook();
|
|
614
|
+
await deps.api.commitMutation(
|
|
615
|
+
selection,
|
|
616
|
+
{ type: "convert-to-keyframes" },
|
|
617
|
+
{ label: "Convert", keyframeTelemetry: false },
|
|
618
|
+
);
|
|
619
|
+
await deps.api.commitMutation(
|
|
620
|
+
selection,
|
|
621
|
+
{ type: "remove-all-keyframes" },
|
|
622
|
+
{ label: "Reset", keyframeAction: "reset" },
|
|
623
|
+
);
|
|
624
|
+
expect(trackStudioEvent.mock.calls.filter(([event]) => event === "keyframe")).toEqual([
|
|
625
|
+
["keyframe", { action: "reset" }],
|
|
626
|
+
]);
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
it("does not count a successful unchanged keyframe write", async () => {
|
|
630
|
+
mockFetchResult({ changed: false });
|
|
631
|
+
const deps = renderCommitHook();
|
|
632
|
+
await deps.api.commitMutation(selection, { type: "add-keyframe" }, { label: "Add" });
|
|
633
|
+
expect(trackStudioEvent.mock.calls.filter(([event]) => event === "keyframe")).toEqual([]);
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
it("does not count a failed write that skipReload suppresses", async () => {
|
|
637
|
+
vi.stubGlobal(
|
|
638
|
+
"fetch",
|
|
639
|
+
vi.fn(async () => ({ ok: false, status: 500 })),
|
|
640
|
+
);
|
|
641
|
+
const deps = renderCommitHook();
|
|
642
|
+
await deps.api.commitMutation(
|
|
643
|
+
selection,
|
|
644
|
+
{ type: "add-keyframe" },
|
|
645
|
+
{ label: "Add", skipReload: true },
|
|
646
|
+
);
|
|
647
|
+
expect(trackStudioEvent.mock.calls.filter(([event]) => event === "keyframe")).toEqual([]);
|
|
648
|
+
});
|
|
649
|
+
|
|
593
650
|
const NESTED_SCRIPT = 'window.__timelines["root"] = tl;';
|
|
594
651
|
const SUB = `<template><div data-composition-id="sub"><div id="nwid" style="left: 40px"></div></div></template>`;
|
|
595
652
|
|
|
@@ -641,7 +698,9 @@ describe("runCommit — instantPatch wiring", () => {
|
|
|
641
698
|
await deps.api.commitMutation(selection, { x: 10 }, { label: "drag", softReload: true });
|
|
642
699
|
});
|
|
643
700
|
|
|
644
|
-
expect(fetch).toHaveBeenCalledWith(expect.stringContaining("/files/compositions%2Fsub.html")
|
|
701
|
+
expect(fetch).toHaveBeenCalledWith(expect.stringContaining("/files/compositions%2Fsub.html"), {
|
|
702
|
+
credentials: "omit",
|
|
703
|
+
});
|
|
645
704
|
expect(applySoftReload).toHaveBeenCalledWith(
|
|
646
705
|
expect.anything(),
|
|
647
706
|
NESTED_SCRIPT,
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
} from "../utils/gsapSoftReload";
|
|
12
12
|
import type { SoftReloadResult } from "../utils/gsapSoftReload";
|
|
13
13
|
import { trackStudioEvent } from "../utils/studioTelemetry";
|
|
14
|
+
import { trackKeyframeCommit } from "../utils/keyframeUsage";
|
|
14
15
|
import { serializeStudioFileMutation } from "../utils/studioFileMutationCoordinator";
|
|
15
16
|
import { trackedStudioEdit } from "../utils/studioPendingEdits";
|
|
16
17
|
import {
|
|
@@ -27,12 +28,13 @@ import {
|
|
|
27
28
|
formatGsapMutationRejectionToast,
|
|
28
29
|
readJsonResponseBody,
|
|
29
30
|
} from "./gsapScriptCommitHelpers";
|
|
30
|
-
import
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
31
|
+
import {
|
|
32
|
+
gsapWriteFile,
|
|
33
|
+
type CommitMutation,
|
|
34
|
+
type CommitMutationCall,
|
|
35
|
+
type CommitMutationOptions,
|
|
36
|
+
type GsapScriptCommitsParams,
|
|
37
|
+
type MutationResult,
|
|
36
38
|
} from "./gsapScriptCommitTypes";
|
|
37
39
|
import { useGsapAnimationOps } from "./useGsapAnimationOps";
|
|
38
40
|
import { useGsapArcPathOps } from "./useGsapArcPathOps";
|
|
@@ -43,13 +45,14 @@ import {
|
|
|
43
45
|
useSafeGsapCommitMutation,
|
|
44
46
|
} from "./useSafeGsapCommitMutation";
|
|
45
47
|
import { studioWriteHeaders } from "../utils/studioFileVersion";
|
|
48
|
+
import { studioApiFetch } from "../utils/studioApiFetch";
|
|
46
49
|
|
|
47
50
|
async function mutateGsapScript(
|
|
48
51
|
projectId: string,
|
|
49
52
|
sourceFile: string,
|
|
50
53
|
mutation: Record<string, unknown>,
|
|
51
54
|
): Promise<MutationResult> {
|
|
52
|
-
const res = await
|
|
55
|
+
const res = await studioApiFetch(
|
|
53
56
|
`/api/projects/${encodeURIComponent(projectId)}/gsap-mutations/${encodeURIComponent(sourceFile)}`,
|
|
54
57
|
{
|
|
55
58
|
method: "POST",
|
|
@@ -68,7 +71,7 @@ async function mutateGsapScriptBatch(
|
|
|
68
71
|
sourceFile: string,
|
|
69
72
|
mutations: Record<string, unknown>[],
|
|
70
73
|
): Promise<MutationResult> {
|
|
71
|
-
const res = await
|
|
74
|
+
const res = await studioApiFetch(
|
|
72
75
|
`/api/projects/${encodeURIComponent(projectId)}/gsap-mutations-batch/${encodeURIComponent(sourceFile)}`,
|
|
73
76
|
{
|
|
74
77
|
method: "POST",
|
|
@@ -391,6 +394,7 @@ export function useGsapScriptCommits({ projectIdRef, activeCompPath, previewIfra
|
|
|
391
394
|
mutateGsapScript(pid, targetPath, mutation),
|
|
392
395
|
);
|
|
393
396
|
if (!result) return;
|
|
397
|
+
trackKeyframeCommit([mutation], result, options);
|
|
394
398
|
options.onResult?.(result);
|
|
395
399
|
await finalizeSuccessfulMutation(pid, compositionPath, selection, mutation, targetPath, result, options);
|
|
396
400
|
}, [showToast, finalizeSuccessfulMutation]);
|
|
@@ -404,6 +408,7 @@ export function useGsapScriptCommits({ projectIdRef, activeCompPath, previewIfra
|
|
|
404
408
|
mutateGsapScriptBatch(pid, targetPath, mutations),
|
|
405
409
|
);
|
|
406
410
|
if (!result) return;
|
|
411
|
+
trackKeyframeCommit(mutations, result, options, calls.map((call) => call.options));
|
|
407
412
|
options.onResult?.(result);
|
|
408
413
|
// Each call brings its own fast-path patch; the batch wrote them all, so the
|
|
409
414
|
// preview sync applies them all rather than just the last call's.
|
|
@@ -439,14 +444,14 @@ export function useGsapScriptCommits({ projectIdRef, activeCompPath, previewIfra
|
|
|
439
444
|
};
|
|
440
445
|
const commit: CommitMutation = trackedStudioEdit((selection, mutation, options) => {
|
|
441
446
|
if (!activeProjectId) return Promise.resolve();
|
|
442
|
-
const file = selection
|
|
447
|
+
const file = gsapWriteFile(selection, activeCompPath);
|
|
443
448
|
return serializeCommit(file, options.serializeKey, () =>
|
|
444
449
|
runCommit(activeProjectId, activeCompPath, file, selection, mutation, options),
|
|
445
450
|
);
|
|
446
451
|
}) as CommitMutation;
|
|
447
452
|
commit.batch = trackedStudioEdit((calls: CommitMutationCall[], options: CommitMutationOptions) => {
|
|
448
453
|
if (!activeProjectId) return Promise.resolve();
|
|
449
|
-
const file = calls[0]
|
|
454
|
+
const file = calls[0] ? gsapWriteFile(calls[0].selection, activeCompPath) : "index.html";
|
|
450
455
|
return serializeCommit(file, options.serializeKey, () =>
|
|
451
456
|
runBatchCommit(activeProjectId, activeCompPath, file, calls, options),
|
|
452
457
|
);
|
|
@@ -42,7 +42,7 @@ function makeParams(overrides: Partial<Params> = {}): Params {
|
|
|
42
42
|
moveKeyframe: vi.fn().mockResolvedValue(true),
|
|
43
43
|
resizeKeyframedTween: vi.fn().mockResolvedValue(true),
|
|
44
44
|
convertToKeyframes: resolved(),
|
|
45
|
-
removeAllKeyframes:
|
|
45
|
+
removeAllKeyframes: vi.fn().mockResolvedValue(true),
|
|
46
46
|
handleDomManualEditsReset: vi.fn(),
|
|
47
47
|
selectedGsapAnimations: [],
|
|
48
48
|
showToast: vi.fn(),
|
|
@@ -109,9 +109,16 @@ export function useGsapSelectionHandlers({
|
|
|
109
109
|
duration?: number,
|
|
110
110
|
commitOverrides?: Partial<CommitMutationOptions>,
|
|
111
111
|
) => Promise<void>;
|
|
112
|
-
removeAllKeyframes: (
|
|
112
|
+
removeAllKeyframes: (
|
|
113
|
+
sel: DomEditSelection,
|
|
114
|
+
animId: string,
|
|
115
|
+
action?: "remove_all" | "reset",
|
|
116
|
+
telemetry?: boolean,
|
|
117
|
+
) => Promise<boolean>;
|
|
113
118
|
|
|
114
|
-
handleDomManualEditsReset: (
|
|
119
|
+
handleDomManualEditsReset: (
|
|
120
|
+
sel: DomEditSelection,
|
|
121
|
+
) => Promise<void | import("./domEditCommitTypes").DomEditPersistOutcome>;
|
|
115
122
|
selectedGsapAnimations: GsapAnimation[];
|
|
116
123
|
showToast: (message: string, tone?: "error" | "info") => void;
|
|
117
124
|
}) {
|
|
@@ -323,7 +330,6 @@ export function useGsapSelectionHandlers({
|
|
|
323
330
|
) => {
|
|
324
331
|
const sel = resolveWriteSelection(selectionOverride);
|
|
325
332
|
if (!sel) return;
|
|
326
|
-
trackStudioEvent("keyframe", { action: "add", property });
|
|
327
333
|
addKeyframe(sel, animId, percentage, property, value);
|
|
328
334
|
},
|
|
329
335
|
[resolveWriteSelection, addKeyframe],
|
|
@@ -441,31 +447,31 @@ export function useGsapSelectionHandlers({
|
|
|
441
447
|
);
|
|
442
448
|
|
|
443
449
|
const handleGsapRemoveAllKeyframes = useCallback(
|
|
444
|
-
(animId: string, selectionOverride?: DomEditSelection | null) => {
|
|
450
|
+
(animId: string, selectionOverride?: DomEditSelection | null, telemetry = true) => {
|
|
445
451
|
const selection = resolveWriteSelection(selectionOverride);
|
|
446
452
|
if (!selection) return Promise.resolve(false);
|
|
447
|
-
return
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
"Remove all keyframes",
|
|
452
|
-
);
|
|
453
|
+
return removeAllKeyframes(selection, animId, "remove_all", telemetry).catch((error) => {
|
|
454
|
+
trackGsapHandlerFailure(error, selection, "remove-all-keyframes", "Remove all keyframes");
|
|
455
|
+
return false;
|
|
456
|
+
});
|
|
453
457
|
},
|
|
454
|
-
[resolveWriteSelection,
|
|
458
|
+
[resolveWriteSelection, trackGsapHandlerFailure, removeAllKeyframes],
|
|
455
459
|
);
|
|
456
460
|
|
|
457
461
|
const handleResetSelectedElementKeyframes = useCallback((): boolean => {
|
|
458
462
|
if (!domEditSelection) return false;
|
|
459
463
|
const withKeyframes = selectedGsapAnimations.find((a) => a.keyframes);
|
|
460
464
|
if (!withKeyframes) return false;
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
465
|
+
void removeAllKeyframes(domEditSelection, withKeyframes.id, "reset").catch((error) => {
|
|
466
|
+
trackGsapHandlerFailure(
|
|
467
|
+
error,
|
|
468
|
+
domEditSelection,
|
|
469
|
+
"remove-all-keyframes",
|
|
470
|
+
"Remove all keyframes",
|
|
471
|
+
);
|
|
472
|
+
});
|
|
467
473
|
return true;
|
|
468
|
-
}, [domEditSelection,
|
|
474
|
+
}, [domEditSelection, trackGsapHandlerFailure, removeAllKeyframes, selectedGsapAnimations]);
|
|
469
475
|
|
|
470
476
|
return {
|
|
471
477
|
handleGsapUpdateProperty,
|
|
@@ -290,17 +290,15 @@ export function usePopulateKeyframeCacheForFile(
|
|
|
290
290
|
const domClipChildrenKey = usePlayerStore((s) =>
|
|
291
291
|
s.domClipChildren.map((c) => `${c.id}<${c.hostId}`).join("|"),
|
|
292
292
|
);
|
|
293
|
-
const
|
|
293
|
+
const loadedRef = useRef<{ dataKey: string; files: Set<string> }>({
|
|
294
|
+
dataKey: "",
|
|
295
|
+
files: new Set(),
|
|
296
|
+
});
|
|
294
297
|
|
|
295
298
|
const runtimeScanDoneRef = useRef("");
|
|
296
|
-
const astFetchDoneRef = useRef("");
|
|
297
299
|
|
|
298
300
|
useEffect(() => {
|
|
299
|
-
const
|
|
300
|
-
if (fetchKey === lastFetchKeyRef.current) return;
|
|
301
|
-
lastFetchKeyRef.current = fetchKey;
|
|
302
|
-
runtimeScanDoneRef.current = "";
|
|
303
|
-
astFetchDoneRef.current = "";
|
|
301
|
+
const dataKey = `kf-cache:${projectId}:${version}:${elementCount}:${domClipChildrenKey}:${compositionSrcKey}`;
|
|
304
302
|
if (!projectId) return;
|
|
305
303
|
|
|
306
304
|
// The active file first: it owns the selection, and each file clears only
|
|
@@ -309,13 +307,21 @@ export function usePopulateKeyframeCacheForFile(
|
|
|
309
307
|
const files = Array.from(
|
|
310
308
|
new Set([sourceFile, ...(compositionSrcKey ? compositionSrcKey.split("|") : [])]),
|
|
311
309
|
);
|
|
312
|
-
const
|
|
310
|
+
const sameData = loadedRef.current.dataKey === dataKey;
|
|
311
|
+
const stale = sameData ? files.filter((sf) => !loadedRef.current.files.has(sf)) : files;
|
|
312
|
+
const covered = sameData ? new Set([...loadedRef.current.files, ...files]) : new Set(files);
|
|
313
|
+
loadedRef.current = { dataKey, files: covered };
|
|
313
314
|
// Everything the previous scan cached for a file this one no longer covers
|
|
314
315
|
// (the composition just switched away from) has no owner left to clear it.
|
|
315
|
-
pruneKeyframeCacheToFiles(files);
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
316
|
+
if (!sameData) pruneKeyframeCacheToFiles(files);
|
|
317
|
+
if (stale.length === 0) return;
|
|
318
|
+
runtimeScanDoneRef.current = "";
|
|
319
|
+
const doc = iframeRef?.current?.contentDocument;
|
|
320
|
+
for (const sf of stale) {
|
|
321
|
+
void populateKeyframeCacheFromAst(projectId, sf, doc).then((loaded) => {
|
|
322
|
+
if (!loaded && loadedRef.current.dataKey === dataKey) loadedRef.current.files.delete(sf);
|
|
323
|
+
});
|
|
324
|
+
}
|
|
319
325
|
// elementCount is in the deps because new timeline elements (e.g. after a
|
|
320
326
|
// sub-composition expand) need their keyframe cache populated immediately;
|
|
321
327
|
// without it the effect won't re-run when elements appear/disappear.
|
|
@@ -3,6 +3,7 @@ import { useState, useCallback, useEffect, useRef, useMemo } from "react";
|
|
|
3
3
|
import type { LintFinding } from "../components/LintModal";
|
|
4
4
|
import { usePlayerStore } from "../player";
|
|
5
5
|
import { isPreviewBooted, whenPreviewBooted } from "../player/store/playerStore";
|
|
6
|
+
import { studioApiFetch } from "../utils/studioApiFetch";
|
|
6
7
|
|
|
7
8
|
interface RawFinding {
|
|
8
9
|
severity?: string;
|
|
@@ -25,7 +26,7 @@ function parseFinding(f: RawFinding): LintFinding & { elementId?: string; file?:
|
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
async function fetchLintFindings(projectId: string) {
|
|
28
|
-
const res = await
|
|
29
|
+
const res = await studioApiFetch(buildProjectApiPath(projectId, `/lint`));
|
|
29
30
|
const data = await res.json();
|
|
30
31
|
return ((data.findings ?? []) as RawFinding[]).map(parseFinding);
|
|
31
32
|
}
|