From 6b49fa778f55bef3aeaa4bb57ed6df5ca6a14bce Mon Sep 17 00:00:00 2001 From: soupette Date: Wed, 27 Nov 2019 17:29:41 +0100 Subject: [PATCH] Add code explanations and refacto drastically the code so it is more understandable --- .../admin/src/containers/FormModal/index.js | 293 +++++++++++------- 1 file changed, 184 insertions(+), 109 deletions(-) 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 5160664cd3..d0a3da5fa6 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 @@ -139,11 +139,17 @@ const FormModal = () => { const iconType = ['components', 'contentType'].includes(state.modalType) ? state.modalType : state.forTarget; - const isCreatingCT = state.modalType === 'contentType'; + const isCreatingContentType = state.modalType === 'contentType'; + const isCreatingComponent = state.modalType === 'component'; + const isCreatingAttribute = state.modalType === 'attribute'; + const isComponentAttribute = + state.attributeType === 'component' && isCreatingAttribute; + const isCreating = state.actionType === 'create'; const isCreatingComponentFromAView = get(modifiedData, 'createComponent', false) || isCreatingComponentWhileAddingAField; + const isInFirstComponentStep = state.step === '1'; const isOpen = !isEmpty(search); const isPickingAttribute = state.modalType === 'chooseAttribute'; @@ -164,26 +170,35 @@ const FormModal = () => { ? get(modifiedData, 'componentToCreate', {}) : modifiedData; - if (state.modalType === 'contentType') { + // Check form validity for content type + if (isCreatingContentType) { schema = forms.contentType.schema(Object.keys(contentTypes)); - } else if (state.modalType === 'component') { + + // Check form validity for component + // This is happening when the user click on the link from the left menu + } else if (isCreatingComponent) { schema = forms.component.schema( Object.keys(components), modifiedData.category || '' ); + + // Check for validity for creating a component + // This is happening when the user creates a component "on the fly" + // Since we temporarily store the component info in another object + // The data is set in the componentToCreate key } else if ( - // We could definitely simplify this condition - // TODO - state.modalType === 'attribute' && - state.attributeType === 'component' && + isComponentAttribute && isCreatingComponentFromAView && - state.step === '1' + isInFirstComponentStep ) { schema = forms.component.schema( Object.keys(components), get(modifiedData, 'componentToCreate.category', '') ); - } else if (state.modalType === 'attribute' && state.step !== '1') { + + // Check form validity for creating a 'common attribute' + // We need to make sure that it is independent from the step + } else if (isCreatingAttribute && !isInFirstComponentStep) { const type = state.attributeType === 'relation' ? 'relation' : modifiedData.type; @@ -195,6 +210,8 @@ const FormModal = () => { state.attributeName, initialData ); + + // This might be used for dynamic zones if not remove this part } else { // TODO validate component schema console.log('Will do something'); @@ -228,6 +245,9 @@ const FormModal = () => { name === 'repeatable') ) { val = value === 'false' ? false : true; + + // The boolean default accepts 3 different values + // This check has been added to allow a reset to null for the bool } else if (type === 'radio' && name === 'default') { if (value === 'false') { val = false; @@ -236,7 +256,8 @@ const FormModal = () => { } else { val = null; } - // val = value === 'false' ? false : true; + + // We store an array for the enum } else if (name === 'enum') { val = value.split('\n'); } else { @@ -245,14 +266,17 @@ const FormModal = () => { const clonedErrors = Object.assign({}, formErrors); + // Reset min error when modifying the max if (name === 'max') { delete clonedErrors.min; } + // Same here if (name === 'maxLength') { delete clonedErrors.minLength; } + // Since the onBlur is deactivated we remove the errors directly when changing an input delete clonedErrors[name]; dispatch({ @@ -274,6 +298,7 @@ const FormModal = () => { await checkFormValidity(); const targetUid = state.forTarget === 'components' ? state.targetUid : uid; + // This should be improved const createNextSearch = searchUid => { return `modalType=chooseAttribute&forTarget=${ state.forTarget @@ -281,106 +306,15 @@ const FormModal = () => { modifiedData.name}`; }; - if (state.modalType === 'contentType') { + if (isCreatingContentType) { // Create the content type schema createSchema(modifiedData, state.modalType, uid); - const nextSlug = isCreatingCT - ? 'content-types' - : 'component-categories'; + push({ - pathname: `/plugins/${pluginId}/${nextSlug}/${uid}`, + pathname: `/plugins/${pluginId}/content-types/${uid}`, search: createNextSearch(targetUid), }); - } else if ( - state.modalType === 'attribute' && - !isCreatingComponentFromAView && - state.attributeType !== 'component' - ) { - addAttribute( - modifiedData, - state.forTarget, - state.targetUid, - state.actionType === 'edit', - initialData - ); - // Adding a component to a dynamiczone is not the same logic as creating a simple field - // so the search is different - const nextSearch = - state.attributeType === 'dynamiczone' - ? '' - : createNextSearch(targetUid); - - push({ search: nextSearch }); - } else if ( - state.modalType === 'attribute' && - !isCreatingComponentFromAView && - state.attributeType === 'component' - ) { - if (state.step === '1') { - push({ - search: `modalType=attribute&actionType=${state.actionType}&settingType=base&forTarget=${state.forTarget}&targetUid=${state.targetUid}&attributeType=component&headerDisplayName=${state.headerDisplayName}&step=2`, - }); - - dispatch({ - type: 'RESET_PROPS_AND_SET_FORM_FOR_ADDING_AN_EXISTING_COMPO', - }); - - return; - } else { - addAttribute( - modifiedData, - state.forTarget, - state.targetUid, - state.actionType === 'edit', - initialData, - true - ); - // Adding a component to a dynamiczone is not the same logic as creating a simple field - // so the search is different - const nextSearch = - state.attributeType === 'dynamiczone' - ? '' - : createNextSearch(targetUid); - - push({ search: nextSearch }); - } - } else if ( - state.modalType === 'attribute' && - isCreatingComponentFromAView - ) { - if (state.step === '1') { - push({ - search: `modalType=attribute&actionType=${state.actionType}&settingType=base&forTarget=${state.forTarget}&targetUid=${state.targetUid}&attributeType=component&headerDisplayName=${state.headerDisplayName}&step=2`, - }); - - dispatch({ - type: 'RESET_PROPS_AND_SAVE_CURRENT_DATA', - }); - - return; - } else { - const { category, type, ...rest } = componentToCreate; - const componentUid = createComponentUid( - componentToCreate.name, - category - ); - // Create the component first and add it to the components data - createSchema( - rest, - type, - componentUid, - category, - isCreatingComponentFromAView - ); - // Add the field to the schema - addAttribute(modifiedData, state.forTarget, state.targetUid, false); - // add the component to main modified data component object - - dispatch({ type: 'RESET_PROPS' }); - push({ search: '' }); - return; - } - } else if (state.modalType === 'component') { + } else if (isCreatingComponent) { // Create the component schema const componentUid = createComponentUid( modifiedData.name, @@ -393,9 +327,125 @@ const FormModal = () => { search: createNextSearch(componentUid), pathname: `/plugins/${pluginId}/component-categories/${category}/${componentUid}`, }); + + // Add/edit a field + } else if (isCreatingAttribute && !isCreatingComponentFromAView) { + // Normal fields like boolean relations + if (!isComponentAttribute) { + addAttribute( + modifiedData, + state.forTarget, + state.targetUid, + state.actionType === 'edit', + initialData + ); + // Adding a component to a dynamiczone is not the same logic as creating a simple field + // so the search is different + const nextSearch = + state.attributeType === 'dynamiczone' + ? '' + : createNextSearch(targetUid); + + push({ search: nextSearch }); + + // Adding an existing component + } else { + if (isInFirstComponentStep) { + // Navigate the user to step 2 + push({ + search: `modalType=attribute&actionType=${state.actionType}&settingType=base&forTarget=${state.forTarget}&targetUid=${state.targetUid}&attributeType=component&headerDisplayName=${state.headerDisplayName}&step=2`, + }); + + // Clear the reducer and prepare the modified data + // This way we don't have to add some logic to re-run the useEffect + // The first step is either needed to create a component or just to navigate + // To the modal for adding a "common field" + dispatch({ + type: 'RESET_PROPS_AND_SET_FORM_FOR_ADDING_AN_EXISTING_COMPO', + }); + + // We don't want all the props to be reset + return; + + // Here we are in step 2 + } else { + addAttribute( + modifiedData, + state.forTarget, + state.targetUid, + // This change the dispatched type + // either 'EDIT_ATTRIBUTE' or 'ADD_ATTRIBUTE' in the DataManagerProvider + state.actionType === 'edit', + // This is for the edit part + initialData, + // Passing true will add the component to the components object + // This way we can add fields to the added component (if it wasn't there already) + true + ); + + push({ search: createNextSearch(targetUid) }); + + // We don't need to end the loop here we want the reducer to be reinitialised + } + } + // Logic for creating a component without clicking on the link in + // the left menu + // We need to separate the logic otherwise the component would be created + // even though the user didn't set any field + // The use case is happening when closing the modal at step 2 without any submission + } else if (isCreatingAttribute && isCreatingComponentFromAView) { + if (isInFirstComponentStep) { + // Here the search could be refactored since it is the same as the case from above + push({ + search: `modalType=attribute&actionType=${state.actionType}&settingType=base&forTarget=${state.forTarget}&targetUid=${state.targetUid}&attributeType=component&headerDisplayName=${state.headerDisplayName}&step=2`, + }); + + // Here we clear the reducer state but we also keep the created component + // If we were to create the component before + dispatch({ + type: 'RESET_PROPS_AND_SAVE_CURRENT_DATA', + }); + + // Terminate because we don't want the reducer to be entirely reset + return; + + // Step 2 + } else { + // We are destructuring because the modifiedData object doesn't have the appropriate format to create a field + const { category, type, ...rest } = componentToCreate; + // Create a the component temp UID + // This could be refactored but I think it's more understandable to separate the logic + const componentUid = createComponentUid( + componentToCreate.name, + category + ); + // Create the component first and add it to the components data + createSchema( + // Component data + rest, + // Type will always be component + // It will dispatch the CREATE_COMPONENT_SCHEMA action + // So the component will be added in the main components object + // This might not be needed if we don't allow navigation between entries while editing + type, + componentUid, + category, + // This will add the created component in the datamanager modifiedData components key + // Like explained above we will be able to modify the created component structure + isCreatingComponentFromAView + ); + // Add the field to the schema + addAttribute(modifiedData, state.forTarget, state.targetUid, false); + + // TODO temporary + dispatch({ type: 'RESET_PROPS' }); + push({ search: '' }); + return; + } } else { - console.log('Do somethign later'); + console.log('handle submit unknown case'); } + dispatch({ type: 'RESET_PROPS', }); @@ -425,13 +475,13 @@ const FormModal = () => { }; const shouldDisableAdvancedTab = () => { return ( - state.modalType === 'attribute' && + isCreatingAttribute && state.attributeType === 'component' && get(modifiedData, ['createComponent'], null) === false ); }; - // Display data + // Display data for the attributes picker modal const displayedAttributes = getAttributes(state.forTarget); // Styles @@ -448,6 +498,7 @@ const FormModal = () => { > { {NAVLINKS.map((link, index) => { return ( { return (
{row.map(input => { + // The divider type is used mainly the advanced tab + // It is the one responsible for displaying the settings label if (input.type === 'divider') { return (
{ ); } + // The spacer type is used mainly to aligne the icon picker if (input.type === 'spacer') { return (
@@ -585,11 +641,17 @@ const FormModal = () => { ); } + // Retrieve the error for a specific input const errorId = get( formErrors, [ ...input.name .split('.') + // The filter here is used when creating a component + // in the component step 1 modal + // Since the component info is stored in the + // componentToCreate object we can access the error + // By removing the key .filter(key => key !== 'componentToCreate'), 'id', ], @@ -604,11 +666,17 @@ const FormModal = () => { let value; + // Condition for the boolean default value + // The radio input doesn't accept false, true or null as value + // So we pass them as string + // This way the data stays accurate and we don't have to operate + // any data mutation if ( input.name === 'default' && state.attributeType === 'boolean' ) { value = toString(retrievedValue); + // Same here for the enum } else if ( input.name === 'enum' && Array.isArray(retrievedValue) @@ -618,6 +686,9 @@ const FormModal = () => { value = retrievedValue; } + // The addon input is not present in buffet so we are used the all lib + // for the moment that's why we don't want them be passed to buffet + // like the other created inputs if (input.type === 'addon') { return ( { customInputs={{ componentIconPicker: ComponentIconPicker, componentSelect: WrapperSelect, - // creatableSelect: CreatableSelect, creatableSelect: WrapperSelect, customCheckboxWithChildren: CustomCheckbox, booleanBox: BooleanBox, }} isCreating={isCreating} + // Props for the componentSelect isCreatingComponentWhileAddingAField={ isCreatingComponentWhileAddingAField } + // Props for the componentSelect + // Since the component is created after adding it to a type + // its name and category can't be retrieved from the data manager componentCategoryNeededForAddingAfieldWhileCreatingAComponent={get( componentToCreate, 'category', null )} + // Props for the componentSelect same explanation componentNameNeededForAddingAfieldWhileCreatingAComponent={get( componentToCreate, 'name',