refactor: improve UX with 2-tab sidebar layout (Option A)

Changed from confusing 3-tab layout to cleaner 2-tab design inspired by
professional DAWs like Ableton Live:

**Tracks Tab:**
- Track management actions (Add/Import/Clear)
- Track count with selected track indicator
- Contextual help text
- Selected track's effects shown below (when track is selected)
- Clear visual separation with border-top

**Master Tab:**
- Master channel description
- Master effects chain
- Preset management for master effects
- Clear that these apply to final mix

Benefits:
- Clear separation: track operations vs master operations
- Contextual: selecting a track shows its effects in same tab
- Less cognitive load than 3 tabs
- Follows DAW conventions (track strip vs master strip)
- Selected track name shown in status area

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-18 07:41:16 +01:00
parent f640f2f9d4
commit a8f2391400

View File

@@ -75,7 +75,7 @@ export function SidePanel({
className, className,
}: SidePanelProps) { }: SidePanelProps) {
const [isCollapsed, setIsCollapsed] = React.useState(false); const [isCollapsed, setIsCollapsed] = React.useState(false);
const [activeTab, setActiveTab] = React.useState<'tracks' | 'trackFx' | 'masterFx'>('tracks'); const [activeTab, setActiveTab] = React.useState<'tracks' | 'master'>('tracks');
const [presetDialogOpen, setPresetDialogOpen] = React.useState(false); const [presetDialogOpen, setPresetDialogOpen] = React.useState(false);
const selectedTrack = tracks.find((t) => t.id === selectedTrackId); const selectedTrack = tracks.find((t) => t.id === selectedTrackId);
@@ -107,27 +107,21 @@ export function SidePanel({
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<Button <Button
variant={activeTab === 'tracks' ? 'secondary' : 'ghost'} variant={activeTab === 'tracks' ? 'secondary' : 'ghost'}
size="icon-sm" size="sm"
onClick={() => setActiveTab('tracks')} onClick={() => setActiveTab('tracks')}
title="Tracks" title="Tracks"
> >
<Music2 className="h-4 w-4" /> <Music2 className="h-4 w-4 mr-1.5" />
Tracks
</Button> </Button>
<Button <Button
variant={activeTab === 'trackFx' ? 'secondary' : 'ghost'} variant={activeTab === 'master' ? 'secondary' : 'ghost'}
size="icon-sm" size="sm"
onClick={() => setActiveTab('trackFx')} onClick={() => setActiveTab('master')}
title="Track Effects" title="Master"
> >
<Link2 className="h-4 w-4" /> <Link2 className="h-4 w-4 mr-1.5 text-primary" />
</Button> Master
<Button
variant={activeTab === 'masterFx' ? 'secondary' : 'ghost'}
size="icon-sm"
onClick={() => setActiveTab('masterFx')}
title="Master Effects"
>
<Link2 className="h-4 w-4 text-primary" />
</Button> </Button>
</div> </div>
<Button <Button
@@ -147,7 +141,7 @@ export function SidePanel({
{/* Track Actions */} {/* Track Actions */}
<div className="space-y-2"> <div className="space-y-2">
<h3 className="text-xs font-semibold text-muted-foreground uppercase"> <h3 className="text-xs font-semibold text-muted-foreground uppercase">
Multi-Track Editor Track Management
</h3> </h3>
<div className="flex gap-2"> <div className="flex gap-2">
<Button <Button
@@ -182,17 +176,25 @@ export function SidePanel({
)} )}
</div> </div>
{/* Track List - Simplified */} {/* Track List Summary */}
{tracks.length > 0 ? ( {tracks.length > 0 ? (
<div className="space-y-2"> <div className="space-y-2">
<h3 className="text-xs font-semibold text-muted-foreground uppercase"> <div className="flex items-center justify-between">
Tracks ({tracks.length}) <h3 className="text-xs font-semibold text-muted-foreground uppercase">
</h3> Tracks ({tracks.length})
</h3>
{selectedTrack && (
<span className="text-xs text-primary">
{String(selectedTrack.name || 'Untitled Track')} selected
</span>
)}
</div>
<div className="text-xs text-muted-foreground"> <div className="text-xs text-muted-foreground">
<p className="mb-2"> <p>
Track controls are located on the left side of each track in the timeline. {selectedTrack
? 'Track controls are on the left of each track. Effects for the selected track are shown below.'
: 'Click a track\'s waveform to select it and edit its effects below.'}
</p> </p>
<p>Click a track to select it and apply effects from the Effect Chain tab.</p>
</div> </div>
</div> </div>
) : ( ) : (
@@ -203,96 +205,97 @@ export function SidePanel({
</p> </p>
</div> </div>
)} )}
{/* Selected Track Effects */}
{selectedTrack && (
<div className="space-y-2 pt-3 border-t border-border">
<div className="flex items-center justify-between">
<h3 className="text-xs font-semibold text-muted-foreground uppercase">
Track Effects
</h3>
{trackEffectChain && trackEffectChain.effects.length > 0 && (
<Button
variant="ghost"
size="icon-sm"
onClick={onClearTrackChain}
title="Clear all effects"
>
<Trash2 className="h-4 w-4 text-destructive" />
</Button>
)}
</div>
<EffectRack
chain={trackEffectChain!}
onToggleEffect={onToggleTrackEffect}
onRemoveEffect={onRemoveTrackEffect}
onReorderEffects={onReorderTrackEffects}
/>
</div>
)}
</> </>
)} )}
{activeTab === 'trackFx' && ( {activeTab === 'master' && (
<div className="space-y-2"> <>
<div className="flex items-center justify-between"> {/* Master Channel Info */}
<div className="space-y-2">
<h3 className="text-xs font-semibold text-muted-foreground uppercase"> <h3 className="text-xs font-semibold text-muted-foreground uppercase">
Track Effects Master Channel
{selectedTrack && (
<span className="text-primary ml-2">({String(selectedTrack.name || 'Untitled Track')})</span>
)}
</h3> </h3>
<div className="flex gap-1"> <div className="text-xs text-muted-foreground">
{trackEffectChain && trackEffectChain.effects.length > 0 && ( <p>
<Button Master effects are applied to the final mix of all tracks.
variant="ghost"
size="icon-sm"
onClick={onClearTrackChain}
title="Clear all effects"
>
<Trash2 className="h-4 w-4 text-destructive" />
</Button>
)}
</div>
</div>
{!selectedTrack ? (
<div className="text-center py-8">
<Link2 className="h-12 w-12 mx-auto text-muted-foreground/50 mb-2" />
<p className="text-sm text-muted-foreground">
Select a track to apply effects
</p> </p>
</div> </div>
) : ( </div>
<EffectRack
chain={trackEffectChain!}
onToggleEffect={onToggleTrackEffect}
onRemoveEffect={onRemoveTrackEffect}
onReorderEffects={onReorderTrackEffects}
/>
)}
</div>
)}
{activeTab === 'masterFx' && ( {/* Master Effects */}
<div className="space-y-2"> <div className="space-y-2 pt-3 border-t border-border">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h3 className="text-xs font-semibold text-muted-foreground uppercase"> <h3 className="text-xs font-semibold text-muted-foreground uppercase">
Master Effects Master Effects
</h3> </h3>
<div className="flex gap-1"> <div className="flex gap-1">
<Button
variant="ghost"
size="icon-sm"
onClick={() => setPresetDialogOpen(true)}
title="Manage presets"
>
<FolderOpen className="h-4 w-4" />
</Button>
{masterEffectChain.effects.length > 0 && (
<Button <Button
variant="ghost" variant="ghost"
size="icon-sm" size="icon-sm"
onClick={onClearMasterChain} onClick={() => setPresetDialogOpen(true)}
title="Clear all effects" title="Manage presets"
> >
<Trash2 className="h-4 w-4 text-destructive" /> <FolderOpen className="h-4 w-4" />
</Button> </Button>
)} {masterEffectChain.effects.length > 0 && (
<Button
variant="ghost"
size="icon-sm"
onClick={onClearMasterChain}
title="Clear all effects"
>
<Trash2 className="h-4 w-4 text-destructive" />
</Button>
)}
</div>
</div> </div>
</div>
<EffectRack <EffectRack
chain={masterEffectChain} chain={masterEffectChain}
onToggleEffect={onToggleMasterEffect} onToggleEffect={onToggleMasterEffect}
onRemoveEffect={onRemoveMasterEffect} onRemoveEffect={onRemoveMasterEffect}
onReorderEffects={onReorderMasterEffects} onReorderEffects={onReorderMasterEffects}
/> />
<PresetManager <PresetManager
open={presetDialogOpen} open={presetDialogOpen}
onClose={() => setPresetDialogOpen(false)} onClose={() => setPresetDialogOpen(false)}
currentChain={masterEffectChain} currentChain={masterEffectChain}
presets={masterEffectPresets} presets={masterEffectPresets}
onSavePreset={onSaveMasterPreset} onSavePreset={onSaveMasterPreset}
onLoadPreset={onLoadMasterPreset} onLoadPreset={onLoadMasterPreset}
onDeletePreset={onDeleteMasterPreset} onDeletePreset={onDeleteMasterPreset}
onExportPreset={() => {}} onExportPreset={() => {}}
onImportPreset={(preset) => onSaveMasterPreset(preset)} onImportPreset={(preset) => onSaveMasterPreset(preset)}
/> />
</div> </div>
</>
)} )}
</div> </div>
</div> </div>