diff --git a/client/src/components/boards/Board/FiniteContent.jsx b/client/src/components/boards/Board/FiniteContent.jsx index 1e6cc174..0b7c0d98 100644 --- a/client/src/components/boards/Board/FiniteContent.jsx +++ b/client/src/components/boards/Board/FiniteContent.jsx @@ -46,6 +46,7 @@ const FiniteContent = React.memo(() => { return ( diff --git a/client/src/components/boards/Board/ListView.jsx b/client/src/components/boards/Board/ListView.jsx index 712c6652..7ac96f09 100755 --- a/client/src/components/boards/Board/ListView.jsx +++ b/client/src/components/boards/Board/ListView.jsx @@ -6,22 +6,39 @@ import React, { useCallback, useState } from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { shallowEqual, useSelector } from 'react-redux'; +import { shallowEqual, useDispatch, useSelector, useStore } from 'react-redux'; import { useInView } from 'react-intersection-observer'; import { useTranslation } from 'react-i18next'; import { Button, Icon, Loader } from 'semantic-ui-react'; +import { DragDropContext, Droppable } from 'react-beautiful-dnd'; import { Tooltip } from '../../../lib/custom-ui'; import selectors from '../../../selectors'; +import entryActions from '../../../entry-actions'; import { BoardMembershipRoles } from '../../../constants/Enums'; +import DroppableTypes from '../../../constants/DroppableTypes'; +import parseDndId from '../../../utils/parse-dnd-id'; +import { closePopup } from '../../../lib/popup'; +import globalStyles from '../../../styles.module.scss'; import Card from '../../cards/Card'; +import DraggableCard from '../../cards/DraggableCard'; import AddCard from '../../cards/AddCard'; import PlusMathIcon from '../../../assets/images/plus-math-icon.svg?react'; import styles from './ListView.module.scss'; const ListView = React.memo( - ({ cardIds, isCardsFetching, isAllCardsFetched, onCardsFetch, onCardCreate, onCardPaste }) => { + ({ + cardIds, + isCardsFetching, + isAllCardsFetched, + isReorderingEnabled, + onCardsFetch, + onCardCreate, + onCardPaste, + }) => { + const store = useStore(); + const dispatch = useDispatch(); const clipboard = useSelector(selectors.selectClipboard); const { canAddCard, canPasteCard } = useSelector((state) => { @@ -54,6 +71,94 @@ const ListView = React.memo( setIsAddCardOpened(false); }, []); + const handleDragStart = useCallback(() => { + document.body.classList.add(globalStyles.dragging); + closePopup(); + }, []); + + const handleDragEnd = useCallback( + ({ draggableId, type, source, destination }) => { + document.body.classList.remove(globalStyles.dragging); + + if (!destination || type !== DroppableTypes.CARD) { + return; + } + + if (source.index === destination.index) { + return; + } + + const cardId = parseDndId(draggableId); + const state = store.getState(); + const getListId = (id) => { + const card = id ? selectors.selectCardById(state, id) : null; + return card ? card.listId : null; + }; + + const newOrder = cardIds.filter((id) => id !== cardId); + const prevId = destination.index > 0 ? newOrder[destination.index - 1] : null; + const nextId = destination.index < newOrder.length ? newOrder[destination.index] : null; + + const targetListId = getListId(prevId) || getListId(nextId); + if (!targetListId) { + return; + } + + let localIndex = 0; + for (let i = 0; i < destination.index; i += 1) { + if (getListId(newOrder[i]) === targetListId) { + localIndex += 1; + } + } + + dispatch(entryActions.moveCard(cardId, targetListId, localIndex)); + }, + [cardIds, dispatch, store], + ); + + const renderCardsContent = () => { + if (cardIds.length === 0) { + return null; + } + + if (!isReorderingEnabled) { + return ( +
+ {cardIds.map((cardId, cardIndex) => ( +
+ +
+ ))} +
+ ); + } + + return ( + + + {({ innerRef, droppableProps, placeholder }) => ( +
+ {cardIds.map((cardId, cardIndex) => ( + + ))} + {placeholder} +
+ )} +
+
+ ); + }; + return (
{canAddCard && @@ -88,15 +193,7 @@ const ListView = React.memo( )}
))} - {cardIds.length > 0 && ( -
- {cardIds.map((cardId, cardIndex) => ( -
- -
- ))} -
- )} + {renderCardsContent()} {isCardsFetching !== undefined && isAllCardsFetched !== undefined && (
{isCardsFetching ? ( @@ -115,6 +212,7 @@ ListView.propTypes = { cardIds: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types isCardsFetching: PropTypes.bool, isAllCardsFetched: PropTypes.bool, + isReorderingEnabled: PropTypes.bool, onCardsFetch: PropTypes.func, onCardCreate: PropTypes.func, onCardPaste: PropTypes.func, @@ -123,6 +221,7 @@ ListView.propTypes = { ListView.defaultProps = { isCardsFetching: undefined, isAllCardsFetched: undefined, + isReorderingEnabled: false, onCardsFetch: undefined, onCardCreate: undefined, onCardPaste: undefined,