mirror of
https://github.com/strapi/strapi.git
synced 2025-10-17 11:08:14 +00:00
Merge pull request #5179 from strapi/single-types/ctb-edit
Edit single type options
This commit is contained in:
commit
7466d64ed8
@ -1,5 +1,4 @@
|
||||
{
|
||||
"kind": "singleType",
|
||||
"connection": "default",
|
||||
"collectionName": "homepage",
|
||||
"info": {
|
||||
@ -7,12 +6,16 @@
|
||||
},
|
||||
"options": {
|
||||
"increments": true,
|
||||
"timestamps": ["created_at", "updated_at"]
|
||||
"timestamps": [
|
||||
"created_at",
|
||||
"updated_at"
|
||||
]
|
||||
},
|
||||
"attributes": {
|
||||
"title": {
|
||||
"type": "string",
|
||||
"required": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"kind": "singleType"
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ const Wrapper = styled(TransitionGroup)`
|
||||
top: 72px;
|
||||
left: 240px;
|
||||
right: 0;
|
||||
z-index: 1000;
|
||||
z-index: 1100;
|
||||
list-style: none;
|
||||
width: 300px;
|
||||
margin: 0 auto;
|
||||
|
@ -256,6 +256,7 @@
|
||||
"components.Input.error.password.noMatch": "Passwords do not match",
|
||||
"form.button.done": "Done",
|
||||
"form.button.finish": "Finish",
|
||||
"notification.contentType.relations.conflict": "Content type has conflicting relations",
|
||||
"notification.form.error.fields": "The form contains some errors",
|
||||
"notification.form.success.fields": "Changes saved",
|
||||
"global.prompt.unsaved": "Are you sure you want to leave this page? All your modifications will be lost"
|
||||
|
@ -14,35 +14,32 @@ const BooleanBox = ({ label, name, onChange, options, value }) => {
|
||||
<Div>
|
||||
<Label htmlFor={name}>{label}</Label>
|
||||
<Wrapper>
|
||||
{options.map(option => {
|
||||
return (
|
||||
<Enumeration
|
||||
key={option.value}
|
||||
id={option.value.toString()}
|
||||
className="option-input"
|
||||
checked={option.value === value}
|
||||
name={name}
|
||||
onChange={onChange}
|
||||
type="radio"
|
||||
value={option.value}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{options.map(option => {
|
||||
return (
|
||||
<EnumerationWrapper
|
||||
className="option"
|
||||
key={option.value}
|
||||
htmlFor={option.value.toString()}
|
||||
>
|
||||
<span className="option__indicator" />
|
||||
<span className="option__title">
|
||||
{formatMessage({ id: option.headerId })}
|
||||
</span>
|
||||
<p>{formatMessage({ id: option.descriptionId })}</p>
|
||||
</EnumerationWrapper>
|
||||
);
|
||||
})}
|
||||
{options.map(option => (
|
||||
<Enumeration
|
||||
{...option}
|
||||
key={option.value}
|
||||
id={option.value.toString()}
|
||||
className="option-input"
|
||||
checked={option.value === value}
|
||||
name={name}
|
||||
onChange={onChange}
|
||||
type="radio"
|
||||
value={option.value}
|
||||
/>
|
||||
))}
|
||||
{options.map(option => (
|
||||
<EnumerationWrapper
|
||||
className="option"
|
||||
key={option.value}
|
||||
htmlFor={option.value.toString()}
|
||||
>
|
||||
<span className="option__indicator" />
|
||||
<span className="option__title">
|
||||
{formatMessage({ id: option.headerId })}
|
||||
</span>
|
||||
<p>{formatMessage({ id: option.descriptionId })}</p>
|
||||
</EnumerationWrapper>
|
||||
))}
|
||||
</Wrapper>
|
||||
</Div>
|
||||
);
|
||||
|
@ -159,7 +159,11 @@ function List({
|
||||
label: isInDevelopmentMode
|
||||
? formatMessage({
|
||||
id: !isSub
|
||||
? `${pluginId}.form.button.add.field.to.${editTarget}`
|
||||
? `${pluginId}.form.button.add.field.to.${
|
||||
modifiedData.contentType
|
||||
? modifiedData.contentType.schema.kind
|
||||
: editTarget
|
||||
}`
|
||||
: `${pluginId}.form.button.add.field.to.component`,
|
||||
})
|
||||
: null,
|
||||
|
@ -399,7 +399,7 @@ const DataManagerProvider = ({ allIcons, children }) => {
|
||||
return <Redirect to={`/plugins/${pluginId}/content-types/${firstCTUid}`} />;
|
||||
}
|
||||
|
||||
const submitData = async () => {
|
||||
const submitData = async additionalContentTypeData => {
|
||||
try {
|
||||
const isCreating = get(
|
||||
modifiedData,
|
||||
@ -416,7 +416,10 @@ const DataManagerProvider = ({ allIcons, children }) => {
|
||||
};
|
||||
|
||||
if (isInContentTypeView) {
|
||||
body.contentType = formatMainDataType(modifiedData.contentType);
|
||||
body.contentType = {
|
||||
...formatMainDataType(modifiedData.contentType),
|
||||
...additionalContentTypeData,
|
||||
};
|
||||
|
||||
emitEvent('willSaveContentType');
|
||||
} else {
|
||||
|
@ -478,7 +478,7 @@ const reducer = (state, action) => {
|
||||
}
|
||||
case 'UPDATE_SCHEMA': {
|
||||
const {
|
||||
data: { name, collectionName, category, icon },
|
||||
data: { name, collectionName, category, icon, kind },
|
||||
schemaType,
|
||||
uid,
|
||||
} = action;
|
||||
@ -493,6 +493,9 @@ const reducer = (state, action) => {
|
||||
.update('category', () => category)
|
||||
.updateIn(['schema', 'icon'], () => icon);
|
||||
}
|
||||
if (action.schemaType === 'contentType') {
|
||||
updatedObj = updatedObj.updateIn(['schema', 'kind'], () => kind);
|
||||
}
|
||||
|
||||
return updatedObj;
|
||||
});
|
||||
|
@ -40,6 +40,7 @@ import { NAVLINKS, INITIAL_STATE_DATA } from './utils/staticData';
|
||||
import init from './init';
|
||||
import reducer, { initialState } from './reducer';
|
||||
import CustomButton from './CustomButton';
|
||||
import canEditContentType from './utils/canEditContentType';
|
||||
|
||||
/* eslint-disable indent */
|
||||
/* eslint-disable react/no-array-index-key */
|
||||
@ -63,6 +64,7 @@ const FormModal = () => {
|
||||
deleteCategory,
|
||||
deleteData,
|
||||
editCategory,
|
||||
submitData,
|
||||
modifiedData: allDataSchema,
|
||||
nestedComponents,
|
||||
setModifiedData,
|
||||
@ -125,6 +127,7 @@ const FormModal = () => {
|
||||
actionType,
|
||||
attributeName,
|
||||
attributeType,
|
||||
contentTypeKind,
|
||||
dynamicZoneTarget,
|
||||
forTarget,
|
||||
modalType,
|
||||
@ -158,7 +161,6 @@ const FormModal = () => {
|
||||
header_info_name_5,
|
||||
header_info_category_5,
|
||||
headerId,
|
||||
contentTypeKind,
|
||||
});
|
||||
|
||||
// Reset all the modification when opening the edit category modal
|
||||
@ -217,7 +219,7 @@ const FormModal = () => {
|
||||
state.modalType !== 'contentType' &&
|
||||
actionType === 'edit'
|
||||
) {
|
||||
const { name, collectionName } = get(
|
||||
const { name, collectionName, kind } = get(
|
||||
allDataSchema,
|
||||
[...pathToSchema, 'schema'],
|
||||
{ name: null, collectionName: null }
|
||||
@ -228,6 +230,7 @@ const FormModal = () => {
|
||||
data: {
|
||||
name,
|
||||
collectionName,
|
||||
kind,
|
||||
},
|
||||
});
|
||||
}
|
||||
@ -601,9 +604,14 @@ const FormModal = () => {
|
||||
uid
|
||||
);
|
||||
} else {
|
||||
updateSchema(modifiedData, state.modalType);
|
||||
// Close the modal
|
||||
push({ search: '' });
|
||||
if (canEditContentType(allDataSchema, modifiedData)) {
|
||||
push({ search: '' });
|
||||
submitData(modifiedData);
|
||||
} else {
|
||||
strapi.notification.error(
|
||||
'notification.contentType.relations.conflict'
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
@ -1346,13 +1354,13 @@ const FormModal = () => {
|
||||
</ModalForm>
|
||||
{!isPickingAttribute && (
|
||||
<ModalFooter>
|
||||
<section>
|
||||
<section style={{ alignItems: 'center' }}>
|
||||
<Button type="button" color="cancel" onClick={handleToggle}>
|
||||
{formatMessage({
|
||||
id: 'components.popUpWarning.button.cancel',
|
||||
})}
|
||||
</Button>
|
||||
<div style={{ margin: 'auto 0' }}>
|
||||
<div>
|
||||
{isCreatingAttribute && !isInFirstComponentStep && (
|
||||
<Button
|
||||
type={isCreating ? 'button' : 'submit'}
|
||||
|
@ -0,0 +1,23 @@
|
||||
import { get } from 'lodash';
|
||||
|
||||
const canEditContentType = (data, modifiedData) => {
|
||||
const kind = get(data, ['contentType', 'schema', 'kind'], '');
|
||||
|
||||
// if kind isn't modified or content type is a single type, there is no need to check attributes.
|
||||
if (kind === 'singleType' || kind === modifiedData.kind) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const contentTypeAttributes = get(
|
||||
data,
|
||||
['contentType', 'schema', 'attributes'],
|
||||
''
|
||||
);
|
||||
const relationAttributes = Object.values(contentTypeAttributes).filter(
|
||||
({ nature }) => nature && !['oneWay', 'manyWay'].includes(nature)
|
||||
);
|
||||
|
||||
return relationAttributes.length === 0;
|
||||
};
|
||||
|
||||
export default canEditContentType;
|
@ -766,6 +766,7 @@ const forms = {
|
||||
.isAllowed(getTrad('error.contentTypeName.reserved-name'))
|
||||
.required(errorsTrads.required),
|
||||
collectionName: yup.string(),
|
||||
kind: yup.string().oneOf(['singleType', 'collectionType']),
|
||||
});
|
||||
},
|
||||
form: {
|
||||
@ -800,6 +801,32 @@ const forms = {
|
||||
});
|
||||
}
|
||||
|
||||
if (actionType === 'edit') {
|
||||
items[0].push({
|
||||
label: {
|
||||
id: getTrad('modalForm.attribute.text.type-selection'),
|
||||
},
|
||||
name: 'kind',
|
||||
type: 'booleanBox',
|
||||
size: 12,
|
||||
options: [
|
||||
{
|
||||
headerId: getTrad('menu.section.models.name.singular'),
|
||||
descriptionId: getTrad(
|
||||
'form.button.collection-type.description'
|
||||
),
|
||||
value: 'collectionType',
|
||||
},
|
||||
{
|
||||
headerId: getTrad('menu.section.single-types.name.singular'),
|
||||
descriptionId: getTrad('form.button.single-type.description'),
|
||||
value: 'singleType',
|
||||
},
|
||||
],
|
||||
validations: {},
|
||||
});
|
||||
}
|
||||
|
||||
return { items };
|
||||
},
|
||||
advanced() {
|
||||
|
@ -0,0 +1,44 @@
|
||||
import canEditContentType from '../canEditContentType';
|
||||
import rawData from './rawData';
|
||||
|
||||
describe('canEditContentType', () => {
|
||||
it('should allow content type edition if one of attributes is a oneWay or manyWay relation', () => {
|
||||
const { postContentType } = rawData;
|
||||
|
||||
expect(
|
||||
canEditContentType(postContentType, {
|
||||
kind: 'singleType',
|
||||
})
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should not allow content type edition if one of attributes is not oneWay or manyWay relation', () => {
|
||||
const { articleContentType } = rawData;
|
||||
|
||||
expect(
|
||||
canEditContentType(articleContentType, {
|
||||
kind: 'singleType',
|
||||
})
|
||||
).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should always allow content type edition if content type is a single type', () => {
|
||||
const { homeSingleType } = rawData;
|
||||
|
||||
expect(
|
||||
canEditContentType(homeSingleType, {
|
||||
kind: 'collectionType',
|
||||
})
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should always allow content type edition if the kind is not modified', () => {
|
||||
const { articleContentType } = rawData;
|
||||
|
||||
expect(
|
||||
canEditContentType(articleContentType, {
|
||||
kind: 'collectionType',
|
||||
})
|
||||
).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,43 @@
|
||||
const data = {
|
||||
homeSingleType: {
|
||||
contentType: {
|
||||
uid: 'plugins::myplugins.home',
|
||||
schema: {
|
||||
name: 'plugins::myplugins.home',
|
||||
kind: 'singleType',
|
||||
attributes: {
|
||||
category: { nature: 'oneWay' },
|
||||
address: { type: 'string' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
articleContentType: {
|
||||
contentType: {
|
||||
uid: 'plugins::myplugins.article',
|
||||
schema: {
|
||||
name: 'plugins::myplugins.article',
|
||||
kind: 'collectionType',
|
||||
attributes: {
|
||||
user: { nature: 'manyToOne' },
|
||||
title: { type: 'string' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
postContentType: {
|
||||
contentType: {
|
||||
uid: 'plugins::myplugins.post',
|
||||
schema: {
|
||||
name: 'plugins::myplugins.post',
|
||||
kind: 'collectionType',
|
||||
attributes: {
|
||||
user: { nature: 'manyWay' },
|
||||
title: { type: 'string' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default data;
|
@ -181,6 +181,7 @@ const ListView = () => {
|
||||
return new Promise(resolve => setTimeout(resolve, 100));
|
||||
};
|
||||
const label = get(modifiedData, [firstMainDataPath, 'schema', 'name'], '');
|
||||
const kind = get(modifiedData, [firstMainDataPath, 'schema', 'kind'], '');
|
||||
|
||||
const headerProps = {
|
||||
actions: isInDevelopmentMode
|
||||
@ -217,9 +218,14 @@ const ListView = () => {
|
||||
onClick: async () => {
|
||||
await wait();
|
||||
|
||||
if (firstMainDataPath === 'contentType') {
|
||||
const contentType = kind || firstMainDataPath;
|
||||
|
||||
if (contentType === 'collectionType') {
|
||||
emitEvent('willEditNameOfContentType');
|
||||
}
|
||||
if (contentType === 'singleType') {
|
||||
emitEvent('willEditNameOfSingleType');
|
||||
}
|
||||
|
||||
push({
|
||||
search: makeSearch({
|
||||
@ -230,7 +236,10 @@ const ListView = () => {
|
||||
targetUid,
|
||||
header_label_1: label,
|
||||
header_icon_isCustom_1: false,
|
||||
header_icon_name_1: firstMainDataPath,
|
||||
header_icon_name_1:
|
||||
contentType === 'singleType'
|
||||
? contentType
|
||||
: firstMainDataPath,
|
||||
headerId: getTrad('modalForm.header-edit'),
|
||||
}),
|
||||
});
|
||||
|
@ -33,8 +33,8 @@
|
||||
"button.attributes.add.another": "Add another field",
|
||||
"button.component.add": "Add a component",
|
||||
"button.component.create": "Create new component",
|
||||
"button.model.create": "Create new collection-type",
|
||||
"button.single-types.create": "Create new single-type",
|
||||
"button.model.create": "Create new collection type",
|
||||
"button.single-types.create": "Create new single type",
|
||||
"component.repeatable": "(repeatable)",
|
||||
"components.componentSelect.no-component-available.with-search": "There is no component matching your search",
|
||||
"components.componentSelect.no-component-available": "You have already added all your components",
|
||||
@ -95,7 +95,8 @@
|
||||
"form.button.add-field": "Add another field",
|
||||
"form.button.add-first-field-to-created-component": "Add first field to the component",
|
||||
"form.button.add.field.to.component": "Add another field to this component",
|
||||
"form.button.add.field.to.contentType": "Add another field to this collection-type",
|
||||
"form.button.add.field.to.collectionType": "Add another field to this collection type",
|
||||
"form.button.add.field.to.singleType": "Add another field to this single type",
|
||||
"form.button.cancel": "Cancel",
|
||||
"form.button.configure-component": "Configure the component",
|
||||
"form.button.configure-view": "Configure the view",
|
||||
@ -104,6 +105,8 @@
|
||||
"form.button.finish": "Finish",
|
||||
"form.button.save": "Save",
|
||||
"form.button.select-component": "Select a component",
|
||||
"form.button.collection-type.description": "Best for multiple instances like articles, products, comments, etc.",
|
||||
"form.button.single-type.description": "Best for single instance like about us, homepage, etc.",
|
||||
"from": "from",
|
||||
"injected-components.content-manager.edit-settings-view.link.components": "Edit the component",
|
||||
"injected-components.content-manager.edit-settings-view.link.content-types": "Edit the collection type",
|
||||
|
@ -34,16 +34,19 @@
|
||||
"form.attribute.item.uniqueField.description": "Vous ne pourrez pas créer une entrée s'il existe un champ similaire",
|
||||
"form.attribute.settings.default": "Valeur par défault",
|
||||
"form.button.add.field.to.component": "Ajouter un nouveau champ à ce composant",
|
||||
"form.button.add.field.to.contentType": "Ajouter un nouveau champ à cette Collection",
|
||||
"form.button.add.field.to.collectionType": "Ajouter un nouveau champ à cette collection",
|
||||
"form.button.add.field.to.singleType": "Ajouter un nouveau champ à ce single type",
|
||||
"form.button.cancel": "Annuler",
|
||||
"form.button.configure-view": "Configurer la vue",
|
||||
"form.button.continue": "Continuer",
|
||||
"form.button.save": "Sauvegarder",
|
||||
"form.button.finish": "Terminer",
|
||||
"form.button.delete": "Supprimer",
|
||||
"from": "de",
|
||||
"menu.section.components.name.plural": "Composants",
|
||||
"menu.section.components.name.singular": "Composant",
|
||||
"menu.section.models.name.plural": "Modèles",
|
||||
"menu.section.models.name.singular": "Modèle",
|
||||
"menu.section.models.name.plural": "Collections",
|
||||
"menu.section.models.name.singular": "Collection",
|
||||
"menu.section.single-types.name.plural": "Single Types",
|
||||
"menu.section.single-types.name.singular": "Single Type",
|
||||
"modalForm.singleType.header-create": "Créer un single type",
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"Analytics": "Analytics",
|
||||
"Content Manager": "Content Manager",
|
||||
"Content Type Builder": "Types Builder",
|
||||
"Content Type Builder": " Content Types Builder",
|
||||
"Email": "Email",
|
||||
"Files Upload": "Files Upload",
|
||||
"HomePage.notification.newsLetter.success": "Successfully subscribed to the newsletter",
|
||||
|
Loading…
x
Reference in New Issue
Block a user