Initial commit
This commit is contained in:
commit
3490d8aab7
|
@ -0,0 +1,21 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015 Thomas FORGIONE
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
|
@ -0,0 +1,18 @@
|
||||||
|
# slideshow.io
|
||||||
|
|
||||||
|
## What is it
|
||||||
|
Client-server application to manage slides on different computers during a
|
||||||
|
presentation.
|
||||||
|
|
||||||
|
You can upload your pdf, and a link will be sent so you can share it with your
|
||||||
|
viewer. You will have a *speaker* interface, allowing you to switch between
|
||||||
|
slides and interact with them (underline elements, use a pointer...) and your
|
||||||
|
viewer will see what you are currently doing.
|
||||||
|
|
||||||
|
## Install
|
||||||
|
A simple `npm install` will work.
|
||||||
|
|
||||||
|
## License
|
||||||
|
This software is under MIT license. Check
|
||||||
|
[LICENSE.md](https://github.com/tforgione/slideshow.io/blob/master/LICENSE.md) for
|
||||||
|
more information.
|
|
@ -0,0 +1,61 @@
|
||||||
|
var express = require('express');
|
||||||
|
var fs = require('fs');
|
||||||
|
var log = require('log.js')
|
||||||
|
|
||||||
|
module.exports = function(parent){
|
||||||
|
|
||||||
|
log.debug("Loading get :");
|
||||||
|
|
||||||
|
fs.readdirSync(__dirname + '/../routes').forEach(function(name){
|
||||||
|
|
||||||
|
// index.js in controller, with function as pages (views.py for django)
|
||||||
|
var obj = require('./../routes/' + name + '/index');
|
||||||
|
|
||||||
|
// urls.js, just like django urls.py
|
||||||
|
var urls = require('./../routes/' + name + '/urls');
|
||||||
|
name = obj.name || name;
|
||||||
|
var app = express();
|
||||||
|
|
||||||
|
// allow specifying the view engine
|
||||||
|
if (obj.engine) app.set('view engine', obj.engine);
|
||||||
|
app.set('views', __dirname + '/../routes/' + name + '/views');
|
||||||
|
|
||||||
|
// generate routes based
|
||||||
|
// on the exported methods
|
||||||
|
|
||||||
|
log.debug(' ' + name + ':');
|
||||||
|
|
||||||
|
for (var key in urls) {
|
||||||
|
|
||||||
|
app.get(key, ((key) => function(req, res, next) {
|
||||||
|
|
||||||
|
var path = obj[urls[key]](req, res, function(view) {
|
||||||
|
res.render(
|
||||||
|
__dirname +
|
||||||
|
'/../routes/' +
|
||||||
|
name + '/views/' +
|
||||||
|
view,
|
||||||
|
res.locals,
|
||||||
|
function(err, out) {
|
||||||
|
if (err !== null) {
|
||||||
|
log.pugerror(err);
|
||||||
|
}
|
||||||
|
res.send(out);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
}, next);
|
||||||
|
|
||||||
|
})(key));
|
||||||
|
|
||||||
|
log.debug(' ' + key + ' -> ' + name + '.' + urls[key]);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug();
|
||||||
|
|
||||||
|
// mount the app
|
||||||
|
parent.use(app);
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
|
@ -0,0 +1,145 @@
|
||||||
|
var express = require('express');
|
||||||
|
var http = require('http');
|
||||||
|
var yargs = require('yargs');
|
||||||
|
|
||||||
|
var argv = yargs.argv;
|
||||||
|
|
||||||
|
var log = {};
|
||||||
|
|
||||||
|
var Color = {
|
||||||
|
DEFAULT:0,
|
||||||
|
BLACK:1,
|
||||||
|
RED:2,
|
||||||
|
GREEN:3,
|
||||||
|
YELLOW:4,
|
||||||
|
BLUE:5,
|
||||||
|
MAGENTA:6,
|
||||||
|
CYAN:7,
|
||||||
|
ORANGE:8
|
||||||
|
}
|
||||||
|
|
||||||
|
function getColorCode(c) {
|
||||||
|
switch (c) {
|
||||||
|
case Color.DEFAULT: return '\u001b[0m';
|
||||||
|
case Color.BLACK: return '\u001b[30m';
|
||||||
|
case Color.RED: return '\u001b[31m';
|
||||||
|
case Color.GREEN: return '\u001b[32m';
|
||||||
|
case Color.YELLOW: return '\u001b[33m';
|
||||||
|
case Color.BLUE: return '\u001b[34m';
|
||||||
|
case Color.MAGENTA: return '\u001b[35m';
|
||||||
|
case Color.CYAN: return '\u001b[36m';
|
||||||
|
case Color.ORANGE: return '\u001b[38;5;202m';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var isDev = require('express')().get('env') === 'development';
|
||||||
|
|
||||||
|
var write;
|
||||||
|
|
||||||
|
if (argv.nolisten || argv.n) {
|
||||||
|
write = function(elt, color) { }
|
||||||
|
} else if (isDev) {
|
||||||
|
write = function(elt, color) {
|
||||||
|
console.log(getColorCode(color) + elt + getColorCode(Color.DEFAULT));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
write = function(elt, color) {
|
||||||
|
console.log(elt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.ready = function(msg) {
|
||||||
|
write('[RDY] ' + new Date() + ' ' + msg, Color.GREEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.request = function(req, res, time) {
|
||||||
|
|
||||||
|
if (req.headers['x-forwarded-for'] !== undefined || isDev) {
|
||||||
|
|
||||||
|
var isStatic = req.url.substr(0, 7) === '/static' || req.url === '/favicon.ico';
|
||||||
|
|
||||||
|
write(
|
||||||
|
'[REQ] ' + new Date() + ' ' +
|
||||||
|
(req.headers['x-forwarded-for'] || req.connection.remoteAddress) +
|
||||||
|
(time !== undefined ? (' in ' + (" " + time).slice(-6) + ' ms') : '') +
|
||||||
|
' : ' + (isStatic && req.url !== '/favicon.ico' ? '/static' + req.url : req.url),
|
||||||
|
isStatic ? Color.YELLOW : Color.CYAN
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
log.socket = {};
|
||||||
|
|
||||||
|
log.socket.connection = function(socket, isTest) {
|
||||||
|
write(
|
||||||
|
'[SOK] ' + new Date() + ' ' +
|
||||||
|
(socket.handshake.headers['x-forwarded-for'] || socket.handshake.address) + (isTest ? ' test' : '') + ' connection',
|
||||||
|
Color.MAGENTA
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.socket.disconnect = function(socket, isTest) {
|
||||||
|
write(
|
||||||
|
'[SOK] ' + new Date() + ' ' +
|
||||||
|
(socket.handshake.headers['x-forwarded-for'] || socket.handshake.address) + (isTest ? ' test' : '') + ' disconnect',
|
||||||
|
Color.MAGENTA
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.dberror = function(error) {
|
||||||
|
write(
|
||||||
|
'[DBE] ' + new Date() + ' ' + error,
|
||||||
|
Color.RED
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.prefetcherror = function(error) {
|
||||||
|
write(
|
||||||
|
'[PFE] ' + new Date() + ' ' + error,
|
||||||
|
Color.RED
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.mailerror = function(error) {
|
||||||
|
write(
|
||||||
|
'[MLE] ' + new Date() + ' ' + error,
|
||||||
|
Color.RED
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug = function(info, force) {
|
||||||
|
if (isDev || force === true) {
|
||||||
|
write(
|
||||||
|
'[DBG] ' + (info !== undefined ? info : ''),
|
||||||
|
Color.YELLOW
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.pugerror = function(error) {
|
||||||
|
write(
|
||||||
|
'[PER] ' + new Date() + ' ' + error,
|
||||||
|
Color.RED
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.warning = function(message) {
|
||||||
|
|
||||||
|
write(
|
||||||
|
'[WRN] ' + new Date() + ' ' + message,
|
||||||
|
Color.ORANGE
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
log.faceerror = function(message) {
|
||||||
|
|
||||||
|
write(
|
||||||
|
'[FER] ' + new Date() + ' ' + message,
|
||||||
|
Color.RED
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = log;
|
|
@ -0,0 +1,34 @@
|
||||||
|
module.exports = function(io) {
|
||||||
|
|
||||||
|
io.on('connection', function(socket) {
|
||||||
|
|
||||||
|
socket.emit('welcome');
|
||||||
|
|
||||||
|
socket.on('viewer', function(filename) {
|
||||||
|
socket.join(filename);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('speaker', function(filename, socketId) {
|
||||||
|
socket.join(filename);
|
||||||
|
socket.speakerId = socketId;
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('change-slide', function(filename, pageNumber) {
|
||||||
|
socket.broadcast.to(filename).emit('update', pageNumber);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('pointer', function(filename, id, x, y) {
|
||||||
|
if (id === socket.speakerId) {
|
||||||
|
socket.broadcast.to(filename).emit('pointer', 'speaker', x, y);
|
||||||
|
} else {
|
||||||
|
socket.broadcast.to(filename).emit('pointer', 'speaker', x, y);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('viewer-laser', function(filename, enabled) {
|
||||||
|
socket.broadcast.to(filename).emit('viewer-laser', enabled);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"name" : "slideshow.io",
|
||||||
|
"version" : "0.1.0",
|
||||||
|
"dependencies" : {
|
||||||
|
"express" : "*",
|
||||||
|
"pug" : "*",
|
||||||
|
"socket.io" : "*",
|
||||||
|
"node-uuid" : "*",
|
||||||
|
"body-parser" : "*",
|
||||||
|
"app-module-path":"*"
|
||||||
|
},
|
||||||
|
"repository" : {
|
||||||
|
"type" : "git",
|
||||||
|
"url" : "https://github.com/tforgione/slideshow.io"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
module.exports.index = function(req, res, render, next) {
|
||||||
|
|
||||||
|
res.setHeader('Content-Type', 'text/html');
|
||||||
|
render('index.pug', res.locals)
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
module.exports = {
|
||||||
|
'/' : 'index'
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
extends ../../../views/main.pug
|
||||||
|
|
||||||
|
block content
|
||||||
|
p.
|
||||||
|
Hello world !
|
|
@ -0,0 +1,25 @@
|
||||||
|
var fs = require('fs');
|
||||||
|
|
||||||
|
module.exports.index = function(req, res, render, next) {
|
||||||
|
|
||||||
|
fs.stat('static/uploaded/' + req.params.file + '.pdf', function(err) {
|
||||||
|
|
||||||
|
if (err === null) {
|
||||||
|
|
||||||
|
res.locals.file = req.params.file;
|
||||||
|
|
||||||
|
res.setHeader('Content-Type', 'text/html');
|
||||||
|
render('index.pug', res.locals);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// 404 : file does not exist
|
||||||
|
var error = new Error('File does not exist');
|
||||||
|
error.status = 404;
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
module.exports = {
|
||||||
|
'/speaker/:file' : 'index'
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
extends ../../../views/main.pug
|
||||||
|
|
||||||
|
block append css
|
||||||
|
link(href="/static/css/style.css", rel="stylesheet")
|
||||||
|
link(href="/static/css/speaker.css", rel="stylesheet")
|
||||||
|
|
||||||
|
block content
|
||||||
|
|
||||||
|
.row
|
||||||
|
.col-md-8
|
||||||
|
#canvases
|
||||||
|
canvas#canvas-pdf(style={"border" : "1px solid #000000", position:'absolute'})
|
||||||
|
canvas#canvas-paint.noselect(onmousedown="sio.onMouseDown(event);", onmousemove="sio.onMouseMove(event);", onmouseup="sio.onMouseUp(event);", onmouseout="sio.onMouseUp(event);", style={position:'absolute'})
|
||||||
|
|
||||||
|
p
|
||||||
|
.btn-group
|
||||||
|
button.btn.btn-default#first(onclick="sio.firstSlide();")
|
||||||
|
span.glyphicon.glyphicon-step-backward
|
||||||
|
button.btn.btn-default#previous(aria-hidden='true', onclick="sio.previousSlide();")
|
||||||
|
span.glyphicon.glyphicon-play.glyphicon-flip
|
||||||
|
input.btn.btn-default#counter(type='text', onclick="sio.clearCounter();", onblur="sio.update();", onkeydown="if (event.keyCode === 13) sio.changeSlideFromCounter(event);")
|
||||||
|
button.btn.btn-default#next(onclick="sio.nextSlide();")
|
||||||
|
span.glyphicon.glyphicon-play
|
||||||
|
button.btn.btn-default#last(onclick="sio.lastSlide();")
|
||||||
|
span.glyphicon.glyphicon-step-forward
|
||||||
|
|
||||||
|
span(style={'margin-left':'10px'})
|
||||||
|
.btn-group
|
||||||
|
button.btn.btn-default#sync(onclick="sio.syncAudience();") Sync viewer
|
||||||
|
|
||||||
|
span(style={'margin-left':'10px'})
|
||||||
|
.btn-group
|
||||||
|
button.btn.btn-default#viewer-laser(onclick="sio.switchAudienceLaser();") Audience laser is disabled
|
||||||
|
|
||||||
|
.col-md-4
|
||||||
|
.btn-group
|
||||||
|
button.btn.btn-default#start(onclick="sio.startPresentation();")
|
||||||
|
span.glyphicon.glyphicon-play
|
||||||
|
button.btn.btn-default#stop(onclick="sio.stopPresentation();", disabled)
|
||||||
|
span.glyphicon.glyphicon-stop
|
||||||
|
p Total time :
|
||||||
|
span#totalTime
|
||||||
|
p Slide time :
|
||||||
|
span#slideTime
|
||||||
|
|
||||||
|
block js
|
||||||
|
script filename = '#{file}';
|
||||||
|
script(src="/static/js/pdf.js")
|
||||||
|
script(src="/static/js/pdf.worker.js")
|
||||||
|
script(src="/socket.io/socket.io.js")
|
||||||
|
script(src="/static/js/speaker.js")
|
|
@ -0,0 +1,25 @@
|
||||||
|
var fs = require('fs');
|
||||||
|
|
||||||
|
module.exports.index = function(req, res, render, next) {
|
||||||
|
|
||||||
|
fs.stat('static/uploaded/' + req.params.file + '.pdf', function(err) {
|
||||||
|
|
||||||
|
if (err === null) {
|
||||||
|
|
||||||
|
res.locals.file = req.params.file;
|
||||||
|
|
||||||
|
res.setHeader('Content-Type', 'text/html');
|
||||||
|
render('index.pug', res.locals);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// 404 : file does not exist
|
||||||
|
var error = new Error('File does not exist');
|
||||||
|
error.status = 404;
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
module.exports = {
|
||||||
|
'/viewer/:file' : 'index'
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
extends ../../../views/base.pug
|
||||||
|
|
||||||
|
block css
|
||||||
|
link(href="/static/css/style.css", rel="stylesheet")
|
||||||
|
link(href="/static/css/viewer.css", rel="stylesheet")
|
||||||
|
|
||||||
|
block base_content
|
||||||
|
#canvases
|
||||||
|
canvas#canvas-pdf.absoluteCenter(style={position:'absolute'})
|
||||||
|
canvas#canvas-paint.absoluteCenter.noselect(onmousedown="sio.onMouseDown(event);", onmousemove="sio.onMouseMove(event);", onmouseup="sio.onMouseUp(event);", onmouseout="sio.onMouseUp(event);", style={position:'absolute'})
|
||||||
|
|
||||||
|
block js
|
||||||
|
script filename = '#{file}';
|
||||||
|
script(src="/static/js/pdf.js")
|
||||||
|
script(src="/static/js/pdf.worker.js")
|
||||||
|
script(src="/socket.io/socket.io.js")
|
||||||
|
script(src="/static/js/viewer.js")
|
|
@ -0,0 +1,72 @@
|
||||||
|
require('app-module-path').addPath('./lib');
|
||||||
|
|
||||||
|
var http = require('http');
|
||||||
|
var express = require('express');
|
||||||
|
var pug = require('pug');
|
||||||
|
var bp = require('body-parser');
|
||||||
|
var socket = require('socket.js');
|
||||||
|
var log = require('log.js');
|
||||||
|
var app = express();
|
||||||
|
|
||||||
|
// Socket.io initialization
|
||||||
|
var http = require('http').Server(app);
|
||||||
|
var io = require('socket.io')(http);
|
||||||
|
require('./lib/socket.js')(io);
|
||||||
|
|
||||||
|
var isDev = app.get('env') === 'development';
|
||||||
|
|
||||||
|
app.set('view engine', 'pug');
|
||||||
|
app.set('trust proxy', 1);
|
||||||
|
|
||||||
|
// parse application/x-www-form-urlencoded
|
||||||
|
app.use(bp.urlencoded({ extended: false }))
|
||||||
|
|
||||||
|
// parse application/json
|
||||||
|
app.use(bp.json())
|
||||||
|
|
||||||
|
// Log request and time to answer
|
||||||
|
app.use(function(req, res, next) {
|
||||||
|
let start = Date.now();
|
||||||
|
res.on('finish', function() {
|
||||||
|
log.request(req, res, Date.now() - start);
|
||||||
|
});
|
||||||
|
res.locals.title = "slideshow.io";
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Load controllers
|
||||||
|
require('./lib/get')(app);
|
||||||
|
|
||||||
|
// Static files
|
||||||
|
app.use('/static', express.static('static'));
|
||||||
|
|
||||||
|
// When error raised
|
||||||
|
app.use(function(err, req, res, next) {
|
||||||
|
if (err.status === 404) {
|
||||||
|
res.setHeader('Content-Type', 'text/html');
|
||||||
|
res.send('Error 404');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// When route not found, raise not found
|
||||||
|
app.use(function(req, res) {
|
||||||
|
res.setHeader('Content-Type', 'text/html');
|
||||||
|
res.send('Error 404');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set ports and ip address
|
||||||
|
var serverPort, serverIpAddress;
|
||||||
|
|
||||||
|
if ( isDev ) {
|
||||||
|
serverPort = 4001;
|
||||||
|
serverIpAddress = 'localhost';
|
||||||
|
} else {
|
||||||
|
// Openhift conf
|
||||||
|
serverPort = process.env.OPENSHIFT_NODEJS_PORT || 8080;
|
||||||
|
serverIpAddress = process.env.OPENSHIFT_NODEJS_IP || '127.0.0.1';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start server
|
||||||
|
http.listen(serverPort, serverIpAddress, function() {
|
||||||
|
log.debug("Now listening " + serverIpAddress + ":" + serverPort);
|
||||||
|
});
|
|
@ -0,0 +1,34 @@
|
||||||
|
.form-signin {
|
||||||
|
max-width: 330px;
|
||||||
|
padding: 15px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
.form-signin .form-signin-heading,
|
||||||
|
.form-signin .checkbox {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.form-signin .checkbox {
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
.form-signin .form-control {
|
||||||
|
position: relative;
|
||||||
|
height: auto;
|
||||||
|
-webkit-box-sizing: border-box;
|
||||||
|
-moz-box-sizing: border-box;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 10px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
.form-signin .form-control:focus {
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
.form-signin input[type="email"] {
|
||||||
|
margin-bottom: -1px;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
}
|
||||||
|
.form-signin input[type="password"] {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
.glyphicon-flip {
|
||||||
|
-moz-transform: scaleX(-1);
|
||||||
|
-o-transform: scaleX(-1);
|
||||||
|
-webkit-transform: scaleX(-1);
|
||||||
|
transform: scaleX(-1);
|
||||||
|
filter: FlipH;
|
||||||
|
-ms-filter: "FlipH";
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
.noselect {
|
||||||
|
cursor: default;
|
||||||
|
-webkit-touch-callout: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-khtml-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
#canvas {
|
||||||
|
margin:auto;
|
||||||
|
position:absolute;
|
||||||
|
top:0;
|
||||||
|
bottom:0;
|
||||||
|
left:0;
|
||||||
|
right:0;
|
||||||
|
max-height:100%;
|
||||||
|
max-width:100%;
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,291 @@
|
||||||
|
$(function() { sio = (function() {
|
||||||
|
|
||||||
|
var sio = {};
|
||||||
|
|
||||||
|
var counter = $('#counter');
|
||||||
|
|
||||||
|
var canvasPdf = $('#canvas-pdf').get(0);
|
||||||
|
var contextPdf = canvasPdf.getContext('2d');
|
||||||
|
|
||||||
|
var canvasPaint = $('#canvas-paint').get(0);
|
||||||
|
var contextPaint = canvasPaint.getContext('2d');
|
||||||
|
|
||||||
|
var pdf;
|
||||||
|
var currentPage = 1;
|
||||||
|
|
||||||
|
var startTime = 0;
|
||||||
|
var startSlide = 0;
|
||||||
|
var refreshIntervalId = 0;
|
||||||
|
|
||||||
|
var pointer = false;
|
||||||
|
var shouldUpdatePointer = false;
|
||||||
|
|
||||||
|
var canvasPdfBounding;
|
||||||
|
|
||||||
|
var lasers = {};
|
||||||
|
var drawAudienceLasers = false;
|
||||||
|
|
||||||
|
PDFJS.getDocument('/static/uploaded/' + filename + '.pdf').then(function getPdf(_pdf) {
|
||||||
|
pdf = _pdf;
|
||||||
|
updateUI();
|
||||||
|
});
|
||||||
|
|
||||||
|
var socket = io();
|
||||||
|
|
||||||
|
socket.on('welcome', function() {
|
||||||
|
socket.emit('speaker', filename, socket.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('pointer', canvasPointer);
|
||||||
|
|
||||||
|
function filterInt(value) {
|
||||||
|
if(/^(\-|\+)?([0-9]+|Infinity)$/.test(value))
|
||||||
|
return Number(value);
|
||||||
|
return NaN;
|
||||||
|
}
|
||||||
|
|
||||||
|
var updateUI = function() {
|
||||||
|
|
||||||
|
counter.val(currentPage + '/' + pdf.pdfInfo.numPages);
|
||||||
|
|
||||||
|
$('#previous,#first').prop('disabled', currentPage === 1);
|
||||||
|
$('#next,#last').prop('disabled', currentPage === pdf.pdfInfo.numPages);
|
||||||
|
|
||||||
|
pdf.getPage(currentPage).then(function(page) {
|
||||||
|
|
||||||
|
previousPage = currentPage;
|
||||||
|
|
||||||
|
var viewport = page.getViewport(2.0);
|
||||||
|
|
||||||
|
canvasPdf.width = viewport.width;
|
||||||
|
canvasPdf.height = viewport.height;
|
||||||
|
|
||||||
|
canvasPaint.width = viewport.width;
|
||||||
|
canvasPaint.height = viewport.height;
|
||||||
|
|
||||||
|
$('#canvases').width(canvasPdf.width + 'px');
|
||||||
|
$('#canvases').height(canvasPdf.height + 'px');
|
||||||
|
|
||||||
|
canvasPdfBounding = canvasPdf.getBoundingClientRect();
|
||||||
|
|
||||||
|
var renderContext = {
|
||||||
|
canvasContext: contextPdf,
|
||||||
|
viewport: viewport
|
||||||
|
};
|
||||||
|
|
||||||
|
page.render(renderContext);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
function updateAudience() { socket.emit('change-slide', filename, currentPage); }
|
||||||
|
|
||||||
|
function formatTime(ms) {
|
||||||
|
var sec = padToTwo(Math.floor((ms / 1000) % 60));
|
||||||
|
var min = padToTwo(Math.floor(((ms / 1000 - sec) % 3600) / 60));
|
||||||
|
|
||||||
|
return min + ':' + sec;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateTimers() {
|
||||||
|
if (refreshIntervalId !== 0) {
|
||||||
|
$('#totalTime').html(formatTime(Date.now() - startTime));
|
||||||
|
$('#slideTime').html(formatTime(Date.now() - startSlide));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function padToTwo(number) {
|
||||||
|
if (number<=99) { number = ("0"+number).slice(-2); }
|
||||||
|
return number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetTimeSlide() {
|
||||||
|
startSlide = Date.now();
|
||||||
|
updateTimers();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Check if number is valid, and updates
|
||||||
|
// number undefined means sync all clients
|
||||||
|
function changeSlide(number) {
|
||||||
|
|
||||||
|
if (isNaN(number) || number < 1 || number > pdf.pdfInfo.numPages) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentPage === undefined || currentPage === number) {
|
||||||
|
updateAudience();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentPage = number;
|
||||||
|
|
||||||
|
updateUI();
|
||||||
|
updateAudience();
|
||||||
|
resetTimeSlide();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function canvasPointer(id,x,y) {
|
||||||
|
|
||||||
|
// Clear canvas
|
||||||
|
canvasPaint.width = canvasPaint.width;
|
||||||
|
|
||||||
|
if (id != undefined) {
|
||||||
|
|
||||||
|
if (x != undefined && y != undefined) {
|
||||||
|
|
||||||
|
lasers[id] = {x:x, y:y};
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
delete lasers[id];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var laserIndex in lasers) {
|
||||||
|
|
||||||
|
var laser = lasers[laserIndex];
|
||||||
|
|
||||||
|
if (drawAudienceLasers === true || laserIndex === socket.id) {
|
||||||
|
|
||||||
|
contextPaint.fillStyle = laserIndex === socket.id ? 'red' : 'green';
|
||||||
|
contextPaint.beginPath();
|
||||||
|
contextPaint.arc(laser.x * canvasPdf.width, laser.y * canvasPdf.height, 10, 0, Math.PI*2, true);
|
||||||
|
contextPaint.closePath();
|
||||||
|
contextPaint.fill();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
sio.onMouseDown = function(event) {
|
||||||
|
|
||||||
|
var x = (event.x - canvasPdfBounding.left) / canvasPdf.width;
|
||||||
|
var y = (event.y - canvasPdfBounding.top) / canvasPdf.height;
|
||||||
|
|
||||||
|
socket.emit('pointer', filename, socket.id, x, y);
|
||||||
|
|
||||||
|
pointer = true;
|
||||||
|
shouldUpdatePointer = true;
|
||||||
|
|
||||||
|
canvasPointer(socket.id, x, y);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
sio.onMouseMove = function(event) {
|
||||||
|
|
||||||
|
if (pointer) {
|
||||||
|
|
||||||
|
var x = (event.x - canvasPdfBounding.left) / canvasPdf.width;
|
||||||
|
var y = (event.y - canvasPdfBounding.top) / canvasPdf.height;
|
||||||
|
|
||||||
|
if (shouldUpdatePointer) {
|
||||||
|
// Update
|
||||||
|
socket.emit('pointer', filename, socket.id, x, y);
|
||||||
|
|
||||||
|
shouldUpdatePointer = false;
|
||||||
|
|
||||||
|
setTimeout(function() {
|
||||||
|
|
||||||
|
shouldUpdatePointer = true;
|
||||||
|
|
||||||
|
}, 20);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
canvasPointer(socket.id, x, y);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
sio.onMouseUp = function(event) {
|
||||||
|
|
||||||
|
socket.emit('pointer', filename, socket.id);
|
||||||
|
|
||||||
|
pointer = false;
|
||||||
|
shouldUpdatePointer = false;
|
||||||
|
|
||||||
|
canvasPointer(socket.id);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
sio.startPresentation = function() {
|
||||||
|
|
||||||
|
startTime = startSlide = Date.now();
|
||||||
|
|
||||||
|
refreshIntervalId = setInterval(updateTimers, 1000);
|
||||||
|
updateTimers();
|
||||||
|
|
||||||
|
$('#start').prop('disabled', true);
|
||||||
|
$('#stop').prop('disabled', false);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
sio.stopPresentation = function() {
|
||||||
|
|
||||||
|
clearInterval(refreshIntervalId);
|
||||||
|
refreshIntervalId = 0;
|
||||||
|
|
||||||
|
$('#start').prop('disabled', false);
|
||||||
|
$('#stop').prop('disabled', true);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
sio.changeSlideFromCounter = function() {
|
||||||
|
|
||||||
|
var slideNumber = filterInt($('#counter').val());
|
||||||
|
|
||||||
|
changeSlide(slideNumber);
|
||||||
|
|
||||||
|
// lose focus
|
||||||
|
$(':focus').blur();
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
sio.switchAudienceLaser = function() {
|
||||||
|
|
||||||
|
var laser = $('#viewer-laser');
|
||||||
|
|
||||||
|
if (drawAudienceLasers === false) {
|
||||||
|
|
||||||
|
// Laser is disabled, will be enabled
|
||||||
|
laser.text('Audience laser is enabled');
|
||||||
|
laser.removeClass('btn-default').addClass('btn-primary');
|
||||||
|
drawAudienceLasers = true;
|
||||||
|
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
laser.text('Audience laser is disabled');
|
||||||
|
laser.removeClass('btn-primary').addClass('btn-default');
|
||||||
|
drawAudienceLasers = false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.emit('viewer-laser', filename, drawAudienceLasers);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
sio.clearCounter = function() { $('#counter').val(''); };
|
||||||
|
sio.nextSlide = function() { changeSlide(currentPage + 1); };
|
||||||
|
sio.previousSlide = function() { changeSlide(currentPage - 1); };
|
||||||
|
sio.firstSlide = function() { changeSlide(1); };
|
||||||
|
sio.lastSlide = function() { changeSlide(pdf.pdfInfo.numPages); };
|
||||||
|
|
||||||
|
sio.syncAudience = updateAudience;
|
||||||
|
sio.update = updateUI;
|
||||||
|
|
||||||
|
$(window).resize(updateUI);
|
||||||
|
|
||||||
|
return sio;
|
||||||
|
|
||||||
|
})(); });
|
|
@ -0,0 +1,175 @@
|
||||||
|
$(function() { sio = (function() {
|
||||||
|
|
||||||
|
var sio = {};
|
||||||
|
|
||||||
|
var canvasPdf = $('#canvas-pdf').get(0);
|
||||||
|
var contextPdf = canvasPdf.getContext('2d');
|
||||||
|
|
||||||
|
var canvasPaint = $('#canvas-paint').get(0);
|
||||||
|
var contextPaint = canvasPaint.getContext('2d');
|
||||||
|
|
||||||
|
var pdf;
|
||||||
|
var pageNumber = 1;
|
||||||
|
|
||||||
|
var pointer = false;
|
||||||
|
var canvasPdfBounding;
|
||||||
|
|
||||||
|
var drawAudienceLaser = false;
|
||||||
|
var lasers = {};
|
||||||
|
|
||||||
|
|
||||||
|
PDFJS.getDocument('/static/uploaded/' + filename + '.pdf').then(function getPdfHelloWorld(_pdf) {
|
||||||
|
|
||||||
|
pdf = _pdf;
|
||||||
|
update();
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
var socket = io();
|
||||||
|
|
||||||
|
socket.on('welcome', function() {
|
||||||
|
socket.emit('viewer', filename);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('update', function(newPageNumber) {
|
||||||
|
pageNumber = newPageNumber;
|
||||||
|
update();
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('viewer-laser', function(enabled) {
|
||||||
|
drawAudienceLaser = enabled;
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('pointer', canvasPointer);
|
||||||
|
|
||||||
|
function update() {
|
||||||
|
|
||||||
|
pdf.getPage(pageNumber).then(function(page) {
|
||||||
|
|
||||||
|
var scale;
|
||||||
|
|
||||||
|
// width / height
|
||||||
|
var ratio = page.pageInfo.view[2] / page.pageInfo.view[3];
|
||||||
|
|
||||||
|
if (window.innerWidth < window.innerHeight) {
|
||||||
|
|
||||||
|
canvasPdf.width = window.innerWidth;
|
||||||
|
canvasPdf.height = canvasPdf.width / ratio;
|
||||||
|
scale = canvasPdf.width / page.getViewport(1.0).width;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
canvasPdf.height = window.innerHeight;
|
||||||
|
canvasPdf.width = ratio * canvasPdf.height;
|
||||||
|
scale = canvasPdf.width / page.getViewport(1.0).width;
|
||||||
|
}
|
||||||
|
|
||||||
|
canvasPaint.width = canvasPdf.width;
|
||||||
|
canvasPaint.height = canvasPdf.height;
|
||||||
|
|
||||||
|
$('#canvases').width(canvasPdf.width + 'px');
|
||||||
|
$('#canvases').height(canvasPdf.height + 'px');
|
||||||
|
|
||||||
|
canvasPdfBounding = canvasPdf.getBoundingClientRect();
|
||||||
|
|
||||||
|
var viewport = page.getViewport(scale);
|
||||||
|
|
||||||
|
var renderContext = {
|
||||||
|
canvasContext: contextPdf,
|
||||||
|
viewport: viewport
|
||||||
|
};
|
||||||
|
|
||||||
|
page.render(renderContext);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function canvasPointer(id,x,y) {
|
||||||
|
|
||||||
|
// Clear canvas
|
||||||
|
canvasPaint.width = canvasPaint.width;
|
||||||
|
|
||||||
|
if (id != undefined) {
|
||||||
|
if ( x != undefined && y != undefined) {
|
||||||
|
lasers[id] = {x:x, y:y};
|
||||||
|
} else {
|
||||||
|
delete lasers[id];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var laserIndex in lasers) {
|
||||||
|
|
||||||
|
var laser = lasers[laserIndex];
|
||||||
|
|
||||||
|
if (drawAudienceLaser === true || laserIndex === 'speaker') {
|
||||||
|
|
||||||
|
contextPaint.fillStyle = laserIndex === 'speaker' ? 'red' : 'green';
|
||||||
|
contextPaint.beginPath();
|
||||||
|
contextPaint.arc(laser.x * canvasPdf.width, laser.y * canvasPdf.height, 10, 0, Math.PI*2, true);
|
||||||
|
contextPaint.closePath();
|
||||||
|
contextPaint.fill();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
$(window).resize(update);
|
||||||
|
|
||||||
|
sio.onMouseDown = function() {
|
||||||
|
|
||||||
|
var x = (event.x - canvasPdfBounding.left) / canvasPdf.width;
|
||||||
|
var y = (event.y - canvasPdfBounding.top) / canvasPdf.height;
|
||||||
|
|
||||||
|
socket.emit('pointer', filename, socket.id, x, y);
|
||||||
|
|
||||||
|
pointer = true;
|
||||||
|
shouldUpdatePointer = true;
|
||||||
|
|
||||||
|
canvasPointer(socket.id, x, y);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
sio.onMouseMove = function(event) {
|
||||||
|
|
||||||
|
if (pointer) {
|
||||||
|
|
||||||
|
var x = (event.x - canvasPdfBounding.left) / canvasPdf.width;
|
||||||
|
var y = (event.y - canvasPdfBounding.top) / canvasPdf.height;
|
||||||
|
|
||||||
|
if (shouldUpdatePointer) {
|
||||||
|
// Update
|
||||||
|
socket.emit('pointer', filename, socket.id, x, y);
|
||||||
|
|
||||||
|
shouldUpdatePointer = false;
|
||||||
|
|
||||||
|
setTimeout(function() {
|
||||||
|
|
||||||
|
shouldUpdatePointer = true;
|
||||||
|
|
||||||
|
}, 20);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
canvasPointer(socket.id, x, y);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
sio.onMouseUp = function(event) {
|
||||||
|
|
||||||
|
socket.emit('pointer', filename, socket.id);
|
||||||
|
|
||||||
|
pointer = false;
|
||||||
|
shouldUpdatePointer = false;
|
||||||
|
|
||||||
|
canvasPointer(socket.id);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
return sio;
|
||||||
|
|
||||||
|
})(); });
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,14 @@
|
||||||
|
doctype
|
||||||
|
html( lang="en" )
|
||||||
|
head
|
||||||
|
title slideshow.io
|
||||||
|
meta( charset='utf-8' )
|
||||||
|
meta( http-equiv='X-UA-Compatible', content='IE=edge' )
|
||||||
|
block css
|
||||||
|
|
||||||
|
body
|
||||||
|
block base_content
|
||||||
|
|
||||||
|
script( src='http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js' )
|
||||||
|
|
||||||
|
block js
|
|
@ -0,0 +1,28 @@
|
||||||
|
extends ./base.pug
|
||||||
|
|
||||||
|
block css
|
||||||
|
meta( name='viewport', content='width=device-width, initial-scale=1.0' )
|
||||||
|
meta( name='description', content='Baking Bootstrap Snippets with Jade' )
|
||||||
|
//- Bootswatch Theme
|
||||||
|
link(href="https://bootswatch.com/cerulean/bootstrap.min.css", rel="stylesheet")
|
||||||
|
|
||||||
|
block base_content
|
||||||
|
nav.navbar.navbar-default( role="navigation" )
|
||||||
|
.container
|
||||||
|
.navbar-header
|
||||||
|
button.navbar-toggle.collapsed( type="button", data-toggle="collapse", data-target="#navbar-inverse", aria-expanded="false", aria-controls="navbar")
|
||||||
|
span.sr-only Toggle navigation
|
||||||
|
span.icon-bar
|
||||||
|
span.icon-bar
|
||||||
|
span.icon-bar
|
||||||
|
a.navbar-brand(href="#") slideshow.io
|
||||||
|
|
||||||
|
#navbar-inverse.collapse.navbar-collapse
|
||||||
|
ul.nav.navbar-nav
|
||||||
|
li: a( href="#about" ) About
|
||||||
|
|
||||||
|
.container
|
||||||
|
block content
|
||||||
|
|
||||||
|
block prepend js
|
||||||
|
script( src='http://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/js/bootstrap.min.js' )
|
Loading…
Reference in New Issue