feat: redesign track layout to DAW-style with left control panel

Major UX improvement inspired by Audacity/Ableton Live:

- Each track now has 2 sections: left control panel (fixed 288px) and right waveform (flexible)
- Left panel contains: track name, color indicator, collapse toggle, volume, pan, solo, mute, delete
- TrackHeader component functionality moved directly into Track component
- Removed redundant track controls from SidePanel
- SidePanel now simplified to show global actions and effect chain
- Track controls are always visible on the left, waveform scrolls horizontally on the right
- Collapsed tracks show only the header row (48px height)
- Much better UX for multi-track editing

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-18 06:41:22 +01:00
parent f42e5d4556
commit e376f3b0b4
2 changed files with 199 additions and 158 deletions

View File

@@ -9,13 +9,10 @@ import {
Trash2,
Link2,
FolderOpen,
Volume2,
Music2,
} from 'lucide-react';
import { Button } from '@/components/ui/Button';
import { Slider } from '@/components/ui/Slider';
import { cn } from '@/lib/utils/cn';
import { formatDuration } from '@/lib/audio/decoder';
import type { Track } from '@/types/track';
import type { EffectChain, EffectPreset } from '@/lib/audio/effects/chain';
import { EffectRack } from '@/components/effects/EffectRack';
@@ -165,122 +162,17 @@ export function SidePanel({
)}
</div>
{/* Track List */}
{/* Track List - Simplified */}
{tracks.length > 0 ? (
<div className="space-y-2">
<h3 className="text-xs font-semibold text-muted-foreground uppercase">
Tracks ({tracks.length})
</h3>
<div className="space-y-2">
{tracks.map((track) => {
const isSelected = selectedTrackId === track.id;
return (
<div
key={track.id}
className={cn(
'p-3 rounded-lg border transition-colors cursor-pointer',
isSelected
? 'bg-primary/10 border-primary'
: 'bg-secondary/30 border-border hover:border-primary/50'
)}
onClick={() => onSelectTrack(isSelected ? null : track.id)}
>
<div className="flex items-center justify-between mb-2">
<div className="flex-1 min-w-0">
<div className="text-sm font-medium text-foreground truncate">
{String(track.name || 'Untitled Track')}
</div>
{track.audioBuffer && (
<div className="text-xs text-muted-foreground">
{formatDuration(track.audioBuffer.duration)}
</div>
)}
</div>
<Button
variant="ghost"
size="icon-sm"
onClick={(e) => {
e.stopPropagation();
onRemoveTrack(track.id);
}}
title="Remove track"
>
<Trash2 className="h-3.5 w-3.5 text-destructive" />
</Button>
</div>
{/* Track Controls - Always visible */}
<div className="space-y-2" onClick={(e) => e.stopPropagation()}>
{/* Volume */}
<div className="space-y-1">
<div className="flex items-center justify-between">
<label className="text-xs text-muted-foreground flex items-center gap-1">
<Volume2 className="h-3 w-3" />
Volume
</label>
<span className="text-xs text-muted-foreground">
{Math.round(track.volume * 100)}%
</span>
</div>
<Slider
value={track.volume}
onChange={(value) => onUpdateTrack(track.id, { volume: value })}
min={0}
max={1}
step={0.01}
/>
</div>
{/* Pan */}
<div className="space-y-1">
<div className="flex items-center justify-between">
<label className="text-xs text-muted-foreground">Pan</label>
<span className="text-xs text-muted-foreground">
{track.pan === 0
? 'C'
: track.pan < 0
? `L${Math.round(Math.abs(track.pan) * 100)}`
: `R${Math.round(track.pan * 100)}`}
</span>
</div>
<Slider
value={track.pan}
onChange={(value) => onUpdateTrack(track.id, { pan: value })}
min={-1}
max={1}
step={0.01}
/>
</div>
{/* Solo / Mute */}
<div className="flex gap-2">
<Button
variant={track.solo ? 'default' : 'outline'}
size="sm"
onClick={(e) => {
e.stopPropagation();
onUpdateTrack(track.id, { solo: !track.solo });
}}
className="flex-1 text-xs"
>
Solo
</Button>
<Button
variant={track.mute ? 'default' : 'outline'}
size="sm"
onClick={(e) => {
e.stopPropagation();
onUpdateTrack(track.id, { mute: !track.mute });
}}
className="flex-1 text-xs"
>
Mute
</Button>
</div>
</div>
</div>
);
})}
<div className="text-xs text-muted-foreground">
<p className="mb-2">
Track controls are located on the left side of each track in the timeline.
</p>
<p>Click a track to select it and apply effects from the Effect Chain tab.</p>
</div>
</div>
) : (