diff --git a/package-lock.json b/package-lock.json index c0ba4be..fea4599 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,8 @@ "react-dom": "^19.2.6", "react-i18next": "^17.0.8", "react-redux": "^9.3.0", - "react-router": "^7.16.0" + "react-router": "^7.16.0", + "use-debounce": "^10.1.1" }, "devDependencies": { "@rsbuild/core": "^2.0.7", @@ -4469,6 +4470,18 @@ } } }, + "node_modules/use-debounce": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/use-debounce/-/use-debounce-10.1.1.tgz", + "integrity": "sha512-kvds8BHR2k28cFsxW8k3nc/tRga2rs1RHYCqmmGqb90MEeE++oALwzh2COiuBLO1/QXiOuShXoSN2ZpWnMmvuQ==", + "license": "MIT", + "engines": { + "node": ">= 16.0.0" + }, + "peerDependencies": { + "react": "*" + } + }, "node_modules/use-sidecar": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", diff --git a/package.json b/package.json index 0192161..6575ca7 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,8 @@ "react-dom": "^19.2.6", "react-i18next": "^17.0.8", "react-redux": "^9.3.0", - "react-router": "^7.16.0" + "react-router": "^7.16.0", + "use-debounce": "^10.1.1" }, "devDependencies": { "@rsbuild/core": "^2.0.7", diff --git a/src/components/player/QueuePanel.tsx b/src/components/player/QueuePanel.tsx index a0f72a3..9dc0892 100644 --- a/src/components/player/QueuePanel.tsx +++ b/src/components/player/QueuePanel.tsx @@ -12,16 +12,24 @@ import { import { toggleQueue } from '../../store/slices/player'; import { openTrackInfo } from '../../store/slices/ui'; import { useResolvedQueueEntry } from '../../hooks/useResolvedQueueEntry'; +import { getCoverUrl, getTrackCoverUrl } from '../../api/endpoints/streaming'; export function QueuePanel() { const { t } = useTranslation(); const dispatch = useAppDispatch(); const queue = useAppSelector((s) => s.queue); + const token = useAppSelector((s) => s.auth.accessToken); const isOpen = useAppSelector((s) => s.player.isQueueOpen); const nowEntry = queue.currentIndex >= 0 ? queue.entries[queue.currentIndex] : undefined; const now = useResolvedQueueEntry(nowEntry); + const nowArtUrl = now + ? (getCoverUrl(now.albumArtUrl) ?? + (token && now.hasCover + ? getTrackCoverUrl(now.trackId, token, true) + : undefined)) + : undefined; const upNext = queue.entries .map((entry, index) => ({ entry, index })) .filter(({ index }) => index > queue.currentIndex); @@ -81,6 +89,7 @@ export function QueuePanel() { seed={now.albumTitle} size={44} label={now.albumTitle} + src={nowArtUrl} />
{now.title}
@@ -171,8 +180,14 @@ function QueueRow({ onRemove: () => void; }) { const { t } = useTranslation(); + const token = useAppSelector((s) => s.auth.accessToken); const resolved = useResolvedQueueEntry(entry); const albumTitle = resolved?.albumTitle ?? entry.albumTitle; + const artUrl = + getCoverUrl(resolved?.albumArtUrl) ?? + (token && resolved?.hasCover + ? getTrackCoverUrl(resolved.trackId, token, true) + : undefined); return (
- +
{resolved?.title ?? entry.title}
{resolved?.artistName ?? entry.artistName}
diff --git a/src/features/library/LibraryPage.tsx b/src/features/library/LibraryPage.tsx index 7d81896..1fcaa62 100644 --- a/src/features/library/LibraryPage.tsx +++ b/src/features/library/LibraryPage.tsx @@ -5,9 +5,9 @@ import { Tabs, TabsList, TabsContent, - SearchField, ScrollArea, Card, + TextField, } from '@olly/modern-sk'; import { useGetTracksQuery, @@ -23,6 +23,7 @@ import { setQueue } from '../../store/slices/queue'; import type { Track, Album, Artist } from '../../api/types'; import { getCoverUrl } from '../../api/endpoints/streaming'; import { formatDuration } from '../../lib/format'; +import { useDebounce } from 'use-debounce'; export function LibraryPage() { const { t } = useTranslation(); @@ -30,10 +31,17 @@ export function LibraryPage() { const dispatch = useAppDispatch(); const [tab, setTab] = useState('tracks'); const [search, setSearch] = useState(''); + const [debouncedSearch] = useDebounce(search, 300); - const tracksQuery = useGetTracksQuery(search ? { search } : undefined); - const albumsQuery = useGetAlbumsQuery(search ? { search } : undefined); - const artistsQuery = useGetArtistsQuery(search ? { search } : undefined); + const tracksQuery = useGetTracksQuery( + debouncedSearch ? { search } : undefined, + ); + const albumsQuery = useGetAlbumsQuery( + debouncedSearch ? { search } : undefined, + ); + const artistsQuery = useGetArtistsQuery( + debouncedSearch ? { search } : undefined, + ); const handlePlayAll = (tracks: Track[]) => { dispatch( @@ -68,11 +76,10 @@ export function LibraryPage() { {t('library.title')}
- setSearch(e.target.value)} placeholder={t('library.searchPlaceholder')} - icon="⌕" />
diff --git a/src/hooks/useResolvedQueueEntry.ts b/src/hooks/useResolvedQueueEntry.ts index 16c30af..330ebe2 100644 --- a/src/hooks/useResolvedQueueEntry.ts +++ b/src/hooks/useResolvedQueueEntry.ts @@ -9,6 +9,7 @@ export interface ResolvedQueueEntry { albumTitle: string; durationMs: number; hasCover: boolean; + albumArtUrl?: string; } /** @@ -33,5 +34,6 @@ export function useResolvedQueueEntry( albumTitle: data?.albumTitle ?? entry.albumTitle, durationMs: data?.durationMs ?? entry.durationMs, hasCover: data?.hasCover ?? false, + albumArtUrl: data?.albumArtUrl ?? entry.albumArtUrl, }; }