mirror of
https://github.com/kilyabin/psysonic.git
synced 2026-07-22 14:35:41 +00:00
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:
committed by
GitHub
parent
cb4d331f99
commit
02b2df1589
@@ -23,10 +23,14 @@ export default function LyricsPane({ currentTrack }: Props) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { syncedLines, wordLines, plainLyrics, source, loading, notFound } = useLyrics(currentTrack);
|
||||
const { staticOnly, sidebarLyricsStyle } = useAuthStore(useShallow(s => ({
|
||||
const { staticOnly, sidebarLyricsStyle, youLyPlusEnabled, lyricsSources } = useAuthStore(useShallow(s => ({
|
||||
staticOnly: s.lyricsStaticOnly,
|
||||
sidebarLyricsStyle: s.sidebarLyricsStyle,
|
||||
youLyPlusEnabled: s.youLyPlusEnabled,
|
||||
lyricsSources: s.lyricsSources,
|
||||
})));
|
||||
// Lyrics fully off: YouLyPlus off and no source enabled (issue #810).
|
||||
const lyricsDisabled = !youLyPlusEnabled && !lyricsSources.some(s => s.enabled);
|
||||
|
||||
const useWords = !staticOnly && wordLines !== null && wordLines.length > 0;
|
||||
const hasSynced = !staticOnly && !useWords && syncedLines !== null && syncedLines.length > 0;
|
||||
@@ -146,6 +150,14 @@ export default function LyricsPane({ currentTrack }: Props) {
|
||||
);
|
||||
}
|
||||
|
||||
if (lyricsDisabled) {
|
||||
return (
|
||||
<div className="lyrics-pane-empty">
|
||||
<p className="lyrics-status">{t('player.lyricsNoSources')}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const sourceLabel = source === 'server'
|
||||
? t('player.lyricsSourceServer')
|
||||
: source === 'lrclib'
|
||||
|
||||
@@ -36,13 +36,13 @@ export function LyricsSourcesCustomizer() {
|
||||
const { t } = useTranslation();
|
||||
const lyricsSources = useAuthStore(useShallow(s => s.lyricsSources));
|
||||
const setLyricsSources = useAuthStore(s => s.setLyricsSources);
|
||||
const lyricsMode = useAuthStore(s => s.lyricsMode);
|
||||
const setLyricsMode = useAuthStore(s => s.setLyricsMode);
|
||||
const youLyPlusEnabled = useAuthStore(s => s.youLyPlusEnabled);
|
||||
const setYouLyPlusEnabled = useAuthStore(s => s.setYouLyPlusEnabled);
|
||||
const lyricsStaticOnly = useAuthStore(s => s.lyricsStaticOnly);
|
||||
const setLyricsStaticOnly = useAuthStore(s => s.setLyricsStaticOnly);
|
||||
const { isDragging: isPsyDragging } = useDragDrop();
|
||||
// useState (not useRef) so the listener-effect re-runs when the container
|
||||
// gets unmounted/remounted by the {lyricsMode === 'standard'} wrapper.
|
||||
// useState (not useRef) so the listener-effect re-binds if the container
|
||||
// element is ever remounted.
|
||||
const [containerEl, setContainerEl] = useState<HTMLDivElement | null>(null);
|
||||
const [dropTarget, setDropTarget] = useState<LyricsDropTarget>(null);
|
||||
const dropTargetRef = useRef<LyricsDropTarget>(null);
|
||||
@@ -107,48 +107,35 @@ export function LyricsSourcesCustomizer() {
|
||||
{t('settings.lyricsSourcesDesc')}
|
||||
</p>
|
||||
|
||||
{/* Mode switch — standard three-provider pipeline vs. YouLyPlus karaoke.
|
||||
YouLyPlus misses silently fall back to the standard pipeline. */}
|
||||
{/* YouLyPlus (karaoke) — independent toggle. When on it is tried first and
|
||||
the enabled sources below act as fallback; when off only those sources
|
||||
are used. YouLyPlus off + every source off = lyrics fully disabled. */}
|
||||
<div className="settings-card" style={{ marginBottom: '0.75rem' }}>
|
||||
<div className="settings-toggle-row">
|
||||
<div>
|
||||
<div style={{ fontWeight: 500 }}>{t('settings.lyricsModeLyricsplus')}</div>
|
||||
<div style={{ fontSize: 12, color: 'var(--text-muted)' }}>{t('settings.lyricsModeLyricsplusDesc')}</div>
|
||||
<div style={{ fontWeight: 500 }}>{t('settings.lyricsYouLyPlus')}</div>
|
||||
<div style={{ fontSize: 12, color: 'var(--text-muted)' }}>{t('settings.lyricsYouLyPlusDesc')}</div>
|
||||
</div>
|
||||
<label className="toggle-switch" aria-label={t('settings.lyricsModeLyricsplus')}>
|
||||
<label className="toggle-switch" aria-label={t('settings.lyricsYouLyPlus')}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={lyricsMode === 'lyricsplus'}
|
||||
onChange={e => { if (e.target.checked) setLyricsMode('lyricsplus'); else setLyricsMode('standard'); }}
|
||||
/>
|
||||
<span className="toggle-track" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className="settings-card" style={{ marginBottom: '0.75rem' }}>
|
||||
<div className="settings-toggle-row">
|
||||
<div>
|
||||
<div style={{ fontWeight: 500 }}>{t('settings.lyricsModeStandard')}</div>
|
||||
<div style={{ fontSize: 12, color: 'var(--text-muted)' }}>{t('settings.lyricsModeStandardDesc')}</div>
|
||||
</div>
|
||||
<label className="toggle-switch" aria-label={t('settings.lyricsModeStandard')}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={lyricsMode === 'standard'}
|
||||
onChange={e => { if (e.target.checked) setLyricsMode('standard'); else setLyricsMode('lyricsplus'); }}
|
||||
checked={youLyPlusEnabled}
|
||||
onChange={e => setYouLyPlusEnabled(e.target.checked)}
|
||||
/>
|
||||
<span className="toggle-track" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{lyricsMode === 'standard' && (
|
||||
<div
|
||||
className="settings-card"
|
||||
style={{ padding: '4px 0', marginBottom: '0.75rem', marginLeft: '1rem' }}
|
||||
ref={setContainerEl}
|
||||
onMouseMove={handleMouseMove}
|
||||
>
|
||||
<div className="playback-rate-derived" style={{ fontSize: 12, color: 'var(--text-muted)', margin: '0 0 0.4rem' }}>
|
||||
{youLyPlusEnabled ? t('settings.lyricsSourcesFallbackHint') : t('settings.lyricsSourcesPrimaryHint')}
|
||||
</div>
|
||||
<div
|
||||
className="settings-card"
|
||||
style={{ padding: '4px 0', marginBottom: '0.75rem' }}
|
||||
ref={setContainerEl}
|
||||
onMouseMove={handleMouseMove}
|
||||
>
|
||||
{lyricsSources.map((src, i) => {
|
||||
const label = t(LYRICS_SOURCE_LABEL_KEYS[src.id]);
|
||||
const isBefore = isPsyDragging && dropTarget?.idx === i && dropTarget.before;
|
||||
@@ -173,7 +160,6 @@ export function LyricsSourcesCustomizer() {
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Static-only toggle — suppresses line/word tracking in both modes. */}
|
||||
<div className="settings-card" style={{ marginBottom: '0.75rem' }}>
|
||||
|
||||
Reference in New Issue
Block a user