From 4cbbb4035d12a6f6c7e5c65827c59fefd8aab71d Mon Sep 17 00:00:00 2001 From: Kendall Garner <17521368+kgarner7@users.noreply.github.com> Date: Sun, 26 Oct 2025 11:51:55 -0700 Subject: [PATCH] feat: add filtering for now playing view and sidebar --- .../components/drawer-play-queue.tsx | 12 +++++++--- .../components/play-queue-list-controls.tsx | 24 +++++++++++++++++-- .../now-playing/components/play-queue.tsx | 14 +++++++++-- .../components/sidebar-play-queue.tsx | 12 +++++++--- .../now-playing/routes/now-playing-route.tsx | 12 +++++++--- .../playlist-detail-song-list-route.tsx | 17 ++----------- src/renderer/utils/search-songs.ts | 20 ++++++++++++++++ 7 files changed, 83 insertions(+), 28 deletions(-) create mode 100644 src/renderer/utils/search-songs.ts diff --git a/src/renderer/features/now-playing/components/drawer-play-queue.tsx b/src/renderer/features/now-playing/components/drawer-play-queue.tsx index 06f61047..800ca043 100644 --- a/src/renderer/features/now-playing/components/drawer-play-queue.tsx +++ b/src/renderer/features/now-playing/components/drawer-play-queue.tsx @@ -1,6 +1,6 @@ import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact'; -import { useRef } from 'react'; +import { useRef, useState } from 'react'; import { PlayQueue } from '/@/renderer/features/now-playing/components/play-queue'; import { PlayQueueListControls } from '/@/renderer/features/now-playing/components/play-queue-list-controls'; @@ -9,6 +9,7 @@ import { Song } from '/@/shared/types/domain-types'; export const DrawerPlayQueue = () => { const queueRef = useRef }>(null); + const [search, setSearch] = useState(undefined); return ( @@ -18,10 +19,15 @@ export const DrawerPlayQueue = () => { borderRadius: '10px', }} > - + - + ); diff --git a/src/renderer/features/now-playing/components/play-queue-list-controls.tsx b/src/renderer/features/now-playing/components/play-queue-list-controls.tsx index e85c2008..a0bbc6a3 100644 --- a/src/renderer/features/now-playing/components/play-queue-list-controls.tsx +++ b/src/renderer/features/now-playing/components/play-queue-list-controls.tsx @@ -1,11 +1,12 @@ import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact'; -import type { MutableRefObject } from 'react'; import isElectron from 'is-electron'; +import { type MutableRefObject, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { TableConfigDropdown } from '/@/renderer/components/virtual-table'; import { updateSong } from '/@/renderer/features/player/update-remote-song'; +import { SearchInput } from '/@/renderer/features/shared/components/search-input'; import { usePlayerControls, useQueueControls } from '/@/renderer/store'; import { usePlayerStore, useSetCurrentTime } from '/@/renderer/store/player.store'; import { usePlaybackType } from '/@/renderer/store/settings.store'; @@ -19,11 +20,18 @@ import { PlaybackType, TableType } from '/@/shared/types/types'; const mpvPlayer = isElectron() ? window.api.mpvPlayer : null; interface PlayQueueListOptionsProps { + handleSearch: (value: string) => void; + searchTerm?: string; tableRef: MutableRefObject }>; type: TableType; } -export const PlayQueueListControls = ({ tableRef, type }: PlayQueueListOptionsProps) => { +export const PlayQueueListControls = ({ + handleSearch, + searchTerm, + tableRef, + type, +}: PlayQueueListOptionsProps) => { const { t } = useTranslation(); const { clearQueue, @@ -119,6 +127,14 @@ export const PlayQueueListControls = ({ tableRef, type }: PlayQueueListOptionsPr } }; + const handleSearchTerm = useCallback( + (term: string) => { + handleSearch(term); + tableRef.current?.grid.api.redrawRows(); + }, + [handleSearch, tableRef], + ); + return ( + handleSearchTerm(e.target.value)} + value={searchTerm} + /> diff --git a/src/renderer/features/now-playing/components/play-queue.tsx b/src/renderer/features/now-playing/components/play-queue.tsx index 0ac8cf2e..c7ee52ca 100644 --- a/src/renderer/features/now-playing/components/play-queue.tsx +++ b/src/renderer/features/now-playing/components/play-queue.tsx @@ -39,6 +39,7 @@ import { useSettingsStoreActions, useTableSettings, } from '/@/renderer/store/settings.store'; +import { searchSongs } from '/@/renderer/utils/search-songs'; import { setQueue, setQueueNext } from '/@/renderer/utils/set-transcoded-queue-data'; import { LibraryItem, QueueSong } from '/@/shared/types/domain-types'; import { PlaybackType, TableType } from '/@/shared/types/types'; @@ -46,10 +47,11 @@ import { PlaybackType, TableType } from '/@/shared/types/types'; const mpvPlayer = isElectron() ? window.api.mpvPlayer : null; type QueueProps = { + searchTerm?: string; type: TableType; }; -export const PlayQueue = forwardRef(({ type }: QueueProps, ref: Ref) => { +export const PlayQueue = forwardRef(({ searchTerm, type }: QueueProps, ref: Ref) => { const tableRef = useRef(null); const mergedRef = useMergedRef(ref, tableRef); const queue = useDefaultQueue(); @@ -67,6 +69,14 @@ export const PlayQueue = forwardRef(({ type }: QueueProps, ref: Ref) => { const isFocused = useAppFocus(); const isFocusedRef = useRef(isFocused); + const songs = useMemo(() => { + if (searchTerm) { + return searchSongs(queue, searchTerm); + } + + return queue; + }, [queue, searchTerm]); + useEffect(() => { if (tableRef.current) { setGridApi(tableRef.current); @@ -276,7 +286,7 @@ export const PlayQueue = forwardRef(({ type }: QueueProps, ref: Ref) => { ref={mergedRef} rowBuffer={50} rowClassRules={rowClassRules} - rowData={queue} + rowData={songs} rowDragEntireRow rowDragMultiRow rowHeight={tableConfig.rowHeight || 40} diff --git a/src/renderer/features/now-playing/components/sidebar-play-queue.tsx b/src/renderer/features/now-playing/components/sidebar-play-queue.tsx index 89c165c2..58a74649 100644 --- a/src/renderer/features/now-playing/components/sidebar-play-queue.tsx +++ b/src/renderer/features/now-playing/components/sidebar-play-queue.tsx @@ -1,6 +1,6 @@ import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact'; -import { useRef } from 'react'; +import { useRef, useState } from 'react'; import { PlayQueueListControls } from './play-queue-list-controls'; @@ -13,15 +13,21 @@ import { Platform } from '/@/shared/types/types'; export const SidebarPlayQueue = () => { const queueRef = useRef }>(null); + const [search, setSearch] = useState(undefined); const { windowBarStyle } = useWindowSettings(); const isWeb = windowBarStyle === Platform.WEB; return ( - + - + ); }; diff --git a/src/renderer/features/now-playing/routes/now-playing-route.tsx b/src/renderer/features/now-playing/routes/now-playing-route.tsx index f20f16ad..2df08b1b 100644 --- a/src/renderer/features/now-playing/routes/now-playing-route.tsx +++ b/src/renderer/features/now-playing/routes/now-playing-route.tsx @@ -1,7 +1,7 @@ import type { Song } from '/@/shared/types/domain-types'; import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact'; -import { useRef } from 'react'; +import { useRef, useState } from 'react'; import { VirtualGridContainer } from '/@/renderer/components/virtual-grid'; import { NowPlayingHeader } from '/@/renderer/features/now-playing/components/now-playing-header'; @@ -11,13 +11,19 @@ import { AnimatedPage } from '/@/renderer/features/shared'; const NowPlayingRoute = () => { const queueRef = useRef }>(null); + const [search, setSearch] = useState(undefined); return ( - - + + ); 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 5bb8d267..a8722878 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,7 +1,6 @@ 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'; @@ -19,6 +18,7 @@ import { usePlaylistSongList } from '/@/renderer/features/playlists/queries/play import { AnimatedPage } from '/@/renderer/features/shared'; import { AppRoute } from '/@/renderer/router/routes'; import { useCurrentServer, usePlaylistDetailStore } from '/@/renderer/store'; +import { searchSongs } from '/@/renderer/utils/search-songs'; import { ActionIcon } from '/@/shared/components/action-icon/action-icon'; import { Box } from '/@/shared/components/box/box'; import { Group } from '/@/shared/components/group/group'; @@ -162,20 +162,7 @@ const PlaylistDetailSongListRoute = () => { 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); + items = searchSongs(items, searchTerm); } const sortBy = page?.table.id[playlistId]?.filter?.sortBy || SongListSort.ID; diff --git a/src/renderer/utils/search-songs.ts b/src/renderer/utils/search-songs.ts new file mode 100644 index 00000000..0ab83a7c --- /dev/null +++ b/src/renderer/utils/search-songs.ts @@ -0,0 +1,20 @@ +import Fuse from 'fuse.js'; + +import { Song } from '/@/shared/types/domain-types'; + +export const searchSongs = (songs: Song[], searchTerm: string) => { + const fuse = new Fuse(songs, { + fieldNormWeight: 1, + ignoreLocation: true, + keys: [ + 'name', + 'album', + { + getFn: (song) => song.artists.map((artist) => artist.name), + name: 'artist', + }, + ], + threshold: 0, + }); + return fuse.search(searchTerm).map((item) => item.item); +};