diff --git a/components/tracks/TrackExtensions.tsx b/components/tracks/TrackExtensions.tsx index 0491fa3..200933a 100644 --- a/components/tracks/TrackExtensions.tsx +++ b/components/tracks/TrackExtensions.tsx @@ -16,6 +16,7 @@ export interface TrackExtensionsProps { onRemoveEffect?: (effectId: string) => void; onUpdateEffect?: (effectId: string, parameters: any) => void; onAddEffect?: (effectType: EffectType) => void; + asOverlay?: boolean; // When true, renders as full overlay without header } export function TrackExtensions({ @@ -25,14 +26,99 @@ export function TrackExtensions({ onRemoveEffect, onUpdateEffect, onAddEffect, + asOverlay = false, }: TrackExtensionsProps) { const [effectBrowserOpen, setEffectBrowserOpen] = React.useState(false); - // Don't render if track is collapsed - if (track.collapsed) { + // Don't render if track is collapsed (unless it's an overlay, which handles its own visibility) + if (!asOverlay && track.collapsed) { return null; } + // Overlay mode: render full-screen effect rack + if (asOverlay) { + return ( + <> +
+ {/* Header with close button */} +
+
+ Effects + + ({track.effectChain.effects.length}) + +
+
+ + +
+
+ + {/* Effects rack */} +
+
+ {track.effectChain.effects.length === 0 ? ( +
+ +
+

No effects yet

+

+ Click + to add an effect +

+
+
+ ) : ( + track.effectChain.effects.map((effect) => ( + onToggleEffect?.(effect.id)} + onRemove={() => onRemoveEffect?.(effect.id)} + onUpdateParameters={(params) => onUpdateEffect?.(effect.id, params)} + onToggleExpanded={() => { + const updatedEffects = track.effectChain.effects.map((e) => + e.id === effect.id ? { ...e, expanded: !e.expanded } : e + ); + onUpdateTrack(track.id, { + effectChain: { ...track.effectChain, effects: updatedEffects }, + }); + }} + /> + )) + )} +
+
+
+ + {/* Effect Browser Dialog */} + setEffectBrowserOpen(false)} + onSelectEffect={(effectType) => { + if (onAddEffect) { + onAddEffect(effectType); + } + }} + /> + + ); + } + + // Original inline mode return ( <> {/* Effects Section (Collapsible, Full Width) */} diff --git a/components/tracks/TrackList.tsx b/components/tracks/TrackList.tsx index cff9452..0f27c81 100644 --- a/components/tracks/TrackList.tsx +++ b/components/tracks/TrackList.tsx @@ -205,197 +205,208 @@ export function TrackList({
{tracks.map((track) => ( - {/* Track Waveform Row */} - onSelectTrack(track.id) : undefined} - onToggleMute={() => - onUpdateTrack(track.id, { mute: !track.mute }) - } - onToggleSolo={() => - onUpdateTrack(track.id, { solo: !track.solo }) - } - onToggleCollapse={() => - onUpdateTrack(track.id, { collapsed: !track.collapsed }) - } - onVolumeChange={(volume) => - onUpdateTrack(track.id, { volume }) - } - onPanChange={(pan) => - onUpdateTrack(track.id, { pan }) - } - onRemove={() => onRemoveTrack(track.id)} - onNameChange={(name) => - onUpdateTrack(track.id, { name }) - } - onUpdateTrack={onUpdateTrack} - onSeek={onSeek} - onLoadAudio={(buffer) => - onUpdateTrack(track.id, { audioBuffer: buffer }) - } - onToggleEffect={(effectId) => { - const updatedChain = { - ...track.effectChain, - effects: track.effectChain.effects.map((e) => - e.id === effectId ? { ...e, enabled: !e.enabled } : e - ), - }; - onUpdateTrack(track.id, { effectChain: updatedChain }); - }} - onRemoveEffect={(effectId) => { - const updatedChain = { - ...track.effectChain, - effects: track.effectChain.effects.filter((e) => e.id !== effectId), - }; - onUpdateTrack(track.id, { effectChain: updatedChain }); - }} - onUpdateEffect={(effectId, parameters) => { - const updatedChain = { - ...track.effectChain, - effects: track.effectChain.effects.map((e) => - e.id === effectId ? { ...e, parameters } : e - ), - }; - onUpdateTrack(track.id, { effectChain: updatedChain }); - }} - onAddEffect={(effectType) => { - const newEffect = createEffect( - effectType, - EFFECT_NAMES[effectType] - ); - const updatedChain = { - ...track.effectChain, - effects: [...track.effectChain.effects, newEffect], - }; - onUpdateTrack(track.id, { effectChain: updatedChain }); - }} - onSelectionChange={ - onSelectionChange - ? (selection) => onSelectionChange(track.id, selection) - : undefined - } - onToggleRecordEnable={ - onToggleRecordEnable - ? () => onToggleRecordEnable(track.id) - : undefined - } - isRecording={recordingTrackId === track.id} - recordingLevel={recordingTrackId === track.id ? recordingLevel : 0} - playbackLevel={trackLevels[track.id] || 0} - onParameterTouched={onParameterTouched} - isPlaying={isPlaying} - renderWaveformOnly={true} - /> + {/* Track Waveform Row with Overlays */} +
+ onSelectTrack(track.id) : undefined} + onToggleMute={() => + onUpdateTrack(track.id, { mute: !track.mute }) + } + onToggleSolo={() => + onUpdateTrack(track.id, { solo: !track.solo }) + } + onToggleCollapse={() => + onUpdateTrack(track.id, { collapsed: !track.collapsed }) + } + onVolumeChange={(volume) => + onUpdateTrack(track.id, { volume }) + } + onPanChange={(pan) => + onUpdateTrack(track.id, { pan }) + } + onRemove={() => onRemoveTrack(track.id)} + onNameChange={(name) => + onUpdateTrack(track.id, { name }) + } + onUpdateTrack={onUpdateTrack} + onSeek={onSeek} + onLoadAudio={(buffer) => + onUpdateTrack(track.id, { audioBuffer: buffer }) + } + onToggleEffect={(effectId) => { + const updatedChain = { + ...track.effectChain, + effects: track.effectChain.effects.map((e) => + e.id === effectId ? { ...e, enabled: !e.enabled } : e + ), + }; + onUpdateTrack(track.id, { effectChain: updatedChain }); + }} + onRemoveEffect={(effectId) => { + const updatedChain = { + ...track.effectChain, + effects: track.effectChain.effects.filter((e) => e.id !== effectId), + }; + onUpdateTrack(track.id, { effectChain: updatedChain }); + }} + onUpdateEffect={(effectId, parameters) => { + const updatedChain = { + ...track.effectChain, + effects: track.effectChain.effects.map((e) => + e.id === effectId ? { ...e, parameters } : e + ), + }; + onUpdateTrack(track.id, { effectChain: updatedChain }); + }} + onAddEffect={(effectType) => { + const newEffect = createEffect( + effectType, + EFFECT_NAMES[effectType] + ); + const updatedChain = { + ...track.effectChain, + effects: [...track.effectChain.effects, newEffect], + }; + onUpdateTrack(track.id, { effectChain: updatedChain }); + }} + onSelectionChange={ + onSelectionChange + ? (selection) => onSelectionChange(track.id, selection) + : undefined + } + onToggleRecordEnable={ + onToggleRecordEnable + ? () => onToggleRecordEnable(track.id) + : undefined + } + isRecording={recordingTrackId === track.id} + recordingLevel={recordingTrackId === track.id ? recordingLevel : 0} + playbackLevel={trackLevels[track.id] || 0} + onParameterTouched={onParameterTouched} + isPlaying={isPlaying} + renderWaveformOnly={true} + /> - {/* Automation Lane Section */} - {!track.collapsed && track.automation?.showAutomation && ( -
- {track.automation.lanes - .filter((lane) => lane.parameterId === track.automation.selectedParameterId) - .map((lane) => ( - { - const newPoint = createAutomationPoint(time, value); - const updatedLanes = track.automation.lanes.map((l) => - l.id === lane.id - ? { ...l, points: [...l.points, newPoint].sort((a, b) => a.time - b.time) } - : l - ); - onUpdateTrack(track.id, { - automation: { ...track.automation, lanes: updatedLanes }, - }); + {/* Automation Lane Overlay */} + {!track.collapsed && track.automation?.showAutomation && ( +
+
+ {track.automation.lanes + .filter((lane) => lane.parameterId === track.automation.selectedParameterId) + .map((lane) => ( + { + const newPoint = createAutomationPoint(time, value); + const updatedLanes = track.automation.lanes.map((l) => + l.id === lane.id + ? { ...l, points: [...l.points, newPoint].sort((a, b) => a.time - b.time) } + : l + ); + onUpdateTrack(track.id, { + automation: { ...track.automation, lanes: updatedLanes }, + }); + }} + onUpdatePoint={(pointId, updates) => { + const updatedLanes = track.automation.lanes.map((l) => + l.id === lane.id + ? { + ...l, + points: l.points.map((p) => + p.id === pointId ? { ...p, ...updates } : p + ), + } + : l + ); + onUpdateTrack(track.id, { + automation: { ...track.automation, lanes: updatedLanes }, + }); + }} + onRemovePoint={(pointId) => { + const updatedLanes = track.automation.lanes.map((l) => + l.id === lane.id + ? { ...l, points: l.points.filter((p) => p.id !== pointId) } + : l + ); + onUpdateTrack(track.id, { + automation: { ...track.automation, lanes: updatedLanes }, + }); + }} + onUpdateLane={(updates) => { + const updatedLanes = track.automation.lanes.map((l) => + l.id === lane.id ? { ...l, ...updates } : l + ); + onUpdateTrack(track.id, { + automation: { ...track.automation, lanes: updatedLanes }, + }); + }} + onSeek={onSeek} + /> + ))} +
+
+ )} + + {/* Effects Overlay */} + {!track.collapsed && track.showEffects && ( +
+
+ { + const updatedChain = { + ...track.effectChain, + effects: track.effectChain.effects.map((e) => + e.id === effectId ? { ...e, enabled: !e.enabled } : e + ), + }; + onUpdateTrack(track.id, { effectChain: updatedChain }); }} - onUpdatePoint={(pointId, updates) => { - const updatedLanes = track.automation.lanes.map((l) => - l.id === lane.id - ? { - ...l, - points: l.points.map((p) => - p.id === pointId ? { ...p, ...updates } : p - ), - } - : l - ); - onUpdateTrack(track.id, { - automation: { ...track.automation, lanes: updatedLanes }, - }); + onRemoveEffect={(effectId) => { + const updatedChain = { + ...track.effectChain, + effects: track.effectChain.effects.filter((e) => e.id !== effectId), + }; + onUpdateTrack(track.id, { effectChain: updatedChain }); }} - onRemovePoint={(pointId) => { - const updatedLanes = track.automation.lanes.map((l) => - l.id === lane.id - ? { ...l, points: l.points.filter((p) => p.id !== pointId) } - : l - ); - onUpdateTrack(track.id, { - automation: { ...track.automation, lanes: updatedLanes }, - }); + onUpdateEffect={(effectId, parameters) => { + const updatedChain = { + ...track.effectChain, + effects: track.effectChain.effects.map((e) => + e.id === effectId ? { ...e, parameters } : e + ), + }; + onUpdateTrack(track.id, { effectChain: updatedChain }); }} - onUpdateLane={(updates) => { - const updatedLanes = track.automation.lanes.map((l) => - l.id === lane.id ? { ...l, ...updates } : l + onAddEffect={(effectType) => { + const newEffect = createEffect( + effectType, + EFFECT_NAMES[effectType] ); - onUpdateTrack(track.id, { - automation: { ...track.automation, lanes: updatedLanes }, - }); + const updatedChain = { + ...track.effectChain, + effects: [...track.effectChain.effects, newEffect], + }; + onUpdateTrack(track.id, { effectChain: updatedChain }); }} - onSeek={onSeek} /> - ))} -
- )} - - {/* Effects Section */} - { - const updatedChain = { - ...track.effectChain, - effects: track.effectChain.effects.map((e) => - e.id === effectId ? { ...e, enabled: !e.enabled } : e - ), - }; - onUpdateTrack(track.id, { effectChain: updatedChain }); - }} - onRemoveEffect={(effectId) => { - const updatedChain = { - ...track.effectChain, - effects: track.effectChain.effects.filter((e) => e.id !== effectId), - }; - onUpdateTrack(track.id, { effectChain: updatedChain }); - }} - onUpdateEffect={(effectId, parameters) => { - const updatedChain = { - ...track.effectChain, - effects: track.effectChain.effects.map((e) => - e.id === effectId ? { ...e, parameters } : e - ), - }; - onUpdateTrack(track.id, { effectChain: updatedChain }); - }} - onAddEffect={(effectType) => { - const newEffect = createEffect( - effectType, - EFFECT_NAMES[effectType] - ); - const updatedChain = { - ...track.effectChain, - effects: [...track.effectChain.effects, newEffect], - }; - onUpdateTrack(track.id, { effectChain: updatedChain }); - }} - /> +
+
+ )} +
))}