class WordFinder extends WordQuiz {
namehtml(name) {
- //let wbr = new RegExp('\w{4} [^aoeuiyc\W] [rl]?+ \K (?= [^aoeuiy\W] [rl]? [aoeuiy] \w)', 'g');
let aliases = name.split('/');
let html = aliases.shift();
html = html.replace(/\((.+)\)/, '<small>$1</small>');
}
add(catitem, rows) {
- rows.forEach(ref => {
- const [title, level, imgid, subs] = this.data[ref];
+ rows.forEach(word => {
+ if (!word) return;
const worditem = put(catitem, 'li');
const figitem = put(worditem, 'figure');
- if (imgid) {
- put(figitem, 'img[src=$]', `/data/word/32/${imgid}.jpg`);
+ if (word.imgid) {
+ put(figitem, 'img[src=$]', word.thumb());
}
- if (title) {
+ if (word.title) {
put(figitem, 'figcaption', {
- innerHTML: this.namehtml(title),
+ innerHTML: this.namehtml(word.title),
});
}
- put(worditem, '.level' + level);
- if (level <= 1 && subs.length >= 4) {
+ put(worditem, '.level' + word.level);
+ if (word.level <= 1 && word.subs.length >= 4) {
put(worditem, '.large');
}
- if (subs.length) {
+ if (word.subs.length) {
// delve into subcategory
put(worditem, '.parent');
- this.add(put(worditem, 'ul'), subs);
+ this.add(put(worditem, 'ul'), word.subs);
}
if (this.preset.debug) {
- put(figitem, '[title=$]', `id ${ref} level ${level}`);
+ put(figitem, '[title=$]', `id ${ref} level ${word.level}`);
}
});
}
}
this.form.innerHTML = '';
put(this.form, 'p', 'Under construction.');
- for (let cat of this.preset.cat || this.data[''][3]) {
+ for (let cat of this.data.root()) {
this.add(put(this.form, 'ul.gallery'), [cat]);
}
}
let cols = Math.round(Math.sqrt(count) * aspect**.5);
count = cols * Math.ceil(count / cols);
this.form.style['grid-template-columns'] = `repeat(${cols}, 1fr)`;
- cards = this.words.splice(0, count>>1).map(row => row[2]);
+ cards = this.words.splice(0, count>>1).map(row => row.imgid);
cards.push(...cards.map(val => -val));
}
else {
if (this.words.length < 4) return;
let word = this.words.shift();
let form = put(this.form,
- '+img[src=$]+ul', `/data/word/32/${word[2]}.jpg`,
+ '+img[src=$]+ul', word.thumb()
);
- let answers = [word[0], this.words[0][0], this.words[1][0], this.words[2][0]]
+ let answers = [word, this.words[0], this.words[1], this.words[2]]
.shuffle()
- this.log('ask', word[2], answers);
+ this.log('ask', word.id, answers.map(w => w.id));
answers.forEach(suggest => {
- let label = suggest.replace(/\/.*/, '');
- let option = put(form, 'li', label, {onclick: () => {
- this.log('pick', suggest, null, word[0]);
- if (suggest != word[0]) {
+ let option = put(form, 'li', suggest.label, {onclick: () => {
+ this.log('pick', suggest.id, null, word.id);
+ if (suggest.label != word.label) {
// incorrect
put(option, '.wrong');
return;
return this;
};
-Set.prototype.filter = function (f) {
- return new Set([...this].filter(f));
-};
+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
-
- for (let cat of selection[''][3]) {
- if (selection[cat])
- selection[cat][1] = 0; // keep root categories
- }
+ const selection = new Words(json, this.preset.cat);
- 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;
}
this.question.innerHTML = '';
put(this.question,
- '[data-id=$] img[src=$]', word[2],
- `/data/word/32/${word[2]}.jpg`
+ '[data-id=$] img[src=$]', word.id,
+ word.thumb()
);
}
let answers = put(this.form, 'ul');
this.words
.forEach((answer, seq) => {
- let label = answer[0].replace(/\/.*/, ''); // primary form
put(answers, 'li[data-id=$][onclick=""]',
- answer[2], label, {
+ answer.id, answer.label, {
onclick: e => this.verify(e),
index: seq,
}