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 [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();
|
||||
setItems(data);
|
||||
|
||||
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">
|
||||
|
|
|
|||
Loading…
Reference in New Issue