Add play handlers and item count to list pages

This commit is contained in:
jeffvli 2023-01-07 03:28:03 -08:00
parent 6bb0474d62
commit 915b0eb372
6 changed files with 183 additions and 55 deletions

View file

@ -16,7 +16,6 @@ import { api } from '/@/renderer/api';
import { controller } from '/@/renderer/api/controller'; import { controller } from '/@/renderer/api/controller';
import { queryKeys } from '/@/renderer/api/query-keys'; import { queryKeys } from '/@/renderer/api/query-keys';
import { Album, AlbumListSort, LibraryItem } from '/@/renderer/api/types'; import { Album, AlbumListSort, LibraryItem } from '/@/renderer/api/types';
import { useAlbumList } from '/@/renderer/features/albums/queries/album-list-query';
import { useQueryClient } from '@tanstack/react-query'; import { useQueryClient } from '@tanstack/react-query';
import { import {
useCurrentServer, useCurrentServer,
@ -47,10 +46,11 @@ import { usePlayQueueAdd } from '/@/renderer/features/player';
interface AlbumListContentProps { interface AlbumListContentProps {
gridRef: MutableRefObject<VirtualInfiniteGridRef | null>; gridRef: MutableRefObject<VirtualInfiniteGridRef | null>;
itemCount?: number;
tableRef: MutableRefObject<AgGridReactType | null>; tableRef: MutableRefObject<AgGridReactType | null>;
} }
export const AlbumListContent = ({ gridRef, tableRef }: AlbumListContentProps) => { export const AlbumListContent = ({ itemCount, gridRef, tableRef }: AlbumListContentProps) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const navigate = useNavigate(); const navigate = useNavigate();
const server = useCurrentServer(); const server = useCurrentServer();
@ -66,12 +66,6 @@ export const AlbumListContent = ({ gridRef, tableRef }: AlbumListContentProps) =
const isPaginationEnabled = page.display === ListDisplayType.TABLE_PAGINATED; const isPaginationEnabled = page.display === ListDisplayType.TABLE_PAGINATED;
const checkAlbumList = useAlbumList({
limit: 1,
startIndex: 0,
...page.filter,
});
const columnDefs: ColDef[] = useMemo( const columnDefs: ColDef[] = useMemo(
() => getColumnDefs(page.table.columns), () => getColumnDefs(page.table.columns),
[page.table.columns], [page.table.columns],
@ -311,12 +305,12 @@ export const AlbumListContent = ({ gridRef, tableRef }: AlbumListContentProps) =
handlePlayQueueAdd={handlePlayQueueAdd} handlePlayQueueAdd={handlePlayQueueAdd}
height={height} height={height}
initialScrollOffset={page?.grid.scrollOffset || 0} initialScrollOffset={page?.grid.scrollOffset || 0}
itemCount={checkAlbumList?.data?.totalRecordCount || 0} itemCount={itemCount || 0}
itemData={itemData} itemData={itemData}
itemGap={20} itemGap={20}
itemSize={150 + page.grid?.size} itemSize={150 + page.grid?.size}
itemType={LibraryItem.ALBUM} itemType={LibraryItem.ALBUM}
loading={checkAlbumList.isLoading} loading={!itemCount}
minimumBatchSize={40} minimumBatchSize={40}
route={{ route={{
route: AppRoute.LIBRARY_ALBUMS_DETAIL, route: AppRoute.LIBRARY_ALBUMS_DETAIL,
@ -340,7 +334,7 @@ export const AlbumListContent = ({ gridRef, tableRef }: AlbumListContentProps) =
blockLoadDebounceMillis={200} blockLoadDebounceMillis={200}
columnDefs={columnDefs} columnDefs={columnDefs}
getRowId={(data) => data.data.id} getRowId={(data) => data.data.id}
infiniteInitialRowCount={checkAlbumList.data?.totalRecordCount || 1} infiniteInitialRowCount={itemCount || 1}
pagination={isPaginationEnabled} pagination={isPaginationEnabled}
paginationAutoPageSize={isPaginationEnabled} paginationAutoPageSize={isPaginationEnabled}
paginationPageSize={page.table.pagination.itemsPerPage || 100} paginationPageSize={page.table.pagination.itemsPerPage || 100}

View file

@ -17,9 +17,10 @@ import styled from 'styled-components';
import { api } from '/@/renderer/api'; import { api } from '/@/renderer/api';
import { controller } from '/@/renderer/api/controller'; import { controller } from '/@/renderer/api/controller';
import { queryKeys } from '/@/renderer/api/query-keys'; import { queryKeys } from '/@/renderer/api/query-keys';
import { AlbumListSort, ServerType, SortOrder } from '/@/renderer/api/types'; import { AlbumListSort, LibraryItem, ServerType, SortOrder } from '/@/renderer/api/types';
import { import {
ALBUM_TABLE_COLUMNS, ALBUM_TABLE_COLUMNS,
Badge,
Button, Button,
DropdownMenu, DropdownMenu,
MultiSelect, MultiSelect,
@ -27,6 +28,7 @@ import {
Popover, Popover,
SearchInput, SearchInput,
Slider, Slider,
SpinnerIcon,
Switch, Switch,
Text, Text,
TextTitle, TextTitle,
@ -45,7 +47,8 @@ import {
useSetAlbumTable, useSetAlbumTable,
useSetAlbumTablePagination, useSetAlbumTablePagination,
} from '/@/renderer/store'; } from '/@/renderer/store';
import { ListDisplayType, TableColumn } from '/@/renderer/types'; import { ListDisplayType, Play, TableColumn } from '/@/renderer/types';
import { usePlayQueueAdd } from '/@/renderer/features/player';
const FILTERS = { const FILTERS = {
jellyfin: [ jellyfin: [
@ -90,10 +93,11 @@ const HeaderItems = styled.div`
interface AlbumListHeaderProps { interface AlbumListHeaderProps {
gridRef: MutableRefObject<VirtualInfiniteGridRef | null>; gridRef: MutableRefObject<VirtualInfiniteGridRef | null>;
itemCount?: number;
tableRef: MutableRefObject<AgGridReactType | null>; tableRef: MutableRefObject<AgGridReactType | null>;
} }
export const AlbumListHeader = ({ gridRef, tableRef }: AlbumListHeaderProps) => { export const AlbumListHeader = ({ itemCount, gridRef, tableRef }: AlbumListHeaderProps) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const server = useCurrentServer(); const server = useCurrentServer();
const setPage = useSetAlbumStore(); const setPage = useSetAlbumStore();
@ -213,6 +217,11 @@ export const AlbumListHeader = ({ gridRef, tableRef }: AlbumListHeaderProps) =>
[page.display, tableRef, setPagination, server, queryClient, gridRef, fetch], [page.display, tableRef, setPagination, server, queryClient, gridRef, fetch],
); );
const handleRefresh = useCallback(() => {
queryClient.invalidateQueries(queryKeys.albums.list(server?.id || ''));
handleFilterChange(filters);
}, [filters, handleFilterChange, queryClient, server?.id]);
const handleSetSortBy = useCallback( const handleSetSortBy = useCallback(
(e: MouseEvent<HTMLButtonElement>) => { (e: MouseEvent<HTMLButtonElement>) => {
if (!e.currentTarget?.value || !server?.type) return; if (!e.currentTarget?.value || !server?.type) return;
@ -301,6 +310,31 @@ export const AlbumListHeader = ({ gridRef, tableRef }: AlbumListHeaderProps) =>
} }
}; };
const handlePlayQueueAdd = usePlayQueueAdd();
const handlePlay = async (play: Play) => {
if (!itemCount || itemCount === 0) return;
const query = { startIndex: 0, ...filters };
const queryKey = queryKeys.albums.list(server?.id || '', query);
const albumListRes = await queryClient.fetchQuery({
queryFn: ({ signal }) => api.controller.getAlbumList({ query, server, signal }),
queryKey,
});
const albumIds =
api.normalize.albumList(albumListRes, server).items?.map((item) => item.id) || [];
handlePlayQueueAdd?.({
byItemType: {
id: albumIds,
type: LibraryItem.ALBUM,
},
play,
});
};
return ( return (
<PageHeader p="1rem"> <PageHeader p="1rem">
<HeaderItems ref={cq.ref}> <HeaderItems ref={cq.ref}>
@ -311,20 +345,30 @@ export const AlbumListHeader = ({ gridRef, tableRef }: AlbumListHeaderProps) =>
> >
<DropdownMenu position="bottom-start"> <DropdownMenu position="bottom-start">
<DropdownMenu.Target> <DropdownMenu.Target>
<Button <Group>
compact <Button
px={0} compact
rightIcon={<RiArrowDownSLine size={15} />} px={0}
size="xl" rightIcon={<RiArrowDownSLine size={15} />}
variant="subtle" size="xl"
> variant="subtle"
<TextTitle
fw="bold"
order={3}
> >
Albums <Group>
</TextTitle> <TextTitle
</Button> fw="bold"
order={3}
>
Albums
</TextTitle>
<Badge
radius="xl"
size="lg"
>
{itemCount === null || itemCount === undefined ? <SpinnerIcon /> : itemCount}
</Badge>
</Group>
</Button>
</Group>
</DropdownMenu.Target> </DropdownMenu.Target>
<DropdownMenu.Dropdown> <DropdownMenu.Dropdown>
<DropdownMenu.Label>Display type</DropdownMenu.Label> <DropdownMenu.Label>Display type</DropdownMenu.Label>
@ -495,10 +539,15 @@ export const AlbumListHeader = ({ gridRef, tableRef }: AlbumListHeaderProps) =>
</Button> </Button>
</DropdownMenu.Target> </DropdownMenu.Target>
<DropdownMenu.Dropdown> <DropdownMenu.Dropdown>
<DropdownMenu.Item disabled>Play</DropdownMenu.Item> <DropdownMenu.Item onClick={() => handlePlay(Play.NOW)}>Play</DropdownMenu.Item>
<DropdownMenu.Item disabled>Add to queue (next)</DropdownMenu.Item> <DropdownMenu.Item onClick={() => handlePlay(Play.LAST)}>
<DropdownMenu.Item disabled>Add to queue (last)</DropdownMenu.Item> Add to queue (last)
<DropdownMenu.Item disabled>Add to playlist</DropdownMenu.Item> </DropdownMenu.Item>
<DropdownMenu.Item onClick={() => handlePlay(Play.NEXT)}>
Add to queue (next)
</DropdownMenu.Item>
<DropdownMenu.Divider />
<DropdownMenu.Item onClick={handleRefresh}>Refresh</DropdownMenu.Item>
</DropdownMenu.Dropdown> </DropdownMenu.Dropdown>
</DropdownMenu> </DropdownMenu>
</Flex> </Flex>

View file

@ -4,20 +4,42 @@ import { AlbumListHeader } from '/@/renderer/features/albums/components/album-li
import { AlbumListContent } from '/@/renderer/features/albums/components/album-list-content'; import { AlbumListContent } from '/@/renderer/features/albums/components/album-list-content';
import { useRef } from 'react'; import { useRef } from 'react';
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 { useAlbumList } from '/@/renderer/features/albums/queries/album-list-query';
import { useAlbumListFilters } from '/@/renderer/store';
const AlbumListRoute = () => { const AlbumListRoute = () => {
const gridRef = useRef<VirtualInfiniteGridRef | null>(null); const gridRef = useRef<VirtualInfiniteGridRef | null>(null);
const tableRef = useRef<AgGridReactType | null>(null); const tableRef = useRef<AgGridReactType | null>(null);
const filters = useAlbumListFilters();
const itemCountCheck = useAlbumList(
{
limit: 1,
startIndex: 0,
...filters,
},
{
cacheTime: 1000 * 60 * 60 * 2,
staleTime: 1000 * 60 * 60 * 2,
},
);
const itemCount =
itemCountCheck.data?.totalRecordCount === null
? undefined
: itemCountCheck.data?.totalRecordCount;
return ( return (
<AnimatedPage> <AnimatedPage>
<VirtualGridContainer> <VirtualGridContainer>
<AlbumListHeader <AlbumListHeader
gridRef={gridRef} gridRef={gridRef}
itemCount={itemCount}
tableRef={tableRef} tableRef={tableRef}
/> />
<AlbumListContent <AlbumListContent
gridRef={gridRef} gridRef={gridRef}
itemCount={itemCount}
tableRef={tableRef} tableRef={tableRef}
/> />
</VirtualGridContainer> </VirtualGridContainer>

View file

@ -19,7 +19,6 @@ import {
VirtualGridAutoSizerContainer, VirtualGridAutoSizerContainer,
VirtualTable, VirtualTable,
} from '/@/renderer/components'; } from '/@/renderer/components';
import { useSongList } from '/@/renderer/features/songs/queries/song-list-query';
import { import {
useCurrentServer, useCurrentServer,
useSetSongTable, useSetSongTable,
@ -38,10 +37,11 @@ import { LibraryItem, QueueSong } from '/@/renderer/api/types';
import { usePlayQueueAdd } from '/@/renderer/features/player'; import { usePlayQueueAdd } from '/@/renderer/features/player';
interface SongListContentProps { interface SongListContentProps {
itemCount?: number;
tableRef: MutableRefObject<AgGridReactType | null>; tableRef: MutableRefObject<AgGridReactType | null>;
} }
export const SongListContent = ({ tableRef }: SongListContentProps) => { export const SongListContent = ({ itemCount, tableRef }: SongListContentProps) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const server = useCurrentServer(); const server = useCurrentServer();
const page = useSongListStore(); const page = useSongListStore();
@ -54,12 +54,6 @@ export const SongListContent = ({ tableRef }: SongListContentProps) => {
const isPaginationEnabled = page.display === ListDisplayType.TABLE_PAGINATED; const isPaginationEnabled = page.display === ListDisplayType.TABLE_PAGINATED;
const checkSongList = useSongList({
limit: 1,
startIndex: 0,
...page.filter,
});
const columnDefs: ColDef[] = useMemo( const columnDefs: ColDef[] = useMemo(
() => getColumnDefs(page.table.columns), () => getColumnDefs(page.table.columns),
[page.table.columns], [page.table.columns],
@ -224,7 +218,7 @@ export const SongListContent = ({ tableRef }: SongListContentProps) => {
defaultColDef={defaultColumnDefs} defaultColDef={defaultColumnDefs}
enableCellChangeFlash={false} enableCellChangeFlash={false}
getRowId={(data) => data.data.id} getRowId={(data) => data.data.id}
infiniteInitialRowCount={checkSongList.data?.totalRecordCount || 100} infiniteInitialRowCount={itemCount || 100}
pagination={isPaginationEnabled} pagination={isPaginationEnabled}
paginationAutoPageSize={isPaginationEnabled} paginationAutoPageSize={isPaginationEnabled}
paginationPageSize={page.table.pagination.itemsPerPage || 100} paginationPageSize={page.table.pagination.itemsPerPage || 100}

View file

@ -13,7 +13,13 @@ import {
} from 'react-icons/ri'; } from 'react-icons/ri';
import { api } from '/@/renderer/api'; import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys'; import { queryKeys } from '/@/renderer/api/query-keys';
import { ServerType, SongListSort, SortOrder } from '/@/renderer/api/types'; import {
LibraryItem,
ServerType,
SongListQuery,
SongListSort,
SortOrder,
} from '/@/renderer/api/types';
import { import {
Button, Button,
DropdownMenu, DropdownMenu,
@ -25,7 +31,10 @@ import {
MultiSelect, MultiSelect,
Text, Text,
SONG_TABLE_COLUMNS, SONG_TABLE_COLUMNS,
Badge,
SpinnerIcon,
} from '/@/renderer/components'; } from '/@/renderer/components';
import { usePlayQueueAdd } from '/@/renderer/features/player';
import { useMusicFolders } from '/@/renderer/features/shared'; import { useMusicFolders } from '/@/renderer/features/shared';
import { JellyfinSongFilters } from '/@/renderer/features/songs/components/jellyfin-song-filters'; import { JellyfinSongFilters } from '/@/renderer/features/songs/components/jellyfin-song-filters';
import { NavidromeSongFilters } from '/@/renderer/features/songs/components/navidrome-song-filters'; import { NavidromeSongFilters } from '/@/renderer/features/songs/components/navidrome-song-filters';
@ -40,7 +49,7 @@ import {
useSetSongTablePagination, useSetSongTablePagination,
useSongListStore, useSongListStore,
} from '/@/renderer/store'; } from '/@/renderer/store';
import { ListDisplayType, TableColumn } from '/@/renderer/types'; import { ListDisplayType, Play, TableColumn } from '/@/renderer/types';
const FILTERS = { const FILTERS = {
jellyfin: [ jellyfin: [
@ -80,16 +89,18 @@ const ORDER = [
]; ];
interface SongListHeaderProps { interface SongListHeaderProps {
itemCount?: number;
tableRef: MutableRefObject<AgGridReactType | null>; tableRef: MutableRefObject<AgGridReactType | null>;
} }
export const SongListHeader = ({ tableRef }: SongListHeaderProps) => { export const SongListHeader = ({ itemCount, tableRef }: SongListHeaderProps) => {
const server = useCurrentServer(); const server = useCurrentServer();
const page = useSongListStore(); const page = useSongListStore();
const setPage = useSetSongStore(); const setPage = useSetSongStore();
const setFilter = useSetSongFilters(); const setFilter = useSetSongFilters();
const setTable = useSetSongTable(); const setTable = useSetSongTable();
const setPagination = useSetSongTablePagination(); const setPagination = useSetSongTablePagination();
const handlePlayQueueAdd = usePlayQueueAdd();
const cq = useContainerQuery(); const cq = useContainerQuery();
const musicFoldersQuery = useMusicFolders(); const musicFoldersQuery = useMusicFolders();
@ -244,6 +255,24 @@ export const SongListHeader = ({ tableRef }: SongListHeaderProps) => {
setTable({ rowHeight: e }); setTable({ rowHeight: e });
}; };
const handleRefresh = () => {
queryClient.invalidateQueries(queryKeys.songs.list(server?.id || ''));
handleFilterChange(page.filter);
};
const handlePlay = async (play: Play) => {
if (!itemCount || itemCount === 0) return;
const query: SongListQuery = { startIndex: 0, ...page.filter };
handlePlayQueueAdd?.({
byItemType: {
id: query,
type: LibraryItem.SONG,
},
play,
});
};
return ( return (
<PageHeader p="1rem"> <PageHeader p="1rem">
<Flex <Flex
@ -265,12 +294,20 @@ export const SongListHeader = ({ tableRef }: SongListHeaderProps) => {
sx={{ paddingLeft: 0, paddingRight: 0 }} sx={{ paddingLeft: 0, paddingRight: 0 }}
variant="subtle" variant="subtle"
> >
<TextTitle <Group>
fw="bold" <TextTitle
order={3} fw="bold"
> order={3}
Tracks >
</TextTitle> Tracks
</TextTitle>
<Badge
radius="xl"
size="lg"
>
{itemCount === null || itemCount === undefined ? <SpinnerIcon /> : itemCount}
</Badge>
</Group>
</Button> </Button>
</DropdownMenu.Target> </DropdownMenu.Target>
<DropdownMenu.Dropdown> <DropdownMenu.Dropdown>
@ -420,10 +457,15 @@ export const SongListHeader = ({ tableRef }: SongListHeaderProps) => {
</Button> </Button>
</DropdownMenu.Target> </DropdownMenu.Target>
<DropdownMenu.Dropdown> <DropdownMenu.Dropdown>
<DropdownMenu.Item disabled>Play</DropdownMenu.Item> <DropdownMenu.Item onClick={() => handlePlay(Play.NOW)}>Play</DropdownMenu.Item>
<DropdownMenu.Item disabled>Add to queue (last)</DropdownMenu.Item> <DropdownMenu.Item onClick={() => handlePlay(Play.LAST)}>
<DropdownMenu.Item disabled>Add to queue (next)</DropdownMenu.Item> Add to queue (last)
<DropdownMenu.Item disabled>Add to playlist</DropdownMenu.Item> </DropdownMenu.Item>
<DropdownMenu.Item onClick={() => handlePlay(Play.NEXT)}>
Add to queue (next)
</DropdownMenu.Item>
<DropdownMenu.Divider />
<DropdownMenu.Item onClick={handleRefresh}>Refresh</DropdownMenu.Item>
</DropdownMenu.Dropdown> </DropdownMenu.Dropdown>
</DropdownMenu> </DropdownMenu>
</Flex> </Flex>

View file

@ -4,15 +4,42 @@ import { VirtualGridContainer } from '/@/renderer/components';
import { AnimatedPage } from '/@/renderer/features/shared'; import { AnimatedPage } from '/@/renderer/features/shared';
import { SongListContent } from '/@/renderer/features/songs/components/song-list-content'; import { SongListContent } from '/@/renderer/features/songs/components/song-list-content';
import { SongListHeader } from '/@/renderer/features/songs/components/song-list-header'; import { SongListHeader } from '/@/renderer/features/songs/components/song-list-header';
import { useSongList } from '/@/renderer/features/songs/queries/song-list-query';
import { useSongListFilters } from '/@/renderer/store';
const TrackListRoute = () => { const TrackListRoute = () => {
const tableRef = useRef<AgGridReactType | null>(null); const tableRef = useRef<AgGridReactType | null>(null);
const filters = useSongListFilters();
const itemCountCheck = useSongList(
{
limit: 1,
startIndex: 0,
...filters,
},
{
cacheTime: 1000 * 60 * 60 * 2,
staleTime: 1000 * 60 * 60 * 2,
},
);
const itemCount =
itemCountCheck.data?.totalRecordCount === null
? undefined
: itemCountCheck.data?.totalRecordCount;
return ( return (
<AnimatedPage> <AnimatedPage>
<VirtualGridContainer> <VirtualGridContainer>
<SongListHeader tableRef={tableRef} /> <SongListHeader
<SongListContent tableRef={tableRef} /> itemCount={itemCount}
tableRef={tableRef}
/>
<SongListContent
itemCount={itemCount}
tableRef={tableRef}
/>
</VirtualGridContainer> </VirtualGridContainer>
</AnimatedPage> </AnimatedPage>
); );