Merge pull request #3189 from FinnStutzenstein/pdf-zip

Pdf zip
This commit is contained in:
Emanuel Schütze 2017-04-13 11:54:19 +02:00 committed by GitHub
commit 7d2785b9ec
5 changed files with 132 additions and 52 deletions

View File

@ -17,6 +17,7 @@ Motions:
- Fixed empty motion comment field in motion update form [#3194]. - Fixed empty motion comment field in motion update form [#3194].
- Removed server side image to base64 transformation and - Removed server side image to base64 transformation and
added local transformation [#2705] added local transformation [#2705]
- Added support for export motions in a zip archive [#3189].
Core: Core:
- No reload on logoff. OpenSlides is now a full single page - No reload on logoff. OpenSlides is now a full single page

View File

@ -28,6 +28,7 @@
"jquery.cookie": "~1.4.1", "jquery.cookie": "~1.4.1",
"js-data": "~2.9.0", "js-data": "~2.9.0",
"js-data-angular": "~3.2.1", "js-data-angular": "~3.2.1",
"jszip": "~3.1.3",
"lodash": "~4.16.0", "lodash": "~4.16.0",
"ng-dialog": "~0.6.4", "ng-dialog": "~0.6.4",
"ng-file-upload": "~11.2.3", "ng-file-upload": "~11.2.3",

View File

@ -859,10 +859,11 @@ angular.module('OpenSlidesApp.core.pdf', [])
.factory('PdfCreate', [ .factory('PdfCreate', [
'$timeout', '$timeout',
'$q',
'gettextCatalog', 'gettextCatalog',
'FileSaver', 'FileSaver',
'Messaging', 'Messaging',
function ($timeout, gettextCatalog, FileSaver, Messaging) { function ($timeout, $q, gettextCatalog, FileSaver, Messaging) {
var filenameMessageMap = {}; var filenameMessageMap = {};
var b64toBlob = function(b64Data) { var b64toBlob = function(b64Data) {
var byteCharacters = atob(b64Data); var byteCharacters = atob(b64Data);
@ -898,19 +899,28 @@ angular.module('OpenSlidesApp.core.pdf', [])
}, 1); }, 1);
}; };
return { return {
getBase64FromDocument: function (pdfDocument) {
return $q(function (resolve, reject) {
var pdfWorker = new Worker('/static/js/workers/pdf-worker.js');
pdfWorker.addEventListener('message', function (event) {
resolve(event.data);
});
pdfWorker.addEventListener('error', function (event) {
reject(event);
});
pdfWorker.postMessage(JSON.stringify(pdfDocument));
});
},
download: function (pdfDocument, filename) { download: function (pdfDocument, filename) {
stateChange('info', filename); stateChange('info', filename);
var pdfWorker = new Worker('/static/js/workers/pdf-worker.js');
pdfWorker.addEventListener('message', function (event) { this.getBase64FromDocument(pdfDocument).then(function (data) {
var blob = b64toBlob(event.data); var blob = b64toBlob(data);
stateChange('success', filename); stateChange('success', filename);
FileSaver.saveAs(blob, filename); FileSaver.saveAs(blob, filename);
}, function (error) {
stateChange('error', filename, error.message);
}); });
pdfWorker.addEventListener('error', function (event) {
stateChange('error', filename, event.message);
});
pdfWorker.postMessage(JSON.stringify(pdfDocument));
}, },
}; };
} }

View File

@ -288,7 +288,6 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
for (var i = 0; i < fields.length; i++) { for (var i = 0; i < fields.length; i++) {
if (motion.comments[i] && canSeeComment(i)) { if (motion.comments[i] && canSeeComment(i)) {
var title = gettextCatalog.getString('Comment') + ' ' + fields[i].name; var title = gettextCatalog.getString('Comment') + ' ' + fields[i].name;
console.log(fields[i]);
if (!fields[i].public) { if (!fields[i].public) {
title += ' (' + gettextCatalog.getString('internal') + ')'; title += ' (' + gettextCatalog.getString('internal') + ')';
} }
@ -602,6 +601,7 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
.factory('MotionPdfExport', [ .factory('MotionPdfExport', [
'$http', '$http',
'$q',
'Config', 'Config',
'gettextCatalog', 'gettextCatalog',
'MotionChangeRecommendation', 'MotionChangeRecommendation',
@ -614,15 +614,14 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
'PdfMakeBallotPaperProvider', 'PdfMakeBallotPaperProvider',
'PdfCreate', 'PdfCreate',
'PDFLayout', 'PDFLayout',
'$q', 'Messaging',
function ($http, Config, gettextCatalog, MotionChangeRecommendation, HTMLValidizer, PdfMakeConverter, 'FileSaver',
function ($http, $q, Config, gettextCatalog, MotionChangeRecommendation, HTMLValidizer, PdfMakeConverter,
MotionContentProvider, MotionCatalogContentProvider, PdfMakeDocumentProvider, PollContentProvider, MotionContentProvider, MotionCatalogContentProvider, PdfMakeDocumentProvider, PollContentProvider,
PdfMakeBallotPaperProvider, PdfCreate, PDFLayout, $q) { PdfMakeBallotPaperProvider, PdfCreate, PDFLayout, Messaging, FileSaver) {
return { return {
export: function (motions, params, singleMotion) { getDocumentProvider: function (motions, params, singleMotion) {
if (!params) { params = _.clone(params || {}); // Clone this to avoid sideeffects.
params = {};
}
_.defaults(params, { _.defaults(params, {
filename: gettextCatalog.getString('motions') + '.pdf', filename: gettextCatalog.getString('motions') + '.pdf',
changeRecommendationMode: Config.get('motions_recommendation_text_mode').value, changeRecommendationMode: Config.get('motions_recommendation_text_mode').value,
@ -643,11 +642,11 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
angular.forEach(motions, function (motion) { angular.forEach(motions, function (motion) {
if (singleMotion) { if (singleMotion) {
motion.changeRecommendations = MotionChangeRecommendation.filter({ motion.changeRecommendations = MotionChangeRecommendation.filter({
'where': {'motion_version_id': {'==': motion.active_version}} 'where': {'motion_version_id': {'==': params.version}}
}); });
} else { } else {
motion.changeRecommendations = MotionChangeRecommendation.filter({ motion.changeRecommendations = MotionChangeRecommendation.filter({
'where': {'motion_version_id': {'==': params.version}} 'where': {'motion_version_id': {'==': motion.active_version}}
}); });
} }
var text = motion.getTextByMode(params.changeRecommendationMode, null); var text = motion.getTextByMode(params.changeRecommendationMode, null);
@ -659,43 +658,93 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
image_sources = image_sources.concat(tmp_image_sources); image_sources = image_sources.concat(tmp_image_sources);
}); });
var image_map = {}; var imageMap = {};
_.forEach(image_sources, function (image_source) { var imagePromises = _.map(image_sources, function (image_source) {
image_map[image_source] = PDFLayout.imageURLtoBase64(image_source); return PDFLayout.imageURLtoBase64(image_source).then(function (base64Str) {
imageMap[image_source] = base64Str;
});
}); });
var image_promises = Object.keys(image_map).map(function( key ) { return $q(function (resolve) {
return image_map[key]; //resolve promises to get base64
$q.all(imagePromises).then(function(base64Str) {
var converter = PdfMakeConverter.createInstance(imageMap);
var motionContentProviderArray = [];
//convert all motions to motionContentProviders
angular.forEach(motions, function (motion) {
var version = (singleMotion ? params.version : motion.active_version);
motionContentProviderArray.push(MotionContentProvider.createInstance(
converter, motion, version, params.changeRecommendationMode,
motion.changeRecommendations, params.lineNumberMode,
params.includeReason, params.includeComments
));
});
var documentProvider;
if (singleMotion) {
documentProvider = PdfMakeDocumentProvider.createInstance(motionContentProviderArray[0]);
} else {
var motionCatalogContentProvider = MotionCatalogContentProvider.createInstance(motionContentProviderArray);
documentProvider = PdfMakeDocumentProvider.createInstance(motionCatalogContentProvider);
}
resolve(documentProvider);
});
}); });
},
//resolv promises to get base64 export: function (motions, params, singleMotion) {
$q.all(image_promises).then(function(base64Str) { _.defaults(params, {
Object.keys(image_map).map(function(key, i) { filename: gettextCatalog.getString('motions') + '.pdf',
image_map[key] = base64Str[i]; });
}); this.getDocumentProvider(motions, params, singleMotion).then(
function (documentProvider) {
var converter = PdfMakeConverter.createInstance(image_map); PdfCreate.download(documentProvider.getDocument(), params.filename);
var motionContentProviderArray = [];
//convert all motions to motionContentProviders
angular.forEach(motions, function (motion) {
var version = (singleMotion ? params.version : motion.active_version);
motionContentProviderArray.push(MotionContentProvider.createInstance(
converter, motion, version, params.changeRecommendationMode,
motion.changeRecommendations, params.lineNumberMode,
params.includeReason, params.includeComments
));
});
var documentProvider;
if (singleMotion) {
documentProvider = PdfMakeDocumentProvider.createInstance(motionContentProviderArray[0]);
} else {
var motionCatalogContentProvider = MotionCatalogContentProvider.createInstance(motionContentProviderArray);
documentProvider = PdfMakeDocumentProvider.createInstance(motionCatalogContentProvider);
} }
);
},
exportZip: function (motions, params) {
var messageId = Messaging.addMessage('<i class="fa fa-spinner fa-pulse fa-lg spacer-right"></i>' +
gettextCatalog.getString('Generating PDFs and ZIP archive') + ' ...', 'info');
var zipFilename = params.filename || gettextCatalog.getString('motions') + '.zip';
params.filename = void 0; // clear this, so we do not override the default filenames for each pdf.
PdfCreate.download(documentProvider.getDocument(), params.filename); var self = this;
var pdfs = {};
var pdfPromises = _.map(motions, function (motion) {
var identifier = motion.identifier ? '-' + motion.identifier : '';
var filename = gettextCatalog.getString('Motion') + identifier + '.pdf';
return $q(function (resolve, reject) {
// get documentProvider for every motion.
self.getDocumentProvider(motion, params, true).then(function (documentProvider) {
var doc = documentProvider.getDocument();
PdfCreate.getBase64FromDocument(doc).then(function (data) {
pdfs[filename] = data;
resolve();
}, function (error) {
reject(error);
});
});
});
});
// Wait for all documents to be generated. Then put them into a zip and download it.
$q.all(pdfPromises).then(function () {
var zip = new JSZip();
_.forEach(pdfs, function (data, filename) {
zip.file(filename, data, {base64: true});
});
Messaging.createOrEditMessage(messageId, '<i class="fa fa-check fa-lg spacer-right"></i>' +
gettextCatalog.getString('ZIP successfully generated.'), 'success', {timeout: 3000});
zip.generateAsync({type: 'blob'}).then(function (content) {
FileSaver.saveAs(content, zipFilename);
});
}, function (error) {
Messaging.createOrEditMessage(messageId, '<i class="fa fa-exclamation-triangle fa-lg ' +
'spacer-right"></i>' + gettextCatalog.getString('Error while generating ZIP file') +
': <code>' + error + '</code>', 'error');
}); });
}, },
createPollPdf: function (motion, version) { createPollPdf: function (motion, version) {

View File

@ -733,9 +733,23 @@ angular.module('OpenSlidesApp.motions.site', [
], ],
}, },
hideExpression: "model.format !== 'pdf'", hideExpression: "model.format !== 'pdf'",
}); });
} }
} }
if (!singleMotion) {
fields.push({
key: 'pdfFormat',
type: 'select-radio',
templateOptions: {
label: gettextCatalog.getString('PDF format'),
options: [
{name: gettextCatalog.getString('One PDF'), value: 'pdf'},
{name: gettextCatalog.getString('Multiple PDFs in a zip arcive'), value: 'zip'},
],
},
hideExpression: "model.format !== 'pdf'",
});
}
return fields; return fields;
}, },
}; };
@ -761,6 +775,7 @@ angular.module('OpenSlidesApp.motions.site', [
$scope.params = params || {}; $scope.params = params || {};
_.defaults($scope.params, { _.defaults($scope.params, {
format: 'pdf', format: 'pdf',
pdfFormat: 'pdf',
changeRecommendationMode: Config.get('motions_recommendation_text_mode').value, changeRecommendationMode: Config.get('motions_recommendation_text_mode').value,
lineNumberMode: Config.get('motions_default_line_numbering').value, lineNumberMode: Config.get('motions_default_line_numbering').value,
includeReason: true, includeReason: true,
@ -772,7 +787,11 @@ angular.module('OpenSlidesApp.motions.site', [
$scope.export = function () { $scope.export = function () {
switch ($scope.params.format) { switch ($scope.params.format) {
case 'pdf': case 'pdf':
MotionPdfExport.export(motions, $scope.params, singleMotion); if ($scope.params.pdfFormat === 'pdf') {
MotionPdfExport.export(motions, $scope.params, singleMotion);
} else {
MotionPdfExport.exportZip(motions, $scope.params);
}
break; break;
case 'csv': case 'csv':
MotionCsvExport.export(motions, $scope.params); MotionCsvExport.export(motions, $scope.params);