116 lines
3.7 KiB
TypeScript
116 lines
3.7 KiB
TypeScript
'use client';
|
|
|
|
import { useState, useEffect, useCallback } from 'react';
|
|
import { Album } from '@/lib/navidrome';
|
|
import { useNavidrome } from '@/app/components/NavidromeContext';
|
|
|
|
const INITIAL_BATCH_SIZE = 24; // Initial number of albums to load
|
|
const BATCH_SIZE = 24; // Number of albums to load in each batch
|
|
const SCROLL_THRESHOLD = 200; // Pixels from bottom before loading more
|
|
|
|
export type AlbumSortOption = 'alphabeticalByName' | 'newest' | 'recent' | 'frequent' | 'random' | 'alphabeticalByArtist' | 'starred' | 'highest';
|
|
|
|
export function useProgressiveAlbumLoading(sortBy: AlbumSortOption = 'alphabeticalByName') {
|
|
const [albums, setAlbums] = useState<Album[]>([]);
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
const [hasMore, setHasMore] = useState(true);
|
|
const [currentOffset, setCurrentOffset] = useState(0);
|
|
const { api } = useNavidrome();
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
// Load initial batch
|
|
useEffect(() => {
|
|
loadInitialBatch();
|
|
}, [sortBy]);
|
|
|
|
// Cleanup when sort changes
|
|
useEffect(() => {
|
|
return () => {
|
|
setAlbums([]);
|
|
setCurrentOffset(0);
|
|
setHasMore(true);
|
|
};
|
|
}, [sortBy]);
|
|
|
|
// Load initial batch of albums
|
|
const loadInitialBatch = useCallback(async () => {
|
|
if (!api) return;
|
|
|
|
setIsLoading(true);
|
|
setError(null);
|
|
|
|
try {
|
|
const albumData = await api.getAlbums(sortBy, INITIAL_BATCH_SIZE, 0);
|
|
setAlbums(albumData);
|
|
setCurrentOffset(albumData.length);
|
|
// Assume there are more unless we got fewer than we asked for
|
|
setHasMore(albumData.length >= INITIAL_BATCH_SIZE);
|
|
} catch (err) {
|
|
console.error('Failed to load initial albums batch:', err);
|
|
setError(err instanceof Error ? err.message : 'Unknown error loading albums');
|
|
setAlbums([]);
|
|
setHasMore(false);
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
}, [api, sortBy]);
|
|
|
|
// Load more albums when scrolling
|
|
const loadMoreAlbums = useCallback(async () => {
|
|
if (isLoading || !hasMore || !api) return;
|
|
|
|
setIsLoading(true);
|
|
|
|
try {
|
|
const newAlbums = await api.getAlbums(sortBy, BATCH_SIZE, currentOffset);
|
|
setAlbums(prev => [...prev, ...newAlbums]);
|
|
setCurrentOffset(currentOffset + newAlbums.length);
|
|
// If we get fewer albums than we asked for, we've reached the end
|
|
setHasMore(newAlbums.length >= BATCH_SIZE);
|
|
} catch (err) {
|
|
console.error('Failed to load more albums:', err);
|
|
setError(err instanceof Error ? err.message : 'Unknown error loading more albums');
|
|
setHasMore(false);
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
}, [api, currentOffset, isLoading, hasMore, sortBy]);
|
|
|
|
// Manual refresh (useful for pull-to-refresh functionality)
|
|
const refreshAlbums = useCallback(() => {
|
|
setAlbums([]);
|
|
setCurrentOffset(0);
|
|
setHasMore(true);
|
|
loadInitialBatch();
|
|
}, [loadInitialBatch]);
|
|
|
|
// Setup scroll listener
|
|
useEffect(() => {
|
|
const handleScroll = () => {
|
|
// Don't trigger if already loading
|
|
if (isLoading || !hasMore) return;
|
|
|
|
// Check if we're near the bottom
|
|
const scrollHeight = document.documentElement.scrollHeight;
|
|
const currentScroll = window.innerHeight + document.documentElement.scrollTop;
|
|
|
|
if (scrollHeight - currentScroll <= SCROLL_THRESHOLD) {
|
|
loadMoreAlbums();
|
|
}
|
|
};
|
|
|
|
window.addEventListener('scroll', handleScroll);
|
|
return () => window.removeEventListener('scroll', handleScroll);
|
|
}, [isLoading, hasMore, loadMoreAlbums]);
|
|
|
|
return {
|
|
albums,
|
|
isLoading,
|
|
hasMore,
|
|
loadMoreAlbums,
|
|
refreshAlbums,
|
|
error,
|
|
resetAndLoad: refreshAlbums // Alias for consistency
|
|
};
|
|
}
|