import {cond, flow, includedIn, isDefined, isNot, Mapping, Map, on, subtract, undefinedOrEmpty, identity, compose, Pair, dissoc, pairWith, prune, filter, update, or, copy} from 'tsfun'; import {update as updateAsc, lookup, map, reduce} from 'tsfun/associative'; import {clone, update as updateStruct} from 'tsfun/struct'; import {LibraryCategoryDefinition} from '../model/library-category-definition'; import {CustomCategoryDefinition} from '../model/custom-category-definition'; import {ConfigurationErrors} from './configuration-errors'; import {ValuelistDefinition} from '../model/valuelist-definition'; import {withDissoc} from '../../util/utils'; import {TransientFieldDefinition, TransientCategoryDefinition} from '../model/transient-category-definition'; import {BuiltinCategoryDefinition} from '../model/builtin-category-definition'; import {mergeBuiltInWithLibraryCategories} from './merge-builtin-with-library-categories'; import {Assertions} from './assertions'; import {getDefinedParents, iterateOverFieldsOfCategories} from './helpers'; import {addSourceField} from './add-source-field'; import {mergeCategories} from './merge-categories'; import {addExtraFields} from './add-extra-fields'; import {hideFields} from './hide-fields'; import {RelationDefinition} from '../model/relation-definition'; import {addRelations} from './add-relations'; import {applySearchConfiguration} from './apply-search-configuration'; import {orderFields} from './order-fields'; import {makeCategoryTreeList} from './make-category-tree-list'; import {RawProjectConfiguration} from '../project-configuration'; import {Category} from '../model/category'; import {Group, Groups} from '../model/group'; import {Labelled} from '../../util/named'; import {RelationsUtil} from '../relations-utils'; import {CategoryDefinition} from '../model/category-definition'; import {ProjectCategories} from '../project-categories'; import {FieldDefinition} from '../model/field-definition'; import {mapTrees, mapTreeList, TreeList, ITEMNAMEPATH} from '../../util/tree-list'; import {sortStructArray} from '../../util/sort-struct-array'; import {linkParentAndChildInstances} from '../category-tree-list'; import {applyLanguageConfigurations} from './apply-language-configurations'; const CATEGORIES = [0]; /** * @author Daniel de Oliveira * @author Thomas Kleinke */ export function buildRawProjectConfiguration(builtInCategories: Map, libraryCategories: Map, customCategories: Map = {}, commonFields: Map = {}, valuelistsConfiguration: Map = {}, extraFields: Map = {}, relations: Array = [], languageConfigurations: any[] = [], searchConfiguration: any = {}, orderConfiguration: any = {}, validateFields: any = identity): RawProjectConfiguration { Assertions.performAssertions(builtInCategories, libraryCategories, customCategories, commonFields, valuelistsConfiguration); addSourceField(builtInCategories, libraryCategories, customCategories, commonFields); return flow( mergeBuiltInWithLibraryCategories(builtInCategories, libraryCategories), Assertions.assertInputTypesAreSet(Assertions.assertInputTypePresentIfNotCommonField(commonFields)), Assertions.assertNoDuplicationInSelection(customCategories), mergeCategories(customCategories, Assertions.assertInputTypePresentIfNotCommonField(commonFields)), eraseUnusedCategories(Object.keys(customCategories)), replaceCommonFields(commonFields), insertValuelistIds, Assertions.assertValuelistIdsProvided, hideFields(customCategories), toCategoriesByFamilyNames, replaceValuelistIdsWithValuelists(valuelistsConfiguration), addExtraFields(extraFields), prepareRawProjectConfiguration, addRelations(relations), applyLanguageConfigurations(languageConfigurations), updateStruct(CATEGORIES, processCategories( orderConfiguration, validateFields, languageConfigurations, searchConfiguration, relations) ) ); } const prepareRawProjectConfiguration = (configuration: Map) => [configuration, [] /* relations */]; function processCategories(orderConfiguration: any, validateFields: any, languageConfigurations: any[][], searchConfiguration: any, relations: Array): Mapping, TreeList> { const sortCategoryGroups = update(Category.GROUPS, sortGroups(Groups.DEFAULT_ORDER)); return compose( applySearchConfiguration(searchConfiguration), addExtraFieldsOrder(orderConfiguration), orderFields(orderConfiguration), validateFields, makeCategoryTreeList, mapTreeList(putRelationsIntoGroups(relations)), mapTreeList(sortCategoryGroups), mapTreeList(setGroupLabels(languageConfigurations)), setGeometriesInGroups(languageConfigurations), orderCategories(orderConfiguration?.categories), linkParentAndChildInstances ); } const setGeometriesInGroups = (languageConfigurations: any[]) => (categoriesTree: TreeList) => mapTreeList(adjustCategoryGeometry(languageConfigurations, categoriesTree), categoriesTree); function adjustCategoryGeometry(languageConfigurations: any[], categoriesTree: TreeList) { return (category: Category /* modified in place */): Category => { if (!ProjectCategories.isGeometryCategory(categoriesTree, category.name)) return category; let positionGroup = category.groups.find(group => group.name === Groups.POSITION); if (!positionGroup) { positionGroup = Group.create(Groups.POSITION); for (let languageConfiguration of languageConfigurations) { if (languageConfiguration.groups?.position) { positionGroup.label = languageConfiguration.groups.position; break; } } category.groups.push(positionGroup); } const geometryField: FieldDefinition = { name: 'geometry', group: 'position', inputType: 'geometry', editable: true }; for (let languageConfiguration of languageConfigurations) { if (languageConfiguration.other?.geometry) { geometryField.label = languageConfiguration.other.geometry; break; } } positionGroup.fields.unshift(geometryField); return category; } } function putRelationsIntoGroups(relations: Array) { return (category: Category /* modified in place */): Category => { const relDefs = RelationsUtil.getRelationDefinitions(relations, category.name); for (let relation of relDefs) { const groupName: string|undefined = Groups.getGroupNameForRelation(relation.name); if (!groupName) continue; let group = (category.groups as any)[groupName]; if (!group) { group = Group.create(groupName); (category.groups as any)[groupName] = group; } group.relations.push(relation); } return category; } } const sortGroups = (defaultOrder: string[]) => (groups: Map) => flow(defaultOrder, map(lookup(groups)), prune); const orderCategories = (categoriesOrder: string[] = []) => (categories: TreeList): TreeList => mapTrees(sortStructArray(categoriesOrder, ITEMNAMEPATH), categories) as TreeList; function setGroupLabels(languageConfigurations: any[]) { return (category: Category) => { const groupLabel = ({ name: name }: Group) => { if (name === Groups.PARENT) { return category.parentCategory ? category.parentCategory.label : category.label; } else if (name === Groups.CHILD) { return category.label; } else { for (let languageConfiguration of languageConfigurations) { if (languageConfiguration.groups?.[name]) { return languageConfiguration.groups[name]; } } } }; return update( Category.GROUPS, compose( map(pairWith(groupLabel)), map(([group, label]: Pair) => updateAsc(Labelled.LABEL, label)(group as any))))(category); }; } function addExtraFieldsOrder(orderConfiguration: any) { return (categories: any) => { if (!orderConfiguration.fields) orderConfiguration.fields = {}; Object.keys(categories).forEach(categoryName => { if (!orderConfiguration.fields[categoryName]) orderConfiguration.fields[categoryName] = []; orderConfiguration.fields[categoryName] = [].concat(orderConfiguration.fields[categoryName]); }); return categories; } } function insertValuelistIds(mergedCategories: Map) { iterateOverFieldsOfCategories(mergedCategories, (categoryName, category, fieldName, field) => { if (category.valuelists && category.valuelists[fieldName]) { field.valuelistId = category.valuelists[fieldName]; } if (category.positionValuelists && category.positionValuelists[fieldName]) { field.positionValuelistId = category.positionValuelists[fieldName]; } }); return mergedCategories; } function replaceValuelistIdsWithValuelists(valuelistDefinitionsMap: Map) : Mapping> { return map( cond( on(TransientCategoryDefinition.FIELDS, isNot(undefinedOrEmpty)), update(TransientCategoryDefinition.FIELDS, map( cond( or( on(TransientFieldDefinition.VALUELISTID, isDefined), on(TransientFieldDefinition.POSITION_VALUELIST_ID, isDefined) ), replaceValuelistIdWithActualValuelist(valuelistDefinitionsMap)))))) as any; } function replaceValuelistIdWithActualValuelist(valuelistDefinitionMap: Map) { return (fd: TransientFieldDefinition) => flow(fd, updateAsc(TransientFieldDefinition.VALUELIST, valuelistDefinitionMap[fd.valuelistId!]), updateAsc(TransientFieldDefinition.POSITION_VALUES, valuelistDefinitionMap[fd.positionValuelistId!]), dissoc(TransientFieldDefinition.VALUELISTID), dissoc(TransientFieldDefinition.POSITION_VALUELIST_ID) ); } function eraseUnusedCategories(selectedCategoriesNames: string[]) : Mapping> { return (categories: Map) => { const keysOfUnselectedCategories = flow( categories, Object.keys, filter(isNot(includedIn(selectedCategoriesNames))) ); const parentNamesOfSelectedCategories: string[] = flow( keysOfUnselectedCategories, reduce(withDissoc, categories), getDefinedParents ); const categoriesToErase = subtract(parentNamesOfSelectedCategories)(keysOfUnselectedCategories); return categoriesToErase.reduce(withDissoc, categories) as Map; } } function replaceCommonFields(commonFields: Map) : Mapping> { return map( cond( on(TransientCategoryDefinition.COMMONS, isDefined), (mergedCategory: TransientCategoryDefinition) => { const clonedMergedCategory = clone(mergedCategory); for (let commonFieldName of clonedMergedCategory.commons) { if (!commonFields[commonFieldName]) { throw [ConfigurationErrors.COMMON_FIELD_NOT_PROVIDED, commonFieldName]; } if (!clonedMergedCategory.fields[commonFieldName]) { clonedMergedCategory.fields[commonFieldName] = {}; } clonedMergedCategory.fields[commonFieldName] = copy(commonFields[commonFieldName]) as any; } delete clonedMergedCategory.commons; return clonedMergedCategory; })) as any; } function toCategoriesByFamilyNames(transientCategories: Map) : Map { return flow( transientCategories, reduce( (acc: any, transientCategory, transientCategoryName) => { acc[transientCategory.categoryName ? transientCategory.categoryName : transientCategoryName] = transientCategory; return acc; }, {})); }