266 lines
8.5 KiB
JavaScript
Raw Normal View History

import React, { memo, useCallback, useMemo, useState } from 'react';
2019-11-04 09:00:59 +01:00
import { get } from 'lodash';
import isEqual from 'react-fast-compare';
import PropTypes from 'prop-types';
2019-11-04 10:10:38 +01:00
import { FormattedMessage } from 'react-intl';
2019-12-02 19:20:55 +01:00
import { Arrow } from '@buffetjs/icons';
2019-12-06 11:09:48 +01:00
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
2019-11-04 10:10:38 +01:00
import pluginId from '../../pluginId';
2019-11-19 16:17:15 +01:00
import useEditView from '../../hooks/useEditView';
2019-11-04 10:10:38 +01:00
import DynamicComponentCard from '../DynamicComponentCard';
2019-11-06 16:29:19 +01:00
import FieldComponent from '../FieldComponent';
import NotAllowedInput from '../NotAllowedInput';
import connect from './utils/connect';
import select from './utils/select';
import BaselineAlignement from './BaselineAlignement';
2019-11-04 09:00:59 +01:00
import Button from './Button';
2019-11-04 10:10:38 +01:00
import ComponentsPicker from './ComponentsPicker';
2019-11-06 16:29:19 +01:00
import ComponentWrapper from './ComponentWrapper';
2019-11-26 16:15:59 +01:00
import DynamicZoneWrapper from './DynamicZoneWrapper';
2019-11-06 16:29:19 +01:00
import Label from './Label';
import RoundCTA from './RoundCTA';
2019-11-04 09:00:59 +01:00
import Wrapper from './Wrapper';
/* eslint-disable react/no-array-index-key */
const DynamicZone = ({
max,
min,
name,
// Passed with the select function
addComponentToDynamicZone,
formErrors,
isCreatingEntry,
isFieldAllowed,
isFieldReadable,
layout,
moveComponentUp,
moveComponentDown,
removeComponentFromDynamicZone,
dynamicDisplayedComponents,
}) => {
2019-11-20 17:33:49 +01:00
const [isOpen, setIsOpen] = useState(false);
2019-11-18 18:14:36 +01:00
2019-11-19 16:17:15 +01:00
const { components } = useEditView();
const getDynamicComponentSchemaData = useCallback(
componentUid => {
const component = components.find(compo => compo.uid === componentUid);
const { schema } = component;
return schema;
},
[components]
);
2019-11-19 16:17:15 +01:00
const getDynamicComponentInfos = useCallback(
componentUid => {
const {
info: { icon, name },
} = getDynamicComponentSchemaData(componentUid);
2019-11-19 16:17:15 +01:00
return { icon, name };
},
[getDynamicComponentSchemaData]
);
const dynamicZoneErrors = useMemo(() => {
return Object.keys(formErrors)
.filter(key => {
return key === name;
})
.map(key => formErrors[key]);
}, [formErrors, name]);
const dynamicZoneAvailableComponents = useMemo(
() => get(layout, ['schema', 'attributes', name, 'components'], []),
[layout, name]
2019-11-04 09:00:59 +01:00
);
2019-11-18 18:14:36 +01:00
const metas = useMemo(() => get(layout, ['metadatas', name, 'edit'], {}), [layout, name]);
const dynamicDisplayedComponentsLength = dynamicDisplayedComponents.length;
const missingComponentNumber = min - dynamicDisplayedComponentsLength;
const hasError = dynamicZoneErrors.length > 0;
const hasMinError =
dynamicZoneErrors.length > 0 && get(dynamicZoneErrors, [0, 'id'], '').includes('min');
2019-11-20 17:33:49 +01:00
const hasRequiredError = hasError && !hasMinError;
2019-12-11 16:52:35 +01:00
const hasMaxError =
hasError && get(dynamicZoneErrors, [0, 'id'], '') === 'components.Input.error.validation.max';
2019-11-20 17:33:49 +01:00
if (!isFieldAllowed && isCreatingEntry) {
return (
<BaselineAlignement>
<NotAllowedInput label={metas.label} spacerHeight="3px" />
</BaselineAlignement>
);
}
if (!isFieldAllowed && !isFieldReadable && !isCreatingEntry) {
return (
<BaselineAlignement>
<NotAllowedInput label={metas.label} spacerHeight="3px" />
</BaselineAlignement>
);
}
2019-11-04 09:00:59 +01:00
return (
2019-11-26 16:15:59 +01:00
<DynamicZoneWrapper>
{dynamicDisplayedComponentsLength > 0 && (
2019-11-06 16:29:19 +01:00
<Label>
<p>{metas.label}</p>
<p>{metas.description}</p>
</Label>
)}
2019-11-19 18:26:01 +01:00
<ComponentWrapper>
{dynamicDisplayedComponents.map((componentUid, index) => {
2019-11-19 18:26:01 +01:00
const showDownIcon =
isFieldAllowed &&
dynamicDisplayedComponentsLength > 0 &&
index < dynamicDisplayedComponentsLength - 1;
const showUpIcon = isFieldAllowed && dynamicDisplayedComponentsLength > 0 && index > 0;
2019-11-19 18:26:01 +01:00
return (
<div key={index}>
2019-12-02 19:20:55 +01:00
<div className="arrow-icons">
{showDownIcon && (
<RoundCTA
className="arrow-btn arrow-down"
onClick={() => moveComponentDown(name, index)}
>
<Arrow />
</RoundCTA>
)}
{showUpIcon && (
<RoundCTA
className="arrow-btn arrow-up"
onClick={() => moveComponentUp(name, index)}
>
<Arrow />
</RoundCTA>
)}
</div>
{isFieldAllowed && (
<RoundCTA onClick={() => removeComponentFromDynamicZone(name, index)}>
<FontAwesomeIcon icon="trash-alt" />
</RoundCTA>
)}
2019-11-19 18:26:01 +01:00
<FieldComponent
componentUid={componentUid}
componentFriendlyName={getDynamicComponentInfos(componentUid).name}
2019-12-06 11:37:58 +01:00
icon={getDynamicComponentInfos(componentUid).icon}
2019-11-19 18:26:01 +01:00
label=""
name={`${name}.${index}`}
isFromDynamicZone
2019-11-19 18:26:01 +01:00
/>
</div>
);
})}
</ComponentWrapper>
{isFieldAllowed ? (
<Wrapper>
<Button
type="button"
hasError={hasError}
className={isOpen && 'isOpen'}
onClick={() => {
if (dynamicDisplayedComponentsLength < max) {
setIsOpen(prev => !prev);
} else {
strapi.notification.toggle({
type: 'info',
message: { id: `${pluginId}.components.notification.info.maximum-requirement` },
});
}
}}
/>
{hasRequiredError && !isOpen && !hasMaxError && (
<div className="error-label">
<FormattedMessage id={`${pluginId}.components.DynamicZone.required`} />
</div>
)}
{hasMaxError && !isOpen && (
<div className="error-label">
<FormattedMessage id="components.Input.error.validation.max" />
</div>
)}
{hasMinError && !isOpen && (
<div className="error-label">
<FormattedMessage
id={`${pluginId}.components.DynamicZone.missing${
missingComponentNumber > 1 ? '.plural' : '.singular'
}`}
values={{ count: missingComponentNumber }}
/>
</div>
)}
<div className="info">
2019-11-26 16:15:59 +01:00
<FormattedMessage
id={`${pluginId}.components.DynamicZone.add-compo`}
values={{ componentName: name }}
2019-11-26 16:15:59 +01:00
/>
2019-11-21 16:49:06 +01:00
</div>
<ComponentsPicker isOpen={isOpen}>
<div>
<p className="componentPickerTitle">
<FormattedMessage id={`${pluginId}.components.DynamicZone.pick-compo`} />
</p>
<div className="componentsList">
{dynamicZoneAvailableComponents.map(componentUid => {
const { icon, name: friendlyName } = getDynamicComponentInfos(componentUid);
return (
<DynamicComponentCard
key={componentUid}
componentUid={componentUid}
friendlyName={friendlyName}
icon={icon}
onClick={() => {
setIsOpen(false);
const shouldCheckErrors = hasError;
addComponentToDynamicZone(name, componentUid, shouldCheckErrors);
}}
/>
);
})}
</div>
2019-11-18 18:14:36 +01:00
</div>
</ComponentsPicker>
</Wrapper>
) : (
<BaselineAlignement top="9px" />
)}
2019-11-26 16:15:59 +01:00
</DynamicZoneWrapper>
2019-11-04 09:00:59 +01:00
);
};
DynamicZone.defaultProps = {
dynamicDisplayedComponents: [],
max: Infinity,
min: -Infinity,
};
DynamicZone.propTypes = {
addComponentToDynamicZone: PropTypes.func.isRequired,
dynamicDisplayedComponents: PropTypes.array,
formErrors: PropTypes.object.isRequired,
isCreatingEntry: PropTypes.bool.isRequired,
isFieldAllowed: PropTypes.bool.isRequired,
isFieldReadable: PropTypes.bool.isRequired,
layout: PropTypes.object.isRequired,
moveComponentUp: PropTypes.func.isRequired,
moveComponentDown: PropTypes.func.isRequired,
2019-11-08 16:29:51 +01:00
max: PropTypes.number,
min: PropTypes.number,
name: PropTypes.string.isRequired,
removeComponentFromDynamicZone: PropTypes.func.isRequired,
};
const Memoized = memo(DynamicZone, isEqual);
export default connect(Memoized, select);
export { DynamicZone };