mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-04 16:42:27 +00:00
feat(studio): scale GSAP positions on clip resize + shift on drag + diamond fixes (#1448)
Resize: proportionally scale all GSAP animation positions and durations to fit the new clip duration via scalePositionsInScript. This preserves clip-relative keyframe percentages — diamonds don't move during resize, nothing disappears. Modeled after After Effects Time Stretch behavior. Drag: shift all GSAP positions by the time delta (unchanged from before). Diamond rendering: - Clamp diamonds at 0%/100% so they stay fully visible at clip edges - Filter out-of-range keyframes using predicted percentages during resize - Clamp connection lines to clip boundaries - PropertyRows: same edge clamping for SVG diamonds Parser: scalePositionsInScript (proportional position + duration scaling), shiftPositionsInScript (rigid shift), scale-positions + shift-positions mutation types, 5 shift tests passing.
This commit is contained in:
@@ -144,5 +144,66 @@ export async function readFileContent(projectId: string, targetPath: string): Pr
|
||||
return data.content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shift all GSAP animation positions targeting a given element by a time delta.
|
||||
* Calls the server-side GSAP mutation endpoint which uses the AST-based parser.
|
||||
*/
|
||||
export async function shiftGsapPositions(
|
||||
projectId: string,
|
||||
filePath: string,
|
||||
elementId: string,
|
||||
delta: number,
|
||||
): Promise<void> {
|
||||
if (delta === 0 || !elementId) return;
|
||||
const res = await fetch(
|
||||
`/api/projects/${projectId}/gsap-mutations/${encodeURIComponent(filePath)}`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
type: "shift-positions",
|
||||
targetSelector: `#${elementId}`,
|
||||
delta,
|
||||
}),
|
||||
},
|
||||
);
|
||||
if (!res.ok) {
|
||||
const err = await res.json().catch(() => null);
|
||||
throw new Error((err as { error?: string })?.error ?? "shift-positions failed");
|
||||
}
|
||||
}
|
||||
|
||||
export async function scaleGsapPositions(
|
||||
projectId: string,
|
||||
filePath: string,
|
||||
elementId: string,
|
||||
oldStart: number,
|
||||
oldDuration: number,
|
||||
newStart: number,
|
||||
newDuration: number,
|
||||
): Promise<void> {
|
||||
if (!elementId || oldDuration <= 0 || newDuration <= 0) return;
|
||||
if (oldStart === newStart && oldDuration === newDuration) return;
|
||||
const res = await fetch(
|
||||
`/api/projects/${projectId}/gsap-mutations/${encodeURIComponent(filePath)}`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
type: "scale-positions",
|
||||
targetSelector: `#${elementId}`,
|
||||
oldStart,
|
||||
oldDuration,
|
||||
newStart,
|
||||
newDuration,
|
||||
}),
|
||||
},
|
||||
);
|
||||
if (!res.ok) {
|
||||
const err = await res.json().catch(() => null);
|
||||
throw new Error((err as { error?: string })?.error ?? "scale-positions failed");
|
||||
}
|
||||
}
|
||||
|
||||
// Re-export applyPatchByTarget for use in the hook (avoids double import in callers)
|
||||
export { applyPatchByTarget, formatTimelineAttributeNumber };
|
||||
|
||||
Reference in New Issue
Block a user