Merge pull request #4709 from GabrielInTheWorld/utilities
Implements the export of assignment-list as pdf
This commit is contained in:
commit
7e2045aa76
@ -136,6 +136,19 @@ export class HtmlToPdfService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to convert plain html text without linenumbering.
|
||||
*
|
||||
* @param text The html text that should be converted to PDF.
|
||||
*
|
||||
* @returns {object} The converted html as DocDef.
|
||||
*/
|
||||
public addPlainText(text: string): object {
|
||||
return {
|
||||
columns: [{ stack: this.convertHtml(text, LineNumberingMode.None) }]
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes an HTML string, converts to HTML using a DOM parser and recursivly parses
|
||||
* the content into pdfmake compatible doc definition
|
||||
|
@ -8,6 +8,14 @@ import { TranslateService } from '@ngx-translate/core';
|
||||
import { ConfigService } from './config.service';
|
||||
import { HttpService } from '../core-services/http.service';
|
||||
|
||||
/**
|
||||
* Enumeration to define possible values for the styling.
|
||||
*/
|
||||
export enum StyleType {
|
||||
DEFAULT = 'tocEntry',
|
||||
CATEGORY_SECTION = 'tocCategorySection'
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom PDF error class to handle errors in a safer way
|
||||
*/
|
||||
@ -594,4 +602,92 @@ export class PdfDocumentService {
|
||||
pdfMake.vfs[url] = base64;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the title for the motion list as pdfmake doc definition
|
||||
*
|
||||
* @returns The motion list title for the PDF document
|
||||
*/
|
||||
public createTitle(configVariable: string): object {
|
||||
const titleText = this.translate.instant(this.configService.instant<string>(configVariable));
|
||||
return {
|
||||
text: titleText,
|
||||
style: 'title'
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the preamble for the motion list as pdfmake doc definition
|
||||
*
|
||||
* @returns The motion list preamble for the PDF document
|
||||
*/
|
||||
public createPreamble(configVariable: string): object {
|
||||
const preambleText = this.configService.instant<string>(configVariable);
|
||||
|
||||
if (preambleText) {
|
||||
return {
|
||||
text: preambleText,
|
||||
style: 'preamble'
|
||||
};
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
public getPageBreak(): Object {
|
||||
return {
|
||||
text: '',
|
||||
pageBreak: 'after'
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the table definition for the TOC
|
||||
*
|
||||
* @param tocBody the body of the table
|
||||
* @returns The table of contents as doc definition
|
||||
*/
|
||||
public createTocTableDef(tocBody: object, style: StyleType = StyleType.DEFAULT): object {
|
||||
return {
|
||||
table: {
|
||||
widths: ['auto', '*', 'auto'],
|
||||
body: tocBody
|
||||
},
|
||||
layout: 'noBorders',
|
||||
style: style
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Function, that creates a line for the 'Table of contents'
|
||||
*
|
||||
* @param identifier The identifier/prefix for the line
|
||||
* @param title The name of the line
|
||||
* @param pageReference Defaults to the page, where the object begins
|
||||
* @param style Optional style. Defaults to `'tocEntry'`
|
||||
*
|
||||
* @returns A line for the toc
|
||||
*/
|
||||
public createTocLine(
|
||||
identifier: string,
|
||||
title: string,
|
||||
pageReference: string,
|
||||
style: StyleType = StyleType.DEFAULT
|
||||
): Object {
|
||||
return [
|
||||
{
|
||||
text: identifier,
|
||||
style: style
|
||||
},
|
||||
{
|
||||
text: title,
|
||||
style: 'tocEntry'
|
||||
},
|
||||
{
|
||||
pageReference: pageReference,
|
||||
style: 'tocEntry',
|
||||
alignment: 'right'
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -61,7 +61,7 @@
|
||||
<h1>{{ assignment.getTitle() }}</h1>
|
||||
</div>
|
||||
<div *ngIf="assignment">
|
||||
<div *ngIf="assignment.assignment.description" [innerHTML]="assignment.assignment.description"></div>
|
||||
<div *ngIf="assignment.assignment.description" [innerHTML]="getSanitizedText(assignment.assignment.description)"></div>
|
||||
</div>
|
||||
<div class="meta-info-grid">
|
||||
<div class="number-of-elected">
|
||||
|
@ -2,7 +2,7 @@ import { Component, OnInit } from '@angular/core';
|
||||
import { FormBuilder, FormGroup } from '@angular/forms';
|
||||
import { MatSnackBar } from '@angular/material';
|
||||
import { Router, ActivatedRoute } from '@angular/router';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { Title, DomSanitizer, SafeHtml } from '@angular/platform-browser';
|
||||
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
@ -181,7 +181,8 @@ export class AssignmentDetailComponent extends BaseViewComponent implements OnIn
|
||||
private tagRepo: TagRepositoryService,
|
||||
private promptService: PromptService,
|
||||
private pdfService: AssignmentPdfExportService,
|
||||
private mediafileRepo: MediafileRepositoryService
|
||||
private mediafileRepo: MediafileRepositoryService,
|
||||
private sanitizer: DomSanitizer
|
||||
) {
|
||||
super(title, translate, matSnackBar);
|
||||
this.subscriptions.push(
|
||||
@ -501,4 +502,15 @@ export class AssignmentDetailComponent extends BaseViewComponent implements OnIn
|
||||
.sortCandidates(listInNewOrder.map(relatedUser => relatedUser.id), this.assignment)
|
||||
.then(null, this.raiseError);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize the text.
|
||||
*
|
||||
* @param text {string} The text to display.
|
||||
*
|
||||
* @returns {SafeHtml} the sanitized text.
|
||||
*/
|
||||
public getSanitizedText(text: string): SafeHtml {
|
||||
return this.sanitizer.bypassSecurityTrustHtml(text);
|
||||
}
|
||||
}
|
||||
|
@ -111,6 +111,15 @@
|
||||
<span translate>Deselect all</span>
|
||||
</button>
|
||||
<mat-divider></mat-divider>
|
||||
<button
|
||||
*osPerms="'assignment.can_manage'"
|
||||
mat-menu-item
|
||||
[disabled]="!selectedRows.length"
|
||||
(click)="downloadAssignmentButton(selectedRows)">
|
||||
<mat-icon>archive</mat-icon>
|
||||
<span>{{ 'Export selected elections' | translate }}</span>
|
||||
</button>
|
||||
<mat-divider></mat-divider>
|
||||
<button
|
||||
mat-menu-item
|
||||
class="red-warning-text"
|
||||
|
@ -14,6 +14,7 @@ import { OperatorService } from 'app/core/core-services/operator.service';
|
||||
import { PromptService } from 'app/core/ui-services/prompt.service';
|
||||
import { StorageService } from 'app/core/core-services/storage.service';
|
||||
import { ViewAssignment, AssignmentPhases } from '../../models/view-assignment';
|
||||
import { AssignmentPdfExportService } from '../../services/assignment-pdf-export.service';
|
||||
|
||||
/**
|
||||
* List view for the assignments
|
||||
@ -55,6 +56,7 @@ export class AssignmentListComponent
|
||||
private promptService: PromptService,
|
||||
public filterService: AssignmentFilterListService,
|
||||
public sortService: AssignmentSortListService,
|
||||
private pdfService: AssignmentPdfExportService,
|
||||
protected route: ActivatedRoute,
|
||||
private router: Router,
|
||||
public operator: OperatorService
|
||||
@ -93,10 +95,12 @@ export class AssignmentListComponent
|
||||
|
||||
/**
|
||||
* Function to download the assignment list
|
||||
* TODO: Not yet implemented
|
||||
*
|
||||
* @param assignments Optional parameter: If given, the chosen list will be exported,
|
||||
* otherwise the whole list of assignments is exported.
|
||||
*/
|
||||
public downloadAssignmentButton(): void {
|
||||
this.raiseError('TODO: assignment download not yet implemented');
|
||||
public downloadAssignmentButton(assignments?: ViewAssignment[]): void {
|
||||
this.pdfService.exportMultipleAssignments(assignments ? assignments : this.repo.getSortedViewModelList());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2,7 +2,7 @@ import { Injectable } from '@angular/core';
|
||||
import { ViewAssignment } from '../models/view-assignment';
|
||||
import { AssignmentPdfService } from './assignment-pdf.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { PdfDocumentService } from 'app/core/ui-services/pdf-document.service';
|
||||
import { PdfDocumentService, PdfError } from 'app/core/ui-services/pdf-document.service';
|
||||
|
||||
/**
|
||||
* Controls PDF export for assignments
|
||||
@ -37,4 +37,78 @@ export class AssignmentPdfExportService {
|
||||
};
|
||||
this.pdfDocumentService.download(doc, filename, metadata);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a pdf document for a list of assignments
|
||||
*
|
||||
* @param assignments The list of assignments that should be exported as pdf.
|
||||
*/
|
||||
public exportMultipleAssignments(assignments: ViewAssignment[]): void {
|
||||
const doc = this.createDocOfMultipleAssignments(assignments);
|
||||
|
||||
const filename = this.translate.instant('Elections');
|
||||
const metaData = {
|
||||
title: filename
|
||||
};
|
||||
this.pdfDocumentService.download(doc, filename, metaData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to generate from a list of assignments a document for the pdf export.
|
||||
*
|
||||
* @param assignments The list of assignments
|
||||
*
|
||||
* @returns doc definition as object
|
||||
*/
|
||||
private createDocOfMultipleAssignments(assignments: ViewAssignment[]): object {
|
||||
const doc = [];
|
||||
const fileList = assignments.map((assignment, index) => {
|
||||
try {
|
||||
const assignmentDocDef = this.assignmentPdfService.assignmentToDocDef(assignment);
|
||||
assignmentDocDef[0].id = `${assignment.id}`;
|
||||
return index < assignments.length - 1
|
||||
? [assignmentDocDef, this.pdfDocumentService.getPageBreak()]
|
||||
: [assignmentDocDef];
|
||||
} catch (error) {
|
||||
const errorText = `${this.translate.instant('Error during PDF creation of assignment:')} ${
|
||||
assignment.title
|
||||
}`;
|
||||
console.error(`${errorText}\nDebugInfo:\n`, error);
|
||||
throw new PdfError(errorText);
|
||||
}
|
||||
});
|
||||
|
||||
if (assignments.length > 1) {
|
||||
doc.push(
|
||||
this.pdfDocumentService.createTitle('assignments_pdf_title'),
|
||||
this.pdfDocumentService.createPreamble('assignments_pdf_preamble'),
|
||||
this.createToc(assignments)
|
||||
);
|
||||
}
|
||||
|
||||
doc.push(fileList);
|
||||
return doc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to create the 'Table of contents'
|
||||
*
|
||||
* @param assignments All the assignments, who should be exported as PDF.
|
||||
*
|
||||
* @returns The toc as
|
||||
*/
|
||||
private createToc(assignments: ViewAssignment[]): Object {
|
||||
const toc = [];
|
||||
const tocTitle = {
|
||||
text: this.translate.instant('Table of contents'),
|
||||
style: 'heading2'
|
||||
};
|
||||
|
||||
const tocBody = assignments.map((assignment, index) =>
|
||||
this.pdfDocumentService.createTocLine(`${index + 1}`, assignment.title, `${assignment.id}`)
|
||||
);
|
||||
toc.push(this.pdfDocumentService.createTocTableDef(tocBody));
|
||||
|
||||
return [tocTitle, toc, this.pdfDocumentService.getPageBreak()];
|
||||
}
|
||||
}
|
||||
|
@ -102,7 +102,7 @@ export class AssignmentPdfService {
|
||||
*/
|
||||
private createDescription(assignment: ViewAssignment): object {
|
||||
if (assignment.description) {
|
||||
const assignmentHtml = this.htmlToPdfService.convertHtml(assignment.description);
|
||||
const descriptionDocDef = this.htmlToPdfService.addPlainText(assignment.description);
|
||||
|
||||
const descriptionText = `${this.translate.instant('Description')}: `;
|
||||
const description = [
|
||||
@ -111,11 +111,7 @@ export class AssignmentPdfService {
|
||||
bold: true,
|
||||
style: 'textItem'
|
||||
},
|
||||
{
|
||||
text: assignmentHtml,
|
||||
style: 'textItem',
|
||||
margin: [10, 0, 0, 0]
|
||||
}
|
||||
descriptionDocDef
|
||||
];
|
||||
return description;
|
||||
} else {
|
||||
|
@ -6,7 +6,7 @@ import { ViewMotion, LineNumberingMode, ChangeRecoMode } from '../models/view-mo
|
||||
import { MotionPdfService, InfoToExport } from './motion-pdf.service';
|
||||
import { ConfigService } from 'app/core/ui-services/config.service';
|
||||
import { ViewCategory } from '../models/view-category';
|
||||
import { PdfError } from 'app/core/ui-services/pdf-document.service';
|
||||
import { PdfError, PdfDocumentService, StyleType } from 'app/core/ui-services/pdf-document.service';
|
||||
|
||||
/**
|
||||
* Service to export a list of motions.
|
||||
@ -20,14 +20,6 @@ import { PdfError } from 'app/core/ui-services/pdf-document.service';
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class MotionPdfCatalogService {
|
||||
/**
|
||||
* Helper to add page breaks to documents
|
||||
*/
|
||||
private pageBreak = {
|
||||
text: '',
|
||||
pageBreak: 'after'
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
@ -38,7 +30,8 @@ export class MotionPdfCatalogService {
|
||||
public constructor(
|
||||
private translate: TranslateService,
|
||||
private configService: ConfigService,
|
||||
private motionPdfService: MotionPdfService
|
||||
private motionPdfService: MotionPdfService,
|
||||
private pdfService: PdfDocumentService
|
||||
) {}
|
||||
|
||||
/**
|
||||
@ -81,7 +74,7 @@ export class MotionPdfCatalogService {
|
||||
motionDocList.push(motionDocDef);
|
||||
|
||||
if (motionIndex < motions.length - 1) {
|
||||
motionDocList.push(this.pageBreak);
|
||||
motionDocList.push(this.pdfService.getPageBreak());
|
||||
}
|
||||
} catch (err) {
|
||||
const errorText = `${this.translate.instant('Error during PDF creation of motion:')} ${
|
||||
@ -94,7 +87,11 @@ export class MotionPdfCatalogService {
|
||||
|
||||
// print extra data (title, preamble, categories, toc) only if there are more than 1 motion
|
||||
if (motions.length > 1) {
|
||||
doc.push(this.createTitle(), this.createPreamble(), this.createToc(motions));
|
||||
doc.push(
|
||||
this.pdfService.createTitle('motions_export_title'),
|
||||
this.pdfService.createPreamble('motions_export_preamble'),
|
||||
this.createToc(motions)
|
||||
);
|
||||
}
|
||||
|
||||
doc = doc.concat(motionDocList);
|
||||
@ -102,37 +99,6 @@ export class MotionPdfCatalogService {
|
||||
return doc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the title for the motion list as pdfmake doc definition
|
||||
*
|
||||
* @returns The motion list title for the PDF document
|
||||
*/
|
||||
private createTitle(): object {
|
||||
const titleText = this.translate.instant(this.configService.instant<string>('motions_export_title'));
|
||||
return {
|
||||
text: titleText,
|
||||
style: 'title'
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the preamble for the motion list as pdfmake doc definition
|
||||
*
|
||||
* @returns The motion list preamble for the PDF document
|
||||
*/
|
||||
private createPreamble(): object {
|
||||
const preambleText = this.configService.instant<string>('motions_export_preamble');
|
||||
|
||||
if (preambleText) {
|
||||
return {
|
||||
text: preambleText,
|
||||
style: 'preamble'
|
||||
};
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the table of contents for the motion book.
|
||||
* Considers sorting by categories and no sorting.
|
||||
@ -177,71 +143,38 @@ export class MotionPdfCatalogService {
|
||||
|
||||
const tocBody = motions
|
||||
.filter(motion => category === motion.category)
|
||||
.map(motion => this.createTocLine(motion, 'tocCategoryEntry'));
|
||||
.map(motion =>
|
||||
this.pdfService.createTocLine(
|
||||
`${motion.identifier}`,
|
||||
motion.title,
|
||||
`${motion.id}`,
|
||||
StyleType.CATEGORY_SECTION
|
||||
)
|
||||
);
|
||||
|
||||
catTocBody.push(this.createTocTableDef(tocBody));
|
||||
catTocBody.push(this.pdfService.createTocTableDef(tocBody, StyleType.CATEGORY_SECTION));
|
||||
}
|
||||
|
||||
// handle those without category
|
||||
const uncatTocBody = motions
|
||||
.filter(motion => !motion.category)
|
||||
.map(motion => this.createTocLine(motion, 'tocEntry'));
|
||||
.map(motion => this.pdfService.createTocLine(`${motion.identifier}`, motion.title, `${motion.id}`));
|
||||
|
||||
// only push this array if there is at least one entry
|
||||
if (uncatTocBody.length > 0) {
|
||||
catTocBody.push(this.createTocTableDef(uncatTocBody));
|
||||
catTocBody.push(this.pdfService.createTocTableDef(uncatTocBody, StyleType.CATEGORY_SECTION));
|
||||
}
|
||||
|
||||
toc.push(catTocBody);
|
||||
} else {
|
||||
// all motions in the same table
|
||||
const tocBody = motions.map(motion => this.createTocLine(motion, 'tocEntry'));
|
||||
toc.push(this.createTocTableDef(tocBody));
|
||||
const tocBody = motions.map(motion =>
|
||||
this.pdfService.createTocLine(`${motion.identifier}`, motion.title, `${motion.id}`)
|
||||
);
|
||||
toc.push(this.pdfService.createTocTableDef(tocBody, StyleType.CATEGORY_SECTION));
|
||||
}
|
||||
|
||||
return [tocTitle, toc, this.pageBreak];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the table definition for the TOC
|
||||
*
|
||||
* @param tocBody the body of the table
|
||||
* @returns The table of contents as doc definition
|
||||
*/
|
||||
private createTocTableDef(tocBody: object): object {
|
||||
return {
|
||||
table: {
|
||||
widths: ['auto', '*', 'auto'],
|
||||
body: tocBody
|
||||
},
|
||||
layout: 'noBorders',
|
||||
style: 'tocCategorySection'
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a line in the TOC as list object
|
||||
*
|
||||
* @param motion motion to make a toc entry to
|
||||
* @param style the desired style
|
||||
*/
|
||||
private createTocLine(motion: ViewMotion, style: string): object {
|
||||
const firstColumn = motion.identifier;
|
||||
return [
|
||||
{
|
||||
text: firstColumn,
|
||||
style: style
|
||||
},
|
||||
{
|
||||
text: motion.title,
|
||||
style: 'tocEntry'
|
||||
},
|
||||
{
|
||||
pageReference: `${motion.id}`,
|
||||
style: 'tocEntry',
|
||||
alignment: 'right'
|
||||
}
|
||||
];
|
||||
return [tocTitle, toc, this.pdfService.getPageBreak()];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -564,7 +564,7 @@ export class MotionPdfService {
|
||||
// columnWidth = '100%';
|
||||
// }
|
||||
|
||||
reason.push(this.addHtml(motion.reason));
|
||||
reason.push(this.htmlToPdfService.addPlainText(motion.reason));
|
||||
|
||||
return reason;
|
||||
} else {
|
||||
@ -707,25 +707,9 @@ export class MotionPdfService {
|
||||
const section = motion.getCommentForSection(viewComment);
|
||||
if (section && section.comment) {
|
||||
result.push({ text: viewComment.name, style: 'heading3', margin: [0, 25, 0, 10] });
|
||||
result.push(this.addHtml(section.comment));
|
||||
result.push(this.htmlToPdfService.addPlainText(section.comment));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to add simple rendered HTML in a new column-stack object.
|
||||
* Prevents all kinds of malformation
|
||||
*
|
||||
* @param content the HTML content
|
||||
*/
|
||||
private addHtml(content: string): object {
|
||||
return {
|
||||
columns: [
|
||||
{
|
||||
stack: this.htmlToPdfService.convertHtml(content, LineNumberingMode.None)
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user