feat: Update AudioPlayer and LyricSearch components for improved functionality and user experience

This commit is contained in:
2025-11-25 13:05:37 -05:00
parent 8500cd6e67
commit ee25ad243c
5 changed files with 133 additions and 122 deletions

View File

@@ -55,6 +55,8 @@ export function LyricSearchInputField({ id, placeholder, setShowLyrics }) {
const [inputStatus, setInputStatus] = useState("hint");
const searchToastRef = useRef(null);
const autoCompleteRef = useRef(null);
const autoCompleteInputRef = useRef(null);
const searchButtonRef = useRef(null);
const [theme, setTheme] = useState(document.documentElement.getAttribute("data-theme") || "light");
const statusLabels = {
hint: "Format: Artist - Song",
@@ -196,6 +198,7 @@ export function LyricSearchInputField({ id, placeholder, setShowLyrics }) {
if (autoCompleteRef.current) {
autoCompleteRef.current.hide();
}
autoCompleteInputRef.current?.blur(); // blur early so the suggestion panel closes immediately
const evaluation = evaluateSearchValue(searchValue);
if (!evaluation?.valid) {
@@ -209,13 +212,19 @@ export function LyricSearchInputField({ id, placeholder, setShowLyrics }) {
setLyricsResult(null);
setShowLyrics(false);
if (!toast.isActive('lyrics-searching-toast')) {
searchToastRef.current = toast.info("Searching...", {
toastId: "lyrics-searching-toast"
});
}
const toastId = "lyrics-searching-toast";
toast.dismiss(toastId);
searchToastRef.current = toast.info("Searching...", {
toastId,
});
const startTime = Date.now();
const dismissSearchToast = () => {
if (searchToastRef.current) {
toast.dismiss(searchToastRef.current);
searchToastRef.current = null;
}
};
try {
const res = await fetch(`${API_URL}/lyric/search`, {
@@ -230,6 +239,10 @@ export function LyricSearchInputField({ id, placeholder, setShowLyrics }) {
}),
});
if (res.status === 429) {
throw new Error("Too many requests. Please wait a moment and try again.");
}
const data = await res.json();
if (!res.ok || !data.lyrics) {
throw new Error(data.errorText || "Unknown error.");
@@ -244,21 +257,22 @@ export function LyricSearchInputField({ id, placeholder, setShowLyrics }) {
const hash = `#${encodeURIComponent(data.artist)}/${encodeURIComponent(data.song)}`;
window.history.pushState(null, '', hash);
toast.update(searchToastRef.current, {
type: "success",
render: `Found! (Took ${duration}s)`,
className: "Toastify__toast--success",
dismissSearchToast();
toast.success(`Found! (Took ${duration}s)`, {
autoClose: 2500,
toastId: `lyrics-success-${Date.now()}`,
});
} catch (error) {
toast.update(searchToastRef.current, {
type: "error",
render: error.message,
dismissSearchToast();
toast.error(error.message, {
icon: () => "😕",
autoClose: 5000,
});
} finally {
setIsLoading(false);
autoCompleteInputRef.current?.blur();
searchButtonRef.current?.blur();
searchToastRef.current = null;
}
};
@@ -329,6 +343,9 @@ export function LyricSearchInputField({ id, placeholder, setShowLyrics }) {
className={`lyric-search-input ${inputStatus === "error" ? "has-error" : ""} ${inputStatus === "ready" ? "has-ready" : ""}`}
aria-invalid={inputStatus === "error"}
aria-label={`Lyric search input. ${statusTitle}`}
inputRef={(el) => {
autoCompleteInputRef.current = el;
}}
aria-controls="lyric-search-input"
/>
<span
@@ -343,14 +360,18 @@ export function LyricSearchInputField({ id, placeholder, setShowLyrics }) {
{statusTitle}
</span>
</div>
<Button onClick={() => handleSearch()} className="btn">
<Button
onClick={() => handleSearch()}
className="btn"
ref={searchButtonRef}
>
Search
</Button>
<div className="mt-4">
Exclude:<br />
<div id="exclude-checkboxes">
<UICheckbox id="excl-Genius" label="Genius" onToggle={toggleExclusion} />
<UICheckbox id="excl-LRCLib" label="LRCLib" onToggle={toggleExclusion} />
<UICheckbox id="excl-LRCLib-Cache" label="LRCLib-Cache" onToggle={toggleExclusion} />
<UICheckbox id="excl-Cache" label="Cache" onToggle={toggleExclusion} />
</div>
</div>
@@ -373,6 +394,7 @@ export function LyricSearchInputField({ id, placeholder, setShowLyrics }) {
type="button"
className={`text-size-btn text-size-large ${textSize === "large" ? "active" : ""}`}
onClick={() => setTextSize("large")}
title="Larger text"
>
Aa
</button>
@@ -380,6 +402,7 @@ export function LyricSearchInputField({ id, placeholder, setShowLyrics }) {
type="button"
className={`text-size-btn ${textSize === "normal" ? "active" : ""}`}
onClick={() => setTextSize("normal")}
title="Default text"
>
Aa
</button>
@@ -389,6 +412,7 @@ export function LyricSearchInputField({ id, placeholder, setShowLyrics }) {
variant="plain"
color="neutral"
aria-label="Copy lyrics"
title="Copy lyrics"
className="lyrics-action-button"
onClick={handleCopyLyrics}
>
@@ -399,6 +423,7 @@ export function LyricSearchInputField({ id, placeholder, setShowLyrics }) {
variant="plain"
color="neutral"
aria-label="Copy lyric link"
title="Copy shareable link"
className="lyrics-action-button"
onClick={handleCopyLink}
>