888 lines
29 KiB
JavaScript
Raw Normal View History

import React, {
memo,
useCallback,
useMemo,
useEffect,
// useState,
useReducer,
useRef,
} from 'react';
2019-07-11 11:35:18 +02:00
import PropTypes from 'prop-types';
import {
// cloneDeep,
get,
// isEqual,
} from 'lodash';
import { useHistory, useLocation, useParams } from 'react-router-dom';
2019-07-11 11:35:18 +02:00
import {
BackHeader,
2019-07-11 11:35:18 +02:00
getQueryParameters,
// LoadingIndicatorPage,
2019-07-11 16:53:00 +02:00
LiLink,
2019-07-11 11:35:18 +02:00
PluginHeader,
// PopUpWarning,
// getYupInnerErrors,
// request,
2019-07-11 11:35:18 +02:00
templateObject,
} from 'strapi-helper-plugin';
import pluginId from '../../pluginId';
2019-07-17 17:39:43 +02:00
import { EditViewProvider } from '../../contexts/EditView';
2019-07-11 11:35:18 +02:00
import Container from '../../components/Container';
import DynamicZone from '../../components/DynamicZone';
import FormWrapper from '../../components/FormWrapper';
// import ComponentField from '../../components/ComponentField';
// import Inputs from '../../components/Inputs';
2019-07-17 17:39:43 +02:00
import SelectWrapper from '../../components/SelectWrapper';
// import createYupSchema from './utils/schema';
// import setDefaultForm from './utils/createDefaultForm';
2019-08-22 17:15:15 +02:00
import getInjectedComponents from './utils/getComponents';
import init from './init';
2019-07-11 11:35:18 +02:00
import reducer, { initialState } from './reducer';
2019-08-22 17:15:15 +02:00
import {
LinkWrapper,
// MainWrapper,
SubWrapper,
} from './components';
// import {
// getMediaAttributes,
// cleanData,
// mapDataKeysToFilesToUpload,
// } from './utils/formatData';
// import {
// getDefaultComponentValues,
// retrieveDisplayedComponents,
// retrieveComponentsLayoutsToFetch,
// } from './utils/components';
import createAttributesLayout from './utils/createAttributesLayout';
// const getRequestUrl = path => `/${pluginId}/explorer/${path}`;
const EditView = ({
2019-07-11 16:53:00 +02:00
currentEnvironment,
emitEvent,
2019-07-11 11:35:18 +02:00
layouts,
2019-07-11 16:53:00 +02:00
plugins,
slug,
}) => {
const formatLayoutRef = useRef();
formatLayoutRef.current = createAttributesLayout;
// Retrieve push to programmatically navigate between views
const { push } = useHistory();
// Retrieve the search
const { search } = useLocation();
// Retrieve the document's id
2019-10-24 17:08:52 +02:00
const { id } = useParams();
// eslint-disable-next-line react-hooks/exhaustive-deps
2019-07-11 11:35:18 +02:00
const [reducerState, dispatch] = useReducer(reducer, initialState, () =>
init(initialState)
);
const currentContentTypeLayoutData = useMemo(
() => get(layouts, [slug, 'contentType'], {}),
[layouts, slug]
);
const currentContentTypeLayout = useMemo(
() => get(currentContentTypeLayoutData, ['layouts', 'edit'], []),
[currentContentTypeLayoutData]
);
const currentContentTypeLayoutRelations = useMemo(
() => get(currentContentTypeLayoutData, ['layouts', 'editRelations'], []),
[currentContentTypeLayoutData]
);
const currentContentTypeSchema = useMemo(
() => get(currentContentTypeLayoutData, ['schema'], {}),
[currentContentTypeLayoutData]
2019-07-11 11:35:18 +02:00
);
const source = getQueryParameters(search, 'source');
const getFieldType = useCallback(
fieldName => {
return get(
currentContentTypeSchema,
['attributes', fieldName, 'type'],
''
);
},
[currentContentTypeSchema]
);
// Check if a block is a dynamic zone
const isDynamicZone = useCallback(
block => {
return block.every(subBlock => {
return subBlock.every(obj => getFieldType(obj.name) === 'dynamiczone');
});
},
[getFieldType]
);
2019-07-11 11:35:18 +02:00
const isCreatingEntry = id === 'create';
2019-07-12 18:38:29 +02:00
useEffect(() => {
2019-09-16 17:47:43 +02:00
// Force state to be cleared when navigation from one entry to another
dispatch({ type: 'RESET_PROPS' });
dispatch({
type: 'SET_LAYOUT_DATA',
formattedContentTypeLayout: formatLayoutRef.current(
currentContentTypeLayout,
currentContentTypeSchema.attributes
),
});
}, [currentContentTypeLayout, currentContentTypeSchema.attributes]);
2019-09-16 17:47:43 +02:00
const { formattedContentTypeLayout, initialData } = reducerState.toJS();
2019-07-18 16:53:12 +02:00
const currentContentTypeMainField = get(
currentContentTypeLayoutData,
['settings', 'mainField'],
'id'
);
2019-07-11 11:35:18 +02:00
const pluginHeaderTitle = isCreatingEntry
? { id: `${pluginId}.containers.Edit.pluginHeader.title.new` }
: templateObject({ mainField: currentContentTypeMainField }, initialData)
2019-07-11 11:35:18 +02:00
.mainField;
2019-09-13 14:46:31 +02:00
// We can't use the getQueryParameters helper here because the search
// can contain 'redirectUrl' several times since we can navigate between documents
const redirectURL = search
.split('redirectUrl=')
.filter((_, index) => index !== 0)
.join('');
const redirectToPreviousPage = () => push(redirectURL);
2019-09-13 14:46:31 +02:00
2019-07-18 16:53:12 +02:00
const handleSubmit = async e => {
e.preventDefault();
};
2019-07-11 11:35:18 +02:00
return (
<EditViewProvider>
<BackHeader onClick={() => redirectToPreviousPage()} />
<Container className="container-fluid">
<form onSubmit={handleSubmit}>
<PluginHeader
actions={[
{
label: `${pluginId}.containers.Edit.reset`,
kind: 'secondary',
onClick: () => {
// toggleWarningCancel();
},
type: 'button',
// disabled: isSubmitting, // TODO STATE WHEN SUBMITING
},
{
kind: 'primary',
label: `${pluginId}.containers.Edit.submit`,
type: 'submit',
// loader: isSubmitting,
// style: isSubmitting ? { marginRight: '18px' } : {},
// disabled: isSubmitting, // TODO STATE WHEN SUBMITING
},
]}
subActions={
isCreatingEntry
? []
: [
{
label: 'app.utils.delete',
kind: 'delete',
onClick: () => {
// toggleWarningDelete();
},
type: 'button',
// disabled: isSubmitting, // TODO STATE WHEN SUBMITING
},
]
}
title={pluginHeaderTitle}
/>
2019-07-11 16:53:00 +02:00
<div className="row">
2019-07-24 18:24:23 +02:00
<div className="col-md-12 col-lg-9">
{formattedContentTypeLayout.map((block, blockIndex) => {
if (isDynamicZone(block)) {
const {
0: {
0: { name },
},
} = block;
2019-08-09 13:23:39 +02:00
return <DynamicZone name={name} />;
}
return <FormWrapper key={blockIndex}>SUBBLOCK</FormWrapper>;
})}
2019-07-12 14:15:56 +02:00
</div>
2019-07-12 14:15:56 +02:00
<div className="col-md-12 col-lg-3">
{currentContentTypeLayoutRelations.length > 0 && (
2019-07-12 14:15:56 +02:00
<SubWrapper
2019-07-29 17:11:53 +02:00
style={{ padding: '0 20px 1px', marginBottom: '26px' }}
2019-07-12 14:15:56 +02:00
>
2019-07-22 11:41:27 +02:00
<div style={{ paddingTop: '22px' }}>
{currentContentTypeLayoutRelations.map(relationName => {
2019-07-16 18:53:41 +02:00
const relation = get(
currentContentTypeLayoutData,
2019-07-16 18:53:41 +02:00
['schema', 'attributes', relationName],
{}
);
const relationMetas = get(
currentContentTypeLayoutData,
2019-07-24 10:16:44 +02:00
['metadatas', relationName, 'edit'],
2019-07-16 18:53:41 +02:00
{}
);
const value = get({}, [relationName], null);
2019-07-16 18:53:41 +02:00
return (
2019-07-17 12:06:19 +02:00
<SelectWrapper
2019-07-16 18:53:41 +02:00
{...relation}
{...relationMetas}
2019-07-17 12:06:19 +02:00
key={relationName}
2019-07-16 18:53:41 +02:00
name={relationName}
2019-07-24 10:16:44 +02:00
relationsType={relation.relationType}
2019-07-16 18:53:41 +02:00
value={value}
/>
);
})}
2019-07-15 13:00:31 +02:00
</div>
2019-07-12 14:15:56 +02:00
</SubWrapper>
)}
2019-07-11 16:53:00 +02:00
<LinkWrapper>
<ul>
<LiLink
message={{
id: 'app.links.configure-view',
2019-07-11 16:53:00 +02:00
}}
icon="layout"
key={`${pluginId}.link`}
2019-10-24 17:08:52 +02:00
// url={`/plugins/${pluginId}/ctm-configurations/edit-settings/content-types/${slug}${`?source=${source}`}`}
url={`ctm-configurations/edit-settings/content-types${`?source=${source}`}`}
2019-07-11 16:53:00 +02:00
onClick={() => {
// emitEvent('willEditContentTypeLayoutFromEditView');
2019-07-11 16:53:00 +02:00
}}
/>
2019-08-22 17:15:15 +02:00
{getInjectedComponents(
'right.links',
plugins,
currentEnvironment,
slug,
source,
emitEvent
)}
2019-07-11 16:53:00 +02:00
</ul>
</LinkWrapper>
</div>
</div>
</form>
</Container>
2019-07-17 17:39:43 +02:00
</EditViewProvider>
2019-07-11 11:35:18 +02:00
);
};
EditView.defaultProps = {
currentEnvironment: 'production',
emitEvent: () => {},
plugins: {},
};
2019-07-10 09:31:26 +02:00
2019-07-11 11:35:18 +02:00
EditView.propTypes = {
currentEnvironment: PropTypes.string,
emitEvent: PropTypes.func,
layouts: PropTypes.object.isRequired,
2019-10-24 17:08:52 +02:00
slug: PropTypes.string.isRequired,
2019-07-11 16:53:00 +02:00
plugins: PropTypes.object,
2019-07-11 11:35:18 +02:00
};
export { EditView };
2019-07-11 11:35:18 +02:00
export default memo(EditView);
// function EditView({
// currentEnvironment,
// emitEvent,
// layouts,
// location: { pathname, search },
// history: { push },
// slug,
// plugins,
// }) {
// const { id } = useParams();
// const abortController = new AbortController();
// const { signal } = abortController;
// const layout = get(layouts, [slug, 'contentType'], {});
// const isCreatingEntry = id === 'create';
// const attributes = get(layout, ['schema', 'attributes'], {});
// const components = retrieveDisplayedComponents(attributes);
// const componentLayoutsToGet = retrieveComponentsLayoutsToFetch(components);
// // States
// const [showWarningCancel, setWarningCancel] = useState(false);
// const [showWarningDelete, setWarningDelete] = useState(false);
// const [isSubmitting, setIsSubmitting] = useState(false);
// const [reducerState, dispatch] = useReducer(reducer, initialState, () =>
// init(initialState, layout, isCreatingEntry)
// );
// const state = reducerState.toJS();
// const {
// didCheckErrors,
// errors,
// componentLayoutsData,
// initialData,
// modifiedData,
// isLoading,
// isLoadingForLayouts,
// } = state;
// const source = getQueryParameters(search, 'source');
// const shouldShowLoader =
// isLoadingForLayouts || (!isCreatingEntry && isLoading);
// useEffect(() => {
// const fetchComponentLayouts = async () => {
// try {
// const data = await Promise.all(
// componentLayoutsToGet.map(uid =>
// request(`/${pluginId}/components/${uid}`, {
// method: 'GET',
// signal,
// })
// )
// );
// const componentLayouts = data.reduce((acc, current) => {
// acc[current.data.uid] = current.data;
// return acc;
// }, {});
// // Retrieve all the default values for the repeatables and init the form
// const defaultComponentValues = getDefaultComponentValues(
// components,
// componentLayouts
// );
// dispatch({
// type: 'GET_COMPONENT_LAYOUTS_SUCCEEDED',
// componentLayouts,
// defaultComponentValues,
// isCreatingEntry,
// });
// } catch (err) {
// // TODO ADD A TRAD
// if (err.code !== 20) {
// strapi.notification.error('notification.error');
// }
// }
// };
// const fetchData = async () => {
// try {
// const data = await request(getRequestUrl(`${slug}/${id}`), {
// method: 'GET',
// params: { source },
// signal,
// });
// dispatch({
// type: 'GET_DATA_SUCCEEDED',
// data,
// defaultForm: setDefaultForm(get(layout, ['schema', 'attributes'])),
// });
// fetchComponentLayouts();
// } catch (err) {
// if (err.code !== 20) {
// strapi.notification.error(`${pluginId}.error.record.fetch`);
// }
// }
// };
// // Force state to be cleared when navigation from one entry to another
// dispatch({ type: 'RESET_PROPS' });
// if (!isCreatingEntry) {
// fetchData();
// } else {
// dispatch({
// type: 'INIT',
// data: setDefaultForm(get(layout, ['schema', 'attributes'])),
// defaultForm: setDefaultForm(get(layout, ['schema', 'attributes'])),
// });
// fetchComponentLayouts();
// }
// return () => {
// abortController.abort();
// };
// // eslint-disable-next-line react-hooks/exhaustive-deps
// }, [id, isCreatingEntry, slug, source, pathname]);
// if (shouldShowLoader) {
// return <LoadingIndicatorPage />;
// }
// const toggleWarningCancel = () => setWarningCancel(prevState => !prevState);
// const toggleWarningDelete = () => setWarningDelete(prevState => !prevState);
// const redirectURL = search
// .split('redirectUrl=')
// .filter((_, index) => index !== 0)
// .join('');
// const redirectToPreviousPage = () => push(redirectURL);
// const handleConfirmDelete = async () => {
// toggleWarningDelete();
// setIsSubmitting(true);
// try {
// await request(getRequestUrl(`${slug}/${id}`), {
// method: 'DELETE',
// params: { source },
// });
// strapi.notification.success(`${pluginId}.success.record.delete`);
// redirectToPreviousPage();
// } catch (err) {
// setIsSubmitting(false);
// strapi.notification.error(`${pluginId}.error.record.delete`);
// }
// };
// const displayedFieldNameInHeader = get(
// layout,
// ['settings', 'mainField'],
// 'id'
// );
// const pluginHeaderTitle = isCreatingEntry
// ? { id: `${pluginId}.containers.Edit.pluginHeader.title.new` }
// : templateObject({ mainField: displayedFieldNameInHeader }, initialData)
// .mainField;
// const displayedRelations = get(layout, ['layouts', 'editRelations'], []);
// const hasRelations = displayedRelations.length > 0;
// const fields = get(layout, ['layouts', 'edit'], []);
// const checkFormErrors = async () => {
// const schema = createYupSchema(layout, {
// components: componentLayoutsData,
// });
// let errors = {};
// try {
// // Validate the form using yup
// await schema.validate(modifiedData, { abortEarly: false });
// } catch (err) {
// errors = getYupInnerErrors(err);
// }
// dispatch({
// type: 'SET_ERRORS',
// errors,
// });
// };
// 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,
// });
// };
// const handleSubmit = async e => {
// e.preventDefault();
// const schema = createYupSchema(layout, {
// components: componentLayoutsData,
// });
// try {
// // Validate the form using yup
// await schema.validate(modifiedData, { abortEarly: false });
// // Set the loading state in the plugin header
// setIsSubmitting(true);
// emitEvent('willSaveEntry');
// // Create an object containing all the paths of the media fields
// const filesMap = getMediaAttributes(layout, componentLayoutsData);
// // Create an object that maps the keys with the related files to upload
// const filesToUpload = mapDataKeysToFilesToUpload(filesMap, modifiedData);
// const cleanedData = cleanData(
// cloneDeep(modifiedData),
// layout,
// componentLayoutsData
// );
// 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);
// }
// } catch (err) {
// setIsSubmitting(false);
// const errors = get(err, 'inner', []).reduce((acc, curr) => {
// acc[
// curr.path
// .split('[')
// .join('.')
// .split(']')
// .join('')
// ] = [{ id: curr.message }];
// return acc;
// }, {});
// dispatch({
// type: 'SET_ERRORS',
// errors,
// });
// strapi.notification.error(
// `${pluginId}.containers.EditView.notification.errors`
// );
// }
// };
// // return null;
// return (
// <EditViewProvider
// addRelation={({ target: { name, value } }) => {
// dispatch({
// type: 'ADD_RELATION',
// keys: name.split('.'),
// value,
// });
// }}
// checkFormErrors={checkFormErrors}
// didCheckErrors={didCheckErrors}
// errors={errors}
// moveRelation={(dragIndex, overIndex, name) => {
// dispatch({
// type: 'MOVE_FIELD',
// dragIndex,
// overIndex,
// keys: name.split('.'),
// });
// }}
// onChange={handleChange}
// onRemove={keys => {
// dispatch({
// type: 'REMOVE_RELATION',
// keys,
// });
// }}
// pathname={pathname}
// resetErrors={() => {
// dispatch({
// type: 'SET_ERRORS',
// errors: {},
// });
// }}
// resetComponentData={componentName => {
// dispatch({
// type: 'RESET_COMPONENT_DATA',
// componentName,
// });
// }}
// search={search}
// >
// <BackHeader onClick={() => redirectToPreviousPage()} />
// <Container className="container-fluid">
// <form onSubmit={handleSubmit}>
// <PluginHeader
// actions={[
// {
// label: `${pluginId}.containers.Edit.reset`,
// kind: 'secondary',
// onClick: () => {
// toggleWarningCancel();
// },
// type: 'button',
// disabled: isSubmitting, // TODO STATE WHEN SUBMITING
// },
// {
// kind: 'primary',
// label: `${pluginId}.containers.Edit.submit`,
// type: 'submit',
// loader: isSubmitting,
// style: isSubmitting ? { marginRight: '18px' } : {},
// disabled: isSubmitting, // TODO STATE WHEN SUBMITING
// },
// ]}
// subActions={
// isCreatingEntry
// ? []
// : [
// {
// label: 'app.utils.delete',
// kind: 'delete',
// onClick: () => {
// toggleWarningDelete();
// },
// type: 'button',
// disabled: isSubmitting, // TODO STATE WHEN SUBMITING
// },
// ]
// }
// title={pluginHeaderTitle}
// />
// <div className="row">
// <div className="col-md-12 col-lg-9">
// <MainWrapper>
// {fields.map((fieldsRow, key) => {
// if (fieldsRow.length === 0) {
// return null;
// }
// const [{ name }] = fieldsRow;
// const component = get(
// layout,
// ['schema', 'attributes', name],
// {}
// );
// const componentMetas = get(
// layout,
// ['metadatas', name, 'edit'],
// {}
// );
// const componentValue = get(
// modifiedData,
// [name],
// component.repeatable ? [] : {}
// );
// if (
// fieldsRow.length === 1 &&
// component.type === 'component'
// ) {
// // Array containing all the keys with of the error object created by YUP
// // It is used only to know if whether or not we need to apply an orange border to the n+1 field item
// const componentErrorKeys = Object.keys(errors)
// .filter(errorKey => errorKey.includes(name))
// .map(errorKey =>
// errorKey
// .split('.')
// .slice(0, 2)
// .join('.')
// );
// return (
// <ComponentField
// {...component}
// {...componentMetas}
// addField={(keys, isRepeatable = true) => {
// dispatch({
// type: 'ADD_FIELD_TO_COMPONENT',
// keys: keys.split('.'),
// isRepeatable,
// });
// }}
// componentErrorKeys={componentErrorKeys}
// componentValue={componentValue}
// key={key}
// isRepeatable={component.repeatable || false}
// name={name}
// modifiedData={modifiedData}
// moveComponentField={(dragIndex, overIndex, name) => {
// dispatch({
// type: 'MOVE_FIELD',
// dragIndex,
// overIndex,
// keys: name.split('.'),
// });
// }}
// onChange={handleChange}
// layout={get(
// componentLayoutsData,
// component.component,
// {}
// )}
// pathname={pathname}
// removeField={(keys, shouldAddEmptyField) => {
// dispatch({
// type: 'ON_REMOVE_FIELD',
// keys: keys.split('.'),
// shouldAddEmptyField,
// });
// }}
// />
// );
// }
// return (
// <div key={key} className="row">
// {fieldsRow.map(({ name }, index) => {
// return (
// <Inputs
// autoFocus={key === 0 && index === 0}
// didCheckErrors={didCheckErrors}
// errors={errors}
// key={name}
// keys={name}
// layout={layout}
// modifiedData={modifiedData}
// name={name}
// onChange={handleChange}
// />
// );
// })}
// </div>
// );
// })}
// </MainWrapper>
// </div>
// <div className="col-md-12 col-lg-3">
// {hasRelations && (
// <SubWrapper
// style={{ padding: '0 20px 1px', marginBottom: '26px' }}
// >
// <div style={{ paddingTop: '22px' }}>
// {displayedRelations.map(relationName => {
// const relation = get(
// layout,
// ['schema', 'attributes', relationName],
// {}
// );
// const relationMetas = get(
// layout,
// ['metadatas', relationName, 'edit'],
// {}
// );
// const value = get(modifiedData, [relationName], null);
// return (
// <SelectWrapper
// {...relation}
// {...relationMetas}
// key={relationName}
// name={relationName}
// relationsType={relation.relationType}
// value={value}
// />
// );
// })}
// </div>
// </SubWrapper>
// )}
// <LinkWrapper>
// <ul>
// <LiLink
// message={{
// id: 'app.links.configure-view',
// }}
// icon="layout"
// key={`${pluginId}.link`}
// // url={`/plugins/${pluginId}/ctm-configurations/edit-settings/content-types/${slug}${`?source=${source}`}`}
// url={`ctm-configurations/edit-settings/content-types${`?source=${source}`}`}
// onClick={() => {
// emitEvent('willEditContentTypeLayoutFromEditView');
// }}
// />
// {getInjectedComponents(
// 'right.links',
// plugins,
// currentEnvironment,
// slug,
// source,
// emitEvent
// )}
// </ul>
// </LinkWrapper>
// </div>
// </div>
// </form>
// <PopUpWarning
// isOpen={showWarningCancel}
// toggleModal={toggleWarningCancel}
// content={{
// title: `${pluginId}.popUpWarning.title`,
// message: `${pluginId}.popUpWarning.warning.cancelAllSettings`,
// cancel: `${pluginId}.popUpWarning.button.cancel`,
// confirm: `${pluginId}.popUpWarning.button.confirm`,
// }}
// popUpWarningType="danger"
// onConfirm={() => {
// dispatch({
// type: 'RESET_FORM',
// });
// toggleWarningCancel();
// }}
// />
// <PopUpWarning
// isOpen={showWarningDelete}
// toggleModal={toggleWarningDelete}
// content={{
// title: `${pluginId}.popUpWarning.title`,
// message: `${pluginId}.popUpWarning.bodyMessage.contentType.delete`,
// cancel: `${pluginId}.popUpWarning.button.cancel`,
// confirm: `${pluginId}.popUpWarning.button.confirm`,
// }}
// popUpWarningType="danger"
// onConfirm={handleConfirmDelete}
// />
// </Container>
// </EditViewProvider>
// );
// }
// EditView.propTypes = {
// currentEnvironment: PropTypes.string.isRequired,
// emitEvent: PropTypes.func.isRequired,
// history: PropTypes.shape({
// push: PropTypes.func.isRequired,
// }),
// layouts: PropTypes.object,
// location: PropTypes.shape({
// pathname: PropTypes.string,
// search: PropTypes.string,
// }),
// slug: PropTypes.string.isRequired,
// plugins: PropTypes.object,
// };
// export default memo(EditView);