X-Git-Url: http://git.shiar.net/sheet.git/blobdiff_plain/30bf0fcde3f7e44f1ec7bcfc8253dfce8f81e8e6..e5a6d30b2cf44f7791d2cef79f8d08da3891f79d:/word/quiz.js diff --git a/word/quiz.js b/word/quiz.js index ce60d3a..9436f7a 100644 --- a/word/quiz.js +++ b/word/quiz.js @@ -1,41 +1,184 @@ -let quiz = { -dataurl: '/data/wordlist.nl.json', - -next: () => { - let word = quiz.words.shift(); - let question = document.createElement('img'); - question.src = `/data/word/en/${word[0]}.jpg`; - question.style.maxWidth = '50%'; - - let answers = [word[2], quiz.words[1][2], quiz.words[2][2], quiz.words[3][2]] - .sort(() => {return .5 - Math.random()}) // shuffle - let form = document.createElement('ul'); - answers.forEach(suggest => { - let option = document.createElement('li'); - option.onclick = () => { - if (suggest != word[2]) { - // incorrect - option.classList.add('wrong'); - return; - } - option.classList.add('good'); - window.setTimeout(quiz.next, 500); - }; - option.append(suggest); - form.append(option); - }); - quiz.form.append(question, form); -}, - -setup: () => { - fetch(quiz.dataurl).then(res => res.json()).then(json => { - quiz.form = document.getElementById('quiz'); - quiz.words = Object.values(json) - .sort(() => {return .5 - Math.random()}) // shuffle - .map(row => row.split(/:/)) - quiz.next(); - }); -}, +Array.prototype.shuffle = function () { + for (let i = this.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); // random index 0..i + [this[i], this[j]] = [this[j], this[i]]; // swap elements + } + return this; }; -quiz.setup(); +function hashparams() { + const encodedhash = window.location.href.split('#').slice(1) || ''; + if (encodedhash.length == 1) { + // location.hash is not encoded in firefox + return decodeURIComponent(encodedhash).split('#'); + } + return encodedhash; +} + +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 + }, + get html() { + let aliases = this.title.split('/'); + let html = aliases.shift(); + html = html.replace(/\((.+)\)/, '$1'); + for (let alias of aliases) { + html += ` (${alias})`; + } + return html; + }, + 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); + return [...this.data.random()]; + } + + datafilter(json) { + // find viable rows from json data + const selection = new Words(json, this.preset.cat); + + if (this.preset.images) { + selection.filter(id => json[id][2]); + } + if (this.preset.level !== undefined) { + selection.filter(id => json[id][1] <= this.preset.level); + } + + if (this.preset.distinct) { + // remove referenced categories + selection.filter(id => !selection.get(id).subs.length); + } + + return selection; + } + + configure(params = hashparams()) { + 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 if (query === 'debug') { + this.preset.debug = true; + } + else { + this.preset[query] = val; + } + } + this.preset.dataurl = `/data/wordlist.${this.preset.lang}.json` + } + + setup() { + this.form = document.getElementById('quiz'); + } + + load() { + this.configure(); + fetch(this.preset.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() { + this.preset = {images: true, lang: 'en'}; + this.load(); + this.history = []; + window.onbeforeunload = e => { + this.stop('abort'); + }; + window.onhashchange = e => { + this.load(); + }; + } +}