bridge_ui: token picker styling imrpovements

fixes https://github.com/certusone/wormhole/issues/484

fixes https://github.com/certusone/wormhole/issues/490

fixes https://github.com/certusone/wormhole/issues/487

Change-Id: I7405e17371dfde2921e63604d8353ebffed975c9
This commit is contained in:
Evan Gray 2021-09-27 15:18:45 -04:00
parent 77ecc035a3
commit c565152c13
4 changed files with 163 additions and 65 deletions

View File

@ -7,7 +7,7 @@ import {
Typography,
} from "@material-ui/core";
import { Autocomplete, createFilterOptions } from "@material-ui/lab";
import React, { useCallback, useEffect, useState } from "react";
import React, { useCallback, useEffect, useMemo, useState } from "react";
import { useEthereumProvider } from "../../contexts/EthereumProviderContext";
import { CovalentData } from "../../hooks/useGetSourceParsedTokenAccounts";
import { DataWrapper } from "../../store/helpers";
@ -28,18 +28,34 @@ import NFTViewer from "./NFTViewer";
import { useDebounce } from "use-debounce/lib";
import RefreshButtonWrapper from "./RefreshButtonWrapper";
import { CHAIN_ID_ETH } from "@certusone/wormhole-sdk";
import { sortParsedTokenAccounts } from "../../utils/sort";
const useStyles = makeStyles(() =>
const useStyles = makeStyles((theme) =>
createStyles({
selectInput: { minWidth: "10rem" },
tokenOverviewContainer: {
display: "flex",
width: "100%",
alignItems: "center",
"& div": {
margin: ".5rem",
margin: theme.spacing(1),
flexBasis: "33%",
"&$tokenImageContainer": {
maxWidth: 40,
},
"&:last-child": {
textAlign: "right",
},
},
},
tokenImageContainer: {
display: "flex",
alignItems: "center",
justifyContent: "center",
width: 40,
},
tokenImage: {
maxHeight: "2.5rem",
maxHeight: "2.5rem", //Eyeballing this based off the text size
},
})
);
@ -86,7 +102,7 @@ const renderAccount = (
const symbol = getSymbol(account) || "Unknown";
return (
<div className={classes.tokenOverviewContainer}>
<div>
<div className={classes.tokenImageContainer}>
{uri && <img alt="" className={classes.tokenImage} src={uri} />}
</div>
<div>
@ -119,7 +135,7 @@ const renderNFTAccount = (
const name = account.name || "Unknown";
return (
<div className={classes.tokenOverviewContainer}>
<div>
<div className={classes.tokenImageContainer}>
{uri && <img alt="" className={classes.tokenImage} src={uri} />}
</div>
<div>
@ -437,9 +453,19 @@ export default function EthereumSourceTokenSelector(
setAdvancedMode(!advancedMode);
};
const handleAutocompleteChange = (newValue: ParsedTokenAccount | null) => {
setAutocompleteHolder(newValue);
};
const handleAutocompleteChange = useCallback(
(event, newValue: ParsedTokenAccount | null) => {
setAutocompleteHolder(newValue);
},
[]
);
const tokenAccountsData = tokenAccounts?.data;
const sortedOptions = useMemo(() => {
const options = tokenAccountsData || [];
options.sort(sortParsedTokenAccounts);
return options;
}, [tokenAccountsData]);
const isLoading =
props.covalent?.isFetching || props.tokenAccounts?.isFetching;
@ -449,22 +475,19 @@ export default function EthereumSourceTokenSelector(
<Autocomplete
autoComplete
autoHighlight
autoSelect
blurOnSelect
clearOnBlur
fullWidth={true}
filterOptions={nft ? filterConfigNFT : filterConfig}
value={autocompleteHolder}
onChange={(event, newValue) => {
handleAutocompleteChange(newValue);
}}
onChange={handleAutocompleteChange}
disabled={disabled}
noOptionsText={
nft
? "No ERC-721 tokens found at the moment."
: "No ERC-20 tokens found at the moment."
}
options={tokenAccounts?.data || []}
options={sortedOptions}
renderInput={(params) => (
<TextField {...params} label="Token Account" variant="outlined" />
)}

View File

@ -1,6 +1,11 @@
import { CHAIN_ID_SOLANA } from "@certusone/wormhole-sdk";
import { CircularProgress, TextField, Typography } from "@material-ui/core";
import { createStyles, makeStyles, Theme } from "@material-ui/core/styles";
import {
CircularProgress,
TextField,
Typography,
createStyles,
makeStyles,
} from "@material-ui/core";
import { Alert, Autocomplete } from "@material-ui/lab";
import { createFilterOptions } from "@material-ui/lab/Autocomplete";
import { TokenInfo } from "@solana/spl-token-registry";
@ -15,21 +20,46 @@ import {
WORMHOLE_V1_MINT_AUTHORITY,
} from "../../utils/consts";
import { ExtractedMintInfo, shortenAddress } from "../../utils/solana";
import { sortParsedTokenAccounts } from "../../utils/sort";
import NFTViewer from "./NFTViewer";
import RefreshButtonWrapper from "./RefreshButtonWrapper";
const useStyles = makeStyles((theme: Theme) =>
const useStyles = makeStyles((theme) =>
createStyles({
selectInput: { minWidth: "10rem" },
tokenOverviewContainer: {
display: "flex",
width: "100%",
alignItems: "center",
"& div": {
margin: ".5rem",
margin: theme.spacing(1),
flexBasis: "33%",
"&$tokenImageContainer": {
maxWidth: 40,
},
"&:last-child": {
textAlign: "right",
},
},
},
tokenImageContainer: {
display: "flex",
alignItems: "center",
justifyContent: "center",
width: 40,
},
tokenImage: {
maxHeight: "2.5rem", //Eyeballing this based off the text size
},
v1Warning: {
width: "100%",
},
migrationAlert: {
width: "100%",
"& .MuiAlert-message": {
width: "100%",
},
},
})
);
@ -169,39 +199,39 @@ export default function SolanaSourceTokenSelector(
const name = getName(account) || "--";
const content = (
<>
<div className={classes.tokenOverviewContainer}>
<div>
{uri && <img alt="" className={classes.tokenImage} src={uri} />}
</div>
<div>
<Typography variant="subtitle1">{symbol}</Typography>
<Typography variant="subtitle2">{name}</Typography>
</div>
<div>
{account.isNativeAsset ? (
<Typography>{"Native"}</Typography>
) : (
<>
<Typography variant="body1">
{"Mint : " + mintPrettyString}
</Typography>
<Typography variant="body1">
{"Account :" + accountAddressPrettyString}
</Typography>
</>
)}
</div>
<div className={classes.tokenOverviewContainer}>
<div className={classes.tokenImageContainer}>
{uri && <img alt="" className={classes.tokenImage} src={uri} />}
</div>
<div>
<Typography variant="subtitle1">{symbol}</Typography>
<Typography variant="subtitle2">{name}</Typography>
</div>
<div>
{account.isNativeAsset ? (
<Typography>{"Native"}</Typography>
) : (
<>
<Typography variant="body1">
{"Mint : " + mintPrettyString}
</Typography>
<Typography variant="body1">
{"Account :" + accountAddressPrettyString}
</Typography>
</>
)}
</div>
{nft ? null : (
<div>
<Typography variant="body2">{"Balance"}</Typography>
<Typography variant="h6">{account.uiAmountString}</Typography>
</div>
</div>
</>
)}
</div>
);
const v1Warning = (
<div>
<div className={classes.v1Warning}>
<Typography variant="body2">
Wormhole v1 tokens are not eligible for transfer.
</Typography>
@ -210,7 +240,7 @@ export default function SolanaSourceTokenSelector(
);
const migrationRender = (
<div>
<div className={classes.migrationAlert}>
<Alert severity="warning">
<Typography variant="body2">
This is a legacy asset eligible for migration.
@ -226,7 +256,15 @@ export default function SolanaSourceTokenSelector(
? v1Warning
: content;
},
[getLogo, getSymbol, getName, classes, isWormholev1, isMigrationEligible]
[
getLogo,
getSymbol,
getName,
classes,
isWormholev1,
isMigrationEligible,
nft,
]
);
//The autocomplete doesn't rerender the option label unless the value changes.
@ -244,18 +282,26 @@ export default function SolanaSourceTokenSelector(
//This exists to remove NFTs from the list of potential options. It requires reading the metaplex data, so it would be
//difficult to do before this point.
const filteredOptions = useMemo(() => {
return props.accounts.filter((x) => {
const zeroBalance = x.amount === "0";
if (zeroBalance) {
return false;
}
const isNFT =
x.decimals === 0 &&
metaplex.data?.get(x.mintKey)?.data?.uri &&
mintAccounts?.data?.get(x.mintKey)?.supply === "1";
return nft ? isNFT : !isNFT;
});
}, [mintAccounts?.data, metaplex.data, nft, props.accounts]);
const tokenList = props.accounts
.filter((x) => {
const zeroBalance = x.amount === "0";
if (zeroBalance) {
return false;
}
const isNFT =
x.decimals === 0 &&
metaplex.data?.get(x.mintKey)?.data?.uri &&
mintAccounts?.data?.get(x.mintKey)?.supply === "1";
return nft ? isNFT : !isNFT;
})
.map((account) => ({
...account,
symbol: account.symbol || getSymbol(account) || undefined,
}));
tokenList.sort(sortParsedTokenAccounts);
return tokenList;
}, [mintAccounts?.data, metaplex.data, nft, props.accounts, getSymbol]);
console.log(filteredOptions);
const isOptionDisabled = useMemo(() => {
return (value: ParsedTokenAccount) => {
@ -314,7 +360,6 @@ export default function SolanaSourceTokenSelector(
<Autocomplete
autoComplete
autoHighlight
autoSelect
blurOnSelect
clearOnBlur
fullWidth={false}

View File

@ -23,17 +23,31 @@ import { shortenAddress } from "../../utils/solana";
import OffsetButton from "./OffsetButton";
import RefreshButtonWrapper from "./RefreshButtonWrapper";
const useStyles = makeStyles(() =>
const useStyles = makeStyles((theme) =>
createStyles({
selectInput: { minWidth: "10rem" },
tokenOverviewContainer: {
display: "flex",
width: "100%",
alignItems: "center",
"& div": {
margin: ".5rem",
margin: theme.spacing(1),
"&$tokenImageContainer": {
maxWidth: 40,
},
},
},
tokenImageContainer: {
display: "flex",
alignItems: "center",
justifyContent: "center",
width: 40,
},
tokenImage: {
maxHeight: "2.5rem",
maxHeight: "2.5rem", //Eyeballing this based off the text size
},
tokenSymbolContainer: {
flexBasis: 112,
},
})
);
@ -168,10 +182,10 @@ export default function TerraSourceTokenSelector(
const renderOption = (option: TerraTokenMetadata) => {
return (
<div className={classes.tokenOverviewContainer}>
<div>
<div className={classes.tokenImageContainer}>
<img alt="" className={classes.tokenImage} src={option.icon} />
</div>
<div>
<div className={classes.tokenSymbolContainer}>
<Typography variant="h6">{option.symbol}</Typography>
<Typography variant="body2">{option.protocol}</Typography>
</div>
@ -200,7 +214,6 @@ export default function TerraSourceTokenSelector(
<Autocomplete
autoComplete
autoHighlight
autoSelect
blurOnSelect
clearOnBlur
fullWidth={false}

View File

@ -0,0 +1,17 @@
import { ParsedTokenAccount } from "../store/transferSlice";
export const sortParsedTokenAccounts = (
a: ParsedTokenAccount,
b: ParsedTokenAccount
) =>
a.isNativeAsset && !b.isNativeAsset
? -1
: !a.isNativeAsset && b.isNativeAsset
? 1
: a.symbol && b.symbol
? a.symbol.localeCompare(b.symbol)
: a.symbol
? -1
: b.symbol
? 1
: 0;