mirror of
https://github.com/meowarex/TidaLuna-Plugins.git
synced 2026-10-15 07:53:13 +11:00
Merge pull request #156 from frostplexx/loading-spinner
feat: add loading lyrics indicator
This commit is contained in:
commit
a50bdea428
2 files changed
+104
-3
No files matched your search
@@ -536,14 +536,59 @@ const toggleRadiantLyrics = function (): void {
|
||||
|
||||
// Create buttons
|
||||
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 => {
|
||||
flushLocked = false;
|
||||
setFlushSpinner(false);
|
||||
const btn = document.querySelector(".flush-lyrics-button") as HTMLButtonElement;
|
||||
if (btn) {
|
||||
btn.disabled = false;
|
||||
btn.style.opacity = "";
|
||||
btn.style.cursor = "";
|
||||
btn.setAttribute("title", "Flush Lyrics");
|
||||
btn.setAttribute("data-tip", "Flush Lyrics");
|
||||
btn.setAttribute("aria-label", "Flush Lyrics");
|
||||
}
|
||||
};
|
||||
@@ -555,15 +600,17 @@ const lockFlush = (): void => {
|
||||
btn.style.opacity = "0.3";
|
||||
btn.style.cursor = "not-allowed";
|
||||
}
|
||||
setFlushSpinner(true);
|
||||
};
|
||||
const disableFlushNoLyrics = (): void => {
|
||||
flushLocked = true;
|
||||
setFlushSpinner(false);
|
||||
const btn = document.querySelector(".flush-lyrics-button") as HTMLButtonElement;
|
||||
if (btn) {
|
||||
btn.disabled = true;
|
||||
btn.style.opacity = "0.3";
|
||||
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");
|
||||
}
|
||||
};
|
||||
@@ -584,9 +631,11 @@ const flushLyrics = async (): Promise<void> => {
|
||||
if (!result.ok) {
|
||||
if (result.notFound) {
|
||||
toast("No lyrics found for this track");
|
||||
unlockFlush();
|
||||
return;
|
||||
}
|
||||
toastErr(`Flush failed (${result.status})`);
|
||||
unlockFlush();
|
||||
return;
|
||||
}
|
||||
const data = result.data;
|
||||
@@ -602,9 +651,12 @@ const flushLyrics = async (): Promise<void> => {
|
||||
cachedLyricsKey = null;
|
||||
cachedLyricsData = null;
|
||||
onTrackChange();
|
||||
} else {
|
||||
unlockFlush();
|
||||
}
|
||||
} catch (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.setAttribute("type", "button");
|
||||
flushButton.setAttribute("aria-label", "Flush Lyrics");
|
||||
flushButton.setAttribute("title", "Flush Lyrics");
|
||||
flushButton.setAttribute("data-tip", "Flush Lyrics");
|
||||
flushButton.disabled = true;
|
||||
flushButton.style.opacity = "0.3";
|
||||
flushButton.style.cursor = "not-allowed";
|
||||
@@ -640,6 +692,12 @@ const flushBtn = function (): void {
|
||||
svgEl.appendChild(useEl);
|
||||
spanWrapper.appendChild(svgEl);
|
||||
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();
|
||||
|
||||
@@ -4009,6 +4067,7 @@ const onTrackChange = async (): Promise<void> => {
|
||||
if (token !== trackChangeToken) return;
|
||||
if (!trackInfo) {
|
||||
trace.log("could not get track info from playback state");
|
||||
setFlushSpinner(false);
|
||||
return;
|
||||
}
|
||||
const nativeHasLyrics = trackHasNativeLyrics(trackInfo.trackId);
|
||||
|
||||
@@ -49,6 +49,48 @@
|
||||
/* MARKER: Lyrics Menu CSS */
|
||||
|
||||
/* 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) {
|
||||
position: relative !important;
|
||||
padding-right: 38px !important;
|
||||
|
||||
Reference in new issue
Block a user