aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.eslintrc8
-rw-r--r--app/scripts/lib/notifications.js104
-rw-r--r--test/unit/lib/icon-factory-test.js31
-rw-r--r--test/unit/linting_test.js2
-rw-r--r--ui/app/accounts/account-list-item.js (renamed from ui/app/accounts/account-panel.js)1
-rw-r--r--ui/app/accounts/index.js4
-rw-r--r--ui/app/components/account-panel.js50
-rw-r--r--ui/app/components/identicon.js7
-rw-r--r--ui/app/components/panel.js54
-rw-r--r--ui/app/components/pending-tx.js32
-rw-r--r--ui/lib/icon-factory.js55
11 files changed, 184 insertions, 164 deletions
diff --git a/.eslintrc b/.eslintrc
index 635c47a95..e64b3e5be 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -42,7 +42,7 @@
"constructor-super": 2,
"curly": [2, "multi-line"],
"dot-location": [2, "property"],
- "eol-last": 2,
+ "eol-last": 1,
"eqeqeq": [2, "allow-null"],
"generator-star-spacing": [2, { "before": true, "after": true }],
"handle-callback-err": [2, "^(err|error)$" ],
@@ -87,7 +87,7 @@
"no-mixed-spaces-and-tabs": 2,
"no-multi-spaces": 2,
"no-multi-str": 2,
- "no-multiple-empty-lines": [2, { "max": 1 }],
+ "no-multiple-empty-lines": [1, { "max": 2 }],
"no-native-reassign": 2,
"no-negated-in-lhs": 2,
"no-new": 2,
@@ -112,7 +112,7 @@
"no-sparse-arrays": 2,
"no-this-before-super": 2,
"no-throw-literal": 2,
- "no-trailing-spaces": 2,
+ "no-trailing-spaces": 1,
"no-undef": 2,
"no-undef-init": 2,
"no-unexpected-multiline": 2,
@@ -129,7 +129,7 @@
"no-with": 2,
"one-var": [2, { "initialized": "never" }],
"operator-linebreak": [2, "after", { "overrides": { "?": "before", ":": "before" } }],
- "padded-blocks": [2, "never"],
+ "padded-blocks": [1, "never"],
"quotes": [2, "single", "avoid-escape"],
"semi": [2, "never"],
"semi-spacing": [2, { "before": false, "after": true }],
diff --git a/app/scripts/lib/notifications.js b/app/scripts/lib/notifications.js
index af2dc2054..5762fd26b 100644
--- a/app/scripts/lib/notifications.js
+++ b/app/scripts/lib/notifications.js
@@ -1,5 +1,11 @@
const createId = require('hat')
+const unmountComponentAtNode = require('react-dom').unmountComponentAtNode
+const findDOMNode = require('react-dom').findDOMNode
+const render = require('react-dom').render
+const h = require('react-hyperscript')
const uiUtils = require('../../../ui/app/util')
+const renderPendingTx = require('../../../ui/app/components/pending-tx').prototype.renderGeneric
+const MetaMaskUiCss = require('../../../ui/css')
var notificationHandlers = {}
module.exports = {
@@ -49,31 +55,32 @@ function createUnlockRequestNotification (opts) {
function createTxNotification (opts) {
// guard for chrome bug https://github.com/MetaMask/metamask-plugin/issues/236
if (!chrome.notifications) return console.error('Chrome notifications API missing...')
- var message = [
- 'Submitted by ' + opts.txParams.origin,
- 'to: ' + uiUtils.addressSummary(opts.txParams.to),
- 'from: ' + uiUtils.addressSummary(opts.txParams.from),
- 'value: ' + uiUtils.formatBalance(opts.txParams.value),
- 'data: ' + uiUtils.dataSize(opts.txParams.data),
- ].join('\n')
- var id = createId()
- chrome.notifications.create(id, {
- type: 'basic',
- requireInteraction: true,
- iconUrl: '/images/icon-128.png',
- title: opts.title,
- message: message,
- buttons: [{
- title: 'confirm',
- }, {
- title: 'cancel',
- }],
+ renderTransactionNotificationSVG(opts, function(err, source){
+ if (err) throw err
+
+ var imageUrl = 'data:image/svg+xml;utf8,' + encodeURIComponent(source)
+
+ var id = createId()
+ chrome.notifications.create(id, {
+ type: 'image',
+ // requireInteraction: true,
+ iconUrl: '/images/icon-128.png',
+ imageUrl: imageUrl,
+ title: opts.title,
+ message: '',
+ buttons: [{
+ title: 'confirm',
+ }, {
+ title: 'cancel',
+ }],
+ })
+ notificationHandlers[id] = {
+ confirm: opts.confirm,
+ cancel: opts.cancel,
+ }
+
})
- notificationHandlers[id] = {
- confirm: opts.confirm,
- cancel: opts.cancel,
- }
}
function createMsgNotification (opts) {
@@ -103,3 +110,54 @@ function createMsgNotification (opts) {
cancel: opts.cancel,
}
}
+
+function renderTransactionNotificationSVG(opts, cb){
+ var state = {
+ nonInteractive: true,
+ inlineIdenticons: true,
+ txData: {
+ txParams: opts.txParams,
+ time: (new Date()).getTime(),
+ },
+ identities: {
+
+ },
+ accounts: {
+
+ },
+ }
+
+ var container = document.createElement('div')
+ var confirmView = h('div.app-primary', {
+ style: {
+ width: '450px',
+ height: '300px',
+ padding: '16px',
+ // background: '#F7F7F7',
+ background: 'white',
+ },
+ }, [
+ h('style', MetaMaskUiCss()),
+ renderPendingTx(h, state),
+ ])
+
+ render(confirmView, container, function ready(){
+ var rootElement = findDOMNode(this)
+ var viewSource = rootElement.outerHTML
+ unmountComponentAtNode(container)
+ var svgSource = svgWrapper(viewSource)
+ // insert content into svg wrapper
+ cb(null, svgSource)
+ })
+}
+
+function svgWrapper(content){
+ var wrapperSource = `
+ <svg xmlns="http://www.w3.org/2000/svg" width="450" height="300">
+ <foreignObject x="0" y="0" width="100%" height="100%">
+ <body xmlns="http://www.w3.org/1999/xhtml" height="100%">{{content}}</body>
+ </foreignObject>
+ </svg>
+ `
+ return wrapperSource.split('{{content}}').join(content)
+} \ No newline at end of file
diff --git a/test/unit/lib/icon-factory-test.js b/test/unit/lib/icon-factory-test.js
deleted file mode 100644
index 2f24d408c..000000000
--- a/test/unit/lib/icon-factory-test.js
+++ /dev/null
@@ -1,31 +0,0 @@
-const assert = require('assert')
-const sinon = require('sinon')
-
-const path = require('path')
-const IconFactoryGen = require(path.join(__dirname, '..', '..', '..', 'ui', 'lib', 'icon-factory.js'))
-
-describe('icon-factory', function() {
- let iconFactory, address, diameter
-
- beforeEach(function() {
- iconFactory = IconFactoryGen((d,n) => 'stubicon')
- address = '0x012345671234567890'
- diameter = 50
- })
-
- it('should return a data-uri string for any address and diameter', function() {
- const output = iconFactory.iconForAddress(address, diameter)
- assert.ok(output.indexOf('data:image/svg') === 0)
- assert.equal(output, iconFactory.cache[address][diameter])
- })
-
- it('should default to cache first', function() {
- const testOutput = 'foo'
- const mockSizeCache = {}
- mockSizeCache[diameter] = testOutput
- iconFactory.cache[address] = mockSizeCache
-
- const output = iconFactory.iconForAddress(address, diameter)
- assert.equal(output, testOutput)
- })
-})
diff --git a/test/unit/linting_test.js b/test/unit/linting_test.js
index 27b4b2b1e..75d90652d 100644
--- a/test/unit/linting_test.js
+++ b/test/unit/linting_test.js
@@ -3,7 +3,7 @@ const lint = require('mocha-eslint');
const lintPaths = ['app/**/*.js', 'ui/**/*.js', '!node_modules/**', '!dist/**', '!docs/**', '!app/scripts/chromereload.js']
const lintOptions = {
- strict: true,
+ strict: false,
}
lint(lintPaths, lintOptions) \ No newline at end of file
diff --git a/ui/app/accounts/account-panel.js b/ui/app/accounts/account-list-item.js
index af3d0d347..b42de182e 100644
--- a/ui/app/accounts/account-panel.js
+++ b/ui/app/accounts/account-list-item.js
@@ -33,6 +33,7 @@ NewComponent.prototype.render = function () {
this.pendingOrNot(),
h(Identicon, {
address: identity.address,
+ imageify: true,
}),
]),
diff --git a/ui/app/accounts/index.js b/ui/app/accounts/index.js
index 775df400b..f7ae5c53e 100644
--- a/ui/app/accounts/index.js
+++ b/ui/app/accounts/index.js
@@ -5,7 +5,7 @@ const connect = require('react-redux').connect
const actions = require('../actions')
const valuesFor = require('../util').valuesFor
const findDOMNode = require('react-dom').findDOMNode
-const AccountPanel = require('./account-panel')
+const AccountListItem = require('./account-list-item')
module.exports = connect(mapStateToProps)(AccountsScreen)
@@ -74,7 +74,7 @@ AccountsScreen.prototype.render = function () {
}
})
- return h(AccountPanel, {
+ return h(AccountListItem, {
key: `acct-panel-${identity.address}`,
identity,
selectedAddress: this.props.selectedAddress,
diff --git a/ui/app/components/account-panel.js b/ui/app/components/account-panel.js
index 112b897d5..b98a8cb45 100644
--- a/ui/app/components/account-panel.js
+++ b/ui/app/components/account-panel.js
@@ -1,13 +1,13 @@
const inherits = require('util').inherits
const Component = require('react').Component
const h = require('react-hyperscript')
+const Identicon = require('./identicon')
const formatBalance = require('../util').formatBalance
const addressSummary = require('../util').addressSummary
-const Panel = require('./panel')
-
module.exports = AccountPanel
+
inherits(AccountPanel, Component)
function AccountPanel () {
Component.call(this)
@@ -19,13 +19,8 @@ AccountPanel.prototype.render = function () {
var account = state.account || {}
var isFauceting = state.isFauceting
- var panelOpts = {
+ var panelState = {
key: `accountPanel${identity.address}`,
- onClick: (event) => {
- if (state.onShowDetail) {
- state.onShowDetail(identity.address, event)
- }
- },
identiconKey: identity.address,
identiconLabel: identity.name,
attributes: [
@@ -37,10 +32,41 @@ AccountPanel.prototype.render = function () {
],
}
- return h(Panel, panelOpts,
- !state.onShowDetail ? null : h('.arrow-right.cursor-pointer', [
- h('i.fa.fa-chevron-right.fa-lg'),
- ]))
+ return (
+
+ h('.identity-panel.flex-row.flex-space-between', {
+ style: {
+ flex: '1 0 auto',
+ cursor: panelState.onClick ? 'pointer' : undefined,
+ },
+ onClick: panelState.onClick,
+ }, [
+
+ // account identicon
+ h('.identicon-wrapper.flex-column.select-none', [
+ h(Identicon, {
+ address: panelState.identiconKey,
+ imageify: !state.inlineIdenticons,
+ }),
+ h('span.font-small', panelState.identiconLabel),
+ ]),
+
+ // account address, balance
+ h('.identity-data.flex-column.flex-justify-center.flex-grow.select-none', [
+
+ panelState.attributes.map((attr) => {
+ return h('.flex-row.flex-space-between', {
+ key: '' + Math.round(Math.random() * 1000000),
+ }, [
+ h('label.font-small.no-select', attr.key),
+ h('span.font-small', attr.value),
+ ])
+ }),
+ ]),
+
+ ])
+
+ )
}
function balanceOrFaucetingIndication (account, isFauceting) {
diff --git a/ui/app/components/identicon.js b/ui/app/components/identicon.js
index 5fe07ce7a..c17bd5122 100644
--- a/ui/app/components/identicon.js
+++ b/ui/app/components/identicon.js
@@ -39,12 +39,9 @@ IdenticonComponent.prototype.componentDidMount = function () {
if (!address) return
var container = findDOMNode(this)
-
var diameter = state.diameter || this.defaultDiameter
- var dataUri = iconFactory.iconForAddress(address, diameter)
-
- var img = document.createElement('img')
- img.src = dataUri
+ var imageify = state.imageify
+ var img = iconFactory.iconForAddress(address, diameter, imageify)
container.appendChild(img)
}
diff --git a/ui/app/components/panel.js b/ui/app/components/panel.js
deleted file mode 100644
index cbdc82982..000000000
--- a/ui/app/components/panel.js
+++ /dev/null
@@ -1,54 +0,0 @@
-const inherits = require('util').inherits
-const Component = require('react').Component
-const h = require('react-hyperscript')
-const Identicon = require('./identicon')
-
-module.exports = Panel
-
-inherits(Panel, Component)
-function Panel () {
- Component.call(this)
-}
-
-Panel.prototype.render = function () {
- var state = this.props
-
- var style = {
- flex: '1 0 auto',
- }
-
- if (state.onClick) style.cursor = 'pointer'
-
- return (
- h('.identity-panel.flex-row.flex-space-between', {
- style,
- onClick: state.onClick,
- }, [
-
- // account identicon
- h('.identicon-wrapper.flex-column.select-none', [
- h(Identicon, {
- address: state.identiconKey,
- }),
- h('span.font-small', state.identiconLabel),
- ]),
-
- // account address, balance
- h('.identity-data.flex-column.flex-justify-center.flex-grow.select-none', [
-
- state.attributes.map((attr) => {
- return h('.flex-row.flex-space-between', {
- key: '' + Math.round(Math.random() * 1000000),
- }, [
- h('label.font-small.no-select', attr.key),
- h('span.font-small', attr.value),
- ])
- }),
- ]),
-
- // outlet for inserting additional stuff
- state.children,
- ])
- )
-}
-
diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js
index 1835239e5..484046827 100644
--- a/ui/app/components/pending-tx.js
+++ b/ui/app/components/pending-tx.js
@@ -16,6 +16,10 @@ function PendingTx () {
PendingTx.prototype.render = function () {
var state = this.props
+ return this.renderGeneric(h, state)
+}
+
+PendingTx.prototype.renderGeneric = function (h, state) {
var txData = state.txData
var txParams = txData.txParams || {}
@@ -24,6 +28,7 @@ PendingTx.prototype.render = function () {
var account = state.accounts[address] || { address: address }
return (
+
h('.transaction', {
key: txData.id,
}, [
@@ -40,6 +45,7 @@ PendingTx.prototype.render = function () {
showFullAddress: true,
identity: identity,
account: account,
+ inlineIdenticons: state.inlineIdenticons,
}),
// tx data
@@ -62,15 +68,25 @@ PendingTx.prototype.render = function () {
]),
// send + cancel
- h('.flex-row.flex-space-around', [
- h('button', {
- onClick: state.cancelTransaction,
- }, 'Cancel'),
- h('button', {
- onClick: state.sendTransaction,
- }, 'Send'),
- ]),
+ state.nonInteractive ? null : actionButtons(state),
+
])
+
)
+
}
+function actionButtons(state){
+ return (
+
+ h('.flex-row.flex-space-around', [
+ h('button', {
+ onClick: state.cancelTransaction,
+ }, 'Cancel'),
+ h('button', {
+ onClick: state.sendTransaction,
+ }, 'Send'),
+ ])
+
+ )
+} \ No newline at end of file
diff --git a/ui/lib/icon-factory.js b/ui/lib/icon-factory.js
index 1f7cca859..a30041114 100644
--- a/ui/lib/icon-factory.js
+++ b/ui/lib/icon-factory.js
@@ -12,42 +12,49 @@ function IconFactory (jazzicon) {
this.cache = {}
}
-IconFactory.prototype.iconForAddress = function (address, diameter) {
- if (this.isCached(address, diameter)) {
- return this.cache[address][diameter]
+IconFactory.prototype.iconForAddress = function (address, diameter, imageify) {
+ if (imageify) {
+ return this.generateIdenticonImg(address, diameter)
+ } else {
+ return this.generateIdenticonSvg(address, diameter)
}
-
- const dataUri = this.generateNewUri(address, diameter)
- this.cacheIcon(address, diameter, dataUri)
- return dataUri
}
-IconFactory.prototype.generateNewUri = function (address, diameter) {
- var numericRepresentation = jsNumberForAddress(address)
- var identicon = this.jazzicon(diameter, numericRepresentation)
+// returns img dom element
+IconFactory.prototype.generateIdenticonImg = function (address, diameter) {
+ var identicon = this.generateIdenticonSvg(address, diameter)
var identiconSrc = identicon.innerHTML
- var dataUri = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(identiconSrc)
- return dataUri
+ var dataUri = toDataUri(identiconSrc)
+ var img = document.createElement('img')
+ img.src = dataUri
+ return img
}
-IconFactory.prototype.cacheIcon = function (address, diameter, icon) {
- if (!(address in this.cache)) {
- var sizeCache = {}
- sizeCache[diameter] = icon
- this.cache[address] = sizeCache
- return sizeCache
- } else {
- this.cache[address][diameter] = icon
- return icon
- }
+// 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
}
-IconFactory.prototype.isCached = function (address, diameter) {
- return address in this.cache && diameter in this.cache[address]
+// creates a new identicon
+IconFactory.prototype.generateNewIdenticon = function (address, diameter) {
+ var numericRepresentation = jsNumberForAddress(address)
+ var identicon = this.jazzicon(diameter, numericRepresentation)
+ return identicon
}
+// util
+
function jsNumberForAddress (address) {
var addr = address.slice(2, 10)
var seed = parseInt(addr, 16)
return seed
}
+
+function toDataUri(identiconSrc){
+ return 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(identiconSrc)
+} \ No newline at end of file