277 lines
8.4 KiB
JavaScript
277 lines
8.4 KiB
JavaScript
import React, { useCallback, useEffect, useState, useMemo } from "react";
|
|
import { useDispatch, useSelector } from "react-redux";
|
|
import { DragDropContext } from "../dnd/lib";
|
|
import PropTypes from "prop-types";
|
|
import pick from "lodash/pick";
|
|
import isEqual from "lodash/isEqual";
|
|
import Lane from "./Lane";
|
|
import { PopoverWrapper } from "react-popopo";
|
|
import * as actions from "../../../redux/trello/trello.actions.js";
|
|
|
|
/**
|
|
* BoardContainer is a React component that represents a Trello-like board.
|
|
* It uses Redux for state management and provides a variety of props to customize its behavior.
|
|
*
|
|
* @component
|
|
* @param {Object} props - Component props
|
|
* @param {string} props.id - The unique identifier for the board
|
|
* @param {Object} props.components - Custom components to use in the board
|
|
* @param {Object} props.data - The initial data for the board
|
|
* @param {boolean} props.draggable - Whether the board is draggable
|
|
* @param {Object} props.style - The CSS styles to apply to the board
|
|
* @param {Function} props.onDataChange - Callback function when the board data changes
|
|
* @param {Function} props.onDragEnd - Callback function when a drag ends
|
|
* @param {boolean} props.editable - Whether the board is editable
|
|
* @param {boolean} props.canAddLanes - Whether lanes can be added to the board
|
|
* @param {Object} props.laneStyle - The CSS styles to apply to the lanes
|
|
* @param {string} props.orientation - The orientation of the board ("horizontal" or "vertical")
|
|
* @param {Function} props.eventBusHandle - Function to handle events from the event bus
|
|
* @param {Object} props.reducerData - The initial data for the Redux reducer
|
|
* @param {Object} props.cardStyle - The CSS styles to apply to the cards
|
|
* @param {Object} props.otherProps - Any other props to pass to the board
|
|
* @returns {JSX.Element} A Trello-like board
|
|
*/
|
|
const BoardContainer = ({
|
|
id,
|
|
components,
|
|
data,
|
|
draggable = false,
|
|
style,
|
|
onDataChange = () => {},
|
|
onDragEnd = () => {},
|
|
editable = false,
|
|
canAddLanes = false,
|
|
laneStyle,
|
|
orientation = "horizontal",
|
|
eventBusHandle,
|
|
reducerData,
|
|
cardStyle,
|
|
...otherProps
|
|
}) => {
|
|
const [isDragging, setIsDragging] = useState(false);
|
|
const [isProcessing, setIsProcessing] = useState(false);
|
|
|
|
const dispatch = useDispatch();
|
|
const currentReducerData = useSelector((state) => (state.trello.lanes ? state.trello : {}));
|
|
|
|
const groupName = `TrelloBoard${id}`;
|
|
|
|
const wireEventBus = useCallback(() => {
|
|
const eventBus = {
|
|
publish: (event) => {
|
|
switch (event.type) {
|
|
case "ADD_CARD":
|
|
return dispatch(actions.addCard({ laneId: event.laneId, card: event.card }));
|
|
case "REMOVE_CARD":
|
|
return dispatch(actions.removeCard({ laneId: event.laneId, cardId: event.cardId }));
|
|
case "REFRESH_BOARD":
|
|
return dispatch(actions.loadBoard(event.data));
|
|
case "MOVE_CARD":
|
|
return dispatch(
|
|
actions.moveCardAcrossLanes({
|
|
fromLaneId: event.fromLaneId,
|
|
toLaneId: event.toLaneId,
|
|
cardId: event.cardId,
|
|
index: event.index,
|
|
event
|
|
})
|
|
);
|
|
case "UPDATE_CARDS":
|
|
return dispatch(actions.updateCards({ laneId: event.laneId, cards: event.cards }));
|
|
case "UPDATE_CARD":
|
|
return dispatch(actions.updateCard({ laneId: event.laneId, updatedCard: event.card }));
|
|
case "UPDATE_LANES":
|
|
return dispatch(actions.updateLanes(event.lanes));
|
|
case "UPDATE_LANE":
|
|
return dispatch(actions.updateLane(event.lane));
|
|
default:
|
|
return;
|
|
}
|
|
}
|
|
};
|
|
eventBusHandle(eventBus);
|
|
}, [dispatch, eventBusHandle]);
|
|
|
|
useEffect(() => {
|
|
dispatch(actions.loadBoard(data));
|
|
if (eventBusHandle) {
|
|
wireEventBus();
|
|
}
|
|
}, [data, eventBusHandle, dispatch, wireEventBus]);
|
|
|
|
useEffect(() => {
|
|
if (!isEqual(currentReducerData, reducerData)) {
|
|
onDataChange(currentReducerData);
|
|
}
|
|
}, [currentReducerData, reducerData, onDataChange]);
|
|
|
|
const onDragStart = useCallback(() => {
|
|
setIsDragging(true);
|
|
}, [setIsDragging]);
|
|
|
|
const getCardDetails = useCallback(
|
|
(laneId, cardIndex) => {
|
|
return currentReducerData.lanes.find((lane) => lane.id === laneId).cards[cardIndex];
|
|
},
|
|
[currentReducerData]
|
|
);
|
|
|
|
const passThroughProps = useMemo(
|
|
() =>
|
|
pick(
|
|
{
|
|
id,
|
|
components,
|
|
data,
|
|
draggable,
|
|
style,
|
|
onDataChange,
|
|
editable,
|
|
canAddLanes,
|
|
laneStyle,
|
|
orientation,
|
|
eventBusHandle,
|
|
reducerData,
|
|
cardStyle,
|
|
...otherProps
|
|
},
|
|
[
|
|
"onLaneScroll",
|
|
"onLaneDelete",
|
|
"onLaneUpdate",
|
|
"onCardClick",
|
|
"onBeforeCardDelete",
|
|
"onCardDelete",
|
|
"onCardAdd",
|
|
"onCardUpdate",
|
|
"onLaneClick",
|
|
"laneSortFunction",
|
|
"draggable",
|
|
"cardDraggable",
|
|
"collapsibleLanes",
|
|
"canAddLanes",
|
|
"hideCardDeleteIcon",
|
|
"tagStyle",
|
|
"handleDragStart",
|
|
"handleDragEnd",
|
|
"cardDragClass",
|
|
"editLaneTitle",
|
|
"orientation"
|
|
]
|
|
),
|
|
[
|
|
id,
|
|
components,
|
|
data,
|
|
draggable,
|
|
style,
|
|
onDataChange,
|
|
|
|
editable,
|
|
canAddLanes,
|
|
laneStyle,
|
|
orientation,
|
|
eventBusHandle,
|
|
reducerData,
|
|
cardStyle,
|
|
otherProps
|
|
]
|
|
);
|
|
|
|
const onLaneDrag = useCallback(
|
|
async ({ draggableId, type, source, reason, mode, destination, combine }) => {
|
|
setIsDragging(false);
|
|
|
|
if (!type || type !== "lane" || !source || !destination) return;
|
|
|
|
setIsProcessing(true);
|
|
|
|
dispatch(
|
|
actions.moveCardAcrossLanes({
|
|
fromLaneId: source.droppableId,
|
|
toLaneId: destination.droppableId,
|
|
cardId: draggableId,
|
|
index: destination.index
|
|
})
|
|
);
|
|
|
|
try {
|
|
await onDragEnd({ draggableId, type, source, reason, mode, destination, combine });
|
|
} catch (err) {
|
|
console.error("Error in onLaneDrag", err);
|
|
} finally {
|
|
setIsProcessing(false);
|
|
}
|
|
},
|
|
[dispatch, onDragEnd]
|
|
);
|
|
|
|
return (
|
|
<components.BoardWrapper style={style} orientation={orientation}>
|
|
<PopoverWrapper>
|
|
<DragDropContext onDragEnd={onLaneDrag} onDragStart={onDragStart} contextId={groupName}>
|
|
{currentReducerData.lanes.map((lane, index) => {
|
|
const { id, droppable, ...laneOtherProps } = lane;
|
|
return (
|
|
<Lane
|
|
key={id}
|
|
boardId={groupName}
|
|
components={components}
|
|
id={id}
|
|
getCardDetails={getCardDetails}
|
|
index={index}
|
|
droppable={droppable === undefined ? true : droppable}
|
|
style={laneStyle || lane.style || {}}
|
|
labelStyle={lane.labelStyle || {}}
|
|
cardStyle={cardStyle || lane.cardStyle}
|
|
editable={editable && !lane.disallowAddingCard}
|
|
{...laneOtherProps}
|
|
{...passThroughProps}
|
|
cards={lane.cards}
|
|
isDragging={isDragging}
|
|
isProcessing={isProcessing}
|
|
/>
|
|
);
|
|
})}
|
|
</DragDropContext>
|
|
</PopoverWrapper>
|
|
</components.BoardWrapper>
|
|
);
|
|
};
|
|
|
|
BoardContainer.propTypes = {
|
|
id: PropTypes.string,
|
|
components: PropTypes.object,
|
|
actions: PropTypes.object,
|
|
data: PropTypes.object.isRequired,
|
|
reducerData: PropTypes.object,
|
|
onDataChange: PropTypes.func,
|
|
eventBusHandle: PropTypes.func,
|
|
onLaneScroll: PropTypes.func,
|
|
onCardClick: PropTypes.func,
|
|
onBeforeCardDelete: PropTypes.func,
|
|
onCardDelete: PropTypes.func,
|
|
onCardAdd: PropTypes.func,
|
|
onCardUpdate: PropTypes.func,
|
|
onLaneAdd: PropTypes.func,
|
|
onLaneDelete: PropTypes.func,
|
|
onLaneClick: PropTypes.func,
|
|
onLaneUpdate: PropTypes.func,
|
|
laneSortFunction: PropTypes.func,
|
|
draggable: PropTypes.bool,
|
|
collapsibleLanes: PropTypes.bool,
|
|
editable: PropTypes.bool,
|
|
canAddLanes: PropTypes.bool,
|
|
hideCardDeleteIcon: PropTypes.bool,
|
|
handleDragStart: PropTypes.func,
|
|
handleDragEnd: PropTypes.func,
|
|
style: PropTypes.object,
|
|
tagStyle: PropTypes.object,
|
|
cardDraggable: PropTypes.bool,
|
|
cardDragClass: PropTypes.string,
|
|
orientation: PropTypes.string,
|
|
cardStyle: PropTypes.object
|
|
};
|
|
|
|
export default BoardContainer;
|