mirror of
https://github.com/meowarex/TidaLuna-Plugins.git
synced 2026-10-14 23:43:13 +11:00
feat: add loading lyrics indicator
This commit is contained in:
1 parent
62b645f918
commit
3d595028cf
2 files changed
+104
-3
No files matched your search
@@ -536,14 +536,59 @@ const toggleRadiantLyrics = function (): void {
|
|||||||
|
|
||||||
// Create buttons
|
// Create buttons
|
||||||
let flushLocked = false;
|
let flushLocked = false;
|
||||||
|
let flushSpinning = false;
|
||||||
|
|
||||||
|
// Swap the flush button icon between the lyrics-sync glyph and a loading spinner
|
||||||
|
const setFlushSpinner = (on: boolean): void => {
|
||||||
|
flushSpinning = on;
|
||||||
|
const btn = document.querySelector(".flush-lyrics-button") as HTMLButtonElement | null;
|
||||||
|
if (!btn) return;
|
||||||
|
const svg = btn.querySelector("svg");
|
||||||
|
if (!svg) return;
|
||||||
|
const svgClass = (svg.getAttribute("class") ?? "")
|
||||||
|
.split(/\s+/)
|
||||||
|
.filter((c) => c && c !== "rl-spinner");
|
||||||
|
if (on) {
|
||||||
|
if (svg.classList.contains("rl-spinner")) return;
|
||||||
|
btn.setAttribute("data-tip", "Loading lyrics…");
|
||||||
|
btn.setAttribute("aria-label", "Loading lyrics…");
|
||||||
|
const spinner = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
||||||
|
if (svgClass.length) spinner.setAttribute("class", svgClass.join(" "));
|
||||||
|
spinner.classList.add("rl-spinner");
|
||||||
|
spinner.setAttribute("viewBox", "0 0 20 20");
|
||||||
|
spinner.setAttribute("aria-hidden", "true");
|
||||||
|
const circle = document.createElementNS("http://www.w3.org/2000/svg", "circle");
|
||||||
|
circle.setAttribute("cx", "10");
|
||||||
|
circle.setAttribute("cy", "10");
|
||||||
|
circle.setAttribute("r", "8");
|
||||||
|
circle.setAttribute(
|
||||||
|
"style",
|
||||||
|
"fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-dasharray:42 8",
|
||||||
|
);
|
||||||
|
spinner.appendChild(circle);
|
||||||
|
svg.replaceWith(spinner);
|
||||||
|
} else {
|
||||||
|
const spinner = btn.querySelector("svg.rl-spinner");
|
||||||
|
if (!spinner) return;
|
||||||
|
const syncSvg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
||||||
|
if (svgClass.length) syncSvg.setAttribute("class", svgClass.join(" "));
|
||||||
|
syncSvg.setAttribute("viewBox", "0 0 20 20");
|
||||||
|
const use = document.createElementNS("http://www.w3.org/2000/svg", "use");
|
||||||
|
use.setAttribute("href", "#general__lyrics-sync");
|
||||||
|
syncSvg.appendChild(use);
|
||||||
|
spinner.replaceWith(syncSvg);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const unlockFlush = (): void => {
|
const unlockFlush = (): void => {
|
||||||
flushLocked = false;
|
flushLocked = false;
|
||||||
|
setFlushSpinner(false);
|
||||||
const btn = document.querySelector(".flush-lyrics-button") as HTMLButtonElement;
|
const btn = document.querySelector(".flush-lyrics-button") as HTMLButtonElement;
|
||||||
if (btn) {
|
if (btn) {
|
||||||
btn.disabled = false;
|
btn.disabled = false;
|
||||||
btn.style.opacity = "";
|
btn.style.opacity = "";
|
||||||
btn.style.cursor = "";
|
btn.style.cursor = "";
|
||||||
btn.setAttribute("title", "Flush Lyrics");
|
btn.setAttribute("data-tip", "Flush Lyrics");
|
||||||
btn.setAttribute("aria-label", "Flush Lyrics");
|
btn.setAttribute("aria-label", "Flush Lyrics");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -555,15 +600,17 @@ const lockFlush = (): void => {
|
|||||||
btn.style.opacity = "0.3";
|
btn.style.opacity = "0.3";
|
||||||
btn.style.cursor = "not-allowed";
|
btn.style.cursor = "not-allowed";
|
||||||
}
|
}
|
||||||
|
setFlushSpinner(true);
|
||||||
};
|
};
|
||||||
const disableFlushNoLyrics = (): void => {
|
const disableFlushNoLyrics = (): void => {
|
||||||
flushLocked = true;
|
flushLocked = true;
|
||||||
|
setFlushSpinner(false);
|
||||||
const btn = document.querySelector(".flush-lyrics-button") as HTMLButtonElement;
|
const btn = document.querySelector(".flush-lyrics-button") as HTMLButtonElement;
|
||||||
if (btn) {
|
if (btn) {
|
||||||
btn.disabled = true;
|
btn.disabled = true;
|
||||||
btn.style.opacity = "0.3";
|
btn.style.opacity = "0.3";
|
||||||
btn.style.cursor = "not-allowed";
|
btn.style.cursor = "not-allowed";
|
||||||
btn.setAttribute("title", "Track has no lyrics");
|
btn.setAttribute("data-tip", "Track has no lyrics");
|
||||||
btn.setAttribute("aria-label", "Track has no lyrics");
|
btn.setAttribute("aria-label", "Track has no lyrics");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -584,9 +631,11 @@ const flushLyrics = async (): Promise<void> => {
|
|||||||
if (!result.ok) {
|
if (!result.ok) {
|
||||||
if (result.notFound) {
|
if (result.notFound) {
|
||||||
toast("No lyrics found for this track");
|
toast("No lyrics found for this track");
|
||||||
|
unlockFlush();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
toastErr(`Flush failed (${result.status})`);
|
toastErr(`Flush failed (${result.status})`);
|
||||||
|
unlockFlush();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const data = result.data;
|
const data = result.data;
|
||||||
@@ -602,9 +651,12 @@ const flushLyrics = async (): Promise<void> => {
|
|||||||
cachedLyricsKey = null;
|
cachedLyricsKey = null;
|
||||||
cachedLyricsData = null;
|
cachedLyricsData = null;
|
||||||
onTrackChange();
|
onTrackChange();
|
||||||
|
} else {
|
||||||
|
unlockFlush();
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toastErr(`Flush error: ${err instanceof Error ? err.message : String(err)}`);
|
toastErr(`Flush error: ${err instanceof Error ? err.message : String(err)}`);
|
||||||
|
unlockFlush();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -622,7 +674,7 @@ const flushBtn = function (): void {
|
|||||||
flushButton.removeAttribute("data-test");
|
flushButton.removeAttribute("data-test");
|
||||||
flushButton.setAttribute("type", "button");
|
flushButton.setAttribute("type", "button");
|
||||||
flushButton.setAttribute("aria-label", "Flush Lyrics");
|
flushButton.setAttribute("aria-label", "Flush Lyrics");
|
||||||
flushButton.setAttribute("title", "Flush Lyrics");
|
flushButton.setAttribute("data-tip", "Flush Lyrics");
|
||||||
flushButton.disabled = true;
|
flushButton.disabled = true;
|
||||||
flushButton.style.opacity = "0.3";
|
flushButton.style.opacity = "0.3";
|
||||||
flushButton.style.cursor = "not-allowed";
|
flushButton.style.cursor = "not-allowed";
|
||||||
@@ -640,6 +692,12 @@ const flushBtn = function (): void {
|
|||||||
svgEl.appendChild(useEl);
|
svgEl.appendChild(useEl);
|
||||||
spanWrapper.appendChild(svgEl);
|
spanWrapper.appendChild(svgEl);
|
||||||
flushButton.appendChild(spanWrapper);
|
flushButton.appendChild(spanWrapper);
|
||||||
|
// Button recreated while lyrics are still loading — show the spinner again
|
||||||
|
if (flushSpinning) {
|
||||||
|
flushButton.setAttribute("data-tip", "Loading lyrics…");
|
||||||
|
flushButton.setAttribute("aria-label", "Loading lyrics…");
|
||||||
|
setFlushSpinner(true);
|
||||||
|
}
|
||||||
|
|
||||||
flushButton.onclick = () => flushLyrics();
|
flushButton.onclick = () => flushLyrics();
|
||||||
|
|
||||||
@@ -4009,6 +4067,7 @@ const onTrackChange = async (): Promise<void> => {
|
|||||||
if (token !== trackChangeToken) return;
|
if (token !== trackChangeToken) return;
|
||||||
if (!trackInfo) {
|
if (!trackInfo) {
|
||||||
trace.log("could not get track info from playback state");
|
trace.log("could not get track info from playback state");
|
||||||
|
setFlushSpinner(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const nativeHasLyrics = trackHasNativeLyrics(trackInfo.trackId);
|
const nativeHasLyrics = trackHasNativeLyrics(trackInfo.trackId);
|
||||||
|
|||||||
@@ -49,6 +49,48 @@
|
|||||||
/* MARKER: Lyrics Menu CSS */
|
/* MARKER: Lyrics Menu CSS */
|
||||||
|
|
||||||
/* Lyrics toggle button */
|
/* Lyrics toggle button */
|
||||||
|
|
||||||
|
/* Flush lyrics button — loading spinner while waiting for lyrics */
|
||||||
|
.flush-lyrics-button svg.rl-spinner {
|
||||||
|
animation: rl-spinner-rotate 0.9s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes rl-spinner-rotate {
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Flush lyrics button — hover tooltip (native title doesn't show reliably) */
|
||||||
|
.flush-lyrics-button {
|
||||||
|
position: relative !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flush-lyrics-button::after {
|
||||||
|
content: attr(data-tip);
|
||||||
|
position: absolute;
|
||||||
|
top: calc(100% + 6px);
|
||||||
|
right: 0;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: rgba(0, 0, 0, 0.85);
|
||||||
|
color: #fff;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 1.3;
|
||||||
|
white-space: nowrap;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 10000;
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
transition: opacity 0.12s ease, transform 0.12s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flush-lyrics-button:hover::after {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
[data-test="toggle-lyrics"]:has(.rl-lyrics-menu-trigger) {
|
[data-test="toggle-lyrics"]:has(.rl-lyrics-menu-trigger) {
|
||||||
position: relative !important;
|
position: relative !important;
|
||||||
padding-right: 38px !important;
|
padding-right: 38px !important;
|
||||||
|
|||||||
Reference in new issue
Block a user