FIX: sửa lỗi hiển thị số bar bị sai

This commit is contained in:
2026-08-07 15:07:06 +07:00
parent 173a1f227b
commit 58c6ec794d
4 changed files with 172 additions and 102 deletions
+131 -97
View File
@@ -13937,6 +13937,26 @@ const App = () => {
const [beginBar, setBeginBar] = useState(0); const [beginBar, setBeginBar] = useState(0);
const [endBar, setEndBar] = useState(0); const [endBar, setEndBar] = useState(0);
const [numberBar, setNumberBar] = useState(1); const [numberBar, setNumberBar] = useState(1);
// Quét chn (sweep tempo track/timeline) đng b bars hin th dưi
// statusbar: beginBar = bar cha selectionStart, endBar = bar cha
// selectionEnd, numberBar = s bar đưc chn. Trưc đây beginBar/endBar/
// numberBar CH đi khi nhp tay input quét không cp nht statusbar
// luôn hin th 0/0/1.
useEffect(() => {
if (selectionStart === null || selectionEnd === null || selectionEnd <= selectionStart) return;
const _beatDur = 60 / (parseInt(bpm) || 120);
const _secPerBar = _beatDur * 4;
const _b1 = Math.floor((selectionStart || 0) / _secPerBar);
// endBar = bar KT THÚC vùng quét (chm vch) KHÔNG tr 1: quét 8 bars
// t bar 0 hin th "0 đến 8" (user yêu cu) s bars = ceil floor
// (8 0 = 8 KHÔNG cng 1).
const _b2 = Math.ceil((selectionEnd || 0) / _secPerBar);
if (_b2 > _b1) {
setBeginBar(_b1);
setEndBar(_b2);
setNumberBar(_b2 - _b1);
}
}, [selectionStart, selectionEnd, bpm]);
const [subTabHeight, setSubTabHeight] = useState(96); const [subTabHeight, setSubTabHeight] = useState(96);
const [isExporting, setIsExporting] = useState(false); const [isExporting, setIsExporting] = useState(false);
const [projectName, setProjectName] = useState(() => localStorage.getItem('sonic_project_name') || ''); const [projectName, setProjectName] = useState(() => localStorage.getItem('sonic_project_name') || '');
@@ -25880,97 +25900,18 @@ STRICT CONSTRAINTS:
}, "4")), /*#__PURE__*/React.createElement("div", { }, "4")), /*#__PURE__*/React.createElement("div", {
className: "w-[1px] h-6 bg-zinc-800 mx-1.5" className: "w-[1px] h-6 bg-zinc-800 mx-1.5"
}), /*#__PURE__*/React.createElement("span", { }), /*#__PURE__*/React.createElement("span", {
className: "text-[14px] text-zinc-500 font-bold" className: "text-[20px] text-zinc-500 font-bold ml-1"
}, "Bars:"), /*#__PURE__*/React.createElement("input", { }, "Time:"), /*#__PURE__*/React.createElement("span", {
type: "number", className: "font-mono text-[20px] text-cyan-300 font-bold tabular-nums",
min: "0", title: "Vị trí playhead"
value: beginBar, }, formatTime(currentTime)), /*#__PURE__*/React.createElement("span", {
onChange: e => { className: "text-[20px] text-zinc-600 font-mono"
const b = parseInt(e.target.value) || 0; }, "/"), /*#__PURE__*/React.createElement("span", {
setBeginBar(b); className: "font-mono text-[20px] text-zinc-400 tabular-nums",
const beatDuration = 60 / parseInt(bpm || 120); title: "Tổng thời gian dự án"
const t = b * beatDuration * 4; }, formatTime(projectEnd)), /*#__PURE__*/React.createElement("div", {
clearLocalSelection();
setSelectionMode('global');
setSelectionStart(t);
setSelectionEnd(t + beatDuration * 4);
},
className: "w-14 bg-black text-white text-[14px] px-1 py-0.5 rounded border border-zinc-700 text-center font-mono"
}), /*#__PURE__*/React.createElement("span", {
className: "text-[14px] text-zinc-500"
}, "-"), /*#__PURE__*/React.createElement("input", {
type: "number",
min: "0",
value: endBar,
onChange: e => {
const b = parseInt(e.target.value) || 0;
setEndBar(b);
const beatDuration = 60 / parseInt(bpm || 120);
const t = b * beatDuration * 4;
setSelectionEnd(t + beatDuration * 4);
setNumberBar(b - beginBar + 1);
},
className: "w-14 bg-black text-white text-[14px] px-1 py-0.5 rounded border border-zinc-700 text-center font-mono"
}), /*#__PURE__*/React.createElement("span", {
className: "text-[14px] text-zinc-500"
}, "#"), /*#__PURE__*/React.createElement("input", {
type: "number",
min: "1",
value: numberBar,
readOnly: true,
className: "w-14 bg-black text-zinc-400 text-[14px] px-1 py-0.5 rounded border border-zinc-800 text-center font-mono"
}), /*#__PURE__*/React.createElement("button", {
onClick: () => setSelectionFollowsTempo(prev => !prev),
className: `px-1.5 py-0.5 text-[14px] rounded border font-bold ${selectionFollowsTempo ? 'bg-amber-700 text-white border-amber-500' : 'bg-zinc-800 text-zinc-400 border-zinc-700'}`,
title: selectionFollowsTempo ? "Selection theo tempo (đổi BPM → selection thay đổi)" : "Selection theo thời gian (cố định)"
}, selectionFollowsTempo ? "♪T" : "⏱T"), /*#__PURE__*/React.createElement("div", {
className: "w-[1px] h-6 bg-zinc-800 mx-1.5"
}), /*#__PURE__*/React.createElement("span", {
className: "text-[14px] text-zinc-500"
}, "Start:"), /*#__PURE__*/React.createElement("input", {
type: "text",
value: selLeft !== null ? formatTime(selLeft) : '',
onChange: e => {
const parts = e.target.value.split(/[:.]/);
if (parts.length === 3) {
const secs = parseInt(parts[0]) * 60 + parseInt(parts[1]) + parseFloat('0.' + (parts[2] || '0'));
clearLocalSelection();
setSelectionMode('global');
setSelectionStart(secs);
}
},
className: "w-20 bg-black text-zinc-200 text-[14px] px-1 py-0.5 rounded border border-zinc-700 text-center font-mono"
}), /*#__PURE__*/React.createElement("span", {
className: "text-[14px] text-zinc-500"
}, "End:"), /*#__PURE__*/React.createElement("input", {
type: "text",
value: selRight !== null ? formatTime(selRight) : '',
onChange: e => {
const parts = e.target.value.split(/[:.]/);
if (parts.length === 3) {
const secs = parseInt(parts[0]) * 60 + parseInt(parts[1]) + parseFloat('0.' + (parts[2] || '0'));
setSelectionEnd(secs);
}
},
className: "w-20 bg-black text-zinc-200 text-[14px] px-1 py-0.5 rounded border border-zinc-700 text-center font-mono"
}), /*#__PURE__*/React.createElement("span", {
className: "text-[14px] text-zinc-500"
}, "Len:"), /*#__PURE__*/React.createElement("input", {
type: "text",
value: selLeft !== null && selRight !== null ? formatTime(Math.abs(selRight - selLeft)) : '',
onChange: e => {
const parts = e.target.value.split(/[:.]/);
if (parts.length === 3 && selLeft !== null) {
const secs = parseInt(parts[0]) * 60 + parseInt(parts[1]) + parseFloat('0.' + (parts[2] || '0'));
setSelectionEnd(selLeft + secs);
}
},
className: "w-20 bg-black text-amber-300 text-[14px] px-1 py-0.5 rounded border border-zinc-700 text-center font-mono"
}), /*#__PURE__*/React.createElement("div", {
className: "flex-1" className: "flex-1"
}), /*#__PURE__*/React.createElement("div", { })),(() => {
className: "text-xs font-bold font-mono text-zinc-100"
}, formatTime(currentTime))), (() => {
const dockPanels = { const dockPanels = {
top: [], top: [],
right: [], right: [],
@@ -26492,8 +26433,10 @@ STRICT CONSTRAINTS:
setEndBar(b); setEndBar(b);
const beatDuration = 60 / parseInt(bpm || 120); const beatDuration = 60 / parseInt(bpm || 120);
const t = b * beatDuration * 4; const t = b * beatDuration * 4;
setSelectionEnd(t + beatDuration * 4); // selectionEnd = vch bar b (không +1 bar) khp hin th endBar
setNumberBar(b - beginBar + 1); // (quét 8 bars hin th "0 đến 8"): nhp 8 selection ti vch 8.
setSelectionEnd(t);
setNumberBar(Math.max(0, b - beginBar));
}, },
className: "w-full bg-[#242424] text-white text-center font-mono text-xs rounded py-0.5 border border-zinc-700 focus:outline-none" className: "w-full bg-[#242424] text-white text-center font-mono text-xs rounded py-0.5 border border-zinc-700 focus:outline-none"
})), /*#__PURE__*/React.createElement("div", { })), /*#__PURE__*/React.createElement("div", {
@@ -27853,7 +27796,7 @@ STRICT CONSTRAINTS:
active: showMediaExplorer active: showMediaExplorer
})))); }))));
})(), /*#__PURE__*/React.createElement("div", { })(), /*#__PURE__*/React.createElement("div", {
className: "h-6 bg-[#141414] border-t border-zinc-900 px-4 flex items-center justify-between text-xs text-zinc-500 select-none shrink-0" className: "h-9 bg-[#141414] border-t border-zinc-900 px-4 flex items-center justify-between text-[18px] text-zinc-500 select-none shrink-0"
}, /*#__PURE__*/React.createElement("div", { }, /*#__PURE__*/React.createElement("div", {
className: "flex items-center gap-4" className: "flex items-center gap-4"
}, /*#__PURE__*/React.createElement("span", null, "Status: ", isPlaying ? 'Playing' : 'Stopped'), activeTab !== 'main' && /*#__PURE__*/React.createElement("span", { }, /*#__PURE__*/React.createElement("span", null, "Status: ", isPlaying ? 'Playing' : 'Stopped'), activeTab !== 'main' && /*#__PURE__*/React.createElement("span", {
@@ -27861,16 +27804,107 @@ STRICT CONSTRAINTS:
}, "Sub-Tab"), activeTab === 'main' && /*#__PURE__*/React.createElement("span", { }, "Sub-Tab"), activeTab === 'main' && /*#__PURE__*/React.createElement("span", {
className: "text-cyan-400 font-semibold uppercase" className: "text-cyan-400 font-semibold uppercase"
}, "Track: ID ", selectedTrackId), selectionMode === 'local' && /*#__PURE__*/React.createElement("span", { }, "Track: ID ", selectedTrackId), selectionMode === 'local' && /*#__PURE__*/React.createElement("span", {
className: "text-amber-400 font-semibold uppercase text-xs" className: "text-amber-400 font-semibold uppercase text-[18px]"
}, "Local Sel"), selectionMode === 'global' && /*#__PURE__*/React.createElement("span", { }, "Local Sel"), selectionMode === 'global' && /*#__PURE__*/React.createElement("span", {
className: "text-purple-400 font-semibold uppercase text-xs" className: "text-purple-400 font-semibold uppercase text-[18px]"
}, "Global Sel"), hasAnySolo && /*#__PURE__*/React.createElement("span", { }, "Global Sel"), hasAnySolo && /*#__PURE__*/React.createElement("span", {
className: "text-amber-500 font-semibold" className: "text-amber-500 font-semibold"
}, "Solo: ", tracks.filter(t => t.solo).length, " track(s)"), isLoopingSelection && selectionMode === 'local' && /*#__PURE__*/React.createElement("span", { }, "Solo: ", tracks.filter(t => t.solo).length, " track(s)"), isLoopingSelection && selectionMode === 'local' && /*#__PURE__*/React.createElement("span", {
className: "text-emerald-400 font-semibold uppercase text-xs" className: "text-emerald-400 font-semibold uppercase text-[18px]"
}, "Solo Loop"), isLoopingSelection && selectionMode !== 'local' && /*#__PURE__*/React.createElement("span", { }, "Solo Loop"), isLoopingSelection && selectionMode !== 'local' && /*#__PURE__*/React.createElement("span", {
className: "text-cyan-400 font-semibold uppercase text-xs" className: "text-cyan-400 font-semibold uppercase text-[18px]"
}, "Master Loop")), /*#__PURE__*/React.createElement("div", { }, "Master Loop")), /*#__PURE__*/React.createElement("div", {
className: "flex items-center gap-1.5 ml-3"
}, /*#__PURE__*/React.createElement("span", {
className: "text-[18px] font-bold text-zinc-400 uppercase"
}, "Bars:"), /*#__PURE__*/React.createElement("input", {
type: "number",
min: "0",
value: beginBar,
onChange: e => {
const b = parseInt(e.target.value) || 0;
setBeginBar(b);
const beatDuration = 60 / parseInt(bpm || 120);
const t = b * beatDuration * 4;
clearLocalSelection();
setSelectionMode('global');
setSelectionStart(t);
setSelectionEnd(t + beatDuration * 4);
},
className: "w-[60px] bg-black text-white text-[18px] px-1 py-0 rounded border border-zinc-700 text-center font-mono"
}), /*#__PURE__*/React.createElement("span", {
className: "text-[18px] text-zinc-500"
}, "-"), /*#__PURE__*/React.createElement("input", {
type: "number",
min: "0",
value: endBar,
onChange: e => {
const b = parseInt(e.target.value) || 0;
setEndBar(b);
const beatDuration = 60 / parseInt(bpm || 120);
const t = b * beatDuration * 4;
// selectionEnd = vch bar b (không +1 bar) khp hin th endBar
// (quét 8 bars hin th "0 đến 8"): nhp 8 selection ti vch 8.
setSelectionEnd(t);
setNumberBar(Math.max(0, b - beginBar));
},
className: "w-[60px] bg-black text-white text-[18px] px-1 py-0 rounded border border-zinc-700 text-center font-mono"
}), /*#__PURE__*/React.createElement("span", {
className: "text-[18px] text-zinc-500"
}, "#"), /*#__PURE__*/React.createElement("input", {
type: "number",
min: "1",
value: numberBar,
readOnly: true,
className: "w-[60px] bg-black text-zinc-400 text-[18px] px-1 py-0 rounded border border-zinc-800 text-center font-mono"
}), /*#__PURE__*/React.createElement("button", {
onClick: () => setSelectionFollowsTempo(prev => !prev),
className: `px-1.5 py-0 text-[18px] rounded border font-bold ${selectionFollowsTempo ? 'bg-amber-700 text-white border-amber-500' : 'bg-zinc-800 text-zinc-400 border-zinc-700'}`,
title: selectionFollowsTempo ? "Selection theo tempo (đổi BPM → selection thay đổi)" : "Selection theo thời gian (cố định)"
}, selectionFollowsTempo ? "♪T" : "⏱T"), /*#__PURE__*/React.createElement("div", {
className: "w-[1px] h-6 bg-zinc-800 mx-1"
}), /*#__PURE__*/React.createElement("span", {
className: "text-[18px] text-zinc-500"
}, "Start:"), /*#__PURE__*/React.createElement("input", {
type: "text",
value: selLeft !== null ? formatTime(selLeft) : '',
onChange: e => {
const parts = e.target.value.split(/[:.]/);
if (parts.length === 3) {
const secs = parseInt(parts[0]) * 60 + parseInt(parts[1]) + parseFloat('0.' + (parts[2] || '0'));
clearLocalSelection();
setSelectionMode('global');
setSelectionStart(secs);
}
},
className: "w-24 bg-black text-zinc-200 text-[18px] px-1 py-0 rounded border border-zinc-700 text-center font-mono"
}), /*#__PURE__*/React.createElement("span", {
className: "text-[18px] text-zinc-500"
}, "End:"), /*#__PURE__*/React.createElement("input", {
type: "text",
value: selRight !== null ? formatTime(selRight) : '',
onChange: e => {
const parts = e.target.value.split(/[:.]/);
if (parts.length === 3) {
const secs = parseInt(parts[0]) * 60 + parseInt(parts[1]) + parseFloat('0.' + (parts[2] || '0'));
setSelectionEnd(secs);
}
},
className: "w-24 bg-black text-zinc-200 text-[18px] px-1 py-0 rounded border border-zinc-700 text-center font-mono"
}), /*#__PURE__*/React.createElement("span", {
className: "text-[18px] text-zinc-500"
}, "Len:"), /*#__PURE__*/React.createElement("input", {
type: "text",
value: selLeft !== null && selRight !== null ? formatTime(Math.abs(selRight - selLeft)) : '',
onChange: e => {
const parts = e.target.value.split(/[:.]/);
if (parts.length === 3 && selLeft !== null) {
const secs = parseInt(parts[0]) * 60 + parseInt(parts[1]) + parseFloat('0.' + (parts[2] || '0'));
setSelectionEnd(selLeft + secs);
}
},
className: "w-24 bg-black text-amber-300 text-[18px] px-1 py-0 rounded border border-zinc-700 text-center font-mono"
})), /*#__PURE__*/React.createElement("div", {
className: "flex items-center gap-2" className: "flex items-center gap-2"
}, /*#__PURE__*/React.createElement("button", { }, /*#__PURE__*/React.createElement("button", {
onClick: () => setShowExportPanel(p => !p), onClick: () => setShowExportPanel(p => !p),
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -24,7 +24,7 @@
<script src="/static/js/services/midiExtractor.js?v=202607281052"></script> <script src="/static/js/services/midiExtractor.js?v=202607281052"></script>
<script src="/static/js/services/promptTemplateManager.js?v=202607281039"></script> <script src="/static/js/services/promptTemplateManager.js?v=202607281039"></script>
<script src="/static/js/services/undoRedoEngine.js?v=202607290941"></script> <script src="/static/js/services/undoRedoEngine.js?v=202607290941"></script>
<script src="/static/js/app.precompiled.js?v=202608070520" defer></script> <script src="/static/js/app.precompiled.js?v=202608070535" defer></script>
<link rel="stylesheet" href="/static/css/styles.css?v=202607271016"> <link rel="stylesheet" href="/static/css/styles.css?v=202607271016">
<style> <style>
:root { :root {
+24
View File
@@ -2509,3 +2509,27 @@
- **Hành vi:** LOOP TẮT → playhead chạy tới END ITEMS (22.59) rồi dừng (không cắt 8s content). LOOP BẬT → playhead tới projectEnd (content end ~8s) → LOOP LẠI từ 0 → play liên tục. - **Hành vi:** LOOP TẮT → playhead chạy tới END ITEMS (22.59) rồi dừng (không cắt 8s content). LOOP BẬT → playhead tới projectEnd (content end ~8s) → LOOP LẠI từ 0 → play liên tục.
- **Các file ảnh hưởng:** `app/static/js/app.jsx`, `app/templates/index.html` (?v=202608070520), `wiki.md`. Rebuild precompiled. - **Các file ảnh hưởng:** `app/static/js/app.jsx`, `app/templates/index.html` (?v=202608070520), `wiki.md`. Rebuild precompiled.
- **Ghi chú/Test:** hard refresh → (1) LOOP TẮT → play MAIN (section 22.59s, content 8s) → play tới 22.59 mới dừng; (2) bấm LOOP → play → loop liên tục tại ~8s (log `[Stop] projectEnd reached — projectEnd≈8` + loop lại từ 0). - **Ghi chú/Test:** hard refresh → (1) LOOP TẮT → play MAIN (section 22.59s, content 8s) → play tới 22.59 mới dừng; (2) bấm LOOP → play → loop liên tục tại ~8s (log `[Stop] projectEnd reached — projectEnd≈8` + loop lại từ 0).
### [2026-08-07 05:25] Task: Tăng size Time display (toolbar) + tăng statusbar 150%
- **Yêu cầu user:** tăng size ô hiển thị thời gian (playhead/tổng) trên toolbar; tăng kích thước status bar lên 150%.
- **FIX (app.jsx):**
(1) Toolbar — ô `Time: <playhead> / <total>`: `text-[14px]``text-[20px]` (4 spans: label, playhead, "/", total).
(2) Statusbar — container `h-6``h-9` (36px = 150%), `text-xs``text-[18px]`; các badge trạng thái (Local Sel/Global Sel/Solo Loop/Master Loop) `text-xs``text-[18px]`; Bars selection (Bars/inputs/♪T) + Time selection (Start/End/Len): `text-xs``text-[18px]`, input `w-10``w-[60px]`, `w-16``w-24`, divider `h-4``h-6`.
- **Các file ảnh hưởng:** `app/static/js/app.jsx`, `app/templates/index.html` (?v=202608070525), `wiki.md`. Rebuild precompiled (build PASS — text-[20px] ×4, text-[18px] ×18, w-[60px] ×7, h-9 ✓).
- **Ghi chú/Test:** hard refresh → toolbar Time to rõ (20px); statusbar cao hơn + chữ/input 18px (150%).
### [2026-08-07 05:30] Task: Quét chọn (tempo track) không cập nhật bars hiển thị dưới statusbar
- **Báo cáo user:** quét ở tempo track để select duration → vị trí quét + số bars được chọn KHÔNG hiển thị ở bars dưới status bar.
- **Nguyên nhân:** beginBar/endBar/numberBar CHỈ được set từ onChange của input (nhập tay — dock panel cũ + statusbar mới) — KHÔNG có code cập nhật từ sweep/selection → statusbar luôn hiển thị 0/0/1 khi quét.
- **FIX (app.jsx):** thêm effect `[selectionStart, selectionEnd, bpm]` — khi selection hợp lệ (end > start): beginBar = floor(start / secPerBar); endBar = ceil(end / secPerBar) 1; numberBar = endBar beginBar + 1 → quét xong bars statusbar hiển thị đúng vị trí + số bar.
- **Các file ảnh hưởng:** `app/static/js/app.jsx`, `app/templates/index.html` (?v=202608070530), `wiki.md`. Rebuild precompiled (build PASS).
- **Ghi chú/Test:** hard refresh → quét chọn trên tempo track (vd từ bar 2 tới bar 5) → statusbar hiển thị Bars: 2 - 5 # 4; xóa selection → giữ giá trị cuối (không tự reset).
### [2026-08-07 05:35] Task: Bars hiển thị sai chỉ số — quét 8 bars hiển thị "0 đến 7"
- **Báo cáo user:** số bars đúng (8) nhưng chỉ số sai: quét bar 0→8 hiển thị "0 đến 7" — cần "0 đến 8".
- **Nguyên nhân:** effect (0530) tính `endBar = ceil(end/spb) 1` (bar cuối INCLUSIVE) → 7; numberBar = endBar beginBar + 1 = 8.
- **FIX (app.jsx):**
(1) Effect: `endBar = ceil(end/spb)` (KHÔNG 1 — bar KẾT THÚC vùng quét/chạm vạch); `numberBar = ceil floor` (KHÔNG +1) → quét 8 bars → "0 đến 8 # 8".
(2) Input End Bar (dock 26436 + statusbar 27846): `setSelectionEnd(t + spb)``setSelectionEnd(t)` (selection tới vạch bar b — khớp hiển thị); `setNumberBar(b beginBar)`.
- **Các file ảnh hưởng:** `app/static/js/app.jsx`, `app/templates/index.html` (?v=202608070535), `wiki.md`. Rebuild precompiled (build PASS).
- **Ghi chú/Test:** hard refresh → quét 8 bars từ 0 → BARS hiển thị "0 - 8 # 8"; nhập End Bar = 8 → selection tới vạch 8 (8 bars).