Merge pull request #1903 from emanuelschuetze/pdfMediafilePresentation
Mediafile pdf presentation with angular-pdf and pdf.js (Fixes #1664).
This commit is contained in:
commit
e041656115
@ -31,9 +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.2.x"
|
||||
"angular": ">=1.4.9 <1.5"
|
||||
}
|
||||
}
|
||||
|
10
gulpfile.js
10
gulpfile.js
@ -21,6 +21,7 @@ var argv = require('yargs').argv,
|
||||
mainBowerFiles = require('main-bower-files'),
|
||||
minifyCSS = require('gulp-minify-css'),
|
||||
path = require('path'),
|
||||
rename = require("gulp-rename"),
|
||||
through = require('through2'),
|
||||
uglify = require('gulp-uglify'),
|
||||
vsprintf = require('sprintf-js').vsprintf;
|
||||
@ -69,6 +70,13 @@ gulp.task('ckeditor', function () {
|
||||
.pipe(gulp.dest(path.join(output_directory, 'ckeditor')));
|
||||
});
|
||||
|
||||
// Extra task only for pdfjs
|
||||
gulp.task('pdfjs', function () {
|
||||
return gulp.src(path.join('bower_components', 'pdfjs-dist', 'build', 'pdf.worker.js'))
|
||||
.pipe(rename(path.join('openslides-libs.worker.js')))
|
||||
.pipe(gulp.dest(path.join(output_directory, 'js')));
|
||||
});
|
||||
|
||||
// Compiles translation files (*.po) to *.json and saves them in the directory
|
||||
// openslides/static/i18n/.
|
||||
gulp.task('translations', function () {
|
||||
@ -80,7 +88,7 @@ gulp.task('translations', function () {
|
||||
});
|
||||
|
||||
// Gulp default task. Runs all other tasks before.
|
||||
gulp.task('default', ['js-libs', 'css-libs', 'fonts-libs', 'ckeditor', 'translations'], function () {});
|
||||
gulp.task('default', ['js-libs', 'css-libs', 'fonts-libs', 'ckeditor', 'pdfjs', 'translations'], function () {});
|
||||
|
||||
|
||||
/**
|
||||
|
@ -10,6 +10,7 @@ angular.module('OpenSlidesApp.core', [
|
||||
'ngSanitize', // TODO: only use this in functions that need it.
|
||||
'ui.bootstrap',
|
||||
'ui.tree',
|
||||
'pdf'
|
||||
])
|
||||
|
||||
.config([
|
||||
|
@ -7,6 +7,7 @@
|
||||
<link rel="stylesheet" href="static/css/projector.css">
|
||||
<link rel="icon" href="/static/img/favicon.png">
|
||||
<script src="static/js/openslides-libs.js"></script>
|
||||
<script src="static/js/openslides-libs.worker.js"></script>
|
||||
|
||||
<style type="text/css">
|
||||
#header, #footer {
|
||||
|
@ -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):
|
||||
|
@ -2,6 +2,57 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
angular.module('OpenSlidesApp.mediafiles.projector', ['OpenSlidesApp.mediafiles']);
|
||||
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) {
|
||||
// load mediafile object
|
||||
var mediafile = Mediafile.find($scope.element.id);
|
||||
mediafile.then(function(mediafile) {
|
||||
$scope.pdfName = mediafile.title;
|
||||
$scope.pdfUrl = mediafile.mediafileUrl;
|
||||
})
|
||||
// get page from projector
|
||||
$scope.page = $scope.element.page;
|
||||
$scope.scroll = 0;
|
||||
|
||||
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();
|
||||
|
||||
$scope.getNavStyle = function(scroll) {
|
||||
if (scroll > 100) {
|
||||
return 'pdf-controls fixed';
|
||||
} else {
|
||||
return 'pdf-controls';
|
||||
}
|
||||
};
|
||||
|
||||
$scope.onLoad = function() {
|
||||
$scope.loading = '';
|
||||
};
|
||||
}
|
||||
]);
|
||||
|
||||
})();
|
||||
|
@ -51,7 +51,8 @@ angular.module('OpenSlidesApp.mediafiles.site', ['ngFileUpload', 'OpenSlidesApp.
|
||||
'Mediafile',
|
||||
'MediafileForm',
|
||||
'User',
|
||||
function($scope, $http, ngDialog, Mediafile, MediafileForm, User) {
|
||||
'Projector',
|
||||
function($scope, $http, ngDialog, Mediafile, MediafileForm, User, Projector) {
|
||||
Mediafile.bindAll({}, $scope, 'mediafiles');
|
||||
User.bindAll({}, $scope, 'users');
|
||||
|
||||
@ -59,6 +60,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 +126,105 @@ angular.module('OpenSlidesApp.mediafiles.site', ['ngFileUpload', 'OpenSlidesApp.
|
||||
$scope.delete = function (mediafile) {
|
||||
Mediafile.destroy(mediafile.id);
|
||||
};
|
||||
|
||||
// ** PDF presentation functions **/
|
||||
// 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] = {
|
||||
id: mediafile.id,
|
||||
numPages: mediafile.mediafile.pages,
|
||||
page: 1,
|
||||
pageFit: true,
|
||||
scale: 1,
|
||||
visible: true
|
||||
};
|
||||
} else {
|
||||
postUrl = '/rest/core/projector/1/prune_elements/';
|
||||
data = [{
|
||||
name: 'mediafiles/mediafile',
|
||||
id: mediafile.id,
|
||||
numPages: mediafile.mediafile.pages,
|
||||
visible: true,
|
||||
pageFit: true,
|
||||
scale: 1,
|
||||
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) {
|
||||
sendMediafileCommand({
|
||||
page: parseInt(mediafileElement.page) - 1
|
||||
});
|
||||
}
|
||||
};
|
||||
$scope.mediafileGoNext = function () {
|
||||
var mediafileElement = getCurrentlyPresentedMediafile();
|
||||
if (mediafileElement.page < mediafileElement.numPages) {
|
||||
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 === 270) {
|
||||
rotation = 0;
|
||||
} else {
|
||||
rotation = currentRotation + 90;
|
||||
}
|
||||
sendMediafileCommand({
|
||||
rotation: rotation
|
||||
});
|
||||
};
|
||||
}
|
||||
])
|
||||
|
||||
|
@ -60,6 +60,40 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- mediafile pdf controls -->
|
||||
<div os-perms="core.can_manage_projector" ng-show="presentedMediafiles.length">
|
||||
<div ng-repeat="presentedMediafile in presentedMediafiles" class="well well-sm">
|
||||
<h3 translate>PDF control elements</h3>
|
||||
<!-- TODO: show filename / title of mediafile -->
|
||||
<nav ng-class="getNavStyle(scroll)" class="form-inline">
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-default" ng-click="mediafileGoPrevious()" title="{{ 'Previouse page' | translate }}">
|
||||
<i class="fa fa-backward"></i>
|
||||
</button>
|
||||
<button class="btn btn-default" ng-click="mediafileGoNext()" title="{{ 'Next page' | translate }}">
|
||||
<i class="fa fa-forward"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-default" ng-click="mediafileZoomOut()" title="{{ 'Zoom out' | translate }}">
|
||||
<i class="fa fa-search-minus"></i>
|
||||
</button>
|
||||
<button class="btn btn-default" ng-click="mediafileFit()" title="{{ 'Reset zoom' | translate }}">
|
||||
100%
|
||||
</button>
|
||||
<button class="btn btn-default" ng-click="mediafileZoomIn()" title="{{ 'Zoom in' | translate }}">
|
||||
<i class="fa fa-search-plus"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon" translate>Page</span>
|
||||
<input type="number" min=1 ng-model="presentedMediafile.page" class="form-control" style="width: 80px">
|
||||
<span class="input-group-addon"><translate>of</translate> {{presentedMediafile.numPages}}</span>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="spacer-top-lg italic">
|
||||
{{ mediafilesFiltered.length }} /
|
||||
{{ mediafiles.length }} {{ "files" | translate }}<span ng-if="(mediafiles|filter:{selected:true}).length > 0">,
|
||||
@ -110,14 +144,12 @@
|
||||
class="animate-item"
|
||||
ng-class="{ 'activeline': mediafile.isProjected(), 'selected': mediafile.selected }">
|
||||
<!-- projector column -->
|
||||
<!-- TOOD: implement project pdf feature -->
|
||||
<td ng-show="!isDeleteMode"
|
||||
os-perms="core.can_manage_projector">
|
||||
<a class="btn btn-default btn-sm"
|
||||
ng-if="mediafile.mediafile.type == 'application/pdf'"
|
||||
ng-class="{ 'btn-primary': mediafile.isProjected() }"
|
||||
ng-click="mediafile.project()"
|
||||
ng-bootbox-alert="{{ 'Sorry, the function to project pdf files is not yet implemented.' | translate }}"
|
||||
ng-click="showPdf(mediafile)"
|
||||
title="{{ 'Project mediafile' | translate }}">
|
||||
<i class="fa fa-video-camera"></i>
|
||||
</a>
|
||||
|
@ -0,0 +1,8 @@
|
||||
<div ng-controller="SlideMediafileCtrl" class="content">
|
||||
<ng-pdf template-url="/static/templates/mediafiles/slide_mediafile_partial.html"
|
||||
scale="page-fit"
|
||||
ng-attr-scale="{{ scale }}"
|
||||
ng-attr-page="{{ page }}"
|
||||
debug="true">
|
||||
</ng-pdf>
|
||||
</div>
|
@ -0,0 +1 @@
|
||||
<canvas id="pdf-canvas" class="rotate0"></canvas>
|
@ -7,13 +7,14 @@
|
||||
"gulp-angular-gettext": "~2.1.0",
|
||||
"gulp-concat": "~2.6.0",
|
||||
"gulp-if": "~2.0.0",
|
||||
"gulp-jshint": "~2.0.0",
|
||||
"gulp-minify-css": "~1.2.3",
|
||||
"gulp-rename": "~1.2.2",
|
||||
"gulp-uglify": "~1.5.1",
|
||||
"main-bower-files": "~2.11.1",
|
||||
"yargs": "~3.32.0",
|
||||
"po2json": "~0.4.1",
|
||||
"gulp-jshint": "~2.0.0",
|
||||
"sprintf-js": "~1.0.3",
|
||||
"through2": "~2.0.0",
|
||||
"sprintf-js": "~1.0.3"
|
||||
"yargs": "~3.32.0"
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user