feat: fix menubar, add lazy loading, improve image quality, limit search results, filter browse artists

This commit is contained in:
2026-01-25 01:16:17 +00:00
committed by GitHub
parent f77a280e34
commit 0f719ab3d5
6 changed files with 29 additions and 19 deletions

View File

@@ -124,13 +124,13 @@ export default function AlbumPage() {
// Dynamic cover art URLs based on image size // Dynamic cover art URLs based on image size
const getMobileCoverArtUrl = () => { const getMobileCoverArtUrl = () => {
return album.coverArt && api return album.coverArt && api
? api.getCoverArtUrl(album.coverArt, 280) ? api.getCoverArtUrl(album.coverArt, 600)
: '/default-user.jpg'; : '/default-user.jpg';
}; };
const getDesktopCoverArtUrl = () => { const getDesktopCoverArtUrl = () => {
return album.coverArt && api return album.coverArt && api
? api.getCoverArtUrl(album.coverArt, 300) ? api.getCoverArtUrl(album.coverArt, 600)
: '/default-user.jpg'; : '/default-user.jpg';
}; };
@@ -146,8 +146,8 @@ export default function AlbumPage() {
<Image <Image
src={getMobileCoverArtUrl()} src={getMobileCoverArtUrl()}
alt={album.name} alt={album.name}
width={280} width={600}
height={280} height={600}
className="rounded-md shadow-lg" className="rounded-md shadow-lg"
/> />
</div> </div>
@@ -182,8 +182,8 @@ export default function AlbumPage() {
<Image <Image
src={getDesktopCoverArtUrl()} src={getDesktopCoverArtUrl()}
alt={album.name} alt={album.name}
width={300} width={600}
height={300} height={600}
className="rounded-md" className="rounded-md"
/> />
<div className="space-y-2"> <div className="space-y-2">

View File

@@ -19,7 +19,9 @@ import Loading from '@/app/components/loading';
import { useInView } from 'react-intersection-observer'; import { useInView } from 'react-intersection-observer';
export default function BrowsePage() { export default function BrowsePage() {
const { artists, isLoading: contextLoading } = useNavidrome(); const { artists: allArtists, isLoading: contextLoading } = useNavidrome();
// Filter to only show album artists (artists with at least one album)
const artists = allArtists.filter(artist => artist.albumCount && artist.albumCount > 0);
const { shuffleAllAlbums } = useAudioPlayer(); const { shuffleAllAlbums } = useAudioPlayer();
// Use our progressive loading hook // Use our progressive loading hook
@@ -78,12 +80,13 @@ export default function BrowsePage() {
<div className="relative"> <div className="relative">
<ScrollArea> <ScrollArea>
<div className="flex space-x-4 pb-4"> <div className="flex space-x-4 pb-4">
{artists.map((artist) => ( {artists.map((artist, index) => (
<ArtistIcon <ArtistIcon
key={artist.id} key={artist.id}
artist={artist} artist={artist}
className="shrink-0 overflow-hidden" className="shrink-0 overflow-hidden"
size={190} size={190}
loading={index < 10 ? 'eager' : 'lazy'}
/> />
))} ))}
</div> </div>
@@ -110,7 +113,7 @@ export default function BrowsePage() {
<ScrollArea className="h-full"> <ScrollArea className="h-full">
<div className="h-full overflow-y-auto"> <div className="h-full overflow-y-auto">
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 2xl:grid-cols-7 gap-4 p-4 pb-8"> <div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 2xl:grid-cols-7 gap-4 p-4 pb-8">
{albums.map((album) => ( {albums.map((album, index) => (
<AlbumArtwork <AlbumArtwork
key={album.id} key={album.id}
album={album} album={album}
@@ -118,6 +121,7 @@ export default function BrowsePage() {
aspectRatio="square" aspectRatio="square"
width={200} width={200}
height={200} height={200}
loading={index < 20 ? 'eager' : 'lazy'}
/> />
))} ))}
</div> </div>

View File

@@ -36,6 +36,7 @@ interface AlbumArtworkProps extends Omit<
aspectRatio?: "portrait" | "square" aspectRatio?: "portrait" | "square"
width?: number width?: number
height?: number height?: number
loading?: 'eager' | 'lazy'
} }
export function AlbumArtwork({ export function AlbumArtwork({
@@ -43,6 +44,7 @@ export function AlbumArtwork({
aspectRatio = "portrait", aspectRatio = "portrait",
width, width,
height, height,
loading = 'lazy',
className, className,
...props ...props
}: AlbumArtworkProps) { }: AlbumArtworkProps) {
@@ -160,7 +162,7 @@ export function AlbumArtwork({
onLoad={handleImageLoad} onLoad={handleImageLoad}
onError={handleImageError} onError={handleImageError}
priority={false} priority={false}
loading="lazy" loading={loading}
/> />
) : ( ) : (
<div className="w-full h-full bg-muted rounded flex items-center justify-center"> <div className="w-full h-full bg-muted rounded flex items-center justify-center">

View File

@@ -27,6 +27,7 @@ interface ArtistIconProps extends React.HTMLAttributes<HTMLDivElement> {
size?: number size?: number
imageOnly?: boolean imageOnly?: boolean
responsive?: boolean responsive?: boolean
loading?: 'eager' | 'lazy'
} }
export function ArtistIcon({ export function ArtistIcon({
@@ -34,6 +35,7 @@ export function ArtistIcon({
size = 150, size = 150,
imageOnly = false, imageOnly = false,
responsive = false, responsive = false,
loading = 'lazy',
className, className,
...props ...props
}: ArtistIconProps) { }: ArtistIconProps) {
@@ -77,6 +79,7 @@ export function ArtistIcon({
width={size} width={size}
height={size} height={size}
className="w-full h-full object-cover transition-all hover:scale-105" className="w-full h-full object-cover transition-all hover:scale-105"
loading={loading}
/> />
</div> </div>
); );
@@ -116,6 +119,7 @@ export function ArtistIcon({
} }
)} )}
className={isResponsive ? "object-cover" : "object-cover w-full h-full"} className={isResponsive ? "object-cover" : "object-cover w-full h-full"}
loading={loading}
/> />
</div> </div>
</div> </div>

View File

@@ -191,11 +191,8 @@ export function Menu({ toggleSidebar, isSidebarVisible, toggleStatusBar, isStatu
<MenubarMenu> <MenubarMenu>
<MenubarTrigger className="relative">File</MenubarTrigger> <MenubarTrigger className="relative">File</MenubarTrigger>
<MenubarContent> <MenubarContent>
<MenubarSub> <MenubarItem onClick={() => router.push('/library/playlists')}>
<MenubarSubTrigger>New</MenubarSubTrigger> View Playlists
<MenubarSubContent className="w-[230px]">
<MenubarItem>
Playlist <MenubarShortcut>N</MenubarShortcut>
</MenubarItem> </MenubarItem>
<MenubarItem disabled> <MenubarItem disabled>
Playlist from Selection <MenubarShortcut>N</MenubarShortcut> Playlist from Selection <MenubarShortcut>N</MenubarShortcut>
@@ -205,8 +202,6 @@ export function Menu({ toggleSidebar, isSidebarVisible, toggleStatusBar, isStatu
</MenubarItem> </MenubarItem>
<MenubarItem>Playlist Folder</MenubarItem> <MenubarItem>Playlist Folder</MenubarItem>
<MenubarItem disabled>Genius Playlist</MenubarItem> <MenubarItem disabled>Genius Playlist</MenubarItem>
</MenubarSubContent>
</MenubarSub>
<MenubarItem> <MenubarItem>
Open Stream URL <MenubarShortcut>U</MenubarShortcut> Open Stream URL <MenubarShortcut>U</MenubarShortcut>
</MenubarItem> </MenubarItem>
@@ -386,7 +381,7 @@ export function Menu({ toggleSidebar, isSidebarVisible, toggleStatusBar, isStatu
) : navidromeUrl ? ( ) : navidromeUrl ? (
navidromeUrl navidromeUrl
) : ( ) : (
<span className="italic text-gray-400">Not set</span> <span className="italic text-gray-400">Auto-configured</span>
)} )}
</span> </span>
</div> </div>

View File

@@ -35,7 +35,12 @@ export default function SearchPage() {
try { try {
setIsSearching(true); setIsSearching(true);
const results = await search2(query); const results = await search2(query);
setSearchResults(results); // Limit results to 5 of each type
setSearchResults({
artists: results.artists.slice(0, 5),
albums: results.albums.slice(0, 5),
songs: results.songs.slice(0, 5)
});
} catch (error) { } catch (error) {
console.error('Search failed:', error); console.error('Search failed:', error);
setSearchResults({ artists: [], albums: [], songs: [] }); setSearchResults({ artists: [], albums: [], songs: [] });