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 PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import { Link, useLocation } from 'react-router-dom'; 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 { request } from 'strapi-helper-plugin';
import pluginId from '../../pluginId'; import pluginId from '../../pluginId';
import useDataManager from '../../hooks/useDataManager'; import useDataManager from '../../hooks/useDataManager';

View File

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

View File

@ -28,11 +28,17 @@ const reducer = (state, action) => {
return fromJS(rest); return fromJS(rest);
} }
) )
.updateIn(['modifiedData', 'schema', 'attributes'], obj => { .updateIn(
const type = get(rest, 'type'); ['modifiedData', ...pathToDataToEdit, 'schema', 'attributes'],
obj => {
const type = get(rest, 'type', 'relation');
const target = get(rest, 'target', null); const target = get(rest, 'target', null);
const nature = get(rest, 'nature', null); const nature = get(rest, 'nature', null);
const currentUid = state.getIn(['modifiedData', 'uid']); 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 // 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 // that is the opposite of the created one
@ -58,7 +64,8 @@ const reducer = (state, action) => {
} }
return obj; return obj;
}); }
);
} }
case 'GET_DATA_SUCCEEDED': case 'GET_DATA_SUCCEEDED':
return state return state

View File

@ -14,7 +14,7 @@ import {
import { Inputs } from '@buffetjs/custom'; import { Inputs } from '@buffetjs/custom';
import { useHistory, useLocation } from 'react-router-dom'; import { useHistory, useLocation } from 'react-router-dom';
import { FormattedMessage } from 'react-intl'; 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 pluginId from '../../pluginId';
import useQuery from '../../hooks/useQuery'; import useQuery from '../../hooks/useQuery';
import useDataManager from '../../hooks/useDataManager'; import useDataManager from '../../hooks/useDataManager';
@ -44,6 +44,7 @@ const FormModal = () => {
targetUid: null, targetUid: null,
attributeType: null, attributeType: null,
headerDisplayName: null, headerDisplayName: null,
pathToSchema: [],
}; };
const [state, setState] = useState(initialStateData); const [state, setState] = useState(initialStateData);
const [reducerState, dispatch] = useReducer(reducer, initialState, init); const [reducerState, dispatch] = useReducer(reducer, initialState, init);
@ -59,34 +60,60 @@ const FormModal = () => {
modifiedData: allDataSchema, modifiedData: allDataSchema,
sortedContentTypesList, sortedContentTypesList,
} = useDataManager(); } = useDataManager();
const { formErrors, modifiedData } = reducerState.toJS(); const { formErrors, initialData, modifiedData } = reducerState.toJS();
useEffect(() => { useEffect(() => {
if (!isEmpty(search)) { if (!isEmpty(search)) {
// Return 'null' if there isn't any attributeType search params // 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 attributeType = query.get('attributeType');
const modalType = query.get('modalType'); const modalType = query.get('modalType');
const actionType = query.get('actionType'); 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({ setState({
attributeName: query.get('attributeName'), attributeName,
actionType, actionType,
modalType, modalType,
settingType: query.get('settingType'), settingType,
forTarget: query.get('forTarget'), forTarget,
targetUid: query.get('targetUid'), targetUid,
headerDisplayName: query.get('headerDisplayName'), headerDisplayName,
attributeType, attributeType,
pathToSchema,
}); });
// Set the predefined data structure to create an attribute // Set the predefined data structure to create an attribute
if ( if (
attributeType && attributeType &&
attributeType !== 'null' && 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' 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({ dispatch({
type: 'SET_ATTRIBUTE_DATA_SCHEMA', type: 'SET_ATTRIBUTE_DATA_SCHEMA',
attributeType, attributeType,
@ -96,6 +123,8 @@ const FormModal = () => {
'error' 'error'
), ),
targetUid: get(sortedContentTypesList, ['0', 'uid'], 'error'), targetUid: get(sortedContentTypesList, ['0', 'uid'], 'error'),
isEditing: actionType === 'edit',
modifiedDataToSetForEditing: attributeToEdit,
}); });
} }
} }
@ -122,8 +151,6 @@ const FormModal = () => {
headerId = null; headerId = null;
} }
console.log({ allDataSchema });
const checkFormValidity = async () => { const checkFormValidity = async () => {
let schema; let schema;
@ -134,15 +161,16 @@ const FormModal = () => {
// && state.forTarget !== 'components' && // && state.forTarget !== 'components' &&
// state.forTarget !== 'component' // state.forTarget !== 'component'
) { ) {
const pathToSchemaAttributes = const type =
state.forTarget === 'contentType' || state.forTarget === 'component' state.attributeType === 'relation' ? 'relation' : modifiedData.type;
? [state.forTarget]
: [state.forTarget, state.targetUid];
schema = forms[state.modalType].schema( schema = forms[state.modalType].schema(
get(allDataSchema, pathToSchemaAttributes, {}), get(allDataSchema, state.pathToSchema, {}),
modifiedData.type, type,
modifiedData modifiedData,
state.actionType === 'edit',
state.attributeName,
initialData
); );
} else { } else {
// TODO validate component schema // TODO validate component schema

View File

@ -4,6 +4,7 @@ import pluralize from 'pluralize';
const initialState = fromJS({ const initialState = fromJS({
formErrors: {}, formErrors: {},
modifiedData: {}, modifiedData: {},
initialData: {},
}); });
export const shouldPluralizeTargetAttribute = nature => export const shouldPluralizeTargetAttribute = nature =>
@ -75,7 +76,20 @@ const reducer = (state, action) => {
case 'RESET_PROPS': case 'RESET_PROPS':
return initialState; return initialState;
case 'SET_ATTRIBUTE_DATA_SCHEMA': { 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; let dataToSet;
if (attributeType === 'text') { if (attributeType === 'text') {

View File

@ -17,7 +17,7 @@ yup.addMethod(yup.mixed, 'defined', function() {
yup.addMethod(yup.string, 'unique', function( yup.addMethod(yup.string, 'unique', function(
message, message,
allReadyTakenValues, alreadyTakenAttributes,
validator validator
) { ) {
return this.test('unique', message, function(string) { return this.test('unique', message, function(string) {
@ -25,7 +25,7 @@ yup.addMethod(yup.string, 'unique', function(
return false; return false;
} }
return !allReadyTakenValues.includes( return !alreadyTakenAttributes.includes(
typeof validator === 'function' ? validator(string) : string typeof validator === 'function' ? validator(string) : string
); );
}); });
@ -46,18 +46,42 @@ const ATTRIBUTES_THAT_DONT_HAVE_MIN_MAX_SETTINGS = [
const forms = { const forms = {
attribute: { attribute: {
schema(currentSchema, attributeType, dataToValidate) { schema(
const allreadyTakenAttributes = Object.keys( currentSchema,
attributeType,
dataToValidate,
isEditing,
attributeToEditName,
initialData
) {
const alreadyTakenAttributes = Object.keys(
get(currentSchema, ['schema', 'attributes'], {}) get(currentSchema, ['schema', 'attributes'], {})
).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 targetAttributeAllreadyTakenValue = dataToValidate.name }
? [...allreadyTakenAttributes, dataToValidate.name]
: allreadyTakenAttributes;
const commonShape = { const commonShape = {
name: yup name: yup
.string() .string()
.unique(errorsTrads.unique, allreadyTakenAttributes) .unique(errorsTrads.unique, alreadyTakenAttributes)
.required(errorsTrads.required), .required(errorsTrads.required),
type: yup.string().required(errorsTrads.required), type: yup.string().required(errorsTrads.required),
default: yup.string().nullable(), default: yup.string().nullable(),
@ -144,13 +168,12 @@ const forms = {
return yup.object().shape({ return yup.object().shape({
name: yup name: yup
.string() .string()
.unique(errorsTrads.unique, allreadyTakenAttributes) .unique(errorsTrads.unique, alreadyTakenAttributes)
.required(errorsTrads.required), .required(errorsTrads.required),
targetAttribute: yup targetAttribute: yup
.string() .string()
.unique(errorsTrads.unique, targetAttributeAllreadyTakenValue) .unique(errorsTrads.unique, targetAttributeAlreadyTakenValue)
.required(errorsTrads.required), .required(errorsTrads.required),
type: yup.string().required(errorsTrads.required),
target: yup.string().required(errorsTrads.required), target: yup.string().required(errorsTrads.required),
nature: yup.string().required(), nature: yup.string().required(),
dominant: yup.boolean().nullable(), dominant: yup.boolean().nullable(),
@ -586,11 +609,11 @@ const forms = {
}, },
}, },
contentType: { contentType: {
schema(allReadyTakenValues) { schema(alreadyTakenAttributes) {
return yup.object().shape({ return yup.object().shape({
name: yup name: yup
.string() .string()
.unique(errorsTrads.unique, allReadyTakenValues, createUid) .unique(errorsTrads.unique, alreadyTakenAttributes, createUid)
.required(errorsTrads.required), .required(errorsTrads.required),
collectionName: yup.string(), collectionName: yup.string(),
}); });