diff --git a/src/renderer/api/jellyfin/jellyfin-controller.ts b/src/renderer/api/jellyfin/jellyfin-controller.ts index e7cd37dd3..c8ac48646 100644 --- a/src/renderer/api/jellyfin/jellyfin-controller.ts +++ b/src/renderer/api/jellyfin/jellyfin-controller.ts @@ -535,7 +535,6 @@ const getPlaylistSongList = async (args: PlaylistSongListArgs): Promise jfNormalize.song(item, apiClientProps.server, '')), - startIndex: query.startIndex, + startIndex: 0, totalRecordCount: res.body.TotalRecordCount, }; }; diff --git a/src/renderer/api/navidrome/navidrome-controller.ts b/src/renderer/api/navidrome/navidrome-controller.ts index 18a640718..166b23ddc 100644 --- a/src/renderer/api/navidrome/navidrome-controller.ts +++ b/src/renderer/api/navidrome/navidrome-controller.ts @@ -303,7 +303,7 @@ const createPlaylist = async (args: CreatePlaylistArgs): Promise ndNormalize.song(item, apiClientProps.server, '')), - startIndex: query?.startIndex || 0, + startIndex: 0, totalRecordCount: Number(res.body.headers.get('x-total-count') || 0), }; }; diff --git a/src/renderer/api/subsonic/subsonic-controller.ts b/src/renderer/api/subsonic/subsonic-controller.ts index 7e21bbc1e..9bf4c12fb 100644 --- a/src/renderer/api/subsonic/subsonic-controller.ts +++ b/src/renderer/api/subsonic/subsonic-controller.ts @@ -1,5 +1,7 @@ import orderBy from 'lodash/orderBy'; +import shuffle from 'lodash/shuffle'; import filter from 'lodash/filter'; +import reverse from 'lodash/reverse'; import md5 from 'md5'; import { fsLog } from '/@/logger'; import { subsonicApiClient } from '/@/renderer/api/subsonic/subsonic-api'; @@ -13,6 +15,7 @@ import { GenreListSort, LibraryItem, PlaylistListSort, + SongListSort, } from '/@/renderer/api/types'; import { randomString } from '/@/renderer/utils'; @@ -566,6 +569,25 @@ export const SubsonicController: ControllerEndpoint = { totalRecordCount: res.body['subsonic-response'].musicFolders.musicFolder.length, }; }, + getPlaylistDetail: async (args) => { + const { query, apiClientProps } = args; + + const res = await subsonicApiClient(apiClientProps).getPlaylist({ + query: { + id: query.id, + }, + }); + + if (res.status !== 200) { + fsLog.error('Failed to get playlist detail'); + throw new Error('Failed to get playlist detail'); + } + + return subsonicNormalize.playlist( + res.body['subsonic-response'].playlist, + apiClientProps.server, + ); + }, getPlaylistList: async (args) => { const { query, apiClientProps } = args; const sortOrder = query.sortOrder.toLowerCase() as 'asc' | 'desc'; @@ -619,7 +641,7 @@ export const SubsonicController: ControllerEndpoint = { }; }, getPlaylistListCount: async (args) => { - const { apiClientProps } = args; + const { query, apiClientProps } = args; const res = await subsonicApiClient(apiClientProps).getPlaylists({}); @@ -628,8 +650,127 @@ export const SubsonicController: ControllerEndpoint = { throw new Error('Failed to get playlist list count'); } + if (query.searchTerm) { + const searchResults = filter( + res.body['subsonic-response'].playlists.playlist, + (playlist) => { + return playlist.name.toLowerCase().includes(query.searchTerm!.toLowerCase()); + }, + ); + + return searchResults.length; + } + return res.body['subsonic-response'].playlists.playlist.length; }, + getPlaylistSongList: async (args) => { + const { query, apiClientProps } = args; + const sortOrder = query.sortOrder.toLowerCase() as 'asc' | 'desc'; + + const res = await subsonicApiClient(apiClientProps).getPlaylist({ + query: { + id: query.id, + }, + }); + + if (res.status !== 200) { + fsLog.error('Failed to get playlist song list'); + throw new Error('Failed to get playlist song list'); + } + + let results = res.body['subsonic-response'].playlist.entry || []; + + if (query.searchTerm) { + const searchResults = filter(results, (entry) => { + return entry.title.toLowerCase().includes(query.searchTerm!.toLowerCase()); + }); + + results = searchResults; + } + + if (query.sortBy) { + switch (query.sortBy) { + case SongListSort.ALBUM: + results = orderBy( + results, + [(v) => v.album?.toLowerCase(), 'discNumber', 'track'], + [sortOrder, 'asc', 'asc'], + ); + break; + case SongListSort.ALBUM_ARTIST: + results = orderBy( + results, + ['albumArtist', (v) => v.album?.toLowerCase(), 'discNumber', 'track'], + [sortOrder, sortOrder, 'asc', 'asc'], + ); + break; + case SongListSort.ARTIST: + results = orderBy( + results, + ['artist', (v) => v.album?.toLowerCase(), 'discNumber', 'track'], + [sortOrder, sortOrder, 'asc', 'asc'], + ); + break; + case SongListSort.DURATION: + results = orderBy(results, ['duration'], [sortOrder]); + break; + case SongListSort.FAVORITED: + results = orderBy( + results, + ['starred', (v) => v.title.toLowerCase()], + [sortOrder], + ); + break; + case SongListSort.GENRE: + results = orderBy( + results, + ['genre', (v) => v.album?.toLowerCase(), 'discNumber', 'track'], + [sortOrder, sortOrder, 'asc', 'asc'], + ); + break; + case SongListSort.ID: + if (sortOrder === 'desc') { + results = reverse(results); + } + break; + case SongListSort.NAME: + results = orderBy(results, [(v) => v.title.toLowerCase()], [sortOrder]); + break; + case SongListSort.PLAY_COUNT: + results = orderBy(results, ['playCount'], [sortOrder]); + break; + case SongListSort.RANDOM: + results = shuffle(results); + break; + case SongListSort.RATING: + results = orderBy( + results, + ['userRating', (v) => v.title.toLowerCase()], + [sortOrder], + ); + break; + case SongListSort.RECENTLY_ADDED: + results = orderBy(results, ['created'], [sortOrder]); + break; + case SongListSort.YEAR: + results = orderBy( + results, + ['year', (v) => v.album?.toLowerCase(), 'discNumber', 'track'], + [sortOrder, 'asc', 'asc', 'asc'], + ); + break; + + default: + break; + } + } + + return { + items: results?.map((song) => subsonicNormalize.song(song, apiClientProps.server, '')), + startIndex: 0, + totalRecordCount: results?.length || 0, + }; + }, getRandomSongList: async (args) => { const { query, apiClientProps } = args; diff --git a/src/renderer/api/subsonic/subsonic-types.ts b/src/renderer/api/subsonic/subsonic-types.ts index c8e825b1b..20ec25b29 100644 --- a/src/renderer/api/subsonic/subsonic-types.ts +++ b/src/renderer/api/subsonic/subsonic-types.ts @@ -109,7 +109,7 @@ const playlist = z.object({ coverArt: z.string().optional(), created: z.string(), duration: z.number(), - entry: z.array(song), + entry: z.array(song).optional(), id: z.string(), name: z.string(), owner: z.string(), diff --git a/src/renderer/api/types.ts b/src/renderer/api/types.ts index fc6f8dc99..0f12aea77 100644 --- a/src/renderer/api/types.ts +++ b/src/renderer/api/types.ts @@ -815,6 +815,7 @@ export type CreatePlaylistBody = { }; comment?: string; name: string; + public?: boolean; }; export type CreatePlaylistArgs = { body: CreatePlaylistBody; serverId?: string } & BaseEndpointArgs; @@ -935,10 +936,9 @@ export type PlaylistSongListResponse = BasePaginatedResponse | null | un export type PlaylistSongListQuery = { id: string; - limit?: number; - sortBy?: SongListSort; - sortOrder?: SortOrder; - startIndex: number; + searchTerm?: string; + sortBy: SongListSort; + sortOrder: SortOrder; }; export type PlaylistSongListArgs = { query: PlaylistSongListQuery } & BaseEndpointArgs; diff --git a/src/renderer/features/player/utils.ts b/src/renderer/features/player/utils.ts index 0b16f2aab..ffa102e9e 100644 --- a/src/renderer/features/player/utils.ts +++ b/src/renderer/features/player/utils.ts @@ -23,7 +23,6 @@ export const getPlaylistSongsById = async (args: { id, sortBy: SongListSort.ID, sortOrder: SortOrder.ASC, - startIndex: 0, ...query, }; @@ -139,7 +138,9 @@ export const getGenreSongsById = async (args: { ); data.items.push(...res!.items); - data.totalRecordCount += res!.totalRecordCount; + if (data.totalRecordCount) { + data.totalRecordCount += res!.totalRecordCount || 0; + } } return data; diff --git a/src/renderer/features/playlists/components/add-to-playlist-context-modal.tsx b/src/renderer/features/playlists/components/add-to-playlist-context-modal.tsx index 08a623a22..2b53de50b 100644 --- a/src/renderer/features/playlists/components/add-to-playlist-context-modal.tsx +++ b/src/renderer/features/playlists/components/add-to-playlist-context-modal.tsx @@ -136,7 +136,6 @@ export const AddToPlaylistContextModal = ({ if (values.skipDuplicates) { const query = { id: playlistId, - startIndex: 0, }; const queryKey = queryKeys.playlists.songList(server?.id || '', playlistId, query); @@ -151,7 +150,11 @@ export const AddToPlaylistContextModal = ({ server, signal, }, - query: { id: playlistId, startIndex: 0 }, + query: { + id: playlistId, + sortBy: SongListSort.ID, + sortOrder: SortOrder.ASC, + }, }); }); diff --git a/src/renderer/features/playlists/components/create-playlist-form.tsx b/src/renderer/features/playlists/components/create-playlist-form.tsx index 63c9fc477..9faa8eb45 100644 --- a/src/renderer/features/playlists/components/create-playlist-form.tsx +++ b/src/renderer/features/playlists/components/create-playlist-form.tsx @@ -32,6 +32,7 @@ export const CreatePlaylistForm = ({ onCancel }: CreatePlaylistFormProps) => { }, comment: '', name: '', + public: false, }, }); const [isSmartPlaylist, setIsSmartPlaylist] = useState(false); @@ -86,7 +87,8 @@ export const CreatePlaylistForm = ({ onCancel }: CreatePlaylistFormProps) => { ); }); - const isPublicDisplayed = server?.type === ServerType.NAVIDROME; + const isPublicDisplayed = + server?.type === ServerType.NAVIDROME || server?.type === ServerType.SUBSONIC; const isSubmitDisabled = !form.values.name || mutation.isLoading; return ( @@ -115,7 +117,7 @@ export const CreatePlaylistForm = ({ onCancel }: CreatePlaylistFormProps) => { context: 'public', postProcess: 'titleCase', })} - {...form.getInputProps('_custom.navidrome.public', { + {...form.getInputProps('public', { type: 'checkbox', })} /> diff --git a/src/renderer/features/playlists/components/playlist-detail-song-list-content.tsx b/src/renderer/features/playlists/components/playlist-detail-song-list-content.tsx index dc4063525..070196efa 100644 --- a/src/renderer/features/playlists/components/playlist-detail-song-list-content.tsx +++ b/src/renderer/features/playlists/components/playlist-detail-song-list-content.tsx @@ -2,25 +2,15 @@ import type { BodyScrollEvent, ColDef, GridReadyEvent, - IDatasource, PaginationChangedEvent, RowDoubleClickedEvent, } from '@ag-grid-community/core'; import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact'; -import { useQueryClient } from '@tanstack/react-query'; import { AnimatePresence } from 'framer-motion'; import debounce from 'lodash/debounce'; import { MutableRefObject, useCallback, useMemo } from 'react'; import { useParams } from 'react-router'; -import { api } from '/@/renderer/api'; -import { queryKeys } from '/@/renderer/api/query-keys'; -import { - LibraryItem, - PlaylistSongListQuery, - QueueSong, - SongListSort, - SortOrder, -} from '/@/renderer/api/types'; +import { LibraryItem, QueueSong, Song } from '/@/renderer/api/types'; import { VirtualGridAutoSizerContainer } from '/@/renderer/components/virtual-grid'; import { TablePagination, VirtualTable, getColumnDefs } from '/@/renderer/components/virtual-table'; import { useCurrentSongRowStyles } from '/@/renderer/components/virtual-table/hooks/use-current-song-row-styles'; @@ -31,7 +21,7 @@ import { } from '/@/renderer/features/context-menu/context-menu-items'; import { usePlayQueueAdd } from '/@/renderer/features/player'; import { usePlaylistDetail } from '/@/renderer/features/playlists/queries/playlist-detail-query'; -import { usePlaylistSongList } from '/@/renderer/features/playlists/queries/playlist-song-list-query'; +import { useAppFocus } from '/@/renderer/hooks'; import { useCurrentServer, useCurrentSong, @@ -43,26 +33,19 @@ import { } from '/@/renderer/store'; import { usePlayButtonBehavior } from '/@/renderer/store/settings.store'; import { ListDisplayType } from '/@/renderer/types'; -import { useAppFocus } from '/@/renderer/hooks'; interface PlaylistDetailContentProps { + songs: Song[]; tableRef: MutableRefObject; } -export const PlaylistDetailSongListContent = ({ tableRef }: PlaylistDetailContentProps) => { +export const PlaylistDetailSongListContent = ({ songs, tableRef }: PlaylistDetailContentProps) => { const { playlistId } = useParams() as { playlistId: string }; - const queryClient = useQueryClient(); const status = useCurrentStatus(); const isFocused = useAppFocus(); const currentSong = useCurrentSong(); const server = useCurrentServer(); const page = usePlaylistDetailStore(); - const filters: Partial = useMemo(() => { - return { - sortBy: page?.table.id[playlistId]?.filter?.sortBy || SongListSort.ID, - sortOrder: page?.table.id[playlistId]?.filter?.sortOrder || SortOrder.ASC, - }; - }, [page?.table.id, playlistId]); const detailQuery = usePlaylistDetail({ query: { id: playlistId }, serverId: server?.id }); @@ -82,15 +65,6 @@ export const PlaylistDetailSongListContent = ({ tableRef }: PlaylistDetailConten const isPaginationEnabled = page.display === ListDisplayType.TABLE_PAGINATED; - const checkPlaylistList = usePlaylistSongList({ - query: { - id: playlistId, - limit: 1, - startIndex: 0, - }, - serverId: server?.id, - }); - const columnDefs: ColDef[] = useMemo( () => getColumnDefs(page.table.columns, false, 'generic'), [page.table.columns], @@ -98,44 +72,9 @@ export const PlaylistDetailSongListContent = ({ tableRef }: PlaylistDetailConten const onGridReady = useCallback( (params: GridReadyEvent) => { - const dataSource: IDatasource = { - getRows: async (params) => { - const limit = params.endRow - params.startRow; - const startIndex = params.startRow; - - const query: PlaylistSongListQuery = { - id: playlistId, - limit, - startIndex, - ...filters, - }; - - const queryKey = queryKeys.playlists.songList( - server?.id || '', - playlistId, - query, - ); - - if (!server) return; - - const songsRes = await queryClient.fetchQuery(queryKey, async ({ signal }) => - api.controller.getPlaylistSongList({ - apiClientProps: { - server, - signal, - }, - query, - }), - ); - - params.successCallback(songsRes?.items || [], songsRes?.totalRecordCount || 0); - }, - rowCount: undefined, - }; - params.api.setDatasource(dataSource); params.api?.ensureIndexVisible(pagination.scrollOffset, 'top'); }, - [filters, pagination.scrollOffset, playlistId, queryClient, server], + [pagination.scrollOffset], ); const handleGridSizeChange = () => { @@ -249,13 +188,13 @@ export const PlaylistDetailSongListContent = ({ tableRef }: PlaylistDetailConten status, }} getRowId={(data) => data.data.uniqueId} - infiniteInitialRowCount={checkPlaylistList.data?.totalRecordCount || 100} pagination={isPaginationEnabled} paginationAutoPageSize={isPaginationEnabled} paginationPageSize={pagination.itemsPerPage || 100} rowClassRules={rowClassRules} + rowData={songs} rowHeight={page.table.rowHeight || 40} - rowModelType="infinite" + rowModelType="clientSide" onBodyScrollEnd={handleScroll} onCellContextMenu={handleContextMenu} onColumnMoved={handleColumnChange} diff --git a/src/renderer/features/playlists/components/playlist-detail-song-list-header-filters.tsx b/src/renderer/features/playlists/components/playlist-detail-song-list-header-filters.tsx index 83d4311a1..3e7fc60cf 100644 --- a/src/renderer/features/playlists/components/playlist-detail-song-list-header-filters.tsx +++ b/src/renderer/features/playlists/components/playlist-detail-song-list-header-filters.tsx @@ -1,53 +1,50 @@ -import { useCallback, 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 { Divider, Flex, Group, Stack } from '@mantine/core'; import { closeAllModals, openModal } from '@mantine/modals'; import { useQueryClient } from '@tanstack/react-query'; +import { ChangeEvent, MouseEvent, MutableRefObject, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { - RiMoreFill, - RiSettings3Fill, - RiPlayFill, - RiAddCircleFill, RiAddBoxFill, - RiEditFill, + RiAddCircleFill, RiDeleteBinFill, + RiEditFill, + RiMoreFill, + RiPlayFill, RiRefreshLine, + RiSettings3Fill, } from 'react-icons/ri'; -import { api } from '/@/renderer/api'; +import { useNavigate, useParams } from 'react-router'; +import i18n from '/@/i18n/i18n'; import { queryKeys } from '/@/renderer/api/query-keys'; import { LibraryItem, PlaylistSongListQuery, SongListSort, SortOrder } from '/@/renderer/api/types'; import { - DropdownMenu, Button, - Slider, + ConfirmModal, + DropdownMenu, MultiSelect, + Slider, Switch, Text, - ConfirmModal, toast, } from '/@/renderer/components'; +import { SONG_TABLE_COLUMNS } from '/@/renderer/components/virtual-table'; import { usePlayQueueAdd } from '/@/renderer/features/player'; +import { openUpdatePlaylistModal } from '/@/renderer/features/playlists/components/update-playlist-form'; +import { useDeletePlaylist } from '/@/renderer/features/playlists/mutations/delete-playlist-mutation'; +import { usePlaylistDetail } from '/@/renderer/features/playlists/queries/playlist-detail-query'; +import { OrderToggleButton } from '/@/renderer/features/shared'; import { useContainerQuery } from '/@/renderer/hooks'; +import { AppRoute } from '/@/renderer/router/routes'; import { useCurrentServer, - SongListFilter, usePlaylistDetailStore, useSetPlaylistDetailFilters, useSetPlaylistDetailTable, useSetPlaylistStore, useSetPlaylistTablePagination, } from '/@/renderer/store'; -import { ListDisplayType, ServerType, Play, TableColumn } from '/@/renderer/types'; -import { usePlaylistDetail } from '/@/renderer/features/playlists/queries/playlist-detail-query'; -import { useParams, useNavigate } from 'react-router'; -import { SONG_TABLE_COLUMNS } from '/@/renderer/components/virtual-table'; -import { openUpdatePlaylistModal } from '/@/renderer/features/playlists/components/update-playlist-form'; -import { useDeletePlaylist } from '/@/renderer/features/playlists/mutations/delete-playlist-mutation'; -import { AppRoute } from '/@/renderer/router/routes'; -import { OrderToggleButton } from '/@/renderer/features/shared'; -import i18n from '/@/i18n/i18n'; +import { ListDisplayType, Play, ServerType, TableColumn } from '/@/renderer/types'; const FILTERS = { jellyfin: [ @@ -150,7 +147,7 @@ const FILTERS = { }, { defaultOrder: SortOrder.ASC, - name: i18n.t('filter.playCount', { postProcess: 'titleCase' }), + name: i18n.t('filter.genre', { postProcess: 'titleCase' }), value: SongListSort.GENRE, }, { @@ -184,6 +181,68 @@ const FILTERS = { value: SongListSort.YEAR, }, ], + subsonic: [ + { + defaultOrder: SortOrder.ASC, + name: i18n.t('filter.id', { postProcess: 'titleCase' }), + value: SongListSort.ID, + }, + { + defaultOrder: SortOrder.ASC, + name: i18n.t('filter.album', { postProcess: 'titleCase' }), + value: SongListSort.ALBUM, + }, + { + defaultOrder: SortOrder.ASC, + name: i18n.t('filter.albumArtist', { postProcess: 'titleCase' }), + value: SongListSort.ALBUM_ARTIST, + }, + { + defaultOrder: SortOrder.ASC, + name: i18n.t('filter.artist', { postProcess: 'titleCase' }), + value: SongListSort.ARTIST, + }, + { + defaultOrder: SortOrder.DESC, + name: i18n.t('filter.duration', { postProcess: 'titleCase' }), + value: SongListSort.DURATION, + }, + { + defaultOrder: SortOrder.DESC, + name: i18n.t('filter.isFavorited', { postProcess: 'titleCase' }), + value: SongListSort.FAVORITED, + }, + { + defaultOrder: SortOrder.ASC, + name: i18n.t('filter.genre', { postProcess: 'titleCase' }), + value: SongListSort.GENRE, + }, + { + defaultOrder: SortOrder.ASC, + name: i18n.t('filter.name', { postProcess: 'titleCase' }), + value: SongListSort.NAME, + }, + { + defaultOrder: SortOrder.DESC, + name: i18n.t('filter.rating', { postProcess: 'titleCase' }), + value: SongListSort.RATING, + }, + { + defaultOrder: SortOrder.DESC, + name: i18n.t('filter.recentlyAdded', { postProcess: 'titleCase' }), + value: SongListSort.RECENTLY_ADDED, + }, + { + defaultOrder: SortOrder.DESC, + name: i18n.t('filter.recentlyPlayed', { postProcess: 'titleCase' }), + value: SongListSort.RECENTLY_PLAYED, + }, + { + defaultOrder: SortOrder.DESC, + name: i18n.t('filter.releaseYear', { postProcess: 'titleCase' }), + value: SongListSort.YEAR, + }, + ], }; interface PlaylistDetailSongListHeaderFiltersProps { @@ -228,56 +287,18 @@ export const PlaylistDetailSongListHeaderFilters = ({ setTable({ rowHeight: e }); }; - const handleFilterChange = useCallback( - async (filters: SongListFilter) => { - const dataSource: IDatasource = { - getRows: async (params) => { - const limit = params.endRow - params.startRow; - const startIndex = params.startRow; + const handleFilterChange = useCallback(async () => { + tableRef.current?.api.redrawRows(); + tableRef.current?.api.ensureIndexVisible(0, 'top'); - const queryKey = queryKeys.playlists.songList(server?.id || '', playlistId, { - id: playlistId, - limit, - startIndex, - ...filters, - }); - - const songsRes = await queryClient.fetchQuery( - queryKey, - async ({ signal }) => - api.controller.getPlaylistSongList({ - apiClientProps: { - server, - signal, - }, - query: { - id: playlistId, - limit, - startIndex, - ...filters, - }, - }), - { cacheTime: 1000 * 60 * 1 }, - ); - - params.successCallback(songsRes?.items || [], songsRes?.totalRecordCount || 0); - }, - rowCount: undefined, - }; - tableRef.current?.api.setDatasource(dataSource); - tableRef.current?.api.purgeInfiniteCache(); - tableRef.current?.api.ensureIndexVisible(0, 'top'); - - if (page.display === ListDisplayType.TABLE_PAGINATED) { - setPagination({ data: { currentPage: 0 } }); - } - }, - [tableRef, page.display, server, playlistId, queryClient, setPagination], - ); + if (page.display === ListDisplayType.TABLE_PAGINATED) { + setPagination({ data: { currentPage: 0 } }); + } + }, [tableRef, page.display, setPagination]); const handleRefresh = () => { queryClient.invalidateQueries(queryKeys.albums.list(server?.id || '')); - handleFilterChange({ ...page?.table.id[playlistId].filter, ...filters }); + handleFilterChange(); }; const handleSetSortBy = useCallback( @@ -288,20 +309,20 @@ export const PlaylistDetailSongListHeaderFilters = ({ (f) => f.value === e.currentTarget.value, )?.defaultOrder; - const updatedFilters = setFilter(playlistId, { + setFilter(playlistId, { sortBy: e.currentTarget.value as SongListSort, sortOrder: sortOrder || SortOrder.ASC, }); - handleFilterChange(updatedFilters); + handleFilterChange(); }, [handleFilterChange, playlistId, server?.type, setFilter], ); const handleToggleSortOrder = useCallback(() => { const newSortOrder = filters.sortOrder === SortOrder.ASC ? SortOrder.DESC : SortOrder.ASC; - const updatedFilters = setFilter(playlistId, { sortOrder: newSortOrder }); - handleFilterChange(updatedFilters); + setFilter(playlistId, { sortOrder: newSortOrder }); + handleFilterChange(); }, [filters.sortOrder, handleFilterChange, playlistId, setFilter]); const handleSetViewType = useCallback( diff --git a/src/renderer/features/playlists/components/playlist-detail-song-list-header.tsx b/src/renderer/features/playlists/components/playlist-detail-song-list-header.tsx index 29e7a51ec..4a25d3f03 100644 --- a/src/renderer/features/playlists/components/playlist-detail-song-list-header.tsx +++ b/src/renderer/features/playlists/components/playlist-detail-song-list-header.tsx @@ -1,6 +1,6 @@ import { MutableRefObject } from 'react'; import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact'; -import { Stack } from '@mantine/core'; +import { Flex, Stack } from '@mantine/core'; import { useTranslation } from 'react-i18next'; import { useParams } from 'react-router'; import { LibraryItem } from '/@/renderer/api/types'; @@ -45,23 +45,30 @@ export const PlaylistDetailSongListHeader = ({ return ( - - handlePlay(playButtonBehavior)} /> - {detailQuery?.data?.name} - - {itemCount === null || itemCount === undefined ? ( - - ) : ( - itemCount - )} - - {isSmartPlaylist && {t('entity.smartPlaylist')}} - + + + handlePlay(playButtonBehavior)} + /> + {detailQuery?.data?.name} + + {itemCount === null || itemCount === undefined ? ( + + ) : ( + itemCount + )} + + {isSmartPlaylist && {t('entity.smartPlaylist')}} + + { const page = usePlaylistDetailStore(); const filters: Partial = { - sortBy: page?.table.id[playlistId]?.filter?.sortBy || SongListSort.ID, - sortOrder: page?.table.id[playlistId]?.filter?.sortOrder || SortOrder.ASC, + sortBy: page?.table.id[playlistId]?.filter?.sortBy, + sortOrder: page?.table.id[playlistId]?.filter?.sortOrder, }; - const itemCountCheck = usePlaylistSongList({ - options: { - cacheTime: 1000 * 60 * 60 * 2, - staleTime: 1000 * 60 * 60 * 2, - }, + const { data } = usePlaylistSongList({ query: { id: playlistId, - limit: 1, - startIndex: 0, - ...filters, + sortBy: filters.sortBy || SongListSort.ID, + sortOrder: filters.sortOrder || SortOrder.ASC, }, serverId: server?.id, }); - const itemCount = - itemCountCheck.data?.totalRecordCount === null - ? undefined - : itemCountCheck.data?.totalRecordCount; + const itemCount = data?.items.length; return ( @@ -206,7 +198,10 @@ const PlaylistDetailSongListRoute = () => { )} - + ); };