diff --git a/packages/strapi-plugin-content-manager/admin/src/components/SelectWrapper/index.js b/packages/strapi-plugin-content-manager/admin/src/components/SelectWrapper/index.js
index 21b0529c69..3ec0eeb3e6 100644
--- a/packages/strapi-plugin-content-manager/admin/src/components/SelectWrapper/index.js
+++ b/packages/strapi-plugin-content-manager/admin/src/components/SelectWrapper/index.js
@@ -3,7 +3,7 @@ import React, { useState, useEffect, useRef, memo } from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import { Link, useLocation } from 'react-router-dom';
-import { get, isArray, isEmpty } from 'lodash';
+import { get, cloneDeep, isArray, isEmpty } from 'lodash';
import { request } from 'strapi-helper-plugin';
import pluginId from '../../pluginId';
import useDataManager from '../../hooks/useDataManager';
diff --git a/packages/strapi-plugin-content-type-builder/admin/src/containers/DataManagerProvider/index.js b/packages/strapi-plugin-content-type-builder/admin/src/containers/DataManagerProvider/index.js
index 602ef963ca..d33b8f2b01 100644
--- a/packages/strapi-plugin-content-type-builder/admin/src/containers/DataManagerProvider/index.js
+++ b/packages/strapi-plugin-content-type-builder/admin/src/containers/DataManagerProvider/index.js
@@ -138,7 +138,7 @@ const DataManagerProvider = ({ children }) => {
return ;
}
- console.log({ modifiedData });
+ console.log({ modifiedData, contentTypes });
return (
{
return fromJS(rest);
}
)
- .updateIn(['modifiedData', 'schema', 'attributes'], obj => {
- const type = get(rest, 'type');
- const target = get(rest, 'target', null);
- const nature = get(rest, 'nature', null);
- const currentUid = state.getIn(['modifiedData', 'uid']);
+ .updateIn(
+ ['modifiedData', ...pathToDataToEdit, 'schema', 'attributes'],
+ obj => {
+ const type = get(rest, 'type', 'relation');
+ const target = get(rest, 'target', null);
+ const nature = get(rest, 'nature', null);
+ const currentUid = state.getIn([
+ 'modifiedData',
+ ...pathToDataToEdit,
+ 'uid',
+ ]);
- // When the user in creating a relation with the same content type we need to create another attribute
- // that is the opposite of the created one
- if (
- type === 'relation' &&
- nature !== 'oneWay' &&
- nature !== 'manyWay' &&
- target === currentUid
- ) {
- const oppositeAttribute = {
- nature,
- target,
- type,
- unique: rest.unique,
- required: rest.required,
- dominant: nature === 'manyToMany' ? !rest.dominant : null,
- targetAttribute: name,
- };
+ // When the user in creating a relation with the same content type we need to create another attribute
+ // that is the opposite of the created one
+ if (
+ type === 'relation' &&
+ nature !== 'oneWay' &&
+ nature !== 'manyWay' &&
+ target === currentUid
+ ) {
+ const oppositeAttribute = {
+ nature,
+ target,
+ type,
+ unique: rest.unique,
+ required: rest.required,
+ dominant: nature === 'manyToMany' ? !rest.dominant : null,
+ targetAttribute: name,
+ };
- return obj.update(rest.targetAttribute, () => {
- return fromJS(oppositeAttribute);
- });
+ return obj.update(rest.targetAttribute, () => {
+ return fromJS(oppositeAttribute);
+ });
+ }
+
+ return obj;
}
-
- return obj;
- });
+ );
}
case 'GET_DATA_SUCCEEDED':
return state
diff --git a/packages/strapi-plugin-content-type-builder/admin/src/containers/FormModal/index.js b/packages/strapi-plugin-content-type-builder/admin/src/containers/FormModal/index.js
index 853fa4ce96..990bdf9778 100644
--- a/packages/strapi-plugin-content-type-builder/admin/src/containers/FormModal/index.js
+++ b/packages/strapi-plugin-content-type-builder/admin/src/containers/FormModal/index.js
@@ -14,7 +14,7 @@ import {
import { Inputs } from '@buffetjs/custom';
import { useHistory, useLocation } from 'react-router-dom';
import { FormattedMessage } from 'react-intl';
-import { get, isEmpty, toString, upperFirst } from 'lodash';
+import { get, has, isEmpty, set, toString, upperFirst } from 'lodash';
import pluginId from '../../pluginId';
import useQuery from '../../hooks/useQuery';
import useDataManager from '../../hooks/useDataManager';
@@ -44,6 +44,7 @@ const FormModal = () => {
targetUid: null,
attributeType: null,
headerDisplayName: null,
+ pathToSchema: [],
};
const [state, setState] = useState(initialStateData);
const [reducerState, dispatch] = useReducer(reducer, initialState, init);
@@ -59,34 +60,60 @@ const FormModal = () => {
modifiedData: allDataSchema,
sortedContentTypesList,
} = useDataManager();
- const { formErrors, modifiedData } = reducerState.toJS();
+ const { formErrors, initialData, modifiedData } = reducerState.toJS();
useEffect(() => {
if (!isEmpty(search)) {
// Return 'null' if there isn't any attributeType search params
- //TODO need targetUID
- // TODO change target by targetUID & add displayName instead of target
const attributeType = query.get('attributeType');
const modalType = query.get('modalType');
const actionType = query.get('actionType');
+ const attributeName = query.get('attributeName');
+ const settingType = query.get('settingType');
+ const forTarget = query.get('forTarget');
+ const targetUid = query.get('targetUid');
+ const headerDisplayName = query.get('headerDisplayName');
+ const pathToSchema =
+ forTarget === 'contentType' || forTarget === 'component'
+ ? [forTarget]
+ : [forTarget, targetUid];
setState({
- attributeName: query.get('attributeName'),
+ attributeName,
actionType,
modalType,
- settingType: query.get('settingType'),
- forTarget: query.get('forTarget'),
- targetUid: query.get('targetUid'),
- headerDisplayName: query.get('headerDisplayName'),
+ settingType,
+ forTarget,
+ targetUid,
+ headerDisplayName,
attributeType,
+ pathToSchema,
});
// Set the predefined data structure to create an attribute
if (
attributeType &&
attributeType !== 'null' &&
+ // This condition is added to prevent the reducer state to be cleared when navigating from the base tab to tha advanced one
state.modalType !== 'attribute'
) {
+ const attributeToEditNotFormatted = get(
+ allDataSchema,
+ [...pathToSchema, 'schema', 'attributes', attributeName],
+ {}
+ );
+ const attributeToEdit = {
+ ...attributeToEditNotFormatted,
+ name: attributeName,
+ };
+
+ if (
+ attributeType === 'relation' &&
+ !has(attributeToEdit, ['targetAttribute'])
+ ) {
+ set(attributeToEdit, ['targetAttribute'], '-');
+ }
+
dispatch({
type: 'SET_ATTRIBUTE_DATA_SCHEMA',
attributeType,
@@ -96,6 +123,8 @@ const FormModal = () => {
'error'
),
targetUid: get(sortedContentTypesList, ['0', 'uid'], 'error'),
+ isEditing: actionType === 'edit',
+ modifiedDataToSetForEditing: attributeToEdit,
});
}
}
@@ -122,8 +151,6 @@ const FormModal = () => {
headerId = null;
}
- console.log({ allDataSchema });
-
const checkFormValidity = async () => {
let schema;
@@ -134,15 +161,16 @@ const FormModal = () => {
// && state.forTarget !== 'components' &&
// state.forTarget !== 'component'
) {
- const pathToSchemaAttributes =
- state.forTarget === 'contentType' || state.forTarget === 'component'
- ? [state.forTarget]
- : [state.forTarget, state.targetUid];
+ const type =
+ state.attributeType === 'relation' ? 'relation' : modifiedData.type;
schema = forms[state.modalType].schema(
- get(allDataSchema, pathToSchemaAttributes, {}),
- modifiedData.type,
- modifiedData
+ get(allDataSchema, state.pathToSchema, {}),
+ type,
+ modifiedData,
+ state.actionType === 'edit',
+ state.attributeName,
+ initialData
);
} else {
// TODO validate component schema
diff --git a/packages/strapi-plugin-content-type-builder/admin/src/containers/FormModal/reducer.js b/packages/strapi-plugin-content-type-builder/admin/src/containers/FormModal/reducer.js
index d54201595b..7ada2a6551 100644
--- a/packages/strapi-plugin-content-type-builder/admin/src/containers/FormModal/reducer.js
+++ b/packages/strapi-plugin-content-type-builder/admin/src/containers/FormModal/reducer.js
@@ -4,6 +4,7 @@ import pluralize from 'pluralize';
const initialState = fromJS({
formErrors: {},
modifiedData: {},
+ initialData: {},
});
export const shouldPluralizeTargetAttribute = nature =>
@@ -75,7 +76,20 @@ const reducer = (state, action) => {
case 'RESET_PROPS':
return initialState;
case 'SET_ATTRIBUTE_DATA_SCHEMA': {
- const { attributeType, nameToSetForRelation, targetUid } = action;
+ const {
+ attributeType,
+ isEditing,
+ modifiedDataToSetForEditing,
+ nameToSetForRelation,
+ targetUid,
+ } = action;
+
+ if (isEditing) {
+ return state
+ .update('modifiedData', () => fromJS(modifiedDataToSetForEditing))
+ .update('initialData', () => fromJS(modifiedDataToSetForEditing));
+ }
+
let dataToSet;
if (attributeType === 'text') {
diff --git a/packages/strapi-plugin-content-type-builder/admin/src/containers/FormModal/utils/forms.js b/packages/strapi-plugin-content-type-builder/admin/src/containers/FormModal/utils/forms.js
index de37fb39c1..9aa3cdf8dc 100644
--- a/packages/strapi-plugin-content-type-builder/admin/src/containers/FormModal/utils/forms.js
+++ b/packages/strapi-plugin-content-type-builder/admin/src/containers/FormModal/utils/forms.js
@@ -17,7 +17,7 @@ yup.addMethod(yup.mixed, 'defined', function() {
yup.addMethod(yup.string, 'unique', function(
message,
- allReadyTakenValues,
+ alreadyTakenAttributes,
validator
) {
return this.test('unique', message, function(string) {
@@ -25,7 +25,7 @@ yup.addMethod(yup.string, 'unique', function(
return false;
}
- return !allReadyTakenValues.includes(
+ return !alreadyTakenAttributes.includes(
typeof validator === 'function' ? validator(string) : string
);
});
@@ -46,18 +46,42 @@ const ATTRIBUTES_THAT_DONT_HAVE_MIN_MAX_SETTINGS = [
const forms = {
attribute: {
- schema(currentSchema, attributeType, dataToValidate) {
- const allreadyTakenAttributes = Object.keys(
+ schema(
+ currentSchema,
+ attributeType,
+ dataToValidate,
+ isEditing,
+ attributeToEditName,
+ initialData
+ ) {
+ const alreadyTakenAttributes = Object.keys(
get(currentSchema, ['schema', 'attributes'], {})
- );
- const targetAttributeAllreadyTakenValue = dataToValidate.name
- ? [...allreadyTakenAttributes, dataToValidate.name]
- : allreadyTakenAttributes;
+ ).filter(attribute => {
+ if (isEditing) {
+ return attribute !== attributeToEditName;
+ }
+
+ return true;
+ });
+
+ let targetAttributeAlreadyTakenValue = dataToValidate.name
+ ? [...alreadyTakenAttributes, dataToValidate.name]
+ : alreadyTakenAttributes;
+
+ if (
+ isEditing &&
+ attributeType === 'relation' &&
+ dataToValidate.target === currentSchema.uid
+ ) {
+ targetAttributeAlreadyTakenValue = targetAttributeAlreadyTakenValue.filter(
+ attribute => attribute !== initialData.targetAttribute
+ );
+ }
const commonShape = {
name: yup
.string()
- .unique(errorsTrads.unique, allreadyTakenAttributes)
+ .unique(errorsTrads.unique, alreadyTakenAttributes)
.required(errorsTrads.required),
type: yup.string().required(errorsTrads.required),
default: yup.string().nullable(),
@@ -144,13 +168,12 @@ const forms = {
return yup.object().shape({
name: yup
.string()
- .unique(errorsTrads.unique, allreadyTakenAttributes)
+ .unique(errorsTrads.unique, alreadyTakenAttributes)
.required(errorsTrads.required),
targetAttribute: yup
.string()
- .unique(errorsTrads.unique, targetAttributeAllreadyTakenValue)
+ .unique(errorsTrads.unique, targetAttributeAlreadyTakenValue)
.required(errorsTrads.required),
- type: yup.string().required(errorsTrads.required),
target: yup.string().required(errorsTrads.required),
nature: yup.string().required(),
dominant: yup.boolean().nullable(),
@@ -586,11 +609,11 @@ const forms = {
},
},
contentType: {
- schema(allReadyTakenValues) {
+ schema(alreadyTakenAttributes) {
return yup.object().shape({
name: yup
.string()
- .unique(errorsTrads.unique, allReadyTakenValues, createUid)
+ .unique(errorsTrads.unique, alreadyTakenAttributes, createUid)
.required(errorsTrads.required),
collectionName: yup.string(),
});