@hyperframes/studio 0.8.11 → 0.8.13
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-C6J6fsAD.js → hyperframes-player-BP9rShvS.js} +1 -1
- package/dist/assets/{index-1vMlv05L.js → index-CBm9bYHQ.js} +1 -1
- package/dist/assets/{index-BX0piiWc.js → index-CDerRsMw.js} +1 -1
- package/dist/assets/index-CQMHN4nk.js +431 -0
- 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 +7646 -6514
- 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/domSelectionTimelineMirror.ts +12 -3
- 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/useDomSelectionSelectionGuards.test.ts +115 -0
- 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/AutomationEnvelopePaths.tsx +54 -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 +180 -0
- package/src/player/components/TimelineAutomationLane.tsx +129 -36
- package/src/player/components/TimelineAutomationLaneSlot.tsx +22 -3
- package/src/player/components/TimelineFxButton.test.tsx +167 -21
- package/src/player/components/TimelineFxButton.tsx +156 -31
- package/src/player/components/TimelineGroupHeader.test.tsx +0 -5
- package/src/player/components/TimelineGroupHeader.tsx +100 -102
- package/src/player/components/TimelineGroupLaneLabels.tsx +99 -0
- package/src/player/components/TimelineGroupRow.test.tsx +222 -0
- package/src/player/components/TimelineGroupRow.tsx +140 -71
- 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/automationLaneDragMath.ts +28 -0
- package/src/player/components/automationLaneGeometry.ts +52 -13
- 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/useAutomationLaneGestures.ts +66 -18
- package/src/player/components/useAutomationSegmentDrag.ts +110 -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-DoW5v5eS.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
|
@@ -101,6 +101,37 @@ function mount(dataAttributes: Record<string, string>, alone = false, voices = 2
|
|
|
101
101
|
return { host, onSetAttributeQuiet, onSetAttributeLive };
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
+
function mountGroup(memberStart: number) {
|
|
105
|
+
const bus = document.createElement("hf-audio-group");
|
|
106
|
+
bus.id = "voiceover";
|
|
107
|
+
document.body.append(bus);
|
|
108
|
+
const member = document.createElement("audio");
|
|
109
|
+
member.id = "vo-1";
|
|
110
|
+
member.setAttribute("data-audio-group", "voiceover");
|
|
111
|
+
member.setAttribute("data-start", String(memberStart));
|
|
112
|
+
member.setAttribute("data-duration", "5");
|
|
113
|
+
document.body.append(member);
|
|
114
|
+
|
|
115
|
+
const host = document.createElement("div");
|
|
116
|
+
document.body.append(host);
|
|
117
|
+
const selection = {
|
|
118
|
+
dataAttributes: { "fx-chain": CHAIN },
|
|
119
|
+
id: "voiceover",
|
|
120
|
+
element: bus,
|
|
121
|
+
tagName: "hf-audio-group",
|
|
122
|
+
} as unknown as DomEditSelection;
|
|
123
|
+
act(() => {
|
|
124
|
+
createRoot(host).render(
|
|
125
|
+
<AudioFxGroup
|
|
126
|
+
element={selection}
|
|
127
|
+
onSetAttributeQuiet={vi.fn()}
|
|
128
|
+
onSetAttributeLive={vi.fn()}
|
|
129
|
+
/>,
|
|
130
|
+
);
|
|
131
|
+
});
|
|
132
|
+
return host;
|
|
133
|
+
}
|
|
134
|
+
|
|
104
135
|
const rowFor = (host: HTMLElement, label: string): HTMLElement | null => {
|
|
105
136
|
for (const row of Array.from(host.querySelectorAll<HTMLElement>(".hf-fx-row"))) {
|
|
106
137
|
if (row.querySelector(".hf-fx-label")?.textContent === label) return row;
|
|
@@ -565,6 +596,20 @@ describe("AudioFxGroup dynamic carve", () => {
|
|
|
565
596
|
expect(store().playbackRequest?.returnTo).toBe(42);
|
|
566
597
|
});
|
|
567
598
|
|
|
599
|
+
it("seeks a group audition to the next member span", () => {
|
|
600
|
+
act(() =>
|
|
601
|
+
usePlayerStore.setState({
|
|
602
|
+
isPlaying: false,
|
|
603
|
+
currentTime: 2,
|
|
604
|
+
requestedSeekTime: null,
|
|
605
|
+
}),
|
|
606
|
+
);
|
|
607
|
+
const host = mountGroup(10);
|
|
608
|
+
hoverPreset(host);
|
|
609
|
+
expect(store().requestedSeekTime).toBe(10);
|
|
610
|
+
leaveShelf(host);
|
|
611
|
+
});
|
|
612
|
+
|
|
568
613
|
it("leaves a transport the author started alone", () => {
|
|
569
614
|
// Stopping their playback because they passed over a preset would be the
|
|
570
615
|
// panel taking a decision nobody offered it.
|
|
@@ -1341,6 +1386,62 @@ describe("AudioFxGroup carve by default", () => {
|
|
|
1341
1386
|
expect(onSetAttributeQuiet.mock.calls.some((c) => c[0] === "data-fx-carve")).toBe(false);
|
|
1342
1387
|
expect(host.querySelector(".hf-fx-carve-module")).toBeNull();
|
|
1343
1388
|
});
|
|
1389
|
+
|
|
1390
|
+
/**
|
|
1391
|
+
* Mount a track of the test's own naming as the selection, with siblings.
|
|
1392
|
+
*
|
|
1393
|
+
* `mount` always calls the selected track `bed`, which is what let the near-end
|
|
1394
|
+
* hole go unnoticed: nothing ever selected a track whose NAME said voice.
|
|
1395
|
+
*/
|
|
1396
|
+
const mountNamed = (id: string, siblings: string[]) => {
|
|
1397
|
+
const onSetAttributeQuiet = vi.fn();
|
|
1398
|
+
const selected = document.createElement("audio");
|
|
1399
|
+
selected.id = id;
|
|
1400
|
+
document.body.append(selected);
|
|
1401
|
+
for (const other of siblings) {
|
|
1402
|
+
const el = document.createElement("audio");
|
|
1403
|
+
el.id = other;
|
|
1404
|
+
document.body.append(el);
|
|
1405
|
+
}
|
|
1406
|
+
const host = document.createElement("div");
|
|
1407
|
+
document.body.append(host);
|
|
1408
|
+
act(() => {
|
|
1409
|
+
createRoot(host).render(
|
|
1410
|
+
<AudioFxGroup
|
|
1411
|
+
element={
|
|
1412
|
+
{
|
|
1413
|
+
dataAttributes: { "fx-chain": "" },
|
|
1414
|
+
id,
|
|
1415
|
+
element: selected,
|
|
1416
|
+
} as unknown as DomEditSelection
|
|
1417
|
+
}
|
|
1418
|
+
onSetAttributeQuiet={onSetAttributeQuiet}
|
|
1419
|
+
onSetAttributeLive={vi.fn()}
|
|
1420
|
+
/>,
|
|
1421
|
+
);
|
|
1422
|
+
});
|
|
1423
|
+
return { host, onSetAttributeQuiet };
|
|
1424
|
+
};
|
|
1425
|
+
|
|
1426
|
+
// The reported bug. A carve makes room in a bed for a voice, so a voice track
|
|
1427
|
+
// is the one thing that can never be the bed — `couldBeCarveSource` has said
|
|
1428
|
+
// as much since it was written, and nothing called it. Selecting a narration
|
|
1429
|
+
// clip offered it the module, found one candidate, and applied a carve nobody
|
|
1430
|
+
// asked for.
|
|
1431
|
+
it("never offers the carve on a track whose name says voice", () => {
|
|
1432
|
+
const { host, onSetAttributeQuiet } = mountNamed("vo-2", ["music-bed"]);
|
|
1433
|
+
expect(onSetAttributeQuiet.mock.calls.some((c) => c[0] === "data-fx-carve")).toBe(false);
|
|
1434
|
+
expect(host.querySelector(".hf-fx-carve-module")).toBeNull();
|
|
1435
|
+
});
|
|
1436
|
+
|
|
1437
|
+
// Offering is a suggestion, applying is a decision. An unnamed track keeps the
|
|
1438
|
+
// module — the author may know better than the name does — but nothing is
|
|
1439
|
+
// written until they say so.
|
|
1440
|
+
it("offers but does not apply on a track whose name says nothing", () => {
|
|
1441
|
+
const { host, onSetAttributeQuiet } = mountNamed("a1", ["vo-1"]);
|
|
1442
|
+
expect(host.querySelector(".hf-fx-carve-module")).not.toBeNull();
|
|
1443
|
+
expect(onSetAttributeQuiet.mock.calls.some((c) => c[0] === "data-fx-carve")).toBe(false);
|
|
1444
|
+
});
|
|
1344
1445
|
});
|
|
1345
1446
|
|
|
1346
1447
|
describe("AudioFxGroup carve source list", () => {
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* budget, and self-contained enough to test on its own.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { useState } from "react";
|
|
10
|
+
import { useMemo, useState } from "react";
|
|
11
11
|
import {
|
|
12
12
|
HF_AUDIO_FX_ATTR,
|
|
13
13
|
HF_AUDIO_FX_DATA_KEY,
|
|
@@ -40,12 +40,47 @@ import {
|
|
|
40
40
|
} from "./propertyPanelAutomation";
|
|
41
41
|
import type { DomEditSelection } from "./domEditingTypes";
|
|
42
42
|
import { useLivePlayheadTime } from "../../hooks/useLivePlayheadTime";
|
|
43
|
+
import { usePlayerStore } from "../../player/store/playerStore";
|
|
44
|
+
import { isRevealedAudioFxRequestCurrent } from "../../player/store/keyframeSlice";
|
|
43
45
|
import { FxSection } from "./propertyPanelFxSection.js";
|
|
44
46
|
import { clipStart } from "./propertyPanelAudioFxGroupUtils.js";
|
|
45
47
|
import { useFxChainObserved } from "./useFxChainObserved.js";
|
|
46
48
|
import { useFxCarve } from "./useFxCarve.js";
|
|
49
|
+
import { audioFxSignalPath } from "./audioFxSignalPath.js";
|
|
50
|
+
import type { AuditionSpan } from "./useAuditionTransport.js";
|
|
51
|
+
import {
|
|
52
|
+
HF_AUDIO_GROUP_ATTR,
|
|
53
|
+
HF_AUDIO_GROUP_TAG,
|
|
54
|
+
resolveAudioGroups,
|
|
55
|
+
} from "@hyperframes/core/audio-groups";
|
|
47
56
|
import { useFxLevelling } from "./useFxLevelling.js";
|
|
48
57
|
|
|
58
|
+
function auditionSpan(startRaw: string | undefined, durationRaw: string | undefined) {
|
|
59
|
+
const start = Number.parseFloat(startRaw ?? "");
|
|
60
|
+
const duration = Number.parseFloat(durationRaw ?? "");
|
|
61
|
+
return Number.isFinite(start) && Number.isFinite(duration) && duration > 0
|
|
62
|
+
? { start, duration }
|
|
63
|
+
: null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** The selected clip, or every current member when the selected rack is a bus. */
|
|
67
|
+
function auditionSpansFor(element: DomEditSelection): AuditionSpan[] {
|
|
68
|
+
const own = auditionSpan(element.dataAttributes?.["start"], element.dataAttributes?.["duration"]);
|
|
69
|
+
if (own) return [own];
|
|
70
|
+
if (element.tagName?.toLowerCase() !== HF_AUDIO_GROUP_TAG || !element.id) return [];
|
|
71
|
+
const doc = element.element?.ownerDocument;
|
|
72
|
+
if (!doc) return [];
|
|
73
|
+
return [...doc.querySelectorAll(`audio[${HF_AUDIO_GROUP_ATTR}]`)]
|
|
74
|
+
.filter((member) => member.getAttribute(HF_AUDIO_GROUP_ATTR) === element.id)
|
|
75
|
+
.flatMap((member) => {
|
|
76
|
+
const span = auditionSpan(
|
|
77
|
+
member.getAttribute("data-start") ?? undefined,
|
|
78
|
+
member.getAttribute("data-duration") ?? undefined,
|
|
79
|
+
);
|
|
80
|
+
return span ? [span] : [];
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
49
84
|
/**
|
|
50
85
|
* Bridges the FX panel to the element/attribute world. Chain and carve are
|
|
51
86
|
* serialised onto the element the way colour grading carries its config, so
|
|
@@ -112,6 +147,9 @@ export function AudioFxGroup({
|
|
|
112
147
|
* came under the playhead.
|
|
113
148
|
*/
|
|
114
149
|
const playhead = useLivePlayheadTime();
|
|
150
|
+
const revealRequest = usePlayerStore((s) => s.revealedAudioFxTarget);
|
|
151
|
+
const timelineProjectId = usePlayerStore((s) => s.timelineProjectId);
|
|
152
|
+
const timelineSessionEpoch = usePlayerStore((s) => s.timelineSessionEpoch);
|
|
115
153
|
const localTime = playhead - clipStart(element.dataAttributes?.["start"]);
|
|
116
154
|
const liveAutomationValues = ((): Map<string, number> => {
|
|
117
155
|
const values = new Map<string, number>();
|
|
@@ -226,6 +264,31 @@ export function AudioFxGroup({
|
|
|
226
264
|
|
|
227
265
|
const [analysing, setAnalysing] = useState(false);
|
|
228
266
|
|
|
267
|
+
// The rack's In/Out lines. Resolved from the live document because a group's
|
|
268
|
+
// membership lives on the members, so neither end of the routing can be read
|
|
269
|
+
// off the selected element alone.
|
|
270
|
+
// Keyed on the store's element array as well as the selection: membership is
|
|
271
|
+
// held by the MEMBERS, so a clip joining or leaving this group changes neither
|
|
272
|
+
// `element` nor its attributes, and the path went stale — "OUT to mix" on a
|
|
273
|
+
// clip that had just been grouped. `syncStoredGroupAttribute` and
|
|
274
|
+
// `updateElement` both replace the array, so its identity is the cheap signal
|
|
275
|
+
// that membership may have moved.
|
|
276
|
+
const storeElements = usePlayerStore((s) => s.elements);
|
|
277
|
+
const signalPath = useMemo(() => {
|
|
278
|
+
const doc = element.element?.ownerDocument;
|
|
279
|
+
return audioFxSignalPath(
|
|
280
|
+
element.tagName?.toLowerCase(),
|
|
281
|
+
element.id ?? undefined,
|
|
282
|
+
doc ? resolveAudioGroups(doc) : [],
|
|
283
|
+
);
|
|
284
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
285
|
+
}, [element, storeElements]);
|
|
286
|
+
|
|
287
|
+
// A bus has no span of its own, so resolve the live members. The
|
|
288
|
+
// `storeElements` subscription above rerenders this panel when membership
|
|
289
|
+
// changes without changing the selection.
|
|
290
|
+
const auditionSpans = auditionSpansFor(element);
|
|
291
|
+
|
|
229
292
|
const { carvedAgainstBy, sourceOptions, setCarve } = useFxCarve(
|
|
230
293
|
element,
|
|
231
294
|
chain,
|
|
@@ -249,6 +312,32 @@ export function AudioFxGroup({
|
|
|
249
312
|
|
|
250
313
|
return (
|
|
251
314
|
<FxSection
|
|
315
|
+
// A lane's reveal request, but only when it names THIS element: the rack
|
|
316
|
+
// shows one element, and a request aimed at another must not reopen
|
|
317
|
+
// whatever happens to be mounted. Stale requests (other project, pre-
|
|
318
|
+
// reload session) are refused by the same check.
|
|
319
|
+
revealTarget={
|
|
320
|
+
revealRequest &&
|
|
321
|
+
revealRequest.elementKey === element.id &&
|
|
322
|
+
isRevealedAudioFxRequestCurrent(revealRequest, {
|
|
323
|
+
timelineProjectId,
|
|
324
|
+
timelineSessionEpoch,
|
|
325
|
+
})
|
|
326
|
+
? revealRequest.automationTarget
|
|
327
|
+
: null
|
|
328
|
+
}
|
|
329
|
+
// Forwarded, not dropped: the section consumes by nonce, so without it a
|
|
330
|
+
// request never fires and a second click on the same lane is inert.
|
|
331
|
+
revealNonce={
|
|
332
|
+
revealRequest &&
|
|
333
|
+
revealRequest.elementKey === element.id &&
|
|
334
|
+
isRevealedAudioFxRequestCurrent(revealRequest, {
|
|
335
|
+
timelineProjectId,
|
|
336
|
+
timelineSessionEpoch,
|
|
337
|
+
})
|
|
338
|
+
? revealRequest.nonce
|
|
339
|
+
: null
|
|
340
|
+
}
|
|
252
341
|
// Locked while the carve is measuring. `analyse` captures the chain and
|
|
253
342
|
// the automation BEFORE its fetch and decode, then rewrites the whole
|
|
254
343
|
// attribute from that snapshot — so an effect added, or a knob committed,
|
|
@@ -273,7 +362,8 @@ export function AudioFxGroup({
|
|
|
273
362
|
next.nodes.length ? serializeAudioFxChain(next) : null,
|
|
274
363
|
)
|
|
275
364
|
}
|
|
276
|
-
|
|
365
|
+
signalPath={signalPath}
|
|
366
|
+
onAuditionTransport={(on) => auditionTransport(on, auditionSpans)}
|
|
277
367
|
onChainPreview={(next) =>
|
|
278
368
|
// Live writes skip the preview refresh entirely, so dragging a knob no
|
|
279
369
|
// longer reloads the composition and restarts playback on every pixel.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A collapsed group's header row in the flat inspector.
|
|
3
|
+
*
|
|
4
|
+
* Its own module so `PropertyPanelFlat.tsx` stays under the studio's 600-line
|
|
5
|
+
* cap; it reads only its arguments, which is what makes it separable.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { DesignPanelInputProvider } from "../../contexts/DesignPanelInputContext";
|
|
9
|
+
import { slugifyDesignInput } from "../../utils/designInputTracking";
|
|
10
|
+
import { FlatGroupHeader } from "./propertyPanelFlatPrimitives";
|
|
11
|
+
import type { FlatGroupDescriptor } from "./propertyPanelFlatDescriptors";
|
|
12
|
+
|
|
13
|
+
/** Its title, its one-line summary, and the entrance animation only the group
|
|
14
|
+
* just toggled gets. */
|
|
15
|
+
export function closedGroupHeader(
|
|
16
|
+
group: FlatGroupDescriptor,
|
|
17
|
+
toggleOpen: (id: string) => void,
|
|
18
|
+
justToggledIds: readonly string[],
|
|
19
|
+
) {
|
|
20
|
+
return (
|
|
21
|
+
<DesignPanelInputProvider key={group.id} section={slugifyDesignInput(group.title)}>
|
|
22
|
+
<FlatGroupHeader
|
|
23
|
+
title={group.title}
|
|
24
|
+
isOpen={false}
|
|
25
|
+
onToggleOpen={() => toggleOpen(group.id)}
|
|
26
|
+
summary={group.summary}
|
|
27
|
+
animateEntrance={justToggledIds.includes(group.id)}
|
|
28
|
+
/>
|
|
29
|
+
</DesignPanelInputProvider>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Is the selection hidden RIGHT NOW.
|
|
35
|
+
*
|
|
36
|
+
* `selectedElementHidden` is derived from `timelineElements`, and an
|
|
37
|
+
* `<hf-audio-group>` is not one — it is a mixer bus, and the runtime no longer
|
|
38
|
+
* stamps timing on it, so it has no timeline row to carry a `hidden` flag. Its
|
|
39
|
+
* `data-hidden` lives only on the element, so the attribute is the fallback.
|
|
40
|
+
*/
|
|
41
|
+
export function isSelectionHidden(
|
|
42
|
+
fromTimeline: boolean,
|
|
43
|
+
element: { dataAttributes?: Record<string, string | undefined> } | null | undefined,
|
|
44
|
+
): boolean {
|
|
45
|
+
return fromTimeline || element?.dataAttributes?.["hidden"] != null;
|
|
46
|
+
}
|
|
@@ -172,3 +172,34 @@ export function FlatMotionSection({
|
|
|
172
172
|
</div>
|
|
173
173
|
);
|
|
174
174
|
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* What the Motion section is called, and what its collapsed line says.
|
|
178
|
+
*
|
|
179
|
+
* "Motion" names the tween editor. On audio the section is Start/Duration/End
|
|
180
|
+
* and nothing else, so the label would promise what it no longer offers — and
|
|
181
|
+
* "Motion: 0 effects" on a sound is a category error, hence the span instead of
|
|
182
|
+
* a count.
|
|
183
|
+
*
|
|
184
|
+
* Keyed on the TAG by its caller, not on whether the effects half is showing:
|
|
185
|
+
* that half also disappears when a host simply has not wired the GSAP handlers,
|
|
186
|
+
* and a div in that state is still a thing that moves — renaming its section
|
|
187
|
+
* would be describing the host's wiring rather than the element.
|
|
188
|
+
*/
|
|
189
|
+
export function motionSectionLabel(args: {
|
|
190
|
+
timingOnly: boolean;
|
|
191
|
+
start: number;
|
|
192
|
+
duration: number;
|
|
193
|
+
effectCount: number;
|
|
194
|
+
}): { title: string; summary: string } {
|
|
195
|
+
if (args.timingOnly) {
|
|
196
|
+
return {
|
|
197
|
+
title: "Timing",
|
|
198
|
+
summary: `${formatTimingValue(args.start)} – ${formatTimingValue(args.start + args.duration)}`,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
title: "Motion",
|
|
203
|
+
summary: `${args.effectCount} effect${args.effectCount === 1 ? "" : "s"}`,
|
|
204
|
+
};
|
|
205
|
+
}
|