Adds progress bar on DMP editor

This commit is contained in:
apapachristou 2020-10-15 13:46:59 +03:00
parent f08d8e2e01
commit e179d90f12
14 changed files with 186 additions and 39 deletions

View File

@ -46,6 +46,8 @@ import { LicenseInfoComponent } from './editor/license-info/license-info.compone
import { StartNewDatasetDialogComponent } from './start-new-dataset-dialogue/start-new-dataset-dialog.component';
import { NgxDropzoneModule } from 'ngx-dropzone';
import { DmpToDatasetDialogComponent } from './dmp-to-dataset/dmp-to-dataset-dialog.component';
import { FormProgressIndicationComponent } from '../misc/dataset-description-form/components/form-progress-indication/form-progress-indication.component';
import { FormProgressIndicationModule } from '../misc/dataset-description-form/components/form-progress-indication/form-progress-indication.module';
@NgModule({
imports: [
@ -62,7 +64,8 @@ import { DmpToDatasetDialogComponent } from './dmp-to-dataset/dmp-to-dataset-dia
MultipleChoiceDialogModule,
DatasetEditorDetailsModule,
DatasetDescriptionFormModule,
NgxDropzoneModule
NgxDropzoneModule,
FormProgressIndicationModule
],
declarations: [
DmpListingComponent,

View File

@ -33,10 +33,18 @@
<div class="stepper-title">{{'DMP-EDITOR.STEPPER.USER-GUIDE' | translate}}</div>
<div class="stepper-options">
<ol class="stepper-list">
<li (click)="changeStep(0)" [ngClass]="{'active': this.step === 0}">{{'DMP-EDITOR.STEPPER.MAIN-INFO' | translate}} (7)</li>
<li (click)="changeStep(1)" [ngClass]="{'active': this.step === 1}">{{'DMP-EDITOR.STEPPER.FUNDING-INFO' | translate}} (3)</li>
<li (click)="changeStep(0)" *ngIf="!formGroup.get('extraProperties').get('visible').value && !mainInfoValid()" [ngClass]="{'active': this.step === 0}">{{'DMP-EDITOR.STEPPER.MAIN-INFO' | translate}} (1)</li>
<li (click)="changeStep(0)" *ngIf="!formGroup.get('extraProperties').get('visible').value && mainInfoValid()" [ngClass]="{'active': this.step === 0}">{{'DMP-EDITOR.STEPPER.MAIN-INFO' | translate}} (<mat-icon class="done-icon">done</mat-icon>)</li>
<li (click)="changeStep(0)" *ngIf="formGroup.get('extraProperties').get('visible').value && !mainInfoValid()" [ngClass]="{'active': this.step === 0}">{{'DMP-EDITOR.STEPPER.MAIN-INFO' | translate}} (2)</li>
<li (click)="changeStep(0)" *ngIf="formGroup.get('extraProperties').get('visible').value && mainInfoValid()" [ngClass]="{'active': this.step === 0}">{{'DMP-EDITOR.STEPPER.MAIN-INFO' | translate}} (<mat-icon class="done-icon">done</mat-icon>)</li>
<li (click)="changeStep(1)" *ngIf="!fundingInfoValid()" [ngClass]="{'active': this.step === 1}">{{'DMP-EDITOR.STEPPER.FUNDING-INFO' | translate}} (2)</li>
<li (click)="changeStep(1)" *ngIf="fundingInfoValid()" [ngClass]="{'active': this.step === 1}">{{'DMP-EDITOR.STEPPER.FUNDING-INFO' | translate}} (<mat-icon class="done-icon">done</mat-icon>)</li>
<li (click)="changeStep(2)" [ngClass]="{'active': this.step === 2}">{{'DMP-EDITOR.STEPPER.LICENSE-INFO' | translate}}</li>
<li (click)="changeStep(3)" [ngClass]="{'active': this.step === 3}">{{'DMP-EDITOR.STEPPER.DATASET-INFO' | translate}}</li>
<li (click)="changeStep(3)" *ngIf="!datasetInfoValid()" [ngClass]="{'active': this.step === 3}">{{'DMP-EDITOR.STEPPER.DATASET-INFO' | translate}} (1)</li>
<li (click)="changeStep(3)" *ngIf="datasetInfoValid()" [ngClass]="{'active': this.step === 3}">{{'DMP-EDITOR.STEPPER.DATASET-INFO' | translate}} (<mat-icon class="done-icon">done</mat-icon>)</li>
<!-- <li *ngFor="let dataset of datasets.controls; let i = index" (click)="changeStep(i + stepsBeforeDatasets, dataset)" [ngClass]="{'active-dataset': this.step === i + stepsBeforeDatasets}"> -->
<li *ngFor="let dataset of datasets.controls; let i = index" (click)="editDataset(dataset.get('id').value, false)" class="active-dataset">
@ -70,6 +78,9 @@
<div>{{'DMP-EDITOR.ACTIONS.SAVE' | translate}} & {{'DMP-LISTING.ACTIONS.ADD-DATASET-SHORT' | translate}}</div>
</div>
</div>
<div class="col-auto pr-0">
<app-form-progress-indication class="col-12" *ngIf="formGroup" [formGroup]="formGroup" [isEditor]="true"></app-form-progress-indication>
</div>
</div>
<div class="col-auto form">
<main-info [hidden]="this.step !== 0" [formGroup]="formGroup" [isUserOwner]="isUserOwner" (onFormChanged)="formChanged()"></main-info>

View File

@ -442,6 +442,14 @@ mat-icon.size-16 {
list-style-type: none;
}
.done-icon {
display: inline-flex;
vertical-align: middle;
font-size: 16px !important;
height: auto;
width: auto;
}
// ::ng-deep .mat-tab-labels {
// justify-content: space-between;
// }

View File

@ -413,7 +413,6 @@ export class DmpEditorComponent extends BaseComponent implements OnInit, IBreadC
}
public isFormValid() {
this.formGroup.markAllAsTouched();
return this.formGroup.valid;
// return this.formGroup.get('label').valid && this.formGroup.get('profiles').valid &&
// (this.formGroup.get('funder').get('label').valid || this.formGroup.get('funder').get('existFunder').valid) &&
@ -896,6 +895,30 @@ export class DmpEditorComponent extends BaseComponent implements OnInit, IBreadC
this.dmp.id != null ? this.router.navigate(['/plans', 'edit', this.dmp.id]) : this.router.navigate(['/plans']);
}
mainInfoValid(): boolean {
if (this.formGroup.get('label').valid) {
if (this.formGroup.get('publicDate')) {
if (this.formGroup.get('publicDate').valid) {
return true;
} else if (this.formGroup.get('publicDate').invalid) {
return false
}
} else {
return true;
}
} else {
return false;
}
}
fundingInfoValid(): boolean {
return this.formGroup.get('funder') && this.formGroup.get('funder').valid && this.formGroup.get('grant') && this.formGroup.get('grant').valid;
}
datasetInfoValid(): boolean {
return this.formGroup.get('profiles') && this.formGroup.get('profiles').valid && this.formGroup.get('profiles').value.length > 0;
}
// advancedClicked() {
// const dialogRef = this.dialog.open(ExportMethodDialogComponent, {
// maxWidth: '500px',

View File

@ -92,7 +92,7 @@ export class DmpEditorModel {
groupId: [{ value: this.groupId, disabled: disabled }, context.getValidation('groupId').validators],
version: [{ value: this.version, disabled: disabled }, context.getValidation('version').validators],
status: [{ value: this.status, disabled: disabled }, context.getValidation('status').validators],
description: [{ value: this.description, disabled: disabled }],
description: [{ value: this.description, disabled: disabled }, context.getValidation('description').validators],
grant: this.grant.buildForm(),
project: this.project.buildForm(),
funder: this.funder.buildForm(),
@ -135,10 +135,10 @@ export class DmpEditorModel {
baseContext.validation.push({ key: 'groupId', validators: [BackendErrorValidator(this.validationErrorModel, 'groupId')] });
baseContext.validation.push({ key: 'version', validators: [BackendErrorValidator(this.validationErrorModel, 'version')] });
baseContext.validation.push({ key: 'status', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, 'status')] });
baseContext.validation.push({ key: 'description', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, 'description')] });
baseContext.validation.push({ key: 'description', validators: [BackendErrorValidator(this.validationErrorModel, 'description')] });
baseContext.validation.push({ key: 'grant', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, 'grant')] });
baseContext.validation.push({ key: 'project', validators: [BackendErrorValidator(this.validationErrorModel, 'project')] });
baseContext.validation.push({ key: 'funder', validators: [BackendErrorValidator(this.validationErrorModel, 'funder')] });
baseContext.validation.push({ key: 'funder', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, 'funder')] });
baseContext.validation.push({ key: 'organisations', validators: [BackendErrorValidator(this.validationErrorModel, 'organisations')] });
baseContext.validation.push({ key: 'researchers', validators: [BackendErrorValidator(this.validationErrorModel, 'researchers')] });
baseContext.validation.push({ key: 'profiles', validators: [Validators.required, ValidJsonValidator, BackendErrorValidator(this.validationErrorModel, 'profiles')] });

View File

@ -112,14 +112,14 @@
<!-- Create New Project -->
<div *ngIf="isCreateNewProject">
<mat-form-field appearance="outline">
<input matInput placeholder="{{'QUICKWIZARD.CREATE-ADD.CREATE.QUICKWIZARD_CREATE.FIRST-STEP.FIELDS.PROJECT-LABEL' | translate}}" type="text" name="label" [formControl]="projectFormGroup.get('label')" required>
<input matInput placeholder="{{'QUICKWIZARD.CREATE-ADD.CREATE.QUICKWIZARD_CREATE.FIRST-STEP.FIELDS.PROJECT-LABEL' | translate}}" type="text" name="label" [formControl]="projectFormGroup.get('label')">
<mat-error *ngIf="projectFormGroup.get('label').hasError('backendError')">
{{projectFormGroup.get('label').getError('backendError').message}}</mat-error>
<mat-error *ngIf="projectFormGroup.get('label').hasError('required')">
{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</mat-form-field>
<mat-form-field appearance="outline">
<textarea matInput class="description-area" placeholder="{{'QUICKWIZARD.CREATE-ADD.CREATE.QUICKWIZARD_CREATE.FIRST-STEP.FIELDS.DESCRIPTION' | translate}}" [formControl]="projectFormGroup.get('description')" [required]="true"></textarea>
<textarea matInput class="description-area" placeholder="{{'QUICKWIZARD.CREATE-ADD.CREATE.QUICKWIZARD_CREATE.FIRST-STEP.FIELDS.DESCRIPTION' | translate}}" [formControl]="projectFormGroup.get('description')"></textarea>
<mat-error *ngIf="projectFormGroup.get('description').hasError('backendError')">
{{projectFormGroup.get('description').getError('backendError').message}}</mat-error>
<mat-error *ngIf="projectFormGroup.get('description').hasError('required')">

View File

@ -4,6 +4,7 @@ import { BackendErrorValidator } from '@common/forms/validation/custom-validator
import { CostModel } from '@app/core/model/dmp/cost';
import { isNullOrUndefined } from 'util';
import { CostEditorModel } from '../cost-editor/add-cost/add-cost.model';
import { ValidationErrorModel } from '@common/forms/validation/error-model/validation-error-model';
export class ExtraPropertiesFormModel {
public language: string;
@ -12,6 +13,7 @@ export class ExtraPropertiesFormModel {
public publicDate: Date;
public contact: string;
public costs: CostEditorModel[] = [];
public validationErrorModel: ValidationErrorModel = new ValidationErrorModel();
fromModel(item: any): ExtraPropertiesFormModel {
this.language = item.language;
@ -53,12 +55,12 @@ export class ExtraPropertiesFormModel {
createValidationContext(): ValidationContext {
const baseContext: ValidationContext = new ValidationContext();
baseContext.validation.push({ key: 'language', validators: [] });
baseContext.validation.push({ key: 'license', validators: [] });
baseContext.validation.push({ key: 'visible', validators: [] });
baseContext.validation.push({ key: 'publicDate', validators: [] });
baseContext.validation.push({ key: 'contact', validators: [] });
baseContext.validation.push({ key: 'costs', validators: [] });
baseContext.validation.push({ key: 'language', validators: [BackendErrorValidator(this.validationErrorModel, 'language')] });
baseContext.validation.push({ key: 'license', validators: [BackendErrorValidator(this.validationErrorModel, 'license')] });
baseContext.validation.push({ key: 'visible', validators: [BackendErrorValidator(this.validationErrorModel, 'visible')] });
baseContext.validation.push({ key: 'publicDate', validators: [BackendErrorValidator(this.validationErrorModel, 'publicDate')] });
baseContext.validation.push({ key: 'contact', validators: [BackendErrorValidator(this.validationErrorModel, 'contact')] });
baseContext.validation.push({ key: 'costs', validators: [BackendErrorValidator(this.validationErrorModel, 'costs')] });
return baseContext;
}

View File

@ -36,9 +36,9 @@ export class ProjectFormModel {
createValidationContext(): ValidationContext {
const baseContext: ValidationContext = new ValidationContext();
baseContext.validation.push({ key: 'id', validators: [] });
baseContext.validation.push({ key: 'label', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, 'label')] });
baseContext.validation.push({ key: 'label', validators: [BackendErrorValidator(this.validationErrorModel, 'label')] });
baseContext.validation.push({ key: 'status', validators: [] });
baseContext.validation.push({ key: 'description', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, 'description')] });
baseContext.validation.push({ key: 'description', validators: [BackendErrorValidator(this.validationErrorModel, 'description')] });
baseContext.validation.push({ key: 'existProject', validators: [BackendErrorValidator(this.validationErrorModel, 'existProject')] });
return baseContext;
}

View File

@ -127,7 +127,7 @@
<!-- Contact Field -->
<div class="row">
<div class="col-12">
<div class="heading">1.7 {{'DMP-EDITOR.FIELDS.CONTACT' | translate}}</div>
<div class="heading">1.7 {{'DMP-EDITOR.FIELDS.CONTACT' | translate}}*</div>
<!-- <div class="hint">{{'DMP-EDITOR.LICENSE-INFO.HINT' | translate}}</div> -->
<div class="contact-form">
<mat-form-field>
@ -147,17 +147,17 @@
<!-- Publication Field -->
<div class="row" *ngIf="formGroup.get('extraProperties').get('visible').value">
<div class="col-12">
<div class="heading">1.8 {{'DMP-EDITOR.FIELDS.PUBLICATION' | translate}}</div>
<div class="heading">1.8 {{'DMP-EDITOR.FIELDS.PUBLICATION' | translate}}*</div>
<!-- <div class="hint">{{'DMP-EDITOR.LICENSE-INFO.HINT' | translate}}</div> -->
<div class="publication-form">
<mat-form-field>
<input matInput [matDatepicker]="picker" [formControl]="formGroup.get('extraProperties').get('publicDate')" placeholder="{{'DMP-EDITOR.FIELDS.PUBLICATION' | translate}}">
<mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
<mat-datepicker #picker></mat-datepicker>
<mat-error *ngIf="formGroup.get('extraProperties').get('publicDate').hasError('backendError')">
{{formGroup.get('extraProperties').get('publicDate').getError('backendError').message}}</mat-error>
<mat-error *ngIf="formGroup.get('extraProperties').get('publicDate').hasError('required')">
{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
<input matInput [matDatepicker]="picker" [formControl]="formGroup.get('extraProperties').get('publicDate')" placeholder="{{'DMP-EDITOR.FIELDS.PUBLICATION' | translate}}" [required]="formGroup.get('extraProperties').get('visible').value">
<mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
<mat-datepicker #picker></mat-datepicker>
<mat-error *ngIf="formGroup.get('extraProperties').get('publicDate').hasError('backendError')">
{{formGroup.get('extraProperties').get('publicDate').getError('backendError').message}}</mat-error>
<mat-error *ngIf="formGroup.get('extraProperties').get('publicDate').hasError('required')">
{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</mat-form-field>
</div>
</div>

View File

@ -1,3 +1,5 @@
<div class="demo-progress-bar-container">
<mat-progress-bar class="form-progress-bar" mode="determinate" [value]="value"></mat-progress-bar>
<div *ngIf="isEditor" class="percentage d-flex justify-content-center">{{progressSoFar}} {{'GENERAL.PREPOSITIONS.OF' | translate}} {{total}}</div>
<mat-progress-bar class="form-progress-bar" [ngClass]="{'progress-bar': isEditor}" mode="determinate" [value]="value"></mat-progress-bar>
<div *ngIf="isEditor" class="percentage" [ngStyle]="{'padding-left': value ? value - 10 + '%' : 0 + '%' }">{{value}}%</div>
</div>

View File

@ -0,0 +1,16 @@
.percentage {
color: #212121;
opacity: 0.7;
font-weight: 400;
font-size: 0.875rem;
}
.progress-bar {
border-radius: 20px;
height: 11px;
}
::ng-deep .mat-progress-bar .mat-progress-bar-fill::after {
border-radius: 20px !important;
}

View File

@ -1,36 +1,46 @@
import { Component, Input, OnInit } from '@angular/core';
import { AbstractControl, FormArray, FormGroup } from '@angular/forms';
import { Component, ElementRef, Input, OnInit } from '@angular/core';
import { AbstractControl, FormArray, FormControl, FormGroup } from '@angular/forms';
import { VisibilityRulesService } from '@app/ui/misc/dataset-description-form/visibility-rules/visibility-rules.service';
import { BaseComponent } from '@common/base/base.component';
import { takeUntil } from 'rxjs/operators';
@Component({
selector: 'app-form-progress-indication',
templateUrl: './form-progress-indication.component.html'
templateUrl: './form-progress-indication.component.html',
styleUrls: ['./form-progress-indication.component.scss']
})
export class FormProgressIndicationComponent extends BaseComponent implements OnInit {
@Input() formGroup: FormGroup;
@Input() isEditor: boolean;
@Input() public progressValueAccuracy = 2;
determinateProgressValue: number;
progressSoFar: number;
total: number;
percent: number;
constructor(private visibilityRulesService: VisibilityRulesService) { super(); }
public value = 0;
ngOnInit() {
this.calculateValueForProgressbar();
setTimeout(() => {this.calculateValueForProgressbar();});
this.formGroup
.valueChanges
.pipe(takeUntil(this._destroyed))
.subscribe(control => {
this.calculateValueForProgressbar();
setTimeout(() => {this.calculateValueForProgressbar();});
});
}
calculateValueForProgressbar() {
const progressSoFar = this.countFormControlsWithValueForProgress(this.formGroup);
const total = this.CountFormControlDepthLengthFotTotal(this.formGroup);
const perc = (progressSoFar / total) * 100;
this.value = Number.parseFloat(perc.toPrecision(this.progressValueAccuracy));
if (this.isEditor) {
this.progressSoFar = this.countFormControlsValidForProgress(this.formGroup);
this.total = this.countFormControlsRequiredFieldsForTotal(this.formGroup);
} else {
this.progressSoFar = this.countFormControlsWithValueForProgress(this.formGroup);
this.total = this.CountFormControlDepthLengthFotTotal(this.formGroup);
}
this.percent = (this.progressSoFar / this.total) * 100;
this.value = Number.parseFloat(this.percent.toPrecision(this.progressValueAccuracy));
}
countFormControlsWithValueForProgress(formControl: AbstractControl): number {
@ -125,4 +135,57 @@ export class FormProgressIndicationComponent extends BaseComponent implements On
}
return valueCurent;
}
countFormControlsValidForProgress(formControl: AbstractControl): number {
let valueCurrent = 0;
if (formControl instanceof FormControl) {
if (this.controlRequired(formControl) && this.controlEnabled(formControl) && formControl['nativeElement'] && formControl.valid) {
valueCurrent++;
}
} else if (formControl instanceof FormGroup) {
Object.keys(formControl.controls).forEach(item => {
const control = formControl.get(item);
valueCurrent = valueCurrent + this.countFormControlsValidForProgress(control);
});
} else if (formControl instanceof FormArray) {
formControl.controls.forEach(item => {
valueCurrent = valueCurrent + this.countFormControlsValidForProgress(item);
});
}
return valueCurrent;
}
countFormControlsRequiredFieldsForTotal(formControl: AbstractControl): number {
let valueCurrent = 0;
if (formControl instanceof FormControl) {
if (this.controlRequired(formControl) && this.controlEnabled(formControl) && formControl['nativeElement']) {
valueCurrent++;
}
} else if (formControl instanceof FormGroup) {
Object.keys(formControl.controls).forEach(item => {
const control = formControl.get(item);
valueCurrent = valueCurrent + this.countFormControlsRequiredFieldsForTotal(control);
});
} else if (formControl instanceof FormArray) {
formControl.controls.forEach(item => {
valueCurrent = valueCurrent + this.countFormControlsRequiredFieldsForTotal(item);
});
}
return valueCurrent;
}
controlRequired(formControl: AbstractControl) {
if (formControl.validator) {
const validator = formControl.validator({} as AbstractControl);
if (validator && validator.required) {
return true;
}
} else { return false }
}
controlEnabled(formControl: AbstractControl) {
if (formControl.enabled) {
return true;
} else { return false }
}
}

View File

@ -0,0 +1,18 @@
import { NgModule } from '@angular/core';
import { CommonFormsModule } from '@common/forms/common-forms.module';
import { CommonUiModule } from '@common/ui/common-ui.module';
import { FormProgressIndicationComponent } from './form-progress-indication.component';
@NgModule({
imports: [
CommonUiModule,
CommonFormsModule
],
declarations: [
FormProgressIndicationComponent
],
exports: [
FormProgressIndicationComponent
]
})
export class FormProgressIndicationModule { }

View File

@ -12,18 +12,19 @@ import { CommonUiModule } from '@common/ui/common-ui.module';
import { FormCompositeTitleComponent } from './components/form-composite-title/form-composite-title.component';
import { ExternalSourcesModule } from '../external-sources/external-sources.module';
import { DatasetDescriptionComponent } from './dataset-description.component';
import { FormProgressIndicationModule } from './components/form-progress-indication/form-progress-indication.module';
@NgModule({
imports: [
CommonUiModule,
CommonFormsModule,
AutoCompleteModule,
ExternalSourcesModule
ExternalSourcesModule,
FormProgressIndicationModule
],
declarations: [
DatasetDescriptionFormComponent,
DatasetDescriptionComponent,
FormProgressIndicationComponent,
FormSectionComponent,
FormCompositeFieldComponent,
FormFieldComponent,