Add artist top songs list

This commit is contained in:
jeffvli 2023-01-13 13:51:19 -08:00
parent 8afd626806
commit 92cde507d9
4 changed files with 225 additions and 2 deletions

View file

@ -0,0 +1,73 @@
import { MutableRefObject, useMemo } from 'react';
import type { ColDef, RowDoubleClickedEvent } from '@ag-grid-community/core';
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
import { getColumnDefs, VirtualGridAutoSizerContainer, VirtualTable } from '/@/renderer/components';
import { useCurrentServer, useSongListStore } from '/@/renderer/store';
import { useHandleTableContextMenu } from '/@/renderer/features/context-menu';
import { SONG_CONTEXT_MENU_ITEMS } from '/@/renderer/features/context-menu/context-menu-items';
import { usePlayButtonBehavior } from '/@/renderer/store/settings.store';
import { LibraryItem, QueueSong } from '/@/renderer/api/types';
import { usePlayQueueAdd } from '/@/renderer/features/player';
interface AlbumArtistSongListContentProps {
data: QueueSong[];
tableRef: MutableRefObject<AgGridReactType | null>;
}
export const AlbumArtistDetailTopSongsListContent = ({
tableRef,
data,
}: AlbumArtistSongListContentProps) => {
const server = useCurrentServer();
const page = useSongListStore();
const handlePlayQueueAdd = usePlayQueueAdd();
const playButtonBehavior = usePlayButtonBehavior();
const columnDefs: ColDef[] = useMemo(
() => getColumnDefs(page.table.columns),
[page.table.columns],
);
const handleContextMenu = useHandleTableContextMenu(LibraryItem.SONG, SONG_CONTEXT_MENU_ITEMS);
const handleRowDoubleClick = (e: RowDoubleClickedEvent<QueueSong>) => {
if (!e.data) return;
handlePlayQueueAdd?.({
byData: [e.data],
play: playButtonBehavior,
});
};
return (
<>
<VirtualGridAutoSizerContainer>
<VirtualTable
// https://github.com/ag-grid/ag-grid/issues/5284
// Key is used to force remount of table when display, rowHeight, or server changes
key={`table-${page.display}-${page.table.rowHeight}-${server?.id}`}
ref={tableRef}
alwaysShowHorizontalScroll
animateRows
maintainColumnOrder
suppressCopyRowsToClipboard
suppressMoveWhenRowDragging
suppressPaginationPanel
suppressRowDrag
suppressScrollOnNewData
autoFitColumns={page.table.autoFit}
columnDefs={columnDefs}
enableCellChangeFlash={false}
getRowId={(data) => data.data.uniqueId}
rowBuffer={20}
rowData={data}
rowHeight={page.table.rowHeight || 40}
rowModelType="clientSide"
rowSelection="multiple"
onCellContextMenu={handleContextMenu}
onRowDoubleClicked={handleRowDoubleClick}
/>
</VirtualGridAutoSizerContainer>
</>
);
};

View file

@ -0,0 +1,96 @@
import { Flex, Group } from '@mantine/core';
import { RiMoreFill } from 'react-icons/ri';
import { QueueSong } from '/@/renderer/api/types';
import {
Button,
DropdownMenu,
PageHeader,
TextTitle,
Badge,
SpinnerIcon,
} from '/@/renderer/components';
import { usePlayQueueAdd } from '/@/renderer/features/player';
import { useContainerQuery } from '/@/renderer/hooks';
import { Play } from '/@/renderer/types';
interface AlbumArtistDetailTopSongsListHeaderProps {
data: QueueSong[];
itemCount?: number;
title: string;
}
export const AlbumArtistDetailTopSongsListHeader = ({
title,
itemCount,
data,
}: AlbumArtistDetailTopSongsListHeaderProps) => {
const handlePlayQueueAdd = usePlayQueueAdd();
const cq = useContainerQuery();
const handlePlay = async (play: Play) => {
handlePlayQueueAdd?.({
byData: data,
play,
});
};
return (
<PageHeader p="1rem">
<Flex
ref={cq.ref}
direction="row"
justify="space-between"
>
<Flex
align="center"
gap="md"
justify="center"
>
<Button
compact
size="xl"
sx={{ paddingLeft: 0, paddingRight: 0 }}
variant="subtle"
>
<Group noWrap>
<TextTitle
fw="bold"
maw="20vw"
order={3}
overflow="hidden"
>
{title}
</TextTitle>
<Badge
radius="xl"
size="lg"
>
{itemCount === null || itemCount === undefined ? <SpinnerIcon /> : itemCount}
</Badge>
</Group>
</Button>
<DropdownMenu position="bottom-start">
<DropdownMenu.Target>
<Button
compact
fw="600"
variant="subtle"
>
<RiMoreFill size={15} />
</Button>
</DropdownMenu.Target>
<DropdownMenu.Dropdown>
<DropdownMenu.Item onClick={() => handlePlay(Play.NOW)}>Play</DropdownMenu.Item>
<DropdownMenu.Item onClick={() => handlePlay(Play.LAST)}>
Add to queue
</DropdownMenu.Item>
<DropdownMenu.Item onClick={() => handlePlay(Play.NEXT)}>
Add to queue next
</DropdownMenu.Item>
</DropdownMenu.Dropdown>
</DropdownMenu>
</Flex>
</Flex>
</PageHeader>
);
};

View file

@ -0,0 +1,46 @@
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
import { useRef } from 'react';
import { useParams } from 'react-router';
import { VirtualGridContainer } from '/@/renderer/components';
import { AlbumArtistDetailTopSongsListContent } from '/@/renderer/features/artists/components/album-artist-detail-top-songs-list-content';
import { AlbumArtistDetailTopSongsListHeader } from '/@/renderer/features/artists/components/album-artist-detail-top-songs-list-header';
import { useAlbumArtistDetail } from '/@/renderer/features/artists/queries/album-artist-detail-query';
import { useTopSongsList } from '/@/renderer/features/artists/queries/top-songs-list-query';
import { AnimatedPage } from '/@/renderer/features/shared';
import { useCurrentServer } from '/@/renderer/store';
import { ServerType } from '/@/renderer/types';
const AlbumArtistDetailTopSongsListRoute = () => {
const tableRef = useRef<AgGridReactType | null>(null);
const { albumArtistId } = useParams() as { albumArtistId: string };
const server = useCurrentServer();
const detailQuery = useAlbumArtistDetail({ id: albumArtistId });
const topSongsQuery = useTopSongsList(
{ artist: detailQuery?.data?.name || '' },
{ enabled: server?.type !== ServerType.JELLYFIN && !!detailQuery?.data?.name },
);
const itemCount = topSongsQuery?.data?.items?.length || 0;
if (detailQuery.isLoading || topSongsQuery?.isLoading) return null;
return (
<AnimatedPage>
<VirtualGridContainer>
<AlbumArtistDetailTopSongsListHeader
data={topSongsQuery?.data?.items || []}
itemCount={itemCount}
title={detailQuery?.data?.name || 'Unknown'}
/>
<AlbumArtistDetailTopSongsListContent
data={topSongsQuery?.data?.items || []}
tableRef={tableRef}
/>
</VirtualGridContainer>
</AnimatedPage>
);
};
export default AlbumArtistDetailTopSongsListRoute;

View file

@ -3,7 +3,7 @@ import {
Route,
createRoutesFromElements,
RouterProvider,
createHashRouter,
createBrowserRouter,
} from 'react-router-dom';
import { AppRoute } from './routes';
import { DefaultLayout } from '/@/renderer/layouts';
@ -52,6 +52,10 @@ const AlbumArtistDetailSongListRoute = lazy(
() => import('../features/artists/routes/album-artist-detail-song-list-route'),
);
const AlbumArtistDetailTopSongsListRoute = lazy(
() => import('../features/artists/routes/album-artist-detail-top-songs-list-route'),
);
const AlbumArtistDetailDiscographyRoute = lazy(
() => import('../features/artists/routes/album-artist-detail-discography-route'),
);
@ -65,7 +69,7 @@ const RouteErrorBoundary = lazy(
);
export const AppRouter = () => {
const router = createHashRouter(
const router = createBrowserRouter(
createRoutesFromElements(
<>
<Route element={<TitlebarOutlet />}>
@ -140,6 +144,10 @@ export const AppRouter = () => {
element={<AlbumArtistDetailSongListRoute />}
path={AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL_SONGS}
/>
<Route
element={<AlbumArtistDetailTopSongsListRoute />}
path={AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL_TOP_SONGS}
/>
</Route>
</Route>
<Route