459 lines
14 KiB
JavaScript
Raw Normal View History

import React, { memo, useEffect, useState, useReducer } from 'react';
2019-07-11 11:35:18 +02:00
import PropTypes from 'prop-types';
2019-07-12 15:39:18 +02:00
import { get, omit } from 'lodash';
2019-07-12 14:15:56 +02:00
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,
2019-07-11 11:35:18 +02:00
request,
templateObject,
} from 'strapi-helper-plugin';
import pluginId from '../../pluginId';
2019-07-10 09:31:26 +02:00
2019-07-11 11:35:18 +02:00
import Container from '../../components/Container';
2019-07-12 14:15:56 +02:00
import { LinkWrapper, MainWrapper, SubWrapper } from './components';
2019-07-12 18:38:29 +02:00
import Group from './Group';
2019-07-12 15:39:18 +02:00
import Inputs from './Inputs';
2019-07-11 11:35:18 +02:00
import init from './init';
import reducer, { initialState } from './reducer';
const getRequestUrl = path => `/${pluginId}/explorer/${path}`;
function EditView({
2019-07-11 16:53:00 +02:00
currentEnvironment,
emitEvent,
2019-07-11 11:35:18 +02:00
layouts,
location: { search },
history: { push },
2019-07-11 11:35:18 +02:00
match: {
params: { slug, id },
},
2019-07-11 16:53:00 +02:00
plugins,
2019-07-11 11:35:18 +02:00
}) {
2019-07-12 15:39:18 +02:00
const layout = get(layouts, [slug], {});
const isCreatingEntry = id === 'create';
2019-07-12 18:38:29 +02:00
const attributes = get(layout, ['schema', 'attributes'], {});
const groups = Object.keys(attributes).reduce((acc, current) => {
const { group, repeatable, type } = get(attributes, [current], {
group: '',
type: '',
repeatable,
});
if (type === 'group') {
acc.push({ key: current, group, repeatable, isOpen: !repeatable });
}
return acc;
}, []);
const groupLayoutsToGet = groups
.filter(
(current, index) =>
groups.findIndex(el => el.group === current.group) === index
)
.map(({ group }) => group);
2019-07-12 15:39:18 +02:00
const [showWarningCancel, setWarningCancel] = useState(false);
const [showWarningDelete, setWarningDelete] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false);
2019-07-11 11:35:18 +02:00
const [reducerState, dispatch] = useReducer(reducer, initialState, () =>
2019-07-12 15:39:18 +02:00
init(initialState, layout, isCreatingEntry)
2019-07-11 11:35:18 +02:00
);
2019-07-12 15:39:18 +02:00
const state = reducerState.toJS();
2019-07-12 18:38:29 +02:00
const {
groupLayoutsData,
initialData,
modifiedData,
isLoading,
isLoadingForLayouts,
} = state;
2019-07-11 11:35:18 +02:00
const source = getQueryParameters(search, 'source');
2019-07-12 18:38:29 +02:00
const shouldShowLoader =
isLoadingForLayouts || (!isCreatingEntry && isLoading);
// Keep these lines if we make the Group component collapsable
// useEffect(() => {
// dispatch({
// type: 'SET_COLLAPSES_COMPONENTS_STATE',
// collapses: groups.reduce((acc, current) => {
// const { key, isOpen, repeatable } = current;
// acc[key] = { isOpen, repeatable };
// return acc;
// }, {}),
// });
// // eslint-disable-next-line react-hooks/exhaustive-deps
// }, []);
2019-07-11 11:35:18 +02:00
useEffect(() => {
const fetchData = async () => {
try {
const data = await request(getRequestUrl(`${slug}/${id}`), {
method: 'GET',
params: { source },
});
2019-07-11 11:35:18 +02:00
dispatch({
type: 'GET_DATA_SUCCEEDED',
data,
});
} catch (err) {
strapi.notification.error(`${pluginId}.error.record.fetch`);
}
2019-07-11 11:35:18 +02:00
};
2019-07-12 18:38:29 +02:00
const fetchGroupLayouts = async () => {
try {
const data = await Promise.all(
groupLayoutsToGet.map(uid =>
request(`/${pluginId}/fixtures/layouts/${uid}`, { method: 'GET' })
)
);
const groupLayouts = data.reduce((acc, current) => {
acc[current.layout.uid] = current.layout;
return acc;
}, {});
dispatch({
type: 'GET_GROUP_LAYOUTS_SUCCEEDED',
groupLayouts,
});
} catch (err) {
console.log({ err });
// TODO ADD A TRAD
strapi.notification.error('notification.error');
}
};
fetchGroupLayouts();
2019-07-11 11:35:18 +02:00
if (!isCreatingEntry) {
fetchData();
}
2019-07-12 18:38:29 +02:00
// eslint-disable-next-line react-hooks/exhaustive-deps
2019-07-11 11:35:18 +02:00
}, [id, isCreatingEntry, slug, source]);
if (shouldShowLoader) {
return <LoadingIndicatorPage />;
}
const toggleWarningCancel = () => setWarningCancel(prevState => !prevState);
const toggleWarningDelete = () => setWarningDelete(prevState => !prevState);
const redirectURL = search.split('redirectUrl=')[1];
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`);
}
};
2019-07-11 11:35:18 +02:00
const handleSubmit = e => {
e.preventDefault();
};
const displayedFieldNameInHeader = get(
layout,
['settings', 'mainField'],
'id'
);
2019-07-11 11:35:18 +02:00
const pluginHeaderTitle = isCreatingEntry
? { id: `${pluginId}.containers.Edit.pluginHeader.title.new` }
: templateObject({ mainField: displayedFieldNameInHeader }, initialData)
.mainField;
2019-07-12 14:15:56 +02:00
const hasRelations = get(layout, ['layouts', 'editRelations'], []).length > 0;
const fields = get(layout, ['layouts', 'edit'], []);
2019-07-12 15:39:18 +02:00
2019-07-11 16:53:00 +02:00
/**
* Retrieve external links from injected components
* @type {Array} List of external links to display
*/
const retrieveLinksContainerComponent = () => {
const componentToInject = Object.keys(plugins).reduce((acc, current) => {
// Retrieve injected compos from plugin
// if compo can be injected in left.links area push the compo in the array
const currentPlugin = plugins[current];
const injectedComponents = get(currentPlugin, 'injectedComponents', []);
const compos = injectedComponents
.filter(compo => {
return (
compo.plugin === `${pluginId}.editPage` &&
compo.area === 'right.links'
);
})
.map(compo => {
const Component = compo.component;
return (
<Component
currentEnvironment={currentEnvironment}
getModelName={() => slug}
getSource={() => source}
getContentTypeBuilderBaseUrl={() =>
'/plugins/content-type-builder/models/'
}
{...compo.props}
key={compo.key}
onClick={() => {
emitEvent('willEditContentTypeFromEditView');
}}
/>
);
});
return [...acc, ...compos];
}, []);
return componentToInject;
};
2019-07-11 11:35:18 +02:00
return (
<>
<BackHeader onClick={() => redirectToPreviousPage()} />
2019-07-12 14:15:56 +02:00
<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', flexGrow: 2 }
: { flexGrow: 2 },
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-12 18:38:29 +02:00
<div className="coel-md-12 col-lg-9">
2019-07-12 14:15:56 +02:00
<MainWrapper>
{fields.map((fieldsRow, key) => {
//
2019-07-12 18:38:29 +02:00
const [{ name }] = fieldsRow;
const group = get(layout, ['schema', 'attributes', name], {});
const groupMeta = get(layout, ['metadata', name, 'edit'], {});
2019-07-13 10:04:25 +02:00
const groupValue = get(
modifiedData,
[name],
group.repeatable ? [] : {}
);
2019-07-12 18:38:29 +02:00
if (fieldsRow.length === 1 && group.type === 'group') {
return (
<Group
{...group}
{...groupMeta}
2019-07-13 10:04:25 +02:00
groupValue={groupValue}
key={key}
2019-07-12 18:38:29 +02:00
isRepeatable={group.repeatable}
2019-07-13 10:04:25 +02:00
name={name}
onChange={({ target: { name, value } }) => {
dispatch({
type: 'ON_CHANGE',
keys: name.split('.'),
value,
});
}}
2019-07-12 18:38:29 +02:00
layout={get(groupLayoutsData, name, {})}
/>
);
}
2019-07-12 14:15:56 +02:00
return (
<div key={key} className="row">
2019-07-12 15:39:18 +02:00
{fieldsRow.map(({ name }) => {
const attribute = get(
layout,
['schema', 'attributes', name],
{}
);
const { model, collection } = attribute;
const isMedia =
get(attribute, 'plugin', '') === 'upload' &&
(model || collection) === 'file';
const multiple = collection == 'file';
const metadata = get(
layout,
['metadata', name, 'edit'],
{}
);
const type = isMedia
? 'file'
: get(attribute, 'type', null);
const inputStyle =
type === 'text' ? { height: '196px' } : {};
const validations = omit(attribute, [
'type',
'model',
'via',
'collection',
'default',
'plugin',
'enum',
]);
const value = get(modifiedData, name);
2019-07-12 18:38:29 +02:00
const { description, visible } = metadata;
2019-07-12 15:39:18 +02:00
2019-07-13 10:04:25 +02:00
// Remove the updatedAt & createdAt fields
2019-07-12 18:38:29 +02:00
if (visible === false) {
2019-07-12 15:39:18 +02:00
return null;
}
2019-07-12 14:15:56 +02:00
return (
2019-07-12 15:39:18 +02:00
<Inputs
{...metadata}
2019-07-12 18:38:29 +02:00
inputDescription={description}
2019-07-12 15:39:18 +02:00
inputStyle={inputStyle}
key={name}
multiple={multiple}
name={name}
2019-07-12 15:44:23 +02:00
onChange={({ target: { name, value } }) => {
dispatch({
type: 'ON_CHANGE',
keys: name.split('.'),
value,
});
}}
2019-07-12 15:39:18 +02:00
selectOptions={get(attribute, 'enum', [])}
type={type}
validations={validations}
value={value}
/>
2019-07-12 14:15:56 +02:00
);
})}
</div>
);
})}
</MainWrapper>
</div>
<div className="col-md-12 col-lg-3">
{hasRelations && (
<SubWrapper
style={{ padding: '0 20px 1px', marginBottom: '28px' }}
>
<div style={{ paddingTop: '19px' }}>Relations</div>
</SubWrapper>
)}
2019-07-11 16:53:00 +02:00
<LinkWrapper>
<ul>
<LiLink
message={{
id: `${pluginId}.containers.Edit.Link.Layout`,
}}
icon="layout"
key={`${pluginId}.link`}
url={`/plugins/${pluginId}/ctm-configurations/models/${slug}/edit-settings`}
onClick={() => {
emitEvent('willEditContentTypeLayoutFromEditView');
}}
/>
{retrieveLinksContainerComponent()}
</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}
2019-07-11 11:35:18 +02:00
/>
</Container>
</>
2019-07-11 11:35:18 +02:00
);
2019-07-10 09:31:26 +02:00
}
2019-07-11 11:35:18 +02:00
EditView.propTypes = {
2019-07-11 16:53:00 +02:00
currentEnvironment: PropTypes.string.isRequired,
emitEvent: PropTypes.func.isRequired,
history: PropTypes.shape({
push: PropTypes.func.isRequired,
}),
2019-07-11 11:35:18 +02:00
layouts: PropTypes.object,
location: PropTypes.shape({
search: PropTypes.string,
}),
match: PropTypes.shape({
params: PropTypes.shape({
id: PropTypes.string.isRequired,
slug: PropTypes.string.isRequired,
}),
}),
2019-07-11 16:53:00 +02:00
plugins: PropTypes.object,
2019-07-11 11:35:18 +02:00
};
export default memo(EditView);