diff --git a/components/WaveformEditor.tsx b/components/WaveformEditor.tsx index 972ef7a..469b0a5 100644 --- a/components/WaveformEditor.tsx +++ b/components/WaveformEditor.tsx @@ -19,6 +19,7 @@ export default function WaveformEditor({ audioUrl, startTime, duration, unlockSt const [zoom, setZoom] = useState(1); // 1 = full view, higher = zoomed in const [viewOffset, setViewOffset] = useState(0); // Offset in seconds for panning const [playbackPosition, setPlaybackPosition] = useState(null); // Current playback position in seconds + const [hoverPreviewTime, setHoverPreviewTime] = useState(null); // Preview position on hover const audioContextRef = useRef(null); const sourceRef = useRef(null); const playbackStartTimeRef = useRef(0); // When playback started @@ -134,6 +135,24 @@ export default function WaveformEditor({ audioUrl, startTime, duration, unlockSt }); 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 if (playbackPosition !== null) { 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) => { if (!canvasRef.current || !audioDuration) return; @@ -176,6 +195,26 @@ export default function WaveformEditor({ audioUrl, startTime, duration, unlockSt onStartTimeChange(Math.floor(newStartTime)); }; + const handleCanvasMouseMove = (e: React.MouseEvent) => { + 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 = () => { sourceRef.current?.stop(); setIsPlaying(false); @@ -337,6 +376,8 @@ export default function WaveformEditor({ audioUrl, startTime, duration, unlockSt width={800} height={150} onClick={handleCanvasClick} + onMouseMove={handleCanvasMouseMove} + onMouseLeave={handleCanvasMouseLeave} style={{ width: '100%', height: 'auto',