Add dynamic to edit a field

This commit is contained in:
soupette 2019-11-22 11:18:49 +01:00
parent c29212aa2c
commit d9ca6451d7
6 changed files with 135 additions and 63 deletions

View File

@ -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';

View File

@ -138,7 +138,7 @@ const DataManagerProvider = ({ children }) => {
return <Redirect to={`/plugins/${pluginId}/content-types/${firstCTUid}`} />;
}
console.log({ modifiedData });
console.log({ modifiedData, contentTypes });
return (
<DataManagerContext.Provider

View File

@ -28,37 +28,44 @@ const reducer = (state, action) => {
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

View File

@ -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

View File

@ -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') {

View File

@ -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(),
});