1
0
Fork 0
mirror of https://github.com/plankanban/planka.git synced 2025-07-22 06:39:44 +02:00
planka/client/src/components/Board/Board.jsx

188 lines
5.9 KiB
React
Raw Normal View History

import React, { useCallback, useEffect, useRef, useState } from 'react';
2019-08-31 04:07:25 +05:00
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import { DragDropContext, Droppable } from 'react-beautiful-dnd';
import { closePopup } from '../../lib/popup';
import DroppableTypes from '../../constants/DroppableTypes';
import ListContainer from '../../containers/ListContainer';
import CardModalContainer from '../../containers/CardModalContainer';
import ListAdd from './ListAdd';
2019-08-31 04:07:25 +05:00
import { ReactComponent as PlusMathIcon } from '../../assets/images/plus-math-icon.svg';
2022-12-26 21:10:50 +01:00
import styles from './Board.module.scss';
import globalStyles from '../../styles.module.scss';
2019-08-31 04:07:25 +05:00
2020-03-25 00:15:47 +05:00
const parseDndId = (dndId) => dndId.split(':')[1];
2019-08-31 04:07:25 +05:00
2022-12-26 21:10:50 +01:00
const Board = React.memo(
({ listIds, isCardModalOpened, canEdit, onListCreate, onListMove, onCardMove }) => {
2019-08-31 04:07:25 +05:00
const [t] = useTranslation();
const [isListAddOpened, setIsListAddOpened] = useState(false);
const wrapper = useRef(null);
const prevPosition = useRef(null);
const handleAddListClick = useCallback(() => {
setIsListAddOpened(true);
}, []);
const handleAddListClose = useCallback(() => {
setIsListAddOpened(false);
}, []);
2019-08-31 04:07:25 +05:00
const handleDragStart = useCallback(() => {
document.body.classList.add(globalStyles.dragging);
2019-08-31 04:07:25 +05:00
closePopup();
}, []);
const handleDragEnd = useCallback(
({ draggableId, type, source, destination }) => {
document.body.classList.remove(globalStyles.dragging);
2019-08-31 04:07:25 +05:00
if (
!destination ||
(source.droppableId === destination.droppableId && source.index === destination.index)
2019-08-31 04:07:25 +05:00
) {
return;
}
const id = parseDndId(draggableId);
2019-08-31 04:07:25 +05:00
switch (type) {
case DroppableTypes.LIST:
onListMove(id, destination.index);
break;
case DroppableTypes.CARD:
onCardMove(id, parseDndId(destination.droppableId), destination.index);
2019-08-31 04:07:25 +05:00
break;
default:
}
},
[onListMove, onCardMove],
);
const handleMouseDown = useCallback(
(event) => {
// If button is defined and not equal to 0 (left click)
if (event.button) {
return;
}
if (event.target !== wrapper.current && !event.target.dataset.dragScroller) {
return;
}
prevPosition.current = event.clientX;
},
[wrapper],
);
const handleWindowMouseMove = useCallback(
(event) => {
if (!prevPosition.current) {
return;
}
event.preventDefault();
window.scrollBy({
left: prevPosition.current - event.clientX,
});
prevPosition.current = event.clientX;
},
[prevPosition],
);
const handleWindowMouseUp = useCallback(() => {
prevPosition.current = null;
}, [prevPosition]);
2020-05-26 00:46:04 +05:00
useEffect(() => {
document.body.style.overflowX = 'auto';
return () => {
document.body.style.overflowX = null;
};
}, []);
useEffect(() => {
if (isListAddOpened) {
window.scroll(document.body.scrollWidth, 0);
}
}, [listIds, isListAddOpened]);
useEffect(() => {
window.addEventListener('mouseup', handleWindowMouseUp);
window.addEventListener('mousemove', handleWindowMouseMove);
return () => {
window.removeEventListener('mouseup', handleWindowMouseUp);
window.removeEventListener('mousemove', handleWindowMouseMove);
};
}, [handleWindowMouseUp, handleWindowMouseMove]);
2019-08-31 04:07:25 +05:00
return (
<>
{/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}
<div ref={wrapper} className={styles.wrapper} onMouseDown={handleMouseDown}>
<div>
<DragDropContext onDragStart={handleDragStart} onDragEnd={handleDragEnd}>
<Droppable droppableId="board" type={DroppableTypes.LIST} direction="horizontal">
{({ innerRef, droppableProps, placeholder }) => (
<div
{...droppableProps} // eslint-disable-line react/jsx-props-no-spreading
data-drag-scroller
ref={innerRef}
className={styles.lists}
>
{listIds.map((listId, index) => (
<ListContainer key={listId} id={listId} index={index} />
))}
{placeholder}
{canEdit && (
<div data-drag-scroller className={styles.list}>
{isListAddOpened ? (
<ListAdd onCreate={onListCreate} onClose={handleAddListClose} />
) : (
<button
type="button"
className={styles.addListButton}
onClick={handleAddListClick}
>
<PlusMathIcon className={styles.addListButtonIcon} />
<span className={styles.addListButtonText}>
{listIds.length > 0
? t('action.addAnotherList')
: t('action.addList')}
</span>
</button>
)}
</div>
)}
2019-08-31 04:07:25 +05:00
</div>
)}
</Droppable>
</DragDropContext>
</div>
2019-08-31 04:07:25 +05:00
</div>
{isCardModalOpened && <CardModalContainer />}
</>
);
},
);
2022-12-26 21:10:50 +01:00
Board.propTypes = {
listIds: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types
2019-08-31 04:07:25 +05:00
isCardModalOpened: PropTypes.bool.isRequired,
canEdit: PropTypes.bool.isRequired,
2019-08-31 04:07:25 +05:00
onListCreate: PropTypes.func.isRequired,
onListMove: PropTypes.func.isRequired,
onCardMove: PropTypes.func.isRequired,
};
2022-12-26 21:10:50 +01:00
export default Board;