+auto grading + finish erdiag + corr tp1
+-----
+
TODO: format général TXT: (compilé en JSON)
10 (time)
"statements": {
props: ['assessment','inputs','student','stage'],
// TODO: general render function for nested exercises
- // TODO: with answer if stage==4 : class "wrong" if ticked AND stage==4 AND received answers
- // class "right" if stage == 4 AND received answers (background-color: red / green)
// There should be a questions navigator below, or next (visible if display=='all')
// Full questions tree is rendered, but some parts hidden depending on display settings
render(h) {
if (assessment.mode != "secure")
return;
window.addEventListener("keydown", e => {
- // (Try to) Ignore F11 + F12 (avoid accidental window resize)
- // NOTE: in Chromium at least, exiting fullscreen mode with F11 cannot be prevented.
+ // Ignore F12 (avoid accidental window resize due to devtools)
+ // NOTE: in Chromium at least, fullscreen mode exit with F11 cannot be prevented.
// Workaround: disable key at higher level. Possible xbindkey config:
// "false"
// m:0x10 + c:95
// Mod2 + F11
- if ([122,123].includes(e.keyCode))
+ if (e.keyCode == 123)
e.preventDefault();
}, false);
window.addEventListener("blur", () => {
return this.$emit("warning", ret.errmsg);
else
gotoNext();
- //socket.emit(message.newAnswer, answer);
+ socket.emit(message.newAnswer, answerData);
},
});
},
// Got password: students answers locked to this page until potential teacher
// action (power failure, computer down, ...)
}
- // TODO: password also exchanged by sockets to check identity
- //socket = io.connect("/" + assessment.name, {
- // query: "number=" + this.student.number + "&password=" + this.password
- //});
- //socket.on(message.allAnswers, this.setAnswers);
- //socket.on("disconnect", () => { }); //TODO: notify monitor (highlight red), redirect
+ socket = io.connect("/" + assessment.name, {
+ query: "number=" + this.student.number + "&password=" + this.password
+ });
+ socket.on(message.allAnswers, this.setAnswers);
initializeStage2(s.questions, s.paper);
},
});
assessment.conclusion = ret.conclusion;
this.stage = 3;
delete this.student["password"]; //unable to send new answers now
- //socket.disconnect();
- //socket = null;
+ socket.disconnect();
+ socket = null;
},
});
},
// Doit reprendre les données en base si refresh (sinon : sockets)
// Also buttons "start exam", "end exam" for logged in teacher
+
+// TODO: réutiliser le component... trouver un moyen
+
+let socket = null; //monitor answers in real time
+
+function libsRefresh()
+{
+ // Run Prism + MathJax on questions text
+ $("#statements").find("code[class^=language-]").each( (i,elem) => {
+ Prism.highlightElement(elem);
+ });
+ MathJax.Hub.Queue(["Typeset",MathJax.Hub,"statements"]);
+}
+
+new Vue({
+ el: "#monitor",
+ data: {
+ password: "", //from password field
+ assessment: null, //obtained after authentication
+ // Stage 0: unauthenticated (password),
+ // 1: authenticated (password hash validated), start monitoring
+ stage: 0,
+ },
+ components: {
+ "statements": {
+ props: ['assessment','inputs','student','stage'],
+ // TODO: general render function for nested exercises
+ // There should be a questions navigator below, or next (visible if display=='all')
+ // Full questions tree is rendered, but some parts hidden depending on display settings
+ render(h) {
+ let self = this;
+ let questions = (assessment.questions || [ ]).map( (q,i) => {
+ let questionContent = [ ];
+ questionContent.push(
+ h(
+ "div",
+ {
+ "class": {
+ wording: true,
+ },
+ domProps: {
+ innerHTML: q.wording,
+ },
+ }
+ )
+ );
+ let optionsOrder = _.range(q.options.length);
+ if (!q.fixed)
+ optionsOrder = _.shuffle(optionsOrder);
+ let optionList = [ ];
+ optionsOrder.forEach( idx => {
+ let option = [ ];
+ option.push(
+ h(
+ "input",
+ {
+ domProps: {
+ checked: this.inputs.length > 0 && this.inputs[i][idx],
+ },
+ attrs: {
+ id: this.inputId(i,idx),
+ type: "checkbox",
+ },
+ on: {
+ change: e => { this.inputs[i][idx] = e.target.checked; },
+ },
+ },
+ )
+ );
+ option.push(
+ h(
+ "label",
+ {
+ domProps: {
+ innerHTML: q.options[idx],
+ },
+ attrs: {
+ "for": this.inputId(i,idx),
+ },
+ }
+ )
+ );
+ optionList.push(
+ h(
+ "div",
+ {
+ "class": {
+ option: true,
+ choiceCorrect: this.stage == 4 && assessment.questions[i].answer.includes(idx),
+ choiceWrong: this.stage == 4 && this.inputs[i][idx] && !assessment.questions[i].answer.includes(idx),
+ },
+ },
+ option
+ )
+ );
+ });
+ questionContent.push(
+ h(
+ "div",
+ {
+ "class": {
+ optionList: true,
+ },
+ },
+ optionList
+ )
+ );
+ return h(
+ "div",
+ {
+ "class": {
+ "question": true,
+ "hide": this.stage == 2 && assessment.display == 'one' && assessment.indices[assessment.index] != i,
+ },
+ },
+ questionContent
+ );
+ });
+ if (this.stage == 2)
+ {
+ questions.unshift(
+ h(
+ "button",
+ {
+ "class": {
+ "waves-effect": true,
+ "waves-light": true,
+ "btn": true,
+ },
+ style: {
+ "display": "block",
+ "margin-left": "auto",
+ "margin-right": "auto",
+ },
+ on: {
+ click: () => this.sendAnswer(assessment.indices[assessment.index]),
+ },
+ },
+ "Send"
+ )
+ );
+ }
+ return h(
+ "div",
+ {
+ attrs: {
+ id: "statements",
+ },
+ },
+ questions
+ );
+ },
+ mounted: function() {
+ libsRefresh();
+ },
+ methods: {
+ inputId: function(i,j) {
+ return "q" + i + "_" + "input" + j;
+ },
+ },
+ },
+ },
+ methods: {
+ // stage 0 --> 1
+ startMonitoring: function() {
+ $.ajax("/start/monitoring", {
+ method: "GET",
+ data: {
+ password: this.,
+ aname: examName,
+ cname: courseName,
+ },
+ dataType: "json",
+ success: s => {
+ if (!!s.errmsg)
+ return this.warning(s.errmsg);
+ this.stage = 1;
+ },
+ });
+ },
+ // TODO: 2-level sockets, for prof and monitors
+ socket = io.connect("/" + assessment.name, {
+ query: "number=" + this.student.number + "&password=" + this.password
+ });
+ socket.on(message.allAnswers, this.setAnswers);
+ initializeStage2(s.questions, s.paper);
+ },
+ });
+ },
+ // stage 2 --> 3 (or 4)
+ // from a message by statements component, or time over
+ // TODO: also function startAssessment (for main teacher only)
+ endAssessment: function() {
+ // Set endTime, destroy password
+ $("#leftButton, #rightButton").show();
+ if (assessment.mode == "open")
+ {
+ this.stage = 4;
+ return;
+ }
+ $.ajax("/end/assessment", {
+ method: "GET",
+ data: {
+ aid: assessment._id,
+ number: this.student.number,
+ password: this.student.password,
+ },
+ dataType: "json",
+ success: ret => {
+ if (!!ret.errmsg)
+ return this.warning(ret.errmsg);
+ assessment.conclusion = ret.conclusion;
+ this.stage = 3;
+ delete this.student["password"]; //unable to send new answers now
+ socket.disconnect();
+ socket = null;
+ },
+ });
+ },
+ },
+});
-var message = require("./public/javascripts/utils/socketMessages.js");
+const message = require("./public/javascripts/utils/socketMessages");
const params = require("./config/parameters");
+const AssessmentEntity = require("./entities/assessment");
+const ObjectId = require("bson-objectid");
// TODO: when teacher connect on monitor, io.of("appropriate namespace").on(connect student) { ... }
// --> 2 sockets on monitoring page: one with ns "/" et one dedicated to the exam, triggered after the first
// --> The monitoring page should not be closed during exam (otherwise monitors won't receive any more data)
-function quizzRoom(socket) {
+function examRoom(socket) {
let students = { };
+ const aid = ObjectId(socket.handshake.query.aid);
// Student or monitor stuff
const isTeacher = !!socket.handshake.query.secret && socket.handshake.query.secret == params.secret;
if (isTeacher)
{
- // TODO: on student disconnect, too
socket.on(message.newAnswer, m => { //got answer from student
socket.emit(message.newAnswer, m);
});
- socket.on(message.socketFeedback, m => { //send feedback to student (answers)
- if (!!students[m.number])
- socket.broadcast.to(students[m.number]).emit(message.newFeedback, { feedback:m.feedback });
+ socket.on(message.allAnswers, m => { //send feedback to student (answers)
+ if (!!students[m.number]) //TODO: namespace here... room quiz
+ socket.broadcast.to(students[m.number]).emit(message.allAnswers, m);
});
socket.on("disconnect", m => {
// Reset student array if no more active teacher connections (TODO: condition)
{
const number = socket.handshake.query.number;
const password = socket.handshake.query.password;
- // Prevent socket connection (just ignore) if student already connected
- if (!!students[number] && students[number].password != password)
- return;
- students[number] = {
- sid: socket.id,
- password: password,
- };
- socket.on(message.newFeedback, () => { //got feedback from teacher
- socket.emit(message.newFeedback, m);
+ AssessmentEntity.checkPassword(aid, number, password, (err,ret) => {
+ if (!!err || !ret)
+ return; //wrong password, or some unexpected error...
+ // Prevent socket connection (just ignore) if student already connected
+ if (!!students[number])
+ return;
+ students[number] = {
+ sid: socket.id,
+ password: password,
+ };
+ socket.on(message.allAnswers, () => { //got all answers from teacher
+ socket.emit(message.allAnswers, m);
+ });
+ socket.on("disconnect", () => {
+ // ..
+ //TODO: notify monitor (highlight red), redirect
+ });
+ // NOTE: nothing on disconnect --> teacher disconnect trigger students cleaning
});
- // NOTE: nothing on disconnect --> teacher disconnect trigger students cleaning
}
}
// array with results + quiz details (displayed in another tab) + init socket (with hash too)
// buttons "start quiz" and "stop quiz" for teacher only: trigger actions (impacting sockets)
- body
- p TODO
+ // TODO: data = papers (modified after socket messages + retrived at start)
+ // But get examName by server at loading
+
+block content
+ .container#assessment
+ .row
+ .col.s12.m10.offset-m1.l8.offset-l2.xl6.offset-xl3
+ h4= examName
+ #stage0(v-show="stage==0")
+ .card
+ .input-field.inline.on-left
+ label(for="password") Password
+ input#password(type="password" v-model="password" @keyup.enter="startMonitoring()")
+ button.waves-effect.waves-light.btn(@click="startMonitoring()") Send
+ #stage2(v-show="stage==1")
+ .card
+ .introduction(v-html="assessment.introduction")
+ .card
+ statements(:assessment="assessment" :student="student" :stage="stage" :inputs="inputs" @gameover="endAssessment" @warning="warning")
+ .card
+ .conclusion(v-html="assessment.conclusion")
block append javascripts
- script. TODO
+ script(src="/javascripts/monitor.js")