
diff --git a/packages/strapi-plugin-content-type-builder/admin/src/containers/App/actions.js b/packages/strapi-plugin-content-type-builder/admin/src/containers/App/actions.js
index be8e5230a2..052e48d9e4 100644
--- a/packages/strapi-plugin-content-type-builder/admin/src/containers/App/actions.js
+++ b/packages/strapi-plugin-content-type-builder/admin/src/containers/App/actions.js
@@ -23,6 +23,8 @@ import {
RESET_EXISTING_CONTENT_TYPE_MAIN_INFOS,
RESET_EDIT_TEMP_CONTENT_TYPE,
RESET_PROPS,
+ SAVE_EDITED_ATTRIBUTE,
+ SET_TEMPORARY_ATTRIBUTE,
SUBMIT_TEMP_CONTENT_TYPE,
SUBMIT_TEMP_CONTENT_TYPE_SUCCEEDED,
UPDATE_TEMP_CONTENT_TYPE,
@@ -140,6 +142,24 @@ export function onCreateAttribute({ target }) {
};
}
+export function saveEditedAttribute(attributeName, isModelTemporary, modelName) {
+ return {
+ type: SAVE_EDITED_ATTRIBUTE,
+ attributeName,
+ isModelTemporary,
+ modelName,
+ };
+}
+
+export function setTemporaryAttribute(attributeName, isModelTemporary, modelName) {
+ return {
+ type: SET_TEMPORARY_ATTRIBUTE,
+ attributeName,
+ isModelTemporary,
+ modelName,
+ };
+}
+
export function resetNewContentTypeMainInfos() {
return {
type: RESET_NEW_CONTENT_TYPE_MAIN_INFOS,
diff --git a/packages/strapi-plugin-content-type-builder/admin/src/containers/App/constants.js b/packages/strapi-plugin-content-type-builder/admin/src/containers/App/constants.js
index 652ec282e4..ef47c7cdaf 100644
--- a/packages/strapi-plugin-content-type-builder/admin/src/containers/App/constants.js
+++ b/packages/strapi-plugin-content-type-builder/admin/src/containers/App/constants.js
@@ -28,6 +28,8 @@ export const RESET_EXISTING_CONTENT_TYPE_MAIN_INFOS =
'ContentTypeBuilder/App/RESET_EXISTING_CONTENT_TYPE_MAIN_INFOS';
export const RESET_EDIT_TEMP_CONTENT_TYPE = 'ContentTypeBuilder/App/RESET_EDIT_TEMP_CONTENT_TYPE';
export const RESET_PROPS = 'ContentTypeBuilder/App/RESET_PROPS';
+export const SAVE_EDITED_ATTRIBUTE = 'ContentTypeBuilder/App/SAVE_EDITED_ATTRIBUTE';
+export const SET_TEMPORARY_ATTRIBUTE = 'ContentTypeBuilder/App/SET_TEMPORARY_ATTRIBUTE';
export const SUBMIT_TEMP_CONTENT_TYPE = 'ContentTypeBuilder/App/SUBMIT_TEMP_CONTENT_TYPE';
export const SUBMIT_TEMP_CONTENT_TYPE_SUCCEEDED = 'ContentTypeBuilder/App/SUBMIT_TEMP_CONTENT_TYPE_SUCCEEDED';
export const UPDATE_TEMP_CONTENT_TYPE = 'ContentTypeBuilder/App/UPDATE_TEMP_CONTENT_TYPE';
diff --git a/packages/strapi-plugin-content-type-builder/admin/src/containers/App/index.js b/packages/strapi-plugin-content-type-builder/admin/src/containers/App/index.js
index be9db2fbe3..6059badcdb 100644
--- a/packages/strapi-plugin-content-type-builder/admin/src/containers/App/index.js
+++ b/packages/strapi-plugin-content-type-builder/admin/src/containers/App/index.js
@@ -29,6 +29,8 @@ import {
resetExistingContentTypeMainInfos,
resetNewContentTypeMainInfos,
resetProps,
+ saveEditedAttribute,
+ setTemporaryAttribute,
updateTempContentType,
} from './actions';
@@ -106,6 +108,7 @@ App.propTypes = {
onChangeExistingContentTypeMainInfos: PropTypes.func.isRequired,
onChangeNewContentTypeMainInfos: PropTypes.func.isRequired,
resetProps: PropTypes.func.isRequired,
+ saveEditedAttribute: PropTypes.func.isRequired,
};
const mapStateToProps = makeSelectApp();
@@ -123,6 +126,8 @@ export function mapDispatchToProps(dispatch) {
resetExistingContentTypeMainInfos,
resetNewContentTypeMainInfos,
resetProps,
+ saveEditedAttribute,
+ setTemporaryAttribute,
updateTempContentType,
},
dispatch,
diff --git a/packages/strapi-plugin-content-type-builder/admin/src/containers/App/reducer.js b/packages/strapi-plugin-content-type-builder/admin/src/containers/App/reducer.js
index 485ae0a54d..01ffd7afd5 100644
--- a/packages/strapi-plugin-content-type-builder/admin/src/containers/App/reducer.js
+++ b/packages/strapi-plugin-content-type-builder/admin/src/containers/App/reducer.js
@@ -24,6 +24,8 @@ import {
RESET_EDIT_EXISTING_CONTENT_TYPE,
RESET_EDIT_TEMP_CONTENT_TYPE,
RESET_PROPS,
+ SAVE_EDITED_ATTRIBUTE,
+ SET_TEMPORARY_ATTRIBUTE,
SUBMIT_TEMP_CONTENT_TYPE_SUCCEEDED,
UPDATE_TEMP_CONTENT_TYPE,
} from './constants';
@@ -158,6 +160,25 @@ function appReducer(state = initialState, action) {
});
case RESET_PROPS:
return initialState;
+ case SAVE_EDITED_ATTRIBUTE: {
+ const basePath = action.isModelTemporary ? ['newContentType'] : ['modifiedData', action.modelName];
+
+ return state.updateIn([...basePath, 'attributes'], attributes => {
+ const temporaryAttribute = state.get('temporaryAttribute');
+ const newAttribute = temporaryAttribute.remove('name');
+
+ return attributes.remove(action.attributeName).set(temporaryAttribute.get('name'), newAttribute);
+ });
+ }
+ case SET_TEMPORARY_ATTRIBUTE:
+ return state.update('temporaryAttribute', () => {
+ const basePath = action.isModelTemporary ? ['newContentType'] : ['modifiedData', action.modelName];
+ const attribute = state
+ .getIn([...basePath, 'attributes', action.attributeName])
+ .set('name', action.attributeName);
+
+ return attribute;
+ });
case SUBMIT_TEMP_CONTENT_TYPE_SUCCEEDED:
return state
.updateIn(['initialData', state.getIn(['newContentType', 'name'])], () => state.get('newContentType'))
diff --git a/packages/strapi-plugin-content-type-builder/admin/src/containers/App/tests/index.test.js b/packages/strapi-plugin-content-type-builder/admin/src/containers/App/tests/index.test.js
index 2b936ad33a..684646bce7 100644
--- a/packages/strapi-plugin-content-type-builder/admin/src/containers/App/tests/index.test.js
+++ b/packages/strapi-plugin-content-type-builder/admin/src/containers/App/tests/index.test.js
@@ -46,6 +46,7 @@ describe('
', () => {
modifiedData: {},
onChangeExistingContentTypeMainInfos: jest.fn(),
onChangeNewContentTypeMainInfos: jest.fn(),
+ saveEditedAttribute: jest.fn(),
resetProps: jest.fn(),
};
});
diff --git a/packages/strapi-plugin-content-type-builder/admin/src/containers/AttributeForm/index.js b/packages/strapi-plugin-content-type-builder/admin/src/containers/AttributeForm/index.js
index 2eaddd41a2..5bd4ad6a29 100644
--- a/packages/strapi-plugin-content-type-builder/admin/src/containers/AttributeForm/index.js
+++ b/packages/strapi-plugin-content-type-builder/admin/src/containers/AttributeForm/index.js
@@ -40,15 +40,17 @@ class AttributeForm extends React.Component {
};
getFormErrors = () => {
- const { alreadyTakenAttributes, modifiedData } = this.props;
+ const { alreadyTakenAttributes, attributeToEditName, modifiedData } = this.props;
const currentForm = this.getCurrentForm();
let formErrors = {};
-
+ const alreadyTakenAttributesUpdated = alreadyTakenAttributes.filter(
+ attribute => attribute !== attributeToEditName,
+ );
if (isEmpty(modifiedData.name)) {
formErrors = { name: [{ id: `${pluginId}.error.validation.required` }] };
}
- if (alreadyTakenAttributes.includes(get(modifiedData, 'name', ''))) {
+ if (alreadyTakenAttributesUpdated.includes(get(modifiedData, 'name', ''))) {
formErrors = { name: [{ id: `${pluginId}.error.attribute.taken` }] };
}
@@ -81,10 +83,10 @@ class AttributeForm extends React.Component {
};
handleGoTo = to => {
- const { attributeType, push } = this.props;
+ const { actionType, attributeType, push } = this.props;
push({
- search: `modalType=attributeForm&attributeType=${attributeType}&settingType=${to}&actionType=create`,
+ search: `modalType=attributeForm&attributeType=${attributeType}&settingType=${to}&actionType=${actionType}`,
});
};
@@ -99,7 +101,11 @@ class AttributeForm extends React.Component {
handleSubmit = () => {
if (isEmpty(this.getFormErrors())) {
- this.props.onSubmit();
+ if (this.props.actionType === 'create') {
+ this.props.onSubmit();
+ } else {
+ this.props.onSubmitEdit();
+ }
}
};
@@ -107,7 +113,11 @@ class AttributeForm extends React.Component {
e.preventDefault();
if (isEmpty(this.getFormErrors())) {
- this.props.onSubmit(true);
+ if (this.props.actionType === 'create') {
+ this.props.onSubmit(true);
+ } else {
+ this.props.onSubmitEdit(true);
+ }
}
};
@@ -166,9 +176,10 @@ class AttributeForm extends React.Component {
};
render() {
- const { attributeType, isOpen } = this.props;
+ const { actionType, attributeToEditName, attributeType, isOpen } = this.props;
const { showForm } = this.state;
const currentForm = this.getCurrentForm();
+ const titleContent = actionType === 'create' ? attributeType : attributeToEditName;
return (
-
+
- {attributeType}
+ {titleContent}
@@ -205,7 +216,9 @@ class AttributeForm extends React.Component {
}
AttributeForm.defaultProps = {
+ actionType: 'create',
activeTab: 'base',
+ attributeToEditName: '',
alreadyTakenAttributes: [],
attributeType: 'string',
isOpen: false,
@@ -216,14 +229,17 @@ AttributeForm.defaultProps = {
};
AttributeForm.propTypes = {
+ actionType: PropTypes.string,
activeTab: PropTypes.string,
alreadyTakenAttributes: PropTypes.array,
+ attributeToEditName: PropTypes.string,
attributeType: PropTypes.string,
isOpen: PropTypes.bool,
modifiedData: PropTypes.object, // TODO: Clearly define this object (It's working without it though)
onCancel: PropTypes.func,
onChange: PropTypes.func,
onSubmit: PropTypes.func.isRequired,
+ onSubmitEdit: PropTypes.func.isRequired,
push: PropTypes.func,
};
diff --git a/packages/strapi-plugin-content-type-builder/admin/src/containers/AttributeForm/tests/index.test.js b/packages/strapi-plugin-content-type-builder/admin/src/containers/AttributeForm/tests/index.test.js
index 3358cb6338..3205bec326 100644
--- a/packages/strapi-plugin-content-type-builder/admin/src/containers/AttributeForm/tests/index.test.js
+++ b/packages/strapi-plugin-content-type-builder/admin/src/containers/AttributeForm/tests/index.test.js
@@ -19,6 +19,7 @@ describe('', () => {
beforeEach(() => {
props = {
onSubmit: jest.fn(),
+ onSubmitEdit: jest.fn(),
};
});
diff --git a/packages/strapi-plugin-content-type-builder/admin/src/containers/AttributesPickerModal/index.js b/packages/strapi-plugin-content-type-builder/admin/src/containers/AttributesPickerModal/index.js
index 5a8bf225c4..13fc71bd13 100644
--- a/packages/strapi-plugin-content-type-builder/admin/src/containers/AttributesPickerModal/index.js
+++ b/packages/strapi-plugin-content-type-builder/admin/src/containers/AttributesPickerModal/index.js
@@ -19,7 +19,8 @@ import WrapperModal from '../../components/WrapperModal';
import attributes from './attributes.json';
-class AttributesPickerModal extends React.Component { // eslint-disable-line react/prefer-stateless-function
+class AttributesPickerModal extends React.Component {
+ // eslint-disable-line react/prefer-stateless-function
state = { isDisplayed: false, nodeToFocus: 0 };
componentDidMount() {
@@ -55,24 +56,24 @@ class AttributesPickerModal extends React.Component { // eslint-disable-line rea
return attr.type !== 'media';
});
- }
+ };
addEventListener = () => {
document.addEventListener('keydown', this.handleKeyDown);
- }
+ };
removeEventListener = () => {
document.removeEventListener('keydown', this.handleKeyDown);
- }
+ };
- handleClick = (type) => {
+ handleClick = type => {
const { push } = this.props;
- push({ search: `modalType=attributeForm&attributeType=${type}&settingType=base` });
- }
+ push({ search: `modalType=attributeForm&attributeType=${type}&settingType=base&actionType=create` });
+ };
/* istanbul ignore next */
- handleKeyDown = (e) => {
+ handleKeyDown = e => {
const { push } = this.props;
/* istanbul ignore next */
@@ -101,7 +102,9 @@ class AttributesPickerModal extends React.Component { // eslint-disable-line rea
e.preventDefault();
push({
- search: `modalType=attributeForm&attributeType=${attributes[nodeToFocus].type}&settingType=base`,
+ search: `modalType=attributeForm&attributeType=${
+ attributes[nodeToFocus].type
+ }&settingType=base&actionType=create`,
});
break;
default:
@@ -110,7 +113,7 @@ class AttributesPickerModal extends React.Component { // eslint-disable-line rea
/* istanbul ignore next */
this.updateNodeToFocus(next);
- }
+ };
handleOnClosed = () => this.setState(prevState => ({ isDisplayed: !prevState.isDisplayed }));
@@ -120,7 +123,7 @@ class AttributesPickerModal extends React.Component { // eslint-disable-line rea
const { push } = this.props;
push({ search: '' });
- }
+ };
updateNodeToFocus = position => this.setState({ nodeToFocus: position });
@@ -138,7 +141,7 @@ class AttributesPickerModal extends React.Component { // eslint-disable-line rea
{...attribute}
/>
);
- }
+ };
render() {
const { isOpen } = this.props;
@@ -151,13 +154,9 @@ class AttributesPickerModal extends React.Component { // eslint-disable-line rea
onOpened={this.handleOnOpened}
>
-
+
-
- {attributes.map(this.renderAttribute)}
-
+ {attributes.map(this.renderAttribute)}
);
diff --git a/packages/strapi-plugin-content-type-builder/admin/src/containers/AttributesPickerModal/tests/index.test.js b/packages/strapi-plugin-content-type-builder/admin/src/containers/AttributesPickerModal/tests/index.test.js
index bad0b00d23..e08ad31180 100644
--- a/packages/strapi-plugin-content-type-builder/admin/src/containers/AttributesPickerModal/tests/index.test.js
+++ b/packages/strapi-plugin-content-type-builder/admin/src/containers/AttributesPickerModal/tests/index.test.js
@@ -11,7 +11,8 @@ import AttributesPickerModal from '../index';
const messages = formatMessagesWithPluginId(pluginId, pluginTradsEn);
-const renderComponent = (props = {}, context = {}) => mountWithIntl(
, messages, context);
+const renderComponent = (props = {}, context = {}) =>
+ mountWithIntl(
, messages, context);
describe('
', () => {
let props;
@@ -80,8 +81,8 @@ describe('
', () => {
const { getAttributes } = wrapper.instance();
expect(getAttributes()).not.toContain({
- "type": "media",
- "description": "content-type-builder.popUpForm.attributes.media.description",
+ type: 'media',
+ description: 'content-type-builder.popUpForm.attributes.media.description',
});
});
@@ -89,15 +90,15 @@ describe('
', () => {
const context = {
plugins: {
'content-type-builder': {},
- 'upload': {},
+ upload: {},
},
};
const wrapper = renderComponent(props, context);
const { getAttributes } = wrapper.instance();
expect(getAttributes()).toContainEqual({
- "type": "media",
- "description": "content-type-builder.popUpForm.attributes.media.description",
+ type: 'media',
+ description: 'content-type-builder.popUpForm.attributes.media.description',
});
wrapper.unmount();
@@ -107,7 +108,7 @@ describe('
', () => {
const context = {
plugins: fromJS({
'content-type-builder': {},
- 'upload': {},
+ upload: {},
}),
};
const wrapper = renderComponent(props, context);
@@ -154,7 +155,7 @@ describe('
', () => {
const context = {
plugins: fromJS({
'content-type-builder': {},
- 'upload': {},
+ upload: {},
}),
};
const wrapper = renderComponent(props, context);
@@ -175,7 +176,9 @@ describe('
', () => {
handleClick('test');
- expect(props.push).toHaveBeenCalledWith({ search: 'modalType=attributeForm&attributeType=test&settingType=base' });
+ expect(props.push).toHaveBeenCalledWith({
+ search: 'modalType=attributeForm&attributeType=test&settingType=base&actionType=create',
+ });
});
});
});
diff --git a/packages/strapi-plugin-content-type-builder/admin/src/containers/ModelPage/index.js b/packages/strapi-plugin-content-type-builder/admin/src/containers/ModelPage/index.js
index 39a1b39576..9e02eaae21 100644
--- a/packages/strapi-plugin-content-type-builder/admin/src/containers/ModelPage/index.js
+++ b/packages/strapi-plugin-content-type-builder/admin/src/containers/ModelPage/index.js
@@ -62,6 +62,22 @@ export class ModelPage extends React.Component {
// eslint-disable-line react/prefer-stateless-function
state = { attrToDelete: null, removePrompt: false, showWarning: false };
+ componentDidMount() {
+ const { setTemporaryAttribute } = this.props;
+
+ if (
+ this.getModalType() === 'attributeForm' &&
+ this.getActionType() === 'edit' &&
+ !this.isTryingToEditAnUnknownAttribute()
+ ) {
+ setTemporaryAttribute(
+ this.getAttributeName(),
+ this.isUpdatingTemporaryContentType(),
+ this.getModelName(),
+ );
+ }
+ }
+
componentDidUpdate(prevProps) {
const {
location: { search },
@@ -80,20 +96,22 @@ export class ModelPage extends React.Component {
}
}
- getFormData = () => {
- const {
- location: { search },
- modifiedData,
- newContentType,
- } = this.props;
+ getActionType = () => getQueryParameters(this.getSearch(), 'actionType');
- if (getQueryParameters(search, 'actionType') === 'create' || this.isUpdatingTemporaryContentType()) {
+ getAttributeName = () => getQueryParameters(this.getSearch(), 'attributeName');
+
+ getFormData = () => {
+ const { modifiedData, newContentType } = this.props;
+
+ if (this.getActionType() === 'create' || this.isUpdatingTemporaryContentType()) {
return newContentType;
}
return get(modifiedData, this.getModelName());
};
+ getModalType = () => getQueryParameters(this.getSearch(), 'modalType');
+
getModel = () => {
const { modifiedData, newContentType } = this.props;
@@ -194,14 +212,41 @@ export class ModelPage extends React.Component {
return title;
};
+ getSearch = () => {
+ const {
+ location: { search },
+ } = this.props;
+
+ return search;
+ };
+
getSectionTitle = () => {
const base = `${pluginId}.menu.section.contentTypeBuilder.name.`;
return this.getModelsNumber() > 1 ? `${base}plural` : `${base}singular`;
};
+ handleClickEditAttribute = async (attributeName, type) => {
+ // modalType=attributeForm&attributeType=boolean&settingType=base
+ const {
+ history: { push },
+ } = this.props;
+ const attributeType = ['integer', 'biginteger', 'float', 'decimal'].includes(type) ? 'number' : type;
+
+ this.props.setTemporaryAttribute(
+ attributeName,
+ this.isUpdatingTemporaryContentType(),
+ this.getModelName(),
+ );
+ await this.wait();
+ push({
+ search: `modalType=attributeForm&attributeType=${attributeType}&settingType=base&actionType=edit&attributeName=${attributeName}`,
+ });
+ };
+
handleClickEditModelMainInfos = async () => {
const { canOpenModal } = this.props;
+
await this.wait();
if (canOpenModal || this.isUpdatingTemporaryContentType()) {
@@ -284,15 +329,25 @@ export class ModelPage extends React.Component {
push({ search: nextSearch });
};
- hasModelBeenModified = () => {
+ handleSubmitEdit = (shouldContinue = false) => {
const {
- initialData,
- location: { search },
- modifiedData,
+ history: { push },
+ saveEditedAttribute,
} = this.props;
+ const attributeName = this.getAttributeName();
+
+ saveEditedAttribute(attributeName, this.isUpdatingTemporaryContentType(), this.getModelName());
+
+ const nextSearch = shouldContinue ? 'modalType=chooseAttributes' : '';
+
+ push({ search: nextSearch });
+ };
+
+ hasModelBeenModified = () => {
+ const { initialData, modifiedData } = this.props;
const currentModel = this.getModelName();
- return !isEqual(initialData[currentModel], modifiedData[currentModel]) && search === '';
+ return !isEqual(initialData[currentModel], modifiedData[currentModel]) && this.getSearch() === '';
};
isUpdatingTemporaryContentType = (modelName = this.getModelName()) => {
@@ -307,10 +362,19 @@ export class ModelPage extends React.Component {
setPrompt = () => this.setState({ removePrompt: false });
+ isTryingToEditAnUnknownAttribute = () => {
+ const hasAttribute = Object.keys(this.getModelAttributes()).indexOf(this.getAttributeName()) !== -1;
+
+ return this.getActionType() === 'edit' && this.getModalType() === 'attributeForm' && !hasAttribute;
+ };
+
shouldRedirect = () => {
const { models } = this.props;
- return models.findIndex(model => model.name === this.getModelName()) === -1;
+ return (
+ models.findIndex(model => model.name === this.getModelName()) === -1 ||
+ this.isTryingToEditAnUnknownAttribute()
+ );
};
toggleModalWarning = () => this.setState(prevState => ({ showWarning: !prevState.showWarning }));
@@ -353,6 +417,7 @@ export class ModelPage extends React.Component {
key={attribute}
name={attribute}
attributeInfos={attributeInfos}
+ onClick={this.handleClickEditAttribute}
onClickOnTrashIcon={this.handleClickOnTrashIcon}
/>
);
@@ -385,10 +450,11 @@ export class ModelPage extends React.Component {
return
;
}
- const modalType = getQueryParameters(search, 'modalType');
+ // const modalType = getQueryParameters(search, 'modalType');
+ const modalType = this.getModalType();
const settingType = getQueryParameters(search, 'settingType');
const attributeType = getQueryParameters(search, 'attributeType');
- const actionType = getQueryParameters(search, 'actionType');
+ const actionType = this.getActionType();
return (
@@ -473,15 +539,18 @@ export class ModelPage extends React.Component {