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:
Miguel Ángel
2026-07-07 04:26:56 -04:00
committed by GitHub
parent 5d59835446
commit 037266e72b
56 changed files with 2407 additions and 623 deletions
+25
View File
@@ -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