mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-01 19:42:03 +00:00
* feat(studio): GSAP tween editing in Design panel
Add a GSAP animation editor to the studio Design panel: select an element,
view and edit its tweens (properties, easing, timing), add/delete animations,
and drag custom bezier speed curves — all persisted back to the composition
HTML. Gated behind VITE_STUDIO_ENABLE_GSAP_PANEL.
Parsing of existing GSAP source now uses a recast + Babel AST parser instead of
regex, giving scope resolution, stable tween IDs, and round-trip preservation of
extras and unresolved raw values.
recast compiles to CommonJS that calls require("fs"), which breaks browser and
Vite SSR bundles. To contain it, @hyperframes/core is split into an isomorphic
layer and a Node-only AST layer:
- gsapSerialize.ts holds the recast-free helpers (serialization, keyframe
conversion, validation, shared types). htmlParser.ts is now fully isomorphic.
- parseGsapScript and the script-mutation helpers live in gsapParser.ts,
reachable only via the @hyperframes/core/gsap-parser subpath, loaded
server-side by the studio-api mutation routes and the linter via dynamic
import (recast stays external under SSR).
- The barrel and the gsap-constants subpath are recast-free, so studio browser
bundles never trace recast.
Adds AST parser unit + stress coverage and e2e helpers for the panel.
* fix(lint): await async lintHyperframeHtml in all callers
lintHyperframeHtml became async (gsap rules use dynamic import)
but lintProject and check-hyperframe-static weren't awaiting it,
causing typecheck failures and runtime crashes in CI.
Also wire LintRule type in gsap rules to fix fallow unused-type
finding, and suppress render.ts exported-for-tests symbols.
89 lines
2.6 KiB
Bash
89 lines
2.6 KiB
Bash
#!/bin/bash
|
|
# E2E test helpers for agent-browser that work reliably with React components.
|
|
# Uses JS-based clicks (element.click()) instead of pointer events to avoid
|
|
# stale ref issues after iframe reloads.
|
|
|
|
# Click a button by its exact text content
|
|
click_button() {
|
|
local text="$1"
|
|
agent-browser eval --stdin <<EVALEOF
|
|
(function() {
|
|
var btns = document.querySelectorAll('button');
|
|
for (var i = 0; i < btns.length; i++) {
|
|
if (btns[i].textContent.trim() === '${text}') {
|
|
btns[i].click();
|
|
return 'clicked: ${text}';
|
|
}
|
|
}
|
|
return 'not found: ${text}';
|
|
})()
|
|
EVALEOF
|
|
}
|
|
|
|
# Click a button whose text contains a substring
|
|
click_button_contains() {
|
|
local text="$1"
|
|
agent-browser eval --stdin <<EVALEOF
|
|
(function() {
|
|
var btns = document.querySelectorAll('button');
|
|
for (var i = 0; i < btns.length; i++) {
|
|
if (btns[i].textContent.includes('${text}')) {
|
|
btns[i].click();
|
|
return 'clicked: ' + btns[i].textContent.trim().substring(0, 60);
|
|
}
|
|
}
|
|
return 'not found: ${text}';
|
|
})()
|
|
EVALEOF
|
|
}
|
|
|
|
# Set an input value by label text and commit via Enter
|
|
set_input() {
|
|
local label="$1"
|
|
local value="$2"
|
|
agent-browser eval --stdin <<EVALEOF
|
|
(function() {
|
|
var labels = document.querySelectorAll('label, span');
|
|
for (var i = 0; i < labels.length; i++) {
|
|
if (labels[i].textContent.trim() === '${label}') {
|
|
var input = labels[i].closest('[class]')?.querySelector('input');
|
|
if (!input) continue;
|
|
var nativeSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
|
|
nativeSetter.call(input, '${value}');
|
|
input.dispatchEvent(new Event('input', {bubbles: true}));
|
|
input.dispatchEvent(new KeyboardEvent('keydown', {key: 'Enter', code: 'Enter', bubbles: true}));
|
|
return 'set ${label} = ${value}';
|
|
}
|
|
}
|
|
return 'not found: ${label}';
|
|
})()
|
|
EVALEOF
|
|
}
|
|
|
|
# Select from a combobox/select by label and option value
|
|
select_option() {
|
|
local label="$1"
|
|
local value="$2"
|
|
agent-browser eval --stdin <<EVALEOF
|
|
(function() {
|
|
var selects = document.querySelectorAll('select');
|
|
for (var i = 0; i < selects.length; i++) {
|
|
var lbl = selects[i].closest('[class]')?.querySelector('label, span');
|
|
if (lbl && lbl.textContent.trim() === '${label}') {
|
|
selects[i].value = '${value}';
|
|
selects[i].dispatchEvent(new Event('change', {bubbles: true}));
|
|
return 'selected ${label} = ${value}';
|
|
}
|
|
}
|
|
return 'not found: ${label}';
|
|
})()
|
|
EVALEOF
|
|
}
|
|
|
|
# Wait for text to appear in the page
|
|
wait_for_text() {
|
|
local text="$1"
|
|
local timeout="${2:-10000}"
|
|
agent-browser wait --text "${text}" --timeout "${timeout}" 2>/dev/null || echo "timeout waiting for: ${text}"
|
|
}
|