Allow component creation

This commit is contained in:
soupette 2019-11-25 14:49:50 +01:00
parent 5d0384f045
commit bc28d3daf1
7 changed files with 141 additions and 48 deletions

View File

@ -1,26 +1,32 @@
import React from 'react'; import React from 'react';
import Creatable from 'react-select/creatable'; import Creatable from 'react-select/creatable';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { import { SelectWrapper, SelectNav } from 'strapi-helper-plugin';
SelectWrapper, import { ErrorMessage } from '@buffetjs/styles';
SelectNav,
useGlobalContext,
} from 'strapi-helper-plugin';
import useDataManager from '../../hooks/useDataManager'; import useDataManager from '../../hooks/useDataManager';
const CreatableSelect = ({ label, name }) => { const CreatableSelect = ({ error, label, onChange, name }) => {
const { allComponentsCategories } = useDataManager(); const { allComponentsCategories } = useDataManager();
console.log({ label });
const { formatMessage } = useGlobalContext(); const handleChange = (inputValue, actionMeta) => {
const handleInputChange = (newValue, actionMeta) => { const { action } = actionMeta;
console.log({ actionMeta, newValue });
console.log(actionMeta); if (action === 'clear') {
onChange({ target: { name, value: '' } });
}
if (action === 'create-option' || action === 'select-option') {
onChange({ target: { name, value: inputValue.value } });
}
}; };
const styles = { const styles = {
control: (base, state) => ({ control: (base, state) => ({
...base, ...base,
border: state.isFocused border: state.isFocused
? '1px solid #78caff !important' ? '1px solid #78caff !important'
: error
? '1px solid red !important'
: '1px solid #E3E9F3 !important', : '1px solid #E3E9F3 !important',
}), }),
menu: base => { menu: base => {
@ -36,7 +42,7 @@ const CreatableSelect = ({ label, name }) => {
}; };
return ( return (
<SelectWrapper className="form-group"> <SelectWrapper className="form-group" style={{ marginBottom: 0 }}>
<SelectNav> <SelectNav>
<div> <div>
<label htmlFor={name}>{label}</label> <label htmlFor={name}>{label}</label>
@ -44,18 +50,24 @@ const CreatableSelect = ({ label, name }) => {
</SelectNav> </SelectNav>
<Creatable <Creatable
isClearable isClearable
onInputChange={handleInputChange} onChange={handleChange}
styles={styles} styles={styles}
options={formatOptions()} options={formatOptions()}
// menuIsOpen
/> />
{error && <ErrorMessage style={{ paddingTop: 10 }}>{error}</ErrorMessage>}
</SelectWrapper> </SelectWrapper>
); );
}; };
CreatableSelect.defaultProps = {
error: null,
};
CreatableSelect.propTypes = { CreatableSelect.propTypes = {
error: PropTypes.string,
label: PropTypes.string.isRequired, label: PropTypes.string.isRequired,
name: PropTypes.string.isRequired, name: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
}; };
export default CreatableSelect; export default CreatableSelect;

View File

@ -93,10 +93,16 @@ const DataManagerProvider = ({ children }) => {
}); });
}; };
const createSchema = (data, schemaType, uid) => { const createSchema = (data, schemaType, uid, componentCategory) => {
const type =
schemaType === 'contentType'
? 'CREATE_SCHEMA'
: 'CREATE_COMPONENT_SCHEMA';
dispatch({ dispatch({
type: 'CREATE_SCHEMA', type,
data, data,
componentCategory,
schemaType, schemaType,
uid, uid,
}); });
@ -169,8 +175,6 @@ const DataManagerProvider = ({ children }) => {
return <Redirect to={`/plugins/${pluginId}/content-types/${firstCTUid}`} />; return <Redirect to={`/plugins/${pluginId}/content-types/${firstCTUid}`} />;
} }
console.log({ modifiedData });
return ( return (
<DataManagerContext.Provider <DataManagerContext.Provider
value={{ value={{

View File

@ -88,10 +88,27 @@ const reducer = (state, action) => {
attributes: {}, attributes: {},
}, },
}; };
const key =
action.schemaType === 'contentType' ? 'contentTypes' : 'components';
return state.updateIn([key, action.uid], () => fromJS(newSchema)); // const key =
// action.schemaType === 'contentType' ? 'contentTypes' : 'components';
return state.updateIn(['contentTypes', action.uid], () =>
fromJS(newSchema)
);
}
case 'CREATE_COMPONENT_SCHEMA': {
const newSchema = {
uid: action.uid,
isTemporary: true,
category: action.componentCategory,
schema: {
...action.data,
attributes: {},
},
};
return state.updateIn(['components', action.uid], () =>
fromJS(newSchema)
);
} }
case 'EDIT_ATTRIBUTE': { case 'EDIT_ATTRIBUTE': {
const { const {

View File

@ -28,7 +28,7 @@ import HeaderNavLink from '../../components/HeaderNavLink';
import getTrad from '../../utils/getTrad'; import getTrad from '../../utils/getTrad';
import getAttributes from './utils/attributes'; import getAttributes from './utils/attributes';
import forms from './utils/forms'; import forms from './utils/forms';
import { createUid } from './utils/createUid'; import { createComponentUid, createUid } from './utils/createUid';
import init from './init'; import init from './init';
import reducer, { initialState } from './reducer'; import reducer, { initialState } from './reducer';
import RelationForm from '../../components/RelationForm'; import RelationForm from '../../components/RelationForm';
@ -57,6 +57,7 @@ const FormModal = () => {
const { const {
addAttribute, addAttribute,
contentTypes, contentTypes,
components,
createSchema, createSchema,
modifiedData: allDataSchema, modifiedData: allDataSchema,
sortedContentTypesList, sortedContentTypesList,
@ -155,13 +156,14 @@ const FormModal = () => {
const checkFormValidity = async () => { const checkFormValidity = async () => {
let schema; let schema;
if (state.modalType === 'contentType' || state.modalType === 'component') { if (state.modalType === 'contentType') {
schema = forms[state.modalType].schema(Object.keys(contentTypes)); schema = forms.contentType.schema(Object.keys(contentTypes));
} else if ( } else if (state.modalType === 'component') {
state.modalType === 'attribute' schema = forms.component.schema(
// && state.forTarget !== 'components' && Object.keys(components),
// state.forTarget !== 'component' modifiedData.category || ''
) { );
} else if (state.modalType === 'attribute') {
const type = const type =
state.attributeType === 'relation' ? 'relation' : modifiedData.type; state.attributeType === 'relation' ? 'relation' : modifiedData.type;
@ -275,11 +277,12 @@ const FormModal = () => {
await checkFormValidity(); await checkFormValidity();
const targetUid = const targetUid =
state.forTarget === 'components' ? state.targetUid : uid; state.forTarget === 'components' ? state.targetUid : uid;
const createNextSearch = searchUid => {
const nextSearch = `modalType=chooseAttribute&forTarget=${ return `modalType=chooseAttribute&forTarget=${
state.forTarget state.forTarget
}&targetUid=${targetUid}&headerDisplayName=${state.headerDisplayName || }&targetUid=${searchUid}&headerDisplayName=${state.headerDisplayName ||
modifiedData.name}`; modifiedData.name}`;
};
if (state.modalType === 'contentType') { if (state.modalType === 'contentType') {
// Create the content type schema // Create the content type schema
@ -289,7 +292,7 @@ const FormModal = () => {
: 'component-categories'; : 'component-categories';
push({ push({
pathname: `/plugins/${pluginId}/${nextSlug}/${uid}`, pathname: `/plugins/${pluginId}/${nextSlug}/${uid}`,
search: nextSearch, search: createNextSearch(targetUid),
}); });
} else if (state.modalType === 'attribute') { } else if (state.modalType === 'attribute') {
addAttribute( addAttribute(
@ -299,9 +302,22 @@ const FormModal = () => {
state.actionType === 'edit', state.actionType === 'edit',
initialData initialData
); );
push({ search: nextSearch }); push({ search: createNextSearch(targetUid) });
} else if (state.modalType === 'component') {
// Create the component schema
const componentUid = createComponentUid(
modifiedData.name,
modifiedData.category
);
const { category, ...rest } = modifiedData;
createSchema(rest, 'component', componentUid, category);
push({
search: createNextSearch(componentUid),
pathname: `/plugins/${pluginId}/component-categories/${category}/${componentUid}`,
});
} else { } else {
console.log('Do something with component later'); console.log('Do somethign later');
} }
dispatch({ dispatch({
type: 'RESET_PROPS', type: 'RESET_PROPS',

View File

@ -9,4 +9,9 @@ const createUid = name => {
return uid; return uid;
}; };
export { createUid, nameToSlug }; // From `content-type-builder/services/Components/createComponentUid`
const createComponentUid = (name, category) => {
return `${nameToSlug(category)}.${nameToSlug(name)}`;
};
export { createComponentUid, createUid, nameToSlug };

View File

@ -5,7 +5,7 @@ import { translatedErrors as errorsTrads } from 'strapi-helper-plugin';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import pluginId from '../../../pluginId'; import pluginId from '../../../pluginId';
import getTrad from '../../../utils/getTrad'; import getTrad from '../../../utils/getTrad';
import { createUid, nameToSlug } from './createUid'; import { createComponentUid, createUid, nameToSlug } from './createUid';
yup.addMethod(yup.mixed, 'defined', function() { yup.addMethod(yup.mixed, 'defined', function() {
return this.test( return this.test(
@ -18,7 +18,8 @@ yup.addMethod(yup.mixed, 'defined', function() {
yup.addMethod(yup.string, 'unique', function( yup.addMethod(yup.string, 'unique', function(
message, message,
alreadyTakenAttributes, alreadyTakenAttributes,
validator validator,
category = ''
) { ) {
return this.test('unique', message, function(string) { return this.test('unique', message, function(string) {
if (!string) { if (!string) {
@ -26,7 +27,7 @@ yup.addMethod(yup.string, 'unique', function(
} }
return !alreadyTakenAttributes.includes( return !alreadyTakenAttributes.includes(
typeof validator === 'function' ? validator(string) : string typeof validator === 'function' ? validator(string, category) : string
); );
}); });
}); });
@ -604,8 +605,6 @@ const forms = {
]); ]);
} }
console.log({ items });
return { return {
items, items,
}; };
@ -676,14 +675,44 @@ const forms = {
}, },
}, },
component: { component: {
schema(alreadyTakenAttributes) { schema(alreadyTakenAttributes, componentCategory) {
return yup.object().shape({ return yup.object().shape({
name: yup.string().required(), name: yup
.string()
.unique(
errorsTrads.unique,
alreadyTakenAttributes,
createComponentUid,
componentCategory
)
.required(),
category: yup.string().required(), category: yup.string().required(),
icon: yup.string().required(),
collectionName: yup.string(),
}); });
}, },
form: { form: {
base(data) { advanced() {
return {
items: [
[
{
autoFocus: true,
label: {
id: `${pluginId}.contentType.collectionName.label`,
},
description: {
id: `${pluginId}.contentType.collectionName.description`,
},
name: 'collectionName',
type: 'text',
validations: {},
},
],
],
};
},
base() {
const defaultItems = [ const defaultItems = [
[ [
{ {
@ -706,7 +735,7 @@ const forms = {
type: 'creatableSelect', type: 'creatableSelect',
label: { label: {
id: getTrad( id: getTrad(
'modalForm.components.create-component.categoryLabel' 'modalForm.components.create-component.category.label'
), ),
}, },
validations: { validations: {
@ -714,6 +743,15 @@ const forms = {
}, },
}, },
], ],
[
{
name: 'icon',
type: 'componentIconPicker',
label: {
id: getTrad('modalForm.components.icon.label'),
},
},
],
]; ];
return { return {

View File

@ -246,5 +246,6 @@
"modalForm.sub-header.chooseAttribute.contentType": "Select a field for your content type", "modalForm.sub-header.chooseAttribute.contentType": "Select a field for your content type",
"modalForm.sub-header.attribute.create": "Add new {type} field", "modalForm.sub-header.attribute.create": "Add new {type} field",
"modalForm.sub-header.attribute.edit": "Edit {name}", "modalForm.sub-header.attribute.edit": "Edit {name}",
"modalForm.components.create-component.categoryLabel": "Select a category or enter a name to create a new one" "modalForm.components.create-component.category.label": "Select a category or enter a name to create a new one",
"modalForm.components.icon.label": "Icon"
} }