浏览代码

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
Evan Gray 4 年之前
父节点
当前提交
c565152c13

+ 37 - 14
bridge_ui/src/components/TokenSelectors/EthereumSourceTokenSelector.tsx

@@ -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" />
         )}

+ 90 - 45
bridge_ui/src/components/TokenSelectors/SolanaSourceTokenSelector.tsx

@@ -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}

+ 19 - 6
bridge_ui/src/components/TokenSelectors/TerraSourceTokenSelector.tsx

@@ -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}

+ 17 - 0
bridge_ui/src/utils/sort.ts

@@ -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;