Merge pull request #1649 from symonbaikov/feat/list-view-reorder
feat: allow re-ordering cards in list view Conflicted only on the import block, where the icon button tooltips had added a line. Both imports stay.
This commit is contained in:
@@ -46,6 +46,7 @@ const FiniteContent = React.memo(() => {
|
|||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
cardIds={cardIds}
|
cardIds={cardIds}
|
||||||
|
isReorderingEnabled={board.view === BoardViews.LIST}
|
||||||
onCardCreate={canAddCard ? handleCardCreate : undefined}
|
onCardCreate={canAddCard ? handleCardCreate : undefined}
|
||||||
onCardPaste={canAddCard ? handleCardPaste : undefined}
|
onCardPaste={canAddCard ? handleCardPaste : undefined}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -6,22 +6,39 @@
|
|||||||
import React, { useCallback, useState } from 'react';
|
import React, { useCallback, useState } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classNames from 'classnames';
|
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 { useInView } from 'react-intersection-observer';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Button, Icon, Loader } from 'semantic-ui-react';
|
import { Button, Icon, Loader } from 'semantic-ui-react';
|
||||||
|
import { DragDropContext, Droppable } from 'react-beautiful-dnd';
|
||||||
import { Tooltip } from '../../../lib/custom-ui';
|
import { Tooltip } from '../../../lib/custom-ui';
|
||||||
|
|
||||||
import selectors from '../../../selectors';
|
import selectors from '../../../selectors';
|
||||||
|
import entryActions from '../../../entry-actions';
|
||||||
import { BoardMembershipRoles } from '../../../constants/Enums';
|
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 Card from '../../cards/Card';
|
||||||
|
import DraggableCard from '../../cards/DraggableCard';
|
||||||
import AddCard from '../../cards/AddCard';
|
import AddCard from '../../cards/AddCard';
|
||||||
import PlusMathIcon from '../../../assets/images/plus-math-icon.svg?react';
|
import PlusMathIcon from '../../../assets/images/plus-math-icon.svg?react';
|
||||||
|
|
||||||
import styles from './ListView.module.scss';
|
import styles from './ListView.module.scss';
|
||||||
|
|
||||||
const ListView = React.memo(
|
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 clipboard = useSelector(selectors.selectClipboard);
|
||||||
|
|
||||||
const { canAddCard, canPasteCard } = useSelector((state) => {
|
const { canAddCard, canPasteCard } = useSelector((state) => {
|
||||||
@@ -54,6 +71,94 @@ const ListView = React.memo(
|
|||||||
setIsAddCardOpened(false);
|
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 (
|
||||||
|
<div className={classNames(styles.segment, styles.cards)}>
|
||||||
|
{cardIds.map((cardId, cardIndex) => (
|
||||||
|
<div key={cardId} className={styles.card}>
|
||||||
|
<Card isInline id={cardId} index={cardIndex} />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DragDropContext onDragStart={handleDragStart} onDragEnd={handleDragEnd}>
|
||||||
|
<Droppable droppableId="list-view" type={DroppableTypes.CARD}>
|
||||||
|
{({ innerRef, droppableProps, placeholder }) => (
|
||||||
|
<div
|
||||||
|
{...droppableProps} // eslint-disable-line react/jsx-props-no-spreading
|
||||||
|
ref={innerRef}
|
||||||
|
className={classNames(styles.segment, styles.cards)}
|
||||||
|
>
|
||||||
|
{cardIds.map((cardId, cardIndex) => (
|
||||||
|
<DraggableCard
|
||||||
|
key={cardId}
|
||||||
|
isInline
|
||||||
|
id={cardId}
|
||||||
|
index={cardIndex}
|
||||||
|
className={styles.card}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
{placeholder}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Droppable>
|
||||||
|
</DragDropContext>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.wrapper}>
|
<div className={styles.wrapper}>
|
||||||
{canAddCard &&
|
{canAddCard &&
|
||||||
@@ -88,15 +193,7 @@ const ListView = React.memo(
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
{cardIds.length > 0 && (
|
{renderCardsContent()}
|
||||||
<div className={classNames(styles.segment, styles.cards)}>
|
|
||||||
{cardIds.map((cardId, cardIndex) => (
|
|
||||||
<div key={cardId} className={styles.card}>
|
|
||||||
<Card isInline id={cardId} index={cardIndex} />
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{isCardsFetching !== undefined && isAllCardsFetched !== undefined && (
|
{isCardsFetching !== undefined && isAllCardsFetched !== undefined && (
|
||||||
<div className={styles.loaderWrapper}>
|
<div className={styles.loaderWrapper}>
|
||||||
{isCardsFetching ? (
|
{isCardsFetching ? (
|
||||||
@@ -115,6 +212,7 @@ ListView.propTypes = {
|
|||||||
cardIds: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types
|
cardIds: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types
|
||||||
isCardsFetching: PropTypes.bool,
|
isCardsFetching: PropTypes.bool,
|
||||||
isAllCardsFetched: PropTypes.bool,
|
isAllCardsFetched: PropTypes.bool,
|
||||||
|
isReorderingEnabled: PropTypes.bool,
|
||||||
onCardsFetch: PropTypes.func,
|
onCardsFetch: PropTypes.func,
|
||||||
onCardCreate: PropTypes.func,
|
onCardCreate: PropTypes.func,
|
||||||
onCardPaste: PropTypes.func,
|
onCardPaste: PropTypes.func,
|
||||||
@@ -123,6 +221,7 @@ ListView.propTypes = {
|
|||||||
ListView.defaultProps = {
|
ListView.defaultProps = {
|
||||||
isCardsFetching: undefined,
|
isCardsFetching: undefined,
|
||||||
isAllCardsFetched: undefined,
|
isAllCardsFetched: undefined,
|
||||||
|
isReorderingEnabled: false,
|
||||||
onCardsFetch: undefined,
|
onCardsFetch: undefined,
|
||||||
onCardCreate: undefined,
|
onCardCreate: undefined,
|
||||||
onCardPaste: undefined,
|
onCardPaste: undefined,
|
||||||
|
|||||||
Reference in New Issue
Block a user