511 lines
16 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';
import { get } 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-17 17:39:43 +02:00
import { EditViewProvider } from '../../contexts/EditView';
2019-07-11 11:35:18 +02:00
import Container from '../../components/Container';
2019-07-17 17:39:43 +02:00
import Group from '../../components/Group';
import Inputs from '../../components/Inputs';
import SelectWrapper from '../../components/SelectWrapper';
2019-07-11 11:35:18 +02:00
2019-07-16 15:19:28 +02:00
import init, { setDefaultForm } from './init';
2019-07-11 11:35:18 +02:00
import reducer, { initialState } from './reducer';
2019-07-17 17:39:43 +02:00
import { LinkWrapper, MainWrapper, SubWrapper } from './components';
2019-07-11 11:35:18 +02:00
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,
2019-07-16 18:53:41 +02:00
location: { pathname, 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) => {
2019-07-16 15:19:28 +02:00
const { group, repeatable, type, min } = get(attributes, [current], {
2019-07-12 18:38:29 +02:00
group: '',
type: '',
repeatable,
});
if (type === 'group') {
2019-07-16 15:19:28 +02:00
acc.push({ key: current, group, repeatable, isOpen: !repeatable, min });
2019-07-12 18:38:29 +02:00
}
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-17 17:39:43 +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);
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;
}, {});
2019-07-16 15:19:28 +02:00
// Retrieve all the default values for the repeatables and init the form
const defaultGroupValues = groups.reduce((acc, current) => {
const defaultForm = setDefaultForm(
get(groupLayouts, [current.group, 'schema', 'attributes'], {})
);
const arr = [];
if (current.min && current.repeatable === true) {
for (let i = 0; i < current.min; i++) {
arr.push({ ...defaultForm, _temp__id: i });
}
}
acc[current.key] = {
toSet: arr,
defaultRepeatable: defaultForm,
};
if (current.repeatable === false) {
acc[current.key] = {
toSet: defaultForm,
defaultRepeatable: defaultForm,
};
}
return acc;
}, {});
2019-07-12 18:38:29 +02:00
dispatch({
type: 'GET_GROUP_LAYOUTS_SUCCEEDED',
groupLayouts,
2019-07-16 15:19:28 +02:00
defaultGroupValues,
isCreatingEntry,
2019-07-12 18:38:29 +02:00
});
} 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);
2019-07-16 18:53:41 +02:00
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`);
}
};
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-16 18:53:41 +02:00
const displayedRelations = get(layout, ['layouts', 'editRelations'], []);
const hasRelations = displayedRelations.length > 0;
2019-07-12 14:15:56 +02:00
const fields = get(layout, ['layouts', 'edit'], []);
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 (
2019-07-17 17:39:43 +02:00
<EditViewProvider
addRelation={({ target: { name, value } }) => {
dispatch({
type: 'ADD_RELATION',
keys: name.split('.'),
value,
});
}}
moveRelation={(dragIndex, overIndex, name) => {
dispatch({
type: 'MOVE_FIELD',
dragIndex,
overIndex,
keys: name.split('.'),
});
}}
onChange={({ target: { name, value } }) => {
dispatch({
type: 'ON_CHANGE',
keys: name.split('.'),
value,
});
}}
onRemove={keys => {
dispatch({
type: 'REMOVE_RELATION',
keys,
});
}}
pathname={pathname}
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', 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-15 13:00:31 +02:00
addField={keys => {
dispatch({
type: 'ADD_FIELD_TO_GROUP',
keys: keys.split('.'),
});
}}
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}
modifiedData={modifiedData}
2019-07-15 17:29:13 +02:00
moveGroupField={(dragIndex, overIndex, name) => {
dispatch({
type: 'MOVE_FIELD',
2019-07-15 17:29:13 +02:00
dragIndex,
overIndex,
keys: name.split('.'),
});
}}
2019-07-13 10:04:25 +02:00
onChange={({ target: { name, value } }) => {
dispatch({
type: 'ON_CHANGE',
keys: name.split('.'),
value,
});
}}
layout={get(groupLayoutsData, group.group, {})}
2019-07-17 16:14:00 +02:00
pathname={pathname}
removeField={(keys, shouldAddEmptyField) => {
2019-07-15 13:00:31 +02:00
dispatch({
type: 'ON_REMOVE_FIELD',
keys: keys.split('.'),
shouldAddEmptyField,
2019-07-15 13:00:31 +02:00
});
}}
2019-07-12 18:38:29 +02:00
/>
);
}
2019-07-12 14:15:56 +02:00
return (
<div key={key} className="row">
2019-07-17 12:06:19 +02:00
{fieldsRow.map(({ name }, index) => {
2019-07-12 14:15:56 +02:00
return (
2019-07-12 15:39:18 +02:00
<Inputs
2019-07-17 12:06:19 +02:00
autoFocus={key === 0 && index === 0}
2019-07-12 15:39:18 +02:00
key={name}
keys={name}
layout={layout}
modifiedData={modifiedData}
2019-07-12 15:39:18 +02:00
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
/>
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' }}
>
2019-07-15 13:00:31 +02:00
<div style={{ paddingTop: '19px' }}>
2019-07-16 18:53:41 +02:00
{displayedRelations.map(relationName => {
const relation = get(
layout,
['schema', 'attributes', relationName],
{}
);
const relationMetas = get(
layout,
['metadata', relationName, 'edit'],
{}
);
const value = get(modifiedData, [relationName], null);
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-17 12:06:19 +02:00
relationType={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: `${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-17 17:39:43 +02:00
</EditViewProvider>
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);