1db80a081c1d683ce96587241bbf38097d2a78d8
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 window
.addEventListener("keydown", e
=> {
250 // If F12 or ctrl+shift (ways to access devtools)
251 if (e
.keyCode
== 123 || (e
.ctrlKey
&& e
.shiftKey
))
254 // Devtools detect based on https://jsfiddle.net/ebhjxfwv/4/
255 let div
= document
.createElement('div');
256 let devtoolsLoop
= setInterval(
258 if (assessment
.mode
!= "open")
266 Object
.defineProperty(div
, "id", {
268 clearInterval(devtoolsLoop
);
269 if (assessment
.mode
!= "open")
272 this.endAssessment();
273 document
.location
.href
= "/nodevtools";
279 countdown: function() {
280 let seconds
= this.remainingTime
% 60;
281 let minutes
= Math
.floor(this.remainingTime
/ 60);
282 return this.padWithZero(minutes
) + ":" + this.padWithZero(seconds
);
287 padWithZero: function(x
) {
293 getStudent: function(cb
) {
294 $.ajax("/get/student", {
297 number: this.student
.number
,
303 return alert(s
.errmsg
);
305 this.student
= s
.student
;
306 Vue
.nextTick( () => { Materialize
.updateTextFields(); });
313 cancelStudent: function() {
316 // stage 1 --> 2 (get all questions, set password)
317 startAssessment: function() {
319 let initializeStage2
= questions
=> {
320 $("#leftButton, #rightButton").hide();
321 if (assessment
.time
> 0)
323 this.remainingTime
= assessment
.time
* 60;
326 // Initialize structured answer(s) based on questions type and nesting (TODO: more general)
328 assessment
.questions
= questions
;
329 for (let q
of assessment
.questions
)
330 this.inputs
.push( _(q
.options
.length
).times( _
.constant(false) ) );
331 assessment
.indices
= assessment
.fixed
332 ? _
.range(assessment
.questions
.length
)
333 : _
.shuffle( _
.range(assessment
.questions
.length
) );
335 Vue
.nextTick( () => { libsRefresh(); });
337 if (assessment
.mode
== "open")
338 return initializeStage2();
339 $.ajax("/start/assessment", {
342 number: this.student
.number
,
348 return alert(s
.errmsg
);
349 this.student
.password
= s
.password
;
350 // Got password: students answers locked to this page until potential teacher
351 // action (power failure, computer down, ...)
352 // TODO: password also exchanged by sockets to check identity
353 //socket = io.connect("/" + assessment.name, {
354 // query: "number=" + this.student.number + "&password=" + this.password
356 //socket.on(message.allAnswers, this.setAnswers);
357 initializeStage2(s
.questions
);
362 runTimer: function() {
363 if (assessment
.time
<= 0)
366 setInterval( function() {
367 self
.remainingTime
--;
368 if (self
.remainingTime
<= 0 || self
.stage
>= 4)
369 self
.endAssessment();
373 // stage 2 after disconnect (socket)
374 resumeAssessment: function() {
376 // TODO: get stored answers (papers[number cookie]), inject (inputs), set index+indices
378 // stage 2 --> 3 (or 4)
379 // from a message by statements component
380 endAssessment: function() {
381 // If time over or cheating: set endTime, destroy password
382 $("#leftButton, #rightButton").show();
383 //this.sendAnswer(...); //TODO: for each non-answered (and non-empty!) index (yet)
384 if (assessment
.mode
!= "open")
386 $.ajax("/end/assessment", {
390 number: this.student
.number
,
391 password: this.student
.password
,
396 return alert(ret
.errmsg
);
397 assessment
.conclusion
= ret
.conclusion
;
399 delete this.student
["password"]; //unable to send new answers now
400 //socket.disconnect();
408 // stage 3 --> 4 (on socket message "feedback")
409 setAnswers: function(answers
) {
410 for (let i
=0; i
<answers
.length
; i
++)
411 assessment
.questions
[i
].answer
= answers
[i
];