hydration
This commit is contained in:
@@ -1,123 +1,17 @@
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo } from 'react';
|
||||
import {
|
||||
DownloadIcon,
|
||||
ArrowLeftIcon,
|
||||
ArrowRightIcon,
|
||||
DetailPanelCollapseIcon,
|
||||
WindowMaximizeIcon,
|
||||
} from '../ui/icons';
|
||||
import PanelHeader from '../ui/PanelHeader';
|
||||
import { resolveDocumentAssetUrl, createAssetView } from '../asset_manager';
|
||||
import { useAssetNavigator } from '../hooks/useAssetNavigator';
|
||||
import { resolveDocumentAssetUrl } from '../asset_manager';
|
||||
import { describeDocumentSummary } from '../documents/documentSummary';
|
||||
import { createDocumentActionState } from '../documents/documentActions';
|
||||
import PreviewZoomOverlay from './PreviewZoomOverlay';
|
||||
import DocumentInfoPanel from '../documents/DocumentInfoPanel';
|
||||
import { sortCorrespondents, buildCorrespondentOptions } from '../documents/DocumentSummarySection';
|
||||
import BreadcrumbTrail from '../ui/BreadcrumbTrail';
|
||||
|
||||
const derivePreviewOrientation = (metadata) => {
|
||||
const width = Number(metadata?.width);
|
||||
const height = Number(metadata?.height);
|
||||
if (Number.isFinite(width) && Number.isFinite(height) && width > 0 && height > 0) {
|
||||
return width >= height ? 'landscape' : 'portrait';
|
||||
}
|
||||
return 'landscape';
|
||||
};
|
||||
|
||||
const PreviewImage = ({
|
||||
item,
|
||||
emptyMessage = 'Preview unavailable',
|
||||
emptyContent = null,
|
||||
onActivate,
|
||||
onOpenPreview,
|
||||
onZoomPreview,
|
||||
showNav = false,
|
||||
canGoPrev = false,
|
||||
canGoNext = false,
|
||||
onGoPrev = null,
|
||||
onGoNext = null,
|
||||
}) => {
|
||||
if (!item) {
|
||||
return (
|
||||
<div className="preview-image preview-image--empty">
|
||||
{emptyContent || <span className="meta">{emptyMessage}</span>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const handleActivate = (event) => {
|
||||
event.stopPropagation();
|
||||
if (onZoomPreview) {
|
||||
onZoomPreview(item);
|
||||
} else if (onOpenPreview) {
|
||||
onOpenPreview(item.id);
|
||||
} else if (onActivate) {
|
||||
onActivate(item.id);
|
||||
}
|
||||
};
|
||||
|
||||
const interceptNavPointer = (event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="preview-image">
|
||||
<img
|
||||
src={item.url}
|
||||
alt={item.alt}
|
||||
className={`preview-image__content orientation-${item.orientation || 'landscape'}`}
|
||||
onClick={handleActivate}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Enter' || event.key === ' ') {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
handleActivate(event);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{showNav ? (
|
||||
<div className="preview-pane__nav preview-pane__nav--overlay">
|
||||
<button
|
||||
type="button"
|
||||
className="preview-pane__nav-button preview-pane__nav-button--prev"
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
onGoPrev?.();
|
||||
}}
|
||||
onPointerDown={interceptNavPointer}
|
||||
onPointerUp={interceptNavPointer}
|
||||
onMouseDown={interceptNavPointer}
|
||||
onMouseUp={interceptNavPointer}
|
||||
disabled={!canGoPrev}
|
||||
aria-label="Previous preview"
|
||||
>
|
||||
<ArrowLeftIcon />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="preview-pane__nav-button preview-pane__nav-button--next"
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
onGoNext?.();
|
||||
}}
|
||||
onPointerDown={interceptNavPointer}
|
||||
onPointerUp={interceptNavPointer}
|
||||
onMouseDown={interceptNavPointer}
|
||||
onMouseUp={interceptNavPointer}
|
||||
disabled={!canGoNext}
|
||||
aria-label="Next preview"
|
||||
>
|
||||
<ArrowRightIcon />
|
||||
</button>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
import { extractDocumentMetadataPayload } from '../documents/documentMetadata';
|
||||
import DocumentViewerLayout from '../preview/DocumentViewerLayout';
|
||||
|
||||
const DetailPanel = ({
|
||||
document = null,
|
||||
@@ -126,7 +20,6 @@ const DetailPanel = ({
|
||||
onTagAdd,
|
||||
onTagRemove,
|
||||
onOpenPreview,
|
||||
onPromoteSelection,
|
||||
onUpdateTitle = async () => false,
|
||||
onUpdateIssued = async () => false,
|
||||
ensureAssetUrl = null,
|
||||
@@ -138,11 +31,10 @@ const DetailPanel = ({
|
||||
resolveApiPath,
|
||||
onFolderNavigate = null,
|
||||
resolveFolderPath = null,
|
||||
previewEntry = null,
|
||||
onClose = () => {},
|
||||
}) => {
|
||||
const singleDoc = document || null;
|
||||
const singleDocId = singleDoc?.id || null;
|
||||
const selectionKey = singleDocId || 'none';
|
||||
|
||||
const { downloadHref: singleDownloadHref } = useMemo(
|
||||
() =>
|
||||
@@ -192,81 +84,6 @@ const DetailPanel = ({
|
||||
];
|
||||
}, [singleDoc, resolveFolderPath, detailSummary, onFolderNavigate]);
|
||||
|
||||
const [zoomedPreview, setZoomedPreview] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
setZoomedPreview(null);
|
||||
}, [selectionKey]);
|
||||
|
||||
const handlePreviewActivate = useCallback(
|
||||
(docId) => {
|
||||
if (!docId) return;
|
||||
onPromoteSelection?.(docId);
|
||||
},
|
||||
[onPromoteSelection],
|
||||
);
|
||||
|
||||
const singlePreviewNavigator = useAssetNavigator({
|
||||
document: singleDoc,
|
||||
assetType: 'preview',
|
||||
ensureAssetUrl,
|
||||
getAsset: getDocumentAsset,
|
||||
prefetch: 3,
|
||||
});
|
||||
|
||||
const makePreviewItem = useCallback(
|
||||
(doc, ordinal = 1) => {
|
||||
if (!doc) return null;
|
||||
const asset = getDocumentAsset(doc, 'preview');
|
||||
const assetView = createAssetView(asset);
|
||||
const object = assetView.getObject(ordinal);
|
||||
let url = object?.url || null;
|
||||
if (!url) {
|
||||
url = resolveDocumentAssetUrl(doc, 'preview', {
|
||||
ensureAssetUrl,
|
||||
getAsset: getDocumentAsset,
|
||||
ensureOptions: { start: ordinal, limit: 1 },
|
||||
objectOrdinal: ordinal,
|
||||
});
|
||||
}
|
||||
if (!url) {
|
||||
return null;
|
||||
}
|
||||
const metadata = object?.metadata || assetView.getPrimaryMetadata() || {};
|
||||
const orientation = derivePreviewOrientation(metadata);
|
||||
return {
|
||||
id: doc.id,
|
||||
url,
|
||||
orientation,
|
||||
alt: doc.title,
|
||||
};
|
||||
},
|
||||
[ensureAssetUrl, getDocumentAsset],
|
||||
);
|
||||
|
||||
const singlePreviewItem = useMemo(() => {
|
||||
if (!singleDoc) return null;
|
||||
const url = singlePreviewNavigator.currentUrl;
|
||||
if (url) {
|
||||
return {
|
||||
id: singleDoc.id,
|
||||
url,
|
||||
orientation: derivePreviewOrientation(singlePreviewNavigator.currentMetadata),
|
||||
alt: singleDoc.title,
|
||||
};
|
||||
}
|
||||
return makePreviewItem(singleDoc, 1);
|
||||
}, [
|
||||
singleDoc,
|
||||
singlePreviewNavigator.currentUrl,
|
||||
singlePreviewNavigator.currentMetadata,
|
||||
makePreviewItem,
|
||||
]);
|
||||
|
||||
const singleCardinality = singlePreviewNavigator.cardinality;
|
||||
const singleEffectiveCardinality = singleCardinality || (singlePreviewNavigator.currentUrl ? 1 : 0);
|
||||
const singleHasPreview = Boolean(singlePreviewNavigator.currentUrl);
|
||||
|
||||
const correspondentOptions = useMemo(
|
||||
() => buildCorrespondentOptions(Array.isArray(correspondents) ? correspondents : []),
|
||||
[correspondents],
|
||||
@@ -277,6 +94,12 @@ const DetailPanel = ({
|
||||
return sortCorrespondents(singleDoc.correspondents || []);
|
||||
}, [singleDoc]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof ensurePreviewData === 'function' && singleDoc?.id) {
|
||||
ensurePreviewData(singleDoc.id);
|
||||
}
|
||||
}, [ensurePreviewData, singleDoc?.id]);
|
||||
|
||||
const singleSummaryProps = useMemo(
|
||||
() => ({
|
||||
tagLookupById,
|
||||
@@ -304,6 +127,11 @@ const DetailPanel = ({
|
||||
],
|
||||
);
|
||||
|
||||
const singleMetadataPayload = useMemo(
|
||||
() => extractDocumentMetadataPayload(singleDoc),
|
||||
[singleDoc],
|
||||
);
|
||||
|
||||
const singleHasOcr = useMemo(() => {
|
||||
if (!singleDoc || typeof getDocumentAsset !== 'function') {
|
||||
return false;
|
||||
@@ -365,183 +193,22 @@ const DetailPanel = ({
|
||||
[singleHasOcr, loadSingleOcrContent],
|
||||
);
|
||||
|
||||
const openZoomPreview = useCallback((docId) => {
|
||||
if (!docId) return;
|
||||
setZoomedPreview({ docId });
|
||||
}, []);
|
||||
|
||||
const closeZoomPreview = useCallback(() => {
|
||||
setZoomedPreview(null);
|
||||
}, []);
|
||||
|
||||
const handleSingleZoom = useCallback(
|
||||
(entry) => {
|
||||
if (!singleHasPreview) return;
|
||||
const targetId = entry?.id ?? singleDocId;
|
||||
if (!targetId) return;
|
||||
openZoomPreview(targetId);
|
||||
},
|
||||
[openZoomPreview, singleHasPreview, singleDocId],
|
||||
);
|
||||
|
||||
const zoomDisplay = useMemo(() => {
|
||||
if (
|
||||
!zoomedPreview
|
||||
|| !singleDoc
|
||||
|| !singleDocId
|
||||
|| zoomedPreview.docId !== singleDocId
|
||||
|| !singleHasPreview
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
url: singlePreviewNavigator.currentUrl,
|
||||
alt: singleDoc.title,
|
||||
canGoPrev:
|
||||
singleEffectiveCardinality > 1 && Boolean(singlePreviewNavigator.canGoPrev),
|
||||
canGoNext:
|
||||
singleEffectiveCardinality > 1 && Boolean(singlePreviewNavigator.canGoNext),
|
||||
goPrev: singlePreviewNavigator.goPrev,
|
||||
goNext: singlePreviewNavigator.goNext,
|
||||
};
|
||||
}, [
|
||||
zoomedPreview,
|
||||
singleDoc,
|
||||
singleDocId,
|
||||
singleHasPreview,
|
||||
singlePreviewNavigator.currentUrl,
|
||||
singlePreviewNavigator.canGoPrev,
|
||||
singlePreviewNavigator.canGoNext,
|
||||
singlePreviewNavigator.goPrev,
|
||||
singlePreviewNavigator.goNext,
|
||||
singleEffectiveCardinality,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (zoomedPreview && !zoomDisplay) {
|
||||
setZoomedPreview(null);
|
||||
}
|
||||
}, [zoomedPreview, zoomDisplay]);
|
||||
|
||||
const {
|
||||
documentId: singleNavigatorDocId,
|
||||
asset: singleNavigatorAsset,
|
||||
ordinal: singleNavigatorOrdinal,
|
||||
canGoPrev: singleNavigatorCanGoPrev,
|
||||
canGoNext: singleNavigatorCanGoNext,
|
||||
cardinality: singleNavigatorCardinality,
|
||||
} = singlePreviewNavigator;
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof ensureAssetUrl !== 'function') {
|
||||
return;
|
||||
}
|
||||
if (!singleNavigatorDocId || !singleNavigatorAsset || !Number.isFinite(singleNavigatorOrdinal)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const requests = [];
|
||||
if (singleNavigatorCanGoPrev) {
|
||||
const prevOrdinal = Math.max(1, singleNavigatorOrdinal - 1);
|
||||
if (!singleNavigatorCardinality || prevOrdinal <= singleNavigatorCardinality) {
|
||||
requests.push(
|
||||
ensureAssetUrl(singleNavigatorDocId, singleNavigatorAsset, {
|
||||
start: prevOrdinal,
|
||||
limit: 1,
|
||||
objectOrdinal: prevOrdinal,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
if (singleNavigatorCanGoNext) {
|
||||
const nextOrdinal = singleNavigatorOrdinal + 1;
|
||||
if (!singleNavigatorCardinality || nextOrdinal <= singleNavigatorCardinality) {
|
||||
requests.push(
|
||||
ensureAssetUrl(singleNavigatorDocId, singleNavigatorAsset, {
|
||||
start: nextOrdinal,
|
||||
limit: 1,
|
||||
objectOrdinal: nextOrdinal,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
requests.forEach((promise) => promise?.catch?.(() => {}));
|
||||
}, [
|
||||
ensureAssetUrl,
|
||||
singleNavigatorDocId,
|
||||
singleNavigatorAsset,
|
||||
singleNavigatorOrdinal,
|
||||
singleNavigatorCanGoPrev,
|
||||
singleNavigatorCanGoNext,
|
||||
singleNavigatorCardinality,
|
||||
]);
|
||||
|
||||
const renderContent = () => {
|
||||
if (!singleDoc) {
|
||||
return <p className="meta">Select a document to view metadata, tags and actions.</p>;
|
||||
}
|
||||
|
||||
const effectiveCardinality = singleEffectiveCardinality;
|
||||
const navCanGoPrev = Boolean(singlePreviewNavigator.canGoPrev);
|
||||
const navCanGoNext = Boolean(singlePreviewNavigator.canGoNext);
|
||||
const hasPreviewImage = Boolean(singlePreviewNavigator.currentUrl);
|
||||
const previewMissingAsset = !singlePreviewNavigator.currentUrl;
|
||||
const displayContentType = singleDoc.content_type || 'this file type';
|
||||
const displayFilename =
|
||||
singleDoc.filename || singleDoc.original_name || singleDoc.title || 'download';
|
||||
const previewFallback = previewMissingAsset ? (
|
||||
<div className="preview-pane__unsupported">
|
||||
<div className="preview-pane__unsupported-message">
|
||||
Preview not available for {displayContentType} files.
|
||||
</div>
|
||||
<div className="preview-pane__unsupported-filename">{displayFilename}</div>
|
||||
{singleDownloadHref ? (
|
||||
<a
|
||||
className="button-link preview-pane__unsupported-download"
|
||||
href={singleDownloadHref}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<DownloadIcon />
|
||||
<span>Download</span>
|
||||
</a>
|
||||
) : null}
|
||||
</div>
|
||||
) : null;
|
||||
const emptyMessage = previewMissingAsset ? 'Preview unavailable' : 'Preview loading…';
|
||||
const showNav = hasPreviewImage && (effectiveCardinality > 1 || navCanGoPrev || navCanGoNext);
|
||||
|
||||
return (
|
||||
<div className="preview-pane">
|
||||
<div className="preview-pane__media">
|
||||
<PreviewImage
|
||||
item={singlePreviewItem}
|
||||
emptyMessage={emptyMessage}
|
||||
emptyContent={previewFallback}
|
||||
onActivate={handlePreviewActivate}
|
||||
onOpenPreview={onOpenPreview}
|
||||
onZoomPreview={handleSingleZoom}
|
||||
showNav={showNav}
|
||||
canGoPrev={navCanGoPrev}
|
||||
canGoNext={navCanGoNext}
|
||||
onGoPrev={navCanGoPrev ? singlePreviewNavigator.goPrev : undefined}
|
||||
onGoNext={navCanGoNext ? singlePreviewNavigator.goNext : undefined}
|
||||
/>
|
||||
</div>
|
||||
<div className="document-viewer__details">
|
||||
<DocumentInfoPanel
|
||||
document={singleDoc}
|
||||
summaryProps={singleSummaryProps}
|
||||
contentConfig={singleContentConfig}
|
||||
classNamePrefix="document-viewer"
|
||||
defaultTabId="details"
|
||||
resetKey={singleDocId}
|
||||
hideTabNavWhenSingle={false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<section className="document-viewer document-viewer--stacked">
|
||||
<DocumentViewerLayout
|
||||
document={singleDoc}
|
||||
previewEntry={previewEntry}
|
||||
summaryProps={singleSummaryProps}
|
||||
metadataPayload={singleMetadataPayload}
|
||||
contentTabConfig={singleContentConfig}
|
||||
previewLoadingMessage="Loading preview…"
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -598,37 +265,26 @@ const DetailPanel = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<aside className="detail-panel panel">
|
||||
<PanelHeader
|
||||
leading={headerLeading}
|
||||
title={
|
||||
headerBreadcrumbs ? (
|
||||
<BreadcrumbTrail
|
||||
entries={headerBreadcrumbs}
|
||||
separator="/"
|
||||
className="panel-header__breadcrumbs"
|
||||
truncateFromStart
|
||||
/>
|
||||
) : (
|
||||
headerTitle
|
||||
)
|
||||
}
|
||||
titleTag="h3"
|
||||
actions={headerActions.length ? headerActions : null}
|
||||
/>
|
||||
<div className="panel-body">
|
||||
{renderContent()}
|
||||
</div>
|
||||
</aside>
|
||||
{singleDoc && (
|
||||
<PreviewZoomOverlay
|
||||
open={Boolean(zoomDisplay)}
|
||||
display={zoomDisplay}
|
||||
onClose={closeZoomPreview}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
<aside className="detail-panel panel">
|
||||
<PanelHeader
|
||||
leading={headerLeading}
|
||||
title={
|
||||
headerBreadcrumbs ? (
|
||||
<BreadcrumbTrail
|
||||
entries={headerBreadcrumbs}
|
||||
separator="/"
|
||||
className="panel-header__breadcrumbs"
|
||||
truncateFromStart
|
||||
/>
|
||||
) : (
|
||||
headerTitle
|
||||
)
|
||||
}
|
||||
titleTag="h3"
|
||||
actions={headerActions.length ? headerActions : null}
|
||||
/>
|
||||
<div className="panel-body">{renderContent()}</div>
|
||||
</aside>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@ const useDetailWorkspace = ({
|
||||
previewDocumentId,
|
||||
activePreviewId,
|
||||
openDocumentPreview,
|
||||
promoteSelectionOrder,
|
||||
handleDocumentTitleUpdate,
|
||||
handleDocumentIssuedUpdate,
|
||||
handleDocumentTagAdd,
|
||||
@@ -242,7 +241,6 @@ const useDetailWorkspace = ({
|
||||
onTagRemove: handleTagRemove,
|
||||
previewEntry: selectedPreviewEntry,
|
||||
onOpenPreview: openDocumentPreview,
|
||||
onPromoteSelection: promoteSelectionOrder,
|
||||
activePreviewId,
|
||||
onUpdateTitle: handleDocumentTitleUpdate,
|
||||
onUpdateIssued: handleDocumentIssuedUpdate,
|
||||
@@ -272,7 +270,6 @@ const useDetailWorkspace = ({
|
||||
handleDocumentTitleUpdate,
|
||||
handleTagRemove,
|
||||
openDocumentPreview,
|
||||
promoteSelectionOrder,
|
||||
resolveApiPath,
|
||||
resolveFolderPath,
|
||||
selectFolder,
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import DocumentInfoPanel from '../documents/DocumentInfoPanel';
|
||||
import { DownloadIcon } from '../ui/icons';
|
||||
|
||||
const DocumentViewerLayout = ({
|
||||
document,
|
||||
previewEntry,
|
||||
summaryProps,
|
||||
metadataPayload,
|
||||
contentTabConfig,
|
||||
resetKey,
|
||||
classNamePrefix = 'document-viewer',
|
||||
defaultTabId = 'details',
|
||||
infoPanelProps = {},
|
||||
previewLoadingMessage = 'Preparing preview…',
|
||||
}) => {
|
||||
const previewContent = useMemo(() => {
|
||||
if (!document || !previewEntry?.url) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const normalizedContentType = (previewEntry.contentType
|
||||
|| document.content_type
|
||||
|| '')
|
||||
.toLowerCase();
|
||||
const isImage = normalizedContentType.startsWith('image/');
|
||||
const isPdf = normalizedContentType === 'application/pdf'
|
||||
|| normalizedContentType === 'application/x-pdf';
|
||||
|
||||
if (isImage) {
|
||||
return (
|
||||
<img
|
||||
src={previewEntry.url}
|
||||
alt={`Preview of ${document.title}`}
|
||||
className="document-viewer__object document-viewer__object--image"
|
||||
draggable={false}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (isPdf) {
|
||||
return (
|
||||
<iframe
|
||||
src={previewEntry.url}
|
||||
title={`Preview of ${document.title}`}
|
||||
className="document-viewer__object"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const displayContentType = document.content_type || previewEntry.contentType || 'this file type';
|
||||
const displayFilename = previewEntry.filename
|
||||
|| document.filename
|
||||
|| document.original_name
|
||||
|| 'download';
|
||||
|
||||
return (
|
||||
<div className="document-viewer__unsupported">
|
||||
<div className="document-viewer__unsupported-message">
|
||||
Preview is not available for {displayContentType} files.
|
||||
</div>
|
||||
<div className="document-viewer__unsupported-filename">{displayFilename}</div>
|
||||
<a
|
||||
className="button-link document-viewer__unsupported-download"
|
||||
href={previewEntry.url}
|
||||
download={displayFilename}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<DownloadIcon />
|
||||
<span>Download</span>
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}, [previewEntry, document]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="document-viewer__details-pane">
|
||||
<div className="document-viewer__details">
|
||||
<DocumentInfoPanel
|
||||
document={document}
|
||||
summaryProps={summaryProps}
|
||||
metadataPayload={metadataPayload}
|
||||
contentConfig={contentTabConfig}
|
||||
defaultTabId={defaultTabId}
|
||||
classNamePrefix={classNamePrefix}
|
||||
hideTabNavWhenSingle={false}
|
||||
resetKey={resetKey || document?.id}
|
||||
{...infoPanelProps}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="document-viewer__viewport">
|
||||
{!previewEntry?.url ? (
|
||||
<div className="document-viewer__message">{previewLoadingMessage}</div>
|
||||
) : (
|
||||
previewContent
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DocumentViewerLayout;
|
||||
@@ -11,10 +11,10 @@ import {
|
||||
buildCorrespondentOptions,
|
||||
sortCorrespondents,
|
||||
} from '../documents/DocumentSummarySection';
|
||||
import DocumentInfoPanel from '../documents/DocumentInfoPanel';
|
||||
import { extractDocumentMetadataPayload } from '../documents/documentMetadata';
|
||||
import { createDocumentActionState } from '../documents/documentActions';
|
||||
import { resolveDocumentAssetUrl } from '../asset_manager';
|
||||
import DocumentViewerLayout from './DocumentViewerLayout';
|
||||
|
||||
const PORTRAIT_WIDTH_TO_HEIGHT = 1 / Math.sqrt(2); // ≈0.707 (A-series aspect ratio)
|
||||
const DOCUMENT_VIEWER_PORTRAIT_HEIGHT_RATIO = 0.6;
|
||||
@@ -83,66 +83,6 @@ const DocumentViewerPanel = ({
|
||||
[correspondents],
|
||||
);
|
||||
|
||||
const previewContent = useMemo(() => {
|
||||
if (!document || !previewEntry?.url) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const normalizedContentType = (previewEntry.contentType
|
||||
|| document.content_type
|
||||
|| '')
|
||||
.toLowerCase();
|
||||
const isImage = normalizedContentType.startsWith('image/');
|
||||
const isPdf = normalizedContentType === 'application/pdf'
|
||||
|| normalizedContentType === 'application/x-pdf';
|
||||
|
||||
if (isImage) {
|
||||
return (
|
||||
<img
|
||||
src={previewEntry.url}
|
||||
alt={`Preview of ${document.title}`}
|
||||
className="document-viewer__object document-viewer__object--image"
|
||||
draggable={false}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (isPdf) {
|
||||
return (
|
||||
<iframe
|
||||
src={previewEntry.url}
|
||||
title={`Preview of ${document.title}`}
|
||||
className="document-viewer__object"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const displayContentType = document.content_type || previewEntry.contentType || 'this file type';
|
||||
const displayFilename = previewEntry.filename
|
||||
|| document.filename
|
||||
|| document.original_name
|
||||
|| 'download';
|
||||
|
||||
return (
|
||||
<div className="document-viewer__unsupported">
|
||||
<div className="document-viewer__unsupported-message">
|
||||
Preview is not available for {displayContentType} files.
|
||||
</div>
|
||||
<div className="document-viewer__unsupported-filename">{displayFilename}</div>
|
||||
<a
|
||||
className="button-link document-viewer__unsupported-download"
|
||||
href={previewEntry.url}
|
||||
download={displayFilename}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<DownloadIcon />
|
||||
<span>Download</span>
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}, [previewEntry, document]);
|
||||
|
||||
const metadataPayload = useMemo(
|
||||
() => extractDocumentMetadataPayload(document),
|
||||
[document],
|
||||
@@ -316,27 +256,14 @@ const DocumentViewerPanel = ({
|
||||
|
||||
return (
|
||||
<section className={viewerClassName} ref={viewerRef}>
|
||||
<div className="document-viewer__details-pane">
|
||||
<div className="document-viewer__details">
|
||||
<DocumentInfoPanel
|
||||
document={document}
|
||||
summaryProps={summaryProps}
|
||||
metadataPayload={metadataPayload}
|
||||
contentConfig={contentTabConfig}
|
||||
defaultTabId="details"
|
||||
classNamePrefix="document-viewer"
|
||||
hideTabNavWhenSingle={false}
|
||||
resetKey={document?.id}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="document-viewer__viewport">
|
||||
{!previewEntry?.url ? (
|
||||
<div className="document-viewer__message">Loading preview…</div>
|
||||
) : (
|
||||
previewContent
|
||||
)}
|
||||
</div>
|
||||
<DocumentViewerLayout
|
||||
document={document}
|
||||
previewEntry={previewEntry}
|
||||
summaryProps={summaryProps}
|
||||
metadataPayload={metadataPayload}
|
||||
contentTabConfig={contentTabConfig}
|
||||
previewLoadingMessage="Loading preview…"
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user