Skip to content

Commit b6bf1b1

Browse files
fix(core): split-into-property-groups and replace-with-keyframes mutations (#1355)
* fix(core): per-property-group keyframe foundations Add PropertyGroupName type system (position/scale/size/rotation/visual/other), PROPERTY_GROUPS constant, classifyPropertyGroup/classifyTweenPropertyGroup functions. Parser generates group-aware animation IDs, resolves position strings (+=, -=, <, >), uses numeric matching with 2% tolerance, and preserves IDs across all mutations. * fix(core): add split-into-property-groups and replace-with-keyframes mutations Server-side mutations for atomic property-group splitting and keyframe replacement. Client commitMutation returns early on changed:false instead of throwing.
1 parent 889e9f0 commit b6bf1b1

2 files changed

Lines changed: 49 additions & 4 deletions

File tree

packages/core/src/studio-api/routes/files.ts

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -409,13 +409,31 @@ type GsapMutationRequest =
409409
}>;
410410
ease?: string;
411411
}
412+
| {
413+
type: "replace-with-keyframes";
414+
animationId: string;
415+
targetSelector: string;
416+
position: number;
417+
duration: number;
418+
keyframes: Array<{
419+
percentage: number;
420+
properties: Record<string, number | string>;
421+
ease?: string;
422+
auto?: boolean;
423+
}>;
424+
ease?: string;
425+
}
412426
| {
413427
type: "split-animations";
414428
originalId: string;
415429
newId: string;
416430
splitTime: number;
417431
elementStart: number;
418432
elementDuration: number;
433+
}
434+
| {
435+
type: "split-into-property-groups";
436+
animationId: string;
419437
};
420438

421439
// ── GSAP mutation executor ──────────────────────────────────────────────────
@@ -445,6 +463,7 @@ async function executeGsapMutation(
445463
removeArcPathFromScript,
446464
addAnimationWithKeyframesToScript,
447465
splitAnimationsInScript,
466+
splitIntoPropertyGroups,
448467
} = parser;
449468

450469
function requireAnimation(
@@ -617,6 +636,18 @@ async function executeGsapMutation(
617636
);
618637
return result.script;
619638
}
639+
case "replace-with-keyframes": {
640+
const script = removeAnimationFromScript(block.scriptText, body.animationId);
641+
const added = addAnimationWithKeyframesToScript(
642+
script,
643+
body.targetSelector,
644+
body.position,
645+
body.duration,
646+
body.keyframes,
647+
body.ease,
648+
);
649+
return added.script;
650+
}
620651
case "split-animations": {
621652
if (
622653
typeof body.originalId !== "string" ||
@@ -647,6 +678,10 @@ async function executeGsapMutation(
647678
elementDuration: body.elementDuration,
648679
});
649680
}
681+
case "split-into-property-groups": {
682+
const result = splitIntoPropertyGroups(block.scriptText, body.animationId);
683+
return result.script;
684+
}
650685
default:
651686
return respond({ error: `unknown mutation type: ${(body as { type: string }).type}` }, 400);
652687
}
@@ -1061,15 +1096,17 @@ export function registerFileRoutes(api: Hono, adapter: StudioApiAdapter): void {
10611096
if (result instanceof Response) return result;
10621097

10631098
const newScript = typeof result === "string" ? result : result.script;
1064-
const newHtml = block.replaceScript(newScript);
1065-
if (newHtml !== html) {
1099+
const changed = newScript !== block.scriptText;
1100+
const newHtml = changed ? block.replaceScript(newScript) : html;
1101+
if (changed) {
10661102
writeFileSync(res.absPath, newHtml, "utf-8");
10671103
}
10681104

10691105
const { parseGsapScript } = await loadGsapParser();
10701106
const freshParsed = parseGsapScript(newScript);
10711107
const responsePayload: Record<string, unknown> = {
10721108
ok: true,
1109+
changed,
10731110
parsed: freshParsed,
10741111
before: html,
10751112
after: newHtml,

packages/studio/src/hooks/useGsapScriptCommits.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ function ensureElementAddressable(selection: DomEditSelection): {
5151

5252
interface MutationResult {
5353
ok: boolean;
54+
changed?: boolean;
5455
parsed?: ParsedGsap;
5556
before?: string;
5657
after?: string;
@@ -131,9 +132,16 @@ export function useGsapScriptCommits({
131132
const pid = projectIdRef.current;
132133
if (!pid) return;
133134
const targetPath = selection.sourceFile || activeCompPath || "index.html";
134-
135135
const result = await mutateGsapScript(pid, targetPath, mutation);
136-
if (!result?.ok) return;
136+
if (!result) {
137+
if (options.skipReload) return;
138+
throw new Error(`Mutation failed: ${mutation.type}`);
139+
}
140+
141+
if (result.changed === false) {
142+
if (options.skipReload) return;
143+
return;
144+
}
137145

138146
domEditSaveTimestampRef.current = Date.now();
139147

0 commit comments

Comments
 (0)