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:
parent
cd7331c6be
commit
ca716c400a
|
@ -15,10 +15,11 @@ export function NFTHeader({
|
||||||
address: string;
|
address: string;
|
||||||
}) {
|
}) {
|
||||||
const metadata = nftData.metadata;
|
const metadata = nftData.metadata;
|
||||||
|
const data = nftData.json;
|
||||||
return (
|
return (
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-auto ms-2 d-flex align-items-center">
|
<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>
|
||||||
<div className="col mb-3 ms-0.5 mt-3">
|
<div className="col mb-3 ms-0.5 mt-3">
|
||||||
{<h6 className="header-pretitle ms-1">Metaplex NFT</h6>}
|
{<h6 className="header-pretitle ms-1">Metaplex NFT</h6>}
|
||||||
|
|
|
@ -361,6 +361,21 @@ function NonFungibleTokenMintAccountCard({
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</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 && (
|
{nftData?.metadata.data && (
|
||||||
<tr>
|
<tr>
|
||||||
<td>Seller Fee</td>
|
<td>Seller Fee</td>
|
||||||
|
|
|
@ -9,7 +9,7 @@ import {
|
||||||
} from "@metaplex/js";
|
} from "@metaplex/js";
|
||||||
import ContentLoader from "react-content-loader";
|
import ContentLoader from "react-content-loader";
|
||||||
import ErrorLogo from "img/logos-solana/dark-solana-logo.svg";
|
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 */
|
const MAX_TIME_LOADING_IMAGE = 5000; /* 5 seconds */
|
||||||
|
|
||||||
|
@ -242,6 +242,7 @@ export const ArtContent = ({
|
||||||
uri,
|
uri,
|
||||||
animationURL,
|
animationURL,
|
||||||
files,
|
files,
|
||||||
|
data,
|
||||||
}: {
|
}: {
|
||||||
metadata: programs.metadata.MetadataData;
|
metadata: programs.metadata.MetadataData;
|
||||||
category?: MetaDataJsonCategory;
|
category?: MetaDataJsonCategory;
|
||||||
|
@ -250,11 +251,8 @@ export const ArtContent = ({
|
||||||
uri?: string;
|
uri?: string;
|
||||||
animationURL?: string;
|
animationURL?: string;
|
||||||
files?: (MetadataJsonFile | string)[];
|
files?: (MetadataJsonFile | string)[];
|
||||||
|
data: MetadataJson | undefined;
|
||||||
}) => {
|
}) => {
|
||||||
const id = pubkeyToString(pubkey);
|
|
||||||
|
|
||||||
const { data } = useExtendedArt(id, metadata);
|
|
||||||
|
|
||||||
if (pubkey && data) {
|
if (pubkey && data) {
|
||||||
uri = data.image;
|
uri = data.image;
|
||||||
animationURL = data.animation_url;
|
animationURL = data.animation_url;
|
||||||
|
@ -357,56 +355,3 @@ export const useCachedImage = (uri: string) => {
|
||||||
|
|
||||||
return { cachedBlob };
|
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 };
|
|
||||||
};
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { pubkeyToString } from "utils";
|
||||||
import { PublicKey, Connection, StakeActivationData } from "@solana/web3.js";
|
import { PublicKey, Connection, StakeActivationData } from "@solana/web3.js";
|
||||||
import { useCluster, Cluster } from "../cluster";
|
import { useCluster, Cluster } from "../cluster";
|
||||||
import { HistoryProvider } from "./history";
|
import { HistoryProvider } from "./history";
|
||||||
|
@ -25,7 +26,7 @@ import {
|
||||||
UpgradeableLoaderAccount,
|
UpgradeableLoaderAccount,
|
||||||
} from "validators/accounts/upgradeable-program";
|
} from "validators/accounts/upgradeable-program";
|
||||||
import { RewardsProvider } from "./rewards";
|
import { RewardsProvider } from "./rewards";
|
||||||
import { programs } from "@metaplex/js";
|
import { programs, MetadataJson } from "@metaplex/js";
|
||||||
import getEditionInfo, { EditionInfo } from "./utils/getEditionInfo";
|
import getEditionInfo, { EditionInfo } from "./utils/getEditionInfo";
|
||||||
export { useAccountHistory } from "./history";
|
export { useAccountHistory } from "./history";
|
||||||
|
|
||||||
|
@ -45,6 +46,7 @@ export type UpgradeableLoaderAccountData = {
|
||||||
|
|
||||||
export type NFTData = {
|
export type NFTData = {
|
||||||
metadata: programs.metadata.MetadataData;
|
metadata: programs.metadata.MetadataData;
|
||||||
|
json: MetadataJson | undefined;
|
||||||
editionInfo: EditionInfo;
|
editionInfo: EditionInfo;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -252,8 +254,16 @@ async function fetchAccountInfo(
|
||||||
metadata,
|
metadata,
|
||||||
connection
|
connection
|
||||||
);
|
);
|
||||||
|
const id = pubkeyToString(pubkey);
|
||||||
nftData = { metadata: metadata.data, editionInfo };
|
const metadataJSON = await getMetaDataJSON(
|
||||||
|
id,
|
||||||
|
metadata.data
|
||||||
|
);
|
||||||
|
nftData = {
|
||||||
|
metadata: metadata.data,
|
||||||
|
json: metadataJSON,
|
||||||
|
editionInfo,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} 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() {
|
export function useAccounts() {
|
||||||
const context = React.useContext(StateContext);
|
const context = React.useContext(StateContext);
|
||||||
if (!context) {
|
if (!context) {
|
||||||
|
|
Loading…
Reference in New Issue