1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-22 09:57:02 +01:00

[FLASK] Expanded Snap authorship (#18775)

This commit is contained in:
Guillaume Roux 2023-04-25 19:20:37 +02:00 committed by GitHub
parent a4a5b28f2e
commit 6126c156ea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 396 additions and 197 deletions

View File

@ -1066,6 +1066,10 @@
"description": { "description": {
"message": "Description" "message": "Description"
}, },
"descriptionFromSnap": {
"message": "Description from $1",
"description": "$1 represents the name of the snap"
},
"desktopConnectionCriticalErrorDescription": { "desktopConnectionCriticalErrorDescription": {
"message": "This error could be intermittent, so try restarting the extension or disable MetaMask Desktop." "message": "This error could be intermittent, so try restarting the extension or disable MetaMask Desktop."
}, },
@ -1331,10 +1335,7 @@
"message": "Enable smart transactions" "message": "Enable smart transactions"
}, },
"enableSnap": { "enableSnap": {
"message": "Enable snap" "message": "Enable"
},
"enableSnapDescription": {
"message": "Your installed snap will only have access to its permissions and run if its enabled."
}, },
"enableToken": { "enableToken": {
"message": "enable $1", "message": "enable $1",
@ -1851,6 +1852,13 @@
"install": { "install": {
"message": "Install" "message": "Install"
}, },
"installOrigin": {
"message": "Install origin"
},
"installedOn": {
"message": "Installed on $1",
"description": "$1 is the date when the snap has been installed"
},
"institutionalFeatures": { "institutionalFeatures": {
"message": "Institutional Features" "message": "Institutional Features"
}, },
@ -2192,6 +2200,9 @@
"mmiAuthenticate": { "mmiAuthenticate": {
"message": "The page at $1 would like to authorise the following projects compliance settings in MetaMask Institutional" "message": "The page at $1 would like to authorise the following projects compliance settings in MetaMask Institutional"
}, },
"more": {
"message": "more"
},
"moreComingSoon": { "moreComingSoon": {
"message": "More coming soon..." "message": "More coming soon..."
}, },
@ -3576,6 +3587,10 @@
"settingsSearchMatchingNotFound": { "settingsSearchMatchingNotFound": {
"message": "No matching results found." "message": "No matching results found."
}, },
"shortVersion": {
"message": "v$1",
"description": "$1 is the version number to show"
},
"show": { "show": {
"message": "Show" "message": "Show"
}, },
@ -4510,7 +4525,6 @@
"transactionFailed": { "transactionFailed": {
"message": "Transaction Failed" "message": "Transaction Failed"
}, },
"transactionFee": { "transactionFee": {
"message": "Transaction fee" "message": "Transaction fee"
}, },
@ -4737,6 +4751,9 @@
"message": "Verify this token on $1 and make sure this is the token you want to trade.", "message": "Verify this token on $1 and make sure this is the token you want to trade.",
"description": "Points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\"" "description": "Points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\""
}, },
"version": {
"message": "Version"
},
"view": { "view": {
"message": "View" "message": "View"
}, },
@ -4870,10 +4887,6 @@
"message": "You've added all the popular networks. You can discover more networks $1 Or you can $2", "message": "You've added all the popular networks. You can discover more networks $1 Or you can $2",
"description": "$1 is a link with the text 'here' and $2 is a button with the text 'add more networks manually'" "description": "$1 is a link with the text 'here' and $2 is a button with the text 'add more networks manually'"
}, },
"youInstalled": {
"message": "You installed",
"description": "Part of version description for installed snap"
},
"youNeedToAllowCameraAccess": { "youNeedToAllowCameraAccess": {
"message": "You need to allow camera access to use this feature." "message": "You need to allow camera access to use this feature."
}, },

View File

@ -43,6 +43,7 @@
@import 'snaps/snap-settings-card/index'; @import 'snaps/snap-settings-card/index';
@import 'snaps/update-snap-permission-list/index'; @import 'snaps/update-snap-permission-list/index';
@import 'snaps/copyable/index'; @import 'snaps/copyable/index';
@import 'snaps/snap-version/index';
@import 'gas-details-item/index'; @import 'gas-details-item/index';
@import 'gas-details-item/gas-details-item-title/index'; @import 'gas-details-item/gas-details-item-title/index';
@import 'gas-timing/index'; @import 'gas-timing/index';

View File

@ -2,30 +2,42 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import classnames from 'classnames'; import classnames from 'classnames';
import { getSnapPrefix } from '@metamask/snaps-utils'; import { getSnapPrefix } from '@metamask/snaps-utils';
import { useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import Box from '../../../ui/box'; import Box from '../../../ui/box';
import { import {
BackgroundColor, BackgroundColor,
TextColor, TextColor,
IconColor,
FLEX_DIRECTION, FLEX_DIRECTION,
TextVariant, TextVariant,
BorderColor, BorderColor,
AlignItems, AlignItems,
DISPLAY, DISPLAY,
BorderRadius,
BLOCK_SIZES, BLOCK_SIZES,
JustifyContent,
BorderStyle,
Color,
BorderRadius,
} from '../../../../helpers/constants/design-system'; } from '../../../../helpers/constants/design-system';
import { import {
formatDate,
getSnapName, getSnapName,
removeSnapIdPrefix, removeSnapIdPrefix,
} from '../../../../helpers/utils/util'; } from '../../../../helpers/utils/util';
import { ButtonIcon, IconName, Text } from '../../../component-library';
import { Text, ButtonLink } from '../../../component-library';
import { getTargetSubjectMetadata } from '../../../../selectors'; import { getTargetSubjectMetadata } from '../../../../selectors';
import SnapAvatar from '../snap-avatar'; import SnapAvatar from '../snap-avatar';
import { useI18nContext } from '../../../../hooks/useI18nContext';
import Tooltip from '../../../ui/tooltip/tooltip';
import ToggleButton from '../../../ui/toggle-button';
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 t = useI18nContext();
const dispatch = useDispatch();
const SnapAuthorship = ({ snapId, className }) => {
// We're using optional chaining with snapId, because with the current implementation // 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 // 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 // update request is rejected because the reference comes from the request itself and not subject metadata
@ -43,48 +55,127 @@ const SnapAuthorship = ({ snapId, className }) => {
const friendlyName = snapId && getSnapName(snapId, subjectMetadata); const friendlyName = snapId && getSnapName(snapId, subjectMetadata);
// Expanded data
const versionHistory = snap?.versionHistory ?? [];
const installInfo = versionHistory.length
? versionHistory[versionHistory.length - 1]
: undefined;
const installOrigin = useOriginMetadata(installInfo?.origin);
const onToggle = () => {
if (snap?.enabled) {
dispatch(disableSnap(snap?.id));
} else {
dispatch(enableSnap(snap?.id));
}
};
return ( return (
<Box <Box
className={classnames('snaps-authorship', className)} className={classnames('snaps-authorship', className)}
backgroundColor={BackgroundColor.backgroundDefault} backgroundColor={BackgroundColor.backgroundDefault}
borderColor={BorderColor.borderDefault} borderColor={BorderColor.borderDefault}
borderWidth={1} borderWidth={1}
alignItems={AlignItems.center}
paddingLeft={2}
paddingTop={2}
paddingBottom={2}
paddingRight={4}
borderRadius={BorderRadius.pill}
display={DISPLAY.FLEX}
width={BLOCK_SIZES.FULL} width={BLOCK_SIZES.FULL}
borderRadius={expanded ? BorderRadius.LG : BorderRadius.pill}
> >
<Box>
<SnapAvatar snapId={snapId} />
</Box>
<Box <Box
marginLeft={4} alignItems={AlignItems.center}
marginRight={2}
display={DISPLAY.FLEX} display={DISPLAY.FLEX}
flexDirection={FLEX_DIRECTION.COLUMN} width={BLOCK_SIZES.FULL}
style={{ overflow: 'hidden' }} paddingLeft={expanded ? 4 : 2}
paddingRight={expanded ? 4 : 2}
paddingTop={expanded ? 3 : 1}
paddingBottom={expanded ? 3 : 1}
> >
<Text ellipsis>{friendlyName}</Text> <Box>
<Text <SnapAvatar snapId={snapId} />
ellipsis </Box>
variant={TextVariant.bodySm} <Box
color={TextColor.textAlternative} marginLeft={2}
marginRight={expanded ? 0 : 2}
display={DISPLAY.FLEX}
flexDirection={FLEX_DIRECTION.COLUMN}
style={{ overflow: 'hidden' }}
> >
{packageName} <Text ellipsis>{friendlyName}</Text>
</Text> <Text
ellipsis
variant={TextVariant.bodySm}
color={TextColor.textAlternative}
>
{packageName}
</Text>
</Box>
{!expanded && (
<Box marginLeft="auto">
<SnapVersion version={subjectMetadata?.version} url={url} />
</Box>
)}
</Box> </Box>
<ButtonIcon {expanded && (
rel="noopener noreferrer" <Box flexDirection={FLEX_DIRECTION.COLUMN} width={BLOCK_SIZES.FULL}>
target="_blank" <Box
href={url} flexDirection={FLEX_DIRECTION.ROW}
iconName={IconName.Export} justifyContent={JustifyContent.spaceBetween}
color={IconColor.infoDefault} paddingLeft={4}
style={{ marginLeft: 'auto' }} 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>
<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
flexDirection={FLEX_DIRECTION.ROW}
justifyContent={JustifyContent.spaceBetween}
alignItems={AlignItems.center}
marginTop={4}
>
<Text variant={TextVariant.bodyMdBold}>{t('version')}</Text>
<SnapVersion version={snap?.version} url={url} />
</Box>
</Box>
</Box>
)}
</Box> </Box>
); );
}; };
@ -98,6 +189,14 @@ SnapAuthorship.propTypes = {
* The className of the SnapAuthorship * The className of the SnapAuthorship
*/ */
className: PropTypes.string, 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
*/
snap: PropTypes.object,
}; };
export default SnapAuthorship; export default SnapAuthorship;

View File

@ -50,10 +50,10 @@ const SnapAvatar = ({ snapId, className }) => {
position={BadgeWrapperPosition.bottomRight} position={BadgeWrapperPosition.bottomRight}
> >
{iconUrl ? ( {iconUrl ? (
<AvatarFavicon size={Size.LG} src={iconUrl} name={friendlyName} /> <AvatarFavicon size={Size.MD} src={iconUrl} name={friendlyName} />
) : ( ) : (
<AvatarBase <AvatarBase
size={Size.LG} size={Size.MD}
display={DISPLAY.FLEX} display={DISPLAY.FLEX}
alignItems={AlignItems.center} alignItems={AlignItems.center}
justifyContent={JustifyContent.center} justifyContent={JustifyContent.center}

View File

@ -0,0 +1 @@
export { default } from './snap-version';

View File

@ -0,0 +1,15 @@
.snap-version {
&:hover {
opacity: 1;
}
&__wrapper {
&:hover {
background-color: var(--color-info-muted);
& * {
color: var(--color-info-default);
}
}
}
}

View File

@ -0,0 +1,72 @@
import React from 'react';
import PropTypes from 'prop-types';
import {
AlignItems,
BackgroundColor,
BorderRadius,
Color,
FLEX_DIRECTION,
TextVariant,
} from '../../../../helpers/constants/design-system';
import Box from '../../../ui/box';
import {
BUTTON_TYPES,
Button,
Icon,
IconName,
IconSize,
Text,
} from '../../../component-library';
import Preloader from '../../../ui/icon/preloader/preloader-icon.component';
import { useI18nContext } from '../../../../hooks/useI18nContext';
const SnapVersion = ({ version, url }) => {
const t = useI18nContext();
return (
<Button
type={BUTTON_TYPES.LINK}
href={url}
target="_blank"
className="snap-version"
>
<Box
className="snap-version__wrapper"
flexDirection={FLEX_DIRECTION.ROW}
alignItems={AlignItems.center}
backgroundColor={BackgroundColor.backgroundAlternative}
borderRadius={BorderRadius.pill}
paddingTop={1}
paddingBottom={1}
paddingLeft={2}
paddingRight={2}
>
{version ? (
<Text color={Color.textAlternative} variant={TextVariant.bodyMd}>
{t('shortVersion', [version])}
</Text>
) : (
<Preloader size={18} />
)}
<Icon
name={IconName.Export}
color={Color.textAlternative}
size={IconSize.Sm}
marginLeft={1}
/>
</Box>
</Button>
);
};
SnapVersion.propTypes = {
/**
* The version of the snap
*/
version: PropTypes.string,
/**
* The url to the snap package
*/
url: PropTypes.string,
};
export default SnapVersion;

View File

@ -0,0 +1,20 @@
import React from 'react';
import SnapVersion from '.';
export default {
title: 'Components/App/Snaps/SnapVersion',
component: SnapVersion,
};
export const DefaultStory = (args) => <SnapVersion {...args} />;
DefaultStory.args = {
version: '1.4.2',
url: 'https://www.npmjs.com/package/@metamask/test-snap-error',
};
export const LoadingStory = (args) => <SnapVersion {...args} />;
LoadingStory.args = {
version: undefined,
url: 'https://www.npmjs.com/package/@metamask/test-snap-error',
};

View File

@ -0,0 +1,27 @@
import * as React from 'react';
import { renderWithLocalization } from '../../../../../test/lib/render-helpers';
import SnapVersion from './snap-version';
describe('SnapVersion', () => {
const args = {
version: '1.4.2',
url: 'https://www.npmjs.com/package/@metamask/test-snap-error',
};
it('should render the SnapVersion without crashing and display a version', () => {
const { getByText, container } = renderWithLocalization(
<SnapVersion {...args} />,
);
expect(getByText(`v${args.version}`)).toBeDefined();
expect(container.firstChild).toHaveAttribute('href', args.url);
});
it('should have a loading state if no version is passed', () => {
args.version = undefined;
const { container } = renderWithLocalization(<SnapVersion {...args} />);
expect(container.getElementsByClassName('preloader__icon')).toHaveLength(1);
expect(container.firstChild).toHaveAttribute('href', args.url);
});
});

View File

@ -3,6 +3,7 @@ export enum DelineatorType {
// eslint-disable-next-line @typescript-eslint/no-shadow // eslint-disable-next-line @typescript-eslint/no-shadow
Error = 'error', Error = 'error',
Insights = 'insights', Insights = 'insights',
Description = 'description',
} }
export const getDelineatorTitle = (type: DelineatorType) => { export const getDelineatorTitle = (type: DelineatorType) => {
@ -11,6 +12,8 @@ export const getDelineatorTitle = (type: DelineatorType) => {
return 'errorWithSnap'; return 'errorWithSnap';
case DelineatorType.Insights: case DelineatorType.Insights:
return 'insightsFromSnap'; return 'insightsFromSnap';
case DelineatorType.Description:
return 'descriptionFromSnap';
default: default:
return 'contentFromSnap'; return 'contentFromSnap';
} }

View File

@ -1,7 +1,7 @@
.snap-install { .snap-install {
box-shadow: none; box-shadow: none;
.headers { .content {
flex: 1; flex: 1;
.loader-container { .loader-container {

View File

@ -84,13 +84,13 @@ export default function SnapInstall({
flexDirection={FLEX_DIRECTION.COLUMN} flexDirection={FLEX_DIRECTION.COLUMN}
> >
<Box <Box
className="headers" className="header"
alignItems={AlignItems.center} alignItems={AlignItems.center}
paddingLeft={4}
paddingRight={4}
flexDirection={FLEX_DIRECTION.COLUMN} flexDirection={FLEX_DIRECTION.COLUMN}
> >
<Box paddingLeft={4} paddingRight={4}> <SnapAuthorship snapId={targetSubjectMetadata.origin} />
<SnapAuthorship snapId={targetSubjectMetadata.origin} />
</Box>
{!hasError && ( {!hasError && (
<Text <Text
variant={TextVariant.headingLg} variant={TextVariant.headingLg}
@ -100,6 +100,8 @@ export default function SnapInstall({
{t('snapInstall')} {t('snapInstall')}
</Text> </Text>
)} )}
</Box>
<Box className="content">
{isLoading && ( {isLoading && (
<Box <Box
className="loader-container" className="loader-container"
@ -116,7 +118,7 @@ export default function SnapInstall({
{hasPermissions && ( {hasPermissions && (
<> <>
<Text <Text
className="headers__permission-description" className="content__permission-description"
paddingBottom={4} paddingBottom={4}
paddingLeft={4} paddingLeft={4}
paddingRight={4} paddingRight={4}

View File

@ -1,7 +1,7 @@
.snap-update { .snap-update {
box-shadow: none; box-shadow: none;
.headers { .content {
flex: 1; flex: 1;
.loader-container { .loader-container {

View File

@ -90,7 +90,7 @@ export default function SnapUpdate({
flexDirection={FLEX_DIRECTION.COLUMN} flexDirection={FLEX_DIRECTION.COLUMN}
> >
<Box <Box
className="headers" className="header"
paddingLeft={4} paddingLeft={4}
paddingRight={4} paddingRight={4}
alignItems={AlignItems.center} alignItems={AlignItems.center}
@ -106,6 +106,8 @@ export default function SnapUpdate({
{t('snapUpdate')} {t('snapUpdate')}
</Text> </Text>
)} )}
</Box>
<Box className="content">
{isLoading && ( {isLoading && (
<Box <Box
className="loader-container" className="loader-container"
@ -122,8 +124,10 @@ export default function SnapUpdate({
{hasPermissions && ( {hasPermissions && (
<> <>
<Text <Text
className="headers__permission-description" className="content__permission-description"
paddingBottom={4} paddingBottom={4}
paddingLeft={4}
paddingRight={4}
textAlign={TEXT_ALIGN.CENTER} textAlign={TEXT_ALIGN.CENTER}
> >
{t('snapUpdateRequestsPermission', [ {t('snapUpdateRequestsPermission', [

View File

@ -1,19 +1,36 @@
.view-snap { .view-snap {
max-width: 475px; max-width: 475px;
&__version_info { &__description {
&__version-number { &__wrapper {
font-weight: bold; position: relative;
overflow: hidden;
max-height: 5.5rem;
@include screen-md-min {
max-height: 6rem;
}
&.open {
max-height: none;
}
} }
&__link { &__more-button {
vertical-align: top; position: absolute;
bottom: 0;
right: 0;
width: unset;
padding: 0;
padding-left: 32px;
background: linear-gradient(90deg, rgba(255, 255, 255, 0) 0%, var(--color-background-default) 33%);
} }
} }
&__enable {
&__tooltip_wrapper { &__permissions {
max-width: 52px; .permission-cell {
margin: 0;
} }
} }

View File

@ -1,28 +1,26 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useRef, useState } from 'react';
import { useHistory, useLocation } from 'react-router-dom'; import { useHistory, useLocation } from 'react-router-dom';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { import {
SnapCaveatType, SnapCaveatType,
WALLET_SNAP_PERMISSION_KEY, WALLET_SNAP_PERMISSION_KEY,
} from '@metamask/rpc-methods'; } from '@metamask/rpc-methods';
import { getSnapPrefix } from '@metamask/snaps-utils'; import classnames from 'classnames';
import Button from '../../../../components/ui/button'; import Button from '../../../../components/ui/button';
import { useI18nContext } from '../../../../hooks/useI18nContext'; import { useI18nContext } from '../../../../hooks/useI18nContext';
import { import {
Size, Color,
FLEX_WRAP,
TextColor, TextColor,
TextVariant, TextVariant,
} from '../../../../helpers/constants/design-system'; } from '../../../../helpers/constants/design-system';
import SnapAuthorship from '../../../../components/app/snaps/snap-authorship'; import SnapAuthorship from '../../../../components/app/snaps/snap-authorship';
import Box from '../../../../components/ui/box'; import Box from '../../../../components/ui/box';
import SnapRemoveWarning from '../../../../components/app/snaps/snap-remove-warning'; import SnapRemoveWarning from '../../../../components/app/snaps/snap-remove-warning';
import ToggleButton from '../../../../components/ui/toggle-button';
import ConnectedSitesList from '../../../../components/app/connected-sites-list'; import ConnectedSitesList from '../../../../components/app/connected-sites-list';
import Tooltip from '../../../../components/ui/tooltip';
import { SNAPS_LIST_ROUTE } from '../../../../helpers/constants/routes'; import { SNAPS_LIST_ROUTE } from '../../../../helpers/constants/routes';
import { import {
disableSnap,
enableSnap,
removeSnap, removeSnap,
removePermissionsFor, removePermissionsFor,
updateCaveat, updateCaveat,
@ -34,18 +32,17 @@ import {
getPermissionSubjects, getPermissionSubjects,
getTargetSubjectMetadata, getTargetSubjectMetadata,
} from '../../../../selectors'; } from '../../../../selectors';
import { import { getSnapName } from '../../../../helpers/utils/util';
formatDate, import { Text, BUTTON_TYPES } from '../../../../components/component-library';
getSnapName,
removeSnapIdPrefix,
} from '../../../../helpers/utils/util';
import { ButtonLink, Text } from '../../../../components/component-library';
import SnapPermissionsList from '../../../../components/app/snaps/snap-permissions-list'; import SnapPermissionsList from '../../../../components/app/snaps/snap-permissions-list';
import { SnapDelineator } from '../../../../components/app/snaps/snap-delineator';
import { DelineatorType } from '../../../../helpers/constants/flask';
function ViewSnap() { function ViewSnap() {
const t = useI18nContext(); const t = useI18nContext();
const history = useHistory(); const history = useHistory();
const location = useLocation(); const location = useLocation();
const descriptionRef = useRef(null);
const { pathname } = location; const { pathname } = location;
// The snap ID is in URI-encoded form in the last path segment of the URL. // The snap ID is in URI-encoded form in the last path segment of the URL.
const decodedSnapId = decodeURIComponent(pathname.match(/[^/]+$/u)[0]); const decodedSnapId = decodeURIComponent(pathname.match(/[^/]+$/u)[0]);
@ -55,6 +52,8 @@ function ViewSnap() {
.find((snapState) => snapState.id === decodedSnapId); .find((snapState) => snapState.id === decodedSnapId);
const [isShowingRemoveWarning, setIsShowingRemoveWarning] = useState(false); const [isShowingRemoveWarning, setIsShowingRemoveWarning] = useState(false);
const [isDescriptionOpen, setIsDescriptionOpen] = useState(false);
const [isOverflowing, setIsOverflowing] = useState(false);
useEffect(() => { useEffect(() => {
if (!snap) { if (!snap) {
@ -62,6 +61,14 @@ function ViewSnap() {
} }
}, [history, snap]); }, [history, snap]);
useEffect(() => {
setIsOverflowing(
descriptionRef.current &&
descriptionRef.current.offsetHeight <
descriptionRef.current.scrollHeight,
);
}, [descriptionRef]);
const connectedSubjects = useSelector((state) => const connectedSubjects = useSelector((state) =>
getSubjectsWithSnapPermission(state, snap?.id), getSubjectsWithSnapPermission(state, snap?.id),
); );
@ -74,14 +81,6 @@ function ViewSnap() {
); );
const dispatch = useDispatch(); const dispatch = useDispatch();
const onToggle = () => {
if (snap.enabled) {
dispatch(disableSnap(snap.id));
} else {
dispatch(enableSnap(snap.id));
}
};
const onDisconnect = (connectedOrigin, snapId) => { const onDisconnect = (connectedOrigin, snapId) => {
const caveatValue = const caveatValue =
subjects[connectedOrigin].permissions[WALLET_SNAP_PERMISSION_KEY] subjects[connectedOrigin].permissions[WALLET_SNAP_PERMISSION_KEY]
@ -110,121 +109,51 @@ function ViewSnap() {
return null; return null;
} }
const versionHistory = snap.versionHistory ?? [];
const installInfo = versionHistory.length
? versionHistory[versionHistory.length - 1]
: undefined;
const packageName = snap.id && removeSnapIdPrefix(snap.id);
const snapPrefix = snap.id && getSnapPrefix(snap.id);
const isNPM = snapPrefix === 'npm:';
const url = isNPM
? `https://www.npmjs.com/package/${packageName}`
: packageName;
const snapName = getSnapName(snap.id, targetSubjectMetadata); const snapName = getSnapName(snap.id, targetSubjectMetadata);
const shouldDisplayMoreButton = isOverflowing && !isDescriptionOpen;
const handleMoreClick = () => {
setIsDescriptionOpen(true);
};
return ( return (
<Box <Box
className="view-snap" className="view-snap"
paddingBottom={8} paddingBottom={[4, 8]}
paddingLeft={3} paddingTop={[4, 8]}
paddingRight={3} paddingLeft={4}
paddingRight={4}
> >
<Box <SnapAuthorship snapId={snap.id} snap={snap} expanded />
className="view-snap__header" <Box className="view-snap__description" marginTop={[4, 7]}>
paddingTop={8} <SnapDelineator type={DelineatorType.Description} snapName={snapName}>
marginLeft={4} <Box
marginRight={4} className={classnames('view-snap__description__wrapper', {
> open: isDescriptionOpen,
<SnapAuthorship snapId={snap.id} /> })}
</Box> ref={descriptionRef}
<Box
className="view-snap__description"
marginTop={4}
marginLeft={4}
marginRight={4}
>
<Text variant={TextVariant.bodyMd} color={TextColor.textDefault}>
{snap.manifest.description}
</Text>
</Box>
<Box
className="view-snap__version_info"
marginTop={2}
marginLeft={4}
marginRight={4}
>
<Text variant={TextVariant.bodyMd} color={TextColor.textDefault}>
{`${t('youInstalled')} `}
<span className="view-snap__version_info__version-number">
v{snap.version}
</span>
{` ${t('ofTextNofM')} `}
<ButtonLink
size={Size.auto}
href={url}
target="_blank"
className="view-snap__version_info__link"
> >
{packageName} <Text>{snap?.manifest.description}</Text>
</ButtonLink> {shouldDisplayMoreButton && (
{installInfo && ` ${t('from').toLowerCase()} `} <Button
{installInfo && ( className="view-snap__description__more-button"
<ButtonLink type={BUTTON_TYPES.LINK}
size={Size.auto} onClick={handleMoreClick}
href={installInfo.origin} >
target="_blank" <Text color={Color.infoDefault}>{t('more')}</Text>
className="view-snap__version_info__link" </Button>
> )}
{installInfo.origin} </Box>
</ButtonLink> </SnapDelineator>
)}
{installInfo &&
` ${t('on').toLowerCase()} ${formatDate(
installInfo.date,
'dd MMM yyyy',
)}`}
.
</Text>
</Box>
<Box
className="view-snap__enable"
marginTop={12}
marginLeft={4}
marginRight={4}
>
<Text variant={TextVariant.bodyLgMedium}>{t('enableSnap')}</Text>
<Text
variant={TextVariant.bodyMd}
color={TextColor.textDefault}
marginBottom={4}
>
{t('enableSnapDescription')}
</Text>
<Box className="view-snap__enable__tooltip_wrapper">
<Tooltip interactive position="left" html={t('snapsToggle')}>
<ToggleButton
value={snap.enabled}
onToggle={onToggle}
className="view-snap__toggle-button"
/>
</Tooltip>
</Box>
</Box> </Box>
<Box className="view-snap__permissions" marginTop={12}> <Box className="view-snap__permissions" marginTop={12}>
<Text variant={TextVariant.bodyLgMedium} marginLeft={4} marginRight={4}> <Text variant={TextVariant.bodyLgMedium}>{t('permissions')}</Text>
{t('permissions')}
</Text>
<SnapPermissionsList <SnapPermissionsList
permissions={permissions ?? {}} permissions={permissions ?? {}}
targetSubjectMetadata={targetSubjectMetadata} targetSubjectMetadata={targetSubjectMetadata}
/> />
</Box> </Box>
<Box <Box className="view-snap__connected-sites" marginTop={12}>
className="view-snap__connected-sites"
marginTop={12}
marginLeft={4}
marginRight={4}
>
<Text variant={TextVariant.bodyLgMedium} marginBottom={4}> <Text variant={TextVariant.bodyLgMedium} marginBottom={4}>
{t('connectedSites')} {t('connectedSites')}
</Text> </Text>
@ -235,12 +164,7 @@ function ViewSnap() {
}} }}
/> />
</Box> </Box>
<Box <Box className="view-snap__remove" marginTop={12}>
className="view-snap__remove"
marginTop={12}
marginLeft={4}
marginRight={4}
>
<Text variant={TextVariant.bodyLgMedium} color={TextColor.textDefault}> <Text variant={TextVariant.bodyLgMedium} color={TextColor.textDefault}>
{t('removeSnap')} {t('removeSnap')}
</Text> </Text>
@ -253,7 +177,13 @@ function ViewSnap() {
type="danger" type="danger"
onClick={() => setIsShowingRemoveWarning(true)} onClick={() => setIsShowingRemoveWarning(true)}
> >
<Text variant={TextVariant.bodyMd} color={TextColor.errorDefault}> <Text
variant={TextVariant.bodyMd}
color={TextColor.errorDefault}
flexWrap={FLEX_WRAP.NO_WRAP}
ellipsis
style={{ overflow: 'hidden' }}
>
{`${t('remove')} ${snapName}`} {`${t('remove')} ${snapName}`}
</Text> </Text>
</Button> </Button>

View File

@ -48,12 +48,7 @@ describe('ViewSnap', () => {
// Snap version info // Snap version info
expect(getByText('v5.1.2')).toBeDefined(); expect(getByText('v5.1.2')).toBeDefined();
// Enable Snap // Enable Snap
expect(getByText('Enable snap')).toBeDefined(); expect(getByText('Enable')).toBeDefined();
expect(
getByText(
'Your installed snap will only have access to its permissions and run if its enabled.',
),
).toBeDefined();
expect(container.getElementsByClassName('toggle-button')?.length).toBe(1); expect(container.getElementsByClassName('toggle-button')?.length).toBe(1);
// Permissions // Permissions
expect(getByText('Permissions')).toBeDefined(); expect(getByText('Permissions')).toBeDefined();