mirror of
https://github.com/strapi/strapi.git
synced 2025-12-26 14:44:31 +00:00
Change group to components
This commit is contained in:
parent
75378bfe06
commit
beb6884c4b
@ -8,7 +8,8 @@
|
||||
"attributes": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"required": true
|
||||
"required": true,
|
||||
"default": "something"
|
||||
},
|
||||
"is_available": {
|
||||
"type": "boolean",
|
||||
|
||||
@ -0,0 +1,17 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Flex = styled.div`
|
||||
display: flex;
|
||||
> button {
|
||||
cursor: pointer;
|
||||
padding-top: 0;
|
||||
}
|
||||
.trash-icon {
|
||||
color: #4b515a;
|
||||
}
|
||||
.button-wrapper {
|
||||
line-height: 35px;
|
||||
}
|
||||
`;
|
||||
|
||||
export default Flex;
|
||||
@ -0,0 +1,24 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const ImgWrapper = styled.div`
|
||||
width: 19px;
|
||||
height: 19px;
|
||||
margin: auto;
|
||||
margin-right: 19px;
|
||||
border-radius: 50%;
|
||||
background-color: ${({ hasErrors, isOpen }) => {
|
||||
if (hasErrors) {
|
||||
return '#FAA684';
|
||||
} else if (isOpen) {
|
||||
return '#AED4FB';
|
||||
} else {
|
||||
return '#F3F4F4';
|
||||
}
|
||||
}}
|
||||
text-align: center;
|
||||
line-height: 19px;
|
||||
|
||||
${({ isOpen }) => !isOpen && 'transform: rotate(180deg)'}
|
||||
`;
|
||||
|
||||
export default ImgWrapper;
|
||||
@ -0,0 +1,87 @@
|
||||
import styled, { css } from 'styled-components';
|
||||
import Flex from './Flex';
|
||||
|
||||
const Wrapper = styled(Flex)`
|
||||
height: 36px;
|
||||
padding: 0 10px 0 15px;
|
||||
justify-content: space-between;
|
||||
border: 1px solid
|
||||
${({ hasErrors, isOpen }) => {
|
||||
if (hasErrors) {
|
||||
return '#FFA784';
|
||||
} else if (isOpen) {
|
||||
return '#AED4FB';
|
||||
} else {
|
||||
return 'rgba(227, 233, 243, 0.75)';
|
||||
}
|
||||
}};
|
||||
|
||||
${({ doesPreviousFieldContainErrorsAndIsOpen }) => {
|
||||
if (doesPreviousFieldContainErrorsAndIsOpen) {
|
||||
return css`
|
||||
border-top: 1px solid #ffa784;
|
||||
`;
|
||||
}
|
||||
}}
|
||||
|
||||
${({ isFirst }) => {
|
||||
if (isFirst) {
|
||||
return css`
|
||||
border-top-right-radius: 2px;
|
||||
border-top-left-radius: 2px;
|
||||
`;
|
||||
}
|
||||
}}
|
||||
border-bottom: 0;
|
||||
line-height: 36px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
|
||||
background-color: ${({ hasErrors, isOpen }) => {
|
||||
if (hasErrors && isOpen) {
|
||||
return '#FFE9E0';
|
||||
} else if (isOpen) {
|
||||
return '#E6F0FB';
|
||||
} else {
|
||||
return '#ffffff';
|
||||
}
|
||||
}};
|
||||
|
||||
|
||||
${({ hasErrors, isOpen }) => {
|
||||
if (hasErrors) {
|
||||
return css`
|
||||
color: #f64d0a;
|
||||
font-weight: 600;
|
||||
`;
|
||||
}
|
||||
if (isOpen) {
|
||||
return css`
|
||||
color: #007eff;
|
||||
font-weight: 600;
|
||||
`;
|
||||
}
|
||||
}}
|
||||
|
||||
button,
|
||||
i, img {
|
||||
&:active,
|
||||
&:focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
${({ isOpen }) => {
|
||||
if (isOpen) {
|
||||
return css`
|
||||
&.trash-icon i {
|
||||
color: #007eff;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}}
|
||||
|
||||
webkit-font-smoothing: antialiased;
|
||||
`;
|
||||
|
||||
export default Wrapper;
|
||||
@ -0,0 +1,131 @@
|
||||
import React, { forwardRef } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { get } from 'lodash';
|
||||
|
||||
import pluginId from '../../pluginId';
|
||||
|
||||
import Grab from '../../assets/images/grab_icon.svg';
|
||||
import Logo from '../../assets/images/caret_top.svg';
|
||||
import LogoGrey from '../../assets/images/caret_top_grey.svg';
|
||||
import LogoError from '../../assets/images/caret_top_error.svg';
|
||||
import GrabBlue from '../../assets/images/grab_icon_blue.svg';
|
||||
import GrabError from '../../assets/images/grab_icon_error.svg';
|
||||
|
||||
import PreviewCarret from '../PreviewCarret';
|
||||
|
||||
import Flex from './Flex';
|
||||
import ImgWrapper from './ImgWrapper';
|
||||
import Wrapper from './Wrapper';
|
||||
|
||||
// eslint-disable-next-line react/display-name
|
||||
const ComponentBanner = forwardRef(
|
||||
(
|
||||
{
|
||||
doesPreviousFieldContainErrorsAndIsOpen,
|
||||
hasErrors,
|
||||
isFirst,
|
||||
isDragging,
|
||||
isOpen,
|
||||
mainField,
|
||||
modifiedData,
|
||||
name,
|
||||
onClick,
|
||||
removeField,
|
||||
style,
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
let logo = isOpen ? Logo : LogoGrey;
|
||||
let grab = isOpen ? GrabBlue : Grab;
|
||||
const opacity = isDragging ? 0.5 : 1;
|
||||
const displayedValue = get(
|
||||
modifiedData,
|
||||
[...name.split('.'), mainField],
|
||||
null
|
||||
);
|
||||
|
||||
if (hasErrors) {
|
||||
grab = GrabError;
|
||||
logo = LogoError;
|
||||
}
|
||||
|
||||
return (
|
||||
<div ref={ref}>
|
||||
{isDragging ? (
|
||||
<PreviewCarret isComponent />
|
||||
) : (
|
||||
<Wrapper
|
||||
doesPreviousFieldContainErrorsAndIsOpen={
|
||||
doesPreviousFieldContainErrorsAndIsOpen
|
||||
}
|
||||
hasErrors={hasErrors}
|
||||
isFirst={isFirst}
|
||||
isOpen={isOpen}
|
||||
onClick={onClick}
|
||||
style={{ opacity, ...style }}
|
||||
>
|
||||
<Flex>
|
||||
<ImgWrapper hasErrors={hasErrors} isOpen={isOpen}>
|
||||
<img src={logo} alt="logo" />
|
||||
</ImgWrapper>
|
||||
<FormattedMessage
|
||||
id={`${pluginId}.containers.Edit.pluginHeader.title.new`}
|
||||
>
|
||||
{msg => {
|
||||
return <span>{displayedValue || msg}</span>;
|
||||
}}
|
||||
</FormattedMessage>
|
||||
</Flex>
|
||||
<Flex className="button-wrapper">
|
||||
<button
|
||||
className="trash-icon"
|
||||
type="button"
|
||||
style={{ marginRight: 6 }}
|
||||
onClick={removeField}
|
||||
>
|
||||
<i className="fa fa-trash" />
|
||||
</button>
|
||||
<button type="button">
|
||||
<img
|
||||
src={grab}
|
||||
alt="grab icon"
|
||||
style={{ verticalAlign: 'unset' }}
|
||||
/>
|
||||
</button>
|
||||
</Flex>
|
||||
</Wrapper>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
ComponentBanner.defaultProps = {
|
||||
doesPreviousFieldContainErrorsAndIsOpen: false,
|
||||
hasErrors: false,
|
||||
isCreating: true,
|
||||
isDragging: false,
|
||||
isFirst: false,
|
||||
isOpen: false,
|
||||
mainField: 'id',
|
||||
onClick: () => {},
|
||||
removeField: () => {},
|
||||
style: {},
|
||||
};
|
||||
|
||||
ComponentBanner.propTypes = {
|
||||
doesPreviousFieldContainErrorsAndIsOpen: PropTypes.bool,
|
||||
hasErrors: PropTypes.bool,
|
||||
isFirst: PropTypes.bool,
|
||||
isDragging: PropTypes.bool,
|
||||
isOpen: PropTypes.bool,
|
||||
mainField: PropTypes.string,
|
||||
modifiedData: PropTypes.object,
|
||||
name: PropTypes.string,
|
||||
onClick: PropTypes.func,
|
||||
removeField: PropTypes.func,
|
||||
style: PropTypes.object,
|
||||
};
|
||||
|
||||
export default ComponentBanner;
|
||||
@ -0,0 +1,38 @@
|
||||
import styled, { css } from 'styled-components';
|
||||
|
||||
const Button = styled.div`
|
||||
width: 100%;
|
||||
height: 37px;
|
||||
margin-bottom: 27px;
|
||||
text-align: center;
|
||||
border: 1px solid rgba(227, 233, 243, 0.75);
|
||||
border-top: 1px solid
|
||||
${({ doesPreviousFieldContainErrorsAndIsClosed }) =>
|
||||
doesPreviousFieldContainErrorsAndIsClosed
|
||||
? '#FFA784'
|
||||
: 'rgba(227, 233, 243, 0.75)'};
|
||||
|
||||
border-bottom-left-radius: 2px;
|
||||
border-bottom-right-radius: 2px;
|
||||
${({ withBorderRadius }) => {
|
||||
if (withBorderRadius) {
|
||||
return css`
|
||||
border-radius: 2px;
|
||||
`;
|
||||
}
|
||||
}}
|
||||
|
||||
color: #007eff;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
line-height: 37px;
|
||||
cursor: pointer;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
> i {
|
||||
margin-right: 10px;
|
||||
}
|
||||
`;
|
||||
|
||||
export default Button;
|
||||
@ -0,0 +1,174 @@
|
||||
import React, { Fragment, useMemo, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { get } from 'lodash';
|
||||
import { DragSource, DropTarget } from 'react-dnd';
|
||||
import { getEmptyImage } from 'react-dnd-html5-backend';
|
||||
import { Collapse } from 'reactstrap';
|
||||
|
||||
import ItemTypes from '../../utils/ItemTypes';
|
||||
|
||||
import ComponentBanner from '../ComponentBanner';
|
||||
|
||||
import Form from './Form';
|
||||
import FormWrapper from './FormWrapper';
|
||||
|
||||
function ComponentCollapse({
|
||||
checkFormErrors,
|
||||
connectDragSource,
|
||||
connectDropTarget,
|
||||
connectDragPreview,
|
||||
doesPreviousFieldContainErrorsAndIsOpen,
|
||||
hasErrors,
|
||||
isDragging,
|
||||
isFirst,
|
||||
isOpen,
|
||||
layout,
|
||||
modifiedData,
|
||||
name,
|
||||
onChange,
|
||||
onClick,
|
||||
removeField,
|
||||
}) {
|
||||
const mainField = useMemo(
|
||||
() => get(layout, ['settings', 'mainField'], 'id'),
|
||||
[layout]
|
||||
);
|
||||
|
||||
const fields = get(layout, ['layouts', 'edit'], []);
|
||||
const ref = React.useRef(null);
|
||||
|
||||
connectDragSource(ref);
|
||||
connectDropTarget(ref);
|
||||
|
||||
useEffect(() => {
|
||||
connectDragPreview(getEmptyImage(), { captureDraggingState: true });
|
||||
}, [connectDragPreview]);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<ComponentBanner
|
||||
doesPreviousFieldContainErrorsAndIsOpen={
|
||||
doesPreviousFieldContainErrorsAndIsOpen
|
||||
}
|
||||
hasErrors={hasErrors}
|
||||
isFirst={isFirst}
|
||||
isDragging={isDragging}
|
||||
isOpen={isOpen}
|
||||
modifiedData={modifiedData}
|
||||
mainField={mainField}
|
||||
name={name}
|
||||
onClick={onClick}
|
||||
ref={ref}
|
||||
removeField={removeField}
|
||||
/>
|
||||
<Collapse isOpen={isOpen} style={{ backgroundColor: '#FAFAFB' }}>
|
||||
<FormWrapper hasErrors={hasErrors} isOpen={isOpen}>
|
||||
{fields.map((fieldRow, key) => {
|
||||
return (
|
||||
<div className="row" key={key}>
|
||||
{fieldRow.map(field => {
|
||||
const keys = `${name}.${field.name}`;
|
||||
|
||||
return (
|
||||
<Form
|
||||
checkFormErrors={checkFormErrors}
|
||||
key={keys}
|
||||
modifiedData={modifiedData}
|
||||
keys={keys}
|
||||
fieldName={field.name}
|
||||
layout={layout}
|
||||
onChange={onChange}
|
||||
shouldCheckErrors={hasErrors}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</FormWrapper>
|
||||
</Collapse>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
ComponentCollapse.defaultProps = {
|
||||
addRelation: () => {},
|
||||
doesPreviousFieldContainErrorsAndIsOpen: false,
|
||||
hasErrors: false,
|
||||
isCreating: true,
|
||||
isDragging: false,
|
||||
isFirst: false,
|
||||
isOpen: false,
|
||||
layout: {},
|
||||
move: () => {},
|
||||
removeField: () => {},
|
||||
};
|
||||
|
||||
ComponentCollapse.propTypes = {
|
||||
checkFormErrors: PropTypes.func.isRequired,
|
||||
connectDragPreview: PropTypes.func.isRequired,
|
||||
connectDragSource: PropTypes.func.isRequired,
|
||||
connectDropTarget: PropTypes.func.isRequired,
|
||||
doesPreviousFieldContainErrorsAndIsOpen: PropTypes.bool,
|
||||
hasErrors: PropTypes.bool,
|
||||
isDragging: PropTypes.bool,
|
||||
isFirst: PropTypes.bool,
|
||||
isOpen: PropTypes.bool,
|
||||
layout: PropTypes.object,
|
||||
modifiedData: PropTypes.object,
|
||||
name: PropTypes.string.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
onClick: PropTypes.func.isRequired,
|
||||
removeField: PropTypes.func,
|
||||
};
|
||||
|
||||
export default DropTarget(
|
||||
ItemTypes.COMPONENT,
|
||||
{
|
||||
canDrop: () => true,
|
||||
hover(props, monitor) {
|
||||
const { id: draggedId } = monitor.getItem();
|
||||
const { id: overId } = props;
|
||||
|
||||
if (draggedId !== overId) {
|
||||
const { index: overIndex } = props.findField(overId);
|
||||
props.move(draggedId, overIndex, props.componentName);
|
||||
}
|
||||
},
|
||||
},
|
||||
connect => ({
|
||||
connectDropTarget: connect.dropTarget(),
|
||||
})
|
||||
)(
|
||||
DragSource(
|
||||
ItemTypes.COMPONENT,
|
||||
{
|
||||
beginDrag: props => {
|
||||
props.collapseAll();
|
||||
props.resetErrors();
|
||||
|
||||
return {
|
||||
id: props.id,
|
||||
mainField: get(props.layout, ['settings', 'mainField'], 'id'),
|
||||
modifiedData: props.modifiedData,
|
||||
name: props.name,
|
||||
originalIndex: props.findField(props.id).index,
|
||||
};
|
||||
},
|
||||
// COMMENTING ON PURPOSE NOT SURE IF WE ALLOW DROPPING OUTSIDE THE DROP TARGET
|
||||
// endDrag(props, monitor) {
|
||||
// const { id: droppedId, originalIndex } = monitor.getItem();
|
||||
// const didDrop = monitor.didDrop();
|
||||
|
||||
// if (!didDrop) {
|
||||
// props.move(droppedId, originalIndex, props.groupName);
|
||||
// }
|
||||
// },
|
||||
},
|
||||
(connect, monitor) => ({
|
||||
connectDragSource: connect.dragSource(),
|
||||
connectDragPreview: connect.dragPreview(),
|
||||
isDragging: monitor.isDragging(),
|
||||
})
|
||||
)(ComponentCollapse)
|
||||
);
|
||||
@ -0,0 +1,13 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const EmptyComponent = styled.div`
|
||||
height: 72px;
|
||||
border: 1px solid rgba(227, 233, 243, 0.75);
|
||||
border-top-left-radius: 2px;
|
||||
border-top-right-radius: 2px;
|
||||
border-bottom: 0;
|
||||
line-height: 73px;
|
||||
text-align: center;
|
||||
`;
|
||||
|
||||
export default EmptyComponent;
|
||||
@ -0,0 +1,74 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import React, { memo, useMemo } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { get } from 'lodash';
|
||||
|
||||
import Inputs from '../Inputs';
|
||||
import SelectWrapper from '../SelectWrapper';
|
||||
|
||||
const Form = ({
|
||||
checkFormErrors,
|
||||
keys,
|
||||
layout,
|
||||
modifiedData,
|
||||
fieldName,
|
||||
onChange,
|
||||
shouldCheckErrors,
|
||||
}) => {
|
||||
const currentField = useMemo(() => {
|
||||
// We are not providing any deps to the hook in purpose
|
||||
// We don't need any recalculation there since these values are not changed in the component's lifecycle
|
||||
return get(layout, ['schema', 'attributes', fieldName], '');
|
||||
}, []);
|
||||
const currentFieldMeta = useMemo(() => {
|
||||
return get(layout, ['metadatas', fieldName, 'edit'], {});
|
||||
}, []);
|
||||
|
||||
if (currentField.type === 'relation') {
|
||||
return (
|
||||
<div className="col-6" key={keys}>
|
||||
<SelectWrapper
|
||||
{...currentFieldMeta}
|
||||
name={keys}
|
||||
plugin={currentField.plugin}
|
||||
relationType={currentField.relationType}
|
||||
targetModel={currentField.targetModel}
|
||||
value={get(modifiedData, keys)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Inputs
|
||||
key={keys}
|
||||
layout={layout}
|
||||
modifiedData={modifiedData}
|
||||
keys={keys}
|
||||
name={fieldName}
|
||||
onBlur={shouldCheckErrors ? checkFormErrors : null}
|
||||
onChange={({ target: { value } }) => {
|
||||
onChange({
|
||||
target: { name: keys, value },
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
Form.defaultProps = {
|
||||
checkFormErrors: () => {},
|
||||
shouldCheckErrors: false,
|
||||
};
|
||||
|
||||
Form.propTypes = {
|
||||
checkFormErrors: PropTypes.func,
|
||||
fieldName: PropTypes.string.isRequired,
|
||||
keys: PropTypes.string.isRequired,
|
||||
layout: PropTypes.object.isRequired,
|
||||
modifiedData: PropTypes.object.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
shouldCheckErrors: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default memo(Form);
|
||||
@ -0,0 +1,20 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const FormWrapper = styled.div`
|
||||
padding-top: 27px;
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
padding-bottom: 8px;
|
||||
border-top: 1px solid
|
||||
${({ hasErrors, isOpen }) => {
|
||||
if (hasErrors) {
|
||||
return '#ffa784';
|
||||
} else if (isOpen) {
|
||||
return '#AED4FB';
|
||||
} else {
|
||||
return 'rgba(227, 233, 243, 0.75)';
|
||||
}
|
||||
}};
|
||||
`;
|
||||
|
||||
export default FormWrapper;
|
||||
@ -0,0 +1,67 @@
|
||||
import React, { memo } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import pluginId from '../../pluginId';
|
||||
import Form from './Form';
|
||||
import P from './P';
|
||||
import NonRepeatableWrapper from './NonRepeatableWrapper';
|
||||
|
||||
const NonRepeatableComponent = ({
|
||||
addField,
|
||||
isInitialized,
|
||||
fields,
|
||||
modifiedData,
|
||||
name,
|
||||
layout,
|
||||
onChange,
|
||||
}) => {
|
||||
if (!isInitialized) {
|
||||
return (
|
||||
<NonRepeatableWrapper isEmpty onClick={() => addField(name, false)}>
|
||||
<div />
|
||||
<FormattedMessage id={`${pluginId}.components.Group.empty.repeatable`}>
|
||||
{msg => <P style={{ paddingTop: 75 }}>{msg}</P>}
|
||||
</FormattedMessage>
|
||||
</NonRepeatableWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<NonRepeatableWrapper>
|
||||
{fields.map((fieldRow, key) => {
|
||||
return (
|
||||
<div className="row" key={key}>
|
||||
{fieldRow.map(field => {
|
||||
const keys = `${name}.${field.name}`;
|
||||
|
||||
return (
|
||||
<Form
|
||||
key={keys}
|
||||
modifiedData={modifiedData}
|
||||
keys={keys}
|
||||
fieldName={field.name}
|
||||
layout={layout}
|
||||
onChange={onChange}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</NonRepeatableWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
NonRepeatableComponent.defaultProps = {};
|
||||
NonRepeatableComponent.propTypes = {
|
||||
addField: PropTypes.func.isRequired,
|
||||
isInitialized: PropTypes.bool,
|
||||
fields: PropTypes.array,
|
||||
modifiedData: PropTypes.object,
|
||||
name: PropTypes.string.isRequired,
|
||||
layout: PropTypes.object,
|
||||
onChange: PropTypes.func,
|
||||
};
|
||||
|
||||
export default memo(NonRepeatableComponent);
|
||||
@ -0,0 +1,66 @@
|
||||
import styled, { css } from 'styled-components';
|
||||
|
||||
const NonRepeatableWrapper = styled.div`
|
||||
margin: 0 15px !important;
|
||||
padding: 0 20px !important;
|
||||
|
||||
${({ isEmpty }) => {
|
||||
if (isEmpty) {
|
||||
return css`
|
||||
position: relative;
|
||||
height: 108px;
|
||||
margin-bottom: 21px !important;
|
||||
background-color: #fafafb;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
border-radius: 2px;
|
||||
|
||||
> div {
|
||||
position: absolute;
|
||||
top: 30px;
|
||||
left: calc(50% - 18px);
|
||||
height: 36px;
|
||||
width: 36px;
|
||||
line-height: 38px;
|
||||
border-radius: 50%;
|
||||
background-color: #f3f4f4;
|
||||
cursor: pointer;
|
||||
&:before {
|
||||
content: '\f067';
|
||||
font-family: FontAwesome;
|
||||
font-size: 15px;
|
||||
color: #b4b6ba;
|
||||
}
|
||||
}
|
||||
border: 1px solid transparent;
|
||||
|
||||
&:hover {
|
||||
border: 1px solid #aed4fb;
|
||||
background-color: #e6f0fb;
|
||||
|
||||
> p {
|
||||
color: #007eff;
|
||||
}
|
||||
|
||||
> div {
|
||||
background-color: #aed4fb;
|
||||
&:before {
|
||||
content: '\f067';
|
||||
font-family: FontAwesome;
|
||||
font-size: 15px;
|
||||
color: #007eff;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
return css`
|
||||
padding-top: 21px !important;
|
||||
background-color: #f7f8f8;
|
||||
margin-bottom: 18px !important;
|
||||
`;
|
||||
}}
|
||||
`;
|
||||
|
||||
export default NonRepeatableWrapper;
|
||||
@ -0,0 +1,9 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const P = styled.p`
|
||||
color: #9ea7b8;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
`;
|
||||
|
||||
export default P;
|
||||
@ -0,0 +1,41 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const ResetComponent = styled.div`
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 15px;
|
||||
display: flex;
|
||||
|
||||
cursor: pointer;
|
||||
color: #4b515a;
|
||||
|
||||
> span {
|
||||
margin-right: 10px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
> div {
|
||||
background-color: #faa684;
|
||||
}
|
||||
color: #f64d0a;
|
||||
> span {
|
||||
display: initial;
|
||||
}
|
||||
}
|
||||
|
||||
> div {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background-color: #f3f4f4;
|
||||
text-align: center;
|
||||
border-radius: 2px;
|
||||
&:after {
|
||||
content: '\f1f8';
|
||||
font-size: 14px;
|
||||
font-family: FontAwesome;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default ResetComponent;
|
||||
@ -0,0 +1,273 @@
|
||||
import React, { useEffect, useReducer, memo } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { get, size } from 'lodash';
|
||||
|
||||
import pluginId from '../../pluginId';
|
||||
import { useEditView } from '../../contexts/EditView';
|
||||
import Button from './Button';
|
||||
import ComponentCollapse from './ComponentCollapse';
|
||||
import EmptyComponent from './EmptyComponent';
|
||||
import P from './P';
|
||||
import ResetComponent from './ResetComponent';
|
||||
import init from './init';
|
||||
import reducer, { initialState } from './reducer';
|
||||
import NonRepeatableComponent from './NonRepeatableComponent';
|
||||
|
||||
function ComponentField({
|
||||
addField,
|
||||
componentErrorKeys,
|
||||
moveComponentField,
|
||||
componentValue,
|
||||
isRepeatable,
|
||||
label,
|
||||
layout,
|
||||
min,
|
||||
max,
|
||||
modifiedData,
|
||||
name,
|
||||
onChange,
|
||||
removeField,
|
||||
}) {
|
||||
const {
|
||||
checkFormErrors,
|
||||
didCheckErrors,
|
||||
errors,
|
||||
resetErrors,
|
||||
resetComponentData,
|
||||
} = useEditView();
|
||||
const fields = get(layout, ['layouts', 'edit'], []);
|
||||
const [state, dispatch] = useReducer(reducer, initialState, () =>
|
||||
init(initialState, componentValue)
|
||||
);
|
||||
const { collapses } = state.toJS();
|
||||
|
||||
const findField = React.useCallback(
|
||||
id => {
|
||||
const field = componentValue.filter(
|
||||
current => current._temp__id === id
|
||||
)[0];
|
||||
|
||||
return {
|
||||
field,
|
||||
index: componentValue.indexOf(field),
|
||||
};
|
||||
},
|
||||
[componentValue]
|
||||
);
|
||||
|
||||
const move = React.useCallback(
|
||||
(id, atIndex) => {
|
||||
const { index } = findField(id);
|
||||
|
||||
moveComponentField(index, atIndex, name);
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[componentValue]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const collapsesToOpen = Object.keys(errors)
|
||||
.filter(errorPath => errorPath.split('.')[0] === name && isRepeatable)
|
||||
.map(errorPath => errorPath.split('.')[1]);
|
||||
|
||||
if (collapsesToOpen.length > 0) {
|
||||
dispatch({
|
||||
type: 'OPEN_COLLAPSES_THAT_HAVE_ERRORS',
|
||||
collapsesToOpen: collapsesToOpen.filter(
|
||||
(v, index) => collapsesToOpen.indexOf(v) === index
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [didCheckErrors]);
|
||||
|
||||
const componentValueLength = size(componentValue);
|
||||
const isInitialized = get(modifiedData, name, null) !== null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="row">
|
||||
<div
|
||||
className="col-12"
|
||||
style={{
|
||||
paddingTop: 0,
|
||||
marginTop: '-2px',
|
||||
paddingBottom: isRepeatable ? 7 : 14,
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
fontSize: 13,
|
||||
fontWeight: 500,
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
{isRepeatable && `(${componentValueLength})`}
|
||||
</span>
|
||||
{!isRepeatable && isInitialized && (
|
||||
<ResetComponent
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
resetComponentData(name);
|
||||
}}
|
||||
>
|
||||
<FormattedMessage id={`${pluginId}.components.Group.reset`} />
|
||||
<div />
|
||||
</ResetComponent>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{!isRepeatable ? (
|
||||
<NonRepeatableComponent
|
||||
addField={addField}
|
||||
isInitialized={isInitialized}
|
||||
fields={fields}
|
||||
modifiedData={modifiedData}
|
||||
layout={layout}
|
||||
name={name}
|
||||
onChange={onChange}
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
margin: '0 15px',
|
||||
}}
|
||||
>
|
||||
{componentValue.length === 0 && (
|
||||
<EmptyComponent>
|
||||
<FormattedMessage
|
||||
id={`${pluginId}.components.Group.empty.repeatable`}
|
||||
>
|
||||
{msg => <P>{msg}</P>}
|
||||
</FormattedMessage>
|
||||
</EmptyComponent>
|
||||
)}
|
||||
{componentValue.map((field, index) => {
|
||||
const componentFieldName = `${name}.${index}`;
|
||||
const doesPreviousFieldContainErrorsAndIsOpen =
|
||||
componentErrorKeys.includes(`${name}.${index - 1}`) &&
|
||||
index !== 0 &&
|
||||
collapses[index - 1].isOpen === false;
|
||||
const hasErrors = componentErrorKeys.includes(componentFieldName);
|
||||
|
||||
return (
|
||||
<ComponentCollapse
|
||||
key={field._temp__id}
|
||||
checkFormErrors={checkFormErrors}
|
||||
collapseAll={() => {
|
||||
dispatch({
|
||||
type: 'COLLAPSE_ALL',
|
||||
});
|
||||
}}
|
||||
doesPreviousFieldContainErrorsAndIsOpen={
|
||||
doesPreviousFieldContainErrorsAndIsOpen
|
||||
}
|
||||
onClick={() => {
|
||||
dispatch({
|
||||
type: 'TOGGLE_COLLAPSE',
|
||||
index,
|
||||
});
|
||||
}}
|
||||
findField={findField}
|
||||
componentName={name}
|
||||
hasErrors={hasErrors}
|
||||
isFirst={index === 0}
|
||||
isOpen={collapses[index].isOpen}
|
||||
id={field._temp__id}
|
||||
layout={layout}
|
||||
modifiedData={modifiedData}
|
||||
move={move}
|
||||
name={componentFieldName}
|
||||
onChange={onChange}
|
||||
removeField={e => {
|
||||
e.stopPropagation();
|
||||
|
||||
if (componentValue.length - 1 < min) {
|
||||
strapi.notification.info(
|
||||
`${pluginId}.components.Group.notification.info.minimum-requirement`
|
||||
);
|
||||
}
|
||||
|
||||
const shouldAddEmptyField = componentValue.length - 1 < min;
|
||||
|
||||
dispatch({
|
||||
type: 'REMOVE_COLLAPSE',
|
||||
index,
|
||||
shouldAddEmptyField,
|
||||
});
|
||||
removeField(`${name}.${index}`, shouldAddEmptyField);
|
||||
}}
|
||||
resetErrors={resetErrors}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
<Button
|
||||
doesPreviousFieldContainErrorsAndIsClosed={
|
||||
componentValueLength > 0 &&
|
||||
componentErrorKeys.includes(
|
||||
`${name}.${componentValueLength - 1}`
|
||||
) &&
|
||||
collapses[componentValueLength - 1].isOpen === false
|
||||
}
|
||||
onClick={() => {
|
||||
if (componentValue.length < max) {
|
||||
addField(name);
|
||||
|
||||
dispatch({
|
||||
type: 'ADD_NEW_FIELD',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
strapi.notification.info(
|
||||
`${pluginId}.components.Group.notification.info.maximum-requirement`
|
||||
);
|
||||
}}
|
||||
withBorderRadius={false}
|
||||
>
|
||||
<i className="fa fa-plus" />
|
||||
<FormattedMessage
|
||||
id={`${pluginId}.containers.EditView.Group.add.new`}
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
ComponentField.defaultProps = {
|
||||
addRelation: () => {},
|
||||
componentErrorKeys: [],
|
||||
componentValue: {},
|
||||
label: '',
|
||||
layout: {},
|
||||
max: Infinity,
|
||||
min: -Infinity,
|
||||
modifiedData: {},
|
||||
};
|
||||
|
||||
ComponentField.propTypes = {
|
||||
addField: PropTypes.func.isRequired,
|
||||
addRelation: PropTypes.func,
|
||||
componentErrorKeys: PropTypes.array,
|
||||
componentValue: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
|
||||
isRepeatable: PropTypes.bool.isRequired,
|
||||
label: PropTypes.string,
|
||||
layout: PropTypes.object,
|
||||
max: PropTypes.number,
|
||||
min: PropTypes.number,
|
||||
modifiedData: PropTypes.object,
|
||||
moveComponentField: PropTypes.func.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
pathname: PropTypes.string.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
removeField: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default memo(ComponentField);
|
||||
@ -0,0 +1,18 @@
|
||||
import { fromJS } from 'immutable';
|
||||
import { isArray } from 'lodash';
|
||||
|
||||
function init(initialState, componentValues) {
|
||||
return initialState.update('collapses', list => {
|
||||
if (isArray(componentValues)) {
|
||||
return fromJS(
|
||||
componentValues.map(() => ({
|
||||
isOpen: false,
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
return list;
|
||||
});
|
||||
}
|
||||
|
||||
export default init;
|
||||
@ -0,0 +1,68 @@
|
||||
import { fromJS } from 'immutable';
|
||||
|
||||
const initialState = fromJS({ collapses: [], collapsesToOpen: [] });
|
||||
|
||||
function reducer(state, action) {
|
||||
switch (action.type) {
|
||||
case 'ADD_NEW_FIELD':
|
||||
return state.update('collapses', list => {
|
||||
return list
|
||||
.map(obj => obj.update('isOpen', () => false))
|
||||
.push(fromJS({ isOpen: true }));
|
||||
});
|
||||
case 'COLLAPSE_ALL':
|
||||
return state
|
||||
.update('collapsesToOpen', () => fromJS([]))
|
||||
.update('collapses', list =>
|
||||
list.map(obj => obj.update('isOpen', () => false))
|
||||
);
|
||||
case 'OPEN_COLLAPSES_THAT_HAVE_ERRORS':
|
||||
return state
|
||||
.update('collapsesToOpen', () => fromJS(action.collapsesToOpen))
|
||||
.update('collapses', list => {
|
||||
return list.map((obj, index) => {
|
||||
if (action.collapsesToOpen.indexOf(index.toString()) !== -1) {
|
||||
return obj.update('isOpen', () => true);
|
||||
}
|
||||
|
||||
return obj.update('isOpen', () => false);
|
||||
});
|
||||
});
|
||||
case 'TOGGLE_COLLAPSE':
|
||||
return state.update('collapses', list => {
|
||||
return list.map((obj, index) => {
|
||||
if (index === action.index) {
|
||||
return obj.update('isOpen', v => !v);
|
||||
}
|
||||
|
||||
if (
|
||||
state
|
||||
.get('collapsesToOpen')
|
||||
.toJS()
|
||||
.indexOf(index.toString()) !== -1
|
||||
) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
return obj.update('isOpen', () => false);
|
||||
});
|
||||
});
|
||||
|
||||
case 'REMOVE_COLLAPSE':
|
||||
return state
|
||||
.removeIn(['collapses', action.index])
|
||||
.update('collapses', list => list.map(obj => obj.set('isOpen', false)))
|
||||
.update('collapses', list => {
|
||||
if (action.shouldAddEmptyField) {
|
||||
return list.push(fromJS({ isOpen: true }));
|
||||
}
|
||||
|
||||
return list;
|
||||
});
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
export default reducer;
|
||||
export { initialState };
|
||||
@ -3,7 +3,7 @@ import { useDragLayer } from 'react-dnd';
|
||||
|
||||
import ItemTypes from '../../utils/ItemTypes';
|
||||
|
||||
import GroupBanner from '../GroupBanner';
|
||||
import ComponentBanner from '../ComponentBanner';
|
||||
import RelationItem from '../SelectMany/Relation';
|
||||
import { Li } from '../SelectMany/components';
|
||||
import DraggedField from '../DraggedField';
|
||||
@ -54,9 +54,9 @@ const CustomDragLayer = () => {
|
||||
switch (itemType) {
|
||||
case ItemTypes.FIELD:
|
||||
return <DraggedField name={item.id} selectedItem={item.name} />;
|
||||
case ItemTypes.GROUP:
|
||||
case ItemTypes.COMPONENT:
|
||||
return (
|
||||
<GroupBanner
|
||||
<ComponentBanner
|
||||
{...item}
|
||||
isOpen
|
||||
style={{
|
||||
|
||||
@ -18,7 +18,7 @@ const DraggedField = forwardRef(
|
||||
children,
|
||||
count,
|
||||
goTo,
|
||||
groupUid,
|
||||
componentUid,
|
||||
isDragging,
|
||||
isDraggingSibling,
|
||||
isHidden,
|
||||
@ -127,7 +127,7 @@ const DraggedField = forwardRef(
|
||||
</RemoveWrapper>
|
||||
</SubWrapper>
|
||||
)}
|
||||
{type === 'group' && (
|
||||
{type === 'component' && (
|
||||
<FormattedMessage
|
||||
id={`${pluginId}.components.FieldItem.linkToGroupLayout`}
|
||||
>
|
||||
@ -137,7 +137,7 @@ const DraggedField = forwardRef(
|
||||
e.stopPropagation();
|
||||
|
||||
goTo(
|
||||
`/plugins/${pluginId}/ctm-configurations/edit-settings/groups/${groupUid}`
|
||||
`/plugins/${pluginId}/ctm-configurations/edit-settings/components/${componentUid}`
|
||||
);
|
||||
}}
|
||||
>
|
||||
@ -155,7 +155,7 @@ DraggedField.defaultProps = {
|
||||
children: null,
|
||||
count: 1,
|
||||
goTo: () => {},
|
||||
groupUid: null,
|
||||
componentUid: null,
|
||||
isDragging: false,
|
||||
isDraggingSibling: false,
|
||||
isHidden: false,
|
||||
@ -172,7 +172,7 @@ DraggedField.propTypes = {
|
||||
children: PropTypes.node,
|
||||
count: PropTypes.number,
|
||||
goTo: PropTypes.func,
|
||||
groupUid: PropTypes.string,
|
||||
componentUid: PropTypes.string,
|
||||
isDragging: PropTypes.bool,
|
||||
isDraggingSibling: PropTypes.bool,
|
||||
isHidden: PropTypes.bool,
|
||||
|
||||
@ -11,8 +11,8 @@ const DraggedFieldWithPreview = forwardRef(
|
||||
(
|
||||
{
|
||||
goTo,
|
||||
groupUid,
|
||||
groupLayouts,
|
||||
componentUid,
|
||||
componentLayouts,
|
||||
isDragging,
|
||||
isDraggingSibling,
|
||||
label,
|
||||
@ -34,11 +34,18 @@ const DraggedFieldWithPreview = forwardRef(
|
||||
const isFullSize = size === 12;
|
||||
const display = isFullSize && dragStart ? 'none' : '';
|
||||
const width = isFullSize && dragStart ? 0 : '100%';
|
||||
const higherFields = ['json', 'text', 'file', 'media', 'group', 'richtext'];
|
||||
const higherFields = [
|
||||
'json',
|
||||
'text',
|
||||
'file',
|
||||
'media',
|
||||
'component',
|
||||
'richtext',
|
||||
];
|
||||
const withLongerHeight = higherFields.includes(type) && !dragStart;
|
||||
|
||||
const groupData = get(groupLayouts, [groupUid], {});
|
||||
const groupLayout = get(groupData, ['layouts', 'edit'], []);
|
||||
const componentData = get(componentLayouts, [componentUid], {});
|
||||
const componentLayout = get(componentData, ['layouts', 'edit'], []);
|
||||
const getWrapperWitdh = colNum => `${(1 / 12) * colNum * 100}%`;
|
||||
|
||||
return (
|
||||
@ -69,7 +76,7 @@ const DraggedFieldWithPreview = forwardRef(
|
||||
<div className="sub" style={{ width, opacity }}>
|
||||
<DraggedField
|
||||
goTo={goTo}
|
||||
groupUid={groupUid}
|
||||
componentUid={componentUid}
|
||||
isHidden={isHidden}
|
||||
isDraggingSibling={isDraggingSibling}
|
||||
label={label}
|
||||
@ -82,10 +89,10 @@ const DraggedFieldWithPreview = forwardRef(
|
||||
type={type}
|
||||
withLongerHeight={withLongerHeight}
|
||||
>
|
||||
{type === 'group' &&
|
||||
groupLayout.map((row, i) => {
|
||||
{type === 'component' &&
|
||||
componentLayout.map((row, i) => {
|
||||
const marginBottom =
|
||||
i === groupLayout.length - 1 ? '29px' : '';
|
||||
i === componentLayout.length - 1 ? '29px' : '';
|
||||
const marginTop = i === 0 ? '5px' : '';
|
||||
|
||||
return (
|
||||
@ -99,12 +106,12 @@ const DraggedFieldWithPreview = forwardRef(
|
||||
>
|
||||
{row.map(field => {
|
||||
const fieldType = get(
|
||||
groupData,
|
||||
componentData,
|
||||
['schema', 'attributes', field.name, 'type'],
|
||||
''
|
||||
);
|
||||
const label = get(
|
||||
groupData,
|
||||
componentData,
|
||||
['metadatas', field.name, 'edit', 'label'],
|
||||
''
|
||||
);
|
||||
@ -143,8 +150,8 @@ const DraggedFieldWithPreview = forwardRef(
|
||||
|
||||
DraggedFieldWithPreview.defaultProps = {
|
||||
goTo: () => {},
|
||||
groupLayouts: {},
|
||||
groupUid: null,
|
||||
componentLayouts: {},
|
||||
componentUid: null,
|
||||
isDragging: false,
|
||||
isDraggingSibling: false,
|
||||
label: '',
|
||||
@ -160,8 +167,8 @@ DraggedFieldWithPreview.defaultProps = {
|
||||
|
||||
DraggedFieldWithPreview.propTypes = {
|
||||
goTo: PropTypes.func,
|
||||
groupLayouts: PropTypes.object,
|
||||
groupUid: PropTypes.string,
|
||||
componentLayouts: PropTypes.object,
|
||||
componentUid: PropTypes.string,
|
||||
isDragging: PropTypes.bool,
|
||||
isDraggingSibling: PropTypes.bool,
|
||||
label: PropTypes.string,
|
||||
|
||||
@ -10,7 +10,7 @@ import DraggedFieldWithPreview from '../DraggedFieldWithPreview';
|
||||
import ItemTypes from '../../utils/ItemTypes';
|
||||
|
||||
const Item = ({
|
||||
groupUid,
|
||||
componentUid,
|
||||
itemIndex,
|
||||
moveItem,
|
||||
moveRow,
|
||||
@ -22,7 +22,7 @@ const Item = ({
|
||||
}) => {
|
||||
const {
|
||||
goTo,
|
||||
groupLayouts,
|
||||
componentLayouts,
|
||||
metadatas,
|
||||
setEditFieldToSelect,
|
||||
selectedItemName,
|
||||
@ -201,8 +201,8 @@ const Item = ({
|
||||
return (
|
||||
<DraggedFieldWithPreview
|
||||
goTo={goTo}
|
||||
groupUid={groupUid}
|
||||
groupLayouts={groupLayouts}
|
||||
componentUid={componentUid}
|
||||
componentLayouts={componentLayouts}
|
||||
isDragging={isDragging}
|
||||
label={get(metadatas, [name, 'edit', 'label'], '')}
|
||||
name={name}
|
||||
@ -222,12 +222,12 @@ const Item = ({
|
||||
};
|
||||
|
||||
Item.defaultProps = {
|
||||
groupUid: '',
|
||||
componentUid: '',
|
||||
type: 'string',
|
||||
};
|
||||
|
||||
Item.propTypes = {
|
||||
groupUid: PropTypes.string,
|
||||
componentUid: PropTypes.string,
|
||||
itemIndex: PropTypes.number.isRequired,
|
||||
moveItem: PropTypes.func.isRequired,
|
||||
moveRow: PropTypes.func.isRequired,
|
||||
|
||||
@ -19,9 +19,9 @@ const FieldsReorder = ({ className }) => {
|
||||
onAddData,
|
||||
removeField,
|
||||
} = useLayoutDnd();
|
||||
const getGroup = useCallback(
|
||||
const getComponent = useCallback(
|
||||
attributeName => {
|
||||
return get(attributes, [attributeName, 'group'], '');
|
||||
return get(attributes, [attributeName, 'component'], '');
|
||||
},
|
||||
[attributes]
|
||||
);
|
||||
@ -53,7 +53,7 @@ const FieldsReorder = ({ className }) => {
|
||||
|
||||
return (
|
||||
<Item
|
||||
groupUid={getGroup(name)}
|
||||
componentUid={getComponent(name)}
|
||||
itemIndex={index}
|
||||
key={name}
|
||||
moveRow={moveRow}
|
||||
|
||||
@ -17,7 +17,13 @@ import FilterPickerOption from '../FilterPickerOption';
|
||||
import init from './init';
|
||||
import reducer, { initialState } from './reducer';
|
||||
|
||||
const NOT_ALLOWED_FILTERS = ['json', 'group', 'relation', 'media', 'richtext'];
|
||||
const NOT_ALLOWED_FILTERS = [
|
||||
'json',
|
||||
'component',
|
||||
'relation',
|
||||
'media',
|
||||
'richtext',
|
||||
];
|
||||
|
||||
function FilterPicker({
|
||||
actions,
|
||||
|
||||
@ -7,8 +7,8 @@ const Wrapper = styled.div`
|
||||
height: 30px;
|
||||
width: 100%;
|
||||
padding: 0 5px;
|
||||
${({ isGroup }) => {
|
||||
if (isGroup) {
|
||||
${({ isComponent }) => {
|
||||
if (isComponent) {
|
||||
return css`
|
||||
height: 36px;
|
||||
border: 1px solid #e3e9f3;
|
||||
|
||||
@ -3,19 +3,19 @@ import PropTypes from 'prop-types';
|
||||
|
||||
import Wrapper from './components';
|
||||
|
||||
const PreviewCarret = ({ isGroup, style }) => (
|
||||
<Wrapper isGroup={isGroup} style={style}>
|
||||
const PreviewCarret = ({ isComponent, style }) => (
|
||||
<Wrapper isComponent={isComponent} style={style}>
|
||||
<div />
|
||||
</Wrapper>
|
||||
);
|
||||
|
||||
PreviewCarret.defaultProps = {
|
||||
isGroup: false,
|
||||
isComponent: false,
|
||||
style: {},
|
||||
};
|
||||
|
||||
PreviewCarret.propTypes = {
|
||||
isGroup: PropTypes.bool,
|
||||
isComponent: PropTypes.bool,
|
||||
style: PropTypes.object,
|
||||
};
|
||||
|
||||
|
||||
@ -31,20 +31,20 @@ import { unformatLayout } from '../../utils/layout';
|
||||
import getInputProps from './utils/getInputProps';
|
||||
// TODO to remove when the API is available
|
||||
import {
|
||||
retrieveDisplayedGroups,
|
||||
retrieveGroupLayoutsToFetch,
|
||||
} from '../EditView/utils/groups';
|
||||
retrieveDisplayedComponents,
|
||||
retrieveComponentsLayoutsToFetch,
|
||||
} from '../EditView/utils/components';
|
||||
|
||||
import reducer, { initialState } from './reducer';
|
||||
|
||||
const EditSettingsView = ({
|
||||
deleteLayout,
|
||||
groupsAndModelsMainPossibleMainFields,
|
||||
componentsAndModelsMainPossibleMainFields,
|
||||
history: { push },
|
||||
location: { search },
|
||||
slug,
|
||||
}) => {
|
||||
const { groupSlug, type } = useParams();
|
||||
const { componentSlug, type } = useParams();
|
||||
const [reducerState, dispatch] = useReducer(reducer, initialState);
|
||||
const [isModalFormOpen, setIsModalFormOpen] = useState(false);
|
||||
|
||||
@ -55,7 +55,7 @@ const EditSettingsView = ({
|
||||
const params = source === 'content-manager' && type ? {} : { source };
|
||||
|
||||
const {
|
||||
groupLayouts,
|
||||
componentLayouts,
|
||||
isLoading,
|
||||
initialData,
|
||||
metaToEdit,
|
||||
@ -107,28 +107,28 @@ const EditSettingsView = ({
|
||||
|
||||
const getSelectedItemSelectOptions = useCallback(
|
||||
formType => {
|
||||
if (formType !== 'relation' && formType !== 'group') {
|
||||
if (formType !== 'relation' && formType !== 'component') {
|
||||
return [];
|
||||
}
|
||||
|
||||
const targetKey = formType === 'group' ? 'group' : 'targetModel';
|
||||
const targetKey = formType === 'component' ? 'component' : 'targetModel';
|
||||
const key = get(
|
||||
modifiedData,
|
||||
['schema', 'attributes', metaToEdit, targetKey],
|
||||
''
|
||||
);
|
||||
|
||||
return get(groupsAndModelsMainPossibleMainFields, [key], []);
|
||||
return get(componentsAndModelsMainPossibleMainFields, [key], []);
|
||||
},
|
||||
|
||||
[metaToEdit, groupsAndModelsMainPossibleMainFields, modifiedData]
|
||||
[metaToEdit, componentsAndModelsMainPossibleMainFields, modifiedData]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const getData = async () => {
|
||||
try {
|
||||
const { data } = await request(
|
||||
getRequestUrl(`${type}/${slug || groupSlug}`),
|
||||
getRequestUrl(`${type}/${slug || componentSlug}`),
|
||||
{
|
||||
method: 'GET',
|
||||
params,
|
||||
@ -137,21 +137,23 @@ const EditSettingsView = ({
|
||||
);
|
||||
|
||||
// TODO temporary to remove when api available
|
||||
const groups = retrieveDisplayedGroups(
|
||||
const components = retrieveDisplayedComponents(
|
||||
get(data, 'schema.attributes', {})
|
||||
);
|
||||
const groupLayoutsToGet = retrieveGroupLayoutsToFetch(groups);
|
||||
const componentLayoutsToGet = retrieveComponentsLayoutsToFetch(
|
||||
components
|
||||
);
|
||||
|
||||
const groupData = await Promise.all(
|
||||
groupLayoutsToGet.map(uid =>
|
||||
request(`/${pluginId}/groups/${uid}`, {
|
||||
const componentData = await Promise.all(
|
||||
componentLayoutsToGet.map(uid =>
|
||||
request(`/${pluginId}/components/${uid}`, {
|
||||
method: 'GET',
|
||||
signal,
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
const groupLayouts = groupData.reduce((acc, current) => {
|
||||
const componentLayouts = componentData.reduce((acc, current) => {
|
||||
acc[current.data.uid] = current.data;
|
||||
|
||||
return acc;
|
||||
@ -161,7 +163,7 @@ const EditSettingsView = ({
|
||||
type: 'GET_DATA_SUCCEEDED',
|
||||
data,
|
||||
// TODO temporary to remove when api available
|
||||
groupLayouts,
|
||||
componentLayouts,
|
||||
});
|
||||
} catch (err) {
|
||||
if (err.code !== 20) {
|
||||
@ -203,12 +205,12 @@ const EditSettingsView = ({
|
||||
delete body.schema;
|
||||
delete body.uid;
|
||||
delete body.source;
|
||||
delete body.isGroup;
|
||||
delete body.isComponent;
|
||||
|
||||
await request(getRequestUrl(`${type}/${slug || groupSlug}`), {
|
||||
await request(getRequestUrl(`${type}/${slug || componentSlug}`), {
|
||||
method: 'PUT',
|
||||
body,
|
||||
params: type === 'groups' ? {} : params,
|
||||
params: type === 'components' ? {} : params,
|
||||
signal,
|
||||
});
|
||||
|
||||
@ -266,7 +268,10 @@ const EditSettingsView = ({
|
||||
getForm().map((meta, index) => {
|
||||
const formType = get(getAttributes, [metaToEdit, 'type']);
|
||||
|
||||
if ((formType === 'group' || formType === 'media') && meta !== 'label') {
|
||||
if (
|
||||
(formType === 'component' || formType === 'media') &&
|
||||
meta !== 'label'
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -319,7 +324,7 @@ const EditSettingsView = ({
|
||||
attributes={getAttributes}
|
||||
buttonData={getEditRemainingFields()}
|
||||
goTo={push}
|
||||
groupLayouts={groupLayouts}
|
||||
componentLayouts={componentLayouts}
|
||||
layout={getEditLayout()}
|
||||
metadatas={get(modifiedData, ['metadatas'], {})}
|
||||
moveItem={moveItem}
|
||||
@ -374,7 +379,7 @@ const EditSettingsView = ({
|
||||
});
|
||||
}}
|
||||
onConfirmSubmit={handleConfirm}
|
||||
slug={slug || groupSlug}
|
||||
slug={slug || componentSlug}
|
||||
isEditSettings
|
||||
>
|
||||
<div className="row">
|
||||
@ -384,7 +389,7 @@ const EditSettingsView = ({
|
||||
description={`${pluginId}.containers.SettingPage.editSettings.description`}
|
||||
/>
|
||||
</LayoutTitle>
|
||||
{type !== 'groups' && (
|
||||
{type !== 'components' && (
|
||||
<LayoutTitle className="col-4">
|
||||
<FormTitle
|
||||
title={`${pluginId}.containers.SettingPage.relations`}
|
||||
@ -394,7 +399,7 @@ const EditSettingsView = ({
|
||||
)}
|
||||
|
||||
<FieldsReorder className={fieldsReorderClassName} />
|
||||
{type !== 'groups' && (
|
||||
{type !== 'components' && (
|
||||
<SortableList
|
||||
addItem={name => {
|
||||
dispatch({
|
||||
@ -449,7 +454,7 @@ EditSettingsView.defaultProps = {
|
||||
|
||||
EditSettingsView.propTypes = {
|
||||
deleteLayout: PropTypes.func.isRequired,
|
||||
groupsAndModelsMainPossibleMainFields: PropTypes.object.isRequired,
|
||||
componentsAndModelsMainPossibleMainFields: PropTypes.object.isRequired,
|
||||
history: PropTypes.shape({
|
||||
push: PropTypes.func,
|
||||
}).isRequired,
|
||||
|
||||
@ -16,7 +16,7 @@ import {
|
||||
import pluginId from '../../pluginId';
|
||||
import { EditViewProvider } from '../../contexts/EditView';
|
||||
import Container from '../../components/Container';
|
||||
import Group from '../../components/Group';
|
||||
import ComponentField from '../../components/ComponentField';
|
||||
import Inputs from '../../components/Inputs';
|
||||
import SelectWrapper from '../../components/SelectWrapper';
|
||||
import createYupSchema from './utils/schema';
|
||||
@ -30,11 +30,12 @@ import {
|
||||
cleanData,
|
||||
mapDataKeysToFilesToUpload,
|
||||
} from './utils/formatData';
|
||||
|
||||
import {
|
||||
getDefaultGroupValues,
|
||||
retrieveDisplayedGroups,
|
||||
retrieveGroupLayoutsToFetch,
|
||||
} from './utils/groups';
|
||||
getDefaultComponentValues,
|
||||
retrieveDisplayedComponents,
|
||||
retrieveComponentsLayoutsToFetch,
|
||||
} from './utils/components';
|
||||
|
||||
const getRequestUrl = path => `/${pluginId}/explorer/${path}`;
|
||||
|
||||
@ -49,14 +50,16 @@ function EditView({
|
||||
plugins,
|
||||
}) {
|
||||
const { id } = useParams();
|
||||
|
||||
console.log({ layouts });
|
||||
const abortController = new AbortController();
|
||||
const { signal } = abortController;
|
||||
const layout = get(layouts, [slug], {});
|
||||
const isCreatingEntry = id === 'create';
|
||||
const attributes = get(layout, ['schema', 'attributes'], {});
|
||||
const groups = retrieveDisplayedGroups(attributes);
|
||||
const groupLayoutsToGet = retrieveGroupLayoutsToFetch(groups);
|
||||
|
||||
const components = retrieveDisplayedComponents(attributes);
|
||||
const componentLayoutsToGet = retrieveComponentsLayoutsToFetch(components);
|
||||
|
||||
// States
|
||||
const [showWarningCancel, setWarningCancel] = useState(false);
|
||||
const [showWarningDelete, setWarningDelete] = useState(false);
|
||||
@ -69,7 +72,7 @@ function EditView({
|
||||
const {
|
||||
didCheckErrors,
|
||||
errors,
|
||||
groupLayoutsData,
|
||||
componentLayoutsData,
|
||||
initialData,
|
||||
modifiedData,
|
||||
isLoading,
|
||||
@ -81,30 +84,33 @@ function EditView({
|
||||
isLoadingForLayouts || (!isCreatingEntry && isLoading);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchGroupLayouts = async () => {
|
||||
const fetchComponentLayouts = async () => {
|
||||
try {
|
||||
const data = await Promise.all(
|
||||
groupLayoutsToGet.map(uid =>
|
||||
request(`/${pluginId}/groups/${uid}`, {
|
||||
componentLayoutsToGet.map(uid =>
|
||||
request(`/${pluginId}/components/${uid}`, {
|
||||
method: 'GET',
|
||||
signal,
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
const groupLayouts = data.reduce((acc, current) => {
|
||||
const componentLayouts = data.reduce((acc, current) => {
|
||||
acc[current.data.uid] = current.data;
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
// Retrieve all the default values for the repeatables and init the form
|
||||
const defaultGroupValues = getDefaultGroupValues(groups, groupLayouts);
|
||||
const defaultComponentValues = getDefaultComponentValues(
|
||||
components,
|
||||
componentLayouts
|
||||
);
|
||||
|
||||
dispatch({
|
||||
type: 'GET_GROUP_LAYOUTS_SUCCEEDED',
|
||||
groupLayouts,
|
||||
defaultGroupValues,
|
||||
type: 'GET_COMPONENT_LAYOUTS_SUCCEEDED',
|
||||
componentLayouts,
|
||||
defaultComponentValues,
|
||||
isCreatingEntry,
|
||||
});
|
||||
} catch (err) {
|
||||
@ -128,7 +134,7 @@ function EditView({
|
||||
data,
|
||||
defaultForm: setDefaultForm(get(layout, ['schema', 'attributes'])),
|
||||
});
|
||||
fetchGroupLayouts();
|
||||
fetchComponentLayouts();
|
||||
} catch (err) {
|
||||
if (err.code !== 20) {
|
||||
strapi.notification.error(`${pluginId}.error.record.fetch`);
|
||||
@ -147,7 +153,7 @@ function EditView({
|
||||
data: setDefaultForm(get(layout, ['schema', 'attributes'])),
|
||||
defaultForm: setDefaultForm(get(layout, ['schema', 'attributes'])),
|
||||
});
|
||||
fetchGroupLayouts();
|
||||
fetchComponentLayouts();
|
||||
}
|
||||
|
||||
return () => {
|
||||
@ -201,7 +207,9 @@ function EditView({
|
||||
const fields = get(layout, ['layouts', 'edit'], []);
|
||||
|
||||
const checkFormErrors = async () => {
|
||||
const schema = createYupSchema(layout, { groups: groupLayoutsData });
|
||||
const schema = createYupSchema(layout, {
|
||||
components: componentLayoutsData,
|
||||
});
|
||||
let errors = {};
|
||||
|
||||
try {
|
||||
@ -235,7 +243,9 @@ function EditView({
|
||||
|
||||
const handleSubmit = async e => {
|
||||
e.preventDefault();
|
||||
const schema = createYupSchema(layout, { groups: groupLayoutsData });
|
||||
const schema = createYupSchema(layout, {
|
||||
components: componentLayoutsData,
|
||||
});
|
||||
|
||||
try {
|
||||
// Validate the form using yup
|
||||
@ -244,14 +254,14 @@ function EditView({
|
||||
setIsSubmitting(true);
|
||||
emitEvent('willSaveEntry');
|
||||
// Create an object containing all the paths of the media fields
|
||||
const filesMap = getMediaAttributes(layout, groupLayoutsData);
|
||||
const filesMap = getMediaAttributes(layout, componentLayoutsData);
|
||||
// Create an object that maps the keys with the related files to upload
|
||||
const filesToUpload = mapDataKeysToFilesToUpload(filesMap, modifiedData);
|
||||
|
||||
const cleanedData = cleanData(
|
||||
cloneDeep(modifiedData),
|
||||
layout,
|
||||
groupLayoutsData
|
||||
componentLayoutsData
|
||||
);
|
||||
|
||||
const formData = new FormData();
|
||||
@ -359,10 +369,10 @@ function EditView({
|
||||
errors: {},
|
||||
});
|
||||
}}
|
||||
resetGroupData={groupName => {
|
||||
resetComponentData={componentName => {
|
||||
dispatch({
|
||||
type: 'RESET_GROUP_DATA',
|
||||
groupName,
|
||||
type: 'RESET_COMPONENT_DATA',
|
||||
componentName,
|
||||
});
|
||||
}}
|
||||
search={search}
|
||||
@ -416,22 +426,29 @@ function EditView({
|
||||
}
|
||||
|
||||
const [{ name }] = fieldsRow;
|
||||
const group = get(layout, ['schema', 'attributes', name], {});
|
||||
const groupMetas = get(
|
||||
const component = get(
|
||||
layout,
|
||||
['schema', 'attributes', name],
|
||||
{}
|
||||
);
|
||||
const componentMetas = get(
|
||||
layout,
|
||||
['metadatas', name, 'edit'],
|
||||
{}
|
||||
);
|
||||
const groupValue = get(
|
||||
const componentValue = get(
|
||||
modifiedData,
|
||||
[name],
|
||||
group.repeatable ? [] : {}
|
||||
component.repeatable ? [] : {}
|
||||
);
|
||||
|
||||
if (fieldsRow.length === 1 && group.type === 'group') {
|
||||
if (
|
||||
fieldsRow.length === 1 &&
|
||||
component.type === 'component'
|
||||
) {
|
||||
// Array containing all the keys with of the error object created by YUP
|
||||
// It is used only to know if whether or not we need to apply an orange border to the n+1 field item
|
||||
const groupErrorKeys = Object.keys(errors)
|
||||
const componentErrorKeys = Object.keys(errors)
|
||||
.filter(errorKey => errorKey.includes(name))
|
||||
.map(errorKey =>
|
||||
errorKey
|
||||
@ -441,23 +458,23 @@ function EditView({
|
||||
);
|
||||
|
||||
return (
|
||||
<Group
|
||||
{...group}
|
||||
{...groupMetas}
|
||||
<ComponentField
|
||||
{...component}
|
||||
{...componentMetas}
|
||||
addField={(keys, isRepeatable = true) => {
|
||||
dispatch({
|
||||
type: 'ADD_FIELD_TO_GROUP',
|
||||
type: 'ADD_FIELD_TO_COMPONENT',
|
||||
keys: keys.split('.'),
|
||||
isRepeatable,
|
||||
});
|
||||
}}
|
||||
groupErrorKeys={groupErrorKeys}
|
||||
groupValue={groupValue}
|
||||
componentErrorKeys={componentErrorKeys}
|
||||
componentValue={componentValue}
|
||||
key={key}
|
||||
isRepeatable={group.repeatable || false}
|
||||
isRepeatable={component.repeatable || false}
|
||||
name={name}
|
||||
modifiedData={modifiedData}
|
||||
moveGroupField={(dragIndex, overIndex, name) => {
|
||||
moveComponentField={(dragIndex, overIndex, name) => {
|
||||
dispatch({
|
||||
type: 'MOVE_FIELD',
|
||||
dragIndex,
|
||||
@ -466,7 +483,11 @@ function EditView({
|
||||
});
|
||||
}}
|
||||
onChange={handleChange}
|
||||
layout={get(groupLayoutsData, group.group, {})}
|
||||
layout={get(
|
||||
componentLayoutsData,
|
||||
component.component,
|
||||
{}
|
||||
)}
|
||||
pathname={pathname}
|
||||
removeField={(keys, shouldAddEmptyField) => {
|
||||
dispatch({
|
||||
|
||||
@ -4,12 +4,12 @@ const initialState = fromJS({
|
||||
collapses: {},
|
||||
didCheckErrors: false,
|
||||
errors: {},
|
||||
groupLayoutsData: {},
|
||||
componentLayoutsData: {},
|
||||
initialData: {},
|
||||
isLoading: true,
|
||||
isLoadingForLayouts: true,
|
||||
modifiedData: {},
|
||||
defaultGroupValues: {},
|
||||
defaultComponentValues: {},
|
||||
defaultForm: {},
|
||||
});
|
||||
|
||||
@ -22,10 +22,10 @@ const getMax = arr => {
|
||||
|
||||
function reducer(state, action) {
|
||||
switch (action.type) {
|
||||
case 'ADD_FIELD_TO_GROUP':
|
||||
case 'ADD_FIELD_TO_COMPONENT':
|
||||
return state.updateIn(['modifiedData', ...action.keys], list => {
|
||||
const defaultAttribute = state.getIn([
|
||||
'defaultGroupValues',
|
||||
'defaultComponentValues',
|
||||
...action.keys,
|
||||
'defaultRepeatable',
|
||||
]);
|
||||
@ -71,16 +71,16 @@ function reducer(state, action) {
|
||||
.update('initialData', () => fromJS(action.data))
|
||||
.update('modifiedData', () => fromJS(action.data))
|
||||
.update('isLoading', () => false);
|
||||
case 'GET_GROUP_LAYOUTS_SUCCEEDED': {
|
||||
const addTempIdToGroupData = obj => {
|
||||
const { defaultGroupValues } = action;
|
||||
case 'GET_COMPONENT_LAYOUTS_SUCCEEDED': {
|
||||
const addTempIdToComponentData = obj => {
|
||||
const { defaultComponentValues } = action;
|
||||
|
||||
if (action.isCreatingEntry === true) {
|
||||
return obj.keySeq().reduce((acc, current) => {
|
||||
if (defaultGroupValues[current]) {
|
||||
if (defaultComponentValues[current]) {
|
||||
return acc.set(
|
||||
current,
|
||||
fromJS(defaultGroupValues[current].toSet)
|
||||
fromJS(defaultComponentValues[current].toSet)
|
||||
);
|
||||
}
|
||||
|
||||
@ -88,7 +88,10 @@ function reducer(state, action) {
|
||||
}, obj);
|
||||
} else {
|
||||
return obj.keySeq().reduce((acc, current) => {
|
||||
if (defaultGroupValues[current] && List.isList(obj.get(current))) {
|
||||
if (
|
||||
defaultComponentValues[current] &&
|
||||
List.isList(obj.get(current))
|
||||
) {
|
||||
const formatted = obj.get(current).reduce((acc2, curr, index) => {
|
||||
return acc2.set(index, curr.set('_temp__id', index));
|
||||
}, List([]));
|
||||
@ -102,13 +105,15 @@ function reducer(state, action) {
|
||||
};
|
||||
|
||||
return state
|
||||
.update('groupLayoutsData', () => fromJS(action.groupLayouts))
|
||||
.update('defaultGroupValues', () => fromJS(action.defaultGroupValues))
|
||||
.update('componentLayoutsData', () => fromJS(action.componentLayouts))
|
||||
.update('defaultComponentValues', () =>
|
||||
fromJS(action.defaultComponentValues)
|
||||
)
|
||||
.update('modifiedData', obj => {
|
||||
return addTempIdToGroupData(obj);
|
||||
return addTempIdToComponentData(obj);
|
||||
})
|
||||
.update('initialData', obj => {
|
||||
return addTempIdToGroupData(obj);
|
||||
return addTempIdToComponentData(obj);
|
||||
})
|
||||
.update('isLoadingForLayouts', () => false);
|
||||
}
|
||||
@ -123,16 +128,18 @@ function reducer(state, action) {
|
||||
.delete(action.dragIndex)
|
||||
.insert(action.overIndex, list.get(action.dragIndex));
|
||||
});
|
||||
|
||||
case 'ON_CHANGE': {
|
||||
let newState = state;
|
||||
const [nonRepeatableGroupKey] = action.keys;
|
||||
const [nonRepeatableComponentKey] = action.keys;
|
||||
|
||||
if (
|
||||
action.keys.length === 2 &&
|
||||
state.getIn(['modifiedData', nonRepeatableGroupKey]) === null
|
||||
state.getIn(['modifiedData', nonRepeatableComponentKey]) === null
|
||||
) {
|
||||
newState = state.updateIn(['modifiedData', nonRepeatableGroupKey], () =>
|
||||
fromJS({})
|
||||
newState = state.updateIn(
|
||||
['modifiedData', nonRepeatableComponentKey],
|
||||
() => fromJS({})
|
||||
);
|
||||
}
|
||||
|
||||
@ -140,13 +147,14 @@ function reducer(state, action) {
|
||||
return action.value;
|
||||
});
|
||||
}
|
||||
|
||||
case 'ON_REMOVE_FIELD':
|
||||
return state
|
||||
.removeIn(['modifiedData', ...action.keys])
|
||||
.updateIn(['modifiedData', action.keys[0]], list => {
|
||||
if (action.shouldAddEmptyField) {
|
||||
const defaultAttribute = state.getIn([
|
||||
'defaultGroupValues',
|
||||
'defaultComponentValues',
|
||||
action.keys[0],
|
||||
'defaultRepeatable',
|
||||
]);
|
||||
@ -164,13 +172,14 @@ function reducer(state, action) {
|
||||
.update('modifiedData', () => state.get('initialData'))
|
||||
.update('errors', () => fromJS({}))
|
||||
.update('didCheckErrors', v => !v);
|
||||
case 'RESET_GROUP_DATA': {
|
||||
const groupPath = ['modifiedData', action.groupName];
|
||||
|
||||
case 'RESET_COMPONENT_DATA': {
|
||||
const componentPath = ['modifiedData', action.componentName];
|
||||
|
||||
return state
|
||||
.updateIn(
|
||||
groupPath,
|
||||
() => state.getIn(['defaultForm', action.groupName]) || null
|
||||
componentPath,
|
||||
() => state.getIn(['defaultForm', action.componentName]) || null
|
||||
)
|
||||
.update('errors', () => fromJS({}))
|
||||
.update('didCheckErrors', v => !v);
|
||||
|
||||
@ -0,0 +1,288 @@
|
||||
import {
|
||||
getDefaultComponentValues,
|
||||
retrieveDisplayedComponents,
|
||||
retrieveComponentsLayoutsToFetch,
|
||||
} from '../utils/components';
|
||||
|
||||
describe('Content Manager | EditView | utils | components', () => {
|
||||
describe('getDefaultComponentValues', () => {
|
||||
it('should return an empty object if the args are empty', () => {
|
||||
expect(getDefaultComponentValues([], {})).toEqual({});
|
||||
});
|
||||
|
||||
it('should return the correct data', () => {
|
||||
const component1 = {
|
||||
schema: {
|
||||
attributes: {
|
||||
title: {
|
||||
type: 'string',
|
||||
default: 'test',
|
||||
},
|
||||
description: {
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const component2 = {
|
||||
schema: {
|
||||
attributes: {
|
||||
otherTitle: {
|
||||
type: 'string',
|
||||
default: 'test',
|
||||
},
|
||||
otherDescription: {
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const component3 = {
|
||||
schema: {
|
||||
attributes: {
|
||||
otherTitle: {
|
||||
type: 'string',
|
||||
},
|
||||
otherDescription: {
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const component4 = {
|
||||
schema: {
|
||||
attributes: {
|
||||
otherTitle: {
|
||||
type: 'string',
|
||||
},
|
||||
otherDescription: {
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const component5 = {
|
||||
schema: {
|
||||
attributes: {
|
||||
otherTitle: {
|
||||
type: 'string',
|
||||
},
|
||||
otherDescription: {
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const components = [
|
||||
{
|
||||
key: 'component1',
|
||||
component: 'component1',
|
||||
},
|
||||
{
|
||||
key: 'component2',
|
||||
component: 'component2',
|
||||
repeatable: true,
|
||||
min: 1,
|
||||
},
|
||||
{
|
||||
key: 'component3',
|
||||
component: 'component3',
|
||||
repeatable: true,
|
||||
},
|
||||
{
|
||||
key: 'component4',
|
||||
component: 'component4',
|
||||
},
|
||||
{
|
||||
key: 'component5',
|
||||
component: 'component5',
|
||||
required: true,
|
||||
repeatable: true,
|
||||
},
|
||||
{
|
||||
key: 'component6',
|
||||
component: 'component5',
|
||||
min: 1,
|
||||
repeatable: true,
|
||||
},
|
||||
];
|
||||
const componentLayouts = {
|
||||
component1,
|
||||
component2,
|
||||
component3,
|
||||
component4,
|
||||
component5,
|
||||
};
|
||||
const expected = {
|
||||
component1: {
|
||||
toSet: {
|
||||
title: 'test',
|
||||
},
|
||||
defaultRepeatable: {
|
||||
title: 'test',
|
||||
},
|
||||
},
|
||||
component2: {
|
||||
toSet: [{ _temp__id: 0, otherTitle: 'test' }],
|
||||
defaultRepeatable: {
|
||||
otherTitle: 'test',
|
||||
},
|
||||
},
|
||||
component3: {
|
||||
toSet: [],
|
||||
defaultRepeatable: {},
|
||||
},
|
||||
component4: {
|
||||
toSet: {},
|
||||
defaultRepeatable: {},
|
||||
},
|
||||
component5: {
|
||||
toSet: [],
|
||||
defaultRepeatable: {},
|
||||
},
|
||||
component6: {
|
||||
toSet: [{ _temp__id: 0 }],
|
||||
defaultRepeatable: {},
|
||||
},
|
||||
};
|
||||
|
||||
expect(getDefaultComponentValues(components, componentLayouts)).toEqual(
|
||||
expected
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('retrieveDisplayedComponents', () => {
|
||||
it('should return an array with all the components', () => {
|
||||
const attributes = {
|
||||
name: {
|
||||
maxLength: 50,
|
||||
required: true,
|
||||
minLength: 5,
|
||||
type: 'string',
|
||||
},
|
||||
cover: {
|
||||
model: 'file',
|
||||
via: 'related',
|
||||
plugin: 'upload',
|
||||
},
|
||||
menu: {
|
||||
model: 'menu',
|
||||
via: 'restaurant',
|
||||
},
|
||||
categories: {
|
||||
collection: 'category',
|
||||
},
|
||||
price_range: {
|
||||
enum: [
|
||||
'very_cheap',
|
||||
'cheap',
|
||||
'average',
|
||||
'expensive',
|
||||
'very_expensive',
|
||||
],
|
||||
type: 'enumeration',
|
||||
},
|
||||
description: {
|
||||
type: 'richtext',
|
||||
required: true,
|
||||
},
|
||||
opening_times: {
|
||||
component: 'openingtimes',
|
||||
type: 'component',
|
||||
required: true,
|
||||
repeatable: true,
|
||||
min: 1,
|
||||
max: 10,
|
||||
},
|
||||
opening_times2: {
|
||||
component: 'openingtimes',
|
||||
type: 'component',
|
||||
},
|
||||
closing_period: {
|
||||
component: 'closingperiod',
|
||||
type: 'component',
|
||||
},
|
||||
services: {
|
||||
component: 'restaurantservice',
|
||||
required: true,
|
||||
repeatable: true,
|
||||
type: 'component',
|
||||
},
|
||||
address: {
|
||||
model: 'address',
|
||||
},
|
||||
};
|
||||
const expected = [
|
||||
{
|
||||
key: 'opening_times',
|
||||
component: 'openingtimes',
|
||||
repeatable: true,
|
||||
min: 1,
|
||||
isOpen: false,
|
||||
},
|
||||
{
|
||||
key: 'opening_times2',
|
||||
component: 'openingtimes',
|
||||
isOpen: true,
|
||||
min: undefined,
|
||||
repeatable: undefined,
|
||||
},
|
||||
{
|
||||
key: 'closing_period',
|
||||
component: 'closingperiod',
|
||||
isOpen: true,
|
||||
min: undefined,
|
||||
repeatable: undefined,
|
||||
},
|
||||
{
|
||||
key: 'services',
|
||||
component: 'restaurantservice',
|
||||
repeatable: true,
|
||||
isOpen: false,
|
||||
min: undefined,
|
||||
},
|
||||
];
|
||||
|
||||
expect(retrieveDisplayedComponents(attributes)).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('retrieveComponentsLayoutsToFetch', () => {
|
||||
it('should return a filterd array of the components to fetch', () => {
|
||||
const components = [
|
||||
{
|
||||
key: 'opening_times',
|
||||
component: 'openingtimes',
|
||||
repeatable: true,
|
||||
min: 1,
|
||||
isOpen: false,
|
||||
},
|
||||
{
|
||||
key: 'opening_times2',
|
||||
component: 'openingtimes',
|
||||
isOpen: true,
|
||||
min: undefined,
|
||||
repeatable: undefined,
|
||||
},
|
||||
{
|
||||
key: 'closing_period',
|
||||
component: 'closingperiod',
|
||||
isOpen: true,
|
||||
min: undefined,
|
||||
repeatable: undefined,
|
||||
},
|
||||
{
|
||||
key: 'services',
|
||||
component: 'restaurantservice',
|
||||
repeatable: true,
|
||||
isOpen: false,
|
||||
min: undefined,
|
||||
},
|
||||
];
|
||||
const expected = ['openingtimes', 'closingperiod', 'restaurantservice'];
|
||||
|
||||
expect(retrieveComponentsLayoutsToFetch(components)).toEqual(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -1,7 +1,7 @@
|
||||
import createDefaultForm from '../utils/createDefaultForm';
|
||||
|
||||
describe('Content Manager | EditView | utils | createDefaultForm', () => {
|
||||
it('should return an empty object if there is no group or default value in the argument', () => {
|
||||
it('should return an empty object if there is no component or default value in the argument', () => {
|
||||
const attributes = {
|
||||
title: {
|
||||
type: 'string',
|
||||
@ -69,7 +69,7 @@ describe('Content Manager | EditView | utils | createDefaultForm', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle the group fields correctly', () => {
|
||||
it('should handle the component fields correctly', () => {
|
||||
const attributes = {
|
||||
title: {
|
||||
type: 'string',
|
||||
@ -78,40 +78,40 @@ describe('Content Manager | EditView | utils | createDefaultForm', () => {
|
||||
description: {
|
||||
type: 'text',
|
||||
},
|
||||
group: {
|
||||
type: 'group',
|
||||
component: {
|
||||
type: 'component',
|
||||
},
|
||||
group1: {
|
||||
type: 'group',
|
||||
component1: {
|
||||
type: 'component',
|
||||
required: true,
|
||||
},
|
||||
group2: {
|
||||
type: 'group',
|
||||
component2: {
|
||||
type: 'component',
|
||||
repeatable: true,
|
||||
},
|
||||
group3: {
|
||||
type: 'group',
|
||||
component3: {
|
||||
type: 'component',
|
||||
repeatable: true,
|
||||
required: true,
|
||||
},
|
||||
group4: {
|
||||
type: 'group',
|
||||
component4: {
|
||||
type: 'component',
|
||||
repeatable: true,
|
||||
required: true,
|
||||
min: 2,
|
||||
},
|
||||
group5: {
|
||||
type: 'group',
|
||||
component5: {
|
||||
type: 'component',
|
||||
repeatable: true,
|
||||
min: 2,
|
||||
},
|
||||
};
|
||||
expect(createDefaultForm(attributes)).toEqual({
|
||||
title: 'test',
|
||||
group1: {},
|
||||
group3: [],
|
||||
group4: [{ _temp__id: 0 }, { _temp__id: 1 }],
|
||||
group5: [{ _temp__id: 0 }, { _temp__id: 1 }],
|
||||
component1: {},
|
||||
component3: [],
|
||||
component4: [{ _temp__id: 0 }, { _temp__id: 1 }],
|
||||
component5: [{ _temp__id: 0 }, { _temp__id: 1 }],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -8,14 +8,14 @@ const ctLayout = {
|
||||
enum: { type: 'enumeration', enum: ['un', 'deux'] },
|
||||
fb_cta: {
|
||||
required: true,
|
||||
type: 'group',
|
||||
group: 'cta_facebook',
|
||||
type: 'component',
|
||||
component: 'cta_facebook',
|
||||
repeatable: false,
|
||||
},
|
||||
id: { type: 'integer' },
|
||||
ingredients: {
|
||||
type: 'group',
|
||||
group: 'ingredients',
|
||||
type: 'component',
|
||||
component: 'ingredients',
|
||||
repeatable: true,
|
||||
min: 1,
|
||||
max: 10,
|
||||
@ -31,8 +31,8 @@ const ctLayout = {
|
||||
type: 'relation',
|
||||
},
|
||||
mainIngredient: {
|
||||
type: 'group',
|
||||
group: 'ingredients',
|
||||
type: 'component',
|
||||
component: 'ingredients',
|
||||
repeatable: false,
|
||||
},
|
||||
mainTag: {
|
||||
@ -67,7 +67,7 @@ const ctLayout = {
|
||||
},
|
||||
};
|
||||
|
||||
const groupLayouts = {
|
||||
const componentLayouts = {
|
||||
cta_facebook: {
|
||||
schema: {
|
||||
attributes: {
|
||||
@ -123,4 +123,4 @@ const simpleCtLayout = {
|
||||
settings: {},
|
||||
};
|
||||
|
||||
export { ctLayout, groupLayouts, simpleCtLayout };
|
||||
export { ctLayout, componentLayouts, simpleCtLayout };
|
||||
|
||||
@ -3,17 +3,17 @@ import {
|
||||
getMediaAttributes,
|
||||
helperCleanData,
|
||||
} from '../utils/formatData';
|
||||
import { ctLayout, groupLayouts, simpleCtLayout } from './data';
|
||||
import { ctLayout, componentLayouts, simpleCtLayout } from './data';
|
||||
|
||||
describe('Content Manager | EditView | utils | cleanData', () => {
|
||||
let simpleContentTypeLayout;
|
||||
let contentTypeLayout;
|
||||
let grpLayouts;
|
||||
let cpLayouts;
|
||||
|
||||
beforeEach(() => {
|
||||
simpleContentTypeLayout = simpleCtLayout;
|
||||
contentTypeLayout = ctLayout;
|
||||
grpLayouts = groupLayouts;
|
||||
cpLayouts = componentLayouts;
|
||||
});
|
||||
|
||||
it('should format de data correctly if the content type has no group and no file has been added', () => {
|
||||
@ -60,7 +60,7 @@ describe('Content Manager | EditView | utils | cleanData', () => {
|
||||
pictures: [1, 2],
|
||||
};
|
||||
|
||||
expect(cleanData(data, simpleContentTypeLayout, grpLayouts)).toEqual(
|
||||
expect(cleanData(data, simpleContentTypeLayout, cpLayouts)).toEqual(
|
||||
expected
|
||||
);
|
||||
});
|
||||
@ -148,7 +148,9 @@ describe('Content Manager | EditView | utils | cleanData', () => {
|
||||
title: 'test',
|
||||
};
|
||||
|
||||
expect(cleanData(data, contentTypeLayout, groupLayouts)).toEqual(expected);
|
||||
expect(cleanData(data, contentTypeLayout, componentLayouts)).toEqual(
|
||||
expected
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -202,36 +204,40 @@ describe('Content Manager | EditView | utils | helperCleanData', () => {
|
||||
|
||||
describe('Content Manager | EditView | utils | getMediasAttributes', () => {
|
||||
let contentTypeLayout;
|
||||
let grpLayouts;
|
||||
let cpLayouts;
|
||||
|
||||
beforeEach(() => {
|
||||
contentTypeLayout = ctLayout;
|
||||
grpLayouts = groupLayouts;
|
||||
cpLayouts = componentLayouts;
|
||||
});
|
||||
|
||||
it('should return an array containing the paths of all the medias attributes', () => {
|
||||
const expected = {
|
||||
'ingredients.testMultiple': {
|
||||
multiple: true,
|
||||
isGroup: true,
|
||||
isComponent: true,
|
||||
repeatable: true,
|
||||
},
|
||||
'ingredients.test': {
|
||||
multiple: false,
|
||||
isComponent: true,
|
||||
repeatable: true,
|
||||
},
|
||||
'ingredients.test': { multiple: false, isGroup: true, repeatable: true },
|
||||
'mainIngredient.testMultiple': {
|
||||
multiple: true,
|
||||
isGroup: true,
|
||||
isComponent: true,
|
||||
repeatable: false,
|
||||
},
|
||||
'mainIngredient.test': {
|
||||
multiple: false,
|
||||
isGroup: true,
|
||||
isComponent: true,
|
||||
repeatable: false,
|
||||
},
|
||||
pic: { multiple: false, isGroup: false, repeatable: false },
|
||||
pictures: { multiple: true, isGroup: false, repeatable: false },
|
||||
pic: { multiple: false, isComponent: false, repeatable: false },
|
||||
pictures: { multiple: true, isComponent: false, repeatable: false },
|
||||
};
|
||||
|
||||
expect(getMediaAttributes(contentTypeLayout, grpLayouts)).toMatchObject(
|
||||
expect(getMediaAttributes(contentTypeLayout, cpLayouts)).toMatchObject(
|
||||
expected
|
||||
);
|
||||
});
|
||||
|
||||
@ -0,0 +1,72 @@
|
||||
import { get } from 'lodash';
|
||||
|
||||
import setDefaultForm from './createDefaultForm';
|
||||
|
||||
// Retrieve all the default values for the repeatables and init the form
|
||||
const getDefaultComponentValues = (components, componentLayouts) => {
|
||||
const defaultComponentValues = components.reduce((acc, current) => {
|
||||
const defaultForm = setDefaultForm(
|
||||
get(componentLayouts, [current.component, 'schema', 'attributes'], {})
|
||||
);
|
||||
const arr = [];
|
||||
|
||||
if (current.min && current.repeatable === true) {
|
||||
for (let i = 0; i < current.min; i++) {
|
||||
arr.push({ ...defaultForm, _temp__id: i });
|
||||
}
|
||||
}
|
||||
|
||||
acc[current.key] = {
|
||||
toSet: arr,
|
||||
defaultRepeatable: defaultForm,
|
||||
};
|
||||
|
||||
if (current.repeatable !== true) {
|
||||
acc[current.key] = {
|
||||
toSet: defaultForm,
|
||||
defaultRepeatable: defaultForm,
|
||||
};
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
return defaultComponentValues;
|
||||
};
|
||||
|
||||
const retrieveDisplayedComponents = attributes => {
|
||||
return Object.keys(attributes).reduce((acc, current) => {
|
||||
const { component, repeatable, type, min } = get(attributes, [current], {
|
||||
component: '',
|
||||
type: '',
|
||||
repeatable,
|
||||
});
|
||||
|
||||
if (type === 'component') {
|
||||
acc.push({
|
||||
key: current,
|
||||
component,
|
||||
repeatable,
|
||||
isOpen: !repeatable,
|
||||
min,
|
||||
});
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
};
|
||||
|
||||
const retrieveComponentsLayoutsToFetch = components => {
|
||||
return components
|
||||
.filter(
|
||||
(current, index) =>
|
||||
components.findIndex(el => el.component === current.component) === index
|
||||
)
|
||||
.map(({ component }) => component);
|
||||
};
|
||||
|
||||
export {
|
||||
getDefaultComponentValues,
|
||||
retrieveDisplayedComponents,
|
||||
retrieveComponentsLayoutsToFetch,
|
||||
};
|
||||
@ -23,7 +23,7 @@ const setDefaultForm = attributes => {
|
||||
acc[current] = defaultValue;
|
||||
}
|
||||
|
||||
if (type === 'group') {
|
||||
if (type === 'component') {
|
||||
if (required === true) {
|
||||
acc[current] = repeatable === true ? [] : {};
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { get, isArray, isEmpty, isObject } from 'lodash';
|
||||
|
||||
export const cleanData = (retrievedData, ctLayout, groupLayouts) => {
|
||||
export const cleanData = (retrievedData, ctLayout, componentLayouts) => {
|
||||
const getType = (schema, attrName) =>
|
||||
get(schema, ['attributes', attrName, 'type'], '');
|
||||
const getOtherInfos = (schema, arr) =>
|
||||
@ -10,7 +10,7 @@ export const cleanData = (retrievedData, ctLayout, groupLayouts) => {
|
||||
return Object.keys(data).reduce((acc, current) => {
|
||||
const attrType = getType(layout.schema, current);
|
||||
const value = get(data, current);
|
||||
const group = getOtherInfos(layout.schema, [current, 'group']);
|
||||
const component = getOtherInfos(layout.schema, [current, 'component']);
|
||||
const isRepeatable = getOtherInfos(layout.schema, [
|
||||
current,
|
||||
'repeatable',
|
||||
@ -40,14 +40,14 @@ export const cleanData = (retrievedData, ctLayout, groupLayouts) => {
|
||||
get(value, 0) instanceof File ? null : get(value, 'id', null);
|
||||
}
|
||||
break;
|
||||
case 'group':
|
||||
case 'component':
|
||||
if (isRepeatable) {
|
||||
cleanedData = value
|
||||
? value.map(data => {
|
||||
delete data._temp__id;
|
||||
const subCleanedData = recursiveCleanData(
|
||||
data,
|
||||
groupLayouts[group]
|
||||
componentLayouts[component]
|
||||
);
|
||||
|
||||
return subCleanedData;
|
||||
@ -55,7 +55,7 @@ export const cleanData = (retrievedData, ctLayout, groupLayouts) => {
|
||||
: value;
|
||||
} else {
|
||||
cleanedData = value
|
||||
? recursiveCleanData(value, groupLayouts[group])
|
||||
? recursiveCleanData(value, componentLayouts[component])
|
||||
: value;
|
||||
}
|
||||
break;
|
||||
@ -72,11 +72,11 @@ export const cleanData = (retrievedData, ctLayout, groupLayouts) => {
|
||||
return recursiveCleanData(retrievedData, ctLayout);
|
||||
};
|
||||
|
||||
export const getMediaAttributes = (ctLayout, groupLayouts) => {
|
||||
export const getMediaAttributes = (ctLayout, componentLayouts) => {
|
||||
const getMedia = (
|
||||
layout,
|
||||
prefix = '',
|
||||
isGroupType = false,
|
||||
isComponentType = false,
|
||||
repeatable = false
|
||||
) => {
|
||||
const attributes = get(layout, ['schema', 'attributes'], {});
|
||||
@ -85,21 +85,26 @@ export const getMediaAttributes = (ctLayout, groupLayouts) => {
|
||||
const type = get(attributes, [current, 'type']);
|
||||
const multiple = get(attributes, [current, 'multiple'], false);
|
||||
const isRepeatable = get(attributes, [current, 'repeatable']);
|
||||
const isGroup = type === 'group';
|
||||
const isComponent = type === 'component';
|
||||
|
||||
if (isGroup) {
|
||||
const group = get(attributes, [current, 'group']);
|
||||
if (isComponent) {
|
||||
const component = get(attributes, [current, 'component']);
|
||||
|
||||
return {
|
||||
...acc,
|
||||
...getMedia(groupLayouts[group], current, isGroup, isRepeatable),
|
||||
...getMedia(
|
||||
componentLayouts[component],
|
||||
current,
|
||||
isComponent,
|
||||
isRepeatable
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
if (type === 'media') {
|
||||
const path = prefix !== '' ? `${prefix}.${current}` : current;
|
||||
|
||||
acc[path] = { multiple, isGroup: isGroupType, repeatable };
|
||||
acc[path] = { multiple, isComponent: isComponentType, repeatable };
|
||||
}
|
||||
|
||||
return acc;
|
||||
@ -123,7 +128,7 @@ export const mapDataKeysToFilesToUpload = (filesMap, data) => {
|
||||
return Object.keys(filesMap).reduce((acc, current) => {
|
||||
const keys = current.split('.');
|
||||
const isMultiple = get(filesMap, [current, 'multiple'], false);
|
||||
const isGroup = get(filesMap, [current, 'isGroup'], false);
|
||||
const isComponent = get(filesMap, [current, 'isComponent'], false);
|
||||
const isRepeatable = get(filesMap, [current, 'repeatable'], false);
|
||||
|
||||
const getFilesToUpload = path => {
|
||||
@ -152,10 +157,10 @@ export const mapDataKeysToFilesToUpload = (filesMap, data) => {
|
||||
}
|
||||
}
|
||||
|
||||
if (isGroup && isRepeatable) {
|
||||
if (isComponent && isRepeatable) {
|
||||
const [key, targetKey] = current.split('.');
|
||||
const groupData = get(data, [key], []);
|
||||
const groupFiles = groupData.reduce((acc1, current, index) => {
|
||||
const componentData = get(data, [key], []);
|
||||
const componentFiles = componentData.reduce((acc1, current, index) => {
|
||||
const files = isMultiple
|
||||
? getFilesToUpload([key, index, targetKey])
|
||||
: getFileToUpload([key, index, targetKey]);
|
||||
@ -167,7 +172,7 @@ export const mapDataKeysToFilesToUpload = (filesMap, data) => {
|
||||
return acc1;
|
||||
}, {});
|
||||
|
||||
return { ...acc, ...groupFiles };
|
||||
return { ...acc, ...componentFiles };
|
||||
}
|
||||
|
||||
return acc;
|
||||
|
||||
@ -20,13 +20,13 @@ yup.addMethod(yup.mixed, 'defined', function() {
|
||||
|
||||
const getAttributes = data => get(data, ['schema', 'attributes'], {});
|
||||
|
||||
const createYupSchema = (model, { groups }) => {
|
||||
const createYupSchema = (model, { components }) => {
|
||||
const attributes = getAttributes(model);
|
||||
|
||||
return yup.object().shape(
|
||||
Object.keys(attributes).reduce((acc, current) => {
|
||||
const attribute = attributes[current];
|
||||
if (attribute.type !== 'relation' && attribute.type !== 'group') {
|
||||
if (attribute.type !== 'relation' && attribute.type !== 'component') {
|
||||
const formatted = createYupSchemaAttribute(attribute.type, attribute);
|
||||
acc[current] = formatted;
|
||||
}
|
||||
@ -43,32 +43,35 @@ const createYupSchema = (model, { groups }) => {
|
||||
: yup.array().nullable();
|
||||
}
|
||||
|
||||
if (attribute.type === 'group') {
|
||||
const groupFieldSchema = createYupSchema(groups[attribute.group], {
|
||||
groups,
|
||||
});
|
||||
if (attribute.type === 'component') {
|
||||
const componentFieldSchema = createYupSchema(
|
||||
components[attribute.component],
|
||||
{
|
||||
components,
|
||||
}
|
||||
);
|
||||
|
||||
if (attribute.repeatable === true) {
|
||||
const groupSchema =
|
||||
const componentSchema =
|
||||
attribute.required === true
|
||||
? yup
|
||||
.array()
|
||||
.of(groupFieldSchema)
|
||||
.of(componentFieldSchema)
|
||||
.defined()
|
||||
: yup
|
||||
.array()
|
||||
.of(groupFieldSchema)
|
||||
.of(componentFieldSchema)
|
||||
.nullable();
|
||||
|
||||
acc[current] = groupSchema;
|
||||
acc[current] = componentSchema;
|
||||
|
||||
return acc;
|
||||
} else {
|
||||
const groupSchema = yup.lazy(obj => {
|
||||
const componentSchema = yup.lazy(obj => {
|
||||
if (obj !== undefined) {
|
||||
return attribute.required === true
|
||||
? groupFieldSchema.defined()
|
||||
: groupFieldSchema.nullable();
|
||||
? componentFieldSchema.defined()
|
||||
: componentFieldSchema.nullable();
|
||||
}
|
||||
|
||||
return attribute.required === true
|
||||
@ -76,7 +79,7 @@ const createYupSchema = (model, { groups }) => {
|
||||
: yup.object().nullable();
|
||||
});
|
||||
|
||||
acc[current] = groupSchema;
|
||||
acc[current] = componentSchema;
|
||||
|
||||
return acc;
|
||||
}
|
||||
|
||||
@ -93,7 +93,7 @@ const ListSettingsView = ({ deleteLayout, location: { search }, slug }) => {
|
||||
.filter(key => {
|
||||
const type = get(attributes, [key, 'type'], '');
|
||||
|
||||
return !['json', 'relation', 'group'].includes(type) && !!type;
|
||||
return !['json', 'relation', 'component'].includes(type) && !!type;
|
||||
})
|
||||
.filter(field => {
|
||||
return !getListDisplayedFields().includes(field);
|
||||
|
||||
@ -124,7 +124,7 @@ function ListView({
|
||||
Object.keys(getMetaDatas())
|
||||
.filter(
|
||||
key =>
|
||||
!['json', 'group', 'relation', 'richtext'].includes(
|
||||
!['json', 'component', 'relation', 'richtext'].includes(
|
||||
get(layouts, [slug, 'schema', 'attributes', key, 'type'], '')
|
||||
)
|
||||
)
|
||||
|
||||
@ -29,10 +29,10 @@ export function getData() {
|
||||
};
|
||||
}
|
||||
|
||||
export function getDataSucceeded(groups, models, mainFields) {
|
||||
export function getDataSucceeded(components, models, mainFields) {
|
||||
return {
|
||||
type: GET_DATA_SUCCEEDED,
|
||||
groups,
|
||||
components,
|
||||
models: models.filter(model => model.isDisplayed === true),
|
||||
mainFields,
|
||||
};
|
||||
|
||||
@ -25,8 +25,8 @@ function Main({
|
||||
deleteLayout,
|
||||
getData,
|
||||
getLayout,
|
||||
groups,
|
||||
groupsAndModelsMainPossibleMainFields,
|
||||
components,
|
||||
componentsAndModelsMainPossibleMainFields,
|
||||
isLoading,
|
||||
layouts,
|
||||
location: { pathname, search },
|
||||
@ -72,9 +72,9 @@ function Main({
|
||||
currentEnvironment={currentEnvironment}
|
||||
deleteLayout={deleteLayout}
|
||||
emitEvent={emitEvent}
|
||||
groups={groups}
|
||||
groupsAndModelsMainPossibleMainFields={
|
||||
groupsAndModelsMainPossibleMainFields
|
||||
components={components}
|
||||
componentsAndModelsMainPossibleMainFields={
|
||||
componentsAndModelsMainPossibleMainFields
|
||||
}
|
||||
layouts={layouts}
|
||||
models={models}
|
||||
@ -84,7 +84,7 @@ function Main({
|
||||
);
|
||||
const routes = [
|
||||
{
|
||||
path: 'ctm-configurations/edit-settings/:type/:groupSlug',
|
||||
path: 'ctm-configurations/edit-settings/:type/:componentSlug',
|
||||
comp: EditSettingsView,
|
||||
},
|
||||
{ path: ':slug', comp: RecursivePath },
|
||||
@ -114,8 +114,8 @@ Main.propTypes = {
|
||||
currentEnvironment: PropTypes.string.isRequired,
|
||||
plugins: PropTypes.object,
|
||||
}),
|
||||
groups: PropTypes.array.isRequired,
|
||||
groupsAndModelsMainPossibleMainFields: PropTypes.object.isRequired,
|
||||
components: PropTypes.array.isRequired,
|
||||
componentsAndModelsMainPossibleMainFields: PropTypes.object.isRequired,
|
||||
isLoading: PropTypes.bool,
|
||||
layouts: PropTypes.object.isRequired,
|
||||
location: PropTypes.shape({
|
||||
|
||||
@ -15,8 +15,8 @@ import {
|
||||
} from './constants';
|
||||
|
||||
export const initialState = fromJS({
|
||||
groupsAndModelsMainPossibleMainFields: {},
|
||||
groups: [],
|
||||
componentsAndModelsMainPossibleMainFields: {},
|
||||
components: [],
|
||||
initialLayouts: {},
|
||||
isLoading: true,
|
||||
layouts: {},
|
||||
@ -31,9 +31,9 @@ function mainReducer(state = initialState, action) {
|
||||
return state.update('layouts', () => fromJS({}));
|
||||
case GET_DATA_SUCCEEDED:
|
||||
return state
|
||||
.update('groups', () => fromJS(action.groups))
|
||||
.update('components', () => fromJS(action.components))
|
||||
.update('models', () => fromJS(action.models))
|
||||
.update('groupsAndModelsMainPossibleMainFields', () =>
|
||||
.update('componentsAndModelsMainPossibleMainFields', () =>
|
||||
fromJS(action.mainFields)
|
||||
)
|
||||
.update('isLoading', () => false);
|
||||
|
||||
@ -9,13 +9,13 @@ import { GET_DATA, GET_LAYOUT } from './constants';
|
||||
|
||||
const getRequestUrl = path => `/${pluginId}/${path}`;
|
||||
|
||||
const createPossibleMainFieldsForModelsAndGroups = array => {
|
||||
const createPossibleMainFieldsForModelsAndComponents = array => {
|
||||
return array.reduce((acc, current) => {
|
||||
const attributes = get(current, ['schema', 'attributes'], {});
|
||||
const possibleMainFields = Object.keys(attributes).filter(attr => {
|
||||
return ![
|
||||
'boolean',
|
||||
'group',
|
||||
'component',
|
||||
'json',
|
||||
'media',
|
||||
'password',
|
||||
@ -33,16 +33,16 @@ const createPossibleMainFieldsForModelsAndGroups = array => {
|
||||
|
||||
function* getData() {
|
||||
try {
|
||||
const [{ data: groups }, { data: models }] = yield all(
|
||||
['groups', 'content-types'].map(endPoint =>
|
||||
const [{ data: components }, { data: models }] = yield all(
|
||||
['components', 'content-types'].map(endPoint =>
|
||||
call(request, getRequestUrl(endPoint), { method: 'GET' })
|
||||
)
|
||||
);
|
||||
|
||||
yield put(
|
||||
getDataSucceeded(groups, models, {
|
||||
...createPossibleMainFieldsForModelsAndGroups(groups),
|
||||
...createPossibleMainFieldsForModelsAndGroups(models),
|
||||
getDataSucceeded(components, models, {
|
||||
...createPossibleMainFieldsForModelsAndComponents(components),
|
||||
...createPossibleMainFieldsForModelsAndComponents(models),
|
||||
})
|
||||
);
|
||||
} catch (err) {
|
||||
|
||||
@ -12,8 +12,8 @@ describe('Content Manager | Main | reducer', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
groupsAndModelsMainPossibleMainFields: {},
|
||||
groups: [],
|
||||
componentsAndModelsMainPossibleMainFields: {},
|
||||
components: [],
|
||||
initialLayouts: {
|
||||
test: {
|
||||
layouts: {
|
||||
|
||||
@ -5,10 +5,6 @@ const EditView = lazy(() => import('../EditView'));
|
||||
const EditSettingsView = lazy(() => import('../EditSettingsView'));
|
||||
const ListView = lazy(() => import('../ListView'));
|
||||
const ListSettingsView = lazy(() => import('../ListSettingsView'));
|
||||
// import EditView from '../EditView';
|
||||
// import EditSettingsView from '../EditSettingsView';
|
||||
// import ListSettingsView from '../ListSettingsView';
|
||||
// import ListView from '../ListView';
|
||||
|
||||
const RecursivePath = props => {
|
||||
const { url } = useRouteMatch();
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
export default {
|
||||
COMPONENT: 'component',
|
||||
EDIT_FIELD: 'editField',
|
||||
EDIT_RELATION: 'editRelation',
|
||||
FIELD: 'field',
|
||||
GROUP: 'group',
|
||||
RELATION: 'relation',
|
||||
};
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
exports[`<Loader /> should not crash 1`] = `
|
||||
<div
|
||||
className="sc-jnlKLf dzQmya"
|
||||
className="sc-fYxtnH jGsbTZ"
|
||||
>
|
||||
<div
|
||||
className="centered"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user