refactor(doclist): remove loading state and unify navigation prefetch behavior
Eliminate unused loading state and related effects from document tile and list row components. Standardize navigation by disabling prefetch on all document open links in doclist views for consistent client-side routing. Cache document list state on load to improve state restoration and initialization.
This commit is contained in:
parent
eff25ad8df
commit
b9490a53ac
5 changed files with 42 additions and 39 deletions
|
|
@ -36,6 +36,8 @@ import { ListView } from './views/ListView';
|
|||
import { ColumnsView } from './views/ColumnsView';
|
||||
import { GalleryView } from './views/GalleryView';
|
||||
|
||||
let cachedDocumentListState: DocumentListState | null = null;
|
||||
|
||||
type DocumentToDelete = {
|
||||
id: string;
|
||||
name: string;
|
||||
|
|
@ -116,22 +118,25 @@ interface DocumentListInnerProps {
|
|||
}
|
||||
|
||||
function DocumentListInner({ brand, appActions }: DocumentListInnerProps) {
|
||||
const [sortBy, setSortBy] = useState<SortBy>(DEFAULT_STATE.sortBy);
|
||||
const cachedState = cachedDocumentListState;
|
||||
const [sortBy, setSortBy] = useState<SortBy>(cachedState?.sortBy ?? DEFAULT_STATE.sortBy);
|
||||
const [sortDirection, setSortDirection] = useState<SortDirection>(
|
||||
DEFAULT_STATE.sortDirection,
|
||||
cachedState?.sortDirection ?? DEFAULT_STATE.sortDirection,
|
||||
);
|
||||
const [viewMode, setViewMode] = useState<ViewMode>(DEFAULT_STATE.viewMode);
|
||||
const [iconSize, setIconSize] = useState<IconSize>(DEFAULT_STATE.iconSize);
|
||||
const [folders, setFolders] = useState<Folder[]>(DEFAULT_STATE.folders);
|
||||
const [showHint, setShowHint] = useState(true);
|
||||
const [sidebarWidth, setSidebarWidth] = useState(DEFAULT_STATE.sidebarWidth);
|
||||
const [sidebarFilter, setSidebarFilter] = useState<SidebarFilter>('all');
|
||||
const [sidebarOpen, setSidebarOpen] = useState(true);
|
||||
const [viewMode, setViewMode] = useState<ViewMode>(
|
||||
normalizeViewMode(cachedState?.viewMode ?? DEFAULT_STATE.viewMode),
|
||||
);
|
||||
const [iconSize, setIconSize] = useState<IconSize>(cachedState?.iconSize ?? DEFAULT_STATE.iconSize);
|
||||
const [folders, setFolders] = useState<Folder[]>(cachedState?.folders ?? DEFAULT_STATE.folders);
|
||||
const [showHint, setShowHint] = useState(cachedState?.showHint ?? true);
|
||||
const [sidebarWidth, setSidebarWidth] = useState(cachedState?.sidebarWidth ?? DEFAULT_STATE.sidebarWidth);
|
||||
const [sidebarFilter, setSidebarFilter] = useState<SidebarFilter>(cachedState?.sidebarFilter ?? 'all');
|
||||
const [sidebarOpen, setSidebarOpen] = useState(!(cachedState?.sidebarCollapsed ?? false));
|
||||
const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false);
|
||||
const [query, setQuery] = useState('');
|
||||
const [recentlyOpenedById, setRecentlyOpenedById] = useState<Record<string, number>>({});
|
||||
|
||||
const [isInitialized, setIsInitialized] = useState(false);
|
||||
const [isInitialized, setIsInitialized] = useState(cachedState !== null);
|
||||
|
||||
const [documentToDelete, setDocumentToDelete] = useState<DocumentToDelete | null>(null);
|
||||
const [pendingMerge, setPendingMerge] = useState<
|
||||
|
|
@ -164,6 +169,7 @@ function DocumentListInner({ brand, appActions }: DocumentListInnerProps) {
|
|||
const saved = await getDocumentListState();
|
||||
if (cancelled) return;
|
||||
if (saved) {
|
||||
cachedDocumentListState = saved;
|
||||
setSortBy(saved.sortBy);
|
||||
setSortDirection(saved.sortDirection);
|
||||
setFolders(saved.folders ?? []);
|
||||
|
|
@ -173,6 +179,17 @@ function DocumentListInner({ brand, appActions }: DocumentListInnerProps) {
|
|||
setSidebarWidth(saved.sidebarWidth ?? DEFAULT_STATE.sidebarWidth);
|
||||
setSidebarFilter(saved.sidebarFilter ?? 'all');
|
||||
setSidebarOpen(!(saved.sidebarCollapsed ?? false));
|
||||
} else {
|
||||
cachedDocumentListState = null;
|
||||
setSortBy(DEFAULT_STATE.sortBy);
|
||||
setSortDirection(DEFAULT_STATE.sortDirection);
|
||||
setFolders(DEFAULT_STATE.folders);
|
||||
setShowHint(DEFAULT_STATE.showHint);
|
||||
setViewMode(DEFAULT_STATE.viewMode);
|
||||
setIconSize(DEFAULT_STATE.iconSize);
|
||||
setSidebarWidth(DEFAULT_STATE.sidebarWidth);
|
||||
setSidebarFilter(DEFAULT_STATE.sidebarFilter);
|
||||
setSidebarOpen(!DEFAULT_STATE.sidebarCollapsed);
|
||||
}
|
||||
setIsInitialized(true);
|
||||
})();
|
||||
|
|
@ -196,6 +213,7 @@ function DocumentListInner({ brand, appActions }: DocumentListInnerProps) {
|
|||
sidebarFilter,
|
||||
sidebarCollapsed: !sidebarOpen,
|
||||
};
|
||||
cachedDocumentListState = state;
|
||||
void saveDocumentListState(state);
|
||||
}, [
|
||||
sortBy,
|
||||
|
|
|
|||
|
|
@ -102,6 +102,9 @@ export function ColumnsView({
|
|||
}: ColumnsViewProps) {
|
||||
const { setVisibleOrder } = useDocumentSelection();
|
||||
const [selectedDoc, setSelectedDoc] = useState<DocumentListDocument | null>(null);
|
||||
const openHref = selectedDoc
|
||||
? `/${selectedDoc.type}/${encodeURIComponent(selectedDoc.id)}`
|
||||
: null;
|
||||
|
||||
useEffect(() => {
|
||||
setVisibleOrder(documents);
|
||||
|
|
@ -154,7 +157,8 @@ export function ColumnsView({
|
|||
</p>
|
||||
<div className="mt-3 flex gap-2">
|
||||
<Link
|
||||
href={`/${selectedDoc.type}/${encodeURIComponent(selectedDoc.id)}`}
|
||||
href={openHref || '/app'}
|
||||
prefetch={false}
|
||||
className="flex-1 inline-flex items-center justify-center h-8 rounded-md bg-accent text-background text-[12px] font-medium hover:bg-secondary-accent hover:scale-[1.01] transition-all duration-200 ease-out"
|
||||
>
|
||||
Open
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useDrag, useDrop, type DragSourceMonitor } from 'react-dnd';
|
||||
import { Button } from '@headlessui/react';
|
||||
import { PDFIcon, EPUBIcon, FileIcon } from '@/components/icons/Icons';
|
||||
|
|
@ -70,7 +69,6 @@ export function DocumentTile({
|
|||
onDelete,
|
||||
onMergeIntoFolder,
|
||||
}: DocumentTileProps) {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { authEnabled } = useAuthConfig();
|
||||
const { data: session } = useAuthSession();
|
||||
const href = `/${doc.type}/${encodeURIComponent(doc.id)}`;
|
||||
|
|
@ -136,19 +134,9 @@ export function DocumentTile({
|
|||
if (e.shiftKey || e.metaKey || e.ctrlKey) {
|
||||
e.preventDefault();
|
||||
selection.select(doc, { shift: e.shiftKey, meta: e.metaKey || e.ctrlKey });
|
||||
return;
|
||||
}
|
||||
selection.replace([doc]);
|
||||
setLoading(true);
|
||||
};
|
||||
|
||||
// Cap any stuck loading flag if the user navigates back.
|
||||
useEffect(() => {
|
||||
if (!loading) return;
|
||||
const t = setTimeout(() => setLoading(false), 2500);
|
||||
return () => clearTimeout(t);
|
||||
}, [loading]);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={setRefs}
|
||||
|
|
@ -160,12 +148,12 @@ export function DocumentTile({
|
|||
? 'border-accent bg-offbase'
|
||||
: 'border-offbase bg-base hover:bg-offbase hover:border-accent') +
|
||||
(isDropTarget ? ' ring-1 ring-accent' : '') +
|
||||
(isDragging ? ' opacity-50' : '') +
|
||||
(loading ? ' prism-outline' : '')
|
||||
(isDragging ? ' opacity-50' : '')
|
||||
}
|
||||
>
|
||||
<Link
|
||||
href={href}
|
||||
prefetch={false}
|
||||
draggable={false}
|
||||
className="block"
|
||||
aria-label={`Open ${doc.name}`}
|
||||
|
|
@ -176,6 +164,7 @@ export function DocumentTile({
|
|||
<div className={`flex items-center w-full ${BOTTOM_PADDING_CLASSES[iconSize]}`}>
|
||||
<Link
|
||||
href={href}
|
||||
prefetch={false}
|
||||
draggable={false}
|
||||
className={`flex items-center flex-1 min-w-0 rounded-md ${LINK_PADDING_CLASS} ${GAP_CLASSES[iconSize]}`}
|
||||
onClick={handleClick}
|
||||
|
|
|
|||
|
|
@ -114,6 +114,7 @@ export function GalleryView({
|
|||
const railRef = useRef<HTMLDivElement | null>(null);
|
||||
const [activeIdx, setActiveIdx] = useState(0);
|
||||
const activeDoc = useMemo(() => documents[activeIdx], [documents, activeIdx]);
|
||||
const openHref = activeDoc ? `/${activeDoc.type}/${encodeURIComponent(activeDoc.id)}` : null;
|
||||
|
||||
useEffect(() => {
|
||||
setVisibleOrder(documents);
|
||||
|
|
@ -163,7 +164,8 @@ export function GalleryView({
|
|||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Link
|
||||
href={`/${activeDoc.type}/${encodeURIComponent(activeDoc.id)}`}
|
||||
href={openHref || '/app'}
|
||||
prefetch={false}
|
||||
className="h-8 px-4 inline-flex items-center justify-center rounded-md bg-accent text-background text-[12px] font-medium hover:bg-secondary-accent hover:scale-[1.01] transition-all duration-200 ease-out"
|
||||
>
|
||||
Open
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { useDrag, useDrop } from 'react-dnd';
|
||||
import { Button } from '@headlessui/react';
|
||||
import type {
|
||||
|
|
@ -87,7 +87,6 @@ function DocRow({
|
|||
const selection = useDocumentSelection();
|
||||
const isSelected = selection.isSelected(doc);
|
||||
const isInFolder = Boolean(doc.folderId);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const href = `/${doc.type}/${encodeURIComponent(doc.id)}`;
|
||||
|
||||
const [{ isDragging }, dragRef] = useDrag<DocumentDragItem, void, { isDragging: boolean }>(() => ({
|
||||
|
|
@ -117,22 +116,13 @@ function DocRow({
|
|||
dropRef(node);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading) return;
|
||||
const t = setTimeout(() => setLoading(false), 2500);
|
||||
return () => clearTimeout(t);
|
||||
}, [loading]);
|
||||
|
||||
const isTarget = isOver && canDrop;
|
||||
|
||||
const handleClick: React.MouseEventHandler = (e) => {
|
||||
if (e.shiftKey || e.metaKey || e.ctrlKey) {
|
||||
e.preventDefault();
|
||||
selection.select(doc, { shift: e.shiftKey, meta: e.metaKey || e.ctrlKey });
|
||||
return;
|
||||
}
|
||||
selection.replace([doc]);
|
||||
setLoading(true);
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
@ -146,12 +136,12 @@ function DocRow({
|
|||
? 'bg-offbase text-accent'
|
||||
: 'text-foreground hover:bg-offbase') +
|
||||
(isTarget ? ' ring-1 ring-accent ring-inset' : '') +
|
||||
(isDragging ? ' opacity-50' : '') +
|
||||
(loading ? ' prism-outline' : '')
|
||||
(isDragging ? ' opacity-50' : '')
|
||||
}
|
||||
>
|
||||
<Link
|
||||
href={href}
|
||||
prefetch={false}
|
||||
draggable={false}
|
||||
onClick={handleClick}
|
||||
className="flex items-center gap-2 min-w-0 px-2 py-1.5"
|
||||
|
|
|
|||
Loading…
Reference in a new issue