221 lines
6.1 KiB
React
221 lines
6.1 KiB
React
import React, { useMemo } from 'react';
|
|
import { formatFileSize } from '../utils/format';
|
|
import { DownloadIcon, TextScanIcon, AnalyzeIcon, CloseIcon } from '../ui/icons';
|
|
import { openOcrTextInNewTab } from '../utils/ocr';
|
|
|
|
const PreviewWorkspace = ({
|
|
document,
|
|
previewEntry,
|
|
}) => {
|
|
if (!document) {
|
|
return null;
|
|
}
|
|
|
|
const title = document.title || document.original_name || 'Document';
|
|
const mime = previewEntry?.contentType || document.content_type || 'application/pdf';
|
|
const sizeBytes = Number(document.current_version?.size_bytes) || 0;
|
|
const sizeLabel = sizeBytes > 0 ? formatFileSize(sizeBytes) : null;
|
|
const metadata =
|
|
document.metadata && Object.keys(document.metadata).length > 0 ? document.metadata : null;
|
|
const folderName = document.folder_path || document.folder_name || null;
|
|
const issuedAt = document.issued_at || document.current_version?.issued_at || null;
|
|
const createdAt = document.created_at || null;
|
|
const updatedAt = document.updated_at || null;
|
|
const tags = Array.isArray(document.tags) ? document.tags : [];
|
|
const correspondents = Array.isArray(document.correspondents) ? document.correspondents : [];
|
|
|
|
const metadataSummary = useMemo(() => {
|
|
const rows = [];
|
|
if (mime) rows.push(['Type', mime]);
|
|
if (sizeLabel) rows.push(['Size', sizeLabel]);
|
|
if (issuedAt) rows.push(['Issued', new Date(issuedAt).toLocaleString()]);
|
|
if (createdAt) rows.push(['Created', new Date(createdAt).toLocaleString()]);
|
|
if (updatedAt) rows.push(['Updated', new Date(updatedAt).toLocaleString()]);
|
|
if (folderName) rows.push(['Folder', folderName]);
|
|
if (tags.length) {
|
|
rows.push(['Tags', tags.map((tag) => tag.label || tag.name || tag.slug).filter(Boolean).join(', ')]);
|
|
}
|
|
if (correspondents.length) {
|
|
rows.push([
|
|
'Correspondents',
|
|
correspondents
|
|
.map((entry) => entry.name || entry.label || entry.slug)
|
|
.filter(Boolean)
|
|
.join(', '),
|
|
]);
|
|
}
|
|
return rows;
|
|
}, [mime, sizeLabel, issuedAt, createdAt, updatedAt, folderName, tags, correspondents]);
|
|
|
|
return (
|
|
<section className="preview-workspace">
|
|
<aside className="preview-workspace__sidebar">
|
|
<div className="preview-workspace__info">
|
|
{metadataSummary.length ? (
|
|
<dl className="preview-workspace__summary">
|
|
{metadataSummary.map(([label, value]) => (
|
|
<div className="preview-workspace__summary-row" key={label}>
|
|
<dt>{label}</dt>
|
|
<dd>{value || '—'}</dd>
|
|
</div>
|
|
))}
|
|
</dl>
|
|
) : null}
|
|
</div>
|
|
{metadata ? (
|
|
<section className="preview-workspace__metadata">
|
|
<h4>Metadata payload</h4>
|
|
<pre>{JSON.stringify(metadata, null, 2)}</pre>
|
|
</section>
|
|
) : null}
|
|
</aside>
|
|
<div className="preview-workspace__viewer">
|
|
{!previewEntry?.url ? (
|
|
<div className="preview-workspace__message">Loading preview…</div>
|
|
) : (
|
|
<iframe
|
|
src={previewEntry.url}
|
|
title={`Preview of ${title}`}
|
|
className="preview-workspace__object"
|
|
/>
|
|
)}
|
|
</div>
|
|
</section>
|
|
);
|
|
};
|
|
|
|
export default PreviewWorkspace;
|
|
|
|
export const createPreviewWorkspaceHeaderActions = ({
|
|
document,
|
|
ensureAssetUrl,
|
|
getDocumentAsset,
|
|
resolveApiPath,
|
|
notifyApiError,
|
|
onRegenerate,
|
|
}) => {
|
|
if (!document) {
|
|
return null;
|
|
}
|
|
|
|
const downloadHref = document.current_version?.download_path
|
|
? resolveApiPath(document.current_version.download_path)
|
|
: null;
|
|
|
|
const ocrAsset = getDocumentAsset(document, 'ocr-text');
|
|
const hasOcr = Boolean(ocrAsset);
|
|
|
|
const handleOcrClick = async () => {
|
|
try {
|
|
const success = await openOcrTextInNewTab({
|
|
document,
|
|
getDocumentAsset,
|
|
ensureAssetUrl,
|
|
});
|
|
if (!success) {
|
|
throw new Error('OCR text URL unavailable.');
|
|
}
|
|
} catch (error) {
|
|
notifyApiError(error, 'Unable to open OCR text.');
|
|
}
|
|
};
|
|
|
|
return (
|
|
<>
|
|
{downloadHref ? (
|
|
<a
|
|
className="icon-button"
|
|
href={downloadHref}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
aria-label="Download document"
|
|
title="Download document"
|
|
>
|
|
<DownloadIcon />
|
|
</a>
|
|
) : null}
|
|
{hasOcr ? (
|
|
<button
|
|
type="button"
|
|
className="icon-button ghost"
|
|
onClick={handleOcrClick}
|
|
aria-label="View OCR text"
|
|
title="View OCR text"
|
|
>
|
|
<TextScanIcon />
|
|
</button>
|
|
) : null}
|
|
<button
|
|
type="button"
|
|
className="icon-button ghost"
|
|
onClick={() => onRegenerate(document.id)}
|
|
aria-label="Re-run analysis"
|
|
title="Re-run analysis"
|
|
>
|
|
<AnalyzeIcon />
|
|
</button>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export const createPreviewSurface = ({
|
|
document,
|
|
previewEntry,
|
|
ensureAssetUrl,
|
|
getDocumentAsset,
|
|
resolveApiPath,
|
|
notifyApiError,
|
|
onRegenerate,
|
|
onClose,
|
|
renderSidebarToggle,
|
|
}) => {
|
|
if (!document) {
|
|
return null;
|
|
}
|
|
|
|
const title = document.title || document.original_name || 'Document preview';
|
|
const sidebarToggle = renderSidebarToggle ? renderSidebarToggle() : null;
|
|
const closeButton = onClose
|
|
? (
|
|
<button
|
|
type="button"
|
|
className="icon-button ghost"
|
|
onClick={() => onClose?.()}
|
|
aria-label="Close preview"
|
|
title="Close preview"
|
|
>
|
|
<CloseIcon />
|
|
</button>
|
|
)
|
|
: null;
|
|
const leading = sidebarToggle || closeButton
|
|
? (
|
|
<>
|
|
{sidebarToggle}
|
|
{closeButton}
|
|
</>
|
|
)
|
|
: null;
|
|
const header = {
|
|
title,
|
|
subtitle: null,
|
|
leading,
|
|
actions: createPreviewWorkspaceHeaderActions({
|
|
document,
|
|
ensureAssetUrl,
|
|
getDocumentAsset,
|
|
resolveApiPath,
|
|
notifyApiError,
|
|
onRegenerate,
|
|
}),
|
|
};
|
|
|
|
return {
|
|
key: 'preview',
|
|
variant: 'preview',
|
|
header,
|
|
content: <PreviewWorkspace document={document} previewEntry={previewEntry} />,
|
|
supportsDetail: false,
|
|
};
|
|
};
|