From 86d367957fe8ac04462f716fe0ba2bfa4e5ff3f6 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 20 Jul 2017 12:38:38 -0700 Subject: Move responsive ui into its own folder for easier merges --- ui/lib/account-link.js | 26 ++++++++++++++ ui/lib/contract-namer.js | 33 +++++++++++++++++ ui/lib/etherscan-prefix-for-network.js | 21 +++++++++++ ui/lib/explorer-link.js | 6 ++++ ui/lib/icon-factory.js | 65 ++++++++++++++++++++++++++++++++++ ui/lib/lost-accounts-notice.js | 23 ++++++++++++ ui/lib/persistent-form.js | 61 +++++++++++++++++++++++++++++++ ui/lib/tx-helper.js | 17 +++++++++ 8 files changed, 252 insertions(+) create mode 100644 ui/lib/account-link.js create mode 100644 ui/lib/contract-namer.js create mode 100644 ui/lib/etherscan-prefix-for-network.js create mode 100644 ui/lib/explorer-link.js create mode 100644 ui/lib/icon-factory.js create mode 100644 ui/lib/lost-accounts-notice.js create mode 100644 ui/lib/persistent-form.js create mode 100644 ui/lib/tx-helper.js (limited to 'ui/lib') diff --git a/ui/lib/account-link.js b/ui/lib/account-link.js new file mode 100644 index 000000000..d061d0ad1 --- /dev/null +++ b/ui/lib/account-link.js @@ -0,0 +1,26 @@ +module.exports = function (address, network) { + const net = parseInt(network) + let link + switch (net) { + case 1: // main net + link = `http://etherscan.io/address/${address}` + break + case 2: // morden test net + link = `http://morden.etherscan.io/address/${address}` + break + case 3: // ropsten test net + link = `http://ropsten.etherscan.io/address/${address}` + break + case 4: // rinkeby test net + link = `http://rinkeby.etherscan.io/address/${address}` + break + case 42: // kovan test net + link = `http://kovan.etherscan.io/address/${address}` + break + default: + link = '' + break + } + + return link +} diff --git a/ui/lib/contract-namer.js b/ui/lib/contract-namer.js new file mode 100644 index 000000000..f05e770cc --- /dev/null +++ b/ui/lib/contract-namer.js @@ -0,0 +1,33 @@ +/* CONTRACT NAMER + * + * Takes an address, + * Returns a nicname if we have one stored, + * otherwise returns null. + */ + +const contractMap = require('eth-contract-metadata') +const ethUtil = require('ethereumjs-util') + +module.exports = function (addr, identities = {}) { + const checksummed = ethUtil.toChecksumAddress(addr) + if (contractMap[checksummed] && contractMap[checksummed].name) { + return contractMap[checksummed].name + } + + const address = addr.toLowerCase() + const ids = hashFromIdentities(identities) + return addrFromHash(address, ids) +} + +function hashFromIdentities (identities) { + const result = {} + for (const key in identities) { + result[key] = identities[key].name + } + return result +} + +function addrFromHash (addr, hash) { + const address = addr.toLowerCase() + return hash[address] || null +} diff --git a/ui/lib/etherscan-prefix-for-network.js b/ui/lib/etherscan-prefix-for-network.js new file mode 100644 index 000000000..2c1904f1c --- /dev/null +++ b/ui/lib/etherscan-prefix-for-network.js @@ -0,0 +1,21 @@ +module.exports = function (network) { + const net = parseInt(network) + let prefix + switch (net) { + case 1: // main net + prefix = '' + break + case 3: // ropsten test net + prefix = 'ropsten.' + break + case 4: // rinkeby test net + prefix = 'rinkeby.' + break + case 42: // kovan test net + prefix = 'kovan.' + break + default: + prefix = '' + } + return prefix +} diff --git a/ui/lib/explorer-link.js b/ui/lib/explorer-link.js new file mode 100644 index 000000000..3b82ecd5f --- /dev/null +++ b/ui/lib/explorer-link.js @@ -0,0 +1,6 @@ +const prefixForNetwork = require('./etherscan-prefix-for-network') + +module.exports = function (hash, network) { + const prefix = prefixForNetwork(network) + return `http://${prefix}etherscan.io/tx/${hash}` +} diff --git a/ui/lib/icon-factory.js b/ui/lib/icon-factory.js new file mode 100644 index 000000000..27a74de66 --- /dev/null +++ b/ui/lib/icon-factory.js @@ -0,0 +1,65 @@ +var iconFactory +const isValidAddress = require('ethereumjs-util').isValidAddress +const toChecksumAddress = require('ethereumjs-util').toChecksumAddress +const contractMap = require('eth-contract-metadata') + +module.exports = function (jazzicon) { + if (!iconFactory) { + iconFactory = new IconFactory(jazzicon) + } + return iconFactory +} + +function IconFactory (jazzicon) { + this.jazzicon = jazzicon + this.cache = {} +} + +IconFactory.prototype.iconForAddress = function (address, diameter) { + const addr = toChecksumAddress(address) + if (iconExistsFor(addr)) { + return imageElFor(addr) + } + + return this.generateIdenticonSvg(address, diameter) +} + +// returns svg dom element +IconFactory.prototype.generateIdenticonSvg = function (address, diameter) { + var cacheId = `${address}:${diameter}` + // check cache, lazily generate and populate cache + var identicon = this.cache[cacheId] || (this.cache[cacheId] = this.generateNewIdenticon(address, diameter)) + // create a clean copy so you can modify it + var cleanCopy = identicon.cloneNode(true) + return cleanCopy +} + +// creates a new identicon +IconFactory.prototype.generateNewIdenticon = function (address, diameter) { + var numericRepresentation = jsNumberForAddress(address) + var identicon = this.jazzicon(diameter, numericRepresentation) + return identicon +} + +// util + +function iconExistsFor (address) { + return contractMap[address] && isValidAddress(address) && contractMap[address].logo +} + +function imageElFor (address) { + const contract = contractMap[address] + const fileName = contract.logo + const path = `images/contract/${fileName}` + const img = document.createElement('img') + img.src = path + img.style.width = '75%' + return img +} + +function jsNumberForAddress (address) { + var addr = address.slice(2, 10) + var seed = parseInt(addr, 16) + return seed +} + diff --git a/ui/lib/lost-accounts-notice.js b/ui/lib/lost-accounts-notice.js new file mode 100644 index 000000000..948b13db6 --- /dev/null +++ b/ui/lib/lost-accounts-notice.js @@ -0,0 +1,23 @@ +const summary = require('../app/util').addressSummary + +module.exports = function (lostAccounts) { + return { + date: new Date().toDateString(), + title: 'Account Problem Caught', + body: `MetaMask has fixed a bug where some accounts were previously mis-generated. This was a rare issue, but you were affected! + +We have successfully imported the accounts that were mis-generated, but they will no longer be recovered with your normal seed phrase. + +We have marked the affected accounts as "Loose", and recommend you transfer ether and tokens away from those accounts, or export & back them up elsewhere. + +Your affected accounts are: +${lostAccounts.map(acct => ` - ${summary(acct)}`).join('\n')} + +These accounts have been marked as "Loose" so they will be easy to recognize in the account list. + +For more information, please read [our blog post.][1] + +[1]: https://medium.com/metamask/metamask-3-migration-guide-914b79533cdd#.7d8ktj4h3 + `, + } +} diff --git a/ui/lib/persistent-form.js b/ui/lib/persistent-form.js new file mode 100644 index 000000000..d4dc20b03 --- /dev/null +++ b/ui/lib/persistent-form.js @@ -0,0 +1,61 @@ +const inherits = require('util').inherits +const Component = require('react').Component +const defaultKey = 'persistent-form-default' +const eventName = 'keyup' + +module.exports = PersistentForm + +function PersistentForm () { + Component.call(this) +} + +inherits(PersistentForm, Component) + +PersistentForm.prototype.componentDidMount = function () { + const fields = document.querySelectorAll('[data-persistent-formid]') + const store = this.getPersistentStore() + + for (var i = 0; i < fields.length; i++) { + const field = fields[i] + const key = field.getAttribute('data-persistent-formid') + const cached = store[key] + if (cached !== undefined) { + field.value = cached + } + + field.addEventListener(eventName, this.persistentFieldDidUpdate.bind(this)) + } +} + +PersistentForm.prototype.getPersistentStore = function () { + let store = window.localStorage[this.persistentFormParentId || defaultKey] + if (store && store !== 'null') { + store = JSON.parse(store) + } else { + store = {} + } + return store +} + +PersistentForm.prototype.setPersistentStore = function (newStore) { + window.localStorage[this.persistentFormParentId || defaultKey] = JSON.stringify(newStore) +} + +PersistentForm.prototype.persistentFieldDidUpdate = function (event) { + const field = event.target + const store = this.getPersistentStore() + const key = field.getAttribute('data-persistent-formid') + const val = field.value + store[key] = val + this.setPersistentStore(store) +} + +PersistentForm.prototype.componentWillUnmount = function () { + const fields = document.querySelectorAll('[data-persistent-formid]') + for (var i = 0; i < fields.length; i++) { + const field = fields[i] + field.removeEventListener(eventName, this.persistentFieldDidUpdate.bind(this)) + } + this.setPersistentStore({}) +} + diff --git a/ui/lib/tx-helper.js b/ui/lib/tx-helper.js new file mode 100644 index 000000000..ec19daf64 --- /dev/null +++ b/ui/lib/tx-helper.js @@ -0,0 +1,17 @@ +const valuesFor = require('../app/util').valuesFor + +module.exports = function (unapprovedTxs, unapprovedMsgs, personalMsgs, network) { + log.debug('tx-helper called with params:') + log.debug({ unapprovedTxs, unapprovedMsgs, personalMsgs, network }) + + const txValues = network ? valuesFor(unapprovedTxs).filter(txMeta => txMeta.metamaskNetworkId === network) : valuesFor(unapprovedTxs) + log.debug(`tx helper found ${txValues.length} unapproved txs`) + const msgValues = valuesFor(unapprovedMsgs) + log.debug(`tx helper found ${msgValues.length} unsigned messages`) + let allValues = txValues.concat(msgValues) + const personalValues = valuesFor(personalMsgs) + log.debug(`tx helper found ${personalValues.length} unsigned personal messages`) + allValues = allValues.concat(personalValues) + + return allValues.sort(txMeta => txMeta.time) +} -- cgit