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 { ColumnsView } from './views/ColumnsView';
|
||||||
import { GalleryView } from './views/GalleryView';
|
import { GalleryView } from './views/GalleryView';
|
||||||
|
|
||||||
|
let cachedDocumentListState: DocumentListState | null = null;
|
||||||
|
|
||||||
type DocumentToDelete = {
|
type DocumentToDelete = {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
|
@ -116,22 +118,25 @@ interface DocumentListInnerProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
function DocumentListInner({ brand, appActions }: 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>(
|
const [sortDirection, setSortDirection] = useState<SortDirection>(
|
||||||
DEFAULT_STATE.sortDirection,
|
cachedState?.sortDirection ?? DEFAULT_STATE.sortDirection,
|
||||||
);
|
);
|
||||||
const [viewMode, setViewMode] = useState<ViewMode>(DEFAULT_STATE.viewMode);
|
const [viewMode, setViewMode] = useState<ViewMode>(
|
||||||
const [iconSize, setIconSize] = useState<IconSize>(DEFAULT_STATE.iconSize);
|
normalizeViewMode(cachedState?.viewMode ?? DEFAULT_STATE.viewMode),
|
||||||
const [folders, setFolders] = useState<Folder[]>(DEFAULT_STATE.folders);
|
);
|
||||||
const [showHint, setShowHint] = useState(true);
|
const [iconSize, setIconSize] = useState<IconSize>(cachedState?.iconSize ?? DEFAULT_STATE.iconSize);
|
||||||
const [sidebarWidth, setSidebarWidth] = useState(DEFAULT_STATE.sidebarWidth);
|
const [folders, setFolders] = useState<Folder[]>(cachedState?.folders ?? DEFAULT_STATE.folders);
|
||||||
const [sidebarFilter, setSidebarFilter] = useState<SidebarFilter>('all');
|
const [showHint, setShowHint] = useState(cachedState?.showHint ?? true);
|
||||||
const [sidebarOpen, setSidebarOpen] = useState(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 [mobileSidebarOpen, setMobileSidebarOpen] = useState(false);
|
||||||
const [query, setQuery] = useState('');
|
const [query, setQuery] = useState('');
|
||||||
const [recentlyOpenedById, setRecentlyOpenedById] = useState<Record<string, number>>({});
|
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 [documentToDelete, setDocumentToDelete] = useState<DocumentToDelete | null>(null);
|
||||||
const [pendingMerge, setPendingMerge] = useState<
|
const [pendingMerge, setPendingMerge] = useState<
|
||||||
|
|
@ -164,6 +169,7 @@ function DocumentListInner({ brand, appActions }: DocumentListInnerProps) {
|
||||||
const saved = await getDocumentListState();
|
const saved = await getDocumentListState();
|
||||||
if (cancelled) return;
|
if (cancelled) return;
|
||||||
if (saved) {
|
if (saved) {
|
||||||
|
cachedDocumentListState = saved;
|
||||||
setSortBy(saved.sortBy);
|
setSortBy(saved.sortBy);
|
||||||
setSortDirection(saved.sortDirection);
|
setSortDirection(saved.sortDirection);
|
||||||
setFolders(saved.folders ?? []);
|
setFolders(saved.folders ?? []);
|
||||||
|
|
@ -173,6 +179,17 @@ function DocumentListInner({ brand, appActions }: DocumentListInnerProps) {
|
||||||
setSidebarWidth(saved.sidebarWidth ?? DEFAULT_STATE.sidebarWidth);
|
setSidebarWidth(saved.sidebarWidth ?? DEFAULT_STATE.sidebarWidth);
|
||||||
setSidebarFilter(saved.sidebarFilter ?? 'all');
|
setSidebarFilter(saved.sidebarFilter ?? 'all');
|
||||||
setSidebarOpen(!(saved.sidebarCollapsed ?? false));
|
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);
|
setIsInitialized(true);
|
||||||
})();
|
})();
|
||||||
|
|
@ -196,6 +213,7 @@ function DocumentListInner({ brand, appActions }: DocumentListInnerProps) {
|
||||||
sidebarFilter,
|
sidebarFilter,
|
||||||
sidebarCollapsed: !sidebarOpen,
|
sidebarCollapsed: !sidebarOpen,
|
||||||
};
|
};
|
||||||
|
cachedDocumentListState = state;
|
||||||
void saveDocumentListState(state);
|
void saveDocumentListState(state);
|
||||||
}, [
|
}, [
|
||||||
sortBy,
|
sortBy,
|
||||||
|
|
|
||||||
|
|
@ -102,6 +102,9 @@ export function ColumnsView({
|
||||||
}: ColumnsViewProps) {
|
}: ColumnsViewProps) {
|
||||||
const { setVisibleOrder } = useDocumentSelection();
|
const { setVisibleOrder } = useDocumentSelection();
|
||||||
const [selectedDoc, setSelectedDoc] = useState<DocumentListDocument | null>(null);
|
const [selectedDoc, setSelectedDoc] = useState<DocumentListDocument | null>(null);
|
||||||
|
const openHref = selectedDoc
|
||||||
|
? `/${selectedDoc.type}/${encodeURIComponent(selectedDoc.id)}`
|
||||||
|
: null;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setVisibleOrder(documents);
|
setVisibleOrder(documents);
|
||||||
|
|
@ -154,7 +157,8 @@ export function ColumnsView({
|
||||||
</p>
|
</p>
|
||||||
<div className="mt-3 flex gap-2">
|
<div className="mt-3 flex gap-2">
|
||||||
<Link
|
<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"
|
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
|
Open
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { useEffect, useState } from 'react';
|
|
||||||
import { useDrag, useDrop, type DragSourceMonitor } from 'react-dnd';
|
import { useDrag, useDrop, type DragSourceMonitor } from 'react-dnd';
|
||||||
import { Button } from '@headlessui/react';
|
import { Button } from '@headlessui/react';
|
||||||
import { PDFIcon, EPUBIcon, FileIcon } from '@/components/icons/Icons';
|
import { PDFIcon, EPUBIcon, FileIcon } from '@/components/icons/Icons';
|
||||||
|
|
@ -70,7 +69,6 @@ export function DocumentTile({
|
||||||
onDelete,
|
onDelete,
|
||||||
onMergeIntoFolder,
|
onMergeIntoFolder,
|
||||||
}: DocumentTileProps) {
|
}: DocumentTileProps) {
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const { authEnabled } = useAuthConfig();
|
const { authEnabled } = useAuthConfig();
|
||||||
const { data: session } = useAuthSession();
|
const { data: session } = useAuthSession();
|
||||||
const href = `/${doc.type}/${encodeURIComponent(doc.id)}`;
|
const href = `/${doc.type}/${encodeURIComponent(doc.id)}`;
|
||||||
|
|
@ -136,19 +134,9 @@ export function DocumentTile({
|
||||||
if (e.shiftKey || e.metaKey || e.ctrlKey) {
|
if (e.shiftKey || e.metaKey || e.ctrlKey) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
selection.select(doc, { shift: e.shiftKey, meta: e.metaKey || e.ctrlKey });
|
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 (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={setRefs}
|
ref={setRefs}
|
||||||
|
|
@ -160,12 +148,12 @@ export function DocumentTile({
|
||||||
? 'border-accent bg-offbase'
|
? 'border-accent bg-offbase'
|
||||||
: 'border-offbase bg-base hover:bg-offbase hover:border-accent') +
|
: 'border-offbase bg-base hover:bg-offbase hover:border-accent') +
|
||||||
(isDropTarget ? ' ring-1 ring-accent' : '') +
|
(isDropTarget ? ' ring-1 ring-accent' : '') +
|
||||||
(isDragging ? ' opacity-50' : '') +
|
(isDragging ? ' opacity-50' : '')
|
||||||
(loading ? ' prism-outline' : '')
|
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Link
|
<Link
|
||||||
href={href}
|
href={href}
|
||||||
|
prefetch={false}
|
||||||
draggable={false}
|
draggable={false}
|
||||||
className="block"
|
className="block"
|
||||||
aria-label={`Open ${doc.name}`}
|
aria-label={`Open ${doc.name}`}
|
||||||
|
|
@ -176,6 +164,7 @@ export function DocumentTile({
|
||||||
<div className={`flex items-center w-full ${BOTTOM_PADDING_CLASSES[iconSize]}`}>
|
<div className={`flex items-center w-full ${BOTTOM_PADDING_CLASSES[iconSize]}`}>
|
||||||
<Link
|
<Link
|
||||||
href={href}
|
href={href}
|
||||||
|
prefetch={false}
|
||||||
draggable={false}
|
draggable={false}
|
||||||
className={`flex items-center flex-1 min-w-0 rounded-md ${LINK_PADDING_CLASS} ${GAP_CLASSES[iconSize]}`}
|
className={`flex items-center flex-1 min-w-0 rounded-md ${LINK_PADDING_CLASS} ${GAP_CLASSES[iconSize]}`}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
|
|
|
||||||
|
|
@ -114,6 +114,7 @@ export function GalleryView({
|
||||||
const railRef = useRef<HTMLDivElement | null>(null);
|
const railRef = useRef<HTMLDivElement | null>(null);
|
||||||
const [activeIdx, setActiveIdx] = useState(0);
|
const [activeIdx, setActiveIdx] = useState(0);
|
||||||
const activeDoc = useMemo(() => documents[activeIdx], [documents, activeIdx]);
|
const activeDoc = useMemo(() => documents[activeIdx], [documents, activeIdx]);
|
||||||
|
const openHref = activeDoc ? `/${activeDoc.type}/${encodeURIComponent(activeDoc.id)}` : null;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setVisibleOrder(documents);
|
setVisibleOrder(documents);
|
||||||
|
|
@ -163,7 +164,8 @@ export function GalleryView({
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Link
|
<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"
|
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
|
Open
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useDrag, useDrop } from 'react-dnd';
|
import { useDrag, useDrop } from 'react-dnd';
|
||||||
import { Button } from '@headlessui/react';
|
import { Button } from '@headlessui/react';
|
||||||
import type {
|
import type {
|
||||||
|
|
@ -87,7 +87,6 @@ function DocRow({
|
||||||
const selection = useDocumentSelection();
|
const selection = useDocumentSelection();
|
||||||
const isSelected = selection.isSelected(doc);
|
const isSelected = selection.isSelected(doc);
|
||||||
const isInFolder = Boolean(doc.folderId);
|
const isInFolder = Boolean(doc.folderId);
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const href = `/${doc.type}/${encodeURIComponent(doc.id)}`;
|
const href = `/${doc.type}/${encodeURIComponent(doc.id)}`;
|
||||||
|
|
||||||
const [{ isDragging }, dragRef] = useDrag<DocumentDragItem, void, { isDragging: boolean }>(() => ({
|
const [{ isDragging }, dragRef] = useDrag<DocumentDragItem, void, { isDragging: boolean }>(() => ({
|
||||||
|
|
@ -117,22 +116,13 @@ function DocRow({
|
||||||
dropRef(node);
|
dropRef(node);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!loading) return;
|
|
||||||
const t = setTimeout(() => setLoading(false), 2500);
|
|
||||||
return () => clearTimeout(t);
|
|
||||||
}, [loading]);
|
|
||||||
|
|
||||||
const isTarget = isOver && canDrop;
|
const isTarget = isOver && canDrop;
|
||||||
|
|
||||||
const handleClick: React.MouseEventHandler = (e) => {
|
const handleClick: React.MouseEventHandler = (e) => {
|
||||||
if (e.shiftKey || e.metaKey || e.ctrlKey) {
|
if (e.shiftKey || e.metaKey || e.ctrlKey) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
selection.select(doc, { shift: e.shiftKey, meta: e.metaKey || e.ctrlKey });
|
selection.select(doc, { shift: e.shiftKey, meta: e.metaKey || e.ctrlKey });
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
selection.replace([doc]);
|
|
||||||
setLoading(true);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -146,12 +136,12 @@ function DocRow({
|
||||||
? 'bg-offbase text-accent'
|
? 'bg-offbase text-accent'
|
||||||
: 'text-foreground hover:bg-offbase') +
|
: 'text-foreground hover:bg-offbase') +
|
||||||
(isTarget ? ' ring-1 ring-accent ring-inset' : '') +
|
(isTarget ? ' ring-1 ring-accent ring-inset' : '') +
|
||||||
(isDragging ? ' opacity-50' : '') +
|
(isDragging ? ' opacity-50' : '')
|
||||||
(loading ? ' prism-outline' : '')
|
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Link
|
<Link
|
||||||
href={href}
|
href={href}
|
||||||
|
prefetch={false}
|
||||||
draggable={false}
|
draggable={false}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
className="flex items-center gap-2 min-w-0 px-2 py-1.5"
|
className="flex items-center gap-2 min-w-0 px-2 py-1.5"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue