From ca716c400a73efb71e6cad3721e5dadf97d3422d Mon Sep 17 00:00:00 2001 From: Ryan Date: Sun, 28 Nov 2021 16:22:27 -0800 Subject: [PATCH] Explorer Metaplex NFTs: Adding External URL Button (#21095) * Metaplex NFTs: Added external URL button to explorer - moved fetching MetadataJSON from URI to MetaplexNFTHeader - checked if external_url exists in MetadataJSON - rendered a button to link to the external url if it exists * cleanup unused import * fixed code formatting * Changed NFT website button to overview section field and Moved NFT URI fetch logic to accounts provider. * removed unused CSS * Update TokenAccountSection.tsx * fixed formatting and 1 other error --- .../components/account/MetaplexNFTHeader.tsx | 3 +- .../account/TokenAccountSection.tsx | 15 +++++ explorer/src/components/common/NFTArt.tsx | 61 +----------------- explorer/src/providers/accounts/index.tsx | 63 ++++++++++++++++++- 4 files changed, 80 insertions(+), 62 deletions(-) diff --git a/explorer/src/components/account/MetaplexNFTHeader.tsx b/explorer/src/components/account/MetaplexNFTHeader.tsx index cfd06f877..f60d54db3 100644 --- a/explorer/src/components/account/MetaplexNFTHeader.tsx +++ b/explorer/src/components/account/MetaplexNFTHeader.tsx @@ -15,10 +15,11 @@ export function NFTHeader({ address: string; }) { const metadata = nftData.metadata; + const data = nftData.json; return (
- +
{
Metaplex NFT
} diff --git a/explorer/src/components/account/TokenAccountSection.tsx b/explorer/src/components/account/TokenAccountSection.tsx index 795353b6d..2023e612e 100644 --- a/explorer/src/components/account/TokenAccountSection.tsx +++ b/explorer/src/components/account/TokenAccountSection.tsx @@ -361,6 +361,21 @@ function NonFungibleTokenMintAccountCard({ /> + {nftData?.json && nftData.json.external_url && ( + + Website + + + {nftData.json.external_url} + + + + + )} {nftData?.metadata.data && ( Seller Fee diff --git a/explorer/src/components/common/NFTArt.tsx b/explorer/src/components/common/NFTArt.tsx index 6713ead5b..abaab86d9 100644 --- a/explorer/src/components/common/NFTArt.tsx +++ b/explorer/src/components/common/NFTArt.tsx @@ -9,7 +9,7 @@ import { } from "@metaplex/js"; import ContentLoader from "react-content-loader"; import ErrorLogo from "img/logos-solana/dark-solana-logo.svg"; -import { getLast, pubkeyToString } from "utils"; +import { getLast } from "utils"; const MAX_TIME_LOADING_IMAGE = 5000; /* 5 seconds */ @@ -242,6 +242,7 @@ export const ArtContent = ({ uri, animationURL, files, + data, }: { metadata: programs.metadata.MetadataData; category?: MetaDataJsonCategory; @@ -250,11 +251,8 @@ export const ArtContent = ({ uri?: string; animationURL?: string; files?: (MetadataJsonFile | string)[]; + data: MetadataJson | undefined; }) => { - const id = pubkeyToString(pubkey); - - const { data } = useExtendedArt(id, metadata); - if (pubkey && data) { uri = data.image; animationURL = data.animation_url; @@ -357,56 +355,3 @@ export const useCachedImage = (uri: string) => { return { cachedBlob }; }; - -export const useExtendedArt = ( - id: string, - metadata: programs.metadata.MetadataData -) => { - const [data, setData] = useState(); - - useEffect(() => { - if (id && !data) { - if (metadata.data.uri) { - const uri = metadata.data.uri; - - const processJson = (extended: any) => { - if (!extended || extended?.properties?.files?.length === 0) { - return; - } - - if (extended?.image) { - extended.image = extended.image.startsWith("http") - ? extended.image - : `${metadata.data.uri}/${extended.image}`; - } - - return extended; - }; - - try { - fetch(uri) - .then(async (_) => { - try { - const data = await _.json(); - try { - localStorage.setItem(uri, JSON.stringify(data)); - } catch { - // ignore - } - setData(processJson(data)); - } catch { - return undefined; - } - }) - .catch(() => { - return undefined; - }); - } catch (ex) { - console.error(ex); - } - } - } - }, [id, data, setData, metadata.data.uri]); - - return { data }; -}; diff --git a/explorer/src/providers/accounts/index.tsx b/explorer/src/providers/accounts/index.tsx index 362e05220..4c9ab7724 100644 --- a/explorer/src/providers/accounts/index.tsx +++ b/explorer/src/providers/accounts/index.tsx @@ -1,4 +1,5 @@ import React from "react"; +import { pubkeyToString } from "utils"; import { PublicKey, Connection, StakeActivationData } from "@solana/web3.js"; import { useCluster, Cluster } from "../cluster"; import { HistoryProvider } from "./history"; @@ -25,7 +26,7 @@ import { UpgradeableLoaderAccount, } from "validators/accounts/upgradeable-program"; import { RewardsProvider } from "./rewards"; -import { programs } from "@metaplex/js"; +import { programs, MetadataJson } from "@metaplex/js"; import getEditionInfo, { EditionInfo } from "./utils/getEditionInfo"; export { useAccountHistory } from "./history"; @@ -45,6 +46,7 @@ export type UpgradeableLoaderAccountData = { export type NFTData = { metadata: programs.metadata.MetadataData; + json: MetadataJson | undefined; editionInfo: EditionInfo; }; @@ -252,8 +254,16 @@ async function fetchAccountInfo( metadata, connection ); - - nftData = { metadata: metadata.data, editionInfo }; + const id = pubkeyToString(pubkey); + const metadataJSON = await getMetaDataJSON( + id, + metadata.data + ); + nftData = { + metadata: metadata.data, + json: metadataJSON, + editionInfo, + }; } } } catch (error) { @@ -298,6 +308,53 @@ async function fetchAccountInfo( }); } +const getMetaDataJSON = async ( + id: string, + metadata: programs.metadata.MetadataData +): Promise => { + return new Promise(async (resolve, reject) => { + const uri = metadata.data.uri; + if (!uri) return resolve(undefined); + + const processJson = (extended: any) => { + if (!extended || extended?.properties?.files?.length === 0) { + return; + } + + if (extended?.image) { + extended.image = extended.image.startsWith("http") + ? extended.image + : `${metadata.data.uri}/${extended.image}`; + } + + return extended; + }; + + try { + fetch(uri) + .then(async (_) => { + try { + const data = await _.json(); + try { + localStorage.setItem(uri, JSON.stringify(data)); + } catch { + // ignore + } + resolve(processJson(data)); + } catch { + resolve(undefined); + } + }) + .catch(() => { + resolve(undefined); + }); + } catch (ex) { + console.error(ex); + resolve(undefined); + } + }); +}; + export function useAccounts() { const context = React.useContext(StateContext); if (!context) {