Commit f566ff9e authored by tom goriunov's avatar tom goriunov Committed by GitHub

truncate metadata field values (#992)

* truncate metadata field values

* add link support in NFT attributes
parent 45394dcf
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="m17.667 13.75-5.5-9.834c-.417-.833-1.25-1.25-2.167-1.25-.916 0-1.75.417-2.167 1.25l-5.5 9.834a2.656 2.656 0 0 0 0 2.5C2.75 17 3.583 17.5 4.5 17.5h11c.917 0 1.667-.5 2.167-1.25.5-.75.416-1.667 0-2.5Zm-1.417 1.666c-.083.084-.25.417-.75.417h-11c-.417 0-.667-.25-.75-.417-.083-.166-.25-.416 0-.833l5.5-9.916c.25-.417.584-.417.75-.417.167 0 .5 0 .75.417l5.5 9.916c.167.417 0 .75 0 .833Z" fill="currentColor"/>
<path d="M10 7.5c-.5 0-.833.333-.833.833v2.5c0 .5.333.834.833.834.5 0 .834-.334.834-.834v-2.5c0-.5-.334-.833-.834-.833ZM10 14.167a.833.833 0 1 0 0-1.667.833.833 0 0 0 0 1.667Z" fill="currentColor"/>
<path d="M10 7.5c-.5 0-.833.333-.833.833v2.5c0 .5.333.834.833.834.5 0 .834-.334.834-.834v-2.5c0-.5-.334-.833-.834-.833Zm0 6.667a.833.833 0 1 0 0-1.667.833.833 0 0 0 0 1.667Z" fill="currentColor"/>
</svg>
import _capitalize from 'lodash/capitalize';
import _upperFirst from 'lodash/upperFirst';
import type { Metadata } from 'types/client/token';
import type { Metadata, MetadataAttributes } from 'types/client/token';
import dayjs from 'lib/date/dayjs';
function formatValue(value: string | number, display: string | undefined): string {
function formatValue(value: string | number, display: string | undefined, trait: string | undefined): Pick<MetadataAttributes, 'value' | 'value_type'> {
// https://docs.opensea.io/docs/metadata-standards#attributes
switch (display) {
case 'boost_number': {
return `+${ value } boost`;
return {
value: `+${ value } boost`,
};
}
case 'boost_percentage': {
return `${ value }% boost`;
return {
value: `${ value }% boost`,
};
}
case 'date': {
return dayjs(value).format('YYYY-MM-DD');
return {
value: dayjs(value).format('YYYY-MM-DD'),
};
}
default: {
return String(value);
try {
if (trait?.toLowerCase().includes('url')) {
const url = new URL(String(value));
return {
value: url.toString(),
value_type: 'URL',
};
}
throw new Error();
} catch (error) {
return {
value: String(value),
};
}
}
}
}
......@@ -38,8 +57,8 @@ export default function attributesParser(attributes: Array<unknown>): Metadata['
}
return {
value: formatValue(value, display),
trait_type: _capitalize(trait || 'property'),
...formatValue(value, display, trait),
trait_type: _upperFirst(trait || 'property'),
};
})
.filter(Boolean);
......
......@@ -46,8 +46,8 @@ export const base: TokenInstance = {
value: '5',
},
{
trait_type: 'bg color',
value: '64',
trait_type: 'eventURL',
value: 'https://twitter.com/lilnounsdao?s=21&t=xAihrtwPd6avwdsQqeMXCw',
},
{
trait_type: 'p1',
......
......@@ -7,4 +7,5 @@ export interface Metadata {
export interface MetadataAttributes {
value: string;
trait_type: string;
value_type?: 'URL';
}
import { Flex, Grid, GridItem, Skeleton, useColorModeValue } from '@chakra-ui/react';
import { Flex, Grid, Skeleton } from '@chakra-ui/react';
import React from 'react';
import type { TokenInstance } from 'types/api/token';
import parseMetadata from 'lib/token/parseMetadata';
import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon';
import AddressLink from 'ui/shared/address/AddressLink';
......@@ -15,6 +14,8 @@ import NftMedia from 'ui/shared/nft/NftMedia';
import TokenSnippet from 'ui/shared/TokenSnippet/TokenSnippet';
import TokenInstanceCreatorAddress from './details/TokenInstanceCreatorAddress';
import TokenInstanceDivider from './details/TokenInstanceDivider';
import TokenInstanceMetadataInfo from './details/TokenInstanceMetadataInfo';
import TokenInstanceTransfersCount from './details/TokenInstanceTransfersCount';
interface Props {
......@@ -31,20 +32,6 @@ const TokenInstanceDetails = ({ data, scrollRef, isLoading }: Props) => {
}, 500);
}, [ scrollRef ]);
const metadata = parseMetadata(data?.metadata);
const hasMetadata = metadata && Boolean((metadata.name || metadata.description || metadata.attributes));
const attributeBgColor = useColorModeValue('blackAlpha.50', 'whiteAlpha.50');
const divider = (
<GridItem
colSpan={{ base: undefined, lg: 2 }}
mt={{ base: 2, lg: 3 }}
mb={{ base: 0, lg: 3 }}
borderBottom="1px solid"
borderColor="divider"
/>
);
if (!data) {
return null;
}
......@@ -110,68 +97,8 @@ const TokenInstanceDetails = ({ data, scrollRef, isLoading }: Props) => {
templateColumns={{ base: 'minmax(0, 1fr)', lg: '200px minmax(0, 1fr)' }}
overflow="hidden"
>
{ hasMetadata && (
<>
{ divider }
{ metadata?.name && (
<DetailsInfoItem
title="Name"
hint="NFT name"
whiteSpace="normal"
wordBreak="break-word"
isLoading={ isLoading }
>
<Skeleton isLoaded={ !isLoading }>
{ metadata.name }
</Skeleton>
</DetailsInfoItem>
) }
{ metadata?.description && (
<DetailsInfoItem
title="Description"
hint="NFT description"
whiteSpace="normal"
wordBreak="break-word"
isLoading={ isLoading }
>
<Skeleton isLoaded={ !isLoading }>
{ metadata.description }
</Skeleton>
</DetailsInfoItem>
) }
{ metadata?.attributes && (
<DetailsInfoItem
title="Attributes"
hint="NFT attributes"
whiteSpace="normal"
isLoading={ isLoading }
>
<Grid gap={ 2 } templateColumns="repeat(auto-fill,minmax(160px, 1fr))" w="100%">
{ metadata.attributes.map((attribute, index) => (
<GridItem
key={ index }
bgColor={ attributeBgColor }
borderRadius="md"
px={ 4 }
py={ 2 }
display="flex"
flexDir="column"
alignItems="flex-start"
>
<Skeleton isLoaded={ !isLoading } fontSize="xs" lineHeight={ 4 } color="text_secondary" fontWeight={ 500 } mb={ 1 }>
<span>{ attribute.trait_type }</span>
</Skeleton>
<Skeleton isLoaded={ !isLoading } fontSize="sm">
<span>{ attribute.value }</span>
</Skeleton>
</GridItem>
)) }
</Grid>
</DetailsInfoItem>
) }
</>
) }
{ divider }
<TokenInstanceMetadataInfo data={ data } isLoading={ isLoading }/>
<TokenInstanceDivider/>
<DetailsSponsoredItem isLoading={ isLoading }/>
</Grid>
</>
......
import { GridItem } from '@chakra-ui/react';
import React from 'react';
const TokenInstanceDivider = () => {
return (
<GridItem
colSpan={{ base: undefined, lg: 2 }}
mt={{ base: 2, lg: 3 }}
mb={{ base: 0, lg: 3 }}
borderBottom="1px solid"
borderColor="divider"
/>
);
};
export default TokenInstanceDivider;
import { Box, Grid, GridItem, Icon, Link, Skeleton, useColorModeValue } from '@chakra-ui/react';
import React from 'react';
import type { TokenInstance } from 'types/api/token';
import type { MetadataAttributes } from 'types/client/token';
import arrowIcon from 'icons/arrows/north-east.svg';
import parseMetadata from 'lib/token/parseMetadata';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import TruncatedTextTooltip from 'ui/shared/TruncatedTextTooltip';
import TokenInstanceDivider from './TokenInstanceDivider';
interface Props {
data?: TokenInstance;
isLoading?: boolean;
}
interface ItemProps {
data: MetadataAttributes;
isLoading?: boolean;
}
const Item = ({ data, isLoading }: ItemProps) => {
const attributeBgColor = useColorModeValue('blackAlpha.50', 'whiteAlpha.50');
const value = (() => {
if (data.value_type === 'URL') {
return (
<Link whiteSpace="nowrap" display="flex" alignItems="center" w="100%" overflow="hidden" fontSize="sm">
<TruncatedTextTooltip label={ data.value }>
<Box w="calc(100% - 16px)" overflow="hidden" textOverflow="ellipsis">
<span>{ data.value }</span>
</Box>
</TruncatedTextTooltip>
<Icon as={ arrowIcon } boxSize={ 4 }/>
</Link>
);
}
return (
<TruncatedTextTooltip label={ data.value }>
<Skeleton isLoaded={ !isLoading } fontSize="sm" w="100%" overflow="hidden" whiteSpace="nowrap" textOverflow="ellipsis">
{ data.value }
</Skeleton>
</TruncatedTextTooltip>
);
})();
return (
<GridItem
bgColor={ attributeBgColor }
borderRadius="md"
px={ 4 }
py={ 2 }
display="flex"
flexDir="column"
alignItems="flex-start"
>
<Skeleton isLoaded={ !isLoading } fontSize="xs" lineHeight={ 4 } color="text_secondary" fontWeight={ 500 } mb={ 1 }>
<span>{ data.trait_type }</span>
</Skeleton>
{ value }
</GridItem>
);
};
const TokenInstanceMetadataInfo = ({ data, isLoading }: Props) => {
const metadata = React.useMemo(() => parseMetadata(data?.metadata), [ data ]);
const hasMetadata = metadata && Boolean((metadata.name || metadata.description || metadata.attributes));
if (!hasMetadata) {
return null;
}
return (
<>
<TokenInstanceDivider/>
{ metadata?.name && (
<DetailsInfoItem
title="Name"
hint="NFT name"
whiteSpace="normal"
wordBreak="break-word"
isLoading={ isLoading }
>
<Skeleton isLoaded={ !isLoading }>
{ metadata.name }
</Skeleton>
</DetailsInfoItem>
) }
{ metadata?.description && (
<DetailsInfoItem
title="Description"
hint="NFT description"
whiteSpace="normal"
wordBreak="break-word"
isLoading={ isLoading }
>
<Skeleton isLoaded={ !isLoading }>
{ metadata.description }
</Skeleton>
</DetailsInfoItem>
) }
{ metadata?.attributes && (
<DetailsInfoItem
title="Attributes"
hint="NFT attributes"
whiteSpace="normal"
isLoading={ isLoading }
>
<Grid gap={ 2 } templateColumns="repeat(auto-fill,minmax(160px, 1fr))" w="100%">
{ metadata.attributes.map((attribute, index) => <Item key={ index } data={ attribute } isLoading={ isLoading }/>) }
</Grid>
</DetailsInfoItem>
) }
</>
);
};
export default React.memo(TokenInstanceMetadataInfo);
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment