From c08f05bc0455b7a5f918053b933492ea65b2ac4c Mon Sep 17 00:00:00 2001 From: Diamantis Tziotzios Date: Tue, 31 Oct 2023 11:19:52 +0200 Subject: [PATCH] no message --- .../eu/eudat/commons/enums/FieldType.java | 2 +- .../descriptiontemplate/FieldEntity.java | 2 +- .../eu/eudat/data/UserDatasetProfile.java | 4 +- .../FieldSetPersist.java | 14 +- .../dmpblueprint/DmpBlueprintServiceImpl.java | 4 +- .../FieldDataHelperServiceProvider.java | 4 + .../eu/eudat/controllers/FileController.java | 2 +- .../eudat/logic/managers/DatasetManager.java | 2 +- ...iption-template-field-autocomplete-type.ts | 4 + .../description-template-persist.ts | 164 +++- .../description-template.ts | 1 - .../description-template-type.service.ts | 54 +- .../services/dmp/dmp-blueprint.service.ts | 17 +- .../description-template.module.ts | 21 +- .../editor/animations/animations.ts | 90 ++ ...description-template-editor.component.html | 514 ++++++----- .../description-template-editor.component.ts | 848 +++++++++++++++++- .../description-template-editor.model.ts | 719 ++++++++++----- .../description-template-editor.resolver.ts | 2 +- ...iption-template-table-of-contents-entry.ts | 29 + ...escription-template-table-of-contents.html | 32 + ...escription-template-table-of-contents.scss | 92 ++ .../description-template-table-of-contents.ts | 605 +++++++++++++ ...te-table-of-contents-internal-section.html | 252 ++++++ ...te-table-of-contents-internal-section.scss | 116 +++ ...late-table-of-contents-internal-section.ts | 280 ++++++ .../description-template-listing.component.ts | 2 +- ...description-template.dialog.component.html | 0 ...description-template.dialog.component.scss | 0 ...t-description-template.dialog.component.ts | 0 30 files changed, 3379 insertions(+), 497 deletions(-) create mode 100644 dmp-frontend/src/app/core/common/enum/description-template-field-autocomplete-type.ts create mode 100644 dmp-frontend/src/app/ui/admin/description-template/editor/animations/animations.ts create mode 100644 dmp-frontend/src/app/ui/admin/description-template/editor/table-of-contents/description-template-table-of-contents-entry.ts create mode 100644 dmp-frontend/src/app/ui/admin/description-template/editor/table-of-contents/description-template-table-of-contents.html create mode 100644 dmp-frontend/src/app/ui/admin/description-template/editor/table-of-contents/description-template-table-of-contents.scss create mode 100644 dmp-frontend/src/app/ui/admin/description-template/editor/table-of-contents/description-template-table-of-contents.ts create mode 100644 dmp-frontend/src/app/ui/admin/description-template/editor/table-of-contents/table-of-contents-internal-section/description-template-table-of-contents-internal-section.html create mode 100644 dmp-frontend/src/app/ui/admin/description-template/editor/table-of-contents/table-of-contents-internal-section/description-template-table-of-contents-internal-section.scss create mode 100644 dmp-frontend/src/app/ui/admin/description-template/editor/table-of-contents/table-of-contents-internal-section/description-template-table-of-contents-internal-section.ts rename dmp-frontend/src/app/ui/admin/description-template/listing/{import-dmp-blueprint => import-description-template}/import-description-template.dialog.component.html (100%) rename dmp-frontend/src/app/ui/admin/description-template/listing/{import-dmp-blueprint => import-description-template}/import-description-template.dialog.component.scss (100%) rename dmp-frontend/src/app/ui/admin/description-template/listing/{import-dmp-blueprint => import-description-template}/import-description-template.dialog.component.ts (100%) diff --git a/dmp-backend/core/src/main/java/eu/eudat/commons/enums/FieldType.java b/dmp-backend/core/src/main/java/eu/eudat/commons/enums/FieldType.java index 0c8d40bb9..ed63a0e83 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/commons/enums/FieldType.java +++ b/dmp-backend/core/src/main/java/eu/eudat/commons/enums/FieldType.java @@ -35,7 +35,7 @@ public enum FieldType implements DatabaseEnum { ORGANIZATIONS("organizations"), DATASET_IDENTIFIER("datasetIdentifier"), CURRENCY("currency"), - VALIDATION("validation");; + VALIDATION("validation"); private final String value; FieldType(String value) { diff --git a/dmp-backend/core/src/main/java/eu/eudat/commons/types/descriptiontemplate/FieldEntity.java b/dmp-backend/core/src/main/java/eu/eudat/commons/types/descriptiontemplate/FieldEntity.java index 25635b74c..cde8964f1 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/commons/types/descriptiontemplate/FieldEntity.java +++ b/dmp-backend/core/src/main/java/eu/eudat/commons/types/descriptiontemplate/FieldEntity.java @@ -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()); } } diff --git a/dmp-backend/core/src/main/java/eu/eudat/data/UserDatasetProfile.java b/dmp-backend/core/src/main/java/eu/eudat/data/UserDatasetProfile.java index 4ea086a57..f8fc8109b 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/data/UserDatasetProfile.java +++ b/dmp-backend/core/src/main/java/eu/eudat/data/UserDatasetProfile.java @@ -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 { @Id @GeneratedValue diff --git a/dmp-backend/core/src/main/java/eu/eudat/model/persist/descriptiontemplatedefinition/FieldSetPersist.java b/dmp-backend/core/src/main/java/eu/eudat/model/persist/descriptiontemplatedefinition/FieldSetPersist.java index f23d9e08d..a88301969 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/model/persist/descriptiontemplatedefinition/FieldSetPersist.java +++ b/dmp-backend/core/src/main/java/eu/eudat/model/persist/descriptiontemplatedefinition/FieldSetPersist.java @@ -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 diff --git a/dmp-backend/core/src/main/java/eu/eudat/service/dmpblueprint/DmpBlueprintServiceImpl.java b/dmp-backend/core/src/main/java/eu/eudat/service/dmpblueprint/DmpBlueprintServiceImpl.java index bbebb46af..f7a3c50a7 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/service/dmpblueprint/DmpBlueprintServiceImpl.java +++ b/dmp-backend/core/src/main/java/eu/eudat/service/dmpblueprint/DmpBlueprintServiceImpl.java @@ -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){ diff --git a/dmp-backend/core/src/main/java/eu/eudat/service/fielddatahelper/FieldDataHelperServiceProvider.java b/dmp-backend/core/src/main/java/eu/eudat/service/fielddatahelper/FieldDataHelperServiceProvider.java index 82fc0fff6..15a44dacb 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/service/fielddatahelper/FieldDataHelperServiceProvider.java +++ b/dmp-backend/core/src/main/java/eu/eudat/service/fielddatahelper/FieldDataHelperServiceProvider.java @@ -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 diff --git a/dmp-backend/web/src/main/java/eu/eudat/controllers/FileController.java b/dmp-backend/web/src/main/java/eu/eudat/controllers/FileController.java index fc5bcf828..904905f24 100644 --- a/dmp-backend/web/src/main/java/eu/eudat/controllers/FileController.java +++ b/dmp-backend/web/src/main/java/eu/eudat/controllers/FileController.java @@ -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 fieldNodes = new HashSet<>(); fieldNodes.addAll(JsonSearcher.findNodes(propertiesJson, "id", fieldId, false)); diff --git a/dmp-backend/web/src/main/java/eu/eudat/logic/managers/DatasetManager.java b/dmp-backend/web/src/main/java/eu/eudat/logic/managers/DatasetManager.java index c6ef08813..55c6af695 100644 --- a/dmp-backend/web/src/main/java/eu/eudat/logic/managers/DatasetManager.java +++ b/dmp-backend/web/src/main/java/eu/eudat/logic/managers/DatasetManager.java @@ -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 uploadNodes = new HashSet<>(); diff --git a/dmp-frontend/src/app/core/common/enum/description-template-field-autocomplete-type.ts b/dmp-frontend/src/app/core/common/enum/description-template-field-autocomplete-type.ts new file mode 100644 index 000000000..ed3534b95 --- /dev/null +++ b/dmp-frontend/src/app/core/common/enum/description-template-field-autocomplete-type.ts @@ -0,0 +1,4 @@ +export enum DescriptionTemplateFieldAutocompleteType { + UNCACHED = 0, + CACHED = 1 +} \ No newline at end of file diff --git a/dmp-frontend/src/app/core/model/description-template/description-template-persist.ts b/dmp-frontend/src/app/core/model/description-template/description-template-persist.ts index ae052b1f5..d82acc975 100644 --- a/dmp-frontend/src/app/core/model/description-template/description-template-persist.ts +++ b/dmp-frontend/src/app/core/model/description-template/description-template-persist.ts @@ -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; } \ No newline at end of file diff --git a/dmp-frontend/src/app/core/model/description-template/description-template.ts b/dmp-frontend/src/app/core/model/description-template/description-template.ts index cd14d67f4..be4e6741a 100644 --- a/dmp-frontend/src/app/core/model/description-template/description-template.ts +++ b/dmp-frontend/src/app/core/model/description-template/description-template.ts @@ -68,7 +68,6 @@ export interface DescriptionTemplateField { defaultValue: string; visibilityRules: DescriptionTemplateRule[]; validations: DescriptionTemplateFieldValidationType[]; - fieldType: DescriptionTemplateFieldType; includeInExport: boolean; data: DescriptionTemplateBaseFieldData; } diff --git a/dmp-frontend/src/app/core/services/description-template-type/description-template-type.service.ts b/dmp-frontend/src/app/core/services/description-template-type/description-template-type.service.ts index 6f95d24cf..fc1ce6762 100644 --- a/dmp-frontend/src/app/core/services/description-template-type/description-template-type.service.ts +++ b/dmp-frontend/src/app/core/services/description-template-type/description-template-type.service.ts @@ -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(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(x => x.id), + nameof(x => x.name) + ] + }; + lookup.order = { items: [nameof(x => x.name)] }; + if (like) { lookup.like = this.filterService.transformLike(like); } + return lookup; + } } diff --git a/dmp-frontend/src/app/core/services/dmp/dmp-blueprint.service.ts b/dmp-frontend/src/app/core/services/dmp/dmp-blueprint.service.ts index 30662946d..222c958d1 100644 --- a/dmp-frontend/src/app/core/services/dmp/dmp-blueprint.service.ts +++ b/dmp-frontend/src/app/core/services/dmp/dmp-blueprint.service.ts @@ -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 { diff --git a/dmp-frontend/src/app/ui/admin/description-template/description-template.module.ts b/dmp-frontend/src/app/ui/admin/description-template/description-template.module.ts index a321f7ba1..988baff9a 100644 --- a/dmp-frontend/src/app/ui/admin/description-template/description-template.module.ts +++ b/dmp-frontend/src/app/ui/admin/description-template/description-template.module.ts @@ -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 { } diff --git a/dmp-frontend/src/app/ui/admin/description-template/editor/animations/animations.ts b/dmp-frontend/src/app/ui/admin/description-template/editor/animations/animations.ts new file mode 100644 index 000000000..5b4cd484a --- /dev/null +++ b/dmp-frontend/src/app/ui/admin/description-template/editor/animations/animations.ts @@ -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}))]), + ]), + + +] \ No newline at end of file diff --git a/dmp-frontend/src/app/ui/admin/description-template/editor/description-template-editor.component.html b/dmp-frontend/src/app/ui/admin/description-template/editor/description-template-editor.component.html index 3f73a47f1..a608559b0 100644 --- a/dmp-frontend/src/app/ui/admin/description-template/editor/description-template-editor.component.html +++ b/dmp-frontend/src/app/ui/admin/description-template/editor/description-template-editor.component.html @@ -1,228 +1,318 @@ - +
+
+ +
+
+
+
+ + + done + + + {{idx+1}} + + {{step.label}} + +
+
+
+
+
-
- - -
-
- - {{'DESCRIPTION-TEMPLATE-EDITOR.FIELDS.NAME' | translate}} - - - {{'GENERAL.VALIDATION.REQUIRED' | translate}} + + + + + + + + + +
+
+
+
1.1 {{'DATASET-PROFILE-EDITOR.STEPS.GENERAL-INFO.DATASET-TEMPLATE-NAME'| translate}} *
+
{{'DATASET-PROFILE-EDITOR.STEPS.GENERAL-INFO.DATASET-TEMPLATE-NAME-HINT'| translate}}
+ + + {{'GENERAL.VALIDATION.REQUIRED' | + translate}} +
-

{{'DESCRIPTION-TEMPLATE-EDITOR.FIELDS.SECTIONS' | translate}}

- -
-
-
- - -
-
- {{'DESCRIPTION-TEMPLATE-EDITOR.FIELDS.SECTION-PREFIX' | translate}} {{sectionIndex + 1}} -
-
drag_indicator
- -
- -
-
-
- -
-
- - {{'DESCRIPTION-TEMPLATE-EDITOR.FIELDS.SECTION-NAME' | translate}} - - - {{'GENERAL.VALIDATION.REQUIRED' | translate}} - -
-
- - {{'DESCRIPTION-TEMPLATE-EDITOR.FIELDS.SECTION-DESCRIPTION' | translate}} - - - {{'GENERAL.VALIDATION.REQUIRED' | translate}} - -
-
- - {{'DESCRIPTION-TEMPLATE-EDITOR.FIELDS.SYSTEM-FIELDS' | translate}} - - {{enumUtils.toDescriptionTemplateSystemFieldTypeString(systemFieldType)}} - - -
-
- -
- - -
-
- -
- {{fieldIndex + 1}} -
-
- drag_indicator -
-
- - {{'DESCRIPTION-TEMPLATE-EDITOR.FIELDS.SYSTEM-FIELD' | translate}} - - -
-
- - {{'DESCRIPTION-TEMPLATE-EDITOR.FIELDS.FIELD-DATA-TYPE' | translate}} - - - {{enumUtils.toDescriptionTemplateExtraFieldDataTypeString(extraFieldDataType)}} - - - - {{'GENERAL.VALIDATION.REQUIRED' | translate}} - -
-
- - {{'DESCRIPTION-TEMPLATE-EDITOR.FIELDS.FIELD-LABEL' | translate}} - - - {{'GENERAL.VALIDATION.REQUIRED' | translate}} - -
-
- - {{'DESCRIPTION-TEMPLATE-EDITOR.FIELDS.FIELD-PLACEHOLDER' | translate}} - - -
-
- - {{'DESCRIPTION-TEMPLATE-EDITOR.FIELDS.FIELD-DESCRIPTION' | translate}} - - -
-
- {{'DESCRIPTION-TEMPLATE-EDITOR.FIELDS.FIELD-REQUIRED' | translate}} -
-
- -
-
- -
-
-
-
-
- -
-
-
- - {{'DESCRIPTION-TEMPLATE-EDITOR.FIELDS.DESCRIPTION-TEMPLATES' | translate}} - -
-
-
- -
-
-
- - {{'DESCRIPTION-TEMPLATE-EDITOR.FIELDS.DESCRIPTION-TEMPLATES' | translate}} - - - -
-
-
-
-
-
-
- - {{'DESCRIPTION-TEMPLATE-EDITOR.FIELDS.DESCRIPTION-TEMPLATE-LABEL' | translate}} - - -
-
- - {{'DESCRIPTION-TEMPLATE-EDITOR.FIELDS.DESCRIPTION-TEMPLATE-MIN-MULTIPLICITY' | translate}} - - -
-
- - {{'DESCRIPTION-TEMPLATE-EDITOR.FIELDS.DESCRIPTION-TEMPLATE-MAX-MULTIPLICITY' | translate}} - - -
-
-
-
-
- +
+
1.2 {{'DATASET-PROFILE-EDITOR.STEPS.GENERAL-INFO.DATASET-TEMPLATE-DESCRIPTION'| translate}} *
+ +
{{'DATASET-PROFILE-EDITOR.STEPS.GENERAL-INFO.DATASET-TEMPLATE-DESCRIPTION-HINT'| translate}}
+
+ + +
+ + {{'GENERAL.VALIDATION.REQUIRED'| translate}} +
-
-
-
-
- +
1.3 {{'DATASET-PROFILE-EDITOR.STEPS.GENERAL-INFO.DESCRIPTION-TEMPLATE-TYPE'| translate}} *
+ + {{'DATASET-PROFILE-EDITOR.STEPS.GENERAL-INFO.DESCRIPTION-TEMPLATE-SELECT-TYPE' | translate}} + + + {{'GENERAL.VALIDATION.REQUIRED' | translate}} + +
+
+ +
1.4 {{'DATASET-PROFILE-EDITOR.STEPS.GENERAL-INFO.DATASET-TEMPLATE-LANGUAGE'| translate}} *
+ + + + + {{ lang.name }} + + + {{'GENERAL.VALIDATION.REQUIRED' | + translate}} + + +
+ + + + +
+ + + +
+ + +
+ + +
+ + +
+ + +
+
+ + + +
+ +
+ +
+ + + +
-
-
- -
-
-
- +
+ + + +
+
+
+ + + +
- - - -
--> \ No newline at end of file + + + + + + + + + arrow_upward + +
SCROLL
+
+ + +
+ + +
+
+
+ + + + +
+ + + + + + + + + + + + + + +
+
\ No newline at end of file diff --git a/dmp-frontend/src/app/ui/admin/description-template/editor/description-template-editor.component.ts b/dmp-frontend/src/app/ui/admin/description-template/editor/description-template-editor.component.ts index 56c3e2f27..d55baec70 100644 --- a/dmp-frontend/src/app/ui/admin/description-template/editor/description-template-editor.component.ts +++ b/dmp-frontend/src/app/ui/admin/description-template/editor/description-template-editor.component.ts @@ -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 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; + toCEntries: ToCEntry[]; + selectedTocEntry: ToCEntry; + colorizeInvalid: boolean = false; + + // selectedSystemFields: Array = []; // descriptionTemplateSectionFieldCategory = DescriptionTemplateSectionFieldCategory; // descriptionTemplateSystemFieldType = DescriptionTemplateSystemFieldType; @@ -101,11 +116,13 @@ export class DescriptionTemplateEditorComponent extends BaseEditor { + 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 { + 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 { -// 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); + // }); + // } } diff --git a/dmp-frontend/src/app/ui/admin/description-template/editor/description-template-editor.model.ts b/dmp-frontend/src/app/ui/admin/description-template/editor/description-template-editor.model.ts index 843580008..ca015f558 100644 --- a/dmp-frontend/src/app/ui/admin/description-template/editor/description-template-editor.model.ts +++ b/dmp-frontend/src/app/ui/admin/description-template/editor/description-template-editor.model.ts @@ -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(); 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(); -// baseValidationArray.push({ key: 'sections', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}sections`)] }); - -// baseContext.validation = baseValidationArray; -// return baseContext; -// } + const baseContext: ValidationContext = new ValidationContext(); + const baseValidationArray: Validation[] = new Array(); + 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(); -// 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(); + 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(); + 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(); -// 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(); + 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(); + 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(); + 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(); + 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; diff --git a/dmp-frontend/src/app/ui/admin/description-template/editor/description-template-editor.resolver.ts b/dmp-frontend/src/app/ui/admin/description-template/editor/description-template-editor.resolver.ts index 60ee8ad1e..d817fac5a 100644 --- a/dmp-frontend/src/app/ui/admin/description-template/editor/description-template-editor.resolver.ts +++ b/dmp-frontend/src/app/ui/admin/description-template/editor/description-template-editor.resolver.ts @@ -58,7 +58,7 @@ export class DescriptionTemplateEditorResolver extends BaseEditorResolver { [nameof(x => x.definition), nameof(x => x.sections), nameof(x => x.fieldSets), nameof(x => x.fields), nameof(x => x.schematics)].join('.'), [nameof(x => x.definition), nameof(x => x.sections), nameof(x => x.fieldSets), nameof(x => x.fields), nameof(x => x.defaultValue)].join('.'), [nameof(x => x.definition), nameof(x => x.sections), nameof(x => x.fieldSets), nameof(x => x.fields), nameof(x => x.defaultValue)].join('.'), - [nameof(x => x.definition), nameof(x => x.sections), nameof(x => x.fieldSets), nameof(x => x.fields), nameof(x => x.fieldType)].join('.'), + // [nameof(x => x.definition), nameof(x => x.sections), nameof(x => x.fieldSets), nameof(x => x.fields), nameof(x => x.fieldType)].join('.'), [nameof(x => x.definition), nameof(x => x.sections), nameof(x => x.fieldSets), nameof(x => x.fields), nameof(x => x.includeInExport)].join('.'), [nameof(x => x.definition), nameof(x => x.sections), nameof(x => x.fieldSets), nameof(x => x.fields), nameof(x => x.validations)].join('.'), diff --git a/dmp-frontend/src/app/ui/admin/description-template/editor/table-of-contents/description-template-table-of-contents-entry.ts b/dmp-frontend/src/app/ui/admin/description-template/editor/table-of-contents/description-template-table-of-contents-entry.ts new file mode 100644 index 000000000..b1ddf7e9a --- /dev/null +++ b/dmp-frontend/src/app/ui/admin/description-template/editor/table-of-contents/description-template-table-of-contents-entry.ts @@ -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; +} \ No newline at end of file diff --git a/dmp-frontend/src/app/ui/admin/description-template/editor/table-of-contents/description-template-table-of-contents.html b/dmp-frontend/src/app/ui/admin/description-template/editor/table-of-contents/description-template-table-of-contents.html new file mode 100644 index 000000000..57c87b5fb --- /dev/null +++ b/dmp-frontend/src/app/ui/admin/description-template/editor/table-of-contents/description-template-table-of-contents.html @@ -0,0 +1,32 @@ +
+

{{'DESCRIPTION-TEMPLATE-EDITOR.STEPS.GENERAL-INFO.TEMPLATE-OUTLINE' | translate}}

+
+ + + +
+
+ keyboard_arrow_up +
+
+
+
+ keyboard_arrow_down +
+
+
+
\ No newline at end of file diff --git a/dmp-frontend/src/app/ui/admin/description-template/editor/table-of-contents/description-template-table-of-contents.scss b/dmp-frontend/src/app/ui/admin/description-template/editor/table-of-contents/description-template-table-of-contents.scss new file mode 100644 index 000000000..dc1e64f35 --- /dev/null +++ b/dmp-frontend/src/app/ui/admin/description-template/editor/table-of-contents/description-template-table-of-contents.scss @@ -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; +} \ No newline at end of file diff --git a/dmp-frontend/src/app/ui/admin/description-template/editor/table-of-contents/description-template-table-of-contents.ts b/dmp-frontend/src/app/ui/admin/description-template/editor/table-of-contents/description-template-table-of-contents.ts new file mode 100644 index 000000000..2bc4db504 --- /dev/null +++ b/dmp-frontend/src/app/ui/admin/description-template/editor/table-of-contents/description-template-table-of-contents.ts @@ -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(); + // @Output() newEntry = new EventEmitter(); + @Output() removeEntry = new EventEmitter(); + @Output() createEntry = new EventEmitter(); + @Output() dataNeedsRefresh = new EventEmitter(); + + 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; + } + + +} diff --git a/dmp-frontend/src/app/ui/admin/description-template/editor/table-of-contents/table-of-contents-internal-section/description-template-table-of-contents-internal-section.html b/dmp-frontend/src/app/ui/admin/description-template/editor/table-of-contents/table-of-contents-internal-section/description-template-table-of-contents-internal-section.html new file mode 100644 index 000000000..33446ba73 --- /dev/null +++ b/dmp-frontend/src/app/ui/admin/description-template/editor/table-of-contents/table-of-contents-internal-section/description-template-table-of-contents-internal-section.html @@ -0,0 +1,252 @@ + + +
+ +
+ + + + + {{parentLink.subEntries?.length}} + + + + + + delete + +
+
+ + + + +
+ + +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + +
+ + +
+ +
\ No newline at end of file diff --git a/dmp-frontend/src/app/ui/admin/description-template/editor/table-of-contents/table-of-contents-internal-section/description-template-table-of-contents-internal-section.scss b/dmp-frontend/src/app/ui/admin/description-template/editor/table-of-contents/table-of-contents-internal-section/description-template-table-of-contents-internal-section.scss new file mode 100644 index 000000000..3e4c13517 --- /dev/null +++ b/dmp-frontend/src/app/ui/admin/description-template/editor/table-of-contents/table-of-contents-internal-section/description-template-table-of-contents-internal-section.scss @@ -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; +} \ No newline at end of file diff --git a/dmp-frontend/src/app/ui/admin/description-template/editor/table-of-contents/table-of-contents-internal-section/description-template-table-of-contents-internal-section.ts b/dmp-frontend/src/app/ui/admin/description-template/editor/table-of-contents/table-of-contents-internal-section/description-template-table-of-contents-internal-section.ts new file mode 100644 index 000000000..1ab901ec5 --- /dev/null +++ b/dmp-frontend/src/app/ui/admin/description-template/editor/table-of-contents/table-of-contents-internal-section/description-template-table-of-contents-internal-section.ts @@ -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(); + @Output() removeEntry = new EventEmitter(); + + @Output() createFooEntry = new EventEmitter(); + + @Output() dataNeedsRefresh = new EventEmitter(); + + + + @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 = new Set(); + // @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) { + // // 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; + } +} \ No newline at end of file diff --git a/dmp-frontend/src/app/ui/admin/description-template/listing/description-template-listing.component.ts b/dmp-frontend/src/app/ui/admin/description-template/listing/description-template-listing.component.ts index 18f92147d..0ac92ca54 100644 --- a/dmp-frontend/src/app/ui/admin/description-template/listing/description-template-listing.component.ts +++ b/dmp-frontend/src/app/ui/admin/description-template/listing/description-template-listing.component.ts @@ -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'; diff --git a/dmp-frontend/src/app/ui/admin/description-template/listing/import-dmp-blueprint/import-description-template.dialog.component.html b/dmp-frontend/src/app/ui/admin/description-template/listing/import-description-template/import-description-template.dialog.component.html similarity index 100% rename from dmp-frontend/src/app/ui/admin/description-template/listing/import-dmp-blueprint/import-description-template.dialog.component.html rename to dmp-frontend/src/app/ui/admin/description-template/listing/import-description-template/import-description-template.dialog.component.html diff --git a/dmp-frontend/src/app/ui/admin/description-template/listing/import-dmp-blueprint/import-description-template.dialog.component.scss b/dmp-frontend/src/app/ui/admin/description-template/listing/import-description-template/import-description-template.dialog.component.scss similarity index 100% rename from dmp-frontend/src/app/ui/admin/description-template/listing/import-dmp-blueprint/import-description-template.dialog.component.scss rename to dmp-frontend/src/app/ui/admin/description-template/listing/import-description-template/import-description-template.dialog.component.scss diff --git a/dmp-frontend/src/app/ui/admin/description-template/listing/import-dmp-blueprint/import-description-template.dialog.component.ts b/dmp-frontend/src/app/ui/admin/description-template/listing/import-description-template/import-description-template.dialog.component.ts similarity index 100% rename from dmp-frontend/src/app/ui/admin/description-template/listing/import-dmp-blueprint/import-description-template.dialog.component.ts rename to dmp-frontend/src/app/ui/admin/description-template/listing/import-description-template/import-description-template.dialog.component.ts