ocr view
This commit is contained in:
@@ -296,10 +296,9 @@ const DetailPanel = ({
|
||||
const [titleSaving, setTitleSaving] = useState(false);
|
||||
const [titleError, setTitleError] = useState(null);
|
||||
const [ocrOpen, setOcrOpen] = useState(false);
|
||||
const [ocrUrl, setOcrUrl] = useState(null);
|
||||
const [ocrLoading, setOcrLoading] = useState(false);
|
||||
const [ocrError, setOcrError] = useState(null);
|
||||
const [ocrText, setOcrText] = useState('');
|
||||
const [ocrLoadedDocId, setOcrLoadedDocId] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!singleDoc) {
|
||||
@@ -310,8 +309,7 @@ const DetailPanel = ({
|
||||
setOcrOpen(false);
|
||||
setOcrLoading(false);
|
||||
setOcrError(null);
|
||||
setOcrText('');
|
||||
setOcrLoadedDocId(null);
|
||||
setOcrUrl(null);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -327,8 +325,7 @@ const DetailPanel = ({
|
||||
setOcrOpen(false);
|
||||
setOcrLoading(false);
|
||||
setOcrError(null);
|
||||
setOcrText('');
|
||||
setOcrLoadedDocId(null);
|
||||
setOcrUrl(null);
|
||||
}, [singleDoc?.id]);
|
||||
|
||||
const startTitleEdit = useCallback(() => {
|
||||
@@ -384,29 +381,25 @@ const DetailPanel = ({
|
||||
[singleDoc, getDocumentAsset],
|
||||
);
|
||||
|
||||
const loadOcrText = useCallback(async () => {
|
||||
const loadOcrUrl = useCallback(async () => {
|
||||
if (!singleDoc) {
|
||||
return;
|
||||
}
|
||||
if (ocrLoadedDocId === singleDoc.id && ocrText) {
|
||||
return;
|
||||
}
|
||||
const asset = getDocumentAsset(singleDoc, 'ocr-text');
|
||||
if (!asset) {
|
||||
setOcrError('No OCR text available for this document.');
|
||||
setOcrText('');
|
||||
setOcrLoadedDocId(singleDoc.id);
|
||||
setOcrUrl(null);
|
||||
return;
|
||||
}
|
||||
setOcrLoading(true);
|
||||
setOcrError(null);
|
||||
try {
|
||||
let entry = asset;
|
||||
if (ensureAssetUrl) {
|
||||
entry = (await ensureAssetUrl(singleDoc.id, asset, { force: false })) || asset;
|
||||
}
|
||||
let url = entry?.url || null;
|
||||
if (!url) {
|
||||
let url = resolveDocumentAssetUrl(singleDoc, 'ocr-text', {
|
||||
ensureAssetUrl,
|
||||
getAsset: getDocumentAsset,
|
||||
});
|
||||
if (!url && typeof ensureAssetUrl === 'function') {
|
||||
await ensureAssetUrl(singleDoc.id, asset, { force: false });
|
||||
url = resolveDocumentAssetUrl(singleDoc, 'ocr-text', {
|
||||
ensureAssetUrl,
|
||||
getAsset: getDocumentAsset,
|
||||
@@ -415,30 +408,24 @@ const DetailPanel = ({
|
||||
if (!url) {
|
||||
throw new Error('OCR text URL is unavailable.');
|
||||
}
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch OCR text (status ${response.status}).`);
|
||||
}
|
||||
const text = await response.text();
|
||||
setOcrText(text);
|
||||
setOcrLoadedDocId(singleDoc.id);
|
||||
setOcrUrl(url);
|
||||
} catch (error) {
|
||||
setOcrError(error.message || 'Failed to load OCR text.');
|
||||
setOcrText('');
|
||||
setOcrUrl(null);
|
||||
} finally {
|
||||
setOcrLoading(false);
|
||||
}
|
||||
}, [singleDoc, ensureAssetUrl, getDocumentAsset, ocrLoadedDocId, ocrText]);
|
||||
}, [singleDoc, ensureAssetUrl, getDocumentAsset]);
|
||||
|
||||
const openOcr = useCallback(() => {
|
||||
const openOcrModal = useCallback(() => {
|
||||
if (!singleDoc) {
|
||||
return;
|
||||
}
|
||||
setOcrOpen(true);
|
||||
loadOcrText();
|
||||
}, [singleDoc, loadOcrText]);
|
||||
loadOcrUrl();
|
||||
}, [singleDoc, loadOcrUrl]);
|
||||
|
||||
const closeOcr = useCallback(() => {
|
||||
const closeOcrModal = useCallback(() => {
|
||||
setOcrOpen(false);
|
||||
}, []);
|
||||
|
||||
@@ -773,7 +760,7 @@ const DetailPanel = ({
|
||||
</div>
|
||||
{hasOcrAsset ? (
|
||||
<div className="detail-ocr-trigger">
|
||||
<button type="button" className="secondary" onClick={openOcr}>
|
||||
<button type="button" className="secondary" onClick={openOcrModal}>
|
||||
View OCR text
|
||||
</button>
|
||||
</div>
|
||||
@@ -819,7 +806,7 @@ const DetailPanel = ({
|
||||
)}
|
||||
{hasOcrAsset && ocrOpen
|
||||
? createPortal(
|
||||
<div className="modal-backdrop" role="presentation" onClick={closeOcr}>
|
||||
<div className="modal-backdrop" role="presentation" onClick={closeOcrModal}>
|
||||
<div
|
||||
className="modal modal--ocr"
|
||||
role="dialog"
|
||||
@@ -832,7 +819,7 @@ const DetailPanel = ({
|
||||
<button
|
||||
type="button"
|
||||
className="icon-button ghost"
|
||||
onClick={closeOcr}
|
||||
onClick={closeOcrModal}
|
||||
aria-label="Close OCR text"
|
||||
>
|
||||
×
|
||||
@@ -843,8 +830,12 @@ const DetailPanel = ({
|
||||
<div className="meta">Loading OCR text…</div>
|
||||
) : ocrError ? (
|
||||
<div className="status-inline error">{ocrError}</div>
|
||||
) : ocrText ? (
|
||||
<pre>{ocrText}</pre>
|
||||
) : ocrUrl ? (
|
||||
<iframe
|
||||
title="OCR text"
|
||||
src={ocrUrl}
|
||||
className="detail-ocr__frame"
|
||||
/>
|
||||
) : (
|
||||
<div className="meta">OCR text empty.</div>
|
||||
)}
|
||||
|
||||
@@ -1539,6 +1539,15 @@ button.icon-button.ghost:hover:not([disabled]) {
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.detail-ocr__frame {
|
||||
width: 100%;
|
||||
height: 60vh;
|
||||
border: none;
|
||||
background: var(--surface-subtle);
|
||||
border-radius: 4px;
|
||||
box-shadow: inset 0 1px 2px rgba(15, 23, 42, 0.05);
|
||||
}
|
||||
|
||||
.modal__body {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user