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; /** * 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( '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} An array containing the `DocDefinitions` for `pdf-make`. */ private appendSubmittersAndRecommendation(motion: ViewMotion, style: StyleType = StyleType.DEFAULT): Array { 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) ); } }