fix: cannot login with default password
This commit is contained in:
+135
-105
@@ -1600,10 +1600,10 @@
|
||||
{/* 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 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>
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="mouse-pointer" className="w-4 h-4"></i></span>
|
||||
</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>
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="hand" className="w-4 h-4"></i></span>
|
||||
</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">
|
||||
@@ -1613,22 +1613,22 @@
|
||||
</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>
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="pen-tool" className="w-4 h-4"></i></span>
|
||||
</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>
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="link" className="w-3.5 h-3.5"></i></span>
|
||||
</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>
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="scissors" className="w-3.5 h-3.5"></i></span>
|
||||
</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>
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="copy" className="w-3.5 h-3.5"></i></span>
|
||||
</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>
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="clipboard" className="w-3.5 h-3.5"></i></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -1683,30 +1683,30 @@
|
||||
{/* 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>
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="skip-back" className="w-4 h-4"></i></span>
|
||||
</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>
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide={isPlaying ? 'pause' : 'play'} className="w-4 h-4 fill-current"></i></span>
|
||||
</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>
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="square" className="w-4 h-4 fill-current"></i></span>
|
||||
</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>
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="skip-forward" className="w-4 h-4"></i></span>
|
||||
</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>
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="repeat" className="w-4 h-4"></i></span>
|
||||
</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>
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="circle" className="w-4 h-4"></i></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -1898,15 +1898,24 @@
|
||||
const [newPassword, setNewPassword] = useState('');
|
||||
const [error, setError] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
useEffect(() => { if (mode) setActiveTab(mode); }, [mode]);
|
||||
useEffect(() => {
|
||||
if (mode) setActiveTab(mode);
|
||||
if (mode === 'force_change' && !oldPassword) {
|
||||
setOldPassword('admin123');
|
||||
}
|
||||
}, [mode]);
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault(); setError(''); setLoading(true);
|
||||
try {
|
||||
if (activeTab === 'login') {
|
||||
const targetUsername = username.trim() || 'admin';
|
||||
const res = await window.SonicAPI.login(targetUsername, password.trim());
|
||||
const targetPwd = password.trim() || 'admin123';
|
||||
const res = await window.SonicAPI.login(targetUsername, targetPwd);
|
||||
localStorage.setItem('sonic_token', res.access_token);
|
||||
localStorage.setItem('sonic_user', JSON.stringify(res.user));
|
||||
if (res.user && res.user.must_change_password) {
|
||||
setOldPassword(targetPwd);
|
||||
}
|
||||
onSuccess(res.user, res.access_token);
|
||||
} else if (activeTab === 'register') {
|
||||
const res = await window.SonicAPI.register(username.trim(), email.trim(), password.trim());
|
||||
@@ -1922,7 +1931,7 @@
|
||||
onSuccess(user, res.access_token);
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err.message || 'Thao tác không thành công');
|
||||
setError(err.message || (activeTab === 'login' ? 'Tài khoản hoặc mật khẩu không chính xác. (Nếu bạn đã đổi mật khẩu trước đó, vui lòng nhập mật khẩu mới mà bạn đã tạo)' : 'Thao tác không thành công'));
|
||||
} finally { setLoading(false); }
|
||||
};
|
||||
const isForceMode = activeTab === 'force_change';
|
||||
@@ -1980,6 +1989,15 @@
|
||||
<button type="submit" disabled={loading} className="w-full py-2 bg-teal-600 hover:bg-teal-500 text-white font-semibold rounded-lg shadow transition duration-150">
|
||||
{loading ? 'Đang xác thực...' : (isForceMode ? 'Đổi Mật Khẩu Ngay' : (activeTab === 'login' ? 'Đăng Nhập System' : 'Tạo Tài Khoản Mới'))}
|
||||
</button>
|
||||
{activeTab === 'login' && !isForceMode && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => { setUsername('admin'); setPassword('admin123'); setError(''); }}
|
||||
className="w-full mt-2 py-1.5 bg-amber-950/60 hover:bg-amber-900/80 text-amber-300 border border-amber-800/60 text-xs font-semibold rounded-lg transition flex items-center justify-center gap-1.5"
|
||||
>
|
||||
🔑 Điền Nhanh Tài Khoản Admin (admin / admin123)
|
||||
</button>
|
||||
)}
|
||||
</form>
|
||||
{!isForceMode && (
|
||||
<div className="mt-4 pt-4 border-t border-[#383838] text-center text-xs text-slate-400">
|
||||
@@ -2333,7 +2351,7 @@
|
||||
};
|
||||
|
||||
// ── Tab System (LOOP_EDITOR_2.md §1) ──
|
||||
const [activeTab, setActiveTab] = useState('subtab_1');
|
||||
const [activeTab, setActiveTab] = useState('main');
|
||||
const [subTabSelectedNodeTime, setSubTabSelectedNodeTime] = useState(null);
|
||||
const [subTabNormVal, setSubTabNormVal] = useState(0);
|
||||
const [subTabGainVal, setSubTabGainVal] = useState(100);
|
||||
@@ -2790,6 +2808,10 @@
|
||||
|
||||
useEffect(() => {
|
||||
const handler = (e) => {
|
||||
// Bypass global hotkeys when typing inside input/textarea/contentEditable elements
|
||||
if (e.target && (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.isContentEditable)) {
|
||||
return;
|
||||
}
|
||||
const ctrl = e.ctrlKey || e.metaKey;
|
||||
const alt = e.altKey;
|
||||
|
||||
@@ -5762,7 +5784,7 @@
|
||||
) : (
|
||||
<button key={item.label} onClick={(e) => { e.stopPropagation(); item.action(); setMenuOpen(null); }}
|
||||
className="w-full px-3 py-1.5 text-xs text-zinc-200 hover:bg-zinc-700 text-left flex items-center gap-2">
|
||||
<i data-lucide={item.icon} className="w-3.5 h-3.5 text-zinc-500 shrink-0"></i>
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide={item.icon} className="w-3.5 h-3.5 text-zinc-500 shrink-0"></i></span>
|
||||
<span className="flex-1">{item.label}</span>
|
||||
{item.shortcut && <span className="text-purple-400 text-[12px] font-semibold font-mono ml-auto pl-8">{item.shortcut}</span>}
|
||||
</button>
|
||||
@@ -5782,7 +5804,7 @@
|
||||
className={`px-1.5 py-0.5 rounded text-[10px] border transition ${
|
||||
showAIConfig ? 'bg-purple-900 text-purple-200 border-purple-700' : 'bg-zinc-800 text-zinc-400 border-zinc-700 hover:text-zinc-200'
|
||||
}`}>
|
||||
<i data-lucide="cpu" className="w-3 h-3"></i>
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="cpu" className="w-3 h-3"></i></span>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
@@ -5797,7 +5819,7 @@
|
||||
? 'text-cyan-400 border-cyan-500 bg-zinc-800/50'
|
||||
: 'text-zinc-500 border-transparent hover:text-zinc-300 hover:bg-zinc-800/30'
|
||||
}`}>
|
||||
<i data-lucide="layout-dashboard" className="w-3 h-3"></i> Main Session
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="layout-dashboard" className="w-3 h-3"></i></span> Main Session
|
||||
</button>
|
||||
{subTabs.map(st => (
|
||||
<div key={st.id} className="flex items-stretch">
|
||||
@@ -5807,13 +5829,13 @@
|
||||
? 'text-amber-400 border-amber-500 bg-zinc-800/50'
|
||||
: 'text-zinc-500 border-transparent hover:text-zinc-300 hover:bg-zinc-800/30'
|
||||
}`}>
|
||||
<i data-lucide="file-edit" className="w-3 h-3"></i>
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="file-edit" className="w-3 h-3"></i></span>
|
||||
<span className="max-w-[100px] truncate">{st.label}</span>
|
||||
</button>
|
||||
<button onClick={() => closeSubTab(st.id)}
|
||||
className="px-1 text-zinc-600 hover:text-red-400 transition text-[9px]"
|
||||
title="Close tab">
|
||||
<i data-lucide="x" className="w-3 h-3"></i>
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="x" className="w-3 h-3"></i></span>
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
@@ -5823,7 +5845,7 @@
|
||||
{showAIConfig && (
|
||||
<div className="bg-zinc-900 border-b border-purple-900 p-3 flex flex-col gap-2 transition-all">
|
||||
<div className="text-xs font-bold text-purple-400 uppercase tracking-wider flex items-center gap-1">
|
||||
<i data-lucide="cpu" className="w-4 h-4"></i> Cấu hình cổng kết nối API
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="cpu" className="w-4 h-4"></i></span> Cấu hình cổng kết nối API
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-2 text-xs">
|
||||
<div className="flex flex-col gap-1">
|
||||
@@ -5866,17 +5888,17 @@
|
||||
<div className="flex items-center gap-1 bg-zinc-900 border border-zinc-700 rounded px-1.5 py-0.5 shadow-lg"
|
||||
title="Kéo để di chuyển toolbar" style={{ cursor: 'grab' }}>
|
||||
<div className="flex items-center gap-0.5 mr-1 text-zinc-600">
|
||||
<i data-lucide="grip-vertical" className="w-3 h-3"></i>
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="grip-vertical" className="w-3 h-3"></i></span>
|
||||
</div>
|
||||
<button onClick={() => { setActiveTool('select'); showToast('Select Tool', 'info'); }}
|
||||
className={`w-7 h-7 flex items-center justify-center rounded ${activeTool === 'select' ? 'bg-cyan-700 text-white' : 'text-zinc-400 hover:text-zinc-200 hover:bg-zinc-800'}`}
|
||||
title="Select Tool (V)">
|
||||
<i data-lucide="mouse-pointer" className="w-3.5 h-3.5"></i>
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="mouse-pointer" className="w-3.5 h-3.5"></i></span>
|
||||
</button>
|
||||
<button onClick={() => { setActiveTool('grab'); showToast('Grab Tool', 'info'); }}
|
||||
className={`w-7 h-7 flex items-center justify-center rounded ${activeTool === 'grab' ? 'bg-cyan-700 text-white' : 'text-zinc-400 hover:text-zinc-200 hover:bg-zinc-800'}`}
|
||||
title="Grab Tool (H)">
|
||||
<i data-lucide="hand" className="w-3.5 h-3.5"></i>
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="hand" className="w-3.5 h-3.5"></i></span>
|
||||
</button>
|
||||
<div className="flex items-center gap-0.5 border border-zinc-700 rounded bg-zinc-850 px-0.5">
|
||||
<button onClick={() => { setActiveTool('razor'); showToast('Razor Tool', 'info'); }}
|
||||
@@ -5891,40 +5913,47 @@
|
||||
<button onClick={handleGlueTracks}
|
||||
className="w-7 h-7 flex items-center justify-center rounded text-zinc-400 hover:text-purple-400 hover:bg-zinc-800"
|
||||
title="Glue Clips">
|
||||
<i data-lucide="link" className="w-3.5 h-3.5"></i>
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="link" className="w-3.5 h-3.5"></i></span>
|
||||
</button>
|
||||
</div>
|
||||
<button onClick={() => { setActiveTool('pen'); showToast('Pen Tool', 'info'); }}
|
||||
className={`w-7 h-7 flex items-center justify-center rounded ${activeTool === 'pen' ? 'bg-cyan-700 text-white' : 'text-zinc-400 hover:text-zinc-200 hover:bg-zinc-800'}`}
|
||||
title="Pen Tool (P)">
|
||||
<i data-lucide="pen-tool" className="w-3.5 h-3.5"></i>
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="pen-tool" className="w-3.5 h-3.5"></i></span>
|
||||
</button>
|
||||
<div className="w-[1px] h-5 bg-zinc-700 mx-0.5"></div>
|
||||
<button onClick={handleCutTrack}
|
||||
className="w-7 h-7 flex items-center justify-center rounded text-zinc-400 hover:text-red-400 hover:bg-zinc-800"
|
||||
title="Cut (Ctrl+X)">
|
||||
<i data-lucide="scissors" className="w-3.5 h-3.5"></i>
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="scissors" className="w-3.5 h-3.5"></i></span>
|
||||
</button>
|
||||
<button onClick={handleCopyTrack}
|
||||
className="w-7 h-7 flex items-center justify-center rounded text-zinc-400 hover:text-blue-400 hover:bg-zinc-800"
|
||||
title="Copy (Ctrl+C)">
|
||||
<i data-lucide="copy" className="w-3.5 h-3.5"></i>
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="copy" className="w-3.5 h-3.5"></i></span>
|
||||
</button>
|
||||
<button onClick={handlePasteTrack}
|
||||
className="w-7 h-7 flex items-center justify-center rounded text-zinc-400 hover:text-emerald-400 hover:bg-zinc-800"
|
||||
title="Paste (Ctrl+V)">
|
||||
<i data-lucide="clipboard" className="w-3.5 h-3.5"></i>
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="clipboard" className="w-3.5 h-3.5"></i></span>
|
||||
</button>
|
||||
<div className="w-[1px] h-5 bg-zinc-700 mx-0.5"></div>
|
||||
<button onClick={addNewTrack}
|
||||
className="px-2 py-1 bg-cyan-600 hover:bg-cyan-500 text-white font-semibold rounded text-xs flex items-center gap-1 shadow transition"
|
||||
title="Thêm Track Mới (Ctrl+I)">
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="plus" className="w-3.5 h-3.5"></i></span>
|
||||
<span>Track</span>
|
||||
</button>
|
||||
<div className="w-[1px] h-5 bg-zinc-700 mx-0.5"></div>
|
||||
<button onClick={handleUndo} disabled={undoStack.length === 0}
|
||||
className="w-7 h-7 flex items-center justify-center rounded text-zinc-400 hover:text-zinc-200 hover:bg-zinc-800 disabled:opacity-30"
|
||||
title="Undo (Ctrl+Z)">
|
||||
<i data-lucide="undo" className="w-3.5 h-3.5"></i>
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="undo" className="w-3.5 h-3.5"></i></span>
|
||||
</button>
|
||||
<button onClick={handleRedo} disabled={redoStack.length === 0}
|
||||
className="w-7 h-7 flex items-center justify-center rounded text-zinc-400 hover:text-zinc-200 hover:bg-zinc-800 disabled:opacity-30"
|
||||
title="Redo (Ctrl+Y)">
|
||||
<i data-lucide="redo" className="w-3.5 h-3.5"></i>
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="redo" className="w-3.5 h-3.5"></i></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -5938,7 +5967,7 @@
|
||||
}
|
||||
}}
|
||||
className="w-7 h-7 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.5 h-3.5"></i></button>
|
||||
title="Quay lại đầu"><span className="inline-flex items-center shrink-0"><i data-lucide="skip-back" className="w-3.5 h-3.5"></i></span></button>
|
||||
<button onClick={() => {
|
||||
if (activeTab !== 'main') {
|
||||
setSubTabs(prev => prev.map(s => {
|
||||
@@ -5951,7 +5980,7 @@
|
||||
}
|
||||
}}
|
||||
className="w-7 h-7 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-3.5 h-3.5"></i></button>
|
||||
title="Đầu vùng chọn"><span className="inline-flex items-center shrink-0"><i data-lucide="step-back" className="w-3.5 h-3.5"></i></span></button>
|
||||
<button onClick={handlePlayPause}
|
||||
className={`w-7 h-7 flex items-center justify-center rounded border transition ${
|
||||
isPlaying
|
||||
@@ -5959,11 +5988,11 @@
|
||||
: '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.5 h-3.5 fill-current"></i>
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide={isPlaying ? "pause" : "play"} className="w-3.5 h-3.5 fill-current"></i></span>
|
||||
</button>
|
||||
<button onClick={handleStop}
|
||||
className="w-7 h-7 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.5 h-3.5 fill-current"></i></button>
|
||||
title="Stop"><span className="inline-flex items-center shrink-0"><i data-lucide="square" className="w-3.5 h-3.5 fill-current"></i></span></button>
|
||||
<button onClick={() => {
|
||||
if (activeTab !== 'main') {
|
||||
setSubTabs(prev => prev.map(s => {
|
||||
@@ -5976,7 +6005,7 @@
|
||||
}
|
||||
}}
|
||||
className="w-7 h-7 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-3.5 h-3.5"></i></button>
|
||||
title="Cuối vùng chọn"><span className="inline-flex items-center shrink-0"><i data-lucide="step-forward" className="w-3.5 h-3.5"></i></span></button>
|
||||
<button onClick={() => {
|
||||
if (activeTab !== 'main') {
|
||||
setSubTabs(prev => prev.map(s => {
|
||||
@@ -5989,7 +6018,7 @@
|
||||
}
|
||||
}}
|
||||
className="w-7 h-7 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.5 h-3.5"></i></button>
|
||||
title="Đến cuối"><span className="inline-flex items-center shrink-0"><i data-lucide="skip-forward" className="w-3.5 h-3.5"></i></span></button>
|
||||
<div className="w-[1px] h-5 bg-zinc-800 mx-0.5"></div>
|
||||
<button onClick={() => setIsLoopingSelection(prev => !prev)}
|
||||
className={`w-7 h-7 flex items-center justify-center rounded border transition ${
|
||||
@@ -5997,7 +6026,7 @@
|
||||
? '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.5 h-3.5"></i>
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="repeat" className="w-3.5 h-3.5"></i></span>
|
||||
</button>
|
||||
<span className="text-[14px] text-zinc-500 font-bold uppercase ml-2">Snap</span>
|
||||
<select value={snapValue} onChange={e => setSnapValue(e.target.value)}
|
||||
@@ -6087,10 +6116,10 @@
|
||||
<div className="flex flex-col h-full gap-1.5">
|
||||
<div className="flex items-center justify-between shrink-0 cursor-grab active:cursor-grabbing select-none" onMouseDown={e => startPanelDrag('export', e)}>
|
||||
<h3 className="font-bold text-[10px] text-zinc-200 flex items-center gap-1">
|
||||
<i data-lucide="grip-vertical" className="w-3 h-3 text-zinc-500"></i>
|
||||
<i data-lucide="save" className="w-3.5 h-3.5 text-cyan-400"></i> Export
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="grip-vertical" className="w-3 h-3 text-zinc-500"></i></span>
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="save" className="w-3.5 h-3.5 text-cyan-400"></i></span> Export
|
||||
</h3>
|
||||
<button onClick={() => closePanel('export')} className="text-zinc-600 hover:text-zinc-300"><i data-lucide="x" className="w-3 h-3"></i></button>
|
||||
<button onClick={() => closePanel('export')} className="text-zinc-600 hover:text-zinc-300"><span className="inline-flex items-center shrink-0"><i data-lucide="x" className="w-3 h-3"></i></span></button>
|
||||
</div>
|
||||
<div className="grid grid-cols-3 gap-1">
|
||||
<div>
|
||||
@@ -6117,7 +6146,7 @@
|
||||
</div>
|
||||
<button onClick={triggerWavExport} disabled={isExporting}
|
||||
className="w-full py-1 bg-cyan-700 hover:bg-cyan-600 text-zinc-100 font-bold rounded text-[10px] flex items-center justify-center gap-1">
|
||||
<i data-lucide="download-cloud" className="w-3 h-3"></i>
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="download-cloud" className="w-3 h-3"></i></span>
|
||||
{isExporting ? '...' : 'Export'}
|
||||
</button>
|
||||
</div>
|
||||
@@ -6126,10 +6155,10 @@
|
||||
<div className="flex flex-col h-full gap-1.5">
|
||||
<div className="flex items-center justify-between shrink-0 cursor-grab active:cursor-grabbing select-none" onMouseDown={e => startPanelDrag('ai', e)}>
|
||||
<h3 className="font-bold text-[10px] text-zinc-200 flex items-center gap-1">
|
||||
<i data-lucide="grip-vertical" className="w-3 h-3 text-zinc-500"></i>
|
||||
<i data-lucide="cpu" className="w-3.5 h-3.5 text-purple-400"></i> AI
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="grip-vertical" className="w-3 h-3 text-zinc-500"></i></span>
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="cpu" className="w-3.5 h-3.5 text-purple-400"></i></span> AI
|
||||
</h3>
|
||||
<button onClick={() => closePanel('ai')} className="text-zinc-600 hover:text-zinc-300"><i data-lucide="x" className="w-3 h-3"></i></button>
|
||||
<button onClick={() => closePanel('ai')} className="text-zinc-600 hover:text-zinc-300"><span className="inline-flex items-center shrink-0"><i data-lucide="x" className="w-3 h-3"></i></span></button>
|
||||
</div>
|
||||
<div className="p-1.5 bg-[#141414] rounded border border-zinc-800 text-[9px] font-mono min-h-[24px]">
|
||||
<div className="text-zinc-500">// <span className="text-zinc-300">{analysisState.status}</span></div>
|
||||
@@ -6137,13 +6166,13 @@
|
||||
</div>
|
||||
<div className="grid grid-cols-3 gap-1">
|
||||
<button onClick={handleMarkSelection} className="py-1 bg-purple-900 hover:bg-purple-800 text-purple-100 font-bold rounded text-[9px] border border-purple-700 flex items-center justify-center gap-1">
|
||||
<i data-lucide="map-pin" className="w-3 h-3"></i> Mark
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="map-pin" className="w-3 h-3"></i></span> Mark
|
||||
</button>
|
||||
<button onClick={handleAICutToNewTrack} disabled={analysisState.isRunning} className="py-1 bg-purple-700 hover:bg-purple-600 text-white font-bold rounded text-[9px] flex items-center justify-center gap-1">
|
||||
<i data-lucide="scissors" className="w-3 h-3"></i> AI Cut
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="scissors" className="w-3 h-3"></i></span> AI Cut
|
||||
</button>
|
||||
<button onClick={triggerAIAnalysis} disabled={analysisState.isRunning} className="py-1 bg-zinc-700 hover:bg-zinc-600 text-zinc-200 font-bold rounded text-[9px] border border-zinc-600 flex items-center justify-center gap-1">
|
||||
<i data-lucide="sparkles" className="w-3 h-3"></i> Analyze
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="sparkles" className="w-3 h-3"></i></span> Analyze
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -6152,9 +6181,9 @@
|
||||
<div className="flex flex-col h-full gap-1.5">
|
||||
<div className="flex items-center justify-between shrink-0 cursor-grab active:cursor-grabbing select-none" onMouseDown={e => startPanelDrag('selection', e)}>
|
||||
<span className="text-[10px] text-zinc-500 font-bold uppercase flex items-center gap-1">
|
||||
<i data-lucide="grip-vertical" className="w-3 h-3 text-zinc-500"></i> Selection
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="grip-vertical" className="w-3 h-3 text-zinc-500"></i></span> Selection
|
||||
</span>
|
||||
<button onClick={() => closePanel('selection')} className="text-zinc-600 hover:text-zinc-300"><i data-lucide="x" className="w-3 h-3"></i></button>
|
||||
<button onClick={() => closePanel('selection')} className="text-zinc-600 hover:text-zinc-300"><span className="inline-flex items-center shrink-0"><i data-lucide="x" className="w-3 h-3"></i></span></button>
|
||||
</div>
|
||||
<div className="grid grid-cols-3 gap-1 bg-[#141414] p-1.5 rounded border border-zinc-800">
|
||||
<div>
|
||||
@@ -6222,25 +6251,9 @@
|
||||
{renderPanelContent(p)}
|
||||
</div>
|
||||
))}
|
||||
{/* ── Auth & User Modals ── */}
|
||||
<AuthModal
|
||||
isOpen={authModalOpen}
|
||||
mode={authMode}
|
||||
forceMandatory={isMandatoryLogin}
|
||||
onClose={() => setAuthModalOpen(false)}
|
||||
onSuccess={handleAuthSuccess}
|
||||
/>
|
||||
<ProfileModal
|
||||
isOpen={profileModalOpen}
|
||||
onClose={() => setProfileModalOpen(false)}
|
||||
/>
|
||||
<SystemManagerModal
|
||||
isOpen={systemManagerModalOpen}
|
||||
onClose={() => setSystemManagerModalOpen(false)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div ref={workspaceRef} className="flex-1 flex flex-col overflow-hidden select-none daw-bg relative">
|
||||
@@ -6257,7 +6270,7 @@
|
||||
<div className="fixed z-[100] pointer-events-none opacity-80 border border-cyan-500 rounded-lg bg-[#262626] shadow-2xl p-3 w-56"
|
||||
style={{ left: dragGhostPos.x, top: dragGhostPos.y }}>
|
||||
<div className="flex items-center gap-2 text-[10px] text-zinc-200 font-bold">
|
||||
<i data-lucide="move" className="w-3.5 h-3.5 text-cyan-400"></i>
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="move" className="w-3.5 h-3.5 text-cyan-400"></i></span>
|
||||
{dragGhostPanel === 'export' ? 'Export Panel' : dragGhostPanel === 'ai' ? 'AI Panel' : 'Selection Panel'}
|
||||
</div>
|
||||
<div className="text-[8px] text-zinc-500 mt-1">Drop at edge to dock</div>
|
||||
@@ -6275,7 +6288,14 @@
|
||||
onScroll={handleTCPScroll}
|
||||
className="w-[300px] shrink-0 z-30 bg-[#262626] overflow-y-scroll no-scrollbar flex flex-col border-r border-zinc-900"
|
||||
style={{ scrollbarWidth: 'none', msOverflowStyle: 'none' }}>
|
||||
<div className="sticky top-0 z-50 flex items-center h-10 border-b border-zinc-900 bg-[#242424] px-2 shrink-0">
|
||||
<div className="sticky top-0 z-50 flex items-center justify-between h-10 border-b border-zinc-900 bg-[#242424] px-2 shrink-0">
|
||||
<span className="text-xs font-bold text-zinc-300 flex items-center gap-1.5">
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="sliders" className="w-3.5 h-3.5 text-cyan-400"></i></span>
|
||||
TRACKS ({tracks.length})
|
||||
</span>
|
||||
<button onClick={addNewTrack} className="px-2.5 py-1 bg-cyan-600 hover:bg-cyan-500 text-white rounded text-[11px] font-semibold flex items-center gap-1 shadow transition">
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="plus" className="w-3 h-3"></i></span> Add Track
|
||||
</button>
|
||||
</div>
|
||||
<div className="sticky top-10 z-40 flex h-[40px] border-b border-purple-500/30 bg-[#1a1a2e] p-2 border-l-4 border-purple-500 shrink-0">
|
||||
<div className="flex items-center justify-between w-full">
|
||||
@@ -6292,7 +6312,16 @@
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1 flex flex-col divide-y divide-[#141414] bg-[#262626]">
|
||||
{tracks.map((track, idx) => {
|
||||
{tracks.length === 0 ? (
|
||||
<div className="p-6 text-center text-zinc-400 flex flex-col items-center justify-center space-y-3">
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="plus-circle" className="w-8 h-8 text-cyan-400 opacity-80"></i></span>
|
||||
<p className="text-xs font-medium">Chưa có Track nào trong dự án.</p>
|
||||
<button onClick={addNewTrack} className="px-3 py-1.5 bg-cyan-600 hover:bg-cyan-500 text-white rounded text-xs font-semibold flex items-center gap-1.5 shadow">
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="plus" className="w-3.5 h-3.5"></i></span> Thêm Track Mới
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
tracks.map((track, idx) => {
|
||||
const isSelected = selectedTrackId === track.id;
|
||||
return (
|
||||
<div key={track.id} style={{ height: `${track.height || 96}px` }}
|
||||
@@ -6326,7 +6355,7 @@
|
||||
<button onClick={e => { e.stopPropagation(); toggleTrackSoloEvaluate(track.id); }}
|
||||
className={`px-1.5 py-0.5 text-[10px] rounded font-mono font-bold border transition ${soloedTrackId === track.id || track.solo ? 'bg-amber-950 text-amber-400 border-amber-600' : 'bg-zinc-800 text-zinc-400 border-transparent hover:text-zinc-200'}`}>S</button>
|
||||
<button onClick={e => { e.stopPropagation(); deleteTrack(track.id); }}
|
||||
className="p-0.5 rounded text-red-500 hover:bg-red-950/40 hover:text-red-400 transition"><i data-lucide="trash-2" className="w-3.5 h-3.5"></i></button>
|
||||
className="p-0.5 rounded text-red-500 hover:bg-red-950/40 hover:text-red-400 transition"><span className="inline-flex items-center shrink-0"><i data-lucide="trash-2" className="w-3.5 h-3.5"></i></span></button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-0.5 text-[10px]" onClick={e => e.stopPropagation()}>
|
||||
@@ -6343,15 +6372,16 @@
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5 mt-1" onClick={e => e.stopPropagation()}>
|
||||
<input type="file" id={`upload-${track.id}`} accept="audio/*" className="hidden" onChange={e => loadFileOnTrack(track.id, e.target.files[0])} />
|
||||
<label htmlFor={`upload-${track.id}`} className="px-2.5 py-1 bg-zinc-800 hover:bg-zinc-700 text-zinc-300 rounded text-[10px] flex items-center gap-1 cursor-pointer border border-zinc-700 font-medium"><i data-lucide="upload" className="w-3 h-3"></i> File</label>
|
||||
<button onClick={e => { e.stopPropagation(); showToast('FX panel for track ' + track.id, 'info'); }} className="px-2.5 py-1 bg-zinc-800 hover:bg-zinc-700 text-purple-400 border border-purple-900 rounded text-[10px] font-bold flex items-center gap-1"><i data-lucide="wand-2" className="w-3 h-3"></i> FX: <span className="text-zinc-500 font-normal">None</span></button>
|
||||
<button onClick={() => generateSynthToTrack(track.id, 'synth')} className="px-2.5 py-1 bg-zinc-800 hover:bg-zinc-700 text-amber-400 border border-amber-800 rounded text-[10px] font-bold flex items-center gap-1"><i data-lucide="music" className="w-3 h-3"></i> Synth: <span className="text-zinc-500 font-normal">None</span></button>
|
||||
<label htmlFor={`upload-${track.id}`} className="px-2.5 py-1 bg-zinc-800 hover:bg-zinc-700 text-zinc-300 rounded text-[10px] flex items-center gap-1 cursor-pointer border border-zinc-700 font-medium"><span className="inline-flex items-center shrink-0"><i data-lucide="upload" className="w-3 h-3"></i></span> File</label>
|
||||
<button onClick={e => { e.stopPropagation(); showToast('FX panel for track ' + track.id, 'info'); }} className="px-2.5 py-1 bg-zinc-800 hover:bg-zinc-700 text-purple-400 border border-purple-900 rounded text-[10px] font-bold flex items-center gap-1"><span className="inline-flex items-center shrink-0"><i data-lucide="wand-2" className="w-3 h-3"></i></span> FX: <span className="text-zinc-500 font-normal">None</span></button>
|
||||
<button onClick={() => generateSynthToTrack(track.id, 'synth')} className="px-2.5 py-1 bg-zinc-800 hover:bg-zinc-700 text-amber-400 border border-amber-800 rounded text-[10px] font-bold flex items-center gap-1"><span className="inline-flex items-center shrink-0"><i data-lucide="music" className="w-3 h-3"></i></span> Synth: <span className="text-zinc-500 font-normal">None</span></button>
|
||||
</div>
|
||||
<div onMouseDown={e => handleTrackResizeMouseDown(e, track.id)}
|
||||
className="absolute bottom-0 left-0 right-0 h-1 cursor-ns-resize z-30 hover:bg-cyan-500/50 transition-colors" onClick={e => e.stopPropagation()} />
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
<div className="h-[48px] border-r border-zinc-900 bg-[#1e1e1e]/50 shrink-0" />
|
||||
</div>
|
||||
@@ -6402,7 +6432,7 @@
|
||||
{track.buffer && (
|
||||
<div className="absolute right-2 top-2 flex items-center gap-1 z-10 opacity-70 hover:opacity-100">
|
||||
<button onClick={() => handleSplitTrack(track.id)}
|
||||
className="px-1.5 py-0.5 bg-zinc-900/95 text-zinc-300 rounded text-[9px] flex items-center gap-1 border border-zinc-700/50"><i data-lucide="scissors" className="w-2.5 h-2.5 text-cyan-400"></i> Cắt</button>
|
||||
className="px-1.5 py-0.5 bg-zinc-900/95 text-zinc-300 rounded text-[9px] flex items-center gap-1 border border-zinc-700/50"><span className="inline-flex items-center shrink-0"><i data-lucide="scissors" className="w-2.5 h-2.5 text-cyan-400"></i></span> Cắt</button>
|
||||
</div>
|
||||
)}
|
||||
<div onMouseDown={e => handleTrackResizeMouseDown(e, track.id)}
|
||||
@@ -6413,7 +6443,7 @@
|
||||
<div className="h-[48px] flex items-center justify-center text-xs text-zinc-500 hover:bg-zinc-900/20 cursor-pointer select-none border-t border-dashed border-zinc-800"
|
||||
onMouseEnter={() => { if (draggedClipRef.current) { setHoveredTrackId(addNewTrack()); } }}
|
||||
onClick={addNewTrack}>
|
||||
<span className="flex items-center gap-1 text-zinc-400"><i data-lucide="plus" className="w-3.5 h-3.5"></i> Kéo clip xuống hoặc Click tạo Track</span>
|
||||
<span className="flex items-center gap-1 text-zinc-400"><span className="inline-flex items-center shrink-0"><i data-lucide="plus" className="w-3.5 h-3.5"></i></span> Kéo clip xuống hoặc Click tạo Track</span>
|
||||
</div>
|
||||
{selectionMode !== 'local' && selLeft !== null && selRight !== null && selRight > selLeft && (
|
||||
<div className="absolute top-0 bottom-0 border-l border-r border-amber-500 bg-amber-500/10 z-20 selection-interactive-box cursor-grab active:cursor-grabbing"
|
||||
@@ -6447,7 +6477,7 @@
|
||||
<span className="text-[10px] font-bold text-zinc-500 uppercase">Sub-Tab</span>
|
||||
<button onClick={() => closeSubTab(st.id)}
|
||||
className="px-1.5 py-0.5 text-[9px] bg-zinc-800 hover:bg-zinc-700 text-zinc-400 rounded border border-zinc-700 flex items-center gap-1">
|
||||
<i data-lucide="x" className="w-3 h-3"></i> Close
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="x" className="w-3 h-3"></i></span> Close
|
||||
</button>
|
||||
</div>
|
||||
{vTrack ? (
|
||||
@@ -6493,11 +6523,11 @@
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5 text-[14px]">
|
||||
<button onClick={() => setSubTabs(prev => prev.map(s => s.id === st.id ? {...s, isLooping: !s.isLooping} : s))}
|
||||
className={`px-2 py-0.5 text-[14px] rounded border font-bold ${st.isLooping ? 'bg-amber-700 text-white border-amber-500' : 'bg-zinc-800 text-zinc-400 border-zinc-700'}`}><i data-lucide="repeat" className="w-3.5 h-3.5"></i></button>
|
||||
className={`px-2 py-0.5 text-[14px] rounded border font-bold ${st.isLooping ? 'bg-amber-700 text-white border-amber-500' : 'bg-zinc-800 text-zinc-400 border-zinc-700'}`}><span className="inline-flex items-center shrink-0"><i data-lucide="repeat" className="w-3.5 h-3.5"></i></span></button>
|
||||
<button onClick={() => updateSubTabEffects(st.id, { reverse: !((st.effects || {}).reverse) })}
|
||||
className={`px-2 py-0.5 rounded border text-[14px] font-bold ${(st.effects || {}).reverse ? 'bg-zinc-600 text-white border-zinc-500' : 'bg-zinc-800 text-zinc-400 border-zinc-700'} flex items-center justify-center`}
|
||||
title="Reverse">
|
||||
<i data-lucide="arrow-left-right" className="w-3.5 h-3.5"></i>
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="arrow-left-right" className="w-3.5 h-3.5"></i></span>
|
||||
</button>
|
||||
<span className="w-10 text-right text-zinc-500 text-[14px]">Loop:</span>
|
||||
<input type="number" min="0" max="999" value={st.loopCount || 0} onChange={e => setSubTabs(prev => prev.map(s => s.id === st.id ? {...s, loopCount: Math.max(0, parseInt(e.target.value) || 0)} : s))} className="w-12 bg-black text-amber-300 text-[14px] px-1 py-0.5 rounded border border-zinc-700 text-center font-mono" title="Loop count" />
|
||||
@@ -6538,11 +6568,11 @@
|
||||
</div>
|
||||
<button onClick={() => exportSubTabBuffer(st.id)}
|
||||
className="w-full py-1.5 mb-1.5 bg-blue-700 hover:bg-blue-600 text-white font-bold rounded text-[14px] flex items-center justify-center gap-1 transition">
|
||||
<i data-lucide="download" className="w-4 h-4"></i> Export
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="download" className="w-4 h-4"></i></span> Export
|
||||
</button>
|
||||
<button onClick={() => applySubTab(st.id)}
|
||||
className="w-full py-1.5 bg-emerald-700 hover:bg-emerald-600 text-white font-bold rounded text-[14px] flex items-center justify-center gap-1 transition">
|
||||
<i data-lucide="save" className="w-4 h-4"></i> Save
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="save" className="w-4 h-4"></i></span> Save
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -6672,28 +6702,28 @@
|
||||
<button onClick={() => setShowExportPanel(p => !p)}
|
||||
className={`px-1.5 py-0.5 rounded text-[9px] font-semibold transition flex items-center gap-1 ${showExportPanel ? 'bg-cyan-900 text-cyan-300' : 'text-zinc-500 hover:text-zinc-300'}`}
|
||||
title={`Export Panel (${panelPositions.export})`}>
|
||||
<i data-lucide="save" className="w-3 h-3"></i>
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="save" className="w-3 h-3"></i></span>
|
||||
<span className="text-[7px] opacity-60">{showExportPanel ? panelPositions.export[0].toUpperCase() : ''}</span>
|
||||
</button>
|
||||
<button onClick={() => setShowSelectionPanel(p => !p)}
|
||||
className={`px-1.5 py-0.5 rounded text-[9px] font-semibold transition flex items-center gap-1 ${showSelectionPanel ? 'bg-amber-900 text-amber-300' : 'text-zinc-500 hover:text-zinc-300'}`}
|
||||
title={`Selection Panel (${panelPositions.selection})`}>
|
||||
<i data-lucide="sliders" className="w-3 h-3"></i>
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="sliders" className="w-3 h-3"></i></span>
|
||||
<span className="text-[7px] opacity-60">{showSelectionPanel ? panelPositions.selection[0].toUpperCase() : ''}</span>
|
||||
</button>
|
||||
<button onClick={() => setShowAIPanel(p => !p)}
|
||||
className={`px-1.5 py-0.5 rounded text-[9px] font-semibold transition flex items-center gap-1 ${showAIPanel ? 'bg-purple-900 text-purple-300' : 'text-zinc-500 hover:text-zinc-300'}`}
|
||||
title={`AI Panel (${panelPositions.ai})`}>
|
||||
<i data-lucide="cpu" className="w-3 h-3"></i>
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="cpu" className="w-3 h-3"></i></span>
|
||||
<span className="text-[7px] opacity-60">{showAIPanel ? panelPositions.ai[0].toUpperCase() : ''}</span>
|
||||
</button>
|
||||
<span className="w-[1px] h-3 bg-zinc-800 mx-1"></span>
|
||||
<span className="flex items-center gap-1">
|
||||
<i data-lucide="info" className="w-3 h-3 text-zinc-600"></i> Scroll: Zoom
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="info" className="w-3 h-3 text-zinc-600"></i></span> Scroll: Zoom
|
||||
</span>
|
||||
<span>|</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<i data-lucide="keyboard" className="w-3 h-3 text-zinc-600"></i> Ctrl+Scroll: Playhead
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="keyboard" className="w-3 h-3 text-zinc-600"></i></span> Ctrl+Scroll: Playhead
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -6708,28 +6738,28 @@
|
||||
</div>
|
||||
<div className="h-px bg-zinc-700 my-1"></div>
|
||||
<button onClick={() => { handleSubTabCut(contextMenu.subTabId); closeContextMenu(); }} className="w-full px-3 py-1.5 text-xs text-zinc-200 hover:bg-zinc-700 text-left flex items-center gap-2">
|
||||
<i data-lucide="scissors" className="w-3.5 h-3.5 text-rose-400 shrink-0"></i>
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="scissors" className="w-3.5 h-3.5 text-rose-400 shrink-0"></i></span>
|
||||
<span className="flex-1">Cut</span>
|
||||
<span className="text-purple-400 text-[12px] font-semibold font-mono ml-auto">Ctrl+X</span>
|
||||
</button>
|
||||
<button onClick={() => { handleSubTabCopy(contextMenu.subTabId); closeContextMenu(); }} className="w-full px-3 py-1.5 text-xs text-zinc-200 hover:bg-zinc-700 text-left flex items-center gap-2">
|
||||
<i data-lucide="copy" className="w-3.5 h-3.5 text-zinc-400 shrink-0"></i>
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="copy" className="w-3.5 h-3.5 text-zinc-400 shrink-0"></i></span>
|
||||
<span className="flex-1">Copy</span>
|
||||
<span className="text-purple-400 text-[12px] font-semibold font-mono ml-auto">Ctrl+C</span>
|
||||
</button>
|
||||
<button onClick={() => { handleSubTabPaste(contextMenu.subTabId); closeContextMenu(); }} className="w-full px-3 py-1.5 text-xs text-zinc-200 hover:bg-zinc-700 text-left flex items-center gap-2">
|
||||
<i data-lucide="clipboard" className="w-3.5 h-3.5 text-emerald-400 shrink-0"></i>
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="clipboard" className="w-3.5 h-3.5 text-emerald-400 shrink-0"></i></span>
|
||||
<span className="flex-1">Paste</span>
|
||||
<span className="text-purple-400 text-[12px] font-semibold font-mono ml-auto">Ctrl+V</span>
|
||||
</button>
|
||||
<button onClick={() => { handleSubTabDelete(contextMenu.subTabId); closeContextMenu(); }} className="w-full px-3 py-1.5 text-xs text-zinc-200 hover:bg-zinc-700 text-left flex items-center gap-2">
|
||||
<i data-lucide="trash-2" className="w-3.5 h-3.5 text-red-400 shrink-0"></i>
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="trash-2" className="w-3.5 h-3.5 text-red-400 shrink-0"></i></span>
|
||||
<span className="flex-1">Delete Selected Segment</span>
|
||||
<span className="text-amber-400 text-[12px] font-semibold font-mono ml-auto">Del</span>
|
||||
</button>
|
||||
<div className="h-px bg-zinc-700 my-1"></div>
|
||||
<button onClick={() => { handleSubTabLoop(contextMenu.subTabId, 4); closeContextMenu(); }} className="w-full px-3 py-1.5 text-xs text-zinc-200 hover:bg-zinc-700 text-left flex items-center gap-2">
|
||||
<i data-lucide="repeat" className="w-3.5 h-3.5 text-cyan-400 shrink-0"></i>
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="repeat" className="w-3.5 h-3.5 text-cyan-400 shrink-0"></i></span>
|
||||
<span className="flex-1">Loop Selection 4 times</span>
|
||||
<span className="text-purple-400 text-[12px] font-semibold font-mono ml-auto">Ctrl+L</span>
|
||||
</button>
|
||||
@@ -6738,39 +6768,39 @@
|
||||
<div className="fixed z-[60] bg-[#2a2a2a] border border-zinc-700 rounded-lg shadow-2xl py-1 w-64" style={{ left: contextMenu.x, top: contextMenu.y }}
|
||||
onClick={(e) => e.stopPropagation()}>
|
||||
<button onClick={contextMenuEdit} className="w-full px-3 py-1.5 text-xs text-zinc-200 hover:bg-zinc-700 text-left flex items-center gap-2">
|
||||
<i data-lucide="file-edit" className="w-3.5 h-3.5 text-amber-400 shrink-0"></i>
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="file-edit" className="w-3.5 h-3.5 text-amber-400 shrink-0"></i></span>
|
||||
<span className="flex-1">Edit</span>
|
||||
<span className="text-purple-400 text-[12px] font-semibold font-mono ml-auto pl-8">Ctrl+E</span>
|
||||
</button>
|
||||
<button onClick={contextMenuSplit} className="w-full px-3 py-1.5 text-xs text-zinc-200 hover:bg-zinc-700 text-left flex items-center gap-2">
|
||||
<i data-lucide="scissors" className="w-3.5 h-3.5 text-cyan-400 shrink-0"></i>
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="scissors" className="w-3.5 h-3.5 text-cyan-400 shrink-0"></i></span>
|
||||
<span className="flex-1">Split</span>
|
||||
<span className="text-purple-400 text-[12px] font-semibold font-mono ml-auto pl-8">S</span>
|
||||
</button>
|
||||
<button onClick={contextMenuMerge} className="w-full px-3 py-1.5 text-xs text-zinc-200 hover:bg-zinc-700 text-left flex items-center gap-2">
|
||||
<i data-lucide="combine" className="w-3.5 h-3.5 text-purple-400 shrink-0"></i>
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="combine" className="w-3.5 h-3.5 text-purple-400 shrink-0"></i></span>
|
||||
<span className="flex-1">Merge</span>
|
||||
<span className="text-purple-400 text-[12px] font-semibold font-mono ml-auto pl-8">Ctrl+M</span>
|
||||
</button>
|
||||
<div className="h-px bg-zinc-700 my-1"></div>
|
||||
<button onClick={contextMenuCopy} className="w-full px-3 py-1.5 text-xs text-zinc-200 hover:bg-zinc-700 text-left flex items-center gap-2">
|
||||
<i data-lucide="copy" className="w-3.5 h-3.5 text-zinc-400 shrink-0"></i>
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="copy" className="w-3.5 h-3.5 text-zinc-400 shrink-0"></i></span>
|
||||
<span className="flex-1">Copy</span>
|
||||
<span className="text-purple-400 text-[12px] font-semibold font-mono ml-auto pl-8">Ctrl+C</span>
|
||||
</button>
|
||||
<button onClick={contextMenuCut} className="w-full px-3 py-1.5 text-xs text-zinc-200 hover:bg-zinc-700 text-left flex items-center gap-2">
|
||||
<i data-lucide="scissors" className="w-3.5 h-3.5 text-rose-400 shrink-0"></i>
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="scissors" className="w-3.5 h-3.5 text-rose-400 shrink-0"></i></span>
|
||||
<span className="flex-1">Cut</span>
|
||||
<span className="text-purple-400 text-[12px] font-semibold font-mono ml-auto pl-8">Ctrl+X</span>
|
||||
</button>
|
||||
<button onClick={contextMenuPaste} className="w-full px-3 py-1.5 text-xs text-zinc-200 hover:bg-zinc-700 text-left flex items-center gap-2">
|
||||
<i data-lucide="clipboard" className="w-3.5 h-3.5 text-emerald-400 shrink-0"></i>
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="clipboard" className="w-3.5 h-3.5 text-emerald-400 shrink-0"></i></span>
|
||||
<span className="flex-1">Paste</span>
|
||||
<span className="text-purple-400 text-[12px] font-semibold font-mono ml-auto pl-8">Ctrl+V</span>
|
||||
</button>
|
||||
<div className="h-px bg-zinc-700 my-1"></div>
|
||||
<button onClick={contextMenuDelete} className="w-full px-3 py-1.5 text-xs text-red-300 hover:bg-red-950 text-left flex items-center gap-2">
|
||||
<i data-lucide="trash-2" className="w-3.5 h-3.5 text-red-400 shrink-0"></i>
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="trash-2" className="w-3.5 h-3.5 text-red-400 shrink-0"></i></span>
|
||||
<span className="flex-1">Delete</span>
|
||||
<span className="text-amber-400 text-[12px] font-semibold font-mono ml-auto pl-8">Del</span>
|
||||
</button>
|
||||
@@ -6781,11 +6811,11 @@
|
||||
{/* ── Toast ── */}
|
||||
{toastMessage && (
|
||||
<div className="absolute top-14 right-4 z-50 px-4 py-2.5 rounded shadow-lg text-xs font-semibold flex items-center gap-2 border bg-zinc-900 text-zinc-100 border-zinc-800">
|
||||
<i data-lucide="bell" className={`w-4 h-4 ${
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="bell" className={`w-4 h-4 ${
|
||||
toastMessage.type === 'success' ? 'text-emerald-400' :
|
||||
toastMessage.type === 'error' ? 'text-rose-400' :
|
||||
toastMessage.type === 'warning' ? 'text-amber-400' : 'text-cyan-400'
|
||||
}`}></i>
|
||||
}`}></i></span>
|
||||
{toastMessage.text}
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user