diff options
Diffstat (limited to 'ui')
-rw-r--r-- | ui/app/actions.js | 13 | ||||
-rw-r--r-- | ui/app/add-token.js | 31 | ||||
-rw-r--r-- | ui/app/app.js | 2 | ||||
-rw-r--r-- | ui/app/components/account-export.js | 28 | ||||
-rw-r--r-- | ui/app/components/dropdowns/components/account-dropdowns.js | 37 | ||||
-rw-r--r-- | ui/app/components/network.js | 11 | ||||
-rw-r--r-- | ui/app/components/pending-msg-details.js | 2 | ||||
-rw-r--r-- | ui/app/components/pending-msg.js | 18 | ||||
-rw-r--r-- | ui/app/components/pending-tx.js | 90 | ||||
-rw-r--r-- | ui/app/components/token-list.js | 23 | ||||
-rw-r--r-- | ui/app/components/transaction-list-item.js | 11 | ||||
-rw-r--r-- | ui/app/conf-tx.js | 7 | ||||
-rw-r--r-- | ui/app/config.js | 7 | ||||
-rw-r--r-- | ui/app/css/itcss/tools/utilities.scss | 2 | ||||
-rw-r--r-- | ui/app/info.js | 2 | ||||
-rw-r--r-- | ui/app/keychains/hd/create-vault-complete.js | 10 | ||||
-rw-r--r-- | ui/app/reducers.js | 5 | ||||
-rw-r--r-- | ui/app/unlock.js | 2 | ||||
-rw-r--r-- | ui/app/util.js | 16 | ||||
-rw-r--r-- | ui/lib/account-link.js | 10 |
20 files changed, 278 insertions, 49 deletions
diff --git a/ui/app/actions.js b/ui/app/actions.js index 47da70277..678c68a6a 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -126,6 +126,7 @@ var actions = { txError: txError, nextTx: nextTx, previousTx: previousTx, + cancelAllTx: cancelAllTx, viewPendingTx: viewPendingTx, VIEW_PENDING_TX: 'VIEW_PENDING_TX', // app messages @@ -420,6 +421,7 @@ function signPersonalMsg (msgData) { function signTx (txData) { return (dispatch) => { + dispatch(actions.showLoadingIndication()) global.ethQuery.sendTransaction(txData, (err, data) => { dispatch(actions.hideLoadingIndication()) if (err) return dispatch(actions.displayWarning(err.message)) @@ -464,6 +466,7 @@ function updateAndApproveTx (txData) { dispatch(actions.hideLoadingIndication()) if (err) { dispatch(actions.txError(err)) + dispatch(actions.goHome()) return log.error(err.message) } dispatch(actions.completedTx(txData.id)) @@ -506,6 +509,16 @@ function cancelTx (txData) { } } +function cancelAllTx (txsData) { + return (dispatch) => { + txsData.forEach((txData, i) => { + background.cancelTransaction(txData.id, () => { + dispatch(actions.completedTx(txData.id)) + i === txsData.length - 1 ? dispatch(actions.goHome()) : null + }) + }) + } +} // // initialize screen // diff --git a/ui/app/add-token.js b/ui/app/add-token.js index 5c6dea4a0..4374ee586 100644 --- a/ui/app/add-token.js +++ b/ui/app/add-token.js @@ -3,6 +3,8 @@ const Component = require('react').Component const h = require('react-hyperscript') const connect = require('react-redux').connect const actions = require('./actions') +const Tooltip = require('./components/tooltip.js') + const ethUtil = require('ethereumjs-util') const abi = require('human-standard-token-abi') @@ -15,6 +17,7 @@ module.exports = connect(mapStateToProps)(AddTokenScreen) function mapStateToProps (state) { return { + identities: state.metamask.identities, } } @@ -64,15 +67,25 @@ AddTokenScreen.prototype.render = function () { }, [ h('div', [ - h('span', { - style: { fontWeight: 'bold', paddingRight: '10px'}, - }, 'Token Address'), + h(Tooltip, { + position: 'top', + title: 'The contract of the actual token contract. Click for more info.', + }, [ + h('a', { + style: { fontWeight: 'bold', paddingRight: '10px'}, + href: 'https://consensyssupport.happyfox.com/staff/kb/article/24-what-is-a-token-contract-address', + target: '_blank', + }, [ + h('span', 'Token Contract Address '), + h('i.fa.fa-question-circle'), + ]), + ]), ]), h('section.flex-row.flex-center', [ h('input#token-address', { name: 'address', - placeholder: 'Token Address', + placeholder: 'Token Contract Address', onChange: this.tokenAddressDidChange.bind(this), style: { width: 'inherit', @@ -171,7 +184,9 @@ AddTokenScreen.prototype.tokenAddressDidChange = function (event) { AddTokenScreen.prototype.validateInputs = function () { let msg = '' const state = this.state + const identitiesList = Object.keys(this.props.identities) const { address, symbol, decimals } = state + const standardAddress = ethUtil.addHexPrefix(address).toLowerCase() const validAddress = ethUtil.isValidAddress(address) if (!validAddress) { @@ -189,7 +204,12 @@ AddTokenScreen.prototype.validateInputs = function () { msg += 'Symbol must be between 0 and 10 characters.' } - const isValid = validAddress && validDecimals + const ownAddress = identitiesList.includes(standardAddress) + if (ownAddress) { + msg = 'Personal address detected. Input the token contract address.' + } + + const isValid = validAddress && validDecimals && !ownAddress if (!isValid) { this.setState({ @@ -215,4 +235,3 @@ AddTokenScreen.prototype.attemptToAutoFillTokenParams = async function (address) this.setState({ symbol: symbol[0], decimals: decimals[0].toString() }) } } - diff --git a/ui/app/app.js b/ui/app/app.js index 1ca59e406..14e6a26e2 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -46,6 +46,7 @@ function mapStateToProps (state) { identities, accounts, address, + keyrings, } = state.metamask const selected = address || Object.keys(accounts)[0] @@ -75,6 +76,7 @@ function mapStateToProps (state) { // state needed to get account dropdown temporarily rendering from app bar identities, selected, + keyrings, } } diff --git a/ui/app/components/account-export.js b/ui/app/components/account-export.js index 330f73805..32b103c86 100644 --- a/ui/app/components/account-export.js +++ b/ui/app/components/account-export.js @@ -1,6 +1,7 @@ const Component = require('react').Component const h = require('react-hyperscript') const inherits = require('util').inherits +const exportAsFile = require('../util').exportAsFile const copyToClipboard = require('copy-to-clipboard') const actions = require('../actions') const ethUtil = require('ethereumjs-util') @@ -20,20 +21,21 @@ function mapStateToProps (state) { } ExportAccountView.prototype.render = function () { - var state = this.props - var accountDetail = state.accountDetail + const state = this.props + const accountDetail = state.accountDetail + const nickname = state.identities[state.address].name if (!accountDetail) return h('div') - var accountExport = accountDetail.accountExport + const accountExport = accountDetail.accountExport - var notExporting = accountExport === 'none' - var exportRequested = accountExport === 'requested' - var accountExported = accountExport === 'completed' + const notExporting = accountExport === 'none' + const exportRequested = accountExport === 'requested' + const accountExported = accountExport === 'completed' if (notExporting) return h('div') if (exportRequested) { - var warning = `Export private keys at your own risk.` + const warning = `Export private keys at your own risk.` return ( h('div', { style: { @@ -89,6 +91,8 @@ ExportAccountView.prototype.render = function () { } if (accountExported) { + const plainKey = ethUtil.stripHexPrefix(accountDetail.privateKey) + return h('div.privateKey', { style: { margin: '0 20px', @@ -105,10 +109,16 @@ ExportAccountView.prototype.render = function () { onClick: function (event) { copyToClipboard(ethUtil.stripHexPrefix(accountDetail.privateKey)) }, - }, ethUtil.stripHexPrefix(accountDetail.privateKey)), + }, plainKey), h('button', { onClick: () => this.props.dispatch(actions.backToAccountDetail(this.props.address)), }, 'Done'), + h('button', { + style: { + marginLeft: '10px', + }, + onClick: () => exportAsFile(`MetaMask ${nickname} Private Key`, plainKey), + }, 'Save as File'), ]) } } @@ -117,6 +127,6 @@ ExportAccountView.prototype.onExportKeyPress = function (event) { if (event.key !== 'Enter') return event.preventDefault() - var input = document.getElementById('exportAccount').value + const input = document.getElementById('exportAccount').value this.props.dispatch(actions.exportAccount(input, this.props.address)) } diff --git a/ui/app/components/dropdowns/components/account-dropdowns.js b/ui/app/components/dropdowns/components/account-dropdowns.js index bb112dcca..fe80af8b3 100644 --- a/ui/app/components/dropdowns/components/account-dropdowns.js +++ b/ui/app/components/dropdowns/components/account-dropdowns.js @@ -25,7 +25,7 @@ class AccountDropdowns extends Component { } renderAccounts () { - const { identities, accounts, selected, menuItemStyles, actions } = this.props + const { identities, accounts, selected, menuItemStyles, actions, keyrings } = this.props return Object.keys(identities).map((key, index) => { const identity = identities[key] @@ -33,6 +33,12 @@ class AccountDropdowns extends Component { const balanceValue = accounts[key].balance const formattedBalance = balanceValue ? formatBalance(balanceValue, 6) : '...' + const simpleAddress = identity.address.substring(2).toLowerCase() + + const keyring = keyrings.find((kr) => { + return kr.accounts.includes(simpleAddress) || + kr.accounts.includes(identity.address) + }) return h( DropdownMenuItem, @@ -88,6 +94,7 @@ class AccountDropdowns extends Component { marginLeft: '10px', }, }, [ + this.indicateIfLoose(keyring), h('span.account-dropdown-name', { style: { fontSize: '18px', @@ -97,6 +104,7 @@ class AccountDropdowns extends Component { textOverflow: 'ellipsis', }, }, identity.name || ''), + h('span', { style: { marginLeft: '20px', fontSize: '24px' } }, isSelected ? h('.check', '✓') : null), h('span.account-dropdown-balance', { style: { @@ -125,11 +133,35 @@ class AccountDropdowns extends Component { ]), ]), +// ======= +// }, +// ), +// this.indicateIfLoose(keyring), +// h('span', { +// style: { +// marginLeft: '20px', +// fontSize: '24px', +// maxWidth: '145px', +// whiteSpace: 'nowrap', +// overflow: 'hidden', +// textOverflow: 'ellipsis', +// }, +// }, identity.name || ''), +// h('span', { style: { marginLeft: '20px', fontSize: '24px' } }, isSelected ? h('.check', '✓') : null), +// >>>>>>> master:ui/app/components/account-dropdowns.js ] ) }) } + indicateIfLoose (keyring) { + try { // Sometimes keyrings aren't loaded yet: + const type = keyring.type + const isLoose = type !== 'HD Key Tree' + return isLoose ? h('.keyring-label', 'LOOSE') : null + } catch (e) { return } + } + renderAccountSelector () { const { actions, useCssTransition, innerStyle } = this.props const { accountSelectorActive, menuItemStyles } = this.state @@ -389,7 +421,8 @@ AccountDropdowns.defaultProps = { AccountDropdowns.propTypes = { identities: PropTypes.objectOf(PropTypes.object), - selected: PropTypes.string, // TODO: refactor to be more explicit: selectedAddress + selected: PropTypes.string, + keyrings: PropTypes.array, } const mapDispatchToProps = (dispatch) => { diff --git a/ui/app/components/network.js b/ui/app/components/network.js index 9133c78e3..8424a479a 100644 --- a/ui/app/components/network.js +++ b/ui/app/components/network.js @@ -23,7 +23,7 @@ Network.prototype.render = function () { let iconName, hoverText if (networkNumber === 'loading') { - return h('span', { + return h('span.pointer', { style: { display: 'flex', alignItems: 'center', @@ -38,7 +38,7 @@ Network.prototype.render = function () { }, src: 'images/loading.svg', }), - h('i.fa.fa-sort-desc'), + h('i.fa.fa-caret-down'), ]) } else if (providerName === 'mainnet') { hoverText = 'Main Ethereum Network' @@ -77,7 +77,8 @@ Network.prototype.render = function () { style: { color: '#039396', }}, - 'Ethereum Main Net'), + 'Main Network'), + h('i.fa.fa-caret-down.fa-lg'), ]) case 'ropsten-test-network': return h('.network-indicator', [ @@ -90,6 +91,7 @@ Network.prototype.render = function () { color: '#ff6666', }}, 'Ropsten Test Net'), + h('i.fa.fa-caret-down.fa-lg'), ]) case 'kovan-test-network': return h('.network-indicator', [ @@ -102,6 +104,7 @@ Network.prototype.render = function () { color: '#690496', }}, 'Kovan Test Net'), + h('i.fa.fa-caret-down.fa-lg'), ]) case 'rinkeby-test-network': return h('.network-indicator', [ @@ -114,6 +117,7 @@ Network.prototype.render = function () { color: '#e7a218', }}, 'Rinkeby Test Net'), + h('i.fa.fa-caret-down.fa-lg'), ]) default: return h('.network-indicator', [ @@ -129,6 +133,7 @@ Network.prototype.render = function () { color: '#AEAEAE', }}, 'Private Network'), + h('i.fa.fa-caret-down.fa-lg'), ]) } })(), diff --git a/ui/app/components/pending-msg-details.js b/ui/app/components/pending-msg-details.js index 16308d121..718a22de0 100644 --- a/ui/app/components/pending-msg-details.js +++ b/ui/app/components/pending-msg-details.js @@ -38,7 +38,7 @@ PendingMsgDetails.prototype.render = function () { // message data h('.tx-data.flex-column.flex-justify-center.flex-grow.select-none', [ - h('.flex-row.flex-space-between', [ + h('.flex-column.flex-space-between', [ h('label.font-small', 'MESSAGE'), h('span.font-small', msgParams.data), ]), diff --git a/ui/app/components/pending-msg.js b/ui/app/components/pending-msg.js index b2cac164a..834719c53 100644 --- a/ui/app/components/pending-msg.js +++ b/ui/app/components/pending-msg.js @@ -18,6 +18,9 @@ PendingMsg.prototype.render = function () { h('div', { key: msgData.id, + style: { + maxWidth: '350px', + }, }, [ // header @@ -32,10 +35,21 @@ PendingMsg.prototype.render = function () { style: { margin: '10px', }, - }, `Signing this message can have + }, [ + `Signing this message can have dangerous side effects. Only sign messages from sites you fully trust with your entire account. - This will be fixed in a future version.`), + This dangerous method will be removed in a future version. `, + h('a', { + href: 'https://medium.com/metamask/the-new-secure-way-to-sign-data-in-your-browser-6af9dd2a1527', + style: { color: 'rgb(247, 134, 28)' }, + onClick: (event) => { + event.preventDefault() + const url = 'https://medium.com/metamask/the-new-secure-way-to-sign-data-in-your-browser-6af9dd2a1527' + global.platform.openWindow({ url }) + }, + }, 'Read more here.'), + ]), // message details h(PendingTxDetails, state), diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index c1b079a25..a679107c9 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -240,6 +240,15 @@ PendingTx.prototype.render = function () { totalInETH, } = this.getData() + // This is from the latest master + // It handles some of the errors that we are not currently handling + // Leaving as comments fo reference + + // const balanceBn = hexToBn(balance) + // const insufficientBalance = balanceBn.lt(maxCost) + // const buyDisabled = insufficientBalance || !this.state.valid || !isValidAddress || this.state.submitting + // const showRejectAll = props.unconfTxListLength > 1 + this.inputs = [] return ( @@ -332,9 +341,88 @@ PendingTx.prototype.render = function () { h('div.confirm-screen-row-info', `$${totalInUSD} USD`), h('div.confirm-screen-row-detail', `${totalInETH} ETH`), ]), - ]), + ]), ]), +// These are latest errors handling from master +// Leaving as comments as reference when we start implementing error handling +// h('style', ` +// .conf-buttons button { +// margin-left: 10px; +// text-transform: uppercase; +// } +// `), + +// txMeta.simulationFails ? +// h('.error', { +// style: { +// marginLeft: 50, +// fontSize: '0.9em', +// }, +// }, 'Transaction Error. Exception thrown in contract code.') +// : null, + +// !isValidAddress ? +// h('.error', { +// style: { +// marginLeft: 50, +// fontSize: '0.9em', +// }, +// }, 'Recipient address is invalid. Sending this transaction will result in a loss of ETH.') +// : null, + +// insufficientBalance ? +// h('span.error', { +// style: { +// marginLeft: 50, +// fontSize: '0.9em', +// }, +// }, 'Insufficient balance for transaction') +// : null, + +// // send + cancel +// h('.flex-row.flex-space-around.conf-buttons', { +// style: { +// display: 'flex', +// justifyContent: 'flex-end', +// margin: '14px 25px', +// }, +// }, [ +// h('button', { +// onClick: (event) => { +// this.resetGasFields() +// event.preventDefault() +// }, +// }, 'Reset'), + +// // Accept Button or Buy Button +// insufficientBalance ? h('button.btn-green', { onClick: props.buyEth }, 'Buy Ether') : +// h('input.confirm.btn-green', { +// type: 'submit', +// value: 'SUBMIT', +// style: { marginLeft: '10px' }, +// disabled: buyDisabled, +// }), + +// h('button.cancel.btn-red', { +// onClick: props.cancelTransaction, +// }, 'Reject'), +// ]), +// showRejectAll ? h('.flex-row.flex-space-around.conf-buttons', { +// style: { +// display: 'flex', +// justifyContent: 'flex-end', +// margin: '14px 25px', +// }, +// }, [ +// h('button.cancel.btn-red', { +// onClick: props.cancelAllTransactions, +// }, 'Reject All'), +// ]) : null, +// ]), +// ]) +// ) +// } ]), h('form#pending-tx-form.flex-column.flex-center', { diff --git a/ui/app/components/token-list.js b/ui/app/components/token-list.js index 2d1dd0ea7..0efa89c63 100644 --- a/ui/app/components/token-list.js +++ b/ui/app/components/token-list.js @@ -48,10 +48,28 @@ TokenList.prototype.render = function () { if (error) { log.error(error) - return this.message('There was a problem loading your token balances.') + return h('.hotFix', { + style: { + padding: '80px', + }, + }, [ + 'We had trouble loading your token balances. You can view them ', + h('span.hotFix', { + style: { + color: 'rgba(247, 134, 28, 1)', + cursor: 'pointer', + }, + onClick: () => { + global.platform.openWindow({ + url: `https://ethplorer.io/address/${userAddress}`, + }) + }, + }, 'here'), + ]) } return h('div', tokens.map((tokenData) => h(TokenCell, tokenData))) + } TokenList.prototype.message = function (body) { @@ -84,7 +102,7 @@ TokenList.prototype.createFreshTokenTracker = function () { this.tracker = new TokenTracker({ userAddress, provider: global.ethereumProvider, - tokens: uniqueMergeTokens(defaultTokens, this.props.tokens), + tokens: this.props.tokens, pollingInterval: 8000, }) @@ -149,4 +167,3 @@ function uniqueMergeTokens (tokensA, tokensB = []) { }) return result } - diff --git a/ui/app/components/transaction-list-item.js b/ui/app/components/transaction-list-item.js index eca2a7100..880a288af 100644 --- a/ui/app/components/transaction-list-item.js +++ b/ui/app/components/transaction-list-item.js @@ -60,16 +60,7 @@ TransactionListItem.prototype.render = function () { }, [ h('.identicon-wrapper.flex-column.flex-center.select-none', [ - h('.pop-hover', { - onClick: (event) => { - event.stopPropagation() - if (!isTx || isPending) return - var url = `https://metamask.github.io/eth-tx-viz/?tx=${transaction.hash}` - global.platform.openWindow({ url }) - }, - }, [ - h(TransactionIcon, { txParams, transaction, isTx, isMsg }), - ]), + h(TransactionIcon, { txParams, transaction, isTx, isMsg }), ]), h(Tooltip, { diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js index 7cc319509..7062eee6b 100644 --- a/ui/app/conf-tx.js +++ b/ui/app/conf-tx.js @@ -76,6 +76,7 @@ ConfirmTxScreen.prototype.render = function () { cancelMessage: this.cancelMessage.bind(this, txData), cancelPersonalMessage: this.cancelPersonalMessage.bind(this, txData), }) + } function currentTxView (opts) { @@ -116,6 +117,12 @@ ConfirmTxScreen.prototype.cancelTransaction = function (txData, event) { this.props.dispatch(actions.cancelTx(txData)) } +ConfirmTxScreen.prototype.cancelAllTransactions = function (unconfTxList, event) { + this.stopPropagation(event) + event.preventDefault() + this.props.dispatch(actions.cancelAllTx(unconfTxList)) +} + ConfirmTxScreen.prototype.signMessage = function (msgData, event) { log.info('conf-tx.js: signing message') var params = msgData.msgParams diff --git a/ui/app/config.js b/ui/app/config.js index 62785c49b..d64088ccb 100644 --- a/ui/app/config.js +++ b/ui/app/config.js @@ -5,7 +5,8 @@ const connect = require('react-redux').connect const actions = require('./actions') const currencies = require('./conversion.json').rows const validUrl = require('valid-url') -const copyToClipboard = require('copy-to-clipboard') +const exportAsFile = require('./util').exportAsFile + module.exports = connect(mapStateToProps)(ConfigScreen) @@ -110,9 +111,9 @@ ConfigScreen.prototype.render = function () { alignSelf: 'center', }, onClick (event) { - copyToClipboard(window.logState()) + exportAsFile('MetaMask State Logs', window.logState()) }, - }, 'Copy State Logs'), + }, 'Download State Logs'), ]), h('hr.horizontal-line'), diff --git a/ui/app/css/itcss/tools/utilities.scss b/ui/app/css/itcss/tools/utilities.scss index b9c99219b..9f1caa732 100644 --- a/ui/app/css/itcss/tools/utilities.scss +++ b/ui/app/css/itcss/tools/utilities.scss @@ -238,7 +238,7 @@ hr.horizontal-line { border-radius: 10px; height: 20px; min-width: 20px; - position: relative; + position: absolute; display: flex; align-items: center; justify-content: center; diff --git a/ui/app/info.js b/ui/app/info.js index 899841c83..4c7d4cb4c 100644 --- a/ui/app/info.js +++ b/ui/app/info.js @@ -103,7 +103,7 @@ InfoScreen.prototype.render = function () { [ h('div.fa.fa-support', [ h('a.info', { - href: 'http://metamask.consensyssupport.happyfox.com', + href: 'https://support.metamask.io', target: '_blank', }, 'Visit our Support Center'), ]), diff --git a/ui/app/keychains/hd/create-vault-complete.js b/ui/app/keychains/hd/create-vault-complete.js index c32751fff..745990351 100644 --- a/ui/app/keychains/hd/create-vault-complete.js +++ b/ui/app/keychains/hd/create-vault-complete.js @@ -3,6 +3,7 @@ const Component = require('react').Component const connect = require('react-redux').connect const h = require('react-hyperscript') const actions = require('../../actions') +const exportAsFile = require('../../util').exportAsFile module.exports = connect(mapStateToProps)(CreateVaultCompleteScreen) @@ -65,8 +66,17 @@ CreateVaultCompleteScreen.prototype.render = function () { style: { margin: '24px', fontSize: '0.9em', + marginBottom: '10px', }, }, 'I\'ve copied it somewhere safe'), + + h('button.primary', { + onClick: () => exportAsFile(`MetaMask Seed Words`, seed), + style: { + margin: '10px', + fontSize: '0.9em', + }, + }, 'Save Seed Words As File'), ]) ) } diff --git a/ui/app/reducers.js b/ui/app/reducers.js index 36045772f..6a2f44534 100644 --- a/ui/app/reducers.js +++ b/ui/app/reducers.js @@ -42,7 +42,10 @@ function rootReducer (state, action) { } window.logState = function () { - var stateString = JSON.stringify(window.METAMASK_CACHED_LOG_STATE, removeSeedWords, 2) + let state = window.METAMASK_CACHED_LOG_STATE + const version = global.platform.getVersion() + state.version = version + let stateString = JSON.stringify(state, removeSeedWords, 2) return stateString } diff --git a/ui/app/unlock.js b/ui/app/unlock.js index 1918e2e6a..ec97b03bf 100644 --- a/ui/app/unlock.js +++ b/ui/app/unlock.js @@ -80,7 +80,7 @@ UnlockScreen.prototype.render = function () { color: 'rgb(247, 134, 28)', textDecoration: 'underline', }, - }, 'I forgot my password.'), + }, 'Restore from seed phrase'), ]), ]) ) diff --git a/ui/app/util.js b/ui/app/util.js index 6596ebafb..be26e15a5 100644 --- a/ui/app/util.js +++ b/ui/app/util.js @@ -53,6 +53,7 @@ module.exports = { getTxFeeBn, shortenBalance, getContractAtAddress, + exportAsFile: exportAsFile, } function valuesFor (obj) { @@ -250,3 +251,18 @@ function getTxFeeBn (gas, gasPrice = MIN_GAS_PRICE_BN.toString(16), blockGasLimi function getContractAtAddress (tokenAddress) { return global.eth.contract(abi).at(tokenAddress) } + +function exportAsFile (filename, data) { + // source: https://stackoverflow.com/a/33542499 by Ludovic Feltz + const blob = new Blob([data], {type: 'text/csv'}) + if (window.navigator.msSaveOrOpenBlob) { + window.navigator.msSaveBlob(blob, filename) + } else { + const elem = window.document.createElement('a') + elem.href = window.URL.createObjectURL(blob) + elem.download = filename + document.body.appendChild(elem) + elem.click() + document.body.removeChild(elem) + } +} diff --git a/ui/lib/account-link.js b/ui/lib/account-link.js index d061d0ad1..037d990fa 100644 --- a/ui/lib/account-link.js +++ b/ui/lib/account-link.js @@ -3,19 +3,19 @@ module.exports = function (address, network) { let link switch (net) { case 1: // main net - link = `http://etherscan.io/address/${address}` + link = `https://etherscan.io/address/${address}` break case 2: // morden test net - link = `http://morden.etherscan.io/address/${address}` + link = `https://morden.etherscan.io/address/${address}` break case 3: // ropsten test net - link = `http://ropsten.etherscan.io/address/${address}` + link = `https://ropsten.etherscan.io/address/${address}` break case 4: // rinkeby test net - link = `http://rinkeby.etherscan.io/address/${address}` + link = `https://rinkeby.etherscan.io/address/${address}` break case 42: // kovan test net - link = `http://kovan.etherscan.io/address/${address}` + link = `https://kovan.etherscan.io/address/${address}` break default: link = '' |