Initial commit

This commit is contained in:
Thomas FORGIONE 2016-09-09 18:20:59 +02:00
commit 3490d8aab7
28 changed files with 53483 additions and 0 deletions

21
LICENSE.md Normal file
View File

@ -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.

18
README.md Normal file
View File

@ -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.

61
lib/get.js Normal file
View File

@ -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);
});
};

145
lib/log.js Normal file
View File

@ -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;

34
lib/socket.js Normal file
View File

@ -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);
});
});
}

16
package.json Normal file
View File

@ -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"
}
}

6
routes/index/index.js Normal file
View File

@ -0,0 +1,6 @@
module.exports.index = function(req, res, render, next) {
res.setHeader('Content-Type', 'text/html');
render('index.pug', res.locals)
}

3
routes/index/urls.js Normal file
View File

@ -0,0 +1,3 @@
module.exports = {
'/' : 'index'
}

View File

@ -0,0 +1,5 @@
extends ../../../views/main.pug
block content
p.
Hello world !

25
routes/speaker/index.js Normal file
View File

@ -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);
}
});
}

3
routes/speaker/urls.js Normal file
View File

@ -0,0 +1,3 @@
module.exports = {
'/speaker/:file' : 'index'
}

View File

@ -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")

25
routes/viewer/index.js Normal file
View File

@ -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);
}
});
}

3
routes/viewer/urls.js Normal file
View File

@ -0,0 +1,3 @@
module.exports = {
'/viewer/:file' : 'index'
}

View File

@ -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")

72
server.js Normal file
View File

@ -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);
});

34
static/css/signin.css Normal file
View File

@ -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;
}

8
static/css/speaker.css Normal file
View File

@ -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";
}

9
static/css/style.css Normal file
View File

@ -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;
}

10
static/css/viewer.css Normal file
View File

@ -0,0 +1,10 @@
#canvas {
margin:auto;
position:absolute;
top:0;
bottom:0;
left:0;
right:0;
max-height:100%;
max-width:100%;
}

10375
static/js/pdf.js Normal file

File diff suppressed because it is too large Load Diff

42034
static/js/pdf.worker.js vendored Normal file

File diff suppressed because it is too large Load Diff

291
static/js/speaker.js Normal file
View File

@ -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;
})(); });

175
static/js/viewer.js Normal file
View File

@ -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.

BIN
static/uploaded/main.pdf Normal file

Binary file not shown.

14
views/base.pug Normal file
View File

@ -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

28
views/main.pug Normal file
View File

@ -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' )