From 297d5aca1fbc65763610e48132ee9da0c386e032 Mon Sep 17 00:00:00 2001 From: Nils Schneider Date: Sat, 18 Oct 2025 01:33:12 +0200 Subject: [PATCH] frontend: papercorner for multi-page documents --- frontend/src/assets/papercorner.svg | 19 +++++++ frontend/src/documents/DocumentsTable.jsx | 61 ++++++++++++++++++----- frontend/src/styles.css | 41 ++++++++++----- 3 files changed, 95 insertions(+), 26 deletions(-) create mode 100644 frontend/src/assets/papercorner.svg diff --git a/frontend/src/assets/papercorner.svg b/frontend/src/assets/papercorner.svg new file mode 100644 index 0000000..3fb513b --- /dev/null +++ b/frontend/src/assets/papercorner.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/documents/DocumentsTable.jsx b/frontend/src/documents/DocumentsTable.jsx index ee28eca..12660d1 100644 --- a/frontend/src/documents/DocumentsTable.jsx +++ b/frontend/src/documents/DocumentsTable.jsx @@ -1,11 +1,36 @@ import React, { useCallback, useEffect, useMemo, useRef } from 'react'; -import { resolveDocumentAssetUrl } from '../asset_manager'; +import {getAssetFromVersion, resolveDocumentAssetUrl} from '../asset_manager'; import { getTagColorStyle } from '../utils/colors'; import { DownloadIcon, EditIcon, ViewListIcon, ViewGridIcon, FolderIcon } from '../ui/icons'; const TAG_MIME_TYPES = ['application/x-papercrate-tag', 'text/papercrate-tag']; -const DocumentThumbnailImage = ({ document, ensureAssetUrl, getDocumentAsset, alt }) => { +const getPageCount = (doc) => + Number.isFinite(doc?.current_version?.metadata?.page_count) + ? doc.current_version.metadata.page_count + : null; + +const DocumentThumbnailImage = ({ document, ensureAssetUrl, getDocumentAsset, alt, maxSize = 48 }) => { + const resolvedMaxSize = Math.max(1, Math.round(maxSize || 1)); + const thumbnailAsset = useMemo(() => getAssetFromVersion(document?.current_version, 'thumbnail'), [document?.current_version]); + const assetWidth = Number(thumbnailAsset?.metadata?.width); + const assetHeight = Number(thumbnailAsset?.metadata?.height); + + const dimensions = useMemo(() => { + if (!Number.isFinite(assetWidth) || assetWidth <= 0 || !Number.isFinite(assetHeight) || assetHeight <= 0) { + return { width: resolvedMaxSize, height: resolvedMaxSize }; + } + const scale = Math.min(1, resolvedMaxSize / assetWidth, resolvedMaxSize / assetHeight); + return { + width: Math.max(1, Math.round(assetWidth * scale)), + height: Math.max(1, Math.round(assetHeight * scale)), + }; + }, [assetWidth, assetHeight, resolvedMaxSize]); + + const innerStyle = useMemo( + () => ({ width: `${dimensions.width}px`, height: `${dimensions.height}px` }), + [dimensions.height, dimensions.width], + ); const url = useMemo( () => resolveDocumentAssetUrl(document, 'thumbnail', { @@ -15,19 +40,28 @@ const DocumentThumbnailImage = ({ document, ensureAssetUrl, getDocumentAsset, al [document, ensureAssetUrl, getDocumentAsset], ); + const pageCount = getPageCount(document); + const showMultiPageBadge = Number.isFinite(pageCount) && pageCount > 1; + const innerClasses = ['document-thumbnail-inner']; + if (showMultiPageBadge) { + innerClasses.push('document-thumbnail-inner--multipage'); + } + return (
- {url ? ( - {alt event.preventDefault()} - /> - ) : ( -
DOC
- )} +
+ {url ? ( + {alt event.preventDefault()} + /> + ) : ( +
DOC
+ )} +
); }; @@ -413,6 +447,7 @@ const DocumentsTable = ({ ensureAssetUrl={ensureAssetUrl} getDocumentAsset={getDocumentAsset} alt={`Thumbnail for ${doc.title || doc.original_name}`} + maxSize={128} />