diff --git a/client/src/app/core/pdf-services/pdf-document.service.ts b/client/src/app/core/pdf-services/pdf-document.service.ts index 2894e34e3..6a41a941a 100644 --- a/client/src/app/core/pdf-services/pdf-document.service.ts +++ b/client/src/app/core/pdf-services/pdf-document.service.ts @@ -6,7 +6,7 @@ import { TranslateService } from '@ngx-translate/core'; import { saveAs } from 'file-saver'; import { ProgressSnackBarComponent } from 'app/shared/components/progress-snack-bar/progress-snack-bar.component'; -import { ExportFormData } from 'app/site/motions/modules/motion-list/components/motion-export-dialog/motion-export-dialog.component'; +import { MotionExportInfo } from 'app/site/motions/services/motion-export.service'; import { ConfigService } from '../ui-services/config.service'; import { HttpService } from '../core-services/http.service'; import { ProgressService } from '../ui-services/progress.service'; @@ -163,7 +163,7 @@ export class PdfDocumentService { private async getStandardPaper( documentContent: object, metadata?: object, - exportInfo?: ExportFormData, + exportInfo?: MotionExportInfo, imageUrls?: string[], customMargins?: [number, number, number, number], landscape?: boolean @@ -308,7 +308,7 @@ export class PdfDocumentService { * @param lrMargin optionally overriding the margins * @returns the footer doc definition */ - private getFooter(lrMargin?: [number, number], exportInfo?: ExportFormData): object { + private getFooter(lrMargin?: [number, number], exportInfo?: MotionExportInfo): object { const columns = []; const showPageNr = exportInfo && exportInfo.pdfOptions ? exportInfo.pdfOptions.includes('page') : true; const showDate = exportInfo && exportInfo.pdfOptions ? exportInfo.pdfOptions.includes('date') : false; @@ -399,7 +399,7 @@ export class PdfDocumentService { * @param filename the name of the file to use * @param metadata */ - public download(docDefinition: object, filename: string, metadata?: object, exportInfo?: ExportFormData): void { + public download(docDefinition: object, filename: string, metadata?: object, exportInfo?: MotionExportInfo): void { this.getStandardPaper(docDefinition, metadata, exportInfo).then(doc => { this.createPdf(doc, filename); }); diff --git a/client/src/app/core/repositories/motions/motion-repository.service.ts b/client/src/app/core/repositories/motions/motion-repository.service.ts index 4bb21e9ef..99598e1c0 100644 --- a/client/src/app/core/repositories/motions/motion-repository.service.ts +++ b/client/src/app/core/repositories/motions/motion-repository.service.ts @@ -497,6 +497,13 @@ export class MotionRepositoryService extends BaseIsAgendaItemAndListOfSpeakersCo ); } + /** + * @returns all amendments + */ + public getAllAmendmentsInstantly(): ViewMotion[] { + return this.getViewModelList().filter(motion => !!motion.parent_id); + } + /** * Returns the amendments to a given motion * diff --git a/client/src/app/core/ui-services/base-filter-list.service.ts b/client/src/app/core/ui-services/base-filter-list.service.ts index 3764c2f8a..0d54dda38 100644 --- a/client/src/app/core/ui-services/base-filter-list.service.ts +++ b/client/src/app/core/ui-services/base-filter-list.service.ts @@ -146,17 +146,21 @@ export abstract class BaseFilterListService { return this._filterStack; } + /** + * The key to access stored valued + */ + private storageKey: string; + /** * Constructor. * * @param name the name of the filter service * @param store storage service, to read saved filter variables */ - public constructor( - protected name: string, - private store: StorageService, - private OSStatus: OpenSlidesStatusService - ) {} + public constructor(private store: StorageService, private OSStatus: OpenSlidesStatusService) { + this.storageKey = this.constructor.name; + console.log('storage-key: ', this.storageKey); + } /** * Initializes the filterService. @@ -166,7 +170,7 @@ export abstract class BaseFilterListService { public async initFilters(inputData: Observable): Promise { let storedFilter: OsFilter[] = null; if (!this.OSStatus.isInHistoryMode) { - storedFilter = await this.store.get('filter_' + this.name); + storedFilter = await this.store.get('filter_' + this.storageKey); } if (storedFilter && this.isOsFilter(storedFilter)) { @@ -237,7 +241,7 @@ export abstract class BaseFilterListService { let storedFilter = null; if (!this.OSStatus.isInHistoryMode) { - storedFilter = await this.store.get('filter_' + this.name); + storedFilter = await this.store.get('filter_' + this.storageKey); } if (!!storedFilter) { @@ -276,43 +280,43 @@ export abstract class BaseFilterListService { * @param repo repository to create dynamic filters from * @param filter the OSFilter for the filter property * @param noneOptionLabel The label of the non option, if set - * @param exexcludeIds Set if certain ID's should be excluded from filtering + * @param filterFn custom filter function if required */ protected updateFilterForRepo( repo: BaseRepository, filter: OsFilter, noneOptionLabel?: string, - excludeIds?: number[] + filterFn?: (filter: BaseViewModel) => boolean ): void { repo.getViewModelListObservable().subscribe(viewModel => { if (viewModel && viewModel.length) { let filterProperties: (OsFilterOption | string)[]; - filterProperties = viewModel - .filter(model => (excludeIds && excludeIds.length ? !excludeIds.includes(model.id) : true)) - .map((model: HierarchyModel) => { - return { - condition: model.id, - label: model.getTitle(), - isChild: !!model.parent, - children: - model.children && model.children.length - ? model.children.map(child => { - return { - label: child.getTitle(), - condition: child.id - }; - }) - : undefined - }; - }); - - filterProperties.push('-'); - filterProperties.push({ - condition: null, - label: noneOptionLabel + filterProperties = viewModel.filter(filterFn ? filterFn : () => true).map((model: HierarchyModel) => { + return { + condition: model.id, + label: model.getTitle(), + isChild: !!model.parent, + children: + model.children && model.children.length + ? model.children.map(child => { + return { + label: child.getTitle(), + condition: child.id + }; + }) + : undefined + }; }); + if (!!noneOptionLabel) { + filterProperties.push('-'); + filterProperties.push({ + condition: null, + label: noneOptionLabel + }); + } + filter.options = filterProperties; this.setFilterDefinitions(); } @@ -325,7 +329,7 @@ export abstract class BaseFilterListService { public storeActiveFilters(): void { this.updateFilteredData(); if (!this.OSStatus.isInHistoryMode) { - this.store.set('filter_' + this.name, this.filterDefinitions); + this.store.set('filter_' + this.storageKey, this.filterDefinitions); } } diff --git a/client/src/app/shared/components/search-value-selector/search-value-selector.component.ts b/client/src/app/shared/components/search-value-selector/search-value-selector.component.ts index 64005a534..0f5a6b0c5 100644 --- a/client/src/app/shared/components/search-value-selector/search-value-selector.component.ts +++ b/client/src/app/shared/components/search-value-selector/search-value-selector.component.ts @@ -102,7 +102,7 @@ export class SearchValueSelectorComponent implements OnDestroy { * Placeholder of the List */ @Input() - public listname: String; + public listname: string; /** * Name of the Form diff --git a/client/src/app/site/agenda/services/agenda-filter-list.service.ts b/client/src/app/site/agenda/services/agenda-filter-list.service.ts index 226bbaab4..f41d86075 100644 --- a/client/src/app/site/agenda/services/agenda-filter-list.service.ts +++ b/client/src/app/site/agenda/services/agenda-filter-list.service.ts @@ -22,7 +22,7 @@ export class AgendaFilterListService extends BaseFilterListService { * @param translate Translation service */ public constructor(store: StorageService, OSStatus: OpenSlidesStatusService, private translate: TranslateService) { - super('Agenda', store, OSStatus); + super(store, OSStatus); } /** diff --git a/client/src/app/site/assignments/services/assignment-filter.service.ts b/client/src/app/site/assignments/services/assignment-filter.service.ts index ee8133c11..f3dc5c7c9 100644 --- a/client/src/app/site/assignments/services/assignment-filter.service.ts +++ b/client/src/app/site/assignments/services/assignment-filter.service.ts @@ -19,7 +19,7 @@ export class AssignmentFilterListService extends BaseFilterListService + +

Amendments

+ + + + + +
+ + {{ selectedRows.length }} selected +
+ + + + +
+ +
+ +
+ {{ motion.identifier }} + (Line {{ getChangeLines(motion) }}) +
+ + +
+ + by + {{ motion.submitters }} + + + + · + + Sequential number + {{ motion.id }} +
+ + +
+ + {{ motionRepo.getExtendedStateLabel(motion) }} + +
+ + +
+ + {{ this.motionRepo.getExtendedRecommendationLabel(motion) }} + +
+
+
+ + +
+
+
+
+
+ + +
+ +
+
+ + +
+
+ +
+ +
+
+ + +
+
diff --git a/client/src/app/site/motions/modules/amendment-list/amendment-list.component.scss b/client/src/app/site/motions/modules/amendment-list/amendment-list.component.scss new file mode 100644 index 000000000..88be4f51b --- /dev/null +++ b/client/src/app/site/motions/modules/amendment-list/amendment-list.component.scss @@ -0,0 +1,2 @@ +@import '~assets/styles/motion-styles-common'; +@import 'app/site/motions/styles/motion-list-styles.scss'; diff --git a/client/src/app/site/motions/modules/amendment-list/amendment-list.component.spec.ts b/client/src/app/site/motions/modules/amendment-list/amendment-list.component.spec.ts new file mode 100644 index 000000000..3dc4601d6 --- /dev/null +++ b/client/src/app/site/motions/modules/amendment-list/amendment-list.component.spec.ts @@ -0,0 +1,27 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { E2EImportsModule } from 'e2e-imports.module'; + +import { AmendmentListComponent } from './amendment-list.component'; + +describe('AmendmentListComponent', () => { + let component: AmendmentListComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [E2EImportsModule], + declarations: [AmendmentListComponent] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(AmendmentListComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/client/src/app/site/motions/modules/amendment-list/amendment-list.component.ts b/client/src/app/site/motions/modules/amendment-list/amendment-list.component.ts new file mode 100644 index 000000000..1845c66e8 --- /dev/null +++ b/client/src/app/site/motions/modules/amendment-list/amendment-list.component.ts @@ -0,0 +1,189 @@ +import { Component, OnInit } from '@angular/core'; +import { MatDialog, MatSnackBar } from '@angular/material'; +import { DomSanitizer, SafeHtml, Title } from '@angular/platform-browser'; +import { ActivatedRoute } from '@angular/router'; + +import { TranslateService } from '@ngx-translate/core'; +import { PblColumnDefinition } from '@pebula/ngrid'; + +import { AmendmentFilterListService } from '../../services/amendment-filter-list.service'; +import { StorageService } from 'app/core/core-services/storage.service'; +import { MotionRepositoryService } from 'app/core/repositories/motions/motion-repository.service'; +import { ConfigService } from 'app/core/ui-services/config.service'; +import { DiffLinesInParagraph } from 'app/core/ui-services/diff.service'; +import { LinenumberingService } from 'app/core/ui-services/linenumbering.service'; +import { ItemVisibilityChoices } from 'app/shared/models/agenda/item'; +import { largeDialogSettings } from 'app/shared/utils/dialog-settings'; +import { BaseListViewComponent } from 'app/site/base/base-list-view'; +import { MotionExportDialogComponent } from '../shared-motion/motion-export-dialog/motion-export-dialog.component'; +import { MotionExportInfo, MotionExportService } from '../../services/motion-export.service'; +import { MotionSortListService } from '../../services/motion-sort-list.service'; +import { ViewMotion } from '../../models/view-motion'; + +/** + * Shows all the amendments in the NGrid table + */ +@Component({ + selector: 'os-amendment-list', + templateUrl: './amendment-list.component.html', + styleUrls: ['./amendment-list.component.scss'] +}) +export class AmendmentListComponent extends BaseListViewComponent implements OnInit { + private parentMotionId: number; + /** + * Hold item visibility + */ + public itemVisibility = ItemVisibilityChoices; + + /** + * To hold the motions line length + */ + private motionLineLength: number; + + /** + * Column defintiion + */ + public tableColumnDefinition: PblColumnDefinition[] = [ + { + prop: 'meta', + minWidth: 250, + width: '15%' + }, + { + prop: 'summary', + width: 'auto' + }, + { + prop: 'speakers', + width: this.singleButtonWidth + } + ]; + + /** + * To filter stuff + */ + public filterProps = ['submitters', 'title', 'identifier']; + + /** + * + * @param titleService set the title + * @param translate translate stuff + * @param matSnackBar show errors + * @param storage store and recall + * @param motionRepo get the motions + * @param motionSortService the default motion sorter + * + * @param configService get config vars + */ + public constructor( + titleService: Title, + translate: TranslateService, + matSnackBar: MatSnackBar, + storage: StorageService, + route: ActivatedRoute, + public motionRepo: MotionRepositoryService, + public motionSortService: MotionSortListService, + public amendmentFilterService: AmendmentFilterListService, + private sanitizer: DomSanitizer, + private configService: ConfigService, + private dialog: MatDialog, + private motionExport: MotionExportService, + private linenumberingService: LinenumberingService + ) { + super(titleService, translate, matSnackBar, storage); + super.setTitle('Amendments'); + this.canMultiSelect = true; + this.parentMotionId = parseInt(route.snapshot.params.id, 10); + } + + /** + * Observe the line length + */ + public ngOnInit(): void { + this.configService.get('motions_line_length').subscribe(lineLength => { + this.motionLineLength = lineLength; + }); + + if (!!this.parentMotionId) { + this.amendmentFilterService.clearAllFilters(); + this.amendmentFilterService.parentMotionId = this.parentMotionId; + } else { + this.amendmentFilterService.parentMotionId = 0; + } + } + + /** + * Helper function to get amendment paragraphs of a given motion + * + * @param amendment the get the paragraphs from + * @returns DiffLinesInParagraph-List + */ + private getDiffLines(amendment: ViewMotion): DiffLinesInParagraph[] { + if (amendment.isParagraphBasedAmendment()) { + return this.motionRepo.getAmendmentParagraphs(amendment, this.motionLineLength, false); + } else { + return null; + } + } + + /** + * Extract the lines of the amendments + * If an amendments has multiple changes, they will be printed like an array of strings + * + * @param amendment the motion to create the amendment to + * @return The lines of the amendment + */ + public getChangeLines(amendment: ViewMotion): string { + const diffLines = this.getDiffLines(amendment); + + if (!!diffLines) { + return diffLines + .map(diffLine => { + if (diffLine.diffLineTo === diffLine.diffLineFrom + 1) { + return '' + diffLine.diffLineFrom; + } else { + return `${diffLine.diffLineFrom} - ${diffLine.diffLineTo - 1}`; + } + }) + .toString(); + } + } + + /** + * Formulate the amendment summary + * + * @param amendment the motion to create the amendment to + * @returns the amendments as string, if they are multiple they gonna be separated by `[...]` + */ + public getAmendmentSummary(amendment: ViewMotion): string { + const diffLines = this.getDiffLines(amendment); + if (!!diffLines) { + return diffLines + .map(diffLine => { + return this.linenumberingService.stripLineNumbers(diffLine.text); + }) + .join('[...]'); + } + } + + // todo put in own file + public openExportDialog(): void { + const exportDialogRef = this.dialog.open(MotionExportDialogComponent, { + ...largeDialogSettings, + data: this.dataSource + }); + + exportDialogRef + .afterClosed() + .subscribe((exportInfo: MotionExportInfo) => + this.motionExport.evaluateExportRequest( + exportInfo, + this.isMultiSelect ? this.selectedRows : this.dataSource.filteredData + ) + ); + } + + public sanitizeText(text: string): SafeHtml { + return this.sanitizer.bypassSecurityTrustHtml(text); + } +} diff --git a/client/src/app/site/motions/modules/amendment-list/amendment-list.module.ts b/client/src/app/site/motions/modules/amendment-list/amendment-list.module.ts new file mode 100644 index 000000000..54ee13ccf --- /dev/null +++ b/client/src/app/site/motions/modules/amendment-list/amendment-list.module.ts @@ -0,0 +1,13 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; + +import { AmendmentListRoutingModule } from './amendment-list-routing.module'; +import { AmendmentListComponent } from './amendment-list.component'; +import { SharedModule } from 'app/shared/shared.module'; +import { SharedMotionModule } from '../shared-motion/shared-motion.module'; + +@NgModule({ + declarations: [AmendmentListComponent], + imports: [CommonModule, AmendmentListRoutingModule, SharedModule, SharedMotionModule] +}) +export class AmendmentListModule {} diff --git a/client/src/app/site/motions/modules/motion-detail/components/motion-detail/motion-detail.component.html b/client/src/app/site/motions/modules/motion-detail/components/motion-detail/motion-detail.component.html index e2f7df425..9ab256f1a 100644 --- a/client/src/app/site/motions/modules/motion-detail/components/motion-detail/motion-detail.component.html +++ b/client/src/app/site/motions/modules/motion-detail/components/motion-detail/motion-detail.component.html @@ -452,9 +452,12 @@ diff --git a/client/src/app/site/motions/modules/motion-detail/components/motion-detail/motion-detail.component.ts b/client/src/app/site/motions/modules/motion-detail/components/motion-detail/motion-detail.component.ts index 4c26c6945..6c2aca9f6 100644 --- a/client/src/app/site/motions/modules/motion-detail/components/motion-detail/motion-detail.component.ts +++ b/client/src/app/site/motions/modules/motion-detail/components/motion-detail/motion-detail.component.ts @@ -600,6 +600,7 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit, } } }), + this.repo.amendmentsTo(motionId).subscribe((amendments: ViewMotion[]): void => { this.amendments = amendments; this.recalcUnifiedChanges(); diff --git a/client/src/app/site/motions/modules/motion-list/components/motion-list/motion-list.component.html b/client/src/app/site/motions/modules/motion-list/components/motion-list/motion-list.component.html index 89dfbe90b..eea137f7a 100644 --- a/client/src/app/site/motions/modules/motion-list/components/motion-list/motion-list.component.html +++ b/client/src/app/site/motions/modules/motion-list/components/motion-list/motion-list.component.html @@ -104,10 +104,11 @@ +
- {{ getStateLabel(motion) }} + {{ this.motionRepo.getExtendedStateLabel(motion) }}
@@ -117,7 +118,7 @@ class="ellipsis-overflow white spacer-top-3" > - {{ getRecommendationLabel(motion) }} + {{ motionRepo.getExtendedRecommendationLabel(motion) }} @@ -219,6 +220,14 @@ Multiselect +
+ +