284 lines
7.0 KiB
React
284 lines
7.0 KiB
React
import React, { useMemo } from 'react';
|
|
import { DownloadIcon, TextScanIcon, AnalyzeIcon, CloseIcon } from '../ui/icons';
|
|
import DocumentSummarySection, {
|
|
buildCorrespondentOptions,
|
|
sortCorrespondents,
|
|
} from '../documents/DocumentSummarySection';
|
|
import { createDocumentActionState } from '../documents/documentActions';
|
|
|
|
const formatDateTime = (value) => {
|
|
if (!value) {
|
|
return '—';
|
|
}
|
|
const date = new Date(value);
|
|
return Number.isNaN(date.getTime()) ? '—' : date.toLocaleString();
|
|
};
|
|
|
|
const PreviewWorkspace = ({
|
|
document,
|
|
previewEntry,
|
|
tagLookupById,
|
|
tagOptions,
|
|
onTagAdd,
|
|
onTagRemove,
|
|
correspondents,
|
|
onCorrespondentAdd,
|
|
onCorrespondentRemove,
|
|
onUpdateTitle,
|
|
onUpdateIssued,
|
|
resolveFolderPath,
|
|
onFolderNavigate,
|
|
}) => {
|
|
const sortedCorrespondents = useMemo(
|
|
() => sortCorrespondents(document?.correspondents || []),
|
|
[document],
|
|
);
|
|
|
|
const correspondentOptions = useMemo(
|
|
() => buildCorrespondentOptions(Array.isArray(correspondents) ? correspondents : []),
|
|
[correspondents],
|
|
);
|
|
|
|
const metadataItems = useMemo(() => {
|
|
if (!document) {
|
|
return [];
|
|
}
|
|
return [
|
|
{ label: 'Created at', value: formatDateTime(document.created_at) },
|
|
{ label: 'Updated at', value: formatDateTime(document.updated_at) },
|
|
{
|
|
label: 'Filename',
|
|
value: document.archive_path || document.filename || '—',
|
|
},
|
|
{
|
|
label: 'Original filename',
|
|
value: document.original_name || '—',
|
|
},
|
|
{
|
|
label: 'SHA-256 checksum',
|
|
value: document.current_version?.checksum || '—',
|
|
},
|
|
{
|
|
label: 'Content type',
|
|
value: document.content_type || '—',
|
|
},
|
|
];
|
|
}, [document]);
|
|
|
|
const metadataPayload = useMemo(() => {
|
|
if (!document || !document.metadata || Object.keys(document.metadata).length === 0) {
|
|
return null;
|
|
}
|
|
return document.metadata;
|
|
}, [document]);
|
|
|
|
if (!document) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<section className="preview-workspace">
|
|
<div className="preview-workspace__details">
|
|
<DocumentSummarySection
|
|
document={document}
|
|
tagLookupById={tagLookupById}
|
|
tagOptions={tagOptions}
|
|
onTagAdd={onTagAdd}
|
|
onTagRemove={onTagRemove}
|
|
correspondents={sortedCorrespondents}
|
|
correspondentOptions={correspondentOptions}
|
|
onCorrespondentAdd={onCorrespondentAdd}
|
|
onCorrespondentRemove={onCorrespondentRemove}
|
|
onUpdateTitle={onUpdateTitle}
|
|
onUpdateIssued={onUpdateIssued}
|
|
resolveFolderPath={resolveFolderPath}
|
|
onFolderNavigate={onFolderNavigate}
|
|
/>
|
|
<section className="preview-section">
|
|
<h3 className="preview-section__title">Metadata</h3>
|
|
<dl className="preview-section__list">
|
|
{metadataItems.map(({ label, value }) => (
|
|
<div className="preview-section__item" key={label}>
|
|
<dt>{label}</dt>
|
|
<dd>{value || '—'}</dd>
|
|
</div>
|
|
))}
|
|
</dl>
|
|
{metadataPayload ? (
|
|
<details className="preview-section__payload">
|
|
<summary>Show metadata payload</summary>
|
|
<pre>{JSON.stringify(metadataPayload, null, 2)}</pre>
|
|
</details>
|
|
) : null}
|
|
</section>
|
|
</div>
|
|
<div className="preview-workspace__viewer">
|
|
{!previewEntry?.url ? (
|
|
<div className="preview-workspace__message">Loading preview…</div>
|
|
) : (
|
|
<iframe
|
|
src={previewEntry.url}
|
|
title={`Preview of ${document.title}`}
|
|
className="preview-workspace__object"
|
|
/>
|
|
)}
|
|
</div>
|
|
</section>
|
|
);
|
|
};
|
|
|
|
export default PreviewWorkspace;
|
|
|
|
export const createPreviewWorkspaceHeaderActions = ({
|
|
document,
|
|
ensureAssetUrl,
|
|
ensurePreviewData,
|
|
getDocumentAsset,
|
|
resolveApiPath,
|
|
notifyApiError,
|
|
onRegenerate,
|
|
}) => {
|
|
if (!document) {
|
|
return null;
|
|
}
|
|
|
|
const { downloadHref, hasOcr, openOcr } = createDocumentActionState({
|
|
document,
|
|
resolveApiPath,
|
|
ensurePreviewData,
|
|
ensureAssetUrl,
|
|
getDocumentAsset,
|
|
notifyApiError,
|
|
ocrErrorMessage: '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"
|
|
onClick={() => {
|
|
openOcr().catch(() => {});
|
|
}}
|
|
aria-label="View OCR text"
|
|
title="View OCR text"
|
|
>
|
|
<TextScanIcon />
|
|
</button>
|
|
) : null}
|
|
<button
|
|
type="button"
|
|
className="icon-button"
|
|
onClick={() => onRegenerate(document.id)}
|
|
aria-label="Re-run analysis"
|
|
title="Re-run analysis"
|
|
>
|
|
<AnalyzeIcon />
|
|
</button>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export const createPreviewSurface = ({
|
|
document,
|
|
previewEntry,
|
|
ensureAssetUrl,
|
|
ensurePreviewData,
|
|
getDocumentAsset,
|
|
resolveApiPath,
|
|
notifyApiError,
|
|
onRegenerate,
|
|
onClose,
|
|
renderSidebarToggle,
|
|
tagLookupById,
|
|
tagOptions,
|
|
onTagAdd,
|
|
onTagRemove,
|
|
correspondents,
|
|
onCorrespondentAdd,
|
|
onCorrespondentRemove,
|
|
onUpdateTitle,
|
|
onUpdateIssued,
|
|
resolveFolderPath,
|
|
onFolderNavigate,
|
|
}) => {
|
|
if (!document) {
|
|
return null;
|
|
}
|
|
|
|
const title = document.title;
|
|
const closeButton = onClose
|
|
? (
|
|
<button
|
|
type="button"
|
|
className="icon-button"
|
|
onClick={() => onClose?.()}
|
|
aria-label="Close preview"
|
|
title="Close preview"
|
|
>
|
|
<CloseIcon />
|
|
</button>
|
|
)
|
|
: null;
|
|
const sidebarToggle = renderSidebarToggle ? renderSidebarToggle() : null;
|
|
const leading = closeButton || sidebarToggle
|
|
? (
|
|
<>
|
|
{sidebarToggle}
|
|
{closeButton}
|
|
</>
|
|
)
|
|
: null;
|
|
const header = {
|
|
title,
|
|
subtitle: null,
|
|
leading,
|
|
actions: createPreviewWorkspaceHeaderActions({
|
|
document,
|
|
ensureAssetUrl,
|
|
ensurePreviewData,
|
|
getDocumentAsset,
|
|
resolveApiPath,
|
|
notifyApiError,
|
|
onRegenerate,
|
|
}),
|
|
};
|
|
|
|
return {
|
|
key: 'preview',
|
|
variant: 'preview',
|
|
header,
|
|
content: (
|
|
<PreviewWorkspace
|
|
document={document}
|
|
previewEntry={previewEntry}
|
|
tagLookupById={tagLookupById}
|
|
tagOptions={tagOptions}
|
|
onTagAdd={onTagAdd}
|
|
onTagRemove={onTagRemove}
|
|
correspondents={correspondents}
|
|
onCorrespondentAdd={onCorrespondentAdd}
|
|
onCorrespondentRemove={onCorrespondentRemove}
|
|
onUpdateTitle={onUpdateTitle}
|
|
onUpdateIssued={onUpdateIssued}
|
|
resolveFolderPath={resolveFolderPath}
|
|
onFolderNavigate={onFolderNavigate}
|
|
/>
|
|
),
|
|
supportsDetail: false,
|
|
};
|
|
};
|