mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-21 23:05:46 +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
@@ -15,14 +15,14 @@ export function createLyricsSettingsActions(set: SetState): Pick<
|
||||
| 'setLyricsServerFirst'
|
||||
| 'setEnableNeteaselyrics'
|
||||
| 'setLyricsSources'
|
||||
| 'setLyricsMode'
|
||||
| 'setYouLyPlusEnabled'
|
||||
| 'setLyricsStaticOnly'
|
||||
> {
|
||||
return {
|
||||
setLyricsServerFirst: (v) => set({ lyricsServerFirst: v }),
|
||||
setEnableNeteaselyrics: (v) => set({ enableNeteaselyrics: v }),
|
||||
setLyricsSources: (sources) => set({ lyricsSources: sources }),
|
||||
setLyricsMode: (v) => set({ lyricsMode: v }),
|
||||
setYouLyPlusEnabled: (v) => set({ youLyPlusEnabled: v }),
|
||||
setLyricsStaticOnly: (v) => set({ lyricsStaticOnly: v }),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -294,9 +294,11 @@ describe('lyrics source setters', () => {
|
||||
expect(useAuthStore.getState().lyricsSources).toEqual(sources);
|
||||
});
|
||||
|
||||
it('setLyricsMode + setFsLyricsStyle + setSidebarLyricsStyle write enum values through', () => {
|
||||
useAuthStore.getState().setLyricsMode('lyricsplus');
|
||||
expect(useAuthStore.getState().lyricsMode).toBe('lyricsplus');
|
||||
it('setYouLyPlusEnabled + setFsLyricsStyle + setSidebarLyricsStyle write values through', () => {
|
||||
useAuthStore.getState().setYouLyPlusEnabled(true);
|
||||
expect(useAuthStore.getState().youLyPlusEnabled).toBe(true);
|
||||
useAuthStore.getState().setYouLyPlusEnabled(false);
|
||||
expect(useAuthStore.getState().youLyPlusEnabled).toBe(false);
|
||||
|
||||
useAuthStore.getState().setFsLyricsStyle('apple');
|
||||
expect(useAuthStore.getState().fsLyricsStyle).toBe('apple');
|
||||
|
||||
@@ -79,7 +79,7 @@ export const useAuthStore = create<AuthState>()(
|
||||
lyricsServerFirst: true,
|
||||
enableNeteaselyrics: false,
|
||||
lyricsSources: DEFAULT_LYRICS_SOURCES,
|
||||
lyricsMode: 'standard',
|
||||
youLyPlusEnabled: false,
|
||||
lyricsStaticOnly: false,
|
||||
showFullscreenLyrics: true,
|
||||
fsLyricsStyle: 'rail',
|
||||
|
||||
@@ -28,9 +28,12 @@ export const DEFAULT_TRACK_PREVIEW_LOCATIONS: TrackPreviewLocations = {
|
||||
randomMix: true,
|
||||
};
|
||||
|
||||
// Fresh installs ship with every lyrics source off (issue #810 — users who
|
||||
// don't want lyrics get none until they opt in). Existing users keep their
|
||||
// persisted `lyricsSources`; the rehydrate migration preserves them.
|
||||
export const DEFAULT_LYRICS_SOURCES: LyricsSourceConfig[] = [
|
||||
{ id: 'server', enabled: true },
|
||||
{ id: 'lrclib', enabled: true },
|
||||
{ id: 'server', enabled: false },
|
||||
{ id: 'lrclib', enabled: false },
|
||||
{ id: 'netease', enabled: false },
|
||||
];
|
||||
|
||||
|
||||
@@ -40,3 +40,39 @@ describe('computeAuthStoreRehydration — queueDurationDisplayMode', () => {
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe('computeAuthStoreRehydration — lyrics', () => {
|
||||
beforeEach(() => {
|
||||
resetAuthStore();
|
||||
localStorage.clear();
|
||||
});
|
||||
|
||||
it('migrates legacy lyricsMode "lyricsplus" → youLyPlusEnabled true', () => {
|
||||
const base = useAuthStore.getState();
|
||||
const patch = computeAuthStoreRehydration({ ...base, lyricsMode: 'lyricsplus' } as AuthState);
|
||||
expect(patch.youLyPlusEnabled).toBe(true);
|
||||
});
|
||||
|
||||
it('migrates legacy lyricsMode "standard" → youLyPlusEnabled false', () => {
|
||||
const base = useAuthStore.getState();
|
||||
const patch = computeAuthStoreRehydration({ ...base, lyricsMode: 'standard' } as AuthState);
|
||||
expect(patch.youLyPlusEnabled).toBe(false);
|
||||
});
|
||||
|
||||
it('fresh install (no persisted state) keeps every source off — issue #810', () => {
|
||||
localStorage.removeItem('psysonic-auth');
|
||||
const patch = computeAuthStoreRehydration(useAuthStore.getState());
|
||||
// No migration: the all-off default must survive.
|
||||
expect(patch.lyricsSources).toBeUndefined();
|
||||
});
|
||||
|
||||
it('upgrade from a build without lyricsSources migrates the old on-by-default set', () => {
|
||||
localStorage.setItem('psysonic-auth', JSON.stringify({ state: { lyricsServerFirst: true } }));
|
||||
const patch = computeAuthStoreRehydration(useAuthStore.getState());
|
||||
expect(patch.lyricsSources).toEqual([
|
||||
{ id: 'server', enabled: true },
|
||||
{ id: 'lrclib', enabled: true },
|
||||
{ id: 'netease', enabled: false },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -40,10 +40,13 @@ export function computeAuthStoreRehydration(state: AuthState): Partial<AuthState
|
||||
: {};
|
||||
|
||||
// Migrate lyricsServerFirst + enableNeteaselyrics → lyricsSources (one-time).
|
||||
// Only for an *existing* persisted state (upgrade from a build without
|
||||
// lyricsSources). Fresh installs have no persisted state → keep the
|
||||
// all-off default (issue #810); don't resurrect the old on-by-default set.
|
||||
let lyricsSourcesMigrated: { lyricsSources?: LyricsSourceConfig[] } = {};
|
||||
try {
|
||||
const raw = JSON.parse(localStorage.getItem('psysonic-auth') ?? '{}') as { state?: Record<string, unknown> };
|
||||
if (!raw?.state?.lyricsSources) {
|
||||
if (raw?.state && !raw.state.lyricsSources) {
|
||||
const serverFirst = (raw?.state?.lyricsServerFirst as boolean | undefined) ?? true;
|
||||
const neteaseOn = (raw?.state?.enableNeteaselyrics as boolean | undefined) ?? false;
|
||||
const migrated: LyricsSourceConfig[] = serverFirst
|
||||
@@ -53,6 +56,16 @@ export function computeAuthStoreRehydration(state: AuthState): Partial<AuthState
|
||||
}
|
||||
} catch { /* ignore */ }
|
||||
|
||||
// Migrate legacy `lyricsMode` ('standard' | 'lyricsplus') → `youLyPlusEnabled`
|
||||
// (one-time). Existing users keep YouLyPlus on iff they were on lyricsplus
|
||||
// mode; the legacy field is then stripped so it doesn't sit as cruft.
|
||||
let youLyPlusMigrated: { youLyPlusEnabled?: boolean } = {};
|
||||
const legacyLyricsMode = (state as { lyricsMode?: unknown }).lyricsMode;
|
||||
if (legacyLyricsMode === 'lyricsplus' || legacyLyricsMode === 'standard') {
|
||||
youLyPlusMigrated = { youLyPlusEnabled: legacyLyricsMode === 'lyricsplus' };
|
||||
}
|
||||
delete (state as { lyricsMode?: unknown }).lyricsMode;
|
||||
|
||||
// One-time: older builds could persist smooth=false as the default. Force smooth on once
|
||||
// so updates do not leave users on discrete scrolling; after this flag exists, only an
|
||||
// explicit toggle in Settings may turn it off (persisted in psysonic-auth).
|
||||
@@ -145,6 +158,7 @@ export function computeAuthStoreRehydration(state: AuthState): Partial<AuthState
|
||||
loudnessPreIsRefV1: true,
|
||||
...conflictingLegacyState,
|
||||
...lyricsSourcesMigrated,
|
||||
...youLyPlusMigrated,
|
||||
...wheelSmoothOneTime,
|
||||
...seekbarStyleMigrated,
|
||||
...queueDurationDisplayModeMigrated,
|
||||
|
||||
@@ -123,11 +123,12 @@ export interface AuthState {
|
||||
enableNeteaselyrics: boolean;
|
||||
lyricsSources: LyricsSourceConfig[];
|
||||
/**
|
||||
* `'standard'` → server + lrclib + netease pipeline (configurable order).
|
||||
* `'lyricsplus'` → YouLyPlus / lyricsplus first, silent fallback to standard
|
||||
* pipeline when no data is returned.
|
||||
* YouLyPlus (karaoke) as the primary lyrics source. When on, it is tried
|
||||
* first and the enabled `lyricsSources` act as fallback; when off, only the
|
||||
* enabled `lyricsSources` are used. Independent of the source toggles, so all
|
||||
* lyrics can be turned off (YouLyPlus off + every source off).
|
||||
*/
|
||||
lyricsMode: 'standard' | 'lyricsplus';
|
||||
youLyPlusEnabled: boolean;
|
||||
/**
|
||||
* Render synced lines as static text (no auto-scroll, no word highlighting).
|
||||
* Honoured in both lyrics modes.
|
||||
@@ -294,7 +295,7 @@ export interface AuthState {
|
||||
setLyricsServerFirst: (v: boolean) => void;
|
||||
setEnableNeteaselyrics: (v: boolean) => void;
|
||||
setLyricsSources: (sources: LyricsSourceConfig[]) => void;
|
||||
setLyricsMode: (v: 'standard' | 'lyricsplus') => void;
|
||||
setYouLyPlusEnabled: (v: boolean) => void;
|
||||
setLyricsStaticOnly: (v: boolean) => void;
|
||||
setShowFullscreenLyrics: (v: boolean) => void;
|
||||
setFsLyricsStyle: (v: 'rail' | 'apple') => void;
|
||||
|
||||
Reference in New Issue
Block a user