From 55d729339f043852d7dfcaefe7d1aa10b5ce9053 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Fri, 17 May 2024 16:29:46 -0400 Subject: [PATCH] Progress Commit Signed-off-by: Dave Richer --- .../production-board-kanban.component.jsx | 9 +- .../trello-board/controllers/Board.jsx | 2 +- .../controllers/BoardContainer.jsx | 383 +++++++++--------- .../trello-board/controllers/Lane.jsx | 355 ++++++++-------- .../components/trello-board/dnd/Container.jsx | 63 +-- .../components/trello-board/dnd/Draggable.jsx | 41 +- .../trello-board/helpers/LaneHelper.js | 83 ++-- .../components/trello-board/styles/Base.js | 8 +- .../trello-board/widgets/DeleteButton.jsx | 7 +- 9 files changed, 440 insertions(+), 511 deletions(-) diff --git a/client/src/components/production-board-kanban/production-board-kanban.component.jsx b/client/src/components/production-board-kanban/production-board-kanban.component.jsx index 3d43c2286..495ef92c8 100644 --- a/client/src/components/production-board-kanban/production-board-kanban.component.jsx +++ b/client/src/components/production-board-kanban/production-board-kanban.component.jsx @@ -55,7 +55,6 @@ export function ProductionBoardKanbanComponent({ const [isMoving, setIsMoving] = useState(false); const [orientation, setOrientation] = useState("horizontal"); - const { t } = useTranslation(); useEffect(() => { @@ -223,7 +222,7 @@ export function ProductionBoardKanbanComponent({ const components = { Card: (cardProps) => ProductionBoardCard({ card: cardProps, technician, bodyshop, cardSettings }), - LaneHeader: cardSettings.stickyheader ? StickyHeader : NormalHeader + LaneHeader: cardSettings.stickyheader && orientation === "horizontal" ? StickyHeader : NormalHeader }; return ( @@ -255,9 +254,8 @@ export function ProductionBoardKanbanComponent({ diff --git a/client/src/components/trello-board/controllers/BoardContainer.jsx b/client/src/components/trello-board/controllers/BoardContainer.jsx index f247ec055..daa30cf7d 100644 --- a/client/src/components/trello-board/controllers/BoardContainer.jsx +++ b/client/src/components/trello-board/controllers/BoardContainer.jsx @@ -1,6 +1,5 @@ -import React, { Component } from "react"; -import { bindActionCreators } from "redux"; -import { connect } from "react-redux"; +import React, { useState, useEffect, useCallback } from "react"; +import { useDispatch, useSelector } from "react-redux"; import Container from "../dnd/Container"; import Draggable from "../dnd/Draggable"; import PropTypes from "prop-types"; @@ -8,220 +7,214 @@ 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"; -class BoardContainer extends Component { - state = { - addLaneMode: false - }; +const BoardContainer = (props) => { + const [addLaneMode, setAddLaneMode] = useState(false); - get groupName() { - const { id } = this.props; - return `TrelloBoard${id}`; - } + const { + id, + components, + data, + draggable, + laneDraggable, + laneDragClass, + laneDropClass, + style, + onDataChange, + onCardAdd, + onCardUpdate, + onCardClick, + onBeforeCardDelete, + onCardDelete, + onLaneScroll, + onLaneClick, + onLaneAdd, + onLaneDelete, + onLaneUpdate, + editable, + canAddLanes, + laneStyle, + onCardMoveAcrossLanes, + orientation, + eventBusHandle, + handleLaneDragStart, + handleLaneDragEnd, + ...otherProps + } = props; - componentDidMount() { - const { actions, eventBusHandle } = this.props; - actions.loadBoard(this.props.data); - if (eventBusHandle) { - this.wireEventBus(); - } - } + const dispatch = useDispatch(); + const reducerData = useSelector((state) => (state.trello.lanes ? state.trello : {})); - componentDidUpdate(prevProps) { - const { data, reducerData, onDataChange, actions } = this.props; + const groupName = `TrelloBoard${id}`; - if (this.props.reducerData && !isEqual(reducerData, prevProps.reducerData)) { - onDataChange(this.props.reducerData); - } - - if (data && !isEqual(data, prevProps.data)) { - actions.loadBoard(data); - onDataChange(data); - } - } - - onDragStart = ({ payload }) => { - const { handleLaneDragStart } = this.props; - handleLaneDragStart(payload.id); - }; - - onLaneDrop = ({ removedIndex, addedIndex, payload }) => { - const { actions, handleLaneDragEnd } = this.props; - if (removedIndex !== addedIndex) { - actions.moveLane({ oldIndex: removedIndex, newIndex: addedIndex }); - handleLaneDragEnd(removedIndex, addedIndex, payload); - } - }; - - getCardDetails = (laneId, cardIndex) => { - return this.props.reducerData.lanes.find((lane) => lane.id === laneId).cards[cardIndex]; - }; - - getLaneDetails = (index) => { - return this.props.reducerData.lanes[index]; - }; - - wireEventBus = () => { - const { actions, eventBusHandle } = this.props; - let eventBus = { + const wireEventBus = useCallback(() => { + const eventBus = { publish: (event) => { switch (event.type) { case "ADD_CARD": - return actions.addCard({ laneId: event.laneId, card: event.card }); - // Note: Removed because there was a duplicate entry - // case "UPDATE_CARD": - // return actions.updateCard({ laneId: event.laneId, card: event.card }); + return dispatch(actions.addCard({ laneId: event.laneId, card: event.card })); case "REMOVE_CARD": - return actions.removeCard({ laneId: event.laneId, cardId: event.cardId }); + return dispatch(actions.removeCard({ laneId: event.laneId, cardId: event.cardId })); case "REFRESH_BOARD": - return actions.loadBoard(event.data); + return dispatch(actions.loadBoard(event.data)); case "MOVE_CARD": - return actions.moveCardAcrossLanes({ - fromLaneId: event.fromLaneId, - toLaneId: event.toLaneId, - cardId: event.cardId, - index: event.index - }); + return dispatch( + actions.moveCardAcrossLanes({ + fromLaneId: event.fromLaneId, + toLaneId: event.toLaneId, + cardId: event.cardId, + index: event.index + }) + ); case "UPDATE_CARDS": - return actions.updateCards({ laneId: event.laneId, cards: event.cards }); + return dispatch(actions.updateCards({ laneId: event.laneId, cards: event.cards })); case "UPDATE_CARD": - return actions.updateCard({ laneId: event.laneId, updatedCard: event.card }); + return dispatch(actions.updateCard({ laneId: event.laneId, updatedCard: event.card })); case "UPDATE_LANES": - return actions.updateLanes(event.lanes); + return dispatch(actions.updateLanes(event.lanes)); case "UPDATE_LANE": - return actions.updateLane(event.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(reducerData, props.reducerData)) { + onDataChange(reducerData); + } + }, [reducerData, props.reducerData, onDataChange]); + + const onDragStart = useCallback( + ({ payload }) => { + handleLaneDragStart(payload.id); + }, + [handleLaneDragStart] + ); + + const onLaneDrop = useCallback( + ({ removedIndex, addedIndex, payload }) => { + if (removedIndex !== addedIndex) { + dispatch(actions.moveLane({ oldIndex: removedIndex, newIndex: addedIndex })); + handleLaneDragEnd(removedIndex, addedIndex, payload); + } + }, + [dispatch, handleLaneDragEnd] + ); + + const getCardDetails = useCallback( + (laneId, cardIndex) => { + return reducerData.lanes.find((lane) => lane.id === laneId).cards[cardIndex]; + }, + [reducerData] + ); + + const getLaneDetails = useCallback( + (index) => { + return reducerData.lanes[index]; + }, + [reducerData] + ); + + const hideEditableLane = () => { + setAddLaneMode(false); }; - // + add - hideEditableLane = () => { - this.setState({ addLaneMode: false }); + const showEditableLane = () => { + setAddLaneMode(true); }; - showEditableLane = () => { - this.setState({ addLaneMode: true }); + const addNewLane = (params) => { + hideEditableLane(); + dispatch(actions.addLane(params)); + onLaneAdd(params); }; - addNewLane = (params) => { - this.hideEditableLane(); - this.props.actions.addLane(params); - this.props.onLaneAdd(params); - }; + const passThroughProps = pick(props, [ + "onCardMoveAcrossLanes", + "onLaneScroll", + "onLaneDelete", + "onLaneUpdate", + "onCardClick", + "onBeforeCardDelete", + "onCardDelete", + "onCardAdd", + "onCardUpdate", + "onLaneClick", + "laneSortFunction", + "draggable", + "laneDraggable", + "cardDraggable", + "collapsibleLanes", + "canAddLanes", + "hideCardDeleteIcon", + "tagStyle", + "handleDragStart", + "handleDragEnd", + "cardDragClass", + "editLaneTitle", + "orientation" + ]); - render() { - const { - id, - components, - reducerData, - draggable, - laneDraggable, - laneDragClass, - laneDropClass, - style, - onDataChange, - onCardAdd, - onCardUpdate, - onCardClick, - onBeforeCardDelete, - onCardDelete, - onLaneScroll, - onLaneClick, - onLaneAdd, - onLaneDelete, - onLaneUpdate, - editable, - canAddLanes, - laneStyle, - onCardMoveAcrossLanes, - t, - orientation, - ...otherProps - } = this.props; - - const { addLaneMode } = this.state; - // Stick to whitelisting attributes to segregate board and lane props - const passThroughProps = pick(this.props, [ - "onCardMoveAcrossLanes", - "onLaneScroll", - "onLaneDelete", - "onLaneUpdate", - "onCardClick", - "onBeforeCardDelete", - "onCardDelete", - "onCardAdd", - "onCardUpdate", - "onLaneClick", - "laneSortFunction", - "draggable", - "laneDraggable", - "cardDraggable", - "collapsibleLanes", - "canAddLanes", - "hideCardDeleteIcon", - "tagStyle", - "handleDragStart", - "handleDragEnd", - "cardDragClass", - "editLaneTitle", - "orientation" - ]); - - return ( - - - this.getLaneDetails(index)} - groupName={this.groupName} - > - {reducerData.lanes.map((lane, index) => { - const { id, droppable, ...otherProps } = lane; - const laneToRender = ( - - ); - return draggable && laneDraggable ? {laneToRender} : laneToRender; - })} - - - {canAddLanes && ( - - {editable && !addLaneMode ? ( - - ) : ( - addLaneMode && - )} - - )} - - ); - } -} + return ( + + + getLaneDetails(index)} + groupName={groupName} + > + {reducerData.lanes.map((lane, index) => { + const { id, droppable, ...otherProps } = lane; + const laneToRender = ( + + ); + return draggable && laneDraggable ? {laneToRender} : laneToRender; + })} + + + {canAddLanes && ( + + {editable && !addLaneMode ? ( + + ) : ( + addLaneMode && + )} + + )} + + ); +}; BoardContainer.propTypes = { id: PropTypes.string, @@ -259,12 +252,10 @@ BoardContainer.propTypes = { laneDragClass: PropTypes.string, laneDropClass: PropTypes.string, onCardMoveAcrossLanes: PropTypes.func.isRequired, - t: PropTypes.func, orientation: PropTypes.string }; BoardContainer.defaultProps = { - t: (v) => v, onDataChange: () => {}, handleDragStart: () => {}, handleDragEnd: () => {}, @@ -284,16 +275,8 @@ BoardContainer.defaultProps = { cardDraggable: true, cardDragClass: "react_trello_dragClass", laneDragClass: "react_trello_dragLaneClass", - laneDropClass: "", + laneDropClass: "react_trello_dragLaneDropClass", orientation: "horizontal" }; -const mapStateToProps = (state) => { - return state.trello.lanes ? { reducerData: state.trello } : {}; -}; - -const mapDispatchToProps = (dispatch) => ({ - actions: bindActionCreators({ ...actions }, dispatch) -}); - -export default connect(mapStateToProps, mapDispatchToProps)(BoardContainer); +export default BoardContainer; diff --git a/client/src/components/trello-board/controllers/Lane.jsx b/client/src/components/trello-board/controllers/Lane.jsx index 2dbb118d9..af821fe42 100644 --- a/client/src/components/trello-board/controllers/Lane.jsx +++ b/client/src/components/trello-board/controllers/Lane.jsx @@ -1,4 +1,4 @@ -import React, { Component } from "react"; +import React, { useState, useEffect, useRef, useCallback, useMemo } from "react"; import classNames from "classnames"; import PropTypes from "prop-types"; import { bindActionCreators } from "redux"; @@ -12,140 +12,183 @@ import Draggable from "../dnd/Draggable.jsx"; import * as actions from "../../../redux/trello/trello.actions.js"; -class Lane extends Component { - state = { - loading: false, - currentPage: this.props.currentPage, - addCardMode: false, - collapsed: false, - isDraggingOver: false - }; +const defaultProps = { + style: {}, + titleStyle: {}, + labelStyle: {}, + label: undefined, + editable: false, + onLaneUpdate: () => {}, + onCardAdd: () => {}, + onCardUpdate: () => {}, + onCardDelete: () => {}, + onBeforeCardDelete: () => {}, + onLaneDelete: () => {}, + onLaneClick: () => {}, + onLaneScroll: () => {}, + onCardClick: () => {}, + onCardMoveAcrossLanes: () => {}, + draggable: false, + laneDraggable: false, + cardDraggable: true, + collapsibleLanes: false, + droppable: true, + canAddLanes: false, + hideCardDeleteIcon: false, + components: {}, + handleDragStart: () => {}, + handleDragEnd: () => {}, + orientation: "vertical" +}; - get groupName() { - const { boardId } = this.props; - return `TrelloBoard${boardId}Lane`; - } +const Lane = (internalProps) => { + const props = useMemo(() => ({ ...defaultProps, ...internalProps }), [internalProps]); + const [loading, setLoading] = useState(false); + const [currentPage, setCurrentPage] = useState(props.currentPage); + const [addCardMode, setAddCardMode] = useState(false); + const [collapsed, setCollapsed] = useState(false); + const [isDraggingOver, setIsDraggingOver] = useState(false); - handleScroll = (evt) => { - const node = evt.target; - const elemScrollPosition = node.scrollHeight - node.scrollTop - node.clientHeight; - const { onLaneScroll } = this.props; - // In some browsers and/or screen sizes a decimal rest value between 0 and 1 exists, so it should be checked on < 1 instead of < 0 - if (elemScrollPosition < 1 && onLaneScroll && !this.state.loading) { - const { currentPage } = this.state; - this.setState({ loading: true }); - const nextPage = currentPage + 1; - onLaneScroll(nextPage, this.props.id).then((moreCards) => { - if ((moreCards || []).length > 0) { - this.props.actions.paginateLane({ - laneId: this.props.id, - newCards: moreCards, - nextPage: nextPage - }); - } - this.setState({ loading: false }); - }); + const laneRef = useRef(null); + + useEffect(() => { + if (!isEqual(props.cards, currentPage)) { + setCurrentPage(props.currentPage); } - }; + }, [props.cards, props.currentPage, currentPage]); - sortCards(cards, sortFunction) { + const handleScroll = useCallback( + (evt) => { + const node = evt.target; + const elemScrollPosition = node.scrollHeight - node.scrollTop - node.clientHeight; + const { onLaneScroll } = props; + if (elemScrollPosition < 1 && onLaneScroll && !loading) { + const nextPage = currentPage + 1; + setLoading(true); + onLaneScroll(nextPage, props.id).then((moreCards) => { + if ((moreCards || []).length > 0) { + props.actions.paginateLane({ + laneId: props.id, + newCards: moreCards, + nextPage: nextPage + }); + } + setLoading(false); + }); + } + }, + [currentPage, loading, props] + ); + + useEffect(() => { + const node = laneRef.current; + if (node) { + node.addEventListener("scroll", handleScroll); + } + return () => { + if (node) { + node.removeEventListener("scroll", handleScroll); + } + }; + }, [handleScroll]); + + const sortCards = (cards, sortFunction) => { if (!cards) return []; if (!sortFunction) return cards; - return cards.concat().sort(function (card1, card2) { - return sortFunction(card1, card2); - }); - } - - laneDidMount = (node) => { - if (node) { - node.addEventListener("scroll", this.handleScroll); - } + return cards.concat().sort((card1, card2) => sortFunction(card1, card2)); }; - static getDerivedStateFromProps(nextProps, prevState) { - if (!isEqual(prevState.cards, nextProps.cards)) { - return { - currentPage: nextProps.currentPage - }; - } - // Return null if the state hasn't changed - return null; - } - - removeCard = (cardId) => { - if (this.props.onBeforeCardDelete && typeof this.props.onBeforeCardDelete === "function") { - this.props.onBeforeCardDelete(() => { - this.props.onCardDelete && this.props.onCardDelete(cardId, this.props.id); - this.props.actions.removeCard({ laneId: this.props.id, cardId: cardId }); + const removeCard = (cardId) => { + if (props.onBeforeCardDelete && typeof props.onBeforeCardDelete === "function") { + props.onBeforeCardDelete(() => { + props.onCardDelete && props.onCardDelete(cardId, props.id); + props.actions.removeCard({ laneId: props.id, cardId: cardId }); }); } else { - this.props.onCardDelete && this.props.onCardDelete(cardId, this.props.id); - this.props.actions.removeCard({ laneId: this.props.id, cardId: cardId }); + props.onCardDelete && props.onCardDelete(cardId, props.id); + props.actions.removeCard({ laneId: props.id, cardId: cardId }); } }; - handleCardClick = (e, card) => { - const { onCardClick } = this.props; + const handleCardClick = (e, card) => { + const { onCardClick } = props; onCardClick && onCardClick(card.id, card.metadata, card.laneId); e.stopPropagation(); }; - showEditableCard = () => { - this.setState({ addCardMode: true }); + const showEditableCard = () => { + setAddCardMode(true); }; - hideEditableCard = () => { - this.setState({ addCardMode: false }); + const hideEditableCard = () => { + setAddCardMode(false); }; - addNewCard = (params) => { - const laneId = this.props.id; + const addNewCard = (params) => { + const laneId = props.id; const id = v1(); - this.hideEditableCard(); + hideEditableCard(); let card = { id, ...params }; - this.props.actions.addCard({ laneId, card }); - this.props.onCardAdd(card, laneId); + props.actions.addCard({ laneId, card }); + props.onCardAdd(card, laneId); }; - onDragStart = ({ payload }) => { - const { handleDragStart } = this.props; + const onDragStart = ({ payload }) => { + const { handleDragStart } = props; handleDragStart && handleDragStart(payload.id, payload.laneId); }; - shouldAcceptDrop = (sourceContainerOptions) => { - return this.props.droppable && sourceContainerOptions.groupName === this.groupName; + const shouldAcceptDrop = (sourceContainerOptions) => { + return props.droppable && sourceContainerOptions.groupName === groupName; }; - onDragEnd = (laneId, result) => { - const { handleDragEnd } = this.props; + const onDragEnd = (laneId, result) => { + const { handleDragEnd } = props; const { addedIndex, payload } = result; - if (this.state.isDraggingOver) { - this.setState({ isDraggingOver: false }); + if (isDraggingOver) { + setIsDraggingOver(false); } if (addedIndex != null) { const newCard = { ...cloneDeep(payload), laneId }; const response = handleDragEnd ? handleDragEnd(payload.id, payload.laneId, laneId, addedIndex, newCard) : true; if (response === undefined || !!response) { - this.props.actions.moveCardAcrossLanes({ + props.actions.moveCardAcrossLanes({ fromLaneId: payload.laneId, toLaneId: laneId, cardId: payload.id, index: addedIndex }); - this.props.onCardMoveAcrossLanes(payload.laneId, laneId, payload.id, addedIndex); + props.onCardMoveAcrossLanes(payload.laneId, laneId, payload.id, addedIndex); } return response; } }; - updateCard = (updatedCard) => { - this.props.actions.updateCard({ laneId: this.props.id, card: updatedCard }); - this.props.onCardUpdate(this.props.id, updatedCard); + const updateCard = (updatedCard) => { + props.actions.updateCard({ laneId: props.id, card: updatedCard }); + props.onCardUpdate(props.id, updatedCard); }; - renderDragContainer = (isDraggingOver) => { + const removeLane = () => { + const { id } = props; + props.actions.removeLane({ laneId: id }); + props.onLaneDelete(id); + }; + + const updateTitle = (value) => { + props.actions.updateLane({ id: props.id, title: value }); + props.onLaneUpdate(props.id, { title: value }); + }; + + const toggleLaneCollapsed = () => { + props.collapsibleLanes && setCollapsed(!collapsed); + }; + + const groupName = `TrelloBoard${props.boardId}Lane`; + + const renderDragContainer = (isDraggingOver) => { const { id, cards, @@ -159,13 +202,12 @@ class Lane extends Component { cardStyle, components, orientation - } = this.props; - const { addCardMode, collapsed } = this.state; + } = props; const stableCards = collapsed ? [] : cards; - const cardList = this.sortCards(stableCards, laneSortFunction).map((card, idx) => { - const onDeleteCard = () => this.removeCard(card.id); + const cardList = sortCards(stableCards, laneSortFunction).map((card, idx) => { + const onDeleteCard = () => removeCard(card.id); const cardToRender = ( this.handleCardClick(e, card)} - onChange={(updatedCard) => this.updateCard(updatedCard)} + onClick={(e) => handleCardClick(e, card)} + onChange={(updatedCard) => updateCard(updatedCard)} showDeleteButton={!hideCardDeleteIcon} tagStyle={tagStyle} cardDraggable={cardDraggable} @@ -190,93 +232,59 @@ class Lane extends Component { }); return ( - + this.onDragEnd(id, e)} - onDragEnter={() => this.setState({ isDraggingOver: true })} - onDragLeave={() => this.setState({ isDraggingOver: false })} - shouldAcceptDrop={this.shouldAcceptDrop} - getChildPayload={(index) => this.props.getCardDetails(id, index)} + onDragStart={onDragStart} + onDrop={(e) => onDragEnd(id, e)} + onDragEnter={() => setIsDraggingOver(true)} + onDragLeave={() => setIsDraggingOver(false)} + shouldAcceptDrop={shouldAcceptDrop} + getChildPayload={(index) => props.getCardDetails(id, index)} > {cardList} - {editable && !addCardMode && } - {addCardMode && } + {editable && !addCardMode && } + {addCardMode && } ); }; - removeLane = () => { - const { id } = this.props; - this.props.actions.removeLane({ laneId: id }); - this.props.onLaneDelete(id); - }; - - updateTitle = (value) => { - this.props.actions.updateLane({ id: this.props.id, title: value }); - this.props.onLaneUpdate(this.props.id, { title: value }); - }; - - renderHeader = (pickedProps) => { - const { components } = this.props; + const renderHeader = (pickedProps) => { + const { components } = props; return ( ); }; - toggleLaneCollapsed = () => { - this.props.collapsibleLanes && this.setState((state) => ({ collapsed: !state.collapsed })); - }; + const { id, cards, collapsibleLanes, components, onLaneClick, orientation, ...otherProps } = props; + const allClassNames = classNames("react-trello-lane", collapsed ? "lane-collapsed" : "", props.className || ""); + const showFooter = collapsibleLanes && cards.length > 0; - render() { - const { loading, isDraggingOver, collapsed } = this.state; - const { - id, - cards, - collapsibleLanes, - components, - onLaneClick, - onLaneScroll, - onCardClick, - onCardAdd, - onBeforeCardDelete, - onCardDelete, - onLaneDelete, - onLaneUpdate, - onCardUpdate, - onCardMoveAcrossLanes, - orientation, - ...otherProps - } = this.props; - const allClassNames = classNames("react-trello-lane", this.props.className || ""); - const showFooter = collapsibleLanes && cards.length > 0; - return ( - onLaneClick && onLaneClick(id)} - draggable={false} - className={allClassNames} - orientation={orientation} - > - {this.renderHeader({ id, cards, ...otherProps })} - {this.renderDragContainer(isDraggingOver)} - {loading && } - {showFooter && } - - ); - } -} + // Removed the ...otherProps spread from the components.Section + return ( + onLaneClick && onLaneClick(id)} + draggable={false} + className={allClassNames} + orientation={orientation} + > + {renderHeader({ id, cards, ...otherProps })} + {renderDragContainer(isDraggingOver)} + {loading && } + {showFooter && } + + ); +}; Lane.propTypes = { actions: PropTypes.object, @@ -320,35 +328,6 @@ Lane.propTypes = { orientation: PropTypes.string }; -Lane.defaultProps = { - style: {}, - titleStyle: {}, - labelStyle: {}, - label: undefined, - editable: false, - onLaneUpdate: () => {}, - onCardAdd: () => {}, - onCardUpdate: () => {}, - onCardDelete: () => {}, - onBeforeCardDelete: () => {}, - onLaneDelete: () => {}, - onLaneClick: () => {}, - onLaneScroll: () => {}, - onCardClick: () => {}, - onCardMoveAcrossLanes: () => {}, - draggable: false, - laneDraggable: false, - cardDraggable: true, - collapsibleLanes: false, - droppable: true, - canAddLanes: false, - hideCardDeleteIcon: false, - components: {}, - handleDragStart: () => {}, - handleDragEnd: () => {}, - orientation: "vertical" -}; - const mapDispatchToProps = (dispatch) => ({ actions: bindActionCreators(actions, dispatch) }); diff --git a/client/src/components/trello-board/dnd/Container.jsx b/client/src/components/trello-board/dnd/Container.jsx index e62c0b1a0..405d96cf5 100644 --- a/client/src/components/trello-board/dnd/Container.jsx +++ b/client/src/components/trello-board/dnd/Container.jsx @@ -51,52 +51,27 @@ class Container extends Component { getContainerOptions() { const functionProps = {}; + const propKeys = [ + "onDragStart", + "onDragEnd", + "onDrop", + "getChildPayload", + "shouldAnimateDrop", + "shouldAcceptDrop", + "onDragEnter", + "onDragLeave", + "render", + "onDropReady", + "getGhostParent" + ]; - if (this.props.onDragStart) { - functionProps.onDragStart = (...p) => this.props.onDragStart(...p); - } + propKeys.forEach((key) => { + if (this.props[key]) { + functionProps[key] = (...p) => this.props[key](...p); + } + }); - if (this.props.onDragEnd) { - functionProps.onDragEnd = (...p) => this.props.onDragEnd(...p); - } - - if (this.props.onDrop) { - functionProps.onDrop = (...p) => this.props.onDrop(...p); - } - - if (this.props.getChildPayload) { - functionProps.getChildPayload = (...p) => this.props.getChildPayload(...p); - } - - if (this.props.shouldAnimateDrop) { - functionProps.shouldAnimateDrop = (...p) => this.props.shouldAnimateDrop(...p); - } - - if (this.props.shouldAcceptDrop) { - functionProps.shouldAcceptDrop = (...p) => this.props.shouldAcceptDrop(...p); - } - - if (this.props.onDragEnter) { - functionProps.onDragEnter = (...p) => this.props.onDragEnter(...p); - } - - if (this.props.onDragLeave) { - functionProps.onDragLeave = (...p) => this.props.onDragLeave(...p); - } - - if (this.props.render) { - functionProps.render = (...p) => this.props.render(...p); - } - - if (this.props.onDropReady) { - functionProps.onDropReady = (...p) => this.props.onDropReady(...p); - } - - if (this.props.getGhostParent) { - functionProps.getGhostParent = (...p) => this.props.getGhostParent(...p); - } - - return Object.assign({}, this.props, functionProps); + return { ...this.props, ...functionProps }; } } diff --git a/client/src/components/trello-board/dnd/Draggable.jsx b/client/src/components/trello-board/dnd/Draggable.jsx index 080422121..69e24c760 100644 --- a/client/src/components/trello-board/dnd/Draggable.jsx +++ b/client/src/components/trello-board/dnd/Draggable.jsx @@ -1,26 +1,35 @@ -import React, {Component} from 'react' -import PropTypes from 'prop-types' -import {constants} from 'kuika-smooth-dnd' +import React, { Component } from "react"; +import PropTypes from "prop-types"; +import { constants } from "kuika-smooth-dnd"; -const {wrapperClass} = constants +const { wrapperClass } = constants; class Draggable extends Component { render() { - if (this.props.render) { - return React.cloneElement(this.props.render(), {className: wrapperClass}) - } + const { render, className, children, ...restProps } = this.props; - const clsName = `${this.props.className ? this.props.className + ' ' : ''}` - return ( -
- {this.props.children} -
- ) + try { + if (render) { + return React.cloneElement(render(), { className: wrapperClass }); + } + + const clsName = className ? `${className} ` : ""; + return ( +
+ {children} +
+ ); + } catch (error) { + console.error("Error rendering Draggable component:", error); + return null; // Return null if an error occurs to prevent crashing + } } } Draggable.propTypes = { - render: PropTypes.func -} + render: PropTypes.func, + className: PropTypes.string, + children: PropTypes.node +}; -export default Draggable +export default Draggable; diff --git a/client/src/components/trello-board/helpers/LaneHelper.js b/client/src/components/trello-board/helpers/LaneHelper.js index b13a65483..dceebb381 100644 --- a/client/src/components/trello-board/helpers/LaneHelper.js +++ b/client/src/components/trello-board/helpers/LaneHelper.js @@ -1,5 +1,9 @@ import update from "immutability-helper"; +const updateLanes = (state, lanes) => update(state, { lanes: { $set: lanes } }); + +const updateLaneCards = (lane, cards) => update(lane, { cards: { $set: cards } }); + const LaneHelper = { initialiseLanes: (state, { lanes }) => { const newLanes = lanes.map((lane) => { @@ -7,13 +11,13 @@ const LaneHelper = { lane.cards && lane.cards.forEach((c) => (c.laneId = lane.id)); return lane; }); - return update(state, { lanes: { $set: newLanes } }); + return updateLanes(state, newLanes); }, paginateLane: (state, { laneId, newCards, nextPage }) => { const updatedLanes = LaneHelper.appendCardsToLane(state, { laneId: laneId, newCards: newCards }); updatedLanes.find((lane) => lane.id === laneId).currentPage = nextPage; - return update(state, { lanes: { $set: updatedLanes } }); + return updateLanes(state, updatedLanes); }, appendCardsToLane: (state, { laneId, newCards, index }) => { @@ -23,12 +27,11 @@ const LaneHelper = { .filter((c) => lane.cards.find((card) => card.id === c.id) == null); return state.lanes.map((lane) => { if (lane.id === laneId) { - if (index !== undefined) { - return update(lane, { cards: { $splice: [[index, 0, ...newCards]] } }); - } else { - const cardsToUpdate = [...lane.cards, ...newCards]; - return update(lane, { cards: { $set: cardsToUpdate } }); - } + const cardsToUpdate = + index !== undefined + ? [...lane.cards.slice(0, index), ...newCards, ...lane.cards.slice(index)] + : [...lane.cards, ...newCards]; + return updateLaneCards(lane, cardsToUpdate); } else { return lane; } @@ -37,35 +40,29 @@ const LaneHelper = { appendCardToLane: (state, { laneId, card, index }) => { const newLanes = LaneHelper.appendCardsToLane(state, { laneId: laneId, newCards: [card], index }); - return update(state, { lanes: { $set: newLanes } }); + return updateLanes(state, newLanes); }, addLane: (state, lane) => { const newLane = { cards: [], ...lane }; - return update(state, { lanes: { $push: [newLane] } }); + return updateLanes(state, [...state.lanes, newLane]); }, updateLane: (state, updatedLane) => { - const newLanes = state.lanes.map((lane) => { - if (updatedLane.id === lane.id) { - return { ...lane, ...updatedLane }; - } else { - return lane; - } - }); - return update(state, { lanes: { $set: newLanes } }); + const newLanes = state.lanes.map((lane) => (updatedLane.id === lane.id ? { ...lane, ...updatedLane } : lane)); + return updateLanes(state, newLanes); }, removeCardFromLane: (state, { laneId, cardId }) => { const lanes = state.lanes.map((lane) => { if (lane.id === laneId) { - let newCards = lane.cards.filter((card) => card.id !== cardId); - return update(lane, { cards: { $set: newCards } }); + const newCards = lane.cards.filter((card) => card.id !== cardId); + return updateLaneCards(lane, newCards); } else { return lane; } }); - return update(state, { lanes: { $set: lanes } }); + return updateLanes(state, lanes); }, moveCardAcrossLanes: (state, { fromLaneId, toLaneId, cardId, index }) => { @@ -74,50 +71,36 @@ const LaneHelper = { if (lane.id === fromLaneId) { cardToMove = lane.cards.find((card) => card.id === cardId); const newCards = lane.cards.filter((card) => card.id !== cardId); - return update(lane, { cards: { $set: newCards } }); + return updateLaneCards(lane, newCards); } else { return lane; } }); - const updatedState = update(state, { lanes: { $set: interimLanes } }); - return LaneHelper.appendCardToLane(updatedState, { - laneId: toLaneId, - card: cardToMove, - index: index - }); + return LaneHelper.appendCardToLane( + { ...state, lanes: interimLanes }, + { + laneId: toLaneId, + card: cardToMove, + index: index + } + ); }, updateCardsForLane: (state, { laneId, cards }) => { - const lanes = state.lanes.map((lane) => { - if (lane.id === laneId) { - return update(lane, { cards: { $set: cards } }); - } else { - return lane; - } - }); - return update(state, { lanes: { $set: lanes } }); + const lanes = state.lanes.map((lane) => (lane.id === laneId ? updateLaneCards(lane, cards) : lane)); + return updateLanes(state, lanes); }, updateCardForLane: (state, { laneId, card: updatedCard }) => { const lanes = state.lanes.map((lane) => { if (lane.id === laneId) { - const cards = lane.cards.map((card) => { - if (card.id === updatedCard.id) { - return { ...card, ...updatedCard }; - } else { - return card; - } - }); - return update(lane, { cards: { $set: cards } }); + const cards = lane.cards.map((card) => (card.id === updatedCard.id ? { ...card, ...updatedCard } : card)); + return updateLaneCards(lane, cards); } else { return lane; } }); - return update(state, { lanes: { $set: lanes } }); - }, - - updateLanes: (state, lanes) => { - return { ...state, ...{ lanes: lanes } }; + return updateLanes(state, lanes); }, moveLane: (state, { oldIndex, newIndex }) => { @@ -128,7 +111,7 @@ const LaneHelper = { removeLane: (state, { laneId }) => { const updatedLanes = state.lanes.filter((lane) => lane.id !== laneId); - return update(state, { lanes: { $set: updatedLanes } }); + return updateLanes(state, updatedLanes); } }; diff --git a/client/src/components/trello-board/styles/Base.js b/client/src/components/trello-board/styles/Base.js index 7ac66de89..f58436a01 100644 --- a/client/src/components/trello-board/styles/Base.js +++ b/client/src/components/trello-board/styles/Base.js @@ -60,7 +60,6 @@ export const GlobalStyleHorizontal = createGlobalStyle` width: 32px; } `; - export const GlobalStyleVertical = createGlobalStyle` .comPlainTextContentEditable { -webkit-user-modify: read-write-plaintext-only; @@ -101,7 +100,8 @@ export const GlobalStyleVertical = createGlobalStyle` } .smooth-dnd-container { - //min-height: 100px; // Not needed, just for extra landing space + // TODO ? This is the question. We need the same drag-zone we get in horizontal mode + min-height: 50px; // Not needed, just for extra landing space } .react-trello-lane { @@ -186,9 +186,9 @@ export const Header = styled.header` export const Section = styled.section` background-color: #e3e3e3; border-radius: 3px; - margin: 5px 5px; + margin: 2px 2px; position: relative; - padding: 10px; + padding: 5px; ${getSectionStyles}; flex-direction: column; `; diff --git a/client/src/components/trello-board/widgets/DeleteButton.jsx b/client/src/components/trello-board/widgets/DeleteButton.jsx index a89b854b9..0076921c8 100644 --- a/client/src/components/trello-board/widgets/DeleteButton.jsx +++ b/client/src/components/trello-board/widgets/DeleteButton.jsx @@ -1,10 +1,13 @@ import React from "react"; -import { DelButton, DeleteWrapper } from "../styles/Elements"; +import { DeleteWrapper } from "../styles/Elements"; +import { Button } from "antd"; const DeleteButton = (props) => { return ( - + ); };