strapi-plugin-content-manager
Version:
A powerful UI to easily manage your data.
409 lines (365 loc) • 12.7 kB
JavaScript
import React, { useCallback, useMemo, useReducer, useState } from 'react';
import PropTypes from 'prop-types';
import { useHistory } from 'react-router-dom';
import { useSelector, shallowEqual } from 'react-redux';
import { cloneDeep, flatMap, get, set, pick } from 'lodash';
import { request, useGlobalContext } from 'strapi-helper-plugin';
import { Inputs as Input } from '@buffetjs/custom';
import { FormattedMessage } from 'react-intl';
import pluginId from '../../pluginId';
import { getInjectedComponents, getRequestUrl } from '../../utils';
import FieldsReorder from '../../components/FieldsReorder';
import FormTitle from '../../components/FormTitle';
import LayoutTitle from '../../components/LayoutTitle';
import PopupForm from '../../components/PopupForm';
import SettingsViewWrapper from '../../components/SettingsViewWrapper';
import SortableList from '../../components/SortableList';
import { makeSelectModelAndComponentSchemas } from '../Main/selectors';
import LayoutDndProvider from '../LayoutDndProvider';
import init from './init';
import reducer, { initialState } from './reducer';
import { createPossibleMainFieldsForModelsAndComponents, getInputProps } from './utils';
import { unformatLayout } from './utils/layout';
const EditSettingsView = ({ components, mainLayout, isContentTypeView, slug, updateLayout }) => {
const { push } = useHistory();
const { currentEnvironment, emitEvent, plugins } = useGlobalContext();
const [reducerState, dispatch] = useReducer(reducer, initialState, () =>
init(initialState, mainLayout, components)
);
const [isModalFormOpen, setIsModalFormOpen] = useState(false);
const [isDraggingSibling, setIsDraggingSibling] = useState(false);
const schemasSelector = useMemo(makeSelectModelAndComponentSchemas, []);
const { schemas } = useSelector(state => schemasSelector(state), shallowEqual);
const { componentLayouts, initialData, metaToEdit, modifiedData, metaForm } = reducerState.toJS();
const componentsAndModelsPossibleMainFields = useMemo(() => {
return createPossibleMainFieldsForModelsAndComponents(schemas);
}, [schemas]);
const fieldsReorderClassName = isContentTypeView ? 'col-8' : 'col-12';
const attributes = useMemo(() => get(modifiedData, 'attributes', {}), [modifiedData]);
const editLayout = modifiedData.layouts.edit;
const relationsLayout = modifiedData.layouts.editRelations;
const editRelationsLayoutRemainingFields = useMemo(() => {
return Object.keys(attributes)
.filter(attr => attributes[attr].type === 'relation')
.filter(attr => relationsLayout.indexOf(attr) === -1);
}, [attributes, relationsLayout]);
const formToDisplay = useMemo(() => {
if (!metaToEdit) {
return [];
}
const associatedMetas = get(modifiedData, ['metadatas', metaToEdit, 'edit'], {});
return Object.keys(associatedMetas).filter(meta => meta !== 'visible');
}, [metaToEdit, modifiedData]);
const editLayoutRemainingFields = useMemo(() => {
const displayedFields = flatMap(modifiedData.layouts.edit, 'rowContent');
return Object.keys(modifiedData.attributes)
.filter(attr => {
if (!isContentTypeView) {
return true;
}
return get(modifiedData, ['attributes', attr, 'type'], '') !== 'relation';
})
.filter(attr => get(modifiedData, ['metadatas', attr, 'edit', 'visible'], false) === true)
.filter(attr => {
return displayedFields.findIndex(el => el.name === attr) === -1;
})
.sort();
}, [isContentTypeView, modifiedData]);
const getSelectedItemSelectOptions = useCallback(
formType => {
if (formType !== 'relation' && formType !== 'component') {
return [];
}
const targetKey = formType === 'component' ? 'component' : 'targetModel';
const key = get(modifiedData, ['attributes', metaToEdit, targetKey], '');
return get(componentsAndModelsPossibleMainFields, [key], []);
},
[metaToEdit, componentsAndModelsPossibleMainFields, modifiedData]
);
const handleChange = ({ target: { name, value } }) => {
dispatch({
type: 'ON_CHANGE',
keys: name.split('.'),
value,
});
};
const handleChangeMeta = ({ target: { name, value } }) => {
dispatch({
type: 'ON_CHANGE_META',
keys: name.split('.'),
value,
});
};
const handleConfirm = async () => {
try {
const body = pick(cloneDeep(modifiedData), ['layouts', 'metadatas', 'settings']);
// We need to send the unformated edit layout
set(body, 'layouts.edit', unformatLayout(body.layouts.edit));
const requestURL = isContentTypeView
? getRequestUrl(`content-types/${slug}/configuration`)
: getRequestUrl(`components/${slug}/configuration`);
const response = await request(requestURL, { method: 'PUT', body });
if (updateLayout) {
updateLayout(response.data);
}
dispatch({
type: 'SUBMIT_SUCCEEDED',
});
emitEvent('didEditEditSettings');
} catch (err) {
strapi.notification.error('notification.error');
}
};
const handleSubmitMetaForm = e => {
e.preventDefault();
dispatch({
type: 'SUBMIT_META_FORM',
});
toggleModalForm();
};
const moveItem = (dragIndex, hoverIndex, dragRowIndex, hoverRowIndex) => {
// Same row = just reorder
if (dragRowIndex === hoverRowIndex) {
dispatch({
type: 'REORDER_ROW',
dragRowIndex,
dragIndex,
hoverIndex,
});
} else {
dispatch({
type: 'REORDER_DIFF_ROW',
dragIndex,
hoverIndex,
dragRowIndex,
hoverRowIndex,
});
}
};
const moveRow = (dragRowIndex, hoverRowIndex) => {
dispatch({
type: 'MOVE_ROW',
dragRowIndex,
hoverRowIndex,
});
};
const toggleModalForm = () => {
setIsModalFormOpen(prevState => !prevState);
};
const renderForm = () =>
formToDisplay.map((meta, index) => {
const formType = get(attributes, [metaToEdit, 'type']);
if (formType === 'dynamiczone' && !['label', 'description'].includes(meta)) {
return null;
}
if ((formType === 'component' || formType === 'media') && meta !== 'label') {
return null;
}
if ((formType === 'json' || formType === 'boolean') && meta === 'placeholder') {
return null;
}
if (formType === 'richtext' && meta === 'editable') {
return null;
}
return (
<div className="col-6" key={meta}>
<FormattedMessage
id={`${pluginId}.containers.SettingPage.editSettings.relation-field.description`}
>
{description => (
<FormattedMessage
id={get(getInputProps(meta), 'label.id', 'app.utils.defaultMessage')}
>
{label => (
<Input
autoFocus={index === 0}
description={meta === 'mainField' ? description : ''}
label={label}
name={meta}
type={getInputProps(meta).type}
value={get(metaForm, meta, '')}
onChange={handleChangeMeta}
options={getSelectedItemSelectOptions(formType)}
/>
)}
</FormattedMessage>
)}
</FormattedMessage>
</div>
);
});
return (
<LayoutDndProvider
attributes={attributes}
buttonData={editLayoutRemainingFields}
componentLayouts={componentLayouts}
goTo={push}
isDraggingSibling={isDraggingSibling}
layout={editLayout}
metadatas={get(modifiedData, ['metadatas'], {})}
moveItem={moveItem}
moveRow={moveRow}
onAddData={name => {
dispatch({
type: 'ON_ADD_DATA',
name,
});
}}
relationsLayout={relationsLayout}
removeField={(rowIndex, fieldIndex) => {
dispatch({
type: 'REMOVE_FIELD',
rowIndex,
fieldIndex,
});
}}
setEditFieldToSelect={name => {
dispatch({
type: 'SET_FIELD_TO_EDIT',
name,
});
toggleModalForm();
}}
setIsDraggingSibling={setIsDraggingSibling}
selectedItemName={metaToEdit}
>
<SettingsViewWrapper
inputs={[
{
label: {
id: `${pluginId}.containers.SettingPage.editSettings.entry.title`,
},
description: {
id: `${pluginId}.containers.SettingPage.editSettings.entry.title.description`,
},
type: 'select',
name: 'settings.mainField',
customBootstrapClass: 'col-md-4',
selectOptions: ['id'],
didCheckErrors: false,
validations: {},
},
]}
initialData={initialData}
isLoading={false}
modifiedData={modifiedData}
name={modifiedData.info.name}
onChange={handleChange}
onConfirmReset={() => {
dispatch({
type: 'ON_RESET',
});
}}
onConfirmSubmit={handleConfirm}
slug={slug}
isEditSettings
>
<div className="row">
<LayoutTitle className={fieldsReorderClassName}>
<div
style={{
display: 'flex',
justifyContent: 'space-between',
}}
>
<div>
<FormTitle
title={`${pluginId}.global.displayedFields`}
description={`${pluginId}.containers.SettingPage.editSettings.description`}
/>
</div>
<div
style={{
marginTop: -6,
}}
>
{getInjectedComponents(
'editSettingsView',
'left.links',
plugins,
currentEnvironment,
slug,
push,
{
componentSlug: slug,
type: isContentTypeView ? 'content-types' : 'components',
modifiedData,
}
)}
</div>
</div>
</LayoutTitle>
{isContentTypeView && (
<LayoutTitle className="col-4">
<FormTitle
title={`${pluginId}.containers.SettingPage.relations`}
description={`${pluginId}.containers.SettingPage.editSettings.description`}
/>
</LayoutTitle>
)}
<FieldsReorder className={fieldsReorderClassName} />
{isContentTypeView && (
<SortableList
addItem={name => {
dispatch({
type: 'ADD_RELATION',
name,
});
}}
buttonData={editRelationsLayoutRemainingFields}
moveItem={(dragIndex, hoverIndex) => {
dispatch({
type: 'MOVE_RELATION',
dragIndex,
hoverIndex,
});
}}
removeItem={index => {
dispatch({
type: 'REMOVE_RELATION',
index,
});
}}
/>
)}
</div>
</SettingsViewWrapper>
<PopupForm
headerId={`${pluginId}.containers.EditSettingsView.modal-form.edit-field`}
isOpen={isModalFormOpen}
onClosed={() => {
dispatch({
type: 'UNSET_FIELD_TO_EDIT',
});
}}
onSubmit={handleSubmitMetaForm}
onToggle={toggleModalForm}
renderForm={renderForm}
subHeaderContent={metaToEdit}
type={get(attributes, [metaToEdit, 'type'], '')}
/>
</LayoutDndProvider>
);
};
EditSettingsView.defaultProps = {
isContentTypeView: false,
updateLayout: null,
};
EditSettingsView.propTypes = {
components: PropTypes.object.isRequired,
mainLayout: PropTypes.shape({
attributes: PropTypes.object.isRequired,
info: PropTypes.object.isRequired,
layouts: PropTypes.shape({
list: PropTypes.array.isRequired,
editRelations: PropTypes.array.isRequired,
edit: PropTypes.array.isRequired,
}).isRequired,
metadatas: PropTypes.object.isRequired,
options: PropTypes.object.isRequired,
}).isRequired,
isContentTypeView: PropTypes.bool,
slug: PropTypes.string.isRequired,
updateLayout: PropTypes.func,
};
export default EditSettingsView;