Handle queue all songs by double click (#67)
This commit is contained in:
parent
93530008a9
commit
51c2731b07
11 changed files with 129 additions and 55 deletions
|
@ -1,5 +1,5 @@
|
|||
import { MutableRefObject, useCallback, useMemo } from 'react';
|
||||
import { Button, Text } from '/@/renderer/components';
|
||||
import { Button } from '/@/renderer/components';
|
||||
import { ColDef, RowDoubleClickedEvent, RowHeightParams, RowNode } from '@ag-grid-community/core';
|
||||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||
import { Box, Group, Stack } from '@mantine/core';
|
||||
|
@ -213,16 +213,17 @@ export const AlbumDetailContent = ({ tableRef }: AlbumDetailContentProps) => {
|
|||
const handleContextMenu = useHandleTableContextMenu(LibraryItem.SONG, SONG_CONTEXT_MENU_ITEMS);
|
||||
|
||||
const handleRowDoubleClick = (e: RowDoubleClickedEvent<QueueSong>) => {
|
||||
if (!e.data) return;
|
||||
if (!e.data || e.node.isFullWidthCell()) return;
|
||||
|
||||
const rowData: QueueSong[] = [];
|
||||
e.api.forEachNode((node) => {
|
||||
if (!node.data) return;
|
||||
if (!node.data || node.isFullWidthCell()) return;
|
||||
rowData.push(node.data);
|
||||
});
|
||||
|
||||
handlePlayQueueAdd?.({
|
||||
byData: rowData,
|
||||
initialSongId: e.data.id,
|
||||
playType: playButtonBehavior,
|
||||
});
|
||||
};
|
||||
|
|
|
@ -256,9 +256,11 @@ export const AlbumArtistDetailContent = () => {
|
|||
const handleContextMenu = useHandleTableContextMenu(LibraryItem.SONG, SONG_CONTEXT_MENU_ITEMS);
|
||||
|
||||
const handleRowDoubleClick = (e: RowDoubleClickedEvent<QueueSong>) => {
|
||||
if (!e.data) return;
|
||||
if (!e.data || !topSongsQuery?.data) return;
|
||||
|
||||
handlePlayQueueAdd?.({
|
||||
byData: [e.data],
|
||||
byData: topSongsQuery?.data?.items || [],
|
||||
initialSongId: e.data.id,
|
||||
playType: playButtonBehavior,
|
||||
});
|
||||
};
|
||||
|
|
|
@ -34,8 +34,16 @@ export const AlbumArtistDetailTopSongsListContent = ({
|
|||
|
||||
const handleRowDoubleClick = (e: RowDoubleClickedEvent<QueueSong>) => {
|
||||
if (!e.data) return;
|
||||
|
||||
const rowData: QueueSong[] = [];
|
||||
e.api.forEachNode((node) => {
|
||||
if (!node.data) return;
|
||||
rowData.push(node.data);
|
||||
});
|
||||
|
||||
handlePlayQueueAdd?.({
|
||||
byData: [e.data],
|
||||
byData: rowData,
|
||||
initialSongId: e.data.id,
|
||||
playType: playButtonBehavior,
|
||||
});
|
||||
};
|
||||
|
|
|
@ -12,6 +12,7 @@ import {
|
|||
getSongById,
|
||||
getAlbumSongsById,
|
||||
getAlbumArtistSongsById,
|
||||
getSongsByQuery,
|
||||
} from '/@/renderer/features/player/utils';
|
||||
|
||||
const mpvPlayer = isElectron() ? window.electron.mpvPlayer : null;
|
||||
|
@ -44,6 +45,8 @@ export const useHandlePlayQueueAdd = () => {
|
|||
songList = await getAlbumSongsById({ id, query, queryClient, server });
|
||||
} else if (itemType === LibraryItem.ALBUM_ARTIST) {
|
||||
songList = await getAlbumArtistSongsById({ id, query, queryClient, server });
|
||||
} else if (itemType === LibraryItem.SONG) {
|
||||
songList = await getSongsByQuery({ query, queryClient, server });
|
||||
} else {
|
||||
songList = await getSongById({ id: id?.[0], queryClient, server });
|
||||
}
|
||||
|
@ -61,8 +64,6 @@ export const useHandlePlayQueueAdd = () => {
|
|||
|
||||
if (!songs) return toast.warn({ message: 'No songs found' });
|
||||
|
||||
// const index = initialIndex || initial songs.findIndex((song) => song.id === initialSongId);
|
||||
|
||||
if (initialIndex) {
|
||||
initialSongIndex = initialIndex;
|
||||
} else if (initialSongId) {
|
||||
|
|
|
@ -124,6 +124,41 @@ export const getAlbumArtistSongsById = async (args: {
|
|||
return res;
|
||||
};
|
||||
|
||||
export const getSongsByQuery = async (args: {
|
||||
query?: Partial<SongListQuery>;
|
||||
queryClient: QueryClient;
|
||||
server: ServerListItem;
|
||||
}) => {
|
||||
const { queryClient, server, query } = args;
|
||||
|
||||
const queryFilter: SongListQuery = {
|
||||
sortBy: SongListSort.ALBUM,
|
||||
sortOrder: SortOrder.ASC,
|
||||
startIndex: 0,
|
||||
...query,
|
||||
};
|
||||
|
||||
const queryKey = queryKeys.songs.list(server?.id, queryFilter);
|
||||
|
||||
const res = await queryClient.fetchQuery(
|
||||
queryKey,
|
||||
async ({ signal }) =>
|
||||
api.controller.getSongList({
|
||||
apiClientProps: {
|
||||
server,
|
||||
signal,
|
||||
},
|
||||
query: queryFilter,
|
||||
}),
|
||||
{
|
||||
cacheTime: 1000 * 60,
|
||||
staleTime: 1000 * 60,
|
||||
},
|
||||
);
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
export const getSongById = async (args: {
|
||||
id: string;
|
||||
queryClient: QueryClient;
|
||||
|
|
|
@ -201,8 +201,13 @@ export const PlaylistDetailContent = ({ tableRef }: PlaylistDetailContentProps)
|
|||
|
||||
const handleRowDoubleClick = (e: RowDoubleClickedEvent<QueueSong>) => {
|
||||
if (!e.data) return;
|
||||
|
||||
handlePlayQueueAdd?.({
|
||||
byData: [e.data],
|
||||
byItemType: {
|
||||
id: [playlistId],
|
||||
type: LibraryItem.PLAYLIST,
|
||||
},
|
||||
initialSongId: e.data.id,
|
||||
playType: playButtonBehavior,
|
||||
});
|
||||
};
|
||||
|
|
|
@ -25,7 +25,6 @@ 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, SongListQuery } from '/@/renderer/api/types';
|
||||
import { usePlayQueueAdd } from '/@/renderer/features/player';
|
||||
import { useSongListContext } from '/@/renderer/features/songs/context/song-list-context';
|
||||
import { VirtualGridAutoSizerContainer } from '/@/renderer/components/virtual-grid';
|
||||
import { getColumnDefs, VirtualTable, TablePagination } from '/@/renderer/components/virtual-table';
|
||||
|
@ -39,12 +38,11 @@ export const SongListContent = ({ itemCount, tableRef }: SongListContentProps) =
|
|||
const queryClient = useQueryClient();
|
||||
const server = useCurrentServer();
|
||||
|
||||
const { id, pageKey } = useSongListContext();
|
||||
const { id, pageKey, handlePlay } = useSongListContext();
|
||||
const filter = useSongListFilter({ id, key: pageKey });
|
||||
const { display, table } = useSongListStore({ id, key: pageKey });
|
||||
|
||||
const { setTable, setTablePagination } = useListStoreActions();
|
||||
const handlePlayQueueAdd = usePlayQueueAdd();
|
||||
const playButtonBehavior = usePlayButtonBehavior();
|
||||
|
||||
const isPaginationEnabled = display === ListDisplayType.TABLE_PAGINATED;
|
||||
|
@ -160,10 +158,7 @@ export const SongListContent = ({ itemCount, tableRef }: SongListContentProps) =
|
|||
|
||||
const handleRowDoubleClick = (e: RowDoubleClickedEvent<QueueSong>) => {
|
||||
if (!e.data) return;
|
||||
handlePlayQueueAdd?.({
|
||||
byData: [e.data],
|
||||
playType: playButtonBehavior,
|
||||
});
|
||||
handlePlay?.({ initialSongId: e.data.id, playType: playButtonBehavior });
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { useCallback, useMemo, ChangeEvent, MutableRefObject, MouseEvent } from 'react';
|
||||
import { IDatasource } from '@ag-grid-community/core';
|
||||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||
import { Flex, Group, Stack } from '@mantine/core';
|
||||
import { openModal } from '@mantine/modals';
|
||||
import {
|
||||
|
@ -24,7 +25,6 @@ import { JellyfinSongFilters } from '/@/renderer/features/songs/components/jelly
|
|||
import { NavidromeSongFilters } from '/@/renderer/features/songs/components/navidrome-song-filters';
|
||||
import { useContainerQuery } from '/@/renderer/hooks';
|
||||
import { queryClient } from '/@/renderer/lib/react-query';
|
||||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||
import {
|
||||
SongListFilter,
|
||||
useCurrentServer,
|
||||
|
@ -265,13 +265,25 @@ export const SongListHeaderFilters = ({
|
|||
if (!itemCount || itemCount === 0) return;
|
||||
const query: SongListQuery = { startIndex: 0, ...filter, ...customFilters };
|
||||
|
||||
if (id) {
|
||||
handlePlayQueueAdd?.({
|
||||
byItemType: {
|
||||
id: query,
|
||||
id: [id],
|
||||
type: LibraryItem.ALBUM_ARTIST,
|
||||
},
|
||||
playType,
|
||||
query,
|
||||
});
|
||||
} else {
|
||||
handlePlayQueueAdd?.({
|
||||
byItemType: {
|
||||
id: [],
|
||||
type: LibraryItem.SONG,
|
||||
},
|
||||
playType,
|
||||
query,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleOpenFiltersModal = () => {
|
||||
|
|
|
@ -5,9 +5,8 @@ import debounce from 'lodash/debounce';
|
|||
import { ChangeEvent, MutableRefObject, useCallback } from 'react';
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { LibraryItem, SongListQuery } from '/@/renderer/api/types';
|
||||
import { SongListQuery } from '/@/renderer/api/types';
|
||||
import { PageHeader, SearchInput } from '/@/renderer/components';
|
||||
import { usePlayQueueAdd } from '/@/renderer/features/player';
|
||||
import { FilterBar, LibraryHeaderBar } from '/@/renderer/features/shared';
|
||||
import { SongListHeaderFilters } from '/@/renderer/features/songs/components/song-list-header-filters';
|
||||
import { useSongListContext } from '/@/renderer/features/songs/context/song-list-context';
|
||||
|
@ -20,26 +19,18 @@ import {
|
|||
useSongListFilter,
|
||||
} from '/@/renderer/store';
|
||||
import { usePlayButtonBehavior } from '/@/renderer/store/settings.store';
|
||||
import { Play } from '/@/renderer/types';
|
||||
|
||||
interface SongListHeaderProps {
|
||||
customFilters?: Partial<SongListFilter>;
|
||||
itemCount?: number;
|
||||
tableRef: MutableRefObject<AgGridReactType | null>;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export const SongListHeader = ({
|
||||
customFilters,
|
||||
title,
|
||||
itemCount,
|
||||
tableRef,
|
||||
}: SongListHeaderProps) => {
|
||||
export const SongListHeader = ({ title, itemCount, tableRef }: SongListHeaderProps) => {
|
||||
const server = useCurrentServer();
|
||||
const { id, pageKey } = useSongListContext();
|
||||
const { id, pageKey, handlePlay } = useSongListContext();
|
||||
const filter = useSongListFilter({ id, key: pageKey });
|
||||
const { setFilter, setTablePagination } = useListStoreActions();
|
||||
const handlePlayQueueAdd = usePlayQueueAdd();
|
||||
const cq = useContainerQuery();
|
||||
|
||||
const handleFilterChange = useCallback(
|
||||
|
@ -55,7 +46,6 @@ export const SongListHeader = ({
|
|||
limit,
|
||||
startIndex,
|
||||
...pageFilters,
|
||||
...customFilters,
|
||||
};
|
||||
|
||||
const queryKey = queryKeys.songs.list(server?.id || '', query);
|
||||
|
@ -82,7 +72,7 @@ export const SongListHeader = ({
|
|||
tableRef.current?.api.ensureIndexVisible(0, 'top');
|
||||
setTablePagination({ data: { currentPage: 0 }, key: pageKey });
|
||||
},
|
||||
[customFilters, filter, pageKey, server, setTablePagination, tableRef],
|
||||
[filter, pageKey, server, setTablePagination, tableRef],
|
||||
);
|
||||
|
||||
const handleSearch = debounce((e: ChangeEvent<HTMLInputElement>) => {
|
||||
|
@ -94,19 +84,6 @@ export const SongListHeader = ({
|
|||
|
||||
const playButtonBehavior = usePlayButtonBehavior();
|
||||
|
||||
const handlePlay = async (playType: Play) => {
|
||||
if (!itemCount || itemCount === 0) return;
|
||||
const query: SongListQuery = { startIndex: 0, ...filter, ...customFilters };
|
||||
|
||||
handlePlayQueueAdd?.({
|
||||
byItemType: {
|
||||
id: query,
|
||||
type: LibraryItem.SONG,
|
||||
},
|
||||
playType,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack
|
||||
ref={cq.ref}
|
||||
|
@ -118,7 +95,9 @@ export const SongListHeader = ({
|
|||
w="100%"
|
||||
>
|
||||
<LibraryHeaderBar>
|
||||
<LibraryHeaderBar.PlayButton onClick={() => handlePlay(playButtonBehavior)} />
|
||||
<LibraryHeaderBar.PlayButton
|
||||
onClick={() => handlePlay?.({ playType: playButtonBehavior })}
|
||||
/>
|
||||
<LibraryHeaderBar.Title>{title || 'Tracks'}</LibraryHeaderBar.Title>
|
||||
<LibraryHeaderBar.Badge isLoading={itemCount === null || itemCount === undefined}>
|
||||
{itemCount}
|
||||
|
@ -135,7 +114,6 @@ export const SongListHeader = ({
|
|||
</PageHeader>
|
||||
<FilterBar>
|
||||
<SongListHeaderFilters
|
||||
customFilters={customFilters}
|
||||
itemCount={itemCount}
|
||||
tableRef={tableRef}
|
||||
/>
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import { createContext, useContext } from 'react';
|
||||
import { ListKey } from '/@/renderer/store';
|
||||
import { Play } from '/@/renderer/types';
|
||||
|
||||
interface SongListContextProps {
|
||||
handlePlay?: (args: { initialSongId?: string; playType: Play }) => void;
|
||||
id?: string;
|
||||
pageKey: ListKey;
|
||||
}
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||
import { useRef } from 'react';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { useParams, useSearchParams } from 'react-router-dom';
|
||||
import { SongListQuery, LibraryItem } from '/@/renderer/api/types';
|
||||
import { usePlayQueueAdd } from '/@/renderer/features/player';
|
||||
import { AnimatedPage } from '/@/renderer/features/shared';
|
||||
import { SongListContent } from '/@/renderer/features/songs/components/song-list-content';
|
||||
import { SongListHeader } from '/@/renderer/features/songs/components/song-list-header';
|
||||
import { SongListContext } from '/@/renderer/features/songs/context/song-list-context';
|
||||
import { useSongList } from '/@/renderer/features/songs/queries/song-list-query';
|
||||
import { generatePageKey, useCurrentServer, useSongListFilter } from '/@/renderer/store';
|
||||
import { Play } from '/@/renderer/types';
|
||||
|
||||
const TrackListRoute = () => {
|
||||
const tableRef = useRef<AgGridReactType | null>(null);
|
||||
|
@ -18,6 +21,7 @@ const TrackListRoute = () => {
|
|||
albumArtistId ? `${albumArtistId}_${server?.id}` : undefined,
|
||||
);
|
||||
|
||||
const handlePlayQueueAdd = usePlayQueueAdd();
|
||||
const songListFilter = useSongListFilter({ id: albumArtistId, key: pageKey });
|
||||
const itemCountCheck = useSongList({
|
||||
options: {
|
||||
|
@ -37,9 +41,40 @@ const TrackListRoute = () => {
|
|||
? undefined
|
||||
: itemCountCheck.data?.totalRecordCount;
|
||||
|
||||
const handlePlay = useCallback(
|
||||
async (args: { initialSongId?: string; playType: Play }) => {
|
||||
if (!itemCount || itemCount === 0) return;
|
||||
const { initialSongId, playType } = args;
|
||||
const query: SongListQuery = { startIndex: 0, ...songListFilter };
|
||||
|
||||
if (albumArtistId) {
|
||||
handlePlayQueueAdd?.({
|
||||
byItemType: {
|
||||
id: [albumArtistId],
|
||||
type: LibraryItem.ALBUM_ARTIST,
|
||||
},
|
||||
initialSongId,
|
||||
playType,
|
||||
query,
|
||||
});
|
||||
} else {
|
||||
handlePlayQueueAdd?.({
|
||||
byItemType: {
|
||||
id: [],
|
||||
type: LibraryItem.SONG,
|
||||
},
|
||||
initialSongId,
|
||||
playType,
|
||||
query,
|
||||
});
|
||||
}
|
||||
},
|
||||
[albumArtistId, handlePlayQueueAdd, itemCount, songListFilter],
|
||||
);
|
||||
|
||||
return (
|
||||
<AnimatedPage>
|
||||
<SongListContext.Provider value={{ id: albumArtistId, pageKey }}>
|
||||
<SongListContext.Provider value={{ handlePlay, id: albumArtistId, pageKey }}>
|
||||
<SongListHeader
|
||||
itemCount={itemCount}
|
||||
tableRef={tableRef}
|
||||
|
|
Reference in a new issue