- Performance / bug fixes / massive deleting of react-trello files we are not using.

Signed-off-by: Dave Richer <dave@imexsystems.ca>
This commit is contained in:
Dave Richer
2024-07-03 15:10:01 -04:00
parent 6f2c8dba5a
commit 7ab7e18d96
19 changed files with 228 additions and 1029 deletions

View File

@@ -1,7 +1,7 @@
import { ExclamationCircleFilled } from "@ant-design/icons";
import { useMutation } from "@apollo/client";
import { Dropdown } from "antd";
import React, { useCallback, useMemo } from "react";
import { Button } from "antd";
import React, { useCallback } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
@@ -16,58 +16,45 @@ const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type }))
});
export function ProductionListColumnAlert({ record, insertAuditTrail }) {
const ProductionListColumnAlert = ({ record, insertAuditTrail }) => {
const { t } = useTranslation();
const [updateAlert] = useMutation(UPDATE_JOB);
const handleAlertToggle = useCallback(() => {
logImEXEvent("production_toggle_alert");
const newAlertState = !!record.production_vars?.alert ? !record.production_vars.alert : true;
updateAlert({
variables: {
jobId: record.id,
job: {
production_vars: {
...record.production_vars,
alert: !!record.production_vars && !!record.production_vars.alert ? !record.production_vars.alert : true
alert: newAlertState
}
}
}
}).catch((err) => {
console.error(`Something went wrong updating production note: ${err.message || ""}`);
});
insertAuditTrail({
jobid: record.id,
operation: AuditTrailMapping.alertToggle(
!!record.production_vars && !!record.production_vars.alert ? !record.production_vars.alert : true
),
operation: AuditTrailMapping.alertToggle(newAlertState),
type: "alertToggle"
}).then(() => {
if (record.refetch) record.refetch();
});
}, [updateAlert, insertAuditTrail, record]);
const menuItems = useMemo(
() => [
{
key: "toggleAlert",
label:
record.production_vars && record.production_vars.alert
? t("production.labels.alertoff")
: t("production.labels.alerton"),
onClick: handleAlertToggle
}
],
[record.production_vars, t, handleAlertToggle]
);
return (
<Dropdown menu={{ items: menuItems }} trigger={["contextMenu"]}>
<div style={{ height: "19px" }}>
{record.production_vars && record.production_vars.alert ? (
<ExclamationCircleFilled className="production-alert" />
) : null}
</div>
</Dropdown>
<div style={{ height: "19px" }}>
{record.production_vars?.alert && (
<Button className="production-alert" icon={<ExclamationCircleFilled />} onClick={handleAlertToggle} />
)}
</div>
);
}
};
export default connect(mapStateToProps, mapDispatchToProps)(ProductionListColumnAlert);

View File

@@ -1,11 +0,0 @@
import React from "react";
import { AddCardLink } from "../styles/Base";
import { useTranslation } from "react-i18next";
const AddCardLinkComponent = ({ onClick, laneId }) => {
const { t } = useTranslation();
return <AddCardLink onClick={onClick}>{t("trello.labels.add_card")}</AddCardLink>;
};
export default AddCardLinkComponent;

View File

@@ -1,21 +0,0 @@
import React from "react";
import PropTypes from "prop-types";
import { TagSpan } from "../../styles/Base";
const Tag = ({ title, color, bgcolor, tagStyle, ...otherProps }) => {
const style = { color: color || "white", backgroundColor: bgcolor || "orange", ...tagStyle };
return (
<TagSpan style={style} {...otherProps}>
{title}
</TagSpan>
);
};
Tag.propTypes = {
title: PropTypes.string.isRequired,
color: PropTypes.string,
bgcolor: PropTypes.string,
tagStyle: PropTypes.object
};
export default Tag;

View File

@@ -1,62 +0,0 @@
import React from "react";
import PropTypes from "prop-types";
import InlineInput from "../../widgets/InlineInput.jsx";
import { LaneHeader, RightContent, Title } from "../../styles/Base";
import LaneMenu from "./LaneHeader/LaneMenu.jsx";
import { useTranslation } from "react-i18next";
const LaneHeaderComponent = ({
updateTitle,
canAddLanes,
onDelete,
onDoubleClick,
editLaneTitle,
label,
title,
titleStyle,
labelStyle
}) => {
const { t } = useTranslation();
return (
<LaneHeader onDoubleClick={onDoubleClick} editLaneTitle={editLaneTitle}>
<Title style={titleStyle}>
{editLaneTitle ? (
<InlineInput
value={title}
border
placeholder={t("trello.labels.title")}
resize="vertical"
onSave={updateTitle}
/>
) : (
title
)}
</Title>
{label && (
<RightContent>
<span style={labelStyle}>{label}</span>
</RightContent>
)}
{canAddLanes && <LaneMenu onDelete={onDelete} />}
</LaneHeader>
);
};
LaneHeaderComponent.propTypes = {
updateTitle: PropTypes.func,
editLaneTitle: PropTypes.bool,
canAddLanes: PropTypes.bool,
label: PropTypes.string,
title: PropTypes.string,
onDelete: PropTypes.func,
onDoubleClick: PropTypes.func
};
LaneHeaderComponent.defaultProps = {
updateTitle: () => {},
editLaneTitle: false,
canAddLanes: false
};
export default LaneHeaderComponent;

View File

@@ -1,41 +0,0 @@
import React from "react";
import { Popover } from "react-popopo";
import { CustomPopoverContainer, CustomPopoverContent } from "../../../styles/Base";
import {
DeleteWrapper,
GenDelButton,
LaneMenuContent,
LaneMenuHeader,
LaneMenuItem,
LaneMenuTitle,
MenuButton
} from "../../../styles/Elements";
import { useTranslation } from "react-i18next";
const LaneMenu = ({ onDelete }) => {
const { t } = useTranslation();
return (
<Popover
position="bottom"
PopoverContainer={CustomPopoverContainer}
PopoverContent={CustomPopoverContent}
trigger={<MenuButton></MenuButton>}
>
<LaneMenuHeader>
<LaneMenuTitle>{t("trello.labels.lane_actions")}</LaneMenuTitle>
<DeleteWrapper>
<GenDelButton>&#10006;</GenDelButton>
</DeleteWrapper>
</LaneMenuHeader>
<LaneMenuContent>
<LaneMenuItem onClick={onDelete}>{t("trello.labels.delete_lane")}</LaneMenuItem>
</LaneMenuContent>
</Popover>
);
};
export default LaneMenu;

View File

@@ -1,53 +0,0 @@
import React, { useState } from "react";
import PropTypes from "prop-types";
import { CardForm, CardHeader, CardRightContent, CardTitle, CardWrapper, Detail } from "../styles/Base";
import { AddButton, CancelButton } from "../styles/Elements";
import EditableLabel from "../widgets/EditableLabel.jsx";
import { useTranslation } from "react-i18next";
const NewCardForm = ({ onCancel, onAdd }) => {
const [state, setState] = useState({});
const { t } = useTranslation();
const updateField = (field, value) => {
setState((prevState) => ({ ...prevState, [field]: value }));
};
const handleAdd = () => {
onAdd(state);
};
return (
<CardForm>
<CardWrapper>
<CardHeader>
<CardTitle>
<EditableLabel
placeholder={t("trello.labels.title")}
onChange={(val) => updateField("title", val)}
autoFocus
/>
</CardTitle>
<CardRightContent>
<EditableLabel placeholder={t("trello.labels.label")} onChange={(val) => updateField("label", val)} />
</CardRightContent>
</CardHeader>
<Detail>
<EditableLabel
placeholder={t("trello.labels.description")}
onChange={(val) => updateField("description", val)}
/>
</Detail>
</CardWrapper>
<AddButton onClick={handleAdd}>{t("trello.labels.add_card")}</AddButton>
<CancelButton onClick={onCancel}>{t("trello.labels.cancel")}</CancelButton>
</CardForm>
);
};
NewCardForm.propTypes = {
onCancel: PropTypes.func.isRequired,
onAdd: PropTypes.func.isRequired
};
export default NewCardForm;

View File

@@ -1,57 +0,0 @@
import React, { useRef } from "react";
import PropTypes from "prop-types";
import { LaneTitle, NewLaneButtons, Section } from "../styles/Base";
import { AddButton, CancelButton } from "../styles/Elements";
import NewLaneTitleEditor from "../widgets/NewLaneTitleEditor.jsx";
import { v1 } from "uuid";
import { useTranslation } from "react-i18next";
const NewLane = ({ onCancel, onAdd }) => {
const refInput = useRef(null);
const { t } = useTranslation();
const handleSubmit = () => {
onAdd({
id: v1(),
title: getValue()
});
};
const getValue = () => refInput.current.getValue();
// TODO: Commented out because it was never called and it was causing a error
// const onClickOutside = (a, b, c) => {
// if (getValue().length > 0) {
// handleSubmit();
// } else {
// onCancel();
// }
// };
return (
<Section>
<LaneTitle>
<NewLaneTitleEditor
ref={refInput}
placeholder={t("trello.labels.title")}
onCancel={onCancel}
onSave={handleSubmit}
resize="vertical"
border
autoFocus
/>
</LaneTitle>
<NewLaneButtons>
<AddButton onClick={handleSubmit}>{t("trello.labels.add_lane")}</AddButton>
<CancelButton onClick={onCancel}>{t("trello.labels.cancel")}</CancelButton>
</NewLaneButtons>
</Section>
);
};
NewLane.propTypes = {
onCancel: PropTypes.func.isRequired,
onAdd: PropTypes.func.isRequired
};
export default NewLane;

View File

@@ -1,16 +0,0 @@
import React from "react";
import { NewLaneSection } from "../styles/Base";
import { AddLaneLink } from "../styles/Elements";
import { useTranslation } from "react-i18next";
const NewLaneSectionComponent = ({ onClick }) => {
const { t } = useTranslation();
return (
<NewLaneSection>
<AddLaneLink onClick={onClick}>{t("trello.labels.add_lane")}</AddLaneLink>
</NewLaneSection>
);
};
export default NewLaneSectionComponent;

View File

@@ -1,11 +1,7 @@
import LaneHeader from "./Lane/LaneHeader";
import LaneFooter from "./Lane/LaneFooter";
import Card from "./Card";
import Loader from "./Loader.jsx";
import NewLaneForm from "./NewLaneForm.jsx";
import NewCardForm from "./NewCardForm.jsx";
import AddCardLink from "./AddCardLink";
import NewLaneSection from "./NewLaneSection.jsx";
import { BoardWrapper, StyleHorizontal, GlobalStyle, StyleVertical, ScrollableLane, Section } from "../styles/Base";
const exports = {
@@ -15,14 +11,9 @@ const exports = {
BoardWrapper,
Loader,
ScrollableLane,
LaneHeader,
LaneFooter,
Section,
NewLaneForm,
NewLaneSection,
NewCardForm,
Card,
AddCardLink
Card
};
export default exports;

View File

@@ -1,12 +1,11 @@
import { BoardContainer } from "../index";
import classNames from "classnames";
import { useMemo, useState } from "react";
import { v1 } from "uuid";
const Board = ({ id, className, components, orientation, ...additionalProps }) => {
const [storeId] = useState(id || v1());
const allClassNames = useMemo(() => classNames("react-trello-board", className || ""), [className]);
const allClassNames = useMemo(() => `react-trello-board ${className || ""}`.trim(), [className]);
const OrientationStyle = useMemo(
() => (orientation === "horizontal" ? components.StyleHorizontal : components.StyleVertical),
[orientation, components.StyleHorizontal, components.StyleVertical]

View File

@@ -7,6 +7,7 @@ import isEqual from "lodash/isEqual";
import Lane from "./Lane";
import { PopoverWrapper } from "react-popopo";
import * as actions from "../../../redux/trello/trello.actions.js";
import { BoardWrapper } from "../styles/Base.js";
/**
* BoardContainer is a React component that represents a Trello-like board.
@@ -17,17 +18,10 @@ import * as actions from "../../../redux/trello/trello.actions.js";
* @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
*/
@@ -35,17 +29,11 @@ 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);
@@ -60,12 +48,20 @@ const BoardContainer = ({
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 "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 "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));
case "MOVE_CARD":
return dispatch(
actions.moveCardAcrossLanes({
@@ -76,14 +72,7 @@ const BoardContainer = ({
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;
}
@@ -109,13 +98,6 @@ const BoardContainer = ({
setIsDragging(true);
}, [setIsDragging]);
const getCardDetails = useCallback(
(laneId, cardIndex) => {
return currentReducerData.lanes.find((lane) => lane.id === laneId).cards[cardIndex];
},
[currentReducerData]
);
const passThroughProps = useMemo(
() =>
pick(
@@ -123,59 +105,15 @@ const BoardContainer = ({
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"
]
["laneSortFunction", "collapsibleLanes", "orientation"]
),
[
id,
components,
data,
draggable,
style,
onDataChange,
editable,
canAddLanes,
laneStyle,
orientation,
eventBusHandle,
reducerData,
cardStyle,
otherProps
]
[id, components, data, onDataChange, orientation, eventBusHandle, reducerData, otherProps]
);
const onLaneDrag = useCallback(
@@ -207,7 +145,7 @@ const BoardContainer = ({
);
return (
<components.BoardWrapper style={style} orientation={orientation}>
<BoardWrapper orientation={orientation}>
<PopoverWrapper>
<DragDropContext onDragEnd={onLaneDrag} onDragStart={onDragStart} contextId={groupName}>
{currentReducerData.lanes.map((lane, index) => {
@@ -218,13 +156,7 @@ const BoardContainer = ({
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}
@@ -235,7 +167,7 @@ const BoardContainer = ({
})}
</DragDropContext>
</PopoverWrapper>
</components.BoardWrapper>
</BoardWrapper>
);
};
@@ -247,30 +179,10 @@ BoardContainer.propTypes = {
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
orientation: PropTypes.string
};
export default BoardContainer;

View File

@@ -1,14 +1,14 @@
import React, { forwardRef, useCallback, useMemo, useState, useEffect } from "react";
import classNames from "classnames";
import React, { forwardRef, useCallback, useEffect, useMemo, useState } from "react";
import PropTypes from "prop-types";
import { bindActionCreators } from "redux";
import { connect } from "react-redux";
import { v1 } from "uuid";
import * as actions from "../../../redux/trello/trello.actions.js";
import { Draggable, Droppable } from "../dnd/lib";
import { Virtuoso, VirtuosoGrid } from "react-virtuoso";
import HeightPreservingItem from "../components/Lane/HeightPreservingItem.jsx";
import { Section } from "../styles/Base.js";
import LaneFooter from "../components/Lane/LaneFooter.jsx";
const Lane = ({
actions,
@@ -20,36 +20,14 @@ const Lane = ({
laneSortFunction,
style = {},
cardStyle = {},
tagStyle = {},
titleStyle = {},
labelStyle = {},
cards,
label,
draggable = false,
collapsibleLanes = false,
droppable = true,
onCardClick = () => {},
onBeforeCardDelete = () => {},
onCardDelete = () => {},
onCardAdd = () => {},
onCardUpdate = () => {},
onLaneDelete = () => {},
onLaneUpdate = () => {},
onLaneClick = () => {},
onLaneScroll = () => {},
editable = false,
cardDraggable = true,
cardDragClass,
cardDropClass,
canAddLanes = false,
hideCardDeleteIcon = false,
components = {},
orientation = "vertical",
className,
currentPage,
...otherProps
}) => {
const [addCardMode, setAddCardMode] = useState(false);
const [collapsed, setCollapsed] = useState(false);
const [isVisible, setIsVisible] = useState(true);
@@ -65,76 +43,11 @@ const Lane = ({
return cards.concat().sort((card1, card2) => sortFunction(card1, card2));
}, []);
const removeCard = useCallback(
(cardId) => {
if (onBeforeCardDelete && typeof onBeforeCardDelete === "function") {
onBeforeCardDelete(() => {
onCardDelete && onCardDelete(cardId, id);
actions.removeCard({ laneId: id, cardId: cardId });
});
} else {
onCardDelete && onCardDelete(cardId, id);
actions.removeCard({ laneId: id, cardId: cardId });
}
},
[onBeforeCardDelete, onCardDelete, actions, id]
);
const handleCardClick = useCallback(
(e, card) => {
onCardClick && onCardClick(card.id, card.metadata, card.laneId);
e.stopPropagation();
},
[onCardClick]
);
const showEditableCard = useCallback(() => {
setAddCardMode(true);
}, [setAddCardMode]);
const hideEditableCard = useCallback(() => {
setAddCardMode(false);
}, [setAddCardMode]);
const addNewCard = useCallback(
(params) => {
const laneId = id;
const newCardId = v1();
hideEditableCard();
let card = { id: newCardId, ...params };
actions.addCard({ laneId, card });
onCardAdd(card, laneId);
},
[actions, id, onCardAdd, hideEditableCard]
);
const updateCard = useCallback(
(updatedCard) => {
actions.updateCard({ laneId: id, card: updatedCard });
onCardUpdate(id, updatedCard);
},
[actions, id, onCardUpdate]
);
const removeLane = useCallback(() => {
actions.removeLane({ laneId: id });
onLaneDelete(id);
}, [actions, id, onLaneDelete]);
const updateTitle = useCallback(
(value) => {
actions.updateLane({ id, title: value });
onLaneUpdate(id, { title: value });
},
[actions, id, onLaneUpdate]
);
const toggleLaneCollapsed = useCallback(() => {
collapsibleLanes && setCollapsed(!collapsed);
}, [collapsibleLanes, collapsed]);
const Card = React.memo(({ provided, item: card, isDragging }) => {
const onDeleteCard = () => removeCard(card.id);
return (
<div
{...provided.draggableProps}
@@ -144,19 +57,7 @@ const Lane = ({
className={`item ${isDragging ? "is-dragging" : ""}`}
key={card.id}
>
<components.Card
key={card.id}
style={card.style || cardStyle}
className="react-trello-card"
onDelete={onDeleteCard}
onClick={(e) => handleCardClick(e, card)}
onChange={(updatedCard) => updateCard(updatedCard)}
showDeleteButton={!hideCardDeleteIcon}
tagStyle={tagStyle}
cardDraggable={cardDraggable}
editable={editable}
{...card}
/>
<components.Card key={card.id} style={card.style || cardStyle} className="react-trello-card" {...card} />
</div>
);
});
@@ -174,16 +75,6 @@ const Lane = ({
);
};
const renderAddCardLink = useMemo(
() => editable && !addCardMode && <components.AddCardLink onClick={showEditableCard} laneId={id} />,
[editable, addCardMode, showEditableCard, id]
);
const renderNewCardForm = useMemo(
() => addCardMode && <components.NewCardForm onCancel={hideEditableCard} laneId={id} onAdd={addNewCard} />,
[addCardMode, hideEditableCard, addNewCard, id]
);
const ItemWrapper = ({ children, ...props }) => (
<div {...props} className="item-wrapper">
{children}
@@ -196,43 +87,46 @@ const Lane = ({
const commonProps = {
useWindowScroll: true,
data: renderedCards,
overscan: {
main: 22,
reverse: 22
}
data: renderedCards
};
const componentProps =
orientation === "vertical"
? {
...commonProps,
scrollerRef: provided.innerRef,
listClassName: "grid-container",
itemClassName: "grid-item",
components: {
List: forwardRef(({ style, children, ...props }, ref) => (
<div
ref={ref}
{...props}
style={{
...style
}}
>
{children}
</div>
)),
Item: ({ children, ...props }) => <div {...props}>{children}</div>
},
itemContent: (index, item) => <ItemWrapper>{renderDraggable(index, item)}</ItemWrapper>
}
: {
...commonProps,
components: { Item: HeightPreservingItem },
itemContent: (index, item) => renderDraggable(index, item),
scrollerRef: provided.innerRef
};
const verticalProps = {
...commonProps,
// we are using the useWindowScroll, so we don't need to pass the scrollerRef
// scrollerRef: provided.innerRef,
listClassName: "grid-container",
itemClassName: "grid-item",
components: {
List: forwardRef(({ style, children, ...props }, ref) => (
<div
ref={ref}
{...props}
style={{
...style
}}
>
{children}
</div>
)),
Item: ({ children, ...props }) => <div {...props}>{children}</div>
},
itemContent: (index, item) => <ItemWrapper>{renderDraggable(index, item)}</ItemWrapper>,
overscan: { main: 10, reverse: 10 }
};
const horizontalProps = {
...commonProps,
components: { Item: HeightPreservingItem },
overscan: { main: 1, reverse: 1 },
itemContent: (index, item) => renderDraggable(index, item),
scrollerRef: provided.innerRef
};
const componentProps = orientation === "vertical" ? verticalProps : horizontalProps;
// A collapsed lane does not need to render the Virtuoso component
const finalComponentProps = collapsed ? {} : componentProps;
return (
<div style={{ height: "100%" }}>
<div
@@ -244,8 +138,6 @@ const Lane = ({
{isVisible && <FinalComponent {...finalComponentProps} />}
{(orientation === "horizontal" || renderedCards.length === 0 || collapsed) && provided.placeholder}
</div>
{renderAddCardLink}
{renderNewCardForm}
</div>
);
};
@@ -275,18 +167,13 @@ const Lane = ({
};
const renderHeader = (pickedProps) => {
return (
<components.LaneHeader
{...pickedProps}
onDelete={removeLane}
onDoubleClick={toggleLaneCollapsed}
updateTitle={updateTitle}
/>
);
return <components.LaneHeader {...pickedProps} onDoubleClick={toggleLaneCollapsed} />;
};
const allClassNames = classNames("react-trello-lane", collapsed ? "lane-collapsed" : "", className || "");
const allClassNames = useMemo(
() => `react-trello-lane ${collapsed ? "lane-collapsed" : ""} ${className || ""}`.trim(),
[collapsed, className]
);
const passedProps = {
actions,
id,
@@ -296,37 +183,20 @@ const Lane = ({
laneSortFunction,
style,
cardStyle,
tagStyle,
titleStyle,
labelStyle,
cards,
label,
draggable,
collapsibleLanes,
droppable,
editable,
cardDraggable,
cardDragClass,
cardDropClass,
canAddLanes,
hideCardDeleteIcon,
components,
orientation,
className,
currentPage,
...otherProps
};
return (
<components.Section
key={id}
onClick={() => onLaneClick && onLaneClick(id)}
orientation={orientation}
{...passedProps}
>
<Section key={id} orientation={orientation} {...passedProps}>
{renderHeader({ id, cards, ...passedProps })}
{renderDragContainer()}
{collapsibleLanes && <components.LaneFooter onClick={toggleLaneCollapsed} collapsed={collapsed} />}
</components.Section>
{collapsibleLanes && <LaneFooter onClick={toggleLaneCollapsed} collapsed={collapsed} />}
</Section>
);
};
@@ -339,20 +209,9 @@ Lane.propTypes = {
laneSortFunction: PropTypes.func,
style: PropTypes.object,
cardStyle: PropTypes.object,
tagStyle: PropTypes.object,
titleStyle: PropTypes.object,
labelStyle: PropTypes.object,
cards: PropTypes.array,
label: PropTypes.string,
draggable: PropTypes.bool,
collapsibleLanes: PropTypes.bool,
droppable: PropTypes.bool,
editable: PropTypes.bool,
cardDraggable: PropTypes.bool,
cardDragClass: PropTypes.string,
cardDropClass: PropTypes.string,
canAddLanes: PropTypes.bool,
hideCardDeleteIcon: PropTypes.bool,
components: PropTypes.object,
orientation: PropTypes.string
};

View File

@@ -1,10 +1,25 @@
import update from "immutability-helper";
import cloneDeep from "lodash/cloneDeep";
/**
* Update the lanes in the state
* @param state
* @param lanes
* @returns {unknown}
*/
const updateLanes = (state, lanes) => update(state, { lanes: { $set: lanes } });
const updateLaneCards = (lane, cards) => update(lane, { cards: { $set: cards } });
/**
* Helper functions for managing lanes
* @type {{moveCardAcrossLanes: (function(*, {fromLaneId: *, toLaneId: *, cardId: *, index: *}): *), initialiseLanes: (function(*, {lanes: *}): {lanes: *})}}
*/
const LaneHelper = {
/**
* Initialise the lanes
* @param state
* @param lanes
* @returns {unknown}
*/
initialiseLanes: (state, { lanes }) => {
const newLanes = lanes.map((lane) => {
lane.currentPage = 1;
@@ -14,79 +29,6 @@ const LaneHelper = {
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 updateLanes(state, updatedLanes);
},
appendCardsToLane: (state, { laneId, newCards, index }) => {
const lane = state.lanes.find((lane) => lane.id === laneId);
newCards = newCards
.map((c) => update(c, { laneId: { $set: laneId } }))
.filter((c) => lane.cards.find((card) => card.id === c.id) == null);
return state.lanes.map((lane) => {
if (lane.id === laneId) {
const cardsToUpdate =
index !== undefined
? [...lane.cards.slice(0, index), ...newCards, ...lane.cards.slice(index)]
: [...lane.cards, ...newCards];
return updateLaneCards(lane, cardsToUpdate);
} else {
return lane;
}
});
},
appendCardToLane: (state, { laneId, card, index }) => {
const newLanes = LaneHelper.appendCardsToLane(state, { laneId: laneId, newCards: [card], index });
return updateLanes(state, newLanes);
},
addLane: (state, lane) => {
const newLane = { cards: [], ...lane };
return updateLanes(state, [...state.lanes, newLane]);
},
updateLane: (state, updatedLane) => {
const newLanes = state.lanes.map((lane) => (updatedLane.id === lane.id ? { ...lane, ...updatedLane } : lane));
return updateLanes(state, newLanes);
},
removeCardFromLane: (state, { laneId, cardId }) => {
// Clone the state to avoid mutation
const newLanes = cloneDeep(state.lanes);
// Find the lane from which the card will be removed
const lane = newLanes.find((lane) => lane.id === laneId);
// Find the card in the lane
const cardIndex = lane.cards.findIndex((card) => card.id === cardId);
if (cardIndex === -1) {
throw new Error("Card not found in the lane");
}
// Remove the card from the lane
lane.cards.splice(cardIndex, 1);
let idx = 0;
// Update the lane and card indexes for all lanes
newLanes.forEach((lane, laneIndex) => {
lane.cards.forEach((card, cardIndex) => {
card.idx = idx;
card.laneIndex = laneIndex;
card.cardIndex = cardIndex;
card.laneId = lane.id;
idx++;
});
});
return update(state, {
lanes: { $set: newLanes }
});
},
/**
* Move a card from one lane to another
* @param state
@@ -136,28 +78,104 @@ const LaneHelper = {
return update(state, {
lanes: { $set: newLanes }
});
},
updateCardsForLane: (state, { laneId, cards }) => {
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) => (card.id === updatedCard.id ? { ...card, ...updatedCard } : card));
return updateLaneCards(lane, cards);
} else {
return lane;
}
});
return updateLanes(state, lanes);
},
removeLane: (state, { laneId }) => {
const updatedLanes = state.lanes.filter((lane) => lane.id !== laneId);
return updateLanes(state, updatedLanes);
}
// TODO: (Note), the rest of this commented out logic is for the way the original Trello board was implemented
// It would not be hard to adapt should we need to use it in the future
// const updateLaneCards = (lane, cards) => update(lane, { cards: { $set: cards } });
// paginateLane: (state, { laneId, newCards, nextPage }) => {
// const updatedLanes = LaneHelper.appendCardsToLane(state, { laneId: laneId, newCards: newCards });
// updatedLanes.find((lane) => lane.id === laneId).currentPage = nextPage;
// return updateLanes(state, updatedLanes);
// },
// appendCardsToLane: (state, { laneId, newCards, index }) => {
// const lane = state.lanes.find((lane) => lane.id === laneId);
// newCards = newCards
// .map((c) => update(c, { laneId: { $set: laneId } }))
// .filter((c) => lane.cards.find((card) => card.id === c.id) == null);
// return state.lanes.map((lane) => {
// if (lane.id === laneId) {
// const cardsToUpdate =
// index !== undefined
// ? [...lane.cards.slice(0, index), ...newCards, ...lane.cards.slice(index)]
// : [...lane.cards, ...newCards];
// return updateLaneCards(lane, cardsToUpdate);
// } else {
// return lane;
// }
// });
// },
// appendCardToLane: (state, { laneId, card, index }) => {
// const newLanes = LaneHelper.appendCardsToLane(state, { laneId: laneId, newCards: [card], index });
// return updateLanes(state, newLanes);
// },
//
// addLane: (state, lane) => {
// const newLane = { cards: [], ...lane };
// return updateLanes(state, [...state.lanes, newLane]);
// },
//
// updateLane: (state, updatedLane) => {
// const newLanes = state.lanes.map((lane) => (updatedLane.id === lane.id ? { ...lane, ...updatedLane } : lane));
// return updateLanes(state, newLanes);
// },
//
// removeCardFromLane: (state, { laneId, cardId }) => {
// // Clone the state to avoid mutation
// const newLanes = cloneDeep(state.lanes);
//
// // Find the lane from which the card will be removed
// const lane = newLanes.find((lane) => lane.id === laneId);
//
// // Find the card in the lane
// const cardIndex = lane.cards.findIndex((card) => card.id === cardId);
// if (cardIndex === -1) {
// throw new Error("Card not found in the lane");
// }
//
// // Remove the card from the lane
// lane.cards.splice(cardIndex, 1);
//
// let idx = 0;
//
// // Update the lane and card indexes for all lanes
// newLanes.forEach((lane, laneIndex) => {
// lane.cards.forEach((card, cardIndex) => {
// card.idx = idx;
// card.laneIndex = laneIndex;
// card.cardIndex = cardIndex;
// card.laneId = lane.id;
// idx++;
// });
// });
//
// return update(state, {
// lanes: { $set: newLanes }
// });
// },
// updateCardsForLane: (state, { laneId, cards }) => {
// 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) => (card.id === updatedCard.id ? { ...card, ...updatedCard } : card));
// return updateLaneCards(lane, cards);
// } else {
// return lane;
// }
// });
// return updateLanes(state, lanes);
// },
//
// removeLane: (state, { laneId }) => {
// const updatedLanes = state.lanes.filter((lane) => lane.id !== laneId);
// return updateLanes(state, updatedLanes);
// }
};
export default LaneHelper;

View File

@@ -24,6 +24,10 @@ const getSectionStyles = (props) => {
white-space: nowrap;
// overflow-y: none;
min-width: 8.5%;
flex-wrap: wrap;
align-content: center;
justify-content: center;
align-items: stretch;
`;
}
return `
@@ -31,6 +35,18 @@ const getSectionStyles = (props) => {
`;
};
const getLaneFooterStyles = (props) => {
if (props.operation === "horizontal") {
return `
height: 50px;
margin-top: 20px;
`;
}
return `
height: 25px;
`;
};
export const GlobalStyle = createGlobalStyle`
.comPlainTextContentEditable {
-webkit-user-modify: read-write-plaintext-only;
@@ -84,15 +100,18 @@ export const StyleHorizontal = styled.div`
.react-trello-lane.lane-collapsed {
min-height: 15px;
}
.ant-card-body {
padding: 4px;
}
.item-wrapper {
display: flex;
flex: 1 1 auto;
white-space: nowrap;
min-width: 4%;
}
.react-trello-card {
height: auto;
margin: 2px;
@@ -118,10 +137,12 @@ export const StyleVertical = styled.div`
width: 100%;
height: 100%;
}
.grid-container {
display: flex;
flex-wrap: wrap;
}
.grid-item {
width: 8%; // TODO: THIS IS WHERE WE GET VERTICAL CARD CUSTOMIZATION
display: flex;
@@ -133,6 +154,7 @@ export const StyleVertical = styled.div`
height: 100%;
width: 100%;
}
.item-wrapper {
width: 100%;
height: 100%;
@@ -219,27 +241,12 @@ export const Section = styled.section`
${getSectionStyles};
`;
export const LaneHeader = styled(Header)`
margin-bottom: 0;
${(props) =>
props.editLaneTitle &&
css`
padding: 0;
line-height: 30px;
`} ${(props) =>
!props.editLaneTitle &&
css`
padding: 0 5px;
`};
`;
export const LaneFooter = styled.div`
display: flex;
justify-content: center;
align-items: center;
width: 100%;
position: relative;
height: 10px;
${getLaneFooterStyles};
`;
export const ScrollableLane = styled.div`

View File

@@ -1,15 +0,0 @@
import React from "react";
import { DeleteWrapper } from "../styles/Elements";
import { Button } from "antd";
const DeleteButton = (props) => {
return (
<DeleteWrapper {...props}>
<Button type="primary" danger>
Delete
</Button>
</DeleteWrapper>
);
};
export default DeleteButton;

View File

@@ -1,87 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
class EditableLabel extends React.Component {
constructor({value}) {
super()
this.state = {value: value}
}
getText = el => {
return el.innerText
}
onTextChange = ev => {
const value = this.getText(ev.target)
this.setState({value: value})
}
componentDidMount() {
if (this.props.autoFocus) {
this.refDiv.focus()
}
}
onBlur = () => {
this.props.onChange(this.state.value)
}
onPaste = ev => {
ev.preventDefault()
const value = ev.clipboardData.getData('text')
document.execCommand('insertText', false, value)
}
getClassName = () => {
const placeholder = this.state.value === '' ? 'comPlainTextContentEditable--has-placeholder' : ''
return `comPlainTextContentEditable ${placeholder}`
}
onKeyDown = e => {
if (e.keyCode === 13) {
this.props.onChange(this.state.value)
this.refDiv.blur()
e.preventDefault()
}
if (e.keyCode === 27) {
this.refDiv.value = this.props.value
this.setState({value: this.props.value})
// this.refDiv.blur()
e.preventDefault()
e.stopPropagation()
}
}
render() {
const placeholder = this.props.value.length > 0 ? false : this.props.placeholder
return (
<div
ref={ref => (this.refDiv = ref)}
contentEditable="true"
className={this.getClassName()}
onPaste={this.onPaste}
onBlur={this.onBlur}
onInput={this.onTextChange}
onKeyDown={this.onKeyDown}
placeholder={placeholder}
/>
)
}
}
EditableLabel.propTypes = {
onChange: PropTypes.func,
placeholder: PropTypes.string,
autoFocus: PropTypes.bool,
inline: PropTypes.bool,
value: PropTypes.string
}
EditableLabel.defaultProps = {
onChange: () => {},
placeholder: '',
autoFocus: false,
inline: false,
value: ''
}
export default EditableLabel

View File

@@ -1,106 +0,0 @@
import React, { useEffect, useRef, useState } from "react";
import PropTypes from "prop-types";
import { InlineInput } from "../styles/Base";
import autosize from "autosize";
const InlineInputController = ({ onSave, border, placeholder, value, autoFocus, resize, onCancel }) => {
const inputRef = useRef(null);
const [inputValue, setInputValue] = useState(value);
// Effect for autosizing and initial autoFocus
useEffect(() => {
if (inputRef.current && resize !== "none") {
autosize(inputRef.current);
}
if (inputRef.current && autoFocus) {
inputRef.current.focus();
}
}, [resize, autoFocus]);
// Effect to update value when props change
useEffect(() => {
setInputValue(value);
}, [value]);
const handleFocus = (e) => e.target.select();
const handleMouseDown = (e) => {
if (document.activeElement !== e.target) {
e.preventDefault();
inputRef.current.focus();
}
};
const handleBlur = () => {
updateValue();
};
const handleKeyDown = (e) => {
if (e.keyCode === 13) {
// Enter
inputRef.current.blur();
e.preventDefault();
} else if (e.keyCode === 27) {
// Escape
setInputValue(value); // Reset to initial value
inputRef.current.blur();
e.preventDefault();
} else if (e.keyCode === 9) {
// Tab
if (inputValue.length === 0) {
onCancel();
}
inputRef.current.blur();
e.preventDefault();
}
};
const updateValue = () => {
if (inputValue !== value) {
onSave(inputValue);
}
};
return (
<InlineInput
ref={inputRef}
border={border}
onMouseDown={handleMouseDown}
onFocus={handleFocus}
onBlur={handleBlur}
onKeyDown={handleKeyDown}
placeholder={inputValue.length === 0 ? undefined : placeholder}
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
dataGramm="false"
rows={1}
autoFocus={autoFocus}
/>
);
};
InlineInputController.propTypes = {
onSave: PropTypes.func,
onCancel: PropTypes.func,
border: PropTypes.bool,
placeholder: PropTypes.string,
value: PropTypes.string,
autoFocus: PropTypes.bool,
resize: PropTypes.oneOf(["none", "vertical", "horizontal"])
};
InlineInputController.defaultProps = {
onSave: () => {},
onCancel: () => {},
placeholder: "",
value: "",
border: false,
autoFocus: false,
resize: "none"
};
export default InlineInputController;

View File

@@ -1,94 +0,0 @@
import React from "react";
import PropTypes from "prop-types";
import { InlineInput } from "../styles/Base";
import autosize from "autosize";
class NewLaneTitleEditor extends React.Component {
onKeyDown = (e) => {
if (e.keyCode === 13) {
this.refInput.blur();
this.props.onSave();
e.preventDefault();
}
if (e.keyCode === 27) {
this.cancel();
e.preventDefault();
}
if (e.keyCode === 9) {
if (this.getValue().length === 0) {
this.cancel();
} else {
this.props.onSave();
}
e.preventDefault();
}
};
cancel = () => {
this.setValue("");
this.props.onCancel();
this.refInput.blur();
};
getValue = () => this.refInput.value;
setValue = (value) => (this.refInput.value = value);
saveValue = () => {
if (this.getValue() !== this.props.value) {
this.props.onSave(this.getValue());
}
};
focus = () => this.refInput.focus();
setRef = (ref) => {
this.refInput = ref;
if (this.props.resize !== "none") {
autosize(this.refInput);
}
};
render() {
const { autoFocus, resize, border, autoResize, value, placeholder } = this.props;
return (
<InlineInput
style={{ resize: resize }}
ref={this.setRef}
border={border}
onKeyDown={this.onKeyDown}
placeholder={value.length === 0 ? undefined : placeholder}
defaultValue={value}
rows={3}
autoResize={autoResize}
autoFocus={autoFocus}
/>
);
}
}
NewLaneTitleEditor.propTypes = {
onSave: PropTypes.func,
onCancel: PropTypes.func,
border: PropTypes.bool,
placeholder: PropTypes.string,
value: PropTypes.string,
autoFocus: PropTypes.bool,
autoResize: PropTypes.bool,
resize: PropTypes.oneOf(["none", "vertical", "horizontal"])
};
NewLaneTitleEditor.defaultProps = {
inputRef: () => {},
onSave: () => {},
onCancel: () => {},
placeholder: "",
value: "",
border: false,
autoFocus: false,
autoResize: false,
resize: "none"
};
export default NewLaneTitleEditor;

View File

@@ -1,11 +0,0 @@
import DeleteButton from "./DeleteButton";
import EditableLabel from "./EditableLabel";
import InlineInput from "./InlineInput";
const exports = {
DeleteButton,
EditableLabel,
InlineInput
};
export default exports;