'use client'; import { useCallback, useEffect, useRef, useState } from 'react'; import { alpha, useThemeColor } from '@djangocfg/ui-core/styles/palette'; import { usePlayerAudio, usePlayerControls } from '../../context/selectors'; import { useElementWidth } from '../../hooks/useResizeObserver'; import { usePlayheadLoop } from '../../hooks/usePlayheadLoop'; import { useThemeWatcher } from '../../hooks/useThemeWatcher'; import { attachHover, attachSeek } from './waveformInteraction'; import { paintPeaks } from './waveformRenderer'; type Props = { peaks: Float32Array; height: number; barWidth: number; barGap: number; seekStartsPlayback?: boolean; }; export function PeaksWaveform({ peaks, height, barWidth, barGap, seekStartsPlayback }: Props) { const audio = usePlayerAudio(); const controls = usePlayerControls(); // State-backed ref so child hooks re-run once the DOM node attaches. const [container, setContainer] = useState(null); const bgCanvasRef = useRef(null); const fgCanvasRef = useRef(null); const width = useElementWidth(container); const fgHex = useThemeColor('primary'); const mutedHex = useThemeColor('muted-foreground'); const repaint = useCallback(() => { const bg = bgCanvasRef.current; const fg = fgCanvasRef.current; if (!bg || !fg) return; paintPeaks(bg, peaks, { color: alpha(mutedHex, 0.4), barWidth, barGap, minBarHeight: 1 }); paintPeaks(fg, peaks, { color: fgHex, barWidth, barGap, minBarHeight: 1 }); }, [peaks, barWidth, barGap, fgHex, mutedHex]); useEffect(repaint, [repaint, width]); useThemeWatcher(repaint); usePlayheadLoop(audio, container, true); useEffect(() => { if (!container) return; const detachSeek = attachSeek(container, audio, { startsPlayback: seekStartsPlayback, onPlayRequest: () => void controls.play(), }); const detachHover = attachHover(container, audio); return () => { detachSeek(); detachHover(); }; }, [audio, container, controls, seekStartsPlayback]); return (