Files
papercrate/frontend/src/desktop/hooks/usePreviewMetadata.ts
T
2025-11-15 04:11:22 +01:00

110 lines
2.9 KiB
TypeScript

import { useEffect, useState } from 'react';
import { createAssetView } from '../../asset_manager';
interface DocumentLike {
id?: string | number;
current_version?: unknown;
tags?: unknown;
}
interface AssetLike {
id?: string | number;
[key: string]: unknown;
}
interface PreviewMetadataEntry {
docId: string;
width: number;
height: number;
}
type GetDocumentAsset = (doc: DocumentLike, type: string) => AssetLike | null;
type EnsureAssetUrl = (docId: string | number, asset: AssetLike, options?: { start?: number; limit?: number }) => Promise<AssetLike | null>;
const usePreviewMetadata = (
documents: DocumentLike[] | null,
getDocumentAsset?: GetDocumentAsset,
ensureAssetUrl?: EnsureAssetUrl,
) => {
const [metadataMap, setMetadataMap] = useState<Map<string, PreviewMetadataEntry>>(() => new Map());
useEffect(() => {
let cancelled = false;
const docs = Array.isArray(documents) ? documents : [];
if (!docs.length) {
setMetadataMap(new Map());
return () => {
cancelled = true;
};
}
const fetchMetadataForDoc = async (doc: DocumentLike) => {
if (!doc?.id) {
return null;
}
const docId = String(doc.id);
const resolveAsset = (type: string) => getDocumentAsset?.(doc, type) ?? null;
let asset = resolveAsset('preview') || resolveAsset('thumbnail');
let view = createAssetView(asset);
let metadata = view.getPrimaryMetadata();
const hasDimensions = (meta: { width?: number | string; height?: number | string } | null) =>
Number.isFinite(Number(meta?.width)) &&
Number.isFinite(Number(meta?.height)) &&
Number(meta.width) > 0 &&
Number(meta.height) > 0;
if (!hasDimensions(metadata) && ensureAssetUrl && docId && asset?.id) {
try {
const ensured = await ensureAssetUrl(doc.id, asset, { start: 1, limit: 1 });
if (ensured) {
asset = ensured;
view = createAssetView(asset);
metadata = view.getPrimaryMetadata();
}
} catch (error) {
console.warn('[desk] ensureDocumentSize metadata fetch failed', error);
}
}
if (!hasDimensions(metadata)) {
return null;
}
return {
docId,
width: Number(metadata.width),
height: Number(metadata.height),
};
};
let mounted = true;
(async () => {
const entries = await Promise.all(docs.map(fetchMetadataForDoc));
if (!mounted || cancelled) {
return;
}
const next = new Map();
entries.forEach((entry) => {
if (entry && entry.docId) {
next.set(entry.docId, entry);
}
});
if (!cancelled) {
setMetadataMap(next);
}
})();
return () => {
cancelled = true;
mounted = false;
};
}, [documents, getDocumentAsset, ensureAssetUrl]);
return metadataMap;
};
export default usePreviewMetadata;