feat(waveform): add live hover preview for selection positioning

This commit is contained in:
Hördle Bot
2025-11-23 01:18:59 +01:00
parent 54f47a9470
commit ec885212a5

View File

@@ -19,6 +19,7 @@ export default function WaveformEditor({ audioUrl, startTime, duration, unlockSt
const [zoom, setZoom] = useState(1); // 1 = full view, higher = zoomed in const [zoom, setZoom] = useState(1); // 1 = full view, higher = zoomed in
const [viewOffset, setViewOffset] = useState(0); // Offset in seconds for panning const [viewOffset, setViewOffset] = useState(0); // Offset in seconds for panning
const [playbackPosition, setPlaybackPosition] = useState<number | null>(null); // Current playback position in seconds const [playbackPosition, setPlaybackPosition] = useState<number | null>(null); // Current playback position in seconds
const [hoverPreviewTime, setHoverPreviewTime] = useState<number | null>(null); // Preview position on hover
const audioContextRef = useRef<AudioContext | null>(null); const audioContextRef = useRef<AudioContext | null>(null);
const sourceRef = useRef<AudioBufferSourceNode | null>(null); const sourceRef = useRef<AudioBufferSourceNode | null>(null);
const playbackStartTimeRef = useRef<number>(0); // When playback started const playbackStartTimeRef = useRef<number>(0); // When playback started
@@ -134,6 +135,24 @@ export default function WaveformEditor({ audioUrl, startTime, duration, unlockSt
}); });
ctx.setLineDash([]); ctx.setLineDash([]);
// Draw hover preview (semi-transparent)
if (hoverPreviewTime !== null) {
const previewStartPx = ((hoverPreviewTime - visibleStart) / visibleDuration) * width;
const previewWidthPx = (duration / visibleDuration) * width;
if (previewStartPx + previewWidthPx > 0 && previewStartPx < width) {
ctx.fillStyle = 'rgba(16, 185, 129, 0.2)'; // Light green
ctx.fillRect(Math.max(0, previewStartPx), 0, Math.min(previewWidthPx, width - previewStartPx), height);
// Draw preview borders
ctx.strokeStyle = '#10b981';
ctx.lineWidth = 2;
ctx.setLineDash([5, 5]);
ctx.strokeRect(Math.max(0, previewStartPx), 0, Math.min(previewWidthPx, width - previewStartPx), height);
ctx.setLineDash([]);
}
}
// Draw playback cursor // Draw playback cursor
if (playbackPosition !== null) { if (playbackPosition !== null) {
const cursorPx = ((playbackPosition - visibleStart) / visibleDuration) * width; const cursorPx = ((playbackPosition - visibleStart) / visibleDuration) * width;
@@ -156,7 +175,7 @@ export default function WaveformEditor({ audioUrl, startTime, duration, unlockSt
} }
} }
}, [audioBuffer, startTime, duration, audioDuration, zoom, viewOffset, unlockSteps, playbackPosition]); }, [audioBuffer, startTime, duration, audioDuration, zoom, viewOffset, unlockSteps, playbackPosition, hoverPreviewTime]);
const handleCanvasClick = (e: React.MouseEvent<HTMLCanvasElement>) => { const handleCanvasClick = (e: React.MouseEvent<HTMLCanvasElement>) => {
if (!canvasRef.current || !audioDuration) return; if (!canvasRef.current || !audioDuration) return;
@@ -176,6 +195,26 @@ export default function WaveformEditor({ audioUrl, startTime, duration, unlockSt
onStartTimeChange(Math.floor(newStartTime)); onStartTimeChange(Math.floor(newStartTime));
}; };
const handleCanvasMouseMove = (e: React.MouseEvent<HTMLCanvasElement>) => {
if (!canvasRef.current || !audioDuration) return;
const rect = canvasRef.current.getBoundingClientRect();
const x = e.clientX - rect.left;
const visibleDuration = audioDuration / zoom;
const visibleStart = Math.max(0, Math.min(viewOffset, audioDuration - visibleDuration));
const hoveredTime = visibleStart + (x / rect.width) * visibleDuration;
// Calculate where the selection would be centered on this point
let previewStartTime = hoveredTime - (duration / 2);
previewStartTime = Math.max(0, Math.min(previewStartTime, audioDuration - duration));
setHoverPreviewTime(previewStartTime);
};
const handleCanvasMouseLeave = () => {
setHoverPreviewTime(null);
};
const stopPlayback = () => { const stopPlayback = () => {
sourceRef.current?.stop(); sourceRef.current?.stop();
setIsPlaying(false); setIsPlaying(false);
@@ -337,6 +376,8 @@ export default function WaveformEditor({ audioUrl, startTime, duration, unlockSt
width={800} width={800}
height={150} height={150}
onClick={handleCanvasClick} onClick={handleCanvasClick}
onMouseMove={handleCanvasMouseMove}
onMouseLeave={handleCanvasMouseLeave}
style={{ style={{
width: '100%', width: '100%',
height: 'auto', height: 'auto',