mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-09 03:16:38 +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:
@@ -0,0 +1,93 @@
|
||||
// @vitest-environment happy-dom
|
||||
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { updateTimelineActiveClipClasses } from "./useTimelineActiveClips";
|
||||
|
||||
function appendClip(container: HTMLElement, id: string, start: string, end: string): HTMLElement {
|
||||
const clip = document.createElement("div");
|
||||
clip.dataset.clip = "true";
|
||||
clip.dataset.elId = id;
|
||||
clip.dataset.clipStart = start;
|
||||
clip.dataset.clipEnd = end;
|
||||
container.append(clip);
|
||||
return clip;
|
||||
}
|
||||
|
||||
describe("updateTimelineActiveClipClasses", () => {
|
||||
it("toggles data-active only for clips containing the current time", () => {
|
||||
const container = document.createElement("div");
|
||||
const intro = appendClip(container, "intro", "0", "2");
|
||||
const hero = appendClip(container, "hero", "2", "5");
|
||||
const outro = appendClip(container, "outro", "5", "8");
|
||||
const previous = new Set<string>();
|
||||
|
||||
updateTimelineActiveClipClasses(container, previous, 2.25);
|
||||
|
||||
expect(intro.hasAttribute("data-active")).toBe(false);
|
||||
expect(hero.hasAttribute("data-active")).toBe(true);
|
||||
expect(outro.hasAttribute("data-active")).toBe(false);
|
||||
expect(previous).toEqual(new Set(["hero"]));
|
||||
});
|
||||
|
||||
it("never marks hidden clips active inside their time window", () => {
|
||||
const container = document.createElement("div");
|
||||
const hidden = appendClip(container, "hidden", "0", "5");
|
||||
const visible = appendClip(container, "visible", "0", "5");
|
||||
hidden.dataset.clipHidden = "true";
|
||||
const previous = new Set<string>();
|
||||
|
||||
updateTimelineActiveClipClasses(container, previous, 2);
|
||||
|
||||
expect(hidden.hasAttribute("data-active")).toBe(false);
|
||||
expect(visible.hasAttribute("data-active")).toBe(true);
|
||||
expect(previous).toEqual(new Set(["visible"]));
|
||||
});
|
||||
|
||||
it("diffs against the previous active set", () => {
|
||||
const container = document.createElement("div");
|
||||
const intro = appendClip(container, "intro", "0", "2");
|
||||
const hero = appendClip(container, "hero", "2", "5");
|
||||
const previous = new Set(["intro"]);
|
||||
intro.toggleAttribute("data-active", true);
|
||||
|
||||
updateTimelineActiveClipClasses(container, previous, 2);
|
||||
|
||||
expect(intro.hasAttribute("data-active")).toBe(true);
|
||||
expect(hero.hasAttribute("data-active")).toBe(true);
|
||||
expect(previous).toEqual(new Set(["intro", "hero"]));
|
||||
});
|
||||
|
||||
it("keeps a clip active through its inclusive end boundary", () => {
|
||||
const container = document.createElement("div");
|
||||
const intro = appendClip(container, "intro", "0", "2");
|
||||
const previous = new Set<string>();
|
||||
|
||||
updateTimelineActiveClipClasses(container, previous, 0);
|
||||
|
||||
expect(intro.hasAttribute("data-active")).toBe(true);
|
||||
expect(previous).toEqual(new Set(["intro"]));
|
||||
|
||||
updateTimelineActiveClipClasses(container, previous, 2);
|
||||
|
||||
expect(intro.hasAttribute("data-active")).toBe(true);
|
||||
expect(previous).toEqual(new Set(["intro"]));
|
||||
|
||||
updateTimelineActiveClipClasses(container, previous, 2.001);
|
||||
|
||||
expect(intro.hasAttribute("data-active")).toBe(false);
|
||||
expect(previous).toEqual(new Set());
|
||||
});
|
||||
|
||||
it("ignores clips with invalid timing data", () => {
|
||||
const container = document.createElement("div");
|
||||
const missingId = appendClip(container, "", "0", "2");
|
||||
const missingTiming = appendClip(container, "bad", "", "2");
|
||||
const previous = new Set<string>();
|
||||
|
||||
updateTimelineActiveClipClasses(container, previous, 1);
|
||||
|
||||
expect(missingId.hasAttribute("data-active")).toBe(false);
|
||||
expect(missingTiming.hasAttribute("data-active")).toBe(false);
|
||||
expect(previous).toEqual(new Set());
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user