Phase 6.5 Advanced Effects: - Add Pitch Shifter with semitones and cents adjustment - Add Time Stretch with pitch preservation using overlap-add - Add Distortion with soft/hard/tube types and tone control - Add Bitcrusher with bit depth and sample rate reduction - Add AdvancedParameterDialog with real-time waveform visualization - Add 4 professional presets per effect type Improvements: - Fix undefined parameter errors by adding nullish coalescing operators - Add global custom scrollbar styling with color-mix transparency - Add custom-scrollbar utility class for side panel - Improve theme-aware scrollbar appearance in light/dark modes - Fix parameter initialization when switching effect types Integration: - All advanced effects support undo/redo via EffectCommand - Effects accessible via command palette and side panel - Selection-based processing support - Toast notifications for all effects 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
238 lines
4.9 KiB
TypeScript
238 lines
4.9 KiB
TypeScript
'use client';
|
|
|
|
import * as React from 'react';
|
|
import {
|
|
Play,
|
|
Pause,
|
|
Square,
|
|
SkipBack,
|
|
Scissors,
|
|
Copy,
|
|
Clipboard,
|
|
Trash2,
|
|
CropIcon,
|
|
Undo2,
|
|
Redo2,
|
|
ZoomIn,
|
|
ZoomOut,
|
|
Maximize2,
|
|
} from 'lucide-react';
|
|
import { Button } from '@/components/ui/Button';
|
|
import { cn } from '@/lib/utils/cn';
|
|
|
|
export interface ToolbarProps {
|
|
// Playback
|
|
isPlaying: boolean;
|
|
isPaused: boolean;
|
|
onPlay: () => void;
|
|
onPause: () => void;
|
|
onStop: () => void;
|
|
|
|
// Edit
|
|
hasSelection: boolean;
|
|
hasClipboard: boolean;
|
|
onCut: () => void;
|
|
onCopy: () => void;
|
|
onPaste: () => void;
|
|
onDelete: () => void;
|
|
onTrim: () => void;
|
|
|
|
// History
|
|
canUndo: boolean;
|
|
canRedo: boolean;
|
|
onUndo: () => void;
|
|
onRedo: () => void;
|
|
|
|
// Zoom
|
|
onZoomIn: () => void;
|
|
onZoomOut: () => void;
|
|
onFitToView: () => void;
|
|
|
|
disabled?: boolean;
|
|
className?: string;
|
|
}
|
|
|
|
export function Toolbar({
|
|
isPlaying,
|
|
isPaused,
|
|
onPlay,
|
|
onPause,
|
|
onStop,
|
|
hasSelection,
|
|
hasClipboard,
|
|
onCut,
|
|
onCopy,
|
|
onPaste,
|
|
onDelete,
|
|
onTrim,
|
|
canUndo,
|
|
canRedo,
|
|
onUndo,
|
|
onRedo,
|
|
onZoomIn,
|
|
onZoomOut,
|
|
onFitToView,
|
|
disabled = false,
|
|
className,
|
|
}: ToolbarProps) {
|
|
const handlePlayPause = () => {
|
|
if (isPlaying) {
|
|
onPause();
|
|
} else {
|
|
onPlay();
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div
|
|
className={cn(
|
|
'flex items-center gap-1 px-2 py-1.5 bg-card border-b border-border',
|
|
className
|
|
)}
|
|
>
|
|
{/* Transport Controls */}
|
|
<div className="flex items-center gap-0.5 pr-2 border-r border-border">
|
|
<Button
|
|
variant="ghost"
|
|
size="icon-sm"
|
|
onClick={onStop}
|
|
disabled={disabled || (!isPlaying && !isPaused)}
|
|
title="Stop"
|
|
>
|
|
<SkipBack className="h-4 w-4" />
|
|
</Button>
|
|
|
|
<Button
|
|
variant={isPlaying ? 'default' : 'ghost'}
|
|
size="icon-sm"
|
|
onClick={handlePlayPause}
|
|
disabled={disabled}
|
|
title={isPlaying ? 'Pause (Space)' : 'Play (Space)'}
|
|
>
|
|
{isPlaying ? (
|
|
<Pause className="h-4 w-4" />
|
|
) : (
|
|
<Play className="h-4 w-4 ml-0.5" />
|
|
)}
|
|
</Button>
|
|
|
|
<Button
|
|
variant="ghost"
|
|
size="icon-sm"
|
|
onClick={onStop}
|
|
disabled={disabled || (!isPlaying && !isPaused)}
|
|
title="Stop"
|
|
>
|
|
<Square className="h-4 w-4" />
|
|
</Button>
|
|
</div>
|
|
|
|
{/* Edit Tools */}
|
|
<div className="flex items-center gap-0.5 px-2 border-r border-border">
|
|
<Button
|
|
variant="ghost"
|
|
size="icon-sm"
|
|
onClick={onCut}
|
|
disabled={!hasSelection}
|
|
title="Cut (Ctrl+X)"
|
|
>
|
|
<Scissors className="h-4 w-4" />
|
|
</Button>
|
|
|
|
<Button
|
|
variant="ghost"
|
|
size="icon-sm"
|
|
onClick={onCopy}
|
|
disabled={!hasSelection}
|
|
title="Copy (Ctrl+C)"
|
|
>
|
|
<Copy className="h-4 w-4" />
|
|
</Button>
|
|
|
|
<Button
|
|
variant="ghost"
|
|
size="icon-sm"
|
|
onClick={onPaste}
|
|
disabled={!hasClipboard}
|
|
title="Paste (Ctrl+V)"
|
|
>
|
|
<Clipboard className="h-4 w-4" />
|
|
</Button>
|
|
|
|
<Button
|
|
variant="ghost"
|
|
size="icon-sm"
|
|
onClick={onDelete}
|
|
disabled={!hasSelection}
|
|
title="Delete (Del)"
|
|
>
|
|
<Trash2 className="h-4 w-4" />
|
|
</Button>
|
|
|
|
<Button
|
|
variant="ghost"
|
|
size="icon-sm"
|
|
onClick={onTrim}
|
|
disabled={!hasSelection}
|
|
title="Trim to Selection"
|
|
>
|
|
<CropIcon className="h-4 w-4" />
|
|
</Button>
|
|
</div>
|
|
|
|
{/* History */}
|
|
<div className="flex items-center gap-0.5 px-2 border-r border-border">
|
|
<Button
|
|
variant="ghost"
|
|
size="icon-sm"
|
|
onClick={onUndo}
|
|
disabled={!canUndo}
|
|
title="Undo (Ctrl+Z)"
|
|
>
|
|
<Undo2 className="h-4 w-4" />
|
|
</Button>
|
|
|
|
<Button
|
|
variant="ghost"
|
|
size="icon-sm"
|
|
onClick={onRedo}
|
|
disabled={!canRedo}
|
|
title="Redo (Ctrl+Y)"
|
|
>
|
|
<Redo2 className="h-4 w-4" />
|
|
</Button>
|
|
</div>
|
|
|
|
{/* Zoom Controls */}
|
|
<div className="flex items-center gap-0.5 px-2">
|
|
<Button
|
|
variant="ghost"
|
|
size="icon-sm"
|
|
onClick={onZoomOut}
|
|
title="Zoom Out"
|
|
>
|
|
<ZoomOut className="h-4 w-4" />
|
|
</Button>
|
|
|
|
<Button
|
|
variant="ghost"
|
|
size="icon-sm"
|
|
onClick={onZoomIn}
|
|
title="Zoom In"
|
|
>
|
|
<ZoomIn className="h-4 w-4" />
|
|
</Button>
|
|
|
|
<Button
|
|
variant="ghost"
|
|
size="icon-sm"
|
|
onClick={onFitToView}
|
|
title="Fit to View"
|
|
>
|
|
<Maximize2 className="h-4 w-4" />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|