From: Benjamin Auder <benjamin.auder@somewhere>
Date: Mon, 12 Feb 2018 23:35:51 +0000 (+0100)
Subject: Add blur + resize monitoring in exam mode
X-Git-Url: https://git.auder.net/variants/current/doc/DESCRIPTION?a=commitdiff_plain;h=2bada710d28ffe9f45b150e5744b43af83e93d99;p=qomet.git

Add blur + resize monitoring in exam mode
---

diff --git a/bin/www b/bin/www
index eacc6e5..36f54dd 100755
--- 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;
 
diff --git a/public/javascripts/assessment.js b/public/javascripts/assessment.js
index acce548..342e963 100644
--- a/public/javascripts/assessment.js
+++ b/public/javascripts/assessment.js
@@ -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: {
diff --git a/public/javascripts/monitor.js b/public/javascripts/monitor.js
index 8963731..1b8d62f 100644
--- a/public/javascripts/monitor.js
+++ b/public/javascripts/monitor.js
@@ -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; })) }
diff --git a/public/javascripts/utils/socketMessages.js b/public/javascripts/utils/socketMessages.js
index 7c86fd8..1fa607a 100644
--- a/public/javascripts/utils/socketMessages.js
+++ b/public/javascripts/utils/socketMessages.js
@@ -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
diff --git a/public/stylesheets/monitor.css b/public/stylesheets/monitor.css
index b585800..303d68e 100644
--- a/public/stylesheets/monitor.css
+++ b/public/stylesheets/monitor.css
@@ -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; }
diff --git a/sockets.js b/sockets.js
index 9c230be..57fd657 100644
--- a/sockets.js
+++ b/sockets.js
@@ -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});
diff --git a/views/monitor.pug b/views/monitor.pug
index 891dda2..fa9daee 100644
--- a/views/monitor.pug
+++ b/views/monitor.pug
@@ -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'")