Add more option to motion export

Add optional TOC, date and page numbers to the motion export dialog.
Save and restore the last selected entries in the dialog
This commit is contained in:
Sean Engelhardt 2019-07-03 16:42:08 +02:00 committed by Emanuel Schütze
parent e416231ef4
commit f79aa763c2
8 changed files with 225 additions and 173 deletions

View File

@ -7,6 +7,7 @@ import { TranslateService } from '@ngx-translate/core';
import { ConfigService } from './config.service';
import { HttpService } from '../core-services/http.service';
import { ExportFormData } from 'app/site/motions/modules/motion-list/components/motion-export-dialog/motion-export-dialog.component';
/**
* Enumeration to define possible values for the styling.
@ -177,6 +178,7 @@ export class PdfDocumentService {
private async getStandardPaper(
documentContent: object,
metadata?: object,
exportInfo?: ExportFormData,
imageUrls?: string[],
customMargins?: [number, number, number, number],
landscape?: boolean
@ -197,12 +199,14 @@ export class PdfDocumentService {
fontSize: this.configService.instant('general_export_pdf_fontsize')
},
header: this.getHeader(customMargins ? [customMargins[0], customMargins[2]] : null),
// TODO: option for no footer, wherever this can be defined
footer: (currentPage, pageCount) => {
return this.getFooter(
currentPage,
pageCount,
customMargins ? [customMargins[0], customMargins[2]] : null
customMargins ? [customMargins[0], customMargins[2]] : null,
exportInfo
);
},
info: metadata,
@ -337,14 +341,35 @@ export class PdfDocumentService {
* @param lrMargin optionally overriding the margins
* @returns the footer doc definition
*/
private getFooter(currentPage: number, pageCount: number, lrMargin?: [number, number]): object {
private getFooter(
currentPage: number,
pageCount: number,
lrMargin?: [number, number],
exportInfo?: ExportFormData
): object {
const columns = [];
const showPage = exportInfo && exportInfo.pdfOptions ? exportInfo.pdfOptions.includes('page') : true;
const showDate = exportInfo && exportInfo.pdfOptions ? exportInfo.pdfOptions.includes('date') : false;
let logoContainerWidth: string;
let pageNumberPosition: string;
let logoContainerSize: number[];
const logoFooterLeftUrl = this.configService.instant<any>('logo_pdf_footer_L').path;
const logoFooterRightUrl = this.configService.instant<any>('logo_pdf_footer_R').path;
let footerText = '';
if (showPage) {
footerText += `${currentPage} / ${pageCount}`;
if (showDate) {
footerText += '\n';
}
}
if (showDate) {
footerText += `(${this.translate.instant('Created on')}: ${new Date().toLocaleDateString(
this.translate.currentLang
)})`;
}
// if there is a single logo, give it a lot of space
if (logoFooterLeftUrl && logoFooterRightUrl) {
logoContainerWidth = '20%';
@ -377,7 +402,7 @@ export class PdfDocumentService {
// add the page number
columns.push({
text: `${currentPage} / ${pageCount}`,
text: footerText,
style: 'footerPageNumber',
alignment: pageNumberPosition
});
@ -407,8 +432,8 @@ export class PdfDocumentService {
* @param filename the name of the file to use
* @param metadata
*/
public download(docDefinition: object, filename: string, metadata?: object): void {
this.getStandardPaper(docDefinition, metadata).then(doc => {
public download(docDefinition: object, filename: string, metadata?: object, exportInfo?: ExportFormData): void {
this.getStandardPaper(docDefinition, metadata, exportInfo).then(doc => {
this.createPdf(doc, filename);
});
}
@ -421,7 +446,7 @@ export class PdfDocumentService {
* @param metadata
*/
public downloadLandscape(docDefinition: object, filename: string, metadata?: object): void {
this.getStandardPaper(docDefinition, metadata, null, [50, 80, 50, 75], true).then(doc => {
this.getStandardPaper(docDefinition, metadata, null, null, [50, 80, 50, 75], true).then(doc => {
this.createPdf(doc, filename);
});
}

View File

@ -1389,7 +1389,11 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
* Click handler for the pdf button
*/
public onDownloadPdf(): void {
this.pdfExport.exportSingleMotion(this.motion, this.lnMode, this.crMode);
this.pdfExport.exportSingleMotion(this.motion, {
lnMode: this.lnMode,
crMode: this.crMode,
comments: this.motion.commentSectionIds
});
}
/**

View File

@ -6,9 +6,9 @@
<div>
<p class="toggle-group-head" translate>Format</p>
<mat-button-toggle-group class="smaller-buttons" formControlName="format">
<mat-button-toggle value="pdf">PDF</mat-button-toggle>
<mat-button-toggle value="csv">CSV</mat-button-toggle>
<mat-button-toggle value="xlsx">XLSX</mat-button-toggle>
<mat-button-toggle [value]="fileFormat.PDF">PDF</mat-button-toggle>
<mat-button-toggle [value]="fileFormat.CSV">CSV</mat-button-toggle>
<mat-button-toggle [value]="fileFormat.XLSX">XLSX</mat-button-toggle>
</mat-button-toggle-group>
</div>
@ -54,7 +54,16 @@
</mat-button-toggle>
</mat-button-toggle-group>
</div>
<div *ngIf="commentsToExport.length && exportForm.get('format').value === 'pdf'">
<div>
<p class="toggle-group-head" translate>PDF options</p>
<mat-button-toggle-group class="smaller-buttons" multiple formControlName="pdfOptions">
<mat-button-toggle value="toc"> <span translate>Table of Contents</span> </mat-button-toggle>
<mat-button-toggle value="page"> <span translate>Page number</span> </mat-button-toggle>
<mat-button-toggle value="date"> <span translate>Current date</span> </mat-button-toggle>
</mat-button-toggle-group>
</div>
<div *ngIf="commentsToExport.length">
<p class="toggle-group-head" translate>Comments</p>
<mat-button-toggle-group class="smaller-buttons" multiple formControlName="comments">
<mat-button-toggle *ngFor="let comment of commentsToExport" [value]="comment.id">

View File

@ -9,6 +9,30 @@ import { LineNumberingMode, ChangeRecoMode } from 'app/site/motions/models/view-
import { InfoToExport } from 'app/site/motions/services/motion-pdf.service';
import { ViewMotionCommentSection } from 'app/site/motions/models/view-motion-comment-section';
import { motionImportExportHeaderOrder, noMetaData } from 'app/site/motions/motion-import-export-order';
import { StorageService } from 'app/core/core-services/storage.service';
import { auditTime } from 'rxjs/operators';
/**
* Determine the possible file format
*/
export enum FileFormat {
PDF = 1,
CSV,
XLSX
}
/**
* Shape the structure of the dialog data
*/
export interface ExportFormData {
format?: FileFormat;
lnMode?: LineNumberingMode;
crMode?: ChangeRecoMode;
content?: string[];
metaInfo?: InfoToExport[];
pdfOptions?: string[];
comments?: number[];
}
/**
* Dialog component to determine exporting.
@ -29,56 +53,37 @@ export class MotionExportDialogComponent implements OnInit {
*/
public crMode = ChangeRecoMode;
/**
* to use the format in the template
*/
public fileFormat = FileFormat;
/**
* The form that contains the export information.
*/
public exportForm: FormGroup;
/**
* determine the default format to export
* The default export values in contrast to the restored values
*/
private defaultExportFormat = 'pdf';
/**
* Determine the default content to export.
*/
private defaultContentToExport = ['text', 'reason'];
private defaults: ExportFormData = {
format: FileFormat.PDF,
content: ['text', 'reason'],
pdfOptions: ['toc', 'page'],
metaInfo: ['submitters', 'state', 'recommendation', 'category', 'origin', 'tags', 'motion_block', 'polls', 'id']
};
/**
* Determine the export order of the meta data
*/
public metaInfoExportOrder: string[];
/**
* Determine the default meta info to export.
*/
private defaultInfoToExport: InfoToExport[] = [
'submitters',
'state',
'recommendation',
'category',
'origin',
'tags',
'motion_block',
'polls',
'id'
];
/**
* @returns a list of availavble commentSections
*/
public get commentsToExport(): ViewMotionCommentSection[] {
return this.commentRepo.getSortedViewModelList();
}
/**
* Hold the default lnMode. Will be set by the constructor.
*/
private defaultLnMode: LineNumberingMode;
/**
* Hold the default crMode. Will be set by the constructor.
*/
private defaultCrMode: ChangeRecoMode;
/**
* To deactivate the export-as-diff button
@ -87,7 +92,7 @@ export class MotionExportDialogComponent implements OnInit {
public diffVersionButton: MatButtonToggle;
/**
* To deactivate the export-as-diff button
* To deactivate the voting result button
*/
@ViewChild('votingResultButton', { static: true })
public votingResultButton: MatButtonToggle;
@ -107,10 +112,11 @@ export class MotionExportDialogComponent implements OnInit {
public formBuilder: FormBuilder,
public dialogRef: MatDialogRef<MotionExportDialogComponent>,
public configService: ConfigService,
public commentRepo: MotionCommentSectionRepositoryService
public commentRepo: MotionCommentSectionRepositoryService,
private store: StorageService
) {
this.defaultLnMode = this.configService.instant('motions_default_line_numbering');
this.defaultCrMode = this.configService.instant('motions_recommendation_text_mode');
this.defaults.lnMode = this.configService.instant('motions_default_line_numbering');
this.defaults.crMode = this.configService.instant('motions_recommendation_text_mode');
// Get the export order, exclude everything that does not count as meta-data
this.metaInfoExportOrder = motionImportExportHeaderOrder.filter(metaData => {
return !noMetaData.some(noMeta => metaData === noMeta);
@ -123,67 +129,83 @@ export class MotionExportDialogComponent implements OnInit {
* Observes the form for changes to react dynamically
*/
public ngOnInit(): void {
this.exportForm.get('format').valueChanges.subscribe((value: string) => {
// disable content for xslx
if (value === 'xlsx') {
// disable the content selection
this.exportForm.get('content').disable();
// remove the selection of "content"
this.exportForm.get('content').setValue(null);
} else {
this.exportForm.get('content').enable();
}
this.exportForm.valueChanges.pipe(auditTime(500)).subscribe((value: ExportFormData) => {
this.store.set('motion_export_selection', value);
});
if (value === 'csv' || value === 'xlsx') {
// disable and deselect "lnMode"
this.exportForm.get('lnMode').setValue(this.lnMode.None);
this.exportForm.get('lnMode').disable();
this.exportForm.get('format').valueChanges.subscribe((value: FileFormat) => this.onFormatChange(value));
}
// disable and deselect "Change Reco Mode"
// TODO: The current implementation of the motion csv export does not consider anything else than
// the "normal" motion.text, therefore this is disabled for now
this.exportForm.get('crMode').setValue(this.crMode.Original);
this.exportForm.get('crMode').disable();
/**
* React to changes on the file format
* @param format
*/
private onFormatChange(format: FileFormat): void {
// XLSX cannot have "content"
if (format === FileFormat.XLSX) {
this.disableControl('content');
} else {
this.enableControl('content');
}
this.exportForm.get('comments').disable();
if (format === FileFormat.CSV || format === FileFormat.XLSX) {
this.disableControl('lnMode');
this.disableControl('crMode');
this.disableControl('comments');
this.disableControl('pdfOptions');
// remove the selection of "Diff Version" and set it to default or original
// TODO: Use this over the disable block logic above when the export service supports more than
// just the normal motion text
// if (this.exportForm.get('crMode').value === this.crMode.Diff) {
// if (this.defaultCrMode === this.crMode.Diff) {
// this.exportForm.get('crMode').setValue(this.crMode.Original);
// } else {
// this.exportForm.get('crMode').setValue(this.defaultCrMode);
// }
// }
// remove the selection of "votingResult"
let metaInfoVal: string[] = this.exportForm.get('metaInfo').value;
// remove the selection of "votingResult"
let metaInfoVal: string[] = this.exportForm.get('metaInfo').value;
if (metaInfoVal) {
metaInfoVal = metaInfoVal.filter(info => {
return info !== 'polls';
});
this.exportForm.get('metaInfo').setValue(metaInfoVal);
// disable "Diff Version" and "Voting Result"
this.votingResultButton.disabled = true;
// TODO: CSV Issues
// this.diffVersionButton.disabled = true;
} else if (value === 'pdf') {
this.exportForm.get('comments').enable();
this.exportForm.get('lnMode').enable();
this.exportForm.get('lnMode').setValue(this.defaultLnMode);
// TODO: Temporarily necessary until CSV has been fixed
this.exportForm.get('crMode').enable();
this.exportForm.get('crMode').setValue(this.defaultCrMode);
// enable "Diff Version" and "Voting Result"
this.votingResultButton.disabled = false;
// TODO: Temporarily disabled. Will be required after CSV fixes
// this.diffVersionButton.disabled = false;
}
});
this.votingResultButton.disabled = true;
}
if (format === FileFormat.PDF) {
this.enableControl('lnMode');
this.enableControl('crMode');
this.enableControl('comments');
this.enableControl('pdfOptions');
this.votingResultButton.disabled = false;
}
}
/**
* Helper function to easier enable a control
* @param name
*/
private enableControl(name: string): void {
this.exportForm.get(name).enable();
}
/**
* Helper function to easier disable a control
*
* @param name
*/
private disableControl(name: string): void {
this.exportForm.get(name).disable();
this.exportForm.get(name).setValue(this.getOffState(name));
}
/**
* Determine what "off means in certain states"
*
* @param control
*/
private getOffState(control: string): string | null {
switch (control) {
case 'lnMode':
return this.lnMode.None;
case 'crMode':
return this.crMode.Original;
default:
return null;
}
}
/**
@ -191,13 +213,23 @@ export class MotionExportDialogComponent implements OnInit {
*/
public createForm(): void {
this.exportForm = this.formBuilder.group({
format: [this.defaultExportFormat],
lnMode: [this.defaultLnMode],
crMode: [this.defaultCrMode],
content: [this.defaultContentToExport],
metaInfo: [this.defaultInfoToExport],
format: [],
lnMode: [],
crMode: [],
content: [],
metaInfo: [],
pdfOptions: [],
comments: []
});
// restore selection or set default
this.store.get<ExportFormData>('motion_export_selection').then(restored => {
if (!!restored) {
this.exportForm.patchValue(restored);
} else {
this.exportForm.patchValue(this.defaults);
}
});
}
/**

View File

@ -15,7 +15,11 @@ import { MotionRepositoryService } from 'app/core/repositories/motions/motion-re
import { TagRepositoryService } from 'app/core/repositories/tags/tag-repository.service';
import { ViewTag } from 'app/site/tags/models/view-tag';
import { WorkflowRepositoryService } from 'app/core/repositories/motions/workflow-repository.service';
import { MotionExportDialogComponent } from '../motion-export-dialog/motion-export-dialog.component';
import {
MotionExportDialogComponent,
FileFormat,
ExportFormData
} from '../motion-export-dialog/motion-export-dialog.component';
import { ViewMotion, LineNumberingMode, ChangeRecoMode } from 'app/site/motions/models/view-motion';
import { ViewWorkflow } from 'app/site/motions/models/view-workflow';
import { ViewCategory } from 'app/site/motions/models/view-category';
@ -302,19 +306,12 @@ export class MotionListComponent extends BaseListViewComponent<ViewMotion> imple
data: this.dataSource
});
exportDialogRef.afterClosed().subscribe((result: any) => {
if (result && result.format) {
exportDialogRef.afterClosed().subscribe((exportInfo: ExportFormData) => {
if (exportInfo && exportInfo.format) {
const data = this.isMultiSelect ? this.selectedRows : this.dataSource.source;
if (result.format === 'pdf') {
if (exportInfo.format === FileFormat.PDF) {
try {
this.pdfExport.exportMotionCatalog(
data,
result.lnMode,
result.crMode,
result.content,
result.metaInfo,
result.comments
);
this.pdfExport.exportMotionCatalog(data, exportInfo);
} catch (err) {
if (err instanceof PdfError) {
this.raiseError(err.message);
@ -322,10 +319,14 @@ export class MotionListComponent extends BaseListViewComponent<ViewMotion> imple
throw err;
}
}
} else if (result.format === 'csv') {
this.motionCsvExport.exportMotionList(data, [...result.content, ...result.metaInfo], result.crMode);
} else if (result.format === 'xlsx') {
this.motionXlsxExport.exportMotionList(data, result.metaInfo);
} else if (exportInfo.format === FileFormat.CSV) {
this.motionCsvExport.exportMotionList(
data,
[...exportInfo.content, ...exportInfo.metaInfo],
exportInfo.crMode
);
} else if (exportInfo.format === FileFormat.XLSX) {
this.motionXlsxExport.exportMotionList(data, exportInfo.metaInfo);
}
}
});
@ -399,11 +400,12 @@ export class MotionListComponent extends BaseListViewComponent<ViewMotion> imple
* Directly export all motions as pdf, using the current default config settings
*/
public directPdfExport(): void {
this.pdfExport.exportMotionCatalog(
this.dataSource.source,
this.configService.instant<string>('motions_default_line_numbering') as LineNumberingMode,
this.configService.instant<string>('motions_recommendation_text_mode') as ChangeRecoMode
);
const lnMode = this.configService.instant<string>('motions_default_line_numbering') as LineNumberingMode;
const crMode = this.configService.instant<string>('motions_recommendation_text_mode') as ChangeRecoMode;
this.pdfExport.exportMotionCatalog(this.dataSource.source, {
lnMode: lnMode,
crMode: crMode
});
}
/**

View File

@ -5,11 +5,12 @@ 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, InfoToExport } from './motion-pdf.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, LineNumberingMode, ChangeRecoMode } from '../models/view-motion';
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.
@ -55,27 +56,13 @@ export class MotionPdfCatalogService {
* @param commentsToExport
* @returns pdfmake doc definition as object
*/
public motionListToDocDef(
motions: ViewMotion[],
lnMode?: LineNumberingMode,
crMode?: ChangeRecoMode,
contentToExport?: string[],
infoToExport?: InfoToExport[],
commentsToExport?: number[]
): 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],
lnMode,
crMode,
contentToExport,
infoToExport,
commentsToExport
);
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}`;
@ -95,7 +82,7 @@ export class MotionPdfCatalogService {
}
// print extra data (title, preamble, categories, toc) only if there are more than 1 motion
if (motions.length > 1) {
if (motions.length > 1 && (!exportInfo.pdfOptions || exportInfo.pdfOptions.includes('toc'))) {
doc.push(
this.pdfService.createTitle('motions_export_title'),
this.pdfService.createPreamble('motions_export_preamble'),

View File

@ -2,13 +2,14 @@ import { Injectable } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { MotionPdfService, InfoToExport } from './motion-pdf.service';
import { MotionPdfService } from './motion-pdf.service';
import { PdfDocumentService } from 'app/core/ui-services/pdf-document.service';
import { ViewMotion, LineNumberingMode, ChangeRecoMode } from '../models/view-motion';
import { ViewMotion } from '../models/view-motion';
import { ConfigService } from 'app/core/ui-services/config.service';
import { MotionPdfCatalogService } from './motion-pdf-catalog.service';
import { PersonalNoteContent } from 'app/shared/models/users/personal-note';
import { ViewMotionCommentSection } from '../models/view-motion-comment-section';
import { ExportFormData } from '../modules/motion-list/components/motion-export-dialog/motion-export-dialog.component';
/**
* Export service to handle various kind of exporting necessities.
@ -40,8 +41,8 @@ export class MotionPdfExportService {
* @param lnMode the desired line numbering mode
* @param crMode the desired change recomendation mode
*/
public exportSingleMotion(motion: ViewMotion, lnMode?: LineNumberingMode, crMode?: ChangeRecoMode): void {
const doc = this.motionPdfService.motionToDocDef(motion, lnMode, crMode);
public exportSingleMotion(motion: ViewMotion, exportInfo?: ExportFormData): void {
const doc = this.motionPdfService.motionToDocDef(motion, exportInfo);
const filename = `${this.translate.instant('Motion')} ${motion.identifierOrTitle}`;
const metadata = {
title: filename
@ -59,27 +60,13 @@ export class MotionPdfExportService {
* @param infoToExport Determine the meta info to export
* @param commentsToExport Comments (by id) to export
*/
public exportMotionCatalog(
motions: ViewMotion[],
lnMode?: LineNumberingMode,
crMode?: ChangeRecoMode,
contentToExport?: string[],
infoToExport?: InfoToExport[],
commentsToExport?: number[]
): void {
const doc = this.pdfCatalogService.motionListToDocDef(
motions,
lnMode,
crMode,
contentToExport,
infoToExport,
commentsToExport
);
public exportMotionCatalog(motions: ViewMotion[], exportInfo: ExportFormData): void {
const doc = this.pdfCatalogService.motionListToDocDef(motions, exportInfo);
const filename = this.translate.instant(this.configService.instant<string>('motions_export_title'));
const metadata = {
title: filename
};
this.pdfDocumentService.download(doc, filename, metadata);
this.pdfDocumentService.download(doc, filename, metadata, exportInfo);
}
/**

View File

@ -17,6 +17,7 @@ import { PdfDocumentService } from 'app/core/ui-services/pdf-document.service';
import { ViewMotionAmendedParagraph } from '../models/view-motion-amended-paragraph';
import { ViewMotionChangeRecommendation } from '../models/view-motion-change-recommendation';
import { ViewUnifiedChange, ViewUnifiedChangeType } from 'app/shared/models/motions/view-unified-change';
import { ExportFormData } from '../modules/motion-list/components/motion-export-dialog/motion-export-dialog.component';
/**
* Type declaring which strings are valid options for metainfos to be exported into a pdf
@ -91,14 +92,13 @@ export class MotionPdfService {
* @param commentsToExport comments to chose for export. If 'allcomments' is set in infoToExport, this selection will be ignored and all comments exported
* @returns doc def for the motion
*/
public motionToDocDef(
motion: ViewMotion,
lnMode?: LineNumberingMode,
crMode?: ChangeRecoMode,
contentToExport?: string[],
infoToExport?: InfoToExport[],
commentsToExport?: number[]
): object {
public motionToDocDef(motion: ViewMotion, exportInfo?: ExportFormData): object {
let lnMode = exportInfo && exportInfo.lnMode ? exportInfo.lnMode : null;
let crMode = exportInfo && exportInfo.crMode ? exportInfo.crMode : null;
const infoToExport = exportInfo ? exportInfo.metaInfo : null;
const contentToExport = exportInfo ? exportInfo.content : null;
let commentsToExport = exportInfo ? exportInfo.comments : null;
// get the line length from the config
const lineLength = this.configService.instant<number>('motions_line_length');
// whether to append checkboxes to follow the recommendation or not
@ -178,7 +178,13 @@ export class MotionPdfService {
const changedTitle = this.changeRecoRepo.getTitleWithChanges(motion.title, titleChange, crMode);
const identifier = motion.identifier ? ' ' + motion.identifier : '';
const title = `${this.translate.instant('Motion')} ${identifier}: ${changedTitle}`;
const pageSize = this.configService.instant('general_export_pdf_pagesize');
let title = '';
if (pageSize === 'A4') {
title += `${this.translate.instant('Motion')} `;
}
title += `${identifier}: ${changedTitle}`;
return {
text: title,