mirror of
https://github.com/strapi/strapi.git
synced 2025-11-03 11:25:17 +00:00
Init attribute form
This commit is contained in:
parent
af8b17da1b
commit
41f09d410f
@ -19,10 +19,19 @@ const Button = styled.button`
|
||||
&:hover,
|
||||
&:active,
|
||||
&:focus {
|
||||
background: #f7f7f7;
|
||||
outline: 0;
|
||||
> div:after {
|
||||
color: #0097f6;
|
||||
background: #e6f0fb;
|
||||
border-color: #aed4fb;
|
||||
|
||||
.attributeIcon {
|
||||
background-color: #007eff;
|
||||
|
||||
> svg {
|
||||
g {
|
||||
path {
|
||||
fill: #007eff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -6,33 +6,10 @@ const Card = styled.div`
|
||||
align-items: center;
|
||||
max-width: calc(100% - 18px);
|
||||
|
||||
&:after {
|
||||
content: '\f05d';
|
||||
position: absolute;
|
||||
top: 7px;
|
||||
right: 26px;
|
||||
color: #e3e9f3;
|
||||
font-size: 1.4rem;
|
||||
font-family: 'FontAwesome';
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: none;
|
||||
}
|
||||
|
||||
> img {
|
||||
display: inline-block;
|
||||
height: 20px;
|
||||
width: 35px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
> span {
|
||||
white-space: nowrap;
|
||||
color: #9ea7b8;
|
||||
font-size: 1.2rem;
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
margin-top: auto;
|
||||
|
||||
@ -4,81 +4,76 @@
|
||||
*
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, {
|
||||
forwardRef,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useRef,
|
||||
} from 'react';
|
||||
import { AttributeIcon } from '@buffetjs/core';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import attributeIcons from '../../utils/attributeIcons';
|
||||
import pluginId from '../../pluginId';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import getTrad from '../../utils/getTrad';
|
||||
import useQuery from '../../hooks/useQuery';
|
||||
import Button from './Button';
|
||||
import Card from './Card';
|
||||
|
||||
class AttributeOption extends React.Component {
|
||||
componentDidUpdate(prevProps) {
|
||||
const { isDisplayed, nodeToFocus, tabIndex } = this.props;
|
||||
const AttributeOption = forwardRef(({ tabIndex, type }, ref) => {
|
||||
const buttonRef = useRef();
|
||||
const tabRef = useRef();
|
||||
const query = useQuery();
|
||||
const { push } = useHistory();
|
||||
tabRef.current = tabIndex;
|
||||
|
||||
if (
|
||||
prevProps.isDisplayed !== isDisplayed &&
|
||||
isDisplayed &&
|
||||
nodeToFocus === tabIndex
|
||||
) {
|
||||
this.focusNode();
|
||||
useImperativeHandle(ref, () => ({
|
||||
focus: () => {
|
||||
buttonRef.current.focus();
|
||||
},
|
||||
}));
|
||||
|
||||
useEffect(() => {
|
||||
if (tabRef.current === 0) {
|
||||
buttonRef.current.focus();
|
||||
}
|
||||
}, []);
|
||||
|
||||
if (prevProps.nodeToFocus !== nodeToFocus && nodeToFocus === tabIndex) {
|
||||
this.focusNode();
|
||||
}
|
||||
}
|
||||
const handleClick = () => {
|
||||
const forTarget = query.get('for');
|
||||
const target = query.get('target');
|
||||
|
||||
button = React.createRef();
|
||||
|
||||
focusNode = () => {
|
||||
const { current } = this.button;
|
||||
|
||||
current.focus();
|
||||
push({
|
||||
search: `modalType=attribute&actionType=create&settingType=base&for=${forTarget}&target=${target}&attributeType=${type}`,
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { description, onClick, tabIndex, type } = this.props;
|
||||
return (
|
||||
<div className="col-6">
|
||||
<Button ref={buttonRef} type="button" onClick={handleClick}>
|
||||
<Card>
|
||||
<AttributeIcon
|
||||
type={type}
|
||||
style={{ marginRight: 10 }}
|
||||
className="attributeIcon"
|
||||
/>
|
||||
<FormattedMessage id={getTrad(`attribute.${type}`)}>
|
||||
{message => <span className="attributeType">{message}</span>}
|
||||
</FormattedMessage>
|
||||
<FormattedMessage id={getTrad(`attribute.${type}.description`)} />
|
||||
</Card>
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="col-md-6">
|
||||
<Button
|
||||
id={`attrCard${type}`}
|
||||
onClick={() => onClick(type)}
|
||||
type="button"
|
||||
tabIndex={tabIndex + 1}
|
||||
ref={this.button}
|
||||
>
|
||||
<Card>
|
||||
<img src={attributeIcons[type]} alt="ico" />
|
||||
<FormattedMessage
|
||||
id={`${pluginId}.popUpForm.attributes.${type}.name`}
|
||||
>
|
||||
{message => <span className="attributeType">{message}</span>}
|
||||
</FormattedMessage>
|
||||
<FormattedMessage id={description} />
|
||||
</Card>
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
AttributeOption.displayName = 'AttributeOption';
|
||||
|
||||
AttributeOption.defaultProps = {
|
||||
description: 'app.utils.defaultMessage',
|
||||
isDisplayed: false,
|
||||
nodeToFocus: -1,
|
||||
onClick: () => {},
|
||||
tabIndex: 0,
|
||||
type: 'string',
|
||||
type: 'text',
|
||||
};
|
||||
|
||||
AttributeOption.propTypes = {
|
||||
description: PropTypes.string,
|
||||
isDisplayed: PropTypes.bool,
|
||||
nodeToFocus: PropTypes.number,
|
||||
onClick: PropTypes.func,
|
||||
tabIndex: PropTypes.number,
|
||||
type: PropTypes.string,
|
||||
};
|
||||
|
||||
@ -2,15 +2,20 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { HeaderModalTitle } from 'strapi-helper-plugin';
|
||||
import { AttributeIcon } from '@buffetjs/core';
|
||||
import pluginId from '../../pluginId';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { upperFirst } from 'lodash';
|
||||
import pluginId from '../../pluginId';
|
||||
|
||||
const ModalHeader = ({ headerId, name, type }) => {
|
||||
const ModalHeader = ({ headerId, iconType, name }) => {
|
||||
console.log({ iconType });
|
||||
return (
|
||||
<section>
|
||||
<HeaderModalTitle style={{ textTransform: 'none' }}>
|
||||
<AttributeIcon type={type} style={{ margin: 'auto 20px auto 0' }} />
|
||||
<FormattedMessage id={`${pluginId}.${headerId}`} values={{ name }} />
|
||||
<AttributeIcon type={iconType} style={{ margin: 'auto 20px auto 0' }} />
|
||||
{headerId && (
|
||||
<FormattedMessage id={`${pluginId}.${headerId}`} values={{ name }} />
|
||||
)}
|
||||
{!headerId && <span>{upperFirst(name)}</span>}
|
||||
</HeaderModalTitle>
|
||||
</section>
|
||||
);
|
||||
@ -18,14 +23,14 @@ const ModalHeader = ({ headerId, name, type }) => {
|
||||
|
||||
ModalHeader.defaultProps = {
|
||||
headerId: '',
|
||||
iconType: 'contentType',
|
||||
name: '',
|
||||
type: 'contentType',
|
||||
};
|
||||
|
||||
ModalHeader.propTypes = {
|
||||
headerId: PropTypes.string,
|
||||
iconType: PropTypes.string,
|
||||
name: PropTypes.string,
|
||||
type: PropTypes.string,
|
||||
};
|
||||
|
||||
export default ModalHeader;
|
||||
|
||||
@ -18,7 +18,6 @@ const DataManagerProvider = ({ children }) => {
|
||||
isLoading,
|
||||
initialData,
|
||||
modifiedData,
|
||||
newSchema,
|
||||
} = reducerState.toJS();
|
||||
|
||||
const contentTypeMatch = useRouteMatch(
|
||||
@ -90,7 +89,6 @@ const DataManagerProvider = ({ children }) => {
|
||||
createSchema,
|
||||
initialData,
|
||||
modifiedData,
|
||||
newSchema,
|
||||
setModifiedData,
|
||||
}}
|
||||
>
|
||||
|
||||
@ -6,16 +6,6 @@ const initialState = fromJS({
|
||||
initialData: {},
|
||||
modifiedData: {},
|
||||
isLoading: true,
|
||||
newSchema: {
|
||||
schemaType: '',
|
||||
schema: {},
|
||||
uid: '',
|
||||
},
|
||||
newSchemaClone: {
|
||||
schemaType: '',
|
||||
schema: {},
|
||||
uid: '',
|
||||
},
|
||||
});
|
||||
|
||||
const reducer = (state, action) => {
|
||||
@ -25,12 +15,21 @@ const reducer = (state, action) => {
|
||||
.update('components', () => fromJS(action.components))
|
||||
.update('contentTypes', () => fromJS(action.contentTypes))
|
||||
.update('isLoading', () => false);
|
||||
case 'CREATE_SCHEMA':
|
||||
console.log({ action });
|
||||
return state
|
||||
.updateIn(['newSchema', 'schema'], () => fromJS(action.data))
|
||||
.updateIn(['newSchema', 'uid'], () => fromJS(action.uid))
|
||||
.updateIn(['newSchema', 'schemaType'], () => fromJS(action.schemaType));
|
||||
case 'CREATE_SCHEMA': {
|
||||
const newSchema = {
|
||||
uid: action.uid,
|
||||
isTemporary: true,
|
||||
schema: {
|
||||
...action.data,
|
||||
attributes: {},
|
||||
},
|
||||
};
|
||||
const key =
|
||||
action.schemaType === 'contentType' ? 'contentTypes' : 'components';
|
||||
|
||||
return state.updateIn([key, action.uid], () => fromJS(newSchema));
|
||||
}
|
||||
|
||||
case 'SET_MODIFIED_DATA':
|
||||
return state
|
||||
.update('initialData', () => OrderedMap(action.schemaToSet))
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useReducer, useState } from 'react';
|
||||
import React, { useEffect, useReducer, useRef, useState } from 'react';
|
||||
// import PropTypes from 'prop-types';
|
||||
import {
|
||||
ButtonModal,
|
||||
@ -18,15 +18,17 @@ import { get, isEmpty, upperFirst } from 'lodash';
|
||||
import pluginId from '../../pluginId';
|
||||
import useQuery from '../../hooks/useQuery';
|
||||
import useDataManager from '../../hooks/useDataManager';
|
||||
import AttributeOption from '../../components/AttributeOption';
|
||||
import ModalHeader from '../../components/ModalHeader';
|
||||
import HeaderModalNavContainer from '../../components/HeaderModalNavContainer';
|
||||
import HeaderNavLink from '../../components/HeaderNavLink';
|
||||
import getTrad from '../../utils/getTrad';
|
||||
import getAttributes from './utils/attributes';
|
||||
import forms from './utils/forms';
|
||||
import { createUid } from './utils/createUid';
|
||||
import init from './init';
|
||||
import reducer, { initialState } from './reducer';
|
||||
|
||||
const getTrad = id => `${pluginId}.${id}`;
|
||||
const NAVLINKS = [{ id: 'base' }, { id: 'advanced' }];
|
||||
|
||||
const FormModal = () => {
|
||||
@ -34,40 +36,77 @@ const FormModal = () => {
|
||||
actionType: null,
|
||||
modalType: null,
|
||||
settingType: null,
|
||||
// uid: null,
|
||||
for: null,
|
||||
target: null,
|
||||
attributeType: null,
|
||||
};
|
||||
const [state, setState] = useState(initialStateData);
|
||||
const [reducerState, dispatch] = useReducer(reducer, initialState, init);
|
||||
const { push } = useHistory();
|
||||
const { search } = useLocation();
|
||||
const { formatMessage } = useGlobalContext();
|
||||
const isOpen = !isEmpty(search);
|
||||
const query = useQuery();
|
||||
const attributeOptionRef = useRef();
|
||||
|
||||
const { contentTypes, createSchema, initialData } = useDataManager();
|
||||
const { formErrors, modifiedData } = reducerState.toJS();
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
if (!isEmpty(search)) {
|
||||
setState({
|
||||
actionType: query.get('actionType'),
|
||||
modalType: query.get('modalType'),
|
||||
settingType: query.get('settingType'),
|
||||
// uid: query.get('uid'),
|
||||
for: query.get('for'),
|
||||
target: query.get('target'),
|
||||
attributeType: query.get('attributeType'),
|
||||
});
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isOpen]);
|
||||
}, [search]);
|
||||
|
||||
const displayedAttributes = getAttributes(state.for);
|
||||
|
||||
const form = get(forms, [state.modalType, 'form', state.settingType], () => ({
|
||||
items: [],
|
||||
}));
|
||||
const iconType = ['components', 'contentType'].includes(state.modalType)
|
||||
? state.modalType
|
||||
: state.for;
|
||||
const isCreatingCT = state.modalType === 'contentType';
|
||||
const isCreating = state.actionType === 'create';
|
||||
const headerId = isCreating
|
||||
const isOpen = !isEmpty(search);
|
||||
const isPickingAttribute = state.modalType === 'chooseAttribute';
|
||||
const name = get(initialData, ['schema', 'name'], '');
|
||||
const uid = createUid(modifiedData.name || '');
|
||||
|
||||
let headerId = isCreating
|
||||
? `modalForm.${state.modalType}.header-create`
|
||||
: 'modalForm.header-edit';
|
||||
const name = get(initialData, ['schema', 'name'], '');
|
||||
|
||||
if (!['contentType', 'component'].includes(state.modalType)) {
|
||||
headerId = null;
|
||||
}
|
||||
|
||||
const modalBodyStyle = isPickingAttribute
|
||||
? { paddingTop: '0.5rem', paddingBottom: '3rem' }
|
||||
: {};
|
||||
|
||||
const getModalTitleSubHeader = () => {
|
||||
switch (state.modalType) {
|
||||
case 'chooseAttribute':
|
||||
return getTrad(`modalForm.sub-header.chooseAttribute.${state.for}`);
|
||||
default:
|
||||
return getTrad('configurations');
|
||||
}
|
||||
};
|
||||
|
||||
const getNextSearch = nextTab => {
|
||||
const newSearch = Object.keys(state).reduce((acc, current) => {
|
||||
const newSearch = Object.keys(state).reduce((acc, current, index) => {
|
||||
if (current !== 'settingType') {
|
||||
acc = `${acc}&${current}=${state[current]}`;
|
||||
acc = `${acc}${index === 0 ? '' : '&'}${current}=${state[current]}`;
|
||||
} else {
|
||||
acc = `${acc}&${current}=${nextTab}`;
|
||||
acc = `${acc}${index === 0 ? '' : '&'}${current}=${nextTab}`;
|
||||
}
|
||||
|
||||
return acc;
|
||||
@ -87,16 +126,21 @@ const FormModal = () => {
|
||||
e.preventDefault();
|
||||
|
||||
try {
|
||||
const schema = forms.contentType.schema(Object.keys(contentTypes));
|
||||
const schema = forms[state.modalType].schema(Object.keys(contentTypes));
|
||||
|
||||
await schema.validate(modifiedData, { abortEarly: false });
|
||||
createSchema(modifiedData, state.modalType, createUid(modifiedData.name));
|
||||
handleToggle();
|
||||
// push({ p})
|
||||
|
||||
createSchema(modifiedData, state.modalType, uid);
|
||||
const nextSlug = isCreatingCT ? 'content-types' : 'component-categories';
|
||||
push({
|
||||
pathname: `/plugins/${pluginId}/${nextSlug}/${uid}`,
|
||||
search: `modalType=chooseAttribute&for=${state.modalType}&target=${modifiedData.name}`,
|
||||
});
|
||||
dispatch({
|
||||
type: 'RESET_PROPS',
|
||||
});
|
||||
} catch (err) {
|
||||
const errors = getYupInnerErrors(err);
|
||||
// TODO
|
||||
console.log({ errors });
|
||||
dispatch({
|
||||
type: 'SET_ERRORS',
|
||||
errors,
|
||||
@ -112,104 +156,161 @@ const FormModal = () => {
|
||||
type: 'RESET_PROPS',
|
||||
});
|
||||
};
|
||||
const form = get(forms, [state.modalType, 'form', state.settingType], () => ({
|
||||
items: [],
|
||||
}));
|
||||
|
||||
const onOpened = () => {
|
||||
if (state.modalType === 'chooseAttribute') {
|
||||
attributeOptionRef.current.focus();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onClosed={onClosed} onToggle={handleToggle}>
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onOpened={onOpened}
|
||||
onClosed={onClosed}
|
||||
onToggle={handleToggle}
|
||||
>
|
||||
<HeaderModal>
|
||||
<ModalHeader
|
||||
name={name}
|
||||
name={state.target || name}
|
||||
headerId={headerId}
|
||||
type={state.modalType || 'contentType'}
|
||||
iconType={iconType || 'contentType'}
|
||||
/>
|
||||
<section>
|
||||
<HeaderModalTitle>
|
||||
<FormattedMessage id={getTrad('configurations')}>
|
||||
<FormattedMessage id={getModalTitleSubHeader()}>
|
||||
{msg => <span>{upperFirst(msg)}</span>}
|
||||
</FormattedMessage>
|
||||
<div className="settings-tabs">
|
||||
<HeaderModalNavContainer>
|
||||
{NAVLINKS.map((link, index) => {
|
||||
return (
|
||||
<HeaderNavLink
|
||||
isActive={state.settingType === link.id}
|
||||
key={link.id}
|
||||
{...link}
|
||||
onClick={() => {
|
||||
setState(prev => ({ ...prev, settingType: link.id }));
|
||||
push({ search: getNextSearch(link.id) });
|
||||
}}
|
||||
nextTab={index === NAVLINKS.length - 1 ? 0 : index + 1}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</HeaderModalNavContainer>
|
||||
</div>
|
||||
<hr />
|
||||
{!isPickingAttribute && (
|
||||
<>
|
||||
<div className="settings-tabs">
|
||||
<HeaderModalNavContainer>
|
||||
{NAVLINKS.map((link, index) => {
|
||||
return (
|
||||
<HeaderNavLink
|
||||
isActive={state.settingType === link.id}
|
||||
key={link.id}
|
||||
{...link}
|
||||
onClick={() => {
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
settingType: link.id,
|
||||
}));
|
||||
push({ search: getNextSearch(link.id) });
|
||||
}}
|
||||
nextTab={
|
||||
index === NAVLINKS.length - 1 ? 0 : index + 1
|
||||
}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</HeaderModalNavContainer>
|
||||
</div>
|
||||
<hr />
|
||||
</>
|
||||
)}
|
||||
</HeaderModalTitle>
|
||||
</section>
|
||||
</HeaderModal>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<ModalForm>
|
||||
<ModalBody>
|
||||
<ModalBody style={modalBodyStyle}>
|
||||
<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}
|
||||
onBlur={() => {}}
|
||||
description={
|
||||
get(input, 'description.id', null)
|
||||
? formatMessage(input.description)
|
||||
: input.description
|
||||
}
|
||||
label={
|
||||
get(input, 'label.id', null)
|
||||
? formatMessage(input.label)
|
||||
: input.label
|
||||
}
|
||||
{isPickingAttribute
|
||||
? displayedAttributes.map((row, i) => {
|
||||
return (
|
||||
<div key={i} className="row">
|
||||
{i === 1 && (
|
||||
<hr
|
||||
style={{
|
||||
width: 'calc(100% - 30px)',
|
||||
marginBottom: 7,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{row.map((attr, index) => {
|
||||
const tabIndex =
|
||||
i === 0
|
||||
? index
|
||||
: displayedAttributes[0].length + index;
|
||||
|
||||
return (
|
||||
<AttributeOption
|
||||
key={attr}
|
||||
tabIndex={tabIndex}
|
||||
isDisplayed
|
||||
onClick={() => {}}
|
||||
ref={
|
||||
i === 0 && index === 0
|
||||
? attributeOptionRef
|
||||
: null
|
||||
}
|
||||
type={attr}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
})
|
||||
: form(modifiedData, state.attributeType).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}
|
||||
onBlur={() => {}}
|
||||
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>
|
||||
);
|
||||
})}
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
</ModalBody>
|
||||
</ModalForm>
|
||||
<ModalFooter>
|
||||
<section>
|
||||
<ButtonModal
|
||||
message="components.popUpWarning.button.cancel"
|
||||
onClick={handleToggle}
|
||||
isSecondary
|
||||
/>
|
||||
<ButtonModal message="form.button.done" type="submit" />
|
||||
</section>
|
||||
</ModalFooter>
|
||||
{!isPickingAttribute && (
|
||||
<ModalFooter>
|
||||
<section>
|
||||
<ButtonModal
|
||||
message="components.popUpWarning.button.cancel"
|
||||
onClick={handleToggle}
|
||||
isSecondary
|
||||
/>
|
||||
<ButtonModal message="form.button.done" type="submit" />
|
||||
</section>
|
||||
</ModalFooter>
|
||||
)}
|
||||
</form>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
@ -0,0 +1,23 @@
|
||||
const getAttributes = () => {
|
||||
const defaultAttributes = [
|
||||
[
|
||||
'text',
|
||||
'email',
|
||||
'richtext',
|
||||
'password',
|
||||
'number',
|
||||
'enumeration',
|
||||
'date',
|
||||
'media',
|
||||
'boolean',
|
||||
'json',
|
||||
// 'uid',
|
||||
'relation',
|
||||
],
|
||||
['component', 'dynamiczone'],
|
||||
];
|
||||
|
||||
return defaultAttributes;
|
||||
};
|
||||
|
||||
export default getAttributes;
|
||||
@ -1,6 +1,7 @@
|
||||
import * as yup from 'yup';
|
||||
import { translatedErrors as errorsTrads } from 'strapi-helper-plugin';
|
||||
import pluginId from '../../../pluginId';
|
||||
import getTrad from '../../../utils/getTrad';
|
||||
import { createUid, nameToSlug } from './createUid';
|
||||
|
||||
yup.addMethod(yup.mixed, 'defined', function() {
|
||||
@ -22,6 +23,57 @@ yup.addMethod(yup.string, 'unique', function(message, allReadyTakenValues) {
|
||||
});
|
||||
|
||||
const forms = {
|
||||
attribute: {
|
||||
schema() {
|
||||
return yup.object();
|
||||
},
|
||||
form: {
|
||||
advanced() {
|
||||
return {
|
||||
items: [[]],
|
||||
};
|
||||
},
|
||||
base(data, type) {
|
||||
const items = [
|
||||
[
|
||||
{
|
||||
autoFocus: true,
|
||||
name: 'name',
|
||||
type: 'text',
|
||||
label: {
|
||||
id: getTrad('modalForm.attribute.form.base.name'),
|
||||
},
|
||||
description: {
|
||||
id: getTrad('modalForm.attribute.form.base.name.description'),
|
||||
},
|
||||
validations: {
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
];
|
||||
|
||||
if (type === 'text') {
|
||||
items[0].push({
|
||||
label: {
|
||||
id: 'content-type-builder.form.attribute.item.number.type',
|
||||
},
|
||||
name: 'type',
|
||||
type: 'select',
|
||||
value: 'short text',
|
||||
options: ['short text', 'long text'],
|
||||
validations: {
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
items,
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
contentType: {
|
||||
schema(allReadyTakenValues) {
|
||||
return yup.object().shape({
|
||||
|
||||
@ -13,14 +13,19 @@ import CustomLink from '../../components/CustomLink';
|
||||
import useDataManager from '../../hooks/useDataManager';
|
||||
import Wrapper from './Wrapper';
|
||||
|
||||
// const displayNotificationCTNotSaved = () => {
|
||||
// strapi.notification.info(
|
||||
// `${pluginId}.notification.info.contentType.creating.notSaved`
|
||||
// );
|
||||
// };
|
||||
const displayNotificationCTNotSaved = () => {
|
||||
strapi.notification.info(
|
||||
`${pluginId}.notification.info.contentType.creating.notSaved`
|
||||
);
|
||||
};
|
||||
|
||||
function LeftMenu() {
|
||||
const { components, contentTypes, newSchema } = useDataManager();
|
||||
const {
|
||||
components,
|
||||
contentTypes,
|
||||
// initialData,
|
||||
// modifiedData,
|
||||
} = useDataManager();
|
||||
const { currentEnvironment } = useGlobalContext();
|
||||
const { push } = useHistory();
|
||||
const isProduction = currentEnvironment === 'production';
|
||||
@ -40,14 +45,24 @@ function LeftMenu() {
|
||||
})),
|
||||
obj => obj.title
|
||||
);
|
||||
const tempSchemaCT =
|
||||
newSchema.schemaType === 'contentType'
|
||||
? {
|
||||
name: newSchema.uid,
|
||||
title: newSchema.schema.name,
|
||||
to: `/plugins/${pluginId}/content-types/${newSchema.uid}`,
|
||||
}
|
||||
: null;
|
||||
const canOpenModalCreateCTorComponent = () => {
|
||||
return (
|
||||
!Object.keys(contentTypes).some(
|
||||
ct => contentTypes[ct].isTemporary === true
|
||||
) &&
|
||||
!Object.keys(components).some(
|
||||
component => components[component].isTemporary === true
|
||||
)
|
||||
);
|
||||
};
|
||||
const handleClickOpenModal = type => {
|
||||
if (canOpenModalCreateCTorComponent()) {
|
||||
push({ search: `modalType=${type}&actionType=create&settingType=base` });
|
||||
} else {
|
||||
displayNotificationCTNotSaved();
|
||||
}
|
||||
};
|
||||
|
||||
const data = [
|
||||
{
|
||||
name: 'models',
|
||||
@ -61,10 +76,7 @@ function LeftMenu() {
|
||||
disabled: isProduction,
|
||||
id: `${pluginId}.button.model.create`,
|
||||
onClick: () => {
|
||||
push({
|
||||
search:
|
||||
'modalType=contentType&actionType=create&settingType=base',
|
||||
});
|
||||
handleClickOpenModal('contentType');
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -75,7 +87,6 @@ function LeftMenu() {
|
||||
title: contentTypes[uid].schema.name,
|
||||
to: `/plugins/${pluginId}/content-types/${uid}`,
|
||||
}))
|
||||
.concat(tempSchemaCT)
|
||||
.filter(obj => obj !== null),
|
||||
obj => obj.title
|
||||
),
|
||||
@ -92,9 +103,7 @@ function LeftMenu() {
|
||||
disabled: isProduction,
|
||||
id: `${pluginId}.button.component.create`,
|
||||
onClick: () => {
|
||||
push({
|
||||
search: 'modalType=component&actionType=create&settingType=base',
|
||||
});
|
||||
handleClickOpenModal('component');
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@ -2,23 +2,7 @@
|
||||
"model": "Content Type",
|
||||
"group": "Group",
|
||||
"attribute.WYSIWYG": "Text (WYSIWYG)",
|
||||
"attribute.boolean": "Boolean",
|
||||
"attribute.date": "Date",
|
||||
"attribute.decimal": "Decimal",
|
||||
"attribute.email": "Email",
|
||||
"attribute.enumeration": "Enumeration",
|
||||
"attribute.float": "Float",
|
||||
"attribute.group": "Group",
|
||||
"attribute.integer": "Integer",
|
||||
"attribute.biginteger": "Big Integer",
|
||||
"attribute.json": "JSON",
|
||||
"attribute.media": "Media",
|
||||
"attribute.password": "Password",
|
||||
"attribute.relation": "Relation",
|
||||
"attribute.richtext": "Rich text",
|
||||
"attribute.string": "String",
|
||||
"attribute.text": "Text",
|
||||
"attribute.uuid": "Uuid",
|
||||
|
||||
"button.attributes.add": "Add New Field",
|
||||
"button.attributes.add.another": "Add Another Field",
|
||||
"button.contentType.add": "Add a Content Type",
|
||||
@ -146,34 +130,7 @@
|
||||
"notification.success.message.contentType.edit": "Your Content Type has been updated",
|
||||
"plugin.description.long": "Modelize the data structure of your API. Create new fields and relations in just a minute. The files are automatically created and updated in your project.",
|
||||
"plugin.description.short": "Modelize the data structure of your API.",
|
||||
"popUpForm.attributes.boolean.description": "Yes or no, 1 or 0, true or false",
|
||||
"popUpForm.attributes.boolean.name": "Boolean",
|
||||
"popUpForm.attributes.date.description": "Event date, opening hours",
|
||||
"popUpForm.attributes.date.name": "Date",
|
||||
"popUpForm.attributes.email.description": "User's email...",
|
||||
"popUpForm.attributes.email.name": "Email",
|
||||
"popUpForm.attributes.enumeration.description": "List of choices",
|
||||
"popUpForm.attributes.enumeration.name": "Enumeration",
|
||||
"popUpForm.attributes.group.description": "Refers to a Group",
|
||||
"popUpForm.attributes.group.name": "Group",
|
||||
"popUpForm.attributes.json.description": "Data in JSON format",
|
||||
"popUpForm.attributes.json.name": "JSON",
|
||||
"popUpForm.attributes.media.description": "Images, videos, PDFs and other files",
|
||||
"popUpForm.attributes.media.name": "Media",
|
||||
"popUpForm.attributes.number.description": "Everything that is number",
|
||||
"popUpForm.attributes.number.name": "Number",
|
||||
"popUpForm.attributes.password.description": "User password...",
|
||||
"popUpForm.attributes.password.name": "Password",
|
||||
"popUpForm.attributes.relation.description": "Refers to a Content Type",
|
||||
"popUpForm.attributes.relation.name": "Relation",
|
||||
"popUpForm.attributes.richtext.description": "Formatting and creating text",
|
||||
"popUpForm.attributes.richtext.name": "Rich text",
|
||||
"popUpForm.attributes.string.description": "Titles, names, paragraphs, list of names",
|
||||
"popUpForm.attributes.string.name": "String",
|
||||
"popUpForm.attributes.text.description": "Descriptions, text paragraphs, articles",
|
||||
"popUpForm.attributes.text.name": "Text",
|
||||
"popUpForm.attributes.uuid.description": "Unique identifier",
|
||||
"popUpForm.attributes.uuid.name": "Uuid",
|
||||
|
||||
"popUpForm.choose.attributes.header.title": "Add New Field",
|
||||
"popUpForm.choose.attributes.header.subtitle.model": "Select a field for your content type",
|
||||
"popUpForm.choose.attributes.header.subtitle.group": "Select a field for your group",
|
||||
@ -221,13 +178,45 @@
|
||||
"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.",
|
||||
|
||||
"modalForm.contentType.header-create": "Create a content type",
|
||||
"modalForm.header-edit": "Edit {name}",
|
||||
"modalForm.component.header-create": "Create a component",
|
||||
"attribute.boolean": "Boolean",
|
||||
"attribute.boolean.description": "Yes or no, 1 or 0, true or false",
|
||||
"attribute.component": "Component",
|
||||
"attribute.component.description": "Set of fields that you can repeat or reuse",
|
||||
"attribute.date": "Date",
|
||||
"attribute.date.description": "Event date, opening hours",
|
||||
"attribute.dynamiczone": "Dynamic zone",
|
||||
"attribute.dynamiczone.description": "Dynamically pick component when editing content",
|
||||
"attribute.email": "Email",
|
||||
"attribute.email.description": "User's email...",
|
||||
"attribute.enumeration": "Enumeration",
|
||||
"attribute.enumeration.description": "List of choices",
|
||||
"attribute.json": "JSON",
|
||||
"attribute.json.description": "Data in JSON format",
|
||||
"attribute.media": "Media",
|
||||
"attribute.media.description": "Images, videos, PDFs and other files",
|
||||
"attribute.number": "Number",
|
||||
"attribute.number.description": "Everything that is number",
|
||||
"attribute.password": "Password",
|
||||
"attribute.password.description": "User password...",
|
||||
"attribute.relation": "Relation",
|
||||
"attribute.relation.description": "Refers to a Content Type",
|
||||
"attribute.richtext": "Rich text",
|
||||
"attribute.richtext.description": "Formatting and creating text",
|
||||
"attribute.text": "Text",
|
||||
"attribute.text.description": "Small or long text like title or description",
|
||||
"attribute.uid": "Uuid",
|
||||
"attribute.uid.description": "Unique identifier",
|
||||
"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"
|
||||
"contentType.UID.description": "The UID is used to generate the API routes and databases tables/collections",
|
||||
"modalForm.component.header-create": "Create a component",
|
||||
"modalForm.contentType.header-create": "Create a content type",
|
||||
"modalForm.attribute.form.base.name": "Name",
|
||||
"modalForm.attribute.form.base.name.description": "No space is allowed for the name of the attribute",
|
||||
"modalForm.header-edit": "Edit {name}",
|
||||
"modalForm.sub-header.chooseAttribute.component": "Select a field for your component",
|
||||
"modalForm.sub-header.chooseAttribute.contentType": "Select a field for your content type"
|
||||
}
|
||||
|
||||
@ -0,0 +1,5 @@
|
||||
import pluginId from '../pluginId';
|
||||
|
||||
const getTrad = id => `${pluginId}.${id}`;
|
||||
|
||||
export default getTrad;
|
||||
Loading…
x
Reference in New Issue
Block a user