Browse Source

Merge pull request #78 from fancycode/webodf_presentations

Added support for ODF documents.
pull/80/head
Simon Eisenmann 11 years ago
parent
commit
463dc7e0cd
  1. 1
      Makefile.am
  2. 16
      src/styles/components/_presentation.scss
  3. 4
      static/js/directives/directives.js
  4. 274
      static/js/directives/odfcanvas.js
  5. 134
      static/js/directives/pdfcanvas.js
  6. 206
      static/js/directives/presentation.js
  7. 641
      static/js/libs/webodf.js
  8. 13
      static/js/main.js
  9. 16
      static/partials/presentation.html

1
Makefile.am

@ -144,6 +144,7 @@ install:
$(INSTALL) -D static/js/libs/require/require.js $(SHARE)/www/static/js/libs/require/require.js $(INSTALL) -D static/js/libs/require/require.js $(SHARE)/www/static/js/libs/require/require.js
$(INSTALL) $(OUTPUT_JS)/*.js $(SHARE)/www/static/js $(INSTALL) $(OUTPUT_JS)/*.js $(SHARE)/www/static/js
$(INSTALL) $(OUTPUT_JS)/libs/pdf/*.js $(SHARE)/www/static/js/libs/pdf $(INSTALL) $(OUTPUT_JS)/libs/pdf/*.js $(SHARE)/www/static/js/libs/pdf
$(INSTALL) -D static/js/libs/webodf.js $(SHARE)/www/static/js/libs/webodf.js
clean: clean:
GOPATH=$(GOPATH) $(GO) clean -i $(PKG) GOPATH=$(GOPATH) $(GO) clean -i $(PKG)

16
src/styles/components/_presentation.scss

@ -78,6 +78,22 @@
display: block; display: block;
margin: 0 auto; margin: 0 auto;
} }
.odfcanvas {
user-select: none;
cursor: default;
body {
background-color: transparent;
}
}
.odfcontainer {
display: none;
padding: 0;
margin: 0;
}
.odfcontainer.showonepage {
overflow: hidden;
text-align: center;
}
} }
.presentation .overlaybar { .presentation .overlaybar {

4
static/js/directives/directives.js

@ -39,7 +39,8 @@ define([
'directives/contactrequest', 'directives/contactrequest',
'directives/defaultdialog', 'directives/defaultdialog',
'directives/pdfcanvas', 'directives/pdfcanvas',
'directives/presentation'], function(_, onEnter, onEscape, statusMessage, buddyList, buddyPicture, settings, chat, audioVideo, usability, audioLevel, fileInfo, screenshare, roomBar, socialShare, page, contactRequest, defaultDialog, pdfcanvas, presentation) { 'directives/odfcanvas',
'directives/presentation'], function(_, onEnter, onEscape, statusMessage, buddyList, buddyPicture, settings, chat, audioVideo, usability, audioLevel, fileInfo, screenshare, roomBar, socialShare, page, contactRequest, defaultDialog, pdfcanvas, odfcanvas, presentation) {
var directives = { var directives = {
onEnter: onEnter, onEnter: onEnter,
@ -60,6 +61,7 @@ define([
contactRequest: contactRequest, contactRequest: contactRequest,
defaultDialog: defaultDialog, defaultDialog: defaultDialog,
pdfcanvas: pdfcanvas, pdfcanvas: pdfcanvas,
odfcanvas: odfcanvas,
presentation: presentation presentation: presentation
}; };

274
static/js/directives/odfcanvas.js

@ -0,0 +1,274 @@
/*
* Spreed WebRTC.
* Copyright (C) 2013-2014 struktur AG
*
* This file is part of Spreed WebRTC.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
define(['require', 'underscore', 'jquery'], function(require, _, $) {
return ["$window", "$compile", "translation", "safeApply", function($window, $compile, translation, safeApply) {
var webodf = null;
var DOCUMENT_TYPE_PRESENTATION = "presentation";
var DOCUMENT_TYPE_SPREADSHEET = "spreadsheet";
var DOCUMENT_TYPE_TEXT = "text";
var nsResolver = function(prefix) {
var ns = {
'draw': "urn:oasis:names:tc:opendocument:xmlns:drawing:1.0",
'presentation': "urn:oasis:names:tc:opendocument:xmlns:presentation:1.0",
'text': "urn:oasis:names:tc:opendocument:xmlns:text:1.0",
'office': "urn:oasis:names:tc:opendocument:xmlns:office:1.0"
};
return ns[prefix] || console.log('prefix [' + prefix + '] unknown.');
}
var ODFCanvas_read = function(path, offset, length, callback) {
if (typeof path === "string") {
webodf.runtime.orig_read.call(webodf.runtime, path, offset, length, callback);
return;
}
var fp = path.file || path;
if (typeof URL !== "undefined" && URL.createObjectURL) {
var url = URL.createObjectURL(fp);
webodf.runtime.orig_read.call(webodf.runtime, url, offset, length, callback);
return;
}
console.error("TODO(fancycode): implement read for", path);
};
var ODFCanvas_getFileSize = function(path, callback) {
if (typeof path === "string") {
webodf.runtime.orig_getFileSize.call(webodf.runtime, path, callback);
return;
}
var size = path.size || path.info.size;
if (typeof size !== "undefined") {
callback(size);
return;
}
console.error("TODO(fancycode): implement getFileSize for", path);
callback(-1);
};
var controller = ['$scope', '$element', '$attrs', function($scope, $element, $attrs) {
var ODFCanvas = function(scope, container, canvasDom) {
this.scope = scope;
this.container = container;
this.canvasDom = canvasDom;
this.canvas = null;
this.maxPageNumber = -1;
this.currentPageNumber = -1;
this.pendingPageNumber = null;
};
ODFCanvas.prototype._close = function() {
if (this.canvas) {
this.canvas.destroy(function() {
// ignore callback
});
this.canvas = null;
}
this.maxPageNumber = -1;
this.currentPageNumber = -1;
this.pendingPageNumber = null;
};
ODFCanvas.prototype.close = function() {
this._close();
};
ODFCanvas.prototype.open = function(presentation) {
this.scope.$emit("presentationOpening", presentation);
presentation.open(_.bind(function(source) {
console.log("Loading ODF from", source);
this._openFile(source);
}, this));
};
ODFCanvas.prototype._odfLoaded = function() {
this.scope.$apply(_.bind(function(scope) {
var odfcontainer = this.canvas.odfContainer();
this.document_type = odfcontainer.getDocumentType();
// pages only supported for presentations
var pages = [];
switch (this.document_type) {
case DOCUMENT_TYPE_PRESENTATION:
pages = odfcontainer.rootElement.getElementsByTagNameNS(nsResolver('draw'), 'page');
this.container.addClass("showonepage");
break;
default:
this.container.removeClass("showonepage");
break;
}
this.maxPageNumber = Math.max(1, pages.length);
this.currentPageNumber = -1;
console.log("ODF loaded", odfcontainer);
var odfDoc = {
numPages: this.maxPageNumber
};
scope.$emit("presentationLoaded", odfcontainer.getUrl(), odfDoc);
if (this.pendingPageNumber !== null) {
this._showPage(this.pendingPageNumber);
this.pendingPageNumber = null;
}
}, this));
};
ODFCanvas.prototype._doOpenFile = function(source) {
this.scope.$emit("presentationLoading", source);
this.container.hide();
if (!this.canvas) {
this.canvas = new webodf.odf.OdfCanvas(this.canvasDom[0]);
this.canvas.addListener("statereadychange", _.bind(function() {
this._odfLoaded();
}, this));
}
this.canvas.setZoomLevel(1);
this.canvas.load(source);
};
ODFCanvas.prototype._openFile = function(source) {
if (webodf === null) {
// load webodf.js lazily
require(['webodf'], _.bind(function(webodf_) {
console.log("Using webodf.js " + webodf_.webodf.Version);
webodf = webodf_;
// monkey-patch IO functions
webodf.runtime.orig_read = webodf.runtime.read;
webodf.runtime.read = ODFCanvas_read;
webodf.runtime.orig_getFileSize = webodf.runtime.getFileSize;
webodf.runtime.getFileSize = ODFCanvas_getFileSize;
this.scope.$apply(_.bind(function(scope) {
this._doOpenFile(source);
}, this));
}, this));
} else {
this._doOpenFile(source);
}
};
ODFCanvas.prototype._showPage = function(page) {
if (page === this.currentPageNumber) {
return;
}
console.log("Showing page", page, "/", this.maxPageNumber);
this.scope.$emit("presentationPageLoading", page);
this.scope.$emit("presentationPageLoaded", page, null);
// actual rendering of first page must happen after the
// previously hidden DOM elements are visible again to
// make initial scaling work, so defer it
_.defer(_.bind(function() {
this.scope.$apply(_.bind(function() {
if (this.currentPageNumber === -1) {
this.container.show();
this.redrawPage();
}
this.currentPageNumber = page;
this.canvas.showPage(page);
this.scope.$emit("presentationPageRendering", page);
this.scope.$emit("presentationPageRendered", page, this.maxPageNumber);
}, this));
}, this));
};
ODFCanvas.prototype.redrawPage = function() {
if (this.canvas) {
switch (this.document_type) {
case DOCUMENT_TYPE_PRESENTATION:
this.canvas.fitToContainingElement(this.container.width(), this.container.height());
break;
default:
this.canvas.fitToWidth(this.container.width());
break;
}
}
};
ODFCanvas.prototype.showPage = function(page) {
if (page >= 1 && page <= this.maxPageNumber) {
if (!this.canvas) {
this.pendingPageNumber = page;
} else {
this._showPage(page);
}
}
};
var container = $($element);
var canvas = $($element).find(".odfcanvas");
var odfCanvas = new ODFCanvas($scope, container, canvas);
$scope.$watch("currentPresentation", function(presentation, previousPresentation) {
if (presentation) {
safeApply($scope, function(scope) {
odfCanvas.open(presentation);
});
} else {
if (previousPresentation) {
previousPresentation.close();
}
odfCanvas.close();
}
});
$scope.$on("$destroy", function() {
odfCanvas.close();
odfCanvas = null;
});
$scope.$watch("currentPageNumber", function(page, oldValue) {
if (page === oldValue) {
// no change
return;
}
odfCanvas.showPage(page);
});
$($window).on("resize", function() {
$scope.$apply(function(scope) {
odfCanvas.redrawPage();
});
});
}];
return {
restrict: 'E',
replace: true,
template: '<div class="canvasContainer odfcontainer"><div class="odfcanvas"></div></div>',
controller: controller
};
}];
});

134
static/js/directives/pdfcanvas.js

@ -20,7 +20,7 @@
*/ */
define(['require', 'underscore', 'jquery'], function(require, _, $) { define(['require', 'underscore', 'jquery'], function(require, _, $) {
return ["$compile", "translation", function($compile, translation) { return ["$window", "$compile", "translation", "safeApply", function($window, $compile, translation, safeApply) {
var pdfjs = null; var pdfjs = null;
@ -59,31 +59,33 @@ define(['require', 'underscore', 'jquery'], function(require, _, $) {
PDFCanvas.prototype.close = function() { PDFCanvas.prototype.close = function() {
this._close(); this._close();
this.scope.$emit("pdfClosed");
}; };
PDFCanvas.prototype.open = function(file) { PDFCanvas.prototype.open = function(presentation) {
console.log("Loading PDF from", file); this.scope.$emit("presentationOpening", presentation);
this._close(); presentation.open(_.bind(function(source) {
if (typeof file === "string") { console.log("Loading PDF from", source);
// got a url this._close();
this._openFile(file); if (typeof source === "string") {
return; // got a url
} this._openFile(source);
return;
}
var fp = file.file || file; var fp = source.file || source;
if (typeof URL !== "undefined" && URL.createObjectURL) { if (typeof URL !== "undefined" && URL.createObjectURL) {
var url = URL.createObjectURL(fp); var url = URL.createObjectURL(fp);
this._openFile(url); this._openFile(url);
} else { } else {
var fileReader = new FileReader(); var fileReader = new FileReader();
fileReader.onload = _.bind(function(evt) { fileReader.onload = _.bind(function(evt) {
var buffer = evt.target.result; var buffer = evt.target.result;
var uint8Array = new Uint8Array(buffer); var uint8Array = new Uint8Array(buffer);
this._openFile(uint8Array); this._openFile(uint8Array);
}, this); }, this);
fileReader.readAsArrayBuffer(fp); fileReader.readAsArrayBuffer(fp);
} }
}, this));
}; };
PDFCanvas.prototype._pdfLoaded = function(source, doc) { PDFCanvas.prototype._pdfLoaded = function(source, doc) {
@ -92,7 +94,7 @@ define(['require', 'underscore', 'jquery'], function(require, _, $) {
this.maxPageNumber = doc.numPages; this.maxPageNumber = doc.numPages;
this.currentPageNumber = -1; this.currentPageNumber = -1;
console.log("PDF loaded", doc); console.log("PDF loaded", doc);
scope.$emit("pdfLoaded", source, doc); scope.$emit("presentationLoaded", source, doc);
}, this)); }, this));
}; };
@ -114,11 +116,12 @@ define(['require', 'underscore', 'jquery'], function(require, _, $) {
break; break;
} }
this.scope.$apply(_.bind(function(scope) { this.scope.$apply(_.bind(function(scope) {
scope.$emit("pdfLoadError", source, loadErrorMessage); scope.$emit("presentationLoadError", source, loadErrorMessage);
}, this)); }, this));
}; };
PDFCanvas.prototype._doOpenFile = function(source) { PDFCanvas.prototype._doOpenFile = function(source) {
this.scope.$emit("presentationLoading", source);
pdfjs.getDocument(source).then(_.bind(function(doc) { pdfjs.getDocument(source).then(_.bind(function(doc) {
this._pdfLoaded(source, doc); this._pdfLoaded(source, doc);
}, this), _.bind(function(error, exception) { }, this), _.bind(function(error, exception) {
@ -127,7 +130,6 @@ define(['require', 'underscore', 'jquery'], function(require, _, $) {
}; };
PDFCanvas.prototype._openFile = function(source) { PDFCanvas.prototype._openFile = function(source) {
this.scope.$emit("pdfLoading", source);
if (pdfjs === null) { if (pdfjs === null) {
// load pdf.js lazily // load pdf.js lazily
require(['pdf'], _.bind(function(pdf) { require(['pdf'], _.bind(function(pdf) {
@ -145,10 +147,12 @@ define(['require', 'underscore', 'jquery'], function(require, _, $) {
}; };
PDFCanvas.prototype._pageLoaded = function(page, pageObject) { PDFCanvas.prototype._pageLoaded = function(page, pageObject) {
console.log("Got page", pageObject); this.scope.$apply(_.bind(function(scope) {
this.scope.$emit("pdfPageLoaded", page, pageObject); console.log("Got page", pageObject);
this.currentPage = pageObject; scope.$emit("presentationPageLoaded", page, pageObject);
this.drawPage(pageObject); this.currentPage = pageObject;
this.drawPage(pageObject);
}, this));
}; };
PDFCanvas.prototype._pageLoadError = function(page, error, exception) { PDFCanvas.prototype._pageLoadError = function(page, error, exception) {
@ -159,7 +163,9 @@ define(['require', 'underscore', 'jquery'], function(require, _, $) {
} else { } else {
loadErrorMessage = translation._("An unknown error occurred while loading the PDF page."); loadErrorMessage = translation._("An unknown error occurred while loading the PDF page.");
} }
this.scope.$emit("pdfPageLoadError", page, loadErrorMessage); this.scope.$apply(_.bind(function(scope) {
scope.$emit("presentationPageLoadError", page, loadErrorMessage);
}, this));
}; };
PDFCanvas.prototype._showPage = function(page) { PDFCanvas.prototype._showPage = function(page) {
@ -173,7 +179,7 @@ define(['require', 'underscore', 'jquery'], function(require, _, $) {
this.currentPage = null; this.currentPage = null;
} }
this.currentPageNumber = page; this.currentPageNumber = page;
this.scope.$emit("pdfPageLoading", page); this.scope.$emit("presentationPageLoading", page);
this.doc.getPage(page).then(_.bind(function(pageObject) { this.doc.getPage(page).then(_.bind(function(pageObject) {
this._pageLoaded(page, pageObject); this._pageLoaded(page, pageObject);
}, this), _.bind(function(error, exception) { }, this), _.bind(function(error, exception) {
@ -185,7 +191,7 @@ define(['require', 'underscore', 'jquery'], function(require, _, $) {
this.renderTask = null; this.renderTask = null;
this.scope.$apply(_.bind(function(scope) { this.scope.$apply(_.bind(function(scope) {
console.log("Rendered page", pageObject.pageNumber); console.log("Rendered page", pageObject.pageNumber);
this.scope.$emit("pdfPageRendered", pageObject.pageNumber, this.maxPageNumber); this.scope.$emit("presentationPageRendered", pageObject.pageNumber, this.maxPageNumber);
// ...and flip the buffers... // ...and flip the buffers...
scope.canvasIndex = 1 - scope.canvasIndex; scope.canvasIndex = 1 - scope.canvasIndex;
this.showQueuedPage(); this.showQueuedPage();
@ -205,7 +211,7 @@ define(['require', 'underscore', 'jquery'], function(require, _, $) {
loadErrorMessage = translation._("An unknown error occurred while rendering the PDF page."); loadErrorMessage = translation._("An unknown error occurred while rendering the PDF page.");
} }
this.scope.$apply(_.bind(function(scope) { this.scope.$apply(_.bind(function(scope) {
this.scope.$emit("pdfPageRenderError", pageObject.pageNumber, this.maxPageNumber, loadErrorMessage); scope.$emit("presentationPageRenderError", pageObject.pageNumber, this.maxPageNumber, loadErrorMessage);
}, this)); }, this));
}; };
@ -241,7 +247,7 @@ define(['require', 'underscore', 'jquery'], function(require, _, $) {
}; };
console.log("Rendering page", pageObject); console.log("Rendering page", pageObject);
this.scope.$emit("pdfPageRendering", pageObject.pageNumber); this.scope.$emit("presentationPageRendering", pageObject.pageNumber);
// TODO(fancycode): also render images in different resolutions for subscribed peers and send to them when ready // TODO(fancycode): also render images in different resolutions for subscribed peers and send to them when ready
this._stopRendering(); this._stopRendering();
@ -270,14 +276,6 @@ define(['require', 'underscore', 'jquery'], function(require, _, $) {
} }
}; };
PDFCanvas.prototype.prevPage = function() {
this.showPage(this.currentPageNumber - 1);
};
PDFCanvas.prototype.nextPage = function() {
this.showPage(this.currentPageNumber + 1);
};
PDFCanvas.prototype.showQueuedPage = function() { PDFCanvas.prototype.showQueuedPage = function() {
if (this.pendingPageNumber !== null) { if (this.pendingPageNumber !== null) {
this._showPage(this.pendingPageNumber); this._showPage(this.pendingPageNumber);
@ -290,12 +288,17 @@ define(['require', 'underscore', 'jquery'], function(require, _, $) {
var canvases = container.find("canvas"); var canvases = container.find("canvas");
var pdfCanvas = new PDFCanvas($scope, canvases); var pdfCanvas = new PDFCanvas($scope, canvases);
$scope.$on("openPdf", function(event, source) { $scope.$watch("currentPresentation", function(presentation, previousPresentation) {
pdfCanvas.open(source); if (presentation) {
}); safeApply($scope, function(scope) {
pdfCanvas.open(presentation);
$scope.$on("closePdf", function() { });
pdfCanvas.close(); } else {
if (previousPresentation) {
previousPresentation.close();
}
pdfCanvas.close();
}
}); });
$scope.$on("$destroy", function() { $scope.$on("$destroy", function() {
@ -303,34 +306,21 @@ define(['require', 'underscore', 'jquery'], function(require, _, $) {
pdfCanvas = null; pdfCanvas = null;
}); });
$scope.$on("showPdfPage", function(event, page) { $scope.$watch("currentPageNumber", function(page, oldValue) {
pdfCanvas.showPage(page); if (page === oldValue) {
}); // no change
return;
$scope.$on("showQueuedPdfPage", function() { }
pdfCanvas.showQueuedPage();
});
$scope.$on("redrawPdf", function() {
pdfCanvas.redrawPage();
});
$scope.$on("prevPage", function() { pdfCanvas.showPage(page);
pdfCanvas.prevPage();
}); });
$scope.$on("nextPage", function() { $($window).on("resize", function() {
pdfCanvas.nextPage(); $scope.$apply(function(scope) {
pdfCanvas.redrawPage();
});
}); });
$scope.prevPage = function() {
$scope.$emit("prevPage");
};
$scope.nextPage = function() {
$scope.$emit("nextPage");
};
}]; }];
return { return {

206
static/js/directives/presentation.js

@ -22,23 +22,64 @@ define(['jquery', 'underscore', 'text!partials/presentation.html', 'bigscreen'],
return ["$window", "mediaStream", "fileUpload", "fileDownload", "alertify", "translation", "randomGen", "fileData", function($window, mediaStream, fileUpload, fileDownload, alertify, translation, randomGen, fileData) { return ["$window", "mediaStream", "fileUpload", "fileDownload", "alertify", "translation", "randomGen", "fileData", function($window, mediaStream, fileUpload, fileDownload, alertify, translation, randomGen, fileData) {
var DownloadPresentation = function(scope, fileInfo, token, owner) { var SUPPORTED_TYPES = {
// rendered by pdfcanvas directive
"application/pdf": "pdf",
// rendered by odfcanvas directive
// TODO(fancycode): check which formats really work, allow all odf for now
"application/vnd.oasis.opendocument.text": "odf",
"application/vnd.oasis.opendocument.spreadsheet": "odf",
"application/vnd.oasis.opendocument.presentation": "odf",
"application/vnd.oasis.opendocument.graphics": "odf",
"application/vnd.oasis.opendocument.chart": "odf",
"application/vnd.oasis.opendocument.formula": "odf",
"application/vnd.oasis.opendocument.image": "odf",
"application/vnd.oasis.opendocument.text-master": "odf"
};
var BasePresentation = function(scope, fileInfo, token) {
this.e = $({}); this.e = $({});
if (scope) {
this.scope = scope.$new();
this.scope.info = fileInfo;
}
this.info = fileInfo; this.info = fileInfo;
if (fileInfo) {
this.sortkey = (fileInfo.name || "").toLowerCase();
this.type = SUPPORTED_TYPES[fileInfo.type] || "unknown";
}
this.token = token; this.token = token;
this.owner = owner; this.fileid = null;
this.scope = scope.$new(); this.file = null;
this.scope.info = fileInfo;
this.progress = 0;
this.handler = null; this.handler = null;
this.session = null; this.session = null;
this.fileid = null; };
BasePresentation.prototype.stop = function() {
if (this.handler) {
mediaStream.tokens.off(this.token, this.handler);
this.handler = null;
}
};
BasePresentation.prototype.clear = function() {
if (this.fileid) {
fileData.purgeFile(this.fileid);
this.fileid = null;
}
this.file = null; this.file = null;
this.e.off();
};
var DownloadPresentation = function(scope, fileInfo, token, owner) {
BasePresentation.call(this, scope, fileInfo, token);
this.owner = owner;
this.progress = 0;
this.url = null; this.url = null;
this.sortkey = (fileInfo.name || "").toLowerCase();
this.presentable = false; this.presentable = false;
this.downloading = true; this.downloading = true;
this.uploaded = false; this.uploaded = false;
this.openCallback = null;
this.scope.$on("downloadedChunk", _.bind(function(event, idx, byteLength, downloaded, total) { this.scope.$on("downloadedChunk", _.bind(function(event, idx, byteLength, downloaded, total) {
var percentage = Math.ceil((downloaded / total) * 100); var percentage = Math.ceil((downloaded / total) * 100);
@ -58,30 +99,38 @@ define(['jquery', 'underscore', 'text!partials/presentation.html', 'bigscreen'],
this.downloading = false; this.downloading = false;
this.e.triggerHandler("available", [this, url, fileInfo]); this.e.triggerHandler("available", [this, url, fileInfo]);
this.stop(); this.stop();
if (this.openCallback) {
var callback = this.openCallback;
this.openCallback = null;
this.open(callback);
}
}, this)); }, this));
}; };
DownloadPresentation.prototype.open = function($scope) { DownloadPresentation.prototype = new BasePresentation();
$scope.loading = true; DownloadPresentation.prototype.constructor = DownloadPresentation;
DownloadPresentation.prototype.open = function(callback) {
if (this.downloading) { if (this.downloading) {
console.log("Presentation download not finished yet, not showing", this); console.log("Presentation download not finished yet, not showing", this);
this.openCallback = callback;
return; return;
} }
if (this.url && this.url.indexOf("blob:") === 0) { if (this.url && this.url.indexOf("blob:") === 0) {
$scope.$emit("openPdf", this.url); callback(this.url);
return; return;
} }
if (this.file.hasOwnProperty("writer")) { if (this.file.hasOwnProperty("writer")) {
$scope.$emit("openPdf", this.file); callback(this.file);
} else { } else {
this.file.file(function(fp) { this.file.file(function(fp) {
$scope.$emit("openPdf", fp); callback(fp);
}); });
} }
}; };
DownloadPresentation.prototype.close = function($scope) { DownloadPresentation.prototype.close = function() {
$scope.$emit("closePdf"); this.openCallback = null;
}; };
DownloadPresentation.prototype.start = function() { DownloadPresentation.prototype.start = function() {
@ -94,10 +143,7 @@ define(['jquery', 'underscore', 'text!partials/presentation.html', 'bigscreen'],
}; };
DownloadPresentation.prototype.stop = function() { DownloadPresentation.prototype.stop = function() {
if (this.handler) { BasePresentation.prototype.stop.call(this);
mediaStream.tokens.off(this.token, this.handler);
this.handler = null;
}
if (this.session) { if (this.session) {
this.session.cancel(); this.session.cancel();
this.session = null; this.session = null;
@ -111,63 +157,50 @@ define(['jquery', 'underscore', 'text!partials/presentation.html', 'bigscreen'],
this.scope.$destroy(); this.scope.$destroy();
this.scope = null; this.scope = null;
} }
if (this.fileid) { this.openCallback = null;
fileData.purgeFile(this.fileid); BasePresentation.prototype.clear.call(this);
}
this.file = null;
this.e.off();
}; };
var UploadPresentation = function(scope, file, token) { var UploadPresentation = function(scope, file, token) {
this.e = $({}); BasePresentation.call(this, scope, file.info, token);
this.file = file; this.file = file;
this.info = file.info;
this.token = token;
this.scope = scope.$new();
this.scope.info = file.info;
this.sortkey = (file.info.name || "").toLowerCase();
this.presentable = true; this.presentable = true;
this.uploaded = true; this.uploaded = true;
this.session = null; this.fileid = token;
this.handler = null;
}; };
UploadPresentation.prototype.open = function($scope) { UploadPresentation.prototype = new BasePresentation();
$scope.loading = true; UploadPresentation.prototype.constructor = UploadPresentation;
$scope.$emit("openPdf", this.file);
UploadPresentation.prototype.open = function(callback) {
callback(this.file);
}; };
UploadPresentation.prototype.close = function($scope) { UploadPresentation.prototype.close = function() {
$scope.$emit("closePdf");
}; };
UploadPresentation.prototype.start = function() { UploadPresentation.prototype.start = function() {
this.session = fileUpload.startUpload(this.scope, this.token); this.session = fileUpload.startUpload(this.scope, this.token);
// This binds the token to transfer and ui. // This binds the token to transfer and ui.
this.handler = mediaStream.tokens.on(this.token, _.bind(function(event, currenttoken, to, data, type, to2, from, xfer) { this.handler = mediaStream.tokens.on(this.token, _.bind(function(event, currenttoken, to, data, type, to2, from, xfer) {
//console.log("Presentation token request", currenttoken, data, type);
this.session.handleRequest(this.scope, xfer, data); this.session.handleRequest(this.scope, xfer, data);
}, this), "xfer"); }, this), "xfer");
}; };
UploadPresentation.prototype.stop = function() { UploadPresentation.prototype.stop = function() {
if (this.handler) { BasePresentation.prototype.stop.call(this);
mediaStream.tokens.off(this.token, this.handler);
this.handler = null;
}
}; };
UploadPresentation.prototype.clear = function() { UploadPresentation.prototype.clear = function() {
this.stop(); this.stop();
this.close();
if (this.scope) { if (this.scope) {
this.scope.$emit("cancelUpload"); this.scope.$emit("cancelUpload");
this.scope.$destroy(); this.scope.$destroy();
this.scope = null; this.scope = null;
} }
fileData.purgeFile(this.token);
this.session = null; this.session = null;
this.file = null; BasePresentation.prototype.clear.call(this);
this.e.off();
}; };
var controller = ['$scope', '$element', '$attrs', function($scope, $element, $attrs) { var controller = ['$scope', '$element', '$attrs', function($scope, $element, $attrs) {
@ -209,36 +242,31 @@ define(['jquery', 'underscore', 'text!partials/presentation.html', 'bigscreen'],
$scope.loading = false; $scope.loading = false;
}; };
$scope.$on("pdfLoaded", function(event, source, doc) { $scope.$on("presentationOpening", function(event, presentation) {
$scope.currentPageNumber = -1; $scope.loading = true;
});
$scope.$on("presentationLoaded", function(event, source, doc) {
$scope.maxPageNumber = doc.numPages; $scope.maxPageNumber = doc.numPages;
if ($scope.currentPresentation && $scope.currentPresentation.presentable) { if ($scope.currentPresentation && $scope.currentPresentation.presentable) {
$scope.$emit("showPdfPage", 1); $scope.currentPageNumber = 1;
} else if ($scope.pendingPageRequest !== null) { } else if ($scope.pendingPageRequest !== null) {
$scope.$emit("showPdfPage", $scope.pendingPageRequest); $scope.currentPageNumber = $scope.pendingPageRequest;
$scope.pendingPageRequest = null; $scope.pendingPageRequest = null;
} else {
$scope.$emit("showQueuedPdfPage");
} }
$scope.presentationLoaded = true; $scope.presentationLoaded = true;
}); });
$scope.$on("pdfLoadError", function(event, source, errorMessage, moreInfo) { $scope.$on("presentationLoadError", function(event, source, errorMessage, moreInfo) {
$scope.loading = false; $scope.loading = false;
alertify.dialog.alert(errorMessage); alertify.dialog.alert(errorMessage);
}); });
$scope.$watch("currentPageNumber", function(newval, oldval) {
$scope.$emit("showPdfPage", newval);
});
var downloadPresentation = function(fileInfo, from) { var downloadPresentation = function(fileInfo, from) {
var token = fileInfo.id; var token = fileInfo.id;
var existing = $scope.getPresentation(token); var existing = $scope.getPresentation(token);
if (existing) { if (existing) {
console.log("Found existing presentation", existing); console.log("Found existing presentation", existing);
$scope.currentPresentation = existing;
existing.open($scope);
return; return;
} }
@ -257,10 +285,6 @@ define(['jquery', 'underscore', 'text!partials/presentation.html', 'bigscreen'],
if (pos !== -1) { if (pos !== -1) {
$scope.activeDownloads.splice(pos, 1); $scope.activeDownloads.splice(pos, 1);
} }
if ($scope.currentPresentation === download) {
console.log("Current presentation finished downloading, open", download)
download.open($scope);
}
}); });
$scope.activeDownloads.push(download); $scope.activeDownloads.push(download);
$scope.availablePresentations.push(download); $scope.availablePresentations.push(download);
@ -351,7 +375,7 @@ define(['jquery', 'underscore', 'text!partials/presentation.html', 'bigscreen'],
scope.pendingPageRequest = data.Page; scope.pendingPageRequest = data.Page;
} else { } else {
console.log("Received presentation page request", data); console.log("Received presentation page request", data);
scope.$emit("showPdfPage", data.Page); scope.currentPageNumber = data.Page;
} }
}); });
break; break;
@ -434,7 +458,7 @@ define(['jquery', 'underscore', 'text!partials/presentation.html', 'bigscreen'],
} }
}; };
$scope.$on("pdfPageLoading", function(event, page) { $scope.$on("presentationPageLoading", function(event, page) {
$scope.loading = false; $scope.loading = false;
$scope.currentPageNumber = page; $scope.currentPageNumber = page;
if ($scope.receivedPage === page) { if ($scope.receivedPage === page) {
@ -452,12 +476,12 @@ define(['jquery', 'underscore', 'text!partials/presentation.html', 'bigscreen'],
}); });
}); });
$scope.$on("pdfPageLoadError", function(event, page, errorMessage) { $scope.$on("presentationPageLoadError", function(event, page, errorMessage) {
$scope.loading = false; $scope.loading = false;
alertify.dialog.alert(errorMessage); alertify.dialog.alert(errorMessage);
}); });
$scope.$on("pdfPageRenderError", function(event, pageNumber, maxPageNumber, errorMessage) { $scope.$on("presentationPageRenderError", function(event, pageNumber, maxPageNumber, errorMessage) {
$scope.loading = false; $scope.loading = false;
alertify.dialog.alert(errorMessage); alertify.dialog.alert(errorMessage);
}); });
@ -482,9 +506,9 @@ define(['jquery', 'underscore', 'text!partials/presentation.html', 'bigscreen'],
var filesSelected = function(files) { var filesSelected = function(files) {
var valid_files = []; var valid_files = [];
_.each(files, function(f) { _.each(files, function(f) {
if (f.info.type !== "application/pdf") { if (!SUPPORTED_TYPES.hasOwnProperty(f.info.type)) {
console.log("Not sharing file", f); console.log("Not sharing file", f);
alertify.dialog.alert(translation._("Only PDF documents can be shared at this time.")); alertify.dialog.alert(translation._("Only PDF documents and OpenDocument files can be shared at this time."));
valid_files = null; valid_files = null;
return; return;
} }
@ -493,11 +517,13 @@ define(['jquery', 'underscore', 'text!partials/presentation.html', 'bigscreen'],
} }
}); });
_.each(valid_files, function(f) { $scope.$apply(function(scope) {
if (!f.info.hasOwnProperty("id")) { _.each(valid_files, function(f) {
f.info.id = f.id; if (!f.info.hasOwnProperty("id")) {
} f.info.id = f.id;
$scope.advertiseFile(f); }
scope.advertiseFile(f);
});
}); });
}; };
@ -570,7 +596,6 @@ define(['jquery', 'underscore', 'text!partials/presentation.html', 'bigscreen'],
mediaStream.tokens.off(currentToken, tokenHandler); mediaStream.tokens.off(currentToken, tokenHandler);
currentToken = null; currentToken = null;
} }
$scope.$emit("closePdf");
$scope.resetProperties(); $scope.resetProperties();
$scope.layout.presentation = false; $scope.layout.presentation = false;
peers = {}; peers = {};
@ -585,7 +610,7 @@ define(['jquery', 'underscore', 'text!partials/presentation.html', 'bigscreen'],
} }
if ($scope.currentPresentation === presentation) { if ($scope.currentPresentation === presentation) {
// switch back to first page when clicked on current presentation // switch back to first page when clicked on current presentation
$scope.$emit("showPdfPage", 1); $scope.currentPageNumber = 1;
return; return;
} }
mediaStream.webrtc.callForEachCall(function(peercall) { mediaStream.webrtc.callForEachCall(function(peercall) {
@ -599,13 +624,8 @@ define(['jquery', 'underscore', 'text!partials/presentation.html', 'bigscreen'],
$scope.doSelectPresentation = function(presentation) { $scope.doSelectPresentation = function(presentation) {
console.log("Selected", presentation); console.log("Selected", presentation);
$scope.currentPageNumber = -1; $scope.resetProperties();
$scope.maxPageNumber = -1;
$scope.currentPresentation = presentation; $scope.currentPresentation = presentation;
$scope.receivedPage = null;
$scope.presentationLoaded = false;
$scope.pendingPageRequest = null;
presentation.open($scope);
}; };
$scope.deletePresentation = function(presentation, $event) { $scope.deletePresentation = function(presentation, $event) {
@ -625,7 +645,7 @@ define(['jquery', 'underscore', 'text!partials/presentation.html', 'bigscreen'],
}); });
} }
if ($scope.currentPresentation === presentation) { if ($scope.currentPresentation === presentation) {
presentation.close($scope); $scope.currentPresentation = null;
$scope.resetProperties(); $scope.resetProperties();
} }
presentation.clear(); presentation.clear();
@ -643,9 +663,21 @@ define(['jquery', 'underscore', 'text!partials/presentation.html', 'bigscreen'],
}; };
$scope.prevPage = function() {
if ($scope.currentPageNumber > 1) {
$scope.currentPageNumber -= 1;
}
};
$scope.nextPage = function() {
if ($scope.currentPageNumber < $scope.maxPageNumber) {
$scope.currentPageNumber += 1;
}
};
mediaStream.webrtc.e.on("done", function() { mediaStream.webrtc.e.on("done", function() {
_.each($scope.availablePresentations, function(download) { _.each($scope.availablePresentations, function(presentation) {
download.clear(); presentation.clear();
}); });
$scope.availablePresentations = []; $scope.availablePresentations = [];
$scope.activeDownloads = []; $scope.activeDownloads = [];
@ -662,14 +694,14 @@ define(['jquery', 'underscore', 'text!partials/presentation.html', 'bigscreen'],
switch (event.keyCode) { switch (event.keyCode) {
case 37: case 37:
// left arrow // left arrow
$scope.$emit("prevPage"); $scope.prevPage();
event.preventDefault(); event.preventDefault();
break; break;
case 39: case 39:
// right arrow // right arrow
case 32: case 32:
// space // space
$scope.$emit("nextPage"); $scope.nextPage();
event.preventDefault(); event.preventDefault();
break; break;
} }
@ -684,10 +716,6 @@ define(['jquery', 'underscore', 'text!partials/presentation.html', 'bigscreen'],
} }
}); });
$($window).on("resize", function() {
$scope.$emit("redrawPdf");
});
$scope.$watch("layout.main", function(newval, oldval) { $scope.$watch("layout.main", function(newval, oldval) {
if (newval && newval !== "presentation") { if (newval && newval !== "presentation") {
$scope.hidePresentation(); $scope.hidePresentation();

641
static/js/libs/webodf.js

File diff suppressed because one or more lines are too long

13
static/js/main.js

@ -52,6 +52,7 @@ require.config({
'pdf': 'libs/pdf/pdf', 'pdf': 'libs/pdf/pdf',
'pdf.worker': 'libs/pdf/pdf.worker', 'pdf.worker': 'libs/pdf/pdf.worker',
'pdf.compatibility': 'libs/pdf/compatibility', 'pdf.compatibility': 'libs/pdf/compatibility',
'webodf': 'libs/webodf',
'partials': '../partials', 'partials': '../partials',
'sounds': '../sounds', 'sounds': '../sounds',
@ -116,6 +117,16 @@ require.config({
'pdf': { 'pdf': {
deps: ['pdf.compatibility'], deps: ['pdf.compatibility'],
exports: 'PDFJS' exports: 'PDFJS'
},
'webodf': {
exports: 'odf',
init: function() {
return {
webodf: webodf,
odf: odf,
runtime: runtime
}
}
} }
} }
}); });
@ -217,4 +228,4 @@ if (Object.create) {
} else { } else {
alert("Your browser does not support this application. Please update your browser to the latest version."); alert("Your browser does not support this application. Please update your browser to the latest version.");
} }

16
static/partials/presentation.html

@ -1,27 +1,31 @@
<div class="presentation"> <div class="presentation">
<div class="presentationpane nicescroll"> <div class="presentationpane nicescroll">
<div class="welcome container-fluid" ng-show="loading || (maxPageNumber === -1 && activeDownloads.length)"> <div class="welcome container-fluid" ng-show="loading || (!currentPresentation && activeDownloads.length)">
<h1>{{_('Loading presentation ...')}}</h1> <h1>{{_('Loading presentation ...')}}</h1>
<div class="progress" ng-repeat="download in activeDownloads | orderBy: 'sortkey'"> <div class="progress" ng-repeat="download in activeDownloads | orderBy: 'sortkey'">
<span class="download-info">{{download.info.name}} ({{download.info.size|humanizeFilesize}} / {{download.progress}}%)</span> <span class="download-info">{{download.info.name}} ({{download.info.size|humanizeFilesize}} / {{download.progress}}%)</span>
<div class="progress-bar progress-bar-success" ng-style="{'width': download.progress+'%'}"></div> <div class="progress-bar progress-bar-success" ng-style="{'width': download.progress+'%'}"></div>
</div> </div>
</div> </div>
<div class="welcome container-fluid" ng-hide="maxPageNumber !== -1 || loading"> <div class="welcome container-fluid" ng-hide="currentPresentation || loading">
<h1>{{_('Please upload a PDF document')}}</h1> <h1>{{_('Please upload a document')}}</h1>
<div class="center-block"> <div class="center-block">
<p>{{_('Supported formats are PDF documents and OpenDocument files.')}}</p>
<p>{{_('Documents are shared with everyone in this call.')}}</p> <p>{{_('Documents are shared with everyone in this call.')}}</p>
<p><button class="btn btn-lg btn-primary">{{_('Upload')}}</button></p> <p><button class="btn btn-lg btn-primary">{{_('Upload')}}</button></p>
<p>{{_('You can drag files here too.')}}</p> <p>{{_('You can drag files here too.')}}</p>
</div> </div>
</div> </div>
<pdfcanvas ng-hide="maxPageNumber === -1 || loading" /> <div class="canvasContainer" ng-if="currentPresentation" ng-hide="loading" ng-switch="currentPresentation.type">
<pdfcanvas ng-switch-when="pdf" />
<odfcanvas ng-switch-when="odf" />
</div>
</div> </div>
<div class="overlaybar form-horizontal" ng-class="{notvisible: hideControlsBar}"> <div class="overlaybar form-horizontal" ng-class="{notvisible: hideControlsBar}">
<a class="overlaybar-button" ng-model="hideControlsBar" btn-checkbox btn-checkbox btn-checkbox-true="0" btn-checkbox-false="1" title="{{_('Presentation controls')}}"><i class="fa fa-cogs"></i></a> <a class="overlaybar-button" ng-model="hideControlsBar" btn-checkbox btn-checkbox btn-checkbox-true="0" btn-checkbox-false="1" title="{{_('Presentation controls')}}"><i class="fa fa-cogs"></i></a>
<div class="overlaybar-content"> <div class="overlaybar-content">
<div class="pagecontrol" ng-show="maxPageNumber !== -1 || availablePresentations.length"> <div class="pagecontrol" ng-show="currentPresentation || availablePresentations.length">
<div class="clearfix" ng-show="maxPageNumber !== -1"> <div class="clearfix" ng-show="currentPresentation && maxPageNumber &gt; 1">
<a class="overlaybar-button btn-prev" ng-click="prevPage()" ng-show="currentPageNumber &gt; 1" title="{{_('Prev')}}"><i class="fa fa-backward"></i></a> <a class="overlaybar-button btn-prev" ng-click="prevPage()" ng-show="currentPageNumber &gt; 1" title="{{_('Prev')}}"><i class="fa fa-backward"></i></a>
<div class="pageinfo center-block"><input class="form-control input-sm" type="number" ng-model="currentPageNumber" /> / {{ maxPageNumber }}</div> <div class="pageinfo center-block"><input class="form-control input-sm" type="number" ng-model="currentPageNumber" /> / {{ maxPageNumber }}</div>
<a class="overlaybar-button btn-next" ng-click="nextPage()" ng-show="currentPageNumber &lt; maxPageNumber" title="{{_('Next')}}"><i class="fa fa-forward"></i></a> <a class="overlaybar-button btn-next" ng-click="nextPage()" ng-show="currentPageNumber &lt; maxPageNumber" title="{{_('Next')}}"><i class="fa fa-forward"></i></a>

Loading…
Cancel
Save