mirror of
https://github.com/jeffvli/feishin.git
synced 2024-11-20 14:37:06 +01:00
Add dynamic card rows based on sort type
- Split album list content into separate component
This commit is contained in:
parent
7d1083d1f7
commit
f786b6f01b
161
src/renderer/features/albums/components/album-list-content.tsx
Normal file
161
src/renderer/features/albums/components/album-list-content.tsx
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
import {
|
||||||
|
ALBUM_CARD_ROWS,
|
||||||
|
VirtualGridAutoSizerContainer,
|
||||||
|
VirtualInfiniteGrid,
|
||||||
|
} from '/@/renderer/components';
|
||||||
|
import { AppRoute } from '/@/renderer/router/routes';
|
||||||
|
import { CardDisplayType, CardRow, LibraryItem } from '/@/renderer/types';
|
||||||
|
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||||
|
import { useCallback, useMemo } from 'react';
|
||||||
|
import { ListOnScrollProps } from 'react-window';
|
||||||
|
import { api } from '/@/renderer/api';
|
||||||
|
import { controller } from '/@/renderer/api/controller';
|
||||||
|
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||||
|
import { Album, AlbumListSort } from '/@/renderer/api/types';
|
||||||
|
import { useAlbumList } from '/@/renderer/features/albums/queries/album-list-query';
|
||||||
|
import { useHandlePlayQueueAdd } from '/@/renderer/features/player/hooks/use-handle-playqueue-add';
|
||||||
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
|
import { useCurrentServer, useSetAlbumStore, useAlbumListStore } from '/@/renderer/store';
|
||||||
|
|
||||||
|
export const AlbumListContent = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const server = useCurrentServer();
|
||||||
|
const page = useAlbumListStore();
|
||||||
|
const setPage = useSetAlbumStore();
|
||||||
|
const handlePlayQueueAdd = useHandlePlayQueueAdd();
|
||||||
|
|
||||||
|
const albumListQuery = useAlbumList({
|
||||||
|
limit: 1,
|
||||||
|
startIndex: 0,
|
||||||
|
...page.filter,
|
||||||
|
});
|
||||||
|
|
||||||
|
const fetch = useCallback(
|
||||||
|
async ({ skip, take }: { skip: number; take: number }) => {
|
||||||
|
const queryKey = queryKeys.albums.list(server?.id || '', {
|
||||||
|
limit: take,
|
||||||
|
startIndex: skip,
|
||||||
|
...page.filter,
|
||||||
|
});
|
||||||
|
|
||||||
|
const albums = await queryClient.fetchQuery(queryKey, async ({ signal }) =>
|
||||||
|
controller.getAlbumList({
|
||||||
|
query: {
|
||||||
|
limit: take,
|
||||||
|
startIndex: skip,
|
||||||
|
...page.filter,
|
||||||
|
},
|
||||||
|
server,
|
||||||
|
signal,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
return api.normalize.albumList(albums, server);
|
||||||
|
},
|
||||||
|
[page.filter, queryClient, server],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleGridScroll = useCallback(
|
||||||
|
(e: ListOnScrollProps) => {
|
||||||
|
setPage({
|
||||||
|
list: {
|
||||||
|
...page,
|
||||||
|
grid: {
|
||||||
|
...page.grid,
|
||||||
|
scrollOffset: e.scrollOffset,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[page, setPage],
|
||||||
|
);
|
||||||
|
|
||||||
|
const cardRows = useMemo(() => {
|
||||||
|
const rows: CardRow<Album>[] = [ALBUM_CARD_ROWS.name];
|
||||||
|
|
||||||
|
switch (page.filter.sortBy) {
|
||||||
|
case AlbumListSort.ALBUM_ARTIST:
|
||||||
|
rows.push(ALBUM_CARD_ROWS.albumArtists);
|
||||||
|
rows.push(ALBUM_CARD_ROWS.releaseYear);
|
||||||
|
break;
|
||||||
|
case AlbumListSort.ARTIST:
|
||||||
|
rows.push(ALBUM_CARD_ROWS.artists);
|
||||||
|
rows.push(ALBUM_CARD_ROWS.releaseYear);
|
||||||
|
break;
|
||||||
|
case AlbumListSort.COMMUNITY_RATING:
|
||||||
|
rows.push(ALBUM_CARD_ROWS.albumArtists);
|
||||||
|
break;
|
||||||
|
case AlbumListSort.DURATION:
|
||||||
|
rows.push(ALBUM_CARD_ROWS.albumArtists);
|
||||||
|
rows.push(ALBUM_CARD_ROWS.duration);
|
||||||
|
break;
|
||||||
|
case AlbumListSort.FAVORITED:
|
||||||
|
rows.push(ALBUM_CARD_ROWS.albumArtists);
|
||||||
|
rows.push(ALBUM_CARD_ROWS.releaseYear);
|
||||||
|
break;
|
||||||
|
case AlbumListSort.NAME:
|
||||||
|
rows.push(ALBUM_CARD_ROWS.albumArtists);
|
||||||
|
rows.push(ALBUM_CARD_ROWS.releaseYear);
|
||||||
|
break;
|
||||||
|
case AlbumListSort.PLAY_COUNT:
|
||||||
|
rows.push(ALBUM_CARD_ROWS.albumArtists);
|
||||||
|
rows.push(ALBUM_CARD_ROWS.playCount);
|
||||||
|
break;
|
||||||
|
case AlbumListSort.RANDOM:
|
||||||
|
rows.push(ALBUM_CARD_ROWS.albumArtists);
|
||||||
|
rows.push(ALBUM_CARD_ROWS.releaseYear);
|
||||||
|
break;
|
||||||
|
case AlbumListSort.RATING:
|
||||||
|
rows.push(ALBUM_CARD_ROWS.albumArtists);
|
||||||
|
rows.push(ALBUM_CARD_ROWS.rating);
|
||||||
|
break;
|
||||||
|
case AlbumListSort.RECENTLY_ADDED:
|
||||||
|
rows.push(ALBUM_CARD_ROWS.albumArtists);
|
||||||
|
rows.push(ALBUM_CARD_ROWS.createdAt);
|
||||||
|
break;
|
||||||
|
case AlbumListSort.RECENTLY_PLAYED:
|
||||||
|
rows.push(ALBUM_CARD_ROWS.albumArtists);
|
||||||
|
rows.push(ALBUM_CARD_ROWS.lastPlayedAt);
|
||||||
|
break;
|
||||||
|
case AlbumListSort.SONG_COUNT:
|
||||||
|
rows.push(ALBUM_CARD_ROWS.albumArtists);
|
||||||
|
rows.push(ALBUM_CARD_ROWS.songCount);
|
||||||
|
break;
|
||||||
|
case AlbumListSort.YEAR:
|
||||||
|
rows.push(ALBUM_CARD_ROWS.albumArtists);
|
||||||
|
rows.push(ALBUM_CARD_ROWS.releaseYear);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rows;
|
||||||
|
}, [page.filter.sortBy]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<VirtualGridAutoSizerContainer>
|
||||||
|
<AutoSizer>
|
||||||
|
{({ height, width }) => (
|
||||||
|
<VirtualInfiniteGrid
|
||||||
|
cardRows={cardRows}
|
||||||
|
display={page.display || CardDisplayType.CARD}
|
||||||
|
fetchFn={fetch}
|
||||||
|
handlePlayQueueAdd={handlePlayQueueAdd}
|
||||||
|
height={height}
|
||||||
|
initialScrollOffset={page?.grid.scrollOffset || 0}
|
||||||
|
itemCount={albumListQuery?.data?.totalRecordCount || 0}
|
||||||
|
itemGap={20}
|
||||||
|
itemSize={150 + page.grid?.size}
|
||||||
|
itemType={LibraryItem.ALBUM}
|
||||||
|
minimumBatchSize={40}
|
||||||
|
refresh={page.filter}
|
||||||
|
route={{
|
||||||
|
route: AppRoute.LIBRARY_ALBUMS_DETAIL,
|
||||||
|
slugs: [{ idProperty: 'id', slugProperty: 'albumId' }],
|
||||||
|
}}
|
||||||
|
width={width}
|
||||||
|
onScroll={handleGridScroll}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</AutoSizer>
|
||||||
|
</VirtualGridAutoSizerContainer>
|
||||||
|
);
|
||||||
|
};
|
@ -33,27 +33,31 @@ import { JellyfinAlbumFilters } from '/@/renderer/features/albums/components/jel
|
|||||||
|
|
||||||
const FILTERS = {
|
const FILTERS = {
|
||||||
jellyfin: [
|
jellyfin: [
|
||||||
{ name: 'Album Artist', value: AlbumListSort.ALBUM_ARTIST },
|
{ defaultOrder: SortOrder.ASC, name: 'Album Artist', value: AlbumListSort.ALBUM_ARTIST },
|
||||||
{ name: 'Community Rating', value: AlbumListSort.COMMUNITY_RATING },
|
{
|
||||||
{ name: 'Critic Rating', value: AlbumListSort.CRITIC_RATING },
|
defaultOrder: SortOrder.DESC,
|
||||||
{ name: 'Name', value: AlbumListSort.NAME },
|
name: 'Community Rating',
|
||||||
{ name: 'Random', value: AlbumListSort.RANDOM },
|
value: AlbumListSort.COMMUNITY_RATING,
|
||||||
{ name: 'Recently Added', value: AlbumListSort.RECENTLY_ADDED },
|
},
|
||||||
{ name: 'Release Date', value: AlbumListSort.RELEASE_DATE },
|
{ defaultOrder: SortOrder.DESC, name: 'Critic Rating', value: AlbumListSort.CRITIC_RATING },
|
||||||
|
{ defaultOrder: SortOrder.ASC, name: 'Name', value: AlbumListSort.NAME },
|
||||||
|
{ defaultOrder: SortOrder.ASC, name: 'Random', value: AlbumListSort.RANDOM },
|
||||||
|
{ defaultOrder: SortOrder.DESC, name: 'Recently Added', value: AlbumListSort.RECENTLY_ADDED },
|
||||||
|
{ defaultOrder: SortOrder.DESC, name: 'Release Date', value: AlbumListSort.RELEASE_DATE },
|
||||||
],
|
],
|
||||||
navidrome: [
|
navidrome: [
|
||||||
{ name: 'Album Artist', value: AlbumListSort.ALBUM_ARTIST },
|
{ defaultOrder: SortOrder.ASC, name: 'Album Artist', value: AlbumListSort.ALBUM_ARTIST },
|
||||||
{ name: 'Artist', value: AlbumListSort.ARTIST },
|
{ defaultOrder: SortOrder.ASC, name: 'Artist', value: AlbumListSort.ARTIST },
|
||||||
{ name: 'Duration', value: AlbumListSort.DURATION },
|
{ defaultOrder: SortOrder.DESC, name: 'Duration', value: AlbumListSort.DURATION },
|
||||||
{ name: 'Name', value: AlbumListSort.NAME },
|
{ defaultOrder: SortOrder.DESC, name: 'Most Played', value: AlbumListSort.PLAY_COUNT },
|
||||||
{ name: 'Play Count', value: AlbumListSort.PLAY_COUNT },
|
{ defaultOrder: SortOrder.ASC, name: 'Name', value: AlbumListSort.NAME },
|
||||||
{ name: 'Random', value: AlbumListSort.RANDOM },
|
{ defaultOrder: SortOrder.ASC, name: 'Random', value: AlbumListSort.RANDOM },
|
||||||
{ name: 'Rating', value: AlbumListSort.RATING },
|
{ defaultOrder: SortOrder.DESC, name: 'Rating', value: AlbumListSort.RATING },
|
||||||
{ name: 'Recently Added', value: AlbumListSort.RECENTLY_ADDED },
|
{ defaultOrder: SortOrder.DESC, name: 'Recently Added', value: AlbumListSort.RECENTLY_ADDED },
|
||||||
{ name: 'Recently Played', value: AlbumListSort.RECENTLY_PLAYED },
|
{ defaultOrder: SortOrder.DESC, name: 'Recently Played', value: AlbumListSort.RECENTLY_PLAYED },
|
||||||
{ name: 'Song Count', value: AlbumListSort.SONG_COUNT },
|
{ defaultOrder: SortOrder.DESC, name: 'Song Count', value: AlbumListSort.SONG_COUNT },
|
||||||
{ name: 'Favorited', value: AlbumListSort.FAVORITED },
|
{ defaultOrder: SortOrder.ASC, name: 'Favorited', value: AlbumListSort.FAVORITED },
|
||||||
{ name: 'Year', value: AlbumListSort.YEAR },
|
{ defaultOrder: SortOrder.DESC, name: 'Year', value: AlbumListSort.YEAR },
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -79,9 +83,7 @@ export const AlbumListHeader = () => {
|
|||||||
|
|
||||||
const sortByLabel =
|
const sortByLabel =
|
||||||
(server?.type &&
|
(server?.type &&
|
||||||
(FILTERS[server.type as keyof typeof FILTERS] as { name: string; value: string }[]).find(
|
FILTERS[server.type as keyof typeof FILTERS].find((f) => f.value === filters.sortBy)?.name) ||
|
||||||
(f) => f.value === filters.sortBy,
|
|
||||||
)?.name) ||
|
|
||||||
'Unknown';
|
'Unknown';
|
||||||
|
|
||||||
const setSize = throttle(
|
const setSize = throttle(
|
||||||
@ -94,12 +96,18 @@ export const AlbumListHeader = () => {
|
|||||||
|
|
||||||
const handleSetSortBy = useCallback(
|
const handleSetSortBy = useCallback(
|
||||||
(e: MouseEvent<HTMLButtonElement>) => {
|
(e: MouseEvent<HTMLButtonElement>) => {
|
||||||
if (!e.currentTarget?.value) return;
|
if (!e.currentTarget?.value || !server?.type) return;
|
||||||
|
|
||||||
|
const sortOrder = FILTERS[server.type as keyof typeof FILTERS].find(
|
||||||
|
(f) => f.value === e.currentTarget.value,
|
||||||
|
)?.defaultOrder;
|
||||||
|
|
||||||
setFilter({
|
setFilter({
|
||||||
sortBy: e.currentTarget.value as AlbumListSort,
|
sortBy: e.currentTarget.value as AlbumListSort,
|
||||||
|
sortOrder: sortOrder || SortOrder.ASC,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[setFilter],
|
[server?.type, setFilter],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleSetMusicFolder = useCallback(
|
const handleSetMusicFolder = useCallback(
|
||||||
@ -170,15 +178,14 @@ export const AlbumListHeader = () => {
|
|||||||
<DropdownMenu.Target>
|
<DropdownMenu.Target>
|
||||||
<Button
|
<Button
|
||||||
compact
|
compact
|
||||||
pl={0}
|
px={0}
|
||||||
pr="0.5rem"
|
|
||||||
rightIcon={<RiArrowDownSLine size={15} />}
|
rightIcon={<RiArrowDownSLine size={15} />}
|
||||||
size="xl"
|
size="xl"
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
>
|
>
|
||||||
<TextTitle
|
<TextTitle
|
||||||
fw="bold"
|
fw="bold"
|
||||||
order={2}
|
order={3}
|
||||||
>
|
>
|
||||||
Albums
|
Albums
|
||||||
</TextTitle>
|
</TextTitle>
|
||||||
|
@ -1,127 +1,14 @@
|
|||||||
/* eslint-disable no-plusplus */
|
import { VirtualGridContainer } from '/@/renderer/components';
|
||||||
import { useCallback } from 'react';
|
|
||||||
import { useQueryClient } from '@tanstack/react-query';
|
|
||||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
|
||||||
import type { ListOnScrollProps } from 'react-window';
|
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
|
||||||
import {
|
|
||||||
VirtualGridAutoSizerContainer,
|
|
||||||
VirtualGridContainer,
|
|
||||||
VirtualInfiniteGrid,
|
|
||||||
} from '/@/renderer/components';
|
|
||||||
import { AppRoute } from '/@/renderer/router/routes';
|
|
||||||
import { useAlbumListStore, useCurrentServer, useSetAlbumStore } from '/@/renderer/store';
|
|
||||||
import { LibraryItem, CardDisplayType } from '/@/renderer/types';
|
|
||||||
import { useAlbumList } from '../queries/album-list-query';
|
|
||||||
import { controller } from '/@/renderer/api/controller';
|
|
||||||
import { AnimatedPage } from '/@/renderer/features/shared';
|
import { AnimatedPage } from '/@/renderer/features/shared';
|
||||||
import { AlbumListHeader } from '/@/renderer/features/albums/components/album-list-header';
|
import { AlbumListHeader } from '/@/renderer/features/albums/components/album-list-header';
|
||||||
import { api } from '/@/renderer/api';
|
import { AlbumListContent } from '/@/renderer/features/albums/components/album-list-content';
|
||||||
import { useHandlePlayQueueAdd } from '/@/renderer/features/player/hooks/use-handle-playqueue-add';
|
|
||||||
|
|
||||||
const AlbumListRoute = () => {
|
const AlbumListRoute = () => {
|
||||||
const queryClient = useQueryClient();
|
|
||||||
const server = useCurrentServer();
|
|
||||||
const setPage = useSetAlbumStore();
|
|
||||||
|
|
||||||
const page = useAlbumListStore();
|
|
||||||
const handlePlayQueueAdd = useHandlePlayQueueAdd();
|
|
||||||
|
|
||||||
const albumListQuery = useAlbumList({
|
|
||||||
limit: 1,
|
|
||||||
startIndex: 0,
|
|
||||||
...page.filter,
|
|
||||||
});
|
|
||||||
|
|
||||||
const fetch = useCallback(
|
|
||||||
async ({ skip, take }: { skip: number; take: number }) => {
|
|
||||||
const queryKey = queryKeys.albums.list(server?.id || '', {
|
|
||||||
limit: take,
|
|
||||||
startIndex: skip,
|
|
||||||
...page.filter,
|
|
||||||
});
|
|
||||||
|
|
||||||
const albums = await queryClient.fetchQuery(queryKey, async ({ signal }) =>
|
|
||||||
controller.getAlbumList({
|
|
||||||
query: {
|
|
||||||
limit: take,
|
|
||||||
startIndex: skip,
|
|
||||||
...page.filter,
|
|
||||||
},
|
|
||||||
server,
|
|
||||||
signal,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
return api.normalize.albumList(albums, server);
|
|
||||||
},
|
|
||||||
[page.filter, queryClient, server],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleGridScroll = useCallback(
|
|
||||||
(e: ListOnScrollProps) => {
|
|
||||||
setPage({
|
|
||||||
list: {
|
|
||||||
...page,
|
|
||||||
grid: {
|
|
||||||
...page.grid,
|
|
||||||
scrollOffset: e.scrollOffset,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[page, setPage],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AnimatedPage>
|
<AnimatedPage>
|
||||||
<VirtualGridContainer>
|
<VirtualGridContainer>
|
||||||
<AlbumListHeader />
|
<AlbumListHeader />
|
||||||
<VirtualGridAutoSizerContainer>
|
<AlbumListContent />
|
||||||
<AutoSizer>
|
|
||||||
{({ height, width }) => (
|
|
||||||
<VirtualInfiniteGrid
|
|
||||||
cardRows={[
|
|
||||||
{
|
|
||||||
property: 'name',
|
|
||||||
route: {
|
|
||||||
route: AppRoute.LIBRARY_ALBUMS_DETAIL,
|
|
||||||
slugs: [{ idProperty: 'id', slugProperty: 'albumId' }],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
arrayProperty: 'name',
|
|
||||||
property: 'albumArtists',
|
|
||||||
route: {
|
|
||||||
route: AppRoute.LIBRARY_ALBUMARTISTS_DETAIL,
|
|
||||||
slugs: [{ idProperty: 'id', slugProperty: 'albumArtistId' }],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
property: 'releaseYear',
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
display={page.display || CardDisplayType.CARD}
|
|
||||||
fetchFn={fetch}
|
|
||||||
handlePlayQueueAdd={handlePlayQueueAdd}
|
|
||||||
height={height}
|
|
||||||
initialScrollOffset={page?.grid.scrollOffset || 0}
|
|
||||||
itemCount={albumListQuery?.data?.totalRecordCount || 0}
|
|
||||||
itemGap={20}
|
|
||||||
itemSize={150 + page.grid?.size}
|
|
||||||
itemType={LibraryItem.ALBUM}
|
|
||||||
minimumBatchSize={40}
|
|
||||||
refresh={page.filter}
|
|
||||||
route={{
|
|
||||||
route: AppRoute.LIBRARY_ALBUMS_DETAIL,
|
|
||||||
slugs: [{ idProperty: 'id', slugProperty: 'albumId' }],
|
|
||||||
}}
|
|
||||||
width={width}
|
|
||||||
onScroll={handleGridScroll}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</AutoSizer>
|
|
||||||
</VirtualGridAutoSizerContainer>
|
|
||||||
</VirtualGridContainer>
|
</VirtualGridContainer>
|
||||||
</AnimatedPage>
|
</AnimatedPage>
|
||||||
);
|
);
|
||||||
|
Loading…
Reference in New Issue
Block a user