documentsmanager
This commit is contained in:
@@ -36,8 +36,13 @@ type NavigateHandler = (path: string, options?: { replace?: boolean }) => void;
|
||||
|
||||
interface UseDocumentPreviewArgs {
|
||||
routeDocumentId?: DocumentId | null;
|
||||
documents: DocumentLike[];
|
||||
searchResults: DocumentLike[] | null;
|
||||
documentsManager: {
|
||||
getById: (id: DocumentId) => DocumentLike | null;
|
||||
ensure: (id: DocumentId) => Promise<DocumentLike | null>;
|
||||
getMany: (ids: DocumentId[]) => DocumentLike[];
|
||||
subscribe: (listener: () => void) => () => void;
|
||||
ingest: (docs: unknown[]) => { canonical: DocumentLike[]; changed: boolean };
|
||||
};
|
||||
selectedFolder?: FolderId | null;
|
||||
assetManager: AssetManagerLike;
|
||||
api: ApiClient;
|
||||
@@ -55,7 +60,6 @@ interface UseDocumentPreviewArgs {
|
||||
|
||||
interface UseDocumentPreviewResult {
|
||||
documentLinks: Map<DocumentId, DocumentLink>;
|
||||
previewDocuments: Map<DocumentId, DocumentLike>;
|
||||
ensureDownloadUrl: (documentId: DocumentId, options?: { force?: boolean }) => Promise<DocumentLink | null>;
|
||||
ensurePreviewData: (documentId: DocumentId) => Promise<DocumentLike | null>;
|
||||
openDocumentPreview: (documentId: DocumentId, options?: { replace?: boolean }) => void;
|
||||
@@ -66,10 +70,9 @@ interface UseDocumentPreviewResult {
|
||||
|
||||
const useDocumentPreview = ({
|
||||
routeDocumentId,
|
||||
documents,
|
||||
searchResults,
|
||||
documentsManager,
|
||||
selectedFolder,
|
||||
assetManager,
|
||||
assetManager: _assetManager,
|
||||
api,
|
||||
resolveApiPath,
|
||||
notifyApiError,
|
||||
@@ -80,7 +83,6 @@ const useDocumentPreview = ({
|
||||
setActivePreviewId,
|
||||
}: UseDocumentPreviewArgs): UseDocumentPreviewResult => {
|
||||
const [documentLinks, setDocumentLinks] = useState<Map<DocumentId, DocumentLink>>(() => new Map());
|
||||
const [previewDocuments, setPreviewDocuments] = useState<Map<DocumentId, DocumentLike>>(() => new Map());
|
||||
const previewInflightRef = useRef<Map<DocumentId, Promise<DocumentLink | null>>>(new Map());
|
||||
const previewReturnPathRef = useRef<string | null>(null);
|
||||
|
||||
@@ -110,35 +112,6 @@ const useDocumentPreview = ({
|
||||
});
|
||||
}, []);
|
||||
|
||||
const cachePreviewDocument = useCallback((doc: DocumentLike) => {
|
||||
if (!doc?.id) {
|
||||
return;
|
||||
}
|
||||
setPreviewDocuments((prev) => {
|
||||
const existing = prev.get(doc.id);
|
||||
if (existing === doc) {
|
||||
return prev;
|
||||
}
|
||||
const next = new Map(prev);
|
||||
next.set(doc.id, doc);
|
||||
return next;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const removeCachedPreviewDocument = useCallback((documentId: DocumentId) => {
|
||||
if (!documentId) {
|
||||
return;
|
||||
}
|
||||
setPreviewDocuments((prev) => {
|
||||
if (!prev.has(documentId)) {
|
||||
return prev;
|
||||
}
|
||||
const next = new Map(prev);
|
||||
next.delete(documentId);
|
||||
return next;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const ensureDownloadUrl = useCallback(
|
||||
async (documentId: DocumentId, { force = false }: { force?: boolean } = {}): Promise<DocumentLink | null> => {
|
||||
if (!documentId) return null;
|
||||
@@ -193,32 +166,22 @@ const useDocumentPreview = ({
|
||||
async (documentId: DocumentId): Promise<DocumentLike | null> => {
|
||||
if (!documentId) return null;
|
||||
|
||||
const findInCache = () => {
|
||||
const pool = searchResults ?? documents;
|
||||
return pool.find((item) => item.id === documentId) || null;
|
||||
};
|
||||
const findInCache = () => documentsManager.getById(documentId);
|
||||
|
||||
let doc = findInCache();
|
||||
|
||||
if (!doc) {
|
||||
doc = await documentsManager.ensure(documentId);
|
||||
}
|
||||
|
||||
if (!doc) {
|
||||
const { data } = await api.get(`/documents/${documentId}`);
|
||||
const hydratedDetail = assetManager.hydrateDetail(data);
|
||||
const fetched = hydratedDetail?.document || (data as { document?: DocumentLike })?.document || data;
|
||||
doc = fetched ? assetManager.hydrateDocument(fetched) || null : null;
|
||||
const fetched = (data as { document?: DocumentLike })?.document || data;
|
||||
const { canonical } = documentsManager.ingest([fetched as unknown]);
|
||||
doc = (canonical[0] as DocumentLike | undefined) || null;
|
||||
if (!doc) {
|
||||
throw new Error('Document metadata unavailable.');
|
||||
}
|
||||
|
||||
const existsInDocuments = documents.some((item) => item.id === doc?.id);
|
||||
const existsInSearch = Array.isArray(searchResults)
|
||||
? searchResults.some((item) => item.id === doc?.id)
|
||||
: false;
|
||||
|
||||
if (doc?.id && (existsInDocuments || existsInSearch)) {
|
||||
removeCachedPreviewDocument(doc.id);
|
||||
} else {
|
||||
cachePreviewDocument(doc);
|
||||
}
|
||||
}
|
||||
|
||||
if (!previewReturnPathRef.current) {
|
||||
@@ -232,14 +195,10 @@ const useDocumentPreview = ({
|
||||
return doc;
|
||||
},
|
||||
[
|
||||
searchResults,
|
||||
documents,
|
||||
assetManager,
|
||||
documentsManager,
|
||||
ensureDownloadUrl,
|
||||
setActivePreviewId,
|
||||
api,
|
||||
cachePreviewDocument,
|
||||
removeCachedPreviewDocument,
|
||||
],
|
||||
);
|
||||
|
||||
@@ -270,21 +229,6 @@ const useDocumentPreview = ({
|
||||
[navigate, selectedFolder],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!routeDocumentId) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Escape') {
|
||||
closeDocumentPreview();
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
return () => window.removeEventListener('keydown', handleKeyDown);
|
||||
}, [routeDocumentId, closeDocumentPreview]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!routeDocumentId) {
|
||||
return undefined;
|
||||
@@ -305,33 +249,8 @@ const useDocumentPreview = ({
|
||||
};
|
||||
}, [routeDocumentId, ensurePreviewData, notifyApiError, closeDocumentPreview]);
|
||||
|
||||
useEffect(() => {
|
||||
setPreviewDocuments((prev) => {
|
||||
if (!prev.size) {
|
||||
return prev;
|
||||
}
|
||||
const next = new Map(prev);
|
||||
let changed = false;
|
||||
const prune = (list?: DocumentLike[] | null) => {
|
||||
if (!Array.isArray(list)) {
|
||||
return;
|
||||
}
|
||||
list.forEach((doc) => {
|
||||
if (doc?.id && next.has(doc.id)) {
|
||||
next.delete(doc.id);
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
};
|
||||
prune(documents);
|
||||
prune(searchResults);
|
||||
return changed ? next : prev;
|
||||
});
|
||||
}, [documents, searchResults]);
|
||||
|
||||
return {
|
||||
documentLinks,
|
||||
previewDocuments,
|
||||
ensureDownloadUrl,
|
||||
ensurePreviewData,
|
||||
openDocumentPreview,
|
||||
|
||||
@@ -10,13 +10,8 @@ type ApiClient = {
|
||||
get: <T = unknown>(url: string, config?: { params?: Record<string, unknown> }) => Promise<{ data: T }>;
|
||||
};
|
||||
|
||||
type AssetManagerLike = {
|
||||
hydrateDocuments: (payload: unknown[]) => DocumentLike[];
|
||||
};
|
||||
|
||||
interface UseDocumentsSearchArgs {
|
||||
api: ApiClient;
|
||||
assetManager: AssetManagerLike;
|
||||
token?: string | null;
|
||||
selectedFolder?: Identifier | 'root' | null;
|
||||
navigate?: (path: string, options?: { replace?: boolean }) => void;
|
||||
@@ -28,13 +23,16 @@ interface UseDocumentsSearchArgs {
|
||||
notifyApiError: (error: unknown, message: string) => void;
|
||||
setLoading: (state: boolean) => void;
|
||||
setSearchIncludeDescendants: (value: boolean) => void;
|
||||
documentsManager: {
|
||||
ingest: (docs: unknown[]) => { canonical: DocumentLike[]; changed: boolean };
|
||||
};
|
||||
}
|
||||
|
||||
interface UseDocumentsSearchResult {
|
||||
searchQuery: string;
|
||||
setSearchQuery: Dispatch<SetStateAction<string>>;
|
||||
searchResults: DocumentLike[] | null;
|
||||
setSearchResults: Dispatch<SetStateAction<DocumentLike[] | null>>;
|
||||
searchResultIds: Identifier[] | null;
|
||||
setSearchResultIds: Dispatch<SetStateAction<Identifier[] | null>>;
|
||||
searchLoading: boolean;
|
||||
setSearchLoading: Dispatch<SetStateAction<boolean>>;
|
||||
activeTagFilters: Identifier[];
|
||||
@@ -49,7 +47,7 @@ interface UseDocumentsSearchResult {
|
||||
handleSearchSubmit: () => void;
|
||||
documentsFilterValue: {
|
||||
query: string;
|
||||
searchResults: DocumentLike[] | null;
|
||||
searchResultIds: Identifier[] | null;
|
||||
searchLoading: boolean;
|
||||
includeDescendants: boolean;
|
||||
activeTagIds: Identifier[];
|
||||
@@ -66,7 +64,6 @@ interface UseDocumentsSearchResult {
|
||||
|
||||
const useDocumentsSearch = ({
|
||||
api,
|
||||
assetManager,
|
||||
token,
|
||||
selectedFolder,
|
||||
navigate,
|
||||
@@ -78,11 +75,12 @@ const useDocumentsSearch = ({
|
||||
notifyApiError,
|
||||
setLoading,
|
||||
setSearchIncludeDescendants,
|
||||
documentsManager,
|
||||
}: UseDocumentsSearchArgs): UseDocumentsSearchResult => {
|
||||
const [searchQuery, setSearchQuery] = useState<string>('');
|
||||
const [activeTagFilters, setActiveTagFilters] = useState<Identifier[]>([]);
|
||||
const [activeCorrespondentFilters, setActiveCorrespondentFilters] = useState<Identifier[]>([]);
|
||||
const [searchResults, setSearchResults] = useState<DocumentLike[] | null>(null);
|
||||
const [searchResultIds, setSearchResultIds] = useState<Identifier[] | null>(null);
|
||||
const [searchLoading, setSearchLoading] = useState<boolean>(false);
|
||||
|
||||
const toggleTagFilter = useCallback((tagId: Identifier) => {
|
||||
@@ -122,6 +120,7 @@ const useDocumentsSearch = ({
|
||||
setActiveCorrespondentFilters([]);
|
||||
setSearchLoading(false);
|
||||
setSearchIncludeDescendants(true);
|
||||
setSearchResultIds(null);
|
||||
}, [
|
||||
setSearchIncludeDescendants,
|
||||
]);
|
||||
@@ -142,7 +141,7 @@ const useDocumentsSearch = ({
|
||||
const documentsFilterValue = useMemo(
|
||||
() => ({
|
||||
query: searchQuery,
|
||||
searchResults,
|
||||
searchResultIds,
|
||||
searchLoading,
|
||||
includeDescendants: Boolean(searchIncludeDescendants),
|
||||
activeTagIds: activeTagFilters,
|
||||
@@ -157,7 +156,7 @@ const useDocumentsSearch = ({
|
||||
}),
|
||||
[
|
||||
searchQuery,
|
||||
searchResults,
|
||||
searchResultIds,
|
||||
searchLoading,
|
||||
searchIncludeDescendants,
|
||||
activeTagFilters,
|
||||
@@ -176,7 +175,7 @@ const useDocumentsSearch = ({
|
||||
if (!token) return undefined;
|
||||
|
||||
if (!isFilterActive) {
|
||||
setSearchResults(null);
|
||||
setSearchResultIds(null);
|
||||
setSearchLoading(false);
|
||||
return undefined;
|
||||
}
|
||||
@@ -225,17 +224,21 @@ const useDocumentsSearch = ({
|
||||
const { data } = await api.get<unknown[]>('/documents', { params });
|
||||
if (cancelled) return;
|
||||
|
||||
const results = assetManager.hydrateDocuments(data || []);
|
||||
setSearchResults(results);
|
||||
const results = Array.isArray(data) ? data : [];
|
||||
const { canonical } = documentsManager.ingest(results);
|
||||
const ids = canonical
|
||||
.map((doc) => (doc?.id ?? null) as Identifier | null)
|
||||
.filter((id): id is Identifier => id != null);
|
||||
setSearchResultIds(ids);
|
||||
|
||||
if (!results.length) {
|
||||
if (!ids.length) {
|
||||
setSearchLoading(false);
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
if (cancelled) return;
|
||||
notifyApiError(error, 'Search failed. Please try again.');
|
||||
setSearchResults(null);
|
||||
setSearchResultIds(null);
|
||||
} finally {
|
||||
if (!cancelled && started) {
|
||||
setLoading(false);
|
||||
@@ -264,15 +267,15 @@ const useDocumentsSearch = ({
|
||||
documentsSortDirection,
|
||||
selectedFolder,
|
||||
notifyApiError,
|
||||
assetManager,
|
||||
setLoading,
|
||||
documentsManager,
|
||||
]);
|
||||
|
||||
return {
|
||||
searchQuery,
|
||||
setSearchQuery,
|
||||
searchResults,
|
||||
setSearchResults,
|
||||
searchResultIds,
|
||||
setSearchResultIds,
|
||||
searchLoading,
|
||||
setSearchLoading,
|
||||
activeTagFilters,
|
||||
|
||||
@@ -123,7 +123,7 @@ export const useWorkspaceSurface = ({
|
||||
const showPreviewWorkspace = Boolean(previewDocumentId);
|
||||
|
||||
const previewSurface = useMemo<WorkspaceSurface>(() => {
|
||||
if (!showPreviewWorkspace || !previewWorkspaceDocument) {
|
||||
if (!showPreviewWorkspace) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@ export const useWorkspaceSurface = ({
|
||||
return {
|
||||
content: (
|
||||
<DocumentViewerPanel
|
||||
document={previewWorkspaceDocument}
|
||||
document={previewWorkspaceDocument || null}
|
||||
documentLink={documentLink}
|
||||
hydrateDocument={ensurePreviewData}
|
||||
tagLookupById={tagLookupById}
|
||||
|
||||
Reference in New Issue
Block a user