X-Git-Url: http://git.shiar.nl/sheet.git/blobdiff_plain/ebbfbfbb295d4268eba729b0cee0b85679a04e3f..20ca68ae05fb47fc98efa06bb675df13d4c94de5:/word/quiz.js diff --git a/word/quiz.js b/word/quiz.js index 68c2ee8..dce5f7a 100644 --- a/word/quiz.js +++ b/word/quiz.js @@ -6,63 +6,118 @@ Array.prototype.shuffle = function () { return this; }; -Set.prototype.filter = function (f) { - return new Set([...this].filter(f)); -}; +function hashparams() { + // location.hash is not encoded in firefox + const encodedhash = (window.location.href.split('#'))[1] || ''; + return decodeURIComponent(encodedhash).split('#'); +} + +class Words { + constructor(data, root = undefined) { + this.data = data; + this.selection = root || this.data[''][3]; + this.visible = new Set(root || Object.keys(data).flatMap(id => id && parseInt(id))); + if (root) { + let children = root; + for (let loop = 0; children.length && loop < 20; loop++) { + for (let child of children) this.visible.add(child); + children = children.map(cat => data[cat][3]).filter(is => is).flat(); + } + } + } + + filter(f) { + // keep only matching entries, and root selection regardless + this.visible = new Set([...this.visible].filter(f).concat(this.selection)); + } + + *root() { + for (let i of this.selection) { + if (!this.has(i)) { + continue; + } + yield this.get(i); + } + } + + *random() { + let order = [...this.visible.keys()].shuffle(); + for (let i of order) { + if (!this.has(i)) { + continue; + } + yield this.get(i); + } + } + + has(id) { + return this.visible.has(id); + } + + subs(id) { + let refs = this.data[id][3]; + if (!refs) { + return []; + } + for (let ref of refs) { + // retain orphaned references in grandparent categories + if (!this.has(ref)) { + refs = refs.concat(this.subs(ref)); + } + } + return refs; + } + + get(id) { + if (!this.has(id)) { + return; + } + const p = this; + const row = this.data[id]; + return row && { + id: id, + title: row[0], + get label() { + return row[0].replace(/\/.*/, ''); // primary form + }, + level: row[1], + imgid: row[2], + thumb(size = 32) { + return `/data/word/${size}/${row[2]}.jpg`; + }, + get subs() { + return p.subs(id).map(e => p.get(e)); + }, + }; + } +} 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(); + return [...this.data.random()]; } datafilter(json) { // find viable rows from json data - let ids = new Set(Object.keys(json)); - const selection = {...json}; // clone + const selection = new Words(json, this.preset.cat); - for (let cat of selection[''][3]) { - if (selection[cat]) - selection[cat][1] = 0; // keep root categories - } - - if (this.preset.cat !== undefined) { - ids.clear(); - let children = this.preset.cat; - for (let loop = 0; children.length && loop < 20; loop++) { - for (let child of children) ids.add(child.toString()); - children = children.map(cat => json[cat][3]).filter(is => is).flat() - } - } if (this.preset.images) { - ids = ids.filter(id => json[id][2]); + selection.filter(id => json[id][2]); } if (this.preset.level !== undefined) { - ids = ids.filter(id => json[id][1] <= this.preset.level); + selection.filter(id => json[id][1] <= this.preset.level); } - // keep only wanted ids - for (let id in selection) { - if (id && !ids.has(id)) { - delete selection[id]; - } + if (this.preset.distinct) { + // remove referenced categories + selection.filter(id => !selection.get(id).subs.length); } - // 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) { + configure(params = hashparams()) { const opts = new Map(params.map(arg => arg.split(/[:=](.*)/))); for (let [query, val] of opts) { if (query.match(/^\d+$/)) { @@ -78,8 +133,12 @@ class WordQuiz { this.preset.dataurl = `/data/wordlist.${this.preset.lang}.json` } + setup() { + this.form = document.getElementById('quiz'); + } + load() { - this.configure(window.location.hash.split('#')); + this.configure(); fetch(this.preset.dataurl).then(res => res.json()).then(json => { this.words = this.dataselect(json) this.setup();