From ad6fa1feb9748fb354791d7227b1b002ab7484e3 Mon Sep 17 00:00:00 2001 From: 3dtours Date: Wed, 29 Jul 2026 11:08:11 +0700 Subject: [PATCH] fix: auto-add tracks when drag exceeds track count Compute maxTargetIdx across all multiIds items. Prepend new tracks to prev array before mapping if needed. --- app/static/js/app.jsx | 16 ++++++++++++++++ app/static/js/app.precompiled.js | 3 ++- wiki.md | 6 ++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/app/static/js/app.jsx b/app/static/js/app.jsx index 0a4f9e1..bd575d7 100644 --- a/app/static/js/app.jsx +++ b/app/static/js/app.jsx @@ -12075,6 +12075,22 @@ const App = () => { if (drag.multiIds) { var dragOrigStart = drag.multiIds[drag.itemId] ? drag.multiIds[drag.itemId].start : 0; var delta = newStart - dragOrigStart; + // Ensure enough tracks exist for all displaced items + var maxTrg = 0; + Object.keys(drag.multiIds).forEach(function(mid) { + var inf = drag.multiIds[mid]; + var origIdx = trkIds.indexOf(inf.trackId); + if (origIdx < 0) origIdx = baseIdx; + var trg = Math.max(0, origIdx + crossOffset); + if (trg > maxTrg) maxTrg = trg; + }); + var tracksToAdd = []; + while (prev.length <= maxTrg) { + var newId = (prev.length + tracksToAdd.length + 1).toString(); + var colors = ['#0f766e', '#1d4ed8', '#701a75', '#a21caf', '#b45309']; + tracksToAdd.push({ id: newId, name: 'Track ' + newId, buffer: null, startTime: 0, volumeDb: 0, pan: 0, muted: false, solo: false, color: colors[(prev.length + tracksToAdd.length) % colors.length], clips: [], midiItems: [], sections: [], markers: [], isArmed: false, height: 140 }); + } + if (tracksToAdd.length > 0) prev = prev.concat(tracksToAdd); return prev.map(function(t) { var resS = (t.sections || []).slice(); var resM = (t.midiItems || []).slice(); diff --git a/app/static/js/app.precompiled.js b/app/static/js/app.precompiled.js index 33786cc..26d463e 100644 --- a/app/static/js/app.precompiled.js +++ b/app/static/js/app.precompiled.js @@ -343,7 +343,8 @@ const handleSweepSelectStart=(trackId,startTime,startY)=>{isSweepingRef.current= 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,trackId:t.id};});(t.midiItems||[]).forEach(function(m){if(selArr.indexOf(m.id)>=0)originals[m.id]={type:'midiItem',start:m.startTime,trackId:t.id};});(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,trackId:t.id};});});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 rearrangeNewId='sec_dup_'+Date.now()+'_'+Math.random().toString(36).substr(2,5);updatedSections.push({...sec,id:rearrangeNewId,name:sec.name+' (Copy)'});newOriginals[rearrangeNewId]={type:'section',start:sec.start};}}else if(info.type==='midiItem'){var mid=(t.midiItems||[]).find(function(m){return m.id===oid;});if(mid){var rearrangeNewId='midi_dup_'+Date.now()+'_'+Math.random().toString(36).substr(2,5);updatedMidi.push({...mid,id:rearrangeNewId,name:mid.name+' (Copy)'});newOriginals[rearrangeNewId]={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 rearrangeNewId='clip_dup_'+Date.now()+'_'+Math.random().toString(36).substr(2,5);updatedClips.push({...clip,id:rearrangeNewId,startTime:clip.startTime,name:clip.name+' (Copy)'});newOriginals[rearrangeNewId]={type:'clip',start:clip.startTime};}}});return{...t,sections:updatedSections,midiItems:updatedMidi,clips:updatedClips};});});var newItemId=Object.keys(newOriginals)[0]||itemId;setDraggedSectionItem({trackId,itemType,itemId:newItemId,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 rearrangeNewId=itemType+'_dup_'+Date.now();var newItem={...item,id:rearrangeNewId,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:rearrangeNewId,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 ── useEffect(()=>{const handleMouseMove=e=>{const drag=draggedSectionItemRef.current;if(!drag)return;const wrapper=timelineWrapperRef.current;if(!wrapper)return;autoScrollTimeline(e.clientX);const rect=wrapper.getBoundingClientRect();const scrollLeft=wrapper.scrollLeft;const mouseX=e.clientX-rect.left+scrollLeft;const time=Math.max(0,mouseX/zoom-leadInMarginRef.current);const beatSec=60.0/(parseInt(bpm)||120);const secondsPerBar=beatSec*4;const marginBar=maxDurationRef.current-secondsPerBar;const rawStart=Math.max(0,Math.min(time-drag.clickOffset,marginBar));const newStart=snapTime(rawStart,snapValueRef.current,bpmRef.current);const itemPx=newStart*zoom;const keepMargin=80;if(itemPx>scrollLeft+rect.width-keepMargin){wrapper.scrollLeft=itemPx-rect.width+keepMargin;setCanvasRedrawCount(n=>n+1);}else if(itemPxn+1);}updateActiveTracks(prev=>{// Compute target track from mouse Y position relative to timeline wrapper -var trkIds=prev.map(function(tr){return tr.id;});var baseIdx=trkIds.indexOf(drag.trackId);if(baseIdx<0)baseIdx=0;var trackTop=0;var targetIdx=-1;var mouseY=e.clientY-rect.top;for(var ti=0;ti=trackTop&&mouseYit.id===drag.itemId);const mid=(track.midiItems||[]).find(it=>it.id===drag.itemId);if(sec||mid){movedItem=sec||mid;break;}}if(drag.multiIds){var dragOrigStart=drag.multiIds[drag.itemId]?drag.multiIds[drag.itemId].start:0;var delta=newStart-dragOrigStart;return prev.map(function(t){var resS=(t.sections||[]).slice();var resM=(t.midiItems||[]).slice();var resC=(t.clips||[]).slice();Object.keys(drag.multiIds).forEach(function(mid){var inf=drag.multiIds[mid];var newVal=inf.start+delta;// Use ORIGINAL trackId stored at drag start for track offset math +var trkIds=prev.map(function(tr){return tr.id;});var baseIdx=trkIds.indexOf(drag.trackId);if(baseIdx<0)baseIdx=0;var trackTop=0;var targetIdx=-1;var mouseY=e.clientY-rect.top;for(var ti=0;ti=trackTop&&mouseYit.id===drag.itemId);const mid=(track.midiItems||[]).find(it=>it.id===drag.itemId);if(sec||mid){movedItem=sec||mid;break;}}if(drag.multiIds){var dragOrigStart=drag.multiIds[drag.itemId]?drag.multiIds[drag.itemId].start:0;var delta=newStart-dragOrigStart;// Ensure enough tracks exist for all displaced items +var maxTrg=0;Object.keys(drag.multiIds).forEach(function(mid){var inf=drag.multiIds[mid];var origIdx=trkIds.indexOf(inf.trackId);if(origIdx<0)origIdx=baseIdx;var trg=Math.max(0,origIdx+crossOffset);if(trg>maxTrg)maxTrg=trg;});var tracksToAdd=[];while(prev.length<=maxTrg){var newId=(prev.length+tracksToAdd.length+1).toString();var colors=['#0f766e','#1d4ed8','#701a75','#a21caf','#b45309'];tracksToAdd.push({id:newId,name:'Track '+newId,buffer:null,startTime:0,volumeDb:0,pan:0,muted:false,solo:false,color:colors[(prev.length+tracksToAdd.length)%colors.length],clips:[],midiItems:[],sections:[],markers:[],isArmed:false,height:140});}if(tracksToAdd.length>0)prev=prev.concat(tracksToAdd);return prev.map(function(t){var resS=(t.sections||[]).slice();var resM=(t.midiItems||[]).slice();var resC=(t.clips||[]).slice();Object.keys(drag.multiIds).forEach(function(mid){var inf=drag.multiIds[mid];var newVal=inf.start+delta;// Use ORIGINAL trackId stored at drag start for track offset math var origTid=inf.trackId;var origIdx=trkIds.indexOf(origTid);if(origIdx<0)origIdx=baseIdx;var itemTrgIdx=Math.max(0,origIdx+crossOffset);var itemTid=itemTrgIdx{const drag=draggedSectionItemRef.current;if(!drag)return;setDraggedSectionItem(null);showToast(`Đã di chuyển ${drag.itemType==='section'?'section':'MIDI item'}.`,'success');};document.addEventListener('mousemove',handleMouseMove);document.addEventListener('mouseup',handleMouseUp);return()=>{document.removeEventListener('mousemove',handleMouseMove);document.removeEventListener('mouseup',handleMouseUp);};},[zoom,activeTab,sessionTabs]);// ── Pending drag: Ctrl+click toggles selection; mousemove > threshold starts copy-drag ── useEffect(()=>{const handleMouseMove=e=>{var pd=pendingDragRef.current;if(!pd)return;var dx=e.clientX-pd.startX;if(Math.abs(dx)>5){var pdSnap=pendingDragRef.current;pendingDragRef.current=null;if(handleSectionItemDragStartRef.current)handleSectionItemDragStartRef.current(pdSnap.trackId,pdSnap.itemType,pdSnap.itemId,pdSnap.clickOffset,true,pdSnap.selectedIds);}};document.addEventListener('mousemove',handleMouseMove);var handleMouseUp=function(){pendingDragRef.current=null;};document.addEventListener('mouseup',handleMouseUp);return function(){document.removeEventListener('mousemove',handleMouseMove);document.removeEventListener('mouseup',handleMouseUp);pendingDragRef.current=null;};},[]);// ── Document-level mousemove/mouseup for Section/MIDI item resize ── diff --git a/wiki.md b/wiki.md index c9bf518..017dc93 100644 --- a/wiki.md +++ b/wiki.md @@ -766,3 +766,9 @@ - **Các file ảnh hưởng:** `app/static/js/app.jsx` - **Ghi chú/Test (nếu có):** `npm run build` — build passes. --- + +### [2026-07-29 11:07] Task: Auto-add tracks when drag exceeds existing track count +- **Tóm tắt thay đổi:** Tính `maxTrg` từ tất cả items trong multiIds. Nếu `prev.length <= maxTrg`, tự động thêm track mới vào `prev` trước khi xử lí. Item track2 cần xuống track3 → auto-add track3. +- **Các file ảnh hưởng:** `app/static/js/app.jsx` +- **Ghi chú/Test (nếu có):** `npm run build` — build passes. +---