49ba6e52f663868c84235408eeb70445938406d1
1 let socket
= null; //monitor answers in real time
3 if (evaluation
.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 evaluation: evaluation
,
19 answers: { }, //filled later with answering parameters
20 student: { }, //filled later (name, password)
21 // Stage 0: unauthenticated (number),
22 // 1: authenticated (got a name, unvalidated)
23 // 2: locked: password set, exam started
26 remainingTime: evaluation
.time
, //integer or array
27 stage: evaluation
.mode
!= "open" ? 0 : 1,
31 countdown: function() {
32 const remainingTime
= evaluation
.display
== "one" && _
.isArray(evaluation
.time
)
33 ? this.remainingTime
[this.answers
.index
]
35 let seconds
= remainingTime
% 60;
36 let minutes
= Math
.floor(remainingTime
/ 60);
37 return this.padWithZero(minutes
) + ":" + this.padWithZero(seconds
);
42 if (["exam","open"].includes(evaluation
.mode
))
44 window
.addEventListener("blur", () => {
47 if (evaluation
.mode
== "secure")
50 document
.location
.href
= "/noblur";
53 socket
.emit(message
.studentBlur
, {number:this.student
.number
});
55 if (evaluation
.mode
== "watch")
57 window
.addEventListener("focus", () => {
59 socket
.emit(message
.studentFocus
, {number:this.student
.number
});
62 window
.addEventListener("resize", e
=> {
65 if (evaluation
.mode
== "secure")
68 document
.location
.href
= "/fullscreen";
72 if (checkWindowSize())
73 socket
.emit(message
.studentFullscreen
, {number:this.student
.number
});
75 socket
.emit(message
.studentResize
, {number:this.student
.number
});
80 // In case of AJAX errors (not blur-ing)
81 showWarning: function(message
) {
82 this.warnMsg
= message
;
83 $("#warning").modal("open");
85 padWithZero: function(x
) {
91 getStudent: function() {
92 $.ajax("/courses/student", {
95 number: this.student
.number
,
101 return this.showWarning(s
.errmsg
);
103 this.student
= s
.student
;
104 Vue
.nextTick( () => { Materialize
.updateTextFields(); });
109 cancelStudent: function() {
112 // stage 1 --> 2 (get all questions, set password)
113 startEvaluation: function() {
114 let initializeStage2
= paper
=> {
115 $("#leftButton, #rightButton").hide();
116 // Initialize structured answer(s) based on questions type and nesting (TODO: more general)
118 // if display == "all" getQuestionS
119 // otherwise get first question
123 evaluation
.questions
= questions
;
124 this.answers
.inputs
= [ ];
125 for (let q
of evaluation
.questions
)
126 this.answers
.inputs
.push( _(q
.options
.length
).times( _
.constant(false) ) );
129 this.answers
.indices
= evaluation
.fixed
130 ? _
.range(evaluation
.questions
.length
)
131 : _
.shuffle( _
.range(evaluation
.questions
.length
) );
136 let indices
= paper
.inputs
.map( input
=> { return input
.index
; });
137 let remainingIndices
= _
.difference( _
.range(evaluation
.questions
.length
).map(String
), indices
);
138 this.answers
.indices
= indices
.concat( _
.shuffle(remainingIndices
) );
147 if (evaluation
.time
> 0)
150 // TODO: distinguish total exam time AND question time
152 const deltaTime
= !!paper
? Date
.now() - paper
.startTime : 0;
153 this.remainingTime
= evaluation
.time
* 60 - Math
.round(deltaTime
/ 1000);
158 this.answers
.index
= !!paper
? paper
.inputs
.length : 0;
159 this.answers
.displayAll
= evaluation
.display
== "all";
160 this.answers
.showSolution
= false;
163 if (evaluation
.mode
== "open")
164 return initializeStage2();
165 $.ajax("/evaluations/start", {
168 number: this.student
.number
,
174 return this.showWarning(s
.errmsg
);
177 // Resuming: receive stored answers + startTime
178 this.student
.password
= s
.paper
.password
;
179 this.answers
.inputs
= s
.paper
.inputs
.map( inp
=> { return inp
.input
; });
183 this.student
.password
= s
.password
;
184 // Got password: students answers locked to this page until potential teacher
185 // action (power failure, computer down, ...)
187 socket
= io
.connect("/", {
188 query: "aid=" + evaluation
._id
+ "&number=" + this.student
.number
+ "&password=" + this.student
.password
190 socket
.on(message
.allAnswers
, this.setAnswers
);
191 initializeStage2(s
.questions
, s
.paper
);
198 runGlobalTimer: function() {
199 if (evaluation
.time
<= 0)
202 setInterval( function() {
203 self
.remainingTime
--;
204 if (self
.remainingTime
<= 0)
207 self
.endEvaluation();
212 runQuestionTimer: function(idx
) {
213 if (evaluation
.questions
[idx
].time
<= 0)
215 let self
= this; //TODO: question remaining time
216 setInterval( function() {
217 self
.remainingTime
--;
218 if (self
.remainingTime
<= 0)
221 self
.endEvaluation();
227 //TODO: get question after sending answer
230 sendOneAnswer: function() {
231 const realIndex
= this.answers
.indices
[this.answers
.index
];
232 let gotoNext
= () => {
233 if (this.answers
.index
== evaluation
.questions
.length
- 1)
234 this.endEvaluation();
236 this.answers
.index
++;
237 this.$children
[0].$forceUpdate(); //TODO: bad HACK, and shouldn't be required...
239 if (evaluation
.mode
== "open")
240 return gotoNext(); //only local
243 answer: JSON
.stringify({
244 index: realIndex
.toString(),
245 input: this.answers
.inputs
[realIndex
]
246 .map( (tf
,i
) => { return {val:tf
,idx:i
}; } )
247 .filter( item
=> { return item
.val
; })
248 .map( item
=> { return item
.idx
; })
250 number: this.student
.number
,
251 password: this.student
.password
,
253 $.ajax("/evaluations/answer", {
259 return this.showWarning(ret
.errmsg
);
261 socket
.emit(message
.newAnswer
, answerData
);
265 // TODO: I don't like that + sending should not be definitive in exam mode with display = all
266 sendAnswer: function() {
267 if (evaluation
.display
== "one")
268 this.sendOneAnswer();
270 evaluation
.questions
.forEach(this.sendOneAnswer
);
272 // stage 2 --> 3 (or 4)
273 // from a message by statements component, or time over
274 endEvaluation: function() {
275 // Set endTime, destroy password
276 $("#leftButton, #rightButton").show();
277 if (evaluation
.mode
== "open")
280 this.answers
.showSolution
= true;
281 this.answers
.displayAll
= true;
284 $.ajax("/evaluations/end", {
288 number: this.student
.number
,
289 password: this.student
.password
,
294 return this.showWarning(ret
.errmsg
);
296 delete this.student
["password"]; //unable to send new answers now
300 // stage 3 --> 4 (on socket message "feedback")
301 setAnswers: function(m
) {
302 const answers
= JSON
.parse(m
.answers
);
303 for (let i
=0; i
<answers
.length
; i
++)
304 evaluation
.questions
[i
].answer
= answers
[i
];
305 this.answers
.showSolution
= true;
306 this.answers
.displayAll
= true;