Skip to content

Commit

Permalink
refactor: recompute colors only when needed (electron#773)
Browse files Browse the repository at this point in the history
* refactor: use json-stable-stringify in colors.json

This ensures that the generated files will only have diffs when
the contents change, rather than due to arbitrary sorting of an
object's properties

* refactor: sort require calls

* refactor: simplify color object creation

* feat: if we already have color info for an app, reuse it

* feat: only recalculate colors when icons change

Add a revHash for the icon file & use the old color values
if the revHash is unchanged since the last run.

* refactor: skip redundant file loads

Previously we loaded once to get the revHash, then a second time to
compute the complimentary colors. Now we load it once into a buffer,
then use the buffer both tasks

* chore: update package-lock.json

since this branch adds three new dependencies

* chore: update colors file

this update adds the icon file revHashes

* refactor: control colors endpoints for testability

* chore: fix linting

* test: add tests for color script

* docs: add some function comments

* refactor: color script readability

* chore: update package-lock

* chore: update package-lock

* refactor: better color error.log testing

* fix: incorrect terminology in comments

* fix: indentation whitespace

* fix: silence incorrect linter warning

* fix: hide intentional test errors

The tests feed bad data into the color reader, so it logs errors.
Dumping these errors during `npm run test` makes the output noisy.
So instead of having an `console.error` spy, actually intercept it
and test to confirm the errors the expected information.

* chore: remove sinon, sinon-chai dependencies

Sinon was unused after the `console.error` spy was removed.

* fix: function name typo

* fix: remove unused variable
  • Loading branch information
ckerr authored and zeke committed Aug 29, 2018
1 parent 6211150 commit 6b92ee6
Show file tree
Hide file tree
Showing 6 changed files with 5,065 additions and 1,986 deletions.
93 changes: 93 additions & 0 deletions lib/colors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
'use strict'

const fs = require('fs')
const colorConvert = require('color-convert')
const getImageColors = require('get-image-colors')
const mime = require('mime-types')
const path = require('path')
const pickAGoodColor = require('pick-a-good-color')
const revHash = require('rev-hash')
const stringify = require('json-stable-stringify')

/**
* Generates good colors for an image.
*
* @param slugsAndIconPaths [ { slug: foo, iconPath: bar } ... ]
* @param oldColors: reference colors from previous call to getColors()
* @param root: repo toplevel directory so that saved iconPaths are relative to it
* @return { slug: { palette, goodColorOnWhite, goodColorOnBlack, faintColorOnWhite, source: { revHash, iconPath } }
*/
async function getColors (
slugsAndIconPaths,
oldColors,
root
) {
return Promise.all(
slugsAndIconPaths.map(async (app) => {
const slug = app.slug
try {
const data = fs.readFileSync(app.iconPath)
const hash = revHash(data)

// if nothing's changed, don't recalculate
let o = oldColors[slug]
if (o && o.source && o.source.revHash === hash) return {[slug]: o}

console.info(`calculating good colors for ${slug}`)
return await getImageColors(data, mime.lookup(app.iconPath))
.then(iconColors => {
const palette = iconColors.map(color => color.hex())
const goodColorOnWhite = pickAGoodColor(palette)
const goodColorOnBlack = pickAGoodColor(palette, {background: 'black'})
const faintColorOnWhite = `rgba(${colorConvert.hex.rgb(goodColorOnWhite).join(', ')}, 0.1)`
return {[slug]: {
source: {
revHash: hash,
path: path.relative(root, app.iconPath)
},
palette,
goodColorOnWhite,
goodColorOnBlack,
faintColorOnWhite
}}
})
} catch (e) {
console.error(`Error processing ${app.iconPath}`, e)
}
})
)
.then(values => Object.assign({}, ...values))
}

/**
* Wrapper around getColors() that uses the same file for input & output,
* refreshing the file when the data changes
*
* @param slugsAndIconPaths [ { slug: foo, iconPath: bar } ... ]
* @param colorsFile: the file that keeps the list of complimentary colors
* @param root: repo toplevel directory so that saved iconPaths are relative to it
*/
const rebuildColorFile = (
slugsAndIconPaths,
colorsFile,
root
) => {
let oldColors
try {
oldColors = require(colorsFile)
} catch (e) {
oldColors = {}
}

getColors(slugsAndIconPaths, oldColors, root)
.then(colors => {
try {
fs.writeFileSync(colorsFile, stringify(colors, {space: 2}))
} catch (e) {
console.error(`Error writing ${colorsFile}`, e)
}
})
}

module.exports = rebuildColorFile
module.exports.getColors = getColors
Loading

0 comments on commit 6b92ee6

Please sign in to comment.