fix: auto-scroll continuous + batch CC velocity for selected notes
This commit is contained in:
+52
-8
@@ -5243,9 +5243,30 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, onClose, onUpdateNot
|
|||||||
const edgeThreshold = 30;
|
const edgeThreshold = 30;
|
||||||
const scrollStep = 6;
|
const scrollStep = 6;
|
||||||
if (e.clientY < cr.top + edgeThreshold) {
|
if (e.clientY < cr.top + edgeThreshold) {
|
||||||
container.scrollTop = Math.max(0, container.scrollTop - scrollStep);
|
if (!brushAutoScrollRef.current || brushAutoScrollRef.current.direction !== 'up') {
|
||||||
|
if (brushAutoScrollRef.current) clearInterval(brushAutoScrollRef.current.id);
|
||||||
|
brushAutoScrollRef.current = {
|
||||||
|
direction: 'up',
|
||||||
|
id: setInterval(() => {
|
||||||
|
if (gridScrollRef.current) gridScrollRef.current.scrollTop = Math.max(0, gridScrollRef.current.scrollTop - scrollStep);
|
||||||
|
}, 30)
|
||||||
|
};
|
||||||
|
}
|
||||||
} else if (e.clientY > cr.bottom - edgeThreshold) {
|
} else if (e.clientY > cr.bottom - edgeThreshold) {
|
||||||
container.scrollTop = Math.min(container.scrollHeight - container.clientHeight, container.scrollTop + scrollStep);
|
if (!brushAutoScrollRef.current || brushAutoScrollRef.current.direction !== 'down') {
|
||||||
|
if (brushAutoScrollRef.current) clearInterval(brushAutoScrollRef.current.id);
|
||||||
|
brushAutoScrollRef.current = {
|
||||||
|
direction: 'down',
|
||||||
|
id: setInterval(() => {
|
||||||
|
if (gridScrollRef.current) gridScrollRef.current.scrollTop = Math.min(gridScrollRef.current.scrollHeight - gridScrollRef.current.clientHeight, gridScrollRef.current.scrollTop + scrollStep);
|
||||||
|
}, 30)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (brushAutoScrollRef.current) {
|
||||||
|
clearInterval(brushAutoScrollRef.current.id);
|
||||||
|
brushAutoScrollRef.current = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -5322,6 +5343,10 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, onClose, onUpdateNot
|
|||||||
setDraggedNote(null);
|
setDraggedNote(null);
|
||||||
setSelectionMarquee(null);
|
setSelectionMarquee(null);
|
||||||
rightClickDragRef.current = { active: false, startX: 0, startY: 0 };
|
rightClickDragRef.current = { active: false, startX: 0, startY: 0 };
|
||||||
|
if (brushAutoScrollRef.current) {
|
||||||
|
clearInterval(brushAutoScrollRef.current.id);
|
||||||
|
brushAutoScrollRef.current = null;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleContextMenu = (e) => {
|
const handleContextMenu = (e) => {
|
||||||
@@ -5336,6 +5361,7 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, onClose, onUpdateNot
|
|||||||
};
|
};
|
||||||
|
|
||||||
const ccDragRef = React.useRef(null);
|
const ccDragRef = React.useRef(null);
|
||||||
|
const brushAutoScrollRef = React.useRef(null);
|
||||||
const rightClickDragRef = React.useRef({ active: false, startX: 0, startY: 0 });
|
const rightClickDragRef = React.useRef({ active: false, startX: 0, startY: 0 });
|
||||||
const swallowContextMenuRef = React.useRef(false);
|
const swallowContextMenuRef = React.useRef(false);
|
||||||
|
|
||||||
@@ -5373,10 +5399,21 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, onClose, onUpdateNot
|
|||||||
|
|
||||||
if (e.ctrlKey) {
|
if (e.ctrlKey) {
|
||||||
if (selectedNoteIds.length > 0) {
|
if (selectedNoteIds.length > 0) {
|
||||||
|
const updates = {};
|
||||||
selectedNoteIds.forEach(id => {
|
selectedNoteIds.forEach(id => {
|
||||||
const idx = notes.findIndex(n => n.id === id);
|
const idx = notes.findIndex(n => n.id === id);
|
||||||
if (idx !== -1) paintNote(idx, val);
|
if (idx !== -1) updates[idx] = val;
|
||||||
});
|
});
|
||||||
|
const idxs = Object.keys(updates).map(Number);
|
||||||
|
if (idxs.length > 0) {
|
||||||
|
setNotes(prev => prev.map((n, i) => {
|
||||||
|
if (i in updates) {
|
||||||
|
if (ccMode === 'pan') return { ...n, pan: (updates[i] - 0.5) * 2.0 };
|
||||||
|
return { ...n, velocity: updates[i] };
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}));
|
||||||
|
}
|
||||||
ccDragRef.current = { active: true, lastBeat: beat, selectedMode: true, lastPainted: selectedNoteIds.map(id => notes.findIndex(n => n.id === id)).filter(i => i !== -1) };
|
ccDragRef.current = { active: true, lastBeat: beat, selectedMode: true, lastPainted: selectedNoteIds.map(id => notes.findIndex(n => n.id === id)).filter(i => i !== -1) };
|
||||||
} else {
|
} else {
|
||||||
if (noteIdx !== -1) paintNote(noteIdx, val);
|
if (noteIdx !== -1) paintNote(noteIdx, val);
|
||||||
@@ -5403,16 +5440,23 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, onClose, onUpdateNot
|
|||||||
const painted = drag.lastPainted || [];
|
const painted = drag.lastPainted || [];
|
||||||
|
|
||||||
if (drag.selectedMode && selectedNoteIds.length > 0) {
|
if (drag.selectedMode && selectedNoteIds.length > 0) {
|
||||||
|
const updates = {};
|
||||||
selectedNoteIds.forEach(id => {
|
selectedNoteIds.forEach(id => {
|
||||||
const idx = notes.findIndex(n => n.id === id);
|
const idx = notes.findIndex(n => n.id === id);
|
||||||
if (idx !== -1 && !painted.includes(idx)) {
|
if (idx !== -1 && !painted.includes(idx)) {
|
||||||
setNotes(prev => prev.map((n, i) => {
|
updates[idx] = val;
|
||||||
if (i !== idx) return n;
|
|
||||||
if (ccMode === 'pan') return { ...n, pan: (val - 0.5) * 2.0 };
|
|
||||||
return { ...n, velocity: val };
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
const idxs = Object.keys(updates).map(Number);
|
||||||
|
if (idxs.length > 0) {
|
||||||
|
setNotes(prev => prev.map((n, i) => {
|
||||||
|
if (i in updates) {
|
||||||
|
if (ccMode === 'pan') return { ...n, pan: (updates[i] - 0.5) * 2.0 };
|
||||||
|
return { ...n, velocity: updates[i] };
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}));
|
||||||
|
}
|
||||||
const newPainted = selectedNoteIds
|
const newPainted = selectedNoteIds
|
||||||
.map(id => notes.findIndex(n => n.id === id))
|
.map(id => notes.findIndex(n => n.id === id))
|
||||||
.filter(i => i !== -1 && !painted.includes(i));
|
.filter(i => i !== -1 && !painted.includes(i));
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -13,7 +13,13 @@
|
|||||||
### [2026-07-26 09:36] Task: Piano Roll 4 features (SNAP, velocity selected, auto-scroll, Synth button)
|
### [2026-07-26 09:36] Task: Piano Roll 4 features (SNAP, velocity selected, auto-scroll, Synth button)
|
||||||
- **Tóm tắt thay đổi:** (1) SNAP trong MIDI tab — grid snap cho note drawing, selection marquee, loop range. (2) Ctrl+drag velocity — selected notes màu xanh dương, affect only selected; no selection = paint all. (3) Auto-scroll Piano Roll khi brush-drag gần cạnh top/bottom. (4) Synth button trong MIDI tab toolbar — mở instrument selector khi nhấn. Layout fix: thêm `h-full` cho outer div.
|
- **Tóm tắt thay đổi:** (1) SNAP trong MIDI tab — grid snap cho note drawing, selection marquee, loop range. (2) Ctrl+drag velocity — selected notes màu xanh dương, affect only selected; no selection = paint all. (3) Auto-scroll Piano Roll khi brush-drag gần cạnh top/bottom. (4) Synth button trong MIDI tab toolbar — mở instrument selector khi nhấn. Layout fix: thêm `h-full` cho outer div.
|
||||||
- **Các file ảnh hưởng:** `app/static/js/app.jsx`
|
- **Các file ảnh hưởng:** `app/static/js/app.jsx`
|
||||||
- **Ghi chú/Test (nếu có):** Build báo lỗi pre-existing tại return statement (original code cũng lỗi tương tự). Các thay đổi logic đã verified qua diff.
|
- **Ghi chú/Test (nếu có):** `npm run build` pass. Fix thiếu 1 `)` tại line 5959 return statement (gây build error pre-existing).
|
||||||
|
---
|
||||||
|
|
||||||
|
### [2026-07-26 10:08] Task: Fix auto-scroll continuous + CC velocity batch + flicker
|
||||||
|
- **Tóm tắt thay đổi:** (1) Brush-draw gần cạnh top/bottom giờ dùng `setInterval` 30ms để cuộn liên tục (trước đây cuộn 1 lần per mousemove). Stop interval khi mouseup hoặc chuột rời khỏi threshold. (2) Ctrl+drag velocity cho selected notes — gộp tất cả `setNotes` vào 1 batch call (thay vì 1 call per note), tránh flicker do multi re-render. (3) `handleCCMouseDown` selectedMode cũng batch `setNotes`.
|
||||||
|
- **Các file ảnh hưởng:** `app/static/js/app.jsx`
|
||||||
|
- **Ghi chú/Test (nếu có):** `npm run build` pass. Thêm ref `brushAutoScrollRef` cho setInterval auto-scroll. Batch `setNotes` trong cả `handleCCMouseDown` và `handleCCMouseMove` để tránh flicker.
|
||||||
---
|
---
|
||||||
|
|
||||||
### [2026-07-25 07:25] Task: Fix auto-scroll + maxDuration tab isolation + 1-bar margin
|
### [2026-07-25 07:25] Task: Fix auto-scroll + maxDuration tab isolation + 1-bar margin
|
||||||
|
|||||||
Reference in New Issue
Block a user