no message

This commit is contained in:
Diamantis Tziotzios 2023-10-31 11:19:52 +02:00
parent 2b074f268d
commit c08f05bc04
30 changed files with 3379 additions and 497 deletions

View File

@ -35,7 +35,7 @@ public enum FieldType implements DatabaseEnum<String> {
ORGANIZATIONS("organizations"),
DATASET_IDENTIFIER("datasetIdentifier"),
CURRENCY("currency"),
VALIDATION("validation");;
VALIDATION("validation");
private final String value;
FieldType(String value) {

View File

@ -122,7 +122,7 @@ public class FieldEntity implements DatabaseViewStyleDefinition, XmlSerializable
viewStyle.setAttribute("renderstyle", FieldType.INTERNAL_DMP_ENTRIES.getValue());
break;
}
case BOOLEAN_DECISION: viewStyle.setAttribute("renderstyle", this.data.getFieldType().getValue());;
case BOOLEAN_DECISION: viewStyle.setAttribute("renderstyle", this.data.getFieldType().getValue());
}
}

View File

@ -9,8 +9,8 @@ import org.hibernate.annotations.GenericGenerator;
import java.util.List;
import java.util.UUID;
@Entity
@Table(name = "\"UserDatasetProfile\"")
//@Entity
//@Table(name = "\"UserDatasetProfile\"")
public class UserDatasetProfile implements DataEntity<UserDatasetProfile, UUID> {
@Id
@GeneratedValue

View File

@ -10,30 +10,30 @@ public class FieldSetPersist {
@NotNull(message = "{validation.empty}")
@NotEmpty(message = "{validation.empty}")
private String id = null;;
private String id = null;
@NotNull(message = "{validation.empty}")
private Integer ordinal = null;;
private Integer ordinal = null;
@NotNull(message = "{validation.empty}")
@NotEmpty(message = "{validation.empty}")
private String numbering = null;;
private String numbering = null;
@NotNull(message = "{validation.empty}")
@NotEmpty(message = "{validation.empty}")
private String title = null;;
private String title = null;
@NotNull(message = "{validation.empty}")
@NotEmpty(message = "{validation.empty}")
private String description = null;;
private String description = null;
@NotNull(message = "{validation.empty}")
@NotEmpty(message = "{validation.empty}")
private String extendedDescription = null;;
private String extendedDescription = null;
@NotNull(message = "{validation.empty}")
@NotEmpty(message = "{validation.empty}")
private String additionalInformation = null;;
private String additionalInformation = null;
@NotNull(message = "{validation.empty}")
@Valid

View File

@ -256,7 +256,7 @@ public class DmpBlueprintServiceImpl implements DmpBlueprintService {
}
private void reassignDefinition(Definition model){
if (model == null) return;;
if (model == null) return;
if (model.getSections() != null){
for (Section section : model.getSections()) {
@ -266,7 +266,7 @@ public class DmpBlueprintServiceImpl implements DmpBlueprintService {
}
private void reassignSection(Section model){
if (model == null) return;;
if (model == null) return;
model.setId(UUID.randomUUID());
if (model.getFields() != null){

View File

@ -3,8 +3,12 @@ package eu.eudat.service.fielddatahelper;
import eu.eudat.commons.enums.FieldType;
import gr.cite.tools.data.builder.Builder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Component
public class FieldDataHelperServiceProvider {
@Autowired

View File

@ -71,7 +71,7 @@ public class FileController {
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
String json = mapper.writeValueAsString(datasetprofile.getSections());;
String json = mapper.writeValueAsString(datasetprofile.getSections());
JsonNode propertiesJson = mapper.readTree(json);
Set<JsonNode> fieldNodes = new HashSet<>();
fieldNodes.addAll(JsonSearcher.findNodes(propertiesJson, "id", fieldId, false));

View File

@ -666,7 +666,7 @@ public class DatasetManager {
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
String json = mapper.writeValueAsString(datasetWizardModel.getDatasetProfileDefinition());;
String json = mapper.writeValueAsString(datasetWizardModel.getDatasetProfileDefinition());
JsonNode propertiesJson = mapper.readTree(json);
Set<JsonNode> uploadNodes = new HashSet<>();

View File

@ -0,0 +1,4 @@
export enum DescriptionTemplateFieldAutocompleteType {
UNCACHED = 0,
CACHED = 1
}

View File

@ -1,16 +1,17 @@
import { DescriptionTemplateFieldAutocompleteType } from "@app/core/common/enum/description-template-field-autocomplete-type";
import { DescriptionTemplateFieldDataExternalDatasetType } from "@app/core/common/enum/description-template-field-data-external-dataset-type";
import { DescriptionTemplateFieldType } from "@app/core/common/enum/description-template-field-type";
import { DescriptionTemplateFieldValidationType } from "@app/core/common/enum/description-template-field-validation-type";
import { DescriptionTemplateStatus } from "@app/core/common/enum/description-template-status";
import { BaseEntityPersist } from "@common/base/base-entity.model";
import { Guid } from "@common/types/guid";
import { DescriptionTemplateType } from "../description-template-type/description-template-type";
export interface DescriptionTemplatePersist extends BaseEntityPersist {
label: string;
description: string;
groupId: Guid;
version: string;
language: string;
type: DescriptionTemplateType;
type: Guid;
status: DescriptionTemplateStatus;
definition: DescriptionTemplateDefinitionPersist;
}
@ -32,11 +33,9 @@ export interface DescriptionTemplateSectionPersist {
ordinal: number;
defaultVisibility: boolean;
multiplicity: boolean;
numbering: string;
page: string;
title: string;
description: string;
extendedDescription: string;
sections?: DescriptionTemplateSectionPersist[];
fieldSets: DescriptionTemplateFieldSetPersist[];
@ -50,7 +49,7 @@ export interface DescriptionTemplateFieldSetPersist {
description: string;
extendedDescription: string;
additionalInformation: string;
multiplicity: DescriptionTemplateMultiplicityPersist
multiplicity: DescriptionTemplateMultiplicityPersist;
hasCommentField: boolean;
fields: DescriptionTemplateFieldPersist[];
}
@ -58,12 +57,10 @@ export interface DescriptionTemplateFieldSetPersist {
export interface DescriptionTemplateFieldPersist {
id: Guid;
ordinal: number;
numbering: string;
schematics: string[];
defaultValue: string;
visibilityRules: DescriptionTemplateRulePersist[];
// validations: DescriptionTemplateFieldValidationType[];
// fieldType: DescriptionTemplateFieldType;
validations: DescriptionTemplateFieldValidationType[];
includeInExport: boolean;
data: DescriptionTemplateBaseFieldDataPersist;
}
@ -82,4 +79,151 @@ export interface DescriptionTemplateMultiplicityPersist {
export interface DescriptionTemplateBaseFieldDataPersist {
label: string;
fieldType: DescriptionTemplateFieldType;
}
//
// Field Types
//
export interface DescriptionTemplateAutoCompleteDataPersist extends DescriptionTemplateBaseFieldDataPersist {
multiAutoComplete: boolean;
autoCompleteSingleDataList: DescriptionTemplateAutoCompleteSingleDataPersist[];
}
export interface DescriptionTemplateBooleanDecisionDataPersist extends DescriptionTemplateBaseFieldDataPersist {
}
export interface DescriptionTemplateDatasetAutoCompleteDataPersist extends DescriptionTemplateBaseFieldDataPersist {
multiAutoComplete: boolean;
}
export interface DescriptionTemplateDmpAutoCompleteDataPersist extends DescriptionTemplateBaseFieldDataPersist {
multiAutoComplete: boolean;
}
export interface DescriptionTemplateCheckBoxDataPersist extends DescriptionTemplateBaseFieldDataPersist {
}
export interface DescriptionTemplateDatePickerDataPersist extends DescriptionTemplateBaseFieldDataPersist {
}
export interface DescriptionTemplateExternalDatasetDataPersist extends DescriptionTemplateBaseFieldDataPersist {
multiAutoComplete: boolean;
type: DescriptionTemplateFieldDataExternalDatasetType;
}
export interface DescriptionTemplateFreeTextDataPersist extends DescriptionTemplateBaseFieldDataPersist {
}
export interface DescriptionTemplateLicenseDataPersist extends DescriptionTemplateBaseFieldDataPersist {
multiAutoComplete: boolean;
}
export interface DescriptionTemplateOrganizationDataPersist extends DescriptionTemplateBaseFieldDataPersist {
multiAutoComplete: boolean;
}
export interface DescriptionTemplatePublicationDataPersist extends DescriptionTemplateBaseFieldDataPersist {
multiAutoComplete: boolean;
}
export interface DescriptionTemplateRadioBoxDataPersist extends DescriptionTemplateBaseFieldDataPersist {
options: DescriptionTemplateRadioBoxOptionPersist;
}
export interface DescriptionTemplateRegistryDataPersist extends DescriptionTemplateBaseFieldDataPersist {
multiAutoComplete: boolean;
}
export interface DescriptionTemplateResearcherAutoCompleteDataPersist extends DescriptionTemplateBaseFieldDataPersist {
multiAutoComplete: boolean;
}
export interface DescriptionTemplateResearcherDataPersist extends DescriptionTemplateBaseFieldDataPersist {
multiAutoComplete: boolean;
}
export interface DescriptionTemplateRichTextAreaDataPersist extends DescriptionTemplateBaseFieldDataPersist {
}
export interface DescriptionTemplateServiceDataPersist extends DescriptionTemplateBaseFieldDataPersist {
multiAutoComplete: boolean;
}
export interface DescriptionTemplateTagDataPersist extends DescriptionTemplateBaseFieldDataPersist {
}
export interface DescriptionTemplateTaxonomyDataPersist extends DescriptionTemplateBaseFieldDataPersist {
multiAutoComplete: boolean;
}
export interface DescriptionTemplateTextAreaDataPersist extends DescriptionTemplateBaseFieldDataPersist {
}
export interface DescriptionTemplateUploadDataPersist extends DescriptionTemplateBaseFieldDataPersist {
types: DescriptionTemplateUploadOptionPersist[];
}
export interface DescriptionTemplateValidationDataPersist extends DescriptionTemplateBaseFieldDataPersist {
}
export interface DescriptionTemplateDatasetIdentifierDataPersist extends DescriptionTemplateBaseFieldDataPersist {
}
export interface DescriptionTemplateCurrencyDataPersist extends DescriptionTemplateBaseFieldDataPersist {
}
export interface DescriptionTemplateWordListDataPersist extends DescriptionTemplateBaseFieldDataPersist {
options: DescriptionTemplateComboBoxOptionPersist[];
multiList: boolean;
}
export interface DescriptionTemplateDataRepositoryDataPersist extends DescriptionTemplateBaseFieldDataPersist {
multiAutoComplete: boolean;
}
export interface DescriptionTemplateJournalRepositoryDataPersist extends DescriptionTemplateBaseFieldDataPersist {
multiAutoComplete: boolean;
}
export interface DescriptionTemplatePublicationRepositoryDataPersist extends DescriptionTemplateBaseFieldDataPersist {
multiAutoComplete: boolean;
}
//
// Others
//
export interface DescriptionTemplateAutoCompleteSingleDataPersist {
autocompleteType: DescriptionTemplateFieldAutocompleteType;
url: string;
autoCompleteOptions: DescriptionTemplateComboBoxOptionPersist;
optionsRoot: string;
hasAuth: boolean;
auth: DescriptionTemplateAuthAutoCompleteDataPersist
method: string;
}
export interface DescriptionTemplateAuthAutoCompleteDataPersist {
url: string;
method: string;
body: string;
path: string;
type: string;
}
export interface DescriptionTemplateComboBoxOptionPersist {
label: string;
value: string;
source: string;
uri: string;
}
export interface DescriptionTemplateRadioBoxOptionPersist {
label: string;
value: string;
}
export interface DescriptionTemplateUploadOptionPersist {
label: string;
value: string;
}

View File

@ -68,7 +68,6 @@ export interface DescriptionTemplateField {
defaultValue: string;
visibilityRules: DescriptionTemplateRule[];
validations: DescriptionTemplateFieldValidationType[];
fieldType: DescriptionTemplateFieldType;
includeInExport: boolean;
data: DescriptionTemplateBaseFieldData;
}

View File

@ -1,17 +1,27 @@
import { Injectable } from '@angular/core';
import { IsActive } from '@app/core/common/enum/is-active.enum';
import { DescriptionTemplateType, DescriptionTemplateTypePersist } from '@app/core/model/description-template-type/description-template-type';
import { DescriptionTemplateTypeLookup } from '@app/core/query/description-template-type.lookup';
import { DmpBlueprintLookup } from '@app/core/query/dmp-blueprint.lookup';
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 { QueryResult } from '@common/model/query-result';
import { FilterService } from '@common/modules/text-filter/filter-service';
import { Guid } from '@common/types/guid';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { catchError, map } from 'rxjs/operators';
import { nameof } from 'ts-simple-nameof';
import { ConfigurationService } from '../configuration/configuration.service';
import { BaseHttpV2Service } from '../http/base-http-v2.service';
@Injectable()
export class DescriptionTemplateTypeService {
constructor(private http: BaseHttpV2Service, private installationConfiguration: ConfigurationService) { }
constructor(
private http: BaseHttpV2Service,
private installationConfiguration: ConfigurationService,
private filterService: FilterService
) { }
private get apiBase(): string { return `${this.installationConfiguration.server}description-template-type`; }
@ -46,4 +56,44 @@ export class DescriptionTemplateTypeService {
.delete<DescriptionTemplateType>(url).pipe(
catchError((error: any) => throwError(error)));
}
//
// Autocomplete Commons
//
// tslint:disable-next-line: member-ordering
singleAutocompleteConfiguration: SingleAutoCompleteConfiguration = {
initialItems: (data?: any) => this.query(this.buildAutocompleteLookup()).pipe(map(x => x.items)),
filterFn: (searchQuery: string, data?: any) => this.query(this.buildAutocompleteLookup(searchQuery)).pipe(map(x => x.items)),
getSelectedItem: (selectedItem: any) => this.query(this.buildAutocompleteLookup(null, null, [selectedItem])).pipe(map(x => x.items[0])),
displayFn: (item: DescriptionTemplateType) => item.name,
titleFn: (item: DescriptionTemplateType) => item.name,
valueAssign: (item: DescriptionTemplateType) => item.id,
};
// tslint:disable-next-line: member-ordering
multipleAutocompleteConfiguration: MultipleAutoCompleteConfiguration = {
initialItems: (excludedItems: any[], data?: any) => this.query(this.buildAutocompleteLookup(null, excludedItems ? excludedItems : null)).pipe(map(x => x.items)),
filterFn: (searchQuery: string, excludedItems: any[]) => this.query(this.buildAutocompleteLookup(searchQuery, excludedItems)).pipe(map(x => x.items)),
getSelectedItems: (selectedItems: any[]) => this.query(this.buildAutocompleteLookup(null, null, selectedItems)).pipe(map(x => x.items)),
displayFn: (item: DescriptionTemplateType) => item.name,
titleFn: (item: DescriptionTemplateType) => item.name,
valueAssign: (item: DescriptionTemplateType) => item.id,
};
private buildAutocompleteLookup(like?: string, excludedIds?: Guid[], ids?: Guid[]): DmpBlueprintLookup {
const lookup: DmpBlueprintLookup = new DmpBlueprintLookup();
lookup.page = { size: 100, offset: 0 };
if (excludedIds && excludedIds.length > 0) { lookup.excludedIds = excludedIds; }
if (ids && ids.length > 0) { lookup.ids = ids; }
lookup.isActive = [IsActive.Active];
lookup.project = {
fields: [
nameof<DescriptionTemplateType>(x => x.id),
nameof<DescriptionTemplateType>(x => x.name)
]
};
lookup.order = { items: [nameof<DescriptionTemplateType>(x => x.name)] };
if (like) { lookup.like = this.filterService.transformLike(like); }
return lookup;
}
}

View File

@ -1,26 +1,21 @@
import { HttpClient, HttpHeaders, HttpResponse } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { IsActive } from '@app/core/common/enum/is-active.enum';
import { DataTableData } from '@app/core/model/data-table/data-table-data';
import { DatasetListingModel } from '@app/core/model/dataset/dataset-listing';
import { DmpBlueprint, DmpBlueprintPersist } from '@app/core/model/dmp-blueprint/dmp-blueprint';
import { DmpBlueprintLookup } from '@app/core/query/dmp-blueprint.lookup';
import { DmpBlueprintExternalAutocompleteCriteria } from '@app/core/query/dmp/dmp-profile-external-autocomplete-criteria';
import { RequestItem } from '@app/core/query/request-item';
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 { BaseHttpParams } from '@common/http/base-http-params';
import { InterceptorType } from '@common/http/interceptors/interceptor-type';
import { QueryResult } from '@common/model/query-result';
import { FilterService } from '@common/modules/text-filter/filter-service';
import { Guid } from '@common/types/guid';
import { Observable, throwError } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
import { nameof } from 'ts-simple-nameof';
import { ConfigurationService } from '../configuration/configuration.service';
import { BaseHttpV2Service } from '../http/base-http-v2.service';
import { DmpBlueprintCriteria } from '@app/core/query/dmp/dmp-blueprint-criteria';
import { DataTableRequest } from '@app/core/model/data-table/data-table-request';
import { SingleAutoCompleteConfiguration } from '@app/library/auto-complete/single/single-auto-complete-configuration';
import { MultipleAutoCompleteConfiguration } from '@app/library/auto-complete/multiple/multiple-auto-complete-configuration';
import { IsActive } from '@app/core/common/enum/is-active.enum';
import { nameof } from 'ts-simple-nameof';
import { FilterService } from '@common/modules/text-filter/filter-service';
import { Observable, throwError } from 'rxjs';
@Injectable()
export class DmpBlueprintService {

View File

@ -1,6 +1,7 @@
import { DragDropModule } from '@angular/cdk/drag-drop';
import { NgModule } from "@angular/core";
import { AutoCompleteModule } from "@app/library/auto-complete/auto-complete.module";
import { RichTextEditorModule } from '@app/library/rich-text-editor/rich-text-editor.module';
import { UrlListingModule } from '@app/library/url-listing/url-listing.module';
import { CommonFormattingModule } from '@common/formatting/common-formatting.module';
import { CommonFormsModule } from '@common/forms/common-forms.module';
@ -12,9 +13,11 @@ import { CommonUiModule } from '@common/ui/common-ui.module';
import { NgxDropzoneModule } from "ngx-dropzone";
import { DescriptionTemplateRoutingModule } from './description-template.routing';
import { DescriptionTemplateEditorComponent } from './editor/description-template-editor.component';
import { DescriptionTemplateTableOfContents } from './editor/table-of-contents/description-template-table-of-contents';
import { DescriptionTemplateTableOfContentsInternalSection } from './editor/table-of-contents/table-of-contents-internal-section/description-template-table-of-contents-internal-section';
import { DescriptionTemplateListingComponent } from './listing/description-template-listing.component';
import { DescriptionTemplateListingFiltersComponent } from "./listing/filters/description-template-listing-filters.component";
import { ImportDescriptionTemplateDialogComponent } from './listing/import-dmp-blueprint/import-description-template.dialog.component';
import { ImportDescriptionTemplateDialogComponent } from './listing/import-description-template/import-description-template.dialog.component';
@NgModule({
imports: [
@ -29,13 +32,23 @@ import { ImportDescriptionTemplateDialogComponent } from './listing/import-dmp-b
HybridListingModule,
TextFilterModule,
UserSettingsModule,
CommonFormattingModule
CommonFormattingModule,
RichTextEditorModule,
// FormattingModule,
// FormProgressIndicationModule,
// AngularStickyThingsModule,
// MatBadgeModule,
// DragulaModule,
// TransitionGroupModule,
],
declarations: [
// DescriptionTemplateEditorComponent,
DescriptionTemplateEditorComponent,
DescriptionTemplateListingComponent,
DescriptionTemplateListingFiltersComponent,
ImportDescriptionTemplateDialogComponent
ImportDescriptionTemplateDialogComponent,
DescriptionTemplateTableOfContents,
DescriptionTemplateTableOfContentsInternalSection
]
})
export class DescriptionTemplateModule { }

View File

@ -0,0 +1,90 @@
import { animate, keyframes, state, style, transition, trigger } from "@angular/animations";
export const STEPPER_ANIMATIONS = [
trigger('previous_btn',[
transition(':enter',[
style({'transform': 'translateX(100%)', 'z-index':'9999', 'opacity':0.4}),
animate('600ms ease-out', style({
'transform': 'translateX(0)',
'opacity':1
}))
]),
transition(':leave',[
style({
'transform': 'translateX(0)',
'opacity':'1',
'z-index':'9999'
}),
animate('800ms ease-out', keyframes([
style({'transform': 'translateX(120%)', offset:0.8}),
style({'opacity': '0.2', offset:0.96})
]))
])
]),
trigger('next_btn', [
transition(':leave',[
style({opacity:1, position:'absolute', 'z-index':'9999'}),
animate('700ms ease-out', keyframes( [
style({ transform:'translateX(-100%)', offset:0.6}),
style({ opacity:'0.2', offset:0.87})
]))
]),
transition(':enter',[
style({opacity:0.3, 'z-index':'9999', transform:'translateX(-100%)'}),
animate('600ms ease-out', style({ opacity:'1', transform:'translateX(0)' }))
])
]),
trigger('finalize_btn',[
transition(':enter',[
style({opacity:0.3}),
animate('400ms ease-in', style({opacity:1}))
]),
transition(':leave',[
style({opacity:1, position:'absolute'}),
animate('600ms ease-in', style({opacity:0.3}))
])
])
];
export const GENERAL_ANIMATIONS = [
trigger('enterIn',[
transition(':enter',[
style({
transform:'scale(0)',
'transform-origin':'50% 0',
opacity:0
}),
animate('800ms ease', style({transform:'scale(1)', opacity:1}))
])
]),
trigger('fadeElement',[
state('updated',style({opacity:0})),
transition("*=>updated",
animate('2s 1s ease-out'))
]),
trigger('add-new-user-field', [
state('untriggered', style({
transform:'translateX(120%)'
})),
state('triggered', style({
transform:'translateX(0)'
})),
transition('untriggered => triggered', animate('400ms ease')),
transition('triggered => untriggered', animate('400ms 100ms ease'))
]),
trigger('scroll-on-top-btn',[
transition(":enter", [style({opacity:0, transform:'scale(0)'}), animate('400ms ease', style({'opacity':1, transform:'scale(1)'}))]),
transition(":leave", [style({opacity:1,transform:'scale(1)'}), animate('400ms ease', style({'opacity':0, transform:'scale(0)'}))])
]),
trigger('action-btn',[
transition(":enter", [style({opacity:0, transform:'scale(0)'}), animate('400ms ease', style({'opacity':1, transform:'scale(1)'}))]),
transition(":leave", [style({opacity:1,transform:'scale(1)'}), animate('400ms ease', style({'opacity':0, transform:'scale(0)'}))])
]),
trigger('fade-in',[
transition(":enter", [style({opacity:0}), animate('1000ms 800ms ease', style({'opacity':1}))]),
]),
trigger('fade-in-fast',[
transition(":enter", [style({opacity:0}), animate('800ms 100ms ease', style({'opacity':1}))]),
]),
]

View File

@ -1,228 +1,318 @@
<!-- <div class="container-fluid description-template-editor">
<div class="row align-items-center mb-4" *ngIf="formGroup">
<div class="col-auto">
<h3 *ngIf="isNew && !isClone">{{'DESCRIPTION-TEMPLATE-EDITOR.TITLE.NEW' | translate}}</h3>
<h3 *ngIf="isNew && isClone">
<span>{{'DESCRIPTION-TEMPLATE-EDITOR.TITLE.CLONE' | translate}}</span>
{{formGroup.get('label').value}}
</h3>
<h3 *ngIf="!isNew">{{formGroup.get('label').value}}</h3>
<div class="container-fluid description-template-editor">
<div id="header-outer-wrapper">
<div class="row">
<div class="col-12 d-flex" id="title-column">
<div style="padding-left: 2em;">
<h3 *ngIf="isNew && !isClone && !isNewVersion">{{'DATASET-PROFILE-EDITOR.TITLE.NEW-PROFILE' | translate}}</h3>
<h3 *ngIf="isNew && isClone">
<span *ngIf="isClone">{{'DATASET-PROFILE-EDITOR.TITLE.NEW-PROFILE-CLONE' | translate}}</span>
{{formGroup.get('label').value}}
</h3>
<h3 *ngIf="isNew && isNewVersion">
<span *ngIf="isNewVersion">{{'DATASET-PROFILE-EDITOR.TITLE.NEW-PROFILE-VERSION' | translate}}</span>
{{formGroup.get('label').value}}
</h3>
<h3 *ngIf="!isNew">{{formGroup.get('label').value}}</h3>
</div>
<ng-container *ngTemplateOutlet="actions"></ng-container>
</div>
</div>
<div class="col"></div>
<div class="col-auto" *ngIf="!isNew">
<button mat-button class="action-btn" type="button" (click)="delete()">
<mat-icon>delete</mat-icon>
{{'DESCRIPTION-TEMPLATE-EDITOR.ACTIONS.DELETE' | translate}}
</button>
</div>
<div class="col-auto" *ngIf="formGroup.get('status').value==1">
<button mat-button class="finalize-btn" (click)="downloadXML()" type="button">{{'DESCRIPTION-TEMPLATE-EDITOR.ACTIONS.DOWNLOAD-XML' | translate }}</button>
</div>
<div *ngIf="formGroup.get('status').value!=1" class="col-auto">
<button mat-button class="finalize-btn" (click)="finalize()" [disabled]="!this.isFormValid()" type="button">{{'DESCRIPTION-TEMPLATE-EDITOR.ACTIONS.FINALIZE' | translate }}</button>
<div class="row stepper-navigation-outer-wrapper">
<!-- Steps Navigation -->
<div id="stepper-navigation-wrapper">
<div class="col-12 d-flex" *ngIf="steps" id="stepper-navigation">
<div class="col-7 bg-white" style="overflow: hidden; padding: 0px" id="progress-container">
<div id="progress" [ngStyle]="progressStyle"></div>
<div class="row h-100 progress-min-height">
<div class="col text-center align-self-center" *ngFor="let step of steps; index as idx">
<span (click)="stepper.selectedIndex=idx" class="stepper-title-label" [ngClass]="{'stepper-title-label-locked': !isStepUnlocked(idx),'stepper-title-label-completed':idx < stepper.selectedIndex} ">
<ng-container *ngIf="(step.completed &&(idx!=steps.length-1)) else numberLabel">
<mat-icon style="font-size:0.7em; height: 0px;">done</mat-icon>
</ng-container>
<ng-template #numberLabel>
{{idx+1}}
</ng-template>
{{step.label}}
</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<form *ngIf="formGroup" (ngSubmit)="formSubmit()">
<mat-card>
<mat-card-content>
<div class="row">
<div class="col-6">
<mat-form-field class="w-100">
<mat-label>{{'DESCRIPTION-TEMPLATE-EDITOR.FIELDS.NAME' | translate}}</mat-label>
<input matInput type="text" name="label" [formControl]="formGroup.get('label')" required>
<mat-error *ngIf="formGroup.get('label').hasError('required')">
{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
<mat-horizontal-stepper [linear]="true" #stepper class="stepper" (selectionChange)="onMatStepperSelectionChange($event)" style="padding-left: 8px; padding-right: 15px;">
<!-- IMPORTANT TO BE !INVALID (WHEN THE TEMPLATE IS FINALIZED THE CONTORLS ARE DISABLED) -->
<mat-step [label]="'DATASET-PROFILE-EDITOR.STEPS.GENERAL-INFO.TITLE' | translate" [completed]="(!formGroup.get('label').invalid && !formGroup.get('description').invalid && !formGroup.get('language').invalid)">
<!-- <ng-template matStepLabel>{{'DATASET-PROFILE-EDITOR.STEPS.GENERAL-INFO.TITLE' | translate}}
</ng-template> -->
<div class="col-9">
<div class="col">
<div class="col-12">
<div class="heading">1.1 {{'DATASET-PROFILE-EDITOR.STEPS.GENERAL-INFO.DATASET-TEMPLATE-NAME'| translate}} *</div>
<div class="hint">{{'DATASET-PROFILE-EDITOR.STEPS.GENERAL-INFO.DATASET-TEMPLATE-NAME-HINT'| translate}}</div>
<mat-form-field class="full-width basic-info-input">
<input matInput [formControl]="formGroup.get('label')" placeholder="{{'DATASET-PROFILE-EDITOR.FIELDS.DATASET-TITLE' | translate}}">
<mat-error *ngIf="formGroup.get('label').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' |
translate}}
</mat-error>
</mat-form-field>
</div>
<h4 class="col-12">{{'DESCRIPTION-TEMPLATE-EDITOR.FIELDS.SECTIONS' | translate}}</h4>
<div class="col-12" cdkDropList (cdkDropListDropped)="dropSections($event)">
<div *ngFor="let section of formGroup.get('definition').get('sections').controls; let sectionIndex=index;" class="row mb-3" cdkDrag [cdkDragDisabled]="formGroup.disabled">
<div class="col-12">
<mat-card>
<mat-card-header>
<div class="row mb-3 d-flex align-items-center">
<div class="col-auto d-flex">
<mat-card-title>{{'DESCRIPTION-TEMPLATE-EDITOR.FIELDS.SECTION-PREFIX' | translate}} {{sectionIndex + 1}}</mat-card-title>
</div>
<div class="col-auto d-flex"><mat-icon cdkDragHandle style="cursor: move; color: #129d99;">drag_indicator</mat-icon></div>
<div class="col-auto d-flex">
<button mat-icon-button class="action-list-icon" matTooltip="{{'DESCRIPTION-TEMPLATE-EDITOR.ACTIONS.REMOVE-SECTION' | translate}}" (click)="removeSection(sectionIndex)" [disabled]="formGroup.disabled">
<mat-icon>delete</mat-icon>
</button>
</div>
</div>
</mat-card-header>
<mat-card-content>
<div class="row">
<div class="col-6">
<mat-form-field class="w-100">
<mat-label>{{'DESCRIPTION-TEMPLATE-EDITOR.FIELDS.SECTION-NAME' | translate}}</mat-label>
<input matInput type="text" name="label" [formControl]="section.get('label')" required>
<mat-error *ngIf="section.get('label').hasError('required')">
{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</mat-form-field>
</div>
<div class="col-6">
<mat-form-field class="w-100">
<mat-label>{{'DESCRIPTION-TEMPLATE-EDITOR.FIELDS.SECTION-DESCRIPTION' | translate}}</mat-label>
<input matInput type="text" name="description" [formControl]="section.get('description')">
<mat-error *ngIf="section.get('description').hasError('required')">
{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</mat-form-field>
</div>
<div class="col-6">
<mat-form-field class="w-100">
<mat-label>{{'DESCRIPTION-TEMPLATE-EDITOR.FIELDS.SYSTEM-FIELDS' | translate}}</mat-label>
<mat-select multiple [(ngModel)]="selectedSystemFields" [ngModelOptions]="{standalone: true}">
<mat-option *ngFor="let systemFieldType of descriptionTemplateSystemFieldTypeEnum" [disabled]="systemFieldDisabled(systemFieldType)" [value]="systemFieldType">{{enumUtils.toDescriptionTemplateSystemFieldTypeString(systemFieldType)}}</mat-option>
</mat-select>
</mat-form-field>
</div>
<div class="col-auto">
<button mat-button class="action-btn" type="button" (click)="addExtraField(sectionIndex)" [disabled]="formGroup.disabled">{{'DESCRIPTION-TEMPLATE-EDITOR.ACTIONS.ADD-EXTRA-FIELD' | translate}}</button>
</div>
<div cdkDropList class="col-12" (cdkDropListDropped)="dropFields($event, sectionIndex)">
<div *ngFor="let field of section.get('fields').controls; let fieldIndex=index;" cdkDrag class="row align-items-center" [cdkDragDisabled]="formGroup.disabled">
<div class="col-auto">
<span style="font-size: 15px;">{{fieldIndex + 1}}</span>
</div>
<div class="col-auto">
<mat-icon cdkDragHandle style="cursor: move; color: #129d99;">drag_indicator</mat-icon>
</div>
<div class="col-auto" *ngIf="field.get('category').value === descriptionTemplateSectionFieldCategory.SYSTEM">
<mat-form-field class="w-100">
<mat-label>{{'DESCRIPTION-TEMPLATE-EDITOR.FIELDS.SYSTEM-FIELD' | translate}}</mat-label>
<input matInput disabled value="{{enumUtils.toDescriptionTemplateSystemFieldTypeString(field.get('systemFieldType').value)}}" type="text" name="name">
</mat-form-field>
</div>
<div class="col-auto" *ngIf="field.get('category').value === descriptionTemplateSectionFieldCategory.EXTRA">
<mat-form-field class="w-100">
<mat-label>{{'DESCRIPTION-TEMPLATE-EDITOR.FIELDS.FIELD-DATA-TYPE' | translate}}</mat-label>
<mat-select [formControl]="field.get('dataType')" required>
<mat-option *ngFor="let extraFieldDataType of descriptionTemplateExtraFieldDataTypeEnum" [value]="extraFieldDataType">
{{enumUtils.toDescriptionTemplateExtraFieldDataTypeString(extraFieldDataType)}}
</mat-option>
</mat-select>
<mat-error *ngIf="field.get('dataType').hasError('required')">
{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</mat-form-field>
</div>
<div class="col">
<mat-form-field class="w-100">
<mat-label>{{'DESCRIPTION-TEMPLATE-EDITOR.FIELDS.FIELD-LABEL' | translate}}</mat-label>
<input matInput type="text" name="label" [formControl]="field.get('label')" required>
<mat-error *ngIf="field.get('label').hasError('required')">
{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</mat-form-field>
</div>
<div class="col">
<mat-form-field class="w-100">
<mat-label>{{'DESCRIPTION-TEMPLATE-EDITOR.FIELDS.FIELD-PLACEHOLDER' | translate}}</mat-label>
<input matInput type="text" name="placeholder" [formControl]="field.get('placeholder')">
</mat-form-field>
</div>
<div class="col">
<mat-form-field class="w-100">
<mat-label>{{'DESCRIPTION-TEMPLATE-EDITOR.FIELDS.FIELD-DESCRIPTION' | translate}}</mat-label>
<input matInput type="text" name="description" [formControl]="field.get('description')">
</mat-form-field>
</div>
<div class="col-auto">
<mat-checkbox [disabled]="field.get('systemFieldType')?.value === descriptionTemplateSystemFieldType.TEXT || field.get('systemFieldType')?.value === descriptionTemplateSystemFieldType.HTML_TEXT" [formControl]="field.get('required')"><span>{{'DESCRIPTION-TEMPLATE-EDITOR.FIELDS.FIELD-REQUIRED' | translate}}</span></mat-checkbox>
</div>
<div *ngIf="field.get('category').value === descriptionTemplateSectionFieldCategory.SYSTEM" [hidden]="viewOnly" class="col-auto">
<button mat-icon-button matTooltip="{{'DESCRIPTION-TEMPLATE-EDITOR.ACTIONS.REMOVE-SYSTEM-FIELD' | translate}}" (click)="removeSystemField(sectionIndex, fieldIndex)" [disabled]="formGroup.disabled">
<mat-icon>delete</mat-icon>
</button>
</div>
<div *ngIf="field.get('category').value === descriptionTemplateSectionFieldCategory.EXTRA" [hidden]="viewOnly" class="col-auto">
<button mat-icon-button matTooltip="{{'DESCRIPTION-TEMPLATE-EDITOR.ACTIONS.REMOVE-EXTRA-FIELD' | translate}}" (click)="removeExtraField(sectionIndex, fieldIndex)" [disabled]="formGroup.disabled">
<mat-icon>delete</mat-icon>
</button>
</div>
</div>
</div>
</div>
</mat-card-content>
<div class="col-12">
<div class="row">
<div class="col-auto">
<mat-checkbox [formControl]="section.get('hasTemplates')" (change)="checkForBlueprints($event, sectionIndex)">
{{'DESCRIPTION-TEMPLATE-EDITOR.FIELDS.DESCRIPTION-TEMPLATES' | translate}}
</mat-checkbox>
</div>
</div>
</div>
<div class="col-12" *ngIf="section.get('hasTemplates').value == true">
<div class="row">
<div class="col-12">
<mat-form-field class="w-100">
<mat-label>{{'DESCRIPTION-TEMPLATE-EDITOR.FIELDS.DESCRIPTION-TEMPLATES' | translate}}</mat-label>
<app-multiple-auto-complete [disabled]="formGroup.disabled" [hidePlaceholder]="true" required='false' [configuration]="blueprintsAutoCompleteConfiguration" (optionRemoved)="onRemoveDescritionTemplate($event, sectionIndex)" (optionSelected)="onSelectDescritionTemplate($event, sectionIndex)">
</app-multiple-auto-complete>
</mat-form-field>
</div>
</div>
</div>
<div *ngFor="let descriptionTemplate of section.get('descriptionTemplates').controls; let j=index;" class="col-12">
<div class="col-12">
<div class="row">
<div class="col-4">
<mat-form-field class="w-100">
<mat-label>{{'DESCRIPTION-TEMPLATE-EDITOR.FIELDS.DESCRIPTION-TEMPLATE-LABEL' | translate}}</mat-label>
<input matInput type="text" value="descriptionTemplate.get('label')" name="label" [formControl]="descriptionTemplate.get('label')">
</mat-form-field>
</div>
<div class="col-4">
<mat-form-field class="w-100">
<mat-label>{{'DESCRIPTION-TEMPLATE-EDITOR.FIELDS.DESCRIPTION-TEMPLATE-MIN-MULTIPLICITY' | translate}}</mat-label>
<input matInput type="number" min="0" name="minMultiplicity" [formControl]="descriptionTemplate.get('minMultiplicity')">
</mat-form-field>
</div>
<div class="col-4">
<mat-form-field class="w-100">
<mat-label>{{'DESCRIPTION-TEMPLATE-EDITOR.FIELDS.DESCRIPTION-TEMPLATE-MAX-MULTIPLICITY' | translate}}</mat-label>
<input matInput type="number" min="1" name="maxMultiplicity" [formControl]="descriptionTemplate.get('maxMultiplicity')">
</mat-form-field>
</div>
</div>
</div>
</div>
</mat-card>
<div class="col-12">
<div class="heading">1.2 {{'DATASET-PROFILE-EDITOR.STEPS.GENERAL-INFO.DATASET-TEMPLATE-DESCRIPTION'| translate}} *</div>
<!-- <div class="hint">{{'DMP-EDITOR.MAIN-INFO.HINT' | translate}}</div> -->
<div class="hint">{{'DATASET-PROFILE-EDITOR.STEPS.GENERAL-INFO.DATASET-TEMPLATE-DESCRIPTION-HINT'| translate}}</div>
<div class="full-width basic-info-input">
<rich-text-editor-component [parentFormGroup]="formGroup" [controlName]="'description'" [placeholder]="'DATASET-PROFILE-EDITOR.STEPS.GENERAL-INFO.DATASET-TEMPLATE-DESCRIPTION-PLACEHOLDER'" [wrapperClasses]="(formGroup.get('description').touched && formGroup.get('description').hasError('required')) ? 'required' : ''" [editable]="formGroup.controls['description'].status !== 'DISABLED'">
</rich-text-editor-component>
<div [class]="(formGroup.get('description').touched && formGroup.get('description').hasError('required')) ? 'visible' : 'invisible'" class="mat-form-field formGroup-field-subscript-wrapper">
<mat-error>
{{'GENERAL.VALIDATION.REQUIRED'| translate}}
</mat-error>
</div>
</div>
</div>
<div class="col-12">
<div class="row">
<div class="col-auto">
<button mat-button class="action-btn" type="button" (click)="addSection()" [disabled]="formGroup.disabled">{{'DESCRIPTION-TEMPLATE-EDITOR.ACTIONS.ADD-SECTION' | translate}}</button>
<div class="heading">1.3 {{'DATASET-PROFILE-EDITOR.STEPS.GENERAL-INFO.DESCRIPTION-TEMPLATE-TYPE'| translate}} *</div>
<mat-form-field class="w-100">
<mat-label>{{'DATASET-PROFILE-EDITOR.STEPS.GENERAL-INFO.DESCRIPTION-TEMPLATE-SELECT-TYPE' | translate}}</mat-label>
<app-single-auto-complete [required]="false" [formControl]="formGroup.get('type')" placeholder="{{'DATASET-PROFILE-EDITOR.STEPS.GENERAL-INFO.DESCRIPTION-TEMPLATE-SELECT-TYPE' | translate}}" [configuration]="descriptionTemplateTypeService.singleAutocompleteConfiguration">
</app-single-auto-complete>
<mat-error *ngIf="formGroup.get('type').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</mat-form-field>
</div>
<div class="col-12">
<!-- <div class="heading">1.4 {{'DMP-EDITOR.FIELDS.LANGUAGE' | translate}}</div> -->
<div class="heading">1.4 {{'DATASET-PROFILE-EDITOR.STEPS.GENERAL-INFO.DATASET-TEMPLATE-LANGUAGE'| translate}} *</div>
<mat-form-field class="full-width basic-info-input">
<!-- <input matInput formControlName="description" placeholder="{{'DATASET-PROFILE-EDITOR.FIELDS.DATASET-DESCRIPTION' | translate}}" required> -->
<mat-select [formControl]="formGroup.get('language')" placeholder="{{'DATASET-PROFILE-EDITOR.STEPS.GENERAL-INFO.DATASET-TEMPLATE-SELECT-LANGUAGE'| translate}}">
<mat-option *ngFor="let lang of availableLanguages" [value]="lang.code">
{{ lang.name }}
</mat-option>
</mat-select>
<mat-error *ngIf="formGroup.get('language').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' |
translate}}
</mat-error>
</mat-form-field>
</div>
<!-- <div class="col-12">
<div class="heading">1.5 {{'DATASET-PROFILE-EDITOR.STEPS.GENERAL-INFO.DATASET-TEMPLATE-USERS'| translate}}</div>
<div class="hint">{{'DATASET-PROFILE-EDITOR.STEPS.GENERAL-INFO.DATASET-TEMPLATE-USERS-HINT'| translate}}</div>
<div class="full-width basic-info-input">
<table class="col-12 user-table">
<thead class="user-table-header">
<tr>
<th>{{'USERS.LISTING.NAME' | translate}}</th>
<th>{{'USERS.LISTING.EMAIL' | translate}}</th>
<th></th>
</tr>
</thead>
<tbody class="user-table-body">
<tr *ngFor="let user of userChipList" class="user-table-row">
<td>{{user.name}}</td>
<td >{{user.email}}</td>
<td>
<button mat-button class="delete-btn" (click)="verifyAndRemoveUser(user)" [matTooltip]="'DATASET-PROFILE-EDITOR.STEPS.GENERAL-INFO.DATASET-TEMPLATE-REMOVE-USER'| translate"><mat-icon>person_remove</mat-icon></button>
</td>
</tr>
<tr *ngIf="!userChipList || !userChipList.length">
<td style="text-align: end; line-height: 3em;" colspan="2" >
{{'DATASET-PROFILE-EDITOR.STEPS.GENERAL-INFO.DATASET-TEMPLATE-NO-USERS-YET' | translate}}
</td>
</tr>
</tbody>
</table>
</div>
</div> -->
<!-- <div class="col-12">
<div class="row justify-content-end">
<div class="col d-flex justify-content-end" style="overflow: hidden;">
<div style="min-width: 20em;max-width: 25em;" [@add-new-user-field]="inputUserState">
<mat-form-field>
<input matInput #email placeholder="{{'DATASET-PROFILE-EDITOR.STEPS.GENERAL-INFO.DATASET-TEMPLATE-USERS'| translate}}" (focus)="onUserFieldFocus()" (blur)="onUserFieldBlur()" (keyup.enter)="addUser(email)">
</mat-form-field>
</div>
</div>
<div class="col-auto">
<button mat-mini-fab color="primary" (click)="addUser(email)" (focus)="onUserButtonFocus()" (blur)="onUserButtonBlur()" [matTooltip]="'DATASET-PROFILE-EDITOR.STEPS.GENERAL-INFO.DATASET-TEMPLATE-VALIDATE-AND-ADD-USER'| translate" [disabled]="userFormDisabled">
<ng-container *ngIf="inputUserState === 'untriggered' else triggericon">
<mat-icon>add</mat-icon>
</ng-container>
<ng-template #triggericon>
<mat-icon>person_add</mat-icon>
</ng-template>
</button>
</div>
</div>
</div> -->
</div>
<!-- <div class="col-12">
<button mat-button class="full-width" (click)="addPage()"
[disabled]="viewOnly">{{'DATASET-PROFILE-EDITOR.ACTIONS.NEXT' | translate}}</button>
</div> -->
</div>
</mat-step>
<mat-step [label]="'DATASET-PROFILE-EDITOR.STEPS.FORM.TITLE' | translate" [completed]="formGroup.valid">
<div class="row">
<!-- TABLE -->
<div class="col-3">
<div class="row sticky-top table-container" style="top : 7em;">
<description-template-table-of-contents class="toc-pane-container col" style="margin-bottom: 2em;" [links]="toCEntries" (itemClick)="displayItem($event)" (createEntry)="addNewEntry($event)" (removeEntry)="onRemoveEntry($event)" [itemSelected]="selectedTocEntry" [viewOnly]="formGroup.disabled" (dataNeedsRefresh)="onDataNeedsRefresh($event)" [colorizeInvalid]="colorizeInvalid">
</description-template-table-of-contents>
</div>
</div>
<!-- DISPLAYER -->
<div class="col ml-3">
<div class="row">
<div class="col">
<!-- <div class="col-12" *ngIf="selectedTocEntry">
<div class="col-12 content-displayer" *ngIf="selectedTocEntry.type === tocEntryEnumValues.Page" [@fade-in-fast]>
<formGroup [formGroup]="selectedTocEntry.formGroup" class="page-infos">
<div class="row">
<div class="col-12">
<div class="heading">{{'DATASET-PROFILE-EDITOR.STEPS.PAGE-INFO.PAGE-NAME' | translate}} *</div>
<div class="hint">{{'DATASET-PROFILE-EDITOR.STEPS.PAGE-INFO.PAGE-NAME-HINT' | translate}}</div>
<mat-form-field>
<input type="text" matInput formControlName="title" [placeholder]="('DATASET-PROFILE-EDITOR.STEPS.GENERAL-INFO.UNTITLED' | translate) +' '+ ('DATASET-PROFILE-EDITOR.STEPS.PAGE-INFO.PAGE' |translate)">
<mat-error>{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</mat-form-field>
</div>
<div class="col-12" *ngIf="!viewOnly && (!selectedTocEntry?.subEntries?.length)">
<button class="create-section-btn" (click)="addNewEntry({parent:selectedTocEntry, childType: tocEntryEnumValues.Section})">{{'DATASET-PROFILE-EDITOR.STEPS.PAGE-INFO.ACTIONS.CREATE-SECTION' | translate}}</button>
</div>
</div>
</formGroup>
</div>
<div class="col-12" *ngIf="(selectedTocEntry.type === tocEntryEnumValues.Section) || (selectedTocEntry.type === tocEntryEnumValues.FieldSet)">
<app-description-template-editor-section-fieldset-component [tocentry]="selectedTocEntry" [viewOnly]="viewOnly" [datasetProfileId]="datasetProfileId" (addNewFieldSet)="addNewEntry({childType: tocEntryEnumValues.FieldSet,parent: {formGroup: $event}})" (removeFieldSet)="onRemoveEntry(_findTocEntryById($event, toCEntries))" (cloneFieldSet)="cloneFieldSet($event)" (selectedEntryId)="displayItem(_findTocEntryById($event, toCEntries))" (dataNeedsRefresh)="onDataNeedsRefresh()">
</app-description-template-editor-section-fieldset-component>
</div>
</div> -->
<!-- <div class="content-displayer row justify-content-center" *ngIf="!numOfPages" style="min-height: 30em;">
<div class="col-auto align-self-center">
<div class="row w-100 justify-content-center">
<div class="col-auto">
{{'DATASET-PROFILE-EDITOR.STEPS.PAGE-INFO.ACTIONS.NOTHING-HERE-HINT'| translate}}
</div>
</div>
<div class="row justify-content-center">
<div class="col-auto d-flex aling-contents-center">
{{'DATASET-PROFILE-EDITOR.STEPS.PAGE-INFO.ACTIONS.START-CREATING-PAGE-START'| translate}}
<mat-icon color="accent" style="font-size: 1.5em; text-align: center; width: 1.5em;">add</mat-icon>
<strong style="cursor: pointer;" (click)="addNewEntry({childType: tocEntryEnumValues.Page,parent: null})">
{{'DATASET-PROFILE-EDITOR.STEPS.PAGE-INFO.ACTIONS.START-CREATING-PAGE-END'| translate}}
</strong>
</div>
</div>
</div>
</div> -->
</div>
</div>
</div>
<div class="row mt-4">
<div class="col-auto">
<button mat-button class="action-btn" (click)="cancel()" type="button">{{'DESCRIPTION-TEMPLATE-EDITOR.ACTIONS.CANCEL' | translate}}</button>
</div>
<div class="col"></div>
<div class="col-auto">
<button mat-button class="action-btn" [disabled]="formGroup.disabled" type="submit">
{{'DESCRIPTION-TEMPLATE-EDITOR.ACTIONS.SAVE' | translate}}
</button>
</div>
</mat-step>
<mat-step [label]="'DATASET-PROFILE-EDITOR.ACTIONS.PREVIEW-AND-FINALIZE' | translate">
<ng-container *ngIf="formGroup">
<div class="col-9">
<div class="col">
<div class="col-12">
<app-final-preview-component [formGroup]="formGroup" [visibilityRules]="visibilityRules">
</app-final-preview-component>
</div>
</div>
</div>
</mat-card-content>
</mat-card>
</form>
</div> -->
</ng-container>
</mat-step>
</mat-horizontal-stepper>
<a class="scroll-on-top d-flex flex-column align-items-center" (click)="scrollOnTop()" [@scroll-on-top-btn] [matTooltip]="'DATASET-PROFILE-EDITOR.ACTIONS.BACK-TO-TOP'| translate">
<mat-icon>
arrow_upward
</mat-icon>
<div>SCROLL</div>
</a>
<ng-container *ngIf="steps && stepper">
<div class="floating-btn">
<button *ngIf="stepper?.selectedIndex > 0" [@previous_btn] mat-button class="navigate-btn" (click)="stepper?.previous()">
<mat-icon class="back-icon pointer">chevron_left</mat-icon>
{{'DMP-EDITOR.STEPPER.PREVIOUS' | translate}}
</button>
<button *ngIf="stepper?.selectedIndex < (steps.length-1)" mat-button class="navigate-btn ml-3" [@next_btn] (click)="validateStep(stepper?.selectedIndex);stepper?.next();" [ngClass]="{'navigate-btn-disabled': !isStepCompleted(stepper?.selectedIndex)}">
<span>{{'DMP-EDITOR.STEPPER.NEXT' | translate}}</span>
<mat-icon class="back-icon pointer">chevron_right</mat-icon>
</button>
</div>
</ng-container>
</div>
<ng-template #actions>
<div>
<button mat-raised-button class="template_action_btn mr-3" (click)="discardChanges()">{{'DATASET-WIZARD.ACTIONS.CLOSE' | translate}}</button>
<button *ngIf="!formGroup.disabled && formGroup.get('status').value!=1" [disabled]="!formGroup.valid" mat-raised-button class="template_action_btn save-btn" type="button">
<span class="d-flex flex-row row">
<span (click)="onSubmit()" class="col">{{'DATASET-PROFILE-EDITOR.ACTIONS.SAVE' |translate}}</span>
<mat-divider [vertical]="true"></mat-divider>
<span class="align-items-center justify-content-center col-4 d-flex" (click)="$event.stopPropagation();" [matMenuTriggerFor]="menuSave">
<mat-icon>expand_more</mat-icon>
</span>
</span>
</button>
<mat-menu #menuSave="matMenu">
<button [disabled]="!formGroup.valid" mat-menu-item (click)="onSubmit(true)" type="button">{{ 'DATASET-PROFILE-EDITOR.ACTIONS.SAVE-AND-CLOSE' | translate }}</button>
<button [disabled]="!formGroup.valid" mat-menu-item (click)="onSubmit()" type="button">{{ 'DATASET-PROFILE-EDITOR.ACTIONS.SAVE-AND-CONTINUE' | translate }}</button>
</mat-menu>
<button *ngIf="formGroup.disabled || formGroup.get('status').value==1" [@finalize_btn] mat-raised-button class="template_action_btn save-btn" [disabled]="!formGroup.valid" (click)="updateAndFinalize()">
{{'DATASET-PROFILE-EDITOR.ACTIONS.UPDATE' |translate}}
<mat-icon (click)="$event.stopPropagation();" style="width: 14px;" [matMenuTriggerFor]="menuUpdate">expand_more</mat-icon>
</button>
<mat-menu #menuUpdate="matMenu">
<button [disabled]="!formGroup.valid" mat-menu-item (click)="updateAndFinalize(true)" type="button">{{ 'DATASET-PROFILE-EDITOR.ACTIONS.UPDATE-AND-CLOSE' | translate }}</button>
<button [disabled]="!formGroup.valid" mat-menu-item (click)="updateAndFinalize()" type="button">{{ 'DATASET-PROFILE-EDITOR.ACTIONS.UPDATE-AND-CONTINUE' | translate }}</button>
</mat-menu>
<button *ngIf="!formGroup.disabled && formGroup.get('status').value!=1 && steps?.length-1 === stepper?.selectedIndex" [@finalize_btn] mat-button class="finalize-btn ml-3" [disabled]="!formGroup.valid" [class.invisible]="steps?.length-1 !== stepper?.selectedIndex" (click)="updateAndFinalize(true)">
{{'DATASET-PROFILE-EDITOR.ACTIONS.FINALIZE' |translate}}
</button>
</div>
</ng-template>

View File

@ -1,57 +1,72 @@
import { Component, OnInit } from '@angular/core';
import { FormArray, UntypedFormGroup } from '@angular/forms';
import { Component, OnInit, QueryList, ViewChild } from '@angular/core';
import { UntypedFormArray, UntypedFormGroup } from '@angular/forms';
import { MatDialog } from '@angular/material/dialog';
import { ActivatedRoute, Router } from '@angular/router';
import { SnackBarNotificationLevel, UiNotificationService } from '@app/core/services/notification/ui-notification-service';
import { EnumUtils } from '@app/core/services/utilities/enum-utils.service';
// import { BreadcrumbItem } from '@app/ui/misc/breadcrumb/definition/breadcrumb-item';
import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';
import { CdkStep, StepperSelectionEvent } from '@angular/cdk/stepper';
import { DatePipe } from '@angular/common';
import { MatStepper } from '@angular/material/stepper';
import { DatasetProfileComboBoxType } from '@app/core/common/enum/dataset-profile-combo-box-type';
import { DatasetProfileFieldViewStyle } from '@app/core/common/enum/dataset-profile-field-view-style';
import { DescriptionTemplateStatus } from '@app/core/common/enum/description-template-status';
import { IsActive } from '@app/core/common/enum/is-active.enum';
import { AppPermission } from '@app/core/common/enum/permission.enum';
import { DataTableRequest } from '@app/core/model/data-table/data-table-request';
import { DatasetProfileModel } from '@app/core/model/dataset/dataset-profile';
import { DescriptionTemplate } from '@app/core/model/description-template/description-template';
import { DatasetProfileCriteria } from '@app/core/query/dataset-profile/dataset-profile-criteria';
import { DescriptionTemplatePersist } from '@app/core/model/description-template/description-template-persist';
import { LanguageInfo } from '@app/core/model/language-info';
import { AuthService } from '@app/core/services/auth/auth.service';
import { LanguageInfoService } from '@app/core/services/culture/language-info-service';
import { DescriptionTemplateTypeService } from '@app/core/services/description-template-type/description-template-type.service';
import { DescriptionTemplateService } from '@app/core/services/description-template/description-template.service';
import { DmpService } from '@app/core/services/dmp/dmp.service';
import { LoggingService } from '@app/core/services/logging/logging-service';
import { MatomoService } from '@app/core/services/matomo/matomo-service';
import { FileUtils } from '@app/core/services/utilities/file-utils.service';
import { QueryParamsService } from '@app/core/services/utilities/query-params.service';
import { MultipleAutoCompleteConfiguration } from '@app/library/auto-complete/multiple/multiple-auto-complete-configuration';
import { BaseEditor } from '@common/base/base-editor';
import { FormService } from '@common/forms/form-service';
import { FormValidationErrorsDialogComponent } from '@common/forms/form-validation-errors-dialog/form-validation-errors-dialog.component';
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 * as FileSaver from 'file-saver';
import { Observable } from 'rxjs';
import { map, takeUntil } from 'rxjs/operators';
import { GENERAL_ANIMATIONS, STEPPER_ANIMATIONS } from './animations/animations';
import { DescriptionTemplateEditorModel, DescriptionTemplateFieldEditorModel, DescriptionTemplateFieldSetEditorModel, DescriptionTemplatePageEditorModel, DescriptionTemplateSectionEditorModel } from './description-template-editor.model';
import { DescriptionTemplateEditorResolver } from './description-template-editor.resolver';
import { DescriptionTemplateEditorService } from './description-template-editor.service';
import { DescriptionTemplateEditorModel } from './description-template-editor.model';
import { DescriptionTemplateService } from '@app/core/services/description-template/description-template.service';
import { DescriptionTemplatePersist } from '@app/core/model/description-template/description-template-persist';
import { NewEntryType, ToCEntry, ToCEntryType } from './table-of-contents/description-template-table-of-contents-entry';
@Component({
selector: 'app-description-template-editor-component',
templateUrl: 'description-template-editor.component.html',
styleUrls: ['./description-template-editor.component.scss'],
animations: [...STEPPER_ANIMATIONS, ...GENERAL_ANIMATIONS],
providers: [DescriptionTemplateEditorService]
})
export class DescriptionTemplateEditorComponent extends BaseEditor<DescriptionTemplateEditorModel, DescriptionTemplate> implements OnInit {
@ViewChild('stepper') stepper: MatStepper;
isNew = true;
isDeleted = false;
formGroup: UntypedFormGroup = null;
showInactiveDetails = false;
availableLanguages: LanguageInfo[] = this.languageInfoService.getLanguageInfoValues();
isNewVersion = false;
isClone = false;
steps: QueryList<CdkStep>;
toCEntries: ToCEntry[];
selectedTocEntry: ToCEntry;
colorizeInvalid: boolean = false;
// selectedSystemFields: Array<DescriptionTemplateSystemFieldType> = [];
// descriptionTemplateSectionFieldCategory = DescriptionTemplateSectionFieldCategory;
// descriptionTemplateSystemFieldType = DescriptionTemplateSystemFieldType;
@ -101,11 +116,13 @@ export class DescriptionTemplateEditorComponent extends BaseEditor<DescriptionTe
public authService: AuthService,
public enumUtils: EnumUtils,
private descriptionTemplateService: DescriptionTemplateService,
public descriptionTemplateTypeService: DescriptionTemplateTypeService,
private logger: LoggingService,
private descriptionTemplateEditorService: DescriptionTemplateEditorService,
private fileUtils: FileUtils,
private matomoService: MatomoService,
private dmpService: DmpService
private dmpService: DmpService,
private languageInfoService: LanguageInfoService
) {
super(dialog, language, formService, router, uiNotificationService, httpErrorHandlingService, filterService, datePipe, route, queryParamsService);
}
@ -129,6 +146,12 @@ export class DescriptionTemplateEditorComponent extends BaseEditor<DescriptionTe
this.editorModel = data ? new DescriptionTemplateEditorModel().fromModel(data) : new DescriptionTemplateEditorModel();
this.isDeleted = data ? data.isActive === IsActive.Inactive : false;
this.buildForm();
setTimeout(() => {
this.steps = this.stepper.steps;
});
this._initializeToCEntries();
} catch (error) {
this.logger.error('Could not parse descriptionTemplate item: ' + data + error);
this.uiNotificationService.snackBarNotification(this.language.instant('COMMONS.ERRORS.DEFAULT'), SnackBarNotificationLevel.Error);
@ -210,6 +233,769 @@ export class DescriptionTemplateEditorComponent extends BaseEditor<DescriptionTe
this.formService.validateAllFormFields(this.formGroup);
}
//
//
// Stepper
//
//
onMatStepperSelectionChange(event: StepperSelectionEvent) {
if (event.selectedIndex === (this.steps.length - 1)) {//preview selected
// this.generatePreviewForm();//TODO LAZY LOADING IN THE TEMPLATE
// this.getPreview();
} else {
// this.previewForm = null;
// this.formGroup = null;
}
this.formGroup.markAsUntouched();
}
isStepCompleted(stepIndex: number) {
let stepCompleted = false;
this.steps.forEach((step, index) => {
if (stepIndex === index) {
stepCompleted = step.completed;
}
});
return stepCompleted;
}
isStepUnlocked(stepIndex: number): boolean {
if (stepIndex === 0) return true;
if (stepIndex < 0) return false;
//if previous step is valid then unlock
let stepUnlocked: boolean = false;
if (!this.isStepUnlocked(stepIndex - 1)) return false;
this.steps.forEach((step, index) => {
if (index + 1 == stepIndex) {//previous step
if (step.completed) {
stepUnlocked = true;
}
}
});
return stepUnlocked;
}
validateStep(selectedIndex) {
if (selectedIndex === 1) {//form description
if (this.formGroup.invalid) {
this.checkFormValidation();
}
}
}
//
//
// Table of Contents
//
//
private _initializeToCEntries() {
const tocentries = this.refreshToCEntries();//tocentries are sorted based on their ordinal value
this._updateOrdinals(tocentries);
if (tocentries && tocentries.length) {
this.selectedTocEntry = tocentries[0];
}
//Checking invalid visibilty RULES
const fieldsetEntries = this._getAllFieldSets(this.toCEntries);
const fieldSetHavingInvalidVisibilityRules: ToCEntry[] = fieldsetEntries
.filter(entry => {
const fieldsFormGroup = entry.form.get('fields');
const invalid = (fieldsFormGroup as UntypedFormArray).controls.filter(field => {
return this.hasInvalidVisibilityRule(field as UntypedFormGroup);
});
if (invalid && invalid.length) {
return true;
}
return false;
});
if (fieldSetHavingInvalidVisibilityRules.length) {
const occurences = fieldSetHavingInvalidVisibilityRules.map(record => record.numbering).join(' , ');
this.dialog.open(ConfirmationDialogComponent, {
data: {
message: this.language.instant('DATASET-PROFILE-EDITOR.ERRORS.INVALID-VISIBILITY-RULES.MESSAGE-START') + occurences + this.language.instant('DATASET-PROFILE-EDITOR.ERRORS.INVALID-VISIBILITY-RULES.MESSAGE-END'),
confirmButton: this.language.instant('DATASET-PROFILE-EDITOR.ERRORS.INVALID-VISIBILITY-RULES.CONFIRM-YES'),
cancelButton: this.language.instant('DATASET-PROFILE-EDITOR.ERRORS.INVALID-VISIBILITY-RULES.CONFIRM-NO')
},
maxWidth: '30em'
})
.afterClosed()
.subscribe(confirm => {
if (confirm) {
this.removeFieldSetVisibilityRules(fieldSetHavingInvalidVisibilityRules);
this.uiNotificationService.snackBarNotification(this.language.instant('DATASET-PROFILE-EDITOR.ERRORS.INVALID-VISIBILITY-RULES.REMOVE-SUCCESS'), SnackBarNotificationLevel.Success);
} else {
console.log('User not confirmed');
}
})
}
}
private refreshToCEntries(): ToCEntry[] {
this.toCEntries = this.getTocEntries();
//update selected tocentry
if (this.selectedTocEntry) {
this.selectedTocEntry = this._findTocEntryById(this.selectedTocEntry.id, this.toCEntries);
}
// this.updateOrdinals(this.toCEntries);
// this._updateNumbering(this.toCEntries);
return this.toCEntries;
}
/**
* Updates entries ordinal form value
* based on the index they have on the tocentry array.
* Tocentries that are on the same level have distinct ordinal value
*
* @param tocentries
*
*/
private _updateOrdinals(tocentries: ToCEntry[]) {
if (!tocentries || !tocentries.length) return;
tocentries.forEach((e, idx) => {
const ordinalControl = e.form.get('ordinal');
if (ordinalControl) {
ordinalControl.setValue(idx);
ordinalControl.updateValueAndValidity();
}
this._updateOrdinals(e.subEntries);
});
}
getTocEntries(): ToCEntry[] {
if (this.formGroup == null) { return []; }
const result: ToCEntry[] = [];
//build parent pages
(this.formGroup.get('pages') as UntypedFormArray).controls.forEach((pageElement, i) => {
result.push({
id: pageElement.get('id').value,
label: pageElement.get('title').value,
type: ToCEntryType.Page,
form: pageElement,
numbering: (i + 1).toString(),
subEntriesType: ToCEntryType.Section
} as ToCEntry)
});
// build first level sections
(this.formGroup.get('sections') as UntypedFormArray).controls.forEach((sectionElement, i) => {
const currentSectionPageId = sectionElement.get('page').value;
const pageToAdd = result.filter(x => x.id == currentSectionPageId)[0];
if (pageToAdd.subEntries == null) pageToAdd.subEntries = [];
const item = {
id: sectionElement.get('id').value,
label: sectionElement.get('title').value,
type: ToCEntryType.Section,
form: sectionElement,
numbering: pageToAdd.numbering + '.' + (pageToAdd.subEntries.length + 1)
} as ToCEntry;
const sectionItems = this.populateSections(sectionElement.get('sections') as UntypedFormArray, item.numbering);
const fieldSetItems = this.populateFieldSets(sectionElement.get('fieldSets') as UntypedFormArray, item.numbering);
if (sectionItems != null) {
item.subEntries = sectionItems;
item.subEntriesType = ToCEntryType.Section;
}
if (fieldSetItems != null) {
if (item.subEntries == null) {
item.subEntries = fieldSetItems;
} else {
item.subEntries.push(...fieldSetItems);
}
item.subEntriesType = ToCEntryType.FieldSet;
}
pageToAdd.subEntries.push(item);
});
this._sortToCentries(result);//ordeby ordinal
this._updateNumbering(result, '');//update nubering if needed
return result;
}
private populateSections(sections: UntypedFormArray, existingNumbering: string): ToCEntry[] {
if (sections == null || sections.controls == null || sections.controls.length == 0) { return null; }
const result: ToCEntry[] = [];
sections.controls.forEach((sectionElement, i) => {
const item = {
id: sectionElement.get('id').value,
label: sectionElement.get('title').value,
type: ToCEntryType.Section,
form: sectionElement,
numbering: existingNumbering + '.' + (i + 1)
} as ToCEntry;
const sectionItems = this.populateSections(sectionElement.get('sections') as UntypedFormArray, item.numbering);
const fieldSetItems = this.populateFieldSets(sectionElement.get('fieldSets') as UntypedFormArray, item.numbering);
if (sectionItems != null) {
item.subEntries = sectionItems;
item.subEntriesType = ToCEntryType.Section;
}
if (fieldSetItems != null) {
if (item.subEntries == null) {
item.subEntries = fieldSetItems;
} else {
item.subEntries.push(...fieldSetItems);
}
item.subEntriesType = ToCEntryType.FieldSet;
}
result.push(item);
});
return result;
}
private populateFieldSets(fieldSets: UntypedFormArray, existingNumbering: string): ToCEntry[] {
if (fieldSets == null || fieldSets.controls == null || fieldSets.controls.length == 0) { return null; }
const result: ToCEntry[] = [];
fieldSets.controls.forEach((fieldSetElement, i) => {
result.push({
id: fieldSetElement.get('id').value,
label: fieldSetElement.get('title').value,
type: ToCEntryType.FieldSet,
//subEntries: this.populateSections((fieldSetElement.get('fieldSets') as FormArray), existingNumbering + '.' + i),
form: fieldSetElement,
numbering: existingNumbering + '.' + (i + 1)
} as ToCEntry)
});
return result;
}
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;
}
addNewEntry(tce: NewEntryType) {
const parent = tce.parent;
//define entry type
switch (tce.childType) {
case ToCEntryType.Page:
const pagesArray = (this.formGroup.get('pages') as UntypedFormArray);
const page: DescriptionTemplatePageEditorModel = new DescriptionTemplatePageEditorModel();
if (isNaN(pagesArray.length)) { page.ordinal = 0; } else { page.ordinal = pagesArray.length; }
const pageForm = page.buildForm();
// this.dataModel.pages.push(page);
pagesArray.push(pageForm);
// this.form.updateValueAndValidity();
this.refreshToCEntries();
this.selectedTocEntry = this._findTocEntryById(pageForm.get('id').value, this.toCEntries);
break;
case ToCEntryType.Section:
const section: DescriptionTemplateSectionEditorModel = new DescriptionTemplateSectionEditorModel();
section.id = Guid.create();
let sectionsArray: UntypedFormArray;
if (parent.type === ToCEntryType.Page) {//FIRST LEVEL SECTION
sectionsArray = this.formGroup.get('sections') as UntypedFormArray;
section.page = parent.id;
try {
const max = sectionsArray.controls.filter(control => control.get('page').value === parent.id)
.map(control => control.get('ordinal').value)
.reduce((a, b) => Math.max(a, b));
section.ordinal = max + 1;
} catch {
section.ordinal = sectionsArray.length;
}
sectionsArray.push(section.buildForm());
// this.form.updateValueAndValidity();
} else if (parent.type == ToCEntryType.Section) { //SUBSECTION OF SECTION
sectionsArray = parent.form.get('sections') as UntypedFormArray;
//adding page parent MAYBE NOT NEEDED
section.page = parent.form.get('page').value;
try {
const maxOrdinal = sectionsArray.controls.map(control => control.get('ordinal').value).reduce((a, b) => Math.max(a, b));
section.ordinal = maxOrdinal + 1;
} catch {
section.ordinal = sectionsArray.length;
}
sectionsArray.push(section.buildForm());
// (child.form.parent as FormArray).push(section.buildForm());
} else {
console.error('Section can only be child of a page or another section');
}
const sectionAdded = sectionsArray.at(sectionsArray.length - 1) as UntypedFormGroup;
// sectionAdded.setValidators(this.customEditorValidators.sectionHasAtLeastOneChildOf('fieldSets','sections'));
// sectionAdded.updateValueAndValidity();
this.refreshToCEntries();
this.selectedTocEntry = this._findTocEntryById(sectionAdded.get('id').value, this.toCEntries);
break;
case ToCEntryType.FieldSet:
//create one field form fieldset
const field: DescriptionTemplateFieldEditorModel = new DescriptionTemplateFieldEditorModel();
field.id = Guid.create();
field.ordinal = 0;//first filed in the fields list
const fieldForm = field.buildForm();
// fieldForm.setValidators(this.customFieldValidator());
// fieldForm.get('viewStyle').get('renderStyle').setValidators(Validators.required);
// fieldSet.fields.push(field);
// field.ordinal = fieldSet.fields.length-1;
const fieldSetsArray = parent.form.get('fieldSets') as UntypedFormArray
//give fieldset id and ordinal
const fieldSet: DescriptionTemplateFieldSetEditorModel = new DescriptionTemplateFieldSetEditorModel();
const fieldSetId = Guid.create();
fieldSet.id = fieldSetId;
try {
const maxOrdinal = fieldSetsArray.controls.map(control => control.get('ordinal').value).reduce((a, b) => Math.max(a, b));
fieldSet.ordinal = maxOrdinal + 1;
} catch {
fieldSet.ordinal = fieldSetsArray.length;
}
const fieldsetForm = fieldSet.buildForm();
(fieldsetForm.get('fields') as UntypedFormArray).push(fieldForm);
fieldSetsArray.push(fieldsetForm);
this.refreshToCEntries();
this.selectedTocEntry = this._findTocEntryById(fieldSetId.toString(), this.toCEntries);
// fieldForm.updateValueAndValidity();
break;
default:
break;
}
this.formGroup.updateValueAndValidity();
}
onRemoveEntry(tce: ToCEntry) {
const dialogRef = this.dialog.open(ConfirmationDialogComponent, {
restoreFocus: false,
data: {
message: this.language.instant('GENERAL.CONFIRMATION-DIALOG.DELETE-ITEM'),
confirmButton: this.language.instant('GENERAL.CONFIRMATION-DIALOG.ACTIONS.CONFIRM'),
cancelButton: this.language.instant('GENERAL.CONFIRMATION-DIALOG.ACTIONS.CANCEL'),
isDeleteConfirmation: true
}
});
dialogRef.afterClosed().pipe(takeUntil(this._destroyed)).subscribe(result => {
if (result) {
this._deleteEntry(tce);
}
});
}
private _deleteEntry(tce: ToCEntry) {
//define entry type
switch (tce.type) {
case ToCEntryType.Page:
const pages = this.formGroup.get('pages') as UntypedFormArray;
let pageIndex = -1;
//get the index
for (let i = 0; i < pages.length; i++) {
let page = pages.at(i) as UntypedFormGroup;
if (page.controls.id.value === tce.id) {
pageIndex = i;
break;
}
}
if (pageIndex >= 0) {
//remove page
this._updateSelectedItem(tce);
pages.removeAt(pageIndex);
//clean up sections of removed page
const sections = (this.formGroup.get('sections') as UntypedFormArray);
const sectionsIndexToBeRemoved = [];
sections.controls.forEach((section, idx) => {
if (section.get('page').value === tce.id) {
sectionsIndexToBeRemoved.push(idx);
}
});
if (sectionsIndexToBeRemoved.length) {
sectionsIndexToBeRemoved.reverse().forEach(index => {
sections.removeAt(index);
});
}
//update page ordinals
for (let i = 0; i < pages.length; i++) {
pages.at(i).get('ordinal').patchValue(i);
}
//update validity
// this.form.controls.sections.updateValueAndValidity();
}
break;
case ToCEntryType.Section:
//FIRST LEVEL SECTION CASE
let index = -1;
const sections = (this.formGroup.get('sections') as UntypedFormArray);
for (let i = 0; i < sections.length; i++) {
let section = sections.at(i);
let sectionId = section.get('id').value;
if (sectionId == tce.id) {
index = i;
break;
}
}
if (index >= 0) { //section found
const sections = (this.formGroup.get('sections') as UntypedFormArray);
//remove section
this._updateSelectedItem(tce);
sections.removeAt(index);
//update ordinal
for (let i = 0; i < sections.length; i++) {
sections.at(i).get('ordinal').patchValue(i);
}
} else {//NOT FOUND IN FIRST LEVEL CASE
//LOOK FOR SUBSECTION CASE
let parentFormArray = tce.form.parent as UntypedFormArray;
for (let i = 0; i < parentFormArray.length; i++) {
let section = parentFormArray.at(i);
if (section.get('id').value == tce.id) {
index = i;
break;
}
}
if (index >= 0) {
this._updateSelectedItem(tce);
parentFormArray.removeAt(index);
//update odrinal
for (let i = 0; i < parentFormArray.length; i++) {
parentFormArray.at(i).get('ordinal').patchValue(i);
}
}
}
break;
case ToCEntryType.FieldSet:
const parentFormArray = tce.form.parent as UntypedFormArray;
let idx = -1;
for (let i = 0; i < parentFormArray.length; i++) {
let inspectingField = parentFormArray.at(i);
if (inspectingField.get('id').value === tce.id) {
//fieldset found
idx = i;
break;
}
}
if (idx >= 0) {//fieldset found
this._updateSelectedItem(tce);
parentFormArray.removeAt(idx);
//patching order
for (let i = 0; i < parentFormArray.length; i++) {
parentFormArray.at(i).get('ordinal').patchValue(i);
}
}
break;
default:
break;
}
//in case selectedtocentrhy is child of the removed element
// this.refreshToCEntries();
this.onDataNeedsRefresh();
this.formGroup.updateValueAndValidity();
}
private _updateSelectedItem(tce: ToCEntry) {
if (this.selectedTocEntry) {
if (this.tocEntryIsChildOf(this.selectedTocEntry, tce)) {
if (this.selectedTocEntry.type == ToCEntryType.Page) {
this.selectedTocEntry = null;
} else {
//if first level section
const firstLevelSections = (this.formGroup.get('sections') as UntypedFormArray);
let isFirstLevel: boolean = false;
firstLevelSections.controls.forEach(section => {
if (section.get('id').value === tce.id) {
isFirstLevel = true;
}
});
let parentId = null;
if (isFirstLevel) {
parentId = tce.form.get('page').value;
} else {
parentId = tce.form.parent.parent.get('id').value
}
// const parentId = tce.form.parent.parent.get('id').value;
if (parentId) {
const tocentries = this.getTocEntries();
const parent = this._findTocEntryById(parentId, tocentries);
if (parent) {
this.selectedTocEntry = parent;
} else {
this.selectedTocEntry = null;
}
} else {
this.selectedTocEntry = null;
}
}
}
}
}
tocEntryIsChildOf(testingChild: ToCEntry, parent: ToCEntry): boolean {
if (!testingChild || !parent) return false;
if (testingChild.id == parent.id) { return true; }
if (parent.subEntries) {
let childFound: boolean = false;
parent.subEntries.forEach(subEntry => {
if (this.tocEntryIsChildOf(testingChild, subEntry)) {
childFound = true;
return true;
}
})
return childFound;
}
return false;
}
onDataNeedsRefresh(params?) {
const tocentries = this.refreshToCEntries();
if (params && params.draggedItemId) {
if (params.draggedItemId) {
this.displayItem(this._findTocEntryById(params.draggedItemId, tocentries));
}
}
this.formGroup.markAsDirty();
}
displayItem(entry: ToCEntry): void {
this.selectedTocEntry = entry;
}
/**
* 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;
}
private _sortToCentries(entries: ToCEntry[]) {
if (!entries || !entries.length) return;
entries.sort(this._compareOrdinals);
entries.forEach(e => {
this._sortToCentries(e.subEntries)
});
}
private _compareOrdinals(a, b) {
const aValue = a.form.get('ordinal').value as number;
const bValue = b.form.get('ordinal').value as number;
// if(!aValue || !bValue) return 0;
return aValue - bValue;
}
private _updateNumbering(entries: ToCEntry[], parentNumbering: string) {
if (!entries || !entries.length) return;
let prefix = '';
if (parentNumbering.length) {
prefix = parentNumbering + '.';
}
entries.forEach((entry, index) => {
const numbering = prefix + (index + 1);
entry.numbering = numbering;
this._updateNumbering(entry.subEntries, numbering);
})
}
//
//
// Visibility Rules
//
//
private hasInvalidVisibilityRule(field: UntypedFormGroup): boolean {
const renderStyle = field.get('viewStyle').get('renderStyle').value;
if (renderStyle && ![
DatasetProfileFieldViewStyle.TextArea,
DatasetProfileFieldViewStyle.RichTextArea,
DatasetProfileFieldViewStyle.Upload,
DatasetProfileFieldViewStyle.FreeText,
DatasetProfileFieldViewStyle.BooleanDecision,
DatasetProfileFieldViewStyle.RadioBox,
DatasetProfileFieldViewStyle.CheckBox,
DatasetProfileFieldViewStyle.DatePicker,
DatasetProfileFieldViewStyle.ComboBox,
].includes(renderStyle)) {
if (((renderStyle === DatasetProfileFieldViewStyle) && (field.get('data').get('type').value === DatasetProfileComboBoxType.WordList))) {
return false;
}
try {
if (field.get('visible').get('rules').value.length) {
return true;
}
return false;
} catch {
return false;
}
} else {
return false;
}
}
private removeFieldSetVisibilityRules(fieldsets: ToCEntry[]) {
if (!fieldsets || !fieldsets.length) return;
fieldsets.forEach(fieldset => {
if (fieldset.type != ToCEntryType.FieldSet) {
return;
}
const fields = fieldset.form.get('fields') as UntypedFormArray;
fields.controls.forEach(fieldControl => {
if (this.hasInvalidVisibilityRule(fieldControl as UntypedFormGroup)) {
try {
(fieldControl.get('visible').get('rules') as UntypedFormArray).clear();
} catch { }
}
})
})
}
//
//
// Other
//
//
scrollOnTop() {
try {
const topPage = document.getElementById('main-content');
topPage.scrollIntoView({ behavior: 'smooth' });
} catch {
console.log('coulnd not scroll');
}
}
checkFormValidation() {
this.colorizeInvalid = true;
// this.printMyErrors(this.form);
}
// //
// //
// // Sections
@ -752,23 +1538,23 @@ export class DescriptionTemplateEditorComponent extends BaseEditor<DescriptionTe
// }
// finalize() {
// if (this.checkValidity()) {
// this.formGroup.get('status').setValue(DescriptionTemplateStatus.Finalized);
// this.formSubmit();
// }
// }
// finalize() {
// if (this.checkValidity()) {
// this.formGroup.get('status').setValue(DescriptionTemplateStatus.Finalized);
// this.formSubmit();
// }
// }
// downloadXML(): void {
// const blueprintId = this.formGroup.get('id').value;
// if (blueprintId == null) return;
// this.descriptionTemplateService.downloadXML(blueprintId)
// .pipe(takeUntil(this._destroyed))
// .subscribe(response => {
// const blob = new Blob([response.body], { type: 'application/xml' });
// const filename = this.fileUtils.getFilenameFromContentDispositionHeader(response.headers.get('Content-Disposition'));
// downloadXML(): void {
// const blueprintId = this.formGroup.get('id').value;
// if (blueprintId == null) return;
// this.descriptionTemplateService.downloadXML(blueprintId)
// .pipe(takeUntil(this._destroyed))
// .subscribe(response => {
// const blob = new Blob([response.body], { type: 'application/xml' });
// const filename = this.fileUtils.getFilenameFromContentDispositionHeader(response.headers.get('Content-Disposition'));
// FileSaver.saveAs(blob, filename);
// });
// }
// FileSaver.saveAs(blob, filename);
// });
// }
}

View File

@ -1,18 +1,21 @@
import { UntypedFormBuilder, UntypedFormGroup, Validators } from "@angular/forms";
import { DescriptionTemplateFieldValidationType } from "@app/core/common/enum/description-template-field-validation-type";
import { DescriptionTemplateStatus } from "@app/core/common/enum/description-template-status";
import { DescriptionTemplate, DescriptionTemplateDefinition } from "@app/core/model/description-template/description-template";
import { DescriptionTemplateDefinitionPersist, DescriptionTemplatePersist } from "@app/core/model/description-template/description-template-persist";
import { DescriptionTemplate, DescriptionTemplateDefinition, DescriptionTemplateField, DescriptionTemplateFieldSet, DescriptionTemplateMultiplicity, DescriptionTemplatePage, DescriptionTemplateRule, DescriptionTemplateSection } from "@app/core/model/description-template/description-template";
import { DescriptionTemplateBaseFieldDataPersist, DescriptionTemplateDefinitionPersist, DescriptionTemplateFieldPersist, DescriptionTemplateFieldSetPersist, DescriptionTemplateMultiplicityPersist, DescriptionTemplatePagePersist, DescriptionTemplatePersist, DescriptionTemplateRulePersist, DescriptionTemplateSectionPersist } from "@app/core/model/description-template/description-template-persist";
import { BaseEditorModel } from "@common/base/base-form-editor-model";
import { BackendErrorValidator } from "@common/forms/validation/custom-validator";
import { ValidationErrorModel } from "@common/forms/validation/error-model/validation-error-model";
import { Validation, ValidationContext } from "@common/forms/validation/validation-context";
import { Guid } from "@common/types/guid";
export class DescriptionTemplateEditorModel extends BaseEditorModel { // implements DescriptionTemplatePersist {
export class DescriptionTemplateEditorModel extends BaseEditorModel implements DescriptionTemplatePersist {
label: string;
// definition: DescriptionTemplateDefinitionEditorModel;
status: DescriptionTemplateStatus = DescriptionTemplateStatus.Draft;
description: string;
language: string;
type: Guid;
status: DescriptionTemplateStatus = DescriptionTemplateStatus.Draft;
definition: DescriptionTemplateDefinitionEditorModel;
permissions: string[];
public validationErrorModel: ValidationErrorModel = new ValidationErrorModel();
@ -26,7 +29,7 @@ export class DescriptionTemplateEditorModel extends BaseEditorModel { // impleme
this.label = item.label;
this.status = item.status;
this.description = item.description;
//this.definition = new DescriptionTemplateDefinitionEditorModel().fromModel(item.definition);
this.definition = new DescriptionTemplateDefinitionEditorModel().fromModel(item.definition);
}
return this;
}
@ -37,10 +40,13 @@ export class DescriptionTemplateEditorModel extends BaseEditorModel { // impleme
return this.formBuilder.group({
id: [{ value: this.id, disabled: disabled }, context.getValidation('id').validators],
label: [{ value: this.label, disabled: disabled }, context.getValidation('label').validators],
description: [{ value: this.description, disabled: disabled }, context.getValidation('description').validators],
language: [{ value: this.language, disabled: disabled }, context.getValidation('language').validators],
type: [{ value: this.type, disabled: disabled }, context.getValidation('type').validators],
status: [{ value: this.status, disabled: disabled }, context.getValidation('status').validators],
// definition: this.definition.buildForm({
// rootPath: `definition.`
// }),
definition: this.definition.buildForm({
rootPath: `definition.`
}),
hash: [{ value: this.hash, disabled: disabled }, context.getValidation('hash').validators]
});
}
@ -50,6 +56,10 @@ export class DescriptionTemplateEditorModel extends BaseEditorModel { // impleme
const baseValidationArray: Validation[] = new Array<Validation>();
baseValidationArray.push({ key: 'id', validators: [BackendErrorValidator(this.validationErrorModel, 'id')] });
baseValidationArray.push({ key: 'label', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, 'label')] });
baseValidationArray.push({ key: 'description', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, 'description')] });
baseValidationArray.push({ key: 'language', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, 'language')] });
baseValidationArray.push({ key: 'type', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, 'type')] });
baseValidationArray.push({ key: 'type', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, 'type')] });
baseValidationArray.push({ key: 'status', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, 'status')] });
baseValidationArray.push({ key: 'hash', validators: [] });
@ -58,236 +68,527 @@ export class DescriptionTemplateEditorModel extends BaseEditorModel { // impleme
}
}
// export class DescriptionTemplateDefinitionEditorModel implements DescriptionTemplateDefinitionPersist {
// sections: DescriptionTemplateDefinitionSectionEditorModel[] = [];
export class DescriptionTemplateDefinitionEditorModel implements DescriptionTemplateDefinitionPersist {
pages: DescriptionTemplatePageEditorModel[] = [];
sections: DescriptionTemplateSectionEditorModel[] = [];
// protected formBuilder: UntypedFormBuilder = new UntypedFormBuilder();
protected formBuilder: UntypedFormBuilder = new UntypedFormBuilder();
// constructor(
// public validationErrorModel: ValidationErrorModel = new ValidationErrorModel()
// ) { }
constructor(
public validationErrorModel: ValidationErrorModel = new ValidationErrorModel()
) { }
// public fromModel(item: DescriptionTemplateDefinition): DescriptionTemplateDefinitionEditorModel {
// if (item) {
// if (item.sections) { item.sections.map(x => this.sections.push(new DescriptionTemplateDefinitionSectionEditorModel().fromModel(x))); }
// }
// return this;
// }
public fromModel(item: DescriptionTemplateDefinition): DescriptionTemplateDefinitionEditorModel {
if (item) {
if (item.pages) { item.pages.map(x => this.pages.push(new DescriptionTemplatePageEditorModel().fromModel(x))); }
if (item.sections) { item.sections.map(x => this.sections.push(new DescriptionTemplateSectionEditorModel().fromModel(x))); }
}
return this;
}
// buildForm(params?: {
// context?: ValidationContext,
// disabled?: boolean,
// rootPath?: string
// }): UntypedFormGroup {
// let { context = null, disabled = false, rootPath } = params ?? {}
// if (context == null) {
// context = DescriptionTemplateDefinitionEditorModel.createValidationContext({
// validationErrorModel: this.validationErrorModel,
// rootPath
// });
// }
buildForm(params?: {
context?: ValidationContext,
disabled?: boolean,
rootPath?: string
}): UntypedFormGroup {
let { context = null, disabled = false, rootPath } = params ?? {}
if (context == null) {
context = DescriptionTemplateDefinitionEditorModel.createValidationContext({
validationErrorModel: this.validationErrorModel,
rootPath
});
}
// return this.formBuilder.group({
// sections: this.formBuilder.array(
// (this.sections ?? []).map(
// (item, index) => new DescriptionTemplateDefinitionSectionEditorModel(
// this.validationErrorModel
// ).fromModel(item).buildForm({
// rootPath: `sections[${index}].`
// }), context.getValidation('sections')
// )
// ),
// });
// }
return this.formBuilder.group({
pages: this.formBuilder.array(
(this.pages ?? []).map(
(item, index) => new DescriptionTemplatePageEditorModel(
this.validationErrorModel
).fromModel(item).buildForm({
rootPath: `pages[${index}].`
}), context.getValidation('pages')
)
),
sections: this.formBuilder.array(
(this.sections ?? []).map(
(item, index) => new DescriptionTemplateSectionEditorModel(
this.validationErrorModel
).fromModel(item).buildForm({
rootPath: `sections[${index}].`
}), context.getValidation('sections')
)
),
});
}
// static createValidationContext(params: {
// rootPath?: string,
// validationErrorModel: ValidationErrorModel
// }): ValidationContext {
// const { rootPath = '', validationErrorModel } = params;
static createValidationContext(params: {
rootPath?: string,
validationErrorModel: ValidationErrorModel
}): ValidationContext {
const { rootPath = '', validationErrorModel } = params;
// const baseContext: ValidationContext = new ValidationContext();
// const baseValidationArray: Validation[] = new Array<Validation>();
// baseValidationArray.push({ key: 'sections', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}sections`)] });
// baseContext.validation = baseValidationArray;
// return baseContext;
// }
const baseContext: ValidationContext = new ValidationContext();
const baseValidationArray: Validation[] = new Array<Validation>();
baseValidationArray.push({ key: 'pages', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}pages`)] });
baseValidationArray.push({ key: 'sections', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}sections`)] });
// }
baseContext.validation = baseValidationArray;
return baseContext;
}
// export class DescriptionTemplateDefinitionSectionEditorModel implements DescriptionTemplateDefinitionSectionPersist {
// id: Guid;
// label: string;
// description: string;
// ordinal: number;
// fields: FieldInSectionEditorModel[] = [];
// hasTemplates: boolean;
// descriptionTemplates?: DescriptionTemplatesInSectionEditorModel[] = [];
}
// protected formBuilder: UntypedFormBuilder = new UntypedFormBuilder();
export class DescriptionTemplatePageEditorModel implements DescriptionTemplatePagePersist {
id: Guid;
ordinal: number;
title: string;
// constructor(
// public validationErrorModel: ValidationErrorModel = new ValidationErrorModel()
// ) { }
protected formBuilder: UntypedFormBuilder = new UntypedFormBuilder();
// public fromModel(item: DescriptionTemplateDefinitionSection): DescriptionTemplateDefinitionSectionEditorModel {
// if (item) {
// this.id = item.id;
// this.label = item.label;
// this.description = item.description;
// this.ordinal = item.ordinal;
// this.hasTemplates = item.hasTemplates;
// if (item.fields) { item.fields.map(x => this.fields.push(new FieldInSectionEditorModel().fromModel(x))); }
// if (item.descriptionTemplates) { item.descriptionTemplates.map(x => this.descriptionTemplates.push(new DescriptionTemplatesInSectionEditorModel().fromModel(x))); }
// }
// return this;
// }
constructor(
public validationErrorModel: ValidationErrorModel = new ValidationErrorModel()
) { }
// buildForm(params?: {
// context?: ValidationContext,
// disabled?: boolean,
// rootPath?: string
// }): UntypedFormGroup {
// let { context = null, disabled = false, rootPath } = params ?? {}
// if (context == null) {
// context = DescriptionTemplateDefinitionSectionEditorModel.createValidationContext({
// validationErrorModel: this.validationErrorModel,
// rootPath
// });
// }
public fromModel(item: DescriptionTemplatePage): DescriptionTemplatePageEditorModel {
if (item) {
this.id = item.id;
this.ordinal = item.ordinal;
this.title = item.title;
}
return this;
}
// return this.formBuilder.group({
// id: [{ value: this.id, disabled: disabled }, context.getValidation('id').validators],
// label: [{ value: this.label, disabled: disabled }, context.getValidation('label').validators],
// ordinal: [{ value: this.ordinal, disabled: disabled }, context.getValidation('ordinal').validators],
// description: [{ value: this.description, disabled: disabled }, context.getValidation('description').validators],
// hasTemplates: [{ value: this.hasTemplates, disabled: disabled }, context.getValidation('hasTemplates').validators],
// fields: this.formBuilder.array(
// (this.fields ?? []).map(
// (item, index) => new FieldInSectionEditorModel(
// this.validationErrorModel
// ).fromModel(item).buildForm({
// rootPath: `fields[${index}].`
// }), context.getValidation('fields')
// )
// ),
// descriptionTemplates: this.formBuilder.array(
// (this.descriptionTemplates ?? []).map(
// (item, index) => new DescriptionTemplatesInSectionEditorModel(
// this.validationErrorModel
// ).fromModel(item).buildForm({
// rootPath: `fields[${index}].`
// }), context.getValidation('descriptionTemplates')
// )
// )
// });
// }
buildForm(params?: {
context?: ValidationContext,
disabled?: boolean,
rootPath?: string
}): UntypedFormGroup {
let { context = null, disabled = false, rootPath } = params ?? {}
if (context == null) {
context = DescriptionTemplatePageEditorModel.createValidationContext({
validationErrorModel: this.validationErrorModel,
rootPath
});
}
// static createValidationContext(params: {
// rootPath?: string,
// validationErrorModel: ValidationErrorModel
// }): ValidationContext {
// const { rootPath = '', validationErrorModel } = params;
return this.formBuilder.group({
id: [{ value: this.id, disabled: disabled }, context.getValidation('id').validators],
ordinal: [{ value: this.ordinal, disabled: disabled }, context.getValidation('ordinal').validators],
title: [{ value: this.title, disabled: disabled }, context.getValidation('title').validators]
});
}
// const baseContext: ValidationContext = new ValidationContext();
// const baseValidationArray: Validation[] = new Array<Validation>();
// baseValidationArray.push({ key: 'id', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}id`)] });
// baseValidationArray.push({ key: 'label', validators: [Validators.required,BackendErrorValidator(validationErrorModel, `${rootPath}label`)] });
// baseValidationArray.push({ key: 'ordinal', validators: [Validators.required, BackendErrorValidator(validationErrorModel, `${rootPath}ordinal`)] });
// baseValidationArray.push({ key: 'description', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}description`)] });
// baseValidationArray.push({ key: 'hasTemplates', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}hasTemplates`)] });
// baseValidationArray.push({ key: 'fields', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}fields`)] });
// baseValidationArray.push({ key: 'descriptionTemplates', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}descriptionTemplates`)] });
// baseValidationArray.push({ key: 'hash', validators: [] });
static createValidationContext(params: {
rootPath?: string,
validationErrorModel: ValidationErrorModel
}): ValidationContext {
const { rootPath = '', validationErrorModel } = params;
// baseContext.validation = baseValidationArray;
// return baseContext;
// }
const baseContext: ValidationContext = new ValidationContext();
const baseValidationArray: Validation[] = new Array<Validation>();
baseValidationArray.push({ key: 'id', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}id`)] });
baseValidationArray.push({ key: 'ordinal', validators: [Validators.required, BackendErrorValidator(validationErrorModel, `${rootPath}ordinal`)] });
baseValidationArray.push({ key: 'title', validators: [Validators.required, BackendErrorValidator(validationErrorModel, `${rootPath}title`)] });
// }
baseContext.validation = baseValidationArray;
return baseContext;
}
// export class FieldInSectionEditorModel implements FieldInSectionPersist {
// public id: Guid;
// public category: DescriptionTemplateSectionFieldCategory;
// public dataType: DescriptionTemplateExtraFieldDataType;
// public systemFieldType: DescriptionTemplateSystemFieldType;
// public label: string;
// public placeholder: string;
// public description: string;
// public required: boolean = false;
// public ordinal: number;
}
// protected formBuilder: UntypedFormBuilder = new UntypedFormBuilder();
export class DescriptionTemplateSectionEditorModel implements DescriptionTemplateSectionPersist {
id: Guid;
ordinal: number;
defaultVisibility: boolean;
multiplicity: boolean;
page: string;
title: string;
description: string;
sections?: DescriptionTemplateSectionEditorModel[] = [];
fieldSets: DescriptionTemplateFieldSetEditorModel[] = [];
// constructor(
// public validationErrorModel: ValidationErrorModel = new ValidationErrorModel()
// ) { }
protected formBuilder: UntypedFormBuilder = new UntypedFormBuilder();
// fromModel(item: FieldInSection): FieldInSectionEditorModel {
// this.id = item.id;
// this.category = item.category;
// this.dataType = item.dataType;
// this.systemFieldType = item.systemFieldType;
// this.label = item.label;
// this.placeholder = item.placeholder;
// this.description = item.description;
// this.required = item.required;
// this.ordinal = item.ordinal;
// return this;
// }
constructor(
public validationErrorModel: ValidationErrorModel = new ValidationErrorModel()
) { }
// buildForm(params?: {
// context?: ValidationContext,
// disabled?: boolean,
// rootPath?: string
// }): UntypedFormGroup {
// let { context = null, disabled = false, rootPath } = params ?? {}
// if (context == null) {
// context = FieldInSectionEditorModel.createValidationContext({
// validationErrorModel: this.validationErrorModel,
// rootPath
// });
// }
public fromModel(item: DescriptionTemplateSection | DescriptionTemplateSectionEditorModel): DescriptionTemplateSectionEditorModel {
if (item) {
this.id = item.id;
this.ordinal = item.ordinal;
this.defaultVisibility = item.defaultVisibility;
this.multiplicity = item.multiplicity;
this.page = item.page;
this.title = item.title;
this.description = item.description;
if (item.sections) { item.sections.map(x => this.sections.push(new DescriptionTemplateSectionEditorModel().fromModel(x))); }
if (item.fieldSets) { item.fieldSets.map(x => this.fieldSets.push(new DescriptionTemplateFieldSetEditorModel().fromModel(x))); }
}
return this;
}
// return this.formBuilder.group({
// id: [{ value: this.id, disabled: disabled }, context.getValidation('id').validators],
buildForm(params?: {
context?: ValidationContext,
disabled?: boolean,
rootPath?: string
}): UntypedFormGroup {
let { context = null, disabled = false, rootPath } = params ?? {}
if (context == null) {
context = DescriptionTemplateSectionEditorModel.createValidationContext({
validationErrorModel: this.validationErrorModel,
rootPath
});
}
// category: [{ value: this.category, disabled: disabled }, context.getValidation('category').validators],
// dataType: [{ value: this.dataType, disabled: disabled }, context.getValidation('dataType').validators],
// systemFieldType: [{ value: this.systemFieldType, disabled: disabled }, context.getValidation('systemFieldType').validators],
// label: [{ value: this.label, disabled: disabled }, context.getValidation('label').validators],
// placeholder: [{ value: this.placeholder, disabled: disabled }, context.getValidation('placeholder').validators],
// description: [{ value: this.description, disabled: disabled }, context.getValidation('description').validators],
// required: [{ value: this.required, disabled: disabled }, context.getValidation('required').validators],
// ordinal: [{ value: this.ordinal, disabled: disabled }, context.getValidation('ordinal').validators],
return this.formBuilder.group({
id: [{ value: this.id, disabled: disabled }, context.getValidation('id').validators],
ordinal: [{ value: this.ordinal, disabled: disabled }, context.getValidation('ordinal').validators],
defaultVisibility: [{ value: this.defaultVisibility, disabled: disabled }, context.getValidation('defaultVisibility').validators],
multiplicity: [{ value: this.multiplicity, disabled: disabled }, context.getValidation('multiplicity').validators],
page: [{ value: this.page, disabled: disabled }, context.getValidation('page').validators],
title: [{ value: this.title, disabled: disabled }, context.getValidation('title').validators],
description: [{ value: this.description, disabled: disabled }, context.getValidation('description').validators],
sections: this.formBuilder.array(
(this.sections ?? []).map(
(item, index) => new DescriptionTemplateSectionEditorModel(
this.validationErrorModel
).fromModel(item).buildForm({
rootPath: `sections[${index}].`
}), context.getValidation('sections')
)
),
fieldSets: this.formBuilder.array(
(this.fieldSets ?? []).map(
(item, index) => new DescriptionTemplateFieldSetEditorModel(
this.validationErrorModel
).fromModel(item).buildForm({
rootPath: `fieldSets[${index}].`
}), context.getValidation('fieldSets')
)
)
});
}
// });
// }
static createValidationContext(params: {
rootPath?: string,
validationErrorModel: ValidationErrorModel
}): ValidationContext {
const { rootPath = '', validationErrorModel } = params;
// static createValidationContext(params: {
// rootPath?: string,
// validationErrorModel: ValidationErrorModel
// }): ValidationContext {
// const { rootPath = '', validationErrorModel } = params;
const baseContext: ValidationContext = new ValidationContext();
const baseValidationArray: Validation[] = new Array<Validation>();
baseValidationArray.push({ key: 'id', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}id`)] });
baseValidationArray.push({ key: 'ordinal', validators: [Validators.required, BackendErrorValidator(validationErrorModel, `${rootPath}ordinal`)] });
baseValidationArray.push({ key: 'defaultVisibility', validators: [Validators.required, BackendErrorValidator(validationErrorModel, `${rootPath}defaultVisibility`)] });
baseValidationArray.push({ key: 'multiplicity', validators: [Validators.required, BackendErrorValidator(validationErrorModel, `${rootPath}multiplicity`)] });
baseValidationArray.push({ key: 'page', validators: [Validators.required, BackendErrorValidator(validationErrorModel, `${rootPath}page`)] });
baseValidationArray.push({ key: 'title', validators: [Validators.required, BackendErrorValidator(validationErrorModel, `${rootPath}title`)] });
baseValidationArray.push({ key: 'description', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}description`)] });
baseValidationArray.push({ key: 'sections', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}sections`)] });
baseValidationArray.push({ key: 'fieldSets', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}fieldSets`)] });
// const baseContext: ValidationContext = new ValidationContext();
// const baseValidationArray: Validation[] = new Array<Validation>();
// baseValidationArray.push({ key: 'id', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}id`)] });
baseContext.validation = baseValidationArray;
return baseContext;
}
// baseValidationArray.push({ key: 'category', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}category`)] });
// baseValidationArray.push({ key: 'dataType', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}dataType`)] });
// baseValidationArray.push({ key: 'systemFieldType', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}systemFieldType`)] });
// baseValidationArray.push({ key: 'label', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}label`)] });
// baseValidationArray.push({ key: 'placeholder', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}placeholder`)] });
// baseValidationArray.push({ key: 'description', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}description`)] });
// baseValidationArray.push({ key: 'required', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}required`)] });
// baseValidationArray.push({ key: 'ordinal', validators: [Validators.required, BackendErrorValidator(validationErrorModel, `${rootPath}ordinal`)] });
}
// baseContext.validation = baseValidationArray;
// return baseContext;
// }
// }
export class DescriptionTemplateFieldSetEditorModel implements DescriptionTemplateFieldSetPersist {
id: Guid;
ordinal: number;
numbering: string;
title: string;
description: string;
extendedDescription: string;
additionalInformation: string;
multiplicity: DescriptionTemplateMultiplicityEditorModel;
hasCommentField: boolean;
fields: DescriptionTemplateFieldEditorModel[] = [];
protected formBuilder: UntypedFormBuilder = new UntypedFormBuilder();
constructor(
public validationErrorModel: ValidationErrorModel = new ValidationErrorModel()
) { }
fromModel(item: DescriptionTemplateFieldSet | DescriptionTemplateFieldSetEditorModel): DescriptionTemplateFieldSetEditorModel {
if (item) {
this.id = item.id;
this.ordinal = item.ordinal;
this.numbering = item.numbering;
this.title = item.title;
this.description = item.description;
this.extendedDescription = item.extendedDescription;
this.additionalInformation = item.additionalInformation;
this.hasCommentField = item.hasCommentField;
this.multiplicity = new DescriptionTemplateMultiplicityEditorModel().fromModel(item.multiplicity);
if (item.fields) { item.fields.map(x => this.fields.push(new DescriptionTemplateFieldEditorModel().fromModel(x))); }
}
return this;
}
buildForm(params?: {
context?: ValidationContext,
disabled?: boolean,
rootPath?: string
}): UntypedFormGroup {
let { context = null, disabled = false, rootPath } = params ?? {}
if (context == null) {
context = DescriptionTemplateFieldSetEditorModel.createValidationContext({
validationErrorModel: this.validationErrorModel,
rootPath
});
}
return this.formBuilder.group({
id: [{ value: this.id, disabled: disabled }, context.getValidation('id').validators],
ordinal: [{ value: this.ordinal, disabled: disabled }, context.getValidation('ordinal').validators],
numbering: [{ value: this.numbering, disabled: disabled }, context.getValidation('numbering').validators],
title: [{ value: this.title, disabled: disabled }, context.getValidation('title').validators],
description: [{ value: this.description, disabled: disabled }, context.getValidation('description').validators],
extendedDescription: [{ value: this.extendedDescription, disabled: disabled }, context.getValidation('extendedDescription').validators],
additionalInformation: [{ value: this.additionalInformation, disabled: disabled }, context.getValidation('additionalInformation').validators],
hasCommentField: [{ value: this.hasCommentField, disabled: disabled }, context.getValidation('hasCommentField').validators],
multiplicity: this.multiplicity.buildForm({
rootPath: `multiplicity.`
}),
fields: this.formBuilder.array(
(this.fields ?? []).map(
(item, index) => new DescriptionTemplateFieldEditorModel(
this.validationErrorModel
).fromModel(item).buildForm({
rootPath: `fields[${index}].`
}), context.getValidation('fields')
)
)
});
}
static createValidationContext(params: {
rootPath?: string,
validationErrorModel: ValidationErrorModel
}): ValidationContext {
const { rootPath = '', validationErrorModel } = params;
const baseContext: ValidationContext = new ValidationContext();
const baseValidationArray: Validation[] = new Array<Validation>();
baseValidationArray.push({ key: 'id', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}id`)] });
baseValidationArray.push({ key: 'ordinal', validators: [Validators.required, BackendErrorValidator(validationErrorModel, `${rootPath}ordinal`)] });
baseValidationArray.push({ key: 'numbering', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}numbering`)] });
baseValidationArray.push({ key: 'title', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}title`)] });
baseValidationArray.push({ key: 'description', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}description`)] });
baseValidationArray.push({ key: 'extendedDescription', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}extendedDescription`)] });
baseValidationArray.push({ key: 'additionalInformation', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}additionalInformation`)] });
baseValidationArray.push({ key: 'hasCommentField', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}hasCommentField`)] });
baseValidationArray.push({ key: 'fields', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}fields`)] });
baseContext.validation = baseValidationArray;
return baseContext;
}
}
export class DescriptionTemplateMultiplicityEditorModel implements DescriptionTemplateMultiplicityPersist {
min: number;
max: number;
placeholder: string;
tableView: boolean;
protected formBuilder: UntypedFormBuilder = new UntypedFormBuilder();
constructor(
public validationErrorModel: ValidationErrorModel = new ValidationErrorModel()
) { }
fromModel(item: DescriptionTemplateMultiplicity): DescriptionTemplateMultiplicityEditorModel {
if (item) {
this.min = item.min;
this.max = item.max;
this.placeholder = item.placeholder;
this.tableView = item.tableView;
}
return this;
}
buildForm(params?: {
context?: ValidationContext,
disabled?: boolean,
rootPath?: string
}): UntypedFormGroup {
let { context = null, disabled = false, rootPath } = params ?? {}
if (context == null) {
context = DescriptionTemplateMultiplicityEditorModel.createValidationContext({
validationErrorModel: this.validationErrorModel,
rootPath
});
}
return this.formBuilder.group({
min: [{ value: this.min, disabled: disabled }, context.getValidation('min').validators],
max: [{ value: this.max, disabled: disabled }, context.getValidation('max').validators],
placeholder: [{ value: this.placeholder, disabled: disabled }, context.getValidation('placeholder').validators],
tableView: [{ value: this.tableView, disabled: disabled }, context.getValidation('tableView').validators],
});
}
static createValidationContext(params: {
rootPath?: string,
validationErrorModel: ValidationErrorModel
}): ValidationContext {
const { rootPath = '', validationErrorModel } = params;
const baseContext: ValidationContext = new ValidationContext();
const baseValidationArray: Validation[] = new Array<Validation>();
baseValidationArray.push({ key: 'min', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}min`)] });
baseValidationArray.push({ key: 'max', validators: [Validators.required, BackendErrorValidator(validationErrorModel, `${rootPath}max`)] });
baseValidationArray.push({ key: 'placeholder', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}placeholder`)] });
baseValidationArray.push({ key: 'tableView', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}tableView`)] });
baseContext.validation = baseValidationArray;
return baseContext;
}
}
export class DescriptionTemplateFieldEditorModel implements DescriptionTemplateFieldPersist {
id: Guid;
ordinal: number;
schematics: string[];
defaultValue: string;
visibilityRules: DescriptionTemplateRuleEditorModel[] = [];
validations: DescriptionTemplateFieldValidationType[];
includeInExport: boolean;
data: DescriptionTemplateBaseFieldDataPersist;
protected formBuilder: UntypedFormBuilder = new UntypedFormBuilder();
constructor(
public validationErrorModel: ValidationErrorModel = new ValidationErrorModel()
) { }
fromModel(item: DescriptionTemplateField | DescriptionTemplateFieldEditorModel): DescriptionTemplateFieldEditorModel {
if (item) {
this.id = item.id;
this.ordinal = item.ordinal;
this.schematics = item.schematics;
this.defaultValue = item.defaultValue;
this.validations = item.validations;
this.includeInExport = item.includeInExport;
//this.data = new DescriptionTemplateBaseFieldDataEditorModel().fromModel(item.data);
if (item.visibilityRules) { item.visibilityRules.map(x => this.visibilityRules.push(new DescriptionTemplateRuleEditorModel().fromModel(x))); }
}
return this;
}
buildForm(params?: {
context?: ValidationContext,
disabled?: boolean,
rootPath?: string
}): UntypedFormGroup {
let { context = null, disabled = false, rootPath } = params ?? {}
if (context == null) {
context = DescriptionTemplateFieldEditorModel.createValidationContext({
validationErrorModel: this.validationErrorModel,
rootPath
});
}
return this.formBuilder.group({
id: [{ value: this.id, disabled: disabled }, context.getValidation('id').validators],
ordinal: [{ value: this.ordinal, disabled: disabled }, context.getValidation('ordinal').validators],
schematics: [{ value: this.schematics, disabled: disabled }, context.getValidation('schematics').validators],
defaultValue: [{ value: this.defaultValue, disabled: disabled }, context.getValidation('defaultValue').validators],
validations: [{ value: this.validations, disabled: disabled }, context.getValidation('validations').validators],
includeInExport: [{ value: this.includeInExport, disabled: disabled }, context.getValidation('includeInExport').validators],
// data: this.data.buildForm({
// rootPath: `data.`
// }),
visibilityRules: this.formBuilder.array(
(this.visibilityRules ?? []).map(
(item, index) => new DescriptionTemplateRuleEditorModel(
this.validationErrorModel
).fromModel(item).buildForm({
rootPath: `visibilityRules[${index}].`
}), context.getValidation('visibilityRules')
)
)
});
}
static createValidationContext(params: {
rootPath?: string,
validationErrorModel: ValidationErrorModel
}): ValidationContext {
const { rootPath = '', validationErrorModel } = params;
const baseContext: ValidationContext = new ValidationContext();
const baseValidationArray: Validation[] = new Array<Validation>();
baseValidationArray.push({ key: 'id', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}id`)] });
baseValidationArray.push({ key: 'ordinal', validators: [Validators.required, BackendErrorValidator(validationErrorModel, `${rootPath}ordinal`)] });
baseValidationArray.push({ key: 'schematics', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}schematics`)] });
baseValidationArray.push({ key: 'defaultValue', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}defaultValue`)] });
baseValidationArray.push({ key: 'validations', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}validations`)] });
baseValidationArray.push({ key: 'includeInExport', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}includeInExport`)] });
baseValidationArray.push({ key: 'visibilityRules', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}visibilityRules`)] });
baseContext.validation = baseValidationArray;
return baseContext;
}
}
export class DescriptionTemplateRuleEditorModel implements DescriptionTemplateRulePersist {
target: string;
value: string;
protected formBuilder: UntypedFormBuilder = new UntypedFormBuilder();
constructor(
public validationErrorModel: ValidationErrorModel = new ValidationErrorModel()
) { }
fromModel(item: DescriptionTemplateRule): DescriptionTemplateRuleEditorModel {
if (item) {
this.target = item.target;
this.value = item.value;
}
return this;
}
buildForm(params?: {
context?: ValidationContext,
disabled?: boolean,
rootPath?: string
}): UntypedFormGroup {
let { context = null, disabled = false, rootPath } = params ?? {}
if (context == null) {
context = DescriptionTemplateRuleEditorModel.createValidationContext({
validationErrorModel: this.validationErrorModel,
rootPath
});
}
return this.formBuilder.group({
target: [{ value: this.target, disabled: disabled }, context.getValidation('target').validators],
value: [{ value: this.value, disabled: disabled }, context.getValidation('value').validators]
});
}
static createValidationContext(params: {
rootPath?: string,
validationErrorModel: ValidationErrorModel
}): ValidationContext {
const { rootPath = '', validationErrorModel } = params;
const baseContext: ValidationContext = new ValidationContext();
const baseValidationArray: Validation[] = new Array<Validation>();
baseValidationArray.push({ key: 'target', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}target`)] });
baseValidationArray.push({ key: 'value', validators: [Validators.required, BackendErrorValidator(validationErrorModel, `${rootPath}value`)] });
baseContext.validation = baseValidationArray;
return baseContext;
}
}
// export class DescriptionTemplatesInSectionEditorModel implements DescriptionTemplatesInSectionPersist {
// id: Guid;

View File

@ -58,7 +58,7 @@ export class DescriptionTemplateEditorResolver extends BaseEditorResolver {
[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<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.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<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('.'),

View File

@ -0,0 +1,29 @@
import { AbstractControl } from "@angular/forms";
export interface ToCEntry {
id: string;
label: string;
subEntriesType: ToCEntryType;
subEntries: ToCEntry[];
type: ToCEntryType;
form: AbstractControl;
numbering: string;
}
export enum ToCEntryType {
Page = 0,
Section = 1,
FieldSet = 2,
Field = 3
}
export interface NewEntryType {
childType: ToCEntryType,
parent: ToCEntry
}
export interface TableUpdateInfo{
draggedItemId?: string;
data?:any;
}

View File

@ -0,0 +1,32 @@
<div class="table-container">
<h3 id="guide-steps" [class.opacity-0]="isDragging">{{'DESCRIPTION-TEMPLATE-EDITOR.STEPS.GENERAL-INFO.TEMPLATE-OUTLINE' | translate}}</h3>
<div class="scroll-container" id="tocentrytable">
<app-description-template-table-of-contents-internal-section [links]="links" (itemClick)="itemClicked($event)"
(removeEntry)="deleteEntry($event)"
(createFooEntry)="createNewEntry($event)"
[parentLink]="{ subEntriesType: tocEntryType.Page, subEntries : links , id: ROOT_ID}"
[itemSelected]="itemSelected"
[viewOnly]="viewOnly"
(dataNeedsRefresh)="onDataNeedsRefresh()"
[DRAGULA_ITEM_ID_PREFIX]="DRAGULA_ITEM_ID_PREFIX"
[overContainerId]="overcontainer"
[isDragging]="isDragging"
[draggingItemId]="draggingItemId"
[parentRootId]="ROOT_ID"
style="padding-right: 1em;"
[colorizeInvalid]="colorizeInvalid"
>
</app-description-template-table-of-contents-internal-section>
<div class="top-scroller table-scroller" [hidden]="!isDragging">
<div class="col-auto">
<mat-icon>keyboard_arrow_up</mat-icon>
</div>
</div>
<div class="bottom-scroller table-scroller" [hidden]="!isDragging">
<div class="col-auto">
<mat-icon>keyboard_arrow_down</mat-icon>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,92 @@
$scroller-height: 3em;
.scroll-container {
// overflow-y: auto;
max-height: 60vh;
overflow-y: scroll;
padding-left: .2em;
padding-right: 1em;
// padding-top: $scroller-height;
// padding-bottom: $scroller-height;
}
// #style-6::-webkit-scrollbar-track
// {
// -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
// background-color: #F5F5F5;
// }
// #style-6::-webkit-scrollbar
// {
// width: 6px;
// background-color: #F5F5F5;
// }
// #style-6::-webkit-scrollbar-thumb
// {
// background-color: rgb(162, 163, 163);
// background-image: -webkit-linear-gradient(45deg,
// rgba(255, 255, 255, .2) 25%,
// transparent 25%,
// transparent 50%,
// rgba(255, 255, 255, .2) 50%,
// rgba(255, 255, 255, .2) 75%,
// transparent 75%,
// transparent)
// }
#tocentrytable::-webkit-scrollbar-track
{
-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
border-radius: 10px;
background-color: #F5F5F5;
}
#tocentrytable::-webkit-scrollbar
{
width: 4px;
background-color: #F5F5F5;
}
#tocentrytable::-webkit-scrollbar-thumb
{
border-radius: 2px;
-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3);
background-color: rgb(158, 158, 158);// #FFF;//$blue-color-light;// rgb(162, 163, 163);// #D62929;
}
#guide-steps{
color: #212121;
opacity: 0.6;
font-size: 1.6em;
margin-top: 0px;
}
.table-container{
position: relative;
}
.table-scroller{
// background-color: #5cf7f221;
position: absolute;
width: 95%;
height: $scroller-height;
display: flex;
align-items: center;
justify-content: center;
// z-index: -9999;
}
.top-scroller{
top: 1px;
background: rgb(255,255,255);
background: linear-gradient(0deg, rgba(255,255,255,0) 0%, rgba(92,247,242,0.4542191876750701) 100%);
}
.bottom-scroller{
bottom: 1px;
background: rgb(255,255,255);
background: linear-gradient(180deg, rgba(255,255,255,0) 0%, rgba(92,247,242,0.4542191876750701) 100%);
}
.opacity-0{
opacity: 0 !important;
}

View File

@ -0,0 +1,605 @@
import { DOCUMENT } from '@angular/common';
import { AfterViewInit, Component, EventEmitter, Inject, Input, OnInit, Output } from '@angular/core';
import { UntypedFormArray } from '@angular/forms';
import { MatSnackBar, MatSnackBarConfig } from '@angular/material/snack-bar';
import { BaseComponent } from '@common/base/base.component';
import { TranslateService } from '@ngx-translate/core';
import { DragulaService } from 'ng2-dragula';
import { interval } from 'rxjs';
import { filter, takeUntil } from 'rxjs/operators';
import { NewEntryType, TableUpdateInfo, ToCEntry, ToCEntryType } from './description-template-table-of-contents-entry';
@Component({
selector: 'description-template-table-of-contents',
styleUrls: ['./description-template-table-of-contents.scss'],
templateUrl: './description-template-table-of-contents.html'
})
export class DescriptionTemplateTableOfContents extends BaseComponent implements OnInit, AfterViewInit {
@Input() links: ToCEntry[];
@Input() itemSelected: ToCEntry;
@Input() colorizeInvalid: boolean = false;
@Input() viewOnly: boolean;
@Output() itemClick = new EventEmitter<ToCEntry>();
// @Output() newEntry = new EventEmitter<ToCEntry>();
@Output() removeEntry = new EventEmitter<ToCEntry>();
@Output() createEntry = new EventEmitter<NewEntryType>();
@Output() dataNeedsRefresh = new EventEmitter<TableUpdateInfo>();
isDragging: boolean = false;
draggingItemId: string = null;
tocEntryType = ToCEntryType;
DRAGULA_ITEM_ID_PREFIX = "table_item_id_";
ROOT_ID: string = "ROOT_ID";//no special meaning
private _dragStartedAt;
private VALID_DROP_TIME = 500;//ms
overcontainer: string = null;
$clock = interval(10);
scrollTableTop = false;
scrollTableBottom = false;
pxToScroll = 15;
constructor(
@Inject(DOCUMENT) private _document: Document,
private dragulaService: DragulaService,
private snackbar: MatSnackBar,
private language: TranslateService
) {
super();
if (this.dragulaService.find('TABLEDRAG')) {
this.dragulaService.destroy('TABLEDRAG');
}
const dragula = this.dragulaService.createGroup('TABLEDRAG', {});
const drake = dragula.drake;
drake.on('drop', (el, target, source, sibling) => {
if (this._dragStartedAt) {
const timeNow = new Date().getTime();
if (timeNow - this._dragStartedAt > this.VALID_DROP_TIME) {
// console.log('timenow: ', timeNow);
// console.log('timestarted', this._dragStartedAt);
this._dragStartedAt = null;
} else {
this.dataNeedsRefresh.emit();// even though the data is not changed the TABLE DRAG may has changed
return;
}
} else {
this.dataNeedsRefresh.emit();// even though the data is not changed the TABLE DRAG may has changed
return;
}
const elementId = (el.id as string).replace(this.DRAGULA_ITEM_ID_PREFIX, '');
const targetId = target.id as string;
const sourceId = source.id as string;
if (!(elementId && targetId && sourceId)) {
console.info('Elements do not have an id');
this.dataNeedsRefresh.emit();
return;
}
const element: ToCEntry = this._findTocEntryById(elementId, this.links);
const targetContainer: ToCEntry = this._findTocEntryById(targetId, this.links);
const sourceContainer: ToCEntry = this._findTocEntryById(sourceId, this.links);
if (!(element && (targetContainer || ((element.type === ToCEntryType.Page) && (targetId === this.ROOT_ID))) && (sourceContainer || ((element.type === ToCEntryType.Page) && (sourceId === this.ROOT_ID))))) {
// console.info('Could not find elements');
this.dataNeedsRefresh.emit();
//TODO: angular update //drake.cancel(true);
return;
}
switch (element.type) {
case ToCEntryType.FieldSet: {
if (targetContainer.type != this.tocEntryType.Section) {
// const message = 'Fieldset can only be child of Subsections';
const message = this.language.instant('DESCRIPTION-TEMPLATE-EDITOR.STEPS.FORM.TABLE-OF-CONTENTS.ERROR-MESSAGES.FIELDSET-MUST-HAVE-PARENT-SECTION');
// console.error(message);
this.notifyUser(message)
this.dataNeedsRefresh.emit();
return;
}
//check if target container has no sections
if ((targetContainer.form.get('sections') as UntypedFormArray).length) {
// const message = 'Cannot have inputs and sections on the same level';
const message = this.language.instant('DESCRIPTION-TEMPLATE-EDITOR.STEPS.FORM.TABLE-OF-CONTENTS.ERROR-MESSAGES.INPUT-SECTION-SAME-LEVEL');
this.notifyUser(message);
// console.error(message);
this.dataNeedsRefresh.emit();
return;
}
const fieldsetForm = element.form;
const targetFieldsets = targetContainer.form.get('fieldSets') as UntypedFormArray;
const sourceFieldsets = sourceContainer.form.get('fieldSets') as UntypedFormArray;
if (!targetFieldsets) {
console.info('Not target fieldsets container found');
this.dataNeedsRefresh.emit();
return;
}
let sourceOrdinal = -1;
let idx = -1;
sourceFieldsets.controls.forEach((elem, index) => {
if (elem.get('id').value === elementId) {
sourceOrdinal = elem.get('ordinal').value;
idx = index
}
});
if (sourceOrdinal >= 0 && idx >= 0) {
sourceFieldsets.removeAt(idx);
sourceFieldsets.controls.forEach(control => {
const ordinal = control.get('ordinal');
if ((ordinal.value >= sourceOrdinal) && sourceOrdinal > 0) {
const updatedOrdinalVal = ordinal.value - 1;
ordinal.setValue(updatedOrdinalVal);
}
});
sourceFieldsets.controls.sort(this._compareOrdinals);
}
let position: number = targetFieldsets.length;
if (!sibling || !sibling.id) {
console.info('No sibling Id found');
} else {
const siblingId = (sibling.id as string).replace(this.DRAGULA_ITEM_ID_PREFIX, '');
let siblingIndex = -1;
targetFieldsets.controls.forEach((e, idx) => {
if (e.get('id').value === siblingId) {
siblingIndex = idx;
position = e.get('ordinal').value;
}
});
if (siblingIndex >= 0) { //sibling found
targetFieldsets.controls.filter(control => control.get('ordinal').value >= position).forEach(control => {
const ordinal = control.get('ordinal');
const updatedOrdinalVal = ordinal.value + 1;
ordinal.setValue(updatedOrdinalVal);
})
}
}
fieldsetForm.get('ordinal').setValue(position);
targetFieldsets.insert(position, fieldsetForm);
targetFieldsets.controls.sort(this._compareOrdinals);
this.dataNeedsRefresh.emit({ draggedItemId: elementId });
break;
}
case ToCEntryType.Section: {
if (targetContainer.type == ToCEntryType.Section) {
if ((targetContainer.form.get('fieldSets') as UntypedFormArray).length) {
// const message = 'Cannot have inputs and sections on the same level';
const message = this.language.instant('DESCRIPTION-TEMPLATE-EDITOR.STEPS.FORM.TABLE-OF-CONTENTS.ERROR-MESSAGES.INPUT-SECTION-SAME-LEVEL');;
this.notifyUser(message);
// console.info(message);
this.dataNeedsRefresh.emit();
return;
}
const targetSections = targetContainer.form.get('sections') as UntypedFormArray;
const elementSectionForm = element.form;
const sourceSections = elementSectionForm.parent as UntypedFormArray;
if (!(targetSections && sourceSections && elementSectionForm)) {
console.info('Could not load sections');
this.dataNeedsRefresh.emit();
return;
}
let idx = -1;
sourceSections.controls.forEach((section, i) => {
if (section.get('id').value === elementId) {
idx = i;
}
});
if (!(idx >= 0)) {
console.info('Could not find element in Parent container');
this.dataNeedsRefresh.emit();
return;
}
sourceSections.controls.filter(control => control.get('ordinal').value >= elementSectionForm.get('ordinal').value).forEach(control => {
const ordinal = control.get('ordinal');
const updatedOrdinalVal = ordinal.value ? ordinal.value - 1 : 0;
ordinal.setValue(updatedOrdinalVal);
});
sourceSections.removeAt(idx);
let targetOrdinal = targetSections.length;
if (sibling && sibling.id) {
const siblingId = sibling.id.replace(this.DRAGULA_ITEM_ID_PREFIX, '');
targetSections.controls.forEach((section, i) => {
if (section.get('id').value === siblingId) {
targetOrdinal = section.get('ordinal').value;
}
})
// if(targetOrdinal!=targetSections.length){//mporei na einai idio
// section.get('ordinal').setValue(i+1);
targetSections.controls.filter(control => control.get('ordinal').value >= targetOrdinal).forEach(control => {
const ordinal = control.get('ordinal');
const updatedOrdinalVal = ordinal.value + 1;
ordinal.setValue(updatedOrdinalVal);
});
// }
} else {
console.info('no siblings found');
}
elementSectionForm.get('ordinal').setValue(targetOrdinal);
targetSections.insert(targetOrdinal, elementSectionForm);
} else if (targetContainer.type === ToCEntryType.Page) {
// const pageId = targetContainer.form.get('id').value;
const rootform = targetContainer.form.root;
const sectionForm = element.form;
const parentSections = sectionForm.parent as UntypedFormArray;
let parentIndex = -1;
parentSections.controls.forEach((section, i) => {
if (section.get('id').value === elementId) {
parentIndex = i
}
})
if (parentIndex < 0) {
console.info('could not locate section in parents array');
this.dataNeedsRefresh.emit();
return;
}
//update parent sections ordinal
parentSections.controls.filter(section => section.get('ordinal').value >= sectionForm.get('ordinal').value).forEach(section => {
const ordinal = section.get('ordinal');
const updatedOrdinalVal = ordinal.value ? ordinal.value - 1 : 0;
ordinal.setValue(updatedOrdinalVal);
})
parentSections.removeAt(parentIndex);
let position = 0;
if (targetContainer.subEntries) {
position = targetContainer.subEntries.length;
}
//populate sections
const targetSectionsArray = rootform.get('sections') as UntypedFormArray;
if (sibling && sibling.id) {
const siblingId = sibling.id.replace(this.DRAGULA_ITEM_ID_PREFIX, '');
let indx = -1;
targetContainer.subEntries.forEach((e, i) => {
if (e.form.get('id').value === siblingId) {
indx = i;
position = e.form.get('ordinal').value;
}
});
if (indx >= 0) {
// e.form.get('ordinal').setValue(i+1);
targetContainer.subEntries.filter(e => e.form.get('ordinal').value >= position).forEach(e => {
const ordinal = e.form.get('ordinal');
const updatedOrdinalVal = ordinal.value + 1;
ordinal.setValue(updatedOrdinalVal);
});
}
} else {
console.info('No sibling found');
}
sectionForm.get('ordinal').setValue(position);
sectionForm.get('page').setValue(targetContainer.id);
targetSectionsArray.push(sectionForm);
} else {
// const message = 'Drag not support to specific container';
const message = this.language.instant('DESCRIPTION-TEMPLATE-EDITOR.STEPS.FORM.TABLE-OF-CONTENTS.ERROR-MESSAGES.DRAG-NOT-SUPPORTED');
this.notifyUser(message);
// console.info(message);
this.dataNeedsRefresh.emit();
return;
}
this.dataNeedsRefresh.emit({ draggedItemId: elementId });
break;
}
case ToCEntryType.Page: {
if (targetId != this.ROOT_ID) {
// const message = 'A page element can only be at top level';
const message = this.language.instant('DESCRIPTION-TEMPLATE-EDITOR.STEPS.FORM.TABLE-OF-CONTENTS.ERROR-MESSAGES.PAGE-ELEMENT-ONLY-TOP-LEVEL');
this.notifyUser(message);
// console.info(message);
this.dataNeedsRefresh.emit();
return;
}
const rootForm = element.form.root;
if (!rootForm) {
console.info('Could not find root!')
this.dataNeedsRefresh.emit();
return;
}
const pages = rootForm.get('pages') as UntypedFormArray;
const pageForm = element.form;
let index = -1;
pages.controls.forEach((page, i) => {
if (page.get('id').value === elementId) {
index = i;
}
});
if (index < 0) {
console.info('Could not locate page on pages');
this.dataNeedsRefresh.emit();
return;
}
//ordinality
pages.controls.filter(page => page.get('ordinal').value >= pageForm.get('ordinal').value).forEach(page => {
const ordinal = page.get('ordinal');
const ordinalVal = ordinal.value ? ordinal.value - 1 : 0;
ordinal.setValue(ordinalVal);
});
pages.removeAt(index);
let targetPosition = pages.length;
if (sibling) {
const siblingId = sibling.id.replace(this.DRAGULA_ITEM_ID_PREFIX, '');
pages.controls.forEach((page, i) => {
if (page.get('id').value === siblingId) {
targetPosition = page.get('ordinal').value;
}
});
}
pageForm.get('ordinal').setValue(targetPosition);
//update ordinality
pages.controls.filter(page => page.get('ordinal').value >= targetPosition).forEach(page => {
const ordinal = page.get('ordinal');
const ordinalVal = ordinal.value + 1;
ordinal.setValue(ordinalVal);
});
pages.insert(targetPosition, pageForm);
this.dataNeedsRefresh.emit({ draggedItemId: elementId });
break;
}
default:
console.info('Could not support moving objects for specific type of element');
this.dataNeedsRefresh.emit();
return;
}
});
drake.on('drag', (el, source) => {
this._dragStartedAt = new Date().getTime();
// console.log('drag fired');
this.isDragging = true;
this.draggingItemId = (el.id as string).replace(this.DRAGULA_ITEM_ID_PREFIX, '');
// setTimeout(() => {
// if(this.isDragging){
// this._scrollIntoDragginItem(this.draggingItemId);
// }
// }, this.VALID_DROP_TIME);
});
drake.on('over', (el, container, source) => {
try {
this.overcontainer = container.id;
} catch (error) {
this.overcontainer = null;
}
});
drake.on('dragend', (el) => {
this.isDragging = false;
this.draggingItemId = null;
this.overcontainer = null;
});
}
ngAfterViewInit(): void {
const top = document.querySelector('.top-scroller');
const bottom = document.querySelector('.bottom-scroller');
const tableDiv = document.querySelector('#tocentrytable');
try {
top.addEventListener('mouseover', (e) => { this.scrollTableTop = true; }, {
passive: true
});
bottom.addEventListener('mouseover', (e) => { this.scrollTableBottom = true; }, {
passive: true
});
top.addEventListener('mouseout', (e) => { this.scrollTableTop = false }, {
passive: true
});
bottom.addEventListener('mouseout', (e) => { this.scrollTableBottom = false; }, {
passive: true
});
this.$clock
.pipe(
takeUntil(this._destroyed),
filter(() => this.scrollTableTop)
)
.subscribe(() => {
try {
tableDiv.scrollBy(0, -this.pxToScroll);
} catch { }
});
this.$clock
.pipe(
takeUntil(this._destroyed),
filter(() => this.scrollTableBottom)
)
.subscribe(() => {
try {
tableDiv.scrollBy(0, this.pxToScroll);
} catch { }
});
} catch {
console.log('could not find scrolling elements');
}
}
private _scrollIntoDragginItem(id: string) {
// const table = document.getElementById('tocentrytable');
// if(table){
// // const element = document.getElementById('TABLE_ENTRY'+id);
// console.log('Table found!');
// const element = document.getElementById('TABLE_ENTRY' + id);
// const elementFromTable = table.closest('#TABLE_ENTRY'+ id);
// if(elementFromTable){
// console.log('found from table:', elementFromTable);
// }
// if(element){
// console.log('Element found!');
// // element.classList.add('text-danger');
// // console.log(element);
// const tableRect = table.getBoundingClientRect();
// const elementRect = element.getBoundingClientRect();
// console.log('tablerect :',tableRect);
// console.log('elementRect :',elementRect);
// const dY = elementRect.top - tableRect.top;
// console.log('Distance from table is ', dY);
// // table.scroll({top:dY,behavior:'smooth'});
// console.log('found from document ', element);
// // element.scrollIntoView();
// }
// // element.scrollIntoView();
// }
}
private _findTocEntryById(id: string, tocentries: ToCEntry[]): ToCEntry {
if (!tocentries) {
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;
}
ngOnInit(): void {
}
itemClicked(item: ToCEntry) {
//leaf node
this.itemClick.emit(item);
}
// addNewEntry(tce: ToCEntry){
// this.newEntry.emit(tce);
// }
deleteEntry(currentLink: ToCEntry) {
this.removeEntry.emit(currentLink);
}
createNewEntry(newEntry: NewEntryType) {
this.createEntry.emit(newEntry);
}
onDataNeedsRefresh() {
this.dataNeedsRefresh.emit();
}
notifyUser(message: string) {
this.snackbar.open(message, null, this._snackBarConfig);
}
private _snackBarConfig: MatSnackBarConfig = {
duration: 2000
}
private _compareOrdinals(a, b) {
const aValue = a.get('ordinal').value as number;
const bValue = b.get('ordinal').value as number;
// if(!aValue || !bValue) return 0;
return aValue - bValue;
}
}

View File

@ -0,0 +1,252 @@
<!-- <span style="cursor: pointer;" [ngClass]="{'active': itemSelected?.id == parentLink?.id}" (click)="itemClicked(parentLink)" *ngIf="!(parentLink.type == undefined)" >
{{parentLink?.numbering}} {{parentLink?.label? parentLink?.label : 'DESCRIPTION-TEMPLATE-EDITOR.STEPS.GENERAL-INFO.UNTITLED' | translate}}
<span class="mat-button" (click)="deleteEntry(parentLink)" *ngIf="itemSelected?.id == parentLink?.id"><mat-icon>delete</mat-icon></span>
</span> -->
<div class="table-item row align-items-center" *ngIf="!(parentLink.type == undefined)">
<div class="col link-info"
>
<!-- [ngStyle]="{'padding-top': (!((parentLink?.subEntriesType == tocEntryType.FieldSet) && !selectedItemInLinks) || itemSelected?.id == parentLink.id)? '0em': '.6em'}" -->
<!-- <span style="cursor: pointer;" [ngClass]="{'active': itemSelected?.id == parentLink?.id}" (click)="itemClicked(parentLink)" >
{{parentLink?.numbering}} {{parentLink?.label? parentLink?.label : 'DESCRIPTION-TEMPLATE-EDITOR.STEPS.GENERAL-INFO.UNTITLED' | translate}}
</span> -->
<!-- <span style="cursor: pointer;" [ngClass]="{'active': itemSelected?.id == parentLink?.id}" (click)="itemClicked(parentLink)"
[matBadge]="parentLink.subEntries?.length"
matBadgeOverlap="false"
[matBadgeHidden]="!((parentLink?.subEntriesType == tocEntryType.FieldSet) && !selectedItemInLinks) || itemSelected?.id == parentLink.id"
matBadgePosition="before"
matBadgeColor="accent"
matBadgeSize="small"
[ngStyle]="{'font-size' : (parentLink.type == tocEntryType.FieldSet? '.9rem':'1rem')}"
> -->
<span class="table-label-element"
[ngClass]="{'table-label-element-active': itemSelected?.id == parentLink?.id, 'text-danger': colorError() ||( (parentLink?.subEntriesType === tocEntryType.FieldSet )&& !colorError() && !selectedItemInLinks && parentLink?.form.invalid && colorizeInvalid && (itemSelected?.id != parentLink?.id) && !_findTocEntryById(itemSelected?.id, parentLink?.subEntries))}"
(click)="itemClicked(parentLink)"
[ngStyle]="{'font-size' : (parentLink.type == tocEntryType.FieldSet? '.9rem':'1rem')}"
[id]="'TABLE_ENTRY'+parentLink.id"
>
<!-- {{parentLink?.numbering}} {{parentLink?.label? parentLink?.label : 'DESCRIPTION-TEMPLATE-EDITOR.STEPS.GENERAL-INFO.UNTITLED' | translate}} -->
<!-- {{parentLink?.numbering}} {{parentLink?.form.get('title').value? parentLink?.form.get('title').value : ('DESCRIPTION-TEMPLATE-EDITOR.STEPS.GENERAL-INFO.UNTITLED' | translate) + ' '+ ( (parentLink.type ===tocEntryType.Page? ('DESCRIPTION-TEMPLATE-EDITOR.STEPS.PAGE-INFO.PAGE' | translate) : (parentLink.type === tocEntryType.Section? ('DESCRIPTION-TEMPLATE-EDITOR.STEPS.SECTION-INFO.SECTION' | translate) : '') ) | lowercase )}} -->
{{parentLink?.numbering}} {{parentLink?.form.get('title').value? parentLink?.form.get('title').value : 'DESCRIPTION-TEMPLATE-EDITOR.STEPS.GENERAL-INFO.UNTITLED' | translate}}
<ng-container *ngIf="!parentLink.form.get('title').value" [ngSwitch]="parentLink.type">
<ng-container *ngSwitchCase="tocEntryType.FieldSet">
{{'DESCRIPTION-TEMPLATE-EDITOR.STEPS.GENERAL-INFO.QUESTION' | translate |lowercase}}
</ng-container>
<ng-container *ngSwitchCase="tocEntryType.Section">
{{'DESCRIPTION-TEMPLATE-EDITOR.STEPS.SECTION-INFO.SECTION' | translate |lowercase}}
</ng-container>
<ng-container *ngSwitchCase="tocEntryType.Page">
{{'DESCRIPTION-TEMPLATE-EDITOR.STEPS.PAGE-INFO.PAGE' | translate | lowercase}}
</ng-container>
</ng-container>
<!-- {{parentLink?.numbering}} {{parentLink?.form.get('title').value? parentLink?.form.get('title').value : parentLink.id}} -->
</span>
</div>
<div class="col-auto d-flex align-items-center" >
<ng-container *ngIf="!(!((parentLink?.subEntriesType == tocEntryType.FieldSet) && !selectedItemInLinks) || itemSelected?.id == parentLink.id ||isDragging)">
<!-- <mat-icon class="text-danger" style="font-size: 1.4em;" *ngIf="!colorError() && parentLink?.form.invalid && colorizeInvalid && allFieldsAreTouched(parentLink?.form)">priority_high</mat-icon> -->
<span class="badge-ball"
>
{{parentLink.subEntries?.length}}
</span>
</ng-container>
<span style="cursor: pointer;" (click)="deleteEntry(parentLink)"
[hidden]="!(!viewOnly && (itemSelected?.id == parentLink?.id) && (parentLink?.type != tocEntryType.FieldSet))"
>
<mat-icon style="font-size: 1.4em;">delete</mat-icon>
</span>
</div>
</div>
<!-- When item is not selected then show only the pages (first level) -->
<!-- <ng-container *ngIf="tocEntryIsChildOf(itemSelected,parentLink) || (!itemSelected && parentLink?.subEntriesType == tocEntryType.Page)"> -->
<!-- [ngClass]="{'border-left-active':itemSelected?.id == parentLink?.id, 'pl-1':itemSelected?.id == parentLink?.id, 'pb-4': isDragging && (parentLink?.type!= tocEntryType.FieldSet) && (parentLink?.id != draggingItemId) }" -->
<div dragula="TABLEDRAG" class="ml-2"
[ngClass]="{'border-left-active':itemSelected?.id == parentLink?.id, 'ml-1':itemSelected?.id == parentLink?.id, 'pb-4': isDragging && (parentLink?.type!= tocEntryType.FieldSet) && (parentLink?.id != draggingItemId) }"
[hidden]="!((parentLink?.subEntriesType!= tocEntryType.FieldSet) || selectedItemInLinks || parentLink?.id === itemSelected?.id ||isDragging)"
class="cdk-link-list"
[id]="parentLink.id"
[ngStyle]="{'border': overContainerId === parentLink?.id? '1px solid #129D99': '', 'min-height': ((!links?.length||(links.length ==1) ) && (parentLink?.type != tocEntryType.FieldSet) && (isDragging) && (draggingItemId != parentLink?.id)) ? '3em':'inherit'}"
>
<ng-container *ngIf="draggingItemId != parentLink?.id">
<div *ngFor="let link of links; last as isLast"
[ngClass]="{'mb-3': link.type === tocEntryType.Page}"
[id]="DRAGULA_ITEM_ID_PREFIX + link.id"
>
<div class="docs-link mt-0">
<!-- <div class="link-name"> -->
<!-- <div class="table-item row">
<div class="col link-info">
<span style="cursor: pointer;" [ngClass]="{'active': itemSelected?.id == link.id}" (click)="itemClicked(link)" >
{{link.numbering}} {{link.label? link.label : 'DESCRIPTION-TEMPLATE-EDITOR.STEPS.GENERAL-INFO.UNTITLED' | translate}}
</span>
</div>
<div class="table-item-actions col-auto" *ngIf="!viewOnly">
<button class="mat-button" (click)="deleteEntry(link)"><mat-icon>delete</mat-icon></button>
</div>
</div> -->
<!-- For dev purposes -->
<!-- <ng-container [ngSwitch]="link.type">
<div *ngSwitchCase="tocEntryType.FieldSet">
<span style="background-color: yellow;"> Fieldset</span>
</div>
<div *ngSwitchCase="tocEntryType.Page">
<span style="background-color: lightblue;"> Page</span>
</div>
<div *ngSwitchCase="tocEntryType.Section">
<span style="background-color: lightgreen;"> Section</span>
</div>
</ng-container> -->
<!-- </div> -->
<!-- <button cdkDragHandle>drab</button> -->
<div [ngClass]="{'ml-3': link.type != tocEntryType.Page }">
<app-description-template-table-of-contents-internal-section
[links]="link.subEntries"
(itemClick)="itemClicked($event)"
(removeEntry)="deleteEntry($event)"
[parentLink]="link"
[itemSelected] = "itemSelected"
(createFooEntry)="createNewEntry($event)"
[viewOnly]="viewOnly"
(dataNeedsRefresh)="onDataNeedsRefresh()"
[DRAGULA_ITEM_ID_PREFIX]="DRAGULA_ITEM_ID_PREFIX"
[overContainerId]="overContainerId"
[isDragging]="isDragging"
[draggingItemId]="draggingItemId"
[parentRootId]="parentRootId"
[colorizeInvalid]="colorizeInvalid">
</app-description-template-table-of-contents-internal-section>
<ng-container *ngIf="!isDragging">
<!-- <div *ngIf="links && !viewOnly && !(parentLink?.subEntriesType == tocEntryType.Page) " > -->
<ng-container *ngIf="selectedItemInLinks && (link.type != tocEntryType.Page) && isLast && (!viewOnly)">
<button class="mat-button add-new-entry" style="padding-left: 0px;" (click)="createNewEntry({childType:link.type,parent:parentLink})">
<!-- <mat-icon>add</mat-icon> -->
<ng-container [ngSwitch]="link.type">
<ng-container *ngSwitchCase="tocEntryType.Section">
<div class="d-flex" style="align-items: center;" >
<mat-icon color="accent" style="font-size: 1.6em;margin-left: -0.3em;">add</mat-icon>
<!-- Create new subsection -->
{{'DESCRIPTION-TEMPLATE-EDITOR.STEPS.PAGE-INFO.ACTIONS.CREATE-NEW-SUBSECTION' |translate}}
</div>
<!-- Subsection + -->
</ng-container>
<!-- <ng-container *ngSwitchCase="tocEntryType.Page">
Section
</ng-container> -->
<ng-container *ngSwitchCase="tocEntryType.FieldSet">
<img src="/assets/images/editor/icons/add_input_set.svg" alt="" style="margin-left: 0.1em;" class="add-input-icon">
{{'DESCRIPTION-TEMPLATE-EDITOR.STEPS.TOOLKIT.NEW-INPUT-SET' | translate}}
</ng-container>
</ng-container>
</button>
</ng-container>
<!-- </div> -->
</ng-container>
</div>
</div>
</div> <!-- END OF LOOP-->
</ng-container>
</div>
<ng-container *ngIf="!isDragging">
<!-- BUILD SUBENTRIES IF THEY DONT EXIST -- CURRENT ITEM DOES HAVE CHILDREN -->
<div *ngIf="(!links && parentLink.type!= tocEntryType.FieldSet) && !viewOnly &&parentLink?.id == itemSelected?.id" class="docs-link mt-0">
<div class="ml-3">
<!-- Give option to generate fieldset (only if parent is section) -->
<button *ngIf="parentLink.type == tocEntryType.Section" class="mat-button" style="padding-left: 0px;" (click)="createNewEntry({childType:tocEntryType.FieldSet,parent:parentLink})">
<img src="/assets/images/editor/icons/add_input_set.svg" alt="" style="margin-left: -0.2em;" class="add-input-icon">
{{'DESCRIPTION-TEMPLATE-EDITOR.STEPS.TOOLKIT.NEW-INPUT-SET' | translate}}
</button>
<!-- Give option to generate section -->
<button class="mat-button" style="padding-left: 0px; display: block;" (click)="createNewEntry({childType:tocEntryType.Section,parent:parentLink})">
<!-- <mat-icon>add</mat-icon> -->
<!-- Subsection + -->
<div class="d-flex" style="align-items: center;" >
<mat-icon color="accent" style="font-size: 1.6em;margin-left: -0.3em;">add</mat-icon>
<!-- Create new subsection -->
{{'DESCRIPTION-TEMPLATE-EDITOR.STEPS.PAGE-INFO.ACTIONS.CREATE-NEW-SUBSECTION' |translate}}
</div>
</button>
</div>
</div>
<!-- BUILD TYPE OF SAME LEVEL AS LINK OF LINKS -->
<!-- <div *ngIf="links && !viewOnly && !(parentLink?.subEntriesType == tocEntryType.Page) " >
<ng-container >
<button class="mat-button" style="padding-left: 0px;" (click)="createNewEntry({childType:parentLink.subEntriesType,parent:parentLink})">
<mat-icon>add</mat-icon>
<ng-container [ngSwitch]="parentLink?.subEntriesType">
<ng-container *ngSwitchCase="tocEntryType.Section">
Subsection
</ng-container>
<ng-container *ngSwitchCase="tocEntryType.Page">
Section
</ng-container>
<ng-container *ngSwitchCase="tocEntryType.FieldSet">
Fieldset
</ng-container>
</ng-container>
</button>
</ng-container>
</div> -->
<!-- </ng-container> -->
<!-- Only for the page -->
<!-- style="margin-left: -0.5em;" -->
<div *ngIf="parentLink?.subEntriesType == tocEntryType.Page && !viewOnly" >
<button class="mat-button mt-2" (click)="createNewEntry({childType:parentLink.subEntriesType,parent:parentLink})" style="padding-left:0px">
<!-- <mat-icon>add</mat-icon> -->
<div class="d-flex" style="align-items: center;" >
<mat-icon color="accent" style="font-size: 1.6em;margin-left: -0.3em;">add</mat-icon>
<ng-container *ngIf="!links?.length else createsection">
{{'DESCRIPTION-TEMPLATE-EDITOR.STEPS.PAGE-INFO.ACTIONS.CREATE-FIRST-SECTION'| translate}}
<!-- Create the first section -->
</ng-container>
<ng-template #createsection>
{{'DESCRIPTION-TEMPLATE-EDITOR.STEPS.PAGE-INFO.ACTIONS.CREATE-NEW-SECTION'| translate}}
<!-- Create new section -->
</ng-template>
</div>
</button>
<!-- <button (click)="showDroplists()">show droplist</button>
<button (click)="showStructs()">structs</button> -->
</div>
</ng-container>

View File

@ -0,0 +1,116 @@
// .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;
// cursor: pointer;;
// // 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;
// }
// .table-item-actions{
// // display: none;
// display: inline-block;
// visibility: hidden;
// }
// .table-item:hover {
// .table-item-actions{
// // display: inline-block;
// visibility: visible;
// }
// }
// .table-item col{
// text-overflow: ellipsis;
// overflow: hidden;
// white-space: nowrap;
// }
.link-info{
// display: inline-block; cursor: pointer;
// padding-top: .6em;
// padding-left: .6em;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.border-left-active{
border-left: 1px solid #000;
}
.side-bolder{
border-left: 1px solid red;
}
.cdk-link-list {
display: block;
// background: white;
overflow: hidden;
}
$blue-color : var(--primary-color);
$blue-color-light: #5cf7f2;
$yellow: var(--secondary-color);
.badge-ball{
display: inline-block;
border-radius: 50%;
background-color: #FFF;
font-size: small;
font-weight: bold;
min-width: 2em;
text-align: center;
}
.table-label-element{
cursor: pointer;
// font-weight: normal;
// transition-property: font-weight;
// transition-duration: 160ms;
// transition-delay: 50ms;
// transition-timing-function: ease-in-out;
}
.table-label-element-active{
font-weight: bold;
// color: red;
}
.add-input-icon{
width: 20px;
}

View File

@ -0,0 +1,280 @@
import { DOCUMENT } from '@angular/common';
import { Component, EventEmitter, Inject, Input, OnInit, Output, SimpleChanges } from '@angular/core';
import { AbstractControl, UntypedFormArray, UntypedFormControl, UntypedFormGroup } from '@angular/forms';
import { BaseComponent } from '@common/base/base.component';
import { NewEntryType, ToCEntry, ToCEntryType } from '../description-template-table-of-contents-entry';
@Component({
selector: 'app-description-template-table-of-contents-internal-section',
styleUrls: ['./description-template-table-of-contents-internal-section.scss'],
templateUrl: './description-template-table-of-contents-internal-section.html'
})
export class DescriptionTemplateTableOfContentsInternalSection extends BaseComponent implements OnInit {
@Input() links: ToCEntry[];
@Output() itemClick = new EventEmitter<ToCEntry>();
@Output() removeEntry = new EventEmitter<ToCEntry>();
@Output() createFooEntry = new EventEmitter<NewEntryType>();
@Output() dataNeedsRefresh = new EventEmitter<void>();
@Input() parentLink: ToCEntry;
@Input() itemSelected: ToCEntry;
@Input() DRAGULA_ITEM_ID_PREFIX;
@Input() overContainerId: string;
@Input() isDragging;
@Input() draggingItemId: string;
@Input() parentRootId: string;
@Input() colorizeInvalid: boolean = false;
@Input() viewOnly: boolean;
// @Input() dropListGroup: Set<string> = new Set<string>();
// @Input() dropListGroup: string[];
// @Input() dragHoveringOver: boolean = false;
// @Input() depth: number = 0;
// @Input() dropListStruct: { id: string, depth: number}[] = [];
constructor(
@Inject(DOCUMENT) private _document: Document) {
super();
}
tocEntryType = ToCEntryType;
// compareFn(a, b){
// if(a.depth> b.depth) return -1;
// if(a.depth< b.depth) return 1;
// return 0;
// }
ngOnInit(): void {
}
// hoveroverEnter(){
// // console.log('user hovering drag over', this.parentLink.id, this.parentLink.label);
// this.dragHoveringOver = true;
// }
// hoveroverLeft(){
// this.dragHoveringOver = false;
// }
ngOnChanges(changes: SimpleChanges) {
}
// get grouListIds(){
// return Array.from(this.dropListGroup);
// }
itemClicked(item: ToCEntry) {
//leaf node
this.itemClick.emit(item);
}
deleteEntry(currentLink: ToCEntry) {
this.removeEntry.emit(currentLink);
}
createNewEntry(foo: NewEntryType) {
this.createFooEntry.emit(foo);
}
// tocEntryIsChildOf(testingChild: ToCEntry,parent: ToCEntry): boolean{
// if(!testingChild || !parent) return false;
// if(testingChild.id == parent.id){return true;}
// if(parent.subEntries){
// let childFound:boolean = false;
// parent.subEntries.forEach(subEntry=>{
// if(this.tocEntryIsChildOf(testingChild, subEntry)){
// childFound = true;
// return true;
// }
// })
// return childFound;
// }
// return false;
// }
get selectedItemInLinks() {
if (!this.links || !this.itemSelected) return false;
const link = this.links.find(l => l.id === this.itemSelected.id);
if (link) return true;
return false;
}
// appendAndGetDroplists(dropList: CdkDropList){
// this.dropListGroup.push(dropList);
// return this.dropListGroup;
// }
// drop(event: CdkDragDrop<string[]>) {
// // if(!this.links || !this.links.length) return;
// if(event.container === event.previousContainer){
// moveItemInArray(this.links, event.previousIndex, event.currentIndex);
// let arrayToUpdate: FormArray = this.links[0].form.parent as FormArray;
// // if(this.parentLink && this.parentLink.form){
// // switch(this.parentLink.subEntriesType){
// // case this.tocEntryType.Field:
// // arrayToUpdate = (this.parentLink.form.get('fields') as FormArray);
// // break;
// // case this.tocEntryType.FieldSet:
// // arrayToUpdate = (this.parentLink.form.get('fieldSets') as FormArray);
// // break;
// // case this.tocEntryType.Section:
// // arrayToUpdate = (this.parentLink.form.get('sections') as FormArray);
// // break
// // }
// // }
// if(arrayToUpdate.controls){
// moveItemInArray(arrayToUpdate.controls, event.previousIndex, event.currentIndex);
// //update ordinality
// arrayToUpdate.controls.forEach((element,idx ) => {
// element.get('ordinal').setValue(idx);
// element.updateValueAndValidity();
// });
// }
// this.dataNeedsRefresh.emit();
// }else{
// console.log('not same container');
// }
// console.log(event.container.id);
// }
onDataNeedsRefresh() {
this.dataNeedsRefresh.emit();
}
// get hoveringOverParent(){
// if(!this.overContainerId) return false;
// const child = this._findTocEntryById(this.overContainerId, this.parentLink.subEntries);
// if(!child) return false;
// return true;
// }
public _findTocEntryById(id: string, tocentries: ToCEntry[]): ToCEntry {
if (!tocentries) {
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;
}
colorError(): boolean {
if (!this.colorizeInvalid) return false;
const form = this.parentLink.form;
if ((!form || form.valid || !form.touched) && this.parentLink.type !== this.tocEntryType.Page) return false;
const allFieldsAreTouched = this.allFieldsAreTouched(form);
//fieldset may have errros that are inside its controls and not in the fieldsetFormGroup
if (this.parentLink.type === this.tocEntryType.FieldSet && allFieldsAreTouched) return true;
if (form.errors && allFieldsAreTouched) return true;
//check if page has sections
if (this.parentLink.type === this.tocEntryType.Page && allFieldsAreTouched) {
const rootForm = form.root;
if (rootForm) {
const sections = rootForm.get('sections') as UntypedFormArray;
if (!sections.controls.find(section => section.get('page').value === this.parentLink.id)) {
return true;
}
}
}
//checking first child form controls if have errors
let hasErrors = false;
if (allFieldsAreTouched) {
if (form instanceof UntypedFormGroup) {
const formGroup = form as UntypedFormGroup;
const controls = Object.keys(formGroup.controls);
controls.forEach(control => {
if (formGroup.get(control).errors) {
hasErrors = true;
}
})
}
}
return hasErrors;
}
allFieldsAreTouched(aControl: AbstractControl) {//auto na testaroume
if (!aControl || aControl.untouched) return false;
if (aControl instanceof UntypedFormControl) {
return aControl.touched;
} else if (aControl instanceof UntypedFormGroup) {
const controlKeys = Object.keys((aControl as UntypedFormGroup).controls);
let areAllTouched = true;
controlKeys.forEach(key => {
if (!this.allFieldsAreTouched(aControl.get(key))) {
areAllTouched = false;
}
})
// const areAllTouched = controlKeys.reduce((acc, key)=>acc && this._allFieldsAreTouched(aControl.get(key)), true);
return areAllTouched;
} else if (aControl instanceof UntypedFormArray) {
const controls = (aControl as UntypedFormArray).controls;
// const areAllTouched = controls.reduce((acc, control)=>acc && this._allFieldsAreTouched(control), true);
let areAllTouched = true;
// controls.reduce((acc, control)=>acc && this._allFieldsAreTouched(control), true);
controls.forEach(control => {
if (!this.allFieldsAreTouched(control)) {
areAllTouched = false;
}
});
return areAllTouched;
}
return false;
}
}

View File

@ -23,7 +23,7 @@ import * as FileSaver from 'file-saver';
import { Observable } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { nameof } from 'ts-simple-nameof';
import { ImportDescriptionTemplateDialogComponent } from './import-dmp-blueprint/import-description-template.dialog.component';
import { ImportDescriptionTemplateDialogComponent } from './import-description-template/import-description-template.dialog.component';
import { DescriptionTemplateService } from '@app/core/services/description-template/description-template.service';
import { DescriptionTemplateStatus } from '@app/core/common/enum/description-template-status';
import { DescriptionTemplateLookup } from '@app/core/query/description-template.lookup';