Commit 8feae373 authored by Paul-Elliot Anglès d'Auriac's avatar Paul-Elliot Anglès d'Auriac
Browse files

Merge branch 'directQuestion'

parents 09d60c79 e39308ad
......@@ -80,7 +80,7 @@ Au final je n'ai pas utilisé flex
** TODO voir pour le problème de timers.enroll deprecated (express-session-mysql est bloqué sur mysql v2.15, on peut manuellement l'upgrader en v2.16)
** TODO Randomiser l'affichage des réponses
Fait (mais commenté) modulo le fait que ça se passe du coté client...
** DONE faire les routes GET de delete, update, etc...
CLOSED: [2018-09-12 mer. 22:26]
** DONE Refaire l'interface d'ajout d'une question
......@@ -89,13 +89,14 @@ Au final je n'ai pas utilisé flex
** TODO Réfléchir à ce qui est le mieux pour indexSet et currentQuestion...
** TODO Empecher les noms vides
** DONE style css pour souligner dans quelle page nous sommes dans le menu...
CLOSED: [2018-09-19 mer. 14:31]
** TODO mettre configuration.js en configuration.js.example et mettre à jour le README en accord avec cela...
** TODO mettre configuration.js en configuration.js.example et mettre à jour le README en accord avec cela
* BUGs
** TODO Lors d'une reconnexion, on recrée un nouvelle réponse au lieu d'updater la précédente...
** DONE Lors d'une reconnexion, on recrée un nouvelle réponse au lieu d'updater la précédente...
CLOSED: [2018-09-19 mer. 00:17]
** DONE Bug game.questionFromRoom is not a function
CLOSED: [2018-09-17 lun. 14:16]
/home/panglesd/class-panic/controllers/sockets.js:111
......
......@@ -38,9 +38,27 @@ module.exports = function (server, sessionMiddleware) {
/* Utilitaires d'envoi */
/**************************************************************************/
function sendRoomQuestion(room, socket) {
function sendRoomQuestion(socket, callback) {
game.questionFromRoomID(socket.room.id, function (err, question) {
// question.reponses.sort(function() { return 0.5 - Math.random() });
socket.emit("newQuestion", question);
callback();
});
}
function sendRoomOwnedQuestion(user, socket, callback) {
game.questionOwnedFromRoomID(user, socket.room.id, function (err, question) {
socket.emit("newQuestion", question);
callback();
});
}
function broadcastRoomQuestion(room, callback) {
// console.log("room", room);
game.questionFromRoomID(room.id, function (err, question) {
socket.emit("question", question);
// question.reponses.sort(function() { return 0.5 - Math.random() });
io.of("/student").to(room.id).emit("newQuestion", question);
io.of("/admin").to(room.id).emit("newQuestion", question);
callback();
});
}
......@@ -89,8 +107,7 @@ module.exports = function (server, sessionMiddleware) {
// console.log("socket.request.session.user is ",socket.request.session.user);
game.enterRoom(socket.request.session.user, socket.room, function (err) {
sendOwnedStats(socket.room);
game.questionFromRoomID(socket.room.id, function (err, question) {
socket.emit("newQuestion", question);
sendRoomQuestion(socket, function () {
room.getStatus(socket.room, function (err, status) {
if(status == "revealed") {
game.getStatsFromRoomID(socket.room.id, function (r,e) {
......@@ -108,10 +125,8 @@ module.exports = function (server, sessionMiddleware) {
/******************************************/
socket.on('sendQuestionPlease', function () {
console.log(socket.room);
game.questionFromRoomID(socket.room.id, function (err, question) {
socket.emit("newQuestion", question);
});
// console.log(socket.room);
sendRoomQuestion(socket,function() {});
});
/******************************************/
......@@ -119,6 +134,7 @@ module.exports = function (server, sessionMiddleware) {
/******************************************/
socket.on('chosenAnswer', function (answer) {
// console.log(answer);
game.registerAnswer(socket.request.session.user, socket.room, answer, function () {
sendOwnedStats(socket.room)
});
......@@ -129,10 +145,12 @@ module.exports = function (server, sessionMiddleware) {
/******************************************/
socket.on('disconnect', function (reason) {
game.leaveRoom(socket.request.session.user, socket.room, function (err) {
if (err) throw err;
sendOwnedStats(socket.room);
});
if(socket.room) {
game.leaveRoom(socket.request.session.user, socket.room, function (err) {
if (err) throw err;
sendOwnedStats(socket.room);
});
}
});
}
}
......@@ -172,9 +190,7 @@ module.exports = function (server, sessionMiddleware) {
room.getOwnedByID(socket.request.session.user, parseInt(newRoom), function (err, res) {
socket.room = res;
socket.join(socket.room.id);
game.questionOwnedFromRoomID(socket.request.session.user, socket.room.id, function (err, question) {
socket.emit("newQuestion", question);
});
sendRoomOwnedQuestion(socket.request.session.user, socket, function (err) {if(err) throw err});
sendOwnedStats(socket.room);
});
});
......@@ -187,7 +203,7 @@ module.exports = function (server, sessionMiddleware) {
// console.log("should emit to", socket.room.id, "the correction");
game.getStatsFromRoomID(socket.room.id, function (r,e) {
io.of("/student").to(socket.room.id).emit("correction", e);
room.setStatusForRoom(socket.room, "revealed", function () {});
room.setStatusForRoomID(socket.room.id, "revealed", function () {});
});
});
......@@ -197,13 +213,13 @@ module.exports = function (server, sessionMiddleware) {
socket.on('changeToQuestion', function (i) {
// console.log("on souhaite changer à la question", i)
game.setQuestionFromRoom(socket.room, parseInt(i), function () {
game.questionFromRoomID(socket.room.id, function (err, question) {
io.of("/student").to(socket.room.id).emit("newQuestion", question);
io.of('/admin').to(socket.room.id).emit("newQuestion", question);
room.setStatusForRoom(socket.room, "pending", function () {sendOwnedStats(socket.room);});
});
})
game.setQuestionFromRoomID(socket.room.id, parseInt(i), function () {
room.setStatusForRoomID(socket.room.id, "pending", function () {
sendOwnedStats(socket.room);
broadcastRoomQuestion(socket.room, function (err) {if(err) throw err});
sendRoomOwnedQuestion(socket.request.session.user, socket, function () {});
})
});
});
/******************************************/
......@@ -214,19 +230,52 @@ module.exports = function (server, sessionMiddleware) {
//TO BE IMPLEMENTED
});
/******************************************/
/* Un admin me demande la question */
/******************************************/
socket.on('sendQuestionPlease', function () {
// console.log(socket.room);
sendRoomOwnedQuestion(socket.request.session.user, socket, function() {});
});
/******************************************/
/* On souhaite passer à la question suivante */
/******************************************/
socket.on('changeQuestionPlease', function (nextQuestion) {
game.nextQuestionFromRoom(socket.room, function () {
game.questionFromRoomID(socket.room.id, function (err, question) {
io.of("/student").to(socket.room.id).emit("newQuestion", question);
io.of('/admin').to(socket.room.id).emit("newQuestion", question);
room.setStatusForRoom(socket.room, "pending", function () {sendOwnedStats(socket.room);});
game.nextQuestionFromRoomID(socket.room.id, function (err) {
room.setStatusForRoomID(socket.room.id, "pending", function () {
broadcastRoomQuestion(socket.room, function () {});
sendRoomOwnedQuestion(socket.request.session.user, socket, function () {});
sendOwnedStats(socket.room);
});
})
});
})
/******************************************/
/* On souhaite une question custom */
/******************************************/
socket.on('customQuestion', function (customQuestion) {
// console.log(customQuestion);
delete(customQuestion.id);
game.setQuestion(socket.room.id, customQuestion, function () {
broadcastRoomQuestion(socket.room, function(err, res) {});
sendOwnedStats(socket.room);
});
});
/******************************************/
/* On souhaite revenir aux questions du set*/
/******************************************/
/* socket.on('backToSet', function () {
// console.log("backToSet");
game.backToSet(socket.room.id, function(err, res) {
broadcastRoomQuestion(socket.room, function(err,res) {})
});
});*/
}
}
});
......
......@@ -11,15 +11,11 @@ Set = require("./set");
/***********************************************************************/
exports.questionFromRoomID = function (roomID, callback) {
Room.getByID(roomID, function (err, room) {
Question.getByID(room.id_currentQuestion, function (err, row) { callback(err, row) });
});
bdd.query("SELECT question FROM rooms WHERE id = ?", roomID, function(err, row) { callback(err,JSON.parse(row[0].question))})
}
exports.questionOwnedFromRoomID = function (user, room, callback) {
Room.getOwnedByID(user, room, function (err, room) {
Question.getOwnedByID(user, room.id_currentQuestion, function (err, row) {callback(err, row) });
});
exports.questionOwnedFromRoomID = function (user, roomID, callback) {
bdd.query("SELECT question FROM rooms WHERE id = ? AND ownerID = ?", [roomID, user.id], function(err, row) { callback(err, JSON.parse(row[0].question))})
}
/***********************************************************************/
......@@ -53,7 +49,7 @@ exports.getStatsFromRoomID = function (roomID, callback) {
bdd.query("SELECT response AS answer,COUNT(response) AS count FROM `poll` WHERE `roomID` = ? AND `poll`.`pseudo` != (SELECT pseudo FROM users WHERE id = (SELECT ownerID FROM rooms WHERE `id` = ?)) GROUP BY response", [roomID, roomID], function(err, row) {callback(err,row)});
},
correctAnswer : function (callback) {
bdd.query("SELECT correct FROM `questions` WHERE `id` = (SELECT `id_currentQuestion` FROM `rooms` WHERE `id` = ?)", [roomID], function(a,b) {callback(a,b[0].correct);});
exports.questionFromRoomID(roomID, function (err, q) { callback(err, q.correct)});
}
},
callback);
......@@ -66,7 +62,7 @@ exports.getStatsFromOwnedRoomID = function (roomID, callback) {
bdd.query("SELECT `users`.`id`, `poll`.`pseudo`, `poll`.`response` FROM `poll` INNER JOIN `users` ON `poll`.`pseudo` = `users`.`pseudo` WHERE `roomID` = ? AND `poll`.`pseudo` != (SELECT pseudo FROM users WHERE id = (SELECT ownerID FROM rooms WHERE `id` = ?)) ", [roomID, roomID], function(err, row) {callback(err, row)});
},
correctAnswer : function (callback) {
bdd.query("SELECT correct FROM `questions` WHERE `id` = (SELECT `id_currentQuestion` FROM `rooms` WHERE `id` = ?)", [roomID], function (err, res) {callback(err, res[0].correct)});
exports.questionFromRoomID(roomID, function (err, q) { callback(err, q.correct)});
}
},
callback);
......@@ -76,42 +72,43 @@ exports.getStatsFromOwnedRoomID = function (roomID, callback) {
/* Passer à la question suivante d'une room */
/***********************************************************************/
exports.nextQuestionFromRoom = function (room, callback) {
bdd.query(
"UPDATE `rooms` SET \
`id_currentQuestion` = (SELECT id FROM `questions` WHERE \
`class` = (SELECT questionSet FROM (SELECT * FROM `rooms`) AS trick WHERE id = ?) \
AND `indexSet` > (SELECT indexSet FROM `questions` WHERE \
id = (SELECT id_currentQuestion FROM (SELECT * FROM `rooms`) AS trick WHERE id = ?)\
) \
ORDER BY indexSet LIMIT 1\
)\
WHERE `id` = ?", [room.id, room.id, room.id], function (err1, rows) {
if (err1) {
bdd.query(
"UPDATE `rooms` SET \
`id_currentQuestion` = (SELECT id FROM `questions` WHERE \
`class` = (SELECT questionSet FROM (SELECT * FROM `rooms`) AS trick WHERE id = ?) \
AND `indexSet` = 0 \
)\
WHERE `id` = ?", [room.id, room.id], function (err1, rows) {
if (err1) throw err1;
});
}
exports.flushOldPlayers(room, function() {
Room.setStatusForRoom(room, "pending", callback);
});
});
exports.nextQuestionFromRoomID = function (roomID, callback) {
async.waterfall([
function(callback) { // 1 Récupérer l'indice vers lequel on pointe,
bdd.query("SELECT * FROM `questions` INNER JOIN `rooms` ON `questions`.id = id_currentQuestion WHERE `rooms`.id = ?", [roomID],
function(err,rows) { callback(err, rows[0])});
},
function(currentQ, callback) { // 2 Voir s'il existe une question après
bdd.query("SELECT * FROM `questions` WHERE `class` = ? AND `indexSet` = ?", [currentQ.class, currentQ.indexSet+1], function(err, row) { callback(err, row[0], currentQ.class) })
},
function(nextQ, currentClass, callback) { // 3 Sinon, chercher la première question
if(nextQ)
callback(null, nextQ);
else
bdd.query("SELECT * FROM `questions` WHERE `class` = ? AND `indexSet` = ?", [currentClass, 0], function(err, row) { callback(err, row[0]) })
},
function(nextQ, callback) { // 4 Updater la room
nextQ.reponses = JSON.parse(nextQ.reponses);
bdd.query("UPDATE `rooms` SET `id_currentQuestion` = ?, `question` = ? WHERE id = ?", [nextQ.id, JSON.stringify(nextQ), roomID], function(err) {
Room.setStatusForRoomID(roomID, "pending", function() {
exports.flushOldPlayers(roomID, callback);
});
});
}
],
function(err, res){ callback(err);}
);
}
/***********************************************************************/
/* Entrer et sortir d'une room */
/***********************************************************************/
// Flusher les anciens participants d'une room
exports.flushOldPlayers = function (room, callback) {
bdd.query("DELETE FROM `poll` WHERE ADDTIME(`last_activity`, '0 3:0:0')<NOW() AND `roomID` = ?", [room.id], function () {
bdd.query("UPDATE `poll` SET `response`=-1 WHERE `roomID`= ? ", [room.id], callback);
exports.flushOldPlayers = function (roomID, callback) {
bdd.query("DELETE FROM `poll` WHERE ADDTIME(`last_activity`, '0 3:0:0')<NOW() AND `roomID` = ?", [roomID], function () {
bdd.query("UPDATE `poll` SET `response`=-1 WHERE `roomID`= ? ", [roomID], callback);
});
}
......@@ -131,18 +128,32 @@ exports.enterRoom = function (user, room, callback) {
/* Passer à une question donnée */
/***********************************************************************/
exports.setQuestionFromRoom = function (room, questionID, callback) {
bdd.query(
"UPDATE `rooms` SET \
`id_currentQuestion` = (SELECT id FROM `questions` WHERE \
`class` = (SELECT questionSet FROM (SELECT * FROM `rooms`) AS trick WHERE id = ?) \
AND `id` = ? \
)\
WHERE `id` = ?", [room.id, questionID, room.id], function (err1, rows) {
if (err1) throw err1;
exports.flushOldPlayers(room, function() {
Room.setStatusForRoom(room, "pending", callback);
});
});
exports.setQuestion = function(roomID, question, callback) {
exports.flushOldPlayers(roomID, function(err) {
query = "UPDATE rooms SET status = \"pending\", question = ? "+(question.id ? (", id_currentQuestion = " + question.id) : "") +" WHERE id = ?";
// console.log(query);
bdd.query(query, [JSON.stringify(question), roomID], function(err, res) { callback(err, res);});
});
}
/***********************************************************************/
/* Passer à une question donnée par son ID */
/***********************************************************************/
exports.setQuestionFromRoomID = function (roomID, questionID, callback) {
Question.getByID(questionID, function(err, question) {exports.setQuestion(roomID, question, callback)});
}
/***********************************************************************/
/* Passer d'une question custom à celle du set */
/***********************************************************************/
exports.backToSet = function (roomID, callback) {
query = "SELECT * FROM `questions` WHERE id = (SELECT id_currentQuestion FROM rooms WHERE id = ?)";
bdd.query(query, [roomID], function (err, questionL) {
question = questionL[0];
question.reponses = JSON.parse(question.reponses);
exports.setQuestion(roomID, questionL[0], callback);
});
}
......@@ -44,6 +44,7 @@ exports.listOwnedByRoomID = function (user, id, callback) {
exports.getByID = function (questionId, callback) {
bdd.query("SELECT * FROM `questions` WHERE `id` = ?", [questionId], function (err, rows) {
// console.log(rows);
q = rows[0];
q.reponses = JSON.parse(q.reponses);
q.reponses.forEach(function(rep) { delete rep.validity });
......
......@@ -109,8 +109,8 @@ exports.isOwnedBy= function (room, user, callback) {
});
}
exports.setStatusForRoom = function (room, status, callback) {
bdd.query("UPDATE `rooms` SET `status` = ? WHERE `id` = ?", [status, room.id], function (err, rows) {
exports.setStatusForRoomID = function (roomID, status, callback) {
bdd.query("UPDATE `rooms` SET `status` = ? WHERE `id` = ?", [status, roomID], function (err, rows) {
callback();
});
}
......
//var socketAdmin = io.connect('http://192.168.0.12:3000/admin');
//var socketAdmin = io.connect('http://localhost:3000/admin');
var socketAdmin = io.connect(server+'/admin');
var isAdmin = true;
/*********************************************************************/
/* Actions à effectuer à toute connection */
......@@ -19,12 +20,19 @@ function changeQuestionPlease() {
socketAdmin.emit("changeQuestionPlease");
}
/*********************************************************************/
/* pour redemander d'envoyer la question */
/*********************************************************************/
function sendOwnedQuestionPlease() {
socket.emit("sendQuestionPlease");
}
/*********************************************************************/
/* lorsque l'on veut reveler les resultats */
/*********************************************************************/
function revealResults() {
console.log("rev");
socketAdmin.emit("revealResults");
}
......@@ -41,7 +49,7 @@ function gotoQuestion(i) {
/*********************************************************************/
socketAdmin.on('newStats', function (newStats) {
console.log(newStats);
// console.log(newStats);
ul = document.createElement("ul")
ul.innerHTML = '<li style="font-family: Impact, \'Arial Black\', Arial, Verdana, sans-serif;"> Ce qu\'en disent les élèves : </li>';
......@@ -62,9 +70,84 @@ socketAdmin.on('newStats', function (newStats) {
/*********************************************************************/
socketAdmin.on('newQuestion', function (reponse) {
console.log("fromAdminnewQuestion", reponse);
document.querySelector("li.currentQuestion").classList.remove("currentQuestion");
// document.querySelector("li.nextQuestion").classList.remove("nextQuestion");
document.querySelector("li#q"+reponse.id).classList.add("currentQuestion");
// document.querySelector("li#q"+reponse.nextQuestion).classList.add("nextQuestion");
// console.log("fromAdminnewQuestion", reponse);
if(temp=document.querySelector("li.inactiveQuestion")) {
if(reponse.id)
temp.classList.remove("inactiveQuestion")
}
if(temp=document.querySelector("li.currentQuestion")) {
temp.classList.remove("currentQuestion");
// console.log("reponse is", reponse);
if(!reponse.id)
temp.classList.add("inactiveQuestion")
}
if(reponse.id) {
document.querySelector("li#q"+reponse.id).classList.add("currentQuestion");
}
document.querySelector("#customQuestion").innerHTML = "Créer sa propre question temporaire";
document.querySelector("#customQuestion").onclick = customQuestion;
/* }
else {
document.querySelector("#customQuestion").innerHTML = "Revenir à la question du set";
document.querySelector("#customQuestion").onclick = backToSetQuestion;
}*/
document.querySelector("#question").contentEditable = false;
});
/*********************************************************************/
/* Pour les questions custom */
/*********************************************************************/
backToSetQuestion = function (event) {
document.querySelector("#customQuestion").innerHTML = "Créer sa propre question temporaire";
document.querySelector("#customQuestion").onclick = customQuestion;
// document.querySelector("#question").contentEditable = false;
sendOwnedQuestionPlease();
// socketAdmin.emit("backToSet");
}
addReponse = function (event) {
n = document.createElement("div");
n.classList.add("reponse");
n.classList.add("notSelected");
n.innerHTML = "<span contenteditable=\"true\">Réponse éditable</span><button onclick=\"chooseAsCorrect(this)\">Choisir comme réponse juste</button><button onclick=\"removeReponse(this)\"> Retirer</button>";
document.querySelector("#plus").parentNode.insertBefore(n,document.querySelector("#plus"));
}
removeReponse = function (elem) {
elem.parentNode.remove();
}
sendReponse = function() {
newQuestion = {};
newQuestion.reponses = [];
i=0;
document.querySelectorAll("#wrapperAnswer div span").forEach(function(span) {
newQuestion.reponses.push({reponse:span.textContent, validity:false});
if(span.parentNode.classList.contains("juste"))
newQuestion.correct = i;
i++;
});
newQuestion.enonce = document.querySelector("#question").textContent;
// console.log(newQuestion);
// backToSetQuestion();
socketAdmin.emit("customQuestion", newQuestion);
}
chooseAsCorrect = function (elem) {
// console.log(elem);
if(temp=document.querySelector(".juste"))
temp.classList.remove("juste");
elem.parentNode.classList.add("juste");
}
customQuestion = function(event) {
document.querySelector("#question").contentEditable = true;//innerHTML = "<input type=\"textarea\" placeholder=\"Votre question\">";
document.querySelector("#question").innerHTML = "Question éditable";//innerHTML = "<input type=\"textarea\" placeholder=\"Votre question\">";
document.querySelector("#wrapperAnswer").innerHTML = "<div class=\"reponse notSelected juste\" id=\"r0\"><span contentEditable=\"true\">Réponse éditable</span> <button onclick=\"chooseAsCorrect(this)\">Choisir comme réponse juste</button><button onclick=\"removeReponse(this)\">Retirer</button></div><div class=\"reponse notSelected\" id=\"plus\"> <button onclick=\"addReponse()\"> Ajouter une réponse</button><button onclick=\"sendReponse()\"> Envoyer aux élèves </button></div>";
document.querySelector("#customQuestion").innerHTML = "Revenir à la question du set";
document.querySelector("#customQuestion").onclick = backToSetQuestion;
}
......@@ -16,7 +16,7 @@ socket.on('connect', () => {
/*********************************************************************/
socket.on('newQuestion', function (reponse) {
console.log(reponse);
// console.log(reponse);
document.querySelector("#question").textContent=reponse.enonce;
wrapper = document.querySelector("#wrapperAnswer");
while (wrapper.firstChild) {
......@@ -27,9 +27,10 @@ socket.on('newQuestion', function (reponse) {
elem.classList.add("reponse");
elem.classList.add("notSelected");
elem.id = "r"+index;
elem.addEventListener("click", function (ev) {
chooseAnswer(index);
});
if(typeof isAdmin == "undefined")
elem.addEventListener("click", function (ev) {
chooseAnswer(index);
});
elem.textContent = rep.reponse;
wrapper.appendChild(elem);
});
......@@ -40,7 +41,7 @@ socket.on('newQuestion', function (reponse) {
/*********************************************************************/
socket.on('correction', function (correction) {
console.log(correction);
// console.log(correction);
document.querySelectorAll(".reponse").forEach(function (elem) {elem.style.boxShadow="0 0 8px 10px red"});
// document.querySelector("#rep"+correction.correct).style.boxShadow="0 0 8px 15px green";
document.querySelector("#r"+correction.correctAnswer).style.boxShadow="0 0 8px 15px green";
......@@ -51,7 +52,7 @@ socket.on('correction', function (correction) {
if(v.answer!=-1) {
// console.log("#rep"+v.answer);
document.querySelector("#r"+v.answer).style.background =
"linear-gradient(to right, rgba(0,0,0,0.5) "+((0.+v.count)/total*100.-5)+"%,#F5F5DC "+((0.+v.count)/total*100.)+"%)";
"linear-gradient(to right, rgba(0,0,0,0.5) "+((0.+v.count)/total*100./*-5*/)+"%,#F5F5DC "+((0.+v.count)/total*100.)+"%)";
}
});
});
......@@ -79,12 +80,9 @@ function chooseAnswer(i) {
reponses.forEach(function (rep) {
rep.classList.replace('selected', 'notSelected');
});
console.log('a');
if(i>-1) {
console.log('a');
a = document.querySelector("#r"+i);
a.classList.replace("notSelected", "selected");
}
console.log("socket emit chosen answer", i);
}
......@@ -44,8 +44,14 @@
li.currentQuestion {
border-left: 15px solid rgba(255,255,255,1);
}
li.inactiveQuestion {
border-left: 15px solid rgba(255,255,255,0.5);
}
#chooseQFromSet li:not(:first-child) {
cursor:pointer;
}
.juste {
box-shadow: 0 0 8px 10px green;
}
......@@ -36,6 +36,9 @@
<% }); %>
<%// } while (i != set.firstQ) %>
</ul>
<div onclick="customQuestion()" class="controlPanel" id="customQuestion" style="font-family: Impact, 'Arial Black', Arial, Verdana, sans-serif;">
Créer sa question temporaire
</div>
<div class="controlPanel" id="stats">
<ul>