2019-10-30 18:46:19 +01:00
|
|
|
import React, { useEffect, useReducer } from 'react';
|
|
|
|
import { useLocation, useParams } from 'react-router-dom';
|
2019-10-30 13:56:20 +01:00
|
|
|
import PropTypes from 'prop-types';
|
2019-11-07 16:52:12 +01:00
|
|
|
import { cloneDeep, get } from 'lodash';
|
2019-10-30 18:46:19 +01:00
|
|
|
import {
|
|
|
|
getQueryParameters,
|
|
|
|
request,
|
|
|
|
LoadingIndicatorPage,
|
|
|
|
} from 'strapi-helper-plugin';
|
|
|
|
import pluginId from '../../pluginId';
|
2019-10-30 14:47:12 +01:00
|
|
|
import EditViewDataManagerContext from '../../contexts/EditViewDataManager';
|
2019-10-30 18:46:19 +01:00
|
|
|
import createYupSchema from './utils/schema';
|
2019-11-07 14:06:50 +01:00
|
|
|
import createDefaultForm from './utils/createDefaultForm';
|
2019-11-07 16:52:12 +01:00
|
|
|
import getFilesToUpload from './utils/getFilesToUpload';
|
|
|
|
import cleanData from './utils/cleanData';
|
2019-10-30 13:56:20 +01:00
|
|
|
import init from './init';
|
|
|
|
import reducer, { initialState } from './reducer';
|
|
|
|
|
2019-10-30 18:46:19 +01:00
|
|
|
const getRequestUrl = path => `/${pluginId}/explorer/${path}`;
|
2019-10-30 14:47:12 +01:00
|
|
|
|
2019-10-30 19:06:40 +01:00
|
|
|
const EditViewDataManagerProvider = ({
|
|
|
|
allLayoutData,
|
|
|
|
children,
|
|
|
|
redirectToPreviousPage,
|
|
|
|
slug,
|
|
|
|
}) => {
|
2019-10-30 18:46:19 +01:00
|
|
|
const { id } = useParams();
|
|
|
|
// Retrieve the search
|
|
|
|
const { search } = useLocation();
|
2019-10-30 13:56:20 +01:00
|
|
|
const [reducerState, dispatch] = useReducer(reducer, initialState, init);
|
2019-10-30 18:46:19 +01:00
|
|
|
const {
|
|
|
|
formErrors,
|
|
|
|
initialData,
|
|
|
|
isLoading,
|
|
|
|
modifiedData,
|
|
|
|
shouldShowLoadingState,
|
|
|
|
} = reducerState.toJS();
|
|
|
|
|
|
|
|
const currentContentTypeLayout = get(allLayoutData, ['contentType'], {});
|
|
|
|
const abortController = new AbortController();
|
|
|
|
const { signal } = abortController;
|
|
|
|
const isCreatingEntry = id === 'create';
|
|
|
|
const source = getQueryParameters(search, 'source');
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
const fetchData = async () => {
|
|
|
|
try {
|
|
|
|
const data = await request(getRequestUrl(`${slug}/${id}`), {
|
|
|
|
method: 'GET',
|
|
|
|
params: { source },
|
|
|
|
signal,
|
|
|
|
});
|
|
|
|
|
|
|
|
dispatch({
|
|
|
|
type: 'GET_DATA_SUCCEEDED',
|
|
|
|
data,
|
|
|
|
});
|
|
|
|
} catch (err) {
|
|
|
|
if (err.code !== 20) {
|
|
|
|
strapi.notification.error(`${pluginId}.error.record.fetch`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2019-11-07 14:06:50 +01:00
|
|
|
const componentsDataStructure = Object.keys(
|
|
|
|
allLayoutData.components
|
|
|
|
).reduce((acc, current) => {
|
|
|
|
acc[current] = createDefaultForm(
|
|
|
|
get(allLayoutData, ['components', current, 'schema', 'attributes'], {}),
|
|
|
|
allLayoutData.components
|
|
|
|
);
|
|
|
|
return acc;
|
|
|
|
}, {});
|
|
|
|
const contentTypeDataStructure = createDefaultForm(
|
|
|
|
currentContentTypeLayout.schema.attributes,
|
|
|
|
allLayoutData.components
|
|
|
|
);
|
|
|
|
|
2019-10-30 18:46:19 +01:00
|
|
|
// Force state to be cleared when navigation from one entry to another
|
|
|
|
dispatch({ type: 'RESET_PROPS' });
|
2019-11-07 14:06:50 +01:00
|
|
|
dispatch({
|
|
|
|
type: 'SET_DEFAULT_DATA_STRUCTURES',
|
|
|
|
componentsDataStructure,
|
|
|
|
contentTypeDataStructure,
|
|
|
|
});
|
2019-10-30 18:46:19 +01:00
|
|
|
|
|
|
|
if (!isCreatingEntry) {
|
|
|
|
fetchData();
|
|
|
|
} else {
|
|
|
|
// Will create default form
|
2019-11-07 14:06:50 +01:00
|
|
|
dispatch({
|
|
|
|
type: 'SET_DEFAULT_MODIFIED_DATA_STRUCTURE',
|
|
|
|
contentTypeDataStructure,
|
|
|
|
});
|
2019-10-30 18:46:19 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
abortController.abort();
|
|
|
|
};
|
|
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
|
|
}, [id, slug, source]);
|
2019-10-30 13:56:20 +01:00
|
|
|
|
2019-11-04 14:23:46 +01:00
|
|
|
const addComponentToDynamicZone = (keys, componentUid) => {
|
|
|
|
dispatch({
|
|
|
|
type: 'ADD_COMPONENT_TO_DYNAMIC_ZONE',
|
|
|
|
keys: keys.split('.'),
|
|
|
|
componentUid,
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
const addNonRepeatableComponentToField = (keys, componentUid) => {
|
|
|
|
dispatch({
|
|
|
|
type: 'ADD_NON_REPEATABLE_COMPONENT_TO_FIELD',
|
|
|
|
keys: keys.split('.'),
|
|
|
|
componentUid,
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2019-10-30 15:06:38 +01:00
|
|
|
const addRelation = ({ target: { name, value } }) => {
|
|
|
|
dispatch({
|
|
|
|
type: 'ADD_RELATION',
|
|
|
|
keys: name.split('.'),
|
|
|
|
value,
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2019-11-07 14:06:50 +01:00
|
|
|
const addRepeatableComponentToField = (keys, componentUid) => {
|
2019-11-05 11:51:04 +01:00
|
|
|
dispatch({
|
|
|
|
type: 'ADD_REPEATABLE_COMPONENT_TO_FIELD',
|
|
|
|
keys: keys.split('.'),
|
2019-11-07 14:06:50 +01:00
|
|
|
componentUid,
|
2019-11-05 11:51:04 +01:00
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2019-10-30 15:06:38 +01:00
|
|
|
const handleChange = ({ target: { name, value, type } }) => {
|
|
|
|
let inputValue = value;
|
|
|
|
|
|
|
|
// Empty string is not a valid date,
|
|
|
|
// Set the date to null when it's empty
|
|
|
|
if (type === 'date' && value === '') {
|
|
|
|
inputValue = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
dispatch({
|
|
|
|
type: 'ON_CHANGE',
|
|
|
|
keys: name.split('.'),
|
|
|
|
value: inputValue,
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2019-10-30 18:46:19 +01:00
|
|
|
const handleSubmit = async e => {
|
2019-10-30 13:56:20 +01:00
|
|
|
e.preventDefault();
|
2019-10-30 18:46:19 +01:00
|
|
|
|
2019-11-08 13:04:17 +01:00
|
|
|
// Create yup schema
|
2019-10-30 18:46:19 +01:00
|
|
|
const schema = createYupSchema(currentContentTypeLayout, {
|
|
|
|
components: get(allLayoutData, 'components', {}),
|
2019-10-30 13:56:20 +01:00
|
|
|
});
|
2019-10-30 18:46:19 +01:00
|
|
|
|
|
|
|
try {
|
|
|
|
// Validate the form using yup
|
|
|
|
await schema.validate(modifiedData, { abortEarly: false });
|
|
|
|
// Set the loading state in the plugin header
|
2019-11-07 16:52:12 +01:00
|
|
|
const filesToUpload = getFilesToUpload(modifiedData);
|
2019-11-08 13:04:17 +01:00
|
|
|
// Remove keys that are not needed
|
|
|
|
// Clean relations
|
2019-11-07 16:52:12 +01:00
|
|
|
const cleanedData = cleanData(
|
|
|
|
cloneDeep(modifiedData),
|
|
|
|
currentContentTypeLayout,
|
|
|
|
allLayoutData.components
|
|
|
|
);
|
|
|
|
|
|
|
|
const formData = new FormData();
|
|
|
|
|
|
|
|
formData.append('data', JSON.stringify(cleanedData));
|
|
|
|
|
|
|
|
Object.keys(filesToUpload).forEach(key => {
|
|
|
|
const files = filesToUpload[key];
|
|
|
|
|
|
|
|
files.forEach(file => {
|
|
|
|
formData.append(`files.${key}`, file);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
// Change the request helper default headers so we can pass a FormData
|
|
|
|
const headers = {};
|
|
|
|
const method = isCreatingEntry ? 'POST' : 'PUT';
|
|
|
|
const endPoint = isCreatingEntry ? slug : `${slug}/${id}`;
|
|
|
|
|
|
|
|
try {
|
|
|
|
// Time to actually send the data
|
|
|
|
await request(
|
|
|
|
getRequestUrl(endPoint),
|
|
|
|
{
|
|
|
|
method,
|
|
|
|
headers,
|
|
|
|
params: { source },
|
|
|
|
body: formData,
|
|
|
|
signal,
|
|
|
|
},
|
|
|
|
false,
|
|
|
|
false
|
|
|
|
);
|
|
|
|
// emitEvent('didSaveEntry');
|
|
|
|
redirectToPreviousPage();
|
|
|
|
} catch (err) {
|
|
|
|
const error = get(
|
|
|
|
err,
|
|
|
|
['response', 'payload', 'message', '0', 'messages', '0', 'id'],
|
|
|
|
'SERVER ERROR'
|
|
|
|
);
|
|
|
|
|
|
|
|
setIsSubmitting(false);
|
|
|
|
// emitEvent('didNotSaveEntry', { error: err });
|
|
|
|
strapi.notification.error(error);
|
|
|
|
}
|
|
|
|
|
|
|
|
// setIsSubmitting();
|
2019-10-30 18:46:19 +01:00
|
|
|
} catch (err) {
|
|
|
|
const errors = get(err, 'inner', []).reduce((acc, curr) => {
|
|
|
|
acc[
|
|
|
|
curr.path
|
|
|
|
.split('[')
|
|
|
|
.join('.')
|
|
|
|
.split(']')
|
|
|
|
.join('')
|
|
|
|
] = { id: curr.message };
|
|
|
|
|
|
|
|
return acc;
|
|
|
|
}, {});
|
|
|
|
dispatch({
|
|
|
|
type: 'SUBMIT_ERRORS',
|
|
|
|
errors,
|
|
|
|
});
|
|
|
|
console.log({ errors });
|
|
|
|
}
|
2019-10-30 13:56:20 +01:00
|
|
|
};
|
|
|
|
|
2019-11-06 16:29:19 +01:00
|
|
|
const moveComponentDown = (dynamicZoneName, currentIndex) => {
|
|
|
|
dispatch({
|
|
|
|
type: 'MOVE_COMPONENT_DOWN',
|
|
|
|
dynamicZoneName,
|
|
|
|
currentIndex,
|
|
|
|
});
|
|
|
|
};
|
|
|
|
const moveComponentUp = (dynamicZoneName, currentIndex) => {
|
|
|
|
dispatch({
|
|
|
|
type: 'MOVE_COMPONENT_UP',
|
|
|
|
dynamicZoneName,
|
|
|
|
currentIndex,
|
|
|
|
});
|
|
|
|
};
|
2019-11-05 17:50:22 +01:00
|
|
|
const moveComponentField = (pathToComponent, dragIndex, hoverIndex) => {
|
|
|
|
dispatch({
|
|
|
|
type: 'MOVE_COMPONENT_FIELD',
|
|
|
|
pathToComponent,
|
|
|
|
dragIndex,
|
|
|
|
hoverIndex,
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2019-10-30 15:06:38 +01:00
|
|
|
const moveRelation = (dragIndex, overIndex, name) => {
|
|
|
|
dispatch({
|
|
|
|
type: 'MOVE_FIELD',
|
|
|
|
dragIndex,
|
|
|
|
overIndex,
|
|
|
|
keys: name.split('.'),
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
const onRemoveRelation = keys => {
|
|
|
|
dispatch({
|
|
|
|
type: 'REMOVE_RELATION',
|
|
|
|
keys,
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2019-11-04 14:23:46 +01:00
|
|
|
// REMOVE_COMPONENT_FROM_FIELD
|
2019-11-06 16:29:19 +01:00
|
|
|
const removeComponentFromDynamicZone = (dynamicZoneName, index) => {
|
|
|
|
dispatch({
|
|
|
|
type: 'REMOVE_COMPONENT_FROM_DYNAMIC_ZONE',
|
|
|
|
dynamicZoneName,
|
|
|
|
index,
|
|
|
|
});
|
|
|
|
};
|
2019-11-04 14:23:46 +01:00
|
|
|
const removeComponentFromField = (keys, componentUid) => {
|
|
|
|
dispatch({
|
|
|
|
type: 'REMOVE_COMPONENT_FROM_FIELD',
|
|
|
|
keys: keys.split('.'),
|
|
|
|
componentUid,
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2019-11-05 15:36:22 +01:00
|
|
|
const removeRepeatableField = (keys, componentUid) => {
|
|
|
|
dispatch({
|
|
|
|
type: 'REMOVE_REPEATABLE_FIELD',
|
|
|
|
keys: keys.split('.'),
|
|
|
|
componentUid,
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2019-10-30 19:06:40 +01:00
|
|
|
const setIsSubmitting = (value = true) => {
|
|
|
|
dispatch({ type: 'IS_SUBMITTING', value });
|
|
|
|
};
|
|
|
|
|
2019-10-30 18:46:19 +01:00
|
|
|
const showLoader = !isCreatingEntry && isLoading;
|
|
|
|
|
2019-10-30 13:56:20 +01:00
|
|
|
return (
|
|
|
|
<EditViewDataManagerContext.Provider
|
2019-10-30 15:06:38 +01:00
|
|
|
value={{
|
2019-11-04 14:23:46 +01:00
|
|
|
addComponentToDynamicZone,
|
|
|
|
addNonRepeatableComponentToField,
|
2019-10-30 15:06:38 +01:00
|
|
|
addRelation,
|
2019-11-05 11:51:04 +01:00
|
|
|
addRepeatableComponentToField,
|
2019-11-04 09:00:59 +01:00
|
|
|
allLayoutData,
|
2019-10-30 18:46:19 +01:00
|
|
|
formErrors,
|
2019-10-30 15:06:38 +01:00
|
|
|
initialData,
|
2019-10-30 18:46:19 +01:00
|
|
|
layout: currentContentTypeLayout,
|
2019-10-30 15:06:38 +01:00
|
|
|
modifiedData,
|
2019-11-06 16:29:19 +01:00
|
|
|
moveComponentDown,
|
2019-11-05 17:50:22 +01:00
|
|
|
moveComponentField,
|
2019-11-06 16:29:19 +01:00
|
|
|
moveComponentUp,
|
2019-10-30 15:06:38 +01:00
|
|
|
moveRelation,
|
|
|
|
onChange: handleChange,
|
|
|
|
onRemoveRelation,
|
2019-10-30 19:06:40 +01:00
|
|
|
redirectToPreviousPage,
|
2019-11-06 16:29:19 +01:00
|
|
|
removeComponentFromDynamicZone,
|
2019-11-04 14:23:46 +01:00
|
|
|
removeComponentFromField,
|
2019-11-05 15:36:22 +01:00
|
|
|
removeRepeatableField,
|
2019-10-30 19:06:40 +01:00
|
|
|
resetData: () => {
|
|
|
|
dispatch({
|
|
|
|
type: 'RESET_DATA',
|
|
|
|
});
|
|
|
|
},
|
|
|
|
setIsSubmitting,
|
2019-10-30 18:46:19 +01:00
|
|
|
shouldShowLoadingState,
|
2019-10-30 19:06:40 +01:00
|
|
|
slug,
|
|
|
|
source,
|
2019-10-30 15:06:38 +01:00
|
|
|
}}
|
2019-10-30 13:56:20 +01:00
|
|
|
>
|
2019-10-30 18:46:19 +01:00
|
|
|
{showLoader ? (
|
|
|
|
<LoadingIndicatorPage />
|
|
|
|
) : (
|
|
|
|
<form onSubmit={handleSubmit}>{children}</form>
|
|
|
|
)}
|
2019-10-30 13:56:20 +01:00
|
|
|
</EditViewDataManagerContext.Provider>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2019-10-30 19:06:40 +01:00
|
|
|
EditViewDataManagerProvider.defaultProps = {
|
|
|
|
redirectToPreviousPage: () => {},
|
|
|
|
};
|
|
|
|
|
2019-10-30 13:56:20 +01:00
|
|
|
EditViewDataManagerProvider.propTypes = {
|
2019-10-30 18:46:19 +01:00
|
|
|
allLayoutData: PropTypes.object.isRequired,
|
2019-10-30 13:56:20 +01:00
|
|
|
children: PropTypes.node.isRequired,
|
2019-10-30 19:06:40 +01:00
|
|
|
redirectToPreviousPage: PropTypes.func,
|
2019-10-30 18:46:19 +01:00
|
|
|
slug: PropTypes.string.isRequired,
|
2019-10-30 13:56:20 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
export default EditViewDataManagerProvider;
|