1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-23 09:52:26 +01:00

Add fallback image/card for NFTs when image was not fetched correctly or does not exist (#15034)

* add fallback image/card for collectibles when image was not fetched correctly or does not exist

* UI and storybook updates (#15071)

* UI and storybook updates

* Adding break so token id is displayed

* subtle border fix

* Updating content

* Removing unused image

* Adding proptype descriptions

* Lint fix

Co-authored-by: George Marshall <george.marshall@consensys.net>
This commit is contained in:
Alex Donesky 2022-06-30 10:46:38 -05:00 committed by GitHub
parent afb3475d17
commit 3a31326199
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 201 additions and 47 deletions

View File

@ -3865,6 +3865,9 @@
"unknownCameraErrorTitle": {
"message": "Ooops! Something went wrong...."
},
"unknownCollection": {
"message": "Unnamed collection"
},
"unknownNetwork": {
"message": "Unknown Private Network"
},

View File

@ -14,6 +14,7 @@
@import 'collectibles-items/index';
@import 'collectibles-tab/index';
@import 'collectible-details/index';
@import 'collectible-default-image/index';
@import 'collectible-options/index';
@import 'collectibles-detection-notice/index';
@import 'connected-accounts-list/index';

View File

@ -0,0 +1,41 @@
import React from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import Typography from '../../ui/typography';
import { TYPOGRAPHY } from '../../../helpers/constants/design-system';
import { useI18nContext } from '../../../hooks/useI18nContext';
export default function CollectibleDefaultImage({
name,
tokenId,
handleImageClick,
}) {
const t = useI18nContext();
return (
<div
className={classnames('collectible-default', {
'collectible-default--clickable': handleImageClick,
})}
onClick={handleImageClick}
>
<Typography variant={TYPOGRAPHY.H6} className="collectible-default__text">
{name ?? t('unknownCollection')} <br /> #{tokenId}
</Typography>
</div>
);
}
CollectibleDefaultImage.propTypes = {
/**
* The name of the collectible collection if not supplied will default to "Unnamed collection"
*/
name: PropTypes.string,
/**
* The token id of the collectible
*/
tokenId: PropTypes.string,
/**
* The click handler for the collectible default image
*/
handleImageClick: PropTypes.func,
};

View File

@ -0,0 +1,42 @@
import React from 'react';
import CollectibleDefaultImage from '.';
export default {
title: 'Components/App/CollectibleDefaultImage',
id: __filename,
argTypes: {
name: {
control: 'text',
},
tokenId: {
control: 'text',
},
handleImageClick: {
action: 'handleImageClick',
},
},
args: {
name: null,
tokenId: '12345',
handleImageClick: null,
},
};
export const DefaultStory = (args) => (
<div style={{ width: 200, height: 200 }}>
<CollectibleDefaultImage {...args} />
</div>
);
DefaultStory.storyName = 'Default';
export const handleImageClick = (args) => (
<div style={{ width: 200, height: 200 }}>
<CollectibleDefaultImage {...args} />
</div>
);
handleImageClick.args = {
// eslint-disable-next-line no-alert
handleImageClick: () => window.alert('CollectibleDefaultImage clicked!'),
};

View File

@ -0,0 +1 @@
export { default } from './collectible-default-image';

View File

@ -0,0 +1,22 @@
.collectible-default {
background-color: var(--color-background-alternative);
padding-top: 100%; // retains 1:1 aspect ratio
position: relative;
width: 100%;
&__text {
overflow: hidden;
text-overflow: ellipsis;
text-align: center;
position: absolute;
white-space: nowrap;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: calc(100% - 32px);
}
&--clickable {
cursor: pointer;
}
}

View File

@ -52,6 +52,7 @@ import { usePrevious } from '../../../hooks/usePrevious';
import { useCopyToClipboard } from '../../../hooks/useCopyToClipboard';
import { isEqualCaseInsensitive } from '../../../../shared/modules/string-utils';
import { ASSET_TYPES } from '../../../../shared/constants/transaction';
import CollectibleDefaultImage from '../collectible-default-image';
export default function CollectibleDetails({ collectible }) {
const {
@ -176,7 +177,11 @@ export default function CollectibleDetails({ collectible }) {
justifyContent={JUSTIFY_CONTENT.CENTER}
className="collectible-details__card"
>
{image ? (
<img className="collectible-details__image" src={image} />
) : (
<CollectibleDefaultImage name={name} tokenId={tokenId} />
)}
</Card>
<Box
flexDirection={FLEX_DIRECTION.COLUMN}
@ -215,6 +220,7 @@ export default function CollectibleDetails({ collectible }) {
<Typography
color={COLORS.TEXT_ALTERNATIVE}
variant={TYPOGRAPHY.H6}
overflowWrap={OVERFLOW_WRAP.BREAK_WORD}
boxProps={{ margin: 0, marginBottom: 4 }}
>
{description}

View File

@ -1,16 +1,6 @@
import React from 'react';
import CollectibleDetails from './collectible-details';
export default {
title: 'Components/App/CollectiblesDetail',
id: __filename,
argTypes: {
collectible: {
control: 'object',
},
},
};
const collectible = {
name: 'Catnip Spicywright',
tokenId: '1124157',
@ -20,12 +10,32 @@ const collectible = {
"Good day. My name is Catnip Spicywight, which got me teased a lot in high school. If I want to put low fat mayo all over my hamburgers, I shouldn't have to answer to anyone about it, am I right? One time I beat Arlene in an arm wrestle.",
};
export const DefaultStory = () => {
return <CollectibleDetails collectible={collectible} />;
export default {
title: 'Components/App/CollectiblesDetail',
id: __filename,
argTypes: {
collectible: {
control: 'object',
},
},
args: {
collectible,
},
};
export const DefaultStory = (args) => {
return <CollectibleDetails {...args} />;
};
DefaultStory.storyName = 'Default';
DefaultStory.args = {
collectible,
export const NoImage = (args) => {
return <CollectibleDetails {...args} />;
};
NoImage.args = {
collectible: {
...collectible,
image: undefined,
},
};

View File

@ -28,6 +28,8 @@ import { getAssetImageURL } from '../../../helpers/utils/util';
import { updateCollectibleDropDownState } from '../../../store/actions';
import { usePrevious } from '../../../hooks/usePrevious';
import { getCollectiblesDropdownState } from '../../../ducks/metamask/metamask';
import { useI18nContext } from '../../../hooks/useI18nContext';
import CollectibleDefaultImage from '../collectible-default-image';
const width =
getEnvironmentType() === ENVIRONMENT_TYPE_POPUP
@ -46,6 +48,7 @@ export default function CollectiblesItems({
const previousCollectionKeys = usePrevious(collectionsKeys);
const selectedAddress = useSelector(getSelectedAddress);
const chainId = useSelector(getCurrentChainId);
const t = useI18nContext();
useEffect(() => {
if (
@ -101,7 +104,7 @@ export default function CollectiblesItems({
}
return (
<div className="collectibles-items__collection-image-alt">
{collectionName[0]}
{collectionName?.[0]?.toUpperCase() ?? null}
</div>
);
};
@ -164,7 +167,9 @@ export default function CollectiblesItems({
variant={TYPOGRAPHY.H5}
margin={[0, 0, 0, 2]}
>
{`${collectionName} (${collectibles.length})`}
{`${collectionName ?? t('unknownCollection')} (${
collectibles.length
})`}
</Typography>
</Box>
<Box alignItems={ALIGN_ITEMS.FLEX_END}>
@ -180,29 +185,48 @@ export default function CollectiblesItems({
{isExpanded ? (
<Box display={DISPLAY.FLEX} flexWrap={FLEX_WRAP.WRAP} gap={4}>
{collectibles.map((collectible, i) => {
const { image, address, tokenId, backgroundColor } = collectible;
const {
image,
address,
tokenId,
backgroundColor,
name,
} = collectible;
const collectibleImage = getAssetImageURL(image, ipfsGateway);
const handleImageClick = () =>
history.push(`${ASSET_ROUTE}/${address}/${tokenId}`);
return (
<Box
width={width}
key={`collectible-${i}`}
className="collectibles-items__collection-item-wrapper"
className="collectibles-items__item-wrapper"
>
<Card padding={0} justifyContent={JUSTIFY_CONTENT.CENTER}>
<Card
padding={0}
justifyContent={JUSTIFY_CONTENT.CENTER}
className="collectibles-items__item-wrapper__card"
>
{collectibleImage ? (
<div
className="collectibles-items__collection-item"
className="collectibles-items__item"
style={{
backgroundColor,
}}
>
<img
onClick={() =>
history.push(`${ASSET_ROUTE}/${address}/${tokenId}`)
}
className="collectibles-items__collection-item-image"
onClick={handleImageClick}
className="collectibles-items__item-image"
src={collectibleImage}
/>
</div>
) : (
<CollectibleDefaultImage
name={name}
tokenId={tokenId}
handleImageClick={handleImageClick}
/>
)}
</Card>
</Box>
);

View File

@ -27,29 +27,33 @@
color: var(--color-overlay-inverse);
text-align: center;
}
&-item-wrapper {
align-self: center;
}
&-item {
&__item-wrapper {
align-self: center;
&__card {
overflow: hidden;
}
}
&__item {
border-radius: 4px;
width: 100%;
display: flex;
justify-content: center;
cursor: pointer;
align-self: center;
}
&-item-image {
&-image {
border-radius: 4px;
width: 100%;
height: 100%;
cursor: pointer;
}
}
&__icon-chevron {
color: var(--color-icon-default);
}
}
}