1
0
Fork 0
mirror of https://github.com/jellyfin/jellyfin-web synced 2025-03-30 19:56:21 +00:00

Update favorite and played state to use Query Invalidation

This commit is contained in:
grafixeyehero 2024-01-31 04:32:54 +03:00
parent 97472ac8bb
commit 31a77c25f3
5 changed files with 79 additions and 59 deletions

View file

@ -18,7 +18,7 @@ const ProgramsSectionView: FC<ProgramsSectionViewProps> = ({
sectionType, sectionType,
isUpcomingRecordingsEnabled = false isUpcomingRecordingsEnabled = false
}) => { }) => {
const { isLoading, data: sectionsWithItems } = useGetProgramsSectionsWithItems(parentId, sectionType); const { isLoading, data: sectionsWithItems, refetch } = useGetProgramsSectionsWithItems(parentId, sectionType);
const { const {
isLoading: isUpcomingRecordingsLoading, isLoading: isUpcomingRecordingsLoading,
data: upcomingRecordings data: upcomingRecordings
@ -60,8 +60,10 @@ const ProgramsSectionView: FC<ProgramsSectionViewProps> = ({
sectionTitle={globalize.translate(section.name)} sectionTitle={globalize.translate(section.name)}
items={items ?? []} items={items ?? []}
url={getRouteUrl(section)} url={getRouteUrl(section)}
reloadItems={refetch}
cardOptions={{ cardOptions={{
...section.cardOptions ...section.cardOptions,
queryKey: ['ProgramSectionWithItems']
}} }}
/> />
@ -73,6 +75,7 @@ const ProgramsSectionView: FC<ProgramsSectionViewProps> = ({
sectionTitle={group.name} sectionTitle={group.name}
items={group.timerInfo ?? []} items={group.timerInfo ?? []}
cardOptions={{ cardOptions={{
queryKey: ['Timers'],
shape: 'overflowBackdrop', shape: 'overflowBackdrop',
showTitle: true, showTitle: true,
showParentTitleOrTitle: true, showParentTitleOrTitle: true,

View file

@ -102,6 +102,7 @@ const SuggestionsSectionView: FC<SuggestionsSectionViewProps> = ({
url={getRouteUrl(section)} url={getRouteUrl(section)}
cardOptions={{ cardOptions={{
...section.cardOptions, ...section.cardOptions,
queryKey: ['SuggestionSectionWithItems'],
showTitle: true, showTitle: true,
centerText: true, centerText: true,
cardLayout: false, cardLayout: false,
@ -117,6 +118,7 @@ const SuggestionsSectionView: FC<SuggestionsSectionViewProps> = ({
sectionTitle={getRecommendationTittle(recommendation)} sectionTitle={getRecommendationTittle(recommendation)}
items={recommendation.Items ?? []} items={recommendation.Items ?? []}
cardOptions={{ cardOptions={{
queryKey: ['MovieRecommendations'],
shape: 'overflowPortrait', shape: 'overflowPortrait',
showYear: true, showYear: true,
scalable: true, scalable: true,

View file

@ -1,4 +1,5 @@
import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client'; import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client';
import { useQueryClient } from '@tanstack/react-query';
import React, { FC, useCallback } from 'react'; import React, { FC, useCallback } from 'react';
import CheckIcon from '@mui/icons-material/Check'; import CheckIcon from '@mui/icons-material/Check';
import { IconButton } from '@mui/material'; import { IconButton } from '@mui/material';
@ -10,28 +11,30 @@ interface PlayedButtonProps {
className?: string; className?: string;
isPlayed : boolean | undefined; isPlayed : boolean | undefined;
itemId: string | null | undefined; itemId: string | null | undefined;
itemType: string | null | undefined itemType: string | null | undefined,
queryKey?: string[]
} }
const PlayedButton: FC<PlayedButtonProps> = ({ const PlayedButton: FC<PlayedButtonProps> = ({
className, className,
isPlayed = false, isPlayed = false,
itemId, itemId,
itemType itemType,
queryKey
}) => { }) => {
const queryClient = useQueryClient();
const { mutateAsync: togglePlayedMutation } = useTogglePlayedMutation(); const { mutateAsync: togglePlayedMutation } = useTogglePlayedMutation();
const [playedState, setPlayedState] = React.useState<boolean>(isPlayed);
const getTitle = useCallback(() => { const getTitle = useCallback(() => {
let buttonTitle; let buttonTitle;
if (itemType !== BaseItemKind.AudioBook) { if (itemType !== BaseItemKind.AudioBook) {
buttonTitle = playedState ? globalize.translate('Watched') : globalize.translate('MarkPlayed'); buttonTitle = isPlayed ? globalize.translate('Watched') : globalize.translate('MarkPlayed');
} else { } else {
buttonTitle = playedState ? globalize.translate('Played') : globalize.translate('MarkPlayed'); buttonTitle = isPlayed ? globalize.translate('Played') : globalize.translate('MarkPlayed');
} }
return buttonTitle; return buttonTitle;
}, [playedState, itemType]); }, [itemType, isPlayed]);
const onClick = useCallback(async () => { const onClick = useCallback(async () => {
try { try {
@ -39,23 +42,29 @@ const PlayedButton: FC<PlayedButtonProps> = ({
throw new Error('Item has no Id'); throw new Error('Item has no Id');
} }
const _isPlayed = await togglePlayedMutation({ await togglePlayedMutation({
itemId, itemId,
playedState isPlayed
}); },
setPlayedState(!!_isPlayed); { onSuccess: async() => {
await queryClient.invalidateQueries({
queryKey: queryKey,
type: 'all',
refetchType: 'active'
});
} });
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} }
}, [playedState, itemId, togglePlayedMutation]); }, [itemId, togglePlayedMutation, isPlayed, queryClient, queryKey]);
const btnClass = classNames( const btnClass = classNames(
className, className,
{ 'playstatebutton-played': playedState } { 'playstatebutton-played': isPlayed }
); );
const iconClass = classNames( const iconClass = classNames(
{ 'playstatebutton-icon-played': playedState } { 'playstatebutton-icon-played': isPlayed }
); );
return ( return (
<IconButton <IconButton

View file

@ -1,4 +1,5 @@
import React, { FC, useCallback } from 'react'; import React, { FC, useCallback } from 'react';
import { useQueryClient } from '@tanstack/react-query';
import FavoriteIcon from '@mui/icons-material/Favorite'; import FavoriteIcon from '@mui/icons-material/Favorite';
import { IconButton } from '@mui/material'; import { IconButton } from '@mui/material';
import classNames from 'classnames'; import classNames from 'classnames';
@ -8,16 +9,18 @@ import globalize from 'scripts/globalize';
interface FavoriteButtonProps { interface FavoriteButtonProps {
className?: string; className?: string;
isFavorite: boolean | undefined; isFavorite: boolean | undefined;
itemId: string | null | undefined itemId: string | null | undefined;
queryKey?: string[]
} }
const FavoriteButton: FC<FavoriteButtonProps> = ({ const FavoriteButton: FC<FavoriteButtonProps> = ({
className, className,
isFavorite = false, isFavorite = false,
itemId itemId,
queryKey
}) => { }) => {
const queryClient = useQueryClient();
const { mutateAsync: toggleFavoriteMutation } = useToggleFavoriteMutation(); const { mutateAsync: toggleFavoriteMutation } = useToggleFavoriteMutation();
const [favoriteState, setFavoriteState] = React.useState<boolean>(isFavorite);
const onClick = useCallback(async () => { const onClick = useCallback(async () => {
try { try {
@ -25,28 +28,34 @@ const FavoriteButton: FC<FavoriteButtonProps> = ({
throw new Error('Item has no Id'); throw new Error('Item has no Id');
} }
const _isFavorite = await toggleFavoriteMutation({ await toggleFavoriteMutation({
itemId, itemId,
favoriteState isFavorite
}); },
setFavoriteState(!!_isFavorite); { onSuccess: async() => {
await queryClient.invalidateQueries({
queryKey: queryKey,
type: 'all',
refetchType: 'active'
});
} });
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} }
}, [favoriteState, itemId, toggleFavoriteMutation]); }, [isFavorite, itemId, queryClient, queryKey, toggleFavoriteMutation]);
const btnClass = classNames( const btnClass = classNames(
className, className,
{ 'ratingbutton-withrating': favoriteState } { 'ratingbutton-withrating': isFavorite }
); );
const iconClass = classNames( const iconClass = classNames(
{ 'ratingbutton-icon-withrating': favoriteState } { 'ratingbutton-icon-withrating': isFavorite }
); );
return ( return (
<IconButton <IconButton
title={favoriteState ? globalize.translate('Favorite') : globalize.translate('AddToFavorites')} title={isFavorite ? globalize.translate('Favorite') : globalize.translate('AddToFavorites')}
className={btnClass} className={btnClass}
size='small' size='small'
onClick={onClick} onClick={onClick}

View file

@ -376,10 +376,12 @@ export const useGetItemsViewByType = (
return useQuery({ return useQuery({
queryKey: [ queryKey: [
'ItemsViewByType', 'ItemsViewByType',
viewType, {
parentId, viewType,
itemType, parentId,
libraryViewSettings itemType,
libraryViewSettings
}
], ],
queryFn: ({ signal }) => queryFn: ({ signal }) =>
fetchGetItemsViewByType( fetchGetItemsViewByType(
@ -526,17 +528,17 @@ export const useGetGroupsUpcomingEpisodes = (parentId: ParentId) => {
interface ToggleFavoriteMutationProp { interface ToggleFavoriteMutationProp {
itemId: string; itemId: string;
favoriteState: boolean isFavorite: boolean
} }
const fetchUpdateFavoriteStatus = async ( const fetchUpdateFavoriteStatus = async (
currentApi: JellyfinApiContext, currentApi: JellyfinApiContext,
itemId: string, itemId: string,
favoriteState: boolean isFavorite: boolean
) => { ) => {
const { api, user } = currentApi; const { api, user } = currentApi;
if (api && user?.Id) { if (api && user?.Id) {
if (favoriteState) { if (isFavorite) {
const response = await getUserLibraryApi(api).unmarkFavoriteItem({ const response = await getUserLibraryApi(api).unmarkFavoriteItem({
userId: user.Id, userId: user.Id,
itemId: itemId itemId: itemId
@ -555,24 +557,24 @@ const fetchUpdateFavoriteStatus = async (
export const useToggleFavoriteMutation = () => { export const useToggleFavoriteMutation = () => {
const currentApi = useApi(); const currentApi = useApi();
return useMutation({ return useMutation({
mutationFn: ({ itemId, favoriteState }: ToggleFavoriteMutationProp) => mutationFn: ({ itemId, isFavorite }: ToggleFavoriteMutationProp) =>
fetchUpdateFavoriteStatus(currentApi, itemId, favoriteState ) fetchUpdateFavoriteStatus(currentApi, itemId, isFavorite )
}); });
}; };
interface TogglePlayedMutationProp { interface TogglePlayedMutationProp {
itemId: string; itemId: string;
playedState: boolean isPlayed: boolean
} }
const fetchUpdatePlayedState = async ( const fetchUpdatePlayedState = async (
currentApi: JellyfinApiContext, currentApi: JellyfinApiContext,
itemId: string, itemId: string,
playedState: boolean isPlayed: boolean
) => { ) => {
const { api, user } = currentApi; const { api, user } = currentApi;
if (api && user?.Id) { if (api && user?.Id) {
if (playedState) { if (isPlayed) {
const response = await getPlaystateApi(api).markUnplayedItem({ const response = await getPlaystateApi(api).markUnplayedItem({
userId: user.Id, userId: user.Id,
itemId: itemId itemId: itemId
@ -591,8 +593,8 @@ const fetchUpdatePlayedState = async (
export const useTogglePlayedMutation = () => { export const useTogglePlayedMutation = () => {
const currentApi = useApi(); const currentApi = useApi();
return useMutation({ return useMutation({
mutationFn: ({ itemId, playedState }: TogglePlayedMutationProp) => mutationFn: ({ itemId, isPlayed }: TogglePlayedMutationProp) =>
fetchUpdatePlayedState(currentApi, itemId, playedState ) fetchUpdatePlayedState(currentApi, itemId, isPlayed )
}); });
}; };
@ -676,7 +678,7 @@ const fetchGetTimers = async (
export const useGetTimers = (isUpcomingRecordingsEnabled: boolean, indexByDate?: boolean) => { export const useGetTimers = (isUpcomingRecordingsEnabled: boolean, indexByDate?: boolean) => {
const currentApi = useApi(); const currentApi = useApi();
return useQuery({ return useQuery({
queryKey: ['Timers', isUpcomingRecordingsEnabled, indexByDate], queryKey: ['Timers', { isUpcomingRecordingsEnabled, indexByDate }],
queryFn: ({ signal }) => queryFn: ({ signal }) =>
isUpcomingRecordingsEnabled ? fetchGetTimers(currentApi, indexByDate, { signal }) : [] isUpcomingRecordingsEnabled ? fetchGetTimers(currentApi, indexByDate, { signal }) : []
}); });
@ -830,7 +832,7 @@ const fetchGetSectionItems = async (
], ],
parentId: parentId ?? undefined, parentId: parentId ?? undefined,
imageTypeLimit: 1, imageTypeLimit: 1,
enableImageTypes: [ImageType.Primary], enableImageTypes: [ImageType.Primary, ImageType.Thumb],
...section.parametersOptions ...section.parametersOptions
}, },
{ {
@ -882,19 +884,15 @@ const getSectionsWithItems = async (
const updatedSectionWithItems: SectionWithItems[] = []; const updatedSectionWithItems: SectionWithItems[] = [];
for (const section of sections) { for (const section of sections) {
try { const items = await fetchGetSectionItems(
const items = await fetchGetSectionItems( currentApi, parentId, section, options
currentApi, parentId, section, options );
);
if (items && items.length > 0) { if (items && items.length > 0) {
updatedSectionWithItems.push({ updatedSectionWithItems.push({
section, section,
items items
}); });
}
} catch (error) {
console.error(`Error occurred for section ${section.type}: ${error}`);
} }
} }
@ -908,7 +906,7 @@ export const useGetSuggestionSectionsWithItems = (
const currentApi = useApi(); const currentApi = useApi();
const sections = getSuggestionSections(); const sections = getSuggestionSections();
return useQuery({ return useQuery({
queryKey: ['SuggestionSectionWithItems', suggestionSectionType], queryKey: ['SuggestionSectionWithItems', { suggestionSectionType }],
queryFn: ({ signal }) => queryFn: ({ signal }) =>
getSectionsWithItems(currentApi, parentId, sections, suggestionSectionType, { signal }), getSectionsWithItems(currentApi, parentId, sections, suggestionSectionType, { signal }),
enabled: !!parentId enabled: !!parentId
@ -922,9 +920,8 @@ export const useGetProgramsSectionsWithItems = (
const currentApi = useApi(); const currentApi = useApi();
const sections = getProgramSections(); const sections = getProgramSections();
return useQuery({ return useQuery({
queryKey: ['ProgramSectionWithItems', programSectionType], queryKey: ['ProgramSectionWithItems', { programSectionType }],
queryFn: ({ signal }) => queryFn: ({ signal }) => getSectionsWithItems(currentApi, parentId, sections, programSectionType, { signal })
getSectionsWithItems(currentApi, parentId, sections, programSectionType, { signal })
}); });
}; };