mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 01:39:44 +01:00
parent
a0e97f4681
commit
be65eb7339
@ -36,6 +36,7 @@ export default class AppStateController extends EventEmitter {
|
||||
trezorModel: null,
|
||||
...initState,
|
||||
qrHardware: {},
|
||||
collectiblesDropdownState: {},
|
||||
});
|
||||
this.timer = null;
|
||||
|
||||
@ -282,4 +283,15 @@ export default class AppStateController extends EventEmitter {
|
||||
enableEIP1559V2NoticeDismissed,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* A setter for the `collectiblesDropdownState` property
|
||||
*
|
||||
* @param collectiblesDropdownState
|
||||
*/
|
||||
updateCollectibleDropDownState(collectiblesDropdownState) {
|
||||
this.store.updateState({
|
||||
collectiblesDropdownState,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1271,6 +1271,9 @@ export default class MetamaskController extends EventEmitter {
|
||||
setEnableEIP1559V2NoticeDismissed: appStateController.setEnableEIP1559V2NoticeDismissed.bind(
|
||||
appStateController,
|
||||
),
|
||||
updateCollectibleDropDownState: appStateController.updateCollectibleDropDownState.bind(
|
||||
appStateController,
|
||||
),
|
||||
// EnsController
|
||||
tryReverseResolveAddress: ensController.reverseResolveAddress.bind(
|
||||
ensController,
|
||||
|
@ -30,6 +30,7 @@ import {
|
||||
getSelectedIdentity,
|
||||
} from '../../../selectors';
|
||||
import AssetNavigation from '../../../pages/asset/components/asset-navigation';
|
||||
import Copy from '../../ui/icon/copy-icon.component';
|
||||
import { getCollectibleContracts } from '../../../ducks/metamask/metamask';
|
||||
import { DEFAULT_ROUTE, SEND_ROUTE } from '../../../helpers/constants/routes';
|
||||
import {
|
||||
@ -52,10 +53,12 @@ import { ASSET_TYPES, updateSendAsset } from '../../../ducks/send';
|
||||
import InfoTooltip from '../../ui/info-tooltip';
|
||||
import { ERC721 } from '../../../helpers/constants/common';
|
||||
import { usePrevious } from '../../../hooks/usePrevious';
|
||||
import { useCopyToClipboard } from '../../../hooks/useCopyToClipboard';
|
||||
|
||||
export default function CollectibleDetails({ collectible }) {
|
||||
const {
|
||||
image,
|
||||
imageOriginal,
|
||||
name,
|
||||
description,
|
||||
address,
|
||||
@ -70,6 +73,7 @@ export default function CollectibleDetails({ collectible }) {
|
||||
const ipfsGateway = useSelector(getIpfsGateway);
|
||||
const collectibleContracts = useSelector(getCollectibleContracts);
|
||||
const currentNetwork = useSelector(getCurrentChainId);
|
||||
const [copied, handleCopy] = useCopyToClipboard();
|
||||
|
||||
const collectibleContractName = collectibleContracts.find(
|
||||
({ address: contractAddress }) =>
|
||||
@ -78,7 +82,10 @@ export default function CollectibleDetails({ collectible }) {
|
||||
const selectedAccountName = useSelector(
|
||||
(state) => getSelectedIdentity(state).name,
|
||||
);
|
||||
const collectibleImageURL = getAssetImageURL(image, ipfsGateway);
|
||||
const collectibleImageURL = getAssetImageURL(
|
||||
imageOriginal ?? image,
|
||||
ipfsGateway,
|
||||
);
|
||||
|
||||
const onRemove = () => {
|
||||
dispatch(removeAndIgnoreCollectible(address, tokenId));
|
||||
@ -171,10 +178,7 @@ export default function CollectibleDetails({ collectible }) {
|
||||
justifyContent={JUSTIFY_CONTENT.CENTER}
|
||||
className="collectible-details__card"
|
||||
>
|
||||
<img
|
||||
className="collectible-details__image"
|
||||
src={collectibleImageURL}
|
||||
/>
|
||||
<img className="collectible-details__image" src={image} />
|
||||
</Card>
|
||||
<Box
|
||||
flexDirection={FLEX_DIRECTION.COLUMN}
|
||||
@ -252,7 +256,7 @@ export default function CollectibleDetails({ collectible }) {
|
||||
href={collectibleImageURL}
|
||||
title={collectibleImageURL}
|
||||
>
|
||||
{image}
|
||||
{collectibleImageURL}
|
||||
</a>
|
||||
</Typography>
|
||||
</Box>
|
||||
@ -270,31 +274,49 @@ export default function CollectibleDetails({ collectible }) {
|
||||
>
|
||||
{t('contractAddress')}
|
||||
</Typography>
|
||||
<Typography
|
||||
color={COLORS.PRIMARY1}
|
||||
variant={TYPOGRAPHY.H6}
|
||||
overflowWrap={OVERFLOW_WRAP.BREAK_WORD}
|
||||
boxProps={{
|
||||
margin: 0,
|
||||
marginBottom: 4,
|
||||
}}
|
||||
className="collectible-details__contract-link"
|
||||
<Box
|
||||
display={DISPLAY.FLEX}
|
||||
flexDirection={FLEX_DIRECTION.ROW}
|
||||
className="collectible-details__contract-wrapper"
|
||||
>
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href={getTokenTrackerLink(
|
||||
address,
|
||||
currentNetwork,
|
||||
null,
|
||||
null,
|
||||
rpcPrefs,
|
||||
)}
|
||||
title={address}
|
||||
<Typography
|
||||
color={COLORS.PRIMARY1}
|
||||
variant={TYPOGRAPHY.H6}
|
||||
overflowWrap={OVERFLOW_WRAP.BREAK_WORD}
|
||||
boxProps={{
|
||||
margin: 0,
|
||||
marginBottom: 4,
|
||||
}}
|
||||
className="collectible-details__contract-link"
|
||||
>
|
||||
{inPopUp ? shortenAddress(address) : address}
|
||||
</a>
|
||||
</Typography>
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href={getTokenTrackerLink(
|
||||
address,
|
||||
currentNetwork,
|
||||
null,
|
||||
null,
|
||||
rpcPrefs,
|
||||
)}
|
||||
title={address}
|
||||
>
|
||||
{inPopUp ? shortenAddress(address) : address}
|
||||
</a>
|
||||
</Typography>
|
||||
<button
|
||||
className="collectible-details__contract-copy-button"
|
||||
onClick={() => {
|
||||
handleCopy(address);
|
||||
}}
|
||||
>
|
||||
{copied ? (
|
||||
t('copiedExclamation')
|
||||
) : (
|
||||
<Copy size={15} color="#6a737d" />
|
||||
)}
|
||||
</button>
|
||||
</Box>
|
||||
</Box>
|
||||
{inPopUp ? renderSendButton() : null}
|
||||
</Box>
|
||||
@ -314,6 +336,7 @@ CollectibleDetails.propTypes = {
|
||||
standard: PropTypes.string,
|
||||
imageThumbnail: PropTypes.string,
|
||||
imagePreview: PropTypes.string,
|
||||
imageOriginal: PropTypes.string,
|
||||
creator: PropTypes.shape({
|
||||
address: PropTypes.string,
|
||||
config: PropTypes.string,
|
||||
|
@ -53,18 +53,40 @@ $spacer-break-small: 16px;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
&__contract-link,
|
||||
&__image-link {
|
||||
word-break: break-all;
|
||||
&__contract-wrapper {
|
||||
max-width: calc(100% - #{$link-title-width});
|
||||
}
|
||||
|
||||
@media screen and (max-width: $break-small) {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
width: 90%;
|
||||
&__contract-copy-button {
|
||||
@include H6;
|
||||
|
||||
width: 80px;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
background-color: transparent;
|
||||
cursor: pointer;
|
||||
color: var(--ui-4);
|
||||
border: 0;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.97);
|
||||
}
|
||||
}
|
||||
|
||||
&__contract-link {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&__image-link {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
max-width: 332px;
|
||||
}
|
||||
|
||||
&__link-title {
|
||||
flex: 0 0 $link-title-width;
|
||||
max-width: 0 0 $link-title-width;
|
||||
|
@ -1,9 +1,10 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import Box from '../../ui/box';
|
||||
import Typography from '../../ui/typography/typography';
|
||||
import Card from '../../ui/card';
|
||||
import {
|
||||
COLORS,
|
||||
TYPOGRAPHY,
|
||||
@ -19,6 +20,9 @@ import { getEnvironmentType } from '../../../../app/scripts/lib/util';
|
||||
import { getIpfsGateway } from '../../../selectors';
|
||||
import { ASSET_ROUTE } from '../../../helpers/constants/routes';
|
||||
import { getAssetImageURL } from '../../../helpers/utils/util';
|
||||
import { updateCollectibleDropDownState } from '../../../store/actions';
|
||||
import { usePrevious } from '../../../hooks/usePrevious';
|
||||
import { getCollectiblesDropdownState } from '../../../ducks/metamask/metamask';
|
||||
|
||||
const width =
|
||||
getEnvironmentType() === ENVIRONMENT_TYPE_POPUP
|
||||
@ -31,20 +35,30 @@ export default function CollectiblesItems({
|
||||
collections = {},
|
||||
previouslyOwnedCollection = {},
|
||||
}) {
|
||||
const dispatch = useDispatch();
|
||||
const collectionsKeys = Object.keys(collections);
|
||||
const collectiblesDropdownState = useSelector(getCollectiblesDropdownState);
|
||||
const previousCollectionKeys = usePrevious(collectionsKeys);
|
||||
|
||||
// if there is only one collection present set it to open when component mounts
|
||||
const [dropdownState, setDropdownState] = useState(() => {
|
||||
return collectionsKeys.length === 1
|
||||
? {
|
||||
[PREVIOUSLY_OWNED_KEY]: false,
|
||||
[collectionsKeys[0]]: true,
|
||||
}
|
||||
: { [PREVIOUSLY_OWNED_KEY]: false };
|
||||
});
|
||||
useEffect(() => {
|
||||
if (
|
||||
!Object.keys(collectiblesDropdownState).length &&
|
||||
previousCollectionKeys !== collectionsKeys
|
||||
) {
|
||||
const initState = {};
|
||||
collectionsKeys.forEach((key) => {
|
||||
initState[key] = true;
|
||||
});
|
||||
dispatch(updateCollectibleDropDownState(initState));
|
||||
}
|
||||
}, [
|
||||
collectionsKeys,
|
||||
previousCollectionKeys,
|
||||
collectiblesDropdownState,
|
||||
dispatch,
|
||||
]);
|
||||
|
||||
const ipfsGateway = useSelector(getIpfsGateway);
|
||||
|
||||
const history = useHistory();
|
||||
|
||||
const renderCollectionImage = (
|
||||
@ -81,46 +95,50 @@ export default function CollectiblesItems({
|
||||
return null;
|
||||
}
|
||||
|
||||
const isExpanded = dropdownState[key];
|
||||
const isExpanded = collectiblesDropdownState[key];
|
||||
return (
|
||||
<div
|
||||
className="collectibles-items__collection"
|
||||
key={`collection-${key}`}
|
||||
onClick={() => {
|
||||
setDropdownState((_dropdownState) => ({
|
||||
..._dropdownState,
|
||||
[key]: !isExpanded,
|
||||
}));
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
marginBottom={2}
|
||||
display={DISPLAY.FLEX}
|
||||
alignItems={ALIGN_ITEMS.CENTER}
|
||||
justifyContent={JUSTIFY_CONTENT.SPACE_BETWEEN}
|
||||
className="collectibles-items__collection-accordion-title"
|
||||
<div className="collectibles-items__collection" key={`collection-${key}`}>
|
||||
<button
|
||||
onClick={() => {
|
||||
dispatch(
|
||||
updateCollectibleDropDownState({
|
||||
...collectiblesDropdownState,
|
||||
[key]: !isExpanded,
|
||||
}),
|
||||
);
|
||||
}}
|
||||
className="collectibles-items__collection-wrapper"
|
||||
>
|
||||
<Box
|
||||
marginBottom={2}
|
||||
display={DISPLAY.FLEX}
|
||||
alignItems={ALIGN_ITEMS.CENTER}
|
||||
className="collectibles-items__collection-header"
|
||||
justifyContent={JUSTIFY_CONTENT.SPACE_BETWEEN}
|
||||
className="collectibles-items__collection-accordion-title"
|
||||
>
|
||||
{renderCollectionImage(
|
||||
isPreviouslyOwnedCollection,
|
||||
collectionImage,
|
||||
collectionName,
|
||||
)}
|
||||
<Typography
|
||||
color={COLORS.BLACK}
|
||||
variant={TYPOGRAPHY.H5}
|
||||
margin={[0, 0, 0, 2]}
|
||||
<Box
|
||||
alignItems={ALIGN_ITEMS.CENTER}
|
||||
className="collectibles-items__collection-header"
|
||||
>
|
||||
{`${collectionName} (${collectibles.length})`}
|
||||
</Typography>
|
||||
{renderCollectionImage(
|
||||
isPreviouslyOwnedCollection,
|
||||
collectionImage,
|
||||
collectionName,
|
||||
)}
|
||||
<Typography
|
||||
color={COLORS.BLACK}
|
||||
variant={TYPOGRAPHY.H5}
|
||||
margin={[0, 0, 0, 2]}
|
||||
>
|
||||
{`${collectionName} (${collectibles.length})`}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box alignItems={ALIGN_ITEMS.FLEX_END}>
|
||||
<i className={`fa fa-chevron-${isExpanded ? 'down' : 'right'}`} />
|
||||
</Box>
|
||||
</Box>
|
||||
<Box alignItems={ALIGN_ITEMS.FLEX_END}>
|
||||
<i className={`fa fa-chevron-${isExpanded ? 'down' : 'right'}`} />
|
||||
</Box>
|
||||
</Box>
|
||||
</button>
|
||||
|
||||
{isExpanded ? (
|
||||
<Box display={DISPLAY.FLEX} flexWrap={FLEX_WRAP.WRAP} gap={4}>
|
||||
{collectibles.map((collectible, i) => {
|
||||
@ -132,20 +150,22 @@ export default function CollectiblesItems({
|
||||
key={`collectible-${i}`}
|
||||
className="collectibles-items__collection-item-wrapper"
|
||||
>
|
||||
<div
|
||||
className="collectibles-items__collection-item"
|
||||
style={{
|
||||
backgroundColor,
|
||||
}}
|
||||
>
|
||||
<img
|
||||
onClick={() =>
|
||||
history.push(`${ASSET_ROUTE}/${address}/${tokenId}`)
|
||||
}
|
||||
className="collectibles-items__collection-item-image"
|
||||
src={collectibleImage}
|
||||
/>
|
||||
</div>
|
||||
<Card padding={0} justifyContent={JUSTIFY_CONTENT.CENTER}>
|
||||
<div
|
||||
className="collectibles-items__collection-item"
|
||||
style={{
|
||||
backgroundColor,
|
||||
}}
|
||||
>
|
||||
<img
|
||||
onClick={() =>
|
||||
history.push(`${ASSET_ROUTE}/${address}/${tokenId}`)
|
||||
}
|
||||
className="collectibles-items__collection-item-image"
|
||||
src={collectibleImage}
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
|
@ -6,6 +6,12 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&-wrapper {
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&-image {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
|
@ -136,6 +136,11 @@ const COLLECTIBLES_CONTRACTS = [
|
||||
},
|
||||
];
|
||||
|
||||
const collectiblesDropdownState = {
|
||||
0x495f947276749ce646f68ac8c248420045cb7b5e: true,
|
||||
0xdc7382eb0bc9c352a4cba23c909bda01e0206414: true,
|
||||
};
|
||||
|
||||
const ACCOUNT_1 = '0x123';
|
||||
const ACCOUNT_2 = '0x456';
|
||||
|
||||
@ -164,6 +169,7 @@ const render = ({
|
||||
selectedAddress,
|
||||
collectiblesDetectionNoticeDismissed,
|
||||
useCollectibleDetection,
|
||||
collectiblesDropdownState,
|
||||
},
|
||||
});
|
||||
return renderWithProvider(<CollectiblesTab onAddNFT={onAddNFT} />, store);
|
||||
|
@ -707,6 +707,7 @@
|
||||
|
||||
&__label {
|
||||
margin-right: 0.25rem;
|
||||
min-width: 52px;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -264,6 +264,10 @@ export function getCollectiblesDetectionNoticeDismissed(state) {
|
||||
return state.metamask.collectiblesDetectionNoticeDismissed;
|
||||
}
|
||||
|
||||
export function getCollectiblesDropdownState(state) {
|
||||
return state.metamask.collectiblesDropdownState;
|
||||
}
|
||||
|
||||
export function getEnableEIP1559V2NoticeDismissed(state) {
|
||||
return state.metamask.enableEIP1559V2NoticeDismissed;
|
||||
}
|
||||
|
@ -1491,6 +1491,19 @@ export function updateSendAsset({ type, details }) {
|
||||
dispatch(displayWarning(err.message));
|
||||
}
|
||||
}
|
||||
|
||||
if (details.standard === undefined) {
|
||||
const { standard } = await getTokenStandardAndDetails(
|
||||
details.address,
|
||||
userAddress,
|
||||
);
|
||||
details.standard = standard;
|
||||
}
|
||||
|
||||
if (details.standard === ERC1155) {
|
||||
throw new Error('Sends of ERC1155 tokens are not currently supported');
|
||||
}
|
||||
|
||||
if (isCurrentOwner) {
|
||||
error = null;
|
||||
balance = '0x1';
|
||||
|
@ -4,7 +4,7 @@ import SendRowWrapper from '../send-row-wrapper';
|
||||
import Identicon from '../../../../components/ui/identicon';
|
||||
import TokenBalance from '../../../../components/ui/token-balance';
|
||||
import UserPreferencedCurrencyDisplay from '../../../../components/app/user-preferenced-currency-display';
|
||||
import { ERC20, PRIMARY } from '../../../../helpers/constants/common';
|
||||
import { ERC20, ERC721, PRIMARY } from '../../../../helpers/constants/common';
|
||||
import { ASSET_TYPES } from '../../../../ducks/send';
|
||||
import { isEqualCaseInsensitive } from '../../../../helpers/utils/util';
|
||||
|
||||
@ -57,7 +57,8 @@ export default class SendAssetRow extends Component {
|
||||
async componentDidMount() {
|
||||
const sendableTokens = this.props.tokens.filter((token) => !token.isERC721);
|
||||
const sendableCollectibles = this.props.collectibles.filter(
|
||||
(collectible) => collectible.isCurrentlyOwned,
|
||||
(collectible) =>
|
||||
collectible.isCurrentlyOwned && collectible.standard === ERC721,
|
||||
);
|
||||
this.setState({ sendableTokens, sendableCollectibles });
|
||||
}
|
||||
|
@ -64,10 +64,11 @@ export default class ExperimentalTab extends PureComponent {
|
||||
useCollectibleDetection,
|
||||
setUseCollectibleDetection,
|
||||
openSeaEnabled,
|
||||
setOpenSeaEnabled,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<div className="settings-page__content-row">
|
||||
<div className="settings-page__content-row--dependent">
|
||||
<div className="settings-page__content-item">
|
||||
<span>{t('useCollectibleDetection')}</span>
|
||||
<div className="settings-page__content-description">
|
||||
@ -77,7 +78,6 @@ export default class ExperimentalTab extends PureComponent {
|
||||
<div className="settings-page__content-item">
|
||||
<div className="settings-page__content-item-col">
|
||||
<ToggleButton
|
||||
disabled={!openSeaEnabled}
|
||||
value={useCollectibleDetection}
|
||||
onToggle={(value) => {
|
||||
this.context.metricsEvent({
|
||||
@ -87,6 +87,9 @@ export default class ExperimentalTab extends PureComponent {
|
||||
name: 'Collectible Detection',
|
||||
},
|
||||
});
|
||||
if (!value && !openSeaEnabled) {
|
||||
setOpenSeaEnabled(!value);
|
||||
}
|
||||
setUseCollectibleDetection(!value);
|
||||
}}
|
||||
offLabel={t('off')}
|
||||
@ -103,10 +106,15 @@ export default class ExperimentalTab extends PureComponent {
|
||||
return null;
|
||||
}
|
||||
const { t } = this.context;
|
||||
const { openSeaEnabled, setOpenSeaEnabled } = this.props;
|
||||
const {
|
||||
openSeaEnabled,
|
||||
setOpenSeaEnabled,
|
||||
useCollectibleDetection,
|
||||
setUseCollectibleDetection,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<div className="settings-page__content-row">
|
||||
<div className="settings-page__content-row--parent">
|
||||
<div className="settings-page__content-item">
|
||||
<span>{t('enableOpenSeaAPI')}</span>
|
||||
<div className="settings-page__content-description">
|
||||
@ -126,6 +134,9 @@ export default class ExperimentalTab extends PureComponent {
|
||||
},
|
||||
});
|
||||
setOpenSeaEnabled(!value);
|
||||
if (value && !useCollectibleDetection) {
|
||||
setUseCollectibleDetection(true);
|
||||
}
|
||||
}}
|
||||
offLabel={t('off')}
|
||||
onLabel={t('on')}
|
||||
|
@ -168,6 +168,15 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 10px 0 20px;
|
||||
|
||||
&--parent {
|
||||
padding: 10px 0 10px;
|
||||
}
|
||||
|
||||
&--dependent {
|
||||
margin-left: 48px;
|
||||
padding: 0 0 20px;
|
||||
}
|
||||
}
|
||||
|
||||
&__content-item {
|
||||
|
@ -1829,6 +1829,13 @@ export function hideAlert() {
|
||||
};
|
||||
}
|
||||
|
||||
export function updateCollectibleDropDownState(value) {
|
||||
return async (dispatch) => {
|
||||
await promisifiedBackground.updateCollectibleDropDownState(value);
|
||||
await forceUpdateMetamaskState(dispatch);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* This action will receive two types of values via qrCodeData
|
||||
* an object with the following structure {type, values}
|
||||
|
Loading…
Reference in New Issue
Block a user