@hyperframes/studio 0.8.10 → 0.8.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/{hyperframes-player-DoTrWV8q.js → hyperframes-player-B243szNa.js} +1 -1
- package/dist/assets/{index-DV45YLqR.js → index-BTP86sD-.js} +1 -1
- package/dist/assets/index-BZfUJ2He.js +431 -0
- package/dist/assets/{index-BQs1fahB.js → index-DQ_lkuqY.js} +1 -1
- package/dist/assets/index-yGhfxxoL.css +1 -0
- package/dist/{chunk-ZALQ3CW7.js → chunk-6BT6DTB4.js} +2 -1
- package/dist/{domEditingLayers-XRVXRTNX.js → domEditingLayers-URA7BLWE.js} +2 -2
- package/dist/index.d.ts +84 -43
- package/dist/index.html +2 -2
- package/dist/index.js +6440 -5514
- package/dist/index.js.map +1 -1
- package/package.json +8 -8
- package/src/App.tsx +0 -2
- package/src/components/StudioRightPanel.tsx +15 -57
- package/src/components/editor/PropertyPanel.test.tsx +101 -3
- package/src/components/editor/PropertyPanel.tsx +15 -1
- package/src/components/editor/PropertyPanelEmptyState.test.tsx +74 -0
- package/src/components/editor/PropertyPanelEmptyState.tsx +35 -20
- package/src/components/editor/PropertyPanelFlat.tsx +82 -21
- package/src/components/editor/PropertyPanelFlatHeader.test.tsx +21 -0
- package/src/components/editor/TimelineFxPopover.tsx +24 -4
- package/src/components/editor/audioFxRevealTarget.test.ts +59 -0
- package/src/components/editor/audioFxRevealTarget.ts +113 -0
- package/src/components/editor/audioFxSignalPath.test.ts +50 -0
- package/src/components/editor/audioFxSignalPath.ts +71 -0
- package/src/components/editor/audioFxSummary.test.ts +8 -0
- package/src/components/editor/audioFxSummary.ts +28 -16
- package/src/components/editor/propertyPanelAudioFxGroup.test.tsx +101 -0
- package/src/components/editor/propertyPanelAudioFxGroup.tsx +92 -2
- package/src/components/editor/propertyPanelFlatClosedGroup.tsx +46 -0
- package/src/components/editor/propertyPanelFlatMotionSection.tsx +31 -0
- package/src/components/editor/propertyPanelFxCarveModule.tsx +203 -125
- package/src/components/editor/propertyPanelFxControls.test.tsx +50 -0
- package/src/components/editor/propertyPanelFxControls.tsx +10 -2
- package/src/components/editor/propertyPanelFxEqModule.tsx +3 -0
- package/src/components/editor/propertyPanelFxNodeRow.tsx +11 -1
- package/src/components/editor/propertyPanelFxPresetMenu.tsx +12 -2
- package/src/components/editor/propertyPanelFxRackChain.tsx +12 -5
- package/src/components/editor/propertyPanelFxSection.test.tsx +42 -0
- package/src/components/editor/propertyPanelFxSection.tsx +63 -4
- package/src/components/editor/propertyPanelFxSectionTypes.ts +19 -0
- package/src/components/editor/useAudioFxRevealSection.ts +64 -0
- package/src/components/editor/useAuditionTransport.test.ts +36 -0
- package/src/components/editor/useAuditionTransport.ts +78 -0
- package/src/components/editor/useFxAudition.ts +12 -1
- package/src/components/editor/useFxCarve.ts +0 -0
- package/src/components/editor/useFxCarveGrouping.test.ts +118 -0
- package/src/components/editor/useFxCarveGrouping.ts +139 -4
- package/src/components/editor/useFxCarveNodes.ts +153 -0
- package/src/components/editor/useFxLevelling.ts +5 -34
- package/src/components/nle/NLEContext.tsx +3 -10
- package/src/components/nle/PreviewPane.tsx +0 -1
- package/src/components/nle/useTimelineEditCallbacks.ts +5 -1
- package/src/contexts/DesignPanelInputContext.tsx +6 -5
- package/src/contexts/DomEditContext.tsx +10 -3
- package/src/contexts/FileManagerContext.tsx +3 -2
- package/src/contexts/PanelLayoutContext.tsx +3 -2
- package/src/contexts/StudioContext.tsx +7 -3
- package/src/contexts/TimelineEditContext.tsx +6 -2
- package/src/contexts/VariablePromoteContext.tsx +6 -2
- package/src/contexts/ViewModeContext.tsx +2 -2
- package/src/hooks/domEditDeleteMembers.ts +34 -0
- package/src/hooks/timelineAudioGroupCreate.ts +345 -0
- package/src/hooks/timelineAudioGroupVolume.test.ts +204 -0
- package/src/hooks/timelineAudioGroupVolume.ts +135 -3
- package/src/hooks/timelineEditingHelpers.test.ts +86 -0
- package/src/hooks/timelineEditingHelpers.ts +19 -7
- package/src/hooks/timelineElementFxAttribute.ts +0 -3
- package/src/hooks/timelineTrackVisibility.test.ts +95 -13
- package/src/hooks/timelineTrackVisibility.ts +6 -182
- package/src/hooks/useAudioGroupCarveAssignment.test.tsx +139 -0
- package/src/hooks/useBlockedTimelineEditToast.ts +27 -0
- package/src/hooks/useDomEditAttributeCommits.ts +13 -0
- package/src/hooks/useDomEditSession.test.tsx +57 -2
- package/src/hooks/useDomEditSession.ts +23 -24
- package/src/hooks/useEffectiveTimelineDuration.ts +9 -7
- package/src/hooks/useLivePlayheadTime.ts +3 -1
- package/src/hooks/useRemoveBackground.ts +70 -0
- package/src/hooks/useTimelineEditing.ts +5 -19
- package/src/hooks/useTimelineEditingTypes.ts +7 -0
- package/src/player/components/AutomationSelectionMenu.tsx +4 -0
- package/src/player/components/LayerDisclosureRow.tsx +59 -25
- package/src/player/components/PlayerControls.tsx +0 -38
- package/src/player/components/Timeline.test.ts +40 -1
- package/src/player/components/Timeline.tsx +5 -2
- package/src/player/components/TimelineAutomationLane.test.tsx +67 -0
- package/src/player/components/TimelineAutomationLane.tsx +89 -12
- package/src/player/components/TimelineAutomationLaneSlot.tsx +22 -3
- package/src/player/components/TimelineFxButton.test.tsx +184 -7
- package/src/player/components/TimelineFxButton.tsx +171 -23
- package/src/player/components/TimelineGroupHeader.test.tsx +0 -5
- package/src/player/components/TimelineGroupHeader.tsx +100 -102
- package/src/player/components/TimelineGroupLaneLabels.tsx +88 -0
- package/src/player/components/TimelineGroupRow.test.tsx +146 -0
- package/src/player/components/TimelineGroupRow.tsx +123 -64
- package/src/player/components/TimelineLanes.test.tsx +3 -3
- package/src/player/components/TimelineLanes.tsx +25 -23
- package/src/player/components/TimelineTrackHeader.test.tsx +298 -13
- package/src/player/components/TimelineTrackHeader.tsx +301 -381
- package/src/player/components/TimelineTrackPlainHeader.test.tsx +47 -69
- package/src/player/components/TimelineTrackPlainHeader.tsx +64 -54
- package/src/player/components/automationLaneData.test.ts +94 -0
- package/src/player/components/automationLaneData.ts +23 -0
- package/src/player/components/groupAutomationElement.test.ts +51 -0
- package/src/player/components/groupAutomationElement.ts +37 -0
- package/src/player/components/timelineCallbacks.ts +6 -2
- package/src/player/components/timelineKeyboardNavigation.test.ts +2 -2
- package/src/player/components/timelineKeyboardNavigation.ts +25 -8
- package/src/player/components/timelineLayout.ts +0 -1
- package/src/player/components/timelineViewModel.ts +30 -1
- package/src/player/components/trackHeaderLabelRows.tsx +328 -0
- package/src/player/components/useTimelineClipDisclosure.ts +41 -0
- package/src/player/components/useTimelineLaneRowIndexes.ts +1 -1
- package/src/player/components/useTimelineLogicalFocus.ts +2 -2
- package/src/player/components/useTimelineLogicalRows.test.tsx +2 -2
- package/src/player/components/useTimelineLogicalRows.ts +3 -3
- package/src/player/components/useTimelineTrackDerivations.ts +47 -8
- package/src/player/components/useTimelineTrackLayout.test.ts +119 -0
- package/src/player/components/useTimelineTrackLayout.ts +12 -4
- package/src/player/hooks/previewMessageRouter.ts +4 -11
- package/src/player/hooks/timelineSyncHydration.ts +395 -0
- package/src/player/hooks/useExpandedTimelineElements.test.ts +83 -0
- package/src/player/hooks/useExpandedTimelineElements.ts +40 -1
- package/src/player/hooks/useTimelinePlayer.ts +2 -7
- package/src/player/hooks/useTimelineSyncCallbacks.ts +43 -219
- package/src/player/lib/automationStoreSync.test.ts +26 -0
- package/src/player/lib/automationStoreSync.ts +28 -4
- package/src/player/lib/runtimeAudioId.test.ts +58 -0
- package/src/player/lib/timelineDOM.test.ts +115 -0
- package/src/player/lib/timelineDOM.ts +3 -32
- package/src/player/lib/timelineElementHelpers.ts +27 -0
- package/src/player/lib/timelineGroupInfo.ts +134 -0
- package/src/player/lib/timelineIframeHelpers.test.ts +21 -0
- package/src/player/lib/timelineIframeHelpers.ts +17 -0
- package/src/player/store/keyframeSlice.ts +79 -5
- package/src/player/store/playerStore.ts +37 -47
- package/src/player/store/playerStoreDevHandle.ts +22 -0
- package/src/player/store/playerStoreSelection.ts +41 -0
- package/src/player/store/timelineElement.ts +32 -0
- package/src/utils/hmrStableContext.ts +64 -0
- package/src/utils/timelineInspector.test.ts +35 -1
- package/src/utils/timelineInspector.ts +38 -0
- package/dist/assets/index-Ba9zbpT9.css +0 -1
- package/dist/assets/index-C6m2nHiX.js +0 -428
- package/src/hooks/useAudioSoloBridge.ts +0 -61
- package/src/hooks/useGroupLevel.ts +0 -36
- package/src/player/components/TimelineGroupBusStrip.test.tsx +0 -146
- package/src/player/components/TimelineGroupBusStrip.tsx +0 -103
- package/src/player/components/TimelineSoloButton.tsx +0 -32
- package/src/player/store/audioSoloSlice.test.ts +0 -113
- package/src/player/store/audioSoloSlice.ts +0 -43
- package/src/player/store/groupLevels.ts +0 -33
- /package/dist/{chunk-ZALQ3CW7.js.map → chunk-6BT6DTB4.js.map} +0 -0
- /package/dist/{domEditingLayers-XRVXRTNX.js.map → domEditingLayers-URA7BLWE.js.map} +0 -0
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import type { TimelineElement } from "../store/playerStore";
|
|
2
|
-
import { usePlayerStore } from "../store/playerStore";
|
|
3
|
-
import { isGroupHalfLitUnderSolo } from "../store/audioSoloSlice";
|
|
4
1
|
import {
|
|
5
2
|
HF_AUDIO_FX_ATTR,
|
|
6
3
|
serializeAudioFxChain,
|
|
@@ -11,28 +8,43 @@ import type { TimelineTrackGroupInfo } from "./useTimelineTrackDerivations";
|
|
|
11
8
|
import type { TimelineLogicalRow } from "./timelineKeyboardNavigation";
|
|
12
9
|
import { TimelineTrackRow } from "./TimelineTrackRow";
|
|
13
10
|
import { TimelineGroupHeader } from "./TimelineGroupHeader";
|
|
14
|
-
import { TimelineGroupBusStrip } from "./TimelineGroupBusStrip";
|
|
15
11
|
import { groupAutomationLanes } from "./automationLaneData";
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
12
|
+
import { groupAutomationElement } from "./groupAutomationElement";
|
|
13
|
+
import { TimelineAutomationLaneSlot } from "./TimelineAutomationLaneSlot";
|
|
14
|
+
import { TimelineGroupLaneLabels } from "./TimelineGroupLaneLabels";
|
|
15
|
+
import { LABEL_COL_W, TRACK_H } from "./timelineLayout";
|
|
16
|
+
import type { UseAutomationLanesResult } from "./useAutomationLanes";
|
|
17
|
+
import { useDomEditSelectionContextOptional } from "../../contexts/DomEditContext";
|
|
18
|
+
import { useTimelineEditContextOptional } from "../../contexts/TimelineEditContext";
|
|
18
19
|
import { useDomEditActionsContextOptional } from "../../contexts/DomEditContext";
|
|
19
20
|
|
|
21
|
+
/** Accent rail on a group-owned lane — the same green the member rail uses, so
|
|
22
|
+
* "this belongs to the group" reads the same in both places (groups doc §5). */
|
|
23
|
+
const GROUP_LANE_ACCENT = "#3CE6AC";
|
|
24
|
+
|
|
20
25
|
interface TimelineGroupRowProps {
|
|
21
26
|
index: number;
|
|
22
27
|
rowKey: number;
|
|
23
28
|
group: TimelineTrackGroupInfo;
|
|
24
29
|
logicalRow: TimelineLogicalRow;
|
|
25
|
-
tracks: readonly (readonly [number, readonly TimelineElement[]])[];
|
|
26
30
|
top: number;
|
|
27
31
|
height: number;
|
|
28
32
|
virtualized: boolean;
|
|
29
33
|
contentOrigin: number;
|
|
30
34
|
theme: TimelineTheme;
|
|
31
35
|
rovingTargetId?: string | null;
|
|
32
|
-
|
|
36
|
+
collapsedGroupIds: ReadonlySet<string>;
|
|
33
37
|
expandedLaneOwnerIds: ReadonlySet<string>;
|
|
34
38
|
toggleGroupExpanded: (id: string) => void;
|
|
35
39
|
toggleLaneOwnerExpanded: (id: string) => void;
|
|
40
|
+
lanes: UseAutomationLanesResult;
|
|
41
|
+
pps: number;
|
|
42
|
+
currentTime: number;
|
|
43
|
+
/** A group's lanes are in composition time (§1.3), so this is their span. */
|
|
44
|
+
compositionDuration: number;
|
|
45
|
+
beatTimes?: readonly number[];
|
|
46
|
+
contentGutter: number;
|
|
47
|
+
trackContentWidth: number;
|
|
36
48
|
}
|
|
37
49
|
|
|
38
50
|
/** A group's own row: the accessible shell (shared with track rows) plus the group header. */
|
|
@@ -41,31 +53,45 @@ export function TimelineGroupRow({
|
|
|
41
53
|
rowKey,
|
|
42
54
|
group,
|
|
43
55
|
logicalRow,
|
|
44
|
-
tracks,
|
|
45
56
|
top,
|
|
46
57
|
height,
|
|
47
58
|
virtualized,
|
|
48
59
|
contentOrigin,
|
|
49
60
|
theme,
|
|
50
61
|
rovingTargetId = null,
|
|
51
|
-
|
|
62
|
+
collapsedGroupIds,
|
|
52
63
|
expandedLaneOwnerIds,
|
|
53
64
|
toggleGroupExpanded,
|
|
54
65
|
toggleLaneOwnerExpanded,
|
|
66
|
+
lanes,
|
|
67
|
+
pps,
|
|
68
|
+
currentTime,
|
|
69
|
+
compositionDuration,
|
|
70
|
+
beatTimes,
|
|
71
|
+
contentGutter,
|
|
72
|
+
trackContentWidth,
|
|
55
73
|
}: TimelineGroupRowProps) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
74
|
+
// From the group, NOT from `tracks`: a collapsed group emits no member rows
|
|
75
|
+
// into the display list, and every one of these reads silently degraded to
|
|
76
|
+
// empty in that (default) state — the lane count
|
|
77
|
+
// read 0, and the bus strip fell back to "track 1", "track 2".
|
|
78
|
+
const memberElements = group.memberElements;
|
|
79
|
+
// The group wearing a clip's shape so the lane machinery can render it — see
|
|
80
|
+
// `groupAutomationElement` for why that beats a second, parallel lane path.
|
|
81
|
+
const groupElement = groupAutomationElement(group, compositionDuration);
|
|
82
|
+
// The binder writes through the dom-edit selection, so a group lane is
|
|
83
|
+
// editable exactly when the group is the selected element — which clicking
|
|
84
|
+
// its name in the header does.
|
|
85
|
+
const domSelection = useDomEditSelectionContextOptional()?.domEditSelection ?? null;
|
|
86
|
+
const isGroupSelected = domSelection?.id === group.id;
|
|
63
87
|
const isLaneOpen = expandedLaneOwnerIds.has(group.id);
|
|
64
|
-
|
|
88
|
+
// Optional, like every sibling row: Timeline renders outside the edit
|
|
89
|
+
// provider in read-only hosts (Timeline.test.ts asserts it), and the throwing
|
|
90
|
+
// hook took the whole timeline down with it the moment a group existed —
|
|
91
|
+
// not just this row.
|
|
92
|
+
const { onSetAudioGroupAttributeLive, onSetAudioGroupAttributeQuiet } =
|
|
93
|
+
useTimelineEditContextOptional();
|
|
65
94
|
const domEditActions = useDomEditActionsContextOptional();
|
|
66
|
-
const soloed = usePlayerStore((s) => s.soloed);
|
|
67
|
-
const toggleSolo = usePlayerStore((s) => s.toggleSolo);
|
|
68
|
-
const memberIds = memberElements.map((el) => el.key ?? el.id);
|
|
69
95
|
const writeGroupFxChain = (next: HfAudioFxChain, live: boolean) => {
|
|
70
96
|
const value = next.nodes.length ? serializeAudioFxChain(next) : null;
|
|
71
97
|
if (live) onSetAudioGroupAttributeLive?.(group.id, HF_AUDIO_FX_ATTR, value);
|
|
@@ -95,51 +121,84 @@ export function TimelineGroupRow({
|
|
|
95
121
|
borderColor={theme.rowBorder}
|
|
96
122
|
rovingTargetId={rovingTargetId}
|
|
97
123
|
>
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
onSetAudioGroupAttributeQuiet?.(
|
|
135
|
-
group.id,
|
|
136
|
-
"data-volume",
|
|
137
|
-
String(value),
|
|
138
|
-
"Set group volume",
|
|
139
|
-
)
|
|
140
|
-
}
|
|
124
|
+
{/* Header and its lane labels in ONE sticky column — the shape
|
|
125
|
+
`TimelineTrackHeader` already uses: a fixed TRACK_H line box with the
|
|
126
|
+
lane rows absolutely positioned beneath it, the whole thing pinned.
|
|
127
|
+
The labels used to be SIBLINGS of the header, so `absolute left-0`
|
|
128
|
+
resolved against the ROW, and the row is what scrolls horizontally —
|
|
129
|
+
they slid away with the canvas. Sticky lives here rather than on the
|
|
130
|
+
header, which keeps its own box inside; a zero-width sticky wrapper
|
|
131
|
+
around the labels alone does not work either, because as a flex item
|
|
132
|
+
after the header it starts at x = columnWidth, i.e. inside the lanes. */}
|
|
133
|
+
<div
|
|
134
|
+
className="sticky left-0 z-[12] shrink-0"
|
|
135
|
+
style={{ width: contentOrigin >= LABEL_COL_W ? LABEL_COL_W : contentOrigin }}
|
|
136
|
+
>
|
|
137
|
+
<TimelineGroupHeader
|
|
138
|
+
label={group.label}
|
|
139
|
+
memberCount={group.memberTracks.length}
|
|
140
|
+
isExpanded={!collapsedGroupIds.has(group.id)}
|
|
141
|
+
onToggleExpanded={() => toggleGroupExpanded(group.id)}
|
|
142
|
+
// The GROUP's own lanes, not its members'. `∿` is per-row (groups doc
|
|
143
|
+
// §5: "∿ is lit on vo-1 but not vo-2, the same control per row"), and
|
|
144
|
+
// counting the members' here made the group advertise curves it does
|
|
145
|
+
// not own and cannot show.
|
|
146
|
+
laneCount={groupAutomationLanes([groupElement]).length}
|
|
147
|
+
isLaneOpen={isLaneOpen}
|
|
148
|
+
onToggleLanes={() => toggleLaneOwnerExpanded(group.id)}
|
|
149
|
+
fxChain={group.fxChain}
|
|
150
|
+
onFxChainChange={(next) => writeGroupFxChain(next, false)}
|
|
151
|
+
onFxChainPreview={(next) => writeGroupFxChain(next, true)}
|
|
152
|
+
auditionSpans={memberElements}
|
|
153
|
+
onOpenFxRack={openGroupFxRack}
|
|
154
|
+
// Same width as every other row's header. The group row needs a real
|
|
155
|
+
// label column, but it gets one by turning `labelMode` on for the whole
|
|
156
|
+
// timeline (see Timeline.tsx) rather than by overhanging alone — an
|
|
157
|
+
// overhanging header paints opaquely across the rest of its row and
|
|
158
|
+
// stays pinned there through horizontal scroll.
|
|
159
|
+
columnWidth={contentOrigin >= LABEL_COL_W ? LABEL_COL_W : contentOrigin}
|
|
141
160
|
theme={theme}
|
|
142
161
|
/>
|
|
162
|
+
{/* The group's OWN curves, under the strip. Selected-gated exactly like a
|
|
163
|
+
clip's: the binder writes through the dom-edit selection, so a lane is
|
|
164
|
+
editable once the group is selected — which clicking its name does. */}
|
|
165
|
+
{/* The label column for those lanes, on the accent rail — inside the
|
|
166
|
+
sticky column above, so they pin with the header. */}
|
|
167
|
+
{isLaneOpen && (
|
|
168
|
+
<TimelineGroupLaneLabels
|
|
169
|
+
groupElement={groupElement}
|
|
170
|
+
groupLabel={group.label}
|
|
171
|
+
top={TRACK_H}
|
|
172
|
+
columnWidth={contentOrigin >= LABEL_COL_W ? LABEL_COL_W : contentOrigin}
|
|
173
|
+
gutterBackground={theme.gutterBackground}
|
|
174
|
+
accentColor={GROUP_LANE_ACCENT}
|
|
175
|
+
/>
|
|
176
|
+
)}
|
|
177
|
+
</div>
|
|
178
|
+
{isLaneOpen && (
|
|
179
|
+
// The same offset content cell a track row wraps its lanes in — the
|
|
180
|
+
// slot positions absolutely, so mounted straight on the row it resolved
|
|
181
|
+
// against the row instead and drew the envelope across the label gutter
|
|
182
|
+
// from x=0.
|
|
183
|
+
<div
|
|
184
|
+
role="gridcell"
|
|
185
|
+
aria-colindex={2}
|
|
186
|
+
style={{ width: trackContentWidth, marginLeft: contentGutter }}
|
|
187
|
+
className="relative"
|
|
188
|
+
>
|
|
189
|
+
<TimelineAutomationLaneSlot
|
|
190
|
+
elements={[groupElement]}
|
|
191
|
+
isSelected={() => isGroupSelected}
|
|
192
|
+
lanes={lanes}
|
|
193
|
+
pps={pps}
|
|
194
|
+
// Below the strip, which sits directly under the header row.
|
|
195
|
+
laneCount={0}
|
|
196
|
+
topOffset={TRACK_H}
|
|
197
|
+
accentColor={GROUP_LANE_ACCENT}
|
|
198
|
+
currentTime={currentTime}
|
|
199
|
+
beatTimes={beatTimes}
|
|
200
|
+
/>
|
|
201
|
+
</div>
|
|
143
202
|
)}
|
|
144
203
|
</TimelineTrackRow>
|
|
145
204
|
);
|
|
@@ -118,7 +118,7 @@ function renderLanes(options: RenderLanesOptions = {}): {
|
|
|
118
118
|
selectedElementId: null,
|
|
119
119
|
selectedElementIds: next.selectedElementIds ?? new Set(),
|
|
120
120
|
expandedClipIds: new Set(next.expandedClipIds ?? []),
|
|
121
|
-
|
|
121
|
+
collapsedGroupIds: new Set(),
|
|
122
122
|
expandedLaneOwnerIds: new Set(),
|
|
123
123
|
groups: [],
|
|
124
124
|
trackGroupOf: new Map(),
|
|
@@ -203,8 +203,8 @@ describe("TimelineLanes track numbering", () => {
|
|
|
203
203
|
const second = view.host.querySelector<HTMLButtonElement>('button[aria-label="Hide track 2"]');
|
|
204
204
|
act(() => second?.click());
|
|
205
205
|
|
|
206
|
-
//
|
|
207
|
-
//
|
|
206
|
+
// Both, and they are different numbers: the real key acts, the display row
|
|
207
|
+
// is what the undo-history label must announce (see `onToggleTrackHidden`).
|
|
208
208
|
expect(onToggleTrackHidden).toHaveBeenCalledWith(TRACK_B, true, 2);
|
|
209
209
|
act(() => view.root.unmount());
|
|
210
210
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Fragment, useId } from "react";
|
|
1
|
+
import { Fragment, useId, useMemo } from "react";
|
|
2
2
|
import { BeatStrip, BeatBackgroundLines } from "./BeatStrip";
|
|
3
3
|
import { TimelineClip } from "./TimelineClip";
|
|
4
4
|
import { TimelineCompactDiamonds } from "./TimelineCompactDiamonds";
|
|
@@ -9,6 +9,7 @@ import { useAutomationSelectionKeyboard } from "../../hooks/useAutomationSelecti
|
|
|
9
9
|
import { TimelineTrackHeader } from "./TimelineTrackHeader";
|
|
10
10
|
import { TimelineGroupRow } from "./TimelineGroupRow";
|
|
11
11
|
import { useTimelineLaneRowIndexes, useTimelineGroupDisclosure } from "./useTimelineLaneRowIndexes";
|
|
12
|
+
import { useTimelineClipDisclosure } from "./useTimelineClipDisclosure";
|
|
12
13
|
import {
|
|
13
14
|
isTrackRowExpanded,
|
|
14
15
|
resolveTrackKeyframeClip,
|
|
@@ -22,7 +23,6 @@ import { usePlayerStore } from "../store/playerStore";
|
|
|
22
23
|
import { isMultiDragPassenger, multiDragPassengerOffsetPx } from "./timelineMultiDragPreview";
|
|
23
24
|
import { useTimelineMultiDragActorWindows } from "./useTimelineMultiDragActorWindows";
|
|
24
25
|
import type { TimelineLanesProps } from "./timelineLaneProps";
|
|
25
|
-
import { trackStudioKeyframeLaneExpand } from "../../telemetry/events";
|
|
26
26
|
import { isAudioTimelineElement, isMusicTrack } from "../../utils/timelineInspector";
|
|
27
27
|
import { createClipGestureHandlers } from "./timelineClipGestureHandlers";
|
|
28
28
|
import { renderClipChildren, resolveClipRenderContext } from "./timelineClipChildren";
|
|
@@ -100,29 +100,24 @@ export function TimelineLanes({
|
|
|
100
100
|
// from resolving into a second timeline that renders the same logical rows.
|
|
101
101
|
const lanesIdPrefix = `timeline-lanes${useId().replaceAll(":", "")}`;
|
|
102
102
|
const expandedClipIds = usePlayerStore((s) => s.expandedClipIds);
|
|
103
|
-
const {
|
|
103
|
+
const { collapsedGroupIds, expandedLaneOwnerIds, toggleGroupExpanded, toggleLaneOwnerExpanded } =
|
|
104
104
|
useTimelineGroupDisclosure();
|
|
105
105
|
const automationLanes = useAutomationLanes();
|
|
106
|
+
// A group's automation clock is COMPOSITION time (groups doc §1.3), so its
|
|
107
|
+
// synthetic lane element spans the whole composition rather than a clip.
|
|
108
|
+
const compositionDuration = usePlayerStore((s) => s.duration);
|
|
106
109
|
useAutomationSelectionKeyboard({ lanes: automationLanes });
|
|
107
|
-
const expandClips = usePlayerStore((s) => s.expandClips);
|
|
108
|
-
const setClipExpanded = usePlayerStore((s) => s.setClipExpanded);
|
|
109
|
-
const toggleClipExpanded = usePlayerStore((s) => s.toggleClipExpanded);
|
|
110
110
|
const { logicalRowsByTrack, groupByAnchor } = useTimelineLaneRowIndexes(logicalRows, groups);
|
|
111
|
-
//
|
|
112
|
-
//
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
};
|
|
121
|
-
const toggleClipExpandedTracked = (key: string) => {
|
|
122
|
-
const willExpand = !expandedClipIds.has(key);
|
|
123
|
-
trackStudioKeyframeLaneExpand({ expanded: willExpand });
|
|
124
|
-
toggleClipExpanded(key);
|
|
125
|
-
};
|
|
111
|
+
// Which tracks are group MEMBERS, so their headers can render the level-2
|
|
112
|
+
// nesting their `aria-level` already reports.
|
|
113
|
+
const groupMemberTracks = useMemo(
|
|
114
|
+
() => new Set(groups.flatMap((group) => group.memberTracks)),
|
|
115
|
+
[groups],
|
|
116
|
+
);
|
|
117
|
+
const {
|
|
118
|
+
toggleRowExpanded: toggleRowExpandedTracked,
|
|
119
|
+
toggleClipExpanded: toggleClipExpandedTracked,
|
|
120
|
+
} = useTimelineClipDisclosure();
|
|
126
121
|
const actorWindows = useTimelineMultiDragActorWindows(
|
|
127
122
|
multiDragPreview,
|
|
128
123
|
rowsVirtualized,
|
|
@@ -163,17 +158,23 @@ export function TimelineLanes({
|
|
|
163
158
|
rowKey={rowKey}
|
|
164
159
|
group={group}
|
|
165
160
|
logicalRow={groupLogicalRow}
|
|
166
|
-
tracks={tracks}
|
|
167
161
|
top={rowGeometry.getRowTop(row)}
|
|
168
162
|
height={rowGeometry.getRowHeight(row)}
|
|
169
163
|
virtualized={rowsVirtualized}
|
|
170
164
|
contentOrigin={contentOrigin}
|
|
171
165
|
theme={theme}
|
|
172
166
|
rovingTargetId={keyboard.rovingTargetId}
|
|
173
|
-
|
|
167
|
+
collapsedGroupIds={collapsedGroupIds}
|
|
174
168
|
expandedLaneOwnerIds={expandedLaneOwnerIds}
|
|
175
169
|
toggleGroupExpanded={toggleGroupExpanded}
|
|
176
170
|
toggleLaneOwnerExpanded={toggleLaneOwnerExpanded}
|
|
171
|
+
lanes={automationLanes}
|
|
172
|
+
pps={pps}
|
|
173
|
+
currentTime={currentTime}
|
|
174
|
+
compositionDuration={compositionDuration}
|
|
175
|
+
beatTimes={beatAnalysis?.beatTimes}
|
|
176
|
+
contentGutter={contentGutter}
|
|
177
|
+
trackContentWidth={trackContentWidth}
|
|
177
178
|
/>
|
|
178
179
|
);
|
|
179
180
|
}
|
|
@@ -290,6 +291,7 @@ export function TimelineLanes({
|
|
|
290
291
|
currentTime={currentTime}
|
|
291
292
|
isTrackHidden={isTrackHidden}
|
|
292
293
|
isAudioTrack={isAudioTrack}
|
|
294
|
+
isGroupMember={groupMemberTracks.has(trackNum)}
|
|
293
295
|
theme={theme}
|
|
294
296
|
onToggleClipExpanded={() => {
|
|
295
297
|
const keys = els.map(getTimelineElementIdentity);
|