mirror of
https://github.com/strapi/strapi.git
synced 2025-11-10 07:10:11 +00:00
Init attribute form
This commit is contained in:
parent
af8b17da1b
commit
41f09d410f
@ -19,10 +19,19 @@ const Button = styled.button`
|
|||||||
&:hover,
|
&:hover,
|
||||||
&:active,
|
&:active,
|
||||||
&:focus {
|
&:focus {
|
||||||
background: #f7f7f7;
|
background: #e6f0fb;
|
||||||
outline: 0;
|
border-color: #aed4fb;
|
||||||
> div:after {
|
|
||||||
color: #0097f6;
|
.attributeIcon {
|
||||||
|
background-color: #007eff;
|
||||||
|
|
||||||
|
> svg {
|
||||||
|
g {
|
||||||
|
path {
|
||||||
|
fill: #007eff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -6,33 +6,10 @@ const Card = styled.div`
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
max-width: calc(100% - 18px);
|
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 {
|
> span {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
color: #9ea7b8;
|
color: #9ea7b8;
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
font-style: italic;
|
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
margin-top: auto;
|
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 PropTypes from 'prop-types';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
import { useHistory } from 'react-router-dom';
|
||||||
import attributeIcons from '../../utils/attributeIcons';
|
import getTrad from '../../utils/getTrad';
|
||||||
import pluginId from '../../pluginId';
|
import useQuery from '../../hooks/useQuery';
|
||||||
import Button from './Button';
|
import Button from './Button';
|
||||||
import Card from './Card';
|
import Card from './Card';
|
||||||
|
|
||||||
class AttributeOption extends React.Component {
|
const AttributeOption = forwardRef(({ tabIndex, type }, ref) => {
|
||||||
componentDidUpdate(prevProps) {
|
const buttonRef = useRef();
|
||||||
const { isDisplayed, nodeToFocus, tabIndex } = this.props;
|
const tabRef = useRef();
|
||||||
|
const query = useQuery();
|
||||||
|
const { push } = useHistory();
|
||||||
|
tabRef.current = tabIndex;
|
||||||
|
|
||||||
if (
|
useImperativeHandle(ref, () => ({
|
||||||
prevProps.isDisplayed !== isDisplayed &&
|
focus: () => {
|
||||||
isDisplayed &&
|
buttonRef.current.focus();
|
||||||
nodeToFocus === tabIndex
|
},
|
||||||
) {
|
}));
|
||||||
this.focusNode();
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (tabRef.current === 0) {
|
||||||
|
buttonRef.current.focus();
|
||||||
}
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
if (prevProps.nodeToFocus !== nodeToFocus && nodeToFocus === tabIndex) {
|
const handleClick = () => {
|
||||||
this.focusNode();
|
const forTarget = query.get('for');
|
||||||
}
|
const target = query.get('target');
|
||||||
}
|
|
||||||
|
|
||||||
button = React.createRef();
|
push({
|
||||||
|
search: `modalType=attribute&actionType=create&settingType=base&for=${forTarget}&target=${target}&attributeType=${type}`,
|
||||||
focusNode = () => {
|
});
|
||||||
const { current } = this.button;
|
|
||||||
|
|
||||||
current.focus();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
|
||||||
const { description, onClick, tabIndex, type } = this.props;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="col-md-6">
|
<div className="col-6">
|
||||||
<Button
|
<Button ref={buttonRef} type="button" onClick={handleClick}>
|
||||||
id={`attrCard${type}`}
|
|
||||||
onClick={() => onClick(type)}
|
|
||||||
type="button"
|
|
||||||
tabIndex={tabIndex + 1}
|
|
||||||
ref={this.button}
|
|
||||||
>
|
|
||||||
<Card>
|
<Card>
|
||||||
<img src={attributeIcons[type]} alt="ico" />
|
<AttributeIcon
|
||||||
<FormattedMessage
|
type={type}
|
||||||
id={`${pluginId}.popUpForm.attributes.${type}.name`}
|
style={{ marginRight: 10 }}
|
||||||
>
|
className="attributeIcon"
|
||||||
|
/>
|
||||||
|
<FormattedMessage id={getTrad(`attribute.${type}`)}>
|
||||||
{message => <span className="attributeType">{message}</span>}
|
{message => <span className="attributeType">{message}</span>}
|
||||||
</FormattedMessage>
|
</FormattedMessage>
|
||||||
<FormattedMessage id={description} />
|
<FormattedMessage id={getTrad(`attribute.${type}.description`)} />
|
||||||
</Card>
|
</Card>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
}
|
|
||||||
|
AttributeOption.displayName = 'AttributeOption';
|
||||||
|
|
||||||
AttributeOption.defaultProps = {
|
AttributeOption.defaultProps = {
|
||||||
description: 'app.utils.defaultMessage',
|
|
||||||
isDisplayed: false,
|
|
||||||
nodeToFocus: -1,
|
|
||||||
onClick: () => {},
|
|
||||||
tabIndex: 0,
|
tabIndex: 0,
|
||||||
type: 'string',
|
type: 'text',
|
||||||
};
|
};
|
||||||
|
|
||||||
AttributeOption.propTypes = {
|
AttributeOption.propTypes = {
|
||||||
description: PropTypes.string,
|
|
||||||
isDisplayed: PropTypes.bool,
|
|
||||||
nodeToFocus: PropTypes.number,
|
|
||||||
onClick: PropTypes.func,
|
|
||||||
tabIndex: PropTypes.number,
|
tabIndex: PropTypes.number,
|
||||||
type: PropTypes.string,
|
type: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -2,15 +2,20 @@ import React from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { HeaderModalTitle } from 'strapi-helper-plugin';
|
import { HeaderModalTitle } from 'strapi-helper-plugin';
|
||||||
import { AttributeIcon } from '@buffetjs/core';
|
import { AttributeIcon } from '@buffetjs/core';
|
||||||
import pluginId from '../../pluginId';
|
|
||||||
import { FormattedMessage } from 'react-intl';
|
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 (
|
return (
|
||||||
<section>
|
<section>
|
||||||
<HeaderModalTitle style={{ textTransform: 'none' }}>
|
<HeaderModalTitle style={{ textTransform: 'none' }}>
|
||||||
<AttributeIcon type={type} style={{ margin: 'auto 20px auto 0' }} />
|
<AttributeIcon type={iconType} style={{ margin: 'auto 20px auto 0' }} />
|
||||||
|
{headerId && (
|
||||||
<FormattedMessage id={`${pluginId}.${headerId}`} values={{ name }} />
|
<FormattedMessage id={`${pluginId}.${headerId}`} values={{ name }} />
|
||||||
|
)}
|
||||||
|
{!headerId && <span>{upperFirst(name)}</span>}
|
||||||
</HeaderModalTitle>
|
</HeaderModalTitle>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
@ -18,14 +23,14 @@ const ModalHeader = ({ headerId, name, type }) => {
|
|||||||
|
|
||||||
ModalHeader.defaultProps = {
|
ModalHeader.defaultProps = {
|
||||||
headerId: '',
|
headerId: '',
|
||||||
|
iconType: 'contentType',
|
||||||
name: '',
|
name: '',
|
||||||
type: 'contentType',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
ModalHeader.propTypes = {
|
ModalHeader.propTypes = {
|
||||||
headerId: PropTypes.string,
|
headerId: PropTypes.string,
|
||||||
|
iconType: PropTypes.string,
|
||||||
name: PropTypes.string,
|
name: PropTypes.string,
|
||||||
type: PropTypes.string,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ModalHeader;
|
export default ModalHeader;
|
||||||
|
|||||||
@ -18,7 +18,6 @@ const DataManagerProvider = ({ children }) => {
|
|||||||
isLoading,
|
isLoading,
|
||||||
initialData,
|
initialData,
|
||||||
modifiedData,
|
modifiedData,
|
||||||
newSchema,
|
|
||||||
} = reducerState.toJS();
|
} = reducerState.toJS();
|
||||||
|
|
||||||
const contentTypeMatch = useRouteMatch(
|
const contentTypeMatch = useRouteMatch(
|
||||||
@ -90,7 +89,6 @@ const DataManagerProvider = ({ children }) => {
|
|||||||
createSchema,
|
createSchema,
|
||||||
initialData,
|
initialData,
|
||||||
modifiedData,
|
modifiedData,
|
||||||
newSchema,
|
|
||||||
setModifiedData,
|
setModifiedData,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -6,16 +6,6 @@ const initialState = fromJS({
|
|||||||
initialData: {},
|
initialData: {},
|
||||||
modifiedData: {},
|
modifiedData: {},
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
newSchema: {
|
|
||||||
schemaType: '',
|
|
||||||
schema: {},
|
|
||||||
uid: '',
|
|
||||||
},
|
|
||||||
newSchemaClone: {
|
|
||||||
schemaType: '',
|
|
||||||
schema: {},
|
|
||||||
uid: '',
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const reducer = (state, action) => {
|
const reducer = (state, action) => {
|
||||||
@ -25,12 +15,21 @@ const reducer = (state, action) => {
|
|||||||
.update('components', () => fromJS(action.components))
|
.update('components', () => fromJS(action.components))
|
||||||
.update('contentTypes', () => fromJS(action.contentTypes))
|
.update('contentTypes', () => fromJS(action.contentTypes))
|
||||||
.update('isLoading', () => false);
|
.update('isLoading', () => false);
|
||||||
case 'CREATE_SCHEMA':
|
case 'CREATE_SCHEMA': {
|
||||||
console.log({ action });
|
const newSchema = {
|
||||||
return state
|
uid: action.uid,
|
||||||
.updateIn(['newSchema', 'schema'], () => fromJS(action.data))
|
isTemporary: true,
|
||||||
.updateIn(['newSchema', 'uid'], () => fromJS(action.uid))
|
schema: {
|
||||||
.updateIn(['newSchema', 'schemaType'], () => fromJS(action.schemaType));
|
...action.data,
|
||||||
|
attributes: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const key =
|
||||||
|
action.schemaType === 'contentType' ? 'contentTypes' : 'components';
|
||||||
|
|
||||||
|
return state.updateIn([key, action.uid], () => fromJS(newSchema));
|
||||||
|
}
|
||||||
|
|
||||||
case 'SET_MODIFIED_DATA':
|
case 'SET_MODIFIED_DATA':
|
||||||
return state
|
return state
|
||||||
.update('initialData', () => OrderedMap(action.schemaToSet))
|
.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 PropTypes from 'prop-types';
|
||||||
import {
|
import {
|
||||||
ButtonModal,
|
ButtonModal,
|
||||||
@ -18,15 +18,17 @@ 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';
|
||||||
|
import AttributeOption from '../../components/AttributeOption';
|
||||||
import ModalHeader from '../../components/ModalHeader';
|
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 getTrad from '../../utils/getTrad';
|
||||||
|
import getAttributes from './utils/attributes';
|
||||||
import forms from './utils/forms';
|
import forms from './utils/forms';
|
||||||
import { createUid } from './utils/createUid';
|
import { createUid } from './utils/createUid';
|
||||||
import init from './init';
|
import init from './init';
|
||||||
import reducer, { initialState } from './reducer';
|
import reducer, { initialState } from './reducer';
|
||||||
|
|
||||||
const getTrad = id => `${pluginId}.${id}`;
|
|
||||||
const NAVLINKS = [{ id: 'base' }, { id: 'advanced' }];
|
const NAVLINKS = [{ id: 'base' }, { id: 'advanced' }];
|
||||||
|
|
||||||
const FormModal = () => {
|
const FormModal = () => {
|
||||||
@ -34,40 +36,77 @@ const FormModal = () => {
|
|||||||
actionType: null,
|
actionType: null,
|
||||||
modalType: null,
|
modalType: null,
|
||||||
settingType: null,
|
settingType: null,
|
||||||
// uid: null,
|
for: null,
|
||||||
|
target: null,
|
||||||
|
attributeType: null,
|
||||||
};
|
};
|
||||||
const [state, setState] = useState(initialStateData);
|
const [state, setState] = useState(initialStateData);
|
||||||
const [reducerState, dispatch] = useReducer(reducer, initialState, init);
|
const [reducerState, dispatch] = useReducer(reducer, initialState, init);
|
||||||
const { push } = useHistory();
|
const { push } = useHistory();
|
||||||
const { search } = useLocation();
|
const { search } = useLocation();
|
||||||
const { formatMessage } = useGlobalContext();
|
const { formatMessage } = useGlobalContext();
|
||||||
const isOpen = !isEmpty(search);
|
|
||||||
const query = useQuery();
|
const query = useQuery();
|
||||||
|
const attributeOptionRef = useRef();
|
||||||
|
|
||||||
const { contentTypes, createSchema, initialData } = useDataManager();
|
const { contentTypes, createSchema, initialData } = useDataManager();
|
||||||
const { formErrors, modifiedData } = reducerState.toJS();
|
const { formErrors, modifiedData } = reducerState.toJS();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isOpen) {
|
if (!isEmpty(search)) {
|
||||||
setState({
|
setState({
|
||||||
actionType: query.get('actionType'),
|
actionType: query.get('actionType'),
|
||||||
modalType: query.get('modalType'),
|
modalType: query.get('modalType'),
|
||||||
settingType: query.get('settingType'),
|
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
|
// 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 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.${state.modalType}.header-create`
|
||||||
: 'modalForm.header-edit';
|
: '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 getNextSearch = nextTab => {
|
||||||
const newSearch = Object.keys(state).reduce((acc, current) => {
|
const newSearch = Object.keys(state).reduce((acc, current, index) => {
|
||||||
if (current !== 'settingType') {
|
if (current !== 'settingType') {
|
||||||
acc = `${acc}&${current}=${state[current]}`;
|
acc = `${acc}${index === 0 ? '' : '&'}${current}=${state[current]}`;
|
||||||
} else {
|
} else {
|
||||||
acc = `${acc}&${current}=${nextTab}`;
|
acc = `${acc}${index === 0 ? '' : '&'}${current}=${nextTab}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
@ -87,16 +126,21 @@ const FormModal = () => {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const schema = forms.contentType.schema(Object.keys(contentTypes));
|
const schema = forms[state.modalType].schema(Object.keys(contentTypes));
|
||||||
|
|
||||||
await schema.validate(modifiedData, { abortEarly: false });
|
await schema.validate(modifiedData, { abortEarly: false });
|
||||||
createSchema(modifiedData, state.modalType, createUid(modifiedData.name));
|
|
||||||
handleToggle();
|
createSchema(modifiedData, state.modalType, uid);
|
||||||
// push({ p})
|
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) {
|
} catch (err) {
|
||||||
const errors = getYupInnerErrors(err);
|
const errors = getYupInnerErrors(err);
|
||||||
// TODO
|
|
||||||
console.log({ errors });
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'SET_ERRORS',
|
type: 'SET_ERRORS',
|
||||||
errors,
|
errors,
|
||||||
@ -112,23 +156,33 @@ const FormModal = () => {
|
|||||||
type: 'RESET_PROPS',
|
type: 'RESET_PROPS',
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
const form = get(forms, [state.modalType, 'form', state.settingType], () => ({
|
|
||||||
items: [],
|
const onOpened = () => {
|
||||||
}));
|
if (state.modalType === 'chooseAttribute') {
|
||||||
|
attributeOptionRef.current.focus();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal isOpen={isOpen} onClosed={onClosed} onToggle={handleToggle}>
|
<Modal
|
||||||
|
isOpen={isOpen}
|
||||||
|
onOpened={onOpened}
|
||||||
|
onClosed={onClosed}
|
||||||
|
onToggle={handleToggle}
|
||||||
|
>
|
||||||
<HeaderModal>
|
<HeaderModal>
|
||||||
<ModalHeader
|
<ModalHeader
|
||||||
name={name}
|
name={state.target || name}
|
||||||
headerId={headerId}
|
headerId={headerId}
|
||||||
type={state.modalType || 'contentType'}
|
iconType={iconType || 'contentType'}
|
||||||
/>
|
/>
|
||||||
<section>
|
<section>
|
||||||
<HeaderModalTitle>
|
<HeaderModalTitle>
|
||||||
<FormattedMessage id={getTrad('configurations')}>
|
<FormattedMessage id={getModalTitleSubHeader()}>
|
||||||
{msg => <span>{upperFirst(msg)}</span>}
|
{msg => <span>{upperFirst(msg)}</span>}
|
||||||
</FormattedMessage>
|
</FormattedMessage>
|
||||||
|
{!isPickingAttribute && (
|
||||||
|
<>
|
||||||
<div className="settings-tabs">
|
<div className="settings-tabs">
|
||||||
<HeaderModalNavContainer>
|
<HeaderModalNavContainer>
|
||||||
{NAVLINKS.map((link, index) => {
|
{NAVLINKS.map((link, index) => {
|
||||||
@ -138,24 +192,68 @@ const FormModal = () => {
|
|||||||
key={link.id}
|
key={link.id}
|
||||||
{...link}
|
{...link}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setState(prev => ({ ...prev, settingType: link.id }));
|
setState(prev => ({
|
||||||
|
...prev,
|
||||||
|
settingType: link.id,
|
||||||
|
}));
|
||||||
push({ search: getNextSearch(link.id) });
|
push({ search: getNextSearch(link.id) });
|
||||||
}}
|
}}
|
||||||
nextTab={index === NAVLINKS.length - 1 ? 0 : index + 1}
|
nextTab={
|
||||||
|
index === NAVLINKS.length - 1 ? 0 : index + 1
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</HeaderModalNavContainer>
|
</HeaderModalNavContainer>
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
<hr />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</HeaderModalTitle>
|
</HeaderModalTitle>
|
||||||
</section>
|
</section>
|
||||||
</HeaderModal>
|
</HeaderModal>
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit}>
|
||||||
<ModalForm>
|
<ModalForm>
|
||||||
<ModalBody>
|
<ModalBody style={modalBodyStyle}>
|
||||||
<div className="container-fluid">
|
<div className="container-fluid">
|
||||||
{form(modifiedData).items.map((row, index) => {
|
{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 (
|
return (
|
||||||
<div className="row" key={index}>
|
<div className="row" key={index}>
|
||||||
{row.map(input => {
|
{row.map(input => {
|
||||||
@ -196,10 +294,12 @@ const FormModal = () => {
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
}
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
</ModalForm>
|
</ModalForm>
|
||||||
|
{!isPickingAttribute && (
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<section>
|
<section>
|
||||||
<ButtonModal
|
<ButtonModal
|
||||||
@ -210,6 +310,7 @@ const FormModal = () => {
|
|||||||
<ButtonModal message="form.button.done" type="submit" />
|
<ButtonModal message="form.button.done" type="submit" />
|
||||||
</section>
|
</section>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
|
)}
|
||||||
</form>
|
</form>
|
||||||
</Modal>
|
</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 * 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 pluginId from '../../../pluginId';
|
||||||
|
import getTrad from '../../../utils/getTrad';
|
||||||
import { createUid, nameToSlug } from './createUid';
|
import { createUid, nameToSlug } from './createUid';
|
||||||
|
|
||||||
yup.addMethod(yup.mixed, 'defined', function() {
|
yup.addMethod(yup.mixed, 'defined', function() {
|
||||||
@ -22,6 +23,57 @@ yup.addMethod(yup.string, 'unique', function(message, allReadyTakenValues) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const forms = {
|
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: {
|
contentType: {
|
||||||
schema(allReadyTakenValues) {
|
schema(allReadyTakenValues) {
|
||||||
return yup.object().shape({
|
return yup.object().shape({
|
||||||
|
|||||||
@ -13,14 +13,19 @@ import CustomLink from '../../components/CustomLink';
|
|||||||
import useDataManager from '../../hooks/useDataManager';
|
import useDataManager from '../../hooks/useDataManager';
|
||||||
import Wrapper from './Wrapper';
|
import Wrapper from './Wrapper';
|
||||||
|
|
||||||
// const displayNotificationCTNotSaved = () => {
|
const displayNotificationCTNotSaved = () => {
|
||||||
// strapi.notification.info(
|
strapi.notification.info(
|
||||||
// `${pluginId}.notification.info.contentType.creating.notSaved`
|
`${pluginId}.notification.info.contentType.creating.notSaved`
|
||||||
// );
|
);
|
||||||
// };
|
};
|
||||||
|
|
||||||
function LeftMenu() {
|
function LeftMenu() {
|
||||||
const { components, contentTypes, newSchema } = useDataManager();
|
const {
|
||||||
|
components,
|
||||||
|
contentTypes,
|
||||||
|
// initialData,
|
||||||
|
// modifiedData,
|
||||||
|
} = useDataManager();
|
||||||
const { currentEnvironment } = useGlobalContext();
|
const { currentEnvironment } = useGlobalContext();
|
||||||
const { push } = useHistory();
|
const { push } = useHistory();
|
||||||
const isProduction = currentEnvironment === 'production';
|
const isProduction = currentEnvironment === 'production';
|
||||||
@ -40,14 +45,24 @@ function LeftMenu() {
|
|||||||
})),
|
})),
|
||||||
obj => obj.title
|
obj => obj.title
|
||||||
);
|
);
|
||||||
const tempSchemaCT =
|
const canOpenModalCreateCTorComponent = () => {
|
||||||
newSchema.schemaType === 'contentType'
|
return (
|
||||||
? {
|
!Object.keys(contentTypes).some(
|
||||||
name: newSchema.uid,
|
ct => contentTypes[ct].isTemporary === true
|
||||||
title: newSchema.schema.name,
|
) &&
|
||||||
to: `/plugins/${pluginId}/content-types/${newSchema.uid}`,
|
!Object.keys(components).some(
|
||||||
|
component => components[component].isTemporary === true
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const handleClickOpenModal = type => {
|
||||||
|
if (canOpenModalCreateCTorComponent()) {
|
||||||
|
push({ search: `modalType=${type}&actionType=create&settingType=base` });
|
||||||
|
} else {
|
||||||
|
displayNotificationCTNotSaved();
|
||||||
}
|
}
|
||||||
: null;
|
};
|
||||||
|
|
||||||
const data = [
|
const data = [
|
||||||
{
|
{
|
||||||
name: 'models',
|
name: 'models',
|
||||||
@ -61,10 +76,7 @@ function LeftMenu() {
|
|||||||
disabled: isProduction,
|
disabled: isProduction,
|
||||||
id: `${pluginId}.button.model.create`,
|
id: `${pluginId}.button.model.create`,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
push({
|
handleClickOpenModal('contentType');
|
||||||
search:
|
|
||||||
'modalType=contentType&actionType=create&settingType=base',
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -75,7 +87,6 @@ function LeftMenu() {
|
|||||||
title: contentTypes[uid].schema.name,
|
title: contentTypes[uid].schema.name,
|
||||||
to: `/plugins/${pluginId}/content-types/${uid}`,
|
to: `/plugins/${pluginId}/content-types/${uid}`,
|
||||||
}))
|
}))
|
||||||
.concat(tempSchemaCT)
|
|
||||||
.filter(obj => obj !== null),
|
.filter(obj => obj !== null),
|
||||||
obj => obj.title
|
obj => obj.title
|
||||||
),
|
),
|
||||||
@ -92,9 +103,7 @@ function LeftMenu() {
|
|||||||
disabled: isProduction,
|
disabled: isProduction,
|
||||||
id: `${pluginId}.button.component.create`,
|
id: `${pluginId}.button.component.create`,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
push({
|
handleClickOpenModal('component');
|
||||||
search: 'modalType=component&actionType=create&settingType=base',
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -2,23 +2,7 @@
|
|||||||
"model": "Content Type",
|
"model": "Content Type",
|
||||||
"group": "Group",
|
"group": "Group",
|
||||||
"attribute.WYSIWYG": "Text (WYSIWYG)",
|
"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": "Add New Field",
|
||||||
"button.attributes.add.another": "Add Another Field",
|
"button.attributes.add.another": "Add Another Field",
|
||||||
"button.contentType.add": "Add a Content Type",
|
"button.contentType.add": "Add a Content Type",
|
||||||
@ -146,34 +130,7 @@
|
|||||||
"notification.success.message.contentType.edit": "Your Content Type has been updated",
|
"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.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.",
|
"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.title": "Add New Field",
|
||||||
"popUpForm.choose.attributes.header.subtitle.model": "Select a field for your content type",
|
"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",
|
"popUpForm.choose.attributes.header.subtitle.group": "Select a field for your group",
|
||||||
@ -221,13 +178,45 @@
|
|||||||
"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",
|
"attribute.boolean": "Boolean",
|
||||||
"modalForm.header-edit": "Edit {name}",
|
"attribute.boolean.description": "Yes or no, 1 or 0, true or false",
|
||||||
"modalForm.component.header-create": "Create a component",
|
"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",
|
"configurations": "configurations",
|
||||||
"contentType.displayName.label": "Display name",
|
"contentType.displayName.label": "Display name",
|
||||||
"contentType.collectionName.description": "Useful when the name of your Content Type and your table name differ",
|
"contentType.collectionName.description": "Useful when the name of your Content Type and your table name differ",
|
||||||
"contentType.collectionName.label": "Collection name",
|
"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