diff --git a/frontend/src/documents/DocumentsTable.jsx b/frontend/src/documents/DocumentsTable.jsx index 26993b0..954ae4f 100644 --- a/frontend/src/documents/DocumentsTable.jsx +++ b/frontend/src/documents/DocumentsTable.jsx @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect, useMemo, useRef } from 'react'; +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { getAssetFromVersion, resolveDocumentAssetUrl, createAssetView } from '../asset_manager'; import { getTagColorStyle } from '../utils/colors'; import { DownloadIcon, EditIcon, ViewListIcon, ViewGridIcon, FolderIcon, TrashIcon } from '../ui/icons'; @@ -13,13 +13,62 @@ const getPageCount = (doc) => ? doc.current_version.metadata.page_count : null; +// Detects when an element becomes visible within a scroll container. +const useLazyVisibility = (rootRef, resetKey) => { + const targetRef = useRef(null); + const [isVisible, setIsVisible] = useState(false); + + useEffect(() => { + setIsVisible(false); + }, [resetKey]); + + const rootNode = rootRef?.current || null; + + useEffect(() => { + if (isVisible) { + return undefined; + } + const element = targetRef.current; + if (!element) { + return undefined; + } + if (typeof window === 'undefined' || typeof IntersectionObserver === 'undefined') { + setIsVisible(true); + return undefined; + } + + const observer = new IntersectionObserver( + (entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + setIsVisible(true); + observer.disconnect(); + } + }); + }, + { + root: rootNode, + rootMargin: '200px 0px', + threshold: 0.01, + }, + ); + + observer.observe(element); + return () => observer.disconnect(); + }, [isVisible, rootNode, resetKey]); + + return { ref: targetRef, isVisible }; +}; + const DocumentThumbnailImage = ({ document, ensureAssetUrl, getDocumentAsset, alt, maxSize = LIST_ICON_SIZE, + scrollRootRef = null, }) => { + const { ref: visibilityRef, isVisible } = useLazyVisibility(scrollRootRef, document?.id); const resolvedMaxSize = Math.max(1, Math.round(maxSize || 1)); const thumbnailAsset = useMemo( () => getAssetFromVersion(document?.current_version, 'thumbnail'), @@ -45,14 +94,15 @@ const DocumentThumbnailImage = ({ () => ({ width: `${dimensions.width}px`, height: `${dimensions.height}px` }), [dimensions.height, dimensions.width], ); - const url = useMemo( - () => - resolveDocumentAssetUrl(document, 'thumbnail', { - ensureAssetUrl, - getAsset: getDocumentAsset, - }), - [document, ensureAssetUrl, getDocumentAsset], - ); + const url = useMemo(() => { + if (!isVisible) { + return null; + } + return resolveDocumentAssetUrl(document, 'thumbnail', { + ensureAssetUrl, + getAsset: getDocumentAsset, + }); + }, [document, ensureAssetUrl, getDocumentAsset, isVisible]); const pageCount = getPageCount(document); const showMultiPageBadge = Number.isFinite(pageCount) && pageCount > 1; @@ -62,13 +112,15 @@ const DocumentThumbnailImage = ({ } return ( -