Explorer: use identicon and token layout for unlisted tokens (#16347)
* feat: use identicon and token layout for unlisted tokens * feat: add identicon to smaller icons and change dependency to current package * fix: add proper library
This commit is contained in:
parent
105a6bfb46
commit
a7f8239b46
34
explorer/package-lock.json
generated
34
explorer/package-lock.json
generated
@ -2342,6 +2342,35 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"@metamask/jazzicon": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@metamask/jazzicon/-/jazzicon-2.0.0.tgz",
|
||||
"integrity": "sha512-7M+WSZWKcQAo0LEhErKf1z+D3YX0tEDAcGvcKbDyvDg34uvgeKR00mFNIYwAhdAS9t8YXxhxZgsrRBBg6X8UQg==",
|
||||
"requires": {
|
||||
"color": "^0.11.3",
|
||||
"mersenne-twister": "^1.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"color": {
|
||||
"version": "0.11.4",
|
||||
"resolved": "https://registry.npmjs.org/color/-/color-0.11.4.tgz",
|
||||
"integrity": "sha1-bXtcdPtl6EHNSHkq0e1eB7kE12Q=",
|
||||
"requires": {
|
||||
"clone": "^1.0.2",
|
||||
"color-convert": "^1.3.0",
|
||||
"color-string": "^0.3.0"
|
||||
}
|
||||
},
|
||||
"color-string": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/color-string/-/color-string-0.3.0.tgz",
|
||||
"integrity": "sha1-J9RvtnAlxcL6JZk7+/V55HhBuZE=",
|
||||
"requires": {
|
||||
"color-name": "^1.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@nodelib/fs.scandir": {
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz",
|
||||
@ -12967,6 +12996,11 @@
|
||||
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
|
||||
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="
|
||||
},
|
||||
"mersenne-twister": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/mersenne-twister/-/mersenne-twister-1.1.0.tgz",
|
||||
"integrity": "sha1-+RZhjuQ9cXnvz2Qb7EUx65Zwl4o="
|
||||
},
|
||||
"methods": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
|
||||
|
@ -3,6 +3,7 @@
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@metamask/jazzicon": "^2.0.0",
|
||||
"@project-serum/serum": "^0.13.33",
|
||||
"@react-hook/debounce": "^3.0.0",
|
||||
"@sentry/react": "^6.2.5",
|
||||
|
@ -14,9 +14,12 @@ import { Link } from "react-router-dom";
|
||||
import { Location } from "history";
|
||||
import { useTokenRegistry } from "providers/mints/token-registry";
|
||||
import { BigNumber } from "bignumber.js";
|
||||
import { Identicon } from "components/common/Identicon";
|
||||
|
||||
type Display = "summary" | "detail" | null;
|
||||
|
||||
const SMALL_IDENTICON_WIDTH = 16;
|
||||
|
||||
const useQueryDisplay = (): Display => {
|
||||
const query = useQuery();
|
||||
const filter = query.get("display");
|
||||
@ -102,12 +105,18 @@ function HoldingsDetailTable({ tokens }: { tokens: TokenInfoWithPubkey[] }) {
|
||||
<tr key={address}>
|
||||
{showLogos && (
|
||||
<td className="w-1 p-0 text-center">
|
||||
{tokenDetails?.logoURI && (
|
||||
{tokenDetails?.logoURI ? (
|
||||
<img
|
||||
src={tokenDetails.logoURI}
|
||||
alt="token icon"
|
||||
className="token-icon rounded-circle border border-4 border-gray-dark"
|
||||
/>
|
||||
) : (
|
||||
<Identicon
|
||||
address={address}
|
||||
className="avatar-img identicon-wrapper identicon-wrapper-small"
|
||||
style={{ width: SMALL_IDENTICON_WIDTH }}
|
||||
/>
|
||||
)}
|
||||
</td>
|
||||
)}
|
||||
@ -171,12 +180,18 @@ function HoldingsSummaryTable({ tokens }: { tokens: TokenInfoWithPubkey[] }) {
|
||||
<tr key={mintAddress}>
|
||||
{showLogos && (
|
||||
<td className="w-1 p-0 text-center">
|
||||
{tokenDetails?.logoURI && (
|
||||
{tokenDetails?.logoURI ? (
|
||||
<img
|
||||
src={tokenDetails.logoURI}
|
||||
alt="token icon"
|
||||
className="token-icon rounded-circle border border-4 border-gray-dark"
|
||||
/>
|
||||
) : (
|
||||
<Identicon
|
||||
address={mintAddress}
|
||||
className="avatar-img identicon-wrapper identicon-wrapper-small"
|
||||
style={{ width: SMALL_IDENTICON_WIDTH }}
|
||||
/>
|
||||
)}
|
||||
</td>
|
||||
)}
|
||||
|
34
explorer/src/components/common/Identicon.tsx
Normal file
34
explorer/src/components/common/Identicon.tsx
Normal file
@ -0,0 +1,34 @@
|
||||
import React, { useEffect, useRef } from "react";
|
||||
|
||||
// @ts-ignore
|
||||
import Jazzicon from "@metamask/jazzicon";
|
||||
import bs58 from "bs58";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
|
||||
export function Identicon(props: {
|
||||
address?: string | PublicKey;
|
||||
style?: React.CSSProperties;
|
||||
className?: string;
|
||||
}) {
|
||||
const { style, className } = props;
|
||||
const address =
|
||||
typeof props.address === "string"
|
||||
? props.address
|
||||
: props.address?.toBase58();
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (address && ref.current) {
|
||||
ref.current.innerHTML = "";
|
||||
ref.current.className = className || "";
|
||||
ref.current.appendChild(
|
||||
Jazzicon(
|
||||
style?.width || 16,
|
||||
parseInt(bs58.decode(address).toString("hex").slice(5, 15), 16)
|
||||
)
|
||||
);
|
||||
}
|
||||
}, [address, style, className]);
|
||||
|
||||
return <div className="identicon-wrapper" ref={ref} style={props.style} />;
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { FetchStatus } from "providers/cache";
|
||||
import { CacheEntry, FetchStatus } from "providers/cache";
|
||||
import {
|
||||
useFetchAccountInfo,
|
||||
useAccountInfo,
|
||||
@ -30,7 +30,9 @@ import { ConfigAccountSection } from "components/account/ConfigAccountSection";
|
||||
import { useFlaggedAccounts } from "providers/accounts/flagged-accounts";
|
||||
import { UpgradeableLoaderAccountSection } from "components/account/UpgradeableLoaderAccountSection";
|
||||
import { useTokenRegistry } from "providers/mints/token-registry";
|
||||
import { Identicon } from "components/common/Identicon";
|
||||
|
||||
const IDENTICON_WIDTH = 64;
|
||||
const TABS_LOOKUP: { [id: string]: Tab } = {
|
||||
"spl-token:mint": {
|
||||
slug: "largest",
|
||||
@ -69,49 +71,77 @@ const TOKEN_TABS_HIDDEN = [
|
||||
|
||||
type Props = { address: string; tab?: string };
|
||||
export function AccountDetailsPage({ address, tab }: Props) {
|
||||
const fetchAccount = useFetchAccountInfo();
|
||||
const { status } = useCluster();
|
||||
const info = useAccountInfo(address);
|
||||
let pubkey: PublicKey | undefined;
|
||||
|
||||
try {
|
||||
pubkey = new PublicKey(address);
|
||||
} catch (err) {}
|
||||
|
||||
// Fetch account on load
|
||||
React.useEffect(() => {
|
||||
if (!info && status === ClusterStatus.Connected && pubkey) {
|
||||
fetchAccount(pubkey);
|
||||
}
|
||||
}, [address, status]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
return (
|
||||
<div className="container mt-n3">
|
||||
<div className="header">
|
||||
<div className="header-body">
|
||||
<AccountHeader address={address} />
|
||||
<AccountHeader address={address} info={info} />
|
||||
</div>
|
||||
</div>
|
||||
{!pubkey ? (
|
||||
<ErrorCard text={`Address "${address}" is not valid`} />
|
||||
) : (
|
||||
<DetailsSections pubkey={pubkey} tab={tab} />
|
||||
<DetailsSections pubkey={pubkey} tab={tab} info={info} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function AccountHeader({ address }: { address: string }) {
|
||||
export function AccountHeader({
|
||||
address,
|
||||
info,
|
||||
}: {
|
||||
address: string;
|
||||
info?: CacheEntry<Account>;
|
||||
}) {
|
||||
const { tokenRegistry } = useTokenRegistry();
|
||||
const tokenDetails = tokenRegistry.get(address);
|
||||
if (tokenDetails) {
|
||||
const account = info?.data;
|
||||
const data = account?.details?.data;
|
||||
const isToken = data?.program === "spl-token" && data?.parsed.type === "mint";
|
||||
|
||||
if (tokenDetails || isToken) {
|
||||
return (
|
||||
<div className="row align-items-end">
|
||||
{tokenDetails.logoURI && (
|
||||
<div className="col-auto">
|
||||
<div className="avatar avatar-lg header-avatar-top">
|
||||
{tokenDetails?.logoURI ? (
|
||||
<img
|
||||
src={tokenDetails.logoURI}
|
||||
alt="token logo"
|
||||
className="avatar-img rounded-circle border border-4 border-body"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<Identicon
|
||||
address={address}
|
||||
className="avatar-img rounded-circle border border-body identicon-wrapper"
|
||||
style={{ width: IDENTICON_WIDTH }}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="col mb-3 ml-n3 ml-md-n2">
|
||||
<h6 className="header-pretitle">Token</h6>
|
||||
<h2 className="header-title">{tokenDetails.name}</h2>
|
||||
<h2 className="header-title">
|
||||
{tokenDetails?.name || "Unlisted Token"}
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@ -125,19 +155,20 @@ export function AccountHeader({ address }: { address: string }) {
|
||||
);
|
||||
}
|
||||
|
||||
function DetailsSections({ pubkey, tab }: { pubkey: PublicKey; tab?: string }) {
|
||||
function DetailsSections({
|
||||
pubkey,
|
||||
tab,
|
||||
info,
|
||||
}: {
|
||||
pubkey: PublicKey;
|
||||
tab?: string;
|
||||
info?: CacheEntry<Account>;
|
||||
}) {
|
||||
const fetchAccount = useFetchAccountInfo();
|
||||
const address = pubkey.toBase58();
|
||||
const info = useAccountInfo(address);
|
||||
const { status } = useCluster();
|
||||
const location = useLocation();
|
||||
const { flaggedAccounts } = useFlaggedAccounts();
|
||||
|
||||
// Fetch account on load
|
||||
React.useEffect(() => {
|
||||
if (!info && status === ClusterStatus.Connected) fetchAccount(pubkey);
|
||||
}, [address, status]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
if (!info || info.status === FetchStatus.Fetching) {
|
||||
return <LoadingCard />;
|
||||
} else if (
|
||||
|
@ -382,3 +382,11 @@ p.updated-time {
|
||||
.change-negative {
|
||||
color: $warning;
|
||||
}
|
||||
|
||||
.identicon-wrapper {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.identicon-wrapper-small {
|
||||
margin-left: .4rem;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user