From d5c378b09aff6e56585f9d05779d72c4e7919d5b Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 6 Jun 2016 14:05:13 -0700 Subject: [PATCH] Cache identicons Fixes #197 Also as a side effect, by creating this `iconFactory.cache` object, we have a convenient place for specifying stock icons for known contracts! We can just hard-code image addresses in the `ui/lib/icon-factory.js` cache instantiation, and those values will be injected into the identicon image tag `src` attributes. --- CHANGELOG.md | 2 ++ test/unit/lib/icon-factory-test.js | 31 ++++++++++++++++++ ui/app/components/identicon.js | 17 ++++------ ui/lib/icon-factory.js | 52 ++++++++++++++++++++++++++++++ 4 files changed, 91 insertions(+), 11 deletions(-) create mode 100644 test/unit/lib/icon-factory-test.js create mode 100644 ui/lib/icon-factory.js diff --git a/CHANGELOG.md b/CHANGELOG.md index ea5da782e..7763e5211 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current Master +- Cache identicon images to optimize for long lists of transactions. + ## 2.3.0 2016-06-06 - Show network status in title bar diff --git a/test/unit/lib/icon-factory-test.js b/test/unit/lib/icon-factory-test.js new file mode 100644 index 000000000..2f24d408c --- /dev/null +++ b/test/unit/lib/icon-factory-test.js @@ -0,0 +1,31 @@ +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/ui/app/components/identicon.js b/ui/app/components/identicon.js index ef625cc62..fd61b3125 100644 --- a/ui/app/components/identicon.js +++ b/ui/app/components/identicon.js @@ -1,8 +1,10 @@ const Component = require('react').Component const h = require('react-hyperscript') const inherits = require('util').inherits -const jazzicon = require('jazzicon') const findDOMNode = require('react-dom').findDOMNode +const jazzicon = require('jazzicon') +const iconFactoryGen = require('../../lib/icon-factory') +const iconFactory = iconFactoryGen(jazzicon) module.exports = IdenticonComponent @@ -35,21 +37,14 @@ IdenticonComponent.prototype.componentDidMount = function(){ var address = state.address if (!address) return - var numericRepresentation = jsNumberForAddress(address) var container = findDOMNode(this) - // jazzicon with hack to fix inline svg error + var diameter = state.diameter || this.defaultDiameter - var identicon = jazzicon(diameter, numericRepresentation) - var identiconSrc = identicon.innerHTML - var dataUri = 'data:image/svg+xml;charset=utf-8,'+encodeURIComponent(identiconSrc) + var dataUri = iconFactory.iconForAddress(address, diameter) + var img = document.createElement('img') img.src = dataUri container.appendChild(img) } -function jsNumberForAddress(address) { - var addr = address.slice(2, 10) - var seed = parseInt(addr, 16) - return seed -} diff --git a/ui/lib/icon-factory.js b/ui/lib/icon-factory.js new file mode 100644 index 000000000..1b1df9490 --- /dev/null +++ b/ui/lib/icon-factory.js @@ -0,0 +1,52 @@ +var iconFactory + +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) { + if (this.isCached(address, diameter)) { + return this.cache[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) + var identiconSrc = identicon.innerHTML + var dataUri = 'data:image/svg+xml;charset=utf-8,'+encodeURIComponent(identiconSrc) + return dataUri +} + +IconFactory.prototype.cacheIcon = function(address, diameter, icon) { + if (!(address in this.cache)) { + var sizeCache = {} + sizeCache[diameter] = icon + return this.cache[address] = sizeCache + + } else { + return this.cache[address][diameter] = icon + } +} + +IconFactory.prototype.isCached = function(address, diameter) { + return address in this.cache && diameter in this.cache[address] +} + +function jsNumberForAddress(address) { + var addr = address.slice(2, 10) + var seed = parseInt(addr, 16) + return seed +}