From 393df8fd7be94100ce5b935f283a385227fb3cfa Mon Sep 17 00:00:00 2001 From: 3dtours Date: Fri, 7 Aug 2026 21:48:14 +0700 Subject: [PATCH] =?UTF-8?q?FIX:=20cho=20ph=C3=A9p=20shift+click=20=C4=91?= =?UTF-8?q?=E1=BB=83=20ch=E1=BB=8Dn=20notes,=20drag=20=C4=91=E1=BB=83=20di?= =?UTF-8?q?=20chuy=E1=BB=83n=20c=C3=A1c=20notes,=20ctrl+drag=20=C4=91?= =?UTF-8?q?=E1=BB=83=20copy=20nhanh=20c=C3=A1c=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/static/js/app.jsx | 34 ++++++++++++++++++++++++++++++-- app/static/js/app.precompiled.js | 7 +++++-- app/templates/index.html | 2 +- wiki.md | 17 ++++++++++++++++ 4 files changed, 55 insertions(+), 5 deletions(-) diff --git a/app/static/js/app.jsx b/app/static/js/app.jsx index 53bb202..bc8e260 100644 --- a/app/static/js/app.jsx +++ b/app/static/js/app.jsx @@ -7762,8 +7762,9 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, activeTracks, onClos } } - // Ctrl+click: toggle selection (multi-select) - if (e.ctrlKey && !e.altKey && !e.shiftKey) { + // Shift+click: toggle selection (multi-select) — user 09:30 (trước đây là + // Ctrl+click — Ctrl giờ dành cho COPY-drag) + if (e.shiftKey && !e.altKey && !e.ctrlKey) { if (clickedNoteIdx !== -1) { const clickedNote = notes[clickedNoteIdx]; if (selectedNoteIds.includes(clickedNote.id)) { @@ -7772,6 +7773,35 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, activeTracks, onClos setSelectedNoteIds(prev => [...prev, clickedNote.id]); } return; + } + } + + // Ctrl+click NOTE + drag → COPY nhanh nhóm notes/note đến vị trí mới + // (cùng pitch ban đầu; drag đổi vị trí + pitch) — user 09:30 + if (e.ctrlKey && !e.altKey && !e.shiftKey) { + if (clickedNoteIdx !== -1) { + const clickedNote = notes[clickedNoteIdx]; + const groupIds = (selectedNoteIds.includes(clickedNote.id) && selectedNoteIds.length > 1) + ? selectedNoteIds : [clickedNote.id]; + const src = notes.filter(n => groupIds.includes(n.id)); + if (!src.length) return; + pushToUndo(notes); + notesBeforeDragRef.current = JSON.parse(JSON.stringify(notes)); + const clones = src.map(n => ({ ...JSON.parse(JSON.stringify(n)), id: 'note_' + Date.now() + Math.random().toString(36).substr(2, 8) })); + setNotes(prev => [...prev, ...clones]); + const cloneIds = clones.map(c => c.id); + setSelectedNoteIds(cloneIds); + const cloneOffsets = clones.map(n => ({ id: n.id, originalStartBeat: n.start_beat, originalPitch: n.pitch })); + setDraggedNote({ + mode: 'move', + idx: -1, + startOffsetBeat: beat - clickedNote.start_beat, + startOffsetPitch: pitch, + selectedNotesOffset: cloneOffsets, + clickedOriginalStartBeat: clickedNote.start_beat + }); + showToast('Kéo để copy ' + clones.length + ' nốt.', 'info'); + return; } else { // Ctrl+click on a dim (same-track) MIDI note → select it and focus its MIDI item var ctrlGhostHit = null; diff --git a/app/static/js/app.precompiled.js b/app/static/js/app.precompiled.js index 4c2a241..d9cf322 100644 --- a/app/static/js/app.precompiled.js +++ b/app/static/js/app.precompiled.js @@ -435,8 +435,11 @@ React.useEffect(function(){setFocusItemId(st.target_id);},[st.id,st.target_id]); if(e.button===2){e.preventDefault();const clickedNote=notes.find(n=>{return pitch===n.pitch&&beat>=n.start_beat&&beatprev.filter(n=>n.id!==clickedNote.id));setSelectedNoteIds(prev=>prev.filter(id=>id!==clickedNote.id));swallowContextMenuRef.current=true;showToast('Đã xóa nốt!','info');}else{rightClickDragRef.current={active:true,startX:e.clientX,startY:e.clientY};}return;}if(e.button!==0)return;// Only handle left click // Check if clicking on an existing note const clickedNoteIdx=notes.findIndex(n=>{return pitch===n.pitch&&beat>=n.start_beat&&beatprev.filter(id=>id!==clickedNote.id));}else{setSelectedNoteIds(prev=>[...prev,clickedNote.id]);}return;}else{// Ctrl+click on a dim (same-track) MIDI note → select it and focus its MIDI item +if(clickedNoteIdx!==-1&&!e.ctrlKey&&!e.shiftKey&&!e.altKey){if(window.SonicSF){const ctx=getAudioContext();var clTrk=activeTracks.find(function(t){return t.id===st.trackId;});var clCtx=resolveTrackInstrumentCtx(clTrk,activeTracks);ensureSonicInstrument(clCtx);window.SonicSF.playNote(notes[clickedNoteIdx].pitch,100,300,ctx.currentTime,clCtx.program,null,clCtx.ch,clCtx.synthEngine);}}// Shift+click: toggle selection (multi-select) — user 09:30 (trước đây là +// Ctrl+click — Ctrl giờ dành cho COPY-drag) +if(e.shiftKey&&!e.altKey&&!e.ctrlKey){if(clickedNoteIdx!==-1){const clickedNote=notes[clickedNoteIdx];if(selectedNoteIds.includes(clickedNote.id)){setSelectedNoteIds(prev=>prev.filter(id=>id!==clickedNote.id));}else{setSelectedNoteIds(prev=>[...prev,clickedNote.id]);}return;}}// Ctrl+click NOTE + drag → COPY nhanh nhóm notes/note đến vị trí mới +// (cùng pitch ban đầu; drag đổi vị trí + pitch) — user 09:30 +if(e.ctrlKey&&!e.altKey&&!e.shiftKey){if(clickedNoteIdx!==-1){const clickedNote=notes[clickedNoteIdx];const groupIds=selectedNoteIds.includes(clickedNote.id)&&selectedNoteIds.length>1?selectedNoteIds:[clickedNote.id];const src=notes.filter(n=>groupIds.includes(n.id));if(!src.length)return;pushToUndo(notes);notesBeforeDragRef.current=JSON.parse(JSON.stringify(notes));const clones=src.map(n=>({...JSON.parse(JSON.stringify(n)),id:'note_'+Date.now()+Math.random().toString(36).substr(2,8)}));setNotes(prev=>[...prev,...clones]);const cloneIds=clones.map(c=>c.id);setSelectedNoteIds(cloneIds);const cloneOffsets=clones.map(n=>({id:n.id,originalStartBeat:n.start_beat,originalPitch:n.pitch}));setDraggedNote({mode:'move',idx:-1,startOffsetBeat:beat-clickedNote.start_beat,startOffsetPitch:pitch,selectedNotesOffset:cloneOffsets,clickedOriginalStartBeat:clickedNote.start_beat});showToast('Kéo để copy '+clones.length+' nốt.','info');return;}else{// Ctrl+click on a dim (same-track) MIDI note → select it and focus its MIDI item var ctrlGhostHit=null;for(var cgi=0;cgi=cgnote.relative_start_beat&&beatprev.filter(id=>id!==ctrlGhostHit.id));}else{setSelectedNoteIds(prev=>[...prev,ctrlGhostHit.id]);}return;}// Ctrl+click on empty space: start selection marquee setSelectedNoteIds([]);const snapStart=getSnapBeat(beat,snapValue);setSelectionMarquee({startBeat:snapStart,startPitch:pitch,currentBeat:snapStart,currentPitch:pitch});return;}}// Ctrl+Shift+click on note → split at click position if(e.ctrlKey&&e.shiftKey){if(clickedNoteIdx!==-1){const target=notes[clickedNoteIdx];const splitBeat=getSnapBeat(beat,snapValue);if(splitBeat>target.start_beat+0.03125&&splitBeat{const idx=prev.findIndex(n=>n.id===target.id);if(idx===-1)return prev;const result=[...prev];result.splice(idx,1,noteA);result.splice(idx+1,0,noteB);return result;});setSelectedNoteIds([noteA.id,noteB.id]);showToast('Đã tách nốt!','info');}}else{// Ctrl+Shift+click on empty space → duplicate selected + clicked notes diff --git a/app/templates/index.html b/app/templates/index.html index df23721..747b4c7 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -24,7 +24,7 @@ - +