#7528: [NEW] Upload files field type - frontend.

1. dataset-profile-field-view-style.ts: Include "upload" field type.
2. field-data.ts: Export interface UploadFieldData.
3. config.json: Added new property "maxFileSizeInMB":10.
4. configuration.service.ts: Added _maxFileSizeInMB field and getter for it.
5. enum-utils.service.ts: Added cases for upload and table (not ready, to be added) field types.
6. rich-text-editor.module.ts: [Bug fix] Removed import of HttpClientModule (this is imported only once in order to work with interceptor).
7. field-editor-model.ts & dataset-profile-editor.component.ts: Added case and validators for "upload" field type.
8. dataset-profile.module.ts: Imports for upload.
9.dataset-profile-editor-composite-field.component & dataset-profile-editor-field.component: Added button for upload.
10. form-field.component: Added functionality for uploading and downloading files.
11. dataset-description-form.module.ts: Added "FileService" provider and import of "NgxDropzoneModule".
12. en.json: New literals for upload and table (to be added in other language files too).
13. New files for upload field type: file.service.ts, upload-field-data-editor-model.ts, dataset-profile-editor-upload-field.component.html, dataset-profile-editor-upload-field.component.ts, dataset-profile-editor-upload-field.component.scss
This commit is contained in:
Konstantina Galouni 2022-03-16 13:00:03 +02:00
parent 2a9d8b8296
commit 9ab9e6abc9
24 changed files with 672 additions and 20 deletions

View File

@ -1,6 +1,8 @@
export enum DatasetProfileFieldViewStyle {
TextArea = "textarea",
RichTextArea = "richTextarea",
Table = "table",
Upload = "upload",
BooleanDecision = "booleanDecision",
ComboBox = "combobox",
CheckBox = "checkBox",

View File

@ -43,6 +43,16 @@ export interface RichTextAreaFieldData extends FieldData {
}
// export interface TableFieldData extends FieldData {
// headers: string[];
// rows: Array<string[]>;
// }
export interface UploadFieldData extends FieldData {
types: Array<FieldDataOption>;
maxFileSizeInMB: number;
}
export interface WordListFieldData extends FieldData {
type: DatasetProfileComboBoxType;
options: Array<FieldDataOption>;

View File

@ -91,6 +91,11 @@ export class ConfigurationService extends BaseComponent {
return this._matomoSiteId;
}
private _maxFileSizeInMB: number;
get maxFileSizeInMB(): number {
return this._maxFileSizeInMB;
}
public loadConfiguration(): Promise<any> {
return new Promise((r, e) => {
@ -134,6 +139,7 @@ export class ConfigurationService extends BaseComponent {
this._matomoSiteUrl = config.matomo.url;
this._matomoSiteId = config.matomo.siteId;
}
this._maxFileSizeInMB = config.maxFileSizeInMB;
}
}

View File

@ -0,0 +1,68 @@
import {RequestItem} from "@app/core/query/request-item";
import {ResearcherCriteria} from "@app/core/query/researcher/researcher-criteria";
import {Observable} from "rxjs";
import {ExternalSourceItemModel} from "@app/core/model/external-sources/external-source-item";
import {Injectable} from "@angular/core";
import {HttpClient, HttpHeaders, HttpResponse} from "@angular/common/http";
import {BaseHttpService} from "@app/core/services/http/base-http.service";
import {ConfigurationService} from "@app/core/services/configuration/configuration.service";
import {BaseHttpParams} from "@common/http/base-http-params";
import {InterceptorType} from "@common/http/interceptors/interceptor-type";
import {tap} from "rxjs/operators";
@Injectable()
export class FileService {
private actionUrl: string;
private headers: HttpHeaders;
constructor(private http: BaseHttpService, private httpClient: HttpClient, private configurationService: ConfigurationService) {
this.actionUrl = configurationService.server + 'file/';
this.headers = new HttpHeaders();
this.headers = this.headers.set('Content-Type', 'application/json');
this.headers = this.headers.set('Accept', 'application/json');
}
public upload(fileList: File, datasetProfileId: string, fieldId: string): Observable<any> {
const formData: FormData = new FormData();
// if (fileList instanceof FileList) {
// for (let i = 0; i < fileList.length; i++) {
// formData.append('file', fileList[i], "test-konstantina");
// }
// } else if (Array.isArray(fileList)) {
// formData.append('files', fileList);
// } else {
formData.append('file', fileList);
formData.append('datasetProfileId', datasetProfileId);
formData.append('fieldId', fieldId);
// }
// formData.append("fileType", fileList.)
console.log(fileList.type, fileList.name, fileList.size);
const params = new BaseHttpParams();
params.interceptorContext = {
excludedInterceptors: [InterceptorType.JSONContentType]
};
return this.http.post(this.actionUrl + 'upload', formData
, { params: params }
);
}
public deleteFromTempFolder(uuid: string) {
const params = new BaseHttpParams();
params.interceptorContext = {
excludedInterceptors: [InterceptorType.JSONContentType]
};
return this.http.post(this.actionUrl + 'delete-temp', uuid
, { params: params }
);
}
public download(id: string): Observable<HttpResponse<Blob>> {
// let headerDocx: HttpHeaders = this.headers.set('Content-Type', 'application/msword')
return this.httpClient.get(this.actionUrl + id, { responseType: 'blob', observe: 'response'
, headers: this.headers
});//.pipe(tap(x => console.log(x));
}
}

View File

@ -80,6 +80,8 @@ export class EnumUtils {
case DatasetProfileFieldViewStyle.RadioBox: return this.language.instant('TYPES.DATASET-PROFILE-FIELD-VIEW-STYLE.RADIO-BOX');
case DatasetProfileFieldViewStyle.TextArea: return this.language.instant('TYPES.DATASET-PROFILE-FIELD-VIEW-STYLE.TEXT-AREA');
case DatasetProfileFieldViewStyle.RichTextArea: return this.language.instant('TYPES.DATASET-PROFILE-FIELD-VIEW-STYLE.RICH-TEXT-AREA');
case DatasetProfileFieldViewStyle.Upload: return this.language.instant('TYPES.DATASET-PROFILE-FIELD-VIEW-STYLE.UPLOAD');
case DatasetProfileFieldViewStyle.Table: return this.language.instant('TYPES.DATASET-PROFILE-FIELD-VIEW-STYLE.TABLE');
case DatasetProfileFieldViewStyle.DatePicker: return this.language.instant('TYPES.DATASET-PROFILE-FIELD-VIEW-STYLE.DATE-PICKER');
case DatasetProfileFieldViewStyle.ExternalDatasets: return this.language.instant('TYPES.DATASET-PROFILE-FIELD-VIEW-STYLE.EXTERNAL-DATASETS');
case DatasetProfileFieldViewStyle.DataRepositories: return this.language.instant('TYPES.DATASET-PROFILE-FIELD-VIEW-STYLE.DATA-REPOSITORIES');
@ -108,6 +110,8 @@ export class EnumUtils {
case ViewStyleType.RadioBox: return this.language.instant('TYPES.DATASET-PROFILE-FIELD-VIEW-STYLE.RADIO-BOX');
case ViewStyleType.TextArea: return this.language.instant('TYPES.DATASET-PROFILE-FIELD-VIEW-STYLE.TEXT-AREA');
case ViewStyleType.RichTextArea: return this.language.instant('TYPES.DATASET-PROFILE-FIELD-VIEW-STYLE.RICH-TEXT-AREA');
case ViewStyleType.Table: return this.language.instant('TYPES.DATASET-PROFILE-FIELD-VIEW-STYLE.TABLE');
case ViewStyleType.Upload: return this.language.instant('TYPES.DATASET-PROFILE-FIELD-VIEW-STYLE.UPLOAD');
case ViewStyleType.DatePicker: return this.language.instant('TYPES.DATASET-PROFILE-FIELD-VIEW-STYLE.DATE-PICKER');
case ViewStyleType.ExternalDatasets: return this.language.instant('TYPES.DATASET-PROFILE-FIELD-VIEW-STYLE.EXTERNAL-DATASETS');
case ViewStyleType.DataRepositories: return this.language.instant('TYPES.DATASET-PROFILE-FIELD-VIEW-STYLE.DATA-REPOSITORIES');

View File

@ -1,5 +1,4 @@
import {NgModule} from "@angular/core";
import {HttpClientModule} from "@angular/common/http";
import {CommonUiModule} from "@common/ui/common-ui.module";
import {CommonFormsModule} from "@common/forms/common-forms.module";
import {AngularEditorModule} from "@kolkov/angular-editor";
@ -9,7 +8,7 @@ import {RichTextEditorComponent} from "@app/library/rich-text-editor/rich-text-e
imports: [
CommonUiModule,
CommonFormsModule,
HttpClientModule, AngularEditorModule
AngularEditorModule
],
declarations: [
RichTextEditorComponent

View File

@ -0,0 +1,39 @@
import { FormGroup } from '@angular/forms';
import {
FieldDataOption,
UploadFieldData
} from '../../../../../core/model/dataset-profile-definition/field-data/field-data';
import { FieldDataEditorModel } from './field-data-editor-model';
import {FieldDataOptionEditorModel} from "@app/ui/admin/dataset-profile/admin/field-data/field-data-option-editor-model";
export class UploadFieldDataEditorModel extends FieldDataEditorModel<UploadFieldDataEditorModel> {
public types: Array<FieldDataOptionEditorModel> = [];
public maxFileSizeInMB: number;
buildForm(disabled: boolean = false, skipDisable: Array<String> = []): FormGroup {
const formGroup = this.formBuilder.group({
label: [{ value: this.label, disabled: (disabled && !skipDisable.includes('UploadFieldDataEditorModel.label')) }],
maxFileSizeInMB: [{ value: this.maxFileSizeInMB, disabled: (disabled && !skipDisable.includes('UploadFieldDataEditorModel.maxFileSizeInMB')) }],
// types: [{ value: this.types, disabled: (disabled && !skipDisable.includes('UploadFieldDataEditorModel.types')) }]
});
const optionsFormArray = new Array<FormGroup>();
if (this.types) {
this.types.forEach(item => {
const form: FormGroup = item.buildForm(disabled, skipDisable);
optionsFormArray.push(form);
});
}
formGroup.addControl('types', this.formBuilder.array(optionsFormArray));
if(disabled && !skipDisable.includes('UploadFieldDataEditorModel.types')) {
formGroup.disable();
}
return formGroup; }
fromModel(item: UploadFieldData): UploadFieldDataEditorModel {
if (item.types) { this.types = item.types.map(x => new FieldDataOptionEditorModel().fromModel(x)); }
this.label = item.label;
this.maxFileSizeInMB = item.maxFileSizeInMB;
// this.types = item.types;
return this;
}
}

View File

@ -11,6 +11,7 @@ import { FreeTextFieldDataEditorModel } from './field-data/free-text-field-data-
import { RadioBoxFieldDataEditorModel } from './field-data/radio-box-field-data-editor-model';
import { TextAreaFieldDataEditorModel } from './field-data/text-area-field-data-editor-model';
import { RichTextAreaFieldDataEditorModel } from './field-data/rich-text-area-field-data-editor-model';
import {UploadFieldDataEditorModel} from "./field-data/upload-field-data-editor-model";
import { WordListFieldDataEditorModel } from './field-data/word-list-field-data-editor-model';
import { ViewStyleEditorModel } from './view-style-editor-model';
import { VisibilityEditorModel } from './visibility-editor-model';
@ -71,6 +72,7 @@ export class FieldEditorModel extends BaseFormModel {
if (this.viewStyle.renderStyle === 'checkBox') { this.data = new CheckBoxFieldDataEditorModel().fromModel(item.data); }
if (this.viewStyle.renderStyle === 'textarea') { this.data = new TextAreaFieldDataEditorModel().fromModel(item.data); }
if (this.viewStyle.renderStyle === 'richTextarea') { this.data = new RichTextAreaFieldDataEditorModel().fromModel(item.data); }
if (this.viewStyle.renderStyle === 'upload') { this.data = new UploadFieldDataEditorModel().fromModel(item.data); }
if (this.viewStyle.renderStyle === 'freetext') { this.data = new FreeTextFieldDataEditorModel().fromModel(item.data); }
if (this.viewStyle.renderStyle === 'booleanDecision') { this.data = new BooleanDecisionFieldDataEditorModel().fromModel(item.data); }
if (this.viewStyle.renderStyle === 'datePicker') { this.data = new DatePickerDataEditorModel().fromModel(item.data); }
@ -148,7 +150,11 @@ export class FieldEditorModel extends BaseFormModel {
}
}else if(renderStyleValue === 'radiobox'){
formGroup.get('data').setValidators(EditorCustomValidators.atLeastOneElementListValidator('options'));
}else if(renderStyleValue === 'upload'){
formGroup.get('data').setValidators(EditorCustomValidators.atLeastOneElementListValidator('types'));
formGroup.get('data').get('maxFileSizeInMB').setValidators(Validators.required);
}
formGroup.get('data').updateValueAndValidity();
}

View File

@ -59,6 +59,9 @@ import {DatasetProfileEditorLicensesFieldComponent} from "@app/ui/admin/dataset-
import {DatasetProfileEditorPublicationsFieldComponent} from "@app/ui/admin/dataset-profile/editor/components/field-type/publications/dataset-profile-editor-publications-field.component";
import {DatasetProfileEditorJournalRepositoriesFieldComponent} from "@app/ui/admin/dataset-profile/editor/components/field-type/journal-repositories/dataset-profile-editor-journal-repositories-field.component";
import {DatasetProfileEditorPubRepositoriesFieldComponent} from "@app/ui/admin/dataset-profile/editor/components/field-type/pub-repositories/dataset-profile-editor-pub-repositories-field.component";
import {DatasetProfileEditorUploadFieldComponent} from "@app/ui/admin/dataset-profile/editor/components/field-type/upload/dataset-profile-editor-upload-field.component";
// import { TableEditorModule } from "@app/library/table-editor/table-editor.module";
// import {DatasetProfileEditorTableFieldComponent} from "@app/ui/admin/dataset-profile/editor/components/field-type/table/dataset-profile-editor-table-field.component";
@ -78,7 +81,8 @@ import {DatasetProfileEditorPubRepositoriesFieldComponent} from "@app/ui/admin/d
DragulaModule,
AutoCompleteModule,
TransitionGroupModule,
RichTextEditorModule
RichTextEditorModule,
// TableEditorModule
],
declarations: [
DatasetProfileListingComponent,
@ -97,6 +101,8 @@ import {DatasetProfileEditorPubRepositoriesFieldComponent} from "@app/ui/admin/d
DatasetProfileEditorRadioBoxFieldComponent,
DatasetProfileEditorTextAreaFieldComponent,
DatasetProfileEditorRichTextAreaFieldComponent,
DatasetProfileEditorUploadFieldComponent,
// DatasetProfileEditorTableFieldComponent,
DatasetProfileEditorDatePickerFieldComponent,
DatasetProfileEditorWordListFieldComponent,
DatasetProfileEditorDefaultValueComponent,

View File

@ -285,6 +285,12 @@
{{enumUtils.toDatasetProfileViewTypeString(viewTypeEnum.FreeText)}}
</button>
<mat-divider></mat-divider>
<button mat-list-item (click)="addNewInput(viewTypeEnum.Upload)">
<!-- <img src="/assets/images/editor/icons/text_area.svg" class="input_icon" alt="Upload icon">-->
<mat-icon class="input_icon" style="font-size: 14px; color: #129d99; display: inline-flex; align-items: center">upload</mat-icon>
{{enumUtils.toDatasetProfileViewTypeString(viewTypeEnum.Upload)}}
</button>
<mat-divider></mat-divider>
<button mat-list-item (click)="addNewInput(viewTypeEnum.BooleanDecision)">
<img src="/assets/images/editor/icons/boolean.svg" class="input_icon" alt="Boolean icon">
{{enumUtils.toDatasetProfileViewTypeString(viewTypeEnum.BooleanDecision)}}

View File

@ -41,6 +41,7 @@ import {
TagsFieldData,
TextAreaFieldData,
RichTextAreaFieldData,
UploadFieldData,
ValidationFieldData,
WordListFieldData,
TaxonomiesFieldData,
@ -54,6 +55,7 @@ import {debounceTime, delay, map, takeUntil, tap} from 'rxjs/operators';
import {GENERAL_ANIMATIONS} from '../../animations/animations';
import {BaseComponent} from '@common/base/base.component';
import {TransitionGroupComponent} from "@app/ui/transition-group/transition-group.component";
import {ConfigurationService} from "@app/core/services/configuration/configuration.service";
@Component({
selector: 'app-dataset-profile-editor-composite-field-component',
@ -93,8 +95,9 @@ export class DatasetProfileEditorCompositeFieldComponent extends BaseComponent i
private dialog: MatDialog,
private language: TranslateService,
public enumUtils: EnumUtils,
public datasetProfileService: DatasetProfileService
) {
public datasetProfileService: DatasetProfileService,
private configurationService: ConfigurationService
) {
super();
}
@ -702,6 +705,23 @@ export class DatasetProfileEditorCompositeFieldComponent extends BaseComponent i
break;
}
case this.viewTypeEnum.Upload:{
// fieldForm.get('viewStyle').get('renderStyle').setValue(DatasetProfileFieldViewStyle.TextArea)
// fieldForm.addControl('data', new TextAreaFieldDataEditorModel().buildForm());
const data: UploadFieldData = {
label:'',
types: [],
maxFileSizeInMB: this.configurationService.maxFileSizeInMB
}
field.viewStyle.renderStyle = DatasetProfileFieldViewStyle.Upload;
field.data = data;
break;
}
case this.viewTypeEnum.DatePicker:{

View File

@ -0,0 +1,62 @@
<!--[formGroup]="form"-->
<form class="row" *ngIf="form.get('data')">
<h5 style="font-weight: bold" class="col-12">{{'DATASET-PROFILE-EDITOR.STEPS.FORM.FIELD.FIELDS.FIELD-UPLOAD-TITLE'
| translate}}</h5>
<mat-form-field class="col-12">
<mat-label>
{{'DATASET-PROFILE-EDITOR.STEPS.FORM.FIELD.FIELDS.FIELD-UPLOAD-PLACEHOLDER' | translate}}
</mat-label>
<input matInput type="string"
[formControl]="form.get('data').get('label')">
</mat-form-field>
<mat-form-field class="col-12">
<mat-label>
{{ "DATASET-PROFILE-EDITOR.STEPS.FORM.FIELD.FIELDS.FIELD-UPLOAD-MAX-FILE-SIZE" |
translate: { maxfilesize: getConfiguration().maxFileSizeInMB.toString() } }}
</mat-label>
<input matInput type="number" min="1" [max]="getConfiguration().maxFileSizeInMB"
[formControl]="form.get('data').get('maxFileSizeInMB')">
</mat-form-field>
<mat-form-field class="col-12">
<mat-label>
{{'DATASET-PROFILE-EDITOR.STEPS.FORM.FIELD.FIELDS.FIELD-UPLOAD-SELECT-FILETYPE' | translate}}
</mat-label>
<mat-select multiple [formControl]="typesFormControl">
<mat-option *ngFor="let type of types; let i=index" [value]="type.value" (click)="selectedType(type)"
[disabled]="form.get('data').get('types').disabled">
{{type.label}}
</mat-option>
</mat-select>
</mat-form-field>
<div class="col-12">
<div>{{'DATASET-PROFILE-EDITOR.STEPS.FORM.FIELD.FIELDS.FIELD-UPLOAD-CUSTOM-FILETYPE' | translate}}</div>
<ng-container *ngFor="let type of form.get('data').get('types')['controls'] index as i">
<div *ngIf="isCustomType(type.value.value)" class="row">
<mat-form-field class="col">
<mat-label>{{'DATASET-PROFILE-EDITOR.STEPS.FORM.FIELD.FIELDS.FIELD-UPLOAD-LABEL' | translate}}</mat-label>
<input matInput type="string"
[formControl]="this.form.get('data').get('types').get(''+i).get('label')">
</mat-form-field>
<mat-form-field class="col">
<mat-label>{{'DATASET-PROFILE-EDITOR.STEPS.FORM.FIELD.FIELDS.FIELD-UPLOAD-VALUE' | translate}}</mat-label>
<input matInput type="string"
[formControl]="this.form.get('data').get('types').get(''+i).get('value')">
</mat-form-field>
<button mat-icon-button class="col-auto" (click)="deleteRow(i)" type="button"
[disabled]="form.get('data').get('types').disabled">
<mat-icon>delete</mat-icon>
</button>
</div>
</ng-container>
</div>
<div class="col-auto">
<button mat-icon-button (click)="addNewRow()" type="button" [disabled]="form.get('data').get('types').disabled">
<mat-icon>add</mat-icon>
</button>
</div>
</form>

View File

@ -0,0 +1,102 @@
import {Component, Input, OnInit, ViewChild} from '@angular/core';
import {FormArray, FormBuilder, FormControl, FormGroup, Validators} from '@angular/forms';
import { UploadFieldDataEditorModel } from '../../../../admin/field-data/upload-field-data-editor-model';
import {FieldDataOption} from "@app/core/model/dataset-profile-definition/field-data/field-data";
import {FieldDataOptionEditorModel} from "@app/ui/admin/dataset-profile/admin/field-data/field-data-option-editor-model";
import {MatSelect} from "@angular/material/select";
import {ConfigurationService} from "@app/core/services/configuration/configuration.service";
@Component({
selector: 'app-dataset-profile-editor-upload-field-component',
styleUrls: ['./dataset-profile-editor-upload-field.component.scss'],
templateUrl: './dataset-profile-editor-upload-field.component.html'
})
export class DatasetProfileEditorUploadFieldComponent implements OnInit {
types: Array<FieldDataOption> = [
// images
{label: "Animated Portable Network Graphics (APNG)", value: "image/apng", source: ""},
{label: "AV1 Image File Format (AVIF)", value: "image/avif", source: ""},
{label: "Graphics Interchange Format (GIF)", value: "image/gif", source: ""},
{label: "Joint Photographic Expert Group image (JPEG)", value: "image/jpeg", source: ""},
{label: "Portable Network Graphics (PNG)", value: "image/png", source: ""},
{label: "Scalable Vector Graphics (SVG)", value: "image/svg+xml", source: ""},
{label: "Web Picture format (WEBP)", value: "image/webp", source: ""},
// office word
{label: "Microsoft Word 97-2003", value: "application/msword", source: ""}, // .doc, .dot
{label: "Microsoft Word 2007-2013", value: "application/vnd.openxmlformats-officedocument.wordprocessingml.document", source: ""}, // .docx
{label: "OpenDocument Text", value: "application/vnd.openxmlformats-officedocument.wordprocessingml.document", source: ""}, // .odt
// office excel
{label: "Microsoft Excel 97-2003", value: "application/vnd.ms-excel", source: ""}, // .xls, .xlt, .xla
{label: "Microsoft Excel 2007-2013", value: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", source: ""}, // .xlsx
{label: "OpenDocument Spreadsheet", value: "application/vnd.oasis.opendocument.spreadsheet", source: ""}, // .ods
// office powerpoint
{label: "Microsoft PowerPoint 97-2003", value: "application/vnd.ms-powerpoint", source: ""}, // .ppt, .pot, .pps, .ppa
{label: "Microsoft PowerPoint 2007-2013", value: "application/vnd.openxmlformats-officedocument.presentationml.presentation", source: ""}, // .pptx
{label: "OpenDocument Presentation", value: "application/vnd.oasis.opendocument.presentation", source: ""}, // .odp
{label: "Comma-Seperated Values (CSV)", value: "text/csv", source: ""},
{label: "Adobe Portable Document Format (PDF)", value: "application/pdf", source: ""}
];
selected: string[] = [];
public typesFormControl = new FormControl();
@Input() form: FormGroup;
private data: UploadFieldDataEditorModel = new UploadFieldDataEditorModel();
constructor(private configurationService: ConfigurationService) {}
ngOnInit() {
let typeValues: string[] = this.types.map(type => type.value);
if (!this.form.get('data')) { this.form.addControl('data', this.data.buildForm()); }
if(this.form.get('data') && this.form.get('data').get('types')) {
for(let type of this.form.get('data').get('types').value) {
if(typeValues.indexOf(type.value) != -1) {
this.selected.push(type.value);
}
}
this.typesFormControl.setValue(this.selected);
// if (this.form.get('data').get('types').disabled) {
// this.typesFormControl.disable();
// }
}
}
selectedType(type: FieldDataOption) {
if (!this.form.get('data').get('types').disabled) {
let index = this.selected.indexOf(type.value);
if (index == -1) {
this.selected.push(type.value);
this.addNewRow(type);
} else {
this.selected.splice(index, 1);
this.deleteRow(index);
}
}
}
isCustomType(value: string) {
return this.selected.indexOf(value) == -1;
}
addNewRow(type: FieldDataOption = null) {
const typeListOptions: FieldDataOptionEditorModel = new FieldDataOptionEditorModel();
if(type != null) {
typeListOptions.fromModel(type);
}
(<FormGroup>this.form.get('data')).addControl('types', new FormBuilder().array([]));
(<FormArray>this.form.get('data').get('types')).push(typeListOptions.buildForm());
}
deleteRow(index: number) {
if (this.form.get('data').get('types')) { (<FormArray>this.form.get('data').get('types')).removeAt(index); }
}
public getConfiguration() {
return this.configurationService;
}
}

View File

@ -72,10 +72,13 @@
</mat-select> -->
<!-- NEW VERSION -->
<mat-select placeholder="{{'DATASET-PROFILE-EDITOR.STEPS.FORM.FIELD.FIELDS.VIEW-STYLE' | translate}}" [(ngModel)]="viewType" (selectionChange)="onInputTypeChange()"
<mat-select #select placeholder="{{'DATASET-PROFILE-EDITOR.STEPS.FORM.FIELD.FIELDS.VIEW-STYLE' | translate}}" [(ngModel)]="viewType" (selectionChange)="onInputTypeChange()"
[disabled]="viewOnly"
[errorStateMatcher]="this"
>
<mat-select-trigger>
{{enumUtils.toDatasetProfileViewTypeString(select.value)}}
</mat-select-trigger>
<mat-option [value]="viewTypeEnum.TextArea">
<img src="/assets/images/editor/icons/text_area.svg" class="input_icon" alt="TextArea icon">
{{enumUtils.toDatasetProfileViewTypeString(viewTypeEnum.TextArea)}}
@ -88,6 +91,16 @@
<img src="/assets/images/editor/icons/free_text.svg" class="input_icon" alt="FreeText icon">
{{enumUtils.toDatasetProfileViewTypeString(viewTypeEnum.FreeText)}}
</mat-option>
<mat-divider></mat-divider>
<mat-option [value]="viewTypeEnum.Upload">
<!-- <img src="/assets/images/editor/icons/text_area.svg" class="input_icon" alt="Upload icon">-->
<mat-icon class="input_icon" style="font-size: 14px; color: #129d99; display: inline-flex; align-items: center">upload</mat-icon>
{{enumUtils.toDatasetProfileViewTypeString(viewTypeEnum.Upload)}}
</mat-option>
<!-- <mat-option [value]="viewTypeEnum.Table">-->
<!-- <img src="/assets/images/editor/icons/text_area.svg" class="input_icon" alt="Table icon">-->
<!-- {{enumUtils.toDatasetProfileViewTypeString(viewTypeEnum.Table)}}-->
<!-- </mat-option>-->
<mat-divider></mat-divider>
<mat-option [value]="viewTypeEnum.BooleanDecision">
<img src="/assets/images/editor/icons/boolean.svg" class="input_icon" alt="Boolean icon">
@ -258,6 +271,8 @@
<app-dataset-profile-editor-free-text-field-component *ngSwitchCase="viewStyleEnum.FreeText" class="col-12" [form]="form"></app-dataset-profile-editor-free-text-field-component>
<app-dataset-profile-editor-text-area-field-component *ngSwitchCase="viewStyleEnum.TextArea" class="col-12" [form]="form"></app-dataset-profile-editor-text-area-field-component>
<app-dataset-profile-editor-rich-text-area-field-component *ngSwitchCase="viewStyleEnum.RichTextArea" class="col-12" [form]="form"></app-dataset-profile-editor-rich-text-area-field-component>
<app-dataset-profile-editor-upload-field-component *ngSwitchCase="viewStyleEnum.Upload" class="col-12" [form]="form"></app-dataset-profile-editor-upload-field-component>
<!-- <app-dataset-profile-editor-table-field-component *ngSwitchCase="viewStyleEnum.Table" class="col-12" [form]="form"></app-dataset-profile-editor-table-field-component>-->
<app-dataset-profile-editor-date-picker-field-component *ngSwitchCase="viewStyleEnum.DatePicker" class="col-12" [form]="form"></app-dataset-profile-editor-date-picker-field-component>
<app-dataset-profile-editor-boolean-decision-field-component *ngSwitchCase="viewStyleEnum.BooleanDecision" class="col-12"

View File

@ -34,13 +34,16 @@ import {
RegistriesFieldData,
ResearchersAutoCompleteFieldData,
RichTextAreaFieldData,
UploadFieldData,
ServicesFieldData,
// TableFieldData,
TagsFieldData,
TaxonomiesFieldData,
TextAreaFieldData,
ValidationFieldData,
WordListFieldData
} from '@app/core/model/dataset-profile-definition/field-data/field-data';
import {ConfigurationService} from "@app/core/services/configuration/configuration.service";
@Component({
selector: 'app-dataset-profile-editor-field-component',
@ -73,7 +76,8 @@ export class DatasetProfileEditorFieldComponent extends BaseComponent implements
constructor(
public enumUtils: EnumUtils,
public datasetProfileService: DatasetProfileService,
private dialog: MatDialog
private dialog: MatDialog,
private configurationService: ConfigurationService
) { super();
}
@ -137,6 +141,12 @@ export class DatasetProfileEditorFieldComponent extends BaseComponent implements
case DatasetProfileFieldViewStyle.RichTextArea:
this.viewType = this.viewTypeEnum.RichTextArea;
break;
case DatasetProfileFieldViewStyle.Upload:
this.viewType = this.viewTypeEnum.Upload;
break;
case DatasetProfileFieldViewStyle.Table:
this.viewType = this.viewTypeEnum.Table;
break;
case DatasetProfileFieldViewStyle.DatePicker:
this.viewType = this.viewTypeEnum.DatePicker;
break;
@ -409,6 +419,7 @@ export class DatasetProfileEditorFieldComponent extends BaseComponent implements
switch(this.viewType){
case this.viewTypeEnum.TextArea:
case this.viewTypeEnum.RichTextArea:
case this.viewTypeEnum.Upload:
case this.viewTypeEnum.FreeText:
case this.viewTypeEnum.BooleanDecision:
case this.viewTypeEnum.RadioBox:
@ -626,6 +637,28 @@ export class DatasetProfileEditorFieldComponent extends BaseComponent implements
field.data = data;
break;
}
case this.viewTypeEnum.Upload:{
const data: UploadFieldData = {
label:'',
types: [],
maxFileSizeInMB: this.configurationService.maxFileSizeInMB
}
field.viewStyle.renderStyle = DatasetProfileFieldViewStyle.Upload;
field.data = data;
break;
}
// case this.viewTypeEnum.Table:{
// const data: TableFieldData = {
// label:'',
// headers: [],
// rows: []
// }
//
//
// field.viewStyle.renderStyle = DatasetProfileFieldViewStyle.Table;
// field.data = data;
// break;
// }
case this.viewTypeEnum.DatePicker:{

View File

@ -1,6 +1,8 @@
export enum ViewStyleType{
TextArea = "textarea",
RichTextArea = "richTextarea",
Upload = "upload",
Table = "table",
BooleanDecision = "booleanDecision",
CheckBox = "checkBox",
FreeText = "freetext",

View File

@ -344,6 +344,7 @@ export class DatasetProfileEditorComponent extends CheckDeactivateBaseComponent
if(renderStyle && ![
DatasetProfileFieldViewStyle.TextArea,
DatasetProfileFieldViewStyle.RichTextArea,
DatasetProfileFieldViewStyle.Upload,
DatasetProfileFieldViewStyle.FreeText,
DatasetProfileFieldViewStyle.BooleanDecision,
DatasetProfileFieldViewStyle.RadioBox,
@ -2020,6 +2021,9 @@ export class DatasetProfileEditorComponent extends CheckDeactivateBaseComponent
}else if(renderStyleValue === DatasetProfileFieldViewStyle.RadioBox){
field.get('data').setValidators(EditorCustomValidators.atLeastOneElementListValidator('options'));
} else if(renderStyleValue === DatasetProfileFieldViewStyle.Upload) {
field.get('data').setValidators(EditorCustomValidators.atLeastOneElementListValidator('types'));
field.get('data').get('maxFileSizeInMB').setValidators(Validators.required);
}
});
}

View File

@ -122,6 +122,36 @@
</div>
</div>
</ng-container>
<ng-container *ngSwitchCase="datasetProfileFieldViewStyleEnum.Upload">
<div class="col-12 d-flex justify-content-center">
<ngx-dropzone #drop class="drop-file col-12" (change)="fileChangeEvent($event, true)"
[multiple]="false" [accept]="typesToString()">
<ngx-dropzone-preview *ngIf="form.value.value && form.value.value.name" class="file-preview"
[removable]="true" (removed)="onRemove()">
<ngx-dropzone-label class="file-label">{{ form.value.value.name }}</ngx-dropzone-label>
</ngx-dropzone-preview>
</ngx-dropzone>
</div>
<div class="col-12 d-flex justify-content-center attach-btn">
<button *ngIf="!form.value.value || filesToUpload" mat-button (click)="drop.showFileSelector()" type="button" class="attach-file-btn"
[disabled]="form.value.value">
<mat-icon class="mr-2">upload</mat-icon>
<mat-label>{{ (form.get('data').value.label | translate)}}</mat-label>
</button>
<button *ngIf="form.value.value && !filesToUpload" mat-button (click)="download()" type="button" class="attach-file-btn">
<mat-icon class="mr-2">download</mat-icon>
<mat-label>{{ "TYPES.DATASET-PROFILE-UPLOAD-TYPE.DOWNLOAD" | translate }}</mat-label>
</button>
</div>
</ng-container>
<!-- <ng-container *ngSwitchCase="datasetProfileFieldViewStyleEnum.Table">-->
<!-- <table-editor-component class="col-12"></table-editor-component>-->
<!-- <div [class]="(form.get('value')['errors'] && form.get('value').hasError('required')) ? 'visible' : 'invisible'" class="mat-form-field form-field-subscript-wrapper">/-->
<!-- <mat-error *ngIf="form.get('value')['errors'] && form.get('value').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>-->
<!-- </div>-->
<!-- </ng-container>-->
<div *ngSwitchCase="datasetProfileFieldViewStyleEnum.BooleanDecision" class="col-12">

View File

@ -28,3 +28,78 @@
font-size: 1rem;
padding: 0.6em 0 1em 0 !important;
}
.attach-btn {
top: -20px;
}
.attach-file-btn {
min-width: 156px;
height: 44px;
color: #ffffff;
background: #129d99 0% 0% no-repeat padding-box;
box-shadow: 0px 3px 6px #1e202029;
border-radius: 30px;
}
.attach-file-btn:hover {
background-color: #ffffff;
border: 1px solid #129d99;
color: #129d99;
}
.attach-file-btn.mat-button-disabled, .attach-file-btn.mat-button-disabled:hover {
background-color: #ffffff;
border: 1px solid darkgray;
color: darkgrey !important;
}
//
//.mat-button-disabled .attach-file-btn > ::ng-deep mat-button-wrapper:hover > * {
// color: darkgrey !important;
//}
.drop-file {
background-color: #fafafa;
border: 1px dashed #d1d1d1;
border-radius: 4px;
//max-width: 480px;
height: 98px;
margin-top: 0.5rem;
}
.file-preview {
height: auto !important;
width: auto !important;
max-width: 500px !important;
min-height: 1rem !important;
background-color: #e0e0e0 !important;
background-image: none !important;
color: rgba(0, 0, 0, 0.87) !important;
font-weight: 500 !important;
border-radius: 24px !important;
line-height: 1.25 !important;
}
.file-label {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-size: 14px !important;
}
::ng-deep ngx-dropzone-remove-badge {
opacity: 1 !important;
margin-left: .5rem !important;
position: initial !important;
}
::ng-deep .upload-form .mat-form-field-appearance-outline .mat-form-field-outline {
background: #fafafa !important;
}
::ng-deep .upload-form .mat-form-field-appearance-outline .mat-form-field-infix {
font-size: 1rem;
padding: 0.6em 0 1em 0 !important;
}

View File

@ -1,5 +1,5 @@
import { ChangeDetectionStrategy, Component, Input, OnInit, SimpleChanges } from '@angular/core';
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit, SimpleChanges} from '@angular/core';
import { FormGroup, FormArray, FormControl } from '@angular/forms';
import { DatasetProfileComboBoxType } from '@app/core/common/enum/dataset-profile-combo-box-type';
import { DatasetProfileFieldViewStyle } from '@app/core/common/enum/dataset-profile-field-view-style';
@ -38,6 +38,16 @@ import { AutoCompleteSingleData } from '@app/core/model/dataset-profile-definiti
import {LicenseCriteria} from "@app/core/query/license/license-criteria";
import {TaxonomyCriteria} from "@app/core/query/taxonomy/taxonomy-criteria";
import {PublicationCriteria} from "@app/core/query/publication/publication-criteria";
import {FileService} from "@app/core/services/file/file.service";
import {
SnackBarNotificationLevel,
UiNotificationService
} from "@app/core/services/notification/ui-notification-service";
import {FormValidationErrorsDialogComponent} from "@common/forms/form-validation-errors-dialog/form-validation-errors-dialog.component";
import {MatDialog} from "@angular/material/dialog";
import {HttpError} from "@common/modules/errors/error-handling/http-error-handling.service";
import {HttpErrorResponse} from "@angular/common/http";
import * as FileSaver from "file-saver";
@Component({
selector: 'app-form-field',
@ -104,6 +114,8 @@ export class FormFieldComponent extends BaseComponent implements OnInit {
{ name: 'Zenodo', value: 'zenodo' }
];
filesToUpload: FileList;
constructor(
public visibilityRulesService: VisibilityRulesService,
private datasetExternalAutocompleteService: DatasetExternalAutocompleteService,
@ -111,7 +123,11 @@ export class FormFieldComponent extends BaseComponent implements OnInit {
private language: TranslateService,
private datasetService: DatasetService,
private dmpService: DmpService,
private currencyService: CurrencyService
private currencyService: CurrencyService,
private fileService: FileService,
private cdr: ChangeDetectorRef,
private uiNotificationService: UiNotificationService,
public dialog: MatDialog
) {
super();
@ -579,4 +595,121 @@ export class FormFieldComponent extends BaseComponent implements OnInit {
});
}
public upload() {
this.fileService.upload(this.filesToUpload[0], this.datasetProfileId.id, this.form.value.id).subscribe((fileId: string) => {
this.form.get("value").patchValue(
{"name": this.filesToUpload[0].name, "id": fileId+"", "type": this.filesToUpload[0].type});
this.cdr.detectChanges();
}, error => {
this.onCallbackUploadFail(error.error);
})
}
private onCallbackUploadFail(error: any) {
this.makeFilesNull();
this.uiNotificationService.snackBarNotification(this.language.instant(error.message), SnackBarNotificationLevel.Error);
}
fileChangeEvent(fileInput: any, dropped: boolean = false) {
if(this.form.value.value) {
this.onRemove(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];
if (this.form.get("data") && this.form.get("data").value.types
&& this.form.get("data").value.types.map(type => type.value).includes(fileToUpload.type)
&& this.form.get("data").value.maxFileSizeInMB
&& this.form.get("data").value.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': this.form.get("data").value.maxFileSizeInMB}));
messages.push(this.language.instant('DATASET-WIZARD.MESSAGES.ACCEPTED-FILE-TYPES')+ this.form.get("data").value.types.map(type => type.value).join(", "));
}
if(messages && messages.length > 0) {
this.dialog.open(FormValidationErrorsDialogComponent, {
data: {
errorMessages: messages
}
})
}
}
}
onRemove(makeFilesNull: boolean = true) {
// delete from tmp folder - subscribe call
this.fileService.deleteFromTempFolder(this.form.value.value.id).subscribe(res => {
if(makeFilesNull) {
this.makeFilesNull();
}
this.cdr.detectChanges();
}, error => {
if(makeFilesNull) {
this.makeFilesNull();
}
})
}
makeFilesNull() {
this.filesToUpload = null;
this.form.value.value = null;
this.form.get("value").patchValue(null);
}
typesToString() {
return this.form.get("data").value.types.map(type => type.value).toString();
}
download(): void {
this.fileService.download(this.form.value.value.id)
.pipe(takeUntil(this._destroyed))
.subscribe(response => {
const blob = new Blob([response.body], {type: this.form.value.value.type});
const filename = this.getFilenameFromContentDispositionHeader(response.headers.get('Content-Disposition'));
FileSaver.saveAs(blob, filename);
});
}
// create a fileUtils/ fileHelper file and add this function (and any other possibly generic) there
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;
}
// isImageFile(fileType: string) {
// if(!fileType) {
// return false;
// }
// return fileType.startsWith("image/");
// }
}

View File

@ -15,17 +15,22 @@ import { DatasetDescriptionComponent } from './dataset-description.component';
import { FormProgressIndicationModule } from './components/form-progress-indication/form-progress-indication.module';
import { FormSectionInnerComponent } from './components/form-section/form-section-inner/form-section-inner.component';
import {RichTextEditorModule} from "@app/library/rich-text-editor/rich-text-editor.module";
// import {TableEditorModule} from "@app/library/table-editor/table-editor.module";
import {FileService} from "@app/core/services/file/file.service";
import {NgxDropzoneModule} from "ngx-dropzone";
@NgModule({
imports: [
CommonUiModule,
CommonFormsModule,
AutoCompleteModule,
ExternalSourcesModule,
FormProgressIndicationModule,
RichTextEditorModule
],
imports: [
CommonUiModule,
CommonFormsModule,
AutoCompleteModule,
ExternalSourcesModule,
FormProgressIndicationModule,
RichTextEditorModule,
// TableEditorModule,
NgxDropzoneModule
],
declarations: [
DatasetDescriptionFormComponent,
DatasetDescriptionComponent,
@ -44,7 +49,8 @@ import {RichTextEditorModule} from "@app/library/rich-text-editor/rich-text-edit
],
providers: [
VisibilityRulesService,
FormFocusService
FormFocusService,
FileService
]
})
export class DatasetDescriptionFormModule { }

View File

@ -58,5 +58,6 @@
"allowOrganizationCreator": true,
"doiLink": "https://sandbox.zenodo.org/record/",
"useSplash": false,
"orcidPath": "https://orcid.org/"
"orcidPath": "https://orcid.org/",
"maxFileSizeInMB": 10
}

View File

@ -398,6 +398,16 @@
"FIELD-TEXT-AREA-PLACEHOLDER": "Input Placeholder Text",
"FIELD-RICH-TEXT-AREA-TITLE": "Rich Text Area Data",
"FIELD-RICH-TEXT-AREA-PLACEHOLDER": "Input Placeholder Text",
"FIELD-UPLOAD-TITLE": "Upload Data",
"FIELD-UPLOAD-PLACEHOLDER": "Upload Placeholder Text",
"FIELD-UPLOAD-MAX-FILE-SIZE": "Max File Upload Size (up to {{maxfilesize}} Megabytes)",
"FIELD-UPLOAD-CUSTOM-FILETYPE": "Other accepted file types",
"FIELD-UPLOAD-SELECT-FILETYPE": "Select the accepted file types",
"FIELD-UPLOAD-LABEL": "Media type name",
"FIELD-UPLOAD-VALUE": "Media type value",
"FIELD-TABLE-TITLE": "Table Data",
"FIELD-TABLE-ADD-COLUMNS": "Add Columns in the table",
"FIELD-TABLE-ADD-ROWS": "Add Rows in the table",
"FIELD-BOOLEAN-DECISION-TITLE": "Boolean Decision Data",
"FIELD-BOOLEAN-DECISION-PLACEHOLDER": "Input Placeholder Text",
"FIELD-CHECKBOX-TITLE": "Checkbox Data",
@ -735,7 +745,12 @@
"DATASET-NOT-FOUND": "Dataset does not exist",
"DATASET-NOT-ALLOWED": "You have no access to this Dataset",
"SUCCESS-UPDATE-DATASET-PROFILE": "Dataset Template updated successfully",
"MISSING-FIELDS": "There are some required fields left unfilled. Please check the DMP and make sure that all required questions are answered and URLs are provided with valid input. (Missing fields are marked in red color)"
"MISSING-FIELDS": "There are some required fields left unfilled. Please check the DMP and make sure that all required questions are answered and URLs are provided with valid input. (Missing fields are marked in red color)",
"NO-FILES-SELECTED": "There is no selected file to upload",
"LARGE-FILE-OR-UNACCEPTED-TYPE": "The selected file is too large or the file type is not accepted.",
"MAX-FILE-SIZE": "Max file size is {{maxfilesize}} MB.",
"ACCEPTED-FILE-TYPES": "Accepted file media types are: "
},
"UPLOAD": {
"UPLOAD-XML": "Import",
@ -1384,6 +1399,8 @@
"RADIO-BOX": "Radio Box",
"TEXT-AREA": "Text Area",
"RICH-TEXT-AREA": "Rich Text Area",
"UPLOAD": "Upload",
"TABLE": "Table",
"DATE-PICKER": "Date Picker",
"EXTERNAL-DATASETS": "External Datasets",
"DATA-REPOSITORIES": "Data Repositories",
@ -1403,6 +1420,9 @@
"OTHER": "Other",
"SELECT": "Select"
},
"DATASET-PROFILE-UPLOAD-TYPE": {
"DOWNLOAD": "Download file"
},
"DATASET-PROFILE-COMBO-BOX-TYPE": {
"WORD-LIST": "Word List",
"AUTOCOMPLETE": "Autocomplete",