mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-04 07:19:52 +00:00
C1: the FX button in the track/group header, and its popover — the
"reach FX from the timeline" entry point, last on purpose because it
targets a group or a single clip, never "a track" (N clips = N chains
is the ill-defined thing the design doc refuses to build).
The button (TimelineFxButton.tsx): renders on group rows and on track
rows holding exactly one audio clip, reading "FX" (or "FX n" once the
target's data-fx-chain has n enabled nodes). A multi-clip ungrouped
audio track gets a pointer instead ("Group these clips to add effects
to all of them" + a Group action) rather than silently hiding the
entry point — reuses B6's exact auto-grouping write
(useAudioGroupCarveAssignment, exposed as onGroupClips) with a minted
group id (mintGroupId, exported from useFxCarveGrouping.ts).
The popover (TimelineFxPopover.tsx, components/editor/): a thin
positioner around FxPresetMenu exactly as the property panel renders
it — same audition contract (useFxAudition), same preset-apply
computation (extracted into useApplyAudioFxPreset.ts's
applyPresetToChain, now shared with propertyPanelFxSection.tsx's own
applyPreset rather than duplicated). Escape closes without
deselecting whatever is behind it; an outside pointerdown dismisses.
Footer's "+ effect"/"Open rack ›" both select the target and hand off
to the property panel (a simplification from the step doc's two
distinct behaviors — remotely toggling the rack's own internal
"adding" state isn't plumbed anywhere, and building that plumbing
would be new UI-state wiring beyond what "reuse existing selection
dispatch" asks for).
Writes, one path per target kind, neither a new persistence mechanism:
- Group: B7/B5's existing onSetAudioGroupAttributeLive/Quiet
(data-fx-chain, same as data-volume/data-hidden already do).
- Clip: a NEW onSetElementAttributeLive/Quiet pair
(timelineElementFxAttribute.ts), addressed by the TimelineElement
itself rather than the current selection. This is the one real
architectural gap the step doc's assumption didn't survive: the
property panel's onSetAttributeQuiet closes over domEditSelection,
so writing a clip that isn't already selected has no synchronous
path through it. Extracted the shared live-patch-then-persist core
(persistElementAttribute, timelineEditingHelpers.ts) out of both
this new path and the existing setAudioGroupAttribute, which the
fallow duplication gate flagged as a 66-line clone on first pass —
now a single ~50-line core parameterized by patchLive/readLive, with
each caller a ~15-line wrapper resolving its own patch target
(buildPatchTarget({domId}) for a group, buildPatchTarget(element)
for an arbitrary clip) and live-DOM lookup.
Data plumbing: HfAudioGroup.fxChain (already on the B1 model) mirrored
onto TimelineElement.audioGroupFxChain (timelineDOM.ts's groupInfoFor
cache) and TimelineTrackGroupInfo.fxChain (useTimelineTrackDerivations.ts),
alongside the existing volume/hidden mirrors.
Deferred: the property panel's own rack doesn't (yet) expose a way to
remotely force its add-menu open, so "+ effect" and "Open rack ›"
converge on the same navigation rather than the step doc's two
distinct ones. A grouped multi-clip track (some clips already carry
data-audio-group) gets neither the chain button nor the pointer —
its members' own per-clip FX buttons still work individually, and the
group's own FX button on TimelineGroupHeader covers the group level.
Gates: bun run build clean; packages/studio full suite 4286/4304 (18
pre-existing todo, up from 4276/4294 — 10 new tests, 0 regressions);
new TimelineFxPopover.test.tsx (6) + TimelineFxButton.test.tsx (4)
cover exactly-one-write-per-apply, hover-audition-reverts-on-leave,
Escape-without-deselecting, outside/inside pointerdown dismissal, and
the group-pointer's Group action; oxfmt/oxlint clean on all 22 touched
files; fallow clean (0 new dead-code/unused-export/duplication
findings — the pointer test caught during the first commit attempt).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
89 lines
4.0 KiB
TypeScript
89 lines
4.0 KiB
TypeScript
/**
|
|
* One row in the timeline: a clip as Studio needs it, translated from the
|
|
* runtime's manifest at createTimelineElementFromManifestClip.
|
|
*
|
|
* Split out of playerStore, which had reached the 600-line studio ceiling and
|
|
* could not carry another field. Re-exported from there, so every existing
|
|
* importer is unaffected.
|
|
*/
|
|
|
|
import type { ClipManifestClip } from "../lib/playbackTypes";
|
|
|
|
export interface TimelineElement {
|
|
id: string;
|
|
label?: string;
|
|
key?: string;
|
|
kind?: ClipManifestClip["kind"];
|
|
tag: string;
|
|
start: number;
|
|
duration: number;
|
|
track: number;
|
|
/**
|
|
* The data-track-index as written in the source file. Set at the manifest
|
|
* translation boundary (createTimelineElementFromManifestClip) from the
|
|
* runtime clip's verbatim track, and preserved through display-lane remaps
|
|
* (normalizeToZones packs sparse authored tracks onto contiguous display
|
|
* lanes; expanded sub-comp children get synthetic display rows). Lane edits
|
|
* must persist THIS space — writing a display-lane number into a sparse file
|
|
* re-targets the wrong track. For an expanded child the value is in its OWN
|
|
* source file's coordinate space, not the host timeline's.
|
|
*/
|
|
authoredTrack?: number;
|
|
/** Resolved z-index for stacking-aware timeline ordering. */
|
|
zIndex?: number;
|
|
/** True when the effective z-index was authored inline or through CSS, not auto. */
|
|
hasExplicitZIndex?: boolean;
|
|
/** Canonical CSS stacking context this element's z-index participates in. */
|
|
stackingContextId?: string | null;
|
|
/** Nearest parent composition context, matching RuntimeTimelineClip. */
|
|
parentCompositionId?: string | null;
|
|
/** Composition ancestry from root to nearest parent, matching RuntimeTimelineClip. */
|
|
compositionAncestors?: string[];
|
|
domId?: string;
|
|
/** Stable `data-hf-id` attribute value — used as primary patch target when present */
|
|
hfId?: string;
|
|
/** Best-effort selector used when patching source HTML back from timeline edits */
|
|
selector?: string;
|
|
/** Zero-based occurrence index for non-unique selectors */
|
|
selectorIndex?: number;
|
|
/** Source composition file that owns this element, when known */
|
|
sourceFile?: string;
|
|
src?: string;
|
|
playbackStart?: number;
|
|
playbackStartAttr?: "media-start" | "playback-start";
|
|
playbackRate?: number;
|
|
sourceDuration?: number;
|
|
volume?: number;
|
|
/** Verbatim `data-fx-chain` / `data-automation`; see automationLaneData. */
|
|
fxChain?: string;
|
|
automation?: string;
|
|
/** Path from data-composition-src — identifies sub-composition elements */
|
|
compositionSrc?: string;
|
|
/** Whether this row came from authored clip timing or Studio's full-duration layer fallback. */
|
|
timingSource?: "authored" | "implicit";
|
|
/** Set by data-timeline-locked on the host element — disables move and trim in Studio. */
|
|
timelineLocked?: boolean;
|
|
/** Set by data-hidden on the host element — hides the clip in preview and render. */
|
|
hidden?: boolean;
|
|
/** Value of data-timeline-role attribute — used to identify music vs. voiceover. */
|
|
timelineRole?: string;
|
|
/** Verbatim `data-audio-group` — the id of the `<hf-audio-group>` this clip belongs to, when any. */
|
|
audioGroup?: string;
|
|
/** The owning group's `data-label` (falls back to its id) — resolved once per parse. */
|
|
audioGroupLabel?: string;
|
|
/** The owning group's `data-volume` (defaults to 1) — resolved once per parse. */
|
|
audioGroupVolume?: number;
|
|
/** The owning group's `data-hidden` (defaults to false) — resolved once per parse. */
|
|
audioGroupHidden?: boolean;
|
|
/** The owning group's serialized `data-fx-chain`, when set — resolved once per parse. */
|
|
audioGroupFxChain?: string;
|
|
/**
|
|
* Set by useExpandedTimelineElements on an inline-expanded sub-composition
|
|
* child: the absolute master-timeline start of the sub-comp host the child
|
|
* lives in. Presence marks the element as expanded; edits subtract it to get
|
|
* the child's local (sourceFile-relative) time. Works at any nesting depth.
|
|
*/
|
|
expandedParentStart?: number;
|
|
expandedHostKey?: string;
|
|
}
|