mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-22 09:23:21 +01:00
[FLASK] Rework Snaps headers and footers (#19442)
* Add new snap header and footer to snap install * Add new snap header and footer to snap result and snap update * Fix loading state * Fix lint * Add required scrolling * Adjust avatar component * Apply new headers and footers to snaps confirmations * Rename previous SnapAuthorship component to SnapAuthorshipExpanded * Fix lint * Fix font weight * Fix fencing * Fix a test * Fix lint after rebase * Fix E2E * Fix locale lint * Fix another E2E * Fix test ID * Address PR comments * Better scroll button centering * Address design comments * Fix unit test * Fix E2Es
This commit is contained in:
parent
546d8349e7
commit
789779f4d5
7
app/_locales/en/messages.json
generated
7
app/_locales/en/messages.json
generated
@ -1371,6 +1371,9 @@
|
||||
"message": "enable $1",
|
||||
"description": "$1 is a token symbol, e.g. ETH"
|
||||
},
|
||||
"enabled": {
|
||||
"message": "Enabled"
|
||||
},
|
||||
"encryptionPublicKeyNotice": {
|
||||
"message": "$1 would like your public encryption key. By consenting, this site will be able to compose encrypted messages to you.",
|
||||
"description": "$1 is the web3 site name"
|
||||
@ -3777,10 +3780,6 @@
|
||||
"message": "Install snap"
|
||||
},
|
||||
"snapInstallRequest": {
|
||||
"message": "$1 wants to install $2. Make sure you trust the authors before you proceed.",
|
||||
"description": "$1 is the dApp origin requesting the snap and $2 is the snap name"
|
||||
},
|
||||
"snapInstallRequestsPermission": {
|
||||
"message": "Installing $1 gives it the following permissions. Only continue if you trust $1.",
|
||||
"description": "$1 is the snap name."
|
||||
},
|
||||
|
@ -165,7 +165,13 @@
|
||||
}
|
||||
},
|
||||
"frequentRpcListDetail": [],
|
||||
"subjectMetadata": {},
|
||||
"subjectMetadata": {
|
||||
"npm:@metamask/test-snap-bip44": {
|
||||
"name": "@metamask/test-snap-bip44",
|
||||
"version": "1.2.3",
|
||||
"subjectType": "snap"
|
||||
}
|
||||
},
|
||||
"notifications": {
|
||||
"test": {
|
||||
"id": "test",
|
||||
|
@ -54,6 +54,8 @@ describe('Test Snap bip-32', function () {
|
||||
|
||||
await driver.waitForSelector({ text: 'Install' });
|
||||
|
||||
await driver.clickElement('[data-testid="snap-install-scroll"]');
|
||||
|
||||
await driver.clickElement({
|
||||
text: 'Install',
|
||||
tag: 'button',
|
||||
|
@ -90,7 +90,7 @@ describe('Test Snap Cronjob', function () {
|
||||
|
||||
// try to click on the Ok button and pass test if it works
|
||||
await driver.clickElement({
|
||||
text: 'Ok',
|
||||
text: 'OK',
|
||||
tag: 'button',
|
||||
});
|
||||
},
|
||||
|
@ -95,7 +95,7 @@ describe('Test Snap Dialog', function () {
|
||||
|
||||
// click ok button
|
||||
await driver.clickElement({
|
||||
text: 'Ok',
|
||||
text: 'OK',
|
||||
tag: 'button',
|
||||
});
|
||||
|
||||
|
@ -97,7 +97,7 @@ describe('Test Snap networkAccess', function () {
|
||||
|
||||
// click ok button
|
||||
await driver.clickElement({
|
||||
text: 'Ok',
|
||||
text: 'OK',
|
||||
tag: 'button',
|
||||
});
|
||||
},
|
||||
|
@ -55,6 +55,8 @@ describe('Test Snap RPC', function () {
|
||||
|
||||
await driver.waitForSelector({ text: 'Install' });
|
||||
|
||||
await driver.clickElement('[data-testid="snap-install-scroll"]');
|
||||
|
||||
await driver.clickElement({
|
||||
text: 'Install',
|
||||
tag: 'button',
|
||||
|
@ -55,6 +55,8 @@ describe('Test Snap update', function () {
|
||||
|
||||
await driver.waitForSelector({ text: 'Install' });
|
||||
|
||||
await driver.clickElement('[data-testid="snap-install-scroll"]');
|
||||
|
||||
await driver.clickElement({
|
||||
text: 'Install',
|
||||
tag: 'button',
|
||||
@ -105,6 +107,8 @@ describe('Test Snap update', function () {
|
||||
|
||||
await driver.waitForSelector({ text: 'Update' });
|
||||
|
||||
await driver.clickElement('[data-testid="snap-update-scroll"]');
|
||||
|
||||
await driver.clickElement({
|
||||
text: 'Update',
|
||||
tag: 'button',
|
||||
|
@ -7,17 +7,8 @@ import {
|
||||
FLEX_DIRECTION,
|
||||
JustifyContent,
|
||||
} from '../../../helpers/constants/design-system';
|
||||
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
||||
import SnapAuthorship from '../snaps/snap-authorship';
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
|
||||
export default class PermissionsConnectHeader extends Component {
|
||||
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
||||
static contextTypes = {
|
||||
t: PropTypes.func,
|
||||
};
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
|
||||
static propTypes = {
|
||||
className: PropTypes.string,
|
||||
iconUrl: PropTypes.string,
|
||||
@ -28,10 +19,6 @@ export default class PermissionsConnectHeader extends Component {
|
||||
headerText: PropTypes.string,
|
||||
leftIcon: PropTypes.node,
|
||||
rightIcon: PropTypes.node,
|
||||
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
||||
snapVersion: PropTypes.string,
|
||||
isSnapInstallOrUpdate: PropTypes.bool,
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
@ -42,22 +29,7 @@ export default class PermissionsConnectHeader extends Component {
|
||||
};
|
||||
|
||||
renderHeaderIcon() {
|
||||
const {
|
||||
iconUrl,
|
||||
iconName,
|
||||
siteOrigin,
|
||||
leftIcon,
|
||||
rightIcon,
|
||||
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
||||
isSnapInstallOrUpdate,
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
} = this.props;
|
||||
|
||||
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
||||
if (isSnapInstallOrUpdate) {
|
||||
return null;
|
||||
}
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
const { iconUrl, iconName, siteOrigin, leftIcon, rightIcon } = this.props;
|
||||
|
||||
return (
|
||||
<div className="permissions-connect-header__icon">
|
||||
@ -75,16 +47,7 @@ export default class PermissionsConnectHeader extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
boxProps,
|
||||
className,
|
||||
headerTitle,
|
||||
headerText,
|
||||
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
||||
siteOrigin,
|
||||
isSnapInstallOrUpdate,
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
} = this.props;
|
||||
const { boxProps, className, headerTitle, headerText } = this.props;
|
||||
return (
|
||||
<Box
|
||||
className={classnames('permissions-connect-header', className)}
|
||||
@ -94,11 +57,6 @@ export default class PermissionsConnectHeader extends Component {
|
||||
>
|
||||
{this.renderHeaderIcon()}
|
||||
<div className="permissions-connect-header__title">{headerTitle}</div>
|
||||
{
|
||||
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
||||
isSnapInstallOrUpdate && <SnapAuthorship snapId={siteOrigin} />
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
}
|
||||
<div className="permissions-connect-header__subtitle">{headerText}</div>
|
||||
</Box>
|
||||
);
|
||||
|
@ -0,0 +1 @@
|
||||
export { default } from './snap-authorship-expanded';
|
@ -17,6 +17,7 @@ import {
|
||||
BorderStyle,
|
||||
Color,
|
||||
BorderRadius,
|
||||
FontWeight,
|
||||
} from '../../../../helpers/constants/design-system';
|
||||
import {
|
||||
formatDate,
|
||||
@ -34,7 +35,7 @@ import { disableSnap, enableSnap } from '../../../../store/actions';
|
||||
import { useOriginMetadata } from '../../../../hooks/useOriginMetadata';
|
||||
import SnapVersion from '../snap-version/snap-version';
|
||||
|
||||
const SnapAuthorship = ({ snapId, className, expanded = false, snap }) => {
|
||||
const SnapAuthorshipExpanded = ({ snapId, className, snap }) => {
|
||||
const t = useI18nContext();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
@ -55,7 +56,6 @@ const SnapAuthorship = ({ snapId, className, expanded = false, snap }) => {
|
||||
|
||||
const friendlyName = snapId && getSnapName(snapId, subjectMetadata);
|
||||
|
||||
// Expanded data
|
||||
const versionHistory = snap?.versionHistory ?? [];
|
||||
const installInfo = versionHistory.length
|
||||
? versionHistory[versionHistory.length - 1]
|
||||
@ -72,33 +72,35 @@ const SnapAuthorship = ({ snapId, className, expanded = false, snap }) => {
|
||||
|
||||
return (
|
||||
<Box
|
||||
className={classnames('snaps-authorship', className)}
|
||||
className={classnames('snaps-authorship-expanded', className)}
|
||||
backgroundColor={BackgroundColor.backgroundDefault}
|
||||
borderColor={BorderColor.borderDefault}
|
||||
borderWidth={1}
|
||||
width={BLOCK_SIZES.FULL}
|
||||
borderRadius={expanded ? BorderRadius.LG : BorderRadius.pill}
|
||||
borderRadius={BorderRadius.LG}
|
||||
>
|
||||
<Box
|
||||
alignItems={AlignItems.center}
|
||||
display={DISPLAY.FLEX}
|
||||
width={BLOCK_SIZES.FULL}
|
||||
paddingLeft={expanded ? 4 : 2}
|
||||
paddingRight={expanded ? 4 : 2}
|
||||
paddingTop={expanded ? 3 : 1}
|
||||
paddingBottom={expanded ? 3 : 1}
|
||||
paddingLeft={4}
|
||||
paddingRight={4}
|
||||
paddingTop={3}
|
||||
paddingBottom={3}
|
||||
>
|
||||
<Box>
|
||||
<SnapAvatar snapId={snapId} />
|
||||
</Box>
|
||||
<Box
|
||||
marginLeft={2}
|
||||
marginRight={expanded ? 0 : 2}
|
||||
marginLeft={4}
|
||||
marginRight={0}
|
||||
display={DISPLAY.FLEX}
|
||||
flexDirection={FLEX_DIRECTION.COLUMN}
|
||||
style={{ overflow: 'hidden' }}
|
||||
>
|
||||
<Text ellipsis>{friendlyName}</Text>
|
||||
<Text ellipsis fontWeight={FontWeight.Medium}>
|
||||
{friendlyName}
|
||||
</Text>
|
||||
<Text
|
||||
ellipsis
|
||||
variant={TextVariant.bodySm}
|
||||
@ -107,80 +109,77 @@ const SnapAuthorship = ({ snapId, className, expanded = false, snap }) => {
|
||||
{packageName}
|
||||
</Text>
|
||||
</Box>
|
||||
{!expanded && (
|
||||
<Box marginLeft="auto">
|
||||
<SnapVersion version={subjectMetadata?.version} url={url} />
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
{expanded && (
|
||||
<Box flexDirection={FLEX_DIRECTION.COLUMN} width={BLOCK_SIZES.FULL}>
|
||||
<Box
|
||||
flexDirection={FLEX_DIRECTION.ROW}
|
||||
justifyContent={JustifyContent.spaceBetween}
|
||||
paddingLeft={4}
|
||||
paddingTop={4}
|
||||
paddingBottom={4}
|
||||
borderColor={BorderColor.borderDefault}
|
||||
width={BLOCK_SIZES.FULL}
|
||||
style={{
|
||||
borderLeft: BorderStyle.none,
|
||||
borderRight: BorderStyle.none,
|
||||
}}
|
||||
>
|
||||
<Text variant={TextVariant.bodyMdBold}>{t('enableSnap')}</Text>
|
||||
<Box style={{ maxWidth: '52px' }}>
|
||||
<Tooltip interactive position="left" html={t('snapsToggle')}>
|
||||
<ToggleButton value={snap?.enabled} onToggle={onToggle} />
|
||||
</Tooltip>
|
||||
</Box>
|
||||
<Box flexDirection={FLEX_DIRECTION.COLUMN} width={BLOCK_SIZES.FULL}>
|
||||
<Box
|
||||
flexDirection={FLEX_DIRECTION.ROW}
|
||||
justifyContent={JustifyContent.spaceBetween}
|
||||
paddingLeft={4}
|
||||
paddingTop={4}
|
||||
paddingBottom={4}
|
||||
borderColor={BorderColor.borderDefault}
|
||||
width={BLOCK_SIZES.FULL}
|
||||
style={{
|
||||
borderLeft: BorderStyle.none,
|
||||
borderRight: BorderStyle.none,
|
||||
}}
|
||||
>
|
||||
<Text variant={TextVariant.bodyMd} fontWeight={FontWeight.Medium}>
|
||||
{t('enabled')}
|
||||
</Text>
|
||||
<Box style={{ maxWidth: '52px' }}>
|
||||
<Tooltip interactive position="left" html={t('snapsToggle')}>
|
||||
<ToggleButton value={snap?.enabled} onToggle={onToggle} />
|
||||
</Tooltip>
|
||||
</Box>
|
||||
<Box
|
||||
flexDirection={FLEX_DIRECTION.COLUMN}
|
||||
padding={4}
|
||||
width={BLOCK_SIZES.FULL}
|
||||
>
|
||||
{installOrigin && installInfo && (
|
||||
<Box
|
||||
flexDirection={FLEX_DIRECTION.ROW}
|
||||
justifyContent={JustifyContent.spaceBetween}
|
||||
width={BLOCK_SIZES.FULL}
|
||||
>
|
||||
<Text variant={TextVariant.bodyMdBold}>
|
||||
{t('installOrigin')}
|
||||
</Text>
|
||||
<Box
|
||||
flexDirection={FLEX_DIRECTION.COLUMN}
|
||||
alignItems={AlignItems.flexEnd}
|
||||
>
|
||||
<ButtonLink href={installOrigin.origin} target="_blank">
|
||||
{installOrigin.host}
|
||||
</ButtonLink>
|
||||
<Text color={Color.textMuted}>
|
||||
{t('installedOn', [
|
||||
formatDate(installInfo.date, 'dd MMM yyyy'),
|
||||
])}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
<Box
|
||||
flexDirection={FLEX_DIRECTION.COLUMN}
|
||||
padding={4}
|
||||
width={BLOCK_SIZES.FULL}
|
||||
>
|
||||
{installOrigin && installInfo && (
|
||||
<Box
|
||||
flexDirection={FLEX_DIRECTION.ROW}
|
||||
justifyContent={JustifyContent.spaceBetween}
|
||||
alignItems={AlignItems.center}
|
||||
marginTop={4}
|
||||
width={BLOCK_SIZES.FULL}
|
||||
>
|
||||
<Text variant={TextVariant.bodyMdBold}>{t('version')}</Text>
|
||||
<SnapVersion version={snap?.version} url={url} />
|
||||
<Text variant={TextVariant.bodyMd} fontWeight={FontWeight.Medium}>
|
||||
{t('installOrigin')}
|
||||
</Text>
|
||||
<Box
|
||||
flexDirection={FLEX_DIRECTION.COLUMN}
|
||||
alignItems={AlignItems.flexEnd}
|
||||
>
|
||||
<ButtonLink href={installOrigin.origin} target="_blank">
|
||||
{installOrigin.host}
|
||||
</ButtonLink>
|
||||
<Text color={Color.textMuted}>
|
||||
{t('installedOn', [
|
||||
formatDate(installInfo.date, 'dd MMM yyyy'),
|
||||
])}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
<Box
|
||||
flexDirection={FLEX_DIRECTION.ROW}
|
||||
justifyContent={JustifyContent.spaceBetween}
|
||||
alignItems={AlignItems.center}
|
||||
marginTop={4}
|
||||
>
|
||||
<Text variant={TextVariant.bodyMd} fontWeight={FontWeight.Medium}>
|
||||
{t('version')}
|
||||
</Text>
|
||||
<SnapVersion version={snap?.version} url={url} />
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
SnapAuthorship.propTypes = {
|
||||
SnapAuthorshipExpanded.propTypes = {
|
||||
/**
|
||||
* The id of the snap
|
||||
*/
|
||||
@ -190,13 +189,9 @@ SnapAuthorship.propTypes = {
|
||||
*/
|
||||
className: PropTypes.string,
|
||||
/**
|
||||
* If the authorship component should be expanded
|
||||
*/
|
||||
expanded: PropTypes.bool,
|
||||
/**
|
||||
* The snap object. Can be undefined if the component is not expanded
|
||||
* The snap object.
|
||||
*/
|
||||
snap: PropTypes.object,
|
||||
};
|
||||
|
||||
export default SnapAuthorship;
|
||||
export default SnapAuthorshipExpanded;
|
1
ui/components/app/snaps/snap-authorship-header/index.js
Normal file
1
ui/components/app/snaps/snap-authorship-header/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './snap-authorship-header';
|
@ -0,0 +1,96 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import { getSnapPrefix } from '@metamask/snaps-utils';
|
||||
import { useSelector } from 'react-redux';
|
||||
import Box from '../../../ui/box';
|
||||
import {
|
||||
BackgroundColor,
|
||||
TextColor,
|
||||
FLEX_DIRECTION,
|
||||
TextVariant,
|
||||
AlignItems,
|
||||
DISPLAY,
|
||||
BLOCK_SIZES,
|
||||
FontWeight,
|
||||
} from '../../../../helpers/constants/design-system';
|
||||
import {
|
||||
getSnapName,
|
||||
removeSnapIdPrefix,
|
||||
} from '../../../../helpers/utils/util';
|
||||
|
||||
import { Text } from '../../../component-library';
|
||||
import { getTargetSubjectMetadata } from '../../../../selectors';
|
||||
import SnapAvatar from '../snap-avatar';
|
||||
import SnapVersion from '../snap-version/snap-version';
|
||||
|
||||
const SnapAuthorshipHeader = ({ snapId, className }) => {
|
||||
// We're using optional chaining with snapId, because with the current implementation
|
||||
// of snap update in the snap controller, we do not have reference to snapId when an
|
||||
// update request is rejected because the reference comes from the request itself and not subject metadata
|
||||
// like it is done with snap install
|
||||
const snapPrefix = snapId && getSnapPrefix(snapId);
|
||||
const packageName = snapId && removeSnapIdPrefix(snapId);
|
||||
const isNPM = snapPrefix === 'npm:';
|
||||
const url = isNPM
|
||||
? `https://www.npmjs.com/package/${packageName}`
|
||||
: packageName;
|
||||
|
||||
const subjectMetadata = useSelector((state) =>
|
||||
getTargetSubjectMetadata(state, snapId),
|
||||
);
|
||||
|
||||
const friendlyName = snapId && getSnapName(snapId, subjectMetadata);
|
||||
|
||||
return (
|
||||
<Box
|
||||
className={classnames('snaps-authorship-header', className)}
|
||||
backgroundColor={BackgroundColor.backgroundDefault}
|
||||
width={BLOCK_SIZES.FULL}
|
||||
alignItems={AlignItems.center}
|
||||
display={DISPLAY.FLEX}
|
||||
padding={4}
|
||||
style={{
|
||||
boxShadow: 'var(--shadow-size-lg) var(--color-shadow-default)',
|
||||
}}
|
||||
>
|
||||
<Box>
|
||||
<SnapAvatar snapId={snapId} />
|
||||
</Box>
|
||||
<Box
|
||||
marginLeft={4}
|
||||
marginRight={4}
|
||||
display={DISPLAY.FLEX}
|
||||
flexDirection={FLEX_DIRECTION.COLUMN}
|
||||
style={{ overflow: 'hidden' }}
|
||||
>
|
||||
<Text ellipsis fontWeight={FontWeight.Medium}>
|
||||
{friendlyName}
|
||||
</Text>
|
||||
<Text
|
||||
ellipsis
|
||||
variant={TextVariant.bodySm}
|
||||
color={TextColor.textAlternative}
|
||||
>
|
||||
{packageName}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box marginLeft="auto">
|
||||
<SnapVersion version={subjectMetadata?.version} url={url} />
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
SnapAuthorshipHeader.propTypes = {
|
||||
/**
|
||||
* The id of the snap
|
||||
*/
|
||||
snapId: PropTypes.string,
|
||||
/**
|
||||
* The className of the SnapAuthorship
|
||||
*/
|
||||
className: PropTypes.string,
|
||||
};
|
||||
|
||||
export default SnapAuthorshipHeader;
|
@ -0,0 +1,21 @@
|
||||
import React from 'react';
|
||||
import SnapAuthorshipHeader from './snap-authorship-header';
|
||||
|
||||
export default {
|
||||
title: 'Components/App/Snaps/SnapAuthorshipHeader',
|
||||
|
||||
component: SnapAuthorshipHeader,
|
||||
argTypes: {
|
||||
snapId: {
|
||||
control: 'text',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const DefaultStory = (args) => <SnapAuthorshipHeader {...args} />;
|
||||
|
||||
DefaultStory.storyName = 'Default';
|
||||
|
||||
DefaultStory.args = {
|
||||
snapId: 'npm:@metamask/test-snap-bip44',
|
||||
};
|
@ -1 +0,0 @@
|
||||
export { default } from './snap-authorship';
|
@ -1,21 +0,0 @@
|
||||
import React from 'react';
|
||||
import SnapAuthorship from '.';
|
||||
|
||||
export default {
|
||||
title: 'Components/App/Snaps/SnapAuthorship',
|
||||
|
||||
component: SnapAuthorship,
|
||||
argTypes: {
|
||||
snapId: {
|
||||
control: 'text',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const DefaultStory = (args) => <SnapAuthorship {...args} />;
|
||||
|
||||
DefaultStory.storyName = 'Default';
|
||||
|
||||
DefaultStory.args = {
|
||||
snapId: 'npm:@metamask/test-snap-bip44',
|
||||
};
|
@ -9,6 +9,7 @@ import {
|
||||
DISPLAY,
|
||||
JustifyContent,
|
||||
Size,
|
||||
BackgroundColor,
|
||||
} from '../../../../helpers/constants/design-system';
|
||||
import { getSnapName } from '../../../../helpers/utils/util';
|
||||
import {
|
||||
@ -39,10 +40,12 @@ const SnapAvatar = ({ snapId, className }) => {
|
||||
badge={
|
||||
<AvatarIcon
|
||||
iconName={IconName.Snaps}
|
||||
size={IconSize.Xs}
|
||||
size={IconSize.Sm}
|
||||
backgroundColor={IconColor.infoDefault}
|
||||
borderColor={BackgroundColor.backgroundDefault}
|
||||
borderWidth={2}
|
||||
iconProps={{
|
||||
size: IconSize.Xs,
|
||||
size: IconSize.Sm,
|
||||
color: IconColor.infoInverse,
|
||||
}}
|
||||
/>
|
||||
@ -50,10 +53,10 @@ const SnapAvatar = ({ snapId, className }) => {
|
||||
position={BadgeWrapperPosition.bottomRight}
|
||||
>
|
||||
{iconUrl ? (
|
||||
<AvatarFavicon size={Size.MD} src={iconUrl} name={friendlyName} />
|
||||
<AvatarFavicon size={Size.LG} src={iconUrl} name={friendlyName} />
|
||||
) : (
|
||||
<AvatarBase
|
||||
size={Size.MD}
|
||||
size={Size.LG}
|
||||
display={DISPLAY.FLEX}
|
||||
alignItems={AlignItems.center}
|
||||
justifyContent={JustifyContent.center}
|
||||
|
@ -47,19 +47,20 @@ export const SnapDelineator = ({
|
||||
>
|
||||
<AvatarIcon
|
||||
iconName={IconName.Snaps}
|
||||
size={IconSize.Xs}
|
||||
size={IconSize.Sm}
|
||||
backgroundColor={
|
||||
isError ? IconColor.errorDefault : IconColor.infoDefault
|
||||
}
|
||||
margin={1}
|
||||
borderColor={BackgroundColor.backgroundDefault}
|
||||
borderWidth={2}
|
||||
iconProps={{
|
||||
size: IconSize.Xs,
|
||||
size: IconSize.Sm,
|
||||
color: IconColor.infoInverse,
|
||||
}}
|
||||
/>
|
||||
<Text
|
||||
variant={TextVariant.bodySm}
|
||||
color={isError ? TextColor.errorDefault : TextColor.default}
|
||||
color={isError ? TextColor.errorDefault : TextColor.textAlternative}
|
||||
className="snap-delineator__header__text"
|
||||
marginLeft={1}
|
||||
marginTop={0}
|
||||
|
44
ui/hooks/useScrollRequired.js
Normal file
44
ui/hooks/useScrollRequired.js
Normal file
@ -0,0 +1,44 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { debounce } from 'lodash';
|
||||
|
||||
/**
|
||||
* Utility hook for requiring users to scroll through content.
|
||||
* Returns an object containing state and helpers to accomplish this.
|
||||
*
|
||||
* The hook expects both the `ref` and the `onScroll` handler to be passed to the scrolling element.
|
||||
*
|
||||
* @param dependencies - Any optional hook dependencies for updating the scroll state.
|
||||
* @returns Flags for isScrollable and isScrollToBottom, a ref to use for the scrolling content, a scrollToBottom function and a onScroll handler.
|
||||
*/
|
||||
export const useScrollRequired = (dependencies = []) => {
|
||||
const ref = useRef();
|
||||
const [isScrollableState, setIsScrollable] = useState(false);
|
||||
const [isScrolledToBottomState, setIsScrolledToBottom] = useState(false);
|
||||
|
||||
const update = () => {
|
||||
const isScrollable =
|
||||
ref.current && ref.current.scrollHeight > ref.current.clientHeight;
|
||||
const isScrolledToBottom = isScrollable
|
||||
? Math.round(ref.current.scrollTop) + ref.current.offsetHeight >=
|
||||
ref.current.scrollHeight
|
||||
: true;
|
||||
setIsScrollable(isScrollable);
|
||||
setIsScrolledToBottom(isScrolledToBottom);
|
||||
};
|
||||
|
||||
useEffect(update, [ref, ...dependencies]);
|
||||
|
||||
const scrollToBottom = () => {
|
||||
if (ref.current) {
|
||||
ref.current.scrollTo(0, ref.current.scrollHeight);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
isScrollable: isScrollableState,
|
||||
isScrolledToBottom: isScrolledToBottomState,
|
||||
scrollToBottom,
|
||||
ref,
|
||||
onScroll: debounce(update, 25),
|
||||
};
|
||||
};
|
@ -12,12 +12,14 @@ export default function ConfirmationFooter({
|
||||
alerts,
|
||||
loading,
|
||||
submitAlerts,
|
||||
actionsStyle,
|
||||
style,
|
||||
}) {
|
||||
return (
|
||||
<div className="confirmation-footer">
|
||||
<div className="confirmation-footer" style={style}>
|
||||
{alerts}
|
||||
{submitAlerts}
|
||||
<div className="confirmation-footer__actions">
|
||||
<div className="confirmation-footer__actions" style={actionsStyle}>
|
||||
{onCancel ? (
|
||||
<Button type="secondary" onClick={onCancel}>
|
||||
{cancelText}
|
||||
@ -47,4 +49,6 @@ ConfirmationFooter.propTypes = {
|
||||
loadingText: PropTypes.string,
|
||||
loading: PropTypes.bool,
|
||||
submitAlerts: PropTypes.node,
|
||||
style: PropTypes.object,
|
||||
actionsStyle: PropTypes.object,
|
||||
};
|
||||
|
@ -38,7 +38,7 @@ import Callout from '../../components/ui/callout';
|
||||
import SiteOrigin from '../../components/ui/site-origin';
|
||||
import { Icon, IconName } from '../../components/component-library';
|
||||
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
||||
import SnapAuthorship from '../../components/app/snaps/snap-authorship';
|
||||
import SnapAuthorshipHeader from '../../components/app/snaps/snap-authorship-header';
|
||||
import { getSnapName } from '../../helpers/utils/util';
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
import ConfirmationFooter from './components/confirmation-footer';
|
||||
@ -372,13 +372,7 @@ export default function ConfirmationPage({
|
||||
{
|
||||
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
||||
isSnapDialog && (
|
||||
<Box
|
||||
alignItems="center"
|
||||
margin={4}
|
||||
flexDirection={FLEX_DIRECTION.COLUMN}
|
||||
>
|
||||
<SnapAuthorship snapId={pendingConfirmation?.origin} />
|
||||
</Box>
|
||||
<SnapAuthorshipHeader snapId={pendingConfirmation?.origin} />
|
||||
)
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
}
|
||||
@ -412,6 +406,22 @@ export default function ConfirmationPage({
|
||||
</Callout>
|
||||
))
|
||||
}
|
||||
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
||||
style={
|
||||
isSnapDialog
|
||||
? {
|
||||
boxShadow: 'var(--shadow-size-lg) var(--color-shadow-default)',
|
||||
}
|
||||
: {}
|
||||
}
|
||||
actionsStyle={
|
||||
isSnapDialog
|
||||
? {
|
||||
borderTop: 0,
|
||||
}
|
||||
: {}
|
||||
}
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
onSubmit={handleSubmit}
|
||||
onCancel={templatedValues.onCancel}
|
||||
submitText={templatedValues.submitText}
|
||||
|
@ -14,6 +14,7 @@ function getValues(pendingApproval, t, actions) {
|
||||
element: 'Box',
|
||||
key: 'snap-dialog-content-wrapper',
|
||||
props: {
|
||||
marginTop: 4,
|
||||
marginLeft: 4,
|
||||
marginRight: 4,
|
||||
},
|
||||
@ -29,7 +30,7 @@ function getValues(pendingApproval, t, actions) {
|
||||
},
|
||||
},
|
||||
],
|
||||
submitText: t('ok'),
|
||||
submitText: t('ok').toUpperCase(),
|
||||
onSubmit: () => actions.resolvePendingApproval(pendingApproval.id, null),
|
||||
};
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ function getValues(pendingApproval, t, actions) {
|
||||
element: 'Box',
|
||||
key: 'snap-dialog-content-wrapper',
|
||||
props: {
|
||||
marginTop: 4,
|
||||
marginLeft: 4,
|
||||
marginRight: 4,
|
||||
},
|
||||
|
@ -15,6 +15,7 @@ function getValues(pendingApproval, t, actions, _history, setInputState) {
|
||||
element: 'Box',
|
||||
key: 'snap-dialog-content-wrapper',
|
||||
props: {
|
||||
marginTop: 4,
|
||||
marginLeft: 4,
|
||||
marginRight: 4,
|
||||
},
|
||||
|
@ -49,6 +49,7 @@ export default class PermissionConnect extends Component {
|
||||
setSnapsInstallPrivacyWarningShownStatus: PropTypes.func.isRequired,
|
||||
snapsInstallPrivacyWarningShown: PropTypes.bool.isRequired,
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
hideTopBar: PropTypes.bool,
|
||||
totalPages: PropTypes.string.isRequired,
|
||||
page: PropTypes.string.isRequired,
|
||||
targetSubjectMetadata: PropTypes.shape({
|
||||
@ -296,6 +297,7 @@ export default class PermissionConnect extends Component {
|
||||
permissionsRequestId,
|
||||
connectPath,
|
||||
confirmPermissionPath,
|
||||
hideTopBar,
|
||||
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
||||
snapInstallPath,
|
||||
snapUpdatePath,
|
||||
@ -318,7 +320,7 @@ export default class PermissionConnect extends Component {
|
||||
|
||||
return (
|
||||
<div className="permissions-connect">
|
||||
{this.renderTopBar()}
|
||||
{!hideTopBar && this.renderTopBar()}
|
||||
{redirecting && permissionsApproved ? (
|
||||
<PermissionsRedirect subjectMetadata={targetSubjectMetadata} />
|
||||
) : (
|
||||
|
@ -78,8 +78,6 @@ const mapStateToProps = (state, ownProps) => {
|
||||
const requestType = getRequestType(state, permissionsRequestId);
|
||||
|
||||
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
||||
const isSnap = targetSubjectMetadata.subjectType === SubjectType.Snap;
|
||||
|
||||
const requestState = getRequestState(state, permissionsRequestId);
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
|
||||
@ -101,11 +99,15 @@ const mapStateToProps = (state, ownProps) => {
|
||||
const snapInstallPath = `${CONNECT_ROUTE}/${permissionsRequestId}${CONNECT_SNAP_INSTALL_ROUTE}`;
|
||||
const snapUpdatePath = `${CONNECT_ROUTE}/${permissionsRequestId}${CONNECT_SNAP_UPDATE_ROUTE}`;
|
||||
const snapResultPath = `${CONNECT_ROUTE}/${permissionsRequestId}${CONNECT_SNAP_RESULT_ROUTE}`;
|
||||
const isSnapInstallOrUpdateOrResult =
|
||||
pathname === snapInstallPath ||
|
||||
pathname === snapUpdatePath ||
|
||||
pathname === snapResultPath;
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
|
||||
let totalPages = 1 + isRequestingAccounts;
|
||||
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
||||
totalPages += isSnap;
|
||||
totalPages += isSnapInstallOrUpdateOrResult;
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
totalPages = totalPages.toString();
|
||||
|
||||
@ -115,11 +117,7 @@ const mapStateToProps = (state, ownProps) => {
|
||||
} else if (pathname === confirmPermissionPath) {
|
||||
page = isRequestingAccounts ? '2' : '1';
|
||||
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
||||
} else if (
|
||||
pathname === snapInstallPath ||
|
||||
pathname === snapUpdatePath ||
|
||||
pathname === snapResultPath
|
||||
) {
|
||||
} else if (isSnapInstallOrUpdateOrResult) {
|
||||
page = isRequestingAccounts ? '3' : '2';
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
} else {
|
||||
@ -134,7 +132,7 @@ const mapStateToProps = (state, ownProps) => {
|
||||
snapUpdatePath,
|
||||
snapResultPath,
|
||||
requestState,
|
||||
isSnap,
|
||||
hideTopBar: isSnapInstallOrUpdateOrResult,
|
||||
snapsInstallPrivacyWarningShown: getSnapsInstallPrivacyWarningShown(state),
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
permissionsRequest,
|
||||
|
@ -2,15 +2,22 @@
|
||||
box-shadow: none;
|
||||
|
||||
&__content {
|
||||
flex: 1;
|
||||
|
||||
&__loader-container {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&__scroll-button {
|
||||
position: absolute;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
right: 0;
|
||||
left: 0;
|
||||
bottom: 90px;
|
||||
}
|
||||
|
||||
.page-container__footer {
|
||||
width: 100%;
|
||||
margin-top: 12px;
|
||||
border-top: 0;
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import SnapInstallWarning from '../../../../components/app/snaps/snap-install-wa
|
||||
import Box from '../../../../components/ui/box/box';
|
||||
import {
|
||||
AlignItems,
|
||||
BackgroundColor,
|
||||
BLOCK_SIZES,
|
||||
BorderStyle,
|
||||
FLEX_DIRECTION,
|
||||
@ -17,11 +18,16 @@ import {
|
||||
import { getSnapInstallWarnings } from '../util';
|
||||
import PulseLoader from '../../../../components/ui/pulse-loader/pulse-loader';
|
||||
import InstallError from '../../../../components/app/snaps/install-error/install-error';
|
||||
import SnapAuthorship from '../../../../components/app/snaps/snap-authorship';
|
||||
import { Text, ValidTag } from '../../../../components/component-library';
|
||||
import { useOriginMetadata } from '../../../../hooks/useOriginMetadata';
|
||||
import SnapAuthorshipHeader from '../../../../components/app/snaps/snap-authorship-header';
|
||||
import {
|
||||
AvatarIcon,
|
||||
IconName,
|
||||
Text,
|
||||
ValidTag,
|
||||
} from '../../../../components/component-library';
|
||||
import { getSnapName } from '../../../../helpers/utils/util';
|
||||
import SnapPermissionsList from '../../../../components/app/snaps/snap-permissions-list';
|
||||
import { useScrollRequired } from '../../../../hooks/useScrollRequired';
|
||||
|
||||
export default function SnapInstall({
|
||||
request,
|
||||
@ -33,7 +39,9 @@ export default function SnapInstall({
|
||||
const t = useI18nContext();
|
||||
|
||||
const [isShowingWarning, setIsShowingWarning] = useState(false);
|
||||
const originMetadata = useOriginMetadata(request.metadata?.dappOrigin) || {};
|
||||
|
||||
const { isScrollable, isScrolledToBottom, scrollToBottom, ref, onScroll } =
|
||||
useScrollRequired([requestState]);
|
||||
|
||||
const onCancel = useCallback(
|
||||
() => rejectSnapInstall(request.metadata.id),
|
||||
@ -49,13 +57,6 @@ export default function SnapInstall({
|
||||
|
||||
const isLoading = requestState.loading;
|
||||
|
||||
const hasPermissions =
|
||||
!hasError &&
|
||||
requestState?.permissions &&
|
||||
Object.keys(requestState.permissions).length > 0;
|
||||
|
||||
const isEmpty = !isLoading && !hasError && !hasPermissions;
|
||||
|
||||
const warnings = getSnapInstallWarnings(
|
||||
requestState?.permissions ?? {},
|
||||
targetSubjectMetadata,
|
||||
@ -84,25 +85,16 @@ export default function SnapInstall({
|
||||
borderStyle={BorderStyle.none}
|
||||
flexDirection={FLEX_DIRECTION.COLUMN}
|
||||
>
|
||||
<SnapAuthorshipHeader snapId={targetSubjectMetadata.origin} />
|
||||
<Box
|
||||
className="snap-install__header"
|
||||
alignItems={AlignItems.center}
|
||||
paddingLeft={4}
|
||||
paddingRight={4}
|
||||
flexDirection={FLEX_DIRECTION.COLUMN}
|
||||
ref={ref}
|
||||
onScroll={onScroll}
|
||||
className="snap-install__content"
|
||||
style={{
|
||||
overflowY: 'auto',
|
||||
flex: !isLoading && '1',
|
||||
}}
|
||||
>
|
||||
<SnapAuthorship snapId={targetSubjectMetadata.origin} />
|
||||
{!isLoading && !hasError && (
|
||||
<Text
|
||||
variant={TextVariant.headingLg}
|
||||
paddingTop={4}
|
||||
paddingBottom={2}
|
||||
>
|
||||
{t('snapInstall')}
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
<Box className="snap-install__content">
|
||||
{isLoading && (
|
||||
<Box
|
||||
className="snap-install__content__loader-container"
|
||||
@ -116,8 +108,16 @@ export default function SnapInstall({
|
||||
{hasError && (
|
||||
<InstallError error={requestState.error} title={t('requestFailed')} />
|
||||
)}
|
||||
{hasPermissions && (
|
||||
{!hasError && !isLoading && (
|
||||
<>
|
||||
<Text
|
||||
variant={TextVariant.headingLg}
|
||||
paddingTop={4}
|
||||
paddingBottom={2}
|
||||
textAlign="center"
|
||||
>
|
||||
{t('snapInstall')}
|
||||
</Text>
|
||||
<Text
|
||||
className="snap-install__content__permission-description"
|
||||
paddingBottom={4}
|
||||
@ -125,7 +125,7 @@ export default function SnapInstall({
|
||||
paddingRight={4}
|
||||
textAlign={TEXT_ALIGN.CENTER}
|
||||
>
|
||||
{t('snapInstallRequestsPermission', [
|
||||
{t('snapInstallRequest', [
|
||||
<Text
|
||||
as={ValidTag.Span}
|
||||
key="2"
|
||||
@ -140,40 +140,38 @@ export default function SnapInstall({
|
||||
permissions={requestState.permissions || {}}
|
||||
targetSubjectMetadata={targetSubjectMetadata}
|
||||
/>
|
||||
{isScrollable && !isScrolledToBottom ? (
|
||||
<AvatarIcon
|
||||
className="snap-install__scroll-button"
|
||||
data-testid="snap-install-scroll"
|
||||
iconName={IconName.Arrow2Down}
|
||||
backgroundColor={BackgroundColor.infoDefault}
|
||||
color={BackgroundColor.backgroundDefault}
|
||||
onClick={scrollToBottom}
|
||||
style={{ cursor: 'pointer' }}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
{isEmpty && (
|
||||
<Box
|
||||
flexDirection={FLEX_DIRECTION.COLUMN}
|
||||
height={BLOCK_SIZES.FULL}
|
||||
alignItems={AlignItems.center}
|
||||
justifyContent={JustifyContent.center}
|
||||
>
|
||||
<Text textAlign={TEXT_ALIGN.CENTER}>
|
||||
{t('snapInstallRequest', [
|
||||
<b key="1">{originMetadata?.hostname}</b>,
|
||||
<b key="2">{snapName}</b>,
|
||||
])}
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
<Box
|
||||
className="snap-install__footer"
|
||||
alignItems={AlignItems.center}
|
||||
flexDirection={FLEX_DIRECTION.COLUMN}
|
||||
style={{
|
||||
boxShadow: 'var(--shadow-size-lg) var(--color-shadow-default)',
|
||||
}}
|
||||
>
|
||||
<PageContainerFooter
|
||||
cancelButtonType="default"
|
||||
hideCancel={hasError}
|
||||
disabled={isLoading}
|
||||
disabled={
|
||||
isLoading || (!hasError && isScrollable && !isScrolledToBottom)
|
||||
}
|
||||
onCancel={onCancel}
|
||||
cancelText={t('cancel')}
|
||||
onSubmit={handleSubmit}
|
||||
submitText={t(
|
||||
// eslint-disable-next-line no-nested-ternary
|
||||
hasError ? 'ok' : 'install',
|
||||
)}
|
||||
submitText={t(hasError ? 'ok' : 'install')}
|
||||
/>
|
||||
</Box>
|
||||
{isShowingWarning && (
|
||||
|
@ -0,0 +1,122 @@
|
||||
import React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import configureStore from '../../../../store/store';
|
||||
import mockState from '../../../../../test/data/mock-state.json';
|
||||
import SnapInstall from '.';
|
||||
|
||||
const store = configureStore(mockState);
|
||||
|
||||
export default {
|
||||
title: 'Pages/Snaps/SnapInstall',
|
||||
|
||||
component: SnapInstall,
|
||||
argTypes: {},
|
||||
decorators: [(story) => <Provider store={store}>{story()}</Provider>],
|
||||
};
|
||||
|
||||
export const DefaultStory = (args) => <SnapInstall {...args} />;
|
||||
|
||||
DefaultStory.storyName = 'Default';
|
||||
|
||||
DefaultStory.args = {
|
||||
request: {
|
||||
metadata: {
|
||||
id: 'foo',
|
||||
},
|
||||
},
|
||||
requestState: {
|
||||
loading: false,
|
||||
permissions: {
|
||||
'endowment:rpc': {
|
||||
caveats: [
|
||||
{
|
||||
type: 'rpcOrigin',
|
||||
value: {
|
||||
dapps: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
snap_dialog: {},
|
||||
snap_getBip44Entropy: {
|
||||
caveats: [
|
||||
{
|
||||
type: 'permittedCoinTypes',
|
||||
value: [
|
||||
{
|
||||
coinType: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
targetSubjectMetadata: {
|
||||
origin: 'npm:@metamask/test-snap-bip44',
|
||||
},
|
||||
};
|
||||
|
||||
export const LoadingStory = (args) => <SnapInstall {...args} />;
|
||||
|
||||
LoadingStory.storyName = 'Loading';
|
||||
|
||||
LoadingStory.args = {
|
||||
request: {
|
||||
metadata: {
|
||||
id: 'foo',
|
||||
},
|
||||
},
|
||||
requestState: {
|
||||
loading: true,
|
||||
},
|
||||
targetSubjectMetadata: {
|
||||
origin: 'npm:@metamask/test-snap-bip44',
|
||||
},
|
||||
};
|
||||
|
||||
export const ScrollingStory = (args) => <SnapInstall {...args} />;
|
||||
|
||||
ScrollingStory.storyName = 'Scrolling';
|
||||
|
||||
ScrollingStory.args = {
|
||||
request: {
|
||||
metadata: {
|
||||
id: 'foo',
|
||||
},
|
||||
},
|
||||
requestState: {
|
||||
loading: false,
|
||||
permissions: {
|
||||
'endowment:rpc': {
|
||||
caveats: [
|
||||
{
|
||||
type: 'rpcOrigin',
|
||||
value: {
|
||||
dapps: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
'endowment:network-access': {},
|
||||
snap_notify: {},
|
||||
snap_dialog: {},
|
||||
snap_getBip44Entropy: {
|
||||
caveats: [
|
||||
{
|
||||
type: 'permittedCoinTypes',
|
||||
value: [
|
||||
{
|
||||
coinType: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
targetSubjectMetadata: {
|
||||
origin: 'npm:@metamask/test-snap-bip44',
|
||||
},
|
||||
};
|
@ -1,16 +1,8 @@
|
||||
.snap-result {
|
||||
box-shadow: none;
|
||||
|
||||
&__header {
|
||||
flex: 1;
|
||||
|
||||
&__loader-container {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.page-container__footer {
|
||||
width: 100%;
|
||||
margin-top: 12px;
|
||||
border-top: 0;
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ import {
|
||||
} from '../../../../components/component-library';
|
||||
import PulseLoader from '../../../../components/ui/pulse-loader/pulse-loader';
|
||||
import InstallError from '../../../../components/app/snaps/install-error/install-error';
|
||||
import SnapAuthorship from '../../../../components/app/snaps/snap-authorship';
|
||||
import SnapAuthorshipHeader from '../../../../components/app/snaps/snap-authorship-header';
|
||||
import { getSnapName } from '../../../../helpers/utils/util';
|
||||
|
||||
export default function SnapResult({
|
||||
@ -140,17 +140,20 @@ export default function SnapResult({
|
||||
borderStyle={BorderStyle.none}
|
||||
flexDirection={FLEX_DIRECTION.COLUMN}
|
||||
>
|
||||
<SnapAuthorshipHeader snapId={targetSubjectMetadata.origin} />
|
||||
<Box
|
||||
className="snap-result__header"
|
||||
className="snap-result__content"
|
||||
paddingLeft={4}
|
||||
paddingRight={4}
|
||||
alignItems={AlignItems.center}
|
||||
flexDirection={FLEX_DIRECTION.COLUMN}
|
||||
style={{
|
||||
overflowY: 'auto',
|
||||
}}
|
||||
>
|
||||
<SnapAuthorship snapId={targetSubjectMetadata.origin} />
|
||||
{isLoading && (
|
||||
<Box
|
||||
className="snap-result__header__loader-container"
|
||||
className="snap-result__content__loader-container"
|
||||
flexDirection={FLEX_DIRECTION.COLUMN}
|
||||
alignItems={AlignItems.center}
|
||||
justifyContent={JustifyContent.center}
|
||||
@ -167,6 +170,9 @@ export default function SnapResult({
|
||||
className="snap-result__footer"
|
||||
alignItems={AlignItems.center}
|
||||
flexDirection={FLEX_DIRECTION.COLUMN}
|
||||
style={{
|
||||
boxShadow: 'var(--shadow-size-lg) var(--color-shadow-default)',
|
||||
}}
|
||||
>
|
||||
<PageContainerFooter
|
||||
hideCancel
|
||||
|
@ -0,0 +1,64 @@
|
||||
import React from 'react';
|
||||
import SnapResult from '.';
|
||||
|
||||
export default {
|
||||
title: 'Pages/Snaps/SnapResult',
|
||||
|
||||
component: SnapResult,
|
||||
argTypes: {},
|
||||
};
|
||||
|
||||
export const DefaultStory = (args) => <SnapResult {...args} />;
|
||||
|
||||
DefaultStory.storyName = 'Default';
|
||||
|
||||
DefaultStory.args = {
|
||||
request: {
|
||||
metadata: {
|
||||
id: 'foo',
|
||||
},
|
||||
},
|
||||
requestState: {
|
||||
loading: false,
|
||||
},
|
||||
targetSubjectMetadata: {
|
||||
origin: 'npm:@metamask/test-snap-bip44',
|
||||
},
|
||||
};
|
||||
|
||||
export const LoadingStory = (args) => <SnapResult {...args} />;
|
||||
|
||||
LoadingStory.storyName = 'Loading';
|
||||
|
||||
LoadingStory.args = {
|
||||
request: {
|
||||
metadata: {
|
||||
id: 'foo',
|
||||
},
|
||||
},
|
||||
requestState: {
|
||||
loading: true,
|
||||
},
|
||||
targetSubjectMetadata: {
|
||||
origin: 'npm:@metamask/test-snap-bip44',
|
||||
},
|
||||
};
|
||||
|
||||
export const ErrorStory = (args) => <SnapResult {...args} />;
|
||||
|
||||
ErrorStory.storyName = 'Error';
|
||||
|
||||
ErrorStory.args = {
|
||||
request: {
|
||||
metadata: {
|
||||
id: 'foo',
|
||||
},
|
||||
},
|
||||
requestState: {
|
||||
loading: false,
|
||||
error: 'foo',
|
||||
},
|
||||
targetSubjectMetadata: {
|
||||
origin: 'npm:@metamask/test-snap-bip44',
|
||||
},
|
||||
};
|
@ -2,20 +2,23 @@
|
||||
box-shadow: none;
|
||||
|
||||
&__content {
|
||||
flex: 1;
|
||||
|
||||
&__loader-container {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&__permission-description {
|
||||
border-bottom: 1px solid var(--color-border-default);
|
||||
}
|
||||
&__scroll-button {
|
||||
position: absolute;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
right: 0;
|
||||
left: 0;
|
||||
bottom: 90px;
|
||||
}
|
||||
|
||||
.page-container__footer {
|
||||
width: 100%;
|
||||
margin-top: 12px;
|
||||
border-top: 0;
|
||||
|
||||
button {
|
||||
padding: 0.75rem;
|
||||
|
@ -6,9 +6,11 @@ import SnapInstallWarning from '../../../../components/app/snaps/snap-install-wa
|
||||
import Box from '../../../../components/ui/box/box';
|
||||
import {
|
||||
AlignItems,
|
||||
BackgroundColor,
|
||||
BLOCK_SIZES,
|
||||
BorderStyle,
|
||||
FLEX_DIRECTION,
|
||||
FontWeight,
|
||||
JustifyContent,
|
||||
TextVariant,
|
||||
TEXT_ALIGN,
|
||||
@ -18,10 +20,16 @@ import UpdateSnapPermissionList from '../../../../components/app/snaps/update-sn
|
||||
import { getSnapInstallWarnings } from '../util';
|
||||
import PulseLoader from '../../../../components/ui/pulse-loader/pulse-loader';
|
||||
import InstallError from '../../../../components/app/snaps/install-error/install-error';
|
||||
import SnapAuthorship from '../../../../components/app/snaps/snap-authorship';
|
||||
import { Text } from '../../../../components/component-library';
|
||||
import SnapAuthorshipHeader from '../../../../components/app/snaps/snap-authorship-header';
|
||||
import {
|
||||
AvatarIcon,
|
||||
IconName,
|
||||
Text,
|
||||
ValidTag,
|
||||
} from '../../../../components/component-library';
|
||||
import { useOriginMetadata } from '../../../../hooks/useOriginMetadata';
|
||||
import { getSnapName } from '../../../../helpers/utils/util';
|
||||
import { useScrollRequired } from '../../../../hooks/useScrollRequired';
|
||||
|
||||
export default function SnapUpdate({
|
||||
request,
|
||||
@ -35,6 +43,9 @@ export default function SnapUpdate({
|
||||
const [isShowingWarning, setIsShowingWarning] = useState(false);
|
||||
const originMetadata = useOriginMetadata(request.metadata?.dappOrigin) || {};
|
||||
|
||||
const { isScrollable, isScrolledToBottom, scrollToBottom, ref, onScroll } =
|
||||
useScrollRequired([requestState]);
|
||||
|
||||
const onCancel = useCallback(
|
||||
() => rejectSnapUpdate(request.metadata.id),
|
||||
[request, rejectSnapUpdate],
|
||||
@ -81,25 +92,26 @@ export default function SnapUpdate({
|
||||
borderStyle={BorderStyle.none}
|
||||
flexDirection={FLEX_DIRECTION.COLUMN}
|
||||
>
|
||||
<SnapAuthorshipHeader snapId={targetSubjectMetadata.origin} />
|
||||
<Box
|
||||
className="snap-update__header"
|
||||
paddingLeft={4}
|
||||
paddingRight={4}
|
||||
alignItems={AlignItems.center}
|
||||
flexDirection={FLEX_DIRECTION.COLUMN}
|
||||
ref={ref}
|
||||
onScroll={onScroll}
|
||||
className="snap-update__content"
|
||||
style={{
|
||||
overflowY: 'auto',
|
||||
flex: !isLoading && '1',
|
||||
}}
|
||||
>
|
||||
<SnapAuthorship snapId={targetSubjectMetadata.origin} />
|
||||
{!isLoading && !hasError && (
|
||||
<Text
|
||||
paddingBottom={4}
|
||||
paddingTop={4}
|
||||
variant={TextVariant.headingLg}
|
||||
textAlign="center"
|
||||
>
|
||||
{t('snapUpdate')}
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
<Box className="snap-update__content">
|
||||
{isLoading && (
|
||||
<Box
|
||||
className="snap-update__content__loader-container"
|
||||
@ -123,9 +135,30 @@ export default function SnapUpdate({
|
||||
textAlign={TEXT_ALIGN.CENTER}
|
||||
>
|
||||
{t('snapUpdateRequest', [
|
||||
<b key="1">{originMetadata?.hostname}</b>,
|
||||
<b key="2">{snapName}</b>,
|
||||
<b key="3">v{newVersion}</b>,
|
||||
<Text
|
||||
as={ValidTag.Span}
|
||||
key="1"
|
||||
variant={TextVariant.bodyMd}
|
||||
fontWeight={FontWeight.Medium}
|
||||
>
|
||||
{originMetadata?.hostname}
|
||||
</Text>,
|
||||
<Text
|
||||
as={ValidTag.Span}
|
||||
key="2"
|
||||
variant={TextVariant.bodyMd}
|
||||
fontWeight={FontWeight.Medium}
|
||||
>
|
||||
{snapName}
|
||||
</Text>,
|
||||
<Text
|
||||
as={ValidTag.Span}
|
||||
key="3"
|
||||
variant={TextVariant.bodyMd}
|
||||
fontWeight={FontWeight.Medium}
|
||||
>
|
||||
{newVersion}
|
||||
</Text>,
|
||||
])}
|
||||
</Text>
|
||||
<UpdateSnapPermissionList
|
||||
@ -134,6 +167,17 @@ export default function SnapUpdate({
|
||||
newPermissions={newPermissions}
|
||||
targetSubjectMetadata={targetSubjectMetadata}
|
||||
/>
|
||||
{isScrollable && !isScrolledToBottom ? (
|
||||
<AvatarIcon
|
||||
className="snap-install__scroll-button"
|
||||
data-testid="snap-update-scroll"
|
||||
iconName={IconName.Arrow2Down}
|
||||
backgroundColor={BackgroundColor.infoDefault}
|
||||
color={BackgroundColor.backgroundDefault}
|
||||
onClick={scrollToBottom}
|
||||
style={{ cursor: 'pointer' }}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
@ -141,11 +185,16 @@ export default function SnapUpdate({
|
||||
className="snap-update__footer"
|
||||
alignItems={AlignItems.center}
|
||||
flexDirection={FLEX_DIRECTION.COLUMN}
|
||||
style={{
|
||||
boxShadow: 'var(--shadow-size-lg) var(--color-shadow-default)',
|
||||
}}
|
||||
>
|
||||
<PageContainerFooter
|
||||
cancelButtonType="default"
|
||||
hideCancel={hasError}
|
||||
disabled={isLoading}
|
||||
disabled={
|
||||
isLoading || (!hasError && isScrollable && !isScrolledToBottom)
|
||||
}
|
||||
onCancel={onCancel}
|
||||
cancelText={t('cancel')}
|
||||
onSubmit={handleSubmit}
|
||||
|
@ -0,0 +1,124 @@
|
||||
import React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import configureStore from '../../../../store/store';
|
||||
import mockState from '../../../../../test/data/mock-state.json';
|
||||
import SnapUpdate from '.';
|
||||
|
||||
const store = configureStore(mockState);
|
||||
|
||||
export default {
|
||||
title: 'Pages/Snaps/SnapUpdate',
|
||||
|
||||
component: SnapUpdate,
|
||||
argTypes: {},
|
||||
decorators: [(story) => <Provider store={store}>{story()}</Provider>],
|
||||
};
|
||||
|
||||
export const DefaultStory = (args) => <SnapUpdate {...args} />;
|
||||
|
||||
DefaultStory.storyName = 'Default';
|
||||
|
||||
DefaultStory.args = {
|
||||
request: {
|
||||
metadata: {
|
||||
id: 'foo',
|
||||
},
|
||||
},
|
||||
requestState: {
|
||||
loading: false,
|
||||
newVersion: '2.0.0',
|
||||
approvedPermissions: {
|
||||
'endowment:rpc': {
|
||||
caveats: [
|
||||
{
|
||||
type: 'rpcOrigin',
|
||||
value: {
|
||||
dapps: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
snap_dialog: {},
|
||||
snap_getBip44Entropy: {
|
||||
caveats: [
|
||||
{
|
||||
type: 'permittedCoinTypes',
|
||||
value: [
|
||||
{
|
||||
coinType: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
targetSubjectMetadata: {
|
||||
origin: 'npm:@metamask/test-snap-bip44',
|
||||
},
|
||||
};
|
||||
|
||||
export const LoadingStory = (args) => <SnapUpdate {...args} />;
|
||||
|
||||
LoadingStory.storyName = 'Loading';
|
||||
|
||||
LoadingStory.args = {
|
||||
request: {
|
||||
metadata: {
|
||||
id: 'foo',
|
||||
},
|
||||
},
|
||||
requestState: {
|
||||
loading: true,
|
||||
},
|
||||
targetSubjectMetadata: {
|
||||
origin: 'npm:@metamask/test-snap-bip44',
|
||||
},
|
||||
};
|
||||
|
||||
export const ScrollingStory = (args) => <SnapUpdate {...args} />;
|
||||
|
||||
ScrollingStory.storyName = 'Scrolling';
|
||||
|
||||
ScrollingStory.args = {
|
||||
request: {
|
||||
metadata: {
|
||||
id: 'foo',
|
||||
},
|
||||
},
|
||||
requestState: {
|
||||
loading: false,
|
||||
newVersion: '2.0.0',
|
||||
approvedPermissions: {
|
||||
'endowment:rpc': {
|
||||
caveats: [
|
||||
{
|
||||
type: 'rpcOrigin',
|
||||
value: {
|
||||
dapps: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
'endowment:network-access': {},
|
||||
snap_notify: {},
|
||||
snap_dialog: {},
|
||||
snap_getBip44Entropy: {
|
||||
caveats: [
|
||||
{
|
||||
type: 'permittedCoinTypes',
|
||||
value: [
|
||||
{
|
||||
coinType: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
targetSubjectMetadata: {
|
||||
origin: 'npm:@metamask/test-snap-bip44',
|
||||
},
|
||||
};
|
@ -14,7 +14,7 @@ import {
|
||||
TextColor,
|
||||
TextVariant,
|
||||
} from '../../../../helpers/constants/design-system';
|
||||
import SnapAuthorship from '../../../../components/app/snaps/snap-authorship';
|
||||
import SnapAuthorshipExpanded from '../../../../components/app/snaps/snap-authorship-expanded';
|
||||
import Box from '../../../../components/ui/box';
|
||||
import SnapRemoveWarning from '../../../../components/app/snaps/snap-remove-warning';
|
||||
import ConnectedSitesList from '../../../../components/app/connected-sites-list';
|
||||
@ -124,7 +124,7 @@ function ViewSnap() {
|
||||
paddingLeft={4}
|
||||
paddingRight={4}
|
||||
>
|
||||
<SnapAuthorship snapId={snap.id} snap={snap} expanded />
|
||||
<SnapAuthorshipExpanded snapId={snap.id} snap={snap} />
|
||||
<Box className="view-snap__description" marginTop={[4, 7]}>
|
||||
<SnapDelineator type={DelineatorType.Description} snapName={snapName}>
|
||||
<Box
|
||||
|
@ -38,9 +38,9 @@ describe('ViewSnap', () => {
|
||||
|
||||
// Snap name & Snap authorship component
|
||||
expect(getByText('BIP-44 Test Snap')).toBeDefined();
|
||||
expect(container.getElementsByClassName('snaps-authorship')?.length).toBe(
|
||||
1,
|
||||
);
|
||||
expect(
|
||||
container.getElementsByClassName('snaps-authorship-expanded')?.length,
|
||||
).toBe(1);
|
||||
// Snap description
|
||||
expect(
|
||||
getByText('An example Snap that signs messages using BLS.'),
|
||||
@ -48,7 +48,7 @@ describe('ViewSnap', () => {
|
||||
// Snap version info
|
||||
expect(getByText('v5.1.2')).toBeDefined();
|
||||
// Enable Snap
|
||||
expect(getByText('Enable')).toBeDefined();
|
||||
expect(getByText('Enabled')).toBeDefined();
|
||||
expect(container.getElementsByClassName('toggle-button')?.length).toBe(1);
|
||||
// Permissions
|
||||
expect(getByText('Permissions')).toBeDefined();
|
||||
|
Loading…
Reference in New Issue
Block a user