fb36be12f94bc5f7b026c85ed42014f1c5ba6df5
[sheet.git] / word / quiz.js
1 Array.prototype.shuffle = function () {
2         for (let i = this.length - 1; i > 0; i--) {
3                 const j = Math.floor(Math.random() * (i + 1)); // random index 0..i
4                 [this[i], this[j]] = [this[j], this[i]]; // swap elements
5         }
6         return this;
7 };
8
9 Set.prototype.filter = function (f) {
10         return new Set([...this].filter(f));
11 };
12
13 class WordQuiz {
14         dataselect(json) {
15                 this.data = this.datafilter(json);
16                 let rows = Object.values(this.data);
17                 rows = rows.filter(row => !row[3].length); // remove referenced categories
18                 return rows.shuffle();
19         }
20
21         datafilter(json) {
22                 // find viable rows from json data
23                 let ids = new Set(Object.keys(json));
24                 const selection = {...json}; // clone
25
26                 for (let cat of selection[''][3]) {
27                         if (selection[cat])
28                         selection[cat][1] = 0; // keep root categories
29                 }
30
31                 if (this.preset.cat !== undefined) {
32                         ids.clear();
33                         let children = [this.preset.cat];
34                         for (let loop = 0; children.length && loop < 20; loop++) {
35                                 for (let child of children) ids.add(child.toString());
36                                 children = children.map(cat => json[cat][3]).filter(is => is).flat()
37                         }
38                 }
39                 if (this.preset.image) {
40                         ids = ids.filter(id => json[id][2]);
41                 }
42                 if (this.preset.level !== undefined) {
43                         ids = ids.filter(id => json[id][1] <= this.preset.level);
44                 }
45
46                 // keep only wanted ids
47                 for (let id in selection) {
48                         if (id && !ids.has(id)) {
49                                 delete selection[id];
50                         }
51                 }
52
53                 // retain orphaned references in grandparent categories
54                 for (let id in selection) {
55                         selection[id][3] = function subresolve(subs) {
56                                 //console.log(subs);
57                                 return (subs || []).flatMap(sub =>
58                                         sub in selection ? [sub] : json[sub] ? subresolve(json[sub][3]) : []
59                                 );
60                         }(selection[id][3]);
61                 }
62                 return selection;
63         }
64
65         load(dataurl) {
66                 this.preset = {};
67                 let input;
68                 if (input = window.location.hash.match(/\d+/)) {
69                         this.preset.cat = parseInt(input[0]);
70                 }
71                 if (window.location.hash.match(/a/)) {
72                         this.preset.level = 3;
73                 }
74
75                 fetch(dataurl).then(res => res.json()).then(json => {
76                         this.words = this.dataselect(json)
77                         this.setup();
78                 });
79         }
80
81         log(...args) {
82                 this.history.push([new Date().toISOString(), ...args]);
83         }
84
85         stop(...args) {
86                 this.log(...args);
87                 window.onbeforeunload = null;
88                 fetch('/word/report', {method: 'POST', body: JSON.stringify(this.history)});
89         }
90
91         constructor(dataurl) {
92                 this.load(dataurl);
93                 this.history = [];
94                 window.onbeforeunload = e => {
95                         this.stop('abort');
96                 };
97                 window.onhashchange = e => {
98                         this.load(dataurl);
99                 };
100         }
101 }