mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 12:54:29 +00:00
feat(studio): timeline revamp with active-clip highlighting and hide controls (#2017)
Timeline UI - Highlight clips visible at the playhead in the primary color; others share one neutral color - Minimalist rounded clips, single-color track rows, no gutter icons or superscript labels - Per-track eye toggle and a per-element hide button in the design panel - Ruler zoom fixes: sub-second tick intervals and correct label formatting at high zoom - Sticky gutter so track controls stay visible while scrolling WYSIWYG visibility (data-hidden) - Runtime honors data-hidden (display:none), so hiding affects the render, not just the preview - HTML stays the source of truth; hide state persists and round-trips on reload Split several studio files to stay under the 600-line cap; pure relocations, no behavior change.
This commit is contained in:
@@ -776,6 +776,52 @@ describe("initSandboxRuntimeModular", () => {
|
||||
expect(bottomBand.style.visibility).toBe("hidden");
|
||||
});
|
||||
|
||||
it("forces data-hidden timed elements out of layout until the attribute is removed", () => {
|
||||
const root = document.createElement("div");
|
||||
root.setAttribute("data-composition-id", "main");
|
||||
root.setAttribute("data-root", "true");
|
||||
root.setAttribute("data-start", "0");
|
||||
root.setAttribute("data-duration", "10");
|
||||
root.setAttribute("data-width", "1920");
|
||||
root.setAttribute("data-height", "1080");
|
||||
document.body.appendChild(root);
|
||||
|
||||
const hiddenClip = document.createElement("div");
|
||||
hiddenClip.style.position = "absolute";
|
||||
hiddenClip.setAttribute("data-start", "2");
|
||||
hiddenClip.setAttribute("data-duration", "4");
|
||||
hiddenClip.setAttribute("data-hidden", "");
|
||||
root.appendChild(hiddenClip);
|
||||
|
||||
window.__timelines = {
|
||||
main: createMockTimeline(10),
|
||||
};
|
||||
|
||||
initSandboxRuntimeModular();
|
||||
|
||||
const player = window.__player;
|
||||
expect(player).toBeDefined();
|
||||
|
||||
player?.seek(0);
|
||||
expect(hiddenClip.style.display).toBe("none");
|
||||
|
||||
player?.seek(3);
|
||||
expect(hiddenClip.style.display).toBe("none");
|
||||
|
||||
player?.seek(7);
|
||||
expect(hiddenClip.style.display).toBe("none");
|
||||
|
||||
hiddenClip.removeAttribute("data-hidden");
|
||||
|
||||
player?.seek(3);
|
||||
expect(hiddenClip.style.visibility).toBe("visible");
|
||||
expect(hiddenClip.style.display).toBe("");
|
||||
|
||||
player?.seek(7);
|
||||
expect(hiddenClip.style.visibility).toBe("hidden");
|
||||
expect(hiddenClip.style.display).toBe("");
|
||||
});
|
||||
|
||||
it("does not stamp Studio timing on GSAP targets inside authored timed clips", () => {
|
||||
withStudioIframe(() => {
|
||||
const root = document.createElement("div");
|
||||
|
||||
@@ -1666,6 +1666,8 @@ export function initSandboxRuntimeModular(): void {
|
||||
timedClipInFlow = new WeakMap<Element, boolean>();
|
||||
timedClipIsLeaf = new WeakMap<Element, boolean>();
|
||||
};
|
||||
const dataHiddenDisplayRestores = new WeakMap<HTMLElement, string>();
|
||||
const dataHiddenDisplayNodes = new WeakSet<HTMLElement>();
|
||||
|
||||
const syncMediaForCurrentState = () => {
|
||||
const resolveMediaCompositionContext = (element: HTMLVideoElement | HTMLAudioElement) => {
|
||||
@@ -1754,6 +1756,29 @@ export function initSandboxRuntimeModular(): void {
|
||||
for (const rawNode of visibilityNodes) {
|
||||
if (!(rawNode instanceof HTMLElement)) continue;
|
||||
|
||||
if (rawNode.hasAttribute("data-hidden")) {
|
||||
if (!dataHiddenDisplayNodes.has(rawNode)) {
|
||||
dataHiddenDisplayRestores.set(rawNode, rawNode.style.getPropertyValue("display"));
|
||||
dataHiddenDisplayNodes.add(rawNode);
|
||||
}
|
||||
rawNode.style.display = "none";
|
||||
if (rawNode instanceof HTMLVideoElement || rawNode instanceof HTMLImageElement) {
|
||||
colorGradingRuntime?.setSourceVisibility(rawNode, false);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (dataHiddenDisplayNodes.has(rawNode)) {
|
||||
const previousDisplay = dataHiddenDisplayRestores.get(rawNode);
|
||||
if (previousDisplay) {
|
||||
rawNode.style.display = previousDisplay;
|
||||
} else {
|
||||
rawNode.style.removeProperty("display");
|
||||
}
|
||||
dataHiddenDisplayRestores.delete(rawNode);
|
||||
dataHiddenDisplayNodes.delete(rawNode);
|
||||
}
|
||||
|
||||
let isVisibleNow = isTimedElementVisibleAt(rawNode, state.currentTime);
|
||||
// Descendants must not override a hidden ancestor clip. CSS visibility can
|
||||
// otherwise leak child pixels through inactive scenes because a descendant
|
||||
|
||||
Reference in New Issue
Block a user