import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop'; import { DatePipe } from '@angular/common'; import { Component, OnInit } from '@angular/core'; import { FormArray, UntypedFormArray, UntypedFormGroup } from '@angular/forms'; import { MatDialog } from '@angular/material/dialog'; import { ActivatedRoute, Router } from '@angular/router'; import { DescriptionStatus } from '@app/core/common/enum/description-status'; import { DmpAccessType } from '@app/core/common/enum/dmp-access-type'; import { DmpBlueprintFieldCategory } from '@app/core/common/enum/dmp-blueprint-field-category'; import { DmpBlueprintExtraFieldDataType } from '@app/core/common/enum/dmp-blueprint-field-type'; import { DmpBlueprintStatus } from '@app/core/common/enum/dmp-blueprint-status'; import { DmpBlueprintSystemFieldType } from '@app/core/common/enum/dmp-blueprint-system-field-type'; import { DmpContactType } from '@app/core/common/enum/dmp-contact-type'; import { DmpStatus } from '@app/core/common/enum/dmp-status'; import { DmpUserRole } from '@app/core/common/enum/dmp-user-role'; import { DmpUserType } from '@app/core/common/enum/dmp-user-type'; import { IsActive } from '@app/core/common/enum/is-active.enum'; import { LockTargetType } from '@app/core/common/enum/lock-target-type'; import { AppPermission } from '@app/core/common/enum/permission.enum'; import { DescriptionSectionPermissionResolver } from '@app/core/model/description/description'; import { DmpBlueprint } from '@app/core/model/dmp-blueprint/dmp-blueprint'; import { Dmp, DmpPersist } from '@app/core/model/dmp/dmp'; import { LanguageInfo } from '@app/core/model/language-info'; import { AuthService } from '@app/core/services/auth/auth.service'; import { ConfigurationService } from '@app/core/services/configuration/configuration.service'; import { LanguageInfoService } from '@app/core/services/culture/language-info-service'; import { DescriptionTemplateService } from '@app/core/services/description-template/description-template.service'; import { DescriptionService } from '@app/core/services/description/description.service'; import { DmpBlueprintService } from '@app/core/services/dmp/dmp-blueprint.service'; import { DmpService } from '@app/core/services/dmp/dmp.service'; import { LockService } from '@app/core/services/lock/lock.service'; import { LoggingService } from '@app/core/services/logging/logging-service'; import { MatomoService } from '@app/core/services/matomo/matomo-service'; import { SnackBarNotificationLevel, UiNotificationService } from '@app/core/services/notification/ui-notification-service'; import { UserService } from '@app/core/services/user/user.service'; import { EnumUtils } from '@app/core/services/utilities/enum-utils.service'; import { QueryParamsService } from '@app/core/services/utilities/query-params.service'; import { SingleAutoCompleteConfiguration } from '@app/library/auto-complete/single/single-auto-complete-configuration'; import { PopupNotificationDialogComponent } from '@app/library/notification/popup/popup-notification.component'; import { isNullOrUndefined } from '@app/utilities/enhancers/utils'; import { BaseEditor } from '@common/base/base-editor'; import { FormService } from '@common/forms/form-service'; import { ConfirmationDialogComponent } from '@common/modules/confirmation-dialog/confirmation-dialog.component'; import { HttpErrorHandlingService } from '@common/modules/errors/error-handling/http-error-handling.service'; import { FilterService } from '@common/modules/text-filter/filter-service'; import { Guid } from '@common/types/guid'; import { TranslateService } from '@ngx-translate/core'; import { interval } from 'rxjs'; import { map, takeUntil } from 'rxjs/operators'; import { DmpEditorModel } from './dmp-editor.model'; import { DmpEditorResolver } from './dmp-editor.resolver'; import { DmpEditorService } from './dmp-editor.service'; @Component({ selector: 'app-dmp-editor', templateUrl: './dmp-editor.component.html', styleUrls: ['./dmp-editor.component.scss'], providers: [DmpEditorService] }) export class DmpEditorComponent extends BaseEditor implements OnInit { isNew = true; isDeleted = false; item: Dmp; selectedBlueprint: DmpBlueprint; step: number = 0; //Enums descriptionStatusEnum = DescriptionStatus; dmpBlueprintSectionFieldCategoryEnum = DmpBlueprintFieldCategory; dmpBlueprintSystemFieldTypeEnum = DmpBlueprintSystemFieldType; dmpBlueprintExtraFieldDataTypeEnum = DmpBlueprintExtraFieldDataType; dmpAccessTypeEnum = DmpAccessType; dmpAccessTypeEnumValues = this.enumUtils.getEnumValues(DmpAccessType); dmpContactTypeEnum = DmpContactType; dmpContactTypeEnumValues = this.enumUtils.getEnumValues(DmpContactType); dmpUserTypeEnum = DmpUserType; dmpUserTypeEnumValues = this.enumUtils.getEnumValues(DmpUserType); dmpUserRoleEnumValues = this.enumUtils.getEnumValues(DmpUserRole); permissionPerSection: Map; hoveredContact: number = -1; singleAutocompleteBlueprintConfiguration: SingleAutoCompleteConfiguration = { initialItems: (data?: any) => this.dmpBlueprintService.query(this.dmpBlueprintService.buildAutocompleteLookup(null, null, null, [DmpBlueprintStatus.Finalized])).pipe(map(x => x.items)), filterFn: (searchQuery: string, data?: any) => this.dmpBlueprintService.query(this.dmpBlueprintService.buildAutocompleteLookup(searchQuery, null, null, [DmpBlueprintStatus.Finalized])).pipe(map(x => x.items)), getSelectedItem: (selectedItem: any) => this.dmpBlueprintService.query(this.dmpBlueprintService.buildAutocompleteLookup(null, null, [selectedItem])).pipe(map(x => x.items[0])), displayFn: (item: DmpBlueprint) => item.label, titleFn: (item: DmpBlueprint) => item.label, valueAssign: (item: DmpBlueprint) => item.id, }; protected get canDelete(): boolean { return !this.isDeleted && !this.isNew && (this.hasPermission(this.authService.permissionEnum.DeleteDmp) || this.item?.authorizationFlags?.some(x => x === AppPermission.DeleteDmp)); } protected get canSave(): boolean { return !this.isDeleted && (this.hasPermission(this.authService.permissionEnum.EditDmp) || this.item?.authorizationFlags?.some(x => x === AppPermission.EditDmp)); } protected get canFinalize(): boolean { return !this.isDeleted && (this.hasPermission(this.authService.permissionEnum.EditDmp) || this.item?.authorizationFlags?.some(x => x === AppPermission.EditDmp)); } protected canEditSection(id: Guid): boolean { return !this.isDeleted && (this.hasPermission(this.authService.permissionEnum.EditDescription) || this.item?.authorizationFlags?.some(x => x === AppPermission.EditDescription) || ( this.permissionPerSection && this.permissionPerSection[id.toString()] && this.permissionPerSection[id.toString()].some(x => x === AppPermission.EditDescription) )); } protected canDeleteSection(id: Guid): boolean { return !this.isDeleted && (this.hasPermission(this.authService.permissionEnum.DeleteDescription) || this.item?.authorizationFlags?.some(x => x === AppPermission.DeleteDescription) || ( this.permissionPerSection && this.permissionPerSection[id.toString()] && this.permissionPerSection[id.toString()].some(x => x === AppPermission.DeleteDescription) )); } private hasPermission(permission: AppPermission): boolean { return this.authService.hasPermission(permission) || this.item?.authorizationFlags?.some(x => x === permission) || this.editorModel?.permissions?.includes(permission); } constructor( // BaseFormEditor injected dependencies protected dialog: MatDialog, protected language: TranslateService, protected formService: FormService, protected router: Router, protected uiNotificationService: UiNotificationService, protected httpErrorHandlingService: HttpErrorHandlingService, protected filterService: FilterService, protected datePipe: DatePipe, protected route: ActivatedRoute, protected queryParamsService: QueryParamsService, protected lockService: LockService, protected authService: AuthService, protected configurationService: ConfigurationService, // Rest dependencies. Inject any other needed deps here: private dmpService: DmpService, private logger: LoggingService, public dmpBlueprintService: DmpBlueprintService, private matomoService: MatomoService, // public visibilityRulesService: VisibilityRulesService, private languageInfoService: LanguageInfoService, public enumUtils: EnumUtils, public descriptionTemplateService: DescriptionTemplateService, public userService: UserService, public descriptionService: DescriptionService ) { super(dialog, language, formService, router, uiNotificationService, httpErrorHandlingService, filterService, datePipe, route, queryParamsService, lockService, authService, configurationService); } ngOnInit(): void { this.matomoService.trackPageView('DMP Editor'); super.ngOnInit(); } getItem(itemId: Guid, successFunction: (item: Dmp) => void) { this.dmpService.getSingle(itemId, DmpEditorResolver.lookupFields()) .pipe(map(data => data as Dmp), takeUntil(this._destroyed)) .subscribe( data => successFunction(data), error => this.onCallbackError(error) ); } prepareForm(data: Dmp) { try { this.editorModel = data ? new DmpEditorModel().fromModel(data) : new DmpEditorModel(); if (data && data.descriptions) { if (data.status == DmpStatus.Finalized) { data.descriptions = data.descriptions.filter(x => x.isActive === IsActive.Active && x.status === DescriptionStatus.Finalized); } else { data.descriptions = data.descriptions.filter(x => x.isActive === IsActive.Active && x.status !== DescriptionStatus.Canceled); } } this.item = data; this.selectedBlueprint = data?.blueprint; this.isDeleted = data ? data.isActive === IsActive.Inactive : false; if (data && data.id) { const descriptionSectionPermissionResolverModel: DescriptionSectionPermissionResolver = { dmpId: data.id, sectionIds: data?.blueprint?.definition?.sections?.map(x => x.id), permissions: [AppPermission.EditDescription, AppPermission.DeleteDescription] } this.descriptionService.getDescriptionSectionPermissions(descriptionSectionPermissionResolverModel) .pipe(takeUntil(this._destroyed)).subscribe( complete => { this.permissionPerSection = complete, this.buildForm(); }, error => this.onCallbackError(error) ); } else { this.buildForm(); } if (this.item && this.item.id != null) { this.checkLock(this.item.id, LockTargetType.Dmp, 'DMP-EDITOR.LOCKED-DIALOG.TITLE', 'DMP-EDITOR.LOCKED-DIALOG.MESSAGE'); } } catch (error) { this.logger.error('Could not parse Dmp item: ' + data + error); this.uiNotificationService.snackBarNotification(this.language.instant('COMMONS.ERRORS.DEFAULT'), SnackBarNotificationLevel.Error); } } buildForm() { const canedit = this.isNew ? this.authService.hasPermission(AppPermission.NewDmp) : this.item.authorizationFlags?.some(x => x === AppPermission.EditDmp) || this.authService.hasPermission(AppPermission.EditDmp); this.formGroup = this.editorModel.buildForm(null, this.isDeleted || !canedit); if (this.editorModel.status == DmpStatus.Finalized || this.isDeleted) { this.formGroup.disable(); } if (this.item != null) { this.nextStep(); } } refreshData(): void { this.getItem(this.editorModel.id, (data: Dmp) => this.prepareForm(data)); } refreshOnNavigateToData(id?: Guid): void { this.formGroup.markAsPristine(); if (this.isNew) { let route = []; route.push('/plans/overview/' + id); this.router.navigate(route, { queryParams: { 'lookup': this.queryParamsService.serializeLookup(this.lookupParams), 'lv': ++this.lv }, replaceUrl: true, relativeTo: this.route }); } else { this.refreshData(); } } persistEntity(onSuccess?: (response) => void): void { const formData = this.formService.getValue(this.formGroup.value) as DmpPersist; //Transform to persist //Transform descriptionTemplates formData.descriptionTemplates = []; for (const sectionId in (this.formGroup.get('descriptionTemplates') as UntypedFormGroup).controls) { formData.descriptionTemplates.push( ...(this.formGroup.get('descriptionTemplates').get(sectionId).value as Guid[]).map(x => { return { sectionId: Guid.parse(sectionId), descriptionTemplateGroupId: x } }) ); } this.dmpService.persist(formData) .pipe(takeUntil(this._destroyed)).subscribe( complete => onSuccess ? onSuccess(complete) : this.onCallbackSuccess(complete), error => this.onCallbackError(error) ); } formSubmit(): void { this.formService.removeAllBackEndErrors(this.formGroup); this.formService.touchAllFormFields(this.formGroup); if (!this.isFormValid()) { return; } this.persistEntity(); } public delete() { const value = this.formGroup.value; if (value.id) { const dialogRef = this.dialog.open(ConfirmationDialogComponent, { maxWidth: '300px', data: { message: this.language.instant('GENERAL.CONFIRMATION-DIALOG.DELETE-ITEM'), confirmButton: this.language.instant('GENERAL.CONFIRMATION-DIALOG.ACTIONS.CONFIRM'), cancelButton: this.language.instant('GENERAL.CONFIRMATION-DIALOG.ACTIONS.CANCEL') } }); dialogRef.afterClosed().pipe(takeUntil(this._destroyed)).subscribe(result => { if (result) { this.dmpService.delete(value.id).pipe(takeUntil(this._destroyed)) .subscribe( complete => this.onCallbackSuccess(), error => this.onCallbackError(error) ); } }); } } clearErrorModel() { this.editorModel.validationErrorModel.clear(); this.formService.validateAllFormFields(this.formGroup); } // // // Steps // // changeStep(index: number) { this.step = index; this.resetScroll(); } nextStep() { this.step = this.step < this.selectedBlueprint.definition.sections.length ? this.step + 1 : this.step; this.resetScroll(); } previousStep() { this.step = this.step !== 1 ? this.step - 1 : this.step; this.resetScroll(); } private resetScroll() { if (document.getElementById('editor-form') != null) document.getElementById('editor-form').scrollTop = 0; } // // // Blueprint // // selectBlueprint() { this.dmpBlueprintService.getSingle(this.formGroup.get('blueprint').value, DmpEditorResolver.blueprintLookupFields()).pipe(takeUntil(this._destroyed)).subscribe(data => { this.selectedBlueprint = data; this.buildFormAfterBlueprintSelection(); this.nextStep(); }); } selectDefaultBlueprint() { this.dmpBlueprintService.getSingle(this.configurationService.defaultBlueprintId, DmpEditorResolver.blueprintLookupFields()).pipe(takeUntil(this._destroyed)).subscribe(data => { this.selectedBlueprint = data; this.formGroup.get('blueprint').setValue(this.selectedBlueprint.id); this.buildFormAfterBlueprintSelection(); this.nextStep(); }); } private buildFormAfterBlueprintSelection() { const dmp: Dmp = { label: this.formGroup.get('label').value, description: this.formGroup.get('description').value, blueprint: this.selectedBlueprint, status: DmpStatus.Draft } this.prepareForm(dmp); } // // // Contacts // // isContactSelected(contactId: number): boolean { return this.hoveredContact === contactId; } onContactHover(contactIndex: number): void { this.hoveredContact = contactIndex; } clearHoveredContact(): void { this.hoveredContact = -1; } addContact(): void { const contactArray = this.formGroup.get('properties').get('contacts') as FormArray; (this.formGroup.get('properties').get('contacts') as FormArray).push(this.editorModel.createChildContact(contactArray.length)); } removeContact(contactIndex: number): void { (this.formGroup.get('properties').get('contacts') as FormArray).removeAt(contactIndex); DmpEditorModel.reApplyPropertiesValidators( { formGroup: this.formGroup, validationErrorModel: this.editorModel.validationErrorModel } ); this.formGroup.get('properties').get('contacts').markAsDirty(); } dropContacts(event: CdkDragDrop) { const contactsFormArray = (this.formGroup.get('properties').get('contacts') as FormArray); moveItemInArray(contactsFormArray.controls, event.previousIndex, event.currentIndex); contactsFormArray.updateValueAndValidity(); DmpEditorModel.reApplyPropertiesValidators( { formGroup: this.formGroup, validationErrorModel: this.editorModel.validationErrorModel } ); this.formGroup.get('properties').get('contacts').markAsDirty(); } // // // Descriptions // // public descriptionsInSection(sectionId: Guid) { return this.item?.descriptions?.filter(x => x?.dmpDescriptionTemplate?.sectionId === sectionId) || []; } editDescription(id: string, isNew: boolean) { if (!isNew) { this.router.navigate(['/descriptions', 'edit', id]); } } public removeDescription(descriptionId: Guid) { const dialogRef = this.dialog.open(ConfirmationDialogComponent, { maxWidth: '300px', restoreFocus: false, data: { message: this.language.instant('GENERAL.CONFIRMATION-DIALOG.DELETE-ITEM'), confirmButton: this.language.instant('GENERAL.CONFIRMATION-DIALOG.ACTIONS.DELETE'), cancelButton: this.language.instant('GENERAL.CONFIRMATION-DIALOG.ACTIONS.CANCEL'), isDeleteConfirmation: true } }); dialogRef.afterClosed().pipe(takeUntil(this._destroyed)).subscribe(result => { if (result) { if (descriptionId) { this.descriptionService.delete(descriptionId) .pipe(takeUntil(this._destroyed)) .subscribe( complete => { this.onCallbackSuccess(); }, error => this.onCallbackError(error) ); } // this.formGroup.get('datasets')['controls'].splice(index, 1); this.step = 0; } }); } // // // Description Template // // onRemoveDescriptionTemplate(event, sectionIndex: number) { let found = false; const section = this.selectedBlueprint.definition.sections[sectionIndex]; let sectionDescriptionTemplates = (this.formGroup.get('descriptionTemplates') as UntypedFormArray).controls.find(x => x.get('sectionId').value === event.id); } onPreviewDescriptionTemplate(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) { // this.addProfile(event, sectionIndex); // 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' // }; // } // }); } // // // Misc // // public isDirty(): boolean { return this.formGroup && this.formGroup.dirty; //&& this.hasChanges; } getLanguageInfos(): LanguageInfo[] { return this.languageInfoService.getLanguageInfoValues(); } }