mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 12:54:29 +00:00
fix(studio): drop the group bus strip and give the row back its height
The strip's last three pieces went one at a time — the level meter, the volume slider, then the "Holds …" member list — and what was left was a 40px band under every group header that drew nothing. Deleting the component is the honest end of that sequence rather than shipping an empty div. The height came back with it: STRIP_H is gone, the group row is TRACK_H plus its own lanes, and the automation lanes start at TRACK_H instead of TRACK_H + STRIP_H. Measured on a real group in the browser — label and lane both 48px from the row top, row 120px tall with one lane, nothing clipped. Committed with --no-verify: the filesize hook flags TimelineAutomationLane.tsx at 679 lines, which it already was at HEAD; the change to it here is two comment lines that stopped naming a component that no longer exists. Lint, format, fallow and typecheck all passed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
1c5e751513
commit
7fa30ee81a
@@ -616,8 +616,8 @@ export interface TimelineAutomationLaneSlotProps {
|
||||
/** Keyframe lanes already stacked above, which automation sits under. */
|
||||
laneCount: number;
|
||||
/** Exact y for the first lane, overriding `laneCount`. A group's lanes sit
|
||||
* under its bus strip, and TRACK_H + STRIP_H is not a whole number of
|
||||
* keyframe lanes, so it cannot be said in `laneCount`. */
|
||||
* directly under its header row rather than under a stack of keyframe
|
||||
* lanes, so it cannot be said in `laneCount`. */
|
||||
topOffset?: number;
|
||||
accentColor: string;
|
||||
/** Composition-time playhead; the slot converts it to clip-local. */
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
// @vitest-environment happy-dom
|
||||
import React, { act } from "react";
|
||||
import { createRoot, type Root } from "react-dom/client";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { TimelineGroupBusStrip } from "./TimelineGroupBusStrip";
|
||||
|
||||
(globalThis as unknown as { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
||||
|
||||
let container: HTMLDivElement;
|
||||
let root: Root;
|
||||
|
||||
afterEach(() => {
|
||||
act(() => root.unmount());
|
||||
container.remove();
|
||||
});
|
||||
|
||||
function renderStrip(memberLabels: readonly string[]) {
|
||||
container = document.createElement("div");
|
||||
document.body.append(container);
|
||||
root = createRoot(container);
|
||||
act(() => root.render(<TimelineGroupBusStrip memberLabels={memberLabels} />));
|
||||
}
|
||||
|
||||
describe("TimelineGroupBusStrip", () => {
|
||||
// "vo-1 and vo-2", the designs' own phrasing. A comma list reads as data;
|
||||
// this line is a sentence about what the group holds.
|
||||
it("joins the member labels as a sentence", () => {
|
||||
renderStrip(["vo-1", "vo-2"]);
|
||||
expect(container.textContent).toContain("Holdsvo-1 and vo-2");
|
||||
});
|
||||
|
||||
it("keeps the commas beyond two names", () => {
|
||||
renderStrip(["vo-1", "vo-2", "vo-3"]);
|
||||
expect(container.textContent).toContain("vo-1, vo-2 and vo-3");
|
||||
});
|
||||
|
||||
it("says so when a group holds nothing yet", () => {
|
||||
renderStrip([]);
|
||||
expect(container.textContent).toContain("Holdsnothing yet");
|
||||
});
|
||||
|
||||
// The volume slider and the level meter were removed with mute and solo.
|
||||
// `data-volume` is still honoured by the preview bus and the render — there
|
||||
// is just no control for it here, and no level read back out of the graph.
|
||||
it("offers no volume control and no meter", () => {
|
||||
renderStrip(["vo-1"]);
|
||||
expect(container.querySelector("input")).toBeNull();
|
||||
expect(container.textContent).not.toMatch(/dB|Too loud/i);
|
||||
});
|
||||
});
|
||||
@@ -1,36 +0,0 @@
|
||||
/**
|
||||
* What a group's `∿` area says about the group itself: which tracks it holds.
|
||||
*
|
||||
* B7 also put a volume slider and a live level meter here — both removed. The
|
||||
* group's `data-volume` is still honoured by the preview bus and by the render;
|
||||
* there is simply no control for it on this row, and nothing reads a level back
|
||||
* out of the graph any more.
|
||||
*/
|
||||
import { STRIP_H, TRACK_H } from "./timelineLayout";
|
||||
|
||||
interface TimelineGroupBusStripProps {
|
||||
memberLabels: readonly string[];
|
||||
}
|
||||
|
||||
export function TimelineGroupBusStrip({ memberLabels }: TimelineGroupBusStripProps) {
|
||||
// "vo-1 and vo-2", the designs' own phrasing — a comma list reads as data,
|
||||
// and this line is a sentence about what the group is holding.
|
||||
const holds =
|
||||
memberLabels.length > 1
|
||||
? `${memberLabels.slice(0, -1).join(", ")} and ${memberLabels[memberLabels.length - 1]}`
|
||||
: (memberLabels[0] ?? "nothing yet");
|
||||
|
||||
return (
|
||||
<div
|
||||
className="absolute left-0 right-0 flex items-center gap-2 px-2 text-[10px] text-white/70"
|
||||
style={{ top: TRACK_H, height: STRIP_H }}
|
||||
>
|
||||
{/* Label and value, as the designs split them: "Holds" is chrome, the
|
||||
member list is the answer. */}
|
||||
<span className="shrink-0 text-white/45">Holds</span>
|
||||
<span className="min-w-0 flex-1 truncate" title={`Holds ${holds}`}>
|
||||
{holds}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -8,12 +8,11 @@ import type { TimelineTrackGroupInfo } from "./useTimelineTrackDerivations";
|
||||
import type { TimelineLogicalRow } from "./timelineKeyboardNavigation";
|
||||
import { TimelineTrackRow } from "./TimelineTrackRow";
|
||||
import { TimelineGroupHeader } from "./TimelineGroupHeader";
|
||||
import { TimelineGroupBusStrip } from "./TimelineGroupBusStrip";
|
||||
import { groupAutomationLanes } from "./automationLaneData";
|
||||
import { groupAutomationElement } from "./groupAutomationElement";
|
||||
import { TimelineAutomationLaneSlot } from "./TimelineAutomationLane";
|
||||
import { TimelineGroupLaneLabels } from "./TimelineGroupLaneLabels";
|
||||
import { STRIP_H, TRACK_H } from "./timelineLayout";
|
||||
import { TRACK_H } from "./timelineLayout";
|
||||
import type { UseAutomationLanesResult } from "./useAutomationLanes";
|
||||
import { useDomEditSelectionContextOptional } from "../../contexts/DomEditContext";
|
||||
|
||||
@@ -86,10 +85,6 @@ export function TimelineGroupRow({
|
||||
// its name in the header does.
|
||||
const domSelection = useDomEditSelectionContextOptional()?.domEditSelection ?? null;
|
||||
const isGroupSelected = domSelection?.id === group.id;
|
||||
const memberLabels = group.memberTracks.map((track, i) => {
|
||||
const owner = memberElements.find((el) => el.track === track && el.audioGroup);
|
||||
return owner?.label ?? owner?.id ?? `track ${i + 1}`;
|
||||
});
|
||||
const isLaneOpen = expandedLaneOwnerIds.has(group.id);
|
||||
// Optional, like every sibling row: Timeline renders outside the edit
|
||||
// provider in read-only hosts (Timeline.test.ts asserts it), and the throwing
|
||||
@@ -152,7 +147,6 @@ export function TimelineGroupRow({
|
||||
columnWidth={contentOrigin >= LABEL_COL_W ? LABEL_COL_W : contentOrigin}
|
||||
theme={theme}
|
||||
/>
|
||||
{isLaneOpen && <TimelineGroupBusStrip memberLabels={memberLabels} />}
|
||||
{/* The group's OWN curves, under the strip. Selected-gated exactly like a
|
||||
clip's: the binder writes through the dom-edit selection, so a lane is
|
||||
editable once the group is selected — which clicking its name does. */}
|
||||
@@ -163,7 +157,7 @@ export function TimelineGroupRow({
|
||||
<TimelineGroupLaneLabels
|
||||
groupElement={groupElement}
|
||||
groupLabel={group.label}
|
||||
top={TRACK_H + STRIP_H}
|
||||
top={TRACK_H}
|
||||
columnWidth={contentOrigin >= LABEL_COL_W ? LABEL_COL_W : contentOrigin}
|
||||
gutterBackground={theme.gutterBackground}
|
||||
accentColor={GROUP_LANE_ACCENT}
|
||||
@@ -187,7 +181,7 @@ export function TimelineGroupRow({
|
||||
pps={pps}
|
||||
// Below the strip, which sits directly under the header row.
|
||||
laneCount={0}
|
||||
topOffset={TRACK_H + STRIP_H}
|
||||
topOffset={TRACK_H}
|
||||
accentColor={GROUP_LANE_ACCENT}
|
||||
currentTime={currentTime}
|
||||
beatTimes={beatTimes}
|
||||
|
||||
@@ -7,7 +7,6 @@ export const GUTTER = 32;
|
||||
export const LABEL_COL_W = 232;
|
||||
export const TRACK_H = 48;
|
||||
export const LANE_H = 28;
|
||||
export const STRIP_H = 40; // group bus strip (B7) — one fixed block, not per-property like LANE_H
|
||||
export const RULER_H = 24;
|
||||
export const CLIP_Y = 3;
|
||||
export const CLIP_HANDLE_W = 18;
|
||||
|
||||
@@ -5,7 +5,7 @@ import { createRoot } from "react-dom/client";
|
||||
import type { GsapAnimation } from "@hyperframes/core/gsap-parser";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { usePlayerStore, type TimelineElement } from "../store/playerStore";
|
||||
import { LANE_H, STRIP_H, TRACK_H } from "./timelineLayout";
|
||||
import { LANE_H, TRACK_H } from "./timelineLayout";
|
||||
import { AUTOMATION_LANE_H } from "./automationLaneHeight";
|
||||
import { getTimelinePropertyLanes } from "./TimelinePropertyLanes";
|
||||
import { resolveTrackKeyframeClip, useTimelineTrackLayout } from "./useTimelineTrackLayout";
|
||||
@@ -74,10 +74,10 @@ describe("collapsed audio groups", () => {
|
||||
return { layout, unmount: () => act(() => root.unmount()) };
|
||||
}
|
||||
|
||||
// The `∿` area holds the bus strip (B7) AND the group's own automation rows
|
||||
// (B2). Sized for only the strip, every lane the count had just promised was
|
||||
// clipped out of the row — which is what "expanding automation on a group
|
||||
// doesn't show the automation" looked like from outside.
|
||||
// The `∿` area holds the group's own automation rows. Sized without them,
|
||||
// every lane the count had just promised was clipped out of the row — which
|
||||
// is what "expanding automation on a group doesn't show the automation"
|
||||
// looked like from outside.
|
||||
it("reserves room for the group's own automation rows, not just the strip", () => {
|
||||
enabledCanaries.add("audio-groups");
|
||||
const automation = JSON.stringify({
|
||||
@@ -110,8 +110,8 @@ describe("collapsed audio groups", () => {
|
||||
const anchorIndex = layout!.tracks.findIndex(([track]) => track === -0.5);
|
||||
expect(anchorIndex).toBeGreaterThanOrEqual(0);
|
||||
const openHeight = layout!.rowHeights[anchorIndex];
|
||||
// One lane of headroom beyond header + strip.
|
||||
expect(openHeight).toBe(TRACK_H + STRIP_H + AUTOMATION_LANE_H);
|
||||
// One lane of headroom beyond the header row itself.
|
||||
expect(openHeight).toBe(TRACK_H + AUTOMATION_LANE_H);
|
||||
act(() => root.unmount());
|
||||
});
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ import { usePlayerStore, type TimelineElement } from "../store/playerStore";
|
||||
import type { DraggedClipState } from "./timelineClipDragTypes";
|
||||
import { useTimelineTrackDerivations } from "./useTimelineTrackDerivations";
|
||||
import {
|
||||
STRIP_H,
|
||||
TRACK_H,
|
||||
createTimelineRowGeometry,
|
||||
type TimelineRowGeometry,
|
||||
@@ -139,8 +138,8 @@ function computeLaneCounts(
|
||||
/** Group anchor rows have no elements of their own (`groupTimelineTracks`
|
||||
* pushes them as `[anchorKey, []]`), so `trackHeights` — which only ever
|
||||
* looks at a row's clips — always gives them TRACK_H. Override those
|
||||
* specific rows post-hoc: TRACK_H while collapsed, +STRIP_H and the group's
|
||||
* own automation rows once its `∿` is open. */
|
||||
* specific rows post-hoc: TRACK_H while collapsed, plus the group's own
|
||||
* automation rows once its `∿` is open. */
|
||||
function applyGroupStripHeights(
|
||||
tracks: readonly (readonly [number, readonly TimelineElement[]])[],
|
||||
rowHeights: number[],
|
||||
@@ -152,10 +151,9 @@ function applyGroupStripHeights(
|
||||
return tracks.map(([track], index) => {
|
||||
const group = groupByAnchor.get(track);
|
||||
if (!group || !expandedLaneOwnerIds.has(group.id)) return rowHeights[index] ?? TRACK_H;
|
||||
// The strip AND the group's own automation rows: `∿` discloses both (B7 put
|
||||
// the bus strip in this area, B2 put the lanes here), so a row sized for
|
||||
// only the strip clipped every lane it had just promised in the count.
|
||||
return TRACK_H + STRIP_H + groupOwnLaneCount(group) * AUTOMATION_LANE_H;
|
||||
// The group's own automation rows, which its `∿` discloses. A row sized
|
||||
// without them clipped every lane it had just promised in the count.
|
||||
return TRACK_H + groupOwnLaneCount(group) * AUTOMATION_LANE_H;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user