diff --git a/src/features/search/components/LiveSearch.tsx b/src/features/search/components/LiveSearch.tsx index c40b4fca..bef9bf80 100644 --- a/src/features/search/components/LiveSearch.tsx +++ b/src/features/search/components/LiveSearch.tsx @@ -1,5 +1,5 @@ import { subscribeLibrarySyncIdle, subscribeLibrarySyncProgress } from '@/lib/api/library'; -import type { SearchResults, SubsonicArtist } from '@/lib/api/subsonicTypes'; +import type { SearchResults } from '@/lib/api/subsonicTypes'; import { songToTrack } from '@/lib/media/songToTrack'; import { LIVE_SEARCH_DEBOUNCE_NETWORK_MS, @@ -29,13 +29,11 @@ import { useAuthStore } from '@/store/authStore'; import { useLibraryIndexStore } from '@/store/libraryIndexStore'; import { useTranslation } from 'react-i18next'; import { albumArtistDisplayName } from '@/features/album'; -import { FETCH_QUEUE_BIAS_SEARCH_ARTIST_OVER_ALBUM } from '@/ui/CachedImage'; -import type { SubsonicSong } from '@/lib/api/subsonicTypes'; -import { AlbumCoverArtImage } from '@/cover/AlbumCoverArtImage'; -import { ArtistCoverArtImage } from '@/cover/ArtistCoverArtImage'; -import { CoverArtImage } from '@/cover/CoverArtImage'; -import { COVER_DENSE_SEARCH_CSS_PX } from '@/cover/layoutSizes'; -import { albumCoverRef } from '@/cover/ref'; +import { + LiveSearchAlbumThumb, + LiveSearchSongThumb, + LiveSearchArtistThumb, +} from '@/features/search/components/liveSearchResultThumbs'; import { showToast } from '@/lib/dom/toast'; import { useShareSearch } from '@/features/search/hooks/useShareSearch'; import ShareSearchResults from '@/features/search/components/ShareSearchResults'; @@ -58,69 +56,6 @@ import { resolveIndexKey } from '@/lib/server/serverIndexKey'; type LiveSearchSource = 'local' | 'network'; -function LiveSearchAlbumThumb({ albumId, coverArt }: { albumId: string; coverArt: string }) { - return ( - - ); -} - -function LiveSearchSongThumb({ song }: { song: Pick }) { - // Search results carry the per-track `mf-…` coverArt id, which the cover - // pipeline fails to resolve and the thumbnail goes blank. The album-scoped - // `al-_0` id is what actually loads (verified in the RC1 blank-thumb - // investigation), and a song's search thumbnail is its album cover anyway — - // so fetch the album cover from the albumId. Falls back to a music glyph when - // there is no album to key on. - const albumId = song.albumId?.trim(); - const coverRef = React.useMemo( - () => (albumId ? albumCoverRef(albumId, `al-${albumId}_0`) : undefined), - [albumId], - ); - if (!coverRef) return
; - return ( - - ); -} - -function LiveSearchArtistThumb({ artist }: { artist: Pick }) { - const [failed, setFailed] = useState(false); - // React Compiler set-state-in-effect rule: state set from an async result resolved in this effect. - // eslint-disable-next-line react-hooks/set-state-in-effect - useEffect(() => { setFailed(false); }, [artist.id, artist.coverArt]); - if (failed) return
; - return ( - setFailed(true)} - /> - ); -} - export default function LiveSearch() { const { t } = useTranslation(); const query = useLiveSearchScopeStore(s => s.query); diff --git a/src/features/search/components/liveSearchResultThumbs.tsx b/src/features/search/components/liveSearchResultThumbs.tsx new file mode 100644 index 00000000..613e3245 --- /dev/null +++ b/src/features/search/components/liveSearchResultThumbs.tsx @@ -0,0 +1,72 @@ +import React, { useState, useEffect } from 'react'; +import { Music, Users } from 'lucide-react'; +import type { SubsonicSong, SubsonicArtist } from '@/lib/api/subsonicTypes'; +import { AlbumCoverArtImage } from '@/cover/AlbumCoverArtImage'; +import { ArtistCoverArtImage } from '@/cover/ArtistCoverArtImage'; +import { CoverArtImage } from '@/cover/CoverArtImage'; +import { COVER_DENSE_SEARCH_CSS_PX } from '@/cover/layoutSizes'; +import { albumCoverRef } from '@/cover/ref'; +import { FETCH_QUEUE_BIAS_SEARCH_ARTIST_OVER_ALBUM } from '@/ui/CachedImage'; + +export function LiveSearchAlbumThumb({ albumId, coverArt }: { albumId: string; coverArt: string }) { + return ( + + ); +} + +export function LiveSearchSongThumb({ song }: { song: Pick }) { + // Search results carry the per-track `mf-…` coverArt id, which the cover + // pipeline fails to resolve and the thumbnail goes blank. The album-scoped + // `al-_0` id is what actually loads (verified in the RC1 blank-thumb + // investigation), and a song's search thumbnail is its album cover anyway — + // so fetch the album cover from the albumId. Falls back to a music glyph when + // there is no album to key on. + const albumId = song.albumId?.trim(); + const coverRef = React.useMemo( + () => (albumId ? albumCoverRef(albumId, `al-${albumId}_0`) : undefined), + [albumId], + ); + if (!coverRef) return
; + return ( + + ); +} + +export function LiveSearchArtistThumb({ artist }: { artist: Pick }) { + const [failed, setFailed] = useState(false); + // React Compiler set-state-in-effect rule: state set from an async result resolved in this effect. + // eslint-disable-next-line react-hooks/set-state-in-effect + useEffect(() => { setFailed(false); }, [artist.id, artist.coverArt]); + if (failed) return
; + return ( + setFailed(true)} + /> + ); +}