'update'
[qomet.git] / public / javascripts / components / statements.js
1 /*
2 * questions group by index prefix 1.2.3 1.1 ...etc --> '1'
3
4 NOTE: questions can contain parameterized exercises (how ?
5 --> describe variables (syntax ?)
6 --> write javascript script (OK, users trusted ? ==> safe mode possible if public website)
7 Imaginary example: (using math.js)
8 <params> (avant l'exo)
9 x: math.random()
10 y: math.random()
11 M: math.matrix([[7, x], [y, -3]]);
12 res: math.det(M)
13 </params>
14 <div>Calculer le déterminant de
15 $$\begin{matrix}7 & x\\y & -3\end{matrix}$$</div>
16 * ...
17
18 + fixed + question time (syntax ?)
19
20 --> input of type text (number, or vector, or matrix e.g. in R syntax)
21 --> parameter stored in question.param (TODO)
22
23 */
24
25 Vue.component("statements", {
26 // 'inputs': object with key = question index and value = text or boolean array
27 // display: 'all', 'one', 'solution'
28 // iidx: current level-0 integer index (can match a group of questions / inputs)
29 props: ['questions','inputs','display','iidx'],
30 data: function() {
31 return {
32 displayStyle: "compact", //or "all": all on same page
33 };
34 }
35 // Full questions tree is rendered, but some parts hidden depending on display settings
36 render(h) {
37 // Prepare questions groups, ordered
38 let questions = this.questions || [ ]
39 let questionGroups = _.groupBy(questions, q => {
40 const dotPos = q.index.indexOf(".");
41 return dotPos === -1 ? q.index : q.index.substring(0,dotPos);
42 });
43 let domTree = questionGroups.map( (qg,i) => {
44 // Re-order questions 1.1.1 then 1.1.2 then...
45 const orderedQg = qg.sort( (a,b) => {
46 let aParts = a.split('.').map(Number);
47 let bParts = b.split('.').map(Number);
48 const La = aParts.length, Lb = bParts.length;
49 for (let i=0; i<Math.min(La,Lb); i++)
50 {
51 if (aParts[i] != bParts[i])
52 return aParts[i] - bParts[i];
53 }
54 return La - Lb; //the longer should appear after
55 });
56 let qgDom = orderedQg.map( q => {
57 let questionContent = [ ];
58 questionContent.push(
59 h(
60 "h4",
61 {
62 "class": {
63 "questionIndex": true,
64 }
65 },
66 q.index
67 )
68 );
69 questionContent.push(
70 h(
71 "div",
72 {
73 "class": {
74 wording: true,
75 },
76 domProps: {
77 innerHTML: q.wording,
78 },
79 }
80 )
81 );
82 if (!!q.options)
83 {
84 // quiz-like question
85 let optionsOrder = _.range(q.options.length);
86 if (!q.fixed)
87 optionsOrder = _.shuffle(optionsOrder);
88 let optionList = [ ];
89 optionsOrder.forEach( idx => {
90 let option = [ ];
91 option.push(
92 h(
93 "input",
94 {
95 domProps: {
96 checked: !!this.inputs && this.inputs[q.index][idx],
97 disabled: monitoring || this.display == "solution",
98 },
99 attrs: {
100 id: this.inputId(q.index,idx),
101 type: "checkbox",
102 },
103 on: {
104 change: e => { this.inputs[q.index][idx] = e.target.checked; },
105 },
106 },
107 [ '' ] //to work in Firefox 45.9 ESR @ ENSTA...
108 )
109 );
110 option.push(
111 h(
112 "label",
113 {
114 domProps: {
115 innerHTML: q.options[idx],
116 },
117 attrs: {
118 "for": this.inputId(q.index,idx),
119 },
120 }
121 )
122 );
123 optionList.push(
124 h(
125 "div",
126 {
127 "class": {
128 option: true,
129 choiceCorrect: this.display == "solution" && q.answer.includes(idx),
130 choiceWrong: this.display == "solution" && !!this.inputs && this.inputs[q.index][idx] && !q.answer.includes(idx),
131 },
132 },
133 option
134 )
135 );
136 });
137 questionContent.push(
138 h(
139 "div",
140 {
141 "class": {
142 optionList: true,
143 },
144 },
145 optionList
146 )
147 );
148 }
149 const depth = (q.index.match(/\./g) || []).length;
150 return h(
151 "div",
152 {
153 "class": {
154 "question": true,
155 "depth" + depth: true,
156 },
157 },
158 questionContent
159 );
160 });
161 return h(
162 "div",
163 {
164 "class": {
165 "questionGroup": true,
166 "hide": this.display == "one" && this.iidx != i,
167 },
168 }
169 qgDom
170 );
171 });
172 const navigator = h(
173 "div",
174 {
175 "class": {
176 "hide": this.displayStyle == "all"
177 },
178 },
179 [
180 h(
181 "button",
182 {
183 "class": {
184 "btn": true,
185 },
186 on: {
187 click: () => {
188 this.index = Math.max(0, this.index - 1);
189 },
190 },
191 },
192 [ h("span", { "class": { "material-icon": true } }, "fast_rewind") ]
193 ),
194 h("span",{ },(this.iidx+1).toString()),
195 h(
196 "button",
197 {
198 "class": {
199 "btn": true,
200 },
201 on: {
202 click: () => {
203 this.index = Math.min(this.index+1, this.questions.length-1)
204 },
205 },
206 },
207 [ h("span", { "class": { "material-icon": true } }, "fast_forward") ]
208 )
209 ]
210 );
211 domTree.push(navigator);
212 domTree.push(
213 h(
214 "button",
215 {
216 on: {
217 click: () => {
218 this.displayStyle = displayStyle == "compact" ? "all" : "compact";
219 },
220 },
221 },
222 this.displayStyle == "compact" ? "Show all" : "Navigator"
223 )
224 );
225 return h(
226 "div",
227 {
228 attrs: {
229 id: "statements",
230 },
231 },
232 domTree
233 );
234 },
235 mounted: function() {
236 statementsLibsRefresh();
237 },
238 updated: function() {
239 statementsLibsRefresh();
240 },
241 methods: {
242 inputId: function(i,j) {
243 return "q" + i + "_" + "input" + j;
244 },
245 },
246 });