fix: passthrough công cụ từ main sang sub

This commit is contained in:
2026-07-19 11:30:30 +07:00
parent acee68e8f5
commit da923a7a79
2 changed files with 153 additions and 4724 deletions
+153 -47
View File
@@ -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"> <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"> <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 rounded ${activeTool === 'select' ? 'bg-cyan-700' : 'bg-zinc-700'}`} onClick={() => setActiveTool('select')} title="Select Tool">
<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> <i data-lucide="mouse-pointer" className="w-4 h-4"></i>
<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>
<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 === '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>
<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"> <div className="flex items-center gap-1">
<span className="text-[10px] text-zinc-400">Normalize:</span> <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"/> <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>
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<span className="text-[10px] text-zinc-400">Gain:</span> <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"/> <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>
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<span className="text-[10px] text-zinc-400">Pitch:</span> <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"/> <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>
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<span className="text-[10px] text-zinc-400">Stretch:</span> <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"/> <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> </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, '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> <button onClick={() => handleSubTabFade(st.id, 'out')} className="px-2 py-1 text-[10px] rounded hover:bg-zinc-700">Fade Out</button>
</div> </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> </div>
); );
};
const App = () => { const App = () => {
// ── State Definitions ── // ── State Definitions ──
@@ -3991,6 +4092,36 @@
> >
<i data-lucide="clipboard" className="w-3.5 h-3.5"></i> <i data-lucide="clipboard" className="w-3.5 h-3.5"></i>
</button> </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>
<div className="w-[1px] h-4 bg-zinc-800 mx-0.5"></div> <div className="w-[1px] h-4 bg-zinc-800 mx-0.5"></div>
@@ -4315,10 +4446,10 @@
</button> </button>
</section> </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"> <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"> <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 className="text-sm font-bold font-mono text-zinc-100">{formatTime(currentTime)}</div>
</div> </div>
@@ -4340,44 +4471,6 @@
<span className="text-zinc-200 font-mono text-[11px] font-semibold mt-0.5">{selectionStats.length}s</span> <span className="text-zinc-200 font-mono text-[11px] font-semibold mt-0.5">{selectionStats.length}s</span>
</div> </div>
</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> </section>
{/* AI Analysis & Edit Section */} {/* AI Analysis & Edit Section */}
@@ -4487,6 +4580,19 @@
handleSubTabPitch={handleSubTabPitch} handleSubTabPitch={handleSubTabPitch}
handleSubTabStretch={handleSubTabStretch} handleSubTabStretch={handleSubTabStretch}
handleSubTabFade={handleSubTabFadeState} 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 <SubTabWaveform
buffer={st.buffer} buffer={st.buffer}
-4677
View File
File diff suppressed because it is too large Load Diff