diff --git a/src/renderer/features/albums/components/album-detail-content.tsx b/src/renderer/features/albums/components/album-detail-content.tsx index c09dbc5c..1e2eb66b 100644 --- a/src/renderer/features/albums/components/album-detail-content.tsx +++ b/src/renderer/features/albums/components/album-detail-content.tsx @@ -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) => { - 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, }); }; diff --git a/src/renderer/features/artists/components/album-artist-detail-content.tsx b/src/renderer/features/artists/components/album-artist-detail-content.tsx index ad8d1049..5e771bb0 100644 --- a/src/renderer/features/artists/components/album-artist-detail-content.tsx +++ b/src/renderer/features/artists/components/album-artist-detail-content.tsx @@ -256,9 +256,11 @@ export const AlbumArtistDetailContent = () => { const handleContextMenu = useHandleTableContextMenu(LibraryItem.SONG, SONG_CONTEXT_MENU_ITEMS); const handleRowDoubleClick = (e: RowDoubleClickedEvent) => { - if (!e.data) return; + if (!e.data || !topSongsQuery?.data) return; + handlePlayQueueAdd?.({ - byData: [e.data], + byData: topSongsQuery?.data?.items || [], + initialSongId: e.data.id, playType: playButtonBehavior, }); }; diff --git a/src/renderer/features/artists/components/album-artist-detail-top-songs-list-content.tsx b/src/renderer/features/artists/components/album-artist-detail-top-songs-list-content.tsx index 70716d08..ea7d7554 100644 --- a/src/renderer/features/artists/components/album-artist-detail-top-songs-list-content.tsx +++ b/src/renderer/features/artists/components/album-artist-detail-top-songs-list-content.tsx @@ -34,8 +34,16 @@ export const AlbumArtistDetailTopSongsListContent = ({ const handleRowDoubleClick = (e: RowDoubleClickedEvent) => { 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, }); }; diff --git a/src/renderer/features/player/hooks/use-handle-playqueue-add.ts b/src/renderer/features/player/hooks/use-handle-playqueue-add.ts index 26ef4a0a..cb40b77d 100644 --- a/src/renderer/features/player/hooks/use-handle-playqueue-add.ts +++ b/src/renderer/features/player/hooks/use-handle-playqueue-add.ts @@ -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) { diff --git a/src/renderer/features/player/utils.ts b/src/renderer/features/player/utils.ts index 155a83a5..36c94d6b 100644 --- a/src/renderer/features/player/utils.ts +++ b/src/renderer/features/player/utils.ts @@ -124,6 +124,41 @@ export const getAlbumArtistSongsById = async (args: { return res; }; +export const getSongsByQuery = async (args: { + query?: Partial; + 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; diff --git a/src/renderer/features/playlists/components/playlist-detail-content.tsx b/src/renderer/features/playlists/components/playlist-detail-content.tsx index 51ad1ffb..ee5425c6 100644 --- a/src/renderer/features/playlists/components/playlist-detail-content.tsx +++ b/src/renderer/features/playlists/components/playlist-detail-content.tsx @@ -201,8 +201,13 @@ export const PlaylistDetailContent = ({ tableRef }: PlaylistDetailContentProps) const handleRowDoubleClick = (e: RowDoubleClickedEvent) => { if (!e.data) return; + handlePlayQueueAdd?.({ - byData: [e.data], + byItemType: { + id: [playlistId], + type: LibraryItem.PLAYLIST, + }, + initialSongId: e.data.id, playType: playButtonBehavior, }); }; diff --git a/src/renderer/features/songs/components/song-list-content.tsx b/src/renderer/features/songs/components/song-list-content.tsx index ef438f7a..6135a282 100644 --- a/src/renderer/features/songs/components/song-list-content.tsx +++ b/src/renderer/features/songs/components/song-list-content.tsx @@ -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) => { if (!e.data) return; - handlePlayQueueAdd?.({ - byData: [e.data], - playType: playButtonBehavior, - }); + handlePlay?.({ initialSongId: e.data.id, playType: playButtonBehavior }); }; return ( diff --git a/src/renderer/features/songs/components/song-list-header-filters.tsx b/src/renderer/features/songs/components/song-list-header-filters.tsx index 68d9f2fa..465f7978 100644 --- a/src/renderer/features/songs/components/song-list-header-filters.tsx +++ b/src/renderer/features/songs/components/song-list-header-filters.tsx @@ -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 }; - handlePlayQueueAdd?.({ - byItemType: { - id: query, - type: LibraryItem.SONG, - }, - playType, - }); + if (id) { + handlePlayQueueAdd?.({ + byItemType: { + id: [id], + type: LibraryItem.ALBUM_ARTIST, + }, + playType, + query, + }); + } else { + handlePlayQueueAdd?.({ + byItemType: { + id: [], + type: LibraryItem.SONG, + }, + playType, + query, + }); + } }; const handleOpenFiltersModal = () => { diff --git a/src/renderer/features/songs/components/song-list-header.tsx b/src/renderer/features/songs/components/song-list-header.tsx index 0747ee9e..c80f1c9d 100644 --- a/src/renderer/features/songs/components/song-list-header.tsx +++ b/src/renderer/features/songs/components/song-list-header.tsx @@ -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; itemCount?: number; tableRef: MutableRefObject; 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) => { @@ -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 ( - handlePlay(playButtonBehavior)} /> + handlePlay?.({ playType: playButtonBehavior })} + /> {title || 'Tracks'} {itemCount} @@ -135,7 +114,6 @@ export const SongListHeader = ({ diff --git a/src/renderer/features/songs/context/song-list-context.tsx b/src/renderer/features/songs/context/song-list-context.tsx index 94c9151c..8e77ae2d 100644 --- a/src/renderer/features/songs/context/song-list-context.tsx +++ b/src/renderer/features/songs/context/song-list-context.tsx @@ -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; } diff --git a/src/renderer/features/songs/routes/song-list-route.tsx b/src/renderer/features/songs/routes/song-list-route.tsx index afe323cc..9d57487b 100644 --- a/src/renderer/features/songs/routes/song-list-route.tsx +++ b/src/renderer/features/songs/routes/song-list-route.tsx @@ -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(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 ( - +