simplify
This commit is contained in:
@@ -40,7 +40,7 @@ const DocumentsInner: React.FC<{
|
|||||||
|
|
||||||
const handleOpenSidepanel = useCallback((docId: string) => {
|
const handleOpenSidepanel = useCallback((docId: string) => {
|
||||||
if (openDetailPanel) {
|
if (openDetailPanel) {
|
||||||
openDetailPanel({ documentIds: [docId] });
|
openDetailPanel(docId);
|
||||||
}
|
}
|
||||||
}, [openDetailPanel]);
|
}, [openDetailPanel]);
|
||||||
|
|
||||||
|
|||||||
@@ -1,97 +1,46 @@
|
|||||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
import type { Identifier } from '../types/identifiers';
|
||||||
|
|
||||||
interface DetailDocument {
|
interface DetailDocument {
|
||||||
id?: string;
|
id?: Identifier;
|
||||||
[key: string]: unknown;
|
[key: string]: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UseDetailPanelOptions {
|
interface UseDetailPanelOptions {
|
||||||
documentLookup: Map<string, DetailDocument>;
|
documentLookup: Map<Identifier, DetailDocument>;
|
||||||
orderedSelectedDocuments: DetailDocument[];
|
|
||||||
}
|
|
||||||
|
|
||||||
interface OpenDetailPanelArgs {
|
|
||||||
documentId?: string;
|
|
||||||
document?: DetailDocument | null;
|
|
||||||
documentIds?: Array<string>;
|
|
||||||
documents?: DetailDocument[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useDetailPanel = ({
|
export const useDetailPanel = ({
|
||||||
documentLookup,
|
documentLookup,
|
||||||
orderedSelectedDocuments,
|
|
||||||
}: UseDetailPanelOptions) => {
|
}: UseDetailPanelOptions) => {
|
||||||
const [detailPanelOpen, setDetailPanelOpen] = useState(false);
|
const [detailPanelDocId, setDetailPanelDocId] = useState<Identifier | null>(null);
|
||||||
const [detailPanelDocId, setDetailPanelDocId] = useState<string | null>(null);
|
|
||||||
const [detailPanelDocument, setDetailPanelDocument] = useState<DetailDocument | null>(null);
|
const [detailPanelDocument, setDetailPanelDocument] = useState<DetailDocument | null>(null);
|
||||||
const latestOrderedDocsRef = useRef<DetailDocument[]>([]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
const detailPanelOpen = detailPanelDocId !== null;
|
||||||
latestOrderedDocsRef.current = orderedSelectedDocuments;
|
|
||||||
if (detailPanelOpen && orderedSelectedDocuments.length) {
|
|
||||||
const nextDoc = orderedSelectedDocuments[orderedSelectedDocuments.length - 1];
|
|
||||||
if (nextDoc?.id) {
|
|
||||||
setDetailPanelDocId(nextDoc.id);
|
|
||||||
setDetailPanelDocument(nextDoc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [orderedSelectedDocuments, detailPanelOpen]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!detailPanelDocId) {
|
if (!detailPanelDocId) {
|
||||||
if (!detailPanelOpen) {
|
setDetailPanelDocument(null);
|
||||||
setDetailPanelDocument(null);
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const resolved = documentLookup.get(detailPanelDocId);
|
const resolved = documentLookup.get(detailPanelDocId) ?? null;
|
||||||
if (resolved && resolved !== detailPanelDocument) {
|
if (resolved !== detailPanelDocument) {
|
||||||
setDetailPanelDocument(resolved);
|
setDetailPanelDocument(resolved);
|
||||||
}
|
}
|
||||||
}, [detailPanelDocId, documentLookup, detailPanelDocument, detailPanelOpen]);
|
}, [detailPanelDocId, documentLookup, detailPanelDocument]);
|
||||||
|
|
||||||
const openDetailPanel = useCallback(
|
const openDetailPanel = useCallback(
|
||||||
({ documentId, document, documentIds, documents }: OpenDetailPanelArgs = {}) => {
|
(documentId: Identifier) => {
|
||||||
let targetDoc = document || null;
|
if (!documentId) {
|
||||||
let targetId = documentId ?? document?.id ?? null;
|
|
||||||
|
|
||||||
if (!targetDoc && Array.isArray(documents) && documents.length) {
|
|
||||||
targetDoc = documents[documents.length - 1];
|
|
||||||
targetId = targetDoc?.id ?? targetId;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!targetDoc && Array.isArray(documentIds) && documentIds.length) {
|
|
||||||
targetId = documentIds[documentIds.length - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!targetDoc && targetId != null) {
|
|
||||||
targetDoc = documentLookup.get(String(targetId)) || null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!targetDoc) {
|
|
||||||
const fallbackDocs = latestOrderedDocsRef.current;
|
|
||||||
const fallbackDoc = Array.isArray(fallbackDocs) && fallbackDocs.length
|
|
||||||
? fallbackDocs[fallbackDocs.length - 1]
|
|
||||||
: null;
|
|
||||||
if (fallbackDoc) {
|
|
||||||
targetDoc = fallbackDoc;
|
|
||||||
targetId = fallbackDoc.id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!targetDoc && targetId == null) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
setDetailPanelDocId(documentId);
|
||||||
setDetailPanelDocId(targetDoc?.id ?? targetId ?? null);
|
|
||||||
setDetailPanelDocument(targetDoc || null);
|
|
||||||
setDetailPanelOpen(Boolean(targetDoc || targetId));
|
|
||||||
},
|
},
|
||||||
[documentLookup],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
const closeDetailPanel = useCallback(() => {
|
const closeDetailPanel = useCallback(() => {
|
||||||
setDetailPanelOpen(false);
|
setDetailPanelDocId(null);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -99,6 +48,5 @@ export const useDetailPanel = ({
|
|||||||
detailPanelDocument,
|
detailPanelDocument,
|
||||||
openDetailPanel,
|
openDetailPanel,
|
||||||
closeDetailPanel,
|
closeDetailPanel,
|
||||||
setDetailPanelOpen,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import type { UseWorkspaceSurfaceArgs } from './useWorkspaceSurface';
|
|||||||
import type { Identifier } from '../types/identifiers';
|
import type { Identifier } from '../types/identifiers';
|
||||||
|
|
||||||
type WorkspaceSurfaceConfig = Omit<UseWorkspaceSurfaceArgs, 'sidebarHidden' | 'onExpandSidebar'> & {
|
type WorkspaceSurfaceConfig = Omit<UseWorkspaceSurfaceArgs, 'sidebarHidden' | 'onExpandSidebar'> & {
|
||||||
openDetailPanel?: (args: { documentIds?: Identifier[] }) => void;
|
openDetailPanel?: (documentId: Identifier) => void;
|
||||||
closeDetailPanel?: () => void;
|
closeDetailPanel?: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import type { MutableRefObject } from 'react';
|
|||||||
import { resolveDocumentAssetUrl } from '../asset_manager';
|
import { resolveDocumentAssetUrl } from '../asset_manager';
|
||||||
import { useDetailPanel } from '../app/useDetailPanel';
|
import { useDetailPanel } from '../app/useDetailPanel';
|
||||||
import { DEFAULT_FOLDER_NAME } from '../app/workspaceUtils';
|
import { DEFAULT_FOLDER_NAME } from '../app/workspaceUtils';
|
||||||
import { getEntryId, isDocumentEntry } from '../app/entryKey';
|
|
||||||
import type { DocumentInfoPanelProps } from '../documents/DocumentInfoPanel';
|
import type { DocumentInfoPanelProps } from '../documents/DocumentInfoPanel';
|
||||||
import type { EnsureAssetUrl, GetDocumentAsset } from '../utils/ocr';
|
import type { EnsureAssetUrl, GetDocumentAsset } from '../utils/ocr';
|
||||||
import type { Identifier } from '../types/identifiers';
|
import type { Identifier } from '../types/identifiers';
|
||||||
@@ -17,12 +16,10 @@ interface FolderNode {
|
|||||||
|
|
||||||
interface UseDetailWorkspaceArgs {
|
interface UseDetailWorkspaceArgs {
|
||||||
documents: Document[];
|
documents: Document[];
|
||||||
selectionOrder: string[];
|
|
||||||
selectedDocumentIds: Identifier[];
|
|
||||||
documentLookup: Map<Identifier, Document>;
|
documentLookup: Map<Identifier, Document>;
|
||||||
folderNodes: Map<Identifier | 'root', FolderNode>;
|
folderNodes: Map<Identifier | 'root', FolderNode>;
|
||||||
ensureFolderData: (folderId: Identifier | 'root', options?: { force?: boolean; includeDocuments?: boolean }) => Promise<void>;
|
ensureFolderData: (folderId: Identifier | 'root', options?: { force?: boolean; includeDocuments?: boolean }) => Promise<void>;
|
||||||
detailPanelControlRef: MutableRefObject<{ open?: (args?: { documentIds?: Identifier[] }) => void; close?: () => void } | null>;
|
detailPanelControlRef: MutableRefObject<{ open?: (documentId: Identifier) => void; close?: () => void } | null>;
|
||||||
detailFolderFetchRef: MutableRefObject<Set<Identifier | 'root'>>;
|
detailFolderFetchRef: MutableRefObject<Set<Identifier | 'root'>>;
|
||||||
previewDocumentId?: Identifier | null;
|
previewDocumentId?: Identifier | null;
|
||||||
activePreviewId?: Identifier | null;
|
activePreviewId?: Identifier | null;
|
||||||
@@ -56,8 +53,6 @@ interface UseDetailWorkspaceResult {
|
|||||||
|
|
||||||
const useDetailWorkspace = ({
|
const useDetailWorkspace = ({
|
||||||
documents,
|
documents,
|
||||||
selectionOrder,
|
|
||||||
selectedDocumentIds,
|
|
||||||
documentLookup,
|
documentLookup,
|
||||||
folderNodes,
|
folderNodes,
|
||||||
ensureFolderData,
|
ensureFolderData,
|
||||||
@@ -79,37 +74,6 @@ const useDetailWorkspace = ({
|
|||||||
tags,
|
tags,
|
||||||
tagLookupById,
|
tagLookupById,
|
||||||
}: UseDetailWorkspaceArgs): UseDetailWorkspaceResult => {
|
}: UseDetailWorkspaceArgs): UseDetailWorkspaceResult => {
|
||||||
const orderedSelectedDocuments = useMemo(() => {
|
|
||||||
const ordered = [];
|
|
||||||
const seen = new Set();
|
|
||||||
|
|
||||||
const pushDoc = (doc) => {
|
|
||||||
if (doc?.id && !seen.has(doc.id)) {
|
|
||||||
ordered.push(doc);
|
|
||||||
seen.add(doc.id);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
selectionOrder.forEach((key) => {
|
|
||||||
if (!isDocumentEntry(key)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const docId = getEntryId(key);
|
|
||||||
const doc = documentLookup.get(docId) || null;
|
|
||||||
pushDoc(doc);
|
|
||||||
});
|
|
||||||
|
|
||||||
selectedDocumentIds.forEach((docId) => {
|
|
||||||
if (seen.has(docId)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const doc = documentLookup.get(docId) || null;
|
|
||||||
pushDoc(doc);
|
|
||||||
});
|
|
||||||
|
|
||||||
return ordered;
|
|
||||||
}, [selectionOrder, documentLookup, selectedDocumentIds]);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
detailPanelOpen,
|
detailPanelOpen,
|
||||||
detailPanelDocument,
|
detailPanelDocument,
|
||||||
@@ -117,7 +81,6 @@ const useDetailWorkspace = ({
|
|||||||
closeDetailPanel,
|
closeDetailPanel,
|
||||||
} = useDetailPanel({
|
} = useDetailPanel({
|
||||||
documentLookup,
|
documentLookup,
|
||||||
orderedSelectedDocuments,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -127,52 +90,46 @@ const useDetailWorkspace = ({
|
|||||||
};
|
};
|
||||||
}, [detailPanelControlRef, openDetailPanel, closeDetailPanel]);
|
}, [detailPanelControlRef, openDetailPanel, closeDetailPanel]);
|
||||||
|
|
||||||
|
// Prefetch folder ancestors for breadcrumb display
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!orderedSelectedDocuments.length) {
|
const folderId = detailPanelDocument?.folder_id;
|
||||||
|
if (!folderId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const visited = new Set();
|
const visited = new Set();
|
||||||
|
let currentId = folderId;
|
||||||
|
let guard = 0;
|
||||||
|
|
||||||
orderedSelectedDocuments.forEach((doc) => {
|
while (currentId && currentId !== 'root' && guard < 32) {
|
||||||
const folderId = doc?.folder_id;
|
guard += 1;
|
||||||
if (!folderId) {
|
if (visited.has(currentId)) {
|
||||||
return;
|
break;
|
||||||
|
}
|
||||||
|
visited.add(currentId);
|
||||||
|
|
||||||
|
const node = folderNodes.get(currentId);
|
||||||
|
if (!node) {
|
||||||
|
if (!detailFolderFetchRef.current.has(currentId)) {
|
||||||
|
detailFolderFetchRef.current.add(currentId);
|
||||||
|
ensureFolderData(currentId, { force: false, includeDocuments: false })
|
||||||
|
.catch((error) => {
|
||||||
|
console.warn('Failed to preload folder metadata for detail path', currentId, error);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
detailFolderFetchRef.current.delete(currentId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
let currentId = folderId;
|
const parentId = node.parentId ?? 'root';
|
||||||
let guard = 0;
|
if (!parentId || parentId === 'root') {
|
||||||
|
break;
|
||||||
while (currentId && currentId !== 'root' && guard < 32) {
|
|
||||||
guard += 1;
|
|
||||||
if (visited.has(currentId)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
visited.add(currentId);
|
|
||||||
|
|
||||||
const node = folderNodes.get(currentId);
|
|
||||||
if (!node) {
|
|
||||||
if (!detailFolderFetchRef.current.has(currentId)) {
|
|
||||||
detailFolderFetchRef.current.add(currentId);
|
|
||||||
ensureFolderData(currentId, { force: false, includeDocuments: false })
|
|
||||||
.catch((error) => {
|
|
||||||
console.warn('Failed to preload folder metadata for detail path', currentId, error);
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
detailFolderFetchRef.current.delete(currentId);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
const parentId = node.parentId ?? 'root';
|
|
||||||
if (!parentId || parentId === 'root') {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
currentId = parentId;
|
|
||||||
}
|
}
|
||||||
});
|
currentId = parentId;
|
||||||
}, [orderedSelectedDocuments, folderNodes, ensureFolderData, detailFolderFetchRef]);
|
}
|
||||||
|
}, [detailPanelDocument, folderNodes, ensureFolderData, detailFolderFetchRef]);
|
||||||
|
|
||||||
const resolveFolderPath = useCallback(
|
const resolveFolderPath = useCallback(
|
||||||
(folderId) => {
|
(folderId) => {
|
||||||
@@ -240,11 +197,11 @@ const useDetailWorkspace = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const inspectDocument = useCallback(
|
const inspectDocument = useCallback(
|
||||||
(documentId) => {
|
(documentId: Identifier) => {
|
||||||
if (!documentId) {
|
if (!documentId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
openDetailPanel({ documentIds: [documentId] });
|
openDetailPanel(documentId);
|
||||||
},
|
},
|
||||||
[openDetailPanel],
|
[openDetailPanel],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -257,7 +257,6 @@ const useDocumentsWorkspace = ({
|
|||||||
selectedDocumentIds,
|
selectedDocumentIds,
|
||||||
selectedFolderIds,
|
selectedFolderIds,
|
||||||
setSelectedEntries,
|
setSelectedEntries,
|
||||||
selectionOrder,
|
|
||||||
setSelectionOrder,
|
setSelectionOrder,
|
||||||
selectionOrderRef,
|
selectionOrderRef,
|
||||||
selectionAnchorRef,
|
selectionAnchorRef,
|
||||||
@@ -1074,8 +1073,6 @@ const useDocumentsWorkspace = ({
|
|||||||
resolveFolderPath,
|
resolveFolderPath,
|
||||||
} = useDetailWorkspace({
|
} = useDetailWorkspace({
|
||||||
documents: viewDocuments,
|
documents: viewDocuments,
|
||||||
selectionOrder,
|
|
||||||
selectedDocumentIds,
|
|
||||||
documentLookup,
|
documentLookup,
|
||||||
folderNodes,
|
folderNodes,
|
||||||
ensureFolderData,
|
ensureFolderData,
|
||||||
|
|||||||
Reference in New Issue
Block a user