From a63247fc16e4c9cdcd83925a44b6da1cf724b5c3 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Sun, 20 Jul 2014 00:20:04 +0200 Subject: [PATCH] Refactored presentations code to support arbitrary drawing canvases and don't use hardcoded "pdf*" events. --- static/js/directives/pdfcanvas.js | 134 +++++++++--------- static/js/directives/presentation.js | 195 +++++++++++++++------------ static/partials/presentation.html | 12 +- 3 files changed, 176 insertions(+), 165 deletions(-) diff --git a/static/js/directives/pdfcanvas.js b/static/js/directives/pdfcanvas.js index be6b6328..dfbac86c 100644 --- a/static/js/directives/pdfcanvas.js +++ b/static/js/directives/pdfcanvas.js @@ -20,7 +20,7 @@ */ 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; @@ -59,31 +59,33 @@ define(['require', 'underscore', 'jquery'], function(require, _, $) { PDFCanvas.prototype.close = function() { this._close(); - this.scope.$emit("pdfClosed"); }; - PDFCanvas.prototype.open = function(file) { - console.log("Loading PDF from", file); - this._close(); - if (typeof file === "string") { - // got a url - this._openFile(file); - return; - } + PDFCanvas.prototype.open = function(presentation) { + this.scope.$emit("presentationOpening", presentation); + presentation.open(_.bind(function(source) { + console.log("Loading PDF from", source); + this._close(); + if (typeof source === "string") { + // got a url + this._openFile(source); + return; + } - var fp = file.file || file; - if (typeof URL !== "undefined" && URL.createObjectURL) { - var url = URL.createObjectURL(fp); - this._openFile(url); - } else { - var fileReader = new FileReader(); - fileReader.onload = _.bind(function(evt) { - var buffer = evt.target.result; - var uint8Array = new Uint8Array(buffer); - this._openFile(uint8Array); - }, this); - fileReader.readAsArrayBuffer(fp); - } + var fp = source.file || source; + if (typeof URL !== "undefined" && URL.createObjectURL) { + var url = URL.createObjectURL(fp); + this._openFile(url); + } else { + var fileReader = new FileReader(); + fileReader.onload = _.bind(function(evt) { + var buffer = evt.target.result; + var uint8Array = new Uint8Array(buffer); + this._openFile(uint8Array); + }, this); + fileReader.readAsArrayBuffer(fp); + } + }, this)); }; PDFCanvas.prototype._pdfLoaded = function(source, doc) { @@ -92,7 +94,7 @@ define(['require', 'underscore', 'jquery'], function(require, _, $) { this.maxPageNumber = doc.numPages; this.currentPageNumber = -1; console.log("PDF loaded", doc); - scope.$emit("pdfLoaded", source, doc); + scope.$emit("presentationLoaded", source, doc); }, this)); }; @@ -114,11 +116,12 @@ define(['require', 'underscore', 'jquery'], function(require, _, $) { break; } this.scope.$apply(_.bind(function(scope) { - scope.$emit("pdfLoadError", source, loadErrorMessage); + scope.$emit("presentationLoadError", source, loadErrorMessage); }, this)); }; PDFCanvas.prototype._doOpenFile = function(source) { + this.scope.$emit("presentationLoading", source); pdfjs.getDocument(source).then(_.bind(function(doc) { this._pdfLoaded(source, doc); }, this), _.bind(function(error, exception) { @@ -127,7 +130,6 @@ define(['require', 'underscore', 'jquery'], function(require, _, $) { }; PDFCanvas.prototype._openFile = function(source) { - this.scope.$emit("pdfLoading", source); if (pdfjs === null) { // load pdf.js lazily require(['pdf'], _.bind(function(pdf) { @@ -145,10 +147,12 @@ define(['require', 'underscore', 'jquery'], function(require, _, $) { }; PDFCanvas.prototype._pageLoaded = function(page, pageObject) { - console.log("Got page", pageObject); - this.scope.$emit("pdfPageLoaded", page, pageObject); - this.currentPage = pageObject; - this.drawPage(pageObject); + this.scope.$apply(_.bind(function(scope) { + console.log("Got page", pageObject); + scope.$emit("presentationPageLoaded", page, pageObject); + this.currentPage = pageObject; + this.drawPage(pageObject); + }, this)); }; PDFCanvas.prototype._pageLoadError = function(page, error, exception) { @@ -159,7 +163,9 @@ define(['require', 'underscore', 'jquery'], function(require, _, $) { } else { 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) { @@ -173,7 +179,7 @@ define(['require', 'underscore', 'jquery'], function(require, _, $) { this.currentPage = null; } this.currentPageNumber = page; - this.scope.$emit("pdfPageLoading", page); + this.scope.$emit("presentationPageLoading", page); this.doc.getPage(page).then(_.bind(function(pageObject) { this._pageLoaded(page, pageObject); }, this), _.bind(function(error, exception) { @@ -185,7 +191,7 @@ define(['require', 'underscore', 'jquery'], function(require, _, $) { this.renderTask = null; this.scope.$apply(_.bind(function(scope) { 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... scope.canvasIndex = 1 - scope.canvasIndex; this.showQueuedPage(); @@ -205,7 +211,7 @@ define(['require', 'underscore', 'jquery'], function(require, _, $) { loadErrorMessage = translation._("An unknown error occurred while rendering the PDF page."); } this.scope.$apply(_.bind(function(scope) { - this.scope.$emit("pdfPageRenderError", pageObject.pageNumber, this.maxPageNumber, loadErrorMessage); + scope.$emit("presentationPageRenderError", pageObject.pageNumber, this.maxPageNumber, loadErrorMessage); }, this)); }; @@ -241,7 +247,7 @@ define(['require', 'underscore', 'jquery'], function(require, _, $) { }; 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 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() { if (this.pendingPageNumber !== null) { this._showPage(this.pendingPageNumber); @@ -290,12 +288,17 @@ define(['require', 'underscore', 'jquery'], function(require, _, $) { var canvases = container.find("canvas"); var pdfCanvas = new PDFCanvas($scope, canvases); - $scope.$on("openPdf", function(event, source) { - pdfCanvas.open(source); - }); - - $scope.$on("closePdf", function() { - pdfCanvas.close(); + $scope.$watch("currentPresentation", function(presentation, previousPresentation) { + if (presentation) { + safeApply($scope, function(scope) { + pdfCanvas.open(presentation); + }); + } else { + if (previousPresentation) { + previousPresentation.close(); + } + pdfCanvas.close(); + } }); $scope.$on("$destroy", function() { @@ -303,34 +306,21 @@ define(['require', 'underscore', 'jquery'], function(require, _, $) { pdfCanvas = null; }); - $scope.$on("showPdfPage", function(event, page) { - pdfCanvas.showPage(page); - }); - - $scope.$on("showQueuedPdfPage", function() { - pdfCanvas.showQueuedPage(); - }); - - $scope.$on("redrawPdf", function() { - pdfCanvas.redrawPage(); - }); + $scope.$watch("currentPageNumber", function(page, oldValue) { + if (page === oldValue) { + // no change + return; + } - $scope.$on("prevPage", function() { - pdfCanvas.prevPage(); + pdfCanvas.showPage(page); }); - $scope.$on("nextPage", function() { - pdfCanvas.nextPage(); + $($window).on("resize", function() { + $scope.$apply(function(scope) { + pdfCanvas.redrawPage(); + }); }); - $scope.prevPage = function() { - $scope.$emit("prevPage"); - }; - - $scope.nextPage = function() { - $scope.$emit("nextPage"); - }; - }]; return { diff --git a/static/js/directives/presentation.js b/static/js/directives/presentation.js index 5624056e..5816bfcd 100644 --- a/static/js/directives/presentation.js +++ b/static/js/directives/presentation.js @@ -22,23 +22,54 @@ 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) { - var DownloadPresentation = function(scope, fileInfo, token, owner) { + var SUPPORTED_TYPES = { + // rendered by pdfcanvas directive + "application/pdf": "pdf" + }; + + var BasePresentation = function(scope, fileInfo, token) { this.e = $({}); + if (scope) { + this.scope = scope.$new(); + this.scope.info = fileInfo; + } this.info = fileInfo; + if (fileInfo) { + this.sortkey = (fileInfo.name || "").toLowerCase(); + this.type = SUPPORTED_TYPES[fileInfo.type] || "unknown"; + } this.token = token; - this.owner = owner; - this.scope = scope.$new(); - this.scope.info = fileInfo; - this.progress = 0; + this.fileid = null; + this.file = null; this.handler = 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.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.sortkey = (fileInfo.name || "").toLowerCase(); this.presentable = false; this.downloading = true; this.uploaded = false; + this.openCallback = null; this.scope.$on("downloadedChunk", _.bind(function(event, idx, byteLength, downloaded, total) { var percentage = Math.ceil((downloaded / total) * 100); @@ -58,30 +89,38 @@ define(['jquery', 'underscore', 'text!partials/presentation.html', 'bigscreen'], this.downloading = false; this.e.triggerHandler("available", [this, url, fileInfo]); this.stop(); + if (this.openCallback) { + var callback = this.openCallback; + this.openCallback = null; + this.open(callback); + } }, this)); }; - DownloadPresentation.prototype.open = function($scope) { - $scope.loading = true; + DownloadPresentation.prototype = new BasePresentation(); + DownloadPresentation.prototype.constructor = DownloadPresentation; + + DownloadPresentation.prototype.open = function(callback) { if (this.downloading) { console.log("Presentation download not finished yet, not showing", this); + this.openCallback = callback; return; } if (this.url && this.url.indexOf("blob:") === 0) { - $scope.$emit("openPdf", this.url); + callback(this.url); return; } if (this.file.hasOwnProperty("writer")) { - $scope.$emit("openPdf", this.file); + callback(this.file); } else { this.file.file(function(fp) { - $scope.$emit("openPdf", fp); + callback(fp); }); } }; - DownloadPresentation.prototype.close = function($scope) { - $scope.$emit("closePdf"); + DownloadPresentation.prototype.close = function() { + this.openCallback = null; }; DownloadPresentation.prototype.start = function() { @@ -94,10 +133,7 @@ define(['jquery', 'underscore', 'text!partials/presentation.html', 'bigscreen'], }; DownloadPresentation.prototype.stop = function() { - if (this.handler) { - mediaStream.tokens.off(this.token, this.handler); - this.handler = null; - } + BasePresentation.prototype.stop.call(this); if (this.session) { this.session.cancel(); this.session = null; @@ -111,63 +147,50 @@ define(['jquery', 'underscore', 'text!partials/presentation.html', 'bigscreen'], this.scope.$destroy(); this.scope = null; } - if (this.fileid) { - fileData.purgeFile(this.fileid); - } - this.file = null; - this.e.off(); + this.openCallback = null; + BasePresentation.prototype.clear.call(this); }; var UploadPresentation = function(scope, file, token) { - this.e = $({}); + BasePresentation.call(this, scope, file.info, token); 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.uploaded = true; - this.session = null; - this.handler = null; + this.fileid = token; }; - UploadPresentation.prototype.open = function($scope) { - $scope.loading = true; - $scope.$emit("openPdf", this.file); + UploadPresentation.prototype = new BasePresentation(); + UploadPresentation.prototype.constructor = UploadPresentation; + + UploadPresentation.prototype.open = function(callback) { + callback(this.file); }; - UploadPresentation.prototype.close = function($scope) { - $scope.$emit("closePdf"); + UploadPresentation.prototype.close = function() { }; UploadPresentation.prototype.start = function() { this.session = fileUpload.startUpload(this.scope, this.token); // 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) { - //console.log("Presentation token request", currenttoken, data, type); this.session.handleRequest(this.scope, xfer, data); }, this), "xfer"); }; UploadPresentation.prototype.stop = function() { - if (this.handler) { - mediaStream.tokens.off(this.token, this.handler); - this.handler = null; - } + BasePresentation.prototype.stop.call(this); }; UploadPresentation.prototype.clear = function() { this.stop(); + this.close(); if (this.scope) { this.scope.$emit("cancelUpload"); this.scope.$destroy(); this.scope = null; } - fileData.purgeFile(this.token); this.session = null; - this.file = null; - this.e.off(); + BasePresentation.prototype.clear.call(this); }; var controller = ['$scope', '$element', '$attrs', function($scope, $element, $attrs) { @@ -209,36 +232,32 @@ define(['jquery', 'underscore', 'text!partials/presentation.html', 'bigscreen'], $scope.loading = false; }; - $scope.$on("pdfLoaded", function(event, source, doc) { - $scope.currentPageNumber = -1; + $scope.$on("presentationOpening", function(event, presentation) { + $scope.loading = true; + }); + + $scope.$on("presentationLoaded", function(event, source, doc) { $scope.maxPageNumber = doc.numPages; if ($scope.currentPresentation && $scope.currentPresentation.presentable) { - $scope.$emit("showPdfPage", 1); + $scope.currentPageNumber = 1; } else if ($scope.pendingPageRequest !== null) { - $scope.$emit("showPdfPage", $scope.pendingPageRequest); + $scope.currentPageNumber = $scope.pendingPageRequest; $scope.pendingPageRequest = null; - } else { - $scope.$emit("showQueuedPdfPage"); } $scope.presentationLoaded = true; }); - $scope.$on("pdfLoadError", function(event, source, errorMessage, moreInfo) { + $scope.$on("presentationLoadError", function(event, source, errorMessage, moreInfo) { $scope.loading = false; alertify.dialog.alert(errorMessage); }); - $scope.$watch("currentPageNumber", function(newval, oldval) { - $scope.$emit("showPdfPage", newval); - }); - var downloadPresentation = function(fileInfo, from) { var token = fileInfo.id; var existing = $scope.getPresentation(token); if (existing) { console.log("Found existing presentation", existing); $scope.currentPresentation = existing; - existing.open($scope); return; } @@ -257,10 +276,6 @@ define(['jquery', 'underscore', 'text!partials/presentation.html', 'bigscreen'], if (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.availablePresentations.push(download); @@ -351,7 +366,7 @@ define(['jquery', 'underscore', 'text!partials/presentation.html', 'bigscreen'], scope.pendingPageRequest = data.Page; } else { console.log("Received presentation page request", data); - scope.$emit("showPdfPage", data.Page); + scope.currentPageNumber = data.Page; } }); break; @@ -434,7 +449,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.currentPageNumber = page; if ($scope.receivedPage === page) { @@ -452,12 +467,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; alertify.dialog.alert(errorMessage); }); - $scope.$on("pdfPageRenderError", function(event, pageNumber, maxPageNumber, errorMessage) { + $scope.$on("presentationPageRenderError", function(event, pageNumber, maxPageNumber, errorMessage) { $scope.loading = false; alertify.dialog.alert(errorMessage); }); @@ -482,9 +497,9 @@ define(['jquery', 'underscore', 'text!partials/presentation.html', 'bigscreen'], var filesSelected = function(files) { var valid_files = []; _.each(files, function(f) { - if (f.info.type !== "application/pdf") { + if (!SUPPORTED_TYPES.hasOwnProperty(f.info.type)) { 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; return; } @@ -493,11 +508,13 @@ define(['jquery', 'underscore', 'text!partials/presentation.html', 'bigscreen'], } }); - _.each(valid_files, function(f) { - if (!f.info.hasOwnProperty("id")) { - f.info.id = f.id; - } - $scope.advertiseFile(f); + $scope.$apply(function(scope) { + _.each(valid_files, function(f) { + if (!f.info.hasOwnProperty("id")) { + f.info.id = f.id; + } + scope.advertiseFile(f); + }); }); }; @@ -570,7 +587,6 @@ define(['jquery', 'underscore', 'text!partials/presentation.html', 'bigscreen'], mediaStream.tokens.off(currentToken, tokenHandler); currentToken = null; } - $scope.$emit("closePdf"); $scope.resetProperties(); $scope.layout.presentation = false; peers = {}; @@ -585,7 +601,7 @@ define(['jquery', 'underscore', 'text!partials/presentation.html', 'bigscreen'], } if ($scope.currentPresentation === presentation) { // switch back to first page when clicked on current presentation - $scope.$emit("showPdfPage", 1); + $scope.currentPageNumber = 1; return; } mediaStream.webrtc.callForEachCall(function(peercall) { @@ -599,13 +615,8 @@ define(['jquery', 'underscore', 'text!partials/presentation.html', 'bigscreen'], $scope.doSelectPresentation = function(presentation) { console.log("Selected", presentation); - $scope.currentPageNumber = -1; - $scope.maxPageNumber = -1; + $scope.resetProperties(); $scope.currentPresentation = presentation; - $scope.receivedPage = null; - $scope.presentationLoaded = false; - $scope.pendingPageRequest = null; - presentation.open($scope); }; $scope.deletePresentation = function(presentation, $event) { @@ -625,7 +636,7 @@ define(['jquery', 'underscore', 'text!partials/presentation.html', 'bigscreen'], }); } if ($scope.currentPresentation === presentation) { - presentation.close($scope); + $scope.currentPresentation = null; $scope.resetProperties(); } presentation.clear(); @@ -643,9 +654,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() { - _.each($scope.availablePresentations, function(download) { - download.clear(); + _.each($scope.availablePresentations, function(presentation) { + presentation.clear(); }); $scope.availablePresentations = []; $scope.activeDownloads = []; @@ -662,14 +685,14 @@ define(['jquery', 'underscore', 'text!partials/presentation.html', 'bigscreen'], switch (event.keyCode) { case 37: // left arrow - $scope.$emit("prevPage"); + $scope.prevPage(); event.preventDefault(); break; case 39: // right arrow case 32: // space - $scope.$emit("nextPage"); + $scope.nextPage(); event.preventDefault(); break; } @@ -684,10 +707,6 @@ define(['jquery', 'underscore', 'text!partials/presentation.html', 'bigscreen'], } }); - $($window).on("resize", function() { - $scope.$emit("redrawPdf"); - }); - $scope.$watch("layout.main", function(newval, oldval) { if (newval && newval !== "presentation") { $scope.hidePresentation(); diff --git a/static/partials/presentation.html b/static/partials/presentation.html index f93ad441..bef74ead 100644 --- a/static/partials/presentation.html +++ b/static/partials/presentation.html @@ -1,13 +1,13 @@
-
+

{{_('Loading presentation ...')}}

{{download.info.name}} ({{download.info.size|humanizeFilesize}} / {{download.progress}}%)
-
+

{{_('Please upload a PDF document')}}

{{_('Documents are shared with everyone in this call.')}}

@@ -15,13 +15,15 @@

{{_('You can drag files here too.')}}

- +
+ +
-
-
+
+
/ {{ maxPageNumber }}