496 lines
21 KiB
JavaScript
496 lines
21 KiB
JavaScript
var nullFunc = () => { };
|
|
var poppingState = false;
|
|
var closingGallery = false;
|
|
var closingFilterBuilder = false;
|
|
|
|
/* jshint ignore:start */
|
|
var do_eval = (exp) => eval(exp);
|
|
/* jshint ignore:end */
|
|
|
|
function onPopState(event) {
|
|
resetAppState();
|
|
let hash = event.target.location.hash;
|
|
poppingState = true;
|
|
if (hash.startsWith('#js:'))
|
|
do_eval(decodeURIComponent(hash.substr(4)));
|
|
poppingState = false;
|
|
closingGallery = false;
|
|
closingFilterBuilder = false;
|
|
}
|
|
|
|
function onInit() {
|
|
colorAccuracyJSWall.classList.add('nodisplay');
|
|
colorAccuracyJSProtectedContent.classList.remove('nodisplay');
|
|
fillStaticPartOfColorPalette();
|
|
let hash = window.location.hash;
|
|
poppingState = true;
|
|
if (hash === '')
|
|
window.history.replaceState(null, document.title, '#');
|
|
else if (hash.startsWith('#js:'))
|
|
do_eval(decodeURIComponent(hash.substr(4)));
|
|
poppingState = false;
|
|
updateColorPaletteAverage();
|
|
}
|
|
|
|
window.onpopstate = onPopState;
|
|
var imgQualities = {
|
|
'thumb': imgs_thumb,
|
|
'small': imgs_small,
|
|
'dcent': imgs_dcent,
|
|
'large': imgs_large,
|
|
'full': imgs_full
|
|
};
|
|
var qualityName = {
|
|
'thumb': 'Thumbnail',
|
|
'small': 'Small',
|
|
'dcent': 'Decent',
|
|
'large': 'Large',
|
|
'full': 'Full'
|
|
};
|
|
|
|
function formatFileSize(bytecount) {
|
|
units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
magnitude = 0;
|
|
while (bytecount >= 2048) {
|
|
bytecount /= 1024;
|
|
magnitude++;
|
|
}
|
|
bytecount = Math.round(bytecount * 10) / 10;
|
|
return `${bytecount} ${units[magnitude]}`;
|
|
}
|
|
|
|
function openGallery(image, quality) {
|
|
if (closingGallery) { setTimeout(closeGallery, 50); return; }
|
|
if (typeof (quality) == typeof (undefined)) quality = 'dcent';
|
|
if (!poppingState)
|
|
window.history.pushState(null, document.title,
|
|
`#js:openGallery(${encodeURIComponent(JSON.stringify(image))}, ${encodeURIComponent(JSON.stringify(quality))})`
|
|
);
|
|
galleryBrowser.classList.remove('nodisplay');
|
|
item = images[image];
|
|
img = imgQualities[quality][image];
|
|
galleryImage.style.backgroundImage = `url('./${img}')`;
|
|
// Qualities highlight
|
|
if (quality == 'thumb') qualityThumb.style.background = "#619A3588";
|
|
else qualityThumb.style.background = null;
|
|
if (quality == 'small') qualitySmall.style.background = "#619A3588";
|
|
else qualitySmall.style.background = null;
|
|
if (quality == 'dcent') qualityDcent.style.background = "#619A3588";
|
|
else qualityDcent.style.background = null;
|
|
if (quality == 'large') qualityLarge.style.background = "#619A3588";
|
|
else qualityLarge.style.background = null;
|
|
if (quality == 'full') qualityFull.style.background = "#619A3588";
|
|
else qualityFull.style.background = null;
|
|
// Set callback
|
|
seeThumb.setAttribute('onclick', `openGallery(${JSON.stringify(image)}, 'thumb')`);
|
|
seeSmall.setAttribute('onclick', `openGallery(${JSON.stringify(image)}, 'small')`);
|
|
seeDcent.setAttribute('onclick', `openGallery(${JSON.stringify(image)}, 'dcent')`);
|
|
seeLarge.setAttribute('onclick', `openGallery(${JSON.stringify(image)}, 'large')`);
|
|
seeFull.setAttribute('onclick', `openGallery(${JSON.stringify(image)}, 'full')`);
|
|
// Set download targets
|
|
downloadThumb.href = './' + imgQualities.thumb[image];
|
|
downloadSmall.href = './' + imgQualities.small[image];
|
|
downloadDcent.href = './' + imgQualities.dcent[image];
|
|
downloadLarge.href = './' + imgQualities.large[image];
|
|
downloadFull.href = './' + imgQualities.full[image];
|
|
// Set download names
|
|
downloadThumb.setAttribute('download', `${name} - ${image} - ${artists[item.artist].name} - ${item.date.join('-')} (${qualityName.thumb}).${formats[image].thumb}`);
|
|
downloadSmall.setAttribute('download', `${name} - ${image} - ${artists[item.artist].name} - ${item.date.join('-')} (${qualityName.small}).${formats[image].small}`);
|
|
downloadDcent.setAttribute('download', `${name} - ${image} - ${artists[item.artist].name} - ${item.date.join('-')} (${qualityName.dcent}).${formats[image].dcent}`);
|
|
downloadLarge.setAttribute('download', `${name} - ${image} - ${artists[item.artist].name} - ${item.date.join('-')} (${qualityName.large}).${formats[image].large}`);
|
|
downloadFull.setAttribute('download', `${name} - ${image} - ${artists[item.artist].name} - ${item.date.join('-')} (${qualityName.full}).${formats[image].full}`);
|
|
// Set image sizes
|
|
sizeThumb.innerText = `(${formatFileSize(sizes[image].thumb)}) - ${formats[image].thumb}`;
|
|
sizeSmall.innerText = `(${formatFileSize(sizes[image].small)}) - ${formats[image].small}`;
|
|
sizeDcent.innerText = `(${formatFileSize(sizes[image].dcent)}) - ${formats[image].dcent}`;
|
|
sizeLarge.innerText = `(${formatFileSize(sizes[image].large)}) - ${formats[image].large}`;
|
|
sizeFull.innerText = `(${formatFileSize(sizes[image].full)}) - ${formats[image].full}`;
|
|
}
|
|
|
|
var checkVisible = (elem) => {
|
|
let posY = (elm) => {
|
|
let test = elm;
|
|
let top = 0;
|
|
while (!!test && test.tagName.toLowerCase() !== "body") {
|
|
top += test.offsetTop;
|
|
test = test.offsetParent;
|
|
}
|
|
return top;
|
|
};
|
|
|
|
let viewPortHeight = () => {
|
|
let de = document.documentElement;
|
|
if (!!window.innerWidth) { return window.innerHeight; }
|
|
else if (de && !isNaN(de.clientHeight)) { return de.clientHeight; }
|
|
return 0;
|
|
};
|
|
|
|
let scrollY = () => {
|
|
if (window.pageYOffset) { return window.pageYOffset; }
|
|
return Math.max(document.documentElement.scrollTop, document.body.scrollTop);
|
|
};
|
|
|
|
let checkvisible = (elm) => {
|
|
let vpH = viewPortHeight(); // Viewport Height
|
|
let st = scrollY(); // Scroll Top
|
|
let y = posY(elm);
|
|
let dy = elm.offsetHeight;
|
|
return ((vpH + st) > y) && (st < (y + dy));
|
|
};
|
|
|
|
return checkvisible(elem);
|
|
};
|
|
|
|
|
|
function filterArtist(artist) {
|
|
resetAppState();
|
|
if (!poppingState) {
|
|
|
|
window.history.pushState(null, document.title,
|
|
`#js:filterArtist(${encodeURIComponent(JSON.stringify(artist))})`
|
|
);
|
|
}
|
|
document.querySelector('#sideCard>.controlButtons>.showFiltered').classList.add('selected');
|
|
nestedElements.classList.add('nodisplay');
|
|
listElements.classList.remove('nodisplay');
|
|
filterNav.classList.remove('nodisplay');
|
|
let item = artists[artist];
|
|
filterIcon.className = "fa fa-user";
|
|
filterText.innerText = item.name;
|
|
for (let key in images) {
|
|
image = images[key];
|
|
let hideable_row = document.getElementById('row_' + key);
|
|
if (image.artist == artist) {
|
|
document.getElementById('list-' + key).classList.remove('nodisplay');
|
|
if (hideable_row !== null) hideable_row.classList.remove('nodisplay');
|
|
} else {
|
|
document.getElementById('list-' + key).classList.add('nodisplay');
|
|
if (hideable_row !== null) hideable_row.classList.add('nodisplay');
|
|
}
|
|
}
|
|
updateColorPaletteAverage();
|
|
}
|
|
|
|
function filterTag(tag) {
|
|
resetAppState();
|
|
if (!poppingState)
|
|
window.history.pushState(null, document.title,
|
|
`#js:filterTag(${encodeURIComponent(JSON.stringify(tag))})`
|
|
);
|
|
document.querySelector('#sideCard>.controlButtons>.showFiltered').classList.add('selected');
|
|
nestedElements.classList.add('nodisplay');
|
|
listElements.classList.remove('nodisplay');
|
|
filterNav.classList.remove('nodisplay');
|
|
let item = tags[tag];
|
|
filterIcon.className = "fa fa-" + item.icon;
|
|
filterText.innerText = item.name;
|
|
for (let key in images) {
|
|
image = images[key];
|
|
let hideable_row = document.getElementById('row_' + key);
|
|
if ((image.technology == tag) || (image.tags.indexOf(tag) >= 0)) {
|
|
document.getElementById('list-' + key).classList.remove('nodisplay');
|
|
if (hideable_row !== null) hideable_row.classList.remove('nodisplay');
|
|
} else {
|
|
document.getElementById('list-' + key).classList.add('nodisplay');
|
|
if (hideable_row !== null) hideable_row.classList.add('nodisplay');
|
|
}
|
|
}
|
|
updateColorPaletteAverage();
|
|
}
|
|
|
|
function showAsList() {
|
|
resetAppState();
|
|
if (!poppingState)
|
|
window.history.pushState(null, document.title,
|
|
`#js:showAsList()`
|
|
);
|
|
nestedElements.classList.add('nodisplay');
|
|
listElements.classList.remove('nodisplay');
|
|
filterNav.classList.add('nodisplay');
|
|
for (let key in images) {
|
|
image = images[key];
|
|
let hideable_row = document.getElementById('row_' + key);
|
|
document.getElementById('list-' + key).classList.remove('nodisplay');
|
|
if (hideable_row !== null) hideable_row.classList.remove('nodisplay');
|
|
}
|
|
}
|
|
|
|
function buildFilters() {
|
|
if (closingFilterBuilder) { setTimeout(closeFilterBuilder, 50); return; }
|
|
resetAppState();
|
|
if (!poppingState)
|
|
window.history.pushState(null, document.title,
|
|
`#js:buildFilters()`
|
|
);
|
|
document.querySelector('#sideCard>.controlButtons>.showFiltered').classList.add('selected');
|
|
document.getElementById('filterBuilder').classList.remove('nodisplay');
|
|
}
|
|
|
|
function resetAppState(skipScroll) {
|
|
document.querySelector('#sideCard>.controlButtons>.showNested').classList.remove('selected');
|
|
document.querySelector('#sideCard>.controlButtons>.showListed').classList.remove('selected');
|
|
document.querySelector('#sideCard>.controlButtons>.showFiltered').classList.remove('selected');
|
|
document.getElementById('filterBuilder').classList.add('nodisplay');
|
|
nestedElements.classList.remove('nodisplay');
|
|
listElements.classList.add('nodisplay');
|
|
filterNav.classList.add('nodisplay');
|
|
galleryBrowser.classList.add('nodisplay');
|
|
for (let item of Array.from(document.querySelectorAll('#colorAccuracy tr.hideablePaletteItem')))
|
|
item.classList.remove('nodisplay');
|
|
for (let item of Array.from(document.querySelectorAll('#sideCard>.fastNavigation>.fastNavigationCard'))) {
|
|
item.classList.remove('nodisplay');
|
|
item.classList.remove('displaying');
|
|
item.onclick = null;
|
|
}
|
|
updateColorPaletteAverage();
|
|
if (!skipScroll)
|
|
window.scrollTo(0, 0);
|
|
}
|
|
|
|
var clearFilters = () => { resetAppState(); window.history.back(); };
|
|
var closeGallery = () => { resetAppState(true); closingGallery = true; window.history.back(); };
|
|
var closeFilterBuilder = () => { resetAppState(true); closingFilterBuilder = true; window.history.back(); };
|
|
|
|
var rgxHexColor = /^#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/;
|
|
var parseHexColor = x => rgxHexColor.exec(x).slice(1).map(i => parseInt(i, 16));
|
|
var toHexColor = l => '#' + (l.map(i => ('0' + Math.round(i).toString(16)).slice(-2)).join('')).toUpperCase();
|
|
var zip = (...rows) => [...rows[0]].map((_, c) => rows.map(row => row[c]));
|
|
var euclideanDistance2 = (a1, a2) => zip(a1, a2).map(x => x[0] - x[1]).map(x => x * x).reduce((a, v) => a + v, 0);
|
|
var euclideanDistance = (a1, a2) => Math.sqrt(euclideanDistance2(a1, a2));
|
|
var avg = (...values) => [...values].reduce((acc, val) => acc + val) / values.length;
|
|
var mixColorChannel = (...values) => Math.sqrt(values.map(v => v * v).reduce((a, v) => a + v) / values.length);
|
|
|
|
function inflateFontAwesome(icon) {
|
|
let fa = document.createElement('i');
|
|
fa.setAttribute('class', `fa fa-${icon}`);
|
|
fa.setAttribute('aria-hidden', 'true');
|
|
return fa;
|
|
}
|
|
|
|
function round(number, digits) {
|
|
if (digits === undefined) digits = 0;
|
|
let factor = Math.pow(10, digits);
|
|
return Math.round(number * factor) / factor;
|
|
}
|
|
|
|
function colorSimilarity(color1, color2) {
|
|
let c1 = parseHexColor(color1).map(x => x / 255);
|
|
let c2 = parseHexColor(color2).map(x => x / 255);
|
|
return 1 - (euclideanDistance(c1, c2) / Math.sqrt(3));
|
|
}
|
|
|
|
function colorAverage(...colors) {
|
|
let engine = avg; // Easy to implement; bad colors
|
|
engine = mixColorChannel; // Simulates light mixing with gamma=2.0
|
|
if (colors.length == 0)
|
|
return null;
|
|
else
|
|
return toHexColor(
|
|
zip(
|
|
...colors.map(parseHexColor)
|
|
).map(ch => engine(...ch))
|
|
);
|
|
}
|
|
|
|
function determineLightOrDark(color) { // True: light, False: dark
|
|
// https://en.wikipedia.org/wiki/Grayscale#Luma_coding_in_video_systems
|
|
let y = [0.2126, 0.7152, 0.0722];
|
|
let c = parseHexColor(color);
|
|
let hsp = Math.sqrt(
|
|
zip(
|
|
y,
|
|
c.map(x => x * x)
|
|
).map(
|
|
v => v[0] * v[1]
|
|
).reduce(
|
|
(a, v) => a + v
|
|
)
|
|
);
|
|
return hsp > 127;
|
|
}
|
|
|
|
function updateColorPaletteAverage() {
|
|
let fields = Array.from(document.querySelectorAll('#row_avg td'));
|
|
for (let fld of fields) {
|
|
fld.setAttribute("class", "clr_" + fld.id.slice(8));
|
|
fld.setAttribute("title", "");
|
|
fld.style.background = null;
|
|
while (fld.firstChild) fld.firstChild.remove();
|
|
}
|
|
let relevantColors = {};
|
|
for (let color of color_list) {
|
|
if (relevantColors[color] === undefined)
|
|
relevantColors[color] = [];
|
|
for (let idx in images) {
|
|
let hideable_row = document.getElementById('row_' + idx);
|
|
let image = images[idx];
|
|
let colorUsed = ((image.color || {})[color] || {}).colorUsed;
|
|
if (colorUsed !== null && colorUsed !== undefined && !hideable_row.classList.contains('nodisplay')) {
|
|
relevantColors[color].push(colorUsed);
|
|
}
|
|
}
|
|
}
|
|
for (let idx in relevantColors) {
|
|
let colorsShown = relevantColors[idx];
|
|
let cell = document.querySelector('#row_avg td.clr_' + idx);
|
|
let canonical = canonical_colors[idx].color;
|
|
let avgColor = colorAverage(...colorsShown);
|
|
if (avgColor !== null) {
|
|
cell.style.background = avgColor;
|
|
let isLight = determineLightOrDark(avgColor);
|
|
let similarity = round(colorSimilarity(avgColor, canonical) * 100, 2);
|
|
cell.classList.add(isLight ? 'blackText' : 'whiteText');
|
|
cell.appendChild(document.createTextNode(avgColor));
|
|
cell.appendChild(document.createElement('br'));
|
|
cell.appendChild(document.createTextNode(`(${similarity}%)`));
|
|
cell.title = `${similarity}% similar to the ideal value`;
|
|
}
|
|
}
|
|
}
|
|
|
|
function fillStaticPartOfColorPalette() {
|
|
//bodyparts => Display names for body parts
|
|
//canonical_colors => "Right" colors
|
|
let basecolors = color_list.slice(0, color_repeated[false]);
|
|
let deviatedcolors = color_list.slice(color_repeated[false]);
|
|
for (let color of basecolors) {
|
|
let canonical = canonical_colors[color].color;
|
|
for (let imgid in images) {
|
|
let image = images[imgid];
|
|
let colordef = image.color[color];
|
|
if (colordef !== undefined) {
|
|
let cell = `#colorAccuracy table #row_${imgid} .clr_${color}`;
|
|
cell = document.querySelector(cell);
|
|
while (cell.firstChild) cell.firstChild.remove();
|
|
let similarity = round(colorSimilarity(colordef.colorUsed, canonical) * 100, 2);
|
|
cell.style.background = colordef.colorUsed;
|
|
cell.classList.add(colordef.colorIsLight ? 'blackText' : 'whiteText');
|
|
cell.appendChild(document.createTextNode(colordef.colorUsed));
|
|
cell.appendChild(document.createElement('br'));
|
|
cell.appendChild(document.createTextNode(`(${similarity}%)`));
|
|
cell.title = `"${bodyparts[colordef.targetOnImage]}" color\n${similarity}% similar to the ideal value`;
|
|
}
|
|
}
|
|
}
|
|
for (let color of deviatedcolors) {
|
|
let canonical = canonical_colors[color].color;
|
|
for (let imgid in images) {
|
|
let image = images[imgid];
|
|
let colordef = image.color[color];
|
|
if (colordef !== undefined) {
|
|
let cell = `#colorAccuracy table #row_${imgid} .clr_${color}`;
|
|
cell = document.querySelector(cell);
|
|
while (cell.firstChild) cell.firstChild.remove();
|
|
if (colordef === null) {
|
|
cell.appendChild(inflateFontAwesome('2x fa-times-circle'));
|
|
cell.title = 'This feature is missing on the artwork';
|
|
cell.classList.add('featureMissing');
|
|
} else {
|
|
let shouldBeUsing = canonical;
|
|
if (image.color[colordef.targetAppropriate])
|
|
shouldBeUsing = image.color[colordef.targetAppropriate].colorUsed;
|
|
let problem = (colordef.colorUsed != shouldBeUsing);
|
|
cell.style.background = colordef.colorUsed;
|
|
cell.classList.add(colordef.colorIsLight ? 'blackText' : 'whiteText');
|
|
cell.appendChild(document.createTextNode(colordef.colorUsed));
|
|
cell.appendChild(document.createElement('br'));
|
|
cell.appendChild(inflateFontAwesome(problem ? 'warning' : 'info-circle'));
|
|
let title = `"${bodyparts[colordef.targetOnImage]}" color\n`;
|
|
if (problem)
|
|
title += `Should have the same color as "${bodyparts[colordef.targetAppropriate]}"`;
|
|
else
|
|
title += `Same color as "${bodyparts[colordef.targetAppropriate]}"`;
|
|
let parent = canonical;
|
|
let parentColordef = image.color[colordef.targetAppropriate];
|
|
if (parentColordef !== undefined && parentColordef !== null)
|
|
parent = parentColordef.colorUsed;
|
|
let sp = round(colorSimilarity(colordef.colorUsed, parent) * 100, 2);
|
|
let sc = round(colorSimilarity(colordef.colorUsed, canonical) * 100, 2);
|
|
title += '\n';
|
|
if (parentColordef !== undefined && parentColordef !== null)
|
|
title += `${sp}% similar to the one used as "${bodyparts[colordef.targetAppropriate]}"\n`;
|
|
title += `${sc}% similar to the ideal value`;
|
|
cell.title = title;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function onInitRegister() {
|
|
if ('serviceWorker' in navigator) {
|
|
navigator.serviceWorker.register('/sw.js').then(function (registration) {
|
|
console.log('ServiceWorker registration successful with scope: ', registration.scope);
|
|
}, function (err) {
|
|
console.log('ServiceWorker registration failed: ', err);
|
|
});
|
|
}
|
|
}
|
|
|
|
function keepFastNavigationUpdated() {
|
|
setTimeout(keepFastNavigationUpdated, 30);
|
|
document.querySelectorAll('#sideCard>fastNavigation>fastNavigationCard');
|
|
let nestedElement = document.getElementById('nestedElements');
|
|
let listedElement = document.getElementById('listElements');
|
|
let nestedButton = document.querySelector('#sideCard>.controlButtons>.showNested');
|
|
let listedButton = document.querySelector('#sideCard>.controlButtons>.showListed');
|
|
let filterButton = document.querySelector('#sideCard>.controlButtons>.showFiltered');
|
|
if (filterButton.classList.contains('selected')) {
|
|
nestedButton.classList.remove('selected');
|
|
listedButton.classList.remove('selected');
|
|
} else {
|
|
if (nestedElement.offsetParent === null)
|
|
nestedButton.classList.remove('selected');
|
|
else
|
|
nestedButton.classList.add('selected');
|
|
if (listedElement.offsetParent === null)
|
|
listedButton.classList.remove('selected');
|
|
else
|
|
listedButton.classList.add('selected');
|
|
}
|
|
let main = document.getElementById('main');
|
|
let sidebarsHidden = document.getElementById('sideCard').offsetParent === null;
|
|
if (sidebarsHidden) main.classList.add('noSidebars');
|
|
else main.classList.remove('noSidebars');
|
|
let imagesElement = [
|
|
nestedElement,
|
|
listedElement,
|
|
].filter(x => !x.classList.contains('nodisplay'))[0];
|
|
let generateSmoothScrollCallback = (element) => (() => {
|
|
element.scrollIntoView({
|
|
behavior: 'smooth',
|
|
block: 'center',
|
|
inline: 'center'
|
|
});
|
|
if (!checkVisible(element))
|
|
setTimeout(generateSmoothScrollCallback(element), 100);
|
|
});
|
|
for (let cardElement of Array.from(imagesElement.getElementsByClassName('card'))) {
|
|
let element = cardElement.getElementsByTagName('img')[0];
|
|
let imagenum = parseInt(cardElement.getAttribute('id').split('-')[1]);
|
|
let fastNavElem = document.getElementById(`fastnav-${imagenum}`);
|
|
let detailsElem = document.getElementById(`details-${imagenum}`);
|
|
if (checkVisible(element))
|
|
fastNavElem.classList.add('displaying');
|
|
else
|
|
fastNavElem.classList.remove('displaying');
|
|
if (checkVisible(element))
|
|
detailsElem.classList.remove('collapsed');
|
|
else
|
|
detailsElem.classList.add('collapsed');
|
|
if (element.offsetParent === null)
|
|
fastNavElem.classList.add('nodisplay');
|
|
else
|
|
fastNavElem.classList.remove('nodisplay');
|
|
fastNavElem.onclick = generateSmoothScrollCallback(element);
|
|
let titleElem = detailsElem.getElementsByTagName('span')[0];
|
|
titleElem.onclick = generateSmoothScrollCallback(element);
|
|
titleElem.getElementsByTagName('img')[0].onclick = generateSmoothScrollCallback(element);
|
|
titleElem.getElementsByTagName('span')[0].onclick = generateSmoothScrollCallback(element);
|
|
}
|
|
}
|
|
setTimeout(keepFastNavigationUpdated, 10);
|
|
|
|
onInit();
|
|
onInitRegister();
|