feat(lyrics): make lyrics fully disablable (independent YouLyPlus toggle) (#855)

* feat(lyrics): independent YouLyPlus toggle + all-sources-off state

Replace the binary lyricsMode ('standard' | 'lyricsplus') with an
independent youLyPlusEnabled flag so YouLyPlus and the standard sources
are no longer mutually exclusive — turning one off no longer forces the
other on. YouLyPlus (when on) is tried first with the enabled sources as
fallback; off uses only the enabled sources. When YouLyPlus is off and no
source is enabled, useLyrics fetches nothing (issue #810).

Fresh installs ship with every source off; the rehydrate migration only
restores the old on-by-default set for genuine upgrades, not new installs.

* feat(lyrics): YouLyPlus toggle UI + queue 'no sources' hint

Settings: single YouLyPlus toggle replacing the two mutually exclusive
mode switches; the source list is always visible with a context hint
(fallback vs primary). Queue lyric tab shows a hint when no source is
active. en + de strings; other locales fall back to en.

* docs(changelog): lyrics fully disablable (#855)
This commit is contained in:
Frank Stellmacher
2026-05-22 21:06:04 +02:00
committed by GitHub
parent cb4d331f99
commit 02b2df1589
15 changed files with 145 additions and 66 deletions
+23 -8
View File
@@ -71,11 +71,13 @@ export interface UseLyricsResult {
}
export function useLyrics(currentTrack: Track | null): UseLyricsResult {
const cached = currentTrack ? lyricsCache.get(currentTrack.id) : undefined;
const { lyricsSources, lyricsMode } = useAuthStore(useShallow(s => ({
const { lyricsSources, youLyPlusEnabled } = useAuthStore(useShallow(s => ({
lyricsSources: s.lyricsSources,
lyricsMode: s.lyricsMode,
youLyPlusEnabled: s.youLyPlusEnabled,
})));
// Lyrics are fully off when YouLyPlus is off and no source is enabled.
const lyricsActive = youLyPlusEnabled || lyricsSources.some(s => s.enabled);
const cached = (currentTrack && lyricsActive) ? lyricsCache.get(currentTrack.id) : undefined;
const [loading, setLoading] = useState(!cached && !!currentTrack);
const [syncedLines, setSyncedLines] = useState<LrcLine[] | null>(cached?.syncedLines ?? null);
@@ -87,6 +89,19 @@ export function useLyrics(currentTrack: Track | null): UseLyricsResult {
useEffect(() => {
if (!currentTrack) return;
// Lyrics fully disabled (YouLyPlus off + every source off): fetch nothing,
// show nothing — not even embedded/cache (issue #810). LyricsPane surfaces
// the "no sources selected" hint.
if (!lyricsActive) {
setSyncedLines(null);
setWordLines(null);
setPlainLyrics(null);
setSource(null);
setNotFound(false);
setLoading(false);
return;
}
const hit = lyricsCache.get(currentTrack.id);
if (hit) {
setSyncedLines(hit.syncedLines);
@@ -200,7 +215,7 @@ export function useLyrics(currentTrack: Track | null): UseLyricsResult {
/**
* lyricsplus (YouLyPlus). Silent miss → caller falls back to the standard
* pipeline. Only consumed when `lyricsMode === 'lyricsplus'`.
* pipeline. Only consumed when `youLyPlusEnabled`.
*/
const fetchLyricsPlusFn = async (): Promise<boolean> => {
try {
@@ -258,7 +273,7 @@ export function useLyrics(currentTrack: Track | null): UseLyricsResult {
// Skip for 'lyricsplus' mode since the persisted entry might be from
// the standard pipeline (no word-level sync) and the user explicitly
// wants a fresh lyricsplus attempt.
if (lyricsMode !== 'lyricsplus') {
if (!youLyPlusEnabled) {
const serverId = useAuthStore.getState().activeServerId ?? '';
const persisted = await getCachedLyrics(lyricsCacheKey(serverId, currentTrack.id));
if (cancelled) return;
@@ -270,8 +285,8 @@ export function useLyrics(currentTrack: Track | null): UseLyricsResult {
}
}
// YouLyPlus mode: try lyricsplus first, silent fallback to standard.
if (lyricsMode === 'lyricsplus') {
// YouLyPlus on: try lyricsplus first, silent fallback to enabled sources.
if (youLyPlusEnabled) {
if (cancelled) return;
if (await fetchLyricsPlusFn()) return;
}
@@ -288,7 +303,7 @@ export function useLyrics(currentTrack: Track | null): UseLyricsResult {
})();
return () => { cancelled = true; };
}, [currentTrack?.id, lyricsSources, lyricsMode]); // eslint-disable-line react-hooks/exhaustive-deps
}, [currentTrack?.id, lyricsSources, youLyPlusEnabled]); // eslint-disable-line react-hooks/exhaustive-deps
return { syncedLines, wordLines, plainLyrics, source, loading, notFound };
}