let selection = {};
function updateSelection(changes, data) {
Object.assign(selection, changes);
data = Object.create(data);
data.selection = selection;
for (root of document.querySelectorAll('[data-selection]'))
updateSection(root, data);
for (var k in changes)
applyClassIf(k + '-select', classSelector(changes[k]));
}
function updateSection(root, data) {
let changed = root.selection == null;
root.selection ||= {};
for (key of root.dataset.selection.split(',')) {
if (!key) continue;
if (data.selection[key] != root.selection[key]) {
root.selection[key] = data.selection[key];
changed = true;
}
if (data.selection[key] == null) {
root.hidden = true;
return;
}
}
if (changed) {
root.hidden = false;
for (tmpl of root.getElementsByTagName('template'))
reinflate(tmpl, data);
}
}
function inflate(tmpl, data, parent, next) {
let evalExpr = (expr, data) => eval('with (data) { ' + expr + ' }');
let interpolate = (str, data) =>
str.replace(/\{\{(.*?)\}\}/g, (_, expr) => evalExpr(expr, data))
if (tmpl.nodeName != 'TEMPLATE') {
let clone = tmpl.cloneNode();
clone.inflated = true;
if (clone instanceof Text)
clone.textContent = interpolate(clone.textContent, data);
if (clone instanceof Element) {
for (attr of clone.attributes)
attr.value = interpolate(attr.value, data);
for (c of tmpl.childNodes)
inflate(c, data, clone, null);
}
return parent.insertBefore(clone, next);
}
if ('use' in tmpl.dataset)
return inflate(document.getElementById(tmpl.dataset.use), data, parent, next);
function handle(data) {
for (c of tmpl.content.childNodes)
inflate(c, data, parent, next);
}
const directives = {
'for': (nameInExpr, data, proceed) => {
let [name, expr] = nameInExpr.split(' in ');
let newData = Object.create(data);
let index = 0;
for (val of evalExpr(expr, data) || []) {
newData[name] = val;
newData[name + '_index'] = index++;
proceed(newData);
}
},
'if': (expr, data, proceed) => { if (evalExpr(expr, data)) proceed(data); },
'let': (nameEqExpr, data, proceed) => {
let [name, expr] = nameEqExpr.split(' = ');
let newData = Object.create(data);
newData[name] = evalExpr(expr, data);
proceed(newData);
},
}
for (let [dir, value] of Object.entries(tmpl.dataset).reverse()) {
if (dir in directives) {
let proceed = handle;
handle = (data) => directives[dir](value, data, proceed);
}
}
handle(data);
}
function reinflate(tmpl, data) {
while (tmpl.nextSibling && tmpl.nextSibling.inflated)
tmpl.parentNode.removeChild(tmpl.nextSibling);
inflate(tmpl, data, tmpl.parentNode, tmpl.nextSibling);
}
function mouseEventHandler(event, targetSelector, targetToID, data) {
var target = event.type == "mouseout" ? null : event.target.closest(targetSelector);
let selTarget = k => (target && targetToID[k]) ? targetToID[k](target) : null;
if (event.type == "click") {
let newSel = {};
for (var k in targetToID) {
if (k == 'hover') continue;
let t = selTarget(k);
newSel[k] = t;
}
updateSelection(newSel, data);
} else if ("hover" in targetToID) {
applyClassIf("hover", classSelector(selTarget("hover")));
}
}
function watch(rootSelector, targetSelector, targetToID, data) {
var root = document.querySelector(rootSelector);
for (event of ['mouseout', 'mousemove', 'click'])
root.addEventListener(event, e => mouseEventHandler(e, targetSelector, targetToID, data));
}
function watchSelection(data) {
let lastIter = (bb) => `${bb}:${data.cfg[bb].iters}`;
watch('#code', '.c', {
hover: e => e.dataset.elt,
bb: e => e.dataset.bb,
elt: e => e.dataset.elt,
iter: e => (selection.iter && selection.bb == e.dataset.bb) ? selection.iter : lastIter(e.dataset.bb),
}, data);
watch('#cfg', '.bb', {
hover: e => e.id,
bb: e => e.id,
elt: e => e.id + ".0",
iter: e => lastIter(e.id),
}, data);
watch('#timeline', '.entry', {
hover: e => [e.id, e.dataset.bb],
bb: e => e.dataset.bb,
elt: e => e.dataset.bb + ".0",
iter: e => e.id,
}, data);
watch('#bb-elements', 'tr', {
hover: e => e.id,
elt: e => e.id,
}, data);
watch('#iterations', '.chooser', {
hover: e => e.dataset.iter,
iter: e => e.dataset.iter,
}, data);
updateSelection({}, data);
}
function applyClassIf(cls, query) {
document.querySelectorAll('.' + cls).forEach(elt => elt.classList.remove(cls));
document.querySelectorAll(query).forEach(elt => elt.classList.add(cls));
}
function classSelector(cls) {
if (cls == null) return null;
if (Array.isArray(cls)) return cls.map(classSelector).join(', ');
var escaped = cls.replace('.', '\\.').replace(':', '\\:');
return '.' + escaped + ", #" + escaped;
}
function addBBColors(n) {
let sheet = new CSSStyleSheet();
options = [0x001, 0x010, 0x011, 0x100, 0x101, 0x110, 0x111];
function color(hex) {
return "#" + hex.toString(16).padStart(3, "0");
}
function add(selector, property, hex) {
sheet.insertRule(`${selector} { ${property}: ${color(hex)}; }`)
}
for (var i = 0; i < n; ++i) {
let opt = options[i%options.length];
add(`.B${i}`, 'background-color', 0xfff - 2*opt);
add(`#B${i} polygon`, 'fill', 0xfff - 2*opt);
add(`#B${i} polygon`, 'stroke', 0x888 - 4*opt);
}
document.adoptedStyleSheets.push(sheet);
}