Add blur + resize monitoring in exam mode
authorBenjamin Auder <benjamin.auder@somewhere>
Mon, 12 Feb 2018 23:35:51 +0000 (00:35 +0100)
committerBenjamin Auder <benjamin.auder@somewhere>
Mon, 12 Feb 2018 23:35:51 +0000 (00:35 +0100)
bin/www
public/javascripts/assessment.js
public/javascripts/monitor.js
public/javascripts/utils/socketMessages.js
public/stylesheets/monitor.css
sockets.js
views/monitor.pug

diff --git a/bin/www b/bin/www
index eacc6e5..36f54dd 100755 (executable)
--- a/bin/www
+++ b/bin/www
@@ -72,7 +72,7 @@ function onError(error)
   if (error.syscall !== 'listen')
     throw error;
 
-  let bind = typeof port === 'string'
+  const bind = typeof port === 'string'
     ? 'Pipe ' + port
     : 'Port ' + port;
 
index acce548..342e963 100644 (file)
@@ -39,25 +39,53 @@ new Vue({
        },
        mounted: function() {
                $(".modal").modal();
-               if (assessment.mode != "secure")
-                       return;
-               window.addEventListener("keydown", e => {
-                       // 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 (e.keyCode == 123)
-                               e.preventDefault();
-               }, false);
+               if (assessment.mode != "open")
+               {
+                       window.addEventListener("keydown", e => {
+                               // 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 (e.keyCode == 123)
+                                       e.preventDefault();
+                       }, false);
+               }
                window.addEventListener("blur", () => {
-                       this.trySendCurrentAnswer();
-                       document.location.href= "/noblur";
+                       if (!socket)
+                               return;
+                       if (assessment.mode == "secure")
+                       {
+                               this.trySendCurrentAnswer();
+                               document.location.href= "/noblur";
+                       }
+                       else if (assessment.mode == "exam")
+                               socket.emit(message.studentBlur, {number:this.student.number});
                }, false);
+               if (assessment.mode == "exam")
+               {
+                       window.addEventListener("focus", () => {
+                               if (!socket)
+                                       return;
+                               socket.emit(message.studentFocus, {number:this.student.number});
+                       }, false);
+               }
                window.addEventListener("resize", e => {
-                       this.trySendCurrentAnswer();
-                       document.location.href= "/fullscreen";
+                       if (!socket)
+                               return;
+                       if (assessment.mode == "secure")
+                       {
+                               this.trySendCurrentAnswer();
+                               document.location.href= "/fullscreen";
+                       }
+                       else if (assessment.mode == "exam")
+                       {
+                               if (checkWindowSize())
+                                       socket.emit(message.studentFullscreen, {number:this.student.number});
+                               else
+                                       socket.emit(message.studentResize, {number:this.student.number});
+                       }
                }, false);
        },
        methods: {
index 8963731..1b8d62f 100644 (file)
@@ -86,6 +86,33 @@ new Vue({
                                        socket = io.connect("/", {
                                                query: "aid=" + this.assessment._id + "&secret=" + s.secret
                                        });
+                                       socket.on(message.studentBlur, m => {
+                                               const sIdx = this.students.findIndex( item => { return item.number == m.number; });
+                                               Vue.set(this.students, sIdx, Object.assign({},this.students[sIdx],{blur: true}));
+                                               //this.students[sIdx].blur = true;
+                                       });
+                                       socket.on(message.studentFocus, m => {
+                                               const sIdx = this.students.findIndex( item => { return item.number == m.number; });
+                                               this.students[sIdx].blur = false;
+                                       });
+                                       socket.on(message.studentResize, m => {
+                                               const sIdx = this.students.findIndex( item => { return item.number == m.number; });
+                                               Vue.set(this.students, sIdx, Object.assign({},this.students[sIdx],{resize: true}));
+                                               //this.students[sIdx].resize = true;
+                                       });
+                                       socket.on(message.studentFullscreen, m => {
+                                               const sIdx = this.students.findIndex( item => { return item.number == m.number; });
+                                               this.students[sIdx].resize = false;
+                                       });
+                                       socket.on(message.studentDisconnect, m => {
+                                               const sIdx = this.students.findIndex( item => { return item.number == m.number; });
+                                               Vue.set(this.students, sIdx, Object.assign({},this.students[sIdx],{disco: true}));
+                                               //this.students[sIdx].disco = true;
+                                       });
+                                       socket.on(message.studentConnect, m => {
+                                               const sIdx = this.students.findIndex( item => { return item.number == m.number; });
+                                               this.students[sIdx].disco = false;
+                                       });
                                        socket.on(message.newAnswer, m => {
                                                let paperIdx = this.assessment.papers.findIndex( item => {
                                                        return item.number == m.number;
@@ -107,6 +134,7 @@ new Vue({
                },
                endMonitoring: function() {
                        // In the end, send answers to students
+                       // TODO: disable this button until everyone finished (need ability to mark absents)
                        socket.emit(
                                message.allAnswers,
                                { answers: JSON.stringify(this.assessment.questions.map( q => { return q.answer; })) }
index 7c86fd8..1fa607a 100644 (file)
@@ -8,6 +8,11 @@ let message = {
        // Next 2 to monitor students disconnections
        studentConnect: "student connect",
        studentDisconnect: "student disconnect",
+       // And blur + onResize events (sockets only)
+       studentBlur: "student blur",
+       studentFocus: "student focus",
+       studentResize: "student resize",
+       studentFullscreen: "student fullscreen",
 };
 
-try { module.exports = message; } catch (err) {} //for server
+try { module.exports = message; } catch (err) { } //for server
index b585800..303d68e 100644 (file)
@@ -1,3 +1,14 @@
+.blur {
+       background-color: lightsalmon;
+}
+.resize {
+       font-style: italic;
+       color: darkred;
+}
+.disconnect {
+       background-color: grey;
+}
+
 /* TODO: factor this piece of code from assessment (and course, and here...) */
 .question {
        margin: 20px 5px;
@@ -35,7 +46,12 @@ table.in-question {
        width: auto;
        margin: 10px auto;
 }
+
 table.in-question th, table.in-question td {
-       padding: 3px;
+       padding: 7px;
        border-bottom: 1px solid grey;
 }
+
+/*table { border: none; border-collapse: collapse; }*/
+table.in-question td { border-left: 1px solid grey; }
+table.in-question td:first-child { border-left: none; }
index 9c230be..57fd657 100644 (file)
@@ -30,6 +30,18 @@ module.exports = function(io)
                                socket.on(message.newAnswer, m => { //got answer from student client
                                        socket.broadcast.to(aid + "_teacher").emit(message.newAnswer, m);
                                });
+                               socket.on(message.studentBlur, m => {
+                                       socket.broadcast.to(aid + "_teacher").emit(message.studentBlur, m);
+                               });
+                               socket.on(message.studentFocus, m => {
+                                       socket.broadcast.to(aid + "_teacher").emit(message.studentFocus, m);
+                               });
+                               socket.on(message.studentResize, m => {
+                                       socket.broadcast.to(aid + "_teacher").emit(message.studentResize, m);
+                               });
+                               socket.on(message.studentFullscreen, m => {
+                                       socket.broadcast.to(aid + "_teacher").emit(message.studentFullscreen, m);
+                               });
                                socket.on("disconnect", () => { //notify monitor + server
                                        AssessmentEntity.setDiscoTime(ObjectId(aid), number);
                                        socket.broadcast.to(aid + "_teacher").emit(message.studentDisconnect, {number: number});
index 891dda2..fa9daee 100644 (file)
@@ -6,7 +6,7 @@ block append stylesheets
 block content
        .container#monitor
                .row
-                       .col.s12.m10.offset-m1.l8.offset-l2.xl6.offset-xl3
+                       .col.s12.m10.offset-m1
                                h4= examName
                                #stage0(v-show="stage==0")
                                        .card
@@ -31,7 +31,7 @@ block content
                                                                        th(v-for="(q,i) in assessment.questions") Q.{{ (i+1) }}
                                                        tbody
                                                                tr.assessment(v-for="s in studentList(group)")
-                                                                       td {{ s.name }}
+                                                                       td(:class="{blur:!!s.blur,resize:!!s.resize,disconnect:!!s.disco}") {{ s.name }}
                                                                        td(v-for="(q,i) in assessment.questions" :style="{backgroundColor: getColor(s.number,i)}" @click="seeDetails(s.number,i)") &nbsp;
                                        h4.title(@click="toggleDisplay('assessment')") Assessment
                                        div(v-show="display=='assessment'")