Files

211 lines
7.6 KiB
TypeScript
Raw Permalink Normal View History

'use client';
import * as React from 'react';
import { X, Download } from 'lucide-react';
import { Button } from '@/components/ui/Button';
import { cn } from '@/lib/utils/cn';
export interface ExportSettings {
format: 'wav' | 'mp3';
scope: 'project' | 'selection' | 'tracks'; // Export scope
bitDepth: 16 | 24 | 32;
bitrate: number; // For MP3: 128, 192, 256, 320 kbps
normalize: boolean;
filename: string;
}
export interface ExportDialogProps {
open: boolean;
onClose: () => void;
onExport: (settings: ExportSettings) => void;
isExporting?: boolean;
hasSelection?: boolean; // Whether any track has a selection
}
export function ExportDialog({ open, onClose, onExport, isExporting, hasSelection }: ExportDialogProps) {
const [settings, setSettings] = React.useState<ExportSettings>({
format: 'wav',
scope: 'project',
bitDepth: 16,
bitrate: 192, // Default MP3 bitrate
normalize: true,
filename: 'mix',
});
const handleExport = () => {
onExport(settings);
};
if (!open) return null;
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
<div className="bg-card border border-border rounded-lg shadow-xl w-full max-w-md p-6">
{/* Header */}
<div className="flex items-center justify-between mb-6">
<h2 className="text-lg font-semibold text-foreground">Export Audio</h2>
<button
onClick={onClose}
className="text-muted-foreground hover:text-foreground transition-colors"
disabled={isExporting}
>
<X className="h-5 w-5" />
</button>
</div>
{/* Settings */}
<div className="space-y-4">
{/* Filename */}
<div>
<label className="block text-sm font-medium text-foreground mb-2">
Filename
</label>
<input
type="text"
value={settings.filename}
onChange={(e) => setSettings({ ...settings, filename: e.target.value })}
className="w-full px-3 py-2 bg-background border border-border rounded text-foreground focus:outline-none focus:ring-2 focus:ring-primary"
disabled={isExporting}
/>
<p className="text-xs text-muted-foreground mt-1">
.{settings.format} will be added automatically
</p>
</div>
{/* Format */}
<div>
<label className="block text-sm font-medium text-foreground mb-2">
Format
</label>
<select
value={settings.format}
onChange={(e) => setSettings({ ...settings, format: e.target.value as 'wav' | 'mp3' })}
className="w-full px-3 py-2 bg-background border border-border rounded text-foreground focus:outline-none focus:ring-2 focus:ring-primary"
disabled={isExporting}
>
<option value="wav">WAV (Lossless, Uncompressed)</option>
<option value="mp3">MP3 (Lossy, Compressed)</option>
</select>
</div>
{/* Export Scope */}
<div>
<label className="block text-sm font-medium text-foreground mb-2">
Export Scope
</label>
<select
value={settings.scope}
onChange={(e) => setSettings({ ...settings, scope: e.target.value as 'project' | 'selection' | 'tracks' })}
className="w-full px-3 py-2 bg-background border border-border rounded text-foreground focus:outline-none focus:ring-2 focus:ring-primary"
disabled={isExporting}
>
<option value="project">Entire Project (Mix All Tracks)</option>
<option value="selection" disabled={!hasSelection}>
Selected Region {!hasSelection && '(No selection)'}
</option>
<option value="tracks">Individual Tracks (Separate Files)</option>
</select>
<p className="text-xs text-muted-foreground mt-1">
{settings.scope === 'project' && 'Mix all tracks into a single file'}
{settings.scope === 'selection' && 'Export only the selected region'}
{settings.scope === 'tracks' && 'Export each track as a separate file'}
</p>
</div>
{/* Bit Depth (WAV only) */}
{settings.format === 'wav' && (
<div>
<label className="block text-sm font-medium text-foreground mb-2">
Bit Depth
</label>
<div className="flex gap-2">
{[16, 24, 32].map((depth) => (
<button
key={depth}
onClick={() => setSettings({ ...settings, bitDepth: depth as 16 | 24 | 32 })}
className={cn(
'flex-1 px-3 py-2 rounded text-sm font-medium transition-colors',
settings.bitDepth === depth
? 'bg-primary text-primary-foreground'
: 'bg-background border border-border text-foreground hover:bg-accent'
)}
disabled={isExporting}
>
{depth}-bit {depth === 32 && '(Float)'}
</button>
))}
</div>
</div>
)}
{/* MP3 Bitrate */}
{settings.format === 'mp3' && (
<div>
<label className="block text-sm font-medium text-foreground mb-2">
Bitrate
</label>
<div className="flex gap-2">
{[128, 192, 256, 320].map((rate) => (
<button
key={rate}
onClick={() => setSettings({ ...settings, bitrate: rate })}
className={cn(
'flex-1 px-3 py-2 rounded text-sm font-medium transition-colors',
settings.bitrate === rate
? 'bg-primary text-primary-foreground'
: 'bg-background border border-border text-foreground hover:bg-accent'
)}
disabled={isExporting}
>
{rate} kbps
</button>
))}
</div>
</div>
)}
{/* Normalize */}
<div>
<label className="flex items-center gap-2 cursor-pointer">
<input
type="checkbox"
checked={settings.normalize}
onChange={(e) => setSettings({ ...settings, normalize: e.target.checked })}
className="w-4 h-4 rounded border-border text-primary focus:ring-primary"
disabled={isExporting}
/>
<span className="text-sm font-medium text-foreground">
Normalize audio
</span>
</label>
<p className="text-xs text-muted-foreground mt-1 ml-6">
Prevents clipping by adjusting peak levels
</p>
</div>
</div>
{/* Actions */}
<div className="flex gap-3 mt-6">
<Button
variant="outline"
onClick={onClose}
className="flex-1"
disabled={isExporting}
>
Cancel
</Button>
<Button
onClick={handleExport}
className="flex-1"
disabled={isExporting || !settings.filename.trim()}
>
<Download className="h-4 w-4 mr-2" />
{isExporting ? 'Exporting...' : 'Export'}
</Button>
</div>
</div>
</div>
);
}