import { Suspense, useEffect, memo } from '@wordpress/element';
import { FilePreview, Skeleton } from '@bsf/force-ui';
import ErrorBoundary from './error-boundary';
import { createResourcePromise } from '@Functions/utils';
import { fetchImageDataByUrl } from '@Functions/api';
// Create a resource cache for fetching image data
const imageCache = new Map();
const fetchImageDataById = ( imageId ) => {
const image = wp.media.attachment( imageId );
return image.fetch();
};
// Function to fetch image data with caching
const fetchImageData = ( { imageId = '', imageUrl = '' } ) => {
// Return from cache if available
if ( imageCache.has( imageId || imageUrl ) ) {
return imageCache.get( imageId || imageUrl );
}
// Create a new resource if not in cache
const resource = createResourcePromise(
new Promise( ( resolve ) => {
if ( ! imageId && ! imageUrl ) {
resolve( {} );
return;
}
const processById = ( id ) => {
if ( ! id ) {
resolve( {} );
return;
}
const image = fetchImageDataById( id );
// Handle both promise and non-promise returns
if ( image && typeof image.then === 'function' ) {
image
.then( () => {
resolve( image );
} )
.catch( () => {
resolve( {} );
} );
} else {
resolve( image );
}
};
// If image url is provided, fetch the image data by url first
if ( imageUrl ) {
fetchImageDataByUrl( imageUrl )
.then( ( images ) => {
if ( images ) {
processById( images.id );
} else {
resolve( {} );
}
} )
.catch( () => {
resolve( {} );
} );
} else if ( imageId ) {
// If only ID is provided, fetch directly by ID
processById( imageId );
} else {
resolve( {} );
}
} )
);
// Store in cache and return
imageCache.set( imageId || imageUrl, resource );
return resource;
};
// Loading fallback component
const LoadingFallback = () => <Skeleton className="h-14 w-full"></Skeleton>;
// Main component that reads from the resource
const MediaPreviewContent = ( { imageId, imageUrl, onRemove } ) => {
// Use the cached resource
let imageData = fetchImageData( { imageId, imageUrl } ).read() || {};
if ( 'attributes' in imageData ) {
imageData = imageData.attributes;
}
// Safely extract properties with defaults
const filename = imageData.filename || '';
const filesizeHumanReadable = imageData.filesizeInBytes || '';
const url = imageData.url || '';
const type = imageData.type || '';
return (
<div className="[&>div]:m-0">
<FilePreview
file={ {
name: filename,
url,
type,
size: filesizeHumanReadable,
} }
onRemove={ onRemove }
size="md"
/>
</div>
);
};
// Wrapper component with Suspense
const MediaPreview = ( { imageId, imageUrl, onRemove } ) => {
useEffect( () => {
const handleBeforeUnload = () => {
if ( imageId || imageUrl ) {
imageCache.delete( imageId || imageUrl );
}
};
window.addEventListener( 'beforeunload', handleBeforeUnload );
return () => {
window.removeEventListener( 'beforeunload', handleBeforeUnload );
};
}, [ imageId, imageUrl ] );
// Don't render anything if no imageId is provided
if ( ! imageId && ! imageUrl ) {
return null;
}
return (
<ErrorBoundary>
<Suspense fallback={ <LoadingFallback /> }>
<MediaPreviewContent
imageId={ imageId }
imageUrl={ imageUrl }
onRemove={ onRemove }
/>
</Suspense>
</ErrorBoundary>
);
};
export default memo( MediaPreview );