feat: update token icon sourcing (#5208)
* initial setup * working version * optimized image quality * refactoring file structure * updating backup logos * cleaning up code * refactored file structure * fixing state update issue * updated test * fixed currency logo bug * updated image background color * finished pr comments * fixed additional PR comments
This commit is contained in:
parent
7ce58b55a1
commit
3f6dc180cf
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@ -16,4 +16,4 @@
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import styled, { css } from 'styled-components/macro'
|
||||
|
||||
import { nativeOnChain } from '../../constants/tokens'
|
||||
import { useCurrency } from '../../hooks/Tokens'
|
||||
import CurrencyLogo from '../CurrencyLogo'
|
||||
import CurrencyLogo from '../Logo/CurrencyLogo'
|
||||
|
||||
const CurrencyWrap = styled.div`
|
||||
position: relative;
|
||||
|
@ -6,6 +6,7 @@ import { Pair } from '@uniswap/v2-sdk'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import { LoadingOpacityContainer, loadingOpacityMixin } from 'components/Loader/styled'
|
||||
import CurrencyLogo from 'components/Logo/CurrencyLogo'
|
||||
import { isSupportedChain } from 'constants/chains'
|
||||
import { darken } from 'polished'
|
||||
import { ReactNode, useCallback, useState } from 'react'
|
||||
@ -18,7 +19,6 @@ import { ReactComponent as DropDown } from '../../assets/images/dropdown.svg'
|
||||
import { useCurrencyBalance } from '../../state/connection/hooks'
|
||||
import { ThemedText } from '../../theme'
|
||||
import { ButtonGray } from '../Button'
|
||||
import CurrencyLogo from '../CurrencyLogo'
|
||||
import DoubleCurrencyLogo from '../DoubleLogo'
|
||||
import { Input as NumericalInput } from '../NumericalInput'
|
||||
import { RowBetween, RowFixed } from '../Row'
|
||||
|
@ -18,8 +18,8 @@ import { ReactComponent as DropDown } from '../../assets/images/dropdown.svg'
|
||||
import { useCurrencyBalance } from '../../state/connection/hooks'
|
||||
import { ThemedText } from '../../theme'
|
||||
import { ButtonGray } from '../Button'
|
||||
import CurrencyLogo from '../CurrencyLogo'
|
||||
import DoubleCurrencyLogo from '../DoubleLogo'
|
||||
import CurrencyLogo from '../Logo/CurrencyLogo'
|
||||
import { Input as NumericalInput } from '../NumericalInput'
|
||||
import { RowBetween, RowFixed } from '../Row'
|
||||
import CurrencySearchModal from '../SearchModal/CurrencySearchModal'
|
||||
|
@ -1,51 +0,0 @@
|
||||
import { Currency } from '@uniswap/sdk-core'
|
||||
import useCurrencyLogoURIs from 'lib/hooks/useCurrencyLogoURIs'
|
||||
import React, { useMemo } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import Logo from '../Logo'
|
||||
|
||||
const StyledLogo = styled(Logo)<{ size: string }>`
|
||||
width: ${({ size }) => size};
|
||||
height: ${({ size }) => size};
|
||||
background: radial-gradient(white 50%, #ffffff00 calc(75% + 1px), #ffffff00 100%);
|
||||
border-radius: 50%;
|
||||
-mox-box-shadow: 0 0 1px black;
|
||||
-webkit-box-shadow: 0 0 1px black;
|
||||
box-shadow: 0 0 1px black;
|
||||
border: 0px solid rgba(255, 255, 255, 0);
|
||||
`
|
||||
|
||||
const StyledNativeLogo = styled(StyledLogo)`
|
||||
-mox-box-shadow: 0 0 1px white;
|
||||
-webkit-box-shadow: 0 0 1px white;
|
||||
box-shadow: 0 0 1px white;
|
||||
`
|
||||
|
||||
export default function CurrencyLogo({
|
||||
currency,
|
||||
symbol,
|
||||
size = '24px',
|
||||
style,
|
||||
src,
|
||||
...rest
|
||||
}: {
|
||||
currency?: Currency | null
|
||||
symbol?: string | null
|
||||
size?: string
|
||||
style?: React.CSSProperties
|
||||
src?: string | null
|
||||
}) {
|
||||
const logoURIs = useCurrencyLogoURIs(currency)
|
||||
const srcs = useMemo(() => (src ? [src, ...logoURIs] : logoURIs), [src, logoURIs])
|
||||
const props = {
|
||||
alt: `${currency?.symbol ?? 'token'} logo`,
|
||||
size,
|
||||
srcs,
|
||||
symbol: symbol ?? currency?.symbol,
|
||||
style,
|
||||
...rest,
|
||||
}
|
||||
|
||||
return currency?.isNative ? <StyledNativeLogo {...props} /> : <StyledLogo {...props} />
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import { Currency } from '@uniswap/sdk-core'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import CurrencyLogo from '../CurrencyLogo'
|
||||
import CurrencyLogo from '../Logo/CurrencyLogo'
|
||||
|
||||
const Wrapper = styled.div<{ margin: boolean; sizeraw: number }>`
|
||||
position: relative;
|
||||
|
68
src/components/Logo/AssetLogo.tsx
Normal file
68
src/components/Logo/AssetLogo.tsx
Normal file
@ -0,0 +1,68 @@
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import useTokenLogoSource from 'hooks/useAssetLogoSource'
|
||||
import React from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
export const MissingImageLogo = styled.div<{ size?: string }>`
|
||||
--size: ${({ size }) => size};
|
||||
border-radius: 100px;
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
background-color: ${({ theme }) => theme.backgroundInteractive};
|
||||
font-size: calc(var(--size) / 3);
|
||||
font-weight: 500;
|
||||
height: ${({ size }) => size ?? '24px'};
|
||||
line-height: ${({ size }) => size ?? '24px'};
|
||||
text-align: center;
|
||||
width: ${({ size }) => size ?? '24px'};
|
||||
`
|
||||
|
||||
const LogoImage = styled.img<{ size: string }>`
|
||||
width: ${({ size }) => size};
|
||||
height: ${({ size }) => size};
|
||||
background: radial-gradient(white 60%, #ffffff00 calc(70% + 1px));
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 1px white;
|
||||
`
|
||||
|
||||
export type AssetLogoBaseProps = {
|
||||
symbol?: string | null
|
||||
backupImg?: string | null
|
||||
size?: string
|
||||
style?: React.CSSProperties
|
||||
}
|
||||
type AssetLogoProps = AssetLogoBaseProps & { isNative?: boolean; address?: string | null; chainId?: number }
|
||||
|
||||
// TODO(cartcrom): add prop to optionally render an L2Icon w/ the logo
|
||||
/**
|
||||
* Renders an image by prioritizing a list of sources, and then eventually a fallback triangle alert
|
||||
*/
|
||||
export default function AssetLogo({
|
||||
isNative,
|
||||
address,
|
||||
chainId = SupportedChainId.MAINNET,
|
||||
symbol,
|
||||
backupImg,
|
||||
size = '24px',
|
||||
style,
|
||||
...rest
|
||||
}: AssetLogoProps) {
|
||||
const imageProps = {
|
||||
alt: `${symbol ?? 'token'} logo`,
|
||||
size,
|
||||
style,
|
||||
...rest,
|
||||
}
|
||||
|
||||
const [src, nextSrc] = useTokenLogoSource(address, chainId, isNative, backupImg)
|
||||
|
||||
if (src) {
|
||||
return <LogoImage {...imageProps} src={src} onError={nextSrc} />
|
||||
} else {
|
||||
return (
|
||||
<MissingImageLogo size={size}>
|
||||
{/* use only first 3 characters of Symbol for design reasons */}
|
||||
{symbol?.toUpperCase().replace('$', '').replace(/\s+/g, '').slice(0, 3)}
|
||||
</MissingImageLogo>
|
||||
)
|
||||
}
|
||||
}
|
21
src/components/Logo/CurrencyLogo.tsx
Normal file
21
src/components/Logo/CurrencyLogo.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import { Currency } from '@uniswap/sdk-core'
|
||||
import { TokenInfo } from '@uniswap/token-lists'
|
||||
|
||||
import AssetLogo, { AssetLogoBaseProps } from './AssetLogo'
|
||||
|
||||
export default function CurrencyLogo(
|
||||
props: AssetLogoBaseProps & {
|
||||
currency?: Currency | null
|
||||
}
|
||||
) {
|
||||
return (
|
||||
<AssetLogo
|
||||
isNative={props.currency?.isNative}
|
||||
chainId={props.currency?.chainId}
|
||||
address={props.currency?.wrapped.address}
|
||||
symbol={props.symbol ?? props.currency?.symbol}
|
||||
backupImg={(props.currency as TokenInfo)?.logoURI}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
25
src/components/Logo/QueryTokenLogo.tsx
Normal file
25
src/components/Logo/QueryTokenLogo.tsx
Normal file
@ -0,0 +1,25 @@
|
||||
import { NATIVE_CHAIN_ID } from 'constants/tokens'
|
||||
import { TokenQueryData } from 'graphql/data/Token'
|
||||
import { TopToken } from 'graphql/data/TopTokens'
|
||||
import { CHAIN_NAME_TO_CHAIN_ID } from 'graphql/data/util'
|
||||
|
||||
import AssetLogo, { AssetLogoBaseProps } from './AssetLogo'
|
||||
|
||||
export default function QueryTokenLogo(
|
||||
props: AssetLogoBaseProps & {
|
||||
token?: TopToken | TokenQueryData
|
||||
}
|
||||
) {
|
||||
const chainId = props.token?.chain ? CHAIN_NAME_TO_CHAIN_ID[props.token?.chain] : undefined
|
||||
|
||||
return (
|
||||
<AssetLogo
|
||||
isNative={props.token?.address === NATIVE_CHAIN_ID}
|
||||
chainId={chainId}
|
||||
address={props.token?.address}
|
||||
symbol={props.token?.symbol}
|
||||
backupImg={props.token?.project?.logoUrl}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
import { useState } from 'react'
|
||||
import { ImageProps } from 'rebass'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
const BAD_SRCS: { [tokenAddress: string]: true } = {}
|
||||
|
||||
interface LogoProps extends Pick<ImageProps, 'style' | 'alt' | 'className'> {
|
||||
srcs: string[]
|
||||
symbol?: string
|
||||
size?: string
|
||||
}
|
||||
|
||||
const MissingImageLogo = styled.div<{ size?: string }>`
|
||||
--size: ${({ size }) => size};
|
||||
border-radius: 100px;
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
background-color: ${({ theme }) => theme.backgroundInteractive};
|
||||
font-size: calc(var(--size) / 3);
|
||||
font-weight: 500;
|
||||
height: ${({ size }) => size ?? '24px'};
|
||||
line-height: ${({ size }) => size ?? '24px'};
|
||||
text-align: center;
|
||||
width: ${({ size }) => size ?? '24px'};
|
||||
`
|
||||
|
||||
/**
|
||||
* Renders an image by sequentially trying a list of URIs, and then eventually a fallback triangle alert
|
||||
*/
|
||||
export default function Logo({ srcs, alt, style, size, symbol, ...rest }: LogoProps) {
|
||||
const [, refresh] = useState<number>(0)
|
||||
|
||||
const src: string | undefined = srcs.find((src) => !BAD_SRCS[src])
|
||||
|
||||
if (src) {
|
||||
return (
|
||||
<img
|
||||
{...rest}
|
||||
alt={alt}
|
||||
src={src}
|
||||
style={style}
|
||||
onError={() => {
|
||||
if (src) BAD_SRCS[src] = true
|
||||
refresh((i) => i + 1)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<MissingImageLogo size={size}>
|
||||
{/* use only first 3 characters of Symbol for design reasons */}
|
||||
{symbol?.toUpperCase().replace('$', '').replace(/\s+/g, '').slice(0, 3)}
|
||||
</MissingImageLogo>
|
||||
)
|
||||
}
|
@ -2,12 +2,13 @@ import { sendAnalyticsEvent } from '@uniswap/analytics'
|
||||
import { EventName } from '@uniswap/analytics-events'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import clsx from 'clsx'
|
||||
import AssetLogo from 'components/Logo/AssetLogo'
|
||||
import { L2NetworkLogo, LogoContainer } from 'components/Tokens/TokenTable/TokenRow'
|
||||
import TokenSafetyIcon from 'components/TokenSafety/TokenSafetyIcon'
|
||||
import { getChainInfo } from 'constants/chainInfo'
|
||||
import { NATIVE_CHAIN_ID } from 'constants/tokens'
|
||||
import { checkWarning } from 'constants/tokenSafety'
|
||||
import { getTokenDetailsURL } from 'graphql/data/util'
|
||||
import uriToHttp from 'lib/utils/uriToHttp'
|
||||
import { Box } from 'nft/components/Box'
|
||||
import { Column, Row } from 'nft/components/Flex'
|
||||
import { VerifiedIcon } from 'nft/components/icons'
|
||||
@ -18,10 +19,15 @@ import { ethNumberStandardFormatter } from 'nft/utils/currency'
|
||||
import { putCommas } from 'nft/utils/putCommas'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { Link, useNavigate } from 'react-router-dom'
|
||||
import styled from 'styled-components/macro'
|
||||
import { formatDollar } from 'utils/formatNumbers'
|
||||
|
||||
import * as styles from './SearchBar.css'
|
||||
|
||||
const StyledLogoContainer = styled(LogoContainer)`
|
||||
margin-right: 8px;
|
||||
`
|
||||
|
||||
interface CollectionRowProps {
|
||||
collection: GenieCollection
|
||||
isHovered: boolean
|
||||
@ -127,8 +133,6 @@ interface TokenRowProps {
|
||||
}
|
||||
|
||||
export const TokenRow = ({ token, isHovered, setHoveredIndex, toggleOpen, index, eventProperties }: TokenRowProps) => {
|
||||
const [brokenImage, setBrokenImage] = useState(false)
|
||||
const [loaded, setLoaded] = useState(false)
|
||||
const addToSearchHistory = useSearchHistory(
|
||||
(state: { addItem: (item: FungibleToken | GenieCollection) => void }) => state.addItem
|
||||
)
|
||||
@ -167,21 +171,17 @@ export const TokenRow = ({ token, isHovered, setHoveredIndex, toggleOpen, index,
|
||||
style={{ background: isHovered ? vars.color.lightGrayOverlay : 'none' }}
|
||||
>
|
||||
<Row style={{ width: '65%' }}>
|
||||
{!brokenImage && token.logoURI ? (
|
||||
<LogoContainer>
|
||||
<Box
|
||||
as="img"
|
||||
src={token.logoURI.includes('ipfs://') ? uriToHttp(token.logoURI)[0] : token.logoURI}
|
||||
alt={token.name}
|
||||
className={clsx(loaded ? styles.suggestionImage : styles.imageHolder)}
|
||||
onError={() => setBrokenImage(true)}
|
||||
onLoad={() => setLoaded(true)}
|
||||
/>
|
||||
<L2NetworkLogo networkUrl={L2Icon} size="16px" />
|
||||
</LogoContainer>
|
||||
) : (
|
||||
<Box className={styles.imageHolder} />
|
||||
)}
|
||||
<StyledLogoContainer>
|
||||
<AssetLogo
|
||||
isNative={token.address === NATIVE_CHAIN_ID}
|
||||
address={token.address}
|
||||
chainId={token.chainId}
|
||||
symbol={token.symbol}
|
||||
size="36px"
|
||||
backupImg={token.logoURI}
|
||||
/>
|
||||
<L2NetworkLogo networkUrl={L2Icon} size="16px" />
|
||||
</StyledLogoContainer>
|
||||
<Column className={styles.suggestionPrimaryContainer}>
|
||||
<Row gap="4" width="full">
|
||||
<Box className={styles.primaryText}>{token.name}</Box>
|
||||
|
@ -19,9 +19,9 @@ import { unwrappedToken } from '../../utils/unwrappedToken'
|
||||
import { ButtonEmpty, ButtonPrimary, ButtonSecondary } from '../Button'
|
||||
import { LightCard } from '../Card'
|
||||
import { AutoColumn } from '../Column'
|
||||
import CurrencyLogo from '../CurrencyLogo'
|
||||
import DoubleCurrencyLogo from '../DoubleLogo'
|
||||
import { CardNoise } from '../earn/styled'
|
||||
import CurrencyLogo from '../Logo/CurrencyLogo'
|
||||
import { AutoRow, RowBetween, RowFixed } from '../Row'
|
||||
import { Dots } from '../swap/styleds'
|
||||
import { FixedHeightRow } from '.'
|
||||
|
@ -20,9 +20,9 @@ import { unwrappedToken } from '../../utils/unwrappedToken'
|
||||
import { ButtonEmpty, ButtonPrimary, ButtonSecondary } from '../Button'
|
||||
import { GrayCard, LightCard } from '../Card'
|
||||
import { AutoColumn } from '../Column'
|
||||
import CurrencyLogo from '../CurrencyLogo'
|
||||
import DoubleCurrencyLogo from '../DoubleLogo'
|
||||
import { CardNoise } from '../earn/styled'
|
||||
import CurrencyLogo from '../Logo/CurrencyLogo'
|
||||
import { AutoRow, RowBetween, RowFixed } from '../Row'
|
||||
import { Dots } from '../swap/styleds'
|
||||
|
||||
|
@ -4,9 +4,9 @@ import { Position } from '@uniswap/v3-sdk'
|
||||
import RangeBadge from 'components/Badge/RangeBadge'
|
||||
import { LightCard } from 'components/Card'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import CurrencyLogo from 'components/CurrencyLogo'
|
||||
import DoubleCurrencyLogo from 'components/DoubleLogo'
|
||||
import { Break } from 'components/earn/styled'
|
||||
import CurrencyLogo from 'components/Logo/CurrencyLogo'
|
||||
import RateToggle from 'components/RateToggle'
|
||||
import { RowBetween, RowFixed } from 'components/Row'
|
||||
import JSBI from 'jsbi'
|
||||
|
@ -28,7 +28,7 @@ const multiRoute: RoutingDiagramEntry[] = [
|
||||
]
|
||||
|
||||
jest.mock(
|
||||
'components/CurrencyLogo',
|
||||
'components/Logo/CurrencyLogo',
|
||||
() =>
|
||||
({ currency }: { currency: Currency }) =>
|
||||
`CurrencyLogo currency=${currency.symbol}`
|
||||
|
@ -3,8 +3,8 @@ import { Protocol } from '@uniswap/router-sdk'
|
||||
import { Currency } from '@uniswap/sdk-core'
|
||||
import { FeeAmount } from '@uniswap/v3-sdk'
|
||||
import Badge from 'components/Badge'
|
||||
import CurrencyLogo from 'components/CurrencyLogo'
|
||||
import DoubleCurrencyLogo from 'components/DoubleLogo'
|
||||
import CurrencyLogo from 'components/Logo/CurrencyLogo'
|
||||
import Row, { AutoRow } from 'components/Row'
|
||||
import { RoutingDiagramEntry } from 'components/swap/SwapRoute'
|
||||
import { useTokenInfoFromActiveList } from 'hooks/useTokenInfoFromActiveList'
|
||||
|
@ -2,7 +2,7 @@ import { TraceEvent } from '@uniswap/analytics'
|
||||
import { BrowserEvent, ElementName, EventName } from '@uniswap/analytics-events'
|
||||
import { Currency } from '@uniswap/sdk-core'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import CurrencyLogo from 'components/CurrencyLogo'
|
||||
import CurrencyLogo from 'components/Logo/CurrencyLogo'
|
||||
import { AutoRow } from 'components/Row'
|
||||
import { COMMON_BASES } from 'constants/routing'
|
||||
import { useTokenInfoFromActiveList } from 'hooks/useTokenInfoFromActiveList'
|
||||
|
@ -16,7 +16,7 @@ const mockCurrencyAmt = {
|
||||
}
|
||||
|
||||
jest.mock(
|
||||
'components/CurrencyLogo',
|
||||
'components/Logo/CurrencyLogo',
|
||||
() =>
|
||||
({ currency }: { currency: Currency }) =>
|
||||
`CurrencyLogo currency=${currency.symbol}`
|
||||
|
@ -15,8 +15,8 @@ import { useCurrencyBalance } from '../../../state/connection/hooks'
|
||||
import { WrappedTokenInfo } from '../../../state/lists/wrappedTokenInfo'
|
||||
import { ThemedText } from '../../../theme'
|
||||
import Column, { AutoColumn } from '../../Column'
|
||||
import CurrencyLogo from '../../CurrencyLogo'
|
||||
import Loader from '../../Loader'
|
||||
import CurrencyLogo from '../../Logo/CurrencyLogo'
|
||||
import Row, { RowFixed } from '../../Row'
|
||||
import { MouseoverTooltip } from '../../Tooltip'
|
||||
import { LoadingRows, MenuItem } from '../styleds'
|
||||
|
@ -2,7 +2,7 @@ import { Trans } from '@lingui/macro'
|
||||
import { Token } from '@uniswap/sdk-core'
|
||||
import { ButtonPrimary } from 'components/Button'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import CurrencyLogo from 'components/CurrencyLogo'
|
||||
import CurrencyLogo from 'components/Logo/CurrencyLogo'
|
||||
import TokenSafetyLabel from 'components/TokenSafety/TokenSafetyLabel'
|
||||
import { checkWarning, getWarningCopy, TOKEN_SAFETY_ARTICLE, Warning } from 'constants/tokenSafety'
|
||||
import { useToken } from 'hooks/Tokens'
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Currency, CurrencyAmount, Token } from '@uniswap/sdk-core'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import CurrencyLogo from 'components/CurrencyLogo'
|
||||
import CurrencyLogo from 'components/Logo/CurrencyLogo'
|
||||
import { useStablecoinValue } from 'hooks/useStablecoinPrice'
|
||||
import useCurrencyBalance from 'lib/hooks/useCurrencyBalance'
|
||||
import { formatToDecimal } from 'lib/utils/analytics'
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Currency } from '@uniswap/sdk-core'
|
||||
import { NATIVE_CHAIN_ID } from 'constants/tokens'
|
||||
import { TokenQueryData } from 'graphql/data/Token'
|
||||
import { chainIdToBackendName } from 'graphql/data/util'
|
||||
import { useOnClickOutside } from 'hooks/useOnClickOutside'
|
||||
import { useRef } from 'react'
|
||||
import { Twitter } from 'react-feather'
|
||||
@ -63,12 +64,7 @@ const ShareAction = styled.div`
|
||||
}
|
||||
`
|
||||
|
||||
interface TokenInfo {
|
||||
token: NonNullable<TokenQueryData>
|
||||
isNative: boolean
|
||||
}
|
||||
|
||||
export default function ShareButton(tokenInfo: TokenInfo) {
|
||||
export default function ShareButton({ currency }: { currency: Currency }) {
|
||||
const theme = useTheme()
|
||||
const node = useRef<HTMLDivElement | null>(null)
|
||||
const open = useModalIsOpen(ApplicationModal.SHARE)
|
||||
@ -76,12 +72,16 @@ export default function ShareButton(tokenInfo: TokenInfo) {
|
||||
useOnClickOutside(node, open ? toggleShare : undefined)
|
||||
const positionX = (window.screen.width - TWITTER_WIDTH) / 2
|
||||
const positionY = (window.screen.height - TWITTER_HEIGHT) / 2
|
||||
const tokenAddress = tokenInfo.isNative ? NATIVE_CHAIN_ID : tokenInfo.token.address
|
||||
const address = currency.isNative ? NATIVE_CHAIN_ID : currency.wrapped.address
|
||||
|
||||
const shareTweet = () => {
|
||||
toggleShare()
|
||||
window.open(
|
||||
`https://twitter.com/intent/tweet?text=Check%20out%20${tokenInfo.token.name}%20(${tokenInfo.token.symbol})%20https://app.uniswap.org/%23/tokens/${tokenInfo.token.chain}/${tokenAddress}%20via%20@uniswap`,
|
||||
`https://twitter.com/intent/tweet?text=Check%20out%20${currency.name}%20(${
|
||||
currency.symbol
|
||||
})%20https://app.uniswap.org/%23/tokens/${chainIdToBackendName(
|
||||
currency.chainId
|
||||
).toLowerCase()}/${address}%20via%20@uniswap`,
|
||||
'newwindow',
|
||||
`left=${positionX}, top=${positionY}, width=${TWITTER_WIDTH}, height=${TWITTER_HEIGHT}`
|
||||
)
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Trace } from '@uniswap/analytics'
|
||||
import { PageName } from '@uniswap/analytics-events'
|
||||
import { Currency, NativeCurrency, Token } from '@uniswap/sdk-core'
|
||||
import CurrencyLogo from 'components/CurrencyLogo'
|
||||
import { Currency, Token } from '@uniswap/sdk-core'
|
||||
import CurrencyLogo from 'components/Logo/CurrencyLogo'
|
||||
import { AboutSection } from 'components/Tokens/TokenDetails/About'
|
||||
import AddressSection from 'components/Tokens/TokenDetails/AddressSection'
|
||||
import BalanceSummary from 'components/Tokens/TokenDetails/BalanceSummary'
|
||||
@ -24,17 +24,14 @@ import TokenSafetyMessage from 'components/TokenSafety/TokenSafetyMessage'
|
||||
import TokenSafetyModal from 'components/TokenSafety/TokenSafetyModal'
|
||||
import Widget from 'components/Widget'
|
||||
import { getChainInfo } from 'constants/chainInfo'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import { DEFAULT_ERC20_DECIMALS, NATIVE_CHAIN_ID, nativeOnChain } from 'constants/tokens'
|
||||
import { checkWarning } from 'constants/tokenSafety'
|
||||
import { TokenPriceQuery } from 'graphql/data/__generated__/TokenPriceQuery.graphql'
|
||||
import { Chain, TokenQuery } from 'graphql/data/Token'
|
||||
import { QueryToken, tokenQuery, TokenQueryData } from 'graphql/data/Token'
|
||||
import { TopToken } from 'graphql/data/TopTokens'
|
||||
import { QueryToken, tokenQuery } from 'graphql/data/Token'
|
||||
import { CHAIN_NAME_TO_CHAIN_ID } from 'graphql/data/util'
|
||||
import { useIsUserAddedTokenOnChain } from 'hooks/Tokens'
|
||||
import { useOnGlobalChainSwitch } from 'hooks/useGlobalChainSwitch'
|
||||
import useCurrencyLogoURIs from 'lib/hooks/useCurrencyLogoURIs'
|
||||
import { useCallback, useMemo, useState, useTransition } from 'react'
|
||||
import { ArrowLeft } from 'react-feather'
|
||||
import { PreloadedQuery, usePreloadedQuery } from 'react-relay'
|
||||
@ -53,15 +50,6 @@ const TokenActions = styled.div`
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
`
|
||||
|
||||
export function useTokenLogoURI(token?: TokenQueryData | TopToken, nativeCurrency?: Token | NativeCurrency) {
|
||||
const chainId = token ? CHAIN_NAME_TO_CHAIN_ID[token.chain] : SupportedChainId.MAINNET
|
||||
return [
|
||||
...useCurrencyLogoURIs(nativeCurrency),
|
||||
...useCurrencyLogoURIs({ ...token, chainId }),
|
||||
token?.project?.logoUrl,
|
||||
][0]
|
||||
}
|
||||
|
||||
type TokenDetailsProps = {
|
||||
tokenAddress: string | undefined
|
||||
chain: Chain
|
||||
@ -85,12 +73,12 @@ export default function TokenDetails({
|
||||
const isNative = tokenAddress === NATIVE_CHAIN_ID
|
||||
|
||||
const tokenQueryData = usePreloadedQuery(tokenQuery, tokenQueryReference).tokens?.[0]
|
||||
|
||||
const token = useMemo(() => {
|
||||
if (isNative) return nativeCurrency
|
||||
if (tokenQueryData) return new QueryToken(tokenQueryData)
|
||||
return new Token(pageChainId, tokenAddress, DEFAULT_ERC20_DECIMALS)
|
||||
}, [isNative, nativeCurrency, pageChainId, tokenAddress, tokenQueryData])
|
||||
|
||||
const tokenWarning = tokenAddress ? checkWarning(tokenAddress) : null
|
||||
const isBlockedToken = tokenWarning?.canProceed === false
|
||||
|
||||
@ -133,7 +121,6 @@ export default function TokenDetails({
|
||||
[continueSwap, setContinueSwap]
|
||||
)
|
||||
|
||||
const logoSrc = useTokenLogoURI(tokenQueryData, isNative ? nativeCurrency : undefined)
|
||||
const L2Icon = getChainInfo(pageChainId)?.circleLogoUrl
|
||||
|
||||
return (
|
||||
@ -147,12 +134,7 @@ export default function TokenDetails({
|
||||
<TokenInfoContainer>
|
||||
<TokenNameCell>
|
||||
<LogoContainer>
|
||||
<CurrencyLogo
|
||||
src={logoSrc}
|
||||
size="32px"
|
||||
symbol={isNative ? nativeCurrency?.symbol : token?.symbol}
|
||||
currency={isNative ? nativeCurrency : token}
|
||||
/>
|
||||
<CurrencyLogo currency={token} size="32px" />
|
||||
<L2NetworkLogo networkUrl={L2Icon} size="16px" />
|
||||
</LogoContainer>
|
||||
{token?.name ?? <Trans>Name not found</Trans>}
|
||||
@ -160,7 +142,7 @@ export default function TokenDetails({
|
||||
</TokenNameCell>
|
||||
<TokenActions>
|
||||
{tokenQueryData?.name && tokenQueryData.symbol && tokenQueryData.address && (
|
||||
<ShareButton token={tokenQueryData} isNative={!!nativeCurrency} />
|
||||
<ShareButton currency={token} />
|
||||
)}
|
||||
</TokenActions>
|
||||
</TokenInfoContainer>
|
||||
|
@ -3,7 +3,7 @@ import { sendAnalyticsEvent } from '@uniswap/analytics'
|
||||
import { EventName } from '@uniswap/analytics-events'
|
||||
import { ParentSize } from '@visx/responsive'
|
||||
import SparklineChart from 'components/Charts/SparklineChart'
|
||||
import CurrencyLogo from 'components/CurrencyLogo'
|
||||
import QueryTokenLogo from 'components/Logo/QueryTokenLogo'
|
||||
import { getChainInfo } from 'constants/chainInfo'
|
||||
import { SparklineMap, TopToken } from 'graphql/data/TopTokens'
|
||||
import { CHAIN_NAME_TO_CHAIN_ID, getTokenDetailsURL } from 'graphql/data/util'
|
||||
@ -31,7 +31,6 @@ import {
|
||||
TokenSortMethod,
|
||||
useSetSortMethod,
|
||||
} from '../state'
|
||||
import { useTokenLogoURI } from '../TokenDetails'
|
||||
import InfoTip from '../TokenDetails/InfoTip'
|
||||
import { formatDelta, getDeltaArrow } from '../TokenDetails/PriceChart'
|
||||
|
||||
@ -438,7 +437,8 @@ export const LoadedRow = forwardRef((props: LoadedRowProps, ref: ForwardedRef<HT
|
||||
|
||||
const lowercaseChainName = useParams<{ chainName?: string }>().chainName?.toUpperCase() ?? 'ethereum'
|
||||
const filterNetwork = lowercaseChainName.toUpperCase()
|
||||
const L2Icon = getChainInfo(CHAIN_NAME_TO_CHAIN_ID[filterNetwork])?.circleLogoUrl
|
||||
const chainId = CHAIN_NAME_TO_CHAIN_ID[filterNetwork]
|
||||
const L2Icon = getChainInfo(chainId)?.circleLogoUrl
|
||||
const timePeriod = useAtomValue(filterTimeAtom)
|
||||
const delta = token.market?.pricePercentChange?.value
|
||||
const arrow = getDeltaArrow(delta)
|
||||
@ -446,7 +446,7 @@ export const LoadedRow = forwardRef((props: LoadedRowProps, ref: ForwardedRef<HT
|
||||
const rank = sortAscending ? tokenListLength - tokenListIndex : tokenListIndex + 1
|
||||
|
||||
const exploreTokenSelectedEventProperties = {
|
||||
chain_id: filterNetwork,
|
||||
chain_id: chainId,
|
||||
token_address: tokenAddress,
|
||||
token_symbol: tokenSymbol,
|
||||
token_list_index: tokenListIndex,
|
||||
@ -469,7 +469,7 @@ export const LoadedRow = forwardRef((props: LoadedRowProps, ref: ForwardedRef<HT
|
||||
tokenInfo={
|
||||
<ClickableName>
|
||||
<LogoContainer>
|
||||
<CurrencyLogo src={useTokenLogoURI(token)} symbol={tokenSymbol} />
|
||||
<QueryTokenLogo token={token} />
|
||||
<L2NetworkLogo networkUrl={L2Icon} />
|
||||
</LogoContainer>
|
||||
<TokenInfoCell>
|
||||
|
@ -17,7 +17,7 @@ import { ButtonPrimary } from '../Button'
|
||||
import { LightCard } from '../Card'
|
||||
import { AutoColumn } from '../Column'
|
||||
import { FiatValue } from '../CurrencyInputPanel/FiatValue'
|
||||
import CurrencyLogo from '../CurrencyLogo'
|
||||
import CurrencyLogo from '../Logo/CurrencyLogo'
|
||||
import { RowBetween, RowFixed } from '../Row'
|
||||
import TradePrice from '../swap/TradePrice'
|
||||
import { AdvancedSwapDetails } from './AdvancedSwapDetails'
|
||||
|
@ -4,7 +4,7 @@ import { useWeb3React } from '@web3-react/core'
|
||||
import { ButtonEmpty } from 'components/Button'
|
||||
import Card, { OutlineCard } from 'components/Card'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import CurrencyLogo from 'components/CurrencyLogo'
|
||||
import CurrencyLogo from 'components/Logo/CurrencyLogo'
|
||||
import Modal from 'components/Modal'
|
||||
import { AutoRow, RowBetween } from 'components/Row'
|
||||
import { useState } from 'react'
|
||||
|
38
src/constants/TokenLogoLookupTable.ts
Normal file
38
src/constants/TokenLogoLookupTable.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import store from 'state'
|
||||
|
||||
import { DEFAULT_LIST_OF_LISTS } from './lists'
|
||||
|
||||
class TokenLogoLookupTable {
|
||||
private dict: { [key: string]: string[] | undefined } = {}
|
||||
private initialized = false
|
||||
|
||||
initialize() {
|
||||
const dict: { [key: string]: string[] | undefined } = {}
|
||||
|
||||
DEFAULT_LIST_OF_LISTS.forEach((list) =>
|
||||
store.getState().lists.byUrl[list].current?.tokens.forEach((token) => {
|
||||
if (token.logoURI) {
|
||||
const lowercaseAddress = token.address.toLowerCase()
|
||||
const currentEntry = dict[lowercaseAddress]
|
||||
if (currentEntry) {
|
||||
currentEntry.push(token.logoURI)
|
||||
} else {
|
||||
dict[lowercaseAddress] = [token.logoURI]
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
this.dict = dict
|
||||
this.initialized = true
|
||||
}
|
||||
getIcons(address?: string | null) {
|
||||
if (!address) return undefined
|
||||
|
||||
if (!this.initialized) {
|
||||
this.initialize()
|
||||
}
|
||||
return this.dict[address.toLowerCase()]
|
||||
}
|
||||
}
|
||||
|
||||
export default new TokenLogoLookupTable()
|
@ -61,14 +61,14 @@ export type TokenQueryData = NonNullable<TokenQuery$data['tokens']>[number]
|
||||
|
||||
// TODO: Return a QueryToken from useTokenQuery instead of TokenQueryData to make it more usable in Currency-centric interfaces.
|
||||
export class QueryToken extends WrappedTokenInfo {
|
||||
constructor(data: NonNullable<TokenQueryData>) {
|
||||
constructor(data: NonNullable<TokenQueryData>, logoSrc?: string) {
|
||||
super({
|
||||
chainId: CHAIN_NAME_TO_CHAIN_ID[data.chain],
|
||||
address: data.address,
|
||||
decimals: data.decimals ?? DEFAULT_ERC20_DECIMALS,
|
||||
symbol: data.symbol ?? '',
|
||||
name: data.name ?? '',
|
||||
logoURI: data.project?.logoUrl ?? undefined,
|
||||
logoURI: logoSrc ?? data.project?.logoUrl ?? undefined,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
81
src/hooks/useAssetLogoSource.ts
Normal file
81
src/hooks/useAssetLogoSource.ts
Normal file
@ -0,0 +1,81 @@
|
||||
import TokenLogoLookupTable from 'constants/TokenLogoLookupTable'
|
||||
import { chainIdToNetworkName, getNativeLogoURI } from 'lib/hooks/useCurrencyLogoURIs'
|
||||
import uriToHttp from 'lib/utils/uriToHttp'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { isAddress } from 'utils'
|
||||
|
||||
export const BAD_SRCS: { [tokenAddress: string]: true } = {}
|
||||
|
||||
// Converts uri's into fetchable urls
|
||||
function parseLogoSources(uris: string[]) {
|
||||
const urls: string[] = []
|
||||
uris.forEach((uri) => urls.push(...uriToHttp(uri)))
|
||||
return urls
|
||||
}
|
||||
|
||||
// Parses uri's, favors non-coingecko images, and improves coingecko logo quality
|
||||
function prioritizeLogoSources(uris: string[]) {
|
||||
const parsedUris = uris.map((uri) => uriToHttp(uri)).flat(1)
|
||||
const preferredUris: string[] = []
|
||||
|
||||
// Consolidate duplicate coingecko urls into one fallback source
|
||||
let coingeckoUrl: string | undefined = undefined
|
||||
|
||||
parsedUris.forEach((uri) => {
|
||||
if (uri.startsWith('https://assets.coingecko')) {
|
||||
if (!coingeckoUrl) {
|
||||
coingeckoUrl = uri.replace(/small|thumb/g, 'large')
|
||||
}
|
||||
} else {
|
||||
preferredUris.push(uri)
|
||||
}
|
||||
})
|
||||
// Places coingecko urls in the back of the source array
|
||||
return coingeckoUrl ? [...preferredUris, coingeckoUrl] : preferredUris
|
||||
}
|
||||
|
||||
function getInitialUrl(address?: string | null, chainId?: number | null, isNative?: boolean) {
|
||||
if (chainId && isNative) return getNativeLogoURI(chainId)
|
||||
|
||||
const networkName = chainId ? chainIdToNetworkName(chainId) : 'ethereum'
|
||||
const checksummedAddress = isAddress(address)
|
||||
if (checksummedAddress) {
|
||||
return `https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/${networkName}/assets/${checksummedAddress}/logo.png`
|
||||
} else {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
export default function useAssetLogoSource(
|
||||
address?: string | null,
|
||||
chainId?: number | null,
|
||||
isNative?: boolean,
|
||||
backupImg?: string | null
|
||||
): [string | undefined, () => void] {
|
||||
const [current, setCurrent] = useState<string | undefined>(getInitialUrl(address, chainId, isNative))
|
||||
const [fallbackSrcs, setFallbackSrcs] = useState<string[] | undefined>(undefined)
|
||||
|
||||
useEffect(() => {
|
||||
setCurrent(getInitialUrl(address, chainId, isNative))
|
||||
setFallbackSrcs(undefined)
|
||||
}, [address, chainId, isNative])
|
||||
|
||||
const nextSrc = useCallback(() => {
|
||||
if (current) {
|
||||
BAD_SRCS[current] = true
|
||||
}
|
||||
// Parses and stores logo sources from tokenlists if assets repo url fails
|
||||
if (!fallbackSrcs) {
|
||||
const uris = TokenLogoLookupTable.getIcons(address) ?? []
|
||||
if (backupImg) uris.push(backupImg)
|
||||
const tokenListIcons = prioritizeLogoSources(parseLogoSources(uris))
|
||||
|
||||
setCurrent(tokenListIcons.find((src) => !BAD_SRCS[src]))
|
||||
setFallbackSrcs(tokenListIcons)
|
||||
} else {
|
||||
setCurrent(fallbackSrcs.find((src) => !BAD_SRCS[src]))
|
||||
}
|
||||
}, [current, fallbackSrcs, address, backupImg])
|
||||
|
||||
return [current, nextSrc]
|
||||
}
|
@ -5,7 +5,7 @@ import { useMemo } from 'react'
|
||||
|
||||
import useENSContentHash from './useENSContentHash'
|
||||
|
||||
export default function useHttpLocations(uri: string | undefined): string[] {
|
||||
export default function useHttpLocations(uri: string | undefined | null): string[] {
|
||||
const ens = useMemo(() => (uri ? parseENSAddress(uri) : undefined), [uri])
|
||||
const resolvedContentHash = useENSContentHash(ens?.ensName)
|
||||
return useMemo(() => {
|
||||
|
@ -10,7 +10,7 @@ import { isCelo, NATIVE_CHAIN_ID, nativeOnChain } from '../../constants/tokens'
|
||||
|
||||
type Network = 'ethereum' | 'arbitrum' | 'optimism' | 'polygon'
|
||||
|
||||
function chainIdToNetworkName(networkId: SupportedChainId): Network {
|
||||
export function chainIdToNetworkName(networkId: SupportedChainId): Network {
|
||||
switch (networkId) {
|
||||
case SupportedChainId.MAINNET:
|
||||
return 'ethereum'
|
||||
@ -60,7 +60,7 @@ export default function useCurrencyLogoURIs(
|
||||
isToken?: boolean
|
||||
address?: string
|
||||
chainId: number
|
||||
logoURI?: string
|
||||
logoURI?: string | null
|
||||
}
|
||||
| null
|
||||
| undefined
|
||||
|
@ -3,7 +3,7 @@ import { Currency, CurrencyAmount, Fraction, Percent } from '@uniswap/sdk-core'
|
||||
import { Text } from 'rebass'
|
||||
|
||||
import { ButtonPrimary } from '../../components/Button'
|
||||
import CurrencyLogo from '../../components/CurrencyLogo'
|
||||
import CurrencyLogo from '../../components/Logo/CurrencyLogo'
|
||||
import { RowBetween, RowFixed } from '../../components/Row'
|
||||
import { Field } from '../../state/mint/actions'
|
||||
import { ThemedText } from '../../theme'
|
||||
|
@ -35,8 +35,8 @@ import { formatCurrencyAmount } from 'utils/formatCurrencyAmount'
|
||||
import { unwrappedToken } from 'utils/unwrappedToken'
|
||||
|
||||
import { AutoColumn } from '../../components/Column'
|
||||
import CurrencyLogo from '../../components/CurrencyLogo'
|
||||
import FormattedCurrencyAmount from '../../components/FormattedCurrencyAmount'
|
||||
import CurrencyLogo from '../../components/Logo/CurrencyLogo'
|
||||
import { AutoRow, RowBetween, RowFixed } from '../../components/Row'
|
||||
import { V2_FACTORY_ADDRESSES } from '../../constants/addresses'
|
||||
import { WRAPPED_NATIVE_CURRENCY } from '../../constants/tokens'
|
||||
|
@ -11,9 +11,9 @@ import Badge from 'components/Badge'
|
||||
import { ButtonConfirmed, ButtonGray, ButtonPrimary } from 'components/Button'
|
||||
import { DarkCard, LightCard } from 'components/Card'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import CurrencyLogo from 'components/CurrencyLogo'
|
||||
import DoubleCurrencyLogo from 'components/DoubleLogo'
|
||||
import Loader from 'components/Loader'
|
||||
import CurrencyLogo from 'components/Logo/CurrencyLogo'
|
||||
import { RowBetween, RowFixed } from 'components/Row'
|
||||
import { Dots } from 'components/swap/styleds'
|
||||
import Toggle from 'components/Toggle'
|
||||
|
@ -13,7 +13,7 @@ import { ButtonDropdownLight } from '../../components/Button'
|
||||
import { LightCard } from '../../components/Card'
|
||||
import { BlueCard } from '../../components/Card'
|
||||
import { AutoColumn, ColumnCenter } from '../../components/Column'
|
||||
import CurrencyLogo from '../../components/CurrencyLogo'
|
||||
import CurrencyLogo from '../../components/Logo/CurrencyLogo'
|
||||
import { FindPoolTabs } from '../../components/NavigationTabs'
|
||||
import { MinimalPositionCard } from '../../components/PositionCard'
|
||||
import Row from '../../components/Row'
|
||||
|
@ -9,11 +9,11 @@ import RangeBadge from 'components/Badge/RangeBadge'
|
||||
import { ButtonConfirmed, ButtonPrimary } from 'components/Button'
|
||||
import { LightCard } from 'components/Card'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import CurrencyLogo from 'components/CurrencyLogo'
|
||||
import DoubleCurrencyLogo from 'components/DoubleLogo'
|
||||
import { Break } from 'components/earn/styled'
|
||||
import FormattedCurrencyAmount from 'components/FormattedCurrencyAmount'
|
||||
import Loader from 'components/Loader'
|
||||
import CurrencyLogo from 'components/Logo/CurrencyLogo'
|
||||
import { AddRemoveTabs } from 'components/NavigationTabs'
|
||||
import { AutoRow, RowBetween, RowFixed } from 'components/Row'
|
||||
import Slider from 'components/Slider'
|
||||
|
@ -18,8 +18,8 @@ import { ButtonConfirmed, ButtonError, ButtonLight, ButtonPrimary } from '../../
|
||||
import { BlueCard, LightCard } from '../../components/Card'
|
||||
import { AutoColumn, ColumnCenter } from '../../components/Column'
|
||||
import CurrencyInputPanel from '../../components/CurrencyInputPanel'
|
||||
import CurrencyLogo from '../../components/CurrencyLogo'
|
||||
import DoubleCurrencyLogo from '../../components/DoubleLogo'
|
||||
import CurrencyLogo from '../../components/Logo/CurrencyLogo'
|
||||
import { AddRemoveTabs } from '../../components/NavigationTabs'
|
||||
import { MinimalPositionCard } from '../../components/PositionCard'
|
||||
import Row, { RowBetween, RowFixed } from '../../components/Row'
|
||||
|
Loading…
Reference in New Issue
Block a user