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