feat: Version 2

Closes #627, closes #1047
This commit is contained in:
Maksim Eltyshev
2025-05-10 02:09:06 +02:00
parent ad7fb51cfa
commit 2ee1166747
1557 changed files with 76832 additions and 47042 deletions
+14 -4
View File
@@ -1,3 +1,8 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import { all, apply, fork, select, take } from 'redux-saga/effects';
import watchers from './watchers';
@@ -18,9 +23,14 @@ export default function* coreSaga() {
const oidcConfig = yield select(selectors.selectOidcConfig);
if (oidcConfig && oidcConfig.endSessionUrl !== null) {
// Redirect the user to the IDP to log out.
window.location.href = oidcConfig.endSessionUrl;
} else {
window.location.href = Paths.LOGIN;
const currentUser = yield select(selectors.selectCurrentUser);
if (!currentUser || currentUser.isSsoUser) {
// Redirect the user to the IDP to log out.
window.location.href = oidcConfig.endSessionUrl;
return;
}
}
window.location.href = Paths.LOGIN;
}
+10 -3
View File
@@ -1,4 +1,9 @@
import { call, fork, join, put, select, take } 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, join, put, select, spawn, take } from 'redux-saga/effects';
import selectors from '../../selectors';
import entryActions from '../../entry-actions';
@@ -10,7 +15,9 @@ function* queueRequest(method, ...args) {
if (lastRequestTask) {
try {
yield join(lastRequestTask);
} catch {} // eslint-disable-line no-empty
} catch {
/* empty */
}
}
const accessToken = yield select(selectors.selectAccessToken);
@@ -30,7 +37,7 @@ function* queueRequest(method, ...args) {
}
export default function* request(method, ...args) {
lastRequestTask = yield fork(queueRequest, method, ...args);
lastRequestTask = yield spawn(queueRequest, method, ...args);
return yield join(lastRequestTask);
}
+53 -15
View File
@@ -1,8 +1,14 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import { call, select } from 'redux-saga/effects';
import request from '../request';
import selectors from '../../../selectors';
import api from '../../../api';
import mergeRecords from '../../../utils/merge-records';
import Paths from '../../../constants/Paths';
export function* fetchBoardByCurrentPath() {
@@ -10,16 +16,29 @@ export function* fetchBoardByCurrentPath() {
let board;
let card;
let users;
let users1;
let users2;
let projects;
let boardMemberships;
let labels;
let lists;
let cards;
let cardMemberships;
let cardLabels;
let tasks;
let attachments;
let cardMemberships1;
let cardMemberships2;
let cardLabels1;
let cardLabels2;
let taskLists1;
let taskLists2;
let tasks1;
let tasks2;
let attachments1;
let attachments2;
let customFieldGroups1;
let customFieldGroups2;
let customFields1;
let customFields2;
let customFieldValues1;
let customFieldValues2;
if (pathsMatch) {
let boardId;
@@ -29,6 +48,17 @@ export function* fetchBoardByCurrentPath() {
({
item: card,
item: { boardId },
included: {
users: users1,
cardMemberships: cardMemberships1,
cardLabels: cardLabels1,
taskLists: taskLists1,
tasks: tasks1,
attachments: attachments1,
customFieldGroups: customFieldGroups1,
customFields: customFields1,
customFieldValues: customFieldValues1,
},
} = yield call(request, api.getCard, pathsMatch.params.id));
}
@@ -36,16 +66,20 @@ export function* fetchBoardByCurrentPath() {
({
item: board,
included: {
users,
projects,
boardMemberships,
labels,
lists,
cards,
cardMemberships,
cardLabels,
tasks,
attachments,
users: users2,
cardMemberships: cardMemberships2,
cardLabels: cardLabels2,
taskLists: taskLists2,
tasks: tasks2,
attachments: attachments2,
customFieldGroups: customFieldGroups2,
customFields: customFields2,
customFieldValues: customFieldValues2,
},
} = yield call(request, api.getBoard, boardId, true));
}
@@ -54,16 +88,20 @@ export function* fetchBoardByCurrentPath() {
return {
board,
card,
users,
boardMemberships,
labels,
lists,
cards,
cardMemberships,
cardLabels,
tasks,
attachments,
project: projects[0],
users: mergeRecords(users1, users2),
cardMemberships: mergeRecords(cardMemberships1, cardMemberships2),
cardLabels: mergeRecords(cardLabels1, cardLabels2),
taskLists: mergeRecords(taskLists1, taskLists2),
tasks: mergeRecords(tasks1, tasks2),
attachments: mergeRecords(attachments1, attachments2),
customFieldGroups: mergeRecords(customFieldGroups1, customFieldGroups2),
customFields: mergeRecords(customFields1, customFields2),
customFieldValues: mergeRecords(customFieldValues1, customFieldValues2),
};
}
+53 -17
View File
@@ -1,22 +1,44 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import { call } from 'redux-saga/effects';
import { fetchBoardByCurrentPath } from './boards';
import request from '../request';
import api from '../../../api';
import mergeRecords from '../../../utils/merge-records';
import { isUserAdminOrProjectOwner } from '../../../utils/record-helpers';
export function* fetchCore() {
const { item: user } = yield call(request, api.getCurrentUser, true);
const { items: users1 } = yield call(request, api.getUsers);
const {
item: user,
included: { notificationServices: notificationServices1 },
} = yield call(request, api.getCurrentUser, true);
let users1;
if (isUserAdminOrProjectOwner(user)) {
({ items: users1 } = yield call(request, api.getUsers));
}
const {
items: projects1,
included: { projectManagers, boards, boardMemberships: boardMemberships1 },
included: {
projectManagers,
backgroundImages,
baseCustomFieldGroups,
boards,
users: users2,
boardMemberships: boardMemberships1,
customFields: customFields1,
notificationServices: notificationServices2,
},
} = yield call(request, api.getProjects);
let board;
let card;
let users2;
let users3;
let projects2;
let boardMemberships2;
let labels;
@@ -24,43 +46,51 @@ export function* fetchCore() {
let cards1;
let cardMemberships;
let cardLabels;
let taskLists;
let tasks;
let attachments;
let customFieldGroups;
let customFields2;
let customFieldValues;
try {
({
board,
card,
users: users2,
projects: projects2,
boardMemberships: boardMemberships2,
labels,
lists,
cards: cards1,
cardMemberships,
cardLabels,
taskLists,
tasks,
attachments,
customFieldGroups,
customFieldValues,
users: users3,
projects: projects2,
boardMemberships: boardMemberships2,
cards: cards1,
customFields: customFields2,
} = yield call(fetchBoardByCurrentPath));
} catch {} // eslint-disable-line no-empty
} catch {
/* empty */
}
const body = yield call(request, api.getNotifications);
let { items: notifications } = body;
const {
included: { users: users3, cards: cards2, activities },
included: { users: users4 },
} = body;
if (card) {
const notificationIds = notifications.flatMap((notification) =>
notification.cardId === card.id ? [notification.id] : [],
notification.cardId === card.id ? notification.id : [],
);
if (notificationIds.length > 0) {
yield call(request, api.updateNotifications, notificationIds, {
isRead: true,
});
yield call(request, api.readCardNotifications, card.id);
notifications = notifications.filter(
(notification) => !notificationIds.includes(notification.id),
@@ -72,19 +102,25 @@ export function* fetchCore() {
user,
board,
projectManagers,
backgroundImages,
baseCustomFieldGroups,
boards,
labels,
lists,
cardMemberships,
cardLabels,
taskLists,
tasks,
attachments,
activities,
customFieldGroups,
customFieldValues,
notifications,
users: mergeRecords(users1, users2, users3),
users: mergeRecords(users1, users2, users3, users4),
projects: mergeRecords(projects1, projects2),
boardMemberships: mergeRecords(boardMemberships1, boardMemberships2),
cards: mergeRecords(cards1, cards2),
cards: mergeRecords(card && [card], cards1),
customFields: mergeRecords(customFields1, customFields2),
notificationServices: mergeRecords(notificationServices1, notificationServices2),
};
}
+5
View File
@@ -1,3 +1,8 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import core from './core';
import boards from './boards';
+7 -45
View File
@@ -1,3 +1,8 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import { call, put, select } from 'redux-saga/effects';
import request from '../request';
@@ -6,8 +11,7 @@ import actions from '../../../actions';
import api from '../../../api';
export function* fetchActivities(cardId) {
const { isActivitiesDetailsVisible } = yield select(selectors.selectCardById, cardId);
const lastId = yield select(selectors.selectLastActivityIdByCardId, cardId);
const { lastActivityId } = yield select(selectors.selectCardById, cardId);
yield put(actions.fetchActivities(cardId));
@@ -19,8 +23,7 @@ export function* fetchActivities(cardId) {
items: activities,
included: { users },
} = yield call(request, api.getActivities, cardId, {
beforeId: lastId,
withDetails: isActivitiesDetailsVisible,
beforeId: lastActivityId || undefined,
}));
} catch (error) {
yield put(actions.fetchActivities.failure(cardId, error));
@@ -36,53 +39,12 @@ export function* fetchActivitiesInCurrentCard() {
yield call(fetchActivities, cardId);
}
export function* toggleActivitiesDetails(cardId, isVisible) {
yield put(actions.toggleActivitiesDetails(cardId, isVisible));
if (isVisible) {
let activities;
let users;
try {
({
items: activities,
included: { users },
} = yield call(request, api.getActivities, cardId, {
withDetails: isVisible,
}));
} catch (error) {
yield put(actions.toggleActivitiesDetails.failure(cardId, error));
return;
}
yield put(actions.toggleActivitiesDetails.success(cardId, activities, users));
}
}
export function* toggleActivitiesDetailsInCurrentCard(isVisible) {
const { cardId } = yield select(selectors.selectPath);
yield call(toggleActivitiesDetails, cardId, isVisible);
}
export function* handleActivityCreate(activity) {
yield put(actions.handleActivityCreate(activity));
}
export function* handleActivityUpdate(activity) {
yield put(actions.handleActivityUpdate(activity));
}
export function* handleActivityDelete(activity) {
yield put(actions.handleActivityDelete(activity));
}
export default {
fetchActivities,
fetchActivitiesInCurrentCard,
toggleActivitiesDetails,
toggleActivitiesDetailsInCurrentCard,
handleActivityCreate,
handleActivityUpdate,
handleActivityDelete,
};
+21 -2
View File
@@ -1,3 +1,10 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import omit from 'lodash/omit';
import truncate from 'lodash/truncate';
import { call, put, select } from 'redux-saga/effects';
import request from '../request';
@@ -5,21 +12,33 @@ import selectors from '../../../selectors';
import actions from '../../../actions';
import api from '../../../api';
import { createLocalId } from '../../../utils/local-id';
import { AttachmentTypes } from '../../../constants/Enums';
export function* createAttachment(cardId, data) {
const localId = yield call(createLocalId);
const currentUserId = yield select(selectors.selectCurrentUserId);
const nextData = {
...data,
name: truncate(data.name, {
length: 128,
}),
};
yield put(
actions.createAttachment({
...omit(nextData, ['file', 'url']),
cardId,
id: localId,
name: data.file.name,
creatorUserId: currentUserId,
}),
);
let attachment;
try {
({ item: attachment } = yield call(request, api.createAttachment, cardId, data, localId));
({ item: attachment } = yield nextData.type === AttachmentTypes.FILE
? call(request, api.createAttachmentWithFile, cardId, nextData, localId)
: call(request, api.createAttachment, cardId, nextData));
} catch (error) {
yield put(actions.createAttachment.failure(localId, error));
return;
@@ -0,0 +1,81 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import omit from 'lodash/omit';
import { call, put, select } from 'redux-saga/effects';
import request from '../request';
import selectors from '../../../selectors';
import actions from '../../../actions';
import api from '../../../api';
import { createLocalId } from '../../../utils/local-id';
export function* createBackgroundImage(projectId, data) {
const localId = yield call(createLocalId);
yield put(
actions.createBackgroundImage({
...omit(data, ['file', 'url']),
projectId,
id: localId,
}),
);
let backgroundImage;
try {
({ item: backgroundImage } = yield call(
request,
api.createBackgroundImage,
projectId,
data,
localId,
));
} catch (error) {
yield put(actions.createBackgroundImage.failure(localId, error));
return;
}
yield put(actions.createBackgroundImage.success(localId, backgroundImage));
}
export function* createBackgroundImageInCurrentProject(data) {
const { projectId } = yield select(selectors.selectPath);
yield call(createBackgroundImage, projectId, data);
}
export function* handleBackgroundImageCreate(backgroundImage, requestId) {
const isExists = yield select(selectors.selectIsBackgroundImageWithIdExists, requestId);
if (!isExists) {
yield put(actions.handleBackgroundImageCreate(backgroundImage));
}
}
export function* deleteBackgroundImage(id) {
yield put(actions.deleteBackgroundImage(id));
let backgroundImage;
try {
({ item: backgroundImage } = yield call(request, api.deleteBackgroundImage, id));
} catch (error) {
yield put(actions.deleteBackgroundImage.failure(id, error));
return;
}
yield put(actions.deleteBackgroundImage.success(backgroundImage));
}
export function* handleBackgroundImageDelete(backgroundImage) {
yield put(actions.handleBackgroundImageDelete(backgroundImage));
}
export default {
createBackgroundImage,
createBackgroundImageInCurrentProject,
handleBackgroundImageCreate,
deleteBackgroundImage,
handleBackgroundImageDelete,
};
@@ -0,0 +1,100 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import { call, put, select } from 'redux-saga/effects';
import request from '../request';
import selectors from '../../../selectors';
import actions from '../../../actions';
import api from '../../../api';
import { createLocalId } from '../../../utils/local-id';
export function* createBaseCustomFieldGroup(projectId, data) {
const localId = yield call(createLocalId);
yield put(
actions.createBaseCustomFieldGroup({
...data,
projectId,
id: localId,
}),
);
let baseCustomFieldGroup;
try {
({ item: baseCustomFieldGroup } = yield call(
request,
api.createBaseCustomFieldGroup,
projectId,
data,
));
} catch (error) {
yield put(actions.createBaseCustomFieldGroup.failure(localId, error));
return;
}
yield put(actions.createBaseCustomFieldGroup.success(localId, baseCustomFieldGroup));
}
export function* createBaseCustomFieldGroupInCurrentProject(data) {
const { projectId } = yield select(selectors.selectPath);
yield call(createBaseCustomFieldGroup, projectId, data);
}
export function* handleBaseCustomFieldGroupCreate(baseCustomFieldGroup) {
yield put(actions.handleBaseCustomFieldGroupCreate(baseCustomFieldGroup));
}
export function* updateBaseCustomFieldGroup(id, data) {
yield put(actions.updateBaseCustomFieldGroup(id, data));
let baseCustomFieldGroup;
try {
({ item: baseCustomFieldGroup } = yield call(
request,
api.updateBaseCustomFieldGroup,
id,
data,
));
} catch (error) {
yield put(actions.updateBaseCustomFieldGroup.failure(id, error));
return;
}
yield put(actions.updateBaseCustomFieldGroup.success(baseCustomFieldGroup));
}
export function* handleBaseCustomFieldGroupUpdate(baseCustomFieldGroup) {
yield put(actions.handleBaseCustomFieldGroupUpdate(baseCustomFieldGroup));
}
export function* deleteBaseCustomFieldGroup(id) {
yield put(actions.deleteBaseCustomFieldGroup(id));
let baseCustomFieldGroup;
try {
({ item: baseCustomFieldGroup } = yield call(request, api.deleteBaseCustomFieldGroup, id));
} catch (error) {
yield put(actions.deleteBaseCustomFieldGroup.failure(id, error));
return;
}
yield put(actions.deleteBaseCustomFieldGroup.success(baseCustomFieldGroup));
}
export function* handleBaseCustomFieldGroupDelete(baseCustomFieldGroup) {
yield put(actions.handleBaseCustomFieldGroupDelete(baseCustomFieldGroup));
}
export default {
createBaseCustomFieldGroup,
createBaseCustomFieldGroupInCurrentProject,
handleBaseCustomFieldGroupCreate,
updateBaseCustomFieldGroup,
handleBaseCustomFieldGroupUpdate,
deleteBaseCustomFieldGroup,
handleBaseCustomFieldGroupDelete,
};
@@ -1,3 +1,8 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import { call, put, select } from 'redux-saga/effects';
import { goToProject } from './router';
@@ -37,16 +42,22 @@ export function* createMembershipInCurrentBoard(data) {
yield call(createBoardMembership, boardId, data);
}
export function* handleBoardMembershipCreate(boardMembership) {
export function* handleBoardMembershipCreate(boardMembership, users) {
const currentUserId = yield select(selectors.selectCurrentUserId);
const isCurrentUser = boardMembership.userId === currentUserId;
let user;
const isExternalAccessibleForCurrentUser = yield select(
selectors.selectIsProjectWithIdExternalAccessibleForCurrentUser,
boardMembership.projectId,
);
let project;
let board1;
let board;
let users1;
let users2;
let projectManagers;
let backgroundImages;
let baseCustomFieldGroups;
let boards;
let boardMemberships1;
let boardMemberships2;
@@ -55,72 +66,86 @@ export function* handleBoardMembershipCreate(boardMembership) {
let cards;
let cardMemberships;
let cardLabels;
let taskLists;
let tasks;
let attachments;
let deletedNotifications;
if (isCurrentUser) {
let board2;
try {
({ item: board2 } = yield call(request, api.getBoard, boardMembership.boardId, false));
} catch {
return;
}
let customFieldGroups;
let customFields1;
let customFields2;
let customFieldValues;
let notificationsToDelete;
let notificationServices;
if (isCurrentUser && !isExternalAccessibleForCurrentUser) {
const { boardId } = yield select(selectors.selectPath);
yield put(
actions.handleBoardMembershipCreate.fetchProject(board2.projectId, currentUserId, boardId),
);
try {
({
item: project,
included: { users: users1, projectManagers, boards, boardMemberships: boardMemberships1 },
} = yield call(request, api.getProject, board2.projectId));
included: {
projectManagers,
backgroundImages,
baseCustomFieldGroups,
boards,
notificationServices,
users: users1,
boardMemberships: boardMemberships1,
customFields: customFields1,
},
} = yield call(request, api.getProject, boardMembership.projectId));
} catch {
return;
}
let body;
try {
body = yield call(requests.fetchBoardByCurrentPath);
} catch {} // eslint-disable-line no-empty
if (boardId === null) {
let body;
try {
body = yield call(requests.fetchBoardByCurrentPath);
} catch {
/* empty */
}
if (body && body.project && body.project.id === board2.projectId) {
({
project,
board: board1,
users: users2,
boardMemberships: boardMemberships2,
labels,
lists,
cards,
cardMemberships,
cardLabels,
tasks,
attachments,
} = body);
if (body) {
({
project,
board,
labels,
lists,
cards,
cardMemberships,
cardLabels,
taskLists,
tasks,
attachments,
customFieldGroups,
customFieldValues,
users: users2,
boardMemberships: boardMemberships2,
customFields: customFields2,
} = body);
if (body.card) {
deletedNotifications = yield select(selectors.selectNotificationsByCardId, body.card.id);
if (body.card) {
notificationsToDelete = yield select(selectors.selectNotificationsByCardId, body.card.id);
}
}
}
} else {
try {
({ item: user } = yield call(request, api.getUser, boardMembership.userId));
} catch {
return;
}
}
const isProjectAvailable = yield select(
selectors.selectIsProjectWithIdAvailableForCurrentUser,
boardMembership.projectId,
);
yield put(
actions.handleBoardMembershipCreate(
boardMembership,
isProjectAvailable,
project,
board1,
isCurrentUser ? mergeRecords(users1, users2) : [user],
board,
mergeRecords(users, users1, users2),
projectManagers,
backgroundImages,
baseCustomFieldGroups,
boards,
mergeRecords(boardMemberships1, boardMemberships2),
labels,
@@ -128,9 +153,14 @@ export function* handleBoardMembershipCreate(boardMembership) {
cards,
cardMemberships,
cardLabels,
taskLists,
tasks,
attachments,
deletedNotifications,
customFieldGroups,
mergeRecords(customFields1, customFields2),
customFieldValues,
notificationsToDelete,
notificationServices,
),
);
}
@@ -156,21 +186,24 @@ export function* handleBoardMembershipUpdate(boardMembership) {
export function* deleteBoardMembership(id) {
let boardMembership = yield select(selectors.selectBoardMembershipById, id);
const currentUserId = yield select(selectors.selectCurrentUserId);
const { boardId, projectId } = yield select(selectors.selectPath);
if (boardMembership.userId === currentUserId && boardMembership.boardId === boardId) {
const isCurrentUserManager = yield select(
selectors.selectIsCurrentUserManagerForCurrentProject,
const currentUserId = yield select(selectors.selectCurrentUserId);
const isCurrentUser = boardMembership.userId === currentUserId;
yield put(actions.deleteBoardMembership(id, isCurrentUser));
if (isCurrentUser && boardMembership.boardId === boardId) {
const isAvailableForCurrentUser = yield select(
selectors.selectIsBoardWithIdAvailableForCurrentUser,
boardMembership.boardId,
);
if (!isCurrentUserManager) {
if (!isAvailableForCurrentUser) {
yield call(goToProject, projectId);
}
}
yield put(actions.deleteBoardMembership(id));
try {
({ item: boardMembership } = yield call(request, api.deleteBoardMembership, id));
} catch (error) {
@@ -178,24 +211,27 @@ export function* deleteBoardMembership(id) {
return;
}
yield put(actions.deleteBoardMembership.success(boardMembership));
yield put(actions.deleteBoardMembership.success(boardMembership, isCurrentUser));
}
export function* handleBoardMembershipDelete(boardMembership) {
const currentUserId = yield select(selectors.selectCurrentUserId);
const { boardId, projectId } = yield select(selectors.selectPath);
if (boardMembership.userId === currentUserId && boardMembership.boardId === boardId) {
const isCurrentUserManager = yield select(
selectors.selectIsCurrentUserManagerForCurrentProject,
const currentUserId = yield select(selectors.selectCurrentUserId);
const isCurrentUser = boardMembership.userId === currentUserId;
yield put(actions.handleBoardMembershipDelete(boardMembership, isCurrentUser));
if (isCurrentUser && boardMembership.boardId === boardId) {
const isAvailableForCurrentUser = yield select(
selectors.selectIsBoardWithIdAvailableForCurrentUser,
boardMembership.boardId,
);
if (!isCurrentUserManager) {
if (!isAvailableForCurrentUser) {
yield call(goToProject, projectId);
}
}
yield put(actions.handleBoardMembershipDelete(boardMembership));
}
export default {
+91 -16
View File
@@ -1,20 +1,28 @@
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, put, select, take } from 'redux-saga/effects';
import { goToBoard, goToProject } from './router';
import { openModal } from './modals';
import request from '../request';
import selectors from '../../../selectors';
import actions from '../../../actions';
import api from '../../../api';
import { createLocalId } from '../../../utils/local-id';
import ActionTypes from '../../../constants/ActionTypes';
import ModalTypes from '../../../constants/ModalTypes';
export function* createBoard(projectId, { import: boardImport, ...data }) {
const localId = yield call(createLocalId);
const nextData = {
...data,
position: yield select(selectors.selectNextBoardPosition, projectId),
};
const localId = yield call(createLocalId);
yield put(
actions.createBoard({
...nextData,
@@ -23,6 +31,11 @@ export function* createBoard(projectId, { import: boardImport, ...data }) {
}),
);
// TODO: use race instead
const watchForCreateBoardActionTask = yield fork(function* watchForCreateBoardAction() {
yield take(ActionTypes.BOARD_CREATE);
});
let board;
let boardMemberships;
@@ -49,7 +62,14 @@ export function* createBoard(projectId, { import: boardImport, ...data }) {
}
yield put(actions.createBoard.success(localId, board, boardMemberships));
yield call(goToBoard, board.id);
if (watchForCreateBoardActionTask.isRunning()) {
yield call(goToBoard, board.id);
yield call(openModal, ModalTypes.BOARD_SETTINGS, {
id: board.id,
openPreferences: true,
});
}
}
export function* createBoardInCurrentProject(data) {
@@ -58,11 +78,11 @@ export function* createBoardInCurrentProject(data) {
yield call(createBoard, projectId, data);
}
export function* handleBoardCreate(board, requestId) {
export function* handleBoardCreate(board, boardMemberships, requestId) {
const isExists = yield select(selectors.selectIsBoardWithIdExists, requestId);
if (!isExists) {
yield put(actions.handleBoardCreate(board));
yield put(actions.handleBoardCreate(board, boardMemberships));
}
}
@@ -78,8 +98,12 @@ export function* fetchBoard(id) {
let cards;
let cardMemberships;
let cardLabels;
let taskLists;
let tasks;
let attachments;
let customFieldGroups;
let customFields;
let customFieldValues;
try {
({
@@ -93,8 +117,12 @@ export function* fetchBoard(id) {
cards,
cardMemberships,
cardLabels,
taskLists,
tasks,
attachments,
customFieldGroups,
customFields,
customFieldValues,
},
} = yield call(request, api.getBoard, id, true));
} catch (error) {
@@ -113,8 +141,12 @@ export function* fetchBoard(id) {
cards,
cardMemberships,
cardLabels,
taskLists,
tasks,
attachments,
customFieldGroups,
customFields,
customFieldValues,
),
);
}
@@ -133,6 +165,12 @@ export function* updateBoard(id, data) {
yield put(actions.updateBoard.success(board));
}
export function* updateCurrentBoard(data) {
const { boardId } = yield select(selectors.selectPath);
yield call(updateBoard, boardId, data);
}
export function* handleBoardUpdate(board) {
yield put(actions.handleBoardUpdate(board));
}
@@ -146,15 +184,46 @@ export function* moveBoard(id, index) {
});
}
export function* deleteBoard(id) {
const { boardId, projectId } = yield select(selectors.selectPath);
export function* updateBoardContext(id, value) {
yield put(actions.updateBoardContext(id, value));
}
if (id === boardId) {
yield call(goToProject, projectId);
}
export function* updateContextInCurrentBoard(value) {
const { boardId } = yield select(selectors.selectPath);
yield call(updateBoardContext, boardId, value);
}
export function* updateBoardView(id, value) {
yield put(
actions.updateBoard(id, {
view: value,
}),
);
}
export function* updateViewInCurrentBoard(value) {
const { boardId } = yield select(selectors.selectPath);
yield call(updateBoardView, boardId, value);
}
export function* searchInCurrentBoard(value) {
const { boardId } = yield select(selectors.selectPath);
const currentListId = yield select(selectors.selectCurrentListId);
yield put(actions.searchInBoard(boardId, value, currentListId));
}
export function* deleteBoard(id) {
const currentBoard = yield select(selectors.selectCurrentBoard);
yield put(actions.deleteBoard(id));
if (currentBoard && id === currentBoard.id) {
yield call(goToProject, currentBoard.projectId);
}
let board;
try {
({ item: board } = yield call(request, api.deleteBoard, id));
@@ -167,13 +236,13 @@ export function* deleteBoard(id) {
}
export function* handleBoardDelete(board) {
const { boardId, projectId } = yield select(selectors.selectPath);
if (board.id === boardId) {
yield call(goToProject, projectId);
}
const { boardId } = yield select(selectors.selectPath);
yield put(actions.handleBoardDelete(board));
if (board.id === boardId) {
yield call(goToProject, board.projectId);
}
}
export default {
@@ -182,8 +251,14 @@ export default {
handleBoardCreate,
fetchBoard,
updateBoard,
updateCurrentBoard,
handleBoardUpdate,
moveBoard,
updateBoardContext,
updateContextInCurrentBoard,
updateBoardView,
updateViewInCurrentBoard,
searchInCurrentBoard,
deleteBoard,
handleBoardDelete,
};
+422 -73
View File
@@ -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,
};
@@ -1,74 +0,0 @@
import { call, put, select } from 'redux-saga/effects';
import request from '../request';
import selectors from '../../../selectors';
import actions from '../../../actions';
import api from '../../../api';
import { createLocalId } from '../../../utils/local-id';
import { ActivityTypes } from '../../../constants/Enums';
export function* createCommentActivity(cardId, data) {
const localId = yield call(createLocalId);
const userId = yield select(selectors.selectCurrentUserId);
yield put(
actions.createCommentActivity({
cardId,
userId,
data,
id: localId,
type: ActivityTypes.COMMENT_CARD,
}),
);
let activity;
try {
({ item: activity } = yield call(request, api.createCommentActivity, cardId, data));
} catch (error) {
yield put(actions.createCommentActivity.failure(localId, error));
return;
}
yield put(actions.createCommentActivity.success(localId, activity));
}
export function* createCommentActivityInCurrentCard(data) {
const { cardId } = yield select(selectors.selectPath);
yield call(createCommentActivity, cardId, data);
}
export function* updateCommentActivity(id, data) {
yield put(actions.updateCommentActivity(id, data));
let activity;
try {
({ item: activity } = yield call(request, api.updateCommentActivity, id, data));
} catch (error) {
yield put(actions.updateCommentActivity.failure(id, error));
return;
}
yield put(actions.updateCommentActivity.success(activity));
}
export function* deleteCommentActivity(id) {
yield put(actions.deleteCommentActivity(id));
let activity;
try {
({ item: activity } = yield call(request, api.deleteCommentActivity, id));
} catch (error) {
yield put(actions.deleteCommentActivity.failure(id, error));
return;
}
yield put(actions.deleteCommentActivity.success(activity));
}
export default {
createCommentActivity,
createCommentActivityInCurrentCard,
updateCommentActivity,
deleteCommentActivity,
};
+123
View File
@@ -0,0 +1,123 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import { call, put, select } from 'redux-saga/effects';
import request from '../request';
import selectors from '../../../selectors';
import actions from '../../../actions';
import api from '../../../api';
import { createLocalId } from '../../../utils/local-id';
export function* fetchComments(cardId) {
const { lastCommentId } = yield select(selectors.selectCardById, cardId);
yield put(actions.fetchComments(cardId));
let comments;
let users;
try {
({
items: comments,
included: { users },
} = yield call(request, api.getComments, cardId, {
beforeId: lastCommentId || undefined,
}));
} catch (error) {
yield put(actions.fetchComments.failure(cardId, error));
return;
}
yield put(actions.fetchComments.success(cardId, comments, users));
}
export function* fetchCommentsInCurrentCard() {
const { cardId } = yield select(selectors.selectPath);
yield call(fetchComments, cardId);
}
export function* createComment(cardId, data) {
const localId = yield call(createLocalId);
const currentUser = yield select(selectors.selectCurrentUser);
yield put(
actions.createComment({
...data,
cardId,
id: localId,
userId: currentUser.id,
}),
);
let comment;
try {
({ item: comment } = yield call(request, api.createComment, cardId, data));
} catch (error) {
yield put(actions.createComment.failure(localId, error));
return;
}
yield put(actions.createComment.success(localId, comment));
}
export function* createCommentInCurrentCard(data) {
const { cardId } = yield select(selectors.selectPath);
yield call(createComment, cardId, data);
}
export function* handleCommentCreate(comment, users) {
yield put(actions.handleCommentCreate(comment, users));
}
export function* updateComment(id, data) {
yield put(actions.updateComment(id, data));
let comment;
try {
({ item: comment } = yield call(request, api.updateComment, id, data));
} catch (error) {
yield put(actions.updateComment.failure(id, error));
return;
}
yield put(actions.updateComment.success(comment));
}
export function* handleCommentUpdate(comment) {
yield put(actions.handleCommentUpdate(comment));
}
export function* deleteComment(id) {
yield put(actions.deleteComment(id));
let comment;
try {
({ item: comment } = yield call(request, api.deleteComment, id));
} catch (error) {
yield put(actions.deleteComment.failure(id, error));
return;
}
yield put(actions.deleteComment.success(comment));
}
export function* handleCommentDelete(comment) {
yield put(actions.handleCommentDelete(comment));
}
export default {
fetchComments,
fetchCommentsInCurrentCard,
createComment,
createCommentInCurrentCard,
handleCommentCreate,
updateComment,
handleCommentUpdate,
deleteComment,
handleCommentDelete,
};
+60 -11
View File
@@ -1,3 +1,8 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import { call, put, select } from 'redux-saga/effects';
import request from '../request';
@@ -9,14 +14,9 @@ import i18n from '../../../i18n';
import { removeAccessToken } from '../../../utils/access-token-storage';
export function* initializeCore() {
const currentConfig = yield select(selectors.selectConfig); // TODO: add boolean selector?
const { item: config } = yield call(request, api.getConfig); // TODO: handle error
let config;
if (!currentConfig) {
({ item: config } = yield call(api.getConfig)); // TODO: handle error
yield put(actions.initializeCore.fetchConfig(config));
}
yield put(actions.initializeCore.fetchConfig(config));
const {
user,
@@ -24,6 +24,8 @@ export function* initializeCore() {
users,
projects,
projectManagers,
backgroundImages,
baseCustomFieldGroups,
boards,
boardMemberships,
labels,
@@ -31,10 +33,14 @@ export function* initializeCore() {
cards,
cardMemberships,
cardLabels,
taskLists,
tasks,
attachments,
activities,
customFieldGroups,
customFields,
customFieldValues,
notifications,
notificationServices,
} = yield call(requests.fetchCore); // TODO: handle error
yield call(i18n.changeLanguage, user.language);
@@ -47,6 +53,8 @@ export function* initializeCore() {
users,
projects,
projectManagers,
backgroundImages,
baseCustomFieldGroups,
boards,
boardMemberships,
labels,
@@ -54,10 +62,14 @@ export function* initializeCore() {
cards,
cardMemberships,
cardLabels,
taskLists,
tasks,
attachments,
activities,
customFieldGroups,
customFields,
customFieldValues,
notifications,
notificationServices,
),
);
}
@@ -73,7 +85,39 @@ export function* changeCoreLanguage(language) {
}
}
export function* logout(invalidateAccessToken = true) {
export function* toggleFavorites(isEnabled) {
yield put(actions.toggleFavorites(isEnabled));
const currentUserId = yield select(selectors.selectCurrentUserId);
try {
yield call(request, api.updateUser, currentUserId, {
enableFavoritesByDefault: isEnabled,
});
} catch {
/* empty */
}
}
export function* toggleEditMode(isEnabled) {
yield put(actions.toggleEditMode(isEnabled));
}
export function* updateHomeView(value) {
yield put(actions.updateHomeView(value));
const currentUserId = yield select(selectors.selectCurrentUserId);
try {
yield call(request, api.updateUser, currentUserId, {
defaultHomeView: value,
});
} catch {
/* empty */
}
}
export function* logout(invalidateAccessToken) {
yield call(removeAccessToken);
if (invalidateAccessToken) {
@@ -81,7 +125,9 @@ export function* logout(invalidateAccessToken = true) {
try {
yield call(request, api.deleteCurrentAccessToken);
} catch (error) {} // eslint-disable-line no-empty
} catch {
/* empty */
}
}
yield put(actions.logout()); // TODO: next url
@@ -90,5 +136,8 @@ export function* logout(invalidateAccessToken = true) {
export default {
initializeCore,
changeCoreLanguage,
toggleFavorites,
toggleEditMode,
updateHomeView,
logout,
};
@@ -0,0 +1,166 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import { call, put, select } from 'redux-saga/effects';
import request from '../request';
import selectors from '../../../selectors';
import actions from '../../../actions';
import api from '../../../api';
import { createLocalId } from '../../../utils/local-id';
export function* createCustomFieldGroupInBoard(boardId, data) {
const localId = yield call(createLocalId);
const nextData = {
...data,
position: yield select(selectors.selectNextCustomFieldGroupPositionInBoard, boardId),
};
yield put(
actions.createCustomFieldGroup({
...nextData,
boardId,
id: localId,
}),
);
let customFieldGroup;
try {
({ item: customFieldGroup } = yield call(
request,
api.createCustomFieldGroupInBoard,
boardId,
nextData,
));
} catch (error) {
yield put(actions.createCustomFieldGroup.failure(localId, error));
return;
}
yield put(actions.createCustomFieldGroup.success(localId, customFieldGroup));
}
export function* createCustomFieldGroupInCurrentBoard(data) {
const { boardId } = yield select(selectors.selectPath);
yield call(createCustomFieldGroupInBoard, boardId, data);
}
export function* createCustomFieldGroupInCard(cardId, data) {
const localId = yield call(createLocalId);
const nextData = {
...data,
position: yield select(selectors.selectNextCustomFieldGroupPositionInCard, cardId),
};
yield put(
actions.createCustomFieldGroup({
...nextData,
cardId,
id: localId,
}),
);
let customFieldGroup;
try {
({ item: customFieldGroup } = yield call(
request,
api.createCustomFieldGroupInCard,
cardId,
nextData,
));
} catch (error) {
yield put(actions.createCustomFieldGroup.failure(localId, error));
return;
}
yield put(actions.createCustomFieldGroup.success(localId, customFieldGroup));
}
export function* createCustomFieldGroupInCurrentCard(data) {
const { cardId } = yield select(selectors.selectPath);
yield call(createCustomFieldGroupInCard, cardId, data);
}
export function* handleCustomFieldGroupCreate(customFieldGroup) {
yield put(actions.handleCustomFieldGroupCreate(customFieldGroup));
}
export function* updateCustomFieldGroup(id, data) {
yield put(actions.updateCustomFieldGroup(id, data));
let customFieldGroup;
try {
({ item: customFieldGroup } = yield call(request, api.updateCustomFieldGroup, id, data));
} catch (error) {
yield put(actions.updateCustomFieldGroup.failure(id, error));
return;
}
yield put(actions.updateCustomFieldGroup.success(customFieldGroup));
}
export function* handleCustomFieldGroupUpdate(customFieldGroup) {
yield put(actions.handleCustomFieldGroupUpdate(customFieldGroup));
}
export function* moveCustomFieldGroup(id, index) {
const customFieldGroup = yield select(selectors.selectCustomFieldGroupById, id);
let position;
if (customFieldGroup.boardId) {
position = yield select(
selectors.selectNextCustomFieldGroupPositionInBoard,
customFieldGroup.boardId,
index,
id,
);
} else if (customFieldGroup.cardId) {
position = yield select(
selectors.selectNextCustomFieldGroupPositionInCard,
customFieldGroup.cardId,
index,
id,
);
}
yield call(updateCustomFieldGroup, id, {
position,
});
}
export function* deleteCustomFieldGroup(id) {
yield put(actions.deleteCustomFieldGroup(id));
let customFieldGroup;
try {
({ item: customFieldGroup } = yield call(request, api.deleteCustomFieldGroup, id));
} catch (error) {
yield put(actions.deleteCustomFieldGroup.failure(id, error));
return;
}
yield put(actions.deleteCustomFieldGroup.success(customFieldGroup));
}
export function* handleCustomFieldGroupDelete(customFieldGroup) {
yield put(actions.handleCustomFieldGroupDelete(customFieldGroup));
}
export default {
createCustomFieldGroupInBoard,
createCustomFieldGroupInCurrentBoard,
createCustomFieldGroupInCard,
createCustomFieldGroupInCurrentCard,
handleCustomFieldGroupCreate,
updateCustomFieldGroup,
handleCustomFieldGroupUpdate,
moveCustomFieldGroup,
deleteCustomFieldGroup,
handleCustomFieldGroupDelete,
};
@@ -0,0 +1,83 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import { call, put } from 'redux-saga/effects';
import request from '../request';
import actions from '../../../actions';
import api from '../../../api';
import { buildCustomFieldValueId } from '../../../models/CustomFieldValue';
import { createLocalId } from '../../../utils/local-id';
export function* updateCustomFieldValue(cardId, customFieldGroupId, customFieldId, data) {
const localId = yield call(createLocalId);
yield put(
actions.updateCustomFieldValue({
...data,
cardId,
customFieldGroupId,
customFieldId,
}),
);
let customFieldValue;
try {
({ item: customFieldValue } = yield call(
request,
api.updateCustomFieldValue,
cardId,
customFieldGroupId,
customFieldId,
data,
));
} catch (error) {
yield put(actions.updateCustomFieldValue.failure(localId, error));
return;
}
yield put(actions.updateCustomFieldValue.success(localId, customFieldValue));
}
export function* handleCustomFieldValueUpdate(customFieldValue) {
yield put(actions.handleCustomFieldValueUpdate(customFieldValue));
}
export function* deleteCustomFieldValue(cardId, customFieldGroupId, customFieldId) {
const id = buildCustomFieldValueId({
cardId,
customFieldGroupId,
customFieldId,
});
yield put(actions.deleteCustomFieldValue(id));
let customFieldValue;
try {
({ item: customFieldValue } = yield call(
request,
api.deleteCustomFieldValue,
cardId,
customFieldGroupId,
customFieldId,
));
} catch (error) {
yield put(actions.deleteCustomFieldValue.failure(id, error));
return;
}
yield put(actions.deleteCustomFieldValue.success(customFieldValue));
}
export function* handleCustomFieldValueDelete(customFieldValue) {
yield put(actions.handleCustomFieldValueDelete(customFieldValue));
}
export default {
updateCustomFieldValue,
handleCustomFieldValueUpdate,
deleteCustomFieldValue,
handleCustomFieldValueDelete,
};
@@ -0,0 +1,155 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import { call, put, select } from 'redux-saga/effects';
import request from '../request';
import selectors from '../../../selectors';
import actions from '../../../actions';
import api from '../../../api';
import { createLocalId } from '../../../utils/local-id';
export function* createCustomFieldInBaseGroup(baseCustomFieldGroupId, data) {
const localId = yield call(createLocalId);
const nextData = {
...data,
position: yield select(
selectors.selectNextCustomFieldPositionInBaseGroup,
baseCustomFieldGroupId,
),
};
yield put(
actions.createCustomField({
...nextData,
baseCustomFieldGroupId,
id: localId,
}),
);
let customField;
try {
({ item: customField } = yield call(
request,
api.createCustomFieldInBaseGroup,
baseCustomFieldGroupId,
nextData,
));
} catch (error) {
yield put(actions.createCustomField.failure(localId, error));
return;
}
yield put(actions.createCustomField.success(localId, customField));
}
export function* createCustomFieldInGroup(customFieldGroupId, data) {
const localId = yield call(createLocalId);
const nextData = {
...data,
position: yield select(selectors.selectNextCustomFieldPositionInGroup, customFieldGroupId),
};
yield put(
actions.createCustomField({
...nextData,
customFieldGroupId,
id: localId,
}),
);
let customField;
try {
({ item: customField } = yield call(
request,
api.createCustomFieldInGroup,
customFieldGroupId,
nextData,
));
} catch (error) {
yield put(actions.createCustomField.failure(localId, error));
return;
}
yield put(actions.createCustomField.success(localId, customField));
}
export function* handleCustomFieldCreate(customField) {
yield put(actions.handleCustomFieldCreate(customField));
}
export function* updateCustomField(id, data) {
yield put(actions.updateCustomField(id, data));
let customField;
try {
({ item: customField } = yield call(request, api.updateCustomField, id, data));
} catch (error) {
yield put(actions.updateCustomField.failure(id, error));
return;
}
yield put(actions.updateCustomField.success(customField));
}
export function* handleCustomFieldUpdate(customField) {
yield put(actions.handleCustomFieldUpdate(customField));
}
export function* moveCustomField(id, index) {
const customField = yield select(selectors.selectCustomFieldById, id);
let position;
if (customField.baseCustomFieldGroupId) {
position = yield select(
selectors.selectNextCustomFieldPositionInBaseGroup,
customField.baseCustomFieldGroupId,
index,
id,
);
} else if (customField.customFieldGroupId) {
position = yield select(
selectors.selectNextCustomFieldPositionInGroup,
customField.customFieldGroupId,
index,
id,
);
}
yield call(updateCustomField, id, {
position,
});
}
export function* deleteCustomField(id) {
yield put(actions.deleteCustomField(id));
let customField;
try {
({ item: customField } = yield call(request, api.deleteCustomField, id));
} catch (error) {
yield put(actions.deleteCustomField.failure(id, error));
return;
}
yield put(actions.deleteCustomField.success(customField));
}
export function* handleCustomFieldDelete(customField) {
yield put(actions.handleCustomFieldDelete(customField));
}
export default {
createCustomFieldInBaseGroup,
createCustomFieldInGroup,
handleCustomFieldCreate,
updateCustomField,
handleCustomFieldUpdate,
moveCustomField,
deleteCustomField,
handleCustomFieldDelete,
};
+21 -2
View File
@@ -1,3 +1,8 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import router from './router';
import socket from './socket';
import core from './core';
@@ -5,16 +10,23 @@ import modals from './modals';
import users from './users';
import projects from './projects';
import projectManagers from './project-managers';
import backgroundImages from './background-images';
import baseCustomFieldGroups from './base-custom-field-groups';
import boards from './boards';
import boardMemberships from './board-memberships';
import labels from './labels';
import lists from './lists';
import cards from './cards';
import taskLists from './task-lists';
import tasks from './tasks';
import attachments from './attachments';
import customFieldGroups from './custom-field-groups';
import customFields from './custom-fields';
import customFieldValues from './custom-field-values';
import comments from './comments';
import activities from './activities';
import commentActivities from './comment-activities';
import notifications from './notifications';
import notificationServices from './notification-services';
export default {
...router,
@@ -24,14 +36,21 @@ export default {
...users,
...projects,
...projectManagers,
...backgroundImages,
...baseCustomFieldGroups,
...boards,
...boardMemberships,
...labels,
...lists,
...cards,
...taskLists,
...tasks,
...attachments,
...customFieldGroups,
...customFields,
...customFieldValues,
...comments,
...activities,
...commentActivities,
...notifications,
...notificationServices,
};
+52 -4
View File
@@ -1,3 +1,8 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import { call, put, select } from 'redux-saga/effects';
import request from '../request';
@@ -7,13 +12,13 @@ import api from '../../../api';
import { createLocalId } from '../../../utils/local-id';
export function* createLabel(boardId, data) {
const localId = yield call(createLocalId);
const nextData = {
...data,
position: yield select(selectors.selectNextLabelPosition, boardId),
};
const localId = yield call(createLocalId);
yield put(
actions.createLabel({
...nextData,
@@ -39,6 +44,44 @@ export function* createLabelInCurrentBoard(data) {
yield call(createLabel, boardId, data);
}
export function* createLabelFromCard(cardId, data) {
const localId = yield call(createLocalId);
const { boardId } = yield select(selectors.selectCardById, cardId);
const nextData = {
...data,
position: yield select(selectors.selectNextLabelPosition, boardId),
};
yield put(
actions.createLabelFromCard(cardId, {
...nextData,
boardId,
id: localId,
}),
);
let label;
try {
({ item: label } = yield call(request, api.createLabel, boardId, nextData));
} catch (error) {
yield put(actions.createLabelFromCard.failure(cardId, localId, error));
return;
}
let cardLabel;
try {
({ item: cardLabel } = yield call(request, api.createCardLabel, cardId, {
labelId: label.id,
}));
} catch (error) {
yield put(actions.createLabelFromCard.failure(cardId, localId, error));
return;
}
yield put(actions.createLabelFromCard.success(localId, label, cardLabel));
}
export function* handleLabelCreate(label) {
yield put(actions.handleLabelCreate(label));
}
@@ -139,7 +182,9 @@ export function* handleLabelFromCardRemove(cardLabel) {
}
export function* addLabelToBoardFilter(id, boardId) {
yield put(actions.addLabelToBoardFilter(id, boardId));
const currentListId = yield select(selectors.selectCurrentListId);
yield put(actions.addLabelToBoardFilter(id, boardId, currentListId));
}
export function* addLabelToFilterInCurrentBoard(id) {
@@ -149,7 +194,9 @@ export function* addLabelToFilterInCurrentBoard(id) {
}
export function* removeLabelFromBoardFilter(id, boardId) {
yield put(actions.removeLabelFromBoardFilter(id, boardId));
const currentListId = yield select(selectors.selectCurrentListId);
yield put(actions.removeLabelFromBoardFilter(id, boardId, currentListId));
}
export function* removeLabelFromFilterInCurrentBoard(id) {
@@ -161,6 +208,7 @@ export function* removeLabelFromFilterInCurrentBoard(id) {
export default {
createLabel,
createLabelInCurrentBoard,
createLabelFromCard,
handleLabelCreate,
updateLabel,
handleLabelUpdate,
+85 -12
View File
@@ -1,19 +1,26 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import { call, put, select } from 'redux-saga/effects';
import toast from 'react-hot-toast';
import request from '../request';
import selectors from '../../../selectors';
import actions from '../../../actions';
import api from '../../../api';
import { createLocalId } from '../../../utils/local-id';
import ToastTypes from '../../../constants/ToastTypes';
export function* createList(boardId, data) {
const localId = yield call(createLocalId);
const nextData = {
...data,
position: yield select(selectors.selectNextListPosition, boardId),
};
const localId = yield call(createLocalId);
yield put(
actions.createList({
...nextData,
@@ -70,7 +77,6 @@ export function* moveList(id, index) {
});
}
// TODO: sort locally
export function* sortList(id, data) {
yield put(actions.sortList(id, data));
@@ -90,26 +96,91 @@ export function* sortList(id, data) {
yield put(actions.sortList.success(list, cards));
}
export function* handleListSort(list, cards) {
yield put(actions.handleListSort(list, cards));
export function* moveListCards(id, nextId) {
const cardIds = yield select(selectors.selectCardIdsByListId, id);
if (cardIds.length === 0) {
return;
}
yield put(actions.moveListCards(id, nextId, cardIds));
let list;
let cards;
let activities;
try {
({
item: list,
included: { cards, activities },
} = yield call(request, api.moveListCards, id, {
listId: nextId,
}));
} catch (error) {
yield put(actions.moveListCards.failure(id, error));
return;
}
yield put(actions.moveListCards.success(list, cards, activities));
}
export function* deleteList(id) {
yield put(actions.deleteList(id));
export function* moveListCardsToArchiveList(id) {
const archiveListId = yield select(selectors.selectArchiveListIdForCurrentBoard);
yield call(moveListCards, id, archiveListId);
}
export function* clearTrashListInCurrentBoard() {
const trashListId = yield select(selectors.selectTrashListIdForCurrentBoard);
yield put(actions.clearList(trashListId));
yield call(toast, {
type: ToastTypes.EMPTY_TRASH,
params: {
listId: trashListId,
},
});
let list;
try {
({ item: list } = yield call(request, api.deleteList, id));
({ item: list } = yield call(request, api.clearList, trashListId));
} catch (error) {
yield put(actions.clearList.failure(trashListId, error));
return;
}
yield put(actions.clearList.success(list));
}
export function* handleListClear(list) {
yield put(actions.handleListClear(list));
}
export function* deleteList(id) {
const trashListId = yield select(selectors.selectTrashListIdForCurrentBoard);
const cardIds = yield select(selectors.selectCardIdsByListId, id);
yield put(actions.deleteList(id, trashListId, cardIds));
let list;
let cards;
try {
({
item: list,
included: { cards },
} = yield call(request, api.deleteList, id));
} catch (error) {
yield put(actions.deleteList.failure(id, error));
return;
}
yield put(actions.deleteList.success(list));
yield put(actions.deleteList.success(list, cards));
}
export function* handleListDelete(list) {
yield put(actions.handleListDelete(list));
export function* handleListDelete(list, cards) {
yield put(actions.handleListDelete(list, cards));
}
export default {
@@ -120,7 +191,9 @@ export default {
handleListUpdate,
moveList,
sortList,
handleListSort,
moveListCardsToArchiveList,
clearTrashListInCurrentBoard,
handleListClear,
deleteList,
handleListDelete,
};
+7 -2
View File
@@ -1,9 +1,14 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import { put } from 'redux-saga/effects';
import actions from '../../../actions';
export function* openModal(type) {
yield put(actions.openModal(type));
export function* openModal(type, params) {
yield put(actions.openModal(type, params));
}
export function* closeModal() {
@@ -0,0 +1,132 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import { call, put, select } from 'redux-saga/effects';
import request from '../request';
import selectors from '../../../selectors';
import actions from '../../../actions';
import api from '../../../api';
import { createLocalId } from '../../../utils/local-id';
export function* createNotificationServiceInCurrentUser(data) {
const localId = yield call(createLocalId);
const currentUserId = yield select(selectors.selectCurrentUserId);
yield put(
actions.createNotificationService({
...data,
id: localId,
userId: currentUserId,
}),
);
let notificationService;
try {
({ item: notificationService } = yield call(
request,
api.createNotificationServiceInUser,
currentUserId,
data,
));
} catch (error) {
yield put(actions.createNotificationService.failure(localId, error));
return;
}
yield put(actions.createNotificationService.success(localId, notificationService));
}
export function* createNotificationServiceInBoard(boardId, data) {
const localId = yield call(createLocalId);
yield put(
actions.createNotificationService({
...data,
boardId,
id: localId,
}),
);
let notificationService;
try {
({ item: notificationService } = yield call(
request,
api.createNotificationServiceInBoard,
boardId,
data,
));
} catch (error) {
yield put(actions.createNotificationService.failure(localId, error));
return;
}
yield put(actions.createNotificationService.success(localId, notificationService));
}
export function* handleNotificationServiceCreate(notificationService) {
yield put(actions.handleNotificationServiceCreate(notificationService));
}
export function* updateNotificationService(id, data) {
yield put(actions.updateNotificationService(id, data));
let notificationService;
try {
({ item: notificationService } = yield call(request, api.updateNotificationService, id, data));
} catch (error) {
yield put(actions.updateNotificationService.failure(id, error));
return;
}
yield put(actions.updateNotificationService.success(notificationService));
}
export function* handleNotificationServiceUpdate(notificationService) {
yield put(actions.handleNotificationServiceUpdate(notificationService));
}
export function* testNotificationService(id) {
yield put(actions.testNotificationService(id));
let notificationService;
try {
({ item: notificationService } = yield call(request, api.testNotificationService, id));
} catch (error) {
yield put(actions.testNotificationService.failure(id, error));
return;
}
yield put(actions.testNotificationService.success(notificationService));
}
export function* deleteNotificationService(id) {
yield put(actions.deleteNotificationService(id));
let notificationService;
try {
({ item: notificationService } = yield call(request, api.deleteNotificationService, id));
} catch (error) {
yield put(actions.deleteNotificationService.failure(id, error));
return;
}
yield put(actions.deleteNotificationService.success(notificationService));
}
export function* handleNotificationServiceDelete(notificationService) {
yield put(actions.handleNotificationServiceDelete(notificationService));
}
export default {
createNotificationServiceInCurrentUser,
createNotificationServiceInBoard,
handleNotificationServiceCreate,
updateNotificationService,
handleNotificationServiceUpdate,
testNotificationService,
deleteNotificationService,
handleNotificationServiceDelete,
};
+28 -18
View File
@@ -1,3 +1,8 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import { call, put, select } from 'redux-saga/effects';
import request from '../request';
@@ -5,38 +10,42 @@ import selectors from '../../../selectors';
import actions from '../../../actions';
import api from '../../../api';
export function* handleNotificationCreate(notification) {
export function* deleteAllNotifications() {
yield put(actions.deleteAllNotifications());
let notifications;
try {
({ items: notifications } = yield call(request, api.readAllNotifications));
} catch (error) {
yield put(actions.deleteAllNotifications.failure(error));
return;
}
yield put(actions.deleteAllNotifications.success(notifications));
}
export function* handleNotificationCreate(notification, users) {
const { cardId } = yield select(selectors.selectPath);
if (notification.cardId === cardId) {
try {
yield call(request, api.updateNotifications, [notification.id], {
yield call(request, api.updateNotification, notification.id, {
isRead: true,
});
} catch {} // eslint-disable-line no-empty
} else {
let users;
let cards;
let activities;
try {
({
included: { users, cards, activities },
} = yield call(request, api.getNotification, notification.id));
} catch {
return;
/* empty */
}
yield put(actions.handleNotificationCreate(notification, users, cards, activities));
} else {
yield put(actions.handleNotificationCreate(notification, users));
}
}
export function* deleteNotification(id) {
yield put(actions.deleteNotification(id));
let notifications;
let notification;
try {
({ items: notifications } = yield call(request, api.updateNotifications, [id], {
({ item: notification } = yield call(request, api.updateNotification, id, {
isRead: true,
}));
} catch (error) {
@@ -44,7 +53,7 @@ export function* deleteNotification(id) {
return;
}
yield put(actions.deleteNotification.success(notifications[0]));
yield put(actions.deleteNotification.success(notification));
}
export function* handleNotificationDelete(notification) {
@@ -52,6 +61,7 @@ export function* handleNotificationDelete(notification) {
}
export default {
deleteAllNotifications,
handleNotificationCreate,
deleteNotification,
handleNotificationDelete,
@@ -1,3 +1,8 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import { call, put, select } from 'redux-saga/effects';
import request from '../request';
@@ -36,16 +41,17 @@ export function* createManagerInCurrentProject(data) {
yield call(createProjectManager, projectId, data);
}
export function* handleProjectManagerCreate(projectManager) {
export function* handleProjectManagerCreate(projectManager, users) {
const currentUserId = yield select(selectors.selectCurrentUserId);
const isCurrentUser = projectManager.userId === currentUserId;
let user;
let project;
let board;
let users1;
let users2;
let projectManagers;
let backgroundImages;
let baseCustomFieldGroups;
let boards;
let boardMemberships1;
let boardMemberships2;
@@ -54,69 +60,95 @@ export function* handleProjectManagerCreate(projectManager) {
let cards;
let cardMemberships;
let cardLabels;
let taskLists;
let tasks;
let attachments;
let deletedNotifications;
let customFieldGroups;
let customFields1;
let customFields2;
let customFieldValues;
let notificationsToDelete;
let notificationServices;
if (isCurrentUser) {
const { boardId } = yield select(selectors.selectPath);
yield put(
actions.handleProjectManagerCreate.fetchProject(
projectManager.projectId,
currentUserId,
boardId,
),
const isExternalAccessibleForCurrentUser = yield select(
selectors.selectIsProjectWithIdExternalAccessibleForCurrentUser,
projectManager.projectId,
);
try {
({
item: project,
included: { users: users1, projectManagers, boards, boardMemberships: boardMemberships1 },
included: {
projectManagers,
backgroundImages,
baseCustomFieldGroups,
boards,
notificationServices,
users: users1,
boardMemberships: boardMemberships1,
customFields: customFields1,
},
} = yield call(request, api.getProject, projectManager.projectId));
} catch {
return;
}
let body;
try {
body = yield call(requests.fetchBoardByCurrentPath);
} catch {} // eslint-disable-line no-empty
if (boardId === null && !isExternalAccessibleForCurrentUser) {
let body;
try {
body = yield call(requests.fetchBoardByCurrentPath);
} catch {
/* empty */
}
if (body && body.project && body.project.id === projectManager.projectId) {
({
project,
board,
users: users2,
boardMemberships: boardMemberships2,
labels,
lists,
cards,
cardMemberships,
cardLabels,
tasks,
attachments,
} = body);
if (body) {
({
project,
board,
labels,
lists,
cards,
cardMemberships,
cardLabels,
taskLists,
tasks,
attachments,
customFieldGroups,
customFieldValues,
users: users2,
boardMemberships: boardMemberships2,
customFields: customFields2,
} = body);
if (body.card) {
deletedNotifications = yield select(selectors.selectNotificationsByCardId, body.card.id);
if (body.card) {
notificationsToDelete = yield select(selectors.selectNotificationsByCardId, body.card.id);
}
}
}
} else {
try {
({ item: user } = yield call(request, api.getUser, projectManager.userId));
} catch {
return;
}
}
const boardIds = yield select(selectors.selectBoardIdsByProjectId, projectManager.projectId);
const isProjectAvailable = yield select(
selectors.selectIsProjectWithIdAvailableForCurrentUser,
projectManager.projectId,
);
yield put(
actions.handleProjectManagerCreate(
projectManager,
boardIds,
isCurrentUser,
isProjectAvailable,
project,
board,
isCurrentUser ? mergeRecords(users1, users2) : [user],
mergeRecords(users, users1, users2),
projectManagers,
backgroundImages,
baseCustomFieldGroups,
boards,
mergeRecords(boardMemberships1, boardMemberships2),
labels,
@@ -124,26 +156,31 @@ export function* handleProjectManagerCreate(projectManager) {
cards,
cardMemberships,
cardLabels,
taskLists,
tasks,
attachments,
deletedNotifications,
customFieldGroups,
mergeRecords(customFields1, customFields2),
customFieldValues,
notificationsToDelete,
notificationServices,
),
);
}
export function* deleteProjectManager(id) {
let projectManager = yield select(selectors.selectProjectManagerById, id);
const currentUserId = yield select(selectors.selectCurrentUserId);
const { projectId } = yield select(selectors.selectPath);
yield put(
actions.deleteProjectManager(
id,
projectManager.userId === currentUserId,
projectManager.projectId === projectId,
),
);
yield put(actions.deleteProjectManager(id));
if (projectManager.userId === currentUserId) {
const isAvailableForCurrentUser = yield select(selectors.isCurrentModalAvailableForCurrentUser);
if (!isAvailableForCurrentUser) {
yield put(actions.closeModal());
}
}
try {
({ item: projectManager } = yield call(request, api.deleteProjectManager, id));
@@ -157,15 +194,16 @@ export function* deleteProjectManager(id) {
export function* handleProjectManagerDelete(projectManager) {
const currentUserId = yield select(selectors.selectCurrentUserId);
const { projectId } = yield select(selectors.selectPath);
yield put(
actions.handleProjectManagerDelete(
projectManager,
projectManager.userId === currentUserId,
projectManager.projectId === projectId,
),
);
yield put(actions.handleProjectManagerDelete(projectManager));
if (projectManager.userId === currentUserId) {
const isAvailableForCurrentUser = yield select(selectors.isCurrentModalAvailableForCurrentUser);
if (!isAvailableForCurrentUser) {
yield put(actions.closeModal());
}
}
}
export default {
+198 -25
View File
@@ -1,13 +1,44 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import omit from 'lodash/omit';
import { call, put, select } from 'redux-saga/effects';
import { goToProject, goToRoot } from './router';
import request from '../request';
import requests from '../requests';
import selectors from '../../../selectors';
import actions from '../../../actions';
import api from '../../../api';
import mergeRecords from '../../../utils/merge-records';
import { UserRoles } from '../../../constants/Enums';
export function* searchProjects(value) {
yield put(actions.searchProjects(value));
}
export function* updateProjectsOrder(value) {
yield put(actions.updateProjectsOrder(value));
const currentUserId = yield select(selectors.selectCurrentUserId);
try {
yield call(request, api.updateUser, currentUserId, {
defaultProjectsOrder: value,
});
} catch {
/* empty */
}
}
export function* toggleHiddenProjects(isVisible) {
yield put(actions.toggleHiddenProjects(isVisible));
}
export function* createProject(data) {
yield put(actions.createProject(data));
yield put(actions.createProject(omit(data, 'type')));
let project;
let projectManagers;
@@ -30,24 +61,55 @@ export function* handleProjectCreate({ id }) {
let project;
let users;
let projectManagers;
let backgroundImages;
let baseCustomFieldGroups;
let boards;
let boardMemberships;
let customFields;
let notificationServices;
try {
({
item: project,
included: { users, projectManagers, boards, boardMemberships },
included: {
users,
projectManagers,
backgroundImages,
baseCustomFieldGroups,
boards,
boardMemberships,
customFields,
notificationServices,
},
} = yield call(request, api.getProject, id));
} catch (error) {
} catch {
return;
}
yield put(actions.handleProjectCreate(project, users, projectManagers, boards, boardMemberships));
yield put(
actions.handleProjectCreate(
project,
users,
projectManagers,
backgroundImages,
baseCustomFieldGroups,
boards,
boardMemberships,
customFields,
notificationServices,
),
);
}
export function* updateProject(id, data) {
yield put(actions.updateProject(id, data));
const isAvailableForCurrentUser = yield select(selectors.isCurrentModalAvailableForCurrentUser);
if (!isAvailableForCurrentUser) {
yield put(actions.closeModal());
}
let project;
try {
({ item: project } = yield call(request, api.updateProject, id, data));
@@ -66,38 +128,148 @@ export function* updateCurrentProject(data) {
}
export function* handleProjectUpdate(project) {
yield put(actions.handleProjectUpdate(project));
}
const prevProject = yield select(selectors.selectProjectById, project.id);
export function* updateProjectBackgroundImage(id, data) {
yield put(actions.updateProjectBackgroundImage(id));
const isChangedToShared =
(!prevProject || !!prevProject.ownerProjectManagerId) && !project.ownerProjectManagerId;
let project;
try {
({ item: project } = yield call(request, api.updateProjectBackgroundImage, id, data));
} catch (error) {
yield put(actions.updateProjectBackgroundImage.failure(id, error));
return;
const currentUser = yield select(selectors.selectCurrentUser);
const isCurrentUserAdmin = currentUser.role === UserRoles.ADMIN;
const isExternalAccessibleForCurrentUser = yield select(
selectors.selectIsProjectWithIdExternalAccessibleForCurrentUser,
project.id,
);
let board;
let users1;
let users2;
let projectManagers;
let backgroundImages;
let baseCustomFieldGroups;
let boards;
let boardMemberships1;
let boardMemberships2;
let labels;
let lists;
let cards;
let cardMemberships;
let cardLabels;
let taskLists;
let tasks;
let attachments;
let customFieldGroups;
let customFields1;
let customFields2;
let customFieldValues;
let notificationsToDelete;
let notificationServices;
if (isCurrentUserAdmin && isChangedToShared && !isExternalAccessibleForCurrentUser) {
const { boardId } = yield select(selectors.selectPath);
try {
({
item: project, // eslint-disable-line no-param-reassign
included: {
projectManagers,
backgroundImages,
baseCustomFieldGroups,
boards,
notificationServices,
users: users1,
boardMemberships: boardMemberships1,
customFields: customFields1,
},
} = yield call(request, api.getProject, project.id));
} catch {
return;
}
if (boardId === null) {
let body;
try {
body = yield call(requests.fetchBoardByCurrentPath);
} catch {
/* empty */
}
if (body) {
({
board,
labels,
lists,
cards,
cardMemberships,
cardLabels,
taskLists,
tasks,
attachments,
customFieldGroups,
customFieldValues,
users: users2,
boardMemberships: boardMemberships2,
customFields: customFields2,
} = body);
if (body.card) {
notificationsToDelete = yield select(selectors.selectNotificationsByCardId, body.card.id);
}
}
}
}
yield put(actions.updateProjectBackgroundImage.success(project));
}
const boardIds = yield select(selectors.selectBoardIdsByProjectId, project.id);
export function* updateCurrentProjectBackgroundImage(data) {
const { projectId } = yield select(selectors.selectPath);
const isAvailable = yield select(
selectors.selectIsProjectWithIdAvailableForCurrentUser,
project.id,
);
yield call(updateProjectBackgroundImage, projectId, data);
yield put(
actions.handleProjectUpdate(
project,
boardIds,
isAvailable,
board,
mergeRecords(users1, users2),
projectManagers,
backgroundImages,
baseCustomFieldGroups,
boards,
mergeRecords(boardMemberships1, boardMemberships2),
labels,
lists,
cards,
cardMemberships,
cardLabels,
taskLists,
tasks,
attachments,
customFieldGroups,
mergeRecords(customFields1, customFields2),
customFieldValues,
notificationsToDelete,
notificationServices,
),
);
const isAvailableForCurrentUser = yield select(selectors.isCurrentModalAvailableForCurrentUser);
if (!isAvailableForCurrentUser) {
yield put(actions.closeModal());
}
}
export function* deleteProject(id) {
const { projectId } = yield select(selectors.selectPath);
yield put(actions.deleteProject(id));
if (id === projectId) {
yield call(goToRoot);
}
yield put(actions.deleteProject(id));
let project;
try {
({ item: project } = yield call(request, api.deleteProject, id));
@@ -118,21 +290,22 @@ export function* deleteCurrentProject() {
export function* handleProjectDelete(project) {
const { projectId } = yield select(selectors.selectPath);
yield put(actions.handleProjectDelete(project));
if (project.id === projectId) {
yield call(goToRoot);
}
yield put(actions.handleProjectDelete(project));
}
export default {
searchProjects,
updateProjectsOrder,
toggleHiddenProjects,
createProject,
handleProjectCreate,
updateProject,
updateCurrentProject,
handleProjectUpdate,
updateProjectBackgroundImage,
updateCurrentProjectBackgroundImage,
deleteProject,
deleteCurrentProject,
handleProjectDelete,
+177 -57
View File
@@ -1,3 +1,8 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import { call, put, select, take } from 'redux-saga/effects';
import { push } from '../../../lib/redux-router';
@@ -7,23 +12,28 @@ import selectors from '../../../selectors';
import actions from '../../../actions';
import api from '../../../api';
import { getAccessToken } from '../../../utils/access-token-storage';
import mergeRecords from '../../../utils/merge-records';
import ActionTypes from '../../../constants/ActionTypes';
import Paths from '../../../constants/Paths';
export function* goTo(pathname) {
yield put(push(pathname));
}
export function* goToRoot() {
yield put(push(Paths.ROOT));
yield call(goTo, Paths.ROOT);
}
export function* goToProject(projectId) {
yield put(push(Paths.PROJECTS.replace(':id', projectId)));
yield call(goTo, Paths.PROJECTS.replace(':id', projectId));
}
export function* goToBoard(boardId) {
yield put(push(Paths.BOARDS.replace(':id', boardId)));
yield call(goTo, Paths.BOARDS.replace(':id', boardId));
}
export function* goToCard(cardId) {
yield put(push(Paths.CARDS.replace(':id', cardId)));
yield call(goTo, Paths.CARDS.replace(':id', cardId));
}
export function* handleLocationChange() {
@@ -45,7 +55,7 @@ export function* handleLocationChange() {
case Paths.OIDC_CALLBACK:
yield call(goToRoot);
return;
break;
default:
}
@@ -55,87 +65,197 @@ export function* handleLocationChange() {
yield take(ActionTypes.CORE_INITIALIZE);
}
let currentBoard = yield select(selectors.selectCurrentBoard);
let currentBoardId = null;
let currentCardId = null;
let isEditModeEnabled;
let board;
let users;
let card;
let users1;
let users2;
let projects;
let boardMemberships;
let labels;
let lists;
let cards;
let cardMemberships;
let cardLabels;
let tasks;
let attachments;
let deletedNotifications;
let cardMemberships1;
let cardMemberships2;
let cardLabels1;
let cardLabels2;
let taskLists1;
let taskLists2;
let tasks1;
let tasks2;
let attachments1;
let attachments2;
let customFieldGroups1;
let customFieldGroups2;
let customFields1;
let customFields2;
let customFieldValues1;
let customFieldValues2;
let notificationsToDelete;
switch (pathsMatch.pattern.path) {
case Paths.BOARDS:
case Paths.CARDS: {
const currentBoard = yield select(selectors.selectCurrentBoard);
case Paths.ROOT:
isEditModeEnabled = false;
if (currentBoard && currentBoard.isFetching === null) {
yield put(actions.handleLocationChange.fetchBoard(currentBoard.id));
break;
case Paths.PROJECTS: {
const boardIds = yield select(selectors.selectBoardIdsForCurrentProject);
try {
({
item: board,
included: {
users,
projects,
boardMemberships,
labels,
lists,
cards,
cardMemberships,
cardLabels,
tasks,
attachments,
},
} = yield call(request, api.getBoard, currentBoard.id, true));
} catch (error) {} // eslint-disable-line no-empty
}
if (pathsMatch.pattern.path === Paths.CARDS) {
const notificationIds = yield select(selectors.selectNotificationIdsForCurrentCard);
if (notificationIds && notificationIds.length > 0) {
try {
({ items: deletedNotifications } = yield call(
request,
api.updateNotifications,
notificationIds,
{
isRead: true,
},
));
} catch (error) {} // eslint-disable-line no-empty
}
if (boardIds && boardIds.length === 0) {
isEditModeEnabled = true;
}
break;
}
case Paths.BOARDS:
if (currentBoard) {
({ id: currentBoardId } = currentBoard);
if (currentBoard.isFetching === null) {
yield put(actions.handleLocationChange.fetchBoard(currentBoard.id));
try {
({
item: board,
included: {
projects,
boardMemberships,
labels,
lists,
cards,
users: users1,
cardMemberships: cardMemberships1,
cardLabels: cardLabels1,
taskLists: taskLists1,
tasks: tasks1,
attachments: attachments1,
customFieldGroups: customFieldGroups1,
customFields: customFields1,
customFieldValues: customFieldValues1,
},
} = yield call(request, api.getBoard, currentBoard.id, true));
} catch {
/* empty */
}
}
}
break;
case Paths.CARDS:
({ cardId: currentCardId, boardId: currentBoardId } = yield select(selectors.selectPath));
if (!currentCardId) {
yield put(actions.handleLocationChange.fetchContent());
try {
({
item: card,
included: {
users: users1,
cardMemberships: cardMemberships1,
cardLabels: cardLabels1,
taskLists: taskLists1,
tasks: tasks1,
attachments: attachments1,
customFieldGroups: customFieldGroups1,
customFields: customFields1,
customFieldValues: customFieldValues1,
},
} = yield call(request, api.getCard, pathsMatch.params.id));
} catch {
/* empty */
}
if (card) {
({ id: currentCardId } = card);
currentBoard = yield select(selectors.selectBoardById, card.boardId);
if (currentBoard) {
({ id: currentBoardId } = currentBoard);
if (currentBoard.isFetching === null) {
try {
({
item: board,
included: {
projects,
boardMemberships,
labels,
lists,
cards,
users: users2,
cardMemberships: cardMemberships2,
cardLabels: cardLabels2,
taskLists: taskLists2,
tasks: tasks2,
attachments: attachments2,
customFieldGroups: customFieldGroups2,
customFields: customFields2,
customFieldValues: customFieldValues2,
},
} = yield call(request, api.getBoard, card.boardId, true));
} catch {
/* empty */
}
}
}
}
}
if (currentCardId) {
const notificationIds = yield select(
selectors.selectNotificationIdsByCardId,
currentCardId,
);
if (notificationIds.length > 0) {
try {
({
included: { notifications: notificationsToDelete },
} = yield call(request, api.readCardNotifications, currentCardId));
} catch {
/* empty */
}
}
}
break;
default:
}
yield put(
actions.handleLocationChange(
pathsMatch.pathname,
currentBoardId,
currentCardId,
isEditModeEnabled,
board,
users,
mergeRecords(users1, users2),
projects,
boardMemberships,
labels,
lists,
cards,
cardMemberships,
cardLabels,
tasks,
attachments,
deletedNotifications,
mergeRecords(card && [card], cards),
mergeRecords(cardMemberships1, cardMemberships2),
mergeRecords(cardLabels1, cardLabels2),
mergeRecords(taskLists1, taskLists2),
mergeRecords(tasks1, tasks2),
mergeRecords(attachments1, attachments2),
mergeRecords(customFieldGroups1, customFieldGroups2),
mergeRecords(customFields1, customFields2),
mergeRecords(customFieldValues1, customFieldValues2),
notificationsToDelete,
),
);
}
export default {
goTo,
goToRoot,
goToProject,
goToBoard,
+40 -5
View File
@@ -1,24 +1,34 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import { call, put, select } from 'redux-saga/effects';
import request from '../request';
import requests from '../requests';
import selectors from '../../../selectors';
import actions from '../../../actions';
import api from '../../../api';
export function* handleSocketDisconnect() {
yield put(actions.handleSocketDisconnect());
}
export function* handleSocketReconnect() {
const currentUserId = yield select(selectors.selectCurrentUserId);
const { boardId } = yield select(selectors.selectPath);
const currentUserId = yield select(selectors.selectCurrentUserId);
yield put(actions.handleSocketReconnect.fetchCore(currentUserId, boardId));
let config;
let user;
let board;
let users;
let projects;
let projectManagers;
let backgroundImages;
let baseCustomFieldGroups;
let boards;
let boardMemberships;
let labels;
@@ -26,18 +36,26 @@ export function* handleSocketReconnect() {
let cards;
let cardMemberships;
let cardLabels;
let taskLists;
let tasks;
let attachments;
let activities;
let customFieldGroups;
let customFields;
let customFieldValues;
let notifications;
let notificationServices;
try {
({ item: config } = yield call(request, api.getConfig));
({
user,
board,
users,
projects,
projectManagers,
backgroundImages,
baseCustomFieldGroups,
boards,
boardMemberships,
labels,
@@ -45,22 +63,29 @@ export function* handleSocketReconnect() {
cards,
cardMemberships,
cardLabels,
taskLists,
tasks,
attachments,
activities,
customFieldGroups,
customFields,
customFieldValues,
notifications,
notificationServices,
} = yield call(requests.fetchCore));
} catch (error) {
} catch {
return;
}
yield put(
actions.handleSocketReconnect(
config,
user,
board,
users,
projects,
projectManagers,
backgroundImages,
baseCustomFieldGroups,
boards,
boardMemberships,
labels,
@@ -68,12 +93,22 @@ export function* handleSocketReconnect() {
cards,
cardMemberships,
cardLabels,
taskLists,
tasks,
attachments,
activities,
customFieldGroups,
customFields,
customFieldValues,
notifications,
notificationServices,
),
);
const isAvailableForCurrentUser = yield select(selectors.isCurrentModalAvailableForCurrentUser);
if (!isAvailableForCurrentUser) {
yield put(actions.closeModal());
}
}
export default {
@@ -0,0 +1,105 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import { call, put, select } from 'redux-saga/effects';
import request from '../request';
import selectors from '../../../selectors';
import actions from '../../../actions';
import api from '../../../api';
import { createLocalId } from '../../../utils/local-id';
export function* createTaskList(cardId, data) {
const localId = yield call(createLocalId);
const nextData = {
...data,
position: yield select(selectors.selectNextTaskListPosition, cardId),
};
yield put(
actions.createTaskList({
...nextData,
cardId,
id: localId,
}),
);
let taskList;
try {
({ item: taskList } = yield call(request, api.createTaskList, cardId, nextData));
} catch (error) {
yield put(actions.createTaskList.failure(localId, error));
return;
}
yield put(actions.createTaskList.success(localId, taskList));
}
export function* createTaskListInCurrentCard(data) {
const { cardId } = yield select(selectors.selectPath);
yield call(createTaskList, cardId, data);
}
export function* handleTaskListCreate(taskList) {
yield put(actions.handleTaskListCreate(taskList));
}
export function* updateTaskList(id, data) {
yield put(actions.updateTaskList(id, data));
let taskList;
try {
({ item: taskList } = yield call(request, api.updateTaskList, id, data));
} catch (error) {
yield put(actions.updateTaskList.failure(id, error));
return;
}
yield put(actions.updateTaskList.success(taskList));
}
export function* handleTaskListUpdate(taskList) {
yield put(actions.handleTaskListUpdate(taskList));
}
export function* moveTaskList(id, index) {
const { cardId } = yield select(selectors.selectTaskListById, id);
const position = yield select(selectors.selectNextTaskListPosition, cardId, index, id);
yield call(updateTaskList, id, {
position,
});
}
export function* deleteTaskList(id) {
yield put(actions.deleteTaskList(id));
let taskList;
try {
({ item: taskList } = yield call(request, api.deleteTaskList, id));
} catch (error) {
yield put(actions.deleteTaskList.failure(id, error));
return;
}
yield put(actions.deleteTaskList.success(taskList));
}
export function* handleTaskListDelete(taskList) {
yield put(actions.handleTaskListDelete(taskList));
}
export default {
createTaskList,
createTaskListInCurrentCard,
handleTaskListCreate,
updateTaskList,
handleTaskListUpdate,
moveTaskList,
deleteTaskList,
handleTaskListDelete,
};
+14 -16
View File
@@ -1,3 +1,8 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import { call, put, select } from 'redux-saga/effects';
import request from '../request';
@@ -6,25 +11,25 @@ import actions from '../../../actions';
import api from '../../../api';
import { createLocalId } from '../../../utils/local-id';
export function* createTask(cardId, data) {
export function* createTask(taskListId, data) {
const localId = yield call(createLocalId);
const nextData = {
...data,
position: yield select(selectors.selectNextTaskPosition, cardId),
position: yield select(selectors.selectNextTaskPosition, taskListId),
};
const localId = yield call(createLocalId);
yield put(
actions.createTask({
...nextData,
cardId,
taskListId,
id: localId,
}),
);
let task;
try {
({ item: task } = yield call(request, api.createTask, cardId, nextData));
({ item: task } = yield call(request, api.createTask, taskListId, nextData));
} catch (error) {
yield put(actions.createTask.failure(localId, error));
return;
@@ -33,12 +38,6 @@ export function* createTask(cardId, data) {
yield put(actions.createTask.success(localId, task));
}
export function* createTaskInCurrentCard(data) {
const { cardId } = yield select(selectors.selectPath);
yield call(createTask, cardId, data);
}
export function* handleTaskCreate(task) {
yield put(actions.handleTaskCreate(task));
}
@@ -61,11 +60,11 @@ export function* handleTaskUpdate(task) {
yield put(actions.handleTaskUpdate(task));
}
export function* moveTask(id, index) {
const { cardId } = yield select(selectors.selectTaskById, id);
const position = yield select(selectors.selectNextTaskPosition, cardId, index, id);
export function* moveTask(id, taskListId, index) {
const position = yield select(selectors.selectNextTaskPosition, taskListId, index, id);
yield call(updateTask, id, {
taskListId,
position,
});
}
@@ -90,7 +89,6 @@ export function* handleTaskDelete(task) {
export default {
createTask,
createTaskInCurrentCard,
handleTaskCreate,
updateTask,
handleTaskUpdate,
+182 -29
View File
@@ -1,11 +1,20 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import { call, put, select } from 'redux-saga/effects';
import { changeCoreLanguage, logout } from './core';
import request from '../request';
import requests from '../requests';
import selectors from '../../../selectors';
import actions from '../../../actions';
import api from '../../../api';
import { setAccessToken } from '../../../utils/access-token-storage';
import mergeRecords from '../../../utils/merge-records';
import { isUserAdminOrProjectOwner } from '../../../utils/record-helpers';
import { UserRoles } from '../../../constants/Enums';
export function* createUser(data) {
yield put(actions.createUser(data));
@@ -44,21 +53,147 @@ export function* updateUser(id, data) {
}
export function* updateCurrentUser(data) {
const id = yield select(selectors.selectCurrentUserId);
const currentUserId = yield select(selectors.selectCurrentUserId);
yield call(updateUser, id, data);
yield call(updateUser, currentUserId, data);
}
export function* handleUserUpdate(user) {
const currentUser = yield select(selectors.selectCurrentUser);
const isCurrent = user.id === currentUser.id;
const prevUser = yield select(selectors.selectUserById, user.id);
let users;
if (isCurrent && !currentUser.isAdmin && user.isAdmin) {
({ items: users } = yield call(request, api.getUsers));
const isChangedToAdminOrProjectOwner =
(!prevUser || (prevUser.role !== user.role && prevUser.role !== UserRoles.ADMIN)) &&
isUserAdminOrProjectOwner(user);
const currentUser = yield select(selectors.selectCurrentUser);
const isCurrentUser = user.id === currentUser.id;
let config;
let board;
let users1;
let users2;
let users3;
let projects;
let projectManagers;
let backgroundImages;
let baseCustomFieldGroups;
let boards;
let boardMemberships1;
let boardMemberships2;
let labels;
let lists;
let cards;
let cardMemberships;
let cardLabels;
let taskLists;
let tasks;
let attachments;
let customFieldGroups;
let customFields1;
let customFields2;
let customFieldValues;
let notificationsToDelete;
let notificationServices;
if (isCurrentUser && isChangedToAdminOrProjectOwner) {
const { boardId } = yield select(selectors.selectPath);
({ items: users1 } = yield call(request, api.getUsers));
if (user.role === UserRoles.ADMIN) {
({ item: config } = yield call(request, api.getConfig));
({
items: projects,
included: {
projectManagers,
backgroundImages,
baseCustomFieldGroups,
boards,
notificationServices,
users: users2,
boardMemberships: boardMemberships1,
customFields: customFields1,
},
} = yield call(request, api.getProjects));
if (boardId === null) {
let body;
try {
body = yield call(requests.fetchBoardByCurrentPath);
} catch {
/* empty */
}
if (body) {
({
board,
labels,
lists,
cards,
cardMemberships,
cardLabels,
taskLists,
tasks,
attachments,
customFieldGroups,
customFieldValues,
users: users3,
boardMemberships: boardMemberships2,
customFields: customFields2,
} = body);
if (body.card) {
notificationsToDelete = yield select(
selectors.selectNotificationsByCardId,
body.card.id,
);
}
}
}
}
}
yield put(actions.handleUserUpdate(user, users, isCurrent));
const projectIds = yield select(selectors.selectProjectIdsForCurrentUser);
const boardIds = yield select(selectors.selectBoardIdsForCurrentUser);
yield put(
actions.handleUserUpdate(
user,
projectIds,
boardIds,
config,
board,
mergeRecords(users1, users2, users3),
projects,
projectManagers,
backgroundImages,
baseCustomFieldGroups,
boards,
mergeRecords(boardMemberships1, boardMemberships2),
labels,
lists,
cards,
cardMemberships,
cardLabels,
taskLists,
tasks,
attachments,
customFieldGroups,
mergeRecords(customFields1, customFields2),
customFieldValues,
notificationsToDelete,
notificationServices,
),
);
if (isCurrentUser) {
const isAvailableForCurrentUser = yield select(selectors.isCurrentModalAvailableForCurrentUser);
if (!isAvailableForCurrentUser) {
yield put(actions.closeModal());
}
}
}
// TODO: add loading state
@@ -71,9 +206,9 @@ export function* updateUserLanguage(id, language) {
}
export function* updateCurrentUserLanguage(language) {
const id = yield select(selectors.selectCurrentUserId);
const currentUserId = yield select(selectors.selectCurrentUserId);
yield call(updateUserLanguage, id, language);
yield call(updateUserLanguage, currentUserId, language);
}
export function* updateUserEmail(id, data) {
@@ -91,9 +226,9 @@ export function* updateUserEmail(id, data) {
}
export function* updateCurrentUserEmail(data) {
const id = yield select(selectors.selectCurrentUserId);
const currentUserId = yield select(selectors.selectCurrentUserId);
yield call(updateUserEmail, id, data);
yield call(updateUserEmail, currentUserId, data);
}
export function* clearUserEmailUpdateError(id) {
@@ -101,9 +236,9 @@ export function* clearUserEmailUpdateError(id) {
}
export function* clearCurrentUserEmailUpdateError() {
const id = yield select(selectors.selectCurrentUserId);
const currentUserId = yield select(selectors.selectCurrentUserId);
yield call(clearUserEmailUpdateError, id);
yield call(clearUserEmailUpdateError, currentUserId);
}
export function* updateUserPassword(id, data) {
@@ -134,9 +269,9 @@ export function* updateUserPassword(id, data) {
}
export function* updateCurrentUserPassword(data) {
const id = yield select(selectors.selectCurrentUserId);
const currentUserId = yield select(selectors.selectCurrentUserId);
yield call(updateUserPassword, id, data);
yield call(updateUserPassword, currentUserId, data);
}
export function* clearUserPasswordUpdateError(id) {
@@ -144,9 +279,9 @@ export function* clearUserPasswordUpdateError(id) {
}
export function* clearCurrentUserPasswordUpdateError() {
const id = yield select(selectors.selectCurrentUserId);
const currentUserId = yield select(selectors.selectCurrentUserId);
yield call(clearUserPasswordUpdateError, id);
yield call(clearUserPasswordUpdateError, currentUserId);
}
export function* updateUserUsername(id, data) {
@@ -164,9 +299,9 @@ export function* updateUserUsername(id, data) {
}
export function* updateCurrentUserUsername(data) {
const id = yield select(selectors.selectCurrentUserId);
const currentUserId = yield select(selectors.selectCurrentUserId);
yield call(updateUserUsername, id, data);
yield call(updateUserUsername, currentUserId, data);
}
export function* clearUserUsernameUpdateError(id) {
@@ -174,9 +309,9 @@ export function* clearUserUsernameUpdateError(id) {
}
export function* clearCurrentUserUsernameUpdateError() {
const id = yield select(selectors.selectCurrentUserId);
const currentUserId = yield select(selectors.selectCurrentUserId);
yield call(clearUserUsernameUpdateError, id);
yield call(clearUserUsernameUpdateError, currentUserId);
}
export function* updateUserAvatar(id, data) {
@@ -194,9 +329,9 @@ export function* updateUserAvatar(id, data) {
}
export function* updateCurrentUserAvatar(data) {
const id = yield select(selectors.selectCurrentUserId);
const currentUserId = yield select(selectors.selectCurrentUserId);
yield call(updateUserAvatar, id, data);
yield call(updateUserAvatar, currentUserId, data);
}
export function* deleteUser(id) {
@@ -248,6 +383,12 @@ export function* addUserToCurrentCard(id) {
yield call(addUserToCard, id, cardId);
}
export function* addCurrentUserToCurrentCard() {
const currentUserId = yield select(selectors.selectCurrentUserId);
yield call(addUserToCurrentCard, currentUserId);
}
export function* handleUserToCardAdd(cardMembership) {
yield put(actions.handleUserToCardAdd(cardMembership));
}
@@ -272,22 +413,32 @@ export function* removeUserFromCurrentCard(id) {
yield call(removeUserFromCard, id, cardId);
}
export function* removeCurrentUserFromCurrentCard() {
const currentUserId = yield select(selectors.selectCurrentUserId);
yield call(removeUserFromCurrentCard, currentUserId);
}
export function* handleUserFromCardRemove(cardMembership) {
yield put(actions.handleUserFromCardRemove(cardMembership));
}
export function* addUserToBoardFilter(id, boardId) {
yield put(actions.addUserToBoardFilter(id, boardId));
export function* addUserToBoardFilter(id, boardId, replace) {
const currentListId = yield select(selectors.selectCurrentListId);
yield put(actions.addUserToBoardFilter(id, boardId, replace, currentListId));
}
export function* addUserToFilterInCurrentBoard(id) {
export function* addUserToFilterInCurrentBoard(id, replace) {
const { boardId } = yield select(selectors.selectPath);
yield call(addUserToBoardFilter, id, boardId);
yield call(addUserToBoardFilter, id, boardId, replace);
}
export function* removeUserFromBoardFilter(id, boardId) {
yield put(actions.removeUserFromBoardFilter(id, boardId));
const currentListId = yield select(selectors.selectCurrentListId);
yield put(actions.removeUserFromBoardFilter(id, boardId, currentListId));
}
export function* removeUserFromFilterInCurrentBoard(id) {
@@ -323,9 +474,11 @@ export default {
handleUserDelete,
addUserToCard,
addUserToCurrentCard,
addCurrentUserToCurrentCard,
handleUserToCardAdd,
removeUserFromCard,
removeUserFromCurrentCard,
removeCurrentUserFromCurrentCard,
handleUserFromCardRemove,
addUserToBoardFilter,
addUserToFilterInCurrentBoard,
+5 -10
View File
@@ -1,3 +1,8 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import { all, takeEvery } from 'redux-saga/effects';
import services from '../services';
@@ -8,18 +13,8 @@ export default function* activitiesWatchers() {
takeEvery(EntryActionTypes.ACTIVITIES_IN_CURRENT_CARD_FETCH, () =>
services.fetchActivitiesInCurrentCard(),
),
takeEvery(
EntryActionTypes.ACTIVITIES_DETAILS_IN_CURRENT_CARD_TOGGLE,
({ payload: { isVisible } }) => services.toggleActivitiesDetailsInCurrentCard(isVisible),
),
takeEvery(EntryActionTypes.ACTIVITY_CREATE_HANDLE, ({ payload: { activity } }) =>
services.handleActivityCreate(activity),
),
takeEvery(EntryActionTypes.ACTIVITY_UPDATE_HANDLE, ({ payload: { activity } }) =>
services.handleActivityUpdate(activity),
),
takeEvery(EntryActionTypes.ACTIVITY_DELETE_HANDLE, ({ payload: { activity } }) =>
services.handleActivityDelete(activity),
),
]);
}
@@ -1,3 +1,8 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import { all, takeEvery } from 'redux-saga/effects';
import services from '../services';
@@ -0,0 +1,29 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import { all, takeEvery } from 'redux-saga/effects';
import services from '../services';
import EntryActionTypes from '../../../constants/EntryActionTypes';
export default function* backgroundImagesWatchers() {
yield all([
takeEvery(
EntryActionTypes.BACKGROUND_IMAGE_IN_CURRENT_PROJECT_CREATE,
({ payload: { data } }) => services.createBackgroundImageInCurrentProject(data),
),
takeEvery(
EntryActionTypes.BACKGROUND_IMAGE_CREATE_HANDLE,
({ payload: { backgroundImage, requestId } }) =>
services.handleBackgroundImageCreate(backgroundImage, requestId),
),
takeEvery(EntryActionTypes.BACKGROUND_IMAGE_DELETE, ({ payload: { id } }) =>
services.deleteBackgroundImage(id),
),
takeEvery(EntryActionTypes.BACKGROUND_IMAGE_DELETE_HANDLE, ({ payload: { backgroundImage } }) =>
services.handleBackgroundImageDelete(backgroundImage),
),
]);
}
@@ -0,0 +1,39 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import { all, takeEvery } from 'redux-saga/effects';
import services from '../services';
import EntryActionTypes from '../../../constants/EntryActionTypes';
export default function* baseCustomFieldGroupsWatchers() {
yield all([
takeEvery(
EntryActionTypes.BASE_CUSTOM_FIELD_GROUP_IN_CURRENT_PROJECT_CREATE,
({ payload: { data } }) => services.createBaseCustomFieldGroupInCurrentProject(data),
),
takeEvery(
EntryActionTypes.BASE_CUSTOM_FIELD_GROUP_CREATE_HANDLE,
({ payload: { baseCustomFieldGroup } }) =>
services.handleBaseCustomFieldGroupCreate(baseCustomFieldGroup),
),
takeEvery(EntryActionTypes.BASE_CUSTOM_FIELD_GROUP_UPDATE, ({ payload: { id, data } }) =>
services.updateBaseCustomFieldGroup(id, data),
),
takeEvery(
EntryActionTypes.BASE_CUSTOM_FIELD_GROUP_UPDATE_HANDLE,
({ payload: { baseCustomFieldGroup } }) =>
services.handleBaseCustomFieldGroupUpdate(baseCustomFieldGroup),
),
takeEvery(EntryActionTypes.BASE_CUSTOM_FIELD_GROUP_DELETE, ({ payload: { id } }) =>
services.deleteBaseCustomFieldGroup(id),
),
takeEvery(
EntryActionTypes.BASE_CUSTOM_FIELD_GROUP_DELETE_HANDLE,
({ payload: { baseCustomFieldGroup } }) =>
services.handleBaseCustomFieldGroupDelete(baseCustomFieldGroup),
),
]);
}
@@ -1,3 +1,8 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import { all, takeEvery } from 'redux-saga/effects';
import services from '../services';
@@ -8,8 +13,10 @@ export default function* boardMembershipsWatchers() {
takeEvery(EntryActionTypes.MEMBERSHIP_IN_CURRENT_BOARD_CREATE, ({ payload: { data } }) =>
services.createMembershipInCurrentBoard(data),
),
takeEvery(EntryActionTypes.BOARD_MEMBERSHIP_CREATE_HANDLE, ({ payload: { boardMembership } }) =>
services.handleBoardMembershipCreate(boardMembership),
takeEvery(
EntryActionTypes.BOARD_MEMBERSHIP_CREATE_HANDLE,
({ payload: { boardMembership, users } }) =>
services.handleBoardMembershipCreate(boardMembership, users),
),
takeEvery(EntryActionTypes.BOARD_MEMBERSHIP_UPDATE, ({ payload: { id, data } }) =>
services.updateBoardMembership(id, data),
+21 -2
View File
@@ -1,3 +1,8 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import { all, takeEvery } from 'redux-saga/effects';
import services from '../services';
@@ -8,19 +13,33 @@ export default function* boardsWatchers() {
takeEvery(EntryActionTypes.BOARD_IN_CURRENT_PROJECT_CREATE, ({ payload: { data } }) =>
services.createBoardInCurrentProject(data),
),
takeEvery(EntryActionTypes.BOARD_CREATE_HANDLE, ({ payload: { board, requestId } }) =>
services.handleBoardCreate(board, requestId),
takeEvery(
EntryActionTypes.BOARD_CREATE_HANDLE,
({ payload: { board, boardMemberships, requestId } }) =>
services.handleBoardCreate(board, boardMemberships, requestId),
),
takeEvery(EntryActionTypes.BOARD_FETCH, ({ payload: { id } }) => services.fetchBoard(id)),
takeEvery(EntryActionTypes.BOARD_UPDATE, ({ payload: { id, data } }) =>
services.updateBoard(id, data),
),
takeEvery(EntryActionTypes.CURRENT_BOARD_UPDATE, ({ payload: { data } }) =>
services.updateCurrentBoard(data),
),
takeEvery(EntryActionTypes.BOARD_UPDATE_HANDLE, ({ payload: { board } }) =>
services.handleBoardUpdate(board),
),
takeEvery(EntryActionTypes.BOARD_MOVE, ({ payload: { id, index } }) =>
services.moveBoard(id, index),
),
takeEvery(EntryActionTypes.CONTEXT_IN_CURRENT_BOARD_UPDATE, ({ payload: { value } }) =>
services.updateContextInCurrentBoard(value),
),
takeEvery(EntryActionTypes.VIEW_IN_CURRENT_BOARD_UPDATE, ({ payload: { value } }) =>
services.updateViewInCurrentBoard(value),
),
takeEvery(EntryActionTypes.IN_CURRENT_BOARD_SEARCH, ({ payload: { value } }) =>
services.searchInCurrentBoard(value),
),
takeEvery(EntryActionTypes.BOARD_DELETE, ({ payload: { id } }) => services.deleteBoard(id)),
takeEvery(EntryActionTypes.BOARD_DELETE_HANDLE, ({ payload: { board } }) =>
services.handleBoardDelete(board),
+39 -7
View File
@@ -1,3 +1,8 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import { all, takeEvery } from 'redux-saga/effects';
import services from '../services';
@@ -5,9 +10,22 @@ import EntryActionTypes from '../../../constants/EntryActionTypes';
export default function* cardsWatchers() {
yield all([
takeEvery(EntryActionTypes.CARDS_IN_CURRENT_LIST_FETCH, () =>
services.fetchCardsInCurrentList(),
),
takeEvery(EntryActionTypes.CARDS_UPDATE_HANDLE, ({ payload: { cards, activities } }) =>
services.handleCardsUpdate(cards, activities),
),
takeEvery(EntryActionTypes.CARD_CREATE, ({ payload: { listId, data, autoOpen } }) =>
services.createCard(listId, data, autoOpen),
),
takeEvery(EntryActionTypes.CARD_IN_CURRENT_LIST_CREATE, ({ payload: { data, autoOpen } }) =>
services.createCardInCurrentList(data, autoOpen),
),
takeEvery(
EntryActionTypes.CARD_IN_FIRST_FINITE_LIST_CREATE,
({ payload: { data, autoOpen } }) => services.createCardInFirstFiniteList(data, autoOpen),
),
takeEvery(EntryActionTypes.CARD_CREATE_HANDLE, ({ payload: { card } }) =>
services.handleCardCreate(card),
),
@@ -23,24 +41,38 @@ export default function* cardsWatchers() {
takeEvery(EntryActionTypes.CARD_MOVE, ({ payload: { id, listId, index } }) =>
services.moveCard(id, listId, index),
),
takeEvery(EntryActionTypes.CURRENT_CARD_MOVE, ({ payload: { listId, index } }) =>
services.moveCurrentCard(listId, index),
takeEvery(EntryActionTypes.CURRENT_CARD_MOVE, ({ payload: { listId, index, autoClose } }) =>
services.moveCurrentCard(listId, index, autoClose),
),
takeEvery(EntryActionTypes.CARD_TO_ARCHIVE_MOVE, ({ payload: { id } }) =>
services.moveCardToArchive(id),
),
takeEvery(EntryActionTypes.CURRENT_CARD_TO_ARCHIVE_MOVE, () =>
services.moveCurrentCardToArchive(),
),
takeEvery(EntryActionTypes.CARD_TO_TRASH_MOVE, ({ payload: { id } }) =>
services.moveCardToTrash(id),
),
takeEvery(EntryActionTypes.CURRENT_CARD_TO_TRASH_MOVE, () => services.moveCurrentCardToTrash()),
takeEvery(EntryActionTypes.CARD_TRANSFER, ({ payload: { id, boardId, listId, index } }) =>
services.transferCard(id, boardId, listId, index),
),
takeEvery(EntryActionTypes.CURRENT_CARD_TRANSFER, ({ payload: { boardId, listId, index } }) =>
services.transferCurrentCard(boardId, listId, index),
),
takeEvery(EntryActionTypes.CARD_DUPLICATE, ({ payload: { id } }) => services.duplicateCard(id)),
takeEvery(EntryActionTypes.CURRENT_CARD_DUPLICATE, () => services.duplicateCurrentCard()),
takeEvery(EntryActionTypes.CARD_DUPLICATE, ({ payload: { id, data } }) =>
services.duplicateCard(id, data),
),
takeEvery(EntryActionTypes.CURRENT_CARD_DUPLICATE, ({ payload: { data } }) =>
services.duplicateCurrentCard(data),
),
takeEvery(EntryActionTypes.TO_ADJACENT_CARD_GO, ({ payload: { direction } }) =>
services.goToAdjacentCard(direction),
),
takeEvery(EntryActionTypes.CARD_DELETE, ({ payload: { id } }) => services.deleteCard(id)),
takeEvery(EntryActionTypes.CURRENT_CARD_DELETE, () => services.deleteCurrentCard()),
takeEvery(EntryActionTypes.CARD_DELETE_HANDLE, ({ payload: { card } }) =>
services.handleCardDelete(card),
),
takeEvery(EntryActionTypes.TEXT_FILTER_IN_CURRENT_BOARD, ({ payload: { text } }) =>
services.handleTextFilter(text),
),
]);
}
@@ -1,18 +0,0 @@
import { all, takeEvery } from 'redux-saga/effects';
import services from '../services';
import EntryActionTypes from '../../../constants/EntryActionTypes';
export default function* commentActivitiesWatchers() {
yield all([
takeEvery(EntryActionTypes.COMMENT_ACTIVITY_IN_CURRENT_CARD_CREATE, ({ payload: { data } }) =>
services.createCommentActivityInCurrentCard(data),
),
takeEvery(EntryActionTypes.COMMENT_ACTIVITY_UPDATE, ({ payload: { id, data } }) =>
services.updateCommentActivity(id, data),
),
takeEvery(EntryActionTypes.COMMENT_ACTIVITY_DELETE, ({ payload: { id } }) =>
services.deleteCommentActivity(id),
),
]);
}
@@ -0,0 +1,33 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import { all, takeEvery } from 'redux-saga/effects';
import services from '../services';
import EntryActionTypes from '../../../constants/EntryActionTypes';
export default function* commentsWatchers() {
yield all([
takeEvery(EntryActionTypes.COMMENTS_IN_CURRENT_CARD_FETCH, () =>
services.fetchCommentsInCurrentCard(),
),
takeEvery(EntryActionTypes.COMMENT_IN_CURRENT_CARD_CREATE, ({ payload: { data } }) =>
services.createCommentInCurrentCard(data),
),
takeEvery(EntryActionTypes.COMMENT_CREATE_HANDLE, ({ payload: { comment, users } }) =>
services.handleCommentCreate(comment, users),
),
takeEvery(EntryActionTypes.COMMENT_UPDATE, ({ payload: { id, data } }) =>
services.updateComment(id, data),
),
takeEvery(EntryActionTypes.COMMENT_UPDATE_HANDLE, ({ payload: { comment } }) =>
services.handleCommentUpdate(comment),
),
takeEvery(EntryActionTypes.COMMENT_DELETE, ({ payload: { id } }) => services.deleteComment(id)),
takeEvery(EntryActionTypes.COMMENT_DELETE_HANDLE, ({ payload: { comment } }) =>
services.handleCommentDelete(comment),
),
]);
}
+14
View File
@@ -1,3 +1,8 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import { all, takeEvery } from 'redux-saga/effects';
import services from '../services';
@@ -5,6 +10,15 @@ import EntryActionTypes from '../../../constants/EntryActionTypes';
export default function* coreWatchers() {
yield all([
takeEvery(EntryActionTypes.FAVORITES_TOGGLE, ({ payload: { isEnabled } }) =>
services.toggleFavorites(isEnabled),
),
takeEvery(EntryActionTypes.EDIT_MODE_TOGGLE, ({ payload: { isEnabled } }) =>
services.toggleEditMode(isEnabled),
),
takeEvery(EntryActionTypes.HOME_VIEW_UPDATE, ({ payload: { value } }) =>
services.updateHomeView(value),
),
takeEvery(EntryActionTypes.LOGOUT, ({ payload: { invalidateAccessToken } }) =>
services.logout(invalidateAccessToken),
),
@@ -0,0 +1,45 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import { all, takeEvery } from 'redux-saga/effects';
import services from '../services';
import EntryActionTypes from '../../../constants/EntryActionTypes';
export default function* customFieldGroupsWatchers() {
yield all([
takeEvery(
EntryActionTypes.CUSTOM_FIELD_GROUP_IN_CURRENT_BOARD_CREATE,
({ payload: { data } }) => services.createCustomFieldGroupInCurrentBoard(data),
),
takeEvery(EntryActionTypes.CUSTOM_FIELD_GROUP_IN_CURRENT_CARD_CREATE, ({ payload: { data } }) =>
services.createCustomFieldGroupInCurrentCard(data),
),
takeEvery(
EntryActionTypes.CUSTOM_FIELD_GROUP_CREATE_HANDLE,
({ payload: { customFieldGroup } }) =>
services.handleCustomFieldGroupCreate(customFieldGroup),
),
takeEvery(EntryActionTypes.CUSTOM_FIELD_GROUP_UPDATE, ({ payload: { id, data } }) =>
services.updateCustomFieldGroup(id, data),
),
takeEvery(
EntryActionTypes.CUSTOM_FIELD_GROUP_UPDATE_HANDLE,
({ payload: { customFieldGroup } }) =>
services.handleCustomFieldGroupUpdate(customFieldGroup),
),
takeEvery(EntryActionTypes.CUSTOM_FIELD_GROUP_MOVE, ({ payload: { id, index } }) =>
services.moveCustomFieldGroup(id, index),
),
takeEvery(EntryActionTypes.CUSTOM_FIELD_GROUP_DELETE, ({ payload: { id } }) =>
services.deleteCustomFieldGroup(id),
),
takeEvery(
EntryActionTypes.CUSTOM_FIELD_GROUP_DELETE_HANDLE,
({ payload: { customFieldGroup } }) =>
services.handleCustomFieldGroupDelete(customFieldGroup),
),
]);
}
@@ -0,0 +1,34 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import { all, takeEvery } from 'redux-saga/effects';
import services from '../services';
import EntryActionTypes from '../../../constants/EntryActionTypes';
export default function* customFieldValuessWatchers() {
yield all([
takeEvery(
EntryActionTypes.CUSTOM_FIELD_VALUE_UPDATE,
({ payload: { cardId, customFieldGroupId, customFieldId, data } }) =>
services.updateCustomFieldValue(cardId, customFieldGroupId, customFieldId, data),
),
takeEvery(
EntryActionTypes.CUSTOM_FIELD_VALUE_UPDATE_HANDLE,
({ payload: { customFieldValue } }) =>
services.handleCustomFieldValueUpdate(customFieldValue),
),
takeEvery(
EntryActionTypes.CUSTOM_FIELD_VALUE_DELETE,
({ payload: { cardId, customFieldGroupId, customFieldId } }) =>
services.deleteCustomFieldValue(cardId, customFieldGroupId, customFieldId),
),
takeEvery(
EntryActionTypes.CUSTOM_FIELD_VALUE_DELETE_HANDLE,
({ payload: { customFieldValue } }) =>
services.handleCustomFieldValueDelete(customFieldValue),
),
]);
}
@@ -0,0 +1,42 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import { all, takeEvery } from 'redux-saga/effects';
import services from '../services';
import EntryActionTypes from '../../../constants/EntryActionTypes';
export default function* customFieldsWatchers() {
yield all([
takeEvery(
EntryActionTypes.CUSTOM_FIELD_IN_BASE_GROUP_CREATE,
({ payload: { baseCustomFieldGroupId, data } }) =>
services.createCustomFieldInBaseGroup(baseCustomFieldGroupId, data),
),
takeEvery(
EntryActionTypes.CUSTOM_FIELD_IN_GROUP_CREATE,
({ payload: { customFieldGroupId, data } }) =>
services.createCustomFieldInGroup(customFieldGroupId, data),
),
takeEvery(EntryActionTypes.CUSTOM_FIELD_CREATE_HANDLE, ({ payload: { customField } }) =>
services.handleCustomFieldCreate(customField),
),
takeEvery(EntryActionTypes.CUSTOM_FIELD_UPDATE, ({ payload: { id, data } }) =>
services.updateCustomField(id, data),
),
takeEvery(EntryActionTypes.CUSTOM_FIELD_UPDATE_HANDLE, ({ payload: { customField } }) =>
services.handleCustomFieldUpdate(customField),
),
takeEvery(EntryActionTypes.CUSTOM_FIELD_MOVE, ({ payload: { id, index } }) =>
services.moveCustomField(id, index),
),
takeEvery(EntryActionTypes.CUSTOM_FIELD_DELETE, ({ payload: { id } }) =>
services.deleteCustomField(id),
),
takeEvery(EntryActionTypes.CUSTOM_FIELD_DELETE_HANDLE, ({ payload: { customField } }) =>
services.handleCustomFieldDelete(customField),
),
]);
}
+21 -2
View File
@@ -1,3 +1,8 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import router from './router';
import socket from './socket';
import core from './core';
@@ -5,16 +10,23 @@ import modals from './modals';
import users from './users';
import projects from './projects';
import projectManagers from './project-managers';
import backgroundImages from './background-images';
import baseCustomFieldGroups from './base-custom-field-groups';
import boards from './boards';
import boardMemberships from './board-memberships';
import labels from './labels';
import lists from './lists';
import cards from './cards';
import taskLists from './task-lists';
import tasks from './tasks';
import attachments from './attachments';
import customFieldGroups from './custom-field-groups';
import customFields from './custom-fields';
import customFieldValues from './custom-field-values';
import comments from './comments';
import activities from './activities';
import commentActivities from './comment-activities';
import notifications from './notifications';
import notificationServices from './notification-services';
export default [
router,
@@ -24,14 +36,21 @@ export default [
users,
projects,
projectManagers,
backgroundImages,
baseCustomFieldGroups,
boards,
boardMemberships,
labels,
lists,
cards,
taskLists,
tasks,
attachments,
customFieldGroups,
customFields,
customFieldValues,
comments,
activities,
commentActivities,
notifications,
notificationServices,
];
+8
View File
@@ -1,3 +1,8 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import { all, takeEvery } from 'redux-saga/effects';
import services from '../services';
@@ -8,6 +13,9 @@ export default function* labelsWatchers() {
takeEvery(EntryActionTypes.LABEL_IN_CURRENT_BOARD_CREATE, ({ payload: { data } }) =>
services.createLabelInCurrentBoard(data),
),
takeEvery(EntryActionTypes.LABEL_FROM_CARD_CREATE, ({ payload: { cardId, data } }) =>
services.createLabelFromCard(cardId, data),
),
takeEvery(EntryActionTypes.LABEL_CREATE_HANDLE, ({ payload: { label } }) =>
services.handleLabelCreate(label),
),
+15 -4
View File
@@ -1,3 +1,8 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import { all, takeEvery } from 'redux-saga/effects';
import services from '../services';
@@ -23,12 +28,18 @@ export default function* listsWatchers() {
takeEvery(EntryActionTypes.LIST_SORT, ({ payload: { id, data } }) =>
services.sortList(id, data),
),
takeEvery(EntryActionTypes.LIST_SORT_HANDLE, ({ payload: { list, cards } }) =>
services.handleListSort(list, cards),
takeEvery(EntryActionTypes.LIST_CARDS_TO_ARCHIVE_LIST_MOVE, ({ payload: { id } }) =>
services.moveListCardsToArchiveList(id),
),
takeEvery(EntryActionTypes.TRASH_LIST_IN_CURRENT_BOARD_CLEAR, () =>
services.clearTrashListInCurrentBoard(),
),
takeEvery(EntryActionTypes.LIST_CLEAR_HANDLE, ({ payload: { list } }) =>
services.handleListClear(list),
),
takeEvery(EntryActionTypes.LIST_DELETE, ({ payload: { id } }) => services.deleteList(id)),
takeEvery(EntryActionTypes.LIST_DELETE_HANDLE, ({ payload: { list } }) =>
services.handleListDelete(list),
takeEvery(EntryActionTypes.LIST_DELETE_HANDLE, ({ payload: { list, cards } }) =>
services.handleListDelete(list, cards),
),
]);
}
+8 -1
View File
@@ -1,3 +1,8 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import { all, takeEvery } from 'redux-saga/effects';
import services from '../services';
@@ -5,7 +10,9 @@ import EntryActionTypes from '../../../constants/EntryActionTypes';
export default function* modalsWatchers() {
yield all([
takeEvery(EntryActionTypes.MODAL_OPEN, ({ payload: { type } }) => services.openModal(type)),
takeEvery(EntryActionTypes.MODAL_OPEN, ({ payload: { type, params } }) =>
services.openModal(type, params),
),
takeEvery(EntryActionTypes.MODAL_CLOSE, () => services.closeModal()),
]);
}
@@ -0,0 +1,46 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import { all, takeEvery } from 'redux-saga/effects';
import services from '../services';
import EntryActionTypes from '../../../constants/EntryActionTypes';
export default function* notificationServicesWatchers() {
yield all([
takeEvery(
EntryActionTypes.NOTIFICATION_SERVICE_IN_CURRENT_USER_CREATE,
({ payload: { data } }) => services.createNotificationServiceInCurrentUser(data),
),
takeEvery(
EntryActionTypes.NOTIFICATION_SERVICE_IN_BOARD_CREATE,
({ payload: { boardId, data } }) => services.createNotificationServiceInBoard(boardId, data),
),
takeEvery(
EntryActionTypes.NOTIFICATION_SERVICE_CREATE_HANDLE,
({ payload: { notificationService } }) =>
services.handleNotificationServiceCreate(notificationService),
),
takeEvery(EntryActionTypes.NOTIFICATION_SERVICE_UPDATE, ({ payload: { id, data } }) =>
services.updateNotificationService(id, data),
),
takeEvery(
EntryActionTypes.NOTIFICATION_SERVICE_UPDATE_HANDLE,
({ payload: { notificationService } }) =>
services.handleNotificationServiceUpdate(notificationService),
),
takeEvery(EntryActionTypes.NOTIFICATION_SERVICE_TEST, ({ payload: { id } }) =>
services.testNotificationService(id),
),
takeEvery(EntryActionTypes.NOTIFICATION_SERVICE_DELETE, ({ payload: { id } }) =>
services.deleteNotificationService(id),
),
takeEvery(
EntryActionTypes.NOTIFICATION_SERVICE_DELETE_HANDLE,
({ payload: { notificationService } }) =>
services.handleNotificationServiceDelete(notificationService),
),
]);
}
@@ -1,3 +1,8 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import { all, takeEvery } from 'redux-saga/effects';
import services from '../services';
@@ -5,8 +10,9 @@ import EntryActionTypes from '../../../constants/EntryActionTypes';
export default function* notificationsWatchers() {
yield all([
takeEvery(EntryActionTypes.NOTIFICATION_CREATE_HANDLE, ({ payload: { notification } }) =>
services.handleNotificationCreate(notification),
takeEvery(EntryActionTypes.ALL_NOTIFICATIONS_DELETE, () => services.deleteAllNotifications()),
takeEvery(EntryActionTypes.NOTIFICATION_CREATE_HANDLE, ({ payload: { notification, users } }) =>
services.handleNotificationCreate(notification, users),
),
takeEvery(EntryActionTypes.NOTIFICATION_DELETE, ({ payload: { id } }) =>
services.deleteNotification(id),
@@ -1,3 +1,8 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import { all, takeEvery } from 'redux-saga/effects';
import services from '../services';
@@ -8,8 +13,10 @@ export default function* projectManagersWatchers() {
takeEvery(EntryActionTypes.MANAGER_IN_CURRENT_PROJECT_CREATE, ({ payload: { data } }) =>
services.createManagerInCurrentProject(data),
),
takeEvery(EntryActionTypes.PROJECT_MANAGER_CREATE_HANDLE, ({ payload: { projectManager } }) =>
services.handleProjectManagerCreate(projectManager),
takeEvery(
EntryActionTypes.PROJECT_MANAGER_CREATE_HANDLE,
({ payload: { projectManager, users } }) =>
services.handleProjectManagerCreate(projectManager, users),
),
takeEvery(EntryActionTypes.PROJECT_MANAGER_DELETE, ({ payload: { id } }) =>
services.deleteProjectManager(id),
+17 -3
View File
@@ -1,3 +1,8 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import { all, takeEvery } from 'redux-saga/effects';
import services from '../services';
@@ -5,21 +10,30 @@ import EntryActionTypes from '../../../constants/EntryActionTypes';
export default function* projectsWatchers() {
yield all([
takeEvery(EntryActionTypes.PROJECTS_SEARCH, ({ payload: { value } }) =>
services.searchProjects(value),
),
takeEvery(EntryActionTypes.PROJECTS_ORDER_UPDATE, ({ payload: { value } }) =>
services.updateProjectsOrder(value),
),
takeEvery(EntryActionTypes.HIDDEN_PROJECTS_TOGGLE, ({ payload: { isVisible } }) =>
services.toggleHiddenProjects(isVisible),
),
takeEvery(EntryActionTypes.PROJECT_CREATE, ({ payload: { data } }) =>
services.createProject(data),
),
takeEvery(EntryActionTypes.PROJECT_CREATE_HANDLE, ({ payload: { project } }) =>
services.handleProjectCreate(project),
),
takeEvery(EntryActionTypes.PROJECT_UPDATE, ({ payload: { id, data } }) =>
services.updateProject(id, data),
),
takeEvery(EntryActionTypes.CURRENT_PROJECT_UPDATE, ({ payload: { data } }) =>
services.updateCurrentProject(data),
),
takeEvery(EntryActionTypes.PROJECT_UPDATE_HANDLE, ({ payload: { project } }) =>
services.handleProjectUpdate(project),
),
takeEvery(EntryActionTypes.CURRENT_PROJECT_BACKGROUND_IMAGE_UPDATE, ({ payload: { data } }) =>
services.updateCurrentProjectBackgroundImage(data),
),
takeEvery(EntryActionTypes.CURRENT_PROJECT_DELETE, () => services.deleteCurrentProject()),
takeEvery(EntryActionTypes.PROJECT_DELETE_HANDLE, ({ payload: { project } }) =>
services.handleProjectDelete(project),
+5
View File
@@ -1,3 +1,8 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import { takeEvery } from 'redux-saga/effects';
import { LOCATION_CHANGE_HANDLE } from '../../../lib/redux-router';
+202 -43
View File
@@ -1,3 +1,8 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import { eventChannel } from 'redux-saga';
import { all, call, cancelled, put, take, takeEvery } from 'redux-saga/effects';
@@ -20,17 +25,21 @@ const createSocketEventsChannel = () =>
emit(entryActions.logout(false));
};
const handleUserCreate = api.makeHandleUserCreate(({ item }) => {
const handleConfigUpdate = ({ item }) => {
emit(entryActions.handleConfigUpdate(item));
};
const handleUserCreate = ({ item }) => {
emit(entryActions.handleUserCreate(item));
});
};
const handleUserUpdate = api.makeHandleUserUpdate(({ item }) => {
const handleUserUpdate = ({ item }) => {
emit(entryActions.handleUserUpdate(item));
});
};
const handleUserDelete = api.makeHandleUserDelete(({ item }) => {
const handleUserDelete = ({ item }) => {
emit(entryActions.handleUserDelete(item));
});
};
const handleProjectCreate = ({ item }) => {
emit(entryActions.handleProjectCreate(item));
@@ -44,16 +53,36 @@ const createSocketEventsChannel = () =>
emit(entryActions.handleProjectDelete(item));
};
const handleProjectManagerCreate = api.makeHandleProjectManagerCreate(({ item }) => {
emit(entryActions.handleProjectManagerCreate(item));
});
const handleProjectManagerCreate = ({ item, included: { users } }) => {
emit(entryActions.handleProjectManagerCreate(item, users));
};
const handleProjectManagerDelete = api.makeHandleProjectManagerDelete(({ item }) => {
const handleProjectManagerDelete = ({ item }) => {
emit(entryActions.handleProjectManagerDelete(item));
});
};
const handleBoardCreate = ({ item, requestId }) => {
emit(entryActions.handleBoardCreate(item, requestId));
const handleBackgroundImageCreate = ({ item, requestId }) => {
emit(entryActions.handleBackgroundImageCreate(item, requestId));
};
const handleBackgroundImageDelete = ({ item }) => {
emit(entryActions.handleBackgroundImageDelete(item));
};
const handleBaseCustomFieldGroupCreate = ({ item }) => {
emit(entryActions.handleBaseCustomFieldGroupCreate(item));
};
const handleBaseCustomFieldGroupUpdate = ({ item }) => {
emit(entryActions.handleBaseCustomFieldGroupUpdate(item));
};
const handleBaseCustomFieldGroupDelete = ({ item }) => {
emit(entryActions.handleBaseCustomFieldGroupDelete(item));
};
const handleBoardCreate = ({ item, included: { boardMemberships }, requestId }) => {
emit(entryActions.handleBoardCreate(item, boardMemberships, requestId));
};
const handleBoardUpdate = ({ item }) => {
@@ -64,17 +93,17 @@ const createSocketEventsChannel = () =>
emit(entryActions.handleBoardDelete(item));
};
const handleBoardMembershipCreate = api.makeHandleBoardMembershipCreate(({ item }) => {
emit(entryActions.handleBoardMembershipCreate(item));
});
const handleBoardMembershipCreate = ({ item, included: { users } = {} }) => {
emit(entryActions.handleBoardMembershipCreate(item, users));
};
const handleBoardMembershipUpdate = api.makeHandleBoardMembershipUpdate(({ item }) => {
const handleBoardMembershipUpdate = ({ item }) => {
emit(entryActions.handleBoardMembershipUpdate(item));
});
};
const handleBoardMembershipDelete = api.makeHandleBoardMembershipDelete(({ item }) => {
const handleBoardMembershipDelete = ({ item }) => {
emit(entryActions.handleBoardMembershipDelete(item));
});
};
const handleListCreate = ({ item }) => {
emit(entryActions.handleListCreate(item));
@@ -84,14 +113,14 @@ const createSocketEventsChannel = () =>
emit(entryActions.handleListUpdate(item));
};
const handleListSort = api.makeHandleListSort(({ item, included: { cards } }) => {
emit(entryActions.handleListSort(item, cards));
});
const handleListDelete = ({ item }) => {
emit(entryActions.handleListDelete(item));
const handleListClear = ({ item }) => {
emit(entryActions.handleListClear(item));
};
const handleListDelete = api.makeHandleListDelete(({ item, included: { cards } }) => {
emit(entryActions.handleListDelete(item, cards));
});
const handleLabelCreate = ({ item }) => {
emit(entryActions.handleLabelCreate(item));
};
@@ -104,6 +133,12 @@ const createSocketEventsChannel = () =>
emit(entryActions.handleLabelDelete(item));
};
const handleCardsUpdate = api.makeHandleCardsUpdate(
({ items, included: { activities } = {} }) => {
emit(entryActions.handleCardsUpdate(items, activities));
},
);
const handleCardCreate = api.makeHandleCardCreate(({ item }) => {
emit(entryActions.handleCardCreate(item));
});
@@ -132,6 +167,18 @@ const createSocketEventsChannel = () =>
emit(entryActions.handleLabelFromCardRemove(item));
};
const handleTaskListCreate = ({ item }) => {
emit(entryActions.handleTaskListCreate(item));
};
const handleTaskListUpdate = ({ item }) => {
emit(entryActions.handleTaskListUpdate(item));
};
const handleTaskListDelete = ({ item }) => {
emit(entryActions.handleTaskListDelete(item));
};
const handleTaskCreate = ({ item }) => {
emit(entryActions.handleTaskCreate(item));
};
@@ -156,31 +203,83 @@ const createSocketEventsChannel = () =>
emit(entryActions.handleAttachmentDelete(item));
});
const handleCustomFieldGroupCreate = ({ item }) => {
emit(entryActions.handleCustomFieldGroupCreate(item));
};
const handleCustomFieldGroupUpdate = ({ item }) => {
emit(entryActions.handleCustomFieldGroupUpdate(item));
};
const handleCustomFieldGroupDelete = ({ item }) => {
emit(entryActions.handleCustomFieldGroupDelete(item));
};
const handleCustomFieldCreate = ({ item }) => {
emit(entryActions.handleCustomFieldCreate(item));
};
const handleCustomFieldUpdate = ({ item }) => {
emit(entryActions.handleCustomFieldUpdate(item));
};
const handleCustomFieldDelete = ({ item }) => {
emit(entryActions.handleCustomFieldDelete(item));
};
const handleCustomFieldValueUpdate = ({ item }) => {
emit(entryActions.handleCustomFieldValueUpdate(item));
};
const handleCustomFieldValueDelete = ({ item }) => {
emit(entryActions.handleCustomFieldValueDelete(item));
};
const handleCommentCreate = api.makeHandleCommentCreate(({ item, included: { users } }) => {
emit(entryActions.handleCommentCreate(item, users));
});
const handleCommentUpdate = api.makeHandleCommentUpdate(({ item }) => {
emit(entryActions.handleCommentUpdate(item));
});
const handleCommentDelete = api.makeHandleCommentDelete(({ item }) => {
emit(entryActions.handleCommentDelete(item));
});
const handleActivityCreate = api.makeHandleActivityCreate(({ item }) => {
emit(entryActions.handleActivityCreate(item));
});
const handleActivityUpdate = api.makeHandleActivityUpdate(({ item }) => {
emit(entryActions.handleActivityUpdate(item));
});
const handleActivityDelete = api.makeHandleActivityDelete(({ item }) => {
emit(entryActions.handleActivityDelete(item));
});
const handleNotificationCreate = api.makeHandleNotificationCreate(({ item }) => {
emit(entryActions.handleNotificationCreate(item));
});
const handleNotificationCreate = api.makeHandleNotificationCreate(
({ item, included: { users } }) => {
emit(entryActions.handleNotificationCreate(item, users));
},
);
const handleNotificationUpdate = api.makeHandleNotificationUpdate(({ item }) => {
emit(entryActions.handleNotificationDelete(item));
});
const handleNotificationServiceCreate = ({ item }) => {
emit(entryActions.handleNotificationServiceCreate(item));
};
const handleNotificationServiceUpdate = ({ item }) => {
emit(entryActions.handleNotificationServiceUpdate(item));
};
const handleNotificationServiceDelete = ({ item }) => {
emit(entryActions.handleNotificationServiceDelete(item));
};
socket.on('disconnect', handleDisconnect);
socket.on('reconnect', handleReconnect);
socket.on('logout', handleLogout);
socket.on('configUpdate', handleConfigUpdate);
socket.on('userCreate', handleUserCreate);
socket.on('userUpdate', handleUserUpdate);
socket.on('userDelete', handleUserDelete);
@@ -192,6 +291,13 @@ const createSocketEventsChannel = () =>
socket.on('projectManagerCreate', handleProjectManagerCreate);
socket.on('projectManagerDelete', handleProjectManagerDelete);
socket.on('backgroundImageCreate', handleBackgroundImageCreate);
socket.on('backgroundImageDelete', handleBackgroundImageDelete);
socket.on('baseCustomFieldGroupCreate', handleBaseCustomFieldGroupCreate);
socket.on('baseCustomFieldGroupUpdate', handleBaseCustomFieldGroupUpdate);
socket.on('baseCustomFieldGroupDelete', handleBaseCustomFieldGroupDelete);
socket.on('boardCreate', handleBoardCreate);
socket.on('boardUpdate', handleBoardUpdate);
socket.on('boardDelete', handleBoardDelete);
@@ -202,13 +308,14 @@ const createSocketEventsChannel = () =>
socket.on('listCreate', handleListCreate);
socket.on('listUpdate', handleListUpdate);
socket.on('listSort', handleListSort);
socket.on('listClear', handleListClear);
socket.on('listDelete', handleListDelete);
socket.on('labelCreate', handleLabelCreate);
socket.on('labelUpdate', handleLabelUpdate);
socket.on('labelDelete', handleLabelDelete);
socket.on('cardsUpdate', handleCardsUpdate);
socket.on('cardCreate', handleCardCreate);
socket.on('cardUpdate', handleCardUpdate);
socket.on('cardDelete', handleCardDelete);
@@ -219,6 +326,10 @@ const createSocketEventsChannel = () =>
socket.on('cardLabelCreate', handleLabelToCardAdd);
socket.on('cardLabelDelete', handleLabelFromCardRemove);
socket.on('taskListCreate', handleTaskListCreate);
socket.on('taskListUpdate', handleTaskListUpdate);
socket.on('taskListDelete', handleTaskListDelete);
socket.on('taskCreate', handleTaskCreate);
socket.on('taskUpdate', handleTaskUpdate);
socket.on('taskDelete', handleTaskDelete);
@@ -227,19 +338,38 @@ const createSocketEventsChannel = () =>
socket.on('attachmentUpdate', handleAttachmentUpdate);
socket.on('attachmentDelete', handleAttachmentDelete);
socket.on('customFieldGroupCreate', handleCustomFieldGroupCreate);
socket.on('customFieldGroupUpdate', handleCustomFieldGroupUpdate);
socket.on('customFieldGroupDelete', handleCustomFieldGroupDelete);
socket.on('customFieldCreate', handleCustomFieldCreate);
socket.on('customFieldUpdate', handleCustomFieldUpdate);
socket.on('customFieldDelete', handleCustomFieldDelete);
socket.on('customFieldValueUpdate', handleCustomFieldValueUpdate);
socket.on('customFieldValueDelete', handleCustomFieldValueDelete);
socket.on('commentCreate', handleCommentCreate);
socket.on('commentUpdate', handleCommentUpdate);
socket.on('commentDelete', handleCommentDelete);
socket.on('actionCreate', handleActivityCreate);
socket.on('actionUpdate', handleActivityUpdate);
socket.on('actionDelete', handleActivityDelete);
socket.on('notificationCreate', handleNotificationCreate);
socket.on('notificationUpdate', handleNotificationUpdate);
socket.on('notificationServiceCreate', handleNotificationServiceCreate);
socket.on('notificationServiceUpdate', handleNotificationServiceUpdate);
socket.on('notificationServiceDelete', handleNotificationServiceDelete);
return () => {
socket.off('disconnect', handleDisconnect);
socket.off('reconnect', handleReconnect);
socket.off('logout', handleLogout);
socket.off('configUpdate', handleConfigUpdate);
socket.off('userCreate', handleUserCreate);
socket.off('userUpdate', handleUserUpdate);
socket.off('userDelete', handleUserDelete);
@@ -251,6 +381,13 @@ const createSocketEventsChannel = () =>
socket.off('projectManagerCreate', handleProjectManagerCreate);
socket.off('projectManagerDelete', handleProjectManagerDelete);
socket.off('backgroundImageCreate', handleBackgroundImageCreate);
socket.off('backgroundImageDelete', handleBackgroundImageDelete);
socket.off('baseCustomFieldGroupCreate', handleBaseCustomFieldGroupCreate);
socket.off('baseCustomFieldGroupUpdate', handleBaseCustomFieldGroupUpdate);
socket.off('baseCustomFieldGroupDelete', handleBaseCustomFieldGroupDelete);
socket.off('boardCreate', handleBoardCreate);
socket.off('boardUpdate', handleBoardUpdate);
socket.off('boardDelete', handleBoardDelete);
@@ -261,13 +398,14 @@ const createSocketEventsChannel = () =>
socket.off('listCreate', handleListCreate);
socket.off('listUpdate', handleListUpdate);
socket.off('listSort', handleListSort);
socket.off('listClear', handleListClear);
socket.off('listDelete', handleListDelete);
socket.off('labelCreate', handleLabelCreate);
socket.off('labelUpdate', handleLabelUpdate);
socket.off('labelDelete', handleLabelDelete);
socket.off('cardsUpdate', handleCardsUpdate);
socket.off('cardCreate', handleCardCreate);
socket.off('cardUpdate', handleCardUpdate);
socket.off('cardDelete', handleCardDelete);
@@ -278,6 +416,10 @@ const createSocketEventsChannel = () =>
socket.off('cardLabelCreate', handleLabelToCardAdd);
socket.off('cardLabelDelete', handleLabelFromCardRemove);
socket.off('taskListCreate', handleTaskListCreate);
socket.off('taskListUpdate', handleTaskListUpdate);
socket.off('taskListDelete', handleTaskListDelete);
socket.off('taskCreate', handleTaskCreate);
socket.off('taskUpdate', handleTaskUpdate);
socket.off('taskDelete', handleTaskDelete);
@@ -286,12 +428,29 @@ const createSocketEventsChannel = () =>
socket.off('attachmentUpdate', handleAttachmentUpdate);
socket.off('attachmentDelete', handleAttachmentDelete);
socket.off('customFieldGroupCreate', handleCustomFieldGroupCreate);
socket.off('customFieldGroupUpdate', handleCustomFieldGroupUpdate);
socket.off('customFieldGroupDelete', handleCustomFieldGroupDelete);
socket.off('customFieldCreate', handleCustomFieldCreate);
socket.off('customFieldUpdate', handleCustomFieldUpdate);
socket.off('customFieldDelete', handleCustomFieldDelete);
socket.off('customFieldValueUpdate', handleCustomFieldValueUpdate);
socket.off('customFieldValueDelete', handleCustomFieldValueDelete);
socket.off('commentCreate', handleCommentCreate);
socket.off('commentUpdate', handleCommentUpdate);
socket.off('commentDelete', handleCommentDelete);
socket.off('actionCreate', handleActivityCreate);
socket.off('actionUpdate', handleActivityUpdate);
socket.off('actionDelete', handleActivityDelete);
socket.off('notificationCreate', handleNotificationCreate);
socket.off('notificationUpdate', handleNotificationUpdate);
socket.off('notificationServiceCreate', handleNotificationServiceCreate);
socket.off('notificationServiceUpdate', handleNotificationServiceUpdate);
socket.off('notificationServiceDelete', handleNotificationServiceDelete);
};
});
@@ -0,0 +1,35 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import { all, takeEvery } from 'redux-saga/effects';
import services from '../services';
import EntryActionTypes from '../../../constants/EntryActionTypes';
export default function* taskListsWatchers() {
yield all([
takeEvery(EntryActionTypes.TASK_LIST_IN_CURRENT_CARD_CREATE, ({ payload: { data } }) =>
services.createTaskListInCurrentCard(data),
),
takeEvery(EntryActionTypes.TASK_LIST_CREATE_HANDLE, ({ payload: { taskList } }) =>
services.handleTaskListCreate(taskList),
),
takeEvery(EntryActionTypes.TASK_LIST_UPDATE, ({ payload: { id, data } }) =>
services.updateTaskList(id, data),
),
takeEvery(EntryActionTypes.TASK_LIST_UPDATE_HANDLE, ({ payload: { taskList } }) =>
services.handleTaskListUpdate(taskList),
),
takeEvery(EntryActionTypes.TASK_LIST_MOVE, ({ payload: { id, index } }) =>
services.moveTaskList(id, index),
),
takeEvery(EntryActionTypes.TASK_LIST_DELETE, ({ payload: { id } }) =>
services.deleteTaskList(id),
),
takeEvery(EntryActionTypes.TASK_LIST_DELETE_HANDLE, ({ payload: { taskList } }) =>
services.handleTaskListDelete(taskList),
),
]);
}
+9 -4
View File
@@ -1,3 +1,8 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import { all, takeEvery } from 'redux-saga/effects';
import services from '../services';
@@ -5,8 +10,8 @@ import EntryActionTypes from '../../../constants/EntryActionTypes';
export default function* tasksWatchers() {
yield all([
takeEvery(EntryActionTypes.TASK_IN_CURRENT_CARD_CREATE, ({ payload: { data } }) =>
services.createTaskInCurrentCard(data),
takeEvery(EntryActionTypes.TASK_CREATE, ({ payload: { taskListId, data } }) =>
services.createTask(taskListId, data),
),
takeEvery(EntryActionTypes.TASK_CREATE_HANDLE, ({ payload: { task } }) =>
services.handleTaskCreate(task),
@@ -17,8 +22,8 @@ export default function* tasksWatchers() {
takeEvery(EntryActionTypes.TASK_UPDATE_HANDLE, ({ payload: { task } }) =>
services.handleTaskUpdate(task),
),
takeEvery(EntryActionTypes.TASK_MOVE, ({ payload: { id, index } }) =>
services.moveTask(id, index),
takeEvery(EntryActionTypes.TASK_MOVE, ({ payload: { id, taskListId, index } }) =>
services.moveTask(id, taskListId, index),
),
takeEvery(EntryActionTypes.TASK_DELETE, ({ payload: { id } }) => services.deleteTask(id)),
takeEvery(EntryActionTypes.TASK_DELETE_HANDLE, ({ payload: { task } }) =>
+14 -2
View File
@@ -1,3 +1,8 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import { all, takeEvery } from 'redux-saga/effects';
import services from '../services';
@@ -71,6 +76,9 @@ export default function* usersWatchers() {
takeEvery(EntryActionTypes.USER_TO_CURRENT_CARD_ADD, ({ payload: { id } }) =>
services.addUserToCurrentCard(id),
),
takeEvery(EntryActionTypes.CURRENT_USER_TO_CURRENT_CARD_ADD, () =>
services.addCurrentUserToCurrentCard(),
),
takeEvery(EntryActionTypes.USER_TO_CARD_ADD_HANDLE, ({ payload: { cardMembership } }) =>
services.handleUserToCardAdd(cardMembership),
),
@@ -80,11 +88,15 @@ export default function* usersWatchers() {
takeEvery(EntryActionTypes.USER_FROM_CURRENT_CARD_REMOVE, ({ payload: { id } }) =>
services.removeUserFromCurrentCard(id),
),
takeEvery(EntryActionTypes.CURRENT_USER_FROM_CURRENT_CARD_REMOVE, () =>
services.removeCurrentUserFromCurrentCard(),
),
takeEvery(EntryActionTypes.USER_FROM_CARD_REMOVE_HANDLE, ({ payload: { cardMembership } }) =>
services.handleUserFromCardRemove(cardMembership),
),
takeEvery(EntryActionTypes.USER_TO_FILTER_IN_CURRENT_BOARD_ADD, ({ payload: { id } }) =>
services.addUserToFilterInCurrentBoard(id),
takeEvery(
EntryActionTypes.USER_TO_FILTER_IN_CURRENT_BOARD_ADD,
({ payload: { id, replace } }) => services.addUserToFilterInCurrentBoard(id, replace),
),
takeEvery(EntryActionTypes.USER_FROM_FILTER_IN_CURRENT_BOARD_REMOVE, ({ payload: { id } }) =>
services.removeUserFromFilterInCurrentBoard(id),
+8 -3
View File
@@ -1,11 +1,16 @@
import { call } 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, select } from 'redux-saga/effects';
import loginSaga from './login';
import coreSaga from './core';
import { getAccessToken } from '../utils/access-token-storage';
import selectors from '../selectors';
export default function* rootSaga() {
const accessToken = yield call(getAccessToken);
const accessToken = yield select(selectors.selectAccessToken);
if (!accessToken) {
yield call(loginSaga);
+6 -2
View File
@@ -1,3 +1,8 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import { all, call, cancel, fork, take } from 'redux-saga/effects';
import watchers from './watchers';
@@ -8,8 +13,7 @@ export default function* loginSaga() {
const watcherTasks = yield all(watchers.map((watcher) => fork(watcher)));
yield fork(services.initializeLogin);
yield take([ActionTypes.AUTHENTICATE__SUCCESS, ActionTypes.USING_OIDC_AUTHENTICATE__SUCCESS]);
yield take([ActionTypes.AUTHENTICATE__SUCCESS, ActionTypes.WITH_OIDC_AUTHENTICATE__SUCCESS]);
yield cancel(watcherTasks);
yield call(services.goToRoot);
+5
View File
@@ -1,3 +1,8 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import router from './router';
import login from './login';
+16 -11
View File
@@ -1,3 +1,8 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import { nanoid } from 'nanoid';
import { call, put, select } from 'redux-saga/effects';
import { replace } from '../../../lib/redux-router';
@@ -29,7 +34,7 @@ export function* authenticate(data) {
yield put(actions.authenticate.success(accessToken));
}
export function* authenticateUsingOidc() {
export function* authenticateWithOidc() {
const oidcConfig = yield select(selectors.selectOidcConfig);
const state = nanoid();
@@ -45,7 +50,7 @@ export function* authenticateUsingOidc() {
window.location.href = redirectUrl;
}
export function* authenticateUsingOidcCallback() {
export function* authenticateWithOidcCallback() {
// https://github.com/plankanban/planka/issues/511#issuecomment-1771385639
const params = new URLSearchParams(window.location.hash.substring(1) || window.location.search);
@@ -59,7 +64,7 @@ export function* authenticateUsingOidcCallback() {
if (params.get('error') !== null) {
yield put(
actions.authenticateUsingOidc.failure(
actions.authenticateWithOidc.failure(
new Error(
`OIDC Authorization error: ${params.get('error')}: ${params.get('error_description')}`,
),
@@ -71,14 +76,14 @@ export function* authenticateUsingOidcCallback() {
const code = params.get('code');
if (code === null) {
yield put(
actions.authenticateUsingOidc.failure(new Error('Invalid OIDC response: no code parameter')),
actions.authenticateWithOidc.failure(new Error('Invalid OIDC response: no code parameter')),
);
return;
}
if (params.get('state') !== state) {
yield put(
actions.authenticateUsingOidc.failure(
actions.authenticateWithOidc.failure(
new Error('Unable to process OIDC response: state mismatch'),
),
);
@@ -87,7 +92,7 @@ export function* authenticateUsingOidcCallback() {
if (nonce === null) {
yield put(
actions.authenticateUsingOidc.failure(
actions.authenticateWithOidc.failure(
new Error('Unable to process OIDC response: no nonce issued'),
),
);
@@ -96,17 +101,17 @@ export function* authenticateUsingOidcCallback() {
let accessToken;
try {
({ item: accessToken } = yield call(api.exchangeForAccessTokenUsingOidc, {
({ item: accessToken } = yield call(api.exchangeForAccessTokenWithOidc, {
code,
nonce,
}));
} catch (error) {
yield put(actions.authenticateUsingOidc.failure(error));
yield put(actions.authenticateWithOidc.failure(error));
return;
}
yield call(setAccessToken, accessToken);
yield put(actions.authenticateUsingOidc.success(accessToken));
yield put(actions.authenticateWithOidc.success(accessToken));
}
export function* clearAuthenticateError() {
@@ -116,7 +121,7 @@ export function* clearAuthenticateError() {
export default {
initializeLogin,
authenticate,
authenticateUsingOidc,
authenticateUsingOidcCallback,
authenticateWithOidc,
authenticateWithOidcCallback,
clearAuthenticateError,
};
+15 -5
View File
@@ -1,17 +1,26 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import { call, put, select, take } from 'redux-saga/effects';
import { push } from '../../../lib/redux-router';
import { authenticateUsingOidc, authenticateUsingOidcCallback } from './login';
import { authenticateWithOidc, authenticateWithOidcCallback } from './login';
import selectors from '../../../selectors';
import ActionTypes from '../../../constants/ActionTypes';
import Paths from '../../../constants/Paths';
export function* goTo(pathname) {
yield put(push(pathname));
}
export function* goToLogin() {
yield put(push(Paths.LOGIN));
yield call(goTo, Paths.LOGIN);
}
export function* goToRoot() {
yield put(push(Paths.ROOT));
yield call(goTo, Paths.ROOT);
}
export function* handleLocationChange() {
@@ -46,14 +55,14 @@ export function* handleLocationChange() {
const params = new URLSearchParams(window.location.search);
if (params.has('authenticateWithOidc')) {
yield call(authenticateUsingOidc);
yield call(authenticateWithOidc);
}
}
break;
}
case Paths.OIDC_CALLBACK:
yield call(authenticateUsingOidcCallback);
yield call(authenticateWithOidcCallback);
break;
default:
@@ -61,6 +70,7 @@ export function* handleLocationChange() {
}
export default {
goTo,
goToLogin,
goToRoot,
handleLocationChange,
+5
View File
@@ -1,3 +1,8 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import router from './router';
import login from './login';
+6 -1
View File
@@ -1,3 +1,8 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import { all, takeEvery } from 'redux-saga/effects';
import services from '../services';
@@ -8,7 +13,7 @@ export default function* loginWatchers() {
takeEvery(EntryActionTypes.AUTHENTICATE, ({ payload: { data } }) =>
services.authenticate(data),
),
takeEvery(EntryActionTypes.USING_OIDC_AUTHENTICATE, () => services.authenticateUsingOidc()),
takeEvery(EntryActionTypes.WITH_OIDC_AUTHENTICATE, () => services.authenticateWithOidc()),
takeEvery(EntryActionTypes.AUTHENTICATE_ERROR_CLEAR, () => services.clearAuthenticateError()),
]);
}
@@ -1,3 +1,8 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import { takeEvery } from 'redux-saga/effects';
import { LOCATION_CHANGE_HANDLE } from '../../../lib/redux-router';