feat(waveform): add live hover preview for selection positioning
This commit is contained in:
@@ -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<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 sourceRef = useRef<AudioBufferSourceNode | null>(null);
|
||||
const playbackStartTimeRef = useRef<number>(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<HTMLCanvasElement>) => {
|
||||
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<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 = () => {
|
||||
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',
|
||||
|
||||
Reference in New Issue
Block a user