feat: bổ sung logic của phần section: thêm các items, xóa track
This commit is contained in:
+74
-23
@@ -5176,15 +5176,27 @@ const App = () => {
|
|||||||
};
|
};
|
||||||
const contextMenuDelete = () => {
|
const contextMenuDelete = () => {
|
||||||
const tid = contextMenu.trackId;
|
const tid = contextMenu.trackId;
|
||||||
|
const sessionTab = sessionTabs.find(s => s.id === activeTab);
|
||||||
|
if (sessionTab) {
|
||||||
|
updateActiveTracks(prev => prev.filter(t => t.id !== tid));
|
||||||
|
if (selectedTrackId === tid) setSelectedTrackId('1');
|
||||||
|
closeContextMenu();
|
||||||
|
showToast('Đã xoá track.', 'info');
|
||||||
|
} else {
|
||||||
|
const track = tracks.find(t => t.id === tid);
|
||||||
|
if (track && track.sections && track.sections.length > 0) {
|
||||||
|
closeContextMenu();
|
||||||
|
showToast('Không thể xoá track chứa Section item.', 'warning');
|
||||||
|
return;
|
||||||
|
}
|
||||||
const beforeSnap = captureTrackSnapshot(tid);
|
const beforeSnap = captureTrackSnapshot(tid);
|
||||||
setTracks(prev => prev.filter(t => t.id !== tid));
|
setTracks(prev => prev.filter(t => t.id !== tid));
|
||||||
const afterSnap = captureTrackSnapshot(tid);
|
const afterSnap = captureTrackSnapshot(tid);
|
||||||
pushAction('DELETE', tid, beforeSnap, afterSnap);
|
pushAction('DELETE', tid, beforeSnap, afterSnap);
|
||||||
if (selectedTrackId === tid) {
|
if (selectedTrackId === tid) setSelectedTrackId(tracks.filter(t => t.id !== tid)[0]?.id || '1');
|
||||||
setSelectedTrackId(tracks.filter(t => t.id !== tid)[0]?.id || '1');
|
|
||||||
}
|
|
||||||
closeContextMenu();
|
closeContextMenu();
|
||||||
showToast('Đã xoá track.', 'info');
|
showToast('Đã xoá track.', 'info');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
const contextMenuCopy = () => {
|
const contextMenuCopy = () => {
|
||||||
const track = tracks.find(t => t.id === contextMenu.trackId);
|
const track = tracks.find(t => t.id === contextMenu.trackId);
|
||||||
@@ -5558,8 +5570,20 @@ const App = () => {
|
|||||||
};
|
};
|
||||||
const handleDeleteTrack = () => {
|
const handleDeleteTrack = () => {
|
||||||
const tid = selectedTrackId;
|
const tid = selectedTrackId;
|
||||||
|
const sessionTab = sessionTabs.find(s => s.id === activeTab);
|
||||||
|
if (sessionTab) {
|
||||||
|
updateActiveTracks(prev => prev.filter(t => t.id !== tid));
|
||||||
|
setSelectedTrackId(activeTracks.filter(t => t.id !== tid)[0]?.id || '1');
|
||||||
|
} else {
|
||||||
|
const curTracks = tracks;
|
||||||
|
const track = curTracks.find(t => t.id === tid);
|
||||||
|
if (track && track.sections && track.sections.length > 0) {
|
||||||
|
showToast('Không thể xoá track chứa Section item.', 'warning');
|
||||||
|
return;
|
||||||
|
}
|
||||||
setTracks(p => p.filter(t => t.id !== tid));
|
setTracks(p => p.filter(t => t.id !== tid));
|
||||||
setSelectedTrackId(tracks.filter(t => t.id !== tid)[0]?.id || '1');
|
setSelectedTrackId(curTracks.filter(t => t.id !== tid)[0]?.id || '1');
|
||||||
|
}
|
||||||
showToast('Deleted track.', 'info');
|
showToast('Deleted track.', 'info');
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -6415,14 +6439,26 @@ const App = () => {
|
|||||||
document.addEventListener('mouseup', handleMouseUp);
|
document.addEventListener('mouseup', handleMouseUp);
|
||||||
};
|
};
|
||||||
const deleteTrack = trackId => {
|
const deleteTrack = trackId => {
|
||||||
|
const sessionTab = sessionTabs.find(s => s.id === activeTab);
|
||||||
|
if (sessionTab) {
|
||||||
|
updateActiveTracks(prev => {
|
||||||
|
const filtered = prev.filter(t => t.id !== trackId);
|
||||||
|
if (filtered.length > 0) setSelectedTrackId(filtered[0].id);
|
||||||
|
return filtered;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const track = tracks.find(t => t.id === trackId);
|
||||||
|
if (track && track.sections && track.sections.length > 0) {
|
||||||
|
showToast('Không thể xoá track chứa Section item.', 'warning');
|
||||||
|
return;
|
||||||
|
}
|
||||||
const beforeSnap = captureTrackSnapshot(trackId);
|
const beforeSnap = captureTrackSnapshot(trackId);
|
||||||
setTracks(prev => {
|
setTracks(prev => {
|
||||||
const filtered = prev.filter(t => t.id !== trackId);
|
const filtered = prev.filter(t => t.id !== trackId);
|
||||||
if (filtered.length > 0) {
|
if (filtered.length > 0) setSelectedTrackId(filtered[0].id);
|
||||||
setSelectedTrackId(filtered[0].id);
|
|
||||||
}
|
|
||||||
return filtered;
|
return filtered;
|
||||||
});
|
});
|
||||||
|
}
|
||||||
showToast('Đã xóa track.', 'info');
|
showToast('Đã xóa track.', 'info');
|
||||||
};
|
};
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -6564,7 +6600,8 @@ const App = () => {
|
|||||||
|
|
||||||
// ── Section / MIDI Item Resize Start ──
|
// ── Section / MIDI Item Resize Start ──
|
||||||
const handleSectionItemResizeStart = (trackId, itemType, itemId, side, clickTime) => {
|
const handleSectionItemResizeStart = (trackId, itemType, itemId, side, clickTime) => {
|
||||||
const track = tracks.find(t => t.id === trackId);
|
const curTracks = activeTracks;
|
||||||
|
const track = curTracks.find(t => t.id === trackId);
|
||||||
if (!track) return;
|
if (!track) return;
|
||||||
const items = itemType === 'section' ? track.sections : track.midiItems;
|
const items = itemType === 'section' ? track.sections : track.midiItems;
|
||||||
const item = (items || []).find(it => it.id === itemId);
|
const item = (items || []).find(it => it.id === itemId);
|
||||||
@@ -6586,7 +6623,7 @@ const App = () => {
|
|||||||
const time = mouseX / zoom;
|
const time = mouseX / zoom;
|
||||||
const newStart = Math.max(0, time - drag.clickOffset);
|
const newStart = Math.max(0, time - drag.clickOffset);
|
||||||
const targetTrackId = hoveredTrackIdRef.current || drag.trackId;
|
const targetTrackId = hoveredTrackIdRef.current || drag.trackId;
|
||||||
setTracks(prev => prev.map(t => {
|
updateActiveTracks(prev => prev.map(t => {
|
||||||
const items = drag.itemType === 'section' ? (t.sections || []) : (t.midiItems || []);
|
const items = drag.itemType === 'section' ? (t.sections || []) : (t.midiItems || []);
|
||||||
const updatedItems = items.filter(it => it.id !== drag.itemId);
|
const updatedItems = items.filter(it => it.id !== drag.itemId);
|
||||||
if (t.id === targetTrackId) {
|
if (t.id === targetTrackId) {
|
||||||
@@ -6623,7 +6660,7 @@ const App = () => {
|
|||||||
document.removeEventListener('mousemove', handleMouseMove);
|
document.removeEventListener('mousemove', handleMouseMove);
|
||||||
document.removeEventListener('mouseup', handleMouseUp);
|
document.removeEventListener('mouseup', handleMouseUp);
|
||||||
};
|
};
|
||||||
}, [zoom]);
|
}, [zoom, activeTab, sessionTabs]);
|
||||||
|
|
||||||
// ── Document-level mousemove/mouseup for Section/MIDI item resize ──
|
// ── Document-level mousemove/mouseup for Section/MIDI item resize ──
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -6636,7 +6673,7 @@ const App = () => {
|
|||||||
const scrollLeft = wrapper.scrollLeft;
|
const scrollLeft = wrapper.scrollLeft;
|
||||||
const mouseX = e.clientX - rect.left + scrollLeft;
|
const mouseX = e.clientX - rect.left + scrollLeft;
|
||||||
const time = mouseX / zoom;
|
const time = mouseX / zoom;
|
||||||
setTracks(prev => prev.map(t => {
|
updateActiveTracks(prev => prev.map(t => {
|
||||||
if (t.id !== resize.trackId) return t;
|
if (t.id !== resize.trackId) return t;
|
||||||
const items = resize.itemType === 'section' ? [...(t.sections || [])] : [...(t.midiItems || [])];
|
const items = resize.itemType === 'section' ? [...(t.sections || [])] : [...(t.midiItems || [])];
|
||||||
const idx = items.findIndex(it => it.id === resize.itemId);
|
const idx = items.findIndex(it => it.id === resize.itemId);
|
||||||
@@ -6670,7 +6707,7 @@ const App = () => {
|
|||||||
document.removeEventListener('mousemove', handleMouseMove);
|
document.removeEventListener('mousemove', handleMouseMove);
|
||||||
document.removeEventListener('mouseup', handleMouseUp);
|
document.removeEventListener('mouseup', handleMouseUp);
|
||||||
};
|
};
|
||||||
}, [zoom]);
|
}, [zoom, activeTab, sessionTabs]);
|
||||||
|
|
||||||
const handleSelectRange = (start, end, reset) => {
|
const handleSelectRange = (start, end, reset) => {
|
||||||
const maxLen = maxDuration;
|
const maxLen = maxDuration;
|
||||||
@@ -6981,10 +7018,11 @@ const App = () => {
|
|||||||
|
|
||||||
// ── Add Track ──
|
// ── Add Track ──
|
||||||
const addNewTrack = () => {
|
const addNewTrack = () => {
|
||||||
const newId = (tracks.length + 1).toString();
|
const curTracks = activeTracks;
|
||||||
|
const newId = (curTracks.length + 1).toString();
|
||||||
const colors = ['#0f766e', '#1d4ed8', '#701a75', '#a21caf', '#b45309'];
|
const colors = ['#0f766e', '#1d4ed8', '#701a75', '#a21caf', '#b45309'];
|
||||||
const selectColor = colors[tracks.length % colors.length];
|
const selectColor = colors[curTracks.length % colors.length];
|
||||||
setTracks(prev => [...prev, {
|
updateActiveTracks(prev => [...prev, {
|
||||||
id: newId,
|
id: newId,
|
||||||
name: `Track ${newId}`,
|
name: `Track ${newId}`,
|
||||||
buffer: null,
|
buffer: null,
|
||||||
@@ -7006,11 +7044,22 @@ const App = () => {
|
|||||||
return newId;
|
return newId;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ── Update tracks in active context (main session or section tab) ──
|
||||||
|
const updateActiveTracks = updater => {
|
||||||
|
const sessionTab = sessionTabs.find(s => s.id === activeTab);
|
||||||
|
if (sessionTab) {
|
||||||
|
setSessionTabs(prev => prev.map(st => st.id === activeTab ? { ...st, tracks: updater(st.tracks) } : st));
|
||||||
|
} else {
|
||||||
|
setTracks(updater);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// ── Insert Track Below Selected ──
|
// ── Insert Track Below Selected ──
|
||||||
const insertTrackBelow = () => {
|
const insertTrackBelow = () => {
|
||||||
|
const curTracks = activeTracks;
|
||||||
const newId = `t${Date.now()}`;
|
const newId = `t${Date.now()}`;
|
||||||
const colors = ['#0f766e', '#1d4ed8', '#701a75', '#a21caf', '#b45309'];
|
const colors = ['#0f766e', '#1d4ed8', '#701a75', '#a21caf', '#b45309'];
|
||||||
const selectColor = colors[tracks.length % colors.length];
|
const selectColor = colors[(curTracks.length) % colors.length];
|
||||||
const newTrack = {
|
const newTrack = {
|
||||||
id: newId,
|
id: newId,
|
||||||
name: `Track ${newId}`,
|
name: `Track ${newId}`,
|
||||||
@@ -7028,7 +7077,7 @@ const App = () => {
|
|||||||
sections: [],
|
sections: [],
|
||||||
midiItems: []
|
midiItems: []
|
||||||
};
|
};
|
||||||
setTracks(prev => {
|
updateActiveTracks(prev => {
|
||||||
const idx = prev.findIndex(t => t.id === selectedTrackId);
|
const idx = prev.findIndex(t => t.id === selectedTrackId);
|
||||||
if (idx === -1) return [...prev, newTrack];
|
if (idx === -1) return [...prev, newTrack];
|
||||||
const copy = [...prev];
|
const copy = [...prev];
|
||||||
@@ -7060,7 +7109,8 @@ const App = () => {
|
|||||||
|
|
||||||
// ── Insert MIDI Item at Playhead ──
|
// ── Insert MIDI Item at Playhead ──
|
||||||
const insertMidiItemAtPlayhead = () => {
|
const insertMidiItemAtPlayhead = () => {
|
||||||
const track = tracks.find(t => t.id === selectedTrackId);
|
const curTracks = activeTracks;
|
||||||
|
const track = curTracks.find(t => t.id === selectedTrackId);
|
||||||
if (!track) { showToast('Chọn track trước', 'warning'); return; }
|
if (!track) { showToast('Chọn track trước', 'warning'); return; }
|
||||||
const midiItem = {
|
const midiItem = {
|
||||||
id: `midi_${Date.now()}`,
|
id: `midi_${Date.now()}`,
|
||||||
@@ -7070,7 +7120,7 @@ const App = () => {
|
|||||||
notes: [],
|
notes: [],
|
||||||
color: '#a78bfa'
|
color: '#a78bfa'
|
||||||
};
|
};
|
||||||
setTracks(prev => prev.map(t => t.id === selectedTrackId ? {
|
updateActiveTracks(prev => prev.map(t => t.id === selectedTrackId ? {
|
||||||
...t,
|
...t,
|
||||||
midiItems: [...(t.midiItems || []), midiItem]
|
midiItems: [...(t.midiItems || []), midiItem]
|
||||||
} : t));
|
} : t));
|
||||||
@@ -7079,7 +7129,8 @@ const App = () => {
|
|||||||
|
|
||||||
// ── Insert Sound Clip at Cursor ──
|
// ── Insert Sound Clip at Cursor ──
|
||||||
const insertSoundClipAtCursor = () => {
|
const insertSoundClipAtCursor = () => {
|
||||||
const track = tracks.find(t => t.id === selectedTrackId);
|
const curTracks = activeTracks;
|
||||||
|
const track = curTracks.find(t => t.id === selectedTrackId);
|
||||||
if (!track) { showToast('Chọn track trước', 'warning'); return; }
|
if (!track) { showToast('Chọn track trước', 'warning'); return; }
|
||||||
const input = document.createElement('input');
|
const input = document.createElement('input');
|
||||||
input.type = 'file';
|
input.type = 'file';
|
||||||
@@ -7096,7 +7147,7 @@ const App = () => {
|
|||||||
const { audioBuffer: decodedBuffer, channelInfo } = await window.SonicAudio.decodeAudioFile(file);
|
const { audioBuffer: decodedBuffer, channelInfo } = await window.SonicAudio.decodeAudioFile(file);
|
||||||
const clipId = `clip_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`;
|
const clipId = `clip_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`;
|
||||||
const newClip = { id: clipId, buffer: decodedBuffer, startTime: offset, name: file.name, speed: 1.0 };
|
const newClip = { id: clipId, buffer: decodedBuffer, startTime: offset, name: file.name, speed: 1.0 };
|
||||||
setTracks(prev => prev.map(t => t.id === selectedTrackId ? {
|
updateActiveTracks(prev => prev.map(t => t.id === selectedTrackId ? {
|
||||||
...t,
|
...t,
|
||||||
clips: [...(t.clips || []), newClip]
|
clips: [...(t.clips || []), newClip]
|
||||||
} : t));
|
} : t));
|
||||||
@@ -9147,11 +9198,11 @@ const App = () => {
|
|||||||
}]
|
}]
|
||||||
}, {
|
}, {
|
||||||
label: 'Insert',
|
label: 'Insert',
|
||||||
items: [{
|
items: [...(!sessionTabs.some(s => s.id === activeTab) ? [{
|
||||||
label: 'Insert Section',
|
label: 'Insert Section',
|
||||||
icon: 'folder-plus',
|
icon: 'folder-plus',
|
||||||
action: insertSectionAtPlayhead
|
action: insertSectionAtPlayhead
|
||||||
}, {
|
}] : []), {
|
||||||
label: 'Insert MIDI item',
|
label: 'Insert MIDI item',
|
||||||
icon: 'music',
|
icon: 'music',
|
||||||
action: insertMidiItemAtPlayhead
|
action: insertMidiItemAtPlayhead
|
||||||
|
|||||||
@@ -5235,15 +5235,27 @@ const App = () => {
|
|||||||
};
|
};
|
||||||
const contextMenuDelete = () => {
|
const contextMenuDelete = () => {
|
||||||
const tid = contextMenu.trackId;
|
const tid = contextMenu.trackId;
|
||||||
|
const sessionTab = sessionTabs.find(s => s.id === activeTab);
|
||||||
|
if (sessionTab) {
|
||||||
|
updateActiveTracks(prev => prev.filter(t => t.id !== tid));
|
||||||
|
if (selectedTrackId === tid) setSelectedTrackId('1');
|
||||||
|
closeContextMenu();
|
||||||
|
showToast('Đã xoá track.', 'info');
|
||||||
|
} else {
|
||||||
|
const track = tracks.find(t => t.id === tid);
|
||||||
|
if (track && track.sections && track.sections.length > 0) {
|
||||||
|
closeContextMenu();
|
||||||
|
showToast('Không thể xoá track chứa Section item.', 'warning');
|
||||||
|
return;
|
||||||
|
}
|
||||||
const beforeSnap = captureTrackSnapshot(tid);
|
const beforeSnap = captureTrackSnapshot(tid);
|
||||||
setTracks(prev => prev.filter(t => t.id !== tid));
|
setTracks(prev => prev.filter(t => t.id !== tid));
|
||||||
const afterSnap = captureTrackSnapshot(tid);
|
const afterSnap = captureTrackSnapshot(tid);
|
||||||
pushAction('DELETE', tid, beforeSnap, afterSnap);
|
pushAction('DELETE', tid, beforeSnap, afterSnap);
|
||||||
if (selectedTrackId === tid) {
|
if (selectedTrackId === tid) setSelectedTrackId(tracks.filter(t => t.id !== tid)[0]?.id || '1');
|
||||||
setSelectedTrackId(tracks.filter(t => t.id !== tid)[0]?.id || '1');
|
|
||||||
}
|
|
||||||
closeContextMenu();
|
closeContextMenu();
|
||||||
showToast('Đã xoá track.', 'info');
|
showToast('Đã xoá track.', 'info');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
const contextMenuCopy = () => {
|
const contextMenuCopy = () => {
|
||||||
const track = tracks.find(t => t.id === contextMenu.trackId);
|
const track = tracks.find(t => t.id === contextMenu.trackId);
|
||||||
@@ -5625,8 +5637,20 @@ const App = () => {
|
|||||||
};
|
};
|
||||||
const handleDeleteTrack = () => {
|
const handleDeleteTrack = () => {
|
||||||
const tid = selectedTrackId;
|
const tid = selectedTrackId;
|
||||||
|
const sessionTab = sessionTabs.find(s => s.id === activeTab);
|
||||||
|
if (sessionTab) {
|
||||||
|
updateActiveTracks(prev => prev.filter(t => t.id !== tid));
|
||||||
|
setSelectedTrackId(activeTracks.filter(t => t.id !== tid)[0]?.id || '1');
|
||||||
|
} else {
|
||||||
|
const curTracks = tracks;
|
||||||
|
const track = curTracks.find(t => t.id === tid);
|
||||||
|
if (track && track.sections && track.sections.length > 0) {
|
||||||
|
showToast('Không thể xoá track chứa Section item.', 'warning');
|
||||||
|
return;
|
||||||
|
}
|
||||||
setTracks(p => p.filter(t => t.id !== tid));
|
setTracks(p => p.filter(t => t.id !== tid));
|
||||||
setSelectedTrackId(tracks.filter(t => t.id !== tid)[0]?.id || '1');
|
setSelectedTrackId(curTracks.filter(t => t.id !== tid)[0]?.id || '1');
|
||||||
|
}
|
||||||
showToast('Deleted track.', 'info');
|
showToast('Deleted track.', 'info');
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -6503,14 +6527,26 @@ const App = () => {
|
|||||||
document.addEventListener('mouseup', handleMouseUp);
|
document.addEventListener('mouseup', handleMouseUp);
|
||||||
};
|
};
|
||||||
const deleteTrack = trackId => {
|
const deleteTrack = trackId => {
|
||||||
|
const sessionTab = sessionTabs.find(s => s.id === activeTab);
|
||||||
|
if (sessionTab) {
|
||||||
|
updateActiveTracks(prev => {
|
||||||
|
const filtered = prev.filter(t => t.id !== trackId);
|
||||||
|
if (filtered.length > 0) setSelectedTrackId(filtered[0].id);
|
||||||
|
return filtered;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const track = tracks.find(t => t.id === trackId);
|
||||||
|
if (track && track.sections && track.sections.length > 0) {
|
||||||
|
showToast('Không thể xoá track chứa Section item.', 'warning');
|
||||||
|
return;
|
||||||
|
}
|
||||||
const beforeSnap = captureTrackSnapshot(trackId);
|
const beforeSnap = captureTrackSnapshot(trackId);
|
||||||
setTracks(prev => {
|
setTracks(prev => {
|
||||||
const filtered = prev.filter(t => t.id !== trackId);
|
const filtered = prev.filter(t => t.id !== trackId);
|
||||||
if (filtered.length > 0) {
|
if (filtered.length > 0) setSelectedTrackId(filtered[0].id);
|
||||||
setSelectedTrackId(filtered[0].id);
|
|
||||||
}
|
|
||||||
return filtered;
|
return filtered;
|
||||||
});
|
});
|
||||||
|
}
|
||||||
showToast('Đã xóa track.', 'info');
|
showToast('Đã xóa track.', 'info');
|
||||||
};
|
};
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -6652,7 +6688,8 @@ const App = () => {
|
|||||||
|
|
||||||
// ── Section / MIDI Item Resize Start ──
|
// ── Section / MIDI Item Resize Start ──
|
||||||
const handleSectionItemResizeStart = (trackId, itemType, itemId, side, clickTime) => {
|
const handleSectionItemResizeStart = (trackId, itemType, itemId, side, clickTime) => {
|
||||||
const track = tracks.find(t => t.id === trackId);
|
const curTracks = activeTracks;
|
||||||
|
const track = curTracks.find(t => t.id === trackId);
|
||||||
if (!track) return;
|
if (!track) return;
|
||||||
const items = itemType === 'section' ? track.sections : track.midiItems;
|
const items = itemType === 'section' ? track.sections : track.midiItems;
|
||||||
const item = (items || []).find(it => it.id === itemId);
|
const item = (items || []).find(it => it.id === itemId);
|
||||||
@@ -6674,7 +6711,7 @@ const App = () => {
|
|||||||
const time = mouseX / zoom;
|
const time = mouseX / zoom;
|
||||||
const newStart = Math.max(0, time - drag.clickOffset);
|
const newStart = Math.max(0, time - drag.clickOffset);
|
||||||
const targetTrackId = hoveredTrackIdRef.current || drag.trackId;
|
const targetTrackId = hoveredTrackIdRef.current || drag.trackId;
|
||||||
setTracks(prev => prev.map(t => {
|
updateActiveTracks(prev => prev.map(t => {
|
||||||
const items = drag.itemType === 'section' ? (t.sections || []) : (t.midiItems || []);
|
const items = drag.itemType === 'section' ? (t.sections || []) : (t.midiItems || []);
|
||||||
const updatedItems = items.filter(it => it.id !== drag.itemId);
|
const updatedItems = items.filter(it => it.id !== drag.itemId);
|
||||||
if (t.id === targetTrackId) {
|
if (t.id === targetTrackId) {
|
||||||
@@ -6711,7 +6748,7 @@ const App = () => {
|
|||||||
document.removeEventListener('mousemove', handleMouseMove);
|
document.removeEventListener('mousemove', handleMouseMove);
|
||||||
document.removeEventListener('mouseup', handleMouseUp);
|
document.removeEventListener('mouseup', handleMouseUp);
|
||||||
};
|
};
|
||||||
}, [zoom]);
|
}, [zoom, activeTab, sessionTabs]);
|
||||||
|
|
||||||
// ── Document-level mousemove/mouseup for Section/MIDI item resize ──
|
// ── Document-level mousemove/mouseup for Section/MIDI item resize ──
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -6724,7 +6761,7 @@ const App = () => {
|
|||||||
const scrollLeft = wrapper.scrollLeft;
|
const scrollLeft = wrapper.scrollLeft;
|
||||||
const mouseX = e.clientX - rect.left + scrollLeft;
|
const mouseX = e.clientX - rect.left + scrollLeft;
|
||||||
const time = mouseX / zoom;
|
const time = mouseX / zoom;
|
||||||
setTracks(prev => prev.map(t => {
|
updateActiveTracks(prev => prev.map(t => {
|
||||||
if (t.id !== resize.trackId) return t;
|
if (t.id !== resize.trackId) return t;
|
||||||
const items = resize.itemType === 'section' ? [...(t.sections || [])] : [...(t.midiItems || [])];
|
const items = resize.itemType === 'section' ? [...(t.sections || [])] : [...(t.midiItems || [])];
|
||||||
const idx = items.findIndex(it => it.id === resize.itemId);
|
const idx = items.findIndex(it => it.id === resize.itemId);
|
||||||
@@ -6758,7 +6795,7 @@ const App = () => {
|
|||||||
document.removeEventListener('mousemove', handleMouseMove);
|
document.removeEventListener('mousemove', handleMouseMove);
|
||||||
document.removeEventListener('mouseup', handleMouseUp);
|
document.removeEventListener('mouseup', handleMouseUp);
|
||||||
};
|
};
|
||||||
}, [zoom]);
|
}, [zoom, activeTab, sessionTabs]);
|
||||||
|
|
||||||
const handleSelectRange = (start, end, reset) => {
|
const handleSelectRange = (start, end, reset) => {
|
||||||
const maxLen = maxDuration;
|
const maxLen = maxDuration;
|
||||||
@@ -7073,12 +7110,23 @@ const App = () => {
|
|||||||
showToast(`Đã nạp sóng âm tổng hợp: ${type.toUpperCase()}`, 'success');
|
showToast(`Đã nạp sóng âm tổng hợp: ${type.toUpperCase()}`, 'success');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ── Update tracks in active context (main session or section tab) ──
|
||||||
|
const updateActiveTracks = updater => {
|
||||||
|
const sessionTab = sessionTabs.find(s => s.id === activeTab);
|
||||||
|
if (sessionTab) {
|
||||||
|
setSessionTabs(prev => prev.map(st => st.id === activeTab ? { ...st, tracks: updater(st.tracks) } : st));
|
||||||
|
} else {
|
||||||
|
setTracks(updater);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// ── Add Track ──
|
// ── Add Track ──
|
||||||
const addNewTrack = () => {
|
const addNewTrack = () => {
|
||||||
const newId = (tracks.length + 1).toString();
|
const curTracks = activeTracks;
|
||||||
|
const newId = (curTracks.length + 1).toString();
|
||||||
const colors = ['#0f766e', '#1d4ed8', '#701a75', '#a21caf', '#b45309'];
|
const colors = ['#0f766e', '#1d4ed8', '#701a75', '#a21caf', '#b45309'];
|
||||||
const selectColor = colors[tracks.length % colors.length];
|
const selectColor = colors[curTracks.length % colors.length];
|
||||||
setTracks(prev => [...prev, {
|
updateActiveTracks(prev => [...prev, {
|
||||||
id: newId,
|
id: newId,
|
||||||
name: `Track ${newId}`,
|
name: `Track ${newId}`,
|
||||||
buffer: null,
|
buffer: null,
|
||||||
@@ -7102,9 +7150,10 @@ const App = () => {
|
|||||||
|
|
||||||
// ── Insert Track Below Selected ──
|
// ── Insert Track Below Selected ──
|
||||||
const insertTrackBelow = () => {
|
const insertTrackBelow = () => {
|
||||||
|
const curTracks = activeTracks;
|
||||||
const newId = `t${Date.now()}`;
|
const newId = `t${Date.now()}`;
|
||||||
const colors = ['#0f766e', '#1d4ed8', '#701a75', '#a21caf', '#b45309'];
|
const colors = ['#0f766e', '#1d4ed8', '#701a75', '#a21caf', '#b45309'];
|
||||||
const selectColor = colors[tracks.length % colors.length];
|
const selectColor = colors[curTracks.length % colors.length];
|
||||||
const newTrack = {
|
const newTrack = {
|
||||||
id: newId,
|
id: newId,
|
||||||
name: `Track ${newId}`,
|
name: `Track ${newId}`,
|
||||||
@@ -7122,7 +7171,7 @@ const App = () => {
|
|||||||
sections: [],
|
sections: [],
|
||||||
midiItems: []
|
midiItems: []
|
||||||
};
|
};
|
||||||
setTracks(prev => {
|
updateActiveTracks(prev => {
|
||||||
const idx = prev.findIndex(t => t.id === selectedTrackId);
|
const idx = prev.findIndex(t => t.id === selectedTrackId);
|
||||||
if (idx === -1) return [...prev, newTrack];
|
if (idx === -1) return [...prev, newTrack];
|
||||||
const copy = [...prev];
|
const copy = [...prev];
|
||||||
@@ -7154,7 +7203,8 @@ const App = () => {
|
|||||||
|
|
||||||
// ── Insert MIDI Item at Playhead ──
|
// ── Insert MIDI Item at Playhead ──
|
||||||
const insertMidiItemAtPlayhead = () => {
|
const insertMidiItemAtPlayhead = () => {
|
||||||
const track = tracks.find(t => t.id === selectedTrackId);
|
const curTracks = activeTracks;
|
||||||
|
const track = curTracks.find(t => t.id === selectedTrackId);
|
||||||
if (!track) { showToast('Chọn track trước', 'warning'); return; }
|
if (!track) { showToast('Chọn track trước', 'warning'); return; }
|
||||||
const midiItem = {
|
const midiItem = {
|
||||||
id: `midi_${Date.now()}`,
|
id: `midi_${Date.now()}`,
|
||||||
@@ -7164,7 +7214,7 @@ const App = () => {
|
|||||||
notes: [],
|
notes: [],
|
||||||
color: '#a78bfa'
|
color: '#a78bfa'
|
||||||
};
|
};
|
||||||
setTracks(prev => prev.map(t => t.id === selectedTrackId ? {
|
updateActiveTracks(prev => prev.map(t => t.id === selectedTrackId ? {
|
||||||
...t,
|
...t,
|
||||||
midiItems: [...(t.midiItems || []), midiItem]
|
midiItems: [...(t.midiItems || []), midiItem]
|
||||||
} : t));
|
} : t));
|
||||||
@@ -7173,7 +7223,8 @@ const App = () => {
|
|||||||
|
|
||||||
// ── Insert Sound Clip at Cursor ──
|
// ── Insert Sound Clip at Cursor ──
|
||||||
const insertSoundClipAtCursor = () => {
|
const insertSoundClipAtCursor = () => {
|
||||||
const track = tracks.find(t => t.id === selectedTrackId);
|
const curTracks = activeTracks;
|
||||||
|
const track = curTracks.find(t => t.id === selectedTrackId);
|
||||||
if (!track) { showToast('Chọn track trước', 'warning'); return; }
|
if (!track) { showToast('Chọn track trước', 'warning'); return; }
|
||||||
const input = document.createElement('input');
|
const input = document.createElement('input');
|
||||||
input.type = 'file';
|
input.type = 'file';
|
||||||
@@ -7190,7 +7241,7 @@ const App = () => {
|
|||||||
const { audioBuffer: decodedBuffer, channelInfo } = await window.SonicAudio.decodeAudioFile(file);
|
const { audioBuffer: decodedBuffer, channelInfo } = await window.SonicAudio.decodeAudioFile(file);
|
||||||
const clipId = `clip_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`;
|
const clipId = `clip_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`;
|
||||||
const newClip = { id: clipId, buffer: decodedBuffer, startTime: offset, name: file.name, speed: 1.0 };
|
const newClip = { id: clipId, buffer: decodedBuffer, startTime: offset, name: file.name, speed: 1.0 };
|
||||||
setTracks(prev => prev.map(t => t.id === selectedTrackId ? {
|
updateActiveTracks(prev => prev.map(t => t.id === selectedTrackId ? {
|
||||||
...t,
|
...t,
|
||||||
clips: [...(t.clips || []), newClip]
|
clips: [...(t.clips || []), newClip]
|
||||||
} : t));
|
} : t));
|
||||||
@@ -9639,11 +9690,11 @@ const App = () => {
|
|||||||
}]
|
}]
|
||||||
}, {
|
}, {
|
||||||
label: 'Insert',
|
label: 'Insert',
|
||||||
items: [{
|
items: [...(!sessionTabs.some(s => s.id === activeTab) ? [{
|
||||||
label: 'Insert Section',
|
label: 'Insert Section',
|
||||||
icon: 'folder-plus',
|
icon: 'folder-plus',
|
||||||
action: insertSectionAtPlayhead
|
action: insertSectionAtPlayhead
|
||||||
}, {
|
}] : []), {
|
||||||
label: 'Insert MIDI item',
|
label: 'Insert MIDI item',
|
||||||
icon: 'music',
|
icon: 'music',
|
||||||
action: insertMidiItemAtPlayhead
|
action: insertMidiItemAtPlayhead
|
||||||
|
|||||||
Binary file not shown.
Reference in New Issue
Block a user