fix: volume slider and mute button race conditions
- Mute button click no longer triggers node drag (priority check in onMouseDown) - Volume slider clamped to 0-100% in both display and API - Added draggable circle handle on slider end - Volume drag state tracked globally, not per-element - Backend: fixed spa_pod_builder API usage (push_object/pop with frame) - Volume calculated from SVG coordinates properly via svgPoint conversion
This commit is contained in:
@@ -214,6 +214,22 @@
|
||||
return null;
|
||||
}
|
||||
|
||||
// Volume drag state
|
||||
let volumeDragging = $state<{ nodeId: number } | null>(null);
|
||||
|
||||
function applyVolumeAtMouse(e: MouseEvent, nodeId: number) {
|
||||
const nd = graphNodes.find(n => n.id === nodeId);
|
||||
if (!nd || !svgEl) return;
|
||||
const volX = nd.x + 8;
|
||||
const volW = nd.width - 36;
|
||||
const svgRect = svgEl.getBoundingClientRect();
|
||||
// Convert mouse X to SVG coordinate
|
||||
const mouseSvgX = viewBox.x + (e.clientX - svgRect.left) * viewBox.w / svgRect.width;
|
||||
const ratio = (mouseSvgX - volX) / volW;
|
||||
const clamped = Math.max(0, Math.min(1, ratio));
|
||||
setNodeVolume(nodeId, clamped);
|
||||
}
|
||||
|
||||
// Mouse handlers
|
||||
function onMouseDown(e: MouseEvent) {
|
||||
contextMenu = null;
|
||||
@@ -221,6 +237,29 @@
|
||||
const pt = svgPoint(e);
|
||||
const target = e.target as HTMLElement;
|
||||
|
||||
// Volume slider handle - highest priority
|
||||
if (target.classList.contains('vol-handle') || target.classList.contains('vol-bar') || target.classList.contains('vol-bg')) {
|
||||
const nodeEl = target.closest('.node-group');
|
||||
if (nodeEl) {
|
||||
const nodeId = Number((nodeEl as HTMLElement).dataset.nodeId);
|
||||
volumeDragging = { nodeId };
|
||||
// Immediately apply volume at click position
|
||||
applyVolumeAtMouse(e, nodeId);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Mute button
|
||||
if (target.classList.contains('mute-btn') || target.classList.contains('mute-text')) {
|
||||
const nodeEl = target.closest('.node-group');
|
||||
if (nodeEl) {
|
||||
const nodeId = Number((nodeEl as HTMLElement).dataset.nodeId);
|
||||
const nd = graphNodes.find(n => n.id === nodeId);
|
||||
if (nd) setNodeMute(nodeId, !nd.mute);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (target.classList.contains('port-circle')) {
|
||||
const portId = Number(target.dataset.portId);
|
||||
const port = $portById.get(portId);
|
||||
@@ -253,6 +292,11 @@
|
||||
}
|
||||
|
||||
function onMouseMove(e: MouseEvent) {
|
||||
// Volume dragging takes priority
|
||||
if (volumeDragging) {
|
||||
applyVolumeAtMouse(e, volumeDragging.nodeId);
|
||||
return;
|
||||
}
|
||||
if (!dragging && !connecting) return;
|
||||
const pt = svgPoint(e);
|
||||
if (connecting) {
|
||||
@@ -272,6 +316,10 @@
|
||||
}
|
||||
|
||||
function onMouseUp(e: MouseEvent) {
|
||||
if (volumeDragging) {
|
||||
volumeDragging = null;
|
||||
return;
|
||||
}
|
||||
if (connecting) {
|
||||
const target = e.target as HTMLElement;
|
||||
if (target.classList.contains('port-circle')) {
|
||||
@@ -479,44 +527,30 @@
|
||||
{/each}
|
||||
|
||||
<!-- Volume slider + mute button at bottom of node -->
|
||||
<!-- Mute button -->
|
||||
<rect
|
||||
x={nd.x + nd.width - 24} y={nd.y + nd.height - 17} width="16" height="12" rx="2"
|
||||
fill={nd.mute ? '#a44' : '#2a2a3e'} stroke="#555" stroke-width="0.5"
|
||||
style="cursor:pointer"
|
||||
onclick={() => setNodeMute(nd.id, !nd.mute)}
|
||||
class="mute-btn"
|
||||
x={nd.x + nd.width - 24} y={nd.y + nd.height - 17}
|
||||
width="16" height="12" rx="2"
|
||||
fill={nd.mute ? '#a44' : '#2a2a3e'}
|
||||
stroke="#555" stroke-width="0.5"
|
||||
/>
|
||||
<text
|
||||
class="mute-text"
|
||||
x={nd.x + nd.width - 16} y={nd.y + nd.height - 8}
|
||||
font-size="8" font-family="monospace" fill={nd.mute ? '#fff' : '#888'}
|
||||
text-anchor="middle" style="pointer-events:none"
|
||||
font-size="8" font-family="monospace"
|
||||
fill={nd.mute ? '#fff' : '#888'}
|
||||
text-anchor="middle"
|
||||
>{nd.mute ? 'M' : 'm'}</text>
|
||||
|
||||
<!-- Volume bar background -->
|
||||
<rect x={nd.x + 8} y={nd.y + nd.height - 12} width={nd.width - 36} height="3" rx="1.5" fill="#333" />
|
||||
<!-- Volume bar fill -->
|
||||
<rect x={nd.x + 8} y={nd.y + nd.height - 12} width={(nd.width - 36) * Math.min(nd.volume, 1)} height="3" rx="1.5" fill={nd.mute ? '#666' : '#4a9'} />
|
||||
<!-- Volume slider hit area -->
|
||||
<rect
|
||||
x={nd.x + 8} y={nd.y + nd.height - 16} width={nd.width - 36} height="11"
|
||||
fill="transparent" style="cursor:pointer"
|
||||
onmousedown={(e) => {
|
||||
e.stopPropagation();
|
||||
const svgRect = svgEl?.getBoundingClientRect();
|
||||
if (!svgRect) return;
|
||||
const vbx = nd.x + 8;
|
||||
const vbw = nd.width - 36;
|
||||
const onMove = (ev: MouseEvent) => {
|
||||
const ratio = (ev.clientX - svgRect.left - (vbx - viewBox.x) * svgRect.width / viewBox.w) / (vbw * svgRect.width / viewBox.w);
|
||||
setNodeVolume(nd.id, Math.max(0, Math.min(1.5, ratio)));
|
||||
};
|
||||
const onUp = () => { window.removeEventListener('mousemove', onMove); window.removeEventListener('mouseup', onUp); };
|
||||
window.addEventListener('mousemove', onMove);
|
||||
window.addEventListener('mouseup', onUp);
|
||||
}}
|
||||
/>
|
||||
<rect class="vol-bg" x={nd.x + 8} y={nd.y + nd.height - 12} width={nd.width - 36} height="4" rx="2" fill="#333" />
|
||||
<!-- Volume bar fill (clamped to 1.0 for display) -->
|
||||
<rect class="vol-bar" x={nd.x + 8} y={nd.y + nd.height - 12} width={(nd.width - 36) * Math.max(0, Math.min(1, nd.volume))} height="4" rx="2" fill={nd.mute ? '#666' : '#4a9'} />
|
||||
<!-- Volume handle circle -->
|
||||
<circle class="vol-handle" cx={nd.x + 8 + (nd.width - 36) * Math.max(0, Math.min(1, nd.volume))} cy={nd.y + nd.height - 10} r="5" fill={nd.mute ? '#888' : '#6cb'} stroke="#fff" stroke-width="1" />
|
||||
<!-- Volume % label -->
|
||||
<text x={nd.x + 8} y={nd.y + nd.height - 14} font-size="7" font-family="monospace" fill="#666">
|
||||
{Math.round(nd.volume * 100)}%
|
||||
<text x={nd.x + 8} y={nd.y + nd.height - 15} font-size="7" font-family="monospace" fill="#888">
|
||||
{Math.round(Math.max(0, Math.min(1, nd.volume)) * 100)}%
|
||||
</text>
|
||||
|
||||
</g>
|
||||
@@ -758,4 +792,12 @@
|
||||
.port-circle:hover { filter: brightness(1.5); }
|
||||
.edge-path { cursor: pointer; pointer-events: stroke; }
|
||||
.edge-path:hover { stroke-width: 4; }
|
||||
|
||||
.mute-btn { cursor: pointer; }
|
||||
.mute-btn:hover { filter: brightness(1.3); }
|
||||
.mute-text { pointer-events: none; }
|
||||
.vol-bg { pointer-events: none; }
|
||||
.vol-bar { pointer-events: none; }
|
||||
.vol-handle { cursor: ew-resize; }
|
||||
.vol-handle:hover { filter: brightness(1.3); r: 6; }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user