fix: brush velocity isolation from CC lane + AGENTs.md compliance
This commit is contained in:
+130
-35
@@ -438,7 +438,9 @@ const WaveformLane = ({
|
||||
// Grid lines based on Snap value
|
||||
ctx.strokeStyle = 'rgba(255, 255, 255, 0.03)';
|
||||
ctx.lineWidth = 1;
|
||||
const PADDING_LEFT = 2; // seconds of empty space on left edge
|
||||
const beatDuration = (60.0 / (parseFloat(bpm) || 120));
|
||||
const barDuration = beatDuration * 4;
|
||||
const PADDING_LEFT = beatDuration; // 1-beat margin on left
|
||||
const tStart = scrollLeftVal / zoom - PADDING_LEFT;
|
||||
const tEnd = (scrollLeftVal + drawWidth) / zoom;
|
||||
let gridSpacing = 1.0;
|
||||
@@ -1215,7 +1217,7 @@ const TempoTrackLane = ({
|
||||
ctx.fillRect(0, 0, drawWidth, height);
|
||||
const beatDuration = 60 / bpm;
|
||||
const barDuration = beatDuration * 4;
|
||||
const PADDING_LEFT = 2; // seconds of empty space on left edge
|
||||
const PADDING_LEFT = beatDuration; // 1-beat margin on left
|
||||
const tStart = scrollLeftVal / zoom - PADDING_LEFT;
|
||||
const tEnd = (scrollLeftVal + drawWidth) / zoom;
|
||||
const firstBeat = Math.floor(tStart / beatDuration) * beatDuration;
|
||||
@@ -4440,6 +4442,7 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, onClose, onUpdateNot
|
||||
const viewBeats = Math.ceil(viewWidth / pixelsPerBeat) + 4;
|
||||
|
||||
const [notes, setNotes] = React.useState(st.notes || []);
|
||||
const brushVelocityRef = React.useRef(0.8);
|
||||
const [selectedNoteIds, setSelectedNoteIds] = React.useState([]);
|
||||
|
||||
// Undo/redo stacks
|
||||
@@ -4683,7 +4686,7 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, onClose, onUpdateNot
|
||||
if (!canvas) return;
|
||||
const ctx = canvas.getContext('2d');
|
||||
const dpr = window.devicePixelRatio || 1;
|
||||
const h = 80;
|
||||
const h = ccHeight;
|
||||
canvas.width = viewWidth * dpr;
|
||||
canvas.height = h * dpr;
|
||||
ctx.scale(dpr, dpr);
|
||||
@@ -4899,7 +4902,7 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, onClose, onUpdateNot
|
||||
pitch: snapPitchToScale(pitch, selectedScale),
|
||||
start_beat: start,
|
||||
duration_beats: initialDur,
|
||||
velocity: 0.8,
|
||||
velocity: brushVelocityRef.current,
|
||||
pan: 0.0
|
||||
};
|
||||
setNotes(prev => [...prev, newNote]);
|
||||
@@ -4999,7 +5002,7 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, onClose, onUpdateNot
|
||||
pitch: p,
|
||||
start_beat: draggedNote.initialBeat + i * perNoteDur,
|
||||
duration_beats: Math.max(0.125, perNoteDur * 0.9),
|
||||
velocity: 0.8,
|
||||
velocity: brushVelocityRef.current,
|
||||
pan: 0.0
|
||||
}));
|
||||
const newBrushIds = brushNotes.map(bn => bn.id);
|
||||
@@ -5244,6 +5247,8 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, onClose, onUpdateNot
|
||||
const [scaleMenuPos, setScaleMenuPos] = React.useState(null);
|
||||
const [aiPrompt, setAiPrompt] = React.useState('');
|
||||
const [aiLoading, setAiLoading] = React.useState(false);
|
||||
const [showCC, setShowCC] = React.useState(true);
|
||||
const [ccHeight, setCcHeight] = React.useState(80);
|
||||
|
||||
const snapPitchToScale = (pitch, scale) => {
|
||||
if (!scale) return pitch;
|
||||
@@ -5398,7 +5403,10 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, onClose, onUpdateNot
|
||||
key: mode,
|
||||
onClick: () => setCcMode(mode),
|
||||
className: `px-2.5 py-1 rounded capitalize ${ccMode === mode ? 'bg-purple-900/60 text-purple-300 font-bold border border-purple-700' : 'text-zinc-400 hover:text-zinc-200'}`
|
||||
}, mode)))), /*#__PURE__*/React.createElement("div", {
|
||||
}, mode)))), /*#__PURE__*/React.createElement("button", {
|
||||
onClick: () => setShowCC(!showCC),
|
||||
className: `px-2 py-1 rounded text-xs ${showCC ? 'bg-purple-900/60 text-purple-300 border border-purple-700' : 'text-zinc-500 hover:text-zinc-300'}`
|
||||
}, ccMode === 'pan' ? 'Pan' : 'Vel'), /*#__PURE__*/React.createElement("div", {
|
||||
className: "flex items-center gap-1 flex-1 max-w-[300px] ml-2"
|
||||
}, /*#__PURE__*/React.createElement("input", {
|
||||
type: "text",
|
||||
@@ -5475,9 +5483,21 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, onClose, onUpdateNot
|
||||
onMouseLeave: handleGridMouseUp,
|
||||
onContextMenu: handleContextMenu,
|
||||
className: "absolute inset-0 cursor-crosshair"
|
||||
})))), /*#__PURE__*/React.createElement("div", {
|
||||
className: "h-20 bg-[#161616] border-t border-zinc-900 flex shrink-0"
|
||||
})))), showCC && /*#__PURE__*/React.createElement("div", {
|
||||
style: { height: `${ccHeight}px` },
|
||||
className: "bg-[#161616] border-t border-zinc-900 flex shrink-0 relative"
|
||||
}, /*#__PURE__*/React.createElement("div", {
|
||||
onMouseDown: e => {
|
||||
e.preventDefault();
|
||||
const startY = e.clientY;
|
||||
const startH = ccHeight;
|
||||
const onMove = ev => { setCcHeight(Math.max(40, startH + ev.clientY - startY)); };
|
||||
const onUp = () => { document.removeEventListener('mousemove', onMove); document.removeEventListener('mouseup', onUp); };
|
||||
document.addEventListener('mousemove', onMove);
|
||||
document.addEventListener('mouseup', onUp);
|
||||
},
|
||||
className: "absolute top-0 left-0 right-0 h-1.5 cursor-n-resize z-10 hover:bg-purple-600/30"
|
||||
}), /*#__PURE__*/React.createElement("div", {
|
||||
className: "w-[60px] bg-[#222222] border-r border-zinc-900 flex items-center justify-center text-[10px] text-zinc-500 font-mono shrink-0 font-bold"
|
||||
}, ccMode.toUpperCase()), /*#__PURE__*/React.createElement("div", {
|
||||
ref: ccWrapperRef,
|
||||
@@ -5494,7 +5514,7 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, onClose, onUpdateNot
|
||||
onMouseMove: handleCCMouseMove,
|
||||
onMouseUp: () => { ccDragRef.current = null; },
|
||||
onMouseLeave: () => { ccDragRef.current = null; },
|
||||
className: "absolute inset-0 cursor-ns-resize"
|
||||
className: "absolute inset-0"
|
||||
})))), scaleMenuPos && renderScaleContextMenu());
|
||||
};
|
||||
|
||||
@@ -6073,11 +6093,14 @@ const App = () => {
|
||||
recordingStateRef.current = recordingState;
|
||||
const lastTempCompileTimeRef = useRef(0);
|
||||
const nextMetronomeBeatRef = useRef(0);
|
||||
const [showExportPanel, setShowExportPanel] = useState(true);
|
||||
const [showExportPanel, setShowExportPanel] = useState(false);
|
||||
const [showAIPanel, setShowAIPanel] = useState(true);
|
||||
const [showSelectionPanel, setShowSelectionPanel] = useState(true);
|
||||
const [showPythonToolsPanel, setShowPythonToolsPanel] = useState(true);
|
||||
const [showMediaExplorer, setShowMediaExplorer] = useState(true);
|
||||
const [showSelectionPanel, setShowSelectionPanel] = useState(false);
|
||||
const [showPythonToolsPanel, setShowPythonToolsPanel] = useState(false);
|
||||
const [showMediaExplorer, setShowMediaExplorer] = useState(false);
|
||||
const [scrollBufferExtra, setScrollBufferExtra] = useState(0);
|
||||
const scrollBufferExtraRef = useRef(0);
|
||||
scrollBufferExtraRef.current = scrollBufferExtra;
|
||||
const [showFxRack, setShowFxRack] = useState(false);
|
||||
const [showMidiEvents, setShowMidiEvents] = useState(false);
|
||||
const [rightSidebarWidth, setRightSidebarWidth] = useState(320);
|
||||
@@ -8498,8 +8521,9 @@ const App = () => {
|
||||
// ── Computed Values ──
|
||||
const maxDuration = useMemo(() => {
|
||||
const secPerBar = (60.0 / (parseInt(bpm) || 120)) * 4;
|
||||
const cur = activeTracks;
|
||||
let max = 10;
|
||||
tracks.forEach(t => {
|
||||
cur.forEach(t => {
|
||||
const clips = t.clips && t.clips.length > 0 ? t.clips : t.buffer ? [{
|
||||
id: 'default',
|
||||
buffer: t.buffer,
|
||||
@@ -8524,8 +8548,9 @@ const App = () => {
|
||||
if (recordingState === 'RECORDING' || recordingState === 'COUNT_IN') {
|
||||
max = Math.max(max, currentTime + 60);
|
||||
}
|
||||
max += secPerBar * 6 + scrollBufferExtra;
|
||||
return max;
|
||||
}, [tracks, recordingState, currentTime]);
|
||||
}, [activeTracks, recordingState, currentTime, bpm, scrollBufferExtra]);
|
||||
const maxDurationRef = useRef(maxDuration);
|
||||
maxDurationRef.current = maxDuration;
|
||||
const minZoom = useMemo(() => {
|
||||
@@ -10005,7 +10030,8 @@ const App = () => {
|
||||
const nextClipId = () => `clip_${Date.now()}_${++clipSeqCounter}`;
|
||||
|
||||
const handleClipDragStart = (trackId, clipId, clickOffset, isDuplicate = false) => {
|
||||
const track = tracks.find(t => t.id === trackId);
|
||||
const curTracks = activeTracksRef.current || activeTracks;
|
||||
const track = curTracks.find(t => t.id === trackId);
|
||||
if (!track) return;
|
||||
const existingClips = track.clips && track.clips.length > 0 ? track.clips : track.buffer ? [{
|
||||
id: 'default_' + track.id,
|
||||
@@ -10019,7 +10045,7 @@ const App = () => {
|
||||
if (isDuplicate) {
|
||||
const cloneId = nextClipId();
|
||||
const clone = { ...clip, id: cloneId, startTime: clip.startTime || 0, name: clip.name + ' (Copy)' };
|
||||
setTracks(prev => prev.map(t => {
|
||||
updateActiveTracks(prev => prev.map(t => {
|
||||
if (t.id === trackId) {
|
||||
const newClips = [...existingClips, clone];
|
||||
return { ...t, clips: newClips, buffer: newClips[0].buffer, startTime: newClips[0].startTime, name: newClips[0].name };
|
||||
@@ -10033,7 +10059,7 @@ const App = () => {
|
||||
return;
|
||||
}
|
||||
if (!track.clips || track.clips.length === 0) {
|
||||
setTracks(prev => prev.map(t => {
|
||||
updateActiveTracks(prev => prev.map(t => {
|
||||
if (t.id === trackId) {
|
||||
return { ...t, clips: existingClips };
|
||||
}
|
||||
@@ -10049,7 +10075,8 @@ const App = () => {
|
||||
const stretchedClipRef = useRef(null);
|
||||
stretchedClipRef.current = stretchedClip;
|
||||
const handleClipStretchStart = (trackId, clipId, clickTime) => {
|
||||
const track = tracks.find(t => t.id === trackId);
|
||||
const curTracks = activeTracksRef.current || activeTracks;
|
||||
const track = curTracks.find(t => t.id === trackId);
|
||||
if (!track) return;
|
||||
const clips = track.clips && track.clips.length > 0 ? track.clips : track.buffer ? [{
|
||||
id: 'default_' + track.id,
|
||||
@@ -10062,7 +10089,7 @@ const App = () => {
|
||||
if (!clip || !clip.buffer) return;
|
||||
const beforeSnap = captureTrackSnapshot(trackId);
|
||||
if (!track.clips || track.clips.length === 0) {
|
||||
setTracks(prev => prev.map(t => t.id === trackId ? {
|
||||
updateActiveTracks(prev => prev.map(t => t.id === trackId ? {
|
||||
...t,
|
||||
clips
|
||||
} : t));
|
||||
@@ -10158,10 +10185,19 @@ const App = () => {
|
||||
const wrapper = timelineWrapperRef.current;
|
||||
if (!wrapper) return;
|
||||
const wr = wrapper.getBoundingClientRect();
|
||||
const margin = 60;
|
||||
const margin = 80;
|
||||
if (clientX > wr.right - margin) {
|
||||
const speed = 15;
|
||||
const depth = (clientX - (wr.right - margin)) / margin;
|
||||
const speed = Math.round(5 + depth * depth * 40);
|
||||
wrapper.scrollLeft += speed;
|
||||
if (wrapper.scrollLeft > wrapper.scrollWidth - wrapper.clientWidth - 100) {
|
||||
const secPerBar = (60.0 / (parseInt(bpm) || 120)) * 4;
|
||||
setScrollBufferExtra(prev => prev + secPerBar * 4);
|
||||
}
|
||||
} else if (clientX < wr.left + margin) {
|
||||
const depth = ((wr.left + margin) - clientX) / margin;
|
||||
const speed = Math.round(5 + depth * depth * 40);
|
||||
wrapper.scrollLeft = Math.max(0, wrapper.scrollLeft - speed);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -10176,10 +10212,14 @@ const App = () => {
|
||||
const scrollLeft = wrapper.scrollLeft;
|
||||
const mouseX = e.clientX - rect.left + scrollLeft;
|
||||
const time = mouseX / zoom;
|
||||
const rawStart = Math.max(0, time - drag.clickOffset);
|
||||
const newStart = snapTime(rawStart, snapValueRef.current, bpmRef.current);
|
||||
const beatSec = (60.0 / (parseInt(bpmRef.current) || 120));
|
||||
const rawStart = Math.max(beatSec, time - drag.clickOffset);
|
||||
const secPerBar = beatSec * 4;
|
||||
const marginBar = maxDurationRef.current - secPerBar;
|
||||
const clampedStart = Math.min(rawStart, marginBar);
|
||||
const newStart = snapTime(clampedStart, snapValueRef.current, bpmRef.current);
|
||||
const targetTrackId = hoveredTrackIdRef.current || drag.trackId;
|
||||
setTracks(prev => prev.map(t => {
|
||||
updateActiveTracks(prev => prev.map(t => {
|
||||
// Clear the clip from its previous track if it moved to a new track
|
||||
if (t.id === drag.trackId && drag.trackId !== targetTrackId) {
|
||||
const updatedClips = (t.clips || []).filter(c => c.id !== drag.clipId);
|
||||
@@ -10252,15 +10292,19 @@ const App = () => {
|
||||
const handleMouseMove = e => {
|
||||
const stretch = stretchedClipRef.current;
|
||||
if (!stretch) return;
|
||||
autoScrollTimeline(e.clientX);
|
||||
const wrapper = timelineWrapperRef.current;
|
||||
if (!wrapper) return;
|
||||
const rect = wrapper.getBoundingClientRect();
|
||||
const scrollLeft = wrapper.scrollLeft;
|
||||
const mouseX = e.clientX - rect.left + scrollLeft;
|
||||
const time = mouseX / zoom;
|
||||
const newDuration = Math.max(0.1, time - stretch.startTime);
|
||||
const secPerBar = (60.0 / (parseInt(bpmRef.current) || 120)) * 4;
|
||||
const marginBar = maxDurationRef.current - secPerBar;
|
||||
const maxEnd = Math.min(time, marginBar);
|
||||
const newDuration = Math.max(0.1, maxEnd - stretch.startTime);
|
||||
const speedRatio = stretch.originalDuration / newDuration;
|
||||
setTracks(prev => prev.map(t => {
|
||||
updateActiveTracks(prev => prev.map(t => {
|
||||
if (t.id === stretch.trackId) {
|
||||
const updatedClips = (t.clips || []).map(c => {
|
||||
if (c.id === stretch.clipId) {
|
||||
@@ -10327,9 +10371,11 @@ const App = () => {
|
||||
const scrollLeft = wrapper.scrollLeft;
|
||||
const mouseX = e.clientX - rect.left + scrollLeft;
|
||||
const time = mouseX / zoom;
|
||||
const newStart = Math.max(0, time - drag.clickOffset);
|
||||
const beatSec = (60.0 / (parseInt(bpm) || 120));
|
||||
const secondsPerBar = beatSec * 4;
|
||||
const marginBar = maxDurationRef.current - secondsPerBar;
|
||||
const newStart = Math.max(beatSec, Math.min(time - drag.clickOffset, marginBar));
|
||||
const targetTrackId = hoveredTrackIdRef.current || drag.trackId;
|
||||
const secondsPerBar = (60.0 / (parseInt(bpm) || 120)) * 4;
|
||||
updateActiveTracks(prev => {
|
||||
let movedItem = null;
|
||||
for (let track of prev) {
|
||||
@@ -10398,7 +10444,8 @@ const App = () => {
|
||||
if (idx === -1) return t;
|
||||
const item = items[idx];
|
||||
if (resize.side === 'left') {
|
||||
const newStart = Math.min(time, resize.originalStart + resize.originalDuration - 0.1);
|
||||
const beatSec = (60.0 / (parseInt(bpm) || 120));
|
||||
const newStart = Math.max(beatSec, Math.min(time, resize.originalStart + resize.originalDuration - 0.1));
|
||||
const end = resize.originalStart + resize.originalDuration;
|
||||
const newDuration = end - newStart;
|
||||
if (newDuration < 0.1) return t;
|
||||
@@ -10406,7 +10453,10 @@ const App = () => {
|
||||
? { ...item, start: newStart, duration: newDuration }
|
||||
: { ...item, startTime: newStart, duration: newDuration };
|
||||
} else {
|
||||
const newDuration = Math.max(0.1, time - resize.originalStart);
|
||||
const secondsPerBar = (60.0 / (parseInt(bpm) || 120)) * 4;
|
||||
const marginBar = maxDurationRef.current - secondsPerBar;
|
||||
const clampedTime = Math.min(time, marginBar);
|
||||
const newDuration = Math.max(0.1, clampedTime - resize.originalStart);
|
||||
items[idx] = { ...item, duration: newDuration };
|
||||
}
|
||||
return resize.itemType === 'section'
|
||||
@@ -10950,7 +11000,8 @@ const App = () => {
|
||||
|
||||
// ── Insert Section at Playhead ──
|
||||
const insertSectionAtPlayhead = () => {
|
||||
const track = tracks.find(t => t.id === selectedTrackId);
|
||||
const curTracks = activeTracks;
|
||||
const track = curTracks.find(t => t.id === selectedTrackId);
|
||||
if (!track) { showToast('Chọn track trước', 'warning'); return; }
|
||||
const section = {
|
||||
id: `sec_${Date.now()}`,
|
||||
@@ -10959,7 +11010,7 @@ const App = () => {
|
||||
duration: 4,
|
||||
color: track.color || '#06b6d4'
|
||||
};
|
||||
setTracks(prev => prev.map(t => t.id === selectedTrackId ? {
|
||||
updateActiveTracks(prev => prev.map(t => t.id === selectedTrackId ? {
|
||||
...t,
|
||||
sections: [...(t.sections || []), section]
|
||||
} : t));
|
||||
@@ -11970,7 +12021,7 @@ const App = () => {
|
||||
if (!prompt) { showToast('Vui lòng nhập nội dung prompt.', 'warning'); return; }
|
||||
if (window.DAWCommandDispatcher) {
|
||||
window.DAWCommandDispatcher.currentSelectedTrackId = selectedTrackId;
|
||||
window.DAWCommandDispatcher.currentTracks = tracks;
|
||||
window.DAWCommandDispatcher.currentTracks = activeTracks;
|
||||
window.DAWCommandDispatcher.lastCutSourceTrackId = null;
|
||||
window.DAWCommandDispatcher.lastCutNewTrackId = null;
|
||||
}
|
||||
@@ -11994,7 +12045,7 @@ const App = () => {
|
||||
const model = provider.model_name || provider.model || 'deepseek-chat';
|
||||
setAiActionLog(prev => [...prev, { type: 'info', text: ` Provider: ${provider.name || 'default'} | Model: ${model} | URL: ${baseUrl.slice(0, 40)}`, time: Date.now() }]);
|
||||
const dawContext = window.AIGateway.buildAIPromptContext({
|
||||
tracks, bpm, selectedTrackId, currentTime, selLeft, selRight
|
||||
tracks: activeTracks, bpm, selectedTrackId, currentTime, selLeft, selRight
|
||||
});
|
||||
|
||||
let matchedInstruction = '';
|
||||
@@ -15659,6 +15710,50 @@ const App = () => {
|
||||
className: "text-purple-400 text-xs font-semibold font-mono ml-auto pl-8"
|
||||
}, "Ctrl+M")), /*#__PURE__*/React.createElement("div", {
|
||||
className: "h-px bg-zinc-700 my-1"
|
||||
}), /*#__PURE__*/React.createElement("div", {
|
||||
className: "px-3 py-0.5 text-[10px] text-zinc-500 font-bold uppercase tracking-wider"
|
||||
}, "Insert"), /*#__PURE__*/React.createElement("button", {
|
||||
onClick: () => { insertSectionAtPlayhead(); closeContextMenu(); },
|
||||
className: "w-full px-3 py-1.5 text-xs text-zinc-200 hover:bg-zinc-700 text-left flex items-center gap-2"
|
||||
}, /*#__PURE__*/React.createElement("span", {
|
||||
className: "inline-flex items-center shrink-0"
|
||||
}, /*#__PURE__*/React.createElement("i", {
|
||||
"data-lucide": "folder-plus",
|
||||
className: "w-3.5 h-3.5 text-amber-400 shrink-0"
|
||||
})), /*#__PURE__*/React.createElement("span", {
|
||||
className: "flex-1"
|
||||
}, "Insert Section")), /*#__PURE__*/React.createElement("button", {
|
||||
onClick: () => { insertMidiItemAtPlayhead(); closeContextMenu(); },
|
||||
className: "w-full px-3 py-1.5 text-xs text-zinc-200 hover:bg-zinc-700 text-left flex items-center gap-2"
|
||||
}, /*#__PURE__*/React.createElement("span", {
|
||||
className: "inline-flex items-center shrink-0"
|
||||
}, /*#__PURE__*/React.createElement("i", {
|
||||
"data-lucide": "music",
|
||||
className: "w-3.5 h-3.5 text-purple-400 shrink-0"
|
||||
})), /*#__PURE__*/React.createElement("span", {
|
||||
className: "flex-1"
|
||||
}, "Insert MIDI Item")), /*#__PURE__*/React.createElement("button", {
|
||||
onClick: () => { insertSoundClipAtCursor(); closeContextMenu(); },
|
||||
className: "w-full px-3 py-1.5 text-xs text-zinc-200 hover:bg-zinc-700 text-left flex items-center gap-2"
|
||||
}, /*#__PURE__*/React.createElement("span", {
|
||||
className: "inline-flex items-center shrink-0"
|
||||
}, /*#__PURE__*/React.createElement("i", {
|
||||
"data-lucide": "file-audio",
|
||||
className: "w-3.5 h-3.5 text-emerald-400 shrink-0"
|
||||
})), /*#__PURE__*/React.createElement("span", {
|
||||
className: "flex-1"
|
||||
}, "Insert Sound Clip")), /*#__PURE__*/React.createElement("button", {
|
||||
onClick: () => { insertTrackBelow(); closeContextMenu(); },
|
||||
className: "w-full px-3 py-1.5 text-xs text-zinc-200 hover:bg-zinc-700 text-left flex items-center gap-2"
|
||||
}, /*#__PURE__*/React.createElement("span", {
|
||||
className: "inline-flex items-center shrink-0"
|
||||
}, /*#__PURE__*/React.createElement("i", {
|
||||
"data-lucide": "plus-square",
|
||||
className: "w-3.5 h-3.5 text-cyan-400 shrink-0"
|
||||
})), /*#__PURE__*/React.createElement("span", {
|
||||
className: "flex-1"
|
||||
}, "Insert Track")), /*#__PURE__*/React.createElement("div", {
|
||||
className: "h-px bg-zinc-700 my-1"
|
||||
}), /*#__PURE__*/React.createElement("button", {
|
||||
onClick: contextMenuCopy,
|
||||
className: "w-full px-3 py-1.5 text-xs text-zinc-200 hover:bg-zinc-700 text-left flex items-center gap-2"
|
||||
|
||||
Reference in New Issue
Block a user