| 1 | // UNIMPLEMENTED |
| 2 | |
| 3 | // TODO: onglets pour chaque groupe + section déroulante questionnaire (chargé avec réponses) |
| 4 | // NOM Prenom (par grp, puis alphabétique) |
| 5 | // réponse : vert si OK (+ choix), rouge si faux, gris si texte (clic pour voir) |
| 6 | // + temps total ? |
| 7 | // click sur en-tête de colonne : tri alphabétique, tri décroissant... |
| 8 | // Affiché si (hash du) mdp du cours est correctement entré |
| 9 | // Doit reprendre les données en base si refresh (sinon : sockets) |
| 10 | |
| 11 | // Also buttons "start exam", "end exam" for logged in teacher |
| 12 | |
| 13 | // TODO: réutiliser le component... trouver un moyen |
| 14 | |
| 15 | let socket = null; //monitor answers in real time |
| 16 | |
| 17 | function libsRefresh() |
| 18 | { |
| 19 | // Run Prism + MathJax on questions text |
| 20 | $("#statements").find("code[class^=language-]").each( (i,elem) => { |
| 21 | Prism.highlightElement(elem); |
| 22 | }); |
| 23 | MathJax.Hub.Queue(["Typeset",MathJax.Hub,"statements"]); |
| 24 | } |
| 25 | |
| 26 | new Vue({ |
| 27 | el: "#monitor", |
| 28 | data: { |
| 29 | password: "", //from password field |
| 30 | assessment: null, //obtained after authentication |
| 31 | // Stage 0: unauthenticated (password), |
| 32 | // 1: authenticated (password hash validated), start monitoring |
| 33 | stage: 0, |
| 34 | }, |
| 35 | components: { |
| 36 | "statements": { |
| 37 | props: ['assessment','inputs','student','stage'], |
| 38 | // TODO: general render function for nested exercises |
| 39 | // There should be a questions navigator below, or next (visible if display=='all') |
| 40 | // Full questions tree is rendered, but some parts hidden depending on display settings |
| 41 | render(h) { |
| 42 | let self = this; |
| 43 | let questions = (assessment.questions || [ ]).map( (q,i) => { |
| 44 | let questionContent = [ ]; |
| 45 | questionContent.push( |
| 46 | h( |
| 47 | "div", |
| 48 | { |
| 49 | "class": { |
| 50 | wording: true, |
| 51 | }, |
| 52 | domProps: { |
| 53 | innerHTML: q.wording, |
| 54 | }, |
| 55 | } |
| 56 | ) |
| 57 | ); |
| 58 | let optionsOrder = _.range(q.options.length); |
| 59 | if (!q.fixed) |
| 60 | optionsOrder = _.shuffle(optionsOrder); |
| 61 | let optionList = [ ]; |
| 62 | optionsOrder.forEach( idx => { |
| 63 | let option = [ ]; |
| 64 | option.push( |
| 65 | h( |
| 66 | "input", |
| 67 | { |
| 68 | domProps: { |
| 69 | checked: this.inputs.length > 0 && this.inputs[i][idx], |
| 70 | }, |
| 71 | attrs: { |
| 72 | id: this.inputId(i,idx), |
| 73 | type: "checkbox", |
| 74 | }, |
| 75 | on: { |
| 76 | change: e => { this.inputs[i][idx] = e.target.checked; }, |
| 77 | }, |
| 78 | }, |
| 79 | ) |
| 80 | ); |
| 81 | option.push( |
| 82 | h( |
| 83 | "label", |
| 84 | { |
| 85 | domProps: { |
| 86 | innerHTML: q.options[idx], |
| 87 | }, |
| 88 | attrs: { |
| 89 | "for": this.inputId(i,idx), |
| 90 | }, |
| 91 | } |
| 92 | ) |
| 93 | ); |
| 94 | optionList.push( |
| 95 | h( |
| 96 | "div", |
| 97 | { |
| 98 | "class": { |
| 99 | option: true, |
| 100 | choiceCorrect: this.stage == 4 && assessment.questions[i].answer.includes(idx), |
| 101 | choiceWrong: this.stage == 4 && this.inputs[i][idx] && !assessment.questions[i].answer.includes(idx), |
| 102 | }, |
| 103 | }, |
| 104 | option |
| 105 | ) |
| 106 | ); |
| 107 | }); |
| 108 | questionContent.push( |
| 109 | h( |
| 110 | "div", |
| 111 | { |
| 112 | "class": { |
| 113 | optionList: true, |
| 114 | }, |
| 115 | }, |
| 116 | optionList |
| 117 | ) |
| 118 | ); |
| 119 | return h( |
| 120 | "div", |
| 121 | { |
| 122 | "class": { |
| 123 | "question": true, |
| 124 | "hide": this.stage == 2 && assessment.display == 'one' && assessment.indices[assessment.index] != i, |
| 125 | }, |
| 126 | }, |
| 127 | questionContent |
| 128 | ); |
| 129 | }); |
| 130 | if (this.stage == 2) |
| 131 | { |
| 132 | questions.unshift( |
| 133 | h( |
| 134 | "button", |
| 135 | { |
| 136 | "class": { |
| 137 | "waves-effect": true, |
| 138 | "waves-light": true, |
| 139 | "btn": true, |
| 140 | }, |
| 141 | style: { |
| 142 | "display": "block", |
| 143 | "margin-left": "auto", |
| 144 | "margin-right": "auto", |
| 145 | }, |
| 146 | on: { |
| 147 | click: () => this.sendAnswer(assessment.indices[assessment.index]), |
| 148 | }, |
| 149 | }, |
| 150 | "Send" |
| 151 | ) |
| 152 | ); |
| 153 | } |
| 154 | return h( |
| 155 | "div", |
| 156 | { |
| 157 | attrs: { |
| 158 | id: "statements", |
| 159 | }, |
| 160 | }, |
| 161 | questions |
| 162 | ); |
| 163 | }, |
| 164 | mounted: function() { |
| 165 | libsRefresh(); |
| 166 | }, |
| 167 | methods: { |
| 168 | inputId: function(i,j) { |
| 169 | return "q" + i + "_" + "input" + j; |
| 170 | }, |
| 171 | }, |
| 172 | }, |
| 173 | }, |
| 174 | methods: { |
| 175 | // stage 0 --> 1 |
| 176 | startMonitoring: function() { |
| 177 | $.ajax("/start/monitoring", { |
| 178 | method: "GET", |
| 179 | data: { |
| 180 | password: this., |
| 181 | aname: examName, |
| 182 | cname: courseName, |
| 183 | }, |
| 184 | dataType: "json", |
| 185 | success: s => { |
| 186 | if (!!s.errmsg) |
| 187 | return this.warning(s.errmsg); |
| 188 | this.stage = 1; |
| 189 | }, |
| 190 | }); |
| 191 | }, |
| 192 | // TODO: 2-level sockets, for prof and monitors |
| 193 | socket = io.connect("/" + assessment.name, { |
| 194 | query: "number=" + this.student.number + "&password=" + this.password |
| 195 | }); |
| 196 | socket.on(message.allAnswers, this.setAnswers); |
| 197 | initializeStage2(s.questions, s.paper); |
| 198 | }, |
| 199 | }); |
| 200 | }, |
| 201 | // stage 2 --> 3 (or 4) |
| 202 | // from a message by statements component, or time over |
| 203 | // TODO: also function startAssessment (for main teacher only) |
| 204 | endAssessment: function() { |
| 205 | // Set endTime, destroy password |
| 206 | $("#leftButton, #rightButton").show(); |
| 207 | if (assessment.mode == "open") |
| 208 | { |
| 209 | this.stage = 4; |
| 210 | return; |
| 211 | } |
| 212 | $.ajax("/end/assessment", { |
| 213 | method: "GET", |
| 214 | data: { |
| 215 | aid: assessment._id, |
| 216 | number: this.student.number, |
| 217 | password: this.student.password, |
| 218 | }, |
| 219 | dataType: "json", |
| 220 | success: ret => { |
| 221 | if (!!ret.errmsg) |
| 222 | return this.warning(ret.errmsg); |
| 223 | assessment.conclusion = ret.conclusion; |
| 224 | this.stage = 3; |
| 225 | delete this.student["password"]; //unable to send new answers now |
| 226 | socket.disconnect(); |
| 227 | socket = null; |
| 228 | }, |
| 229 | }); |
| 230 | }, |
| 231 | }, |
| 232 | }); |