1 let socket
= null; //monitor answers in real time
3 if (assessment
.mode
== "secure" && !checkWindowSize())
4 document
.location
.href
= "/fullscreen";
6 function checkWindowSize()
8 // NOTE: temporarily accept smartphone (security hole: pretend being a smartphone on desktop browser...)
9 if (navigator
.userAgent
.match(/(iPhone|iPod|iPad|Android|BlackBerry)/))
11 // 3 is arbitrary, but a small tolerance is required (e.g. in Firefox)
12 return window
.innerWidth
>= screen
.width
-3 && window
.innerHeight
>= screen
.height
-3;
18 assessment: assessment
,
19 inputs: [ ], //student's answers
20 student: { }, //filled later
21 // Stage 0: unauthenticated (number),
22 // 1: authenticated (got a name, unvalidated)
23 // 2: locked: password set, exam started
26 stage: assessment
.mode
!= "open" ? 0 : 1,
27 remainingTime: 0, //global, in seconds
31 countdown: function() {
32 let seconds
= this.remainingTime
% 60;
33 let minutes
= Math
.floor(this.remainingTime
/ 60);
34 return this.padWithZero(minutes
) + ":" + this.padWithZero(seconds
);
39 if (assessment
.mode
!= "secure")
41 window
.addEventListener("keydown", e
=> {
42 // Ignore F12 (avoid accidental window resize due to devtools)
43 // NOTE: in Chromium at least, fullscreen mode exit with F11 cannot be prevented.
44 // Workaround: disable key at higher level. Possible xbindkey config:
51 window
.addEventListener("blur", () => {
52 this.trySendCurrentAnswer();
53 document
.location
.href
= "/noblur";
55 window
.addEventListener("resize", e
=> {
56 this.trySendCurrentAnswer();
57 document
.location
.href
= "/fullscreen";
60 trySendCurrentAnswer: function() {
62 this.sendAnswer(assessment
.indices
[assessment
.index
]);
66 // In case of AJAX errors
67 warning: function(message
) {
68 this.warnMsg
= message
;
69 $("#warning").modal("open");
71 padWithZero: function(x
) {
77 getStudent: function(cb
) {
78 $.ajax("/get/student", {
81 number: this.student
.number
,
87 return this.warning(s
.errmsg
);
89 this.student
= s
.student
;
90 Vue
.nextTick( () => { Materialize
.updateTextFields(); });
97 cancelStudent: function() {
100 // stage 1 --> 2 (get all questions, set password)
101 startAssessment: function() {
102 let initializeStage2
= (questions
,paper
) => {
103 $("#leftButton, #rightButton").hide();
104 if (assessment
.time
> 0)
106 const deltaTime
= !!paper
? Date
.now() - paper
.startTime : 0;
107 this.remainingTime
= assessment
.time
* 60 - Math
.round(deltaTime
/ 1000);
110 // Initialize structured answer(s) based on questions type and nesting (TODO: more general)
112 assessment
.questions
= questions
;
113 for (let q
of assessment
.questions
)
114 this.inputs
.push( _(q
.options
.length
).times( _
.constant(false) ) );
117 assessment
.indices
= assessment
.fixed
118 ? _
.range(assessment
.questions
.length
)
119 : _
.shuffle( _
.range(assessment
.questions
.length
) );
124 let indices
= paper
.inputs
.map( input
=> { return input
.index
; });
125 let remainingIndices
= _
.difference( _
.range(assessment
.questions
.length
).map(String
), indices
);
126 assessment
.indices
= indices
.concat( _
.shuffle(remainingIndices
) );
128 assessment
.index
= !!paper
? paper
.inputs
.length : 0;
129 Vue
.nextTick(libsRefresh
);
132 if (assessment
.mode
== "open")
133 return initializeStage2();
134 $.ajax("/start/assessment", {
137 number: this.student
.number
,
143 return this.warning(s
.errmsg
);
146 // Resuming: receive stored answers + startTime
147 this.student
.password
= s
.paper
.password
;
148 this.inputs
= s
.paper
.inputs
.map( inp
=> { return inp
.input
; });
152 this.student
.password
= s
.password
;
153 // Got password: students answers locked to this page until potential teacher
154 // action (power failure, computer down, ...)
156 socket
= io
.connect("/" + assessment
.name
, {
157 query: "number=" + this.student
.number
+ "&password=" + this.password
159 socket
.on(message
.allAnswers
, this.setAnswers
);
160 initializeStage2(s
.questions
, s
.paper
);
165 runTimer: function() {
166 if (assessment
.time
<= 0)
169 setInterval( function() {
170 self
.remainingTime
--;
171 if (self
.remainingTime
<= 0 || self
.stage
>= 4)
172 self
.endAssessment();
177 // TODO: currentIndex ? click: () => this.sendAnswer(assessment.indices[assessment.index]),
178 // De même, cette condition sur le display d'une question doit remonter (résumée dans 'index' property) :
179 // à faire par ici : "hide": this.stage == 2 && assessment.display == 'one' && assessment.indices[assessment.index] != i,
180 sendAnswer: function(realIndex
) {
181 let gotoNext
= () => {
182 if (assessment
.index
== assessment
.questions
.length
- 1)
183 this.$emit("gameover");
186 this.$forceUpdate(); //TODO: shouldn't be required
188 if (assessment
.mode
== "open")
189 return gotoNext(); //only local
192 answer: JSON
.stringify({
193 index:realIndex
.toString(),
194 input:this.inputs
[realIndex
]
195 .map( (tf
,i
) => { return {val:tf
,idx:i
}; } )
196 .filter( item
=> { return item
.val
; })
197 .map( item
=> { return item
.idx
; })
199 number: this.student
.number
,
200 password: this.student
.password
,
202 $.ajax("/send/answer", {
208 return this.$emit("warning", ret
.errmsg
);
211 socket
.emit(message
.newAnswer
, answerData
);
215 // stage 2 --> 3 (or 4)
216 // from a message by statements component, or time over
217 endAssessment: function() {
218 // Set endTime, destroy password
219 $("#leftButton, #rightButton").show();
220 if (assessment
.mode
== "open")
225 $.ajax("/end/assessment", {
229 number: this.student
.number
,
230 password: this.student
.password
,
235 return this.warning(ret
.errmsg
);
236 assessment
.conclusion
= ret
.conclusion
;
238 delete this.student
["password"]; //unable to send new answers now
244 // stage 3 --> 4 (on socket message "feedback")
245 setAnswers: function(answers
) {
246 for (let i
=0; i
<answers
.length
; i
++)
247 assessment
.questions
[i
].answer
= answers
[i
];