Merge pull request #4190 from MetaMask/i3614-unlock

Add new unlock screen design
feature/default_network_editable
Frankie 7 years ago committed by GitHub
commit 6bd1b21d3b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 11
      app/_locales/en/messages.json
  2. 1
      mascara/src/app/first-time/index.css
  3. 56
      mascara/src/app/first-time/index.js
  4. 319
      package-lock.json
  5. 1
      package.json
  6. 19
      test/integration/lib/mascara-first-time.js
  7. 4
      test/integration/lib/tx-list-items.js
  8. 2
      ui/app/actions.js
  9. 130
      ui/app/app.js
  10. 106
      ui/app/components/app-header/app-header.component.js
  11. 38
      ui/app/components/app-header/app-header.container.js
  12. 2
      ui/app/components/app-header/index.js
  13. 2
      ui/app/components/export-text-container/export-text-container.scss
  14. 2
      ui/app/components/pages/unlock-page/index.js
  15. 181
      ui/app/components/pages/unlock-page/unlock-page.component.js
  16. 33
      ui/app/components/pages/unlock-page/unlock-page.container.js
  17. 51
      ui/app/components/pages/unlock-page/unlock-page.scss
  18. 194
      ui/app/components/pages/unlock.js
  19. 2
      ui/app/components/text-field/index.js
  20. 54
      ui/app/components/text-field/text-field.component.js
  21. 24
      ui/app/components/text-field/text-field.stories.js
  22. 6
      ui/app/components/wallet-view.js
  23. 118
      ui/app/css/itcss/components/header.scss
  24. 4
      ui/app/css/itcss/components/pages/index.scss
  25. 9
      ui/app/css/itcss/components/pages/unlock.scss
  26. 41
      ui/app/css/itcss/components/sections.scss
  27. 4
      ui/app/css/itcss/components/settings.scss
  28. 5
      ui/app/css/itcss/generic/index.scss
  29. 2
      ui/app/main-container.js
  30. 141
      ui/app/unlock.js

@ -393,6 +393,9 @@
"message": "Imported", "message": "Imported",
"description": "status showing that an account has been fully loaded into the keyring" "description": "status showing that an account has been fully loaded into the keyring"
}, },
"importUsingSeed": {
"message": "Import using account seed phrase"
},
"infoHelp": { "infoHelp": {
"message": "Info & Help" "message": "Info & Help"
}, },
@ -632,7 +635,7 @@
"message": "Reset Account" "message": "Reset Account"
}, },
"restoreFromSeed": { "restoreFromSeed": {
"message": "Restore from seed phrase" "message": "Restore account?"
}, },
"restoreVault": { "restoreVault": {
"message": "Restore Vault" "message": "Restore Vault"
@ -896,6 +899,9 @@
"unknownNetworkId": { "unknownNetworkId": {
"message": "Unknown network ID" "message": "Unknown network ID"
}, },
"unlockMessage": {
"message": "The decentralized web awaits"
},
"uriErrorMsg": { "uriErrorMsg": {
"message": "URIs require the appropriate HTTP/HTTPS prefix." "message": "URIs require the appropriate HTTP/HTTPS prefix."
}, },
@ -924,6 +930,9 @@
"warning": { "warning": {
"message": "Warning" "message": "Warning"
}, },
"welcomeBack": {
"message": "Welcome Back!"
},
"welcomeBeta": { "welcomeBeta": {
"message": "Welcome to MetaMask Beta" "message": "Welcome to MetaMask Beta"
}, },

@ -21,6 +21,7 @@
display: flex; display: flex;
justify-content: center; justify-content: center;
background: #f7861c; background: #f7861c;
flex: 0 0 auto;
} }
.alpha-warning, .alpha-warning,

@ -3,6 +3,8 @@ import PropTypes from 'prop-types'
import {connect} from 'react-redux' import {connect} from 'react-redux'
import { withRouter, Switch, Route } from 'react-router-dom' import { withRouter, Switch, Route } from 'react-router-dom'
import { compose } from 'recompose' import { compose } from 'recompose'
import classnames from 'classnames'
import CreatePasswordScreen from './create-password-screen' import CreatePasswordScreen from './create-password-screen'
import UniqueImageScreen from './unique-image-screen' import UniqueImageScreen from './unique-image-screen'
import NoticeScreen from './notice-screen' import NoticeScreen from './notice-screen'
@ -33,6 +35,7 @@ class FirstTimeFlow extends Component {
isUnlocked: PropTypes.bool, isUnlocked: PropTypes.bool,
history: PropTypes.object, history: PropTypes.object,
welcomeScreenSeen: PropTypes.bool, welcomeScreenSeen: PropTypes.bool,
isPopup: PropTypes.bool,
}; };
static defaultProps = { static defaultProps = {
@ -41,23 +44,44 @@ class FirstTimeFlow extends Component {
noActiveNotices: false, noActiveNotices: false,
}; };
renderAppBar () {
const { welcomeScreenSeen } = this.props
return (
<div className="alpha-warning__container">
<h2 className={classnames({
'alpha-warning': welcomeScreenSeen,
'alpha-warning-welcome-screen': !welcomeScreenSeen,
})}
>
Please be aware that this version is still under development
</h2>
</div>
)
}
render () { render () {
const { isPopup } = this.props
return ( return (
<div className="first-time-flow"> <div className="flex-column flex-grow">
<Switch> { !isPopup && this.renderAppBar() }
<Route exact path={INITIALIZE_IMPORT_ACCOUNT_ROUTE} component={ImportAccountScreen} /> <div className="first-time-flow">
<Route <Switch>
exact <Route exact path={INITIALIZE_IMPORT_ACCOUNT_ROUTE} component={ImportAccountScreen} />
path={INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE} <Route
component={ImportSeedPhraseScreen} exact
/> path={INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE}
<Route exact path={INITIALIZE_UNIQUE_IMAGE_ROUTE} component={UniqueImageScreen} /> component={ImportSeedPhraseScreen}
<Route exact path={INITIALIZE_NOTICE_ROUTE} component={NoticeScreen} /> />
<Route exact path={INITIALIZE_BACKUP_PHRASE_ROUTE} component={BackupPhraseScreen} /> <Route exact path={INITIALIZE_UNIQUE_IMAGE_ROUTE} component={UniqueImageScreen} />
<Route exact path={INITIALIZE_CONFIRM_SEED_ROUTE} component={ConfirmSeed} /> <Route exact path={INITIALIZE_NOTICE_ROUTE} component={NoticeScreen} />
<Route exact path={INITIALIZE_CREATE_PASSWORD_ROUTE} component={CreatePasswordScreen} /> <Route exact path={INITIALIZE_BACKUP_PHRASE_ROUTE} component={BackupPhraseScreen} />
<Route exact path={INITIALIZE_ROUTE} component={WelcomeScreen} /> <Route exact path={INITIALIZE_CONFIRM_SEED_ROUTE} component={ConfirmSeed} />
</Switch> <Route exact path={INITIALIZE_CREATE_PASSWORD_ROUTE} component={CreatePasswordScreen} />
<Route exact path={INITIALIZE_ROUTE} component={WelcomeScreen} />
</Switch>
</div>
</div> </div>
) )
} }
@ -73,6 +97,7 @@ const mapStateToProps = ({ metamask }) => {
isMascara, isMascara,
isUnlocked, isUnlocked,
welcomeScreenSeen, welcomeScreenSeen,
isPopup,
} = metamask } = metamask
return { return {
@ -84,6 +109,7 @@ const mapStateToProps = ({ metamask }) => {
forgottenPassword, forgottenPassword,
isUnlocked, isUnlocked,
welcomeScreenSeen, welcomeScreenSeen,
isPopup,
} }
} }

319
package-lock.json generated

@ -1365,12 +1365,37 @@
} }
} }
}, },
"@types/jss": {
"version": "9.5.2",
"resolved": "https://registry.npmjs.org/@types/jss/-/jss-9.5.2.tgz",
"integrity": "sha512-EX87yNYcisXO5BU9tT7stB7OGuDJyV3JwtMwhfUprrmHwYKWh9a3vchAy6DYzUSbmTA7bD46h8qata5jP1V7Zw==",
"requires": {
"csstype": "2.4.2",
"indefinite-observable": "1.0.1"
}
},
"@types/node": { "@types/node": {
"version": "8.5.5", "version": "8.5.5",
"resolved": "https://registry.npmjs.org/@types/node/-/node-8.5.5.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-8.5.5.tgz",
"integrity": "sha512-JRnfoh0Ll4ElmIXKxbUfcOodkGvcNHljct6mO1X9hE/mlrMzAx0hYCLAD7sgT53YAY1HdlpzUcV0CkmDqUqTuA==", "integrity": "sha512-JRnfoh0Ll4ElmIXKxbUfcOodkGvcNHljct6mO1X9hE/mlrMzAx0hYCLAD7sgT53YAY1HdlpzUcV0CkmDqUqTuA==",
"dev": true "dev": true
}, },
"@types/react": {
"version": "16.3.14",
"resolved": "https://registry.npmjs.org/@types/react/-/react-16.3.14.tgz",
"integrity": "sha512-wNUGm49fPl7eE2fnYdF0v5vSOrUMdKMQD/4NwtQRnb6mnPwtkhabmuFz37eq90+hhyfz0pWd38jkZHOcaZ6LGw==",
"requires": {
"csstype": "2.4.2"
}
},
"@types/react-transition-group": {
"version": "2.0.9",
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-2.0.9.tgz",
"integrity": "sha512-Id2MtQcmOgLymqqLqg1VjzNpN7O5vGoF47h3s7jxhzqWdMCtk2GwxFUqcKbGrRmHzzQGyRatfG8yahonIys74Q==",
"requires": {
"@types/react": "16.3.14"
}
},
"JSONStream": { "JSONStream": {
"version": "1.3.2", "version": "1.3.2",
"resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.2.tgz", "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.2.tgz",
@ -3862,8 +3887,7 @@
"brcast": { "brcast": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/brcast/-/brcast-3.0.1.tgz", "resolved": "https://registry.npmjs.org/brcast/-/brcast-3.0.1.tgz",
"integrity": "sha512-eI3yqf9YEqyGl9PCNTR46MGvDylGtaHjalcz6Q3fAPnP/PhpKkkve52vFdfGpwp4VUvK6LUr4TQN+2stCrEwTg==", "integrity": "sha512-eI3yqf9YEqyGl9PCNTR46MGvDylGtaHjalcz6Q3fAPnP/PhpKkkve52vFdfGpwp4VUvK6LUr4TQN+2stCrEwTg=="
"dev": true
}, },
"brfs": { "brfs": {
"version": "1.4.3", "version": "1.4.3",
@ -5907,6 +5931,14 @@
} }
} }
}, },
"css-vendor": {
"version": "0.3.8",
"resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-0.3.8.tgz",
"integrity": "sha1-ZCHP0wNM5mT+dnOXL9ARn8KJQfo=",
"requires": {
"is-in-browser": "1.1.3"
}
},
"css-what": { "css-what": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.0.tgz", "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.0.tgz",
@ -6053,8 +6085,7 @@
"csstype": { "csstype": {
"version": "2.4.2", "version": "2.4.2",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-2.4.2.tgz", "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.4.2.tgz",
"integrity": "sha512-1TnkyZwDy0oUl//6685j2bTMNe61SzntWntijNdmmEzvpYbGmVMZkj204gv4glcQp6z/ypg+YRziT91XVFmOyg==", "integrity": "sha512-1TnkyZwDy0oUl//6685j2bTMNe61SzntWntijNdmmEzvpYbGmVMZkj204gv4glcQp6z/ypg+YRziT91XVFmOyg=="
"dev": true
}, },
"currency-formatter": { "currency-formatter": {
"version": "1.4.2", "version": "1.4.2",
@ -13161,8 +13192,7 @@
"hyphenate-style-name": { "hyphenate-style-name": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.2.tgz", "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.2.tgz",
"integrity": "sha1-MRYKNpMK2vH8BMYHT360FGXU7Es=", "integrity": "sha1-MRYKNpMK2vH8BMYHT360FGXU7Es="
"dev": true
}, },
"i": { "i": {
"version": "0.3.6", "version": "0.3.6",
@ -13301,6 +13331,21 @@
"resolved": "https://registry.npmjs.org/in-publish/-/in-publish-2.0.0.tgz", "resolved": "https://registry.npmjs.org/in-publish/-/in-publish-2.0.0.tgz",
"integrity": "sha1-4g/146KvwmkDILbcVSaCqcf631E=" "integrity": "sha1-4g/146KvwmkDILbcVSaCqcf631E="
}, },
"indefinite-observable": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/indefinite-observable/-/indefinite-observable-1.0.1.tgz",
"integrity": "sha1-CZFUI8yNb36xy3iCrRNGM8mm7cM=",
"requires": {
"symbol-observable": "1.0.4"
},
"dependencies": {
"symbol-observable": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.4.tgz",
"integrity": "sha1-Kb9hXUqnEhvdiYsi1LP5vE4qoD0="
}
}
},
"indent-string": { "indent-string": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz",
@ -13792,6 +13837,11 @@
"resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.1.tgz", "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.1.tgz",
"integrity": "sha1-bghLvJIGH7sJcexYts5tQE4k2mk=" "integrity": "sha1-bghLvJIGH7sJcexYts5tQE4k2mk="
}, },
"is-in-browser": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz",
"integrity": "sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU="
},
"is-my-ip-valid": { "is-my-ip-valid": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz", "resolved": "https://registry.npmjs.org/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz",
@ -14755,6 +14805,101 @@
"verror": "1.10.0" "verror": "1.10.0"
} }
}, },
"jss": {
"version": "9.8.1",
"resolved": "https://registry.npmjs.org/jss/-/jss-9.8.1.tgz",
"integrity": "sha512-a9dXInEPTRmdSmzw3LNhbAwdQVZgCRmFU7dFzrpLTMAcdolHXNamhxQ6J+PNIqUtWa9yRbZIzWX6aUlI55LZ/A==",
"requires": {
"is-in-browser": "1.1.3",
"symbol-observable": "1.1.0",
"warning": "3.0.0"
}
},
"jss-camel-case": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/jss-camel-case/-/jss-camel-case-6.1.0.tgz",
"integrity": "sha512-HPF2Q7wmNW1t79mCqSeU2vdd/vFFGpkazwvfHMOhPlMgXrJDzdj9viA2SaHk9ZbD5pfL63a8ylp4++irYbbzMQ==",
"requires": {
"hyphenate-style-name": "1.0.2"
}
},
"jss-compose": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/jss-compose/-/jss-compose-5.0.0.tgz",
"integrity": "sha512-YofRYuiA0+VbeOw0VjgkyO380sA4+TWDrW52nSluD9n+1FWOlDzNbgpZ/Sb3Y46+DcAbOS21W5jo6SAqUEiuwA==",
"requires": {
"warning": "3.0.0"
}
},
"jss-default-unit": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/jss-default-unit/-/jss-default-unit-8.0.2.tgz",
"integrity": "sha512-WxNHrF/18CdoAGw2H0FqOEvJdREXVXLazn7PQYU7V6/BWkCV0GkmWsppNiExdw8dP4TU1ma1dT9zBNJ95feLmg=="
},
"jss-expand": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/jss-expand/-/jss-expand-5.3.0.tgz",
"integrity": "sha512-NiM4TbDVE0ykXSAw6dfFmB1LIqXP/jdd0ZMnlvlGgEMkMt+weJIl8Ynq1DsuBY9WwkNyzWktdqcEW2VN0RAtQg=="
},
"jss-extend": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/jss-extend/-/jss-extend-6.2.0.tgz",
"integrity": "sha512-YszrmcB6o9HOsKPszK7NeDBNNjVyiW864jfoiHoMlgMIg2qlxKw70axZHqgczXHDcoyi/0/ikP1XaHDPRvYtEA==",
"requires": {
"warning": "3.0.0"
}
},
"jss-global": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/jss-global/-/jss-global-3.0.0.tgz",
"integrity": "sha512-wxYn7vL+TImyQYGAfdplg7yaxnPQ9RaXY/cIA8hawaVnmmWxDHzBK32u1y+RAvWboa3lW83ya3nVZ/C+jyjZ5Q=="
},
"jss-nested": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/jss-nested/-/jss-nested-6.0.1.tgz",
"integrity": "sha512-rn964TralHOZxoyEgeq3hXY8hyuCElnvQoVrQwKHVmu55VRDd6IqExAx9be5HgK0yN/+hQdgAXQl/GUrBbbSTA==",
"requires": {
"warning": "3.0.0"
}
},
"jss-preset-default": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/jss-preset-default/-/jss-preset-default-4.5.0.tgz",
"integrity": "sha512-qZbpRVtHT7hBPpZEBPFfafZKWmq3tA/An5RNqywDsZQGrlinIF/mGD9lmj6jGqu8GrED2SMHZ3pPKLmjCZoiaQ==",
"requires": {
"jss-camel-case": "6.1.0",
"jss-compose": "5.0.0",
"jss-default-unit": "8.0.2",
"jss-expand": "5.3.0",
"jss-extend": "6.2.0",
"jss-global": "3.0.0",
"jss-nested": "6.0.1",
"jss-props-sort": "6.0.0",
"jss-template": "1.0.1",
"jss-vendor-prefixer": "7.0.0"
}
},
"jss-props-sort": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/jss-props-sort/-/jss-props-sort-6.0.0.tgz",
"integrity": "sha512-E89UDcrphmI0LzmvYk25Hp4aE5ZBsXqMWlkFXS0EtPkunJkRr+WXdCNYbXbksIPnKlBenGB9OxzQY+mVc70S+g=="
},
"jss-template": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/jss-template/-/jss-template-1.0.1.tgz",
"integrity": "sha512-m5BqEWha17fmIVXm1z8xbJhY6GFJxNB9H68GVnCWPyGYfxiAgY9WTQyvDAVj+pYRgrXSOfN5V1T4+SzN1sJTeg==",
"requires": {
"warning": "3.0.0"
}
},
"jss-vendor-prefixer": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/jss-vendor-prefixer/-/jss-vendor-prefixer-7.0.0.tgz",
"integrity": "sha512-Agd+FKmvsI0HLcYXkvy8GYOw3AAASBUpsmIRvVQheps+JWaN892uFOInTr0DRydwaD91vSSUCU4NssschvF7MA==",
"requires": {
"css-vendor": "0.3.8"
}
},
"jstransform": { "jstransform": {
"version": "10.1.0", "version": "10.1.0",
"resolved": "https://registry.npmjs.org/jstransform/-/jstransform-10.1.0.tgz", "resolved": "https://registry.npmjs.org/jstransform/-/jstransform-10.1.0.tgz",
@ -15134,8 +15279,7 @@
"keycode": { "keycode": {
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/keycode/-/keycode-2.2.0.tgz", "resolved": "https://registry.npmjs.org/keycode/-/keycode-2.2.0.tgz",
"integrity": "sha1-PQr1bce4uOXLqNCpfxByBO7CKwQ=", "integrity": "sha1-PQr1bce4uOXLqNCpfxByBO7CKwQ="
"dev": true
}, },
"keyv": { "keyv": {
"version": "3.0.0", "version": "3.0.0",
@ -16642,6 +16786,78 @@
"integrity": "sha1-UpJZPmdUyxvMK5gDDk4Najr8nqE=", "integrity": "sha1-UpJZPmdUyxvMK5gDDk4Najr8nqE=",
"dev": true "dev": true
}, },
"material-ui": {
"version": "1.0.0-beta.44",
"resolved": "https://registry.npmjs.org/material-ui/-/material-ui-1.0.0-beta.44.tgz",
"integrity": "sha512-m5SJxvDz77bVKcjyZG/AyG6RBR+UUwkPgvHHLJa2jyAHBNtJMCQ5GVouTXOxaUKlvD5cbO/mcH0YtzugyQTAVg==",
"requires": {
"@types/jss": "9.5.2",
"@types/react-transition-group": "2.0.9",
"babel-runtime": "6.26.0",
"brcast": "3.0.1",
"classnames": "2.2.5",
"deepmerge": "2.1.0",
"dom-helpers": "3.3.1",
"hoist-non-react-statics": "2.5.0",
"jss": "9.8.1",
"jss-camel-case": "6.1.0",
"jss-default-unit": "8.0.2",
"jss-global": "3.0.0",
"jss-nested": "6.0.1",
"jss-props-sort": "6.0.0",
"jss-vendor-prefixer": "7.0.0",
"keycode": "2.2.0",
"lodash": "4.17.4",
"normalize-scroll-left": "0.1.2",
"prop-types": "15.6.1",
"react-event-listener": "0.5.3",
"react-jss": "8.4.0",
"react-lifecycles-compat": "2.0.2",
"react-popper": "0.10.4",
"react-scrollbar-size": "2.1.0",
"react-transition-group": "2.2.1",
"recompose": "0.27.0",
"scroll": "2.0.3",
"warning": "3.0.0"
},
"dependencies": {
"deepmerge": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.1.0.tgz",
"integrity": "sha512-Q89Z26KAfA3lpPGhbF6XMfYAm3jIV3avViy6KOJ2JLzFbeWHOvPQUu5aSJIWXap3gDZC2y1eF5HXEPI2wGqgvw=="
},
"hoist-non-react-statics": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.0.tgz",
"integrity": "sha512-6Bl6XsDT1ntE0lHbIhr4Kp2PGcleGZ66qu5Jqk8lc0Xc/IeG6gVLmwUGs/K0Us+L8VWoKgj0uWdPMataOsm31w=="
},
"react-lifecycles-compat": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-2.0.2.tgz",
"integrity": "sha512-BPksUj7VMAAFhcCw79sZA0Ow/LTAEjs3Sio1AQcuwLeOP+ua0f/08Su2wyiW+JjDDH6fRqNy3h5CLXh21u1mVg=="
},
"recompose": {
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/recompose/-/recompose-0.27.0.tgz",
"integrity": "sha512-hivr1EopLhzjchhv2Y7VcLA2H5NGztwV/qfYqmIAhTkNowNQ9PyXdfq9Q8QCa0TMrPM1NtStlUyi5I/p8XfUNQ==",
"requires": {
"babel-runtime": "6.26.0",
"change-emitter": "0.1.6",
"fbjs": "0.8.16",
"hoist-non-react-statics": "2.5.0",
"react-lifecycles-compat": "3.0.3",
"symbol-observable": "1.1.0"
},
"dependencies": {
"react-lifecycles-compat": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.3.tgz",
"integrity": "sha512-bOr65SSYgxDgDNqLnDqt+gropXGPNB1Wbyys4tOYiNuP/qYWC4qFM9XH1ruzq+tT6EjE29pJsCr19rclKtpUEg=="
}
}
}
}
},
"math-expression-evaluator": { "math-expression-evaluator": {
"version": "1.2.17", "version": "1.2.17",
"resolved": "https://registry.npmjs.org/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz", "resolved": "https://registry.npmjs.org/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz",
@ -18140,6 +18356,11 @@
"resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
"integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=" "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI="
}, },
"normalize-scroll-left": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/normalize-scroll-left/-/normalize-scroll-left-0.1.2.tgz",
"integrity": "sha512-F9YMRls0zCF6BFIE2YnXDRpHPpfd91nOIaNdDgrx5YMoPLo8Wqj+6jNXHQsYBavJeXP4ww8HCt0xQAKc5qk2Fg=="
},
"normalize-selector": { "normalize-selector": {
"version": "0.2.0", "version": "0.2.0",
"resolved": "https://registry.npmjs.org/normalize-selector/-/normalize-selector-0.2.0.tgz", "resolved": "https://registry.npmjs.org/normalize-selector/-/normalize-selector-0.2.0.tgz",
@ -23395,6 +23616,14 @@
"performance-now": "2.1.0" "performance-now": "2.1.0"
} }
}, },
"rafl": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/rafl/-/rafl-1.2.2.tgz",
"integrity": "sha1-/pMPdYIRAg1H44gV9Rlqi+QVB0A=",
"requires": {
"global": "4.3.2"
}
},
"railroad-diagrams": { "railroad-diagrams": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz", "resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz",
@ -23688,6 +23917,17 @@
"integrity": "sha512-FlsPxavEyMuR6TjVbSSywovXSEyOg6ZDj5+Z8nbsRl9EkOzAhEIcS+GLoQDC5fz/t9suhUXWmUrOBrgeUvrMxw==", "integrity": "sha512-FlsPxavEyMuR6TjVbSSywovXSEyOg6ZDj5+Z8nbsRl9EkOzAhEIcS+GLoQDC5fz/t9suhUXWmUrOBrgeUvrMxw==",
"dev": true "dev": true
}, },
"react-event-listener": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/react-event-listener/-/react-event-listener-0.5.3.tgz",
"integrity": "sha512-fTGYvhe7eTsqq0m664Km0rxKQcqLIGZWZINmy1LU0fu312tay8Mt3Twq2P5Xj1dfDVvvzT1Ql3/FDkiMPJ1MOg==",
"requires": {
"babel-runtime": "6.26.0",
"fbjs": "0.8.16",
"prop-types": "15.6.1",
"warning": "3.0.0"
}
},
"react-fuzzy": { "react-fuzzy": {
"version": "0.5.2", "version": "0.5.2",
"resolved": "https://registry.npmjs.org/react-fuzzy/-/react-fuzzy-0.5.2.tgz", "resolved": "https://registry.npmjs.org/react-fuzzy/-/react-fuzzy-0.5.2.tgz",
@ -23747,6 +23987,18 @@
"is-dom": "1.0.9" "is-dom": "1.0.9"
} }
}, },
"react-jss": {
"version": "8.4.0",
"resolved": "https://registry.npmjs.org/react-jss/-/react-jss-8.4.0.tgz",
"integrity": "sha512-yIi4udcTIIh5u4KJ47wsL3UZYMuSrp5xR1YBvPeRNshpCdRoJxt5BWmCu1RA3LIa+//dnRsAtAQmMAYeg1W9oQ==",
"requires": {
"hoist-non-react-statics": "2.3.1",
"jss": "9.8.1",
"jss-preset-default": "4.5.0",
"prop-types": "15.6.1",
"theming": "1.3.0"
}
},
"react-lifecycles-compat": { "react-lifecycles-compat": {
"version": "3.0.2", "version": "3.0.2",
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.2.tgz", "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.2.tgz",
@ -23800,6 +24052,22 @@
"integrity": "sha512-p84kBqGaMoa7VYT0vZ/aOYRfJB+gw34yjpda1Z5KeLflg70HipZOT+MXQenEhdkPAABuE2Astq4zEPdMqUQxcg==", "integrity": "sha512-p84kBqGaMoa7VYT0vZ/aOYRfJB+gw34yjpda1Z5KeLflg70HipZOT+MXQenEhdkPAABuE2Astq4zEPdMqUQxcg==",
"dev": true "dev": true
}, },
"react-popper": {
"version": "0.10.4",
"resolved": "https://registry.npmjs.org/react-popper/-/react-popper-0.10.4.tgz",
"integrity": "sha1-rypBXqIike3VBGeNev2opu4ylao=",
"requires": {
"popper.js": "1.14.3",
"prop-types": "15.6.1"
},
"dependencies": {
"popper.js": {
"version": "1.14.3",
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.14.3.tgz",
"integrity": "sha1-FDj5jQRqz3tNeM1QK/QYrGTU8JU="
}
}
},
"react-redux": { "react-redux": {
"version": "5.0.6", "version": "5.0.6",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-5.0.6.tgz", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-5.0.6.tgz",
@ -23855,6 +24123,17 @@
"warning": "3.0.0" "warning": "3.0.0"
} }
}, },
"react-scrollbar-size": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/react-scrollbar-size/-/react-scrollbar-size-2.1.0.tgz",
"integrity": "sha512-9dDUJvk7S48r0TRKjlKJ9e/LkLLYgc9LdQR6W21I8ZqtSrEsedPOoMji4nU3DHy7fx2l8YMScJS/N7qiloYzXQ==",
"requires": {
"babel-runtime": "6.26.0",
"prop-types": "15.6.1",
"react-event-listener": "0.5.3",
"stifle": "1.0.4"
}
},
"react-select": { "react-select": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/react-select/-/react-select-1.1.0.tgz", "resolved": "https://registry.npmjs.org/react-select/-/react-select-1.1.0.tgz",
@ -25106,6 +25385,14 @@
} }
} }
}, },
"scroll": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/scroll/-/scroll-2.0.3.tgz",
"integrity": "sha512-3ncZzf8gUW739h3LeS68nSssO60O+GGjT3SxzgofQmT8PIoyHzebql9HHPJopZX8iT6TKOdwaWFMqL6LzUN3DQ==",
"requires": {
"rafl": "1.2.2"
}
},
"scrypt": { "scrypt": {
"version": "6.0.3", "version": "6.0.3",
"resolved": "https://registry.npmjs.org/scrypt/-/scrypt-6.0.3.tgz", "resolved": "https://registry.npmjs.org/scrypt/-/scrypt-6.0.3.tgz",
@ -26522,6 +26809,11 @@
"resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz",
"integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks="
}, },
"stifle": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/stifle/-/stifle-1.0.4.tgz",
"integrity": "sha1-izvN9SQZsKnHnjWtrc5QEjwdjpk="
},
"stream-browserify": { "stream-browserify": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz", "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz",
@ -27985,6 +28277,17 @@
"integrity": "sha1-ZUhjk+4fK7A5pgy7oFsLaL2VAdI=", "integrity": "sha1-ZUhjk+4fK7A5pgy7oFsLaL2VAdI=",
"dev": true "dev": true
}, },
"theming": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/theming/-/theming-1.3.0.tgz",
"integrity": "sha512-ya5Ef7XDGbTPBv5ENTwrwkPUexrlPeiAg/EI9kdlUAZhNlRbCdhMKRgjNX1IcmsmiPcqDQZE6BpSaH+cr31FKw==",
"requires": {
"brcast": "3.0.1",
"is-function": "1.0.1",
"is-plain-object": "2.0.4",
"prop-types": "15.6.1"
}
},
"thenify": { "thenify": {
"version": "3.3.0", "version": "3.3.0",
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.0.tgz", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.0.tgz",

@ -136,6 +136,7 @@
"lodash.shuffle": "^4.2.0", "lodash.shuffle": "^4.2.0",
"lodash.uniqby": "^4.7.0", "lodash.uniqby": "^4.7.0",
"loglevel": "^1.4.1", "loglevel": "^1.4.1",
"material-ui": "1.0.0-beta.44",
"metamascara": "^2.0.0", "metamascara": "^2.0.0",
"metamask-logo": "^2.1.4", "metamask-logo": "^2.1.4",
"mkdirp": "^0.5.1", "mkdirp": "^0.5.1",

@ -71,10 +71,23 @@ async function runFirstTimeUsageTest (assert, done) {
assert.ok(lock, 'Lock menu item found') assert.ok(lock, 'Lock menu item found')
lock.click() lock.click()
const pwBox2 = (await findAsync(app, '#password-box'))[0] await timeout(1000)
pwBox2.value = PASSWORD
const createButton2 = (await findAsync(app, 'button.primary'))[0] const pwBox2 = (await findAsync(app, '#password'))[0]
pwBox2.focus()
await timeout(1000)
// Used to set values on TextField input component
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
window.HTMLInputElement.prototype, 'value'
).set
nativeInputValueSetter.call(pwBox2, PASSWORD)
var ev2 = new Event('input', { bubbles: true})
pwBox2.dispatchEvent(ev2)
const createButton2 = (await findAsync(app, 'button[type="submit"]'))[0]
createButton2.click() createButton2.click()
const detail2 = (await findAsync(app, '.wallet-view'))[0] const detail2 = (await findAsync(app, '.wallet-view'))[0]

@ -21,7 +21,7 @@ async function runTxListItemsTest(assert, done) {
selectState.val('tx list items') selectState.val('tx list items')
reactTriggerChange(selectState[0]) reactTriggerChange(selectState[0])
const metamaskLogo = await queryAsync($, '.left-menu-wrapper') const metamaskLogo = await queryAsync($, '.app-header__logo-container')
assert.ok(metamaskLogo[0], 'metamask logo present') assert.ok(metamaskLogo[0], 'metamask logo present')
metamaskLogo[0].click() metamaskLogo[0].click()
@ -46,7 +46,7 @@ async function runTxListItemsTest(assert, done) {
const failedTx = txListItems[4] const failedTx = txListItems[4]
const failedTxRenderedStatus = await findAsync($(failedTx), '.tx-list-status') const failedTxRenderedStatus = await findAsync($(failedTx), '.tx-list-status')
assert.equal(failedTxRenderedStatus[0].textContent, 'Failed', 'failedTx has correct label') assert.equal(failedTxRenderedStatus[0].textContent, 'Failed', 'failedTx has correct label')
const shapeShiftTx = txListItems[5] const shapeShiftTx = txListItems[5]
const shapeShiftTxStatus = await findAsync($(shapeShiftTx), '.flex-column div:eq(1)') const shapeShiftTxStatus = await findAsync($(shapeShiftTx), '.flex-column div:eq(1)')
assert.equal(shapeShiftTxStatus[0].textContent, 'No deposits received', 'shapeShiftTx has correct status') assert.equal(shapeShiftTxStatus[0].textContent, 'No deposits received', 'shapeShiftTx has correct status')

@ -317,6 +317,7 @@ function tryUnlockMetamask (password) {
background.verifySeedPhrase(err => { background.verifySeedPhrase(err => {
if (err) { if (err) {
dispatch(actions.displayWarning(err.message)) dispatch(actions.displayWarning(err.message))
return reject(err)
} }
resolve() resolve()
@ -330,6 +331,7 @@ function tryUnlockMetamask (password) {
.catch(err => { .catch(err => {
dispatch(actions.unlockFailed(err.message)) dispatch(actions.unlockFailed(err.message))
dispatch(actions.hideLoadingIndication()) dispatch(actions.hideLoadingIndication())
return Promise.reject(err)
}) })
} }
} }

@ -1,7 +1,7 @@
const { Component } = require('react') const { Component } = require('react')
const PropTypes = require('prop-types') const PropTypes = require('prop-types')
const connect = require('react-redux').connect const connect = require('react-redux').connect
const { Route, Switch, withRouter } = require('react-router-dom') const { Route, Switch, withRouter, matchPath } = require('react-router-dom')
const { compose } = require('recompose') const { compose } = require('recompose')
const h = require('react-hyperscript') const h = require('react-hyperscript')
const actions = require('./actions') const actions = require('./actions')
@ -22,7 +22,7 @@ const Home = require('./components/pages/home')
const Authenticated = require('./components/pages/authenticated') const Authenticated = require('./components/pages/authenticated')
const Initialized = require('./components/pages/initialized') const Initialized = require('./components/pages/initialized')
const Settings = require('./components/pages/settings') const Settings = require('./components/pages/settings')
const UnlockPage = require('./components/pages/unlock') const UnlockPage = require('./components/pages/unlock-page')
const RestoreVaultPage = require('./components/pages/keychains/restore-vault') const RestoreVaultPage = require('./components/pages/keychains/restore-vault')
const RevealSeedConfirmation = require('./components/pages/keychains/reveal-seed') const RevealSeedConfirmation = require('./components/pages/keychains/reveal-seed')
const AddTokenPage = require('./components/pages/add-token') const AddTokenPage = require('./components/pages/add-token')
@ -30,8 +30,6 @@ const CreateAccountPage = require('./components/pages/create-account')
const NoticeScreen = require('./components/pages/notice') const NoticeScreen = require('./components/pages/notice')
const Loading = require('./components/loading-screen') const Loading = require('./components/loading-screen')
const NetworkIndicator = require('./components/network')
const Identicon = require('./components/identicon')
const ReactCSSTransitionGroup = require('react-addons-css-transition-group') const ReactCSSTransitionGroup = require('react-addons-css-transition-group')
const NetworkDropdown = require('./components/dropdowns/network-dropdown') const NetworkDropdown = require('./components/dropdowns/network-dropdown')
const AccountMenu = require('./components/account-menu') const AccountMenu = require('./components/account-menu')
@ -39,6 +37,8 @@ const AccountMenu = require('./components/account-menu')
// Global Modals // Global Modals
const Modal = require('./components/modals/index').Modal const Modal = require('./components/modals/index').Modal
const AppHeader = require('./components/app-header')
// Routes // Routes
const { const {
DEFAULT_ROUTE, DEFAULT_ROUTE,
@ -69,11 +69,11 @@ class App extends Component {
return ( return (
h(Switch, [ h(Switch, [
h(Route, { path: INITIALIZE_ROUTE, component: InitializeScreen }), h(Route, { path: INITIALIZE_ROUTE, component: InitializeScreen }),
h(Initialized, { path: REVEAL_SEED_ROUTE, exact, component: RevealSeedConfirmation }),
h(Initialized, { path: UNLOCK_ROUTE, exact, component: UnlockPage }), h(Initialized, { path: UNLOCK_ROUTE, exact, component: UnlockPage }),
h(Initialized, { path: SETTINGS_ROUTE, component: Settings }),
h(Initialized, { path: RESTORE_VAULT_ROUTE, exact, component: RestoreVaultPage }), h(Initialized, { path: RESTORE_VAULT_ROUTE, exact, component: RestoreVaultPage }),
h(Initialized, { path: NOTICE_ROUTE, exact, component: NoticeScreen }), h(Authenticated, { path: REVEAL_SEED_ROUTE, exact, component: RevealSeedConfirmation }),
h(Authenticated, { path: SETTINGS_ROUTE, component: Settings }),
h(Authenticated, { path: NOTICE_ROUTE, exact, component: NoticeScreen }),
h(Authenticated, { path: CONFIRM_TRANSACTION_ROUTE, component: ConfirmTxScreen }), h(Authenticated, { path: CONFIRM_TRANSACTION_ROUTE, component: ConfirmTxScreen }),
h(Authenticated, { path: SEND_ROUTE, exact, component: SendTransactionScreen2 }), h(Authenticated, { path: SEND_ROUTE, exact, component: SendTransactionScreen2 }),
h(Authenticated, { path: ADD_TOKEN_ROUTE, exact, component: AddTokenPage }), h(Authenticated, { path: ADD_TOKEN_ROUTE, exact, component: AddTokenPage }),
@ -83,6 +83,15 @@ class App extends Component {
) )
} }
renderAppHeader () {
const { location } = this.props
const isInitializing = matchPath(location.pathname, {
path: INITIALIZE_ROUTE, exact: false,
})
return isInitializing ? null : h(AppHeader)
}
render () { render () {
const { const {
isLoading, isLoading,
@ -119,8 +128,7 @@ class App extends Component {
// global modal // global modal
h(Modal, {}, []), h(Modal, {}, []),
// app bar this.renderAppHeader(),
this.renderAppBar(),
// sidebar // sidebar
this.renderSidebar(), this.renderSidebar(),
@ -197,110 +205,6 @@ class App extends Component {
]) ])
} }
renderAppBar () {
const {
isUnlocked,
network,
provider,
networkDropdownOpen,
showNetworkDropdown,
hideNetworkDropdown,
isInitialized,
welcomeScreenSeen,
isPopup,
betaUI,
} = this.props
if (window.METAMASK_UI_TYPE === 'notification') {
return null
}
const props = this.props
const {isMascara, isOnboarding} = props
// Do not render header if user is in mascara onboarding
if (isMascara && isOnboarding) {
return null
}
// Do not render header if user is in mascara buy ether
if (isMascara && props.currentView.name === 'buyEth') {
return null
}
return (
h('.full-width', {
style: {},
}, [
(isInitialized || welcomeScreenSeen || isPopup || !betaUI) && h('.app-header.flex-row.flex-space-between', {
className: classnames({
'app-header--initialized': !isOnboarding,
}),
}, [
h('div.app-header-contents', {}, [
h('div.left-menu-wrapper', {
onClick: () => props.history.push(DEFAULT_ROUTE),
}, [
// mini logo
h('img.metafox-icon', {
height: 42,
width: 42,
src: '/images/metamask-fox.svg',
}),
// metamask name
h('.flex-row', [
h('h1', this.context.t('appName')),
h('div.beta-label', this.context.t('beta')),
]),
]),
betaUI && isInitialized && h('div.header__right-actions', [
h('div.network-component-wrapper', {
style: {},
}, [
// Network Indicator
h(NetworkIndicator, {
network,
provider,
disabled: this.props.location.pathname === CONFIRM_TRANSACTION_ROUTE,
onClick: (event) => {
event.preventDefault()
event.stopPropagation()
return networkDropdownOpen === false
? showNetworkDropdown()
: hideNetworkDropdown()
},
}),
]),
isUnlocked && h('div.account-menu__icon', { onClick: this.props.toggleAccountMenu }, [
h(Identicon, {
address: this.props.selectedAddress,
diameter: 32,
}),
]),
]),
]),
]),
!isInitialized && !isPopup && betaUI && h('.alpha-warning__container', {}, [
h('h2', {
className: classnames({
'alpha-warning': welcomeScreenSeen,
'alpha-warning-welcome-screen': !welcomeScreenSeen,
}),
}, 'Please be aware that this version is still under development'),
]),
])
)
}
toggleMetamaskActive () { toggleMetamaskActive () {
if (!this.props.isUnlocked) { if (!this.props.isUnlocked) {
// currently inactive: redirect to password box // currently inactive: redirect to password box

@ -0,0 +1,106 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
const { ENVIRONMENT_TYPE_NOTIFICATION } = require('../../../../app/scripts/lib/enums')
const { DEFAULT_ROUTE, CONFIRM_TRANSACTION_ROUTE } = require('../../routes')
const Identicon = require('../identicon')
const NetworkIndicator = require('../network')
class AppHeader extends Component {
static propTypes = {
history: PropTypes.object,
location: PropTypes.object,
network: PropTypes.string,
provider: PropTypes.object,
networkDropdownOpen: PropTypes.bool,
showNetworkDropdown: PropTypes.func,
hideNetworkDropdown: PropTypes.func,
toggleAccountMenu: PropTypes.func,
selectedAddress: PropTypes.string,
isUnlocked: PropTypes.bool,
}
static contextTypes = {
t: PropTypes.func,
}
handleNetworkIndicatorClick (event) {
event.preventDefault()
event.stopPropagation()
const { networkDropdownOpen, showNetworkDropdown, hideNetworkDropdown } = this.props
return networkDropdownOpen === false
? showNetworkDropdown()
: hideNetworkDropdown()
}
renderAccountMenu () {
const { isUnlocked, toggleAccountMenu, selectedAddress } = this.props
return isUnlocked && (
<div
className="account-menu__icon"
onClick={toggleAccountMenu}
>
<Identicon
address={selectedAddress}
diameter={32}
/>
</div>
)
}
render () {
const {
network,
provider,
history,
location,
isUnlocked,
} = this.props
if (window.METAMASK_UI_TYPE === ENVIRONMENT_TYPE_NOTIFICATION) {
return null
}
return (
<div
className={classnames('app-header', { 'app-header--back-drop': isUnlocked })}>
<div className="app-header__contents">
<div
className="app-header__logo-container"
onClick={() => history.push(DEFAULT_ROUTE)}
>
<img
className="app-header__metafox"
src="/images/metamask-fox.svg"
height={42}
width={42}
/>
<div className="flex-row">
<h1>{ this.context.t('appName') }</h1>
<div className="app-header__beta-label">
{ this.context.t('beta') }
</div>
</div>
</div>
<div className="app-header__account-menu-container">
<div className="network-component-wrapper">
<NetworkIndicator
network={network}
provider={provider}
onClick={event => this.handleNetworkIndicatorClick(event)}
disabled={location.pathname === CONFIRM_TRANSACTION_ROUTE}
/>
</div>
{ this.renderAccountMenu() }
</div>
</div>
</div>
)
}
}
export default AppHeader

@ -0,0 +1,38 @@
import { connect } from 'react-redux'
import { withRouter } from 'react-router-dom'
import { compose } from 'recompose'
import AppHeader from './app-header.component'
const actions = require('../../actions')
const mapStateToProps = state => {
const { appState, metamask } = state
const { networkDropdownOpen } = appState
const {
network,
provider,
selectedAddress,
isUnlocked,
} = metamask
return {
networkDropdownOpen,
network,
provider,
selectedAddress,
isUnlocked,
}
}
const mapDispatchToProps = dispatch => {
return {
showNetworkDropdown: () => dispatch(actions.showNetworkDropdown()),
hideNetworkDropdown: () => dispatch(actions.hideNetworkDropdown()),
toggleAccountMenu: () => dispatch(actions.toggleAccountMenu()),
}
}
export default compose(
withRouter,
connect(mapStateToProps, mapDispatchToProps)
)(AppHeader)

@ -0,0 +1,2 @@
import AppHeader from './app-header.container'
module.exports = AppHeader

@ -37,7 +37,7 @@
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
font-size: 14px; font-size: 12px;
cursor: pointer; cursor: pointer;
color: $curious-blue; color: $curious-blue;

@ -0,0 +1,2 @@
import UnlockPage from './unlock-page.container'
module.exports = UnlockPage

@ -0,0 +1,181 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import Button from 'material-ui/Button'
import TextField from '../../text-field'
const { ENVIRONMENT_TYPE_POPUP } = require('../../../../../app/scripts/lib/enums')
const { getEnvironmentType } = require('../../../../../app/scripts/lib/util')
const getCaretCoordinates = require('textarea-caret')
const EventEmitter = require('events').EventEmitter
const Mascot = require('../../mascot')
const { DEFAULT_ROUTE, RESTORE_VAULT_ROUTE } = require('../../../routes')
class UnlockPage extends Component {
static contextTypes = {
t: PropTypes.func,
}
constructor (props) {
super(props)
this.state = {
password: '',
error: null,
}
this.animationEventEmitter = new EventEmitter()
}
componentWillMount () {
const { isUnlocked, history } = this.props
if (isUnlocked) {
history.push(DEFAULT_ROUTE)
}
}
tryUnlockMetamask (password) {
const { tryUnlockMetamask, history } = this.props
tryUnlockMetamask(password)
.then(() => history.push(DEFAULT_ROUTE))
.catch(({ message }) => this.setState({ error: message }))
}
handleSubmit (event) {
event.preventDefault()
event.stopPropagation()
const { password } = this.state
const { tryUnlockMetamask, history } = this.props
if (password === '') {
return
}
this.setState({ error: null })
tryUnlockMetamask(password)
.then(() => history.push(DEFAULT_ROUTE))
.catch(({ message }) => this.setState({ error: message }))
}
handleInputChange ({ target }) {
this.setState({ password: target.value, error: null })
// tell mascot to look at page action
const element = target
const boundingRect = element.getBoundingClientRect()
const coordinates = getCaretCoordinates(element, element.selectionEnd)
this.animationEventEmitter.emit('point', {
x: boundingRect.left + coordinates.left - element.scrollLeft,
y: boundingRect.top + coordinates.top - element.scrollTop,
})
}
renderSubmitButton () {
const style = {
backgroundColor: '#f7861c',
color: 'white',
marginTop: '20px',
height: '60px',
fontWeight: '400',
boxShadow: 'none',
borderRadius: '4px',
}
return (
<Button
type="submit"
style={style}
disabled={!this.state.password}
fullWidth
variant="raised"
size="large"
onClick={event => this.handleSubmit(event)}
disableRipple
>
{ this.context.t('login') }
</Button>
)
}
render () {
const { error } = this.state
return (
<div className="unlock-page__container">
<div className="unlock-page">
<div className="unlock-page__mascot-container">
<Mascot
animationEventEmitter={this.animationEventEmitter}
width="120"
height="120"
/>
</div>
<h1 className="unlock-page__title">
{ this.context.t('welcomeBack') }
</h1>
<div>{ this.context.t('unlockMessage') }</div>
<form
className="unlock-page__form"
onSubmit={event => this.handleSubmit(event)}
>
<TextField
id="password"
label="Password"
type="password"
value={this.state.password}
onChange={event => this.handleInputChange(event)}
error={error}
autoFocus
autoComplete="current-password"
fullWidth
/>
</form>
{ this.renderSubmitButton() }
<div className="unlock-page__links">
<div
className="unlock-page__link"
onClick={() => {
this.props.markPasswordForgotten()
this.props.history.push(RESTORE_VAULT_ROUTE)
if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP) {
global.platform.openExtensionInBrowser()
}
}}
>
{ this.context.t('restoreFromSeed') }
</div>
<div
className="unlock-page__link unlock-page__link--import"
onClick={() => {
this.props.markPasswordForgotten()
this.props.history.push(RESTORE_VAULT_ROUTE)
if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP) {
global.platform.openExtensionInBrowser()
}
}}
>
{ this.context.t('importUsingSeed') }
</div>
</div>
</div>
</div>
)
}
}
UnlockPage.propTypes = {
forgotPassword: PropTypes.func,
tryUnlockMetamask: PropTypes.func,
markPasswordForgotten: PropTypes.func,
history: PropTypes.object,
isUnlocked: PropTypes.bool,
t: PropTypes.func,
useOldInterface: PropTypes.func,
setNetworkEndpoints: PropTypes.func,
}
export default UnlockPage

@ -0,0 +1,33 @@
import { connect } from 'react-redux'
import { withRouter } from 'react-router-dom'
import { compose } from 'recompose'
const {
tryUnlockMetamask,
forgotPassword,
markPasswordForgotten,
setNetworkEndpoints,
} = require('../../../actions')
import UnlockPage from './unlock-page.component'
const mapStateToProps = state => {
const { metamask: { isUnlocked } } = state
return {
isUnlocked,
}
}
const mapDispatchToProps = dispatch => {
return {
forgotPassword: () => dispatch(forgotPassword()),
tryUnlockMetamask: password => dispatch(tryUnlockMetamask(password)),
markPasswordForgotten: () => dispatch(markPasswordForgotten()),
setNetworkEndpoints: type => dispatch(setNetworkEndpoints(type)),
}
}
export default compose(
withRouter,
connect(mapStateToProps, mapDispatchToProps)
)(UnlockPage)

@ -0,0 +1,51 @@
.unlock-page {
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
width: 357px;
padding: 30px;
font-weight: 400;
color: $silver-chalice;
&__container {
background: $white;
display: flex;
align-self: stretch;
justify-content: center;
flex: 1 0 auto;
}
&__mascot-container {
margin-top: 24px;
}
&__title {
margin-top: 5px;
font-size: 2rem;
font-weight: 800;
color: $tundora;
}
&__form {
width: 100%;
margin: 56px 0 8px;
}
&__links {
margin-top: 25px;
width: 100%;
}
&__link {
cursor: pointer;
&--import {
color: $ecstasy;
}
&--use-classic {
margin-top: 10px;
}
}
}

@ -1,194 +0,0 @@
const { Component } = require('react')
const PropTypes = require('prop-types')
const connect = require('../../metamask-connect')
const h = require('react-hyperscript')
const { withRouter } = require('react-router-dom')
const { compose } = require('recompose')
const {
tryUnlockMetamask,
forgotPassword,
markPasswordForgotten,
setNetworkEndpoints,
setFeatureFlag,
} = require('../../actions')
const { ENVIRONMENT_TYPE_POPUP } = require('../../../../app/scripts/lib/enums')
const { getEnvironmentType } = require('../../../../app/scripts/lib/util')
const getCaretCoordinates = require('textarea-caret')
const EventEmitter = require('events').EventEmitter
const Mascot = require('../mascot')
const { OLD_UI_NETWORK_TYPE } = require('../../../../app/scripts/controllers/network/enums')
const { DEFAULT_ROUTE, RESTORE_VAULT_ROUTE } = require('../../routes')
class UnlockScreen extends Component {
constructor (props) {
super(props)
this.state = {
error: null,
}
this.animationEventEmitter = new EventEmitter()
}
componentWillMount () {
const { isUnlocked, history } = this.props
if (isUnlocked) {
history.push(DEFAULT_ROUTE)
}
}
componentDidMount () {
const passwordBox = document.getElementById('password-box')
if (passwordBox) {
passwordBox.focus()
}
}
tryUnlockMetamask (password) {
const { tryUnlockMetamask, history } = this.props
tryUnlockMetamask(password)
.then(() => history.push(DEFAULT_ROUTE))
.catch(({ message }) => this.setState({ error: message }))
}
onSubmit (event) {
const input = document.getElementById('password-box')
const password = input.value
this.tryUnlockMetamask(password)
}
onKeyPress (event) {
if (event.key === 'Enter') {
this.submitPassword(event)
}
}
submitPassword (event) {
var element = event.target
var password = element.value
// reset input
element.value = ''
this.tryUnlockMetamask(password)
}
inputChanged (event) {
// tell mascot to look at page action
var element = event.target
var boundingRect = element.getBoundingClientRect()
var coordinates = getCaretCoordinates(element, element.selectionEnd)
this.animationEventEmitter.emit('point', {
x: boundingRect.left + coordinates.left - element.scrollLeft,
y: boundingRect.top + coordinates.top - element.scrollTop,
})
}
render () {
const { error } = this.state
return (
h('.unlock-screen', [
h(Mascot, {
animationEventEmitter: this.animationEventEmitter,
}),
h('h1', {
style: {
fontSize: '1.4em',
textTransform: 'uppercase',
color: '#7F8082',
},
}, this.props.t('appName')),
h('input.large-input', {
type: 'password',
id: 'password-box',
placeholder: 'enter password',
style: {
background: 'white',
},
onKeyPress: this.onKeyPress.bind(this),
onInput: this.inputChanged.bind(this),
}),
h('.error', {
style: {
display: error ? 'block' : 'none',
padding: '0 20px',
textAlign: 'center',
},
}, error),
h('button.primary.cursor-pointer', {
onClick: this.onSubmit.bind(this),
style: {
margin: 10,
},
}, this.props.t('login')),
h('p.pointer', {
onClick: () => {
this.props.markPasswordForgotten()
this.props.history.push(RESTORE_VAULT_ROUTE)
if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP) {
global.platform.openExtensionInBrowser()
}
},
style: {
fontSize: '0.8em',
color: 'rgb(247, 134, 28)',
textDecoration: 'underline',
},
}, this.props.t('restoreFromSeed')),
h('p.pointer', {
onClick: () => {
this.props.useOldInterface()
.then(() => this.props.setNetworkEndpoints(OLD_UI_NETWORK_TYPE))
},
style: {
fontSize: '0.8em',
color: '#aeaeae',
textDecoration: 'underline',
marginTop: '32px',
},
}, this.props.t('classicInterface')),
])
)
}
}
UnlockScreen.propTypes = {
forgotPassword: PropTypes.func,
tryUnlockMetamask: PropTypes.func,
markPasswordForgotten: PropTypes.func,
history: PropTypes.object,
isUnlocked: PropTypes.bool,
t: PropTypes.func,
useOldInterface: PropTypes.func,
setNetworkEndpoints: PropTypes.func,
}
const mapStateToProps = state => {
const { metamask: { isUnlocked } } = state
return {
isUnlocked,
}
}
const mapDispatchToProps = dispatch => {
return {
forgotPassword: () => dispatch(forgotPassword()),
tryUnlockMetamask: password => dispatch(tryUnlockMetamask(password)),
markPasswordForgotten: () => dispatch(markPasswordForgotten()),
useOldInterface: () => dispatch(setFeatureFlag('betaUI', false, 'OLD_UI_NOTIFICATION_MODAL')),
setNetworkEndpoints: type => dispatch(setNetworkEndpoints(type)),
}
}
module.exports = compose(
withRouter,
connect(mapStateToProps, mapDispatchToProps)
)(UnlockScreen)

@ -0,0 +1,2 @@
import TextField from './text-field.component'
module.exports = TextField

@ -0,0 +1,54 @@
import React from 'react'
import PropTypes from 'prop-types'
import { withStyles } from 'material-ui/styles'
import { default as MaterialTextField } from 'material-ui/TextField'
const styles = {
cssLabel: {
'&$cssFocused': {
color: '#aeaeae',
},
fontWeight: '400',
color: '#aeaeae',
},
cssFocused: {},
cssUnderline: {
'&:after': {
backgroundColor: '#f7861c',
},
},
}
const TextField = props => {
const { error, classes, ...textFieldProps } = props
return (
<MaterialTextField
error={Boolean(error)}
helperText={error}
InputLabelProps={{
FormLabelClasses: {
root: classes.cssLabel,
focused: classes.cssFocused,
},
}}
InputProps={{
classes: {
underline: classes.cssUnderline,
},
}}
{...textFieldProps}
/>
)
}
TextField.defaultProps = {
error: null,
}
TextField.propTypes = {
error: PropTypes.string,
classes: PropTypes.object,
}
export default withStyles(styles)(TextField)

@ -0,0 +1,24 @@
import React from 'react'
import { storiesOf } from '@storybook/react'
import TextField from './'
storiesOf('TextField', module)
.add('text', () =>
<TextField
label="Text"
type="text"
/>
)
.add('password', () =>
<TextField
label="Password"
type="password"
/>
)
.add('error', () =>
<TextField
type="text"
label="Name"
error="Invalid value"
/>
)

@ -102,6 +102,7 @@ WalletView.prototype.render = function () {
selectedIdentity, selectedIdentity,
keyrings, keyrings,
showAccountDetailModal, showAccountDetailModal,
sidebarOpen,
hideSidebar, hideSidebar,
history, history,
} = this.props } = this.props
@ -182,7 +183,10 @@ WalletView.prototype.render = function () {
h(TokenList), h(TokenList),
h('button.btn-primary.wallet-view__add-token-button', { h('button.btn-primary.wallet-view__add-token-button', {
onClick: () => history.push(ADD_TOKEN_ROUTE), onClick: () => {
history.push(ADD_TOKEN_ROUTE)
sidebarOpen && hideSidebar()
},
}, this.context.t('addToken')), }, this.context.t('addToken')),
]) ])
} }

@ -1,15 +1,15 @@
.app-header { .app-header {
align-items: center; align-items: center;
visibility: visible;
background: $gallery; background: $gallery;
position: relative; position: relative;
z-index: $header-z-index; z-index: $header-z-index;
display: flex; display: flex;
flex-flow: column nowrap; flex-flow: column nowrap;
width: 100%;
flex: 0 0 auto;
@media screen and (max-width: 575px) { @media screen and (max-width: 575px) {
padding: 12px; padding: 12px;
width: 100%;
box-shadow: 0 0 0 1px rgba(0, 0, 0, .08); box-shadow: 0 0 0 1px rgba(0, 0, 0, .08);
z-index: $mobile-header-z-index; z-index: $mobile-header-z-index;
} }
@ -17,48 +17,75 @@
@media screen and (min-width: 576px) { @media screen and (min-width: 576px) {
height: 75px; height: 75px;
justify-content: center; justify-content: center;
&--back-drop {
&::after {
content: '';
position: absolute;
width: 100%;
height: 32px;
background: $gallery;
bottom: -32px;
}
}
} }
.metafox-icon { &__metafox {
cursor: pointer; cursor: pointer;
} }
}
.app-header--initialized {
@media screen and (min-width: 576px) { &__beta-label {
&::after { font-family: Roboto;
content: ''; text-transform: uppercase;
position: absolute; font-weight: 500;
width: 100%; font-size: .8rem;
height: 32px; color: $buttercup;
background: $gallery; margin-left: 5px;
bottom: -32px; line-height: initial;
@media screen and (max-width: 575px) {
display: none;
} }
} }
}
.app-header-contents { &__contents {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
flex-flow: row nowrap; flex-flow: row nowrap;
width: 100%; width: 100%;
height: 6.9vh;
@media screen and (max-width: 575px) { @media screen and (max-width: 575px) {
height: 100%; height: 100%;
} }
@media screen and (min-width: 576px) { @media screen and (min-width: 576px) {
width: 85vw; width: 85vw;
}
@media screen and (min-width: 769px) {
width: 80vw;
}
@media screen and (min-width: 1281px) {
width: 62vw;
}
} }
@media screen and (min-width: 769px) { &__logo-container {
width: 80vw; display: flex;
flex-direction: row;
align-items: center;
cursor: pointer;
} }
@media screen and (min-width: 1281px) { &__account-menu-container {
width: 62vw; display: flex;
flex-flow: row nowrap;
align-items: center;
.identicon {
cursor: pointer;
}
} }
} }
@ -76,20 +103,6 @@
} }
} }
.beta-label {
font-family: Roboto;
text-transform: uppercase;
font-weight: 500;
font-size: .8rem;
color: $buttercup;
margin-left: 5px;
line-height: initial;
@media screen and (max-width: 575px) {
display: none;
}
}
h2.page-subtitle { h2.page-subtitle {
text-transform: uppercase; text-transform: uppercase;
color: #aeaeae; color: #aeaeae;
@ -102,20 +115,3 @@ h2.page-subtitle {
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
} }
.left-menu-wrapper {
display: flex;
flex-direction: row;
align-items: center;
cursor: pointer;
}
.header__right-actions {
display: flex;
flex-flow: row nowrap;
align-items: center;
.identicon {
cursor: pointer;
}
}

@ -1,3 +1,3 @@
@import './unlock.scss';
@import './reveal-seed.scss'; @import './reveal-seed.scss';
@import '../../../../components/pages/unlock-page/unlock-page.scss';

@ -1,9 +0,0 @@
.unlock-page {
box-shadow: none;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: rgb(247, 247, 247);
width: 100%;
}

@ -95,19 +95,6 @@ textarea.twelve-word-phrase {
margin: -2px 8px 0px -8px; margin: -2px 8px 0px -8px;
} }
.unlock-screen #metamask-mascot-container {
margin-top: 24px;
}
.unlock-screen h1 {
margin-top: -28px;
margin-bottom: 42px;
}
.unlock-screen input[type=password] {
width: 260px;
}
.sizing-input { .sizing-input {
font-size: 14px; font-size: 14px;
height: 30px; height: 30px;
@ -118,34 +105,6 @@ textarea.twelve-word-phrase {
display: flex; display: flex;
} }
/* Webkit */
.unlock-screen input::-webkit-input-placeholder {
text-align: center;
font-size: 1.2em;
}
/* Firefox 18- */
.unlock-screen input:-moz-placeholder {
text-align: center;
font-size: 1.2em;
}
/* Firefox 19+ */
.unlock-screen input::-moz-placeholder {
text-align: center;
font-size: 1.2em;
}
/* IE */
.unlock-screen input:-ms-input-placeholder {
text-align: center;
font-size: 1.2em;
}
/* accounts */ /* accounts */
.accounts-section { .accounts-section {

@ -3,8 +3,6 @@
background: $white; background: $white;
display: flex; display: flex;
flex-flow: column nowrap; flex-flow: column nowrap;
height: auto;
overflow: auto;
} }
.settings__header { .settings__header {
@ -29,6 +27,8 @@
.settings__content { .settings__content {
padding: 0 25px; padding: 0 25px;
height: auto;
overflow: auto;
} }
.settings__content-row { .settings__content-row {

@ -205,10 +205,8 @@ input.large-input {
} }
&__content { &__content {
height: 100%;
overflow-y: auto; overflow-y: auto;
min-height: 250px; flex: 1;
max-height: 400px;
} }
&__warning-container { &__warning-container {
@ -256,6 +254,7 @@ input.large-input {
overflow-y: auto; overflow-y: auto;
background-color: $white; background-color: $white;
border-radius: 0; border-radius: 0;
flex: 1;
} }
} }

@ -3,7 +3,7 @@ const h = require('react-hyperscript')
const inherits = require('util').inherits const inherits = require('util').inherits
const AccountAndTransactionDetails = require('./account-and-transaction-details') const AccountAndTransactionDetails = require('./account-and-transaction-details')
const Settings = require('./components/pages/settings') const Settings = require('./components/pages/settings')
const UnlockScreen = require('./components/pages/unlock') const UnlockScreen = require('./components/pages/unlock-page')
const log = require('loglevel') const log = require('loglevel')
module.exports = MainContainer module.exports = MainContainer

@ -1,141 +0,0 @@
const inherits = require('util').inherits
const Component = require('react').Component
const PropTypes = require('prop-types')
const h = require('react-hyperscript')
const connect = require('react-redux').connect
const actions = require('./actions')
const getCaretCoordinates = require('textarea-caret')
const EventEmitter = require('events').EventEmitter
const { OLD_UI_NETWORK_TYPE } = require('../../app/scripts/controllers/network/enums')
const { getEnvironmentType } = require('../../app/scripts/lib/util')
const { ENVIRONMENT_TYPE_POPUP } = require('../../app/scripts/lib/enums')
const Mascot = require('./components/mascot')
UnlockScreen.contextTypes = {
t: PropTypes.func,
}
module.exports = connect(mapStateToProps)(UnlockScreen)
inherits(UnlockScreen, Component)
function UnlockScreen () {
Component.call(this)
this.animationEventEmitter = new EventEmitter()
}
function mapStateToProps (state) {
return {
warning: state.appState.warning,
}
}
UnlockScreen.prototype.render = function () {
const state = this.props
const warning = state.warning
return (
h('.unlock-screen', [
h(Mascot, {
animationEventEmitter: this.animationEventEmitter,
}),
h('h1', {
style: {
fontSize: '1.4em',
textTransform: 'uppercase',
color: '#7F8082',
},
}, this.context.t('appName')),
h('input.large-input', {
type: 'password',
id: 'password-box',
placeholder: 'enter password',
style: {
background: 'white',
},
onKeyPress: this.onKeyPress.bind(this),
onInput: this.inputChanged.bind(this),
}),
h('.error', {
style: {
display: warning ? 'block' : 'none',
padding: '0 20px',
textAlign: 'center',
},
}, warning),
h('button.primary.cursor-pointer', {
onClick: this.onSubmit.bind(this),
style: {
margin: 10,
},
}, this.context.t('login')),
h('p.pointer', {
onClick: () => {
this.props.dispatch(actions.markPasswordForgotten())
if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP) {
global.platform.openExtensionInBrowser()
}
},
style: {
fontSize: '0.8em',
color: 'rgb(247, 134, 28)',
textDecoration: 'underline',
},
}, this.context.t('restoreFromSeed')),
h('p.pointer', {
onClick: () => {
this.props.dispatch(actions.setFeatureFlag('betaUI', false, 'OLD_UI_NOTIFICATION_MODAL'))
.then(() => this.props.dispatch(actions.setNetworkEndpoints(OLD_UI_NETWORK_TYPE)))
},
style: {
fontSize: '0.8em',
color: '#aeaeae',
textDecoration: 'underline',
marginTop: '32px',
},
}, this.context.t('classicInterface')),
])
)
}
UnlockScreen.prototype.componentDidMount = function () {
document.getElementById('password-box').focus()
}
UnlockScreen.prototype.onSubmit = function (event) {
const input = document.getElementById('password-box')
const password = input.value
this.props.dispatch(actions.tryUnlockMetamask(password))
}
UnlockScreen.prototype.onKeyPress = function (event) {
if (event.key === 'Enter') {
this.submitPassword(event)
}
}
UnlockScreen.prototype.submitPassword = function (event) {
var element = event.target
var password = element.value
// reset input
element.value = ''
this.props.dispatch(actions.tryUnlockMetamask(password))
}
UnlockScreen.prototype.inputChanged = function (event) {
// tell mascot to look at page action
var element = event.target
var boundingRect = element.getBoundingClientRect()
var coordinates = getCaretCoordinates(element, element.selectionEnd)
this.animationEventEmitter.emit('point', {
x: boundingRect.left + coordinates.left - element.scrollLeft,
y: boundingRect.top + coordinates.top - element.scrollTop,
})
}
Loading…
Cancel
Save