Monster commit : a lot of cleaning

- Removed the series of nested callback in
    controller/prototype/index.js. Now, there are dbrequests that gives
    functions to easily access results of SQL requests.

  - Cleaning in the views : removed if / else if / else if in prototype
    and replaced by template inheritance
This commit is contained in:
Thomas FORGIONE 2015-05-22 12:04:39 +02:00
parent ea56745b3f
commit 74eda889be
9 changed files with 495 additions and 350 deletions

345
controllers/prototype/dbrequests.js vendored Normal file
View File

@ -0,0 +1,345 @@
var pg = require('pg');
var pgc = require('../../private.js');
var Info = function(id, finishAction) {
this.id = id;
this.ready = {
cameras: false,
coins: false,
arrows: false,
resets : false,
previousNext: false,
hovered: false
};
this.results = {};
this.finishAction = finishAction;
// Connect to db
var self = this;
pg.connect(pgc.url, function(err, client, release) {
self.client = client;
self.release = release;
self.loadAll();
});
}
Info.prototype.loadAll = function() {
this.loadCameras();
this.loadCoins();
this.loadArrows();
this.loadResets();
this.loadPreviousNext();
this.loadHovered();
}
Info.prototype.tryMerge = function() {
// If not ready, do nothing
for (var i in this.ready) {
if (!this.ready[i]) {
return;
}
}
// Release db connection
this.release();
this.release = null;
this.client = null;
this.merge();
this.finishAction(this.finalResult);
}
Info.prototype.merge = function() {
this.finalResult = [];
for (;;) {
// Find next element
var nextElement = null;
var nextIndex = null;
for (var i in this.results) {
// The next element is placed at the index 0 (since the elements
// gotten from the database are sorted)
if (this.results[i].length !== 0 &&
(nextElement === null || this.results[i][0].time < nextElement.time)) {
nextElement = this.results[i][0];
nextIndex = i;
}
}
// If there is no next element, we're done
if (nextElement === null) {
break;
}
// Add the next element in results and shift its table
this.finalResult.push(this.results[nextIndex].shift());
}
}
Info.prototype.loadCameras = function() {
var self = this;
this.client.query(
"SELECT ((camera).position).x AS px, " +
"((camera).position).y AS py, " +
"((camera).position).z AS pz, " +
"((camera).target).x AS tx, " +
"((camera).target).y AS ty, " +
"((camera).target).z AS tz, " +
"time AS time " +
"FROM keyboardevent WHERE user_id = $1 ORDER BY time;",
[self.id],
function(err, result) {
self.results.cameras = [];
for (var i in result.rows) {
self.results.cameras.push(
{
type: 'camera',
position : {
x: result.rows[i].px,
y: result.rows[i].py,
z: result.rows[i].pz
},
target : {
x: result.rows[i].tx,
y: result.rows[i].ty,
z: result.rows[i].tz
},
time: result.rows[i].time
}
);
}
self.ready.cameras = true;
self.tryMerge();
}
);
}
Info.prototype.loadCoins = function() {
var self = this;
this.client.query(
"SELECT coin_id, time FROM coinclicked WHERE user_id = $1 ORDER BY time;",
[self.id],
function(err,result) {
self.results.coins = [];
for (var i in result.rows) {
self.results.coins.push(
{
type: 'coin',
time: result.rows[i].time,
id: result.rows[i].coin_id
}
);
}
self.ready.coins = true;
self.tryMerge();
}
);
}
Info.prototype.loadArrows = function() {
var self = this;
this.client.query(
"SELECT arrow_id, time FROM arrowclicked WHERE user_id = $1 ORDER BY time;",
[self.id],
function(err, result) {
self.results.arrows = [];
for (var i in result.rows) {
self.results.arrows.push(
{
type: 'arrow',
time: result.rows[i].time,
id: result.rows[i].arrow_id
}
);
}
self.ready.arrows = true;
self.tryMerge();
}
);
}
Info.prototype.loadResets = function() {
var self = this;
this.client.query(
"SELECT time FROM resetclicked WHERE user_id = $1 ORDER BY time;",
[self.id],
function(err, result) {
self.results.resets = [];
for (var i in result.rows) {
self.results.resets.push(
{
type: 'reset',
time: result.rows[i].time
}
);
}
self.ready.resets = true;
self.tryMerge();
}
);
}
Info.prototype.loadPreviousNext = function () {
var self = this;
this.client.query(
"SELECT ((camera).position).x AS px, " +
"((camera).position).y AS py, " +
"((camera).position).z AS pz, " +
"((camera).target).x AS tx, " +
"((camera).target).y AS ty, " +
"((camera).target).z AS tz, " +
"time AS time " +
"FROM previousnextclicked WHERE user_id = $1 ORDER BY time;",
[self.id],
function(err, result) {
self.results.previousNext = [];
for (var i in result.rows) {
self.results.previousNext.push(
{
type: 'previousnext',
time: result.rows[i].time,
previous: result.rows[i].previousnext == 'p',
position : {
x: result.rows[i].px,
y: result.rows[i].py,
z: result.rows[i].pz
},
target : {
x: result.rows[i].tx,
y: result.rows[i].ty,
z: result.rows[i].tz
}
}
);
}
self.ready.previousNext = true;
self.tryMerge();
}
);
}
Info.prototype.loadHovered = function() {
var self = this;
this.client.query(
"SELECT start, time, arrow_id FROM hovered WHERE user_id = $1 ORDER BY time;",
[self.id],
function(err, result) {
self.results.hovered = [];
for (var i in result.rows) {
self.results.hovered.push(
{
type: "hovered",
time: result.rows[i].time,
start: result.rows[i].start,
id: result.rows[i].arrow_id
}
);
}
self.ready.hovered = true;
self.tryMerge();
}
);
}
var IdCreator = function(finishAction) {
this.finishAction = finishAction;
// Connect to db
var self = this;
pg.connect(pgc.url, function(err, client, release) {
self.client = client;
self.release = release;
self.execute();
});
}
IdCreator.prototype.execute = function() {
var self = this;
this.client.query(
"INSERT INTO users(name) VALUES('anonymous'); SELECT currval('users_id_seq');",
[],
function(err, result) {
self.finalResult = result.rows[0].currval;
self.finish();
}
);
}
IdCreator.prototype.finish = function() {
this.release();
this.client = null;
this.release = null;
this.finishAction(this.finalResult);
}
var IdChecker = function(id, finishAction) {
this.id = id;
this.finishAction = finishAction;
var self = this;
pg.connect(pgc.url, function(err, client, release) {
self.client = client;
self.release = release;
self.execute();
});
}
IdChecker.prototype.execute = function() {
var self = this;
this.client.query(
"SELECT count(id) > 0 AS answer FROM users WHERE id = $1;",
[self.id],
function(err, result) {
self.finalResult = result.rows[0].answer;
self.finish();
}
);
}
IdChecker.prototype.finish = function() {
this.release();
this.client = null;
this.release = null;
this.finishAction(this.finalResult);
}
var UserGetter = function(finishAction) {
this.finishAction = finishAction;
var self = this;
pg.connect(pgc.url, function(err, client, release) {
self.client = client;
self.release = release;
self.execute();
});
}
UserGetter.prototype.execute = function() {
var self = this;
this.client.query(
"SELECT id, name FROM users",
[],
function(err, result) {
self.finalResult = result.rows;
self.finish();
}
);
}
UserGetter.prototype.finish = function() {
this.release();
this.client = null;
this.release = null;
this.finishAction(this.finalResult);
}
module.exports.getInfo = function(id, callback) { new Info(id, callback); };
module.exports.createId = function(callback) { new IdCreator(callback); };
module.exports.checkId = function(id, callback) { new IdChecker(id, callback); };
module.exports.getAllUsers = function(callback) { new UserGetter(callback); };

View File

@ -1,207 +1,7 @@
var tools = require('../../my_modules/filterInt.js'); var tools = require('../../my_modules/filterInt');
var pg = require('pg'); var pg = require('pg');
var pgc = require('../../private.js'); var pgc = require('../../private');
var db = require('./dbrequests');
var createNewId = function(req, res, callback) {
pg.connect(pgc.url, function(err, client, release) {
client.query(
"INSERT INTO users(name) VALUES('anonymous'); SELECT currval('users_id_seq');",
[],
function(err, result) {
req.session.user_id = result.rows[0].currval;
req.session.save();
callback();
release();
}
);
});
}
var checkId = function(req, res, next, callback, id) {
pg.connect(pgc.url, function(err, client, release) {
client.query(
"SELECT id FROM users WHERE id = $1",
[id],
function(err, result) {
if (result.rows.length > 0) {
callback();
} else {
var error = new Error("Id not found");
error.status = 404;
next(error);
}
release();
}
);
});
}
var addCamerasFromId = function(client, req, res, callback, id) {
client.query(
"SELECT ((camera).position).x AS px, " +
"((camera).position).y AS py, " +
"((camera).position).z AS pz, " +
"((camera).target).x AS tx, " +
"((camera).target).y AS ty, " +
"((camera).target).z AS tz, " +
"time AS time " +
"FROM keyboardevent WHERE user_id = $1 ORDER BY time;",
[id],
function(err, result) {
res.locals.path = res.locals.path || [];
for (var i in result.rows) {
res.locals.path.push(
{
type: 'camera',
position : {
x: result.rows[i].px,
y: result.rows[i].py,
z: result.rows[i].pz
},
target : {
x: result.rows[i].tx,
y: result.rows[i].ty,
z: result.rows[i].tz
},
time: result.rows[i].time
}
);
}
callback();
}
);
}
var addCoinsFromId = function(client, req, res, callback, id) {
client.query(
"SELECT coin_id, time FROM coinclicked WHERE user_id = $1",
[id],
function(err,result) {
res.locals.path = res.locals.path || [];
for (var i in result.rows) {
res.locals.path.push(
{
type: 'coin',
time: result.rows[i].time,
id: result.rows[i].coin_id
}
);
}
callback();
}
);
}
var addArrowsFromId = function(client, req, res, callback, id) {
client.query(
"SELECT arrow_id, time FROM arrowclicked WHERE user_id = $1",
[id],
function(err, result) {
res.locals.path = res.locals.path || [];
for (var i in result.rows) {
res.locals.path.push(
{
type: 'arrow',
time: result.rows[i].time,
id: result.rows[i].arrow_id
}
);
}
callback();
}
);
}
var addResetsFromId = function(client, req, res, callback, id) {
client.query(
"SELECT time FROM resetclicked WHERE user_id = $1",
[id],
function(err, result) {
res.locals.path = res.locals.path || [];
for (var i in result.rows) {
res.locals.path.push(
{
type: 'reset',
time: result.rows[i].time
}
);
}
callback();
}
);
}
var addPreviousNextFromId = function(client, req, res, callback, id) {
client.query(
"SELECT ((camera).position).x AS px, " +
"((camera).position).y AS py, " +
"((camera).position).z AS pz, " +
"((camera).target).x AS tx, " +
"((camera).target).y AS ty, " +
"((camera).target).z AS tz, " +
"time AS time " +
"FROM previousnextclicked WHERE user_id = $1;",
[id],
function(err, result) {
res.locals.path = res.locals.path || [];
for (var i in result.rows) {
res.locals.path.push(
{
type: 'previousnext',
time: result.rows[i].time,
previous: result.rows[i].previousnext == 'p',
position : {
x: result.rows[i].px,
y: result.rows[i].py,
z: result.rows[i].pz
},
target : {
x: result.rows[i].tx,
y: result.rows[i].ty,
z: result.rows[i].tz
}
}
);
}
callback();
}
);
}
var addHoveredFromId = function(client, req, res, callback, id) {
client.query(
"SELECT start, time, arrow_id FROM hovered WHERE user_id = $1",
[id],
function(err, result) {
res.locals.path = res.locals.path || [];
for (var i in result.rows) {
res.locals.path.push(
{
type: "hovered",
time: result.rows[i].time,
start: result.rows[i].start,
id: result.rows[i].arrow_id
}
);
}
callback();
}
);
}
var getAllUsers = function(req, res, callback) {
pg.connect(pgc.url, function(err, client, release) {
client.query(
"SELECT id, name FROM users;",
[],
function(err, result) {
res.locals.ids = result.rows;
callback();
release();
}
);
});
}
module.exports.index = function(req, res) { module.exports.index = function(req, res) {
res.setHeader('Content-Type', 'text/html'); res.setHeader('Content-Type', 'text/html');
@ -211,41 +11,23 @@ module.exports.index = function(req, res) {
}); });
} }
module.exports.arrows = function(req, res) { var protoHelper = function(template) {
createNewId(req, res, function() { return function(req, res) {
res.setHeader('Content-Type', 'text/html'); db.createId(function(id) {
req.session.user_id = id;
req.session.save();
res.locals.cameraStyle = 'arrows'; res.setHeader('Content-Type','text/html');
res.render(template, res.locals, function(err, result) {
res.render('prototype.jade', res.locals, function(err, result) {
res.send(result); res.send(result);
}); });
}); });
};
} }
module.exports.viewports = function(req, res) { module.exports.arrows = protoHelper('prototype_arrows.jade');
createNewId(req, res, function() { module.exports.viewports = protoHelper('prototype_viewports.jade');
res.setHeader('Content-Type', 'text/html'); module.exports.reverse = protoHelper('prototype_reverse.jade');
res.locals.cameraStyle = 'viewports';
res.render('prototype.jade', res.locals, function(err, result) {
res.send(result);
});
});
}
module.exports.reverse = function(req, res) {
createNewId(req, res, function() {
res.setHeader('Content-Type', 'text/html');
res.locals.cameraStyle = 'reverse';
res.render('prototype.jade', res.locals, function(err, result) {
res.send(result);
});
});
}
module.exports.replay_info = function(req, res) { module.exports.replay_info = function(req, res) {
res.setHeader('Content-Type', 'text/plain'); res.setHeader('Content-Type', 'text/plain');
@ -253,46 +35,34 @@ module.exports.replay_info = function(req, res) {
// Parse id // Parse id
var id = tools.filterInt(req.params.id); var id = tools.filterInt(req.params.id);
pg.connect(pgc.url, function(err, client, release) { db.getInfo(id, function(results) {
addCamerasFromId(client, req, res, function() { res.send(JSON.stringify(results));
addCoinsFromId(client, req, res, function() {
addArrowsFromId(client, req, res, function() {
addResetsFromId(client, req, res, function() {
addPreviousNextFromId(client, req, res, function() {
addHoveredFromId(client, req, res, function() {
res.locals.path.sort(function(elt1, elt2) {
// Dates as string can be compared
if (elt1.time < elt2.time)
return -1;
if (elt1.time > elt2.time)
return 1;
return 0;
});
res.send(JSON.stringify(res.locals.path));
}, id);
}, id);
}, id);
}, id);
}, id);
}, id);
release();
}); });
} }
module.exports.replay = function(req, res, next) { module.exports.replay = function(req, res, next) {
// Get id parameter
res.locals.id = tools.filterInt(req.params.id); res.locals.id = tools.filterInt(req.params.id);
checkId(req,res, next, function() {
db.checkId(res.locals.id, function(idExist) {
if (!idExist) {
var err = new Error("This replay does not exist");
err.status = 404;
next(err);
} else {
res.setHeader('Content-Type', 'text/html'); res.setHeader('Content-Type', 'text/html');
res.locals.cameraStyle = "replay"; res.render('prototype_replays.jade', res.locals, function(err, result) {
res.render('prototype.jade', res.locals, function(err, result) {
res.send(result); res.send(result);
}); });
}, res.locals.id); }
});
} }
module.exports.replay_index = function(req, res, next) { module.exports.replay_index = function(req, res, next) {
getAllUsers(req, res, function() { db.getAllUsers(function(result) {
res.locals.users = result;
res.setHeader('Content-Type', 'text/html');
res.render("replay_index.jade", res.locals, function(err, result) { res.render("replay_index.jade", res.locals, function(err, result) {
res.send(result); res.send(result);
}); });

View File

@ -15,20 +15,8 @@ block extrajs
script(src="/static/js/prototype/Coin.js") script(src="/static/js/prototype/Coin.js")
script(src="/static/js/Logger.js") script(src="/static/js/Logger.js")
if cameraStyle == 'replay' block configjs
script var params = params || {}; params.get = params.get || {}; params.get.id = #{id}; block mainjs
script(src="/static/js/ReplayCamera.js")
script(src="/static/js/prototype/replay.js")
else
if cameraStyle == 'arrows'
script RecommendedCamera = FixedCamera;
else if cameraStyle == 'viewports'
script RecommendedCamera = OldFixedCamera;
else if cameraStyle == 'reverse'
script RecommendedCamera = ReverseCamera
script(src="/static/js/prototype/main.js")
script document.getElementById('music').volume = 0.5; script document.getElementById('music').volume = 0.5;
block extrahead block extrahead
@ -36,75 +24,7 @@ block extrahead
block content block content
#main-div.panel-group(style={'margin-top':'10px', 'margin-bottom':'10px'}) #main-div.panel-group(style={'margin-top':'10px', 'margin-bottom':'10px'})
if cameraStyle != 'replay' block description
#instructions.panel.panel-default
.panel-heading
h4.panel-title
a(href="#", data-target="#collapseInstructions", data-toggle="collapse",onclick="setTimeout(onWindowResize,500);") Instructions
.panel-collapse.collapse.in#collapseInstructions
.panel-body
p
| This is the prototype of a 3D interface. You can move
| the camera with the arrow keys of your keyboard (or
| WASD if you like FPS-games, and by the way, if you
| use azerty keyboard, you can also use ZQSD instead),
| and change the angle of the camera by dragging and
| dropping the scene around it (you can also use your
| numpad, 2 to look lower, 8 to look higher, 4 to look
| on the left and 6 to look on the right, but if you're
| more comfortable with non-numpad keys, you can also
| use i for up, j for left, k for down, and l for
| right).
p
| This is a re-creation of the Bob-omb Battlefield
| level from Super Mario 64, and with its 8 red coins.
| You can click on them to get them, and once you got
| them all, you get... well you get nothing but the
| sound of the star is played (but the star doesn't
| appear... sorry guys)
p
| It contains <em>recommended views</em> : 3D objects
| here to guide you through this coin search.
if cameraStyle == 'arrows'
p
| Recommended views are displayed with a
| transparent blue arrow. They disappear when you
| come closer to them, and shows the motion between
| your current position and the recommendation.
else if cameraStyle == 'viewports'
p
| Recommended views are displayed with a
| transparent red rectangle and some lines.
| Basically, it represents the position of a camera
| (the point at the extramities of the lines
| represents the optical center, and the red
| rectangle represents the image plane).
else if cameraStyle == 'reverse'
p
| Recommended views are displayed with a strange
| blue object. Basically, the curve at the
| begining of this weird object shows the motion
| that starts from where you are and leads to the
| recommended view, and the extremity is in fact an
| object representing a camera.
p
| You can click on a recommendation to move to the
| recommended viewpoint. The recommendation will become
| more and more transparent as you come closer, and
| will disappear when you reach it.
p
| You may now <a href="#" data-target="#collapseInstructions" data-toggle="collapse" onclick="setTimeout(onWindowResize,500);">hide this panel</a> and start playing !
button#full.btn.btn-primary(style={'margin-right': '10px', 'margin-bottom': '10px'}) Fullscreen button#full.btn.btn-primary(style={'margin-right': '10px', 'margin-bottom': '10px'}) Fullscreen
button#reset.btn.btn-primary(style={'margin-right': '10px', 'margin-bottom':'10px'}) Reset camera button#reset.btn.btn-primary(style={'margin-right': '10px', 'margin-bottom':'10px'}) Reset camera

View File

@ -0,0 +1,14 @@
extends ./prototype_interactive
block title
title #{title} - Prototype - Arrows
block configjs
script RecommendedCamera = FixedCamera;
block preciseDescription
p
| Recommended views are displayed with a
| transparent blue arrow. They disappear when you
| come closer to them, and shows the motion between
| your current position and the recommendation.

View File

@ -0,0 +1,51 @@
extends ./prototype
block title
title #{title} - Prototype
block mainjs
script(src="/static/js/prototype/main.js")
block description
#instructions.panel.panel-default
.panel-heading
h4.panel-title
a(href="#", data-target="#collapseInstructions", data-toggle="collapse",onclick="setTimeout(onWindowResize,500);") Instructions
.panel-collapse.collapse.in#collapseInstructions
.panel-body
p
| This is the prototype of a 3D interface. You can move
| the camera with the arrow keys of your keyboard (or
| WASD if you like FPS-games, and by the way, if you
| use azerty keyboard, you can also use ZQSD instead),
| and change the angle of the camera by dragging and
| dropping the scene around it (you can also use your
| numpad, 2 to look lower, 8 to look higher, 4 to look
| on the left and 6 to look on the right, but if you're
| more comfortable with non-numpad keys, you can also
| use i for up, j for left, k for down, and l for
| right).
p
| This is a re-creation of the Bob-omb Battlefield
| level from Super Mario 64, and with its 8 red coins.
| You can click on them to get them, and once you got
| them all, you get... well you get nothing but the
| sound of the star is played (but the star doesn't
| appear... sorry guys)
p
| It contains <em>recommended views</em> : 3D objects
| here to guide you through this coin search.
block preciseDescription
p
| You can click on a recommendation to move to the
| recommended viewpoint. The recommendation will become
| more and more transparent as you come closer, and
| will disappear when you reach it.
p
| You may now <a href="#" data-target="#collapseInstructions" data-toggle="collapse" onclick="setTimeout(onWindowResize,500);">hide this panel</a> and start playing !

View File

@ -0,0 +1,12 @@
extends ./prototype
block title
title #{title} - Prototype - Replay
block configjs
script RecommendedCamera = FixedCamera;
script var params = params || {}; params.get = params.get || {}; params.get.id = #{id};
script(src="/static/js/ReplayCamera.js")
block mainjs
script(src="/static/js/prototype/replay.js")

View File

@ -0,0 +1,17 @@
extends ./prototype_interactive
block title
title #{title} - Prototype - Reverse
block configjs
script RecommendedCamera = ReverseCamera;
block preciseDescription
p
| Recommended views are displayed with a strange
| blue object. Basically, the curve at the
| begining of this weird object shows the motion
| that starts from where you are and leads to the
| recommended view, and the extremity is in fact an
| object representing a camera.

View File

@ -0,0 +1,16 @@
extends ./prototype_interactive
block title
title #{title} - Prototype - Viewports
block configjs
script RecommendedCamera = OldFixedCamera;
block preciseDescription
p
| Recommended views are displayed with a
| transparent red rectangle and some lines.
| Basically, it represents the position of a camera
| (the point at the extramities of the lines
| represents the optical center, and the red
| rectangle represents the image plane).

View File

@ -5,9 +5,9 @@ block title
block content block content
h2 Replays available h2 Replays available
if ids.length == 0 if users.length == 0
p Sorry, there are no replays available... try later or do an experiment ! p Sorry, there are no replays available... try later or do an experiment !
else else
ol ol
each elt in ids each elt in users
li <a href="/prototype/replay/#{elt.id}">#{elt.name}</a> li <a href="/prototype/replay/#{elt.id}">#{elt.name}</a>