diff --git a/src/components/HomeContent.tsx b/src/components/HomeContent.tsx index 138f3c3..6cc2be8 100644 --- a/src/components/HomeContent.tsx +++ b/src/components/HomeContent.tsx @@ -1,12 +1,8 @@ 'use client'; -import { Header } from '@/components/Header'; -import { DocumentUploader } from '@/components/documents/DocumentUploader'; import { DocumentList } from '@/components/doclist/DocumentList'; -import { DocumentListSkeleton } from '@/components/doclist/DocumentListSkeleton'; import { SettingsModal } from '@/components/SettingsModal'; import { UserMenu } from '@/components/auth/UserMenu'; -import { useDocuments } from '@/contexts/DocumentContext'; const Brand = () => (
@@ -26,31 +22,6 @@ const AppActions = () => ( ); export function HomeContent() { - const { pdfDocs, epubDocs, htmlDocs, isPDFLoading } = useDocuments(); - const totalDocs = (pdfDocs?.length || 0) + (epubDocs?.length || 0) + (htmlDocs?.length || 0); - - if (isPDFLoading) { - return ( -
-
} right={} /> -
- -
-
- ); - } - - if (totalDocs === 0) { - return ( -
-
} right={} /> -
- -
-
- ); - } - return (
} appActions={} /> diff --git a/src/components/doclist/DocumentList.tsx b/src/components/doclist/DocumentList.tsx index 0ccbe9e..663120e 100644 --- a/src/components/doclist/DocumentList.tsx +++ b/src/components/doclist/DocumentList.tsx @@ -409,17 +409,7 @@ function DocumentListInner({ brand, appActions }: DocumentListInnerProps) { [allDocuments], ); - if (isPDFLoading || isEPUBLoading || isHTMLLoading) { - return ; - } - - if (allDocuments.length === 0) { - return ( -
- -
- ); - } + const isLoading = isPDFLoading || isEPUBLoading || isHTMLLoading; const fallbackViewMode: ViewMode = viewMode === 'columns' && isNarrow ? 'list' : viewMode; @@ -473,7 +463,7 @@ function DocumentListInner({ brand, appActions }: DocumentListInnerProps) { sidebarOpen={sidebarOpen} onSidebarOpenChange={setSidebarOpen} > - {showHint && allDocuments.length > 1 && ( + {!isLoading && showHint && allDocuments.length > 1 && (

@@ -493,57 +483,67 @@ function DocumentListInner({ brand, appActions }: DocumentListInnerProps) {

)} - - {fallbackViewMode === 'icons' && ( - - )} - {fallbackViewMode === 'list' && ( - { - setSortBy(b); - setSortDirection(d); - }} - collapsedFolders={collapsedFolders} - onToggleCollapse={toggleFolderCollapse} - onDeleteFolder={handleDeleteFolder} - onDeleteDoc={handleDeleteDoc} - onDropOnFolder={handleDropOnFolder} - onMergeIntoFolder={handleMergeIntoFolder} - /> - )} - {fallbackViewMode === 'columns' && ( - - )} - {fallbackViewMode === 'gallery' && ( - - )} - + {isLoading ? ( +
+ +
+ ) : allDocuments.length === 0 ? ( +
+ +
+ ) : ( + + {fallbackViewMode === 'icons' && ( + + )} + {fallbackViewMode === 'list' && ( + { + setSortBy(b); + setSortDirection(d); + }} + collapsedFolders={collapsedFolders} + onToggleCollapse={toggleFolderCollapse} + onDeleteFolder={handleDeleteFolder} + onDeleteDoc={handleDeleteDoc} + onDropOnFolder={handleDropOnFolder} + onMergeIntoFolder={handleMergeIntoFolder} + /> + )} + {fallbackViewMode === 'columns' && ( + + )} + {fallbackViewMode === 'gallery' && ( + + )} + + )} = { + sm: 'grid-cols-3 sm:grid-cols-5 md:grid-cols-7 lg:grid-cols-8', + md: 'grid-cols-2 sm:grid-cols-4 md:grid-cols-5 lg:grid-cols-6', + lg: 'grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5', + xl: 'grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4', +}; +function IconsSkeleton({ iconSize }: { iconSize: IconSize }) { return ( -
-
-
-
-
-
-
- -
- {placeholders.map((_, index) => ( -
- {viewMode === 'grid' ? ( - <> -
-
-
-
-
- - ) : null} +
+
+ {Array.from({ length: 12 }).map((_, index) => ( +
+
+
+
+
+
))}
); } + +function ListSkeleton() { + return ( +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {Array.from({ length: 10 }).map((_, index) => ( +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ))} +
+
+ ); +} + +function ColumnsSkeleton() { + return ( +
+
+
+
+
+
+ {Array.from({ length: 11 }).map((_, rowIndex) => ( +
+ ))} +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ); +} + +function GallerySkeleton() { + return ( +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {Array.from({ length: 10 }).map((_, index) => ( +
+
+
+
+
+
+ ))} +
+
+
+ ); +} + +export function DocumentListSkeleton({ + viewMode = 'icons', + iconSize = 'md', +}: DocumentListSkeletonProps) { + let body; + if (viewMode === 'list') { + body = ; + } else if (viewMode === 'columns') { + body = ; + } else if (viewMode === 'gallery') { + body = ; + } else { + body = ; + } + + return ( +
+ {body} +
+ ); +} diff --git a/src/components/doclist/views/ColumnsView.tsx b/src/components/doclist/views/ColumnsView.tsx index 507278d..67b87ce 100644 --- a/src/components/doclist/views/ColumnsView.tsx +++ b/src/components/doclist/views/ColumnsView.tsx @@ -1,7 +1,7 @@ 'use client'; import Link from 'next/link'; -import { useEffect, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import { useDrag, useDrop } from 'react-dnd'; import type { DocumentListDocument, Folder } from '@/types/documents'; import { PDFIcon, EPUBIcon, FileIcon } from '@/components/icons/Icons'; @@ -74,15 +74,17 @@ function ColumnDocRow({ data-doc-tile onClick={onClick} className={ - 'flex items-center gap-2 px-2 py-1.5 rounded-md text-[12px] transition-colors duration-200 ease-out cursor-pointer ' + + 'group flex items-center gap-2 px-2 py-1 rounded-md text-[12px] border transform transition-all duration-200 ease-out cursor-pointer text-left hover:scale-[1.01] ' + (active || isSelected - ? 'bg-accent text-background' - : 'text-foreground hover:bg-offbase') + + ? 'border-accent bg-offbase text-accent' + : 'border-transparent text-foreground hover:border-accent hover:bg-offbase hover:text-accent') + (isOver && canDrop ? ' ring-1 ring-accent' : '') + (isDragging ? ' opacity-50' : '') } > - + + + {doc.name}
); @@ -111,19 +113,19 @@ function ColumnFolderRow({ ref={dropRef as unknown as React.RefObject} onClick={onClick} className={ - 'flex items-center gap-2 px-2 py-1.5 rounded-md text-[12px] transition-colors duration-200 ease-out cursor-pointer ' + + 'group flex items-center gap-2 px-2 py-1 rounded-md text-[12px] border transform transition-all duration-200 ease-out cursor-pointer text-left hover:scale-[1.01] ' + (active - ? 'bg-accent text-background' - : 'text-foreground hover:bg-offbase') + + ? 'border-accent bg-offbase text-accent' + : 'border-transparent text-foreground hover:border-accent hover:bg-offbase hover:text-accent') + (isOver && canDrop ? ' ring-1 ring-accent' : '') } > - + {folder.name} - + {folder.documents.length} - +
); } @@ -150,19 +152,41 @@ export function ColumnsView({ }: ColumnsViewProps) { const { setVisibleOrder } = useDocumentSelection(); const [selected, setSelected] = useState(null); + const allDocs = useMemo( + () => [...folders.flatMap((f) => f.documents), ...unfolderedDocs], + [folders, unfolderedDocs], + ); useEffect(() => { - const all = [...folders.flatMap((f) => f.documents), ...unfolderedDocs]; - setVisibleOrder(all); - }, [folders, unfolderedDocs, setVisibleOrder]); + setVisibleOrder(allDocs); + }, [allDocs, setVisibleOrder]); - // Drop selection if it no longer exists. + // Keep selection valid and default to the first document when entering this view. useEffect(() => { - if (!selected) return; - if (selected.kind === 'folder' && !folders.find((f) => f.id === selected.id)) { - setSelected(null); + if (allDocs.length === 0) { + if (selected !== null) setSelected(null); + return; } - }, [folders, selected]); + + if (!selected) { + setSelected({ kind: 'doc', doc: allDocs[0] }); + return; + } + + if (selected.kind === 'folder') { + if (!folders.find((f) => f.id === selected.id)) { + setSelected({ kind: 'doc', doc: allDocs[0] }); + } + return; + } + + const selectedStillExists = allDocs.some( + (d) => d.id === selected.doc.id && d.type === selected.doc.type, + ); + if (!selectedStillExists) { + setSelected({ kind: 'doc', doc: allDocs[0] }); + } + }, [allDocs, folders, selected]); const selectedFolder = selected?.kind === 'folder' ? folders.find((f) => f.id === selected.id) : undefined;