fix: tempo track height 40px fixed + clamp selection >= 0

- TempoTrackLane canvas height fixed to 40px, wrapper h-10 shrink-0 overflow-hidden
- selLeft/selRight clamped to Math.max(0, ...)
- rulerAnchorRef/setSelectionStart/setSelectionEnd clamped >= 0
This commit is contained in:
2026-07-25 10:55:46 +07:00
parent e14006ffcc
commit 8be4ce99e1
3 changed files with 25 additions and 16 deletions
+15 -12
View File
@@ -1292,7 +1292,7 @@ const TempoTrackLane = ({
const ctx = canvas.getContext('2d'); const ctx = canvas.getContext('2d');
const dpr = window.devicePixelRatio || 1; const dpr = window.devicePixelRatio || 1;
const scrollLeftVal = scrollLeft || 0; const scrollLeftVal = scrollLeft || 0;
const height = canvas.parentElement ? canvas.parentElement.clientHeight : 40; const height = 40;
canvas.width = Math.min(Math.round(drawWidth * dpr), 32768); canvas.width = Math.min(Math.round(drawWidth * dpr), 32768);
canvas.height = Math.min(Math.round(height * dpr), 32768); canvas.height = Math.min(Math.round(height * dpr), 32768);
ctx.scale(dpr, dpr); ctx.scale(dpr, dpr);
@@ -8676,17 +8676,17 @@ const App = () => {
const playheadLeftPos = useMemo(() => (currentTime + leadInMargin) * zoom, [currentTime, zoom, leadInMargin]); const playheadLeftPos = useMemo(() => (currentTime + leadInMargin) * zoom, [currentTime, zoom, leadInMargin]);
const selLeft = useMemo(() => { const selLeft = useMemo(() => {
if (selectionMode === 'local' && localSelectionStart !== null && localSelectionEnd !== null) { if (selectionMode === 'local' && localSelectionStart !== null && localSelectionEnd !== null) {
return Math.min(localSelectionStart, localSelectionEnd); return Math.max(0, Math.min(localSelectionStart, localSelectionEnd));
} }
if (selectionStart === null || selectionEnd === null) return null; if (selectionStart === null || selectionEnd === null) return null;
return Math.min(selectionStart, selectionEnd); return Math.max(0, Math.min(selectionStart, selectionEnd));
}, [selectionStart, selectionEnd, selectionMode, localSelectionStart, localSelectionEnd]); }, [selectionStart, selectionEnd, selectionMode, localSelectionStart, localSelectionEnd]);
const selRight = useMemo(() => { const selRight = useMemo(() => {
if (selectionMode === 'local' && localSelectionStart !== null && localSelectionEnd !== null) { if (selectionMode === 'local' && localSelectionStart !== null && localSelectionEnd !== null) {
return Math.max(localSelectionStart, localSelectionEnd); return Math.max(0, Math.max(localSelectionStart, localSelectionEnd));
} }
if (selectionStart === null || selectionEnd === null) return null; if (selectionStart === null || selectionEnd === null) return null;
return Math.max(selectionStart, selectionEnd); return Math.max(0, Math.max(selectionStart, selectionEnd));
}, [selectionStart, selectionEnd, selectionMode, localSelectionStart, localSelectionEnd]); }, [selectionStart, selectionEnd, selectionMode, localSelectionStart, localSelectionEnd]);
const dspSelectionStats = useMemo(() => { const dspSelectionStats = useMemo(() => {
@@ -10032,14 +10032,14 @@ const App = () => {
e.stopPropagation(); e.stopPropagation();
// Shift+click on ruler: lock existing anchor (or currentTime fallback) and extend global selection // 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 anchor = rulerAnchorRef.current !== null && rulerAnchorRef.current !== undefined ? rulerAnchorRef.current : selectionStart !== null && selectionStart !== undefined ? selectionStart : currentTime;
const selS = Math.min(anchor, time); const selS = Math.max(0, Math.min(anchor, time));
const selE = Math.max(anchor, time); const selE = Math.max(0, Math.max(anchor, time));
setSelectionStart(selS); setSelectionStart(selS);
setSelectionEnd(selE); setSelectionEnd(selE);
} else { } else {
rulerAnchorRef.current = time; rulerAnchorRef.current = Math.max(0, time);
setSelectionStart(time); setSelectionStart(Math.max(0, time));
setSelectionEnd(time); setSelectionEnd(Math.max(0, time));
} }
handlePlayheadSet(time); handlePlayheadSet(time);
}; };
@@ -14960,7 +14960,10 @@ const App = () => {
onRulerMouseDown: handleRulerMouseDown, onRulerMouseDown: handleRulerMouseDown,
scrollLeft: scrollLeft, scrollLeft: scrollLeft,
canvasRedrawCount: canvasRedrawCount canvasRedrawCount: canvasRedrawCount
}), /*#__PURE__*/React.createElement(TempoTrackLane, { }), /*#__PURE__*/React.createElement("div", {
className: "h-10 shrink-0 relative overflow-hidden",
style: { height: '40px' }
}, /*#__PURE__*/React.createElement(TempoTrackLane, {
bpm: parseInt(bpm) || 120, bpm: parseInt(bpm) || 120,
zoom: zoom, zoom: zoom,
timelineWidth: timelineWidth, timelineWidth: timelineWidth,
@@ -14970,7 +14973,7 @@ const App = () => {
onRulerMouseDown: handleRulerMouseDown, onRulerMouseDown: handleRulerMouseDown,
scrollLeft: scrollLeft, scrollLeft: scrollLeft,
canvasRedrawCount: canvasRedrawCount canvasRedrawCount: canvasRedrawCount
}), selectionMode === 'global' && selLeft !== null && selRight !== null && selRight > selLeft && /*#__PURE__*/React.createElement("div", { })), selectionMode === 'global' && selLeft !== null && selRight !== null && selRight > selLeft && /*#__PURE__*/React.createElement("div", {
className: "absolute inset-0 pointer-events-none z-20", className: "absolute inset-0 pointer-events-none z-20",
style: { style: {
left: `${selLeft * zoom}px`, left: `${selLeft * zoom}px`,
File diff suppressed because one or more lines are too long
+6
View File
@@ -92,6 +92,12 @@
- **Ghi chú/Test (nếu có):** `npm run build` — build passes. - **Ghi chú/Test (nếu có):** `npm run build` — build passes.
--- ---
### [2026-07-25 10:54] Task: Fix TempoTrackLane height + clamp selection at column 0
- **Tóm tắt thay đổi:** Fix TempoTrackLane canvas height = 40px cố định (thay vì parentElement.clientHeight gây tràn). Wrapper `h-10 shrink-0 overflow-hidden` để không đẩy track lanes lệch TCP. Clamp selLeft/selRight/ruler handlers >= 0 để selection không vào vùng âm.
- **Các file ảnh hưởng:** `app/static/js/app.jsx`, `app/static/js/app.precompiled.js`
- **Ghi chú/Test (nếu có):** `npm run build` — build passes.
---
### [2026-07-25 10:44] Task: Zero lead-in, time ruler + tempo track split layout ### [2026-07-25 10:44] Task: Zero lead-in, time ruler + tempo track split layout
- **Tóm tắt thay đổi:** Xóa leadInMargin (về 0) — time 0 tại mép trái, chỉ còn 8px PADDING_LEFT. TimelineRuler đơn giản thành time-only ruler (40px, chỉ hiển thị time labels). Thêm TempoTrackLane ngay dưới (40px, bars/beats/BPM). Selection overlay top từ 48→80px. Đồng bộ WaveformLane PADDING_LEFT=8/zoom. - **Tóm tắt thay đổi:** Xóa leadInMargin (về 0) — time 0 tại mép trái, chỉ còn 8px PADDING_LEFT. TimelineRuler đơn giản thành time-only ruler (40px, chỉ hiển thị time labels). Thêm TempoTrackLane ngay dưới (40px, bars/beats/BPM). Selection overlay top từ 48→80px. Đồng bộ WaveformLane PADDING_LEFT=8/zoom.
- **Các file ảnh hưởng:** `app/static/js/app.jsx`, `app/static/js/app.precompiled.js` - **Các file ảnh hưởng:** `app/static/js/app.jsx`, `app/static/js/app.precompiled.js`