OpenSlides/client/src/app/site/motions/services/motion-pdf-catalog.service.ts
Emanuel Schütze 4e6eddef98 Motion PDF improvements
- show motion export dialog for all users
- print recommendation in table of content lines in italic
- only print categories of filtered motions in table of content
- add page break after each (sub)category
- reduce font size for footer date
2019-07-12 14:30:24 +02:00

255 lines
10 KiB
TypeScript

import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import { TranslateService } from '@ngx-translate/core';
import { CategoryRepositoryService } from 'app/core/repositories/motions/category-repository.service';
import { ConfigService } from 'app/core/ui-services/config.service';
import { MotionPdfService } from './motion-pdf.service';
import { MotionRepositoryService } from 'app/core/repositories/motions/motion-repository.service';
import { PdfError, PdfDocumentService, StyleType, BorderType } from 'app/core/ui-services/pdf-document.service';
import { ViewCategory } from '../models/view-category';
import { ViewMotion } from '../models/view-motion';
import { ExportFormData } from '../modules/motion-list/components/motion-export-dialog/motion-export-dialog.component';
/**
* Service to export a list of motions.
*
* @example
* ```ts
* const docDef = this.motionPdfCatalogService.motionListToDocDef(myListOfViewMotions);
* ```
*/
@Injectable({
providedIn: 'root'
})
export class MotionPdfCatalogService {
private categoryObserver: BehaviorSubject<ViewCategory[]>;
/**
* Constructor
*
* @param translate handle translations
* @param configService read out config variables
* @param motionPdfService handle motion to pdf conversion
*/
public constructor(
private translate: TranslateService,
private configService: ConfigService,
private motionPdfService: MotionPdfService,
private pdfService: PdfDocumentService,
private motionRepo: MotionRepositoryService,
private categoryRepo: CategoryRepositoryService
) {
this.categoryObserver = this.categoryRepo.getViewModelListBehaviorSubject();
}
/**
* Converts the list of motions to pdfmake doc definition.
* Public entry point to conversion of multiple motions
*
* @param motions the list of view motions to convert
* @param lnMode
* @param crMode
* @param contentToExport
* @param infoToExport
* @param commentsToExport
* @returns pdfmake doc definition as object
*/
public motionListToDocDef(motions: ViewMotion[], exportInfo: ExportFormData): object {
let doc = [];
const motionDocList = [];
for (let motionIndex = 0; motionIndex < motions.length; ++motionIndex) {
try {
const motionDocDef: any = this.motionPdfService.motionToDocDef(motions[motionIndex], exportInfo);
// add id field to the first page of a motion to make it findable over TOC
motionDocDef[0].id = `${motions[motionIndex].id}`;
motionDocList.push(motionDocDef);
if (motionIndex < motions.length - 1) {
motionDocList.push(this.pdfService.getPageBreak());
}
} catch (err) {
const errorText = `${this.translate.instant('Error during PDF creation of motion:')} ${
motions[motionIndex].identifierOrTitle
}`;
console.error(`${errorText}\nDebugInfo:\n`, err);
throw new PdfError(errorText);
}
}
// print extra data (title, preamble, categories, toc) only if there are more than 1 motion
if (motions.length > 1 && (!exportInfo.pdfOptions || exportInfo.pdfOptions.includes('toc'))) {
doc.push(
this.pdfService.createTitle('motions_export_title'),
this.pdfService.createPreamble('motions_export_preamble'),
this.createToc(motions)
);
}
doc = doc.concat(motionDocList);
return doc;
}
/**
* Creates the table of contents for the motion book.
* Considers sorting by categories and no sorting.
*
* @param motions The motions to add in the TOC
* @param sorting The optional sorting strategy
* @returns the table of contents as document definition
*/
private createToc(motions: ViewMotion[], sorting?: string): object {
const toc = [];
const categories = this.categoryObserver.value;
// Create the toc title
const tocTitle = {
text: this.translate.instant('Table of contents'),
style: 'heading2'
};
const exportSubmitterRecommendation = this.configService.instant<boolean>(
'motions_export_submitter_recommendation'
);
// Initialize the header and the layout for border-style.
const header = exportSubmitterRecommendation ? this.getTocHeaderDefinition() : undefined;
const layout = exportSubmitterRecommendation ? BorderType.LIGHT_HORIZONTAL_LINES : BorderType.DEFAULT;
if (categories && categories.length) {
const catTocBody = [];
for (const category of categories.sort((a, b) => a.weight - b.weight)) {
// find out if the category has any motions
const motionToCurrentCat = motions.filter(motionIn => category === motionIn.category);
if (motionToCurrentCat && motionToCurrentCat.length) {
catTocBody.push({
table: {
body: [
[
{
text: category.getTitle(),
style: !!category.parent ? 'tocSubcategoryTitle' : 'tocCategoryTitle'
}
]
]
},
layout: exportSubmitterRecommendation ? 'lightHorizontalLines' : 'noBorders'
});
const tocBody = [];
for (const motion of motionToCurrentCat) {
if (exportSubmitterRecommendation) {
tocBody.push(this.appendSubmittersAndRecommendation(motion, StyleType.CATEGORY_SECTION));
} else {
tocBody.push(
this.pdfService.createTocLine(
`${motion.identifier ? motion.identifier : ''}`,
motion.title,
`${motion.id}`,
StyleType.CATEGORY_SECTION
)
);
}
}
catTocBody.push(
this.pdfService.createTocTableDef(tocBody, StyleType.CATEGORY_SECTION, layout, header)
);
catTocBody.push(this.pdfService.getPageBreak());
}
}
// handle those without category
const uncatTocBody = motions
.filter(motion => !motion.category)
.map(motion =>
this.pdfService.createTocLine(
`${motion.identifier ? motion.identifier : ''}`,
motion.title,
`${motion.id}`
)
);
// only push this array if there is at least one entry
if (uncatTocBody.length > 0) {
catTocBody.push(this.pdfService.createTocTableDef(uncatTocBody, StyleType.CATEGORY_SECTION));
}
toc.push(catTocBody);
} else {
// all motions in the same table
const tocBody = [];
for (const motion of motions) {
if (exportSubmitterRecommendation) {
tocBody.push(this.appendSubmittersAndRecommendation(motion));
} else {
tocBody.push(
this.pdfService.createTocLine(
`${motion.identifier ? motion.identifier : ''}`,
motion.title,
`${motion.id}`
)
);
}
}
toc.push(this.pdfService.createTocTableDef(tocBody, StyleType.CATEGORY_SECTION, layout, header));
}
return [tocTitle, toc, this.pdfService.getPageBreak()];
}
/**
* Function to get the definition for the header
* for exporting motion-list as PDF. Needed, if submitters
* and recommendation should also be exported to the `Table of contents`.
*
* @returns {object} The DocDefinition for `pdfmake.js`.
*/
private getTocHeaderDefinition(): object {
return [
{ text: this.translate.instant('Identifier'), style: 'tocHeaderRow' },
{
style: 'tocHeaderRow',
text: [
`${this.translate.instant('Title')} · ${this.translate.instant('Submitters')} · `,
{ text: `${this.translate.instant('Recommendation')}`, italics: true }
]
},
{ text: this.translate.instant('Page'), style: 'tocHeaderRow', alignment: 'right' }
];
}
/**
* Creates lines for the `Table of contents` containing submitters and recommendation.
*
* @param motion The motion containing the information
* @param style Optional `StyleType`. Defaults to `tocEntry`.
*
* @returns {Array<Object>} An array containing the `DocDefinitions` for `pdf-make`.
*/
private appendSubmittersAndRecommendation(motion: ViewMotion, style: StyleType = StyleType.DEFAULT): Array<Object> {
const recommendation = this.motionRepo.getExtendedRecommendationLabel(motion);
let submitterList = '';
for (let i = 0; i < motion.submitters.length; ++i) {
submitterList +=
i !== motion.submitters.length - 1
? motion.submitters[i].getTitle() + ', '
: motion.submitters[i].getTitle();
}
return this.pdfService.createTocLine(
`${motion.identifier ? motion.identifier : ''}`,
motion.title,
`${motion.id}`,
style,
this.pdfService.createTocLineInline(submitterList),
this.pdfService.createTocLineInline(recommendation, true)
);
}
}