import { AfterViewInit, Component } from '@angular/core'; import { FormArray, FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms'; import { ActivatedRoute, Params, Router } from '@angular/router'; import { DmpProfileFieldDataType } from '@app/core/common/enum/dmp-profile-field-type'; import { DmpProfileStatus } from '@app/core/common/enum/dmp-profile-status'; import { DmpProfileType } from '@app/core/common/enum/dmp-profile-type'; import { DmpProfile } from '@app/core/model/dmp-profile/dmp-profile'; import { DmpProfileService } from '@app/core/services/dmp/dmp-profile.service'; import { SnackBarNotificationLevel, UiNotificationService } from '@app/core/services/notification/ui-notification-service'; import { EnumUtils } from '@app/core/services/utilities/enum-utils.service'; import { DmpProfileEditorModel, DmpProfileFieldEditorModel } from '@app/ui/admin/dmp-profile/editor/dmp-profile-editor.model'; import { DmpProfileExternalAutoCompleteFieldDataEditorModel } from '@app/ui/admin/dmp-profile/editor/external-autocomplete/dmp-profile-external-autocomplete-field-editor.model'; import { BreadcrumbItem } from '@app/ui/misc/breadcrumb/definition/breadcrumb-item'; import { BaseComponent } from '@common/base/base.component'; import { FormService } from '@common/forms/form-service'; import { ValidationErrorModel } from '@common/forms/validation/error-model/validation-error-model'; import { TranslateService } from '@ngx-translate/core'; import { environment } from 'environments/environment'; import * as FileSaver from 'file-saver'; import { Observable, of as observableOf } from 'rxjs'; import { map, takeUntil } from 'rxjs/operators'; import { ConfigurationService } from '@app/core/services/configuration/configuration.service'; import { HttpClient } from '@angular/common/http'; import { MatomoService } from '@app/core/services/matomo/matomo-service'; import { MatDialog } from '@angular/material/dialog'; import { ConfirmationDialogComponent } from '@common/modules/confirmation-dialog/confirmation-dialog.component'; import { MultipleAutoCompleteConfiguration } from '@app/library/auto-complete/multiple/multiple-auto-complete-configuration'; import { DatasetProfileModel } from '@app/core/model/dataset/dataset-profile'; import { DataTableRequest } from '@app/core/model/data-table/data-table-request'; import { DatasetProfileCriteria } from '@app/core/query/dataset-profile/dataset-profile-criteria'; import { DmpService } from '@app/core/services/dmp/dmp.service'; import { AvailableProfilesComponent } from '@app/ui/dmp/editor/available-profiles/available-profiles.component'; import { DatasetPreviewDialogComponent } from '@app/ui/dmp/dataset-preview/dataset-preview-dialog.component'; import { CdkDragDrop, CdkDropList, CdkDrag, moveItemInArray } from '@angular/cdk/drag-drop'; import { DmpBlueprint, DmpBlueprintDefinition, ExtraFieldType, FieldCategory, SystemFieldType } from '@app/core/model/dmp/dmp-blueprint/dmp-blueprint'; import { DmpBlueprintEditor } from './dmp-blueprint-editor.model'; import { Guid } from '@common/types/guid'; import { isNullOrUndefined } from '@app/utilities/enhancers/utils'; @Component({ selector: 'app-dmp-profile-editor-component', templateUrl: 'dmp-profile-editor.component.html', styleUrls: ['./dmp-profile-editor.component.scss'] }) export class DmpProfileEditorComponent extends BaseComponent implements AfterViewInit { isNew = true; viewOnly = false; dmpProfileModel: DmpProfileEditorModel; dmpBlueprintModel: DmpBlueprintEditor; formGroup: FormGroup = null; host: string; dmpProfileId: string; breadCrumbs: Observable; dmpBlueprintsFormGroup: FormGroup = null; profilesAutoCompleteConfiguration: MultipleAutoCompleteConfiguration; fieldList = [ {label: 'Title', type: SystemFieldType.TEXT}, {label: 'Description', type: SystemFieldType.HTML_TEXT}, {label: 'Researchers', type: SystemFieldType.RESEARCHERS}, {label: 'Organizations', type: SystemFieldType.ORGANIZATIONS}, {label: 'Language', type: SystemFieldType.LANGUAGE}, {label: 'Contact', type: SystemFieldType.CONTACT}, {label: 'Funder', type: SystemFieldType.FUNDER}, {label: 'Grant', type: SystemFieldType.GRANT}, {label: 'Project', type: SystemFieldType.PROJECT}, {label: 'License', type: SystemFieldType.LICENSE}, {label: 'Access Rights', type: SystemFieldType.ACCESS_RIGHTS} ]; selectedSystemFields: string[] = []; constructor( private dmpProfileService: DmpProfileService, private _service: DmpService, private route: ActivatedRoute, private router: Router, private language: TranslateService, private enumUtils: EnumUtils, private uiNotificationService: UiNotificationService, private formService: FormService, private fb: FormBuilder, private configurationService: ConfigurationService, private httpClient: HttpClient, private matomoService: MatomoService, private dialog: MatDialog ) { super(); this.host = configurationService.server; } ngAfterViewInit() { this.matomoService.trackPageView('Admin: DMP Profile Edit'); this.profilesAutoCompleteConfiguration = { filterFn: this.filterProfiles.bind(this), initialItems: (excludedItems: any[]) => this.filterProfiles('').pipe(map(result => result.filter(resultItem => (excludedItems || []).map(x => x.id).indexOf(resultItem.id) === -1))), displayFn: (item) => item['label'], titleFn: (item) => item['label'], subtitleFn: (item) => item['description'], popupItemActionIcon: 'visibility' }; this.route.params .pipe(takeUntil(this._destroyed)) .subscribe((params: Params) => { this.dmpProfileId = params['id']; if (this.dmpProfileId != null) { this.isNew = false; this.dmpProfileService.getSingle(this.dmpProfileId).pipe(map(data => data as DmpProfile)) .pipe(takeUntil(this._destroyed)) .subscribe(data => { this.dmpProfileModel = new DmpProfileEditorModel().fromModel(data); this.formGroup = this.dmpProfileModel.buildForm(); if (this.dmpProfileModel.status == DmpProfileStatus.Finalized) { this.formGroup.disable(); this.viewOnly = true } this.breadCrumbs = observableOf([{ parentComponentName: 'DmpProfileListingComponent', label: this.language.instant('NAV-BAR.TEMPLATE'), url: '/dmp-profiles/' + this.dmpProfileId }]); }); } else { this.dmpProfileModel = new DmpProfileEditorModel(); this.dmpBlueprintModel = new DmpBlueprintEditor(); setTimeout(() => { // this.formGroup = this.dmpProfileModel.buildForm(); // this.addField(); this.formGroup = this.dmpBlueprintModel.buildForm(); }); this.breadCrumbs = observableOf([{ parentComponentName: 'DmpProfileListingComponent', label: this.language.instant('NAV-BAR.TEMPLATE'), url: '/dmp-profiles/' + this.dmpProfileId }]); } }); this.initDmpBlueptintForm(); } filterProfiles(value: string): Observable { const request = new DataTableRequest(null, null, { fields: ['+label'] }); const criteria = new DatasetProfileCriteria(); criteria.like = value; request.criteria = criteria; return this._service.searchDMPProfiles(request); } initDmpBlueptintForm(): void { this.dmpBlueprintsFormGroup = this.fb.group({ label: this.fb.control(''), sections: this.fb.array([]) }); } sectionsArray(): FormArray { return this.dmpBlueprintsFormGroup.get('sections') as FormArray; } initSection(ordinal: number): FormGroup { return this.fb.group({ id: this.fb.control(Guid.create().toString()), label: this.fb.control(''), description: this.fb.control(''), ordinal: this.fb.control(ordinal), fields: this.fb.array([]), //systemFields: this.fb.array([]), descriptionTemplates: this.fb.control(''), // this.fb.array([this.initDescriptionTemplate()]), //extraFields: this.fb.array([]) }); } addSection(): void { this.sectionsArray().push(this.initSection(this.sectionsArray().length)); } removeSection(sectionIndex: number): void { this.sectionsArray().removeAt(sectionIndex); } fieldsArray(sectionIndex: number): FormArray { return this.sectionsArray().at(sectionIndex).get('fields') as FormArray; } initField(fieldCategory: FieldCategory, fieldType?: number): FormGroup { return this.fb.group({ id: this.fb.control(Guid.create().toString()), category: this.fb.control(fieldCategory), label: this.fb.control(''), placeholder: this.fb.control(''), description: this.fb.control(''), type: (isNullOrUndefined(fieldType)) ? this.fb.control('') : this.fb.control(fieldType), required: (!isNullOrUndefined(fieldType) && (fieldType == 0 || fieldType == 1)) ? this.fb.control(true) : this.fb.control(false), ordinal: this.fb.control('') }); } addField(sectionIndex: number, fieldCategory: FieldCategory, fieldType?: number): void { this.fieldsArray(sectionIndex).push(this.initField(fieldCategory, fieldType)); } removeField(sectionIndex: number, fieldIndex: number): void { this.fieldsArray(sectionIndex).removeAt(fieldIndex); } systemFieldsArray(sectionIndex: number): FormArray { return this.sectionsArray().at(sectionIndex).get('systemFields') as FormArray; } initSystemField(systemField?: SystemFieldType): FormGroup { return this.fb.group({ id: this.fb.control(Guid.create().toString()), type: this.fb.control(systemField), label: this.fb.control(''), placeholder: this.fb.control(''), description: this.fb.control(''), required: this.fb.control(true), ordinal: this.fb.control('') }); } addSystemField(sectionIndex: number, systemField?: SystemFieldType): void { this.fieldsArray(sectionIndex).push(this.initField(FieldCategory.SYSTEM, systemField)); } transfromEnumToString(type: SystemFieldType): string{ return this.fieldList.find(f => f.type == type).label; } selectedFieldType(systemField: string, type: SystemFieldType, sectionIndex: number): void { let index = this.selectedSystemFields.indexOf(systemField); if (index == -1) { this.selectedSystemFields.push(systemField); this.addSystemField(sectionIndex, type); } else { this.selectedSystemFields.splice(index, 1); this.removeSystemField(sectionIndex, type); } } systemFieldDisabled(systemField: SystemFieldType, sectionIndex: number) { let i = 0; for (let s in this.sectionsArray().controls) { if (i != sectionIndex) { for (let f of this.fieldsArray(i).controls) { if (f.get('category').value == FieldCategory.SYSTEM && f.get('type').value == systemField) { return true; } } } i++; } return false; } removeSystemFieldWithIndex(sectionIndex: number, fieldIndex: number): void { this.fieldsArray(sectionIndex).removeAt(fieldIndex); } removeSystemField(sectionIndex: number, systemField: SystemFieldType): void { let i = 0; for(let f of this.fieldsArray(sectionIndex).controls){ if(f.get('category').value == FieldCategory.SYSTEM && f.get('type').value == systemField){ this.fieldsArray(sectionIndex).removeAt(i); return; } i++; } } descriptionTemplatesArray(sectionIndex: number): FormArray { return this.sectionsArray().at(sectionIndex).get('descriptionTemplates') as FormArray; } initDescriptionTemplate(): FormGroup { return this.fb.group({ descriptionTemplateId: this.fb.control(''), label: this.fb.control(''), minMultiplicity: this.fb.control(''), maxMultiplicity: this.fb.control('') }); } addDescriptionTemplate(descriptionTemplate, sectionIndex: number): void { this.descriptionTemplatesArray(sectionIndex).push(this.fb.group({ label: this.fb.control(descriptionTemplate.value) })); } removeDescriptionTemplate(sectionIndex: number, templateIndex: number): void { this.descriptionTemplatesArray(sectionIndex).removeAt(templateIndex); } extraFieldsArray(sectionIndex: number): FormArray { return this.sectionsArray().at(sectionIndex).get('extraFields') as FormArray; } initExtraField(): FormGroup { return this.fb.group({ id: this.fb.control(Guid.create().toString()), label: this.fb.control(''), placeholder: this.fb.control(''), description: this.fb.control(''), type: this.fb.control(''), required: this.fb.control(false), ordinal: this.fb.control('') }); } addExtraField(sectionIndex: number): void { this.fieldsArray(sectionIndex).push(this.initField(FieldCategory.EXTRA)); } removeExtraField(sectionIndex: number, fieldIndex: number): void { this.fieldsArray(sectionIndex).removeAt(fieldIndex); } getExtraFieldTypes(): Number[] { let keys: string[] = Object.keys(ExtraFieldType); keys = keys.slice(0, keys.length / 2); const values: Number[] = keys.map(Number); return values; } getExtraFieldTypeValue(extraFieldType: ExtraFieldType): string { switch (extraFieldType) { case ExtraFieldType.TEXT: return 'Text'; case ExtraFieldType.RICH_TEXT: return 'Rich Text'; case ExtraFieldType.DATE: return 'Date'; case ExtraFieldType.NUMBER: return 'Number'; } } onSubmitTest(): void { } drop(event: CdkDragDrop, sectionIndex: number) { moveItemInArray(this.fieldsArray(sectionIndex).controls, event.previousIndex, event.currentIndex); moveItemInArray(this.fieldsArray(sectionIndex).value, event.previousIndex, event.currentIndex); } dropSections(event: CdkDragDrop) { moveItemInArray(this.sectionsArray().controls, event.previousIndex, event.currentIndex); moveItemInArray(this.sectionsArray().value, event.previousIndex, event.currentIndex); } moveItemInFormArray(formArray: FormArray, fromIndex: number, toIndex: number): void { const dir = toIndex > fromIndex ? 1 : -1; const item = formArray.at(fromIndex); for (let i = fromIndex; i * dir < toIndex * dir; i = i + dir) { const current = formArray.at(i + dir); formArray.setControl(i, current); } formArray.setControl(toIndex, item); } // clearForm(): void{ // this.dmpBlueprintsFormGroup.reset(); // } canGoUp(index: number): boolean { return index > 0; } canGoDown(index: number): boolean { return index < (this.sectionsArray().length - 1); } onRemoveTemplate(event, sectionIndex: number) { // let found = false; // const profiles = this.descriptionTemplatesArray(sectionIndex).value;//this.formGroup.get('profiles').value; // this.formGroup.get('datasets')['controls'].forEach(element => { // if (element.get('profile').value.id === event.id) { // found = true; // this.uiNotificationService.snackBarNotification(this.language.instant('GENERAL.SNACK-BAR.UNSUCCESSFUL-REMOVE-TEMPLATE'), SnackBarNotificationLevel.Success); // } // }); // if (found) { // this.formGroup.get('profiles').setValue(profiles); // this.profilesAutoCompleteConfiguration = { // filterFn: this.filterProfiles.bind(this), // initialItems: (excludedItems: any[]) => this.filterProfiles('').pipe(map(result => result.filter(resultItem => (excludedItems || []).map(x => x.id).indexOf(resultItem.id) === -1))), // displayFn: (item) => item['label'], // titleFn: (item) => item['label'], // subtitleFn: (item) => item['description'], // popupItemActionIcon: 'visibility' // }; // } } onPreviewTemplate(event, sectionIndex: number) { const dialogRef = this.dialog.open(DatasetPreviewDialogComponent, { width: '590px', minHeight: '200px', restoreFocus: false, data: { template: event }, panelClass: 'custom-modalbox' }); dialogRef.afterClosed().pipe(takeUntil(this._destroyed)).subscribe(result => { if (result) { let profiles = this.descriptionTemplatesArray(sectionIndex).value;//this.formGroup.get('profiles').value; profiles.push(event); this.descriptionTemplatesArray(sectionIndex).setValue(profiles);//this.formGroup.get('profiles').setValue(profiles); this.profilesAutoCompleteConfiguration = { filterFn: this.filterProfiles.bind(this), initialItems: (excludedItems: any[]) => this.filterProfiles('').pipe(map(result => result.filter(resultItem => (excludedItems || []).map(x => x.id).indexOf(resultItem.id) === -1))), displayFn: (item) => item['label'], titleFn: (item) => item['label'], subtitleFn: (item) => item['description'], popupItemActionIcon: 'visibility' }; } }); } onOptionSelected(sectionIndex: number){ try{ const profiles = this.descriptionTemplatesArray(sectionIndex).value as {id:string, label:string}[];//this.formGroup.get('profiles').value as {id:string, label:string}[]; const profileCounts: Map = new Map(); profiles.forEach((value) => profileCounts.set(value.id, (profileCounts.get(value.id) !== undefined ? profileCounts.get(value.id): 0 ) + 1)); const duplicateProfiles = profiles.filter((value) => { let isOk = profileCounts.get(value.id) > 1; if (isOk) { profileCounts.set(value.id, 0); } return isOk; }); duplicateProfiles.forEach((value) => profiles.splice(profiles.lastIndexOf(value), 1)); profiles.sort((a,b)=> a.label.localeCompare(b.label)); } catch{ console.info('Could not sort Dataset Templates') } } formSubmit(): void { this.formService.touchAllFormFields(this.formGroup); if (!this.isFormValid()) { return; } this.onSubmit(); } public isFormValid() { return this.formGroup.valid; } onSubmit(): void { this.dmpProfileService.createDmp(this.formGroup.value) .pipe(takeUntil(this._destroyed)) .subscribe( complete => this.onCallbackSuccess(), error => this.onCallbackError(error) ); } onCallbackSuccess(): void { this.uiNotificationService.snackBarNotification(this.isNew ? this.language.instant('GENERAL.SNACK-BAR.SUCCESSFUL-CREATION') : this.language.instant('GENERAL.SNACK-BAR.SUCCESSFUL-UPDATE'), SnackBarNotificationLevel.Success); this.router.navigate(['/dmp-profiles']); } onCallbackError(errorResponse: any) { this.setErrorModel(errorResponse.error); this.formService.validateAllFormFields(this.formGroup); } public setErrorModel(validationErrorModel: ValidationErrorModel) { Object.keys(validationErrorModel).forEach(item => { (this.dmpProfileModel.validationErrorModel)[item] = (validationErrorModel)[item]; }); } public cancel(): void { this.router.navigate(['/dmp-profiles']); } // addField() { // (this.formGroup.get('definition').get('fields')).push(new DmpProfileFieldEditorModel().buildForm()); // } // removeField(index: number) { // (this.formGroup.get('definition').get('fields')).controls.splice(index, 1); // } getDMPProfileFieldDataTypeValues(): Number[] { let keys: string[] = Object.keys(DmpProfileFieldDataType); keys = keys.slice(0, keys.length / 2); const values: Number[] = keys.map(Number); return values; } getDMPProfileFieldDataTypeWithLanguage(fieldType: DmpProfileFieldDataType): string { let result = ''; this.language.get(this.enumUtils.toDmpProfileFieldDataTypeString(fieldType)) .pipe(takeUntil(this._destroyed)) .subscribe((value: string) => { result = value; }); return result; } getDMPProfileFieldTypeValues(): Number[] { let keys: string[] = Object.keys(DmpProfileType); keys = keys.slice(0, keys.length / 2); const values: Number[] = keys.map(Number); return values; } getDMPProfileFieldTypeWithLanguage(profileType: DmpProfileType): string { let result = ''; this.language.get(this.enumUtils.toDmpProfileTypeString(profileType)) .pipe(takeUntil(this._destroyed)) .subscribe((value: string) => { result = value; }); return result; } delete() { this.dialog.open(ConfirmationDialogComponent,{data:{ isDeleteConfirmation: true, confirmButton: this.language.instant('DMP-PROFILE-EDITOR.CONFIRM-DELETE-DIALOG.CONFIRM-BUTTON'), cancelButton: this.language.instant("DMP-PROFILE-EDITOR.CONFIRM-DELETE-DIALOG.CANCEL-BUTTON"), message: this.language.instant("DMP-PROFILE-EDITOR.CONFIRM-DELETE-DIALOG.MESSAGE") }}) .afterClosed() .subscribe( confirmed =>{ if(confirmed){ this.formGroup.get('status').setValue(DmpProfileStatus.Deleted); this.dmpProfileService.createDmp(this.formGroup.value) .pipe(takeUntil(this._destroyed)) .subscribe( complete => this.onCallbackSuccess(), error => this.onCallbackError(error) ); } } ) } finalize() { //const data = this.form.value; this.formGroup.get('status').setValue(DmpProfileStatus.Finalized); this.onSubmit(); } downloadXML(): void { this.dmpProfileService.downloadXML(this.dmpProfileId) .pipe(takeUntil(this._destroyed)) .subscribe(response => { const blob = new Blob([response.body], { type: 'application/xml' }); const filename = this.getFilenameFromContentDispositionHeader(response.headers.get('Content-Disposition')); FileSaver.saveAs(blob, filename); }); } getFilenameFromContentDispositionHeader(header: string): string { const regex: RegExp = new RegExp(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/g); const matches = header.match(regex); let filename: string; for (let i = 0; i < matches.length; i++) { const match = matches[i]; if (match.includes('filename="')) { filename = match.substring(10, match.length - 1); break; } else if (match.includes('filename=')) { filename = match.substring(9); break; } } return filename; } isExternalAutocomplete(formGroup: FormGroup) { if (formGroup.get('dataType').value == DmpProfileFieldDataType.ExternalAutocomplete) { this.addControl(formGroup); return true; } else { this.removeControl(formGroup); return false; } } addControl(formGroup: FormGroup) { if (formGroup.get('dataType').value == 3) formGroup.addControl('externalAutocomplete', new DmpProfileExternalAutoCompleteFieldDataEditorModel().buildForm()); } removeControl(formGroup: FormGroup) { if (formGroup.get('dataType').value != 3) formGroup.removeControl('externalAutocomplete'); } }