@hyperframes/studio 0.8.75 → 0.8.77
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-BSDTJ9EF.js +464 -0
- package/dist/assets/{index-BlUYw1MB.js → index-2GgHjQq-.js} +163 -163
- package/dist/assets/{index-0w6iIlwZ.css → index-BWJqxeaH.css} +1 -1
- package/dist/assets/{index-BiFKcNjs.js → index-CsFCRdMo.js} +1 -1
- package/dist/assets/{index-HjexPsny.js → index-DcxWQ7Za.js} +1 -1
- package/dist/{chunk-7BP32G62.js → chunk-5VANA2TG.js} +12 -1
- package/dist/chunk-5VANA2TG.js.map +1 -0
- package/dist/{domEditingLayers-UKMVYXT6.js → domEditingLayers-IGPLTI5E.js} +4 -2
- package/dist/index.d.ts +34 -69
- package/dist/index.html +2 -2
- package/dist/index.js +5287 -5277
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +7 -7
- package/src/components/StudioRightPanels.types.ts +0 -2
- package/src/components/editor/DomEditOverlay.test.ts +5 -0
- package/src/components/editor/LayersPanel.tsx +3 -14
- package/src/components/editor/PreviewOverlayProvider.test.tsx +6 -1
- package/src/components/editor/domEditing.ts +1 -0
- package/src/components/editor/domEditingElement.ts +13 -0
- package/src/components/editor/domEditingLayers.test.ts +27 -0
- package/src/components/editor/domEditingLayers.ts +9 -0
- package/src/components/editor/gsapLivePreview.test.ts +63 -0
- package/src/components/editor/gsapLivePreview.ts +2 -14
- package/src/components/editor/offCanvasIndicatorRefresh.test.tsx +57 -35
- package/src/components/editor/overlayFrameLoop.test.ts +16 -0
- package/src/components/editor/overlayFrameLoop.ts +11 -0
- package/src/components/editor/propertyPanelHelpers.ts +4 -6
- package/src/components/editor/usePreviewCompositionRect.test.tsx +6 -1
- package/src/components/nle/NLEPreview.test.ts +202 -22
- package/src/components/nle/NLEPreview.tsx +65 -46
- package/src/components/nle/PreviewPoster.tsx +99 -0
- package/src/components/nle/PreviewZoomOverlay.tsx +111 -0
- package/src/components/nle/previewZoom.test.ts +42 -0
- package/src/components/nle/previewZoom.ts +33 -0
- package/src/components/nle/useCanvasZOrderTimelineMirror.test.tsx +30 -49
- package/src/components/panels/VariablesPanel.tsx +0 -2
- package/src/components/sidebar/CompositionsTab.drag.test.tsx +5 -1
- package/src/components/sidebar/CompositionsTab.rootBadge.test.tsx +14 -0
- package/src/components/sidebar/CompositionsTab.test.ts +4 -6
- package/src/components/sidebar/CompositionsTab.tsx +28 -21
- package/src/components/studioColorGradingScope.ts +0 -3
- package/src/hooks/domEditDeleteMembers.ts +0 -2
- package/src/hooks/gsapScriptCommitTypes.ts +0 -2
- package/src/hooks/keyframeCacheAstLoad.test.ts +31 -1
- package/src/hooks/keyframeCacheAstLoad.ts +2 -1
- package/src/hooks/timelineAudioGroupCreate.ts +0 -1
- package/src/hooks/timelineEditingHelpers.ts +0 -3
- package/src/hooks/timelineTimingSync.ts +0 -1
- package/src/hooks/timelineTrackVisibility.ts +0 -1
- package/src/hooks/useBlockHandlers.ts +0 -2
- package/src/hooks/useClipboard.test.ts +69 -10
- package/src/hooks/useClipboard.ts +28 -19
- package/src/hooks/useDomEditCommits.test.tsx +0 -1
- package/src/hooks/useDomEditCommits.ts +0 -4
- package/src/hooks/useDomEditWiring.ts +1 -3
- package/src/hooks/useEditHistoryActions.test.tsx +31 -14
- package/src/hooks/useEditHistoryActions.ts +12 -7
- package/src/hooks/useEditorSave.ts +0 -3
- package/src/hooks/useElementLifecycleOps.ts +0 -1
- package/src/hooks/useEnableKeyframes.test.ts +9 -1
- package/src/hooks/useFileManager.ts +0 -2
- package/src/hooks/useGestureRecording.test.tsx +1 -1
- package/src/hooks/useGestureRecording.ts +6 -10
- package/src/hooks/useGroupCommits.ts +0 -1
- package/src/hooks/useGsapScriptCommits.ts +0 -1
- package/src/hooks/useGsapTweenCache.element.test.tsx +39 -0
- package/src/hooks/useGsapTweenCache.ts +5 -13
- package/src/hooks/useLintModal.test.tsx +34 -1
- package/src/hooks/useLintModal.ts +32 -30
- package/src/hooks/usePersistentEditHistory.test.ts +221 -315
- package/src/hooks/usePersistentEditHistory.ts +155 -342
- package/src/hooks/usePopulateKeyframeCacheForFile.test.tsx +2 -0
- package/src/hooks/usePreviewPersistence.ts +0 -2
- package/src/hooks/useProjectCompositionVariables.ts +1 -3
- package/src/hooks/useRazorSplit.history.test.tsx +46 -67
- package/src/hooks/useRazorSplit.test.ts +17 -47
- package/src/hooks/useRenderClipContent.test.ts +29 -0
- package/src/hooks/useRenderClipContent.ts +6 -3
- package/src/hooks/useSdkSession.lifecycle.test.tsx +34 -4
- package/src/hooks/useSdkSession.ts +8 -2
- package/src/hooks/useSlideshowPersist.ts +0 -2
- package/src/hooks/useStudioContextValue.ts +6 -1
- package/src/hooks/useTimelineAssetDropOps.ts +0 -1
- package/src/hooks/useTimelineDeleteOps.ts +0 -1
- package/src/hooks/useTimelineEditing.test.tsx +0 -1
- package/src/hooks/useTimelineEditingTypes.ts +0 -2
- package/src/hostMount.test.tsx +53 -22
- package/src/main.tsx +3 -0
- package/src/player/components/CompositionThumbnail.tsx +14 -0
- package/src/player/components/Player.test.ts +49 -0
- package/src/player/components/Player.tsx +21 -7
- package/src/player/components/timelineClipDragCommit.test.ts +7 -39
- package/src/player/hooks/timelineDomBinding.test.ts +50 -0
- package/src/player/hooks/timelineSyncHydration.test.ts +25 -1
- package/src/player/hooks/timelineSyncHydration.ts +3 -3
- package/src/player/hooks/useShadowPreviewReload.ts +1 -0
- package/src/player/hooks/useTimelinePlayer.play.test.ts +120 -0
- package/src/player/hooks/useTimelinePlayer.ts +1 -0
- package/src/player/hooks/useTimelineSyncCallbacks.ts +2 -0
- package/src/player/lib/automationStoreSync.test.ts +24 -0
- package/src/player/lib/automationStoreSync.ts +3 -9
- package/src/player/lib/playbackScrub.ts +1 -1
- package/src/player/lib/timelineDOM.test.ts +28 -0
- package/src/player/lib/timelineDOM.ts +4 -2
- package/src/player/lib/timelineElementHelpers.ts +78 -9
- package/src/player/lib/timelineIframeHelpers.test.ts +49 -5
- package/src/player/lib/timelineIframeHelpers.ts +13 -12
- package/src/player/store/playerStore.test.ts +35 -1
- package/src/player/store/playerStore.ts +29 -0
- package/src/player/store/readinessSlice.ts +34 -6
- package/src/utils/blockInstaller.ts +0 -3
- package/src/utils/previewPrefetch.test.ts +22 -0
- package/src/utils/previewPrefetch.ts +14 -0
- package/src/utils/razorSplitTransaction.test.ts +0 -1
- package/src/utils/razorSplitTransaction.ts +1 -1
- package/src/utils/sdkEditTransaction.ts +0 -3
- package/src/utils/studioFileHistory.test.ts +0 -9
- package/src/utils/studioFileHistory.ts +1 -5
- package/src/utils/studioUiPreferences.test.ts +9 -3
- package/src/utils/studioUiPreferences.ts +0 -20
- package/src/utils/timelineCompositionInsert.ts +0 -1
- package/dist/assets/hyperframes-player-WbBVbWuO.js +0 -464
- package/dist/chunk-7BP32G62.js.map +0 -1
- package/src/hooks/usePersistentEditHistory.projectOwnership.test.tsx +0 -60
- package/src/utils/editHistory.test.ts +0 -534
- package/src/utils/editHistory.ts +0 -236
- package/src/utils/editHistoryStorage.test.ts +0 -37
- package/src/utils/editHistoryStorage.ts +0 -99
- /package/dist/{domEditingLayers-UKMVYXT6.js.map → domEditingLayers-IGPLTI5E.js.map} +0 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
import { act } from "react";
|
|
3
|
+
import { expect, it, vi } from "vitest";
|
|
4
|
+
import type { GsapAnimation } from "@hyperframes/core/gsap-parser";
|
|
5
|
+
import type { DomEditSelection } from "../components/editor/domEditingTypes";
|
|
6
|
+
import { installReactActEnvironment, mountReactHarness } from "./domSelectionTestHarness";
|
|
7
|
+
|
|
8
|
+
installReactActEnvironment();
|
|
9
|
+
|
|
10
|
+
const dotTween = {
|
|
11
|
+
id: "dots",
|
|
12
|
+
targetSelector: ".dot",
|
|
13
|
+
method: "from",
|
|
14
|
+
position: 0,
|
|
15
|
+
properties: { opacity: 0 },
|
|
16
|
+
} as GsapAnimation;
|
|
17
|
+
vi.mock("./keyframeCacheAstLoad", async (importOriginal) => ({
|
|
18
|
+
...(await importOriginal<typeof import("./keyframeCacheAstLoad")>()),
|
|
19
|
+
fetchParsedAnimations: async () => ({ animations: [dotTween] }),
|
|
20
|
+
}));
|
|
21
|
+
const { useGsapAnimationsForElement } = await import("./useGsapTweenCache");
|
|
22
|
+
|
|
23
|
+
it("attributes a class tween by the selected element, not an earlier same-id copy in a sub-composition", async () => {
|
|
24
|
+
document.body.innerHTML =
|
|
25
|
+
'<div data-composition-id="strip" data-composition-src="compositions/strip.html"><div id="card"></div></div>' +
|
|
26
|
+
'<div id="card" class="dot"></div>';
|
|
27
|
+
const element = document.querySelectorAll<HTMLElement>("#card")[1];
|
|
28
|
+
const selection = { id: "card", sourceFile: "index.html", element } as DomEditSelection;
|
|
29
|
+
const iframeRef = { current: { contentDocument: document } as unknown as HTMLIFrameElement };
|
|
30
|
+
let animations: GsapAnimation[] = [];
|
|
31
|
+
function Harness() {
|
|
32
|
+
animations = useGsapAnimationsForElement("p", "index.html", selection, 0, iframeRef).animations;
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
const root = mountReactHarness(<Harness />);
|
|
36
|
+
await act(async () => {});
|
|
37
|
+
expect(animations.map((animation) => animation.id)).toEqual(["dots"]);
|
|
38
|
+
act(() => root.unmount());
|
|
39
|
+
});
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { useEffect, useMemo, useRef, useState, useCallback } from "react";
|
|
2
2
|
import type { GsapAnimation, GsapKeyframesData } from "@hyperframes/core/gsap-parser";
|
|
3
3
|
import { usePlayerStore } from "../player/store/playerStore";
|
|
4
|
+
import type { DomEditSelection } from "../components/editor/domEditingTypes";
|
|
5
|
+
import { findPreviewNode } from "../components/editor/domEditingElement";
|
|
4
6
|
import { readRuntimeKeyframes, scanAllRuntimeKeyframes } from "./gsapRuntimeBridge";
|
|
5
7
|
import {
|
|
6
8
|
clearKeyframeCacheForElement,
|
|
@@ -73,7 +75,7 @@ export function getAnimationsForElement(
|
|
|
73
75
|
export function useGsapAnimationsForElement(
|
|
74
76
|
projectId: string | null,
|
|
75
77
|
sourceFile: string,
|
|
76
|
-
target:
|
|
78
|
+
target: DomEditSelection | null,
|
|
77
79
|
version: number,
|
|
78
80
|
iframeRef?: React.RefObject<HTMLIFrameElement | null>,
|
|
79
81
|
): {
|
|
@@ -157,24 +159,14 @@ export function useGsapAnimationsForElement(
|
|
|
157
159
|
// gsap.from(".dot", {stagger})) attribute to every matching element, not
|
|
158
160
|
// just the one whose exact selector equals the tween's. `version` re-runs
|
|
159
161
|
// this after composition reloads.
|
|
160
|
-
|
|
161
|
-
const doc = iframeRef?.current?.contentDocument;
|
|
162
|
-
if (doc) {
|
|
163
|
-
try {
|
|
164
|
-
element =
|
|
165
|
-
(targetId ? doc.getElementById(targetId) : null) ??
|
|
166
|
-
(targetSelector ? doc.querySelector(targetSelector) : null);
|
|
167
|
-
} catch {
|
|
168
|
-
element = null;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
162
|
+
const element = target ? findPreviewNode(iframeRef?.current?.contentDocument, target) : null;
|
|
171
163
|
return getAnimationsForElement(
|
|
172
164
|
allAnimations,
|
|
173
165
|
{ id: targetId, selector: targetSelector },
|
|
174
166
|
element,
|
|
175
167
|
);
|
|
176
168
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
177
|
-
}, [allAnimations,
|
|
169
|
+
}, [allAnimations, target, version, iframeRef]);
|
|
178
170
|
|
|
179
171
|
// fallow-ignore-next-line complexity
|
|
180
172
|
const animations = useMemo(() => {
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
import { act } from "react";
|
|
4
4
|
import { createRoot, type Root } from "react-dom/client";
|
|
5
|
-
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
6
6
|
import { useLintModal } from "./useLintModal";
|
|
7
|
+
import { usePlayerStore } from "../player/store/playerStore";
|
|
7
8
|
|
|
8
9
|
(globalThis as unknown as { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
|
9
10
|
|
|
@@ -15,6 +16,10 @@ function Probe() {
|
|
|
15
16
|
return null;
|
|
16
17
|
}
|
|
17
18
|
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
usePlayerStore.setState({ timelineProjectId: "demo", previewBooted: true });
|
|
21
|
+
});
|
|
22
|
+
|
|
18
23
|
afterEach(() => {
|
|
19
24
|
if (root) act(() => root?.unmount());
|
|
20
25
|
root = null;
|
|
@@ -63,3 +68,31 @@ describe("useLintModal hasLintError", () => {
|
|
|
63
68
|
expect(latest?.hasLintError).toBe(true);
|
|
64
69
|
});
|
|
65
70
|
});
|
|
71
|
+
|
|
72
|
+
describe("useLintModal background lint", () => {
|
|
73
|
+
it("waits for the live preview to boot before asking the server to lint", async () => {
|
|
74
|
+
usePlayerStore.setState({ previewBooted: false });
|
|
75
|
+
const fetchStub = vi.fn(lintResponse([]));
|
|
76
|
+
vi.stubGlobal("fetch", fetchStub);
|
|
77
|
+
root = createRoot(document.createElement("div"));
|
|
78
|
+
await act(async () => root?.render(<Probe />));
|
|
79
|
+
expect(fetchStub).not.toHaveBeenCalled();
|
|
80
|
+
|
|
81
|
+
await act(async () => usePlayerStore.getState().markPreviewBooted());
|
|
82
|
+
expect(fetchStub).toHaveBeenCalledTimes(1);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("never lints a project the user left before its preview booted", async () => {
|
|
86
|
+
usePlayerStore.setState({ previewBooted: false });
|
|
87
|
+
const fetchStub = vi.fn(lintResponse([]));
|
|
88
|
+
vi.stubGlobal("fetch", fetchStub);
|
|
89
|
+
root = createRoot(document.createElement("div"));
|
|
90
|
+
await act(async () => root?.render(<Probe />));
|
|
91
|
+
|
|
92
|
+
await act(async () => {
|
|
93
|
+
usePlayerStore.getState().beginTimelineSession("another-project");
|
|
94
|
+
usePlayerStore.getState().markPreviewBooted();
|
|
95
|
+
});
|
|
96
|
+
expect(fetchStub).not.toHaveBeenCalled();
|
|
97
|
+
});
|
|
98
|
+
});
|
|
@@ -2,6 +2,7 @@ import { buildProjectApiPath } from "../utils/projectRouting";
|
|
|
2
2
|
import { useState, useCallback, useEffect, useRef, useMemo } from "react";
|
|
3
3
|
import type { LintFinding } from "../components/LintModal";
|
|
4
4
|
import { usePlayerStore } from "../player";
|
|
5
|
+
import { isPreviewBooted, whenPreviewBooted } from "../player/store/playerStore";
|
|
5
6
|
|
|
6
7
|
interface RawFinding {
|
|
7
8
|
severity?: string;
|
|
@@ -23,6 +24,12 @@ function parseFinding(f: RawFinding): LintFinding & { elementId?: string; file?:
|
|
|
23
24
|
};
|
|
24
25
|
}
|
|
25
26
|
|
|
27
|
+
async function fetchLintFindings(projectId: string) {
|
|
28
|
+
const res = await fetch(buildProjectApiPath(projectId, `/lint`));
|
|
29
|
+
const data = await res.json();
|
|
30
|
+
return ((data.findings ?? []) as RawFinding[]).map(parseFinding);
|
|
31
|
+
}
|
|
32
|
+
|
|
26
33
|
export function useLintModal(projectId: string | null, refreshKey?: number) {
|
|
27
34
|
const [lintModal, setLintModal] = useState<LintFinding[] | null>(null);
|
|
28
35
|
const [linting, setLinting] = useState(false);
|
|
@@ -31,33 +38,28 @@ export function useLintModal(projectId: string | null, refreshKey?: number) {
|
|
|
31
38
|
>([]);
|
|
32
39
|
const autoLintRanRef = useRef(false);
|
|
33
40
|
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const parsed = ((data.findings ?? []) as RawFinding[]).map(parseFinding);
|
|
42
|
-
if (opts?.background) {
|
|
43
|
-
setBackgroundFindings(parsed);
|
|
44
|
-
} else {
|
|
45
|
-
setLintModal(parsed);
|
|
46
|
-
setBackgroundFindings(parsed);
|
|
47
|
-
}
|
|
48
|
-
} catch (err) {
|
|
49
|
-
if (!opts?.background) {
|
|
50
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
51
|
-
setLintModal([{ severity: "error", message: `Failed to run lint: ${msg}` }]);
|
|
52
|
-
}
|
|
53
|
-
} finally {
|
|
54
|
-
if (!opts?.background) setLinting(false);
|
|
55
|
-
}
|
|
56
|
-
},
|
|
57
|
-
[projectId],
|
|
58
|
-
);
|
|
41
|
+
const runBackgroundLint = useCallback(async () => {
|
|
42
|
+
if (!projectId) return;
|
|
43
|
+
if (!isPreviewBooted(projectId) && !(await whenPreviewBooted(projectId))) return;
|
|
44
|
+
try {
|
|
45
|
+
setBackgroundFindings(await fetchLintFindings(projectId));
|
|
46
|
+
} catch {}
|
|
47
|
+
}, [projectId]);
|
|
59
48
|
|
|
60
|
-
const handleLint = useCallback(() =>
|
|
49
|
+
const handleLint = useCallback(async () => {
|
|
50
|
+
if (!projectId) return;
|
|
51
|
+
setLinting(true);
|
|
52
|
+
try {
|
|
53
|
+
const parsed = await fetchLintFindings(projectId);
|
|
54
|
+
setLintModal(parsed);
|
|
55
|
+
setBackgroundFindings(parsed);
|
|
56
|
+
} catch (err) {
|
|
57
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
58
|
+
setLintModal([{ severity: "error", message: `Failed to run lint: ${msg}` }]);
|
|
59
|
+
} finally {
|
|
60
|
+
setLinting(false);
|
|
61
|
+
}
|
|
62
|
+
}, [projectId]);
|
|
61
63
|
|
|
62
64
|
const prevProjectIdRef = useRef(projectId);
|
|
63
65
|
useEffect(() => {
|
|
@@ -67,14 +69,14 @@ export function useLintModal(projectId: string | null, refreshKey?: number) {
|
|
|
67
69
|
}
|
|
68
70
|
if (!projectId || autoLintRanRef.current) return;
|
|
69
71
|
autoLintRanRef.current = true;
|
|
70
|
-
void
|
|
71
|
-
}, [projectId,
|
|
72
|
+
void runBackgroundLint();
|
|
73
|
+
}, [projectId, runBackgroundLint]);
|
|
72
74
|
|
|
73
75
|
useEffect(() => {
|
|
74
76
|
if (!projectId || !refreshKey) return;
|
|
75
|
-
const timer = setTimeout(() => void
|
|
77
|
+
const timer = setTimeout(() => void runBackgroundLint(), 1000);
|
|
76
78
|
return () => clearTimeout(timer);
|
|
77
|
-
}, [projectId, refreshKey,
|
|
79
|
+
}, [projectId, refreshKey, runBackgroundLint]);
|
|
78
80
|
|
|
79
81
|
const closeLintModal = useCallback(() => setLintModal(null), []);
|
|
80
82
|
|