diff --git a/packages/core/admin/admin/src/content-manager/components/EditViewDataManagerProvider/index.js b/packages/core/admin/admin/src/content-manager/components/EditViewDataManagerProvider/index.js index 6975e24b33..9f2d5bf301 100644 --- a/packages/core/admin/admin/src/content-manager/components/EditViewDataManagerProvider/index.js +++ b/packages/core/admin/admin/src/content-manager/components/EditViewDataManagerProvider/index.js @@ -498,12 +498,12 @@ const EditViewDataManagerProvider = ({ [shouldCheckDZErrors] ); - const moveComponentField = useCallback((pathToComponent, dragIndex, hoverIndex) => { + const moveComponentField = useCallback(({ name, newIndex, currentIndex }) => { dispatch({ type: 'MOVE_COMPONENT_FIELD', - pathToComponent, - dragIndex, - hoverIndex, + keys: name.split('.'), + newIndex, + oldIndex: currentIndex, }); }, []); diff --git a/packages/core/admin/admin/src/content-manager/components/EditViewDataManagerProvider/reducer.js b/packages/core/admin/admin/src/content-manager/components/EditViewDataManagerProvider/reducer.js index d8e58893f2..80849ad2eb 100644 --- a/packages/core/admin/admin/src/content-manager/components/EditViewDataManagerProvider/reducer.js +++ b/packages/core/admin/admin/src/content-manager/components/EditViewDataManagerProvider/reducer.js @@ -178,6 +178,7 @@ const reducer = (state, action) => break; } + case 'MOVE_COMPONENT_FIELD': case 'REORDER_RELATION': { const { oldIndex, newIndex, keys } = action; const path = ['modifiedData', ...keys]; @@ -265,25 +266,6 @@ const reducer = (state, action) => draftState.shouldCheckErrors = false; break; } - case 'MOVE_COMPONENT_FIELD': { - const currentValue = get(state, ['modifiedData', ...action.pathToComponent]); - const valueToInsert = get(state, [ - 'modifiedData', - ...action.pathToComponent, - action.dragIndex, - ]); - - const updatedValue = moveFields( - currentValue, - action.dragIndex, - action.hoverIndex, - valueToInsert - ); - - set(draftState, ['modifiedData', ...action.pathToComponent], updatedValue); - - break; - } case 'MOVE_COMPONENT_UP': case 'MOVE_COMPONENT_DOWN': { const { currentIndex, dynamicZoneName, shouldCheckErrors } = action; diff --git a/packages/core/admin/admin/src/content-manager/components/RepeatableComponent/DraggedItem/DraggingSibling.js b/packages/core/admin/admin/src/content-manager/components/RepeatableComponent/DraggedItem/DraggingSibling.js deleted file mode 100644 index ad42753867..0000000000 --- a/packages/core/admin/admin/src/content-manager/components/RepeatableComponent/DraggedItem/DraggingSibling.js +++ /dev/null @@ -1,72 +0,0 @@ -import React from 'react'; -import styled from 'styled-components'; -import PropTypes from 'prop-types'; -import { Stack } from '@strapi/design-system/Stack'; -import { Flex } from '@strapi/design-system/Flex'; -import { TextButton } from '@strapi/design-system/TextButton'; -import { Icon } from '@strapi/design-system/Icon'; -import { Typography } from '@strapi/design-system/Typography'; -import Trash from '@strapi/icons/Trash'; -import Drag from '@strapi/icons/Drag'; -import DropdownIcon from '@strapi/icons/CarretDown'; -import { CustomIconButtonSibling } from './IconButtonCustoms'; - -const SiblingWrapper = styled.span` - display: flex; - justify-content: space-between; - padding-left: ${({ theme }) => theme.spaces[4]}; - padding-right: ${({ theme }) => theme.spaces[4]}; - background-color: ${({ theme }) => theme.colors.neutral0}; - height: ${50 / 16}rem; -`; - -const ToggleButton = styled(TextButton)` - text-align: left; - - svg { - width: ${14 / 16}rem; - height: ${14 / 16}rem; - - path { - fill: ${({ theme, expanded }) => - expanded ? theme.colors.primary600 : theme.colors.neutral500}; - } - } -`; - -const DraggingSibling = ({ displayedValue }) => { - return ( - - - - - - - {}} flex={1}> - - {displayedValue} - - - - - - {}} icon={} /> - } noBorder /> - - - ); -}; - -DraggingSibling.propTypes = { - displayedValue: PropTypes.string.isRequired, -}; - -export default DraggingSibling; diff --git a/packages/core/admin/admin/src/content-manager/components/RepeatableComponent/DraggedItem/Preview.js b/packages/core/admin/admin/src/content-manager/components/RepeatableComponent/DraggedItem/Preview.js index 08055e08b6..d008d08119 100644 --- a/packages/core/admin/admin/src/content-manager/components/RepeatableComponent/DraggedItem/Preview.js +++ b/packages/core/admin/admin/src/content-manager/components/RepeatableComponent/DraggedItem/Preview.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { forwardRef } from 'react'; import styled from 'styled-components'; const StyledSpan = styled.span` @@ -9,8 +9,8 @@ const StyledSpan = styled.span` padding: ${({ theme }) => theme.spaces[6]}; `; -const Preview = () => { - return ; -}; +const Preview = forwardRef((_, ref) => { + return ; +}); export default Preview; diff --git a/packages/core/admin/admin/src/content-manager/components/RepeatableComponent/DraggedItem/index.js b/packages/core/admin/admin/src/content-manager/components/RepeatableComponent/DraggedItem/index.js index 41fcb02fc1..34e7ceafbf 100644 --- a/packages/core/admin/admin/src/content-manager/components/RepeatableComponent/DraggedItem/index.js +++ b/packages/core/admin/admin/src/content-manager/components/RepeatableComponent/DraggedItem/index.js @@ -1,24 +1,27 @@ /* eslint-disable import/no-cycle */ -import React, { memo, useEffect, useRef, useState } from 'react'; +import React, { memo, useEffect, useRef } from 'react'; import PropTypes from 'prop-types'; import { useDrag, useDrop } from 'react-dnd'; import { getEmptyImage } from 'react-dnd-html5-backend'; import styled from 'styled-components'; import { useIntl } from 'react-intl'; import toString from 'lodash/toString'; + import { Accordion, AccordionToggle, AccordionContent } from '@strapi/design-system/Accordion'; import { Grid, GridItem } from '@strapi/design-system/Grid'; import { Stack } from '@strapi/design-system/Stack'; import { Box } from '@strapi/design-system/Box'; import { Tooltip } from '@strapi/design-system/Tooltip'; + import Trash from '@strapi/icons/Trash'; import Drag from '@strapi/icons/Drag'; -import ItemTypes from '../../../utils/ItemTypes'; -import getTrad from '../../../utils/getTrad'; + +import { composeRefs, getTrad, ItemTypes } from '../../../utils'; + import Inputs from '../../Inputs'; import FieldComponent from '../../FieldComponent'; + import Preview from './Preview'; -import DraggingSibling from './DraggingSibling'; import { CustomIconButton } from './IconButtonCustoms'; import { connect, select } from './utils'; @@ -48,7 +51,7 @@ const DraggedItem = ({ // Errors are retrieved from the AccordionGroupCustom cloneElement hasErrorMessage, hasErrors, - isDraggingSibling, + index, isOpen, isReadOnly, onClickToggle, @@ -57,90 +60,70 @@ const DraggedItem = ({ // Retrieved from the select function moveComponentField, removeRepeatableField, - setIsDraggingSibling, triggerFormValidation, - // checkFormErrors, displayedValue, }) => { - const dragRef = useRef(null); - const dropRef = useRef(null); - const [, forceRerenderAfterDnd] = useState(false); + const accordionRef = useRef(null); + const boxRef = useRef(null); const { formatMessage } = useIntl(); const fields = schema.layouts.edit; - const [, drop] = useDrop({ + const [{ handlerId }, dropRef] = useDrop({ accept: ItemTypes.COMPONENT, - canDrop() { - return false; + collect(monitor) { + return { + handlerId: monitor.getHandlerId(), + }; }, hover(item, monitor) { - if (!dropRef.current) { + if (!boxRef.current) { return; } - const dragPath = item.originalPath; - const hoverPath = componentFieldName; - const fullPathToComponentArray = dragPath.split('.'); - const dragIndexString = fullPathToComponentArray.slice().splice(-1).join(''); - const hoverIndexString = hoverPath.split('.').splice(-1).join(''); - const pathToComponentArray = fullPathToComponentArray.slice( - 0, - fullPathToComponentArray.length - 1 - ); - const dragIndex = parseInt(dragIndexString, 10); - const hoverIndex = parseInt(hoverIndexString, 10); + const dragIndex = item.index; + const currentIndex = index; // Don't replace items with themselves - if (dragIndex === hoverIndex) { + if (dragIndex === currentIndex) { return; } - // Determine rectangle on screen - const hoverBoundingRect = dropRef.current.getBoundingClientRect(); - // Get vertical middle + const hoverBoundingRect = boxRef.current.getBoundingClientRect(); const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2; - // Determine mouse position const clientOffset = monitor.getClientOffset(); - // Get pixels to the top const hoverClientY = clientOffset.y - hoverBoundingRect.top; - // Only perform the move when the mouse has crossed half of the items height - // When dragging downwards, only move when the cursor is below 50% - // When dragging upwards, only move when the cursor is above 50% // Dragging downwards - if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) { + if (dragIndex < currentIndex && hoverClientY < hoverMiddleY) { return; } - // Dragging upwards - if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) { - return; - } - // If They are not in the same level, should not move - if (dragPath.split('.').length !== hoverPath.split('.').length) { - return; - } - // Time to actually perform the action in the data - moveComponentField(pathToComponentArray, dragIndex, hoverIndex); - item.originalPath = hoverPath; + // Dragging upwards + if (dragIndex > currentIndex && hoverClientY > hoverMiddleY) { + return; + } + + // Time to actually perform the action + moveComponentField(dragIndex, currentIndex); + + item.index = currentIndex; }, }); - const [{ isDragging }, drag, preview] = useDrag({ + const [{ isDragging }, dragRef, previewRef] = useDrag({ type: ItemTypes.COMPONENT, item() { // Close all collapses toggleCollapses(-1); return { - displayedValue, - originalPath: componentFieldName, + index, }; }, end() { // Update the errors triggerFormValidation(); - setIsDraggingSibling(false); + // setIsDraggingSibling(false); }, collect: (monitor) => ({ isDragging: monitor.isDragging(), @@ -148,43 +131,20 @@ const DraggedItem = ({ }); useEffect(() => { - preview(getEmptyImage(), { captureDraggingState: false }); - }, [preview]); - - useEffect(() => { - if (isDragging) { - setIsDraggingSibling(true); - } - }, [isDragging, setIsDraggingSibling]); - - // Effect in order to force a rerender after reordering the components - // Since we are removing the Accordion when doing the DnD we are losing the dragRef, therefore the replaced element cannot be dragged - // anymore, this hack forces a rerender in order to apply the dragRef - useEffect(() => { - if (!isDraggingSibling) { - forceRerenderAfterDnd((prev) => !prev); - } - }, [isDraggingSibling]); - - // Create the refs - // We need 1 for the drop target - // 1 for the drag target - const refs = { - dragRef: drag(dragRef), - dropRef: drop(dropRef), - }; + previewRef(getEmptyImage(), { captureDraggingState: false }); + }, [previewRef]); const accordionTitle = toString(displayedValue); const accordionHasError = hasErrors ? 'error' : undefined; - return ( - - {isDragging && } - {!isDragging && isDraggingSibling && ( - - )} + const composedAccordionRefs = composeRefs(accordionRef, dragRef); + const composedBoxRefs = composeRefs(boxRef, dropRef); - {!isDragging && !isDraggingSibling && ( + return ( + + {isDragging ? ( + + ) : ( e.stopPropagation()} + data-handler-id={handlerId} > @@ -290,9 +251,7 @@ const DraggedItem = ({ DraggedItem.defaultProps = { componentUid: undefined, - isDraggingSibling: false, isOpen: false, - setIsDraggingSibling() {}, toggleCollapses() {}, }; @@ -301,7 +260,7 @@ DraggedItem.propTypes = { componentUid: PropTypes.string, hasErrorMessage: PropTypes.bool.isRequired, hasErrors: PropTypes.bool.isRequired, - isDraggingSibling: PropTypes.bool, + index: PropTypes.number.isRequired, isOpen: PropTypes.bool, isReadOnly: PropTypes.bool.isRequired, onClickToggle: PropTypes.func.isRequired, @@ -309,9 +268,7 @@ DraggedItem.propTypes = { toggleCollapses: PropTypes.func, moveComponentField: PropTypes.func.isRequired, removeRepeatableField: PropTypes.func.isRequired, - setIsDraggingSibling: PropTypes.func, triggerFormValidation: PropTypes.func.isRequired, - // checkFormErrors: PropTypes.func.isRequired, displayedValue: PropTypes.string.isRequired, }; diff --git a/packages/core/admin/admin/src/content-manager/components/RepeatableComponent/DraggedItem/utils/select.js b/packages/core/admin/admin/src/content-manager/components/RepeatableComponent/DraggedItem/utils/select.js index bd158c6ddb..10209a6569 100644 --- a/packages/core/admin/admin/src/content-manager/components/RepeatableComponent/DraggedItem/utils/select.js +++ b/packages/core/admin/admin/src/content-manager/components/RepeatableComponent/DraggedItem/utils/select.js @@ -3,13 +3,7 @@ import { get, toString } from 'lodash'; import { useCMEditViewDataManager } from '@strapi/helper-plugin'; function useSelect({ schema, componentFieldName }) { - const { - checkFormErrors, - modifiedData, - moveComponentField, - removeRepeatableField, - triggerFormValidation, - } = useCMEditViewDataManager(); + const { modifiedData, removeRepeatableField, triggerFormValidation } = useCMEditViewDataManager(); const mainField = useMemo(() => get(schema, ['settings', 'mainField'], 'id'), [schema]); const displayedValue = toString( @@ -19,8 +13,6 @@ function useSelect({ schema, componentFieldName }) { return { displayedValue, mainField, - checkFormErrors, - moveComponentField, removeRepeatableField, schema, triggerFormValidation, diff --git a/packages/core/admin/admin/src/content-manager/components/RepeatableComponent/index.js b/packages/core/admin/admin/src/content-manager/components/RepeatableComponent/index.js index d71f45b6ff..8caf3dd591 100644 --- a/packages/core/admin/admin/src/content-manager/components/RepeatableComponent/index.js +++ b/packages/core/admin/admin/src/content-manager/components/RepeatableComponent/index.js @@ -1,25 +1,26 @@ -import React, { memo, useCallback, useMemo, useState } from 'react'; /* eslint-disable import/no-cycle */ -import { useDrop } from 'react-dnd'; +import React, { memo, useCallback, useMemo, useState } from 'react'; import { useIntl } from 'react-intl'; import styled from 'styled-components'; import PropTypes from 'prop-types'; import get from 'lodash/get'; -import { useNotification } from '@strapi/helper-plugin'; + +import { useNotification, useCMEditViewDataManager } from '@strapi/helper-plugin'; + import { Box } from '@strapi/design-system/Box'; import { Flex } from '@strapi/design-system/Flex'; import { TextButton } from '@strapi/design-system/TextButton'; import Plus from '@strapi/icons/Plus'; + import { getMaxTempKey, getTrad } from '../../utils'; import { useContentTypeLayout } from '../../hooks'; -import ItemTypes from '../../utils/ItemTypes'; + import ComponentInitializer from '../ComponentInitializer'; -import connect from './utils/connect'; -import select from './utils/select'; -import getComponentErrorKeys from './utils/getComponentErrorKeys'; import DraggedItem from './DraggedItem'; import AccordionGroupCustom from './AccordionGroupCustom'; +import getComponentErrorKeys from './utils/getComponentErrorKeys'; + const TextButtonCustom = styled(TextButton)` height: 100%; width: 100%; @@ -33,8 +34,6 @@ const TextButtonCustom = styled(TextButton)` `; const RepeatableComponent = ({ - addRepeatableComponentToField, - formErrors, componentUid, componentValue, componentValueLength, @@ -43,11 +42,11 @@ const RepeatableComponent = ({ min, name, }) => { + const { addRepeatableComponentToField, formErrors, moveComponentField } = + useCMEditViewDataManager(); const toggleNotification = useNotification(); const { formatMessage } = useIntl(); const [collapseToOpen, setCollapseToOpen] = useState(''); - const [isDraggingSibling, setIsDraggingSibling] = useState(false); - const [, drop] = useDrop({ accept: ItemTypes.COMPONENT }); const { getComponentLayout, components } = useContentTypeLayout(); const componentLayoutData = useMemo( () => getComponentLayout(componentUid), @@ -124,8 +123,16 @@ const RepeatableComponent = ({ }; } + const handleMoveComponentField = (newIndex, currentIndex) => { + moveComponentField({ + name, + newIndex, + currentIndex, + }); + }; + return ( - + ); })} @@ -177,25 +184,20 @@ const RepeatableComponent = ({ RepeatableComponent.defaultProps = { componentValue: null, componentValueLength: 0, - formErrors: {}, max: Infinity, min: 0, }; RepeatableComponent.propTypes = { - addRepeatableComponentToField: PropTypes.func.isRequired, componentUid: PropTypes.string.isRequired, componentValue: PropTypes.oneOfType([PropTypes.array, PropTypes.object]), componentValueLength: PropTypes.number, - formErrors: PropTypes.object, isReadOnly: PropTypes.bool.isRequired, max: PropTypes.number, min: PropTypes.number, name: PropTypes.string.isRequired, }; -const Memoized = memo(RepeatableComponent); - -export default connect(Memoized, select); +export default memo(RepeatableComponent); export { RepeatableComponent }; diff --git a/packages/core/admin/admin/src/content-manager/components/RepeatableComponent/utils/connect.js b/packages/core/admin/admin/src/content-manager/components/RepeatableComponent/utils/connect.js deleted file mode 100644 index f763338c32..0000000000 --- a/packages/core/admin/admin/src/content-manager/components/RepeatableComponent/utils/connect.js +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react'; - -function connect(WrappedComponent, select) { - return (props) => { - const selectors = select(props); - - return ; - }; -} - -export default connect; diff --git a/packages/core/admin/admin/src/content-manager/components/RepeatableComponent/utils/select.js b/packages/core/admin/admin/src/content-manager/components/RepeatableComponent/utils/select.js deleted file mode 100644 index 93d746e76c..0000000000 --- a/packages/core/admin/admin/src/content-manager/components/RepeatableComponent/utils/select.js +++ /dev/null @@ -1,12 +0,0 @@ -import { useCMEditViewDataManager } from '@strapi/helper-plugin'; - -function useSelect() { - const { addRepeatableComponentToField, formErrors } = useCMEditViewDataManager(); - - return { - addRepeatableComponentToField, - formErrors, - }; -} - -export default useSelect;