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,
};
}