@@ -1,32 +1,155 @@
|
||||
import { call, put, select } from 'redux-saga/effects';
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import { call, fork, join, put, race, select, take } from 'redux-saga/effects';
|
||||
import { LOCATION_CHANGE_HANDLE } from '../../../lib/redux-router';
|
||||
|
||||
import { goToBoard, goToCard } from './router';
|
||||
import request from '../request';
|
||||
import selectors from '../../../selectors';
|
||||
import actions from '../../../actions';
|
||||
import api from '../../../api';
|
||||
import i18n from '../../../i18n';
|
||||
import { createLocalId } from '../../../utils/local-id';
|
||||
import { isListArchiveOrTrash, isListFinite } from '../../../utils/record-helpers';
|
||||
import ActionTypes from '../../../constants/ActionTypes';
|
||||
import { BoardViews, ListTypes } from '../../../constants/Enums';
|
||||
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
const _preloadImage = (url) =>
|
||||
new Promise((resolve) => {
|
||||
const image = new Image();
|
||||
|
||||
image.onload = resolve;
|
||||
image.onerror = resolve;
|
||||
|
||||
image.src = url;
|
||||
});
|
||||
|
||||
export function* fetchCards(listId) {
|
||||
const { boardId, lastCard } = yield select(selectors.selectListById, listId);
|
||||
const { search } = yield select(selectors.selectBoardById, boardId);
|
||||
const filterUserIds = yield select(selectors.selectFilterUserIdsForCurrentBoard);
|
||||
const filterLabelIds = yield select(selectors.selectFilterLabelIdsForCurrentBoard);
|
||||
|
||||
function* getCardsRequest() {
|
||||
const response = {};
|
||||
|
||||
try {
|
||||
response.body = yield call(request, api.getCards, listId, {
|
||||
search: (search && search.trim()) || undefined,
|
||||
filterUserIds: filterUserIds.length > 0 ? filterUserIds.join(',') : undefined,
|
||||
filterLabelIds: filterLabelIds.length > 0 ? filterLabelIds.join(',') : undefined,
|
||||
before: lastCard || undefined,
|
||||
});
|
||||
} catch (error) {
|
||||
response.error = error;
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
yield put(actions.fetchCards(listId));
|
||||
|
||||
const getCardsRequestTask = yield fork(getCardsRequest);
|
||||
|
||||
const [response] = yield race([
|
||||
join(getCardsRequestTask),
|
||||
take((action) => action.type === ActionTypes.CARDS_FETCH && action.payload.listId === listId),
|
||||
]);
|
||||
|
||||
if (!response) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.error) {
|
||||
yield put(actions.fetchCards.failure(listId, response.error));
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
body: {
|
||||
items: cards,
|
||||
included: {
|
||||
users,
|
||||
cardMemberships,
|
||||
cardLabels,
|
||||
taskLists,
|
||||
tasks,
|
||||
attachments,
|
||||
customFieldGroups,
|
||||
customFields,
|
||||
customFieldValues,
|
||||
},
|
||||
},
|
||||
} = response;
|
||||
|
||||
yield put(
|
||||
actions.fetchCards.success(
|
||||
listId,
|
||||
cards,
|
||||
users,
|
||||
cardMemberships,
|
||||
cardLabels,
|
||||
taskLists,
|
||||
tasks,
|
||||
attachments,
|
||||
customFieldGroups,
|
||||
customFields,
|
||||
customFieldValues,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
export function* fetchCardsInCurrentList() {
|
||||
const currentListId = yield select(selectors.selectCurrentListId);
|
||||
|
||||
yield call(fetchCards, currentListId);
|
||||
}
|
||||
|
||||
export function* handleCardsUpdate(cards, activities) {
|
||||
yield put(actions.handleCardsUpdate(cards, activities));
|
||||
}
|
||||
|
||||
export function* createCard(listId, data, autoOpen) {
|
||||
const { boardId } = yield select(selectors.selectListById, listId);
|
||||
const localId = yield call(createLocalId);
|
||||
const list = yield select(selectors.selectListById, listId);
|
||||
|
||||
const currentUserMembership = yield select(
|
||||
selectors.selectCurrentUserMembershipByBoardId,
|
||||
list.boardId,
|
||||
);
|
||||
|
||||
const nextData = {
|
||||
...data,
|
||||
position: yield select(selectors.selectNextCardPosition, listId),
|
||||
};
|
||||
|
||||
const localId = yield call(createLocalId);
|
||||
if (isListFinite(list)) {
|
||||
nextData.position = yield select(selectors.selectNextCardPosition, listId);
|
||||
}
|
||||
|
||||
yield put(
|
||||
actions.createCard({
|
||||
...nextData,
|
||||
boardId,
|
||||
listId,
|
||||
id: localId,
|
||||
}),
|
||||
actions.createCard(
|
||||
{
|
||||
...nextData,
|
||||
listId,
|
||||
id: localId,
|
||||
boardId: list.boardId,
|
||||
creatorUserId: currentUserMembership.userId,
|
||||
},
|
||||
autoOpen,
|
||||
),
|
||||
);
|
||||
|
||||
// TODO: use race instead
|
||||
let watchForCreateCardActionTask;
|
||||
if (autoOpen) {
|
||||
watchForCreateCardActionTask = yield fork(function* watchForCreateCardAction() {
|
||||
yield take((action) => action.type === ActionTypes.CARD_CREATE && action.payload.autoOpen);
|
||||
});
|
||||
}
|
||||
|
||||
let card;
|
||||
try {
|
||||
({ item: card } = yield call(request, api.createCard, listId, nextData));
|
||||
@@ -37,32 +160,94 @@ export function* createCard(listId, data, autoOpen) {
|
||||
|
||||
yield put(actions.createCard.success(localId, card));
|
||||
|
||||
if (autoOpen) {
|
||||
if (watchForCreateCardActionTask && watchForCreateCardActionTask.isRunning()) {
|
||||
yield call(goToCard, card.id);
|
||||
}
|
||||
}
|
||||
|
||||
export function* handleCardCreate({ id }) {
|
||||
let card;
|
||||
export function* createCardInCurrentList(data, autoOpen) {
|
||||
const currentListId = yield select(selectors.selectCurrentListId);
|
||||
|
||||
yield call(createCard, currentListId, data, autoOpen);
|
||||
}
|
||||
|
||||
export function* createCardInFirstFiniteList(data, autoOpen) {
|
||||
const firstFiniteListId = yield select(selectors.selectFirstFiniteListId);
|
||||
|
||||
yield call(createCard, firstFiniteListId, data, autoOpen);
|
||||
}
|
||||
|
||||
export function* handleCardCreate(card) {
|
||||
let users;
|
||||
let cardMemberships;
|
||||
let cardLabels;
|
||||
let taskLists;
|
||||
let tasks;
|
||||
let attachments;
|
||||
let customFieldGroups;
|
||||
let customFields;
|
||||
let customFieldValues;
|
||||
|
||||
try {
|
||||
({
|
||||
item: card,
|
||||
included: { cardMemberships, cardLabels, tasks, attachments },
|
||||
} = yield call(request, api.getCard, id));
|
||||
} catch (error) {
|
||||
item: card, // eslint-disable-line no-param-reassign
|
||||
included: {
|
||||
users,
|
||||
cardMemberships,
|
||||
cardLabels,
|
||||
taskLists,
|
||||
tasks,
|
||||
attachments,
|
||||
customFieldGroups,
|
||||
customFields,
|
||||
customFieldValues,
|
||||
},
|
||||
} = yield call(request, api.getCard, card.id));
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
yield put(actions.handleCardCreate(card, cardMemberships, cardLabels, tasks, attachments));
|
||||
yield put(
|
||||
actions.handleCardCreate(
|
||||
card,
|
||||
users,
|
||||
cardMemberships,
|
||||
cardLabels,
|
||||
taskLists,
|
||||
tasks,
|
||||
attachments,
|
||||
customFieldGroups,
|
||||
customFields,
|
||||
customFieldValues,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
export function* updateCard(id, data) {
|
||||
yield put(actions.updateCard(id, data));
|
||||
let prevListId;
|
||||
if (data.listId) {
|
||||
const list = yield select(selectors.selectListById, data.listId);
|
||||
|
||||
const card = yield select(selectors.selectCardById, id);
|
||||
const prevList = yield select(selectors.selectListById, card.listId);
|
||||
|
||||
if (prevList.type === ListTypes.TRASH) {
|
||||
prevListId = null;
|
||||
} else if (isListArchiveOrTrash(list)) {
|
||||
prevListId = prevList.id;
|
||||
} else if (prevList.type === ListTypes.ARCHIVE) {
|
||||
prevListId = null;
|
||||
}
|
||||
}
|
||||
|
||||
yield put(
|
||||
actions.updateCard(id, {
|
||||
...data,
|
||||
...(prevListId !== undefined && {
|
||||
prevListId,
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
let card;
|
||||
try {
|
||||
@@ -82,59 +267,142 @@ export function* updateCurrentCard(data) {
|
||||
}
|
||||
|
||||
export function* handleCardUpdate(card) {
|
||||
const { cardId, boardId } = yield select(selectors.selectPath);
|
||||
|
||||
let fetch = false;
|
||||
if (card.boardId) {
|
||||
const prevCard = yield select(selectors.selectCardById, card.id);
|
||||
fetch = !prevCard || prevCard.boardId !== card.boardId;
|
||||
const isAvailableForCurrentUser = yield select(
|
||||
selectors.selectIsCardWithIdAvailableForCurrentUser,
|
||||
card.id,
|
||||
);
|
||||
|
||||
fetch = !isAvailableForCurrentUser;
|
||||
}
|
||||
|
||||
let users;
|
||||
let cardMemberships;
|
||||
let cardLabels;
|
||||
let taskLists;
|
||||
let tasks;
|
||||
let attachments;
|
||||
let customFieldGroups;
|
||||
let customFields;
|
||||
let customFieldValues;
|
||||
|
||||
if (fetch) {
|
||||
try {
|
||||
({
|
||||
item: card, // eslint-disable-line no-param-reassign
|
||||
included: { cardMemberships, cardLabels, tasks, attachments },
|
||||
included: {
|
||||
users,
|
||||
cardMemberships,
|
||||
cardLabels,
|
||||
taskLists,
|
||||
tasks,
|
||||
attachments,
|
||||
customFieldGroups,
|
||||
customFields,
|
||||
customFieldValues,
|
||||
},
|
||||
} = yield call(request, api.getCard, card.id));
|
||||
} catch (error) {
|
||||
} catch {
|
||||
fetch = false;
|
||||
}
|
||||
}
|
||||
|
||||
yield put(actions.handleCardUpdate(card, fetch, cardMemberships, cardLabels, tasks, attachments));
|
||||
yield put(
|
||||
actions.handleCardUpdate(
|
||||
card,
|
||||
fetch,
|
||||
users,
|
||||
cardMemberships,
|
||||
cardLabels,
|
||||
taskLists,
|
||||
tasks,
|
||||
attachments,
|
||||
customFieldGroups,
|
||||
customFields,
|
||||
customFieldValues,
|
||||
),
|
||||
);
|
||||
|
||||
if (card.boardId === null && card.id === cardId) {
|
||||
yield call(goToBoard, boardId);
|
||||
}
|
||||
}
|
||||
|
||||
export function* moveCard(id, listId, index = 0) {
|
||||
const position = yield select(selectors.selectNextCardPosition, listId, index, id);
|
||||
export function* moveCard(id, listId, index) {
|
||||
const data = {};
|
||||
if (listId) {
|
||||
data.listId = listId;
|
||||
} else {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
({ listId } = yield select(selectors.selectCardById, id));
|
||||
}
|
||||
|
||||
yield call(updateCard, id, {
|
||||
listId,
|
||||
position,
|
||||
});
|
||||
const list = yield select(selectors.selectListById, listId);
|
||||
|
||||
if (isListFinite(list)) {
|
||||
data.position = yield select(selectors.selectNextCardPosition, listId, index, id);
|
||||
}
|
||||
|
||||
yield call(updateCard, id, data);
|
||||
}
|
||||
|
||||
export function* moveCurrentCard(listId, index) {
|
||||
const { cardId } = yield select(selectors.selectPath);
|
||||
export function* moveCurrentCard(listId, index, autoClose) {
|
||||
const { cardId, boardId } = yield select(selectors.selectPath);
|
||||
|
||||
if (autoClose) {
|
||||
yield call(goToBoard, boardId);
|
||||
}
|
||||
|
||||
yield call(moveCard, cardId, listId, index);
|
||||
}
|
||||
|
||||
export function* transferCard(id, boardId, listId, index = 0) {
|
||||
const { cardId: currentCardId, boardId: currentBoardId } = yield select(selectors.selectPath);
|
||||
const position = yield select(selectors.selectNextCardPosition, listId, index, id);
|
||||
export function* moveCardToArchive(id) {
|
||||
const archiveListId = yield select(selectors.selectArchiveListIdForCurrentBoard);
|
||||
|
||||
yield call(moveCard, id, archiveListId);
|
||||
}
|
||||
|
||||
export function* moveCurrentCardToArchive() {
|
||||
const archiveListId = yield select(selectors.selectArchiveListIdForCurrentBoard);
|
||||
|
||||
yield call(moveCurrentCard, archiveListId, undefined, true);
|
||||
}
|
||||
|
||||
export function* moveCardToTrash(id) {
|
||||
const trashListId = yield select(selectors.selectTrashListIdForCurrentBoard);
|
||||
|
||||
yield call(moveCard, id, trashListId);
|
||||
}
|
||||
|
||||
export function* moveCurrentCardToTrash() {
|
||||
const trashListId = yield select(selectors.selectTrashListIdForCurrentBoard);
|
||||
|
||||
yield call(moveCurrentCard, trashListId, undefined, true);
|
||||
}
|
||||
|
||||
export function* transferCard(id, boardId, listId, index) {
|
||||
const { cardId: currentCardId, boardId: currentBoardId } = yield select(selectors.selectPath);
|
||||
|
||||
// TODO: hack?
|
||||
if (id === currentCardId) {
|
||||
yield call(goToBoard, currentBoardId);
|
||||
}
|
||||
|
||||
yield call(updateCard, id, {
|
||||
boardId,
|
||||
const list = yield select(selectors.selectListById, listId);
|
||||
|
||||
const data = {
|
||||
listId,
|
||||
position,
|
||||
});
|
||||
boardId,
|
||||
};
|
||||
|
||||
if (isListFinite(list)) {
|
||||
data.position = yield select(selectors.selectNextCardPosition, listId, index, id);
|
||||
}
|
||||
|
||||
yield call(updateCard, id, data);
|
||||
}
|
||||
|
||||
export function* transferCurrentCard(boardId, listId, index) {
|
||||
@@ -143,64 +411,142 @@ export function* transferCurrentCard(boardId, listId, index) {
|
||||
yield call(transferCard, cardId, boardId, listId, index);
|
||||
}
|
||||
|
||||
export function* duplicateCard(id) {
|
||||
const { listId, name } = yield select(selectors.selectCardById, id);
|
||||
export function* duplicateCard(id, data) {
|
||||
const localId = yield call(createLocalId);
|
||||
const { boardId, listId } = yield select(selectors.selectCardById, id);
|
||||
const index = yield select(selectors.selectCardIndexById, id);
|
||||
|
||||
const currentUserMembership = yield select(
|
||||
selectors.selectCurrentUserMembershipByBoardId,
|
||||
boardId,
|
||||
);
|
||||
|
||||
const nextData = {
|
||||
...data,
|
||||
position: yield select(selectors.selectNextCardPosition, listId, index + 1),
|
||||
name: `${name} (${i18n.t('common.copy', {
|
||||
context: 'inline',
|
||||
})})`,
|
||||
};
|
||||
|
||||
const localId = yield call(createLocalId);
|
||||
const taskIds = yield select(selectors.selectTaskIdsByCardId, id);
|
||||
|
||||
yield put(
|
||||
actions.duplicateCard(
|
||||
id,
|
||||
{
|
||||
...nextData,
|
||||
id: localId,
|
||||
},
|
||||
taskIds,
|
||||
),
|
||||
actions.duplicateCard(id, localId, {
|
||||
...nextData,
|
||||
creatorUserId: currentUserMembership.userId,
|
||||
}),
|
||||
);
|
||||
|
||||
let card;
|
||||
let cardMemberships;
|
||||
let cardLabels;
|
||||
let taskLists;
|
||||
let tasks;
|
||||
let attachments;
|
||||
let customFieldGroups;
|
||||
let customFields;
|
||||
let customFieldValues;
|
||||
|
||||
try {
|
||||
({
|
||||
item: card,
|
||||
included: { cardMemberships, cardLabels, tasks },
|
||||
included: {
|
||||
cardMemberships,
|
||||
cardLabels,
|
||||
taskLists,
|
||||
tasks,
|
||||
attachments,
|
||||
customFieldGroups,
|
||||
customFields,
|
||||
customFieldValues,
|
||||
},
|
||||
} = yield call(request, api.duplicateCard, id, nextData));
|
||||
} catch (error) {
|
||||
yield put(actions.duplicateCard.failure(localId, error));
|
||||
return;
|
||||
}
|
||||
|
||||
yield put(actions.duplicateCard.success(localId, card, cardMemberships, cardLabels, tasks));
|
||||
if (card.coverAttachmentId) {
|
||||
const coverAttachment = attachments.find(
|
||||
(attachment) => attachment.id === card.coverAttachmentId,
|
||||
);
|
||||
|
||||
if (coverAttachment) {
|
||||
yield call(_preloadImage, coverAttachment.data.thumbnailUrls.outside360);
|
||||
}
|
||||
}
|
||||
|
||||
yield put(
|
||||
actions.duplicateCard.success(
|
||||
localId,
|
||||
card,
|
||||
cardMemberships,
|
||||
cardLabels,
|
||||
taskLists,
|
||||
tasks,
|
||||
attachments,
|
||||
customFieldGroups,
|
||||
customFields,
|
||||
customFieldValues,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
export function* duplicateCurrentCard() {
|
||||
export function* duplicateCurrentCard(data) {
|
||||
const { cardId } = yield select(selectors.selectPath);
|
||||
|
||||
yield call(duplicateCard, cardId);
|
||||
yield call(duplicateCard, cardId, data);
|
||||
}
|
||||
|
||||
export function* goToAdjacentCard(direction) {
|
||||
const card = yield select(selectors.selectCurrentCard);
|
||||
const list = yield select(selectors.selectListById, card.listId);
|
||||
|
||||
let cardIds;
|
||||
if (isListFinite(list)) {
|
||||
const { view } = yield select(selectors.selectCurrentBoard);
|
||||
|
||||
if (view === BoardViews.KANBAN) {
|
||||
cardIds = yield select(selectors.selectFilteredCardIdsByListId, card.listId);
|
||||
} else {
|
||||
cardIds = yield select(selectors.selectFilteredCardIdsForCurrentBoard);
|
||||
}
|
||||
} else {
|
||||
cardIds = yield select(selectors.selectFilteredCardIdsByListId, card.listId);
|
||||
|
||||
if (direction === 1 && card.id === cardIds[cardIds.length - 1]) {
|
||||
if (list.isCardsFetching || list.isAllCardsFetched) {
|
||||
return;
|
||||
}
|
||||
|
||||
const [, cancelled] = yield race([call(fetchCards, list.id), take(LOCATION_CHANGE_HANDLE)]);
|
||||
|
||||
if (cancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
cardIds = yield select(selectors.selectFilteredCardIdsByListId, card.listId);
|
||||
}
|
||||
}
|
||||
|
||||
const index = cardIds.indexOf(card.id);
|
||||
|
||||
if (index === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const adjacentCardId = cardIds[index + direction];
|
||||
|
||||
if (adjacentCardId) {
|
||||
yield call(goToCard, adjacentCardId);
|
||||
}
|
||||
}
|
||||
|
||||
export function* deleteCard(id) {
|
||||
const { cardId, boardId } = yield select(selectors.selectPath);
|
||||
|
||||
yield put(actions.deleteCard(id));
|
||||
|
||||
if (id === cardId) {
|
||||
yield call(goToBoard, boardId);
|
||||
}
|
||||
|
||||
yield put(actions.deleteCard(id));
|
||||
|
||||
let card;
|
||||
try {
|
||||
({ item: card } = yield call(request, api.deleteCard, id));
|
||||
@@ -219,35 +565,38 @@ export function* deleteCurrentCard() {
|
||||
}
|
||||
|
||||
export function* handleCardDelete(card) {
|
||||
const { cardId, boardId } = yield select(selectors.selectPath);
|
||||
|
||||
if (card.id === cardId) {
|
||||
yield call(goToBoard, boardId);
|
||||
}
|
||||
const { cardId } = yield select(selectors.selectPath);
|
||||
|
||||
yield put(actions.handleCardDelete(card));
|
||||
}
|
||||
|
||||
export function* handleTextFilter(text) {
|
||||
const { boardId } = yield select(selectors.selectPath);
|
||||
|
||||
yield put(actions.filterText(boardId, text));
|
||||
if (card.id === cardId) {
|
||||
yield call(goToBoard, card.boardId);
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
fetchCards,
|
||||
fetchCardsInCurrentList,
|
||||
handleCardsUpdate,
|
||||
createCard,
|
||||
createCardInCurrentList,
|
||||
handleCardCreate,
|
||||
createCardInFirstFiniteList,
|
||||
updateCard,
|
||||
updateCurrentCard,
|
||||
handleCardUpdate,
|
||||
moveCard,
|
||||
moveCurrentCard,
|
||||
moveCardToArchive,
|
||||
moveCurrentCardToArchive,
|
||||
moveCardToTrash,
|
||||
moveCurrentCardToTrash,
|
||||
transferCard,
|
||||
transferCurrentCard,
|
||||
duplicateCard,
|
||||
duplicateCurrentCard,
|
||||
goToAdjacentCard,
|
||||
deleteCard,
|
||||
deleteCurrentCard,
|
||||
handleCardDelete,
|
||||
handleTextFilter,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user