1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-24 19:10:22 +01:00

Added IPFS toggle (#20172)

* added ipfs toggle

* added placeholder valur

* fixed snapshot

* updated tests

* updated spec file

* hide input if toggle is disabled

* updated e2e tests for nft image

* fixed view-ercc-1155 spec

* updated e2e tests for nfts

* added ipfs toggle modal

* updated UI for ipfs

* updated tests

* updated classname

* added placeholder image

* lint fix

* removed prop ipfsEnabled and used with selector

* fixed ui for ipfs toggle

* updated test

* updated  test to handle cases

* nit fix

* ensure default image height match nft image
This commit is contained in:
Nidhi Kumari 2023-08-01 23:51:51 +05:30 committed by GitHub
parent e27a9e4344
commit 4c37448c97
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 341 additions and 172 deletions

View File

@ -2054,6 +2054,16 @@
"invalidSeedPhraseCaseSensitive": {
"message": "Invalid input! Secret Recovery Phrase is case sensitive."
},
"ipfsToggleModalDescriptionOne": {
"message": "We use third-party services to show images of your NFTs stored on IPFS, display information related to ENS addresses entered in your browser's address bar, and fetch icons for different tokens. Your IP address may be exposed to these services when youre using them."
},
"ipfsToggleModalDescriptionTwo": {
"message": "Selecting Confirm turns on IPFS resolution. You can turn it off in $1 at any time.",
"description": "$1 is the method to turn off ipfs"
},
"ipfsToggleModalSettings": {
"message": "Settings > Security and privacy"
},
"jazzAndBlockies": {
"message": "Jazzicons and Blockies are two different styles of unique icons that help you identify an account at a glance."
},
@ -3821,6 +3831,9 @@
"showMore": {
"message": "Show more"
},
"showNft": {
"message": "Show NFT"
},
"showPermissions": {
"message": "Show permissions"
},

BIN
app/images/default_nft.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

View File

@ -93,7 +93,7 @@ describe('ERC721 NFTs testdapp interaction', function () {
await driver.clickElement({ text: 'NFTs', tag: 'button' });
await driver.findElement({ text: 'TestDappNFTs (3)' });
const nftsListItemsFirstCheck = await driver.findElements(
'.nft-item__item',
'.nft-item__container',
);
assert.equal(nftsListItemsFirstCheck.length, 3);
@ -119,7 +119,7 @@ describe('ERC721 NFTs testdapp interaction', function () {
await driver.clickElement({ text: 'NFTs', tag: 'button' });
await driver.findElement({ text: 'TestDappNFTs (6)' });
const nftsListItemsSecondCheck = await driver.findElements(
'.nft-item__item',
'.nft-item__container',
);
assert.equal(nftsListItemsSecondCheck.length, 6);
},
@ -215,7 +215,7 @@ describe('ERC721 NFTs testdapp interaction', function () {
await driver.clickElement({ text: 'NFTs', tag: 'button' });
await driver.findElement({ text: 'TestDappNFTs (5)' });
const nftsListItemsSecondCheck = await driver.findElements(
'.nft-item__item',
'.nft-item__container',
);
assert.equal(nftsListItemsSecondCheck.length, 5);

View File

@ -58,7 +58,7 @@ describe('Import ERC1155 NFT', function () {
assert.equal(await importedERC1155.isDisplayed(), true);
const importedERC1155Image = await driver.findVisibleElement(
'.nft-item__item',
'.nft-item__container',
);
assert.equal(await importedERC1155Image.isDisplayed(), true);
},

View File

@ -56,7 +56,7 @@ describe('Import NFT', function () {
text: 'TestDappNFTs',
});
const importedNftImage = await driver.findElement(
'.nft-item__item-image',
'.nft-item__container',
);
assert.equal(await importedNft.isDisplayed(), true);
assert.equal(await importedNftImage.isDisplayed(), true);

View File

@ -32,7 +32,7 @@ describe('Remove ERC1155 NFT', function () {
// Open the details page and click remove nft button
await driver.clickElement('[data-testid="home__nfts-tab"]');
const importedNftImage = await driver.findVisibleElement(
'.nft-item__item',
'.nft-item__container',
);
await importedNftImage.click();
await driver.clickElement('[data-testid="nft-options__button"]');

View File

@ -31,7 +31,7 @@ describe('Remove NFT', function () {
// Open the details and click remove nft button
await driver.clickElement('[data-testid="home__nfts-tab"]');
await driver.clickElement('.nft-item__item-image');
await driver.clickElement('.nft-item__container');
await driver.clickElement('[data-testid="nft-options__button"]');
await driver.clickElement('[data-testid="nft-item-remove"]');

View File

@ -31,7 +31,7 @@ describe('Send NFT', function () {
// Fill the send NFT form and confirm the transaction
await driver.clickElement('[data-testid="home__nfts-tab"]');
await driver.clickElement('.nft-item__item-image');
await driver.clickElement('.nft-item__container');
await driver.clickElement({ text: 'Send', tag: 'button' });
await driver.fill(
'input[placeholder="Enter public address (0x) or ENS name"]',

View File

@ -34,7 +34,7 @@ describe('View ERC1155 NFT details', function () {
// Click to open the NFT details page and check displayed account
await driver.clickElement('[data-testid="home__nfts-tab"]');
const importedNftImage = await driver.findVisibleElement(
'.nft-item__item',
'.nft-item__container',
);
await importedNftImage.click();
const detailsPageAccount = await driver.findElement(
@ -54,7 +54,7 @@ describe('View ERC1155 NFT details', function () {
'This is a collection of Rock NFTs.',
);
const nftImage = await driver.findElement('.nft-item__item-image');
const nftImage = await driver.findElement('.nft-item__container');
assert.equal(await nftImage.isDisplayed(), true);
const nftImageSource = await driver.findElement(

View File

@ -33,7 +33,7 @@ describe('View NFT details', function () {
// Click to open the NFT details page and check title
await driver.clickElement('[data-testid="home__nfts-tab"]');
await driver.clickElement('.nft-item__item-image');
await driver.clickElement('.nft-item__container');
const detailsPageTitle = await driver.findElement('.asset-breadcrumb');
assert.equal(
@ -53,7 +53,7 @@ describe('View NFT details', function () {
'Test Dapp NFTs for testing.',
);
const nftImage = await driver.findElement('.nft-item__item-image');
const nftImage = await driver.findElement('.nft-item__container');
assert.equal(await nftImage.isDisplayed(), true);
const nftImageSource = await driver.findElement(

View File

@ -3,58 +3,29 @@
exports[`NFT Default Image should match snapshot with all provided props 1`] = `
<div>
<div
class="box nft-default nft-default--clickable box--display-flex box--flex-direction-row box--width-full box--background-color-background-alternative box--rounded-lg"
class="mm-box nft-default mm-box--display-flex mm-box--rounded-lg"
data-testid="nft-default-image"
tabindex="0"
>
<h6
class="mm-box mm-text nft-default__text mm-text--body-sm mm-text--ellipsis mm-text--text-align-center mm-box--color-text-default"
>
NFT Name
<br />
#
123
</h6>
</div>
/>
</div>
`;
exports[`NFT Default Image should match snapshot with missing clickable prop 1`] = `
<div>
<div
class="box nft-default box--display-flex box--flex-direction-row box--width-full box--background-color-background-alternative box--rounded-lg"
class="mm-box nft-default mm-box--display-flex mm-box--rounded-lg"
data-testid="nft-default-image"
tabindex="0"
>
<h6
class="mm-box mm-text nft-default__text mm-text--body-sm mm-text--ellipsis mm-text--text-align-center mm-box--color-text-default"
>
NFT Name
<br />
#
123
</h6>
</div>
/>
</div>
`;
exports[`NFT Default Image should render with no props 1`] = `
<div>
<div
class="box nft-default box--display-flex box--flex-direction-row box--width-full box--background-color-background-alternative box--rounded-lg"
class="mm-box nft-default mm-box--display-flex mm-box--rounded-lg"
data-testid="nft-default-image"
tabindex="0"
>
<h6
class="mm-box mm-text nft-default__text mm-text--body-sm mm-text--ellipsis mm-text--text-align-center mm-box--color-text-default"
>
[unknownCollection]
<br />
#
</h6>
</div>
/>
</div>
`;

View File

@ -1,17 +1,12 @@
.nft-default {
padding-top: 100%;
position: relative;
background-image: url('/images/default_nft.png');
background-size: cover;
background-repeat: no-repeat;
&--clickable {
cursor: pointer;
}
&__text {
overflow: hidden;
&__button {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: calc(100% - 32px);
bottom: 16px;
}
}

View File

@ -1,27 +1,23 @@
import React from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import { useDispatch, useSelector } from 'react-redux';
import {
Display,
AlignItems,
BlockSize,
JustifyContent,
TextVariant,
BorderRadius,
TextAlign,
BackgroundColor,
} from '../../../helpers/constants/design-system';
import { useI18nContext } from '../../../hooks/useI18nContext';
import { Text } from '../../component-library';
import Box from '../../ui/box/box';
import { ButtonLink, Box } from '../../component-library';
import { showIpfsModal } from '../../../store/actions';
import { getIpfsGateway } from '../../../selectors';
export default function NftDefaultImage({
name,
tokenId,
className,
clickable = false,
}) {
export default function NftDefaultImage({ className, clickable }) {
const t = useI18nContext();
const dispatch = useDispatch();
const isIpfsEnabled = useSelector(getIpfsGateway);
return (
<Box
tabIndex={0}
@ -32,32 +28,25 @@ export default function NftDefaultImage({
display={Display.Flex}
alignItems={AlignItems.Center}
justifyContent={JustifyContent.Center}
backgroundColor={BackgroundColor.backgroundAlternative}
width={BlockSize.Full}
borderRadius={BorderRadius.LG}
>
<Text
variant={TextVariant.bodySm}
textAlign={TextAlign.Center}
ellipsis
as="h6"
className="nft-default__text"
>
{name ?? t('unknownCollection')} <br /> #{tokenId}
</Text>
{!isIpfsEnabled && (
<ButtonLink
block
className="nft-default__button"
onClick={(e) => {
e.stopPropagation();
dispatch(showIpfsModal());
}}
>
{t('show')}
</ButtonLink>
)}
</Box>
);
}
NftDefaultImage.propTypes = {
/**
* The name of the NFT collection if not supplied will default to "Unnamed collection"
*/
name: PropTypes.string,
/**
* The token id of the nft
*/
tokenId: PropTypes.string,
/**
* Controls the css class for the cursor hover
*/

View File

@ -5,21 +5,10 @@ export default {
title: 'Components/App/NftDefaultImage',
argTypes: {
name: {
control: 'text',
},
tokenId: {
control: 'text',
},
clickable: {
control: 'boolean',
},
},
args: {
name: null,
tokenId: '12345',
clickable: true,
},
};
export const DefaultStory = (args) => (

View File

@ -1,72 +1,54 @@
import React from 'react';
import configureStore from 'redux-mock-store';
import { renderWithProvider } from '../../../../test/lib/render-helpers';
import mockState from '../../../../test/data/mock-state.json';
import NftDefaultImage from '.';
describe('NFT Default Image', () => {
const mockShowIpfsModal = jest.fn();
jest.mock('../../../store/actions.ts', () => ({
showIpfsModal: () => mockShowIpfsModal,
}));
const store = configureStore()(mockState);
it('should render with no props', () => {
const { container } = renderWithProvider(<NftDefaultImage />);
const { container } = renderWithProvider(<NftDefaultImage />, store);
expect(container).toMatchSnapshot();
});
it('should match snapshot with all provided props', () => {
const props = {
name: 'NFT Name',
tokenId: '123',
clickable: true,
clickable: false,
};
const { container } = renderWithProvider(<NftDefaultImage {...props} />);
const { container } = renderWithProvider(
<NftDefaultImage {...props} />,
store,
);
expect(container).toMatchSnapshot();
});
it('should match snapshot with missing clickable prop', () => {
const props = {
name: 'NFT Name',
tokenId: '123',
};
const { container } = renderWithProvider(<NftDefaultImage {...props} />);
const { container } = renderWithProvider(<NftDefaultImage />, store);
expect(container).toMatchSnapshot();
});
it('should render NFT name', () => {
const props = {
name: 'NFT Name',
};
const { queryByText } = renderWithProvider(<NftDefaultImage {...props} />);
const nftElement = queryByText(`${props.name} #`);
expect(nftElement).toBeInTheDocument();
});
it('should render NFT name and tokenId', () => {
const props = {
name: 'NFT Name',
tokenId: '123',
};
const { queryByText } = renderWithProvider(<NftDefaultImage {...props} />);
const nftElement = queryByText(`${props.name} #${props.tokenId}`);
expect(nftElement).toBeInTheDocument();
});
it('does not render component with clickable class when clickable is false', () => {
const { container } = renderWithProvider(
<NftDefaultImage name="NFT Name" tokenId="123" clickable={false} />,
<NftDefaultImage clickable={false} />,
store,
);
expect(container.firstChild).not.toHaveClass('nft-default--clickable');
});
it('renders component with clickable class when clickable is true', () => {
const { container } = renderWithProvider(
<NftDefaultImage name="NFT Name" tokenId="123" clickable />,
<NftDefaultImage clickable />,
store,
);
expect(container.firstChild).toHaveClass('nft-default--clickable');
});

View File

@ -0,0 +1,70 @@
import React from 'react';
import PropTypes from 'prop-types';
import { useDispatch } from 'react-redux';
import {
Box,
ButtonPrimary,
Modal,
ModalContent,
ModalHeader,
ModalOverlay,
Text,
} from '../../component-library';
import { useI18nContext } from '../../../hooks/useI18nContext';
import { hideIpfsModal, setIpfsGateway } from '../../../store/actions';
import { IPFS_DEFAULT_GATEWAY_URL } from '../../../../shared/constants/network';
import {
Size,
TextColor,
TextVariant,
} from '../../../helpers/constants/design-system';
export const ToggleIpfsModal = ({ onClose }) => {
const t = useI18nContext();
const dispatch = useDispatch();
return (
<Modal isOpen onClose={onClose} className="toggle-ipfs-modal">
<ModalOverlay />
<ModalContent>
<ModalHeader onClose={onClose}>{t('showNft')}</ModalHeader>
<Box className="toggle-ipfs-modal" marginTop={6}>
<Text variant={TextVariant.bodyMd} color={TextColor.textAlternative}>
{t('ipfsToggleModalDescriptionOne')}
</Text>
<Text
variant={TextVariant.bodyMd}
color={TextColor.textAlternative}
marginTop={6}
>
{t('ipfsToggleModalDescriptionTwo', [
<Text
variant={TextVariant.bodyMdBold}
color={TextColor.textAlternative}
as="span"
key="span"
>
{t('ipfsToggleModalSettings')},
</Text>,
])}
</Text>
</Box>
<ButtonPrimary
block
marginTop={9}
onClick={() => {
dispatch(setIpfsGateway(IPFS_DEFAULT_GATEWAY_URL));
dispatch(hideIpfsModal());
}}
size={Size.LG}
>
{t('confirm')}
</ButtonPrimary>
</ModalContent>
</Modal>
);
};
ToggleIpfsModal.propTypes = {
onClose: PropTypes.func.isRequired,
};

View File

@ -56,7 +56,7 @@ exports[`NFT Details should match minimal props and state snapshot 1`] = `
alt="MUNK #1 1"
class="mm-box nft-item__item nft-item__item-image mm-box--display-block mm-box--justify-content-center"
data-testid="nft-image"
src="https://bafybeiclzx7zfjvuiuwobn5ip3ogc236bjqfjzoblumf4pau4ep6dqramu.ipfs.dweb.link"
src="ipfs://QmTSZUNt8AKyDabkyXXXP4oHWDnaVXgNdXoJGEyaYzLbeL"
/>
<div
class="mm-box mm-badge-wrapper__badge-container"
@ -73,7 +73,7 @@ exports[`NFT Details should match minimal props and state snapshot 1`] = `
</button>
</div>
<div
class="box nft-details__info box--flex-direction-column box--justify-content-space-between box--display-flex"
class="box nft-details__info box--margin-top-4 box--flex-direction-column box--justify-content-space-between box--display-flex"
>
<div>
<h4

View File

@ -188,7 +188,8 @@ export default function NftDetails({ nft }) {
>
<Box className="nft-details__nft-item">
<NftItem
src={image ? nftImageURL : ''}
nftImageURL={nftImageURL}
src={image}
alt={image ? nftImageAlt : ''}
name={name}
tokenId={tokenId}
@ -199,6 +200,7 @@ export default function NftDetails({ nft }) {
<Box
flexDirection={FlexDirection.Column}
className="nft-details__info"
marginTop={4}
justifyContent={JustifyContent.spaceBetween}
>
<div>

View File

@ -52,6 +52,7 @@ export default function NftsItems({
const chainId = useSelector(getCurrentChainId);
const currentChain = useSelector(getCurrentNetwork);
const t = useI18nContext();
const ipfsGateway = useSelector(getIpfsGateway);
useEffect(() => {
if (
@ -86,7 +87,6 @@ export default function NftsItems({
dispatch,
]);
const ipfsGateway = useSelector(getIpfsGateway);
const history = useHistory();
const renderCollectionImage = (collectionImage, collectionName) => {
@ -169,9 +169,13 @@ export default function NftsItems({
{isExpanded ? (
<Box display={DISPLAY.FLEX} flexWrap={FLEX_WRAP.WRAP} gap={4}>
{nfts.map((nft, i) => {
const { image, address, tokenId, name } = nft;
const nftImage = getAssetImageURL(image, ipfsGateway);
const { image, address, tokenId, name, imageOriginal } = nft;
const nftImage = getAssetImageURL(imageOriginal, ipfsGateway);
const nftImageAlt = getNftImageAlt(nft);
const nftImageURL = imageOriginal?.includes('ipfs')
? nftImage
: image;
const handleImageClick = () =>
history.push(`${ASSET_ROUTE}/${address}/${tokenId}`);
return (
@ -182,8 +186,9 @@ export default function NftsItems({
className="nfts-items__item-wrapper"
>
<NftItem
src={nftImage}
nftImageURL={nftImageURL}
alt={nftImageAlt}
src={image}
name={name}
tokenId={tokenId}
networkName={currentChain.nickname}

View File

@ -80,8 +80,12 @@ describe('NFTs Item Component', () => {
);
const nftImages = queryAllByTestId('nft-image');
fireEvent.click(nftImages[0]);
const nftDefaultImages = queryAllByTestId('nft-default-image');
if (nftImages.length) {
fireEvent.click(nftImages[0]);
} else {
fireEvent.click(nftDefaultImages[0]);
}
const firstNft = nfts[0];
const nftRoute = `/asset/${firstNft.address}/${firstNft.tokenId}`;

View File

@ -15,7 +15,10 @@ import {
Display,
JustifyContent,
} from '../../../helpers/constants/design-system';
import { getTestNetworkBackgroundColor } from '../../../selectors';
import {
getIpfsGateway,
getTestNetworkBackgroundColor,
} from '../../../selectors';
export const NftItem = ({
alt,
@ -25,9 +28,12 @@ export const NftItem = ({
networkSrc,
tokenId,
onClick,
clickable = false,
clickable,
nftImageURL,
}) => {
const testNetworkBackgroundColor = useSelector(getTestNetworkBackgroundColor);
const isIpfsEnabled = useSelector(getIpfsGateway);
const isIpfsURL = nftImageURL?.includes('ipfs:');
return (
<Box
className="nft-item__container"
@ -58,7 +64,7 @@ export const NftItem = ({
/>
}
>
{src ? (
{isIpfsEnabled ? (
<Box
className="nft-item__item nft-item__item-image"
data-testid="nft-image"
@ -69,13 +75,27 @@ export const NftItem = ({
justifyContent={JustifyContent.center}
/>
) : (
<NftDefaultImage
className="nft-item__default-image"
data-testid="nft-default-image"
name={name}
tokenId={tokenId}
clickable={clickable}
/>
<>
{isIpfsURL ? (
<NftDefaultImage
className="nft-item__default-image"
data-testid="nft-default-image"
name={name}
tokenId={tokenId}
clickable={clickable}
/>
) : (
<Box
className="nft-item__item nft-item__item-image"
data-testid="nft-image"
as="img"
src={src}
alt={alt}
display={Display.Block}
justifyContent={JustifyContent.center}
/>
)}
</>
)}
</BadgeWrapper>
</Box>
@ -91,4 +111,5 @@ NftItem.propTypes = {
tokenId: PropTypes.string.isRequired,
onClick: PropTypes.func,
clickable: PropTypes.bool,
nftImageURL: PropTypes.string,
};

View File

@ -1,13 +1,28 @@
import React from 'react';
import configureStore from 'redux-mock-store';
import { fireEvent } from '@testing-library/react';
import configureStore from '../../../store/store';
import '@testing-library/jest-dom/extend-expect';
import mockState from '../../../../test/data/mock-state.json';
import { renderWithProvider } from '../../../../test/lib/render-helpers';
import { NftItem } from '.';
const store = configureStore({
metamask: {
...mockState.metamask,
},
});
const noIpfsStore = configureStore({
metamask: {
...mockState.metamask,
ipfsGateway: '',
},
});
describe('NftItem component', () => {
const store = configureStore()(mockState);
jest.mock('../../../store/actions.ts', () => ({
getTokenStandardAndDetails: jest.fn().mockResolvedValue(),
}));
describe('render', () => {
const props = {
alt: 'Test Alt',
@ -18,6 +33,7 @@ describe('NftItem component', () => {
networkSrc: 'test-network-src',
tokenId: '1',
onClick: jest.fn(),
nftImageURL: '',
};
it('renders correctly with an image source', () => {
@ -29,10 +45,13 @@ describe('NftItem component', () => {
expect(getByTestId('nft-image')).toHaveAttribute('src', 'test-src');
});
it('renders correctly with default image when no image source is provided', () => {
it('renders correctly with default image when no ipfs is off and no image is provided', () => {
const { getByTestId, queryByTestId } = renderWithProvider(
<NftItem {...props} src="" />,
store,
<NftItem
{...props}
nftImageURL="ipfs://QmTSZUNt8AKyDabkyXXXP4oHWDnaVXgNdXoJGEyaYzLbeL"
/>,
noIpfsStore,
);
expect(queryByTestId('nft-image')).not.toBeInTheDocument();

View File

@ -27,6 +27,7 @@ interface AppState {
} | null;
networkDropdownOpen: boolean;
importNftsModalOpen: boolean;
showIpfsModalOpen: boolean;
accountDetail: {
subview?: string;
accountExport?: string;
@ -96,6 +97,7 @@ const initialState: AppState = {
qrCodeData: null,
networkDropdownOpen: false,
importNftsModalOpen: false,
showIpfsModalOpen: false,
accountDetail: {
privateKey: '',
},
@ -176,6 +178,19 @@ export default function reduceApp(
...appState,
importNftsModalOpen: false,
};
case actionConstants.SHOW_IPFS_MODAL_OPEN:
return {
...appState,
showIpfsModalOpen: true,
};
case actionConstants.SHOW_IPFS_MODAL_CLOSE:
return {
...appState,
showIpfsModalOpen: false,
};
// alert methods
case actionConstants.ALERT_OPEN:
return {

View File

@ -117,6 +117,7 @@ import DeprecatedTestNetworks from '../../components/ui/deprecated-test-networks
import NewNetworkInfo from '../../components/ui/new-network-info/new-network-info';
import { ThemeType } from '../../../shared/constants/preferences';
import { Box } from '../../components/component-library';
import { ToggleIpfsModal } from '../../components/app/nft-default-image/toggle-ipfs-modal';
export default class Routes extends Component {
static propTypes = {
@ -159,6 +160,8 @@ export default class Routes extends Component {
accountDetailsAddress: PropTypes.string,
isImportNftsModalOpen: PropTypes.bool.isRequired,
hideImportNftsModal: PropTypes.func.isRequired,
isIpfsModalOpen: PropTypes.bool.isRequired,
hideIpfsModal: PropTypes.func.isRequired,
};
static contextTypes = {
@ -515,6 +518,8 @@ export default class Routes extends Component {
location,
isImportNftsModalOpen,
hideImportNftsModal,
isIpfsModalOpen,
hideIpfsModal,
} = this.props;
const loadMessage =
loadingMessage || isNetworkLoading
@ -576,6 +581,9 @@ export default class Routes extends Component {
{isImportNftsModalOpen ? (
<ImportNftsModal onClose={() => hideImportNftsModal()} />
) : null}
{isIpfsModalOpen ? (
<ToggleIpfsModal onClose={() => hideIpfsModal()} />
) : null}
<Box className="main-container-wrapper">
{isLoading ? <Loading loadingMessage={loadMessage} /> : null}
{!isLoading && isNetworkLoading ? <LoadingNetwork /> : null}

View File

@ -16,6 +16,7 @@ import {
import {
lockMetamask,
hideImportNftsModal,
hideIpfsModal,
setCurrentCurrency,
setLastActiveTime,
setMouseUserState,
@ -65,6 +66,7 @@ function mapStateToProps(state) {
isNetworkMenuOpen: state.metamask.isNetworkMenuOpen,
accountDetailsAddress: state.appState.accountDetailsAddress,
isImportNftsModalOpen: state.appState.importNftsModalOpen,
isIpfsModalOpen: state.appState.showIpfsModalOpen,
};
}
@ -80,6 +82,7 @@ function mapDispatchToProps(dispatch) {
toggleAccountMenu: () => dispatch(toggleAccountMenu()),
toggleNetworkMenu: () => dispatch(toggleNetworkMenu()),
hideImportNftsModal: () => dispatch(hideImportNftsModal()),
hideIpfsModal: () => dispatch(hideIpfsModal()),
};
}

View File

@ -381,6 +381,55 @@ exports[`Security Tab should match snapshot 1`] = `
The IPFS gateway makes it possible to access and view data hosted by third parties. You can add a custom IPFS gateway or continue using the default.
</div>
</div>
<div
class="settings-page__content-item"
>
<label
class="toggle-button toggle-button--on"
tabindex="0"
>
<div
style="display: flex; width: 52px; align-items: center; justify-content: flex-start; position: relative; cursor: pointer; background-color: transparent; border: 0px; padding: 0px; user-select: none;"
>
<div
style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(242, 244, 246);"
>
<div
style="font-size: 11px; display: flex; align-items: center; justify-content: center; font-family: 'Helvetica Neue', Helvetica, sans-serif; position: relative; color: rgb(250, 250, 250); margin-top: auto; margin-bottom: auto; line-height: 0; opacity: 1; width: 26px; height: 20px; left: 4px;"
/>
<div
style="font-size: 11px; display: flex; align-items: center; justify-content: center; font-family: 'Helvetica Neue', Helvetica, sans-serif; position: relative; color: rgba(255, 255, 255, 0.6); bottom: 0px; margin-top: auto; margin-bottom: auto; padding-right: 5px; line-height: 0; width: 26px; height: 20px; opacity: 0;"
/>
</div>
<div
style="position: absolute; height: 100%; top: 0px; left: 0px; display: flex; flex: 1; align-self: stretch; align-items: center; justify-content: flex-start;"
>
<div
style="width: 18px; height: 18px; display: flex; align-self: center; box-shadow: none; border-radius: 50%; box-sizing: border-box; position: relative; background-color: rgb(3, 125, 214); left: 18px;"
/>
</div>
<input
style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: absolute; width: 1px;"
type="checkbox"
value="dweb.link"
/>
</div>
<div
class="toggle-button__status"
>
<span
class="toggle-button__label-off"
>
Off
</span>
<span
class="toggle-button__label-on"
>
On
</span>
</div>
</label>
</div>
<div
class="settings-page__content-item"
>

View File

@ -11,6 +11,7 @@ import {
MetaMetricsEventKeyType,
MetaMetricsEventName,
} from '../../../../shared/constants/metametrics';
import { IPFS_DEFAULT_GATEWAY_URL } from '../../../../shared/constants/network';
import {
AUTO_DETECT_TOKEN_LEARN_MORE_LINK,
COINGECKO_LINK,
@ -68,6 +69,7 @@ export default class SecurityTab extends PureComponent {
ipfsGateway: this.props.ipfsGateway,
ipfsGatewayError: '',
srpQuizModalVisible: false,
ipfsToggle: false,
};
settingsRefCounter = 0;
@ -320,7 +322,7 @@ export default class SecurityTab extends PureComponent {
this.props;
const handleIpfsGatewaySave = (gateway) => {
const url = new URL(addUrlProtocolPrefix(gateway));
const url = gateway ? new URL(addUrlProtocolPrefix(gateway)) : '';
const { host } = url;
this.props.setIpfsGateway(host);
@ -355,6 +357,11 @@ export default class SecurityTab extends PureComponent {
});
};
const handleIpfsToggle = (url) => {
url?.length < 1
? handleIpfsGatewayChange(IPFS_DEFAULT_GATEWAY_URL)
: handleIpfsGatewayChange('');
};
return (
<div
ref={this.settingsRefs[5]}
@ -368,17 +375,31 @@ export default class SecurityTab extends PureComponent {
</div>
</div>
<div className="settings-page__content-item">
<div className="settings-page__content-item-col">
<TextField
type="text"
value={this.state.ipfsGateway}
onChange={(e) => handleIpfsGatewayChange(e.target.value)}
error={ipfsGatewayError}
fullWidth
margin="dense"
/>
</div>
<ToggleButton
value={this.state.ipfsGateway}
onToggle={(value) => {
handleIpfsToggle(value);
this.setState({ ipfsToggle: Boolean(value) });
}}
offLabel={t('off')}
onLabel={t('on')}
/>
</div>
{!this.state.ipfsToggle && (
<div className="settings-page__content-item">
<div className="settings-page__content-item-col">
<TextField
type="text"
disabled={!this.state.ipfsGateway}
value={this.state.ipfsGateway}
onChange={(e) => handleIpfsGatewayChange(e.target.value)}
error={ipfsGatewayError}
fullWidth
margin="dense"
/>
</div>
</div>
)}
<div
className="settings-page__content-item"
ref={this.settingsRefs[9]}

View File

@ -11,6 +11,8 @@ export const NETWORK_DROPDOWN_OPEN = 'UI_NETWORK_DROPDOWN_OPEN';
export const NETWORK_DROPDOWN_CLOSE = 'UI_NETWORK_DROPDOWN_CLOSE';
export const IMPORT_NFTS_MODAL_OPEN = 'UI_IMPORT_NFTS_MODAL_OPEN';
export const IMPORT_NFTS_MODAL_CLOSE = 'UI_IMPORT_NFTS_MODAL_CLOSE';
export const SHOW_IPFS_MODAL_OPEN = 'UI_IPFS_MODAL_OPEN';
export const SHOW_IPFS_MODAL_CLOSE = 'UI_IPFS_MODAL_CLOSE';
// remote state
export const UPDATE_METAMASK_STATE = 'UPDATE_METAMASK_STATE';
export const SELECTED_ADDRESS_CHANGED = 'SELECTED_ADDRESS_CHANGED';

View File

@ -2427,6 +2427,17 @@ export function hideImportNftsModal(): Action {
};
}
export function showIpfsModal(): Action {
return {
type: actionConstants.SHOW_IPFS_MODAL_OPEN,
};
}
export function hideIpfsModal(): Action {
return {
type: actionConstants.SHOW_IPFS_MODAL_CLOSE,
};
}
export function closeCurrentNotificationWindow(): ThunkAction<
void,
MetaMaskReduxState,