diff --git a/controllers/prototype/dbrequests.js b/controllers/prototype/dbrequests.js
new file mode 100644
index 0000000..5118290
--- /dev/null
+++ b/controllers/prototype/dbrequests.js
@@ -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); };
diff --git a/controllers/prototype/index.js b/controllers/prototype/index.js
index a01449a..e13cff5 100644
--- a/controllers/prototype/index.js
+++ b/controllers/prototype/index.js
@@ -1,207 +1,7 @@
-var tools = require('../../my_modules/filterInt.js');
+var tools = require('../../my_modules/filterInt');
var pg = require('pg');
-var pgc = require('../../private.js');
-
-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();
- }
- );
- });
-}
+var pgc = require('../../private');
+var db = require('./dbrequests');
module.exports.index = function(req, res) {
res.setHeader('Content-Type', 'text/html');
@@ -211,41 +11,23 @@ module.exports.index = function(req, res) {
});
}
-module.exports.arrows = function(req, res) {
- createNewId(req, res, function() {
- res.setHeader('Content-Type', 'text/html');
+var protoHelper = function(template) {
+ return function(req, res) {
+ db.createId(function(id) {
+ req.session.user_id = id;
+ req.session.save();
- res.locals.cameraStyle = 'arrows';
-
- res.render('prototype.jade', res.locals, function(err, result) {
- res.send(result);
+ res.setHeader('Content-Type','text/html');
+ res.render(template, res.locals, function(err, result) {
+ res.send(result);
+ });
});
- });
+ };
}
-module.exports.viewports = function(req, res) {
- createNewId(req, res, function() {
- res.setHeader('Content-Type', 'text/html');
-
- 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.arrows = protoHelper('prototype_arrows.jade');
+module.exports.viewports = protoHelper('prototype_viewports.jade');
+module.exports.reverse = protoHelper('prototype_reverse.jade');
module.exports.replay_info = function(req, res) {
res.setHeader('Content-Type', 'text/plain');
@@ -253,46 +35,34 @@ module.exports.replay_info = function(req, res) {
// Parse id
var id = tools.filterInt(req.params.id);
- pg.connect(pgc.url, function(err, client, release) {
- addCamerasFromId(client, req, res, function() {
- 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();
+ db.getInfo(id, function(results) {
+ res.send(JSON.stringify(results));
});
}
module.exports.replay = function(req, res, next) {
+ // Get id parameter
res.locals.id = tools.filterInt(req.params.id);
- checkId(req,res, next, function() {
- res.setHeader('Content-Type', 'text/html');
- res.locals.cameraStyle = "replay";
- res.render('prototype.jade', res.locals, function(err, result) {
- res.send(result);
- });
- }, res.locals.id);
+ 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.render('prototype_replays.jade', res.locals, function(err, result) {
+ res.send(result);
+ });
+ }
+ });
}
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.send(result);
});
diff --git a/controllers/prototype/views/prototype.jade b/controllers/prototype/views/prototype.jade
index 838b84e..fb82db6 100644
--- a/controllers/prototype/views/prototype.jade
+++ b/controllers/prototype/views/prototype.jade
@@ -15,20 +15,8 @@ block extrajs
script(src="/static/js/prototype/Coin.js")
script(src="/static/js/Logger.js")
- if cameraStyle == 'replay'
- script var params = params || {}; params.get = params.get || {}; params.get.id = #{id};
- 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")
-
+ block configjs
+ block mainjs
script document.getElementById('music').volume = 0.5;
block extrahead
@@ -36,75 +24,7 @@ block extrahead
block content
#main-div.panel-group(style={'margin-top':'10px', 'margin-bottom':'10px'})
- if cameraStyle != 'replay'
- #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 recommended views : 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 hide this panel and start playing !
+ block description
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
diff --git a/controllers/prototype/views/prototype_arrows.jade b/controllers/prototype/views/prototype_arrows.jade
new file mode 100644
index 0000000..8c3c656
--- /dev/null
+++ b/controllers/prototype/views/prototype_arrows.jade
@@ -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.
diff --git a/controllers/prototype/views/prototype_interactive.jade b/controllers/prototype/views/prototype_interactive.jade
new file mode 100644
index 0000000..69f4315
--- /dev/null
+++ b/controllers/prototype/views/prototype_interactive.jade
@@ -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 recommended views : 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 hide this panel and start playing !
diff --git a/controllers/prototype/views/prototype_replays.jade b/controllers/prototype/views/prototype_replays.jade
new file mode 100644
index 0000000..ba28af7
--- /dev/null
+++ b/controllers/prototype/views/prototype_replays.jade
@@ -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")
diff --git a/controllers/prototype/views/prototype_reverse.jade b/controllers/prototype/views/prototype_reverse.jade
new file mode 100644
index 0000000..4fe3d7f
--- /dev/null
+++ b/controllers/prototype/views/prototype_reverse.jade
@@ -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.
+
diff --git a/controllers/prototype/views/prototype_viewports.jade b/controllers/prototype/views/prototype_viewports.jade
new file mode 100644
index 0000000..71f69f8
--- /dev/null
+++ b/controllers/prototype/views/prototype_viewports.jade
@@ -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).
diff --git a/controllers/prototype/views/replay_index.jade b/controllers/prototype/views/replay_index.jade
index 6a9e49c..92e806f 100644
--- a/controllers/prototype/views/replay_index.jade
+++ b/controllers/prototype/views/replay_index.jade
@@ -5,9 +5,9 @@ block title
block content
h2 Replays available
- if ids.length == 0
+ if users.length == 0
p Sorry, there are no replays available... try later or do an experiment !
else
ol
- each elt in ids
+ each elt in users
li #{elt.name}