This commit is contained in:
2025-11-18 14:17:53 +01:00
parent ea6e42175a
commit 3e21e72953
11 changed files with 118 additions and 130 deletions
+3 -3
View File
@@ -35,7 +35,7 @@ interface DocumentsRouteAppShell {
openTagsModal?: () => void; openTagsModal?: () => void;
openCorrespondentsModal?: () => void; openCorrespondentsModal?: () => void;
previewWorkspaceDocument?: unknown; previewWorkspaceDocument?: unknown;
previewWorkspaceEntry?: unknown; documentLink?: unknown;
previewDocumentId?: Identifier | null; previewDocumentId?: Identifier | null;
closeDocumentPreview?: () => void; closeDocumentPreview?: () => void;
ensurePreviewData?: EnsurePreviewData; ensurePreviewData?: EnsurePreviewData;
@@ -54,7 +54,7 @@ const DocumentsRouteContent: React.FC = () => {
openTagsModal, openTagsModal,
openCorrespondentsModal, openCorrespondentsModal,
previewWorkspaceDocument, previewWorkspaceDocument,
previewWorkspaceEntry, documentLink,
previewDocumentId, previewDocumentId,
closeDocumentPreview, closeDocumentPreview,
ensurePreviewData, ensurePreviewData,
@@ -107,7 +107,7 @@ const DocumentsRouteContent: React.FC = () => {
detailPanelProps, detailPanelProps,
detailPanelOpen, detailPanelOpen,
previewWorkspaceDocument, previewWorkspaceDocument,
previewWorkspaceEntry, documentLink,
previewDocumentId, previewDocumentId,
ensureAssetUrl, ensureAssetUrl,
ensurePreviewData, ensurePreviewData,
+17 -17
View File
@@ -16,7 +16,7 @@ type DocumentLike = {
[key: string]: unknown; [key: string]: unknown;
}; };
type PreviewEntry = { type DocumentLink = {
url?: string; url?: string;
contentType?: string | null; contentType?: string | null;
filename?: string | null; filename?: string | null;
@@ -54,14 +54,14 @@ interface UseDocumentPreviewArgs {
} }
interface UseDocumentPreviewResult { interface UseDocumentPreviewResult {
previewEntries: Map<DocumentId, PreviewEntry>; documentLinks: Map<DocumentId, DocumentLink>;
previewDocuments: Map<DocumentId, DocumentLike>; previewDocuments: Map<DocumentId, DocumentLike>;
ensureDownloadUrl: (documentId: DocumentId, options?: { force?: boolean }) => Promise<PreviewEntry | null>; ensureDownloadUrl: (documentId: DocumentId, options?: { force?: boolean }) => Promise<DocumentLink | null>;
ensurePreviewData: (documentId: DocumentId) => Promise<DocumentLike | null>; ensurePreviewData: (documentId: DocumentId) => Promise<DocumentLike | null>;
openDocumentPreview: (documentId: DocumentId, options?: { replace?: boolean }) => void; openDocumentPreview: (documentId: DocumentId, options?: { replace?: boolean }) => void;
closeDocumentPreview: (folderId?: FolderId) => void; closeDocumentPreview: (folderId?: FolderId) => void;
resetPreviewState: () => void; resetPreviewState: () => void;
removePreviewEntries: (ids: DocumentId[]) => void; removeDocumentLinks: (ids: DocumentId[]) => void;
} }
const useDocumentPreview = ({ const useDocumentPreview = ({
@@ -79,22 +79,22 @@ const useDocumentPreview = ({
detailPanelControlRef, detailPanelControlRef,
setActivePreviewId, setActivePreviewId,
}: UseDocumentPreviewArgs): UseDocumentPreviewResult => { }: UseDocumentPreviewArgs): UseDocumentPreviewResult => {
const [previewEntries, setPreviewEntries] = useState<Map<DocumentId, PreviewEntry>>(() => new Map()); const [documentLinks, setDocumentLinks] = useState<Map<DocumentId, DocumentLink>>(() => new Map());
const [previewDocuments, setPreviewDocuments] = useState<Map<DocumentId, DocumentLike>>(() => new Map()); const [previewDocuments, setPreviewDocuments] = useState<Map<DocumentId, DocumentLike>>(() => new Map());
const previewInflightRef = useRef<Map<DocumentId, Promise<PreviewEntry | null>>>(new Map()); const previewInflightRef = useRef<Map<DocumentId, Promise<DocumentLink | null>>>(new Map());
const previewReturnPathRef = useRef<string | null>(null); const previewReturnPathRef = useRef<string | null>(null);
const resetPreviewState = useCallback(() => { const resetPreviewState = useCallback(() => {
setPreviewEntries(() => new Map()); setDocumentLinks(() => new Map());
previewInflightRef.current = new Map(); previewInflightRef.current = new Map();
previewReturnPathRef.current = null; previewReturnPathRef.current = null;
}, []); }, []);
const removePreviewEntries = useCallback((ids: DocumentId[]) => { const removeDocumentLinks = useCallback((ids: DocumentId[]) => {
if (!Array.isArray(ids) || ids.length === 0) { if (!Array.isArray(ids) || ids.length === 0) {
return; return;
} }
setPreviewEntries((prev) => { setDocumentLinks((prev) => {
if (!prev.size) { if (!prev.size) {
return prev; return prev;
} }
@@ -140,10 +140,10 @@ const useDocumentPreview = ({
}, []); }, []);
const ensureDownloadUrl = useCallback( const ensureDownloadUrl = useCallback(
async (documentId: DocumentId, { force = false }: { force?: boolean } = {}): Promise<PreviewEntry | null> => { async (documentId: DocumentId, { force = false }: { force?: boolean } = {}): Promise<DocumentLink | null> => {
if (!documentId) return null; if (!documentId) return null;
const existing = previewEntries.get(documentId) || null; const existing = documentLinks.get(documentId) || null;
const now = Date.now(); const now = Date.now();
const expiresAt = Number.isFinite(existing?.expiresAt) ? Number(existing?.expiresAt) : null; const expiresAt = Number.isFinite(existing?.expiresAt) ? Number(existing?.expiresAt) : null;
if (!force && existing && (!expiresAt || expiresAt > now)) { if (!force && existing && (!expiresAt || expiresAt > now)) {
@@ -154,7 +154,7 @@ const useDocumentPreview = ({
return previewInflightRef.current.get(documentId) || null; return previewInflightRef.current.get(documentId) || null;
} }
const request: Promise<PreviewEntry | null> = (async () => { const request: Promise<DocumentLink | null> = (async () => {
try { try {
const docResponse = await api.get<{ document?: Record<string, any> }>(`/documents/${documentId}`); const docResponse = await api.get<{ document?: Record<string, any> }>(`/documents/${documentId}`);
const downloadPath = docResponse.data?.document?.current_version?.download_path; const downloadPath = docResponse.data?.document?.current_version?.download_path;
@@ -163,13 +163,13 @@ const useDocumentPreview = ({
} }
const href = resolveApiPath(downloadPath); const href = resolveApiPath(downloadPath);
const entry: PreviewEntry = { const entry: DocumentLink = {
url: href, url: href,
contentType: docResponse.data?.document?.current_version?.version?.content_type || null, contentType: docResponse.data?.document?.current_version?.version?.content_type || null,
filename: docResponse.data?.document?.filename, filename: docResponse.data?.document?.filename,
expiresAt: Date.now() + 5 * 60 * 1000, expiresAt: Date.now() + 5 * 60 * 1000,
}; };
setPreviewEntries((prev) => { setDocumentLinks((prev) => {
const next = new Map(prev); const next = new Map(prev);
next.set(documentId, entry); next.set(documentId, entry);
return next; return next;
@@ -186,7 +186,7 @@ const useDocumentPreview = ({
previewInflightRef.current.set(documentId, request); previewInflightRef.current.set(documentId, request);
return request; return request;
}, },
[previewEntries, api, resolveApiPath, notifyApiError], [documentLinks, api, resolveApiPath, notifyApiError],
); );
const ensurePreviewData = useCallback( const ensurePreviewData = useCallback(
@@ -330,14 +330,14 @@ const useDocumentPreview = ({
}, [documents, searchResults]); }, [documents, searchResults]);
return { return {
previewEntries, documentLinks,
previewDocuments, previewDocuments,
ensureDownloadUrl, ensureDownloadUrl,
ensurePreviewData, ensurePreviewData,
openDocumentPreview, openDocumentPreview,
closeDocumentPreview, closeDocumentPreview,
resetPreviewState, resetPreviewState,
removePreviewEntries, removeDocumentLinks,
}; };
}; };
+4 -4
View File
@@ -24,7 +24,7 @@ interface UseWorkspaceSurfaceArgs {
detailPanelProps?: (Record<string, any> & { onClose?: () => void }) | null; detailPanelProps?: (Record<string, any> & { onClose?: () => void }) | null;
detailPanelOpen?: boolean; detailPanelOpen?: boolean;
previewWorkspaceDocument?: unknown; previewWorkspaceDocument?: unknown;
previewWorkspaceEntry?: unknown; documentLink?: unknown;
previewDocumentId?: Identifier | null; previewDocumentId?: Identifier | null;
ensureAssetUrl?: EnsureAssetUrl; ensureAssetUrl?: EnsureAssetUrl;
ensurePreviewData?: EnsurePreviewData; ensurePreviewData?: EnsurePreviewData;
@@ -45,7 +45,7 @@ export const useWorkspaceSurface = ({
detailPanelProps, detailPanelProps,
detailPanelOpen = false, detailPanelOpen = false,
previewWorkspaceDocument, previewWorkspaceDocument,
previewWorkspaceEntry, documentLink,
previewDocumentId, previewDocumentId,
ensureAssetUrl, ensureAssetUrl,
ensurePreviewData, ensurePreviewData,
@@ -122,7 +122,7 @@ export const useWorkspaceSurface = ({
} = detailExtras; } = detailExtras;
return createDocumentViewerSurface({ return createDocumentViewerSurface({
document: previewWorkspaceDocument, document: previewWorkspaceDocument,
previewEntry: previewWorkspaceEntry, documentLink,
ensureAssetUrl, ensureAssetUrl,
ensurePreviewData, ensurePreviewData,
getDocumentAsset, getDocumentAsset,
@@ -144,7 +144,7 @@ export const useWorkspaceSurface = ({
}, [ }, [
showPreviewWorkspace, showPreviewWorkspace,
previewWorkspaceDocument, previewWorkspaceDocument,
previewWorkspaceEntry, documentLink,
ensureAssetUrl, ensureAssetUrl,
ensurePreviewData, ensurePreviewData,
getDocumentAsset, getDocumentAsset,
+8 -8
View File
@@ -32,14 +32,14 @@ import '../styles/workspace/workspace-cards.css';
type Identifier = string | number; type Identifier = string | number;
type TagLike = { id?: Identifier | null; label?: string; color?: string | null } | null; type TagLike = { id?: Identifier | null; label?: string; color?: string | null } | null;
type PreviewEntryLike = { url?: string | null; contentType?: string | null }; type DocumentLinkLike = { url?: string | null; contentType?: string | null };
type OverlaySource = { url: string; alt?: string | null; contentType?: string | null }; type OverlaySource = { url: string; alt?: string | null; contentType?: string | null };
export interface DeskDocument { export interface DeskDocument {
id?: Identifier | null; id?: Identifier | null;
title?: string; title?: string;
tags?: TagLike[] | null; tags?: TagLike[] | null;
previewEntry?: OverlaySource | null; documentLink?: OverlaySource | null;
[key: string]: unknown; [key: string]: unknown;
} }
@@ -208,7 +208,7 @@ const DesktopWorkspace: React.FC<DesktopWorkspaceProps> = ({
onClearSelection = null, onClearSelection = null,
tenantId = null, tenantId = null,
viewId = 'default', viewId = 'default',
previewEntries, documentLinks,
ensureDownloadUrl, ensureDownloadUrl,
}) => { }) => {
const items = useMemo<DeskDocument[]>( const items = useMemo<DeskDocument[]>(
@@ -217,7 +217,7 @@ const DesktopWorkspace: React.FC<DesktopWorkspaceProps> = ({
); );
const allowLayoutPersistence = Boolean(tenantId && viewId && viewId.startsWith('folder:')); const allowLayoutPersistence = Boolean(tenantId && viewId && viewId.startsWith('folder:'));
const previewEntryMap = previewEntries instanceof Map ? previewEntries : null; const documentLinkMap = documentLinks instanceof Map ? documentLinks : null;
const containerRef = useRef<HTMLDivElement | null>(null); const containerRef = useRef<HTMLDivElement | null>(null);
const itemRefs = useRef<Map<string, HTMLDivElement | null>>(new Map()); const itemRefs = useRef<Map<string, HTMLDivElement | null>>(new Map());
@@ -562,7 +562,7 @@ const DesktopWorkspace: React.FC<DesktopWorkspaceProps> = ({
const docContentType = doc?.content_type ?? null; const docContentType = doc?.content_type ?? null;
const versionContentType = (doc?.current_version as { version?: { content_type?: string | null } } | null)?.version?.content_type ?? null; const versionContentType = (doc?.current_version as { version?: { content_type?: string | null } } | null)?.version?.content_type ?? null;
const applyEntry = (entry?: PreviewEntryLike | null) => { const applyEntry = (entry?: DocumentLinkLike | null) => {
if (!entry?.url) { if (!entry?.url) {
setOverlaySource(null); setOverlaySource(null);
return; return;
@@ -574,7 +574,7 @@ const DesktopWorkspace: React.FC<DesktopWorkspaceProps> = ({
}); });
}; };
const cachedEntry = previewEntryMap?.get(docIdentifier) || null; const cachedEntry = documentLinkMap?.get(docIdentifier) || null;
if (cachedEntry?.url) { if (cachedEntry?.url) {
applyEntry(cachedEntry); applyEntry(cachedEntry);
return () => { return () => {
@@ -605,7 +605,7 @@ const DesktopWorkspace: React.FC<DesktopWorkspaceProps> = ({
return () => { return () => {
cancelled = true; cancelled = true;
}; };
}, [overlayDocId, documentLookup, previewEntryMap, ensureDownloadUrl]); }, [overlayDocId, documentLookup, documentLinkMap, ensureDownloadUrl]);
const closeOverlay = useCallback(() => { const closeOverlay = useCallback(() => {
setOverlayDocId(null); setOverlayDocId(null);
@@ -666,7 +666,7 @@ const DesktopWorkspace: React.FC<DesktopWorkspaceProps> = ({
} }
const baseDoc = documentLookup.get(String(overlayDocId)) || null; const baseDoc = documentLookup.get(String(overlayDocId)) || null;
if (baseDoc && overlayDisplay?.url) { if (baseDoc && overlayDisplay?.url) {
return { ...baseDoc, previewEntry: overlayDisplay }; return { ...baseDoc, documentLink: overlayDisplay };
} }
return baseDoc; return baseDoc;
}, [documentLookup, overlayDisplay, overlayDocId]); }, [documentLookup, overlayDisplay, overlayDocId]);
+18 -18
View File
@@ -20,15 +20,15 @@ type NaturalSize = { width: number | null; height: number | null };
type FocusPoint = { xRatio: number; yRatio: number } | null; type FocusPoint = { xRatio: number; yRatio: number } | null;
type DisplayKind = 'image' | 'pdf'; type DisplayKind = 'image' | 'pdf';
type PreviewEntry = { type DocumentLink = {
url: string; url: string;
alt?: string; alt?: string;
contentType?: string | null; contentType?: string | null;
}; };
type DocumentLikeWithPreview = DocumentLike & { previewEntry?: PreviewEntry }; type DocumentLikeWithPreview = DocumentLike & { documentLink?: DocumentLink };
const determineDisplayKind = (entry?: PreviewEntry | null): DisplayKind => { const determineDisplayKind = (entry?: DocumentLink | null): DisplayKind => {
const type = entry?.contentType?.toLowerCase?.() || ''; const type = entry?.contentType?.toLowerCase?.() || '';
if (type.includes('pdf')) { if (type.includes('pdf')) {
return 'pdf'; return 'pdf';
@@ -66,17 +66,17 @@ const PreviewZoomOverlay: React.FC<PreviewZoomOverlayProps> = ({
const currentDocument = overlayDocument as DocumentLikeWithPreview | null; const currentDocument = overlayDocument as DocumentLikeWithPreview | null;
useEffect(() => { useEffect(() => {
if (currentDocument?.previewEntry?.url) { if (currentDocument?.documentLink?.url) {
setDocumentSnapshot(currentDocument); setDocumentSnapshot(currentDocument);
} }
}, [currentDocument]); }, [currentDocument]);
const activeDocument = open && currentDocument?.previewEntry?.url ? currentDocument : documentSnapshot; const activeDocument = open && currentDocument?.documentLink?.url ? currentDocument : documentSnapshot;
const previewEntry = activeDocument?.previewEntry || null; const documentLink = activeDocument?.documentLink || null;
const displayKind = useMemo(() => determineDisplayKind(previewEntry), [previewEntry]); const displayKind = useMemo(() => determineDisplayKind(documentLink), [documentLink]);
const isPdfDisplay = displayKind === 'pdf'; const isPdfDisplay = displayKind === 'pdf';
const documentTitle = activeDocument?.title || undefined; const documentTitle = activeDocument?.title || undefined;
const effectiveAlt = previewEntry?.alt || documentTitle || 'Document preview'; const effectiveAlt = documentLink?.alt || documentTitle || 'Document preview';
useEffect(() => { useEffect(() => {
if (visibilityTimerRef.current) { if (visibilityTimerRef.current) {
@@ -88,7 +88,7 @@ const PreviewZoomOverlay: React.FC<PreviewZoomOverlayProps> = ({
displayTimerRef.current = null; displayTimerRef.current = null;
} }
if (open && previewEntry?.url) { if (open && documentLink?.url) {
setRenderBackdrop(true); setRenderBackdrop(true);
displayTimerRef.current = requestAnimationFrame(() => { displayTimerRef.current = requestAnimationFrame(() => {
displayTimerRef.current = requestAnimationFrame(() => { displayTimerRef.current = requestAnimationFrame(() => {
@@ -114,7 +114,7 @@ const PreviewZoomOverlay: React.FC<PreviewZoomOverlayProps> = ({
visibilityTimerRef.current = null; visibilityTimerRef.current = null;
} }
}; };
}, [open, previewEntry?.url]); }, [open, documentLink?.url]);
useEffect(() => () => { useEffect(() => () => {
if (visibilityTimerRef.current) { if (visibilityTimerRef.current) {
@@ -126,7 +126,7 @@ const PreviewZoomOverlay: React.FC<PreviewZoomOverlayProps> = ({
}, []); }, []);
useEffect(() => { useEffect(() => {
}, [isPdfDisplay, open, previewEntry?.url]); }, [isPdfDisplay, open, documentLink?.url]);
useEffect(() => { useEffect(() => {
setIsNativeScale(false); setIsNativeScale(false);
@@ -137,7 +137,7 @@ const PreviewZoomOverlay: React.FC<PreviewZoomOverlayProps> = ({
scrollEl.scrollLeft = 0; scrollEl.scrollLeft = 0;
scrollEl.scrollTop = 0; scrollEl.scrollTop = 0;
} }
}, [open, previewEntry?.url, displayKind]); }, [open, documentLink?.url, displayKind]);
useEffect(() => { useEffect(() => {
@@ -180,7 +180,7 @@ const PreviewZoomOverlay: React.FC<PreviewZoomOverlayProps> = ({
}, [open]); }, [open]);
useEffect(() => { useEffect(() => {
if (!previewEntry?.url) { if (!documentLink?.url) {
return; return;
} }
@@ -190,10 +190,10 @@ const PreviewZoomOverlay: React.FC<PreviewZoomOverlayProps> = ({
scrollEl.scrollTop = 0; scrollEl.scrollTop = 0;
} }
focusRef.current = null; focusRef.current = null;
}, [previewEntry?.url]); }, [documentLink?.url]);
useEffect(() => { useEffect(() => {
if (!renderBackdrop || !previewEntry?.url) { if (!renderBackdrop || !documentLink?.url) {
return undefined; return undefined;
} }
@@ -202,7 +202,7 @@ const PreviewZoomOverlay: React.FC<PreviewZoomOverlayProps> = ({
}); });
return () => cancelAnimationFrame(frame); return () => cancelAnimationFrame(frame);
}, [renderBackdrop, previewEntry?.url]); }, [renderBackdrop, documentLink?.url]);
const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => { const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
event.stopPropagation(); event.stopPropagation();
@@ -267,7 +267,7 @@ const PreviewZoomOverlay: React.FC<PreviewZoomOverlayProps> = ({
toggleZoomAtPoint(event.clientX ?? 0, event.clientY ?? 0); toggleZoomAtPoint(event.clientX ?? 0, event.clientY ?? 0);
}; };
const shouldRender = renderBackdrop && Boolean(previewEntry?.url); const shouldRender = renderBackdrop && Boolean(documentLink?.url);
const stageClassName = isPdfDisplay const stageClassName = isPdfDisplay
? 'preview-zoom__stage preview-zoom__stage--pdf' ? 'preview-zoom__stage preview-zoom__stage--pdf'
@@ -308,7 +308,7 @@ const PreviewZoomOverlay: React.FC<PreviewZoomOverlayProps> = ({
return null; return null;
} }
const effectiveDisplay = previewEntry; const effectiveDisplay = documentLink;
return createPortal( return createPortal(
( (
+9 -21
View File
@@ -25,7 +25,7 @@ interface FolderNode {
parentId?: Identifier | 'root'; parentId?: Identifier | 'root';
} }
type PreviewEntry = { type DocumentLink = {
url?: string; url?: string;
contentType?: string | null; contentType?: string | null;
} | null; } | null;
@@ -41,7 +41,7 @@ interface UseDetailWorkspaceArgs {
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?: (args?: { documentIds?: Identifier[] }) => void; close?: () => void } | null>;
detailFolderFetchRef: MutableRefObject<Set<Identifier | 'root'>>; detailFolderFetchRef: MutableRefObject<Set<Identifier | 'root'>>;
previewEntries: Map<Identifier, PreviewEntry>; documentLinks: Map<Identifier, DocumentLink>;
previewDocumentId?: Identifier | null; previewDocumentId?: Identifier | null;
activePreviewId?: Identifier | null; activePreviewId?: Identifier | null;
openDocumentPreview?: (args: { documentIds: Identifier[] }) => void; openDocumentPreview?: (args: { documentIds: Identifier[] }) => void;
@@ -70,7 +70,7 @@ interface UseDetailWorkspaceResult {
inspectDocument: (docId: Identifier | null) => void; inspectDocument: (docId: Identifier | null) => void;
previewActive: boolean; previewActive: boolean;
previewWorkspaceDocument: DocumentLike | null; previewWorkspaceDocument: DocumentLike | null;
previewWorkspaceEntry: PreviewEntry; documentLink: DocumentLink;
resolveThumbnailUrlForDoc: (doc: DocumentLike | null) => string | null; resolveThumbnailUrlForDoc: (doc: DocumentLike | null) => string | null;
resolveFolderPath: (folderId?: Identifier | 'root') => Array<{ id: Identifier | 'root'; name: string }>; resolveFolderPath: (folderId?: Identifier | 'root') => Array<{ id: Identifier | 'root'; name: string }>;
} }
@@ -86,7 +86,7 @@ const useDetailWorkspace = ({
ensureFolderData, ensureFolderData,
detailPanelControlRef, detailPanelControlRef,
detailFolderFetchRef, detailFolderFetchRef,
previewEntries, documentLinks,
previewDocumentId, previewDocumentId,
activePreviewId, activePreviewId,
openDocumentPreview, openDocumentPreview,
@@ -245,21 +245,9 @@ const useDetailWorkspace = ({
[folderNodes], [folderNodes],
); );
const detailPanelPreviewEntry = useMemo(() => { const documentLink = useMemo(() => (detailPanelDocument ? documentLinks.get(detailPanelDocument.id) || null : null), [detailPanelDocument, documentLinks]);
if (!detailPanelDocument) {
return null;
}
return previewEntries.get(detailPanelDocument.id) || null;
}, [detailPanelDocument, previewEntries]);
const previewWorkspaceEntry = useMemo(() => { const previewWorkspaceDocument = useMemo(() => {
if (!previewDocumentId) {
return null;
}
return previewEntries.get(previewDocumentId) || null;
}, [previewDocumentId, previewEntries]);
const previewWorkspaceDocument = useMemo(() => {
if (!previewDocumentId) { if (!previewDocumentId) {
return null; return null;
} }
@@ -301,7 +289,7 @@ const useDetailWorkspace = ({
tagLookupById, tagLookupById,
onTagAdd: handleDocumentTagAdd, onTagAdd: handleDocumentTagAdd,
onTagRemove: handleTagRemove, onTagRemove: handleTagRemove,
previewEntry: detailPanelPreviewEntry, documentLink,
onOpenPreview: openDocumentPreview, onOpenPreview: openDocumentPreview,
activePreviewId, activePreviewId,
onUpdateTitle: handleDocumentTitleUpdate, onUpdateTitle: handleDocumentTitleUpdate,
@@ -335,7 +323,7 @@ const useDetailWorkspace = ({
resolveApiPath, resolveApiPath,
resolveFolderPath, resolveFolderPath,
selectFolder, selectFolder,
detailPanelPreviewEntry, documentLink,
tags, tags,
tagLookupById, tagLookupById,
], ],
@@ -350,7 +338,7 @@ const useDetailWorkspace = ({
inspectDocument, inspectDocument,
previewActive, previewActive,
previewWorkspaceDocument, previewWorkspaceDocument,
previewWorkspaceEntry, documentLink,
resolveThumbnailUrlForDoc, resolveThumbnailUrlForDoc,
resolveFolderPath, resolveFolderPath,
}; };
@@ -2,7 +2,7 @@ import { useMemo } from 'react';
type Identifier = string | number; type Identifier = string | number;
interface PreviewEntryLike { interface DocumentLinkLike {
url?: string | null; url?: string | null;
contentType?: string | null; contentType?: string | null;
} }
@@ -70,8 +70,8 @@ export interface UseDocumentsPanelPropsArgs {
handleBulkSelectionReanalyze?: (...args: unknown[]) => void; handleBulkSelectionReanalyze?: (...args: unknown[]) => void;
folderOptions?: unknown[]; folderOptions?: unknown[];
moveDocumentsToFolder?: (...args: unknown[]) => void; moveDocumentsToFolder?: (...args: unknown[]) => void;
previewEntries?: Map<Identifier, PreviewEntryLike>; documentLinks?: Map<Identifier, DocumentLinkLike>;
ensureDownloadUrl?: (documentId: Identifier, options?: { force?: boolean }) => Promise<PreviewEntryLike | null>; ensureDownloadUrl?: (documentId: Identifier, options?: { force?: boolean }) => Promise<DocumentLinkLike | null>;
} }
export type DocumentsPanelProps = ReturnType<typeof useDocumentsPanelProps>; export type DocumentsPanelProps = ReturnType<typeof useDocumentsPanelProps>;
@@ -126,7 +126,7 @@ const useDocumentsPanelProps = (props: UseDocumentsPanelPropsArgs) => {
handleBulkSelectionReanalyze, handleBulkSelectionReanalyze,
folderOptions, folderOptions,
moveDocumentsToFolder, moveDocumentsToFolder,
previewEntries, documentLinks,
ensureDownloadUrl, ensureDownloadUrl,
} = props; } = props;
@@ -182,7 +182,7 @@ const useDocumentsPanelProps = (props: UseDocumentsPanelPropsArgs) => {
onBulkReanalyze: handleBulkSelectionReanalyze, onBulkReanalyze: handleBulkSelectionReanalyze,
folderOptions, folderOptions,
onMoveDocumentsToFolder: moveDocumentsToFolder, onMoveDocumentsToFolder: moveDocumentsToFolder,
previewEntries, documentLinks,
ensureDownloadUrl, ensureDownloadUrl,
}), }),
[ [
@@ -234,7 +234,7 @@ const useDocumentsPanelProps = (props: UseDocumentsPanelPropsArgs) => {
ensureAssetUrl, ensureAssetUrl,
getDocumentAsset, getDocumentAsset,
folderOptions, folderOptions,
previewEntries, documentLinks,
ensureDownloadUrl, ensureDownloadUrl,
], ],
); );
@@ -27,7 +27,7 @@ interface DocumentsPanelProps {
const defaultGetDocumentAsset = (_doc?: unknown, _type?: string) => null; const defaultGetDocumentAsset = (_doc?: unknown, _type?: string) => null;
export type PreviewEntryLike = { url?: string | null; contentType?: string | null }; export type DocumentLinkLike = { url?: string | null; contentType?: string | null };
const DocumentsPanel: React.FC<DocumentsPanelProps> = ({ const DocumentsPanel: React.FC<DocumentsPanelProps> = ({
headerConfig, headerConfig,
@@ -62,7 +62,7 @@ const DocumentsPanel: React.FC<DocumentsPanelProps> = ({
onDocumentTagDrop, onDocumentTagDrop,
viewMode = 'list', viewMode = 'list',
onViewModeChange: _onViewModeChange, onViewModeChange: _onViewModeChange,
previewEntries, documentLinks,
ensureDownloadUrl, ensureDownloadUrl,
deskWorkspaceProps = null, deskWorkspaceProps = null,
}): ReactNode => { }): ReactNode => {
@@ -75,7 +75,7 @@ const DocumentsPanel: React.FC<DocumentsPanelProps> = ({
} = useWorkspaceSelectionContext(); } = useWorkspaceSelectionContext();
const showingSearchResults = searchResults !== null; const showingSearchResults = searchResults !== null;
const rows = showingSearchResults ? searchResults : documents; const rows = showingSearchResults ? searchResults : documents;
const previewEntryMap = previewEntries instanceof Map ? previewEntries : null; const documentLinkMap = documentLinks instanceof Map ? documentLinks : null;
const currentFolderId = useMemo(() => { const currentFolderId = useMemo(() => {
if (showingSearchResults) { if (showingSearchResults) {
@@ -159,7 +159,7 @@ const DocumentsPanel: React.FC<DocumentsPanelProps> = ({
const zoomDisplay = previewZoomSource; const zoomDisplay = previewZoomSource;
const overlayDocument = useMemo(() => ( const overlayDocument = useMemo(() => (
previewDoc && zoomDisplay?.url previewDoc && zoomDisplay?.url
? { ...previewDoc, previewEntry: zoomDisplay } ? { ...previewDoc, documentLink: zoomDisplay }
: previewDoc : previewDoc
), [previewDoc, zoomDisplay]); ), [previewDoc, zoomDisplay]);
@@ -175,7 +175,7 @@ const DocumentsPanel: React.FC<DocumentsPanelProps> = ({
const versionContentType = previewDoc.current_version?.version?.content_type; const versionContentType = previewDoc.current_version?.version?.content_type;
const contentFallback = docContentType || versionContentType || null; const contentFallback = docContentType || versionContentType || null;
const applyEntry = (entry?: PreviewEntryLike | null) => { const applyEntry = (entry?: DocumentLinkLike | null) => {
if (!entry?.url) { if (!entry?.url) {
setPreviewZoomSource(null); setPreviewZoomSource(null);
return; return;
@@ -187,7 +187,7 @@ const DocumentsPanel: React.FC<DocumentsPanelProps> = ({
}); });
}; };
const cachedEntry = previewEntryMap?.get(previewDocId) || null; const cachedEntry = documentLinkMap?.get(previewDocId) || null;
if (cachedEntry?.url) { if (cachedEntry?.url) {
applyEntry(cachedEntry); applyEntry(cachedEntry);
return () => { return () => {
@@ -218,7 +218,7 @@ const DocumentsPanel: React.FC<DocumentsPanelProps> = ({
return () => { return () => {
cancelled = true; cancelled = true;
}; };
}, [previewDocId, previewDoc, previewEntryMap, ensureDownloadUrl]); }, [previewDocId, previewDoc, documentLinkMap, ensureDownloadUrl]);
const closePreviewOverlay = useCallback(() => { const closePreviewOverlay = useCallback(() => {
setPreviewDocId(null); setPreviewDocId(null);
@@ -230,12 +230,12 @@ const DocumentsPanel: React.FC<DocumentsPanelProps> = ({
if (!doc || !doc.id) { if (!doc || !doc.id) {
return; return;
} }
if (!ensureDownloadUrl && !(previewEntryMap?.get(doc.id)?.url)) { if (!ensureDownloadUrl && !(documentLinkMap?.get(doc.id)?.url)) {
return; return;
} }
setPreviewDocId(doc.id); setPreviewDocId(doc.id);
}, },
[ensureDownloadUrl, previewEntryMap], [ensureDownloadUrl, documentLinkMap],
); );
const handleDocumentActivate = useCallback( const handleDocumentActivate = useCallback(
@@ -375,14 +375,14 @@ const useDocumentsWorkspace = ({
}, [setSearchResults]); }, [setSearchResults]);
const { const {
previewEntries, documentLinks,
previewDocuments, previewDocuments,
ensurePreviewData, ensurePreviewData,
ensureDownloadUrl, ensureDownloadUrl,
openDocumentPreview, openDocumentPreview,
closeDocumentPreview, closeDocumentPreview,
resetPreviewState, resetPreviewState,
removePreviewEntries, removeDocumentLinks,
} = useDocumentPreview({ } = useDocumentPreview({
routeDocumentId: previewDocumentId, routeDocumentId: previewDocumentId,
documents, documents,
@@ -793,9 +793,9 @@ const useDocumentsWorkspace = ({
return changed ? next : prev; return changed ? next : prev;
}); });
removePreviewEntries(Array.from(idSet)); removeDocumentLinks(Array.from(idSet));
}, },
[setDocuments, setSearchResults, setFolderContents, removePreviewEntries], [setDocuments, setSearchResults, setFolderContents, removeDocumentLinks],
); );
const { const {
@@ -1235,7 +1235,7 @@ const useDocumentsWorkspace = ({
inspectDocument, inspectDocument,
previewActive, previewActive,
previewWorkspaceDocument, previewWorkspaceDocument,
previewWorkspaceEntry, documentLink,
resolveFolderPath, resolveFolderPath,
} = useDetailWorkspace({ } = useDetailWorkspace({
documents, documents,
@@ -1248,7 +1248,7 @@ const useDocumentsWorkspace = ({
ensureFolderData, ensureFolderData,
detailPanelControlRef, detailPanelControlRef,
detailFolderFetchRef, detailFolderFetchRef,
previewEntries, documentLinks,
previewDocumentId, previewDocumentId,
activePreviewId, activePreviewId,
openDocumentPreview: openDocumentPreviewForDetail, openDocumentPreview: openDocumentPreviewForDetail,
@@ -1465,7 +1465,7 @@ const useDocumentsWorkspace = ({
onClearSelection: clearDocumentSelection, onClearSelection: clearDocumentSelection,
tenantId: currentTenantId, tenantId: currentTenantId,
viewId: deskViewId, viewId: deskViewId,
previewEntries, documentLinks,
ensureDownloadUrl, ensureDownloadUrl,
}), }),
[ [
@@ -1483,7 +1483,7 @@ const useDocumentsWorkspace = ({
clearDocumentSelection, clearDocumentSelection,
currentTenantId, currentTenantId,
deskViewId, deskViewId,
previewEntries, documentLinks,
ensureDownloadUrl, ensureDownloadUrl,
], ],
); );
@@ -1537,7 +1537,7 @@ const useDocumentsWorkspace = ({
folderOptions, folderOptions,
moveDocumentsToFolder, moveDocumentsToFolder,
selectFolder, selectFolder,
previewEntries, documentLinks,
ensureDownloadUrl, ensureDownloadUrl,
}); });
@@ -1619,7 +1619,7 @@ const useDocumentsWorkspace = ({
revokePasskey, revokePasskey,
previewActive, previewActive,
previewWorkspaceDocument, previewWorkspaceDocument,
previewWorkspaceEntry, documentLink,
previewDocumentId, previewDocumentId,
closeDocumentPreview, closeDocumentPreview,
handleThumbnailRegeneration, handleThumbnailRegeneration,
@@ -1670,7 +1670,7 @@ const useDocumentsWorkspace = ({
revokePasskey, revokePasskey,
previewActive, previewActive,
previewWorkspaceDocument, previewWorkspaceDocument,
previewWorkspaceEntry, documentLink,
previewDocumentId, previewDocumentId,
closeDocumentPreview, closeDocumentPreview,
handleThumbnailRegeneration, handleThumbnailRegeneration,
+16 -16
View File
@@ -13,7 +13,7 @@ interface DocumentLike {
[key: string]: unknown; [key: string]: unknown;
} }
interface PreviewEntry { interface DocumentLink {
url?: string; url?: string;
contentType?: string; contentType?: string;
filename?: string; filename?: string;
@@ -36,7 +36,7 @@ type LayoutMode = 'split' | 'stacked' | (string & {});
interface DocumentViewerLayoutProps { interface DocumentViewerLayoutProps {
document?: DocumentLike | null; document?: DocumentLike | null;
previewEntry?: PreviewEntry | null; documentLink?: DocumentLink | null;
summaryProps?: Record<string, unknown>; summaryProps?: Record<string, unknown>;
metadataPayload?: unknown; metadataPayload?: unknown;
contentTabConfig?: ContentTabConfig | null; contentTabConfig?: ContentTabConfig | null;
@@ -81,7 +81,7 @@ const getFileExtension = (filename?: string | null) => {
const DocumentViewerLayout = ({ const DocumentViewerLayout = ({
document, document,
previewEntry, documentLink,
summaryProps = {}, summaryProps = {},
metadataPayload, metadataPayload,
contentTabConfig, contentTabConfig,
@@ -96,15 +96,15 @@ const DocumentViewerLayout = ({
const viewportRef = useRef<HTMLDivElement>(null); const viewportRef = useRef<HTMLDivElement>(null);
const previewContent = useMemo(() => { const previewContent = useMemo(() => {
if (!document || !previewEntry?.url) { if (!document || !documentLink?.url) {
return null; return null;
} }
const normalizedContentType = (previewEntry.contentType const normalizedContentType = (documentLink.contentType
|| document.content_type || document.content_type
|| '') || '')
.toLowerCase(); .toLowerCase();
const normalizedFilename = previewEntry.filename const normalizedFilename = documentLink.filename
|| document.filename || document.filename
|| document.original_name || document.original_name
|| ''; || '';
@@ -123,7 +123,7 @@ const DocumentViewerLayout = ({
if (isImage) { if (isImage) {
return ( return (
<img <img
src={previewEntry.url} src={documentLink.url}
alt={`Preview of ${document.title}`} alt={`Preview of ${document.title}`}
className="document-viewer__object document-viewer__object--image" className="document-viewer__object document-viewer__object--image"
draggable={false} draggable={false}
@@ -137,7 +137,7 @@ const DocumentViewerLayout = ({
|| document.original_name; || document.original_name;
return ( return (
<PdfViewer <PdfViewer
src={previewEntry.url} src={documentLink.url}
title={documentTitle ? `Preview of ${documentTitle}` : undefined} title={documentTitle ? `Preview of ${documentTitle}` : undefined}
viewportRef={viewportRef} viewportRef={viewportRef}
/> />
@@ -150,7 +150,7 @@ const DocumentViewerLayout = ({
className="document-viewer__object document-viewer__object--audio" className="document-viewer__object document-viewer__object--audio"
controls controls
preload="metadata" preload="metadata"
src={previewEntry.url} src={documentLink.url}
aria-label={`Audio preview of ${mediaLabel}`} aria-label={`Audio preview of ${mediaLabel}`}
> >
</audio> </audio>
@@ -163,15 +163,15 @@ const DocumentViewerLayout = ({
className="document-viewer__object document-viewer__object--video" className="document-viewer__object document-viewer__object--video"
controls controls
preload="metadata" preload="metadata"
src={previewEntry.url} src={documentLink.url}
aria-label={`Video preview of ${mediaLabel}`} aria-label={`Video preview of ${mediaLabel}`}
> >
</video> </video>
); );
} }
const displayContentType = document.content_type || previewEntry.contentType || 'this file type'; const displayContentType = document.content_type || documentLink.contentType || 'this file type';
const displayFilename = previewEntry.filename const displayFilename = documentLink.filename
|| document.filename || document.filename
|| document.original_name || document.original_name
|| 'download'; || 'download';
@@ -184,7 +184,7 @@ const DocumentViewerLayout = ({
<div className="document-viewer__unsupported-filename">{displayFilename}</div> <div className="document-viewer__unsupported-filename">{displayFilename}</div>
<a <a
className="button-link document-viewer__unsupported-download" className="button-link document-viewer__unsupported-download"
href={previewEntry.url} href={documentLink.url}
download={displayFilename} download={displayFilename}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
@@ -194,17 +194,17 @@ const DocumentViewerLayout = ({
</a> </a>
</div> </div>
); );
}, [document, previewEntry, viewportRef]); }, [document, documentLink, viewportRef]);
const renderViewportPane = useCallback(() => ( const renderViewportPane = useCallback(() => (
<div className="document-viewer__viewport" ref={viewportRef}> <div className="document-viewer__viewport" ref={viewportRef}>
{!previewEntry?.url ? ( {!documentLink?.url ? (
<div className="document-viewer__message">{previewLoadingMessage}</div> <div className="document-viewer__message">{previewLoadingMessage}</div>
) : ( ) : (
previewContent previewContent
)} )}
</div> </div>
), [previewContent, previewEntry?.url, previewLoadingMessage, viewportRef]); ), [previewContent, documentLink?.url, previewLoadingMessage, viewportRef]);
const viewportPane = renderViewportPane(); const viewportPane = renderViewportPane();
+17 -17
View File
@@ -39,7 +39,7 @@ interface DocumentLike {
version_number?: number; version_number?: number;
version?: { content_type?: string | null } | null; version?: { content_type?: string | null } | null;
} | null; } | null;
previewEntry?: { documentLink?: {
url: string; url: string;
alt?: string; alt?: string;
contentType?: string | null; contentType?: string | null;
@@ -56,7 +56,7 @@ interface AssetLike {
interface DocumentViewerPanelProps extends DocumentSummarySectionProps { interface DocumentViewerPanelProps extends DocumentSummarySectionProps {
document: DocumentLike | null; document: DocumentLike | null;
previewEntry?: { documentLink?: {
url?: string; url?: string;
contentType?: string | null; contentType?: string | null;
filename?: string | null; filename?: string | null;
@@ -79,7 +79,7 @@ interface DocumentViewerPanelProps extends DocumentSummarySectionProps {
export const createDocumentViewerHeaderActions = ({ export const createDocumentViewerHeaderActions = ({
document, document,
actionState, actionState,
previewEntry, documentLink,
onZoom, onZoom,
canZoom = false, canZoom = false,
}) => { }) => {
@@ -87,7 +87,7 @@ export const createDocumentViewerHeaderActions = ({
return null; return null;
} }
const downloadHref = actionState?.downloadHref || previewEntry?.url; const downloadHref = actionState?.downloadHref || documentLink?.url;
if (!downloadHref && !(canZoom && onZoom)) { if (!downloadHref && !(canZoom && onZoom)) {
return null; return null;
} }
@@ -123,7 +123,7 @@ export const createDocumentViewerHeaderActions = ({
const DocumentViewerPanel: React.FC<DocumentViewerPanelProps> = ({ const DocumentViewerPanel: React.FC<DocumentViewerPanelProps> = ({
document, document,
previewEntry, documentLink,
hydrateDocument, hydrateDocument,
tagLookupById, tagLookupById,
tagOptions, tagOptions,
@@ -254,11 +254,11 @@ const DocumentViewerPanel: React.FC<DocumentViewerPanelProps> = ({
const [zoomOverlayOpen, setZoomOverlayOpen] = useState(false); const [zoomOverlayOpen, setZoomOverlayOpen] = useState(false);
const handleZoomOpen = useCallback(() => { const handleZoomOpen = useCallback(() => {
if (!previewEntry?.url) { if (!documentLink?.url) {
return; return;
} }
setZoomOverlayOpen(true); setZoomOverlayOpen(true);
}, [previewEntry?.url]); }, [documentLink?.url]);
const handleZoomClose = useCallback(() => { const handleZoomClose = useCallback(() => {
setZoomOverlayOpen(false); setZoomOverlayOpen(false);
@@ -266,7 +266,7 @@ const DocumentViewerPanel: React.FC<DocumentViewerPanelProps> = ({
useEffect(() => { useEffect(() => {
setZoomOverlayOpen(false); setZoomOverlayOpen(false);
}, [previewEntry?.url, document?.id]); }, [documentLink?.url, document?.id]);
useEffect(() => { useEffect(() => {
if (hydrateDocument && document?.id) { if (hydrateDocument && document?.id) {
@@ -355,23 +355,23 @@ const DocumentViewerPanel: React.FC<DocumentViewerPanelProps> = ({
}, [breadcrumbs, handleBreadcrumbNavigate]); }, [breadcrumbs, handleBreadcrumbNavigate]);
const zoomDisplay = useMemo(() => { const zoomDisplay = useMemo(() => {
if (!previewEntry?.url || !document) { if (!documentLink?.url || !document) {
return null; return null;
} }
const docContentType = document.content_type; const docContentType = document.content_type;
const versionContentType = document.current_version?.version?.content_type; const versionContentType = document.current_version?.version?.content_type;
const normalizedContentType = previewEntry.contentType || docContentType || versionContentType || null; const normalizedContentType = documentLink.contentType || docContentType || versionContentType || null;
return { return {
url: previewEntry.url, url: documentLink.url,
alt: document.title, alt: document.title,
contentType: normalizedContentType || undefined, contentType: normalizedContentType || undefined,
}; };
}, [previewEntry?.url, previewEntry?.contentType, document]); }, [documentLink?.url, documentLink?.contentType, document]);
const headerActions = createDocumentViewerHeaderActions({ const headerActions = createDocumentViewerHeaderActions({
document, document,
actionState, actionState,
previewEntry, documentLink,
onZoom: zoomDisplay ? handleZoomOpen : null, onZoom: zoomDisplay ? handleZoomOpen : null,
canZoom: Boolean(zoomDisplay), canZoom: Boolean(zoomDisplay),
}); });
@@ -471,7 +471,7 @@ const DocumentViewerPanel: React.FC<DocumentViewerPanelProps> = ({
<section className={viewerClassName}> <section className={viewerClassName}>
<DocumentViewerLayout <DocumentViewerLayout
document={document} document={document}
previewEntry={previewEntry} documentLink={documentLink}
summaryProps={summaryProps} summaryProps={summaryProps}
metadataPayload={metadataPayload} metadataPayload={metadataPayload}
contentTabConfig={contentTabConfig} contentTabConfig={contentTabConfig}
@@ -495,7 +495,7 @@ const DocumentViewerPanel: React.FC<DocumentViewerPanelProps> = ({
const overlayDocument = useMemo(() => ( const overlayDocument = useMemo(() => (
document && zoomDisplay?.url document && zoomDisplay?.url
? { ...document, previewEntry: zoomDisplay } ? { ...document, documentLink: zoomDisplay }
: document : document
), [document, zoomDisplay]); ), [document, zoomDisplay]);
@@ -549,7 +549,7 @@ export default DocumentViewerPanel;
export const createDocumentViewerSurface = ({ export const createDocumentViewerSurface = ({
document, document,
previewEntry, documentLink,
ensureAssetUrl, ensureAssetUrl,
ensurePreviewData, ensurePreviewData,
getDocumentAsset, getDocumentAsset,
@@ -581,7 +581,7 @@ export const createDocumentViewerSurface = ({
content: ( content: (
<DocumentViewerPanel <DocumentViewerPanel
document={document} document={document}
previewEntry={previewEntry} documentLink={documentLink}
hydrateDocument={ensurePreviewData} hydrateDocument={ensurePreviewData}
tagLookupById={tagLookupById} tagLookupById={tagLookupById}
tagOptions={tagOptions} tagOptions={tagOptions}