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:
parent
77ecc035a3
commit
c565152c13
|
@ -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" />
|
||||
)}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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;
|
Loading…
Reference in New Issue