import { useEffect, useMemo, useRef } from "react"; import { Music2, X } from "lucide-react"; import { Button } from "./Button"; import styles from "./LyricsPanel.module.css"; type LyricsPanelProps = { title?: string; artist?: string; plain?: string; synced?: string; instrumental?: boolean; positionMs: number; loading?: boolean; error?: string; onClose: () => void; }; type SyncedLine = { timeMs: number; text: string; }; function parseSynced(value?: string) { if (!value) return [] as SyncedLine[]; return value .split(/\r?\n/) .map((line) => { const match = line.match(/^\[(\d{1,2}):(\d{2})(?:[.:](\d{1,3}))?\](.*)$/); if (!match) return undefined; const minutes = Number(match[1]); const seconds = Number(match[2]); const fraction = match[3] || "0"; const millis = fraction.length === 3 ? Number(fraction) : fraction.length === 2 ? Number(fraction) * 10 : Number(fraction) * 100; return { timeMs: (minutes * 60 + seconds) * 1000 + millis, text: match[4].trim(), }; }) .filter((line): line is SyncedLine => Boolean(line)); } export function LyricsPanel({ title, artist, plain, synced, instrumental, positionMs, loading, error, onClose, }: LyricsPanelProps) { const lines = useMemo(() => parseSynced(synced), [synced]); const activeIndex = useMemo(() => { let active = -1; for (let index = 0; index < lines.length; index += 1) { if (lines[index].timeMs <= positionMs + 120) active = index; else break; } return active; }, [lines, positionMs]); const containerRef = useRef(null); useEffect(() => { if (activeIndex < 0 || !containerRef.current) return; const active = containerRef.current.querySelector( `[data-line="${activeIndex}"]`, ); active?.scrollIntoView({ block: "center", behavior: "smooth" }); }, [activeIndex]); return ( ); }