This commit is contained in:
2025-11-02 02:05:05 +01:00
parent 7d8595b63f
commit b9c95437d2
2 changed files with 249 additions and 56 deletions
+159 -37
View File
@@ -1,10 +1,11 @@
import React, { useMemo } from 'react';
import React, { useEffect, useMemo, useState } from 'react';
import { DownloadIcon, TextScanIcon, AnalyzeIcon, CloseIcon } from '../ui/icons';
import DocumentSummarySection, {
buildCorrespondentOptions,
sortCorrespondents,
} from '../documents/DocumentSummarySection';
import { createDocumentActionState } from '../documents/documentActions';
import { resolveDocumentAssetUrl } from '../asset_manager';
const formatDateTime = (value) => {
if (!value) {
@@ -27,6 +28,9 @@ const DocumentViewerPanel = ({
onCorrespondentRemove,
onUpdateTitle,
onUpdateIssued,
hasOcr = false,
ensureAssetUrl,
getDocumentAsset,
}) => {
const sortedCorrespondents = useMemo(
() => sortCorrespondents(document?.correspondents || []),
@@ -99,6 +103,63 @@ const DocumentViewerPanel = ({
return document.metadata;
}, [document]);
const [activeTab, setActiveTab] = useState('details');
useEffect(() => {
setActiveTab('details');
}, [document?.id, hasOcr, metadataPayload]);
const [ocrUrl, setOcrUrl] = useState(null);
const [ocrLoading, setOcrLoading] = useState(false);
const [ocrError, setOcrError] = useState(null);
useEffect(() => {
let cancelled = false;
if (!document || !hasOcr || typeof getDocumentAsset !== 'function') {
setOcrUrl(null);
setOcrLoading(false);
setOcrError(null);
return () => {
cancelled = true;
};
}
const updateUrl = () =>
resolveDocumentAssetUrl(document, 'ocr-text', {
ensureAssetUrl,
getAsset: getDocumentAsset,
});
const asset = getDocumentAsset(document, 'ocr-text');
const ensureAndUpdate = async () => {
setOcrLoading(true);
setOcrError(null);
let url = updateUrl();
if (!url && document.id && asset?.id && typeof ensureAssetUrl === 'function') {
try {
await ensureAssetUrl(document.id, asset, { start: 1, limit: 1 });
url = updateUrl();
} catch (error) {
if (!cancelled) {
setOcrError('Unable to load OCR content.');
}
}
}
if (!cancelled) {
setOcrUrl(url);
setOcrLoading(false);
}
};
ensureAndUpdate();
return () => {
cancelled = true;
};
}, [document, hasOcr, ensureAssetUrl, getDocumentAsset]);
if (!document) {
return (
<section className="document-viewer document-viewer--loading">
@@ -130,23 +191,85 @@ const DocumentViewerPanel = ({
onUpdateTitle={onUpdateTitle}
onUpdateIssued={onUpdateIssued}
/>
<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 className="document-viewer__tabs-wrapper">
<div className="document-viewer__tabs" role="tablist" aria-label="Document details">
<button
type="button"
role="tab"
aria-selected={activeTab === 'details'}
className={`document-viewer__tab${activeTab === 'details' ? ' is-active' : ''}`}
onClick={() => setActiveTab('details')}
>
Details
</button>
{hasOcr ? (
<button
type="button"
role="tab"
aria-selected={activeTab === 'content'}
className={`document-viewer__tab${activeTab === 'content' ? ' is-active' : ''}`}
onClick={() => setActiveTab('content')}
>
Content
</button>
) : null}
{metadataPayload ? (
<button
type="button"
role="tab"
aria-selected={activeTab === 'metadata'}
className={`document-viewer__tab${activeTab === 'metadata' ? ' is-active' : ''}`}
onClick={() => setActiveTab('metadata')}
>
Metadata
</button>
) : null}
</div>
<div className="document-viewer__tabpanes">
{activeTab === 'details' ? (
<div role="tabpanel" className="document-viewer__tabpanel">
<section className="document-viewer__section">
<dl className="document-viewer__section-list">
{metadataItems.map(({ label, value }) => (
<div className="document-viewer__section-item" key={label}>
<dt>{label}</dt>
<dd>{value || '—'}</dd>
</div>
))}
</dl>
</section>
</div>
))}
</dl>
{metadataPayload ? (
<details className="preview-section__payload">
<summary>Show metadata payload</summary>
<pre>{JSON.stringify(metadataPayload, null, 2)}</pre>
</details>
) : null}
</section>
) : null}
{activeTab === 'content' && hasOcr ? (
<div role="tabpanel" className="document-viewer__tabpanel">
{ocrLoading ? (
<div className="document-viewer__message">Loading OCR content</div>
) : ocrError ? (
<div className="document-viewer__message document-viewer__message--error">
{ocrError}
</div>
) : ocrUrl ? (
<embed
src={ocrUrl}
type="text/plain"
className="document-viewer__object document-viewer__object--ocr"
/>
) : (
<div className="document-viewer__message">No OCR content available.</div>
)}
</div>
) : null}
{activeTab === 'metadata' && metadataPayload ? (
<div role="tabpanel" className="document-viewer__tabpanel">
<section className="document-viewer__section document-viewer__section--metadata-json">
<pre className="document-viewer__metadata-json">
{JSON.stringify(metadataPayload, null, 2)}
</pre>
</section>
</div>
) : null}
</div>
</div>
</div>
<div className="document-viewer__viewport">
{!previewEntry?.url ? (
@@ -163,26 +286,14 @@ export default DocumentViewerPanel;
export const createDocumentViewerHeaderActions = ({
document,
ensureAssetUrl,
ensurePreviewData,
getDocumentAsset,
resolveApiPath,
notifyApiError,
actionState,
onRegenerate,
}) => {
if (!document) {
if (!document || !actionState) {
return null;
}
const { downloadHref, hasOcr, openOcr } = createDocumentActionState({
document,
resolveApiPath,
ensurePreviewData,
ensureAssetUrl,
getDocumentAsset,
notifyApiError,
ocrErrorMessage: 'Unable to open OCR text.',
});
const { downloadHref, hasOcr, openOcr } = actionState;
return (
<>
@@ -289,17 +400,25 @@ export const createDocumentViewerSurface = ({
];
}
const actionState = document
? createDocumentActionState({
document,
resolveApiPath,
ensurePreviewData,
ensureAssetUrl,
getDocumentAsset,
notifyApiError,
ocrErrorMessage: 'Unable to open OCR text.',
})
: null;
const header = {
title,
subtitle: null,
leading,
actions: createDocumentViewerHeaderActions({
document,
ensureAssetUrl,
ensurePreviewData,
getDocumentAsset,
resolveApiPath,
notifyApiError,
actionState,
onRegenerate,
}),
breadcrumbs,
@@ -323,6 +442,9 @@ export const createDocumentViewerSurface = ({
onCorrespondentRemove={onCorrespondentRemove}
onUpdateTitle={onUpdateTitle}
onUpdateIssued={onUpdateIssued}
hasOcr={Boolean(actionState?.hasOcr)}
ensureAssetUrl={ensureAssetUrl}
getDocumentAsset={getDocumentAsset}
/>
),
supportsDetail: false,