@hyperframes/studio 0.8.11 → 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-C6J6fsAD.js → hyperframes-player-B243szNa.js} +1 -1
- package/dist/assets/{index-BX0piiWc.js → index-BTP86sD-.js} +1 -1
- package/dist/assets/index-BZfUJ2He.js +431 -0
- package/dist/assets/{index-1vMlv05L.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 +6431 -5515
- 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 +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 +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-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
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a carve analysis COMPILES TO: the filter nodes it mints and the
|
|
3
|
+
* automation lanes it writes for them.
|
|
4
|
+
*
|
|
5
|
+
* Split out of `useFxCarve.ts` to keep it under the studio's 600-line cap. This
|
|
6
|
+
* half is pure — it takes measurements and returns nodes and lanes — while the
|
|
7
|
+
* hook keeps the effects, the persistence and the auto-carve decisions.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
defaultAudioFxParams,
|
|
12
|
+
mintAudioFxNodeId,
|
|
13
|
+
type HfAudioFxChain,
|
|
14
|
+
type HfAudioFxNode,
|
|
15
|
+
} from "@hyperframes/core/audio-fx";
|
|
16
|
+
import {
|
|
17
|
+
analyseCarveBands,
|
|
18
|
+
analyseCarveDuck,
|
|
19
|
+
analyseCarveDynamics,
|
|
20
|
+
carveBandsToChain,
|
|
21
|
+
carveProfile,
|
|
22
|
+
mixCarveSources,
|
|
23
|
+
} from "@hyperframes/core/audio-carve";
|
|
24
|
+
import { fxAutomationTarget, type HfAutomationLane } from "@hyperframes/core/audio-automation";
|
|
25
|
+
import { clipStart } from "./propertyPanelAudioFxGroupUtils.js";
|
|
26
|
+
|
|
27
|
+
/** Decode rate every carve measurement shares — see `measureCarve`. */
|
|
28
|
+
const DECODE_SAMPLE_RATE = 48000;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Turns the analysed bands (and, if the carve is asked to match levels, a
|
|
32
|
+
* ducking envelope) into chain nodes — tagged so a re-run replaces them
|
|
33
|
+
* instead of stacking, and minted against the nodes already claiming an id
|
|
34
|
+
* because a dynamic carve automates these filters and a lane addresses its
|
|
35
|
+
* node by id.
|
|
36
|
+
*/
|
|
37
|
+
export function mintCarveNodes(
|
|
38
|
+
chain: HfAudioFxChain,
|
|
39
|
+
carved: HfAudioFxChain,
|
|
40
|
+
duck: { t: number; v: number }[],
|
|
41
|
+
): { next: HfAudioFxChain; carvedNodes: HfAudioFxNode[]; duckNode: HfAudioFxNode | null } {
|
|
42
|
+
const kept = chain.nodes.filter((n) => !n.fromCarve);
|
|
43
|
+
let claimed: HfAudioFxChain = { version: 1, nodes: kept };
|
|
44
|
+
const mint = (node: HfAudioFxNode): HfAudioFxNode => {
|
|
45
|
+
const withId = { ...node, id: mintAudioFxNodeId(claimed), fromCarve: true };
|
|
46
|
+
claimed = { version: 1, nodes: [...claimed.nodes, withId] };
|
|
47
|
+
return withId;
|
|
48
|
+
};
|
|
49
|
+
const carvedNodes: HfAudioFxNode[] = carved.nodes.map(mint);
|
|
50
|
+
// The gain stage sits after the filters, and only exists when the carve was
|
|
51
|
+
// asked to make level room. It sits at 0 and is driven by the envelope below.
|
|
52
|
+
const duckNode =
|
|
53
|
+
duck.length > 0
|
|
54
|
+
? mint({ type: "gain", enabled: true, params: { ...defaultAudioFxParams("gain"), gain: 0 } })
|
|
55
|
+
: null;
|
|
56
|
+
return {
|
|
57
|
+
next: { version: 1, nodes: [...carvedNodes, ...(duckNode ? [duckNode] : []), ...kept] },
|
|
58
|
+
carvedNodes,
|
|
59
|
+
duckNode,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Decode every voice, mix them onto the bed's own clock, and measure the
|
|
65
|
+
* bands (and, if the profile calls for it, the ducking envelope) from that
|
|
66
|
+
* mix. Null on anything that leaves nothing to build a carve from — the
|
|
67
|
+
* platform lacking an offline context, or a mix that decoded to silence.
|
|
68
|
+
*/
|
|
69
|
+
export async function measureCarve(
|
|
70
|
+
doc: Document,
|
|
71
|
+
voices: { src: string; start: string | null }[],
|
|
72
|
+
strength: number,
|
|
73
|
+
bedStartAttr: string | null | undefined,
|
|
74
|
+
bedSrc: string | null | undefined,
|
|
75
|
+
): Promise<{
|
|
76
|
+
bands: ReturnType<typeof analyseCarveBands>;
|
|
77
|
+
carved: HfAudioFxChain;
|
|
78
|
+
duck: { t: number; v: number }[];
|
|
79
|
+
voiceMix: Float32Array;
|
|
80
|
+
} | null> {
|
|
81
|
+
// Decoded in an OfflineAudioContext, not a live one. Opening a second output
|
|
82
|
+
// device mid-playback makes the running track glitch while the hardware is
|
|
83
|
+
// reconfigured; an offline context touches no device.
|
|
84
|
+
const Ctor =
|
|
85
|
+
window.OfflineAudioContext ??
|
|
86
|
+
(window as unknown as { webkitOfflineAudioContext?: typeof OfflineAudioContext })
|
|
87
|
+
.webkitOfflineAudioContext;
|
|
88
|
+
if (!Ctor) return null;
|
|
89
|
+
const decode = async (relative: string): Promise<AudioBuffer> => {
|
|
90
|
+
const res = await fetch(new URL(relative, doc.baseURI).href);
|
|
91
|
+
return new Ctor(1, 1, DECODE_SAMPLE_RATE).decodeAudioData(await res.arrayBuffer());
|
|
92
|
+
};
|
|
93
|
+
const bedStart = clipStart(bedStartAttr);
|
|
94
|
+
// Every voice, summed onto the bed's own clock. One question — where and
|
|
95
|
+
// when is speech masking this bed — with one answer, even when the answer
|
|
96
|
+
// comes from three people talking at different times. Doing this before the
|
|
97
|
+
// analysis is also what lets the bands and the envelopes stay a single set:
|
|
98
|
+
// the chain is fixed, so there is no per-voice filter to switch between.
|
|
99
|
+
const decoded = await Promise.all(
|
|
100
|
+
voices.map(async (voice) => ({
|
|
101
|
+
samples: (await decode(voice.src)).getChannelData(0),
|
|
102
|
+
offsetSeconds: clipStart(voice.start) - bedStart,
|
|
103
|
+
})),
|
|
104
|
+
);
|
|
105
|
+
const voiceMix = mixCarveSources(decoded, DECODE_SAMPLE_RATE);
|
|
106
|
+
if (voiceMix.length === 0) return null;
|
|
107
|
+
// Strength is what the author set; these are the numbers it means.
|
|
108
|
+
const profile = carveProfile(strength);
|
|
109
|
+
// The bed as well as the voice, when the carve is asked to match levels:
|
|
110
|
+
// "how far over the voice is this bed" cannot be answered by listening to
|
|
111
|
+
// one of them.
|
|
112
|
+
const bedBuffer = profile.duckDb > 0 && bedSrc ? await decode(bedSrc).catch(() => null) : null;
|
|
113
|
+
const bands = analyseCarveBands(voiceMix, DECODE_SAMPLE_RATE, profile);
|
|
114
|
+
// The level half of the carve, measured against the speech it has to sit
|
|
115
|
+
// under. No offset to apply: the mix is already on the bed's clock.
|
|
116
|
+
const duck = bedBuffer
|
|
117
|
+
? analyseCarveDuck(voiceMix, bedBuffer.getChannelData(0), DECODE_SAMPLE_RATE, profile, 0)
|
|
118
|
+
: [];
|
|
119
|
+
return { bands, carved: carveBandsToChain(bands), duck, voiceMix };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Each filter's depth as an envelope, plus the level envelope if there is one. */
|
|
123
|
+
export function carveLanes(
|
|
124
|
+
carvedNodes: HfAudioFxNode[],
|
|
125
|
+
duckNode: HfAudioFxNode | null,
|
|
126
|
+
duck: { t: number; v: number }[],
|
|
127
|
+
voiceMix: Float32Array,
|
|
128
|
+
bands: ReturnType<typeof analyseCarveBands>,
|
|
129
|
+
): HfAutomationLane[] {
|
|
130
|
+
// Each filter's depth becomes an envelope of the speech's level in that
|
|
131
|
+
// band, so pauses leave the bed alone and whoever is talking sets the depth.
|
|
132
|
+
const lanes = analyseCarveDynamics(voiceMix, DECODE_SAMPLE_RATE, bands).flatMap((dyn, i) => {
|
|
133
|
+
const id = carvedNodes[i]?.id;
|
|
134
|
+
return id ? carveLaneFor(id, dyn.points) : [];
|
|
135
|
+
});
|
|
136
|
+
// The level envelope rides the gain stage, on the same clock as the bands.
|
|
137
|
+
if (duckNode?.id && duck.length > 0) lanes.push(...carveLaneFor(duckNode.id, duck));
|
|
138
|
+
return lanes;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* One carve envelope as a lane on this bed's clock.
|
|
143
|
+
*
|
|
144
|
+
* No shifting: the voices were summed onto the bed's clock before the analysis
|
|
145
|
+
* ran, so what comes back is already in the bed's own time. A lane does hold
|
|
146
|
+
* its first value backwards to the start of its clip, so an envelope that
|
|
147
|
+
* begins later needs an explicit "no cut" at zero or the bed starts out ducked.
|
|
148
|
+
*/
|
|
149
|
+
function carveLaneFor(id: string, points: { t: number; v: number }[]): HfAutomationLane[] {
|
|
150
|
+
const timed = points.map((p) => ({ t: Number(p.t.toFixed(3)), v: p.v })).filter((p) => p.t >= 0);
|
|
151
|
+
if ((timed[0]?.t ?? 0) > 0) timed.unshift({ t: 0, v: 0 });
|
|
152
|
+
return timed.length > 1 ? [{ target: fxAutomationTarget(id, "gain"), points: timed }] : [];
|
|
153
|
+
}
|
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
} from "./propertyPanelAutomation";
|
|
24
24
|
import { trackLeveller } from "./audioFxTelemetry.js";
|
|
25
25
|
import type { DomEditSelection } from "./domEditingTypes";
|
|
26
|
-
import {
|
|
26
|
+
import { useAuditionTransport } from "./useAuditionTransport.js";
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
29
|
* Rate the track is decoded at. Analysis is self-consistent because it reads
|
|
@@ -125,39 +125,10 @@ export function useFxLevelling(
|
|
|
125
125
|
}
|
|
126
126
|
};
|
|
127
127
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
*/
|
|
133
|
-
const auditionReturn = useRef<number | null>(null);
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Start playback for an audition, and stop it again on the way out.
|
|
137
|
-
*
|
|
138
|
-
* An audition writes the preset to the running graph, which is silent while
|
|
139
|
-
* the transport is paused — so a paused author hovering a preset heard
|
|
140
|
-
* nothing at all, and the whole affordance only worked mid-playback. Hovering
|
|
141
|
-
* now plays from the playhead, and leaving stops and rewinds to exactly where
|
|
142
|
-
* it started: browsing the shelf must not cost the author their place.
|
|
143
|
-
*
|
|
144
|
-
* Already playing, this does nothing in either direction. The author started
|
|
145
|
-
* that, and stopping their transport because they passed over a preset would
|
|
146
|
-
* be the panel taking a decision that was not offered to it.
|
|
147
|
-
*/
|
|
148
|
-
const auditionTransport = (on: boolean): void => {
|
|
149
|
-
const store = usePlayerStore.getState();
|
|
150
|
-
if (on) {
|
|
151
|
-
if (store.isPlaying || auditionReturn.current !== null) return;
|
|
152
|
-
auditionReturn.current = store.currentTime;
|
|
153
|
-
store.requestPlayback(true);
|
|
154
|
-
return;
|
|
155
|
-
}
|
|
156
|
-
const returnTo = auditionReturn.current;
|
|
157
|
-
if (returnTo === null) return;
|
|
158
|
-
auditionReturn.current = null;
|
|
159
|
-
store.requestPlayback(false, returnTo);
|
|
160
|
-
};
|
|
128
|
+
// Hovering plays from the playhead and leaving rewinds to exactly where it
|
|
129
|
+
// started: browsing the shelf must not cost the author their place. Shared
|
|
130
|
+
// with the timeline's FX popover — see `useAuditionTransport`.
|
|
131
|
+
const auditionTransport = useAuditionTransport();
|
|
161
132
|
|
|
162
133
|
const [auditioningLevel, setAuditioningLevel] = useState(false);
|
|
163
134
|
/**
|
|
@@ -1,12 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createContext,
|
|
3
|
-
useContext,
|
|
4
|
-
useState,
|
|
5
|
-
useCallback,
|
|
6
|
-
useRef,
|
|
7
|
-
useEffect,
|
|
8
|
-
type ReactNode,
|
|
9
|
-
} from "react";
|
|
1
|
+
import { useContext, useState, useCallback, useRef, useEffect, type ReactNode } from "react";
|
|
10
2
|
import { useTimelinePlayer, usePlayerStore } from "../../player";
|
|
11
3
|
import type { TimelineElement } from "../../player";
|
|
12
4
|
import type { CompositionLevel } from "./CompositionBreadcrumb";
|
|
@@ -16,6 +8,7 @@ import { setCompositionSourceMap } from "../editor/domEditingDom";
|
|
|
16
8
|
import { ensureMotionPathPluginLoaded } from "../../utils/gsapSoftReload";
|
|
17
9
|
import { readStudioUiPreferences, writeStudioUiPreferences } from "../../utils/studioUiPreferences";
|
|
18
10
|
import { useAssetPreviewStore } from "../../utils/assetPreviewStore";
|
|
11
|
+
import { createStableContext } from "../../utils/hmrStableContext";
|
|
19
12
|
|
|
20
13
|
// Timeline gets a generous default height so the preview isn't oversized and the
|
|
21
14
|
// tracks have room to breathe (CapCut-style). Users can still drag the divider.
|
|
@@ -55,7 +48,7 @@ export interface NLEContextValue {
|
|
|
55
48
|
setPreviewCompositionSize: (size: { width: number; height: number } | null) => void;
|
|
56
49
|
}
|
|
57
50
|
|
|
58
|
-
const NLEContext =
|
|
51
|
+
const NLEContext = createStableContext<NLEContextValue | null>("NLEContext", null);
|
|
59
52
|
|
|
60
53
|
export function useNLEContext(): NLEContextValue {
|
|
61
54
|
const ctx = useContext(NLEContext);
|
|
@@ -47,7 +47,11 @@ export interface TimelineEditCallbackDeps {
|
|
|
47
47
|
handleRazorSplit: (element: TimelineElement, splitTime: number) => Promise<void> | void;
|
|
48
48
|
handleRazorSplitAll: (splitTime: number) => Promise<void> | void;
|
|
49
49
|
/** C1's ungrouped-track FX pointer — same auto-grouping write B6's carve uses. */
|
|
50
|
-
handleGroupClips?: (
|
|
50
|
+
handleGroupClips?: (
|
|
51
|
+
clipIds: readonly string[],
|
|
52
|
+
groupId: string,
|
|
53
|
+
groupLabel?: string,
|
|
54
|
+
) => Promise<void>;
|
|
51
55
|
/** C1's single-clip FX write, addressed by the clip itself. */
|
|
52
56
|
setElementFxAttribute?: {
|
|
53
57
|
setLive: (element: TimelineElement, attr: string, value: string | null) => void;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useCallback, useContext, useMemo, type ReactNode } from "react";
|
|
2
|
+
import { createStableContext } from "../utils/hmrStableContext";
|
|
2
3
|
import { trackDesignInput, type DesignInputUi } from "../utils/designInputTracking";
|
|
3
4
|
|
|
4
5
|
// Carries which inspector UI and which section the currently-rendered design-panel
|
|
@@ -11,10 +12,10 @@ interface DesignPanelInputContextValue {
|
|
|
11
12
|
section: string;
|
|
12
13
|
}
|
|
13
14
|
|
|
14
|
-
const DesignPanelInputContext =
|
|
15
|
-
|
|
16
|
-
section: "unknown",
|
|
17
|
-
|
|
15
|
+
const DesignPanelInputContext = createStableContext<DesignPanelInputContextValue>(
|
|
16
|
+
"DesignPanelInputContext",
|
|
17
|
+
{ ui: "classic", section: "unknown" },
|
|
18
|
+
);
|
|
18
19
|
|
|
19
20
|
export function DesignPanelInputProvider({
|
|
20
21
|
ui,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// fallow-ignore-file code-duplication
|
|
2
|
-
import { createContext, useCallback, useContext, useMemo, useRef, type ReactNode } from "react";
|
|
3
2
|
import type { useDomEditSession } from "../hooks/useDomEditSession";
|
|
3
|
+
import { useCallback, useContext, useMemo, useRef, type ReactNode } from "react";
|
|
4
|
+
import { createStableContext } from "../utils/hmrStableContext";
|
|
4
5
|
|
|
5
6
|
type DomEditValue = ReturnType<typeof useDomEditSession>;
|
|
6
7
|
|
|
@@ -93,8 +94,14 @@ export interface DomEditSelectionValue extends Pick<
|
|
|
93
94
|
| "agentPromptSelectionContext"
|
|
94
95
|
> {}
|
|
95
96
|
|
|
96
|
-
const DomEditActionsContext =
|
|
97
|
-
|
|
97
|
+
const DomEditActionsContext = createStableContext<DomEditActionsValue | null>(
|
|
98
|
+
"DomEditActionsContext",
|
|
99
|
+
null,
|
|
100
|
+
);
|
|
101
|
+
const DomEditSelectionContext = createStableContext<DomEditSelectionValue | null>(
|
|
102
|
+
"DomEditSelectionContext",
|
|
103
|
+
null,
|
|
104
|
+
);
|
|
98
105
|
|
|
99
106
|
export function useDomEditActionsContext(): DomEditActionsValue {
|
|
100
107
|
const ctx = useContext(DomEditActionsContext);
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { createContext, useContext, useMemo, type ReactNode } from "react";
|
|
2
1
|
import type { useFileManager } from "../hooks/useFileManager";
|
|
2
|
+
import { useContext, useMemo, type ReactNode } from "react";
|
|
3
|
+
import { createStableContext } from "../utils/hmrStableContext";
|
|
3
4
|
|
|
4
5
|
type FileManagerValue = ReturnType<typeof useFileManager>;
|
|
5
6
|
|
|
6
|
-
const FileManagerContext =
|
|
7
|
+
const FileManagerContext = createStableContext<FileManagerValue | null>("FileManagerContext", null);
|
|
7
8
|
|
|
8
9
|
export function useFileManagerContext(): FileManagerValue {
|
|
9
10
|
const ctx = useContext(FileManagerContext);
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { createContext, useContext, useMemo, type ReactNode } from "react";
|
|
2
1
|
import type { usePanelLayout } from "../hooks/usePanelLayout";
|
|
2
|
+
import { useContext, useMemo, type ReactNode } from "react";
|
|
3
|
+
import { createStableContext } from "../utils/hmrStableContext";
|
|
3
4
|
|
|
4
5
|
type PanelLayoutValue = ReturnType<typeof usePanelLayout>;
|
|
5
6
|
|
|
6
|
-
const PanelLayoutContext =
|
|
7
|
+
const PanelLayoutContext = createStableContext<PanelLayoutValue | null>("PanelLayoutContext", null);
|
|
7
8
|
|
|
8
9
|
export function usePanelLayoutContext(): PanelLayoutValue {
|
|
9
10
|
const ctx = useContext(PanelLayoutContext);
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { createContext, useContext, useMemo, type ReactNode } from "react";
|
|
2
1
|
import type { TimelineElement } from "../player";
|
|
3
2
|
import type { CompositionDimensions } from "../components/renders/RenderQueue";
|
|
4
3
|
import type { FfmpegStatus } from "../components/renders/useFfmpegStatus";
|
|
4
|
+
import { useContext, useMemo, type ReactNode } from "react";
|
|
5
|
+
import { createStableContext } from "../utils/hmrStableContext";
|
|
5
6
|
|
|
6
7
|
export interface StudioShellValue {
|
|
7
8
|
projectId: string;
|
|
@@ -52,8 +53,11 @@ export interface StudioPlaybackValue {
|
|
|
52
53
|
|
|
53
54
|
export type StudioContextValue = StudioShellValue & StudioPlaybackValue;
|
|
54
55
|
|
|
55
|
-
const StudioShellContext =
|
|
56
|
-
const StudioPlaybackContext =
|
|
56
|
+
const StudioShellContext = createStableContext<StudioShellValue | null>("StudioShellContext", null);
|
|
57
|
+
const StudioPlaybackContext = createStableContext<StudioPlaybackValue | null>(
|
|
58
|
+
"StudioPlaybackContext",
|
|
59
|
+
null,
|
|
60
|
+
);
|
|
57
61
|
|
|
58
62
|
export function useStudioShellContext(): StudioShellValue {
|
|
59
63
|
const ctx = useContext(StudioShellContext);
|
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useContext, useMemo, type ReactNode } from "react";
|
|
2
|
+
import { createStableContext } from "../utils/hmrStableContext";
|
|
2
3
|
import type { TimelineEditCallbacks } from "../player/components/timelineCallbacks";
|
|
3
4
|
|
|
4
|
-
const TimelineEditContext =
|
|
5
|
+
const TimelineEditContext = createStableContext<TimelineEditCallbacks | null>(
|
|
6
|
+
"TimelineEditContext",
|
|
7
|
+
null,
|
|
8
|
+
);
|
|
5
9
|
|
|
6
10
|
export function useTimelineEditContext(): TimelineEditCallbacks {
|
|
7
11
|
const ctx = useContext(TimelineEditContext);
|
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
* and callbacks to promote or to edit the bound variable's default in place.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { createContext, useContext, useEffect, useMemo, useState } from "react";
|
|
11
10
|
import type { Composition, CompositionVariable } from "@hyperframes/sdk";
|
|
12
11
|
import type { DomEditSelection } from "../components/editor/domEditingTypes";
|
|
13
12
|
import {
|
|
@@ -22,6 +21,8 @@ import {
|
|
|
22
21
|
uniqueId,
|
|
23
22
|
type PromoteChannel,
|
|
24
23
|
} from "./variablePromoteHelpers";
|
|
24
|
+
import { useContext, useEffect, useMemo, useState } from "react";
|
|
25
|
+
import { createStableContext } from "../utils/hmrStableContext";
|
|
25
26
|
|
|
26
27
|
export type { PromoteChannel };
|
|
27
28
|
|
|
@@ -47,7 +48,10 @@ interface VariablePromoteContextValue {
|
|
|
47
48
|
onPersistError: (error: unknown) => void;
|
|
48
49
|
}
|
|
49
50
|
|
|
50
|
-
const VariablePromoteContext =
|
|
51
|
+
const VariablePromoteContext = createStableContext<VariablePromoteContextValue | null>(
|
|
52
|
+
"VariablePromoteContext",
|
|
53
|
+
null,
|
|
54
|
+
);
|
|
51
55
|
|
|
52
56
|
function readBinding(session: Composition, hfId: string, channel: PromoteChannel): string | null {
|
|
53
57
|
const snapshot = session.getElement(hfId);
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import {
|
|
2
|
-
createContext,
|
|
3
2
|
useCallback,
|
|
4
3
|
useContext,
|
|
5
4
|
useEffect,
|
|
@@ -8,6 +7,7 @@ import {
|
|
|
8
7
|
useState,
|
|
9
8
|
type ReactNode,
|
|
10
9
|
} from "react";
|
|
10
|
+
import { createStableContext } from "../utils/hmrStableContext";
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Top-level Studio view mode.
|
|
@@ -123,7 +123,7 @@ export function useViewModeState(): ViewModeValue {
|
|
|
123
123
|
);
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
-
const ViewModeContext =
|
|
126
|
+
const ViewModeContext = createStableContext<ViewModeValue | null>("ViewModeContext", null);
|
|
127
127
|
|
|
128
128
|
export function useViewMode(): ViewModeValue {
|
|
129
129
|
const ctx = useContext(ViewModeContext);
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which elements a delete acts on.
|
|
3
|
+
*
|
|
4
|
+
* Its own module so `useDomEditSession.ts` stays under the studio's 600-line
|
|
5
|
+
* cap; it reads only its arguments.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { DomEditSelection } from "../components/editor/domEditingTypes";
|
|
9
|
+
import type { EditHistoryKind } from "../utils/editHistory";
|
|
10
|
+
|
|
11
|
+
/** One entry in the studio's edit history, as `useDomEditSession`'s caller
|
|
12
|
+
* supplies it. */
|
|
13
|
+
export interface RecordEditInput {
|
|
14
|
+
label: string;
|
|
15
|
+
kind: EditHistoryKind;
|
|
16
|
+
coalesceKey?: string;
|
|
17
|
+
files: Record<string, { before: string; after: string }>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Which elements a delete acts on. `expandGroup` widens the primary to the
|
|
22
|
+
* whole marquee group, which is what the Delete key means.
|
|
23
|
+
*
|
|
24
|
+
* The caller chooses rather than the delete deciding for everyone: Cut copies
|
|
25
|
+
* the primary alone, so expanding for it put one element on the clipboard and
|
|
26
|
+
* removed every other member of the group with it.
|
|
27
|
+
*/
|
|
28
|
+
export function membersForDelete(
|
|
29
|
+
selection: DomEditSelection,
|
|
30
|
+
group: DomEditSelection[],
|
|
31
|
+
options?: { expandGroup?: boolean },
|
|
32
|
+
): DomEditSelection[] {
|
|
33
|
+
return options?.expandGroup && group.length > 0 ? group : [selection];
|
|
34
|
+
}
|