import { CircularProgress } from "@mui/joy"; import React, { forwardRef, useImperativeHandle, useEffect, useRef, useState, } from "react"; import Alert from '@mui/joy/Alert'; import Box from '@mui/joy/Box'; import Button from "@mui/joy/Button"; import Checkbox from "@mui/joy/Checkbox"; import ContentCopyIcon from '@mui/icons-material/ContentCopy'; import { AutoComplete } from 'primereact/autocomplete'; import { API_URL } from '../config'; export default function LyricSearch() { const [showLyrics, setShowLyrics] = useState(false); return (

Lyric Search

); } export function LyricSearchInputField({ id, placeholder, setShowLyrics }) { const [value, setValue] = useState(""); const [suggestions, setSuggestions] = useState([]); const [alertVisible, setAlertVisible] = useState(false); const [isLoading, setIsLoading] = useState(false); const [excludedSources, setExcludedSources] = useState([]); const [lyricsResult, setLyricsResult] = useState(null); const autoCompleteRef = useRef(null); const [theme, setTheme] = useState(document.documentElement.getAttribute("data-theme") || "light"); useEffect(() => { const handler = (e) => { const newTheme = e.detail; setTheme(newTheme); }; document.addEventListener('set-theme', handler); return () => { document.removeEventListener('set-theme', handler); }; }, []); // Typeahead: fetch suggestions const fetchSuggestions = async (event) => { const query = event.query; const res = await fetch(`${API_URL}/typeahead/lyrics`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ query }), }); const json = await res.json(); setSuggestions(json); }; // Toggle exclusion state for checkboxes const toggleExclusion = (source) => { const lower = source.toLowerCase(); setExcludedSources((prev) => prev.includes(lower) ? prev.filter((s) => s !== lower) : [...prev, lower] ); }; // Show scrollable dropdown panel with mouse wheel handling const handlePanelShow = () => { setTimeout(() => { const panel = document.querySelector(".p-autocomplete-panel"); const items = panel?.querySelector(".p-autocomplete-items"); if (items) { items.style.maxHeight = "200px"; items.style.overflowY = "auto"; items.style.overscrollBehavior = "contain"; const wheelHandler = (e) => { const delta = e.deltaY; const atTop = items.scrollTop === 0; const atBottom = items.scrollTop + items.clientHeight >= items.scrollHeight; if ((delta < 0 && atTop) || (delta > 0 && atBottom)) { e.preventDefault(); } else { e.stopPropagation(); } }; items.removeEventListener("wheel", wheelHandler); items.addEventListener("wheel", wheelHandler, { passive: false }); } }, 0); }; const handleSearch = async () => { if (autoCompleteRef.current) { autoCompleteRef.current.hide(); } if (!value.includes(" - ")) { setAlertVisible(true); return; } const [artist, song] = value.split(" - ", 2).map((v) => v.trim()); if (!artist || !song) { setAlertVisible(true); return; } setAlertVisible(false); setIsLoading(true); setLyricsResult(null); setShowLyrics(false); const toastId = toast.info("Searching..."); const startTime = Date.now(); try { const res = await fetch(`${API_URL}/lyric/search`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ a: artist, s: song, excluded_sources: excludedSources, src: "Web", extra: true, }), }); const data = await res.json(); if (!res.ok || !data.lyrics) { throw new Error(data.errorText || "Unknown error."); } const duration = ((Date.now() - startTime) / 1000).toFixed(1); setLyricsResult({ artist: data.artist, song: data.song, lyrics: data.lyrics }); setShowLyrics(true); toast.update(toastId, { type: "success", render: `Found! (Took ${duration}s)`, className: "Toastify__toast--success", autoClose: 2500, }); } catch (error) { toast.update(toastId, { type: "error", render: `😕 ${error.message}`, autoClose: 5000, }); } finally { setIsLoading(false); } }; const handleKeyDown = (e) => { if (e.key === "Enter") { e.preventDefault(); handleSearch(); } }; return (
{alertVisible && ( setAlertVisible(false)} sx={{ mb: 2 }} > You must specify both an artist and song to search.
Format: Artist - Song
)} setValue(e.target.value)} onKeyDown={handleKeyDown} onShow={handlePanelShow} placeholder={placeholder} autoFocus size={40} aria-controls="lyric-search-input" />
Exclude:
{isLoading && (
)} {lyricsResult && (
{lyricsResult.artist} - {lyricsResult.song}
)}
); } export const UICheckbox = forwardRef(function UICheckbox(props = {}, ref) { const [checked, setChecked] = useState(false); useImperativeHandle(ref, () => ({ setChecked: (val) => setChecked(val), checked, })); const verifyExclusions = () => { const checkboxes = document.querySelectorAll("#exclude-checkboxes input[type=checkbox]"); const checkedCount = [...checkboxes].filter(cb => cb.checked).length; if (checkedCount === 3) { checkboxes.forEach(cb => cb.click()); toast.error("All sources were excluded; exclusions have been reset."); } }; const handleChange = (e) => { const newChecked = e.target.checked; setChecked(newChecked); if (props.onToggle) { const source = props.label; // Use label as source identifier props.onToggle(source); } verifyExclusions(); }; return (
); }); export function LyricResultBox(opts = {}) { return (
{/* */}
) }