fix: Ctrl+S in SECTION-TAB stale closure, saveSectionTab uses initial sessionTabs

Keyboard handler (useEffect [] deps) captures handleSaveSectionTab
from first render, which has sessionTabs = []. When section tab
is opened later, saved handleSaveSectionTab finds no tab and
returns silently. Added handleSaveSectionTabRef, updated on
every render, used in keyboard handler via ref.current.
This commit is contained in:
2026-07-28 10:13:28 +07:00
parent f84431c9a6
commit f0b62d5a1c
2 changed files with 7 additions and 5 deletions
+4 -2
View File
@@ -8068,7 +8068,7 @@ const App = () => {
e.preventDefault();
var curTab = activeTabRef.current;
if (curTab.startsWith('session_')) {
handleSaveSectionTab(curTab);
handleSaveSectionTabRef.current(curTab);
showToast('Đã lưu Section', 'success');
} else {
handleExportSFS();
@@ -8177,7 +8177,7 @@ const App = () => {
});
} else if (curTab.startsWith('session_')) {
// Section tab: save section
handleSaveSectionTab(curTab);
handleSaveSectionTabRef.current(curTab);
showToast('Đã lưu Section', 'success');
} else {
// Sub-tab: save current tab
@@ -11700,6 +11700,8 @@ const App = () => {
};
// Add item to selectedItemIds
const handleSaveSectionTabRef = useRef(handleSaveSectionTab);
handleSaveSectionTabRef.current = handleSaveSectionTab;
const handleAddToSelection = itemId => {
setSelectedItemIds(prev => {
var next = new Set(prev);
+3 -3
View File
@@ -231,10 +231,10 @@ const handleUndoRef=useRef(handleUndo);const handleRedoRef=useRef(handleRedo);ha
const segmentData=data.subarray(startSample,endSample);const addedSamples=len*(loopCount-1);const newBuffer=ctx.createBuffer(1,data.length+addedSamples,sr);const newData=newBuffer.getChannelData(0);let idx=0;for(let i=0;i<endSample;i++)newData[idx++]=data[i];for(let n=0;n<loopCount-1;n++){for(let i=0;i<len;i++)newData[idx++]=segmentData[i];}for(let i=endSample;i<data.length;i++)newData[idx++]=data[i];setSubTabs(prev=>prev.map(s=>s.id===tabId?{...s,buffer:newBuffer,selectionStart:null,selectionEnd:null}:s));showToast(`Đã lặp vùng chọn ${loopCount} lần.`,'success');};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;// Global space play/pause shortcut for transport
if(e.key===' '||e.code==='Space'){e.preventDefault();if(recordingState==='RECORDING'||recordingState==='COUNT_IN'){handleRecordClick();return;}if(handlePlayPauseRef.current)handlePlayPauseRef.current();return;}if(activeTabRef.current!=='main'&&!activeTabRef.current.startsWith('session_')){// Sub-Tab keyboard shortcuts mapping
const curTabId=activeTabRef.current;if(ctrl&&alt&&e.key==='n'){e.preventDefault();handleSubTabNormalize(curTabId);return;}if(e.key==='f'||e.key==='F'){e.preventDefault();handleSubTabFade(curTabId,'in');return;}if(e.key==='g'||e.key==='G'){e.preventDefault();handleSubTabFade(curTabId,'out');return;}if(ctrl&&e.key==='l'){e.preventDefault();handleSubTabLoop(curTabId,4);return;}if(e.key==='v'||e.key==='V'){e.preventDefault();const val=prompt("Nhập Gain điều chỉnh (dB):","0");if(val)handleSubTabGain(curTabId,parseFloat(val)||0);return;}if(ctrl&&e.key==='x'){e.preventDefault();handleSubTabCut(curTabId);return;}if(ctrl&&e.key==='c'){e.preventDefault();handleSubTabCopy(curTabId);return;}if(ctrl&&e.key==='v'){e.preventDefault();handleSubTabPaste(curTabId);return;}if(e.key==='Delete'||e.key==='Backspace'||e.key==='Del'){e.preventDefault();if(subTabSelectedNodeTimeRef.current!==null){const selTime=subTabSelectedNodeTimeRef.current;setSubTabs(prev=>prev.map(s=>{if(s.id!==curTabId)return s;const curNodes=s.graphMode==='pan'?s.panningNodes||[]:s.volumeNodes||[];const updated=curNodes.filter(n=>n.time!==selTime);return{...s,[s.graphMode==='pan'?'panningNodes':'volumeNodes']:updated};}));setSubTabSelectedNodeTime(null);}else{handleSubTabDelete(curTabId);}return;}return;}if(ctrl&&e.key==='z'&&!e.shiftKey){e.preventDefault();handleUndoRef.current();return;}if(ctrl&&(e.key==='y'||e.key==='z'&&e.shiftKey)){e.preventDefault();handleRedoRef.current();return;}if(ctrl&&!alt&&e.key==='o'){e.preventDefault();handleImportSFS();return;}if(ctrl&&!alt&&e.key==='n'){e.preventDefault();setTracks([{id:'1',name:'Track 01',buffer:null,startTime:0,volumeDb:0,pan:0,muted:false,solo:false,color:'#0f766e',markers:[],serverFileId:null},{id:'2',name:'Track 02',buffer:null,startTime:0,volumeDb:0,pan:0,muted:false,solo:false,color:'#1d4ed8',markers:[],serverFileId:null}]);setSelectedTrackId('1');showToast('New project created','info');return;}if(ctrl&&!alt&&!e.shiftKey&&e.key==='a'){e.preventDefault();const curTab=activeTabRef.current;if(curTab!=='main'&&!curTab.startsWith('session_'))return;const allIds=new Set();(activeTracksRef.current||[]).forEach(t=>{(t.sections||[]).forEach(s=>allIds.add(s.id));(t.midiItems||[]).forEach(m=>allIds.add(m.id));(t.clips||[]).forEach(c=>allIds.add(c.id==='default'?'default_'+t.id:c.id));});setSelectedItemIds(allIds);return;}if(ctrl&&!alt&&e.key==='s'){e.preventDefault();var curTab=activeTabRef.current;if(curTab.startsWith('session_')){handleSaveSectionTab(curTab);showToast('Đã lưu Section','success');}else{handleExportSFS();}return;}if(ctrl&&alt&&e.key==='s'||ctrl&&e.shiftKey&&e.key==='s'){e.preventDefault();handleExportSFS();return;}if(ctrl&&!alt&&e.key==='i'){e.preventDefault();addNewTrack();return;}if(ctrl&&alt&&e.key==='i'){e.preventDefault();showToast('Import audio','info');return;}if(ctrl&&!alt&&e.key==='e'){e.preventDefault();openTempTab();return;}if(ctrl&&!alt&&e.key==='m'){e.preventDefault();handleMergeTracks();return;}if(ctrl&&!alt&&e.key==='c'){e.preventDefault();handleCopyTrack();return;}if(ctrl&&!alt&&e.key==='x'){e.preventDefault();handleCutTrack();return;}if(ctrl&&!alt&&e.key==='v'){e.preventDefault();handlePasteTrack();return;}if(e.key==='Delete'||e.key==='Backspace'||e.key==='Del'){const selClip=selectedClipIdRef.current;if(selClip){e.preventDefault();const{trackId,clipId}=selClip;setTracks(prev=>{const track=prev.find(t=>t.id===trackId);if(!track)return prev;const beforeSnap=captureTrackSnapshotRef.current?captureTrackSnapshotRef.current(trackId):null;const updatedClips=(track.clips||[]).filter(c=>c.id!==clipId);const updatedTracks=prev.map(t=>{if(t.id===trackId){return{...t,clips:updatedClips,buffer:updatedClips.length>0?updatedClips[0].buffer:null,startTime:updatedClips.length>0?updatedClips[0].startTime:0,name:updatedClips.length>0?updatedClips[0].name:`Track ${t.id}`};}return t;});setTimeout(()=>{const afterSnap=captureTrackSnapshotRef.current?captureTrackSnapshotRef.current(trackId):null;pushAction('DELETE_CLIP',trackId,beforeSnap,afterSnap);},50);return updatedTracks;});setSelectedClipId(null);showToast('Đã xóa clip.','info');return;}else{e.preventDefault();handleDeleteTrack();return;}}if(ctrl&&!alt&&e.key==='s'){e.preventDefault();const curTab=activeTabRef.current;if(curTab==='main'){// Main session: save project + save all dirty sub-tabs
const curTabId=activeTabRef.current;if(ctrl&&alt&&e.key==='n'){e.preventDefault();handleSubTabNormalize(curTabId);return;}if(e.key==='f'||e.key==='F'){e.preventDefault();handleSubTabFade(curTabId,'in');return;}if(e.key==='g'||e.key==='G'){e.preventDefault();handleSubTabFade(curTabId,'out');return;}if(ctrl&&e.key==='l'){e.preventDefault();handleSubTabLoop(curTabId,4);return;}if(e.key==='v'||e.key==='V'){e.preventDefault();const val=prompt("Nhập Gain điều chỉnh (dB):","0");if(val)handleSubTabGain(curTabId,parseFloat(val)||0);return;}if(ctrl&&e.key==='x'){e.preventDefault();handleSubTabCut(curTabId);return;}if(ctrl&&e.key==='c'){e.preventDefault();handleSubTabCopy(curTabId);return;}if(ctrl&&e.key==='v'){e.preventDefault();handleSubTabPaste(curTabId);return;}if(e.key==='Delete'||e.key==='Backspace'||e.key==='Del'){e.preventDefault();if(subTabSelectedNodeTimeRef.current!==null){const selTime=subTabSelectedNodeTimeRef.current;setSubTabs(prev=>prev.map(s=>{if(s.id!==curTabId)return s;const curNodes=s.graphMode==='pan'?s.panningNodes||[]:s.volumeNodes||[];const updated=curNodes.filter(n=>n.time!==selTime);return{...s,[s.graphMode==='pan'?'panningNodes':'volumeNodes']:updated};}));setSubTabSelectedNodeTime(null);}else{handleSubTabDelete(curTabId);}return;}return;}if(ctrl&&e.key==='z'&&!e.shiftKey){e.preventDefault();handleUndoRef.current();return;}if(ctrl&&(e.key==='y'||e.key==='z'&&e.shiftKey)){e.preventDefault();handleRedoRef.current();return;}if(ctrl&&!alt&&e.key==='o'){e.preventDefault();handleImportSFS();return;}if(ctrl&&!alt&&e.key==='n'){e.preventDefault();setTracks([{id:'1',name:'Track 01',buffer:null,startTime:0,volumeDb:0,pan:0,muted:false,solo:false,color:'#0f766e',markers:[],serverFileId:null},{id:'2',name:'Track 02',buffer:null,startTime:0,volumeDb:0,pan:0,muted:false,solo:false,color:'#1d4ed8',markers:[],serverFileId:null}]);setSelectedTrackId('1');showToast('New project created','info');return;}if(ctrl&&!alt&&!e.shiftKey&&e.key==='a'){e.preventDefault();const curTab=activeTabRef.current;if(curTab!=='main'&&!curTab.startsWith('session_'))return;const allIds=new Set();(activeTracksRef.current||[]).forEach(t=>{(t.sections||[]).forEach(s=>allIds.add(s.id));(t.midiItems||[]).forEach(m=>allIds.add(m.id));(t.clips||[]).forEach(c=>allIds.add(c.id==='default'?'default_'+t.id:c.id));});setSelectedItemIds(allIds);return;}if(ctrl&&!alt&&e.key==='s'){e.preventDefault();var curTab=activeTabRef.current;if(curTab.startsWith('session_')){handleSaveSectionTabRef.current(curTab);showToast('Đã lưu Section','success');}else{handleExportSFS();}return;}if(ctrl&&alt&&e.key==='s'||ctrl&&e.shiftKey&&e.key==='s'){e.preventDefault();handleExportSFS();return;}if(ctrl&&!alt&&e.key==='i'){e.preventDefault();addNewTrack();return;}if(ctrl&&alt&&e.key==='i'){e.preventDefault();showToast('Import audio','info');return;}if(ctrl&&!alt&&e.key==='e'){e.preventDefault();openTempTab();return;}if(ctrl&&!alt&&e.key==='m'){e.preventDefault();handleMergeTracks();return;}if(ctrl&&!alt&&e.key==='c'){e.preventDefault();handleCopyTrack();return;}if(ctrl&&!alt&&e.key==='x'){e.preventDefault();handleCutTrack();return;}if(ctrl&&!alt&&e.key==='v'){e.preventDefault();handlePasteTrack();return;}if(e.key==='Delete'||e.key==='Backspace'||e.key==='Del'){const selClip=selectedClipIdRef.current;if(selClip){e.preventDefault();const{trackId,clipId}=selClip;setTracks(prev=>{const track=prev.find(t=>t.id===trackId);if(!track)return prev;const beforeSnap=captureTrackSnapshotRef.current?captureTrackSnapshotRef.current(trackId):null;const updatedClips=(track.clips||[]).filter(c=>c.id!==clipId);const updatedTracks=prev.map(t=>{if(t.id===trackId){return{...t,clips:updatedClips,buffer:updatedClips.length>0?updatedClips[0].buffer:null,startTime:updatedClips.length>0?updatedClips[0].startTime:0,name:updatedClips.length>0?updatedClips[0].name:`Track ${t.id}`};}return t;});setTimeout(()=>{const afterSnap=captureTrackSnapshotRef.current?captureTrackSnapshotRef.current(trackId):null;pushAction('DELETE_CLIP',trackId,beforeSnap,afterSnap);},50);return updatedTracks;});setSelectedClipId(null);showToast('Đã xóa clip.','info');return;}else{e.preventDefault();handleDeleteTrack();return;}}if(ctrl&&!alt&&e.key==='s'){e.preventDefault();const curTab=activeTabRef.current;if(curTab==='main'){// Main session: save project + save all dirty sub-tabs
handleSaveProject();subTabsRef.current.filter(s=>s.isDirty).forEach(st=>{if(st.type==='PIANO_ROLL'){handleSaveMidiNotes(st.id,st.trackId,st.target_id,st.notes||[]);}else if(st.type==='SECTION'){handleSaveSectionTab(st.id);}else if(st.buffer){// Audio clip sub-tab: save buffer to track
const subTrack=activeTracksRef.current.find(t=>t.id===st.trackId);if(subTrack){updateActiveTracks(prev=>prev.map(t=>t.id===st.trackId?{...t,buffer:st.buffer}:t));}}showToast('Đã lưu','success');});}else if(curTab.startsWith('session_')){// Section tab: save section
handleSaveSectionTab(curTab);showToast('Đã lưu Section','success');}else{// Sub-tab: save current tab
handleSaveSectionTabRef.current(curTab);showToast('Đã lưu Section','success');}else{// Sub-tab: save current tab
const st=subTabsRef.current.find(s=>s.id===curTab);if(st){if(st.type==='PIANO_ROLL'){handleSaveMidiNotes(st.id,st.trackId,st.target_id,st.notes||[]);showToast('Đã lưu Piano Roll','success');}else if(st.type==='SECTION'){handleSaveSectionTab(st.id);showToast('Đã lưu Section Tab','success');}else if(st.buffer){const subTrack=activeTracksRef.current.find(t=>t.id===st.trackId);if(subTrack){updateActiveTracks(prev=>prev.map(t=>t.id===st.trackId?{...t,buffer:st.buffer}:t));}showToast('Đã lưu Audio Tab','success');}}}return;}if(!ctrl&&!alt&&e.key==='s'){e.preventDefault();handleSplitTrack(selectedTrackId);return;}};window.addEventListener('keydown',handler,{capture:true});return()=>window.removeEventListener('keydown',handler,{capture:true});},[]);// ── Temp Tab: draw isolated waveform ──
useEffect(()=>{if(!tempTabActive||!tempTabBuffer||!tempTabCanvasRef.current)return;const canvas=tempTabCanvasRef.current;const ctx=canvas.getContext('2d');const dpr=window.devicePixelRatio||1;const rect=canvas.getBoundingClientRect();canvas.width=rect.width*dpr;canvas.height=rect.height*dpr;ctx.scale(dpr,dpr);const w=rect.width;const h=rect.height;ctx.fillStyle='#181818';ctx.fillRect(0,0,w,h);const data=tempTabBuffer.getChannelData(0);const sr=tempTabBuffer.sampleRate;const totalSamples=data.length;if(totalSamples===0)return;ctx.strokeStyle='#6ee7b7';ctx.lineWidth=1;for(let px=0;px<w;px++){const startSample=Math.floor(px/w*totalSamples);const endSample=Math.floor((px+1)/w*totalSamples);let maxVal=0;for(let i=startSample;i<endSample&&i<totalSamples;i++){const abs=Math.abs(data[i]);if(abs>maxVal)maxVal=abs;}const mid=h/2;const peakHeight=maxVal*(h*0.4);ctx.beginPath();ctx.moveTo(px,mid-peakHeight);ctx.lineTo(px,mid+peakHeight);ctx.stroke();}},[tempTabActive,tempTabBuffer]);// ── Sub Tab: open as new tab instead of modal (LOOP_EDITOR_2.md §1) ──
const openTempTab=()=>{const useLocal=selectionMode==='local'&&localSelectionTrackId;const trackId=useLocal?localSelectionTrackId:selectedTrackId;const t=tracks.find(x=>x.id===trackId);if(!t||!t.buffer){showToast('Vui lòng chọn track có dữ liệu âm thanh.','warning');return;}if(selLeft===null||selRight===null||selRight<=selLeft){showToast('Vui lòng chọn một khoảng thời gian trên sóng âm.','warning');return;}// Check if a subtab for this track+range already exists
@@ -330,7 +330,7 @@ if(t.id===drag.trackId&&drag.trackId!==targetTrackId){const updatedClips=(t.clip
if(t.id===targetTrackId){const existingClips=t.clips&&t.clips.length>0?t.clips:t.buffer?[{id:'default_'+t.id,buffer:t.buffer,startTime:t.startTime||0,name:t.name}]:[];const hasClip=existingClips.some(c=>c.id===drag.clipId);let updatedClips;if(hasClip){updatedClips=existingClips.map(c=>c.id===drag.clipId?{...c,startTime:newStart}:c);}else{updatedClips=[...existingClips,{id:drag.clipId,buffer:drag.buffer,startTime:newStart,name:drag.name}];}return{...t,clips:updatedClips,buffer:updatedClips[0].buffer,startTime:updatedClips[0].startTime,name:updatedClips[0].name};}return t;}));if(drag.trackId!==targetTrackId){setDraggedClip(prev=>({...prev,trackId:targetTrackId}));}};const handleMouseUp=()=>{const drag=draggedClipRef.current;if(!drag)return;const afterSnap=captureTrackSnapshotRef.current(drag.trackId);pushAction('MOVE_CLIP',drag.trackId,drag.beforeSnap,afterSnap);setDraggedClip(null);showToast('Đã di chuyển clip.','success');};document.addEventListener('mousemove',handleMouseMove);document.addEventListener('mouseup',handleMouseUp);return()=>{document.removeEventListener('mousemove',handleMouseMove);document.removeEventListener('mouseup',handleMouseUp);};},[zoom]);// Document-level mousemove/mouseup for clip stretching
useEffect(()=>{const handleMouseMove=e=>{const stretch=stretchedClipRef.current;if(!stretch)return;autoScrollTimeline(e.clientX);const wrapper=timelineWrapperRef.current;if(!wrapper)return;const rect=wrapper.getBoundingClientRect();const scrollLeft=wrapper.scrollLeft;const mouseX=e.clientX-rect.left+scrollLeft;const time=mouseX/zoom;const secPerBar=60.0/(parseInt(bpmRef.current)||120)*4;const marginBar=maxDurationRef.current-secPerBar;const maxEnd=Math.min(time,marginBar);const newDuration=Math.max(0.1,maxEnd-stretch.startTime);const speedRatio=stretch.originalDuration/newDuration;updateActiveTracks(prev=>prev.map(t=>{if(t.id===stretch.trackId){const updatedClips=(t.clips||[]).map(c=>{if(c.id===stretch.clipId){return{...c,speed:speedRatio};}return c;});return{...t,clips:updatedClips,buffer:updatedClips[0]?.buffer,startTime:updatedClips[0]?.startTime||0,speed:updatedClips[0]?.speed||1.0};}return t;}));};const handleMouseUp=()=>{const stretch=stretchedClipRef.current;if(!stretch)return;const afterSnap=captureTrackSnapshotRef.current(stretch.trackId);pushAction('STRETCH_CLIP',stretch.trackId,stretch.beforeSnap,afterSnap);setStretchedClip(null);showToast('Đã giãn thời gian clip.','success');};document.addEventListener('mousemove',handleMouseMove);document.addEventListener('mouseup',handleMouseUp);return()=>{document.removeEventListener('mousemove',handleMouseMove);document.removeEventListener('mouseup',handleMouseUp);};},[zoom]);// ── Deselect single item ──
const handleDeselectItem=itemId=>{setSelectedItemIds(prev=>{var next=new Set(prev);next.delete(itemId);return next;});};// ── Add item to selectedItemIds ──
const handleAddToSelection=itemId=>{setSelectedItemIds(prev=>{var next=new Set(prev);next.add(itemId);return next;});};const handleSetPendingDrag=(trackId,itemType,itemId,clickOffset,e,preToggleSnapshot)=>{pendingDragRef.current={trackId,itemType,itemId,clickOffset,startX:e.clientX,startY:e.clientY,selectedIds:preToggleSnapshot||new Set(selectedItemIds)};};// ── Sweep Select ──
const handleSaveSectionTabRef=useRef(handleSaveSectionTab);handleSaveSectionTabRef.current=handleSaveSectionTab;const handleAddToSelection=itemId=>{setSelectedItemIds(prev=>{var next=new Set(prev);next.add(itemId);return next;});};const handleSetPendingDrag=(trackId,itemType,itemId,clickOffset,e,preToggleSnapshot)=>{pendingDragRef.current={trackId,itemType,itemId,clickOffset,startX:e.clientX,startY:e.clientY,selectedIds:preToggleSnapshot||new Set(selectedItemIds)};};// ── Sweep Select ──
const handleSweepSelectStart=(trackId,startTime)=>{isSweepingRef.current=true;sweepStartRef.current=startTime;sweepTrackIdRef.current=trackId;var init={startTime,endTime:startTime};sweepSelectRef.current=init;setSweepSelect(init);setSelectedItemIds(new Set());};// ── Section / MIDI Item Drag Start ──
const handleSectionItemDragStart=(trackId,itemType,itemId,clickOffset,isDuplicate,pendingSelectedIds)=>{var curTracks=activeTracksRef.current||activeTracks;var multiIds=null;var selIds=pendingSelectedIds||selectedItemIds;if(selIds&&selIds.size>0&&selIds.has(itemId)){var selArr=Array.from(selIds);var originals={};curTracks.forEach(function(t){(t.sections||[]).forEach(function(s){if(selArr.indexOf(s.id)>=0)originals[s.id]={type:'section',start:s.start};});(t.midiItems||[]).forEach(function(m){if(selArr.indexOf(m.id)>=0)originals[m.id]={type:'midiItem',start:m.startTime};});(t.clips||[]).forEach(function(c){var cid=c.id==='default'?'default_'+t.id:c.id;if(selArr.indexOf(cid)>=0)originals[cid]={type:'clip',start:c.startTime};});});if(Object.keys(originals).length>0)multiIds=originals;}if(isDuplicate){var track=curTracks.find(function(t){return t.id===trackId;});if(!track)return;if(multiIds){var newOriginals={};updateActiveTracks(function(prev){return prev.map(function(t){var updatedSections=t.sections?t.sections.slice():[];var updatedMidi=t.midiItems?t.midiItems.slice():[];var updatedClips=t.clips?t.clips.slice():[];Object.keys(multiIds).forEach(function(oid){var info=multiIds[oid];if(info.type==='section'){var sec=(t.sections||[]).find(function(s){return s.id===oid;});if(sec){var newId='sec_dup_'+Date.now()+'_'+Math.random().toString(36).substr(2,5);updatedSections.push({...sec,id:newId,name:sec.name+' (Copy)'});newOriginals[newId]={type:'section',start:sec.start};}}else if(info.type==='midiItem'){var mid=(t.midiItems||[]).find(function(m){return m.id===oid;});if(mid){var newId='midi_dup_'+Date.now()+'_'+Math.random().toString(36).substr(2,5);updatedMidi.push({...mid,id:newId,name:mid.name+' (Copy)'});newOriginals[newId]={type:'midiItem',start:mid.startTime};}}else if(info.type==='clip'){var clip=(t.clips||[]).find(function(c){return c.id===oid||'default_'+t.id===oid;});if(clip){var newId='clip_dup_'+Date.now()+'_'+Math.random().toString(36).substr(2,5);updatedClips.push({...clip,id:newId,startTime:clip.startTime,name:clip.name+' (Copy)'});newOriginals[newId]={type:'clip',start:clip.startTime};}}});return{...t,sections:updatedSections,midiItems:updatedMidi,clips:updatedClips};});});setDraggedSectionItem({trackId,itemType,itemId,clickOffset,multiIds:newOriginals});}else{var items=itemType==='section'?track.sections||[]:track.midiItems||[];var item=items.find(function(it){return it.id===itemId;});if(!item)return;var newId=itemType+'_dup_'+Date.now();var newItem={...item,id:newId,name:item.name+' (Copy)'};updateActiveTracks(function(prev){return prev.map(function(t){if(t.id!==trackId)return t;var updated=itemType==='section'?[...(t.sections||[]),newItem]:[...(t.midiItems||[]),newItem];return itemType==='section'?{...t,sections:updated}:{...t,midiItems:updated};});});setDraggedSectionItem({trackId,itemType,itemId:newId,clickOffset,isDuplicate:false});}return;}setDraggedSectionItem({trackId,itemType,itemId,clickOffset,multiIds});};handleSectionItemDragStartRef.current=handleSectionItemDragStart;// ── Section / MIDI Item Resize Start ──
const handleSectionItemResizeStart=(trackId,itemType,itemId,side,clickTime)=>{const curTracks=activeTracks;const track=curTracks.find(t=>t.id===trackId);if(!track)return;const items=itemType==='section'?track.sections:track.midiItems;const item=(items||[]).find(it=>it.id===itemId);if(!item)return;const start=itemType==='section'?item.start:item.startTime;setResizedSectionItem({trackId,itemType,itemId,side,originalStart:start,originalDuration:item.duration});};// ── Document-level mousemove/mouseup for Section/MIDI item drag ──