diff options
Diffstat (limited to 'scripts')
| -rw-r--r-- | scripts/benchmark.js | 76 | ||||
| -rw-r--r-- | scripts/color.js | 36 | ||||
| -rw-r--r-- | scripts/lib/theme.js | 170 |
3 files changed, 282 insertions, 0 deletions
diff --git a/scripts/benchmark.js b/scripts/benchmark.js new file mode 100644 index 0000000..8838e2b --- /dev/null +++ b/scripts/benchmark.js @@ -0,0 +1,76 @@ +'use strict' + +/* global theme */ +/* global Color */ + +function Benchmark () { + this.matches = () => { + var a = [{ id: 'b_inv_f_inv', fc: theme.active.f_inv, bc: theme.active.b_inv }] + for (const fid in theme.active) { + if (fid.substr(0, 1) !== 'f' || fid.indexOf('_inv') > -1) { continue } + const fc = theme.active[fid] + for (const bid in theme.active) { + if (bid.substr(0, 1) !== 'b' || bid.indexOf('_inv') > -1) { continue } + const bc = theme.active[bid] + a.push({ id: `${bid}_${fid}`, fc: fc, bc: bc }) + } + } + return a + } + + this.refresh = () => { + const logs = [] + let score = 0 + let errors = 0 + + for (const match of this.matches()) { + const rating = new Color(match.fc).contrast(new Color(match.bc)) + const cell = document.getElementById(match.id) + let rune = '' + if (rating === 1) { + rune = '[X]' + logs.push(`Error: Overlap for ${match.fc}/${match.bc}`) + errors += 1 + } else if (rating < 1.25) { + rune = '[!]' + score += 1 + } else if (rating < 2) { + rune = '[~]' + score += 2 + } else { + score += 5 + } + cell.innerHTML = `${rating.toFixed(2)}<span style='color:var(--f_inv)'>${rune}<span>` + } + + // Order + const fhigh = new Color(theme.active.f_high).contrast(new Color(theme.active.background)) + const fmed = new Color(theme.active.f_med).contrast(new Color(theme.active.background)) + const flow = new Color(theme.active.f_low).contrast(new Color(theme.active.background)) + const bhigh = new Color(theme.active.b_high).contrast(new Color(theme.active.background)) + const bmed = new Color(theme.active.b_med).contrast(new Color(theme.active.background)) + const blow = new Color(theme.active.b_low).contrast(new Color(theme.active.background)) + + if (fmed < flow) { logs.push('flip f_med with f_low') } + if (fhigh < fmed) { logs.push('flip f_high with f_med') } + if (bmed < blow) { logs.push('flip b_med with b_low') } + if (bhigh < bmed) { logs.push('flip b_high with b_med') } + + // Distribution + const fsum = fhigh + fmed + flow + document.getElementById('dis_f_high').style.width = `${((fhigh / fsum) * 100).toFixed(2)}%` + document.getElementById('dis_f_med').style.width = `${((fmed / fsum) * 100).toFixed(2)}%` + document.getElementById('dis_f_low').style.width = `${((flow / fsum) * 100).toFixed(2)}%` + + const bsum = bhigh + bmed + blow + document.getElementById('dis_b_high').style.width = `${((bhigh / bsum) * 100).toFixed(2)}%` + document.getElementById('dis_b_med').style.width = `${((bmed / bsum) * 100).toFixed(2)}%` + document.getElementById('dis_b_low').style.width = `${((blow / bsum) * 100).toFixed(2)}%` + + const perc = (score / (this.matches().length * 5)) * 100 + const cat = errors > 0 ? 'fix errors' : perc === 100 ? 'perfect' : perc > 80 ? 'good' : perc > 75 ? 'average' : 'bad' + + document.getElementById('score').innerHTML = `<span style='color:var(--f_high)'>${cat}</span> ${score}/${this.matches().length * 5} <span style='color:var(--f_low)'>${perc.toFixed(1)}%</span>` + document.getElementById('debug').innerHTML = logs.join('\n') + } +} diff --git a/scripts/color.js b/scripts/color.js new file mode 100644 index 0000000..0c52018 --- /dev/null +++ b/scripts/color.js @@ -0,0 +1,36 @@ +'use strict' + +function Color (hex = '#000000') { + this.hex = hex + + var r = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(this.hex) + + this.rgb = { r: parseInt(r[1], 16), g: parseInt(r[2], 16), b: parseInt(r[3], 16) } + + this.r = this.rgb.r + this.g = this.rgb.g + this.b = this.rgb.b + + this.average = parseInt((this.rgb.r + this.rgb.g + this.rgb.b) / 3) + this.invert = { r: 255 - this.rgb.r, g: 255 - this.rgb.g, b: 255 - this.rgb.b } + + this.contrast = function (b) { + const lumA = 0.2126 * _linear(this.r / 256) + 0.7152 * _linear(this.g / 256) + 0.0722 * _linear(this.b / 256) + const lumB = 0.2126 * _linear(b.r / 256) + 0.7152 * _linear(b.g / 256) + 0.0722 * _linear(b.b / 256) + return lumA > lumB ? (lumA + 0.05) / (lumB + 0.05) : (lumB + 0.05) / (lumA + 0.05) + } + + this.rgba = function () { + return 'rgba(' + this.rgb().r + ',' + this.rgb().g + ',' + this.rgb().b + ',1)' + } + + this.floats = function () { + return { r: this.rgb.r / 255, g: this.rgb.g / 255, b: this.rgb.b / 255 } + } + + this.toString = function () { + return this.hex + } + + function _linear (v) { return (v <= 0.03928) ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4) } +} diff --git a/scripts/lib/theme.js b/scripts/lib/theme.js new file mode 100644 index 0000000..7d4ac79 --- /dev/null +++ b/scripts/lib/theme.js @@ -0,0 +1,170 @@ +'use strict' + +/* global localStorage */ +/* global FileReader */ +/* global DOMParser */ + +function Theme (client) { + this.el = document.createElement('style') + this.el.type = 'text/css' + + this.active = {} + this.default = { + background: '#eeeeee', + f_high: '#0a0a0a', + f_med: '#4a4a4a', + f_low: '#6a6a6a', + f_inv: '#111111', + b_high: '#a1a1a1', + b_med: '#c1c1c1', + b_low: '#ffffff', + b_inv: '#ffb545' + } + + // Callbacks + this.onLoad = () => {} + + this.install = (host = document.body) => { + window.addEventListener('dragover', this.drag) + window.addEventListener('drop', this.drop) + host.appendChild(this.el) + } + + this.start = () => { + console.log('Theme', 'Starting..') + if (isJson(localStorage.theme)) { + const storage = JSON.parse(localStorage.theme) + if (isValid(storage)) { + console.log('Theme', 'Loading theme in localStorage..') + this.load(storage) + return + } + } + this.load(this.default) + } + + this.open = () => { + console.log('Theme', 'Open theme..') + const input = document.createElement('input') + input.type = 'file' + input.onchange = (e) => { + this.read(e.target.files[0], this.load) + } + input.click() + } + + this.load = (data) => { + const theme = this.parse(data) + if (!isValid(theme)) { console.warn('Theme', 'Invalid format'); return } + console.log('Theme', 'Loaded theme!') + this.el.innerHTML = `:root { + --background: ${theme.background}; + --f_high: ${theme.f_high}; + --f_med: ${theme.f_med}; + --f_low: ${theme.f_low}; + --f_inv: ${theme.f_inv}; + --b_high: ${theme.b_high}; + --b_med: ${theme.b_med}; + --b_low: ${theme.b_low}; + --b_inv: ${theme.b_inv}; + }` + localStorage.setItem('theme', JSON.stringify(theme)) + this.active = theme + if (this.onLoad) { + this.onLoad(data) + } + } + + this.reset = () => { + this.load(this.default) + } + + this.set = (key, val) => { + if (!val) { return } + const hex = (`${val}`.substr(0, 1) !== '#' ? '#' : '') + `${val}` + if (!isColor(hex)) { console.warn('Theme', `${hex} is not a valid color.`); return } + this.active[key] = hex + } + + this.get = (key) => { + return this.active[key] + } + + this.parse = (any) => { + if (isValid(any)) { return any } + if (isJson(any)) { return JSON.parse(any) } + if (isHtml(any)) { return extract(any) } + } + + // Drag + + this.drag = (e) => { + e.stopPropagation() + e.preventDefault() + e.dataTransfer.dropEffect = 'copy' + } + + this.drop = (e) => { + e.preventDefault() + const file = e.dataTransfer.files[0] + if (file.name.indexOf('.svg') > -1) { + this.read(file, this.load) + } + e.stopPropagation() + } + + this.read = (file, callback) => { + const reader = new FileReader() + reader.onload = (event) => { + callback(event.target.result) + } + reader.readAsText(file, 'UTF-8') + } + + // Helpers + + function extract (xml) { + const svg = new DOMParser().parseFromString(xml, 'text/xml') + try { + return { + background: svg.getElementById('background').getAttribute('fill'), + f_high: svg.getElementById('f_high').getAttribute('fill'), + f_med: svg.getElementById('f_med').getAttribute('fill'), + f_low: svg.getElementById('f_low').getAttribute('fill'), + f_inv: svg.getElementById('f_inv').getAttribute('fill'), + b_high: svg.getElementById('b_high').getAttribute('fill'), + b_med: svg.getElementById('b_med').getAttribute('fill'), + b_low: svg.getElementById('b_low').getAttribute('fill'), + b_inv: svg.getElementById('b_inv').getAttribute('fill') + } + } catch (err) { + console.warn('Theme', 'Incomplete SVG Theme', err) + } + } + + function isValid (json) { + if (!json) { return false } + if (!json.background || !isColor(json.background)) { return false } + if (!json.f_high || !isColor(json.f_high)) { return false } + if (!json.f_med || !isColor(json.f_med)) { return false } + if (!json.f_low || !isColor(json.f_low)) { return false } + if (!json.f_inv || !isColor(json.f_inv)) { return false } + if (!json.b_high || !isColor(json.b_high)) { return false } + if (!json.b_med || !isColor(json.b_med)) { return false } + if (!json.b_low || !isColor(json.b_low)) { return false } + if (!json.b_inv || !isColor(json.b_inv)) { return false } + return true + } + + function isColor (hex) { + return /^#([0-9A-F]{3}){1,2}$/i.test(hex) + } + + function isJson (text) { + try { JSON.parse(text); return true } catch (error) { return false } + } + + function isHtml (text) { + try { new DOMParser().parseFromString(text, 'text/xml'); return true } catch (error) { return false } + } +} |
