aboutsummaryrefslogtreecommitdiffstats
path: root/ui
diff options
context:
space:
mode:
authorAlexander Tseung <alextsg@users.noreply.github.com>2017-12-23 03:40:20 +0800
committerGitHub <noreply@github.com>2017-12-23 03:40:20 +0800
commit409d1d30e9d836926e5361fb9e4b5b025b66e313 (patch)
treec7f247d1e973c50697af8b9905d9fd40d4575659 /ui
parentb944a63ff89e3c45f7d7e49b2d93a5442cde4462 (diff)
parent5a58add797fcdbb023678af84a61f1d2bfdafaf1 (diff)
downloadtangerine-wallet-browser-409d1d30e9d836926e5361fb9e4b5b025b66e313.tar
tangerine-wallet-browser-409d1d30e9d836926e5361fb9e4b5b025b66e313.tar.gz
tangerine-wallet-browser-409d1d30e9d836926e5361fb9e4b5b025b66e313.tar.bz2
tangerine-wallet-browser-409d1d30e9d836926e5361fb9e4b5b025b66e313.tar.lz
tangerine-wallet-browser-409d1d30e9d836926e5361fb9e4b5b025b66e313.tar.xz
tangerine-wallet-browser-409d1d30e9d836926e5361fb9e4b5b025b66e313.tar.zst
tangerine-wallet-browser-409d1d30e9d836926e5361fb9e4b5b025b66e313.zip
Merge pull request #2799 from MetaMask/NewUI-flat
Update UAT to version 4.0.5
Diffstat (limited to 'ui')
-rw-r--r--ui/app/actions.js137
-rw-r--r--ui/app/app.js1
-rw-r--r--ui/app/components/account-menu/index.js6
-rw-r--r--ui/app/components/balance-component.js3
-rw-r--r--ui/app/components/coinbase-form.js2
-rw-r--r--ui/app/components/customize-gas-modal/index.js22
-rw-r--r--ui/app/components/dropdowns/network-dropdown.js27
-rw-r--r--ui/app/components/identicon.js67
-rw-r--r--ui/app/components/modals/modal.js37
-rw-r--r--ui/app/components/modals/notification-modal.js51
-rw-r--r--ui/app/components/network.js14
-rw-r--r--ui/app/components/pending-tx/confirm-send-ether.js24
-rw-r--r--ui/app/components/pending-tx/confirm-send-token.js45
-rw-r--r--ui/app/components/send/send-constants.js7
-rw-r--r--ui/app/components/send/send-v2-container.js4
-rw-r--r--ui/app/components/token-cell.js4
-rw-r--r--ui/app/components/transaction-list-item.js129
-rw-r--r--ui/app/components/tx-list-item.js4
-rw-r--r--ui/app/components/tx-view.js9
-rw-r--r--ui/app/css/index.scss1
-rw-r--r--ui/app/css/itcss/components/account-menu.scss6
-rw-r--r--ui/app/css/itcss/components/menu.scss4
-rw-r--r--ui/app/css/itcss/components/modal.scss36
-rw-r--r--ui/app/css/itcss/components/newui-sections.scss6
-rw-r--r--ui/app/css/itcss/components/settings.scss5
-rw-r--r--ui/app/first-time/init-menu.js7
-rw-r--r--ui/app/reducers/metamask.js30
-rw-r--r--ui/app/root.js4
-rw-r--r--ui/app/select-app.js61
-rw-r--r--ui/app/selectors.js23
-rw-r--r--ui/app/send-v2.js104
-rw-r--r--ui/app/settings.js55
-rw-r--r--ui/index.js7
-rw-r--r--ui/lib/blockies.js364
34 files changed, 1115 insertions, 191 deletions
diff --git a/ui/app/actions.js b/ui/app/actions.js
index 2ca62c41f..bd3aab45a 100644
--- a/ui/app/actions.js
+++ b/ui/app/actions.js
@@ -125,6 +125,7 @@ var actions = {
sendTx: sendTx,
signTx: signTx,
signTokenTx: signTokenTx,
+ updateTransaction,
updateAndApproveTx,
cancelTx: cancelTx,
completedTx: completedTx,
@@ -149,6 +150,7 @@ var actions = {
UPDATE_SEND_AMOUNT: 'UPDATE_SEND_AMOUNT',
UPDATE_SEND_MEMO: 'UPDATE_SEND_MEMO',
UPDATE_SEND_ERRORS: 'UPDATE_SEND_ERRORS',
+ UPDATE_MAX_MODE: 'UPDATE_MAX_MODE',
UPDATE_SEND: 'UPDATE_SEND',
CLEAR_SEND: 'CLEAR_SEND',
updateGasLimit,
@@ -160,6 +162,7 @@ var actions = {
updateSendAmount,
updateSendMemo,
updateSendErrors,
+ setMaxModeTo,
updateSend,
clearSend,
setSelectedAddress,
@@ -234,6 +237,21 @@ var actions = {
toggleAccountMenu,
useEtherscanProvider,
+
+ SET_USE_BLOCKIE: 'SET_USE_BLOCKIE',
+ setUseBlockie,
+
+ // Feature Flags
+ setFeatureFlag,
+ updateFeatureFlags,
+ UPDATE_FEATURE_FLAGS: 'UPDATE_FEATURE_FLAGS',
+
+ // Network
+ setNetworkEndpoints,
+ updateNetworkEndpointType,
+ UPDATE_NETWORK_ENDPOINT_TYPE: 'UPDATE_NETWORK_ENDPOINT_TYPE',
+
+ retryTransaction,
}
module.exports = actions
@@ -634,6 +652,13 @@ function updateSendErrors (error) {
}
}
+function setMaxModeTo (bool) {
+ return {
+ type: actions.UPDATE_MAX_MODE,
+ value: bool,
+ }
+}
+
function updateSend (newSend) {
return {
type: actions.UPDATE_SEND,
@@ -675,6 +700,23 @@ function signTokenTx (tokenAddress, toAddress, amount, txData) {
}
}
+function updateTransaction (txData) {
+ log.info('actions: updateTx: ' + JSON.stringify(txData))
+ return (dispatch) => {
+ log.debug(`actions calling background.updateTx`)
+ background.updateTransaction(txData, (err) => {
+ dispatch(actions.hideLoadingIndication())
+ dispatch(actions.updateTransactionParams(txData.id, txData.txParams))
+ if (err) {
+ dispatch(actions.txError(err))
+ dispatch(actions.goHome())
+ return log.error(err.message)
+ }
+ dispatch(actions.showConfTxPage({ id: txData.id }))
+ })
+ }
+}
+
function updateAndApproveTx (txData) {
log.info('actions: updateAndApproveTx: ' + JSON.stringify(txData))
return (dispatch) => {
@@ -737,6 +779,7 @@ function cancelTx (txData) {
return (dispatch) => {
log.debug(`background.cancelTransaction`)
background.cancelTransaction(txData.id, () => {
+ dispatch(actions.clearSend())
dispatch(actions.completedTx(txData.id))
})
}
@@ -985,9 +1028,10 @@ function showConfigPage (transitionForward = true) {
}
}
-function showAddTokenPage () {
+function showAddTokenPage (transitionForward = true) {
return {
type: actions.SHOW_ADD_TOKEN_PAGE,
+ value: transitionForward,
}
}
@@ -1101,6 +1145,19 @@ function markAccountsFound () {
return callBackgroundThenUpdate(background.markAccountsFound)
}
+function retryTransaction (txId) {
+ log.debug(`background.retryTransaction`)
+ return (dispatch) => {
+ background.retryTransaction(txId, (err, newState) => {
+ if (err) {
+ return dispatch(actions.displayWarning(err.message))
+ }
+ dispatch(actions.updateMetamaskState(newState))
+ dispatch(actions.viewPendingTx(txId))
+ })
+ }
+}
+
//
// config
//
@@ -1269,7 +1326,8 @@ function exportAccount (password, address) {
return reject(err)
}
- dispatch(self.exportAccountComplete())
+ // dispatch(self.exportAccountComplete())
+ dispatch(self.showPrivateKey(result))
return resolve(result)
})
@@ -1444,10 +1502,11 @@ function reshowQrCode (data, coin) {
]
dispatch(actions.hideLoadingIndication())
- return dispatch(actions.showModal({
- name: 'SHAPESHIFT_DEPOSIT_TX',
- Qr: { data, message },
- }))
+ return dispatch(actions.showQrView(data, message))
+ // return dispatch(actions.showModal({
+ // name: 'SHAPESHIFT_DEPOSIT_TX',
+ // Qr: { data, message },
+ // }))
})
}
}
@@ -1503,6 +1562,31 @@ function updateTokenExchangeRate (token = '') {
}
}
+function setFeatureFlag (feature, activated, notificationType) {
+ return (dispatch) => {
+ dispatch(actions.showLoadingIndication())
+ return new Promise((resolve, reject) => {
+ background.setFeatureFlag(feature, activated, (err, updatedFeatureFlags) => {
+ dispatch(actions.hideLoadingIndication())
+ if (err) {
+ dispatch(actions.displayWarning(err.message))
+ return reject(err)
+ }
+ dispatch(actions.updateFeatureFlags(updatedFeatureFlags))
+ notificationType && dispatch(actions.showModal({ name: notificationType }))
+ resolve(updatedFeatureFlags)
+ })
+ })
+ }
+}
+
+function updateFeatureFlags (updatedFeatureFlags) {
+ return {
+ type: actions.UPDATE_FEATURE_FLAGS,
+ value: updatedFeatureFlags,
+ }
+}
+
// Call Background Then Update
//
// A function generator for a common pattern wherein:
@@ -1550,3 +1634,44 @@ function toggleAccountMenu () {
type: actions.TOGGLE_ACCOUNT_MENU,
}
}
+
+function setUseBlockie (val) {
+ return (dispatch) => {
+ dispatch(actions.showLoadingIndication())
+ log.debug(`background.setUseBlockie`)
+ background.setUseBlockie(val, (err) => {
+ dispatch(actions.hideLoadingIndication())
+ if (err) {
+ return dispatch(actions.displayWarning(err.message))
+ }
+ })
+ dispatch({
+ type: actions.SET_USE_BLOCKIE,
+ value: val,
+ })
+ }
+}
+
+function setNetworkEndpoints (networkEndpointType) {
+ return dispatch => {
+ log.debug('background.setNetworkEndpoints')
+ return new Promise((resolve, reject) => {
+ background.setNetworkEndpoints(networkEndpointType, err => {
+ if (err) {
+ dispatch(actions.displayWarning(err.message))
+ return reject(err)
+ }
+
+ dispatch(actions.updateNetworkEndpointType(networkEndpointType))
+ resolve(networkEndpointType)
+ })
+ })
+ }
+}
+
+function updateNetworkEndpointType (networkEndpointType) {
+ return {
+ type: actions.UPDATE_NETWORK_ENDPOINT_TYPE,
+ value: networkEndpointType,
+ }
+}
diff --git a/ui/app/app.js b/ui/app/app.js
index e90c3e98e..1f40eccbe 100644
--- a/ui/app/app.js
+++ b/ui/app/app.js
@@ -116,7 +116,6 @@ App.prototype.render = function () {
log.debug('Main ui render function')
return (
-
h('.flex-column.full-height', {
style: {
overflowX: 'hidden',
diff --git a/ui/app/components/account-menu/index.js b/ui/app/components/account-menu/index.js
index a9f075ec7..286a3b587 100644
--- a/ui/app/components/account-menu/index.js
+++ b/ui/app/components/account-menu/index.js
@@ -28,27 +28,33 @@ function mapDispatchToProps (dispatch) {
toggleAccountMenu: () => dispatch(actions.toggleAccountMenu()),
showAccountDetail: address => {
dispatch(actions.showAccountDetail(address))
+ dispatch(actions.hideSidebar())
dispatch(actions.toggleAccountMenu())
},
lockMetamask: () => {
dispatch(actions.lockMetamask())
dispatch(actions.displayWarning(null))
+ dispatch(actions.hideSidebar())
dispatch(actions.toggleAccountMenu())
},
showConfigPage: () => {
dispatch(actions.showConfigPage())
+ dispatch(actions.hideSidebar())
dispatch(actions.toggleAccountMenu())
},
showNewAccountModal: () => {
dispatch(actions.showModal({ name: 'NEW_ACCOUNT' }))
+ dispatch(actions.hideSidebar())
dispatch(actions.toggleAccountMenu())
},
showImportPage: () => {
dispatch(actions.showImportPage())
+ dispatch(actions.hideSidebar())
dispatch(actions.toggleAccountMenu())
},
showInfoPage: () => {
dispatch(actions.showInfoPage())
+ dispatch(actions.hideSidebar())
dispatch(actions.toggleAccountMenu())
},
}
diff --git a/ui/app/components/balance-component.js b/ui/app/components/balance-component.js
index d14aa675f..50007ce14 100644
--- a/ui/app/components/balance-component.js
+++ b/ui/app/components/balance-component.js
@@ -94,7 +94,8 @@ BalanceComponent.prototype.renderFiatValue = function (formattedBalance) {
}
BalanceComponent.prototype.renderFiatAmount = function (fiatDisplayNumber, fiatSuffix, fiatPrefix) {
- if (fiatDisplayNumber === 'N/A') return null
+ const shouldNotRenderFiat = fiatDisplayNumber === 'N/A' || Number(fiatDisplayNumber) === 0
+ if (shouldNotRenderFiat) return null
return h('div.fiat-amount', {
style: {},
diff --git a/ui/app/components/coinbase-form.js b/ui/app/components/coinbase-form.js
index f44d86045..f70208625 100644
--- a/ui/app/components/coinbase-form.js
+++ b/ui/app/components/coinbase-form.js
@@ -40,7 +40,7 @@ CoinbaseForm.prototype.render = function () {
}, 'Continue to Coinbase'),
h('button.btn-red', {
- onClick: () => props.dispatch(actions.backTobuyView(props.accounts.address)),
+ onClick: () => props.dispatch(actions.goHome()),
}, 'Cancel'),
]),
])
diff --git a/ui/app/components/customize-gas-modal/index.js b/ui/app/components/customize-gas-modal/index.js
index 485dacf90..826d2cd4b 100644
--- a/ui/app/components/customize-gas-modal/index.js
+++ b/ui/app/components/customize-gas-modal/index.js
@@ -5,6 +5,8 @@ const connect = require('react-redux').connect
const actions = require('../../actions')
const GasModalCard = require('./gas-modal-card')
+const ethUtil = require('ethereumjs-util')
+
const {
MIN_GAS_PRICE_DEC,
MIN_GAS_LIMIT_DEC,
@@ -19,6 +21,7 @@ const {
conversionUtil,
multiplyCurrencies,
conversionGreaterThan,
+ subtractCurrencies,
} = require('../../conversion-util')
const {
@@ -30,6 +33,7 @@ const {
getSendFrom,
getCurrentAccountWithSendEtherInfo,
getSelectedTokenToFiatRate,
+ getSendMaxModeState,
} = require('../../selectors')
function mapStateToProps (state) {
@@ -42,6 +46,7 @@ function mapStateToProps (state) {
gasLimit: getGasLimit(state),
conversionRate,
amount: getSendAmount(state),
+ maxModeOn: getSendMaxModeState(state),
balance: currentAccount.balance,
primaryCurrency: selectedToken && selectedToken.symbol,
selectedToken,
@@ -55,6 +60,7 @@ function mapDispatchToProps (dispatch) {
updateGasPrice: newGasPrice => dispatch(actions.updateGasPrice(newGasPrice)),
updateGasLimit: newGasLimit => dispatch(actions.updateGasLimit(newGasLimit)),
updateGasTotal: newGasTotal => dispatch(actions.updateGasTotal(newGasTotal)),
+ updateSendAmount: newAmount => dispatch(actions.updateSendAmount(newAmount)),
}
}
@@ -93,8 +99,21 @@ CustomizeGasModal.prototype.save = function (gasPrice, gasLimit, gasTotal) {
updateGasLimit,
hideModal,
updateGasTotal,
+ maxModeOn,
+ selectedToken,
+ balance,
+ updateSendAmount,
} = this.props
+ if (maxModeOn && !selectedToken) {
+ const maxAmount = subtractCurrencies(
+ ethUtil.addHexPrefix(balance),
+ ethUtil.addHexPrefix(gasTotal),
+ { toNumericBase: 'hex' }
+ )
+ updateSendAmount(maxAmount)
+ }
+
updateGasPrice(gasPrice)
updateGasLimit(gasLimit)
updateGasTotal(gasTotal)
@@ -112,12 +131,13 @@ CustomizeGasModal.prototype.validate = function ({ gasTotal, gasLimit }) {
selectedToken,
amountConversionRate,
conversionRate,
+ maxModeOn,
} = this.props
let error = null
const balanceIsSufficient = isBalanceSufficient({
- amount: selectedToken ? '0' : amount,
+ amount: selectedToken || maxModeOn ? '0' : amount,
gasTotal,
balance,
selectedToken,
diff --git a/ui/app/components/dropdowns/network-dropdown.js b/ui/app/components/dropdowns/network-dropdown.js
index 0908faf01..dfaa6b22c 100644
--- a/ui/app/components/dropdowns/network-dropdown.js
+++ b/ui/app/components/dropdowns/network-dropdown.js
@@ -6,6 +6,16 @@ const actions = require('../../actions')
const Dropdown = require('./components/dropdown').Dropdown
const DropdownMenuItem = require('./components/dropdown').DropdownMenuItem
const NetworkDropdownIcon = require('./components/network-dropdown-icon')
+const R = require('ramda')
+
+// classes from nodes of the toggle element.
+const notToggleElementClassnames = [
+ 'menu-icon',
+ 'network-name',
+ 'network-indicator',
+ 'network-caret',
+ 'network-component',
+]
function mapStateToProps (state) {
return {
@@ -32,8 +42,8 @@ function mapDispatchToProps (dispatch) {
showConfigPage: () => {
dispatch(actions.showConfigPage())
},
- showNetworkDropdown: () => { dispatch(actions.showNetworkDropdown()) },
- hideNetworkDropdown: () => { dispatch(actions.hideNetworkDropdown()) },
+ showNetworkDropdown: () => dispatch(actions.showNetworkDropdown()),
+ hideNetworkDropdown: () => dispatch(actions.hideNetworkDropdown()),
}
}
@@ -59,18 +69,13 @@ NetworkDropdown.prototype.render = function () {
}
return h(Dropdown, {
- useCssTransition: true,
isOpen,
onClickOutside: (event) => {
const { classList } = event.target
- const isNotToggleElement = [
- classList.contains('menu-icon'),
- classList.contains('network-name'),
- classList.contains('network-indicator'),
- ].filter(bool => bool).length === 0
- // classes from three constituent nodes of the toggle element
-
- if (isNotToggleElement) {
+ const isInClassList = className => classList.contains(className)
+ const notToggleElementIndex = R.findIndex(isInClassList)(notToggleElementClassnames)
+
+ if (notToggleElementIndex === -1) {
this.props.hideNetworkDropdown()
}
},
diff --git a/ui/app/components/identicon.js b/ui/app/components/identicon.js
index d30b7cd56..b803b7ceb 100644
--- a/ui/app/components/identicon.js
+++ b/ui/app/components/identicon.js
@@ -1,13 +1,15 @@
const Component = require('react').Component
const h = require('react-hyperscript')
const inherits = require('util').inherits
+const connect = require('react-redux').connect
const isNode = require('detect-node')
const findDOMNode = require('react-dom').findDOMNode
const jazzicon = require('jazzicon')
const iconFactoryGen = require('../../lib/icon-factory')
const iconFactory = iconFactoryGen(jazzicon)
+const { toDataUrl } = require('../../lib/blockies')
-module.exports = IdenticonComponent
+module.exports = connect(mapStateToProps)(IdenticonComponent)
inherits(IdenticonComponent, Component)
function IdenticonComponent () {
@@ -16,6 +18,12 @@ function IdenticonComponent () {
this.defaultDiameter = 46
}
+function mapStateToProps (state) {
+ return {
+ useBlockie: state.metamask.useBlockie,
+ }
+}
+
IdenticonComponent.prototype.render = function () {
var props = this.props
const { className = '', address } = props
@@ -51,38 +59,59 @@ IdenticonComponent.prototype.render = function () {
IdenticonComponent.prototype.componentDidMount = function () {
var props = this.props
- const { address } = props
+ const { address, useBlockie } = props
if (!address) return
- // eslint-disable-next-line react/no-find-dom-node
- var container = findDOMNode(this)
-
- var diameter = props.diameter || this.defaultDiameter
if (!isNode) {
- var img = iconFactory.iconForAddress(address, diameter)
- container.appendChild(img)
+ // eslint-disable-next-line react/no-find-dom-node
+ var container = findDOMNode(this)
+
+ const diameter = props.diameter || this.defaultDiameter
+
+ if (useBlockie) {
+ _generateBlockie(container, address, diameter)
+ } else {
+ _generateJazzicon(container, address, diameter)
+ }
}
}
IdenticonComponent.prototype.componentDidUpdate = function () {
var props = this.props
- const { address } = props
+ const { address, useBlockie } = props
if (!address) return
- // eslint-disable-next-line react/no-find-dom-node
- var container = findDOMNode(this)
+ if (!isNode) {
+ // eslint-disable-next-line react/no-find-dom-node
+ var container = findDOMNode(this)
- var children = container.children
- for (var i = 0; i < children.length; i++) {
- container.removeChild(children[i])
- }
+ var children = container.children
+ for (var i = 0; i < children.length; i++) {
+ container.removeChild(children[i])
+ }
- var diameter = props.diameter || this.defaultDiameter
- if (!isNode) {
- var img = iconFactory.iconForAddress(address, diameter)
- container.appendChild(img)
+ const diameter = props.diameter || this.defaultDiameter
+
+ if (useBlockie) {
+ _generateBlockie(container, address, diameter)
+ } else {
+ _generateJazzicon(container, address, diameter)
+ }
}
}
+function _generateBlockie (container, address, diameter) {
+ const img = new Image()
+ img.src = toDataUrl(address)
+ const dia = !diameter || diameter < 50 ? 50 : diameter
+ img.height = dia * 1.25
+ img.width = dia * 1.25
+ container.appendChild(img)
+}
+
+function _generateJazzicon (container, address, diameter) {
+ const img = iconFactory.iconForAddress(address, diameter)
+ container.appendChild(img)
+}
diff --git a/ui/app/components/modals/modal.js b/ui/app/components/modals/modal.js
index f2909f3c3..2ff6accaa 100644
--- a/ui/app/components/modals/modal.js
+++ b/ui/app/components/modals/modal.js
@@ -16,6 +16,7 @@ const NewAccountModal = require('./new-account-modal')
const ShapeshiftDepositTxModal = require('./shapeshift-deposit-tx-modal.js')
const HideTokenConfirmationModal = require('./hide-token-confirmation-modal')
const CustomizeGasModal = require('../customize-gas-modal')
+const NotifcationModal = require('./notification-modal')
const accountModalStyle = {
mobileModalStyle: {
@@ -133,6 +134,42 @@ const MODALS = {
},
},
+ BETA_UI_NOTIFICATION_MODAL: {
+ contents: [
+ h(NotifcationModal, {
+ header: 'Welcome to the New UI (Beta)',
+ message: `You are now using the new Metamask UI. Take a look around, try out new features like sending tokens,
+ and let us know if you have any issues.`,
+ }),
+ ],
+ mobileModalStyle: {
+ width: '95%',
+ top: isPopupOrNotification() === 'popup' ? '52vh' : '36.5vh',
+ },
+ laptopModalStyle: {
+ width: '449px',
+ top: 'calc(33% + 45px)',
+ },
+ },
+
+ OLD_UI_NOTIFICATION_MODAL: {
+ contents: [
+ h(NotifcationModal, {
+ header: 'Old UI',
+ message: `You have returned to the old UI. You can switch back to the New UI through the option in the top
+ right dropdown menu.`,
+ }),
+ ],
+ mobileModalStyle: {
+ width: '95%',
+ top: isPopupOrNotification() === 'popup' ? '52vh' : '36.5vh',
+ },
+ laptopModalStyle: {
+ width: '449px',
+ top: 'calc(33% + 45px)',
+ },
+ },
+
NEW_ACCOUNT: {
contents: [
h(NewAccountModal, {}, []),
diff --git a/ui/app/components/modals/notification-modal.js b/ui/app/components/modals/notification-modal.js
new file mode 100644
index 000000000..239144b0c
--- /dev/null
+++ b/ui/app/components/modals/notification-modal.js
@@ -0,0 +1,51 @@
+const { Component } = require('react')
+const PropTypes = require('prop-types')
+const h = require('react-hyperscript')
+const { connect } = require('react-redux')
+const actions = require('../../actions')
+
+class NotificationModal extends Component {
+ render () {
+ const {
+ header,
+ message,
+ } = this.props
+
+ return h('div', [
+ h('div.notification-modal-wrapper', {
+ }, [
+
+ h('div.notification-modal-header', {}, [
+ header,
+ ]),
+
+ h('div.notification-modal-message-wrapper', {}, [
+ h('div.notification-modal-message', {}, [
+ message,
+ ]),
+ ]),
+
+ h('div.modal-close-x', {
+ onClick: this.props.hideModal,
+ }),
+
+ ]),
+ ])
+ }
+}
+
+NotificationModal.propTypes = {
+ hideModal: PropTypes.func,
+ header: PropTypes.string,
+ message: PropTypes.string,
+}
+
+const mapDispatchToProps = dispatch => {
+ return {
+ hideModal: () => {
+ dispatch(actions.hideModal())
+ },
+ }
+}
+
+module.exports = connect(null, mapDispatchToProps)(NotificationModal)
diff --git a/ui/app/components/network.js b/ui/app/components/network.js
index 915818009..5a8d0763d 100644
--- a/ui/app/components/network.js
+++ b/ui/app/components/network.js
@@ -39,7 +39,7 @@ Network.prototype.render = function () {
},
src: 'images/loading.svg',
}),
- h('i.fa.fa-caret-down'),
+ h('i.fa.fa-caret-down.network-caret'),
])
} else if (providerName === 'mainnet') {
hoverText = 'Main Ethereum Network'
@@ -63,7 +63,7 @@ Network.prototype.render = function () {
return (
h('div.network-component.pointer', {
- className: classnames('network-component pointer', {
+ className: classnames({
'network-component--disabled': this.props.disabled,
'ethereum-network': providerName === 'mainnet',
'ropsten-test-network': providerName === 'ropsten' || parseInt(networkNumber) === 3,
@@ -90,7 +90,7 @@ Network.prototype.render = function () {
color: '#039396',
}},
'Main Network'),
- h('i.fa.fa-caret-down.fa-lg'),
+ h('i.fa.fa-caret-down.fa-lg.network-caret'),
])
case 'ropsten-test-network':
return h('.network-indicator', [
@@ -103,7 +103,7 @@ Network.prototype.render = function () {
color: '#ff6666',
}},
'Ropsten Test Net'),
- h('i.fa.fa-caret-down.fa-lg'),
+ h('i.fa.fa-caret-down.fa-lg.network-caret'),
])
case 'kovan-test-network':
return h('.network-indicator', [
@@ -116,7 +116,7 @@ Network.prototype.render = function () {
color: '#690496',
}},
'Kovan Test Net'),
- h('i.fa.fa-caret-down.fa-lg'),
+ h('i.fa.fa-caret-down.fa-lg.network-caret'),
])
case 'rinkeby-test-network':
return h('.network-indicator', [
@@ -129,7 +129,7 @@ Network.prototype.render = function () {
color: '#e7a218',
}},
'Rinkeby Test Net'),
- h('i.fa.fa-caret-down.fa-lg'),
+ h('i.fa.fa-caret-down.fa-lg.network-caret'),
])
default:
return h('.network-indicator', [
@@ -145,7 +145,7 @@ Network.prototype.render = function () {
color: '#AEAEAE',
}},
'Private Network'),
- h('i.fa.fa-caret-down.fa-lg'),
+ h('i.fa.fa-caret-down.fa-lg.network-caret'),
])
}
})(),
diff --git a/ui/app/components/pending-tx/confirm-send-ether.js b/ui/app/components/pending-tx/confirm-send-ether.js
index 1264da153..566224864 100644
--- a/ui/app/components/pending-tx/confirm-send-ether.js
+++ b/ui/app/components/pending-tx/confirm-send-ether.js
@@ -421,7 +421,9 @@ ConfirmSendEther.prototype.onSubmit = function (event) {
ConfirmSendEther.prototype.cancel = function (event, txMeta) {
event.preventDefault()
- this.props.cancelTransaction(txMeta)
+ const { cancelTransaction } = this.props
+
+ cancelTransaction(txMeta)
}
ConfirmSendEther.prototype.checkValidity = function () {
@@ -445,26 +447,6 @@ ConfirmSendEther.prototype.gatherTxMeta = function () {
const state = this.state
const txData = clone(state.txData) || clone(props.txData)
- if (props.send.editingTransactionId) {
- const {
- send: {
- memo,
- amount: value,
- gasLimit: gas,
- gasPrice,
- },
- } = props
- const { txParams: { from, to } } = txData
- txData.txParams = {
- from: ethUtil.addHexPrefix(from),
- to: ethUtil.addHexPrefix(to),
- memo: memo && ethUtil.addHexPrefix(memo),
- value: ethUtil.addHexPrefix(value),
- gas: ethUtil.addHexPrefix(gas),
- gasPrice: ethUtil.addHexPrefix(gasPrice),
- }
- }
-
// log.debug(`UI has defaulted to tx meta ${JSON.stringify(txData)}`)
return txData
}
diff --git a/ui/app/components/pending-tx/confirm-send-token.js b/ui/app/components/pending-tx/confirm-send-token.js
index cc2df8299..aa4f29fb0 100644
--- a/ui/app/components/pending-tx/confirm-send-token.js
+++ b/ui/app/components/pending-tx/confirm-send-token.js
@@ -2,7 +2,6 @@ const Component = require('react').Component
const { connect } = require('react-redux')
const h = require('react-hyperscript')
const inherits = require('util').inherits
-const ethAbi = require('ethereumjs-abi')
const tokenAbi = require('human-standard-token-abi')
const abiDecoder = require('abi-decoder')
abiDecoder.addABI(tokenAbi)
@@ -67,15 +66,15 @@ function mapDispatchToProps (dispatch, ownProps) {
const { txParams, id } = txMeta
const tokenData = txParams.data && abiDecoder.decodeMethod(txParams.data)
const { params = [] } = tokenData
- const { value } = params[1] || {}
- const amount = conversionUtil(value, {
+ const { value: to } = params[0] || {}
+ const { value: tokenAmountInDec } = params[1] || {}
+ const tokenAmountInHex = conversionUtil(tokenAmountInDec, {
fromNumericBase: 'dec',
toNumericBase: 'hex',
})
const {
gas: gasLimit,
gasPrice,
- to,
} = txParams
dispatch(actions.setSelectedToken(address))
dispatch(actions.updateSend({
@@ -83,7 +82,7 @@ function mapDispatchToProps (dispatch, ownProps) {
gasPrice,
gasTotal: null,
to,
- amount,
+ amount: tokenAmountInHex,
errors: { to: null, amount: null },
editingTransactionId: id,
}))
@@ -415,7 +414,9 @@ ConfirmSendToken.prototype.onSubmit = function (event) {
ConfirmSendToken.prototype.cancel = function (event, txMeta) {
event.preventDefault()
- this.props.cancelTransaction(txMeta)
+ const { cancelTransaction } = this.props
+
+ cancelTransaction(txMeta)
}
ConfirmSendToken.prototype.checkValidity = function () {
@@ -439,38 +440,6 @@ ConfirmSendToken.prototype.gatherTxMeta = function () {
const state = this.state
const txData = clone(state.txData) || clone(props.txData)
- if (props.send.editingTransactionId) {
- const {
- send: {
- memo,
- amount,
- gasLimit: gas,
- gasPrice,
- },
- } = props
-
- const { txParams: { from, to } } = txData
-
- const tokenParams = {
- from: ethUtil.addHexPrefix(from),
- value: '0',
- gas: ethUtil.addHexPrefix(gas),
- gasPrice: ethUtil.addHexPrefix(gasPrice),
- }
-
- const data = '0xa9059cbb' + Array.prototype.map.call(
- ethAbi.rawEncode(['address', 'uint256'], [to, ethUtil.addHexPrefix(amount)]),
- x => ('00' + x.toString(16)).slice(-2)
- ).join('')
-
- txData.txParams = {
- ...tokenParams,
- to: ethUtil.addHexPrefix(to),
- memo: memo && ethUtil.addHexPrefix(memo),
- data,
- }
- }
-
// log.debug(`UI has defaulted to tx meta ${JSON.stringify(txData)}`)
return txData
}
diff --git a/ui/app/components/send/send-constants.js b/ui/app/components/send/send-constants.js
index a961ffcd8..b3ee0899a 100644
--- a/ui/app/components/send/send-constants.js
+++ b/ui/app/components/send/send-constants.js
@@ -3,8 +3,8 @@ const { conversionUtil, multiplyCurrencies } = require('../../conversion-util')
const MIN_GAS_PRICE_HEX = (100000000).toString(16)
const MIN_GAS_PRICE_DEC = '100000000'
-const MIN_GAS_LIMIT_HEX = (21000).toString(16)
-const MIN_GAS_LIMIT_DEC = 21000
+const MIN_GAS_LIMIT_DEC = '21000'
+const MIN_GAS_LIMIT_HEX = (parseInt(MIN_GAS_LIMIT_DEC)).toString(16)
const MIN_GAS_PRICE_GWEI = ethUtil.addHexPrefix(conversionUtil(MIN_GAS_PRICE_HEX, {
fromDenomination: 'WEI',
@@ -20,6 +20,8 @@ const MIN_GAS_TOTAL = multiplyCurrencies(MIN_GAS_LIMIT_HEX, MIN_GAS_PRICE_HEX, {
multiplierBase: 16,
})
+const TOKEN_TRANSFER_FUNCTION_SIGNATURE = '0xa9059cbb'
+
module.exports = {
MIN_GAS_PRICE_GWEI,
MIN_GAS_PRICE_HEX,
@@ -27,4 +29,5 @@ module.exports = {
MIN_GAS_LIMIT_HEX,
MIN_GAS_LIMIT_DEC,
MIN_GAS_TOTAL,
+ TOKEN_TRANSFER_FUNCTION_SIGNATURE,
}
diff --git a/ui/app/components/send/send-v2-container.js b/ui/app/components/send/send-v2-container.js
index 4451a6113..2d2ed4546 100644
--- a/ui/app/components/send/send-v2-container.js
+++ b/ui/app/components/send/send-v2-container.js
@@ -50,6 +50,7 @@ function mapStateToProps (state) {
data,
amountConversionRate: selectedToken ? tokenToFiatRate : conversionRate,
tokenContract: getSelectedTokenContract(state),
+ unapprovedTxs: state.metamask.unapprovedTxs,
}
}
@@ -64,6 +65,7 @@ function mapDispatchToProps (dispatch) {
),
signTx: txParams => dispatch(actions.signTx(txParams)),
updateAndApproveTx: txParams => dispatch(actions.updateAndApproveTx(txParams)),
+ updateTx: txData => dispatch(actions.updateTransaction(txData)),
setSelectedAddress: address => dispatch(actions.setSelectedAddress(address)),
addToAddressBook: address => dispatch(actions.addToAddressBook(address)),
updateGasTotal: newTotal => dispatch(actions.updateGasTotal(newTotal)),
@@ -77,6 +79,6 @@ function mapDispatchToProps (dispatch) {
updateSendErrors: newError => dispatch(actions.updateSendErrors(newError)),
goHome: () => dispatch(actions.goHome()),
clearSend: () => dispatch(actions.clearSend()),
- backToConfirmScreen: editingTransactionId => dispatch(actions.showConfTxPage({ id: editingTransactionId })),
+ setMaxModeTo: bool => dispatch(actions.setMaxModeTo(bool)),
}
}
diff --git a/ui/app/components/token-cell.js b/ui/app/components/token-cell.js
index b40c0ec0d..677b66830 100644
--- a/ui/app/components/token-cell.js
+++ b/ui/app/components/token-cell.js
@@ -86,7 +86,9 @@ TokenCell.prototype.render = function () {
numberOfDecimals: 2,
conversionRate: currentTokenToFiatRate,
})
- formattedFiat = `${currentTokenInFiat} ${currentCurrency.toUpperCase()}`
+ formattedFiat = currentTokenInFiat.toString() === '0'
+ ? ''
+ : `${currentTokenInFiat} ${currentCurrency.toUpperCase()}`
}
const showFiat = Boolean(currentTokenInFiat) && currentCurrency.toUpperCase() !== symbol
diff --git a/ui/app/components/transaction-list-item.js b/ui/app/components/transaction-list-item.js
index 255f0e5eb..4e3d2cb93 100644
--- a/ui/app/components/transaction-list-item.js
+++ b/ui/app/components/transaction-list-item.js
@@ -1,6 +1,7 @@
const Component = require('react').Component
const h = require('react-hyperscript')
const inherits = require('util').inherits
+const connect = require('react-redux').connect
const EthBalance = require('./eth-balance')
const addressSummary = require('../util').addressSummary
@@ -9,18 +10,33 @@ const CopyButton = require('./copyButton')
const vreme = new (require('vreme'))()
const Tooltip = require('./tooltip')
const numberToBN = require('number-to-bn')
+const actions = require('../actions')
const TransactionIcon = require('./transaction-list-item-icon')
const ShiftListItem = require('./shift-list-item')
-module.exports = TransactionListItem
+
+const mapDispatchToProps = dispatch => {
+ return {
+ retryTransaction: transactionId => dispatch(actions.retryTransaction(transactionId)),
+ }
+}
+
+module.exports = connect(null, mapDispatchToProps)(TransactionListItem)
inherits(TransactionListItem, Component)
function TransactionListItem () {
Component.call(this)
}
+TransactionListItem.prototype.showRetryButton = function () {
+ const { transaction = {} } = this.props
+ const { status, time } = transaction
+ return status === 'submitted' && Date.now() - time > 30000
+}
+
TransactionListItem.prototype.render = function () {
const { transaction, network, conversionRate, currentCurrency } = this.props
+ const { status } = transaction
if (transaction.key === 'shapeshift') {
if (network === '1') return h(ShiftListItem, transaction)
}
@@ -32,7 +48,7 @@ TransactionListItem.prototype.render = function () {
var isMsg = ('msgParams' in transaction)
var isTx = ('txParams' in transaction)
- var isPending = transaction.status === 'unapproved'
+ var isPending = status === 'unapproved'
let txParams
if (isTx) {
txParams = transaction.txParams
@@ -44,7 +60,7 @@ TransactionListItem.prototype.render = function () {
const isClickable = ('hash' in transaction && isLinkable) || isPending
return (
- h(`.transaction-list-item.flex-row.flex-space-between${isClickable ? '.pointer' : ''}`, {
+ h('.transaction-list-item.flex-column', {
onClick: (event) => {
if (isPending) {
this.props.showTx(transaction.id)
@@ -56,51 +72,92 @@ TransactionListItem.prototype.render = function () {
},
style: {
padding: '20px 0',
+ alignItems: 'center',
},
}, [
-
- h('.identicon-wrapper.flex-column.flex-center.select-none', [
- h(TransactionIcon, { txParams, transaction, isTx, isMsg }),
+ h(`.flex-row.flex-space-between${isClickable ? '.pointer' : ''}`, {
+ style: {
+ width: '100%',
+ },
+ }, [
+ h('.identicon-wrapper.flex-column.flex-center.select-none', [
+ h(TransactionIcon, { txParams, transaction, isTx, isMsg }),
+ ]),
+
+ h(Tooltip, {
+ title: 'Transaction Number',
+ position: 'right',
+ }, [
+ h('span', {
+ style: {
+ display: 'flex',
+ cursor: 'normal',
+ flexDirection: 'column',
+ alignItems: 'center',
+ justifyContent: 'center',
+ padding: '10px',
+ },
+ }, nonce),
+ ]),
+
+ h('.flex-column', {style: {width: '200px', overflow: 'hidden'}}, [
+ domainField(txParams),
+ h('div', date),
+ recipientField(txParams, transaction, isTx, isMsg),
+ ]),
+
+ // Places a copy button if tx is successful, else places a placeholder empty div.
+ transaction.hash ? h(CopyButton, { value: transaction.hash }) : h('div', {style: { display: 'flex', alignItems: 'center', width: '26px' }}),
+
+ isTx ? h(EthBalance, {
+ value: txParams.value,
+ conversionRate,
+ currentCurrency,
+ width: '55px',
+ shorten: true,
+ showFiat: false,
+ style: {fontSize: '15px'},
+ }) : h('.flex-column'),
]),
- h(Tooltip, {
- title: 'Transaction Number',
- position: 'right',
+ this.showRetryButton() && h('.transition-list-item__retry.grow-on-hover', {
+ onClick: event => {
+ event.stopPropagation()
+ this.resubmit()
+ },
+ style: {
+ height: '22px',
+ borderRadius: '22px',
+ color: '#F9881B',
+ padding: '0 20px',
+ backgroundColor: '#FFE3C9',
+ display: 'flex',
+ justifyContent: 'center',
+ alignItems: 'center',
+ fontSize: '8px',
+ cursor: 'pointer',
+ },
}, [
- h('span', {
+ h('div', {
style: {
- display: 'flex',
- cursor: 'normal',
- flexDirection: 'column',
- alignItems: 'center',
- justifyContent: 'center',
- padding: '10px',
+ paddingRight: '2px',
},
- }, nonce),
- ]),
-
- h('.flex-column', {style: {width: '200px', overflow: 'hidden'}}, [
- domainField(txParams),
- h('div', date),
- recipientField(txParams, transaction, isTx, isMsg),
+ }, 'Taking too long?'),
+ h('div', {
+ style: {
+ textDecoration: 'underline',
+ },
+ }, 'Retry with a higher gas price here'),
]),
-
- // Places a copy button if tx is successful, else places a placeholder empty div.
- transaction.hash ? h(CopyButton, { value: transaction.hash }) : h('div', {style: { display: 'flex', alignItems: 'center', width: '26px' }}),
-
- isTx ? h(EthBalance, {
- value: txParams.value,
- conversionRate,
- currentCurrency,
- width: '55px',
- shorten: true,
- showFiat: false,
- style: {fontSize: '15px'},
- }) : h('.flex-column'),
])
)
}
+TransactionListItem.prototype.resubmit = function () {
+ const { transaction } = this.props
+ this.props.retryTransaction(transaction.id)
+}
+
function domainField (txParams) {
return h('div', {
style: {
diff --git a/ui/app/components/tx-list-item.js b/ui/app/components/tx-list-item.js
index 4e76147a1..8a9253d4d 100644
--- a/ui/app/components/tx-list-item.js
+++ b/ui/app/components/tx-list-item.js
@@ -170,6 +170,7 @@ TxListItem.prototype.getSendTokenTotal = async function () {
TxListItem.prototype.render = function () {
const {
transactionStatus,
+ transactionAmount,
onClick,
transActionId,
dateString,
@@ -177,6 +178,7 @@ TxListItem.prototype.render = function () {
className,
} = this.props
const { total, fiatTotal } = this.state
+ const showFiatTotal = transactionAmount !== '0x0' && fiatTotal
return h(`div${className || ''}`, {
key: transActionId,
@@ -238,7 +240,7 @@ TxListItem.prototype.render = function () {
}),
}, total),
- fiatTotal && h('span.tx-list-fiat-value', fiatTotal),
+ showFiatTotal && h('span.tx-list-fiat-value', fiatTotal),
]),
]),
diff --git a/ui/app/components/tx-view.js b/ui/app/components/tx-view.js
index ebef22680..e42a20c85 100644
--- a/ui/app/components/tx-view.js
+++ b/ui/app/components/tx-view.js
@@ -14,6 +14,7 @@ module.exports = connect(mapStateToProps, mapDispatchToProps)(TxView)
function mapStateToProps (state) {
const sidebarOpen = state.appState.sidebarOpen
+ const isMascara = state.appState.isMascara
const identities = state.metamask.identities
const accounts = state.metamask.accounts
@@ -31,6 +32,7 @@ function mapStateToProps (state) {
selectedToken: selectors.getSelectedToken(state),
identity,
network,
+ isMascara,
}
}
@@ -98,7 +100,7 @@ TxView.prototype.renderButtons = function () {
}
TxView.prototype.render = function () {
- const { selectedAddress, identity, network } = this.props
+ const { selectedAddress, identity, network, isMascara } = this.props
return h('div.tx-view.flex-column', {
style: {},
@@ -107,6 +109,7 @@ TxView.prototype.render = function () {
h('div.flex-row.phone-visible', {
style: {
margin: '1em 0.9em',
+ justifyContent: 'space-between',
alignItems: 'center',
},
}, [
@@ -139,6 +142,10 @@ TxView.prototype.render = function () {
identity.name,
]),
+ !isMascara && h('div.open-in-browser', {
+ onClick: () => global.platform.openExtensionInBrowser(),
+ }, [h('img', { src: 'images/open.svg' })]),
+
]),
this.renderHeroBalance(),
diff --git a/ui/app/css/index.scss b/ui/app/css/index.scss
index 01899ccad..445c819ff 100644
--- a/ui/app/css/index.scss
+++ b/ui/app/css/index.scss
@@ -4,6 +4,7 @@
http://www.creativebloq.com/web-design/manage-large-css-projects-itcss-101517528
https://www.xfive.co/blog/itcss-scalable-maintainable-css-architecture/
*/
+
@import './itcss/settings/index.scss';
@import './itcss/tools/index.scss';
@import './itcss/generic/index.scss';
diff --git a/ui/app/css/itcss/components/account-menu.scss b/ui/app/css/itcss/components/account-menu.scss
index e40e5a8c0..e16d2e024 100644
--- a/ui/app/css/itcss/components/account-menu.scss
+++ b/ui/app/css/itcss/components/account-menu.scss
@@ -40,7 +40,7 @@
font-size: 12px;
line-height: 23px;
padding: 0 24px;
- font-weight: 200;
+ font-weight: 300;
}
img {
@@ -113,7 +113,7 @@
&__name {
color: $white;
font-size: 18px;
- font-weight: 200;
+ font-weight: 300;
line-height: 16px;
}
@@ -126,7 +126,7 @@
&__action {
font-size: 16px;
line-height: 18px;
- font-weight: 200;
+ font-weight: 300;
cursor: pointer;
}
}
diff --git a/ui/app/css/itcss/components/menu.scss b/ui/app/css/itcss/components/menu.scss
index 17e24de98..eb92a1b70 100644
--- a/ui/app/css/itcss/components/menu.scss
+++ b/ui/app/css/itcss/components/menu.scss
@@ -11,8 +11,8 @@
flex-flow: row nowrap;
align-items: center;
position: relative;
- z-index: 200;
- font-weight: 200;
+ font-weight: 300;
+ z-index: 201;
@media screen and (max-width: 575px) {
padding: 14px;
diff --git a/ui/app/css/itcss/components/modal.scss b/ui/app/css/itcss/components/modal.scss
index b69bd5c7e..9b64564d6 100644
--- a/ui/app/css/itcss/components/modal.scss
+++ b/ui/app/css/itcss/components/modal.scss
@@ -563,3 +563,39 @@
}
}
}
+
+//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;
+ font-family: Roboto;
+}
+
+.notification-modal-header {
+ background: $wild-sand;
+ width: 100%;
+ display: flex;
+ justify-content: center;
+ padding: 30px;
+ font-size: 22px;
+ color: $nile-blue;
+ height: 79px;
+}
+
+.notification-modal-message {
+ padding: 20px;
+}
+
+.notification-modal-message {
+ width: 100%;
+ display: flex;
+ justify-content: center;
+ font-size: 17px;
+ color: $nile-blue;
+} \ No newline at end of file
diff --git a/ui/app/css/itcss/components/newui-sections.scss b/ui/app/css/itcss/components/newui-sections.scss
index 244de2ba0..61dfbd176 100644
--- a/ui/app/css/itcss/components/newui-sections.scss
+++ b/ui/app/css/itcss/components/newui-sections.scss
@@ -38,6 +38,10 @@ $wallet-view-bg: $wild-sand;
}
}
+.open-in-browser {
+ cursor: pointer;
+}
+
// wallet view and sidebar
.wallet-view {
@@ -248,7 +252,7 @@ $wallet-view-bg: $wild-sand;
// wallet view
.account-name {
font-size: 24px;
- font-weight: 200;
+ font-weight: 300;
line-height: 20px;
color: $scorpion;
margin-top: 8px;
diff --git a/ui/app/css/itcss/components/settings.scss b/ui/app/css/itcss/components/settings.scss
index 2f29d8017..d60ebd934 100644
--- a/ui/app/css/itcss/components/settings.scss
+++ b/ui/app/css/itcss/components/settings.scss
@@ -145,6 +145,11 @@
color: $monzo;
}
+.settings__clear-button--orange {
+ border: 1px solid rgba(247, 134, 28, 1);
+ color: rgba(247, 134, 28, 1);
+}
+
.settings__info-logo-wrapper {
height: 80px;
margin-bottom: 20px;
diff --git a/ui/app/first-time/init-menu.js b/ui/app/first-time/init-menu.js
index cc7c51bd3..b4587f1ee 100644
--- a/ui/app/first-time/init-menu.js
+++ b/ui/app/first-time/init-menu.js
@@ -8,6 +8,8 @@ const actions = require('../actions')
const Tooltip = require('../components/tooltip')
const getCaretCoordinates = require('textarea-caret')
+let isSubmitting = false
+
module.exports = connect(mapStateToProps)(InitializeMenuScreen)
inherits(InitializeMenuScreen, Component)
@@ -164,7 +166,10 @@ InitializeMenuScreen.prototype.createNewVaultAndKeychain = function () {
return
}
- this.props.dispatch(actions.createNewVaultAndKeychain(password))
+ if (!isSubmitting) {
+ isSubmitting = true
+ this.props.dispatch(actions.createNewVaultAndKeychain(password))
+ }
}
InitializeMenuScreen.prototype.inputChanged = function (event) {
diff --git a/ui/app/reducers/metamask.js b/ui/app/reducers/metamask.js
index 83161320e..294c29948 100644
--- a/ui/app/reducers/metamask.js
+++ b/ui/app/reducers/metamask.js
@@ -1,6 +1,7 @@
const extend = require('xtend')
const actions = require('../actions')
const MetamascaraPlatform = require('../../../app/scripts/platforms/window')
+const { OLD_UI_NETWORK_TYPE } = require('../../../app/scripts/config').enums
module.exports = reduceMetamask
@@ -33,9 +34,13 @@ function reduceMetamask (state, action) {
amount: '0x0',
memo: '',
errors: {},
+ maxModeOn: false,
editingTransactionId: null,
},
coinOptions: {},
+ useBlockie: false,
+ featureFlags: {},
+ networkEndpointType: OLD_UI_NETWORK_TYPE,
}, state.metamask)
switch (action.type) {
@@ -257,6 +262,14 @@ function reduceMetamask (state, action) {
},
})
+ case actions.UPDATE_MAX_MODE:
+ return extend(metamaskState, {
+ send: {
+ ...metamaskState.send,
+ maxModeOn: action.value,
+ },
+ })
+
case actions.UPDATE_SEND:
return extend(metamaskState, {
send: {
@@ -309,11 +322,26 @@ function reduceMetamask (state, action) {
return extend(metamaskState, {
tokenExchangeRates: {
...metamaskState.tokenExchangeRates,
- [marketinfo.pair]: ssMarketInfo,
+ [ssMarketInfo.pair]: ssMarketInfo,
},
coinOptions,
})
+ case actions.SET_USE_BLOCKIE:
+ return extend(metamaskState, {
+ useBlockie: action.value,
+ })
+
+ case actions.UPDATE_FEATURE_FLAGS:
+ return extend(metamaskState, {
+ featureFlags: action.value,
+ })
+
+ case actions.UPDATE_NETWORK_ENDPOINT_TYPE:
+ return extend(metamaskState, {
+ networkEndpointType: action.value,
+ })
+
default:
return metamaskState
diff --git a/ui/app/root.js b/ui/app/root.js
index 9e7314b20..21d6d1829 100644
--- a/ui/app/root.js
+++ b/ui/app/root.js
@@ -2,7 +2,7 @@ const inherits = require('util').inherits
const Component = require('react').Component
const Provider = require('react-redux').Provider
const h = require('react-hyperscript')
-const App = require('./app')
+const SelectedApp = require('./select-app')
module.exports = Root
@@ -15,7 +15,7 @@ Root.prototype.render = function () {
h(Provider, {
store: this.props.store,
}, [
- h(App),
+ h(SelectedApp),
])
)
diff --git a/ui/app/select-app.js b/ui/app/select-app.js
new file mode 100644
index 000000000..ac6867aeb
--- /dev/null
+++ b/ui/app/select-app.js
@@ -0,0 +1,61 @@
+const inherits = require('util').inherits
+const Component = require('react').Component
+const connect = require('react-redux').connect
+const h = require('react-hyperscript')
+const App = require('./app')
+const OldApp = require('../../old-ui/app/app')
+const { autoAddToBetaUI } = require('./selectors')
+const { setFeatureFlag, setNetworkEndpoints } = require('./actions')
+const { BETA_UI_NETWORK_TYPE } = require('../../app/scripts/config').enums
+
+function mapStateToProps (state) {
+ return {
+ betaUI: state.metamask.featureFlags.betaUI,
+ autoAdd: autoAddToBetaUI(state),
+ isUnlocked: state.metamask.isUnlocked,
+ isMascara: state.metamask.isMascara,
+ firstTime: Object.keys(state.metamask.identities).length === 0,
+ }
+}
+
+function mapDispatchToProps (dispatch) {
+ return {
+ setFeatureFlagWithModal: () => {
+ return dispatch(setFeatureFlag('betaUI', true, 'BETA_UI_NOTIFICATION_MODAL'))
+ .then(() => dispatch(setNetworkEndpoints(BETA_UI_NETWORK_TYPE)))
+ },
+ setFeatureFlagWithoutModal: () => {
+ return dispatch(setFeatureFlag('betaUI', true))
+ .then(() => dispatch(setNetworkEndpoints(BETA_UI_NETWORK_TYPE)))
+ },
+ }
+}
+module.exports = connect(mapStateToProps, mapDispatchToProps)(SelectedApp)
+
+inherits(SelectedApp, Component)
+function SelectedApp () {
+ Component.call(this)
+}
+
+SelectedApp.prototype.componentWillReceiveProps = function (nextProps) {
+ const {
+ isUnlocked,
+ setFeatureFlagWithModal,
+ setFeatureFlagWithoutModal,
+ isMascara,
+ firstTime,
+ } = this.props
+
+ if (isMascara || firstTime) {
+ setFeatureFlagWithoutModal()
+ } else if (!isUnlocked && nextProps.isUnlocked && (nextProps.autoAdd)) {
+ setFeatureFlagWithModal()
+ }
+}
+
+SelectedApp.prototype.render = function () {
+ const { betaUI, isMascara, firstTime } = this.props
+
+ const Selected = betaUI || isMascara || firstTime ? App : OldApp
+ return h(Selected)
+}
diff --git a/ui/app/selectors.js b/ui/app/selectors.js
index a5f9a75d8..22ef439c4 100644
--- a/ui/app/selectors.js
+++ b/ui/app/selectors.js
@@ -24,6 +24,8 @@ const selectors = {
getSendAmount,
getSelectedTokenToFiatRate,
getSelectedTokenContract,
+ autoAddToBetaUI,
+ getSendMaxModeState,
}
module.exports = selectors
@@ -135,6 +137,10 @@ function getSendAmount (state) {
return state.metamask.send.amount
}
+function getSendMaxModeState (state) {
+ return state.metamask.send.maxModeOn
+}
+
function getCurrentCurrency (state) {
return state.metamask.currentCurrency
}
@@ -158,3 +164,20 @@ function getSelectedTokenContract (state) {
? global.eth.contract(abi).at(selectedToken.address)
: null
}
+
+function autoAddToBetaUI (state) {
+ const autoAddTransactionThreshold = 12
+ const autoAddAccountsThreshold = 2
+ const autoAddTokensThreshold = 1
+
+ const numberOfTransactions = state.metamask.selectedAddressTxList.length
+ const numberOfAccounts = Object.keys(state.metamask.accounts).length
+ const numberOfTokensAdded = state.metamask.tokens.length
+
+ const userPassesThreshold = (numberOfTransactions > autoAddTransactionThreshold) &&
+ (numberOfAccounts > autoAddAccountsThreshold) &&
+ (numberOfTokensAdded > autoAddTokensThreshold)
+ const userIsNotInBeta = !state.metamask.featureFlags.betaUI
+
+ return userIsNotInBeta && userPassesThreshold
+} \ No newline at end of file
diff --git a/ui/app/send-v2.js b/ui/app/send-v2.js
index 788ae87b4..7c9b6dbc6 100644
--- a/ui/app/send-v2.js
+++ b/ui/app/send-v2.js
@@ -2,6 +2,7 @@ const { inherits } = require('util')
const PersistentForm = require('../lib/persistent-form')
const h = require('react-hyperscript')
+const ethAbi = require('ethereumjs-abi')
const ethUtil = require('ethereumjs-util')
const Identicon = require('./components/identicon')
@@ -13,8 +14,7 @@ const GasFeeDisplay = require('./components/send/gas-fee-display-v2')
const {
MIN_GAS_TOTAL,
- MIN_GAS_PRICE_HEX,
- MIN_GAS_LIMIT_HEX,
+ TOKEN_TRANSFER_FUNCTION_SIGNATURE,
} = require('./components/send/send-constants')
const {
@@ -313,8 +313,9 @@ SendTransactionScreen.prototype.renderToRow = function () {
SendTransactionScreen.prototype.handleAmountChange = function (value) {
const amount = value
- const { updateSendAmount } = this.props
+ const { updateSendAmount, setMaxModeTo } = this.props
+ setMaxModeTo(false)
this.validateAmount(amount)
updateSendAmount(amount)
}
@@ -324,11 +325,9 @@ SendTransactionScreen.prototype.setAmountToMax = function () {
from: { balance },
updateSendAmount,
updateSendErrors,
- updateGasPrice,
- updateGasLimit,
- updateGasTotal,
tokenBalance,
selectedToken,
+ gasTotal,
} = this.props
const { decimals } = selectedToken || {}
const multiplier = Math.pow(10, Number(decimals || 0))
@@ -337,16 +336,12 @@ SendTransactionScreen.prototype.setAmountToMax = function () {
? multiplyCurrencies(tokenBalance, multiplier, {toNumericBase: 'hex'})
: subtractCurrencies(
ethUtil.addHexPrefix(balance),
- ethUtil.addHexPrefix(MIN_GAS_TOTAL),
+ ethUtil.addHexPrefix(gasTotal),
{ toNumericBase: 'hex' }
)
updateSendErrors({ amount: null })
- if (!selectedToken) {
- updateGasPrice(MIN_GAS_PRICE_HEX)
- updateGasLimit(MIN_GAS_LIMIT_HEX)
- updateGasTotal(MIN_GAS_TOTAL)
- }
+
updateSendAmount(maxAmount)
}
@@ -407,19 +402,22 @@ SendTransactionScreen.prototype.renderAmountRow = function () {
amountConversionRate,
errors,
amount,
+ setMaxModeTo,
+ maxModeOn,
} = this.props
return h('div.send-v2__form-row', [
- h('div.send-v2__form-label', [
+ h('div.send-v2__form-label', [
'Amount:',
this.renderErrorMessage('amount'),
!errors.amount && h('div.send-v2__amount-max', {
onClick: (event) => {
event.preventDefault()
+ setMaxModeTo(true)
this.setAmountToMax()
},
- }, [ 'Max' ]),
+ }, [ !maxModeOn ? 'Max' : '' ]),
]),
h('div.send-v2__form-field', [
@@ -556,6 +554,48 @@ SendTransactionScreen.prototype.addToAddressBookIfNew = function (newAddress) {
}
}
+SendTransactionScreen.prototype.getEditedTx = function () {
+ const {
+ from: {address: from},
+ to,
+ amount,
+ gasLimit: gas,
+ gasPrice,
+ selectedToken,
+ editingTransactionId,
+ unapprovedTxs,
+ } = this.props
+
+ const editingTx = {
+ ...unapprovedTxs[editingTransactionId],
+ txParams: {
+ from: ethUtil.addHexPrefix(from),
+ gas: ethUtil.addHexPrefix(gas),
+ gasPrice: ethUtil.addHexPrefix(gasPrice),
+ },
+ }
+
+ if (selectedToken) {
+ const data = TOKEN_TRANSFER_FUNCTION_SIGNATURE + Array.prototype.map.call(
+ ethAbi.rawEncode(['address', 'uint256'], [to, ethUtil.addHexPrefix(amount)]),
+ x => ('00' + x.toString(16)).slice(-2)
+ ).join('')
+
+ Object.assign(editingTx.txParams, {
+ value: ethUtil.addHexPrefix('0'),
+ to: ethUtil.addHexPrefix(selectedToken.address),
+ data,
+ })
+ } else {
+ Object.assign(editingTx.txParams, {
+ value: ethUtil.addHexPrefix(amount),
+ to: ethUtil.addHexPrefix(to),
+ })
+ }
+
+ return editingTx
+}
+
SendTransactionScreen.prototype.onSubmit = function (event) {
event.preventDefault()
const {
@@ -566,10 +606,10 @@ SendTransactionScreen.prototype.onSubmit = function (event) {
gasPrice,
signTokenTx,
signTx,
+ updateTx,
selectedToken,
editingTransactionId,
errors: { amount: amountError, to: toError },
- backToConfirmScreen,
} = this.props
const noErrors = !amountError && toError === null
@@ -581,23 +621,25 @@ SendTransactionScreen.prototype.onSubmit = function (event) {
this.addToAddressBookIfNew(to)
if (editingTransactionId) {
- backToConfirmScreen(editingTransactionId)
- return
- }
+ const editedTx = this.getEditedTx()
- const txParams = {
- from,
- value: '0',
- gas,
- gasPrice,
- }
+ updateTx(editedTx)
+ } else {
- if (!selectedToken) {
- txParams.value = amount
- txParams.to = to
- }
+ const txParams = {
+ from,
+ value: '0',
+ gas,
+ gasPrice,
+ }
+
+ if (!selectedToken) {
+ txParams.value = amount
+ txParams.to = to
+ }
- selectedToken
- ? signTokenTx(selectedToken.address, to, amount, txParams)
- : signTx(txParams)
+ selectedToken
+ ? signTokenTx(selectedToken.address, to, amount, txParams)
+ : signTx(txParams)
+ }
}
diff --git a/ui/app/settings.js b/ui/app/settings.js
index 786a70e7e..a3dd65f14 100644
--- a/ui/app/settings.js
+++ b/ui/app/settings.js
@@ -8,6 +8,8 @@ const validUrl = require('valid-url')
const { exportAsFile } = require('./util')
const TabBar = require('./components/tab-bar')
const SimpleDropdown = require('./components/dropdowns/simple-dropdown')
+const ToggleButton = require('react-toggle-button')
+const { OLD_UI_NETWORK_TYPE } = require('../../app/scripts/config').enums
const getInfuraCurrencyOptions = () => {
const sortedCurrencies = infuraCurrencies.objects.sort((a, b) => {
@@ -51,6 +53,26 @@ class Settings extends Component {
])
}
+ renderBlockieOptIn () {
+ const { metamask: { useBlockie }, setUseBlockie } = this.props
+
+ return h('div.settings__content-row', [
+ h('div.settings__content-item', [
+ h('span', 'Use Blockies Identicon'),
+ ]),
+ h('div.settings__content-item', [
+ h('div.settings__content-item-col', [
+ h(ToggleButton, {
+ value: useBlockie,
+ onToggle: (value) => setUseBlockie(!value),
+ activeLabel: '',
+ inactiveLabel: '',
+ }),
+ ]),
+ ]),
+ ])
+ }
+
renderCurrentConversion () {
const { metamask: { currentCurrency, conversionDate }, setCurrentCurrency } = this.props
@@ -208,17 +230,39 @@ class Settings extends Component {
)
}
+ renderOldUI () {
+ const { setFeatureFlagToBeta } = this.props
+
+ return (
+ h('div.settings__content-row', [
+ h('div.settings__content-item', 'Use old UI'),
+ h('div.settings__content-item', [
+ h('div.settings__content-item-col', [
+ h('button.settings__clear-button.settings__clear-button--orange', {
+ onClick (event) {
+ event.preventDefault()
+ setFeatureFlagToBeta()
+ },
+ }, 'Use old UI'),
+ ]),
+ ]),
+ ])
+ )
+ }
+
renderSettingsContent () {
- const { warning } = this.props
+ const { warning, isMascara } = this.props
return (
h('div.settings__content', [
warning && h('div.settings__error', warning),
+ this.renderBlockieOptIn(),
this.renderCurrentConversion(),
// this.renderCurrentProvider(),
this.renderNewRpcUrl(),
this.renderStateLogs(),
this.renderSeedWords(),
+ !isMascara && this.renderOldUI(),
])
)
}
@@ -335,18 +379,22 @@ class Settings extends Component {
Settings.propTypes = {
tab: PropTypes.string,
metamask: PropTypes.object,
+ setUseBlockie: PropTypes.func,
setCurrentCurrency: PropTypes.func,
setRpcTarget: PropTypes.func,
displayWarning: PropTypes.func,
revealSeedConfirmation: PropTypes.func,
+ setFeatureFlagToBeta: PropTypes.func,
warning: PropTypes.string,
goHome: PropTypes.func,
+ isMascara: PropTypes.bool,
}
const mapStateToProps = state => {
return {
metamask: state.metamask,
warning: state.appState.warning,
+ isMascara: state.metamask.isMascara,
}
}
@@ -357,6 +405,11 @@ const mapDispatchToProps = dispatch => {
setRpcTarget: newRpc => dispatch(actions.setRpcTarget(newRpc)),
displayWarning: warning => dispatch(actions.displayWarning(warning)),
revealSeedConfirmation: () => dispatch(actions.revealSeedConfirmation()),
+ setUseBlockie: value => dispatch(actions.setUseBlockie(value)),
+ setFeatureFlagToBeta: () => {
+ return dispatch(actions.setFeatureFlag('betaUI', false, 'OLD_UI_NOTIFICATION_MODAL'))
+ .then(() => dispatch(actions.setNetworkEndpoints(OLD_UI_NETWORK_TYPE)))
+ },
}
}
diff --git a/ui/index.js b/ui/index.js
index fff677471..bc3676c1f 100644
--- a/ui/index.js
+++ b/ui/index.js
@@ -4,11 +4,12 @@ const Root = require('./app/root')
const actions = require('./app/actions')
const configureStore = require('./app/store')
const txHelper = require('./lib/tx-helper')
+const { OLD_UI_NETWORK_TYPE, BETA_UI_NETWORK_TYPE } = require('../app/scripts/config').enums
+
global.log = require('loglevel')
module.exports = launchMetamaskUi
-
log.setLevel(global.METAMASK_DEBUG ? 'debug' : 'warn')
function launchMetamaskUi (opts, cb) {
@@ -36,6 +37,10 @@ function startApp (metamaskState, accountManager, opts) {
networkVersion: opts.networkVersion,
})
+ const useBetaUi = metamaskState.featureFlags.betaUI
+ const networkEndpointType = useBetaUi ? BETA_UI_NETWORK_TYPE : OLD_UI_NETWORK_TYPE
+ store.dispatch(actions.setNetworkEndpoints(networkEndpointType))
+
// if unconfirmed txs, start on txConf page
const unapprovedTxsAll = txHelper(metamaskState.unapprovedTxs, metamaskState.unapprovedMsgs, metamaskState.unapprovedPersonalMsgs, metamaskState.unapprovedTypedMessages, metamaskState.network)
const numberOfUnapprivedTx = unapprovedTxsAll.length
diff --git a/ui/lib/blockies.js b/ui/lib/blockies.js
new file mode 100644
index 000000000..ee5a2a5ca
--- /dev/null
+++ b/ui/lib/blockies.js
@@ -0,0 +1,364 @@
+(function (global, factory) {
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
+ typeof define === 'function' && define.amd ? define(['exports'], factory) :
+ (factory((global.blockies = {})));
+}(this, (function (exports) { 'use strict';
+
+ /**
+ * A handy class to calculate color values.
+ *
+ * @version 1.0
+ * @author Robert Eisele <robert@xarg.org>
+ * @copyright Copyright (c) 2010, Robert Eisele
+ * @link http://www.xarg.org/2010/03/generate-client-side-png-files-using-javascript/
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ *
+ */
+
+
+// helper functions for that ctx
+ function write(buffer, offs) {
+ for (var i = 2; i < arguments.length; i++) {
+ for (var j = 0; j < arguments[i].length; j++) {
+ buffer[offs++] = arguments[i].charAt(j);
+ }
+ }
+ }
+
+ function byte2(w) {
+ return String.fromCharCode((w >> 8) & 255, w & 255);
+ }
+
+ function byte4(w) {
+ return String.fromCharCode((w >> 24) & 255, (w >> 16) & 255, (w >> 8) & 255, w & 255);
+ }
+
+ function byte2lsb(w) {
+ return String.fromCharCode(w & 255, (w >> 8) & 255);
+ }
+
+ var PNG = function(width,height,depth) {
+
+ this.width = width;
+ this.height = height;
+ this.depth = depth;
+
+ // pixel data and row filter identifier size
+ this.pix_size = height * (width + 1);
+
+ // deflate header, pix_size, block headers, adler32 checksum
+ this.data_size = 2 + this.pix_size + 5 * Math.floor((0xfffe + this.pix_size) / 0xffff) + 4;
+
+ // offsets and sizes of Png chunks
+ this.ihdr_offs = 0; // IHDR offset and size
+ this.ihdr_size = 4 + 4 + 13 + 4;
+ this.plte_offs = this.ihdr_offs + this.ihdr_size; // PLTE offset and size
+ this.plte_size = 4 + 4 + 3 * depth + 4;
+ this.trns_offs = this.plte_offs + this.plte_size; // tRNS offset and size
+ this.trns_size = 4 + 4 + depth + 4;
+ this.idat_offs = this.trns_offs + this.trns_size; // IDAT offset and size
+ this.idat_size = 4 + 4 + this.data_size + 4;
+ this.iend_offs = this.idat_offs + this.idat_size; // IEND offset and size
+ this.iend_size = 4 + 4 + 4;
+ this.buffer_size = this.iend_offs + this.iend_size; // total PNG size
+
+ this.buffer = new Array();
+ this.palette = new Object();
+ this.pindex = 0;
+
+ var _crc32 = new Array();
+
+ // initialize buffer with zero bytes
+ for (var i = 0; i < this.buffer_size; i++) {
+ this.buffer[i] = "\x00";
+ }
+
+ // initialize non-zero elements
+ write(this.buffer, this.ihdr_offs, byte4(this.ihdr_size - 12), 'IHDR', byte4(width), byte4(height), "\x08\x03");
+ write(this.buffer, this.plte_offs, byte4(this.plte_size - 12), 'PLTE');
+ write(this.buffer, this.trns_offs, byte4(this.trns_size - 12), 'tRNS');
+ write(this.buffer, this.idat_offs, byte4(this.idat_size - 12), 'IDAT');
+ write(this.buffer, this.iend_offs, byte4(this.iend_size - 12), 'IEND');
+
+ // initialize deflate header
+ var header = ((8 + (7 << 4)) << 8) | (3 << 6);
+ header+= 31 - (header % 31);
+
+ write(this.buffer, this.idat_offs + 8, byte2(header));
+
+ // initialize deflate block headers
+ for (var i = 0; (i << 16) - 1 < this.pix_size; i++) {
+ var size, bits;
+ if (i + 0xffff < this.pix_size) {
+ size = 0xffff;
+ bits = "\x00";
+ } else {
+ size = this.pix_size - (i << 16) - i;
+ bits = "\x01";
+ }
+ write(this.buffer, this.idat_offs + 8 + 2 + (i << 16) + (i << 2), bits, byte2lsb(size), byte2lsb(~size));
+ }
+
+ /* Create crc32 lookup table */
+ for (var i = 0; i < 256; i++) {
+ var c = i;
+ for (var j = 0; j < 8; j++) {
+ if (c & 1) {
+ c = -306674912 ^ ((c >> 1) & 0x7fffffff);
+ } else {
+ c = (c >> 1) & 0x7fffffff;
+ }
+ }
+ _crc32[i] = c;
+ }
+
+ // compute the index into a png for a given pixel
+ this.index = function(x,y) {
+ var i = y * (this.width + 1) + x + 1;
+ var j = this.idat_offs + 8 + 2 + 5 * Math.floor((i / 0xffff) + 1) + i;
+ return j;
+ };
+
+ // convert a color and build up the palette
+ this.color = function(red, green, blue, alpha) {
+
+ alpha = alpha >= 0 ? alpha : 255;
+ var color = (((((alpha << 8) | red) << 8) | green) << 8) | blue;
+
+ if (typeof this.palette[color] == "undefined") {
+ if (this.pindex == this.depth) return "\x00";
+
+ var ndx = this.plte_offs + 8 + 3 * this.pindex;
+
+ this.buffer[ndx + 0] = String.fromCharCode(red);
+ this.buffer[ndx + 1] = String.fromCharCode(green);
+ this.buffer[ndx + 2] = String.fromCharCode(blue);
+ this.buffer[this.trns_offs+8+this.pindex] = String.fromCharCode(alpha);
+
+ this.palette[color] = String.fromCharCode(this.pindex++);
+ }
+ return this.palette[color];
+ };
+
+ // output a PNG string, Base64 encoded
+ this.getBase64 = function() {
+
+ var s = this.getDump();
+
+ var ch = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
+ var c1, c2, c3, e1, e2, e3, e4;
+ var l = s.length;
+ var i = 0;
+ var r = "";
+
+ do {
+ c1 = s.charCodeAt(i);
+ e1 = c1 >> 2;
+ c2 = s.charCodeAt(i+1);
+ e2 = ((c1 & 3) << 4) | (c2 >> 4);
+ c3 = s.charCodeAt(i+2);
+ if (l < i+2) { e3 = 64; } else { e3 = ((c2 & 0xf) << 2) | (c3 >> 6); }
+ if (l < i+3) { e4 = 64; } else { e4 = c3 & 0x3f; }
+ r+= ch.charAt(e1) + ch.charAt(e2) + ch.charAt(e3) + ch.charAt(e4);
+ } while ((i+= 3) < l);
+ return r;
+ };
+
+ // output a PNG string
+ this.getDump = function() {
+
+ // compute adler32 of output pixels + row filter bytes
+ var BASE = 65521; /* largest prime smaller than 65536 */
+ var NMAX = 5552; /* NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1 */
+ var s1 = 1;
+ var s2 = 0;
+ var n = NMAX;
+
+ for (var y = 0; y < this.height; y++) {
+ for (var x = -1; x < this.width; x++) {
+ s1+= this.buffer[this.index(x, y)].charCodeAt(0);
+ s2+= s1;
+ if ((n-= 1) == 0) {
+ s1%= BASE;
+ s2%= BASE;
+ n = NMAX;
+ }
+ }
+ }
+ s1%= BASE;
+ s2%= BASE;
+ write(this.buffer, this.idat_offs + this.idat_size - 8, byte4((s2 << 16) | s1));
+
+ // compute crc32 of the PNG chunks
+ function crc32(png, offs, size) {
+ var crc = -1;
+ for (var i = 4; i < size-4; i += 1) {
+ crc = _crc32[(crc ^ png[offs+i].charCodeAt(0)) & 0xff] ^ ((crc >> 8) & 0x00ffffff);
+ }
+ write(png, offs+size-4, byte4(crc ^ -1));
+ }
+
+ crc32(this.buffer, this.ihdr_offs, this.ihdr_size);
+ crc32(this.buffer, this.plte_offs, this.plte_size);
+ crc32(this.buffer, this.trns_offs, this.trns_size);
+ crc32(this.buffer, this.idat_offs, this.idat_size);
+ crc32(this.buffer, this.iend_offs, this.iend_size);
+
+ // convert PNG to string
+ return "\x89PNG\r\n\x1A\n"+this.buffer.join('');
+ };
+
+ this.fillRect = function (x, y, w, h, color) {
+ for(var i = 0; i < w; i++) {
+ for (var j = 0; j < h; j++) {
+ this.buffer[this.index(x+i, y+j)] = color;
+ }
+ }
+ };
+ };
+
+// https://stackoverflow.com/questions/2353211/hsl-to-rgb-color-conversion
+ /**
+ * Converts an HSL color value to RGB. Conversion formula
+ * adapted from http://en.wikipedia.org/wiki/HSL_color_space.
+ * Assumes h, s, and l are contained in the set [0, 1] and
+ * returns r, g, and b in the set [0, 255].
+ *
+ * @param {number} h The hue
+ * @param {number} s The saturation
+ * @param {number} l The lightness
+ * @return {Array} The RGB representation
+ */
+
+ function hue2rgb(p, q, t) {
+ if(t < 0) t += 1;
+ if(t > 1) t -= 1;
+ if(t < 1/6) return p + (q - p) * 6 * t;
+ if(t < 1/2) return q;
+ if(t < 2/3) return p + (q - p) * (2/3 - t) * 6;
+ return p;
+ }
+
+ function hsl2rgb(h, s, l){
+ var r, g, b;
+
+ if(s == 0){
+ r = g = b = l; // achromatic
+ }else{
+ var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
+ var p = 2 * l - q;
+ r = hue2rgb(p, q, h + 1/3);
+ g = hue2rgb(p, q, h);
+ b = hue2rgb(p, q, h - 1/3);
+ }
+
+ return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255), 255];
+ }
+
+// The random number is a js implementation of the Xorshift PRNG
+ var randseed = new Array(4); // Xorshift: [x, y, z, w] 32 bit values
+
+ function seedrand(seed) {
+ for (var i = 0; i < randseed.length; i++) {
+ randseed[i] = 0;
+ }
+ for (var i = 0; i < seed.length; i++) {
+ randseed[i % 4] = (randseed[i % 4] << 5) - randseed[i % 4] + seed.charCodeAt(i);
+ }
+ }
+
+ function rand() {
+ // based on Java's String.hashCode(), expanded to 4 32bit values
+ var t = randseed[0] ^ (randseed[0] << 11);
+
+ randseed[0] = randseed[1];
+ randseed[1] = randseed[2];
+ randseed[2] = randseed[3];
+ randseed[3] = randseed[3] ^ (randseed[3] >> 19) ^ t ^ (t >> 8);
+
+ return (randseed[3] >>> 0) / (1 << 31 >>> 0);
+ }
+
+ function createColor() {
+ //saturation is the whole color spectrum
+ var h = Math.floor(rand() * 360);
+ //saturation goes from 40 to 100, it avoids greyish colors
+ var s = rand() * 60 + 40;
+ //lightness can be anything from 0 to 100, but probabilities are a bell curve around 50%
+ var l = (rand() + rand() + rand() + rand()) * 25;
+
+ return [h / 360,s / 100,l / 100];
+ }
+
+ function createImageData(size) {
+ var width = size; // Only support square icons for now
+ var height = size;
+
+ var dataWidth = Math.ceil(width / 2);
+ var mirrorWidth = width - dataWidth;
+
+ var data = [];
+ for (var y = 0; y < height; y++) {
+ var row = [];
+ for (var x = 0; x < dataWidth; x++) {
+ // this makes foreground and background color to have a 43% (1/2.3) probability
+ // spot color has 13% chance
+ row[x] = Math.floor(rand() * 2.3);
+ }
+ var r = row.slice(0, mirrorWidth);
+ r.reverse();
+ row = row.concat(r);
+
+ for (var i = 0; i < row.length; i++) {
+ data.push(row[i]);
+ }
+ }
+
+ return data;
+ }
+
+ function buildOpts(opts) {
+ if (!opts.seed) {
+ throw 'No seed provided'
+ }
+
+ seedrand(opts.seed);
+
+ return Object.assign({
+ size: 8,
+ scale: 16,
+ color: createColor(),
+ bgcolor: createColor(),
+ spotcolor: createColor(),
+ }, opts)
+ }
+
+ function toDataUrl(address) {
+ const opts = buildOpts({seed: address.toLowerCase()});
+
+ const imageData = createImageData(opts.size);
+ const width = Math.sqrt(imageData.length);
+
+ const p = new PNG(opts.size*opts.scale, opts.size*opts.scale, 3);
+ const bgcolor = p.color(...hsl2rgb(...opts.bgcolor));
+ const color = p.color(...hsl2rgb(...opts.color));
+ const spotcolor = p.color(...hsl2rgb(...opts.spotcolor));
+
+ for (var i = 0; i < imageData.length; i++) {
+ var row = Math.floor(i / width);
+ var col = i % width;
+ // if data is 0, leave the background
+ if (imageData[i]) {
+ // if data is 2, choose spot color, if 1 choose foreground
+ const pngColor = imageData[i] == 1 ? color : spotcolor;
+ p.fillRect(col * opts.scale, row * opts.scale, opts.scale, opts.scale, pngColor);
+ }
+ }
+ return `data:image/png;base64,${p.getBase64()}`;
+ }
+
+ exports.toDataUrl = toDataUrl;
+
+ Object.defineProperty(exports, '__esModule', { value: true });
+
+})));