X-Git-Url: http://git.shiar.nl/sheet.git/blobdiff_plain/8c60e0a0d2a6c02068e68f42fc76f196087ba516..9b8d7e610cef31f15f12c1058dfd45efffac5fb0:/word/quiz.js diff --git a/word/quiz.js b/word/quiz.js index e2e8cb6..f47fde1 100644 --- a/word/quiz.js +++ b/word/quiz.js @@ -6,51 +6,104 @@ Array.prototype.shuffle = function () { return this; }; +Set.prototype.filter = function (f) { + return new Set([...this].filter(f)); +}; + class WordQuiz { dataselect(json) { + this.data = this.datafilter(json); + let rows = Object.values(this.data); + rows = rows.filter(row => !row[3].length); // remove referenced categories + return rows.shuffle(); + } + + datafilter(json) { // find viable rows from json data - let rows = Object.values(json); - if (this.preset.cat !== undefined) { - let cats = {}; // category lookup - for (let i in json) { - let cat = json[i][3]; - if (!cats[cat]) cats[cat] = []; - cats[cat].push(i) - } + let ids = new Set(Object.keys(json)); + const selection = {...json}; // clone + + for (let cat of selection[''][3]) { + if (selection[cat]) + selection[cat][1] = 0; // keep root categories + } - rows = []; - let children = cats[this.preset.cat]; + if (this.preset.cat !== undefined) { + ids.clear(); + let children = this.preset.cat; for (let loop = 0; children.length && loop < 20; loop++) { - rows.push(...children); - children = children.map(cat => cats[cat]).filter(is => is).flat(); + for (let child of children) ids.add(child.toString()); + children = children.map(cat => json[cat][3]).filter(is => is).flat() } - rows = rows.map(row => json[row]).filter(row => row[2]); + } + if (this.preset.images) { + ids = ids.filter(id => json[id][2]); } if (this.preset.level !== undefined) { - rows = rows.filter(row => row[1] <= this.preset.level); + ids = ids.filter(id => json[id][1] <= this.preset.level); } - { - let cats = new Set(); - let subcats = rows.map(row => row[3]); // direct parents - for (let loop = 0; subcats.length && loop < 20; loop++) { - subcats.forEach(cat => cats.add(cat)); - subcats = subcats.map(row => json[row] && json[row][3]).filter(val => val); // recurse grandparents + // keep only wanted ids + for (let id in selection) { + if (id && !ids.has(id)) { + delete selection[id]; + } + } + + // retain orphaned references in grandparent categories + for (let id in selection) { + selection[id][3] = function subresolve(subs) { + //console.log(subs); + return (subs || []).flatMap(sub => + sub in selection ? [sub] : json[sub] ? subresolve(json[sub][3]) : [] + ); + }(selection[id][3]); + } + return selection; + } + + configure(params) { + const opts = new Map(params.map(arg => arg.split(/[:=](.*)/))); + for (let [query, val] of opts) { + if (query.match(/^\d+$/)) { + this.preset.cat = [parseInt(query)]; + } + else if (query === 'level') { + this.preset.level = parseInt(val); + } + else { + this.preset[query] = val; } - rows = rows.filter(row => !cats.has(row[2])); // remove referenced categories } - return rows.shuffle(); } load(dataurl) { - this.preset = {}; + this.configure(window.location.hash.split('#')); fetch(dataurl).then(res => res.json()).then(json => { this.words = this.dataselect(json) this.setup(); }); } + log(...args) { + this.history.push([new Date().toISOString(), ...args]); + } + + stop(...args) { + this.log(...args); + window.onbeforeunload = null; + fetch('/word/report', {method: 'POST', body: JSON.stringify(this.history)}); + } + constructor(dataurl) { + this.preset = {images: true}; this.load(dataurl); + this.history = []; + window.onbeforeunload = e => { + this.stop('abort'); + }; + window.onhashchange = e => { + this.load(dataurl); + }; } }