1
0
Fork 0
mirror of https://github.com/plankanban/planka.git synced 2025-08-10 16:05:35 +02:00

Addíng code where copying from list works but from opened card does not.

Copying content on card does not work from any place.
This commit is contained in:
Jens Frost 2023-05-16 16:44:47 +02:00
parent b273a4f68b
commit 21fc0f560a
24 changed files with 573 additions and 109 deletions

View file

@ -89,6 +89,35 @@ const handleCardDelete = (card) => ({
},
});
const copyCard = (id) => ({
type: ActionTypes.CARD_COPY,
payload: {
id,
},
});
copyCard.success = (card) => ({
type: ActionTypes.CARD_COPY__SUCCESS,
payload: {
card,
},
});
copyCard.failure = (id, error) => ({
type: ActionTypes.CARD_COPY__FAILURE,
payload: {
id,
error,
},
});
const handleCardCopy = (card) => ({
type: ActionTypes.CARD_COPY_HANDLE,
payload: {
card,
},
});
export default {
createCard,
handleCardCreate,
@ -96,4 +125,6 @@ export default {
handleCardUpdate,
deleteCard,
handleCardDelete,
copyCard,
handleCardCopy,
};

View file

@ -25,6 +25,7 @@ const Filters = React.memo(
onLabelUpdate,
onLabelMove,
onLabelDelete,
// onCopyCard,
}) => {
const [t] = useTranslation();
@ -83,6 +84,7 @@ const Filters = React.memo(
onUpdate={onLabelUpdate}
onMove={onLabelMove}
onDelete={onLabelDelete}
// onCopyCard={onCopyCard}
>
<button type="button" className={styles.filterButton}>
<span className={styles.filterTitle}>{`${t('common.labels')}:`}</span>
@ -121,6 +123,7 @@ Filters.propTypes = {
onLabelUpdate: PropTypes.func.isRequired,
onLabelMove: PropTypes.func.isRequired,
onLabelDelete: PropTypes.func.isRequired,
// onCopyCard: PropTypes.func.isRequired,
};
export default Filters;

View file

@ -14,115 +14,118 @@ import EditStep from './EditStep';
import styles from './Boards.module.scss';
const Boards = React.memo(({ items, currentId, canEdit, onCreate, onUpdate, onMove, onDelete }) => {
const tabsWrapper = useRef(null);
const Boards = React.memo(
({ items, currentId, canEdit, onCreate, onUpdate, onMove, onDelete, onCopyCard }) => {
const tabsWrapper = useRef(null);
const handleWheel = useCallback(({ deltaY }) => {
tabsWrapper.current.scrollBy({
left: deltaY,
});
}, []);
const handleWheel = useCallback(({ deltaY }) => {
tabsWrapper.current.scrollBy({
left: deltaY,
});
}, []);
const handleDragStart = useCallback(() => {
closePopup();
}, []);
const handleDragStart = useCallback(() => {
closePopup();
}, []);
const handleDragEnd = useCallback(
({ draggableId, source, destination }) => {
if (!destination || source.index === destination.index) {
return;
}
const handleDragEnd = useCallback(
({ draggableId, source, destination }) => {
if (!destination || source.index === destination.index) {
return;
}
onMove(draggableId, destination.index);
},
[onMove],
);
onMove(draggableId, destination.index);
},
[onMove],
);
const handleUpdate = useCallback(
(id, data) => {
onUpdate(id, data);
},
[onUpdate],
);
const handleUpdate = useCallback(
(id, data) => {
onUpdate(id, data);
},
[onUpdate],
);
const handleDelete = useCallback(
(id) => {
onDelete(id);
},
[onDelete],
);
const handleDelete = useCallback(
(id) => {
onDelete(id);
},
[onDelete],
);
const AddPopup = usePopup(AddStep);
const EditPopup = usePopup(EditStep);
const AddPopup = usePopup(AddStep);
const EditPopup = usePopup(EditStep);
const itemsNode = items.map((item, index) => (
<Draggable
key={item.id}
draggableId={item.id}
index={index}
isDragDisabled={!item.isPersisted || !canEdit}
>
{({ innerRef, draggableProps, dragHandleProps }) => (
// eslint-disable-next-line react/jsx-props-no-spreading
<div {...draggableProps} ref={innerRef} className={styles.tabWrapper}>
<div className={classNames(styles.tab, item.id === currentId && styles.tabActive)}>
{item.isPersisted ? (
<>
<Link
{...dragHandleProps} // eslint-disable-line react/jsx-props-no-spreading
to={Paths.BOARDS.replace(':id', item.id)}
title={item.name}
className={styles.link}
>
{item.name}
</Link>
{canEdit && (
<EditPopup
defaultData={pick(item, 'name')}
onUpdate={(data) => handleUpdate(item.id, data)}
onDelete={() => handleDelete(item.id)}
const itemsNode = items.map((item, index) => (
<Draggable
key={item.id}
draggableId={item.id}
index={index}
isDragDisabled={!item.isPersisted || !canEdit}
>
{({ innerRef, draggableProps, dragHandleProps }) => (
// eslint-disable-next-line react/jsx-props-no-spreading
<div {...draggableProps} ref={innerRef} className={styles.tabWrapper}>
<div className={classNames(styles.tab, item.id === currentId && styles.tabActive)}>
{item.isPersisted ? (
<>
<Link
{...dragHandleProps} // eslint-disable-line react/jsx-props-no-spreading
to={Paths.BOARDS.replace(':id', item.id)}
title={item.name}
className={styles.link}
>
<Button className={classNames(styles.editButton, styles.target)}>
<Icon fitted name="pencil" size="small" />
</Button>
</EditPopup>
)}
</>
) : (
// eslint-disable-next-line react/jsx-props-no-spreading
<span {...dragHandleProps} className={styles.link}>
{item.name}
</span>
)}
{item.name}
</Link>
{canEdit && (
<EditPopup
defaultData={pick(item, 'name')}
onUpdate={(data) => handleUpdate(item.id, data)}
onDelete={() => handleDelete(item.id)}
onCopyCard={onCopyCard}
>
<Button className={classNames(styles.editButton, styles.target)}>
<Icon fitted name="pencil" size="small" />
</Button>
</EditPopup>
)}
</>
) : (
// eslint-disable-next-line react/jsx-props-no-spreading
<span {...dragHandleProps} className={styles.link}>
{item.name}
</span>
)}
</div>
</div>
</div>
)}
</Draggable>
));
)}
</Draggable>
));
return (
<div className={styles.wrapper} onWheel={handleWheel}>
<div ref={tabsWrapper} className={styles.tabsWrapper}>
<DragDropContext onDragStart={handleDragStart} onDragEnd={handleDragEnd}>
<Droppable droppableId="boards" type={DroppableTypes.BOARD} direction="horizontal">
{({ innerRef, droppableProps, placeholder }) => (
// eslint-disable-next-line react/jsx-props-no-spreading
<div {...droppableProps} ref={innerRef} className={styles.tabs}>
{itemsNode}
{placeholder}
{canEdit && (
<AddPopup onCreate={onCreate}>
<Button icon="plus" className={styles.addButton} />
</AddPopup>
)}
</div>
)}
</Droppable>
</DragDropContext>
return (
<div className={styles.wrapper} onWheel={handleWheel}>
<div ref={tabsWrapper} className={styles.tabsWrapper}>
<DragDropContext onDragStart={handleDragStart} onDragEnd={handleDragEnd}>
<Droppable droppableId="boards" type={DroppableTypes.BOARD} direction="horizontal">
{({ innerRef, droppableProps, placeholder }) => (
// eslint-disable-next-line react/jsx-props-no-spreading
<div {...droppableProps} ref={innerRef} className={styles.tabs}>
{itemsNode}
{placeholder}
{canEdit && (
<AddPopup onCreate={onCreate} onCopyCard={onCopyCard}>
<Button icon="plus" className={styles.addButton} />
</AddPopup>
)}
</div>
)}
</Droppable>
</DragDropContext>
</div>
</div>
</div>
);
});
);
},
);
Boards.propTypes = {
items: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types
@ -132,6 +135,7 @@ Boards.propTypes = {
onUpdate: PropTypes.func.isRequired,
onMove: PropTypes.func.isRequired,
onDelete: PropTypes.func.isRequired,
onCopyCard: PropTypes.func.isRequired,
};
Boards.defaultProps = {

View file

@ -12,6 +12,7 @@ import DueDateEditStep from '../DueDateEditStep';
import StopwatchEditStep from '../StopwatchEditStep';
import CardMoveStep from '../CardMoveStep';
import DeleteStep from '../DeleteStep';
import CardCopyStep from '../CardCopyStep';
import styles from './ActionsStep.module.scss';
@ -22,6 +23,7 @@ const StepTypes = {
EDIT_STOPWATCH: 'EDIT_STOPWATCH',
MOVE: 'MOVE',
DELETE: 'DELETE',
COPY: 'COPY',
};
const ActionsStep = React.memo(
@ -47,6 +49,7 @@ const ActionsStep = React.memo(
onLabelMove,
onLabelDelete,
onClose,
onCopyCard,
}) => {
const [t] = useTranslation();
const [step, openStep, handleBack] = useSteps();
@ -76,6 +79,10 @@ const ActionsStep = React.memo(
openStep(StepTypes.MOVE);
}, [openStep]);
const handleCopyClick = useCallback(() => {
openStep(StepTypes.COPY);
}, [openStep]);
const handleDeleteClick = useCallback(() => {
openStep(StepTypes.DELETE);
}, [openStep]);
@ -108,6 +115,7 @@ const ActionsStep = React.memo(
onUserSelect={onUserAdd}
onUserDeselect={onUserRemove}
onBack={handleBack}
onCopyCard={onCopyCard}
/>
);
case StepTypes.LABELS:
@ -122,6 +130,7 @@ const ActionsStep = React.memo(
onMove={onLabelMove}
onDelete={onLabelDelete}
onBack={handleBack}
onCopyCard={onCopyCard}
/>
);
case StepTypes.EDIT_DUE_DATE:
@ -131,6 +140,7 @@ const ActionsStep = React.memo(
onUpdate={handleDueDateUpdate}
onBack={handleBack}
onClose={onClose}
onCopyCard={onCopyCard}
/>
);
case StepTypes.EDIT_STOPWATCH:
@ -140,6 +150,7 @@ const ActionsStep = React.memo(
onUpdate={handleStopwatchUpdate}
onBack={handleBack}
onClose={onClose}
onCopyCard={onCopyCard}
/>
);
case StepTypes.MOVE:
@ -152,6 +163,7 @@ const ActionsStep = React.memo(
onBoardFetch={onBoardFetch}
onBack={handleBack}
onClose={onClose}
onCopyCard={onCopyCard}
/>
);
case StepTypes.DELETE:
@ -162,6 +174,20 @@ const ActionsStep = React.memo(
buttonContent="action.deleteCard"
onConfirm={onDelete}
onBack={handleBack}
onCopyCard={onCopyCard}
/>
);
case StepTypes.COPY:
return (
<CardCopyStep
projectsToLists={projectsToLists}
defaultPath={pick(card, ['projectId', 'boardId', 'listId'])}
onCopyCard={onCopyCard}
onTransfer={onTransfer}
onBoardFetch={onBoardFetch}
onBack={handleBack}
onClose={onClose}
onConfirm={onCopyCard}
/>
);
default:
@ -212,6 +238,11 @@ const ActionsStep = React.memo(
context: 'title',
})}
</Menu.Item>
<Menu.Item className={styles.menuItem} onClick={handleCopyClick}>
{t('action.copyCard', {
context: 'title',
})}
</Menu.Item>
</Menu>
</Popup.Content>
</>
@ -243,6 +274,7 @@ ActionsStep.propTypes = {
onLabelMove: PropTypes.func.isRequired,
onLabelDelete: PropTypes.func.isRequired,
onClose: PropTypes.func.isRequired,
onCopyCard: PropTypes.func.isRequired,
};
export default ActionsStep;

View file

@ -51,6 +51,7 @@ const Card = React.memo(
onLabelUpdate,
onLabelMove,
onLabelDelete,
onCopyCard,
}) => {
const nameEdit = useRef(null);
@ -195,6 +196,7 @@ const Card = React.memo(
onLabelUpdate={onLabelUpdate}
onLabelMove={onLabelMove}
onLabelDelete={onLabelDelete}
onCopyCard={onCopyCard}
>
<Button className={classNames(styles.actionsButton, styles.target)}>
<Icon fitted name="pencil" size="small" />
@ -250,6 +252,7 @@ Card.propTypes = {
onLabelDelete: PropTypes.func.isRequired,
// onSortTitleAsc: PropTypes.func.isRequired,
// onSortTitleDesc: PropTypes.func.isRequired,
onCopyCard: PropTypes.func.isRequired,
};
Card.defaultProps = {

View file

@ -0,0 +1,5 @@
import { withPopup } from '../lib/popup';
import CardCopyStep from './CardCopyStep';
export default withPopup(CardCopyStep);

View file

@ -0,0 +1,169 @@
import React, { useMemo, useCallback } from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import { Button, Dropdown, Form } from 'semantic-ui-react';
import { Popup } from '../../lib/custom-ui';
import { useForm } from '../../hooks';
import styles from './CardCopyStep.module.scss';
const CardCopyStep = React.memo(
({ projectsToLists, defaultPath, onTransfer, onBoardFetch, onBack, onClose, onCopyCard }) => {
const [t] = useTranslation();
const [path, handleFieldChange] = useForm(() => ({
projectId: null,
boardId: null,
listId: null,
name: null,
description: null,
tasks: [],
attachments: [],
labels: [],
users: [],
...defaultPath,
}));
const selectedProject = useMemo(
() => projectsToLists.find((project) => project.id === path.projectId) || null,
[projectsToLists, path.projectId],
);
const selectedBoard = useMemo(
() =>
(selectedProject && selectedProject.boards.find((board) => board.id === path.boardId)) ||
null,
[selectedProject, path.boardId],
);
const selectedList = useMemo(
() => (selectedBoard && selectedBoard.lists.find((list) => list.id === path.listId)) || null,
[selectedBoard, path.listId],
);
const handleBoardIdChange = useCallback(
(event, data) => {
if (selectedProject.boards.find((board) => board.id === data.value).isFetching === null) {
onBoardFetch(data.value);
}
handleFieldChange(event, data);
},
[onBoardFetch, handleFieldChange, selectedProject],
);
const handleSubmit = useCallback(() => {
if (selectedBoard.id !== defaultPath.boardId) {
onTransfer(selectedBoard.id, selectedList.id);
} else if (selectedList.id !== defaultPath.listId) {
console.log('test');
}
onCopyCard(selectedList.id, { name: 'test' }, false);
onClose();
}, [defaultPath, onTransfer, onClose, selectedBoard, selectedList, onCopyCard]);
return (
<>
<Popup.Header onBack={onBack}>
{t('action.copyCard', {
context: 'title',
})}
</Popup.Header>
<Popup.Content>
<Form onSubmit={handleSubmit}>
<div className={styles.text}>{t('common.project')}</div>
<Dropdown
fluid
selection
name="projectId"
options={projectsToLists.map((project) => ({
text: project.name,
value: project.id,
}))}
value={selectedProject && selectedProject.id}
placeholder={
projectsToLists.length === 0 ? t('common.noProjects') : t('common.selectProject')
}
disabled={projectsToLists.length === 0}
className={styles.field}
onChange={handleFieldChange}
/>
{selectedProject && (
<>
<div className={styles.text}>{t('common.board')}</div>
<Dropdown
fluid
selection
name="boardId"
options={selectedProject.boards.map((board) => ({
text: board.name,
value: board.id,
}))}
value={selectedBoard && selectedBoard.id}
placeholder={
selectedProject.boards.length === 0
? t('common.noBoards')
: t('common.selectBoard')
}
disabled={selectedProject.boards.length === 0}
className={styles.field}
onChange={handleBoardIdChange}
/>
</>
)}
{selectedBoard && (
<>
<div className={styles.text}>{t('common.list')}</div>
<Dropdown
fluid
selection
name="listId"
options={selectedBoard.lists.map((list) => ({
text: list.name,
value: list.id,
}))}
value={selectedList && selectedList.id}
placeholder={
selectedBoard.isFetching === false && selectedBoard.lists.length === 0
? t('common.noLists')
: t('common.selectList')
}
loading={selectedBoard.isFetching !== false}
disabled={selectedBoard.isFetching !== false || selectedBoard.lists.length === 0}
className={styles.field}
onChange={handleFieldChange}
/>
</>
)}
<Button
positive
content={t('action.copy')} // change this action.copy
disabled={(selectedBoard && selectedBoard.isFetching !== false) || !selectedList}
/>
</Form>
</Popup.Content>
</>
);
},
);
CardCopyStep.propTypes = {
/* eslint-disable react/forbid-prop-types */
projectsToLists: PropTypes.array.isRequired,
defaultPath: PropTypes.object.isRequired,
/* eslint-enable react/forbid-prop-types */
// onMove: PropTypes.func.isRequired,
onTransfer: PropTypes.func.isRequired,
onBoardFetch: PropTypes.func.isRequired,
onBack: PropTypes.func,
onClose: PropTypes.func.isRequired,
onCopyCard: PropTypes.func.isRequired,
};
CardCopyStep.defaultProps = {
onBack: undefined,
};
export default CardCopyStep;

View file

@ -0,0 +1,12 @@
:global(#app) {
.field {
margin-bottom: 8px;
}
.text {
color: #444444;
font-size: 12px;
font-weight: bold;
padding-bottom: 6px;
}
}

View file

@ -0,0 +1,3 @@
import CardCopyStep from './CardCopyStep';
export default CardCopyStep;

View file

@ -24,6 +24,7 @@ import DueDateEditStep from '../DueDateEditStep';
import StopwatchEditStep from '../StopwatchEditStep';
import CardMoveStep from '../CardMoveStep';
import DeleteStep from '../DeleteStep';
import CardCopyStep from '../CardCopyStep';
import CardCopyPopup from '../CardCopyPopup';
import styles from './CardModal.module.scss';
@ -79,6 +80,7 @@ const CardModal = React.memo(
onCommentActivityUpdate,
onCommentActivityDelete,
onClose,
onCopyCard,
}) => {
const [t] = useTranslation();
@ -203,6 +205,7 @@ const CardModal = React.memo(
currentUserIds={userIds}
onUserSelect={onUserAdd}
onUserDeselect={onUserRemove}
onCopyCard={onCopyCard}
>
<User name={user.name} avatarUrl={user.avatarUrl} />
</BoardMembershipsPopup>
@ -217,6 +220,7 @@ const CardModal = React.memo(
currentUserIds={userIds}
onUserSelect={onUserAdd}
onUserDeselect={onUserRemove}
onCopyCard={onCopyCard}
>
<button
type="button"
@ -248,6 +252,7 @@ const CardModal = React.memo(
onUpdate={onLabelUpdate}
onMove={onLabelMove}
onDelete={onLabelDelete}
onCopyCard={onCopyCard}
>
<Label name={label.name} color={label.color} />
</LabelsPopup>
@ -266,6 +271,7 @@ const CardModal = React.memo(
onUpdate={onLabelUpdate}
onMove={onLabelMove}
onDelete={onLabelDelete}
onCopyCard={onCopyCard}
>
<button
type="button"
@ -286,11 +292,15 @@ const CardModal = React.memo(
</div>
<span className={styles.attachment}>
{canEdit ? (
<DueDateEditPopup defaultValue={dueDate} onUpdate={handleDueDateUpdate}>
<DueDate value={dueDate} />
<DueDateEditPopup
defaultValue={dueDate}
onUpdate={handleDueDateUpdate}
onCopyCard={onCopyCard}
>
<DueDate value={dueDate} onCopyCard={onCopyCard} />
</DueDateEditPopup>
) : (
<DueDate value={dueDate} />
<DueDate value={dueDate} onCopyCard={onCopyCard} />
)}
</span>
</div>
@ -308,10 +318,18 @@ const CardModal = React.memo(
defaultValue={stopwatch}
onUpdate={handleStopwatchUpdate}
>
<Stopwatch startedAt={stopwatch.startedAt} total={stopwatch.total} />
<Stopwatch
startedAt={stopwatch.startedAt}
total={stopwatch.total}
onCopyCard={onCopyCard}
/>
</StopwatchEditPopup>
) : (
<Stopwatch startedAt={stopwatch.startedAt} total={stopwatch.total} />
<Stopwatch
startedAt={stopwatch.startedAt}
total={stopwatch.total}
onCopyCard={onCopyCard}
/>
)}
</span>
{canEdit && (
@ -337,7 +355,11 @@ const CardModal = React.memo(
<Icon name="align justify" className={styles.moduleIcon} />
<div className={styles.moduleHeader}>{t('common.description')}</div>
{canEdit ? (
<DescriptionEdit defaultValue={description} onUpdate={handleDescriptionUpdate}>
<DescriptionEdit
defaultValue={description}
onUpdate={handleDescriptionUpdate}
onCopyCard={onCopyCard}
>
{description ? (
<button
type="button"
@ -377,6 +399,7 @@ const CardModal = React.memo(
onUpdate={onTaskUpdate}
onMove={onTaskMove}
onDelete={onTaskDelete}
onCopyCard={onCopyCard}
/>
</div>
</div>
@ -394,6 +417,7 @@ const CardModal = React.memo(
onCoverUpdate={handleCoverUpdate}
onGalleryOpen={handleGalleryOpen}
onGalleryClose={handleGalleryClose}
onCopyCard={onCopyCard}
/>
</div>
</div>
@ -411,6 +435,7 @@ const CardModal = React.memo(
onCommentCreate={onCommentActivityCreate}
onCommentUpdate={onCommentActivityUpdate}
onCommentDelete={onCommentActivityDelete}
onCopyCard={onCopyCard}
/>
</Grid.Column>
{canEdit && (
@ -422,6 +447,7 @@ const CardModal = React.memo(
currentUserIds={userIds}
onUserSelect={onUserAdd}
onUserDeselect={onUserRemove}
onCopyCard={onCopyCard}
>
<Button fluid className={styles.actionButton}>
<Icon name="user outline" className={styles.actionIcon} />
@ -437,13 +463,18 @@ const CardModal = React.memo(
onUpdate={onLabelUpdate}
onMove={onLabelMove}
onDelete={onLabelDelete}
onCopyCard={onCopyCard}
>
<Button fluid className={styles.actionButton}>
<Icon name="bookmark outline" className={styles.actionIcon} />
{t('common.labels')}
</Button>
</LabelsPopup>
<DueDateEditPopup defaultValue={dueDate} onUpdate={handleDueDateUpdate}>
<DueDateEditPopup
defaultValue={dueDate}
onUpdate={handleDueDateUpdate}
onCopyCard={onCopyCard}
>
<Button fluid className={styles.actionButton}>
<Icon name="calendar check outline" className={styles.actionIcon} />
{t('common.dueDate', {
@ -451,13 +482,17 @@ const CardModal = React.memo(
})}
</Button>
</DueDateEditPopup>
<StopwatchEditPopup defaultValue={stopwatch} onUpdate={handleStopwatchUpdate}>
<StopwatchEditPopup
defaultValue={stopwatch}
onUpdate={handleStopwatchUpdate}
onCopyCard={onCopyCard}
>
<Button fluid className={styles.actionButton}>
<Icon name="clock outline" className={styles.actionIcon} />
{t('common.stopwatch')}
</Button>
</StopwatchEditPopup>
<AttachmentAddPopup onCreate={onAttachmentCreate}>
<AttachmentAddPopup onCreate={onAttachmentCreate} onCopyCard={onCopyCard}>
<Button fluid className={styles.actionButton}>
<Icon name="attach" className={styles.actionIcon} />
{t('common.attachment')}
@ -484,6 +519,7 @@ const CardModal = React.memo(
onMove={onMove}
onTransfer={onTransfer}
onBoardFetch={onBoardFetch}
onCopyCard={onCopyCard}
>
<Button
fluid
@ -510,11 +546,12 @@ const CardModal = React.memo(
onMove={onMove}
onTransfer={onTransfer}
onBoardFetch={onBoardFetch}
onCopyCard={onCopyCard}
>
<Button
fluid
className={styles.actionButton}
// onClick={handleToggleSubscriptionClick}
onClick={handleToggleSubscriptionClick}
>
<Icon name="copy outline" className={styles.actionIcon} />
{t('action.copy')}
@ -602,6 +639,7 @@ CardModal.propTypes = {
onCommentActivityUpdate: PropTypes.func.isRequired,
onCommentActivityDelete: PropTypes.func.isRequired,
onClose: PropTypes.func.isRequired,
onCopyCard: PropTypes.func.isRequired,
};
CardModal.defaultProps = {

View file

@ -9,7 +9,16 @@ import { useForm } from '../../hooks';
import styles from './CardMoveStep.module.scss';
const CardMoveStep = React.memo(
({ projectsToLists, defaultPath, onMove, onTransfer, onBoardFetch, onBack, onClose }) => {
({
projectsToLists,
defaultPath,
onMove,
onTransfer,
onBoardFetch,
onBack,
onClose,
onCopyCard,
}) => {
const [t] = useTranslation();
const [path, handleFieldChange] = useForm(() => ({
@ -103,6 +112,7 @@ const CardMoveStep = React.memo(
disabled={selectedProject.boards.length === 0}
className={styles.field}
onChange={handleBoardIdChange}
onCopyCard={onCopyCard}
/>
</>
)}
@ -127,6 +137,7 @@ const CardMoveStep = React.memo(
disabled={selectedBoard.isFetching !== false || selectedBoard.lists.length === 0}
className={styles.field}
onChange={handleFieldChange}
onCopyCard={onCopyCard}
/>
</>
)}
@ -152,6 +163,7 @@ CardMoveStep.propTypes = {
onBoardFetch: PropTypes.func.isRequired,
onBack: PropTypes.func,
onClose: PropTypes.func.isRequired,
onCopyCard: PropTypes.func.isRequired,
};
CardMoveStep.defaultProps = {

View file

@ -32,6 +32,7 @@ const LabelsStep = React.memo(
onMove,
onDelete,
onBack,
onCopyCard,
}) => {
const [t] = useTranslation();
const [step, openStep, handleBack] = useSteps();
@ -118,6 +119,7 @@ const LabelsStep = React.memo(
}}
onCreate={onCreate}
onBack={handleBack}
onCopyCard={onCopyCard}
/>
);
case StepTypes.EDIT: {
@ -130,6 +132,7 @@ const LabelsStep = React.memo(
onUpdate={(data) => handleUpdate(currentItem.id, data)}
onDelete={() => handleDelete(currentItem.id)}
onBack={handleBack}
onCopyCard={onCopyCard}
/>
);
}
@ -180,6 +183,7 @@ const LabelsStep = React.memo(
onSelect={() => handleSelect(item.id)}
onDeselect={() => handleDeselect(item.id)}
onEdit={() => handleEdit(item.id)}
onCopyCard={onCopyCard}
/>
))}
{placeholder}
@ -227,6 +231,7 @@ LabelsStep.propTypes = {
onMove: PropTypes.func.isRequired,
onDelete: PropTypes.func.isRequired,
onBack: PropTypes.func,
onCopyCard: PropTypes.func.isRequired,
};
LabelsStep.defaultProps = {

View file

@ -12,11 +12,23 @@ import NameEdit from './NameEdit';
import CardAdd from './CardAdd';
import ActionsStep from './ActionsStep';
import { ReactComponent as PlusMathIcon } from '../../assets/images/plus-math-icon.svg';
import CardCopyStep from '../CardCopyStep';
import styles from './List.module.scss';
const List = React.memo(
({ id, index, name, isPersisted, cardIds, canEdit, onUpdate, onDelete, onCardCreate }) => {
({
id,
index,
name,
isPersisted,
cardIds,
canEdit,
onUpdate,
onDelete,
onCardCreate,
onCopyCard,
}) => {
const [t] = useTranslation();
const [isAddCardOpened, setIsAddCardOpened] = useState(false);
const [selectedOption, setSelectedOption] = useState('name');
@ -123,6 +135,7 @@ const List = React.memo(
onSort={onSort}
selectedOption={selectedOption}
setSelectedOption={setSelectedOption}
onCopyCard={onCopyCard}
>
<Button className={classNames(styles.headerButton, styles.target)}>
<Icon fitted name="pencil" size="small" />
@ -170,6 +183,7 @@ List.propTypes = {
onUpdate: PropTypes.func.isRequired,
onDelete: PropTypes.func.isRequired,
onCardCreate: PropTypes.func.isRequired,
onCopyCard: PropTypes.func.isRequired,
};
export default List;

View file

@ -197,6 +197,8 @@ export default {
CARD_DELETE__SUCCESS: 'CARD_DELETE__SUCCESS',
CARD_DELETE__FAILURE: 'CARD_DELETE__FAILURE',
CARD_DELETE_HANDLE: 'CARD_DELETE_HANDLE',
CARD_COPY_HANDLE: 'CARD_COPY_HANDLE',
CARD_COPY: 'CARD_COPY',
/* Tasks */

View file

@ -136,6 +136,8 @@ export default {
CARD_DELETE: `${PREFIX}/CARD_DELETE`,
CURRENT_CARD_DELETE: `${PREFIX}/CURRENT_CARD_DELETE`,
CARD_DELETE_HANDLE: `${PREFIX}/CARD_DELETE_HANDLE`,
CARD_COPY: `${PREFIX}/CARD_COPY`,
CARD_COPY_HANDLE: `${PREFIX}/CARD_COPY_HANDLE`,
/* Tasks */

View file

@ -12,4 +12,5 @@ export const ActivityTypes = {
CREATE_CARD: 'createCard',
MOVE_CARD: 'moveCard',
COMMENT_CARD: 'commentCard',
COPY_CARD: 'copyCard',
};

View file

@ -27,6 +27,7 @@ const mapDispatchToProps = (dispatch) =>
onListCreate: entryActions.createListInCurrentBoard,
onListMove: entryActions.moveList,
onCardMove: entryActions.moveCard,
onCardCopy: entryActions.copyCard,
},
dispatch,
);

View file

@ -25,6 +25,7 @@ const mapDispatchToProps = (dispatch) =>
onMove: entryActions.moveBoard,
onDelete: entryActions.deleteBoard,
onSort: entryActions.sortBoard,
onCopyCard: entryActions.createCard,
},
dispatch,
);

View file

@ -72,6 +72,7 @@ const mapDispatchToProps = (dispatch, { id }) =>
onLabelUpdate: (labelId, data) => entryActions.updateLabel(labelId, data),
onLabelMove: (labelId, index) => entryActions.moveLabel(labelId, index),
onLabelDelete: (labelId) => entryActions.deleteLabel(labelId),
onCopyCard: (listId, data) => entryActions.createCard(listId, data, false),
},
dispatch,
);

View file

@ -100,6 +100,7 @@ const mapDispatchToProps = (dispatch) =>
onCommentActivityCreate: entryActions.createCommentActivityInCurrentCard,
onCommentActivityUpdate: entryActions.updateCommentActivity,
onCommentActivityDelete: entryActions.deleteCommentActivity,
onCopyCard: entryActions.copyCard,
push,
},
dispatch,

View file

@ -47,6 +47,15 @@ const moveCard = (id, listId, index = 0) => ({
},
});
const copyCard = (id, listId, index = 0) => ({
type: EntryActionTypes.CARD_COPY,
payload: {
id,
listId,
index,
},
});
const moveCurrentCard = (listId, index = 0) => ({
type: EntryActionTypes.CURRENT_CARD_MOVE,
payload: {
@ -106,4 +115,5 @@ export default {
deleteCard,
deleteCurrentCard,
handleCardDelete,
copyCard,
};

View file

@ -0,0 +1,109 @@
import { ResizeObserver } from '@juggle/resize-observer';
import React, { useCallback, useRef, useState } from 'react';
import PropTypes from 'prop-types';
import { Button, Popup as SemanticUIPopup } from 'semantic-ui-react';
import styles from './Popup.module.css';
export default (WrappedComponent, defaultProps) => {
const Popup = React.memo(({ children, ...props }) => {
const [isOpened, setIsOpened] = useState(false);
const wrapper = useRef(null);
const resizeObserver = useRef(null);
const handleOpen = useCallback(() => {
setIsOpened(true);
}, []);
const handleClose = useCallback(() => {
setIsOpened(false);
}, []);
const handleMouseDown = useCallback((event) => {
event.stopPropagation();
}, []);
const handleClick = useCallback((event) => {
event.stopPropagation();
}, []);
const handleTriggerClick = useCallback(
(event) => {
event.stopPropagation();
const { onClick } = children;
if (onClick) {
onClick(event);
}
},
[children],
);
const handleContentRef = useCallback((element) => {
if (resizeObserver.current) {
resizeObserver.current.disconnect();
}
if (!element) {
resizeObserver.current = null;
return;
}
resizeObserver.current = new ResizeObserver(() => {
if (resizeObserver.current.isInitial) {
resizeObserver.current.isInitial = false;
return;
}
wrapper.current.positionUpdate();
});
resizeObserver.current.isInitial = true;
resizeObserver.current.observe(element);
}, []);
const tigger = React.cloneElement(children, {
onClick: handleTriggerClick,
});
return (
<SemanticUIPopup
basic
wide
ref={wrapper}
trigger={tigger}
on="click"
open={isOpened}
position="bottom left"
popperModifiers={[
{
name: 'preventOverflow',
options: {
boundariesElement: 'window',
},
},
]}
className={styles.wrapper}
onOpen={handleOpen}
onClose={handleClose}
onMouseDown={handleMouseDown}
onClick={handleClick}
{...defaultProps} // eslint-disable-line react/jsx-props-no-spreading
>
<div ref={handleContentRef}>
<Button icon="close" onClick={handleClose} className={styles.closeButton} />
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
<WrappedComponent {...props} onClose={handleClose} />
</div>
</SemanticUIPopup>
);
});
Popup.propTypes = {
children: PropTypes.node.isRequired,
};
return Popup;
};

View file

@ -37,5 +37,9 @@ export default function* cardsWatchers() {
takeEvery(EntryActionTypes.CARD_DELETE_HANDLE, ({ payload: { card } }) =>
services.handleCardDelete(card),
),
takeEvery(EntryActionTypes.CARD_COPY, ({ payload: { listId, data, autoOpen } }) =>
services.createCard(listId, data, autoOpen),
),
]);
}

View file

@ -9,6 +9,7 @@ const Types = {
CREATE_CARD: 'createCard',
MOVE_CARD: 'moveCard',
COMMENT_CARD: 'commentCard',
COPY_CARD: 'copyCard',
};
module.exports = {