From ddd3986f0d711ce24b4d947efd1cb0bda2217cc8 Mon Sep 17 00:00:00 2001 From: angel Date: Thu, 19 Jun 2025 22:49:46 +0000 Subject: [PATCH] feat: implement infinite scroll and load more functionality for albums in BrowsePage --- app/browse/page.tsx | 99 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 90 insertions(+), 9 deletions(-) diff --git a/app/browse/page.tsx b/app/browse/page.tsx index 2feaf63..9b0595d 100644 --- a/app/browse/page.tsx +++ b/app/browse/page.tsx @@ -1,18 +1,81 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ 'use client'; +import { useState, useEffect } from 'react'; import { ScrollArea, ScrollBar } from '@/components/ui/scroll-area'; import { Separator } from '@/components/ui/separator'; +import { Button } from '@/components/ui/button'; import { Tabs, TabsContent } from '@/components/ui/tabs'; import { AlbumArtwork } from '@/app/components/album-artwork'; import { ArtistIcon } from '@/app/components/artist-icon'; import { useNavidrome } from '@/app/components/NavidromeContext'; +import { getNavidromeAPI, Album } from '@/lib/navidrome'; import Loading from '@/app/components/loading'; export default function BrowsePage() { - const { albums, artists, isLoading } = useNavidrome(); + const { artists, isLoading: contextLoading } = useNavidrome(); + const [albums, setAlbums] = useState([]); + const [currentPage, setCurrentPage] = useState(0); + const [isLoadingAlbums, setIsLoadingAlbums] = useState(false); + const [hasMoreAlbums, setHasMoreAlbums] = useState(true); + const albumsPerPage = 84; - if (isLoading) { + const api = getNavidromeAPI(); + + const loadAlbums = async (page: number, append: boolean = false) => { + try { + setIsLoadingAlbums(true); + const offset = page * albumsPerPage; + const newAlbums = await api.getAlbums('newest', albumsPerPage, offset); + + if (append) { + setAlbums(prev => [...prev, ...newAlbums]); + } else { + setAlbums(newAlbums); + } + + // If we got fewer albums than requested, we've reached the end + setHasMoreAlbums(newAlbums.length === albumsPerPage); + } catch (error) { + console.error('Failed to load albums:', error); + } finally { + setIsLoadingAlbums(false); + } + }; + + useEffect(() => { + loadAlbums(0); + }, []); + + // Infinite scroll handler + useEffect(() => { + const handleScroll = (e: Event) => { + const target = e.target as HTMLElement; + if (!target || isLoadingAlbums || !hasMoreAlbums) return; + + const { scrollTop, scrollHeight, clientHeight } = target; + const threshold = 200; // Load more when 200px from bottom + + if (scrollHeight - scrollTop - clientHeight < threshold) { + loadMore(); + } + }; + + const scrollArea = document.querySelector('[data-radix-scroll-area-viewport]'); + if (scrollArea) { + scrollArea.addEventListener('scroll', handleScroll); + return () => scrollArea.removeEventListener('scroll', handleScroll); + } + }, [isLoadingAlbums, hasMoreAlbums, currentPage]); + + const loadMore = () => { + if (isLoadingAlbums || !hasMoreAlbums) return; + const nextPage = currentPage + 1; + setCurrentPage(nextPage); + loadAlbums(nextPage, true); + }; + + if (contextLoading) { return ; } @@ -52,10 +115,10 @@ export default function BrowsePage() {

- Browse + Browse Albums

- Browse the full collection of music available. + Browse the full collection of albums ({albums.length} loaded).

@@ -63,20 +126,38 @@ export default function BrowsePage() {
-
+
{albums.map((album) => ( ))}
+ {hasMoreAlbums && ( +
+ +
+ )} + {!hasMoreAlbums && albums.length > 0 && ( +
+

+ All albums loaded ({albums.length} total) +

+
+ )}
- +