mirror of
https://github.com/strapi/strapi.git
synced 2025-08-21 15:19:22 +00:00
Init content type creation
This commit is contained in:
parent
68ab68d428
commit
c52eb01b49
@ -10,6 +10,7 @@ import { connect } from 'react-redux';
|
|||||||
import { createStructuredSelector } from 'reselect';
|
import { createStructuredSelector } from 'reselect';
|
||||||
import { bindActionCreators, compose } from 'redux';
|
import { bindActionCreators, compose } from 'redux';
|
||||||
import { Switch, Route } from 'react-router-dom';
|
import { Switch, Route } from 'react-router-dom';
|
||||||
|
import { injectIntl } from 'react-intl';
|
||||||
import { isEmpty } from 'lodash';
|
import { isEmpty } from 'lodash';
|
||||||
// Components from strapi-helper-plugin
|
// Components from strapi-helper-plugin
|
||||||
import {
|
import {
|
||||||
@ -145,6 +146,7 @@ export class Admin extends React.Component {
|
|||||||
currentEnvironment={this.props.global.currentEnvironment}
|
currentEnvironment={this.props.global.currentEnvironment}
|
||||||
disableGlobalOverlayBlocker={this.props.disableGlobalOverlayBlocker}
|
disableGlobalOverlayBlocker={this.props.disableGlobalOverlayBlocker}
|
||||||
enableGlobalOverlayBlocker={this.props.enableGlobalOverlayBlocker}
|
enableGlobalOverlayBlocker={this.props.enableGlobalOverlayBlocker}
|
||||||
|
formatMessage={this.props.intl.formatMessage}
|
||||||
plugins={this.props.global.plugins}
|
plugins={this.props.global.plugins}
|
||||||
updatePlugin={this.props.updatePlugin}
|
updatePlugin={this.props.updatePlugin}
|
||||||
>
|
>
|
||||||
@ -212,6 +214,9 @@ Admin.propTypes = {
|
|||||||
showGlobalAppBlocker: PropTypes.bool,
|
showGlobalAppBlocker: PropTypes.bool,
|
||||||
strapiVersion: PropTypes.string,
|
strapiVersion: PropTypes.string,
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
|
intl: PropTypes.shape({
|
||||||
|
formatMessage: PropTypes.func,
|
||||||
|
}),
|
||||||
location: PropTypes.object.isRequired,
|
location: PropTypes.object.isRequired,
|
||||||
setAppError: PropTypes.func.isRequired,
|
setAppError: PropTypes.func.isRequired,
|
||||||
updatePlugin: PropTypes.func.isRequired,
|
updatePlugin: PropTypes.func.isRequired,
|
||||||
@ -243,6 +248,7 @@ const withReducer = injectReducer({ key: 'admin', reducer });
|
|||||||
const withSaga = injectSaga({ key: 'admin', saga });
|
const withSaga = injectSaga({ key: 'admin', saga });
|
||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
|
injectIntl,
|
||||||
withReducer,
|
withReducer,
|
||||||
withSaga,
|
withSaga,
|
||||||
withConnect
|
withConnect
|
||||||
|
@ -128,6 +128,7 @@
|
|||||||
"components.Input.error.validation.minSupMax": "Can't be superior",
|
"components.Input.error.validation.minSupMax": "Can't be superior",
|
||||||
"components.Input.error.validation.regex": "The value not match the regex.",
|
"components.Input.error.validation.regex": "The value not match the regex.",
|
||||||
"components.Input.error.validation.required": "This value is required.",
|
"components.Input.error.validation.required": "This value is required.",
|
||||||
|
"components.Input.error.validation.unique": "This value is already used.",
|
||||||
"components.InputSelect.option.placeholder": "Choose here",
|
"components.InputSelect.option.placeholder": "Choose here",
|
||||||
"components.ListRow.empty": "There is no data to be shown.",
|
"components.ListRow.empty": "There is no data to be shown.",
|
||||||
"components.OverlayBlocker.description": "You're using a feature that needs the server to restart. Please wait until the server is up.",
|
"components.OverlayBlocker.description": "You're using a feature that needs the server to restart. Please wait until the server is up.",
|
||||||
|
@ -7,6 +7,7 @@ const errorsTrads = {
|
|||||||
minLength: 'components.Input.error.validation.minLength',
|
minLength: 'components.Input.error.validation.minLength',
|
||||||
regex: 'components.Input.error.validation.regex',
|
regex: 'components.Input.error.validation.regex',
|
||||||
required: 'components.Input.error.validation.required',
|
required: 'components.Input.error.validation.required',
|
||||||
|
unique: 'components.Input.error.validation.unique',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default errorsTrads;
|
export default errorsTrads;
|
||||||
|
@ -1,18 +1,20 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useReducer, useState } from 'react';
|
||||||
// import PropTypes from 'prop-types';
|
// import PropTypes from 'prop-types';
|
||||||
import { isEmpty } from 'lodash';
|
|
||||||
import {
|
import {
|
||||||
ButtonModal,
|
ButtonModal,
|
||||||
HeaderModal,
|
HeaderModal,
|
||||||
HeaderModalTitle,
|
HeaderModalTitle,
|
||||||
Modal,
|
Modal,
|
||||||
|
ModalBody,
|
||||||
ModalFooter,
|
ModalFooter,
|
||||||
ModalForm,
|
ModalForm,
|
||||||
getYupInnerErrors,
|
getYupInnerErrors,
|
||||||
|
useGlobalContext,
|
||||||
} from 'strapi-helper-plugin';
|
} from 'strapi-helper-plugin';
|
||||||
|
import { Inputs } from '@buffetjs/custom';
|
||||||
import { useHistory, useLocation } from 'react-router-dom';
|
import { useHistory, useLocation } from 'react-router-dom';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
import { get, upperFirst } from 'lodash';
|
import { get, isEmpty, upperFirst } from 'lodash';
|
||||||
import pluginId from '../../pluginId';
|
import pluginId from '../../pluginId';
|
||||||
import useQuery from '../../hooks/useQuery';
|
import useQuery from '../../hooks/useQuery';
|
||||||
import useDataManager from '../../hooks/useDataManager';
|
import useDataManager from '../../hooks/useDataManager';
|
||||||
@ -20,23 +22,28 @@ import ModalHeader from '../../components/ModalHeader';
|
|||||||
import HeaderModalNavContainer from '../../components/HeaderModalNavContainer';
|
import HeaderModalNavContainer from '../../components/HeaderModalNavContainer';
|
||||||
import HeaderNavLink from '../../components/HeaderNavLink';
|
import HeaderNavLink from '../../components/HeaderNavLink';
|
||||||
import forms from './utils/forms';
|
import forms from './utils/forms';
|
||||||
|
import init from './init';
|
||||||
|
import reducer, { initialState } from './reducer';
|
||||||
|
|
||||||
const getTrad = id => `${pluginId}.${id}`;
|
const getTrad = id => `${pluginId}.${id}`;
|
||||||
const NAVLINKS = [{ id: 'base' }, { id: 'advanced' }];
|
const NAVLINKS = [{ id: 'base' }, { id: 'advanced' }];
|
||||||
|
|
||||||
const FormModal = () => {
|
const FormModal = () => {
|
||||||
const initialState = {
|
const initialStateData = {
|
||||||
actionType: null,
|
actionType: null,
|
||||||
modalType: null,
|
modalType: null,
|
||||||
settingType: null,
|
settingType: null,
|
||||||
// uid: null,
|
// uid: null,
|
||||||
};
|
};
|
||||||
const [state, setState] = useState(initialState);
|
const [state, setState] = useState(initialStateData);
|
||||||
|
const [reducerState, dispatch] = useReducer(reducer, initialState, init);
|
||||||
const { push } = useHistory();
|
const { push } = useHistory();
|
||||||
const { search } = useLocation();
|
const { search } = useLocation();
|
||||||
|
const { formatMessage } = useGlobalContext();
|
||||||
const isOpen = !isEmpty(search);
|
const isOpen = !isEmpty(search);
|
||||||
const query = useQuery();
|
const query = useQuery();
|
||||||
const { initialData } = useDataManager();
|
const { contentTypes, initialData } = useDataManager();
|
||||||
|
const { formErrors, modifiedData } = reducerState.toJS();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isOpen) {
|
if (isOpen) {
|
||||||
@ -54,9 +61,6 @@ const FormModal = () => {
|
|||||||
? `modalForm.${state.modalType}.header-create`
|
? `modalForm.${state.modalType}.header-create`
|
||||||
: 'modalForm.header-edit';
|
: 'modalForm.header-edit';
|
||||||
const name = get(initialData, ['schema', 'name'], '');
|
const name = get(initialData, ['schema', 'name'], '');
|
||||||
const onClosed = () => {
|
|
||||||
setState(initialState);
|
|
||||||
};
|
|
||||||
const getNextSearch = nextTab => {
|
const getNextSearch = nextTab => {
|
||||||
const newSearch = Object.keys(state).reduce((acc, current) => {
|
const newSearch = Object.keys(state).reduce((acc, current) => {
|
||||||
if (current !== 'settingType') {
|
if (current !== 'settingType') {
|
||||||
@ -70,22 +74,43 @@ const FormModal = () => {
|
|||||||
|
|
||||||
return newSearch;
|
return newSearch;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleChange = ({ target: { name, value } }) => {
|
||||||
|
dispatch({
|
||||||
|
type: 'ON_CHANGE',
|
||||||
|
keys: name.split('.'),
|
||||||
|
value,
|
||||||
|
});
|
||||||
|
};
|
||||||
const handleSubmit = async e => {
|
const handleSubmit = async e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const schema = forms.contentType.schema(['admin', 'series', 'file']);
|
const schema = forms.contentType.schema(Object.keys(contentTypes));
|
||||||
|
|
||||||
await schema.validate({ name: 'admin' }, { abortEarly: false });
|
await schema.validate(modifiedData, { abortEarly: false });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const errors = getYupInnerErrors(err);
|
const errors = getYupInnerErrors(err);
|
||||||
// TODO
|
// TODO
|
||||||
console.log({ errors });
|
console.log({ errors });
|
||||||
|
dispatch({
|
||||||
|
type: 'SET_ERRORS',
|
||||||
|
errors,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const handleToggle = () => {
|
const handleToggle = () => {
|
||||||
push({ search: '' });
|
push({ search: '' });
|
||||||
};
|
};
|
||||||
|
const onClosed = () => {
|
||||||
|
setState(initialStateData);
|
||||||
|
dispatch({
|
||||||
|
type: 'RESET_PROPS',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const form = get(forms, [state.modalType, 'form', state.settingType], () => ({
|
||||||
|
items: [],
|
||||||
|
}));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal isOpen={isOpen} onClosed={onClosed} onToggle={handleToggle}>
|
<Modal isOpen={isOpen} onClosed={onClosed} onToggle={handleToggle}>
|
||||||
@ -123,7 +148,53 @@ const FormModal = () => {
|
|||||||
</section>
|
</section>
|
||||||
</HeaderModal>
|
</HeaderModal>
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit}>
|
||||||
<ModalForm>{/* <ModalBody>{renderForm()}</ModalBody> */}</ModalForm>
|
<ModalForm>
|
||||||
|
<ModalBody>
|
||||||
|
<div className="container-fluid">
|
||||||
|
{form(modifiedData).items.map((row, index) => {
|
||||||
|
return (
|
||||||
|
<div className="row" key={index}>
|
||||||
|
{row.map(input => {
|
||||||
|
const errorId = get(
|
||||||
|
formErrors,
|
||||||
|
[...input.name.split('.'), 'id'],
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`col-${input.size || 6}`}
|
||||||
|
key={input.name}
|
||||||
|
>
|
||||||
|
<Inputs
|
||||||
|
value={get(modifiedData, input.name, '')}
|
||||||
|
{...input}
|
||||||
|
error={
|
||||||
|
isEmpty(errorId)
|
||||||
|
? null
|
||||||
|
: formatMessage({ id: errorId })
|
||||||
|
}
|
||||||
|
onChange={handleChange}
|
||||||
|
description={
|
||||||
|
get(input, 'description.id', null)
|
||||||
|
? formatMessage(input.description)
|
||||||
|
: input.description
|
||||||
|
}
|
||||||
|
label={
|
||||||
|
get(input, 'label.id', null)
|
||||||
|
? formatMessage(input.label)
|
||||||
|
: input.label
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</ModalBody>
|
||||||
|
</ModalForm>
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<section>
|
<section>
|
||||||
<ButtonModal
|
<ButtonModal
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
function init(initialState) {
|
||||||
|
return initialState;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default init;
|
@ -0,0 +1,25 @@
|
|||||||
|
import { fromJS } from 'immutable';
|
||||||
|
|
||||||
|
const initialState = fromJS({
|
||||||
|
formErrors: {},
|
||||||
|
modifiedData: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
const reducer = (state, action) => {
|
||||||
|
switch (action.type) {
|
||||||
|
case 'ON_CHANGE':
|
||||||
|
return state.updateIn(
|
||||||
|
['modifiedData', ...action.keys],
|
||||||
|
() => action.value
|
||||||
|
);
|
||||||
|
case 'RESET_PROPS':
|
||||||
|
return initialState;
|
||||||
|
case 'SET_ERRORS':
|
||||||
|
return state.update('formErrors', () => fromJS(action.errors));
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default reducer;
|
||||||
|
export { initialState };
|
@ -0,0 +1,12 @@
|
|||||||
|
import slugify from '@sindresorhus/slugify';
|
||||||
|
|
||||||
|
const nameToSlug = name => slugify(name, { separator: '-' });
|
||||||
|
|
||||||
|
const createUid = name => {
|
||||||
|
const modelName = nameToSlug(name);
|
||||||
|
const uid = `application::${modelName}.${modelName}`;
|
||||||
|
|
||||||
|
return uid;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { createUid, nameToSlug };
|
@ -1,5 +1,7 @@
|
|||||||
import * as yup from 'yup';
|
import * as yup from 'yup';
|
||||||
import { translatedErrors as errorsTrads } from 'strapi-helper-plugin';
|
import { translatedErrors as errorsTrads } from 'strapi-helper-plugin';
|
||||||
|
import pluginId from '../../../pluginId';
|
||||||
|
import { createUid, nameToSlug } from './createUid';
|
||||||
|
|
||||||
yup.addMethod(yup.mixed, 'defined', function() {
|
yup.addMethod(yup.mixed, 'defined', function() {
|
||||||
return this.test(
|
return this.test(
|
||||||
@ -10,10 +12,12 @@ yup.addMethod(yup.mixed, 'defined', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
yup.addMethod(yup.string, 'unique', function(message, allReadyTakenValues) {
|
yup.addMethod(yup.string, 'unique', function(message, allReadyTakenValues) {
|
||||||
console.log({ allReadyTakenValues });
|
|
||||||
return this.test('unique', message, function(string) {
|
return this.test('unique', message, function(string) {
|
||||||
console.log({ string });
|
if (!string) {
|
||||||
return !allReadyTakenValues.includes(string);
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !allReadyTakenValues.includes(createUid(string));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -23,21 +27,75 @@ const forms = {
|
|||||||
return yup.object().shape({
|
return yup.object().shape({
|
||||||
name: yup
|
name: yup
|
||||||
.string()
|
.string()
|
||||||
.required()
|
.unique(errorsTrads.unique, allReadyTakenValues)
|
||||||
.unique('duplicate key', allReadyTakenValues),
|
.required(errorsTrads.required),
|
||||||
|
collectionName: yup.string(),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
form: {
|
form: {
|
||||||
base: {
|
base(data = {}) {
|
||||||
name: 'name',
|
return {
|
||||||
type: 'string',
|
items: [
|
||||||
validations: {
|
[
|
||||||
required: true,
|
{
|
||||||
},
|
autoFocus: true,
|
||||||
|
name: 'name',
|
||||||
|
type: 'text',
|
||||||
|
label: {
|
||||||
|
id: `${pluginId}.contentType.displayName.label`,
|
||||||
|
},
|
||||||
|
validations: {
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: {
|
||||||
|
id: `${pluginId}.contentType.UID.description`,
|
||||||
|
},
|
||||||
|
label: 'UID',
|
||||||
|
name: 'uid',
|
||||||
|
type: 'text',
|
||||||
|
readOnly: true,
|
||||||
|
disabled: true,
|
||||||
|
value: data.name ? nameToSlug(data.name) : '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
// Maybe for later
|
||||||
|
// [
|
||||||
|
// {
|
||||||
|
// name: 'repeatable',
|
||||||
|
// type: 'customBooleanContentType',
|
||||||
|
// value: true,
|
||||||
|
// title: 'Something',
|
||||||
|
// description: 'Cool',
|
||||||
|
// icon: 'multipleFiles',
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
advanced() {
|
||||||
|
return {
|
||||||
|
items: [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
autoFocus: true,
|
||||||
|
label: {
|
||||||
|
id: `${pluginId}.contentType.collectionName.label`,
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
id: `${pluginId}.contentType.collectionName.description`,
|
||||||
|
},
|
||||||
|
name: 'collectionName',
|
||||||
|
type: 'text',
|
||||||
|
validations: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
};
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// },
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default forms;
|
export default forms;
|
||||||
|
@ -220,8 +220,14 @@
|
|||||||
"table.relations.title.plural": "including {number} relationships",
|
"table.relations.title.plural": "including {number} relationships",
|
||||||
"table.relations.title.singular": "including {number} relationship",
|
"table.relations.title.singular": "including {number} relationship",
|
||||||
"prompt.content.unsaved": "Are you sure you want to leave this content type? All your modifications will be lost.",
|
"prompt.content.unsaved": "Are you sure you want to leave this content type? All your modifications will be lost.",
|
||||||
|
|
||||||
"modalForm.contentType.header-create": "Create a content type",
|
"modalForm.contentType.header-create": "Create a content type",
|
||||||
"modalForm.header-edit": "Edit {name}",
|
"modalForm.header-edit": "Edit {name}",
|
||||||
"modalForm.component.header-create": "Create a component",
|
"modalForm.component.header-create": "Create a component",
|
||||||
"configurations": "configurations"
|
"configurations": "configurations",
|
||||||
|
"contentType.displayName.label": "Display name",
|
||||||
|
"contentType.collectionName.description": "Useful when the name of your Content Type and your table name differ",
|
||||||
|
"contentType.collectionName.label": "Collection name",
|
||||||
|
|
||||||
|
"contentType.UID.description": "The UID is used to generate the API routes and databases tables/collections"
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user