diff --git a/bower.json b/bower.json index 9186d6099..060064fd2 100644 --- a/bower.json +++ b/bower.json @@ -31,6 +31,10 @@ "ckeditor": "~4.5.4", "angular-ckeditor": "~1.0.0", "roboto-condensed": "~0.3.0", - "open-sans-fontface": "https://github.com/OpenSlides/open-sans.git#1.4.2.post1" + "open-sans-fontface": "https://github.com/OpenSlides/open-sans.git#1.4.2.post1", + "angular-pdf": "~1.3.0" + }, + "resolutions": { + "angular": ">= 1.4.9" } } diff --git a/openslides/mediafiles/serializers.py b/openslides/mediafiles/serializers.py index b86f6b2f4..ffc72db07 100644 --- a/openslides/mediafiles/serializers.py +++ b/openslides/mediafiles/serializers.py @@ -2,6 +2,7 @@ import mimetypes from django.conf import settings from django.db import models as dbmodels +from PyPDF2 import PdfFileReader from ..utils.rest_api import FileField, ModelSerializer, SerializerMethodField from .models import Mediafile @@ -17,10 +18,14 @@ class AngularCompatibleFileField(FileField): def to_representation(self, value): if value is None: return None - return { + filetype = mimetypes.guess_type(value.path)[0] + result = { 'name': value.name, - 'type': mimetypes.guess_type(value.path)[0] + 'type': filetype } + if filetype == 'application/pdf': + result['pages'] = PdfFileReader(open(value.path, 'rb')).getNumPages() + return result class MediafileSerializer(ModelSerializer): diff --git a/openslides/mediafiles/static/js/mediafiles/projector.js b/openslides/mediafiles/static/js/mediafiles/projector.js index 846aa34ef..cb9577d70 100644 --- a/openslides/mediafiles/static/js/mediafiles/projector.js +++ b/openslides/mediafiles/static/js/mediafiles/projector.js @@ -4,4 +4,62 @@ angular.module('OpenSlidesApp.mediafiles.projector', ['OpenSlidesApp.mediafiles']); -}()); +.config([ + 'slidesProvider', + function(slidesProvider) { + slidesProvider.registerSlide('mediafiles/mediafile', { + template: 'static/templates/mediafiles/slide_mediafile.html' + }); + } +]) + +.controller('SlideMediafileCtrl', [ + '$scope', + 'Mediafile', + function($scope, Mediafile) { + + var id = $scope.element.mediafile; + $scope.page = $scope.element.page; + + function updateScale() { + if($scope.element.pageFit) { + $scope.scale = 'page-fit'; + } else { + $scope.scale = $scope.element.scale; + } + } + + $scope.$watch(function() { + return $scope.element.scale; + }, updateScale); + + updateScale(); + + var mediafile = Mediafile.find(id); + mediafile.then(function(mediafile) { + $scope.pdfName = mediafile.title; + $scope.pdfUrl = mediafile.mediafileUrl; + }) + + $scope.scroll = 0; + + $scope.getNavStyle = function(scroll) { + if(scroll > 100) return 'pdf-controls fixed'; + else return 'pdf-controls'; + }; + + $scope.onError = function(error) { + console.log(error); + }; + + $scope.onLoad = function() { + $scope.loading = ''; + }; + + $scope.onProgress = function(progress) { + console.log(progress); + }; + } +]); + +})(); diff --git a/openslides/mediafiles/static/js/mediafiles/site.js b/openslides/mediafiles/static/js/mediafiles/site.js index 8536399d4..11fd1d09c 100644 --- a/openslides/mediafiles/static/js/mediafiles/site.js +++ b/openslides/mediafiles/static/js/mediafiles/site.js @@ -59,6 +59,20 @@ angular.module('OpenSlidesApp.mediafiles.site', ['ngFileUpload', 'OpenSlidesApp. $scope.sortColumn = 'title'; $scope.filterPresent = ''; $scope.reverse = false; + + function updatePresentedMediafiles() { + var projectorElements = _.map(Projector.get(1).elements, function(element) { return element }); + $scope.presentedMediafiles = _.filter(projectorElements, function (element) { + return element.name === 'mediafiles/mediafile'; + }); + } + + $scope.$watch(function() { + return Projector.get(1).elements; + }, updatePresentedMediafiles); + + updatePresentedMediafiles(); + // function to sort by clicked column $scope.toggleSort = function ( column ) { if ( $scope.sortColumn === column ) { @@ -111,6 +125,110 @@ angular.module('OpenSlidesApp.mediafiles.site', ['ngFileUpload', 'OpenSlidesApp. $scope.delete = function (mediafile) { Mediafile.destroy(mediafile.id); }; + + // show document on projector + $scope.showPdf = function (mediafile) { + var postUrl, + data; + if($scope.presentedMediafiles.length > 0) { + // update first mediafile, at the moment there should not be more + var uuid = $scope.presentedMediafiles[0].uuid; + postUrl = '/rest/core/projector/1/update_elements/'; + data = {}; + data[uuid] = { + mediafile: mediafile.id, + numPages: mediafile.mediafile.pages, + page: 1, + pageFit: true, + scale: 1, + visible: true + }; + } else { + postUrl = '/rest/core/projector/1/activate_elements/'; + data = [{ + name: 'mediafiles/mediafile', + visible: true, + numPages: mediafile.mediafile.pages, + pageFit: true, + scale: 1, + mediafile: mediafile.id, + page: 1 + }]; + } + $http.post(postUrl, data); + }; + + function sendMediafileCommand(data) { + var mediafileElement = getCurrentlyPresentedMediafile(); + var updateData = _.extend({}, mediafileElement); + _.extend(updateData, data); + var postData = {}; + postData[mediafileElement.uuid] = updateData; + $http.post('/rest/core/projector/1/update_elements/', postData); + } + + function getCurrentlyPresentedMediafile() { + return $scope.presentedMediafiles[0]; + } + + $scope.mediafileGoPrevious = function () { + var mediafileElement = getCurrentlyPresentedMediafile(); + if (mediafileElement.page <= 1) { + return; + } + sendMediafileCommand({ + page: parseInt(mediafileElement.page) - 1 + }); + }; + $scope.mediafileGoNext = function () { + var mediafileElement = getCurrentlyPresentedMediafile(); + if (mediafileElement.page >= mediafileElement.numPages) { + return; + } + sendMediafileCommand({ + page: parseInt(mediafileElement.page) + 1 + }); + }; + $scope.mediafileZoomIn = function () { + var mediafileElement = getCurrentlyPresentedMediafile(); + sendMediafileCommand({ + pageFit: false, + scale: parseFloat(mediafileElement.scale) + 0.2 + }); + }; + $scope.mediafileFit = function () { + sendMediafileCommand({ + pageFit: true + }); + }; + $scope.mediafileZoomOut = function () { + var mediafileElement = getCurrentlyPresentedMediafile(); + sendMediafileCommand({ + pageFit: false, + scale: parseFloat(mediafileElement.scale) - 0.2 + }); + }; + $scope.mediafileChangePage = function(pageNum) { + sendMediafileCommand({ + pageToDisplay: pageNum + }); + }; + $scope.mediafileRotate = function () { + var rotation; + var currentRotation = $scope.mediafile.rotation; + if (currentRotation === 0) { + rotation = 90; + } else if (currentRotation === 90) { + rotation = 180; + } else if (currentRotation === 180) { + rotation = 270; + } else { + rotation = 0; + } + sendMediafileCommand({ + rotation: rotation + }); + }; } ]) diff --git a/openslides/mediafiles/static/templates/mediafiles/mediafile-list.html b/openslides/mediafiles/static/templates/mediafiles/mediafile-list.html index c2bb3d990..80ba47b6d 100644 --- a/openslides/mediafiles/static/templates/mediafiles/mediafile-list.html +++ b/openslides/mediafiles/static/templates/mediafiles/mediafile-list.html @@ -10,6 +10,27 @@ + +
+ +
+

PDF controls

+ +
+
+
diff --git a/openslides/mediafiles/static/templates/mediafiles/slide_mediafile.html b/openslides/mediafiles/static/templates/mediafiles/slide_mediafile.html new file mode 100644 index 000000000..758788828 --- /dev/null +++ b/openslides/mediafiles/static/templates/mediafiles/slide_mediafile.html @@ -0,0 +1,8 @@ +
+ + +
diff --git a/openslides/mediafiles/static/templates/mediafiles/slide_mediafile_partial.html b/openslides/mediafiles/static/templates/mediafiles/slide_mediafile_partial.html new file mode 100644 index 000000000..78d538229 --- /dev/null +++ b/openslides/mediafiles/static/templates/mediafiles/slide_mediafile_partial.html @@ -0,0 +1 @@ + diff --git a/requirements_production.txt b/requirements_production.txt index a20e23bab..4d23e7cb0 100644 --- a/requirements_production.txt +++ b/requirements_production.txt @@ -9,4 +9,5 @@ reportlab>=3.0,<3.3 roman>=2.0,<2.1 setuptools>=2.2,<20.0 sockjs-tornado>=1.0,<1.1 -Whoosh>=2.7,<2.8 +Whoosh>=2.7.0,<2.8 +PyPDF2==1.25.1