diff --git a/core/stringUtils.js b/core/stringUtils.js index 49973f9a47..bce216f389 100644 --- a/core/stringUtils.js +++ b/core/stringUtils.js @@ -46,7 +46,8 @@ export const nullToEmpty = (value) => (value === null ? '' : value) export const appendIfMissing = (suffix) => (text) => (text.endsWith(suffix) ? text : `${text}${suffix}`) export const prependIfMissing = (prefix) => (text) => (text.startsWith(prefix) ? text : `${prefix}${text}`) export const removePrefix = (prefix) => (text) => (text.startsWith(prefix) ? text.substring(prefix.length) : text) -export const removeSuffix = (suffix) => (text) => text.substring(0, text.length - suffix.length) +export const removeSuffix = (suffix) => (text) => + text.endsWith(suffix) ? text.substring(0, text.length - suffix.length) : text export const quote = (text) => (isBlank(text) ? '' : `'${text}'`) diff --git a/core/survey/nodeDef.js b/core/survey/nodeDef.js index 1b1574f24b..e7e7ea474f 100644 --- a/core/survey/nodeDef.js +++ b/core/survey/nodeDef.js @@ -176,7 +176,16 @@ export const visibleFieldsDefaultByType = { [nodeDefType.taxon]: [valuePropsTaxon.code, valuePropsTaxon.scientificName, valuePropsTaxon.vernacularName], } -export const autoIncrementalKeyExpression = 'index($context) + 1' +export const createAutoIncrementalKeyDefaultValues = ({ nodeDef, nodeDefParent }) => { + const nodeDefName = getName(nodeDef) + const nodeDefParentName = getName(nodeDefParent) + return [ + NodeDefExpression.createExpression({ expression: '1', applyIf: 'index($context) == 0' }), + NodeDefExpression.createExpression({ + expression: `Math.max(parent($context).${nodeDefParentName}.${nodeDefName}) + 1`, + }), + ] +} // ==== READ @@ -506,7 +515,10 @@ export const convertToType = ({ toType }) => (nodeDef) => { const propsUpdated = R.pick(commonAttributePropsKeys)(getProps(nodeDef)) - const propsAdvancedUpdated = R.pick(commonAttributePropsAdvancedKeys)(getPropsAdvanced(nodeDef)) + const propsAdvancedToKeep = isAutoIncrementalKey(nodeDef) + ? commonAttributePropsAdvancedKeys.filter((prop) => prop !== keysPropsAdvanced.defaultValues) + : commonAttributePropsAdvancedKeys + const propsAdvancedUpdated = R.pick(propsAdvancedToKeep)(getPropsAdvanced(nodeDef)) const layout = getLayout(nodeDef) const layoutUpdated = Object.entries(layout).reduce((acc, [cycleKey, cycleLayout]) => { @@ -662,23 +674,17 @@ export const clearNotApplicableProps = (cycle) => (nodeDef) => { export const canHaveMobileProps = (cycle) => (nodeDef) => canBeHiddenInMobile(nodeDef) || canIncludeInMultipleEntitySummary(cycle)(nodeDef) -const isDefaultValueAutoIncrementExpression = (defaultValue) => { - const expression = NodeDefExpression.getExpression(defaultValue) - return ( - (expression === autoIncrementalKeyExpression || - StringUtils.removeSuffix('\n')(expression).replaceAll(' ', '') === - autoIncrementalKeyExpression.replaceAll(' ', '')) && - Objects.isEmpty(NodeDefExpression.getApplyIf(defaultValue)) - ) -} - -export const canHaveAutoIncrementalKey = (nodeDef) => { +export const canHaveAutoIncrementalKey = ({ nodeDef, nodeDefParent }) => { if (!isKey(nodeDef) || !isInteger(nodeDef)) return false const defaultValues = getDefaultValues(nodeDef) if (defaultValues.length === 0) return true - if (defaultValues.length > 1) return false - const defaultValue = defaultValues[0] - return NodeDefExpression.isEmpty(defaultValue) || isDefaultValueAutoIncrementExpression(defaultValue) + const autoIncrementalDefaultValues = createAutoIncrementalKeyDefaultValues({ nodeDef, nodeDefParent }) + return ( + defaultValues.length === autoIncrementalDefaultValues.length && + autoIncrementalDefaultValues.every((defaultValue, index) => + NodeDefExpression.isSimilarTo(defaultValue)(defaultValues[index]) + ) + ) } diff --git a/core/survey/nodeDefExpression.js b/core/survey/nodeDefExpression.js index 3b9330a706..ab532ad477 100644 --- a/core/survey/nodeDefExpression.js +++ b/core/survey/nodeDefExpression.js @@ -53,6 +53,16 @@ export const isPlaceholder = R.propEq(keys.placeholder, true) export const isEmpty = (expression = {}) => StringUtils.isBlank(getExpression(expression)) && StringUtils.isBlank(getApplyIf(expression)) +export const isSimilarTo = (expressionA) => (expressionB) => { + if (isEmpty(expressionA) && isEmpty(expressionB)) return true + if (isEmpty(expressionA) || isEmpty(expressionB)) return false + const prepareExpr = (expr) => StringUtils.removeSuffix('\n')(expr.replaceAll(' ', '')) + return ( + prepareExpr(getExpression(expressionA)) === prepareExpr(getExpression(expressionB)) && + prepareExpr(getApplyIf(expressionA)) === prepareExpr(getApplyIf(expressionB)) + ) +} + // ====== UPDATE const assocProp = (propName, value) => R.pipe(R.assoc(propName, value), R.dissoc(keys.placeholder)) diff --git a/webapp/components/survey/NodeDefDetails/AdvancedProps.js b/webapp/components/survey/NodeDefDetails/AdvancedProps.js index 05def5324c..15a93d48a2 100644 --- a/webapp/components/survey/NodeDefDetails/AdvancedProps.js +++ b/webapp/components/survey/NodeDefDetails/AdvancedProps.js @@ -77,7 +77,6 @@ const AdvancedProps = (props) => { { const nodeDefParentLabel = NodeDef.getLabel(nodeDefParent, lang) const ancestorMultipleEntityDef = Survey.getNodeDefAncestorMultipleEntity(nodeDef)(survey) const canHaveAutoIncrementalKey = - NodeDef.canHaveAutoIncrementalKey(nodeDef) && - ancestorMultipleEntityDef && - !NodeDef.isRoot(ancestorMultipleEntityDef) + NodeDef.isAutoIncrementalKey(nodeDef) || + (NodeDef.canHaveAutoIncrementalKey({ nodeDef, nodeDefParent }) && + ancestorMultipleEntityDef && + !NodeDef.isRoot(ancestorMultipleEntityDef)) return { nodeDef, diff --git a/webapp/components/survey/NodeDefDetails/store/actions/useSetProp.js b/webapp/components/survey/NodeDefDetails/store/actions/useSetProp.js index 6cbf8d5d69..a11e85ed42 100644 --- a/webapp/components/survey/NodeDefDetails/store/actions/useSetProp.js +++ b/webapp/components/survey/NodeDefDetails/store/actions/useSetProp.js @@ -5,7 +5,6 @@ import * as A from '@core/arena' import * as StringUtils from '@core/stringUtils' import * as Survey from '@core/survey/survey' import * as NodeDef from '@core/survey/nodeDef' -import * as NodeDefExpression from '@core/survey/nodeDefExpression' import * as NodeDefLayout from '@core/survey/nodeDefLayout' import * as NodeDefValidations from '@core/survey/nodeDefValidations' @@ -75,24 +74,24 @@ const _generateLabelFromName = (name) => { return parts.join(' ') } -const _onUpdateName = ({ nodeDef, nodeDefPrev, value: name, lang }) => { +const _onUpdateName = ({ survey, nodeDef, nodeDefPrev, value: name, lang }) => { + let nodeDefUpdated = nodeDef const prevNameCapitalized = _generateLabelFromName(NodeDef.getName(nodeDefPrev)) const prevLabel = NodeDef.getLabel(nodeDef, lang, NodeDef.NodeDefLabelTypes.label, false) if (StringUtils.isBlank(prevLabel) || prevNameCapitalized === prevLabel) { const nameCapitalized = _generateLabelFromName(name) - return NodeDef.assocLabel({ label: nameCapitalized, lang })(nodeDef) + nodeDefUpdated = NodeDef.assocLabel({ label: nameCapitalized, lang })(nodeDefUpdated) } - return nodeDef + if (NodeDef.isAutoIncrementalKey(nodeDefUpdated)) { + // re-generate default values; they depend on current node def name + nodeDefUpdated = _onUpdateAutoIncrementalKey({ survey, nodeDef, value: true }) + } + return nodeDefUpdated } -const _onUpdateAutoIncrementalKey = ({ nodeDef, value }) => { - const defaultValues = [] - if (value) { - const defaultValueExpression = NodeDefExpression.createExpression({ - expression: NodeDef.autoIncrementalKeyExpression, - }) - defaultValues.push(defaultValueExpression) - } +const _onUpdateAutoIncrementalKey = ({ survey, nodeDef, value }) => { + const nodeDefParent = Survey.getNodeDefParent(nodeDef)(survey) + const defaultValues = value ? NodeDef.createAutoIncrementalKeyDefaultValues({ nodeDef, nodeDefParent }) : [] return A.pipe( NodeDef.assocDefaultValues(defaultValues), NodeDef.assocDefaultValueEvaluatedOnlyOneTime(value)