fix: sửa UI của bars và lưu thông tin vào profile

This commit is contained in:
2026-07-22 10:54:24 +07:00
parent 58089f40f9
commit b78193dfad
5 changed files with 180 additions and 71 deletions
+86 -6
View File
@@ -751,6 +751,21 @@ const TempoTrackLane = ({
ctx.font = 'bold 9px Inter, sans-serif';
ctx.textAlign = 'left';
ctx.fillText(`${Math.ceil(beatNum / 4)}`, localX + 3, 11);
} else if (zoom >= 2) {
ctx.strokeStyle = 'rgba(255, 255, 255, 0.08)';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(localX, 0);
ctx.lineTo(localX, height);
ctx.stroke();
if (zoom >= 5) {
ctx.fillStyle = 'rgba(255, 255, 255, 0.4)';
ctx.font = '8px Inter, sans-serif';
ctx.textAlign = 'left';
const bar = Math.ceil(beatNum / 4);
const beat = (beatNum - 1) % 4 + 1;
ctx.fillText(`${bar}:${beat}`, localX + 2, 9);
}
} else {
ctx.strokeStyle = 'rgba(255, 255, 255, 0.08)';
ctx.lineWidth = 1;
@@ -2590,7 +2605,14 @@ const AIConfigModal = ({
className: "text-xs uppercase font-bold text-slate-400 block mb-2"
}, "Providers"), providers.map(p => /*#__PURE__*/React.createElement("button", {
key: p.id,
tabIndex: 0,
onClick: () => setSelectedId(p.id),
onKeyDown: e => {
if (e.key === ' ') {
e.preventDefault();
updateProviderField(p.id, 'is_active', !p.is_active);
}
},
className: `w-full text-left px-3 py-2 rounded-lg text-xs font-semibold flex items-center justify-between transition ${selectedId === p.id ? 'bg-cyan-600 text-white shadow' : 'bg-[#1e1e1e] text-slate-300 hover:bg-[#2e2e2e]'}`
}, /*#__PURE__*/React.createElement("span", {
className: "truncate"
@@ -2627,7 +2649,47 @@ const AIConfigModal = ({
}
},
className: "px-2 py-1 bg-red-800 hover:bg-red-700 text-white text-xs font-bold rounded"
}, "Xóa"))), activeProvider && /*#__PURE__*/React.createElement("form", {
}, "Xóa"), providers.length > 1 && /*#__PURE__*/React.createElement("button", {
onClick: function (e) {
e.stopPropagation();
var provs = providers.slice();
var pos = -1;
for (var pi = 0; pi < provs.length; pi++) {
if (provs[pi].id === selectedId) {
pos = pi;
break;
}
}
if (pos > 0) {
var tmp = provs[pos];
provs[pos] = provs[pos - 1];
provs[pos - 1] = tmp;
setProviders(provs);
}
},
className: "px-2 py-1 bg-zinc-700 hover:bg-zinc-600 text-white text-xs font-bold rounded",
title: "Di chuyển lên"
}, "▲"), providers.length > 1 && /*#__PURE__*/React.createElement("button", {
onClick: function (e) {
e.stopPropagation();
var provs = providers.slice();
var pos = -1;
for (var pi = 0; pi < provs.length; pi++) {
if (provs[pi].id === selectedId) {
pos = pi;
break;
}
}
if (pos < provs.length - 1) {
var tmp = provs[pos];
provs[pos] = provs[pos + 1];
provs[pos + 1] = tmp;
setProviders(provs);
}
},
className: "px-2 py-1 bg-zinc-700 hover:bg-zinc-600 text-white text-xs font-bold rounded",
title: "Di chuyển xuống"
}, "▼"))), activeProvider && /*#__PURE__*/React.createElement("form", {
onSubmit: handleSave,
className: "col-span-2 space-y-3.5"
}, /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("label", {
@@ -2993,6 +3055,14 @@ const App = () => {
const [isPlaying, setIsPlaying] = useState(false);
const [selectionStart, setSelectionStart] = useState(null);
const [selectionEnd, setSelectionEnd] = useState(null);
const selectionRef = useRef({
start: null,
end: null
});
selectionRef.current = {
start: selectionStart,
end: selectionEnd
};
const [selectionMode, setSelectionMode] = useState(null); // 'global' (from ruler) | 'local' (from track)
const [localSelectionTrackId, setLocalSelectionTrackId] = useState(null);
const [localSelectionStart, setLocalSelectionStart] = useState(null);
@@ -7117,7 +7187,7 @@ const App = () => {
const cmdResult = window.DAWCommandDispatcher.execute(cmdName, fc.arguments);
setAiActionLog(prev => [...prev, {
type: 'status',
text: `${fc.name}: ${cmdResult.success ? 'thành công' : 'thất bại'}`,
text: `${fc.name}: ${cmdResult.success ? 'thành công' : 'thất bại: ' + (cmdResult.error || 'unknown')}`,
time: Date.now()
}]);
} catch (cmdErr) {
@@ -7536,6 +7606,10 @@ const App = () => {
setSelectionMode('global');
setSelectionStart(start);
setSelectionEnd(end);
selectionRef.current = {
start,
end
};
return {
success: true,
start: parseFloat(start.toFixed(3)),
@@ -7555,14 +7629,16 @@ const App = () => {
error: 'Track has no audio buffer'
};
const barDur = 60 / parseInt(bpm || 120) * 4;
const sel = selectionRef.current;
let rawStart, rawEnd;
if (args.start_time !== undefined) rawStart = args.start_time;else if (args.start_bar !== undefined) rawStart = args.start_bar * barDur;else if (selectionStart !== null) rawStart = selectionStart;else return {
if (args.start_time !== undefined && args.start_time !== null) rawStart = args.start_time;else if (args.start_bar !== undefined && args.start_bar !== null) rawStart = args.start_bar * barDur;else if (sel.start !== null) rawStart = sel.start;else rawStart = currentTime;
if (args.end_time !== undefined && args.end_time !== null) rawEnd = args.end_time;else if (args.end_bar !== undefined && args.end_bar !== null) rawEnd = args.end_bar * barDur;else if (args.length_bars !== undefined && args.length_bars !== null) rawEnd = rawStart + args.length_bars * barDur;else if (sel.end !== null && sel.end > rawStart) rawEnd = sel.end;else return {
success: false,
error: 'No selection or start position provided. Use set_selection first or provide start_time/start_bar.'
error: 'No end position provided. Provide end_time, end_bar, or length_bars.'
};
if (args.end_time !== undefined) rawEnd = args.end_time;else if (args.length_bars !== undefined) rawEnd = rawStart + args.length_bars * barDur;else if (selectionEnd !== null && selectionEnd > rawStart) rawEnd = selectionEnd;else return {
if (rawEnd <= rawStart) return {
success: false,
error: 'No end position provided. Use set_selection first or provide end_time/length_bars.'
error: 'End position must be after start position.'
};
const buffer = track.buffer;
const ctx = getAudioContext();
@@ -7615,6 +7691,10 @@ const App = () => {
return updated;
});
setSelectedTrackId(newId);
selectionRef.current = {
start: 0,
end: slicedBuffer.duration
};
clearLocalSelection();
setSelectionMode('global');
setSelectionStart(0);