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:
parent
dab3ec5f84
commit
90fe3c8fb9
|
|
@ -40,21 +40,44 @@ const FolderViewerPage = () => {
|
||||||
const [selectedPhoto, setSelectedPhoto] = useState<FileSystemItem | null>(null);
|
const [selectedPhoto, setSelectedPhoto] = useState<FileSystemItem | null>(null);
|
||||||
const [isPhotoViewerOpen, setIsPhotoViewerOpen] = useState(false);
|
const [isPhotoViewerOpen, setIsPhotoViewerOpen] = useState(false);
|
||||||
const [currentPhotoIndex, setCurrentPhotoIndex] = useState(0);
|
const [currentPhotoIndex, setCurrentPhotoIndex] = useState(0);
|
||||||
|
const [libraries, setLibraries] = useState<{id: number, path: string}[]>([]);
|
||||||
|
const [error, setError] = useState<string>('');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
fetchLibraries();
|
||||||
if (path) {
|
if (path) {
|
||||||
fetchItems(path);
|
fetchItems(path);
|
||||||
}
|
}
|
||||||
}, [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) => {
|
const fetchItems = async (currentPath: string) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
setError('');
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`/api/files?path=${currentPath}`);
|
const res = await fetch(`/api/files?path=${encodeURIComponent(currentPath)}`);
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
|
if (!Array.isArray(data)) {
|
||||||
|
console.error('Invalid response format:', data);
|
||||||
|
setItems([]);
|
||||||
|
setError('Invalid response from server');
|
||||||
|
} else {
|
||||||
setItems(data);
|
setItems(data);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching items:', error);
|
console.error('Error fetching items:', error);
|
||||||
|
setItems([]);
|
||||||
|
setError('Failed to load directory contents');
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|
@ -101,19 +124,38 @@ const FolderViewerPage = () => {
|
||||||
return `${truncatedDir}/${filename}`;
|
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[] => {
|
const getBreadcrumbs = (currentPath: string): BreadcrumbItem[] => {
|
||||||
if (!currentPath) return [];
|
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[] = [];
|
const breadcrumbs: BreadcrumbItem[] = [];
|
||||||
|
|
||||||
// Add root/home
|
// Use library name as root
|
||||||
breadcrumbs.push({ name: 'Home', path: '' });
|
const libraryName = libraryRoot.split('/').pop() || libraryRoot;
|
||||||
|
breadcrumbs.push({ name: libraryName, path: libraryRoot });
|
||||||
|
|
||||||
// Build breadcrumbs for each path level
|
// Build breadcrumbs for each path level within the library
|
||||||
let accumulatedPath = '';
|
let accumulatedPath = libraryRoot;
|
||||||
pathParts.forEach((part, index) => {
|
pathParts.forEach((part, index) => {
|
||||||
accumulatedPath += (accumulatedPath ? '/' : '') + part;
|
accumulatedPath += '/' + part;
|
||||||
breadcrumbs.push({
|
breadcrumbs.push({
|
||||||
name: part,
|
name: part,
|
||||||
path: accumulatedPath
|
path: accumulatedPath
|
||||||
|
|
@ -125,8 +167,18 @@ const FolderViewerPage = () => {
|
||||||
|
|
||||||
const getParentPath = (currentPath: string): string => {
|
const getParentPath = (currentPath: string): string => {
|
||||||
if (!currentPath) return '';
|
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);
|
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('/');
|
return pathParts.slice(0, -1).join('/');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -252,6 +304,7 @@ const FolderViewerPage = () => {
|
||||||
onClick={handleBackClick}
|
onClick={handleBackClick}
|
||||||
className="text-zinc-400 hover:text-white hover:bg-zinc-800/50 transition-colors"
|
className="text-zinc-400 hover:text-white hover:bg-zinc-800/50 transition-colors"
|
||||||
disabled={!getParentPath(path || '')}
|
disabled={!getParentPath(path || '')}
|
||||||
|
title={getParentPath(path || '') ? 'Go to parent directory' : 'Already at library root'}
|
||||||
>
|
>
|
||||||
<ChevronLeft className="h-4 w-4 mr-2" />
|
<ChevronLeft className="h-4 w-4 mr-2" />
|
||||||
Back
|
Back
|
||||||
|
|
@ -271,11 +324,12 @@ const FolderViewerPage = () => {
|
||||||
: 'hover:underline cursor-pointer'
|
: 'hover:underline cursor-pointer'
|
||||||
}`}
|
}`}
|
||||||
disabled={index === getBreadcrumbs(path || '').length - 1}
|
disabled={index === getBreadcrumbs(path || '').length - 1}
|
||||||
|
title={breadcrumb.path}
|
||||||
>
|
>
|
||||||
{breadcrumb.name === 'Home' ? (
|
{index === 0 ? (
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<Home className="h-3 w-3" />
|
<Home className="h-3 w-3" />
|
||||||
<span>Home</span>
|
<span>{breadcrumb.name}</span>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
breadcrumb.name
|
breadcrumb.name
|
||||||
|
|
@ -292,7 +346,27 @@ const FolderViewerPage = () => {
|
||||||
</div>
|
</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">
|
<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}
|
<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' : ''}`}>
|
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}` : '#'}
|
<Link href={item.isDirectory ? `/folder-viewer?path=${item.path}` : '#'}
|
||||||
|
|
@ -397,7 +471,7 @@ const FolderViewerPage = () => {
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{items.length === 0 && !loading && (
|
{items.length === 0 && !loading && !error && (
|
||||||
<div className="text-center py-20">
|
<div className="text-center py-20">
|
||||||
<div className="max-w-sm mx-auto">
|
<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">
|
<div className="w-16 h-16 bg-slate-100 dark:bg-slate-800 rounded-2xl flex items-center justify-center mx-auto mb-4">
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue