7
This commit is contained in:
+107
-111
@@ -21,7 +21,7 @@ import {
|
||||
matchPath,
|
||||
} from 'react-router-dom';
|
||||
import './styles.css';
|
||||
import AssetManager, { getAssetFromVersion } from './asset_manager';
|
||||
import AssetManager, { getAssetFromVersion, resolveDocumentAssetUrl } from './asset_manager';
|
||||
import useApiError from './hooks/useApiError';
|
||||
import SkeuomorphicWorkspace from './skeuomorphic_ws';
|
||||
|
||||
@@ -502,25 +502,20 @@ const FilterBar = ({
|
||||
</div>
|
||||
);
|
||||
|
||||
const DocumentThumbnailImage = ({ documentId, asset, ensureAssetUrl, alt }) => {
|
||||
useEffect(() => {
|
||||
if (!documentId || !asset?.id || !ensureAssetUrl) {
|
||||
return;
|
||||
}
|
||||
const expiresAt = typeof asset.expiresAt === 'number' ? asset.expiresAt : null;
|
||||
const now = Date.now();
|
||||
const needsRefresh = !asset?.url || (expiresAt && expiresAt <= now);
|
||||
if (!needsRefresh) {
|
||||
return;
|
||||
}
|
||||
const force = Boolean(asset.url && expiresAt && expiresAt <= now);
|
||||
ensureAssetUrl(documentId, asset, { force }).catch(() => {});
|
||||
}, [documentId, asset?.id, asset?.url, asset?.expiresAt, ensureAssetUrl]);
|
||||
const DocumentThumbnailImage = ({ document, ensureAssetUrl, getDocumentAsset, alt }) => {
|
||||
const url = useMemo(
|
||||
() =>
|
||||
resolveDocumentAssetUrl(document, 'thumbnail', {
|
||||
ensureAssetUrl,
|
||||
getAsset: getDocumentAsset,
|
||||
}),
|
||||
[document, ensureAssetUrl, getDocumentAsset],
|
||||
);
|
||||
|
||||
if (asset?.url) {
|
||||
if (url) {
|
||||
return (
|
||||
<img
|
||||
src={asset.url}
|
||||
src={url}
|
||||
alt={alt || ''}
|
||||
className="document-thumbnail"
|
||||
/>
|
||||
@@ -567,10 +562,6 @@ const DocumentsTable = ({
|
||||
const showingSearchResults = searchResults !== null;
|
||||
const rows = showingSearchResults ? searchResults : documents;
|
||||
|
||||
const resolveThumbnailAsset = useCallback((doc) => {
|
||||
if (!doc) return null;
|
||||
return getDocumentAsset(doc, 'thumbnail');
|
||||
}, [getDocumentAsset]);
|
||||
const selectedSet = useMemo(
|
||||
() => new Set(selectedDocumentIds),
|
||||
[selectedDocumentIds],
|
||||
@@ -775,8 +766,6 @@ const DocumentsTable = ({
|
||||
rowClasses.push('dragging');
|
||||
}
|
||||
|
||||
const thumbnailAsset = resolveThumbnailAsset(doc);
|
||||
|
||||
return (
|
||||
<tr
|
||||
key={doc.id}
|
||||
@@ -799,16 +788,12 @@ const DocumentsTable = ({
|
||||
onDragEnd={onDocumentDragEnd}
|
||||
>
|
||||
<td className="thumb-cell">
|
||||
{thumbnailAsset ? (
|
||||
<DocumentThumbnailImage
|
||||
documentId={doc.id}
|
||||
asset={thumbnailAsset}
|
||||
ensureAssetUrl={ensureAssetUrl}
|
||||
alt={`Thumbnail for ${doc.title || doc.original_name}`}
|
||||
/>
|
||||
) : (
|
||||
<div className="thumb-placeholder">DOC</div>
|
||||
)}
|
||||
<DocumentThumbnailImage
|
||||
document={doc}
|
||||
ensureAssetUrl={ensureAssetUrl}
|
||||
getDocumentAsset={getDocumentAsset}
|
||||
alt={`Thumbnail for ${doc.title || doc.original_name}`}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<div className="doc-name">
|
||||
@@ -1076,28 +1061,29 @@ const DetailPanel = ({
|
||||
[onPromoteSelection],
|
||||
);
|
||||
|
||||
const resolvePreviewAsset = useCallback((doc) => {
|
||||
if (!doc) return null;
|
||||
return getDocumentAsset(doc, 'preview');
|
||||
}, [getDocumentAsset]);
|
||||
|
||||
const makePreviewItem = useCallback((doc) => {
|
||||
if (!doc) return null;
|
||||
const previewAsset = resolvePreviewAsset(doc);
|
||||
if (!previewAsset?.url) {
|
||||
return null;
|
||||
}
|
||||
const url = previewAsset.url;
|
||||
const width = Number(previewAsset?.metadata?.width) || 0;
|
||||
const height = Number(previewAsset?.metadata?.height) || 0;
|
||||
const orientation = width > 0 && height > 0 ? (width >= height ? 'landscape' : 'portrait') : 'landscape';
|
||||
return {
|
||||
id: doc.id,
|
||||
url,
|
||||
orientation,
|
||||
alt: doc.title || doc.original_name || 'Document preview',
|
||||
};
|
||||
}, [resolvePreviewAsset]);
|
||||
const makePreviewItem = useCallback(
|
||||
(doc) => {
|
||||
if (!doc) return null;
|
||||
const url = resolveDocumentAssetUrl(doc, 'preview', {
|
||||
ensureAssetUrl,
|
||||
getAsset: getDocumentAsset,
|
||||
});
|
||||
if (!url) {
|
||||
return null;
|
||||
}
|
||||
const asset = getDocumentAsset(doc, 'preview');
|
||||
const width = Number(asset?.metadata?.width) || 0;
|
||||
const height = Number(asset?.metadata?.height) || 0;
|
||||
const orientation = width > 0 && height > 0 ? (width >= height ? 'landscape' : 'portrait') : 'landscape';
|
||||
return {
|
||||
id: doc.id,
|
||||
url,
|
||||
orientation,
|
||||
alt: doc.title || doc.original_name || 'Document preview',
|
||||
};
|
||||
},
|
||||
[ensureAssetUrl, getDocumentAsset],
|
||||
);
|
||||
|
||||
const stackDocuments = useMemo(() => {
|
||||
if (!selectedDocuments.length) return [];
|
||||
@@ -1135,24 +1121,12 @@ const DetailPanel = ({
|
||||
}
|
||||
|
||||
stackDocuments.forEach((doc) => {
|
||||
const docId = doc?.id;
|
||||
if (!docId) {
|
||||
return;
|
||||
}
|
||||
const asset = resolvePreviewAsset(doc);
|
||||
if (!asset?.id) {
|
||||
return;
|
||||
}
|
||||
const expiresAt = typeof asset.expiresAt === 'number' ? asset.expiresAt : null;
|
||||
const now = Date.now();
|
||||
const needsRefresh = !asset.url || (expiresAt && expiresAt <= now);
|
||||
if (!needsRefresh) {
|
||||
return;
|
||||
}
|
||||
const force = Boolean(asset.url && expiresAt && expiresAt <= now);
|
||||
ensureAssetUrl(docId, asset, { force }).catch(() => {});
|
||||
resolveDocumentAssetUrl(doc, 'preview', {
|
||||
ensureAssetUrl,
|
||||
getAsset: getDocumentAsset,
|
||||
});
|
||||
});
|
||||
}, [stackDocuments, resolvePreviewAsset, ensureAssetUrl]);
|
||||
}, [stackDocuments, ensureAssetUrl, getDocumentAsset]);
|
||||
|
||||
const commonTags = useMemo(() => {
|
||||
if (selectedCount < 2) return [];
|
||||
@@ -2121,7 +2095,8 @@ const AppLayout = () => {
|
||||
setPreviewDocumentId(null);
|
||||
setPreviewDocumentLoading(false);
|
||||
assetManager.reset();
|
||||
setPreviewCacheTick(0);
|
||||
setPreviewEntries(() => new Map());
|
||||
previewInflightRef.current = new Map();
|
||||
dragCounterRef.current = 0;
|
||||
breadcrumbFetchRef.current = new Set();
|
||||
bootstrapInitializedRef.current = false;
|
||||
@@ -3367,7 +3342,8 @@ const AppLayout = () => {
|
||||
);
|
||||
|
||||
const folderPathCacheRef = useRef(new Map());
|
||||
const [previewCacheTick, setPreviewCacheTick] = useState(0);
|
||||
const [previewEntries, setPreviewEntries] = useState(() => new Map());
|
||||
const previewInflightRef = useRef(new Map());
|
||||
|
||||
const ensureFolderPathOnServer = useCallback(
|
||||
async (baseFolderId, segments) => {
|
||||
@@ -3452,18 +3428,45 @@ const AppLayout = () => {
|
||||
async (documentId, { force = false } = {}) => {
|
||||
if (!documentId) return null;
|
||||
|
||||
try {
|
||||
const entry = await assetManager.ensurePreview(documentId, { force });
|
||||
if (entry) {
|
||||
setPreviewCacheTick((prev) => prev + 1);
|
||||
}
|
||||
return entry;
|
||||
} catch (error) {
|
||||
notifyApiError(error, 'Unable to fetch document preview.');
|
||||
throw error;
|
||||
const existing = previewEntries.get(documentId) || null;
|
||||
const now = Date.now();
|
||||
const expiresAt = typeof existing?.expiresAt === 'number' ? existing.expiresAt : null;
|
||||
if (!force && existing && (!expiresAt || expiresAt > now)) {
|
||||
return existing;
|
||||
}
|
||||
|
||||
if (!force && previewInflightRef.current.has(documentId)) {
|
||||
return previewInflightRef.current.get(documentId);
|
||||
}
|
||||
|
||||
const request = (async () => {
|
||||
try {
|
||||
const { data } = await api.get(`/documents/${documentId}/download`);
|
||||
const ttl = data.expires_in ? Math.max(data.expires_in - 60, 30) * 1000 : 5 * 60 * 1000;
|
||||
const entry = {
|
||||
url: data.url,
|
||||
contentType: data.content_type || null,
|
||||
filename: data.filename,
|
||||
expiresAt: Date.now() + ttl,
|
||||
};
|
||||
setPreviewEntries((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(documentId, entry);
|
||||
return next;
|
||||
});
|
||||
return entry;
|
||||
} catch (error) {
|
||||
notifyApiError(error, 'Unable to fetch document preview.');
|
||||
throw error;
|
||||
} finally {
|
||||
previewInflightRef.current.delete(documentId);
|
||||
}
|
||||
})();
|
||||
|
||||
previewInflightRef.current.set(documentId, request);
|
||||
return request;
|
||||
},
|
||||
[assetManager, notifyApiError],
|
||||
[previewEntries, notifyApiError],
|
||||
);
|
||||
|
||||
const extractFilesFromDataTransfer = useCallback(async (dataTransfer) => {
|
||||
@@ -4771,8 +4774,15 @@ const AppLayout = () => {
|
||||
return changed ? next : prev;
|
||||
});
|
||||
|
||||
assetManager.deleteDocument(documentId);
|
||||
setPreviewCacheTick((prev) => prev + 1);
|
||||
setPreviewEntries((prev) => {
|
||||
if (!prev.has(documentId)) {
|
||||
return prev;
|
||||
}
|
||||
const next = new Map(prev);
|
||||
next.delete(documentId);
|
||||
return next;
|
||||
});
|
||||
previewInflightRef.current.delete(documentId);
|
||||
|
||||
if (selectedDocumentIds.includes(documentId)) {
|
||||
const remaining = selectedDocumentIds.filter((id) => id !== documentId);
|
||||
@@ -4801,8 +4811,8 @@ const AppLayout = () => {
|
||||
setSearchResults,
|
||||
setDocumentDetails,
|
||||
setFolderContents,
|
||||
assetManager,
|
||||
setPreviewCacheTick,
|
||||
setPreviewEntries,
|
||||
previewInflightRef,
|
||||
previewDocumentId,
|
||||
closeDocumentPreview,
|
||||
selectedDocumentIds,
|
||||
@@ -4951,15 +4961,15 @@ const AppLayout = () => {
|
||||
if (!selectedDocument) {
|
||||
return null;
|
||||
}
|
||||
return assetManager.getPreview(selectedDocument.id);
|
||||
}, [assetManager, selectedDocument, previewCacheTick]);
|
||||
return previewEntries.get(selectedDocument.id) || null;
|
||||
}, [selectedDocument, previewEntries]);
|
||||
|
||||
const previewWorkspaceEntry = useMemo(() => {
|
||||
if (!previewDocumentId) {
|
||||
return null;
|
||||
}
|
||||
return assetManager.getPreview(previewDocumentId);
|
||||
}, [assetManager, previewDocumentId, previewCacheTick]);
|
||||
return previewEntries.get(previewDocumentId) || null;
|
||||
}, [previewDocumentId, previewEntries]);
|
||||
|
||||
const previewWorkspaceDetail = previewDocumentId
|
||||
? documentDetails.get(previewDocumentId)
|
||||
@@ -4992,23 +5002,11 @@ const AppLayout = () => {
|
||||
};
|
||||
|
||||
const resolveThumbnailUrlForDoc = useCallback(
|
||||
(doc) => {
|
||||
if (!doc) {
|
||||
return null;
|
||||
}
|
||||
const asset = getDocumentAsset(doc, 'thumbnail');
|
||||
const now = Date.now();
|
||||
const assetExpiresAt = typeof asset?.expiresAt === 'number' ? asset.expiresAt : null;
|
||||
if (asset?.url && (!assetExpiresAt || assetExpiresAt > now)) {
|
||||
return asset.url;
|
||||
}
|
||||
if (doc.id && asset?.id) {
|
||||
ensureAssetUrl(doc.id, asset, { force: Boolean(assetExpiresAt && assetExpiresAt <= now) }).catch(
|
||||
() => {},
|
||||
);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
(doc) =>
|
||||
resolveDocumentAssetUrl(doc, 'thumbnail', {
|
||||
ensureAssetUrl,
|
||||
getAsset: getDocumentAsset,
|
||||
}),
|
||||
[ensureAssetUrl, getDocumentAsset],
|
||||
);
|
||||
|
||||
@@ -5088,7 +5086,6 @@ const AppLayout = () => {
|
||||
onCreateTag: handleTagCreate,
|
||||
onAssignTagToDocument: handleDocumentTagAttach,
|
||||
onRemoveTagFromDocument: handleTagRemove,
|
||||
resolveAssetPath: resolveApiPath,
|
||||
ensureAssetUrl,
|
||||
getDocumentAsset,
|
||||
}),
|
||||
@@ -5105,7 +5102,6 @@ const AppLayout = () => {
|
||||
handleTagCreate,
|
||||
handleDocumentTagAttach,
|
||||
handleTagRemove,
|
||||
resolveApiPath,
|
||||
ensureAssetUrl,
|
||||
getDocumentAsset,
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user