/** * External dependencies */ import clsx from 'clsx'; /** * WordPress dependencies */ import { useSelect } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; import { __experimentalTruncate as Truncate, __experimentalVStack as VStack, Icon as WCIcon, } from '@wordpress/components'; import { useState, useRef, useLayoutEffect } from '@wordpress/element'; import type { Attachment } from '@wordpress/core-data'; import { getFilename } from '@wordpress/url'; import type { DataViewRenderFieldProps } from '@wordpress/dataviews'; /** * Internal dependencies */ import { getMediaTypeFromMimeType } from '../utils/get-media-type-from-mime-type'; import type { MediaItem } from '../types'; /** * Given the available image sizes and a target display width, returns the URL * of the smallest size whose width is >= the target. Falls back to the largest * available size, or the original source_url. * * @param featuredMedia The media item with size details. * @param configSizes The target display size string (e.g. '900px'). */ export function getBestImageUrl( featuredMedia: Attachment | MediaItem, configSizes?: string ): string { const sizes = featuredMedia?.media_details?.sizes; if ( ! sizes ) { return featuredMedia.source_url; } const sizeEntries = Object.values( sizes ); if ( ! sizeEntries.length ) { return featuredMedia.source_url; } // Parse target width from config.sizes (e.g. '900px' → 900). const targetWidth = configSizes ? parseInt( configSizes, 10 ) : NaN; if ( ! Number.isNaN( targetWidth ) ) { // Filter to entries that have a valid numeric width. const validEntries = sizeEntries.filter( ( s ) => typeof s.width === 'number' && ! Number.isNaN( s.width ) ); if ( ! validEntries.length ) { return featuredMedia.source_url; } // Sort ascending by width. const sorted = [ ...validEntries ].sort( ( a, b ) => a.width - b.width ); // Pick the smallest size that is >= target width. const match = sorted.find( ( s ) => s.width >= targetWidth ); if ( match ) { return match.source_url; } // No size large enough — use the largest available. return sorted[ sorted.length - 1 ].source_url; } // If we can't parse the target, fall back to source_url. return featuredMedia.source_url; } function FallbackView( { item, filename, }: { item: MediaItem; filename: string; } ) { return (
{ !! filename && (
{ filename }
) }
); } function ImageView( { item, configSizes, onError, }: { item: Attachment | MediaItem; configSizes?: string; onError: () => void; } ) { const imageUrl = getBestImageUrl( item, configSizes ); /* * Use three states to avoid fade-in animation for cached images: * 'instant' = image already cached, 'loading' = waiting, 'loaded' = just finished. * * useLayoutEffect runs synchronously after DOM mutations but before paint, * so we can check img.complete to detect disk-cached images and skip the * fade-in animation entirely. */ const imgRef = useRef< HTMLImageElement >( null ); const [ loadingState, setLoadingState ] = useState< 'instant' | 'loading' | 'loaded' >( 'loading' ); useLayoutEffect( () => { if ( imgRef.current?.complete ) { setLoadingState( 'instant' ); } else { setLoadingState( 'loading' ); } }, [ imageUrl ] ); const handleLoad = () => { if ( loadingState === 'loading' ) { setLoadingState( 'loaded' ); } }; return (
{
); } export default function MediaThumbnailView( { item, config, }: DataViewRenderFieldProps< MediaItem > ) { const [ imageError, setImageError ] = useState( false ); const _featuredMedia = useSelect( ( select ) => { // Avoid the network request if it's not needed. `featured_media` is // 0 for images and media without featured media. if ( ! item.featured_media ) { return; } return select( coreStore ).getEntityRecord< Attachment >( 'postType', 'attachment', item.featured_media ); }, [ item.featured_media ] ); const featuredMedia = item.featured_media ? _featuredMedia : item; // Fetching. if ( ! featuredMedia ) { return null; } const filename = getFilename( featuredMedia.source_url || '' ); // Show fallback if image failed to load or if not an image type. if ( imageError || getMediaTypeFromMimeType( featuredMedia.mime_type ).type !== 'image' ) { return ( ); } return ( setImageError( true ) } /> ); }