fix: passthrough công cụ từ main sang sub
This commit is contained in:
+153
-47
@@ -832,40 +832,141 @@
|
||||
);
|
||||
};
|
||||
|
||||
const SubTabToolbar = ({ st, activeTool, setActiveTool, handleSubTabNormalizeWithValue, handleSubTabGainWithValue, handleSubTabPitch, handleSubTabStretch, handleSubTabFade }) => (
|
||||
const SubTabToolbar = ({ st, activeTool, setActiveTool, handleSubTabNormalizeWithValue, handleSubTabGainWithValue, handleSubTabPitch, handleSubTabStretch, handleSubTabFade, onPlayPause, onStop, onRewind, onForward, onLoop, onRecord, onRateChange, onCut, onCopy, onPaste, onGlue, snapValue, onSnapChange }) => {
|
||||
const isPlaying = st?.isPlaying || false;
|
||||
const isLooping = st?.isLooping || false;
|
||||
const isRecording = st?.isRecording || false;
|
||||
|
||||
return (
|
||||
<div className="daw-header flex h-16 items-center px-4 border-b daw-border gap-3 bg-zinc-800">
|
||||
{/* Tools Section - passthrough từ main session */}
|
||||
<div className="flex items-center space-x-1 border-r border-zinc-700 pr-3">
|
||||
<button className={`p-1.5 rounded ${activeTool === 'select' ? 'bg-cyan-700' : 'bg-zinc-700'}`} onClick={() => setActiveTool('select')} title="Select"><i data-lucide="mouse-pointer" className="w-4 h-4"></i></button>
|
||||
<button className={`p-1.5 rounded ${activeTool === 'grab' ? 'bg-cyan-700' : 'bg-zinc-700'}`} onClick={() => setActiveTool('grab')} title="Grab"><i data-lucide="hand" className="w-4 h-4"></i></button>
|
||||
<button className={`p-1.5 rounded ${activeTool === 'razor' ? 'bg-cyan-700' : 'bg-zinc-700'}`} onClick={() => setActiveTool('razor')} title="Razor"><i data-lucide="scissors" className="w-4 h-4"></i></button>
|
||||
<button className={`p-1.5 rounded ${activeTool === 'pen' ? 'bg-cyan-700' : 'bg-zinc-700'}`} onClick={() => setActiveTool('pen')} title="Pen Tool"><i data-lucide="pen-tool" className="w-4 h-4"></i></button>
|
||||
<button className={`p-1 rounded ${activeTool === 'select' ? 'bg-cyan-700' : 'bg-zinc-700'}`} onClick={() => setActiveTool('select')} title="Select Tool">
|
||||
<i data-lucide="mouse-pointer" className="w-4 h-4"></i>
|
||||
</button>
|
||||
<button className={`p-1 rounded ${activeTool === 'grab' ? 'bg-cyan-700' : 'bg-zinc-700'}`} onClick={() => setActiveTool('grab')} title="Grab Tool">
|
||||
<i data-lucide="hand" className="w-4 h-4"></i>
|
||||
</button>
|
||||
<button className={`p-1 rounded ${activeTool === 'razor' ? 'bg-cyan-700' : 'bg-zinc-700'}`} onClick={() => setActiveTool('razor')} title="Razor Tool">
|
||||
<svg className="w-4 h-4 text-orange-400" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="M6 3h12a2 2 0 0 1 2 2v2a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2z"/>
|
||||
<path d="M4 9h16l-3 9H7z"/>
|
||||
<circle cx="12" cy="6" r="1"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button className={`p-1 rounded ${activeTool === 'pen' ? 'bg-cyan-700' : 'bg-zinc-700'}`} onClick={() => setActiveTool('pen')} title="Pen Tool">
|
||||
<i data-lucide="pen-tool" className="w-4 h-4"></i>
|
||||
</button>
|
||||
|
||||
<div className="w-[1px] h-4 bg-zinc-800 mx-0.5"></div>
|
||||
|
||||
<button onClick={onGlue} className="p-0.5 rounded text-zinc-400 hover:bg-zinc-800 hover:text-purple-400 transition" title="Glue Clips">
|
||||
<i data-lucide="link" className="w-3.5 h-3.5"></i>
|
||||
</button>
|
||||
<button onClick={onCut} className="p-0.5 rounded text-zinc-400 hover:bg-zinc-800 hover:text-red-400 transition" title="Cut (Ctrl+X)">
|
||||
<i data-lucide="scissors" className="w-3.5 h-3.5"></i>
|
||||
</button>
|
||||
<button onClick={onCopy} className="p-0.5 rounded text-zinc-400 hover:bg-zinc-800 hover:text-blue-400 transition" title="Copy (Ctrl+C)">
|
||||
<i data-lucide="copy" className="w-3.5 h-3.5"></i>
|
||||
</button>
|
||||
<button onClick={onPaste} className="p-0.5 rounded text-zinc-400 hover:bg-zinc-800 hover:text-emerald-400 transition" title="Paste (Ctrl+V)">
|
||||
<i data-lucide="clipboard" className="w-3.5 h-3.5"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-3 flex-1 overflow-x-auto no-scrollbar">
|
||||
{/* Snap Section - passthrough */}
|
||||
<div className="flex items-center gap-1 border-r border-zinc-700 pr-3">
|
||||
<span className="text-[9px] text-zinc-500 font-bold uppercase">Snap</span>
|
||||
<select
|
||||
value={snapValue || 'free'}
|
||||
onChange={(e) => onSnapChange(e.target.value)}
|
||||
className="bg-zinc-850 text-zinc-300 text-[10px] px-1 py-0.5 rounded border border-zinc-800 focus:outline-none font-mono"
|
||||
>
|
||||
<option value="free">Free</option>
|
||||
<option value="1">1</option>
|
||||
<option value="1/2">1/2</option>
|
||||
<option value="1/4">1/4</option>
|
||||
<option value="1/8">1/8</option>
|
||||
<option value="1/16">1/16</option>
|
||||
<option value="1/32">1/32</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Effects Controls */}
|
||||
<div className="flex items-center space-x-2 flex-1 overflow-x-auto no-scrollbar">
|
||||
<div className="flex items-center gap-1">
|
||||
<span className="text-[10px] text-zinc-400">Normalize:</span>
|
||||
<input type="range" min="-12" max="0" value={st.effects?.normalizeDb || 0} step="0.1" onChange={(e) => handleSubTabNormalizeWithValue(st.id, parseFloat(e.target.value))} className="w-20 h-1.5"/>
|
||||
<span className="text-[10px] text-zinc-500 w-10 text-right">{st.effects?.normalizeDb || 0} dB</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<span className="text-[10px] text-zinc-400">Gain:</span>
|
||||
<input type="range" min="-40" max="24" value={st.effects?.gainDb || 0} step="0.1" onChange={(e) => handleSubTabGainWithValue(st.id, parseFloat(e.target.value))} className="w-20 h-1.5"/>
|
||||
<span className="text-[10px] text-zinc-500 w-10 text-right">{st.effects?.gainDb || 0} dB</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<span className="text-[10px] text-zinc-400">Pitch:</span>
|
||||
<input type="range" min="-12" max="12" value={st.effects?.pitch || 0} step="0.1" onChange={(e) => handleSubTabPitch(st.id, parseFloat(e.target.value))} className="w-20 h-1.5"/>
|
||||
<span className="text-[10px] text-zinc-500 w-10 text-right">{st.effects?.pitch || 0} st</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<span className="text-[10px] text-zinc-400">Stretch:</span>
|
||||
<input type="range" min="50" max="200" value={st.effects?.speedStretch || 100} step="1" onChange={(e) => handleSubTabStretch(st.id, parseInt(e.target.value))} className="w-20 h-1.5"/>
|
||||
<span className="text-[10px] text-zinc-500 w-10 text-right">{st.effects?.speedStretch || 100}%</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-1 pl-3 border-l border-zinc-700">
|
||||
{/* Fade Buttons */}
|
||||
<div className="flex items-center space-x-1 border-r border-zinc-700 pr-3">
|
||||
<button onClick={() => handleSubTabFade(st.id, 'in')} className="px-2 py-1 text-[10px] rounded hover:bg-zinc-700">Fade In</button>
|
||||
<button onClick={() => handleSubTabFade(st.id, 'out')} className="px-2 py-1 text-[10px] rounded hover:bg-zinc-700">Fade Out</button>
|
||||
</div>
|
||||
|
||||
{/* Transport Controls */}
|
||||
<div className="flex items-center space-x-1 border-r border-zinc-700 pr-3">
|
||||
<button onClick={onRewind} className="w-8 h-8 flex items-center justify-center bg-zinc-800 hover:bg-zinc-700 text-zinc-200 rounded border border-zinc-700 transition" title="Rewind">
|
||||
<i data-lucide="skip-back" className="w-4 h-4"></i>
|
||||
</button>
|
||||
<button onClick={onPlayPause} className={`w-8 h-8 flex items-center justify-center rounded border transition ${
|
||||
isPlaying
|
||||
? 'bg-emerald-600 text-black border-emerald-500 hover:bg-emerald-500'
|
||||
: 'bg-cyan-600 text-white border-cyan-500 hover:bg-cyan-500'
|
||||
}`} title={isPlaying ? "Pause" : "Play"}>
|
||||
<i data-lucide={isPlaying ? 'pause' : 'play'} className="w-4 h-4 fill-current"></i>
|
||||
</button>
|
||||
<button onClick={onStop} className="w-8 h-8 flex items-center justify-center bg-zinc-800 hover:bg-zinc-700 text-zinc-200 rounded border border-zinc-700 transition" title="Stop">
|
||||
<i data-lucide="square" className="w-4 h-4 fill-current"></i>
|
||||
</button>
|
||||
<button onClick={onForward} className="w-8 h-8 flex items-center justify-center bg-zinc-800 hover:bg-zinc-700 text-zinc-200 rounded border border-zinc-700 transition" title="Forward">
|
||||
<i data-lucide="skip-forward" className="w-4 h-4"></i>
|
||||
</button>
|
||||
<button onClick={onLoop} className={`w-8 h-8 flex items-center justify-center rounded border transition ${
|
||||
isLooping ? 'bg-amber-600 text-black border-amber-500 hover:bg-amber-500' : 'bg-zinc-800 text-zinc-200 border-zinc-700 hover:bg-zinc-700'
|
||||
}`} title={isLooping ? "Loop On" : "Loop Off"}>
|
||||
<i data-lucide="repeat" className="w-4 h-4"></i>
|
||||
</button>
|
||||
<button onClick={onRecord} className={`w-8 h-8 flex items-center justify-center rounded border transition ${
|
||||
isRecording ? 'bg-red-600 text-white border-red-500' : 'bg-zinc-800 text-zinc-200 border-zinc-700 hover:bg-zinc-700'
|
||||
}`} title={isRecording ? "Recording" : "Record"}>
|
||||
<i data-lucide="circle" className="w-4 h-4"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Rate Control */}
|
||||
<div className="flex items-center space-x-1">
|
||||
<span className="text-[10px] text-zinc-400">Rate:</span>
|
||||
<select value={st.playbackRate || 1} onChange={(e) => onRateChange(parseFloat(e.target.value))} className="bg-zinc-700 text-zinc-100 text-[10px] px-1 py-0.5 rounded border border-zinc-600">
|
||||
<option value="0.5">0.5x</option>
|
||||
<option value="0.75">0.75x</option>
|
||||
<option value="1">1x</option>
|
||||
<option value="1.25">1.25x</option>
|
||||
<option value="1.5">1.5x</option>
|
||||
<option value="2">2x</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const App = () => {
|
||||
// ── State Definitions ──
|
||||
@@ -3991,6 +4092,36 @@
|
||||
>
|
||||
<i data-lucide="clipboard" className="w-3.5 h-3.5"></i>
|
||||
</button>
|
||||
|
||||
<div className="w-[1px] h-4 bg-zinc-800 mx-0.5"></div>
|
||||
|
||||
{/* Transport Controls - gần toolbar */}
|
||||
<button onClick={() => setCurrentTime(0)}
|
||||
className="w-6 h-6 flex items-center justify-center bg-zinc-800 hover:bg-zinc-700 text-zinc-200 rounded border border-zinc-700 transition"
|
||||
title="Quay lại đầu"><i data-lucide="skip-back" className="w-3 h-3"></i></button>
|
||||
<button onClick={handlePlayPause}
|
||||
className={`w-6 h-6 flex items-center justify-center rounded border transition ${
|
||||
isPlaying
|
||||
? 'bg-emerald-600 text-black border-emerald-500 hover:bg-emerald-500'
|
||||
: 'bg-cyan-600 text-white border-cyan-500 hover:bg-cyan-500'
|
||||
}`}
|
||||
title={isPlaying ? "Tạm dừng" : "Play"}>
|
||||
<i data-lucide={isPlaying ? "pause" : "play"} className="w-3 h-3 fill-current"></i>
|
||||
</button>
|
||||
<button onClick={handleStop}
|
||||
className="w-6 h-6 flex items-center justify-center bg-zinc-800 hover:bg-zinc-700 text-zinc-200 rounded border border-zinc-700 transition"
|
||||
title="Stop"><i data-lucide="square" className="w-3 h-3 fill-current"></i></button>
|
||||
<button onClick={() => setCurrentTime(maxDuration)}
|
||||
className="w-6 h-6 flex items-center justify-center bg-zinc-800 hover:bg-zinc-700 text-zinc-200 rounded border border-zinc-700 transition"
|
||||
title="Đến cuối"><i data-lucide="skip-forward" className="w-3 h-3"></i></button>
|
||||
<button onClick={() => setIsLoopingSelection(prev => !prev)}
|
||||
className={`w-6 h-6 flex items-center justify-center rounded border transition ${
|
||||
isLoopingSelection
|
||||
? 'bg-amber-600 text-black border-amber-500 hover:bg-amber-500'
|
||||
: 'bg-zinc-800 text-zinc-200 border-zinc-700 hover:bg-zinc-700'
|
||||
}`} title="Bật/Tắt Lặp vùng chọn">
|
||||
<i data-lucide="repeat" className="w-3 h-3"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="w-[1px] h-4 bg-zinc-800 mx-0.5"></div>
|
||||
@@ -4315,10 +4446,10 @@
|
||||
</button>
|
||||
</section>
|
||||
|
||||
{/* Transport Section */}
|
||||
{/* Selection Info Section */}
|
||||
<section className="md:col-span-4 bg-[#242424] border border-zinc-800 rounded p-3 flex flex-col justify-between items-center text-center">
|
||||
<div className="flex w-full items-center justify-between">
|
||||
<span className="text-[10px] text-zinc-500 font-bold uppercase tracking-wider">Transport Controls</span>
|
||||
<span className="text-[10px] text-zinc-500 font-bold uppercase tracking-wider">Selection Info</span>
|
||||
<div className="text-sm font-bold font-mono text-zinc-100">{formatTime(currentTime)}</div>
|
||||
</div>
|
||||
|
||||
@@ -4340,44 +4471,6 @@
|
||||
<span className="text-zinc-200 font-mono text-[11px] font-semibold mt-0.5">{selectionStats.length}s</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-1 justify-center w-full mt-1.5">
|
||||
<button onClick={() => setCurrentTime(0)}
|
||||
className="w-8 h-8 flex items-center justify-center bg-zinc-800 hover:bg-zinc-700 text-zinc-200 rounded border border-zinc-700 transition"
|
||||
title="Quay lại đầu"><i data-lucide="skip-back" className="w-4 h-4"></i></button>
|
||||
<button onClick={() => { if (selLeft !== null) setCurrentTime(selLeft); }}
|
||||
className="w-8 h-8 flex items-center justify-center bg-zinc-800 hover:bg-zinc-700 text-zinc-200 rounded border border-zinc-700 transition"
|
||||
title="Đầu vùng chọn"><i data-lucide="step-back" className="w-4 h-4"></i></button>
|
||||
<button onClick={handlePlayPause}
|
||||
className={`w-8 h-8 flex items-center justify-center rounded border transition ${
|
||||
isPlaying
|
||||
? 'bg-emerald-600 text-black border-emerald-500 hover:bg-emerald-500'
|
||||
: 'bg-cyan-600 text-white border-cyan-500 hover:bg-cyan-500'
|
||||
}`}
|
||||
title={isPlaying ? "Tạm dừng" : "Play"}>
|
||||
<i data-lucide="play" className="w-4 h-4 fill-current"></i>
|
||||
</button>
|
||||
<button onClick={handlePause}
|
||||
className="w-8 h-8 flex items-center justify-center bg-zinc-800 hover:bg-zinc-700 text-zinc-200 rounded border border-zinc-700 transition"
|
||||
title="Pause"><i data-lucide="pause" className="w-4 h-4"></i></button>
|
||||
<button onClick={handleStop}
|
||||
className="w-8 h-8 flex items-center justify-center bg-zinc-800 hover:bg-zinc-700 text-zinc-200 rounded border border-zinc-700 transition"
|
||||
title="Stop"><i data-lucide="square" className="w-4 h-4 fill-current"></i></button>
|
||||
<button onClick={() => { if (selRight !== null) setCurrentTime(selRight); }}
|
||||
className="w-8 h-8 flex items-center justify-center bg-zinc-800 hover:bg-zinc-700 text-zinc-200 rounded border border-zinc-700 transition"
|
||||
title="Cuối vùng chọn"><i data-lucide="step-forward" className="w-4 h-4"></i></button>
|
||||
<button onClick={() => setCurrentTime(maxDuration)}
|
||||
className="w-8 h-8 flex items-center justify-center bg-zinc-800 hover:bg-zinc-700 text-zinc-200 rounded border border-zinc-700 transition"
|
||||
title="Đến cuối"><i data-lucide="skip-forward" className="w-4 h-4"></i></button>
|
||||
<button onClick={() => setIsLoopingSelection(prev => !prev)}
|
||||
className={`w-8 h-8 flex items-center justify-center rounded border transition ${
|
||||
isLoopingSelection
|
||||
? 'bg-amber-600 text-black border-amber-500 hover:bg-amber-500'
|
||||
: 'bg-zinc-800 text-zinc-200 border-zinc-700 hover:bg-zinc-700'
|
||||
}`} title="Bật/Tắt Lặp vùng chọn">
|
||||
<i data-lucide="repeat" className="w-4 h-4"></i>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* AI Analysis & Edit Section */}
|
||||
@@ -4487,6 +4580,19 @@
|
||||
handleSubTabPitch={handleSubTabPitch}
|
||||
handleSubTabStretch={handleSubTabStretch}
|
||||
handleSubTabFade={handleSubTabFadeState}
|
||||
onPlayPause={handlePlayPause}
|
||||
onStop={handleStop}
|
||||
onRewind={() => setSubTabs(prev => prev.map(s => s.id === st.id ? { ...s, currentTime: Math.max(0, (s.currentTime || 0) - 1) } : s))}
|
||||
onForward={() => setSubTabs(prev => prev.map(s => s.id === st.id ? { ...s, currentTime: Math.min(s.buffer?.duration || 0, (s.currentTime || 0) + 1) } : s))}
|
||||
onLoop={() => setSubTabs(prev => prev.map(s => s.id === st.id ? { ...s, isLooping: !s.isLooping } : s))}
|
||||
onRecord={() => setSubTabs(prev => prev.map(s => s.id === st.id ? { ...s, isRecording: !s.isRecording } : s))}
|
||||
onRateChange={(rate) => setSubTabs(prev => prev.map(s => s.id === st.id ? { ...s, playbackRate: rate } : s))}
|
||||
onCut={() => { const curTabId = activeTab; handleSubTabCut(curTabId); }}
|
||||
onCopy={() => { const curTabId = activeTab; handleSubTabCopy(curTabId); }}
|
||||
onPaste={() => { const curTabId = activeTab; handleSubTabPaste(curTabId); }}
|
||||
onGlue={() => showToast('Glue: Gộp clip trong subtab', 'info')}
|
||||
snapValue={snapValue}
|
||||
onSnapChange={setSnapValue}
|
||||
/>
|
||||
<SubTabWaveform
|
||||
buffer={st.buffer}
|
||||
|
||||
-4677
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user