mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-10 22:20:14 +00:00
fix(studio): fix seek after code edit, improve scrub perf, add click-to-source (#881)
* feat(studio): html-backed motion panel — persist GSAP motion to element attributes Re-architects the motion panel to store GSAP motion data as a JSON data attribute (data-hf-studio-motion) on each element instead of a .hyperframes/studio-motion.json sidecar file. Follows the same pattern as position/resize/rotation edits: write to DOM, build patches, persist to HTML source via commitPositionPatchToHtml. Render pipeline: the studioPositionSeekReapplyRuntime now queries [data-hf-studio-motion] elements after each seek, parses their JSON, builds a GSAP timeline, and seeks it to the current frame time. Studio preview: motion reapply is integrated into the manual edits seek hook (reapplyPositionEditsAfterSeek). useManifestPersistence is slimmed to only handle save queue and seek hooks. * fix(studio): address PR review — html-escape attrs, cache timeline, migrate sidecar, add tests Blocker: JSON attribute values are now HTML-entity-escaped before being written into source HTML. Read-back unescapes automatically. Perf: motion timeline is cached between seeks at render — only rebuilt when the concatenated JSON key changes, not on every frame. Migration: on mount, empties legacy .hyperframes/studio-motion.json so the legacy render script no-ops. Tests: 46 new tests for motion read/write/clear round-trips, JSON attribute escaping, and source patcher entity handling. Nits: removed unused activeCompositionPath param; tightened htmlCompiler attribute substring check. * fix(studio): fix seek after code edit, improve scrub performance, add click-to-source Three issues addressed: 1. **Seek breaks after code edit**: During crossfade refreshes the retiring Player's cleanup unconditionally nulled `iframeRef.current`, clobbering the reference the new Player had already assigned. Guard the cleanup to only clear the ref when it still points to the retiring Player's own iframe. 2. **Scrubber/timeline drag jank**: Every pointermove during a drag called the full seek pipeline (adapter.seek + setCurrentTime + React re-render cascade). RAF-throttle the expensive onSeek call during drags while keeping slider and playhead visuals updated on every pointer event for instant feedback. 3. **Click-to-source**: Clicking an element in the preview now switches to the Code tab, opens the element's source file, and scrolls the editor to the element's opening tag. Uses the existing `findTagByTarget` source patcher to locate the element by id/selector in the HTML source. * fix(studio): address PR review — gate click-to-source, fix fetch race, guard refs - Gate click-to-source on Alt/Option+click so it doesn't steal the Code tab on every preview click, conflicting with select-to-inspect workflow - Fix fetch race in openSourceForSelection: AbortController cancels the previous in-flight fetch, monotonic request ID prevents stale responses from applying the wrong file/offset - Guard the callback-ref branch in Player cleanup (no-op — can't read back from a callback ref to check identity, and the path is unreachable today since the ref is always a MutableRefObject) - Import SidebarTab type instead of duplicating the literal inline
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
import { useEffect } from "react";
|
||||
import { useCallback, useEffect } from "react";
|
||||
import type { TimelineElement } from "../player";
|
||||
import { STUDIO_INSPECTOR_PANELS_ENABLED } from "../components/editor/manualEditingAvailability";
|
||||
import { findElementForSelection } from "../components/editor/domEditing";
|
||||
import { findElementForSelection, type DomEditSelection } from "../components/editor/domEditing";
|
||||
import type { ImportedFontAsset } from "../components/editor/fontAssets";
|
||||
import type { EditHistoryKind } from "../utils/editHistory";
|
||||
import type { RightPanelTab } from "../utils/studioHelpers";
|
||||
import type { PatchTarget } from "../utils/sourcePatcher";
|
||||
import type { SidebarTab } from "../components/sidebar/LeftSidebar";
|
||||
import { useAskAgentModal } from "./useAskAgentModal";
|
||||
import { useDomSelection } from "./useDomSelection";
|
||||
import { usePreviewInteraction } from "./usePreviewInteraction";
|
||||
@@ -52,6 +54,8 @@ export interface UseDomEditSessionParams {
|
||||
syncPreviewHistoryHotkey: (iframe: HTMLIFrameElement | null) => void;
|
||||
reloadPreview: () => void;
|
||||
setRefreshKey: React.Dispatch<React.SetStateAction<number>>;
|
||||
openSourceForSelection?: (sourceFile: string, target: PatchTarget) => void;
|
||||
selectSidebarTab?: (tab: SidebarTab) => void;
|
||||
}
|
||||
|
||||
// ── Hook ──
|
||||
@@ -87,8 +91,25 @@ export function useDomEditSession({
|
||||
syncPreviewHistoryHotkey,
|
||||
reloadPreview,
|
||||
setRefreshKey: _setRefreshKey,
|
||||
openSourceForSelection,
|
||||
selectSidebarTab,
|
||||
}: UseDomEditSessionParams) {
|
||||
void _setRefreshKey;
|
||||
|
||||
const onClickToSource = useCallback(
|
||||
(selection: DomEditSelection) => {
|
||||
if (!openSourceForSelection || !selectSidebarTab) return;
|
||||
if (!selection.sourceFile) return;
|
||||
selectSidebarTab("code");
|
||||
openSourceForSelection(selection.sourceFile, {
|
||||
id: selection.id,
|
||||
selector: selection.selector,
|
||||
selectorIndex: selection.selectorIndex,
|
||||
});
|
||||
},
|
||||
[openSourceForSelection, selectSidebarTab],
|
||||
);
|
||||
|
||||
// ── Selection (delegated to useDomSelection) ──
|
||||
|
||||
const {
|
||||
@@ -164,6 +185,7 @@ export function useDomEditSession({
|
||||
setAgentPromptSelectionContext,
|
||||
setAgentModalAnchorPoint,
|
||||
setAgentModalOpen,
|
||||
onClickToSource,
|
||||
});
|
||||
|
||||
// ── Commit handlers (delegated to useDomEditCommits) ──
|
||||
|
||||
Reference in New Issue
Block a user