From dfa80e9cd097ff30c514231b7c988d3faaa0b139 Mon Sep 17 00:00:00 2001 From: GabrielMeyer Date: Tue, 14 May 2019 16:59:47 +0200 Subject: [PATCH] Separates the attachment field to a custom component - Refactores also the 'search-value-selector.component' --- .../agenda-content-object-form.component.html | 1 - .../attachment-control.component.html | 29 ++++ .../attachment-control.component.scss | 14 ++ .../attachment-control.component.spec.ts | 25 +++ .../attachment-control.component.ts | 107 +++++++++++++ .../extension-field.component.html | 2 - .../search-value-selector.component.html | 17 +-- .../search-value-selector.component.spec.ts | 1 - .../search-value-selector.component.ts | 144 +++++------------- client/src/app/shared/shared.module.ts | 5 +- .../list-of-speakers.component.html | 2 - .../assignment-detail.component.html | 13 +- .../history-list/history-list.component.html | 1 - ...motion-comment-section-list.component.html | 4 - .../manage-submitters.component.html | 2 - .../motion-detail.component.html | 37 +---- .../motion-detail.component.scss | 11 -- .../motion-detail/motion-detail.component.ts | 46 +----- .../topic-detail/topic-detail.component.html | 11 +- .../topic-detail/topic-detail.component.ts | 11 +- 20 files changed, 230 insertions(+), 253 deletions(-) create mode 100644 client/src/app/shared/components/attachment-control/attachment-control.component.html create mode 100644 client/src/app/shared/components/attachment-control/attachment-control.component.scss create mode 100644 client/src/app/shared/components/attachment-control/attachment-control.component.spec.ts create mode 100644 client/src/app/shared/components/attachment-control/attachment-control.component.ts diff --git a/client/src/app/shared/components/agenda-content-object-form/agenda-content-object-form.component.html b/client/src/app/shared/components/agenda-content-object-form/agenda-content-object-form.component.html index fc7b8b651..b830ebed5 100644 --- a/client/src/app/shared/components/agenda-content-object-form/agenda-content-object-form.component.html +++ b/client/src/app/shared/components/agenda-content-object-form/agenda-content-object-form.component.html @@ -21,7 +21,6 @@
+ + +
+ + + +

+ Upload files +

+ +
diff --git a/client/src/app/shared/components/attachment-control/attachment-control.component.scss b/client/src/app/shared/components/attachment-control/attachment-control.component.scss new file mode 100644 index 000000000..d90b7be3a --- /dev/null +++ b/client/src/app/shared/components/attachment-control/attachment-control.component.scss @@ -0,0 +1,14 @@ +:host { + width: 100%; + + .attachment-container { + justify-content: space-between; + display: flex; + .selector { + width: 95%; + } + .mat-icon-button { + top: 20px; + } + } +} diff --git a/client/src/app/shared/components/attachment-control/attachment-control.component.spec.ts b/client/src/app/shared/components/attachment-control/attachment-control.component.spec.ts new file mode 100644 index 000000000..9a4fe10d0 --- /dev/null +++ b/client/src/app/shared/components/attachment-control/attachment-control.component.spec.ts @@ -0,0 +1,25 @@ +import { async, TestBed, ComponentFixture } from '@angular/core/testing'; + +import { AttachmentControlComponent } from './attachment-control.component'; +import { E2EImportsModule } from 'e2e-imports.module'; + +describe('AttachmentControlComponent', () => { + let component: AttachmentControlComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [E2EImportsModule] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(AttachmentControlComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/client/src/app/shared/components/attachment-control/attachment-control.component.ts b/client/src/app/shared/components/attachment-control/attachment-control.component.ts new file mode 100644 index 000000000..912a59c7f --- /dev/null +++ b/client/src/app/shared/components/attachment-control/attachment-control.component.ts @@ -0,0 +1,107 @@ +import { Component, OnInit, TemplateRef, Output, EventEmitter, Input } from '@angular/core'; +import { MatDialog } from '@angular/material'; +import { FormControl, ControlValueAccessor } from '@angular/forms'; +import { BehaviorSubject } from 'rxjs'; + +import { MediafileRepositoryService } from 'app/core/repositories/mediafiles/mediafile-repository.service'; +import { ViewMediafile } from 'app/site/mediafiles/models/view-mediafile'; + +@Component({ + selector: 'os-attachment-control', + templateUrl: './attachment-control.component.html', + styleUrls: ['./attachment-control.component.scss'] +}) +export class AttachmentControlComponent implements OnInit, ControlValueAccessor { + /** + * Output for an error handler + */ + @Output() + public errorHandler: EventEmitter = new EventEmitter(); + + /** + * The form-control name to access the value for the form-control + */ + @Input() + public controlName: FormControl; + + /** + * The file list that is necessary for the `SearchValueSelector` + */ + public mediaFileList: BehaviorSubject = new BehaviorSubject([]); + + /** + * Default constructor + * + * @param dialogService Reference to the `MatDialog` + * @param mediaService Reference for the `MediaFileRepositoryService` + */ + public constructor(private dialogService: MatDialog, private mediaService: MediafileRepositoryService) {} + + /** + * On init method + */ + public ngOnInit(): void { + this.mediaFileList = this.mediaService.getViewModelListBehaviorSubject(); + } + + /** + * Function to open a given dialog + * + * @param dialog the dialog to open + */ + public openUploadDialog(dialog: TemplateRef): void { + this.dialogService.open(dialog, { + width: '750px', + maxWidth: '90vw', + maxHeight: '90vh' + }); + } + + /** + * Function to set the value for the `SearchValueSelector` after successful upload + * + * @param fileIDs a list with the ids of the uploaded files + */ + public uploadSuccess(fileIDs: number[]): void { + if (this.controlName) { + const newValues = [...this.controlName.value, ...fileIDs]; + this.controlName.setValue(newValues); + this.dialogService.closeAll(); + } + } + + /** + * Function to emit an occurring error. + * + * @param error The occurring error + */ + public uploadError(error: string): void { + this.errorHandler.emit(error); + } + + /** + * Function to write a new value to the form. + * Satisfy the interface. + * + * @param value The new value for this form. + */ + public writeValue(value: any): void { + if (value && this.controlName) { + this.controlName.setValue(value); + } + } + + /** + * Function executed when the control's value changed. + * + * @param fn the function that is executed. + */ + public registerOnChange(fn: any): void {} + + /** + * To satisfy the interface + * + * @param fn the registered callback function for onBlur-events. + */ + public registerOnTouched(fn: any): void {} +} diff --git a/client/src/app/shared/components/extension-field/extension-field.component.html b/client/src/app/shared/components/extension-field/extension-field.component.html index c4bd6b7a7..ac471e24c 100644 --- a/client/src/app/shared/components/extension-field/extension-field.component.html +++ b/client/src/app/shared/components/extension-field/extension-field.component.html @@ -36,10 +36,8 @@ diff --git a/client/src/app/shared/components/search-value-selector/search-value-selector.component.html b/client/src/app/shared/components/search-value-selector/search-value-selector.component.html index 778fcd963..fc77383ee 100644 --- a/client/src/app/shared/components/search-value-selector/search-value-selector.component.html +++ b/client/src/app/shared/components/search-value-selector/search-value-selector.component.html @@ -1,25 +1,14 @@ - + - +
- + {{ selectedItem.getTitle() | translate }}
-
-

- Selected values: -

- - - {{ selectedItem.name }} - cancel - - -
diff --git a/client/src/app/shared/components/search-value-selector/search-value-selector.component.spec.ts b/client/src/app/shared/components/search-value-selector/search-value-selector.component.spec.ts index 522ad1acc..4b4bbd6e1 100644 --- a/client/src/app/shared/components/search-value-selector/search-value-selector.component.spec.ts +++ b/client/src/app/shared/components/search-value-selector/search-value-selector.component.spec.ts @@ -45,7 +45,6 @@ describe('SearchValueSelectorComponent', () => { const formGroup = formBuilder.group({ testArray: [] }); - hostComponent.searchValueSelectorComponent.form = formGroup; hostComponent.searchValueSelectorComponent.formControl = formGroup.get('testArray'); hostFixture.detectChanges(); 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 7c1291d57..76f087a88 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 @@ -1,9 +1,9 @@ -import { Component, OnInit, Input, ViewChild, OnDestroy } from '@angular/core'; -import { FormControl, FormGroup } from '@angular/forms'; -import { MatSelect } from '@angular/material/select'; +import { Component, Input, ViewChild, OnDestroy } from '@angular/core'; +import { FormControl } from '@angular/forms'; +import { MatSelect } from '@angular/material'; -import { Subject, ReplaySubject, BehaviorSubject, Subscription } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; +import { BehaviorSubject, Subscription } from 'rxjs'; +import { auditTime } from 'rxjs/operators'; import { TranslateService } from '@ngx-translate/core'; import { Selectable } from '../selectable'; @@ -25,7 +25,6 @@ import { Selectable } from '../selectable'; * [multiple]="true" * placeholder="Placeholder" * [InputListValues]="myListValues" - * [form]="myform_name" * [formControl]="myformcontrol"> * * ``` @@ -37,38 +36,27 @@ import { Selectable } from '../selectable'; templateUrl: './search-value-selector.component.html', styleUrls: ['./search-value-selector.component.scss'] }) -export class SearchValueSelectorComponent implements OnInit, OnDestroy { - /** - * ngModel variable - Deprecated with Angular 7 - * DO NOT USE: READ AT remove() FUNCTION! - */ - public myModel = []; - - /** - * Control for the filtering of the list - */ - public filterControl = new FormControl(); - - /** - * List of the filtered content, when entering something in the search bar - */ - public filteredItems: ReplaySubject = new ReplaySubject(1); - - /** - * The inputlist subject. - */ - private _inputListSubject: BehaviorSubject; - +export class SearchValueSelectorComponent implements OnDestroy { /** * Saves the current subscription to _inputListSubject. */ private _inputListSubscription: Subscription = null; + /** + * Value of the search input + */ + private searchValue = ''; + + /** + * All items + */ + private selectableItems: Selectable[]; + /** * Decide if this should be a single or multi-select-field */ @Input() - public multiple: boolean; + public multiple = false; /** * Decide, if none should be included, if multiple is false. @@ -95,9 +83,14 @@ export class SearchValueSelectorComponent implements OnInit, OnDestroy { if (this._inputListSubscription) { this._inputListSubscription.unsubscribe(); } - this._inputListSubject = value; - this._inputListSubscription = this._inputListSubject.subscribe(() => { - this.filterItems(); + // this.inputSubject = value; + this._inputListSubscription = value.pipe(auditTime(10)).subscribe(items => { + this.selectableItems = items; + if (this.formControl) { + items.length === 0 + ? this.formControl.disable({ emitEvent: false }) + : this.formControl.enable({ emitEvent: false }); + } }); } @@ -107,56 +100,23 @@ export class SearchValueSelectorComponent implements OnInit, OnDestroy { @Input() public listname: String; - /** - * Form Group - */ - @Input() - public form: FormGroup; - /** * Name of the Form */ @Input() public formControl: FormControl; - /** - * DO NOT USE UNTIL BUG IN UPSTREAM ARE RESOLVED! - * READ AT FUNCTION remove() - * - * Displayes the selected Items as Chip-List - */ - // @Input() - public dispSelected = false; - /** * The MultiSelect Component */ @ViewChild('thisSelector', { static: true }) public thisSelector: MatSelect; - /** - * Subject that emits when the component has been destroyed - */ - private _onDestroy = new Subject(); - /** * Empty constructor */ public constructor(protected translate: TranslateService) {} - /** - * onInit with filter ans subscription on filter - */ - public ngOnInit(): void { - if (this._inputListSubject) { - this.filteredItems.next(this._inputListSubject.getValue()); - } - // listen to value changes - this.filterControl.valueChanges.pipe(takeUntil(this._onDestroy)).subscribe(() => { - this.filterItems(); - }); - } - /** * Unsubscribe on destroing. */ @@ -164,53 +124,31 @@ export class SearchValueSelectorComponent implements OnInit, OnDestroy { if (this._inputListSubscription) { this._inputListSubscription.unsubscribe(); } - this._onDestroy.next(); } /** - * the filter function itself + * Function to get a list filtered by the entered search value. + * + * @returns The filtered list of items. */ - private filterItems(): void { - if (!this._inputListSubject) { - return; - } - // get the search keyword - let search = this.filterControl.value; - if (!search) { - this.filteredItems.next(this._inputListSubject.getValue()); - return; - } else { - search = search.toLowerCase(); - } - // filter the values - this.filteredItems.next( - this._inputListSubject.getValue().filter( - selectedItem => - selectedItem + public getFilteredItems(): Selectable[] { + if (this.selectableItems) { + return this.selectableItems.filter( + item => + item .toString() .toLowerCase() - .indexOf(search) > -1 - ) - ); + .indexOf(this.searchValue) > -1 + ); + } } /** - * If the dispSelected value is marked as true, a chipList should be shown below the - * selection list. Unfortunately it is not possible (yet) to change the datamodel in the backend - * https://github.com/angular/material2/issues/10085 - therefore you can display the values in two - * places, but can't reflect the changes in both places. Until this can be done this will be unused code - * @param item the selected item to be removed + * Function to set the search value. + * + * @param searchValue the new value the user is searching for. */ - public remove(item: Selectable): void { - const myArr = this.thisSelector.value; - const index = myArr.indexOf(item, 0); - // my model was the form according to fix - // https://github.com/angular/material2/issues/10044 - // but this causes bad behaviour and will be depricated in Angular 7 - this.myModel = this.myModel.slice(index, 1); - if (index > -1) { - myArr.splice(index, 1); - } - this.thisSelector.value = myArr; + public onSearch(searchValue: string): void { + this.searchValue = searchValue.toLowerCase(); } } diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts index 0cbbf0c8d..7944c729a 100644 --- a/client/src/app/shared/shared.module.ts +++ b/client/src/app/shared/shared.module.ts @@ -92,6 +92,7 @@ import { IconContainerComponent } from './components/icon-container/icon-contain import { ListViewTableComponent } from './components/list-view-table/list-view-table.component'; import { AgendaContentObjectFormComponent } from './components/agenda-content-object-form/agenda-content-object-form.component'; import { ExtensionFieldComponent } from './components/extension-field/extension-field.component'; +import { AttachmentControlComponent } from './components/attachment-control/attachment-control.component'; /** * Share Module for all "dumb" components and pipes. @@ -214,6 +215,7 @@ import { ExtensionFieldComponent } from './components/extension-field/extension- SlideContainerComponent, CountdownTimeComponent, MediaUploadContentComponent, + AttachmentControlComponent, PrecisionPipe, SpeakerButtonComponent, GridLayoutComponent, @@ -262,7 +264,8 @@ import { ExtensionFieldComponent } from './components/extension-field/extension- IconContainerComponent, ListViewTableComponent, AgendaContentObjectFormComponent, - ExtensionFieldComponent + ExtensionFieldComponent, + AttachmentControlComponent ], providers: [ { provide: DateAdapter, useClass: OpenSlidesDateAdapter }, diff --git a/client/src/app/site/agenda/components/list-of-speakers/list-of-speakers.component.html b/client/src/app/site/agenda/components/list-of-speakers/list-of-speakers.component.html index 334aa5eed..f055f87fa 100644 --- a/client/src/app/site/agenda/components/list-of-speakers/list-of-speakers.component.html +++ b/client/src/app/site/agenda/components/list-of-speakers/list-of-speakers.component.html @@ -121,9 +121,7 @@ diff --git a/client/src/app/site/assignments/components/assignment-detail/assignment-detail.component.html b/client/src/app/site/assignments/components/assignment-detail/assignment-detail.component.html index 8777f1a86..c7d337989 100644 --- a/client/src/app/site/assignments/components/assignment-detail/assignment-detail.component.html +++ b/client/src/app/site/assignments/components/assignment-detail/assignment-detail.component.html @@ -195,7 +195,6 @@ -
- +
+
diff --git a/client/src/app/site/history/components/history-list/history-list.component.html b/client/src/app/site/history/components/history-list/history-list.component.html index 53c2c2d43..4b85c02fb 100644 --- a/client/src/app/site/history/components/history-list/history-list.component.html +++ b/client/src/app/site/history/components/history-list/history-list.component.html @@ -16,7 +16,6 @@ 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 75d9fe0a0..1dab4334b 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 @@ -581,7 +581,6 @@
-
- - +
+
@@ -813,7 +794,6 @@
@@ -966,14 +944,3 @@ Final print template - - - -

- Upload files -

- -
diff --git a/client/src/app/site/motions/modules/motion-detail/components/motion-detail/motion-detail.component.scss b/client/src/app/site/motions/modules/motion-detail/components/motion-detail/motion-detail.component.scss index 79edd61ec..d343a32b0 100644 --- a/client/src/app/site/motions/modules/motion-detail/components/motion-detail/motion-detail.component.scss +++ b/client/src/app/site/motions/modules/motion-detail/components/motion-detail/motion-detail.component.scss @@ -231,14 +231,3 @@ span { font-size: 12px; margin-top: 4px; } - -.shortened-selector { - justify-content: space-between; - display: flex; - .selector { - width: 95%; - } - .mat-icon-button { - top: 20px; - } -} 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 a5abcdd75..ac2c772ad 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 @@ -1,5 +1,5 @@ import { ActivatedRoute, Router, NavigationEnd } from '@angular/router'; -import { Component, OnInit, OnDestroy, ElementRef, HostListener, TemplateRef } from '@angular/core'; +import { Component, OnInit, OnDestroy, ElementRef, HostListener } from '@angular/core'; import { DomSanitizer, SafeHtml, Title } from '@angular/platform-browser'; import { FormBuilder, FormGroup, Validators, FormControl } from '@angular/forms'; import { MatCheckboxChange } from '@angular/material/checkbox'; @@ -20,7 +20,6 @@ import { ItemRepositoryService } from 'app/core/repositories/agenda/item-reposit import { LinenumberingService } from 'app/core/ui-services/linenumbering.service'; import { LocalPermissionsService } from 'app/site/motions/services/local-permissions.service'; import { Mediafile } from 'app/shared/models/mediafiles/mediafile'; -import { MediafileRepositoryService } from 'app/core/repositories/mediafiles/mediafile-repository.service'; import { Motion } from 'app/shared/models/motions/motion'; import { MotionChangeRecommendationDialogComponentData, @@ -56,7 +55,6 @@ import { ViewMotionBlock } from 'app/site/motions/models/view-motion-block'; import { ViewCategory } from 'app/site/motions/models/view-category'; import { ViewCreateMotion } from 'app/site/motions/models/view-create-motion'; import { ViewportService } from 'app/core/ui-services/viewport.service'; -import { ViewMediafile } from 'app/site/mediafiles/models/view-mediafile'; import { ViewMotionChangeRecommendation } from 'app/site/motions/models/view-motion-change-recommendation'; import { ViewStatuteParagraph } from 'app/site/motions/models/view-statute-paragraph'; import { ViewTag } from 'app/site/tags/models/view-tag'; @@ -250,11 +248,6 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit, */ public blockObserver: BehaviorSubject; - /** - * Subject for mediafiles - */ - public mediafilesObserver: BehaviorSubject; - /** * Subject for tags */ @@ -445,7 +438,6 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit, private userRepo: UserRepositoryService, private notifyService: NotifyService, private tagRepo: TagRepositoryService, - private mediaFilerepo: MediafileRepositoryService, private workflowRepo: WorkflowRepositoryService, private blockRepo: MotionBlockRepositoryService, private itemRepo: ItemRepositoryService, @@ -462,7 +454,6 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit, public ngOnInit(): void { // get required information from the repositories this.tagObserver = this.tagRepo.getViewModelListBehaviorSubject(); - this.mediafilesObserver = this.mediaFilerepo.getViewModelListBehaviorSubject(); this.workflowObserver = this.workflowRepo.getViewModelListBehaviorSubject(); this.blockObserver = this.blockRepo.getViewModelListBehaviorSubject(); this.motionObserver = this.repo.getViewModelListBehaviorSubject(); @@ -501,17 +492,6 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit, this.configService .get('motions_show_sequential_numbers') .subscribe(shown => (this.showSequential = shown)); - // disable the selector for attachments if there are none - this.mediafilesObserver.subscribe(() => { - if (this.contentForm) { - const attachmentsCtrl = this.contentForm.get('attachments_id'); - if (this.mediafilesObserver.value.length === 0) { - attachmentsCtrl.disable(); - } else { - attachmentsCtrl.enable(); - } - } - }); // Update statute paragraphs this.statuteRepo.getViewModelListObservable().subscribe(newViewStatuteParagraphs => { @@ -1546,30 +1526,6 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit, this.personalNoteService.savePersonalNote(this.motion, this.motion.personalNote).then(null, this.raiseError); } - /** - * Handler for the upload attachments button - */ - public onUploadAttachmentsButton(templateRef: TemplateRef): void { - this.dialogService.open(templateRef, { - maxHeight: '90vh', - width: '750px', - maxWidth: '90vw' - }); - } - - /** - * Handler for successful uploads. - * Adds the IDs of the upload process to the mediafile selector - * - * @param fileIds the ids of the uploads if they were successful - */ - public uploadSuccess(fileIds: number[]): void { - const currentAttachments = this.contentForm.get('attachments_id').value as number[]; - const newAttachments = [...currentAttachments, ...fileIds]; - this.contentForm.get('attachments_id').setValue(newAttachments); - this.dialogService.closeAll(); - } - /** * Handler for upload errors * diff --git a/client/src/app/site/topics/components/topic-detail/topic-detail.component.html b/client/src/app/site/topics/components/topic-detail/topic-detail.component.html index 6a1929d3a..a4f7b60bb 100644 --- a/client/src/app/site/topics/components/topic-detail/topic-detail.component.html +++ b/client/src/app/site/topics/components/topic-detail/topic-detail.component.html @@ -68,14 +68,7 @@
- +
@@ -93,9 +86,7 @@
; - /** * Subject for agenda items */ @@ -88,7 +81,6 @@ export class TopicDetailComponent extends BaseViewComponent { private repo: TopicRepositoryService, private promptService: PromptService, private operator: OperatorService, - private mediafileRepo: MediafileRepositoryService, private itemRepo: ItemRepositoryService, private sanitizer: DomSanitizer ) { @@ -96,7 +88,6 @@ export class TopicDetailComponent extends BaseViewComponent { this.getTopicByUrl(); this.createForm(); - this.mediafilesObserver = this.mediafileRepo.getViewModelListBehaviorSubject(); this.itemObserver = this.itemRepo.getViewModelListBehaviorSubject(); }