Improve lyrics persistence

- Search overrides existing lyrics query
- Separate reset / clear commands
This commit is contained in:
jeffvli 2023-08-04 11:51:01 -07:00
parent 80fb844d3c
commit f6d239d87c
4 changed files with 133 additions and 71 deletions

View file

@ -1111,6 +1111,7 @@ export type LyricSearchQuery = {
export type LyricGetQuery = { export type LyricGetQuery = {
remoteSongId: string; remoteSongId: string;
remoteSource: LyricSource; remoteSource: LyricSource;
song: Song;
}; };
export enum LyricSource { export enum LyricSource {

View file

@ -1,3 +1,4 @@
import { Box, Group } from '@mantine/core';
import isElectron from 'is-electron'; import isElectron from 'is-electron';
import { RiAddFill, RiSubtractFill } from 'react-icons/ri'; import { RiAddFill, RiSubtractFill } from 'react-icons/ri';
import { LyricsOverride } from '/@/renderer/api/types'; import { LyricsOverride } from '/@/renderer/api/types';
@ -12,10 +13,15 @@ import {
interface LyricsActionsProps { interface LyricsActionsProps {
onRemoveLyric: () => void; onRemoveLyric: () => void;
onResetLyric: () => void;
onSearchOverride: (params: LyricsOverride) => void; onSearchOverride: (params: LyricsOverride) => void;
} }
export const LyricsActions = ({ onRemoveLyric, onSearchOverride }: LyricsActionsProps) => { export const LyricsActions = ({
onRemoveLyric,
onResetLyric,
onSearchOverride,
}: LyricsActionsProps) => {
const currentSong = useCurrentSong(); const currentSong = useCurrentSong();
const { setSettings } = useSettingsStoreActions(); const { setSettings } = useSettingsStoreActions();
const { delayMs, sources } = useLyricsSettings(); const { delayMs, sources } = useLyricsSettings();
@ -33,7 +39,8 @@ export const LyricsActions = ({ onRemoveLyric, onSearchOverride }: LyricsActions
const isDesktop = isElectron(); const isDesktop = isElectron();
return ( return (
<> <Box style={{ position: 'relative', width: '100%' }}>
<Group position="center">
{isDesktop && sources.length ? ( {isDesktop && sources.length ? (
<Button <Button
uppercase uppercase
@ -81,11 +88,26 @@ export const LyricsActions = ({ onRemoveLyric, onSearchOverride }: LyricsActions
uppercase uppercase
disabled={isActionsDisabled} disabled={isActionsDisabled}
variant="subtle" variant="subtle"
onClick={onResetLyric}
>
Reset
</Button>
) : null}
</Group>
<Box style={{ position: 'absolute', right: 0, top: 0 }}>
{isDesktop && sources.length ? (
<Button
uppercase
color="red"
disabled={isActionsDisabled}
variant="subtle"
onClick={onRemoveLyric} onClick={onRemoveLyric}
> >
Clear Clear
</Button> </Button>
) : null} ) : null}
</> </Box>
</Box>
); );
}; };

View file

@ -1,6 +1,7 @@
import { useCallback, useEffect, useState } from 'react'; import { useCallback, useEffect, useState } from 'react';
import { Center, Group } from '@mantine/core'; import { Center, Group } from '@mantine/core';
import { AnimatePresence, motion } from 'framer-motion'; import { AnimatePresence, motion } from 'framer-motion';
import { clear } from 'idb-keyval';
import { ErrorBoundary } from 'react-error-boundary'; import { ErrorBoundary } from 'react-error-boundary';
import { RiInformationFill } from 'react-icons/ri'; import { RiInformationFill } from 'react-icons/ri';
import styled from 'styled-components'; import styled from 'styled-components';
@ -9,7 +10,7 @@ import { SynchronizedLyrics } from './synchronized-lyrics';
import { Spinner, TextTitle } from '/@/renderer/components'; import { Spinner, TextTitle } from '/@/renderer/components';
import { ErrorFallback } from '/@/renderer/features/action-required'; import { ErrorFallback } from '/@/renderer/features/action-required';
import { UnsynchronizedLyrics } from '/@/renderer/features/lyrics/unsynchronized-lyrics'; import { UnsynchronizedLyrics } from '/@/renderer/features/lyrics/unsynchronized-lyrics';
import { getServerById, useCurrentSong, usePlayerStore } from '/@/renderer/store'; import { useCurrentSong, usePlayerStore } from '/@/renderer/store';
import { import {
FullLyricsMetadata, FullLyricsMetadata,
LyricsOverride, LyricsOverride,
@ -17,6 +18,8 @@ import {
UnsynchronizedLyricMetadata, UnsynchronizedLyricMetadata,
} from '/@/renderer/api/types'; } from '/@/renderer/api/types';
import { LyricsActions } from '/@/renderer/features/lyrics/lyrics-actions'; import { LyricsActions } from '/@/renderer/features/lyrics/lyrics-actions';
import { queryKeys } from '/@/renderer/api/query-keys';
import { queryClient } from '/@/renderer/lib/react-query';
const ActionsContainer = styled.div` const ActionsContainer = styled.div`
position: absolute; position: absolute;
@ -93,14 +96,11 @@ function isSynchronized(
export const Lyrics = () => { export const Lyrics = () => {
const currentSong = useCurrentSong(); const currentSong = useCurrentSong();
const currentServer = getServerById(currentSong?.serverId);
const [clear, setClear] = useState(false);
const { data, isInitialLoading } = useSongLyricsBySong( const { data, isInitialLoading } = useSongLyricsBySong(
{ {
query: { songId: currentSong?.id || '' }, query: { songId: currentSong?.id || '' },
serverId: currentServer?.id, serverId: currentSong?.serverId || '',
}, },
currentSong, currentSong,
); );
@ -109,18 +109,41 @@ export const Lyrics = () => {
const handleOnSearchOverride = useCallback((params: LyricsOverride) => { const handleOnSearchOverride = useCallback((params: LyricsOverride) => {
setOverride(params); setOverride(params);
setClear(false);
}, []); }, []);
const { data: overrideLyrics, isInitialLoading: isOverrideLoading } = useSongLyricsByRemoteId({ const handleOnResetLyric = useCallback(() => {
queryClient.resetQueries({
exact: true,
queryKey: queryKeys.songs.lyrics(currentSong?.serverId, { songId: currentSong?.id }),
});
}, [currentSong?.id, currentSong?.serverId]);
const handleOnRemoveLyric = useCallback(() => {
queryClient.setQueryData(
queryKeys.songs.lyrics(currentSong?.serverId, { songId: currentSong?.id }),
(prev: FullLyricsMetadata | undefined) => {
if (!prev) {
return undefined;
}
return {
...prev,
lyrics: '',
};
},
);
}, [currentSong?.id, currentSong?.serverId]);
const { isInitialLoading: isOverrideLoading } = useSongLyricsByRemoteId({
options: { options: {
enabled: !!override, enabled: !!override,
}, },
query: { query: {
remoteSongId: override?.id, remoteSongId: override?.id,
remoteSource: override?.source, remoteSource: override?.source,
song: currentSong,
}, },
serverId: currentServer?.id, serverId: currentSong?.serverId,
}); });
useEffect(() => { useEffect(() => {
@ -128,7 +151,6 @@ export const Lyrics = () => {
(state) => state.current.song, (state) => state.current.song,
() => { () => {
setOverride(undefined); setOverride(undefined);
setClear(false);
}, },
{ equalityFn: (a, b) => a?.id === b?.id }, { equalityFn: (a, b) => a?.id === b?.id },
); );
@ -140,20 +162,12 @@ export const Lyrics = () => {
const isLoadingLyrics = isInitialLoading || isOverrideLoading; const isLoadingLyrics = isInitialLoading || isOverrideLoading;
const hasNoLyrics = (!data?.lyrics && !overrideLyrics) || clear; const hasNoLyrics = !data?.lyrics || clear;
const lyricsMetadata: const lyricsMetadata:
| Partial<SynchronizedLyricMetadata> | Partial<SynchronizedLyricMetadata>
| Partial<UnsynchronizedLyricMetadata> | Partial<UnsynchronizedLyricMetadata>
| undefined = overrideLyrics | undefined = data;
? {
artist: override?.artist,
lyrics: overrideLyrics,
name: override?.name,
remote: true,
source: override?.source,
}
: data;
const isSynchronizedLyrics = isSynchronized(lyricsMetadata); const isSynchronizedLyrics = isSynchronized(lyricsMetadata);
@ -198,7 +212,8 @@ export const Lyrics = () => {
)} )}
<ActionsContainer> <ActionsContainer>
<LyricsActions <LyricsActions
onRemoveLyric={() => setClear(true)} onRemoveLyric={handleOnRemoveLyric}
onResetLyric={handleOnResetLyric}
onSearchOverride={handleOnSearchOverride} onSearchOverride={handleOnSearchOverride}
/> />
</ActionsContainer> </ActionsContainer>

View file

@ -1,4 +1,4 @@
import { UseQueryResult, useQuery } from '@tanstack/react-query'; import { UseQueryResult, useQuery, useQueryClient } from '@tanstack/react-query';
import { import {
LyricsQuery, LyricsQuery,
QueueSong, QueueSong,
@ -93,6 +93,8 @@ export const useSongLyricsBySong = (
if (!server) throw new Error('Server not found'); if (!server) throw new Error('Server not found');
if (!song) return null; if (!song) return null;
console.log('refetching song lyrics');
if (song.lyrics) { if (song.lyrics) {
return { return {
artist: song.artists?.[0]?.name, artist: song.artists?.[0]?.name,
@ -145,12 +147,35 @@ export const useSongLyricsBySong = (
export const useSongLyricsByRemoteId = ( export const useSongLyricsByRemoteId = (
args: QueryHookArgs<Partial<LyricGetQuery>>, args: QueryHookArgs<Partial<LyricGetQuery>>,
): UseQueryResult<string | null> => { ): UseQueryResult<string | null> => {
const { query } = args; const queryClient = useQueryClient();
const { query, serverId } = args;
return useQuery({ return useQuery({
cacheTime: 1000 * 60 * 10,
enabled: !!query.remoteSongId && !!query.remoteSource, enabled: !!query.remoteSongId && !!query.remoteSource,
onError: () => {}, onError: () => {},
onSuccess: (data) => {
if (!data || !query.song) {
return;
}
const lyricsResult = {
artist: query.song.artists?.[0]?.name,
lyrics: data,
name: query.song.name,
remote: false,
source: query.remoteSource,
};
console.log(
'queryKeys.songs.lyrics(serverId, { songId: query.song.id }) :>> ',
queryKeys.songs.lyrics(serverId, { songId: query.song.id }),
);
queryClient.setQueryData(
queryKeys.songs.lyrics(serverId, { songId: query.song.id }),
lyricsResult,
);
},
queryFn: async () => { queryFn: async () => {
const remoteLyricsResult = await lyricsIpc?.getRemoteLyricsByRemoteId( const remoteLyricsResult = await lyricsIpc?.getRemoteLyricsByRemoteId(
query as LyricGetQuery, query as LyricGetQuery,
@ -163,6 +188,5 @@ export const useSongLyricsByRemoteId = (
return null; return null;
}, },
queryKey: queryKeys.songs.lyricsByRemoteId(query), queryKey: queryKeys.songs.lyricsByRemoteId(query),
staleTime: 1000 * 60 * 5,
}); });
}; };