05b4d58559042592b9b9efa69fd53e5acdbe9fae
1 let socket
= null; //monitor answers in real time
3 function checkWindowSize()
5 if (assessment
.mode
== "secure")
7 // NOTE: temporarily accept smartphone (security hole: pretend being a smartphone on desktop browser...)
8 if (navigator
.userAgent
.match(/(iPhone|iPod|iPad|Android|BlackBerry)/))
11 return window
.innerWidth
< screen
.width
|| window
.innerHeight
< screen
.height
;
13 const returnVal
= test
;
15 alert("Please enter fullscreen mode (F11)");
21 function libsRefresh()
23 $("#statements").find("code[class^=language-]").each( (i
,elem
) => {
24 Prism
.highlightElement(elem
);
26 MathJax
.Hub
.Queue(["Typeset",MathJax
.Hub
,"statements"]);
29 // TODO: if display == "all", les envois devraient être non définitifs (possibilité de corriger)
30 // Et, blur sur une (sous-)question devrait envoyer la version courante de la sous-question
35 assessment: assessment
,
36 inputs: [ ], //student's answers
37 student: { }, //filled later
38 // Stage 0: unauthenticated (number),
39 // 1: authenticated (got a name, unvalidated)
40 // 2: locked: password set, exam started
43 stage: assessment
.mode
!= "open" ? 0 : 1,
44 remainingTime: 0, //global, in seconds
48 props: ['assessment','inputs','student','stage'],
51 index: 0, //current question index in assessment.indices
55 if (assessment
.mode
!= "secure")
60 this.resumeAssessment();
63 window
.addEventListener("blur", () => {
67 window
.addEventListener("resize", e
=> {
68 if (this.stage
== 2 && !checkWindowSize())
71 //socket.on("disconnect", () => { }); //TODO: notify monitor (highlight red)
76 // TODO: general render function for nested exercises
77 // TODO: with answer if stage==4 : class "wrong" if ticked AND stage==4 AND received answers
78 // class "right" if stage == 4 AND received answers (background-color: red / green)
79 // There should be a questions navigator below, or next (visible if display=='all')
80 // Full questions tree is rendered, but some parts hidden depending on display settings
83 let questions
= assessment
.questions
.map( (q
,i
) => {
84 let questionContent
= [ ];
98 let optionsOrder
= _
.range(q
.options
.length
);
100 optionsOrder
= _
.shuffle(optionsOrder
);
101 let optionList
= [ ];
102 optionsOrder
.forEach( idx
=> {
109 checked: this.inputs
[i
][idx
],
112 id: this.inputId(i
,idx
),
116 change: e
=> { this.inputs
[i
][idx
] = e
.target
.checked
; },
126 innerHTML: q
.options
[idx
],
129 "for": this.inputId(i
,idx
),
140 choiceCorrect: this.stage
== 4 && assessment
.questions
[i
].answer
.includes(idx
),
141 choiceWrong: this.stage
== 4 && this.inputs
[i
][idx
] && !assessment
.questions
[i
].answer
.includes(idx
),
148 questionContent
.push(
164 "hide": this.stage
== 2 && assessment
.display
== 'one' && assessment
.indices
[this.index
] != i
,
172 // TODO: one button per question
178 "waves-effect": true,
183 click: () => this.sendAnswer(assessment
.indices
[this.index
]),
202 inputId: function(i
,j
) {
203 return "q" + i
+ "_" + "input" + j
;
205 showWarning: function(action
) {
206 this.sendAnswer(assessment
.indices
[this.index
]);
207 this.stage
= 32; //fictive stage to hide all elements
208 $("#warning").modal('open');
211 sendAnswer: function(realIndex
) {
212 if (this.index
== assessment
.questions
.length
- 1)
213 this.$emit("gameover");
216 if (assessment
.mode
== "open")
220 answer: JSON
.stringify({
221 index:realIndex
.toString(),
222 input:this.inputs
[realIndex
]
223 .map( (tf
,i
) => { return {val:tf
,idx:i
}; } )
224 .filter( item
=> { return item
.val
; })
225 .map( item
=> { return item
.idx
; })
227 number: this.student
.number
,
228 password: this.student
.password
,
230 $.ajax("/send/answer", {
236 return alert(ret
.errmsg
);
237 //socket.emit(message.newAnswer, answer);
241 // stage 2 after blur or resize
242 resumeAssessment: function() {
248 mounted: function() {
249 if (assessment
.mode
== "open")
250 return; //no security needed in open mode
251 window
.addEventListener("keydown", e
=> {
252 // If F12 or ctrl+shift (ways to access devtools)
253 if (e
.keyCode
== 123 || (e
.ctrlKey
&& e
.shiftKey
))
256 // Devtools detect based on https://jsfiddle.net/ebhjxfwv/4/
257 let div
= document
.createElement('div');
258 let devtoolsLoop
= setInterval(
260 if (assessment
.mode
!= "open")
268 Object
.defineProperty(div
, "id", {
270 clearInterval(devtoolsLoop
);
272 this.endAssessment();
273 document
.location
.href
= "/nodevtools";
278 countdown: function() {
279 let seconds
= this.remainingTime
% 60;
280 let minutes
= Math
.floor(this.remainingTime
/ 60);
281 return this.padWithZero(minutes
) + ":" + this.padWithZero(seconds
);
286 padWithZero: function(x
) {
292 getStudent: function(cb
) {
293 $.ajax("/get/student", {
296 number: this.student
.number
,
302 return alert(s
.errmsg
);
304 this.student
= s
.student
;
305 Vue
.nextTick( () => { Materialize
.updateTextFields(); });
312 cancelStudent: function() {
315 // stage 1 --> 2 (get all questions, set password)
316 startAssessment: function() {
318 let initializeStage2
= questions
=> {
319 $("#leftButton, #rightButton").hide();
320 if (assessment
.time
> 0)
322 this.remainingTime
= assessment
.time
* 60;
325 // Initialize structured answer(s) based on questions type and nesting (TODO: more general)
327 assessment
.questions
= questions
;
328 for (let q
of assessment
.questions
)
329 this.inputs
.push( _(q
.options
.length
).times( _
.constant(false) ) );
330 assessment
.indices
= assessment
.fixed
331 ? _
.range(assessment
.questions
.length
)
332 : _
.shuffle( _
.range(assessment
.questions
.length
) );
334 Vue
.nextTick( () => { libsRefresh(); });
336 if (assessment
.mode
== "open")
337 return initializeStage2();
338 $.ajax("/start/assessment", {
341 number: this.student
.number
,
347 return alert(s
.errmsg
);
348 this.student
.password
= s
.password
;
349 // Got password: students answers locked to this page until potential teacher
350 // action (power failure, computer down, ...)
351 // TODO: password also exchanged by sockets to check identity
352 //socket = io.connect("/" + assessment.name, {
353 // query: "number=" + this.student.number + "&password=" + this.password
355 //socket.on(message.allAnswers, this.setAnswers);
356 initializeStage2(s
.questions
);
361 runTimer: function() {
362 if (assessment
.time
<= 0)
365 setInterval( function() {
366 self
.remainingTime
--;
367 if (self
.remainingTime
<= 0 || self
.stage
>= 4)
368 self
.endAssessment();
372 // stage 2 after disconnect (socket)
373 resumeAssessment: function() {
375 // TODO: get stored answers (papers[number cookie]), inject (inputs), set index+indices
377 // stage 2 --> 3 (or 4)
378 // from a message by statements component
379 endAssessment: function() {
380 // If time over or cheating: set endTime, destroy password
381 $("#leftButton, #rightButton").show();
382 //this.sendAnswer(...); //TODO: for each non-answered (and non-empty!) index (yet)
383 if (assessment
.mode
!= "open")
385 $.ajax("/end/assessment", {
389 number: this.student
.number
,
390 password: this.student
.password
,
395 return alert(ret
.errmsg
);
396 assessment
.conclusion
= ret
.conclusion
;
398 delete this.student
["password"]; //unable to send new answers now
399 //socket.disconnect();
407 // stage 3 --> 4 (on socket message "feedback")
408 setAnswers: function(answers
) {
409 for (let i
=0; i
<answers
.length
; i
++)
410 assessment
.questions
[i
].answer
= answers
[i
];