add recently released to home page, refactor home route

This commit is contained in:
Kendall Garner 2025-10-05 07:51:36 -07:00
parent 7c24f7cba4
commit 1cbb3e56bc
No known key found for this signature in database
GPG key ID: 9355F387FE765C94
5 changed files with 67 additions and 88 deletions

View file

@ -411,6 +411,7 @@
"mostPlayed": "most played", "mostPlayed": "most played",
"newlyAdded": "newly added releases", "newlyAdded": "newly added releases",
"recentlyPlayed": "recently played", "recentlyPlayed": "recently played",
"recentlyReleased": "recently released",
"title": "$t(common.home)" "title": "$t(common.home)"
}, },
"itemDetail": { "itemDetail": {

View file

@ -41,7 +41,7 @@ const ALBUM_LIST_SORT_MAPPING: Record<AlbumListSort, AlbumListSortType | undefin
[AlbumListSort.RATING]: undefined, [AlbumListSort.RATING]: undefined,
[AlbumListSort.RECENTLY_ADDED]: AlbumListSortType.NEWEST, [AlbumListSort.RECENTLY_ADDED]: AlbumListSortType.NEWEST,
[AlbumListSort.RECENTLY_PLAYED]: AlbumListSortType.RECENT, [AlbumListSort.RECENTLY_PLAYED]: AlbumListSortType.RECENT,
[AlbumListSort.RELEASE_DATE]: undefined, [AlbumListSort.RELEASE_DATE]: AlbumListSortType.BY_YEAR,
[AlbumListSort.SONG_COUNT]: undefined, [AlbumListSort.SONG_COUNT]: undefined,
[AlbumListSort.YEAR]: AlbumListSortType.BY_YEAR, [AlbumListSort.YEAR]: AlbumListSortType.BY_YEAR,
}; };

View file

@ -1,8 +1,6 @@
import { useQueryClient } from '@tanstack/react-query';
import { useMemo, useRef } from 'react'; import { useMemo, useRef } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { queryKeys } from '/@/renderer/api/query-keys';
import { FeatureCarousel } from '/@/renderer/components/feature-carousel/feature-carousel'; import { FeatureCarousel } from '/@/renderer/components/feature-carousel/feature-carousel';
import { MemoizedSwiperGridCarousel } from '/@/renderer/components/grid-carousel/grid-carousel'; import { MemoizedSwiperGridCarousel } from '/@/renderer/components/grid-carousel/grid-carousel';
import { NativeScrollArea } from '/@/renderer/components/native-scroll-area/native-scroll-area'; import { NativeScrollArea } from '/@/renderer/components/native-scroll-area/native-scroll-area';
@ -32,12 +30,16 @@ import {
} from '/@/shared/types/domain-types'; } from '/@/shared/types/domain-types';
import { Platform } from '/@/shared/types/types'; import { Platform } from '/@/shared/types/types';
const BASE_QUERY_ARGS = {
limit: 15,
sortOrder: SortOrder.DESC,
startIndex: 0,
};
const HomeRoute = () => { const HomeRoute = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const queryClient = useQueryClient();
const scrollAreaRef = useRef<HTMLDivElement>(null); const scrollAreaRef = useRef<HTMLDivElement>(null);
const server = useCurrentServer(); const server = useCurrentServer();
const itemsPerPage = 15;
const { windowBarStyle } = useWindowSettings(); const { windowBarStyle } = useWindowSettings();
const { homeFeature, homeItems } = useGeneralSettings(); const { homeFeature, homeItems } = useGeneralSettings();
@ -56,59 +58,66 @@ const HomeRoute = () => {
serverId: server?.id, serverId: server?.id,
}); });
const isJellyfin = server?.type === ServerType.JELLYFIN;
const featureItemsWithImage = useMemo(() => { const featureItemsWithImage = useMemo(() => {
return feature.data?.items?.filter((item) => item.imageUrl) ?? []; return feature.data?.items?.filter((item) => item.imageUrl) ?? [];
}, [feature.data?.items]); }, [feature.data?.items]);
const queriesEnabled = useMemo(() => {
return homeItems.reduce(
(previous: Record<HomeItem, boolean>, current) => ({
...previous,
[current.id]: !current.disabled,
}),
{} as Record<HomeItem, boolean>,
);
}, [homeItems]);
const random = useAlbumList({ const random = useAlbumList({
options: { options: {
enabled: queriesEnabled[HomeItem.RANDOM],
staleTime: 1000 * 60 * 5, staleTime: 1000 * 60 * 5,
}, },
query: { query: {
limit: itemsPerPage, ...BASE_QUERY_ARGS,
sortBy: AlbumListSort.RANDOM, sortBy: AlbumListSort.RANDOM,
sortOrder: SortOrder.ASC,
startIndex: 0,
}, },
serverId: server?.id, serverId: server?.id,
}); });
const recentlyPlayed = useRecentlyPlayed({ const recentlyPlayed = useRecentlyPlayed({
options: { options: {
enabled: queriesEnabled[HomeItem.RECENTLY_PLAYED] && !isJellyfin,
staleTime: 0, staleTime: 0,
}, },
query: { query: {
limit: itemsPerPage, ...BASE_QUERY_ARGS,
sortBy: AlbumListSort.RECENTLY_PLAYED, sortBy: AlbumListSort.RECENTLY_PLAYED,
sortOrder: SortOrder.DESC,
startIndex: 0,
}, },
serverId: server?.id, serverId: server?.id,
}); });
const recentlyAdded = useAlbumList({ const recentlyAdded = useAlbumList({
options: { options: {
enabled: queriesEnabled[HomeItem.RECENTLY_ADDED],
staleTime: 1000 * 60 * 5, staleTime: 1000 * 60 * 5,
}, },
query: { query: {
limit: itemsPerPage, ...BASE_QUERY_ARGS,
sortBy: AlbumListSort.RECENTLY_ADDED, sortBy: AlbumListSort.RECENTLY_ADDED,
sortOrder: SortOrder.DESC,
startIndex: 0,
}, },
serverId: server?.id, serverId: server?.id,
}); });
const mostPlayedAlbums = useAlbumList({ const mostPlayedAlbums = useAlbumList({
options: { options: {
enabled: server?.type === ServerType.SUBSONIC || server?.type === ServerType.NAVIDROME, enabled: !isJellyfin && queriesEnabled[HomeItem.MOST_PLAYED],
staleTime: 1000 * 60 * 5, staleTime: 1000 * 60 * 5,
}, },
query: { query: {
limit: itemsPerPage, ...BASE_QUERY_ARGS,
sortBy: AlbumListSort.PLAY_COUNT, sortBy: AlbumListSort.PLAY_COUNT,
sortOrder: SortOrder.DESC,
startIndex: 0,
}, },
serverId: server?.id, serverId: server?.id,
}); });
@ -116,27 +125,38 @@ const HomeRoute = () => {
const mostPlayedSongs = useSongList( const mostPlayedSongs = useSongList(
{ {
options: { options: {
enabled: server?.type === ServerType.JELLYFIN, enabled: isJellyfin && queriesEnabled[HomeItem.MOST_PLAYED],
staleTime: 1000 * 60 * 5, staleTime: 1000 * 60 * 5,
}, },
query: { query: {
limit: itemsPerPage, ...BASE_QUERY_ARGS,
sortBy: SongListSort.PLAY_COUNT, sortBy: SongListSort.PLAY_COUNT,
sortOrder: SortOrder.DESC,
startIndex: 0,
}, },
serverId: server?.id, serverId: server?.id,
}, },
300, 300,
); );
const recentlyReleased = useAlbumList({
options: {
enabled: queriesEnabled[HomeItem.RECENTLY_RELEASED],
staleTime: 1000 * 60 * 5,
},
query: {
...BASE_QUERY_ARGS,
sortBy: AlbumListSort.RELEASE_DATE,
},
serverId: server?.id,
});
const isLoading = const isLoading =
random.isLoading || (random.isLoading && queriesEnabled[HomeItem.RANDOM]) ||
recentlyPlayed.isLoading || (recentlyPlayed.isLoading && queriesEnabled[HomeItem.RECENTLY_PLAYED] && !isJellyfin) ||
recentlyAdded.isLoading || (recentlyAdded.isLoading && queriesEnabled[HomeItem.RECENTLY_ADDED]) ||
(server?.type === ServerType.JELLYFIN && mostPlayedSongs.isLoading) || (recentlyReleased.isLoading && queriesEnabled[HomeItem.RECENTLY_RELEASED]) ||
((server?.type === ServerType.SUBSONIC || server?.type === ServerType.NAVIDROME) && (((isJellyfin && mostPlayedSongs.isLoading) ||
mostPlayedAlbums.isLoading); (!isJellyfin && mostPlayedAlbums.isLoading)) &&
queriesEnabled[HomeItem.MOST_PLAYED]);
if (isLoading) { if (isLoading) {
return <Spinner container />; return <Spinner container />;
@ -144,48 +164,35 @@ const HomeRoute = () => {
const carousels = { const carousels = {
[HomeItem.MOST_PLAYED]: { [HomeItem.MOST_PLAYED]: {
data: data: isJellyfin ? mostPlayedSongs?.data?.items : mostPlayedAlbums?.data?.items,
server?.type === ServerType.JELLYFIN itemType: isJellyfin ? LibraryItem.SONG : LibraryItem.ALBUM,
? mostPlayedSongs?.data?.items query: isJellyfin ? mostPlayedSongs : mostPlayedAlbums,
: mostPlayedAlbums?.data?.items,
itemType: server?.type === ServerType.JELLYFIN ? LibraryItem.SONG : LibraryItem.ALBUM,
pagination: {
itemsPerPage,
},
sortBy:
server?.type === ServerType.JELLYFIN
? SongListSort.PLAY_COUNT
: AlbumListSort.PLAY_COUNT,
sortOrder: SortOrder.DESC,
title: t('page.home.mostPlayed', { postProcess: 'sentenceCase' }), title: t('page.home.mostPlayed', { postProcess: 'sentenceCase' }),
}, },
[HomeItem.RANDOM]: { [HomeItem.RANDOM]: {
data: random?.data?.items, data: random?.data?.items,
itemType: LibraryItem.ALBUM, itemType: LibraryItem.ALBUM,
sortBy: AlbumListSort.RANDOM, query: random,
sortOrder: SortOrder.ASC,
title: t('page.home.explore', { postProcess: 'sentenceCase' }), title: t('page.home.explore', { postProcess: 'sentenceCase' }),
}, },
[HomeItem.RECENTLY_ADDED]: { [HomeItem.RECENTLY_ADDED]: {
data: recentlyAdded?.data?.items, data: recentlyAdded?.data?.items,
itemType: LibraryItem.ALBUM, itemType: LibraryItem.ALBUM,
pagination: { query: recentlyAdded,
itemsPerPage,
},
sortBy: AlbumListSort.RECENTLY_ADDED,
sortOrder: SortOrder.DESC,
title: t('page.home.newlyAdded', { postProcess: 'sentenceCase' }), title: t('page.home.newlyAdded', { postProcess: 'sentenceCase' }),
}, },
[HomeItem.RECENTLY_PLAYED]: { [HomeItem.RECENTLY_PLAYED]: {
data: recentlyPlayed?.data?.items, data: recentlyPlayed?.data?.items,
itemType: LibraryItem.ALBUM, itemType: LibraryItem.ALBUM,
pagination: { query: recentlyPlayed,
itemsPerPage,
},
sortBy: AlbumListSort.RECENTLY_PLAYED,
sortOrder: SortOrder.DESC,
title: t('page.home.recentlyPlayed', { postProcess: 'sentenceCase' }), title: t('page.home.recentlyPlayed', { postProcess: 'sentenceCase' }),
}, },
[HomeItem.RECENTLY_RELEASED]: {
data: recentlyReleased?.data?.items,
itemType: LibraryItem.ALBUM,
query: recentlyReleased,
title: t('page.home.recentlyReleased', { postProcess: 'sentenceCase' }),
},
}; };
const sortedCarousel = homeItems const sortedCarousel = homeItems
@ -193,7 +200,7 @@ const HomeRoute = () => {
if (item.disabled) { if (item.disabled) {
return false; return false;
} }
if (server?.type === ServerType.JELLYFIN && item.id === HomeItem.RECENTLY_PLAYED) { if (isJellyfin && item.id === HomeItem.RECENTLY_PLAYED) {
return false; return false;
} }
@ -204,36 +211,6 @@ const HomeRoute = () => {
uniqueId: item.id, uniqueId: item.id,
})); }));
const invalidateCarouselQuery = (carousel: {
itemType: LibraryItem;
sortBy: AlbumListSort | SongListSort;
sortOrder: SortOrder;
}) => {
if (carousel.itemType === LibraryItem.ALBUM) {
queryClient.invalidateQueries({
exact: false,
queryKey: queryKeys.albums.list(server?.id, {
limit: itemsPerPage,
sortBy: carousel.sortBy,
sortOrder: carousel.sortOrder,
startIndex: 0,
}),
});
}
if (carousel.itemType === LibraryItem.SONG) {
queryClient.invalidateQueries({
exact: false,
queryKey: queryKeys.songs.list(server?.id, {
limit: itemsPerPage,
sortBy: carousel.sortBy,
sortOrder: carousel.sortOrder,
startIndex: 0,
}),
});
}
};
return ( return (
<AnimatedPage> <AnimatedPage>
<NativeScrollArea <NativeScrollArea
@ -266,7 +243,7 @@ const HomeRoute = () => {
slugs: [ slugs: [
{ {
idProperty: idProperty:
server?.type === ServerType.JELLYFIN && isJellyfin &&
carousel.itemType === LibraryItem.SONG carousel.itemType === LibraryItem.SONG
? 'albumId' ? 'albumId'
: 'id', : 'id',
@ -297,8 +274,7 @@ const HomeRoute = () => {
slugs: [ slugs: [
{ {
idProperty: idProperty:
server?.type === ServerType.JELLYFIN && isJellyfin && carousel.itemType === LibraryItem.SONG
carousel.itemType === LibraryItem.SONG
? 'albumId' ? 'albumId'
: 'id', : 'id',
slugProperty: 'albumId', slugProperty: 'albumId',
@ -310,7 +286,7 @@ const HomeRoute = () => {
<Group> <Group>
<TextTitle order={3}>{carousel.title}</TextTitle> <TextTitle order={3}>{carousel.title}</TextTitle>
<ActionIcon <ActionIcon
onClick={() => invalidateCarouselQuery(carousel)} onClick={() => carousel.query.refetch()}
variant="transparent" variant="transparent"
> >
<Icon icon="refresh" /> <Icon icon="refresh" />

View file

@ -5,6 +5,7 @@ const HOME_ITEMS: Array<[string, string]> = [
[HomeItem.RANDOM, 'page.home.explore'], [HomeItem.RANDOM, 'page.home.explore'],
[HomeItem.RECENTLY_PLAYED, 'page.home.recentlyPlayed'], [HomeItem.RECENTLY_PLAYED, 'page.home.recentlyPlayed'],
[HomeItem.RECENTLY_ADDED, 'page.home.newlyAdded'], [HomeItem.RECENTLY_ADDED, 'page.home.newlyAdded'],
[HomeItem.RECENTLY_RELEASED, 'page.home.recentlyReleased'],
[HomeItem.MOST_PLAYED, 'page.home.mostPlayed'], [HomeItem.MOST_PLAYED, 'page.home.mostPlayed'],
]; ];

View file

@ -96,6 +96,7 @@ export enum HomeItem {
RANDOM = 'random', RANDOM = 'random',
RECENTLY_ADDED = 'recentlyAdded', RECENTLY_ADDED = 'recentlyAdded',
RECENTLY_PLAYED = 'recentlyPlayed', RECENTLY_PLAYED = 'recentlyPlayed',
RECENTLY_RELEASED = 'recentlyReleased',
} }
export type SortableItem<T> = { export type SortableItem<T> = {