@hyperframes/core 0.6.88 → 0.6.89
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/generated/runtime-inline.js +1 -1
- package/dist/generated/runtime-inline.js.map +1 -1
- package/dist/hyperframe.manifest.json +1 -1
- package/dist/hyperframe.runtime.iife.js +1 -1
- package/dist/hyperframe.runtime.mjs +1 -1
- package/dist/lint/rules/gsap.d.ts.map +1 -1
- package/dist/lint/rules/gsap.js +26 -0
- package/dist/lint/rules/gsap.js.map +1 -1
- package/dist/parsers/gsapParser.d.ts.map +1 -1
- package/dist/parsers/gsapParser.js +30 -15
- package/dist/parsers/gsapParser.js.map +1 -1
- package/dist/studio-api/routes/files.d.ts.map +1 -1
- package/dist/studio-api/routes/files.js +302 -225
- package/dist/studio-api/routes/files.js.map +1 -1
- package/dist/studio-api/routes/preview.d.ts.map +1 -1
- package/dist/studio-api/routes/preview.js +7 -5
- package/dist/studio-api/routes/preview.js.map +1 -1
- package/package.json +1 -1
|
@@ -122,11 +122,6 @@ function updateReferences(projectDir, oldPath, newPath) {
|
|
|
122
122
|
*/
|
|
123
123
|
function extractGsapScriptBlock(html) {
|
|
124
124
|
const { document } = parseHTML(html);
|
|
125
|
-
// linkedom's querySelectorAll doesn't descend into <template> content, but
|
|
126
|
-
// sub-compositions wrap their markup (and the GSAP <script>) in a <template>.
|
|
127
|
-
// Search top-level scripts first, then each template's own scripts. Operate
|
|
128
|
-
// on the template element directly (NOT .content) so textContent writes are
|
|
129
|
-
// reflected in document.toString().
|
|
130
125
|
const scripts = [
|
|
131
126
|
...document.querySelectorAll("script:not([src])"),
|
|
132
127
|
...Array.from(document.querySelectorAll("template")).flatMap((tmpl) => Array.from(tmpl.querySelectorAll("script:not([src])"))),
|
|
@@ -138,6 +133,7 @@ function extractGsapScriptBlock(html) {
|
|
|
138
133
|
content.includes(".to(")) {
|
|
139
134
|
return {
|
|
140
135
|
scriptText: content,
|
|
136
|
+
document,
|
|
141
137
|
replaceScript(newText) {
|
|
142
138
|
script.textContent = newText;
|
|
143
139
|
return document.toString();
|
|
@@ -147,10 +143,279 @@ function extractGsapScriptBlock(html) {
|
|
|
147
143
|
}
|
|
148
144
|
return null;
|
|
149
145
|
}
|
|
146
|
+
function stripStudioEditsFromTarget(document, selector) {
|
|
147
|
+
if (!selector)
|
|
148
|
+
return 0;
|
|
149
|
+
let stripped = 0;
|
|
150
|
+
try {
|
|
151
|
+
for (const el of document.querySelectorAll(selector)) {
|
|
152
|
+
if (!el.getAttribute("data-hf-studio-path-offset"))
|
|
153
|
+
continue;
|
|
154
|
+
const htmlEl = el;
|
|
155
|
+
const originalTranslate = el.getAttribute("data-hf-studio-original-inline-translate");
|
|
156
|
+
htmlEl.style.removeProperty("--hf-studio-offset-x");
|
|
157
|
+
htmlEl.style.removeProperty("--hf-studio-offset-y");
|
|
158
|
+
if (originalTranslate) {
|
|
159
|
+
htmlEl.style.setProperty("translate", originalTranslate);
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
htmlEl.style.removeProperty("translate");
|
|
163
|
+
}
|
|
164
|
+
el.removeAttribute("data-hf-studio-path-offset");
|
|
165
|
+
el.removeAttribute("data-hf-studio-original-translate");
|
|
166
|
+
el.removeAttribute("data-hf-studio-original-inline-translate");
|
|
167
|
+
stripped++;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
// Invalid selector — skip silently.
|
|
172
|
+
}
|
|
173
|
+
return stripped;
|
|
174
|
+
}
|
|
175
|
+
function bakeVisibilityOnDelete(document, anim) {
|
|
176
|
+
let finalOpacity;
|
|
177
|
+
if (anim.method === "from") {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
if (anim.keyframes) {
|
|
181
|
+
const kfs = anim.keyframes.keyframes;
|
|
182
|
+
for (let i = kfs.length - 1; i >= 0; i--) {
|
|
183
|
+
if ("opacity" in kfs[i].properties) {
|
|
184
|
+
finalOpacity = kfs[i].properties.opacity;
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
else if ("opacity" in anim.properties) {
|
|
190
|
+
finalOpacity = anim.properties.opacity;
|
|
191
|
+
}
|
|
192
|
+
if (finalOpacity == null) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
if (typeof finalOpacity === "string" && /^[+\-*]=/.test(finalOpacity)) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
const numOpacity = Number(finalOpacity);
|
|
199
|
+
if (!Number.isFinite(numOpacity) || numOpacity === 0)
|
|
200
|
+
return;
|
|
201
|
+
try {
|
|
202
|
+
for (const el of document.querySelectorAll(anim.targetSelector)) {
|
|
203
|
+
el.style.setProperty("opacity", String(numOpacity));
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
// Invalid selector — skip silently.
|
|
208
|
+
}
|
|
209
|
+
}
|
|
150
210
|
/** Lazy-load gsapParser to avoid pulling recast into every file-route import. */
|
|
151
211
|
async function loadGsapParser() {
|
|
152
212
|
return import("../../parsers/gsapParser.js");
|
|
153
213
|
}
|
|
214
|
+
// ── GSAP mutation executor ──────────────────────────────────────────────────
|
|
215
|
+
async function executeGsapMutation(body, block, respond) {
|
|
216
|
+
const parser = await loadGsapParser();
|
|
217
|
+
const { parseGsapScript, updateAnimationInScript, addAnimationToScript, removeAnimationFromScript, addKeyframeToScript, removeKeyframeFromScript, updateKeyframeInScript, convertToKeyframesInScript, removeAllKeyframesFromScript, materializeKeyframesInScript, unrollDynamicAnimations, setArcPathInScript, updateArcSegmentInScript, removeArcPathFromScript, addAnimationWithKeyframesToScript, } = parser;
|
|
218
|
+
function requireAnimation(scriptText, animationId) {
|
|
219
|
+
const parsed = parseGsapScript(scriptText);
|
|
220
|
+
const anim = parsed.animations.find((a) => a.id === animationId);
|
|
221
|
+
if (!anim)
|
|
222
|
+
return { err: respond({ error: "animation not found" }, 404) };
|
|
223
|
+
return { anim };
|
|
224
|
+
}
|
|
225
|
+
function requireFromToAnimation(scriptText, animationId) {
|
|
226
|
+
const result = requireAnimation(scriptText, animationId);
|
|
227
|
+
if ("err" in result)
|
|
228
|
+
return result;
|
|
229
|
+
if (result.anim.method !== "fromTo")
|
|
230
|
+
return { err: respond({ error: "animation is not a fromTo" }, 400) };
|
|
231
|
+
return result;
|
|
232
|
+
}
|
|
233
|
+
switch (body.type) {
|
|
234
|
+
case "update-property": {
|
|
235
|
+
const r = requireAnimation(block.scriptText, body.animationId);
|
|
236
|
+
if ("err" in r)
|
|
237
|
+
return r.err;
|
|
238
|
+
return updateAnimationInScript(block.scriptText, body.animationId, {
|
|
239
|
+
properties: { ...r.anim.properties, [body.property]: body.value },
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
case "update-from-property": {
|
|
243
|
+
const r = requireFromToAnimation(block.scriptText, body.animationId);
|
|
244
|
+
if ("err" in r)
|
|
245
|
+
return r.err;
|
|
246
|
+
return updateAnimationInScript(block.scriptText, body.animationId, {
|
|
247
|
+
fromProperties: { ...(r.anim.fromProperties ?? {}), [body.property]: body.value },
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
case "update-meta": {
|
|
251
|
+
return updateAnimationInScript(block.scriptText, body.animationId, body.updates);
|
|
252
|
+
}
|
|
253
|
+
case "add": {
|
|
254
|
+
if (body.fromProperties && body.method !== "fromTo") {
|
|
255
|
+
return respond({ error: "fromProperties is only valid for method=fromTo" }, 400);
|
|
256
|
+
}
|
|
257
|
+
const result = addAnimationToScript(block.scriptText, {
|
|
258
|
+
targetSelector: body.targetSelector,
|
|
259
|
+
method: body.method,
|
|
260
|
+
position: body.position,
|
|
261
|
+
duration: body.duration,
|
|
262
|
+
ease: body.ease,
|
|
263
|
+
properties: body.properties,
|
|
264
|
+
fromProperties: body.fromProperties,
|
|
265
|
+
});
|
|
266
|
+
return result.script;
|
|
267
|
+
}
|
|
268
|
+
case "delete": {
|
|
269
|
+
const delTarget = requireAnimation(block.scriptText, body.animationId);
|
|
270
|
+
if (!("err" in delTarget) && body.stripStudioEdits) {
|
|
271
|
+
stripStudioEditsFromTarget(block.document, delTarget.anim.targetSelector);
|
|
272
|
+
bakeVisibilityOnDelete(block.document, delTarget.anim);
|
|
273
|
+
}
|
|
274
|
+
return removeAnimationFromScript(block.scriptText, body.animationId);
|
|
275
|
+
}
|
|
276
|
+
case "add-property": {
|
|
277
|
+
const r = requireAnimation(block.scriptText, body.animationId);
|
|
278
|
+
if ("err" in r)
|
|
279
|
+
return r.err;
|
|
280
|
+
return updateAnimationInScript(block.scriptText, body.animationId, {
|
|
281
|
+
properties: { ...r.anim.properties, [body.property]: body.defaultValue },
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
case "add-from-property": {
|
|
285
|
+
const r = requireFromToAnimation(block.scriptText, body.animationId);
|
|
286
|
+
if ("err" in r)
|
|
287
|
+
return r.err;
|
|
288
|
+
return updateAnimationInScript(block.scriptText, body.animationId, {
|
|
289
|
+
fromProperties: { ...(r.anim.fromProperties ?? {}), [body.property]: body.defaultValue },
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
case "remove-property": {
|
|
293
|
+
const r = requireAnimation(block.scriptText, body.animationId);
|
|
294
|
+
if ("err" in r)
|
|
295
|
+
return r.err;
|
|
296
|
+
const filtered = { ...r.anim.properties };
|
|
297
|
+
delete filtered[body.property];
|
|
298
|
+
return updateAnimationInScript(block.scriptText, body.animationId, {
|
|
299
|
+
properties: filtered,
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
case "remove-from-property": {
|
|
303
|
+
const r = requireFromToAnimation(block.scriptText, body.animationId);
|
|
304
|
+
if ("err" in r)
|
|
305
|
+
return r.err;
|
|
306
|
+
const filtered = { ...(r.anim.fromProperties ?? {}) };
|
|
307
|
+
delete filtered[body.property];
|
|
308
|
+
return updateAnimationInScript(block.scriptText, body.animationId, {
|
|
309
|
+
fromProperties: filtered,
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
case "add-keyframe": {
|
|
313
|
+
return addKeyframeToScript(block.scriptText, body.animationId, body.percentage, body.properties, body.ease, body.backfillDefaults);
|
|
314
|
+
}
|
|
315
|
+
case "remove-keyframe": {
|
|
316
|
+
return removeKeyframeFromScript(block.scriptText, body.animationId, body.percentage);
|
|
317
|
+
}
|
|
318
|
+
case "update-keyframe": {
|
|
319
|
+
return updateKeyframeInScript(block.scriptText, body.animationId, body.percentage, body.properties, body.ease);
|
|
320
|
+
}
|
|
321
|
+
case "convert-to-keyframes": {
|
|
322
|
+
return convertToKeyframesInScript(block.scriptText, body.animationId, body.resolvedFromValues);
|
|
323
|
+
}
|
|
324
|
+
case "remove-all-keyframes": {
|
|
325
|
+
const preCollapse = requireAnimation(block.scriptText, body.animationId);
|
|
326
|
+
if (!("err" in preCollapse)) {
|
|
327
|
+
bakeVisibilityOnDelete(block.document, preCollapse.anim);
|
|
328
|
+
}
|
|
329
|
+
return removeAllKeyframesFromScript(block.scriptText, body.animationId);
|
|
330
|
+
}
|
|
331
|
+
case "materialize-keyframes": {
|
|
332
|
+
if (body.allElements && body.allElements.length > 0) {
|
|
333
|
+
return unrollDynamicAnimations(block.scriptText, body.animationId, body.allElements);
|
|
334
|
+
}
|
|
335
|
+
return materializeKeyframesInScript(block.scriptText, body.animationId, body.keyframes, body.easeEach, body.resolvedSelector);
|
|
336
|
+
}
|
|
337
|
+
case "set-arc-path": {
|
|
338
|
+
return setArcPathInScript(block.scriptText, body.animationId, {
|
|
339
|
+
enabled: body.enabled,
|
|
340
|
+
autoRotate: body.autoRotate ?? false,
|
|
341
|
+
segments: body.segments ?? [],
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
case "update-arc-segment": {
|
|
345
|
+
return updateArcSegmentInScript(block.scriptText, body.animationId, body.segmentIndex, {
|
|
346
|
+
...(body.curviness !== undefined ? { curviness: body.curviness } : {}),
|
|
347
|
+
...(body.cp1 ? { cp1: body.cp1 } : {}),
|
|
348
|
+
...(body.cp2 ? { cp2: body.cp2 } : {}),
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
case "remove-arc-path": {
|
|
352
|
+
return removeArcPathFromScript(block.scriptText, body.animationId);
|
|
353
|
+
}
|
|
354
|
+
case "add-with-keyframes": {
|
|
355
|
+
const result = addAnimationWithKeyframesToScript(block.scriptText, body.targetSelector, body.position, body.duration, body.keyframes, body.ease);
|
|
356
|
+
return result.script;
|
|
357
|
+
}
|
|
358
|
+
default:
|
|
359
|
+
return respond({ error: `unknown mutation type: ${body.type}` }, 400);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
// ── Upload file processing ──────────────────────────────────────────────────
|
|
363
|
+
async function processUploadedFiles(formData, targetDir, projectDir) {
|
|
364
|
+
const MAX_UPLOAD_BYTES = 500 * 1024 * 1024; // 500 MB per file
|
|
365
|
+
const uploaded = [];
|
|
366
|
+
const skipped = [];
|
|
367
|
+
const invalid = [];
|
|
368
|
+
const entries = formData.entries();
|
|
369
|
+
// Derive the subdirectory prefix from targetDir relative to projectDir
|
|
370
|
+
const subDir = targetDir === projectDir ? "" : targetDir.slice(projectDir.length + 1);
|
|
371
|
+
for (const [, value] of entries) {
|
|
372
|
+
if (typeof value === "string")
|
|
373
|
+
continue;
|
|
374
|
+
// Strip path separators — browsers may include directory components
|
|
375
|
+
const name = value.name.split("/").pop()?.split("\\").pop() ?? "";
|
|
376
|
+
if (!name || name.includes("\0") || name.includes(".."))
|
|
377
|
+
continue;
|
|
378
|
+
// Reject individual files that exceed the size limit
|
|
379
|
+
if (value.size > MAX_UPLOAD_BYTES) {
|
|
380
|
+
skipped.push(name);
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
const destPath = resolve(targetDir, name);
|
|
384
|
+
if (!isSafePath(projectDir, destPath))
|
|
385
|
+
continue;
|
|
386
|
+
// Don't overwrite — append (2), (3), etc.
|
|
387
|
+
let finalPath = destPath;
|
|
388
|
+
let finalName = name;
|
|
389
|
+
if (existsSync(finalPath)) {
|
|
390
|
+
// Handle dotfiles correctly: .gitignore → ext="", base=".gitignore"
|
|
391
|
+
const dotIdx = name.indexOf(".", name.startsWith(".") ? 1 : 0);
|
|
392
|
+
const ext = dotIdx > 0 ? name.slice(dotIdx) : "";
|
|
393
|
+
const base = dotIdx > 0 ? name.slice(0, dotIdx) : name;
|
|
394
|
+
let n = 2;
|
|
395
|
+
while (n < 10000 && existsSync(resolve(targetDir, `${base} (${n})${ext}`)))
|
|
396
|
+
n++;
|
|
397
|
+
if (n >= 10000) {
|
|
398
|
+
skipped.push(name);
|
|
399
|
+
continue;
|
|
400
|
+
}
|
|
401
|
+
finalName = `${base} (${n})${ext}`;
|
|
402
|
+
finalPath = resolve(targetDir, finalName);
|
|
403
|
+
}
|
|
404
|
+
const buffer = Buffer.from(await value.arrayBuffer());
|
|
405
|
+
const validation = validateUploadedMediaBuffer(finalName, buffer);
|
|
406
|
+
if (!validation.ok) {
|
|
407
|
+
invalid.push({ name: finalName, reason: validation.reason });
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
writeFileSync(finalPath, buffer);
|
|
411
|
+
const relativePath = subDir ? join(subDir, finalName) : finalName;
|
|
412
|
+
uploaded.push(relativePath);
|
|
413
|
+
if (isAudioFile(finalName)) {
|
|
414
|
+
generateWaveformCache(projectDir, relativePath).catch(() => { });
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
return { uploaded, skipped, invalid };
|
|
418
|
+
}
|
|
154
419
|
// ── Route registration ──────────────────────────────────────────────────────
|
|
155
420
|
export function registerFileRoutes(api, adapter) {
|
|
156
421
|
// ── Read ──
|
|
@@ -343,57 +608,8 @@ export function registerFileRoutes(api, adapter) {
|
|
|
343
608
|
if (subDir && !existsSync(targetDir))
|
|
344
609
|
mkdirSync(targetDir, { recursive: true });
|
|
345
610
|
const formData = await c.req.formData();
|
|
346
|
-
const
|
|
347
|
-
|
|
348
|
-
const invalid = [];
|
|
349
|
-
const entries = formData.entries();
|
|
350
|
-
for (const [, value] of entries) {
|
|
351
|
-
if (typeof value === "string")
|
|
352
|
-
continue;
|
|
353
|
-
// Strip path separators — browsers may include directory components
|
|
354
|
-
const name = value.name.split("/").pop()?.split("\\").pop() ?? "";
|
|
355
|
-
if (!name || name.includes("\0") || name.includes(".."))
|
|
356
|
-
continue;
|
|
357
|
-
// Reject individual files that exceed the size limit
|
|
358
|
-
if (value.size > MAX_UPLOAD_BYTES) {
|
|
359
|
-
skipped.push(name);
|
|
360
|
-
continue;
|
|
361
|
-
}
|
|
362
|
-
const destPath = resolve(targetDir, name);
|
|
363
|
-
if (!isSafePath(project.dir, destPath))
|
|
364
|
-
continue;
|
|
365
|
-
// Don't overwrite — append (2), (3), etc.
|
|
366
|
-
let finalPath = destPath;
|
|
367
|
-
let finalName = name;
|
|
368
|
-
if (existsSync(finalPath)) {
|
|
369
|
-
// Handle dotfiles correctly: .gitignore → ext="", base=".gitignore"
|
|
370
|
-
const dotIdx = name.indexOf(".", name.startsWith(".") ? 1 : 0);
|
|
371
|
-
const ext = dotIdx > 0 ? name.slice(dotIdx) : "";
|
|
372
|
-
const base = dotIdx > 0 ? name.slice(0, dotIdx) : name;
|
|
373
|
-
let n = 2;
|
|
374
|
-
while (n < 10000 && existsSync(resolve(targetDir, `${base} (${n})${ext}`)))
|
|
375
|
-
n++;
|
|
376
|
-
if (n >= 10000) {
|
|
377
|
-
skipped.push(name);
|
|
378
|
-
continue;
|
|
379
|
-
}
|
|
380
|
-
finalName = `${base} (${n})${ext}`;
|
|
381
|
-
finalPath = resolve(targetDir, finalName);
|
|
382
|
-
}
|
|
383
|
-
const buffer = Buffer.from(await value.arrayBuffer());
|
|
384
|
-
const validation = validateUploadedMediaBuffer(finalName, buffer);
|
|
385
|
-
if (!validation.ok) {
|
|
386
|
-
invalid.push({ name: finalName, reason: validation.reason });
|
|
387
|
-
continue;
|
|
388
|
-
}
|
|
389
|
-
writeFileSync(finalPath, buffer);
|
|
390
|
-
const relativePath = subDir ? join(subDir, finalName) : finalName;
|
|
391
|
-
uploaded.push(relativePath);
|
|
392
|
-
if (isAudioFile(finalName)) {
|
|
393
|
-
generateWaveformCache(project.dir, relativePath).catch(() => { });
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
return c.json({ ok: true, files: uploaded, skipped, invalid }, 201);
|
|
611
|
+
const result = await processUploadedFiles(formData, targetDir, project.dir);
|
|
612
|
+
return c.json({ ok: true, files: result.uploaded, skipped: result.skipped, invalid: result.invalid }, 201);
|
|
397
613
|
});
|
|
398
614
|
// ── GSAP Animations (parse) ──
|
|
399
615
|
api.get("/projects/:id/gsap-animations/*", async (c) => {
|
|
@@ -416,6 +632,7 @@ export function registerFileRoutes(api, adapter) {
|
|
|
416
632
|
const parsed = parseGsapScript(block.scriptText);
|
|
417
633
|
return c.json(parsed);
|
|
418
634
|
});
|
|
635
|
+
// ── GSAP Mutations ──
|
|
419
636
|
api.post("/projects/:id/gsap-mutations/*", async (c) => {
|
|
420
637
|
const res = await resolveProjectPath(c, adapter, (id) => `/projects/${id}/gsap-mutations/`, {
|
|
421
638
|
mustExist: true,
|
|
@@ -426,184 +643,44 @@ export function registerFileRoutes(api, adapter) {
|
|
|
426
643
|
if (!body || !body.type) {
|
|
427
644
|
return c.json({ error: "mutation type required" }, 400);
|
|
428
645
|
}
|
|
429
|
-
|
|
430
|
-
|
|
646
|
+
let html = readFileSync(res.absPath, "utf-8");
|
|
647
|
+
let block = extractGsapScriptBlock(html);
|
|
648
|
+
if (!block && (body.type === "add" || body.type === "add-with-keyframes")) {
|
|
649
|
+
const compId = html.match(/data-composition-id="([^"]+)"/)?.[1] ?? "main";
|
|
650
|
+
const { GSAP_CDN } = await import("../../templates/constants.js");
|
|
651
|
+
const gsapCdn = `<script src="${GSAP_CDN}"></script>`;
|
|
652
|
+
const bootstrap = [
|
|
653
|
+
gsapCdn,
|
|
654
|
+
"<script>",
|
|
655
|
+
"window.__timelines = window.__timelines || {};",
|
|
656
|
+
`const tl = gsap.timeline({ paused: true });`,
|
|
657
|
+
`window.__timelines["${compId}"] = tl;`,
|
|
658
|
+
"</script>",
|
|
659
|
+
].join("\n");
|
|
660
|
+
if (html.includes("</body>")) {
|
|
661
|
+
html = html.replace("</body>", `${bootstrap}\n</body>`);
|
|
662
|
+
}
|
|
663
|
+
else {
|
|
664
|
+
html += `\n${bootstrap}`;
|
|
665
|
+
}
|
|
666
|
+
block = extractGsapScriptBlock(html);
|
|
667
|
+
}
|
|
431
668
|
if (!block) {
|
|
432
669
|
return c.json({ error: "no GSAP script found in file" }, 400);
|
|
433
670
|
}
|
|
434
|
-
const
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
return { err: c.json({ error: "animation not found" }, 404) };
|
|
440
|
-
return { anim };
|
|
441
|
-
}
|
|
442
|
-
function requireFromToAnimation(scriptText, animationId) {
|
|
443
|
-
const result = requireAnimation(scriptText, animationId);
|
|
444
|
-
if ("err" in result)
|
|
445
|
-
return result;
|
|
446
|
-
if (result.anim.method !== "fromTo")
|
|
447
|
-
return { err: c.json({ error: "animation is not a fromTo" }, 400) };
|
|
671
|
+
const respond = (data, status) =>
|
|
672
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- bridge between generic status and Hono's literal union
|
|
673
|
+
status ? c.json(data, status) : c.json(data);
|
|
674
|
+
const result = await executeGsapMutation(body, block, respond);
|
|
675
|
+
if (result instanceof Response)
|
|
448
676
|
return result;
|
|
449
|
-
|
|
450
|
-
let newScript;
|
|
451
|
-
// fallow-ignore-next-line complexity
|
|
452
|
-
switch (body.type) {
|
|
453
|
-
case "update-property": {
|
|
454
|
-
const r = requireAnimation(block.scriptText, body.animationId);
|
|
455
|
-
if ("err" in r)
|
|
456
|
-
return r.err;
|
|
457
|
-
newScript = updateAnimationInScript(block.scriptText, body.animationId, {
|
|
458
|
-
properties: { ...r.anim.properties, [body.property]: body.value },
|
|
459
|
-
});
|
|
460
|
-
break;
|
|
461
|
-
}
|
|
462
|
-
case "update-from-property": {
|
|
463
|
-
const r = requireFromToAnimation(block.scriptText, body.animationId);
|
|
464
|
-
if ("err" in r)
|
|
465
|
-
return r.err;
|
|
466
|
-
newScript = updateAnimationInScript(block.scriptText, body.animationId, {
|
|
467
|
-
fromProperties: { ...(r.anim.fromProperties ?? {}), [body.property]: body.value },
|
|
468
|
-
});
|
|
469
|
-
break;
|
|
470
|
-
}
|
|
471
|
-
case "update-meta": {
|
|
472
|
-
newScript = updateAnimationInScript(block.scriptText, body.animationId, body.updates);
|
|
473
|
-
break;
|
|
474
|
-
}
|
|
475
|
-
case "add": {
|
|
476
|
-
if (body.fromProperties && body.method !== "fromTo") {
|
|
477
|
-
return c.json({ error: "fromProperties is only valid for method=fromTo" }, 400);
|
|
478
|
-
}
|
|
479
|
-
const result = addAnimationToScript(block.scriptText, {
|
|
480
|
-
targetSelector: body.targetSelector,
|
|
481
|
-
method: body.method,
|
|
482
|
-
position: body.position,
|
|
483
|
-
duration: body.duration,
|
|
484
|
-
ease: body.ease,
|
|
485
|
-
properties: body.properties,
|
|
486
|
-
fromProperties: body.fromProperties,
|
|
487
|
-
});
|
|
488
|
-
newScript = result.script;
|
|
489
|
-
break;
|
|
490
|
-
}
|
|
491
|
-
case "delete": {
|
|
492
|
-
newScript = removeAnimationFromScript(block.scriptText, body.animationId);
|
|
493
|
-
break;
|
|
494
|
-
}
|
|
495
|
-
case "add-property": {
|
|
496
|
-
const r = requireAnimation(block.scriptText, body.animationId);
|
|
497
|
-
if ("err" in r)
|
|
498
|
-
return r.err;
|
|
499
|
-
newScript = updateAnimationInScript(block.scriptText, body.animationId, {
|
|
500
|
-
properties: { ...r.anim.properties, [body.property]: body.defaultValue },
|
|
501
|
-
});
|
|
502
|
-
break;
|
|
503
|
-
}
|
|
504
|
-
case "add-from-property": {
|
|
505
|
-
const r = requireFromToAnimation(block.scriptText, body.animationId);
|
|
506
|
-
if ("err" in r)
|
|
507
|
-
return r.err;
|
|
508
|
-
newScript = updateAnimationInScript(block.scriptText, body.animationId, {
|
|
509
|
-
fromProperties: { ...(r.anim.fromProperties ?? {}), [body.property]: body.defaultValue },
|
|
510
|
-
});
|
|
511
|
-
break;
|
|
512
|
-
}
|
|
513
|
-
case "remove-property": {
|
|
514
|
-
const r = requireAnimation(block.scriptText, body.animationId);
|
|
515
|
-
if ("err" in r)
|
|
516
|
-
return r.err;
|
|
517
|
-
const filtered = { ...r.anim.properties };
|
|
518
|
-
delete filtered[body.property];
|
|
519
|
-
newScript = updateAnimationInScript(block.scriptText, body.animationId, {
|
|
520
|
-
properties: filtered,
|
|
521
|
-
});
|
|
522
|
-
break;
|
|
523
|
-
}
|
|
524
|
-
case "remove-from-property": {
|
|
525
|
-
const r = requireFromToAnimation(block.scriptText, body.animationId);
|
|
526
|
-
if ("err" in r)
|
|
527
|
-
return r.err;
|
|
528
|
-
const filtered = { ...(r.anim.fromProperties ?? {}) };
|
|
529
|
-
delete filtered[body.property];
|
|
530
|
-
newScript = updateAnimationInScript(block.scriptText, body.animationId, {
|
|
531
|
-
fromProperties: filtered,
|
|
532
|
-
});
|
|
533
|
-
break;
|
|
534
|
-
}
|
|
535
|
-
case "add-keyframe": {
|
|
536
|
-
const { addKeyframeToScript } = await loadGsapParser();
|
|
537
|
-
newScript = addKeyframeToScript(block.scriptText, body.animationId, body.percentage, body.properties, body.ease, body.backfillDefaults);
|
|
538
|
-
break;
|
|
539
|
-
}
|
|
540
|
-
case "remove-keyframe": {
|
|
541
|
-
const { removeKeyframeFromScript } = await loadGsapParser();
|
|
542
|
-
newScript = removeKeyframeFromScript(block.scriptText, body.animationId, body.percentage);
|
|
543
|
-
break;
|
|
544
|
-
}
|
|
545
|
-
case "update-keyframe": {
|
|
546
|
-
const { updateKeyframeInScript } = await loadGsapParser();
|
|
547
|
-
newScript = updateKeyframeInScript(block.scriptText, body.animationId, body.percentage, body.properties, body.ease);
|
|
548
|
-
break;
|
|
549
|
-
}
|
|
550
|
-
case "convert-to-keyframes": {
|
|
551
|
-
const { convertToKeyframesInScript } = await loadGsapParser();
|
|
552
|
-
newScript = convertToKeyframesInScript(block.scriptText, body.animationId, body.resolvedFromValues);
|
|
553
|
-
break;
|
|
554
|
-
}
|
|
555
|
-
case "remove-all-keyframes": {
|
|
556
|
-
const { removeAllKeyframesFromScript } = await loadGsapParser();
|
|
557
|
-
newScript = removeAllKeyframesFromScript(block.scriptText, body.animationId);
|
|
558
|
-
break;
|
|
559
|
-
}
|
|
560
|
-
case "materialize-keyframes": {
|
|
561
|
-
const { materializeKeyframesInScript, unrollDynamicAnimations } = await loadGsapParser();
|
|
562
|
-
if (body.allElements && body.allElements.length > 0) {
|
|
563
|
-
newScript = unrollDynamicAnimations(block.scriptText, body.animationId, body.allElements);
|
|
564
|
-
}
|
|
565
|
-
else {
|
|
566
|
-
newScript = materializeKeyframesInScript(block.scriptText, body.animationId, body.keyframes, body.easeEach, body.resolvedSelector);
|
|
567
|
-
}
|
|
568
|
-
break;
|
|
569
|
-
}
|
|
570
|
-
case "set-arc-path": {
|
|
571
|
-
const { setArcPathInScript } = await loadGsapParser();
|
|
572
|
-
newScript = setArcPathInScript(block.scriptText, body.animationId, {
|
|
573
|
-
enabled: body.enabled,
|
|
574
|
-
autoRotate: body.autoRotate ?? false,
|
|
575
|
-
segments: body.segments ?? [],
|
|
576
|
-
});
|
|
577
|
-
break;
|
|
578
|
-
}
|
|
579
|
-
case "update-arc-segment": {
|
|
580
|
-
const { updateArcSegmentInScript } = await loadGsapParser();
|
|
581
|
-
newScript = updateArcSegmentInScript(block.scriptText, body.animationId, body.segmentIndex, {
|
|
582
|
-
...(body.curviness !== undefined ? { curviness: body.curviness } : {}),
|
|
583
|
-
...(body.cp1 ? { cp1: body.cp1 } : {}),
|
|
584
|
-
...(body.cp2 ? { cp2: body.cp2 } : {}),
|
|
585
|
-
});
|
|
586
|
-
break;
|
|
587
|
-
}
|
|
588
|
-
case "remove-arc-path": {
|
|
589
|
-
const { removeArcPathFromScript } = await loadGsapParser();
|
|
590
|
-
newScript = removeArcPathFromScript(block.scriptText, body.animationId);
|
|
591
|
-
break;
|
|
592
|
-
}
|
|
593
|
-
case "add-with-keyframes": {
|
|
594
|
-
const { addAnimationWithKeyframesToScript } = await loadGsapParser();
|
|
595
|
-
const result = addAnimationWithKeyframesToScript(block.scriptText, body.targetSelector, body.position, body.duration, body.keyframes, body.ease);
|
|
596
|
-
newScript = result.script;
|
|
597
|
-
break;
|
|
598
|
-
}
|
|
599
|
-
default:
|
|
600
|
-
return c.json({ error: `unknown mutation type: ${body.type}` }, 400);
|
|
601
|
-
}
|
|
677
|
+
const newScript = result;
|
|
602
678
|
const newHtml = block.replaceScript(newScript);
|
|
603
679
|
if (newHtml !== html) {
|
|
604
680
|
writeFileSync(res.absPath, newHtml, "utf-8");
|
|
605
681
|
}
|
|
606
682
|
// Re-parse the mutated script so the UI gets fresh state
|
|
683
|
+
const { parseGsapScript } = await loadGsapParser();
|
|
607
684
|
const freshParsed = parseGsapScript(newScript);
|
|
608
685
|
return c.json({
|
|
609
686
|
ok: true,
|