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:
parent
97472ac8bb
commit
31a77c25f3
5 changed files with 79 additions and 59 deletions
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
},
|
||||||
|
{ onSuccess: async() => {
|
||||||
|
await queryClient.invalidateQueries({
|
||||||
|
queryKey: queryKey,
|
||||||
|
type: 'all',
|
||||||
|
refetchType: 'active'
|
||||||
});
|
});
|
||||||
setPlayedState(!!_isPlayed);
|
} });
|
||||||
} 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
|
||||||
|
|
|
@ -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
|
||||||
|
},
|
||||||
|
{ onSuccess: async() => {
|
||||||
|
await queryClient.invalidateQueries({
|
||||||
|
queryKey: queryKey,
|
||||||
|
type: 'all',
|
||||||
|
refetchType: 'active'
|
||||||
});
|
});
|
||||||
setFavoriteState(!!_isFavorite);
|
} });
|
||||||
} 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}
|
||||||
|
|
|
@ -376,10 +376,12 @@ export const useGetItemsViewByType = (
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: [
|
queryKey: [
|
||||||
'ItemsViewByType',
|
'ItemsViewByType',
|
||||||
|
{
|
||||||
viewType,
|
viewType,
|
||||||
parentId,
|
parentId,
|
||||||
itemType,
|
itemType,
|
||||||
libraryViewSettings
|
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,7 +884,6 @@ 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
|
||||||
);
|
);
|
||||||
|
@ -893,9 +894,6 @@ const getSectionsWithItems = async (
|
||||||
items
|
items
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
console.error(`Error occurred for section ${section.type}: ${error}`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return updatedSectionWithItems;
|
return updatedSectionWithItems;
|
||||||
|
@ -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 })
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue