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
This commit is contained in:
Ryan 2021-11-28 16:22:27 -08:00 committed by GitHub
parent cd7331c6be
commit ca716c400a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 80 additions and 62 deletions

View File

@ -15,10 +15,11 @@ export function NFTHeader({
address: string;
}) {
const metadata = nftData.metadata;
const data = nftData.json;
return (
<div className="row">
<div className="col-auto ms-2 d-flex align-items-center">
<ArtContent metadata={metadata} pubkey={address} />
<ArtContent metadata={metadata} pubkey={address} data={data} />
</div>
<div className="col mb-3 ms-0.5 mt-3">
{<h6 className="header-pretitle ms-1">Metaplex NFT</h6>}

View File

@ -361,6 +361,21 @@ function NonFungibleTokenMintAccountCard({
/>
</td>
</tr>
{nftData?.json && nftData.json.external_url && (
<tr>
<td>Website</td>
<td className="text-lg-end">
<a
rel="noopener noreferrer"
target="_blank"
href={nftData.json.external_url}
>
{nftData.json.external_url}
<span className="fe fe-external-link ms-2"></span>
</a>
</td>
</tr>
)}
{nftData?.metadata.data && (
<tr>
<td>Seller Fee</td>

View File

@ -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<MetadataJson>();
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 };
};

View File

@ -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<MetadataJson | undefined> => {
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) {