Merge pull request #4182 from tsiegleauq/os3-pdf-motion-catalog
Export a list of motions
This commit is contained in:
commit
c9a9f8d809
@ -93,7 +93,7 @@ export class HtmlToPdfService {
|
||||
const parser = new DOMParser();
|
||||
const parsedHtml = parser.parseFromString(htmlText, 'text/html');
|
||||
// Since the spread operator did not work for HTMLCollection, use Array.from
|
||||
const htmlArray = Array.from(parsedHtml.body.children);
|
||||
const htmlArray = Array.from(parsedHtml.body.childNodes);
|
||||
|
||||
// Parse the children of the current HTML element
|
||||
for (const child of htmlArray) {
|
||||
@ -219,6 +219,7 @@ export class HtmlToPdfService {
|
||||
}
|
||||
} else {
|
||||
const children = this.parseChildren(element, styles);
|
||||
|
||||
newParagraph = {
|
||||
...this.create('text'),
|
||||
...this.computeStyle(styles)
|
||||
|
@ -308,13 +308,18 @@ export class PdfDocumentService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads a pdf. Does not seem to work.
|
||||
* Downloads a pdf.
|
||||
*
|
||||
* @param docDefinition the structure of the PDF document
|
||||
*/
|
||||
public download(docDefinition: object, filename: string, metadata?: object): void {
|
||||
this.getStandardPaper(docDefinition, metadata).then(doc => {
|
||||
pdfMake.createPdf(doc).getBlob(blob => saveAs(blob, `${filename}.pdf`, { autoBOM: true }));
|
||||
public async download(docDefinition: object, filename: string, metadata?: object): Promise<void> {
|
||||
const doc = await this.getStandardPaper(docDefinition, metadata);
|
||||
await new Promise<boolean>(resolve => {
|
||||
const pdf = pdfMake.createPdf(doc);
|
||||
pdf.getBlob(blob => {
|
||||
saveAs(blob, `${filename}.pdf`, { autoBOM: true });
|
||||
resolve(true);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -347,6 +352,9 @@ export class PdfDocumentService {
|
||||
margin: [0, -20, 0, 20],
|
||||
color: 'grey'
|
||||
},
|
||||
preamble: {
|
||||
margin: [0, 0, 0, 10]
|
||||
},
|
||||
headerText: {
|
||||
fontSize: 10,
|
||||
margin: [0, 10, 0, 0]
|
||||
@ -362,10 +370,33 @@ export class PdfDocumentService {
|
||||
smallText: {
|
||||
fontSize: 8
|
||||
},
|
||||
heading2: {
|
||||
fontSize: 14,
|
||||
margin: [0, 0, 0, 10],
|
||||
bold: true
|
||||
},
|
||||
heading3: {
|
||||
fontSize: 12,
|
||||
margin: [0, 10, 0, 0],
|
||||
bold: true
|
||||
},
|
||||
tocEntry: {
|
||||
fontSize: 12,
|
||||
margin: [0, 0, 0, 0],
|
||||
bold: false
|
||||
},
|
||||
tocCategoryEntry: {
|
||||
fontSize: 12,
|
||||
margin: [10, 0, 0, 0],
|
||||
bold: false
|
||||
},
|
||||
tocCategoryTitle: {
|
||||
fontSize: 12,
|
||||
margin: [0, 0, 0, 4],
|
||||
bold: true
|
||||
},
|
||||
tocCategorySection: {
|
||||
margin: [0, 0, 0, 10]
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -178,6 +178,10 @@
|
||||
<mat-icon>archive</mat-icon>
|
||||
<span translate>Export as CSV</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="onExportAsPdf()">
|
||||
<mat-icon>picture_as_pdf</mat-icon>
|
||||
<span translate>Export all as PDF</span>
|
||||
</button>
|
||||
<button mat-menu-item routerLink="import">
|
||||
<mat-icon>save_alt</mat-icon>
|
||||
<span translate>Import</span><span> ...</span>
|
||||
|
@ -22,6 +22,7 @@ import { ViewTag } from 'app/site/tags/models/view-tag';
|
||||
import { ViewWorkflow } from '../../models/view-workflow';
|
||||
import { WorkflowState } from '../../../../shared/models/motions/workflow-state';
|
||||
import { WorkflowRepositoryService } from '../../services/workflow-repository.service';
|
||||
import { MotionPdfExportService } from '../../services/motion-pdf-export.service';
|
||||
|
||||
/**
|
||||
* Component that displays all the motions in a Table using DataSource.
|
||||
@ -74,6 +75,7 @@ export class MotionListComponent extends ListViewBaseComponent<ViewMotion> imple
|
||||
* @param categoryRepo: Repo for categories. Used to define filters
|
||||
* @param workflowRepo: Repo for Workflows. Used to define filters
|
||||
* @param motionCsvExport
|
||||
* @param pdfExport To export motions as PDF
|
||||
* @param multiselectService Service for the multiSelect actions
|
||||
* @param userRepo
|
||||
* @param sortService
|
||||
@ -93,6 +95,7 @@ export class MotionListComponent extends ListViewBaseComponent<ViewMotion> imple
|
||||
private workflowRepo: WorkflowRepositoryService,
|
||||
private motionRepo: MotionRepositoryService,
|
||||
private motionCsvExport: MotionCsvExportService,
|
||||
private pdfExport: MotionPdfExportService,
|
||||
public multiselectService: MotionMultiselectService,
|
||||
public sortService: MotionSortListService,
|
||||
public filterService: MotionFilterListService,
|
||||
@ -192,6 +195,13 @@ export class MotionListComponent extends ListViewBaseComponent<ViewMotion> imple
|
||||
this.motionCsvExport.exportMotionList(this.dataSource.data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports motions as PDF.
|
||||
*/
|
||||
public onExportAsPdf(): void {
|
||||
this.pdfExport.exportMotionCatalog(this.dataSource.data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns current definitions for the listView table
|
||||
*/
|
||||
|
@ -0,0 +1,17 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { MotionPdfCatalogService } from './motion-pdf-catalog.service';
|
||||
import { E2EImportsModule } from 'e2e-imports.module';
|
||||
|
||||
describe('MotionPdfCatalogService', () => {
|
||||
beforeEach(() =>
|
||||
TestBed.configureTestingModule({
|
||||
imports: [E2EImportsModule]
|
||||
})
|
||||
);
|
||||
|
||||
it('should be created', () => {
|
||||
const service: MotionPdfCatalogService = TestBed.get(MotionPdfCatalogService);
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,240 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
import { ViewMotion } from '../models/view-motion';
|
||||
import { MotionPdfService } from './motion-pdf.service';
|
||||
import { ConfigService } from 'app/core/services/config.service';
|
||||
import { Category } from 'app/shared/models/motions/category';
|
||||
|
||||
/**
|
||||
* Service to export a list of motions.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const docDef = this.motionPdfCatalogService.motionListToDocDef(myListOfViewMotions);
|
||||
* ```
|
||||
*/
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class MotionPdfCatalogService {
|
||||
/**
|
||||
* Helper to add page breaks to documents
|
||||
*/
|
||||
private pageBreak = {
|
||||
text: '',
|
||||
pageBreak: 'after'
|
||||
};
|
||||
|
||||
/**
|
||||
* 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
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @returns pdfmake doc definition as object
|
||||
*/
|
||||
public motionListToDocDef(motions: ViewMotion[]): object {
|
||||
let doc = [];
|
||||
const motionDocList = [];
|
||||
|
||||
for (let motionIndex = 0; motionIndex < motions.length; ++motionIndex) {
|
||||
const motionDocDef: any = this.motionPdfService.motionToDocDef(motions[motionIndex]);
|
||||
|
||||
// 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.pageBreak);
|
||||
}
|
||||
}
|
||||
|
||||
// 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 = doc.concat(motionDocList);
|
||||
|
||||
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.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.
|
||||
*
|
||||
* @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: Category[] = this.getUniqueCategories(motions);
|
||||
|
||||
// Create the toc title
|
||||
const tocTitle = {
|
||||
text: this.translate.instant('Table of contents'),
|
||||
style: 'heading2'
|
||||
};
|
||||
|
||||
if (!sorting) {
|
||||
sorting = this.configService.instant<string>('motions_export_category_sorting');
|
||||
}
|
||||
const exportCategory = sorting === 'identifier' || sorting === 'prefix';
|
||||
|
||||
if (exportCategory && categories) {
|
||||
const catTocBody = [];
|
||||
for (const category of categories) {
|
||||
// push the name of the category
|
||||
// make a table for correct alignment
|
||||
catTocBody.push({
|
||||
table: {
|
||||
body: [
|
||||
[
|
||||
{
|
||||
text: category.prefix + ' - ' + category.name,
|
||||
style: 'tocCategoryTitle'
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
layout: 'noBorders'
|
||||
});
|
||||
|
||||
const tocBody = motions
|
||||
.filter(motion => category === motion.category)
|
||||
.map(motion => this.createTocLine(motion, 'tocCategoryEntry'));
|
||||
|
||||
catTocBody.push(this.createTocTableDef(tocBody));
|
||||
}
|
||||
|
||||
// handle those without category
|
||||
const uncatTocBody = motions
|
||||
.filter(motion => !motion.category)
|
||||
.map(motion => this.createTocLine(motion, 'tocEntry'));
|
||||
|
||||
// only push this array if there is at least one entry
|
||||
if (uncatTocBody.length > 0) {
|
||||
catTocBody.push(this.createTocTableDef(uncatTocBody));
|
||||
}
|
||||
|
||||
toc.push(catTocBody);
|
||||
} else {
|
||||
// all motions in the same table
|
||||
const tocBody = motions.map(motion => this.createTocLine(motion, 'tocEntry'));
|
||||
toc.push(this.createTocTableDef(tocBody));
|
||||
}
|
||||
|
||||
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'
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the used categories from the given motion list.
|
||||
*
|
||||
* @param motions the list of motions
|
||||
* @returns Unique list of categories
|
||||
*/
|
||||
private getUniqueCategories(motions: ViewMotion[]): Category[] {
|
||||
const categories: Category[] = motions
|
||||
// remove motions without category
|
||||
.filter(motion => (motion.category ? motion : null))
|
||||
// map motions their categories
|
||||
.map(motion => motion.category)
|
||||
// remove redundancies
|
||||
.filter(
|
||||
(category, index, self) =>
|
||||
index ===
|
||||
self.findIndex(compare => compare.prefix === category.prefix && compare.name === category.name)
|
||||
);
|
||||
|
||||
return categories;
|
||||
}
|
||||
}
|
@ -5,6 +5,8 @@ import { TranslateService } from '@ngx-translate/core';
|
||||
import { MotionPdfService } from './motion-pdf.service';
|
||||
import { PdfDocumentService } from 'app/core/services/pdf-document.service';
|
||||
import { ViewMotion, LineNumberingMode, ChangeRecoMode } from '../models/view-motion';
|
||||
import { ConfigService } from 'app/core/services/config.service';
|
||||
import { MotionPdfCatalogService } from './motion-pdf-catalog.service';
|
||||
|
||||
/**
|
||||
* Export service to handle various kind of exporting necessities.
|
||||
@ -17,12 +19,15 @@ export class MotionPdfExportService {
|
||||
* Constructor
|
||||
*
|
||||
* @param translate handle translations
|
||||
* @param configService Read out Config variables
|
||||
* @param motionPdfService Converting actual motions to PDF
|
||||
* @param pdfDocumentService Actual pdfmake functions and global doc definitions
|
||||
*/
|
||||
public constructor(
|
||||
private translate: TranslateService,
|
||||
private configService: ConfigService,
|
||||
private motionPdfService: MotionPdfService,
|
||||
private pdfCatalogService: MotionPdfCatalogService,
|
||||
private pdfDocumentService: PdfDocumentService
|
||||
) {}
|
||||
|
||||
@ -41,4 +46,18 @@ export class MotionPdfExportService {
|
||||
};
|
||||
this.pdfDocumentService.download(doc, filename, metadata);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports multiple motions to a collection of PDFs
|
||||
*
|
||||
* @param motions
|
||||
*/
|
||||
public exportMotionCatalog(motions: ViewMotion[]): void {
|
||||
const doc = this.pdfCatalogService.motionListToDocDef(motions);
|
||||
const filename = this.translate.instant(this.configService.instant<string>('motions_export_title'));
|
||||
const metadata = {
|
||||
title: filename
|
||||
};
|
||||
this.pdfDocumentService.download(doc, filename, metadata);
|
||||
}
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ export class MotionPdfService {
|
||||
|
||||
// determine the default crMode if not explicitly given
|
||||
if (!crMode) {
|
||||
lnMode = this.configService.instant('motions_recommendation_text_mode');
|
||||
crMode = this.configService.instant('motions_recommendation_text_mode');
|
||||
}
|
||||
|
||||
const title = this.createTitle(motion);
|
||||
|
@ -20,7 +20,7 @@ describe('FullscreenProjectorComponent', () => {
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
fit('should create', () => {
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user