description editor refactor - WIP

This commit is contained in:
Diamantis Tziotzios 2023-12-20 09:20:38 +02:00
parent 3e97d0fdd1
commit c6508a0082
148 changed files with 6125 additions and 3246 deletions

View File

@ -72,16 +72,6 @@ const appRoutes: Routes = [
},
{
path: 'datasetcreatewizard',
loadChildren: () => import('./ui/dataset-create-wizard/dataset-create-wizard.module').then(m => m.DatasetCreateWizardModule),
data: {
breadcrumb: true,
title: 'GENERAL.TITLES.DATASETCREATEWIZARD'
}
},
{
path: 'about',
loadChildren: () => import('./ui/about/about.module').then(m => m.AboutModule),
@ -100,14 +90,7 @@ const appRoutes: Routes = [
// }
// },
{
path: 'quick-wizard',
loadChildren: () => import('./ui/quick-wizard/quick-wizard.module').then(m => m.OuickWizardModule),
data: {
breadcrumb: true,
title: "GENERAL.TITLES.QUICK-WIZARD"
}
},
{
path: 'dataset-profiles',
loadChildren: () => import('./ui/admin/dataset-profile/dataset-profile.module').then(m => m.DatasetProfileModule),

View File

@ -1,5 +1,5 @@
import { OverlayModule } from '@angular/cdk/overlay';
import { HttpClient, HttpClientModule } from '@angular/common/http';
import { HttpClientModule } from '@angular/common/http';
import { APP_INITIALIZER, LOCALE_ID, NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MAT_MOMENT_DATE_FORMATS, MatMomentDateModule } from '@angular/material-moment-adapter';
@ -12,7 +12,6 @@ import { AppComponent } from '@app/app.component';
import { CoreServiceModule } from '@app/core/core-service.module';
import { NotificationModule } from '@app/library/notification/notification.module';
import { LoginModule } from '@app/ui/auth/login/login.module';
import { DatasetCreateWizardModule } from '@app/ui/dataset-create-wizard/dataset-create-wizard.module';
// import { BreadcrumbModule } from '@app/ui/misc/breadcrumb/breadcrumb.module';
import { HelpContentModule } from '@app/ui/misc/help-content/help-content.module';
import { NavigationModule } from '@app/ui/misc/navigation/navigation.module';
@ -143,7 +142,6 @@ export function InstallationConfigurationFactory(appConfig: ConfigurationService
HelpContentModule,
ReactiveFormsModule,
FormsModule,
DatasetCreateWizardModule,
NavbarModule,
SidebarModule,
NgcCookieConsentModule.forRoot(cookieConfig),

View File

@ -39,7 +39,6 @@ import { UiNotificationService } from './services/notification/ui-notification-s
import { OrganisationService } from './services/organisation/organisation.service';
import { ProgressIndicationService } from './services/progress-indication/progress-indication-service';
import { ProjectService } from './services/project/project.service';
import { QuickWizardService } from './services/quick-wizard/quick-wizard.service';
import { SearchBarService } from './services/search-bar/search-bar.service';
import { TimezoneService } from './services/timezone/timezone-service';
import { UnlinkAccountEmailConfirmationService } from './services/unlink-account-email-confirmation/unlink-account-email-confirmation.service';
@ -119,7 +118,6 @@ export class CoreServiceModule {
UserServiceOld,
DmpInvitationService,
DatasetExternalAutocompleteService,
QuickWizardService,
OrganisationService,
EmailConfirmationService,
ContactSupportService,

View File

@ -2,5 +2,4 @@ export class Rule {
sourceField: string;
targetField: string;
requiredValue: any;
type: string;
}

View File

@ -30,13 +30,13 @@ export interface DescriptionTemplateDefinitionPersist {
export interface DescriptionTemplatePagePersist {
id: Guid;
id: string;
ordinal: number;
title: string;
}
export interface DescriptionTemplateSectionPersist {
id: Guid;
id: string;
ordinal: number;
defaultVisibility: boolean;
multiplicity: boolean;
@ -49,7 +49,7 @@ export interface DescriptionTemplateSectionPersist {
}
export interface DescriptionTemplateFieldSetPersist {
id: Guid;
id: string;
ordinal: number;
numbering: string;
title: string;
@ -62,7 +62,7 @@ export interface DescriptionTemplateFieldSetPersist {
}
export interface DescriptionTemplateFieldPersist {
id: Guid;
id: string;
ordinal: number;
schematics: string[];
defaultValue: string;

View File

@ -34,13 +34,13 @@ export interface DescriptionTemplateDefinition {
export interface DescriptionTemplatePage {
id: Guid;
id: string;
ordinal: number;
title: string;
}
export interface DescriptionTemplateSection {
id: Guid;
id: string;
ordinal: number;
defaultVisibility: boolean;
multiplicity: boolean;
@ -55,7 +55,7 @@ export interface DescriptionTemplateSection {
}
export interface DescriptionTemplateFieldSet {
id: Guid;
id: string;
ordinal: number;
numbering: string;
title: string;
@ -68,7 +68,7 @@ export interface DescriptionTemplateFieldSet {
}
export interface DescriptionTemplateField {
id: Guid;
id: string;
ordinal: number;
numbering?: string;
schematics?: string[];

View File

@ -1,26 +0,0 @@
import { HttpHeaders } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { environment } from '../../../../environments/environment';
import { QuickWizardEditorWizardModel } from '../../../ui/quick-wizard/quick-wizard-editor/quick-wizard-editor.model';
import { BaseHttpService } from '../http/base-http.service';
import { DatasetCreateWizardModel } from '../../../ui/dataset-create-wizard/dataset-create-wizard.model';
import { ConfigurationService } from '../configuration/configuration.service';
@Injectable()
export class QuickWizardService {
private actionUrl: string;
private headers: HttpHeaders;
constructor(private http: BaseHttpService, private configurationService: ConfigurationService) {
this.actionUrl = configurationService.server + 'quick-wizard/';
}
createQuickWizard(quickWizard: QuickWizardEditorWizardModel): Observable<QuickWizardEditorWizardModel> {
return this.http.post<QuickWizardEditorWizardModel>(this.actionUrl, quickWizard, { headers: this.headers });
}
createQuickDatasetWizard(datasetCreateWizardModel: DatasetCreateWizardModel): Observable<DatasetCreateWizardModel> {
return this.http.post<DatasetCreateWizardModel>(this.actionUrl + 'datasetcreate', datasetCreateWizardModel, { headers: this.headers });
}
}

View File

@ -1,6 +1,6 @@
import { Component, Input, OnInit} from '@angular/core';
import { Rule } from '@app/core/model/dataset-profile-definition/rule';
import { VisibilityRulesService } from '@app/ui/misc/dataset-description-form/visibility-rules/visibility-rules.service';
import { VisibilityRulesService } from '@app/ui/description/editor/description-form/visibility-rules/visibility-rules.service';
@Component({

View File

@ -33,7 +33,7 @@ import { EnumUtils } from '@app/core/services/utilities/enum-utils.service';
import { FieldSetEditorModel } from '../admin/field-set-editor-model';
import { Guid } from '@common/types/guid';
import { FieldEditorModel } from '../admin/field-editor-model';
import { VisibilityRulesService } from '@app/ui/misc/dataset-description-form/visibility-rules/visibility-rules.service';
import { VisibilityRulesService } from '@app/ui/description/editor/description-form/visibility-rules/visibility-rules.service';
import { CdkStep, StepperSelectionEvent } from '@angular/cdk/stepper';
import { DatasetDescriptionCompositeFieldEditorModel, DatasetDescriptionFieldEditorModel, DatasetDescriptionSectionEditorModel } from '@app/ui/misc/dataset-description-form/dataset-description-form.model';
import { Rule } from '@app/core/model/dataset-profile-definition/rule';

View File

@ -92,8 +92,9 @@
</div>
<div [id]="'preview_container'+ form.get('id').value" class="w-100" style="margin-right: -15px; margin-left: -15px;">
<div *ngIf="previewForm && showPreview && firstField?.get('data')?.get('fieldType')?.value" [@fade-in-fast]>
<app-form-section-inner [form]="previewForm" [tableView]="form.getRawValue().multiplicity?.tableView" [datasetProfileId]="datasetProfileId">
</app-form-section-inner>
<!-- Check what we need to do with this. -->
<!-- <app-form-section-inner [form]="previewForm" [tableView]="form.getRawValue().multiplicity?.tableView" [datasetProfileId]="datasetProfileId">
</app-form-section-inner> -->
</div>
</div>

View File

@ -341,7 +341,7 @@ export class DescriptionTemplateEditorCompositeFieldComponent extends BaseCompon
addNewField() {
const field: DescriptionTemplateFieldEditorModel = new DescriptionTemplateFieldEditorModel();
field.id = Guid.create();
field.id = Guid.create().toString();
field.ordinal = (this.form.get('fields') as UntypedFormArray).length;
@ -494,7 +494,7 @@ export class DescriptionTemplateEditorCompositeFieldComponent extends BaseCompon
}
const field = {
id: Guid.create(),
id: Guid.create().toString(),
ordinal: targetOrdinal,
validations: [],
includeInExport: true

View File

@ -1,6 +1,6 @@
import { Component, Input, OnInit} from '@angular/core';
import { Rule } from '@app/core/model/dataset-profile-definition/rule';
import { VisibilityRulesService } from '@app/ui/misc/dataset-description-form/visibility-rules/visibility-rules.service';
import { VisibilityRulesService } from '@app/ui/description/editor/description-form/visibility-rules/visibility-rules.service';
@Component({

View File

@ -22,7 +22,7 @@ export class DescriptionTemplateEditorSectionFieldSetComponent implements OnInit
@Input() datasetProfileId?: string;
@Output() selectedEntryId = new EventEmitter<Guid>();
@Output() selectedEntryId = new EventEmitter<string>();
@Output() dataNeedsRefresh = new EventEmitter<void>();
@Output() removeFieldSet = new EventEmitter<string>();
@Output() addNewFieldSet = new EventEmitter<UntypedFormGroup>();
@ -101,11 +101,11 @@ export class DescriptionTemplateEditorSectionFieldSetComponent implements OnInit
private _selectedFieldSetId: Guid = null;
private _selectedFieldSetId: string = null;
get selectedFieldSetId() {
return this._selectedFieldSetId;
}
set selectedFieldSetId(id: Guid) {
set selectedFieldSetId(id: string) {
if (id === this._selectedFieldSetId) return;
this._selectedFieldSetId = id;
@ -140,7 +140,7 @@ export class DescriptionTemplateEditorSectionFieldSetComponent implements OnInit
} else {
// console.warn('!not found numbering');
}
this._selectedFieldSetId = Guid.parse(this.tocentry.id);
this._selectedFieldSetId = this.tocentry.id;
// this._scrollToElement(this.selectedFieldSetId);
this.scroller.next(this.tocentry.id);
@ -194,13 +194,13 @@ export class DescriptionTemplateEditorSectionFieldSetComponent implements OnInit
addFieldSetAfter(afterOrdinal: number, afterIndex: number): void {
const field: DescriptionTemplateFieldEditorModel = new DescriptionTemplateFieldEditorModel();
field.id = Guid.create();
field.id = Guid.create().toString();
field.ordinal = 0;//first filed in the fields list
const fieldForm = field.buildForm();
//give fieldset id and ordinal
const fieldSet: DescriptionTemplateFieldSetEditorModel = new DescriptionTemplateFieldSetEditorModel();
const fieldSetId = Guid.create();
const fieldSetId = Guid.create().toString();
fieldSet.id = fieldSetId;
fieldSet.ordinal = afterOrdinal < 0 ? 0 : afterOrdinal;

View File

@ -28,10 +28,10 @@ export class DescriptionTemplateEditorSectionComponent extends BaseComponent imp
fieldSet.ordinal = (this.form.get('fieldSets') as UntypedFormArray).length;
const field: DescriptionTemplateFieldEditorModel = new DescriptionTemplateFieldEditorModel();
field.id = Guid.create();
field.id = Guid.create().toString();
field.ordinal = 0;//first field in fields
fieldSet.fields.push(field);
fieldSet.id = Guid.create();
fieldSet.id = Guid.create().toString();
const fieldsetsArray = this.form.get('fieldSets') as UntypedFormArray;
fieldsetsArray.push(fieldSet.buildForm());

View File

@ -566,7 +566,7 @@ export class DescriptionTemplateEditorComponent extends BaseEditor<DescriptionTe
const pagesArray = (this.formGroup.get('definition').get('pages') as UntypedFormArray);
const page: DescriptionTemplatePageEditorModel = new DescriptionTemplatePageEditorModel();
page.id = Guid.create();
page.id = Guid.create().toString();
if (isNaN(pagesArray.length)) { page.ordinal = 0; } else { page.ordinal = pagesArray.length; }
const pageForm = page.buildForm();
// this.dataModel.pages.push(page);
@ -580,7 +580,7 @@ export class DescriptionTemplateEditorComponent extends BaseEditor<DescriptionTe
case ToCEntryType.Section:
const section: DescriptionTemplateSectionEditorModel = new DescriptionTemplateSectionEditorModel();
section.id = Guid.create();
section.id = Guid.create().toString();
let sectionsArray: UntypedFormArray;
if (parent.type === ToCEntryType.Page) {//FIRST LEVEL SECTION
@ -634,7 +634,7 @@ export class DescriptionTemplateEditorComponent extends BaseEditor<DescriptionTe
//create one field form fieldset
const field: DescriptionTemplateFieldEditorModel = new DescriptionTemplateFieldEditorModel();
field.id = Guid.create();
field.id = Guid.create().toString();
field.ordinal = 0;//first filed in the fields list
const fieldForm = field.buildForm();
// fieldForm.setValidators(this.customFieldValidator());
@ -647,7 +647,7 @@ export class DescriptionTemplateEditorComponent extends BaseEditor<DescriptionTe
//give fieldset id and ordinal
const fieldSet: DescriptionTemplateFieldSetEditorModel = new DescriptionTemplateFieldSetEditorModel();
const fieldSetId = Guid.create();
const fieldSetId = Guid.create().toString();
fieldSet.id = fieldSetId;
try {

View File

@ -208,7 +208,7 @@ export class DescriptionTemplateDefinitionEditorModel implements DescriptionTemp
}
export class DescriptionTemplatePageEditorModel implements DescriptionTemplatePagePersist {
id: Guid;
id: string;
ordinal: number;
title: string;
@ -266,7 +266,7 @@ export class DescriptionTemplatePageEditorModel implements DescriptionTemplatePa
}
export class DescriptionTemplateSectionEditorModel implements DescriptionTemplateSectionPersist {
id: Guid;
id: string;
ordinal: number;
defaultVisibility: boolean = false; // TODO: check if used and remove
multiplicity: boolean = false;
@ -364,7 +364,7 @@ export class DescriptionTemplateSectionEditorModel implements DescriptionTemplat
}
export class DescriptionTemplateFieldSetEditorModel implements DescriptionTemplateFieldSetPersist {
id: Guid;
id: string;
ordinal: number;
numbering: string = "0"; // Check if used and remove
title: string;
@ -525,7 +525,7 @@ export class DescriptionTemplateMultiplicityEditorModel implements DescriptionTe
//
//
export class DescriptionTemplateFieldEditorModel implements DescriptionTemplateFieldPersist {
id: Guid;
id: string;
ordinal: number;
schematics: string[];
defaultValue: string;

View File

@ -1,37 +0,0 @@
<div class="main-content">
<div class="container-fluid">
<div class="dataset-create-wizard">
<h3 *ngIf="isNew">{{ 'QUICKWIZARD.CREATE-ADD.ADD.DATASET-WIZARD' | translate }}</h3>
<p *ngIf="isNew">{{ 'QUICKWIZARD.CREATE-ADD.ADD.POST-SELECTION-INFO' | translate }}</p>
<mat-horizontal-stepper linear #stepper>
<mat-step class="step-container" [stepControl]="formGroup.get('dmpMeta')">
<ng-template matStepLabel>{{'DATASET-CREATE-WIZARD.FIRST-STEP.TITLE'| translate}}</ng-template>
<form [formGroup]="formGroup.get('dmpMeta')">
<dataset-dmp-selector-component class="col-12" [formGroup]="formGroup.get('dmpMeta')" [datasetFormGroup]="formGroup.get('datasets')" [stepper]="stepper">
</dataset-dmp-selector-component>
</form>
<div class="col-12">
<div class="row">
<div class="col"></div>
<div><button matStepperNext mat-raised-button color="primary" [disabled]="!formGroup.get('dmpMeta').valid">{{'DATASET-CREATE-WIZARD.ACTIONS.NEXT'| translate}}</button></div>
</div>
</div>
</mat-step>
<mat-step [stepControl]="formGroup">
<ng-template matStepLabel>
{{'QUICKWIZARD.CREATE-ADD.CREATE.QUICKWIZARD_CREATE.THIRD-STEP.TITLE' | translate}}
</ng-template>
<div *ngIf="formGroup.get('dmpMeta').valid && isActive('step2')">
<app-dataset-editor-wizard-component class="col-12" [formGroup]="formGroup" [datasetProfile]="formGroup.get('dmpMeta').get('datasetProfile')" [datasetLabel]="formGroup.get('dmpMeta').get('dmp').value['label']">
</app-dataset-editor-wizard-component>
</div>
<div class="navigation-buttons-container">
<button matStepperPrevious mat-raised-button color="primary">{{'QUICKWIZARD.CREATE-ADD.CREATE.QUICKWIZARD_CREATE.ACTIONS.BACK' | translate}}</button>
<button class="saveAndFinalizeButton" matStepperNext mat-raised-button (click)='saveFinalize()' [disabled]="!isFormValid() || !hasDatasets()" color="primary">{{'QUICKWIZARD.CREATE-ADD.CREATE.QUICKWIZARD_CREATE.ACTIONS.SAVE-AND-FINALIZE' | translate}}</button>
<button class="saveButton" matStepperNext mat-raised-button (click)='save()' [disabled]="!hasDatasets()" color="primary">{{'QUICKWIZARD.CREATE-ADD.CREATE.QUICKWIZARD_CREATE.ACTIONS.SAVE' | translate}}</button>
</div>
</mat-step>
</mat-horizontal-stepper>
</div>
</div>
</div>

View File

@ -1,11 +0,0 @@
.saveAndFinalizeButton{
float:right;
}
.finalizeButton{
float: right;
text-transform: uppercase;
}
.saveButton{
float:right;
margin-right: 15px;
}

View File

@ -1,163 +0,0 @@
import { Component, OnInit, ViewChild } from '@angular/core';
import { UntypedFormArray, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { MatDialog } from '@angular/material/dialog';
import { MatStepper } from '@angular/material/stepper';
import { Router } from '@angular/router';
import { DatasetStatus } from '@app/core/common/enum/dataset-status';
import { SnackBarNotificationLevel, UiNotificationService } from '@app/core/services/notification/ui-notification-service';
import { QuickWizardService } from '@app/core/services/quick-wizard/quick-wizard.service';
import { CheckDeactivateBaseComponent } from '@app/library/deactivate/deactivate.component';
import { DatasetCreateWizardModel } from '@app/ui/dataset-create-wizard/dataset-create-wizard.model';
import { DatasetEditorWizardComponent } from '@app/ui/quick-wizard/dataset-editor/dataset-editor-wizard.component';
import { ConfirmationDialogComponent } from '@common/modules/confirmation-dialog/confirmation-dialog.component';
import { TranslateService } from '@ngx-translate/core';
import { takeUntil } from 'rxjs/operators';
@Component({
selector: 'dataset-create-wizard.component',
templateUrl: 'dataset-create-wizard.component.html',
styleUrls: ['./dataset-create-wizard.component.scss'],
})
export class DatasetCreateWizard extends CheckDeactivateBaseComponent implements OnInit { //IBreadCrumbComponent
//breadCrumbs: Observable<BreadcrumbItem[]>;
@ViewChild(DatasetEditorWizardComponent) datasetEditorWizardComponent: DatasetEditorWizardComponent;
isLinear = false;
isNew = true;
isSubmitted = false;
formGroup: UntypedFormGroup;
datasetCreateWizardModel: DatasetCreateWizardModel;
@ViewChild('stepper', { static: true }) stepper: MatStepper;
constructor(
private router: Router,
private formBuilder: UntypedFormBuilder,
public quickWizardService: QuickWizardService,
public language: TranslateService,
private uiNotificationService: UiNotificationService,
private dialog: MatDialog
) {
super();
}
ngOnInit() {
this.datasetCreateWizardModel = new DatasetCreateWizardModel();
this.formGroup = this.datasetCreateWizardModel.buildForm();
this.language.get('NAV-BAR.DATASET-DESCRIPTION-WIZARD').pipe(takeUntil(this._destroyed)).subscribe(x => {
// this.breadCrumbs = observableOf([
// {
// parentComponentName: 'Dashboard',
// label: x,
// url: '/datasetcreatewizard'
// }]
// );
})
}
save() {
if (this.formGroup.get('datasets') && this.formGroup.get('datasets').get('datasetsList') && (this.formGroup.get('datasets').get('datasetsList') as UntypedFormArray).length > 0) {
for (let control of (this.formGroup.get('datasets').get('datasetsList') as UntypedFormArray).controls) {
control.get('status').setValue(DatasetStatus.Draft);
}
// this.onSubmitSave();
const dmpId = this.formGroup.get('dmpMeta').get('dmp').value.id;
this.quickWizardService.createQuickDatasetWizard(this.formGroup.value)
.pipe(takeUntil(this._destroyed))
.subscribe(
complete => this.onCallbackSuccess(dmpId)
);
} else {
return;
}
}
saveFinalize() {
const dialogRef = this.dialog.open(ConfirmationDialogComponent, {
maxWidth: '300px',
restoreFocus: false,
data: {
message: this.language.instant('GENERAL.CONFIRMATION-DIALOG.FINALIZE-ITEM'),
confirmButton: this.language.instant('GENERAL.CONFIRMATION-DIALOG.ACTIONS.CONFIRM'),
cancelButton: this.language.instant('GENERAL.CONFIRMATION-DIALOG.ACTIONS.CANCEL'),
isDeleteConfirmation: false
}
});
dialogRef.afterClosed().pipe(takeUntil(this._destroyed)).subscribe(result => {
if (result) {
if (!this.isFormValid()) { return; }
if (this.formGroup.get('datasets') && this.formGroup.get('datasets').get('datasetsList') && (this.formGroup.get('datasets').get('datasetsList') as UntypedFormArray).length > 0) {
for (let control of (this.formGroup.get('datasets').get('datasetsList') as UntypedFormArray).controls) {
control.get('status').setValue(DatasetStatus.Finalized);
}
this.onSubmitSaveAndFinalize();
} else {
return;
}
}
});
}
// onSubmitSave() {
// const dialogRef = this.dialog.open(ConfirmationDialogComponent, {
// data: {
// message: this.language.instant('QUICKWIZARD.SAVE-DIALOG.TITLE'),
// confirmButton: this.language.instant('QUICKWIZARD.SAVE-DIALOG.ACTIONS.AFFIRMATIVE'),
// cancelButton: this.language.instant('QUICKWIZARD.SAVE-DIALOG.ACTIONS.NEGATIVE')
// }
// });
// dialogRef.afterClosed().pipe(takeUntil(this._destroyed)).subscribe(result => {
// if (result) {
// this.datasetEditorWizardComponent.addDataset();
// } else if (result === false) {
// this.quickWizardService.createQuickDatasetWizard(this.formGroup.value)
// .pipe(takeUntil(this._destroyed))
// .subscribe(
// complete => this.onCallbackSuccess()
// )
// }
// });
// }
onSubmitSaveAndFinalize() {
const dmpId = this.formGroup.get('dmpMeta').get('dmp').value.id;
this.quickWizardService.createQuickDatasetWizard(this.formGroup.value)
.pipe(takeUntil(this._destroyed))
.subscribe(
complete => this.onCallbackSuccess(dmpId)
);
}
hasDatasets() {
if ((this.formGroup.get('datasets').get('datasetsList') as UntypedFormArray).length > 0) {
return true;
} else {
return false;
}
}
public isFormValid() {
return this.formGroup.valid;
}
onCallbackSuccess(dmpId: string): void {
this.isSubmitted = true;
this.uiNotificationService.snackBarNotification(this.isNew ? this.language.instant('GENERAL.SNACK-BAR.SUCCESSFUL-CREATION') : this.language.instant('GENERAL.SNACK-BAR.SUCCESSFUL-UPDATE'), SnackBarNotificationLevel.Success);
this.router.navigate(['/plans/overview/' + dmpId]);
}
isActive(step: string): boolean {
switch (step) {
case 'step1':
return this.stepper.selectedIndex == 0;
case 'step2':
return this.stepper.selectedIndex == 1;
}
}
canDeactivate(): boolean {
return this.isSubmitted || !this.formGroup.dirty;
}
}

View File

@ -1,43 +0,0 @@
import { UntypedFormBuilder, UntypedFormGroup, Validators } from "@angular/forms";
import { DmpCreateWizardFormModel } from '@app/core/model/dmp/dmp-create-wizard/dmp-create-wizard-form.model';
import { DatasetWizardEditorModel } from '@app/ui/dataset/dataset-wizard/dataset-wizard-editor.model';
import { DatasetEditorWizardModel } from '@app/ui/quick-wizard/dataset-editor/dataset-editor-wizard-model';
import { BackendErrorValidator } from '@common/forms/validation/custom-validator';
import { ValidationErrorModel } from '@common/forms/validation/error-model/validation-error-model';
import { ValidationContext } from '@common/forms/validation/validation-context';
export class DatasetCreateWizardModel {
public dmpMeta: DmpCreateWizardFormModel;
public datasets: DatasetEditorWizardModel;
public validationErrorModel: ValidationErrorModel = new ValidationErrorModel();
fromModelDmp(item: DmpCreateWizardFormModel): DatasetCreateWizardModel {
this.dmpMeta.fromModel(item);
return this;
}
fromModelDataset(item: DatasetWizardEditorModel[]): DatasetCreateWizardModel {
this.datasets.fromModel(item);
return this;
}
buildForm(context: ValidationContext = null): UntypedFormGroup {
if (context == null) { context = this.createValidationContext(); }
const formBuilder = new UntypedFormBuilder();
const formGroup = formBuilder.group({
dmpMeta: new DmpCreateWizardFormModel().buildForm(),
datasets: new DatasetEditorWizardModel().buildForm()
});
return formGroup;
}
createValidationContext(): ValidationContext {
const baseContext: ValidationContext = new ValidationContext();
baseContext.validation.push({ key: 'label', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, 'label')] });
baseContext.validation.push({ key: 'status', validators: [BackendErrorValidator(this.validationErrorModel, 'status')] });
return baseContext;
}
}

View File

@ -1,37 +0,0 @@
import { NgModule } from '@angular/core';
import { FormattingModule } from '@app/core/formatting.module';
import { AutoCompleteModule } from '@app/library/auto-complete/auto-complete.module';
import { ConfirmationDialogModule } from '@common/modules/confirmation-dialog/confirmation-dialog.module';
import { CanDeactivateGuard } from '@app/library/deactivate/can-deactivate.guard';
import { UrlListingModule } from '@app/library/url-listing/url-listing.module';
import { DatasetCreateWizard } from '@app/ui/dataset-create-wizard/dataset-create-wizard.component';
import { DatasetCreateWizardRoutingModule } from '@app/ui/dataset-create-wizard/dataset-create-wizard.routing';
import { DatasetDmpSelector } from '@app/ui/dataset-create-wizard/dmp-selector/dataset-dmp-selector.component';
import { DatasetDescriptionFormModule } from '@app/ui/misc/dataset-description-form/dataset-description-form.module';
import { OuickWizardModule } from '@app/ui/quick-wizard/quick-wizard.module';
import { QuickWizardRoutingModule } from '@app/ui/quick-wizard/quick-wizard.routing';
import { CommonFormsModule } from '@common/forms/common-forms.module';
import { CommonUiModule } from '@common/ui/common-ui.module';
@NgModule({
imports: [
DatasetCreateWizardRoutingModule,
CommonUiModule,
CommonFormsModule,
AutoCompleteModule,
FormattingModule,
UrlListingModule,
ConfirmationDialogModule,
QuickWizardRoutingModule,
DatasetDescriptionFormModule,
OuickWizardModule,
],
declarations: [
DatasetCreateWizard,
DatasetDmpSelector,
],
providers: [
CanDeactivateGuard
]
})
export class DatasetCreateWizardModule { }

View File

@ -1,32 +0,0 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { DatasetCreateWizard } from './dataset-create-wizard.component';
import { DatasetDmpSelector } from './dmp-selector/dataset-dmp-selector.component';
import { CanDeactivateGuard } from '../../library/deactivate/can-deactivate.guard';
import { AuthGuard } from '@app/core/auth-guard.service';
const routes: Routes = [
{
path: '',
component: DatasetCreateWizard,
canActivate: [AuthGuard],
data: {
breadcrumb: true
},
canDeactivate: [CanDeactivateGuard]
},
{
path: '',
component: DatasetDmpSelector,
canActivate: [AuthGuard],
data: {
breadcrumb: true
},
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class DatasetCreateWizardRoutingModule { }

View File

@ -1,20 +0,0 @@
<div class="dataset-dmp-selector" [formGroup]="formGroup" *ngIf="formGroup">
<mat-card>
<div>
<mat-form-field class="col-md-12">
<app-single-auto-complete [required]="true" [formControl]="formGroup.get('dmp')" placeholder="{{'DATASET-CREATE-WIZARD.FIRST-STEP.PLACEHOLDER' | translate}}"
[configuration]="dmpAutoCompleteConfiguration">
</app-single-auto-complete>
</mat-form-field>
</div>
<div>
<mat-form-field class="col-md-12" *ngIf="availableProfiles.length > 1">
<mat-select (selectionChange)="datasetChanged($event)" placeholder=" {{'DATASET-WIZARD.FIRST-STEP.PROFILE'| translate}}" [required]="true" formControlName="datasetProfile">
<mat-option *ngFor="let datasetProfile of availableProfiles" [value]="datasetProfile">
{{datasetProfile.label}}
</mat-option>
</mat-select>
</mat-form-field>
</div>
</mat-card>
</div>

View File

@ -1,104 +0,0 @@
import { Component, Input, OnInit } from '@angular/core';
import { UntypedFormArray, UntypedFormControl, UntypedFormGroup } from '@angular/forms';
import { MatStepper } from '@angular/material/stepper';
import { ActivatedRoute, Router } from '@angular/router';
import { DmpStatus } from '@app/core/common/enum/dmp-status';
import { DataTableRequest } from '@app/core/model/data-table/data-table-request';
import { DatasetProfileModel } from '@app/core/model/dataset/dataset-profile';
import { DmpListingModel } from '@app/core/model/dmp/dmp-listing';
import { DatasetProfileCriteria } from '@app/core/query/dataset-profile/dataset-profile-criteria';
import { DmpCriteria } from '@app/core/query/dmp/dmp-criteria';
import { RequestItem } from '@app/core/query/request-item';
import { DatasetWizardService } from '@app/core/services/dataset-wizard/dataset-wizard.service';
import { DmpService } from '@app/core/services/dmp/dmp.service';
import { SingleAutoCompleteConfiguration } from '@app/library/auto-complete/single/single-auto-complete-configuration';
import { BaseComponent } from '@common/base/base.component';
import { Observable } from 'rxjs';
import { map, takeUntil } from 'rxjs/operators';
import { DatePipe } from "@angular/common";
import { TranslateService } from "@ngx-translate/core";
@Component({
selector: 'dataset-dmp-selector-component',
templateUrl: 'dataset-dmp-selector.component.html',
styleUrls: ['./dataset-dmp-selector.component.scss'],
})
export class DatasetDmpSelector extends BaseComponent implements OnInit {
dmpAutoCompleteConfiguration: SingleAutoCompleteConfiguration;
@Input() formGroup: UntypedFormGroup;
@Input() datasetFormGroup: UntypedFormGroup;
@Input() stepper: MatStepper;
formControl: UntypedFormControl;
availableProfiles: DatasetProfileModel[] = [];
constructor(
public dmpService: DmpService,
private datasetWizardService: DatasetWizardService,
private language: TranslateService,
private datepipe: DatePipe
) {
super();
}
ngOnInit() {
this.dmpAutoCompleteConfiguration = {
filterFn: this.searchDmp.bind(this),
initialItems: (extraData) => this.searchDmp(''),
displayFn: (item) => item['label'],
titleFn: (item) => item['label'],
subtitleFn: (item) => this.language.instant('QUICKWIZARD.CREATE-ADD.ADD.CREATED') + " " + this.datepipe.transform(item['creationTime'], 'yyyy-MM-dd')
};
this.formGroup.get('dmp').valueChanges
.pipe(takeUntil(this._destroyed))
.subscribe(x => {
if (x) { this.loadDatasetProfiles(); }
else {
this.availableProfiles = [];
this.formGroup.get('datasetProfile').reset();
}
});
this.formGroup.get('datasetProfile').valueChanges
.pipe(takeUntil(this._destroyed))
.subscribe(x => {
(this.datasetFormGroup.get('datasetsList') as UntypedFormArray).controls.length = 0;
});
}
searchDmp(query: string): Observable<DmpListingModel[]> {
const fields: Array<string> = new Array<string>();
fields.push('-created');
const dmpDataTableRequest: DataTableRequest<DmpCriteria> = new DataTableRequest(0, null, { fields: fields });
dmpDataTableRequest.criteria = new DmpCriteria();
dmpDataTableRequest.criteria.status = DmpStatus.Draft;
dmpDataTableRequest.criteria.like = query;
return this.dmpService.getPaged(dmpDataTableRequest, "autocomplete").pipe(
map(y => y.data));
}
loadDatasetProfiles() {
const datasetProfileRequestItem: RequestItem<DatasetProfileCriteria> = new RequestItem();
datasetProfileRequestItem.criteria = new DatasetProfileCriteria();
datasetProfileRequestItem.criteria.id = this.formGroup.get('dmp').value.id;
if (datasetProfileRequestItem.criteria.id) {
this.datasetWizardService.getAvailableProfiles(datasetProfileRequestItem)
.pipe(takeUntil(this._destroyed))
.subscribe(items => {
this.availableProfiles = items;
if (this.availableProfiles.length === 1) {
this.formGroup.get('datasetProfile').patchValue(this.availableProfiles[0]);
this.stepper.next();
}
});
}
}
datasetChanged($event) {
this.stepper.next();
}
}

View File

@ -50,7 +50,7 @@ import {
LinkToScroll,
TableOfContents
} from '@app/ui/misc/dataset-description-form/tableOfContentsMaterial/table-of-contents';
import { VisibilityRulesService } from '@app/ui/misc/dataset-description-form/visibility-rules/visibility-rules.service';
import { VisibilityRulesService } from '@app/ui/description/editor/description-form/visibility-rules/visibility-rules.service';
import { isNullOrUndefined } from '@app/utilities/enhancers/utils';
import { FormService } from '@common/forms/form-service';
import {

View File

@ -85,11 +85,11 @@ export class DatasetListingItemComponent extends BaseComponent implements OnInit
getItemLink(): string[] {
// return this.isPublic ? [`/datasets/publicEdit/${this.dataset.id}`] : [`/datasets/edit/${this.dataset.id}`];
return this.isPublic ? ['/datasets/publicOverview/' + this.dataset.id] : ['/datasets/overview/' + this.dataset.id];
return this.isPublic ? ['/datasets/overview/public/' + this.dataset.id] : ['/datasets/overview/' + this.dataset.id];
}
getDmpLink(): string[] {
return this.isPublic ? [`/explore-plans/publicOverview/${this.dataset.dmpId}`] : [`/plans/edit/${this.dataset.dmpId}`];
return this.isPublic ? [`/explore-plans/overview/public/${this.dataset.dmpId}`] : [`/plans/edit/${this.dataset.dmpId}`];
}
downloadPDF(dataset: DatasetListingModel): void {

View File

@ -123,7 +123,7 @@ export class DatasetOverviewComponent extends BaseComponent implements OnInit {
this.users = this.dataset.dmp.users;
// const breadCrumbs = [];
// breadCrumbs.push({ parentComponentName: null, label: this.language.instant('NAV-BAR.PUBLIC DATASETS'), url: "/explore" });
// breadCrumbs.push({ parentComponentName: 'DatasetListingComponent', label: this.dataset.label, url: '/datasets/publicOverview/' + this.dataset.id });
// breadCrumbs.push({ parentComponentName: 'DatasetListingComponent', label: this.dataset.label, url: '/datasets/overview/public/' + this.dataset.id });
// this.breadCrumbs = observableOf(breadCrumbs);
}, (error: any) => {
if (error.status === 404) {
@ -290,7 +290,7 @@ export class DatasetOverviewComponent extends BaseComponent implements OnInit {
dmpClicked(dmp: DmpOverviewModel) {
if (this.isPublicView) {
this.router.navigate(['/explore-plans/publicOverview/' + dmp.id]);
this.router.navigate(['/explore-plans/overview/public/' + dmp.id]);
} else {
this.router.navigate(['/plans/overview/' + dmp.id]);
}

View File

@ -1,107 +0,0 @@
<form class="dataset-editor" *ngIf="formGroup" [formGroup]="formGroup">
<div class="col-12 intro">
<p>{{'DATASET-EDITOR.TITLE.INTRO' | translate}}</p>
<span>{{'DATASET-EDITOR.TITLE.INTRO-TIP' | translate}}</span>
</div>
<div class="col-12 card">
<!-- Title Field -->
<div class="row">
<div class="col-12">
<div class="heading">1.1 {{'DATASET-EDITOR.FIELDS.TITLE' | translate}}*</div>
<!-- <span class="hint">{{'DATASET-EDITOR.HINT.TITLE' | translate}}</span> -->
<!-- <a class="dmp-link dmp-tour-{{ formGroup.get('id').value + 1 }}" (click)="restartTour(formGroup.get('id').value + 1)">{{'DATASET-EDITOR.FIELDS.DMP' | translate}}</a>
<span class="hint">{{'DATASET-EDITOR.HINT.TITLE-REST' | translate}}</span> -->
<div class="title-form">
<mat-form-field>
<input matInput placeholder="{{'DATASET-EDITOR.FIELDS.TITLE' | translate}}" type="text" name="label" formControlName="label" required>
<mat-error *ngIf="formGroup.get('label').hasError('backendError')"> {{formGroup.get('label').getError('backendError').message}}</mat-error>
<mat-error *ngIf="formGroup.get('label').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</mat-form-field>
</div>
</div>
</div>
<!-- Description field -->
<div class="row">
<div class="col-12">
<div class="heading">1.2 {{'DATASET-EDITOR.FIELDS.DESCRIPTION' | translate}}</div>
<span class="hint">{{'DATASET-EDITOR.HINT.DESCRIPTION' | translate}}</span>
<!-- <span class="hint">{{'DATASET-EDITOR.HINT.TITLE' | translate}}</span>
<a class="dmp-link dmp-tour-{{ formGroup.get('id').value + 2 }}" (click)="restartTour(formGroup.get('id').value + 2)">{{'DATASET-EDITOR.FIELDS.DMP' | translate}}</a>
<span class="hint">{{'DATASET-EDITOR.HINT.TITLE-REST' | translate}}</span> -->
<div class="description-form">
<rich-text-editor-component [parentFormGroup]="formGroup" [controlName]="'description'"
[placeholder]="'DMP-EDITOR.PLACEHOLDER.DESCRIPTION'"
[wrapperClasses]="'full-width editor ' +
((formGroup.get('description').touched && (formGroup.get('description').hasError('required') || formGroup.get('description').hasError('backendError'))) ? 'required' : '')"
[editable]="!formGroup.get('description').disabled">
</rich-text-editor-component>
<div [class]="(formGroup.get('description').touched && (formGroup.get('description').hasError('required') || formGroup.get('description').hasError('backendError'))) ? 'visible' : 'invisible'" class="mat-form-field form-field-subscript-wrapper">
<mat-error *ngIf="formGroup.get('description').hasError('backendError')">{{formGroup.get('description').getError('backendError').message}}</mat-error>
<mat-error *ngIf="formGroup.get('description').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</div>
</div>
</div>
</div>
<!-- Uri field -->
<!-- <div class="row">
<div class="col-12">
<div class="heading">1.3 {{'DATASET-EDITOR.FIELDS.EXTERNAL-LINK' | translate}}</div>
<span class="hint"></span>
<div class="uri-form">
<mat-form-field>
<input matInput placeholder="{{'DATASET-EDITOR.PLACEHOLDER.EXTERNAL-LINK' | translate}}" type="text" name="uri" formControlName="uri">
<mat-error *ngIf="formGroup.get('uri').hasError('backendError')">{{formGroup.get('uri').getError('backendError').message}}</mat-error>
<mat-error *ngIf="formGroup.get('uri').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</mat-form-field>
</div>
</div>
</div> -->
<!-- External Fields -->
<app-dataset-external-references-editor-component [formGroup]="formGroup" [viewOnly]="viewOnly"></app-dataset-external-references-editor-component>
<!-- Template Field -->
<div class="heading">1.4 {{'DATASET-EDITOR.FIELDS.PROFILE' | translate}}*</div>
<div class="profile-form">
<mat-form-field>
<mat-select placeholder="{{'DATASET-WIZARD.FIRST-STEP.PROFILE'| translate}}" [required]="true" [compareWith]="compareWith" formControlName="profile">
<mat-option *ngFor="let profile of availableProfiles" [value]="profile">
<div (click)="checkMinMax($event, profile)">
{{profile.label}}
</div>
</mat-option>
</mat-select>
<mat-error *ngIf="formGroup.get('profile').hasError('backendError')">{{formGroup.get('profile').getError('backendError').message}}</mat-error>
<mat-error *ngIf="formGroup.get('label').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</mat-form-field>
</div>
</div>
</form>
<!-- <div class="container-fluid">
<div class="row dataset-editor" [formGroup]="formGroup">
<div class="col-12">
<div class="row">
<mat-form-field class="col-sm-12 col-md-6">
<input matInput placeholder="{{'DATASET-EDITOR.FIELDS.NAME' | translate}}" type="text" name="label" formControlName="label" required>
<mat-error *ngIf="formGroup.get('label').hasError('backendError')">{{formGroup.get('label').getError('backendError').message}}</mat-error>
<mat-error *ngIf="formGroup.get('label').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</mat-form-field>
</div>
<div class="row" *ngIf="showUri">
<mat-form-field class="col-sm-12 col-md-6">
<input matInput placeholder="{{'DATASET-EDITOR.FIELDS.URI' | translate}}" type="text" name="uri" formControlName="uri">
<mat-error *ngIf="formGroup.get('uri').hasError('backendError')">{{formGroup.get('uri').getError('backendError').message}}</mat-error>
<mat-error *ngIf="formGroup.get('uri').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</mat-form-field>
</div>
<div class="row">
<mat-form-field class="col-sm-12 col-md-6">
<textarea matInput class="description-area" placeholder="{{'DATASET-EDITOR.FIELDS.DESCRIPTION' | translate}}" formControlName="description"></textarea>
<mat-error *ngIf="formGroup.get('description').hasError('backendError')">{{formGroup.get('description').getError('backendError').message}}</mat-error>
<mat-error *ngIf="formGroup.get('description').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</mat-form-field>
</div>
</div>
</div>
</div> -->

View File

@ -1,145 +0,0 @@
import { Component, Input } from '@angular/core';
import { UntypedFormGroup } from '@angular/forms';
import { Router } from '@angular/router';
import { BaseComponent } from '@common/base/base.component';
import { GuidedTourService } from '@app/library/guided-tour/guided-tour.service';
import { GuidedTour, Orientation } from '@app/library/guided-tour/guided-tour.constants';
import { TranslateService } from '@ngx-translate/core';
import { DatasetProfileModel } from '@app/core/model/dataset/dataset-profile';
import { takeUntil } from 'rxjs/operators';
import { DmpBlueprintService } from '@app/core/services/dmp/dmp-blueprint.service';
import { MatDialog } from '@angular/material/dialog';
import { PopupNotificationDialogComponent } from '@app/library/notification/popup/popup-notification.component';
import { Guid } from '@common/types/guid';
import { nameof } from 'ts-simple-nameof';
import { DescriptionTemplatesInSection, DmpBlueprint, DmpBlueprintDefinition, DmpBlueprintDefinitionSection, FieldInSection } from '@app/core/model/dmp-blueprint/dmp-blueprint';
@Component({
selector: 'app-dataset-editor-component',
templateUrl: 'dataset-editor.component.html',
styleUrls: ['./dataset-editor.component.scss']
})
export class DatasetEditorComponent extends BaseComponent {
@Input() formGroup: UntypedFormGroup;
// @Input() formGroup: FormGroup = null;
@Input() availableProfiles: DatasetProfileModel[];
@Input() dmpId: string;
showUri: boolean = false;
dmpText: string = null;
viewOnly = false;
constructor(
private router: Router,
private dmpBlueprintService: DmpBlueprintService,
private dialog: MatDialog,
private guidedTourService: GuidedTourService,
private language: TranslateService
) { super(); }
public dashboardTourDmp: GuidedTour = {
tourId: 'only-dmp-tour',
useOrb: true,
steps: [
{
title: this.dmpText,
content: 'Step 1',
orientation: Orientation.Bottom,
highlightPadding: 3,
isStepUnique: true,
customTopOffset: 8
}
]
};
checkMinMax(event, profile: DatasetProfileModel) {
event.stopPropagation();
const dmpSectionIndex = this.formGroup.get('dmpSectionIndex').value;
const blueprintId = this.formGroup.get('dmp').value.profile.id;
this.dmpBlueprintService.getSingle(blueprintId,
[
[nameof<DmpBlueprint>(x => x.definition), nameof<DmpBlueprintDefinition>(x => x.sections), nameof<DmpBlueprintDefinitionSection>(x => x.id)].join('.'),
[nameof<DmpBlueprint>(x => x.definition), nameof<DmpBlueprintDefinition>(x => x.sections), nameof<DmpBlueprintDefinitionSection>(x => x.label)].join('.'),
[nameof<DmpBlueprint>(x => x.definition), nameof<DmpBlueprintDefinition>(x => x.sections), nameof<DmpBlueprintDefinitionSection>(x => x.description)].join('.'),
[nameof<DmpBlueprint>(x => x.definition), nameof<DmpBlueprintDefinition>(x => x.sections), nameof<DmpBlueprintDefinitionSection>(x => x.ordinal)].join('.'),
[nameof<DmpBlueprint>(x => x.definition), nameof<DmpBlueprintDefinition>(x => x.sections), nameof<DmpBlueprintDefinitionSection>(x => x.hasTemplates)].join('.'),
[nameof<DmpBlueprint>(x => x.definition), nameof<DmpBlueprintDefinition>(x => x.sections), nameof<DmpBlueprintDefinitionSection>(x => x.fields), nameof<FieldInSection>(x => x.id)].join('.'),
[nameof<DmpBlueprint>(x => x.definition), nameof<DmpBlueprintDefinition>(x => x.sections), nameof<DmpBlueprintDefinitionSection>(x => x.fields), nameof<FieldInSection>(x => x.category)].join('.'),
[nameof<DmpBlueprint>(x => x.definition), nameof<DmpBlueprintDefinition>(x => x.sections), nameof<DmpBlueprintDefinitionSection>(x => x.fields), nameof<FieldInSection>(x => x.dataType)].join('.'),
[nameof<DmpBlueprint>(x => x.definition), nameof<DmpBlueprintDefinition>(x => x.sections), nameof<DmpBlueprintDefinitionSection>(x => x.fields), nameof<FieldInSection>(x => x.systemFieldType)].join('.'),
[nameof<DmpBlueprint>(x => x.definition), nameof<DmpBlueprintDefinition>(x => x.sections), nameof<DmpBlueprintDefinitionSection>(x => x.fields), nameof<FieldInSection>(x => x.label)].join('.'),
[nameof<DmpBlueprint>(x => x.definition), nameof<DmpBlueprintDefinition>(x => x.sections), nameof<DmpBlueprintDefinitionSection>(x => x.fields), nameof<FieldInSection>(x => x.placeholder)].join('.'),
[nameof<DmpBlueprint>(x => x.definition), nameof<DmpBlueprintDefinition>(x => x.sections), nameof<DmpBlueprintDefinitionSection>(x => x.fields), nameof<FieldInSection>(x => x.description)].join('.'),
[nameof<DmpBlueprint>(x => x.definition), nameof<DmpBlueprintDefinition>(x => x.sections), nameof<DmpBlueprintDefinitionSection>(x => x.fields), nameof<FieldInSection>(x => x.required)].join('.'),
[nameof<DmpBlueprint>(x => x.definition), nameof<DmpBlueprintDefinition>(x => x.sections), nameof<DmpBlueprintDefinitionSection>(x => x.fields), nameof<FieldInSection>(x => x.ordinal)].join('.'),
[nameof<DmpBlueprint>(x => x.definition), nameof<DmpBlueprintDefinition>(x => x.sections), nameof<DmpBlueprintDefinitionSection>(x => x.descriptionTemplates), nameof<DescriptionTemplatesInSection>(x => x.id)].join('.'),
[nameof<DmpBlueprint>(x => x.definition), nameof<DmpBlueprintDefinition>(x => x.sections), nameof<DmpBlueprintDefinitionSection>(x => x.descriptionTemplates), nameof<DescriptionTemplatesInSection>(x => x.descriptionTemplateId)].join('.'),
[nameof<DmpBlueprint>(x => x.definition), nameof<DmpBlueprintDefinition>(x => x.sections), nameof<DmpBlueprintDefinitionSection>(x => x.descriptionTemplates), nameof<DescriptionTemplatesInSection>(x => x.label)].join('.'),
[nameof<DmpBlueprint>(x => x.definition), nameof<DmpBlueprintDefinition>(x => x.sections), nameof<DmpBlueprintDefinitionSection>(x => x.descriptionTemplates), nameof<DescriptionTemplatesInSection>(x => x.minMultiplicity)].join('.'),
[nameof<DmpBlueprint>(x => x.definition), nameof<DmpBlueprintDefinition>(x => x.sections), nameof<DmpBlueprintDefinitionSection>(x => x.descriptionTemplates), nameof<DescriptionTemplatesInSection>(x => x.maxMultiplicity)].join('.'),
]
)
.pipe(takeUntil(this._destroyed))
.subscribe(result => {
const section = result.definition.sections[dmpSectionIndex];
if(section.hasTemplates){
const foundTemplate = section.descriptionTemplates.find(template => template.descriptionTemplateId === Guid.parse(profile.id));
if (foundTemplate !== undefined) {
let count = 0;
if(this.formGroup.get('dmp').value.datasets != null){
for(let dataset of this.formGroup.get('dmp').value.datasets){
if(dataset.dmpSectionIndex === dmpSectionIndex && dataset.profile.id === foundTemplate.descriptionTemplateId){
count++;
}
}
if(count === foundTemplate.maxMultiplicity){
this.dialog.open(PopupNotificationDialogComponent, {
data: {
title: this.language.instant('DATASET-EDITOR.MAX-DESCRIPTION-DIALOG.TITLE'),
message: this.language.instant('DATASET-EDITOR.MAX-DESCRIPTION-DIALOG.MESSAGE')
}, maxWidth: '30em'
});
}
else{
this.formGroup.get('profile').setValue(profile);
}
}
}
else {
this.formGroup.get('profile').setValue(profile);
}
}
else {
this.formGroup.get('profile').setValue(profile);
}
});
}
getDmpText(): string {
return this.language.instant('DMP-LISTING.TEXT-INFO') + '\n\n' +
this.language.instant('DMP-LISTING.TEXT-INFO-QUESTION') + ' ' +
this.language.instant('DMP-LISTING.LINK-ZENODO') + ' ' +
this.language.instant('DMP-LISTING.GET-IDEA');
}
setDashboardTourDmp(label: string): void {
this.dashboardTourDmp.steps[0].title = this.getDmpText();
this.dashboardTourDmp.steps[0].selector = '.dmp-tour-' + label;
}
public restartTour(label: string): void {
this.setDashboardTourDmp(label);
this.guidedTourService.startTour(this.dashboardTourDmp);
}
public cancel(): void {
this.router.navigate(['/datasets']);
}
public compareWith(object1: any, object2: any) {
return object1 && object2 && object1.id === object2.id;
}
}

View File

@ -1,150 +0,0 @@
<div class="main-content">
<div class="container-fluid description-editor">
<form *ngIf="formGroup" [formGroup]="formGroup">
<!-- <form *ngIf="formGroup" [formGroup]="formGroup" (ngSubmit)="formSubmit()"> -->
<!-- Description Header -->
<div class="fixed-editor-header">
<div class="card description-editor-header">
<div class="col">
<div class="row">
<div class="col info">
<ng-container *ngIf="!viewOnly else viewOnlyTemplate">
<div *ngIf="isNew" class="description-title">{{'DMP-EDITOR.TITLE.ADD-DATASET' | translate}}</div>
<div *ngIf="!isNew" class="description-title">{{'DMP-EDITOR.TITLE.EDIT-DESCRIPTION' | translate}}</div>
<div class="description-subtitle">{{ formGroup.get('label').value }} <span *ngIf="isDirty()" class="description-changes">({{'DMP-EDITOR.CHANGES' | translate}})</span></div>
</ng-container>
<ng-template #viewOnlyTemplate>
<div class="description-title">{{'DMP-EDITOR.TITLE.PREVIEW-DATASET' | translate}}</div>
</ng-template>
<div class="d-flex flex-direction-row dmp-info">
<div class="col-auto description-to-dmp">{{'DATASET-LISTING.TOOLTIP.TO-DMP' | translate}}</div>
<div class="dmp-title p-0">:&nbsp;{{ formGroup.get('dmp').value.label }}</div>
<div class="col-auto d-flex align-items-center">
<a [routerLink]="['/overview/' + formGroup.get('dmp').value.id]" target="_blank" class="pointer open-in-new-icon">
<mat-icon class="size-18">open_in_new</mat-icon>
</a>
</div>
</div>
</div>
<div class="row">
<div class="col-auto d-flex align-items-center">
<button *ngIf="formGroup.get('id').value" [disabled]="isDirty()" [matTooltipDisabled]="!isDirty()"
mat-raised-button class="description-export-btn" type="button"
[matMenuTriggerFor]="exportMenu" (click)="$event.stopPropagation();"
[matTooltip]="'DATASET-EDITOR.ACTIONS.DISABLED-EXPORT' | translate">
{{ 'DMP-LISTING.ACTIONS.EXPORT' | translate }}
<mat-icon [disabled]="isDirty()" style="width: 14px;">expand_more</mat-icon>
</button>
<mat-menu #exportMenu="matMenu" xPosition="before">
<button mat-menu-item (click)="downloadPDF(formGroup.get('id').value)">
<i class="fa fa-file-pdf-o pr-2"></i>
<span>{{'GENERAL.FILE-TYPES.PDF' | translate}}</span>
</button>
<button mat-menu-item (click)="downloadDOCX(formGroup.get('id').value)">
<i class="fa fa-file-word-o pr-2"></i>
<span>{{'GENERAL.FILE-TYPES.DOC' | translate}}</span>
</button>
<button mat-menu-item (click)="downloadXML(formGroup.get('id').value)">
<i class="fa fa-file-code-o pr-2"></i>
<span>{{'GENERAL.FILE-TYPES.XML' | translate}}</span>
</button>
</mat-menu>
</div>
<mat-divider *ngIf="formGroup.get('id').value && (!viewOnly || (!lockStatus && !viewOnly) || lockStatus || (hasReversableStatus() && !lockStatus))"
[vertical]="true" class="ml-2 mr-2"></mat-divider>
<div *ngIf="isDirty() && !viewOnly" class="col-auto d-flex align-items-center pr-0">
<button [disabled]="saving" type="button" mat-raised-button class="description-discard-btn" (click)="discardChanges()">
{{'DMP-EDITOR.ACTIONS.DISCARD' | translate}}
</button>
</div>
<div class="col-auto d-flex align-items-center">
<button [disabled]="saving" *ngIf="!lockStatus && !viewOnly" mat-raised-button
class="description-save-btn mr-2" type="button">
<span class="d-flex flex-row row">
<span (click)="!saving?save():null" class="col">{{ 'DATASET-WIZARD.ACTIONS.SAVE' | translate }}</span>
<mat-divider [vertical]="true"></mat-divider>
<span *ngIf="!saving" class="align-items-center justify-content-center col-4 d-flex"
(click)="$event.stopPropagation();" [matMenuTriggerFor]="menu">
<mat-icon >expand_more</mat-icon>
</span>
<span *ngIf="saving" class="align-items-center justify-content-center col-4 d-flex">
<mat-icon >expand_more</mat-icon>
</span>
</span>
</button>
<mat-menu #menu="matMenu">
<button [disabled]="saving" mat-menu-item (click)="save(saveAnd.close)" type="button">{{ 'DATASET-WIZARD.ACTIONS.SAVE-AND-CLOSE' | translate }}</button>
<button [disabled]="saving" mat-menu-item (click)="save(saveAnd.addNew)" type="button">{{ 'DATASET-WIZARD.ACTIONS.SAVE-AND-ADD' | translate }}</button>
<button [disabled]="saving" mat-menu-item (click)="save()" type="button">{{ 'DATASET-WIZARD.ACTIONS.SAVE-AND-CONTINUE' | translate }}</button>
</mat-menu>
<button [disabled]="saving" *ngIf="!lockStatus && !viewOnly" mat-raised-button class="description-save-btn mr-2" type="button" (click)="saveFinalize()">{{ 'DATASET-WIZARD.ACTIONS.FINALIZE' | translate }}</button>
<!-- <button *ngIf="!lockStatus && !viewOnly" mat-raised-button class="description-save-btn mr-2" (click)="save()" type="button">{{ 'DATASET-WIZARD.ACTIONS.SAVE' | translate }}</button>
<button *ngIf="!lockStatus && !viewOnly" mat-raised-button class="description-save-btn mr-2" (click)="save(saveAnd.close)" type="button">{{ 'DATASET-WIZARD.ACTIONS.SAVE-AND-CLOSE' | translate }}</button>
<button *ngIf="!lockStatus && !viewOnly" mat-raised-button class="description-save-btn mr-2" (click)="save(saveAnd.addNew)">{{ 'DATASET-WIZARD.ACTIONS.SAVE-AND-ADD' | translate }}</button> -->
<button [disabled]="saving" *ngIf="lockStatus" mat-raised-button disabled class="description-save-btn cursor-default" type="button">{{ 'DMP-OVERVIEW.LOCKED' | translate}}</button>
<button [disabled]="saving" *ngIf="hasReversableStatus() && !lockStatus" mat-raised-button class="description-save-btn mr-2" (click)="reverse()" type="button">{{ 'DATASET-WIZARD.ACTIONS.REVERSE' | translate }}</button>
<!-- <button *ngIf="!lockStatus" mat-raised-button class="description-save-btn mr-2" (click)="touchForm()" type="button">{{ 'DATASET-WIZARD.ACTIONS.VALIDATE' | translate }}</button> -->
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row editor-content">
<div class="col-auto description-stepper">
<div class="stepper-back d-flex flex-direction-row">
<div class="d-flex align-items-center pr-2 back-to-dmp" (click)="backToDmp(formGroup.get('dmp').value.id)">
<mat-icon class="back-icon pointer">chevron_left</mat-icon>
<span class="pointer">{{'DATASET-WIZARD.ACTIONS.BACK-TO' | translate}}</span>
</div>
<div class="col-auto dmp-label ml-3">{{'DATASET-LISTING.TOOLTIP.DMP' | translate}}</div>
</div>
<div class="stepper-title">{{'DMP-EDITOR.STEPPER.USER-GUIDE' | translate}}</div>
<div class="stepper-options" id="stepper-options">
<div class="col stepper-list">
<div (click)="table0fContents.onToCentrySelected()" *ngIf="!descriptionInfoValid()" class="main-info" [ngClass]="{'active': this.step === 0, 'text-danger':hintErrors}">0. {{'DMP-EDITOR.STEPPER.MAIN-INFO' | translate}} (2)</div>
<div (click)="table0fContents.onToCentrySelected()" *ngIf="descriptionInfoValid()" class="main-info" [ngClass]="{'active': this.step === 0}">0. {{'DMP-EDITOR.STEPPER.MAIN-INFO' | translate}} (<mat-icon class="done-icon">done</mat-icon>)</div>
<div class="row toc-pane-container" #boundary>
<div #spacer></div>
<table-of-contents [visibilityRulesService]="visRulesService" [selectedFieldsetId]="fieldsetIdWithFocus" #table0fContents
[showErrors]="showtocentriesErrors" [TOCENTRY_ID_PREFIX]="TOCENTRY_ID_PREFIX" [hasFocus]="step > 0"
[formGroup]="formGroup" *ngIf="formGroup && formGroup.get('descriptionProfileDefinition')" [links]="links"
[boundary]="boundary" [spacer]="spacer" [isActive]="step !== 0" stickyThing (stepFound)="onStepFound($event)"
(currentLinks)="getLinks($event)" (entrySelected)="changeStep($event.entry, $event.execute)"
[visibilityRules]="formGroup.get('descriptionProfileDefinition').get('rules').value"></table-of-contents>
</div>
</div>
</div>
<div class="stepper-actions">
<div mat-raised-button type="button" class="col-auto previous stepper-btn mr-2" [ngClass]="{'previous-disabled': this.step === 0}" (click)="previousStep()">
<span class="material-icons">chevron_left</span>
<div>{{'DMP-EDITOR.STEPPER.PREVIOUS' | translate}}</div>
</div>
<div *ngIf="this.step < this.maxStep" mat-raised-button type="button" class="col-auto stepper-btn description-next ml-auto" (click)="nextStep()">
<div>{{'DMP-EDITOR.STEPPER.NEXT' | translate}}</div>
<span class="material-icons">chevron_right</span>
</div>
<div *ngIf="!formGroup.get('profile').value" mat-raised-button type="button" class="col-auto stepper-btn description-next next-disabled ml-auto">
<div>{{'DMP-EDITOR.STEPPER.NEXT' | translate}}</div>
<span class="material-icons">chevron_right</span>
</div>
<button [disabled]="saving" (click)="save(saveAnd.addNew)" *ngIf="(step === maxStep) && !lockStatus && formGroup.get('profile').value && !viewOnly" mat-raised-button type="button" class="col-auto stepper-btn add-description-btn ml-auto">
{{ 'DATASET-WIZARD.ACTIONS.SAVE-AND-ADD' | translate }}
</button>
</div>
<div class="col-auto pr-0">
<app-form-progress-indication class="col-12" *ngIf="formGroup && !viewOnly" [formGroup]="formGroup" [isDescriptionEditor]="true"></app-form-progress-indication>
</div>
</div>
<div class="col-auto form" id="description-editor-form">
<app-description-editor-component [hidden]="this.step !== 0" [formGroup]="formGroup" [dmpId]="formGroup.get('dmp').value.id" [availableProfiles]="availableDescriptionTemplates" (formChanged)="formChanged()"></app-description-editor-component>
<app-description-description (visibilityRulesInstance)="visRulesService = $event" [TOCENTRY_ID_PREFIX]="TOCENTRY_ID_PREFIX" [hidden]="this.step === 0" *ngIf="formGroup && formGroup.get('descriptionProfileDefinition')" [form]="this.formGroup.get('descriptionProfileDefinition')" [visibilityRules]="formGroup.get('descriptionProfileDefinition').get('rules').value" [descriptionProfileId]="formGroup.get('profile').value" [linkToScroll]="linkToScroll" (fieldsetFocusChange)="fieldsetIdWithFocus = $event"></app-description-description>
</div>
</div>
</form>
</div>
</div>

View File

@ -1,67 +0,0 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { DescriptionTemplate } from '@app/core/model/description-template/description-template';
import { Description, DescriptionField, DescriptionReference, DescriptionTag, PropertyDefinition } from '@app/core/model/description/description';
import { Dmp, DmpDescriptionTemplate } from '@app/core/model/dmp/dmp';
import { Reference } from '@app/core/model/reference/reference';
import { Tag } from '@app/core/model/tag/tag';
import { DescriptionService } from '@app/core/services/description/description.service';
import { BreadcrumbService } from '@app/ui/misc/breadcrumb/breadcrumb.service';
import { BaseEditorResolver } from '@common/base/base-editor.resolver';
import { Guid } from '@common/types/guid';
import { takeUntil, tap } from 'rxjs/operators';
import { nameof } from 'ts-simple-nameof';
@Injectable()
export class DescriptionEditorResolver extends BaseEditorResolver {
constructor(private descriptionService: DescriptionService, private breadcrumbService: BreadcrumbService) {
super();
}
public static lookupFields(): string[] {
return [
...BaseEditorResolver.lookupFields(),
nameof<Description>(x => x.id),
nameof<Description>(x => x.label),
nameof<Description>(x => x.status),
nameof<Description>(x => x.description),
nameof<Description>(x => x.status),
[nameof<Description>(x => x.dmp), nameof<Dmp>(x => x.id)].join('.'),
[nameof<Description>(x => x.dmpDescriptionTemplate), nameof<DmpDescriptionTemplate>(x => x.id)].join('.'),
[nameof<Description>(x => x.descriptionTemplate), nameof<DescriptionTemplate>(x => x.id)].join('.'),
[nameof<Description>(x => x.properties), nameof<PropertyDefinition>(x => x.fields), nameof<DescriptionField>(x => x.key)].join('.'),
[nameof<Description>(x => x.properties), nameof<PropertyDefinition>(x => x.fields), nameof<DescriptionField>(x => x.value)].join('.'),
[nameof<Description>(x => x.descriptionTags), nameof<DescriptionTag>(x => x.id),].join('.'),
[nameof<Description>(x => x.descriptionTags), nameof<DescriptionTag>(x => x.tag), nameof<Tag>(x => x.label)].join('.'),
[nameof<Description>(x => x.descriptionReferences), nameof<DescriptionReference>(x => x.reference), nameof<Reference>(x => x.id)].join('.'),
[nameof<Description>(x => x.descriptionReferences), nameof<DescriptionReference>(x => x.reference), nameof<Reference>(x => x.label)].join('.'),
[nameof<Description>(x => x.descriptionReferences), nameof<DescriptionReference>(x => x.reference), nameof<Reference>(x => x.type)].join('.'),
[nameof<Description>(x => x.descriptionReferences), nameof<DescriptionReference>(x => x.reference), nameof<Reference>(x => x.reference)].join('.'),
nameof<Description>(x => x.createdAt),
nameof<Description>(x => x.hash),
nameof<Description>(x => x.isActive)
]
}
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
const fields = [
...DescriptionEditorResolver.lookupFields()
];
const id = route.paramMap.get('id');
// const cloneid = route.paramMap.get('cloneid');
if (id != null) {
return this.descriptionService.getSingle(Guid.parse(id), fields).pipe(tap(x => this.breadcrumbService.addIdResolvedValue(x.id?.toString(), x.label)), takeUntil(this._destroyed));
}
//TODO: check this
// else if (cloneid != null) {
// return this.descriptionService.clone(Guid.parse(cloneid), fields).pipe(tap(x => this.breadcrumbService.addIdResolvedValue(x.id?.toString(), x.label)), takeUntil(this._destroyed));
// }
}
}

View File

@ -1,84 +1,19 @@
import { NgModule } from '@angular/core';
import { FormattingModule } from '@app/core/formatting.module';
import { AutoCompleteModule } from '@app/library/auto-complete/auto-complete.module';
import { ExportMethodDialogModule } from '@app/library/export-method-dialog/export-method-dialog.module';
import { RichTextEditorModule } from "@app/library/rich-text-editor/rich-text-editor.module";
import { UrlListingModule } from '@app/library/url-listing/url-listing.module';
// import { DescriptionEditorComponent } from '@app/ui/description/description-wizard/description-editor/description-editor.component';
// import { DescriptionWizardComponent } from '@app/ui/description/description-wizard/description-wizard.component';
// import { DescriptionExternalReferencesEditorComponent } from '@app/ui/description/description-wizard/external-references/description-external-references-editor.component';
// import { DescriptionExternalDataRepositoryDialogEditorComponent } from '@app/ui/description/description-wizard/external-references/editors/data-repository/description-external-data-repository-dialog-editor.component';
// import { DescriptionExternalDescriptionDialogEditorComponent } from '@app/ui/description/description-wizard/external-references/editors/external-description/description-external-description-dialog-editor.component';
// import { DescriptionExternalRegistryDialogEditorComponent } from '@app/ui/description/description-wizard/external-references/editors/registry/description-external-registry-dialog-editor.component';
// import { DescriptionExternalServiceDialogEditorComponent } from '@app/ui/description/description-wizard/external-references/editors/service/description-external-service-dialog-editor.component';
// import { PrefillDescriptionComponent } from "@app/ui/description/description-wizard/prefill-description/prefill-description.component";
import { DescriptionRoutingModule } from '@app/ui/description/description.routing';
// import { DescriptionCriteriaComponent } from '@app/ui/description/listing/criteria/description-criteria.component';
// import { DescriptionUploadDialogue } from '@app/ui/description/listing/criteria/description-upload-dialogue/description-upload-dialogue.component';
import { DescriptionListingComponent } from '@app/ui/description/listing/description-listing.component';
import { DescriptionListingItemComponent } from '@app/ui/description/listing/listing-item/description-listing-item.component';
// import { DescriptionDescriptionFormModule } from '@app/ui/misc/description-description-form/description-description-form.module';
// import { TableOfContentsModule } from '@app/ui/misc/description-description-form/tableOfContentsMaterial/table-of-contents.module';
import { ExternalSourcesModule } from '@app/ui/misc/external-sources/external-sources.module';
import { CommonFormsModule } from '@common/forms/common-forms.module';
import { FormValidationErrorsDialogModule } from '@common/forms/form-validation-errors-dialog/form-validation-errors-dialog.module';
import { ConfirmationDialogModule } from '@common/modules/confirmation-dialog/confirmation-dialog.module';
import { CommonUiModule } from '@common/ui/common-ui.module';
import { AngularStickyThingsModule } from '@w11k/angular-sticky-things';
import { DescriptionCopyDialogModule } from './description-copy-dialog/description-copy-dialog.module';
import { DescriptionOverviewModule } from './overview/description-overview.module';
import { DescriptionEditorComponent } from './dataset-wizard/description-editor.component';
// import { FormProgressIndicationModule } from '../misc/description-description-form/components/form-progress-indication/form-progress-indication.module';
// import { DescriptionCopyDialogModule } from './description-wizard/description-copy-dialogue/description-copy-dialogue.module';
// import { DescriptionCriteriaDialogComponent } from './listing/criteria/description-criteria-dialogue/description-criteria-dialog.component';
// import { DescriptionOverviewModule } from './overview/description-overview.module';
@NgModule({
imports: [
CommonUiModule,
CommonFormsModule,
UrlListingModule,
FormattingModule,
ConfirmationDialogModule,
AutoCompleteModule,
ExternalSourcesModule,
ExportMethodDialogModule,
// DescriptionDescriptionFormModule,
// TableOfContentsModule,
AngularStickyThingsModule,
DescriptionRoutingModule,
FormValidationErrorsDialogModule,
// DescriptionCopyDialogModule,
// DescriptionOverviewModule,
// FormProgressIndicationModule,
RichTextEditorModule,
DescriptionCopyDialogModule,
DescriptionOverviewModule,
],
declarations: [
DescriptionListingComponent,
// DescriptionCriteriaComponent,
// DescriptionWizardComponent,
DescriptionEditorComponent,
// DescriptionExternalReferencesEditorComponent,
// DescriptionExternalDataRepositoryDialogEditorComponent,
// DescriptionExternalDescriptionDialogEditorComponent,
// DescriptionExternalRegistryDialogEditorComponent,
// DescriptionExternalServiceDialogEditorComponent,
// DescriptionUploadDialogue,
DescriptionListingItemComponent,
// DescriptionCriteriaDialogComponent,
// PrefillDescriptionComponent
],
exports: [
// DescriptionExternalReferencesEditorComponent,
// DescriptionExternalDataRepositoryDialogEditorComponent,
// DescriptionExternalDescriptionDialogEditorComponent,
// DescriptionExternalRegistryDialogEditorComponent,
// DescriptionExternalServiceDialogEditorComponent,
// DescriptionEditorComponent,
// DescriptionDescriptionFormModule
]
})
export class DescriptionModule { }

View File

@ -1,136 +1,35 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AuthGuard } from '../../core/auth-guard.service';
// import { DescriptionWizardComponent } from './description-wizard/description-wizard.component';
import { DescriptionListingComponent } from './listing/description-listing.component';
import { DescriptionOverviewComponent } from './overview/description-overview.component';
import { CanDeactivateGuard } from '@app/library/deactivate/can-deactivate.guard';
import { DescriptionEditorComponent } from './dataset-wizard/description-editor.component';
import { AppPermission } from '@app/core/common/enum/permission.enum';
import { DescriptionEditorResolver } from './dataset-wizard/description-editor.resolver';
import { PendingChangesGuard } from '@common/forms/pending-form-changes/pending-form-changes-guard.service';
// import { DescriptionOverviewComponent } from './overview/description-overview.component';
import { BreadcrumbService } from '@app/ui/misc/breadcrumb/breadcrumb.service';
const routes: Routes = [
{
path: '',
component: DescriptionListingComponent,
// canActivate: [AuthGuard],
path: 'overview',
loadChildren: () => import('./overview/description-overview.module').then(m => m.DescriptionOverviewModule),
data: {
breadcrumb: true
},
},
{
path: 'dmp/:dmpId',
component: DescriptionListingComponent,
canActivate: [AuthGuard],
data: {
breadcrumb: true
},
},
{
path: 'overview/:id',
component: DescriptionOverviewComponent,
data: {
breadcrumb: true,
title: 'GENERAL.TITLES.DATASET-OVERVIEW'
},
},
{
path: 'publicOverview/:publicId',
component: DescriptionOverviewComponent,
data: {
breadcrumb: true,
title: 'GENERAL.TITLES.DATASET-OVERVIEW'
},
},
{
path: 'edit/:id',
canActivate: [AuthGuard],
component: DescriptionEditorComponent,
canDeactivate: [PendingChangesGuard],
resolve: {
'entity': DescriptionEditorResolver
},
data: {
...BreadcrumbService.generateRouteDataConfiguration({
title: 'BREADCRUMBS.EDIT-DESCRIPTION'
}),
authContext: {
permissions: [AppPermission.EditDescription]
}
}
},
// {
// path: 'new/:dmpId/:dmpSectionIndex',
// component: DescriptionWizardComponent,
// canActivate: [AuthGuard],
// data: {
// breadcrumb: true,
// title: 'GENERAL.TITLES.DATASET-NEW'
// },
// canDeactivate:[CanDeactivateGuard]
// },
// {
// path: 'edit/:id/finalize',
// component: DescriptionWizardComponent,
// canActivate: [AuthGuard],
// data: {
// breadcrumb: true,
// public: false,
// title: 'GENERAL.TITLES.DATASET-EDIT',
// finalize: true
// },
// canDeactivate:[CanDeactivateGuard]
// },
// {
// path: 'publicEdit/:publicId',
// component: DescriptionWizardComponent,
// //canActivate: [AuthGuard],
// data: {
// public: true,
// title: 'GENERAL.TITLES.DATASET-PUBLIC-EDIT'
// },
// canDeactivate:[CanDeactivateGuard]
// },
// {
// path: 'new',
// component: DescriptionWizardComponent,
// canActivate: [AuthGuard],
// data: {
// breadcrumb: true,
// title: 'GENERAL.TITLES.DATASET-NEW'
// },
// canDeactivate:[CanDeactivateGuard]
// },
// {
// path: 'copy/:id',
// component: DescriptionWizardComponent,
// canActivate: [AuthGuard],
// data: {
// breadcrumb: true,
// title: 'GENERAL.TITLES.DATASET-COPY'
// },
// canDeactivate:[CanDeactivateGuard]
// },
// {
// path: 'profileupdate/:updateId',
// component: DescriptionWizardComponent,
// canActivate: [AuthGuard],
// data: {
// breadcrumb: true,
// title: 'GENERAL.TITLES.DATASET-UPDATE'
// },
// canDeactivate:[CanDeactivateGuard]
// },
{
path: 'edit',
loadChildren: () => import('./editor/description-editor.module').then(m => m.DescriptionEditorModule),
data: {
breadcrumb: true
}
},
{
path: '',
loadChildren: () => import('./listing/description-listing.module').then(m => m.DescriptionListingModule),
data: {
breadcrumb: true
},
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
providers: [DescriptionEditorResolver]
providers: []
})
export class DescriptionRoutingModule { }

View File

@ -0,0 +1,58 @@
<form class="description-base-fields-editor" *ngIf="formGroup" [formGroup]="formGroup">
<div class="col-12 intro">
<p>{{'DATASET-EDITOR.TITLE.INTRO' | translate}}</p>
<span>{{'DATASET-EDITOR.TITLE.INTRO-TIP' | translate}}</span>
</div>
<div class="col-12 card">
<!-- Title Field -->
<div class="row">
<div class="col-12">
<div class="heading">1.1 {{'DATASET-EDITOR.FIELDS.TITLE' | translate}}*</div>
<div class="title-form">
<mat-form-field>
<input matInput placeholder="{{'DATASET-EDITOR.FIELDS.TITLE' | translate}}" type="text" name="label" formControlName="label" required>
<mat-error *ngIf="formGroup.get('label').hasError('backendError')"> {{formGroup.get('label').getError('backendError').message}}</mat-error>
<mat-error *ngIf="formGroup.get('label').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</mat-form-field>
</div>
</div>
</div>
<!-- Description field -->
<div class="row">
<div class="col-12">
<div class="heading">1.2 {{'DATASET-EDITOR.FIELDS.DESCRIPTION' | translate}}</div>
<span class="hint">{{'DATASET-EDITOR.HINT.DESCRIPTION' | translate}}</span>
<div class="description-form">
<rich-text-editor-component [parentFormGroup]="formGroup" [controlName]="'description'"
[placeholder]="'DMP-EDITOR.PLACEHOLDER.DESCRIPTION'"
[wrapperClasses]="'full-width editor ' +
((formGroup.get('description').touched && (formGroup.get('description').hasError('required') || formGroup.get('description').hasError('backendError'))) ? 'required' : '')"
[editable]="!formGroup.get('description').disabled">
</rich-text-editor-component>
<div [class]="(formGroup.get('description').touched && (formGroup.get('description').hasError('required') || formGroup.get('description').hasError('backendError'))) ? 'visible' : 'invisible'" class="mat-form-field form-field-subscript-wrapper">
<mat-error *ngIf="formGroup.get('description').hasError('backendError')">{{formGroup.get('description').getError('backendError').message}}</mat-error>
<mat-error *ngIf="formGroup.get('description').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</div>
</div>
</div>
</div>
<!-- External Fields -->
<!-- TODO: add external references -->
<!-- <app-dataset-external-references-editor-component [formGroup]="formGroup" [viewOnly]="viewOnly"></app-dataset-external-references-editor-component> -->
<!-- Template Field -->
<div class="heading">1.4 {{'DATASET-EDITOR.FIELDS.PROFILE' | translate}}*</div>
<!-- <div class="profile-form">
<mat-form-field>
<mat-select placeholder="{{'DATASET-WIZARD.FIRST-STEP.PROFILE'| translate}}" [required]="true" [compareWith]="compareWith" formControlName="profile">
<mat-option *ngFor="let descriptionTemplates of availableDescriptionTemplates" [value]="profile">
<div (click)="checkMinMax($event, descriptionTemplates)">
{{descriptionTemplates.label}}
</div>
</mat-option>
</mat-select>
<mat-error *ngIf="formGroup.get('profile').hasError('backendError')">{{formGroup.get('profile').getError('backendError').message}}</mat-error>
<mat-error *ngIf="formGroup.get('label').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</mat-form-field>
</div> -->
</div>
</form>

View File

@ -0,0 +1,90 @@
import { Component, Input } from '@angular/core';
import { UntypedFormGroup } from '@angular/forms';
import { DescriptionTemplate } from '@app/core/model/description-template/description-template';
import { BaseComponent } from '@common/base/base.component';
@Component({
selector: 'app-description-base-fields-editor-component',
templateUrl: 'description-base-fields-editor.component.html',
styleUrls: ['./description-base-fields-editor.component.scss']
})
export class DescriptionBaseFieldsEditorComponent extends BaseComponent {
@Input() formGroup: UntypedFormGroup;
@Input() availableDescriptionTemplates: DescriptionTemplate[];
@Input() dmpId: string;
viewOnly = false; //TODO: not used.
constructor(
) { super(); }
checkMinMax(event, profile: DescriptionTemplate) {
//TODO: Add logic for validating description templates.
// event.stopPropagation();
// const dmpSectionIndex = this.formGroup.get('dmpSectionIndex').value;
// const blueprintId = this.formGroup.get('dmp').value.profile.id;
// this.dmpBlueprintService.getSingle(blueprintId,
// [
// [nameof<DmpBlueprint>(x => x.definition), nameof<DmpBlueprintDefinition>(x => x.sections), nameof<DmpBlueprintDefinitionSection>(x => x.id)].join('.'),
// [nameof<DmpBlueprint>(x => x.definition), nameof<DmpBlueprintDefinition>(x => x.sections), nameof<DmpBlueprintDefinitionSection>(x => x.label)].join('.'),
// [nameof<DmpBlueprint>(x => x.definition), nameof<DmpBlueprintDefinition>(x => x.sections), nameof<DmpBlueprintDefinitionSection>(x => x.description)].join('.'),
// [nameof<DmpBlueprint>(x => x.definition), nameof<DmpBlueprintDefinition>(x => x.sections), nameof<DmpBlueprintDefinitionSection>(x => x.ordinal)].join('.'),
// [nameof<DmpBlueprint>(x => x.definition), nameof<DmpBlueprintDefinition>(x => x.sections), nameof<DmpBlueprintDefinitionSection>(x => x.hasTemplates)].join('.'),
// [nameof<DmpBlueprint>(x => x.definition), nameof<DmpBlueprintDefinition>(x => x.sections), nameof<DmpBlueprintDefinitionSection>(x => x.fields), nameof<FieldInSection>(x => x.id)].join('.'),
// [nameof<DmpBlueprint>(x => x.definition), nameof<DmpBlueprintDefinition>(x => x.sections), nameof<DmpBlueprintDefinitionSection>(x => x.fields), nameof<FieldInSection>(x => x.category)].join('.'),
// [nameof<DmpBlueprint>(x => x.definition), nameof<DmpBlueprintDefinition>(x => x.sections), nameof<DmpBlueprintDefinitionSection>(x => x.fields), nameof<FieldInSection>(x => x.dataType)].join('.'),
// [nameof<DmpBlueprint>(x => x.definition), nameof<DmpBlueprintDefinition>(x => x.sections), nameof<DmpBlueprintDefinitionSection>(x => x.fields), nameof<FieldInSection>(x => x.systemFieldType)].join('.'),
// [nameof<DmpBlueprint>(x => x.definition), nameof<DmpBlueprintDefinition>(x => x.sections), nameof<DmpBlueprintDefinitionSection>(x => x.fields), nameof<FieldInSection>(x => x.label)].join('.'),
// [nameof<DmpBlueprint>(x => x.definition), nameof<DmpBlueprintDefinition>(x => x.sections), nameof<DmpBlueprintDefinitionSection>(x => x.fields), nameof<FieldInSection>(x => x.placeholder)].join('.'),
// [nameof<DmpBlueprint>(x => x.definition), nameof<DmpBlueprintDefinition>(x => x.sections), nameof<DmpBlueprintDefinitionSection>(x => x.fields), nameof<FieldInSection>(x => x.description)].join('.'),
// [nameof<DmpBlueprint>(x => x.definition), nameof<DmpBlueprintDefinition>(x => x.sections), nameof<DmpBlueprintDefinitionSection>(x => x.fields), nameof<FieldInSection>(x => x.required)].join('.'),
// [nameof<DmpBlueprint>(x => x.definition), nameof<DmpBlueprintDefinition>(x => x.sections), nameof<DmpBlueprintDefinitionSection>(x => x.fields), nameof<FieldInSection>(x => x.ordinal)].join('.'),
// [nameof<DmpBlueprint>(x => x.definition), nameof<DmpBlueprintDefinition>(x => x.sections), nameof<DmpBlueprintDefinitionSection>(x => x.descriptionTemplates), nameof<DescriptionTemplatesInSection>(x => x.id)].join('.'),
// [nameof<DmpBlueprint>(x => x.definition), nameof<DmpBlueprintDefinition>(x => x.sections), nameof<DmpBlueprintDefinitionSection>(x => x.descriptionTemplates), nameof<DescriptionTemplatesInSection>(x => x.descriptionTemplateId)].join('.'),
// [nameof<DmpBlueprint>(x => x.definition), nameof<DmpBlueprintDefinition>(x => x.sections), nameof<DmpBlueprintDefinitionSection>(x => x.descriptionTemplates), nameof<DescriptionTemplatesInSection>(x => x.label)].join('.'),
// [nameof<DmpBlueprint>(x => x.definition), nameof<DmpBlueprintDefinition>(x => x.sections), nameof<DmpBlueprintDefinitionSection>(x => x.descriptionTemplates), nameof<DescriptionTemplatesInSection>(x => x.minMultiplicity)].join('.'),
// [nameof<DmpBlueprint>(x => x.definition), nameof<DmpBlueprintDefinition>(x => x.sections), nameof<DmpBlueprintDefinitionSection>(x => x.descriptionTemplates), nameof<DescriptionTemplatesInSection>(x => x.maxMultiplicity)].join('.'),
// ]
// )
// .pipe(takeUntil(this._destroyed))
// .subscribe(result => {
// const section = result.definition.sections[dmpSectionIndex];
// if(section.hasTemplates){
// const foundTemplate = section.descriptionTemplates.find(template => template.descriptionTemplateId === Guid.parse(profile.id));
// if (foundTemplate !== undefined) {
// let count = 0;
// if(this.formGroup.get('dmp').value.datasets != null){
// for(let dataset of this.formGroup.get('dmp').value.datasets){
// if(dataset.dmpSectionIndex === dmpSectionIndex && dataset.profile.id === foundTemplate.descriptionTemplateId){
// count++;
// }
// }
// if(count === foundTemplate.maxMultiplicity){
// this.dialog.open(PopupNotificationDialogComponent, {
// data: {
// title: this.language.instant('DATASET-EDITOR.MAX-DESCRIPTION-DIALOG.TITLE'),
// message: this.language.instant('DATASET-EDITOR.MAX-DESCRIPTION-DIALOG.MESSAGE')
// }, maxWidth: '30em'
// });
// }
// else{
// this.formGroup.get('profile').setValue(profile);
// }
// }
// }
// else {
// this.formGroup.get('profile').setValue(profile);
// }
// }
// else {
// this.formGroup.get('profile').setValue(profile);
// }
// });
}
public compareWith(object1: any, object2: any) {
return object1 && object2 && object1.id === object2.id;
}
}

View File

@ -0,0 +1,143 @@
<div class="main-content">
<div class="container-fluid description-editor">
<form *ngIf="formGroup" [formGroup]="formGroup">
<!-- <form *ngIf="formGroup" [formGroup]="formGroup" (ngSubmit)="formSubmit()"> -->
<!-- Description Header -->
<div class="fixed-editor-header">
<div class="card description-editor-header">
<div class="col">
<div class="row">
<div class="col info">
<ng-container *ngIf="!viewOnly else viewOnlyTemplate">
<div *ngIf="isNew" class="description-title">{{'DESCRIPTION-EDITOR.TITLE.ADD-DATASET' | translate}}</div>
<div *ngIf="!isNew" class="description-title">{{'DESCRIPTION-EDITOR.TITLE.EDIT-DESCRIPTION' | translate}}</div>
<div class="description-subtitle">{{ formGroup.get('label').value }} <span *ngIf="isDirty()" class="description-changes">({{'DESCRIPTION-EDITOR.CHANGES' | translate}})</span></div>
</ng-container>
<ng-template #viewOnlyTemplate>
<div class="description-title">{{'DESCRIPTION-EDITOR.TITLE.PREVIEW-DATASET' | translate}}</div>
</ng-template>
<div class="d-flex flex-direction-row dmp-info">
<div class="col-auto description-to-dmp">{{'DESCRIPTION-EDITOR.TOOLTIP.TO-DMP' | translate}}</div>
<div class="dmp-title p-0">:&nbsp;{{ item.dmp.label }}</div>
<div class="col-auto d-flex align-items-center">
<a [routerLink]="['/overview/' + item.dmp.id]" target="_blank" class="pointer open-in-new-icon">
<mat-icon class="size-18">open_in_new</mat-icon>
</a>
</div>
</div>
</div>
<div class="row">
<div class="col-auto d-flex align-items-center">
<button *ngIf="formGroup.get('id').value" [disabled]="isDirty()" [matTooltipDisabled]="!isDirty()" mat-raised-button class="description-export-btn" type="button" [matMenuTriggerFor]="exportMenu" (click)="$event.stopPropagation();" [matTooltip]="'DATASET-EDITOR.ACTIONS.DISABLED-EXPORT' | translate">
{{ 'DMP-LISTING.ACTIONS.EXPORT' | translate }}
<mat-icon [disabled]="isDirty()" style="width: 14px;">expand_more</mat-icon>
</button>
<mat-menu #exportMenu="matMenu" xPosition="before">
<button mat-menu-item (click)="downloadPDF(formGroup.get('id').value)">
<i class="fa fa-file-pdf-o pr-2"></i>
<span>{{'GENERAL.FILE-TYPES.PDF' | translate}}</span>
</button>
<button mat-menu-item (click)="downloadDOCX(formGroup.get('id').value)">
<i class="fa fa-file-word-o pr-2"></i>
<span>{{'GENERAL.FILE-TYPES.DOC' | translate}}</span>
</button>
<button mat-menu-item (click)="downloadXML(formGroup.get('id').value)">
<i class="fa fa-file-code-o pr-2"></i>
<span>{{'GENERAL.FILE-TYPES.XML' | translate}}</span>
</button>
</mat-menu>
</div>
<mat-divider *ngIf="formGroup.get('id').value && (!viewOnly || (!lockStatus && !viewOnly) || lockStatus || (hasReversableStatus() && !lockStatus))" [vertical]="true" class="ml-2 mr-2"></mat-divider>
<div *ngIf="isDirty() && !viewOnly" class="col-auto d-flex align-items-center pr-0">
<button [disabled]="saving" type="button" mat-raised-button class="description-discard-btn" (click)="discardChanges()">
{{'DESCRIPTION-EDITOR.ACTIONS.DISCARD' | translate}}
</button>
</div>
<div class="col-auto d-flex align-items-center">
<button [disabled]="saving" *ngIf="!lockStatus && !viewOnly" mat-raised-button class="description-save-btn mr-2" type="button">
<span class="d-flex flex-row row">
<span (click)="!saving?save():null" class="col">{{ 'DESCRIPTION-EDITOR.ACTIONS.SAVE' | translate }}</span>
<mat-divider [vertical]="true"></mat-divider>
<span *ngIf="!saving" class="align-items-center justify-content-center col-4 d-flex" (click)="$event.stopPropagation();" [matMenuTriggerFor]="menu">
<mat-icon>expand_more</mat-icon>
</span>
<span *ngIf="saving" class="align-items-center justify-content-center col-4 d-flex">
<mat-icon>expand_more</mat-icon>
</span>
</span>
</button>
<mat-menu #menu="matMenu">
<button [disabled]="saving" mat-menu-item (click)="save(saveAnd.close)" type="button">{{ 'DESCRIPTION-EDITOR.ACTIONS.SAVE-AND-CLOSE' | translate }}</button>
<button [disabled]="saving" mat-menu-item (click)="save(saveAnd.addNew)" type="button">{{ 'DESCRIPTION-EDITOR.ACTIONS.SAVE-AND-ADD' | translate }}</button>
<button [disabled]="saving" mat-menu-item (click)="save()" type="button">{{ 'DESCRIPTION-EDITOR.ACTIONS.SAVE-AND-CONTINUE' | translate }}</button>
</mat-menu>
<button [disabled]="saving" *ngIf="!lockStatus && !viewOnly" mat-raised-button class="description-save-btn mr-2" type="button" (click)="saveFinalize()">{{ 'DESCRIPTION-EDITOR.ACTIONS.FINALIZE' | translate }}</button>
<button [disabled]="saving" *ngIf="lockStatus" mat-raised-button disabled class="description-save-btn cursor-default" type="button">{{ 'DMP-OVERVIEW.LOCKED' | translate}}</button>
<button [disabled]="saving" *ngIf="hasReversableStatus() && !lockStatus" mat-raised-button class="description-save-btn mr-2" (click)="reverse()" type="button">{{ 'DESCRIPTION-EDITOR.ACTIONS.REVERSE' | translate }}</button>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row editor-content">
<div class="col-auto description-stepper">
<div class="stepper-back d-flex flex-direction-row">
<div class="d-flex align-items-center pr-2 back-to-dmp" (click)="backToDmp()">
<mat-icon class="back-icon pointer">chevron_left</mat-icon>
<span class="pointer">{{'DESCRIPTION-EDITOR.ACTIONS.BACK-TO' | translate}}</span>
</div>
<div class="col-auto dmp-label ml-3">{{'DESCRIPTION-EDITOR.TOOLTIP.DMP' | translate}}</div>
</div>
<div class="stepper-title">{{'DESCRIPTION-EDITOR.STEPPER.USER-GUIDE' | translate}}</div>
<div class="stepper-options" id="stepper-options">
<div class="col stepper-list">
<div (click)="table0fContents.onToCentrySelected()" *ngIf="!descriptionInfoValid()" class="main-info" [ngClass]="{'active': this.step === 0, 'text-danger':hintErrors}">0. {{'DESCRIPTION-EDITOR.STEPPER.MAIN-INFO' | translate}} (2)</div>
<div (click)="table0fContents.onToCentrySelected()" *ngIf="descriptionInfoValid()" class="main-info" [ngClass]="{'active': this.step === 0}">0. {{'DESCRIPTION-EDITOR.STEPPER.MAIN-INFO' | translate}} (<mat-icon class="done-icon">done</mat-icon>)</div>
<div class="row toc-pane-container" #boundary>
<div #spacer></div>
<app-table-of-contents [visibilityRulesService]="visibilityRulesService" [selectedFieldsetId]="fieldsetIdWithFocus" #table0fContents [showErrors]="showtocentriesErrors" [TOCENTRY_ID_PREFIX]="TOCENTRY_ID_PREFIX" [hasFocus]="step > 0" [propertiesFormGroup]="formGroup.get('properties')" [descriptionTemplate]="item.descriptionTemplate" *ngIf="formGroup" [links]="links" [boundary]="boundary" [spacer]="spacer" [isActive]="step !== 0" stickyThing (stepFound)="onStepFound($event)" (currentLinks)="getLinks($event)" (entrySelected)="changeStep($event.entry, $event.execute)"></app-table-of-contents>
</div>
</div>
</div>
<div class="stepper-actions">
<div mat-raised-button type="button" class="col-auto previous stepper-btn mr-2" [ngClass]="{'previous-disabled': this.step === 0}" (click)="previousStep()">
<span class="material-icons">chevron_left</span>
<div>{{'DESCRIPTION-EDITOR.STEPPER.PREVIOUS' | translate}}</div>
</div>
<div *ngIf="this.step < this.maxStep" mat-raised-button type="button" class="col-auto stepper-btn description-next ml-auto" (click)="nextStep()">
<div>{{'DESCRIPTION-EDITOR.STEPPER.NEXT' | translate}}</div>
<span class="material-icons">chevron_right</span>
</div>
<div *ngIf="!formGroup.get('descriptionTemplateId').value" mat-raised-button type="button" class="col-auto stepper-btn description-next next-disabled ml-auto">
<div>{{'DESCRIPTION-EDITOR.STEPPER.NEXT' | translate}}</div>
<span class="material-icons">chevron_right</span>
</div>
<button [disabled]="saving" (click)="save(saveAnd.addNew)" *ngIf="(step === maxStep) && !lockStatus && formGroup.get('descriptionTemplateId').value && !viewOnly" mat-raised-button type="button" class="col-auto stepper-btn add-description-btn ml-auto">
{{ 'DESCRIPTION-EDITOR.ACTIONS.SAVE-AND-ADD' | translate }}
</button>
</div>
<div class="col-auto pr-0">
<app-form-progress-indication class="col-12" *ngIf="formGroup && !viewOnly" [formGroup]="formGroup" [isDescriptionEditor]="true"></app-form-progress-indication>
</div>
</div>
<div class="col-auto form" id="description-editor-form">
<app-description-base-fields-editor-component [hidden]="this.step !== 0" [formGroup]="formGroup" [dmpId]="item.dmp.id" [availableDescriptionTemplates]="availableDescriptionTemplates" (formChanged)="formChanged()"></app-description-base-fields-editor-component>
<app-description-form
*ngIf="formGroup && formGroup.get('properties')"
[propertiesFormGroup]="formGroup.get('properties')"
[descriptionTemplate]="item.descriptionTemplate"
[visibilityRulesService]="visibilityRulesService"
[TOCENTRY_ID_PREFIX]="TOCENTRY_ID_PREFIX"
[hidden]="this.step === 0"
[linkToScroll]="linkToScroll"
(fieldsetFocusChange)="fieldsetIdWithFocus = $event"></app-description-form>
</div>
</div>
</form>
</div>
</div>

View File

@ -1,27 +1,11 @@
import { Location } from '@angular/common';
import { HttpClient } from '@angular/common/http';
import { Component, OnInit, ViewChild } from '@angular/core';
import { AbstractControl, UntypedFormArray, UntypedFormControl, UntypedFormGroup } from '@angular/forms';
import { UntypedFormGroup } from '@angular/forms';
import { MatDialog } from '@angular/material/dialog';
import { MatSnackBar } from '@angular/material/snack-bar';
import { ActivatedRoute, Router } from '@angular/router';
import { ActivatedRoute, Params, Router } from '@angular/router';
import { DescriptionStatus } from '@app/core/common/enum/description-status';
import { DmpStatus } from '@app/core/common/enum/dmp-status';
import { SaveType } from '@app/core/common/enum/save-type';
import { DataTableRequest } from '@app/core/model/data-table/data-table-request';
import { DmpModel } from '@app/core/model/dmp/dmp';
import { DmpListingModel } from '@app/core/model/dmp/dmp-listing';
import { LockModel } from '@app/core/model/lock/lock.model';
import { UserInfoListingModel } from '@app/core/model/user/user-info-listing';
import { DmpCriteria } from '@app/core/query/dmp/dmp-criteria';
import { RequestItem } from '@app/core/query/request-item';
import { AuthService } from '@app/core/services/auth/auth.service';
import { ConfigurationService } from '@app/core/services/configuration/configuration.service';
import { DmpService } from '@app/core/services/dmp/dmp.service';
import {
ExternalSourcesConfigurationService
} from '@app/core/services/external-sources/external-sources-configuration.service';
import { ExternalSourcesService } from '@app/core/services/external-sources/external-sources.service';
import { LockService } from '@app/core/services/lock/lock.service';
import { MatomoService } from '@app/core/services/matomo/matomo-service';
import {
@ -29,37 +13,34 @@ import {
UiNotificationService
} from '@app/core/services/notification/ui-notification-service';
import { FileUtils } from '@app/core/services/utilities/file-utils.service';
import { SingleAutoCompleteConfiguration } from '@app/library/auto-complete/single/single-auto-complete-configuration';
import { CheckDeactivateBaseComponent } from '@app/library/deactivate/deactivate.component';
import { PopupNotificationDialogComponent } from '@app/library/notification/popup/popup-notification.component';
// import { IBreadCrumbComponent } from '@app/ui/misc/breadcrumb/definition/IBreadCrumbComponent';
// import { BreadcrumbItem } from '@app/ui/misc/breadcrumb/definition/breadcrumb-item';
import { isNullOrUndefined } from '@app/utilities/enhancers/utils';
import { FormService } from '@common/forms/form-service';
import {
FormValidationErrorsDialogComponent
} from '@common/forms/form-validation-errors-dialog/form-validation-errors-dialog.component';
import { ValidationErrorModel } from '@common/forms/validation/error-model/validation-error-model';
import { ConfirmationDialogComponent } from '@common/modules/confirmation-dialog/confirmation-dialog.component';
import { Guid } from '@common/types/guid';
import { TranslateService } from '@ngx-translate/core';
import * as FileSaver from 'file-saver';
import { Observable, interval, of as observableOf } from 'rxjs';
import { catchError, debounceTime, filter, map, takeUntil } from 'rxjs/operators';
import { DescriptionEditorService } from './description-editor.service';
import { BaseEditor } from '@common/base/base-editor';
import { DescriptionEditorModel } from './description-editor.model';
import { Description, DescriptionPersist } from '@app/core/model/description/description';
import { DatePipe } from '@angular/common';
import { IsActive } from '@app/core/common/enum/is-active.enum';
import { AppPermission } from '@app/core/common/enum/permission.enum';
import { HttpErrorHandlingService } from '@common/modules/errors/error-handling/http-error-handling.service';
import { QueryParamsService } from '@app/core/services/utilities/query-params.service';
import { FilterService } from '@common/modules/text-filter/filter-service';
import { EnumUtils } from '@app/core/services/utilities/enum-utils.service';
import { Rule } from '@app/core/model/dataset-profile-definition/rule';
import { Description, DescriptionPersist } from '@app/core/model/description/description';
import { DescriptionService } from '@app/core/services/description/description.service';
import { LoggingService } from '@app/core/services/logging/logging-service';
import { EnumUtils } from '@app/core/services/utilities/enum-utils.service';
import { QueryParamsService } from '@app/core/services/utilities/query-params.service';
import { VisibilityRulesService } from '@app/ui/description/editor/description-form/visibility-rules/visibility-rules.service';
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 { map, takeUntil } from 'rxjs/operators';
import { DescriptionEditorModel } from './description-editor.model';
import { DescriptionEditorResolver } from './description-editor.resolver';
import { IsActive } from '@app/core/common/enum/is-active.enum';
import { DatePipe } from '@angular/common';
import { DescriptionEditorService } from './description-editor.service';
import { ToCEntry } from './table-of-contents/models/toc-entry';
import { TableOfContentsComponent } from './table-of-contents/table-of-contents.component';
import { ToCEntryType } from './table-of-contents/models/toc-entry-type.enum';
@Component({
selector: 'app-description-editor-component',
@ -72,6 +53,7 @@ export class DescriptionEditorComponent extends BaseEditor<DescriptionEditorMode
isNew = true;
isDeleted = false;
formGroup: UntypedFormGroup = null;
item: Description;
// showInactiveDetails = false;
// selectedSystemFields: Array<DescriptionSystemFieldType> = [];
// DescriptionSectionFieldCategory = DescriptionSectionFieldCategory;
@ -89,6 +71,14 @@ export class DescriptionEditorComponent extends BaseEditor<DescriptionEditorMode
// popupItemActionIcon: 'visibility'
// };
// hasChanges = false;
viewOnly = false;
lockStatus: Boolean;
@ViewChild('table0fContents') table0fContents: TableOfContentsComponent;
step: number = 0;
protected get canDelete(): boolean {
return !this.isDeleted && !this.isNew && this.hasPermission(this.authService.permissionEnum.DeleteDescription);
}
@ -126,14 +116,428 @@ export class DescriptionEditorComponent extends BaseEditor<DescriptionEditorMode
private descriptionEditorService: DescriptionEditorService,
private fileUtils: FileUtils,
private matomoService: MatomoService,
private dmpService: DmpService
private dmpService: DmpService,
private lockService: LockService,
public visibilityRulesService: VisibilityRulesService
) {
super(dialog, language, formService, router, uiNotificationService, httpErrorHandlingService, filterService, datePipe, route, queryParamsService);
}
ngOnInit(): void {
this.matomoService.trackPageView('Admin: DMP Blueprints');
this.matomoService.trackPageView('Description Editor');
super.ngOnInit();
// const params = this.route.snapshot.params;
// const queryParams = this.route.snapshot.queryParams;
// const data: any = this.route.snapshot.data;
// this.init();
this.route.params
.pipe(takeUntil(this._destroyed))
.subscribe((params: Params) => {
const isPublicDescription = params['public'];
const itemId = params['id'];
const newDmpId = params['newDmpId'];
// const publicId = params['publicId'];
// this.dmpId = params['dmpId'];
// this.dmpSectionIndex = parseInt(params['dmpSectionIndex']);
// this.newDmpId = queryParams['newDmpId'];
// this.publicId = params['publicId'];
// this.profileUpdateId = params['updateId'];
// this.finalize = data.finalize;
// this.itemId ? this.downloadDocumentId = this.itemId : this.downloadDocumentId = this.publicId
this.viewOnly = isPublicDescription;
// if (itemId != null) {
// this.isNew = false;
// this.isPublicView = false;
// this.descriptionService.getSingle(itemId, this.lookupFields())
// .pipe(takeUntil(this._destroyed))
// .subscribe(data => {
// this.description = data;
// this.researchers = this.referenceService.getReferencesForTypes(this.description?.dmp?.dmpReferences, [ReferenceType.Researcher]);
// // this.users = this.description.dmp.users;
// this.checkLockStatus(this.description.id);
// this.setIsUserOwner();
// }, (error: any) => {
// if (error.status === 404) {
// return this.onFetchingDeletedCallbackError('/descriptions/');
// }
// if (error.status === 403) {
// return this.onFetchingForbiddenCallbackError('/descriptions/');
// }
// });
// }
// else if (publicId != null) {
// this.isNew = false;
// this.isFinalized = true;
// this.isPublicView = true;
// this.descriptionService.getPublicSingle(publicId, this.lookupFields())
// .pipe(takeUntil(this._destroyed))
// .subscribe(data => {
// this.description = data;
// this.researchers = this.referenceService.getReferencesForTypes(this.description?.dmp?.dmpReferences, [ReferenceType.Researcher]);
// // this.users = this.description.dmp.users;
// // const breadCrumbs = [];
// // breadCrumbs.push({ parentComponentName: null, label: this.language.instant('NAV-BAR.PUBLIC DESCRIPTIONS'), url: "/explore" });
// // breadCrumbs.push({ parentComponentName: 'DescriptionListingComponent', label: this.description.label, url: '/descriptions/overview/public/' + this.description.id });
// // this.breadCrumbs = observableOf(breadCrumbs);
// }, (error: any) => {
// if (error.status === 404) {
// return this.onFetchingDeletedCallbackError('/explore-descriptions');
// }
// if (error.status === 403) {
// return this.onFetchingForbiddenCallbackError('/explore-descriptions');
// }
// });
// }
if (itemId != null && newDmpId == null) {
this.isNew = false;
this.lockService.checkLockStatus(itemId).pipe(takeUntil(this._destroyed)).subscribe(lockStatus => {
this.lockStatus = lockStatus;
if (this.item.status === DescriptionStatus.Finalized || lockStatus) {
this.formGroup.disable();
this.viewOnly = true;
}
if (lockStatus) {
this.dialog.open(PopupNotificationDialogComponent, {
data: {
title: this.language.instant('DATASET-WIZARD.LOCKED.TITLE'),
message: this.language.instant('DATASET-WIZARD.LOCKED.MESSAGE')
}, maxWidth: '30em'
});
}
if (!lockStatus && !isNullOrUndefined(this.authService.currentAccountIsAuthenticated())) {
//TODO: lock it.
// const lockedBy: UserInfoListingModel = {
// email: this.authService.getUserProfileEmail(),
// id: this.authService.userId()?.toString(),
// name: this.authService.getPrincipalName(),
// role: 0 //TODO
// //role: this.authService.getRoles()?.at(0)
// }
// this.lock = new LockModel(data.id, lockedBy);
// this.lockService.createOrUpdate(this.lock).pipe(takeUntil(this._destroyed)).subscribe(async result => {
// this.lock.id = Guid.parse(result);
// interval(this.configurationService.lockInterval).pipe(takeUntil(this._destroyed)).subscribe(() => this.pumpLock());
// });
}
// this.loadDescriptionProfiles();
// this.registerFormListeners();
});
}
// if (this.itemId != null && this.newDmpId == null) {
// this.isNew = false;
// this.descriptionService.getSingle(this.itemId)
// .pipe(takeUntil(this._destroyed))
// .subscribe(data => {
// this.lockService.checkLockStatus(data.id).pipe(takeUntil(this._destroyed)).subscribe(lockStatus => {
// this.lockStatus = lockStatus;
// this.descriptionModel = new DescriptionEditorModel().fromModel(data);
// this.dmpSectionIndex = this.descriptionModel.dmpSectionIndex;
// this.needsUpdate();
// // this.breadCrumbs = observableOf([
// // {
// // parentComponentName: null,
// // label: this.descriptionModel.label,
// // url: '/descriptions/edit/' + this.descriptionModel.id,
// // notFoundResolver: [
// // {
// // parentComponentName: null,
// // label: this.language.instant('NAV-BAR.MY-DATASET-DESCRIPTIONS').toUpperCase(),
// // url: '/descriptions'
// // },
// // ]
// // }]);
// this.formGroup = this.descriptionModel.buildForm();
// let profiles = this.descriptionModel.dmp.profiles.filter(profile => profile.data.dmpSectionIndex.includes(this.descriptionModel.dmpSectionIndex));
// for (var profile of profiles) {
// this.availableDescriptionTemplates.push({ id: profile.descriptionTemplateId, label: profile.label, description: "" })
// }
// this.formGroupRawValue = JSON.parse(JSON.stringify(this.formGroup.getRawValue()));
// this.editMode = this.descriptionModel.status === DescriptionStatus.Draft;
// if (this.descriptionModel.status === DescriptionStatus.Finalized || lockStatus) {
// this.formGroup.disable();
// this.viewOnly = true;
// }
// if (!lockStatus && !isNullOrUndefined(this.authService.currentAccountIsAuthenticated())) {
// const lockedBy: UserInfoListingModel = {
// email: this.authService.getUserProfileEmail(),
// id: this.authService.userId()?.toString(),
// name: this.authService.getPrincipalName(),
// role: 0 //TODO
// //role: this.authService.getRoles()?.at(0)
// }
// this.lock = new LockModel(data.id, lockedBy);
// this.lockService.createOrUpdate(this.lock).pipe(takeUntil(this._destroyed)).subscribe(async result => {
// this.lock.id = Guid.parse(result);
// interval(this.configurationService.lockInterval).pipe(takeUntil(this._destroyed)).subscribe(() => this.pumpLock());
// });
// }
// // if (this.viewOnly) { this.formGroup.disable(); } // For future use, to make Description edit like DMP.
// this.loadDescriptionProfiles();
// this.registerFormListeners();
// if (lockStatus) {
// this.dialog.open(PopupNotificationDialogComponent, {
// data: {
// title: this.language.instant('DATASET-WIZARD.LOCKED.TITLE'),
// message: this.language.instant('DATASET-WIZARD.LOCKED.MESSAGE')
// }, maxWidth: '30em'
// });
// }
// if (this.finalize && !this.lockStatus && !this.viewOnly) {
// setTimeout(() => {
// this.saveFinalize();
// }, 0);
// }
// // this.availableProfiles = this.descriptionModel.dmp.profiles;
// });
// },
// error => {
// switch (error.status) {
// case 403:
// this.uiNotificationService.snackBarNotification(this.language.instant('DATASET-WIZARD.MESSAGES.DATASET-NOT-ALLOWED'), SnackBarNotificationLevel.Error);
// break;
// case 404:
// this.uiNotificationService.snackBarNotification(this.language.instant('DATASET-WIZARD.MESSAGES.DATASET-NOT-FOUND'), SnackBarNotificationLevel.Error);
// break;
// default:
// this.uiNotificationService.snackBarNotification(this.language.instant('GENERAL.ERRORS.HTTP-REQUEST-ERROR'), SnackBarNotificationLevel.Error);
// }
// this.router.navigate(['/descriptions/']);
// return observableOf(null);
// });
// } else if (this.dmpId != null) {
// this.isNew = true;
// this.dmpService.getSingle(this.dmpId).pipe(map(data => data as DmpModel))
// .pipe(takeUntil(this._destroyed))
// .subscribe(data => {
// this.descriptionModel = new DescriptionEditorModel();
// setTimeout(() => {
// this.descriptionModel.dmp = data;
// this.descriptionModel.dmpSectionIndex = this.dmpSectionIndex;
// this.formGroup = this.descriptionModel.buildForm();
// let profiles = this.descriptionModel.dmp.profiles.filter(profile => profile.data.dmpSectionIndex.includes(this.dmpSectionIndex));
// for (var profile of profiles) {
// this.availableDescriptionTemplates.push({ id: profile.descriptionTemplateId, label: profile.label, description: "" })
// }
// this.formGroupRawValue = JSON.parse(JSON.stringify(this.formGroup.getRawValue()));
// this.editMode = this.descriptionModel.status === DescriptionStatus.Draft;
// this.formGroup.get('dmp').disable();
// const dialogRef = this.dialog.open(PrefillDescriptionComponent, {
// width: '590px',
// minHeight: '200px',
// restoreFocus: false,
// data: {
// availableProfiles: this.availableDescriptionTemplates,
// descriptionFormGroup: this.formGroup
// },
// panelClass: 'custom-modalbox'
// });
// dialogRef.afterClosed().subscribe(result => {
// if (result) {
// this.descriptionModel = this.descriptionModel.fromModel(result);
// this.descriptionModel.dmp = data;
// this.descriptionModel.dmpSectionIndex = this.dmpSectionIndex;
// this.formGroup = this.descriptionModel.buildForm();
// this.formGroupRawValue = JSON.parse(JSON.stringify(this.formGroup.getRawValue()));
// this.formGroup.get('dmp').disable();
// this.loadDescriptionProfiles();
// this.registerFormListeners();
// }
// })
// this.loadDescriptionProfiles();
// this.registerFormListeners();
// // this.availableProfiles = data.profiles;
// // this.breadCrumbs = observableOf([
// // {
// // parentComponentName: null,
// // label: this.language.instant('NAV-BAR.MY-DATASET-DESCRIPTIONS'),
// // url: '/descriptions',
// // notFoundResolver: [
// // // {
// // // parentComponentName: null,
// // // label: this.descriptionModel.dmp.grant.label,
// // // url: '/grants/edit/' + this.descriptionModel.dmp.grant.id
// // // },
// // {
// // parentComponentName: null,
// // label: this.descriptionModel.dmp.label,
// // url: '/plans/edit/' + this.descriptionModel.dmp.id,
// // }]
// // }]);
// });
// });
// } else if (this.newDmpId != null) {
// this.isNew = false;
// this.isCopy = true;
// this.descriptionService.getSingle(this.itemId)
// .pipe(takeUntil(this._destroyed))
// .subscribe(data => {
// this.lockService.checkLockStatus(data.id).pipe(takeUntil(this._destroyed)).subscribe(lockStatus => {
// this.lockStatus = lockStatus;
// this.descriptionModel = new DescriptionEditorModel().fromModel(data);
// this.dmpSectionIndex = this.descriptionModel.dmpSectionIndex;
// this.descriptionModel.status = 0;
// this.formGroup = this.descriptionModel.buildForm();
// this.formGroup.get('id').setValue(null);
// this.dmpService.getSingleNoDescriptions(this.newDmpId).pipe(map(data => data as DmpModel))
// .pipe(takeUntil(this._destroyed))
// .subscribe(data => {
// setTimeout(() => {
// this.descriptionModel.dmp = data;
// this.formGroup.get('dmp').setValue(this.descriptionModel.dmp);
// this.formGroupRawValue = JSON.parse(JSON.stringify(this.formGroup.getRawValue()));
// this.loadDescriptionProfiles();
// // this.breadCrumbs = observableOf([
// // {
// // parentComponentName: null,
// // label: this.language.instant('NAV-BAR.MY-DATASET-DESCRIPTIONS'),
// // url: '/descriptions',
// // notFoundResolver: [
// // // {
// // // parentComponentName: null,
// // // label: this.descriptionModel.dmp.grant.label,
// // // url: '/grants/edit/' + this.descriptionModel.dmp.grant.id
// // // },
// // {
// // parentComponentName: null,
// // label: this.descriptionModel.dmp.label,
// // url: '/plans/edit/' + this.descriptionModel.dmp.id,
// // }
// // ]
// // }]);
// });
// });
// this.editMode = this.descriptionModel.status === DescriptionStatus.Draft;
// if (this.descriptionModel.status === DescriptionStatus.Finalized || lockStatus) {
// this.formGroup.disable();
// this.viewOnly = true;
// }
// if (!lockStatus && !isNullOrUndefined(this.authService.currentAccountIsAuthenticated())) {
// const lockedBy: UserInfoListingModel = {
// email: this.authService.getUserProfileEmail(),
// id: this.authService.userId()?.toString(),
// name: this.authService.getPrincipalName(),
// role: 0 //TODO
// //role: this.authService.getRoles()?.at(0)
// }
// this.lock = new LockModel(data.id, lockedBy);
// this.lockService.createOrUpdate(this.lock).pipe(takeUntil(this._destroyed)).subscribe(async result => {
// this.lock.id = Guid.parse(result);
// interval(this.configurationService.lockInterval).pipe(takeUntil(this._destroyed)).subscribe(() => this.pumpLock());
// });
// }
// // if (this.viewOnly) { this.formGroup.disable(); } // For future use, to make Description edit like DMP.
// this.loadDescriptionProfiles();
// // this.availableProfiles = data.dmp.profiles;
// })
// });
// } else if (this.publicId != null) { // For Finalized -> Public Descriptions
// this.isNew = false;
// this.descriptionService.getSinglePublic(this.publicId)
// .pipe(takeUntil(this._destroyed)).pipe(
// catchError((error: any) => {
// this.uiNotificationService.snackBarNotification(error.error.message, SnackBarNotificationLevel.Error);
// this.router.navigate(['/descriptions/publicEdit/' + this.publicId]);
// return observableOf(null);
// }))
// .subscribe(data => {
// if (data) {
// this.descriptionModel = new DescriptionEditorModel().fromModel(data);
// this.formGroup = this.descriptionModel.buildForm();
// this.formGroupRawValue = JSON.parse(JSON.stringify(this.formGroup.getRawValue()));
// this.formGroup.disable();
// this.viewOnly = true;
// this.editMode = this.descriptionModel.status === DescriptionStatus.Draft;
// this.formGroup.get('dmp').setValue(this.descriptionModel.dmp);
// const breadcrumbs = [];
// breadcrumbs.push({
// parentComponentName: null,
// label: this.language.instant('NAV-BAR.PUBLIC DATASETS'),
// url: '/explore-descriptions'
// });
// breadcrumbs.push({
// parentComponentName: null,
// label: this.descriptionModel.label,
// url: '/descriptions/publicEdit/' + this.descriptionModel.id
// });
// // this.breadCrumbs = observableOf(breadcrumbs);
// }
// });
// this.publicMode = true;
// } else if (this.profileUpdateId != null) {
// this.descriptionService.updateDescriptionProfile(this.profileUpdateId)
// .pipe(takeUntil(this._destroyed))
// .subscribe(data => {
// this.descriptionModel = new DescriptionEditorModel().fromModel(data);
// this.formGroupRawValue = JSON.parse(JSON.stringify(this.formGroup.getRawValue()));
// this.needsUpdate();
// // this.breadCrumbs = observableOf([
// // {
// // parentComponentName: null,
// // label: this.language.instant('NAV-BAR.MY-DATASET-DESCRIPTIONS'),
// // url: '/descriptions',
// // notFoundResolver: [
// // // {
// // // parentComponentName: null,
// // // label: this.descriptionModel.dmp.grant.label,
// // // url: '/grants/edit/' + this.descriptionModel.dmp.grant.id
// // // },
// // {
// // parentComponentName: null,
// // label: this.descriptionModel.dmp.label,
// // url: '/plans/edit/' + this.descriptionModel.dmp.id,
// // },
// // ]
// // }]);
// this.formGroup = this.descriptionModel.buildForm();
// this.editMode = this.descriptionModel.status === DescriptionStatus.Draft;
// if (this.descriptionModel.status === DescriptionStatus.Finalized) {
// this.formGroup.disable();
// this.viewOnly = true;
// }
// // if (this.viewOnly) { this.formGroup.disable(); } // For future use, to make Description edit like DMP.
// this.loadDescriptionProfiles();
// });
// } else {
// this.descriptionModel = new DescriptionEditorModel();
// this.formGroup = this.descriptionModel.buildForm();
// this.formGroupRawValue = JSON.parse(JSON.stringify(this.formGroup.getRawValue()));
// this.editMode = this.descriptionModel.status === DescriptionStatus.Draft;
// if (this.descriptionModel.status === DescriptionStatus.Finalized) {
// this.formGroup.disable();
// this.viewOnly = true;
// }
// //if (this.viewOnly) { this.formGroup.disable(); } // For future use, to make Description edit like DMP.
// this.registerFormListeners();
// this.dmpValueChanged(null);
// // this.breadCrumbs = observableOf([
// // {
// // parentComponentName: null,
// // label: this.language.instant('DATASET-LISTING.ACTIONS.CREATE-NEW').toUpperCase(),
// // url: '/descriptions/new/'
// // }]);
// }
});
}
getItem(itemId: Guid, successFunction: (item: Description) => void) {
@ -148,6 +552,7 @@ export class DescriptionEditorComponent extends BaseEditor<DescriptionEditorMode
prepareForm(data: Description) {
try {
this.editorModel = data ? new DescriptionEditorModel().fromModel(data) : new DescriptionEditorModel();
this.item = data;
this.isDeleted = data ? data.isActive === IsActive.Inactive : false;
this.buildForm();
} catch (error) {
@ -158,6 +563,8 @@ export class DescriptionEditorComponent extends BaseEditor<DescriptionEditorMode
buildForm() {
this.formGroup = this.editorModel.buildForm(null, this.isDeleted || !this.authService.hasPermission(AppPermission.EditDescription));
this.visibilityRulesService.buildVisibilityRules(this.visibilityRulesService.getVisibilityRulesFromDescriptionTempalte(this.item.descriptionTemplate), this.formGroup);
// this.selectedSystemFields = this.selectedSystemFieldDisabled();
this.descriptionEditorService.setValidationErrorModel(this.editorModel.validationErrorModel);
if (this.editorModel.status == DescriptionStatus.Finalized) {
@ -231,6 +638,148 @@ export class DescriptionEditorComponent extends BaseEditor<DescriptionEditorMode
this.formService.validateAllFormFields(this.formGroup);
}
//
//
// Misc
//
//
isDirty() {
return this.formGroup.dirty; //TODO: check if needed //&& this.hasChanges; // do we need this.formGroup.dirty
}
hasReversableStatus(): boolean {
if (this.item?.dmp) {
return (this.item.dmp.status == DmpStatus.Draft && this.formGroup.get('status').value == DescriptionStatus.Finalized);
} else {
return false;
}
}
descriptionInfoValid(): boolean {
return this.formGroup.get('label') && this.formGroup.get('label').valid && this.formGroup.get('descriptionTemplateId') && this.formGroup.get('descriptionTemplateId').valid;
}
//
//
// Table of Contents
//
//
public changeStep(selected: ToCEntry = null, execute: boolean = true) {
if (execute) {
if (selected) {
let fieldSet = this.getFirstFieldSet(selected);
let index = this.visibleFieldSets.findIndex(entry => entry.id === fieldSet.id);
this.step = index + (selected.type === ToCEntryType.FieldSet ? 1 : 0.5);
} else {
this.step = 0;
this.resetScroll();
}
}
}
getFirstFieldSet(entry: ToCEntry): ToCEntry {
if (entry.type === ToCEntryType.FieldSet && !this.table0fContents.internalTable.hiddenEntries.find(hiddenEntry => hiddenEntry === entry.id.toString())) {
return entry;
} else {
let subEntries = entry.subEntries.filter(subEntry => !this.table0fContents.internalTable.hiddenEntries.find(hiddenEntry => hiddenEntry === subEntry.id.toString()));
if (subEntries.length > 0) {
return this.getFirstFieldSet(subEntries[0]);
} else {
return null;
}
}
}
private resetScroll() {
document.getElementById('description-editor-form').scrollTop = 0;
}
get visibleFieldSets(): ToCEntry[] {
let fieldSets = [];
let arrays = this.table0fContents ? this.table0fContents.tocentries.
filter(entry => !this.table0fContents.internalTable.hiddenEntries.find(hiddenEntry => hiddenEntry === entry.id.toString())).map(entry => {
return this.getEntryVisibleFieldSets(entry);
})
: [];
arrays.forEach(array => {
fieldSets = fieldSets.concat(array);
});
return fieldSets;
}
getEntryVisibleFieldSets(entry: ToCEntry): ToCEntry[] {
let fieldSets = [];
if (entry.type === ToCEntryType.FieldSet && !this.table0fContents.internalTable.hiddenEntries.find(hiddenEntry => hiddenEntry === entry.id.toString())) {
fieldSets.push(entry);
} else if (entry.type !== ToCEntryType.FieldSet) {
entry.subEntries.forEach(subEntry => {
fieldSets = fieldSets.concat(this.getEntryVisibleFieldSets(subEntry));
});
}
return fieldSets;
}
// registerFormListeners() {
// // const dmpSubscription =
// this.formGroup.get('dmp').valueChanges
// .pipe(takeUntil(this._destroyed))
// .subscribe(x => {
// this.dmpValueChanged(x);
// });
// // const profileSubscription =
// this.formGroup.get('profile').valueChanges
// .pipe(takeUntil(this._destroyed))
// .subscribe(x => {
// if (x) {
// this.showtocentriesErrors = false;
// this.descriptionProfileValueChanged(x.id);
// this.formChanged();
// }
// });
// // const labelSubscription =
// this.formGroup.get('label').valueChanges
// .pipe(takeUntil(this._destroyed))
// .subscribe(x => {
// this.formChanged();
// });
// // const descriptionSubscription =
// this.formGroup.get('description').valueChanges
// .pipe(takeUntil(this._destroyed))
// .subscribe(x => {
// this.formChanged();
// });
// // const uriSubscription =
// this.formGroup.get('uri').valueChanges
// .pipe(takeUntil(this._destroyed))
// .subscribe(x => {
// this.formChanged();
// });
// // const tagsSubscription =
// this.formGroup.get('tags').valueChanges
// .pipe(takeUntil(this._destroyed))
// .subscribe(x => {
// this.formChanged();
// });
// if (this.formGroup.get('descriptionProfileDefinition')) {
// // const descriptionProfileDefinitionSubscription =
// this.formGroup.get('descriptionProfileDefinition').valueChanges
// .pipe(takeUntil(this._destroyed))
// .subscribe(x => {
// this.formChanged();
// });
// // this._listenersSubscription.add(descriptionProfileDefinitionSubscription);
// }
// // this._listenersSubscription.add(dmpSubscription);
// // this._listenersSubscription.add(profileSubscription);
// // this._listenersSubscription.add(labelSubscription);
// // this._listenersSubscription.add(descriptionSubscription);
// // this._listenersSubscription.add(uriSubscription);
// // this._listenersSubscription.add(tagsSubscription);
// }
// //
// //
// // Sections
@ -1167,13 +1716,7 @@ export class DescriptionEditorComponent extends BaseEditor<DescriptionEditorMode
// }
// hasReversableStatus(): boolean {
// if (this.formGroup.get('dmp').value) {
// return (this.formGroup.get('dmp').value.status == DmpStatus.Draft && this.formGroup.get('status').value == DescriptionStatus.Finalized);
// } else {
// return false;
// }
// }
// hasNotReversableStatus(): boolean {
// if (this.formGroup.get('dmp').value && !this.publicMode) {
@ -1664,9 +2207,7 @@ export class DescriptionEditorComponent extends BaseEditor<DescriptionEditorMode
// this.router.navigate(['/plans', 'edit', id]);
// }
// descriptionInfoValid(): boolean {
// return this.formGroup.get('label') && this.formGroup.get('label').valid && this.formGroup.get('profile') && this.formGroup.get('profile').valid;
// }
// getLinks(currentLinks: Link[]) {
// this.links = currentLinks;

View File

@ -106,17 +106,27 @@ export class DescriptionPropertyDefinitionEditorModel implements PropertyDefinit
});
}
return this.formBuilder.group({
fields: this.formBuilder.array(
(this.fields ?? []).map(
(item, index) => new DescriptionFieldEditorModel(
this.validationErrorModel
).fromModel(item).buildForm({
rootPath: `${rootPath}fields[${index}].`
}), context.getValidation('fields')
)
),
});
const formGroup = this.formBuilder.group({});
(this.fields ?? []).map(
(item, index) => formGroup.addControl(item.key, new DescriptionFieldEditorModel(
this.validationErrorModel
).fromModel(item).buildForm({
rootPath: `${rootPath}fields[${index}].`
})), context.getValidation('fields')
)
return formGroup;
// return this.formBuilder.group({
// fields: this.formBuilder.array(
// (this.fields ?? []).map(
// (item, index) => new DescriptionFieldEditorModel(
// this.validationErrorModel
// ).fromModel(item).buildForm({
// rootPath: `${rootPath}fields[${index}].`
// }), context.getValidation('fields')
// )
// )
// });
}
static createValidationContext(params: {

View File

@ -0,0 +1,37 @@
import { NgModule } from '@angular/core';
import { FormattingModule } from '@app/core/formatting.module';
import { UrlListingModule } from '@app/library/url-listing/url-listing.module';
import { CommonFormsModule } from '@common/forms/common-forms.module';
import { ConfirmationDialogModule } from '@common/modules/confirmation-dialog/confirmation-dialog.module';
import { CommonUiModule } from '@common/ui/common-ui.module';
import { DescriptionBaseFieldsEditorComponent } from './description-base-fields-editor/description-base-fields-editor.component';
import { DescriptionEditorComponent } from './description-editor.component';
import { DescriptionEditorRoutingModule } from './description-editor.routing';
import { DescriptionFormModule } from './description-form/description-form.module';
import { VisibilityRulesService } from './description-form/visibility-rules/visibility-rules.service';
import { DescriptionFormProgressIndicationModule } from './form-progress-indication/form-progress-indication.module';
import { TableOfContentsModule } from './table-of-contents/table-of-contents.module';
@NgModule({
imports: [
CommonUiModule,
CommonFormsModule,
UrlListingModule,
FormattingModule,
ConfirmationDialogModule,
TableOfContentsModule,
DescriptionFormProgressIndicationModule,
DescriptionFormModule,
DescriptionEditorRoutingModule
],
declarations: [
DescriptionEditorComponent,
DescriptionBaseFieldsEditorComponent
],
exports: [
],
providers: [
VisibilityRulesService
]
})
export class DescriptionEditorModule { }

View File

@ -0,0 +1,112 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { DescriptionTemplate, DescriptionTemplateBaseFieldData, DescriptionTemplateDefinition, DescriptionTemplateField, DescriptionTemplateFieldSet, DescriptionTemplatePage, DescriptionTemplateRule, DescriptionTemplateSection } from '@app/core/model/description-template/description-template';
import { Description, DescriptionField, DescriptionReference, DescriptionTag, PropertyDefinition } from '@app/core/model/description/description';
import { Dmp, DmpDescriptionTemplate } from '@app/core/model/dmp/dmp';
import { Reference } from '@app/core/model/reference/reference';
import { Tag } from '@app/core/model/tag/tag';
import { DescriptionService } from '@app/core/services/description/description.service';
import { BreadcrumbService } from '@app/ui/misc/breadcrumb/breadcrumb.service';
import { BaseEditorResolver } from '@common/base/base-editor.resolver';
import { Guid } from '@common/types/guid';
import { takeUntil, tap } from 'rxjs/operators';
import { nameof } from 'ts-simple-nameof';
@Injectable()
export class DescriptionEditorResolver extends BaseEditorResolver {
constructor(private descriptionService: DescriptionService, private breadcrumbService: BreadcrumbService) {
super();
}
public static lookupFields(): string[] {
return [
...BaseEditorResolver.lookupFields(),
nameof<Description>(x => x.id),
nameof<Description>(x => x.label),
nameof<Description>(x => x.status),
nameof<Description>(x => x.description),
nameof<Description>(x => x.status),
[nameof<Description>(x => x.dmp), nameof<Dmp>(x => x.id)].join('.'),
[nameof<Description>(x => x.dmp), nameof<Dmp>(x => x.label)].join('.'),
[nameof<Description>(x => x.dmpDescriptionTemplate), nameof<DmpDescriptionTemplate>(x => x.id)].join('.'),
[nameof<Description>(x => x.descriptionTemplate), nameof<DescriptionTemplate>(x => x.id)].join('.'),
[nameof<Description>(x => x.descriptionTemplate), nameof<DescriptionTemplate>(x => x.definition), nameof<DescriptionTemplateDefinition>(x => x.pages), nameof<DescriptionTemplatePage>(x => x.id)].join('.'),
[nameof<Description>(x => x.descriptionTemplate), nameof<DescriptionTemplate>(x => x.definition), nameof<DescriptionTemplateDefinition>(x => x.pages), nameof<DescriptionTemplatePage>(x => x.ordinal)].join('.'),
[nameof<Description>(x => x.descriptionTemplate), nameof<DescriptionTemplate>(x => x.definition), nameof<DescriptionTemplateDefinition>(x => x.pages), nameof<DescriptionTemplatePage>(x => x.title)].join('.'),
[nameof<Description>(x => x.descriptionTemplate), nameof<DescriptionTemplate>(x => x.definition), nameof<DescriptionTemplateDefinition>(x => x.sections), nameof<DescriptionTemplateSection>(x => x.id)].join('.'),
[nameof<Description>(x => x.descriptionTemplate), nameof<DescriptionTemplate>(x => x.definition), nameof<DescriptionTemplateDefinition>(x => x.sections), nameof<DescriptionTemplateSection>(x => x.ordinal)].join('.'),
// [nameof<DescriptionTemplate>(x => x.definition), nameof<DescriptionTemplateDefinition>(x => x.sections), nameof<DescriptionTemplateSection>(x => x.defaultVisibility)].join('.'),
// [nameof<DescriptionTemplate>(x => x.definition), nameof<DescriptionTemplateDefinition>(x => x.sections), nameof<DescriptionTemplateSection>(x => x.multiplicity)].join('.'),
// [nameof<DescriptionTemplate>(x => x.definition), nameof<DescriptionTemplateDefinition>(x => x.sections), nameof<DescriptionTemplateSection>(x => x.numbering)].join('.'),
[nameof<Description>(x => x.descriptionTemplate), nameof<DescriptionTemplate>(x => x.definition), nameof<DescriptionTemplateDefinition>(x => x.sections), nameof<DescriptionTemplateSection>(x => x.page)].join('.'),
[nameof<Description>(x => x.descriptionTemplate), nameof<DescriptionTemplate>(x => x.definition), nameof<DescriptionTemplateDefinition>(x => x.sections), nameof<DescriptionTemplateSection>(x => x.title)].join('.'),
[nameof<Description>(x => x.descriptionTemplate), nameof<DescriptionTemplate>(x => x.definition), nameof<DescriptionTemplateDefinition>(x => x.sections), nameof<DescriptionTemplateSection>(x => x.description)].join('.'),
[nameof<Description>(x => x.descriptionTemplate), nameof<DescriptionTemplate>(x => x.definition), nameof<DescriptionTemplateDefinition>(x => x.sections), nameof<DescriptionTemplateSection>(x => x.extendedDescription)].join('.'),
// [nameof<DescriptionTemplate>(x => x.definition), nameof<DescriptionTemplateDefinition>(x => x.sections), nameof<DescriptionTemplateSection>(x => x.ordinal)].join('.'), // TODO: need to sort based on that
// [nameof<DescriptionTemplate>(x => x.definition), nameof<DescriptionTemplateDefinition>(x => x.sections), nameof<DescriptionTemplateSection>(x => x.sections)].join('.'), // TODO: it is recursive here
[nameof<Description>(x => x.descriptionTemplate), nameof<DescriptionTemplate>(x => x.definition), nameof<DescriptionTemplateDefinition>(x => x.sections), nameof<DescriptionTemplateSection>(x => x.fieldSets), nameof<DescriptionTemplateFieldSet>(x => x.id)].join('.'),
[nameof<Description>(x => x.descriptionTemplate), nameof<DescriptionTemplate>(x => x.definition), nameof<DescriptionTemplateDefinition>(x => x.sections), nameof<DescriptionTemplateSection>(x => x.fieldSets), nameof<DescriptionTemplateFieldSet>(x => x.ordinal)].join('.'),
// [nameof<DescriptionTemplate>(x => x.definition), nameof<DescriptionTemplateDefinition>(x => x.sections), nameof<DescriptionTemplateSection>(x => x.fieldSets), nameof<DescriptionTemplateFieldSet>(x => x.numbering)].join('.'),
[nameof<Description>(x => x.descriptionTemplate), nameof<DescriptionTemplate>(x => x.definition), nameof<DescriptionTemplateDefinition>(x => x.sections), nameof<DescriptionTemplateSection>(x => x.fieldSets), nameof<DescriptionTemplateFieldSet>(x => x.title)].join('.'),
[nameof<Description>(x => x.descriptionTemplate), nameof<DescriptionTemplate>(x => x.definition), nameof<DescriptionTemplateDefinition>(x => x.sections), nameof<DescriptionTemplateSection>(x => x.fieldSets), nameof<DescriptionTemplateFieldSet>(x => x.description)].join('.'),
[nameof<Description>(x => x.descriptionTemplate), nameof<DescriptionTemplate>(x => x.definition), nameof<DescriptionTemplateDefinition>(x => x.sections), nameof<DescriptionTemplateSection>(x => x.fieldSets), nameof<DescriptionTemplateFieldSet>(x => x.extendedDescription)].join('.'),
[nameof<Description>(x => x.descriptionTemplate), nameof<DescriptionTemplate>(x => x.definition), nameof<DescriptionTemplateDefinition>(x => x.sections), nameof<DescriptionTemplateSection>(x => x.fieldSets), nameof<DescriptionTemplateFieldSet>(x => x.additionalInformation)].join('.'),
[nameof<Description>(x => x.descriptionTemplate), nameof<DescriptionTemplate>(x => x.definition), nameof<DescriptionTemplateDefinition>(x => x.sections), nameof<DescriptionTemplateSection>(x => x.fieldSets), nameof<DescriptionTemplateFieldSet>(x => x.hasCommentField)].join('.'),
// [nameof<DescriptionTemplate>(x => x.definition), nameof<DescriptionTemplateDefinition>(x => x.sections), nameof<DescriptionTemplateSection>(x => x.fieldSets), nameof<DescriptionTemplateFieldSet>(x => x.multiplicity), nameof<DescriptionTemplateMultiplicity>(x => x.min)].join('.'),
// [nameof<DescriptionTemplate>(x => x.definition), nameof<DescriptionTemplateDefinition>(x => x.sections), nameof<DescriptionTemplateSection>(x => x.fieldSets), nameof<DescriptionTemplateFieldSet>(x => x.multiplicity), nameof<DescriptionTemplateMultiplicity>(x => x.max)].join('.'),
// [nameof<DescriptionTemplate>(x => x.definition), nameof<DescriptionTemplateDefinition>(x => x.sections), nameof<DescriptionTemplateSection>(x => x.fieldSets), nameof<DescriptionTemplateFieldSet>(x => x.multiplicity), nameof<DescriptionTemplateMultiplicity>(x => x.placeholder)].join('.'),
// [nameof<DescriptionTemplate>(x => x.definition), nameof<DescriptionTemplateDefinition>(x => x.sections), nameof<DescriptionTemplateSection>(x => x.fieldSets), nameof<DescriptionTemplateFieldSet>(x => x.multiplicity), nameof<DescriptionTemplateMultiplicity>(x => x.tableView)].join('.'),
[nameof<Description>(x => x.descriptionTemplate), nameof<DescriptionTemplate>(x => x.definition), nameof<DescriptionTemplateDefinition>(x => x.sections), nameof<DescriptionTemplateSection>(x => x.fieldSets), nameof<DescriptionTemplateFieldSet>(x => x.fields), nameof<DescriptionTemplateField>(x => x.id)].join('.'),
// [nameof<DescriptionTemplate>(x => x.definition), nameof<DescriptionTemplateDefinition>(x => x.sections), nameof<DescriptionTemplateSection>(x => x.fieldSets), nameof<DescriptionTemplateFieldSet>(x => x.fields), nameof<DescriptionTemplateField>(x => x.ordinal)].join('.'),
// [nameof<DescriptionTemplate>(x => x.definition), nameof<DescriptionTemplateDefinition>(x => x.sections), nameof<DescriptionTemplateSection>(x => x.fieldSets), nameof<DescriptionTemplateFieldSet>(x => x.fields), nameof<DescriptionTemplateField>(x => x.numbering)].join('.'),
// [nameof<DescriptionTemplate>(x => x.definition), nameof<DescriptionTemplateDefinition>(x => x.sections), nameof<DescriptionTemplateSection>(x => x.fieldSets), nameof<DescriptionTemplateFieldSet>(x => x.fields), nameof<DescriptionTemplateField>(x => x.schematics)].join('.'),
// [nameof<DescriptionTemplate>(x => x.definition), nameof<DescriptionTemplateDefinition>(x => x.sections), nameof<DescriptionTemplateSection>(x => x.fieldSets), nameof<DescriptionTemplateFieldSet>(x => x.fields), nameof<DescriptionTemplateField>(x => x.defaultValue)].join('.'),
[nameof<Description>(x => x.descriptionTemplate), nameof<DescriptionTemplate>(x => x.definition), nameof<DescriptionTemplateDefinition>(x => x.sections), nameof<DescriptionTemplateSection>(x => x.fieldSets), nameof<DescriptionTemplateFieldSet>(x => x.fields), nameof<DescriptionTemplateField>(x => x.defaultValue)].join('.'),
// [nameof<DescriptionTemplate>(x => x.definition), nameof<DescriptionTemplateDefinition>(x => x.sections), nameof<DescriptionTemplateSection>(x => x.fieldSets), nameof<DescriptionTemplateFieldSet>(x => x.fields), nameof<DescriptionTemplateField>(x => x.fieldType)].join('.'),
// [nameof<DescriptionTemplate>(x => x.definition), nameof<DescriptionTemplateDefinition>(x => x.sections), nameof<DescriptionTemplateSection>(x => x.fieldSets), nameof<DescriptionTemplateFieldSet>(x => x.fields), nameof<DescriptionTemplateField>(x => x.includeInExport)].join('.'),
[nameof<Description>(x => x.descriptionTemplate), nameof<DescriptionTemplate>(x => x.definition), nameof<DescriptionTemplateDefinition>(x => x.sections), nameof<DescriptionTemplateSection>(x => x.fieldSets), nameof<DescriptionTemplateFieldSet>(x => x.fields), nameof<DescriptionTemplateField>(x => x.validations)].join('.'),
[nameof<Description>(x => x.descriptionTemplate), nameof<DescriptionTemplate>(x => x.definition), nameof<DescriptionTemplateDefinition>(x => x.sections), nameof<DescriptionTemplateSection>(x => x.fieldSets), nameof<DescriptionTemplateFieldSet>(x => x.fields), nameof<DescriptionTemplateField>(x => x.visibilityRules), nameof<DescriptionTemplateRule>(x => x.target)].join('.'),
[nameof<Description>(x => x.descriptionTemplate), nameof<DescriptionTemplate>(x => x.definition), nameof<DescriptionTemplateDefinition>(x => x.sections), nameof<DescriptionTemplateSection>(x => x.fieldSets), nameof<DescriptionTemplateFieldSet>(x => x.fields), nameof<DescriptionTemplateField>(x => x.visibilityRules), nameof<DescriptionTemplateRule>(x => x.value)].join('.'),
[nameof<Description>(x => x.descriptionTemplate), nameof<DescriptionTemplate>(x => x.definition), nameof<DescriptionTemplateDefinition>(x => x.sections), nameof<DescriptionTemplateSection>(x => x.fieldSets), nameof<DescriptionTemplateFieldSet>(x => x.fields), nameof<DescriptionTemplateField>(x => x.data), nameof<DescriptionTemplateBaseFieldData>(x => x.label)].join('.'),
[nameof<Description>(x => x.descriptionTemplate), nameof<DescriptionTemplate>(x => x.definition), nameof<DescriptionTemplateDefinition>(x => x.sections), nameof<DescriptionTemplateSection>(x => x.fieldSets), nameof<DescriptionTemplateFieldSet>(x => x.fields), nameof<DescriptionTemplateField>(x => x.data), nameof<DescriptionTemplateBaseFieldData>(x => x.fieldType)].join('.'),
[nameof<Description>(x => x.properties), nameof<PropertyDefinition>(x => x.fields), nameof<DescriptionField>(x => x.key)].join('.'),
[nameof<Description>(x => x.properties), nameof<PropertyDefinition>(x => x.fields), nameof<DescriptionField>(x => x.value)].join('.'),
[nameof<Description>(x => x.descriptionTags), nameof<DescriptionTag>(x => x.id),].join('.'),
[nameof<Description>(x => x.descriptionTags), nameof<DescriptionTag>(x => x.tag), nameof<Tag>(x => x.label)].join('.'),
[nameof<Description>(x => x.descriptionReferences), nameof<DescriptionReference>(x => x.reference), nameof<Reference>(x => x.id)].join('.'),
[nameof<Description>(x => x.descriptionReferences), nameof<DescriptionReference>(x => x.reference), nameof<Reference>(x => x.label)].join('.'),
[nameof<Description>(x => x.descriptionReferences), nameof<DescriptionReference>(x => x.reference), nameof<Reference>(x => x.type)].join('.'),
[nameof<Description>(x => x.descriptionReferences), nameof<DescriptionReference>(x => x.reference), nameof<Reference>(x => x.reference)].join('.'),
nameof<Description>(x => x.createdAt),
nameof<Description>(x => x.hash),
nameof<Description>(x => x.isActive)
]
}
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
const fields = [
...DescriptionEditorResolver.lookupFields()
];
const id = route.paramMap.get('id');
// const cloneid = route.paramMap.get('cloneid');
if (id != null) {
return this.descriptionService.getSingle(Guid.parse(id), fields).pipe(tap(x => this.breadcrumbService.addIdResolvedValue(x.id?.toString(), x.label)), takeUntil(this._destroyed));
}
//TODO: check this
// else if (cloneid != null) {
// return this.descriptionService.clone(Guid.parse(cloneid), fields).pipe(tap(x => this.breadcrumbService.addIdResolvedValue(x.id?.toString(), x.label)), takeUntil(this._destroyed));
// }
}
}

View File

@ -0,0 +1,101 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
// import { DescriptionWizardComponent } from './description-wizard/description-wizard.component';
import { AppPermission } from '@app/core/common/enum/permission.enum';
import { PendingChangesGuard } from '@common/forms/pending-form-changes/pending-form-changes-guard.service';
// import { DescriptionOverviewComponent } from './overview/description-overview.component';
import { BreadcrumbService } from '@app/ui/misc/breadcrumb/breadcrumb.service';
import { DescriptionEditorComponent } from './description-editor.component';
import { DescriptionEditorResolver } from './description-editor.resolver';
import { AuthGuard } from '@app/core/auth-guard.service';
const routes: Routes = [
{
path: ':id',
canActivate: [AuthGuard],
component: DescriptionEditorComponent,
canDeactivate: [PendingChangesGuard],
resolve: {
'entity': DescriptionEditorResolver
},
data: {
...BreadcrumbService.generateRouteDataConfiguration({
title: 'BREADCRUMBS.EDIT-DESCRIPTION'
}),
authContext: {
permissions: [AppPermission.EditDescription]
}
}
},
// {
// path: 'new/:dmpId/:dmpSectionIndex',
// component: DescriptionWizardComponent,
// canActivate: [AuthGuard],
// data: {
// breadcrumb: true,
// title: 'GENERAL.TITLES.DATASET-NEW'
// },
// canDeactivate:[CanDeactivateGuard]
// },
// {
// path: 'edit/:id/finalize',
// component: DescriptionWizardComponent,
// canActivate: [AuthGuard],
// data: {
// breadcrumb: true,
// public: false,
// title: 'GENERAL.TITLES.DATASET-EDIT',
// finalize: true
// },
// canDeactivate:[CanDeactivateGuard]
// },
// {
// path: 'publicEdit/:publicId',
// component: DescriptionWizardComponent,
// //canActivate: [AuthGuard],
// data: {
// public: true,
// title: 'GENERAL.TITLES.DATASET-PUBLIC-EDIT'
// },
// canDeactivate:[CanDeactivateGuard]
// },
// {
// path: 'new',
// component: DescriptionWizardComponent,
// canActivate: [AuthGuard],
// data: {
// breadcrumb: true,
// title: 'GENERAL.TITLES.DATASET-NEW'
// },
// canDeactivate:[CanDeactivateGuard]
// },
// {
// path: 'copy/:id',
// component: DescriptionWizardComponent,
// canActivate: [AuthGuard],
// data: {
// breadcrumb: true,
// title: 'GENERAL.TITLES.DATASET-COPY'
// },
// canDeactivate:[CanDeactivateGuard]
// },
// {
// path: 'profileupdate/:updateId',
// component: DescriptionWizardComponent,
// canActivate: [AuthGuard],
// data: {
// breadcrumb: true,
// title: 'GENERAL.TITLES.DATASET-UPDATE'
// },
// canDeactivate:[CanDeactivateGuard]
// },
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
providers: [DescriptionEditorResolver]
})
export class DescriptionEditorRoutingModule { }

View File

@ -0,0 +1,15 @@
<div class="form">
<div class="row d-flex flex-row">
<div class="col-auto ml-auto close-btn" (click)="close()">
<mat-icon>close</mat-icon>
</div>
</div>
<div *ngIf="data.formGroup" mat-dialog-content class="row">
<app-form-composite-field class="align-self-center col" [form]="data.formGroup" [datasetProfileId]="data.datasetProfileId" [altVisibilityRulesService]="visibilityRulesService"
[isChild]="false" [showDelete]="false" [showTitle]="false" [placeholderTitle]="true"></app-form-composite-field>
</div>
<div mat-dialog-actions class="row">
<div class="ml-auto col-auto"><button mat-raised-button type="button" mat-dialog-close (click)="cancel()">{{'DATASET-EDITOR.ACTIONS.CANCEL' | translate}}</button></div>
<div class="col-auto"><button mat-raised-button color="primary" type="button" [disabled]="!data.formGroup.valid" (click)="save()">{{'DATASET-EDITOR.ACTIONS.SAVE' | translate}}</button></div>
</div>
</div>

View File

@ -0,0 +1,31 @@
import {Component, Inject} from "@angular/core";
import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
import { VisibilityRulesService } from "../../visibility-rules/visibility-rules.service";
@Component({
selector: 'app-form-composite-field-dialog',
templateUrl: 'form-composite-field-dialog.component.html'
})
export class FormCompositeFieldDialogComponent {
public visibilityRulesService: VisibilityRulesService;
constructor(
private dialogRef: MatDialogRef<FormCompositeFieldDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: any
) {
this.visibilityRulesService = data.visibilityRulesService;
}
cancel() {
this.dialogRef.close();
}
save() {
this.dialogRef.close(this.data.formGroup);
}
public close() {
this.dialogRef.close(false);
}
}

View File

@ -0,0 +1,25 @@
<div class="col-12">
<div class="row">
<h5 *ngIf="fieldSet.title && !isChild" class="col-auto compositeField toc-compositeField-header" [id]="fieldSet.id">
<!-- TODO: We need the numbering here. -->
<!-- {{tocentry? tocentry.numbering : form.get('numbering').value}} -->
{{fieldSet.title}}
</h5>
<mat-icon class="col-auto info-icon" *ngIf="fieldSet.additionalInformation && !isChild" matTooltip="{{fieldSet.additionalInformation}}">info</mat-icon>
</div>
</div>
<h6 *ngIf="fieldSet.description && !isChild" class="col-12" [innerHTML]="fieldSet.description"></h6>
<div *ngIf="fieldSet.extendedDescription && !isChild" class="col-12 mt-3 mb-3">
<div *ngIf="!showExtendedDescription" (click)="showExtendedDescription = !showExtendedDescription">
<span class="more d-flex justify-content-center">{{'DATASET-EDITOR.QUESTION.EXTENDED-DESCRIPTION.VIEW-MORE' | translate}}</span>
</div>
<div *ngIf="showExtendedDescription">
<h6 [innerHTML]="fieldSet.extendedDescription"></h6>
<span class="more d-flex justify-content-center" (click)="showExtendedDescription = !showExtendedDescription">
{{'DATASET-EDITOR.QUESTION.EXTENDED-DESCRIPTION.VIEW-LESS' | translate}}
</span>
</div>
</div>

View File

@ -0,0 +1,31 @@
.compositeField {
// font-weight: bold;
// color: #3a3737;
// max-width: 100%;
// padding-top: 1em;
text-align: left;
font-weight: 700;
font-size: 18px;
letter-spacing: 0px;
color: #212121;
opacity: 0.81;
margin-top: 1.625rem;
margin-bottom: 0.625rem;
}
.info-icon{
margin-top: 1.625rem;
}
h6 {
text-transform: none;
font-weight: 400;
}
.more {
text-decoration: underline;
color: var(--secondary-color);
cursor: pointer;
font-size: 1rem;
font-weight: 400;
}

View File

@ -0,0 +1,21 @@
import { Component, Input, OnInit } from '@angular/core';
import { DescriptionTemplateFieldSet } from '@app/core/model/description-template/description-template';
@Component({
selector: 'app-form-composite-title',
templateUrl: './form-composite-title.component.html',
styleUrls: ['./form-composite-title.component.scss']
})
export class DescriptionFormCompositeTitleComponent implements OnInit {
@Input() fieldSet: DescriptionTemplateFieldSet;
@Input() isChild: Boolean = false;
public showExtendedDescription: boolean = false;
constructor() { }
ngOnInit() {
}
}

View File

@ -0,0 +1,54 @@
<div *ngIf="fieldSet && isVisibleByVisibilityService && !tableRow" class="dynamic-form-composite-field row">
<div *ngIf="fieldSet.fields.length === 1 && this.visibilityRulesService.isVisibleMap[fieldSet.fields.id] ?? true" class="col-12">
<div class="row">
<div *ngIf="showTitle" class="col-12">
<app-form-composite-title class="row" [fieldSet]="fieldSet" [isChild]="isChild"></app-form-composite-title>
</div>
<div class="col-12">
<div class="row">
<app-form-field class="align-self-center col" [propertiesFormGroup]="propertiesFormGroup" [field]="fieldSet.fields[0]" [fieldSet]="fieldSet" [visibilityRulesService]="visibilityRulesService" [datasetProfileId]="datasetProfileId" [isChild]="isChild"></app-form-field>
<div *ngIf="showDelete" class="col-auto align-self-center">
<button mat-icon-button type="button" class="deleteBtn" (click)="deleteCompositeField();" [disabled]="propertiesFormGroup.get(fieldSet.fields[0].id).disabled">
<mat-icon>delete</mat-icon>
</button>
</div>
</div>
</div>
</div>
</div>
<div *ngIf="fieldSet.fields.length > 1" class="col-12">
<div class="row">
<div *ngIf="showTitle" class="col-12">
<app-form-composite-title class="row" [fieldSet]="fieldSet" [isChild]="isChild"></app-form-composite-title>
</div>
<div class="col align-self-center">
<div *ngFor="let field of fieldSet.fields; let i = index;" class="col-12 compositeField">
<ng-container *ngIf="this.visibilityRulesService.isVisibleMap[field.id] ?? true">
<div class="row">
<h5 *ngIf="placeholderTitle" class="col-auto font-weight-bold">{{field.label}}</h5>
</div>
<app-form-field class="col-12 compositeField" [propertiesFormGroup]="propertiesFormGroup" [field]="field" [fieldSet]="fieldSet" [visibilityRulesService]="visibilityRulesService" [datasetProfileId]="datasetProfileId" [isChild]="true"></app-form-field>
</ng-container>
</div>
</div>
<div *ngIf="showDelete" class="col-auto align-self-center">
<button mat-icon-button type="button" class="deleteBtn" (click)="deleteCompositeField();">
<mat-icon>delete</mat-icon>
</button>
</div>
</div>
</div>
</div>
<ng-container *ngIf="fieldSet && isVisibleByVisibilityService && tableRow">
<ng-container *ngFor="let field of fieldSet.fields;">
<td *ngIf="this.visibilityRulesService.isVisibleMap[field.id] ?? true" class="text-wrap">{{propertiesFormGroup.get(field.id).get('value').getRawValue() | fieldValue | translate}}</td>
</ng-container>
<td class="actions">
<button mat-icon-button type="button" class="deleteBtn btn-sm" (click)="editCompositeFieldInDialog()" [disabled]="propertiesFormGroup.get(field.id).disabled">
<mat-icon>edit</mat-icon>
</button>
<button *ngIf="showDelete" mat-icon-button type="button" class="deleteBtn" (click)="deleteCompositeField();" [disabled]="propertiesFormGroup.get(field.id).disabled">
<mat-icon>delete</mat-icon>
</button>
</td>
</ng-container>

View File

@ -0,0 +1,17 @@
.compositeField {
padding-left: 0em !important;
// padding-top: 2em !important;
}
// ::ng-deep .mat-form-field-appearance-outline .mat-form-field-outline {
// background: #fafafa !important;
// }
// ::ng-deep .mat-form-field-appearance-outline .mat-form-field-infix {
// font-size: 1rem;
// padding: 0.6em 0 1em 0 !important;
// }
.actions {
width: 110px;
}

View File

@ -0,0 +1,115 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input } from '@angular/core';
import { UntypedFormGroup } from '@angular/forms';
import { MatDialog } from "@angular/material/dialog";
import { DescriptionTemplateFieldSet } from '@app/core/model/description-template/description-template';
import { BaseComponent } from '@common/base/base.component';
import { takeUntil } from 'rxjs/operators';
import { ToCEntry } from '../../../table-of-contents/models/toc-entry';
import { VisibilityRulesService } from '../../visibility-rules/visibility-rules.service';
@Component({
selector: 'app-form-field-set',
templateUrl: './form-field-set.component.html',
styleUrls: ['./form-field-set.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class DescriptionFormFieldSetComponent extends BaseComponent {
@Input() fieldSet: DescriptionTemplateFieldSet;
@Input() propertiesFormGroup: UntypedFormGroup;
isVisibleByVisibilityService: boolean = true;
@Input() visibilityRulesService: VisibilityRulesService;
@Input() datasetProfileId: String;
// @Input() form: UntypedFormGroup;
@Input() isChild: Boolean = false;
@Input() showDelete: Boolean = false;
@Input() tocentry: ToCEntry;
@Input() tableRow: boolean = false;
@Input() showTitle: boolean = true;
@Input() placeholderTitle: boolean = false;
constructor(
private dialog: MatDialog,
private changeDetector: ChangeDetectorRef
) {
super();
}
ngOnInit() {
this.visibilityRulesService.getElementVisibilityMapObservable().pipe(takeUntil(this._destroyed)).subscribe(x => {
if (x.has(this.fieldSet.id)) {
this.isVisibleByVisibilityService = x.get(this.fieldSet.id);
// this.changeDetector.markForCheck();
}
});
// if (this.tocentry) {
// this.form = this.tocentry.form as UntypedFormGroup;
// }
}
// editCompositeFieldInDialog() {
// const dialogRef = this.dialog.open(FormCompositeFieldDialogComponent, {
// width: '750px',
// disableClose: true,
// data: {
// formGroup: cloneAbstractControl(this.form),
// datasetProfileId: this.datasetProfileId,
// visibilityRulesService: this.visibilityRulesService
// }
// });
// dialogRef.afterClosed().pipe(takeUntil(this._destroyed)).subscribe(data => {
// if (data) {
// this.form.patchValue(data.value);
// this.changeDetector.detectChanges();
// }
// });
// }
// deleteCompositeField() {
// if (this.isChild) {
// this.deleteMultipeFieldFromCompositeFormGroup();
// } else {
// this.deleteCompositeFieldFormGroup();
// }
// }
// deleteCompositeFieldFormGroup() {
// const compositeFieldId = ((this.form.get('multiplicityItems') as UntypedFormArray).get('' + 0) as UntypedFormGroup).getRawValue().id;
// const fieldIds = (this.form.get('fields') as UntypedFormArray).controls.map(control => control.get('id').value) as string[];
// const numberOfItems = this.form.get('multiplicityItems').get('' + 0).get('fields').value.length;
// for (let i = 0; i < numberOfItems; i++) {
// const multiplicityItem = this.form.get('multiplicityItems').get('' + 0).get('fields').get('' + i).value;
// this.form.get('fields').get('' + i).patchValue(multiplicityItem);
// }
// (<UntypedFormArray>(this.form.get('multiplicityItems'))).removeAt(0);
// this.visibilityRulesService.removeAllIdReferences(compositeFieldId);
// fieldIds.forEach(x => this.visibilityRulesService.removeAllIdReferences(x));
// }
// deleteMultipeFieldFromCompositeFormGroup() {
// const parent = this.form.parent;
// const index = (parent as UntypedFormArray).controls.indexOf(this.form);
// const currentId = this.form.get('id').value;
// const fieldIds = (this.form.get('fields') as UntypedFormArray).controls.map(control => control.get('id').value) as string[];
// this.visibilityRulesService.removeAllIdReferences(currentId);
// fieldIds.forEach(x => this.visibilityRulesService.removeAllIdReferences(x));
// (parent as UntypedFormArray).removeAt(index);
// (parent as UntypedFormArray).controls.forEach((control, i) => {
// try {
// control.get('ordinal').setValue(i);
// } catch {
// throw 'Could not find ordinal';
// }
// });
// }
}

View File

@ -0,0 +1,473 @@
<div *ngIf="field && visible" [id]="field.id"
[ngSwitch]="this.field?.data?.fieldType" class="dynamic-form-field row">
<h5 *ngIf="fieldSet.title && !isChild">{{fieldSet.title}}</h5>
<mat-icon *ngIf="fieldSet.additionalInformation && !isChild" matTooltip="{{fieldSet.additionalInformation}}">info</mat-icon>
<h5 *ngIf="fieldSet.description && !isChild" class="col-12">{{fieldSet.description}}
</h5>
<h5 *ngIf="fieldSet.extendedDescription && !isChild" class="col-12">
<i>{{fieldSet.extendedDescription}}</i>
</h5>
<mat-form-field *ngSwitchCase="descriptionTemplateFieldTypeEnum.FREE_TEXT" class="col-12">
<input matInput [formControl]="propertiesFormGroup.get(field.id).get('value')" placeholder="{{(field.data.label) + (isRequired? ' *': '')}}" [required]="isRequired">
<mat-error *ngIf="propertiesFormGroup.get(field.id).get('value').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
<mat-error *ngIf="propertiesFormGroup.get(field.id).get('value').hasError('pattern')">{{'GENERAL.VALIDATION.URL.MESSAGE' | translate}}</mat-error>
</mat-form-field>
<div *ngSwitchCase="descriptionTemplateFieldTypeEnum.AUTO_COMPLETE" class="col-12">
<div class="row">
<mat-form-field class="col-md-12">
<ng-container *ngIf="field.data.multiAutoComplete">
<app-multiple-auto-complete placeholder="{{ (field.data.label | translate) + (isRequired? ' *': '')}}" [formControl]="propertiesFormGroup.get(field.id).get('value')"
[configuration]="multipleAutoCompleteConfiguration">
</app-multiple-auto-complete>
</ng-container>
<ng-container *ngIf="!(field.data.multiAutoComplete)">
<app-single-auto-complete placeholder="{{ (field.data.label | translate) + (isRequired? ' *': '')}}" [formControl]="propertiesFormGroup.get(field.id).get('value')"
[configuration]="singleAutoCompleteConfiguration" [required]="isRequired">
</app-single-auto-complete>
</ng-container>
<mat-error *ngIf="propertiesFormGroup.get(field.id).get('value').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}
</mat-error>
<mat-hint>{{ "TYPES.DATASET-PROFILE-COMBO-BOX-TYPE.EXTERNAL-SOURCE-HINT" | translate }}</mat-hint>
</mat-form-field>
</div>
</div>
<div *ngSwitchCase="descriptionTemplateFieldTypeEnum.WORD_LIST" class="col-12">
<div class="row">
<mat-form-field class="col-md-12">
<mat-select [formControl]="propertiesFormGroup.get(field.id).get('value')" placeholder="{{ (field.data.label | translate) + (isRequired? ' *': '') }}" [required]="isRequired" [multiple]="field.data.multiList">
<mat-option *ngFor="let opt of field.data.options" [value]="opt.value">{{opt.label}}
</mat-option>
</mat-select>
<mat-error *ngIf="propertiesFormGroup.get(field.id).get('value').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}
</mat-error>
</mat-form-field>
</div>
</div>
<div *ngSwitchCase="descriptionTemplateFieldTypeEnum.INTERNAL_DMP_ENTRIES_RESEARCHERS" class="col-12">
<div class="row">
<mat-form-field class="col-md-12" >
<ng-container *ngIf="field.data.multiAutoComplete">
<app-multiple-auto-complete placeholder="{{ (field.data.label | translate) + (isRequired? ' *': '') }}" [formControl]="propertiesFormGroup.get(field.id).get('value')"
[configuration]="multipleAutoCompleteConfiguration">
</app-multiple-auto-complete>
</ng-container>
<ng-container *ngIf="!(field.data.multiAutoComplete)">
<app-single-auto-complete placeholder="{{ (field.data.label | translate) + (isRequired? ' *': '') }}" [formControl]="propertiesFormGroup.get(field.id).get('value')"
[configuration]="singleAutoCompleteConfiguration" [required]="isRequired">
</app-single-auto-complete>
</ng-container>
<mat-error *ngIf="propertiesFormGroup.get(field.id).get('value').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}
</mat-error>
<mat-hint>{{ "TYPES.DATASET-PROFILE-COMBO-BOX-TYPE.EXTERNAL-SOURCE-HINT" | translate }}</mat-hint>
</mat-form-field>
</div>
</div>
<div *ngSwitchCase="descriptionTemplateFieldTypeEnum.INTERNAL_DMP_ENTRIES_DATASETS" class="col-12">
<div class="row">
<mat-form-field class="col-md-12">
<ng-container *ngIf="field.data.multiAutoComplete">
<app-multiple-auto-complete placeholder="{{ (field.data.label | translate) + (isRequired? ' *': '') }}" [formControl]="propertiesFormGroup.get(field.id).get('value')"
[configuration]="multipleAutoCompleteConfiguration">
</app-multiple-auto-complete>
</ng-container>
<ng-container *ngIf="!(field.data.multiAutoComplete)">
<app-single-auto-complete placeholder="{{ (field.data.label | translate) + (isRequired? ' *': '') }}" [formControl]="propertiesFormGroup.get(field.id).get('value')"
[configuration]="singleAutoCompleteConfiguration" [required]="isRequired">
</app-single-auto-complete>
</ng-container>
<mat-error *ngIf="propertiesFormGroup.get(field.id).get('value').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}
</mat-error>
<mat-hint>{{ "TYPES.DATASET-PROFILE-COMBO-BOX-TYPE.EXTERNAL-SOURCE-HINT" | translate }}</mat-hint>
</mat-form-field>
</div>
</div>
<div *ngSwitchCase="descriptionTemplateFieldTypeEnum.INTERNAL_DMP_ENTRIES_DMPS" class="col-12">
<div class="row">
<mat-form-field class="col-md-12">
<ng-container *ngIf="field.data.multiAutoComplete">
<app-multiple-auto-complete placeholder="{{ (field.data.label | translate) + (isRequired? ' *': '') }}" [formControl]="propertiesFormGroup.get(field.id).get('value')"
[configuration]="multipleAutoCompleteConfiguration">
</app-multiple-auto-complete>
</ng-container>
<ng-container *ngIf="!(field.data.multiAutoComplete)">
<app-single-auto-complete placeholder="{{ (field.data.label | translate) + (isRequired? ' *': '') }}" [formControl]="propertiesFormGroup.get(field.id).get('value')"
[configuration]="singleAutoCompleteConfiguration" [required]="isRequired">
</app-single-auto-complete>
</ng-container>
<mat-error *ngIf="propertiesFormGroup.get(field.id).get('value').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}
</mat-error>
<mat-hint>{{ "TYPES.DATASET-PROFILE-COMBO-BOX-TYPE.EXTERNAL-SOURCE-HINT" | translate }}</mat-hint>
</mat-form-field>
</div>
</div>
<div *ngSwitchCase="descriptionTemplateFieldTypeEnum.CHECK_BOX" class="col-12">
<mat-checkbox [formControl]="propertiesFormGroup.get(field.id).get('value')" [required]="isRequired">
{{field.data.label}}</mat-checkbox>
</div>
<mat-form-field *ngSwitchCase="descriptionTemplateFieldTypeEnum.TEXT_AREA" class="col-12">
<textarea matInput class="text-area" [formControl]="propertiesFormGroup.get(field.id).get('value')" matTextareaAutosize matAutosizeMinRows="3" matAutosizeMaxRows="15" [required]="isRequired"
placeholder="{{ (field.data.label | translate) + (isRequired? ' *': '') }}"></textarea>
<button mat-icon-button type="button" *ngIf="!propertiesFormGroup.get(field.id).get('value').disabled && propertiesFormGroup.get(field.id).get('value').value" matSuffix aria-label="Clear" (click)="this.propertiesFormGroup.get(field.id).get('value').patchValue('')">
<mat-icon>close</mat-icon>
</button>
<mat-error *ngIf="propertiesFormGroup.get(field.id).get('value')['errors'] && propertiesFormGroup.get(field.id).get('value').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</mat-form-field>
<ng-container *ngSwitchCase="descriptionTemplateFieldTypeEnum.RICH_TEXT_AREA">
<rich-text-editor-component class="col-12"
[parentFormGroup]="propertiesFormGroup.get(field.id)" [controlName]="'value'"
[placeholder]="field.data.label"
[required]="isRequired"
[wrapperClasses]="'full-width editor ' +
((isRequired && propertiesFormGroup.get(field.id).get('value').touched && propertiesFormGroup.get(field.id).get('value').hasError('required')) ? 'required' : '')"
[editable]="!propertiesFormGroup.get(field.id).get('value').disabled">
</rich-text-editor-component>
<div [class]="(propertiesFormGroup.get(field.id).get('value')['errors'] && propertiesFormGroup.get(field.id).get('value').hasError('required') && propertiesFormGroup.get(field.id).get('value').touched) ? 'visible' : 'invisible'" class="col-12">
<div class="mat-form-field form-field-subscript-wrapper">
<mat-error *ngIf="propertiesFormGroup.get(field.id).get('value')['errors'] && propertiesFormGroup.get(field.id).get('value').hasError('required') && propertiesFormGroup.get(field.id).get('value').touched">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</div>
</div>
</ng-container>
<ng-container *ngSwitchCase="descriptionTemplateFieldTypeEnum.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()" [disabled]="propertiesFormGroup.get(field.id).get('value').disabled">
<ngx-dropzone-preview *ngIf="propertiesFormGroup.get(field.id).get('value').value && propertiesFormGroup.get(field.id).get('value').value.name" class="file-preview"
[removable]="true" (removed)="onRemove()">
<ngx-dropzone-label class="file-label">{{ propertiesFormGroup.get(field.id).get('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="!propertiesFormGroup.get(field.id).get('value').value || filesToUpload" mat-button (click)="drop.showFileSelector()" type="button" class="attach-file-btn"
[disabled]="!!propertiesFormGroup.get(field.id).get('value').value || propertiesFormGroup.get(field.id).get('value').disabled">
<mat-icon class="mr-2">upload</mat-icon>
<mat-label>{{ (field.data.label | translate)}}</mat-label>
</button>
<button *ngIf="propertiesFormGroup.get(field.id).get('value').value && !filesToUpload" mat-button (click)="download()" type="button" class="attach-file-btn"
[disabled]="propertiesFormGroup.get(field.id).get('value').disabled">
<mat-icon class="mr-2">download</mat-icon>
<mat-label>{{ "TYPES.DATASET-PROFILE-UPLOAD-TYPE.DOWNLOAD" | translate }}</mat-label>
</button>
</div>
</ng-container>
<div *ngSwitchCase="descriptionTemplateFieldTypeEnum.BOOLEAN_DECISION" class="col-12">
<mat-radio-group [formControl]="propertiesFormGroup.get(field.id).get('value')" [required]="isRequired">
<mat-radio-button class="radio-button-item" name="{{propertiesFormGroup.get(field.id).get('key').value}}" value="true">{{ "TYPES.DATASET-PROFILE-COMBO-BOX-TYPE.ACTIONS.YES" | translate }}</mat-radio-button>
<mat-radio-button class="radio-button-item" name="{{propertiesFormGroup.get(field.id).get('key').value}}" value="false">{{ "TYPES.DATASET-PROFILE-COMBO-BOX-TYPE.ACTIONS.NO" | translate }}</mat-radio-button>
</mat-radio-group>
<small class="text-danger d-block" *ngIf="propertiesFormGroup.get(field.id).get('value').hasError('required') && propertiesFormGroup.get(field.id).get('value').touched">{{'GENERAL.VALIDATION.REQUIRED' | translate}}
</small>
<small class="text-muted d-inline-block" *ngIf="(isRequired) && !propertiesFormGroup.get(field.id).get('value').touched">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</small>
</div>
<div *ngSwitchCase="descriptionTemplateFieldTypeEnum.RADIO_BOX" class="col-12">
<mat-radio-group [formControl]="propertiesFormGroup.get(field.id).get('value')" [required]="isRequired">
<mat-radio-button *ngFor="let option of field.data.options let index = index" class="radio-button-item" [value]="option.value">{{option.label}}</mat-radio-button>
</mat-radio-group>
<small class="text-danger d-block" *ngIf="propertiesFormGroup.get(field.id).get('value').hasError('required') && propertiesFormGroup.get(field.id).get('value').touched">{{'GENERAL.VALIDATION.REQUIRED' | translate}}
</small>
<small class="text-muted d-inline-block" *ngIf="(isRequired) && !propertiesFormGroup.get(field.id).get('value').touched">{{'GENERAL.VALIDATION.REQUIRED' | translate}} *</small>
</div>
<mat-form-field *ngSwitchCase="descriptionTemplateFieldTypeEnum.DATE_PICKER" class="col-12">
<input matInput placeholder="{{ (field.data.label | translate) + (isRequired? ' *': '') }}" class="table-input" [matDatepicker]="date" [required]="isRequired"
[formControl]="propertiesFormGroup.get(field.id).get('value')">
<mat-datepicker-toggle matSuffix [for]="date"></mat-datepicker-toggle>
<mat-datepicker #date></mat-datepicker>
<mat-error *ngIf="propertiesFormGroup.get(field.id).get('value').hasError('required')">
{{'GENERAL.VALIDATION.REQUIRED' | translate}}
</mat-error>
</mat-form-field>
<div *ngSwitchCase="descriptionTemplateFieldTypeEnum.EXTERNAL_DATASETS" class="col-12">
<div class="row">
<mat-form-field class="col-md-12">
<ng-container *ngIf="field.data.multiAutoComplete">
<app-multiple-auto-complete placeholder="{{ (field.data.label | translate) + (isRequired? ' *': '') }}" [formControl]="propertiesFormGroup.get(field.id).get('value')"
[configuration]="externalDatasetAutoCompleteConfiguration" [required]="isRequired">
</app-multiple-auto-complete>
</ng-container>
<ng-container *ngIf="!(field.data.multiAutoComplete)">
<app-single-auto-complete placeholder="{{ (field.data.label | translate) + (isRequired? ' *': '') }}" [formControl]="propertiesFormGroup.get(field.id).get('value')"
[configuration]="externalDatasetAutoCompleteConfiguration" [required]="isRequired">
</app-single-auto-complete>
</ng-container>
<mat-error *ngIf="propertiesFormGroup.get(field.id).get('value').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}
</mat-error>
<mat-hint>{{ "TYPES.DATASET-PROFILE-COMBO-BOX-TYPE.EXTERNAL-SOURCE-HINT" | translate }}</mat-hint>
</mat-form-field>
</div>
</div>
<div *ngSwitchCase="descriptionTemplateFieldTypeEnum.DATA_REPOSITORIES" class="col-12">
<div class="row">
<mat-form-field class="col-md-12">
<ng-container *ngIf="field.data.multiAutoComplete">
<app-multiple-auto-complete placeholder="{{ (field.data.label | translate) + (isRequired? ' *': '') }}" [formControl]="propertiesFormGroup.get(field.id).get('value')"
[configuration]="dataRepositoriesAutoCompleteConfiguration" [required]="isRequired">
</app-multiple-auto-complete>
</ng-container>
<ng-container *ngIf="!(field.data.multiAutoComplete)">
<app-single-auto-complete placeholder="{{ (field.data.label | translate) + (isRequired? ' *': '') }}" [formControl]="propertiesFormGroup.get(field.id).get('value')"
[configuration]="dataRepositoriesAutoCompleteConfiguration" [required]="isRequired">
</app-single-auto-complete>
</ng-container>
<mat-error *ngIf="propertiesFormGroup.get(field.id).get('value').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}
</mat-error>
<mat-hint>{{ "TYPES.DATASET-PROFILE-COMBO-BOX-TYPE.EXTERNAL-SOURCE-HINT" | translate }}</mat-hint>
</mat-form-field>
</div>
</div>
<div *ngSwitchCase="descriptionTemplateFieldTypeEnum.PUB_REPOSITORIES" class="col-12">
<div class="row">
<mat-form-field class="col-md-12">
<ng-container *ngIf="field.data.multiAutoComplete">
<app-multiple-auto-complete placeholder="{{ (field.data.label | translate) + (isRequired? ' *': '') }}" [formControl]="propertiesFormGroup.get(field.id).get('value')"
[configuration]="pubRepositoriesAutoCompleteConfiguration" [required]="isRequired">
</app-multiple-auto-complete>
</ng-container>
<ng-container *ngIf="!(field.data.multiAutoComplete)">
<app-single-auto-complete placeholder="{{ (field.data.label | translate) + (isRequired? ' *': '') }}" [formControl]="propertiesFormGroup.get(field.id).get('value')"
[configuration]="pubRepositoriesAutoCompleteConfiguration" [required]="isRequired">
</app-single-auto-complete>
</ng-container>
<mat-error *ngIf="propertiesFormGroup.get(field.id).get('value').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}
</mat-error>
<mat-hint>{{ "TYPES.DATASET-PROFILE-COMBO-BOX-TYPE.EXTERNAL-SOURCE-HINT" | translate }}</mat-hint>
</mat-form-field>
</div>
</div>
<div *ngSwitchCase="descriptionTemplateFieldTypeEnum.JOURNAL_REPOSITORIES" class="col-12">
<div class="row">
<mat-form-field class="col-md-12">
<ng-container *ngIf="field.data.multiAutoComplete">
<app-multiple-auto-complete placeholder="{{ (field.data.label | translate) + (isRequired? ' *': '') }}" [formControl]="propertiesFormGroup.get(field.id).get('value')"
[configuration]="journalRepositoriesAutoCompleteConfiguration" [required]="isRequired">
</app-multiple-auto-complete>
</ng-container>
<ng-container *ngIf="!(field.data.multiAutoComplete)">
<app-single-auto-complete placeholder="{{ (field.data.label | translate) + (isRequired? ' *': '') }}" [formControl]="propertiesFormGroup.get(field.id).get('value')"
[configuration]="journalRepositoriesAutoCompleteConfiguration" [required]="isRequired">
</app-single-auto-complete>
</ng-container>
<mat-error *ngIf="propertiesFormGroup.get(field.id).get('value').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}
</mat-error>
<mat-hint>{{ "TYPES.DATASET-PROFILE-COMBO-BOX-TYPE.EXTERNAL-SOURCE-HINT" | translate }}</mat-hint>
</mat-form-field>
</div>
</div>
<div *ngSwitchCase="descriptionTemplateFieldTypeEnum.TAXONOMIES" class="col-12">
<div *ngIf="field.data" class="row">
<mat-form-field class="col-md-12">
<ng-container *ngIf="field.data.multiAutoComplete">
<app-multiple-auto-complete placeholder="{{ (field.data.label | translate) + (isRequired? ' *': '') }}" [formControl]="propertiesFormGroup.get(field.id).get('value')"
[configuration]="taxonomiesAutoCompleteConfiguration" [required]="isRequired">
</app-multiple-auto-complete>
</ng-container>
<ng-container *ngIf="!(field.data.multiAutoComplete)">
<app-single-auto-complete placeholder="{{ (field.data.label | translate) + (isRequired? ' *': '') }}" [formControl]="propertiesFormGroup.get(field.id).get('value')"
[configuration]="taxonomiesAutoCompleteConfiguration" [required]="isRequired">
</app-single-auto-complete>
</ng-container>
<mat-error *ngIf="propertiesFormGroup.get(field.id).get('value').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}
</mat-error>
<mat-hint>{{ "TYPES.DATASET-PROFILE-COMBO-BOX-TYPE.EXTERNAL-SOURCE-HINT" | translate }}</mat-hint>
</mat-form-field>
</div>
</div>
<div *ngSwitchCase="descriptionTemplateFieldTypeEnum.LICENSES" class="col-12">
<div *ngIf="field.data" class="row">
<mat-form-field class="col-md-12">
<ng-container *ngIf="field.data.multiAutoComplete">
<app-multiple-auto-complete placeholder="{{ (field.data.label | translate) + (isRequired? ' *': '') }}" [formControl]="propertiesFormGroup.get(field.id).get('value')"
[configuration]="licensesAutoCompleteConfiguration" [required]="isRequired">
</app-multiple-auto-complete>
</ng-container>
<ng-container *ngIf="!(field.data.multiAutoComplete)">
<app-single-auto-complete placeholder="{{ (field.data.label | translate) + (isRequired? ' *': '') }}" [formControl]="propertiesFormGroup.get(field.id).get('value')"
[configuration]="licensesAutoCompleteConfiguration" [required]="isRequired">
</app-single-auto-complete>
</ng-container>
<mat-error *ngIf="propertiesFormGroup.get(field.id).get('value').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}
</mat-error>
<mat-hint>{{ "TYPES.DATASET-PROFILE-COMBO-BOX-TYPE.EXTERNAL-SOURCE-HINT" | translate }}</mat-hint>
</mat-form-field>
</div>
</div>
<div *ngSwitchCase="descriptionTemplateFieldTypeEnum.PUBLICATIONS" class="col-12">
<div *ngIf="field.data" class="row">
<mat-form-field class="col-md-12">
<ng-container *ngIf="field.data.multiAutoComplete">
<app-multiple-auto-complete placeholder="{{ (field.data.label | translate) + (isRequired? ' *': '') }}" [formControl]="propertiesFormGroup.get(field.id).get('value')"
[configuration]="publicationsAutoCompleteConfiguration" [required]="isRequired">
</app-multiple-auto-complete>
</ng-container>
<ng-container *ngIf="!(field.data.multiAutoComplete)">
<app-single-auto-complete placeholder="{{ (field.data.label | translate) + (isRequired? ' *': '') }}" [formControl]="propertiesFormGroup.get(field.id).get('value')"
[configuration]="publicationsAutoCompleteConfiguration" [required]="isRequired">
</app-single-auto-complete>
</ng-container>
<mat-error *ngIf="propertiesFormGroup.get(field.id).get('value').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}
</mat-error>
<mat-hint>{{ "TYPES.DATASET-PROFILE-COMBO-BOX-TYPE.EXTERNAL-SOURCE-HINT" | translate }}</mat-hint>
</mat-form-field>
</div>
</div>
<div *ngSwitchCase="descriptionTemplateFieldTypeEnum.REGISTRIES" class="col-12">
<div class="row">
<mat-form-field class="col-md-12">
<ng-container *ngIf="field.data.multiAutoComplete">
<app-multiple-auto-complete placeholder="{{ (field.data.label | translate) + (isRequired? ' *': '') }}" [formControl]="propertiesFormGroup.get(field.id).get('value')"
[configuration]="registriesAutoCompleteConfiguration">
</app-multiple-auto-complete>
</ng-container>
<ng-container *ngIf="!(field.data.multiAutoComplete)">
<app-single-auto-complete placeholder="{{ (field.data.label | translate) + (isRequired? ' *': '') }}" [formControl]="propertiesFormGroup.get(field.id).get('value')"
[configuration]="registriesAutoCompleteConfiguration" [required]="isRequired">
</app-single-auto-complete>
</ng-container>
<mat-error *ngIf="propertiesFormGroup.get(field.id).get('value').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}
</mat-error>
<mat-hint>{{ "TYPES.DATASET-PROFILE-COMBO-BOX-TYPE.EXTERNAL-SOURCE-HINT" | translate }}</mat-hint>
</mat-form-field>
</div>
</div>
<div *ngSwitchCase="descriptionTemplateFieldTypeEnum.SERVICES" class="col-12">
<div class="row">
<mat-form-field class="col-md-12">
<ng-container *ngIf="field.data.multiAutoComplete">
<app-multiple-auto-complete placeholder="{{ (field.data.label | translate) + (isRequired? ' *': '') }}" [formControl]="propertiesFormGroup.get(field.id).get('value')"
[configuration]="servicesAutoCompleteConfiguration">
</app-multiple-auto-complete>
</ng-container>
<ng-container *ngIf="!(field.data.multiAutoComplete)">
<app-single-auto-complete placeholder="{{ (field.data.label | translate) + (isRequired? ' *': '') }}" [formControl]="propertiesFormGroup.get(field.id).get('value')"
[configuration]="servicesAutoCompleteConfiguration" [required]="isRequired">
</app-single-auto-complete>
</ng-container>
<mat-error *ngIf="propertiesFormGroup.get(field.id).get('value').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}
</mat-error>
<mat-hint>{{ "TYPES.DATASET-PROFILE-COMBO-BOX-TYPE.EXTERNAL-SOURCE-HINT" | translate }}</mat-hint>
</mat-form-field>
</div>
</div>
<div *ngSwitchCase="descriptionTemplateFieldTypeEnum.TAGS" class="col-12">
<div class="row">
<mat-form-field class="col-md-12">
<app-multiple-auto-complete [configuration]="tagsAutoCompleteConfiguration" [formControl]="propertiesFormGroup.get(field.id).get('value')" placeholder="{{('DATASET-EDITOR.FIELDS.TAGS' | translate) + (isRequired? ' *': '')}}"></app-multiple-auto-complete>
</mat-form-field>
</div>
</div>
<div *ngSwitchCase="descriptionTemplateFieldTypeEnum.RESEARCHERS" class="col-12">
<div class="row">
<mat-form-field class="col-md-12">
<ng-container *ngIf="field.data.multiAutoComplete">
<app-multiple-auto-complete placeholder="{{ (field.data.label | translate) + (isRequired? ' *': '') }}" [formControl]="propertiesFormGroup.get(field.id).get('value')"
[configuration]="researchersAutoCompleteConfiguration">
</app-multiple-auto-complete>
</ng-container>
<ng-container *ngIf="!(field.data.multiAutoComplete)">
<app-single-auto-complete placeholder="{{ (field.data.label | translate) + (isRequired? ' *': '') }}" [formControl]="propertiesFormGroup.get(field.id).get('value')"
[configuration]="researchersAutoCompleteConfiguration" [required]="isRequired">
</app-single-auto-complete>
</ng-container>
<mat-error *ngIf="propertiesFormGroup.get(field.id).get('value').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}
</mat-error>
<mat-hint>{{ "TYPES.DATASET-PROFILE-COMBO-BOX-TYPE.EXTERNAL-SOURCE-HINT" | translate }}</mat-hint>
</mat-form-field>
</div>
</div>
<div *ngSwitchCase="descriptionTemplateFieldTypeEnum.ORGANIZATIONS" class="col-12">
<div class="row">
<mat-form-field class="col-md-12">
<ng-container *ngIf="field.data.multiAutoComplete">
<app-multiple-auto-complete placeholder="{{ (field.data.label | translate) + (isRequired? ' *': '') }}" [formControl]="propertiesFormGroup.get(field.id).get('value')"
[configuration]="organisationsAutoCompleteConfiguration">
</app-multiple-auto-complete>
</ng-container>
<ng-container *ngIf="!(field.data.multiAutoComplete)">
<app-single-auto-complete placeholder="{{ (field.data.label | translate) + (isRequired? ' *': '') }}" [formControl]="propertiesFormGroup.get(field.id).get('value')"
[configuration]="organisationsAutoCompleteConfiguration" [required]="isRequired">
</app-single-auto-complete>
<mat-error *ngIf="propertiesFormGroup.get(field.id).get('value').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}
</mat-error>
</ng-container>
<mat-hint>{{ "TYPES.DATASET-PROFILE-COMBO-BOX-TYPE.EXTERNAL-SOURCE-HINT" | translate }}</mat-hint>
</mat-form-field>
</div>
</div>
<div *ngSwitchCase="descriptionTemplateFieldTypeEnum.DATASET_IDENTIFIER" class="col-12">
<div class="row" *ngIf="datasetIdInitialized">
<mat-form-field class="col-md-12">
<input matInput class="col-md-12" [formControl]="getDatasetIdControl('identifier')" placeholder="{{(field.data.label) + (isRequired? ' *': '')}}"
[required]="isRequired" [disabled]="propertiesFormGroup.get(field.id).get('value').disabled">
<mat-error *ngIf="propertiesFormGroup.get(field.id).get('value').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}
</mat-error>
</mat-form-field>
<mat-form-field class="col-md-12">
<mat-select class="col-md-12" [formControl]="getDatasetIdControl('type')" [placeholder]="('TYPES.DATASET-PROFILE-IDENTIFIER.IDENTIFIER-TYPE' | translate) + (isRequired? ' *': '')"
[disabled]="propertiesFormGroup.get(field.id).get('value').disabled">
<mat-option *ngFor="let type of datasetIdTypes" [value]="type.value">
{{ type.name }}
</mat-option>
</mat-select>
<mat-error *ngIf="propertiesFormGroup.get(field.id).get('value').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}
</mat-error>
</mat-form-field>
</div>
</div>
<div *ngSwitchCase="descriptionTemplateFieldTypeEnum.CURRENCY" class="col-12">
<div class="row">
<mat-form-field class="col-md-12">
<app-single-auto-complete placeholder="{{ (field.data.label | translate) + (isRequired? ' *': '') }}" [formControl]="propertiesFormGroup.get(field.id).get('value')"
[configuration]="currencyAutoCompleteConfiguration" [required]="isRequired">
</app-single-auto-complete>
<mat-error *ngIf="propertiesFormGroup.get(field.id).get('value').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}
</mat-error>
</mat-form-field>
</div>
</div>
<div *ngSwitchCase="descriptionTemplateFieldTypeEnum.VALIDATION" class="col-12">
<div class="row align-items-baseline">
<mat-form-field class="col-md-4">
<input matInput class="col-md-12" [formControl]="getDatasetIdControl('identifier')" placeholder="{{(field.data.label) + (isRequired? ' *': '')}}" [required]="isRequired">
<mat-error *ngIf="propertiesFormGroup.get(field.id).get('value').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}
</mat-error>
</mat-form-field>
<mat-form-field class="col-md-4">
<mat-select class="col-md-12" [formControl]="getDatasetIdControl('type')" [placeholder]="('TYPES.DATASET-PROFILE-VALIDATOR.REPOSITORIES-PLACEHOLDER' | translate) + (isRequired? ' *': '')">
<mat-option *ngFor="let type of validationTypes" [value]="type.value">
{{ type.name }}
</mat-option>
</mat-select>
<mat-error *ngIf="propertiesFormGroup.get(field.id).get('value').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}
</mat-error>
</mat-form-field>
<div class="col-md-2">
<button type="button" mat-button class="lightblue-btn" (click)="validateId()" [disabled]="propertiesFormGroup.get(field.id).get('value').disabled">{{ "TYPES.DATASET-PROFILE-VALIDATOR.ACTION" | translate }}</button>
<mat-error *ngIf="propertiesFormGroup.get(field.id).get('value').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}
</mat-error>
</div>
<div class="col-md-1">
<mat-progress-spinner *ngIf="validationIcon === 'loading'" mode="indeterminate" [diameter]="24"></mat-progress-spinner>
<mat-icon *ngIf="validationIcon !== 'loading'" [ngClass]="{'success': validationIcon === 'done', 'fail': validationIcon === 'clear'}">{{validationIcon}}</mat-icon>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,105 @@
.dynamic-form-field {
.radio-button-item {
margin-right: 1em;
}
.full-width {
width: 100%;
}
.text-area {
box-sizing: content-box;
}
.success {
color: green;
}
.fail {
color: red;
}
}
::ng-deep .mat-form-field-appearance-outline .mat-form-field-outline {
background: #fafafa !important;
}
::ng-deep .mat-form-field-appearance-outline .mat-form-field-infix {
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: var(--primary-color) 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 var(--primary-color);
color: var(--primary-color);
}
.attach-file-btn.mdc-button-disabled, .attach-file-btn.mdc-button-disabled:hover {
background-color: #ffffff;
border: 1px solid darkgray;
color: darkgrey !important;
}
//
//.mdc-button-disabled .attach-file-btn > ::ng-deep mdc-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

@ -0,0 +1,764 @@
import { COMMA, ENTER } from '@angular/cdk/keycodes';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit, SimpleChanges } from '@angular/core';
import { FormControl, UntypedFormControl, UntypedFormGroup } from '@angular/forms';
import { MatDialog } from "@angular/material/dialog";
import { DescriptionTemplateFieldType } from '@app/core/common/enum/description-template-field-type';
import { ReferenceType } from '@app/core/common/enum/reference-type';
import { DataTableRequest } from '@app/core/model/data-table/data-table-request';
import { DescriptionTemplateAutoCompleteData, DescriptionTemplateAutoCompleteSingleData, DescriptionTemplateField, DescriptionTemplateFieldSet, DescriptionTemplateUploadData, DescriptionTemplateWordListData } from '@app/core/model/description-template/description-template';
import { ExternalSourceItemModel } from '@app/core/model/external-sources/external-source-item';
import { LocalFetchModel } from '@app/core/model/local-fetch/local-fetch.model';
import { FetcherReference, Reference } from '@app/core/model/reference/reference';
import { DatasetExternalAutocompleteCriteria, DatasetExternalAutocompleteOptionsCriteria } from '@app/core/query/dataset/daatset-external-autocomplete-criteria';
import { DatasetCriteria } from '@app/core/query/dataset/dataset-criteria';
import { DmpCriteria } from '@app/core/query/dmp/dmp-criteria';
import { ReferenceSearchLookup } from '@app/core/query/reference-search.lookup';
import { RegistryCriteria } from '@app/core/query/registry/registry-criteria';
import { RequestItem } from '@app/core/query/request-item';
import { ResearcherCriteria } from '@app/core/query/researcher/researcher-criteria';
import { ServiceCriteria } from '@app/core/query/service/service-criteria';
import { TagCriteria } from '@app/core/query/tag/tag-criteria';
import { TaxonomyCriteria } from "@app/core/query/taxonomy/taxonomy-criteria";
import { CurrencyService } from '@app/core/services/currency/currency.service';
import { DatasetExternalAutocompleteService } from '@app/core/services/dataset/dataset-external-autocomplete.service';
import { DatasetService } from '@app/core/services/dataset/dataset.service';
import { DmpService } from '@app/core/services/dmp/dmp.service';
import { ExternalSourcesService } from '@app/core/services/external-sources/external-sources.service';
import { FileService } from "@app/core/services/file/file.service";
import {
SnackBarNotificationLevel,
UiNotificationService
} from "@app/core/services/notification/ui-notification-service";
import { ReferenceService } from '@app/core/services/reference/reference.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 { ExternalTagEditorModel } from '@app/ui/dataset/dataset-wizard/dataset-wizard-editor.model';
import { VisibilityRulesService } from '@app/ui/description/editor/description-form/visibility-rules/visibility-rules.service';
import { isNullOrUndefined } from '@app/utilities/enhancers/utils';
import { BaseComponent } from '@common/base/base.component';
import { TranslateService } from '@ngx-translate/core';
import { Observable } from 'rxjs';
import { distinctUntilChanged, map, takeUntil } from 'rxjs/operators';
import { DescriptionFieldEditorModel } from '../../../description-editor.model';
import { DescriptionTemplateFieldValidationType } from '@app/core/common/enum/description-template-field-validation-type';
@Component({
selector: 'app-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() field: Field;
// @Input() form: UntypedFormGroup;
@Input() datasetProfileId: any;
@Input() isChild: Boolean = false;
autocompleteOptions: DescriptionTemplateAutoCompleteSingleData[];
visible: boolean = true;
// change: Subscription;
// trackByFn = (index, item) => item ? item['id'] : null;
descriptionTemplateFieldTypeEnum = DescriptionTemplateFieldType;
public singleAutoCompleteConfiguration: SingleAutoCompleteConfiguration;
public multipleAutoCompleteConfiguration: MultipleAutoCompleteConfiguration;
externalDatasetAutoCompleteConfiguration: SingleAutoCompleteConfiguration;
dataRepositoriesAutoCompleteConfiguration: SingleAutoCompleteConfiguration;
pubRepositoriesAutoCompleteConfiguration: SingleAutoCompleteConfiguration;
journalRepositoriesAutoCompleteConfiguration: SingleAutoCompleteConfiguration;
taxonomiesAutoCompleteConfiguration: SingleAutoCompleteConfiguration;
licensesAutoCompleteConfiguration: SingleAutoCompleteConfiguration;
publicationsAutoCompleteConfiguration: SingleAutoCompleteConfiguration;
registriesAutoCompleteConfiguration: SingleAutoCompleteConfiguration;
servicesAutoCompleteConfiguration: SingleAutoCompleteConfiguration;
tagsAutoCompleteConfiguration: SingleAutoCompleteConfiguration;
researchersAutoCompleteConfiguration: MultipleAutoCompleteConfiguration;
organisationsAutoCompleteConfiguration: MultipleAutoCompleteConfiguration;
currencyAutoCompleteConfiguration: SingleAutoCompleteConfiguration;
readonly separatorKeysCodes: number[] = [ENTER, COMMA];
tags: ExternalTagEditorModel[] = [];
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;
constructor(
private datasetExternalAutocompleteService: DatasetExternalAutocompleteService,
private externalSourcesService: ExternalSourcesService,
private language: TranslateService,
private datasetService: DatasetService,
private dmpService: DmpService,
private currencyService: CurrencyService,
private fileService: FileService,
private cdr: ChangeDetectorRef,
private uiNotificationService: UiNotificationService,
public dialog: MatDialog,
private fileUtils: FileUtils,
private referenceService: ReferenceService
) {
super();
}
ngOnChanges(changes: SimpleChanges) {
if (changes['form']) {
}
}
ngOnInit() {
this.visibilityRulesService.getElementVisibilityMapObservable().pipe(takeUntil(this._destroyed)).subscribe(x => {
if (x.has(this.field.id)){
this.visible = x.get(this.field.id);
}
});
//TODO: validate that this logic is correct. Validation contenxt path might need to be fixed.
if (this.propertiesFormGroup.get(this.field.id).get('value') == null) {
const item: DescriptionFieldEditorModel = new DescriptionFieldEditorModel();
item.key = this.field.id;
this.propertiesFormGroup.addControl(this.field.id, item.buildForm());
}
if (this.propertiesFormGroup.get(this.field.id).get('value').value) {
this.visibilityRulesService.updateValueAndVisibility(this.field?.id, this.propertiesFormGroup.get(this.field.id).get('value').value);
}
this.isRequired = this.field.validations?.includes(DescriptionTemplateFieldValidationType.Required);
if (this.field?.data?.fieldType === DescriptionTemplateFieldType.WORD_LIST) {
if ((this.field.data as DescriptionTemplateWordListData).multiList) {
const originalValue = <string>this.propertiesFormGroup.get(this.field.id).get('value').value;
if (originalValue !== null && typeof originalValue === 'string') {
let values = (<string>this.propertiesFormGroup.get(this.field.id).get('value').value).slice(1, -1).split(', ').filter((value) => !value.includes('"'));
let specialValue = (<string>this.propertiesFormGroup.get(this.field.id).get('value').value).split('"').filter((value) => !value.startsWith('[') && !value.endsWith(']') && !values.includes(value) && value !== ', ');
specialValue.forEach(value => values.push(value));
if (!originalValue.startsWith('[') && !originalValue.endsWith(']')) {
values = undefined;
values = [originalValue];
}
this.propertiesFormGroup.get(this.field.id).get('value').patchValue(values);
values.forEach(element => {
this.visibilityRulesService.updateValueAndVisibility(this.field?.id, element);
});
}
}
}
// Setup autocomplete configuration if needed
if (this.field?.data?.fieldType === DescriptionTemplateFieldType.AUTO_COMPLETE) {
if (!((this.field.data as DescriptionTemplateAutoCompleteData).multiAutoComplete)) {
this.singleAutoCompleteConfiguration = {
filterFn: this.searchFromAutocomplete.bind(this),
initialItems: () => this.searchFromAutocomplete(''),
displayFn: (item) => { try { return (item != null && item.length > 1) ? JSON.parse(item).label : item['label'] } catch { return '' } },
titleFn: (item) => { try { return item['label'] } catch { return '' } },
valueAssign: (item) => { try { return JSON.stringify(item) } catch { return '' } },
subtitleFn: (item) => { try { return item['source'] ? this.language.instant('DATASET-WIZARD.EDITOR.FIELDS.EXTERNAL-AUTOCOMPLETE-SUBTITLE') + item['source'] : this.language.instant('DATASET-WIZARD.EDITOR.FIELDS.EXTERNAL-AUTOCOMPLETE-NO-SOURCE') } catch { return '' } }
};
}
else {
this.multipleAutoCompleteConfiguration = {
filterFn: this.searchFromAutocomplete.bind(this),
initialItems: () => this.searchFromAutocomplete(''),
displayFn: (item) => { try { return typeof (item) == 'string' ? JSON.parse(item)['label'] : item['label'] } catch { return '' } },
titleFn: (item) => { try { return typeof (item) == 'string' ? JSON.parse(item)['label'] : item['label'] } catch { return '' } },
valueAssign: (item) => { try { return typeof (item) == 'string' ? item : JSON.stringify(item) } catch { return '' } },
subtitleFn: (item) => { try { return item['source'] ? this.language.instant('DATASET-WIZARD.EDITOR.FIELDS.EXTERNAL-AUTOCOMPLETE-SUBTITLE') + item['source'] : this.language.instant('DATASET-WIZARD.EDITOR.FIELDS.EXTERNAL-AUTOCOMPLETE-NO-SOURCE') } catch { return '' } }
}
}
if (isNullOrUndefined(this.datasetProfileId)) {
this.autocompleteOptions = (this.field.data as DescriptionTemplateAutoCompleteData).autoCompleteSingleDataList;
}
}
switch (this.field?.data?.fieldType) {
case DescriptionTemplateFieldType.EXTERNAL_DATASETS:
this.externalDatasetAutoCompleteConfiguration = {
filterFn: this.searchDatasetExternalDatasets.bind(this),
initialItems: () => this.searchDatasetExternalDatasets(''),//.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.source ? this.language.instant('TYPES.EXTERNAL-DATASET-TYPE.SOURCE:') + item.source : item.tag ? this.language.instant('TYPES.EXTERNAL-DATASET-TYPE.SOURCE:') + item.tag : 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.DATA_REPOSITORIES:
this.dataRepositoriesAutoCompleteConfiguration = {
filterFn: this.searchDatasetExternalDataRepositories.bind(this),
initialItems: () => this.searchDatasetExternalDataRepositories(''),
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.source ? this.language.instant('TYPES.EXTERNAL-DATASET-TYPE.SOURCE:') + item.source : item.tag ? this.language.instant('TYPES.EXTERNAL-DATASET-TYPE.SOURCE:') + item.tag : 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.PUB_REPOSITORIES:
this.pubRepositoriesAutoCompleteConfiguration = {
filterFn: this.searchDatasetExternalPubRepositories.bind(this),
initialItems: () => this.searchDatasetExternalPubRepositories(''),
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.source ? this.language.instant('TYPES.EXTERNAL-DATASET-TYPE.SOURCE:') + item.source : item.tag ? this.language.instant('TYPES.EXTERNAL-DATASET-TYPE.SOURCE:') + item.tag : 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.JOURNAL_REPOSITORIES:
this.journalRepositoriesAutoCompleteConfiguration = {
filterFn: this.searchDatasetExternalJournalRepositories.bind(this),
initialItems: () => this.searchDatasetExternalJournalRepositories(''),
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.source ? this.language.instant('TYPES.EXTERNAL-DATASET-TYPE.SOURCE:') + item.source : item.tag ? this.language.instant('TYPES.EXTERNAL-DATASET-TYPE.SOURCE:') + item.tag : 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.TAXONOMIES:
this.taxonomiesAutoCompleteConfiguration = {
filterFn: this.searchDatasetExternalTaxonomies.bind(this),
initialItems: () => this.searchDatasetExternalTaxonomies(''),
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.source ? this.language.instant('TYPES.EXTERNAL-DATASET-TYPE.SOURCE:') + item.source : item.tag ? this.language.instant('TYPES.EXTERNAL-DATASET-TYPE.SOURCE:') + item.tag : 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.LICENSES:
this.licensesAutoCompleteConfiguration = {
filterFn: this.searchDatasetExternalLicences.bind(this),
initialItems: () => this.searchDatasetExternalLicences(''),
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.source ? this.language.instant('TYPES.EXTERNAL-DATASET-TYPE.SOURCE:') + item.source : item.tag ? this.language.instant('TYPES.EXTERNAL-DATASET-TYPE.SOURCE:') + item.tag : 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.PUBLICATIONS:
this.publicationsAutoCompleteConfiguration = {
filterFn: this.searchDatasetExternalPublications.bind(this),
initialItems: () => this.searchDatasetExternalPublications(''),
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.source ? this.language.instant('TYPES.EXTERNAL-DATASET-TYPE.SOURCE:') + item.source : item.tag ? this.language.instant('TYPES.EXTERNAL-DATASET-TYPE.SOURCE:') + item.tag : 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.REGISTRIES:
this.registriesAutoCompleteConfiguration = {
filterFn: this.searchDatasetExternalRegistries.bind(this),
initialItems: () => this.searchDatasetExternalRegistries(''),
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.source ? this.language.instant('TYPES.EXTERNAL-DATASET-TYPE.SOURCE:') + item.source : item.tag ? this.language.instant('TYPES.EXTERNAL-DATASET-TYPE.SOURCE:') + item.tag : 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.SERVICES:
this.servicesAutoCompleteConfiguration = {
filterFn: this.searchDatasetExternalServices.bind(this),
initialItems: () => this.searchDatasetExternalServices(''),
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.source ? this.language.instant('TYPES.EXTERNAL-DATASET-TYPE.SOURCE:') + item.source : item.tag ? this.language.instant('TYPES.EXTERNAL-DATASET-TYPE.SOURCE:') + item.tag : 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.RESEARCHERS:
this.researchersAutoCompleteConfiguration = {
filterFn: this.filterResearchers.bind(this),
initialItems: (excludedItems: any[]) => this.filterResearchers('').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.ORGANIZATIONS:
this.organisationsAutoCompleteConfiguration = {
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.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.CURRENCY:
this.currencyAutoCompleteConfiguration = {
filterFn: this.searchCurrency.bind(this),
initialItems: () => this.searchCurrency(''),
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 '' } },
valueAssign: (item) => { try { return typeof (item) == 'string' ? item : JSON.stringify(item) } catch { return '' } }
};
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.INTERNAL_DMP_ENTRIES_RESEARCHERS:
this.makeAutocompleteConfiguration(this.searchResearchers.bind(this), "name", "tag");
break;
case DescriptionTemplateFieldType.INTERNAL_DMP_ENTRIES_DATASETS:
this.makeAutocompleteConfiguration(this.searchDatasets.bind(this), "label");
break;
case DescriptionTemplateFieldType.INTERNAL_DMP_ENTRIES_DMPS:
this.makeAutocompleteConfiguration(this.searchDmps.bind(this), "label");
break;
}
// this.form = this.visibilityRulesService.getFormGroup(this.field.id);
this.propertiesFormGroup.get(this.field.id).get('value').valueChanges
.pipe(
takeUntil(this._destroyed),
distinctUntilChanged()
)
.subscribe(item => {
// if (this.field?.data?.fieldType === DescriptionTemplateFieldType.ComboBox && this.form.get('data').value.type === DatasetProfileComboBoxType.WordList && this.form.get('data').value.multiList) {
// item.forEach(element => {
// this.visibilityRulesService.updateValueAndVisibility(this.field?.id, element);
// });
// } else {
this.visibilityRulesService.updateValueAndVisibility(this.field?.id, item);
// }
});
}
// _optionRemove(event) {
// const array = JSON.parse(this.propertiesFormGroup.get(this.field.id).get('value').value);
// if (array) {
// const index = array.map(x => x.id).indexOf(event.id);
// if (index >= 0) {
// array.splice(index, 1);
// }
// this.propertiesFormGroup.get(this.field.id).get('value').patchValue(JSON.stringify(array));
// }
// }
searchFromAutocomplete(query: string) {
if (this.autocompleteOptions) {
const autocompleteRequestItem: RequestItem<DatasetExternalAutocompleteOptionsCriteria> = new RequestItem();
autocompleteRequestItem.criteria = new DatasetExternalAutocompleteOptionsCriteria();
//TODO: refactor this
//autocompleteRequestItem.criteria.autoCompleteSingleDataList = this.autocompleteOptions;
autocompleteRequestItem.criteria.like = query;
return this.datasetExternalAutocompleteService.queryApi(autocompleteRequestItem);
}
else {
const autocompleteRequestItem: RequestItem<DatasetExternalAutocompleteCriteria> = new RequestItem();
autocompleteRequestItem.criteria = new DatasetExternalAutocompleteCriteria();
let parseIdArray: string[] = this.field?.id.split('_');
if (parseIdArray.length > 1) {
autocompleteRequestItem.criteria.fieldID = parseIdArray[parseIdArray.length - 1];
} else {
autocompleteRequestItem.criteria.fieldID = this.field?.id;
}
if (typeof this.datasetProfileId === 'string') {
autocompleteRequestItem.criteria.profileID = this.datasetProfileId;
}
else if (this.datasetProfileId != null) {
autocompleteRequestItem.criteria.profileID = this.datasetProfileId.id;
}
autocompleteRequestItem.criteria.like = query;
return this.datasetExternalAutocompleteService.queryAutocomplete(autocompleteRequestItem);
}
}
searchResearchers(query: string) {
const reasearcherAutocompleteRequestItem: RequestItem<ResearcherCriteria> = new RequestItem();
reasearcherAutocompleteRequestItem.criteria = new ResearcherCriteria;
reasearcherAutocompleteRequestItem.criteria.name = query;
return this.externalSourcesService.searchDMPResearchers(reasearcherAutocompleteRequestItem);
}
searchDatasets(query: string) {
let fields: Array<string> = new Array();
const datasetsAutocompleteRequestItem: DataTableRequest<DatasetCriteria> = new DataTableRequest(0, 25, { fields: fields });
datasetsAutocompleteRequestItem.criteria = new DatasetCriteria();
datasetsAutocompleteRequestItem.criteria.like = query;
return this.datasetService.getPaged(datasetsAutocompleteRequestItem).pipe(map(item => item.data));
}
searchDmps(query: string) {
let fields: Array<string> = new Array();
const dmpsAutocompleteRequestItem: DataTableRequest<DmpCriteria> = new DataTableRequest(0, 25, { fields: fields });
dmpsAutocompleteRequestItem.criteria = new DmpCriteria();
dmpsAutocompleteRequestItem.criteria.like = query;
return this.dmpService.getPaged(dmpsAutocompleteRequestItem).pipe(map(item => item.data));
}
makeAutocompleteConfiguration(myfunc: Function, title: string, subtitle?: string): void {
if (!((this.field.data as DescriptionTemplateAutoCompleteData).multiAutoComplete)) {
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 '' } }
}
}
}
searchDatasetExternalDatasets(query: string): Observable<Reference[]> {
// const requestItem: RequestItem<ExternalDatasetCriteria> = new RequestItem();
// requestItem.criteria = new ExternalDatasetCriteria();
// requestItem.criteria.like = query;
// requestItem.criteria.type = '';
// //return this.externalSourcesService.searchDatasetSExternalDatasetservice(requestItem);
// return this.externalSourcesService.listExternal(ReferenceType.Datasets, requestItem.criteria.like, requestItem.criteria.type);
const lookup = new ReferenceSearchLookup();
lookup.like = query;
lookup.key = '';
lookup.type = ReferenceType.Datasets;
return this.referenceService.search(lookup);
}
searchDatasetExternalDataRepositories(query: string): Observable<Reference[]> {
// const requestItem: RequestItem<DataRepositoryCriteria> = new RequestItem();
// requestItem.criteria = new DataRepositoryCriteria();
// requestItem.criteria.like = query;
// requestItem.criteria.type = '';
// return this.externalSourcesService.listExternal(ReferenceType.DataRepositories, requestItem.criteria.like, requestItem.criteria.type);
const lookup = new ReferenceSearchLookup();
lookup.like = query;
lookup.key = '';
lookup.type = ReferenceType.DataRepositories;
return this.referenceService.search(lookup);
}
searchDatasetExternalPubRepositories(query: string): Observable<Reference[]> {
// const requestItem: RequestItem<DataRepositoryCriteria> = new RequestItem();
// requestItem.criteria = new DataRepositoryCriteria();
// requestItem.criteria.like = query;
// requestItem.criteria.type = '';
// return this.externalSourcesService.listExternal(ReferenceType.PubRepositories, requestItem.criteria.like, requestItem.criteria.type);
const lookup = new ReferenceSearchLookup();
lookup.like = query;
lookup.key = '';
lookup.type = ReferenceType.PubRepositories;
return this.referenceService.search(lookup);
}
searchDatasetExternalJournalRepositories(query: string): Observable<Reference[]> {
// const requestItem: RequestItem<DataRepositoryCriteria> = new RequestItem();
// requestItem.criteria = new DataRepositoryCriteria();
// requestItem.criteria.like = query;
// requestItem.criteria.type = '';
// return this.externalSourcesService.listExternal(ReferenceType.Journals, requestItem.criteria.like, requestItem.criteria.type);
const lookup = new ReferenceSearchLookup();
lookup.like = query;
lookup.key = '';
lookup.type = ReferenceType.Journals;
return this.referenceService.search(lookup);
}
searchDatasetExternalTaxonomies(query: string): Observable<FetcherReference[]> {
const requestItem: RequestItem<TaxonomyCriteria> = new RequestItem();
requestItem.criteria = new TaxonomyCriteria();
requestItem.criteria.like = query;
requestItem.criteria.type = '';
return this.externalSourcesService.listExternal(ReferenceType.Taxonomies, requestItem.criteria.like, requestItem.criteria.type);
}
searchDatasetExternalLicences(query: string): Observable<Reference[]> {
// const requestItem: RequestItem<LicenseCriteria> = new RequestItem();
// requestItem.criteria = new LicenseCriteria();
// requestItem.criteria.like = query;
// requestItem.criteria.type = '';
// return this.externalSourcesService.listExternal(ReferenceType.Licenses, requestItem.criteria.like, requestItem.criteria.type);
const lookup = new ReferenceSearchLookup();
lookup.like = query;
lookup.key = '';
lookup.type = ReferenceType.Licenses;
return this.referenceService.search(lookup);
}
searchDatasetExternalPublications(query: string): Observable<Reference[]> {
// const requestItem: RequestItem<PublicationCriteria> = new RequestItem();
// requestItem.criteria = new PublicationCriteria();
// requestItem.criteria.like = query;
// requestItem.criteria.type = '';
//return this.externalSourcesService.listExternal(ReferenceType.Publications, requestItem.criteria.like, requestItem.criteria.type);
const lookup = new ReferenceSearchLookup();
lookup.like = query;
lookup.key = '';
lookup.type = ReferenceType.Publications;
return this.referenceService.search(lookup);
}
searchDatasetExternalRegistries(query: string): Observable<FetcherReference[]> {
const requestItem: RequestItem<RegistryCriteria> = new RequestItem();
requestItem.criteria = new RegistryCriteria();
requestItem.criteria.like = query;
requestItem.criteria.type = '';
return this.externalSourcesService.listExternal(ReferenceType.Registries, requestItem.criteria.like, requestItem.criteria.type);
}
searchDatasetExternalServices(query: string): Observable<FetcherReference[]> {
const requestItem: RequestItem<ServiceCriteria> = new RequestItem();
requestItem.criteria = new ServiceCriteria();
requestItem.criteria.like = query;
requestItem.criteria.type = '';
return this.externalSourcesService.listExternal(ReferenceType.Services, requestItem.criteria.like, requestItem.criteria.type);
}
searchDatasetTags(query: string): Observable<ExternalSourceItemModel[]> {
const requestItem: RequestItem<TagCriteria> = new RequestItem();
requestItem.criteria = new TagCriteria();
requestItem.criteria.like = query;
requestItem.criteria.type = '';
return this.externalSourcesService.searchDatasetTags(requestItem);
}
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<ExternalSourceItemModel[]> {
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;
}
}
filterOrganisations(value: string): Observable<Reference[]> {
//return this.externalSourcesService.searchDMPOrganizations(value);
//return this.externalSourcesService.listExternal(ReferenceType.Organizations, value, '');
const lookup = new ReferenceSearchLookup();
lookup.like = value;
lookup.key = '';
lookup.type = ReferenceType.Organizations;
return this.referenceService.search(lookup);
}
filterResearchers(value: string): Observable<Reference[]> {
//return this.externalSourcesService.searchDMPResearchers({ criteria: { name: value, like: null } });
//return this.externalSourcesService.listExternal(ReferenceType.Researcher, value, '');
const lookup = new ReferenceSearchLookup();
lookup.like = value;
lookup.key = '';
lookup.type = ReferenceType.Researcher;
return this.referenceService.search(lookup);
}
getDatasetIdControl(name: string): UntypedFormControl {
return this.propertiesFormGroup.get(this.field.id).get(name) as UntypedFormControl;
}
searchCurrency(query: string): Observable<LocalFetchModel[]> {
return this.currencyService.get(query);
}
validateId() {
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() {
//TODO: refactor this
// 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) {
//TODO: refactor this
// 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) {
//TODO: refactor this
// 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() {
//TODO: refactor this
// this.filesToUpload = null;
// this.form.value.value = null;
// this.form.get("value").patchValue(null);
}
typesToString() {
return (this.field.data as DescriptionTemplateUploadData).types.map(type => type.value).toString();
}
download(): void {
//TODO: refactor this
// 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.fileUtils.getFilenameFromContentDispositionHeader(response.headers.get('Content-Disposition'));
// FileSaver.saveAs(blob, filename);
// });
}
}

View File

@ -0,0 +1,176 @@
<div class="dynamic-form-section row" [id]="section.id">
<mat-accordion class="col-12">
<mat-expansion-panel class="row expansion-panel toc-section-header" [id]="pathName" [(expanded)]="panelExpanded">
<!-- TODO: uncomment -->
<!-- <mat-expansion-panel-header>
<mat-panel-title>
<h6 class='panel-title'>{{tocentry? tocentry.numbering :form.get('numbering').value}}. {{section.title}}</h6>
</mat-panel-title>
</mat-expansion-panel-header>-->
<mat-panel-description class="col-12">
<h6 class='panel-desc' *ngIf="section.description" [innerHTML]="section.description"></h6>
</mat-panel-description>
<ng-container *ngIf="!tocentry else tocentryCase">
<div *ngFor="let fieldSet of section.fieldSets; let i = index;" class="col-12">
<!-- <div class="row" *ngIf="(this.visibilityRulesService.isVisibleMap[fieldSet.id] ?? true) && this.visibilityRulesService.scanIfChildsOfCompositeFieldHasVisibleItems(fieldSet.id)"> -->
<div class="row" *ngIf="(this.visibilityRulesService.isVisibleMap[fieldSet.id] ?? true) && this.visibilityRulesService.scanIfChildsOfCompositeFieldHasVisibleItems(fieldSet.id)">
<div class="col-12">
<div class="row">
<app-form-field-set class="align-self-center col" [propertiesFormGroup]="propertiesFormGroup" [fieldSet]="fieldSet" [visibilityRulesService]="visibilityRulesService" [datasetProfileId]="datasetProfileId"
[isChild]="false"
></app-form-field-set>
<!-- [showDelete]="(compositeFieldFormGroup.get('multiplicityItems').length) > 0" TODO: add this above -->
</div>
</div>
<div *ngIf="fieldSet" class="col-12">
<div class="row">
<!-- <div class="col-12" *ngFor="let multipleCompositeFieldFormGroup of compositeFieldFormGroup.get('multiplicityItems')['controls']; let j = index">
<div class="row">
<app-form-field-set class=" align-self-center col" [form]="multipleCompositeFieldFormGroup" [visibilityRulesService]="visibilityRulesService" [datasetProfileId]="datasetProfileId"
[isChild]="true" [showDelete]="true"></app-form-field-set>
</div>
</div>
<div *ngIf="(compositeFieldFormGroup.get('multiplicity').value.max - 1) > (compositeFieldFormGroup.get('multiplicityItems').length)"
class="col-12 mt-1 ml-0 mr-0 addOneFieldButton">
<span class="d-inline-flex align-items-center" [ngClass]="compositeFieldFormGroup.disabled ? '' : 'pointer'" (click)="addMultipleField(i)">
<button mat-icon-button color="primary" [disabled]="compositeFieldFormGroup.disabled">
<mat-icon>add_circle</mat-icon>
</button>
<span class="mt-1" *ngIf="compositeFieldFormGroup.get('multiplicity').value.placeholder">{{compositeFieldFormGroup.get('multiplicity').value.placeholder}}</span>
<span class="mt-1" *ngIf="!compositeFieldFormGroup.get('multiplicity').value.placeholder">{{('DATASET-PROFILE-EDITOR.STEPS.FORM.COMPOSITE-FIELD.FIELDS.MULTIPLICITY-ADD-ONE-FIELD' + (compositeFieldFormGroup.get('multiplicity').value.tableView?'-TABLEVIEW':'')) | translate}}</span>
</span>
</div> -->
<div *ngIf="fieldSet.hasCommentField" class="col-12">
<rich-text-editor-component [parentFormGroup]="propertiesFormGroup.get('commentFieldValue' + fieldSet.id)" [controlName]="'value'"
[id]="'editor1'" [placeholder]="'DATASET-PROFILE-EDITOR.STEPS.FORM.COMPOSITE-FIELD.FIELDS.COMMENT-PLACEHOLDER' | translate"
[wrapperClasses]="'mb-2'" [editable]="!propertiesFormGroup.get('commentFieldValue' + fieldSet.id).disabled">
</rich-text-editor-component>
</div>
</div>
</div>
</div>
</div>
<div *ngIf="section.sections" class="col-12">
<div *ngFor="let section of section.sections; let j = index;" class="row">
<app-form-section class="col-12" [section]="section" [propertiesFormGroup]="propertiesFormGroup" [path]="path+'.'+(j+1)" [pathName]="pathName+'.sections.'+j" [linkToScroll]="subsectionLinkToScroll"
[visibilityRulesService]="visibilityRulesService" [datasetProfileId]="datasetProfileId"></app-form-section>
</div>
</div>
</ng-container>
</mat-expansion-panel>
</mat-accordion>
</div>
<!-- WORKING WITH TOCENTRIES -->
<ng-template #tocentryCase>
<ng-container [ngSwitch]="tocentry.subEntriesType">
<ng-container *ngSwitchCase="tocentriesType.FieldSet">
<!-- FIELDSET CASE -->
<div *ngFor="let fieldsetEntry of tocentry.subEntries; let i = index;" class="col-12" [id]="TOCENTRY_ID_PREFIX+fieldsetEntry.id" (click)="onAskedToScroll(fieldsetEntry.id)">
<div class="row" *ngIf="((this.visibilityRulesService.isVisibleMap[fieldsetEntry.form.get('id').value] ?? true) && this.visibilityRulesService.scanIfChildsOfCompositeFieldHasVisibleItems(fieldsetEntry.form)) && !hiddenEntriesIds.includes(fieldsetEntry.id)">
<div class="col-12">
<div *ngIf="!fieldsetEntry.form.get('multiplicity').value.tableView" class="row">
<app-form-field-set [tocentry]="fieldsetEntry" class="align-self-center col" [form]="fieldsetEntry.form" [visibilityRulesService]="visibilityRulesService" [datasetProfileId]="datasetProfileId"
[isChild]="false" [showDelete]="(fieldsetEntry.form.get('multiplicityItems').length) > 0"></app-form-field-set>
</div>
<ng-container *ngIf="fieldsetEntry.form.get('multiplicity').value.tableView">
<div class="row">
<div class="col-12">
<app-form-composite-title class="row" [fieldSet]="fieldsetEntry"></app-form-composite-title>
</div>
</div>
<table class="table table-bordered" style="table-layout: fixed">
<tr>
<ng-container *ngFor="let fieldFormGroup of fieldsetEntry.form.get('fields')['controls']; let i = index;">
<th *ngIf="this.visibilityRulesService.isVisibleMap[fieldFormGroup.get('id').value] ?? true"
class="text-wrap">{{fieldFormGroup.get('data').value.label}}</th>
</ng-container>
<th class="actions"></th>
</tr>
<tr app-form-field-set [form]="fieldsetEntry.form" [datasetProfileId]="datasetProfileId" [visibilityRulesService]="visibilityRulesService"
[isChild]="false" [showDelete]="(fieldsetEntry.form.get('multiplicityItems').length) > 0" [tableRow]="true"></tr>
<ng-container *ngIf="fieldsetEntry.form && fieldsetEntry.form.get('multiplicity').value.tableView">
<tr app-form-field-set *ngFor="let multipleCompositeFieldFormGroup of fieldsetEntry.form.get('multiplicityItems')['controls']; let j = index"
[form]="multipleCompositeFieldFormGroup" [datasetProfileId]="datasetProfileId" [visibilityRulesService]="visibilityRulesService"
[isChild]="true" [showDelete]="true" [tableRow]="true"></tr>
</ng-container>
<tr *ngIf="(fieldsetEntry.form.get('multiplicity').value.max - 1) > (fieldsetEntry.form.get('multiplicityItems').length)">
<td [colSpan]="visibleControls(fieldsetEntry.form.get('fields')['controls']).length + 1" class="text-center">
<span class="d-inline-flex align-items-center" [ngClass]="fieldsetEntry.form.disabled ? '' : 'pointer'" (click)="addMultipleField(i)">
<button mat-icon-button color="primary" [disabled]="fieldsetEntry.form.disabled">
<mat-icon>add_circle</mat-icon>
</button>
<span class="mt-1" *ngIf="fieldsetEntry.form.get('multiplicity').value.placeholder">{{fieldsetEntry.form.get('multiplicity').value.placeholder}}</span>
<span class="mt-1" *ngIf="!fieldsetEntry.form.get('multiplicity').value.placeholder">{{('DATASET-PROFILE-EDITOR.STEPS.FORM.COMPOSITE-FIELD.FIELDS.MULTIPLICITY-ADD-ONE-FIELD' + (fieldsetEntry.form.get('multiplicity').value.tableView?'-TABLEVIEW':'')) | translate}}</span>
</span>
</td>
</tr>
</table>
</ng-container>
</div>
<div *ngIf="fieldsetEntry.form && !fieldsetEntry.form.get('multiplicity').value.tableView" class="col-12">
<div class="row">
<div class="col-12" *ngFor="let multipleCompositeFieldFormGroup of fieldsetEntry.form.get('multiplicityItems')['controls']; let j = index">
<div class="row">
<app-form-field-set class=" align-self-center col" [form]="multipleCompositeFieldFormGroup" [visibilityRulesService]="visibilityRulesService" [datasetProfileId]="datasetProfileId"
[isChild]="true" [showDelete]="true"></app-form-field-set>
</div>
</div>
<div *ngIf="(fieldsetEntry.form.get('multiplicity').value.max - 1) > (fieldsetEntry.form.get('multiplicityItems').length)"
class="col-12 mt-1 ml-0 mr-0 addOneFieldButton">
<span class="d-inline-flex align-items-center" [ngClass]="fieldsetEntry.form.disabled ? '' : 'pointer'" (click)="addMultipleField(i)">
<button mat-icon-button color="primary" [disabled]="fieldsetEntry.form.disabled">
<mat-icon>add_circle</mat-icon>
</button>
<span class="mt-1" *ngIf="fieldsetEntry.form.get('multiplicity').value.placeholder">{{fieldsetEntry.form.get('multiplicity').value.placeholder}}</span>
<span class="mt-1" *ngIf="!fieldsetEntry.form.get('multiplicity').value.placeholder">{{('DATASET-PROFILE-EDITOR.STEPS.FORM.COMPOSITE-FIELD.FIELDS.MULTIPLICITY-ADD-ONE-FIELD' + (fieldsetEntry.form.get('multiplicity').value.tableView?'-TABLEVIEW':'')) | translate}}</span>
</span>
</div>
<div *ngIf="fieldsetEntry.form.get('hasCommentField').value" class="col-12">
<rich-text-editor-component [parentFormGroup]="fieldsetEntry.form" [controlName]="'commentFieldValue'"
[id]="'editor1'" [placeholder]="'DATASET-PROFILE-EDITOR.STEPS.FORM.COMPOSITE-FIELD.FIELDS.COMMENT-PLACEHOLDER' | translate"
[wrapperClasses]="' mb-2'" [editable]="!fieldsetEntry.form.get('commentFieldValue').disabled">
</rich-text-editor-component>
</div>
</div>
</div>
</div>
</div>
</ng-container>
<ng-container *ngSwitchCase="tocentriesType.Section">
<!-- SECTION CASE -->
<div *ngIf="form.get('sections')" class="col-12"><!-- MAYBEE NOT NEEDED-->
<ng-container *ngFor="let sectionEntry of tocentry.subEntries; let j = index;">
<div class="row" *ngIf="!hiddenEntriesIds.includes(sectionEntry.id)">
<app-form-section [TOCENTRY_ID_PREFIX]="TOCENTRY_ID_PREFIX" class="col-12" [tocentry]="sectionEntry" [form]="sectionEntry.form" [path]="path+'.'+(j+1)" [pathName]="pathName+'.sections.'+j" [linkToScroll]="subsectionLinkToScroll"
[visibilityRulesService]="visibilityRulesService" [datasetProfileId]="datasetProfileId"
(askedToScroll)="onAskedToScroll($event)"
[hiddenEntriesIds]="hiddenEntriesIds"
></app-form-section>
</div>
</ng-container>
</div>
</ng-container>
</ng-container>
</ng-template>

View File

@ -0,0 +1,46 @@
.dynamic-form-section {
.expansion-panel {
// background-color: #eeeeee54;
background-color: white;
margin-top: 1em;
margin-bottom: 1em;
// margin-bottom: 1em;
}
.addOneFieldButton {
margin-top: -15px;
margin-left: -11px;
color: var(--primary-color);
}
.panel-title,
.panel-desc {
text-align: left;
font-weight: 700;
font-size: 1rem;
letter-spacing: 0px;
color: #212121;
opacity: 0.81;
margin-top: 1.625rem;
margin-bottom: 0.625rem;
}
.panel-desc {
// text-transform: capitalize;
text-transform: none;
font-weight: 400;
margin-top: .5rem;
}
}
.styleBorder {
border: 0.2em solid lightgray;
border-radius: 0.5em;
margin-bottom: 0.5em;
}
.mat-expansion-panel-header-description {
padding-bottom: 18px;
color: black;
}
::ng-deep .mat-expansion-panel-header {
height: auto !important;
min-height: 48px;
}

View File

@ -0,0 +1,266 @@
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
EventEmitter,
Input,
OnChanges,
OnInit,
Output,
SimpleChanges
} from '@angular/core';
import { UntypedFormGroup } from '@angular/forms';
import { DescriptionTemplateSection } from '@app/core/model/description-template/description-template';
import { BaseComponent } from '@common/base/base.component';
import { takeUntil } from 'rxjs/operators';
import { DescriptionFieldEditorModel } from '../../../description-editor.model';
import { ToCEntry } from '../../../table-of-contents/models/toc-entry';
import { ToCEntryType } from '../../../table-of-contents/models/toc-entry-type.enum';
import { LinkToScroll } from '../../../table-of-contents/table-of-contents.component';
import { VisibilityRulesService } from '../../visibility-rules/visibility-rules.service';
@Component({
selector: 'app-form-section',
templateUrl: './form-section.component.html',
styleUrls: ['./form-section.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class DescriptionFormSectionComponent extends BaseComponent implements OnInit, OnChanges {
@Input() section: DescriptionTemplateSection;
@Input() propertiesFormGroup: UntypedFormGroup;
@Input() visibilityRulesService: VisibilityRulesService;
// @Input() datasetProfileId: String;
// @Input() form: UntypedFormGroup;
@Input() tocentry: ToCEntry;
@Input() pathName: string;
@Input() path: string;
@Input() linkToScroll: LinkToScroll;
@Input() hiddenEntriesIds: string[] = [];
panelExpanded = true;
subsectionLinkToScroll: LinkToScroll;
@Output() askedToScroll = new EventEmitter<string>();
tocentriesType = ToCEntryType;
@Input() TOCENTRY_ID_PREFIX = "";
constructor(
private changeDetector: ChangeDetectorRef
) {
super();
}
ngOnInit() {
this.visibilityRulesService.getElementVisibilityMapObservable().pipe(takeUntil(this._destroyed)).subscribe(x => {
this.changeDetector.markForCheck();
});
// Set comment fields to properties
this.section.fieldSets.forEach(fieldSet => {
if (fieldSet.hasCommentField && !this.propertiesFormGroup.contains('commentFieldValue' + fieldSet.id)) {
const item: DescriptionFieldEditorModel = new DescriptionFieldEditorModel();
item.key = 'commentFieldValue' + fieldSet.id;
this.propertiesFormGroup.addControl('commentFieldValue' + fieldSet.id, item.buildForm());
}
});
//TODO uncomment
// if (this.tocentry) {//maybe not needed as well
// this.form = this.tocentry.form as UntypedFormGroup;
// }
// this.initMultipleFieldsVisibility();
}
ngOnChanges(changes: SimpleChanges) {
if (changes['linkToScroll']) {
if (changes['linkToScroll'].currentValue && changes['linkToScroll'].currentValue.section) {
if (this.pathName === changes['linkToScroll'].currentValue.section) {
this.panelExpanded = true;
} else if (changes['linkToScroll'].currentValue.section.includes(this.pathName)) {
this.subsectionLinkToScroll = changes['linkToScroll'].currentValue;
this.panelExpanded = true;
}
}
}
}
// setMultipleFieldVisibility(parentCompositeField, compositeField: DatasetDescriptionCompositeFieldEditorModel, idMappings: { old: string, new: string }[]) {
// // ** COMPOSITE FIELD IS SHOWN OR HIDDEN FROM ANOTHER ELEMENT
// const compositeFieldVisibilityDependencies = this.visibilityRulesService.getVisibilityDependency(parentCompositeField);
// if (compositeFieldVisibilityDependencies && compositeFieldVisibilityDependencies.length) {
// compositeFieldVisibilityDependencies.forEach(x => {
// const visRule: Rule = {
// targetField: compositeField.id,
// sourceField: x.sourceControlId,
// requiredValue: x.sourceControlValue
// }
// this.visibilityRulesService.addNewRule(visRule);
// });
// }
// // console.log('idMappings ', idMappings);
// parentCompositeField.fields.forEach(element => {
// // console.log(this.visibilityRulesService.getVisibilityDependency(element.id));
// const dependency = this.visibilityRulesService.getVisibilityDependency(element.id);
// if (dependency && dependency.length) {
// // * INNER VISIBILITY DEPENDENCIES
// // * IF INNER INPUT HIDES ANOTHER INNER INPUT
// const innerDependency = parentCompositeField.fields.reduce((innerD, currentElement) => {
// const innerDependecies = dependency.filter(d => d.sourceControlId === currentElement.id);
// return [...innerD, ...innerDependecies];
// }, []) as VisibilityRuleSource[];
// if (innerDependency.length) {
// //Build visibility source
// const updatedRules = innerDependency.map(x => {
// const newId = idMappings.find(y => y.old === x.sourceControlId);
// return { ...x, sourceControlId: newId.new };
// });
// // const visRule: VisibilityRule = {
// // targetControlId: idMappings.find(x => x.old === element.id).new,
// // sourceVisibilityRules: updatedRules
// // }
// const rules = updatedRules.map(x => {
// return {
// requiredValue: x.sourceControlValue,
// sourceField: x.sourceControlId,
// targetField: idMappings.find(l => l.old === element.id).new,
// type: ''
// } as Rule;
// });
// rules.forEach(rule => {
// this.visibilityRulesService.addNewRule(rule);
// })
// // this.visibilityRulesService.appendVisibilityRule(visRule);
// }
// }
// // * OUTER DEPENDENCIES
// // * IF INNER INPUT HIDES OUTER INPUTS
// const innerIds = idMappings.map(x => x.old) as string[];
// const outerTargets = this.visibilityRulesService.getVisibilityTargets(element.id).filter(x => !innerIds.includes(x));
// outerTargets.forEach(target => {
// const outerRules = (this.visibilityRulesService.getVisibilityDependency(target) as VisibilityRuleSource[]).filter(x => x.sourceControlId === element.id);
// const updatedRules = outerRules.map(x => {
// return { ...x, sourceControlId: idMappings.find(y => y.old === element.id).new };
// });
// // const visRule: VisibilityRule = {
// // targetControlId: target,
// // sourceVisibilityRules: updatedRules
// // }
// const rules = updatedRules.map(x => {
// return {
// requiredValue: x.sourceControlValue,
// sourceField: x.sourceControlId,
// targetField: target,
// type: ''
// } as Rule;
// })
// rules.forEach(rule => {
// this.visibilityRulesService.addNewRule(rule);
// })
// // this.visibilityRulesService.appendVisibilityRule(visRule);
// });
// // * IF INNER INPUT IS HIDDEN BY OUTER INPUT
// if (dependency && dependency.length) {
// const fieldsThatHideInnerElement = dependency.filter(x => !innerIds.includes(x.sourceControlId));
// if (fieldsThatHideInnerElement.length) {
// fieldsThatHideInnerElement.forEach(x => {
// const visRule: Rule = {
// targetField: idMappings.find(l => l.old === element.id).new,
// sourceField: x.sourceControlId,
// requiredValue: x.sourceControlValue
// }
// const shouldBeVisibile = this.visibilityRulesService.checkTargetVisibilityProvidedBySource(x.sourceControlId, element.id);
// this.visibilityRulesService.addNewRule(visRule, shouldBeVisibile);
// });
// }
// }
// // console.log(`for ${element.id} targets are`, outerTargets);
// });
// }
// initMultipleFieldsVisibility() {
// (this.form.get('compositeFields') as UntypedFormArray).controls.forEach(control => {
// let parentCompositeField = (control as UntypedFormGroup).getRawValue();
// if (parentCompositeField.multiplicityItems && parentCompositeField.multiplicityItems.length > 0) {
// parentCompositeField.multiplicityItems.forEach(compositeField => {
// let idMappings: { old: string, new: string }[] = [{ old: parentCompositeField.id, new: compositeField.id }];
// parentCompositeField.fields.forEach((field, index) => {
// idMappings.push({ old: field.id, new: compositeField.fields[index].id });
// });
// this.setMultipleFieldVisibility(parentCompositeField, compositeField, idMappings);
// })
// }
// });
// }
// addMultipleField(fieldsetIndex: number) {
// if (this.form.get('compositeFields').get('' + fieldsetIndex).disabled) {
// return;
// }
// const compositeFieldToBeCloned = (this.form.get('compositeFields').get('' + fieldsetIndex) as UntypedFormGroup).getRawValue();
// const multiplicityItemsArray = (<UntypedFormArray>(this.form.get('compositeFields').get('' + fieldsetIndex).get('multiplicityItems')));
// const ordinal = multiplicityItemsArray.length ? multiplicityItemsArray.controls.reduce((ordinal, currentControl) => {
// const currentOrdinal = currentControl.get('ordinal').value as number;
// if (currentOrdinal >= ordinal) {
// return currentOrdinal + 1;
// }
// return ordinal as number;
// }, 0) : 0;
// const idMappings = [] as { old: string, new: string }[];
// const compositeField: DatasetDescriptionCompositeFieldEditorModel = new DatasetDescriptionCompositeFieldEditorModel().cloneForMultiplicity(compositeFieldToBeCloned, ordinal, idMappings);
// this.setMultipleFieldVisibility(compositeFieldToBeCloned, compositeField, idMappings);
// multiplicityItemsArray.push(compositeField.buildForm());
// }
// deleteCompositeFieldFormGroup(compositeFildIndex: number) {
// const numberOfItems = this.form.get('compositeFields').get('' + compositeFildIndex).get('multiplicityItems').get('' + 0).get('fields').value.length;
// for (let i = 0; i < numberOfItems; i++) {
// const multiplicityItem = this.form.get('compositeFields').get('' + compositeFildIndex).get('multiplicityItems').get('' + 0).get('fields').get('' + i).value;
// this.form.get('compositeFields').get('' + compositeFildIndex).get('fields').get('' + i).patchValue(multiplicityItem);
// }
// (<UntypedFormArray>(this.form.get('compositeFields').get('' + compositeFildIndex).get('multiplicityItems'))).removeAt(0);
// }
// deleteMultipeFieldFromCompositeFormGroup(compositeFildIndex: number, fildIndex: number) {
// const multiplicityItemsArray = (<UntypedFormArray>(this.form.get('compositeFields').get('' + compositeFildIndex).get('multiplicityItems')));
// multiplicityItemsArray.removeAt(fildIndex);
// multiplicityItemsArray.controls.forEach((control, i) => {
// try {
// control.get('ordinal').setValue(i);
// } catch {
// throw 'Could not find ordinal';
// }
// });
// }
// onAskedToScroll(id: string) {
// this.panelExpanded = true;
// this.askedToScroll.emit(id);
// }
// visibleControls(controls: any[]) {
// return controls.filter(control => this.visibilityRulesService.isVisibleMap[control.get('id').value));
// }
}

View File

@ -0,0 +1,54 @@

<div *ngIf="datasetDescription" class="col-12 intro" [innerHTML]="datasetDescription"></div>
<form *ngIf="descriptionTemplate && propertiesFormGroup" novalidate class="col-12 card">
<div class="row">
<div class="dynamic-form-editor p-0 col-md-12">
<div id="form-container">
<ng-container *ngIf="!tocentries else toctemplate">
<div *ngFor="let page of descriptionTemplate?.definition?.pages; let z = index;">
<div *ngFor="let section of getSectionsOfPage(page.id); let i = index;">
<div class="row">
<app-form-section class="col-12" [section]="section" [path]="z+1"
[pathName]="'pages.'+z+'.sections.'+i" [propertiesFormGroup]="propertiesFormGroup" [visibilityRulesService]="visibilityRulesService"
[linkToScroll]="linkToScroll"></app-form-section>
</div>
</div>
</div>
</ng-container>
<!-- TOCENTRIES -->
<ng-template #toctemplate>
<!--FIRST LEVEL ALWAYS PAGE-->
<mat-accordion [multi]="true">
<ng-container *ngFor="let pageEntry of tocentries; let z = index;">
<mat-expansion-panel [expanded]="true" #expansionPanel *ngIf="!hiddenEntriesIds.includes(pageEntry.id)">
<mat-expansion-panel-header>
<mat-panel-title>
<h4 class="panel-title toc-page-header">
{{pageEntry.numbering}}. {{pageEntry.label |uppercase}}
</h4>
</mat-panel-title>
</mat-expansion-panel-header>
<!--
<h4 class="toc-page-header">
</h4> -->
<ng-container *ngFor="let sectionEntry of pageEntry.subEntries; let i = index;">
<div class="row" *ngIf="!hiddenEntriesIds.includes(sectionEntry.id)">
<app-form-section [TOCENTRY_ID_PREFIX]="TOCENTRY_ID_PREFIX" class="col-12" [tocentry]="sectionEntry" [path]="z+1"
[pathName]="'pages.'+z+'.sections.'+i" [datasetProfileId]="datasetProfileId" [visibilityRulesService]="visibilityRulesService"
[linkToScroll]="linkToScroll"
(askedToScroll)="onAskedToScroll(expansionPanel, $event)"
[hiddenEntriesIds]="hiddenEntriesIds"
></app-form-section>
</div>
</ng-container>
</mat-expansion-panel>
</ng-container>
</mat-accordion>
</ng-template>
</div>
</div>
</div>
</form>

View File

@ -0,0 +1,47 @@
@media (max-width: 768px) {
.dynamic-form-editor {
.form-container {
padding: 0px;
}
}
}
.form-container {
}
.intro {
text-align: left;
font-weight: 400;
letter-spacing: 0px;
color: #212121;
opacity: 1;
margin: 3rem 0rem 3rem 0rem;
}
.dynamic-form-editor {
mat-vertical-stepper {
background-color: #ffffff;
}
}
// ::ng-deep .mat-form-field-flex > .mat-form-field-infix {padding: 0.4em 0px !important;}
// ::ng-deep .mat-form-field-label-wrapper {
// top: -1.5em;
// }
// ::ng-deep
// .mat-form-field-appearance-outline.mat-form-field-can-float.mat-form-field-should-float
// .mat-form-field-label {
// transform: translateY(-1.1em) scale(0.75);
// width: 133.33333%;
// }
// ::ng-deep .mat-form-field-appearance-outline .mat-form-field-outline {
// background: #fafafa !important;
// }
// ::ng-deep .mat-step-header .mat-step-icon-selected,
// .mat-step-header .mat-step-icon-state-done,
// .mat-step-header .mat-step-icon-state-edit {
// background-color: var(--primary-color) !important;
// }

View File

@ -0,0 +1,371 @@
import { AfterViewInit, Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core';
import { UntypedFormGroup } from '@angular/forms';
import { MatExpansionPanel } from '@angular/material/expansion';
import { Rule } from '@app/core/model/dataset-profile-definition/rule';
import { BaseComponent } from '@common/base/base.component';
import { LinkToScroll } from '../table-of-contents/table-of-contents.component';
import { VisibilityRulesService } from './visibility-rules/visibility-rules.service';
import { DescriptionTemplate, DescriptionTemplateSection } from '@app/core/model/description-template/description-template';
import { Guid } from '@common/types/guid';
@Component({
selector: 'app-description-form',
templateUrl: './description-form.component.html',
styleUrls: ['./description-form.component.scss']
})
export class DescriptionFormComponent extends BaseComponent implements OnInit, AfterViewInit, OnChanges {
@Input() propertiesFormGroup: UntypedFormGroup;
@Input() descriptionTemplate: DescriptionTemplate;
@Input() visibilityRulesService: VisibilityRulesService;
// @ViewChild('stepper', { static: false }) stepper: MatStepper;
@Input() path: string;
@Input() visibilityRules: Rule[] = [];
@Input() datasetDescription: String;
@Input() linkToScroll: LinkToScroll;
@Output() formChanged: EventEmitter<any> = new EventEmitter();
@Output() fieldsetFocusChange: EventEmitter<string> = new EventEmitter<string>();
// tocentries: ToCEntry[];
@Input() TOCENTRY_ID_PREFIX = "";
@Output() visibilityRulesInstance = new EventEmitter<VisibilityRulesService>();
// public hiddenEntriesIds: string[] = [];
constructor(
) {
super();
}
ngOnInit() {
this.init();
}
ngOnChanges(changes: SimpleChanges) {
this.init();
// When the form is changed set stepper index to 0.
// if (this.stepper && changes['form'] && !changes['form'].isFirstChange()) {
// this.stepper.selectedIndex = 0;
// } else if (this.stepper && changes['linkToScroll'] && changes['linkToScroll'].currentValue) {
// if (changes['linkToScroll'].currentValue.page >= 0) {
// this.stepper.selectedIndex = changes['linkToScroll'].currentValue.page;
// }
// }
}
ngAfterViewInit() {
}
getSectionsOfPage(pageId: string) : DescriptionTemplateSection[]{ //TODO: change that to something more performant since its used at html page.
return this.descriptionTemplate.definition.sections.filter(x => x.page == pageId);
}
init() {
// this.tocentries = this.getTocEntries();
// const rules_to_append = this._enrichWithMultiplicityRules(this.tocentries);
// this.visibilityRulesService.buildVisibilityRules([...this.visibilityRules, ...rules_to_append], this.form);
// if (this.form) {
// this.form.valueChanges
// .pipe(takeUntil(this._destroyed))
// .subscribe(val => {
// this.formChanged.emit(val);
// });
// }
// this.visibilityRulesInstance.emit(this.visibilityRulesService);
// this.hiddenEntriesIds = this._findHiddenEntries(this.tocentries);
// this.visibilityRulesService.visibilityChange
// .pipe(
// takeUntil(this._destroyed),
// debounceTime(100)
// )
// .subscribe(_ => {
// this.hiddenEntriesIds = this._findHiddenEntries(this.tocentries);
// })
}
onAskedToScroll(panel: MatExpansionPanel, id?: string) {
panel.open();
this.fieldsetFocusChange.emit(id);
}
// private _enrichWithMultiplicityRules(tocentries: ToCEntry[]): Rule[] {
// if (tocentries) {
// return tocentries.map(entry => {
// if (entry.type === ToCEntryType.Field) return []; // * TODO Me to tora implementation den tha ftasei pote edo
// if (entry.type === ToCEntryType.FieldSet) {
// // if(multiplicity: )
// try {
// // TODO OTAN KANW HIDE MULTIPLE PEDIO TOTE STO ON SHOW HANO TA VALUES (AUTO MPOREI NA EINAI KAI LEGIT) ('NA DOUME AN ONTOS DIAGRAFONTAI I APLA DEN TA DEIXNOUME')
// // * UPDATE KANEI DESTROY TO COMPONENT H NGIF . PITHANOTATA NA XREIASTEI NA TO KANOUME HIDDEN AN THELOUME KATI ALLO
// const multiplicity = entry.form.get('multiplicity').value;
// if ((multiplicity.max > 1) && (multiplicity.min > 0) && (multiplicity.max >= multiplicity.min)) { // has valid multiplicity
// return this._createAndAppendVisibilityRule(entry);
// }
// } catch {
// }
// return [];
// }
// if (entry.subEntries) {
// return this._enrichWithMultiplicityRules(entry.subEntries);
// }
// })
// .reduce((r, c) => { return [...c, ...r] }, []);
// }
// return [];
// }
// private _createAndAppendVisibilityRule(entry: ToCEntry): Rule[] {
// const rules_to_append = [];
// if (entry && (entry.type === ToCEntryType.FieldSet)) {
// //childs that are either target or source
// const childIdsWithVisRules = (entry.form.get('fields') as UntypedFormArray).controls.reduce((all, s) => {
// const sval = s.value as Field;
// return this.visibilityRules.find(x => (x.targetField === sval.id) || (x.sourceField === sval.id)) ? [...all, sval] : all;
// }, []) as Field[];
// const innerCompositeFieldOriginalIds = (entry.form.get('fields') as UntypedFormArray).controls.map(x => x.get('id').value) as string[];
// //multiplicity items
// const multiplicityItemsValue = entry.form.get('multiplicityItems').value as CompositeField[];
// // ********* FIELDS OF FIELDSET ARE EITHER TARGETS OR SOURCES *****
// if (childIdsWithVisRules.length && multiplicityItemsValue && multiplicityItemsValue.length) {
// //check each multiplicity item composite field
// multiplicityItemsValue.forEach(mi => {
// const multiplicityCompositeFieldIds = mi.fields.map(x => x.id);
// const idMappings = multiplicityCompositeFieldIds.map(x => {
// return {
// original: innerCompositeFieldOriginalIds.find(l => x.includes(l)),
// multiplicityIdValue: x
// }
// }) as { original: string, multiplicityIdValue: string }[];
// //each field of mutliplicity item
// mi.fields.forEach(field => {
// //get original visibility rules (original field)
// //original id
// const original_id = childIdsWithVisRules.find(x => field.id.includes(x.id)).id;
// //get vis rules
// //as source
// const original_as_source = this.visibilityRules.filter(x => x.sourceField === original_id);
// const original_as_target = this.visibilityRules.filter(x => x.targetField === original_id);
// if (original_as_source.length) {
// //inner dependencies
// const innerDep = original_as_source.filter(x => innerCompositeFieldOriginalIds.includes(x.targetField));
// innerDep.forEach(x => {
// const newRule = { ...x, sourceField: field.id, targetField: idMappings.find(l => l.original === x.targetField).multiplicityIdValue } as Rule;
// rules_to_append.push(newRule);
// })
// //outer dependencies
// const outerDep = original_as_source.filter(x => !innerCompositeFieldOriginalIds.includes(x.targetField));
// outerDep.forEach(x => {
// const newRule = { ...x, sourceField: field.id };
// rules_to_append.push(newRule);
// })
// }
// if (original_as_target.length) {
// //inner dependencies
// const innerDep = original_as_target.filter(x => innerCompositeFieldOriginalIds.includes(x.sourceField));
// innerDep.forEach(x => {
// const newRule = { ...x, targetField: field.id, sourceField: idMappings.find(l => l.original === x.sourceField).multiplicityIdValue } as Rule;
// rules_to_append.push(newRule);
// })
// //outer dependencies
// const outerDep = original_as_target.filter(x => !innerCompositeFieldOriginalIds.includes(x.sourceField));
// outerDep.forEach(x => {
// const newRule = { ...x, targetField: field.id } as Rule;
// rules_to_append.push(newRule);
// })
// }
// })
// });
// }
// // ** FIELDSET ITSELF IS TARGET
// // ** source it can never be
// const compositeFieldAsTargetRules = this.visibilityRules.filter(x => x.targetField === entry.id);
// const idCompositeFieldMappings = multiplicityItemsValue.map(x => {
// return {
// originalValue: entry.id,
// newValue: x.id
// }
// }) as { originalValue: string, newValue: string }[];
// if (compositeFieldAsTargetRules.length) {
// compositeFieldAsTargetRules.forEach(x => {
// idCompositeFieldMappings.forEach(l => {
// const newRule = { ...x, targetField: l.newValue };
// rules_to_append.push(newRule);
// });
// });
// }
// }
// return rules_to_append;
// }
// private _buildRecursively(form: UntypedFormGroup, whatAmI: ToCEntryType): ToCEntry {
// if (!form) return null;
// switch (whatAmI) {
// case ToCEntryType.Section:
// const sections = form.get('sections') as UntypedFormArray;
// const fieldsets = form.get('compositeFields') as UntypedFormArray;
// const tempResult: ToCEntry[] = [];
// if (sections && sections.length) {
// sections.controls.forEach(section => {
// tempResult.push(this._buildRecursively(section as UntypedFormGroup, ToCEntryType.Section));
// });
// } else if (fieldsets && fieldsets.length) {
// fieldsets.controls.forEach(fieldset => {
// tempResult.push(this._buildRecursively(fieldset as UntypedFormGroup, ToCEntryType.FieldSet));
// });
// }
// return {
// // form: form,
// id: form.get('id').value,
// label: form.get('title').value,
// numbering: '',
// subEntries: tempResult,
// subEntriesType: sections && sections.length ? ToCEntryType.Section : ToCEntryType.FieldSet,
// type: ToCEntryType.Section,
// ordinal: form.get('ordinal').value
// }
// case ToCEntryType.FieldSet:
// return {
// // form: form,
// label: form.get('title').value,
// id: form.get('id').value,
// numbering: 's',
// subEntries: [],
// subEntriesType: ToCEntryType.Field,
// type: ToCEntryType.FieldSet,
// ordinal: form.get('ordinal').value
// }
// }
// }
// private _sortByOrdinal(tocentries: ToCEntry[]) {
// if (!tocentries || !tocentries.length) return;
// tocentries.sort(this._customCompare);
// tocentries.forEach(entry => {
// this._sortByOrdinal(entry.subEntries);
// });
// }
// private _customCompare(a, b) {
// return a.ordinal - b.ordinal;
// }
// private _calculateNumbering(tocentries: ToCEntry[], depth: number[] = []) {
// if (!tocentries || !tocentries.length) {
// return;
// }
// let prefixNumbering = depth.length ? depth.join('.') : '';
// if (depth.length) prefixNumbering = prefixNumbering + ".";
// tocentries.forEach((entry, i) => {
// entry.numbering = prefixNumbering + (i + 1);
// this._calculateNumbering(entry.subEntries, [...depth, i + 1])
// });
// }
// getTocEntries(): ToCEntry[] {
// if (!this.form) { return []; }
// const result: ToCEntry[] = [];
// //build parent pages
// (this.form.get('pages') as UntypedFormArray).controls.forEach((pageElement, i) => {
// result.push({
// id: i + 'id',
// label: pageElement.get('title').value,
// type: ToCEntryType.Page,
// // form: pageElement,
// numbering: (i + 1).toString(),
// subEntriesType: ToCEntryType.Section,
// subEntries: [],
// ordinal: pageElement.get('ordinal').value
// } as ToCEntry)
// });
// result.forEach((entry, i) => {
// const sections = entry.form.get('sections') as UntypedFormArray;
// sections.controls.forEach(section => {
// const tempResults = this._buildRecursively(section as UntypedFormGroup, ToCEntryType.Section);
// entry.subEntries.push(tempResults);
// });
// });
// this._sortByOrdinal(result);
// //calculate numbering
// this._calculateNumbering(result);
// return result;
// }
}

View File

@ -0,0 +1,41 @@
import { NgModule } from '@angular/core';
import { FormattingModule } from "@app/core/formatting.module";
import { FileService } from "@app/core/services/file/file.service";
import { AutoCompleteModule } from '@app/library/auto-complete/auto-complete.module';
import { RichTextEditorModule } from "@app/library/rich-text-editor/rich-text-editor.module";
import { CommonFormsModule } from '@common/forms/common-forms.module';
import { CommonUiModule } from '@common/ui/common-ui.module';
import { NgxDropzoneModule } from "ngx-dropzone";
import { DescriptionFormCompositeTitleComponent } from './components/form-composite-title/form-composite-title.component';
import { DescriptionFormFieldSetComponent } from './components/form-field-set/form-field-set.component';
import { DescriptionFormFieldComponent } from './components/form-field/form-field.component';
import { DescriptionFormSectionComponent } from './components/form-section/form-section.component';
import { DescriptionFormComponent } from './description-form.component';
import { VisibilityRulesService } from './visibility-rules/visibility-rules.service';
@NgModule({
imports: [
CommonUiModule,
CommonFormsModule,
AutoCompleteModule,
RichTextEditorModule,
NgxDropzoneModule,
FormattingModule
],
declarations: [
DescriptionFormComponent,
DescriptionFormSectionComponent,
DescriptionFormFieldSetComponent,
DescriptionFormFieldComponent,
DescriptionFormCompositeTitleComponent
],
exports: [
DescriptionFormComponent
],
providers: [
FileService
]
})
export class DescriptionFormModule { }

View File

@ -0,0 +1,4 @@
export class VisibilityRuleSource {
public sourceControlId: string;
public sourceControlValue: string;
}

View File

@ -0,0 +1,6 @@
import { VisibilityRuleSource } from "./visibility-rule-source";
export class VisibilityRule {
public targetControlId: string;
public sourceVisibilityRules: Array<VisibilityRuleSource>;
}

View File

@ -0,0 +1,47 @@
import { Rule } from "@app/core/model/dataset-profile-definition/rule";
import { VisibilityRule } from "./visibility-rule";
export class VisibilityRulesContext {
public rules: Array<VisibilityRule> = new Array();
constructor() { }
public getRulesFromKey(id: string): VisibilityRule {
for (let i = 0; i < this.rules.length; i++) {
if (id === this.rules[i].targetControlId) { return this.rules[i]; }
}
return null;
}
public buildVisibilityRuleContext(items: Array<Rule>) {
items.forEach(item => {
this.addToVisibilityRulesContext(item);
});
}
public addToVisibilityRulesContext(item: Rule): void {
for (let i = 0; i < this.rules.length; i++) {
if (this.rules[i].targetControlId === item.targetField) {
const newRule = { sourceControlId: item.sourceField, sourceControlValue: item.requiredValue };
const ruleExists = this.rules[i].sourceVisibilityRules.find(x => {
return Object.keys(x).reduce((exact, key) => {
if (!exact) return false;
return x[key] === newRule[key];
}, true);
})
if (!ruleExists) {
this.rules[i].sourceVisibilityRules.push(newRule);
}
return;
}
}
const newVisibilityRuleArray = [({ sourceControlId: item.sourceField, sourceControlValue: item.requiredValue })];
this.rules.push({ targetControlId: item.targetField, sourceVisibilityRules: newVisibilityRuleArray });
return;
}
}

View File

@ -0,0 +1,453 @@
import { ApplicationRef, Injectable, NgZone } from '@angular/core';
import { AbstractControl, UntypedFormArray, UntypedFormGroup } from '@angular/forms';
import { DatasetProfileFieldViewStyle } from '@app/core/common/enum/dataset-profile-field-view-style';
import { isNumeric } from '@app/utilities/enhancers/utils';
import { Observable, Subject } from 'rxjs';
import { VisibilityRule } from './models/visibility-rule';
import { VisibilityRuleSource } from './models/visibility-rule-source';
import { VisibilityRulesContext } from './models/visibility-rules-context';
import { Rule } from '@app/core/model/dataset-profile-definition/rule';
import { DescriptionTemplate, DescriptionTemplateSection } from '@app/core/model/description-template/description-template';
@Injectable()
export class VisibilityRulesService {
private readonly VISIBILITY_RULE_LOGIC: 'OR' | 'AND' = 'OR';
private readonly DEFAULTVISIBILITY = false;
private visibilityRuleContext: VisibilityRulesContext;
private form: AbstractControl;
public elementVisibilityMap = new MapWithDefault();
private elementVisibilityMapSubject = new Subject<Map<String, boolean>>();
private elementComputationalMap = new Map<String, Map<String, boolean>>(); /// keep saved the values of each form control validity value
private _changeMade$ = new Subject<void>();
get isVisibleMap(): MapWithDefault {
// console.log('isVisibleMap');
return this.elementVisibilityMap;
}; /// keep saved the values of each form control validity value
constructor(
public applicationReference: ApplicationRef,
public ngZone: NgZone
) {
}
getElementVisibilityMapObservable(): Observable<Map<String, boolean>> {
this.isVisibleMap
console.log('getElementVisibilityMapObservable: ');
return this.elementVisibilityMapSubject.asObservable();
}
public checkElementVisibility(id: string): boolean {
console.log('checkElementVisibility: ' + id);
return true;
// if (this.visibilityRuleContext.rules.filter(item => item.targetControlId === id).length === 0) { return true; }
// console.log(this.elementVisibilityMap.has(id) ? this.elementVisibilityMap.get(id) : false);
// return this.elementVisibilityMap.has(id) ? this.elementVisibilityMap.get(id) : false;
}
public buildVisibilityRules(item: Array<Rule>, form: AbstractControl) {
this.visibilityRuleContext = new VisibilityRulesContext();
this.visibilityRuleContext.buildVisibilityRuleContext(item || []);
this.form = form;
this.resetVisibilityRules();
}
public updateValueAndVisibility(id: string, value: any) {
console.log('updateValueAndVisibility: ' + id + ' value: ' + value);
const visibilityRules = this.visibilityRuleContext.rules.filter(item => item.sourceVisibilityRules.filter(source => source.sourceControlId === id).length > 0);
if (visibilityRules.length > 0) {
visibilityRules.forEach(item => this.evaluateVisibility(item, value, id));
this.elementVisibilityMapSubject.next(this.elementVisibilityMap);
}
}
private evaluateVisibility(visibilityRule: VisibilityRule, value: any, sourceId: string) {// source controlId is the same
console.log('evaluateVisibility: ' + visibilityRule + ' value: ' + value + ' sourceId: ' + sourceId);
const targetId = visibilityRule.targetControlId;
const visibilityMap = this.elementComputationalMap.get(targetId) ? this.elementComputationalMap.get(targetId) : new Map<String, boolean>();
if (value instanceof Array) {
const parsedSourceControlValues = visibilityRule.sourceVisibilityRules.map(e => this.parseValue(e.sourceControlValue));
const parsedValues = value.map(e => this.parseValue(e));
const isVisible = parsedValues.map(v => parsedSourceControlValues.includes(v)).reduce((acc, current) => acc || current, false);
// if(isVisible){
// this._emitChangesIfNeeded(visibilityRule.targetControlId, true);
// this.elementVisibilityMap.set(visibilityRule.targetControlId, true);
// return;
// }
visibilityMap.set(sourceId, isVisible);
} else {
const visibilityDependencySource = visibilityRule.sourceVisibilityRules.filter(x => x.sourceControlId === sourceId);
const shouldBeVisible = visibilityDependencySource.reduce((isVisible, x) => {
const shouldBeHidden = value !== null && (this.parseValue(value) !== this.parseValue(x.sourceControlValue));
return this.VISIBILITY_RULE_LOGIC === 'OR' ? (isVisible || !shouldBeHidden) : (isVisible && !shouldBeHidden);
// if(value !== null && )
}, this.VISIBILITY_RULE_LOGIC === 'AND');
visibilityMap.set(sourceId, shouldBeVisible);
}
this.elementComputationalMap.set(targetId, visibilityMap);// unnessecary
const isVisible = this._computeVisibility(targetId);
this._emitChangesIfNeeded(targetId, isVisible);
const previousVisibility = this.elementVisibilityMap.get(targetId);
this.elementVisibilityMap.set(targetId, isVisible);
if (!isVisible && previousVisibility !== isVisible) {
this.resetControlWithId(this.form, targetId);
}
// for (let i = 0; i < visibilityRule.sourceVisibilityRules.length; i++) {
// if (value != null && (this.parseValue(value) !== this.parseValue(visibilityRule.sourceVisibilityRules[i].sourceControlValue))) {
// this._emitChangesIfNeeded(visibilityRule.targetControlId, false);
// this.elementVisibilityMap.set(visibilityRule.targetControlId, false);
// this.resetControlWithId(this.form, visibilityRule.targetControlId);
// //this.updateValueAndVisibility(visibilityRule.targetControlId, null);
// // this.clearValues(targetPathKey);
// return;
// }
// }
// this._emitChangesIfNeeded(visibilityRule.targetControlId, true);
// this.elementVisibilityMap.set(visibilityRule.targetControlId, true);
// this.updateValueAndVisibility(visibilityRule.targetControlId, null);
}
private _computeVisibility(targetId: string): boolean {
console.log('_computeVisibility: ' + targetId);
const visibilityMap = this.elementComputationalMap.get(targetId);
const values = visibilityMap.values();
let currentVal = values.next();
let visibilityValues: boolean[] = [];
while (!currentVal.done) {
visibilityValues.push(currentVal.value);
currentVal = values.next();
}
if (visibilityValues.length) {
return visibilityValues.reduce((r, c) => {
if (this.VISIBILITY_RULE_LOGIC === 'OR') {
return r || c;
} else {
return r && c;
}
}, visibilityValues[0]);
}
return this.DEFAULTVISIBILITY;
}
private resetVisibilityRules() {
console.log('resetVisibilityRules: ');
this.elementVisibilityMap.clear();
this.elementVisibilityMap = new MapWithDefault();
this.elementComputationalMap.clear();
this.elementComputationalMap = new Map<String, Map<String, boolean>>();
this._populateComputationMap(); /// !IMPORTANT FOR THE AND LOGIC
this._changeMade$.next();
}
private _populateComputationMap(): void {
console.log('_populateComputationMap: ');
this.visibilityRuleContext.rules.forEach(rule => {
const targetId = rule.targetControlId;
const visibilityMap = this.elementComputationalMap.get(targetId) ? this.elementComputationalMap.get(targetId) : new Map<String, boolean>();
rule.sourceVisibilityRules.forEach(vr => {
visibilityMap.set(vr.sourceControlId, this.DEFAULTVISIBILITY);
});
this.elementComputationalMap.set(targetId, visibilityMap);
});
}
parseValue(value: any) {
if (typeof value === 'string') {
if (isNumeric(value)) { return value; }
else if (value === 'true') {
return true;
}
else if (value === 'false') {
return false;
}
else { return this.translate(value); }
} else { return value; }
}
search(path, obj, target) {
for (const k in obj) {
if (obj.hasOwnProperty(k)) {
if (obj[k] === target) {
return path + '.' + k;
} else if (typeof obj[k] === 'object') {
const result = this.search(path + '.' + k, obj[k], target);
if (result) {
return result;
}
}
}
}
return false;
}
scanIfChildsOfCompositeFieldHasVisibleItems(compositeFieldParent: UntypedFormGroup): boolean {
// console.log('scanIfChildsOfCompositeFieldHasVisibleItems: ' + compositeFieldParent);
//TODO: implement this
return true;
// let isVisible = false;
// (<UntypedFormArray>(compositeFieldParent.get('fields'))).controls.forEach(element => {
// if (this.checkElementVisibility(element.get('id').value)) {
// isVisible = true;
// }
// });
// return isVisible;
}
private translate(item: any) {
try {
return JSON.parse(item).value;
} catch (error) {
return item;
}
}
private resetControlWithId(formControl: AbstractControl, id: string) {
console.log('resetControlWithId: ' + id);
//TODO: implement this
// if (formControl instanceof UntypedFormGroup) {
// if ((formControl as UntypedFormGroup).contains('id') && (formControl as UntypedFormGroup).contains('value') && (formControl as UntypedFormGroup).get('id').value === id) {
// this.resetFieldFormGroup(formControl);
// } if ((formControl as UntypedFormGroup).contains('id') && (formControl as UntypedFormGroup).contains('fields') && (formControl as UntypedFormGroup).get('id').value === id) {
// this.resetCompositeFieldFormGroup(formControl);
// } else {
// Object.keys(formControl.controls).forEach(item => {
// const control = formControl.get(item);
// this.resetControlWithId(control, id);
// });
// }
// } else if (formControl instanceof UntypedFormArray) {
// formControl.controls.forEach(item => {
// this.resetControlWithId(item, id);
// });
// }
}
private resetFieldFormGroup(formGroup: UntypedFormGroup) {
console.log('resetFieldFormGroup: ' + formGroup);
//TODO: implement this
// const renderStyle = formGroup.getRawValue().viewStyle.renderStyle;
// if (renderStyle === DatasetProfileFieldViewStyle.Validation || renderStyle === DatasetProfileFieldViewStyle.DatasetIdentifier) {
// formGroup.get('value').setValue({ identifier: '', type: '' });
// } else {
// formGroup.get('value').setValue(formGroup.get('defaultValue').value ? this.parseValue(formGroup.get('defaultValue').value.value) : undefined);
// }
}
private resetCompositeFieldFormGroup(formGroup: UntypedFormGroup) {
console.log('resetCompositeFieldFormGroup: ' + formGroup);
//TODO: implement this
// (formGroup.get('fields') as UntypedFormArray).controls.forEach((element: UntypedFormGroup) => {
// this.resetFieldFormGroup(element);
// });
// (formGroup.get('multiplicityItems') as UntypedFormArray).controls.splice(0);
}
private _emitChangesIfNeeded(id: string, valueToBeSet: boolean) {
if (this.elementVisibilityMap.has(id)) {
if (this.elementVisibilityMap.get(id) != valueToBeSet) {
this._changeMade$.next();
}
} else {
this._changeMade$.next();
}
}
public get visibilityChange() {
return this._changeMade$.asObservable();
}
public getVisibilityDependency(targetId: string): VisibilityRuleSource[] | null {
console.log('getVisibilityDependency: ' + targetId);
return this.visibilityRuleContext.rules.reduce((hasDependency, rule) => {
if (hasDependency) return hasDependency;
if (rule.targetControlId === targetId) {
return rule.sourceVisibilityRules;
}
return null;
}, null) as VisibilityRuleSource[];
}
public getVisibilityTargets(sourceId: string): string[] {
console.log('getVisibilityTargets: ' + sourceId);
return this.visibilityRuleContext.rules.filter(x => {
const result = x.sourceVisibilityRules.filter(y => y.sourceControlId === sourceId);
return result.length;
}).map(x => x.targetControlId);
}
// public appendVisibilityRule(rule: VisibilityRule): void{
// const existingTargetRule = this.visibilityRuleContext.rules.find( r => r.targetControlId === rule.targetControlId);
// if(existingTargetRule){
// rule.sourceVisibilityRules.forEach(svr =>{
// existingTargetRule.sourceVisibilityRules.push(svr);
// });
// }else{
// this.visibilityRuleContext.rules.push(rule);
// }
// }
//removes rule that has the specific id either as a source either as a target
public removeAllIdReferences(id: string): void {
console.log('removeAllIdReferences: ' + id);
// * Remove from visibility rues and visibility rules context
//remove as a target
const temp = this.visibilityRuleContext.rules.map((x, i) => (x.targetControlId === id) ? i : null);
const indexes = temp.filter(x => x !== null);
indexes.reverse().forEach(index => this.visibilityRuleContext.rules.splice(index, 1));
this.elementVisibilityMap.delete(id);
//remove as a source
const tbd = this.visibilityRuleContext.rules.reduce((to_be_deleted, rule, ruleIdx) => {
const idxs = rule.sourceVisibilityRules.map((x, i) => (x.sourceControlId === id) ? i : null).filter(x => x !== null);
idxs.reverse().forEach(index => rule.sourceVisibilityRules.splice(index, 1));
if (!rule.sourceVisibilityRules.length) {
to_be_deleted.push(ruleIdx);
}
return to_be_deleted
}, []);
//clean up empty
tbd.reverse().forEach(index => {
this.visibilityRuleContext.rules.splice(index, 1);
});
// * Remove from computational map
// as a target
if (this.elementComputationalMap.get(id)) {
this.elementComputationalMap.delete(id);
}
// as a source
const keyIterator = this.elementComputationalMap.keys();
let currentKey = keyIterator.next();
while (!currentKey.done) {
const currentVals = this.elementComputationalMap.get(currentKey.value);
currentVals.delete(id);
currentKey = keyIterator.next();
}
}
public addNewRule(rule: Rule, currentVisibility = this.DEFAULTVISIBILITY): void {
console.log('addNewRule: ' + rule + ' currentVisibility: ' + currentVisibility);
const targetId = rule.targetField;
const sourceId = rule.sourceField;
this.visibilityRuleContext.addToVisibilityRulesContext(rule);
let visibilityMap = this.elementComputationalMap.get(targetId);
if (!visibilityMap) {
visibilityMap = new Map<String, boolean>();
this.elementComputationalMap.set(targetId, visibilityMap);
}
visibilityMap.set(sourceId, currentVisibility);
const isVisible = this._computeVisibility(targetId);
this._emitChangesIfNeeded(targetId, isVisible);
this.elementVisibilityMap.set(targetId, isVisible);
this.elementVisibilityMapSubject.next(this.elementVisibilityMap);
}
/**
* Check what sourceId hides or shows the target field
* return true if no rule found
*/
public checkTargetVisibilityProvidedBySource(sourceId: string, targetId: string): boolean {
console.log('checkTargetVisibilityProvidedBySource: ' + sourceId + ' targetId: ' + targetId);
const computationalMap = this.elementComputationalMap.get(targetId);
if (computationalMap) {
return !!computationalMap.get(sourceId);
}
return true;
}
public getVisibilityRulesFromDescriptionTempalte(descriptionTemplate: DescriptionTemplate): Rule[] {
console.log('getVisibilityRulesFromDescriptionTempalte: ' + descriptionTemplate);
const result: Rule[] = this.getVisibilityRulesFromDescriptionTempalteSections(descriptionTemplate?.definition?.sections);
return result;
}
public getVisibilityRulesFromDescriptionTempalteSections(sections: DescriptionTemplateSection[]): Rule[] {
console.log('getVisibilityRulesFromDescriptionTempalteSections: ' + sections);
const result: Rule[] = [];
sections.forEach(section => {
if (section.sections != null) { result.push(...this.getVisibilityRulesFromDescriptionTempalteSections(section.sections)); };
section?.fieldSets?.forEach(fieldSet => {
fieldSet?.fields?.forEach(field => {
field.visibilityRules?.forEach(visibilityRule => {
result.push({
sourceField: field.id.toString(),
targetField: visibilityRule.target,
requiredValue: visibilityRule.value
})
});
});
});
});
return result;
}
}
class MapWithDefault extends Map<string, boolean> {
get(key) {
console.log('MapWithDefault');
if (!this.has(key)) {
this.set(key, true);
}
return super.get(key);
}
}

View File

@ -0,0 +1,5 @@
<div class="demo-progress-bar-container">
<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

@ -0,0 +1,211 @@
import {ChangeDetectorRef, Component, ElementRef, Input, OnChanges, OnInit, SimpleChanges} from '@angular/core';
import { AbstractControl, UntypedFormArray, UntypedFormControl, UntypedFormGroup } from '@angular/forms';
import { VisibilityRulesService } from '@app/ui/description/editor/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',
styleUrls: ['./form-progress-indication.component.scss']
})
export class FormProgressIndicationComponent extends BaseComponent implements OnInit, OnChanges {
@Input() formGroup: UntypedFormGroup;
@Input() isDmpEditor: boolean;
@Input() isDatasetEditor: boolean;
@Input() public progressValueAccuracy = 2;
determinateProgressValue: number;
progressSoFar: number;
total: number;
percent: number;
constructor(private visibilityRulesService: VisibilityRulesService) { super(); }
public value = 0;
ngOnInit() {
this.init();
}
ngOnChanges(changes: SimpleChanges) {
if(changes.formGroup) {
this.init();
}
}
init() {
setTimeout(() => {this.calculateValueForProgressbar();});
this.formGroup
.valueChanges
.pipe(takeUntil(this._destroyed))
.subscribe(control => {
setTimeout(() => {this.calculateValueForProgressbar();});
});
}
calculateValueForProgressbar() {
if (this.isDmpEditor) {
this.progressSoFar = this.countFormControlsValidForProgress(this.formGroup);
this.total = this.countFormControlsRequiredFieldsForTotal(this.formGroup);
} else if (this.isDatasetEditor) {
this.progressSoFar = this.countFormControlsValidForProgress(this.formGroup) + this.countFormControlsWithValueForProgress(this.formGroup);
this.total = this.countFormControlsRequiredFieldsForTotal(this.formGroup, true) + this.CountFormControlDepthLengthFotTotal(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 {
let valueCurent = 0;
if (formControl instanceof UntypedFormGroup) {
if (this.checkFormsIfIsFieldsAndVisible(formControl) && this.checkIfIsRequired((formControl as UntypedFormGroup))) {
if (this.hasValue(formControl))
valueCurent++;
}
if (this.chechFieldIfIsFieldSetAndVisible((formControl as UntypedFormGroup)) && this.checkIfIsRequired((formControl as UntypedFormGroup))) {
valueCurent = valueCurent + this.compositeFieldsGetChildsForProgress(formControl);
} else {
Object.keys(formControl.controls).forEach(item => {
const control = formControl.get(item);
valueCurent = valueCurent + this.countFormControlsWithValueForProgress(control);
});
}
} else if (formControl instanceof UntypedFormArray) {
formControl.controls.forEach(item => {
valueCurent = valueCurent + this.countFormControlsWithValueForProgress(item);
});
}
return valueCurent;
}
private hasValue(formGroup: UntypedFormGroup): boolean {
return formGroup.get('value').valid && formGroup.get('value').value != null && formGroup.get('value').value !== '' && (this.visibilityRulesService.isVisibleMap[formGroup.get('id').value] ?? true);
}
private compositeFieldsGetChildsForProgress(formGroup: UntypedFormGroup): number {
let valueCurent = 0;
if (this.visibilityRulesService.isVisibleMap[formGroup.get('id').value] ?? true) {
(formGroup.get('fields') as UntypedFormArray).controls.forEach((element: UntypedFormGroup) => {
valueCurent = valueCurent + this.countFormControlsWithValueForProgress(element);
});
(formGroup.get('multiplicityItems') as UntypedFormArray).controls.forEach((element: UntypedFormGroup) => {
valueCurent = valueCurent + this.countFormControlsWithValueForProgress(element);
});
}
return valueCurent;
}
private checkIfIsRequired(formControl: UntypedFormGroup): boolean {
return !!(formControl.get('validationRequired') && formControl.get('validationRequired').value);
}
private checkFormsIfIsFieldsAndVisible(formControl: UntypedFormGroup): boolean {
if (formControl.contains('id') && formControl.contains('value')) {
return true;
}
return false;
}
private chechFieldIfIsFieldSetAndVisible(formControl: UntypedFormGroup): boolean {
if (formControl.contains('id') && formControl.contains('fields')) {
return true;
}
return false;
}
CountFormControlDepthLengthFotTotal(formControl: AbstractControl): number {
let valueCurent = 0;
if (formControl instanceof UntypedFormArray) {
formControl.controls.forEach(item => {
valueCurent = valueCurent + this.CountFormControlDepthLengthFotTotal(item);
});
} else if (formControl instanceof UntypedFormGroup) {
if ((formControl as UntypedFormGroup).contains('id') && (formControl as UntypedFormGroup).contains('value') && (this.visibilityRulesService.isVisibleMap[(formControl as UntypedFormGroup).get('id').value] ?? true) && this.checkIfIsRequired((formControl as UntypedFormGroup))) {
valueCurent++;
} else if ((formControl as UntypedFormGroup).contains('id') && (formControl as UntypedFormGroup).contains('fields')) {
valueCurent = valueCurent + this.compositeFieldsGetChildsForTotal(formControl);
} else {
Object.keys(formControl.controls).forEach(item => {
const control = formControl.get(item);
valueCurent = valueCurent + this.CountFormControlDepthLengthFotTotal(control);
});
}
}
return valueCurent;
}
private compositeFieldsGetChildsForTotal(formGroup: UntypedFormGroup): number {
let valueCurent = 0;
if (this.visibilityRulesService.isVisibleMap[formGroup.get('id').value] ?? true) {
(formGroup.get('fields') as UntypedFormArray).controls.forEach((element: UntypedFormGroup) => {
valueCurent = valueCurent + this.CountFormControlDepthLengthFotTotal(element);
});
(formGroup.get('multiplicityItems') as UntypedFormArray).controls.forEach((element: UntypedFormGroup) => {
valueCurent = valueCurent + this.CountFormControlDepthLengthFotTotal(element);
});
}
return valueCurent;
}
countFormControlsValidForProgress(formControl: AbstractControl): number {
let valueCurrent = 0;
if (formControl instanceof UntypedFormControl) {
if (this.controlRequired(formControl) && this.controlEnabled(formControl) && formControl.valid) {
valueCurrent++;
}
} else if (formControl instanceof UntypedFormGroup) {
Object.keys(formControl.controls).forEach(item => {
const control = formControl.get(item);
valueCurrent = valueCurrent + this.countFormControlsValidForProgress(control);
});
} else if (formControl instanceof UntypedFormArray) {
formControl.controls.forEach(item => {
valueCurrent = valueCurrent + this.countFormControlsValidForProgress(item);
});
}
return valueCurrent;
}
countFormControlsRequiredFieldsForTotal(formControl: AbstractControl, checkVisibility = false): number {
let valueCurrent = 0;
if (formControl instanceof UntypedFormControl) {
if (this.controlRequired(formControl) && this.controlEnabled(formControl)) {
valueCurrent++;
}
} else if (formControl instanceof UntypedFormGroup) {
if(!checkVisibility || (!formControl.get('id')?.value || (this.visibilityRulesService.isVisibleMap[formControl.get('id').value] ?? true))) {
Object.keys(formControl.controls).forEach(item => {
const control = formControl.get(item);
valueCurrent = valueCurrent + this.countFormControlsRequiredFieldsForTotal(control, checkVisibility);
});
}
} else if (formControl instanceof UntypedFormArray) {
formControl.controls.forEach(item => {
valueCurrent = valueCurrent + this.countFormControlsRequiredFieldsForTotal(item, checkVisibility);
});
}
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 }
}
isEditor(): boolean {
return this.isDmpEditor || this.isDatasetEditor;
}
}

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 DescriptionFormProgressIndicationModule { }

View File

@ -0,0 +1,6 @@
export enum ToCEntryType {
Page = 0,
Section = 1,
FieldSet = 2,
Field = 3
}

View File

@ -0,0 +1,14 @@
import { Guid } from "@common/types/guid";
import { ToCEntryType } from "./toc-entry-type.enum";
export interface ToCEntry {
id: string;
label: string;
subEntriesType: ToCEntryType;
subEntries: ToCEntry[];
type: ToCEntryType;
// form: AbstractControl;
numbering: string;
ordinal: number;
hidden?: boolean
}

View File

@ -0,0 +1,50 @@
<div *ngFor="let entry of tocentries; index as idx">
<!-- check if is visible -->
<ng-container *ngIf="!hiddenEntries.includes(entry.id)">
<!-- Is fieldset and has no visible inputs -->
<!-- <ng-container *ngIf="!(entry.type === tocEntryTypeEnum.FieldSet && !visibilityRulesService.scanIfChildsOfCompositeFieldHasVisibleItems(entry.form))"> TODO: add this bellow -->
<ng-container *ngIf="!(entry.type === tocEntryTypeEnum.FieldSet)">
<span class="table-entry"
(click)="toggleExpand(idx);navigateToFieldSet(entry, $event); onEntrySelected(entry)"
[ngStyle]="calculateStyle(entry)"
[ngClass]="calculateClass(entry)"
>
<span [class.text-danger]="showErrors && propertiesFormGroup.get('entry.id')?.invalid && ( entry.type === tocEntryTypeEnum.FieldSet || (entry.type !== tocEntryTypeEnum.FieldSet && !expandChildren[idx])) && invalidChildsVisible(entry) ">
{{entry.numbering}}. {{entry.label}}
</span>
<!-- <mat-icon style="transform: translateY(3px);" class="text-danger"
*ngIf="showErrors && entry.form.invalid && entry.type !== tocEntryTypeEnum.FieldSet && !expandChildren[idx]">
priority_high
</mat-icon> -->
<!-- <ng-container *ngIf="entry.subEntries && entry.subEntries.length && !expandChildren[idx]">
<small>
({{entry.subEntries.length}})
</small>
</ng-container> -->
</span>
<!-- <div class="table-entry-container">
</div> -->
<div class="internal-table">
<table-of-contents-internal
[tocentries]="entry.subEntries"
*ngIf="entry.subEntries && entry.subEntries.length && expandChildren[idx]"
(entrySelected)="onEntrySelected($event)"
[selected]="selected"
[TOCENTRY_ID_PREFIX]="TOCENTRY_ID_PREFIX"
[showErrors]="showErrors"
[hiddenEntries]="hiddenEntries"
[visibilityRulesService]="visibilityRulesService"
>
</table-of-contents-internal>
</div>
</ng-container>
</ng-container>
</div>

View File

@ -0,0 +1,33 @@
.internal-table{
margin-left: 1em;
padding-left: 0.2rem;
}
.table-entry{
text-overflow: ellipsis;
overflow: hidden;
padding-left: 0.2rem;
color: rgba(0, 0, 0, 0.54);
transition: color 100ms;
display: block;
span{
white-space: nowrap;
}
}
.table-entry:hover{
background-color: #ececec;
border-radius: 6px;
}
.selected {
color: #212121 !important;
font-weight: 700 !important;
opacity: 1 !important;
}
.section{
line-height: 1.7em;
}

View File

@ -0,0 +1,168 @@
import { Component, EventEmitter, Input, OnInit, Output, QueryList, SimpleChanges, ViewChildren } from '@angular/core';
import { UntypedFormGroup } from '@angular/forms';
import { VisibilityRulesService } from '@app/ui/description/editor/description-form/visibility-rules/visibility-rules.service';
import { Guid } from '@common/types/guid';
import { ToCEntry } from '../models/toc-entry';
import { ToCEntryType } from '../models/toc-entry-type.enum';
@Component({
selector: 'table-of-contents-internal',
styleUrls: ['./table-of-contents-internal.scss'],
templateUrl: './table-of-contents-internal.html'
})
export class TableOfContentsInternal implements OnInit {
@Input() tocentries: ToCEntry[] = null;
@Input() selected: ToCEntry = null;
// @Input() visibilityRules:Rule[] = [];
@Output() entrySelected = new EventEmitter<ToCEntry>();
@Input() propertiesFormGroup: UntypedFormGroup;
expandChildren: boolean[];
tocEntryTypeEnum = ToCEntryType;
@Input() TOCENTRY_ID_PREFIX = "";
@Input() showErrors: boolean = false;
@Input() hiddenEntries: string[] = [];
@Input() visibilityRulesService: VisibilityRulesService;
@ViewChildren(TableOfContentsInternal) internalTables: QueryList<TableOfContentsInternal>;
constructor() {
}
ngOnInit(): void {
// console.log('component created');
if (this.tocentries) {
this.expandChildren = this.tocentries.map(() => false);
if (this.selected) {
for (let i = 0; i < this.tocentries.length; i++) {
if (this._findTocEntryById(this.selected.id, this.tocentries[i].subEntries)) {
if (this.expandChildren) {
this.expandChildren[i] = true;
}
break;
}
}
}
}
}
ngOnChanges(changes: SimpleChanges) {
if (changes.selected && this.selected) {
for (let i = 0; i < this.tocentries.length; i++) {
if (this._findTocEntryById(this.selected.id, this.tocentries[i].subEntries)) {
if (this.expandChildren) {
this.expandChildren[i] = true;
}
break;
}
}
}
// if (!this.isActive && this.links && this.links.length > 0) {
// this.links.forEach(link => {
// link.selected = false;
// })
// this.links[0].selected = true;
// }
}
toggleExpand(index) {
this.expandChildren[index] = !this.expandChildren[index];
// console.log(this.expandChildren);
}
navigateToFieldSet(entry: ToCEntry, event) {
if (entry.type === ToCEntryType.FieldSet) {
const fieldSetId = entry.id;
const element = document.getElementById(this.TOCENTRY_ID_PREFIX + fieldSetId);
if (element) {
element.click();//open mat expansion panel
//scroll asyn in 200 ms so the expansion panel is expanded and the element coordinates are updated
setTimeout(() => {
const element = document.getElementById(this.TOCENTRY_ID_PREFIX + fieldSetId);
if (element) {
element.scrollIntoView({ behavior: 'smooth' });
}
}, 300);
}
}
}
onEntrySelected(entry: ToCEntry) {
this.entrySelected.emit(entry);
}
calculateStyle(entry: ToCEntry) {
const style = {};
style['font-size'] = entry.type === this.tocEntryTypeEnum.FieldSet ? '.9em' : '1em';
return style;
}
calculateClass(entry: ToCEntry) {
const myClass = {};
if (this.selected && entry.id === this.selected.id) {
myClass['selected'] = true;
}
if (entry.type != this.tocEntryTypeEnum.FieldSet) {
myClass['section'] = true;
}
return myClass;
}
private _findTocEntryById(id: string, tocentries: ToCEntry[]): ToCEntry {
if (!tocentries || !tocentries.length) {
return null;
}
let tocEntryFound = tocentries.find(entry => entry.id === id);
if (tocEntryFound) {
return tocEntryFound;
}
for (let entry of tocentries) {
const result = this._findTocEntryById(id, entry.subEntries);
if (result) {
tocEntryFound = result;
break;
}
}
return tocEntryFound ? tocEntryFound : null;
}
public invalidChildsVisible(entry: ToCEntry): boolean {
if (!entry || !this.visibilityRulesService) {
return false;
}
if (entry.type != this.tocEntryTypeEnum.FieldSet) {
return entry.subEntries.some(_ => this.invalidChildsVisible(_));
}
if (entry.type === this.tocEntryTypeEnum.FieldSet) {
const id = entry.id;
if (!(this.visibilityRulesService.isVisibleMap[id.toString()] ?? true)) {
return false;
}
// const fieldsArray = entry.form.get('fields') as UntypedFormArray;
const hasError = !entry.subEntries.every(field => {//every invalid field should be invisible
//TODO: check this
// if (field.invalid) {
// const id = field.id;
// const isVisible = this.visibilityRulesService.isVisibleMap[id);
// if (isVisible) {
// return false;
// }
// }
return true;
});
return hasError;
}
return false;
}
}

View File

@ -0,0 +1,24 @@
<div *ngIf="links?.length" class="docs-toc-container">
<div class="scroll-container">
<span *ngFor="let link of links; let i = index" (click)="toggle(link); goToStep(link)" class="docs-level-{{link.type}} docs-link mt-0" [class.docs-active]="link.active">
<span *ngIf="link.show" class="link-name"><span [class.selected]="link.selected && isActive">{{link.name}}</span></span>
</span>
</div>
</div>
<div *ngIf="tocentries" class="docs-toc-container">
<div class="scroll-container col-12 internal-table">
<table-of-contents-internal #internalTable [TOCENTRY_ID_PREFIX]="TOCENTRY_ID_PREFIX" [tocentries]="tocentries"
[showErrors]="showErrors"
(entrySelected)="onToCentrySelected($event)"
[selected]="tocentrySelected"
[hiddenEntries]="hiddenEntries"
[visibilityRulesService]="visibilityRulesService"
[propertiesFormGroup]="propertiesFormGroup"
>
</table-of-contents-internal>
</div>
</div>
{{hiddenEntries | json}}

View File

@ -0,0 +1,80 @@
.docs-toc-container {
width: 100%;
padding: 5px 0 10px 0px;
cursor: pointer;
// border-left: solid 4px #0c7489;
.scroll-container {
overflow-y: auto;
// calc(100vh - 250px)
// height: calc(100vh - 250px);
}
.docs-link {
color: rgba(0, 0, 0, 0.54);
// color: mat-color($app-blue-theme-foreground, secondary-text);
transition: color 100ms;
&:hover,
&.docs-active {
.link-name {
background-color: #ececec;
border-radius: 6px;
// color: #0c7489;
}
// color: mat-color($primary, if($is-dark-theme, 200, default));
}
}
}
.docs-toc-heading {
margin: 0;
padding: 0;
font-size: 13px;
font-weight: bold;
}
span {
line-height: 16px;
margin: 6px 0 0;
position: relative;
text-decoration: none;
display: block;
overflow: hidden;
color: #21212194;
font-weight: 400;
max-width: 290px;
min-width: 290px;
padding: 0rem .4rem;
span {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: inline-block;
width: 100%;
}
}
.selected {
color: #212121 !important;
font-weight: 700 !important;
opacity: 1 !important;
}
// .docs-level-mat-expansion-panel {
// margin-left: 12px;
// }
.docs-level-h5 {
margin-left: 24px;
}
// .internal-table-outer{
// padding-left: 1.1em;
// width: 100%;
// }
.internal-table{
max-width: 320px;
min-width: 320px;
}

View File

@ -0,0 +1,514 @@
import { DOCUMENT } from '@angular/common';
import { Component, EventEmitter, Inject, Input, OnChanges, OnInit, Output, SimpleChanges, ViewChild } from '@angular/core';
import { UntypedFormGroup } from '@angular/forms';
import { DescriptionTemplate, DescriptionTemplateField, DescriptionTemplateFieldSet, DescriptionTemplateSection } from '@app/core/model/description-template/description-template';
import { VisibilityRulesService } from '@app/ui/description/editor/description-form/visibility-rules/visibility-rules.service';
import { BaseComponent } from '@common/base/base.component';
import { Subject, Subscription, interval } from 'rxjs';
import { debounceTime, distinctUntilChanged, takeUntil } from 'rxjs/operators';
import { ToCEntry } from './models/toc-entry';
import { ToCEntryType } from './models/toc-entry-type.enum';
import { TableOfContentsInternal } from './table-of-contents-internal/table-of-contents-internal';
export interface Link {
/* id of the section*/
id: string;
/* header type h3/h4 */
type: string;
/* If the anchor is in view of the page */
active: boolean;
/* name of the anchor */
name: string;
/* top offset px of the anchor */
top: number;
page: number;
section: number;
show: boolean;
selected: boolean;
}
@Component({
selector: 'app-table-of-contents',
styleUrls: ['./table-of-contents.component.scss'],
templateUrl: './table-of-contents.component.html'
})
export class TableOfContentsComponent extends BaseComponent implements OnInit, OnChanges {
@ViewChild('internalTable') internalTable: TableOfContentsInternal;
@Input() links: Link[];
container: string;
headerSelectors = '.toc-page-header, .toc-section-header, .toc-compositeField-header';
@Output() stepFound = new EventEmitter<LinkToScroll>();
@Output() currentLinks = new EventEmitter<Link[]>();
@Output() entrySelected = new EventEmitter<any>();
subscription: Subscription;
linksSubject: Subject<HTMLElement[]> = new Subject<HTMLElement[]>();
@Input() isActive: boolean;
tocentries: ToCEntry[] = null;
@Input() TOCENTRY_ID_PREFIX = '';
@Input() showErrors: boolean = false;
@Input() selectedFieldsetId: string;
private _tocentrySelected: ToCEntry = null;
private isSelecting: boolean = false;
private _intersectionObserver: IntersectionObserver;
private _actOnObservation: boolean = true;
public hiddenEntries: string[] = [];
get tocentrySelected() {
return this.hasFocus ? this._tocentrySelected : null;
}
set tocentrySelected(value) {
this._tocentrySelected = value;
}
@Input() propertiesFormGroup: UntypedFormGroup;
@Input() descriptionTemplate: DescriptionTemplate;
@Input() hasFocus: boolean = false;
@Input() visibilityRulesService: VisibilityRulesService;
show: boolean = false;
constructor(
@Inject(DOCUMENT) private _document: Document,
// public visibilityRulesService: VisibilityRulesService
) {
super();
}
ngOnInit(): void {
if (this.descriptionTemplate) {
this.tocentries = this.getTocEntries(this.descriptionTemplate);
if (this.visibilityRulesService) {
this.hiddenEntries = this._findHiddenEntries(this.tocentries);
}
} else {
//emit value every 500ms
const source = interval(500);
this.subscription = source.subscribe(val => {
const headers = Array.from(this._document.querySelectorAll(this.headerSelectors)) as HTMLElement[];
this.linksSubject.next(headers);
});
if (!this.links || this.links.length === 0) {
this.linksSubject.asObservable()
.pipe(distinctUntilChanged((p: HTMLElement[], q: HTMLElement[]) => JSON.stringify(p) == JSON.stringify(q)))
.subscribe(headers => {
const links: Array<Link> = [];
if (headers.length) {
let page;
let section;
let show
for (const header of headers) {
let name;
let id;
if (header.classList.contains('toc-page-header')) { // deprecated after removing stepper
name = header.innerText.trim().replace(/^link/, '');
id = header.id;
page = header.id.split('_')[1];
section = undefined;
show = true;
} else if (header.classList.contains('toc-section-header')) {
name = header.childNodes[0].childNodes[0].childNodes[0].childNodes[0].childNodes[0].nodeValue.trim().replace(/^link/, '');
id = header.id;
page = header.id.split('.')[1];
section = header.id;
if (header.id.split('.')[4]) { show = false; }
else { show = true; }
} else if (header.classList.contains('toc-compositeField-header')) {
name = (header.childNodes[0]).nodeValue.trim().replace(/^link/, '');
id = header.id;
// id = header.parentElement.parentElement.parentElement.id;
show = false;
}
const { top } = header.getBoundingClientRect();
links.push({
name,
id,
type: header.tagName.toLowerCase(),
top: top,
active: false,
page: page,
section: section,
show: show,
selected: false
});
}
}
this.links = links;
// Initialize selected for button next on dataset wizard component editor
this.links.length > 0 ? this.links[0].selected = true : null;
})
}
}
}
private _findHiddenEntries(tocentries: ToCEntry[]): string[] {
if (!tocentries) return [];
const invisibleEntries: string[] = []
tocentries.forEach(entry => {
if (entry.type === ToCEntryType.FieldSet) {
const isVisible = this.visibilityRulesService.isVisibleMap[entry.id.toString()] ?? true;
if (!isVisible) {
invisibleEntries.push(entry.id.toString());
} else {
//check field inputs
const oneFieldAtLeastIsVisible = entry.subEntries.some(field => this.visibilityRulesService.isVisibleMap[field.id.toString()] ?? true);
if (!oneFieldAtLeastIsVisible) {
invisibleEntries.push(entry.id.toString());
}
}
} else {
const hiddenEntries = this._findHiddenEntries(entry.subEntries);
if (entry.subEntries && (entry.subEntries.every(e => hiddenEntries.includes(e.id.toString())))) {
//all children all hidden then hide parent node;
invisibleEntries.push(entry.id.toString());
} else {
invisibleEntries.push(...hiddenEntries);
}
}
})
return invisibleEntries;
}
private _visibilityRulesSubscription: Subscription;
ngOnChanges(changes: SimpleChanges) {
if (this.selectedFieldsetId) {
this.onToCentrySelected(this._findTocEntryById(this.selectedFieldsetId, this.tocentries), false);
this._actOnObservation = false;
setTimeout(() => {
this._actOnObservation = true;
}, 1000);
}
if (changes['hasFocus'] && changes.hasFocus.currentValue) {
this._resetObserver();
}
if ('visibilityRulesService') {
if (this._visibilityRulesSubscription) {
this._visibilityRulesSubscription.unsubscribe();
this._visibilityRulesSubscription = null;
}
if (!this.visibilityRulesService) return;
this._visibilityRulesSubscription = this.visibilityRulesService.visibilityChange
.pipe(takeUntil(this._destroyed))
.pipe(debounceTime(200))
.subscribe(_ => {
if (this.hasFocus) {
this._resetObserver();
this.hiddenEntries = this._findHiddenEntries(this.tocentries);
}
});
this.hiddenEntries = this._findHiddenEntries(this.tocentries);
}
// if (!this.isActive && this.links && this.links.length > 0) {
// this.links.forEach(link => {
// link.selected = false;
// })
// this.links[0].selected = true;
// }
}
private _resetObserver() {
if (this._intersectionObserver) {//clean up
this._intersectionObserver.disconnect();
this._intersectionObserver = null;
}
const options = {
root: null,
rootMargin: '-38% 0px -60% 0px',
threshold: 0
}
this._intersectionObserver = new IntersectionObserver((entries, observer) => {
if (!this._actOnObservation) {
return;
}
entries.forEach(ie => {
if (ie.isIntersecting) {
try {
const target_id = ie.target.id.replace(this.TOCENTRY_ID_PREFIX, '');
if (this.visibilityRulesService.isVisibleMap[target_id] ?? true) {
this.onToCentrySelected(this._findTocEntryById(target_id, this.tocentries));
}
} catch {
}
}
})
}, options);
const fieldsetsEtries = this._getAllFieldSets(this.tocentries);
fieldsetsEtries.forEach(e => {
if (e.type === ToCEntryType.FieldSet) {
try {
const targetElement = document.getElementById(this.TOCENTRY_ID_PREFIX + e.id);
this._intersectionObserver.observe(targetElement);
} catch {
console.log('element not found');
}
}
});
}
goToStep(link: Link) {
this.stepFound.emit({
page: link.page,
section: link.section
});
this.currentLinks.emit(this.links);
setTimeout(() => {
const target = document.getElementById(link.id);
target.scrollIntoView(true);
var scrolledY = window.scrollY;
if (scrolledY) {
window.scroll(0, scrolledY - 70);
}
}, 500);
}
toggle(headerLink: Link) {
const headerPage = +headerLink.name.split(" ", 1);
let innerPage;
for (const link of this.links) {
link.selected = false;
if (link.type === 'mat-expansion-panel') {
innerPage = +link.name.split(".", 1)[0];
if (isNaN(innerPage)) { innerPage = +link.name.split(" ", 1) }
} else if (link.type === 'h5') {
innerPage = +link.name.split(".", 1)[0];
}
if (headerPage === innerPage && (link.type !== 'mat-expansion-panel' || (link.type === 'mat-expansion-panel' && link.id.split(".")[4]))) {
link.show = !link.show;
}
}
headerLink.selected = true;
}
// getIndex(link: Link): number {
// return +link.id.split("_", 2)[1];
// }
private _buildRecursivelySection(item: DescriptionTemplateSection): ToCEntry {
if (!item) return null;
const sections = item.sections;
const fieldsets = item.fieldSets;
const tempResult: ToCEntry[] = [];
if (sections && sections.length) {
sections.forEach(section => {
tempResult.push(this._buildRecursivelySection(section));
});
} else if (fieldsets && fieldsets.length) {
fieldsets.forEach(fieldset => {
tempResult.push(this._buildRecursivelyFieldSet(fieldset));
});
}
return {
// form: form,
id: item.id,
label: item.title,
numbering: '',
subEntries: tempResult,
subEntriesType: sections && sections.length ? ToCEntryType.Section : ToCEntryType.FieldSet,
type: ToCEntryType.Section,
ordinal: item.ordinal
}
}
private _buildRecursivelyFieldSet(item: DescriptionTemplateFieldSet): ToCEntry {
if (!item) return null;
const tempResult: ToCEntry[] = [];
if (item && item.fields.length > 0) {
item.fields.forEach(field => {
tempResult.push(this._buildRecursivelyField(field));
});
}
return {
// form: form,
id: item.id,
label: item.title,
numbering: 's',
subEntries: tempResult,
subEntriesType: ToCEntryType.Field,
type: ToCEntryType.FieldSet,
ordinal: item.ordinal
};
}
private _buildRecursivelyField(item: DescriptionTemplateField): ToCEntry {
if (!item) return null;
return {
// form: form,
id: item.id,
label: null,
numbering: 's',
subEntries: null,
subEntriesType: null,
type: ToCEntryType.Field,
ordinal: item.ordinal,
hidden: true
};
}
private _sortByOrdinal(tocentries: ToCEntry[]) {
if (!tocentries || !tocentries.length) return;
tocentries.sort(this._customCompare);
tocentries.forEach(entry => {
this._sortByOrdinal(entry.subEntries);
});
}
private _customCompare(a, b) {
return a.ordinal - b.ordinal;
}
private _calculateNumbering(tocentries: ToCEntry[], depth: number[] = []) {
if (!tocentries || !tocentries.length) {
return;
}
let prefixNumbering = depth.length ? depth.join('.') : '';
if (depth.length) prefixNumbering = prefixNumbering + ".";
tocentries.forEach((entry, i) => {
entry.numbering = prefixNumbering + (i + 1);
this._calculateNumbering(entry.subEntries, [...depth, i + 1])
});
}
getTocEntries(descriptionTemplate: DescriptionTemplate): ToCEntry[] {
if (descriptionTemplate == null) { return []; }
const result: ToCEntry[] = [];
//build parent pages
descriptionTemplate.definition.pages.forEach((pageElement, i) => {
const tocEntry: ToCEntry = {
// id: i + 'id',
id: pageElement.id,
label: pageElement.title,
type: ToCEntryType.Page,
// form: pageElement,
numbering: (i + 1).toString(),
subEntriesType: ToCEntryType.Section,
subEntries: [],
ordinal: pageElement.ordinal
};
const sections = descriptionTemplate.definition.sections.filter(x => x.page == pageElement.id);
sections.forEach(section => {
const tempResults = this._buildRecursivelySection(section);
tocEntry.subEntries.push(tempResults);
});
result.push(tocEntry)
});
this._sortByOrdinal(result);
//calculate numbering
this._calculateNumbering(result);
return result;
}
onToCentrySelected(entry: ToCEntry = null, execute: boolean = true) {
if (!this.isSelecting) {
this.isSelecting = true;
this.tocentrySelected = entry;
this.entrySelected.emit({ entry: entry, execute: execute });
setTimeout(() => {
this.isSelecting = false;
}, 600);
}
}
private _findTocEntryById(id: string, tocentries: ToCEntry[]): ToCEntry {
if (!tocentries || !tocentries.length) {
return null;
}
let tocEntryFound = tocentries.find(entry => entry.id === id);
if (tocEntryFound) {
return tocEntryFound;
}
for (let entry of tocentries) {
const result = this._findTocEntryById(id, entry.subEntries);
if (result) {
tocEntryFound = result;
break;
}
}
return tocEntryFound ? tocEntryFound : null;
}
/**
* Get all filedsets in a tocentry array;
* @param entries Tocentries to search in
* @returns The tocentries that are Fieldsets provided in the entries
*/
private _getAllFieldSets(entries: ToCEntry[]): ToCEntry[] {
const fieldsets: ToCEntry[] = [];
if (!entries || !entries.length) return fieldsets;
entries.forEach(e => {
if (e.type === ToCEntryType.FieldSet) {
fieldsets.push(e);
} else {
fieldsets.push(...this._getAllFieldSets(e.subEntries));
}
});
return fieldsets;
}
public hasVisibleInvalidFields(): boolean {
if (!this.internalTable || !this.tocentries) {
return false;
}
return this.tocentries.some(e => this.internalTable.invalidChildsVisible(e));
}
protected readonly console = console;
}
export interface LinkToScroll {
page: number;
section: number;
}

View File

@ -0,0 +1,15 @@
import {CommonModule} from '@angular/common';
import {NgModule} from '@angular/core';
import {RouterModule} from '@angular/router';
import { TableOfContentsInternal } from './table-of-contents-internal/table-of-contents-internal';
import { MatIconModule } from '@angular/material/icon';
import { VisibilityRulesService } from '@app/ui/description/editor/description-form/visibility-rules/visibility-rules.service';
import { TableOfContentsComponent } from './table-of-contents.component';
@NgModule({
imports: [CommonModule, RouterModule, MatIconModule],
declarations: [TableOfContentsComponent, TableOfContentsInternal],
exports: [TableOfContentsComponent],
providers: [VisibilityRulesService]
})
export class TableOfContentsModule { }

View File

@ -0,0 +1,27 @@
import { NgModule } from '@angular/core';
import { FormattingModule } from '@app/core/formatting.module';
import { UrlListingModule } from '@app/library/url-listing/url-listing.module';
import { DescriptionListingComponent } from '@app/ui/description/listing/description-listing.component';
import { DescriptionListingItemComponent } from '@app/ui/description/listing/listing-item/description-listing-item.component';
import { CommonFormsModule } from '@common/forms/common-forms.module';
import { CommonUiModule } from '@common/ui/common-ui.module';
import { DescriptionListingRoutingModule } from './description-listing.routing';
import { DescriptionCopyDialogModule } from '../description-copy-dialog/description-copy-dialog.module';
@NgModule({
imports: [
CommonUiModule,
CommonFormsModule,
UrlListingModule,
FormattingModule,
DescriptionCopyDialogModule,
DescriptionListingRoutingModule
],
declarations: [
DescriptionListingComponent,
DescriptionListingItemComponent,
],
exports: [
]
})
export class DescriptionListingModule { }

View File

@ -0,0 +1,30 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AuthGuard } from '@app/core/auth-guard.service';
import { DescriptionListingComponent } from './description-listing.component';
const routes: Routes = [
{
path: '',
component: DescriptionListingComponent,
// canActivate: [AuthGuard],
data: {
breadcrumb: true
},
},
{
path: 'dmp/:dmpId',
component: DescriptionListingComponent,
canActivate: [AuthGuard],
data: {
breadcrumb: true
},
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
providers: []
})
export class DescriptionListingRoutingModule { }

Some files were not shown because too many files have changed in this diff Show More