word/quiz: objects to access row array elements
[sheet.git] / word / quiz.js
index 3243b19ed5c2f52ef019868a32136f253bf41481..5283628407e51fbe84ff5cc54ea02f1c7e88c623 100644 (file)
@@ -6,59 +6,108 @@ Array.prototype.shuffle = function () {
        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;
        }