From 4b732844c9a4d646ee13189a857cd909f281f0f9 Mon Sep 17 00:00:00 2001 From: 3dtours Date: Mon, 27 Jul 2026 21:38:10 +0700 Subject: [PATCH] =?UTF-8?q?fix:=20s=E1=BB=ADa=20l=E1=BB=97i=20hi=E1=BB=83n?= =?UTF-8?q?=20th=E1=BB=8B=20icon=20tr=C6=B0=E1=BB=9Bc=20menu=20item=20?= =?UTF-8?q?=E1=BB=9F=20menu=20context=20trong=20MAIN=20SESSION?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/static/js/app.jsx | 16 +++++++++++++--- app/static/js/app.precompiled.js | 2 +- app/storage/sonicforge.db | Bin 110592 -> 110592 bytes 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/app/static/js/app.jsx b/app/static/js/app.jsx index a9eea04..cd7db01 100644 --- a/app/static/js/app.jsx +++ b/app/static/js/app.jsx @@ -11135,9 +11135,19 @@ const App = () => { e.preventDefault(); e.stopPropagation(); clearLocalSelection(); - setSelectionMode(null); - setSelectionStart(null); - setSelectionEnd(null); + const wrapper = timelineWrapperRef.current; + if (wrapper) { + const rect = wrapper.getBoundingClientRect(); + const scrollLeft = wrapper.scrollLeft; + const rawTime = Math.max(0, (e.clientX - rect.left + scrollLeft) / zoom - leadInMargin); + const time = snapValue !== 'free' ? snapTime(rawTime, snapValue, bpm) : rawTime; + rulerDragStartRef.current = time; + rulerAnchorRef.current = time; + isDraggingRulerRef.current = true; + setSelectionMode('global'); + setSelectionStart(time); + setSelectionEnd(time); + } return; } const wrapper = timelineWrapperRef.current; diff --git a/app/static/js/app.precompiled.js b/app/static/js/app.precompiled.js index 3649a83..22b5956 100644 --- a/app/static/js/app.precompiled.js +++ b/app/static/js/app.precompiled.js @@ -313,7 +313,7 @@ const activePianoRoll=subTabs.find(s=>s.id===activeTab&&s.type==='PIANO_ROLL'&&s console.log(`[DevLog] [MIDI Rec Sync] Temp Item ID: ${midiRec.tempMidiItemId}, Duration: ${durationSec.toFixed(2)}s, ActiveNotes: ${midiRec.activeNotes.size}, RecordedNotes: ${midiRec.recordedNotes.length}`);}setRecTempMidiNotes(allNotes);setCanvasRedrawCount(n=>n+1);updateActiveTracks(prev=>prev.map(t=>{if(t.id!==trackId)return t;return{...t,midiItems:t.midiItems.map(m=>m.id===midiRec.tempMidiItemId?{...m,notes:allNotes,duration:durationSec}:m)};}));}},100);showToast('Đang ghi âm...','info');};const stopRecordingTake=async()=>{setRecordingState('IDLE');stopAllPlayback();const context=getAudioContext();const secondsPerBeat=60.0/(parseInt(bpm)||120);const secondsPerBar=secondsPerBeat*4;const midiRecorders=activeMIDIRecordersRef.current;const audioRecorders=activeAudioRecordersRef.current;Object.keys(trackVuRefs.current).forEach(tid=>{const canvas=trackVuRefs.current[tid];if(canvas)drawVuMeter(canvas,-60);});let hasRecordedAnything=false;for(let trackId in midiRecorders){const midiRec=midiRecorders[trackId];if(midiRec.tempTabId){const recordedNotes=midiRec.stop();if(recordedNotes.length>0){const newNotes=recordedNotes.map((n,i)=>({id:'rec_'+Date.now()+'_'+i,pitch:n.pitch,start_beat:Math.max(0,n.start_beat||0),duration_beats:Math.max(0.125,n.duration_beats||0.25),velocity:Math.min(1,n.velocity||0.8),pan:0.0}));setSubTabs(prev=>prev.map(s=>s.id===midiRec.tempTabId?{...s,notes:[...(s.notes||[]),...newNotes],isDirty:true}:s));setCanvasRedrawCount(n=>n+1);showToast(`Đã ghi ${recordedNotes.length} notes vào Piano Roll.`,'success');}hasRecordedAnything=true;}else if(midiRec.tempMidiItemId){const recCurrentTimeSec=Math.max(0,context.currentTime-midiRec.recStartAudioTime-midiRec.latencyCompSec);const recElapsedBeats=recCurrentTimeSec/(60.0/midiRec.bpm);const totalDurationBeats=Math.max(4.0,recordedNotes.length>0?Math.max(recElapsedBeats,...recordedNotes.map(n=>n.start_beat+n.duration_beats)):recElapsedBeats);updateActiveTracks(prev=>prev.map(t=>{if(t.id!==trackId)return t;const itemIndex=(t.midiItems||[]).findIndex(m=>m.id===midiRec.tempMidiItemId);if(itemIndex>=0){const updatedItems=[...t.midiItems];updatedItems[itemIndex]={...updatedItems[itemIndex],name:recordedNotes.length>0?'Recorded MIDI':'Empty MIDI',notes:recordedNotes,duration:Math.ceil(totalDurationBeats/4)*secondsPerBar};return{...t,midiItems:updatedItems};}const newMidiItem={id:'midi_rec_'+Date.now(),name:'Recorded MIDI',parent_track_id:t.id,startTime:recordingStartTimeRef.current,duration:Math.ceil(totalDurationBeats/4)*secondsPerBar,length_bars:Math.ceil(totalDurationBeats/4),notes:recordedNotes};return{...t,midiItems:[...(t.midiItems||[]),newMidiItem]};}));if(recordedNotes.length>0){hasRecordedAnything=true;}}else if(recordedNotes.length>0){hasRecordedAnything=true;const totalDurationBeats=Math.max(4.0,...recordedNotes.map(n=>n.start_beat+n.duration_beats));const newMidiItem={id:'midi_rec_'+Date.now(),name:'Recorded MIDI',parent_track_id:trackId,startTime:recordingStartTimeRef.current,duration:Math.ceil(totalDurationBeats/4)*secondsPerBar,length_bars:Math.ceil(totalDurationBeats/4),notes:recordedNotes};updateActiveTracks(prev=>prev.map(t=>{if(t.id!==trackId)return t;return{...t,midiItems:[...(t.midiItems||[]),newMidiItem]};}));}}for(let trackId in audioRecorders){const audioRec=audioRecorders[trackId];const audioBuffer=await audioRec.stop();if(audioBuffer&&audioBuffer.duration>0.05){hasRecordedAnything=true;const newClip={id:'clip_rec_'+Date.now(),name:'Recorded Audio.wav',buffer:audioBuffer,startTime:recordingStartTimeRef.current,speed:1.0};updateActiveTracks(prev=>prev.map(t=>{if(t.id!==trackId)return t;const updatedClips=[...(t.clips||[]),newClip];return{...t,clips:updatedClips,buffer:updatedClips[0]?.buffer||null,startTime:updatedClips[0]?.startTime||0,name:updatedClips[0]?.name||t.name};}));}}if(recordingSyncRef.current){clearInterval(recordingSyncRef.current);recordingSyncRef.current=null;}activeMIDIRecordersRef.current={};activeAudioRecordersRef.current={};recordingPCMDataRef.current={};setRecTempMidiNotes([]);setRecTempAudioBuffer(null);if(hasRecordedAnything){showToast('Đã thu và lưu bản ghi vào timeline.','success');}else{showToast('Đã dừng ghi âm (không phát hiện tín hiệu đầu vào).','info');}};const handleSubTabResizeMouseDown=e=>{e.preventDefault();e.stopPropagation();const startY=e.clientY;const startHeight=subTabHeight;const handleMouseMove=moveEvent=>{const deltaY=moveEvent.clientY-startY;const newHeight=Math.max(48,Math.min(400,startHeight+deltaY));setSubTabHeight(newHeight);};const handleMouseUp=()=>{document.removeEventListener('mousemove',handleMouseMove);document.removeEventListener('mouseup',handleMouseUp);};document.addEventListener('mousemove',handleMouseMove);document.addEventListener('mouseup',handleMouseUp);};// ── Playhead set with seek+play ── const handlePlayheadSet=(time,shiftKey)=>{localSelectionAnchorRef.current=time;if(isPlaying){// Click during playback: seek to position and continue playing setCurrentTime(time);stopAllPlayback();setTimeout(()=>{startOffsetTimeRef.current=time;startAudioTimeRef.current=getAudioContext().currentTime;startTrackPlayback(time);setIsPlaying(true);},50);}else{// Normal click: just set playhead -setCurrentTime(time);}};const clearLocalSelection=()=>{setSelectionMode(null);setLocalSelectionTrackId(null);setLocalSelectionStart(null);setLocalSelectionEnd(null);};const handleRulerMouseDown=e=>{if(e.ctrlKey){e.preventDefault();e.stopPropagation();clearLocalSelection();setSelectionMode(null);setSelectionStart(null);setSelectionEnd(null);return;}const wrapper=timelineWrapperRef.current;if(!wrapper)return;const rect=wrapper.getBoundingClientRect();const scrollLeft=wrapper.scrollLeft;const mouseX=e.clientX-rect.left+scrollLeft;const rawTime=Math.max(0,mouseX/zoom-leadInMargin);const time=snapValue!=='free'?snapTime(rawTime,snapValue,bpm):rawTime;clearLocalSelection();setSelectionMode('global');rulerDragStartRef.current=time;isDraggingRulerRef.current=true;if(e.shiftKey){e.preventDefault();e.stopPropagation();// Shift+click on ruler: lock existing anchor (or currentTime fallback) and extend global selection +setCurrentTime(time);}};const clearLocalSelection=()=>{setSelectionMode(null);setLocalSelectionTrackId(null);setLocalSelectionStart(null);setLocalSelectionEnd(null);};const handleRulerMouseDown=e=>{if(e.ctrlKey){e.preventDefault();e.stopPropagation();clearLocalSelection();const wrapper=timelineWrapperRef.current;if(wrapper){const rect=wrapper.getBoundingClientRect();const scrollLeft=wrapper.scrollLeft;const rawTime=Math.max(0,(e.clientX-rect.left+scrollLeft)/zoom-leadInMargin);const time=snapValue!=='free'?snapTime(rawTime,snapValue,bpm):rawTime;rulerDragStartRef.current=time;rulerAnchorRef.current=time;isDraggingRulerRef.current=true;setSelectionMode('global');setSelectionStart(time);setSelectionEnd(time);}return;}const wrapper=timelineWrapperRef.current;if(!wrapper)return;const rect=wrapper.getBoundingClientRect();const scrollLeft=wrapper.scrollLeft;const mouseX=e.clientX-rect.left+scrollLeft;const rawTime=Math.max(0,mouseX/zoom-leadInMargin);const time=snapValue!=='free'?snapTime(rawTime,snapValue,bpm):rawTime;clearLocalSelection();setSelectionMode('global');rulerDragStartRef.current=time;isDraggingRulerRef.current=true;if(e.shiftKey){e.preventDefault();e.stopPropagation();// Shift+click on ruler: lock existing anchor (or currentTime fallback) and extend global selection const anchor=rulerAnchorRef.current!==null&&rulerAnchorRef.current!==undefined?rulerAnchorRef.current:selectionStart!==null&&selectionStart!==undefined?selectionStart:currentTime;const selS=Math.max(0,Math.min(anchor,time));const selE=Math.max(0,Math.max(anchor,time));setSelectionStart(selS);setSelectionEnd(selE);}else{rulerAnchorRef.current=Math.max(0,time);}handlePlayheadSet(time);};// Global Ruler mousemove is tracked via document listener set up in useEffect useEffect(()=>{const handleMouseMove=e=>{if(!isDraggingRulerRef.current)return;const wrapper=timelineWrapperRef.current;if(!wrapper)return;const rect=wrapper.getBoundingClientRect();const scrollLeft=wrapper.scrollLeft;const mouseX=e.clientX-rect.left+scrollLeft;const rawTime=Math.max(0,Math.min(maxDuration,mouseX/zoom-leadInMarginRef.current));const time=snapValueRef.current!=='free'?snapTime(rawTime,snapValueRef.current,bpmRef.current):rawTime;const anchor=rulerAnchorRef.current??rulerDragStartRef.current??time;setSelectionStart(Math.min(anchor,time));setSelectionEnd(Math.max(anchor,time));};const handleMouseUp=()=>{if(isDraggingRulerRef.current){isDraggingRulerRef.current=false;rulerDragStartRef.current=null;}};document.addEventListener('mousemove',handleMouseMove);document.addEventListener('mouseup',handleMouseUp);// Sub-tab ruler drag (section/editor tabs) const handleSubTabMove=e=>{if(!isDraggingSubTabRef.current)return;const wrapper=timelineWrapperRef.current;if(!wrapper)return;const rect=wrapper.getBoundingClientRect();const sl=wrapper.scrollLeft;const raw=Math.max(0,(e.clientX-rect.left+sl)/zoom);const time=snapValueRef.current!=='free'?snapTime(raw,snapValueRef.current,bpmRef.current):raw;const anchor=subTabDragStartRef.current??0;setSubTabs(prev=>prev.map(s=>s.id===activeTabRef.current?{...s,selectionStart:Math.min(anchor,time),selectionEnd:Math.max(anchor,time)}:s));};const handleSubTabUp=()=>{if(isDraggingSubTabRef.current){isDraggingSubTabRef.current=false;subTabDragStartRef.current=null;}};document.addEventListener('mousemove',handleSubTabMove);document.addEventListener('mouseup',handleSubTabUp);return()=>{document.removeEventListener('mousemove',handleMouseMove);document.removeEventListener('mouseup',handleMouseUp);document.removeEventListener('mousemove',handleSubTabMove);document.removeEventListener('mouseup',handleSubTabUp);};},[zoom,maxDuration]);// ── Track Lane Local Selection Drag ── diff --git a/app/storage/sonicforge.db b/app/storage/sonicforge.db index 3c50c91db21f9bea02cd2368beb15ce670a73822..9ae644d535a4f72fe3704e2c0d792f03cb3253e7 100644 GIT binary patch delta 216 zcmZp8z}E19ZGtrKOa=x9X&@E{V(y6==8Q8pCM;dg%JqnW>(ORGfg4<$UD8aP@uBvU z0yl^6SjRYhjvk}?biE0T+Kh&i3wMcd26zTIDue{N`c7B$W;B`}JCRXv`ql}I8jOb1 z?@eIL1yVi}8Fi=2w=k+tU#bt(>lekS!e|82%N~%Kn3u0$4AN;lt(DObs2`}uWIBI1 zqY+R^!fv(c7yKF3xL6iB-kN#aV&NUT#f%RGHeFz!z{oY7BY{zwRSM+%?FI>qm*qDL IJou{*0MWlh1poj5 literal 110592 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