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": { "unknownCameraErrorTitle": {
"message": "Ooops! Something went wrong...." "message": "Ooops! Something went wrong...."
}, },
"unknownCollection": {
"message": "Unnamed collection"
},
"unknownNetwork": { "unknownNetwork": {
"message": "Unknown Private Network" "message": "Unknown Private Network"
}, },

View File

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

View File

@ -1,16 +1,6 @@
import React from 'react'; import React from 'react';
import CollectibleDetails from './collectible-details'; import CollectibleDetails from './collectible-details';
export default {
title: 'Components/App/CollectiblesDetail',
id: __filename,
argTypes: {
collectible: {
control: 'object',
},
},
};
const collectible = { const collectible = {
name: 'Catnip Spicywright', name: 'Catnip Spicywright',
tokenId: '1124157', 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.", "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 = () => { export default {
return <CollectibleDetails collectible={collectible} />; title: 'Components/App/CollectiblesDetail',
id: __filename,
argTypes: {
collectible: {
control: 'object',
},
},
args: {
collectible,
},
};
export const DefaultStory = (args) => {
return <CollectibleDetails {...args} />;
}; };
DefaultStory.storyName = 'Default'; DefaultStory.storyName = 'Default';
DefaultStory.args = { export const NoImage = (args) => {
collectible, 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 { updateCollectibleDropDownState } from '../../../store/actions';
import { usePrevious } from '../../../hooks/usePrevious'; import { usePrevious } from '../../../hooks/usePrevious';
import { getCollectiblesDropdownState } from '../../../ducks/metamask/metamask'; import { getCollectiblesDropdownState } from '../../../ducks/metamask/metamask';
import { useI18nContext } from '../../../hooks/useI18nContext';
import CollectibleDefaultImage from '../collectible-default-image';
const width = const width =
getEnvironmentType() === ENVIRONMENT_TYPE_POPUP getEnvironmentType() === ENVIRONMENT_TYPE_POPUP
@ -46,6 +48,7 @@ export default function CollectiblesItems({
const previousCollectionKeys = usePrevious(collectionsKeys); const previousCollectionKeys = usePrevious(collectionsKeys);
const selectedAddress = useSelector(getSelectedAddress); const selectedAddress = useSelector(getSelectedAddress);
const chainId = useSelector(getCurrentChainId); const chainId = useSelector(getCurrentChainId);
const t = useI18nContext();
useEffect(() => { useEffect(() => {
if ( if (
@ -101,7 +104,7 @@ export default function CollectiblesItems({
} }
return ( return (
<div className="collectibles-items__collection-image-alt"> <div className="collectibles-items__collection-image-alt">
{collectionName[0]} {collectionName?.[0]?.toUpperCase() ?? null}
</div> </div>
); );
}; };
@ -164,7 +167,9 @@ export default function CollectiblesItems({
variant={TYPOGRAPHY.H5} variant={TYPOGRAPHY.H5}
margin={[0, 0, 0, 2]} margin={[0, 0, 0, 2]}
> >
{`${collectionName} (${collectibles.length})`} {`${collectionName ?? t('unknownCollection')} (${
collectibles.length
})`}
</Typography> </Typography>
</Box> </Box>
<Box alignItems={ALIGN_ITEMS.FLEX_END}> <Box alignItems={ALIGN_ITEMS.FLEX_END}>
@ -180,29 +185,48 @@ export default function CollectiblesItems({
{isExpanded ? ( {isExpanded ? (
<Box display={DISPLAY.FLEX} flexWrap={FLEX_WRAP.WRAP} gap={4}> <Box display={DISPLAY.FLEX} flexWrap={FLEX_WRAP.WRAP} gap={4}>
{collectibles.map((collectible, i) => { {collectibles.map((collectible, i) => {
const { image, address, tokenId, backgroundColor } = collectible; const {
image,
address,
tokenId,
backgroundColor,
name,
} = collectible;
const collectibleImage = getAssetImageURL(image, ipfsGateway); const collectibleImage = getAssetImageURL(image, ipfsGateway);
const handleImageClick = () =>
history.push(`${ASSET_ROUTE}/${address}/${tokenId}`);
return ( return (
<Box <Box
width={width} width={width}
key={`collectible-${i}`} key={`collectible-${i}`}
className="collectibles-items__collection-item-wrapper" className="collectibles-items__item-wrapper"
> >
<Card padding={0} justifyContent={JUSTIFY_CONTENT.CENTER}> <Card
<div padding={0}
className="collectibles-items__collection-item" justifyContent={JUSTIFY_CONTENT.CENTER}
style={{ className="collectibles-items__item-wrapper__card"
backgroundColor, >
}} {collectibleImage ? (
> <div
<img className="collectibles-items__item"
onClick={() => style={{
history.push(`${ASSET_ROUTE}/${address}/${tokenId}`) backgroundColor,
} }}
className="collectibles-items__collection-item-image" >
src={collectibleImage} <img
onClick={handleImageClick}
className="collectibles-items__item-image"
src={collectibleImage}
/>
</div>
) : (
<CollectibleDefaultImage
name={name}
tokenId={tokenId}
handleImageClick={handleImageClick}
/> />
</div> )}
</Card> </Card>
</Box> </Box>
); );

View File

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