argos/dmp-frontend/src/app/ui/description/editor/description-form/components/form-field/form-field.component.ts

381 lines
15 KiB
TypeScript

import { COMMA, ENTER } from '@angular/cdk/keycodes';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit, SimpleChanges } from '@angular/core';
import { UntypedFormControl, UntypedFormGroup } from '@angular/forms';
import { MatCheckboxChange } from '@angular/material/checkbox';
import { MatDialog } from "@angular/material/dialog";
import { DescriptionTemplateFieldType } from '@app/core/common/enum/description-template-field-type';
import { DescriptionTemplateFieldValidationType } from '@app/core/common/enum/description-template-field-validation-type';
import { DescriptionTemplateField, DescriptionTemplateFieldSet, DescriptionTemplateLabelAndMultiplicityData, DescriptionTemplateUploadData } from '@app/core/model/description-template/description-template';
import { StorageFile } from '@app/core/model/storage-file/storage-file';
import { DescriptionService } from '@app/core/services/description/description.service';
import { DmpService } from '@app/core/services/dmp/dmp.service';
import { SnackBarNotificationLevel, UiNotificationService } from "@app/core/services/notification/ui-notification-service";
import { ReferenceService } from '@app/core/services/reference/reference.service';
import { StorageFileService } from '@app/core/services/storage-file/storage-file.service';
import { TagService } from '@app/core/services/tag/tag.service';
import { FileUtils } from '@app/core/services/utilities/file-utils.service';
import { MultipleAutoCompleteConfiguration } from '@app/library/auto-complete/multiple/multiple-auto-complete-configuration';
import { SingleAutoCompleteConfiguration } from '@app/library/auto-complete/single/single-auto-complete-configuration';
import { VisibilityRulesService } from '@app/ui/description/editor/description-form/visibility-rules/visibility-rules.service';
import { BaseComponent } from '@common/base/base.component';
import { FormValidationErrorsDialogComponent } from '@common/forms/form-validation-errors-dialog/form-validation-errors-dialog.component';
import { Guid } from '@common/types/guid';
import { TranslateService } from '@ngx-translate/core';
import * as FileSaver from 'file-saver';
import { Observable } from 'rxjs';
import { distinctUntilChanged, map, takeUntil } from 'rxjs/operators';
import { nameof } from 'ts-simple-nameof';
@Component({
selector: 'app-description-form-field',
templateUrl: './form-field.component.html',
styleUrls: ['./form-field.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class DescriptionFormFieldComponent extends BaseComponent implements OnInit {
@Input() field: DescriptionTemplateField;
@Input() fieldSet: DescriptionTemplateFieldSet;
@Input() propertiesFormGroup: UntypedFormGroup;
@Input() visibilityRulesService: VisibilityRulesService;
isRequired: boolean = false;
@Input() datasetProfileId: any;
@Input() isChild: Boolean = false;
visible: boolean = true;
descriptionTemplateFieldTypeEnum = DescriptionTemplateFieldType;
public singleAutoCompleteConfiguration: SingleAutoCompleteConfiguration;
public multipleAutoCompleteConfiguration: MultipleAutoCompleteConfiguration;
tagsAutoCompleteConfiguration: SingleAutoCompleteConfiguration;
multipleReferenceAutoCompleteConfiguration: MultipleAutoCompleteConfiguration;
readonly separatorKeysCodes: number[] = [ENTER, COMMA];
datasetIdInitialized: boolean = false;
validationIcon;
readonly datasetIdTypes: any[] = [
{ name: 'Handle', value: 'handle' },
{ name: 'DOI', value: 'doi' },
{ name: 'Ark', value: 'ark' },
{ name: 'Url', value: 'url' },
{ name: 'Other', value: 'other' }
];
readonly validationTypes: any[] = [
{ name: 'Zenodo', value: 'zenodo' }
];
filesToUpload: FileList;
fileNameDisplay: string = null;
constructor(
private language: TranslateService,
public dmpService: DmpService,
public descriptionService: DescriptionService,
public tagService: TagService,
private cdr: ChangeDetectorRef,
private uiNotificationService: UiNotificationService,
public dialog: MatDialog,
private fileUtils: FileUtils,
private referenceService: ReferenceService,
private storageFileService: StorageFileService
) {
super();
}
ngOnChanges(changes: SimpleChanges) {
if (changes['form']) {
}
}
ngOnInit() {
if(this.field?.data?.fieldType == DescriptionTemplateFieldType.UPLOAD && this.field && this.field.id && (this.propertiesFormGroup?.get(this.field.id).get('textValue').value != undefined) && !this.fileNameDisplay) {
const id = Guid.parse((this.propertiesFormGroup?.get(this.field.id).get('textValue').value as string));
const fields = [
nameof<StorageFile>(x => x.id),
nameof<StorageFile>(x => x.name),
nameof<StorageFile>(x => x.extension),
]
this.storageFileService.getSingle(id, fields).pipe(takeUntil(this._destroyed)).subscribe(storageFile => {
this.createFileNameDisplay(storageFile.name, storageFile.extension);
this.applyFieldType();
});
} else {
this.applyFieldType();
}
}
checkBoxChanged(event: MatCheckboxChange){
if (event.checked){
this.propertiesFormGroup?.get(this.field.id).get('textValue').setValue("true");
} else{
this.propertiesFormGroup?.get(this.field.id).get('textValue').setValue("false");
}
this.visibilityRulesService.reloadVisibility();
}
private applyFieldType(){
this.isRequired = this.field.validations?.includes(DescriptionTemplateFieldValidationType.Required);
switch (this.field?.data?.fieldType) {
case DescriptionTemplateFieldType.EXTERNAL_DATASETS:
//TODO: refactor
// this.multipleReferenceAutoCompleteConfiguration = {
// filterFn: this.filterOrganisations.bind(this),
// initialItems: (excludedItems: any[]) => this.filterOrganisations('').pipe(map(result => result.filter(resultItem => (excludedItems || []).map(x => x.id).indexOf(resultItem.id) === -1))),
// displayFn: (item) => { try { return typeof (item) == 'string' ? JSON.parse(item)['name'] : item.name } catch { return '' } },
// titleFn: (item) => { try { return typeof (item) == 'string' ? JSON.parse(item)['name'] : item.name } catch { return '' } },
// subtitleFn: (item) => { try { return item['tag'] ? this.language.instant('TYPES.EXTERNAL-DATASET-TYPE.SOURCE:') + item['tag'] : (item['key'] ? this.language.instant('TYPES.EXTERNAL-DATASET-TYPE.SOURCE:') + item['key'] : this.language.instant('TYPES.EXTERNAL-DATASET-TYPE.NO-SOURCE')) } catch { return '' } },
// valueAssign: (item) => { try { return typeof (item) == 'string' ? item : JSON.stringify(item) } catch { return '' } }
// };
break;
case DescriptionTemplateFieldType.TAGS:
this.tagsAutoCompleteConfiguration = {
filterFn: this.filterTags.bind(this),
initialItems: (excludedItems: any[]) => this.filterTags('').pipe(map(result => result.filter(resultItem => (excludedItems || []).map(x => x.id).indexOf(resultItem.id) === -1))),
displayFn: (item) => { try { return this.showTag(item) } catch { return '' } },
titleFn: (item) => { try { return item['name'] } catch { return '' } },
valueAssign: (item) => { try { return this.addTag(item) } catch { return '' } }
};
this.parseTags();
break;
case DescriptionTemplateFieldType.DATASET_IDENTIFIER:
// const value = this.propertiesFormGroup.get(this.field.id).get('value').value;
// const disabled = this.propertiesFormGroup.get(this.field.id).disabled;
//TODO: Refactor this.
// this.form.removeControl('value');
// this.form.addControl('value', new DatasetIdModel(value).buildForm());
// if (disabled) {
// this.form.disable();
// }
this.datasetIdInitialized = true;
break;
case DescriptionTemplateFieldType.VALIDATION:
// const value1 = this.propertiesFormGroup.get(this.field.id).get('value').value;
// const disabled1 = this.propertiesFormGroup.get(this.field.id).disabled;
//TODO: Refactor this.
// this.form.removeControl('value');
// this.form.addControl('value', new DatasetIdModel(value1).buildForm());
// if (disabled1) {
// this.form.disable();
// }
break;
case DescriptionTemplateFieldType.CHECK_BOX:
if (this.propertiesFormGroup?.get(this.field.id).get('textValue').value == undefined) this.propertiesFormGroup?.get(this.field.id).get('textValue').setValue("false");
this.visibilityRulesService.reloadVisibility();
break;
}
// this.form = this.visibilityRulesService.getFormGroup(this.field.id);
//TODO: refactor
this.propertiesFormGroup.get(this.field.id).valueChanges
.pipe(
takeUntil(this._destroyed),
distinctUntilChanged()
)
.subscribe(item => {
this.visibilityRulesService.updateVisibilityForSource(this.field?.id);
});
}
makeAutocompleteConfiguration(myfunc: Function, title: string, subtitle?: string): void {
if (!((this.field.data as DescriptionTemplateLabelAndMultiplicityData).multipleSelect)) {
this.singleAutoCompleteConfiguration = {
filterFn: myfunc.bind(this),
initialItems: (extraData) => myfunc(''),
displayFn: (item) => { try { return (item != null && item.length > 1) ? JSON.parse(item)[title] : item[title] } catch { return '' } },
titleFn: (item) => { try { return item[title] } catch { return '' } },
valueAssign: (item) => JSON.stringify(item),
subtitleFn: (item) => { try { return item[subtitle] } catch { return '' } }
};
}
else {
this.multipleAutoCompleteConfiguration = {
filterFn: myfunc.bind(this),
initialItems: (extraData) => myfunc(''),
displayFn: (item) => { try { return typeof (item) == 'string' ? JSON.parse(item)[title] : item[title] } catch { return '' } },
titleFn: (item) => { try { return typeof (item) == 'string' ? JSON.parse(item)[title] : item[title] } catch { return '' } },
valueAssign: (item) => { try { return typeof (item) == 'string' ? item : JSON.stringify(item) } catch { return '' } },
subtitleFn: (item) => { try { return item[subtitle] } catch { return '' } }
}
}
}
parseTags() {
try {
let stringValue = this.propertiesFormGroup.get(this.field.id).get('value').value;
if (typeof stringValue === 'string') {
stringValue = (<string>stringValue).replace(new RegExp('{', 'g'), '{"').replace(new RegExp('=', 'g'), '":"').replace(new RegExp(',', 'g'), '",').replace(new RegExp(', ', 'g'), ', "').replace(new RegExp('}', 'g'), '"}');
stringValue = stringValue.replace(new RegExp('}"', 'g'), '}').replace(new RegExp('"{', 'g'), '{');
} else if (stringValue instanceof Array) {
const tempArray = new Array();
for (let stringTag of stringValue) {
tempArray.push(JSON.parse(stringTag));
}
stringValue = JSON.stringify(tempArray);
}
const tagArray = JSON.parse(stringValue);
this.propertiesFormGroup.get(this.field.id).get('value').patchValue(tagArray);
} catch (e) {
console.warn('Could not parse tags');
}
}
filterTags(value: string): Observable<any[]> {
//TODO refactor
return null;
// const requestItem: RequestItem<TagCriteria> = new RequestItem();
// const criteria: TagCriteria = new TagCriteria();
// criteria.like = value;
// requestItem.criteria = criteria;
// return this.externalSourcesService.searchDatasetTags(requestItem);
}
showTag(ev: any) {
if (typeof ev === 'string') {
return ev;
} else {
return ev.name;
}
}
addTag(ev: any) {
// let item: ExternalTagEditorModel;
// //this.filteredTags = this.formGroup.get('tags').value;
// if (typeof ev === 'string') {
// item = new ExternalTagEditorModel('', ev);
// } else {
// item = ev;
// }
// if (item.name !== '') {
// return item;
// }
}
getDatasetIdControl(name: string): UntypedFormControl {
return this.propertiesFormGroup.get(this.field.id).get(name) as UntypedFormControl;
}
validateId() {
//TODO refactor
return null;
// const identifier = this.getDatasetIdControl('identifier').value;
// const type = this.getDatasetIdControl('type').value;
// this.validationIcon = 'loading';
// this.externalSourcesService.validateIdentifier(identifier, type).pipe(takeUntil(this._destroyed)).subscribe(result => {
// this.validationIcon = result === true ? 'done' : 'clear';
// });
}
public upload() {
this.storageFileService.uploadTempFiles(this.filesToUpload[0])
.pipe(takeUntil(this._destroyed)).subscribe((response) => {
this.propertiesFormGroup?.get(this.field.id).get('textValue').patchValue(response[0].id.toString());
this.createFileNameDisplay(response[0].name, response[0].extension);
this.cdr.detectChanges();
}, error => {
this.onCallbackUploadFail(error.error);
})
}
private createFileNameDisplay(name: string, extension: string){
if (extension.startsWith('.')) this.fileNameDisplay = name + extension;
else this.fileNameDisplay = name + '.' + extension;
}
private onCallbackUploadFail(error: any) {
this.makeFilesNull();
this.uiNotificationService.snackBarNotification(this.language.instant(error.message), SnackBarNotificationLevel.Error);
}
fileChangeEvent(fileInput: any, dropped: boolean = false) {
if (dropped) {
this.filesToUpload = fileInput.addedFiles;
} else {
this.filesToUpload = fileInput.target.files;
}
let messages: string[] = [];
if (this.filesToUpload.length == 0) {
messages.push(this.language.instant('DATASET-WIZARD.MESSAGES.NO-FILES-SELECTED'));
return;
} else {
let fileToUpload = this.filesToUpload[0];
const data = this.field.data as DescriptionTemplateUploadData;
if (data && data.types
&& data.types.map(type => type.value).includes(fileToUpload.type)
&& data.maxFileSizeInMB
&& data.maxFileSizeInMB * 1048576 >= fileToUpload.size) {
this.upload();
} else {
this.filesToUpload = null;
messages.push(this.language.instant('DATASET-WIZARD.MESSAGES.LARGE-FILE-OR-UNACCEPTED-TYPE'));
messages.push(this.language.instant('DATASET-WIZARD.MESSAGES.MAX-FILE-SIZE', { 'maxfilesize': data.maxFileSizeInMB }));
messages.push(this.language.instant('DATASET-WIZARD.MESSAGES.ACCEPTED-FILE-TRANSFOMER') + data.types.map(type => type.value).join(", "));
}
if (messages && messages.length > 0) {
this.dialog.open(FormValidationErrorsDialogComponent, {
data: {
errorMessages: messages
}
})
}
}
}
onRemove(makeFilesNull: boolean = true) {
this.makeFilesNull()
this.cdr.detectChanges();
}
makeFilesNull() {
this.filesToUpload = null;
this.propertiesFormGroup?.get(this.field.id).get('textValue').patchValue(null);
this.fileNameDisplay = null;
}
typesToString() {
return (this.field.data as DescriptionTemplateUploadData).types.map(type => type.value).toString();
}
download(): void {
if (this.propertiesFormGroup?.get(this.field.id).get('textValue').value) {
const id = Guid.parse((this.propertiesFormGroup?.get(this.field.id).get('textValue').value as string));
this.storageFileService.download(id).pipe(takeUntil(this._destroyed))
.subscribe(response => {
const blob = new Blob([response.body]);
const filename = this.fileUtils.getFilenameFromContentDispositionHeader(response.headers.get('Content-Disposition'));
FileSaver.saveAs(blob, filename);
});
}
}
}