mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-04 07:19:52 +00:00
feat(studio): route GSAP tween add/update/delete through SDK (§3.5 PR1) (#1469)
Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com>
This commit is contained in:
co-authored by
Miguel Ángel
parent
e65c3c7918
commit
592f7c775d
@@ -4,6 +4,7 @@ import {
|
||||
sdkCutoverPersist,
|
||||
sdkDeletePersist,
|
||||
sdkTimingPersist,
|
||||
sdkGsapTweenPersist,
|
||||
} from "./sdkCutover";
|
||||
import { openComposition } from "@hyperframes/sdk";
|
||||
import { createMemoryAdapter } from "@hyperframes/sdk/adapters/memory";
|
||||
@@ -445,6 +446,116 @@ describe("sdkTimingPersist", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("sdkGsapTweenPersist", () => {
|
||||
const makeRef = <T>(val: T): MutableRefObject<T> => ({ current: val });
|
||||
const makeDeps = () => ({
|
||||
editHistory: { recordEdit: vi.fn().mockResolvedValue(undefined) },
|
||||
writeProjectFile: vi.fn().mockResolvedValue(undefined),
|
||||
reloadPreview: vi.fn(),
|
||||
domEditSaveTimestampRef: makeRef(0),
|
||||
});
|
||||
|
||||
const makeSession = (opts?: { addGsapTween?: string; hasEl?: boolean }) =>
|
||||
({
|
||||
getElement: vi.fn().mockReturnValue(opts?.hasEl !== false ? { id: "hf-box" } : null),
|
||||
addGsapTween: vi.fn().mockReturnValue(opts?.addGsapTween ?? "tw-1"),
|
||||
setGsapTween: vi.fn(),
|
||||
removeGsapTween: vi.fn(),
|
||||
serialize: vi
|
||||
.fn()
|
||||
.mockReturnValueOnce("<html>before</html>")
|
||||
.mockReturnValue("<html>after</html>"),
|
||||
}) as unknown as Parameters<typeof sdkGsapTweenPersist>[2];
|
||||
|
||||
it("returns false when session is null", async () => {
|
||||
expect(
|
||||
await sdkGsapTweenPersist(
|
||||
"/comp.html",
|
||||
{ kind: "remove", animationId: "tw-1" },
|
||||
null,
|
||||
makeDeps(),
|
||||
),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("calls addGsapTween and writes for kind=add", async () => {
|
||||
const deps = makeDeps();
|
||||
const session = makeSession();
|
||||
const result = await sdkGsapTweenPersist(
|
||||
"/comp.html",
|
||||
{
|
||||
kind: "add",
|
||||
target: "hf-box",
|
||||
spec: { method: "to", duration: 1, properties: { opacity: 1 } },
|
||||
},
|
||||
session,
|
||||
deps,
|
||||
);
|
||||
expect(result).toBe(true);
|
||||
expect(session!.addGsapTween).toHaveBeenCalledWith(
|
||||
"hf-box",
|
||||
expect.objectContaining({ method: "to" }),
|
||||
);
|
||||
expect(deps.writeProjectFile).toHaveBeenCalledWith("/comp.html", "<html>after</html>");
|
||||
});
|
||||
|
||||
it("returns false for kind=add when element not found", async () => {
|
||||
const deps = makeDeps();
|
||||
const session = makeSession({ hasEl: false });
|
||||
const result = await sdkGsapTweenPersist(
|
||||
"/comp.html",
|
||||
{ kind: "add", target: "hf-box", spec: { method: "to", properties: { x: 100 } } },
|
||||
session,
|
||||
deps,
|
||||
);
|
||||
expect(result).toBe(false);
|
||||
expect(deps.writeProjectFile).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("calls setGsapTween and writes for kind=set", async () => {
|
||||
const deps = makeDeps();
|
||||
const session = makeSession();
|
||||
const result = await sdkGsapTweenPersist(
|
||||
"/comp.html",
|
||||
{ kind: "set", animationId: "tw-1", properties: { ease: "power3.in" } },
|
||||
session,
|
||||
deps,
|
||||
);
|
||||
expect(result).toBe(true);
|
||||
expect(session!.setGsapTween).toHaveBeenCalledWith("tw-1", { ease: "power3.in" });
|
||||
expect(deps.reloadPreview).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("calls removeGsapTween for kind=remove", async () => {
|
||||
const deps = makeDeps();
|
||||
const session = makeSession();
|
||||
const result = await sdkGsapTweenPersist(
|
||||
"/comp.html",
|
||||
{ kind: "remove", animationId: "tw-1" },
|
||||
session,
|
||||
deps,
|
||||
);
|
||||
expect(result).toBe(true);
|
||||
expect(session!.removeGsapTween).toHaveBeenCalledWith("tw-1");
|
||||
});
|
||||
|
||||
it("returns false and does not write on SDK error", async () => {
|
||||
const deps = makeDeps();
|
||||
const session = makeSession();
|
||||
(session!.removeGsapTween as ReturnType<typeof vi.fn>).mockImplementation(() => {
|
||||
throw new Error("gsap error");
|
||||
});
|
||||
const result = await sdkGsapTweenPersist(
|
||||
"/comp.html",
|
||||
{ kind: "remove", animationId: "tw-1" },
|
||||
session,
|
||||
deps,
|
||||
);
|
||||
expect(result).toBe(false);
|
||||
expect(deps.writeProjectFile).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("sdkCutoverPersist — GSAP script preservation (integration)", () => {
|
||||
const makeRef = <T>(val: T): MutableRefObject<T> => ({ current: val });
|
||||
const makeDeps = () => ({
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { MutableRefObject } from "react";
|
||||
import type { Composition, EditOp } from "@hyperframes/sdk";
|
||||
import type { Composition, EditOp, GsapTweenSpec } from "@hyperframes/sdk";
|
||||
import type { DomEditSelection } from "../components/editor/domEditing";
|
||||
import type { EditHistoryKind } from "./editHistory";
|
||||
import type { PatchOperation } from "./sourcePatcher";
|
||||
@@ -157,6 +157,38 @@ export async function sdkTimingPersist(
|
||||
}
|
||||
}
|
||||
|
||||
type SdkGsapTweenOp =
|
||||
| { kind: "add"; target: string; spec: GsapTweenSpec }
|
||||
| { kind: "set"; animationId: string; properties: Partial<GsapTweenSpec> }
|
||||
| { kind: "remove"; animationId: string };
|
||||
|
||||
export async function sdkGsapTweenPersist(
|
||||
targetPath: string,
|
||||
op: SdkGsapTweenOp,
|
||||
sdkSession: Composition | null | undefined,
|
||||
deps: CutoverDeps,
|
||||
options?: CutoverOptions,
|
||||
): Promise<boolean> {
|
||||
if (!sdkSession) return false;
|
||||
try {
|
||||
const before = sdkSession.serialize();
|
||||
if (op.kind === "add") {
|
||||
if (!sdkSession.getElement(op.target)) return false;
|
||||
sdkSession.addGsapTween(op.target, op.spec);
|
||||
} else if (op.kind === "set") {
|
||||
sdkSession.setGsapTween(op.animationId, op.properties);
|
||||
} else {
|
||||
sdkSession.removeGsapTween(op.animationId);
|
||||
}
|
||||
await persistSdkSerialize(sdkSession, targetPath, before, deps, options);
|
||||
trackStudioEvent("sdk_cutover_success", { opCount: 1 });
|
||||
return true;
|
||||
} catch (err) {
|
||||
trackStudioEvent("sdk_cutover_fallback", { error: String(err) });
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function sdkDeletePersist(
|
||||
hfId: string,
|
||||
originalContent: string,
|
||||
|
||||
Reference in New Issue
Block a user