Add new swiper carousels to pages

This commit is contained in:
jeffvli 2023-05-17 17:12:23 -07:00
parent 58d912065b
commit 48ef7a987f
3 changed files with 117 additions and 178 deletions

View file

@ -1,5 +1,5 @@
import { MutableRefObject, useCallback, useMemo } from 'react'; import { MutableRefObject, useCallback, useMemo } from 'react';
import { Button, GridCarousel, Text, TextTitle } from '/@/renderer/components'; import { Button, Text } from '/@/renderer/components';
import { ColDef, RowDoubleClickedEvent, RowHeightParams, RowNode } from '@ag-grid-community/core'; import { ColDef, RowDoubleClickedEvent, RowHeightParams, RowNode } from '@ag-grid-community/core';
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact'; import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
import { Box, Group, Stack } from '@mantine/core'; import { Box, Group, Stack } from '@mantine/core';
@ -31,6 +31,7 @@ import {
useFixedTableHeader, useFixedTableHeader,
VirtualTable, VirtualTable,
} from '/@/renderer/components/virtual-table'; } from '/@/renderer/components/virtual-table';
import { SwiperGridCarousel } from '/@/renderer/components/grid-carousel';
const isFullWidthRow = (node: RowNode) => { const isFullWidthRow = (node: RowNode) => {
return node.id?.includes('disc-'); return node.id?.includes('disc-');
@ -174,13 +175,14 @@ export const AlbumDetailContent = ({ tableRef }: AlbumDetailContentProps) => {
query: { query: {
_custom: { _custom: {
jellyfin: { jellyfin: {
albumArtistIds: detailQuery?.data?.albumArtists[0]?.id, AlbumArtistIds: detailQuery?.data?.albumArtists[0]?.id,
ExcludeItemIds: detailQuery?.data?.id,
}, },
navidrome: { navidrome: {
artist_id: detailQuery?.data?.albumArtists[0]?.id, artist_id: detailQuery?.data?.albumArtists[0]?.id,
}, },
}, },
limit: itemsPerPage, limit: 10,
sortBy: AlbumListSort.YEAR, sortBy: AlbumListSort.YEAR,
sortOrder: SortOrder.DESC, sortOrder: SortOrder.DESC,
startIndex: pagination.artist * itemsPerPage, startIndex: pagination.artist * itemsPerPage,
@ -198,14 +200,7 @@ export const AlbumDetailContent = ({ tableRef }: AlbumDetailContentProps) => {
hasPreviousPage: pagination.artist > 0, hasPreviousPage: pagination.artist > 0,
itemsPerPage, itemsPerPage,
}, },
title: ( title: 'More from this artist',
<TextTitle
order={2}
weight={700}
>
More from this artist
</TextTitle>
),
uniqueId: 'mostPlayed', uniqueId: 'mostPlayed',
}, },
]; ];
@ -325,7 +320,10 @@ export const AlbumDetailContent = ({ tableRef }: AlbumDetailContentProps) => {
</Group> </Group>
</Box> </Box>
)} )}
<Box ref={tableContainerRef}> <Box
ref={tableContainerRef}
style={{ minHeight: '300px' }}
>
<VirtualTable <VirtualTable
ref={tableRef} ref={tableRef}
autoFitColumns autoFitColumns
@ -369,8 +367,11 @@ export const AlbumDetailContent = ({ tableRef }: AlbumDetailContentProps) => {
ref={cq.ref} ref={cq.ref}
mt="5rem" mt="5rem"
> >
<>
{cq.height || cq.width ? (
<>
{carousels.map((carousel, index) => ( {carousels.map((carousel, index) => (
<GridCarousel <SwiperGridCarousel
key={`carousel-${carousel.uniqueId}-${index}`} key={`carousel-${carousel.uniqueId}-${index}`}
cardRows={[ cardRows={[
{ {
@ -389,16 +390,22 @@ export const AlbumDetailContent = ({ tableRef }: AlbumDetailContentProps) => {
}, },
}, },
]} ]}
containerWidth={cq.width}
data={carousel.data} data={carousel.data}
isLoading={carousel.loading}
itemType={LibraryItem.ALBUM} itemType={LibraryItem.ALBUM}
loading={carousel.loading} route={{
pagination={carousel.pagination} route: AppRoute.LIBRARY_ALBUMS_DETAIL,
slugs: [{ idProperty: 'id', slugProperty: 'albumId' }],
}}
title={{
label: carousel.title,
}}
uniqueId={carousel.uniqueId} uniqueId={carousel.uniqueId}
> />
<GridCarousel.Title>{carousel.title}</GridCarousel.Title>
</GridCarousel>
))} ))}
</>
) : null}
</>
</Stack> </Stack>
</ContentContainer> </ContentContainer>
); );

View file

@ -1,5 +1,5 @@
import { useMemo } from 'react'; import { useMemo } from 'react';
import { Button, GridCarousel, Text, TextTitle } from '/@/renderer/components'; import { Button, Text, TextTitle } from '/@/renderer/components';
import { ColDef, RowDoubleClickedEvent } from '@ag-grid-community/core'; import { ColDef, RowDoubleClickedEvent } from '@ag-grid-community/core';
import { Box, Group, Stack } from '@mantine/core'; import { Box, Group, Stack } from '@mantine/core';
import { RiHeartFill, RiHeartLine, RiMoreFill } from 'react-icons/ri'; import { RiHeartFill, RiHeartLine, RiMoreFill } from 'react-icons/ri';
@ -14,7 +14,7 @@ import {
useHandleGeneralContextMenu, useHandleGeneralContextMenu,
useHandleTableContextMenu, useHandleTableContextMenu,
} from '/@/renderer/features/context-menu'; } from '/@/renderer/features/context-menu';
import { Play, TableColumn } from '/@/renderer/types'; import { CardRow, Play, TableColumn } from '/@/renderer/types';
import { import {
ARTIST_CONTEXT_MENU_ITEMS, ARTIST_CONTEXT_MENU_ITEMS,
SONG_CONTEXT_MENU_ITEMS, SONG_CONTEXT_MENU_ITEMS,
@ -22,6 +22,8 @@ import {
import { PlayButton, useCreateFavorite, useDeleteFavorite } from '/@/renderer/features/shared'; import { PlayButton, useCreateFavorite, useDeleteFavorite } from '/@/renderer/features/shared';
import { useAlbumList } from '/@/renderer/features/albums/queries/album-list-query'; import { useAlbumList } from '/@/renderer/features/albums/queries/album-list-query';
import { import {
Album,
AlbumArtist,
AlbumListSort, AlbumListSort,
LibraryItem, LibraryItem,
QueueSong, QueueSong,
@ -32,6 +34,7 @@ import { usePlayQueueAdd } from '/@/renderer/features/player';
import { useAlbumArtistDetail } from '/@/renderer/features/artists/queries/album-artist-detail-query'; import { useAlbumArtistDetail } from '/@/renderer/features/artists/queries/album-artist-detail-query';
import { useTopSongsList } from '/@/renderer/features/artists/queries/top-songs-list-query'; import { useTopSongsList } from '/@/renderer/features/artists/queries/top-songs-list-query';
import { getColumnDefs, VirtualTable } from '/@/renderer/components/virtual-table'; import { getColumnDefs, VirtualTable } from '/@/renderer/components/virtual-table';
import { SwiperGridCarousel } from '/@/renderer/components/grid-carousel';
const ContentContainer = styled.div` const ContentContainer = styled.div`
position: relative; position: relative;
@ -55,7 +58,6 @@ export const AlbumArtistDetailContent = () => {
const cq = useContainerQuery(); const cq = useContainerQuery();
const handlePlayQueueAdd = usePlayQueueAdd(); const handlePlayQueueAdd = usePlayQueueAdd();
const server = useCurrentServer(); const server = useCurrentServer();
const itemsPerPage = cq.isXl ? 9 : cq.isLg ? 7 : cq.isMd ? 5 : cq.isSm ? 4 : 3;
const detailQuery = useAlbumArtistDetail({ query: { id: albumArtistId }, serverId: server?.id }); const detailQuery = useAlbumArtistDetail({ query: { id: albumArtistId }, serverId: server?.id });
@ -77,7 +79,7 @@ export const AlbumArtistDetailContent = () => {
query: { query: {
_custom: { _custom: {
jellyfin: { jellyfin: {
...(server?.type === ServerType.JELLYFIN ? { artistIds: albumArtistId } : undefined), ...(server?.type === ServerType.JELLYFIN ? { ArtistIds: albumArtistId } : undefined),
}, },
navidrome: { navidrome: {
...(server?.type === ServerType.NAVIDROME ...(server?.type === ServerType.NAVIDROME
@ -85,7 +87,7 @@ export const AlbumArtistDetailContent = () => {
: undefined), : undefined),
}, },
}, },
limit: itemsPerPage, // limit: 10,
sortBy: AlbumListSort.RELEASE_DATE, sortBy: AlbumListSort.RELEASE_DATE,
sortOrder: SortOrder.DESC, sortOrder: SortOrder.DESC,
startIndex: 0, startIndex: 0,
@ -98,7 +100,7 @@ export const AlbumArtistDetailContent = () => {
_custom: { _custom: {
jellyfin: { jellyfin: {
...(server?.type === ServerType.JELLYFIN ...(server?.type === ServerType.JELLYFIN
? { contributingArtistIds: albumArtistId } ? { ContributingArtistIds: albumArtistId }
: undefined), : undefined),
}, },
navidrome: { navidrome: {
@ -107,7 +109,7 @@ export const AlbumArtistDetailContent = () => {
: undefined), : undefined),
}, },
}, },
limit: itemsPerPage, // limit: 10,
sortBy: AlbumListSort.RELEASE_DATE, sortBy: AlbumListSort.RELEASE_DATE,
sortOrder: SortOrder.DESC, sortOrder: SortOrder.DESC,
startIndex: 0, startIndex: 0,
@ -140,7 +142,7 @@ export const AlbumArtistDetailContent = () => {
[], [],
); );
const cardRows = { const cardRows: Record<string, CardRow<Album>[] | CardRow<AlbumArtist>[]> = {
album: [ album: [
{ {
property: 'name', property: 'name',
@ -169,17 +171,25 @@ export const AlbumArtistDetailContent = () => {
], ],
}; };
const cardRoutes = {
album: {
route: AppRoute.LIBRARY_ALBUMS_DETAIL,
slugs: [{ idProperty: 'id', slugProperty: 'albumId' }],
},
albumArtist: {
route: AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL,
slugs: [{ idProperty: 'id', slugProperty: 'albumArtistId' }],
},
};
const carousels = [ const carousels = [
{ {
data: recentAlbumsQuery?.data?.items, data: recentAlbumsQuery?.data?.items,
isHidden: !recentAlbumsQuery?.data?.items?.length, isHidden: !recentAlbumsQuery?.data?.items?.length,
itemType: LibraryItem.ALBUM, itemType: LibraryItem.ALBUM,
loading: recentAlbumsQuery?.isLoading || recentAlbumsQuery.isFetching, loading: recentAlbumsQuery?.isLoading || recentAlbumsQuery.isFetching,
pagination: {
itemsPerPage,
},
title: ( title: (
<> <Group align="flex-end">
<TextTitle <TextTitle
order={2} order={2}
weight={700} weight={700}
@ -195,7 +205,7 @@ export const AlbumArtistDetailContent = () => {
> >
View discography View discography
</Button> </Button>
</> </Group>
), ),
uniqueId: 'recentReleases', uniqueId: 'recentReleases',
}, },
@ -204,9 +214,6 @@ export const AlbumArtistDetailContent = () => {
isHidden: !compilationAlbumsQuery?.data?.items?.length, isHidden: !compilationAlbumsQuery?.data?.items?.length,
itemType: LibraryItem.ALBUM, itemType: LibraryItem.ALBUM,
loading: compilationAlbumsQuery?.isLoading || compilationAlbumsQuery.isFetching, loading: compilationAlbumsQuery?.isLoading || compilationAlbumsQuery.isFetching,
pagination: {
itemsPerPage,
},
title: ( title: (
<TextTitle <TextTitle
order={2} order={2}
@ -218,13 +225,10 @@ export const AlbumArtistDetailContent = () => {
uniqueId: 'compilationAlbums', uniqueId: 'compilationAlbums',
}, },
{ {
data: detailQuery?.data?.similarArtists?.slice(0, itemsPerPage), data: detailQuery?.data?.similarArtists || [],
isHidden: !detailQuery?.data?.similarArtists, isHidden: !detailQuery?.data?.similarArtists,
itemType: LibraryItem.ALBUM_ARTIST, itemType: LibraryItem.ALBUM_ARTIST,
loading: detailQuery?.isLoading || detailQuery.isFetching, loading: detailQuery?.isLoading || detailQuery.isFetching,
pagination: {
itemsPerPage,
},
title: ( title: (
<TextTitle <TextTitle
order={2} order={2}
@ -446,18 +450,23 @@ export const AlbumArtistDetailContent = () => {
{carousels {carousels
.filter((c) => !c.isHidden) .filter((c) => !c.isHidden)
.map((carousel) => ( .map((carousel) => (
<GridCarousel <SwiperGridCarousel
key={`carousel-${carousel.uniqueId}`} key={`carousel-${carousel.uniqueId}`}
cardRows={cardRows[carousel.itemType as keyof typeof cardRows]} cardRows={cardRows[carousel.itemType as keyof typeof cardRows]}
containerWidth={cq.width}
data={carousel.data} data={carousel.data}
isLoading={carousel.loading}
itemType={carousel.itemType} itemType={carousel.itemType}
loading={carousel.loading} route={cardRoutes[carousel.itemType as keyof typeof cardRoutes]}
pagination={carousel.pagination} swiperProps={{
grid: {
rows: 2,
},
}}
title={{
label: carousel.title,
}}
uniqueId={carousel.uniqueId} uniqueId={carousel.uniqueId}
> />
<GridCarousel.Title>{carousel.title}</GridCarousel.Title>
</GridCarousel>
))} ))}
</Stack> </Stack>
</Box> </Box>

View file

@ -1,27 +1,22 @@
import { useCallback, useMemo, useRef } from 'react'; import { useMemo, useRef } from 'react';
import { Box, Stack } from '@mantine/core'; import { Box, Stack } from '@mantine/core';
import { useSetState } from '@mantine/hooks';
import { AlbumListSort, LibraryItem, ServerType, SortOrder } from '/@/renderer/api/types'; import { AlbumListSort, LibraryItem, ServerType, SortOrder } from '/@/renderer/api/types';
import { TextTitle, FeatureCarousel, GridCarousel, NativeScrollArea } from '/@/renderer/components'; import { FeatureCarousel, NativeScrollArea } from '/@/renderer/components';
import { useAlbumList } from '/@/renderer/features/albums'; import { useAlbumList } from '/@/renderer/features/albums';
import { useRecentlyPlayed } from '/@/renderer/features/home/queries/recently-played-query'; import { useRecentlyPlayed } from '/@/renderer/features/home/queries/recently-played-query';
import { AnimatedPage, LibraryHeaderBar } from '/@/renderer/features/shared'; import { AnimatedPage, LibraryHeaderBar } from '/@/renderer/features/shared';
import { useContainerQuery } from '/@/renderer/hooks'; import { useContainerQuery } from '/@/renderer/hooks';
import { AppRoute } from '/@/renderer/router/routes'; import { AppRoute } from '/@/renderer/router/routes';
import { useCurrentServer } from '/@/renderer/store'; import { useCurrentServer, useWindowSettings } from '/@/renderer/store';
import { SwiperGridCarousel } from '/@/renderer/components/grid-carousel';
import { Platform } from '/@/renderer/types';
const HomeRoute = () => { const HomeRoute = () => {
const scrollAreaRef = useRef<HTMLDivElement>(null); const scrollAreaRef = useRef<HTMLDivElement>(null);
const server = useCurrentServer(); const server = useCurrentServer();
const cq = useContainerQuery(); const cq = useContainerQuery();
const itemsPerPage = cq.isXl ? 9 : cq.isLg ? 7 : cq.isMd ? 5 : cq.isSm ? 4 : 3; const itemsPerPage = 25;
const { windowBarStyle } = useWindowSettings();
const [pagination, setPagination] = useSetState({
mostPlayed: 0,
random: 0,
recentlyAdded: 0,
recentlyPlayed: 0,
});
const feature = useAlbumList({ const feature = useAlbumList({
options: { options: {
@ -43,154 +38,85 @@ const HomeRoute = () => {
const random = useAlbumList({ const random = useAlbumList({
options: { options: {
cacheTime: 1000 * 60, staleTime: 1000 * 60 * 5,
keepPreviousData: true,
staleTime: 1000 * 60,
}, },
query: { query: {
limit: itemsPerPage, limit: itemsPerPage,
sortBy: AlbumListSort.RANDOM, sortBy: AlbumListSort.RANDOM,
sortOrder: SortOrder.ASC, sortOrder: SortOrder.ASC,
startIndex: pagination.random * itemsPerPage, startIndex: 0,
}, },
serverId: server?.id, serverId: server?.id,
}); });
const recentlyPlayed = useRecentlyPlayed({ const recentlyPlayed = useRecentlyPlayed({
options: { options: {
keepPreviousData: true,
staleTime: 0, staleTime: 0,
}, },
query: { query: {
limit: itemsPerPage, limit: itemsPerPage,
sortBy: AlbumListSort.RECENTLY_PLAYED, sortBy: AlbumListSort.RECENTLY_PLAYED,
sortOrder: SortOrder.DESC, sortOrder: SortOrder.DESC,
startIndex: pagination.recentlyPlayed * itemsPerPage, startIndex: 0,
}, },
serverId: server?.id, serverId: server?.id,
}); });
const recentlyAdded = useAlbumList({ const recentlyAdded = useAlbumList({
options: {
keepPreviousData: true,
staleTime: 1000 * 60,
},
query: { query: {
limit: itemsPerPage, limit: itemsPerPage,
sortBy: AlbumListSort.RECENTLY_ADDED, sortBy: AlbumListSort.RECENTLY_ADDED,
sortOrder: SortOrder.DESC, sortOrder: SortOrder.DESC,
startIndex: pagination.recentlyAdded * itemsPerPage, startIndex: 0,
}, },
serverId: server?.id, serverId: server?.id,
}); });
const mostPlayed = useAlbumList({ const mostPlayed = useAlbumList({
options: { options: {
keepPreviousData: true, staleTime: 1000 * 60 * 5,
staleTime: 1000 * 60 * 60,
}, },
query: { query: {
limit: itemsPerPage, limit: itemsPerPage,
sortBy: AlbumListSort.PLAY_COUNT, sortBy: AlbumListSort.PLAY_COUNT,
sortOrder: SortOrder.DESC, sortOrder: SortOrder.DESC,
startIndex: pagination.mostPlayed * itemsPerPage, startIndex: 0,
}, },
serverId: server?.id, serverId: server?.id,
}); });
const handleNextPage = useCallback(
(key: 'mostPlayed' | 'random' | 'recentlyAdded' | 'recentlyPlayed') => {
setPagination({
[key]: pagination[key as keyof typeof pagination] + 1,
});
},
[pagination, setPagination],
);
const handlePreviousPage = useCallback(
(key: 'mostPlayed' | 'random' | 'recentlyAdded' | 'recentlyPlayed') => {
setPagination({
[key]: pagination[key as keyof typeof pagination] - 1,
});
},
[pagination, setPagination],
);
const carousels = [ const carousels = [
{ {
data: random?.data?.items, data: random?.data?.items,
loading: random?.isLoading || random.isFetching, loading: random?.isLoading,
pagination: { title: 'Explore from your library',
handleNextPage: () => handleNextPage('random'),
handlePreviousPage: () => handlePreviousPage('random'),
hasPreviousPage: pagination.random > 0,
itemsPerPage,
},
title: (
<TextTitle
order={2}
weight={700}
>
Explore from your library
</TextTitle>
),
uniqueId: 'random', uniqueId: 'random',
}, },
{ {
data: recentlyPlayed?.data?.items, data: recentlyPlayed?.data?.items,
loading: recentlyPlayed?.isLoading || recentlyPlayed.isFetching, loading: recentlyPlayed?.isLoading,
pagination: { pagination: {
handleNextPage: () => handleNextPage('recentlyPlayed'),
handlePreviousPage: () => handlePreviousPage('recentlyPlayed'),
hasPreviousPage: pagination.recentlyPlayed > 0,
itemsPerPage, itemsPerPage,
}, },
title: ( title: 'Recently played',
<TextTitle
order={2}
weight={700}
>
Recently played
</TextTitle>
),
uniqueId: 'recentlyPlayed', uniqueId: 'recentlyPlayed',
}, },
{ {
data: recentlyAdded?.data?.items, data: recentlyAdded?.data?.items,
loading: recentlyAdded?.isLoading || recentlyAdded.isFetching, loading: recentlyAdded?.isLoading,
pagination: { pagination: {
handleNextPage: () => handleNextPage('recentlyAdded'),
handlePreviousPage: () => handlePreviousPage('recentlyAdded'),
hasPreviousPage: pagination.recentlyAdded > 0,
itemsPerPage, itemsPerPage,
}, },
title: ( title: 'Newly added releases',
<TextTitle
order={2}
weight={700}
>
Newly added releases
</TextTitle>
),
uniqueId: 'recentlyAdded', uniqueId: 'recentlyAdded',
}, },
{ {
data: mostPlayed?.data?.items, data: mostPlayed?.data?.items,
loading: mostPlayed?.isLoading || mostPlayed.isFetching, loading: mostPlayed?.isLoading,
pagination: { pagination: {
handleNextPage: () => handleNextPage('mostPlayed'),
handlePreviousPage: () => handlePreviousPage('mostPlayed'),
hasPreviousPage: pagination.mostPlayed > 0,
itemsPerPage, itemsPerPage,
}, },
title: ( title: 'Most played',
<TextTitle
order={2}
weight={700}
>
Most played
</TextTitle>
),
uniqueId: 'mostPlayed', uniqueId: 'mostPlayed',
}, },
]; ];
@ -211,12 +137,8 @@ const HomeRoute = () => {
> >
<Box <Box
ref={cq.ref} ref={cq.ref}
pt="3rem" pt={windowBarStyle === Platform.WEB ? '5rem' : '3rem'}
px="2rem" px="2rem"
sx={{
height: '100%',
width: '100%',
}}
> >
<Stack spacing={35}> <Stack spacing={35}>
<FeatureCarousel data={featureItemsWithImage} /> <FeatureCarousel data={featureItemsWithImage} />
@ -231,9 +153,9 @@ const HomeRoute = () => {
return carousel; return carousel;
}) })
.map((carousel, index) => ( .map((carousel) => (
<GridCarousel <SwiperGridCarousel
key={`carousel-${carousel.uniqueId}-${index}`} key={`carousel-${carousel.uniqueId}`}
cardRows={[ cardRows={[
{ {
property: 'name', property: 'name',
@ -251,15 +173,16 @@ const HomeRoute = () => {
}, },
}, },
]} ]}
containerWidth={cq.width}
data={carousel.data} data={carousel.data}
isLoading={carousel.loading}
itemType={LibraryItem.ALBUM} itemType={LibraryItem.ALBUM}
loading={carousel.loading} route={{
pagination={carousel.pagination} route: AppRoute.LIBRARY_ALBUMS_DETAIL,
slugs: [{ idProperty: 'id', slugProperty: 'albumId' }],
}}
title={{ label: carousel.title }}
uniqueId={carousel.uniqueId} uniqueId={carousel.uniqueId}
> />
<GridCarousel.Title>{carousel.title}</GridCarousel.Title>
</GridCarousel>
))} ))}
</Stack> </Stack>
</Box> </Box>