From 023c440047064f28d9f09f94c7e756d496161aeb Mon Sep 17 00:00:00 2001 From: tom goriunov Date: Fri, 13 Dec 2024 18:28:17 +0100 Subject: [PATCH] Support CDN for NFT images (#2461) * Support CDN for NFT images Fixes #2424 * add test * fix ts * disable helia in preview env * [skip ci] enable again helia fetch for review stands --- mocks/tokens/tokenInstance.ts | 1 + stubs/token.ts | 1 + types/api/token.ts | 3 ++ ui/shared/nft/NftImage.tsx | 4 +- ui/shared/nft/NftMedia.pw.tsx | 18 ++++++++ ui/shared/nft/NftMedia.tsx | 21 +++++++--- ui/shared/nft/NftVideo.tsx | 9 ++-- ...efault_image-preview-with-thumbnails-1.png | Bin 0 -> 11573 bytes ui/shared/nft/useNftMediaInfo.tsx | 39 +++++++++--------- ui/shared/nft/utils.ts | 2 + 10 files changed, 69 insertions(+), 29 deletions(-) create mode 100644 ui/shared/nft/__screenshots__/NftMedia.pw.tsx_default_image-preview-with-thumbnails-1.png diff --git a/mocks/tokens/tokenInstance.ts b/mocks/tokens/tokenInstance.ts index 712c466b7d..1dc1b96493 100644 --- a/mocks/tokens/tokenInstance.ts +++ b/mocks/tokens/tokenInstance.ts @@ -73,6 +73,7 @@ export const base: TokenInstance = { name: 'GENESIS #188848, 22a5f8bbb1602995. Blockchain pixel PFP NFT + "on music video" trait inspired by God', }, owner: addressMock.withName, + thumbnails: null, }; export const withRichMetadata: TokenInstance = { diff --git a/stubs/token.ts b/stubs/token.ts index 9f489b1113..c301ff7cd2 100644 --- a/stubs/token.ts +++ b/stubs/token.ts @@ -175,4 +175,5 @@ export const TOKEN_INSTANCE: TokenInstance = { }, owner: ADDRESS_PARAMS, holder_address_hash: ADDRESS_HASH, + thumbnails: null, }; diff --git a/types/api/token.ts b/types/api/token.ts index 98feb08bf4..c223d70a03 100644 --- a/types/api/token.ts +++ b/types/api/token.ts @@ -51,6 +51,8 @@ export type TokenHoldersPagination = { value: string; }; +export type ThumbnailSize = '60x60' | '250x250' | '500x500' | 'original'; + export interface TokenInstance { is_unique: boolean; id: string; @@ -60,6 +62,7 @@ export interface TokenInstance { external_app_url: string | null; metadata: Record | null; owner: AddressParam | null; + thumbnails: Partial> | null; } export interface TokenInstanceMetadataSocketMessage { diff --git a/ui/shared/nft/NftImage.tsx b/ui/shared/nft/NftImage.tsx index a728c93d65..4ec4e9c746 100644 --- a/ui/shared/nft/NftImage.tsx +++ b/ui/shared/nft/NftImage.tsx @@ -5,17 +5,19 @@ import { mediaStyleProps } from './utils'; interface Props { src: string; + srcSet?: string; onLoad: () => void; onError: () => void; onClick?: () => void; } -const NftImage = ({ src, onLoad, onError, onClick }: Props) => { +const NftImage = ({ src, srcSet, onLoad, onError, onClick }: Props) => { return ( Token instance image { await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 250, height: 250 } }); }); + test('preview with thumbnails', async({ render, page, mockAssetResponse }) => { + const THUMBNAIL_URL = 'https://localhost:3000/my-image-250.jpg'; + const data = { + animation_url: MEDIA_URL, + image_url: null, + thumbnails: { + '500x500': THUMBNAIL_URL, + }, + } as TokenInstance; + await mockAssetResponse(THUMBNAIL_URL, './playwright/mocks/image_md.jpg'); + await render( + + + , + ); + await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 250, height: 250 } }); + }); + test('preview hover', async({ render, page }) => { const data = { animation_url: MEDIA_URL, diff --git a/ui/shared/nft/NftMedia.tsx b/ui/shared/nft/NftMedia.tsx index 894bbb284f..23ce3a19fd 100644 --- a/ui/shared/nft/NftMedia.tsx +++ b/ui/shared/nft/NftMedia.tsx @@ -64,14 +64,23 @@ const NftMedia = ({ data, className, isLoading, withFullscreen, autoplayVideo }: ...(withFullscreen ? { onClick: onOpen } : {}), }; - switch (mediaInfo.type) { + switch (mediaInfo.mediaType) { case 'video': { return ; } case 'html': return ; - case 'image': + case 'image': { + if (mediaInfo.srcType === 'url' && data.thumbnails) { + const srcSet = data.thumbnails['250x250'] && data.thumbnails['500x500'] ? `${ data.thumbnails['500x500'] } 2x` : undefined; + const src = (srcSet ? data.thumbnails['250x250'] : undefined) || data.thumbnails['500x500'] || data.thumbnails.original; + if (src) { + return ; + } + } + return ; + } default: return null; } @@ -87,13 +96,15 @@ const NftMedia = ({ data, className, isLoading, withFullscreen, autoplayVideo }: onClose, }; - switch (mediaInfo.type) { + switch (mediaInfo.mediaType) { case 'video': return ; case 'html': return ; - case 'image': - return ; + case 'image': { + const src = mediaInfo.srcType === 'url' && data.thumbnails?.original ? data.thumbnails.original : mediaInfo.src; + return ; + } default: return null; } diff --git a/ui/shared/nft/NftVideo.tsx b/ui/shared/nft/NftVideo.tsx index 1df7565edd..8b352a9271 100644 --- a/ui/shared/nft/NftVideo.tsx +++ b/ui/shared/nft/NftVideo.tsx @@ -44,8 +44,9 @@ const NftVideo = ({ src, instance, autoPlay = true, onLoad, onError, onClick }: // otherwise, the skeleton will be shown underneath the element until the video is loaded onLoad(); } catch (error) { - if (instance.image_url) { - ref.current.poster = instance.image_url; + const src = instance.thumbnails?.['500x500'] || instance.thumbnails?.original || instance.image_url; + if (src) { + ref.current.poster = src; // we want to call onLoad right after the poster is loaded // otherwise, the skeleton will be shown underneath the element until the video is loaded @@ -54,10 +55,10 @@ const NftVideo = ({ src, instance, autoPlay = true, onLoad, onError, onClick }: poster.onload = onLoad; } } - }, [ instance.image_url, instance.metadata?.image, onLoad ]); + }, [ instance.image_url, instance.metadata?.image, instance.thumbnails, onLoad ]); React.useEffect(() => { - fetchVideoPoster(); + !autoPlay && fetchVideoPoster(); return () => { controller.current?.abort(); }; diff --git a/ui/shared/nft/__screenshots__/NftMedia.pw.tsx_default_image-preview-with-thumbnails-1.png b/ui/shared/nft/__screenshots__/NftMedia.pw.tsx_default_image-preview-with-thumbnails-1.png new file mode 100644 index 0000000000000000000000000000000000000000..e10f86e0c85f91f825638a14099ff46b793eea39 GIT binary patch literal 11573 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 WFy}!diElb+I3+oC**a { @@ -72,8 +73,8 @@ function composeAssetsData(data: TokenInstance): Record({ type: undefined }); +function useFetchAssetViaIpfs(url: string | undefined, mediaType: MediaType | undefined, isEnabled: boolean): ReturnType | null { + const [ result, setResult ] = React.useState({ mediaType: undefined }); const controller = React.useRef(null); const fetchAsset = React.useCallback(async(url: string) => { @@ -83,7 +84,7 @@ function useFetchAssetViaIpfs(url: string | undefined, type: MediaType | undefin if (response.status === 200) { const blob = await response.blob(); const src = URL.createObjectURL(blob); - setResult({ type: 'image', src }); + setResult({ mediaType: 'image', src, srcType: 'blob' }); return; } } catch (error) {} @@ -92,15 +93,15 @@ function useFetchAssetViaIpfs(url: string | undefined, type: MediaType | undefin React.useEffect(() => { if (isEnabled) { - if (config.UI.views.nft.verifiedFetch.isEnabled && type === 'image' && url && url.includes('ipfs')) { + if (config.UI.views.nft.verifiedFetch.isEnabled && mediaType === 'image' && url && url.includes('ipfs')) { fetchAsset(url); } else { setResult(null); } } else { - setResult({ type: undefined }); + setResult({ mediaType: undefined }); } - }, [ fetchAsset, url, type, isEnabled ]); + }, [ fetchAsset, url, mediaType, isEnabled ]); React.useEffect(() => { return () => { @@ -114,7 +115,7 @@ function useFetchAssetViaIpfs(url: string | undefined, type: MediaType | undefin function useNftMediaTypeQuery(url: string | undefined, enabled: boolean) { const fetch = useFetch(); - return useQuery, ReturnType | null>({ + return useQuery, ReturnType | null>({ queryKey: [ 'nft-media-type', url ], queryFn: async() => { if (!url) { @@ -130,10 +131,10 @@ function useNftMediaTypeQuery(url: string | undefined, enabled: boolean) { const preliminaryType = getPreliminaryMediaType(url); if (preliminaryType) { - return { type: preliminaryType, src: url }; + return { mediaType: preliminaryType, src: url, srcType: 'url' }; } - const type = await (async() => { + const mediaType = await (async() => { try { const mediaTypeResourceUrl = route({ pathname: '/node-api/media-type' as StaticRoute<'/api/media-type'>['pathname'], query: { url } }); const response = await fetch<{ type: MediaType | undefined }, ResourceError>(mediaTypeResourceUrl, undefined, { resource: 'media-type' }); @@ -144,14 +145,14 @@ function useNftMediaTypeQuery(url: string | undefined, enabled: boolean) { } })(); - if (!type) { + if (!mediaType) { return null; } - return { type, src: url }; + return { mediaType, src: url, srcType: 'url' }; }, enabled, - placeholderData: { type: undefined }, + placeholderData: { mediaType: undefined }, staleTime: Infinity, }); } diff --git a/ui/shared/nft/utils.ts b/ui/shared/nft/utils.ts index 3c0ce90bac..d788cdb757 100644 --- a/ui/shared/nft/utils.ts +++ b/ui/shared/nft/utils.ts @@ -1,5 +1,7 @@ export type MediaType = 'image' | 'video' | 'html'; +export type SrcType = 'url' | 'blob'; + const IMAGE_EXTENSIONS = [ '.jpg', 'jpeg', '.png',