feat: enhance folder viewer with library fetching and error handling

- Added functionality to fetch libraries from the API and manage library paths within the folder viewer.
- Implemented error handling for directory loading, displaying user-friendly messages when errors occur.
- Updated breadcrumb navigation to reflect the current library context and improved path management.
- Enhanced UI to show error states and provide a retry option for loading directory contents.
This commit is contained in:
tigeren 2025-08-27 16:16:28 +00:00
parent dab3ec5f84
commit 90fe3c8fb9
1 changed files with 87 additions and 13 deletions

View File

@ -40,21 +40,44 @@ const FolderViewerPage = () => {
const [selectedPhoto, setSelectedPhoto] = useState<FileSystemItem | null>(null);
const [isPhotoViewerOpen, setIsPhotoViewerOpen] = useState(false);
const [currentPhotoIndex, setCurrentPhotoIndex] = useState(0);
const [libraries, setLibraries] = useState<{id: number, path: string}[]>([]);
const [error, setError] = useState<string>('');
useEffect(() => {
fetchLibraries();
if (path) {
fetchItems(path);
}
}, [path]);
const fetchLibraries = async () => {
try {
const res = await fetch('/api/libraries');
const data = await res.json();
setLibraries(data);
} catch (error) {
console.error('Error fetching libraries:', error);
}
};
const fetchItems = async (currentPath: string) => {
setLoading(true);
setError('');
try {
const res = await fetch(`/api/files?path=${currentPath}`);
const res = await fetch(`/api/files?path=${encodeURIComponent(currentPath)}`);
const data = await res.json();
if (!Array.isArray(data)) {
console.error('Invalid response format:', data);
setItems([]);
setError('Invalid response from server');
} else {
setItems(data);
}
} catch (error) {
console.error('Error fetching items:', error);
setItems([]);
setError('Failed to load directory contents');
} finally {
setLoading(false);
}
@ -101,19 +124,38 @@ const FolderViewerPage = () => {
return `${truncatedDir}/${filename}`;
};
const getLibraryRoot = (currentPath: string): string | null => {
if (!currentPath || libraries.length === 0) return null;
// Find the library root that matches the current path
for (const library of libraries) {
if (currentPath.startsWith(library.path)) {
return library.path;
}
}
return null;
};
const getBreadcrumbs = (currentPath: string): BreadcrumbItem[] => {
if (!currentPath) return [];
const pathParts = currentPath.split('/').filter(part => part.length > 0);
const libraryRoot = getLibraryRoot(currentPath);
if (!libraryRoot) return [{ name: 'Unknown', path: currentPath }];
// Get the relative path from library root
const relativePath = currentPath.substring(libraryRoot.length);
const pathParts = relativePath.split('/').filter(part => part.length > 0);
const breadcrumbs: BreadcrumbItem[] = [];
// Add root/home
breadcrumbs.push({ name: 'Home', path: '' });
// Use library name as root
const libraryName = libraryRoot.split('/').pop() || libraryRoot;
breadcrumbs.push({ name: libraryName, path: libraryRoot });
// Build breadcrumbs for each path level
let accumulatedPath = '';
// Build breadcrumbs for each path level within the library
let accumulatedPath = libraryRoot;
pathParts.forEach((part, index) => {
accumulatedPath += (accumulatedPath ? '/' : '') + part;
accumulatedPath += '/' + part;
breadcrumbs.push({
name: part,
path: accumulatedPath
@ -125,8 +167,18 @@ const FolderViewerPage = () => {
const getParentPath = (currentPath: string): string => {
if (!currentPath) return '';
const libraryRoot = getLibraryRoot(currentPath);
if (!libraryRoot) return '';
// Don't allow navigation above library root
if (currentPath === libraryRoot) return '';
const pathParts = currentPath.split('/').filter(part => part.length > 0);
if (pathParts.length <= 1) return '';
const libraryParts = libraryRoot.split('/').filter(part => part.length > 0);
if (pathParts.length <= libraryParts.length) return '';
return pathParts.slice(0, -1).join('/');
};
@ -252,6 +304,7 @@ const FolderViewerPage = () => {
onClick={handleBackClick}
className="text-zinc-400 hover:text-white hover:bg-zinc-800/50 transition-colors"
disabled={!getParentPath(path || '')}
title={getParentPath(path || '') ? 'Go to parent directory' : 'Already at library root'}
>
<ChevronLeft className="h-4 w-4 mr-2" />
Back
@ -271,11 +324,12 @@ const FolderViewerPage = () => {
: 'hover:underline cursor-pointer'
}`}
disabled={index === getBreadcrumbs(path || '').length - 1}
title={breadcrumb.path}
>
{breadcrumb.name === 'Home' ? (
{index === 0 ? (
<div className="flex items-center gap-1">
<Home className="h-3 w-3" />
<span>Home</span>
<span>{breadcrumb.name}</span>
</div>
) : (
breadcrumb.name
@ -292,7 +346,27 @@ const FolderViewerPage = () => {
</div>
<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">
{items.map((item) => (
{error && (
<div className="col-span-full text-center py-12">
<div className="max-w-sm mx-auto">
<div className="w-16 h-16 bg-red-900/20 rounded-2xl flex items-center justify-center mx-auto mb-4">
<Folder className="h-8 w-8 text-red-500" />
</div>
<h3 className="text-lg font-semibold text-red-400 mb-2">Error Loading Directory</h3>
<p className="text-sm text-red-500 mb-4">{error}</p>
<Button
onClick={() => path && fetchItems(path)}
variant="outline"
size="sm"
className="border-red-500 text-red-500 hover:bg-red-500/10"
>
Try Again
</Button>
</div>
</div>
)}
{!error && Array.isArray(items) && items.map((item) => (
<div key={item.name}
className={`group relative bg-white dark:bg-slate-800 rounded-xl shadow-sm hover:shadow-lg transition-all duration-300 hover:-translate-y-1 overflow-hidden ${(item.type === 'video' || item.type === 'photo') ? 'cursor-pointer' : ''}`}>
<Link href={item.isDirectory ? `/folder-viewer?path=${item.path}` : '#'}
@ -397,7 +471,7 @@ const FolderViewerPage = () => {
))}
</div>
{items.length === 0 && !loading && (
{items.length === 0 && !loading && !error && (
<div className="text-center py-20">
<div className="max-w-sm mx-auto">
<div className="w-16 h-16 bg-slate-100 dark:bg-slate-800 rounded-2xl flex items-center justify-center mx-auto mb-4">