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 ec7ed02c..62cdf334 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 @@ -3,20 +3,20 @@ import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/li import { closeAllModals, openModal } from '@mantine/modals'; import { useQueryClient } from '@tanstack/react-query'; import debounce from 'lodash/debounce'; -import { MouseEvent, MutableRefObject, useCallback } from 'react'; +import { ChangeEvent, MouseEvent, MutableRefObject, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { useNavigate, useParams } from 'react-router'; import i18n from '/@/i18n/i18n'; import { queryKeys } from '/@/renderer/api/query-keys'; 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 { ListConfigMenu } from '/@/renderer/features/shared/components/list-config-menu'; import { MoreButton } from '/@/renderer/features/shared/components/more-button'; +import { SearchInput } from '/@/renderer/features/shared/components/search-input'; import { useContainerQuery } from '/@/renderer/hooks'; import { AppRoute } from '/@/renderer/router/routes'; import { @@ -37,13 +37,7 @@ import { Icon } from '/@/shared/components/icon/icon'; import { ConfirmModal } from '/@/shared/components/modal/modal'; import { Text } from '/@/shared/components/text/text'; import { toast } from '/@/shared/components/toast/toast'; -import { - LibraryItem, - PlaylistSongListQueryClientSide, - ServerType, - SongListSort, - SortOrder, -} from '/@/shared/types/domain-types'; +import { ServerType, SongListSort, SortOrder } from '/@/shared/types/domain-types'; import { ListDisplayType, Play } from '/@/shared/types/types'; const FILTERS = { @@ -246,11 +240,13 @@ const FILTERS = { }; interface PlaylistDetailSongListHeaderFiltersProps { + handlePlay: (playType: Play) => void; handleToggleShowQueryBuilder: () => void; tableRef: MutableRefObject; } export const PlaylistDetailSongListHeaderFilters = ({ + handlePlay, handleToggleShowQueryBuilder, tableRef, }: PlaylistDetailSongListHeaderFiltersProps) => { @@ -262,16 +258,13 @@ export const PlaylistDetailSongListHeaderFilters = ({ const setPage = useSetPlaylistStore(); const setFilter = useSetPlaylistDetailFilters(); const page = usePlaylistDetailStore(); - const filters: Partial = { - sortBy: page?.table.id[playlistId]?.filter?.sortBy || SongListSort.ID, - sortOrder: page?.table.id[playlistId]?.filter?.sortOrder || SortOrder.ASC, - }; + const searchTerm = page?.table.id[playlistId]?.filter?.searchTerm; + const sortBy = page?.table.id[playlistId]?.filter?.sortBy || SongListSort.ID; + const sortOrder = page?.table.id[playlistId]?.filter?.sortOrder || SortOrder.ASC; const detailQuery = usePlaylistDetail({ query: { id: playlistId }, serverId: server?.id }); const isSmartPlaylist = detailQuery.data?.rules; - const handlePlayQueueAdd = usePlayQueueAdd(); - const cq = useContainerQuery(); const setPagination = useSetPlaylistTablePagination(); @@ -279,8 +272,7 @@ export const PlaylistDetailSongListHeaderFilters = ({ const sortByLabel = (server?.type && - FILTERS[server.type as keyof typeof FILTERS].find((f) => f.value === filters.sortBy) - ?.name) || + FILTERS[server.type as keyof typeof FILTERS].find((f) => f.value === sortBy)?.name) || 'Unknown'; const handleItemSize = (e: number) => { @@ -307,13 +299,13 @@ export const PlaylistDetailSongListHeaderFilters = ({ (e: MouseEvent) => { if (!e.currentTarget?.value || !server?.type) return; - const sortOrder = FILTERS[server.type as keyof typeof FILTERS].find( + const newSortOrder = FILTERS[server.type as keyof typeof FILTERS].find( (f) => f.value === e.currentTarget.value, )?.defaultOrder; setFilter(playlistId, { sortBy: e.currentTarget.value as SongListSort, - sortOrder: sortOrder || SortOrder.ASC, + sortOrder: newSortOrder || SortOrder.ASC, }); handleFilterChange(); @@ -322,10 +314,15 @@ export const PlaylistDetailSongListHeaderFilters = ({ ); const handleToggleSortOrder = useCallback(() => { - const newSortOrder = filters.sortOrder === SortOrder.ASC ? SortOrder.DESC : SortOrder.ASC; + const newSortOrder = sortOrder === SortOrder.ASC ? SortOrder.DESC : SortOrder.ASC; setFilter(playlistId, { sortOrder: newSortOrder }); handleFilterChange(); - }, [filters.sortOrder, handleFilterChange, playlistId, setFilter]); + }, [sortOrder, handleFilterChange, playlistId, setFilter]); + + const handleSearch = debounce((e: ChangeEvent) => { + setFilter(playlistId, { searchTerm: e.target.value }); + handleFilterChange(); + }, 500); const handleSetViewType = useCallback( (displayType: ListDisplayType) => { @@ -370,14 +367,6 @@ export const PlaylistDetailSongListHeaderFilters = ({ } }; - const handlePlay = async (playType: Play) => { - handlePlayQueueAdd?.({ - byItemType: { id: [playlistId], type: LibraryItem.PLAYLIST }, - playType, - query: filters, - }); - }; - const deletePlaylistMutation = useDeletePlaylist({}); const handleDeletePlaylist = useCallback(() => { @@ -427,7 +416,7 @@ export const PlaylistDetailSongListHeaderFilters = ({ {FILTERS[server?.type as keyof typeof FILTERS].map((filter) => ( @@ -503,6 +492,7 @@ export const PlaylistDetailSongListHeaderFilters = ({ )} + void; + handleToggleShowQueryBuilder: () => void; itemCount?: number; tableRef: MutableRefObject; } export const PlaylistDetailSongListHeader = ({ + handlePlay, handleToggleShowQueryBuilder, itemCount, tableRef, @@ -38,20 +34,6 @@ export const PlaylistDetailSongListHeader = ({ const { playlistId } = useParams() as { playlistId: string }; const server = useCurrentServer(); const detailQuery = usePlaylistDetail({ query: { id: playlistId }, serverId: server?.id }); - const handlePlayQueueAdd = usePlayQueueAdd(); - const page = usePlaylistDetailStore(); - const filters: Partial = { - sortBy: page?.table.id[playlistId]?.filter?.sortBy || SongListSort.ID, - sortOrder: page?.table.id[playlistId]?.filter?.sortOrder || SortOrder.ASC, - }; - - const handlePlay = async (playType: Play) => { - handlePlayQueueAdd?.({ - byItemType: { id: [playlistId], type: LibraryItem.PLAYLIST }, - playType, - query: filters, - }); - }; const playButtonBehavior = usePlayButtonBehavior(); @@ -78,6 +60,7 @@ export const PlaylistDetailSongListHeader = ({ diff --git a/src/renderer/features/playlists/routes/playlist-detail-song-list-route.tsx b/src/renderer/features/playlists/routes/playlist-detail-song-list-route.tsx index 3a124fdb..1b016d19 100644 --- a/src/renderer/features/playlists/routes/playlist-detail-song-list-route.tsx +++ b/src/renderer/features/playlists/routes/playlist-detail-song-list-route.tsx @@ -1,11 +1,13 @@ import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact'; import { closeAllModals, openModal } from '@mantine/modals'; +import Fuse from 'fuse.js'; import { motion } from 'motion/react'; import { useMemo, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { generatePath, useNavigate, useParams } from 'react-router'; +import { useHandlePlayQueueAdd } from '/@/renderer/features/player/hooks/use-handle-playqueue-add'; import { PlaylistDetailSongListContent } from '/@/renderer/features/playlists/components/playlist-detail-song-list-content'; import { PlaylistDetailSongListHeader } from '/@/renderer/features/playlists/components/playlist-detail-song-list-header'; import { PlaylistQueryBuilder } from '/@/renderer/features/playlists/components/playlist-query-builder'; @@ -23,6 +25,7 @@ import { Group } from '/@/shared/components/group/group'; import { Text } from '/@/shared/components/text/text'; import { toast } from '/@/shared/components/toast/toast'; import { ServerType, SongListSort, SortOrder, sortSongList } from '/@/shared/types/domain-types'; +import { Play } from '/@/shared/types/types'; const PlaylistDetailSongListRoute = () => { const { t } = useTranslation(); @@ -30,6 +33,7 @@ const PlaylistDetailSongListRoute = () => { const tableRef = useRef(null); const { playlistId } = useParams() as { playlistId: string }; const server = useCurrentServer(); + const handlePlayQueueAdd = useHandlePlayQueueAdd(); const detailQuery = usePlaylistDetail({ query: { id: playlistId }, serverId: server?.id }); const createPlaylistMutation = useCreatePlaylist({}); @@ -151,21 +155,50 @@ const PlaylistDetailSongListRoute = () => { serverId: server?.id, }); - const itemCount = playlistSongs.data?.totalRecordCount ?? undefined; - const filterSortedSongs = useMemo(() => { - if (playlistSongs.data?.items) { + let items = playlistSongs.data?.items; + + if (items) { + const searchTerm = page?.table.id[playlistId]?.filter.searchTerm; + + if (searchTerm) { + const fuse = new Fuse(items, { + fieldNormWeight: 1, + ignoreLocation: true, + keys: [ + 'name', + 'album', + { + getFn: (song) => song.artists.map((artist) => artist.name), + name: 'artist', + }, + ], + threshold: 0, + }); + items = fuse.search(searchTerm).map((item) => item.item); + } + const sortBy = page?.table.id[playlistId]?.filter?.sortBy || SongListSort.ID; const sortOrder = page?.table.id[playlistId]?.filter?.sortOrder || SortOrder.ASC; - return sortSongList(playlistSongs.data?.items, sortBy, sortOrder); + return sortSongList(items, sortBy, sortOrder); } else { return []; } }, [playlistSongs.data?.items, page?.table.id, playlistId]); + const itemCount = playlistSongs.data?.totalRecordCount ? filterSortedSongs.length : undefined; + + const handlePlay = (play: Play) => { + handlePlayQueueAdd?.({ + byData: filterSortedSongs, + playType: play, + }); + }; + return (