From df8eb494fe0228b634f234070f443a5c37557cb8 Mon Sep 17 00:00:00 2001 From: Brad Decker Date: Fri, 24 Jul 2020 11:26:45 -0500 Subject: [PATCH 001/107] Updates Typography Variables and styles. (#9017) Co-authored-by: Mark Stacey --- .../add-to-addressbook-modal/index.scss | 2 +- .../app/modals/new-account-modal/index.scss | 2 +- .../app/permission-page-container/index.scss | 4 +- .../app/permissions-connect-footer/index.scss | 3 +- .../app/permissions-connect-header/index.scss | 4 +- ui/app/components/ui/button/buttons.scss | 7 +- ui/app/components/ui/list-item/index.scss | 2 +- ui/app/components/ui/popover/index.scss | 7 +- ui/app/css/itcss/components/modal.scss | 3 +- ui/app/css/itcss/settings/index.scss | 1 - ui/app/css/itcss/settings/typography.scss | 84 ----------- ui/app/css/itcss/settings/variables.scss | 69 ---------- ui/app/css/variables/index.scss | 1 + ui/app/css/variables/typography.scss | 130 ++++++++++++++++++ ui/app/pages/home/index.scss | 3 +- .../choose-account/index.scss | 13 +- ui/app/pages/permissions-connect/index.scss | 5 +- .../permissions-connect/redirect/index.scss | 2 +- ui/app/pages/send/send.scss | 8 +- 19 files changed, 159 insertions(+), 191 deletions(-) delete mode 100644 ui/app/css/itcss/settings/typography.scss create mode 100644 ui/app/css/variables/typography.scss diff --git a/ui/app/components/app/modals/add-to-addressbook-modal/index.scss b/ui/app/components/app/modals/add-to-addressbook-modal/index.scss index b5e3b9ae7738..64d0f733641d 100644 --- a/ui/app/components/app/modals/add-to-addressbook-modal/index.scss +++ b/ui/app/components/app/modals/add-to-addressbook-modal/index.scss @@ -9,7 +9,7 @@ border-bottom: 1px solid $Grey-100; &__header { - @extend %h3; + @extend %H3; } } diff --git a/ui/app/components/app/modals/new-account-modal/index.scss b/ui/app/components/app/modals/new-account-modal/index.scss index 31dfff92d58e..97d6210b80c4 100644 --- a/ui/app/components/app/modals/new-account-modal/index.scss +++ b/ui/app/components/app/modals/new-account-modal/index.scss @@ -9,7 +9,7 @@ border-bottom: 1px solid $Grey-100; &__header { - @extend %header--18; + @extend %H4; font-weight: bold; display: flex; diff --git a/ui/app/components/app/permission-page-container/index.scss b/ui/app/components/app/permission-page-container/index.scss index c26189e0c094..458eb5b703bd 100644 --- a/ui/app/components/app/permission-page-container/index.scss +++ b/ui/app/components/app/permission-page-container/index.scss @@ -31,7 +31,7 @@ } &__title { - @extend %header--18; + @extend %H4; line-height: 25px; text-align: center; @@ -84,7 +84,7 @@ } &__permissions-header { - @extend %content-text; + @extend %H6; line-height: 20px; color: #6a737d; diff --git a/ui/app/components/app/permissions-connect-footer/index.scss b/ui/app/components/app/permissions-connect-footer/index.scss index ac650841743e..229da218a389 100644 --- a/ui/app/components/app/permissions-connect-footer/index.scss +++ b/ui/app/components/app/permissions-connect-footer/index.scss @@ -5,9 +5,8 @@ align-items: center; &__text { - @extend %content-text; + @extend %H7; - font-size: 12px; line-height: 17px; color: #6a737d; display: flex; diff --git a/ui/app/components/app/permissions-connect-header/index.scss b/ui/app/components/app/permissions-connect-header/index.scss index 1b29ec3e0611..ca8d4c0e6c4f 100644 --- a/ui/app/components/app/permissions-connect-header/index.scss +++ b/ui/app/components/app/permissions-connect-header/index.scss @@ -26,7 +26,7 @@ } &__title { - @extend %header--24; + @extend %H3; text-align: center; color: $Black-100; @@ -35,7 +35,7 @@ &__text, &__subtitle { - @extend %content-text; + @extend %H6; text-align: center; color: $Grey-500; diff --git a/ui/app/components/ui/button/buttons.scss b/ui/app/components/ui/button/buttons.scss index 2fdc1da013e9..83f02f7e0ba8 100644 --- a/ui/app/components/ui/button/buttons.scss +++ b/ui/app/components/ui/button/buttons.scss @@ -11,7 +11,7 @@ $hover-orange: #ffd3b5; $warning-light-orange: #f8b588; %button { - @include h6; + @extend %H6; font-weight: 500; font-family: Roboto, Arial; @@ -34,7 +34,7 @@ $warning-light-orange: #f8b588; } %link { - @include h4; + @extend %H4; color: $Blue-500; line-height: 1.25rem; @@ -60,8 +60,7 @@ $warning-light-orange: #f8b588; %small-link { @extend %link; - - @include h6; + @extend %H6; } .button { diff --git a/ui/app/components/ui/list-item/index.scss b/ui/app/components/ui/list-item/index.scss index a26efcb4ca73..cc9edd87fe7e 100644 --- a/ui/app/components/ui/list-item/index.scss +++ b/ui/app/components/ui/list-item/index.scss @@ -5,7 +5,7 @@ background: #fff; padding: 24px 16px; - @extend %font; + @extend %Paragraph; border-top: 1px solid $mercury; border-bottom: 1px solid $mercury; diff --git a/ui/app/components/ui/popover/index.scss b/ui/app/components/ui/popover/index.scss index 1c3d8744c262..af3d7b279dcf 100644 --- a/ui/app/components/ui/popover/index.scss +++ b/ui/app/components/ui/popover/index.scss @@ -33,10 +33,9 @@ align-items: center; justify-content: space-between; - @extend %font; + @extend %H4; font-weight: bold; - font-size: 18px; line-height: 25px; padding-bottom: 8px; @@ -52,10 +51,8 @@ } &__subtitle { - @extend %font; + @extend %H6; - font-weight: normal; - font-size: 14px; line-height: 20px; } diff --git a/ui/app/css/itcss/components/modal.scss b/ui/app/css/itcss/components/modal.scss index 567e7a418053..3e804498968e 100644 --- a/ui/app/css/itcss/components/modal.scss +++ b/ui/app/css/itcss/components/modal.scss @@ -287,8 +287,7 @@ } &__button { - @include paragraph; - + @extend %Paragraph; @extend %button; width: 141px; diff --git a/ui/app/css/itcss/settings/index.scss b/ui/app/css/itcss/settings/index.scss index bbf79c80f939..a7152a435356 100644 --- a/ui/app/css/itcss/settings/index.scss +++ b/ui/app/css/itcss/settings/index.scss @@ -1,2 +1 @@ @import './variables'; -@import './typography'; diff --git a/ui/app/css/itcss/settings/typography.scss b/ui/app/css/itcss/settings/typography.scss deleted file mode 100644 index 27390b6ee564..000000000000 --- a/ui/app/css/itcss/settings/typography.scss +++ /dev/null @@ -1,84 +0,0 @@ -$fa-font-path: 'fonts/fontawesome'; - -@import '../../../../../node_modules/@fortawesome/fontawesome-free/scss/fontawesome'; -@import '../../../../../node_modules/@fortawesome/fontawesome-free/scss/solid'; -@import '../../../../../node_modules/@fortawesome/fontawesome-free/scss/regular'; - -@font-face { - font-family: 'Roboto'; - font-style: normal; - font-weight: 100; - src: local('Roboto Thin'), local('Roboto-Thin'), url('fonts/Roboto/Roboto-Thin.ttf') format('truetype'); -} - -@font-face { - font-family: 'Roboto'; - font-style: normal; - font-weight: 300; - src: local('Roboto Light'), local('Roboto-Light'), url('fonts/Roboto/Roboto-Light.ttf') format('truetype'); -} - -@font-face { - font-family: 'Roboto'; - font-style: normal; - font-weight: 400; - src: local('Roboto'), local('Roboto-Regular'), url('fonts/Roboto/Roboto-Regular.ttf') format('truetype'); -} - -@font-face { - font-family: 'Roboto'; - font-style: normal; - font-weight: 500; - src: local('Roboto Medium'), local('Roboto-Medium'), url('fonts/Roboto/Roboto-Medium.ttf') format('truetype'); -} - -@font-face { - font-family: 'Roboto'; - font-style: normal; - font-weight: 700; - src: local('Roboto Bold'), local('Roboto-Bold'), url('fonts/Roboto/Roboto-Bold.ttf') format('truetype'); -} - -@font-face { - font-family: 'Roboto'; - font-style: normal; - font-weight: 900; - src: local('Roboto Black'), local('Roboto-Black'), url('fonts/Roboto/Roboto-Black.ttf') format('truetype'); -} - -@mixin fontScale($weight: 400, $size: 1rem) { - font-weight: $weight; - font-size: $size; -} - -@mixin h1($weight: 400, $size: 2.5rem) { - @include fontScale($weight, $size); -} - -@mixin h2($weight: 400, $size: 2rem) { - @include fontScale($weight, $size); -} - -@mixin h3($weight: 400, $size: 1.5rem) { - @include fontScale($weight, $size); -} - -@mixin h4($weight: 400, $size: 1.125rem) { - @include fontScale($weight, $size); -} - -@mixin h5($weight: 400, $size: 1rem) { - @include fontScale($weight, $size); -} - -@mixin h6($weight: 400, $size: 0.875rem) { - @include fontScale($weight, $size); -} - -@mixin h7($weight: 400, $size: 0.75rem) { - @include fontScale($weight, $size); -} - -@mixin paragraph($weight: 400, $size: 1rem) { - @include fontScale($weight, $size); -} diff --git a/ui/app/css/itcss/settings/variables.scss b/ui/app/css/itcss/settings/variables.scss index 9a40ec9ffc01..fa6727ee292d 100644 --- a/ui/app/css/itcss/settings/variables.scss +++ b/ui/app/css/itcss/settings/variables.scss @@ -105,42 +105,6 @@ $break-small: 575px; $break-midpoint: 780px; $break-large: 576px; - -$primary-font-type: Roboto; - - -// Font Sizes -%h3 { - font-size: 1.5rem; - line-height: 2.125rem; - font-weight: 400; -} - -%h4 { - font-size: 1.125rem; - line-height: 1.3125rem; - font-weight: 400; -} - -%h5 { - font-size: 1rem; - line-height: 1.25rem; - font-weight: 400; -} - -%h6 { - font-size: 0.875rem; - line-height: 1.25rem; - font-weight: 400; -} - -%h8 { - font-size: 0.75rem; - line-height: 1.0625rem; - font-weight: 400; -} - - /* Spacing Variables */ @@ -176,36 +140,3 @@ $xxlarge-spacing: 64px; border-color: $Blue-500; } } - -// Font mixin - -%font { - font-family: Roboto; - font-style: normal; - font-weight: normal; - color: $Grey-800; -} - -%font--bold { - @extend %font; - - font-weight: bold; -} - -%header--18 { - @extend %font; - - font-size: 18px; -} - -%header--24 { - @extend %font; - - font-size: 24px; -} - -%content-text { - @extend %font; - - font-size: 14px; -} diff --git a/ui/app/css/variables/index.scss b/ui/app/css/variables/index.scss index 7aa2d674f096..8d79fd9c7abb 100644 --- a/ui/app/css/variables/index.scss +++ b/ui/app/css/variables/index.scss @@ -1 +1,2 @@ @import './colors.scss'; +@import './typography.scss'; diff --git a/ui/app/css/variables/typography.scss b/ui/app/css/variables/typography.scss new file mode 100644 index 000000000000..12bc4fccbc16 --- /dev/null +++ b/ui/app/css/variables/typography.scss @@ -0,0 +1,130 @@ +$fa-font-path: 'fonts/fontawesome'; + +@import '../../../../node_modules/@fortawesome/fontawesome-free/scss/fontawesome'; +@import '../../../../node_modules/@fortawesome/fontawesome-free/scss/solid'; +@import '../../../../node_modules/@fortawesome/fontawesome-free/scss/regular'; + +@font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 100; + src: local('Roboto Thin'), local('Roboto-Thin'), url('fonts/Roboto/Roboto-Thin.ttf') format('truetype'); +} + +@font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 300; + src: local('Roboto Light'), local('Roboto-Light'), url('fonts/Roboto/Roboto-Light.ttf') format('truetype'); +} + +@font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 400; + src: local('Roboto'), local('Roboto-Regular'), url('fonts/Roboto/Roboto-Regular.ttf') format('truetype'); +} + +@font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 500; + src: local('Roboto Medium'), local('Roboto-Medium'), url('fonts/Roboto/Roboto-Medium.ttf') format('truetype'); +} + +@font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 700; + src: local('Roboto Bold'), local('Roboto-Bold'), url('fonts/Roboto/Roboto-Bold.ttf') format('truetype'); +} + +@font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 900; + src: local('Roboto Black'), local('Roboto-Black'), url('fonts/Roboto/Roboto-Black.ttf') format('truetype'); +} + +$font-family: Roboto, Helvetica, Arial, sans-serif; + +// Typography +%H1 { + font-style: normal; + font-weight: normal; + font-size: 2.5rem; + font-family: $font-family; + line-height: 140%; +} + +%H2 { + font-style: normal; + font-weight: normal; + font-size: 2rem; + font-family: $font-family; + line-height: 140%; +} + +%H3 { + font-style: normal; + font-weight: normal; + font-size: 1.5rem; + font-family: $font-family; + line-height: 140%; +} + +%H4 { + font-style: normal; + font-weight: normal; + font-size: 1.125rem; + font-family: $font-family; + line-height: 140%; +} + +%H5 { + font-style: normal; + font-weight: normal; + font-size: 1rem; + font-family: $font-family; + line-height: 140%; +} + +%H6 { + font-style: normal; + font-weight: normal; + font-size: 0.875rem; + font-family: $font-family; +} + +%Paragraph { + font-style: normal; + font-weight: normal; + font-size: 1rem; + font-family: $font-family; + line-height: 140%; +} + +%H7 { + font-style: normal; + font-weight: normal; + font-size: 0.75rem; + font-family: $font-family; + line-height: 140%; +} + +%H8 { + font-style: normal; + font-weight: normal; + font-size: 0.625rem; + font-family: $font-family; + line-height: 140%; +} + +%H9 { + font-style: normal; + font-weight: normal; + font-size: 0.5rem; + font-family: $font-family; + line-height: 140%; +} + diff --git a/ui/app/pages/home/index.scss b/ui/app/pages/home/index.scss index 814223bf399b..37911bdc6c6d 100644 --- a/ui/app/pages/home/index.scss +++ b/ui/app/pages/home/index.scss @@ -43,10 +43,11 @@ display: flex; flex-direction: column; - @extend %content-text; + @extend %H6; padding-left: 24px; padding-right: 24px; + color: $Grey-800; div { margin-bottom: 20px; diff --git a/ui/app/pages/permissions-connect/choose-account/index.scss b/ui/app/pages/permissions-connect/choose-account/index.scss index 4f71942c175a..b79c9c923bd2 100644 --- a/ui/app/pages/permissions-connect/choose-account/index.scss +++ b/ui/app/pages/permissions-connect/choose-account/index.scss @@ -24,13 +24,13 @@ &__title { - @extend %header--18; + @extend %H4; } &__text, &__text-blue, &__text-grey { - @extend %content-text; + @extend %H6; } &__text-blue { @@ -120,7 +120,7 @@ } &__label { - @extend %content-text; + @extend %H6; color: $Black-100; text-overflow: ellipsis; @@ -129,17 +129,14 @@ } &__balance { - @extend %content-text; + @extend %H7; - font-size: 12px; color: $Grey-500; } &__last-connected { - @extend %content-text; + @extend %H8; - font-size: 10px; - line-height: 140.62%; display: flex; flex-direction: column; align-items: flex-end; diff --git a/ui/app/pages/permissions-connect/index.scss b/ui/app/pages/permissions-connect/index.scss index a5f9f1d2d37d..58263d9b489f 100644 --- a/ui/app/pages/permissions-connect/index.scss +++ b/ui/app/pages/permissions-connect/index.scss @@ -24,7 +24,7 @@ } &__back { - @extend %content-text; + @extend %H6; color: $Grey-600; cursor: pointer; @@ -35,9 +35,8 @@ } &__page-count { - @extend %content-text; + @extend %H7; - font-size: 12px; color: #6a737d; grid-column: 2; justify-self: end; diff --git a/ui/app/pages/permissions-connect/redirect/index.scss b/ui/app/pages/permissions-connect/redirect/index.scss index b2cc68554c42..2151385ed1c4 100644 --- a/ui/app/pages/permissions-connect/redirect/index.scss +++ b/ui/app/pages/permissions-connect/redirect/index.scss @@ -4,7 +4,7 @@ justify-content: center; &__result { - @extend %header--24; + @extend %H3; position: absolute; top: 30%; diff --git a/ui/app/pages/send/send.scss b/ui/app/pages/send/send.scss index e9b8cd265437..eb64a4105679 100644 --- a/ui/app/pages/send/send.scss +++ b/ui/app/pages/send/send.scss @@ -6,7 +6,7 @@ padding: 14px 0 3px 0; .page-container__title { - @extend %h4; + @extend %H4; text-align: center; } @@ -91,7 +91,7 @@ } &__group-label { - @extend %h8; + @extend %H8; background-color: $Grey-000; color: $Grey-600; @@ -136,7 +136,7 @@ } &__subtitle { - @extend %h8; + @extend %H8; color: $Grey-500; } @@ -186,7 +186,7 @@ } &__input { - @extend %h6; + @extend %H6; flex: 1 1 auto; width: 0; From b4663eb78b975886aeef462a2688bbdfa49f222d Mon Sep 17 00:00:00 2001 From: ryanml Date: Fri, 24 Jul 2020 15:47:40 -0700 Subject: [PATCH 002/107] Fixes MetaMask/metamask-extension#8626 - verifies password on requesting seed phrase (#9063) --- app/scripts/metamask-controller.js | 10 ++++++++++ package.json | 2 +- ui/app/store/actions.js | 4 ++-- yarn.lock | 8 ++++---- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 3f1511333e6b..5aa4539e325c 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -478,6 +478,7 @@ export default class MetamaskController extends EventEmitter { // vault management submitPassword: nodeify(this.submitPassword, this), + verifyPassword: nodeify(this.verifyPassword, this), // network management setProviderType: nodeify(networkController.setProviderType, networkController), @@ -808,6 +809,15 @@ export default class MetamaskController extends EventEmitter { return this.keyringController.fullUpdate() } + /** + * Submits a user's password to check its validity. + * + * @param {string} password The user's password + */ + async verifyPassword (password) { + await this.keyringController.verifyPassword(password) + } + /** * @type Identity * @property {string} name - The account nickname. diff --git a/package.json b/package.json index d0b2a48b63e3..a433b99cd6fe 100644 --- a/package.json +++ b/package.json @@ -98,7 +98,7 @@ "eth-json-rpc-filters": "^4.1.1", "eth-json-rpc-infura": "^4.0.2", "eth-json-rpc-middleware": "^5.0.2", - "eth-keyring-controller": "^6.0.1", + "eth-keyring-controller": "^6.1.0", "eth-method-registry": "^1.2.0", "eth-phishing-detect": "^1.1.4", "eth-query": "^2.1.2", diff --git a/ui/app/store/actions.js b/ui/app/store/actions.js index abb855233fa3..49a43da246a4 100644 --- a/ui/app/store/actions.js +++ b/ui/app/store/actions.js @@ -168,7 +168,7 @@ export function createNewVault (password) { export function verifyPassword (password) { return new Promise((resolve, reject) => { - background.submitPassword(password, (error) => { + background.verifyPassword(password, (error) => { if (error) { return reject(error) } @@ -193,7 +193,7 @@ export function verifySeedPhrase () { export function requestRevealSeedWords (password) { return async (dispatch) => { dispatch(showLoadingIndication()) - log.debug(`background.submitPassword`) + log.debug(`background.verifyPassword`) try { await verifyPassword(password) diff --git a/yarn.lock b/yarn.lock index 0290b9ac1b63..30f8e5ecf1d4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10131,10 +10131,10 @@ eth-keyring-controller@^5.3.0, eth-keyring-controller@^5.6.1: loglevel "^1.5.0" obs-store "^4.0.3" -eth-keyring-controller@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/eth-keyring-controller/-/eth-keyring-controller-6.0.1.tgz#6a4cdd5802b0587320c711be6c1752b2a88221aa" - integrity sha512-60j71F1HgLcvwzg7U5R45bA/kgQSUlmiZrsUIIhW4qS7QOYqJn0OQ64enf0ZaxMMPVVcKSfCDersYJiqm/yrlw== +eth-keyring-controller@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/eth-keyring-controller/-/eth-keyring-controller-6.1.0.tgz#dc9313d0b793e085dc1badf84dd4f5e3004e127e" + integrity sha512-wPxH++98VDBcDv9YkPzxhZC0gF1ixuRbyKR2u/NOT/roBpNQDe4reqyllBRC7jhPehiKnRxzf7r6HEyirRnPxQ== dependencies: bip39 "^2.4.0" bluebird "^3.5.0" From ef1b1d57383c90b8a192828857b43615e1fad7bf Mon Sep 17 00:00:00 2001 From: Thomas Huang Date: Mon, 27 Jul 2020 11:33:25 -0700 Subject: [PATCH 003/107] Fix popup/notification when browser is in fullscreen, primarily on macOS. (#9075) * Fix popup/notification when browser is in fullscreen, primarily on OSX. The issue was reported internally via Slack. User was running Mac OSX Chrome in fullscreen mode where Chrome is created in a new Desktop workspace. The issue reproduced on OSX Chrome in fullscreen/maximized view overrides the explicitly set width and height for `windows.create()`. Possibly not overrides, but creates a window based off of the window that it was created from. Found a related [Chromium bug](https://bugs.chromium.org/p/chromium/issues/detail?id=263092&q=window%20create%20width%20os%3DMac&can=2). The fullscreen `popup.left` pixel will calculate the window position incorrectly since we set and assume the width of the created window. The incorrect `left` position the window and transition the focus Desktop/Workspace incorrectly and make is seem to lose focus of the new window/workspace. Incidentally this will make the popup full width/height, and create a new workspace for the view, which we have no control over until Chrome fixes it. This will check if the popup is 'fullscreen', which it gets passed from the origin window, if so then don't reposition the window. If Chrome fixes the issue we can revert this change. * Feedback commit Co-authored-by: Mark Stacey Co-authored-by: Mark Stacey --- app/scripts/lib/notification-manager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/scripts/lib/notification-manager.js b/app/scripts/lib/notification-manager.js index 497100354587..631ae3290257 100644 --- a/app/scripts/lib/notification-manager.js +++ b/app/scripts/lib/notification-manager.js @@ -56,7 +56,7 @@ export default class NotificationManager { }) // Firefox currently ignores left/top for create, but it works for update - if (popupWindow.left !== left) { + if (popupWindow.left !== left && popupWindow.state !== 'fullscreen') { await this.platform.updateWindowPosition(popupWindow.id, left, top) } this._popupId = popupWindow.id From 57715a8da194f9e5d2ad4b1a9913e57a8fda99a1 Mon Sep 17 00:00:00 2001 From: Brad Decker Date: Mon, 27 Jul 2020 15:01:21 -0500 Subject: [PATCH 004/107] support longer text in network dropdown (#9085) --- ui/app/css/itcss/components/network.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/app/css/itcss/components/network.scss b/ui/app/css/itcss/components/network.scss index 194a59fbdba6..02d0dcfcd35f 100644 --- a/ui/app/css/itcss/components/network.scss +++ b/ui/app/css/itcss/components/network.scss @@ -175,7 +175,7 @@ } .network-dropdown-content { - height: 36px; + min-height: 36px; width: 265px; color: $dusty-gray; font-family: Roboto; From d9f07a796d220b7f0b4fc7f391b679dfc1a4986f Mon Sep 17 00:00:00 2001 From: ryanml Date: Mon, 27 Jul 2020 14:03:26 -0700 Subject: [PATCH 005/107] Complete onboarding upon importing/verifying seed (#8873) Fixes #8679 --- .../import-with-seed-phrase.component.js | 6 ++++-- .../import-with-seed-phrase.container.js | 2 ++ .../first-time-flow/end-of-flow/end-of-flow.component.js | 4 +--- .../first-time-flow/end-of-flow/end-of-flow.container.js | 9 +-------- .../end-of-flow/tests/end-of-flow.test.js | 2 -- .../confirm-seed-phrase/confirm-seed-phrase.component.js | 8 +++++++- .../confirm-seed-phrase/confirm-seed-phrase.container.js | 2 ++ .../tests/confirm-seed-phrase-component.test.js | 1 + 8 files changed, 18 insertions(+), 16 deletions(-) diff --git a/ui/app/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js b/ui/app/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js index b130889155fe..2c3dc1c78193 100644 --- a/ui/app/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js +++ b/ui/app/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js @@ -19,6 +19,7 @@ export default class ImportWithSeedPhrase extends PureComponent { onSubmit: PropTypes.func.isRequired, setSeedPhraseBackedUp: PropTypes.func, initializeThreeBox: PropTypes.func, + completeOnboarding: PropTypes.func, } state = { @@ -119,7 +120,7 @@ export default class ImportWithSeedPhrase extends PureComponent { } const { password, seedPhrase } = this.state - const { history, onSubmit, setSeedPhraseBackedUp, initializeThreeBox } = this.props + const { history, onSubmit, setSeedPhraseBackedUp, initializeThreeBox, completeOnboarding } = this.props try { await onSubmit(password, this.parseSeedPhrase(seedPhrase)) @@ -131,7 +132,8 @@ export default class ImportWithSeedPhrase extends PureComponent { }, }) - setSeedPhraseBackedUp(true).then(() => { + setSeedPhraseBackedUp(true).then(async () => { + await completeOnboarding() initializeThreeBox() history.push(INITIALIZE_END_OF_FLOW_ROUTE) }) diff --git a/ui/app/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.container.js b/ui/app/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.container.js index 32038e07d8e0..018666b139cc 100644 --- a/ui/app/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.container.js +++ b/ui/app/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.container.js @@ -3,12 +3,14 @@ import ImportWithSeedPhrase from './import-with-seed-phrase.component' import { setSeedPhraseBackedUp, initializeThreeBox, + setCompletedOnboarding, } from '../../../../store/actions' const mapDispatchToProps = (dispatch) => { return { setSeedPhraseBackedUp: (seedPhraseBackupState) => dispatch(setSeedPhraseBackedUp(seedPhraseBackupState)), initializeThreeBox: () => dispatch(initializeThreeBox()), + completeOnboarding: () => dispatch(setCompletedOnboarding()), } } diff --git a/ui/app/pages/first-time-flow/end-of-flow/end-of-flow.component.js b/ui/app/pages/first-time-flow/end-of-flow/end-of-flow.component.js index 275dcc587f8d..a6510553dafe 100644 --- a/ui/app/pages/first-time-flow/end-of-flow/end-of-flow.component.js +++ b/ui/app/pages/first-time-flow/end-of-flow/end-of-flow.component.js @@ -14,7 +14,6 @@ export default class EndOfFlowScreen extends PureComponent { static propTypes = { history: PropTypes.object, - completeOnboarding: PropTypes.func, completionMetaMetricsName: PropTypes.string, onboardingInitiator: PropTypes.exact({ location: PropTypes.string, @@ -23,9 +22,8 @@ export default class EndOfFlowScreen extends PureComponent { } onComplete = async () => { - const { history, completeOnboarding, completionMetaMetricsName, onboardingInitiator } = this.props + const { history, completionMetaMetricsName, onboardingInitiator } = this.props - await completeOnboarding() this.context.metricsEvent({ eventOpts: { category: 'Onboarding', diff --git a/ui/app/pages/first-time-flow/end-of-flow/end-of-flow.container.js b/ui/app/pages/first-time-flow/end-of-flow/end-of-flow.container.js index 61af2fdaa29e..d52f74dbc560 100644 --- a/ui/app/pages/first-time-flow/end-of-flow/end-of-flow.container.js +++ b/ui/app/pages/first-time-flow/end-of-flow/end-of-flow.container.js @@ -1,6 +1,5 @@ import { connect } from 'react-redux' import EndOfFlow from './end-of-flow.component' -import { setCompletedOnboarding } from '../../../store/actions' import { getOnboardingInitiator } from '../../../selectors' const firstTimeFlowTypeNameMap = { @@ -17,10 +16,4 @@ const mapStateToProps = (state) => { } } -const mapDispatchToProps = (dispatch) => { - return { - completeOnboarding: () => dispatch(setCompletedOnboarding()), - } -} - -export default connect(mapStateToProps, mapDispatchToProps)(EndOfFlow) +export default connect(mapStateToProps)(EndOfFlow) diff --git a/ui/app/pages/first-time-flow/end-of-flow/tests/end-of-flow.test.js b/ui/app/pages/first-time-flow/end-of-flow/tests/end-of-flow.test.js index 56f4aba0d25b..2cc5b971085e 100644 --- a/ui/app/pages/first-time-flow/end-of-flow/tests/end-of-flow.test.js +++ b/ui/app/pages/first-time-flow/end-of-flow/tests/end-of-flow.test.js @@ -12,7 +12,6 @@ describe('End of Flow Screen', function () { history: { push: sinon.spy(), }, - completeOnboarding: sinon.spy(), } beforeEach(function () { @@ -30,7 +29,6 @@ describe('End of Flow Screen', function () { endOfFlowButton.simulate('click') setImmediate(() => { - assert(props.completeOnboarding.calledOnce) assert(props.history.push.calledOnceWithExactly(DEFAULT_ROUTE)) done() }) diff --git a/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.component.js b/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.component.js index 18f496ca74e8..8847a0d01f14 100644 --- a/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.component.js +++ b/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.component.js @@ -26,6 +26,7 @@ export default class ConfirmSeedPhrase extends PureComponent { seedPhrase: PropTypes.string, initializeThreeBox: PropTypes.func, setSeedPhraseBackedUp: PropTypes.func, + completeOnboarding: PropTypes.func, } state = { @@ -66,6 +67,10 @@ export default class ConfirmSeedPhrase extends PureComponent { exportAsFile('', this.props.seedPhrase, 'text/plain') } + setOnboardingCompleted = async () => { + await this.props.completeOnboarding() + } + handleSubmit = async () => { const { history, @@ -86,8 +91,9 @@ export default class ConfirmSeedPhrase extends PureComponent { }, }) - setSeedPhraseBackedUp(true).then(() => { + setSeedPhraseBackedUp(true).then(async () => { initializeThreeBox() + this.setOnboardingCompleted() history.push(INITIALIZE_END_OF_FLOW_ROUTE) }) } catch (error) { diff --git a/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.container.js b/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.container.js index 393998f8d54d..176601e49a40 100644 --- a/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.container.js +++ b/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.container.js @@ -3,12 +3,14 @@ import ConfirmSeedPhrase from './confirm-seed-phrase.component' import { setSeedPhraseBackedUp, initializeThreeBox, + setCompletedOnboarding, } from '../../../../store/actions' const mapDispatchToProps = (dispatch) => { return { setSeedPhraseBackedUp: (seedPhraseBackupState) => dispatch(setSeedPhraseBackedUp(seedPhraseBackupState)), initializeThreeBox: () => dispatch(initializeThreeBox()), + completeOnboarding: () => dispatch(setCompletedOnboarding()), } } diff --git a/ui/app/pages/first-time-flow/seed-phrase/tests/confirm-seed-phrase-component.test.js b/ui/app/pages/first-time-flow/seed-phrase/tests/confirm-seed-phrase-component.test.js index 449bd33477a5..a5846a944af9 100644 --- a/ui/app/pages/first-time-flow/seed-phrase/tests/confirm-seed-phrase-component.test.js +++ b/ui/app/pages/first-time-flow/seed-phrase/tests/confirm-seed-phrase-component.test.js @@ -142,6 +142,7 @@ describe('ConfirmSeedPhrase Component', function () { history: { push: pushSpy }, setSeedPhraseBackedUp: () => Promise.resolve(), initializeThreeBox: initialize3BoxSpy, + completeOnboarding: sinon.spy(), }, { metricsEvent: metricsEventSpy, From f6f8e5cc4a4a46fde826043eaefb66304c493ee7 Mon Sep 17 00:00:00 2001 From: Erik Marks <25517051+rekmarks@users.noreply.github.com> Date: Mon, 27 Jul 2020 14:35:09 -0700 Subject: [PATCH 006/107] Robustify permissions controller requestUserApproval tests (#9064) * convert requestUserApproval mock to wrapper --- .../app/controllers/permissions/helpers.js | 43 +++++++++++++------ .../permissions-controller-test.js | 41 +++++++----------- .../permissions-middleware-test.js | 31 ++++++++++++- 3 files changed, 77 insertions(+), 38 deletions(-) diff --git a/test/unit/app/controllers/permissions/helpers.js b/test/unit/app/controllers/permissions/helpers.js index 8879329a458e..0378dc08910a 100644 --- a/test/unit/app/controllers/permissions/helpers.js +++ b/test/unit/app/controllers/permissions/helpers.js @@ -19,25 +19,44 @@ export function grantPermissions (permController, origin, permissions) { } /** - * Sets the underlying rpc-cap requestUserApproval function, and returns - * a promise that's resolved once it has been set. + * Returns a wrapper for the given permissions controller's requestUserApproval + * function, so we don't have to worry about its internals. * - * This function must be called on the given permissions controller every - * time you want such a Promise. As of writing, it's only called once per test. + * @param {PermissionsController} permController - The permissions controller. + * @return {Function} A convenient wrapper for the requestUserApproval function. + */ +export function getRequestUserApprovalHelper (permController) { + /** + * Returns a request object that can be passed to requestUserApproval. + * + * @param {string} id - The internal permissions request ID (not the RPC request ID). + * @param {string} [origin] - The origin of the request, if necessary. + * @returns {Object} The corresponding request object. + */ + return (id, origin = 'defaultOrigin') => { + return permController.permissions.requestUserApproval({ metadata: { id, origin } }) + } +} + +/** + * Returns a Promise that resolves once a pending user approval has been set. + * Calls the underlying requestUserApproval function as normal, and restores it + * once the Promise is resolved. + * + * This function must be called on the permissions controller for each request. * * @param {PermissionsController} - A permissions controller. * @returns {Promise} A Promise that resolves once a pending approval * has been set. */ export function getUserApprovalPromise (permController) { - return new Promise((resolveForCaller) => { - permController.permissions.requestUserApproval = async (req) => { - const { origin, metadata: { id } } = req - - return new Promise((resolve, reject) => { - permController.pendingApprovals.set(id, { origin, resolve, reject }) - resolveForCaller() - }) + const originalFunction = permController.permissions.requestUserApproval + return new Promise((resolveHelperPromise) => { + permController.permissions.requestUserApproval = (req) => { + const userApprovalPromise = originalFunction(req) + permController.permissions.requestUserApproval = originalFunction + resolveHelperPromise() + return userApprovalPromise } }) } diff --git a/test/unit/app/controllers/permissions/permissions-controller-test.js b/test/unit/app/controllers/permissions/permissions-controller-test.js index 98b0cd55ddda..1b4f315a342d 100644 --- a/test/unit/app/controllers/permissions/permissions-controller-test.js +++ b/test/unit/app/controllers/permissions/permissions-controller-test.js @@ -15,6 +15,7 @@ import { } from '../../../../../app/scripts/controllers/permissions' import { + getRequestUserApprovalHelper, grantPermissions, } from './helpers' @@ -58,12 +59,6 @@ const initPermController = (notifications = initNotifications()) => { }) } -const getMockRequestUserApprovalFunction = (permController) => (id, origin) => { - return new Promise((resolve, reject) => { - permController.pendingApprovals.set(id, { origin, resolve, reject }) - }) -} - describe('permissions controller', function () { describe('getAccounts', function () { @@ -951,13 +946,11 @@ describe('permissions controller', function () { describe('approvePermissionsRequest', function () { - let permController, mockRequestUserApproval + let permController, requestUserApproval beforeEach(function () { permController = initPermController() - mockRequestUserApproval = getMockRequestUserApprovalFunction( - permController, - ) + requestUserApproval = getRequestUserApprovalHelper(permController) }) it('does nothing if called on non-existing request', async function () { @@ -994,14 +987,14 @@ describe('permissions controller', function () { PERMS.requests.eth_accounts(), ) - const requestRejection = assert.rejects( - mockRequestUserApproval(REQUEST_IDS.a), + const rejectionPromise = assert.rejects( + requestUserApproval(REQUEST_IDS.a), ERRORS.validatePermittedAccounts.invalidParam(), - 'should reject bad accounts', + 'should reject with "null" accounts', ) await permController.approvePermissionsRequest(request, null) - await requestRejection + await rejectionPromise assert.equal( permController.pendingApprovals.size, 0, @@ -1014,7 +1007,7 @@ describe('permissions controller', function () { const request = PERMS.approvedRequest(REQUEST_IDS.a, {}) const requestRejection = assert.rejects( - mockRequestUserApproval(REQUEST_IDS.a), + requestUserApproval(REQUEST_IDS.a), ERRORS.approvePermissionsRequest.noPermsRequested(), 'should reject if no permissions in request', ) @@ -1036,7 +1029,7 @@ describe('permissions controller', function () { const requestApproval = assert.doesNotReject( async () => { - perms = await mockRequestUserApproval(REQUEST_IDS.a) + perms = await requestUserApproval(REQUEST_IDS.a) }, 'should not reject single valid request', ) @@ -1065,14 +1058,14 @@ describe('permissions controller', function () { const approval1 = assert.doesNotReject( async () => { - perms1 = await mockRequestUserApproval(REQUEST_IDS.a) + perms1 = await requestUserApproval(REQUEST_IDS.a, DOMAINS.a.origin) }, 'should not reject request', ) const approval2 = assert.doesNotReject( async () => { - perms2 = await mockRequestUserApproval(REQUEST_IDS.b) + perms2 = await requestUserApproval(REQUEST_IDS.b, DOMAINS.b.origin) }, 'should not reject request', ) @@ -1105,13 +1098,11 @@ describe('permissions controller', function () { describe('rejectPermissionsRequest', function () { - let permController, mockRequestUserApproval + let permController, requestUserApproval beforeEach(async function () { permController = initPermController() - mockRequestUserApproval = getMockRequestUserApprovalFunction( - permController, - ) + requestUserApproval = getRequestUserApprovalHelper(permController) }) it('does nothing if called on non-existing request', async function () { @@ -1135,7 +1126,7 @@ describe('permissions controller', function () { it('rejects single existing request', async function () { const requestRejection = assert.rejects( - mockRequestUserApproval(REQUEST_IDS.a), + requestUserApproval(REQUEST_IDS.a), ERRORS.rejectPermissionsRequest.rejection(), 'should reject with expected error', ) @@ -1152,13 +1143,13 @@ describe('permissions controller', function () { it('rejects requests regardless of order', async function () { const requestRejection1 = assert.rejects( - mockRequestUserApproval(REQUEST_IDS.b), + requestUserApproval(REQUEST_IDS.b, DOMAINS.b.origin), ERRORS.rejectPermissionsRequest.rejection(), 'should reject with expected error', ) const requestRejection2 = assert.rejects( - mockRequestUserApproval(REQUEST_IDS.c), + requestUserApproval(REQUEST_IDS.c, DOMAINS.c.origin), ERRORS.rejectPermissionsRequest.rejection(), 'should reject with expected error', ) diff --git a/test/unit/app/controllers/permissions/permissions-middleware-test.js b/test/unit/app/controllers/permissions/permissions-middleware-test.js index b835ea8e8ec2..8b56eb764339 100644 --- a/test/unit/app/controllers/permissions/permissions-middleware-test.js +++ b/test/unit/app/controllers/permissions/permissions-middleware-test.js @@ -70,11 +70,15 @@ describe('permissions middleware', function () { ) const res = {} + const userApprovalPromise = getUserApprovalPromise(permController) + const pendingApproval = assert.doesNotReject( aMiddleware(req, res), 'should not reject permissions request', ) + await userApprovalPromise + assert.equal( permController.pendingApprovals.size, 1, 'perm controller should have single pending approval', @@ -131,11 +135,15 @@ describe('permissions middleware', function () { // send, approve, and validate first request // note use of ACCOUNTS.a.permitted + let userApprovalPromise = getUserApprovalPromise(permController) + const pendingApproval1 = assert.doesNotReject( aMiddleware(req1, res1), 'should not reject permissions request', ) + await userApprovalPromise + const id1 = permController.pendingApprovals.keys().next().value const approvedReq1 = PERMS.approvedRequest(id1, PERMS.requests.eth_accounts()) @@ -187,11 +195,15 @@ describe('permissions middleware', function () { // send, approve, and validate second request // note use of ACCOUNTS.b.permitted + userApprovalPromise = getUserApprovalPromise(permController) + const pendingApproval2 = assert.doesNotReject( aMiddleware(req2, res2), 'should not reject permissions request', ) + await userApprovalPromise + const id2 = permController.pendingApprovals.keys().next().value const approvedReq2 = PERMS.approvedRequest(id2, { ...requestedPerms2 }) @@ -251,12 +263,16 @@ describe('permissions middleware', function () { const expectedError = ERRORS.rejectPermissionsRequest.rejection() + const userApprovalPromise = getUserApprovalPromise(permController) + const requestRejection = assert.rejects( aMiddleware(req, res), expectedError, 'request should be rejected with correct error', ) + await userApprovalPromise + assert.equal( permController.pendingApprovals.size, 1, 'perm controller should have single pending approval', @@ -343,11 +359,15 @@ describe('permissions middleware', function () { ) const resA1 = {} + let userApprovalPromise = getUserApprovalPromise(permController) + const requestApproval1 = assert.doesNotReject( aMiddleware(reqA1, resA1), 'should not reject permissions request', ) + await userApprovalPromise + // create and start processing first request for second origin const reqB1 = RPC_REQUESTS.requestPermission( @@ -355,11 +375,15 @@ describe('permissions middleware', function () { ) const resB1 = {} + userApprovalPromise = getUserApprovalPromise(permController) + const requestApproval2 = assert.doesNotReject( bMiddleware(reqB1, resB1), 'should not reject permissions request', ) + await userApprovalPromise + assert.equal( permController.pendingApprovals.size, 2, 'perm controller should have expected number of pending approvals', @@ -373,12 +397,17 @@ describe('permissions middleware', function () { ) const resA2 = {} - await assert.rejects( + userApprovalPromise = getUserApprovalPromise(permController) + + const requestApprovalFail = assert.rejects( aMiddleware(reqA2, resA2), expectedError, 'request should be rejected with correct error', ) + await userApprovalPromise + await requestApprovalFail + assert.ok( ( !resA2.result && resA2.error && From 5cb22ee8df89c8c283daba87991168f7cea6b9f3 Mon Sep 17 00:00:00 2001 From: Erik Marks Date: Sat, 25 Jul 2020 11:25:34 -0700 Subject: [PATCH 007/107] fix timing-reliant network controller test --- test/unit/app/controllers/network/network-controller-test.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/unit/app/controllers/network/network-controller-test.js b/test/unit/app/controllers/network/network-controller-test.js index 14d3bf31f24f..416274ab8248 100644 --- a/test/unit/app/controllers/network/network-controller-test.js +++ b/test/unit/app/controllers/network/network-controller-test.js @@ -34,8 +34,10 @@ describe('NetworkController', function () { assert.equal(providerProxy.test, true) }) }) + describe('#getNetworkState', function () { it('should return loading when new', function () { + networkController = new NetworkController() const networkState = networkController.getNetworkState() assert.equal(networkState, 'loading', 'network is loading') }) From 13aafa4702b52bb1c94dab0bb8246c39554b74a5 Mon Sep 17 00:00:00 2001 From: Brad Decker Date: Mon, 27 Jul 2020 17:15:44 -0500 Subject: [PATCH 008/107] Add euclid fontface (#9018) --- .../Euclid/EuclidCircularB-Bold-WebXL.ttf | Bin 0 -> 150928 bytes .../Euclid/EuclidCircularB-Regular-WebXL.ttf | Bin 0 -> 154192 bytes .../EuclidCircularB-RegularItalic-WebXL.ttf | Bin 0 -> 157072 bytes ui/app/css/variables/typography.scss | 21 ++++++++++++++++++ 4 files changed, 21 insertions(+) create mode 100644 app/fonts/Euclid/EuclidCircularB-Bold-WebXL.ttf create mode 100644 app/fonts/Euclid/EuclidCircularB-Regular-WebXL.ttf create mode 100644 app/fonts/Euclid/EuclidCircularB-RegularItalic-WebXL.ttf diff --git a/app/fonts/Euclid/EuclidCircularB-Bold-WebXL.ttf b/app/fonts/Euclid/EuclidCircularB-Bold-WebXL.ttf new file mode 100644 index 0000000000000000000000000000000000000000..244ebba0a48132cefec3cb28bc356537190566da GIT binary patch literal 150928 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 _#Tk7*DV*^sG!F#HKA0_!w%gwm1 z!S|ozC7%n}M%3t^sp-kvM3}e`?d*l_VKZ7!KS`9Y;o-t8EF@9Wji}E&)b}jZ>H_=~ z?Ow~%NY^p;i(8&XXy(;_c`0p0G;rO-N6e@}o@pf-tlRDK_KnI-dV}z|Kak z{`I;xbKGjR5cxG@^`PFm5M`{#d-8He{c|DiF2LPd+C{2^;d&(`vMar4H{ou}kY8Je|7sXlY-<_${q*_+Qg zSE=4``i19VUf6iXv}4EzIb@?7^%e{r?$&$ zzEW`RS)0#Oy8qd~D3?HDZgb^Q=!>m*Dq9+wW{2+W#2%hX2LD zH|(F}Uz2}B{)71+7L*nA7VIu~sNi?_`KaL2!eHUN!hMDJ6+Tk<%c3nskKyOyq67T; zYSG)pGfH19eXp#qY^@{TG2nQ(ytjN!#X!YS9#F6&v}h=U*K)$ zUH&&w-rLT@&evT*|8HIG)yF)CtB;8v*Jjr>u5Y;BcDvm5wYSNi2i-~c#qQhO``riJ z4_7Uzdc`y3xyEyY=Vs4g@gu(VJb|2Es(#1wY4tnRLHtg`@8;^=)i+h&g*)+ctj4DQ zl-2I2y%9fJIpX?Yt@wGVmVSQeE%a7-_jw=lKIsel?yM`W+u<+syZrV3PXB8E75-cO z`}}wLpCcUN=S}}_{T~LVXupAP;OD`>4+C!p)nFy?Auw`A@C;x_{Ok+f6?`Q4L?gQ+k4L^Ad7*tt`;ut5bymxyrjMczMIVd)u;pN^1ujQEihVuyNPT|& zlKLl_F0B8k!QYT&Hf?Kqtm!@Rb^8*O`bT=(^ieXe`A+;i zj~u489Bg^I(>b;_USNrbvcRE&M z+>-ZFq6}6NG~;l+955$VtYXYM$nCKA3}KazU?zD${oC=>i8a4onS`G<=ravtWtfhi zc4Zc{emdZ(a4z=Gr{bquS&p9`NWoWuQWM?m14TR=`<`<^u?9ddH(`bQJbtDq7lC3= zRW8BLFyf+I4{fqN_*tObf}e%Ti1Ib8Y`5cQxpEJFRw&<44&og5oA^0H`8IyeRKBY` z1-f=r`61}yPe5nRQ(nc-Mat{=*{b|Pc?+xg`^qQErOGI?DSKEED^kF4u`=aWR>>-r z+nAU6l&>&9^DAG28=sJ}k43Rtx}C*YT)Bg_vR36z*3SNad*=b4MUg)KnN1@hkQjQX zf)qh4fC@G^PCW}yZa_@D2o3>`0ajX%k0ea%slhV^E~s+?C!DZ75-n;oo^GKpifZ$(kJVa z)oZ$+?x!Z|L3)^)q_gx0HBDctFXM~dSLiDk6<({ysE_p{`f2qA|EKv}Ezq;|7iy9I zQh%ww(ewE-_!7NPFH%eOH~Jg(E&urWmhaE5&@0q;dZqqet>n)%tJNy();zUZ=j#Hs zMi=TrwN@AFQkA2(@Wt-}y`34CV!cD}P$l|DgwjSQ(`lwQJ1v|Rs@yr$IZSQE^6#uF zoui$jS#8kG>Bf?S8F#Jr8-YHa)vlVbPK*>cad)C40VR;R?cu| zxIWam#JNnjc1AiQbz5hYGfKDPn@Xc~d*^!Rdfmaf(YaM0?%d7_sUw{`ow2%$bGLK1 zKGwO{c~BqcjC0266P+iWXY@(VbIt_5|Ng4;nm&uQIa73B=UwML-OrioOw$8+*YQI= z2!1T(eblBZ)h%V+@fy~QOjResl|$T3a6&yerK_6YZdX&>ZKV8459GW4J5_>{=~g%` z-OWyW#&Yt%+kxsR=NxyjGuZvexq$oOgnQj7&N$W38P6QSG`H0GM74HixLfeEO@d!1 z!^Ll?v+?QmgDcL4Cx*chr}K~Tw!AUa*6FRzcJ4(do}!Z7WvVvispIBg-DWWE*uXoR zxqL&dKs9k!^IeSs)|hXiZyZUv*Qy8U9S@Oj0%<0}$&bPAC_y9LTHy{SbaeS#rn_1XbxZXy!Xs`TE6q0Qaio9DE!L0o{0VoJe#*_& zPrHRqCcea$Zno3eEr1rq&dKUXN}uAK>gGFVxvQQ2?)T0Bo}WY7^V}lnvj+O)I+qYP z%H81H<>o-6wazPeZl`hn*v*4ZYiXNQYSoka7jc!t+dK8yJ0Lp}sDB~#-%kCDWs(aS z(mpR@{)mZm4Xi)+!8bFK7p~YGB-=?ZmX`RVmUz)4#)W0t+ za1HezN}o&CC#ibSpoBVCQ0ISB=Mw6i!>WN)UFxpU74BMH2@Q6-tKfyreC@f3)4^R& z>m5n!N&U~F-dmi3gmY8_XRu18-lf!glQR+;jNECgmD^h)He4*Xx2R2lB;(DF-oXbbe5ix036n%04)wbW(q za^`O`(H)uUW_KYHzL|Og`d;CFt$(C+E;LhaHkvHeKq>)OF?Ff%>q5y2BjvA4s$3wa zg7c#g--6FACs!`H)5mQXvCM)u&f z4P0qjX%aH^b?Q5re)$H!Bx**^=1TNo8`5{yt+{W5P0*I!zLxU552cE|*2%?VRssGw z;NJ-T^|VehEwi4MNueC?sPV*4aQ_3ABf+u~EZx9zI1+CqQm&ldv6-Hp4A#Y9?F81N z!FmE%7lXAE5-(35OONjEeydL)oMa^69Q11)T6`07q7`(NS|kI%6!>Ytj{{yQ@DhMm zn-mRzR|>pD$s=BYQu>Zis|hs7 zH@sB`X`kVK3@s|)(rvWKM%rYF;j`XIHR(5F;i?I6?_{`j3iqqsN?^7E(jnBV6sb_4 zvykp1kX#jTZzUz#2{+|XmS%8e3+j@IlL<+PUa0SGrqpL}cREiy;@wUsMLHa@g_SQY zSZ}jMbtH5lbR!(^u3#Nu86}&iPM{Pg(!M9T@2ZpCovJ5X+KU$LO?#c{R;tt7AJys9 zl5I2m=X&NOKP7b4fp#^Lfzbe6ELp??QKnk?|LEJ(T$2 zjMB5feFf){?ho|*GBuj>7|M7X_4*TK|1;q)gufE*B>asqmR^4sX&xawN*G6YjPN)? zzU%uGaGoaogYXRDS?V^PvOG^YULd?kc!}^b;T6IJ_rL0&l>1e}zX-1pCK4u*=XJtl z!W)Ds#J$P+EzWNf-XXk8c#rTtcs~T!M?C+S^Cy&Z2H{h}Ov?3d!e@lf33Cba2wxNC z6IMg_T+Vrfb%cCE0bw)kw1rScCl<64HoECmh0c2B97S`K=oe8oJwbBSK>WoUJq6 zA9YhgGeUDh3qngmD|*SHoLdvx5Dp`>CA1^7Cv+fmA{;^JOgNHo6!4EG97E_r=t}5D z{IP`N2*(4nJD~^R1j31glPKrOgi{DT3BTh%RA&&*B%DP!o4WQP^d3CPu@hL)rVeFktD~G^F(r<2A!so z`#9=aj5NzZn&nWp>AF8!MC4a0kTwD-14v2a-bn6ba@QqyV{&gKcQQ6svbzY$g9L*o z8p1z~+|QB5>ySd9V{dLmOOzv(vnf%1`go!zoi`ZioQ0&j8QnV8eUdt?qfba3`hodt zFdqTtxo4GS6#mSW71WF;gUKd>H#N}TawpQVf1zbxRa3D}K5$>B zH$6%3d6M4q6usv$dd)-hl%3S`C9q4YcL95I>iH5S$bjpghU1^0zPV6s1$F*})}KP_ z%h&ZMQ8%lDJAkn{P(%ldZfXmZW+Kw77CluMhdN847Fsmu>p#$q4hT)GZPtY zw1ZMPj5dy9v~e8NDgwhu>eB}3M?kF-MjN#mZFFX|(F>|=f@-;7JOPZyfw75h&A(^c z5C)=?9)TMkMPH4h#qH=KkC8<^MizODDmG!YW-zj7hvk~#^j1eWXEBQC&nRL5*3CI) zRFRMMTI5_p++A4Wc0|#f5yd*J)?!8!>!7s!!>B%O+t6$Orj)V=eBKk=x-VA45L#*q zrAczXqf{Fx)e5nnDaoOfWDaATd`cm9^HQ*{2YZeg*9>+)b7)$BZcC3_5|2TsFI~Ux1e~T775uWZvYxSlSS)8vX zehmF#0=cHp>(S|yW|PiRhhr%;#~w)LNgXt5Yiz_GXoB99{UUny)nK_92xI6K(&kg( z`Xr=GM{*COr)H_cfY1a9xFy0#YYp8SLhDB4w45a4R^g;1sMFr#q@&@a!UR1Ye* zhl}dLMMpwy8K34r?ffcSlw-K)UZ`z3sU_6Ti*Ql`G+9H*TVl1X2761>b2KnhktDs~ zvMg-J3i!Daxw;ct)q;MVsQ*{=qGi}uwHTwl#u%-@jM1KCtak{UxkP2sLOqb4Cz9$S zs5KVb^bz1Z$!Ix~(U^D~I&dDy$aAp!F&2FZqqPlK_3N?f3$f}_=}8s9twauqRh~v) z{u-EX!iy=CneR+8iX01nPM{Y|F%oS(_4pX7E~PH~Uz5<0HXZ1GXKdNPYV|BuYI8s9 z^&X_<9->9UcCDX+aSwW{7j|$KGdlN^@3y+BQ<*QL@3o6N*zhQRAg~4I3WuN z_rrM)2DxZ)Ycp4%qkvpvOx3!a`)dSkNn=?0fY0Li)q^ z^oK8?atVE6C6rzYMYh4g8E|kS9E`qZj9-Z!+DSeciOR@vJy6yIC6C-i&{D=kMPQtS zq?2P06~|G-zZyxX(Wdd#C4oAm5K{3c)Be~1m><%2=c-2I#|-J@uv2|>OT#RIh)W24(*F| z(2wi>ge)|`NNm#4gz?-@BBd?m&6M&g`AjRNOQUpYlr9bI!@zzn*oT4rT(A!V`?>m3 z^7HOGQYQgCDTGvxb->e_UeSxXo(8|2P3VKv68`PSb$>z@+%lf?By}Owz7mYJz*q~6 zwZK>ljBUW!7mRJd*cXg#z}T1ZOfvRMDtf;T_Du$w0n3M0{e)JX4{xl4HwtLoLRuF; zAR|&4@rpNL43hgYCv(JBqP&MtsyB*C$d2$Ta zuZ(K>j=DQZr4V{BYRB4ux(P_R6ha-->+r~-eJ;ipIiKI;U&V$gjWc@RX>w7 z|0aA!_?$48FpuyxVLl<3kVjZY$R`wFBh^A)ClgW#X@qn_f8L8a$DOawB@A}|qtD}f zDc0(>_%yB~Tu-=xa3kR+!p($R2)7c(5N;#fj^$JAE}}ga(H@Ivk421PvKhf-)7D;z zu`YAWn8NX6;OrQq3%F72Ua@mSwym*ieH&G5Q-M-Wd$xrylcA@0{4(f);&rgTwaxU# z0@|XOcF094Z-hruIrbuT7US+iDcj|g;btJ*49ETzo;bskU=Jfd1|vTPBR_`H{{|yJ z1|vTP<0-C(5Rg*T*QU<3bz4F^LVH37!r_GOj6Bb0>~Vn_0QN3m z9{~0)U>|^G6OYbMU`{TDkctnf4*qtrLwliVPNOtu6Z+8BeJMdduKN?Rh#k-QBdn!N zLQ_I3HHs2622(3A4FXduFbx7zD=-Z*vZoE027+lIm}-IPN-&)arnX>82WC2$GQgAq zrgSi+gQ-1j+>$nK3C46Vrh_pZjOk!Y2itJ44F=nAunh*=aIg)geOsane&?r_!aI8J163Z+U$S7G--0bXXna+lsAp58D$B-v(~PlS$oDobIX%_I@(wcpekTa7p<&%s{#M3+k2q#mkPn+>Hou`d>n$FW^Jgv{uB--_ACH8C0DH3_wnf!IhUyJ;8$)7@g{uoEu zicATMu%hy*omkc-@Pb&*b+D9s8fw@XADPcUcN2PIeP2xqJO5L_cxNN^C}-AcyU7b* zGb4dNnI~I`tuWNfC$=0MG8ZCGwv%To5=>?^>QMd+_uoE!sZ)FG?e=O6&=tZTzyRIS+9?b)zk2g$XLC=8>{CqRxd=SIE=(jU<@~a@puQu;Er#^;pF zcocFOqt9S$KA&8(pkIAzo5^@Wu2yiBgM5=Qs`w;ZL*t&zy|%>5(;n|mXXfipR<*!i zhL2)9K8o#>p#X@J@CI#nE+7ndE19RQ#8**?f1(U85Pn%~;*FFpm3EXdNwdf}qzuo% zR`Cun8molYb}9$&NOPy1YUUh|v}E4PITLygWTnD6)ME%Ug_pxABMDbSf$Q;8-AKB- zxgST#o+j@6Ui9 zGH=`)ezSAO$Th}T%pDtVM@#B;s;T=9C|X9{%c%QKydK-|dhDRyJK){|D7+c(M=4a^ z0Ufq8M_mdnwi}$yz}XC(P2nVewo_&}{aH=%*M?>>!Mm0=*aF^4XulJ>?*#W2aLf9I z9pK&`!u<`nH-ftiyqm$h8NBjlQ8F?2L8JSTsM5wV-W1=n%v8&GQ>0Ca@gN`}!MVX} z_s!rHeEHz}!mDRF@Hd(E{e*JNKxfLlt?^!yb3Qq99A?}MrEG~Sflv;FZIrmuD{%$X z+D>T&+Ge1w^{A8sU6{{NDFm^i(y$a;V0R5gTMr{t(4Q^0Xt<>b+|t+iVMGWws8u z!0y6Nl}u|2zwaTzaSU5G(|0egycB0RJiQ&NSpyGJa3HO5$T?WW-aQjh}hC{*hHDneL0! zV={PUl~D<}w*jFW5V`@OD}4VNJvA38`4YW#0cBcGnRZZ~GNj}`!7v3$InhY(k?zZs z?Nzw+CCWFQvb|5)UZ89*L!-%fcqdcFNze&N235qSZUSZ2!>uV$M{JiOC{qv0Bte;C zC}UQtP|7uwsurcHMTyG5wjJ420*0+%z)MfRKa_e#&+Q9DZ!RCG)#mA;_g3mH^8@8T z*Fe_>dVktr0CXRS^bx6a0T_nUBkby(?bNT>>+f^G{x0>KPTeXg#R}U0Y^d6YnTEl% zG@e>?m`LK@w2%0hMFUwWw3!rzz}x}NegMu^XjBG_a_EEQ4vddU@iAq3h4qtlsof@^ ziF^}VZ#j@EDfthy%n!6k5ozD1q(4x)oj?jH15N@Jvv&iLsN&dsT73qlsDMlpW6hWOju*?JH&!{;!wdM;tG`#!#**{mO$%KD+H z_a{`BVo@Z&)4#i}|F{WXL>H;gpH>B}R) z^Lwr@cb|2x;Cv{Aem%0BZFeXy3Da5_W{adVfdz-j-;MRA!e~;^_q@6~Z z>D+$+oR2tvjBNSDeT?>c&-s-5nar`&_9Xjd*luF&ToyyR8|`GIyU5<{wBanYZhf?F zZFpl9@^1?qI1}p^j~1hjp-B2+ge=B)KcerkV`zt2VCW1@*FjIQQe+isI`owJ!Ve5T zT|$o&>+oJ?C#SK>a0b0yxOsuGkVTs=0pBw4tp(pS@HGNoA$Zo(=K0`QMEh<+7KkM@ z7d)ll5!t#49It_6qbGMNJb5El&?4v}{pnjupT@{=C;@K}rELT5HPlg7;ibXdGFm^M znU*1hiwHyUnGZvwF>ByFMM>XcOw|x5vVu&LqbU^1AXQURH5I!gVvkr`q^@U+ti)Qi zcE|%z>S zt}mihR?;FXky^6yZZne0w^7%lh1XNgm6WrH@>I|gGd&5t1N_^-y#cP=3f>~m;!2}k zS>xf+TSIRRy)~3<1SK0m#fF~sR^Qn5V$+Kq{|POH)M7-@3mY+ukwj=_Afk6{vuLpB2wygXfp$;^axUEB2sB0 zQfVSmX(AG7A`)pL5@{m#>pY~=L__`l^x_NL@uGQ9qZLB+*19(L{LQ2_(?#NT7+%Ux7WA>${L@_i%ogxN+{)&QtDx zkUZ0oJoAt|6OlZRIIq!W6S;naxG6k)lk2xg_cqVoL9)Ed{d-(b<@q$~HJ$s92p?my zeBwR~)jmP`Oho!jgyu3AlBN!!y=DF@+}4NC)`!s6jp!ZHBkIz|b!lV1Q%m1G4BoWd zS0-%*=ZU3R2Iq;T*%)glmG%nhbtN)yTmG&)dr2}K?}(vbE2Z5^T^YXJSpgj z+4QER^ro#wj`YWV7)VbUOwBH!*VtaP4(gQwdm%O2LfNg|)c~k)58fU-Ml1!g*j_R} z8@9bRAQf|I|GBjLPPnVmlZY~o+6E>WFILc2+rVPE?E(6^9aHUqjyu7+3Oeo-zY?RJ z3h3hXNjSSVoXuPQuPwy0r6w+Xa}X;7AK~q;Vz+>AYW&CD;cg|^z0zFobhpvx5AqG2 z=56jqp$B2HJI~$ZE_F+ZS>e9vZgI;^4Bjxe5PIfuoaerTt$h$|^cMx!6iQXy?Pix% znOxh-yVK2+BExBG%{AY$g+fo#%Xr)HK;PVN-EZKKsmx>e^x4iR7n&Jb>_9##Z1!p! zo?G4oMQ0x18>=e4jefHeTI_UZxUakU?&t0Xw}>xA{>xnpw@l$5-&5R=-G%OQWYuza zGHG)8w|yCUcB2pBz&>M%`jo0bzI^A-$HGuZq{Yzb8?F{Wqt7`nfkF#77emETc&OC& zPCxenc5duWbF&#;Dv>(=hW2Hn_KFdAq<=vPPb!(G$Siv2OpeRZ{2hTaoi}K)yu2^J44i`~lLj$aARn^vkTclT+&8L*WA@+SQ_BD}N+~>nvtAJI+IPX`z zi6-|qG*$tvFA`=g`zEe8alKNW>}l?zRd1N0#L#8~(Lwi+m4dLtU^;Mr=F zKgb?zWvpb8Bpk&y%rWvOhuhl7A30asJo%F&y^r(SbY zGAd^@A^#=$Nt+pog+?Yn3CIDQiB%qIPe-0Y%U|1u@0qpN!asqB1AjeQN{-eG{d4OS z_^*Sjp9}MUnZNq1m-ykKq-lTSQ7jEx+swBeup6t-*`$}m-l}VUYfV>=kDdQujrF}^ z2S@z_*>iBz_ovj-*Dj35^O*})zZQy~5}$Gb9(_EC;tjMOK}IHASKrKx zV+ko&q5-mjvqr4Y1I-eJR`^$`ml=DrwvtW8@5b&~%hS@`doD<0XQqFR`n|yY0PoC1 zUmuxy<0{M$_&0(FqQ!y@8Z80NC(L847YG88BODl{`E_0~v+xn~#2?he_ciG$@zMty zP`&`pEE((CjrY5mY4z_8jx&Cc1AaY1^_FzsbH8$*qqQS5eysQTj=r(f%zUlnyxm-F z63J}0&|JM(-mCoh*k>!Xgr5Q*Q|y%cV=S|G>!N#0&84i~3&pp(JNo$WV*9E6r~Bub zHTSaBI6`10OOzyr8uO%{?#n-+K76hHVA=R?q}|duHQK{k*Wbf*`%yCdKNZNA17a=8 zeni%&qQtU~q7wXyZ_M~Tfwj$%4V!p5^2OTAh3+!zqnHS~{rtkr}WzlBzoMz}p4UTPCaNvgwJf`KntR9@S@sRko~;rjPxHCwxgE zI>W(oC!ELlZ8Q6aNShMmjL_SxWZ;Rc7&a@Z4-R_RnF{Nz2-g_8tTO(5JQzlvLl@bw z9d?@ajXQ->Cgy;qR!!q4{AdMc&Ok#|X@QpX}N7GkX;Sh%NYrhW9UwN}VjNLt3X}VdzDt=fzWWi?mYUhE=H9<{l zzT!=&t;~;Y=eQk>Rl>@c60xd%sT1Tx9z|qP5PP6s@h-+{Jgdt&Zh=1Apx2iiD~$Xx z^2X;M`W*X#Ne^U+tN>qa*Z0~vMZUVA_@a}nLwuiaQ7U;K$jo!RV0}tZgOR@119^UM zZb)6;C4h5sc;83nqPH2p863p5(d<6Y@O(%0`RfCNYS#Z}{bjsAh$QwmtmUtu#P+}= zFC4=gQD0lmk!Lookx<^XpZmZ$*RYZ$33-f95h}=gnAYAo$X29@%yHjx=kj*pbTfA| zE&SGneM{By0jZ|(1ro(^wfma;qItJ$ou7hOBef44o{3!(Z${L!^{|;gWgXglnJqK( z%3JsrGOI#D^Bn&8q7n&&Rehjt)x7mG_gsd2UhMuG-df6=WGfkM$eQ_Lq;!lodxbK; z#&y0wtwnv`D)!(6_Z^Ol7*mv(l?d2}viXwP-q~YSDupjey`RGl>r??H_`JVE8g1~Pq%(L^lz)X?t&Xb0XEnn~6qRP`*z ztmwhhzxp zUy{(X-e8rLcz%kp0OcD32dEdL=JtykQ7jBe;tae!AM?eMkHl&YHb$-MeEAf_9$Z&d zXU3%VVQ=)oM2%Set9t#>U)#;w-_;-eUknt$elGV%9YT=6K0Fxv+W+;haHj~rhhWz> z8$YUfKaTH9&4F7A2r_z~WZwD~{xLq*U#m8wH^>UIO^lPuId3P(aVt7{8=9JL{)Opt z@Ll7Nm`WQ@4_5bb7g~|Y|Fwh%u!tFV%J-4X#{A7U==a*($hxSOHuk_gT3=|mZsAqn zcr3)$!@JD7jPE3s@egK))AQBU19-#gUFuGt-rkx5sVZExi8Vkn+f@c#iVgpi&=(gF zvmPIzcn$dm2Xi`~V>1)GtIg-T@G2vGjFaipybT1rf8%p32cyg=7Sp?z;fb8VvlYxZ zZe{;O{?S26%(!J&nD+fMYpF!9`>t6J*EE0eQ_AJp08LEZdOieiv@d@okj);su(lTz z!6r%4NzUvwboA>1OsTanCHq*ae0! zD? z#tBR*AM$aV-=AuhP0?&7#-_YoNh{^4ipN^0tt3cf6*q4K6 zsX%t0C*;v;Z}TUj&=&B1C~_ZP!YBO2j+PR6XZ3rf(OOW6n4QLYj?wR}e*Zazf%mpY z`j_xPWV3xba?6ETqb}{13-`*K;yE(^Z}@jPJQvb};avOXn*LF0R(^%74)|L}tunHZ zf4`eqEz=k1N$`2LiIdg!WpKc5SM&HXC8~dIHSakWlHdE5hfo}fNjq7Yw;Vi-mANnG zAIy1TadbAZ`q$-9KYA? z3PLnK_@y#zFAMjXk5~i|h=PO3^(^yeBnOQBq+{SS(k|MJH z2bsnCA8UR5H2kJTCZH4KPi1BU=O+C8B5@M=GfV?Y@S&87`$lk5w)={d$EFRAX8q&) z!Tr9z^2d&pgs;{aUJ~1RrXXp09(2#%dt6g14i>@^NZ5cMvGR8WEoO9@TOL$8c?*8eUvCo$n zPh**VJJ_sj$O|YYcqoUwJKln4+l-B|tSUzFG5nI7Xj!{qVOT5hCHEn&pC?RrpK)J6 zx8=HjK;!?ByLXu9nn?T@dYJe**70|hZ^Lrehqk-3;fOo2q{{iTdL8=k_ds33TZv!# zZy18-1D^g1i>NIY$bZ~BNbzt0*ZbP(YtV3-pC@>_@2`-4qCv&ax&z5vO#7`gF~)bu z6Oo3?31!sgXODP19VIQ|=}oUkGXnOvumqzv_&U2TEP z4X(&Fg-~Yfods2MBLlM~&U!RxKO_-%!pYwl%h388L{5rz=4n7lv+p<7738~*$AVkc zFILSGOr>nQp-n8fHctHBnm7Y5z6s1FFmV%7Zz4jZ(AQ59nwp@TSei~ zWjJLRh9567WlyA+O>gN29RdNaq7eJ*_bigL8m;y?c%Pyr7-{1D=^flc9V=m*( zcY-&yqEi~rFKt$Jv$m^00s_j%!1?)AOviSeB)!2^v1b;y9;K~9HMjq7>)!fUfr``Wz zkTr$g7(l7Edsx24Rnvudu0+(9_Yla({ExBVxLSkt zVOrX~U((qPsf>Svr~GHuebtMU`%)|wtGnf|>JjTi>~?DxnGuGuSn-oZ%8Y%=n`b^8 zLql?eH}h4QrC9b}x#StP6RUE=sj6P{U6Fcte-HKIGrY6thE(%4%$Fs`^nx;D?ba6_ zpmw)7;~m#u~z2W#sDl`4<_y91JI-hwa9wg^`he7kzd=QZ3M~ z>7Fl@(KO?gNUL!7yrhShHc;EyA{DUFHej`1Z{ToX?w7j9TEsGUI_;&pbeV zIE}Hh|E+jr$#;wy7nrghMs3!z(r6ZStA(}AieX>g8HkK$*!)_V8}E=nos~OY?nDkC zjl}**G&Cx5=~uzVs3^hQ$r}gaxfY1Epdx>WFfr8*P$Qil;mttEV?!VQMbD+JvB?vR z&?rMPZ;;oIRH!#NAk^*frVLBn-aFOXlq3@q_?^B+uLif$^m?la}erDdP zoe+%lNFS2HcEyC@$4x;`g;rlsbDyf>&EM+{o@6D2w@!}UAviczcz}e)kqBKpfxh_xv<(wpd`ND+~AmUBW}?5Fj5-gf`qKL<4MWB#{isE^-; zcCh1A8Kd(44oB;a`M{mQ^;?9+gbAMYWc~_){*ZpOuxk0?Ht+qNs!vF5-rw=!cRPZ& zZ2vSzA8w5E|$R;C&I*7zOgvQlP_tV)s1TA%hhI3icfw~J$1CPA_)SH@>I5#7E3 zU*k+*KkZ8eNhp7l@i%#HC^eHgphsCLG=b-NVu8xWN|9{=_b~>o9rOvC!aut2`gSO} zzV>6Qp5^OZ!3MrN=;ME)uV))gzg1QVlm9VyocpjlkiPOJs{|isJ;oEnhDKB&82=#U zd|Lj+Aa2jD(8pqf*#8N8S&Xc)(lXb0Lb6GLt%VobSX_)C;5K9ul3C;tW0%nPGmW(@ zPmFGgE(H>8ZR{3~PG|4@)`I%m;G!b5TP&ENZ|BB>;=!trimn+Y0GFvAHuivQM|zO=wVWFqOIQ8AGlaxDYrbg+F$D1>|J(NbbR%p-gbXG}B} zCbGF|xh)W6;&(vE4(ziC@V&V3tu7B{Hh9RQakGX3!BYq!Jy4O>9<3yRcyj+k+#<2ko5E+bi7}tXvE%(`d)sq+W2P{7My?qd$@e11p-aCa^-6!8zIY9T%|Keg$Unx$-{=a{L23dx@DxD1n*U`SKRaz_? zh?>laj8V6H+ z)!-3|fi@HsDY3u8o>3IiOuT+rg=iybv3$bE#ukuLc-CqNNa)ho)!5syH{k)n#wowmvd1L!gz_XIr*uS z%$K9IV-YQjW~sgz8!wWh`t#@*_YHfGtl0{UR@rwtqSH`Mp< zoodfwrGJ2T8yx>C5-=B^VK&gkX2yP~$;$BKy|%8oe*we5#~bKxMtT^DC;aT&4DOr$ zb9?qN$Q=|DZ7!4(>r2Knv=+x*^0uVdyUl7@U(!Z&v-cGU*8eQ44$m8fRk5)Bbnr+c z_@^lJHlY6~+B#_yi4HDm1(($IU3*85UW*y%*pmo6BfPgRYW%*YC9 zS~Jz)pXP>tG9^{?W^`Aw7k7B{GjkVZQ~LBlw-vxafyLovMDthrFt92376PS;ImSR2 z=LTv0JbQYs3e(D5tf~3TJx$?=UjzKvV3+UJ)SwA`UPl@TpU~!F?e>f4_vjMXyPyv^r1&?n(ij-n zpj9`4?qR?N@;8}#$FKl+rn^?$9r~tuTT!I6`u5T6QhZ_Si|%fENI$J zHb(FTHE}ji(Q!82-X5*y3RB60V5A4~XL$r7<7K-^PXni?r)^wtgb&)AyTgJw|H{~a z^}M_Ker)LM(ay&BErU(X|LCLf8IB8ajj`E77DEUomq>^F;G2!n&-Ug{>^Go^CknmJ z_+m2l^9J~6Bl1zk@ZQ?gK-L9Z5VW4qPhiB}>`VYDLUPR5R>sB?Z?C`k`rY2cL)A1i zTDy|5<7#7dhp6R4*dNc~dhd_r+w(HX_%C>Wnd=vgmBstZT=VaO021RczAZrPbo@+z zWo6NeTz_uYIdJt-m?n&Az4w=c{Qi}Ff7#yolEZ%{XUmQ5Yf*78Ff*~3H?2Qqr2Ze1 zYAJeSE9H3Dz2ChA4J-Sj?jOywm?I6I-p5~maNDQ7BYf!uzNPTZi%yk9**vxtwNKFHHdwzwi;%k0^eD<`kl?>*MCt!K< z+4q%zdA^~FjNh9WNn*;k=UDeP<e&1WHlAe*fyloQE@75CV^m1d5Cqw$( zvw8TB4SB2S_n%UNJ?KsN9tgd&U zhSikSJX(Rj#D}@>&))c8>Ar`&JBYT{yGvE|Il2H34gm>*rBe`=iB;1cDEBce&RtYZ zWVMyoM*i%v2W=N1YkuUYjw+5_);cCAjkd0>9OfqLaF>P*NMt=^Pv)C?vv*Ucs$uF_ zmBl_lU7{{k1Jy|ObJQp`TAizIWb-lin{RDljzFnWFA7=lZ zeoQ~9&(KfnXLKK3tV?x&y+v=+=j!e3L-Y>4Q(vfmWWU(Sbeid*P7CKSo#nK1I_WE% z&Q522jdPT9o*wO7;9R7ibcQ;^^)t>0XOtfAjCRK8iO%iLpY(gqU!4c_2hQJ}NAzrG zoHI^;={)I7(qB1mIPdAz&Qxcn&Zow4&J1-6^}o?0QX*H)xK1Kmr>-Qlr!Gy^;k+r^ z0DaMx|4XMc?it2zw^qC*dlr{xt3IkP5c{eA>;s_AAaivN6g>y}3|8kspLnQ~0BtU0 z78u)zO~zIisf*Q6=sLpCb+n=D)rPL4)wS#wshgngt(?zLkE%1(IQ5v3{GtJFVV*I( z4d>=EN-pPM^KFdFy{JPebFlZN^nJZuN-ujK!a0#$adp184}oJEQI=pILFq?$yYPhU zhrkt5c7OL%kE5>RJlto4SN1-Jc76%^K||U7lK5v!(?QVH?tXmVNGZ>S&O&K_KM$JA z?w8!2FQ5%%AL8v7(i#`4-vPyzaVWKsT{zvZ)d=|A?xU%fUq}BeZ6*6P?2UPD_Ysl0 z-$eUK3;O%5w59AmP3-wm+E(^)W`B$}ehdi0g;&y|m#gd54Uupi@%|Rw(tfOhmoABv z)Ru3Q!5XC4_0{9vWx>hIMRCN$?djUX#@Wov1Udw)1Z zjb&HrPwFA$++Wn6IsS?JKdP?kcJ&8VkKe&vcfx&GSNE#>)jd4>llq$)qfX=gA?_bg zf9KqrxWBXi7Rh=$x$fZjS9Lrvk3-IOBi((({h9qv_7e=myU2GZS9bvSM9%l{^fvD9 ziR@$5-RfkX^Z@#O>LkJ#RRi!CSJBj)WgJ#HFdd%Ybj3`o=UoVz@d=; zcDr3YNa_AU+Pf*ik>vaXm^&G`cc{CFJBs_ikx#YkzP`_lo~=Xs1(DL*I{3#7;_WWq zY3S|y+uecYvil{sXR$eCkN0-5J`#X7*sGCWW~rZl-Hdjzdy>~aem&(ZZRPK0Qg6El zdF{Qt*J<}u+ER9*N3hqWZDmh0dpd2Lj`k2P%%nw|p!<)HgyzI6V-txiIVWiT&{ZPb zRDBn%y=Pl%`rCcGRF0$x-n6$;hz& zSZX6^%^NB4T~G;M042#ldZeNOT0oDZ>6NFT83rKNFM-}S(VBOoQJxN!JDy(Nkbc?$ zO7ujJ3`BBYN=x5tIP?+q51z*T_BZFYQm6Fo-=~$ja!~)it+2yv=@XC%jo{+Lp=B@Z z??G_zWpKtVaK^n@Y|nUQNO>{&Dh)HWuvdXCT`Z=9xuOP#HDd__VzgHsw+mst>5e8=$o&J+c;`( zkJny0Dz1E#9CyeuUYTQ}Ii>>ahWL!VJzh(O_{MvEyy3FZ@hz_1%j0!|>#)5&-gwpM z%i}xl_3_4Idc+@n<6a-T936k$jW_P)@#afzj6dndy*^%dV|?!$Z{EA(O+v#n_V#%D z?{By^zTe&+5vdMAm%nHB7Yj2l!uz5*dE&0uo??P zt}QH))Y#PKD02%M*&#>qWwYsnD)2cH$hziAQ3O!Shq;q?xN7`UgBfoArO4 z6kV=MoI{)h{i9P$Z`GxaR-5o5ZPc6a7?ojGCOSz@vQyis%@EDDA#yIyl*E`SRPdd*T?i`P|Xspx4xxs1a+(oHQha#y^JRSYe3h8|q zQvMX=yo}2RA!~=DsV+t0US&r3*P@ATM)J$3>j_n&d+2lUT@BXf=_~Xf^&R?8`p^2W z`cC~fJyzeP@77=GC3=nC;52nQILA0$odM3}&Xvwp&PeBKXA~Z*Yn{8DH=TE#Y0gK^ z4Cm8G+iBe$uINn(vd|xYL?=A0URCd@&(uP-Ont9%)Oxj1m8%^LBNBC8ouM1*Ox;4a z*6nmheWdQ9lE4jS@c%>O25qAmwL4>)ZgIidqp|mtk3Z|xY+?mLyphG)ebNlb9@mVb%2q{@nyKsAx|?E zrzfa-`WafOKA{1jA)yhWF`)?|lhBmV4Bo1z|H(1VSQ?62{sYVASs>ImeZ+y!4>R<2 zh&hrx0#l(|WhB<$dy;8l1*-q#X7l_g6C*qQIV1NwI_$nnz2_hvEyN>|*_mDSrbjok3VPkHZCv zRjE$Z9dvJfvA#(^s9)C8$&;M`~F+seAR^q4WgVjEuksT05y>^__T0k&;+m zb=R|$CIk?u6aBx+4$UQha^ic@yBynbH4pkLH4Ayr<95i>z* z6qd0eD49?yuveN;uck<5@w{mxiEY`fC9tcsBD=$B&r=Yhl-x0;TRy zMe|#&ze|n3Yi%E*#1X1q#0J@&1yV0!9qigR@Y@p}Vzn%N+ZkZSxK=BTS%ovJaVS$qsN$Ye|;V8ai+e3V;_AB$Nu^@j^_h0Uf*u693Z4R_bJCY z*}0!%Z+YenAun4CJtW{i# zj*Zucdshyl|Ng}Iu-mD0J(4~YOLt;Dirq-+bZQrEFZ=u_7M<8~0YrEY%V;h3PcC-O QI_#MO?3P0CYs=aH4`8D400000 literal 0 HcmV?d00001 diff --git a/app/fonts/Euclid/EuclidCircularB-Regular-WebXL.ttf b/app/fonts/Euclid/EuclidCircularB-Regular-WebXL.ttf new file mode 100644 index 0000000000000000000000000000000000000000..6cf177fe1b2377316697d96a9bde76ff3e90f8af GIT binary patch literal 154192 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�$p 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 E1AJW2Pyhe` literal 0 HcmV?d00001 diff --git a/ui/app/css/variables/typography.scss b/ui/app/css/variables/typography.scss index 12bc4fccbc16..0a51e173833a 100644 --- a/ui/app/css/variables/typography.scss +++ b/ui/app/css/variables/typography.scss @@ -46,6 +46,27 @@ $fa-font-path: 'fonts/fontawesome'; src: local('Roboto Black'), local('Roboto-Black'), url('fonts/Roboto/Roboto-Black.ttf') format('truetype'); } +@font-face { + font-family: 'Euclid'; + font-style: normal; + font-weight: 400; + src: url('fonts/Euclid/EuclidCircularB-Regular-WebXL.ttf') format('truetype'); +} + +@font-face { + font-family: 'Euclid'; + font-style: italic; + font-weight: 400; + src: url('fonts/Euclid/EuclidCircularB-RegularItalic-WebXL.ttf') format('truetype'); +} + +@font-face { + font-family: 'Euclid'; + font-style: normal; + font-weight: 700; + src: url('fonts/Euclid/EuclidCircularB-Bold-WebXL.ttf') format('truetype'); +} + $font-family: Roboto, Helvetica, Arial, sans-serif; // Typography From b3e5befe743a905b7dc1573db77de4d8b74ba398 Mon Sep 17 00:00:00 2001 From: Erik Marks Date: Mon, 27 Jul 2020 15:28:58 -0700 Subject: [PATCH 009/107] call initializeProvider where necessary --- test/unit/app/controllers/network/network-controller-test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/unit/app/controllers/network/network-controller-test.js b/test/unit/app/controllers/network/network-controller-test.js index 416274ab8248..9c2aabc94774 100644 --- a/test/unit/app/controllers/network/network-controller-test.js +++ b/test/unit/app/controllers/network/network-controller-test.js @@ -18,7 +18,6 @@ describe('NetworkController', function () { .reply(200) networkController = new NetworkController() - networkController.initializeProvider(networkControllerProviderConfig) }) afterEach(function () { @@ -37,7 +36,6 @@ describe('NetworkController', function () { describe('#getNetworkState', function () { it('should return loading when new', function () { - networkController = new NetworkController() const networkState = networkController.getNetworkState() assert.equal(networkState, 'loading', 'network is loading') }) @@ -53,11 +51,13 @@ describe('NetworkController', function () { describe('#setProviderType', function () { it('should update provider.type', function () { + networkController.initializeProvider(networkControllerProviderConfig) networkController.setProviderType('mainnet') const type = networkController.getProviderConfig().type assert.equal(type, 'mainnet', 'provider type is updated') }) it('should set the network to loading', function () { + networkController.initializeProvider(networkControllerProviderConfig) networkController.setProviderType('mainnet') const loading = networkController.isNetworkLoading() assert.ok(loading, 'network is loading') From 826d1462f05ec35577303c6fea86cc7647f81dbe Mon Sep 17 00:00:00 2001 From: Erik Marks Date: Mon, 27 Jul 2020 15:31:10 -0700 Subject: [PATCH 010/107] fixup! call initializeProvider where necessary --- test/unit/app/controllers/network/network-controller-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/app/controllers/network/network-controller-test.js b/test/unit/app/controllers/network/network-controller-test.js index 9c2aabc94774..08164d99d39b 100644 --- a/test/unit/app/controllers/network/network-controller-test.js +++ b/test/unit/app/controllers/network/network-controller-test.js @@ -35,7 +35,7 @@ describe('NetworkController', function () { }) describe('#getNetworkState', function () { - it('should return loading when new', function () { + it('should return "loading" when new', function () { const networkState = networkController.getNetworkState() assert.equal(networkState, 'loading', 'network is loading') }) From 99899b5df98749ff1c91e49c143a3d4f36da9fb7 Mon Sep 17 00:00:00 2001 From: Erik Marks <25517051+rekmarks@users.noreply.github.com> Date: Tue, 28 Jul 2020 10:01:24 -0700 Subject: [PATCH 011/107] json-rpc-engine@5.2.0 (#9091) --- package.json | 2 +- yarn.lock | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index a433b99cd6fe..cafe5bc84018 100644 --- a/package.json +++ b/package.json @@ -119,7 +119,7 @@ "fuse.js": "^3.2.0", "human-standard-token-abi": "^2.0.0", "jazzicon": "^2.0.0", - "json-rpc-engine": "^5.1.8", + "json-rpc-engine": "^5.2.0", "json-rpc-middleware-stream": "^2.1.1", "jsonschema": "^1.2.4", "lodash": "^4.17.19", diff --git a/yarn.lock b/yarn.lock index 30f8e5ecf1d4..83d52e6c2814 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15765,6 +15765,14 @@ json-rpc-engine@^5.1.3, json-rpc-engine@^5.1.5, json-rpc-engine@^5.1.8: promise-to-callback "^1.0.0" safe-event-emitter "^1.0.1" +json-rpc-engine@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/json-rpc-engine/-/json-rpc-engine-5.2.0.tgz#0ea1162f56de53a41d47a4995168b56dc82bbf47" + integrity sha512-F9xjrIjMqPNCxzk9jtOgJMs9mt+80p03IbJALnO278grtSU+Sh/fSqb7gNk/gwlTxavO2+ABKenyz4ZP3kwKIQ== + dependencies: + eth-rpc-errors "^2.1.1" + safe-event-emitter "^1.0.1" + json-rpc-error@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/json-rpc-error/-/json-rpc-error-2.0.0.tgz#a7af9c202838b5e905c7250e547f1aff77258a02" From 1e0ef76524a7f7cbd255744766398f7f1d69e041 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Tue, 28 Jul 2020 16:09:20 -0300 Subject: [PATCH 012/107] Use development metametrics project during tests (#9093) e2e tests will now reference the development MetaMetrics project instead of the production one. The metrics endpoint should be stubbed out during e2e tests anyway, but this seemed like a better default regardless. --- ui/app/helpers/utils/metametrics.util.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/app/helpers/utils/metametrics.util.js b/ui/app/helpers/utils/metametrics.util.js index f0ec2249677a..629dc25675fc 100644 --- a/ui/app/helpers/utils/metametrics.util.js +++ b/ui/app/helpers/utils/metametrics.util.js @@ -2,7 +2,7 @@ import ethUtil from 'ethereumjs-util' -const inDevelopment = process.env.NODE_ENV === 'development' +const inDevelopment = process.env.METAMASK_DEBUG || process.env.IN_TEST const METAMETRICS_BASE_URL = 'https://chromeextensionmm.innocraft.cloud/piwik.php' const METAMETRICS_REQUIRED_PARAMS = `?idsite=${inDevelopment ? 1 : 2}&rec=1&apiv=1` From 8a7c8e8aebb45bd6a169d77be36f8e7113b9e713 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Tue, 28 Jul 2020 16:59:27 -0300 Subject: [PATCH 013/107] Use environment variable for MetaMetrics project ID (#9094) The MetaMetrics project ID can now be set via environment variable. It has not been set yet in practice, so for now the old project IDs will still be used. This is in preparation for migrating to a new project. --- development/build/scripts.js | 1 + ui/app/helpers/utils/metametrics.util.js | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/development/build/scripts.js b/development/build/scripts.js index 0cb3e85ea2f7..2ce9b86a0877 100644 --- a/development/build/scripts.js +++ b/development/build/scripts.js @@ -334,6 +334,7 @@ function createScriptTasks ({ browserPlatforms, livereload }) { bundler.transform(envify({ METAMASK_DEBUG: opts.devMode, METAMASK_ENVIRONMENT: environment, + METAMETRICS_PROJECT_ID: process.env.METAMETRICS_PROJECT_ID, NODE_ENV: opts.devMode ? 'development' : 'production', IN_TEST: opts.testing ? 'true' : false, PUBNUB_SUB_KEY: process.env.PUBNUB_SUB_KEY || '', diff --git a/ui/app/helpers/utils/metametrics.util.js b/ui/app/helpers/utils/metametrics.util.js index 629dc25675fc..98b83f3d8aec 100644 --- a/ui/app/helpers/utils/metametrics.util.js +++ b/ui/app/helpers/utils/metametrics.util.js @@ -4,8 +4,13 @@ import ethUtil from 'ethereumjs-util' const inDevelopment = process.env.METAMASK_DEBUG || process.env.IN_TEST +let projectId = process.env.METAMETRICS_PROJECT_ID +if (!projectId) { + projectId = inDevelopment ? 1 : 2 +} + const METAMETRICS_BASE_URL = 'https://chromeextensionmm.innocraft.cloud/piwik.php' -const METAMETRICS_REQUIRED_PARAMS = `?idsite=${inDevelopment ? 1 : 2}&rec=1&apiv=1` +const METAMETRICS_REQUIRED_PARAMS = `?idsite=${projectId}&rec=1&apiv=1` const METAMETRICS_BASE_FULL = METAMETRICS_BASE_URL + METAMETRICS_REQUIRED_PARAMS const METAMETRICS_TRACKING_URL = inDevelopment From 869c252088605037ed0607dd8ea6a663b16bdf07 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Tue, 28 Jul 2020 16:59:47 -0300 Subject: [PATCH 014/107] Disable Sentry in development (#9095) In a non-production environment, Sentry was configured to send error reports to a "test" MetaMask project. It will still do this during e2e tests, but in development Sentry is now disabled completely. In practice this was never useful in development. --- app/scripts/lib/setupSentry.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/scripts/lib/setupSentry.js b/app/scripts/lib/setupSentry.js index 04c4b50144f8..9eca2ef5b23e 100644 --- a/app/scripts/lib/setupSentry.js +++ b/app/scripts/lib/setupSentry.js @@ -72,7 +72,9 @@ export const SENTRY_STATE = { export default function setupSentry ({ release, getState }) { let sentryTarget - if (METAMASK_DEBUG || process.env.IN_TEST) { + if (METAMASK_DEBUG) { + return + } else if (process.env.IN_TEST) { console.log(`Setting up Sentry Remote Error Reporting for '${METAMASK_ENVIRONMENT}': SENTRY_DSN_DEV`) sentryTarget = SENTRY_DSN_DEV } else { From 4cc3fff96afdb8208f35e1eb61e714e7f6921812 Mon Sep 17 00:00:00 2001 From: Brad Decker Date: Tue, 28 Jul 2020 15:16:30 -0500 Subject: [PATCH 015/107] Update css folder structure (#9071) --- .../generic/index.scss => base-styles.scss} | 30 ----------- ui/app/css/design-system/breakpoints.scss | 6 +++ .../{variables => design-system}/colors.scss | 0 .../css/design-system/deprecated-colors.scss | 48 +++++++++++++++++ ui/app/css/design-system/index.scss | 4 ++ .../typography.scss | 1 - ui/app/css/index.scss | 5 +- ui/app/css/itcss/settings/variables.scss | 54 ------------------- ui/app/css/{itcss/generic => }/reset.scss | 2 - ui/app/css/variables/index.scss | 2 - 10 files changed, 61 insertions(+), 91 deletions(-) rename ui/app/css/{itcss/generic/index.scss => base-styles.scss} (80%) create mode 100644 ui/app/css/design-system/breakpoints.scss rename ui/app/css/{variables => design-system}/colors.scss (100%) create mode 100644 ui/app/css/design-system/deprecated-colors.scss create mode 100644 ui/app/css/design-system/index.scss rename ui/app/css/{variables => design-system}/typography.scss (99%) rename ui/app/css/{itcss/generic => }/reset.scss (96%) delete mode 100644 ui/app/css/variables/index.scss diff --git a/ui/app/css/itcss/generic/index.scss b/ui/app/css/base-styles.scss similarity index 80% rename from ui/app/css/itcss/generic/index.scss rename to ui/app/css/base-styles.scss index 64c9688f650b..1e17639f7461 100644 --- a/ui/app/css/itcss/generic/index.scss +++ b/ui/app/css/base-styles.scss @@ -1,8 +1,4 @@ -/* - Generic - */ -@import './reset'; * { box-sizing: border-box; @@ -12,7 +8,6 @@ html, body { font-family: Roboto, Arial; color: #4d4d4d; - font-weight: 400; width: 100%; height: 100%; margin: 0; @@ -25,15 +20,6 @@ html { min-height: 500px; } -.app-root { - overflow: hidden; - position: relative; -} - -.app-primary { - display: flex; -} - .mouse-user-styles { button:focus, input:focus, @@ -121,19 +107,3 @@ input.form-control { } } -.hide-text-overflow { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.pinned-to-bottom { - position: absolute; - bottom: 0; -} - -.pinned-to-bottom-right { - position: absolute; - bottom: 0; - right: 0; -} diff --git a/ui/app/css/design-system/breakpoints.scss b/ui/app/css/design-system/breakpoints.scss new file mode 100644 index 000000000000..80a4176b0e57 --- /dev/null +++ b/ui/app/css/design-system/breakpoints.scss @@ -0,0 +1,6 @@ +/* +Responsive Breakpoints +*/ +$break-small: 575px; +$break-midpoint: 780px; +$break-large: 576px; diff --git a/ui/app/css/variables/colors.scss b/ui/app/css/design-system/colors.scss similarity index 100% rename from ui/app/css/variables/colors.scss rename to ui/app/css/design-system/colors.scss diff --git a/ui/app/css/design-system/deprecated-colors.scss b/ui/app/css/design-system/deprecated-colors.scss new file mode 100644 index 000000000000..c65f0bfa0100 --- /dev/null +++ b/ui/app/css/design-system/deprecated-colors.scss @@ -0,0 +1,48 @@ +/** + These colors are either deprecated or will move into colors.scss + when approved for the design system +**/ + +// Base Colors +$white: #fff; +$black: #000; +$orange: #ffa500; +$red: #f00; +$gray: #808080; + +/* + Colors + http://chir.ag/projects/name-that-color + */ +$gallery: #efefef; +$wild-sand: #f6f6f6; +$dusty-gray: #9b9b9b; +$alto: #dedede; +$alabaster: #fafafa; +$silver-chalice: #aeaeae; +$tundora: #4d4d4d; +$nile-blue: #1b344d; +$scorpion: #5d5d5d; +$silver: #cdcdcd; +$caribbean-green: #02c9b1; +$monzo: #d0021b; +$crimson: #e91550; +$blue-lagoon: #038789; +$purple: #690496; +$tulip-tree: #ebb33f; +$malibu-blue: #7ac9fd; +$athens-grey: #e9edf0; +$geyser: #d2d8dd; +$manatee: #93949d; +$spindle: #c7ddec; +$mid-gray: #5b5d67; +$cape-cod: #38393a; +$dodger-blue: #3099f2; +$ecstasy: #f7861c; +$linen: #fdf4f4; +$oslo-gray: #8c8e94; +$polar: #fafcfe; +$blizzard-blue: #bfdef3; +$mischka: #dddee9; +$web-orange: #f2a202; +$mercury: #e5e5e5; diff --git a/ui/app/css/design-system/index.scss b/ui/app/css/design-system/index.scss new file mode 100644 index 000000000000..08b0c8b25826 --- /dev/null +++ b/ui/app/css/design-system/index.scss @@ -0,0 +1,4 @@ +@import './colors.scss'; +@import './deprecated-colors.scss'; +@import './typography.scss'; +@import './breakpoints.scss'; diff --git a/ui/app/css/variables/typography.scss b/ui/app/css/design-system/typography.scss similarity index 99% rename from ui/app/css/variables/typography.scss rename to ui/app/css/design-system/typography.scss index 0a51e173833a..37e05faeb87b 100644 --- a/ui/app/css/variables/typography.scss +++ b/ui/app/css/design-system/typography.scss @@ -148,4 +148,3 @@ $font-family: Roboto, Helvetica, Arial, sans-serif; font-family: $font-family; line-height: 140%; } - diff --git a/ui/app/css/index.scss b/ui/app/css/index.scss index d757a2374ed7..9b994f19ab71 100644 --- a/ui/app/css/index.scss +++ b/ui/app/css/index.scss @@ -4,7 +4,9 @@ They are included first because they will be used to replace bad variable names in itcss prior to it being fully removed from the system. */ -@import './variables/index'; +@import './reset.scss'; +@import './design-system/index'; +@import './base-styles.scss'; /* ITCSS @@ -17,7 +19,6 @@ */ @import './itcss/settings/index'; @import './itcss/tools/index'; -@import './itcss/generic/index'; @import './itcss/objects/index'; @import './itcss/components/index'; @import './itcss/trumps/index'; diff --git a/ui/app/css/itcss/settings/variables.scss b/ui/app/css/itcss/settings/variables.scss index fa6727ee292d..dec39888414a 100644 --- a/ui/app/css/itcss/settings/variables.scss +++ b/ui/app/css/itcss/settings/variables.scss @@ -1,51 +1,3 @@ -/* - Variables - */ - -// Base Colors -$white: #fff; -$black: #000; -$orange: #ffa500; -$red: #f00; -$gray: #808080; - -/* - Colors - http://chir.ag/projects/name-that-color - */ -$gallery: #efefef; -$wild-sand: #f6f6f6; -$dusty-gray: #9b9b9b; -$alto: #dedede; -$alabaster: #fafafa; -$silver-chalice: #aeaeae; -$tundora: #4d4d4d; -$nile-blue: #1b344d; -$scorpion: #5d5d5d; -$silver: #cdcdcd; -$caribbean-green: #02c9b1; -$monzo: #d0021b; -$crimson: #e91550; -$blue-lagoon: #038789; -$purple: #690496; -$tulip-tree: #ebb33f; -$malibu-blue: #7ac9fd; -$athens-grey: #e9edf0; -$geyser: #d2d8dd; -$manatee: #93949d; -$spindle: #c7ddec; -$mid-gray: #5b5d67; -$cape-cod: #38393a; -$dodger-blue: #3099f2; -$ecstasy: #f7861c; -$linen: #fdf4f4; -$oslo-gray: #8c8e94; -$polar: #fafcfe; -$blizzard-blue: #bfdef3; -$mischka: #dddee9; -$web-orange: #f2a202; -$mercury: #e5e5e5; - /* Z-Indicies */ @@ -98,12 +50,6 @@ $sidebar-overlay-z-index: 25; mascot - 0 - remove? */ -/* - Responsive Breakpoints - */ -$break-small: 575px; -$break-midpoint: 780px; -$break-large: 576px; /* Spacing Variables diff --git a/ui/app/css/itcss/generic/reset.scss b/ui/app/css/reset.scss similarity index 96% rename from ui/app/css/itcss/generic/reset.scss rename to ui/app/css/reset.scss index b60fcd068db1..43146fec8be3 100644 --- a/ui/app/css/itcss/generic/reset.scss +++ b/ui/app/css/reset.scss @@ -88,9 +88,7 @@ video { padding: 0; border: 0; font-size: 100%; - /* stylelint-disable */ font: inherit; - /* stylelint-enable */ vertical-align: baseline; } diff --git a/ui/app/css/variables/index.scss b/ui/app/css/variables/index.scss deleted file mode 100644 index 8d79fd9c7abb..000000000000 --- a/ui/app/css/variables/index.scss +++ /dev/null @@ -1,2 +0,0 @@ -@import './colors.scss'; -@import './typography.scss'; From 1582855e28916edea1e1d4a16ba147796686141f Mon Sep 17 00:00:00 2001 From: Brad Decker Date: Wed, 29 Jul 2020 10:35:53 -0500 Subject: [PATCH 016/107] Use mixins for typography instead of placeholder selectors (#9072) Using extend would not work inside of some css, namely inside of media queries. This made it a clear choice to use mixins for these styles. --- .../add-to-addressbook-modal/index.scss | 2 +- .../app/modals/new-account-modal/index.scss | 2 +- .../app/permission-page-container/index.scss | 4 +-- .../app/permissions-connect-footer/index.scss | 2 +- .../app/permissions-connect-header/index.scss | 4 +-- ui/app/components/ui/button/buttons.scss | 7 +++--- ui/app/components/ui/list-item/index.scss | 2 +- ui/app/components/ui/popover/index.scss | 4 +-- ui/app/css/design-system/typography.scss | 25 ++++++++++--------- ui/app/css/itcss/components/modal.scss | 3 ++- ui/app/pages/home/index.scss | 2 +- .../choose-account/index.scss | 10 ++++---- ui/app/pages/permissions-connect/index.scss | 4 +-- .../permissions-connect/redirect/index.scss | 2 +- ui/app/pages/send/send.scss | 8 +++--- 15 files changed, 42 insertions(+), 39 deletions(-) diff --git a/ui/app/components/app/modals/add-to-addressbook-modal/index.scss b/ui/app/components/app/modals/add-to-addressbook-modal/index.scss index 64d0f733641d..cb84f30a0379 100644 --- a/ui/app/components/app/modals/add-to-addressbook-modal/index.scss +++ b/ui/app/components/app/modals/add-to-addressbook-modal/index.scss @@ -9,7 +9,7 @@ border-bottom: 1px solid $Grey-100; &__header { - @extend %H3; + @include H3; } } diff --git a/ui/app/components/app/modals/new-account-modal/index.scss b/ui/app/components/app/modals/new-account-modal/index.scss index 97d6210b80c4..bbe4400412d3 100644 --- a/ui/app/components/app/modals/new-account-modal/index.scss +++ b/ui/app/components/app/modals/new-account-modal/index.scss @@ -9,7 +9,7 @@ border-bottom: 1px solid $Grey-100; &__header { - @extend %H4; + @include H4; font-weight: bold; display: flex; diff --git a/ui/app/components/app/permission-page-container/index.scss b/ui/app/components/app/permission-page-container/index.scss index 458eb5b703bd..2e3f10aa8919 100644 --- a/ui/app/components/app/permission-page-container/index.scss +++ b/ui/app/components/app/permission-page-container/index.scss @@ -31,7 +31,7 @@ } &__title { - @extend %H4; + @include H4; line-height: 25px; text-align: center; @@ -84,7 +84,7 @@ } &__permissions-header { - @extend %H6; + @include H6; line-height: 20px; color: #6a737d; diff --git a/ui/app/components/app/permissions-connect-footer/index.scss b/ui/app/components/app/permissions-connect-footer/index.scss index 229da218a389..51ee8a09fc57 100644 --- a/ui/app/components/app/permissions-connect-footer/index.scss +++ b/ui/app/components/app/permissions-connect-footer/index.scss @@ -5,7 +5,7 @@ align-items: center; &__text { - @extend %H7; + @include H7; line-height: 17px; color: #6a737d; diff --git a/ui/app/components/app/permissions-connect-header/index.scss b/ui/app/components/app/permissions-connect-header/index.scss index ca8d4c0e6c4f..1ec0628e053a 100644 --- a/ui/app/components/app/permissions-connect-header/index.scss +++ b/ui/app/components/app/permissions-connect-header/index.scss @@ -26,7 +26,7 @@ } &__title { - @extend %H3; + @include H3; text-align: center; color: $Black-100; @@ -35,7 +35,7 @@ &__text, &__subtitle { - @extend %H6; + @include H6; text-align: center; color: $Grey-500; diff --git a/ui/app/components/ui/button/buttons.scss b/ui/app/components/ui/button/buttons.scss index 83f02f7e0ba8..693deec26ea7 100644 --- a/ui/app/components/ui/button/buttons.scss +++ b/ui/app/components/ui/button/buttons.scss @@ -11,7 +11,7 @@ $hover-orange: #ffd3b5; $warning-light-orange: #f8b588; %button { - @extend %H6; + @include H6; font-weight: 500; font-family: Roboto, Arial; @@ -34,7 +34,7 @@ $warning-light-orange: #f8b588; } %link { - @extend %H4; + @include H4; color: $Blue-500; line-height: 1.25rem; @@ -60,7 +60,8 @@ $warning-light-orange: #f8b588; %small-link { @extend %link; - @extend %H6; + + @include H6; } .button { diff --git a/ui/app/components/ui/list-item/index.scss b/ui/app/components/ui/list-item/index.scss index cc9edd87fe7e..2117dfc4d931 100644 --- a/ui/app/components/ui/list-item/index.scss +++ b/ui/app/components/ui/list-item/index.scss @@ -5,7 +5,7 @@ background: #fff; padding: 24px 16px; - @extend %Paragraph; + @include Paragraph; border-top: 1px solid $mercury; border-bottom: 1px solid $mercury; diff --git a/ui/app/components/ui/popover/index.scss b/ui/app/components/ui/popover/index.scss index af3d7b279dcf..a1ef85b06df5 100644 --- a/ui/app/components/ui/popover/index.scss +++ b/ui/app/components/ui/popover/index.scss @@ -33,7 +33,7 @@ align-items: center; justify-content: space-between; - @extend %H4; + @include H4; font-weight: bold; line-height: 25px; @@ -51,7 +51,7 @@ } &__subtitle { - @extend %H6; + @include H6; line-height: 20px; } diff --git a/ui/app/css/design-system/typography.scss b/ui/app/css/design-system/typography.scss index 37e05faeb87b..8c37bcea7f31 100644 --- a/ui/app/css/design-system/typography.scss +++ b/ui/app/css/design-system/typography.scss @@ -70,7 +70,7 @@ $fa-font-path: 'fonts/fontawesome'; $font-family: Roboto, Helvetica, Arial, sans-serif; // Typography -%H1 { +@mixin H1 { font-style: normal; font-weight: normal; font-size: 2.5rem; @@ -78,7 +78,8 @@ $font-family: Roboto, Helvetica, Arial, sans-serif; line-height: 140%; } -%H2 { + +@mixin H2 { font-style: normal; font-weight: normal; font-size: 2rem; @@ -86,7 +87,7 @@ $font-family: Roboto, Helvetica, Arial, sans-serif; line-height: 140%; } -%H3 { +@mixin H3 { font-style: normal; font-weight: normal; font-size: 1.5rem; @@ -94,7 +95,7 @@ $font-family: Roboto, Helvetica, Arial, sans-serif; line-height: 140%; } -%H4 { +@mixin H4 { font-style: normal; font-weight: normal; font-size: 1.125rem; @@ -102,7 +103,7 @@ $font-family: Roboto, Helvetica, Arial, sans-serif; line-height: 140%; } -%H5 { +@mixin H5 { font-style: normal; font-weight: normal; font-size: 1rem; @@ -110,14 +111,14 @@ $font-family: Roboto, Helvetica, Arial, sans-serif; line-height: 140%; } -%H6 { +@mixin H6 { font-style: normal; font-weight: normal; - font-size: 0.875rem; - font-family: $font-family; + font-size: 0.875rem; // 14px @default + line-height: 140%; } -%Paragraph { +@mixin Paragraph { font-style: normal; font-weight: normal; font-size: 1rem; @@ -125,7 +126,7 @@ $font-family: Roboto, Helvetica, Arial, sans-serif; line-height: 140%; } -%H7 { +@mixin H7 { font-style: normal; font-weight: normal; font-size: 0.75rem; @@ -133,7 +134,7 @@ $font-family: Roboto, Helvetica, Arial, sans-serif; line-height: 140%; } -%H8 { +@mixin H8 { font-style: normal; font-weight: normal; font-size: 0.625rem; @@ -141,7 +142,7 @@ $font-family: Roboto, Helvetica, Arial, sans-serif; line-height: 140%; } -%H9 { +@mixin H9 { font-style: normal; font-weight: normal; font-size: 0.5rem; diff --git a/ui/app/css/itcss/components/modal.scss b/ui/app/css/itcss/components/modal.scss index 3e804498968e..a192f20f1c20 100644 --- a/ui/app/css/itcss/components/modal.scss +++ b/ui/app/css/itcss/components/modal.scss @@ -287,7 +287,8 @@ } &__button { - @extend %Paragraph; + @include Paragraph; + @extend %button; width: 141px; diff --git a/ui/app/pages/home/index.scss b/ui/app/pages/home/index.scss index 37911bdc6c6d..4d4926e86f07 100644 --- a/ui/app/pages/home/index.scss +++ b/ui/app/pages/home/index.scss @@ -43,7 +43,7 @@ display: flex; flex-direction: column; - @extend %H6; + @include H6; padding-left: 24px; padding-right: 24px; diff --git a/ui/app/pages/permissions-connect/choose-account/index.scss b/ui/app/pages/permissions-connect/choose-account/index.scss index b79c9c923bd2..bb4b05a42933 100644 --- a/ui/app/pages/permissions-connect/choose-account/index.scss +++ b/ui/app/pages/permissions-connect/choose-account/index.scss @@ -24,13 +24,13 @@ &__title { - @extend %H4; + @include H4; } &__text, &__text-blue, &__text-grey { - @extend %H6; + @include H6; } &__text-blue { @@ -120,7 +120,7 @@ } &__label { - @extend %H6; + @include H6; color: $Black-100; text-overflow: ellipsis; @@ -129,13 +129,13 @@ } &__balance { - @extend %H7; + @include H7; color: $Grey-500; } &__last-connected { - @extend %H8; + @include H8; display: flex; flex-direction: column; diff --git a/ui/app/pages/permissions-connect/index.scss b/ui/app/pages/permissions-connect/index.scss index 58263d9b489f..eb6b14dbf2b2 100644 --- a/ui/app/pages/permissions-connect/index.scss +++ b/ui/app/pages/permissions-connect/index.scss @@ -24,7 +24,7 @@ } &__back { - @extend %H6; + @include H6; color: $Grey-600; cursor: pointer; @@ -35,7 +35,7 @@ } &__page-count { - @extend %H7; + @include H7; color: #6a737d; grid-column: 2; diff --git a/ui/app/pages/permissions-connect/redirect/index.scss b/ui/app/pages/permissions-connect/redirect/index.scss index 2151385ed1c4..fa90ca0c1cc4 100644 --- a/ui/app/pages/permissions-connect/redirect/index.scss +++ b/ui/app/pages/permissions-connect/redirect/index.scss @@ -4,7 +4,7 @@ justify-content: center; &__result { - @extend %H3; + @include H3; position: absolute; top: 30%; diff --git a/ui/app/pages/send/send.scss b/ui/app/pages/send/send.scss index eb64a4105679..81de75a853da 100644 --- a/ui/app/pages/send/send.scss +++ b/ui/app/pages/send/send.scss @@ -6,7 +6,7 @@ padding: 14px 0 3px 0; .page-container__title { - @extend %H4; + @include H4; text-align: center; } @@ -91,7 +91,7 @@ } &__group-label { - @extend %H8; + @include H8; background-color: $Grey-000; color: $Grey-600; @@ -136,7 +136,7 @@ } &__subtitle { - @extend %H8; + @include H8; color: $Grey-500; } @@ -186,7 +186,7 @@ } &__input { - @extend %H6; + @include H6; flex: 1 1 auto; width: 0; From b7715f6e7011ca73f69c5b1705148ed6e4bedb76 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Wed, 29 Jul 2020 13:09:52 -0300 Subject: [PATCH 017/107] Only log error on first occurrence of missing substitution (#9096) A missing substitution for a localized message will now only log an error upon the first occurrence. Further errors are generally not useful. --- ui/app/helpers/utils/i18n-helper.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ui/app/helpers/utils/i18n-helper.js b/ui/app/helpers/utils/i18n-helper.js index a32120b4f71d..7fccc92cbf83 100644 --- a/ui/app/helpers/utils/i18n-helper.js +++ b/ui/app/helpers/utils/i18n-helper.js @@ -6,6 +6,7 @@ import * as Sentry from '@sentry/browser' const warned = {} const missingMessageErrors = {} +const missingSubstitutionErrors = {} /** * Returns a localized message for the given key @@ -55,7 +56,11 @@ export const getMessage = (localeCode, localeMessages, key, substitutions) => { return part } const substituteIndex = Number(subMatch[1]) - 1 - if (substitutions[substituteIndex] == null) { + if (substitutions[substituteIndex] == null && !missingSubstitutionErrors[localeCode]?.[key]) { + if (!missingSubstitutionErrors[localeCode]) { + missingSubstitutionErrors[localeCode] = {} + } + missingSubstitutionErrors[localeCode][key] = true const error = new Error(`Insufficient number of substitutions for message: '${phrase}'`) log.error(error) Sentry.captureException(error) From d7a5319222d597a6470aab8fd566a30f31f70f96 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Wed, 29 Jul 2020 13:14:08 -0300 Subject: [PATCH 018/107] Use environment variable for production Sentry DSN (#9097) The Sentry DSN is now expected to be provided via environment variable for production builds. The build script will fail if it is missing, and an error will be thrown at runtime if it is missing. The `SENTRY_DSN` environment variable has been set in CI to the old value for `SENTRY_PROD_DSN`. We can migrate to a new DSN at some point in the future. --- app/scripts/lib/setupSentry.js | 12 +++++++----- development/build/scripts.js | 5 +++++ 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/app/scripts/lib/setupSentry.js b/app/scripts/lib/setupSentry.js index 9eca2ef5b23e..a6d1bb9b2b2b 100644 --- a/app/scripts/lib/setupSentry.js +++ b/app/scripts/lib/setupSentry.js @@ -5,7 +5,6 @@ import extractEthjsErrorMessage from './extractEthjsErrorMessage' const METAMASK_DEBUG = process.env.METAMASK_DEBUG const METAMASK_ENVIRONMENT = process.env.METAMASK_ENVIRONMENT -const SENTRY_DSN_PROD = 'https://3567c198f8a8412082d32655da2961d0@sentry.io/273505' const SENTRY_DSN_DEV = 'https://f59f3dd640d2429d9d0e2445a87ea8e1@sentry.io/273496' // This describes the subset of Redux state attached to errors sent to Sentry @@ -74,12 +73,15 @@ export default function setupSentry ({ release, getState }) { if (METAMASK_DEBUG) { return - } else if (process.env.IN_TEST) { + } else if (METAMASK_ENVIRONMENT === 'production') { + if (!process.env.SENTRY_DSN) { + throw new Error(`Missing SENTRY_DSN environment variable in production environment`) + } + console.log(`Setting up Sentry Remote Error Reporting for '${METAMASK_ENVIRONMENT}': SENTRY_DSN`) + sentryTarget = process.env.SENTRY_DSN + } else { console.log(`Setting up Sentry Remote Error Reporting for '${METAMASK_ENVIRONMENT}': SENTRY_DSN_DEV`) sentryTarget = SENTRY_DSN_DEV - } else { - console.log(`Setting up Sentry Remote Error Reporting for '${METAMASK_ENVIRONMENT}': SENTRY_DSN_PROD`) - sentryTarget = SENTRY_DSN_PROD } Sentry.init({ diff --git a/development/build/scripts.js b/development/build/scripts.js index 2ce9b86a0877..8a6b4911113f 100644 --- a/development/build/scripts.js +++ b/development/build/scripts.js @@ -330,6 +330,10 @@ function createScriptTasks ({ browserPlatforms, livereload }) { environment = 'other' } + if (environment === 'production' && !process.env.SENTRY_DSN) { + throw new Error('Missing SENTRY_DSN environment variable') + } + // Inject variables into bundle bundler.transform(envify({ METAMASK_DEBUG: opts.devMode, @@ -341,6 +345,7 @@ function createScriptTasks ({ browserPlatforms, livereload }) { PUBNUB_PUB_KEY: process.env.PUBNUB_PUB_KEY || '', ETH_GAS_STATION_API_KEY: process.env.ETH_GAS_STATION_API_KEY || '', CONF: opts.devMode ? conf : ({}), + SENTRY_DSN: process.env.SENTRY_DSN, }), { global: true, }) From a3cad5d52e15cdbe8de9c90369927ab2e64a7ee8 Mon Sep 17 00:00:00 2001 From: Erik Marks <25517051+rekmarks@users.noreply.github.com> Date: Wed, 29 Jul 2020 12:56:24 -0700 Subject: [PATCH 019/107] rpc-cap@3.1.0 (#9103) --- package.json | 2 +- yarn.lock | 83 +++++++++++++++++++++++----------------------------- 2 files changed, 38 insertions(+), 47 deletions(-) diff --git a/package.json b/package.json index cafe5bc84018..f26a98e85fb2 100644 --- a/package.json +++ b/package.json @@ -157,7 +157,7 @@ "redux": "^4.0.5", "redux-thunk": "^2.3.0", "reselect": "^3.0.1", - "rpc-cap": "^3.0.1", + "rpc-cap": "^3.1.0", "safe-event-emitter": "^1.0.1", "safe-json-stringify": "^1.2.0", "single-call-balance-checker-abi": "^1.0.0", diff --git a/yarn.lock b/yarn.lock index 83d52e6c2814..eae41c1f7afb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1714,6 +1714,34 @@ web3 "^0.20.7" web3-provider-engine "^15.0.4" +"@metamask/controllers@^2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@metamask/controllers/-/controllers-2.0.2.tgz#100a5d87b6061751b39edec2288f16f07d471e2d" + integrity sha512-KKxNguTEdkzwvfv2Hl4BE2OMGULLeh7df6iAwcEG8ipXWbTWGZsLr13DBrczQxRi8TiNPaksAakv++6sMu6ngA== + dependencies: + await-semaphore "^0.1.3" + eth-contract-metadata "^1.11.0" + eth-ens-namehash "^2.0.8" + eth-json-rpc-errors "^2.0.2" + eth-json-rpc-infura "^4.0.1" + eth-keyring-controller "^5.6.1" + eth-method-registry "1.1.0" + eth-phishing-detect "^1.1.13" + eth-query "^2.1.2" + eth-sig-util "^2.3.0" + ethereumjs-util "^6.1.0" + ethereumjs-wallet "0.6.0" + ethjs-query "^0.3.8" + human-standard-collectible-abi "^1.0.2" + human-standard-token-abi "^2.0.0" + isomorphic-fetch "^2.2.1" + jsonschema "^1.2.4" + percentile "^1.2.1" + single-call-balance-checker-abi "^1.0.0" + uuid "^3.3.2" + web3 "^0.20.7" + web3-provider-engine "^15.0.4" + "@metamask/eslint-config@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@metamask/eslint-config/-/eslint-config-1.1.0.tgz#03c6fbec8ba3d95fa017d8b98ab5d0701f7458a4" @@ -10116,7 +10144,7 @@ eth-json-rpc-middleware@^5.0.2: pify "^3.0.0" safe-event-emitter "^1.0.1" -eth-keyring-controller@^5.3.0, eth-keyring-controller@^5.6.1: +eth-keyring-controller@^5.6.1: version "5.6.1" resolved "https://registry.yarnpkg.com/eth-keyring-controller/-/eth-keyring-controller-5.6.1.tgz#7b7268400704c8f5ce98a055910341177dd207ca" integrity sha512-sxJ87bJg7PvvPzj1sY1jJYHQL1HVUhh84Q/a4QPrcnzAAng1yibvvUfww0pCez4XJfHuMkJvUxfF8eAusJM8fQ== @@ -12040,34 +12068,6 @@ fuse.js@^3.4.6: resolved "https://registry.yarnpkg.com/fuse.js/-/fuse.js-3.4.6.tgz#545c3411fed88bf2e27c457cab6e73e7af697a45" integrity sha512-H6aJY4UpLFwxj1+5nAvufom5b2BT2v45P1MkPvdGIK8fWjQx/7o6tTT1+ALV0yawQvbmvCF0ufl2et8eJ7v7Cg== -gaba@^1.9.3: - version "1.11.0" - resolved "https://registry.yarnpkg.com/gaba/-/gaba-1.11.0.tgz#7ff49cad2f2b95941222121a1984bb63783fe076" - integrity sha512-rQcLgR/FHQ8W6oNza/vxFnkbav0vgauBxm6LKqK6BkbjJGB979qoBRVja9fU/H5dUMiYmHJO9CrHjL0ofG/ukA== - dependencies: - await-semaphore "^0.1.3" - eth-contract-metadata "^1.11.0" - eth-ens-namehash "^2.0.8" - eth-json-rpc-errors "^2.0.2" - eth-json-rpc-infura "^4.0.1" - eth-keyring-controller "^5.3.0" - eth-method-registry "1.1.0" - eth-phishing-detect "^1.1.13" - eth-query "^2.1.2" - eth-sig-util "^2.3.0" - ethereumjs-util "^6.1.0" - ethereumjs-wallet "0.6.0" - ethjs-query "^0.3.8" - human-standard-collectible-abi "^1.0.2" - human-standard-token-abi "^2.0.0" - isomorphic-fetch "^2.2.1" - jsonschema "^1.2.4" - percentile "^1.2.1" - single-call-balance-checker-abi "^1.0.0" - uuid "^3.3.2" - web3 "^0.20.7" - web3-provider-engine "^15.0.4" - ganache-cli@^6.9.1: version "6.9.1" resolved "https://registry.yarnpkg.com/ganache-cli/-/ganache-cli-6.9.1.tgz#1e13eee098fb9f19b031a191ec3f62ae926ea8b3" @@ -14046,11 +14046,6 @@ interpret@^2.0.0: resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.0.0.tgz#b783ffac0b8371503e9ab39561df223286aa5433" integrity sha512-e0/LknJ8wpMMhTiWcjivB+ESwIuvHnBSlBbmP/pSb8CQJldoj1p2qv7xGZ/+BtbTziYRFSz8OsvdbiX45LtYQA== -intersect-objects@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/intersect-objects/-/intersect-objects-1.0.0.tgz#b7630d28994b89b0f04d44728106136549ce816e" - integrity sha512-MS1xypHKJhWopnJgn4IbitVvt2vFy2KjINQJAPhAtDejZ+ZbMDfyPc6JsS/mWFRt9Eoku4A4usE4f2loEOoeKQ== - into-stream@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/into-stream/-/into-stream-3.1.0.tgz#96fb0a936c12babd6ff1752a17d05616abd094c6" @@ -15755,7 +15750,7 @@ json-rpc-engine@^3.4.0, json-rpc-engine@^3.6.0: promise-to-callback "^1.0.0" safe-event-emitter "^1.0.1" -json-rpc-engine@^5.1.3, json-rpc-engine@^5.1.5, json-rpc-engine@^5.1.8: +json-rpc-engine@^5.1.3, json-rpc-engine@^5.1.5: version "5.1.8" resolved "https://registry.yarnpkg.com/json-rpc-engine/-/json-rpc-engine-5.1.8.tgz#5ba0147ce571899bbaa7133ffbc05317c34a3c7f" integrity sha512-vTBSDEPJV1fPAsbm2g5sEuPjsgLdiab2f1CTn2PyRr8nxggUpA996PDlNQDsM0gnrA99F8KIBLq2nIKrOFl1Mg== @@ -23363,19 +23358,15 @@ rn-host-detect@^1.1.5: resolved "https://registry.yarnpkg.com/rn-host-detect/-/rn-host-detect-1.1.5.tgz#fbecb982b73932f34529e97932b9a63e58d8deb6" integrity sha512-ufk2dFT3QeP9HyZ/xTuMtW27KnFy815CYitJMqQm+pgG3ZAtHBsrU8nXizNKkqXGy3bQmhEoloVbrfbvMJMqkg== -rpc-cap@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/rpc-cap/-/rpc-cap-3.0.1.tgz#127fdc37563736f3e15c4550af31f6866490dd85" - integrity sha512-egeRnp+QVennA3obsOhVi/XhNSR4qQk8LHu0YFBiEpUHcm+68FqBkjvx0U/3CSXBmrRc8WRLo6kE3T64gWW02w== +rpc-cap@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/rpc-cap/-/rpc-cap-3.1.0.tgz#61ae8ca27c43da93f40972393ff34df1a28c3b5e" + integrity sha512-DyvT8xJEJVqdWP+fJ4VCI+q+sG30eOhAgaAczZ7/13ah8mtcl2pv4d5MffmTg8kJM4EId2eEExTVhBG8wgaomg== dependencies: - clone "^2.1.2" - eth-json-rpc-errors "^2.0.2" - fast-deep-equal "^2.0.1" - gaba "^1.9.3" - intersect-objects "^1.0.0" + "@metamask/controllers" "^2.0.2" + eth-rpc-errors "^2.1.1" is-subset "^0.1.1" - json-rpc-engine "^5.1.8" - obs-store "^4.0.3" + json-rpc-engine "^5.2.0" uuid "^3.3.2" rsa-pem-to-jwk@^1.1.3: From 46ba1ef1008d5111924132229c281a449ea1a13a Mon Sep 17 00:00:00 2001 From: Brad Decker Date: Wed, 29 Jul 2020 15:04:02 -0500 Subject: [PATCH 020/107] Update font family globally (#9073) --- .../gas-customization/gas-price-chart/index.scss | 2 -- .../components/app/home-notification/index.scss | 2 -- .../components/app/signature-request/index.scss | 2 -- .../signature-request-message/index.scss | 2 -- ui/app/components/ui/button-group/index.scss | 1 - ui/app/components/ui/button/buttons.scss | 2 -- ui/app/components/ui/dropdown/dropdown.scss | 1 - ui/app/components/ui/menu/menu.scss | 2 -- ui/app/components/ui/toggle-button/index.scss | 1 - ui/app/components/ui/unit-input/index.scss | 1 - ui/app/css/base-styles.scss | 6 +++++- ui/app/css/design-system/typography.scss | 2 +- ui/app/css/itcss/components/account-dropdown.scss | 6 ------ ui/app/css/itcss/components/confirm.scss | 6 ------ ui/app/css/itcss/components/currency-display.scss | 4 ---- ui/app/css/itcss/components/modal.scss | 14 -------------- ui/app/css/itcss/components/network.scss | 3 --- ui/app/css/itcss/components/new-account.scss | 10 ---------- ui/app/css/itcss/components/newui-sections.scss | 2 -- .../itcss/components/request-decrypt-message.scss | 4 ---- .../components/request-encryption-public-key.scss | 4 ---- .../css/itcss/components/request-signature.scss | 7 ------- ui/app/css/itcss/components/sections.scss | 5 ----- ui/app/css/itcss/components/send.scss | 15 --------------- ui/app/css/itcss/components/tooltip.scss | 1 - ui/app/css/itcss/components/transaction-list.scss | 2 -- ui/app/css/reset.scss | 6 +++++- .../confirm-approve-content/index.scss | 2 -- ui/app/pages/error/index.scss | 1 - .../pages/first-time-flow/end-of-flow/index.scss | 1 - ui/app/pages/first-time-flow/index.scss | 2 -- .../first-time-flow/metametrics-opt-in/index.scss | 2 -- .../first-time-flow/select-action/index.scss | 3 --- ui/app/pages/keychains/index.scss | 5 ----- ui/app/pages/settings/contact-list-tab/index.scss | 3 --- ui/app/pages/settings/index.scss | 1 - ui/app/pages/settings/networks-tab/index.scss | 2 -- 37 files changed, 11 insertions(+), 124 deletions(-) diff --git a/ui/app/components/app/gas-customization/gas-price-chart/index.scss b/ui/app/components/app/gas-customization/gas-price-chart/index.scss index a26b477a029a..774760cffd7c 100644 --- a/ui/app/components/app/gas-customization/gas-price-chart/index.scss +++ b/ui/app/components/app/gas-customization/gas-price-chart/index.scss @@ -17,7 +17,6 @@ .tick text, .c3-axis-x-label, .c3-axis-y-label { - font-family: Roboto; font-style: normal; font-weight: bold; line-height: normal; @@ -53,7 +52,6 @@ } .custom-tooltip th { - font-family: Roboto; font-style: normal; font-weight: 500; line-height: normal; diff --git a/ui/app/components/app/home-notification/index.scss b/ui/app/components/app/home-notification/index.scss index 1aedb90ec3cd..56222b454adb 100644 --- a/ui/app/components/app/home-notification/index.scss +++ b/ui/app/components/app/home-notification/index.scss @@ -35,7 +35,6 @@ } &__text { - font-family: Roboto, 'sans-serif'; font-style: normal; font-weight: normal; font-size: 12px; @@ -112,7 +111,6 @@ } &__content { - font-family: Roboto, 'sans-serif'; font-style: normal; font-weight: normal; font-size: 12px; diff --git a/ui/app/components/app/signature-request/index.scss b/ui/app/components/app/signature-request/index.scss index 27e7fcfbc821..4de4f249b9ac 100644 --- a/ui/app/components/app/signature-request/index.scss +++ b/ui/app/components/app/signature-request/index.scss @@ -38,7 +38,6 @@ min-height: min-content; &__title { - font-family: Roboto; font-style: normal; font-weight: 500; font-size: 18px; @@ -65,7 +64,6 @@ &__identicon-initial { position: absolute; - font-family: Roboto; font-style: normal; font-weight: 500; font-size: 60px; diff --git a/ui/app/components/app/signature-request/signature-request-message/index.scss b/ui/app/components/app/signature-request/signature-request-message/index.scss index 7eef17560fdd..37c0fba7250c 100644 --- a/ui/app/components/app/signature-request/signature-request-message/index.scss +++ b/ui/app/components/app/signature-request/signature-request-message/index.scss @@ -28,7 +28,6 @@ padding-left: 12px; padding-right: 12px; width: 360px; - font-family: monospace; @media screen and (min-width: 576px) { width: auto; @@ -36,7 +35,6 @@ } &__type-title { - font-family: monospace; font-style: normal; font-weight: normal; font-size: 14px; diff --git a/ui/app/components/ui/button-group/index.scss b/ui/app/components/ui/button-group/index.scss index aea6d826f8c2..4ba758c30331 100644 --- a/ui/app/components/ui/button-group/index.scss +++ b/ui/app/components/ui/button-group/index.scss @@ -4,7 +4,6 @@ align-items: center; &__button { - font-family: Roboto; font-size: 1rem; color: $tundora; border-style: solid; diff --git a/ui/app/components/ui/button/buttons.scss b/ui/app/components/ui/button/buttons.scss index 693deec26ea7..0e38c049cd61 100644 --- a/ui/app/components/ui/button/buttons.scss +++ b/ui/app/components/ui/button/buttons.scss @@ -14,7 +14,6 @@ $warning-light-orange: #f8b588; @include H6; font-weight: 500; - font-family: Roboto, Arial; line-height: 1.25rem; padding: 0.75rem 1rem; display: flex; @@ -249,7 +248,6 @@ button.primary { box-shadow: 0 3px 6px rgba(247, 134, 28, 0.36); color: $white; font-size: 1.1em; - font-family: Roboto; text-transform: uppercase; } diff --git a/ui/app/components/ui/dropdown/dropdown.scss b/ui/app/components/ui/dropdown/dropdown.scss index 296ea95c03cf..49a684bb7ce3 100644 --- a/ui/app/components/ui/dropdown/dropdown.scss +++ b/ui/app/components/ui/dropdown/dropdown.scss @@ -11,7 +11,6 @@ background-position: right 18px top 50%; background-color: white; padding: 8px 32px 8px 16px; - font-family: Roboto, 'sans-serif'; font-size: 14px; [dir='rtl'] & { diff --git a/ui/app/components/ui/menu/menu.scss b/ui/app/components/ui/menu/menu.scss index de3935539814..a34deda532d7 100644 --- a/ui/app/components/ui/menu/menu.scss +++ b/ui/app/components/ui/menu/menu.scss @@ -9,7 +9,6 @@ flex-direction: column; align-items: center; padding: 0 16px; - font-family: Roboto, 'sans-serif'; font-size: 14px; font-weight: normal; line-height: 20px; @@ -28,7 +27,6 @@ .menu-item { background: none; - font-family: inherit; font-size: inherit; display: grid; grid-template-columns: min-content auto; diff --git a/ui/app/components/ui/toggle-button/index.scss b/ui/app/components/ui/toggle-button/index.scss index cdd99f7f7ead..09827888cd2d 100644 --- a/ui/app/components/ui/toggle-button/index.scss +++ b/ui/app/components/ui/toggle-button/index.scss @@ -3,7 +3,6 @@ $self: &; &__status { - font-family: Roboto; font-style: normal; font-weight: normal; font-size: 16px; diff --git a/ui/app/components/ui/unit-input/index.scss b/ui/app/components/ui/unit-input/index.scss index ed80f26ac315..c86f487b5cef 100644 --- a/ui/app/components/ui/unit-input/index.scss +++ b/ui/app/components/ui/unit-input/index.scss @@ -36,7 +36,6 @@ &__input { color: #4d4d4d; font-size: 1rem; - font-family: Roboto; border: none; max-width: 22ch; height: 16px; diff --git a/ui/app/css/base-styles.scss b/ui/app/css/base-styles.scss index 1e17639f7461..6a970f9a3a11 100644 --- a/ui/app/css/base-styles.scss +++ b/ui/app/css/base-styles.scss @@ -6,7 +6,6 @@ html, body { - font-family: Roboto, Arial; color: #4d4d4d; width: 100%; height: 100%; @@ -107,3 +106,8 @@ input.form-control { } } +/** Default Typography on base elements **/ +* { + font-family: $font-family; +} + diff --git a/ui/app/css/design-system/typography.scss b/ui/app/css/design-system/typography.scss index 8c37bcea7f31..70cc4bef44ad 100644 --- a/ui/app/css/design-system/typography.scss +++ b/ui/app/css/design-system/typography.scss @@ -67,7 +67,7 @@ $fa-font-path: 'fonts/fontawesome'; src: url('fonts/Euclid/EuclidCircularB-Bold-WebXL.ttf') format('truetype'); } -$font-family: Roboto, Helvetica, Arial, sans-serif; +$font-family: Euclid, Roboto, Helvetica, Arial, sans-serif; // Typography @mixin H1 { diff --git a/ui/app/css/itcss/components/account-dropdown.scss b/ui/app/css/itcss/components/account-dropdown.scss index 1d7baf7b6cb9..3d2e29b42dbf 100644 --- a/ui/app/css/itcss/components/account-dropdown.scss +++ b/ui/app/css/itcss/components/account-dropdown.scss @@ -1,7 +1,3 @@ -.account-dropdown-name { - font-family: Roboto; -} - .account-dropdown-balance { color: $dusty-gray; line-height: 19px; @@ -9,7 +5,6 @@ .account-dropdown-edit-button { color: $dusty-gray; - font-family: Roboto; &:hover { color: $white; @@ -69,7 +64,6 @@ &__account-primary-balance, &__account-secondary-balance { - font-family: Roboto; line-height: 16px; font-size: 12px; } diff --git a/ui/app/css/itcss/components/confirm.scss b/ui/app/css/itcss/components/confirm.scss index 7a034e4e9174..b382bcae608e 100644 --- a/ui/app/css/itcss/components/confirm.scss +++ b/ui/app/css/itcss/components/confirm.scss @@ -1,7 +1,6 @@ .confirm-screen-container { position: relative; align-items: center; - font-family: Roboto; flex: 1 0 auto; flex-flow: column nowrap; box-shadow: 0 2px 4px 0 rgba($black, 0.08); @@ -30,7 +29,6 @@ flex-flow: column nowrap; z-index: 25; align-items: center; - font-family: Roboto; position: relative; overflow-y: auto; overflow-x: hidden; @@ -99,7 +97,6 @@ .confirm-screen-back-button { color: $primary-blue; - font-family: Roboto; font-size: 1rem; position: absolute; top: 38px; @@ -162,7 +159,6 @@ text-align: center; font-size: 16px; margin-top: 30px; - font-family: Roboto; font-weight: 300; } @@ -324,7 +320,6 @@ section .confirm-screen-account-number, font-size: 16px; color: $white; text-align: center; - font-family: Roboto; padding-top: 15px; padding-bottom: 15px; border-width: 0; @@ -338,7 +333,6 @@ section .confirm-screen-account-number, background: none; border: none; opacity: 1; - font-family: Roboto; border-width: 0; padding-top: 15px; padding-bottom: 15px; diff --git a/ui/app/css/itcss/components/currency-display.scss b/ui/app/css/itcss/components/currency-display.scss index 9ece7a6721c2..b8a23d06b13d 100644 --- a/ui/app/css/itcss/components/currency-display.scss +++ b/ui/app/css/itcss/components/currency-display.scss @@ -4,7 +4,6 @@ border-radius: 4px; background-color: $white; color: $scorpion; - font-family: Roboto; font-size: 16px; padding: 8px 10px; position: relative; @@ -15,7 +14,6 @@ &__input { color: $scorpion; - font-family: Roboto; font-size: 16px; line-height: 22px; border: none; @@ -25,7 +23,6 @@ &__primary-currency { color: $scorpion; font-weight: 400; - font-family: Roboto; font-size: 16px; line-height: 22px; } @@ -37,7 +34,6 @@ &__converted-value, &__converted-currency { color: $dusty-gray; - font-family: Roboto; font-size: 12px; line-height: 12px; } diff --git a/ui/app/css/itcss/components/modal.scss b/ui/app/css/itcss/components/modal.scss index a192f20f1c20..318cfb1ee91c 100644 --- a/ui/app/css/itcss/components/modal.scss +++ b/ui/app/css/itcss/components/modal.scss @@ -39,7 +39,6 @@ padding: 5px 0 31px 0; border: 1px solid $silver; border-radius: 4px; - font-family: Roboto; button { cursor: pointer; @@ -55,7 +54,6 @@ &__text { margin-top: 2px; - font-family: Roboto; font-size: 14px; line-height: 18px; } @@ -101,7 +99,6 @@ justify-content: center; border: 1px solid $alto; padding: 5px 10px; - font-family: Roboto; margin-top: 7px; width: 286px; } @@ -166,7 +163,6 @@ .private-key-password::-webkit-input-placeholder { color: $dusty-gray; - font-family: Roboto; } .private-key-password-warning { @@ -179,7 +175,6 @@ width: 292px; padding: 9px 15px; margin-top: 18px; - font-family: Roboto; } .export-private-key-buttons { @@ -207,7 +202,6 @@ .private-key-password-display-textarea { color: $crimson; - font-family: Roboto; font-size: 16px; line-height: 21px; border: none; @@ -225,7 +219,6 @@ position: absolute; top: 25px; right: 17.5px; - font-family: sans-serif; cursor: pointer; } @@ -250,7 +243,6 @@ &__symbol { color: $tundora; - font-family: Roboto; font-size: 16px; line-height: 24px; text-align: center; @@ -261,7 +253,6 @@ height: 30px; width: 271.28px; color: $tundora; - font-family: Roboto; font-size: 22px; line-height: 30px; text-align: center; @@ -272,7 +263,6 @@ height: 41px; width: 318px; color: $scorpion; - font-family: Roboto; font-size: 14px; line-height: 18px; text-align: center; @@ -307,7 +297,6 @@ position: relative; border: 1px solid $alto; box-shadow: 0 0 2px 2px $alto; - font-family: Roboto; } &__header { @@ -349,7 +338,6 @@ // Deposit Ether Modal .deposit-ether-modal { border-radius: 8px; - font-family: Roboto; display: flex; flex-flow: column; height: 100%; @@ -503,7 +491,6 @@ width: 100%; height: 45px; line-height: 44px; - font-family: Roboto; font-weight: 300; } @@ -522,7 +509,6 @@ position: relative; border: 1px solid $alto; box-shadow: 0 0 2px 2px $alto; - font-family: Roboto; } .notification-modal-header { diff --git a/ui/app/css/itcss/components/network.scss b/ui/app/css/itcss/components/network.scss index 02d0dcfcd35f..ef8e7843ff86 100644 --- a/ui/app/css/itcss/components/network.scss +++ b/ui/app/css/itcss/components/network.scss @@ -66,7 +66,6 @@ .network-name { padding: 0 4px; - font-family: Roboto; font-size: 12px; line-height: 14px; flex: 1 1 auto; @@ -168,7 +167,6 @@ height: 25px; width: 120px; color: $white; - font-family: Roboto; font-size: 18px; line-height: 25px; text-align: center; @@ -178,7 +176,6 @@ min-height: 36px; width: 265px; color: $dusty-gray; - font-family: Roboto; font-size: 14px; line-height: 18px; } diff --git a/ui/app/css/itcss/components/new-account.scss b/ui/app/css/itcss/components/new-account.scss index 77fd07afa104..c7aa38e035ca 100644 --- a/ui/app/css/itcss/components/new-account.scss +++ b/ui/app/css/itcss/components/new-account.scss @@ -23,7 +23,6 @@ &__title { color: $tundora; - font-family: Roboto; font-size: 32px; font-weight: 500; line-height: 43px; @@ -40,7 +39,6 @@ height: 54px; padding: 15px 10px; color: $dusty-gray; - font-family: Roboto; font-size: 18px; line-height: 24px; text-align: center; @@ -87,7 +85,6 @@ &__select-label { color: $scorpion; - font-family: Roboto; font-size: 16px; line-height: 21px; } @@ -123,7 +120,6 @@ &__instruction { color: $scorpion; - font-family: Roboto; font-size: 16px; line-height: 21px; align-self: flex-start; @@ -144,7 +140,6 @@ background-color: $white; margin-top: 16px; color: $scorpion; - font-family: Roboto; font-size: 16px; padding: 0 20px; } @@ -364,7 +359,6 @@ margin-bottom: 23px; align-self: flex-start; color: $scorpion; - font-family: Roboto; font-size: 16px; line-height: 21px; font-weight: bold; @@ -376,7 +370,6 @@ margin-bottom: 23px; align-self: flex-end; color: $scorpion; - font-family: Roboto; font-size: 16px; line-height: 21px; font-weight: normal; @@ -461,7 +454,6 @@ margin-left: 16px; padding: 0; text-transform: uppercase; - font-family: Roboto; } } @@ -520,7 +512,6 @@ &__input-label { color: $scorpion; - font-family: Roboto; font-size: 16px; line-height: 21px; align-self: flex-start; @@ -533,7 +524,6 @@ border-radius: 4px; background-color: $white; color: $scorpion; - font-family: Roboto; font-size: 16px; line-height: 21px; margin-top: 15px; diff --git a/ui/app/css/itcss/components/newui-sections.scss b/ui/app/css/itcss/components/newui-sections.scss index 2572c5fa40e0..57c1f6d77085 100644 --- a/ui/app/css/itcss/components/newui-sections.scss +++ b/ui/app/css/itcss/components/newui-sections.scss @@ -17,7 +17,6 @@ $sub-mid-size-breakpoint-range: "screen and (min-width: #{$break-large}) and (ma // Main container .main-container { z-index: $main-container-z-index; - font-family: Roboto; } .main-container::-webkit-scrollbar { @@ -120,7 +119,6 @@ $sub-mid-size-breakpoint-range: "screen and (min-width: #{$break-large}) and (ma .unlock-screen-container { z-index: $main-container-z-index; - font-family: Roboto; display: flex; justify-content: center; align-items: center; diff --git a/ui/app/css/itcss/components/request-decrypt-message.scss b/ui/app/css/itcss/components/request-decrypt-message.scss index 7387e944a57d..fd2ee53a8a6f 100644 --- a/ui/app/css/itcss/components/request-decrypt-message.scss +++ b/ui/app/css/itcss/components/request-decrypt-message.scss @@ -8,7 +8,6 @@ flex-flow: column nowrap; z-index: 25; align-items: center; - font-family: Roboto; position: relative; height: 100%; @@ -63,7 +62,6 @@ &__header__text { color: #5b5d67; - font-family: Roboto; font-size: 22px; line-height: 29px; z-index: 3; @@ -105,7 +103,6 @@ &__account-item { height: 22px; background-color: $white; - font-family: Roboto; line-height: 16px; font-size: 12px; width: 124px; @@ -156,7 +153,6 @@ } &__notice { - font-family: "Avenir Next"; font-size: 14px; line-height: 19px; text-align: center; diff --git a/ui/app/css/itcss/components/request-encryption-public-key.scss b/ui/app/css/itcss/components/request-encryption-public-key.scss index a4d6012670f9..8860ca5d897b 100644 --- a/ui/app/css/itcss/components/request-encryption-public-key.scss +++ b/ui/app/css/itcss/components/request-encryption-public-key.scss @@ -8,7 +8,6 @@ flex-flow: column nowrap; z-index: 25; align-items: center; - font-family: Roboto; position: relative; height: 100%; @@ -63,7 +62,6 @@ &__header__text { color: #5b5d67; - font-family: Roboto; font-size: 22px; line-height: 29px; z-index: 3; @@ -105,7 +103,6 @@ &__account-item { height: 22px; background-color: $white; - font-family: Roboto; line-height: 16px; font-size: 12px; width: 124px; @@ -156,7 +153,6 @@ } &__notice { - font-family: "Avenir Next"; font-size: 14px; line-height: 19px; text-align: center; diff --git a/ui/app/css/itcss/components/request-signature.scss b/ui/app/css/itcss/components/request-signature.scss index 03623e187bd5..6af476fd697d 100644 --- a/ui/app/css/itcss/components/request-signature.scss +++ b/ui/app/css/itcss/components/request-signature.scss @@ -8,7 +8,6 @@ flex-flow: column nowrap; z-index: 25; align-items: center; - font-family: Roboto; position: relative; height: 100%; @@ -63,7 +62,6 @@ &__header__text { color: #5b5d67; - font-family: Roboto; font-size: 22px; line-height: 29px; z-index: 3; @@ -104,7 +102,6 @@ &__account-item { height: 22px; background-color: $white; - font-family: Roboto; line-height: 16px; font-size: 12px; width: 124px; @@ -163,7 +160,6 @@ height: 48px; width: 240px; color: $tundora; - font-family: Roboto; font-size: 18px; line-height: 24px; text-align: center; @@ -172,7 +168,6 @@ &__notice, &__warning { - font-family: "Avenir Next"; font-size: 14px; line-height: 19px; text-align: center; @@ -207,7 +202,6 @@ &__row-title { width: 80px; color: $dusty-gray; - font-family: Roboto; font-size: 16px; line-height: 22px; margin-top: 12px; @@ -217,7 +211,6 @@ &__row-value { color: $scorpion; - font-family: Roboto; font-size: 14px; line-height: 19px; width: 100%; diff --git a/ui/app/css/itcss/components/sections.scss b/ui/app/css/itcss/components/sections.scss index f9bba2827f52..c187b121bd82 100644 --- a/ui/app/css/itcss/components/sections.scss +++ b/ui/app/css/itcss/components/sections.scss @@ -252,7 +252,6 @@ textarea.twelve-word-phrase { /* Info screen */ .info-gray { - font-family: Roboto; text-transform: uppercase; color: $silver-chalice; } @@ -262,7 +261,6 @@ textarea.twelve-word-phrase { } .info { - font-family: Roboto, Arial; padding-bottom: 10px; display: inline-block; padding-left: 5px; @@ -311,7 +309,6 @@ textarea.twelve-word-phrase { } .buy-inputs { - font-family: Roboto; font-size: 13px; height: 20px; background: transparent; @@ -355,7 +352,6 @@ textarea.twelve-word-phrase { } .ex-coins { - font-family: Roboto; text-transform: uppercase; text-align: center; font-size: 33px; @@ -366,7 +362,6 @@ textarea.twelve-word-phrase { } .marketinfo { - font-family: Roboto; color: $silver-chalice; font-size: 15px; line-height: 17px; diff --git a/ui/app/css/itcss/components/send.scss b/ui/app/css/itcss/components/send.scss index e82d938d662e..df18572cd196 100644 --- a/ui/app/css/itcss/components/send.scss +++ b/ui/app/css/itcss/components/send.scss @@ -2,7 +2,6 @@ display: flex; flex-flow: column nowrap; z-index: 25; - font-family: Roboto; @media screen and (max-width: $break-small) { width: 100%; @@ -76,7 +75,6 @@ margin: 4px 0 20px; font-size: 16px; line-height: 22.4px; - font-family: Roboto; } .send-screen-gas-input { @@ -186,7 +184,6 @@ padding: 13px 19px; font-size: 16px; border-radius: 4px; - font-family: Roboto; font-weight: 500; } @@ -316,7 +313,6 @@ display: flex; flex-flow: column nowrap; z-index: 25; - font-family: Roboto; &__content { width: 498px; @@ -407,7 +403,6 @@ flex-flow: column nowrap; z-index: 25; align-items: center; - font-family: Roboto; position: relative; @media screen and (max-width: $break-small) { @@ -588,7 +583,6 @@ &__form-label { color: $scorpion; - font-family: Roboto; font-size: 16px; line-height: 22px; width: 95px; @@ -602,7 +596,6 @@ border: 1px solid $alto; border-radius: 4px; background-color: $white; - font-family: Roboto; line-height: 16px; font-size: 12px; color: $tundora; @@ -745,7 +738,6 @@ background-color: $white; color: $tundora; padding: 10px; - font-family: Roboto; font-size: 16px; line-height: 21px; } @@ -763,14 +755,12 @@ background-color: $white; color: $tundora; padding: 10px; - font-family: Roboto; font-size: 16px; line-height: 21px; } } &__amount-max { - font-family: Roboto; font-size: 12px; position: relative; display: inline-block; @@ -873,7 +863,6 @@ border-radius: 4px; background-color: #fff; box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.14); - font-family: Roboto; display: flex; flex-flow: column; @@ -903,7 +892,6 @@ content: '\00D7'; font-size: 1.8em; color: $dusty-gray; - font-family: sans-serif; cursor: pointer; margin-right: 19.25px; } @@ -1000,7 +988,6 @@ &__title { height: 26px; color: $tundora; - font-family: Roboto; font-size: 20px; line-height: 26px; margin-top: 17px; @@ -1010,7 +997,6 @@ height: 38px; width: 314px; color: $tundora; - font-family: Roboto; font-size: 14px; line-height: 19px; margin-top: 17px; @@ -1101,7 +1087,6 @@ font-size: 1em; font-size: 14px; color: #2f9ae0; - font-family: Roboto; } .sliders-icon { diff --git a/ui/app/css/itcss/components/tooltip.scss b/ui/app/css/itcss/components/tooltip.scss index 97840ca1a215..68728538ab4e 100644 --- a/ui/app/css/itcss/components/tooltip.scss +++ b/ui/app/css/itcss/components/tooltip.scss @@ -8,7 +8,6 @@ position: absolute; z-index: 1070; display: block; - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-style: normal; font-weight: normal; letter-spacing: normal; diff --git a/ui/app/css/itcss/components/transaction-list.scss b/ui/app/css/itcss/components/transaction-list.scss index 486c48694cb5..e0aef961e51a 100644 --- a/ui/app/css/itcss/components/transaction-list.scss +++ b/ui/app/css/itcss/components/transaction-list.scss @@ -26,7 +26,6 @@ align-self: center; font-size: 12px; color: $dusty-gray; - font-family: Roboto; text-transform: uppercase; } } @@ -163,7 +162,6 @@ .tx-list-date { color: $dusty-gray; font-size: 12px; - font-family: Roboto; } .tx-list-identicon-wrapper { diff --git a/ui/app/css/reset.scss b/ui/app/css/reset.scss index 43146fec8be3..9ee5607cf3aa 100644 --- a/ui/app/css/reset.scss +++ b/ui/app/css/reset.scss @@ -88,7 +88,11 @@ video { padding: 0; border: 0; font-size: 100%; - font: inherit; + font-weight: inherit; + font-style: inherit; + font-variant: inherit; + font-size: inherit; + line-height: inherit; vertical-align: baseline; } diff --git a/ui/app/pages/confirm-approve/confirm-approve-content/index.scss b/ui/app/pages/confirm-approve/confirm-approve-content/index.scss index 70771fcaf90a..d6a5d71c904d 100644 --- a/ui/app/pages/confirm-approve/confirm-approve-content/index.scss +++ b/ui/app/pages/confirm-approve/confirm-approve-content/index.scss @@ -4,7 +4,6 @@ align-items: center; width: 100%; height: 100%; - font-family: Roboto; font-style: normal; &__identicon-wrapper { @@ -21,7 +20,6 @@ flex-flow: column; align-items: center; width: 390px; - font-family: Roboto; font-style: normal; padding-left: 24px; padding-right: 24px; diff --git a/ui/app/pages/error/index.scss b/ui/app/pages/error/index.scss index cf0c2aeb4dfe..14ec9da846f4 100644 --- a/ui/app/pages/error/index.scss +++ b/ui/app/pages/error/index.scss @@ -2,7 +2,6 @@ display: flex; flex-flow: column nowrap; align-items: center; - font-family: Roboto; font-style: normal; font-weight: normal; padding: 35px 10px 10px 10px; diff --git a/ui/app/pages/first-time-flow/end-of-flow/index.scss b/ui/app/pages/first-time-flow/end-of-flow/index.scss index f085ea99e81f..34d04d1c2284 100644 --- a/ui/app/pages/first-time-flow/end-of-flow/index.scss +++ b/ui/app/pages/first-time-flow/end-of-flow/index.scss @@ -1,6 +1,5 @@ .end-of-flow { color: black; - font-family: Roboto; font-style: normal; .app-header__logo-container { diff --git a/ui/app/pages/first-time-flow/index.scss b/ui/app/pages/first-time-flow/index.scss index 3b7498b2bac4..19e769e10d1f 100644 --- a/ui/app/pages/first-time-flow/index.scss +++ b/ui/app/pages/first-time-flow/index.scss @@ -72,7 +72,6 @@ /*rtl:ignore*/ direction: ltr; font-size: 1rem; - font-family: Roboto; border: 1px solid #cdcdcd; border-radius: 6px; background-color: #fff; @@ -149,7 +148,6 @@ } &__checkbox-label { - font-family: Roboto; font-style: normal; font-weight: normal; line-height: normal; diff --git a/ui/app/pages/first-time-flow/metametrics-opt-in/index.scss b/ui/app/pages/first-time-flow/metametrics-opt-in/index.scss index a5cb6e1dadda..18df92891a62 100644 --- a/ui/app/pages/first-time-flow/metametrics-opt-in/index.scss +++ b/ui/app/pages/first-time-flow/metametrics-opt-in/index.scss @@ -27,7 +27,6 @@ &__title { position: relative; margin-top: 20px; - font-family: Roboto; font-style: normal; font-weight: normal; line-height: normal; @@ -43,7 +42,6 @@ } &__description { - font-family: Roboto; font-style: normal; font-weight: normal; line-height: 21px; diff --git a/ui/app/pages/first-time-flow/select-action/index.scss b/ui/app/pages/first-time-flow/select-action/index.scss index 34a72a10cc6d..1b398a73e242 100644 --- a/ui/app/pages/first-time-flow/select-action/index.scss +++ b/ui/app/pages/first-time-flow/select-action/index.scss @@ -11,7 +11,6 @@ } &__body-header { - font-family: Roboto; font-style: normal; font-weight: normal; line-height: 39px; @@ -59,7 +58,6 @@ } &__button-text-big { - font-family: Roboto; font-style: normal; font-weight: normal; line-height: 28px; @@ -70,7 +68,6 @@ } &__button-text-small { - font-family: Roboto; font-style: normal; font-weight: normal; line-height: 20px; diff --git a/ui/app/pages/keychains/index.scss b/ui/app/pages/keychains/index.scss index a70255d0d09b..54b52981fb65 100644 --- a/ui/app/pages/keychains/index.scss +++ b/ui/app/pages/keychains/index.scss @@ -95,7 +95,6 @@ .import-account__faq-link { font-size: 18px; line-height: 23px; - font-family: Roboto; } .import-account__selector-label { @@ -110,7 +109,6 @@ background-color: #fff; margin-top: 14px; color: #5b5d67; - font-family: Roboto; font-size: 18px; line-height: 23px; padding: 14px 21px; @@ -125,7 +123,6 @@ font-size: 18px; line-height: 23px; margin-top: 21px; - font-family: Roboto; } .import-account__input-wrapper { @@ -173,7 +170,6 @@ border: 1px solid #1b344d; border-radius: 4px; color: #1b344d; - font-family: Roboto; font-size: 18px; display: flex; flex-flow: column nowrap; @@ -190,7 +186,6 @@ .import-account__file-name { color: #000; - font-family: Roboto; font-size: 18px; line-height: 23px; margin-left: 22px; diff --git a/ui/app/pages/settings/contact-list-tab/index.scss b/ui/app/pages/settings/contact-list-tab/index.scss index 02273a66f476..9c33c3701424 100644 --- a/ui/app/pages/settings/contact-list-tab/index.scss +++ b/ui/app/pages/settings/contact-list-tab/index.scss @@ -33,7 +33,6 @@ &__header, &__header--edit { &__name { - font-family: Roboto; font-style: normal; font-weight: normal; font-size: 24px; @@ -179,7 +178,6 @@ } &__header { - font-family: Roboto; font-style: normal; font-weight: normal; font-size: 18px; @@ -193,7 +191,6 @@ } &__text { - font-family: Roboto; font-style: normal; font-weight: normal; font-size: 14px; diff --git a/ui/app/pages/settings/index.scss b/ui/app/pages/settings/index.scss index 0c4371cf4d2a..b72deb39e70c 100644 --- a/ui/app/pages/settings/index.scss +++ b/ui/app/pages/settings/index.scss @@ -70,7 +70,6 @@ } &__sub-header-text { - font-family: Roboto; font-style: normal; font-weight: normal; font-size: 20px; diff --git a/ui/app/pages/settings/networks-tab/index.scss b/ui/app/pages/settings/networks-tab/index.scss index 92504f641943..e539ece40f31 100644 --- a/ui/app/pages/settings/networks-tab/index.scss +++ b/ui/app/pages/settings/networks-tab/index.scss @@ -96,7 +96,6 @@ } &__network-form-label { - font-family: Roboto; font-style: normal; font-weight: normal; font-size: 14px; @@ -177,7 +176,6 @@ &__networks-list-name { margin-left: 11px; - font-family: Roboto; font-style: normal; font-weight: normal; font-size: 16px; From a69245d9ba0d110e9a6ad215f726800a1856ba5b Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Wed, 29 Jul 2020 17:31:01 -0300 Subject: [PATCH 021/107] Improve source maps (#9101) Our source maps were being corrupted during minification, because the `gulp-terser-js` plugin we were using didn't account for the existence of sourcemaps in the input. A configuration option to allow the input of sourcemaps was added in v5.2.0. The plugin has been updated, and we now use this option. Previously the generated sourcemaps had an invalid entry in the "sources" array, with the filename of the bundle itself. This was not a real source. After this change, this invalid source is no longer present. --- development/build/scripts.js | 3 +++ package.json | 2 +- yarn.lock | 20 ++++++++++---------- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/development/build/scripts.js b/development/build/scripts.js index 8a6b4911113f..04f21aa76585 100644 --- a/development/build/scripts.js +++ b/development/build/scripts.js @@ -206,6 +206,9 @@ function createScriptTasks ({ browserPlatforms, livereload }) { mangle: { reserved: [ 'MetamaskInpageProvider' ], }, + sourceMap: { + content: true, + }, })) } diff --git a/package.json b/package.json index f26a98e85fb2..78e66b49b6f1 100644 --- a/package.json +++ b/package.json @@ -236,7 +236,7 @@ "gulp-sass": "^4.0.0", "gulp-sourcemaps": "^2.6.0", "gulp-stylelint": "^13.0.0", - "gulp-terser-js": "^5.0.0", + "gulp-terser-js": "^5.2.2", "gulp-watch": "^5.0.1", "gulp-zip": "^4.0.0", "jsdom": "^11.2.0", diff --git a/yarn.lock b/yarn.lock index eae41c1f7afb..f1ffc3d0c78a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12979,14 +12979,14 @@ gulp-stylelint@^13.0.0: strip-ansi "^6.0.0" through2 "^3.0.1" -gulp-terser-js@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/gulp-terser-js/-/gulp-terser-js-5.0.0.tgz#b570bb14e9c836aab78d633411073af0df8ca514" - integrity sha512-X5bduLqvRdX1RwUJmdm1J+HZu85gfIKkmDK95J6AAfXd4W/oYANt82gmIMZfhUGEmLm6e6xtKGsfTW6c0BRWnQ== +gulp-terser-js@^5.2.2: + version "5.2.2" + resolved "https://registry.yarnpkg.com/gulp-terser-js/-/gulp-terser-js-5.2.2.tgz#3d93db9b2b83f35dfcc5b209af6b1762756eb6a3" + integrity sha512-4ull0HzTWeWjRPiGmAFmdhRcEDOG+r7aXivNHOBQzElLzMaeVKQwmCPDi2juBzUUkjAkPkKb1jHVoJN/PKTvcA== dependencies: - object-assign "^4.1.1" plugin-error "^1.0.1" - terser "^4.1.4" + source-map "^0.7.3" + terser "^4.6.12" through2 "^3.0.1" vinyl-sourcemaps-apply "^0.2.1" @@ -25703,10 +25703,10 @@ terser@^4.1.2, terser@^4.4.3: source-map "~0.6.1" source-map-support "~0.5.12" -terser@^4.1.4: - version "4.3.1" - resolved "https://registry.yarnpkg.com/terser/-/terser-4.3.1.tgz#09820bcb3398299c4b48d9a86aefc65127d0ed65" - integrity sha512-pnzH6dnFEsR2aa2SJaKb1uSCl3QmIsJ8dEkj0Fky+2AwMMcC9doMqLOQIH6wVTEKaVfKVvLSk5qxPBEZT9mywg== +terser@^4.6.12: + version "4.8.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-4.8.0.tgz#63056343d7c70bb29f3af665865a46fe03a0df17" + integrity sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw== dependencies: commander "^2.20.0" source-map "~0.6.1" From 1207d439459dab4fe9bef83e2c0a413c0acdf594 Mon Sep 17 00:00:00 2001 From: Brad Decker Date: Wed, 29 Jul 2020 16:05:19 -0500 Subject: [PATCH 022/107] update email us to contact us (#9104) --- app/_locales/am/messages.json | 3 --- app/_locales/ar/messages.json | 3 --- app/_locales/bg/messages.json | 3 --- app/_locales/bn/messages.json | 3 --- app/_locales/ca/messages.json | 3 --- app/_locales/cs/messages.json | 3 --- app/_locales/da/messages.json | 3 --- app/_locales/de/messages.json | 3 --- app/_locales/el/messages.json | 3 --- app/_locales/en/messages.json | 6 +++--- app/_locales/es/messages.json | 3 --- app/_locales/es_419/messages.json | 3 --- app/_locales/et/messages.json | 3 --- app/_locales/fa/messages.json | 3 --- app/_locales/fi/messages.json | 3 --- app/_locales/fil/messages.json | 3 --- app/_locales/fr/messages.json | 3 --- app/_locales/he/messages.json | 3 --- app/_locales/hi/messages.json | 3 --- app/_locales/hn/messages.json | 3 --- app/_locales/hr/messages.json | 3 --- app/_locales/ht/messages.json | 3 --- app/_locales/hu/messages.json | 3 --- app/_locales/id/messages.json | 3 --- app/_locales/it/messages.json | 3 --- app/_locales/kn/messages.json | 3 --- app/_locales/ko/messages.json | 3 --- app/_locales/lt/messages.json | 3 --- app/_locales/lv/messages.json | 3 --- app/_locales/ms/messages.json | 3 --- app/_locales/nl/messages.json | 3 --- app/_locales/no/messages.json | 3 --- app/_locales/pl/messages.json | 3 --- app/_locales/pt/messages.json | 3 --- app/_locales/pt_BR/messages.json | 3 --- app/_locales/ro/messages.json | 3 --- app/_locales/ru/messages.json | 3 --- app/_locales/sk/messages.json | 3 --- app/_locales/sl/messages.json | 3 --- app/_locales/sr/messages.json | 3 --- app/_locales/sv/messages.json | 3 --- app/_locales/sw/messages.json | 3 --- app/_locales/ta/messages.json | 3 --- app/_locales/th/messages.json | 3 --- app/_locales/tr/messages.json | 3 --- app/_locales/uk/messages.json | 3 --- app/_locales/zh_CN/messages.json | 3 --- app/_locales/zh_TW/messages.json | 3 --- ui/app/pages/settings/info-tab/info-tab.component.js | 4 ++-- 49 files changed, 5 insertions(+), 146 deletions(-) diff --git a/app/_locales/am/messages.json b/app/_locales/am/messages.json index c279f5f170ac..c0ccc935204f 100644 --- a/app/_locales/am/messages.json +++ b/app/_locales/am/messages.json @@ -359,9 +359,6 @@ "editContact": { "message": "ዕውቂያን አርትዕ" }, - "emailUs": { - "message": "ኢሜይል ያድርጉልን!" - }, "endOfFlowMessage1": { "message": "ፈተናውን አልፈዋል - የዘር ሐረግዎን ደህንነቱ በተጠበቀ መንገድ ያስቀምጡ፣ የእርስዎ ሃላፊነት ነው! " }, diff --git a/app/_locales/ar/messages.json b/app/_locales/ar/messages.json index 46f240e2c46a..40815b680c34 100644 --- a/app/_locales/ar/messages.json +++ b/app/_locales/ar/messages.json @@ -359,9 +359,6 @@ "editContact": { "message": "تعديل جهة الاتصال" }, - "emailUs": { - "message": "راسلنا عبر البريد الإلكتروني!" - }, "endOfFlowMessage1": { "message": "لقد نجحت في الاختبار - احتفظ بعبارة الأمان الخاصة بك في مكان آمن، إنها مسؤوليتك!" }, diff --git a/app/_locales/bg/messages.json b/app/_locales/bg/messages.json index 668960321bc3..21b285807382 100644 --- a/app/_locales/bg/messages.json +++ b/app/_locales/bg/messages.json @@ -359,9 +359,6 @@ "editContact": { "message": "Редактиране на контакт" }, - "emailUs": { - "message": "Изпратете ни имейл!" - }, "endOfFlowMessage1": { "message": "Преминахте теста - пазете основната си фраза на сигурно място, това е Ваша отговорност!" }, diff --git a/app/_locales/bn/messages.json b/app/_locales/bn/messages.json index 0b24f07a4ded..773217e6dd39 100644 --- a/app/_locales/bn/messages.json +++ b/app/_locales/bn/messages.json @@ -359,9 +359,6 @@ "editContact": { "message": "পরিচিতি সম্পাদনা করুন" }, - "emailUs": { - "message": "আমাদের ইমেল করুন!" - }, "endOfFlowMessage1": { "message": "আপনি টেস্টটি পাস করেছেন - আপনার সীডফ্রেজ নিরাপদে রাখুন, এটি আপনার দায়িত্ব!" }, diff --git a/app/_locales/ca/messages.json b/app/_locales/ca/messages.json index 1d5eed5719bf..e948bc86e245 100644 --- a/app/_locales/ca/messages.json +++ b/app/_locales/ca/messages.json @@ -356,9 +356,6 @@ "editContact": { "message": "Editar Contacte" }, - "emailUs": { - "message": "Envia'ns un correu!" - }, "endOfFlowMessage1": { "message": "Has passat el test - mantingues la teva seedphrase segura, és la teva responsabilitat!" }, diff --git a/app/_locales/cs/messages.json b/app/_locales/cs/messages.json index cf2cb9b059b2..b1f50c457369 100644 --- a/app/_locales/cs/messages.json +++ b/app/_locales/cs/messages.json @@ -148,9 +148,6 @@ "edit": { "message": "Upravit" }, - "emailUs": { - "message": "Napište nám e-mail!" - }, "enterPassword": { "message": "Zadejte heslo" }, diff --git a/app/_locales/da/messages.json b/app/_locales/da/messages.json index 136dd6a5504e..f6f6b0559012 100644 --- a/app/_locales/da/messages.json +++ b/app/_locales/da/messages.json @@ -359,9 +359,6 @@ "editContact": { "message": "Redigér Kontakt" }, - "emailUs": { - "message": "Send os en email!" - }, "endOfFlowMessage1": { "message": "Du bestod testen - pas godt på din seed-sætning, det er dit ansvar!" }, diff --git a/app/_locales/de/messages.json b/app/_locales/de/messages.json index 14192b687d33..cf8596b32935 100644 --- a/app/_locales/de/messages.json +++ b/app/_locales/de/messages.json @@ -344,9 +344,6 @@ "editContact": { "message": "Kontakt bearbeiten" }, - "emailUs": { - "message": "Schreib uns eine Mail!" - }, "endOfFlowMessage1": { "message": "Sie haben den Test bestanden - bewahren Sie Ihre mnemonische Phrase sicher auf, Sie sind dafür verantwortlich! " }, diff --git a/app/_locales/el/messages.json b/app/_locales/el/messages.json index 7d021f7ab42f..ca3af0a616da 100644 --- a/app/_locales/el/messages.json +++ b/app/_locales/el/messages.json @@ -356,9 +356,6 @@ "editContact": { "message": "Επεξεργασία Επαφής" }, - "emailUs": { - "message": "Στείλτε μας email!" - }, "endOfFlowMessage1": { "message": "Περάσατε τη δοκιμή - κρατήστε τη φράση φύτρου σας ασφαλή, είναι δική σας ευθύνη!" }, diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index caa1e1fa2efa..797c8e709f52 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -409,6 +409,9 @@ "contactsSettingsDescription": { "message": "Add, edit, remove, and manage your contacts" }, + "contactUs": { + "message": "Contact us!" + }, "continueToWyre": { "message": "Continue to Wyre" }, @@ -535,9 +538,6 @@ "editPermission": { "message": "Edit Permission" }, - "emailUs": { - "message": "Email us!" - }, "endOfFlowMessage1": { "message": "You passed the test - keep your seedphrase safe, it's your responsibility!" }, diff --git a/app/_locales/es/messages.json b/app/_locales/es/messages.json index d513a2f19e79..97576dba7323 100644 --- a/app/_locales/es/messages.json +++ b/app/_locales/es/messages.json @@ -313,9 +313,6 @@ "editContact": { "message": "Editar Contacto" }, - "emailUs": { - "message": "¡Envíanos un correo!" - }, "endOfFlowMessage8": { "message": "MetaMask no puede recuperar tu seedphrase. Saber más." }, diff --git a/app/_locales/es_419/messages.json b/app/_locales/es_419/messages.json index 8a83b65e8d30..a856b7e8826b 100644 --- a/app/_locales/es_419/messages.json +++ b/app/_locales/es_419/messages.json @@ -356,9 +356,6 @@ "editContact": { "message": "Editar contacto" }, - "emailUs": { - "message": "¡Envíanos un correo electrónico!" - }, "endOfFlowMessage1": { "message": "Pasaste la prueba. Es responsabilidad tuya mantener la frase de inicialización segura." }, diff --git a/app/_locales/et/messages.json b/app/_locales/et/messages.json index 5f1ca3132503..28c7cb3b7889 100644 --- a/app/_locales/et/messages.json +++ b/app/_locales/et/messages.json @@ -359,9 +359,6 @@ "editContact": { "message": "Muuda kontakti" }, - "emailUs": { - "message": "Saatke meile e-kiri!" - }, "endOfFlowMessage1": { "message": "Läbisite kontrolli – hoidke seemnefraasi turvaliselt, see on teie vastutusel!" }, diff --git a/app/_locales/fa/messages.json b/app/_locales/fa/messages.json index d59bb3f32f3b..30bb2e2a46e3 100644 --- a/app/_locales/fa/messages.json +++ b/app/_locales/fa/messages.json @@ -359,9 +359,6 @@ "editContact": { "message": "ویرایش تماس" }, - "emailUs": { - "message": "به ما ایمیل کنید!" - }, "endOfFlowMessage1": { "message": "شما در امتحان موفق شدید - عبارت بازیابی را مصؤن نگهدارید، این مسؤلیت شماست!" }, diff --git a/app/_locales/fi/messages.json b/app/_locales/fi/messages.json index a67ef40ea8fe..62ac9fd9e88a 100644 --- a/app/_locales/fi/messages.json +++ b/app/_locales/fi/messages.json @@ -356,9 +356,6 @@ "editContact": { "message": "Muokkaa yhteystietoa" }, - "emailUs": { - "message": "Lähetä meille sähköpostia!" - }, "endOfFlowMessage1": { "message": "Läpäisit kokeen – pidä salaustekstisi turvassa. Se on sinun vastuullasi!" }, diff --git a/app/_locales/fil/messages.json b/app/_locales/fil/messages.json index d925e9f63bf2..60ff2e03fa18 100644 --- a/app/_locales/fil/messages.json +++ b/app/_locales/fil/messages.json @@ -332,9 +332,6 @@ "editContact": { "message": "I-edit ang Contact" }, - "emailUs": { - "message": "Mag-email sa amin!" - }, "endOfFlowMessage1": { "message": "Pumasa ka sa test - panatilihing ligtas ang iyong seedphrase, responsibilidad mo ito!" }, diff --git a/app/_locales/fr/messages.json b/app/_locales/fr/messages.json index 7d28b3a0a2cb..3b5e665cdaeb 100644 --- a/app/_locales/fr/messages.json +++ b/app/_locales/fr/messages.json @@ -347,9 +347,6 @@ "editContact": { "message": "Modifier le contact" }, - "emailUs": { - "message": "Envoyez-nous un email !" - }, "endOfFlowMessage1": { "message": "Vous avez réussi l'essai : gardez votre phrase de départ en sécurité, c'est de votre responsabilité !" }, diff --git a/app/_locales/he/messages.json b/app/_locales/he/messages.json index 7fdd31a89099..02c7a496c47a 100644 --- a/app/_locales/he/messages.json +++ b/app/_locales/he/messages.json @@ -359,9 +359,6 @@ "editContact": { "message": "ערוך איש קשר" }, - "emailUs": { - "message": "שלח/י לנו מייל!" - }, "endOfFlowMessage1": { "message": "עברת את הבחינה - יש לשמור את ה-seedphrase שלך במקום בטוח, זה באחריותך!" }, diff --git a/app/_locales/hi/messages.json b/app/_locales/hi/messages.json index 1686b79df716..aad074d700fd 100644 --- a/app/_locales/hi/messages.json +++ b/app/_locales/hi/messages.json @@ -359,9 +359,6 @@ "editContact": { "message": "संपर्क संपादित करें" }, - "emailUs": { - "message": "हमें ईमेल करें!" - }, "endOfFlowMessage1": { "message": "आपने परीक्षा उत्तीर्ण कर ली - अपने बीज वाक्यांश को सुरक्षित रखें, यह आपकी जिम्मेदारी है!" }, diff --git a/app/_locales/hn/messages.json b/app/_locales/hn/messages.json index 81d190e0db7c..bfc32cd4e308 100644 --- a/app/_locales/hn/messages.json +++ b/app/_locales/hn/messages.json @@ -124,9 +124,6 @@ "edit": { "message": "संपादित करें" }, - "emailUs": { - "message": "हमें ईमेल करें!" - }, "enterPassword": { "message": "पासवर्ड दर्ज करें" }, diff --git a/app/_locales/hr/messages.json b/app/_locales/hr/messages.json index bc4d39ff402f..a818b79a6cfe 100644 --- a/app/_locales/hr/messages.json +++ b/app/_locales/hr/messages.json @@ -359,9 +359,6 @@ "editContact": { "message": "Uredi kontakt" }, - "emailUs": { - "message": "Pošaljite nam elektroničku poruku!" - }, "endOfFlowMessage1": { "message": "Prošli ste test – čuvajte svoju početnu rečenicu jer ste vi odgovorni za nju!" }, diff --git a/app/_locales/ht/messages.json b/app/_locales/ht/messages.json index c60e60c5a0c2..6881d8a1848c 100644 --- a/app/_locales/ht/messages.json +++ b/app/_locales/ht/messages.json @@ -205,9 +205,6 @@ "edit": { "message": "Korije" }, - "emailUs": { - "message": "Imèl nou!" - }, "enterPassword": { "message": "Mete modpas" }, diff --git a/app/_locales/hu/messages.json b/app/_locales/hu/messages.json index 84be04277e35..73d19f975ec1 100644 --- a/app/_locales/hu/messages.json +++ b/app/_locales/hu/messages.json @@ -359,9 +359,6 @@ "editContact": { "message": "Kapcsolatok szerkesztése" }, - "emailUs": { - "message": "Írjon nekünk!" - }, "endOfFlowMessage1": { "message": "Átment a teszten - tartsa biztonságban a seed mondatot, ez az ön felelőssége!" }, diff --git a/app/_locales/id/messages.json b/app/_locales/id/messages.json index 7d942d231d2e..267c5c079a6d 100644 --- a/app/_locales/id/messages.json +++ b/app/_locales/id/messages.json @@ -350,9 +350,6 @@ "editContact": { "message": "Sunting Kontak" }, - "emailUs": { - "message": "Kirimkan kami email!" - }, "endOfFlowMessage1": { "message": "Anda lulus tes - jagalah agar frasa benih Anda aman, itu tanggung jawab Anda!" }, diff --git a/app/_locales/it/messages.json b/app/_locales/it/messages.json index a1358dc741c7..62683c407bbd 100644 --- a/app/_locales/it/messages.json +++ b/app/_locales/it/messages.json @@ -531,9 +531,6 @@ "editPermission": { "message": "Modifica Permessi" }, - "emailUs": { - "message": "Mandaci una mail!" - }, "endOfFlowMessage1": { "message": "Hai passato il test - tieni la tua frase seed al sicuro, è tua responsabilità!" }, diff --git a/app/_locales/kn/messages.json b/app/_locales/kn/messages.json index 7b8f6253f020..207dccfb7415 100644 --- a/app/_locales/kn/messages.json +++ b/app/_locales/kn/messages.json @@ -359,9 +359,6 @@ "editContact": { "message": "ಸಂಪರ್ಕವನ್ನು ಸಂಪಾದಿಸಿ" }, - "emailUs": { - "message": "ನಮಗೆ ಇಮೇಲ್ ಮಾಡಿ!" - }, "endOfFlowMessage1": { "message": "ನೀವು ಪರೀಕ್ಷೆಯನ್ನು ಪಾಸ್ ಮಾಡಿರುವಿರಿ - ನಿಮ್ಮ ಸೀಡ್‌ಫ್ರೇಸ್ ಸುರಕ್ಷಿತವಾಗಿರಿಸಿ, ಅದು ನಿಮ್ಮ ಜವಾಬ್ದಾರಿಯಾಗಿದೆ!" }, diff --git a/app/_locales/ko/messages.json b/app/_locales/ko/messages.json index 18f302c7d9c1..8b73492f8516 100644 --- a/app/_locales/ko/messages.json +++ b/app/_locales/ko/messages.json @@ -356,9 +356,6 @@ "editContact": { "message": "연락처 수정" }, - "emailUs": { - "message": "저자에게 메일 보내기!" - }, "endOfFlowMessage1": { "message": "인증을 통과했습니다 - 시드 구문을 안전하게 보관하세요. 그것은 당신의 의무입니다." }, diff --git a/app/_locales/lt/messages.json b/app/_locales/lt/messages.json index 30fa9dd23813..00d1cdb7e332 100644 --- a/app/_locales/lt/messages.json +++ b/app/_locales/lt/messages.json @@ -359,9 +359,6 @@ "editContact": { "message": "Taisyti kontaktą" }, - "emailUs": { - "message": "Rašykite mums el. paštu!" - }, "endOfFlowMessage1": { "message": "Jūs perėjote testą – laikykite saugiai savo atkūrimo frazę, tai jūsų atsakomybė!" }, diff --git a/app/_locales/lv/messages.json b/app/_locales/lv/messages.json index f06839e6c9e1..7d2906c49582 100644 --- a/app/_locales/lv/messages.json +++ b/app/_locales/lv/messages.json @@ -359,9 +359,6 @@ "editContact": { "message": "Rediģēt līgumu" }, - "emailUs": { - "message": "Raksiet mums e-pastu!" - }, "endOfFlowMessage1": { "message": "Jūs izgājāt pārbaudi — glabājiet atkopšanas frāzi drošībā, tā ir jūsu atbildība!" }, diff --git a/app/_locales/ms/messages.json b/app/_locales/ms/messages.json index 1cc5cd6bb3b1..c4d2b538e792 100644 --- a/app/_locales/ms/messages.json +++ b/app/_locales/ms/messages.json @@ -347,9 +347,6 @@ "editContact": { "message": "Edit Kenalan" }, - "emailUs": { - "message": "Hantarkan e-mel kepada kami!" - }, "endOfFlowMessage1": { "message": "Anda telah lulus ujian - simpan frasa benih anda di tempat yang selamat, itu tanggungjawab anda!" }, diff --git a/app/_locales/nl/messages.json b/app/_locales/nl/messages.json index 7d62e859a92b..ff4695c93ef2 100644 --- a/app/_locales/nl/messages.json +++ b/app/_locales/nl/messages.json @@ -118,9 +118,6 @@ "edit": { "message": "Bewerk" }, - "emailUs": { - "message": "Email ons!" - }, "enterPassword": { "message": "Voer wachtwoord in" }, diff --git a/app/_locales/no/messages.json b/app/_locales/no/messages.json index f4352505d4b0..54d2319b9c51 100644 --- a/app/_locales/no/messages.json +++ b/app/_locales/no/messages.json @@ -356,9 +356,6 @@ "editContact": { "message": "Rediger kontakt" }, - "emailUs": { - "message": "Send oss en e-post!" - }, "endOfFlowMessage1": { "message": "Du besto prøven - oppbevar den mnemoniske gjenopprettingsfrasen din på et sikkert sted, det er ditt ansvar! " }, diff --git a/app/_locales/pl/messages.json b/app/_locales/pl/messages.json index a3d06bba8681..b057d1642ee4 100644 --- a/app/_locales/pl/messages.json +++ b/app/_locales/pl/messages.json @@ -356,9 +356,6 @@ "editContact": { "message": "Edytuj kontakt" }, - "emailUs": { - "message": "Napisz do nas!" - }, "endOfFlowMessage1": { "message": "Zaliczyłeś test – zabezpiecz swoją frazę seed, to Twoja odpowiedzialność!" }, diff --git a/app/_locales/pt/messages.json b/app/_locales/pt/messages.json index cda66419d848..c3e710345cdf 100644 --- a/app/_locales/pt/messages.json +++ b/app/_locales/pt/messages.json @@ -124,9 +124,6 @@ "edit": { "message": "Editar" }, - "emailUs": { - "message": "Fale connosco!" - }, "enterPassword": { "message": "Introduza palavra-passe" }, diff --git a/app/_locales/pt_BR/messages.json b/app/_locales/pt_BR/messages.json index 4a5b2efbe6b8..2eda26024885 100644 --- a/app/_locales/pt_BR/messages.json +++ b/app/_locales/pt_BR/messages.json @@ -353,9 +353,6 @@ "editContact": { "message": "Editar Contato" }, - "emailUs": { - "message": "Escreva para nós!" - }, "endOfFlowMessage1": { "message": "Você passou no teste — mantenha sua frase semente segura, a responsabilidade é sua!" }, diff --git a/app/_locales/ro/messages.json b/app/_locales/ro/messages.json index 40cc1f2d5c3e..96e94f9f4685 100644 --- a/app/_locales/ro/messages.json +++ b/app/_locales/ro/messages.json @@ -359,9 +359,6 @@ "editContact": { "message": "Editați contact" }, - "emailUs": { - "message": "Trimiteți-ne un email!" - }, "endOfFlowMessage1": { "message": "Ați trecut testul – păstrați expresia sursă în siguranță, este răspunderea dvs.!" }, diff --git a/app/_locales/ru/messages.json b/app/_locales/ru/messages.json index 33d2e217ec23..24ea1aa245f1 100644 --- a/app/_locales/ru/messages.json +++ b/app/_locales/ru/messages.json @@ -154,9 +154,6 @@ "edit": { "message": "Редактировать" }, - "emailUs": { - "message": "Свяжитесь с нами по электронной почте!" - }, "enterPassword": { "message": "Введите пароль" }, diff --git a/app/_locales/sk/messages.json b/app/_locales/sk/messages.json index 940c816ec9fe..285e4375d3df 100644 --- a/app/_locales/sk/messages.json +++ b/app/_locales/sk/messages.json @@ -350,9 +350,6 @@ "editContact": { "message": "Upraviť kontakt" }, - "emailUs": { - "message": "Napište nám e-mail!" - }, "endOfFlowMessage1": { "message": "Úspešne ste prešli testom – uchovávajte svoju seed frázu v bezpečí. Je to vaša zodpovednosť!" }, diff --git a/app/_locales/sl/messages.json b/app/_locales/sl/messages.json index 84fda7bb1f92..c5659aaa6c6b 100644 --- a/app/_locales/sl/messages.json +++ b/app/_locales/sl/messages.json @@ -359,9 +359,6 @@ "editContact": { "message": "Uredi stik" }, - "emailUs": { - "message": "Pišite nam!" - }, "endOfFlowMessage1": { "message": "Opravili ste test - vaše geslo seed phrase je vaša odgovornost - skrbno pazite nanj!" }, diff --git a/app/_locales/sr/messages.json b/app/_locales/sr/messages.json index fcbcb4cf9999..468b55d6cc8b 100644 --- a/app/_locales/sr/messages.json +++ b/app/_locales/sr/messages.json @@ -356,9 +356,6 @@ "editContact": { "message": "Izmeni kontakt" }, - "emailUs": { - "message": "Pošaljite nam e-poštu!" - }, "endOfFlowMessage1": { "message": "Prošli ste test - čuvajte svoju frazu početnih vrednosti, to je vaša odgovornost!" }, diff --git a/app/_locales/sv/messages.json b/app/_locales/sv/messages.json index 944b710f1b3b..3cc07877f5fb 100644 --- a/app/_locales/sv/messages.json +++ b/app/_locales/sv/messages.json @@ -353,9 +353,6 @@ "editContact": { "message": "Redigera kontakt" }, - "emailUs": { - "message": "Mejla oss!" - }, "endOfFlowMessage1": { "message": "Du klarade testet. Håll din seed phrase hemlig, den är på ditt ansvar!" }, diff --git a/app/_locales/sw/messages.json b/app/_locales/sw/messages.json index 77e7268b5b17..aa0c03cd9b9b 100644 --- a/app/_locales/sw/messages.json +++ b/app/_locales/sw/messages.json @@ -353,9 +353,6 @@ "editContact": { "message": "Hariri Mawasiliano" }, - "emailUs": { - "message": "Tutumie barua pepe!" - }, "endOfFlowMessage1": { "message": "Umefaulu jaribio - weka kirai chako cha kuanzia mahali salama, ni wajibu wako!" }, diff --git a/app/_locales/ta/messages.json b/app/_locales/ta/messages.json index dfb39b6a3f84..fd849f557b9b 100644 --- a/app/_locales/ta/messages.json +++ b/app/_locales/ta/messages.json @@ -139,9 +139,6 @@ "edit": { "message": "திருத்து" }, - "emailUs": { - "message": "எங்களுக்கு மின்னஞ்சல்!" - }, "enterPassword": { "message": "கடவுச்சொல்லை உள்ளிடவும்" }, diff --git a/app/_locales/th/messages.json b/app/_locales/th/messages.json index 44bf097f0696..eacf2f961e50 100644 --- a/app/_locales/th/messages.json +++ b/app/_locales/th/messages.json @@ -178,9 +178,6 @@ "editContact": { "message": "แก้ไขผู้ติดต่อ" }, - "emailUs": { - "message": "อีเมลหาเรา!" - }, "endOfFlowMessage1": { "message": "คุณผ่านการทดสอบแล้ว กรุณารับผิดชอบในการรักษา Seed Phrase ให้ปลอดภัย" }, diff --git a/app/_locales/tr/messages.json b/app/_locales/tr/messages.json index c47144713ce9..df904c9eba71 100644 --- a/app/_locales/tr/messages.json +++ b/app/_locales/tr/messages.json @@ -151,9 +151,6 @@ "edit": { "message": "Düzenle" }, - "emailUs": { - "message": "Bize e-posta atın!" - }, "enterPassword": { "message": "Parolanızı girin" }, diff --git a/app/_locales/uk/messages.json b/app/_locales/uk/messages.json index c9d389c7857b..14ff03db6353 100644 --- a/app/_locales/uk/messages.json +++ b/app/_locales/uk/messages.json @@ -359,9 +359,6 @@ "editContact": { "message": "Редагувати контракт" }, - "emailUs": { - "message": "Напишіть нам!" - }, "endOfFlowMessage1": { "message": "Ви пройшли тест, зберігайте вашу початкову фразу в безпеці - це ваша відповідальність!" }, diff --git a/app/_locales/zh_CN/messages.json b/app/_locales/zh_CN/messages.json index 05566ca8eb25..bd13f3ac1a03 100644 --- a/app/_locales/zh_CN/messages.json +++ b/app/_locales/zh_CN/messages.json @@ -359,9 +359,6 @@ "editContact": { "message": "编辑联系人" }, - "emailUs": { - "message": "联系我们" - }, "endOfFlowMessage1": { "message": "您已通过测试 - 请妥善保管你的种子密语。这是您的责任!" }, diff --git a/app/_locales/zh_TW/messages.json b/app/_locales/zh_TW/messages.json index 532799252175..3e2e5d8345ab 100644 --- a/app/_locales/zh_TW/messages.json +++ b/app/_locales/zh_TW/messages.json @@ -356,9 +356,6 @@ "editContact": { "message": "編輯聯絡資訊" }, - "emailUs": { - "message": "寄 Email 給我們!" - }, "endOfFlowMessage1": { "message": "你通過測試了—安全存放助記詞,這是你的責任!" }, diff --git a/ui/app/pages/settings/info-tab/info-tab.component.js b/ui/app/pages/settings/info-tab/info-tab.component.js index c229f56b7238..9d522a3a1931 100644 --- a/ui/app/pages/settings/info-tab/info-tab.component.js +++ b/ui/app/pages/settings/info-tab/info-tab.component.js @@ -76,12 +76,12 @@ export default class InfoTab extends PureComponent {

From d990de4a0c51cde9ac86509d8f2a83037d73e4b1 Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Wed, 29 Jul 2020 19:39:47 -0230 Subject: [PATCH 023/107] Update dependencies (#9105) This change updates the following two dependencies to address high severity advisories in the production dependencies: * Use elliptic@6.5.3 * Use dot-prop@5.2.0 The public advisories: - `elliptic`: [npm](https://www.npmjs.com/advisories/1547) - `dot-prop`: [npm](https://www.npmjs.com/advisories/1213), [GHSA-ff7x-qrg7-qggm](https://github.com/advisories/GHSA-ff7x-qrg7-qggm) I don't believe there to be any functional changes here: - I don't think we hit any (important?) codepaths of the whole `ipld-zcash/zcash-bitcore-lib/elliptic` subtree of 3Box - `dot-prop` doesn't have a changelog but; - Looking through [`v3.0.0...v4.0.0`](https://github.com/sindresorhus/dot-prop/compare/v3.0.0...v4.0.0) it would seem that the breaking change was requiring Node.js 4 ([`88b6eb6`](https://github.com/sindresorhus/dot-prop/commit/88b6eb66cf4e119175a5ee7bf7faaec3ceb9497e)) - The only breaking change listed for [v5.0.0](https://github.com/sindresorhus/dot-prop/releases/tag/v5.0.0) was requiring Node.js 8. --- package.json | 3 +++ yarn.lock | 63 +++++++++++++--------------------------------------- 2 files changed, 18 insertions(+), 48 deletions(-) diff --git a/package.json b/package.json index 78e66b49b6f1..ae5ba7ec112a 100644 --- a/package.json +++ b/package.json @@ -52,10 +52,13 @@ "generate:migration": "./development/generate-migration.sh" }, "resolutions": { + "**/configstore/dot-prop": "^5.1.1", + "**/ethers/elliptic": "^6.5.3", "**/knex/minimist": "^1.2.5", "**/optimist/minimist": "^1.2.5", "**/socketcluster/minimist": "^1.2.5", "3box/ipfs/ipld-zcash/zcash-bitcore-lib/lodash": "^4.17.19", + "3box/ipfs/ipld-zcash/zcash-bitcore-lib/elliptic": "^6.5.3", "ganache-core/lodash": "^4.17.19" }, "dependencies": { diff --git a/yarn.lock b/yarn.lock index f1ffc3d0c78a..b34e2261ca65 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5449,11 +5449,6 @@ bn.js@^1.0.0: resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-1.3.0.tgz#0db4cbf96f8f23b742f5bcb9d1aa7a9994a05e83" integrity sha1-DbTL+W+PI7dC9by50ap6mZSgXoM= -bn.js@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-2.2.0.tgz#12162bc2ae71fc40a5626c33438f3a875cd37625" - integrity sha1-EhYrwq5x/EClYmwzQ486h1zTdiU= - body-parser@1.19.0, body-parser@^1.15.0, body-parser@^1.16.0: version "1.19.0" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" @@ -8880,12 +8875,12 @@ domutils@^1.7.0: dom-serializer "0" domelementtype "1" -dot-prop@^4.1.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-4.2.0.tgz#1f19e0c2e1aa0e32797c49799f2837ac6af69c57" - integrity sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ== +dot-prop@^4.1.0, dot-prop@^5.1.1: + version "5.2.0" + resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.2.0.tgz#c34ecc29556dc45f1f4c22697b6f4904e0cc4fcb" + integrity sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A== dependencies: - is-obj "^1.0.0" + is-obj "^2.0.0" dotenv-defaults@^1.0.2: version "1.0.2" @@ -9081,43 +9076,10 @@ element-resize-detector@^1.2.1: dependencies: batch-processor "1.0.0" -elliptic@6.3.3: - version "6.3.3" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.3.3.tgz#5482d9646d54bcb89fd7d994fc9e2e9568876e3f" - integrity sha1-VILZZG1UvLif19mU/J4ulWiHbj8= - dependencies: - bn.js "^4.4.0" - brorand "^1.0.1" - hash.js "^1.0.0" - inherits "^2.0.1" - -elliptic@=3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-3.0.3.tgz#865c9b420bfbe55006b9f969f97a0d2c44966595" - integrity sha1-hlybQgv75VAGuflp+XoNLESWZZU= - dependencies: - bn.js "^2.0.0" - brorand "^1.0.1" - hash.js "^1.0.0" - inherits "^2.0.1" - -elliptic@^6.0.0, elliptic@^6.4.0: - version "6.4.0" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.4.0.tgz#cac9af8762c85836187003c8dfe193e5e2eae5df" - integrity sha1-ysmvh2LIWDYYcAPI3+GT5eLq5d8= - dependencies: - bn.js "^4.4.0" - brorand "^1.0.1" - hash.js "^1.0.0" - hmac-drbg "^1.0.0" - inherits "^2.0.1" - minimalistic-assert "^1.0.0" - minimalistic-crypto-utils "^1.0.0" - -elliptic@^6.4.1: - version "6.5.0" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.0.tgz#2b8ed4c891b7de3200e14412a5b8248c7af505ca" - integrity sha512-eFOJTMyCYb7xtE/caJ6JJu+bhi67WCYNbkGSknu20pmM8Ke/bqOfdnZWxyoGN26JgfxTbXrsCkEw4KheCT/KGg== +elliptic@6.3.3, elliptic@=3.0.3, elliptic@^6.0.0, elliptic@^6.4.0, elliptic@^6.4.1, elliptic@^6.5.3: + version "6.5.3" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.3.tgz#cb59eb2efdaf73a0bd78ccd7015a62ad6e0f93d6" + integrity sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw== dependencies: bn.js "^4.4.0" brorand "^1.0.1" @@ -15059,11 +15021,16 @@ is-number@^7.0.0: resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== -is-obj@^1.0.0, is-obj@^1.0.1: +is-obj@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8= +is-obj@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" + integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== + is-object@^1.0.1, is-object@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.1.tgz#8952688c5ec2ffd6b03ecc85e769e02903083470" From e0cc84bbfa029aa7ee28a5ea77d00c9be4ae4dcd Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Wed, 29 Jul 2020 19:50:20 -0230 Subject: [PATCH 024/107] Use async/await for getRestrictedMethods (#9099) --- .../permissions/restrictedMethods.js | 48 +++++++++---------- 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/app/scripts/controllers/permissions/restrictedMethods.js b/app/scripts/controllers/permissions/restrictedMethods.js index 074cbb5a546c..0e1ae4b9ab39 100644 --- a/app/scripts/controllers/permissions/restrictedMethods.js +++ b/app/scripts/controllers/permissions/restrictedMethods.js @@ -1,34 +1,30 @@ export default function getRestrictedMethods ({ getIdentities, getKeyringAccounts }) { return { 'eth_accounts': { - method: (_, res, __, end) => { - getKeyringAccounts() - .then((accounts) => { - const identities = getIdentities() - res.result = accounts - .sort((firstAddress, secondAddress) => { - if (!identities[firstAddress]) { - throw new Error(`Missing identity for address ${firstAddress}`) - } else if (!identities[secondAddress]) { - throw new Error(`Missing identity for address ${secondAddress}`) - } else if (identities[firstAddress].lastSelected === identities[secondAddress].lastSelected) { - return 0 - } else if (identities[firstAddress].lastSelected === undefined) { - return 1 - } else if (identities[secondAddress].lastSelected === undefined) { - return -1 - } + method: async (_, res, __, end) => { + try { + const accounts = await getKeyringAccounts() + const identities = getIdentities() + res.result = accounts.sort((firstAddress, secondAddress) => { + if (!identities[firstAddress]) { + throw new Error(`Missing identity for address ${firstAddress}`) + } else if (!identities[secondAddress]) { + throw new Error(`Missing identity for address ${secondAddress}`) + } else if (identities[firstAddress].lastSelected === identities[secondAddress].lastSelected) { + return 0 + } else if (identities[firstAddress].lastSelected === undefined) { + return 1 + } else if (identities[secondAddress].lastSelected === undefined) { + return -1 + } - return identities[secondAddress].lastSelected - identities[firstAddress].lastSelected - }) - end() + return identities[secondAddress].lastSelected - identities[firstAddress].lastSelected }) - .catch( - (err) => { - res.error = err - end(err) - }, - ) + end() + } catch (err) { + res.error = err + end(err) + } }, }, } From 002f021fc0b15d5b3a20858d9c00af4860897181 Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Wed, 29 Jul 2020 19:50:38 -0230 Subject: [PATCH 025/107] Use async/await for seedPhraseVerifier.verifyAccounts (#9100) --- app/scripts/lib/seed-phrase-verifier.js | 60 +++++++++++-------------- 1 file changed, 26 insertions(+), 34 deletions(-) diff --git a/app/scripts/lib/seed-phrase-verifier.js b/app/scripts/lib/seed-phrase-verifier.js index 6a2925c71e5b..f07e63a2c731 100644 --- a/app/scripts/lib/seed-phrase-verifier.js +++ b/app/scripts/lib/seed-phrase-verifier.js @@ -16,41 +16,33 @@ const seedPhraseVerifier = { * @returns {Promise} - Promises undefined * */ - verifyAccounts (createdAccounts, seedWords) { - - return new Promise((resolve, reject) => { - - if (!createdAccounts || createdAccounts.length < 1) { - return reject(new Error('No created accounts defined.')) - } - - const keyringController = new KeyringController({}) - const Keyring = keyringController.getKeyringClassForType('HD Key Tree') - const opts = { - mnemonic: seedWords, - numberOfAccounts: createdAccounts.length, + async verifyAccounts (createdAccounts, seedWords) { + if (!createdAccounts || createdAccounts.length < 1) { + throw new Error('No created accounts defined.') + } + + const keyringController = new KeyringController({}) + const Keyring = keyringController.getKeyringClassForType('HD Key Tree') + const opts = { + mnemonic: seedWords, + numberOfAccounts: createdAccounts.length, + } + + const keyring = new Keyring(opts) + const restoredAccounts = await keyring.getAccounts() + log.debug('Created accounts: ' + JSON.stringify(createdAccounts)) + log.debug('Restored accounts: ' + JSON.stringify(restoredAccounts)) + + if (restoredAccounts.length !== createdAccounts.length) { + // this should not happen... + throw new Error('Wrong number of accounts') + } + + for (let i = 0; i < restoredAccounts.length; i++) { + if (restoredAccounts[i].toLowerCase() !== createdAccounts[i].toLowerCase()) { + throw new Error('Not identical accounts! Original: ' + createdAccounts[i] + ', Restored: ' + restoredAccounts[i]) } - - const keyring = new Keyring(opts) - keyring.getAccounts() - .then((restoredAccounts) => { - - log.debug('Created accounts: ' + JSON.stringify(createdAccounts)) - log.debug('Restored accounts: ' + JSON.stringify(restoredAccounts)) - - if (restoredAccounts.length !== createdAccounts.length) { - // this should not happen... - return reject(new Error('Wrong number of accounts')) - } - - for (let i = 0; i < restoredAccounts.length; i++) { - if (restoredAccounts[i].toLowerCase() !== createdAccounts[i].toLowerCase()) { - return reject(new Error('Not identical accounts! Original: ' + createdAccounts[i] + ', Restored: ' + restoredAccounts[i])) - } - } - return resolve() - }) - }) + } }, } From d832b98bae620c7f3e67265140c54e5d008e8998 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Thu, 30 Jul 2020 13:55:56 -0300 Subject: [PATCH 026/107] Add `build-artifacts` to .gitignore (#9109) The `build-artifacts` directory is created on CI (as of #7151), and is used to store the sesify visualization and dependency logs. It's useful to have this ignored locally as well, for when those scripts are being tested. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 41575124bf49..6d11965cb7d2 100644 --- a/.gitignore +++ b/.gitignore @@ -34,6 +34,7 @@ builds.zip test-artifacts test-builds +build-artifacts #ignore css output and sourcemaps ui/app/css/output/ From 3f53db184629eea3f5bc26c197f3c0e623f4d947 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Thu, 30 Jul 2020 14:43:02 -0300 Subject: [PATCH 027/107] Update `source-map-explorer` from v2.0.1 to v2.4.2 (#9110) The output remains identical between these two versions, and none of the changelog entries appear relevant to us (aside from maybe some of the bug fixes). --- package.json | 2 +- yarn.lock | 130 +++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 95 insertions(+), 37 deletions(-) diff --git a/package.json b/package.json index ae5ba7ec112a..fa511502ac67 100644 --- a/package.json +++ b/package.json @@ -269,7 +269,7 @@ "sesify-viz": "^3.0.5", "sinon": "^9.0.0", "source-map": "^0.7.2", - "source-map-explorer": "^2.0.1", + "source-map-explorer": "^2.4.2", "string.prototype.matchall": "^4.0.2", "style-loader": "^0.21.0", "stylelint": "^13.6.1", diff --git a/yarn.lock b/yarn.lock index b34e2261ca65..c513bcb96e0e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4110,6 +4110,11 @@ async.util.setimmediate@0.5.2: resolved "https://registry.yarnpkg.com/async.util.setimmediate/-/async.util.setimmediate-0.5.2.tgz#2812ebabf2a58027758d4bc7793d1ccfaf10255f" integrity sha1-KBLrq/KlgCd1jUvHeT0cz68QJV8= +async@0.9.x: + version "0.9.2" + resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d" + integrity sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0= + async@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/async/-/async-2.0.0.tgz#d0900ad385af13804540a109c42166e3ae7b2b9d" @@ -7187,7 +7192,7 @@ convert-source-map@^0.3.3: resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-0.3.5.tgz#f1d802950af7dd2631a1febe0596550c86ab3190" integrity sha1-8dgClQr33SYxof6+BZZVDIarMZA= -convert-source-map@^1.5.0, convert-source-map@^1.5.1, convert-source-map@^1.6.0, convert-source-map@^1.7.0: +convert-source-map@^1.5.0, convert-source-map@^1.5.1, convert-source-map@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442" integrity sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA== @@ -9020,16 +9025,18 @@ ejs@^2.4.1: resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.6.1.tgz#498ec0d495655abc6f23cd61868d926464071aa0" integrity sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ== -ejs@^2.6.2: - version "2.7.1" - resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.7.1.tgz#5b5ab57f718b79d4aca9254457afecd36fa80228" - integrity sha512-kS/gEPzZs3Y1rRsbGX4UOSjtP/CeJP0CxSNZHYxGfVM/VgLcv0ZqM7C45YyTj2DI2g7+P9Dd24C+IMIg6D0nYQ== - ejs@^2.7.4: version "2.7.4" resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.7.4.tgz#48661287573dcc53e366c7a1ae52c3a120eec9ba" integrity sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA== +ejs@^3.0.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.3.tgz#514d967a8894084d18d3d47bd169a1c0560f093d" + integrity sha512-wmtrUGyfSC23GC/B1SMv2ogAUgbQEtDmTIhfqielrG5ExIM9TP4UoYdi90jLF1aTcsWCJNEO0UrgKzP0y3nTSg== + dependencies: + jake "^10.6.1" + electron-download@^4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/electron-download/-/electron-download-4.1.1.tgz#02e69556705cc456e520f9e035556ed5a015ebe8" @@ -11431,6 +11438,13 @@ file-uri-to-path@1, file-uri-to-path@1.0.0: resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== +filelist@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.1.tgz#f10d1a3ae86c1694808e8f20906f43d4c9132dbb" + integrity sha512-8zSK6Nu0DQIC08mUC46sWGXi+q3GGpKydAG36k+JDba6VRpkevvOWUW5a/PhShij4+vHT9M+ghgG7eM+a9JDUQ== + dependencies: + minimatch "^3.0.4" + filename-regex@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26" @@ -13006,6 +13020,14 @@ gzip-size@5.0.0: duplexer "^0.1.1" pify "^3.0.0" +gzip-size@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-5.1.1.tgz#cb9bee692f87c0612b232840a873904e4c135274" + integrity sha512-FNHi6mmoHvs1mxZAds4PpdCS6QG8B4C1krxJsMutgxl5t3+GlRTzzI3NEkifXx2pVsOvJdOGSmIgDhQ55FwdPA== + dependencies: + duplexer "^0.1.1" + pify "^4.0.1" + hamt-sharding@~0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/hamt-sharding/-/hamt-sharding-0.0.2.tgz#53691f72122f1929a92a4688c7bb59545a8998ac" @@ -14783,6 +14805,11 @@ is-directory@^0.3.1: resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1" integrity sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE= +is-docker@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.0.0.tgz#2cb0df0e75e2d064fe1864c37cdeacb7b2dcf25b" + integrity sha512-pJEdRugimx4fBMra5z2/5iRdZ63OhYV0vr0Dwm5+xtW4D1FvRkB8hamMIhnWfyJeDdyr/aa7BDyNbtG38VxgoQ== + is-dom@^1.0.9: version "1.0.9" resolved "https://registry.yarnpkg.com/is-dom/-/is-dom-1.0.9.tgz#483832d52972073de12b9fe3f60320870da8370d" @@ -15296,10 +15323,12 @@ is-wsl@^1.1.0: resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0= -is-wsl@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.1.1.tgz#4a1c152d429df3d441669498e2486d3596ebaf1d" - integrity sha512-umZHcSrwlDHo2TGMXv0DZ8dIUGunZ2Iv68YZnrmCiBPkZ4aaOhtv7pXJKeki9k3qJ3RJr0cDyitcl5wEH3AYog== +is-wsl@^2.1.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" + integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== + dependencies: + is-docker "^2.0.0" is-yarn-global@^0.3.0: version "0.3.0" @@ -15475,6 +15504,16 @@ iterall@^1.1.3, iterall@^1.2.1: resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.2.2.tgz#92d70deb8028e0c39ff3164fdbf4d8b088130cd7" integrity sha512-yynBb1g+RFUPY64fTrFv7nsjRrENBQJaX2UL+2Szc9REFrSNm1rpSXHGzhmAy7a9uv3vlvgBlXnf9RqmPH1/DA== +jake@^10.6.1: + version "10.8.2" + resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.2.tgz#ebc9de8558160a66d82d0eadc6a2e58fbc500a7b" + integrity sha512-eLpKyrfG3mzvGE2Du8VoPbeSkRry093+tyNjdYaBbJS9v17knImYGNXQCUV0gLxQtF82m3E8iRb/wdSQZLoq7A== + dependencies: + async "0.9.x" + chalk "^2.4.2" + filelist "^1.0.1" + minimatch "^3.0.4" + java-properties@^0.2.9: version "0.2.10" resolved "https://registry.yarnpkg.com/java-properties/-/java-properties-0.2.10.tgz#2551560c25fa1ad94d998218178f233ad9b18f60" @@ -19521,19 +19560,13 @@ only@~0.0.2: resolved "https://registry.yarnpkg.com/only/-/only-0.0.2.tgz#2afde84d03e50b9a8edc444e30610a70295edfb4" integrity sha1-Kv3oTQPlC5qO3EROMGEKcCle37Q= -open@^6.3.0: - version "6.4.0" - resolved "https://registry.yarnpkg.com/open/-/open-6.4.0.tgz#5c13e96d0dc894686164f18965ecfe889ecfc8a9" - integrity sha512-IFenVPgF70fSm1keSd2iDBIDIBZkroLeuffXq+wKTzTJlBpesFWojV9lb8mzOfaAzM1sr7HQHuO0vtV0zYekGg== - dependencies: - is-wsl "^1.1.0" - -open@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/open/-/open-7.0.0.tgz#7e52999b14eb73f90f0f0807fe93897c4ae73ec9" - integrity sha512-K6EKzYqnwQzk+/dzJAQSBORub3xlBTxMz+ntpZpH/LyCa1o6KjXhuN+2npAaI9jaSmU3R1Q8NWf4KUWcyytGsQ== +open@^7.0.0, open@^7.0.3: + version "7.1.0" + resolved "https://registry.yarnpkg.com/open/-/open-7.1.0.tgz#68865f7d3cb238520fa1225a63cf28bcf8368a1c" + integrity sha512-lLPI5KgOwEYCDKXf4np7y1PBEkj7HYIyP2DY8mVDRnx0VIIu6bNrRB0R66TuO7Mack6EnTNLm4uvcl1UoklTpA== dependencies: - is-wsl "^2.1.0" + is-docker "^2.0.0" + is-wsl "^2.1.1" opencollective-postinstall@^2.0.0: version "2.0.2" @@ -24380,22 +24413,23 @@ source-list-map@^2.0.0: resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.0.tgz#aaa47403f7b245a92fbc97ea08f250d6087ed085" integrity sha512-I2UmuJSRr/T8jisiROLU3A3ltr+swpniSmNPI4Ml3ZCX6tVnDsuZzK7F2hl5jTqbZBWCEKlj5HRQiPExXLgE8A== -source-map-explorer@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/source-map-explorer/-/source-map-explorer-2.0.1.tgz#988721e8320d7b6925bc40289e5de658fe4cad4d" - integrity sha512-mv2sv2b6oN2L9n18O/eLrYiP5zfWEHESLq4utWBqNw8GnkbuRuXs8twVCOhMT5hxRzfQgS7Yxh7HlQaW8oeiAQ== +source-map-explorer@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/source-map-explorer/-/source-map-explorer-2.4.2.tgz#fb23f86c3112eacde5683f24efaf4ddc9f677985" + integrity sha512-3ECQLffCFV8QgrTqcmddLkWL4/aQs6ljYfgWCLselo5QtizOfOeUCKnS4rFn7MIrdeZLM6TZrseOtsrWZhWKoQ== dependencies: btoa "^1.2.1" - chalk "^2.4.2" - convert-source-map "^1.6.0" - ejs "^2.6.2" + chalk "^3.0.0" + convert-source-map "^1.7.0" + ejs "^3.0.2" escape-html "^1.0.3" - glob "^7.1.4" - lodash "^4.17.11" - open "^6.3.0" + glob "^7.1.6" + gzip-size "^5.1.1" + lodash "^4.17.15" + open "^7.0.3" source-map "^0.7.3" - temp "^0.9.0" - yargs "^13.2.4" + temp "^0.9.1" + yargs "^15.3.1" source-map-resolve@^0.5.0, source-map-resolve@^0.5.1: version "0.5.1" @@ -25605,7 +25639,14 @@ temp-dir@^1.0.0: resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-1.0.0.tgz#0a7c0ea26d3a39afa7e0ebea9c1fc0bc4daa011d" integrity sha1-CnwOom06Oa+n4OvqnB/AvE2qAR0= -temp@^0.9.0, temp@~0.9.0: +temp@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/temp/-/temp-0.9.1.tgz#2d666114fafa26966cd4065996d7ceedd4dd4697" + integrity sha512-WMuOgiua1xb5R56lE0eH6ivpVmg/lq2OHm4+LtT/xtEtPQ+sz6N3bBM6WZ5FvO1lO4IKIOb43qnhoc4qxP5OeA== + dependencies: + rimraf "~2.6.2" + +temp@~0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/temp/-/temp-0.9.0.tgz#61391795a11bd9738d4c4d7f55f012cb8f55edaa" integrity sha512-YfUhPQCJoNQE5N+FJQcdPz63O3x3sdT4Xju69Gj4iZe0lBKOtnAMi0SLj9xKhGkcGhsxThvTJ/usxtFPo438zQ== @@ -28041,7 +28082,7 @@ yargs-parser@13.1.2, yargs-parser@^13.1.0, yargs-parser@^13.1.1, yargs-parser@^1 camelcase "^5.0.0" decamelize "^1.2.0" -yargs-parser@^18.1.1, yargs-parser@^18.1.3: +yargs-parser@^18.1.1, yargs-parser@^18.1.2, yargs-parser@^18.1.3: version "18.1.3" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== @@ -28163,6 +28204,23 @@ yargs@^15.0.0, yargs@^15.0.2: y18n "^4.0.0" yargs-parser "^18.1.1" +yargs@^15.3.1: + version "15.4.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" + integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A== + dependencies: + cliui "^6.0.0" + decamelize "^1.2.0" + find-up "^4.1.0" + get-caller-file "^2.0.1" + require-directory "^2.1.1" + require-main-filename "^2.0.0" + set-blocking "^2.0.0" + string-width "^4.2.0" + which-module "^2.0.0" + y18n "^4.0.0" + yargs-parser "^18.1.2" + yargs@^7.1.0: version "7.1.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-7.1.0.tgz#6ba318eb16961727f5d284f8ea003e8d6154d0c8" From ee291d48e9d9cf78d708d225456f65d58b8faffe Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Thu, 30 Jul 2020 14:55:26 -0300 Subject: [PATCH 028/107] Update `gulp-rename` from v1.4.0 to v2.0.0 (#9112) The changes between these versions don't affect us. The breaking change was related to passing in a function to `gulp-rename`, which we don't do. --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index fa511502ac67..d6977899a3b5 100644 --- a/package.json +++ b/package.json @@ -233,7 +233,7 @@ "gulp-imagemin": "^6.1.0", "gulp-livereload": "4.0.0", "gulp-multi-process": "^1.3.1", - "gulp-rename": "^1.4.0", + "gulp-rename": "^2.0.0", "gulp-replace": "^1.0.0", "gulp-rtlcss": "^1.4.0", "gulp-sass": "^4.0.0", diff --git a/yarn.lock b/yarn.lock index c513bcb96e0e..ab56ca74f60d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12888,10 +12888,10 @@ gulp-multi-process@^1.3.1: dependencies: async.queue "^0.5.2" -gulp-rename@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/gulp-rename/-/gulp-rename-1.4.0.tgz#de1c718e7c4095ae861f7296ef4f3248648240bd" - integrity sha512-swzbIGb/arEoFK89tPY58vg3Ok1bw+d35PfUNwWqdo7KM4jkmuGA78JiDNqR+JeZFaeeHnRg9N7aihX3YPmsyg== +gulp-rename@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/gulp-rename/-/gulp-rename-2.0.0.tgz#9bbc3962b0c0f52fc67cd5eaff6c223ec5b9cf6c" + integrity sha512-97Vba4KBzbYmR5VBs9mWmK+HwIf5mj+/zioxfZhOKeXtx5ZjBk57KFlePf5nxq9QsTtFl0ejnHE3zTC9MHXqyQ== gulp-replace@^1.0.0: version "1.0.0" From 081153a0df3285ea1d849d90c54ac660e67b2c06 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Thu, 30 Jul 2020 14:55:46 -0300 Subject: [PATCH 029/107] Update `sesify-viz` from v3.0.9 to v3.0.10 (#9111) The changes between v3.0.9 and v3.0.10 are minimial - just some minor improvements to error handling. --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index d6977899a3b5..5215472a778c 100644 --- a/package.json +++ b/package.json @@ -266,7 +266,7 @@ "selenium-webdriver": "^4.0.0-alpha.5", "serve-handler": "^6.1.2", "sesify": "^4.2.1", - "sesify-viz": "^3.0.5", + "sesify-viz": "^3.0.10", "sinon": "^9.0.0", "source-map": "^0.7.2", "source-map-explorer": "^2.4.2", diff --git a/yarn.lock b/yarn.lock index ab56ca74f60d..39aebf9e284f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -23886,10 +23886,10 @@ sesify-tofu@^2.0.4: dependencies: acorn-globals "^4.3.3" -sesify-viz@^3.0.5: - version "3.0.9" - resolved "https://registry.yarnpkg.com/sesify-viz/-/sesify-viz-3.0.9.tgz#2fefff0456c3fa4e407e8564d2d0ab8797e29065" - integrity sha512-6weweTW6XQJyeoWK2vdoov52fpN07/lHqTlFwVO/M3LJMNr//BPfgxGxXVxtd09RdSLyAk5NZ7B3m3WgY8vMkQ== +sesify-viz@^3.0.10: + version "3.0.10" + resolved "https://registry.yarnpkg.com/sesify-viz/-/sesify-viz-3.0.10.tgz#1936e9a62bf8155c57dfc42e5eeceaa28b1cb875" + integrity sha512-Gamqr/8qbZDJ7Yq/NGUk3Iq2woTK67x1mZ59Qrt5ssLZZPzeCBYFeqnNWu8SUO42GlP1D3eyiGLHA31xceySHg== dependencies: ncp "^2.0.0" pify "^4.0.1" From b19e048f581a71b0fde624c6845b08790182aa4d Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Thu, 30 Jul 2020 16:02:27 -0300 Subject: [PATCH 030/107] Update `browserify` from v16.2.3 to v16.5.1 (#9113) The changes between these two versions don't seen to affect us a great deal. The browserify dependency updates do result in changes to our production bundle, but the changes have no obvious functional impact. --- package.json | 2 +- yarn.lock | 53 +++++++++++++++++++++++++++++++++------------------- 2 files changed, 35 insertions(+), 20 deletions(-) diff --git a/package.json b/package.json index 5215472a778c..63a41bb394eb 100644 --- a/package.json +++ b/package.json @@ -196,7 +196,7 @@ "babel-loader": "^8.0.6", "babelify": "^10.0.0", "brfs": "^1.6.1", - "browserify": "^16.2.3", + "browserify": "^16.5.1", "browserify-derequire": "^1.0.1", "browserify-transform-tools": "^1.7.0", "chai": "^4.1.0", diff --git a/yarn.lock b/yarn.lock index 39aebf9e284f..3245b5190d7b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5734,17 +5734,17 @@ browserify-zlib@^0.2.0, browserify-zlib@~0.2.0: dependencies: pako "~1.0.5" -browserify@^16.1.0, browserify@^16.2.3: - version "16.2.3" - resolved "https://registry.yarnpkg.com/browserify/-/browserify-16.2.3.tgz#7ee6e654ba4f92bce6ab3599c3485b1cc7a0ad0b" - integrity sha512-zQt/Gd1+W+IY+h/xX2NYMW4orQWhqSwyV+xsblycTtpOuB27h1fZhhNQuipJ4t79ohw4P4mMem0jp/ZkISQtjQ== +browserify@^16.1.0, browserify@^16.5.1: + version "16.5.1" + resolved "https://registry.yarnpkg.com/browserify/-/browserify-16.5.1.tgz#3c13c97436802930d5c3ae28658ddc33bfd37dc2" + integrity sha512-EQX0h59Pp+0GtSRb5rL6OTfrttlzv+uyaUVlK6GX3w11SQ0jKPKyjC/54RhPR2ib2KmfcELM06e8FxcI5XNU2A== dependencies: JSONStream "^1.0.3" assert "^1.4.0" browser-pack "^6.0.1" browser-resolve "^1.11.0" browserify-zlib "~0.2.0" - buffer "^5.0.2" + buffer "~5.2.1" cached-path-relative "^1.0.0" concat-stream "^1.6.0" console-browserify "^1.1.0" @@ -5762,7 +5762,7 @@ browserify@^16.1.0, browserify@^16.2.3: inherits "~2.0.1" insert-module-globals "^7.0.0" labeled-stream-splicer "^2.0.0" - mkdirp "^0.5.0" + mkdirp-classic "^0.5.2" module-deps "^6.0.0" os-browserify "~0.3.0" parents "^1.0.1" @@ -5776,7 +5776,7 @@ browserify@^16.1.0, browserify@^16.2.3: shasum "^1.0.0" shell-quote "^1.6.1" stream-browserify "^2.0.0" - stream-http "^2.0.0" + stream-http "^3.0.0" string_decoder "^1.1.1" subarg "^1.0.0" syntax-error "^1.1.1" @@ -5964,7 +5964,7 @@ buffer@^4.3.0: ieee754 "^1.1.4" isarray "^1.0.0" -buffer@^5.0.2, buffer@^5.0.5: +buffer@^5.0.5, buffer@~5.2.1: version "5.2.1" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.2.1.tgz#dd57fa0f109ac59c602479044dca7b8b3d0b71d6" integrity sha512-c+Ko0loDaFfuPWiL02ls9Xd3GO3cPVmUobQ6t3rXNUk304u6hGq+8N/kFi+QEIKhzK3uwolVhLzszmfLmMLnqg== @@ -18249,6 +18249,11 @@ mixin-object@^2.0.1: for-in "^0.1.3" is-extendable "^0.1.1" +mkdirp-classic@^0.5.2: + version "0.5.3" + resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" + integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== + mkdirp-promise@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/mkdirp-promise/-/mkdirp-promise-5.0.1.tgz#e9b8f68e552c68a9c1713b84883f7a1dd039b8a1" @@ -22448,15 +22453,6 @@ readable-stream@1.1, readable-stream@1.1.x, readable-stream@^1.0.33, readable-st isarray "0.0.1" string_decoder "~0.10.x" -"readable-stream@2 || 3", readable-stream@^3.0.0, readable-stream@^3.0.1, readable-stream@^3.0.2, readable-stream@^3.0.5, readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.4.0.tgz#a51c26754658e0a3c21dbf59163bd45ba6f447fc" - integrity sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - readable-stream@^2.0.0, readable-stream@^2.0.5, readable-stream@^2.2.8, readable-stream@^2.2.9, readable-stream@^2.3.0, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.5, readable-stream@~2.3.6: version "2.3.6" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" @@ -22470,6 +22466,15 @@ readable-stream@^2.0.0, readable-stream@^2.0.5, readable-stream@^2.2.8, readable string_decoder "~1.1.1" util-deprecate "~1.0.1" +"readable-stream@2 || 3", readable-stream@^3.0.0, readable-stream@^3.0.1, readable-stream@^3.0.2, readable-stream@^3.0.5, readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" + integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + readable-stream@~1.0.15: version "1.0.34" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" @@ -24803,7 +24808,7 @@ stream-exhaust@^1.0.1: resolved "https://registry.yarnpkg.com/stream-exhaust/-/stream-exhaust-1.0.2.tgz#acdac8da59ef2bc1e17a2c0ccf6c320d120e555d" integrity sha512-b/qaq/GlBK5xaq1yrK9/zFcyRSTNxmcZwFLGSTG0mXgZl/4Z6GgiyYOXOvY7N3eEvFRAG1bkDRz5EPGSvPYQlw== -stream-http@^2.0.0, stream-http@^2.7.2: +stream-http@^2.7.2: version "2.7.2" resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.7.2.tgz#40a050ec8dc3b53b33d9909415c02c0bf1abfbad" integrity sha512-c0yTD2rbQzXtSsFSVhtpvY/vS6u066PcXOX9kBB3mSO76RiUQzL340uJkGBWnlBg4/HZzqiUXtaVA7wcRcJgEw== @@ -24814,6 +24819,16 @@ stream-http@^2.0.0, stream-http@^2.7.2: to-arraybuffer "^1.0.0" xtend "^4.0.0" +stream-http@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-3.1.1.tgz#0370a8017cf8d050b9a8554afe608f043eaff564" + integrity sha512-S7OqaYu0EkFpgeGFb/NPOoPLxFko7TPqtEeFg5DXPB4v/KETHG0Ln6fRFrNezoelpaDKmycEmmZ81cC9DAwgYg== + dependencies: + builtin-status-codes "^3.0.0" + inherits "^2.0.4" + readable-stream "^3.6.0" + xtend "^4.0.2" + stream-parser@~0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/stream-parser/-/stream-parser-0.3.1.tgz#1618548694420021a1182ff0af1911c129761773" @@ -28015,7 +28030,7 @@ xregexp@2.0.0: resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-2.0.0.tgz#52a63e56ca0b84a7f3a5f3d61872f126ad7a5943" integrity sha1-UqY+VsoLhKfzpfPWGHLxJq16WUM= -xtend@^4.0.0, xtend@~4.0.0: +xtend@^4.0.0, xtend@^4.0.2, xtend@~4.0.0: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== From 9e12cdc01bd42c31526bfe5b0dbf54d450786d7f Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Thu, 30 Jul 2020 16:19:42 -0300 Subject: [PATCH 031/107] Factor out `getEnvironment` function in build script (#9114) There are no functional changes. This was extracted from #8170 --- development/build/scripts.js | 37 +++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/development/build/scripts.js b/development/build/scripts.js index 04f21aa76585..6f206ee57705 100644 --- a/development/build/scripts.js +++ b/development/build/scripts.js @@ -316,23 +316,7 @@ function createScriptTasks ({ browserPlatforms, livereload }) { bundler = bundler.external(opts.externalDependencies) } - let environment - if (opts.devMode) { - environment = 'development' - } else if (opts.testing) { - environment = 'testing' - } else if (process.env.CIRCLE_BRANCH === 'master') { - environment = 'production' - } else if (/^Version-v(\d+)[.](\d+)[.](\d+)/.test(process.env.CIRCLE_BRANCH)) { - environment = 'release-candidate' - } else if (process.env.CIRCLE_BRANCH === 'develop') { - environment = 'staging' - } else if (process.env.CIRCLE_PULL_REQUEST) { - environment = 'pull-request' - } else { - environment = 'other' - } - + const environment = getEnvironment({ devMode: opts.devMode, test: opts.testing }) if (environment === 'production' && !process.env.SENTRY_DSN) { throw new Error('Missing SENTRY_DSN environment variable') } @@ -372,3 +356,22 @@ function createScriptTasks ({ browserPlatforms, livereload }) { function beep () { process.stdout.write('\x07') } + +function getEnvironment ({ devMode, test }) { + // get environment slug + if (devMode) { + return 'development' + } else if (test) { + return 'testing' + } else if (process.env.CIRCLE_BRANCH === 'master') { + return 'production' + } else if (/^Version-v(\d+)[.](\d+)[.](\d+)/.test(process.env.CIRCLE_BRANCH)) { + return 'release-candidate' + } else if (process.env.CIRCLE_BRANCH === 'develop') { + return 'staging' + } else if (process.env.CIRCLE_PULL_REQUEST) { + return 'pull-request' + } else { + return 'other' + } +} From c7db4c5a4d1983fc6a7559bf338d1a2e3c307505 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Thu, 30 Jul 2020 17:44:13 -0300 Subject: [PATCH 032/107] Update `brfs` from v1.6.1 to v2.0.2 (#9115) We were not affected by the breaking changes introduced with v2.0.0. This was updated primarily to get a bugfix relating to source maps, and to update some older transitive dependencies. --- package.json | 2 +- yarn.lock | 157 +++++++++++++++++++++++++-------------------------- 2 files changed, 79 insertions(+), 80 deletions(-) diff --git a/package.json b/package.json index 63a41bb394eb..25634fed147c 100644 --- a/package.json +++ b/package.json @@ -195,7 +195,7 @@ "babel-eslint": "^10.1.0", "babel-loader": "^8.0.6", "babelify": "^10.0.0", - "brfs": "^1.6.1", + "brfs": "^2.0.2", "browserify": "^16.5.1", "browserify-derequire": "^1.0.1", "browserify-transform-tools": "^1.7.0", diff --git a/yarn.lock b/yarn.lock index 3245b5190d7b..885354726adf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3748,6 +3748,11 @@ array-flatten@1.1.1: resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= +array-from@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/array-from/-/array-from-2.1.1.tgz#cfe9d8c26628b9dc5aecc62a9f5d8f1f352c1195" + integrity sha1-z+nYwmYoudxa7MYqn12PHzUsEZU= + array-includes@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.0.3.tgz#184b48f62d92d7452bb31b323165c7f8bd02266d" @@ -5584,14 +5589,14 @@ braces@^3.0.1, braces@~3.0.2: dependencies: fill-range "^7.0.1" -brfs@^1.6.1: - version "1.6.1" - resolved "https://registry.yarnpkg.com/brfs/-/brfs-1.6.1.tgz#b78ce2336d818e25eea04a0947cba6d4fb8849c3" - integrity sha512-OfZpABRQQf+Xsmju8XE9bDjs+uU4vLREGolP7bDgcpsI17QREyZ4Bl+2KLxxx1kCgA0fAIhKQBaBYh+PEcCqYQ== +brfs@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/brfs/-/brfs-2.0.2.tgz#44237878fa82aa479ce4f5fe2c1796ec69f07845" + integrity sha512-IrFjVtwu4eTJZyu8w/V2gxU7iLTtcHih67sgEdzrhjLBMHp2uYefUBfdM4k2UvcuWMgV7PQDZHSLeNWnLFKWVQ== dependencies: quote-stream "^1.0.1" resolve "^1.1.5" - static-module "^2.2.0" + static-module "^3.0.2" through2 "^2.0.0" brorand@^1.0.1: @@ -8002,6 +8007,11 @@ d@^1.0.1: es5-ext "^0.10.50" type "^1.0.1" +dash-ast@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/dash-ast/-/dash-ast-1.0.0.tgz#12029ba5fb2f8aa6f0a861795b23c1b4b6c27d37" + integrity sha512-Vy4dx7gquTeMcQR/hDkYLGUnwVil6vk4FOOct+djUnHOUWt+zJPJAaRIXaAFkPXtJjvlY7o3rfRu0/3hpnwoUA== + dashdash@^1.12.0: version "1.14.1" resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" @@ -9484,7 +9494,7 @@ es6-iterator@^2.0.1, es6-iterator@~2.0.1, es6-iterator@~2.0.3: es5-ext "^0.10.35" es6-symbol "^3.1.1" -es6-map@^0.1.3: +es6-map@^0.1.3, es6-map@^0.1.5: version "0.1.5" resolved "https://registry.yarnpkg.com/es6-map/-/es6-map-0.1.5.tgz#9136e0503dcc06a301690f0bb14ff4e364e949f0" integrity sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA= @@ -9513,7 +9523,7 @@ es6-promisify@^5.0.0: dependencies: es6-promise "^4.0.3" -es6-set@~0.1.5: +es6-set@^0.1.5, es6-set@~0.1.5: version "0.1.5" resolved "https://registry.yarnpkg.com/es6-set/-/es6-set-0.1.5.tgz#d2b3ec5d4d800ced818db538d28974db0a73ccb1" integrity sha1-0rPsXU2ADO2BjbU40ol02wpzzLE= @@ -9570,34 +9580,10 @@ escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1 resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= -escodegen@1.x.x, escodegen@^1.8.1, escodegen@~1.9.0: - version "1.9.1" - resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.9.1.tgz#dbae17ef96c8e4bedb1356f4504fa4cc2f7cb7e2" - integrity sha512-6hTjO1NAWkHnDk3OqQ4YrCuwwmGHL9S3nPlzBOUG/R44rda3wLNrfvQ5fkSGjyhHFKM7ALPKcKGrwvCLe0lC7Q== - dependencies: - esprima "^3.1.3" - estraverse "^4.2.0" - esutils "^2.0.2" - optionator "^0.8.1" - optionalDependencies: - source-map "~0.6.1" - -escodegen@^1.9.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.9.0.tgz#9811a2f265dc1cd3894420ee3717064b632b8852" - integrity sha512-v0MYvNQ32bzwoG2OSFzWAkuahDQHK92JBN0pTAALJ4RIxEZe766QJPDR8Hqy7XNUy5K3fnVL76OqYAdc4TZEIw== - dependencies: - esprima "^3.1.3" - estraverse "^4.2.0" - esutils "^2.0.2" - optionator "^0.8.1" - optionalDependencies: - source-map "~0.5.6" - -escodegen@^1.9.1: - version "1.13.0" - resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.13.0.tgz#c7adf9bd3f3cc675bb752f202f79a720189cab29" - integrity sha512-eYk2dCkxR07DsHA/X2hRBj0CFAZeri/LyDMc0C8JT1Hqi6JnVpMhJ7XFITbb0+yZS3lVkaPL2oCkZ3AVmeVbMw== +escodegen@1.x.x, escodegen@^1.11.1, escodegen@^1.9.0, escodegen@^1.9.1: + version "1.14.3" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.3.tgz#4e7b81fba61581dc97582ed78cab7f0e8d63f503" + integrity sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw== dependencies: esprima "^4.0.1" estraverse "^4.2.0" @@ -9902,7 +9888,7 @@ espree@^6.1.2: acorn-jsx "^5.2.0" eslint-visitor-keys "^1.1.0" -esprima@3.x.x, esprima@^3.1.3: +esprima@3.x.x: version "3.1.3" resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" integrity sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM= @@ -9937,6 +9923,11 @@ estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0: resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13" integrity sha1-De4/7TH81GlhjOc0IJn8GvoL2xM= +estree-is-function@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/estree-is-function/-/estree-is-function-1.0.0.tgz#c0adc29806d7f18a74db7df0f3b2666702e37ad2" + integrity sha512-nSCWn1jkSq2QAtkaVLJZY2ezwcFO161HVc174zL1KPW3RJ+O6C3eJb8Nx7OXzvhoEv+nLgSR1g71oWUHUDTrJA== + esutils@^2.0.0, esutils@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" @@ -11154,7 +11145,7 @@ fake-tag@^1.0.0: resolved "https://registry.yarnpkg.com/fake-tag/-/fake-tag-1.0.1.tgz#1d59da482240a02bd83500ca98976530ed154b0d" integrity sha512-qmewZoBpa71mM+y6oxXYW/d1xOYQmeIvnEXAt1oCmdP0sqcogWYLepR87QL1jQVLSVMVYDq2cjY6ec/Wu8/4pg== -falafel@^2.0.0, falafel@^2.1.0: +falafel@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/falafel/-/falafel-2.1.0.tgz#96bb17761daba94f46d001738b3cedf3a67fe06c" integrity sha1-lrsXdh2rqU9G0AFzizzt86Z/4Gw= @@ -12151,7 +12142,7 @@ gensync@^1.0.0-beta.1: resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.1.tgz#58f4361ff987e5ff6e1e7a210827aa371eaac269" integrity sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg== -get-assigned-identifiers@^1.2.0: +get-assigned-identifiers@^1.1.0, get-assigned-identifiers@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/get-assigned-identifiers/-/get-assigned-identifiers-1.2.0.tgz#6dbf411de648cbaf8d9169ebb0d2d576191e2ff1" integrity sha512-mBBwmeGTrxEMO4pMaaf/uUEFHnYtwr8FTe8Y/mer4rcV/bye0qGm6pw1bGZFGStxC5O76c5ZAVBGnqHmOaJpdQ== @@ -17583,12 +17574,12 @@ mafmt@^6.0.0, mafmt@^6.0.2, mafmt@^6.0.4, mafmt@^6.0.7: dependencies: multiaddr "^6.0.4" -magic-string@^0.22.4: - version "0.22.5" - resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.22.5.tgz#8e9cf5afddf44385c1da5bc2a6a0dbd10b03657e" - integrity sha512-oreip9rJZkzvA8Qzk9HFs8fZGF/u7H/gtrE8EN6RjKJ9kh2HlC+yQ2QezifqTZfGyiuAV0dRv5a+y/8gBb1m9w== +magic-string@0.25.1: + version "0.25.1" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.1.tgz#b1c248b399cd7485da0fe7385c2fc7011843266e" + integrity sha512-sCuTz6pYom8Rlt4ISPFn6wuFodbKMIHUMv4Qko9P17dpxb7s52KJTmRuZZqHdGmLCK9AOcDare039nRIcfdkEg== dependencies: - vlq "^0.2.2" + sourcemap-codec "^1.4.1" make-dir@^1.0.0, make-dir@^1.2.0: version "1.3.0" @@ -19299,11 +19290,6 @@ object-inspect@~1.3.0: resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.3.0.tgz#5b1eb8e6742e2ee83342a637034d844928ba2f6d" integrity sha512-OHHnLgLNXpM++GnJRyyhbr2bwl3pPVm4YvaraHrRvDt/N3r+s/gDVHciA7EJBTkijKXj61ssgSAikq1fb0IBRg== -object-inspect@~1.4.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.4.1.tgz#37ffb10e71adaf3748d05f713b4c9452f402cbc4" - integrity sha512-wqdhLpfCUbEsoEwl3FXwGyv8ief1k/1aUdIPCqVnupM6e8l63BEJdiF/0swtn04/8p05tG/T0FrpTlfwvljOdw== - object-is@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.0.1.tgz#0aa60ec9989a0b3ed795cf4d06f62cf1ad6539b6" @@ -21732,7 +21718,7 @@ quick-lru@^4.0.1: resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f" integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g== -quote-stream@^1.0.1, quote-stream@~1.0.2: +quote-stream@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/quote-stream/-/quote-stream-1.0.2.tgz#84963f8c9c26b942e153feeb53aae74652b7e0b2" integrity sha1-hJY/jJwmuULhU/7rU6rnRlK34LI= @@ -22453,6 +22439,15 @@ readable-stream@1.1, readable-stream@1.1.x, readable-stream@^1.0.33, readable-st isarray "0.0.1" string_decoder "~0.10.x" +"readable-stream@2 || 3", readable-stream@^3.0.0, readable-stream@^3.0.1, readable-stream@^3.0.2, readable-stream@^3.0.5, readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" + integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + readable-stream@^2.0.0, readable-stream@^2.0.5, readable-stream@^2.2.8, readable-stream@^2.2.9, readable-stream@^2.3.0, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.5, readable-stream@~2.3.6: version "2.3.6" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" @@ -22466,15 +22461,6 @@ readable-stream@^2.0.0, readable-stream@^2.0.5, readable-stream@^2.2.8, readable string_decoder "~1.1.1" util-deprecate "~1.0.1" -"readable-stream@2 || 3", readable-stream@^3.0.0, readable-stream@^3.0.1, readable-stream@^3.0.2, readable-stream@^3.0.5, readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" - integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - readable-stream@~1.0.15: version "1.0.34" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" @@ -23653,6 +23639,19 @@ schema-utils@^2.0.1, schema-utils@^2.5.0, schema-utils@^2.6.0, schema-utils@^2.6 ajv "^6.10.2" ajv-keywords "^3.4.1" +scope-analyzer@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/scope-analyzer/-/scope-analyzer-2.1.1.tgz#5156c27de084d74bf75af9e9506aaf95c6e73dd6" + integrity sha512-azEAihtQ9mEyZGhfgTJy3IbOWEzeOrYbg7NcYEshPKnKd+LZmC3TNd5dmDxbLBsTG/JVWmCp+vDJ03vJjeXMHg== + dependencies: + array-from "^2.1.1" + dash-ast "^1.0.0" + es6-map "^0.1.5" + es6-set "^0.1.5" + es6-symbol "^3.1.1" + estree-is-function "^1.0.0" + get-assigned-identifiers "^1.1.0" + scrollbarwidth@^0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/scrollbarwidth/-/scrollbarwidth-0.1.3.tgz#1b0de64e288c38c427f4a01fe00a462a04b94fdf" @@ -24494,7 +24493,7 @@ source-map@^0.4.2: dependencies: amdefine ">=0.0.4" -source-map@^0.5.0, source-map@^0.5.1, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.3, source-map@~0.5.6: +source-map@^0.5.0, source-map@^0.5.1, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.3: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= @@ -24504,6 +24503,11 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== +sourcemap-codec@^1.4.1: + version "1.4.8" + resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" + integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== + space-separated-tokens@^1.0.0: version "1.1.4" resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-1.1.4.tgz#27910835ae00d0adfcdbd0ad7e611fb9544351fa" @@ -24676,12 +24680,12 @@ state-toggle@^1.0.0: resolved "https://registry.yarnpkg.com/state-toggle/-/state-toggle-1.0.2.tgz#75e93a61944116b4959d665c8db2d243631d6ddc" integrity sha512-8LpelPGR0qQM4PnfLiplOQNJcIN1/r2Gy0xKB2zKnIW2YzPMt2sR4I/+gtPjhN7Svh9kw+zqEg2SFwpBO9iNiw== -static-eval@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/static-eval/-/static-eval-2.0.2.tgz#2d1759306b1befa688938454c546b7871f806a42" - integrity sha512-N/D219Hcr2bPjLxPiV+TQE++Tsmrady7TqAJugLy7Xk1EumfDWS/f5dtBbkRCGE7wKKXuYockQoj8Rm2/pVKyg== +static-eval@^2.0.5: + version "2.1.0" + resolved "https://registry.yarnpkg.com/static-eval/-/static-eval-2.1.0.tgz#a16dbe54522d7fa5ef1389129d813fd47b148014" + integrity sha512-agtxZ/kWSsCkI5E4QifRwsaPs0P0JmZV6dkLz6ILYfFYQGn+5plctanRN+IC8dJRiFkyXHrwEE3W9Wmx67uDbw== dependencies: - escodegen "^1.8.1" + escodegen "^1.11.1" static-extend@^0.1.1: version "0.1.2" @@ -24691,24 +24695,24 @@ static-extend@^0.1.1: define-property "^0.2.5" object-copy "^0.1.0" -static-module@^2.2.0: - version "2.2.5" - resolved "https://registry.yarnpkg.com/static-module/-/static-module-2.2.5.tgz#bd40abceae33da6b7afb84a0e4329ff8852bfbbf" - integrity sha512-D8vv82E/Kpmz3TXHKG8PPsCPg+RAX6cbCOyvjM6x04qZtQ47EtJFVwRsdov3n5d6/6ynrOY9XB4JkaZwB2xoRQ== +static-module@^3.0.2: + version "3.0.4" + resolved "https://registry.yarnpkg.com/static-module/-/static-module-3.0.4.tgz#bfbd1d1c38dd1fbbf0bb4af0c1b3ae18a93a2b68" + integrity sha512-gb0v0rrgpBkifXCa3yZXxqVmXDVE+ETXj6YlC/jt5VzOnGXR2C15+++eXuMDUYsePnbhf+lwW0pE1UXyOLtGCw== dependencies: + acorn-node "^1.3.0" concat-stream "~1.6.0" convert-source-map "^1.5.1" duplexer2 "~0.1.4" - escodegen "~1.9.0" - falafel "^2.1.0" + escodegen "^1.11.1" has "^1.0.1" - magic-string "^0.22.4" + magic-string "0.25.1" merge-source-map "1.0.4" - object-inspect "~1.4.0" - quote-stream "~1.0.2" + object-inspect "^1.6.0" readable-stream "~2.3.3" + scope-analyzer "^2.0.1" shallow-copy "~0.0.1" - static-eval "^2.0.0" + static-eval "^2.0.5" through2 "~2.0.3" "statuses@>= 1.3.1 < 2": @@ -27121,11 +27125,6 @@ vinyl@^2.2.0: remove-trailing-separator "^1.0.1" replace-ext "^1.0.0" -vlq@^0.2.2: - version "0.2.3" - resolved "https://registry.yarnpkg.com/vlq/-/vlq-0.2.3.tgz#8f3e4328cf63b1540c0d67e1b2778386f8975b26" - integrity sha512-DRibZL6DsNhIgYQ+wNdWDL2SL3bKPlVrRiBqV5yuMm++op8W4kGFtaQfCs4KEJn0wBZcHVHJ3eoywX8983k1ow== - vm-browserify@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.0.tgz#bd76d6a23323e2ca8ffa12028dc04559c75f9019" From 1c46715b46dc6215020bb14833eb94f9ee62cdca Mon Sep 17 00:00:00 2001 From: Brad Decker Date: Fri, 31 Jul 2020 09:17:29 -0500 Subject: [PATCH 033/107] delete unused confirm styles (#9118) --- ui/app/css/itcss/components/confirm.scss | 344 ----------------------- ui/app/css/itcss/components/index.scss | 1 - 2 files changed, 345 deletions(-) delete mode 100644 ui/app/css/itcss/components/confirm.scss diff --git a/ui/app/css/itcss/components/confirm.scss b/ui/app/css/itcss/components/confirm.scss deleted file mode 100644 index b382bcae608e..000000000000 --- a/ui/app/css/itcss/components/confirm.scss +++ /dev/null @@ -1,344 +0,0 @@ -.confirm-screen-container { - position: relative; - align-items: center; - flex: 1 0 auto; - flex-flow: column nowrap; - box-shadow: 0 2px 4px 0 rgba($black, 0.08); - border-radius: 8px; - display: flex; - - @media screen and (max-width: 575px) { - width: 100%; - box-shadow: initial; - } -} - -.notification { - .confirm-screen-wrapper { - @media screen and (max-width: $break-small) { - height: calc(100vh - 85px); - } - } -} - -.confirm-screen-wrapper { - height: 100%; - width: 380px; - background-color: $white; - display: flex; - flex-flow: column nowrap; - z-index: 25; - align-items: center; - position: relative; - overflow-y: auto; - overflow-x: hidden; - border-top-left-radius: 8px; - border-top-right-radius: 8px; - - @media screen and (max-width: $break-small) { - width: 100%; - overflow-x: hidden; - overflow-y: auto; - top: 0; - box-shadow: none; - height: calc(100vh - 58px - 85px); - border-top-left-radius: 0; - border-top-right-radius: 0; - } -} - -.confirm-screen-wrapper > .confirm-screen-total-box { - margin-left: 10px; - margin-right: 10px; -} - -.confirm-screen-wrapper > .confirm-memo-wrapper { - margin: 0; -} - -.confirm-screen-header { - height: 88px; - background-color: $athens-grey; - position: relative; - display: flex; - justify-content: center; - align-items: center; - font-size: 22px; - line-height: 29px; - width: 100%; - padding: 25px 0; - flex: 0 0 auto; - - @media screen and (max-width: $break-small) { - font-size: 20px; - } -} - -.confirm-screen-header-tip { - height: 25px; - width: 25px; - background: $athens-grey; - position: absolute; - transform: rotate(45deg); - top: 71px; - left: 0; - right: 0; - margin: 0 auto; -} - -.confirm-screen-title { - line-height: 27px; - - @media screen and (max-width: $break-small) { - margin-left: 22px; - margin-right: 8px; - } -} - -.confirm-screen-back-button { - color: $primary-blue; - font-size: 1rem; - position: absolute; - top: 38px; - right: 38px; - background: none; -} - -.confirm-screen-account-wrapper { - display: flex; - flex-direction: column; - align-items: center; -} - -.confirm-screen-account-name { - margin-top: 12px; - font-size: 14px; - line-height: 19px; - color: $scorpion; - text-align: center; -} - -.confirm-screen-row-info { - font-size: 16px; - line-height: 21px; -} - -.confirm-screen-account-number { - font-size: 10px; - line-height: 16px; - color: $dusty-gray; - text-align: center; - height: 16px; -} - -.confirm-send-ether, -.confirm-send-token { - i.fa-arrow-right { - align-self: start; - margin: 24px 14px 0 !important; - } -} - -.confirm-screen-identicons { - margin-top: 24px; - flex: 0 0 auto; - - i.fa-arrow-right { - align-self: start; - margin: 42px 14px 0; - } - - i.fa-file-text-o { - font-size: 60px; - margin: 16px 8px 0 8px; - text-align: center; - } -} - -.confirm-screen-sending-to-message { - text-align: center; - font-size: 16px; - margin-top: 30px; - font-weight: 300; -} - -.confirm-screen-send-amount { - color: $scorpion; - margin-top: 12px; - text-align: center; - font-size: 40px; - line-height: 53px; - flex: 0 0 auto; -} - -.confirm-screen-send-amount-currency { - font-size: 20px; - line-height: 20px; - text-align: center; - flex: 0 0 auto; -} - -.confirm-memo-wrapper { - min-height: 24px; - width: 100%; - border-bottom: 1px solid $alto; - flex: 0 0 auto; -} - -.confirm-screen-send-memo { - color: $scorpion; - font-size: 16px; - line-height: 19px; - font-weight: 400; -} - -.confirm-screen-label { - font-size: 16px; - line-height: 40px; - color: $scorpion; - text-align: left; -} - -section .confirm-screen-account-name, -section .confirm-screen-account-number, -.confirm-screen-row-info, -.confirm-screen-row-detail { - text-align: left; -} - -.confirm-screen-rows { - display: flex; - flex-flow: column nowrap; - width: 100%; - flex: 0 0 auto; -} - -.confirm-screen-section-column { - flex: 0.5; -} - -.confirm-screen-row { - display: flex; - flex-flow: row nowrap; - width: 100%; - align-items: center; - padding: 12px; - padding-left: 35px; - font-size: 16px; - line-height: 22px; - - &:not(:last-of-type) { - border-bottom: 1px solid $alto; - } -} - -@media screen and (max-width: 379px) { - .confirm-screen-row { - span.confirm-screen-section-column { - flex: 0.4; - } - - div.confirm-screen-section-column { - flex: 0.6; - } - - .currency-display__input { - font-size: 14px; - } - } -} - -.confirm-screen-row-detail { - font-size: 12px; - line-height: 16px; - color: $dusty-gray; -} - -.confirm-screen-total-box { - background-color: $wild-sand; - position: relative; - - .confirm-screen-label { - line-height: 21px; - } - - .confirm-screen-row-detail { - color: $scorpion; - } - - &__subtitle { - font-size: 12px; - line-height: 16px; - } - - .confirm-screen-row-info { - font-size: 16px; - font-weight: 500; - line-height: 21px; - } -} - -.confirm-screen-error { - font-size: 12px; - line-height: 12px; - color: #f00; - position: absolute; - right: 12px; - width: 80px; - text-align: right; -} - -.confirm-screen-row.confirm-screen-total-box { - .confirm-screen-section-column--with-error { - flex: 0.6; - } -} - -@media screen and (max-width: 379px) { - .confirm-screen-row.confirm-screen-total-box { - .confirm-screen-section-column--with-error { - flex: 0.4; - } - } -} - -.confirm-screen-form { - position: relative; - - .confirm-screen-error { - right: 0; - width: 100%; - margin-top: 7px; - text-align: center; - } -} - -.confirm-screen-confirm-button { - height: 50px; - border-radius: 4px; - background-color: #02c9b1; - font-size: 16px; - color: $white; - text-align: center; - padding-top: 15px; - padding-bottom: 15px; - border-width: 0; - box-shadow: none; - flex: 1 0 auto; - margin: 0 5px; -} - -.btn-light.confirm-screen-cancel-button { - height: 50px; - background: none; - border: none; - opacity: 1; - border-width: 0; - padding-top: 15px; - padding-bottom: 15px; - font-size: 16px; - box-shadow: none; - cursor: pointer; - flex: 1 0 auto; - margin: 0 5px; -} diff --git a/ui/app/css/itcss/components/index.scss b/ui/app/css/itcss/components/index.scss index b9a698503f09..dda191fbdfbc 100644 --- a/ui/app/css/itcss/components/index.scss +++ b/ui/app/css/itcss/components/index.scss @@ -11,7 +11,6 @@ @import './newui-sections'; @import './account-dropdown'; @import './send'; -@import './confirm'; @import './loading-overlay'; // Tx List and Sections From 943d637cd2624b0cafdc6bb78e53fa01f9721944 Mon Sep 17 00:00:00 2001 From: Brad Decker Date: Fri, 31 Jul 2020 09:17:43 -0500 Subject: [PATCH 034/107] remove unused tx-list styles (#9121) --- ui/app/css/itcss/components/index.scss | 1 - .../itcss/components/transaction-list.scss | 297 ------------------ 2 files changed, 298 deletions(-) delete mode 100644 ui/app/css/itcss/components/transaction-list.scss diff --git a/ui/app/css/itcss/components/index.scss b/ui/app/css/itcss/components/index.scss index dda191fbdfbc..2b104a2eb600 100644 --- a/ui/app/css/itcss/components/index.scss +++ b/ui/app/css/itcss/components/index.scss @@ -14,7 +14,6 @@ @import './loading-overlay'; // Tx List and Sections -@import './transaction-list'; @import './sections'; @import './currency-display'; @import './menu'; diff --git a/ui/app/css/itcss/components/transaction-list.scss b/ui/app/css/itcss/components/transaction-list.scss deleted file mode 100644 index e0aef961e51a..000000000000 --- a/ui/app/css/itcss/components/transaction-list.scss +++ /dev/null @@ -1,297 +0,0 @@ -.tx-list-container { - height: 87.5%; - - @media screen and (min-width: $break-large) { - overflow-y: scroll; - } -} - -.tx-list-header-wrapper { - flex: 0 0 auto; -} - -.tx-list-header { - text-transform: capitalize; -} - -@media screen and (max-width: $break-small) { - .tx-list-header-wrapper { - margin-top: 0.2em; - margin-bottom: 0.6em; - justify-content: center; - flex: 0 0 auto; - } - - .tx-list-header { - align-self: center; - font-size: 12px; - color: $dusty-gray; - text-transform: uppercase; - } -} - -@media screen and (min-width: $break-large) { - .tx-list-header { - font-size: 16px; - margin: 1.1em 2.37em 0.8em; - } - - .tx-list-container::-webkit-scrollbar { - display: none; - } -} - -.tx-list-content-divider { - height: 1px; - background: rgb(231, 231, 231); - flex: 0 0 1px; - - @media screen and (max-width: $break-small) { - margin: 0.1em 0; - } - - @media screen and (min-width: $break-large) { - margin: 0.1em 2.37em; - } -} - -.tx-list-item-wrapper { - flex: 1 1 auto; - width: 0; - align-items: stretch; - justify-content: flex-start; - display: flex; - flex-flow: column nowrap; - - @media screen and (max-width: $break-small) { - padding: 0 1.3em 0.8em; - } - - @media screen and (min-width: $break-large) { - padding-bottom: 8px; - } -} - -.tx-list-pending-item-container { - cursor: pointer; - opacity: 0.5; -} - -.tx-list-date-wrapper { - margin-top: 6px; - flex: 1 1 auto; -} - -.tx-list-content-wrapper { - align-items: stretch; - margin: 4px 0; - flex: 1 0 auto; - width: 100%; - display: flex; - flex-flow: row nowrap; - - @media screen and (max-width: $break-small) { - font-size: 12px; - - .tx-list-status { - font-size: 12px !important; - } - - .tx-list-account { - font-size: 14px !important; - } - - .tx-list-value { - font-size: 14px; - line-height: 18px; - } - - .tx-list-fiat-value { - font-size: 12px; - line-height: 22px; - } - } -} - -.tx-list-item-retry-container { - background: #d1edff; - width: 100%; - border-radius: 12px; - font-size: 0.75rem; - display: flex; - justify-content: center; - margin-left: 44px; - width: calc(100% - 44px); - padding: 4px; - cursor: pointer; - - @media screen and (min-width: 576px) and (max-width: 679px) { - flex-flow: column; - align-items: center; - } - - @media screen and (min-width: 380px) and (max-width: 575px) { - flex-flow: row; - } - - @media screen and (max-width: 379px) { - flex-flow: column; - align-items: center; - } -} - -.tx-list-item-retry-link { - text-decoration: underline; - margin-left: 6px; - cursor: pointer; - - @media screen and (min-width: 576px) and (max-width: 679px) { - margin-left: 0; - } - - @media screen and (min-width: 380px) and (max-width: 575px) { - margin-left: 6px; - } - - @media screen and (max-width: 379px) { - margin-left: 0; - text-align: center; - } -} - -.tx-list-date { - color: $dusty-gray; - font-size: 12px; -} - -.tx-list-identicon-wrapper { - align-self: center; - flex: 0 0 auto; - margin-right: 16px; - display: flex; -} - -.tx-list-account-and-status-wrapper { - display: flex; - flex: 1 1 auto; - flex-flow: row wrap; - width: 0; - - @media screen and (max-width: $break-small) { - flex-direction: column; - justify-content: flex-start; - align-items: flex-start; - align-self: center; - - .tx-list-account-wrapper { - height: 18px; - - .tx-list-account { - line-height: 14px; - } - } - } - - @media screen and (min-width: $break-large) { - flex-direction: row; - justify-content: flex-start; - align-items: center; - - .tx-list-account-wrapper { - flex: 1.3 2 auto; - min-width: 153px; - } - - .tx-list-status-wrapper { - flex: 6 6 auto; - } - } - - .tx-list-account { - font-size: 16px; - color: $scorpion; - } - - .tx-list-status { - color: $dusty-gray; - font-size: 16px; - text-transform: capitalize; - } - - .tx-list-status--rejected, - .tx-list-status--failed { - color: $monzo; - } - - .tx-list-status--dropped { - opacity: 0.5; - } -} - -.tx-list-item { - border-bottom: 1px solid $geyser; - flex: 0 0 auto; - display: flex; - flex-flow: row nowrap; - - @media screen and (min-width: $break-large) { - padding: 0 2.37em; - } - - &:last-of-type { - border-bottom: 1px solid rgb(231, 231, 231); - margin-bottom: 32px; - } - - &__wrapper { - align-self: center; - flex: 2 2 auto; - color: $dusty-gray; - - .tx-list-value { - font-size: 16px; - text-align: right; - } - - .tx-list-value--confirmed { - color: $caribbean-green; - } - - .tx-list-fiat-value { - font-size: 12px; - text-align: right; - } - } - - &--empty { - text-align: center; - border-bottom: none !important; - padding: 16px; - } -} - -.tx-list-details-wrapper { - overflow: hidden; - flex: 0 0 35%; -} - -.tx-list-value { - font-size: 16px; - text-align: right; - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; -} - -.tx-list-fiat-value { - font-size: 12px; - line-height: initial; - text-align: right; - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; -} - -.tx-list-value--confirmed { - color: $caribbean-green; -} From 5df42c09d60e5bd30f4d9299c994a058dcf2ca39 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Tue, 4 Aug 2020 12:09:02 -0300 Subject: [PATCH 035/107] Non-zero exit code upon failure to validate source maps (#9132) The source map validator will now exit with a non-zero exit code when an error occurs, or when the source maps cannot be validated. --- development/sourcemap-validator.js | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/development/sourcemap-validator.js b/development/sourcemap-validator.js index e87c0604f6cb..2fda2a1142e8 100644 --- a/development/sourcemap-validator.js +++ b/development/sourcemap-validator.js @@ -13,13 +13,23 @@ const fsAsync = pify(fs) // if not working it may error or print minified garbage // -start().catch(console.error) +start().catch((error) => { + console.error(error) + process.exit(1) +}) async function start () { const targetFiles = [`inpage.js`, `contentscript.js`, `ui.js`, `background.js`] + let valid = true + for (const buildName of targetFiles) { - await validateSourcemapForFile({ buildName }) + const fileIsValid = await validateSourcemapForFile({ buildName }) + valid = valid && fileIsValid + } + + if (!valid) { + process.exit(1) } } @@ -59,6 +69,7 @@ async function validateSourcemapForFile ({ buildName }) { console.log(` sampling from ${consumer.sources.length} files`) let sampleCount = 0 + let valid = true const buildLines = rawBuild.split('\n') const targetString = 'new Error' @@ -71,6 +82,7 @@ async function validateSourcemapForFile ({ buildName }) { const result = consumer.originalPositionFor(position) // warn if source content is missing if (!result.source) { + valid = false console.warn(`!! missing source for position: ${JSON.stringify(position)}`) // const buildLine = buildLines[position.line - 1] console.warn(` origin in build:`) @@ -86,6 +98,7 @@ async function validateSourcemapForFile ({ buildName }) { const portion = line.slice(result.column) const isMaybeValid = portion.includes(targetString) if (!isMaybeValid) { + valid = false console.error('Sourcemap seems invalid:') console.log(`\n========================== ${result.source} ====================================\n`) console.log(line) @@ -94,6 +107,7 @@ async function validateSourcemapForFile ({ buildName }) { }) }) console.log(` checked ${sampleCount} samples`) + return valid } function indicesOf (substring, string) { From 96dd43553804487e9167cea2e39dc58f3ed6a6a3 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Tue, 4 Aug 2020 12:10:28 -0300 Subject: [PATCH 036/107] Add `validate-source-maps` npm script (#9134) This script executes the `sourcemap-validator.js` script to validate the source maps in the `dist` directory. --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 25634fed147c..578574cb9fa9 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "lint:shellcheck": "./development/shellcheck.sh", "lint:styles": "stylelint '*/**/*.scss'", "lint:lockfile": "lockfile-lint --path yarn.lock --allowed-hosts npm yarn github.com codeload.github.com --empty-hostname false --allowed-schemes \"https:\" \"git+https:\"", + "validate-source-maps": "node ./development/sourcemap-validator.js", "verify-locales": "node ./development/verify-locale-strings.js", "verify-locales:fix": "node ./development/verify-locale-strings.js --fix", "mozilla-lint": "addons-linter dist/firefox", From 7f87bdb2134604c8ca5d3d54e5064eeb5a219f50 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Tue, 4 Aug 2020 12:55:11 -0300 Subject: [PATCH 037/107] Improve sourcemap validator console report (#9131) The report printed to the console for invalid source map samples has been improved in a few ways: * The entire message is now printed using `console.error`, so the contents aren't split between STDERR and STDOUT * The code fence is now guaranteed to be a set length, rather than it varying depending on the filename * The code fence is no longer padded on the inside with newlines, which results in a more compact output that is (in my opinion) just as readable. --- development/sourcemap-validator.js | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/development/sourcemap-validator.js b/development/sourcemap-validator.js index 2fda2a1142e8..d4d9b82937f2 100644 --- a/development/sourcemap-validator.js +++ b/development/sourcemap-validator.js @@ -99,10 +99,7 @@ async function validateSourcemapForFile ({ buildName }) { const isMaybeValid = portion.includes(targetString) if (!isMaybeValid) { valid = false - console.error('Sourcemap seems invalid:') - console.log(`\n========================== ${result.source} ====================================\n`) - console.log(line) - console.log(`\n==============================================================================\n`) + console.error(`Sourcemap seems invalid:\n${getFencedCode(result.source, line)}`) } }) }) @@ -110,6 +107,20 @@ async function validateSourcemapForFile ({ buildName }) { return valid } +const CODE_FENCE_LENGTH = 80 +const TITLE_PADDING_LENGTH = 1 + +function getFencedCode (filename, code) { + const title = `${' '.repeat(TITLE_PADDING_LENGTH)}${filename}${' '.repeat(TITLE_PADDING_LENGTH)}` + const openingFenceLength = Math.max(CODE_FENCE_LENGTH - (filename.length + (TITLE_PADDING_LENGTH * 2)), 0) + const startOpeningFenceLength = Math.floor(openingFenceLength / 2) + const endOpeningFenceLength = Math.ceil(openingFenceLength / 2) + const openingFence = `${'='.repeat(startOpeningFenceLength)}${title}${'='.repeat(endOpeningFenceLength)}` + const closingFence = '='.repeat(CODE_FENCE_LENGTH) + + return `${openingFence}\n${code}\n${closingFence}\n` +} + function indicesOf (substring, string) { const a = [] let i = -1 From e1b9cfa5f8e1a38683e05e913db40d0799aa9d2e Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Tue, 4 Aug 2020 13:45:03 -0300 Subject: [PATCH 038/107] Update source map validator target files (#9133) All JavaScript files included in the final bundle are now listed as target files. The `phishing-detect.js` file is the only new file to be validated that was not validated before. Any files that are expected to fail validation are commented out, with a note explaining why they're expected to fail. --- development/sourcemap-validator.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/development/sourcemap-validator.js b/development/sourcemap-validator.js index d4d9b82937f2..7be473110a4f 100644 --- a/development/sourcemap-validator.js +++ b/development/sourcemap-validator.js @@ -20,7 +20,15 @@ start().catch((error) => { async function start () { - const targetFiles = [`inpage.js`, `contentscript.js`, `ui.js`, `background.js`] + const targetFiles = [ + `background.js`, + // `bg-libs`, skipped because source maps are invalid due to browserify bug: https://github.com/browserify/browserify/issues/1971 + // `contentscript.js`, skipped because the validator is erroneously sampling the inlined `inpage.js` script + `inpage.js`, + 'phishing-detect.js', + `ui.js`, + // `ui-libs.js`, skipped because source maps are invalid due to browserify bug: https://github.com/browserify/browserify/issues/1971 + ] let valid = true for (const buildName of targetFiles) { From f7edc83a4e8b1e7d814593e1dd091301181206e0 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Tue, 4 Aug 2020 14:21:46 -0300 Subject: [PATCH 039/107] Add source map validator to CI (#9135) Source maps are now validated during CI. This is done during a new job called "validate-source-maps`, and it is required to pass for tests to pass. --- .circleci/config.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 240b4aa0274d..d4b0febd7ab0 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -38,6 +38,9 @@ workflows: - test-unit-global: requires: - prep-deps + - validate-source-maps: + requires: + - prep-build - test-mozilla-lint: requires: - prep-deps @@ -49,6 +52,7 @@ workflows: - test-lint-lockfile - test-unit - test-unit-global + - validate-source-maps - test-mozilla-lint - test-e2e-chrome - test-e2e-firefox @@ -358,6 +362,18 @@ jobs: - run: name: test:unit:global command: yarn test:unit:global + + validate-source-maps: + docker: + - image: circleci/node@sha256:e16740707de2ebed45c05d507f33ef204902349c7356d720610b5ec6a35d3d88 + steps: + - checkout + - attach_workspace: + at: . + - run: + name: Validate source maps + command: yarn validate-source-maps + test-mozilla-lint: docker: - image: circleci/node@sha256:e16740707de2ebed45c05d507f33ef204902349c7356d720610b5ec6a35d3d88 From 658e478f29b546e5c3d05585234a98c781e4062b Mon Sep 17 00:00:00 2001 From: Erik Marks <25517051+rekmarks@users.noreply.github.com> Date: Tue, 4 Aug 2020 13:02:48 -0700 Subject: [PATCH 040/107] Fix connection removal bug (#9137) * fix remove connections bug --- app/scripts/metamask-controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 5aa4539e325c..41a2d8fb31bf 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -1722,7 +1722,7 @@ export default class MetamaskController extends EventEmitter { delete connections[id] - if (Object.keys(connections.length === 0)) { + if (Object.keys(connections).length === 0) { delete this.connections[origin] } } From 723e478689b291363f0914a86713d82328147bc2 Mon Sep 17 00:00:00 2001 From: Brad Decker Date: Wed, 5 Aug 2020 08:43:07 -0500 Subject: [PATCH 041/107] move currency-display styles to where they are used (#9119) --- .../itcss/components/currency-display.scss | 82 ------------------- ui/app/css/itcss/components/index.scss | 1 - .../send-gas-row/send-gas-row.scss | 82 +++++++++++++++++++ ui/app/pages/send/send.scss | 2 + 4 files changed, 84 insertions(+), 83 deletions(-) delete mode 100644 ui/app/css/itcss/components/currency-display.scss diff --git a/ui/app/css/itcss/components/currency-display.scss b/ui/app/css/itcss/components/currency-display.scss deleted file mode 100644 index b8a23d06b13d..000000000000 --- a/ui/app/css/itcss/components/currency-display.scss +++ /dev/null @@ -1,82 +0,0 @@ -.currency-display { - height: 54px; - border: 1px solid $alto; - border-radius: 4px; - background-color: $white; - color: $scorpion; - font-size: 16px; - padding: 8px 10px; - position: relative; - - &__primary-row { - display: flex; - } - - &__input { - color: $scorpion; - font-size: 16px; - line-height: 22px; - border: none; - max-width: 22ch; - } - - &__primary-currency { - color: $scorpion; - font-weight: 400; - font-size: 16px; - line-height: 22px; - } - - &__converted-row { - display: flex; - } - - &__converted-value, - &__converted-currency { - color: $dusty-gray; - font-size: 12px; - line-height: 12px; - } - - &__input-wrapper { - position: relative; - display: flex; - flex: 1; - max-width: 100%; - - input[type="number"] { - -moz-appearance: textfield; - } - - input[type="number"]::-webkit-inner-spin-button { - -webkit-appearance: none; - -moz-appearance: none; - display: none; - } - - input[type="number"]:hover::-webkit-inner-spin-button { - -webkit-appearance: none; - -moz-appearance: none; - display: none; - } - } - - &__currency-symbol { - margin-top: 1px; - color: $scorpion; - } - - .react-numeric-input { - input[type="number"]::-webkit-inner-spin-button { - -webkit-appearance: none; - -moz-appearance: none; - display: none; - } - - input[type="number"]:hover::-webkit-inner-spin-button { - -webkit-appearance: none; - -moz-appearance: none; - display: none; - } - } -} diff --git a/ui/app/css/itcss/components/index.scss b/ui/app/css/itcss/components/index.scss index 2b104a2eb600..7d47b8cefaf7 100644 --- a/ui/app/css/itcss/components/index.scss +++ b/ui/app/css/itcss/components/index.scss @@ -15,7 +15,6 @@ // Tx List and Sections @import './sections'; -@import './currency-display'; @import './menu'; @import './gas-slider'; @import './tab-bar'; diff --git a/ui/app/pages/send/send-content/send-gas-row/send-gas-row.scss b/ui/app/pages/send/send-content/send-gas-row/send-gas-row.scss index e69de29bb2d1..b8a23d06b13d 100644 --- a/ui/app/pages/send/send-content/send-gas-row/send-gas-row.scss +++ b/ui/app/pages/send/send-content/send-gas-row/send-gas-row.scss @@ -0,0 +1,82 @@ +.currency-display { + height: 54px; + border: 1px solid $alto; + border-radius: 4px; + background-color: $white; + color: $scorpion; + font-size: 16px; + padding: 8px 10px; + position: relative; + + &__primary-row { + display: flex; + } + + &__input { + color: $scorpion; + font-size: 16px; + line-height: 22px; + border: none; + max-width: 22ch; + } + + &__primary-currency { + color: $scorpion; + font-weight: 400; + font-size: 16px; + line-height: 22px; + } + + &__converted-row { + display: flex; + } + + &__converted-value, + &__converted-currency { + color: $dusty-gray; + font-size: 12px; + line-height: 12px; + } + + &__input-wrapper { + position: relative; + display: flex; + flex: 1; + max-width: 100%; + + input[type="number"] { + -moz-appearance: textfield; + } + + input[type="number"]::-webkit-inner-spin-button { + -webkit-appearance: none; + -moz-appearance: none; + display: none; + } + + input[type="number"]:hover::-webkit-inner-spin-button { + -webkit-appearance: none; + -moz-appearance: none; + display: none; + } + } + + &__currency-symbol { + margin-top: 1px; + color: $scorpion; + } + + .react-numeric-input { + input[type="number"]::-webkit-inner-spin-button { + -webkit-appearance: none; + -moz-appearance: none; + display: none; + } + + input[type="number"]:hover::-webkit-inner-spin-button { + -webkit-appearance: none; + -moz-appearance: none; + display: none; + } + } +} diff --git a/ui/app/pages/send/send.scss b/ui/app/pages/send/send.scss index 81de75a853da..61b1fe805970 100644 --- a/ui/app/pages/send/send.scss +++ b/ui/app/pages/send/send.scss @@ -1,3 +1,5 @@ +@import './send-content/send-gas-row/send-gas-row'; + .send { &__header { position: relative; From b92475ac7c86dee44ab7708803d505726da2a7c4 Mon Sep 17 00:00:00 2001 From: Brad Decker Date: Wed, 5 Aug 2020 08:43:21 -0500 Subject: [PATCH 042/107] Colocate alert styles (#9117) --- ui/app/components/app/index.scss | 1 + .../components/alert.scss => components/ui/alert/index.scss} | 0 ui/app/css/itcss/components/index.scss | 1 - 3 files changed, 1 insertion(+), 1 deletion(-) rename ui/app/{css/itcss/components/alert.scss => components/ui/alert/index.scss} (100%) diff --git a/ui/app/components/app/index.scss b/ui/app/components/app/index.scss index 00724f3e2473..73881885eaa2 100644 --- a/ui/app/components/app/index.scss +++ b/ui/app/components/app/index.scss @@ -1,5 +1,6 @@ @import 'account-menu/index'; @import 'add-token-button/index'; +@import '../ui/alert/index'; @import 'alerts/alerts'; @import 'app-header/index'; @import 'asset-list-item/asset-list-item'; diff --git a/ui/app/css/itcss/components/alert.scss b/ui/app/components/ui/alert/index.scss similarity index 100% rename from ui/app/css/itcss/components/alert.scss rename to ui/app/components/ui/alert/index.scss diff --git a/ui/app/css/itcss/components/index.scss b/ui/app/css/itcss/components/index.scss index 7d47b8cefaf7..19f202711166 100644 --- a/ui/app/css/itcss/components/index.scss +++ b/ui/app/css/itcss/components/index.scss @@ -7,7 +7,6 @@ @import './footer'; @import './network'; @import './modal'; -@import './alert'; @import './newui-sections'; @import './account-dropdown'; @import './send'; From db1b72a95c50ce955b7f8b9d63bc72923ea6b9e5 Mon Sep 17 00:00:00 2001 From: Brad Decker Date: Wed, 5 Aug 2020 08:44:17 -0500 Subject: [PATCH 043/107] colocate editable label styles and code (#9120) --- ui/app/components/app/index.scss | 1 + ui/app/components/ui/{ => editable-label}/editable-label.js | 0 ui/app/components/ui/editable-label/index.js | 1 + .../ui/editable-label/index.scss} | 0 ui/app/css/itcss/components/index.scss | 1 - ui/app/css/itcss/components/sections.scss | 4 ---- 6 files changed, 2 insertions(+), 5 deletions(-) rename ui/app/components/ui/{ => editable-label}/editable-label.js (100%) create mode 100644 ui/app/components/ui/editable-label/index.js rename ui/app/{css/itcss/components/editable-label.scss => components/ui/editable-label/index.scss} (100%) diff --git a/ui/app/components/app/index.scss b/ui/app/components/app/index.scss index 73881885eaa2..5cb6fd2a7682 100644 --- a/ui/app/components/app/index.scss +++ b/ui/app/components/app/index.scss @@ -10,6 +10,7 @@ @import 'confirm-page-container/index'; @import '../ui/currency-input/index'; @import '../ui/currency-display/index'; +@import '../ui/editable-label/index'; @import '../ui/error-message/index'; @import '../ui/export-text-container/index'; @import '../ui/identicon/index'; diff --git a/ui/app/components/ui/editable-label.js b/ui/app/components/ui/editable-label/editable-label.js similarity index 100% rename from ui/app/components/ui/editable-label.js rename to ui/app/components/ui/editable-label/editable-label.js diff --git a/ui/app/components/ui/editable-label/index.js b/ui/app/components/ui/editable-label/index.js new file mode 100644 index 000000000000..43078e7f7435 --- /dev/null +++ b/ui/app/components/ui/editable-label/index.js @@ -0,0 +1 @@ +export { default } from './editable-label' diff --git a/ui/app/css/itcss/components/editable-label.scss b/ui/app/components/ui/editable-label/index.scss similarity index 100% rename from ui/app/css/itcss/components/editable-label.scss rename to ui/app/components/ui/editable-label/index.scss diff --git a/ui/app/css/itcss/components/index.scss b/ui/app/css/itcss/components/index.scss index 19f202711166..ea01b5060d42 100644 --- a/ui/app/css/itcss/components/index.scss +++ b/ui/app/css/itcss/components/index.scss @@ -21,7 +21,6 @@ @import './request-signature'; @import './request-encryption-public-key'; @import './request-decrypt-message'; -@import './editable-label'; @import './pages/index'; @import './new-account'; @import './tooltip'; diff --git a/ui/app/css/itcss/components/sections.scss b/ui/app/css/itcss/components/sections.scss index c187b121bd82..4ddda782a883 100644 --- a/ui/app/css/itcss/components/sections.scss +++ b/ui/app/css/itcss/components/sections.scss @@ -100,10 +100,6 @@ textarea.twelve-word-phrase { padding-left: 5px; } -.editable-label { - display: flex; -} - /* accounts */ .accounts-section { From ecaa6c55dd199f68e4d3aa6a2181f1d263ee53fa Mon Sep 17 00:00:00 2001 From: Brad Decker Date: Thu, 6 Aug 2020 08:04:55 -0500 Subject: [PATCH 044/107] trim unused account-list-item code and co-locate styles (#9116) --- .../account-list-item/account-list-item.js | 51 +++++++++ .../components/app/account-list-item/index.js | 1 + .../app/account-list-item/index.scss | 26 +++++ .../tests/account-list-item-component.test.js | 40 +------ ui/app/components/app/index.scss | 1 + .../signature-request-original.component.js | 3 +- .../signature-request-header.component.js | 3 +- ui/app/css/base-styles.scss | 2 - .../itcss/components/account-dropdown.scss | 104 ------------------ ui/app/css/itcss/components/index.scss | 1 - .../confirm-decrypt-message.component.js | 3 +- ...confirm-encryption-public-key.component.js | 3 +- .../account-list-item.component.js | 101 ----------------- .../account-list-item.container.js | 21 ---- ui/app/pages/send/account-list-item/index.js | 1 - .../tests/account-list-item-container.test.js | 63 ----------- 16 files changed, 85 insertions(+), 339 deletions(-) create mode 100644 ui/app/components/app/account-list-item/account-list-item.js create mode 100644 ui/app/components/app/account-list-item/index.js create mode 100644 ui/app/components/app/account-list-item/index.scss rename ui/app/{pages/send => components/app}/account-list-item/tests/account-list-item-component.test.js (75%) delete mode 100644 ui/app/css/itcss/components/account-dropdown.scss delete mode 100644 ui/app/pages/send/account-list-item/account-list-item.component.js delete mode 100644 ui/app/pages/send/account-list-item/account-list-item.container.js delete mode 100644 ui/app/pages/send/account-list-item/index.js delete mode 100644 ui/app/pages/send/account-list-item/tests/account-list-item-container.test.js diff --git a/ui/app/components/app/account-list-item/account-list-item.js b/ui/app/components/app/account-list-item/account-list-item.js new file mode 100644 index 000000000000..7e001f7d6025 --- /dev/null +++ b/ui/app/components/app/account-list-item/account-list-item.js @@ -0,0 +1,51 @@ +import React from 'react' +import PropTypes from 'prop-types' +import { checksumAddress } from '../../../helpers/utils/util' +import Identicon from '../../ui/identicon' +import AccountMismatchWarning from '../../ui/account-mismatch-warning/account-mismatch-warning.component' + +export default function AccountListItem ({ + account, + className, + displayAddress = false, + handleClick, + icon = null, +}) { + const { name, address, balance } = account || {} + + return ( +
handleClick && handleClick({ name, address, balance })} + > + +
+ + +
{ name || address }
+ + {icon &&
{ icon }
} + + +
+ + {displayAddress && name && ( +
+ { checksumAddress(address) } +
+ )} +
+ ) +} + +AccountListItem.propTypes = { + account: PropTypes.object, + className: PropTypes.string, + displayAddress: PropTypes.bool, + handleClick: PropTypes.func, + icon: PropTypes.node, +} diff --git a/ui/app/components/app/account-list-item/index.js b/ui/app/components/app/account-list-item/index.js new file mode 100644 index 000000000000..1759f6597e2e --- /dev/null +++ b/ui/app/components/app/account-list-item/index.js @@ -0,0 +1 @@ +export { default } from './account-list-item' diff --git a/ui/app/components/app/account-list-item/index.scss b/ui/app/components/app/account-list-item/index.scss new file mode 100644 index 000000000000..dcc08d9b5ee1 --- /dev/null +++ b/ui/app/components/app/account-list-item/index.scss @@ -0,0 +1,26 @@ +.account-list-item { + &__top-row { + display: flex; + margin-top: 10px; + margin-left: 8px; + position: relative; + } + + &__account-name { + font-size: 16px; + margin-left: 8px; + } + + &__icon { + position: absolute; + right: 12px; + top: 1px; + } + + &__account-address { + margin-left: 35px; + width: 80%; + overflow: hidden; + text-overflow: ellipsis; + } +} diff --git a/ui/app/pages/send/account-list-item/tests/account-list-item-component.test.js b/ui/app/components/app/account-list-item/tests/account-list-item-component.test.js similarity index 75% rename from ui/app/pages/send/account-list-item/tests/account-list-item-component.test.js rename to ui/app/components/app/account-list-item/tests/account-list-item-component.test.js index a2949511a3e7..ee4ef3c89a02 100644 --- a/ui/app/pages/send/account-list-item/tests/account-list-item-component.test.js +++ b/ui/app/components/app/account-list-item/tests/account-list-item-component.test.js @@ -3,9 +3,8 @@ import assert from 'assert' import { shallow } from 'enzyme' import sinon from 'sinon' import * as utils from '../../../../helpers/utils/util' -import Identicon from '../../../../components/ui/identicon' -import UserPreferencedCurrencyDisplay from '../../../../components/app/user-preferenced-currency-display' -import AccountListItem from '../account-list-item.component' +import Identicon from '../../../ui/identicon' +import AccountListItem from '../account-list-item' describe('AccountListItem Component', function () { let wrapper, propsMethodSpies, checksumAddressStub @@ -22,11 +21,7 @@ describe('AccountListItem Component', function () { } /> @@ -113,36 +108,5 @@ describe('AccountListItem Component', function () { wrapper.setProps({ account: { address: 'someAddressButNoName' } }) assert.equal(wrapper.find('.account-list-item__account-address').length, 0) }) - - it('should render a CurrencyDisplay with the correct props if displayBalance is true', function () { - wrapper.setProps({ displayBalance: true }) - assert.equal(wrapper.find(UserPreferencedCurrencyDisplay).length, 2) - assert.deepEqual( - wrapper.find(UserPreferencedCurrencyDisplay).at(0).props(), - { - type: 'PRIMARY', - value: 'mockBalance', - hideTitle: true, - }, - ) - }) - - it('should only render one CurrencyDisplay if showFiat is false', function () { - wrapper.setProps({ showFiat: false, displayBalance: true }) - assert.equal(wrapper.find(UserPreferencedCurrencyDisplay).length, 1) - assert.deepEqual( - wrapper.find(UserPreferencedCurrencyDisplay).at(0).props(), - { - type: 'PRIMARY', - value: 'mockBalance', - hideTitle: true, - }, - ) - }) - - it('should not render a CurrencyDisplay if displayBalance is false', function () { - wrapper.setProps({ displayBalance: false }) - assert.equal(wrapper.find(UserPreferencedCurrencyDisplay).length, 0) - }) }) }) diff --git a/ui/app/components/app/index.scss b/ui/app/components/app/index.scss index 5cb6fd2a7682..64136c6619e3 100644 --- a/ui/app/components/app/index.scss +++ b/ui/app/components/app/index.scss @@ -1,3 +1,4 @@ +@import 'account-list-item/index'; @import 'account-menu/index'; @import 'add-token-button/index'; @import '../ui/alert/index'; diff --git a/ui/app/components/app/signature-request-original/signature-request-original.component.js b/ui/app/components/app/signature-request-original/signature-request-original.component.js index 8587f024ed66..29e06bab6f92 100644 --- a/ui/app/components/app/signature-request-original/signature-request-original.component.js +++ b/ui/app/components/app/signature-request-original/signature-request-original.component.js @@ -7,7 +7,7 @@ import { ObjectInspector } from 'react-inspector' import { ENVIRONMENT_TYPE_NOTIFICATION, MESSAGE_TYPE } from '../../../../../app/scripts/lib/enums' import { getEnvironmentType } from '../../../../../app/scripts/lib/util' import Identicon from '../../ui/identicon' -import AccountListItem from '../../../pages/send/account-list-item/account-list-item.component' +import AccountListItem from '../account-list-item' import { conversionUtil } from '../../../helpers/utils/conversion-util' import Button from '../../ui/button' @@ -95,7 +95,6 @@ export default class SignatureRequestOriginal extends Component {
diff --git a/ui/app/components/app/signature-request/signature-request-header/signature-request-header.component.js b/ui/app/components/app/signature-request/signature-request-header/signature-request-header.component.js index 89440acdca1d..2ec74748c7c1 100644 --- a/ui/app/components/app/signature-request/signature-request-header/signature-request-header.component.js +++ b/ui/app/components/app/signature-request/signature-request-header/signature-request-header.component.js @@ -1,6 +1,6 @@ import React, { PureComponent } from 'react' import PropTypes from 'prop-types' -import AccountListItem from '../../../../pages/send/account-list-item/account-list-item.component' +import AccountListItem from '../../account-list-item' import NetworkDisplay from '../../network-display' export default class SignatureRequestHeader extends PureComponent { @@ -16,7 +16,6 @@ export default class SignatureRequestHeader extends PureComponent {
{fromAccount && ( )} diff --git a/ui/app/css/base-styles.scss b/ui/app/css/base-styles.scss index 6a970f9a3a11..5445d2f34f75 100644 --- a/ui/app/css/base-styles.scss +++ b/ui/app/css/base-styles.scss @@ -24,8 +24,6 @@ html { input:focus, textarea:focus, .unit-input__input, - .account-list-item__account-primary-balance, - .account-list-item__input, .currency-display__input { outline: none; } diff --git a/ui/app/css/itcss/components/account-dropdown.scss b/ui/app/css/itcss/components/account-dropdown.scss deleted file mode 100644 index 3d2e29b42dbf..000000000000 --- a/ui/app/css/itcss/components/account-dropdown.scss +++ /dev/null @@ -1,104 +0,0 @@ -.account-dropdown-balance { - color: $dusty-gray; - line-height: 19px; -} - -.account-dropdown-edit-button { - color: $dusty-gray; - - &:hover { - color: $white; - } -} - -.account-list-item { - &__top-row { - display: flex; - margin-top: 10px; - margin-left: 8px; - position: relative; - } - - &__tooltip-wrapper { - left: -10px; - } - - &__account-balances { - height: auto; - border: none; - background-color: transparent; - color: #9b9b9b; - margin-left: 34px; - margin-top: 4px; - position: relative; - } - - &__primary-cached-container { - display: flex; - } - - &__cached-star { - margin-left: 4px; - } - - &__cached-balances { - div:first-of-type { - color: $web-orange; - } - - div:last-of-type { - color: rgba(220, 153, 18, 0.6901960784313725); - } - } - - &__account-name { - font-size: 16px; - margin-left: 8px; - } - - &__icon { - position: absolute; - right: 12px; - top: 1px; - } - - &__account-primary-balance, - &__account-secondary-balance { - line-height: 16px; - font-size: 12px; - } - - &__balance-flag { - position: absolute; - top: 3px; - left: -8px; - color: $primary-blue; - } - - &__account-primary-balance { - color: $scorpion; - border: none; - } - - &__account-secondary-balance { - color: $dusty-gray; - } - - &__account-address { - margin-left: 35px; - width: 80%; - overflow: hidden; - text-overflow: ellipsis; - } - - &__dropdown { - &:hover { - background: rgba($alto, 0.2); - cursor: pointer; - - input { - background: rgba($alto, 0.1); - } - } - } -} diff --git a/ui/app/css/itcss/components/index.scss b/ui/app/css/itcss/components/index.scss index ea01b5060d42..e26483b32064 100644 --- a/ui/app/css/itcss/components/index.scss +++ b/ui/app/css/itcss/components/index.scss @@ -8,7 +8,6 @@ @import './network'; @import './modal'; @import './newui-sections'; -@import './account-dropdown'; @import './send'; @import './loading-overlay'; diff --git a/ui/app/pages/confirm-decrypt-message/confirm-decrypt-message.component.js b/ui/app/pages/confirm-decrypt-message/confirm-decrypt-message.component.js index a95e6d4cded0..a054eb2b33b5 100644 --- a/ui/app/pages/confirm-decrypt-message/confirm-decrypt-message.component.js +++ b/ui/app/pages/confirm-decrypt-message/confirm-decrypt-message.component.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types' import copyToClipboard from 'copy-to-clipboard' import classnames from 'classnames' -import AccountListItem from '../send/account-list-item/account-list-item.component' +import AccountListItem from '../../components/app/account-list-item' import Button from '../../components/ui/button' import Identicon from '../../components/ui/identicon' import Tooltip from '../../components/ui/tooltip-v2' @@ -119,7 +119,6 @@ export default class ConfirmDecryptMessage extends Component {
diff --git a/ui/app/pages/confirm-encryption-public-key/confirm-encryption-public-key.component.js b/ui/app/pages/confirm-encryption-public-key/confirm-encryption-public-key.component.js index 5ef485c72a0d..3dbba9b8f6df 100644 --- a/ui/app/pages/confirm-encryption-public-key/confirm-encryption-public-key.component.js +++ b/ui/app/pages/confirm-encryption-public-key/confirm-encryption-public-key.component.js @@ -1,7 +1,7 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' -import AccountListItem from '../send/account-list-item/account-list-item.component' +import AccountListItem from '../../components/app/account-list-item' import Button from '../../components/ui/button' import Identicon from '../../components/ui/identicon' @@ -99,7 +99,6 @@ export default class ConfirmEncryptionPublicKey extends Component {
diff --git a/ui/app/pages/send/account-list-item/account-list-item.component.js b/ui/app/pages/send/account-list-item/account-list-item.component.js deleted file mode 100644 index 58df336e55b6..000000000000 --- a/ui/app/pages/send/account-list-item/account-list-item.component.js +++ /dev/null @@ -1,101 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' -import classnames from 'classnames' -import { checksumAddress } from '../../../helpers/utils/util' -import Identicon from '../../../components/ui/identicon' -import UserPreferencedCurrencyDisplay from '../../../components/app/user-preferenced-currency-display' -import { PRIMARY, SECONDARY } from '../../../helpers/constants/common' -import Tooltip from '../../../components/ui/tooltip-v2' -import AccountMismatchWarning from '../../../components/ui/account-mismatch-warning/account-mismatch-warning.component' -import { useI18nContext } from '../../../hooks/useI18nContext' - -export default function AccountListItem ({ - account, - className, - displayAddress = false, - displayBalance = true, - handleClick, - icon = null, - balanceIsCached, - showFiat = true, -}) { - const t = useI18nContext() - const { name, address, balance } = account || {} - - return ( -
handleClick && handleClick({ name, address, balance })} - > - -
- - -
{ name || address }
- - {icon &&
{ icon }
} - - -
- - {displayAddress && name && ( -
- { checksumAddress(address) } -
- )} - - {displayBalance && ( - -
-
- - { - balanceIsCached - ? * - : null - } -
- {showFiat && ( - - )} -
-
- )} - -
- ) -} - -AccountListItem.propTypes = { - account: PropTypes.object, - className: PropTypes.string, - displayAddress: PropTypes.bool, - displayBalance: PropTypes.bool, - handleClick: PropTypes.func, - icon: PropTypes.node, - balanceIsCached: PropTypes.bool, - showFiat: PropTypes.bool, -} diff --git a/ui/app/pages/send/account-list-item/account-list-item.container.js b/ui/app/pages/send/account-list-item/account-list-item.container.js deleted file mode 100644 index 40888d3ba8e4..000000000000 --- a/ui/app/pages/send/account-list-item/account-list-item.container.js +++ /dev/null @@ -1,21 +0,0 @@ -import { connect } from 'react-redux' -import { - getNativeCurrency, - getIsMainnet, - isBalanceCached, - getPreferences, -} from '../../../selectors' -import AccountListItem from './account-list-item.component' - -export default connect(mapStateToProps)(AccountListItem) - -function mapStateToProps (state) { - const { showFiatInTestnets } = getPreferences(state) - const isMainnet = getIsMainnet(state) - - return { - nativeCurrency: getNativeCurrency(state), - balanceIsCached: isBalanceCached(state), - showFiat: (isMainnet || !!showFiatInTestnets), - } -} diff --git a/ui/app/pages/send/account-list-item/index.js b/ui/app/pages/send/account-list-item/index.js deleted file mode 100644 index 907485cf773b..000000000000 --- a/ui/app/pages/send/account-list-item/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './account-list-item.container' diff --git a/ui/app/pages/send/account-list-item/tests/account-list-item-container.test.js b/ui/app/pages/send/account-list-item/tests/account-list-item-container.test.js deleted file mode 100644 index 611830d78bf6..000000000000 --- a/ui/app/pages/send/account-list-item/tests/account-list-item-container.test.js +++ /dev/null @@ -1,63 +0,0 @@ -import assert from 'assert' -import proxyquire from 'proxyquire' - -let mapStateToProps - -proxyquire('../account-list-item.container.js', { - 'react-redux': { - connect: (ms) => { - mapStateToProps = ms - return () => ({}) - }, - }, - '../../../selectors': { - getConversionRate: () => `mockConversionRate`, - getCurrentCurrency: () => `mockCurrentCurrency`, - getNativeCurrency: () => `mockNativeCurrency`, - isBalanceCached: () => `mockBalanceIsCached`, - getPreferences: ({ showFiatInTestnets }) => ({ - showFiatInTestnets, - }), - getIsMainnet: ({ isMainnet }) => isMainnet, - }, -}) - -describe('account-list-item container', function () { - - describe('mapStateToProps()', function () { - - it('should map the correct properties to props', function () { - assert.deepEqual(mapStateToProps({ isMainnet: true, showFiatInTestnets: false }), { - nativeCurrency: 'mockNativeCurrency', - balanceIsCached: 'mockBalanceIsCached', - showFiat: true, - }) - }) - - it('should map the correct properties to props when in mainnet and showFiatInTestnet is true', function () { - assert.deepEqual(mapStateToProps({ isMainnet: true, showFiatInTestnets: true }), { - nativeCurrency: 'mockNativeCurrency', - balanceIsCached: 'mockBalanceIsCached', - showFiat: true, - }) - }) - - it('should map the correct properties to props when not in mainnet and showFiatInTestnet is true', function () { - assert.deepEqual(mapStateToProps({ isMainnet: false, showFiatInTestnets: true }), { - nativeCurrency: 'mockNativeCurrency', - balanceIsCached: 'mockBalanceIsCached', - showFiat: true, - }) - }) - - it('should map the correct properties to props when not in mainnet and showFiatInTestnet is false', function () { - assert.deepEqual(mapStateToProps({ isMainnet: false, showFiatInTestnets: false }), { - nativeCurrency: 'mockNativeCurrency', - balanceIsCached: 'mockBalanceIsCached', - showFiat: false, - }) - }) - - }) - -}) From a914eae51e87e660ec03f16789feb06e8ec6697b Mon Sep 17 00:00:00 2001 From: Dan J Miller Date: Thu, 6 Aug 2020 13:52:12 -0230 Subject: [PATCH 045/107] Adds decETHToDecWEI util method (#9141) --- ui/app/helpers/utils/conversions.util.js | 9 ++++++ ui/app/helpers/utils/conversions.util.test.js | 29 +++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 ui/app/helpers/utils/conversions.util.test.js diff --git a/ui/app/helpers/utils/conversions.util.js b/ui/app/helpers/utils/conversions.util.js index 5e1c21ff7cc9..0cde0d0acff7 100644 --- a/ui/app/helpers/utils/conversions.util.js +++ b/ui/app/helpers/utils/conversions.util.js @@ -129,3 +129,12 @@ export function hexWEIToDecGWEI (decGWEI) { toDenomination: 'GWEI', }) } + +export function decETHToDecWEI (decEth) { + return conversionUtil(decEth, { + fromNumericBase: 'dec', + toNumericBase: 'dec', + fromDenomination: 'ETH', + toDenomination: 'WEI', + }) +} diff --git a/ui/app/helpers/utils/conversions.util.test.js b/ui/app/helpers/utils/conversions.util.test.js new file mode 100644 index 000000000000..553fd1fd6f11 --- /dev/null +++ b/ui/app/helpers/utils/conversions.util.test.js @@ -0,0 +1,29 @@ +import * as utils from './conversions.util' +import assert from 'assert' + +describe('decETHToDecWEI', function () { + it('should correctly convert 1 ETH to WEI', function () { + const weiValue = utils.decETHToDecWEI('1') + assert.equal(weiValue, '1000000000000000000') + }) + + it('should correctly convert 0.000000000000000001 ETH to WEI', function () { + const weiValue = utils.decETHToDecWEI('0.000000000000000001') + assert.equal(weiValue, '1') + }) + + it('should correctly convert 1000000.000000000000000001 ETH to WEI', function () { + const weiValue = utils.decETHToDecWEI('1000000.000000000000000001') + assert.equal(weiValue, '1000000000000000000000001') + }) + + it('should correctly convert 9876.543210 ETH to WEI', function () { + const weiValue = utils.decETHToDecWEI('9876.543210') + assert.equal(weiValue, '9876543210000000000000') + }) + + it('should correctly convert 1.0000000000000000 ETH to WEI', function () { + const weiValue = utils.decETHToDecWEI('1.0000000000000000') + assert.equal(weiValue, '1000000000000000000') + }) +}) From 9e1aed88c24a9ec6b3f3388f1489e572d6ec2598 Mon Sep 17 00:00:00 2001 From: Thomas Huang Date: Thu, 6 Aug 2020 12:30:28 -0700 Subject: [PATCH 046/107] Update 'react-devtools' to ^4.8.0 (#9140) * bump-react-devtools * Completed yarn lock after version bump of react-devtools --- package.json | 2 +- yarn.lock | 289 ++++++++++++++++++++++++++------------------------- 2 files changed, 151 insertions(+), 140 deletions(-) diff --git a/package.json b/package.json index 578574cb9fa9..0472e8e567d7 100644 --- a/package.json +++ b/package.json @@ -255,7 +255,7 @@ "proxyquire": "^2.1.3", "randomcolor": "^0.5.4", "rc": "^1.2.8", - "react-devtools": "^4.4.0", + "react-devtools": "^4.8.0", "react-test-renderer": "^16.12.0", "read-installed": "^4.0.3", "redux-mock-store": "^1.5.4", diff --git a/yarn.lock b/yarn.lock index 885354726adf..9938af5181ca 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1104,6 +1104,22 @@ resolved "https://registry.yarnpkg.com/@download/blockies/-/blockies-1.0.3.tgz#9aea770f9de72f3f947f1b3a53ee1e92f8dc4a68" integrity sha512-iGDh2M6pFuXg9kyW+U//963LKylSLFpLG5hZvUppCjhkiDwsYquQPyamxCQlLASYySS3gGKAki2eWG9qIHKCew== +"@electron/get@^1.0.1": + version "1.12.2" + resolved "https://registry.yarnpkg.com/@electron/get/-/get-1.12.2.tgz#6442066afb99be08cefb9a281e4b4692b33764f3" + integrity sha512-vAuHUbfvBQpYTJ5wB7uVIDq5c/Ry0fiTBMs7lnEYAo/qXXppIVcWdfBr57u6eRnKdVso7KSiH6p/LbQAG6Izrg== + dependencies: + debug "^4.1.1" + env-paths "^2.2.0" + fs-extra "^8.1.0" + got "^9.6.0" + progress "^2.0.3" + sanitize-filename "^1.6.2" + sumchecker "^3.0.1" + optionalDependencies: + global-agent "^2.0.2" + global-tunnel-ng "^2.7.1" + "@emotion/cache@^10.0.27": version "10.0.27" resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-10.0.27.tgz#7895db204e2c1a991ae33d51262a3a44f6737303" @@ -2604,6 +2620,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-10.14.14.tgz#a47955df2acf76ba7f0ac3b205d325da193dc9ad" integrity sha512-xXD08vZsvpv4xptQXj1+ky22f7ZoKu5ZNI/4l+/BXG3X+XaeZsmaFbbTKuhSE3NjjvRuZFxFf9sQBMXIcZNFMQ== +"@types/node@^12.0.12": + version "12.12.54" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.54.tgz#a4b58d8df3a4677b6c08bfbc94b7ad7a7a5f82d1" + integrity sha512-ge4xZ3vSBornVYlDnk7yZ0gK6ChHf/CHB7Gl1I0Jhah8DDnEQqBzgohYG4FX4p81TNirSETOiSyn+y1r9/IR6w== + "@types/node@^12.6.1": version "12.12.38" resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.38.tgz#58841a382f231ad005dbb935c36d44aa1118a26b" @@ -5490,6 +5511,11 @@ boolbase@^1.0.0, boolbase@~1.0.0: resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= +boolean@^3.0.0, boolean@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/boolean/-/boolean-3.0.1.tgz#35ecf2b4a2ee191b0b44986f14eb5f052a5cbb4f" + integrity sha512-HRZPIjPcbwAVQvOTxR4YE3o8Xs98NqbbL1iEZDCz7CL8ql0Lt5iOyJFxfnAB0oFs8Oh02F/lLlg30Mexv46LjA== + boom@^7.2.0: version "7.3.0" resolved "https://registry.yarnpkg.com/boom/-/boom-7.3.0.tgz#733a6d956d33b0b1999da3fe6c12996950d017b9" @@ -7326,6 +7352,11 @@ core-js@^3.0.4: resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.6.4.tgz#440a83536b458114b9cb2ac1580ba377dc470647" integrity sha512-4paDGScNgZP2IXXilaffL9X7968RuvwlkK3xWtZRVqgd8SYNiVKRJvkFd1aqqEuPfN7E68ZHEp9hDj6lHj4Hyw== +core-js@^3.6.5: + version "3.6.5" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.6.5.tgz#7395dc273af37fb2e50e9bd3d9fe841285231d1a" + integrity sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA== + core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" @@ -7999,14 +8030,6 @@ d@1: dependencies: es5-ext "^0.10.9" -d@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a" - integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA== - dependencies: - es5-ext "^0.10.50" - type "^1.0.1" - dash-ast@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/dash-ast/-/dash-ast-1.0.0.tgz#12029ba5fb2f8aa6f0a861795b23c1b4b6c27d37" @@ -8124,7 +8147,7 @@ debug-fabulous@1.X: memoizee "0.4.X" object-assign "4.X" -debug@2, debug@2.6.9, debug@^2.1.1, debug@^2.1.3, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.8, debug@^2.6.9: +debug@2, debug@2.6.9, debug@^2.1.1, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.8, debug@^2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== @@ -9047,21 +9070,6 @@ ejs@^3.0.2: dependencies: jake "^10.6.1" -electron-download@^4.1.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/electron-download/-/electron-download-4.1.1.tgz#02e69556705cc456e520f9e035556ed5a015ebe8" - integrity sha512-FjEWG9Jb/ppK/2zToP+U5dds114fM1ZOJqMAR4aXXL5CvyPE9fiqBK/9YcwC9poIFQTEJk/EM/zyRwziziRZrg== - dependencies: - debug "^3.0.0" - env-paths "^1.0.0" - fs-extra "^4.0.1" - minimist "^1.2.0" - nugget "^2.0.1" - path-exists "^3.0.0" - rc "^1.2.1" - semver "^5.4.1" - sumchecker "^2.0.2" - electron-to-chromium@^1.3.122: version "1.3.146" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.146.tgz#d7010f417f87c2068fbb6700ae57767e2393eba7" @@ -9077,13 +9085,13 @@ electron-to-chromium@^1.3.47, electron-to-chromium@^1.3.488: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.498.tgz#fd7188c8a49d6d0b5df1df55a1f1a4bf2c177457" integrity sha512-W1hGwaQEU8j9su2jeAr3aabkPuuXw+j8t73eajGAkEJWbfWiwbxBwQN/8Qmv2qCy3uCDm2rOAaZneYQM8VGC4w== -electron@^5.0.0: - version "5.0.12" - resolved "https://registry.yarnpkg.com/electron/-/electron-5.0.12.tgz#2d5438f8e2b8b99ea99848b61238fc66c283e861" - integrity sha512-Ydby8wfShB39MkZCF2PcAuNACj4PYbGxc0hGpThqZOiDgnSVroqvUAOSX9GCJogrjcP30ZZIe6TgPtQEABYg3w== +electron@^9.1.0: + version "9.1.2" + resolved "https://registry.yarnpkg.com/electron/-/electron-9.1.2.tgz#bfa26d6b192ea13abb6f1461371fd731a8358988" + integrity sha512-xEYadr3XqIqJ4ktBPo0lhzPdovv4jLCpiUUGc2M1frUhFhwqXokwhPaTUcE+zfu5+uf/ONDnQApwjzznBsRrgQ== dependencies: - "@types/node" "^10.12.18" - electron-download "^4.1.0" + "@electron/get" "^1.0.1" + "@types/node" "^12.0.12" extract-zip "^1.0.3" element-resize-detector@^1.2.1: @@ -9247,10 +9255,10 @@ env-ci@^2.1.0: execa "^1.0.0" java-properties "^0.2.9" -env-paths@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-1.0.0.tgz#4168133b42bb05c38a35b1ae4397c8298ab369e0" - integrity sha1-QWgTO0K7BcOKNbGuQ5fIKYqzaeA= +env-paths@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.0.tgz#cdca557dc009152917d6166e2febe1f039685e43" + integrity sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA== envify@^4.1.0: version "4.1.0" @@ -9480,7 +9488,7 @@ es5-shim@^4.5.13: resolved "https://registry.yarnpkg.com/es5-shim/-/es5-shim-4.5.13.tgz#5d88062de049f8969f83783f4a4884395f21d28b" integrity sha512-xi6hh6gsvDE0MaW4Vp1lgNEBpVcCXRWfPXj5egDvtgLz4L9MEvNwYEMdJH+JJinWkwa8c3c3o5HduV7dB/e1Hw== -es6-error@^4.0.1: +es6-error@^4.0.1, es6-error@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d" integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg== @@ -9547,14 +9555,6 @@ es6-symbol@3.1.1, es6-symbol@^3.1.1, es6-symbol@~3.1.1: d "1" es5-ext "~0.10.14" -es6-symbol@^3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18" - integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA== - dependencies: - d "^1.0.1" - ext "^1.1.2" - es6-weak-map@^2.0.1, es6-weak-map@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/es6-weak-map/-/es6-weak-map-2.0.2.tgz#5e3ab32251ffd1538a1f8e5ffa1357772f92d96f" @@ -9580,6 +9580,11 @@ escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1 resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + escodegen@1.x.x, escodegen@^1.11.1, escodegen@^1.9.0, escodegen@^1.9.1: version "1.14.3" resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.3.tgz#4e7b81fba61581dc97582ed78cab7f0e8d63f503" @@ -11022,13 +11027,6 @@ ext-name@^5.0.0: ext-list "^2.0.0" sort-keys-length "^1.0.0" -ext@^1.1.2: - version "1.4.0" - resolved "https://registry.yarnpkg.com/ext/-/ext-1.4.0.tgz#89ae7a07158f79d35517882904324077e4379244" - integrity sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A== - dependencies: - type "^2.0.0" - extend-shallow@^1.1.2: version "1.1.4" resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-1.1.4.tgz#19d6bf94dfc09d76ba711f39b872d21ff4dd9071" @@ -11886,7 +11884,7 @@ fs-extra@^1.0.0: jsonfile "^2.1.0" klaw "^1.0.0" -fs-extra@^4.0.1, fs-extra@^4.0.2: +fs-extra@^4.0.2: version "4.0.3" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.3.tgz#0d852122e5bc5beb453fb028e9c0c9bf36340c94" integrity sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg== @@ -12420,6 +12418,19 @@ glob@^7.1.6: once "^1.3.0" path-is-absolute "^1.0.0" +global-agent@^2.0.2: + version "2.1.12" + resolved "https://registry.yarnpkg.com/global-agent/-/global-agent-2.1.12.tgz#e4ae3812b731a9e81cbf825f9377ef450a8e4195" + integrity sha512-caAljRMS/qcDo69X9BfkgrihGUgGx44Fb4QQToNQjsiWh+YlQ66uqYVAdA8Olqit+5Ng0nkz09je3ZzANMZcjg== + dependencies: + boolean "^3.0.1" + core-js "^3.6.5" + es6-error "^4.1.1" + matcher "^3.0.0" + roarr "^2.15.3" + semver "^7.3.2" + serialize-error "^7.0.1" + global-dirs@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-0.1.1.tgz#b319c0dd4607f353f3be9cca4c72fc148c49f445" @@ -12463,6 +12474,16 @@ global-prefix@^3.0.0: kind-of "^6.0.2" which "^1.3.1" +global-tunnel-ng@^2.7.1: + version "2.7.1" + resolved "https://registry.yarnpkg.com/global-tunnel-ng/-/global-tunnel-ng-2.7.1.tgz#d03b5102dfde3a69914f5ee7d86761ca35d57d8f" + integrity sha512-4s+DyciWBV0eK148wqXxcmVAbFVPqtc3sEtUE/GTQfuU80rySLcMhUmHKSHI7/LDj8q0gDYI1lIhRRB7ieRAqg== + dependencies: + encodeurl "^1.0.2" + lodash "^4.17.10" + npm-conf "^1.1.3" + tunnel "^0.0.6" + global@^4.3.2, global@~4.3.0: version "4.3.2" resolved "https://registry.yarnpkg.com/global/-/global-4.3.2.tgz#e76989268a6c74c38908b1305b10fc0e394e9d0f" @@ -12505,6 +12526,13 @@ globalthis@^1.0.0: function-bind "^1.1.1" object-keys "^1.0.12" +globalthis@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.1.tgz#40116f5d9c071f9e8fb0037654df1ab3a83b7ef9" + integrity sha512-mJPRTc/P39NH/iNG4mXa9aIhNymaQikTrnspeCa2ZuJ+mH2QN/rXwtX3XwKrHqWgUQFbNZKtHM105aHzJalElw== + dependencies: + define-properties "^1.1.3" + globby@8.0.2: version "8.0.2" resolved "https://registry.yarnpkg.com/globby/-/globby-8.0.2.tgz#5697619ccd95c5275dbb2d6faa42087c1a941d8d" @@ -17684,6 +17712,13 @@ matchdep@^2.0.0: resolve "^1.4.0" stack-trace "0.0.10" +matcher@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/matcher/-/matcher-3.0.0.tgz#bd9060f4c5b70aa8041ccc6f80368760994f30ca" + integrity sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng== + dependencies: + escape-string-regexp "^4.0.0" + material-colors@^1.2.1: version "1.2.5" resolved "https://registry.yarnpkg.com/material-colors/-/material-colors-1.2.5.tgz#5292593e6754cb1bcc2b98030e4e0d6a3afc9ea1" @@ -17813,7 +17848,7 @@ memory-fs@^0.4.0, memory-fs@^0.4.1: errno "^0.1.3" readable-stream "^2.0.1" -meow@^3.1.0, meow@^3.3.0, meow@^3.7.0: +meow@^3.3.0, meow@^3.7.0: version "3.7.0" resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" integrity sha1-cstmi0JSKCkKu/qFaJJYcwioAfs= @@ -18145,7 +18180,7 @@ minimist-options@^4.0.2: is-plain-obj "^1.1.0" kind-of "^6.0.3" -minimist@1.2.0, minimist@^1.1.0, minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.5, minimist@~1.2.0: +minimist@1.2.0, minimist@^1.1.0, minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5, minimist@~1.2.0: version "1.2.5" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== @@ -19105,7 +19140,7 @@ npm-bundled@^1.0.1: resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.6.tgz#e7ba9aadcef962bb61248f91721cd932b3fe6bdd" integrity sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g== -npm-conf@^1.1.0: +npm-conf@^1.1.0, npm-conf@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/npm-conf/-/npm-conf-1.1.3.tgz#256cc47bd0e218c259c4e9550bf413bc2192aff9" integrity sha512-Yic4bZHJOt9RCFbRP3GgpqhScOY4HH3V2P8yBj6CeYq118Qr+BLXqT2JvpJ00mryLESpgOxf5XlFv4ZjXxLScw== @@ -19152,19 +19187,6 @@ nth-check@~1.0.1: dependencies: boolbase "~1.0.0" -nugget@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/nugget/-/nugget-2.0.1.tgz#201095a487e1ad36081b3432fa3cada4f8d071b0" - integrity sha1-IBCVpIfhrTYIGzQy+jytpPjQcbA= - dependencies: - debug "^2.1.3" - minimist "^1.1.0" - pretty-bytes "^1.0.2" - progress-stream "^1.1.0" - request "^2.45.0" - single-line-log "^1.1.2" - throttleit "0.0.2" - num2fraction@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede" @@ -21042,14 +21064,6 @@ preserve@^0.2.0: resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" integrity sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks= -pretty-bytes@^1.0.2: - version "1.0.4" - resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-1.0.4.tgz#0a22e8210609ad35542f8c8d5d2159aff0751c84" - integrity sha1-CiLoIQYJrTVUL4yNXSFZr/B1HIQ= - dependencies: - get-stdin "^4.0.1" - meow "^3.1.0" - pretty-bytes@^5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.3.0.tgz#f2849e27db79fb4d6cfe24764fc4134f165989f2" @@ -21123,14 +21137,6 @@ process@~0.5.1: resolved "https://registry.yarnpkg.com/process/-/process-0.5.2.tgz#1638d8a8e34c2f440a91db95ab9aeb677fc185cf" integrity sha1-FjjYqONML0QKkduVq5rrZ3/Bhc8= -progress-stream@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/progress-stream/-/progress-stream-1.2.0.tgz#2cd3cfea33ba3a89c9c121ec3347abe9ab125f77" - integrity sha1-LNPP6jO6OonJwSHsM0er6asSX3c= - dependencies: - speedometer "~0.1.2" - through2 "~0.2.3" - progress-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/progress-stream/-/progress-stream-2.0.0.tgz#fac63a0b3d11deacbb0969abcc93b214bce19ed5" @@ -21851,7 +21857,7 @@ raw-loader@^3.1.0: loader-utils "^1.1.0" schema-utils "^2.0.1" -rc@^1.0.1, rc@^1.1.6, rc@^1.2.1, rc@^1.2.7, rc@^1.2.8: +rc@^1.0.1, rc@^1.1.6, rc@^1.2.7, rc@^1.2.8: version "1.2.8" resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== @@ -21911,25 +21917,24 @@ react-dev-utils@^9.0.0: strip-ansi "5.2.0" text-table "0.2.0" -react-devtools-core@4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/react-devtools-core/-/react-devtools-core-4.4.0.tgz#614cabe5f3d6fb69730dc76da10f8fa4eb033695" - integrity sha512-ayyz+clbjekj5rqTjieI/eE0xGZkgotklVnxfa4Pyk9se5+AHUAhUwMhLvK5N2+mR2PGOZkv159RDTmvgs+wZQ== +react-devtools-core@4.8.2: + version "4.8.2" + resolved "https://registry.yarnpkg.com/react-devtools-core/-/react-devtools-core-4.8.2.tgz#4465f2e8de7795564aa20f28b2f3a9737586db23" + integrity sha512-3Lv3nI8FPAwKqUco35oOlgf+4j8mgYNnIcDv2QTfxEqg2G69q17ZJ8ScU9aBnymS28YC1OW+kTxLmdIQeTN8yg== dependencies: - es6-symbol "^3" shell-quote "^1.6.1" ws "^7" -react-devtools@^4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/react-devtools/-/react-devtools-4.4.0.tgz#03df973d889583bc75afdf3352d3bb29141b3f36" - integrity sha512-Wfa7re+BJGy8fyYodbOfjB1YILKspmfgXLSZ0nPaVvaCpfy2lEqy3Unz5bOzOWoFsDldg7wVL2Xy0LOjd3rz1A== +react-devtools@^4.8.0: + version "4.8.2" + resolved "https://registry.yarnpkg.com/react-devtools/-/react-devtools-4.8.2.tgz#24c2e1d4975ac087665ab48c925cd418a97797c2" + integrity sha512-NGANnExgSsd34IGJlKURCBtDG6Avi2LeWcqfLQ7/oG7khCT6Wm390ZM+GBnI1gGnZD7y/h7oWXd5B/Dp2s5s6Q== dependencies: cross-spawn "^5.0.1" - electron "^5.0.0" + electron "^9.1.0" ip "^1.1.4" - minimist "^1.2.0" - react-devtools-core "4.4.0" + minimist "^1.2.3" + react-devtools-core "4.8.2" update-notifier "^2.1.0" react-dnd-html5-backend@^7.4.4: @@ -22429,7 +22434,7 @@ read-pkg@^5.2.0: string_decoder "~1.0.3" util-deprecate "~1.0.1" -readable-stream@1.1, readable-stream@1.1.x, readable-stream@^1.0.33, readable-stream@~1.1.9: +readable-stream@1.1, readable-stream@1.1.x, readable-stream@^1.0.33: version "1.1.14" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" integrity sha1-fPTFTvZI44EwhMY23SB54WbAgdk= @@ -23054,7 +23059,7 @@ request-promise-native@^1.0.5: tunnel-agent "^0.6.0" uuid "^3.3.2" -request@^2.45.0, request@^2.81.0, request@^2.83.0: +request@^2.81.0, request@^2.83.0: version "2.87.0" resolved "https://registry.yarnpkg.com/request/-/request-2.87.0.tgz#32f00235cd08d482b4d0d68db93a829c0ed5756e" integrity sha512-fcogkm7Az5bsS6Sl0sibkbhcKsnyon/jV1kF3ajGmF0c8HrttdKTPRT9hieOaQHA5HEq6r8OyWOo/o781C1tNw== @@ -23349,6 +23354,18 @@ rn-host-detect@^1.1.5: resolved "https://registry.yarnpkg.com/rn-host-detect/-/rn-host-detect-1.1.5.tgz#fbecb982b73932f34529e97932b9a63e58d8deb6" integrity sha512-ufk2dFT3QeP9HyZ/xTuMtW27KnFy815CYitJMqQm+pgG3ZAtHBsrU8nXizNKkqXGy3bQmhEoloVbrfbvMJMqkg== +roarr@^2.15.3: + version "2.15.3" + resolved "https://registry.yarnpkg.com/roarr/-/roarr-2.15.3.tgz#65248a291a15af3ebfd767cbf7e44cb402d1d836" + integrity sha512-AEjYvmAhlyxOeB9OqPUzQCo3kuAkNfuDk/HqWbZdFsqDFpapkTjiw+p4svNEoRLvuqNTxqfL+s+gtD4eDgZ+CA== + dependencies: + boolean "^3.0.0" + detect-node "^2.0.4" + globalthis "^1.0.1" + json-stringify-safe "^5.0.1" + semver-compare "^1.0.0" + sprintf-js "^1.1.2" + rpc-cap@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/rpc-cap/-/rpc-cap-3.1.0.tgz#61ae8ca27c43da93f40972393ff34df1a28c3b5e" @@ -23507,6 +23524,13 @@ sanitize-filename@^1.6.1: dependencies: truncate-utf8-bytes "^1.0.0" +sanitize-filename@^1.6.2: + version "1.6.3" + resolved "https://registry.yarnpkg.com/sanitize-filename/-/sanitize-filename-1.6.3.tgz#755ebd752045931977e30b2025d340d7c9090378" + integrity sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg== + dependencies: + truncate-utf8-bytes "^1.0.0" + sass-graph@2.2.5: version "2.2.5" resolved "https://registry.yarnpkg.com/sass-graph/-/sass-graph-2.2.5.tgz#a981c87446b8319d96dce0671e487879bd24c2e8" @@ -23757,6 +23781,11 @@ semaphore@>=1.0.1, semaphore@^1.0.3, semaphore@^1.1.0: resolved "https://registry.yarnpkg.com/semaphore/-/semaphore-1.1.0.tgz#aaad8b86b20fe8e9b32b16dc2ee682a8cd26a8aa" integrity sha512-O4OZEaNtkMd/K0i6js9SL+gqy0ZCBMgUvlSqHKi4IBdjhe7wB8pwztUk1BbZ1fmrvpwFrPbHzqd2w5pTcJH6LA== +semver-compare@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" + integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w= + semver-diff@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36" @@ -23808,6 +23837,11 @@ semver@^6.0.0, semver@^6.1.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.1.1.tgz#53f53da9b30b2103cd4f15eab3a18ecbcb210c9b" integrity sha512-rWYq2e5iYW+fFe/oPPtYJxYgjBm8sC4rmoGdUOgBB7VnwKt6HrL793l2voH1UlsyYZpJ4g0wfjnTEO1s1NP2eQ== +semver@^7.3.2: + version "7.3.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" + integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== + semver@~5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" @@ -23832,6 +23866,13 @@ send@0.17.1: range-parser "~1.2.1" statuses "~1.5.0" +serialize-error@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-7.0.1.tgz#f1360b0447f61ffb483ec4157c737fab7d778e18" + integrity sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw== + dependencies: + type-fest "^0.13.1" + serialize-javascript@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-2.1.2.tgz#ecec53b0e0317bdc95ef76ab7074b7384785fa61" @@ -24162,13 +24203,6 @@ single-call-balance-checker-abi@^1.0.0: resolved "https://registry.yarnpkg.com/single-call-balance-checker-abi/-/single-call-balance-checker-abi-1.0.0.tgz#b369009fd4cc6214968cdba650ad93986315d92d" integrity sha512-T5fRBJHmGEMe76JFGB36gcZnOh1ip2S7Qsp7cwmwrfMRjadxTe02zJHtXERpnQf2yvSqNWRxvae5f6e8v4rhng== -single-line-log@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/single-line-log/-/single-line-log-1.1.2.tgz#c2f83f273a3e1a16edb0995661da0ed5ef033364" - integrity sha1-wvg/Jzo+GhbtsJlWYdoO1e8DM2Q= - dependencies: - string-width "^1.0.1" - sinon@^9.0.0: version "9.0.0" resolved "https://registry.yarnpkg.com/sinon/-/sinon-9.0.0.tgz#9f1ed502fa2e287e65220de08f6a44f33e314006" @@ -24562,11 +24596,6 @@ specificity@^0.4.1: resolved "https://registry.yarnpkg.com/specificity/-/specificity-0.4.1.tgz#aab5e645012db08ba182e151165738d00887b019" integrity sha512-1klA3Gi5PD1Wv9Q0wUoOQN1IWAuPu0D1U03ThXTr0cJ20+/iq2tHSDnK7Kk/0LXJ1ztUB2/1Os0wKmfyNgUQfg== -speedometer@~0.1.2: - version "0.1.4" - resolved "https://registry.yarnpkg.com/speedometer/-/speedometer-0.1.4.tgz#9876dbd2a169d3115402d48e6ea6329c8816a50d" - integrity sha1-mHbb0qFp0xFUAtSObqYynIgWpQ0= - speedometer@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/speedometer/-/speedometer-1.0.0.tgz#cd671cb06752c22bca3370e2f334440be4fc62e2" @@ -24607,7 +24636,7 @@ split@^1.0.1: dependencies: through "2" -sprintf-js@1.1.2: +sprintf-js@1.1.2, sprintf-js@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.2.tgz#da1765262bf8c0f571749f2ad6c26300207ae673" integrity sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug== @@ -25284,12 +25313,12 @@ sugarss@^2.0.0: dependencies: postcss "^7.0.2" -sumchecker@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/sumchecker/-/sumchecker-2.0.2.tgz#0f42c10e5d05da5d42eea3e56c3399a37d6c5b3e" - integrity sha1-D0LBDl0F2l1C7qPlbDOZo31sWz4= +sumchecker@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/sumchecker/-/sumchecker-3.0.1.tgz#6377e996795abb0b6d348e9b3e1dfb24345a8e42" + integrity sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg== dependencies: - debug "^2.2.0" + debug "^4.1.0" summary@0.3.x: version "0.3.2" @@ -25773,11 +25802,6 @@ throttle-debounce@^2.1.0: resolved "https://registry.yarnpkg.com/throttle-debounce/-/throttle-debounce-2.1.0.tgz#257e648f0a56bd9e54fe0f132c4ab8611df4e1d5" integrity sha512-AOvyNahXQuU7NN+VVvOOX+uW6FPaWdAOdRP5HfwYxAfCzXTFKRMoIMk+n+po318+ktcChx+F1Dd91G3YHeMKyg== -throttleit@0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-0.0.2.tgz#cfedf88e60c00dd9697b61fdd2a8343a9b680eaf" - integrity sha1-z+34jmDADdlpe2H90qg0OptoDq8= - throttleit@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-1.0.0.tgz#9e785836daf46743145a5984b6268d828528ac6c" @@ -25821,14 +25845,6 @@ through2@^2.0.2, through2@^2.0.5: readable-stream "~2.3.6" xtend "~4.0.1" -through2@~0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/through2/-/through2-0.2.3.tgz#eb3284da4ea311b6cc8ace3653748a52abf25a3f" - integrity sha1-6zKE2k6jEbbMis42U3SKUqvyWj8= - dependencies: - readable-stream "~1.1.9" - xtend "~2.1.1" - through@2, "through@>=2.2.7 <3", through@^2.3.6, through@^2.3.7, through@^2.3.8, through@~2.3, through@~2.3.1, through@~2.3.4, through@~2.3.8: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" @@ -26233,6 +26249,11 @@ tunnel-agent@^0.6.0: dependencies: safe-buffer "^5.0.1" +tunnel@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c" + integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg== + tweetnacl-util@^0.15.0: version "0.15.0" resolved "https://registry.yarnpkg.com/tweetnacl-util/-/tweetnacl-util-0.15.0.tgz#4576c1cee5e2d63d207fee52f1ba02819480bc75" @@ -26303,16 +26324,6 @@ type-is@^1.6.16, type-is@~1.6.17, type-is@~1.6.18: media-typer "0.3.0" mime-types "~2.1.24" -type@^1.0.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0" - integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg== - -type@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/type/-/type-2.0.0.tgz#5f16ff6ef2eb44f260494dae271033b29c09a9c3" - integrity sha512-KBt58xCHry4Cejnc2ISQAF7QY+ORngsWfxezO68+12hKV6lQY8P/psIkcbjeHWn7MqcgciWJyCCevFMJdIXpow== - typed-styles@^0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/typed-styles/-/typed-styles-0.0.7.tgz#93392a008794c4595119ff62dde6809dbc40a3d9" From 365a096e5b58307fe4c22b8c0e7c3ce796ba6227 Mon Sep 17 00:00:00 2001 From: Erik Marks Date: Thu, 6 Aug 2020 15:45:02 -0700 Subject: [PATCH 047/107] remove .network-name height --- ui/app/css/itcss/components/network.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/ui/app/css/itcss/components/network.scss b/ui/app/css/itcss/components/network.scss index ef8e7843ff86..31ca8c201654 100644 --- a/ui/app/css/itcss/components/network.scss +++ b/ui/app/css/itcss/components/network.scss @@ -74,7 +74,6 @@ white-space: nowrap; text-overflow: ellipsis; overflow: hidden; - height: 16px; } .dropdown-menu-item .fa.delete { From d0366ad8f21bdded8d0ede99d3bbb72bc06529ba Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Fri, 7 Aug 2020 12:33:03 -0230 Subject: [PATCH 048/107] Use luxon@1.24.1 (#9154) --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 0472e8e567d7..59b494bff86d 100644 --- a/package.json +++ b/package.json @@ -128,7 +128,7 @@ "jsonschema": "^1.2.4", "lodash": "^4.17.19", "loglevel": "^1.4.1", - "luxon": "^1.23.0", + "luxon": "^1.24.1", "metamask-logo": "^2.1.4", "multihashes": "^0.4.12", "nanoid": "^2.1.6", diff --git a/yarn.lock b/yarn.lock index 9938af5181ca..9d4efe99f625 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17590,10 +17590,10 @@ ltgt@~2.1.1: resolved "https://registry.yarnpkg.com/ltgt/-/ltgt-2.1.3.tgz#10851a06d9964b971178441c23c9e52698eece34" integrity sha1-EIUaBtmWS5cReEQcI8nlJpjuzjQ= -luxon@^1.23.0: - version "1.23.0" - resolved "https://registry.yarnpkg.com/luxon/-/luxon-1.23.0.tgz#23b748ad0f2d5494dc4d2878c19278c1e651410c" - integrity sha512-+6a/bXsCWrrR8vfbL41iM92es12zwV2Rum/KPkT+ubOZnnU3Sqbqok/FmD1xsWlWN2Y9Hu0fU/vNgU24ns7bpA== +luxon@^1.24.1: + version "1.24.1" + resolved "https://registry.yarnpkg.com/luxon/-/luxon-1.24.1.tgz#a8383266131ed4eaed4b5f430f96f3695403a52a" + integrity sha512-CgnIMKAWT0ghcuWFfCWBnWGOddM0zu6c4wZAWmD0NN7MZTnro0+833DF6tJep+xlxRPg4KtsYEHYLfTMBQKwYg== mafmt@^6.0.0, mafmt@^6.0.2, mafmt@^6.0.4, mafmt@^6.0.7: version "6.0.7" From 85e658993b6a6f0ccf459b644e191bb40754cdab Mon Sep 17 00:00:00 2001 From: Brad Decker Date: Fri, 7 Aug 2020 10:25:00 -0500 Subject: [PATCH 049/107] colocate notification-modal styles (#9147) Follows former examples of colocating styles with the modals that use them. --- ui/app/components/app/modals/index.scss | 1 + .../app/modals/notification-modal/index.js | 1 + .../app/modals/notification-modal/index.scss | 56 +++++++++++ .../notification-modal.js | 2 +- ui/app/css/itcss/components/modal.scss | 94 ------------------- 5 files changed, 59 insertions(+), 95 deletions(-) create mode 100644 ui/app/components/app/modals/notification-modal/index.js create mode 100644 ui/app/components/app/modals/notification-modal/index.scss rename ui/app/components/app/modals/{ => notification-modal}/notification-modal.js (97%) diff --git a/ui/app/components/app/modals/index.scss b/ui/app/components/app/modals/index.scss index ebb662b96fbe..c7f01b20b7dd 100644 --- a/ui/app/components/app/modals/index.scss +++ b/ui/app/components/app/modals/index.scss @@ -1,5 +1,6 @@ @import 'cancel-transaction/index'; @import 'confirm-remove-account/index'; +@import 'notification-modal/index'; @import 'qr-scanner/index'; @import 'transaction-confirmed/index'; @import 'metametrics-opt-in-modal/index'; diff --git a/ui/app/components/app/modals/notification-modal/index.js b/ui/app/components/app/modals/notification-modal/index.js new file mode 100644 index 000000000000..a2aab7c1d6b4 --- /dev/null +++ b/ui/app/components/app/modals/notification-modal/index.js @@ -0,0 +1 @@ +export { default } from './notification-modal' diff --git a/ui/app/components/app/modals/notification-modal/index.scss b/ui/app/components/app/modals/notification-modal/index.scss new file mode 100644 index 000000000000..93e085496967 --- /dev/null +++ b/ui/app/components/app/modals/notification-modal/index.scss @@ -0,0 +1,56 @@ +.notification-modal { + &__wrapper { + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: center; + position: relative; + border: 1px solid $alto; + box-shadow: 0 0 2px 2px $alto; + } + + &__header { + background: $wild-sand; + width: 100%; + display: flex; + justify-content: center; + padding: 30px; + font-size: 22px; + color: $nile-blue; + } + + &__message { + padding: 20px; + width: 100%; + display: flex; + justify-content: center; + font-size: 17px; + color: $nile-blue; + } + + &__buttons { + display: flex; + justify-content: space-evenly; + width: 100%; + margin-bottom: 24px; + padding: 0 42px; + + &__btn { + cursor: pointer; + } + } + + &__link { + color: $primary-blue; + } + + .modal-close-x::after { + content: '\00D7'; + font-size: 2em; + color: $dusty-gray; + position: absolute; + top: 25px; + right: 17.5px; + cursor: pointer; + } +} diff --git a/ui/app/components/app/modals/notification-modal.js b/ui/app/components/app/modals/notification-modal/notification-modal.js similarity index 97% rename from ui/app/components/app/modals/notification-modal.js rename to ui/app/components/app/modals/notification-modal/notification-modal.js index 7fe776934c68..71f826747afc 100644 --- a/ui/app/components/app/modals/notification-modal.js +++ b/ui/app/components/app/modals/notification-modal/notification-modal.js @@ -1,7 +1,7 @@ import PropTypes from 'prop-types' import React, { Component } from 'react' import { connect } from 'react-redux' -import { hideModal } from '../../../store/actions' +import { hideModal } from '../../../../store/actions' class NotificationModal extends Component { static contextProps = { diff --git a/ui/app/css/itcss/components/modal.scss b/ui/app/css/itcss/components/modal.scss index 318cfb1ee91c..7b63ffd9f9ee 100644 --- a/ui/app/css/itcss/components/modal.scss +++ b/ui/app/css/itcss/components/modal.scss @@ -212,16 +212,6 @@ padding: 9px 13px 8px; } -.modal-close-x::after { - content: '\00D7'; - font-size: 2em; - color: $dusty-gray; - position: absolute; - top: 25px; - right: 17.5px; - cursor: pointer; -} - // Hide token confirmation .hide-token-confirmation { @@ -286,55 +276,6 @@ } } -//Notification Modal - -.notification-modal { - &__wrapper { - display: flex; - flex-direction: column; - justify-content: flex-start; - align-items: center; - position: relative; - border: 1px solid $alto; - box-shadow: 0 0 2px 2px $alto; - } - - &__header { - background: $wild-sand; - width: 100%; - display: flex; - justify-content: center; - padding: 30px; - font-size: 22px; - color: $nile-blue; - } - - &__message { - padding: 20px; - width: 100%; - display: flex; - justify-content: center; - font-size: 17px; - color: $nile-blue; - } - - &__buttons { - display: flex; - justify-content: space-evenly; - width: 100%; - margin-bottom: 24px; - padding: 0 42px; - - &__btn { - cursor: pointer; - } - } - - &__link { - color: $primary-blue; - } -} - // Deposit Ether Modal .deposit-ether-modal { border-radius: 8px; @@ -498,38 +439,3 @@ text-align: center; } } - -//Notification Modal - -.notification-modal-wrapper { - display: flex; - flex-direction: column; - justify-content: flex-start; - align-items: center; - position: relative; - border: 1px solid $alto; - box-shadow: 0 0 2px 2px $alto; -} - -.notification-modal-header { - background: $wild-sand; - width: 100%; - display: flex; - justify-content: center; - align-items: center; - padding: 30px; - font-size: 22px; - color: $nile-blue; -} - -.notification-modal-message { - padding: 20px; -} - -.notification-modal-message { - width: 100%; - display: flex; - justify-content: center; - font-size: 17px; - color: $nile-blue; -} From f3ba18d79fb2bc833fb4ed166e906a87e1ece3b3 Mon Sep 17 00:00:00 2001 From: Brad Decker Date: Fri, 7 Aug 2020 10:58:48 -0500 Subject: [PATCH 050/107] remove unused section scss (#9146) --- test/e2e/ethereum-on.spec.js | 2 +- test/e2e/from-import-ui.spec.js | 6 +- test/e2e/incremental-security.spec.js | 2 +- test/e2e/metamask-ui.spec.js | 2 +- test/e2e/permissions.spec.js | 2 +- test/e2e/signature-request.spec.js | 2 +- ui/app/components/app/account-panel.js | 63 --- ui/app/components/app/index.scss | 2 + .../export-private-key-modal.component.js | 1 - ui/app/components/ui/qr-code/index.js | 1 + ui/app/components/ui/qr-code/index.scss | 25 + ui/app/components/ui/{ => qr-code}/qr-code.js | 17 +- ui/app/components/ui/readonly-input/index.js | 1 + .../components/ui/readonly-input/index.scss | 8 + .../ui/{ => readonly-input}/readonly-input.js | 5 +- ui/app/css/base-styles.scss | 22 + ui/app/css/itcss/components/index.scss | 1 - ui/app/css/itcss/components/modal.scss | 4 +- ui/app/css/itcss/components/sections.scss | 426 ------------------ ui/app/css/itcss/tools/utilities.scss | 8 - .../mobile-sync/mobile-sync.component.js | 1 - 21 files changed, 80 insertions(+), 521 deletions(-) delete mode 100644 ui/app/components/app/account-panel.js create mode 100644 ui/app/components/ui/qr-code/index.js create mode 100644 ui/app/components/ui/qr-code/index.scss rename ui/app/components/ui/{ => qr-code}/qr-code.js (79%) create mode 100644 ui/app/components/ui/readonly-input/index.js create mode 100644 ui/app/components/ui/readonly-input/index.scss rename ui/app/components/ui/{ => readonly-input}/readonly-input.js (78%) delete mode 100644 ui/app/css/itcss/components/sections.scss diff --git a/test/e2e/ethereum-on.spec.js b/test/e2e/ethereum-on.spec.js index e7412ac689cc..361579cd2c23 100644 --- a/test/e2e/ethereum-on.spec.js +++ b/test/e2e/ethereum-on.spec.js @@ -89,7 +89,7 @@ describe('MetaMask', function () { }) it('gets the current accounts address', async function () { - const addressInput = await driver.findElement(By.css('.qr-ellip-address')) + const addressInput = await driver.findElement(By.css('.readonly-input__input')) publicAddress = await addressInput.getAttribute('value') const accountModal = await driver.findElement(By.css('span .modal')) diff --git a/test/e2e/from-import-ui.spec.js b/test/e2e/from-import-ui.spec.js index 7a112c7221d3..78bdd03528f7 100644 --- a/test/e2e/from-import-ui.spec.js +++ b/test/e2e/from-import-ui.spec.js @@ -99,10 +99,10 @@ describe('Using MetaMask with an existing account', function () { it('shows the correct account address', async function () { await driver.clickElement(By.css('[data-testid="account-options-menu-button"]')) await driver.clickElement(By.css('[data-testid="account-options-menu__account-details"]')) - await driver.findVisibleElement(By.css('.qr-wrapper')) + await driver.findVisibleElement(By.css('.qr-code__wrapper')) await driver.delay(regularDelayMs) - const [address] = await driver.findElements(By.css('input.qr-ellip-address')) + const [address] = await driver.findElements(By.css('.readonly-input__input')) assert.equal(await address.getAttribute('value'), testAddress) await driver.clickElement(By.css('.account-modal-close')) @@ -112,7 +112,7 @@ describe('Using MetaMask with an existing account', function () { it('shows a QR code for the account', async function () { await driver.clickElement(By.css('[data-testid="account-options-menu-button"]')) await driver.clickElement(By.css('[data-testid="account-options-menu__account-details"]')) - await driver.findVisibleElement(By.css('.qr-wrapper')) + await driver.findVisibleElement(By.css('.qr-code__wrapper')) const detailModal = await driver.findElement(By.css('span .modal')) await driver.delay(regularDelayMs) diff --git a/test/e2e/incremental-security.spec.js b/test/e2e/incremental-security.spec.js index 82e3fc0e016a..a6482c58d90f 100644 --- a/test/e2e/incremental-security.spec.js +++ b/test/e2e/incremental-security.spec.js @@ -95,7 +95,7 @@ describe('MetaMask', function () { }) it('gets the current accounts address', async function () { - const addressInput = await driver.findElement(By.css('.qr-ellip-address')) + const addressInput = await driver.findElement(By.css('.readonly-input__input')) publicAddress = await addressInput.getAttribute('value') const accountModal = await driver.findElement(By.css('span .modal')) diff --git a/test/e2e/metamask-ui.spec.js b/test/e2e/metamask-ui.spec.js index 15d0198cd446..d501bdb3ac38 100644 --- a/test/e2e/metamask-ui.spec.js +++ b/test/e2e/metamask-ui.spec.js @@ -121,7 +121,7 @@ describe('MetaMask', function () { it('shows the QR code for the account', async function () { await driver.clickElement(By.css('[data-testid="account-options-menu-button"]')) await driver.clickElement(By.css('[data-testid="account-options-menu__account-details"]')) - await driver.findVisibleElement(By.css('.qr-wrapper')) + await driver.findVisibleElement(By.css('.qr-code__wrapper')) await driver.delay(regularDelayMs) const accountModal = await driver.findElement(By.css('span .modal')) diff --git a/test/e2e/permissions.spec.js b/test/e2e/permissions.spec.js index 8ec86b3cbceb..3d45e447784c 100644 --- a/test/e2e/permissions.spec.js +++ b/test/e2e/permissions.spec.js @@ -90,7 +90,7 @@ describe('MetaMask', function () { }) it('gets the current accounts address', async function () { - const addressInput = await driver.findElement(By.css('.qr-ellip-address')) + const addressInput = await driver.findElement(By.css('.readonly-input__input')) publicAddress = await addressInput.getAttribute('value') const accountModal = await driver.findElement(By.css('span .modal')) diff --git a/test/e2e/signature-request.spec.js b/test/e2e/signature-request.spec.js index 6e2a558ec597..9abac241b849 100644 --- a/test/e2e/signature-request.spec.js +++ b/test/e2e/signature-request.spec.js @@ -124,7 +124,7 @@ describe('MetaMask', function () { await driver.clickElement(By.css('[data-testid="account-options-menu__account-details"]')) await driver.delay(regularDelayMs) - const addressInput = await driver.findElement(By.css('.qr-ellip-address')) + const addressInput = await driver.findElement(By.css('.readonly-input__input')) const newPublicAddress = await addressInput.getAttribute('value') const accountModal = await driver.findElement(By.css('span .modal')) diff --git a/ui/app/components/app/account-panel.js b/ui/app/components/app/account-panel.js deleted file mode 100644 index 36517f474b95..000000000000 --- a/ui/app/components/app/account-panel.js +++ /dev/null @@ -1,63 +0,0 @@ -import PropTypes from 'prop-types' -import React, { Component } from 'react' -import Identicon from '../ui/identicon' -import { addressSummary, formatBalance } from '../../helpers/utils/util' - -export default class AccountPanel extends Component { - static propTypes = { - identity: PropTypes.object, - account: PropTypes.object, - isFauceting: PropTypes.bool, - } - - static defaultProps = { - identity: {}, - account: {}, - isFauceting: false, - } - - render () { - const { identity, account, isFauceting } = this.props - - const panelState = { - key: `accountPanel${identity.address}`, - identiconKey: identity.address, - identiconLabel: identity.name || '', - attributes: [ - { - key: 'ADDRESS', - value: addressSummary(identity.address), - }, - balanceOrFaucetingIndication(account, isFauceting), - ], - } - - return ( -
-
- - {panelState.identiconLabel.substring(0, 7) + '...'} -
-
- {panelState.attributes.map((attr, index) => ( -
- - {attr.value} -
- ))} -
-
- ) - } -} - -function balanceOrFaucetingIndication (account) { - return { - key: 'BALANCE', - value: formatBalance(account.balance), - } -} diff --git a/ui/app/components/app/index.scss b/ui/app/components/app/index.scss index 64136c6619e3..69ed2bb231f1 100644 --- a/ui/app/components/app/index.scss +++ b/ui/app/components/app/index.scss @@ -24,6 +24,8 @@ @import '../ui/page-container/index'; @import '../../pages/index'; @import 'permission-page-container/index'; +@import '../ui/qr-code/index'; +@import '../ui/readonly-input/index'; @import 'selected-account/index'; @import '../ui/sender-to-recipient/index'; @import '../ui/tabs/index'; diff --git a/ui/app/components/app/modals/export-private-key-modal/export-private-key-modal.component.js b/ui/app/components/app/modals/export-private-key-modal/export-private-key-modal.component.js index 695762b7329a..318dd0d5891b 100644 --- a/ui/app/components/app/modals/export-private-key-modal/export-private-key-modal.component.js +++ b/ui/app/components/app/modals/export-private-key-modal/export-private-key-modal.component.js @@ -146,7 +146,6 @@ export default class ExportPrivateKeyModal extends Component { {name}
diff --git a/ui/app/components/ui/qr-code/index.js b/ui/app/components/ui/qr-code/index.js new file mode 100644 index 000000000000..f638ae4fb393 --- /dev/null +++ b/ui/app/components/ui/qr-code/index.js @@ -0,0 +1 @@ +export { default } from './qr-code' diff --git a/ui/app/components/ui/qr-code/index.scss b/ui/app/components/ui/qr-code/index.scss new file mode 100644 index 000000000000..86bd5af4332a --- /dev/null +++ b/ui/app/components/ui/qr-code/index.scss @@ -0,0 +1,25 @@ +.qr-code { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + + &__message-container > div:first-child { + margin-top: 18px; + font-size: 15px; + color: #4d4d4d; + } + + &__message { + font-size: 12px; + color: #f7861c; + } + + &__error { + display: flex; + justify-content: center; + align-items: center; + color: #f7861c; + margin-bottom: 9px; + } +} diff --git a/ui/app/components/ui/qr-code.js b/ui/app/components/ui/qr-code/qr-code.js similarity index 79% rename from ui/app/components/ui/qr-code.js rename to ui/app/components/ui/qr-code/qr-code.js index 267277c011b0..0ddf6caa6783 100644 --- a/ui/app/components/ui/qr-code.js +++ b/ui/app/components/ui/qr-code/qr-code.js @@ -3,8 +3,8 @@ import React from 'react' import qrCode from 'qrcode-generator' import { connect } from 'react-redux' import { isHexPrefixed } from 'ethereumjs-util' -import ReadOnlyInput from './readonly-input' -import { checksumAddress } from '../../helpers/utils/util' +import ReadOnlyInput from '../readonly-input/readonly-input' +import { checksumAddress } from '../../../helpers/utils/util' export default connect(mapStateToProps)(QrCodeView) @@ -24,20 +24,20 @@ function QrCodeView (props) { qrImage.make() return ( -
+
{ Array.isArray(message) ? ( -
+
{props.Qr.message.map((message, index) => ( -
+
{message}
))}
) : message && ( -
+
{message}
) @@ -45,21 +45,20 @@ function QrCodeView (props) { { props.warning ? (props.warning && ( - + {props.warning} )) : null }
diff --git a/ui/app/components/ui/readonly-input/index.js b/ui/app/components/ui/readonly-input/index.js new file mode 100644 index 000000000000..151a02ee8770 --- /dev/null +++ b/ui/app/components/ui/readonly-input/index.js @@ -0,0 +1 @@ +export { default } from './readonly-input' diff --git a/ui/app/components/ui/readonly-input/index.scss b/ui/app/components/ui/readonly-input/index.scss new file mode 100644 index 000000000000..9eff60bbb161 --- /dev/null +++ b/ui/app/components/ui/readonly-input/index.scss @@ -0,0 +1,8 @@ +.readonly-input { + &__input { + direction: ltr; + overflow: hidden; + text-overflow: ellipsis; + width: 100%; + } +} diff --git a/ui/app/components/ui/readonly-input.js b/ui/app/components/ui/readonly-input/readonly-input.js similarity index 78% rename from ui/app/components/ui/readonly-input.js rename to ui/app/components/ui/readonly-input/readonly-input.js index b884da1bcb1c..eba6fd6c2e1c 100644 --- a/ui/app/components/ui/readonly-input.js +++ b/ui/app/components/ui/readonly-input/readonly-input.js @@ -1,5 +1,6 @@ import PropTypes from 'prop-types' import React from 'react' +import classnames from 'classnames' export default function ReadOnlyInput (props) { const { @@ -13,9 +14,9 @@ export default function ReadOnlyInput (props) { const InputType = textarea ? 'textarea' : 'input' return ( -
+
event.target.select()} diff --git a/ui/app/css/base-styles.scss b/ui/app/css/base-styles.scss index 5445d2f34f75..8b2220f9c6aa 100644 --- a/ui/app/css/base-styles.scss +++ b/ui/app/css/base-styles.scss @@ -29,6 +29,28 @@ html { } } +/* + This error class is used in the following files still: + /ui/app/pages/create-account/connect-hardware/index.js + /ui/app/pages/create-account/import-account/json.js + /ui/app/pages/create-account/import-account/private-key.js + /ui/app/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js + /ui/app/pages/keychains/restore-vault.js +*/ +.error { + color: #f7861c; + margin-bottom: 9px; +} + +/* + This warning class is used in the following files still: + /ui/app/pages/create-account/import-account/json.js + /ui/app/pages/confirm-add-suggested-token/confirm-add-suggested-token.component.js +*/ +.warning { + color: #ffae00; +} + /* stylelint-disable */ #app-content { overflow-x: hidden; diff --git a/ui/app/css/itcss/components/index.scss b/ui/app/css/itcss/components/index.scss index e26483b32064..1936706dcb72 100644 --- a/ui/app/css/itcss/components/index.scss +++ b/ui/app/css/itcss/components/index.scss @@ -12,7 +12,6 @@ @import './loading-overlay'; // Tx List and Sections -@import './sections'; @import './menu'; @import './gas-slider'; @import './tab-bar'; diff --git a/ui/app/css/itcss/components/modal.scss b/ui/app/css/itcss/components/modal.scss index 7b63ffd9f9ee..8d47c753e9df 100644 --- a/ui/app/css/itcss/components/modal.scss +++ b/ui/app/css/itcss/components/modal.scss @@ -85,12 +85,12 @@ // Account Details Modal .account-modal-container { - .qr-header { + .qr-code__header { margin-top: 9px; font-size: 20px; } - .qr-wrapper { + .qr-code__wrapper { margin-top: 5px; } diff --git a/ui/app/css/itcss/components/sections.scss b/ui/app/css/itcss/components/sections.scss deleted file mode 100644 index 4ddda782a883..000000000000 --- a/ui/app/css/itcss/components/sections.scss +++ /dev/null @@ -1,426 +0,0 @@ -// Old scss, do not lint - clean up later -/* stylelint-disable */ - - -/* -App Sections - TODO: Move into separate files. -*/ - -/* initialize */ -textarea.twelve-word-phrase { - padding: 12px; - width: 300px; - height: 140px; - font-size: 16px; - background: $white; - resize: none; -} - -.initialize-screen { - width: 100%; - z-index: $main-container-z-index; - background: #f7f7f7; -} - -.initialize-screen hr { - width: 60px; - margin: 12px; - border-color: #f7861c; - border-style: solid; -} - -.initialize-screen label { - margin-top: 20px; -} - -.initialize-screen button.create-vault { - margin-top: 40px; -} - -.initialize-screen .warning { - font-size: 14px; - margin: 0 16px; -} - -/* unlock */ -.error { - color: #f7861c; - margin-bottom: 9px; -} - -.warning { - color: #ffae00; -} - -.lock { - width: 50px; - height: 50px; -} - -.lock.locked { - transform: scale(1.5); - opacity: 0; - transition: opacity 400ms ease-in, transform 400ms ease-in; -} - -.lock.unlocked { - transform: scale(1); - opacity: 1; - transition: opacity 500ms ease-out, transform 500ms ease-out, background 200ms ease-in; -} - -.lock.locked .lock-top { - transform: scaleX(1) translateX(0); - transition: transform 250ms ease-in; -} - -.lock.unlocked .lock-top { - transform: scaleX(-1) translateX(-12px); - transition: transform 250ms ease-in; -} - -.lock.unlocked:hover { - border-radius: 4px; - background: #e5e5e5; - border: 1px solid #b1b1b1; -} - -.lock.unlocked:active { - background: #c3c3c3; -} - -.section-title .fa-arrow-left { - margin: -2px 8px 0 -8px; -} - -.sizing-input { - font-size: 14px; - height: 30px; - padding-left: 5px; -} - -/* accounts */ - -.accounts-section { - margin: 0 0; -} - -.accounts-section .horizontal-line { - margin: 0 18px; -} - -.accounts-list-option { - height: 120px; -} - -.accounts-list-option .identicon-wrapper { - width: 100px; -} - -.unconftx-link { - margin-top: 24px; - cursor: pointer; -} - -.unconftx-link .fa-arrow-right { - margin: 0 -8px 0 8px; -} - -/* identity panel */ - -.identity-panel { - font-weight: 500; -} - -.identity-panel .identicon-wrapper { - margin: 4px; - margin-top: 8px; - display: flex; - align-items: center; -} - -.identity-panel .identicon-wrapper span { - margin: 0 auto; -} - -.identity-panel .identity-data { - margin: 8px 8px 8px 18px; -} - -.identity-panel i { - margin-top: 32px; - margin-right: 6px; - color: #b9b9b9; -} - -.identity-panel .arrow-right { - padding-left: 18px; - width: 42px; - min-width: 18px; - height: 100%; -} - -.identity-copy.flex-column { - flex: 0.25 0 auto; - justify-content: center; -} - -/* accounts screen */ - -.identity-section { -} - -.identity-section .identity-panel { - background: #e9e9e9; - border-bottom: 1px solid #b1b1b1; - cursor: pointer; -} - -.identity-section .identity-panel.selected { - background: $white; - color: #f3c83e; -} - -.identity-section .identity-panel.selected .identicon { - border-color: $orange; -} - -.identity-section .accounts-list-option:hover, -.identity-section .accounts-list-option.selected { - background: $white; -} - -/* account detail screen */ - -.account-detail-section { - display: flex; - flex-wrap: wrap; - overflow-y: auto; - flex-direction: inherit; -} - -.grow-tenx { - flex-grow: 10; -} - -.name-label { -} - -.unapproved-tx-icon { - height: 16px; - width: 16px; - background: rgb(47, 174, 244); - border-color: $silver-chalice; - border-radius: 13px; -} - -.edit-text { - height: 100%; - visibility: hidden; -} - -.editing-label { - display: flex; - justify-content: flex-start; - margin-left: 50px; - margin-bottom: 2px; - font-size: 11px; - text-rendering: geometricPrecision; - color: #f7861c; -} - -.name-label:hover .edit-text { - visibility: visible; -} - -/* tx confirm */ - -.unconftx-section input[type=password] { - height: 22px; - padding: 2px; - margin: 12px; - margin-bottom: 24px; - border-radius: 4px; - border: 2px solid #f3c83e; - background: #faf6f0; -} - -/* Info screen */ -.info-gray { - text-transform: uppercase; - color: $silver-chalice; -} - -.icon-size { - width: 20px; -} - -.info { - padding-bottom: 10px; - display: inline-block; - padding-left: 5px; -} - -/* buy eth warning screen */ -.custom-radios { - justify-content: space-around; - align-items: center; -} - -.custom-radio-selected { - width: 17px; - height: 17px; - border: solid; - border-style: double; - border-radius: 15px; - border-width: 5px; - background: rgba(247, 134, 28, 1); - border-color: #f7f7f7; -} - -.custom-radio-inactive { - width: 14px; - height: 14px; - border: solid; - border-width: 1px; - border-radius: 24px; - border-color: $silver-chalice; -} - -.radio-titles { - color: rgba(247, 134, 28, 1); -} - -.eth-warning { - transition: opacity 400ms ease-in, transform 400ms ease-in; -} - -.buy-subview { - transition: opacity 400ms ease-in, transform 400ms ease-in; -} - -.input-container:hover .edit-text { - visibility: visible; -} - -.buy-inputs { - font-size: 13px; - height: 20px; - background: transparent; - box-sizing: border-box; - border: solid; - border-color: transparent; - border-width: 0.5px; - border-radius: 2px; -} - -.input-container:hover .buy-inputs { - box-sizing: inherit; - border: solid; - border-color: #f7861c; - border-width: 0.5px; - border-radius: 2px; -} - -.buy-inputs:focus { - border: solid; - border-color: #f7861c; - border-width: 0.5px; - border-radius: 2px; -} - -.activeForm { - background: #f7f7f7; - border: none; - border-radius: 8px 8px 0 0; - width: 50%; - text-align: center; - padding-bottom: 4px; -} - -.inactiveForm { - border: none; - border-radius: 8px 8px 0 0; - width: 50%; - text-align: center; - padding-bottom: 4px; -} - -.ex-coins { - text-transform: uppercase; - text-align: center; - font-size: 33px; - width: 118px; - height: 42px; - padding: 1px; - color: #4d4d4d; -} - -.marketinfo { - color: $silver-chalice; - font-size: 15px; - line-height: 17px; -} - -#fromCoin::-webkit-calendar-picker-indicator { - display: none; -} - -#coinList { - width: 400px; - height: 500px; - overflow: scroll; -} - -.icon-control .fa-sync { - visibility: hidden; -} - -.icon-control:hover .fa-sync { - visibility: visible; -} - -.icon-control:hover .fa-chevron-right { - visibility: hidden; -} - -.inactive { - color: $silver-chalice; -} - -.inactive button { - background: $silver-chalice; - color: $white; -} - -.qr-ellip-address, -.ellip-address { - /*rtl:ignore*/ - direction: ltr; - overflow: hidden; - text-overflow: ellipsis; - width: 100%; -} - -.qr-header { - font-size: 25px; - margin-top: 40px; -} - -.qr-message { - font-size: 12px; - color: #f7861c; -} - -div.message-container > div:first-child { - margin-top: 18px; - font-size: 15px; - color: #4d4d4d; -} - -.pop-hover:hover { - transform: scale(1.1); -} - -/* stylelint-enable */ diff --git a/ui/app/css/itcss/tools/utilities.scss b/ui/app/css/itcss/tools/utilities.scss index 1d0d1c463e75..aa80ab8673dc 100644 --- a/ui/app/css/itcss/tools/utilities.scss +++ b/ui/app/css/itcss/tools/utilities.scss @@ -112,14 +112,6 @@ z-index: 1; } -.select-none { - cursor: inherit; - -moz-user-select: none; - -webkit-user-select: none; - -ms-user-select: none; - user-select: none; -} - .pointer { cursor: pointer; } diff --git a/ui/app/pages/mobile-sync/mobile-sync.component.js b/ui/app/pages/mobile-sync/mobile-sync.component.js index d93e64c0d3af..e0da0074c428 100644 --- a/ui/app/pages/mobile-sync/mobile-sync.component.js +++ b/ui/app/pages/mobile-sync/mobile-sync.component.js @@ -369,7 +369,6 @@ export default class MobileSyncPage extends Component { {t('syncWithMobileScanThisCode')}
Date: Fri, 7 Aug 2020 13:45:49 -0300 Subject: [PATCH 051/107] Change MetaMetrics category for background events (#9155) Background events are now sent in the `Background` category, rather than `backend`. Conventionally we use the term "background" over "backend", as it's not really a "backend" in the normal sense since it's a client background process. Also it's capitalized because all of the other event categories are capitalized as well. The metrics URL has been updated to use `background` instead of `backend` as well, for consistency. Luckily we don't have to worry about our metrics being disjointed due to this name change, because the background metrics never worked to begin with! So there will be none under the old name. The metrics will be made functional in a separate PR. --- app/scripts/lib/backend-metametrics.js | 2 +- app/scripts/metamask-controller.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/scripts/lib/backend-metametrics.js b/app/scripts/lib/backend-metametrics.js index baaf1c6661e3..a035b9d91ad8 100644 --- a/app/scripts/lib/backend-metametrics.js +++ b/app/scripts/lib/backend-metametrics.js @@ -14,7 +14,7 @@ export default function backEndMetaMetricsEvent (metaMaskState, eventData) { sendMetaMetricsEvent({ ...stateEventData, ...eventData, - url: METAMETRICS_TRACKING_URL + '/backend', + url: METAMETRICS_TRACKING_URL + '/background', }) } } diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 41a2d8fb31bf..8008cb77946a 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -257,7 +257,7 @@ export default class MetamaskController extends EventEmitter { errorMessage: txMeta.simulationFails?.reason, }, eventOpts: { - category: 'backend', + category: 'Background', action: 'Transactions', name: 'On Chain Failure', }, From 8713927e5e08e45f7653f32af7ea1cb08ef2a2be Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Fri, 7 Aug 2020 14:57:27 -0300 Subject: [PATCH 052/107] Remove `url` parameter from `metricsEvent` (#9157) * Remove `url` parameter from `metricsEvent` The `url` parameter was used to override the `currentPath`, but it never worked correctly. It was supposed to be used for setting the `url` query parameter that was sent to Matomo, but `currentPath` was always used even if it `url` was set and `currentPath` was empty. Instead, `currentPath` is now always used. There was never a need to provide an "override" for `currentPath` when it can be set directly. The metrics provider does set `currentPath` automatically by default, but this can be overwritten already by passing a second parameter to `metricsEvent`. There were two places this `url` parameter was being used: background events, and path changes. Background events were submitted with no `currentPath`, so because of the bug with the `url` parameter, the metrics utility would crash upon each event. So those were never actually sent. This commit will fix that crash. The `currentPath` parameter was supplied as an empty string for the path change events, so those never crashed. They just had the `url` query string parameter set incorrectly (to an empty string). It should now be correctly populated, which should mean we'll be capturing all path changes now. Previously we were only capturing path changes to pages that happened to include an event, because of this blank `url` problem. * Use `url` query parameter as fallback for generating `pv_id` The `pv_id` parameter currently isn't generated correctly on Firefox, as the generation assumes that the current URL starts with `chrome-extension://`. The `url` query parameter is still unique for each path, so it's probably good enough for generating an id for each page. This is just a temporary fix; it will be removed in a future PR, where Firefox will be properly supported. --- app/scripts/lib/backend-metametrics.js | 2 +- ui/app/helpers/utils/metametrics.util.js | 8 +++----- ui/app/pages/routes/routes.component.js | 6 +++--- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/app/scripts/lib/backend-metametrics.js b/app/scripts/lib/backend-metametrics.js index a035b9d91ad8..9328fa3af77d 100644 --- a/app/scripts/lib/backend-metametrics.js +++ b/app/scripts/lib/backend-metametrics.js @@ -14,7 +14,7 @@ export default function backEndMetaMetricsEvent (metaMaskState, eventData) { sendMetaMetricsEvent({ ...stateEventData, ...eventData, - url: METAMETRICS_TRACKING_URL + '/background', + currentPath: METAMETRICS_TRACKING_URL + '/background', }) } } diff --git a/ui/app/helpers/utils/metametrics.util.js b/ui/app/helpers/utils/metametrics.util.js index 98b83f3d8aec..3cba4f21518b 100644 --- a/ui/app/helpers/utils/metametrics.util.js +++ b/ui/app/helpers/utils/metametrics.util.js @@ -13,7 +13,7 @@ const METAMETRICS_BASE_URL = 'https://chromeextensionmm.innocraft.cloud/piwik.ph const METAMETRICS_REQUIRED_PARAMS = `?idsite=${projectId}&rec=1&apiv=1` const METAMETRICS_BASE_FULL = METAMETRICS_BASE_URL + METAMETRICS_REQUIRED_PARAMS -const METAMETRICS_TRACKING_URL = inDevelopment +export const METAMETRICS_TRACKING_URL = inDevelopment ? 'http://www.metamask.io/metametrics' : 'http://www.metamask.io/metametrics-prod' @@ -119,7 +119,6 @@ function composeParamAddition (paramValue, paramName) { * @property {string} config.currentPath The location path the user is on at the time of the event * @property {string} config.metaMetricsId A random id assigned to a user at the time of opting in to metametrics. A hexadecimal number * @property {string} config.confirmTransactionOrigin The origin on a transaction - * @property {string} config.url The url to track an event at. Overrides `currentPath` * @property {boolean} config.excludeMetaMetricsId Whether or not the tracked event data should be associated with a metametrics id * @property {boolean} config.isNewVisit Whether or not the event should be tracked as a new visit/user sessions * @returns {string} - Returns a url to be passed to fetch to make the appropriate request to matomo. @@ -141,7 +140,6 @@ function composeUrl (config) { currentPath, metaMetricsId, confirmTransactionOrigin, - url: configUrl, excludeMetaMetricsId, isNewVisit, } = config @@ -167,10 +165,10 @@ function composeUrl (config) { numberOfTokens: (customVariables && customVariables.numberOfTokens) || numberOfTokens, numberOfAccounts: (customVariables && customVariables.numberOfAccounts) || numberOfAccounts, }) : '' - const url = configUrl || currentPath ? `&url=${encodeURIComponent(currentPath.replace(/chrome-extension:\/\/\w+/, METAMETRICS_TRACKING_URL))}` : '' + const url = currentPath ? `&url=${encodeURIComponent(currentPath.replace(/chrome-extension:\/\/\w+/, METAMETRICS_TRACKING_URL))}` : '' const _id = metaMetricsId && !excludeMetaMetricsId ? `&_id=${metaMetricsId.slice(2, 18)}` : '' const rand = `&rand=${String(Math.random()).slice(2)}` - const pv_id = ((url || currentPath) && `&pv_id=${ethUtil.bufferToHex(ethUtil.sha3(url || currentPath.match(/chrome-extension:\/\/\w+\/(.+)/)[0])).slice(2, 8)}`) || '' + const pv_id = currentPath ? `&pv_id=${ethUtil.bufferToHex(ethUtil.sha3(currentPath.match(/chrome-extension:\/\/\w+\/(.+)/)?.[0] || url)).slice(2, 8)}` : '' const uid = metaMetricsId && !excludeMetaMetricsId ? `&uid=${metaMetricsId.slice(2, 18)}` : excludeMetaMetricsId diff --git a/ui/app/pages/routes/routes.component.js b/ui/app/pages/routes/routes.component.js index 1dc556a98288..1aca07f07175 100644 --- a/ui/app/pages/routes/routes.component.js +++ b/ui/app/pages/routes/routes.component.js @@ -52,6 +52,8 @@ import { UNLOCK_ROUTE, } from '../../helpers/constants/routes' +import { METAMETRICS_TRACKING_URL } from '../../helpers/utils/metametrics.util' + import { ENVIRONMENT_TYPE_NOTIFICATION, ENVIRONMENT_TYPE_POPUP } from '../../../../app/scripts/lib/enums' import { getEnvironmentType } from '../../../../app/scripts/lib/util' @@ -98,11 +100,9 @@ export default class Routes extends Component { this.props.history.listen((locationObj, action) => { if (action === 'PUSH') { pageChanged(locationObj.pathname) - const url = `&url=${encodeURIComponent('http://www.metamask.io/metametrics' + locationObj.pathname)}` this.context.metricsEvent({}, { - currentPath: '', + currentPath: `${METAMETRICS_TRACKING_URL}${locationObj.pathname}`, pathname: locationObj.pathname, - url, pageOpts: { hideDimensions: true, }, From 1419c14fb6a29e7461757f102d6a4af4d70ad286 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Fri, 7 Aug 2020 15:32:46 -0300 Subject: [PATCH 053/107] Use `pathname` instead of URL for `currentPath` metrics parameter (#9158) The `currentPath` parameter passed to our metrics utility had been passed the full URL rather than just the path, contrary to what the name would imply. We only used the path portion, so passing the full URL did lead to complications. Now just the `pathname` is passed in, rather than the full URL. This simplifies the metrics logic, and it incidentally fixes two bugs. The main bug fixed is regarding Firefox metrics. Previously we had assumed the `currentPath` would start with `chrome-extension://`, which of course was not true on Firefox. This lead to us incorrectly parsing the `currentPath`, so path tracking was broken for Firefox events. This broken parsing is now bypassed entirely, so metrics should now work the same on Firefox as on Chrome. The second bug was that we were incorrectly setting the tracking URL for background events during tests. As a result, we were incorrectly detecting ourselves as an internal site that had referred the user to us. But this was not of major concern, since it only affected test metrics (which get sent to the development Matomo project). Lastly, this change let us discard the `pathname` parameter used in the `overrides` parameter of the `metricsEvent` function. Now that `currentPath` is equivalent to `pathname`, the `pathname` parameter is redundant. --- app/scripts/lib/backend-metametrics.js | 8 +------- ui/app/contexts/metametrics.js | 8 ++++---- ui/app/helpers/utils/metametrics.util.js | 12 ++++++------ ui/app/pages/routes/routes.component.js | 5 +---- 4 files changed, 12 insertions(+), 21 deletions(-) diff --git a/app/scripts/lib/backend-metametrics.js b/app/scripts/lib/backend-metametrics.js index 9328fa3af77d..e62f09d0de07 100644 --- a/app/scripts/lib/backend-metametrics.js +++ b/app/scripts/lib/backend-metametrics.js @@ -1,12 +1,6 @@ import { getBackgroundMetaMetricState } from '../../../ui/app/selectors' import { sendMetaMetricsEvent } from '../../../ui/app/helpers/utils/metametrics.util' -const inDevelopment = process.env.NODE_ENV === 'development' - -const METAMETRICS_TRACKING_URL = inDevelopment - ? 'http://www.metamask.io/metametrics' - : 'http://www.metamask.io/metametrics-prod' - export default function backEndMetaMetricsEvent (metaMaskState, eventData) { const stateEventData = getBackgroundMetaMetricState({ metamask: metaMaskState }) @@ -14,7 +8,7 @@ export default function backEndMetaMetricsEvent (metaMaskState, eventData) { sendMetaMetricsEvent({ ...stateEventData, ...eventData, - currentPath: METAMETRICS_TRACKING_URL + '/background', + currentPath: '/background', }) } } diff --git a/ui/app/contexts/metametrics.js b/ui/app/contexts/metametrics.js index ced6d4944d4b..b1440429edd1 100644 --- a/ui/app/contexts/metametrics.js +++ b/ui/app/contexts/metametrics.js @@ -41,7 +41,7 @@ export function MetaMetricsProvider ({ children }) { const numberOfAccounts = useSelector(getNumberOfAccounts) const history = useHistory() const [state, setState] = useState(() => ({ - currentPath: window.location.href, + currentPath: (new URL(window.location.href)).pathname, previousPath: '', })) @@ -49,7 +49,7 @@ export function MetaMetricsProvider ({ children }) { useEffect(() => { const unlisten = history.listen(() => setState((prevState) => ({ - currentPath: window.location.href, + currentPath: (new URL(window.location.href)).pathname, previousPath: prevState.currentPath, }))) // remove this listener if the component is no longer mounted @@ -59,8 +59,8 @@ export function MetaMetricsProvider ({ children }) { const metricsEvent = useCallback((config = {}, overrides = {}) => { const { eventOpts = {} } = config const { name = '' } = eventOpts - const { pathname: overRidePathName = '' } = overrides - const isSendFlow = Boolean(name.match(/^send|^confirm/) || overRidePathName.match(/send|confirm/)) + const { currentPath: overrideCurrentPath = '' } = overrides + const isSendFlow = Boolean(name.match(/^send|^confirm/) || overrideCurrentPath.match(/send|confirm/)) if (participateInMetaMetrics || config.isOptIn) { return sendMetaMetricsEvent({ diff --git a/ui/app/helpers/utils/metametrics.util.js b/ui/app/helpers/utils/metametrics.util.js index 3cba4f21518b..d2563c7fcf43 100644 --- a/ui/app/helpers/utils/metametrics.util.js +++ b/ui/app/helpers/utils/metametrics.util.js @@ -13,7 +13,7 @@ const METAMETRICS_BASE_URL = 'https://chromeextensionmm.innocraft.cloud/piwik.ph const METAMETRICS_REQUIRED_PARAMS = `?idsite=${projectId}&rec=1&apiv=1` const METAMETRICS_BASE_FULL = METAMETRICS_BASE_URL + METAMETRICS_REQUIRED_PARAMS -export const METAMETRICS_TRACKING_URL = inDevelopment +const METAMETRICS_TRACKING_URL = inDevelopment ? 'http://www.metamask.io/metametrics' : 'http://www.metamask.io/metametrics-prod' @@ -74,7 +74,7 @@ const customDimensionsNameIdMap = { function composeUrlRefParamAddition (previousPath, confirmTransactionOrigin) { const externalOrigin = confirmTransactionOrigin && confirmTransactionOrigin !== 'metamask' - return `&urlref=${externalOrigin ? 'EXTERNAL' : encodeURIComponent(previousPath.replace(/chrome-extension:\/\/\w+/, METAMETRICS_TRACKING_URL))}` + return `&urlref=${externalOrigin ? 'EXTERNAL' : encodeURIComponent(`${METAMETRICS_TRACKING_URL}${previousPath}`)}` } // composes query params of the form &dimension[0-999]=[value] @@ -115,8 +115,8 @@ function composeParamAddition (paramValue, paramName) { * @property {string} config.accountType The account type being used at the time of the event: 'hardware', 'imported' or 'default' * @property {number} config.numberOfTokens The number of tokens that the user has added at the time of the event * @property {number} config.numberOfAccounts The number of accounts the user has added at the time of the event - * @property {string} config.previousPath The location path the user was on prior to the path they are on at the time of the event - * @property {string} config.currentPath The location path the user is on at the time of the event + * @property {string} config.previousPath The pathname of the URL the user was on prior to the URL they are on at the time of the event + * @property {string} config.currentPath The pathname of the URL the user is on at the time of the event * @property {string} config.metaMetricsId A random id assigned to a user at the time of opting in to metametrics. A hexadecimal number * @property {string} config.confirmTransactionOrigin The origin on a transaction * @property {boolean} config.excludeMetaMetricsId Whether or not the tracked event data should be associated with a metametrics id @@ -165,10 +165,10 @@ function composeUrl (config) { numberOfTokens: (customVariables && customVariables.numberOfTokens) || numberOfTokens, numberOfAccounts: (customVariables && customVariables.numberOfAccounts) || numberOfAccounts, }) : '' - const url = currentPath ? `&url=${encodeURIComponent(currentPath.replace(/chrome-extension:\/\/\w+/, METAMETRICS_TRACKING_URL))}` : '' + const url = currentPath ? `&url=${encodeURIComponent(`${METAMETRICS_TRACKING_URL}${currentPath}`)}` : '' const _id = metaMetricsId && !excludeMetaMetricsId ? `&_id=${metaMetricsId.slice(2, 18)}` : '' const rand = `&rand=${String(Math.random()).slice(2)}` - const pv_id = currentPath ? `&pv_id=${ethUtil.bufferToHex(ethUtil.sha3(currentPath.match(/chrome-extension:\/\/\w+\/(.+)/)?.[0] || url)).slice(2, 8)}` : '' + const pv_id = currentPath ? `&pv_id=${ethUtil.bufferToHex(ethUtil.sha3(currentPath)).slice(2, 8)}` : '' const uid = metaMetricsId && !excludeMetaMetricsId ? `&uid=${metaMetricsId.slice(2, 18)}` : excludeMetaMetricsId diff --git a/ui/app/pages/routes/routes.component.js b/ui/app/pages/routes/routes.component.js index 1aca07f07175..578483425c34 100644 --- a/ui/app/pages/routes/routes.component.js +++ b/ui/app/pages/routes/routes.component.js @@ -52,8 +52,6 @@ import { UNLOCK_ROUTE, } from '../../helpers/constants/routes' -import { METAMETRICS_TRACKING_URL } from '../../helpers/utils/metametrics.util' - import { ENVIRONMENT_TYPE_NOTIFICATION, ENVIRONMENT_TYPE_POPUP } from '../../../../app/scripts/lib/enums' import { getEnvironmentType } from '../../../../app/scripts/lib/util' @@ -101,8 +99,7 @@ export default class Routes extends Component { if (action === 'PUSH') { pageChanged(locationObj.pathname) this.context.metricsEvent({}, { - currentPath: `${METAMETRICS_TRACKING_URL}${locationObj.pathname}`, - pathname: locationObj.pathname, + currentPath: locationObj.pathname, pageOpts: { hideDimensions: true, }, From d59fc79e0febb2306a4681077641f8680922e8fd Mon Sep 17 00:00:00 2001 From: Brad Decker Date: Fri, 7 Aug 2020 13:57:33 -0500 Subject: [PATCH 054/107] colocate deposit-ether-modal styles (#9148) Moves the still alive deposit-ether-modal styles to be colocated with it's component, as per the examples already in place in the components/modal folder --- .../app/modals/deposit-ether-modal/index.scss | 162 +++++++++++++++++ ui/app/components/app/modals/index.scss | 1 + ui/app/css/itcss/components/modal.scss | 164 ------------------ 3 files changed, 163 insertions(+), 164 deletions(-) create mode 100644 ui/app/components/app/modals/deposit-ether-modal/index.scss diff --git a/ui/app/components/app/modals/deposit-ether-modal/index.scss b/ui/app/components/app/modals/deposit-ether-modal/index.scss new file mode 100644 index 000000000000..c52e4e5637ca --- /dev/null +++ b/ui/app/components/app/modals/deposit-ether-modal/index.scss @@ -0,0 +1,162 @@ +.deposit-ether-modal { + border-radius: 8px; + display: flex; + flex-flow: column; + height: 100%; + + &__header { + width: 100%; + border-radius: 8px 8px 0 0; + background-color: $mid-gray; + display: flex; + position: relative; + padding: 25px; + flex-flow: column; + align-items: flex-start; + + &__title { + color: $white; + font-size: 24px; + line-height: 32px; + } + + &__description { + color: $white; + font-size: 16px; + line-height: 22px; + margin-top: 10px; + } + + &__close::after { + content: '\00D7'; + font-size: 2em; + color: $white; + position: absolute; + top: 20.8px; + right: 28px; + cursor: pointer; + } + } + + &__buy-rows { + width: 100%; + padding: 0 30px; + display: flex; + flex-flow: column nowrap; + flex: 1; + align-items: center; + + @media screen and (max-width: 575px) { + height: 0; + } + } + + &__logo { + height: 60px; + background-repeat: no-repeat; + background-size: contain; + background-position: center; + width: 100%; + display: flex; + justify-content: center; + align-items: center; + } + + &__buy-row { + border-bottom: 1px solid $alto; + display: flex; + justify-content: space-between; + align-items: center; + flex: 1 0 auto; + padding: 30px 0 20px; + min-height: 170px; + + @media screen and (max-width: 575px) { + min-height: 270px; + flex-flow: column; + justify-content: flex-start; + } + + &__back { + position: absolute; + top: 10px; + left: 0; + } + + &__logo-container { + display: flex; + justify-content: center; + flex: 0 0 auto; + padding: 0 20px; + + @media screen and (min-width: 576px) { + width: 12rem; + } + + @media screen and (max-width: 575px) { + width: 100%; + max-height: 6rem; + padding-bottom: 20px; + } + } + + &__right { + display: flex; + } + + &__description { + color: $cape-cod; + padding-bottom: 20px; + align-self: flex-start; + + @media screen and (min-width: 575px) { + width: 15rem; + } + + &__title { + font-size: 20px; + line-height: 30px; + } + + &__text { + font-size: 14px; + line-height: 22px; + margin-top: 7px; + } + } + + &__button { + display: flex; + justify-content: flex-end; + + @media screen and (min-width: 575px) { + min-width: 300px; + } + } + } + + &__buy-row:last-of-type { + border-bottom: 0; + } + + &__deposit-button { + width: 257px; + } + + .simple-dropdown { + color: #5b5d67; + font-size: 16px; + line-height: 21px; + border: 1px solid #d8d8d8; + background-color: #fff; + text-align: center; + width: 100%; + height: 45px; + line-height: 44px; + font-weight: 300; + } + + .simple-dropdown__selected { + text-align: center; + } +} diff --git a/ui/app/components/app/modals/index.scss b/ui/app/components/app/modals/index.scss index c7f01b20b7dd..ae162ea16113 100644 --- a/ui/app/components/app/modals/index.scss +++ b/ui/app/components/app/modals/index.scss @@ -1,5 +1,6 @@ @import 'cancel-transaction/index'; @import 'confirm-remove-account/index'; +@import 'deposit-ether-modal/index'; @import 'notification-modal/index'; @import 'qr-scanner/index'; @import 'transaction-confirmed/index'; diff --git a/ui/app/css/itcss/components/modal.scss b/ui/app/css/itcss/components/modal.scss index 8d47c753e9df..a0ed3728de8d 100644 --- a/ui/app/css/itcss/components/modal.scss +++ b/ui/app/css/itcss/components/modal.scss @@ -275,167 +275,3 @@ margin: 0 5px; } } - -// Deposit Ether Modal -.deposit-ether-modal { - border-radius: 8px; - display: flex; - flex-flow: column; - height: 100%; - - &__header { - width: 100%; - border-radius: 8px 8px 0 0; - background-color: $mid-gray; - display: flex; - position: relative; - padding: 25px; - flex-flow: column; - align-items: flex-start; - - &__title { - color: $white; - font-size: 24px; - line-height: 32px; - } - - &__description { - color: $white; - font-size: 16px; - line-height: 22px; - margin-top: 10px; - } - - &__close::after { - content: '\00D7'; - font-size: 2em; - color: $white; - position: absolute; - top: 20.8px; - right: 28px; - cursor: pointer; - } - } - - &__buy-rows { - width: 100%; - padding: 0 30px; - display: flex; - flex-flow: column nowrap; - flex: 1; - align-items: center; - - @media screen and (max-width: 575px) { - height: 0; - } - } - - &__logo { - height: 60px; - background-repeat: no-repeat; - background-size: contain; - background-position: center; - width: 100%; - display: flex; - justify-content: center; - align-items: center; - } - - &__buy-row { - border-bottom: 1px solid $alto; - display: flex; - justify-content: space-between; - align-items: center; - flex: 1 0 auto; - padding: 30px 0 20px; - min-height: 170px; - - @media screen and (max-width: 575px) { - min-height: 270px; - flex-flow: column; - justify-content: flex-start; - } - - &__back { - position: absolute; - top: 10px; - left: 0; - } - - &__logo-container { - display: flex; - justify-content: center; - flex: 0 0 auto; - padding: 0 20px; - - @media screen and (min-width: 576px) { - width: 12rem; - } - - @media screen and (max-width: 575px) { - width: 100%; - max-height: 6rem; - padding-bottom: 20px; - } - } - - &__right { - display: flex; - } - - &__description { - color: $cape-cod; - padding-bottom: 20px; - align-self: flex-start; - - @media screen and (min-width: 575px) { - width: 15rem; - } - - &__title { - font-size: 20px; - line-height: 30px; - } - - &__text { - font-size: 14px; - line-height: 22px; - margin-top: 7px; - } - } - - &__button { - display: flex; - justify-content: flex-end; - - @media screen and (min-width: 575px) { - min-width: 300px; - } - } - } - - &__buy-row:last-of-type { - border-bottom: 0; - } - - &__deposit-button { - width: 257px; - } - - .simple-dropdown { - color: #5b5d67; - font-size: 16px; - line-height: 21px; - border: 1px solid #d8d8d8; - background-color: #fff; - text-align: center; - width: 100%; - height: 45px; - line-height: 44px; - font-weight: 300; - } - - .simple-dropdown__selected { - text-align: center; - } -} From e5cb63eea2f37718860017421b2e35cb746e8e09 Mon Sep 17 00:00:00 2001 From: Erik Marks <25517051+rekmarks@users.noreply.github.com> Date: Fri, 7 Aug 2020 12:28:23 -0700 Subject: [PATCH 055/107] Add web3 usage metrics, prepare for web3 removal (#9144) * add web3 usage metrics * move web3 metrics method to new middleware * rename some methods, files, and exports --- app/scripts/controllers/permissions/index.js | 4 +- ...ware.js => permissionsMethodMiddleware.js} | 2 +- app/scripts/inpage.js | 27 ++--------- ...tametrics.js => background-metametrics.js} | 4 +- app/scripts/lib/createMethodMiddleware.js | 32 +++++++++++++ .../lib/{auto-reload.js => setupWeb3.js} | 47 +++++++++++++++++-- app/scripts/metamask-controller.js | 40 +++++++++++----- ui/app/helpers/utils/metametrics.util.js | 4 +- 8 files changed, 114 insertions(+), 46 deletions(-) rename app/scripts/controllers/permissions/{methodMiddleware.js => permissionsMethodMiddleware.js} (98%) rename app/scripts/lib/{backend-metametrics.js => background-metametrics.js} (78%) create mode 100644 app/scripts/lib/createMethodMiddleware.js rename app/scripts/lib/{auto-reload.js => setupWeb3.js} (64%) diff --git a/app/scripts/controllers/permissions/index.js b/app/scripts/controllers/permissions/index.js index 311a0d1ca872..a5aeb7b1429a 100644 --- a/app/scripts/controllers/permissions/index.js +++ b/app/scripts/controllers/permissions/index.js @@ -7,7 +7,7 @@ import { CapabilitiesController as RpcCap } from 'rpc-cap' import { ethErrors } from 'eth-json-rpc-errors' import { cloneDeep } from 'lodash' -import createMethodMiddleware from './methodMiddleware' +import createPermissionsMethodMiddleware from './permissionsMethodMiddleware' import PermissionsLogController from './permissionsLog' // Methods that do not require any permissions to use: @@ -90,7 +90,7 @@ export class PermissionsController { engine.push(this.permissionsLog.createMiddleware()) - engine.push(createMethodMiddleware({ + engine.push(createPermissionsMethodMiddleware({ addDomainMetadata: this.addDomainMetadata.bind(this), getAccounts: this.getAccounts.bind(this, origin), getUnlockPromise: () => this._getUnlockPromise(true), diff --git a/app/scripts/controllers/permissions/methodMiddleware.js b/app/scripts/controllers/permissions/permissionsMethodMiddleware.js similarity index 98% rename from app/scripts/controllers/permissions/methodMiddleware.js rename to app/scripts/controllers/permissions/permissionsMethodMiddleware.js index 613ea3876df2..075f67cf85ba 100644 --- a/app/scripts/controllers/permissions/methodMiddleware.js +++ b/app/scripts/controllers/permissions/permissionsMethodMiddleware.js @@ -4,7 +4,7 @@ import { ethErrors } from 'eth-json-rpc-errors' /** * Create middleware for handling certain methods and preprocessing permissions requests. */ -export default function createMethodMiddleware ({ +export default function createPermissionsMethodMiddleware ({ addDomainMetadata, getAccounts, getUnlockPromise, diff --git a/app/scripts/inpage.js b/app/scripts/inpage.js index 67250228635a..c7c92faceede 100644 --- a/app/scripts/inpage.js +++ b/app/scripts/inpage.js @@ -1,5 +1,3 @@ -/*global Web3*/ - // need to make sure we aren't affected by overlapping namespaces // and that we dont affect the app with our namespace // mostly a fix for web3's BigNumber if AMD's "define" is defined... @@ -37,9 +35,7 @@ import LocalMessageDuplexStream from 'post-message-stream' import { initProvider } from '@metamask/inpage-provider' // TODO:deprecate:2020 -import 'web3/dist/web3.min.js' - -import setupDappAutoReload from './lib/auto-reload.js' +import setupWeb3 from './lib/setupWeb3.js' restoreContextAfterImports() @@ -59,11 +55,9 @@ initProvider({ connectionStream: metamaskStream, }) -// // TODO:deprecate:2020 -// +// Setup web3 -// setup web3 if (typeof window.web3 !== 'undefined') { throw new Error(`MetaMask detected another web3. @@ -73,18 +67,5 @@ if (typeof window.web3 !== 'undefined') { and try again.`) } -const web3 = new Web3(window.ethereum) -web3.setProvider = function () { - log.debug('MetaMask - overrode web3.setProvider') -} -log.debug('MetaMask - injected web3') - -Object.defineProperty(window.ethereum, '_web3Ref', { - enumerable: false, - writable: true, - configurable: true, - value: web3.eth, -}) - -// setup dapp auto reload AND proxy web3 -setupDappAutoReload(web3, window.ethereum._publicConfigStore) +// proxy web3, assign to window, and set up site auto reload +setupWeb3(log) diff --git a/app/scripts/lib/backend-metametrics.js b/app/scripts/lib/background-metametrics.js similarity index 78% rename from app/scripts/lib/backend-metametrics.js rename to app/scripts/lib/background-metametrics.js index e62f09d0de07..f381ea3a12b3 100644 --- a/app/scripts/lib/backend-metametrics.js +++ b/app/scripts/lib/background-metametrics.js @@ -1,13 +1,13 @@ import { getBackgroundMetaMetricState } from '../../../ui/app/selectors' import { sendMetaMetricsEvent } from '../../../ui/app/helpers/utils/metametrics.util' -export default function backEndMetaMetricsEvent (metaMaskState, eventData) { +export default function backgroundMetaMetricsEvent (metaMaskState, eventData) { const stateEventData = getBackgroundMetaMetricState({ metamask: metaMaskState }) - if (stateEventData.participateInMetaMetrics) { sendMetaMetricsEvent({ ...stateEventData, ...eventData, + category: 'Background', currentPath: '/background', }) } diff --git a/app/scripts/lib/createMethodMiddleware.js b/app/scripts/lib/createMethodMiddleware.js new file mode 100644 index 000000000000..308cf0a99e56 --- /dev/null +++ b/app/scripts/lib/createMethodMiddleware.js @@ -0,0 +1,32 @@ +/** + * Returns a middleware that implements the following RPC methods: + * - metamask_logInjectedWeb3Usage + * + * @param {Object} opts - The middleware options + * @param {string} opts.origin - The origin for the middleware stack + * @param {Function} opts.sendMetrics - A function for sending a metrics event + * @returns {(req: any, res: any, next: Function, end: Function) => void} + */ +export default function createMethodMiddleware ({ origin, sendMetrics }) { + return function methodMiddleware (req, res, next, end) { + switch (req.method) { + + case 'metamask_logInjectedWeb3Usage': + + const { action, name } = req.params[0] + + sendMetrics({ + action, + name, + customVariables: { origin }, + }) + + res.result = true + break + + default: + return next() + } + return end() + } +} diff --git a/app/scripts/lib/auto-reload.js b/app/scripts/lib/setupWeb3.js similarity index 64% rename from app/scripts/lib/auto-reload.js rename to app/scripts/lib/setupWeb3.js index 2f50e3e88530..da5d8d422b19 100644 --- a/app/scripts/lib/auto-reload.js +++ b/app/scripts/lib/setupWeb3.js @@ -1,26 +1,67 @@ +/*global Web3*/ // TODO:deprecate:2020 +// Delete this file -export default function setupDappAutoReload (web3, observable) { +import 'web3/dist/web3.min.js' + +const shouldLogUsage = !([ + 'docs.metamask.io', + 'metamask.github.io', + 'metamask.io', +].includes(window.location.hostname)) + +export default function setupWeb3 (log) { // export web3 as a global, checking for usage let reloadInProgress = false let lastTimeUsed let lastSeenNetwork let hasBeenWarned = false + const web3 = new Web3(window.ethereum) + web3.setProvider = function () { + log.debug('MetaMask - overrode web3.setProvider') + } + log.debug('MetaMask - injected web3') + + Object.defineProperty(window.ethereum, '_web3Ref', { + enumerable: false, + writable: true, + configurable: true, + value: web3.eth, + }) + const web3Proxy = new Proxy(web3, { get: (_web3, key) => { + // get the time of use lastTimeUsed = Date.now() + // show warning once on web3 access - if (!hasBeenWarned && key !== 'currentProvider') { + if (!hasBeenWarned) { console.warn(`MetaMask: We will stop injecting web3 in Q4 2020.\nPlease see this article for more information: https://medium.com/metamask/no-longer-injecting-web3-js-4a899ad6e59e`) hasBeenWarned = true } + + if (shouldLogUsage) { + window.ethereum.request({ + method: 'metamask_logInjectedWeb3Usage', + params: [{ action: 'window.web3 get', name: key }], + }) + } + // return value normally return _web3[key] }, set: (_web3, key, value) => { + + if (shouldLogUsage) { + window.ethereum.request({ + method: 'metamask_logInjectedWeb3Usage', + params: [{ action: 'window.web3 set', name: key }], + }) + } + // set value normally _web3[key] = value }, @@ -33,7 +74,7 @@ export default function setupDappAutoReload (web3, observable) { value: web3Proxy, }) - observable.subscribe(function (state) { + window.ethereum._publicConfigStore.subscribe((state) => { // if the auto refresh on network change is false do not // do anything if (!window.ethereum.autoRefreshOnNetworkChange) { diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 8008cb77946a..f9821e822cbd 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -19,6 +19,7 @@ import createEngineStream from 'json-rpc-middleware-stream/engineStream' import createFilterMiddleware from 'eth-json-rpc-filters' import createSubscriptionManager from 'eth-json-rpc-filters/subscriptionManager' import createLoggerMiddleware from './lib/createLoggerMiddleware' +import createMethodMiddleware from './lib/createMethodMiddleware' import createOriginMiddleware from './lib/createOriginMiddleware' import createTabIdMiddleware from './lib/createTabIdMiddleware' import createOnboardingMiddleware from './lib/createOnboardingMiddleware' @@ -66,7 +67,7 @@ import { PhishingController, } from '@metamask/controllers' -import backEndMetaMetricsEvent from './lib/backend-metametrics' +import backgroundMetaMetricsEvent from './lib/background-metametrics' export default class MetamaskController extends EventEmitter { @@ -249,18 +250,11 @@ export default class MetamaskController extends EventEmitter { this.platform.showTransactionNotification(txMeta) const { txReceipt } = txMeta - const participateInMetaMetrics = this.preferencesController.getParticipateInMetaMetrics() - if (txReceipt && txReceipt.status === '0x0' && participateInMetaMetrics) { - const metamaskState = await this.getState() - backEndMetaMetricsEvent(metamaskState, { - customVariables: { - errorMessage: txMeta.simulationFails?.reason, - }, - eventOpts: { - category: 'Background', - action: 'Transactions', - name: 'On Chain Failure', - }, + if (txReceipt && txReceipt.status === '0x0') { + this.sendBackgroundMetaMetrics({ + action: 'Transactions', + name: 'On Chain Failure', + customVariables: { errorMessage: txMeta.simulationFails?.reason }, }) } } @@ -1637,6 +1631,10 @@ export default class MetamaskController extends EventEmitter { location, registerOnboarding: this.onboardingController.registerOnboarding, })) + engine.push(createMethodMiddleware({ + origin, + sendMetrics: this.sendBackgroundMetaMetrics.bind(this), + })) // filter and subscription polyfills engine.push(filterMiddleware) engine.push(subscriptionManager.middleware) @@ -1837,6 +1835,22 @@ export default class MetamaskController extends EventEmitter { return nonceLock.nextNonce } + async sendBackgroundMetaMetrics ({ action, name, customVariables } = {}) { + + if (!action || !name) { + throw new Error('Must provide action and name.') + } + + const metamaskState = await this.getState() + backgroundMetaMetricsEvent(metamaskState, { + customVariables, + eventOpts: { + action, + name, + }, + }) + } + //============================================================================= // CONFIG //============================================================================= diff --git a/ui/app/helpers/utils/metametrics.util.js b/ui/app/helpers/utils/metametrics.util.js index d2563c7fcf43..cf9f157202ea 100644 --- a/ui/app/helpers/utils/metametrics.util.js +++ b/ui/app/helpers/utils/metametrics.util.js @@ -23,7 +23,7 @@ const METAMETRICS_CUSTOM_GAS_LIMIT_CHANGE = 'gasLimitChange' const METAMETRICS_CUSTOM_GAS_PRICE_CHANGE = 'gasPriceChange' const METAMETRICS_CUSTOM_FUNCTION_TYPE = 'functionType' const METAMETRICS_CUSTOM_RECIPIENT_KNOWN = 'recipientKnown' -const METAMETRICS_CUSTOM_CONFIRM_SCREEN_ORIGIN = 'origin' +const METAMETRICS_REQUEST_ORIGIN = 'origin' const METAMETRICS_CUSTOM_FROM_NETWORK = 'fromNetwork' const METAMETRICS_CUSTOM_TO_NETWORK = 'toNetwork' const METAMETRICS_CUSTOM_ERROR_FIELD = 'errorField' @@ -36,7 +36,7 @@ const METAMETRICS_CUSTOM_ASSET_SELECTED = 'assetSelected' const customVariableNameIdMap = { [METAMETRICS_CUSTOM_FUNCTION_TYPE]: 1, [METAMETRICS_CUSTOM_RECIPIENT_KNOWN]: 2, - [METAMETRICS_CUSTOM_CONFIRM_SCREEN_ORIGIN]: 3, + [METAMETRICS_REQUEST_ORIGIN]: 3, [METAMETRICS_CUSTOM_GAS_LIMIT_CHANGE]: 4, [METAMETRICS_CUSTOM_GAS_PRICE_CHANGE]: 5, From 5527a6d9e9bcdf860536a8c64d35cef50048114f Mon Sep 17 00:00:00 2001 From: Erik Marks <25517051+rekmarks@users.noreply.github.com> Date: Fri, 7 Aug 2020 12:29:50 -0700 Subject: [PATCH 056/107] Remove web3 e2e tests (#9159) --- package.json | 2 - test/e2e/run-web3.sh | 13 -- test/e2e/web3.spec.js | 288 ------------------------------------------ test/web3/index.html | 105 --------------- test/web3/schema.js | 209 ------------------------------ test/web3/web3.js | 34 ----- 6 files changed, 651 deletions(-) delete mode 100755 test/e2e/run-web3.sh delete mode 100644 test/e2e/web3.spec.js delete mode 100644 test/web3/index.html delete mode 100644 test/web3/schema.js delete mode 100644 test/web3/web3.js diff --git a/package.json b/package.json index 59b494bff86d..c20828989043 100644 --- a/package.json +++ b/package.json @@ -22,8 +22,6 @@ "test:unit:strict": "mocha --exit --require test/env.js --require test/setup.js --recursive \"test/unit/**/permissions/*.js\"", "test:unit:path": "mocha --exit --require test/env.js --require test/setup.js --recursive", "test:e2e:chrome": "SELENIUM_BROWSER=chrome test/e2e/run-all.sh", - "test:web3:chrome": "SELENIUM_BROWSER=chrome test/e2e/run-web3.sh", - "test:web3:firefox": "SELENIUM_BROWSER=firefox test/e2e/run-web3.sh", "test:e2e:firefox": "SELENIUM_BROWSER=firefox test/e2e/run-all.sh", "test:coverage": "nyc --silent --check-coverage yarn test:unit:strict && nyc --silent --no-clean yarn test:unit:lax && nyc report --reporter=text --reporter=html", "test:coverage:strict": "nyc --check-coverage yarn test:unit:strict", diff --git a/test/e2e/run-web3.sh b/test/e2e/run-web3.sh deleted file mode 100755 index 729333b840a6..000000000000 --- a/test/e2e/run-web3.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env bash - -set -e -set -u -set -o pipefail - -export PATH="$PATH:./node_modules/.bin" - -concurrently --kill-others \ - --names 'dapp,e2e' \ - --prefix '[{time}][{name}]' \ - 'node development/static-server.js test/web3 --port 8080' \ - 'sleep 5 && mocha test/e2e/web3.spec' diff --git a/test/e2e/web3.spec.js b/test/e2e/web3.spec.js deleted file mode 100644 index 4bcca74dd3aa..000000000000 --- a/test/e2e/web3.spec.js +++ /dev/null @@ -1,288 +0,0 @@ -const assert = require('assert') -const webdriver = require('selenium-webdriver') - -const { By } = webdriver -const { - regularDelayMs, - largeDelayMs, -} = require('./helpers') -const { buildWebDriver } = require('./webdriver') -const enLocaleMessages = require('../../app/_locales/en/messages.json') - -describe('Using MetaMask with an existing account', function () { - let driver - - const testSeedPhrase = 'forum vessel pink push lonely enact gentle tail admit parrot grunt dress' - - const button = async (x) => { - const buttoncheck = x - await buttoncheck.click() - await driver.delay(largeDelayMs) - const [results] = await driver.findElements(By.css('#results')) - const resulttext = await results.getText() - const parsedData = JSON.parse(resulttext) - - return (parsedData) - - } - - this.timeout(0) - this.bail(true) - - before(async function () { - const result = await buildWebDriver() - driver = result.driver - }) - - afterEach(async function () { - if (process.env.SELENIUM_BROWSER === 'chrome') { - const errors = await driver.checkBrowserForConsoleErrors(driver) - if (errors.length) { - const errorReports = errors.map((err) => err.message) - const errorMessage = `Errors found in browser console:\n${errorReports.join('\n')}` - console.error(new Error(errorMessage)) - } - } - if (this.currentTest.state === 'failed') { - await driver.verboseReportOnFailure(this.currentTest.title) - } - }) - - after(async function () { - await driver.quit() - }) - - describe('First time flow starting from an existing seed phrase', function () { - it('clicks the continue button on the welcome screen', async function () { - await driver.findElement(By.css('.welcome-page__header')) - await driver.clickElement(By.xpath(`//button[contains(text(), '${enLocaleMessages.getStarted.message}')]`)) - await driver.delay(largeDelayMs) - }) - - it('clicks the "Import Wallet" option', async function () { - await driver.clickElement(By.xpath(`//button[contains(text(), 'Import wallet')]`)) - await driver.delay(largeDelayMs) - }) - - it('clicks the "No thanks" option on the metametrics opt-in screen', async function () { - await driver.clickElement(By.css('.btn-default')) - await driver.delay(largeDelayMs) - }) - - it('imports a seed phrase', async function () { - const [seedTextArea] = await driver.findElements(By.css('input[placeholder="Paste seed phrase from clipboard"]')) - await seedTextArea.sendKeys(testSeedPhrase) - await driver.delay(regularDelayMs) - - const [password] = await driver.findElements(By.id('password')) - await password.sendKeys('correct horse battery staple') - const [confirmPassword] = await driver.findElements(By.id('confirm-password')) - confirmPassword.sendKeys('correct horse battery staple') - - await driver.clickElement(By.css('.first-time-flow__terms')) - - await driver.clickElement(By.xpath(`//button[contains(text(), 'Import')]`)) - await driver.delay(regularDelayMs) - }) - - it('clicks through the success screen', async function () { - await driver.findElement(By.xpath(`//div[contains(text(), 'Congratulations')]`)) - await driver.clickElement(By.xpath(`//button[contains(text(), '${enLocaleMessages.endOfFlowMessage10.message}')]`)) - await driver.delay(regularDelayMs) - }) - }) - - - describe('opens dapp', function () { - - it('switches to mainnet', async function () { - await driver.clickElement(By.css('.network-name')) - await driver.delay(regularDelayMs) - - await driver.clickElement(By.xpath(`//span[contains(text(), 'Main Ethereum Network')]`)) - await driver.delay(largeDelayMs * 2) - }) - - it('connects to dapp', async function () { - await driver.openNewPage('http://127.0.0.1:8080/') - await driver.delay(regularDelayMs) - - await driver.clickElement(By.xpath(`//button[contains(text(), 'Connect')]`)) - - await driver.delay(regularDelayMs) - - await driver.waitUntilXWindowHandles(3) - const windowHandles = await driver.getAllWindowHandles() - - const extension = windowHandles[0] - const popup = await driver.switchToWindowWithTitle('MetaMask Notification', windowHandles) - const dapp = windowHandles.find((handle) => handle !== extension && handle !== popup) - - await driver.delay(regularDelayMs) - await driver.clickElement(By.xpath(`//button[contains(text(), 'Connect')]`)) - - await driver.switchToWindow(dapp) - await driver.delay(regularDelayMs) - }) - }) - - describe('testing web3 methods', function () { - - - it('testing hexa methods', async function () { - - - const List = await driver.findClickableElements(By.className('hexaNumberMethods')) - - for (let i = 0; i < List.length; i++) { - try { - - const parsedData = await button(List[i]) - console.log(parsedData) - const result = parseInt(parsedData.result, 16) - - assert.equal((typeof result === 'number'), true) - await driver.delay(regularDelayMs) - } catch (err) { - console.log(err) - assert(false) - - } - } - }) - - it('testing booleanMethods', async function () { - - const List = await driver.findClickableElement(By.className('booleanMethods')) - - for (let i = 0; i < List.length; i++) { - try { - - const parsedData = await button(List[i]) - console.log(parsedData) - const result = parsedData.result - - assert.equal(result, false) - await driver.delay(regularDelayMs) - } catch (err) { - console.log(err) - assert(false) - - - } - } - - }) - - it('testing transactionMethods', async function () { - - const List = await driver.findClickableElement(By.className('transactionMethods')) - - for (let i = 0; i < List.length; i++) { - try { - - const parsedData = await button(List[i]) - - console.log(parsedData.result.blockHash) - - const result = [] - result.push(parseInt(parsedData.result.blockHash, 16)) - result.push(parseInt(parsedData.result.blockNumber, 16)) - result.push(parseInt(parsedData.result.gas, 16)) - result.push(parseInt(parsedData.result.gasPrice, 16)) - result.push(parseInt(parsedData.result.hash, 16)) - result.push(parseInt(parsedData.result.input, 16)) - result.push(parseInt(parsedData.result.nonce, 16)) - result.push(parseInt(parsedData.result.r, 16)) - result.push(parseInt(parsedData.result.s, 16)) - result.push(parseInt(parsedData.result.v, 16)) - result.push(parseInt(parsedData.result.to, 16)) - result.push(parseInt(parsedData.result.value, 16)) - - - result.forEach((value) => { - assert.equal((typeof value === 'number'), true) - }) - - - } catch (err) { - - console.log(err) - assert(false) - - - } - } - - }) - - it('testing blockMethods', async function () { - - const List = await driver.findClickableElement(By.className('blockMethods')) - - for (let i = 0; i < List.length; i++) { - try { - - const parsedData = await button(List[i]) - console.log(JSON.stringify(parsedData) + i) - - console.log(parsedData.result.parentHash) - - const result = parseInt(parsedData.result.parentHash, 16) - - assert.equal((typeof result === 'number'), true) - await driver.delay(regularDelayMs) - } catch (err) { - - console.log(err) - assert(false) - - - } - } - }) - - it('testing methods', async function () { - - const List = await driver.findClickableElement(By.className('methods')) - let parsedData - let result - - for (let i = 0; i < List.length; i++) { - try { - - if (i === 2) { - - parsedData = await button(List[i]) - console.log(parsedData.result.blockHash) - - result = parseInt(parsedData.result.blockHash, 16) - - assert.equal((typeof result === 'number' || (result === 0)), true) - await driver.delay(regularDelayMs) - } else { - parsedData = await button(List[i]) - console.log(parsedData.result) - - result = parseInt(parsedData.result, 16) - - assert.equal((typeof result === 'number' || (result === 0)), true) - await driver.delay(regularDelayMs) - } - - - } catch (err) { - - console.log(err) - assert(false) - - - } - } - }) - - - }) - - -}) diff --git a/test/web3/index.html b/test/web3/index.html deleted file mode 100644 index cbc43290cd51..000000000000 --- a/test/web3/index.html +++ /dev/null @@ -1,105 +0,0 @@ - - - Web3 Test Dapp - - -
-
hexaNumberMethods
-
- - - - - - - -
-
- - - -
-
- - - - - - -
-
-
-
booleanMethods
-
- - - -
-
-
-
transactionMethods
-
- - - - - -
-
- -
-
blockMethods
- -
- - - - -
-
- - - -
-
- -
-
Methods
-
- - - - -
-
-
-
-
- - - - -
- - - - - diff --git a/test/web3/schema.js b/test/web3/schema.js deleted file mode 100644 index 54a6ff1a9708..000000000000 --- a/test/web3/schema.js +++ /dev/null @@ -1,209 +0,0 @@ -/* eslint no-unused-vars: 0 */ - -const params = { - // diffrent params used in the methods - param: [], - blockHashParams: '0xb3b20624f8f0f86eb50dd04688409e5cea4bd02d700bf6e79e9384d47d6a5a35', - filterParams: ['0xfe704947a3cd3ca12541458a4321c869'], - transactionHashParams: [ - '0xbb3a336e3f823ec18197f1e13ee875700f08f03e2cab75f0d0b118dabb44cba0', - ], - blockHashAndIndexParams: [ - '0xb3b20624f8f0f86eb50dd04688409e5cea4bd02d700bf6e79e9384d47d6a5a35', - '0x0', - ], - uncleByBlockNumberAndIndexParams: ['0x29c', '0x0'], - blockParameterParams: '0x5bad55', - data: '0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675', - addressParams: '0xc94770007dda54cF92009BFF0dE90c06F603a09f', - getStorageAtParams: [ - '0x295a70b2de5e3953354a6a8344e616ed314d7251', - '0x6661e9d6d8b923d5bbaab1b96e1dd51ff6ea2a93520fdc9eb75d059238b8c5e9', - '0x65a8db', - ], - getCodeParams: ['0x06012c8cf97bead5deae237070f9587f8e7a266d', '0x65a8db'], - estimateTransaction: { - from: '0xb60e8dd61c5d32be8058bb8eb970870f07233155', - to: '0xd46e8dd67c5d32be8058bb8eb970870f07244567', - gas: '0x76c0', - gasPrice: '0x9184e72a000', - value: '0x9184e72a', - data: '0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675', - }, - filterGetLogs: [{ 'blockHash': '0x7c5a35e9cb3e8ae0e221ab470abae9d446c3a5626ce6689fc777dcffcab52c70', 'topics': ['0x241ea03ca20251805084d27d4440371c34a0b85ff108f6bb5611248f73818b80'] }], - block: { - __required: [], - number: 'Q', - hash: 'D32', - parentHash: 'D32', - nonce: 'D', - sha3Uncles: 'D', - logsBloom: 'D', - transactionsRoot: 'D', - stateRoot: 'D', - receiptsRoot: 'D', - miner: 'D', - difficulty: 'Q', - totalDifficulty: 'Q', - extraData: 'D', - size: 'Q', - gasLimit: 'Q', - gasUsed: 'Q', - timestamp: 'Q', - transactions: ['DATA|Transaction'], - uncles: ['D'], - }, - transaction: { - __required: [], - hash: 'D32', - nonce: 'Q', - blockHash: 'D32', - blockNumber: 'Q', - transactionIndex: 'Q', - from: 'D20', - to: 'D20', - value: 'Q', - gasPrice: 'Q', - gas: 'Q', - input: 'D', - }, - receipt: { - __required: [], - transactionHash: 'D32', - transactionIndex: 'Q', - blockHash: 'D32', - blockNumber: 'Q', - cumulativeGasUsed: 'Q', - gasUsed: 'Q', - contractAddress: 'D20', - logs: ['FilterChange'], - }, - - filterChange: { - __required: [], - removed: 'B', - logIndex: 'Q', - transactionIndex: 'Q', - transactionHash: 'D32', - blockHash: 'D32', - blockNumber: 'Q', - address: 'D20', - data: 'Array|DATA', - topics: ['D'], - }, -} - -const methods = { - hexaNumberMethods: { - // these are the methods which have output in the form of hexa decimal numbers - eth_blockNumber: ['eth_blockNumber', params.param, 'Q'], - eth_gasPrice: ['eth_gasPrice', params.param, 'Q'], - eth_newBlockFilter: ['eth_newBlockFilter', params.param, 'Q'], - eth_newPendingTransactionFilter: [ - 'eth_newPendingTransactionFilter', - params.param, - 'Q', - ], - eth_getUncleCountByBlockHash: [ - 'eth_getUncleCountByBlockHash', - [params.blockHashParams], - 'Q', - 1, - ], - eth_getBlockTransactionCountByHash: [ - 'eth_getBlockTransactionCountByHash', - [params.blockHashParams], - 'Q', - 1, - ], - eth_getTransactionCount: [ - 'eth_getTransactionCount', - [params.addressParams, params.blockParameterParams], - 'Q', - 1, - 2, - ], - eth_getBalance: ['eth_getBalance', [params.addressParams, 'latest'], 'Q', 1, 2], - eth_estimateGas: ['eth_estimateGas', [params.estimateTransaction], 'Q', 1], - eth_getUncleCountByBlockNumber: [ - 'eth_getUncleCountByBlockNumber', - [params.blockParameterParams], - 'Q', - 1, - ], - eth_getBlockTransactionCountByNumber: [ - 'eth_getBlockTransactionCountByNumber', - ['latest'], - 'Q', - 1, - ], - eth_protocolVersion: ['eth_protocolVersion', params.param, 'S'], - eth_getCode: ['eth_getCode', params.getCodeParams, 'D', 1, 2], - }, - booleanMethods: { - // these are the methods which have output in the form of boolean - eth_uninstallFilter: ['eth_uninstallFilter', params.filterParams, 'B', 1], - eth_mining: ['eth_mining', params.param, 'B'], - eth_syncing: ['eth_syncing', params.param, 'B|EthSyncing'], - }, - transactionMethods: { - // these are the methods which have output in the form of transaction object - eth_getTransactionByHash: [ - 'eth_getTransactionByHash', - params.transactionHashParams, - params.transaction, - 1, - ], - eth_getTransactionByBlockHashAndIndex: [ - 'eth_getTransactionByBlockHashAndIndex', - params.blockHashAndIndexParams, - params.transaction, - 2, - ], - eth_getTransactionByBlockNumberAndIndex: [ - 'eth_getTransactionByBlockNumberAndIndex', - [params.blockParameterParams, '0x0'], - params.transaction, - 2, - ], - - }, - blockMethods: { - // these are the methods which have output in the form of a block - - eth_getUncleByBlockNumberAndIndex: [ - 'eth_getUncleByBlockNumberAndIndex', - params.uncleByBlockNumberAndIndexParams, - params.block, - 2, - ], - eth_getBlockByHash: [ - 'eth_getBlockByHash', - [params.params, false], - params.block, - 2, - ], - eth_getBlockByNumber: [ - 'eth_getBlockByNumber', - [params.blockParameterParams, false], - params.block, - 2, - ], - }, - - methods: { - // these are the methods which have output in the form of bytes data - - eth_call: ['eth_call', [params.estimateTransaction, 'latest'], 'D', 1, 2], - eth_getStorageAt: ['eth_getStorageAt', params.getStorageAtParams, 'D', 2, 2], - eth_getTransactionReceipt: [ - 'eth_getTransactionReceipt', - params.transactionHashParams, - params.receipt, - 1, - ], - - }, - -} - diff --git a/test/web3/web3.js b/test/web3/web3.js deleted file mode 100644 index 49898044d7a0..000000000000 --- a/test/web3/web3.js +++ /dev/null @@ -1,34 +0,0 @@ -/* eslint no-undef: 0 */ - -const json = methods - -web3.currentProvider.enable().then(() => { - - Object.keys(json).forEach((methodGroupKey) => { - - console.log(methodGroupKey) - const methodGroup = json[methodGroupKey] - console.log(methodGroup) - Object.keys(methodGroup).forEach((methodKey) => { - - const methodButton = document.getElementById(methodKey) - methodButton.addEventListener('click', () => { - - window.ethereum.sendAsync({ - method: methodKey, - params: methodGroup[methodKey][1], - }, (err, result) => { - if (err) { - console.log(err) - console.log(methodKey) - } else { - document.getElementById('results').innerHTML = JSON.stringify(result) - } - }) - }) - - }) - - }) -}) - From 2e33b57d174c463514772a6ac144ebda572db4cd Mon Sep 17 00:00:00 2001 From: Brad Decker Date: Fri, 7 Aug 2020 14:31:02 -0500 Subject: [PATCH 057/107] colocate hide-token-confirmation modal styles (#9149) --- .../hide-token-confirmation-modal.js | 4 +- .../hide-token-confirmation-modal/index.js | 1 + .../hide-token-confirmation-modal/index.scss | 61 ++++++++++++++++++ ui/app/components/app/modals/index.scss | 1 + ui/app/css/itcss/components/modal.scss | 63 ------------------- 5 files changed, 65 insertions(+), 65 deletions(-) rename ui/app/components/app/modals/{ => hide-token-confirmation-modal}/hide-token-confirmation-modal.js (96%) create mode 100644 ui/app/components/app/modals/hide-token-confirmation-modal/index.js create mode 100644 ui/app/components/app/modals/hide-token-confirmation-modal/index.scss diff --git a/ui/app/components/app/modals/hide-token-confirmation-modal.js b/ui/app/components/app/modals/hide-token-confirmation-modal/hide-token-confirmation-modal.js similarity index 96% rename from ui/app/components/app/modals/hide-token-confirmation-modal.js rename to ui/app/components/app/modals/hide-token-confirmation-modal/hide-token-confirmation-modal.js index 5f8e6001a953..58fd01170652 100644 --- a/ui/app/components/app/modals/hide-token-confirmation-modal.js +++ b/ui/app/components/app/modals/hide-token-confirmation-modal/hide-token-confirmation-modal.js @@ -1,8 +1,8 @@ import PropTypes from 'prop-types' import React, { Component } from 'react' import { connect } from 'react-redux' -import * as actions from '../../../store/actions' -import Identicon from '../../ui/identicon' +import * as actions from '../../../../store/actions' +import Identicon from '../../../ui/identicon' function mapStateToProps (state) { return { diff --git a/ui/app/components/app/modals/hide-token-confirmation-modal/index.js b/ui/app/components/app/modals/hide-token-confirmation-modal/index.js new file mode 100644 index 000000000000..31e8663986e6 --- /dev/null +++ b/ui/app/components/app/modals/hide-token-confirmation-modal/index.js @@ -0,0 +1 @@ +export { default } from './hide-token-confirmation-modal' diff --git a/ui/app/components/app/modals/hide-token-confirmation-modal/index.scss b/ui/app/components/app/modals/hide-token-confirmation-modal/index.scss new file mode 100644 index 000000000000..d640b6c5c0b2 --- /dev/null +++ b/ui/app/components/app/modals/hide-token-confirmation-modal/index.scss @@ -0,0 +1,61 @@ +.hide-token-confirmation { + min-height: 250.72px; + border-radius: 4px; + background-color: $white; + box-shadow: 0 1px 7px 0 rgba(0, 0, 0, 0.5); + + &__container { + padding: 24px 27px 21px; + display: flex; + flex-direction: column; + align-items: center; + } + + &__identicon { + margin-bottom: 10px; + } + + &__symbol { + color: $tundora; + font-size: 16px; + line-height: 24px; + text-align: center; + margin-bottom: 7.5px; + } + + &__title { + height: 30px; + width: 271.28px; + color: $tundora; + font-size: 22px; + line-height: 30px; + text-align: center; + margin-bottom: 10.5px; + } + + &__copy { + height: 41px; + width: 318px; + color: $scorpion; + font-size: 14px; + line-height: 18px; + text-align: center; + } + + &__buttons { + display: flex; + flex-direction: row; + justify-content: center; + margin-top: 15px; + width: 100%; + } + + &__button { + @include Paragraph; + + @extend %button; + + width: 141px; + margin: 0 5px; + } +} diff --git a/ui/app/components/app/modals/index.scss b/ui/app/components/app/modals/index.scss index ae162ea16113..cf5a9d66b73a 100644 --- a/ui/app/components/app/modals/index.scss +++ b/ui/app/components/app/modals/index.scss @@ -1,6 +1,7 @@ @import 'cancel-transaction/index'; @import 'confirm-remove-account/index'; @import 'deposit-ether-modal/index'; +@import 'hide-token-confirmation-modal/index'; @import 'notification-modal/index'; @import 'qr-scanner/index'; @import 'transaction-confirmed/index'; diff --git a/ui/app/css/itcss/components/modal.scss b/ui/app/css/itcss/components/modal.scss index a0ed3728de8d..81b41e73c4d7 100644 --- a/ui/app/css/itcss/components/modal.scss +++ b/ui/app/css/itcss/components/modal.scss @@ -212,66 +212,3 @@ padding: 9px 13px 8px; } -// Hide token confirmation - -.hide-token-confirmation { - min-height: 250.72px; - border-radius: 4px; - background-color: $white; - box-shadow: 0 1px 7px 0 rgba(0, 0, 0, 0.5); - - &__container { - padding: 24px 27px 21px; - display: flex; - flex-direction: column; - align-items: center; - } - - &__identicon { - margin-bottom: 10px; - } - - &__symbol { - color: $tundora; - font-size: 16px; - line-height: 24px; - text-align: center; - margin-bottom: 7.5px; - } - - &__title { - height: 30px; - width: 271.28px; - color: $tundora; - font-size: 22px; - line-height: 30px; - text-align: center; - margin-bottom: 10.5px; - } - - &__copy { - height: 41px; - width: 318px; - color: $scorpion; - font-size: 14px; - line-height: 18px; - text-align: center; - } - - &__buttons { - display: flex; - flex-direction: row; - justify-content: center; - margin-top: 15px; - width: 100%; - } - - &__button { - @include Paragraph; - - @extend %button; - - width: 141px; - margin: 0 5px; - } -} From 3a7097a4cf991e4e2320ba08d84fee9c0d0e116d Mon Sep 17 00:00:00 2001 From: Thomas Huang Date: Sat, 8 Aug 2020 20:42:36 -0700 Subject: [PATCH 058/107] Add category in eventOpts (#9164) --- app/scripts/lib/background-metametrics.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/scripts/lib/background-metametrics.js b/app/scripts/lib/background-metametrics.js index f381ea3a12b3..9905e115bf0d 100644 --- a/app/scripts/lib/background-metametrics.js +++ b/app/scripts/lib/background-metametrics.js @@ -2,12 +2,14 @@ import { getBackgroundMetaMetricState } from '../../../ui/app/selectors' import { sendMetaMetricsEvent } from '../../../ui/app/helpers/utils/metametrics.util' export default function backgroundMetaMetricsEvent (metaMaskState, eventData) { + + eventData.eventOpts['category'] = 'Background' + const stateEventData = getBackgroundMetaMetricState({ metamask: metaMaskState }) if (stateEventData.participateInMetaMetrics) { sendMetaMetricsEvent({ ...stateEventData, ...eventData, - category: 'Background', currentPath: '/background', }) } From c557e25ba62bab9d848dce7c27027e2b4452d46d Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Mon, 10 Aug 2020 12:10:30 -0300 Subject: [PATCH 059/107] Add `version` dimension to background metrics (#9167) The background metrics were missing the `version` custom dimension. It has now been added to all background metric events. --- app/scripts/lib/background-metametrics.js | 3 ++- app/scripts/metamask-controller.js | 17 +++++++++++------ ui/app/helpers/utils/metametrics.util.js | 1 + 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/app/scripts/lib/background-metametrics.js b/app/scripts/lib/background-metametrics.js index 9905e115bf0d..a9baa79b71b2 100644 --- a/app/scripts/lib/background-metametrics.js +++ b/app/scripts/lib/background-metametrics.js @@ -1,7 +1,7 @@ import { getBackgroundMetaMetricState } from '../../../ui/app/selectors' import { sendMetaMetricsEvent } from '../../../ui/app/helpers/utils/metametrics.util' -export default function backgroundMetaMetricsEvent (metaMaskState, eventData) { +export default function backgroundMetaMetricsEvent (metaMaskState, version, eventData) { eventData.eventOpts['category'] = 'Background' @@ -10,6 +10,7 @@ export default function backgroundMetaMetricsEvent (metaMaskState, eventData) { sendMetaMetricsEvent({ ...stateEventData, ...eventData, + version, currentPath: '/background', }) } diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index f9821e822cbd..b0866ce08987 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -1842,13 +1842,18 @@ export default class MetamaskController extends EventEmitter { } const metamaskState = await this.getState() - backgroundMetaMetricsEvent(metamaskState, { - customVariables, - eventOpts: { - action, - name, + const version = this.platform.getVersion() + backgroundMetaMetricsEvent( + metamaskState, + version, + { + customVariables, + eventOpts: { + action, + name, + }, }, - }) + ) } //============================================================================= diff --git a/ui/app/helpers/utils/metametrics.util.js b/ui/app/helpers/utils/metametrics.util.js index cf9f157202ea..0ffe856bfbd1 100644 --- a/ui/app/helpers/utils/metametrics.util.js +++ b/ui/app/helpers/utils/metametrics.util.js @@ -115,6 +115,7 @@ function composeParamAddition (paramValue, paramName) { * @property {string} config.accountType The account type being used at the time of the event: 'hardware', 'imported' or 'default' * @property {number} config.numberOfTokens The number of tokens that the user has added at the time of the event * @property {number} config.numberOfAccounts The number of accounts the user has added at the time of the event + * @property {string} config.version The current version of the MetaMask extension * @property {string} config.previousPath The pathname of the URL the user was on prior to the URL they are on at the time of the event * @property {string} config.currentPath The pathname of the URL the user is on at the time of the event * @property {string} config.metaMetricsId A random id assigned to a user at the time of opting in to metametrics. A hexadecimal number From cb503d9403b7a8bf7efe1282b58cf715f46b6440 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Mon, 10 Aug 2020 12:38:56 -0300 Subject: [PATCH 060/107] Refactor assignment of "Background" metrics category (#9168) The "Background" metrics category was being set in the `backgroundMetaMetricsEvent` function. This function might not necessarily include any event at all though, so setting it here seemed inappropriate. It would also crash if `eventData.eventOpts` was not set, which is not great since that property is optional. The background category is now set in the `sendBackgroundMetaMetrics` function in `metamask-controller`. This method is used solely for event data, so it would make sense for this category to be always set. There is no functional difference, since `backgroundMetaMetricsEvent` is called solely by `sendBackgroundMetaMetrics`. --- app/scripts/lib/background-metametrics.js | 3 --- app/scripts/metamask-controller.js | 1 + 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/app/scripts/lib/background-metametrics.js b/app/scripts/lib/background-metametrics.js index a9baa79b71b2..7af1125c1780 100644 --- a/app/scripts/lib/background-metametrics.js +++ b/app/scripts/lib/background-metametrics.js @@ -2,9 +2,6 @@ import { getBackgroundMetaMetricState } from '../../../ui/app/selectors' import { sendMetaMetricsEvent } from '../../../ui/app/helpers/utils/metametrics.util' export default function backgroundMetaMetricsEvent (metaMaskState, version, eventData) { - - eventData.eventOpts['category'] = 'Background' - const stateEventData = getBackgroundMetaMetricState({ metamask: metaMaskState }) if (stateEventData.participateInMetaMetrics) { sendMetaMetricsEvent({ diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index b0866ce08987..2b32a8a42639 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -1850,6 +1850,7 @@ export default class MetamaskController extends EventEmitter { customVariables, eventOpts: { action, + category: 'Background', name, }, }, From 4922b1b9b56844c68571070a300a4683f74b6946 Mon Sep 17 00:00:00 2001 From: Erik Marks <25517051+rekmarks@users.noreply.github.com> Date: Mon, 10 Aug 2020 08:58:17 -0700 Subject: [PATCH 061/107] Check if getCleanAppState is defined before calling (#9170) --- test/e2e/webdriver/driver.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/e2e/webdriver/driver.js b/test/e2e/webdriver/driver.js index daf3a1acc8f4..6ae17bdcfb46 100644 --- a/test/e2e/webdriver/driver.js +++ b/test/e2e/webdriver/driver.js @@ -180,7 +180,9 @@ class Driver { await fs.writeFile(`${filepathBase}-screenshot.png`, screenshot, { encoding: 'base64' }) const htmlSource = await this.driver.getPageSource() await fs.writeFile(`${filepathBase}-dom.html`, htmlSource) - const uiState = await this.driver.executeScript(() => window.getCleanAppState()) + const uiState = await this.driver.executeScript( + () => window.getCleanAppState && window.getCleanAppState(), + ) await fs.writeFile(`${filepathBase}-state.json`, JSON.stringify(uiState, null, 2)) } From b8edc32f48cdb327d5ae6276a902722b7072f61a Mon Sep 17 00:00:00 2001 From: Dan J Miller Date: Tue, 11 Aug 2020 10:59:24 -0230 Subject: [PATCH 062/107] Fee card component (#9169) * Fee card component * Clean up * Style lint fixes --- .storybook/main.js | 2 +- ui/app/pages/index.scss | 1 + ui/app/pages/token/fee-card/fee-card.js | 69 ++++++++++++ .../pages/token/fee-card/fee-card.stories.js | 47 ++++++++ ui/app/pages/token/fee-card/index.js | 1 + ui/app/pages/token/fee-card/index.scss | 102 ++++++++++++++++++ ui/app/pages/token/index.scss | 1 + 7 files changed, 222 insertions(+), 1 deletion(-) create mode 100644 ui/app/pages/token/fee-card/fee-card.js create mode 100644 ui/app/pages/token/fee-card/fee-card.stories.js create mode 100644 ui/app/pages/token/fee-card/index.js create mode 100644 ui/app/pages/token/fee-card/index.scss create mode 100644 ui/app/pages/token/index.scss diff --git a/.storybook/main.js b/.storybook/main.js index 598003cddb11..74acf6fb81a4 100644 --- a/.storybook/main.js +++ b/.storybook/main.js @@ -1,5 +1,5 @@ module.exports = { - stories: ['../ui/app/components/**/*.stories.js'], + stories: ['../ui/app/**/*.stories.js'], addons: [ '@storybook/addon-knobs', '@storybook/addon-actions', diff --git a/ui/app/pages/index.scss b/ui/app/pages/index.scss index 95bdc748969f..8154fc77d043 100644 --- a/ui/app/pages/index.scss +++ b/ui/app/pages/index.scss @@ -12,3 +12,4 @@ @import 'confirm-approve/index'; @import 'permissions-connect/index'; @import 'asset/asset'; +@import 'token/index'; diff --git a/ui/app/pages/token/fee-card/fee-card.js b/ui/app/pages/token/fee-card/fee-card.js new file mode 100644 index 000000000000..42e751d77191 --- /dev/null +++ b/ui/app/pages/token/fee-card/fee-card.js @@ -0,0 +1,69 @@ +import React from 'react' +import PropTypes from 'prop-types' + +export default function FeeCard ({ + onFeeRowClick = null, + feeRowText, + feeRowLinkText = '', + primaryFee, + secondaryFee = '', + onSecondRowClick = null, + secondRowText = '', + secondRowLinkText = '', + hideSecondRow = false, +}) { + return ( +
+
+
onFeeRowClick && onFeeRowClick()}> +
+
+ {feeRowText} +
+ {onFeeRowClick && ( +
+ {feeRowLinkText} +
+ )} +
+
+
+ {primaryFee} +
+ {secondaryFee && ( +
+ {secondaryFee} +
+ )} +
+
+ {!hideSecondRow && secondRowText && ( +
+
+
+ {secondRowText} +
+ {secondRowLinkText && ( +
onSecondRowClick && onSecondRowClick()}> + {secondRowLinkText} +
+ )} +
+
+ )} +
+
+ ) +} + +FeeCard.propTypes = { + onFeeRowClick: PropTypes.func, + feeRowText: PropTypes.string.isRequired, + feeRowLinkText: PropTypes.string, + primaryFee: PropTypes.string.isRequired, + secondaryFee: PropTypes.string, + onSecondRowClick: PropTypes.func, + secondRowText: PropTypes.string, + secondRowLinkText: PropTypes.string, + hideSecondRow: PropTypes.bool, +} diff --git a/ui/app/pages/token/fee-card/fee-card.stories.js b/ui/app/pages/token/fee-card/fee-card.stories.js new file mode 100644 index 000000000000..8cadfed27033 --- /dev/null +++ b/ui/app/pages/token/fee-card/fee-card.stories.js @@ -0,0 +1,47 @@ +import React from 'react' +import FeeCard from './fee-card.js' +import { action } from '@storybook/addon-actions' +import { text } from '@storybook/addon-knobs/react' + +const containerStyle = { + width: '300px', +} + +export default { + title: 'FeeCard', +} + +export const WithSecondRow = () => { + return ( +
+ +
+ ) +} + +export const WithoutSecondRow = () => { + return ( +
+ +
+ ) +} diff --git a/ui/app/pages/token/fee-card/index.js b/ui/app/pages/token/fee-card/index.js new file mode 100644 index 000000000000..84bc1acbb584 --- /dev/null +++ b/ui/app/pages/token/fee-card/index.js @@ -0,0 +1 @@ +export { default } from './fee-card' diff --git a/ui/app/pages/token/fee-card/index.scss b/ui/app/pages/token/fee-card/index.scss new file mode 100644 index 000000000000..d9acd2554c5e --- /dev/null +++ b/ui/app/pages/token/fee-card/index.scss @@ -0,0 +1,102 @@ +.fee-card { + border-radius: 8px; + border: 1px solid $Grey-100; + width: 100%; + margin-top: auto; + margin-bottom: 8px; + + @include H7; + + &__main { + padding: 16px 16px 12px 16px; + } + + &__row-header { + display: flex; + align-items: center; + margin-top: 8px; + justify-content: space-between; + + @media screen and (min-width: 576px) { + @include H6; + } + + &:first-of-type { + margin-top: 0; + } + + div { + display: flex; + align-items: center; + } + } + + &__row-header-text { + font-weight: bold; + margin-right: 8px; + cursor: pointer; + } + + &__row { + display: flex; + align-items: center; + justify-content: space-between; + margin-top: 8px; + } + + &__row-label { + display: flex; + align-items: center; + + img { + height: 10px; + width: 10px; + margin-left: 4px; + cursor: pointer; + } + } + + &__row-text { + margin-right: 8px; + } + + &__row-fee { + margin-right: 4px; + } + + &__link { + color: $Blue-500; + cursor: pointer; + } + + &__total-box { + border-top: 1px solid $Grey-100; + padding: 12px 16px 16px 16px; + } + + &__total-row { + display: flex; + justify-content: space-between; + align-items: center; + font-weight: bold; + } + + &__total-secondary { + width: 100%; + display: flex; + justify-content: flex-end; + font-weight: bold; + color: $Grey-500; + margin-top: 4px; + } + + &__row-header-secondary { + color: $Grey-500; + margin-right: 20px; + } + + &__row-header-primary { + font-weight: bold; + color: $Grey-500; + } +} diff --git a/ui/app/pages/token/index.scss b/ui/app/pages/token/index.scss new file mode 100644 index 000000000000..013ff392875f --- /dev/null +++ b/ui/app/pages/token/index.scss @@ -0,0 +1 @@ +@import 'fee-card/index'; From 6ba9e657128c6261f0df03b9d465df9cd884d997 Mon Sep 17 00:00:00 2001 From: Brad Decker Date: Tue, 11 Aug 2020 08:58:10 -0500 Subject: [PATCH 063/107] colocate tab-bar styles with the tab-bar component (#9176) Moves styles out of the itcss components folder and places them alongside the component. --- ui/app/components/app/index.scss | 1 + ui/app/components/app/tab-bar/index.js | 1 + ui/app/components/app/tab-bar/index.scss | 80 +++++++++++++++++++ .../components/app/{ => tab-bar}/tab-bar.js | 0 ui/app/css/itcss/components/index.scss | 1 - ui/app/css/itcss/components/tab-bar.scss | 79 ------------------ 6 files changed, 82 insertions(+), 80 deletions(-) create mode 100644 ui/app/components/app/tab-bar/index.js create mode 100644 ui/app/components/app/tab-bar/index.scss rename ui/app/components/app/{ => tab-bar}/tab-bar.js (100%) delete mode 100644 ui/app/css/itcss/components/tab-bar.scss diff --git a/ui/app/components/app/index.scss b/ui/app/components/app/index.scss index 69ed2bb231f1..cff447a65c5e 100644 --- a/ui/app/components/app/index.scss +++ b/ui/app/components/app/index.scss @@ -59,6 +59,7 @@ @import '../ui/dropdown/dropdown'; @import 'permissions-connect-header/index'; @import 'permissions-connect-footer/index'; +@import 'tab-bar/index'; @import 'wallet-overview/index'; @import '../ui/account-mismatch-warning/index'; @import '../ui/icon-border/icon-border'; diff --git a/ui/app/components/app/tab-bar/index.js b/ui/app/components/app/tab-bar/index.js new file mode 100644 index 000000000000..74f9a752921d --- /dev/null +++ b/ui/app/components/app/tab-bar/index.js @@ -0,0 +1 @@ +export { default } from './tab-bar' diff --git a/ui/app/components/app/tab-bar/index.scss b/ui/app/components/app/tab-bar/index.scss new file mode 100644 index 000000000000..0b9d0605d250 --- /dev/null +++ b/ui/app/components/app/tab-bar/index.scss @@ -0,0 +1,80 @@ +.tab-bar { + display: flex; + flex-direction: column; + justify-content: flex-start; + + + &__tab { + display: flex; + flex-flow: row nowrap; + align-items: flex-start; + min-width: 0; + flex: 0 0 auto; + box-sizing: border-box; + font-size: 16px; + padding: 16px 24px; + opacity: 0.5; + transition: opacity 200ms ease-in-out; + + @media screen and (min-width: 576px) { + &:hover { + opacity: 0.4; + } + + &:active { + opacity: 0.6; + } + } + + @media screen and (max-width: 575px) { + font-size: 18px; + padding: 24px; + border-bottom: 1px solid $alto; + opacity: 1; + } + + &__content { + flex: 1 1 auto; + width: 0; + + &__description { + display: none; + + @media screen and (max-width: 575px) { + display: block; + font-size: 14px; + font-weight: 300; + margin-top: 8px; + min-height: 14px; + } + } + } + + &__caret { + display: none; + + @media screen and (max-width: 575px) { + display: block; + background-image: url('/images/caret-right.svg'); + width: 36px; + height: 36px; + opacity: 0.5; + background-size: contain; + background-repeat: no-repeat; + background-position: center; + + [dir='rtl'] & { + transform: rotate(180deg); + } + } + } + + &--active { + opacity: 1 !important; + } + } + + &__grow-tab { + flex-grow: 1; + } +} diff --git a/ui/app/components/app/tab-bar.js b/ui/app/components/app/tab-bar/tab-bar.js similarity index 100% rename from ui/app/components/app/tab-bar.js rename to ui/app/components/app/tab-bar/tab-bar.js diff --git a/ui/app/css/itcss/components/index.scss b/ui/app/css/itcss/components/index.scss index 1936706dcb72..f9ca7c41d295 100644 --- a/ui/app/css/itcss/components/index.scss +++ b/ui/app/css/itcss/components/index.scss @@ -14,7 +14,6 @@ // Tx List and Sections @import './menu'; @import './gas-slider'; -@import './tab-bar'; @import './simple-dropdown'; @import './request-signature'; @import './request-encryption-public-key'; diff --git a/ui/app/css/itcss/components/tab-bar.scss b/ui/app/css/itcss/components/tab-bar.scss deleted file mode 100644 index 2579b279fd52..000000000000 --- a/ui/app/css/itcss/components/tab-bar.scss +++ /dev/null @@ -1,79 +0,0 @@ -.tab-bar { - display: flex; - flex-direction: column; - justify-content: flex-start; -} - -.tab-bar__tab { - display: flex; - flex-flow: row nowrap; - align-items: flex-start; - min-width: 0; - flex: 0 0 auto; - box-sizing: border-box; - font-size: 16px; - padding: 16px 24px; - opacity: 0.5; - transition: opacity 200ms ease-in-out; - - @media screen and (min-width: 576px) { - &:hover { - opacity: 0.4; - } - - &:active { - opacity: 0.6; - } - } - - @media screen and (max-width: 575px) { - font-size: 18px; - padding: 24px; - border-bottom: 1px solid $alto; - opacity: 1; - } - - &__content { - flex: 1 1 auto; - width: 0; - - &__description { - display: none; - - @media screen and (max-width: 575px) { - display: block; - font-size: 14px; - font-weight: 300; - margin-top: 8px; - min-height: 14px; - } - } - } - - &__caret { - display: none; - - @media screen and (max-width: 575px) { - display: block; - background-image: url('/images/caret-right.svg'); - width: 36px; - height: 36px; - opacity: 0.5; - background-size: contain; - background-repeat: no-repeat; - background-position: center; - - [dir='rtl'] & { - transform: rotate(180deg); - } - } - } - - &--active { - opacity: 1 !important; - } -} - -.tab-bar__grow-tab { - flex-grow: 1; -} From 01f69d7e7a2dd19b471d351883d000dee42a992b Mon Sep 17 00:00:00 2001 From: Brad Decker Date: Tue, 11 Aug 2020 11:07:47 -0500 Subject: [PATCH 064/107] colocate account modal styles with their components (#9150) This one gets a bit more complicated because the styles were interwoven and needed to be untangled to be moved. Essentially, though, the goal is to put the styles where they make the most sense and colocate them with their components. --- test/e2e/ethereum-on.spec.js | 2 +- test/e2e/from-import-ui.spec.js | 4 +- test/e2e/incremental-security.spec.js | 2 +- test/e2e/metamask-ui.spec.js | 2 +- test/e2e/permissions.spec.js | 2 +- test/e2e/signature-request.spec.js | 2 +- .../account-details-modal.component.js | 10 +- .../modals/account-details-modal/index.scss | 37 ++++ .../account-modal-container.component.js | 13 +- .../modals/account-modal-container/index.scss | 50 +++++ .../export-private-key-modal.component.js | 29 +-- .../export-private-key-modal/index.scss | 106 ++++++++++ ui/app/components/app/modals/index.scss | 3 + .../tests/account-details-modal.test.js | 10 +- ui/app/css/itcss/components/modal.scss | 184 ------------------ 15 files changed, 236 insertions(+), 220 deletions(-) create mode 100644 ui/app/components/app/modals/account-details-modal/index.scss create mode 100644 ui/app/components/app/modals/account-modal-container/index.scss create mode 100644 ui/app/components/app/modals/export-private-key-modal/index.scss diff --git a/test/e2e/ethereum-on.spec.js b/test/e2e/ethereum-on.spec.js index 361579cd2c23..8c0426a0d126 100644 --- a/test/e2e/ethereum-on.spec.js +++ b/test/e2e/ethereum-on.spec.js @@ -93,7 +93,7 @@ describe('MetaMask', function () { publicAddress = await addressInput.getAttribute('value') const accountModal = await driver.findElement(By.css('span .modal')) - await driver.clickElement(By.css('.account-modal-close')) + await driver.clickElement(By.css('.account-modal__close')) await driver.wait(until.stalenessOf(accountModal)) await driver.delay(regularDelayMs) diff --git a/test/e2e/from-import-ui.spec.js b/test/e2e/from-import-ui.spec.js index 78bdd03528f7..432432b3e579 100644 --- a/test/e2e/from-import-ui.spec.js +++ b/test/e2e/from-import-ui.spec.js @@ -105,7 +105,7 @@ describe('Using MetaMask with an existing account', function () { const [address] = await driver.findElements(By.css('.readonly-input__input')) assert.equal(await address.getAttribute('value'), testAddress) - await driver.clickElement(By.css('.account-modal-close')) + await driver.clickElement(By.css('.account-modal__close')) await driver.delay(largeDelayMs) }) @@ -116,7 +116,7 @@ describe('Using MetaMask with an existing account', function () { const detailModal = await driver.findElement(By.css('span .modal')) await driver.delay(regularDelayMs) - await driver.clickElement(By.css('.account-modal-close')) + await driver.clickElement(By.css('.account-modal__close')) await driver.wait(until.stalenessOf(detailModal)) await driver.delay(regularDelayMs) }) diff --git a/test/e2e/incremental-security.spec.js b/test/e2e/incremental-security.spec.js index a6482c58d90f..7008ec69cb33 100644 --- a/test/e2e/incremental-security.spec.js +++ b/test/e2e/incremental-security.spec.js @@ -100,7 +100,7 @@ describe('MetaMask', function () { const accountModal = await driver.findElement(By.css('span .modal')) - await driver.clickElement(By.css('.account-modal-close')) + await driver.clickElement(By.css('.account-modal__close')) await driver.wait(until.stalenessOf(accountModal)) await driver.delay(regularDelayMs) diff --git a/test/e2e/metamask-ui.spec.js b/test/e2e/metamask-ui.spec.js index d501bdb3ac38..dfff8e49bac1 100644 --- a/test/e2e/metamask-ui.spec.js +++ b/test/e2e/metamask-ui.spec.js @@ -125,7 +125,7 @@ describe('MetaMask', function () { await driver.delay(regularDelayMs) const accountModal = await driver.findElement(By.css('span .modal')) - await driver.clickElement(By.css('.account-modal-close')) + await driver.clickElement(By.css('.account-modal__close')) await driver.wait(until.stalenessOf(accountModal)) await driver.delay(regularDelayMs) diff --git a/test/e2e/permissions.spec.js b/test/e2e/permissions.spec.js index 3d45e447784c..cd6cef45318d 100644 --- a/test/e2e/permissions.spec.js +++ b/test/e2e/permissions.spec.js @@ -94,7 +94,7 @@ describe('MetaMask', function () { publicAddress = await addressInput.getAttribute('value') const accountModal = await driver.findElement(By.css('span .modal')) - await driver.clickElement(By.css('.account-modal-close')) + await driver.clickElement(By.css('.account-modal__close')) await driver.wait(until.stalenessOf(accountModal)) await driver.delay(regularDelayMs) diff --git a/test/e2e/signature-request.spec.js b/test/e2e/signature-request.spec.js index 9abac241b849..4ad70e15e69d 100644 --- a/test/e2e/signature-request.spec.js +++ b/test/e2e/signature-request.spec.js @@ -128,7 +128,7 @@ describe('MetaMask', function () { const newPublicAddress = await addressInput.getAttribute('value') const accountModal = await driver.findElement(By.css('span .modal')) - await driver.clickElement(By.css('.account-modal-close')) + await driver.clickElement(By.css('.account-modal__close')) await driver.wait(until.stalenessOf(accountModal)) await driver.delay(regularDelayMs) diff --git a/ui/app/components/app/modals/account-details-modal/account-details-modal.component.js b/ui/app/components/app/modals/account-details-modal/account-details-modal.component.js index fafdd8444b3b..b6647f4c2036 100644 --- a/ui/app/components/app/modals/account-details-modal/account-details-modal.component.js +++ b/ui/app/components/app/modals/account-details-modal/account-details-modal.component.js @@ -42,9 +42,9 @@ export default class AccountDetailsModal extends Component { } return ( - + setAccountLabel(address, label)} /> @@ -56,11 +56,11 @@ export default class AccountDetailsModal extends Component { }} /> -
+
@@ -44,6 +46,7 @@ AccountModalContainer.defaultProps = { } AccountModalContainer.propTypes = { + className: PropTypes.string, selectedIdentity: PropTypes.object.isRequired, showBackButton: PropTypes.bool, backButtonAction: PropTypes.func, diff --git a/ui/app/components/app/modals/account-modal-container/index.scss b/ui/app/components/app/modals/account-modal-container/index.scss new file mode 100644 index 000000000000..8981854ee811 --- /dev/null +++ b/ui/app/components/app/modals/account-modal-container/index.scss @@ -0,0 +1,50 @@ +// Account Modal Container +.account-modal { + &__container { + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: center; + position: relative; + padding: 5px 0 31px 0; + border: 1px solid $silver; + border-radius: 4px; + } + + &__back { + color: $dusty-gray; + position: absolute; + top: 13px; + left: 17px; + cursor: pointer; + } + + &__text { + margin-top: 2px; + font-size: 14px; + line-height: 18px; + } + + &__close { + font-size: 40px; + background-color: transparent; + color: $dusty-gray; + position: absolute; + cursor: pointer; + top: 10px; + right: 12px; + + &::after { + content: '\00D7'; + } + } + + & .identicon { + position: relative; + left: 0; + right: 0; + margin: 0 auto; + top: -32px; + margin-bottom: -32px; + } +} diff --git a/ui/app/components/app/modals/export-private-key-modal/export-private-key-modal.component.js b/ui/app/components/app/modals/export-private-key-modal/export-private-key-modal.component.js index 318dd0d5891b..ba30d5218919 100644 --- a/ui/app/components/app/modals/export-private-key-modal/export-private-key-modal.component.js +++ b/ui/app/components/app/modals/export-private-key-modal/export-private-key-modal.component.js @@ -47,7 +47,7 @@ export default class ExportPrivateKeyModal extends Component { renderPasswordLabel (privateKey) { return ( - + { privateKey ? this.context.t('copyPrivateKey') @@ -64,7 +64,7 @@ export default class ExportPrivateKeyModal extends Component { return ( this.setState({ password: event.target.value })} /> ) @@ -72,8 +72,8 @@ export default class ExportPrivateKeyModal extends Component { return ( copyToClipboard(plainKey)} @@ -83,12 +83,12 @@ export default class ExportPrivateKeyModal extends Component { renderButtons (privateKey, address, hideModal) { return ( -
+
{!privateKey && ( @@ -111,7 +111,7 @@ export default class ExportPrivateKeyModal extends Component { onClick={() => this.exportAccountAndGetPrivateKey(this.state.password, address)} type="secondary" large - className="export-private-key__button" + className="export-private-key-modal__button" disabled={!this.state.password} > {this.context.t('confirm')} @@ -139,27 +139,28 @@ export default class ExportPrivateKeyModal extends Component { return ( showAccountDetailModal()} > - {name} + {name} -
- {this.context.t('showPrivateKeys')} -
+
+ {this.context.t('showPrivateKeys')} +
{this.renderPasswordLabel(privateKey)} {this.renderPasswordInput(privateKey)} { (showWarning && warning) - ? {warning} + ? {warning} : null }
-
{this.context.t('privateKeyWarning')}
+
{this.context.t('privateKeyWarning')}
{this.renderButtons(privateKey, address, hideModal)} ) diff --git a/ui/app/components/app/modals/export-private-key-modal/index.scss b/ui/app/components/app/modals/export-private-key-modal/index.scss new file mode 100644 index 000000000000..8fd3c31336d2 --- /dev/null +++ b/ui/app/components/app/modals/export-private-key-modal/index.scss @@ -0,0 +1,106 @@ +.export-private-key-modal { + &__body-title { + margin-top: 16px; + margin-bottom: 16px; + font-size: 18px; + } + + &__divider { + width: 100%; + height: 1px; + margin: 19px 0 8px 0; + background-color: $alto; + } + + &__account-name { + margin-top: 9px; + font-size: 20px; + } + + &__password { + display: flex; + flex-direction: column; + } + + &__password-label, + &__password--error { + color: $scorpion; + font-size: 14px; + line-height: 18px; + margin-bottom: 10px; + } + + &__password--error { + color: $crimson; + margin-bottom: 0; + } + + &__password-input { + padding: 10px 0 13px 17px; + font-size: 16px; + line-height: 21px; + width: 291px; + height: 44px; + } + + &__password::-webkit-input-placeholder { + color: $dusty-gray; + } + + &__password--warning { + border-radius: 8px; + background-color: #fff6f6; + font-size: 12px; + font-weight: 500; + line-height: 15px; + color: $crimson; + width: 292px; + padding: 9px 15px; + margin-top: 18px; + } + + &__password-display-wrapper { + height: 80px; + width: 291px; + border: 1px solid $silver; + border-radius: 2px; + } + + &__password-display-textarea { + color: $crimson; + font-size: 16px; + line-height: 21px; + border: none; + height: 75px; + width: 100%; + overflow: hidden; + resize: none; + padding: 9px 13px 8px; + } + + &__buttons { + display: flex; + flex-direction: row; + justify-content: center; + } + + &__button { + margin-top: 17px; + width: 141px; + min-width: initial; + } + + &__button--cancel { + margin-right: 15px; + } + + & .ellip-address-wrapper { + display: flex; + justify-content: center; + border: 1px solid $alto; + padding: 5px 10px; + margin-top: 7px; + width: 286px; + } +} + diff --git a/ui/app/components/app/modals/index.scss b/ui/app/components/app/modals/index.scss index cf5a9d66b73a..84ad997b6eb5 100644 --- a/ui/app/components/app/modals/index.scss +++ b/ui/app/components/app/modals/index.scss @@ -1,6 +1,9 @@ +@import 'account-details-modal/index'; +@import 'account-modal-container/index'; @import 'cancel-transaction/index'; @import 'confirm-remove-account/index'; @import 'deposit-ether-modal/index'; +@import 'export-private-key-modal/index'; @import 'hide-token-confirmation-modal/index'; @import 'notification-modal/index'; @import 'qr-scanner/index'; diff --git a/ui/app/components/app/modals/tests/account-details-modal.test.js b/ui/app/components/app/modals/tests/account-details-modal.test.js index 3c0dd8f2572c..505b823cd12c 100644 --- a/ui/app/components/app/modals/tests/account-details-modal.test.js +++ b/ui/app/components/app/modals/tests/account-details-modal.test.js @@ -46,7 +46,7 @@ describe('Account Details Modal', function () { }) it('sets account label when changing default account label', function () { - const accountLabel = wrapper.find('.account-modal__name').first() + const accountLabel = wrapper.find('.account-details-modal__name').first() accountLabel.simulate('submit', 'New Label') assert(props.setAccountLabel.calledOnce) @@ -54,7 +54,7 @@ describe('Account Details Modal', function () { }) it('opens new tab when view block explorer is clicked', function () { - const modalButton = wrapper.find('.account-modal__button') + const modalButton = wrapper.find('.account-details-modal__button') const etherscanLink = modalButton.first() etherscanLink.simulate('click') @@ -62,7 +62,7 @@ describe('Account Details Modal', function () { }) it('shows export private key modal when clicked', function () { - const modalButton = wrapper.find('.account-modal__button') + const modalButton = wrapper.find('.account-details-modal__button') const etherscanLink = modalButton.last() etherscanLink.simulate('click') @@ -73,9 +73,9 @@ describe('Account Details Modal', function () { const blockExplorerUrl = 'https://block.explorer' wrapper.setProps({ rpcPrefs: { blockExplorerUrl } }) - const modalButton = wrapper.find('.account-modal__button') + const modalButton = wrapper.find('.account-details-modal__button') const blockExplorerLink = modalButton.first() - assert.equal(blockExplorerLink.html(), '') + assert.equal(blockExplorerLink.html(), '') }) }) diff --git a/ui/app/css/itcss/components/modal.scss b/ui/app/css/itcss/components/modal.scss index 81b41e73c4d7..1dbf63e66f20 100644 --- a/ui/app/css/itcss/components/modal.scss +++ b/ui/app/css/itcss/components/modal.scss @@ -28,187 +28,3 @@ .modal > div:focus { outline: none !important; } - -// Account Modal Container -.account-modal-container { - display: flex; - flex-direction: column; - justify-content: flex-start; - align-items: center; - position: relative; - padding: 5px 0 31px 0; - border: 1px solid $silver; - border-radius: 4px; - - button { - cursor: pointer; - } -} - -.account-modal-back { - color: $dusty-gray; - position: absolute; - top: 13px; - left: 17px; - cursor: pointer; - - &__text { - margin-top: 2px; - font-size: 14px; - line-height: 18px; - } -} - -.account-modal-close { - font-size: 40px; - background-color: transparent; - color: $dusty-gray; - position: absolute; - top: 10px; - right: 12px; -} - -.account-modal-close::after { - content: '\00D7'; -} - -.account-modal-container .identicon { - position: relative; - left: 0; - right: 0; - margin: 0 auto; - top: -32px; - margin-bottom: -32px; -} - - -// Account Details Modal - -.account-modal-container { - .qr-code__header { - margin-top: 9px; - font-size: 20px; - } - - .qr-code__wrapper { - margin-top: 5px; - } - - .ellip-address-wrapper { - display: flex; - justify-content: center; - border: 1px solid $alto; - padding: 5px 10px; - margin-top: 7px; - width: 286px; - } - - .account-modal__button { - margin-top: 17px; - padding: 10px 22px; - width: 286px; - } -} - -.account-modal-divider { - width: 100%; - height: 1px; - margin: 19px 0 8px 0; - background-color: $alto; -} - -// Export Private Key Modal - -.account-modal-container .account-name { - margin-top: 9px; - font-size: 20px; -} - -.account-modal-container .modal-body-title { - margin-top: 16px; - margin-bottom: 16px; - font-size: 18px; -} - -.account-modal__name { - margin-top: 9px; - font-size: 20px; -} - -.private-key-password { - display: flex; - flex-direction: column; -} - -.private-key-password-label, -.private-key-password-error { - color: $scorpion; - font-size: 14px; - line-height: 18px; - margin-bottom: 10px; -} - -.private-key-password-error { - color: $crimson; - margin-bottom: 0; -} - -.private-key-password-input { - padding: 10px 0 13px 17px; - font-size: 16px; - line-height: 21px; - width: 291px; - height: 44px; -} - -.private-key-password::-webkit-input-placeholder { - color: $dusty-gray; -} - -.private-key-password-warning { - border-radius: 8px; - background-color: #fff6f6; - font-size: 12px; - font-weight: 500; - line-height: 15px; - color: $crimson; - width: 292px; - padding: 9px 15px; - margin-top: 18px; -} - -.export-private-key-buttons { - display: flex; - flex-direction: row; - justify-content: center; -} - -.export-private-key__button { - margin-top: 17px; - width: 141px; - min-width: initial; -} - -.export-private-key__button--cancel { - margin-right: 15px; -} - -.private-key-password-display-wrapper { - height: 80px; - width: 291px; - border: 1px solid $silver; - border-radius: 2px; -} - -.private-key-password-display-textarea { - color: $crimson; - font-size: 16px; - line-height: 21px; - border: none; - height: 75px; - width: 100%; - overflow: hidden; - resize: none; - padding: 9px 13px 8px; -} - From ef68a7963c30869b374f902b6508b142788adb68 Mon Sep 17 00:00:00 2001 From: Erik Marks Date: Tue, 11 Aug 2020 09:17:50 -0700 Subject: [PATCH 065/107] fix account modal back styling --- .../account-modal-container.component.js | 2 +- .../app/modals/account-modal-container/index.scss | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/ui/app/components/app/modals/account-modal-container/account-modal-container.component.js b/ui/app/components/app/modals/account-modal-container/account-modal-container.component.js index d1fbe0b7e78b..fb8cee58c22d 100644 --- a/ui/app/components/app/modals/account-modal-container/account-modal-container.component.js +++ b/ui/app/components/app/modals/account-modal-container/account-modal-container.component.js @@ -25,7 +25,7 @@ export default function AccountModalContainer (props, context) { {showBackButton && (
- {' ' + context.t('back')} + {context.t('back')}
)} + , { context: { t: (str1, str2) => (str2 ? str1 + str2 : str1) } }, ) diff --git a/ui/app/components/app/transaction-list-item-details/tests/transaction-list-item-details.component.test.js b/ui/app/components/app/transaction-list-item-details/tests/transaction-list-item-details.component.test.js index 0c9726aef166..f0e3edf4df1f 100644 --- a/ui/app/components/app/transaction-list-item-details/tests/transaction-list-item-details.component.test.js +++ b/ui/app/components/app/transaction-list-item-details/tests/transaction-list-item-details.component.test.js @@ -34,7 +34,7 @@ describe('TransactionListItemDetails Component', function () { title="Test Transaction Details" recipientAddress="0x1" senderAddress="0x2" - tryReverseResolveAddress={() => {}} + tryReverseResolveAddress={() => undefined} transactionGroup={transactionGroup} senderNickname="sender-nickname" recipientNickname="recipient-nickname" @@ -77,7 +77,7 @@ describe('TransactionListItemDetails Component', function () { {}} + tryReverseResolveAddress={() => undefined} transactionGroup={transactionGroup} showSpeedUp senderNickname="sender-nickname" @@ -117,7 +117,7 @@ describe('TransactionListItemDetails Component', function () { {}} + tryReverseResolveAddress={() => undefined} transactionGroup={transactionGroup} senderNickname="sender-nickname" recipientNickname="recipient-nickname" @@ -159,7 +159,7 @@ describe('TransactionListItemDetails Component', function () { {}} + tryReverseResolveAddress={() => undefined} transactionGroup={transactionGroup} senderNickname="sender-nickname" recipientNickname="recipient-nickname" diff --git a/ui/app/hooks/tests/useCancelTransaction.test.js b/ui/app/hooks/tests/useCancelTransaction.test.js index be2256cd9df0..920b9e223378 100644 --- a/ui/app/hooks/tests/useCancelTransaction.test.js +++ b/ui/app/hooks/tests/useCancelTransaction.test.js @@ -46,7 +46,7 @@ describe('useCancelTransaction', function () { it(`should return a function that kicks off cancellation for id ${transactionId}`, function () { const { result } = renderHook(() => useCancelTransaction(transactionGroup)) assert.equal(typeof result.current[1], 'function') - result.current[1]({ preventDefault: () => {}, stopPropagation: () => {} }) + result.current[1]({ preventDefault: () => undefined, stopPropagation: () => undefined }) assert.equal( dispatch.calledWith( showModal({ @@ -90,7 +90,7 @@ describe('useCancelTransaction', function () { it(`should return a function that kicks off cancellation for id ${transactionId}`, function () { const { result } = renderHook(() => useCancelTransaction(transactionGroup)) assert.equal(typeof result.current[1], 'function') - result.current[1]({ preventDefault: () => {}, stopPropagation: () => {} }) + result.current[1]({ preventDefault: () => undefined, stopPropagation: () => undefined }) assert.equal( dispatch.calledWith( showModal({ diff --git a/ui/app/hooks/tests/useRetryTransaction.test.js b/ui/app/hooks/tests/useRetryTransaction.test.js index d6c55d0f8b7e..e4e8160a8948 100644 --- a/ui/app/hooks/tests/useRetryTransaction.test.js +++ b/ui/app/hooks/tests/useRetryTransaction.test.js @@ -12,7 +12,7 @@ describe('useRetryTransaction', function () { describe('when transaction meets retry enabled criteria', function () { const dispatch = sinon.spy(() => Promise.resolve({ blockTime: 0 })) const trackEvent = sinon.spy() - const event = { preventDefault: () => {}, stopPropagation: () => {} } + const event = { preventDefault: () => undefined, stopPropagation: () => undefined } before(function () { sinon.stub(reactRedux, 'useDispatch').returns(dispatch) diff --git a/ui/app/pages/add-token/tests/add-token.test.js b/ui/app/pages/add-token/tests/add-token.test.js index fa9e4bd193a1..da4a42f20387 100644 --- a/ui/app/pages/add-token/tests/add-token.test.js +++ b/ui/app/pages/add-token/tests/add-token.test.js @@ -19,7 +19,7 @@ describe('Add Token', function () { const props = { history: { - push: sinon.stub().callsFake(() => {}), + push: sinon.stub().callsFake(() => undefined), }, setPendingTokens: sinon.spy(), clearPendingTokens: sinon.spy(), diff --git a/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/tests/reveal-seed-phrase.test.js b/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/tests/reveal-seed-phrase.test.js index a9ac7b66cc3b..dfc0457786c0 100644 --- a/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/tests/reveal-seed-phrase.test.js +++ b/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/tests/reveal-seed-phrase.test.js @@ -23,7 +23,7 @@ describe('Reveal Seed Phrase', function () { , { context: { t: (str) => str, - metricsEvent: () => {}, + metricsEvent: () => undefined, }, }, ) diff --git a/ui/app/pages/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-component.test.js b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-component.test.js index 0b964dfca197..008bcec30366 100644 --- a/ui/app/pages/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-component.test.js +++ b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-component.test.js @@ -13,7 +13,7 @@ describe('AmountMaxButton Component', function () { setMaxModeTo: sinon.spy(), } - const MOCK_EVENT = { preventDefault: () => {} } + const MOCK_EVENT = { preventDefault: () => undefined } before(function () { sinon.spy(AmountMaxButton.prototype, 'setMaxAmount') @@ -33,7 +33,7 @@ describe('AmountMaxButton Component', function () { ), { context: { t: (str) => str + '_t', - metricsEvent: () => {}, + metricsEvent: () => undefined, }, }) instance = wrapper.instance() diff --git a/ui/app/pages/send/send-content/send-amount-row/tests/send-amount-row-component.test.js b/ui/app/pages/send/send-content/send-amount-row/tests/send-amount-row-component.test.js index 4f99b8909c34..b22819f258dc 100644 --- a/ui/app/pages/send/send-content/send-amount-row/tests/send-amount-row-component.test.js +++ b/ui/app/pages/send/send-content/send-amount-row/tests/send-amount-row-component.test.js @@ -158,7 +158,7 @@ function shallowRenderSendAmountRow () { updateGasFeeError={updateGasFeeError} updateSendAmount={updateSendAmount} updateSendAmountError={updateSendAmountError} - updateGas={() => {}} + updateGas={() => undefined} /> ), { context: { t: (str) => str + '_t' } }) const instance = wrapper.instance() diff --git a/ui/app/pages/send/send-footer/tests/send-footer-component.test.js b/ui/app/pages/send/send-footer/tests/send-footer-component.test.js index d200e2dd71a3..ba2c8c1e556f 100644 --- a/ui/app/pages/send/send-footer/tests/send-footer-component.test.js +++ b/ui/app/pages/send/send-footer/tests/send-footer-component.test.js @@ -18,7 +18,7 @@ describe('SendFooter Component', function () { const historySpies = { push: sinon.spy(), } - const MOCK_EVENT = { preventDefault: () => {} } + const MOCK_EVENT = { preventDefault: () => undefined } before(function () { sinon.spy(SendFooter.prototype, 'onCancel') diff --git a/ui/app/pages/send/tests/send-component.test.js b/ui/app/pages/send/tests/send-component.test.js index 757b8fa636a5..43c296009f2e 100644 --- a/ui/app/pages/send/tests/send-component.test.js +++ b/ui/app/pages/send/tests/send-component.test.js @@ -63,12 +63,12 @@ describe('Send Component', function () { tokenBalance="mockTokenBalance" tokenContract={{ method: 'mockTokenMethod' }} updateAndSetGasLimit={propsMethodSpies.updateAndSetGasLimit} - qrCodeDetected={() => {}} - scanQrCode={() => {}} - updateSendEnsResolution={() => {}} - updateSendEnsResolutionError={() => {}} + qrCodeDetected={() => undefined} + scanQrCode={() => undefined} + updateSendEnsResolution={() => undefined} + updateSendEnsResolutionError={() => undefined} updateSendErrors={propsMethodSpies.updateSendErrors} - updateSendTo={() => {}} + updateSendTo={() => undefined} updateSendTokenBalance={propsMethodSpies.updateSendTokenBalance} resetSendState={propsMethodSpies.resetSendState} updateToNicknameIfNecessary={propsMethodSpies.updateToNicknameIfNecessary} diff --git a/ui/app/pages/send/tests/send-container.test.js b/ui/app/pages/send/tests/send-container.test.js index 84a138d92b23..ae9cfd2d4251 100644 --- a/ui/app/pages/send/tests/send-container.test.js +++ b/ui/app/pages/send/tests/send-container.test.js @@ -21,7 +21,7 @@ proxyquire('../send.container.js', { return () => ({}) }, }, - 'react-router-dom': { withRouter: () => {} }, + 'react-router-dom': { withRouter: () => undefined }, 'redux': { compose: (_, arg2) => () => arg2() }, '../../store/actions': actionSpies, '../../ducks/send/send.duck': duckActionSpies, diff --git a/ui/app/pages/settings/advanced-tab/tests/advanced-tab-component.test.js b/ui/app/pages/settings/advanced-tab/tests/advanced-tab-component.test.js index 69fe064c8588..cd8e9a200ab4 100644 --- a/ui/app/pages/settings/advanced-tab/tests/advanced-tab-component.test.js +++ b/ui/app/pages/settings/advanced-tab/tests/advanced-tab-component.test.js @@ -10,10 +10,10 @@ describe('AdvancedTab Component', function () { const root = shallow( {}} - setIpfsGateway={() => {}} - setShowFiatConversionOnTestnetsPreference={() => {}} - setThreeBoxSyncingPermission={() => {}} + setAutoLockTimeLimit={() => undefined} + setIpfsGateway={() => undefined} + setShowFiatConversionOnTestnetsPreference={() => undefined} + setThreeBoxSyncingPermission={() => undefined} threeBoxDisabled threeBoxSyncingAllowed={false} />, @@ -33,9 +33,9 @@ describe('AdvancedTab Component', function () { {}} - setShowFiatConversionOnTestnetsPreference={() => {}} - setThreeBoxSyncingPermission={() => {}} + setIpfsGateway={() => undefined} + setShowFiatConversionOnTestnetsPreference={() => undefined} + setThreeBoxSyncingPermission={() => undefined} threeBoxDisabled threeBoxSyncingAllowed={false} />, diff --git a/ui/app/pages/settings/security-tab/tests/security-tab.test.js b/ui/app/pages/settings/security-tab/tests/security-tab.test.js index 1ac996d050dd..d5720aa6a0e2 100644 --- a/ui/app/pages/settings/security-tab/tests/security-tab.test.js +++ b/ui/app/pages/settings/security-tab/tests/security-tab.test.js @@ -28,7 +28,7 @@ describe('Security Tab', function () { , { context: { t: (str) => str, - metricsEvent: () => {}, + metricsEvent: () => undefined, }, }, ) From 884775cf710fe4f9e001d54b00249d7ac9e8b0d6 Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Fri, 14 Aug 2020 09:17:43 -0230 Subject: [PATCH 096/107] Fix no-negated-condition issues (#9222) See [`no-negated-condition`](https://eslint.org/docs/rules/no-negated-condition) for more information. This change enables `no-negated-condition` and fixes the issues raised by the rule. --- .eslintrc.js | 1 + app/scripts/controllers/transactions/index.js | 6 +-- .../transactions/tx-state-manager.js | 2 +- app/scripts/lib/freezeGlobals.js | 6 +-- app/scripts/lib/util.js | 14 ++--- app/scripts/metamask-controller.js | 19 +++---- development/metamaskbot-build-announce.js | 8 +-- development/sentry-publish.js | 10 ++-- test/unit/app/nodeify-test.js | 5 +- ...confirm-page-container-header.component.js | 34 ++++++------- .../connected-accounts-list.component.js | 6 +-- .../advanced-tab-content.component.js | 6 +-- .../basic-tab-content.component.js | 6 +-- .../gas-price-button-group.component.js | 8 +-- .../app/info-box/info-box.component.js | 10 ++-- .../components/app/modal/modal.component.js | 51 ++++++++++--------- .../edit-approval-permission.component.js | 2 +- .../modals/qr-scanner/qr-scanner.component.js | 8 +-- .../transaction-breakdown.component.js | 19 +++---- .../transaction-list-item.component.js | 16 +++--- ui/app/ducks/gas/gas.duck.js | 49 +++++++++--------- ui/app/helpers/utils/metametrics.util.js | 22 ++++---- ui/app/hooks/tests/useTokenData.test.js | 6 +-- .../confirm-decrypt-message.component.js | 8 +-- .../metametrics-opt-in.component.js | 4 +- .../onboarding-initiator-util.js | 6 +-- .../confirm-seed-phrase.component.js | 6 +-- .../seed-phrase/seed-phrase.component.js | 6 +-- .../mobile-sync/mobile-sync.component.js | 12 ++--- .../permissions-connect.component.js | 16 +++--- ui/app/pages/routes/routes.component.js | 8 +-- .../add-recipient/ens-input.component.js | 2 +- .../amount-max-button.component.js | 8 +-- ui/app/pages/send/send.container.js | 6 +-- ui/app/selectors/permissions.js | 6 +-- 35 files changed, 204 insertions(+), 198 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 94ddd7bef3c3..c7997bce97ed 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -57,6 +57,7 @@ module.exports = { 'no-eq-null': 'error', 'no-global-assign': 'error', 'no-loop-func': 'error', + 'no-negated-condition': 'error', 'no-nested-ternary': 'error', 'no-plusplus': ['error', { 'allowForLoopAfterthoughts': true }], 'no-process-exit': 'error', diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js index 24c5a1edb036..795c7c6ea20a 100644 --- a/app/scripts/controllers/transactions/index.js +++ b/app/scripts/controllers/transactions/index.js @@ -560,9 +560,9 @@ export default class TransactionController extends EventEmitter { // It seems that sometimes the numerical values being returned from // this.query.getTransactionReceipt are BN instances and not strings. - const gasUsed = typeof txReceipt.gasUsed !== 'string' - ? txReceipt.gasUsed.toString(16) - : txReceipt.gasUsed + const gasUsed = typeof txReceipt.gasUsed === 'string' + ? txReceipt.gasUsed + : txReceipt.gasUsed.toString(16) txMeta.txReceipt = { ...txReceipt, diff --git a/app/scripts/controllers/transactions/tx-state-manager.js b/app/scripts/controllers/transactions/tx-state-manager.js index d064c4d7795f..7de54fb9cad8 100644 --- a/app/scripts/controllers/transactions/tx-state-manager.js +++ b/app/scripts/controllers/transactions/tx-state-manager.js @@ -424,7 +424,7 @@ export default class TransactionStateManager extends EventEmitter { @param {erroObject} err - error object */ setTxStatusFailed (txId, err) { - const error = !err ? new Error('Internal metamask failure') : err + const error = err || new Error('Internal metamask failure') const txMeta = this.getTx(txId) txMeta.err = { diff --git a/app/scripts/lib/freezeGlobals.js b/app/scripts/lib/freezeGlobals.js index 6599088d68de..b17a84781523 100644 --- a/app/scripts/lib/freezeGlobals.js +++ b/app/scripts/lib/freezeGlobals.js @@ -30,11 +30,11 @@ function freeze (target, key, value, enumerable = true) { configurable: false, writable: false, } - if (value !== undefined) { + if (value === undefined) { + target[key] = deepFreeze(target[key]) + } else { opts.value = deepFreeze(value) opts.enumerable = enumerable - } else { - target[key] = deepFreeze(target[key]) } Object.defineProperty(target, key, opts) diff --git a/app/scripts/lib/util.js b/app/scripts/lib/util.js index e52058a86201..ab8653669170 100644 --- a/app/scripts/lib/util.js +++ b/app/scripts/lib/util.js @@ -55,19 +55,19 @@ const getEnvironmentType = (url = window.location.href) => getEnvironmentTypeMem */ const getPlatform = (_) => { const ua = window.navigator.userAgent - if (ua.search('Firefox') !== -1) { - return PLATFORM_FIREFOX - } else { + if (ua.search('Firefox') === -1) { if (window && window.chrome && window.chrome.ipcRenderer) { return PLATFORM_BRAVE - } else if (ua.search('Edge') !== -1) { + } + if (ua.search('Edge') !== -1) { return PLATFORM_EDGE - } else if (ua.search('OPR') !== -1) { + } + if (ua.search('OPR') !== -1) { return PLATFORM_OPERA - } else { - return PLATFORM_CHROME } + return PLATFORM_CHROME } + return PLATFORM_FIREFOX } /** diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index dcf5524343fa..1e4af85b1678 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -710,15 +710,16 @@ export default class MetamaskController extends EventEmitter { Object.keys(accountTokens).forEach((address) => { const checksummedAddress = ethUtil.toChecksumAddress(address) filteredAccountTokens[checksummedAddress] = {} - Object.keys(accountTokens[address]).forEach( - (networkType) => (filteredAccountTokens[checksummedAddress][networkType] = networkType !== 'mainnet' ? - accountTokens[address][networkType] : - accountTokens[address][networkType].filter(({ address }) => { - const tokenAddress = ethUtil.toChecksumAddress(address) - return contractMap[tokenAddress] ? contractMap[tokenAddress].erc20 : true - }) - ), - ) + Object.keys(accountTokens[address]).forEach((networkType) => { + filteredAccountTokens[checksummedAddress][networkType] = networkType === 'mainnet' + ? ( + accountTokens[address][networkType].filter(({ address }) => { + const tokenAddress = ethUtil.toChecksumAddress(address) + return contractMap[tokenAddress] ? contractMap[tokenAddress].erc20 : true + }) + ) + : accountTokens[address][networkType] + }) }) const preferences = { diff --git a/development/metamaskbot-build-announce.js b/development/metamaskbot-build-announce.js index 788929fb3070..2414901dedc5 100755 --- a/development/metamaskbot-build-announce.js +++ b/development/metamaskbot-build-announce.js @@ -81,10 +81,7 @@ async function start () { const summaryPlatform = 'chrome' const summaryPage = 'home' let commentBody - if (!benchmarkResults[summaryPlatform]) { - console.log(`No results for ${summaryPlatform} found; skipping benchmark`) - commentBody = artifactsBody - } else { + if (benchmarkResults[summaryPlatform]) { try { const summaryPageLoad = Math.round(parseFloat(benchmarkResults[summaryPlatform][summaryPage].average.load)) const summaryPageLoadMarginOfError = Math.round(parseFloat(benchmarkResults[summaryPlatform][summaryPage].marginOfError.load)) @@ -147,6 +144,9 @@ async function start () { console.error(`Error constructing benchmark results: '${error}'`) commentBody = artifactsBody } + } else { + console.log(`No results for ${summaryPlatform} found; skipping benchmark`) + commentBody = artifactsBody } const JSON_PAYLOAD = JSON.stringify({ body: commentBody }) diff --git a/development/sentry-publish.js b/development/sentry-publish.js index 81034374e50e..650ac37fdf75 100644 --- a/development/sentry-publish.js +++ b/development/sentry-publish.js @@ -25,12 +25,14 @@ async function start () { // check if version has artifacts or not const versionHasArtifacts = versionAlreadyExists && await checkIfVersionHasArtifacts() - if (!versionHasArtifacts) { - // upload sentry source and sourcemaps - await exec(`./development/sentry-upload-artifacts.sh --release ${VERSION}`) - } else { + if (versionHasArtifacts) { console.log(`Version "${VERSION}" already has artifacts on Sentry, skipping sourcemap upload`) + return } + + // upload sentry source and sourcemaps + await exec(`./development/sentry-upload-artifacts.sh --release ${VERSION}`) + } async function checkIfAuthWorks () { diff --git a/test/unit/app/nodeify-test.js b/test/unit/app/nodeify-test.js index 4008309149d3..c8d1c46d90f7 100644 --- a/test/unit/app/nodeify-test.js +++ b/test/unit/app/nodeify-test.js @@ -16,9 +16,10 @@ describe('nodeify', function () { if (!err) { assert.equal(res, 'barbaz') done() - } else { - done(new Error(err.toString())) + return } + + done(new Error(err.toString())) }) }) diff --git a/ui/app/components/app/confirm-page-container/confirm-page-container-header/confirm-page-container-header.component.js b/ui/app/components/app/confirm-page-container/confirm-page-container-header/confirm-page-container-header.component.js index f1f10a797eb5..c72600274a09 100644 --- a/ui/app/components/app/confirm-page-container/confirm-page-container-header/confirm-page-container-header.component.js +++ b/ui/app/components/app/confirm-page-container/confirm-page-container-header/confirm-page-container-header.component.js @@ -30,8 +30,22 @@ export default function ConfirmPageContainerHeader ({ return (
- { !showAccountInHeader + { showAccountInHeader ? ( +
+
+ +
+
+ { shortenAddress(accountAddress) } +
+ +
+ ) + : (
) - : null - } - { showAccountInHeader - ? ( -
-
- -
-
- { shortenAddress(accountAddress) } -
- -
- ) - : null } { !isFullScreen && }
diff --git a/ui/app/components/app/connected-accounts-list/connected-accounts-list.component.js b/ui/app/components/app/connected-accounts-list/connected-accounts-list.component.js index 567af6b72b06..5509e9cd4751 100644 --- a/ui/app/components/app/connected-accounts-list/connected-accounts-list.component.js +++ b/ui/app/components/app/connected-accounts-list/connected-accounts-list.component.js @@ -149,9 +149,9 @@ export default class ConnectedAccountsList extends PureComponent { : null } action={ - address !== selectedAddress - ? this.renderListItemAction(address) - : null + address === selectedAddress + ? null + : this.renderListItemAction(address) } /> ) diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/advanced-tab-content.component.js b/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/advanced-tab-content.component.js index 19e3aa776432..91c36f0a4171 100644 --- a/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/advanced-tab-content.component.js +++ b/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/advanced-tab-content.component.js @@ -86,9 +86,9 @@ export default class AdvancedTabContent extends Component { ? (
{ t('liveGasPricePredictions') }
- {!gasEstimatesLoading - ? - : + {gasEstimatesLoading + ? + : }
{ t('slower') } diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/basic-tab-content/basic-tab-content.component.js b/ui/app/components/app/gas-customization/gas-modal-page-container/basic-tab-content/basic-tab-content.component.js index 94d22ee4a100..5a80e4c9820b 100644 --- a/ui/app/components/app/gas-customization/gas-modal-page-container/basic-tab-content/basic-tab-content.component.js +++ b/ui/app/components/app/gas-customization/gas-modal-page-container/basic-tab-content/basic-tab-content.component.js @@ -20,15 +20,15 @@ export default class BasicTabContent extends Component {
{ t('estimatedProcessingTimes') }
{ t('selectAHigherGasFee') }
- {!gasPriceButtonGroupProps.loading - ? ( + {gasPriceButtonGroupProps.loading + ? + : ( ) - : }
{ t('acceleratingATransaction') }
diff --git a/ui/app/components/app/gas-customization/gas-price-button-group/gas-price-button-group.component.js b/ui/app/components/app/gas-customization/gas-price-button-group/gas-price-button-group.component.js index b578ff1ed46a..8916c7e8e563 100644 --- a/ui/app/components/app/gas-customization/gas-price-button-group/gas-price-button-group.component.js +++ b/ui/app/components/app/gas-customization/gas-price-button-group/gas-price-button-group.component.js @@ -89,18 +89,18 @@ export default class GasPriceButtonGroup extends Component { } = this.props return ( - !buttonDataLoading - ? ( + buttonDataLoading + ?
{this.context.t('loading')}
+ : ( - { gasButtonInfo.map((obj, index) => this.renderButton(obj, buttonPropsAndFlags, index)) } + {gasButtonInfo.map((obj, index) => this.renderButton(obj, buttonPropsAndFlags, index))} ) - :
{ this.context.t('loading') }
) } } diff --git a/ui/app/components/app/info-box/info-box.component.js b/ui/app/components/app/info-box/info-box.component.js index 68aae5d11ba3..163869ef5245 100644 --- a/ui/app/components/app/info-box/info-box.component.js +++ b/ui/app/components/app/info-box/info-box.component.js @@ -29,17 +29,17 @@ export default class InfoBox extends Component { render () { const { title, description } = this.props - return !this.state.isShowing - ? null - : ( + return this.state.isShowing + ? (
this.handleClose()} /> -
{ title }
-
{ description }
+
{title}
+
{description}
) + : null } } diff --git a/ui/app/components/app/modal/modal.component.js b/ui/app/components/app/modal/modal.component.js index 6c45160fdc63..41e4ea694486 100644 --- a/ui/app/components/app/modal/modal.component.js +++ b/ui/app/components/app/modal/modal.component.js @@ -63,31 +63,32 @@ export default class Modal extends PureComponent {
{ children }
- { !hideFooter - ? ( -
- { - onCancel && ( - - ) - } - -
- ) - : null + { + hideFooter + ? null + : ( +
+ { + onCancel && ( + + ) + } + +
+ ) }
) diff --git a/ui/app/components/app/modals/edit-approval-permission/edit-approval-permission.component.js b/ui/app/components/app/modals/edit-approval-permission/edit-approval-permission.component.js index 9e1dafced8d4..fca21145a65c 100644 --- a/ui/app/components/app/modals/edit-approval-permission/edit-approval-permission.component.js +++ b/ui/app/components/app/modals/edit-approval-permission/edit-approval-permission.component.js @@ -204,7 +204,7 @@ export default class EditApprovalPermission extends PureComponent { return ( { - setCustomAmount(!selectedOptionIsUnlimited ? customSpendLimit : '') + setCustomAmount(selectedOptionIsUnlimited ? '' : customSpendLimit) hideModal() }} submitText={t('save')} diff --git a/ui/app/components/app/modals/qr-scanner/qr-scanner.component.js b/ui/app/components/app/modals/qr-scanner/qr-scanner.component.js index 63ca45b88932..fd79c9e41e4a 100644 --- a/ui/app/components/app/modals/qr-scanner/qr-scanner.component.js +++ b/ui/app/components/app/modals/qr-scanner/qr-scanner.component.js @@ -117,11 +117,11 @@ export default class QrScanner extends Component { const result = this.parseContent(content.text) if (!this.mounted) { return - } else if (result.type !== 'unknown') { + } else if (result.type === 'unknown') { + this.setState({ error: new Error(this.context.t('unknownQrCode')) }) + } else { this.props.qrCodeDetected(result) this.stopAndClose() - } else { - this.setState({ error: new Error(this.context.t('unknownQrCode')) }) } } catch (error) { if (!this.mounted) { @@ -248,7 +248,7 @@ export default class QrScanner extends Component { display: ready === READY_STATE.READY ? 'block' : 'none', }} /> - { ready !== READY_STATE.READY ? : null} + {ready === READY_STATE.READY ? null : }
diff --git a/ui/app/components/app/transaction-breakdown/transaction-breakdown.component.js b/ui/app/components/app/transaction-breakdown/transaction-breakdown.component.js index 85c24a3e3acc..a534ae1a6fcd 100644 --- a/ui/app/components/app/transaction-breakdown/transaction-breakdown.component.js +++ b/ui/app/components/app/transaction-breakdown/transaction-breakdown.component.js @@ -37,13 +37,14 @@ export default class TransactionBreakdown extends PureComponent { { t('transaction') }
- {typeof nonce !== 'undefined' - ? ( + {typeof nonce === 'undefined' + ? null + : ( - ) : null + ) } @@ -57,14 +58,14 @@ export default class TransactionBreakdown extends PureComponent { title={`${t('gasLimit')} (${t('units')})`} className="transaction-breakdown__row-title" > - {typeof gas !== 'undefined' - ? ( + {typeof gas === 'undefined' + ? '?' + : ( ) - : '?' } { @@ -81,8 +82,9 @@ export default class TransactionBreakdown extends PureComponent { ) } - {typeof gasPrice !== 'undefined' - ? ( + {typeof gasPrice === 'undefined' + ? '?' + : ( ) - : '?' } diff --git a/ui/app/components/app/transaction-list-item/transaction-list-item.component.js b/ui/app/components/app/transaction-list-item/transaction-list-item.component.js index bd6a61c913b8..c2c1279eb7b0 100644 --- a/ui/app/components/app/transaction-list-item/transaction-list-item.component.js +++ b/ui/app/components/app/transaction-list-item/transaction-list-item.component.js @@ -86,13 +86,15 @@ export default function TransactionListItem ({ transactionGroup, isEarliestNonce return null } - return !cancelEnabled ? ( - -
- {cancelButton} -
-
- ) : cancelButton + return cancelEnabled + ? cancelButton + : ( + +
+ {cancelButton} +
+
+ ) }, [isPending, t, isUnapproved, cancelEnabled, cancelTransaction, hasCancelled]) diff --git a/ui/app/ducks/gas/gas.duck.js b/ui/app/ducks/gas/gas.duck.js index c1d7336e3578..78b9ef8baa2f 100644 --- a/ui/app/ducks/gas/gas.duck.js +++ b/ui/app/ducks/gas/gas.duck.js @@ -399,29 +399,28 @@ export function fetchGasEstimates (blockTime) { const next = arr[i + 1] if (!next) { return [{ expectedWait, gasprice }] - } else { - const supplementalPrice = getRandomArbitrary(gasprice, next.gasprice) - const supplementalTime = extrapolateY({ - higherY: next.expectedWait, - lowerY: expectedWait, - higherX: next.gasprice, - lowerX: gasprice, - xForExtrapolation: supplementalPrice, - }) - const supplementalPrice2 = getRandomArbitrary(supplementalPrice, next.gasprice) - const supplementalTime2 = extrapolateY({ - higherY: next.expectedWait, - lowerY: supplementalTime, - higherX: next.gasprice, - lowerX: supplementalPrice, - xForExtrapolation: supplementalPrice2, - }) - return [ - { expectedWait, gasprice }, - { expectedWait: supplementalTime, gasprice: supplementalPrice }, - { expectedWait: supplementalTime2, gasprice: supplementalPrice2 }, - ] } + const supplementalPrice = getRandomArbitrary(gasprice, next.gasprice) + const supplementalTime = extrapolateY({ + higherY: next.expectedWait, + lowerY: expectedWait, + higherX: next.gasprice, + lowerX: gasprice, + xForExtrapolation: supplementalPrice, + }) + const supplementalPrice2 = getRandomArbitrary(supplementalPrice, next.gasprice) + const supplementalTime2 = extrapolateY({ + higherY: next.expectedWait, + lowerY: supplementalTime, + higherX: next.gasprice, + lowerX: supplementalPrice, + xForExtrapolation: supplementalPrice2, + }) + return [ + { expectedWait, gasprice }, + { expectedWait: supplementalTime, gasprice: supplementalPrice }, + { expectedWait: supplementalTime2, gasprice: supplementalPrice2 }, + ] })) const withOutliersRemoved = inliersByIQR(withSupplementalTimeEstimates.slice(0).reverse(), 'expectedWait').reverse() const timeMappedToSeconds = withOutliersRemoved.map(({ expectedWait, gasprice }) => { @@ -453,11 +452,11 @@ export function fetchGasEstimates (blockTime) { export function setCustomGasPriceForRetry (newPrice) { return (dispatch) => { - if (newPrice !== '0x0') { - dispatch(setCustomGasPrice(newPrice)) - } else { + if (newPrice === '0x0') { const { fast } = loadLocalStorageData('BASIC_PRICE_ESTIMATES') dispatch(setCustomGasPrice(decGWEIToHexWEI(fast))) + } else { + dispatch(setCustomGasPrice(newPrice)) } } } diff --git a/ui/app/helpers/utils/metametrics.util.js b/ui/app/helpers/utils/metametrics.util.js index f9e56bcbf02c..ce76b8385cde 100644 --- a/ui/app/helpers/utils/metametrics.util.js +++ b/ui/app/helpers/utils/metametrics.util.js @@ -157,15 +157,19 @@ function composeUrl (config) { const urlref = previousPath && composeUrlRefParamAddition(previousPath, confirmTransactionOrigin) - const dimensions = !pageOpts.hideDimensions ? composeCustomDimensionParamAddition({ - network, - environmentType, - activeCurrency, - accountType, - version, - numberOfTokens: (customVariables && customVariables.numberOfTokens) || numberOfTokens, - numberOfAccounts: (customVariables && customVariables.numberOfAccounts) || numberOfAccounts, - }) : '' + const dimensions = pageOpts.hideDimensions + ? '' + : ( + composeCustomDimensionParamAddition({ + network, + environmentType, + activeCurrency, + accountType, + version, + numberOfTokens: (customVariables && customVariables.numberOfTokens) || numberOfTokens, + numberOfAccounts: (customVariables && customVariables.numberOfAccounts) || numberOfAccounts, + }) + ) const url = currentPath ? `&url=${encodeURIComponent(`${METAMETRICS_TRACKING_URL}${currentPath}`)}` : '' const _id = metaMetricsId && !excludeMetaMetricsId ? `&_id=${metaMetricsId.slice(2, 18)}` : '' const rand = `&rand=${String(Math.random()).slice(2)}` diff --git a/ui/app/hooks/tests/useTokenData.test.js b/ui/app/hooks/tests/useTokenData.test.js index 9bc507de4053..7154581c65b0 100644 --- a/ui/app/hooks/tests/useTokenData.test.js +++ b/ui/app/hooks/tests/useTokenData.test.js @@ -65,9 +65,9 @@ const tests = [ describe('useTokenData', function () { tests.forEach((test) => { - const testTitle = test.tokenData !== null - ? `should return properly decoded data with _value ${test.tokenData.params[1].value}` - : `should return null when no data provided` + const testTitle = test.tokenData === null + ? `should return null when no data provided` + : `should return properly decoded data with _value ${test.tokenData.params[1].value}` it(testTitle, function () { const { result } = renderHook(() => useTokenData(test.data)) assert.deepEqual(result.current, test.tokenData) diff --git a/ui/app/pages/confirm-decrypt-message/confirm-decrypt-message.component.js b/ui/app/pages/confirm-decrypt-message/confirm-decrypt-message.component.js index a054eb2b33b5..6b3c0adaebb7 100644 --- a/ui/app/pages/confirm-decrypt-message/confirm-decrypt-message.component.js +++ b/ui/app/pages/confirm-decrypt-message/confirm-decrypt-message.component.js @@ -220,7 +220,7 @@ export default class ConfirmDecryptMessage extends Component { className="request-decrypt-message__message-text" > { !hasDecrypted && !hasError ? txData.msgParams.data : rawMessage } - { !hasError ? '' : errorMessage } + { hasError ? errorMessage : '' }
{ decryptMessageInline(txData, event).then((result) => { - if (!result.error) { - this.setState({ hasDecrypted: true, rawMessage: result.rawData }) - } else { + if (result.error) { this.setState({ hasError: true, errorMessage: this.context.t('decryptInlineError', [result.error]) }) + } else { + this.setState({ hasDecrypted: true, rawMessage: result.rawData }) } }) }} diff --git a/ui/app/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js b/ui/app/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js index e5d622ef24a5..789de5372b66 100644 --- a/ui/app/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js +++ b/ui/app/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js @@ -88,7 +88,7 @@ export default class MetaMetricsOptIn extends Component { onCancel={() => { setParticipateInMetaMetrics(false) .then(() => { - const promise = participateInMetaMetrics !== false + const promise = participateInMetaMetrics === true ? metricsEvent({ eventOpts: { category: 'Onboarding', @@ -110,7 +110,7 @@ export default class MetaMetricsOptIn extends Component { onSubmit={() => { setParticipateInMetaMetrics(true) .then(([_, metaMetricsId]) => { - const promise = participateInMetaMetrics !== true + const promise = participateInMetaMetrics === false ? metricsEvent({ eventOpts: { category: 'Onboarding', diff --git a/ui/app/pages/first-time-flow/onboarding-initiator-util.js b/ui/app/pages/first-time-flow/onboarding-initiator-util.js index 7653f78526c9..a7848bdbe335 100644 --- a/ui/app/pages/first-time-flow/onboarding-initiator-util.js +++ b/ui/app/pages/first-time-flow/onboarding-initiator-util.js @@ -16,12 +16,12 @@ const returnToOnboardingInitiatorTab = async (onboardingInitiator) => { }) })) - if (!tab) { + if (tab) { + window.close() + } else { // this case can happen if the tab was closed since being checked with `extension.tabs.get` log.warn(`Setting current tab to onboarding initiator has failed; falling back to redirect`) window.location.assign(onboardingInitiator.location) - } else { - window.close() } } diff --git a/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.component.js b/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.component.js index 8847a0d01f14..8cc34e2af741 100644 --- a/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.component.js +++ b/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.component.js @@ -173,10 +173,10 @@ export default class ConfirmSeedPhrase extends PureComponent { className="confirm-seed-phrase__seed-word--sorted" selected={isSelected} onClick={() => { - if (!isSelected) { - this.handleSelectSeedWord(index) - } else { + if (isSelected) { this.handleDeselectSeedWord(index) + } else { + this.handleSelectSeedWord(index) } }} word={word} diff --git a/ui/app/pages/first-time-flow/seed-phrase/seed-phrase.component.js b/ui/app/pages/first-time-flow/seed-phrase/seed-phrase.component.js index 67eccb34cce0..ade1904c4993 100644 --- a/ui/app/pages/first-time-flow/seed-phrase/seed-phrase.component.js +++ b/ui/app/pages/first-time-flow/seed-phrase/seed-phrase.component.js @@ -30,10 +30,10 @@ export default class SeedPhrase extends PureComponent { if (!seedPhrase) { verifySeedPhrase() .then((verifiedSeedPhrase) => { - if (!verifiedSeedPhrase) { - history.push(DEFAULT_ROUTE) - } else { + if (verifiedSeedPhrase) { this.setState({ verifiedSeedPhrase }) + } else { + history.push(DEFAULT_ROUTE) } }) } diff --git a/ui/app/pages/mobile-sync/mobile-sync.component.js b/ui/app/pages/mobile-sync/mobile-sync.component.js index 1bd69b9cbf6c..51ad3e6b72bc 100644 --- a/ui/app/pages/mobile-sync/mobile-sync.component.js +++ b/ui/app/pages/mobile-sync/mobile-sync.component.js @@ -194,10 +194,10 @@ export default class MobileSyncPage extends Component { storeInHistory: false, }, (status, response) => { - if (!status.error) { - resolve() - } else { + if (status.error) { reject(response) + } else { + resolve() } }) }) @@ -253,10 +253,10 @@ export default class MobileSyncPage extends Component { storeInHistory: false, }, (status, response) => { - if (!status.error) { - resolve() - } else { + if (status.error) { reject(response) + } else { + resolve() } }, ) diff --git a/ui/app/pages/permissions-connect/permissions-connect.component.js b/ui/app/pages/permissions-connect/permissions-connect.component.js index abb5709d5354..207ce8c592d8 100644 --- a/ui/app/pages/permissions-connect/permissions-connect.component.js +++ b/ui/app/pages/permissions-connect/permissions-connect.component.js @@ -162,26 +162,24 @@ export default class PermissionConnect extends Component { const { redirecting } = this.state const { page } = this.props const { t } = this.context - return !redirecting - ? ( -
- { page === '2' + return redirecting + ? null + : ( +
+ {page === '2' ? (
this.goBack()}> - { t('back') } + {t('back')}
) : null }
- { t('xOfY', [ page, '2' ]) } + {t('xOfY', [page, '2'])}
) - : null } render () { diff --git a/ui/app/pages/routes/routes.component.js b/ui/app/pages/routes/routes.component.js index 578483425c34..88b8321e90c2 100644 --- a/ui/app/pages/routes/routes.component.js +++ b/ui/app/pages/routes/routes.component.js @@ -265,16 +265,16 @@ export default class Routes extends Component { } toggleMetamaskActive () { - if (!this.props.isUnlocked) { + if (this.props.isUnlocked) { + // currently active: deactivate + this.props.lockMetaMask() + } else { // currently inactive: redirect to password box const passwordBox = document.querySelector('input[type=password]') if (!passwordBox) { return } passwordBox.focus() - } else { - // currently active: deactivate - this.props.lockMetaMask() } } diff --git a/ui/app/pages/send/send-content/add-recipient/ens-input.component.js b/ui/app/pages/send/send-content/add-recipient/ens-input.component.js index d25243a15945..df8bf87a2774 100644 --- a/ui/app/pages/send/send-content/add-recipient/ens-input.component.js +++ b/ui/app/pages/send/send-content/add-recipient/ens-input.component.js @@ -122,7 +122,7 @@ export default class EnsInput extends Component { if (!networkHasEnsSupport && !isValidAddress(input) && !isValidAddressHead(input)) { updateEnsResolution('') - updateEnsResolutionError(!networkHasEnsSupport ? 'Network does not support ENS' : '') + updateEnsResolutionError(networkHasEnsSupport ? '' : 'Network does not support ENS') return } diff --git a/ui/app/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.component.js b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.component.js index 59fcea36789a..adab9856e20b 100644 --- a/ui/app/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.component.js +++ b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.component.js @@ -51,12 +51,12 @@ export default class AmountMaxButton extends Component { name: 'Clicked "Amount Max"', }, }) - if (!maxModeOn) { - setMaxModeTo(true) - this.setMaxAmount() - } else { + if (maxModeOn) { setMaxModeTo(false) clearMaxAmount() + } else { + setMaxModeTo(true) + this.setMaxAmount() } } diff --git a/ui/app/pages/send/send.container.js b/ui/app/pages/send/send.container.js index 9698255be9ee..ca58a3e7db90 100644 --- a/ui/app/pages/send/send.container.js +++ b/ui/app/pages/send/send.container.js @@ -90,9 +90,9 @@ function mapDispatchToProps (dispatch) { value, data, }) => { - !editingTransactionId - ? dispatch(updateGasData({ gasPrice, selectedAddress, sendToken, blockGasLimit, to, value, data })) - : dispatch(setGasTotal(calcGasTotal(gasLimit, gasPrice))) + editingTransactionId + ? dispatch(setGasTotal(calcGasTotal(gasLimit, gasPrice))) + : dispatch(updateGasData({ gasPrice, selectedAddress, sendToken, blockGasLimit, to, value, data })) }, updateSendTokenBalance: ({ sendToken, tokenContract, address }) => { dispatch(updateSendTokenBalance({ diff --git a/ui/app/selectors/permissions.js b/ui/app/selectors/permissions.js index d4208115390a..47d7857da53d 100644 --- a/ui/app/selectors/permissions.js +++ b/ui/app/selectors/permissions.js @@ -261,10 +261,10 @@ export function getPermissionsMetadataHostCounts (state) { const metadata = getPermissionDomainsMetadata(state) return Object.values(metadata).reduce((counts, { host }) => { if (host) { - if (!counts[host]) { - counts[host] = 1 - } else { + if (counts[host]) { counts[host] += 1 + } else { + counts[host] = 1 } } return counts From 5d42a9b7732eba86bc4e16b98a3e386f95b2e952 Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Fri, 14 Aug 2020 09:18:42 -0230 Subject: [PATCH 097/107] Fix require-unicode-regexp issues (#9212) * Fix require-unicode-regexp issues See [`require-unicode-regexp`](https://eslint.org/docs/rules/require-unicode-regexp) for more information. This change enables `require-unicode-regexp` and fixes the issues raised by the rule. * Remove case-insensitive flag from regexps --- .eslintrc.js | 1 + app/scripts/contentscript.js | 6 +- app/scripts/controllers/threebox.js | 2 +- app/scripts/lib/decrypt-message-manager.js | 2 +- app/scripts/lib/personal-message-manager.js | 2 +- development/build/scripts.js | 4 +- development/static-server.js | 2 +- development/verify-locale-strings.js | 4 +- test/e2e/address-book.spec.js | 6 +- test/e2e/benchmark.js | 6 +- test/e2e/from-import-ui.spec.js | 2 +- test/e2e/incremental-security.spec.js | 6 +- test/e2e/metamask-responsive-ui.spec.js | 4 +- test/e2e/metamask-ui.spec.js | 66 +++++++++---------- test/e2e/send-edit.spec.js | 2 +- test/e2e/tests/localization.spec.js | 2 +- test/e2e/threebox.spec.js | 4 +- test/e2e/webdriver/index.js | 2 +- .../app/controllers/detect-tokens-test.js | 2 +- .../controllers/metamask-controller-test.js | 4 +- test/unit/migrations/migrator-test.js | 4 +- .../app/menu-bar/account-options-menu.js | 2 +- .../account-details-modal.component.js | 2 +- .../ui/unit-input/unit-input.component.js | 2 +- ui/app/contexts/metametrics.js | 2 +- .../confirm-transaction.duck.test.js | 2 +- ui/app/ducks/gas/gas-duck.test.js | 2 +- ui/app/helpers/utils/common.util.js | 4 +- ui/app/helpers/utils/i18n-helper.js | 4 +- ui/app/helpers/utils/transactions.util.js | 2 +- ui/app/helpers/utils/util.js | 8 +-- ui/app/helpers/utils/util.test.js | 2 +- .../confirm-transaction-base.component.js | 6 +- .../confirm-transaction-switch.container.js | 2 +- .../tests/add-recipient-utils.test.js | 2 +- .../send-hex-data-row.component.js | 2 +- ui/app/pages/send/send.utils.js | 2 +- ui/app/pages/send/tests/send-utils.test.js | 10 +-- .../advanced-tab/advanced-tab.component.js | 2 +- .../contact-list-tab.container.js | 2 +- .../edit-contact/edit-contact.container.js | 2 +- .../view-contact/view-contact.component.js | 2 +- .../view-contact/view-contact.container.js | 2 +- .../network-form/network-form.component.js | 2 +- ui/app/pages/settings/settings.container.js | 2 +- ui/lib/account-link.js | 2 +- 46 files changed, 103 insertions(+), 102 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index c7997bce97ed..2e74bd47614c 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -67,6 +67,7 @@ module.exports = { 'no-useless-concat': 'error', 'prefer-rest-params': 'error', 'prefer-spread': 'error', + 'require-unicode-regexp': 'error', /* End v2 rules */ 'arrow-parens': 'error', 'no-tabs': 'error', diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index 57e95fa634b2..e7a983a693b2 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -155,8 +155,8 @@ function doctypeCheck () { */ function suffixCheck () { const prohibitedTypes = [ - /\.xml$/, - /\.pdf$/, + /\.xml$/u, + /\.pdf$/u, ] const currentUrl = window.location.pathname for (let i = 0; i < prohibitedTypes.length; i++) { @@ -202,7 +202,7 @@ function blockedDomainCheck () { let currentRegex for (let i = 0; i < blockedDomains.length; i++) { const blockedDomain = blockedDomains[i].replace('.', '\\.') - currentRegex = new RegExp(`(?:https?:\\/\\/)(?:(?!${blockedDomain}).)*$`) + currentRegex = new RegExp(`(?:https?:\\/\\/)(?:(?!${blockedDomain}).)*$`, 'u') if (!currentRegex.test(currentUrl)) { return true } diff --git a/app/scripts/controllers/threebox.js b/app/scripts/controllers/threebox.js index bca43d9f6536..726a14ffa358 100644 --- a/app/scripts/controllers/threebox.js +++ b/app/scripts/controllers/threebox.js @@ -123,7 +123,7 @@ export default class ThreeBoxController { const threeBoxConfig = await Box.getConfig(this.address) backupExists = threeBoxConfig.spaces && threeBoxConfig.spaces.metamask } catch (e) { - if (e.message.match(/^Error: Invalid response \(404\)/)) { + if (e.message.match(/^Error: Invalid response \(404\)/u)) { backupExists = false } else { throw e diff --git a/app/scripts/lib/decrypt-message-manager.js b/app/scripts/lib/decrypt-message-manager.js index 2a53c9fa2596..a379d656146b 100644 --- a/app/scripts/lib/decrypt-message-manager.js +++ b/app/scripts/lib/decrypt-message-manager.js @@ -5,7 +5,7 @@ import { ethErrors } from 'eth-json-rpc-errors' import createId from './random-id' import { MESSAGE_TYPE } from './enums' -const hexRe = /^[0-9A-Fa-f]+$/g +const hexRe = /^[0-9A-Fa-f]+$/ug import log from 'loglevel' /** diff --git a/app/scripts/lib/personal-message-manager.js b/app/scripts/lib/personal-message-manager.js index 3cf7564baca1..647b7e58938c 100644 --- a/app/scripts/lib/personal-message-manager.js +++ b/app/scripts/lib/personal-message-manager.js @@ -5,7 +5,7 @@ import { ethErrors } from 'eth-json-rpc-errors' import createId from './random-id' import { MESSAGE_TYPE } from './enums' -const hexRe = /^[0-9A-Fa-f]+$/g +const hexRe = /^[0-9A-Fa-f]+$/ug import log from 'loglevel' /** diff --git a/development/build/scripts.js b/development/build/scripts.js index 6f206ee57705..0831dbf69829 100644 --- a/development/build/scripts.js +++ b/development/build/scripts.js @@ -25,7 +25,7 @@ module.exports = createScriptTasks const dependencies = Object.keys((packageJSON && packageJSON.dependencies) || {}) const materialUIDependencies = ['@material-ui/core'] -const reactDepenendencies = dependencies.filter((dep) => dep.match(/react/)) +const reactDepenendencies = dependencies.filter((dep) => dep.match(/react/u)) const d3Dependencies = ['c3', 'd3'] const externalDependenciesMap = { @@ -365,7 +365,7 @@ function getEnvironment ({ devMode, test }) { return 'testing' } else if (process.env.CIRCLE_BRANCH === 'master') { return 'production' - } else if (/^Version-v(\d+)[.](\d+)[.](\d+)/.test(process.env.CIRCLE_BRANCH)) { + } else if (/^Version-v(\d+)[.](\d+)[.](\d+)/u.test(process.env.CIRCLE_BRANCH)) { return 'release-candidate' } else if (process.env.CIRCLE_BRANCH === 'develop') { return 'staging' diff --git a/development/static-server.js b/development/static-server.js index fff4103c460f..368c186e92a6 100644 --- a/development/static-server.js +++ b/development/static-server.js @@ -61,7 +61,7 @@ const main = async () => { } while (args.length) { - if (/^(--port|-p)$/i.test(args[0])) { + if (/^(--port|-p)$/u.test(args[0])) { if (args[1] === undefined) { throw new Error('Missing port argument') } diff --git a/development/verify-locale-strings.js b/development/verify-locale-strings.js index cef27f6a0aec..3b9e72bf30bd 100644 --- a/development/verify-locale-strings.js +++ b/development/verify-locale-strings.js @@ -169,11 +169,11 @@ async function verifyEnglishLocale (fix = false) { // match "t(`...`)" because constructing message keys from template strings // prevents this script from finding the messages, and then inappropriately // deletes them - const templateStringRegex = /\bt\(`.*`\)/g + const templateStringRegex = /\bt\(`.*`\)/ug const templateUsage = [] // match the keys from the locale file - const keyRegex = /'(\w+)'|"(\w+)"/g + const keyRegex = /'(\w+)'|"(\w+)"/ug const usedMessages = new Set() for await (const fileContents of getFileContents(javascriptFiles)) { for (const match of matchAll.call(fileContents, keyRegex)) { diff --git a/test/e2e/address-book.spec.js b/test/e2e/address-book.spec.js index f9fc7d5bea37..fdd0b6b28a32 100644 --- a/test/e2e/address-book.spec.js +++ b/test/e2e/address-book.spec.js @@ -152,7 +152,7 @@ describe('MetaMask', function () { it('balance renders', async function () { const balance = await driver.findElement(By.css('[data-testid="wallet-balance"] .list-item__heading')) - await driver.wait(until.elementTextMatches(balance, /25\s*ETH/)) + await driver.wait(until.elementTextMatches(balance, /25\s*ETH/u)) await driver.delay(regularDelayMs) }) }) @@ -202,7 +202,7 @@ describe('MetaMask', function () { }, 10000) const txValues = await driver.findElement(By.css('.transaction-list-item__primary-currency')) - await driver.wait(until.elementTextMatches(txValues, /-1\s*ETH/), 10000) + await driver.wait(until.elementTextMatches(txValues, /-1\s*ETH/u), 10000) }) }) @@ -239,7 +239,7 @@ describe('MetaMask', function () { }, 10000) const txValues = await driver.findElement(By.css('.transaction-list-item__primary-currency')) - await driver.wait(until.elementTextMatches(txValues, /-2\s*ETH/), 10000) + await driver.wait(until.elementTextMatches(txValues, /-2\s*ETH/u), 10000) }) }) }) diff --git a/test/e2e/benchmark.js b/test/e2e/benchmark.js index 39786abe93ec..f34436328204 100644 --- a/test/e2e/benchmark.js +++ b/test/e2e/benchmark.js @@ -117,7 +117,7 @@ async function main () { let existingParentDirectory while (args.length) { - if (/^(--pages|-p)$/i.test(args[0])) { + if (/^(--pages|-p)$/u.test(args[0])) { if (args[1] === undefined) { throw new Error('Missing pages argument') } @@ -128,7 +128,7 @@ async function main () { } } args.splice(0, 2) - } else if (/^(--samples|-s)$/i.test(args[0])) { + } else if (/^(--samples|-s)$/u.test(args[0])) { if (args[1] === undefined) { throw new Error('Missing number of samples') } @@ -137,7 +137,7 @@ async function main () { throw new Error(`Invalid 'samples' argument given: '${args[1]}'`) } args.splice(0, 2) - } else if (/^(--out|-o)$/i.test(args[0])) { + } else if (/^(--out|-o)$/u.test(args[0])) { if (args[1] === undefined) { throw new Error('Missing output filename') } diff --git a/test/e2e/from-import-ui.spec.js b/test/e2e/from-import-ui.spec.js index 432432b3e579..b0eab48e498f 100644 --- a/test/e2e/from-import-ui.spec.js +++ b/test/e2e/from-import-ui.spec.js @@ -223,7 +223,7 @@ describe('Using MetaMask with an existing account', function () { const txValues = await driver.findElements(By.css('.transaction-list-item__primary-currency')) assert.equal(txValues.length, 1) - assert.ok(/-1\s*ETH/.test(await txValues[0].getText())) + assert.ok(/-1\s*ETH/u.test(await txValues[0].getText())) }) }) diff --git a/test/e2e/incremental-security.spec.js b/test/e2e/incremental-security.spec.js index 7008ec69cb33..1e1a30349d36 100644 --- a/test/e2e/incremental-security.spec.js +++ b/test/e2e/incremental-security.spec.js @@ -127,7 +127,7 @@ describe('MetaMask', function () { await driver.clickElement(By.css('#send')) const txStatus = await driver.findElement(By.css('#success')) - await driver.wait(until.elementTextMatches(txStatus, /Success/), 15000) + await driver.wait(until.elementTextMatches(txStatus, /Success/u), 15000) }) it('switches back to MetaMask', async function () { @@ -136,7 +136,7 @@ describe('MetaMask', function () { it('should have the correct amount of eth', async function () { const balances = await driver.findElements(By.css('.currency-display-component__text')) - await driver.wait(until.elementTextMatches(balances[0], /1/), 15000) + await driver.wait(until.elementTextMatches(balances[0], /1/u), 15000) const balance = await balances[0].getText() assert.equal(balance, '1') @@ -193,7 +193,7 @@ describe('MetaMask', function () { it('should have the correct amount of eth', async function () { const balances = await driver.findElements(By.css('.currency-display-component__text')) - await driver.wait(until.elementTextMatches(balances[0], /1/), 15000) + await driver.wait(until.elementTextMatches(balances[0], /1/u), 15000) const balance = await balances[0].getText() assert.equal(balance, '1') diff --git a/test/e2e/metamask-responsive-ui.spec.js b/test/e2e/metamask-responsive-ui.spec.js index 0b7e8b4d4380..b2d8e9c5ec74 100644 --- a/test/e2e/metamask-responsive-ui.spec.js +++ b/test/e2e/metamask-responsive-ui.spec.js @@ -167,7 +167,7 @@ describe('MetaMask', function () { it('balance renders', async function () { const balance = await driver.findElement(By.css('[data-testid="eth-overview__primary-currency"]')) - await driver.wait(until.elementTextMatches(balance, /100\s*ETH/)) + await driver.wait(until.elementTextMatches(balance, /100\s*ETH/u)) await driver.delay(regularDelayMs) }) }) @@ -218,7 +218,7 @@ describe('MetaMask', function () { }, 10000) const txValues = await driver.findElement(By.css('.transaction-list-item__primary-currency')) - await driver.wait(until.elementTextMatches(txValues, /-1\s*ETH/), 10000) + await driver.wait(until.elementTextMatches(txValues, /-1\s*ETH/u), 10000) }) }) }) diff --git a/test/e2e/metamask-ui.spec.js b/test/e2e/metamask-ui.spec.js index dfff8e49bac1..bb13c0646eb2 100644 --- a/test/e2e/metamask-ui.spec.js +++ b/test/e2e/metamask-ui.spec.js @@ -208,7 +208,7 @@ describe('MetaMask', function () { it('balance renders', async function () { const balance = await driver.findElement(By.css('[data-testid="wallet-balance"] .list-item__heading')) - await driver.wait(until.elementTextMatches(balance, /100\s*ETH/)) + await driver.wait(until.elementTextMatches(balance, /100\s*ETH/u)) await driver.delay(regularDelayMs) }) }) @@ -273,7 +273,7 @@ describe('MetaMask', function () { }, 10000) const txValues = await driver.findElement(By.css('.transaction-list-item__primary-currency')) - await driver.wait(until.elementTextMatches(txValues, /-1\s*ETH/), 10000) + await driver.wait(until.elementTextMatches(txValues, /-1\s*ETH/u), 10000) }) }) @@ -312,7 +312,7 @@ describe('MetaMask', function () { }, 10000) const txValues = await driver.findElement(By.css('.transaction-list-item__primary-currency')) - await driver.wait(until.elementTextMatches(txValues, /-1\s*ETH/), 10000) + await driver.wait(until.elementTextMatches(txValues, /-1\s*ETH/u), 10000) }) }) @@ -360,7 +360,7 @@ describe('MetaMask', function () { }, 10000) const txValues = await driver.findElement(By.css('.transaction-list-item__primary-currency')) - await driver.wait(until.elementTextMatches(txValues, /-1\s*ETH/), 10000) + await driver.wait(until.elementTextMatches(txValues, /-1\s*ETH/u), 10000) }) }) @@ -463,7 +463,7 @@ describe('MetaMask', function () { }, 10000) const txValue = await driver.findClickableElement(By.css('.transaction-list-item__primary-currency')) - await driver.wait(until.elementTextMatches(txValue, /-3\s*ETH/), 10000) + await driver.wait(until.elementTextMatches(txValue, /-3\s*ETH/u), 10000) }) it('the transaction has the expected gas price', async function () { @@ -471,7 +471,7 @@ describe('MetaMask', function () { await txValue.click() const popoverCloseButton = await driver.findClickableElement(By.css('.popover-header__button')) const txGasPrice = await driver.findElement(By.css('[data-testid="transaction-breakdown__gas-price"]')) - await driver.wait(until.elementTextMatches(txGasPrice, /^10$/), 10000) + await driver.wait(until.elementTextMatches(txGasPrice, /^10$/u), 10000) await popoverCloseButton.click() }) }) @@ -654,7 +654,7 @@ describe('MetaMask', function () { }, 10000) const txAction = await driver.findElements(By.css('.list-item__heading')) - await driver.wait(until.elementTextMatches(txAction[0], /Contract\sDeployment/), 10000) + await driver.wait(until.elementTextMatches(txAction[0], /Contract\sDeployment/u), 10000) await driver.delay(regularDelayMs) }) @@ -663,20 +663,20 @@ describe('MetaMask', function () { await driver.delay(regularDelayMs) let contractStatus = await driver.findElement(By.css('#contractStatus')) - await driver.wait(until.elementTextMatches(contractStatus, /Deployed/), 15000) + await driver.wait(until.elementTextMatches(contractStatus, /Deployed/u), 15000) await driver.clickElement(By.css('#depositButton')) await driver.delay(largeDelayMs) contractStatus = await driver.findElement(By.css('#contractStatus')) - await driver.wait(until.elementTextMatches(contractStatus, /Deposit\sinitiated/), 10000) + await driver.wait(until.elementTextMatches(contractStatus, /Deposit\sinitiated/u), 10000) await driver.switchToWindow(extension) await driver.delay(largeDelayMs * 2) await driver.findElements(By.css('.transaction-list-item')) const txListValue = await driver.findClickableElement(By.css('.transaction-list-item__primary-currency')) - await driver.wait(until.elementTextMatches(txListValue, /-4\s*ETH/), 10000) + await driver.wait(until.elementTextMatches(txListValue, /-4\s*ETH/u), 10000) await txListValue.click() await driver.delay(regularDelayMs) @@ -718,7 +718,7 @@ describe('MetaMask', function () { }, 10000) const txValues = await driver.findElements(By.css('.transaction-list-item__primary-currency')) - await driver.wait(until.elementTextMatches(txValues[0], /-4\s*ETH/), 10000) + await driver.wait(until.elementTextMatches(txValues[0], /-4\s*ETH/u), 10000) }) it('calls and confirms a contract method where ETH is received', async function () { @@ -743,7 +743,7 @@ describe('MetaMask', function () { }, 10000) const txValues = await driver.findElement(By.css('.transaction-list-item__primary-currency')) - await driver.wait(until.elementTextMatches(txValues, /-0\s*ETH/), 10000) + await driver.wait(until.elementTextMatches(txValues, /-0\s*ETH/u), 10000) await driver.closeAllWindowHandlesExcept([extension, dapp]) await driver.switchToWindow(extension) @@ -752,9 +752,9 @@ describe('MetaMask', function () { it('renders the correct ETH balance', async function () { const balance = await driver.findElement(By.css('[data-testid="eth-overview__primary-currency"]')) await driver.delay(regularDelayMs) - await driver.wait(until.elementTextMatches(balance, /^87.*\s*ETH.*$/), 10000) + await driver.wait(until.elementTextMatches(balance, /^87.*\s*ETH.*$/u), 10000) const tokenAmount = await balance.getText() - assert.ok(/^87.*\s*ETH.*$/.test(tokenAmount)) + assert.ok(/^87.*\s*ETH.*$/u.test(tokenAmount)) await driver.delay(regularDelayMs) }) }) @@ -797,7 +797,7 @@ describe('MetaMask', function () { await driver.delay(tinyDelayMs) const tokenContractAddress = await driver.findElement(By.css('#tokenAddress')) - await driver.wait(until.elementTextMatches(tokenContractAddress, /0x/)) + await driver.wait(until.elementTextMatches(tokenContractAddress, /0x/u)) tokenAddress = await tokenContractAddress.getText() await driver.delay(regularDelayMs) @@ -830,9 +830,9 @@ describe('MetaMask', function () { it('renders the balance for the new token', async function () { const balance = await driver.findElement(By.css('.wallet-overview .token-overview__primary-balance')) - await driver.wait(until.elementTextMatches(balance, /^10\s*TST\s*$/)) + await driver.wait(until.elementTextMatches(balance, /^10\s*TST\s*$/u)) const tokenAmount = await balance.getText() - assert.ok(/^10\s*TST\s*$/.test(tokenAmount)) + assert.ok(/^10\s*TST\s*$/u.test(tokenAmount)) await driver.delay(regularDelayMs) }) }) @@ -887,7 +887,7 @@ describe('MetaMask', function () { const confirmDataText = await confirmDataDiv.getText() await driver.delay(regularDelayMs) - assert(confirmDataText.match(/0xa9059cbb0000000000000000000000002f318c334780961fb129d2a6c30d0763d9a5c97/)) + assert(confirmDataText.match(/0xa9059cbb0000000000000000000000002f318c334780961fb129d2a6c30d0763d9a5c97/u)) await driver.clickElement(By.xpath(`//li[contains(text(), 'Details')]`)) await driver.delay(regularDelayMs) @@ -906,10 +906,10 @@ describe('MetaMask', function () { const txValues = await driver.findElements(By.css('.transaction-list-item__primary-currency')) assert.equal(txValues.length, 1) - await driver.wait(until.elementTextMatches(txValues[0], /-1\s*TST/), 10000) + await driver.wait(until.elementTextMatches(txValues[0], /-1\s*TST/u), 10000) const txStatuses = await driver.findElements(By.css('.list-item__heading')) - await driver.wait(until.elementTextMatches(txStatuses[0], /Send\sTST/i), 10000) + await driver.wait(until.elementTextMatches(txStatuses[0], /Send\sTST/u), 10000) }) }) @@ -931,7 +931,7 @@ describe('MetaMask', function () { await driver.findElements(By.css('.transaction-list__pending-transactions')) const txListValue = await driver.findClickableElement(By.css('.transaction-list-item__primary-currency')) - await driver.wait(until.elementTextMatches(txListValue, /-1.5\s*TST/), 10000) + await driver.wait(until.elementTextMatches(txListValue, /-1.5\s*TST/u), 10000) await txListValue.click() await driver.delay(regularDelayMs) @@ -987,12 +987,12 @@ describe('MetaMask', function () { }, 10000) const txValues = await driver.findElements(By.css('.transaction-list-item__primary-currency')) - await driver.wait(until.elementTextMatches(txValues[0], /-1.5\s*TST/)) + await driver.wait(until.elementTextMatches(txValues[0], /-1.5\s*TST/u)) const txStatuses = await driver.findElements(By.css('.list-item__heading')) - await driver.wait(until.elementTextMatches(txStatuses[0], /Send\sTST/), 10000) + await driver.wait(until.elementTextMatches(txStatuses[0], /Send\sTST/u), 10000) const tokenBalanceAmount = await driver.findElements(By.css('.token-overview__primary-balance')) - await driver.wait(until.elementTextMatches(tokenBalanceAmount[0], /7.5\s*TST/), 10000) + await driver.wait(until.elementTextMatches(tokenBalanceAmount[0], /7.5\s*TST/u), 10000) }) }) @@ -1019,7 +1019,7 @@ describe('MetaMask', function () { }, 10000) const [txtListHeading] = await driver.findElements(By.css('.transaction-list-item .list-item__heading')) - await driver.wait(until.elementTextMatches(txtListHeading, /Approve TST spend limit/)) + await driver.wait(until.elementTextMatches(txtListHeading, /Approve TST spend limit/u)) await driver.clickElement(By.css('.transaction-list-item')) await driver.delay(regularDelayMs) }) @@ -1034,7 +1034,7 @@ describe('MetaMask', function () { const confirmDataDiv = await driver.findElement(By.css('.confirm-approve-content__data__data-block')) const confirmDataText = await confirmDataDiv.getText() - assert(confirmDataText.match(/0x095ea7b30000000000000000000000009bc5baf874d2da8d216ae9f137804184ee5afef4/)) + assert(confirmDataText.match(/0x095ea7b30000000000000000000000009bc5baf874d2da8d216ae9f137804184ee5afef4/u)) }) it('opens the gas edit modal', async function () { @@ -1105,7 +1105,7 @@ describe('MetaMask', function () { }, 10000) const txStatuses = await driver.findElements(By.css('.list-item__heading')) - await driver.wait(until.elementTextMatches(txStatuses[0], /Approve TST spend limit/)) + await driver.wait(until.elementTextMatches(txStatuses[0], /Approve TST spend limit/u)) }) }) @@ -1130,7 +1130,7 @@ describe('MetaMask', function () { }, 10000) const [txListValue] = await driver.findElements(By.css('.transaction-list-item__primary-currency')) - await driver.wait(until.elementTextMatches(txListValue, /-1.5\s*TST/)) + await driver.wait(until.elementTextMatches(txListValue, /-1.5\s*TST/u)) await driver.clickElement(By.css('.transaction-list-item')) await driver.delay(regularDelayMs) }) @@ -1148,9 +1148,9 @@ describe('MetaMask', function () { }, 10000) const txValues = await driver.findElements(By.css('.transaction-list-item__primary-currency')) - await driver.wait(until.elementTextMatches(txValues[0], /-1.5\s*TST/)) + await driver.wait(until.elementTextMatches(txValues[0], /-1.5\s*TST/u)) const txStatuses = await driver.findElements(By.css('.list-item__heading')) - await driver.wait(until.elementTextMatches(txStatuses[0], /Send TST/)) + await driver.wait(until.elementTextMatches(txStatuses[0], /Send TST/u)) }) }) @@ -1176,7 +1176,7 @@ describe('MetaMask', function () { }, 10000) const [txtListHeading] = await driver.findElements(By.css('.transaction-list-item .list-item__heading')) - await driver.wait(until.elementTextMatches(txtListHeading, /Approve TST spend limit/)) + await driver.wait(until.elementTextMatches(txtListHeading, /Approve TST spend limit/u)) await driver.clickElement(By.css('.transaction-list-item')) await driver.delay(regularDelayMs) }) @@ -1203,7 +1203,7 @@ describe('MetaMask', function () { }, 10000) const txStatuses = await driver.findElements(By.css('.list-item__heading')) - await driver.wait(until.elementTextMatches(txStatuses[0], /Approve TST spend limit/)) + await driver.wait(until.elementTextMatches(txStatuses[0], /Approve TST spend limit/u)) }) }) @@ -1244,7 +1244,7 @@ describe('MetaMask', function () { it('renders the balance for the chosen token', async function () { const balance = await driver.findElement(By.css('.token-overview__primary-balance')) - await driver.wait(until.elementTextMatches(balance, /0\s*BAT/)) + await driver.wait(until.elementTextMatches(balance, /0\s*BAT/u)) await driver.delay(regularDelayMs) }) }) diff --git a/test/e2e/send-edit.spec.js b/test/e2e/send-edit.spec.js index c3e60f1c2aaa..ddf9bfde4a9f 100644 --- a/test/e2e/send-edit.spec.js +++ b/test/e2e/send-edit.spec.js @@ -205,7 +205,7 @@ describe('Using MetaMask with an existing account', function () { const txValues = await driver.findElements(By.css('.transaction-list-item__primary-currency')) assert.equal(txValues.length, 1) - assert.ok(/-2.2\s*ETH/.test(await txValues[0].getText())) + assert.ok(/-2.2\s*ETH/u.test(await txValues[0].getText())) }) }) }) diff --git a/test/e2e/tests/localization.spec.js b/test/e2e/tests/localization.spec.js index e3d4520cae7b..127d432bd2ae 100644 --- a/test/e2e/tests/localization.spec.js +++ b/test/e2e/tests/localization.spec.js @@ -20,7 +20,7 @@ describe('Localization', function () { await passwordField.sendKeys(Key.ENTER) const secondaryBalance = await driver.findElement(By.css('[data-testid="eth-overview__secondary-currency"]')) const secondaryBalanceText = await secondaryBalance.getText() - const [fiatAmount, fiatUnit] = secondaryBalanceText.trim().split(/\s+/) + const [fiatAmount, fiatUnit] = secondaryBalanceText.trim().split(/\s+/u) assert.ok(fiatAmount.startsWith('₱')) assert.equal(fiatUnit, 'PHP') }, diff --git a/test/e2e/threebox.spec.js b/test/e2e/threebox.spec.js index e7affbfeb123..6d46e037702f 100644 --- a/test/e2e/threebox.spec.js +++ b/test/e2e/threebox.spec.js @@ -97,7 +97,7 @@ describe('MetaMask', function () { it('balance renders', async function () { const balance = await driver.findElement(By.css('[data-testid="wallet-balance"] .list-item__heading')) - await driver.wait(until.elementTextMatches(balance, /25\s*ETH/)) + await driver.wait(until.elementTextMatches(balance, /25\s*ETH/u)) await driver.delay(regularDelayMs) }) }) @@ -203,7 +203,7 @@ describe('MetaMask', function () { it('balance renders', async function () { const balance = await driver2.findElement(By.css('[data-testid="wallet-balance"] .list-item__heading')) - await driver2.wait(until.elementTextMatches(balance, /25\s*ETH/)) + await driver2.wait(until.elementTextMatches(balance, /25\s*ETH/u)) await driver2.delay(regularDelayMs) }) }) diff --git a/test/e2e/webdriver/index.js b/test/e2e/webdriver/index.js index 652e27283d79..8972d54a651f 100644 --- a/test/e2e/webdriver/index.js +++ b/test/e2e/webdriver/index.js @@ -45,7 +45,7 @@ async function setupFetchMocking (driver) { return { json: async () => clone(fetchMockResponses.ethGasBasic) } } else if (url.match(/http(s?):\/\/ethgasstation\.info\/json\/predictTable.*/u)) { return { json: async () => clone(fetchMockResponses.ethGasPredictTable) } - } else if (url.match(/chromeextensionmm/)) { + } else if (url.match(/chromeextensionmm/u)) { return { json: async () => clone(fetchMockResponses.metametrics) } } return window.origFetch(...args) diff --git a/test/unit/app/controllers/detect-tokens-test.js b/test/unit/app/controllers/detect-tokens-test.js index 22bc3795877a..83ca9eb01beb 100644 --- a/test/unit/app/controllers/detect-tokens-test.js +++ b/test/unit/app/controllers/detect-tokens-test.js @@ -24,7 +24,7 @@ describe('DetectTokensController', function () { nock('https://api.infura.io') - .get(/.*/) + .get(/.*/u) .reply(200) keyringMemStore = new ObservableStore({ isUnlocked: false }) diff --git a/test/unit/app/controllers/metamask-controller-test.js b/test/unit/app/controllers/metamask-controller-test.js index f2556bde2617..92be16f51bf5 100644 --- a/test/unit/app/controllers/metamask-controller-test.js +++ b/test/unit/app/controllers/metamask-controller-test.js @@ -92,12 +92,12 @@ describe('MetaMaskController', function () { nock('https://api.infura.io') .persist() - .get(/.*/) + .get(/.*/u) .reply(200) nock('https://min-api.cryptocompare.com') .persist() - .get(/.*/) + .get(/.*/u) .reply(200, '{"JPY":12415.9}') metamaskController = new MetaMaskController({ diff --git a/test/unit/migrations/migrator-test.js b/test/unit/migrations/migrator-test.js index c54866ec0eb5..a3dc2fbb7a7e 100644 --- a/test/unit/migrations/migrator-test.js +++ b/test/unit/migrations/migrator-test.js @@ -50,7 +50,7 @@ describe('migrations', function () { migrationNumbers = fileNames .reduce((acc, filename) => { const name = filename.split('.')[0] - if (/^\d+$/.test(name)) { + if (/^\d+$/u.test(name)) { acc.push(name) } return acc @@ -70,7 +70,7 @@ describe('migrations', function () { const testNumbers = fileNames .reduce((acc, filename) => { const name = filename.split('-test.')[0] - if (/^\d+$/.test(name)) { + if (/^\d+$/u.test(name)) { acc.push(name) } return acc diff --git a/ui/app/components/app/menu-bar/account-options-menu.js b/ui/app/components/app/menu-bar/account-options-menu.js index 9edf908d466d..bd3d5b0528cc 100644 --- a/ui/app/components/app/menu-bar/account-options-menu.js +++ b/ui/app/components/app/menu-bar/account-options-menu.js @@ -97,7 +97,7 @@ export default function AccountOptionsMenu ({ anchorElement, onClose }) { rpcPrefs.blockExplorerUrl ? ( - { rpcPrefs.blockExplorerUrl.match(/^https?:\/\/(.+)/)[1] } + { rpcPrefs.blockExplorerUrl.match(/^https?:\/\/(.+)/u)[1] } ) : null diff --git a/ui/app/components/app/modals/account-details-modal/account-details-modal.component.js b/ui/app/components/app/modals/account-details-modal/account-details-modal.component.js index b43575063d6c..267083227ebe 100644 --- a/ui/app/components/app/modals/account-details-modal/account-details-modal.component.js +++ b/ui/app/components/app/modals/account-details-modal/account-details-modal.component.js @@ -66,7 +66,7 @@ export default class AccountDetailsModal extends Component { }} > {rpcPrefs.blockExplorerUrl - ? this.context.t('blockExplorerView', [rpcPrefs.blockExplorerUrl.match(/^https?:\/\/(.+)/)[1]]) + ? this.context.t('blockExplorerView', [rpcPrefs.blockExplorerUrl.match(/^https?:\/\/(.+)/u)[1]]) : this.context.t('viewOnEtherscan') } diff --git a/ui/app/components/ui/unit-input/unit-input.component.js b/ui/app/components/ui/unit-input/unit-input.component.js index d2368291fa12..6c43aaf2685f 100644 --- a/ui/app/components/ui/unit-input/unit-input.component.js +++ b/ui/app/components/ui/unit-input/unit-input.component.js @@ -58,7 +58,7 @@ export default class UnitInput extends PureComponent { getInputWidth (value) { const valueString = String(value) const valueLength = valueString.length || 1 - const decimalPointDeficit = valueString.match(/\./) ? -0.5 : 0 + const decimalPointDeficit = valueString.match(/\./u) ? -0.5 : 0 return (valueLength + decimalPointDeficit + 0.5) + 'ch' } diff --git a/ui/app/contexts/metametrics.js b/ui/app/contexts/metametrics.js index 9206915093b3..19349bb5d3fa 100644 --- a/ui/app/contexts/metametrics.js +++ b/ui/app/contexts/metametrics.js @@ -60,7 +60,7 @@ export function MetaMetricsProvider ({ children }) { const { eventOpts = {} } = config const { name = '' } = eventOpts const { currentPath: overrideCurrentPath = '' } = overrides - const isSendFlow = Boolean(name.match(/^send|^confirm/) || overrideCurrentPath.match(/send|confirm/)) + const isSendFlow = Boolean(name.match(/^send|^confirm/u) || overrideCurrentPath.match(/send|confirm/u)) if (participateInMetaMetrics || config.isOptIn) { return sendMetaMetricsEvent({ diff --git a/ui/app/ducks/confirm-transaction/confirm-transaction.duck.test.js b/ui/app/ducks/confirm-transaction/confirm-transaction.duck.test.js index e2c99f224ccd..60ada9797da9 100644 --- a/ui/app/ducks/confirm-transaction/confirm-transaction.duck.test.js +++ b/ui/app/ducks/confirm-transaction/confirm-transaction.duck.test.js @@ -482,7 +482,7 @@ describe('Confirm Transaction Duck', function () { beforeEach(function () { global.eth = { getCode: sinon.stub().callsFake( - (address) => Promise.resolve(address && address.match(/isContract/) ? 'not-0x' : '0x'), + (address) => Promise.resolve(address && address.match(/isContract/u) ? 'not-0x' : '0x'), ), } }) diff --git a/ui/app/ducks/gas/gas-duck.test.js b/ui/app/ducks/gas/gas-duck.test.js index c4687569304f..a152bff16f4a 100644 --- a/ui/app/ducks/gas/gas-duck.test.js +++ b/ui/app/ducks/gas/gas-duck.test.js @@ -67,7 +67,7 @@ describe('Gas Duck', function () { { expectedTime: 1, expectedWait: 0.5, gasprice: 20, somethingElse: 'foobar' }, ] const fakeFetch = (url) => new Promise((resolve) => { - const dataToResolve = url.match(/ethgasAPI/) + const dataToResolve = url.match(/ethgasAPI/u) ? mockEthGasApiResponse : mockPredictTableResponse resolve({ diff --git a/ui/app/helpers/utils/common.util.js b/ui/app/helpers/utils/common.util.js index a45fdc407629..365ff4376804 100644 --- a/ui/app/helpers/utils/common.util.js +++ b/ui/app/helpers/utils/common.util.js @@ -1,5 +1,5 @@ export function camelCaseToCapitalize (str = '') { return str - .replace(/([A-Z])/g, ' $1') - .replace(/^./, (str) => str.toUpperCase()) + .replace(/([A-Z])/ug, ' $1') + .replace(/^./u, (str) => str.toUpperCase()) } diff --git a/ui/app/helpers/utils/i18n-helper.js b/ui/app/helpers/utils/i18n-helper.js index ff3514f847e3..198e025d5b84 100644 --- a/ui/app/helpers/utils/i18n-helper.js +++ b/ui/app/helpers/utils/i18n-helper.js @@ -48,10 +48,10 @@ export const getMessage = (localeCode, localeMessages, key, substitutions) => { // perform substitutions if (hasSubstitutions) { - const parts = phrase.split(/(\$\d)/g) + const parts = phrase.split(/(\$\d)/ug) const substitutedParts = parts.map((part) => { - const subMatch = part.match(/\$(\d)/) + const subMatch = part.match(/\$(\d)/u) if (!subMatch) { return part } diff --git a/ui/app/helpers/utils/transactions.util.js b/ui/app/helpers/utils/transactions.util.js index b3d2889e1441..3d14c0336d4d 100644 --- a/ui/app/helpers/utils/transactions.util.js +++ b/ui/app/helpers/utils/transactions.util.js @@ -238,7 +238,7 @@ export function getStatusKey (transaction) { */ export function getBlockExplorerUrlForTx (networkId, hash, rpcPrefs = {}) { if (rpcPrefs.blockExplorerUrl) { - return `${rpcPrefs.blockExplorerUrl.replace(/\/+$/, '')}/tx/${hash}` + return `${rpcPrefs.blockExplorerUrl.replace(/\/+$/u, '')}/tx/${hash}` } const prefix = getEtherscanNetworkPrefix(networkId) return `https://${prefix}etherscan.io/tx/${hash}` diff --git a/ui/app/helpers/utils/util.js b/ui/app/helpers/utils/util.js index 40129375629e..98e58053f86f 100644 --- a/ui/app/helpers/utils/util.js +++ b/ui/app/helpers/utils/util.js @@ -75,7 +75,7 @@ export function isValidDomainName (address) { // Checks that the domain consists of at least one valid domain pieces separated by periods, followed by a tld // Each piece of domain name has only the characters a-z, 0-9, and a hyphen (but not at the start or end of chunk) // A chunk has minimum length of 1, but minimum tld is set to 2 for now (no 1-character tlds exist yet) - .match(/^(?:[a-z0-9](?:[-a-z0-9]*[a-z0-9])?\.)+[a-z0-9][-a-z0-9]*[a-z0-9]$/) + .match(/^(?:[a-z0-9](?:[-a-z0-9]*[a-z0-9])?\.)+[a-z0-9][-a-z0-9]*[a-z0-9]$/u) return match !== null } @@ -102,7 +102,7 @@ export function parseBalance (balance) { let afterDecimal const wei = numericBalance(balance) const weiString = wei.toString() - const trailingZeros = /0+$/ + const trailingZeros = /0+$/u const beforeDecimal = weiString.length > 18 ? weiString.slice(0, weiString.length - 18) : '0' afterDecimal = ('000000000000000000' + wei).slice(-18).replace(trailingZeros, '') @@ -122,7 +122,7 @@ export function formatBalance (balance, decimalsToKeep, needsParse = true, ticke if (decimalsToKeep === undefined) { if (beforeDecimal === '0') { if (afterDecimal !== '0') { - const sigFigs = afterDecimal.match(/^0*(.{2})/) // default: grabs 2 most significant digits + const sigFigs = afterDecimal.match(/^0*(.{2})/u) // default: grabs 2 most significant digits if (sigFigs) { afterDecimal = sigFigs[0] } @@ -219,7 +219,7 @@ export function normalizeNumberToWei (n, currency) { } export function isHex (str) { - return Boolean(str.match(/^(0x)?[0-9a-fA-F]+$/)) + return Boolean(str.match(/^(0x)?[0-9a-fA-F]+$/u)) } export function getContractAtAddress (tokenAddress) { diff --git a/ui/app/helpers/utils/util.test.js b/ui/app/helpers/utils/util.test.js index ef1f5f1531f9..cbeb624d164e 100644 --- a/ui/app/helpers/utils/util.test.js +++ b/ui/app/helpers/utils/util.test.js @@ -307,7 +307,7 @@ describe('util', function () { describe('#getRandomFileName', function () { it('should only return a string containing alphanumeric characters', function () { const result = util.getRandomFileName() - assert(result.match(/^[a-zA-Z0-9]*$/g)) + assert(result.match(/^[a-zA-Z0-9]*$/ug)) }) // 50 samples diff --git a/ui/app/pages/confirm-transaction-base/confirm-transaction-base.component.js b/ui/app/pages/confirm-transaction-base/confirm-transaction-base.component.js index 7f8df41685b9..1b44c4376166 100644 --- a/ui/app/pages/confirm-transaction-base/confirm-transaction-base.component.js +++ b/ui/app/pages/confirm-transaction-base/confirm-transaction-base.component.js @@ -727,7 +727,7 @@ export function getMethodName (camelCase) { } return camelCase - .replace(/([a-z])([A-Z])/g, '$1 $2') - .replace(/([A-Z])([a-z])/g, ' $1$2') - .replace(/ +/g, ' ') + .replace(/([a-z])([A-Z])/ug, '$1 $2') + .replace(/([A-Z])([a-z])/ug, ' $1$2') + .replace(/ +/ug, ' ') } diff --git a/ui/app/pages/confirm-transaction-switch/confirm-transaction-switch.container.js b/ui/app/pages/confirm-transaction-switch/confirm-transaction-switch.container.js index 11daa6cc623b..b0d0a8924b0a 100644 --- a/ui/app/pages/confirm-transaction-switch/confirm-transaction-switch.container.js +++ b/ui/app/pages/confirm-transaction-switch/confirm-transaction-switch.container.js @@ -5,7 +5,7 @@ import { unconfirmedTransactionsListSelector } from '../../selectors' const mapStateToProps = (state, ownProps) => { const { metamask: { unapprovedTxs } } = state const { match: { params = {}, url } } = ownProps - const urlId = url && url.match(/\d+/) && url.match(/\d+/)[0] + const urlId = url && url.match(/\d+/u) && url.match(/\d+/u)[0] const { id: paramsId } = params const transactionId = paramsId || urlId diff --git a/ui/app/pages/send/send-content/add-recipient/tests/add-recipient-utils.test.js b/ui/app/pages/send/send-content/add-recipient/tests/add-recipient-utils.test.js index a35ba5b9face..6284206886a4 100644 --- a/ui/app/pages/send/send-content/add-recipient/tests/add-recipient-utils.test.js +++ b/ui/app/pages/send/send-content/add-recipient/tests/add-recipient-utils.test.js @@ -9,7 +9,7 @@ import { } from '../../../send.constants' const stubs = { - isValidAddress: sinon.stub().callsFake((to) => Boolean(to.match(/^[0xabcdef123456798]+$/))), + isValidAddress: sinon.stub().callsFake((to) => Boolean(to.match(/^[0xabcdef123456798]+$/u))), } const toRowUtils = proxyquire('../add-recipient.js', { diff --git a/ui/app/pages/send/send-content/send-hex-data-row/send-hex-data-row.component.js b/ui/app/pages/send/send-content/send-hex-data-row/send-hex-data-row.component.js index eeff87b84d7c..cf774ae0cea7 100644 --- a/ui/app/pages/send/send-content/send-hex-data-row/send-hex-data-row.component.js +++ b/ui/app/pages/send/send-content/send-hex-data-row/send-hex-data-row.component.js @@ -15,7 +15,7 @@ export default class SendHexDataRow extends Component { onInput = (event) => { const { updateSendHexData, updateGas } = this.props - const data = event.target.value.replace(/\n/g, '') || null + const data = event.target.value.replace(/\n/ug, '') || null updateSendHexData(data) updateGas({ data }) } diff --git a/ui/app/pages/send/send.utils.js b/ui/app/pages/send/send.utils.js index ff3b5295a1a0..ca0fdf0441b8 100644 --- a/ui/app/pages/send/send.utils.js +++ b/ui/app/pages/send/send.utils.js @@ -314,7 +314,7 @@ function getToAddressForGasUpdate (...addresses) { } function removeLeadingZeroes (str) { - return str.replace(/^0*(?=\d)/, '') + return str.replace(/^0*(?=\d)/u, '') } function ellipsify (text, first = 6, last = 4) { diff --git a/ui/app/pages/send/tests/send-utils.test.js b/ui/app/pages/send/tests/send-utils.test.js index 08bc302898e9..b1f57e49a6f6 100644 --- a/ui/app/pages/send/tests/send-utils.test.js +++ b/ui/app/pages/send/tests/send-utils.test.js @@ -10,10 +10,10 @@ import { const stubs = { addCurrencies: sinon.stub().callsFake((a, b) => { - if (String(a).match(/^0x.+/)) { + if (String(a).match(/^0x.+/u)) { a = Number(String(a).slice(2)) } - if (String(b).match(/^0x.+/)) { + if (String(b).match(/^0x.+/u)) { b = Number(String(b).slice(2)) } return a + b @@ -294,8 +294,8 @@ describe('send utils', function () { to: '0xisContract', estimateGasMethod: sinon.stub().callsFake( ({ to }) => { - if (typeof to === 'string' && to.match(/willFailBecauseOf:/)) { - throw new Error(to.match(/:(.+)$/)[1]) + if (typeof to === 'string' && to.match(/willFailBecauseOf:/u)) { + throw new Error(to.match(/:(.+)$/u)[1]) } return { toString: (n) => `0xabc${n}` } }, @@ -311,7 +311,7 @@ describe('send utils', function () { beforeEach(function () { global.eth = { getCode: sinon.stub().callsFake( - (address) => Promise.resolve(address.match(/isContract/) ? 'not-0x' : '0x'), + (address) => Promise.resolve(address.match(/isContract/u) ? 'not-0x' : '0x'), ), } }) diff --git a/ui/app/pages/settings/advanced-tab/advanced-tab.component.js b/ui/app/pages/settings/advanced-tab/advanced-tab.component.js index df952eff6a11..ec8cbbbdaec9 100644 --- a/ui/app/pages/settings/advanced-tab/advanced-tab.component.js +++ b/ui/app/pages/settings/advanced-tab/advanced-tab.component.js @@ -489,7 +489,7 @@ export default class AdvancedTab extends PureComponent { function addUrlProtocolPrefix (urlString) { if (!urlString.match( - /(^http:\/\/)|(^https:\/\/)/, + /(^http:\/\/)|(^https:\/\/)/u, )) { return 'https://' + urlString } diff --git a/ui/app/pages/settings/contact-list-tab/contact-list-tab.container.js b/ui/app/pages/settings/contact-list-tab/contact-list-tab.container.js index d3bb1799a7c5..63a7db52d795 100644 --- a/ui/app/pages/settings/contact-list-tab/contact-list-tab.container.js +++ b/ui/app/pages/settings/contact-list-tab/contact-list-tab.container.js @@ -20,7 +20,7 @@ const mapStateToProps = (state, ownProps) => { const { location } = ownProps const { pathname } = location - const pathNameTail = pathname.match(/[^/]+$/)[0] + const pathNameTail = pathname.match(/[^/]+$/u)[0] const pathNameTailIsAddress = pathNameTail.includes('0x') const viewingContact = Boolean(pathname.match(CONTACT_VIEW_ROUTE) || pathname.match(CONTACT_MY_ACCOUNTS_VIEW_ROUTE)) diff --git a/ui/app/pages/settings/contact-list-tab/edit-contact/edit-contact.container.js b/ui/app/pages/settings/contact-list-tab/edit-contact/edit-contact.container.js index 3766f1ac8588..4cd7bd59ecab 100644 --- a/ui/app/pages/settings/contact-list-tab/edit-contact/edit-contact.container.js +++ b/ui/app/pages/settings/contact-list-tab/edit-contact/edit-contact.container.js @@ -15,7 +15,7 @@ import { addToAddressBook, removeFromAddressBook, setAccountLabel } from '../../ const mapStateToProps = (state, ownProps) => { const { location } = ownProps const { pathname } = location - const pathNameTail = pathname.match(/[^/]+$/)[0] + const pathNameTail = pathname.match(/[^/]+$/u)[0] const pathNameTailIsAddress = pathNameTail.includes('0x') const address = pathNameTailIsAddress ? pathNameTail.toLowerCase() : ownProps.match.params.id diff --git a/ui/app/pages/settings/contact-list-tab/view-contact/view-contact.component.js b/ui/app/pages/settings/contact-list-tab/view-contact/view-contact.component.js index ce9312c87f1e..9b3e9a17c325 100644 --- a/ui/app/pages/settings/contact-list-tab/view-contact/view-contact.component.js +++ b/ui/app/pages/settings/contact-list-tab/view-contact/view-contact.component.js @@ -15,7 +15,7 @@ function quadSplit (address) { '0x ' + address .slice(2) - .match(/.{1,4}/g) + .match(/.{1,4}/ug) .join(' ') ) } diff --git a/ui/app/pages/settings/contact-list-tab/view-contact/view-contact.container.js b/ui/app/pages/settings/contact-list-tab/view-contact/view-contact.container.js index 7f7c5d8c1536..33579d1545ea 100644 --- a/ui/app/pages/settings/contact-list-tab/view-contact/view-contact.container.js +++ b/ui/app/pages/settings/contact-list-tab/view-contact/view-contact.container.js @@ -15,7 +15,7 @@ import { const mapStateToProps = (state, ownProps) => { const { location } = ownProps const { pathname } = location - const pathNameTail = pathname.match(/[^/]+$/)[0] + const pathNameTail = pathname.match(/[^/]+$/u)[0] const pathNameTailIsAddress = pathNameTail.includes('0x') const address = pathNameTailIsAddress ? pathNameTail.toLowerCase() : ownProps.match.params.id diff --git a/ui/app/pages/settings/networks-tab/network-form/network-form.component.js b/ui/app/pages/settings/networks-tab/network-form/network-form.component.js index 34209983a9fd..8f029771f564 100644 --- a/ui/app/pages/settings/networks-tab/network-form/network-form.component.js +++ b/ui/app/pages/settings/networks-tab/network-form/network-form.component.js @@ -214,7 +214,7 @@ export default class NetworkForm extends PureComponent { isValidWhenAppended = (url) => { const appendedRpc = `http://${url}` - return validUrl.isWebUri(appendedRpc) && !url.match(/^https?:\/\/$/) + return validUrl.isWebUri(appendedRpc) && !url.match(/^https?:\/\/$/u) } validateBlockExplorerURL = (url, stateKey) => { diff --git a/ui/app/pages/settings/settings.container.js b/ui/app/pages/settings/settings.container.js index ebeb2ae4bb78..e0b3cfbaf007 100644 --- a/ui/app/pages/settings/settings.container.js +++ b/ui/app/pages/settings/settings.container.js @@ -42,7 +42,7 @@ const ROUTES_TO_I18N_KEYS = { const mapStateToProps = (state, ownProps) => { const { location } = ownProps const { pathname } = location - const pathNameTail = pathname.match(/[^/]+$/)[0] + const pathNameTail = pathname.match(/[^/]+$/u)[0] const isAddressEntryPage = pathNameTail.includes('0x') const isMyAccountsPage = pathname.match('my-accounts') diff --git a/ui/lib/account-link.js b/ui/lib/account-link.js index 1f9ba293e273..3e653b088501 100644 --- a/ui/lib/account-link.js +++ b/ui/lib/account-link.js @@ -1,6 +1,6 @@ export default function getAccountLink (address, network, rpcPrefs) { if (rpcPrefs && rpcPrefs.blockExplorerUrl) { - return `${rpcPrefs.blockExplorerUrl.replace(/\/+$/, '')}/address/${address}` + return `${rpcPrefs.blockExplorerUrl.replace(/\/+$/u, '')}/address/${address}` } const net = parseInt(network) From 937616565ddee27ede58376a56547cfa07d3a567 Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Fri, 14 Aug 2020 11:57:42 -0230 Subject: [PATCH 098/107] Tidy ConnectHardwareForm#checkIfUnlocked (#9224) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change tidies up the implementation of `ConnectHardwareForm#checkIfUnlocked`—passing an `async` function to `forEach` doesn't ensure that the one is run before the other. --- ui/app/pages/create-account/connect-hardware/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/app/pages/create-account/connect-hardware/index.js b/ui/app/pages/create-account/connect-hardware/index.js index 4bae249e6b2c..32189bf63029 100644 --- a/ui/app/pages/create-account/connect-hardware/index.js +++ b/ui/app/pages/create-account/connect-hardware/index.js @@ -35,13 +35,13 @@ class ConnectHardwareForm extends Component { } async checkIfUnlocked () { - ['trezor', 'ledger'].forEach(async (device) => { + for (const device of ['trezor', 'ledger']) { const unlocked = await this.props.checkHardwareStatus(device, this.props.defaultHdPaths[device]) if (unlocked) { this.setState({ unlocked: true }) this.getPage(device, 0, this.props.defaultHdPaths[device]) } - }) + } } connectToHardwareWallet = (device) => { From 2aa4b6bbeeb6ef97ca7f7e58b9a592d9a075e2e0 Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Fri, 14 Aug 2020 12:31:59 -0230 Subject: [PATCH 099/107] Tidy up getAccountLink (#9223) --- .../app/menu-bar/account-options-menu.js | 4 ++-- .../account-details-modal.component.js | 4 ++-- .../confirm-remove-account.component.js | 4 ++-- .../connect-hardware/account-list.js | 4 ++-- ui/lib/account-link.js | 24 ++++++------------- 5 files changed, 15 insertions(+), 25 deletions(-) diff --git a/ui/app/components/app/menu-bar/account-options-menu.js b/ui/app/components/app/menu-bar/account-options-menu.js index bd3d5b0528cc..32c31b6c0c50 100644 --- a/ui/app/components/app/menu-bar/account-options-menu.js +++ b/ui/app/components/app/menu-bar/account-options-menu.js @@ -6,7 +6,7 @@ import { useDispatch, useSelector } from 'react-redux' import { showModal } from '../../../store/actions' import { CONNECTED_ROUTE } from '../../../helpers/constants/routes' import { Menu, MenuItem } from '../../ui/menu' -import genAccountLink from '../../../../lib/account-link' +import getAccountLink from '../../../../lib/account-link' import { getCurrentKeyring, getCurrentNetwork, getRpcPrefsForCurrentProvider, getSelectedIdentity } from '../../../selectors' import { useI18nContext } from '../../../hooks/useI18nContext' import { useMetricEvent } from '../../../hooks/useMetricEvent' @@ -90,7 +90,7 @@ export default function AccountOptionsMenu ({ anchorElement, onClose }) { { viewOnEtherscanEvent() - global.platform.openTab({ url: genAccountLink(address, network, rpcPrefs) }) + global.platform.openTab({ url: getAccountLink(address, network, rpcPrefs) }) onClose() }} subtitle={ diff --git a/ui/app/components/app/modals/account-details-modal/account-details-modal.component.js b/ui/app/components/app/modals/account-details-modal/account-details-modal.component.js index 267083227ebe..858ae8296735 100644 --- a/ui/app/components/app/modals/account-details-modal/account-details-modal.component.js +++ b/ui/app/components/app/modals/account-details-modal/account-details-modal.component.js @@ -1,7 +1,7 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' import AccountModalContainer from '../account-modal-container' -import genAccountLink from '../../../../../lib/account-link' +import getAccountLink from '../../../../../lib/account-link' import QrView from '../../../ui/qr-code' import EditableLabel from '../../../ui/editable-label' import Button from '../../../ui/button' @@ -62,7 +62,7 @@ export default class AccountDetailsModal extends Component { type="secondary" className="account-details-modal__button" onClick={() => { - global.platform.openTab({ url: genAccountLink(address, network, rpcPrefs) }) + global.platform.openTab({ url: getAccountLink(address, network, rpcPrefs) }) }} > {rpcPrefs.blockExplorerUrl diff --git a/ui/app/components/app/modals/confirm-remove-account/confirm-remove-account.component.js b/ui/app/components/app/modals/confirm-remove-account/confirm-remove-account.component.js index 12d652514cc3..4258d9462a64 100644 --- a/ui/app/components/app/modals/confirm-remove-account/confirm-remove-account.component.js +++ b/ui/app/components/app/modals/confirm-remove-account/confirm-remove-account.component.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types' import Modal from '../../modal' import { addressSummary } from '../../../../helpers/utils/util' import Identicon from '../../../ui/identicon' -import genAccountLink from '../../../../../lib/account-link' +import getAccountLink from '../../../../../lib/account-link' export default class ConfirmRemoveAccount extends Component { static propTypes = { @@ -47,7 +47,7 @@ export default class ConfirmRemoveAccount extends Component {
Date: Fri, 14 Aug 2020 12:52:38 -0230 Subject: [PATCH 100/107] Delete page-container.component.test.js (#9229) --- .../ui/page-container/tests/page-container.component.test.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 ui/app/components/ui/page-container/tests/page-container.component.test.js diff --git a/ui/app/components/ui/page-container/tests/page-container.component.test.js b/ui/app/components/ui/page-container/tests/page-container.component.test.js deleted file mode 100644 index e69de29bb2d1..000000000000 From 9e7841fa918435612bc85754dc03943a6514c762 Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Fri, 14 Aug 2020 13:41:25 -0230 Subject: [PATCH 101/107] Consolidate ESLint config files (#9231) --- .eslintrc.js | 7 +++++++ test/e2e/.eslintrc.js | 5 ----- 2 files changed, 7 insertions(+), 5 deletions(-) delete mode 100644 test/e2e/.eslintrc.js diff --git a/.eslintrc.js b/.eslintrc.js index 2e74bd47614c..ee22e7f4c586 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -126,6 +126,13 @@ module.exports = { }, overrides: [{ + files: [ + 'test/e2e/**/*.js', + ], + rules: { + 'mocha/no-hooks-for-single-case': 'off', + }, + }, { files: [ 'app/scripts/migrations/*.js', '*.stories.js', diff --git a/test/e2e/.eslintrc.js b/test/e2e/.eslintrc.js deleted file mode 100644 index d84a4b2fb6d7..000000000000 --- a/test/e2e/.eslintrc.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - rules: { - 'mocha/no-hooks-for-single-case': 'off', - }, -} From e021acdc0ad52e06ca5bbcf6c55022d0c3e37bf7 Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Fri, 14 Aug 2020 15:13:56 -0230 Subject: [PATCH 102/107] Fix max-statements-per-line issues (#9218) See [`max-statements-per-line`](https://eslint.org/docs/rules/max-statements-per-line) for more information. This change enables `max-statements-per-line` and fixes the issues raised by the rule. --- .eslintrc.js | 1 + app/scripts/lib/decrypt-message-manager.js | 3 ++- app/scripts/lib/encryption-public-key-manager.js | 3 ++- app/scripts/lib/message-manager.js | 3 ++- app/scripts/lib/personal-message-manager.js | 3 ++- app/scripts/lib/typed-message-manager.js | 3 ++- 6 files changed, 11 insertions(+), 5 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index ee22e7f4c586..2f5e14f17031 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -49,6 +49,7 @@ module.exports = { 'guard-for-in': 'error', 'implicit-arrow-linebreak': 'error', 'import/extensions': ['error', 'never', { 'json': 'always' }], + 'max-statements-per-line': ['error', { 'max': 1 }], 'no-case-declarations': 'error', 'no-constant-condition': 'error', 'no-dupe-else-if': 'error', diff --git a/app/scripts/lib/decrypt-message-manager.js b/app/scripts/lib/decrypt-message-manager.js index a379d656146b..d6986f52fe34 100644 --- a/app/scripts/lib/decrypt-message-manager.js +++ b/app/scripts/lib/decrypt-message-manager.js @@ -65,7 +65,8 @@ export default class DecryptMessageManager extends EventEmitter { getUnapprovedMsgs () { return this.messages.filter((msg) => msg.status === 'unapproved') .reduce((result, msg) => { - result[msg.id] = msg; return result + result[msg.id] = msg + return result }, {}) } diff --git a/app/scripts/lib/encryption-public-key-manager.js b/app/scripts/lib/encryption-public-key-manager.js index 6ca78fdcfc76..7550a26e37e5 100644 --- a/app/scripts/lib/encryption-public-key-manager.js +++ b/app/scripts/lib/encryption-public-key-manager.js @@ -62,7 +62,8 @@ export default class EncryptionPublicKeyManager extends EventEmitter { getUnapprovedMsgs () { return this.messages.filter((msg) => msg.status === 'unapproved') .reduce((result, msg) => { - result[msg.id] = msg; return result + result[msg.id] = msg + return result }, {}) } diff --git a/app/scripts/lib/message-manager.js b/app/scripts/lib/message-manager.js index b06246852079..6c74b017adca 100644 --- a/app/scripts/lib/message-manager.js +++ b/app/scripts/lib/message-manager.js @@ -64,7 +64,8 @@ export default class MessageManager extends EventEmitter { getUnapprovedMsgs () { return this.messages.filter((msg) => msg.status === 'unapproved') .reduce((result, msg) => { - result[msg.id] = msg; return result + result[msg.id] = msg + return result }, {}) } diff --git a/app/scripts/lib/personal-message-manager.js b/app/scripts/lib/personal-message-manager.js index 647b7e58938c..7770e810658f 100644 --- a/app/scripts/lib/personal-message-manager.js +++ b/app/scripts/lib/personal-message-manager.js @@ -68,7 +68,8 @@ export default class PersonalMessageManager extends EventEmitter { getUnapprovedMsgs () { return this.messages.filter((msg) => msg.status === 'unapproved') .reduce((result, msg) => { - result[msg.id] = msg; return result + result[msg.id] = msg + return result }, {}) } diff --git a/app/scripts/lib/typed-message-manager.js b/app/scripts/lib/typed-message-manager.js index 4bab84d3e964..38762a97ac5d 100644 --- a/app/scripts/lib/typed-message-manager.js +++ b/app/scripts/lib/typed-message-manager.js @@ -61,7 +61,8 @@ export default class TypedMessageManager extends EventEmitter { getUnapprovedMsgs () { return this.messages.filter((msg) => msg.status === 'unapproved') .reduce((result, msg) => { - result[msg.id] = msg; return result + result[msg.id] = msg + return result }, {}) } From c2edc342fb96e2228eeefee93e32e82567ad2a71 Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Fri, 14 Aug 2020 15:51:48 -0230 Subject: [PATCH 103/107] Remove unused buyEth fn from bg (#9236) --- app/scripts/metamask-controller.js | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 1e4af85b1678..cd372978418d 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -47,7 +47,6 @@ import { PermissionsController } from './controllers/permissions' import getRestrictedMethods from './controllers/permissions/restrictedMethods' import nodeify from './lib/nodeify' import accountImporter from './account-import-strategies' -import getBuyEthUrl from './lib/buy-eth-url' import selectChainId from './lib/select-chain-id' import { Mutex } from 'await-semaphore' import { version } from '../manifest/_base.json' @@ -449,7 +448,6 @@ export default class MetamaskController extends EventEmitter { setCurrentLocale: this.setCurrentLocale.bind(this), markPasswordForgotten: this.markPasswordForgotten.bind(this), unMarkPasswordForgotten: this.unMarkPasswordForgotten.bind(this), - buyEth: this.buyEth.bind(this), safelistPhishingDomain: this.safelistPhishingDomain.bind(this), getRequestAccountTabIds: (cb) => cb(null, this.getRequestAccountTabIds()), getOpenMetamaskTabsIds: (cb) => cb(null, this.getOpenMetamaskTabsIds()), @@ -1879,24 +1877,6 @@ export default class MetamaskController extends EventEmitter { } } - /** - * A method for forwarding the user to the easiest way to obtain ether, - * or the network "gas" currency, for the current selected network. - * - * @param {string} address - The address to fund. - * @param {string} amount - The amount of ether desired, as a base 10 string. - */ - buyEth (address, amount) { - if (!amount) { - amount = '5' - } - const network = this.networkController.getNetworkState() - const url = getBuyEthUrl({ network, address, amount }) - if (url) { - this.platform.openTab({ url }) - } - } - // network /** * A method for selecting a custom URL for an ethereum RPC provider and updating it From aaacf66c6efe95947223539efbc9d01e49db3149 Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Fri, 14 Aug 2020 17:34:56 -0230 Subject: [PATCH 104/107] Fix import/no-extraneous-dependencies issues (#9232) See [`import/no-extraneous-dependencies`](https://eslint.org/docs/rules/import/no-extraneous-dependencies) for more information. This change enables `import/no-extraneous-dependencies` and fixes the issues raised by the rule. --- .eslintrc.js | 1 + test/unit/app/controllers/ens-controller-test.js | 4 +--- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 2f5e14f17031..6c505839039a 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -49,6 +49,7 @@ module.exports = { 'guard-for-in': 'error', 'implicit-arrow-linebreak': 'error', 'import/extensions': ['error', 'never', { 'json': 'always' }], + 'import/no-extraneous-dependencies': 'error', 'max-statements-per-line': ['error', { 'max': 1 }], 'no-case-declarations': 'error', 'no-constant-condition': 'error', diff --git a/test/unit/app/controllers/ens-controller-test.js b/test/unit/app/controllers/ens-controller-test.js index 1176b8b42037..cf57bdf1fbad 100644 --- a/test/unit/app/controllers/ens-controller-test.js +++ b/test/unit/app/controllers/ens-controller-test.js @@ -1,7 +1,6 @@ import assert from 'assert' import sinon from 'sinon' import ObservableStore from 'obs-store' -import HttpProvider from 'ethjs-provider-http' import EnsController from '../../../../app/scripts/controllers/ens' const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' @@ -10,11 +9,10 @@ const ZERO_X_ERROR_ADDRESS = '0x' describe('EnsController', function () { describe('#constructor', function () { it('should construct the controller given a provider and a network', async function () { - const provider = new HttpProvider('https://ropsten.infura.io') const currentNetworkId = '3' const networkStore = new ObservableStore(currentNetworkId) const ens = new EnsController({ - provider, + provider: { }, networkStore, }) From f77151003e7ad12a4c265b1b95645d0e9a14211c Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Fri, 14 Aug 2020 19:15:21 -0300 Subject: [PATCH 105/107] Send web3 usage metrics once per origin/property (#9237) The usage metrics for the injected web3 instance were being sent upon each use, which exceeded the limits of our Matomo plan. These metrics are now only being sent upon the first usage, for each origin and property. --- app/scripts/lib/createMethodMiddleware.js | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/app/scripts/lib/createMethodMiddleware.js b/app/scripts/lib/createMethodMiddleware.js index 8224dbf332e8..7a98ce531255 100644 --- a/app/scripts/lib/createMethodMiddleware.js +++ b/app/scripts/lib/createMethodMiddleware.js @@ -1,3 +1,6 @@ + +const recordedWeb3Usage = {} + /** * Returns a middleware that implements the following RPC methods: * - metamask_logInjectedWeb3Usage @@ -15,11 +18,17 @@ export default function createMethodMiddleware ({ origin, sendMetrics }) { const { action, name } = req.params[0] - sendMetrics({ - action, - name, - customVariables: { origin }, - }) + if (!recordedWeb3Usage[origin]) { + recordedWeb3Usage[origin] = {} + } + if (!recordedWeb3Usage[origin][name]) { + recordedWeb3Usage[origin][name] = true + sendMetrics({ + action, + name, + customVariables: { origin }, + }) + } res.result = true break From aad840777deb9e59967cf8e6921198261b25ee73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patryk=20=C5=81ucka?= Date: Sat, 15 Aug 2020 00:18:46 +0200 Subject: [PATCH 106/107] Permit all-caps addresses (#9227) * permit all-caps addresses * handle empty address --- ui/app/helpers/utils/util.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/app/helpers/utils/util.js b/ui/app/helpers/utils/util.js index 98e58053f86f..28f3dd6f46b7 100644 --- a/ui/app/helpers/utils/util.js +++ b/ui/app/helpers/utils/util.js @@ -62,11 +62,11 @@ export function addressSummary (address, firstSegLength = 10, lastSegLength = 4, } export function isValidAddress (address) { - const prefixed = ethUtil.addHexPrefix(address) - if (address === '0x0000000000000000000000000000000000000000') { + if (!address || address === '0x0000000000000000000000000000000000000000') { return false } - return (isAllOneCase(prefixed) && ethUtil.isValidAddress(prefixed)) || ethUtil.isValidChecksumAddress(prefixed) + const prefixed = address.startsWith('0X') ? address : ethUtil.addHexPrefix(address) + return (isAllOneCase(prefixed.slice(2)) && ethUtil.isValidAddress(prefixed)) || ethUtil.isValidChecksumAddress(prefixed) } export function isValidDomainName (address) { From 87e5281a822fc6a6e5f4b2f03d37da7080d54696 Mon Sep 17 00:00:00 2001 From: Thomas Huang Date: Fri, 14 Aug 2020 16:08:26 -0700 Subject: [PATCH 107/107] Clear Account Details in AppState (#9238) * Clear Account Details in AppState We store sensitive information in the AppState under accountDetail for when the modal is active and present. This adds a new action/reducer and componentWillUnmount to clean up the persisted data left after leaving the modal. * Remove reduntant clearAccountDetails call when clicking done button --- test/unit/ui/app/reducers/app.spec.js | 17 +++++++++++++++++ .../export-private-key-modal.component.js | 5 +++++ .../export-private-key-modal.container.js | 3 ++- ui/app/ducks/app/app.js | 6 ++++++ ui/app/store/actionConstants.js | 1 + ui/app/store/actions.js | 6 ++++++ 6 files changed, 37 insertions(+), 1 deletion(-) diff --git a/test/unit/ui/app/reducers/app.spec.js b/test/unit/ui/app/reducers/app.spec.js index cd135d22076f..027f574f2181 100644 --- a/test/unit/ui/app/reducers/app.spec.js +++ b/test/unit/ui/app/reducers/app.spec.js @@ -167,6 +167,23 @@ describe('App State', function () { }) + it('clears account details', function () { + const exportPrivKeyModal = { + accountDetail: { + subview: 'export', + accountExport: 'completed', + privateKey: 'a-priv-key', + }, + } + + const state = { ...metamaskState, appState: { ...exportPrivKeyModal } } + const newState = reduceApp(state, { + type: actions.CLEAR_ACCOUNT_DETAILS, + }) + + assert.deepStrictEqual(newState.accountDetail, {}) + }) + it('shoes account page', function () { const state = reduceApp(metamaskState, { type: actions.SHOW_ACCOUNTS_PAGE, diff --git a/ui/app/components/app/modals/export-private-key-modal/export-private-key-modal.component.js b/ui/app/components/app/modals/export-private-key-modal/export-private-key-modal.component.js index ba30d5218919..965d5a703a9d 100644 --- a/ui/app/components/app/modals/export-private-key-modal/export-private-key-modal.component.js +++ b/ui/app/components/app/modals/export-private-key-modal/export-private-key-modal.component.js @@ -25,6 +25,7 @@ export default class ExportPrivateKeyModal extends Component { warning: PropTypes.node, showAccountDetailModal: PropTypes.func.isRequired, hideModal: PropTypes.func.isRequired, + clearAccountDetails: PropTypes.func.isRequired, previousModalState: PropTypes.string, } @@ -34,6 +35,10 @@ export default class ExportPrivateKeyModal extends Component { showWarning: true, } + componentWillUnmount () { + this.props.clearAccountDetails() + } + exportAccountAndGetPrivateKey = (password, address) => { const { exportAccount } = this.props diff --git a/ui/app/components/app/modals/export-private-key-modal/export-private-key-modal.container.js b/ui/app/components/app/modals/export-private-key-modal/export-private-key-modal.container.js index 3137292178c8..e1676f8be925 100644 --- a/ui/app/components/app/modals/export-private-key-modal/export-private-key-modal.container.js +++ b/ui/app/components/app/modals/export-private-key-modal/export-private-key-modal.container.js @@ -1,5 +1,5 @@ import { connect } from 'react-redux' -import { exportAccount, hideWarning, showModal, hideModal } from '../../../../store/actions' +import { exportAccount, hideWarning, showModal, hideModal, clearAccountDetails } from '../../../../store/actions' import { getSelectedIdentity } from '../../../../selectors' import ExportPrivateKeyModal from './export-private-key-modal.component' @@ -32,6 +32,7 @@ function mapDispatchToProps (dispatch) { }, showAccountDetailModal: () => dispatch(showModal({ name: 'ACCOUNT_DETAILS' })), hideModal: () => dispatch(hideModal()), + clearAccountDetails: () => dispatch(clearAccountDetails()), } } diff --git a/ui/app/ducks/app/app.js b/ui/app/ducks/app/app.js index f45a4b294c11..e7672dd37d7e 100644 --- a/ui/app/ducks/app/app.js +++ b/ui/app/ducks/app/app.js @@ -137,6 +137,12 @@ export default function reduceApp (state = {}, action) { ), } + case actionConstants.CLEAR_ACCOUNT_DETAILS: + return { + ...appState, + accountDetail: {}, + } + case actionConstants.FORGOT_PASSWORD: return { ...appState, diff --git a/ui/app/store/actionConstants.js b/ui/app/store/actionConstants.js index 4a11aa11135e..ac7cc11a0657 100644 --- a/ui/app/store/actionConstants.js +++ b/ui/app/store/actionConstants.js @@ -36,6 +36,7 @@ export const SET_CURRENT_FIAT = 'SET_CURRENT_FIAT' export const SHOW_SEND_TOKEN_PAGE = 'SHOW_SEND_TOKEN_PAGE' export const SHOW_PRIVATE_KEY = 'SHOW_PRIVATE_KEY' export const SET_ACCOUNT_LABEL = 'SET_ACCOUNT_LABEL' +export const CLEAR_ACCOUNT_DETAILS = 'CLEAR_ACCOUNT_DETAILS' // tx conf screen export const COMPLETED_TX = 'COMPLETED_TX' export const TRANSACTION_ERROR = 'TRANSACTION_ERROR' diff --git a/ui/app/store/actions.js b/ui/app/store/actions.js index 9f4396507235..c9c4a63dd109 100644 --- a/ui/app/store/actions.js +++ b/ui/app/store/actions.js @@ -1808,6 +1808,12 @@ export function setAccountLabel (account, label) { } } +export function clearAccountDetails () { + return { + type: actionConstants.CLEAR_ACCOUNT_DETAILS, + } +} + export function showSendTokenPage () { return { type: actionConstants.SHOW_SEND_TOKEN_PAGE,