// set superglobal js var by default 100 for glyph
window.size = 'medium';
var SVG_NS = "http://www.w3.org/2000/svg";
var svgCache = {};
var options = {};
var $badge;
var $badgeRaster;
var $studio = document.getElementById('studio');
var $template = document.getElementById('studio-template');
var $palette = document.getElementById('studio-palette');
var $mask = document.getElementById('studio-mask');
var $glyph = document.getElementById('studio-glyph');
var $glyphSelector;
var $glyphSelectorButton;
window.addEventListener('load', function init() {
$badgeRaster = new Image();
$badgeRaster.id = 'raster';
document.getElementById('output').appendChild($badgeRaster);
$template.addEventListener('change', updateTemplate);
$palette.addEventListener('change', updatePalette);
$mask.addEventListener('change', updateMask);
$glyph.addEventListener('change', updateGlyph);
initStudio();
initPalettes();
initOptions();
updateTemplate();
});
// ==[ General Methods ]======================================================
/**
*
*/
function showError(err) {
// TO DO - show errors :)
console.err(err);
}
// ==[ Studio ]===============================================================
/**
*
*/
function initStudio() {
initGlyphSelector();
document.addEventListener('keydown', function (event) {
if (event.keyCode === 27) { // Escape
if ($glyphSelector && $glyphSelector.offsetWidth)
closeGlyphSelector();
}
}, true);
document.addEventListener('focus', function (event) {
[$glyphSelector].forEach(function ($overlay) {
if ($overlay && $overlay.offsetWidth && !$overlay.contains(event.target)) {
event.stopPropagation();
$overlay.focus();
}
});
}, true);
}
// ==[ Glyph Selector ]=======================================================
/**
*
*/
function initGlyphSelector() {
if ($glyphSelector) {
return false;
}
var glyphLog = [];
$glyphSelectorButton = document.createElement('button');
$glyphSelectorButton.className = 'btn btn-default pull-right';
$glyphSelectorButton.id = 'search-glyphs';
$glyphSelectorButton.type = 'button';
$glyphSelectorButton.innerHTML = ' Search';
$glyphSelectorButton.addEventListener('click', openGlyphSelector);
$glyph.parentNode.insertBefore($glyphSelectorButton, $glyph.nextSibling);
var $$options = $glyph.querySelectorAll('option');
$glyphSelector = importTemplate('glyph-selector', function ($template) {
var $list = $template.querySelector('ul');
for (var i = 0, l = $$options.length; i < l; i++) {
(function ($option, index) {
var value = $option.value;
var id = 'glyph-selector-item-' + value;
var $node = importTemplate('glyph-selector-item', function ($template) {
var $input = $template.querySelector('input');
$input.id = id;
$input.value = index;
var checked = $glyph.selectedIndex === index;
$input[checked ? 'setAttribute' : 'removeAttribute']('checked', 'checked');
var $label = $template.querySelector('label');
$label.id = id + '-label';
$label.className = 'fa fa-' + value;
$label.setAttribute('for', id);
$label.setAttribute('title', $option.text);
}).querySelector('li');
$list.appendChild($node);
glyphLog.push({
id: id,
value: value
});
})($$options[i], i);
}
}).querySelector('#glyph-selector');
$glyphSelector.querySelector('.header').appendChild(makeCloseButton(closeGlyphSelector));
$studio.appendChild($glyphSelector);
$glyphSelector.addEventListener('change', function (event) {
event.stopPropagation();
var index = event.target.value;
$glyph.selectedIndex = index;
updateGlyph();
});
$glyphSelector.addEventListener('click', function (event) {
if (event.target.nodeName.toLowerCase() !== 'label') {
return;
}
event.stopPropagation();
closeGlyphSelector();
});
$glyphSelector.addEventListener('keydown', function (event) {
if (event.keyCode === 13) { // Enter
if (event.target.name)
$glyph.selectedIndex = event.target.value;
return updateGlyph(closeGlyphSelector);
}
if (event.keyCode === 38 || event.keyCode === 40) { // Up / Down
event.preventDefault();
var $container = event.target.parentNode.parentNode;
var itemSize = event.target.parentNode.offsetWidth;
var containerSize = $container.offsetWidth;
var rowCount = Math.floor(containerSize / itemSize);
var currentIndex = parseInt(event.target.value);
var newIndex = currentIndex;
var altFinder;
if (event.keyCode === 38) {
// Move up a row
newIndex = currentIndex - rowCount;
altFinder = 'firstElementChild';
} else {
// Move down a row
newIndex = currentIndex + rowCount;
altFinder = 'lastElementChild';
}
var newItem = $container.querySelector('input[value="' + newIndex + '"]') ||
$container[altFinder].querySelector('input');
$glyph.selectedIndex = newItem.value;
newItem.checked = true;
newItem.focus();
rasterize();
}
});
$glyphSelector.addEventListener('focus', function (event) {
if (event.target !== $glyphSelector)
return;
event.stopPropagation();
}, true);
$glyph.addEventListener('change', function (event) {
var $selectorItem = document.getElementById('glyph-selector-item-' + this.value);
if ($selectorItem) {
$selectorItem.click();
}
});
}
/**
*
*/
function openGlyphSelector() {
if (!$glyphSelector)
initGlyphSelector();
$glyphSelector.classList.remove('hidden');
if ($glyph.value)
document.getElementById('glyph-selector-item-' + $glyph.value + '-label').focus();
$glyphSelector.focus();
}
/**
*
*/
function closeGlyphSelector() {
if (!$glyphSelector)
return;
$glyphSelector.classList.add('hidden');
$glyphSelectorButton.focus();
}
// ==[ Templates ]============================================================
/**
*
*/
function getCurrentTemplate() {
return $template.value;
}
/**
*
*/
function updateTemplate(callback) {
callback = cb(callback);
var path = $template.dataset.path;
var shape = getCurrentTemplate();
loadSVG(path + '/' + shape + '.svg', function (err, $svg) {
if (err)
return showError(err);
$badge = $svg;
extractOptions();
setCustomPalette($svg);
updatePalette(function () {
updateMask(callback);
});
});
}
// ==[ Palettes ]=============================================================
function Palette(colors) {
this._colors = {};
if (colors) {
for (var color in colors) {
if (colors.hasOwnProperty(color)) {
this._colors[color] = Palette.parseColor(colors[color]);
}
}
}
if (!this._colors.hasOwnProperty('glyph'))
this._colors['glyph'] = '#000000';
}
Palette.prototype.toNode = function (id) {
var content = [];
for (var color in this._colors) {
if (this._colors.hasOwnProperty(color)) {
content.push('.color-' + color + ' { fill: ' + this._colors[color] + '; }');
}
}
var $node = document.createElement('style');
$node.type = 'text/css';
$node.id = id || 'palette';
$node.textContent = content.join('\n');
return $node;
}
Palette.prototype.colors = function () {
return Object.keys(this._colors);
}
Palette.prototype.color = function (name) {
return this._colors[name] || '#000';
}
Palette.parseColor = function (str) {
// Should probably be a bit more robust about this!
if (!/^#[a-f0-9]{3}$/i.test(str))
return str.toLowerCase();
return '#' + str.charAt(1) + str.charAt(1)
+ str.charAt(2) + str.charAt(2)
+ str.charAt(3) + str.charAt(3);
}
Palette.fromDataset = function (dataset) {
var colors = {};
for (var item in dataset) {
if (/^color\w+/i.test(item)) {
var color = item
.replace(/^color(\w)/i, function (m, c) {
return c.toLowerCase();
})
.replace(/[A-Z]/, function (m) {
return '-' + m.toLowerCase();
});
colors[color] = dataset[item];
}
}
return new Palette(colors);
}
Palette.fromSVG = function ($svg) {
var colors = {};
var $node = $svg.getElementById('palette');
if (!$node || $node.nodeName !== 'style')
return new Palette();
var $stylesheet = document.createElement('style');
$stylesheet.setAttribute('media', 'print');
$stylesheet.appendChild(document.createTextNode($node.textContent));
document.head.appendChild($stylesheet);
var sheet = $stylesheet.sheet;
document.head.removeChild($stylesheet);
var rules = sheet.rules || sheet.cssRules;
for (var i = 0, l = rules.length; i < l; i++) {
var rule = rules[i];
var selector = rule.selectorText;
if (/^\.color-/.test(selector)) {
var key = selector.replace(/^\.color-/, '');
var value = rule.style.fill || '#000';
colors[key] = value;
}
}
return new Palette(colors);
}
/**
*
*/
function initPalettes() {
var $custom = document.createElement('option');
$custom.disabled = true;
$custom.value = 'custom';
$custom.text = 'Custom';
$custom.id = 'custom-color-option';
$palette.options.add($custom);
var $container = document.getElementById('custom-palette');
$palette.addEventListener('change', function () {
var isCustom = (this.options[this.selectedIndex] === $custom);
$custom.disabled = !isCustom;
setCustomColors();
updatePalette();
});
var changeTimer;
$container.addEventListener('change', function (event) {
var $input = event.target;
$custom.setAttribute('data-color-' + $input.name, $input.value);
$custom.disabled = false;
$palette.selectedIndex = $palette.options.length - 1;
updatePalette();
});
setCustomColors();
}
/**
*
*/
function getCurrentPalette() {
var $option = $palette.options[$palette.selectedIndex];
return Palette.fromDataset($option.dataset);
}
/**
*
*/
function updatePalette(callback) {
callback = cb(callback);
var $oldPalette = $badge.getElementById('palette');
var $newPalette = getCurrentPalette().toNode();
if ($oldPalette) {
$oldPalette.parentNode.insertBefore($newPalette, $oldPalette);
$oldPalette.parentNode.removeChild($oldPalette);
} else {
var $defs = $badge.querySelector('defs') || document.createElement('defs');
if (!$defs.parentNode)
$badge.insertBefore($defs, $badge.childNodes[0]);
$defs.appendChild($newPalette)
}
updateGlyph(callback);
}
/**
*
*/
function setCustomPalette($svg, callback) {
callback = cb(callback);
var colors = Palette.fromSVG($svg).colors();
var $container = document.getElementById('custom-palette');
var display = $container.style.display;
$container.innerHTML = '';
$container.style.display = 'none';
$container.className = 'item';
for (var i = 0, l = colors.length; i < l; i++) {
var name = colors[i];
var label = name.replace(/(^|-)(\w)/g, function (m, x, c) {
return (x ? ' ' : '') + c.toUpperCase();
});
$container.appendChild(importTemplate('custom-color', function ($template) {
var $label = $template.querySelector('span');
$label.textContent = label;
var $input = $template.querySelector('input');
$input.name = name;
$input.id = 'custom-color-picker-' + name;
}));
}
if (colors.length)
$container.style.display = display;
setCustomColors();
}
/**
*
*/
function setCustomColors() {
var $custom = document.getElementById('custom-color-option');
var $option = $palette.options[$palette.selectedIndex];
var palette = Palette.fromDataset($option.dataset);
var colors = palette.colors();
for (var i = 0, l = colors.length; i < l; i++) {
var colorName = colors[i];
var colorValue = palette.color(colorName);
$custom.setAttribute('data-color-' + colorName, colorValue);
var $input = document.getElementById('custom-color-picker-' + colorName);
if ($input) {
$input.value = colorValue;
}
}
}
// ==[ Masks ]================================================================
function getCurrentMask() {
return $mask.value;
}
/**
*
*/
function updateMask(callback) {
callback = cb(callback);
var path = $mask.dataset.path;
var mask = getCurrentMask();
if (!mask) {
var $svg = document.createElementNS(SVG_NS, 'svg');
var $g = document.createElementNS(SVG_NS, 'g');
$g.id = 'mask';
$svg.appendChild($g);
return done(null, $svg);
}
loadSVG(path + '/' + mask + '.svg', done);
function done(err, $svg) {
if (err)
return showError(err);
var $oldMask = $badge.querySelector('#mask');
var $newMask = $svg.querySelector('#mask');
$oldMask.parentNode.insertBefore($newMask, $oldMask);
$oldMask.parentNode.removeChild($oldMask);
rasterize(callback);
}
}
// ==[ Options ]==============================================================
/**
*
*/
function initOptions() {
if ($badge)
extractOptions();
var $options = document.getElementById('options');
$options.addEventListener('change', function (event) {
event.stopPropagation();
var option = event.target.name;
if (!options.hasOwnProperty(option))
return;
options[option] = !!event.target.checked;
setOptions();
});
}
/**
*
*/
function extractOptions() {
var $options = document.getElementById('options');
$options.innerHTML = '';
var $optional = $badge.querySelectorAll('.optional');
if (!$optional.length) {
$options.innerHTML = 'None';
return;
}
for (var i = 0, l = $optional.length; i < l; i++) {
var $option = $optional[i];
var label = $option.getAttribute('title');
var name = $option.id;
var enabled = ($option.getAttribute('display') !== 'none');
if (!options.hasOwnProperty(name))
options[name] = enabled;
$option[!!options[name] ? 'removeAttribute' : 'setAttribute']('display', 'none');
$options.appendChild(importTemplate('option', function ($template) {
var $checkbox = $template.querySelector('input');
$checkbox.name = name;
$checkbox.checked = !!options[name];
var $label = $template.querySelector('span');
$label.textContent = label;
}));
}
}
/**
*
*/
function setOptions(callback) {
callback = cb(callback);
if (!$badge)
return callback();
for (var option in options) {
if (options.hasOwnProperty(option)) {
var $node = $badge.getElementById(option);
var visible = !!options[option];
$node && ($node[visible ? 'removeAttribute' : 'setAttribute']('display', 'none'));
}
}
rasterize(callback)
}
// ==[ Glyphs ]===============================================================
/**
*
*/
function getCurrentGlyph() {
return $glyph.value;
}
/**
*
*/
function getCurrentGlyphValue() {
if (!$glyph.value)
return '';
var $i = document.createElement('i');
$i.className = 'fa fa-' + getCurrentGlyph();
document.body.appendChild($i);
var chr = window.getComputedStyle($i, ':before').content;
document.body.removeChild($i);
chr = chr.replace(/["']/g, '');
return chr;
}
/**
*
*/
function updateGlyph(callback) {
var glyph = getCurrentGlyphValue();
if (!glyph)
return setGlyphImage(null, callback);
var $canvas = document.createElement('canvas');
$canvas.width = parseInt($badgeRaster.offsetWidth);
$canvas.height = parseInt($badgeRaster.offsetHeight);
var ctx = $canvas.getContext('2d');
ctx.font = parseInt($canvas.width / 3) + "px FontAwesome";
ctx.fillStyle = getCurrentPalette().color('glyph');
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.shadowColor = "rgba(0,0,0,0.5)";
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
ctx.shadowBlur = 5;
ctx.fillText(glyph, $canvas.width / 2, $canvas.height / 2);
var $image = new Image();
$image.onload = function () {
setGlyphImage($image, callback);
}
$image.src = $canvas.toDataURL("image/png");
// $image.src = "./media/images/cheese.jpg";
}
/**
*
*/
function setGlyphImage($image, callback) {
callback = cb(callback);
var $newGlyph = document.createElementNS(SVG_NS, 'g');
$newGlyph.id = 'glyph';
if (!$image)
return done();
var gxyAdd = 0;
var whAdd = 0;
if (window.size == 'big') {
gxyAdd = 40;
whAdd = 80
} else if (window.size == 'small') {
gxyAdd = -40;
whAdd = -80
}
var iWidth = $image.width;
var iHeight = $image.height;
var rWidth = $badgeRaster.width;
var rHeight = $badgeRaster.height;
var box = $badge.getAttribute('viewBox').split(' ');
var bWidth = parseInt(box[2]);
var bHeight = parseInt(box[3]);
var cx = bWidth / 2 + parseInt(box[0]);
var cy = bHeight / 2 + parseInt(box[1]);
var gWidth = iWidth / (rWidth / bWidth);
var gHeight = iHeight / (rHeight / bHeight);
var gx = cx - (gWidth / 2);
var gy = cy - (gHeight / 2);
var $glyph = document.createElementNS(SVG_NS, 'image');
$glyph.setAttribute('x', gx - gxyAdd);
$glyph.setAttribute('y', gy - gxyAdd);
$glyph.setAttribute('width', gWidth + whAdd);
$glyph.setAttribute('height', gHeight + whAdd);
$glyph.setAttribute('xlink:href', $image.src);
$newGlyph.appendChild($glyph);
done();
function done() {
var $oldGlyph = $badge.getElementById('glyph');
$oldGlyph.parentNode.insertBefore($newGlyph, $oldGlyph);
$oldGlyph.parentNode.removeChild($oldGlyph);
rasterize(callback);
}
}
// ==[ Helpers ]==============================================================
/**
*
*/
function rasterize(callback) {
callback = cb(callback);
var $svg = $badge.cloneNode(true);
var $canvas = document.createElement('canvas');
$canvas.width = parseInt($svg.getAttribute('width'));
$canvas.height = parseInt($svg.getAttribute('height'));
var ctx = $canvas.getContext('2d');
var svg_xml = (new XMLSerializer()).serializeToString($svg);
/*
// This is the 'official' way of doing this. However, Firefox seems to have
// an issue referencing relative fragment URIs created by `createObjectURL`.
// So we're using a base64 encoding hack instead :( Worth noting that if
// there are non-standard unicode characters in the XML, it'll die a death.
var DOMURL = window.URL || window.webkitURL || window;
var blob = new Blob([svg_xml], {type: 'image/svg+xml;charset=utf-8'});
var url = DOMURL.createObjectURL(blob);
*/
var url = 'data:image/svg+xml;base64,' + btoa(svg_xml);
var $img = new Image();
$img.onload = function () {
ctx.drawImage($img, 0, 0);
$badgeRaster.src = $canvas.toDataURL("image/png");
callback();
}
$img.src = url;
}
/**
*
*/
function cb(fn) {
if (typeof fn === 'function')
return fn;
return function () {
};
}
/**
*
*/
function load(url, method, callback) {
var request = new XMLHttpRequest();
request.onload = function () {
callback(null, request.responseXML || request.responseText, request);
}
request.onerror = function (err) {
callback(err, null, request);
}
request.open(method, url, true);
request.send();
}
/**
*
*/
function loadSVG(path, callback) {
if (svgCache[path])
return callback(null, svgCache[path].cloneNode(true));
load(path, 'GET', function (err, $doc, request) {
if (err)
return callback(err);
if (!$doc || typeof $doc === 'string')
return callback(new Error('Not valid SVG'));
svgCache[path] = $doc.getElementsByTagName('svg')[0];
callback(null, svgCache[path].cloneNode(true));
})
}
/**
*
*/
function importTemplate(name, builder) {
var $template = document.getElementById(name + '-template');
if (typeof builder === 'function')
builder($template.content);
return document.importNode($template.content, true);
}
/**
*
*/
function makeCloseButton(callback) {
var $template = importTemplate('close-button');
$template.querySelector('button').addEventListener('click', callback);
return $template;
}