/** * Lazy Discovery Section Component * Implements lazy loading and virtualization for discovery sections */ import React, { useState, useEffect, useRef, useCallback } from 'react' import { MediaList } from './MediaList' import { MediaItem } from '../api/jellyfin' interface LazyDiscoverySectionProps { title: string description?: string items: MediaItem[] type: 'song' | 'album' onDiscoverDownload: (item: MediaItem) => void seeMoreUrl?: string onSeeMore?: () => void | Promise initialLoadCount?: number loadMoreCount?: number } export const LazyDiscoverySection: React.FC = ({ title, description, items, type, onDiscoverDownload, seeMoreUrl, onSeeMore, initialLoadCount = 12, loadMoreCount = 8 }) => { const [visibleItems, setVisibleItems] = useState([]) const [isVisible, setIsVisible] = useState(false) const [hasLoadedMore, setHasLoadedMore] = useState(false) const sectionRef = useRef(null) const observerRef = useRef(null) // Initialize intersection observer for lazy loading useEffect(() => { if (!sectionRef.current) return observerRef.current = new IntersectionObserver( (entries) => { entries.forEach((entry) => { if (entry.isIntersecting && !isVisible) { setIsVisible(true) setVisibleItems(items.slice(0, initialLoadCount)) } }) }, { rootMargin: '100px', // Start loading 100px before section comes into view threshold: 0.1 } ) observerRef.current.observe(sectionRef.current) return () => { if (observerRef.current) { observerRef.current.disconnect() } } }, [items, initialLoadCount, isVisible]) // Load more items when scrolling near the end const loadMoreItems = useCallback(() => { if (hasLoadedMore || visibleItems.length >= items.length) return const nextBatch = items.slice(0, visibleItems.length + loadMoreCount) setVisibleItems(nextBatch) setHasLoadedMore(true) }, [items, visibleItems.length, loadMoreCount, hasLoadedMore]) // Intersection observer for load more functionality const loadMoreRef = useRef(null) useEffect(() => { if (!loadMoreRef.current || !isVisible || hasLoadedMore) return const loadMoreObserver = new IntersectionObserver( (entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { loadMoreItems() } }) }, { threshold: 0.1 } ) loadMoreObserver.observe(loadMoreRef.current) return () => loadMoreObserver.disconnect() }, [isVisible, hasLoadedMore, loadMoreItems]) // Don't render anything until section is visible if (!isVisible) { return (
{title}
{description &&
{description}
}
Loading {title}...
) } return (
{title}
{description &&
{description}
}
{(onSeeMore || seeMoreUrl) && (
See more ({items.length})
)}
{/* Load more trigger */} {visibleItems.length < items.length && !hasLoadedMore && (
)} {/* Show load more button if there are more items */} {hasLoadedMore && visibleItems.length < items.length && (
)}
) }