mirror of
https://github.com/plankanban/planka.git
synced 2025-12-24 09:15:01 +03:00
@@ -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 { attr, fk } from 'redux-orm';
|
||||
|
||||
import BaseModel from './BaseModel';
|
||||
@@ -13,9 +18,6 @@ export default class extends BaseModel {
|
||||
createdAt: attr({
|
||||
getDefault: () => new Date(),
|
||||
}),
|
||||
isInCard: attr({
|
||||
getDefault: () => true,
|
||||
}),
|
||||
cardId: fk({
|
||||
to: 'Card',
|
||||
as: 'card',
|
||||
@@ -33,62 +35,25 @@ export default class extends BaseModel {
|
||||
case ActionTypes.SOCKET_RECONNECT_HANDLE:
|
||||
Activity.all().delete();
|
||||
|
||||
payload.activities.forEach((activity) => {
|
||||
Activity.upsert({
|
||||
...activity,
|
||||
isInCard: false,
|
||||
});
|
||||
});
|
||||
|
||||
break;
|
||||
case ActionTypes.CORE_INITIALIZE:
|
||||
payload.activities.forEach((activity) => {
|
||||
Activity.upsert({
|
||||
...activity,
|
||||
isInCard: false,
|
||||
});
|
||||
});
|
||||
|
||||
break;
|
||||
case ActionTypes.LIST_CARDS_MOVE__SUCCESS:
|
||||
case ActionTypes.ACTIVITIES_FETCH__SUCCESS:
|
||||
case ActionTypes.ACTIVITIES_DETAILS_TOGGLE__SUCCESS:
|
||||
case ActionTypes.NOTIFICATION_CREATE_HANDLE:
|
||||
payload.activities.forEach((activity) => {
|
||||
Activity.upsert(activity);
|
||||
});
|
||||
|
||||
break;
|
||||
case ActionTypes.ACTIVITY_CREATE_HANDLE:
|
||||
case ActionTypes.ACTIVITY_UPDATE_HANDLE:
|
||||
case ActionTypes.COMMENT_ACTIVITY_CREATE:
|
||||
case ActionTypes.COMMENT_ACTIVITY_UPDATE__SUCCESS:
|
||||
Activity.upsert(payload.activity);
|
||||
|
||||
break;
|
||||
case ActionTypes.ACTIVITY_DELETE_HANDLE:
|
||||
case ActionTypes.COMMENT_ACTIVITY_DELETE__SUCCESS: {
|
||||
const activityModel = Activity.withId(payload.activity.id);
|
||||
|
||||
if (activityModel) {
|
||||
activityModel.delete();
|
||||
case ActionTypes.CARDS_UPDATE_HANDLE:
|
||||
if (payload.activities) {
|
||||
payload.activities.forEach((activity) => {
|
||||
Activity.upsert(activity);
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case ActionTypes.COMMENT_ACTIVITY_CREATE__SUCCESS:
|
||||
Activity.withId(payload.localId).delete();
|
||||
case ActionTypes.ACTIVITY_CREATE_HANDLE:
|
||||
Activity.upsert(payload.activity);
|
||||
|
||||
break;
|
||||
case ActionTypes.COMMENT_ACTIVITY_UPDATE:
|
||||
Activity.withId(payload.id).update({
|
||||
data: payload.data,
|
||||
});
|
||||
|
||||
break;
|
||||
case ActionTypes.COMMENT_ACTIVITY_DELETE:
|
||||
Activity.withId(payload.id).delete();
|
||||
|
||||
break;
|
||||
default:
|
||||
}
|
||||
|
||||
@@ -1,16 +1,41 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import { attr, fk } from 'redux-orm';
|
||||
|
||||
import BaseModel from './BaseModel';
|
||||
import ActionTypes from '../constants/ActionTypes';
|
||||
import { AttachmentTypes } from '../constants/Enums';
|
||||
|
||||
const prepareAttachment = (attachment) => {
|
||||
if (attachment.type !== AttachmentTypes.FILE || !attachment.data) {
|
||||
return attachment;
|
||||
}
|
||||
|
||||
const filename = attachment.data.url.split('/').pop().toLowerCase();
|
||||
|
||||
let extension = filename.slice((Math.max(0, filename.lastIndexOf('.')) || Infinity) + 1);
|
||||
extension = extension ? extension.toLowerCase() : null;
|
||||
|
||||
return {
|
||||
...attachment,
|
||||
data: {
|
||||
...attachment.data,
|
||||
filename,
|
||||
extension,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export default class extends BaseModel {
|
||||
static modelName = 'Attachment';
|
||||
|
||||
static fields = {
|
||||
id: attr(),
|
||||
url: attr(),
|
||||
coverUrl: attr(),
|
||||
image: attr(),
|
||||
type: attr(),
|
||||
data: attr(),
|
||||
name: attr(),
|
||||
createdAt: attr({
|
||||
getDefault: () => new Date(),
|
||||
@@ -20,46 +45,45 @@ export default class extends BaseModel {
|
||||
as: 'card',
|
||||
relatedName: 'attachments',
|
||||
}),
|
||||
creatorUserId: fk({
|
||||
to: 'User',
|
||||
as: 'creatorUser',
|
||||
relatedName: 'createdAttachments',
|
||||
}),
|
||||
};
|
||||
|
||||
static reducer({ type, payload }, Attachment) {
|
||||
switch (type) {
|
||||
case ActionTypes.LOCATION_CHANGE_HANDLE:
|
||||
case ActionTypes.CORE_INITIALIZE:
|
||||
case ActionTypes.USER_UPDATE_HANDLE:
|
||||
case ActionTypes.PROJECT_UPDATE_HANDLE:
|
||||
case ActionTypes.PROJECT_MANAGER_CREATE_HANDLE:
|
||||
case ActionTypes.BOARD_MEMBERSHIP_CREATE_HANDLE:
|
||||
case ActionTypes.CARD_UPDATE_HANDLE:
|
||||
if (payload.attachments) {
|
||||
payload.attachments.forEach((attachment) => {
|
||||
Attachment.upsert(attachment);
|
||||
Attachment.upsert(prepareAttachment(attachment));
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
case ActionTypes.SOCKET_RECONNECT_HANDLE:
|
||||
Attachment.all().delete();
|
||||
|
||||
if (payload.attachments) {
|
||||
// FIXME: bug with oneToOne relation in Redux-ORM
|
||||
const attachmentIds = payload.attachments.map((attachment) => attachment.id);
|
||||
|
||||
Attachment.all()
|
||||
.toModelArray()
|
||||
.forEach((attachmentModel) => {
|
||||
if (!attachmentIds.includes(attachmentModel.id)) {
|
||||
attachmentModel.delete();
|
||||
}
|
||||
});
|
||||
|
||||
payload.attachments.forEach((attachment) => {
|
||||
Attachment.upsert(attachment);
|
||||
Attachment.upsert(prepareAttachment(attachment));
|
||||
});
|
||||
} else {
|
||||
Attachment.all().delete();
|
||||
}
|
||||
|
||||
break;
|
||||
case ActionTypes.BOARD_FETCH__SUCCESS:
|
||||
case ActionTypes.CARDS_FETCH__SUCCESS:
|
||||
case ActionTypes.CARD_CREATE_HANDLE:
|
||||
case ActionTypes.CARD_DUPLICATE__SUCCESS:
|
||||
payload.attachments.forEach((attachment) => {
|
||||
Attachment.upsert(attachment);
|
||||
Attachment.upsert(prepareAttachment(attachment));
|
||||
});
|
||||
|
||||
break;
|
||||
@@ -67,12 +91,16 @@ export default class extends BaseModel {
|
||||
case ActionTypes.ATTACHMENT_CREATE_HANDLE:
|
||||
case ActionTypes.ATTACHMENT_UPDATE__SUCCESS:
|
||||
case ActionTypes.ATTACHMENT_UPDATE_HANDLE:
|
||||
Attachment.upsert(payload.attachment);
|
||||
Attachment.upsert(prepareAttachment(payload.attachment));
|
||||
|
||||
break;
|
||||
case ActionTypes.ATTACHMENT_CREATE__SUCCESS:
|
||||
Attachment.withId(payload.localId).delete();
|
||||
Attachment.upsert(payload.attachment);
|
||||
Attachment.upsert(prepareAttachment(payload.attachment));
|
||||
|
||||
break;
|
||||
case ActionTypes.ATTACHMENT_CREATE__FAILURE:
|
||||
Attachment.withId(payload.localId).delete();
|
||||
|
||||
break;
|
||||
case ActionTypes.ATTACHMENT_UPDATE:
|
||||
@@ -96,4 +124,16 @@ export default class extends BaseModel {
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
duplicate(id, data) {
|
||||
return this.getClass().create({
|
||||
id,
|
||||
cardId: this.cardId,
|
||||
creatorUserId: this.creatorUserId,
|
||||
type: this.type,
|
||||
data: this.data,
|
||||
name: this.name,
|
||||
...data,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
98
client/src/models/BackgroundImage.js
Normal file
98
client/src/models/BackgroundImage.js
Normal file
@@ -0,0 +1,98 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import { attr, fk } from 'redux-orm';
|
||||
|
||||
import BaseModel from './BaseModel';
|
||||
import ActionTypes from '../constants/ActionTypes';
|
||||
|
||||
export default class extends BaseModel {
|
||||
static modelName = 'BackgroundImage';
|
||||
|
||||
static fields = {
|
||||
id: attr(),
|
||||
url: attr(),
|
||||
thumbnailUrls: attr(),
|
||||
projectId: fk({
|
||||
to: 'Project',
|
||||
as: 'project',
|
||||
relatedName: 'backgroundImages',
|
||||
}),
|
||||
};
|
||||
|
||||
static reducer({ type, payload }, BackgroundImage) {
|
||||
switch (type) {
|
||||
case ActionTypes.SOCKET_RECONNECT_HANDLE:
|
||||
BackgroundImage.all().delete();
|
||||
|
||||
payload.backgroundImages.forEach((backgroundImage) => {
|
||||
BackgroundImage.upsert(backgroundImage);
|
||||
});
|
||||
|
||||
break;
|
||||
case ActionTypes.CORE_INITIALIZE:
|
||||
case ActionTypes.PROJECT_CREATE_HANDLE:
|
||||
payload.backgroundImages.forEach((backgroundImage) => {
|
||||
BackgroundImage.upsert(backgroundImage);
|
||||
});
|
||||
|
||||
break;
|
||||
case ActionTypes.USER_UPDATE_HANDLE:
|
||||
case ActionTypes.PROJECT_UPDATE_HANDLE:
|
||||
case ActionTypes.PROJECT_MANAGER_CREATE_HANDLE:
|
||||
case ActionTypes.BOARD_MEMBERSHIP_CREATE_HANDLE:
|
||||
if (payload.backgroundImages) {
|
||||
payload.backgroundImages.forEach((backgroundImage) => {
|
||||
BackgroundImage.upsert(backgroundImage);
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
case ActionTypes.BACKGROUND_IMAGE_CREATE:
|
||||
case ActionTypes.BACKGROUND_IMAGE_CREATE_HANDLE:
|
||||
BackgroundImage.upsert(payload.backgroundImage);
|
||||
|
||||
break;
|
||||
case ActionTypes.BACKGROUND_IMAGE_CREATE__SUCCESS:
|
||||
BackgroundImage.withId(payload.localId).delete();
|
||||
BackgroundImage.upsert(payload.backgroundImage);
|
||||
|
||||
break;
|
||||
case ActionTypes.BACKGROUND_IMAGE_CREATE__FAILURE:
|
||||
BackgroundImage.withId(payload.localId).delete();
|
||||
|
||||
break;
|
||||
case ActionTypes.BACKGROUND_IMAGE_DELETE:
|
||||
BackgroundImage.withId(payload.id).deleteWithRelated();
|
||||
|
||||
break;
|
||||
case ActionTypes.BACKGROUND_IMAGE_DELETE__SUCCESS:
|
||||
case ActionTypes.BACKGROUND_IMAGE_DELETE_HANDLE: {
|
||||
const backgroundImageModel = BackgroundImage.withId(payload.backgroundImage.id);
|
||||
|
||||
if (backgroundImageModel) {
|
||||
backgroundImageModel.deleteWithRelated();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
deleteRelated() {
|
||||
if (this.backgroundedProject) {
|
||||
this.backgroundedProject.update({
|
||||
backgroundType: null,
|
||||
backgroundImageId: null,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
deleteWithRelated() {
|
||||
this.deleteRelated();
|
||||
this.delete();
|
||||
}
|
||||
}
|
||||
108
client/src/models/BaseCustomFieldGroup.js
Normal file
108
client/src/models/BaseCustomFieldGroup.js
Normal file
@@ -0,0 +1,108 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import { attr, fk } from 'redux-orm';
|
||||
|
||||
import BaseModel from './BaseModel';
|
||||
import ActionTypes from '../constants/ActionTypes';
|
||||
|
||||
export default class extends BaseModel {
|
||||
static modelName = 'BaseCustomFieldGroup';
|
||||
|
||||
static fields = {
|
||||
id: attr(),
|
||||
name: attr(),
|
||||
projectId: fk({
|
||||
to: 'Project',
|
||||
as: 'project',
|
||||
relatedName: 'baseCustomFieldGroups',
|
||||
}),
|
||||
};
|
||||
|
||||
static reducer({ type, payload }, BaseCustomFieldGroup) {
|
||||
switch (type) {
|
||||
case ActionTypes.SOCKET_RECONNECT_HANDLE:
|
||||
BaseCustomFieldGroup.all().delete();
|
||||
|
||||
payload.baseCustomFieldGroups.forEach((baseCustomFieldGroup) => {
|
||||
BaseCustomFieldGroup.upsert(baseCustomFieldGroup);
|
||||
});
|
||||
|
||||
break;
|
||||
case ActionTypes.CORE_INITIALIZE:
|
||||
case ActionTypes.PROJECT_CREATE_HANDLE:
|
||||
payload.baseCustomFieldGroups.forEach((baseCustomFieldGroup) => {
|
||||
BaseCustomFieldGroup.upsert(baseCustomFieldGroup);
|
||||
});
|
||||
|
||||
break;
|
||||
case ActionTypes.USER_UPDATE_HANDLE:
|
||||
case ActionTypes.PROJECT_UPDATE_HANDLE:
|
||||
case ActionTypes.PROJECT_MANAGER_CREATE_HANDLE:
|
||||
case ActionTypes.BOARD_MEMBERSHIP_CREATE_HANDLE:
|
||||
if (payload.baseCustomFieldGroups) {
|
||||
payload.baseCustomFieldGroups.forEach((baseCustomFieldGroup) => {
|
||||
BaseCustomFieldGroup.upsert(baseCustomFieldGroup);
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
case ActionTypes.BASE_CUSTOM_FIELD_GROUP_CREATE:
|
||||
case ActionTypes.BASE_CUSTOM_FIELD_GROUP_CREATE_HANDLE:
|
||||
case ActionTypes.BASE_CUSTOM_FIELD_GROUP_UPDATE__SUCCESS:
|
||||
case ActionTypes.BASE_CUSTOM_FIELD_GROUP_UPDATE_HANDLE:
|
||||
BaseCustomFieldGroup.upsert(payload.baseCustomFieldGroup);
|
||||
|
||||
break;
|
||||
case ActionTypes.BASE_CUSTOM_FIELD_GROUP_CREATE__SUCCESS:
|
||||
BaseCustomFieldGroup.withId(payload.localId).delete();
|
||||
BaseCustomFieldGroup.upsert(payload.baseCustomFieldGroup);
|
||||
|
||||
break;
|
||||
case ActionTypes.BASE_CUSTOM_FIELD_GROUP_CREATE__FAILURE:
|
||||
BaseCustomFieldGroup.withId(payload.localId).delete();
|
||||
|
||||
break;
|
||||
case ActionTypes.BASE_CUSTOM_FIELD_GROUP_UPDATE:
|
||||
BaseCustomFieldGroup.withId(payload.id).update(payload.data);
|
||||
|
||||
break;
|
||||
case ActionTypes.BASE_CUSTOM_FIELD_GROUP_DELETE:
|
||||
BaseCustomFieldGroup.withId(payload.id).deleteWithRelated();
|
||||
|
||||
break;
|
||||
case ActionTypes.BASE_CUSTOM_FIELD_GROUP_DELETE__SUCCESS:
|
||||
case ActionTypes.BASE_CUSTOM_FIELD_GROUP_DELETE_HANDLE: {
|
||||
const baseCustomFieldGroupModel = BaseCustomFieldGroup.withId(
|
||||
payload.baseCustomFieldGroup.id,
|
||||
);
|
||||
|
||||
if (baseCustomFieldGroupModel) {
|
||||
baseCustomFieldGroupModel.deleteWithRelated();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
getCustomFieldsQuerySet() {
|
||||
return this.customFields.orderBy(['position', 'id.length', 'id']);
|
||||
}
|
||||
|
||||
deleteRelated() {
|
||||
this.customFields.delete();
|
||||
|
||||
this.customFieldGroups.toModelArray().forEach((customFieldGroupModel) => {
|
||||
customFieldGroupModel.deleteWithRelated();
|
||||
});
|
||||
}
|
||||
|
||||
deleteWithRelated() {
|
||||
this.deleteRelated();
|
||||
this.delete();
|
||||
}
|
||||
}
|
||||
@@ -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 { Model } from 'redux-orm';
|
||||
|
||||
export default class BaseModel extends Model {
|
||||
|
||||
@@ -1,11 +1,23 @@
|
||||
import orderBy from 'lodash/orderBy';
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import { attr, fk, many } from 'redux-orm';
|
||||
|
||||
import BaseModel from './BaseModel';
|
||||
import buildSearchParts from '../utils/build-search-parts';
|
||||
import { isListFinite } from '../utils/record-helpers';
|
||||
import ActionTypes from '../constants/ActionTypes';
|
||||
import { BoardContexts, BoardViews } from '../constants/Enums';
|
||||
|
||||
import User from './User';
|
||||
import Label from './Label';
|
||||
const prepareFetchedBoard = (board) => ({
|
||||
...board,
|
||||
isFetching: false,
|
||||
context: BoardContexts.BOARD,
|
||||
view: board.defaultView,
|
||||
search: '',
|
||||
});
|
||||
|
||||
export default class extends BaseModel {
|
||||
static modelName = 'Board';
|
||||
@@ -14,6 +26,16 @@ export default class extends BaseModel {
|
||||
id: attr(),
|
||||
position: attr(),
|
||||
name: attr(),
|
||||
defaultView: attr(),
|
||||
defaultCardType: attr(),
|
||||
limitCardTypesToDefaultOne: attr(),
|
||||
alwaysDisplayCardCreator: attr(),
|
||||
context: attr(),
|
||||
view: attr(),
|
||||
search: attr(),
|
||||
isSubscribed: attr({
|
||||
getDefault: () => false,
|
||||
}),
|
||||
isFetching: attr({
|
||||
getDefault: () => null,
|
||||
}),
|
||||
@@ -29,19 +51,13 @@ export default class extends BaseModel {
|
||||
}),
|
||||
filterUsers: many('User', 'filterBoards'),
|
||||
filterLabels: many('Label', 'filterBoards'),
|
||||
filterText: attr({
|
||||
getDefault: () => '',
|
||||
}),
|
||||
};
|
||||
|
||||
static reducer({ type, payload }, Board) {
|
||||
switch (type) {
|
||||
case ActionTypes.LOCATION_CHANGE_HANDLE:
|
||||
if (payload.board) {
|
||||
Board.upsert({
|
||||
...payload.board,
|
||||
isFetching: false,
|
||||
});
|
||||
Board.upsert(prepareFetchedBoard(payload.board));
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -52,14 +68,25 @@ export default class extends BaseModel {
|
||||
});
|
||||
|
||||
break;
|
||||
case ActionTypes.SOCKET_RECONNECT_HANDLE:
|
||||
Board.all().delete();
|
||||
case ActionTypes.SOCKET_RECONNECT_HANDLE: {
|
||||
const boardIds = payload.boards.map(({ id }) => id);
|
||||
|
||||
Board.all()
|
||||
.toModelArray()
|
||||
.forEach((boardModel) => {
|
||||
if (boardModel.isFetching === null || !boardIds.includes(boardModel.id)) {
|
||||
boardModel.deleteWithClearable();
|
||||
}
|
||||
});
|
||||
|
||||
if (payload.board) {
|
||||
Board.upsert({
|
||||
...payload.board,
|
||||
isFetching: false,
|
||||
});
|
||||
const boardModel = Board.withId(payload.board.id);
|
||||
|
||||
if (boardModel) {
|
||||
boardModel.update(payload.board);
|
||||
} else {
|
||||
Board.upsert(prepareFetchedBoard(payload.board));
|
||||
}
|
||||
}
|
||||
|
||||
payload.boards.forEach((board) => {
|
||||
@@ -67,6 +94,7 @@ export default class extends BaseModel {
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
case ActionTypes.SOCKET_RECONNECT_HANDLE__CORE_FETCH:
|
||||
Board.all()
|
||||
.toModelArray()
|
||||
@@ -83,10 +111,7 @@ export default class extends BaseModel {
|
||||
break;
|
||||
case ActionTypes.CORE_INITIALIZE:
|
||||
if (payload.board) {
|
||||
Board.upsert({
|
||||
...payload.board,
|
||||
isFetching: false,
|
||||
});
|
||||
Board.upsert(prepareFetchedBoard(payload.board));
|
||||
}
|
||||
|
||||
payload.boards.forEach((board) => {
|
||||
@@ -94,10 +119,37 @@ export default class extends BaseModel {
|
||||
});
|
||||
|
||||
break;
|
||||
case ActionTypes.USER_TO_BOARD_FILTER_ADD:
|
||||
Board.withId(payload.boardId).filterUsers.add(payload.id);
|
||||
case ActionTypes.USER_UPDATE_HANDLE:
|
||||
Board.all()
|
||||
.toModelArray()
|
||||
.forEach((boardModel) => {
|
||||
if (!payload.boardIds.includes(boardModel.id)) {
|
||||
boardModel.deleteWithRelated();
|
||||
}
|
||||
});
|
||||
|
||||
if (payload.board) {
|
||||
Board.upsert(prepareFetchedBoard(payload.board));
|
||||
}
|
||||
|
||||
if (payload.boards) {
|
||||
payload.boards.forEach((board) => {
|
||||
Board.upsert(board);
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
case ActionTypes.USER_TO_BOARD_FILTER_ADD: {
|
||||
const boardModel = Board.withId(payload.boardId);
|
||||
|
||||
if (payload.replace) {
|
||||
boardModel.filterUsers.clear();
|
||||
}
|
||||
|
||||
boardModel.filterUsers.add(payload.id);
|
||||
|
||||
break;
|
||||
}
|
||||
case ActionTypes.USER_FROM_BOARD_FILTER_REMOVE:
|
||||
Board.withId(payload.boardId).filterUsers.remove(payload.id);
|
||||
|
||||
@@ -108,17 +160,16 @@ export default class extends BaseModel {
|
||||
});
|
||||
|
||||
break;
|
||||
case ActionTypes.PROJECT_UPDATE_HANDLE:
|
||||
case ActionTypes.PROJECT_MANAGER_CREATE_HANDLE:
|
||||
case ActionTypes.BOARD_MEMBERSHIP_CREATE_HANDLE:
|
||||
if (payload.board) {
|
||||
Board.upsert(prepareFetchedBoard(payload.board));
|
||||
}
|
||||
|
||||
if (payload.boards) {
|
||||
payload.boards.forEach((board) => {
|
||||
Board.upsert({
|
||||
...board,
|
||||
...(payload.board &&
|
||||
payload.board.id === board.id && {
|
||||
isFetching: false,
|
||||
}),
|
||||
});
|
||||
Board.upsert(board);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -134,12 +185,13 @@ export default class extends BaseModel {
|
||||
Board.withId(payload.localId).delete();
|
||||
Board.upsert(payload.board);
|
||||
|
||||
break;
|
||||
case ActionTypes.BOARD_CREATE__FAILURE:
|
||||
Board.withId(payload.localId).delete();
|
||||
|
||||
break;
|
||||
case ActionTypes.BOARD_FETCH__SUCCESS:
|
||||
Board.upsert({
|
||||
...payload.board,
|
||||
isFetching: false,
|
||||
});
|
||||
Board.upsert(prepareFetchedBoard(payload.board));
|
||||
|
||||
break;
|
||||
case ActionTypes.BOARD_FETCH__FAILURE:
|
||||
@@ -151,6 +203,22 @@ export default class extends BaseModel {
|
||||
case ActionTypes.BOARD_UPDATE:
|
||||
Board.withId(payload.id).update(payload.data);
|
||||
|
||||
break;
|
||||
case ActionTypes.BOARD_CONTEXT_UPDATE: {
|
||||
const boardModel = Board.withId(payload.id);
|
||||
|
||||
boardModel.update({
|
||||
context: payload.value,
|
||||
view: payload.value === BoardContexts.BOARD ? boardModel.defaultView : BoardViews.LIST,
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
case ActionTypes.IN_BOARD_SEARCH:
|
||||
Board.withId(payload.id).update({
|
||||
search: payload.value,
|
||||
});
|
||||
|
||||
break;
|
||||
case ActionTypes.BOARD_DELETE:
|
||||
Board.withId(payload.id).deleteWithRelated();
|
||||
@@ -174,66 +242,41 @@ export default class extends BaseModel {
|
||||
Board.withId(payload.boardId).filterLabels.remove(payload.id);
|
||||
|
||||
break;
|
||||
case ActionTypes.TEXT_FILTER_IN_CURRENT_BOARD: {
|
||||
const board = Board.withId(payload.boardId);
|
||||
let filterText = payload.text;
|
||||
const posSpace = filterText.indexOf(' ');
|
||||
|
||||
// Shortcut to user filters
|
||||
const posAT = filterText.indexOf('@');
|
||||
if (posAT >= 0 && posSpace > 0 && posAT < posSpace) {
|
||||
const userId = User.findUsersFromText(
|
||||
filterText.substring(posAT + 1, posSpace),
|
||||
board.memberships.toModelArray().map((membership) => membership.user),
|
||||
);
|
||||
if (
|
||||
userId &&
|
||||
board.filterUsers.toModelArray().filter((user) => user.id === userId).length === 0
|
||||
) {
|
||||
board.filterUsers.add(userId);
|
||||
filterText = filterText.substring(0, posAT);
|
||||
}
|
||||
}
|
||||
|
||||
// Shortcut to label filters
|
||||
const posSharp = filterText.indexOf('#');
|
||||
if (posSharp >= 0 && posSpace > 0 && posSharp < posSpace) {
|
||||
const labelId = Label.findLabelsFromText(
|
||||
filterText.substring(posSharp + 1, posSpace),
|
||||
board.labels.toModelArray(),
|
||||
);
|
||||
if (
|
||||
labelId &&
|
||||
board.filterLabels.toModelArray().filter((label) => label.id === labelId).length === 0
|
||||
) {
|
||||
board.filterLabels.add(labelId);
|
||||
filterText = filterText.substring(0, posSharp);
|
||||
}
|
||||
}
|
||||
|
||||
board.update({ filterText });
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
getOrderedLabelsQuerySet() {
|
||||
return this.labels.orderBy('position');
|
||||
getMembershipsQuerySet() {
|
||||
return this.memberships.orderBy(['id.length', 'id']);
|
||||
}
|
||||
|
||||
getOrderedListsQuerySet() {
|
||||
return this.lists.orderBy('position');
|
||||
getLabelsQuerySet() {
|
||||
return this.labels.orderBy(['position', 'id.length', 'id']);
|
||||
}
|
||||
|
||||
getOrderedMembershipsModelArray() {
|
||||
return orderBy(this.memberships.toModelArray(), (boardMembershipModel) =>
|
||||
boardMembershipModel.user.name.toLocaleLowerCase(),
|
||||
);
|
||||
getListsQuerySet() {
|
||||
return this.lists.orderBy(['position', 'id.length', 'id']);
|
||||
}
|
||||
|
||||
getMembershipModelForUser(userId) {
|
||||
getFiniteListsQuerySet() {
|
||||
return this.getListsQuerySet().filter((list) => isListFinite(list));
|
||||
}
|
||||
|
||||
getCustomFieldGroupsQuerySet() {
|
||||
return this.customFieldGroups.orderBy(['position', 'id.length', 'id']);
|
||||
}
|
||||
|
||||
getUnreadNotificationsQuerySet() {
|
||||
return this.notifications.filter({
|
||||
isRead: false,
|
||||
});
|
||||
}
|
||||
|
||||
getNotificationServicesQuerySet() {
|
||||
return this.notificationServices.orderBy(['id.length', 'id']);
|
||||
}
|
||||
|
||||
getMembershipModelByUserId(userId) {
|
||||
return this.memberships
|
||||
.filter({
|
||||
userId,
|
||||
@@ -241,7 +284,70 @@ export default class extends BaseModel {
|
||||
.first();
|
||||
}
|
||||
|
||||
hasMembershipForUser(userId) {
|
||||
getCardsModelArray() {
|
||||
return this.getFiniteListsQuerySet()
|
||||
.toModelArray()
|
||||
.flatMap((listModel) => listModel.getCardsModelArray());
|
||||
}
|
||||
|
||||
getFilteredCardsModelArray() {
|
||||
let cardModels = this.getCardsModelArray();
|
||||
|
||||
if (cardModels.length === 0) {
|
||||
return cardModels;
|
||||
}
|
||||
|
||||
if (this.search) {
|
||||
if (this.search.startsWith('/')) {
|
||||
let searchRegex;
|
||||
try {
|
||||
searchRegex = new RegExp(this.search.substring(1), 'i');
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
|
||||
cardModels = cardModels.filter(
|
||||
(cardModel) =>
|
||||
searchRegex.test(cardModel.name) ||
|
||||
(cardModel.description && searchRegex.test(cardModel.description)),
|
||||
);
|
||||
} else {
|
||||
const searchParts = buildSearchParts(this.search);
|
||||
|
||||
cardModels = cardModels.filter((cardModel) => {
|
||||
const name = cardModel.name.toLowerCase();
|
||||
const description = cardModel.description && cardModel.description.toLowerCase();
|
||||
|
||||
return searchParts.every(
|
||||
(searchPart) =>
|
||||
name.includes(searchPart) || (description && description.includes(searchPart)),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const filterUserIds = this.filterUsers.toRefArray().map((user) => user.id);
|
||||
|
||||
if (filterUserIds.length > 0) {
|
||||
cardModels = cardModels.filter((cardModel) => {
|
||||
const users = cardModel.users.toRefArray();
|
||||
return users.some((user) => filterUserIds.includes(user.id));
|
||||
});
|
||||
}
|
||||
|
||||
const filterLabelIds = this.filterLabels.toRefArray().map((label) => label.id);
|
||||
|
||||
if (filterLabelIds.length > 0) {
|
||||
cardModels = cardModels.filter((cardModel) => {
|
||||
const labels = cardModel.labels.toRefArray();
|
||||
return labels.some((label) => filterLabelIds.includes(label.id));
|
||||
});
|
||||
}
|
||||
|
||||
return cardModels;
|
||||
}
|
||||
|
||||
hasMembershipWithUserId(userId) {
|
||||
return this.memberships
|
||||
.filter({
|
||||
userId,
|
||||
@@ -249,24 +355,48 @@ export default class extends BaseModel {
|
||||
.exists();
|
||||
}
|
||||
|
||||
isAvailableForUser(userId) {
|
||||
isAvailableForUser(userModel) {
|
||||
if (!this.project) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
this.project && (this.project.hasManagerForUser(userId) || this.hasMembershipForUser(userId))
|
||||
this.project.isExternalAccessibleForUser(userModel) ||
|
||||
this.hasMembershipWithUserId(userModel.id)
|
||||
);
|
||||
}
|
||||
|
||||
deleteListsWithRelated() {
|
||||
this.lists.toModelArray().forEach((listModel) => {
|
||||
listModel.deleteWithRelated();
|
||||
});
|
||||
}
|
||||
|
||||
deleteClearable() {
|
||||
this.filterUsers.clear();
|
||||
this.filterLabels.clear();
|
||||
}
|
||||
|
||||
deleteRelated(exceptMemberUserId) {
|
||||
this.deleteClearable();
|
||||
|
||||
this.memberships.toModelArray().forEach((boardMembershipModel) => {
|
||||
if (boardMembershipModel.userId !== exceptMemberUserId) {
|
||||
boardMembershipModel.deleteWithRelated();
|
||||
}
|
||||
});
|
||||
|
||||
this.labels.delete();
|
||||
|
||||
this.lists.toModelArray().forEach((listModel) => {
|
||||
listModel.deleteWithRelated();
|
||||
this.labels.toModelArray().forEach((labelModel) => {
|
||||
labelModel.deleteWithRelated();
|
||||
});
|
||||
|
||||
this.deleteListsWithRelated();
|
||||
this.notificationServices.delete();
|
||||
}
|
||||
|
||||
deleteWithClearable() {
|
||||
this.deleteClearable();
|
||||
this.delete();
|
||||
}
|
||||
|
||||
deleteWithRelated() {
|
||||
|
||||
@@ -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 { attr, fk } from 'redux-orm';
|
||||
|
||||
import BaseModel from './BaseModel';
|
||||
@@ -10,9 +15,6 @@ export default class extends BaseModel {
|
||||
id: attr(),
|
||||
role: attr(),
|
||||
canComment: attr(),
|
||||
createdAt: attr({
|
||||
getDefault: () => new Date(),
|
||||
}),
|
||||
boardId: fk({
|
||||
to: 'Board',
|
||||
as: 'board',
|
||||
@@ -28,6 +30,8 @@ export default class extends BaseModel {
|
||||
static reducer({ type, payload }, BoardMembership) {
|
||||
switch (type) {
|
||||
case ActionTypes.LOCATION_CHANGE_HANDLE:
|
||||
case ActionTypes.USER_UPDATE_HANDLE:
|
||||
case ActionTypes.PROJECT_UPDATE_HANDLE:
|
||||
case ActionTypes.PROJECT_MANAGER_CREATE_HANDLE:
|
||||
if (payload.boardMemberships) {
|
||||
payload.boardMemberships.forEach((boardMembership) => {
|
||||
@@ -47,6 +51,7 @@ export default class extends BaseModel {
|
||||
case ActionTypes.CORE_INITIALIZE:
|
||||
case ActionTypes.PROJECT_CREATE_HANDLE:
|
||||
case ActionTypes.BOARD_CREATE__SUCCESS:
|
||||
case ActionTypes.BOARD_CREATE_HANDLE:
|
||||
case ActionTypes.BOARD_FETCH__SUCCESS:
|
||||
payload.boardMemberships.forEach((boardMembership) => {
|
||||
BoardMembership.upsert(boardMembership);
|
||||
@@ -54,6 +59,8 @@ export default class extends BaseModel {
|
||||
|
||||
break;
|
||||
case ActionTypes.BOARD_MEMBERSHIP_CREATE:
|
||||
case ActionTypes.BOARD_MEMBERSHIP_UPDATE__SUCCESS:
|
||||
case ActionTypes.BOARD_MEMBERSHIP_UPDATE_HANDLE:
|
||||
BoardMembership.upsert(payload.boardMembership);
|
||||
|
||||
break;
|
||||
@@ -61,6 +68,10 @@ export default class extends BaseModel {
|
||||
BoardMembership.withId(payload.localId).delete();
|
||||
BoardMembership.upsert(payload.boardMembership);
|
||||
|
||||
break;
|
||||
case ActionTypes.BOARD_MEMBERSHIP_CREATE__FAILURE:
|
||||
BoardMembership.withId(payload.localId).delete();
|
||||
|
||||
break;
|
||||
case ActionTypes.BOARD_MEMBERSHIP_CREATE_HANDLE:
|
||||
BoardMembership.upsert(payload.boardMembership);
|
||||
@@ -75,14 +86,9 @@ export default class extends BaseModel {
|
||||
case ActionTypes.BOARD_MEMBERSHIP_UPDATE:
|
||||
BoardMembership.withId(payload.id).update(payload.data);
|
||||
|
||||
break;
|
||||
case ActionTypes.BOARD_MEMBERSHIP_UPDATE__SUCCESS:
|
||||
case ActionTypes.BOARD_MEMBERSHIP_UPDATE_HANDLE:
|
||||
BoardMembership.upsert(payload.boardMembership);
|
||||
|
||||
break;
|
||||
case ActionTypes.BOARD_MEMBERSHIP_DELETE:
|
||||
BoardMembership.withId(payload.id).deleteWithRelated();
|
||||
BoardMembership.withId(payload.id).deleteWithRelated(payload.isCurrentUser);
|
||||
|
||||
break;
|
||||
case ActionTypes.BOARD_MEMBERSHIP_DELETE__SUCCESS:
|
||||
@@ -90,7 +96,7 @@ export default class extends BaseModel {
|
||||
const boardMembershipModel = BoardMembership.withId(payload.boardMembership.id);
|
||||
|
||||
if (boardMembershipModel) {
|
||||
boardMembershipModel.deleteWithRelated();
|
||||
boardMembershipModel.deleteWithRelated(payload.isCurrentUser);
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -99,20 +105,44 @@ export default class extends BaseModel {
|
||||
}
|
||||
}
|
||||
|
||||
deleteRelated() {
|
||||
deleteRelated(isCurrentUser = false) {
|
||||
if (isCurrentUser) {
|
||||
this.board.isSubscribed = false;
|
||||
}
|
||||
|
||||
this.board.cards.toModelArray().forEach((cardModel) => {
|
||||
if (isCurrentUser) {
|
||||
cardModel.update({
|
||||
isSubscribed: false,
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
cardModel.users.remove(this.userId);
|
||||
} catch {} // eslint-disable-line no-empty
|
||||
} catch {
|
||||
/* empty */
|
||||
}
|
||||
|
||||
cardModel.taskLists.toModelArray().forEach((taskListModel) => {
|
||||
taskListModel.tasks.toModelArray().forEach((taskModel) => {
|
||||
if (taskModel.assigneeUserId === this.userId) {
|
||||
taskModel.update({
|
||||
assigneeUserId: null,
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
try {
|
||||
this.board.filterUsers.remove(this.userId);
|
||||
} catch {} // eslint-disable-line no-empty
|
||||
} catch {
|
||||
/* empty */
|
||||
}
|
||||
}
|
||||
|
||||
deleteWithRelated() {
|
||||
this.deleteRelated();
|
||||
deleteWithRelated(isCurrentUser) {
|
||||
this.deleteRelated(isCurrentUser);
|
||||
this.delete();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +1,51 @@
|
||||
import pick from 'lodash/pick';
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import { attr, fk, many, oneToOne } from 'redux-orm';
|
||||
|
||||
import BaseModel from './BaseModel';
|
||||
import ActionTypes from '../constants/ActionTypes';
|
||||
import Config from '../constants/Config';
|
||||
import { ActivityTypes } from '../constants/Enums';
|
||||
|
||||
export default class extends BaseModel {
|
||||
static modelName = 'Card';
|
||||
|
||||
static fields = {
|
||||
id: attr(),
|
||||
type: attr(),
|
||||
position: attr(),
|
||||
name: attr(),
|
||||
description: attr(),
|
||||
creatorUserId: oneToOne({
|
||||
to: 'User',
|
||||
as: 'creatorUser',
|
||||
relatedName: 'ownCards',
|
||||
}),
|
||||
dueDate: attr(),
|
||||
isDueDateCompleted: attr(),
|
||||
stopwatch: attr(),
|
||||
createdAt: attr({
|
||||
getDefault: () => new Date(),
|
||||
}),
|
||||
listChangedAt: attr({
|
||||
getDefault: () => new Date(),
|
||||
}),
|
||||
isSubscribed: attr({
|
||||
getDefault: () => false,
|
||||
}),
|
||||
lastCommentId: attr({
|
||||
getDefault: () => null,
|
||||
}),
|
||||
isCommentsFetching: attr({
|
||||
getDefault: () => false,
|
||||
}),
|
||||
isAllCommentsFetched: attr({
|
||||
getDefault: () => null,
|
||||
}),
|
||||
lastActivityId: attr({
|
||||
getDefault: () => null,
|
||||
}),
|
||||
isActivitiesFetching: attr({
|
||||
getDefault: () => false,
|
||||
}),
|
||||
isAllActivitiesFetched: attr({
|
||||
getDefault: () => false,
|
||||
}),
|
||||
isActivitiesDetailsVisible: attr({
|
||||
getDefault: () => false,
|
||||
}),
|
||||
isActivitiesDetailsFetching: attr({
|
||||
getDefault: () => false,
|
||||
getDefault: () => null,
|
||||
}),
|
||||
boardId: fk({
|
||||
to: 'Board',
|
||||
@@ -47,6 +57,16 @@ export default class extends BaseModel {
|
||||
as: 'list',
|
||||
relatedName: 'cards',
|
||||
}),
|
||||
creatorUserId: fk({
|
||||
to: 'User',
|
||||
as: 'creatorUser',
|
||||
relatedName: 'createdCards',
|
||||
}),
|
||||
prevListId: fk({
|
||||
to: 'List',
|
||||
as: 'prevList',
|
||||
relatedName: 'prevCards',
|
||||
}),
|
||||
coverAttachmentId: oneToOne({
|
||||
to: 'Attachment',
|
||||
as: 'coverAttachment',
|
||||
@@ -60,6 +80,8 @@ export default class extends BaseModel {
|
||||
switch (type) {
|
||||
case ActionTypes.LOCATION_CHANGE_HANDLE:
|
||||
case ActionTypes.CORE_INITIALIZE:
|
||||
case ActionTypes.USER_UPDATE_HANDLE:
|
||||
case ActionTypes.PROJECT_UPDATE_HANDLE:
|
||||
case ActionTypes.PROJECT_MANAGER_CREATE_HANDLE:
|
||||
case ActionTypes.BOARD_MEMBERSHIP_CREATE_HANDLE:
|
||||
if (payload.cards) {
|
||||
@@ -121,7 +143,9 @@ export default class extends BaseModel {
|
||||
case ActionTypes.USER_TO_CARD_ADD_HANDLE:
|
||||
try {
|
||||
Card.withId(payload.cardMembership.cardId).users.add(payload.cardMembership.userId);
|
||||
} catch {} // eslint-disable-line no-empty
|
||||
} catch {
|
||||
/* empty */
|
||||
}
|
||||
|
||||
break;
|
||||
case ActionTypes.USER_FROM_CARD_REMOVE:
|
||||
@@ -132,7 +156,9 @@ export default class extends BaseModel {
|
||||
case ActionTypes.USER_FROM_CARD_REMOVE_HANDLE:
|
||||
try {
|
||||
Card.withId(payload.cardMembership.cardId).users.remove(payload.cardMembership.userId);
|
||||
} catch {} // eslint-disable-line no-empty
|
||||
} catch {
|
||||
/* empty */
|
||||
}
|
||||
|
||||
break;
|
||||
case ActionTypes.BOARD_FETCH__SUCCESS:
|
||||
@@ -148,6 +174,22 @@ export default class extends BaseModel {
|
||||
Card.withId(cardId).labels.add(labelId);
|
||||
});
|
||||
|
||||
break;
|
||||
case ActionTypes.LABEL_FROM_CARD_CREATE:
|
||||
Card.withId(payload.cardId).labels.add(payload.label.id);
|
||||
|
||||
break;
|
||||
case ActionTypes.LABEL_FROM_CARD_CREATE__SUCCESS: {
|
||||
const cardModel = Card.withId(payload.cardLabel.cardId);
|
||||
|
||||
cardModel.labels.remove(payload.localId);
|
||||
cardModel.labels.add(payload.label.id);
|
||||
|
||||
break;
|
||||
}
|
||||
case ActionTypes.LABEL_FROM_CARD_CREATE__FAILURE:
|
||||
Card.withId(payload.cardId).labels.remove(payload.localId);
|
||||
|
||||
break;
|
||||
case ActionTypes.LABEL_TO_CARD_ADD:
|
||||
Card.withId(payload.cardId).labels.add(payload.id);
|
||||
@@ -157,7 +199,9 @@ export default class extends BaseModel {
|
||||
case ActionTypes.LABEL_TO_CARD_ADD_HANDLE:
|
||||
try {
|
||||
Card.withId(payload.cardLabel.cardId).labels.add(payload.cardLabel.labelId);
|
||||
} catch {} // eslint-disable-line no-empty
|
||||
} catch {
|
||||
/* empty */
|
||||
}
|
||||
|
||||
break;
|
||||
case ActionTypes.LABEL_FROM_CARD_REMOVE:
|
||||
@@ -168,16 +212,74 @@ export default class extends BaseModel {
|
||||
case ActionTypes.LABEL_FROM_CARD_REMOVE_HANDLE:
|
||||
try {
|
||||
Card.withId(payload.cardLabel.cardId).labels.remove(payload.cardLabel.labelId);
|
||||
} catch {} // eslint-disable-line no-empty
|
||||
} catch {
|
||||
/* empty */
|
||||
}
|
||||
|
||||
break;
|
||||
case ActionTypes.LIST_SORT__SUCCESS:
|
||||
case ActionTypes.LIST_SORT_HANDLE:
|
||||
case ActionTypes.NOTIFICATION_CREATE_HANDLE:
|
||||
case ActionTypes.LIST_CARDS_MOVE__SUCCESS:
|
||||
case ActionTypes.LIST_DELETE__SUCCESS:
|
||||
case ActionTypes.CARDS_UPDATE_HANDLE:
|
||||
payload.cards.forEach((card) => {
|
||||
Card.upsert(card);
|
||||
});
|
||||
|
||||
break;
|
||||
case ActionTypes.LIST_CARDS_MOVE: {
|
||||
const listChangedAt = new Date();
|
||||
|
||||
payload.cardIds.forEach((cardId) => {
|
||||
const cardModel = Card.withId(cardId);
|
||||
|
||||
cardModel.update({
|
||||
listChangedAt,
|
||||
listId: payload.nextId,
|
||||
prevListId: cardModel.listId,
|
||||
});
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
case ActionTypes.LIST_DELETE: {
|
||||
const listChangedAt = new Date();
|
||||
|
||||
payload.cardIds.forEach((cardId) => {
|
||||
Card.withId(cardId).update({
|
||||
listChangedAt,
|
||||
listId: payload.trashId,
|
||||
});
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
case ActionTypes.LIST_DELETE_HANDLE:
|
||||
if (payload.cards) {
|
||||
payload.cards.forEach((card) => {
|
||||
Card.upsert(card);
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
case ActionTypes.CARDS_FETCH__SUCCESS:
|
||||
payload.cards.forEach((card) => {
|
||||
const cardModel = Card.withId(card.id);
|
||||
|
||||
if (cardModel) {
|
||||
cardModel.deleteWithRelated();
|
||||
}
|
||||
|
||||
Card.upsert(card);
|
||||
});
|
||||
|
||||
payload.cardMemberships.forEach(({ cardId, userId }) => {
|
||||
Card.withId(cardId).users.add(userId);
|
||||
});
|
||||
|
||||
payload.cardLabels.forEach(({ cardId, labelId }) => {
|
||||
Card.withId(cardId).labels.add(labelId);
|
||||
});
|
||||
|
||||
break;
|
||||
case ActionTypes.CARD_CREATE:
|
||||
case ActionTypes.CARD_UPDATE__SUCCESS:
|
||||
@@ -185,23 +287,26 @@ export default class extends BaseModel {
|
||||
|
||||
break;
|
||||
case ActionTypes.CARD_CREATE__SUCCESS:
|
||||
Card.withId(payload.localId).delete();
|
||||
Card.withId(payload.localId).deleteWithClearable();
|
||||
Card.upsert(payload.card);
|
||||
|
||||
break;
|
||||
case ActionTypes.CARD_CREATE_HANDLE: {
|
||||
const cardModel = Card.upsert(payload.card);
|
||||
case ActionTypes.CARD_CREATE__FAILURE:
|
||||
Card.withId(payload.localId).deleteWithClearable();
|
||||
|
||||
payload.cardMemberships.forEach(({ userId }) => {
|
||||
cardModel.users.add(userId);
|
||||
break;
|
||||
case ActionTypes.CARD_CREATE_HANDLE:
|
||||
Card.upsert(payload.card);
|
||||
|
||||
payload.cardMemberships.forEach(({ cardId, userId }) => {
|
||||
Card.withId(cardId).users.add(userId);
|
||||
});
|
||||
|
||||
payload.cardLabels.forEach(({ labelId }) => {
|
||||
cardModel.labels.add(labelId);
|
||||
payload.cardLabels.forEach(({ cardId, labelId }) => {
|
||||
Card.withId(cardId).labels.add(labelId);
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
case ActionTypes.CARD_UPDATE: {
|
||||
const cardModel = Card.withId(payload.id);
|
||||
|
||||
@@ -209,22 +314,17 @@ export default class extends BaseModel {
|
||||
if (payload.data.boardId && payload.data.boardId !== cardModel.boardId) {
|
||||
cardModel.deleteWithRelated();
|
||||
} else {
|
||||
cardModel.update({
|
||||
...payload.data,
|
||||
...(payload.data.dueDate === null && {
|
||||
isDueDateCompleted: null,
|
||||
}),
|
||||
...(payload.data.dueDate &&
|
||||
!cardModel.dueDate && {
|
||||
isDueDateCompleted: false,
|
||||
}),
|
||||
});
|
||||
if (payload.data.listId && payload.data.listId !== cardModel.listId) {
|
||||
payload.data.listChangedAt = new Date(); // eslint-disable-line no-param-reassign
|
||||
}
|
||||
|
||||
cardModel.update(payload.data);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case ActionTypes.CARD_UPDATE_HANDLE:
|
||||
if (payload.isFetched) {
|
||||
if (payload.card.boardId === null || payload.isFetched) {
|
||||
const cardModel = Card.withId(payload.card.id);
|
||||
|
||||
if (cardModel) {
|
||||
@@ -232,7 +332,9 @@ export default class extends BaseModel {
|
||||
}
|
||||
}
|
||||
|
||||
Card.upsert(payload.card);
|
||||
if (payload.card.boardId !== null) {
|
||||
Card.upsert(payload.card);
|
||||
}
|
||||
|
||||
if (payload.cardMemberships) {
|
||||
payload.cardMemberships.forEach(({ cardId, userId }) => {
|
||||
@@ -247,35 +349,13 @@ export default class extends BaseModel {
|
||||
}
|
||||
|
||||
break;
|
||||
case ActionTypes.CARD_DUPLICATE: {
|
||||
const cardModel = Card.withId(payload.id);
|
||||
|
||||
const nextCardModel = Card.upsert({
|
||||
...pick(cardModel.ref, [
|
||||
'boardId',
|
||||
'listId',
|
||||
'position',
|
||||
'name',
|
||||
'description',
|
||||
'dueDate',
|
||||
'isDueDateCompleted',
|
||||
'stopwatch',
|
||||
]),
|
||||
...payload.card,
|
||||
});
|
||||
|
||||
cardModel.users.toRefArray().forEach(({ id }) => {
|
||||
nextCardModel.users.add(id);
|
||||
});
|
||||
|
||||
cardModel.labels.toRefArray().forEach(({ id }) => {
|
||||
nextCardModel.labels.add(id);
|
||||
});
|
||||
case ActionTypes.CARD_DUPLICATE:
|
||||
Card.withId(payload.id).duplicate(payload.localId, payload.data);
|
||||
|
||||
break;
|
||||
}
|
||||
case ActionTypes.CARD_DUPLICATE__SUCCESS: {
|
||||
Card.withId(payload.localId).deleteWithRelated();
|
||||
|
||||
const cardModel = Card.upsert(payload.card);
|
||||
|
||||
payload.cardMemberships.forEach(({ userId }) => {
|
||||
@@ -288,6 +368,10 @@ export default class extends BaseModel {
|
||||
|
||||
break;
|
||||
}
|
||||
case ActionTypes.CARD_DUPLICATE__FAILURE:
|
||||
Card.withId(payload.localId).deleteWithRelated();
|
||||
|
||||
break;
|
||||
case ActionTypes.CARD_DELETE:
|
||||
Card.withId(payload.id).deleteWithRelated();
|
||||
|
||||
@@ -302,6 +386,22 @@ export default class extends BaseModel {
|
||||
|
||||
break;
|
||||
}
|
||||
case ActionTypes.COMMENTS_FETCH:
|
||||
Card.withId(payload.cardId).update({
|
||||
isCommentsFetching: true,
|
||||
});
|
||||
|
||||
break;
|
||||
case ActionTypes.COMMENTS_FETCH__SUCCESS:
|
||||
Card.withId(payload.cardId).update({
|
||||
isCommentsFetching: false,
|
||||
isAllCommentsFetched: payload.comments.length < Config.COMMENTS_LIMIT,
|
||||
...(payload.comments.length > 0 && {
|
||||
lastCommentId: payload.comments[payload.comments.length - 1].id,
|
||||
}),
|
||||
});
|
||||
|
||||
break;
|
||||
case ActionTypes.ACTIVITIES_FETCH:
|
||||
Card.withId(payload.cardId).update({
|
||||
isActivitiesFetching: true,
|
||||
@@ -312,53 +412,34 @@ export default class extends BaseModel {
|
||||
Card.withId(payload.cardId).update({
|
||||
isActivitiesFetching: false,
|
||||
isAllActivitiesFetched: payload.activities.length < Config.ACTIVITIES_LIMIT,
|
||||
...(payload.activities.length > 0 && {
|
||||
lastActivityId: payload.activities[payload.activities.length - 1].id,
|
||||
}),
|
||||
});
|
||||
|
||||
break;
|
||||
case ActionTypes.ACTIVITIES_DETAILS_TOGGLE: {
|
||||
const cardModel = Card.withId(payload.cardId);
|
||||
cardModel.isActivitiesDetailsVisible = payload.isVisible;
|
||||
|
||||
if (payload.isVisible) {
|
||||
cardModel.isActivitiesDetailsFetching = true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case ActionTypes.ACTIVITIES_DETAILS_TOGGLE__SUCCESS: {
|
||||
const cardModel = Card.withId(payload.cardId);
|
||||
|
||||
cardModel.update({
|
||||
isAllActivitiesFetched: payload.activities.length < Config.ACTIVITIES_LIMIT,
|
||||
isActivitiesDetailsFetching: false,
|
||||
});
|
||||
|
||||
cardModel.deleteActivities();
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
getOrderedTasksQuerySet() {
|
||||
return this.tasks.orderBy('position');
|
||||
getTaskListsQuerySet() {
|
||||
return this.taskLists.orderBy(['position', 'id.length', 'id']);
|
||||
}
|
||||
|
||||
getOrderedAttachmentsQuerySet() {
|
||||
return this.attachments.orderBy('createdAt', false);
|
||||
getAttachmentsQuerySet() {
|
||||
return this.attachments.orderBy(['id.length', 'id'], ['desc', 'desc']);
|
||||
}
|
||||
|
||||
getFilteredOrderedInCardActivitiesQuerySet() {
|
||||
const filter = {
|
||||
isInCard: true,
|
||||
};
|
||||
getCustomFieldGroupsQuerySet() {
|
||||
return this.customFieldGroups.orderBy(['position', 'id.length', 'id']);
|
||||
}
|
||||
|
||||
if (!this.isActivitiesDetailsVisible) {
|
||||
filter.type = ActivityTypes.COMMENT_CARD;
|
||||
}
|
||||
getCommentsQuerySet() {
|
||||
return this.comments.orderBy(['id.length', 'id'], ['desc', 'desc']);
|
||||
}
|
||||
|
||||
return this.activities.filter(filter).orderBy('createdAt', false);
|
||||
getActivitiesQuerySet() {
|
||||
return this.activities.orderBy(['id.length', 'id'], ['desc', 'desc']);
|
||||
}
|
||||
|
||||
getUnreadNotificationsQuerySet() {
|
||||
@@ -367,8 +448,143 @@ export default class extends BaseModel {
|
||||
});
|
||||
}
|
||||
|
||||
isAvailableForUser(userId) {
|
||||
return this.board && this.board.isAvailableForUser(userId);
|
||||
getShownOnFrontOfCardTaskListsModelArray() {
|
||||
return this.getTaskListsQuerySet()
|
||||
.toModelArray()
|
||||
.filter((taskListModel) => taskListModel.showOnFrontOfCard);
|
||||
}
|
||||
|
||||
getCommentsModelArray() {
|
||||
if (this.isAllCommentsFetched === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const commentModels = this.getCommentsQuerySet().toModelArray();
|
||||
|
||||
if (this.lastCommentId && this.isAllCommentsFetched === false) {
|
||||
return commentModels.filter((commentModel) => {
|
||||
if (commentModel.id.length > this.lastCommentId.length) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (commentModel.id.length < this.lastCommentId.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return commentModel.id >= this.lastCommentId;
|
||||
});
|
||||
}
|
||||
|
||||
return commentModels;
|
||||
}
|
||||
|
||||
getActivitiesModelArray() {
|
||||
if (this.isAllActivitiesFetched === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const activityModels = this.getActivitiesQuerySet().toModelArray();
|
||||
|
||||
if (this.lastActivityId && this.isAllActivitiesFetched === false) {
|
||||
return activityModels.filter((activityModel) => {
|
||||
if (activityModel.id.length > this.lastActivityId.length) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (activityModel.id.length < this.lastActivityId.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return activityModel.id >= this.lastActivityId;
|
||||
});
|
||||
}
|
||||
|
||||
return activityModels;
|
||||
}
|
||||
|
||||
hasUserWithId(userId) {
|
||||
return this.cardusersSet
|
||||
.filter({
|
||||
toUserId: userId,
|
||||
})
|
||||
.exists();
|
||||
}
|
||||
|
||||
isAvailableForUser(userModel) {
|
||||
return !!this.list && this.list.isAvailableForUser(userModel);
|
||||
}
|
||||
|
||||
duplicate(id, data, rootId) {
|
||||
if (rootId === undefined) {
|
||||
rootId = id; // eslint-disable-line no-param-reassign
|
||||
}
|
||||
|
||||
const cardModel = this.getClass().create({
|
||||
id,
|
||||
boardId: this.boardId,
|
||||
listId: this.listId,
|
||||
creatorUserId: this.creatorUserId,
|
||||
prevListId: this.prevListId,
|
||||
coverAttachmentId: this.coverAttachmentId && `${this.coverAttachmentId}-${rootId}`,
|
||||
type: this.type,
|
||||
position: this.position,
|
||||
name: this.name,
|
||||
description: this.description,
|
||||
dueDate: this.dueDate,
|
||||
stopwatch: this.stopwatch,
|
||||
...data,
|
||||
});
|
||||
|
||||
this.users.toRefArray().forEach((user) => {
|
||||
cardModel.users.add(user.id);
|
||||
});
|
||||
|
||||
this.labels.toRefArray().forEach((label) => {
|
||||
cardModel.labels.add(label.id);
|
||||
});
|
||||
|
||||
this.taskLists.toModelArray().forEach((taskListModel) => {
|
||||
taskListModel.duplicate(`${taskListModel.id}-${rootId}`, {
|
||||
cardId: cardModel.id,
|
||||
});
|
||||
});
|
||||
|
||||
this.attachments.toModelArray().forEach((attachmentModel) => {
|
||||
attachmentModel.duplicate(`${attachmentModel.id}-${rootId}`, {
|
||||
cardId: cardModel.id,
|
||||
...(data.creatorUserId && {
|
||||
creatorUserId: data.creatorUserId,
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
this.customFieldGroups.toModelArray().forEach((customFieldGroupModel) => {
|
||||
customFieldGroupModel.duplicate(
|
||||
`${customFieldGroupModel.id}-${rootId}`,
|
||||
{
|
||||
cardId: cardModel.id,
|
||||
},
|
||||
rootId,
|
||||
);
|
||||
});
|
||||
|
||||
this.customFieldValues.toModelArray().forEach((customFieldValueModel) => {
|
||||
const customFieldValueData = {
|
||||
cardId: cardModel.id,
|
||||
};
|
||||
|
||||
if (customFieldValueModel.customFieldGroup.cardId) {
|
||||
customFieldValueData.customFieldGroupId = `${customFieldValueModel.customFieldGroupId}-${rootId}`;
|
||||
|
||||
if (customFieldValueModel.customField.customFieldGroupId) {
|
||||
customFieldValueData.customFieldId = `${customFieldValueModel.customFieldId}-${rootId}`;
|
||||
}
|
||||
}
|
||||
|
||||
customFieldValueModel.duplicate(customFieldValueData);
|
||||
});
|
||||
|
||||
return cardModel;
|
||||
}
|
||||
|
||||
deleteClearable() {
|
||||
@@ -376,23 +592,22 @@ export default class extends BaseModel {
|
||||
this.labels.clear();
|
||||
}
|
||||
|
||||
deleteActivities() {
|
||||
this.activities.toModelArray().forEach((activityModel) => {
|
||||
if (activityModel.notification) {
|
||||
activityModel.update({
|
||||
isInCard: false,
|
||||
});
|
||||
} else {
|
||||
activityModel.delete();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
deleteRelated() {
|
||||
this.deleteClearable();
|
||||
this.tasks.delete();
|
||||
|
||||
this.taskLists.toModelArray().forEach((taskListModel) => {
|
||||
taskListModel.deleteWithRelated();
|
||||
});
|
||||
|
||||
this.attachments.delete();
|
||||
this.deleteActivities();
|
||||
|
||||
this.customFieldGroups.toModelArray().forEach((customFieldGroupModel) => {
|
||||
customFieldGroupModel.deleteWithRelated();
|
||||
});
|
||||
|
||||
this.customFieldValues.delete();
|
||||
this.comments.delete();
|
||||
this.activities.delete();
|
||||
}
|
||||
|
||||
deleteWithClearable() {
|
||||
|
||||
81
client/src/models/Comment.js
Executable file
81
client/src/models/Comment.js
Executable file
@@ -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 { attr, fk } from 'redux-orm';
|
||||
|
||||
import BaseModel from './BaseModel';
|
||||
import ActionTypes from '../constants/ActionTypes';
|
||||
|
||||
export default class extends BaseModel {
|
||||
static modelName = 'Comment';
|
||||
|
||||
static fields = {
|
||||
id: attr(),
|
||||
text: attr(),
|
||||
createdAt: attr({
|
||||
getDefault: () => new Date(),
|
||||
}),
|
||||
cardId: fk({
|
||||
to: 'Card',
|
||||
as: 'card',
|
||||
relatedName: 'comments',
|
||||
}),
|
||||
userId: fk({
|
||||
to: 'User',
|
||||
as: 'user',
|
||||
relatedName: 'comments',
|
||||
}),
|
||||
};
|
||||
|
||||
static reducer({ type, payload }, Comment) {
|
||||
switch (type) {
|
||||
case ActionTypes.SOCKET_RECONNECT_HANDLE:
|
||||
Comment.all().delete();
|
||||
|
||||
break;
|
||||
case ActionTypes.COMMENTS_FETCH__SUCCESS:
|
||||
payload.comments.forEach((comment) => {
|
||||
Comment.upsert(comment);
|
||||
});
|
||||
|
||||
break;
|
||||
case ActionTypes.COMMENT_CREATE:
|
||||
case ActionTypes.COMMENT_CREATE_HANDLE:
|
||||
case ActionTypes.COMMENT_UPDATE__SUCCESS:
|
||||
case ActionTypes.COMMENT_UPDATE_HANDLE:
|
||||
Comment.upsert(payload.comment);
|
||||
|
||||
break;
|
||||
case ActionTypes.COMMENT_CREATE__SUCCESS:
|
||||
Comment.withId(payload.localId).delete();
|
||||
Comment.upsert(payload.comment);
|
||||
|
||||
break;
|
||||
case ActionTypes.COMMENT_CREATE__FAILURE:
|
||||
Comment.withId(payload.localId).delete();
|
||||
|
||||
break;
|
||||
case ActionTypes.COMMENT_UPDATE:
|
||||
Comment.withId(payload.id).update(payload.data);
|
||||
|
||||
break;
|
||||
case ActionTypes.COMMENT_DELETE:
|
||||
Comment.withId(payload.id).delete();
|
||||
|
||||
break;
|
||||
case ActionTypes.COMMENT_DELETE__SUCCESS:
|
||||
case ActionTypes.COMMENT_DELETE_HANDLE: {
|
||||
const commentModel = Comment.withId(payload.comment.id);
|
||||
|
||||
if (commentModel) {
|
||||
commentModel.delete();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
125
client/src/models/CustomField.js
Normal file
125
client/src/models/CustomField.js
Normal file
@@ -0,0 +1,125 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import { attr, fk } from 'redux-orm';
|
||||
|
||||
import BaseModel from './BaseModel';
|
||||
import ActionTypes from '../constants/ActionTypes';
|
||||
|
||||
export default class extends BaseModel {
|
||||
static modelName = 'CustomField';
|
||||
|
||||
static fields = {
|
||||
id: attr(),
|
||||
position: attr(),
|
||||
name: attr(),
|
||||
showOnFrontOfCard: attr(),
|
||||
baseCustomFieldGroupId: fk({
|
||||
to: 'BaseCustomFieldGroup',
|
||||
as: 'baseGroup',
|
||||
relatedName: 'customFields',
|
||||
}),
|
||||
customFieldGroupId: fk({
|
||||
to: 'CustomFieldGroup',
|
||||
as: 'group',
|
||||
relatedName: 'customFields',
|
||||
}),
|
||||
};
|
||||
|
||||
static reducer({ type, payload }, CustomField) {
|
||||
switch (type) {
|
||||
case ActionTypes.LOCATION_CHANGE_HANDLE:
|
||||
case ActionTypes.CORE_INITIALIZE:
|
||||
case ActionTypes.USER_UPDATE_HANDLE:
|
||||
case ActionTypes.PROJECT_UPDATE_HANDLE:
|
||||
case ActionTypes.PROJECT_MANAGER_CREATE_HANDLE:
|
||||
case ActionTypes.BOARD_MEMBERSHIP_CREATE_HANDLE:
|
||||
case ActionTypes.CARD_UPDATE_HANDLE:
|
||||
if (payload.customFields) {
|
||||
payload.customFields.forEach((customField) => {
|
||||
CustomField.upsert(customField);
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
case ActionTypes.SOCKET_RECONNECT_HANDLE:
|
||||
CustomField.all().delete();
|
||||
|
||||
if (payload.customFields) {
|
||||
payload.customFields.forEach((customField) => {
|
||||
CustomField.upsert(customField);
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
case ActionTypes.PROJECT_CREATE_HANDLE:
|
||||
case ActionTypes.BOARD_FETCH__SUCCESS:
|
||||
case ActionTypes.CARDS_FETCH__SUCCESS:
|
||||
case ActionTypes.CARD_CREATE_HANDLE:
|
||||
case ActionTypes.CARD_DUPLICATE__SUCCESS:
|
||||
payload.customFields.forEach((customField) => {
|
||||
CustomField.upsert(customField);
|
||||
});
|
||||
|
||||
break;
|
||||
case ActionTypes.CUSTOM_FIELD_CREATE:
|
||||
case ActionTypes.CUSTOM_FIELD_CREATE_HANDLE:
|
||||
case ActionTypes.CUSTOM_FIELD_UPDATE__SUCCESS:
|
||||
case ActionTypes.CUSTOM_FIELD_UPDATE_HANDLE:
|
||||
CustomField.upsert(payload.customField);
|
||||
|
||||
break;
|
||||
case ActionTypes.CUSTOM_FIELD_CREATE__SUCCESS:
|
||||
CustomField.withId(payload.localId).delete();
|
||||
CustomField.upsert(payload.customField);
|
||||
|
||||
break;
|
||||
case ActionTypes.CUSTOM_FIELD_CREATE__FAILURE:
|
||||
CustomField.withId(payload.localId).delete();
|
||||
|
||||
break;
|
||||
case ActionTypes.CUSTOM_FIELD_UPDATE:
|
||||
CustomField.withId(payload.id).update(payload.data);
|
||||
|
||||
break;
|
||||
case ActionTypes.CUSTOM_FIELD_DELETE:
|
||||
CustomField.withId(payload.id).deleteWithRelated();
|
||||
|
||||
break;
|
||||
case ActionTypes.CUSTOM_FIELD_DELETE__SUCCESS:
|
||||
case ActionTypes.CUSTOM_FIELD_DELETE_HANDLE: {
|
||||
const customFieldModel = CustomField.withId(payload.customField.id);
|
||||
|
||||
if (customFieldModel) {
|
||||
customFieldModel.deleteWithRelated();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
duplicate(id, data) {
|
||||
return this.getClass().create({
|
||||
id,
|
||||
baseCustomFieldGroupId: this.baseCustomFieldGroupId,
|
||||
customFieldGroupId: this.customFieldGroupId,
|
||||
position: this.position,
|
||||
name: this.name,
|
||||
showOnFrontOfCard: this.showOnFrontOfCard,
|
||||
...data,
|
||||
});
|
||||
}
|
||||
|
||||
deleteRelated() {
|
||||
this.customFieldValues.delete();
|
||||
}
|
||||
|
||||
deleteWithRelated() {
|
||||
this.deleteRelated();
|
||||
this.delete();
|
||||
}
|
||||
}
|
||||
163
client/src/models/CustomFieldGroup.js
Normal file
163
client/src/models/CustomFieldGroup.js
Normal file
@@ -0,0 +1,163 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import { attr, fk } from 'redux-orm';
|
||||
|
||||
import BaseModel from './BaseModel';
|
||||
import ActionTypes from '../constants/ActionTypes';
|
||||
|
||||
export default class extends BaseModel {
|
||||
static modelName = 'CustomFieldGroup';
|
||||
|
||||
static fields = {
|
||||
id: attr(),
|
||||
position: attr(),
|
||||
name: attr(),
|
||||
boardId: fk({
|
||||
to: 'Board',
|
||||
as: 'board',
|
||||
relatedName: 'customFieldGroups',
|
||||
}),
|
||||
cardId: fk({
|
||||
to: 'Card',
|
||||
as: 'card',
|
||||
relatedName: 'customFieldGroups',
|
||||
}),
|
||||
baseCustomFieldGroupId: fk({
|
||||
to: 'BaseCustomFieldGroup',
|
||||
as: 'baseCustomFieldGroup',
|
||||
relatedName: 'customFieldGroups',
|
||||
}),
|
||||
};
|
||||
|
||||
static reducer({ type, payload }, CustomFieldGroup) {
|
||||
switch (type) {
|
||||
case ActionTypes.LOCATION_CHANGE_HANDLE:
|
||||
case ActionTypes.CORE_INITIALIZE:
|
||||
case ActionTypes.USER_UPDATE_HANDLE:
|
||||
case ActionTypes.PROJECT_UPDATE_HANDLE:
|
||||
case ActionTypes.PROJECT_MANAGER_CREATE_HANDLE:
|
||||
case ActionTypes.BOARD_MEMBERSHIP_CREATE_HANDLE:
|
||||
case ActionTypes.CARD_UPDATE_HANDLE:
|
||||
if (payload.customFieldGroups) {
|
||||
payload.customFieldGroups.forEach((customFieldGroup) => {
|
||||
CustomFieldGroup.upsert(customFieldGroup);
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
case ActionTypes.SOCKET_RECONNECT_HANDLE:
|
||||
CustomFieldGroup.all().delete();
|
||||
|
||||
if (payload.customFieldGroups) {
|
||||
payload.customFieldGroups.forEach((customFieldGroup) => {
|
||||
CustomFieldGroup.upsert(customFieldGroup);
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
case ActionTypes.BOARD_FETCH__SUCCESS:
|
||||
case ActionTypes.CARDS_FETCH__SUCCESS:
|
||||
case ActionTypes.CARD_CREATE_HANDLE:
|
||||
case ActionTypes.CARD_DUPLICATE__SUCCESS:
|
||||
payload.customFieldGroups.forEach((customFieldGroup) => {
|
||||
CustomFieldGroup.upsert(customFieldGroup);
|
||||
});
|
||||
|
||||
break;
|
||||
case ActionTypes.CUSTOM_FIELD_GROUP_CREATE:
|
||||
case ActionTypes.CUSTOM_FIELD_GROUP_CREATE_HANDLE:
|
||||
case ActionTypes.CUSTOM_FIELD_GROUP_UPDATE__SUCCESS:
|
||||
case ActionTypes.CUSTOM_FIELD_GROUP_UPDATE_HANDLE:
|
||||
CustomFieldGroup.upsert(payload.customFieldGroup);
|
||||
|
||||
break;
|
||||
case ActionTypes.CUSTOM_FIELD_GROUP_CREATE__SUCCESS:
|
||||
CustomFieldGroup.withId(payload.localId).delete();
|
||||
CustomFieldGroup.upsert(payload.customFieldGroup);
|
||||
|
||||
break;
|
||||
case ActionTypes.CUSTOM_FIELD_GROUP_CREATE__FAILURE:
|
||||
CustomFieldGroup.withId(payload.localId).delete();
|
||||
|
||||
break;
|
||||
case ActionTypes.CUSTOM_FIELD_GROUP_UPDATE:
|
||||
CustomFieldGroup.withId(payload.id).update(payload.data);
|
||||
|
||||
break;
|
||||
case ActionTypes.CUSTOM_FIELD_GROUP_DELETE:
|
||||
CustomFieldGroup.withId(payload.id).deleteWithRelated();
|
||||
|
||||
break;
|
||||
case ActionTypes.CUSTOM_FIELD_GROUP_DELETE__SUCCESS:
|
||||
case ActionTypes.CUSTOM_FIELD_GROUP_DELETE_HANDLE: {
|
||||
const customFieldGroupModel = CustomFieldGroup.withId(payload.customFieldGroup.id);
|
||||
|
||||
if (customFieldGroupModel) {
|
||||
customFieldGroupModel.deleteWithRelated();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
getCustomFieldsQuerySet() {
|
||||
return this.customFields.orderBy(['position', 'id.length', 'id']);
|
||||
}
|
||||
|
||||
getCustomFieldsModelArray() {
|
||||
if (this.baseCustomFieldGroupId) {
|
||||
return this.baseCustomFieldGroup.getCustomFieldsQuerySet().toModelArray();
|
||||
}
|
||||
|
||||
return this.getCustomFieldsQuerySet().toModelArray();
|
||||
}
|
||||
|
||||
getShownOnFrontOfCardCustomFieldsModelArray() {
|
||||
return this.getCustomFieldsModelArray().filter(
|
||||
(customFieldModel) => customFieldModel.showOnFrontOfCard,
|
||||
);
|
||||
}
|
||||
|
||||
duplicate(id, data, rootId) {
|
||||
if (rootId === undefined) {
|
||||
rootId = id; // eslint-disable-line no-param-reassign
|
||||
}
|
||||
|
||||
const customFieldGroupModel = this.getClass().create({
|
||||
id,
|
||||
boardId: this.boardId,
|
||||
cardId: this.cardId,
|
||||
baseCustomFieldGroupId: this.baseCustomFieldGroupId,
|
||||
position: this.position,
|
||||
name: this.name,
|
||||
...data,
|
||||
});
|
||||
|
||||
this.customFields.toModelArray().forEach((customFieldModel) => {
|
||||
customFieldModel.duplicate(
|
||||
`${customFieldModel.id}-${rootId}`,
|
||||
{
|
||||
customFieldGroupId: customFieldGroupModel.id,
|
||||
},
|
||||
rootId,
|
||||
);
|
||||
});
|
||||
|
||||
return customFieldGroupModel;
|
||||
}
|
||||
|
||||
deleteRelated() {
|
||||
this.customFields.delete();
|
||||
this.customFieldValues.delete();
|
||||
}
|
||||
|
||||
deleteWithRelated() {
|
||||
this.deleteRelated();
|
||||
this.delete();
|
||||
}
|
||||
}
|
||||
126
client/src/models/CustomFieldValue.js
Normal file
126
client/src/models/CustomFieldValue.js
Normal file
@@ -0,0 +1,126 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import { attr, fk } from 'redux-orm';
|
||||
|
||||
import BaseModel from './BaseModel';
|
||||
import ActionTypes from '../constants/ActionTypes';
|
||||
|
||||
export const buildCustomFieldValueId = (customFieldValue) =>
|
||||
JSON.stringify({
|
||||
cardId: customFieldValue.cardId,
|
||||
customFieldGroupId: customFieldValue.customFieldGroupId,
|
||||
customFieldId: customFieldValue.customFieldId,
|
||||
});
|
||||
|
||||
const prepareCustomFieldValue = (customFieldValue) => ({
|
||||
...customFieldValue,
|
||||
id: buildCustomFieldValueId(customFieldValue),
|
||||
});
|
||||
|
||||
export default class extends BaseModel {
|
||||
static modelName = 'CustomFieldValue';
|
||||
|
||||
static fields = {
|
||||
id: attr(),
|
||||
content: attr(),
|
||||
cardId: fk({
|
||||
to: 'Card',
|
||||
as: 'card',
|
||||
relatedName: 'customFieldValues',
|
||||
}),
|
||||
customFieldGroupId: fk({
|
||||
to: 'CustomFieldGroup',
|
||||
as: 'customFieldGroup',
|
||||
relatedName: 'customFieldValues',
|
||||
}),
|
||||
customFieldId: fk({
|
||||
to: 'CustomField',
|
||||
as: 'customField',
|
||||
relatedName: 'customFieldValues',
|
||||
}),
|
||||
};
|
||||
|
||||
static reducer({ type, payload }, CustomFieldValue) {
|
||||
switch (type) {
|
||||
case ActionTypes.LOCATION_CHANGE_HANDLE:
|
||||
case ActionTypes.CORE_INITIALIZE:
|
||||
case ActionTypes.USER_UPDATE_HANDLE:
|
||||
case ActionTypes.PROJECT_UPDATE_HANDLE:
|
||||
case ActionTypes.PROJECT_MANAGER_CREATE_HANDLE:
|
||||
case ActionTypes.BOARD_MEMBERSHIP_CREATE_HANDLE:
|
||||
case ActionTypes.CARD_UPDATE_HANDLE:
|
||||
if (payload.customFieldValues) {
|
||||
payload.customFieldValues.forEach((customFieldValue) => {
|
||||
CustomFieldValue.upsert(prepareCustomFieldValue(customFieldValue));
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
case ActionTypes.SOCKET_RECONNECT_HANDLE:
|
||||
CustomFieldValue.all().delete();
|
||||
|
||||
if (payload.customFieldValues) {
|
||||
payload.customFieldValues.forEach((customFieldValue) => {
|
||||
CustomFieldValue.upsert(prepareCustomFieldValue(customFieldValue));
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
case ActionTypes.BOARD_FETCH__SUCCESS:
|
||||
case ActionTypes.CARDS_FETCH__SUCCESS:
|
||||
case ActionTypes.CARD_CREATE_HANDLE:
|
||||
case ActionTypes.CARD_DUPLICATE__SUCCESS:
|
||||
payload.customFieldValues.forEach((customFieldValue) => {
|
||||
CustomFieldValue.upsert(prepareCustomFieldValue(customFieldValue));
|
||||
});
|
||||
|
||||
break;
|
||||
case ActionTypes.CUSTOM_FIELD_VALUE_UPDATE:
|
||||
case ActionTypes.CUSTOM_FIELD_VALUE_UPDATE__SUCCESS:
|
||||
case ActionTypes.CUSTOM_FIELD_VALUE_UPDATE_HANDLE:
|
||||
CustomFieldValue.upsert(prepareCustomFieldValue(payload.customFieldValue));
|
||||
|
||||
break;
|
||||
case ActionTypes.CUSTOM_FIELD_VALUE_DELETE: {
|
||||
const customFieldValueModel = CustomFieldValue.withId(payload.id);
|
||||
|
||||
if (customFieldValueModel) {
|
||||
customFieldValueModel.delete();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case ActionTypes.CUSTOM_FIELD_VALUE_DELETE__SUCCESS:
|
||||
case ActionTypes.CUSTOM_FIELD_VALUE_DELETE_HANDLE: {
|
||||
const customFieldValueModel = CustomFieldValue.withId(
|
||||
buildCustomFieldValueId(payload.customFieldValue),
|
||||
);
|
||||
|
||||
if (customFieldValueModel) {
|
||||
customFieldValueModel.delete();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
duplicate(data) {
|
||||
const customFieldValue = {
|
||||
cardId: this.cardId,
|
||||
customFieldGroupId: this.customFieldGroupId,
|
||||
customFieldId: this.customFieldId,
|
||||
content: this.content,
|
||||
...data,
|
||||
};
|
||||
|
||||
return this.getClass().create({
|
||||
id: buildCustomFieldValueId(customFieldValue),
|
||||
...customFieldValue,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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 { attr, fk } from 'redux-orm';
|
||||
|
||||
import BaseModel from './BaseModel';
|
||||
@@ -22,6 +27,8 @@ export default class extends BaseModel {
|
||||
switch (type) {
|
||||
case ActionTypes.LOCATION_CHANGE_HANDLE:
|
||||
case ActionTypes.CORE_INITIALIZE:
|
||||
case ActionTypes.USER_UPDATE_HANDLE:
|
||||
case ActionTypes.PROJECT_UPDATE_HANDLE:
|
||||
case ActionTypes.PROJECT_MANAGER_CREATE_HANDLE:
|
||||
case ActionTypes.BOARD_MEMBERSHIP_CREATE_HANDLE:
|
||||
if (payload.labels) {
|
||||
@@ -48,6 +55,7 @@ export default class extends BaseModel {
|
||||
|
||||
break;
|
||||
case ActionTypes.LABEL_CREATE:
|
||||
case ActionTypes.LABEL_FROM_CARD_CREATE:
|
||||
case ActionTypes.LABEL_CREATE_HANDLE:
|
||||
case ActionTypes.LABEL_UPDATE__SUCCESS:
|
||||
case ActionTypes.LABEL_UPDATE_HANDLE:
|
||||
@@ -55,16 +63,22 @@ export default class extends BaseModel {
|
||||
|
||||
break;
|
||||
case ActionTypes.LABEL_CREATE__SUCCESS:
|
||||
case ActionTypes.LABEL_FROM_CARD_CREATE__SUCCESS:
|
||||
Label.withId(payload.localId).delete();
|
||||
Label.upsert(payload.label);
|
||||
|
||||
break;
|
||||
case ActionTypes.LABEL_CREATE__FAILURE:
|
||||
case ActionTypes.LABEL_FROM_CARD_CREATE__FAILURE:
|
||||
Label.withId(payload.localId).delete();
|
||||
|
||||
break;
|
||||
case ActionTypes.LABEL_UPDATE:
|
||||
Label.withId(payload.id).update(payload.data);
|
||||
|
||||
break;
|
||||
case ActionTypes.LABEL_DELETE:
|
||||
Label.withId(payload.id).delete();
|
||||
Label.withId(payload.id).deleteWithRelated();
|
||||
|
||||
break;
|
||||
case ActionTypes.LABEL_DELETE__SUCCESS:
|
||||
@@ -72,7 +86,7 @@ export default class extends BaseModel {
|
||||
const labelModel = Label.withId(payload.label.id);
|
||||
|
||||
if (labelModel) {
|
||||
labelModel.delete();
|
||||
labelModel.deleteWithRelated();
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -81,15 +95,24 @@ export default class extends BaseModel {
|
||||
}
|
||||
}
|
||||
|
||||
static findLabelsFromText(filterText, labels) {
|
||||
const selectLabel = filterText.toLocaleLowerCase();
|
||||
const matchingLabels = labels.filter((label) =>
|
||||
label.name ? label.name.toLocaleLowerCase().startsWith(selectLabel) : false,
|
||||
);
|
||||
if (matchingLabels.length === 1) {
|
||||
// Appens the user to the filter
|
||||
return matchingLabels[0].id;
|
||||
deleteRelated() {
|
||||
this.board.cards.toModelArray().forEach((cardModel) => {
|
||||
try {
|
||||
cardModel.labels.remove(this.id);
|
||||
} catch {
|
||||
/* empty */
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
this.board.filterLabels.remove(this.id);
|
||||
} catch {
|
||||
/* empty */
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
deleteWithRelated() {
|
||||
this.deleteRelated();
|
||||
this.delete();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,51 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import { attr, fk } from 'redux-orm';
|
||||
|
||||
import BaseModel from './BaseModel';
|
||||
import User from './User';
|
||||
import buildSearchParts from '../utils/build-search-parts';
|
||||
import { isListFinite } from '../utils/record-helpers';
|
||||
import ActionTypes from '../constants/ActionTypes';
|
||||
import Config from '../constants/Config';
|
||||
import { ListSortFieldNames, ListTypes, SortOrders } from '../constants/Enums';
|
||||
|
||||
const POSITION_BY_LIST_TYPE = {
|
||||
[ListTypes.ARCHIVE]: Number.MAX_SAFE_INTEGER - 1,
|
||||
[ListTypes.TRASH]: Number.MAX_SAFE_INTEGER,
|
||||
};
|
||||
|
||||
const prepareList = (list) => {
|
||||
if (list.position === undefined) {
|
||||
return list;
|
||||
}
|
||||
|
||||
return {
|
||||
...list,
|
||||
position: list.position === null ? POSITION_BY_LIST_TYPE[list.type] : list.position,
|
||||
};
|
||||
};
|
||||
|
||||
export default class extends BaseModel {
|
||||
static modelName = 'List';
|
||||
|
||||
static fields = {
|
||||
id: attr(),
|
||||
type: attr(),
|
||||
position: attr(),
|
||||
name: attr(),
|
||||
color: attr(),
|
||||
lastCard: attr({
|
||||
getDefault: () => null,
|
||||
}),
|
||||
isCardsFetching: attr({
|
||||
getDefault: () => false,
|
||||
}),
|
||||
isAllCardsFetched: attr({
|
||||
getDefault: () => null,
|
||||
}),
|
||||
boardId: fk({
|
||||
to: 'Board',
|
||||
as: 'board',
|
||||
@@ -23,11 +57,13 @@ export default class extends BaseModel {
|
||||
switch (type) {
|
||||
case ActionTypes.LOCATION_CHANGE_HANDLE:
|
||||
case ActionTypes.CORE_INITIALIZE:
|
||||
case ActionTypes.USER_UPDATE_HANDLE:
|
||||
case ActionTypes.PROJECT_UPDATE_HANDLE:
|
||||
case ActionTypes.PROJECT_MANAGER_CREATE_HANDLE:
|
||||
case ActionTypes.BOARD_MEMBERSHIP_CREATE_HANDLE:
|
||||
if (payload.lists) {
|
||||
payload.lists.forEach((list) => {
|
||||
List.upsert(list);
|
||||
List.upsert(prepareList(list));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -37,14 +73,28 @@ export default class extends BaseModel {
|
||||
|
||||
if (payload.lists) {
|
||||
payload.lists.forEach((list) => {
|
||||
List.upsert(list);
|
||||
List.upsert(prepareList(list));
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
case ActionTypes.USER_TO_BOARD_FILTER_ADD:
|
||||
case ActionTypes.USER_FROM_BOARD_FILTER_REMOVE:
|
||||
case ActionTypes.IN_BOARD_SEARCH:
|
||||
case ActionTypes.LABEL_TO_BOARD_FILTER_ADD:
|
||||
case ActionTypes.LABEL_FROM_BOARD_FILTER_REMOVE:
|
||||
if (payload.currentListId) {
|
||||
List.withId(payload.currentListId).update({
|
||||
lastCard: null,
|
||||
isCardsFetching: false,
|
||||
isAllCardsFetched: null,
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
case ActionTypes.BOARD_FETCH__SUCCESS:
|
||||
payload.lists.forEach((list) => {
|
||||
List.upsert(list);
|
||||
List.upsert(prepareList(list));
|
||||
});
|
||||
|
||||
break;
|
||||
@@ -53,94 +103,192 @@ export default class extends BaseModel {
|
||||
case ActionTypes.LIST_UPDATE__SUCCESS:
|
||||
case ActionTypes.LIST_UPDATE_HANDLE:
|
||||
case ActionTypes.LIST_SORT__SUCCESS:
|
||||
case ActionTypes.LIST_SORT_HANDLE:
|
||||
List.upsert(payload.list);
|
||||
case ActionTypes.LIST_CARDS_MOVE__SUCCESS:
|
||||
case ActionTypes.LIST_CLEAR__SUCCESS:
|
||||
List.upsert(prepareList(payload.list));
|
||||
|
||||
break;
|
||||
case ActionTypes.LIST_CREATE__SUCCESS:
|
||||
List.withId(payload.localId).delete();
|
||||
List.upsert(payload.list);
|
||||
List.upsert(prepareList(payload.list));
|
||||
|
||||
break;
|
||||
case ActionTypes.LIST_CREATE__FAILURE:
|
||||
List.withId(payload.localId).delete();
|
||||
|
||||
break;
|
||||
case ActionTypes.LIST_UPDATE:
|
||||
List.withId(payload.id).update(payload.data);
|
||||
|
||||
break;
|
||||
case ActionTypes.LIST_DELETE:
|
||||
List.withId(payload.id).deleteWithRelated();
|
||||
case ActionTypes.LIST_SORT:
|
||||
List.withId(payload.id).sortCards(payload.data);
|
||||
|
||||
break;
|
||||
case ActionTypes.LIST_DELETE__SUCCESS:
|
||||
case ActionTypes.LIST_CLEAR:
|
||||
List.withId(payload.id).deleteRelated();
|
||||
|
||||
break;
|
||||
case ActionTypes.LIST_CLEAR_HANDLE: {
|
||||
const listModel = List.withId(payload.list.id);
|
||||
|
||||
if (listModel) {
|
||||
listModel.deleteRelated();
|
||||
}
|
||||
|
||||
List.upsert(prepareList(payload.list));
|
||||
|
||||
break;
|
||||
}
|
||||
case ActionTypes.LIST_DELETE:
|
||||
List.withId(payload.id).delete();
|
||||
|
||||
break;
|
||||
case ActionTypes.LIST_DELETE__SUCCESS: {
|
||||
const listModel = List.withId(payload.list.id);
|
||||
|
||||
if (listModel) {
|
||||
listModel.delete();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case ActionTypes.LIST_DELETE_HANDLE: {
|
||||
const listModel = List.withId(payload.list.id);
|
||||
|
||||
if (listModel) {
|
||||
listModel.deleteWithRelated();
|
||||
if (payload.cards) {
|
||||
listModel.delete();
|
||||
} else {
|
||||
listModel.deleteWithRelated();
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case ActionTypes.CARDS_FETCH:
|
||||
List.withId(payload.listId).update({
|
||||
isCardsFetching: true,
|
||||
});
|
||||
|
||||
break;
|
||||
case ActionTypes.CARDS_FETCH__SUCCESS: {
|
||||
const lastCard = payload.cards[payload.cards.length - 1];
|
||||
|
||||
List.withId(payload.listId).update({
|
||||
isCardsFetching: false,
|
||||
isAllCardsFetched: payload.cards.length < Config.CARDS_LIMIT,
|
||||
...(lastCard && {
|
||||
lastCard: {
|
||||
listChangedAt: lastCard.listChangedAt,
|
||||
id: lastCard.id,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
getOrderedCardsQuerySet() {
|
||||
return this.cards.orderBy('position');
|
||||
getCardsQuerySet() {
|
||||
const orderByArgs = isListFinite(this)
|
||||
? [['position', 'id.length', 'id']]
|
||||
: [
|
||||
['listChangedAt', 'id.length', 'id'],
|
||||
['desc', 'desc', 'desc'],
|
||||
];
|
||||
|
||||
return this.cards.orderBy(...orderByArgs);
|
||||
}
|
||||
|
||||
getFilteredOrderedCardsModelArray() {
|
||||
let cardModels = this.getOrderedCardsQuerySet().toModelArray();
|
||||
getCardsModelArray() {
|
||||
const isFinite = isListFinite(this);
|
||||
|
||||
const { filterText } = this.board;
|
||||
if (!isFinite && this.isAllCardsFetched === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (filterText !== '') {
|
||||
let re = null;
|
||||
const posSpace = filterText.indexOf(' ');
|
||||
const cardModels = this.getCardsQuerySet().toModelArray();
|
||||
|
||||
if (filterText.startsWith('/')) {
|
||||
re = new RegExp(filterText.substring(1), 'i');
|
||||
}
|
||||
let doRegularSearch = true;
|
||||
if (re) {
|
||||
cardModels = cardModels.filter(
|
||||
(cardModel) => re.test(cardModel.name) || re.test(cardModel?.description),
|
||||
);
|
||||
doRegularSearch = false;
|
||||
} else if (filterText.startsWith('!') && posSpace > 0) {
|
||||
const creatorUserId = User.findUsersFromText(
|
||||
filterText.substring(1, posSpace),
|
||||
this.board.memberships.toModelArray().map((membership) => membership.user),
|
||||
);
|
||||
if (creatorUserId != null) {
|
||||
doRegularSearch = false;
|
||||
cardModels = cardModels.filter((cardModel) => cardModel.creatorUser.id === creatorUserId);
|
||||
if (!isFinite && this.lastCard && this.isAllCardsFetched === false) {
|
||||
return cardModels.filter((cardModel) => {
|
||||
if (cardModel.listChangedAt > this.lastCard.listChangedAt) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (doRegularSearch) {
|
||||
const lowerCasedFilter = filterText.toLocaleLowerCase();
|
||||
cardModels = cardModels.filter(
|
||||
(cardModel) =>
|
||||
cardModel.name.toLocaleLowerCase().indexOf(lowerCasedFilter) >= 0 ||
|
||||
cardModel.description?.toLocaleLowerCase().indexOf(lowerCasedFilter) >= 0,
|
||||
);
|
||||
|
||||
if (cardModel.listChangedAt < this.lastCard.listChangedAt) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cardModel.id.length > this.lastCard.id.length) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (cardModel.id.length < this.lastCard.id.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return cardModel.id >= this.lastCard.id;
|
||||
});
|
||||
}
|
||||
|
||||
return cardModels;
|
||||
}
|
||||
|
||||
getFilteredCardsModelArray() {
|
||||
let cardModels = this.getCardsModelArray();
|
||||
|
||||
if (cardModels.length === 0) {
|
||||
return cardModels;
|
||||
}
|
||||
|
||||
if (this.board.search) {
|
||||
if (this.board.search.startsWith('/')) {
|
||||
let searchRegex;
|
||||
try {
|
||||
searchRegex = new RegExp(this.board.search.substring(1), 'i');
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (searchRegex) {
|
||||
cardModels = cardModels.filter(
|
||||
(cardModel) =>
|
||||
searchRegex.test(cardModel.name) ||
|
||||
(cardModel.description && searchRegex.test(cardModel.description)),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const searchParts = buildSearchParts(this.board.search);
|
||||
|
||||
cardModels = cardModels.filter((cardModel) => {
|
||||
const name = cardModel.name.toLowerCase();
|
||||
const description = cardModel.description && cardModel.description.toLowerCase();
|
||||
|
||||
return searchParts.every(
|
||||
(searchPart) =>
|
||||
name.includes(searchPart) || (description && description.includes(searchPart)),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const filterUserIds = this.board.filterUsers.toRefArray().map((user) => user.id);
|
||||
const filterLabelIds = this.board.filterLabels.toRefArray().map((label) => label.id);
|
||||
|
||||
if (filterUserIds.length > 0) {
|
||||
cardModels = cardModels.filter((cardModel) => {
|
||||
const users = cardModel.users.toRefArray();
|
||||
|
||||
return users.some((user) => filterUserIds.includes(user.id));
|
||||
});
|
||||
}
|
||||
|
||||
const filterLabelIds = this.board.filterLabels.toRefArray().map((label) => label.id);
|
||||
|
||||
if (filterLabelIds.length > 0) {
|
||||
cardModels = cardModels.filter((cardModel) => {
|
||||
const labels = cardModel.labels.toRefArray();
|
||||
|
||||
return labels.some((label) => filterLabelIds.includes(label.id));
|
||||
});
|
||||
}
|
||||
@@ -148,6 +296,51 @@ export default class extends BaseModel {
|
||||
return cardModels;
|
||||
}
|
||||
|
||||
isAvailableForUser(userModel) {
|
||||
return !!this.board && this.board.isAvailableForUser(userModel);
|
||||
}
|
||||
|
||||
sortCards(options) {
|
||||
const cardModels = this.getCardsQuerySet().toModelArray();
|
||||
|
||||
switch (options.fieldName) {
|
||||
case ListSortFieldNames.NAME:
|
||||
cardModels.sort((card1, card2) => card1.name.localeCompare(card2.name));
|
||||
|
||||
break;
|
||||
case ListSortFieldNames.DUE_DATE:
|
||||
cardModels.sort((card1, card2) => {
|
||||
if (card1.dueDate === null) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (card2.dueDate === null) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return card1.dueDate - card2.dueDate;
|
||||
});
|
||||
|
||||
break;
|
||||
case ListSortFieldNames.CREATED_AT:
|
||||
cardModels.sort((card1, card2) => card1.createdAt - card2.createdAt);
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (options.order === SortOrders.DESC) {
|
||||
cardModels.reverse();
|
||||
}
|
||||
|
||||
cardModels.forEach((cardModel, index) => {
|
||||
cardModel.update({
|
||||
position: Config.POSITION_GAP * (index + 1),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
deleteRelated() {
|
||||
this.cards.toModelArray().forEach((cardModel) => {
|
||||
cardModel.deleteWithRelated();
|
||||
|
||||
@@ -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 { attr, fk, oneToOne } from 'redux-orm';
|
||||
|
||||
import BaseModel from './BaseModel';
|
||||
@@ -16,11 +21,25 @@ export default class extends BaseModel {
|
||||
as: 'user',
|
||||
relatedName: 'notifications',
|
||||
}),
|
||||
creatorUserId: fk({
|
||||
to: 'User',
|
||||
as: 'creatorUser',
|
||||
relatedName: 'createdNotifications',
|
||||
}),
|
||||
boardId: fk({
|
||||
to: 'Board',
|
||||
as: 'board',
|
||||
relatedName: 'notifications',
|
||||
}),
|
||||
cardId: fk({
|
||||
to: 'Card',
|
||||
as: 'card',
|
||||
relatedName: 'notifications',
|
||||
}),
|
||||
commentId: oneToOne({
|
||||
to: 'Comment',
|
||||
as: 'comment',
|
||||
}),
|
||||
activityId: oneToOne({
|
||||
to: 'Activity',
|
||||
as: 'activity',
|
||||
@@ -30,11 +49,13 @@ export default class extends BaseModel {
|
||||
static reducer({ type, payload }, Notification) {
|
||||
switch (type) {
|
||||
case ActionTypes.LOCATION_CHANGE_HANDLE:
|
||||
case ActionTypes.USER_UPDATE_HANDLE:
|
||||
case ActionTypes.PROJECT_UPDATE_HANDLE:
|
||||
case ActionTypes.PROJECT_MANAGER_CREATE_HANDLE:
|
||||
case ActionTypes.BOARD_MEMBERSHIP_CREATE_HANDLE:
|
||||
if (payload.deletedNotifications) {
|
||||
payload.deletedNotifications.forEach((notification) => {
|
||||
Notification.withId(notification.id).deleteWithRelated();
|
||||
if (payload.notificationsToDelete) {
|
||||
payload.notificationsToDelete.forEach((notification) => {
|
||||
Notification.withId(notification.id).delete();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -52,13 +73,27 @@ export default class extends BaseModel {
|
||||
Notification.upsert(notification);
|
||||
});
|
||||
|
||||
break;
|
||||
case ActionTypes.ALL_NOTIFICATIONS_DELETE:
|
||||
Notification.all().delete();
|
||||
|
||||
break;
|
||||
case ActionTypes.ALL_NOTIFICATIONS_DELETE__SUCCESS:
|
||||
payload.notifications.forEach((notification) => {
|
||||
const notificationModel = Notification.withId(notification.id);
|
||||
|
||||
if (notificationModel) {
|
||||
notificationModel.delete();
|
||||
}
|
||||
});
|
||||
|
||||
break;
|
||||
case ActionTypes.NOTIFICATION_CREATE_HANDLE:
|
||||
Notification.upsert(payload.notification);
|
||||
|
||||
break;
|
||||
case ActionTypes.NOTIFICATION_DELETE:
|
||||
Notification.withId(payload.id).deleteWithRelated();
|
||||
Notification.withId(payload.id).delete();
|
||||
|
||||
break;
|
||||
case ActionTypes.NOTIFICATION_DELETE__SUCCESS:
|
||||
@@ -66,7 +101,7 @@ export default class extends BaseModel {
|
||||
const notificationModel = Notification.withId(payload.notification.id);
|
||||
|
||||
if (notificationModel) {
|
||||
notificationModel.deleteWithRelated();
|
||||
notificationModel.delete();
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -74,15 +109,4 @@ export default class extends BaseModel {
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
deleteRelated() {
|
||||
if (this.action && !this.action.isInCard) {
|
||||
this.action.delete();
|
||||
}
|
||||
}
|
||||
|
||||
deleteWithRelated() {
|
||||
this.deleteRelated();
|
||||
this.delete();
|
||||
}
|
||||
}
|
||||
|
||||
117
client/src/models/NotificationService.js
Normal file
117
client/src/models/NotificationService.js
Normal file
@@ -0,0 +1,117 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import { attr, fk } from 'redux-orm';
|
||||
|
||||
import BaseModel from './BaseModel';
|
||||
import ActionTypes from '../constants/ActionTypes';
|
||||
|
||||
export default class extends BaseModel {
|
||||
static modelName = 'NotificationService';
|
||||
|
||||
static fields = {
|
||||
id: attr(),
|
||||
url: attr(),
|
||||
format: attr(),
|
||||
isTesting: attr({
|
||||
getDefault: () => false,
|
||||
}),
|
||||
userId: fk({
|
||||
to: 'User',
|
||||
as: 'user',
|
||||
relatedName: 'notificationServices',
|
||||
}),
|
||||
boardId: fk({
|
||||
to: 'Board',
|
||||
as: 'board',
|
||||
relatedName: 'notificationServices',
|
||||
}),
|
||||
};
|
||||
|
||||
static reducer({ type, payload }, NotificationService) {
|
||||
switch (type) {
|
||||
case ActionTypes.SOCKET_RECONNECT_HANDLE:
|
||||
NotificationService.all().delete();
|
||||
|
||||
payload.notificationServices.forEach((notificationService) => {
|
||||
NotificationService.upsert(notificationService);
|
||||
});
|
||||
|
||||
break;
|
||||
case ActionTypes.CORE_INITIALIZE:
|
||||
case ActionTypes.PROJECT_CREATE_HANDLE:
|
||||
payload.notificationServices.forEach((notificationService) => {
|
||||
NotificationService.upsert(notificationService);
|
||||
});
|
||||
|
||||
break;
|
||||
case ActionTypes.USER_UPDATE_HANDLE:
|
||||
case ActionTypes.PROJECT_UPDATE_HANDLE:
|
||||
case ActionTypes.PROJECT_MANAGER_CREATE_HANDLE:
|
||||
case ActionTypes.BOARD_MEMBERSHIP_CREATE_HANDLE:
|
||||
if (payload.notificationServices) {
|
||||
payload.notificationServices.forEach((notificationService) => {
|
||||
NotificationService.upsert(notificationService);
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
case ActionTypes.NOTIFICATION_SERVICE_CREATE:
|
||||
case ActionTypes.NOTIFICATION_SERVICE_CREATE_HANDLE:
|
||||
case ActionTypes.NOTIFICATION_SERVICE_UPDATE__SUCCESS:
|
||||
case ActionTypes.NOTIFICATION_SERVICE_UPDATE_HANDLE:
|
||||
NotificationService.upsert(payload.notificationService);
|
||||
|
||||
break;
|
||||
case ActionTypes.NOTIFICATION_SERVICE_CREATE__SUCCESS:
|
||||
NotificationService.withId(payload.localId).delete();
|
||||
NotificationService.upsert(payload.notificationService);
|
||||
|
||||
break;
|
||||
case ActionTypes.NOTIFICATION_SERVICE_CREATE__FAILURE:
|
||||
NotificationService.withId(payload.localId).delete();
|
||||
|
||||
break;
|
||||
case ActionTypes.NOTIFICATION_SERVICE_UPDATE:
|
||||
NotificationService.withId(payload.id).update(payload.data);
|
||||
|
||||
break;
|
||||
case ActionTypes.NOTIFICATION_SERVICE_TEST:
|
||||
NotificationService.withId(payload.id).update({
|
||||
isTesting: true,
|
||||
});
|
||||
|
||||
break;
|
||||
case ActionTypes.NOTIFICATION_SERVICE_TEST__SUCCESS:
|
||||
NotificationService.upsert({
|
||||
...payload.notificationService,
|
||||
isTesting: false,
|
||||
});
|
||||
|
||||
break;
|
||||
case ActionTypes.NOTIFICATION_SERVICE_TEST__FAILURE:
|
||||
NotificationService.withId(payload.id).update({
|
||||
isTesting: false,
|
||||
});
|
||||
|
||||
break;
|
||||
case ActionTypes.NOTIFICATION_SERVICE_DELETE:
|
||||
NotificationService.withId(payload.id).delete();
|
||||
|
||||
break;
|
||||
case ActionTypes.NOTIFICATION_SERVICE_DELETE__SUCCESS:
|
||||
case ActionTypes.NOTIFICATION_SERVICE_DELETE_HANDLE: {
|
||||
const notificationServiceModel = NotificationService.withId(payload.notificationService.id);
|
||||
|
||||
if (notificationServiceModel) {
|
||||
notificationServiceModel.delete();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,13 @@
|
||||
import { attr, many } from 'redux-orm';
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import { attr, many, oneToOne } from 'redux-orm';
|
||||
|
||||
import BaseModel from './BaseModel';
|
||||
import ActionTypes from '../constants/ActionTypes';
|
||||
import { ProjectBackgroundTypes } from '../constants/Enums';
|
||||
import { UserRoles } from '../constants/Enums';
|
||||
|
||||
export default class extends BaseModel {
|
||||
static modelName = 'Project';
|
||||
@@ -10,15 +15,27 @@ export default class extends BaseModel {
|
||||
static fields = {
|
||||
id: attr(),
|
||||
name: attr(),
|
||||
background: attr(),
|
||||
backgroundImage: attr(),
|
||||
isBackgroundImageUpdating: attr({
|
||||
description: attr(),
|
||||
backgroundType: attr(),
|
||||
backgroundGradient: attr(),
|
||||
isHidden: attr(),
|
||||
isFavorite: attr({
|
||||
getDefault: () => false,
|
||||
}),
|
||||
ownerProjectManagerId: oneToOne({
|
||||
to: 'ProjectManager',
|
||||
as: 'ownerProjectManager',
|
||||
relatedName: 'ownedProject',
|
||||
}),
|
||||
backgroundImageId: oneToOne({
|
||||
to: 'BackgroundImage',
|
||||
as: 'backgroundImage',
|
||||
relatedName: 'backgroundedProject', // TODO: rename?
|
||||
}),
|
||||
managerUsers: many({
|
||||
to: 'User',
|
||||
through: 'ProjectManager',
|
||||
relatedName: 'projects',
|
||||
relatedName: 'managerProjects',
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -47,46 +64,51 @@ export default class extends BaseModel {
|
||||
});
|
||||
|
||||
break;
|
||||
case ActionTypes.PROJECT_CREATE__SUCCESS:
|
||||
case ActionTypes.PROJECT_CREATE_HANDLE:
|
||||
case ActionTypes.PROJECT_UPDATE__SUCCESS:
|
||||
case ActionTypes.PROJECT_UPDATE_HANDLE:
|
||||
Project.upsert(payload.project);
|
||||
case ActionTypes.USER_UPDATE_HANDLE:
|
||||
Project.all()
|
||||
.toModelArray()
|
||||
.forEach((projectModel) => {
|
||||
if (!payload.projectIds.includes(projectModel.id)) {
|
||||
projectModel.deleteWithRelated();
|
||||
}
|
||||
});
|
||||
|
||||
break;
|
||||
case ActionTypes.PROJECT_UPDATE: {
|
||||
const project = Project.withId(payload.id);
|
||||
project.update(payload.data);
|
||||
|
||||
if (
|
||||
payload.data.backgroundImage === null &&
|
||||
project.background &&
|
||||
project.background.type === ProjectBackgroundTypes.IMAGE
|
||||
) {
|
||||
project.background = null;
|
||||
if (payload.projects) {
|
||||
payload.projects.forEach((project) => {
|
||||
Project.upsert(project);
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
case ActionTypes.PROJECT_CREATE__SUCCESS:
|
||||
case ActionTypes.PROJECT_CREATE_HANDLE:
|
||||
case ActionTypes.PROJECT_UPDATE__SUCCESS:
|
||||
Project.upsert(payload.project);
|
||||
|
||||
break;
|
||||
case ActionTypes.PROJECT_UPDATE:
|
||||
Project.withId(payload.id).update(payload.data);
|
||||
|
||||
break;
|
||||
case ActionTypes.PROJECT_UPDATE_HANDLE: {
|
||||
const projectModel = Project.withId(payload.project.id);
|
||||
|
||||
if (projectModel) {
|
||||
if (payload.isAvailable) {
|
||||
projectModel.boards.toModelArray().forEach((boardModel) => {
|
||||
if (!payload.boardIds.includes(boardModel.id)) {
|
||||
boardModel.deleteWithRelated();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
projectModel.deleteWithRelated();
|
||||
}
|
||||
}
|
||||
|
||||
Project.upsert(payload.project);
|
||||
|
||||
break;
|
||||
}
|
||||
case ActionTypes.PROJECT_BACKGROUND_IMAGE_UPDATE:
|
||||
Project.withId(payload.id).update({
|
||||
isBackgroundImageUpdating: true,
|
||||
});
|
||||
|
||||
break;
|
||||
case ActionTypes.PROJECT_BACKGROUND_IMAGE_UPDATE__SUCCESS:
|
||||
Project.withId(payload.project.id).update({
|
||||
...payload.project,
|
||||
isBackgroundImageUpdating: false,
|
||||
});
|
||||
|
||||
break;
|
||||
case ActionTypes.PROJECT_BACKGROUND_IMAGE_UPDATE__FAILURE:
|
||||
Project.withId(payload.id).update({
|
||||
isBackgroundImageUpdating: false,
|
||||
});
|
||||
|
||||
break;
|
||||
case ActionTypes.PROJECT_DELETE:
|
||||
Project.withId(payload.id).deleteWithRelated();
|
||||
|
||||
@@ -101,64 +123,86 @@ export default class extends BaseModel {
|
||||
|
||||
break;
|
||||
}
|
||||
case ActionTypes.PROJECT_MANAGER_CREATE_HANDLE:
|
||||
case ActionTypes.BOARD_MEMBERSHIP_CREATE_HANDLE:
|
||||
if (payload.project) {
|
||||
const projectModel = Project.withId(payload.project.id);
|
||||
case ActionTypes.PROJECT_MANAGER_CREATE_HANDLE: {
|
||||
const projectModel = Project.withId(payload.projectManager.projectId);
|
||||
|
||||
if (projectModel) {
|
||||
if (projectModel) {
|
||||
if (payload.isProjectAvailable) {
|
||||
projectModel.boards.toModelArray().forEach((boardModel) => {
|
||||
if (payload.boardIds.includes(boardModel.id)) {
|
||||
if (payload.isCurrentUser) {
|
||||
boardModel.notificationServices.delete();
|
||||
}
|
||||
} else {
|
||||
boardModel.deleteWithRelated();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
projectModel.deleteWithRelated();
|
||||
}
|
||||
}
|
||||
|
||||
if (payload.project) {
|
||||
Project.upsert(payload.project);
|
||||
}
|
||||
|
||||
break;
|
||||
case ActionTypes.PROJECT_MANAGER_CREATE_HANDLE__PROJECT_FETCH:
|
||||
case ActionTypes.BOARD_MEMBERSHIP_CREATE_HANDLE__PROJECT_FETCH: {
|
||||
const projectModel = Project.withId(payload.id);
|
||||
}
|
||||
case ActionTypes.BOARD_MEMBERSHIP_CREATE_HANDLE:
|
||||
if (!payload.isProjectAvailable) {
|
||||
const projectModel = Project.withId(payload.boardMembership.projectId);
|
||||
|
||||
if (projectModel) {
|
||||
projectModel.boards.toModelArray().forEach((boardModel) => {
|
||||
if (boardModel.id !== payload.currentBoardId) {
|
||||
boardModel.update({
|
||||
isFetching: null,
|
||||
});
|
||||
if (projectModel) {
|
||||
projectModel.deleteWithRelated();
|
||||
}
|
||||
}
|
||||
|
||||
boardModel.deleteRelated(payload.currentUserId);
|
||||
}
|
||||
});
|
||||
if (payload.project) {
|
||||
Project.upsert(payload.project);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
getOrderedManagersQuerySet() {
|
||||
return this.managers.orderBy('createdAt');
|
||||
static getSharedQuerySet() {
|
||||
return this.filter({
|
||||
ownerProjectManagerId: null,
|
||||
}).orderBy(['id.length', 'id']);
|
||||
}
|
||||
|
||||
getOrderedBoardsQuerySet() {
|
||||
return this.boards.orderBy('position');
|
||||
getManagersQuerySet() {
|
||||
return this.managers.orderBy(['id.length', 'id']);
|
||||
}
|
||||
|
||||
getOrderedBoardsModelArrayForUser(userId) {
|
||||
return this.getOrderedBoardsQuerySet()
|
||||
getBackgroundImagesQuerySet() {
|
||||
return this.backgroundImages.orderBy(['id.length', 'id']);
|
||||
}
|
||||
|
||||
getBaseCustomFieldGroupsQuerySet() {
|
||||
return this.baseCustomFieldGroups.orderBy(['id.length', 'id']);
|
||||
}
|
||||
|
||||
getBoardsQuerySet() {
|
||||
return this.boards.orderBy(['position', 'id.length', 'id']);
|
||||
}
|
||||
|
||||
getBoardsModelArrayForUserWithId(userId) {
|
||||
return this.getBoardsQuerySet()
|
||||
.toModelArray()
|
||||
.filter((boardModel) => boardModel.hasMembershipForUser(userId));
|
||||
.filter((boardModel) => boardModel.hasMembershipWithUserId(userId));
|
||||
}
|
||||
|
||||
getOrderedBoardsModelArrayAvailableForUser(userId) {
|
||||
if (this.hasManagerForUser(userId)) {
|
||||
return this.getOrderedBoardsQuerySet().toModelArray();
|
||||
getBoardsModelArrayAvailableForUser(userModel) {
|
||||
if (this.isExternalAccessibleForUser(userModel)) {
|
||||
return this.getBoardsQuerySet().toModelArray();
|
||||
}
|
||||
|
||||
return this.getOrderedBoardsModelArrayForUser(userId);
|
||||
return this.getBoardsModelArrayForUserWithId(userModel.id);
|
||||
}
|
||||
|
||||
hasManagerForUser(userId) {
|
||||
hasManagerWithUserId(userId) {
|
||||
return this.managers
|
||||
.filter({
|
||||
userId,
|
||||
@@ -166,17 +210,38 @@ export default class extends BaseModel {
|
||||
.exists();
|
||||
}
|
||||
|
||||
hasMembershipInAnyBoardForUser(userId) {
|
||||
return this.boards.toModelArray().some((boardModel) => boardModel.hasMembershipForUser(userId));
|
||||
hasMembershipWithUserIdInAnyBoard(userId) {
|
||||
return this.boards
|
||||
.toModelArray()
|
||||
.some((boardModel) => boardModel.hasMembershipWithUserId(userId));
|
||||
}
|
||||
|
||||
isAvailableForUser(userId) {
|
||||
return this.hasManagerForUser(userId) || this.hasMembershipInAnyBoardForUser(userId);
|
||||
isExternalAccessibleForUser(userModel) {
|
||||
if (!this.ownerProjectManagerId && userModel.role === UserRoles.ADMIN) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return this.hasManagerWithUserId(userModel.id);
|
||||
}
|
||||
|
||||
isAvailableForUser(userModel) {
|
||||
return (
|
||||
this.isExternalAccessibleForUser(userModel) ||
|
||||
this.hasMembershipWithUserIdInAnyBoard(userModel.id)
|
||||
);
|
||||
}
|
||||
|
||||
deleteRelated() {
|
||||
this.managers.delete();
|
||||
|
||||
this.backgroundImages.toModelArray().forEach((backgroundImageModel) => {
|
||||
backgroundImageModel.deleteWithRelated();
|
||||
});
|
||||
|
||||
this.baseCustomFieldGroups.toModelArray().forEach((baseCustomFieldGroupModel) => {
|
||||
baseCustomFieldGroupModel.deleteWithRelated();
|
||||
});
|
||||
|
||||
this.boards.toModelArray().forEach((boardModel) => {
|
||||
boardModel.deleteWithRelated();
|
||||
});
|
||||
|
||||
@@ -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 { attr, fk } from 'redux-orm';
|
||||
|
||||
import BaseModel from './BaseModel';
|
||||
@@ -8,9 +13,6 @@ export default class extends BaseModel {
|
||||
|
||||
static fields = {
|
||||
id: attr(),
|
||||
createdAt: attr({
|
||||
getDefault: () => new Date(),
|
||||
}),
|
||||
projectId: fk({
|
||||
to: 'Project',
|
||||
as: 'project',
|
||||
@@ -40,6 +42,16 @@ export default class extends BaseModel {
|
||||
ProjectManager.upsert(projectManager);
|
||||
});
|
||||
|
||||
break;
|
||||
case ActionTypes.USER_UPDATE_HANDLE:
|
||||
case ActionTypes.PROJECT_UPDATE_HANDLE:
|
||||
case ActionTypes.BOARD_MEMBERSHIP_CREATE_HANDLE:
|
||||
if (payload.projectManagers) {
|
||||
payload.projectManagers.forEach((projectManager) => {
|
||||
ProjectManager.upsert(projectManager);
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
case ActionTypes.PROJECT_MANAGER_CREATE:
|
||||
ProjectManager.upsert(payload.projectManager);
|
||||
@@ -49,6 +61,10 @@ export default class extends BaseModel {
|
||||
ProjectManager.withId(payload.localId).delete();
|
||||
ProjectManager.upsert(payload.projectManager);
|
||||
|
||||
break;
|
||||
case ActionTypes.PROJECT_MANAGER_CREATE__FAILURE:
|
||||
ProjectManager.withId(payload.localId).delete();
|
||||
|
||||
break;
|
||||
case ActionTypes.PROJECT_MANAGER_CREATE_HANDLE:
|
||||
ProjectManager.upsert(payload.projectManager);
|
||||
@@ -74,14 +90,6 @@ export default class extends BaseModel {
|
||||
|
||||
break;
|
||||
}
|
||||
case ActionTypes.BOARD_MEMBERSHIP_CREATE_HANDLE:
|
||||
if (payload.projectManagers) {
|
||||
payload.projectManagers.forEach((projectManager) => {
|
||||
ProjectManager.upsert(projectManager);
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import { attr, fk } from 'redux-orm';
|
||||
|
||||
import { createLocalId } from '../utils/local-id';
|
||||
import BaseModel from './BaseModel';
|
||||
import ActionTypes from '../constants/ActionTypes';
|
||||
|
||||
@@ -14,17 +18,24 @@ export default class extends BaseModel {
|
||||
isCompleted: attr({
|
||||
getDefault: () => false,
|
||||
}),
|
||||
cardId: fk({
|
||||
to: 'Card',
|
||||
as: 'card',
|
||||
taskListId: fk({
|
||||
to: 'TaskList',
|
||||
as: 'taskList',
|
||||
relatedName: 'tasks',
|
||||
}),
|
||||
assigneeUserId: fk({
|
||||
to: 'User',
|
||||
as: 'user',
|
||||
relatedName: 'assignedTasks',
|
||||
}),
|
||||
};
|
||||
|
||||
static reducer({ type, payload }, Task) {
|
||||
switch (type) {
|
||||
case ActionTypes.LOCATION_CHANGE_HANDLE:
|
||||
case ActionTypes.CORE_INITIALIZE:
|
||||
case ActionTypes.USER_UPDATE_HANDLE:
|
||||
case ActionTypes.PROJECT_UPDATE_HANDLE:
|
||||
case ActionTypes.PROJECT_MANAGER_CREATE_HANDLE:
|
||||
case ActionTypes.BOARD_MEMBERSHIP_CREATE_HANDLE:
|
||||
case ActionTypes.CARD_UPDATE_HANDLE:
|
||||
@@ -46,24 +57,13 @@ export default class extends BaseModel {
|
||||
|
||||
break;
|
||||
case ActionTypes.BOARD_FETCH__SUCCESS:
|
||||
case ActionTypes.CARDS_FETCH__SUCCESS:
|
||||
case ActionTypes.CARD_CREATE_HANDLE:
|
||||
case ActionTypes.CARD_DUPLICATE__SUCCESS:
|
||||
payload.tasks.forEach((task) => {
|
||||
Task.upsert(task);
|
||||
});
|
||||
|
||||
break;
|
||||
case ActionTypes.CARD_DUPLICATE:
|
||||
payload.taskIds.forEach((taskId, index) => {
|
||||
const taskModel = Task.withId(taskId);
|
||||
|
||||
Task.upsert({
|
||||
...taskModel.ref,
|
||||
id: `${createLocalId()}-${index}`, // TODO: hack?
|
||||
cardId: payload.card.id,
|
||||
});
|
||||
});
|
||||
|
||||
break;
|
||||
case ActionTypes.TASK_CREATE:
|
||||
case ActionTypes.TASK_CREATE_HANDLE:
|
||||
@@ -76,6 +76,10 @@ export default class extends BaseModel {
|
||||
Task.withId(payload.localId).delete();
|
||||
Task.upsert(payload.task);
|
||||
|
||||
break;
|
||||
case ActionTypes.TASK_CREATE__FAILURE:
|
||||
Task.withId(payload.localId).delete();
|
||||
|
||||
break;
|
||||
case ActionTypes.TASK_UPDATE:
|
||||
Task.withId(payload.id).update(payload.data);
|
||||
@@ -98,4 +102,16 @@ export default class extends BaseModel {
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
duplicate(id, data) {
|
||||
return this.getClass().create({
|
||||
id,
|
||||
taskListId: this.taskListId,
|
||||
assigneeUserId: this.assigneeUserId,
|
||||
position: this.position,
|
||||
name: this.name,
|
||||
isCompleted: this.isCompleted,
|
||||
...data,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
134
client/src/models/TaskList.js
Executable file
134
client/src/models/TaskList.js
Executable file
@@ -0,0 +1,134 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import { attr, fk } from 'redux-orm';
|
||||
|
||||
import BaseModel from './BaseModel';
|
||||
import ActionTypes from '../constants/ActionTypes';
|
||||
|
||||
export default class extends BaseModel {
|
||||
static modelName = 'TaskList';
|
||||
|
||||
static fields = {
|
||||
id: attr(),
|
||||
position: attr(),
|
||||
name: attr(),
|
||||
showOnFrontOfCard: attr(),
|
||||
cardId: fk({
|
||||
to: 'Card',
|
||||
as: 'card',
|
||||
relatedName: 'taskLists',
|
||||
}),
|
||||
};
|
||||
|
||||
static reducer({ type, payload }, TaskList) {
|
||||
switch (type) {
|
||||
case ActionTypes.LOCATION_CHANGE_HANDLE:
|
||||
case ActionTypes.CORE_INITIALIZE:
|
||||
case ActionTypes.USER_UPDATE_HANDLE:
|
||||
case ActionTypes.PROJECT_UPDATE_HANDLE:
|
||||
case ActionTypes.PROJECT_MANAGER_CREATE_HANDLE:
|
||||
case ActionTypes.BOARD_MEMBERSHIP_CREATE_HANDLE:
|
||||
case ActionTypes.CARD_UPDATE_HANDLE:
|
||||
if (payload.taskLists) {
|
||||
payload.taskLists.forEach((taskList) => {
|
||||
TaskList.upsert(taskList);
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
case ActionTypes.SOCKET_RECONNECT_HANDLE:
|
||||
TaskList.all().delete();
|
||||
|
||||
if (payload.taskLists) {
|
||||
payload.taskLists.forEach((taskList) => {
|
||||
TaskList.upsert(taskList);
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
case ActionTypes.BOARD_FETCH__SUCCESS:
|
||||
case ActionTypes.CARDS_FETCH__SUCCESS:
|
||||
case ActionTypes.CARD_CREATE_HANDLE:
|
||||
case ActionTypes.CARD_DUPLICATE__SUCCESS:
|
||||
payload.taskLists.forEach((taskList) => {
|
||||
TaskList.upsert(taskList);
|
||||
});
|
||||
|
||||
break;
|
||||
case ActionTypes.TASK_LIST_CREATE:
|
||||
case ActionTypes.TASK_LIST_CREATE_HANDLE:
|
||||
case ActionTypes.TASK_LIST_UPDATE__SUCCESS:
|
||||
case ActionTypes.TASK_LIST_UPDATE_HANDLE:
|
||||
TaskList.upsert(payload.taskList);
|
||||
|
||||
break;
|
||||
case ActionTypes.TASK_LIST_CREATE__SUCCESS:
|
||||
TaskList.withId(payload.localId).delete();
|
||||
TaskList.upsert(payload.taskList);
|
||||
|
||||
break;
|
||||
case ActionTypes.TASK_LIST_CREATE__FAILURE:
|
||||
TaskList.withId(payload.localId).delete();
|
||||
|
||||
break;
|
||||
case ActionTypes.TASK_LIST_UPDATE:
|
||||
TaskList.withId(payload.id).update(payload.data);
|
||||
|
||||
break;
|
||||
case ActionTypes.TASK_LIST_DELETE:
|
||||
TaskList.withId(payload.id).deleteWithRelated();
|
||||
|
||||
break;
|
||||
case ActionTypes.TASK_LIST_DELETE__SUCCESS:
|
||||
case ActionTypes.TASK_LIST_DELETE_HANDLE: {
|
||||
const taskListModel = TaskList.withId(payload.taskList.id);
|
||||
|
||||
if (taskListModel) {
|
||||
taskListModel.deleteWithRelated();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
getTasksQuerySet() {
|
||||
return this.tasks.orderBy(['position', 'id.length', 'id']);
|
||||
}
|
||||
|
||||
duplicate(id, data, rootId) {
|
||||
if (rootId === undefined) {
|
||||
rootId = id; // eslint-disable-line no-param-reassign
|
||||
}
|
||||
|
||||
const taskListModel = this.getClass().create({
|
||||
id,
|
||||
cardId: this.cardId,
|
||||
position: this.position,
|
||||
name: this.name,
|
||||
showOnFrontOfCard: this.showOnFrontOfCard,
|
||||
...data,
|
||||
});
|
||||
|
||||
this.tasks.toModelArray().forEach((taskModel) => {
|
||||
taskModel.duplicate(`${taskModel.id}-${rootId}`, {
|
||||
taskListId: taskListModel.id,
|
||||
});
|
||||
});
|
||||
|
||||
return taskListModel;
|
||||
}
|
||||
|
||||
deleteRelated() {
|
||||
this.tasks.delete();
|
||||
}
|
||||
|
||||
deleteWithRelated() {
|
||||
this.deleteRelated();
|
||||
this.delete();
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,15 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import { orderBy } from 'lodash';
|
||||
import { attr } from 'redux-orm';
|
||||
|
||||
import BaseModel from './BaseModel';
|
||||
import buildSearchParts from '../utils/build-search-parts';
|
||||
import ActionTypes from '../constants/ActionTypes';
|
||||
import { UserRoles } from '../constants/Enums';
|
||||
|
||||
const DEFAULT_EMAIL_UPDATE_FORM = {
|
||||
data: {
|
||||
@@ -30,28 +38,42 @@ const DEFAULT_USERNAME_UPDATE_FORM = {
|
||||
error: null,
|
||||
};
|
||||
|
||||
const filterProjectModels = (projectModels, search, isHidden) => {
|
||||
let filteredProjectModels = projectModels.filter(
|
||||
(projectModel) => projectModel.isHidden === isHidden,
|
||||
);
|
||||
|
||||
if (filteredProjectModels.length > 0 && search) {
|
||||
const searchParts = buildSearchParts(search);
|
||||
|
||||
filteredProjectModels = filteredProjectModels.filter((projectModel) =>
|
||||
searchParts.every((searchPart) => projectModel.name.toLowerCase().includes(searchPart)),
|
||||
);
|
||||
}
|
||||
|
||||
return filteredProjectModels;
|
||||
};
|
||||
|
||||
export default class extends BaseModel {
|
||||
static modelName = 'User';
|
||||
|
||||
static fields = {
|
||||
id: attr(),
|
||||
email: attr(),
|
||||
role: attr(),
|
||||
username: attr(),
|
||||
name: attr(),
|
||||
avatarUrl: attr(),
|
||||
avatar: attr(),
|
||||
phone: attr(),
|
||||
organization: attr(),
|
||||
language: attr(),
|
||||
subscribeToOwnCards: attr(),
|
||||
isAdmin: attr(),
|
||||
isLocked: attr(),
|
||||
isRoleLocked: attr(),
|
||||
isUsernameLocked: attr(),
|
||||
isDeletionLocked: attr(),
|
||||
deletedAt: attr(),
|
||||
createdAt: attr({
|
||||
getDefault: () => new Date(),
|
||||
}),
|
||||
subscribeToCardWhenCommenting: attr(),
|
||||
turnOffRecentCardHighlighting: attr(),
|
||||
isDefaultAdmin: attr(),
|
||||
isSsoUser: attr(),
|
||||
isDeactivated: attr(),
|
||||
lockedFieldNames: attr(),
|
||||
isAvatarUpdating: attr({
|
||||
getDefault: () => false,
|
||||
}),
|
||||
@@ -69,6 +91,9 @@ export default class extends BaseModel {
|
||||
static reducer({ type, payload }, User) {
|
||||
switch (type) {
|
||||
case ActionTypes.LOCATION_CHANGE_HANDLE:
|
||||
case ActionTypes.PROJECT_UPDATE_HANDLE:
|
||||
case ActionTypes.BOARD_MEMBERSHIP_CREATE_HANDLE:
|
||||
case ActionTypes.CARD_UPDATE_HANDLE:
|
||||
if (payload.users) {
|
||||
payload.users.forEach((user) => {
|
||||
User.upsert(user);
|
||||
@@ -78,7 +103,6 @@ export default class extends BaseModel {
|
||||
break;
|
||||
case ActionTypes.SOCKET_RECONNECT_HANDLE:
|
||||
User.all().delete();
|
||||
|
||||
User.upsert(payload.user);
|
||||
|
||||
payload.users.forEach((user) => {
|
||||
@@ -117,138 +141,117 @@ export default class extends BaseModel {
|
||||
case ActionTypes.USER_EMAIL_UPDATE: {
|
||||
const userModel = User.withId(payload.id);
|
||||
|
||||
userModel.update({
|
||||
emailUpdateForm: {
|
||||
...userModel.emailUpdateForm,
|
||||
data: payload.data,
|
||||
isSubmitting: true,
|
||||
},
|
||||
});
|
||||
userModel.emailUpdateForm = {
|
||||
...userModel.emailUpdateForm,
|
||||
data: payload.data,
|
||||
isSubmitting: true,
|
||||
};
|
||||
|
||||
break;
|
||||
}
|
||||
case ActionTypes.USER_EMAIL_UPDATE__SUCCESS: {
|
||||
case ActionTypes.USER_EMAIL_UPDATE__SUCCESS:
|
||||
User.withId(payload.user.id).update({
|
||||
...payload.user,
|
||||
emailUpdateForm: DEFAULT_EMAIL_UPDATE_FORM,
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
case ActionTypes.USER_EMAIL_UPDATE__FAILURE: {
|
||||
const userModel = User.withId(payload.id);
|
||||
|
||||
userModel.update({
|
||||
emailUpdateForm: {
|
||||
...userModel.emailUpdateForm,
|
||||
isSubmitting: false,
|
||||
error: payload.error,
|
||||
},
|
||||
});
|
||||
userModel.emailUpdateForm = {
|
||||
...userModel.emailUpdateForm,
|
||||
isSubmitting: false,
|
||||
error: payload.error,
|
||||
};
|
||||
|
||||
break;
|
||||
}
|
||||
case ActionTypes.USER_EMAIL_UPDATE_ERROR_CLEAR: {
|
||||
const userModel = User.withId(payload.id);
|
||||
|
||||
userModel.update({
|
||||
emailUpdateForm: {
|
||||
...userModel.emailUpdateForm,
|
||||
error: null,
|
||||
},
|
||||
});
|
||||
userModel.emailUpdateForm = {
|
||||
...userModel.emailUpdateForm,
|
||||
error: null,
|
||||
};
|
||||
|
||||
break;
|
||||
}
|
||||
case ActionTypes.USER_PASSWORD_UPDATE: {
|
||||
const userModel = User.withId(payload.id);
|
||||
|
||||
userModel.update({
|
||||
passwordUpdateForm: {
|
||||
...userModel.passwordUpdateForm,
|
||||
data: payload.data,
|
||||
isSubmitting: true,
|
||||
},
|
||||
});
|
||||
userModel.passwordUpdateForm = {
|
||||
...userModel.passwordUpdateForm,
|
||||
data: payload.data,
|
||||
isSubmitting: true,
|
||||
};
|
||||
|
||||
break;
|
||||
}
|
||||
case ActionTypes.USER_PASSWORD_UPDATE__SUCCESS: {
|
||||
case ActionTypes.USER_PASSWORD_UPDATE__SUCCESS:
|
||||
User.withId(payload.user.id).update({
|
||||
...payload.user,
|
||||
passwordUpdateForm: DEFAULT_PASSWORD_UPDATE_FORM,
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
case ActionTypes.USER_PASSWORD_UPDATE__FAILURE: {
|
||||
const userModel = User.withId(payload.id);
|
||||
|
||||
userModel.update({
|
||||
passwordUpdateForm: {
|
||||
...userModel.passwordUpdateForm,
|
||||
isSubmitting: false,
|
||||
error: payload.error,
|
||||
},
|
||||
});
|
||||
userModel.passwordUpdateForm = {
|
||||
...userModel.passwordUpdateForm,
|
||||
isSubmitting: false,
|
||||
error: payload.error,
|
||||
};
|
||||
|
||||
break;
|
||||
}
|
||||
case ActionTypes.USER_PASSWORD_UPDATE_ERROR_CLEAR: {
|
||||
const userModel = User.withId(payload.id);
|
||||
|
||||
userModel.update({
|
||||
passwordUpdateForm: {
|
||||
...userModel.passwordUpdateForm,
|
||||
error: null,
|
||||
},
|
||||
});
|
||||
userModel.passwordUpdateForm = {
|
||||
...userModel.passwordUpdateForm,
|
||||
error: null,
|
||||
};
|
||||
|
||||
break;
|
||||
}
|
||||
case ActionTypes.USER_USERNAME_UPDATE: {
|
||||
const userModel = User.withId(payload.id);
|
||||
|
||||
userModel.update({
|
||||
usernameUpdateForm: {
|
||||
...userModel.usernameUpdateForm,
|
||||
data: payload.data,
|
||||
isSubmitting: true,
|
||||
},
|
||||
});
|
||||
userModel.usernameUpdateForm = {
|
||||
...userModel.usernameUpdateForm,
|
||||
data: payload.data,
|
||||
isSubmitting: true,
|
||||
};
|
||||
|
||||
break;
|
||||
}
|
||||
case ActionTypes.USER_USERNAME_UPDATE__SUCCESS: {
|
||||
case ActionTypes.USER_USERNAME_UPDATE__SUCCESS:
|
||||
User.withId(payload.user.id).update({
|
||||
...payload.user,
|
||||
usernameUpdateForm: DEFAULT_USERNAME_UPDATE_FORM,
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
case ActionTypes.USER_USERNAME_UPDATE__FAILURE: {
|
||||
const userModel = User.withId(payload.id);
|
||||
|
||||
userModel.update({
|
||||
usernameUpdateForm: {
|
||||
...userModel.usernameUpdateForm,
|
||||
isSubmitting: false,
|
||||
error: payload.error,
|
||||
},
|
||||
});
|
||||
userModel.usernameUpdateForm = {
|
||||
...userModel.usernameUpdateForm,
|
||||
isSubmitting: false,
|
||||
error: payload.error,
|
||||
};
|
||||
|
||||
break;
|
||||
}
|
||||
case ActionTypes.USER_USERNAME_UPDATE_ERROR_CLEAR: {
|
||||
const userModel = User.withId(payload.id);
|
||||
|
||||
userModel.update({
|
||||
usernameUpdateForm: {
|
||||
...userModel.usernameUpdateForm,
|
||||
error: null,
|
||||
},
|
||||
});
|
||||
userModel.usernameUpdateForm = {
|
||||
...userModel.usernameUpdateForm,
|
||||
error: null,
|
||||
};
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -276,14 +279,22 @@ export default class extends BaseModel {
|
||||
|
||||
break;
|
||||
case ActionTypes.USER_DELETE__SUCCESS:
|
||||
case ActionTypes.USER_DELETE_HANDLE:
|
||||
User.withId(payload.user.id).deleteWithRelated(payload.user);
|
||||
case ActionTypes.USER_DELETE_HANDLE: {
|
||||
const userModel = User.withId(payload.user.id);
|
||||
|
||||
if (userModel) {
|
||||
userModel.deleteWithRelated();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case ActionTypes.PROJECT_CREATE_HANDLE:
|
||||
case ActionTypes.PROJECT_MANAGER_CREATE_HANDLE:
|
||||
case ActionTypes.BOARD_FETCH__SUCCESS:
|
||||
case ActionTypes.BOARD_MEMBERSHIP_CREATE_HANDLE:
|
||||
case ActionTypes.CARDS_FETCH__SUCCESS:
|
||||
case ActionTypes.CARD_CREATE_HANDLE:
|
||||
case ActionTypes.COMMENTS_FETCH__SUCCESS:
|
||||
case ActionTypes.COMMENT_CREATE_HANDLE:
|
||||
case ActionTypes.ACTIVITIES_FETCH__SUCCESS:
|
||||
case ActionTypes.NOTIFICATION_CREATE_HANDLE:
|
||||
payload.users.forEach((user) => {
|
||||
@@ -295,82 +306,204 @@ export default class extends BaseModel {
|
||||
}
|
||||
}
|
||||
|
||||
static getOrderedUndeletedQuerySet() {
|
||||
static getAllQuerySet() {
|
||||
return this.orderBy([({ name }) => name.toLowerCase(), 'id.length', 'id']);
|
||||
}
|
||||
|
||||
static getActiveQuerySet() {
|
||||
return this.filter({
|
||||
deletedAt: null,
|
||||
}).orderBy((user) => user.name.toLocaleLowerCase());
|
||||
isDeactivated: false,
|
||||
}).orderBy([({ name }) => name.toLowerCase(), 'id.length', 'id']);
|
||||
}
|
||||
|
||||
getOrderedProjectManagersQuerySet() {
|
||||
return this.projectManagers.orderBy('createdAt');
|
||||
getProjectManagersQuerySet() {
|
||||
return this.projectManagers.orderBy(['id.length', 'id']);
|
||||
}
|
||||
|
||||
getOrderedBoardMembershipsQuerySet() {
|
||||
return this.boardMemberships.orderBy('createdAt');
|
||||
getBoardMembershipsQuerySet() {
|
||||
return this.boardMemberships.orderBy(['id.length', 'id']);
|
||||
}
|
||||
|
||||
getOrderedUnreadNotificationsQuerySet() {
|
||||
getUnreadNotificationsQuerySet() {
|
||||
return this.notifications
|
||||
.filter({
|
||||
isRead: false,
|
||||
})
|
||||
.orderBy('createdAt', false);
|
||||
.orderBy(['id.length', 'id'], ['desc', 'desc']);
|
||||
}
|
||||
|
||||
getOrderedAvailableProjectsModelArray() {
|
||||
getNotificationServicesQuerySet() {
|
||||
return this.notificationServices.orderBy(['id.length', 'id']);
|
||||
}
|
||||
|
||||
getManagerProjectsModelArray() {
|
||||
return this.getProjectManagersQuerySet()
|
||||
.toModelArray()
|
||||
.map(({ project: projectModel }) => projectModel);
|
||||
}
|
||||
|
||||
getMembershipProjectsModelArray() {
|
||||
const projectIds = [];
|
||||
|
||||
const projectModels = this.getOrderedProjectManagersQuerySet()
|
||||
return this.getBoardMembershipsQuerySet()
|
||||
.toModelArray()
|
||||
.map(({ project: projectModel }) => {
|
||||
projectIds.push(projectModel.id);
|
||||
|
||||
return projectModel;
|
||||
});
|
||||
|
||||
this.getOrderedBoardMembershipsQuerySet()
|
||||
.toModelArray()
|
||||
.forEach(({ board: { project: projectModel } }) => {
|
||||
.flatMap(({ board: { project: projectModel } }) => {
|
||||
if (projectIds.includes(projectModel.id)) {
|
||||
return;
|
||||
return [];
|
||||
}
|
||||
|
||||
projectIds.push(projectModel.id);
|
||||
projectModels.push(projectModel);
|
||||
return projectModel;
|
||||
});
|
||||
}
|
||||
|
||||
getSeparatedProjectsModelArray() {
|
||||
const projectIds = [];
|
||||
|
||||
const managerProjectModels = this.getManagerProjectsModelArray().map((projectModel) => {
|
||||
projectIds.push(projectModel.id);
|
||||
return projectModel;
|
||||
});
|
||||
|
||||
const membershipProjectModels = this.getMembershipProjectsModelArray().flatMap(
|
||||
(projectModel) => {
|
||||
if (projectIds.includes(projectModel.id)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
projectIds.push(projectModel.id);
|
||||
return projectModel;
|
||||
},
|
||||
);
|
||||
|
||||
let adminProjectModels = [];
|
||||
if (this.role === UserRoles.ADMIN) {
|
||||
const {
|
||||
session: { Project },
|
||||
} = this.getClass();
|
||||
|
||||
adminProjectModels = Project.getSharedQuerySet()
|
||||
.toModelArray()
|
||||
.flatMap((projectModel) => {
|
||||
if (projectIds.includes(projectModel.id)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
projectIds.push(projectModel.id);
|
||||
return projectModel;
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
managerProjectModels,
|
||||
membershipProjectModels,
|
||||
adminProjectModels,
|
||||
};
|
||||
}
|
||||
|
||||
getProjectsModelArray() {
|
||||
const { managerProjectModels, membershipProjectModels, adminProjectModels } =
|
||||
this.getSeparatedProjectsModelArray();
|
||||
|
||||
return [...managerProjectModels, ...membershipProjectModels, ...adminProjectModels];
|
||||
}
|
||||
|
||||
getFavoriteProjectsModelArray(orderByArgs) {
|
||||
let projectModels = this.getProjectsModelArray();
|
||||
|
||||
projectModels = projectModels.filter(
|
||||
(projectModel) => !projectModel.isHidden && projectModel.isFavorite,
|
||||
);
|
||||
|
||||
if (orderByArgs) {
|
||||
projectModels = orderBy(projectModels, ...orderByArgs);
|
||||
}
|
||||
|
||||
return projectModels;
|
||||
}
|
||||
|
||||
getFilteredSeparatedProjectsModelArray(search, isHidden, orderByArgs) {
|
||||
const separatedProjectModels = this.getSeparatedProjectsModelArray();
|
||||
|
||||
return Object.entries(separatedProjectModels).reduce((result, [key, projectModels]) => {
|
||||
let filteredProjectModels = filterProjectModels(projectModels, search, isHidden);
|
||||
|
||||
if (orderByArgs) {
|
||||
filteredProjectModels = orderBy(filteredProjectModels, ...orderByArgs);
|
||||
}
|
||||
|
||||
return {
|
||||
...result,
|
||||
[key]: filteredProjectModels,
|
||||
};
|
||||
}, {});
|
||||
}
|
||||
|
||||
getFilteredProjectsModelArray(search, isHidden, orderByArgs) {
|
||||
let projectModels = this.getProjectsModelArray();
|
||||
projectModels = filterProjectModels(projectModels, search, isHidden);
|
||||
|
||||
if (orderByArgs) {
|
||||
projectModels = orderBy(projectModels, ...orderByArgs);
|
||||
}
|
||||
|
||||
return projectModels;
|
||||
}
|
||||
|
||||
deleteRelated() {
|
||||
this.projectManagers.delete();
|
||||
this.projectManagers.toModelArray().forEach((projectManagerModel) => {
|
||||
if (projectManagerModel.ownedProject) {
|
||||
projectManagerModel.ownedProject.deleteWithRelated();
|
||||
} else {
|
||||
projectManagerModel.delete();
|
||||
}
|
||||
});
|
||||
|
||||
this.boardMemberships.toModelArray().forEach((boardMembershipModel) => {
|
||||
boardMembershipModel.deleteWithRelated();
|
||||
});
|
||||
|
||||
this.createdCards.toModelArray().forEach((cardModel) => {
|
||||
cardModel.update({
|
||||
creatorUserId: null,
|
||||
});
|
||||
});
|
||||
|
||||
this.assignedTasks.toModelArray().forEach((taskModel) => {
|
||||
taskModel.update({
|
||||
assigneeUserId: null,
|
||||
});
|
||||
});
|
||||
|
||||
this.createdAttachments.toModelArray().forEach((attachmentModel) => {
|
||||
attachmentModel.update({
|
||||
creatorUserId: null,
|
||||
});
|
||||
});
|
||||
|
||||
this.comments.toModelArray().forEach((commentModel) => {
|
||||
commentModel.update({
|
||||
userId: null,
|
||||
});
|
||||
});
|
||||
|
||||
this.activities.toModelArray().forEach((activityModel) => {
|
||||
activityModel.update({
|
||||
userId: null,
|
||||
});
|
||||
});
|
||||
|
||||
this.createdNotifications.toModelArray().forEach((notificationModel) => {
|
||||
notificationModel.update({
|
||||
creatorUserId: null,
|
||||
});
|
||||
});
|
||||
|
||||
this.notificationServices.delete();
|
||||
}
|
||||
|
||||
deleteWithRelated(user) {
|
||||
deleteWithRelated() {
|
||||
this.deleteRelated();
|
||||
|
||||
this.update(
|
||||
user || {
|
||||
deletedAt: new Date(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
static findUsersFromText(filterText, users) {
|
||||
const selectUser = filterText.toLocaleLowerCase();
|
||||
const matchingUsers = users.filter(
|
||||
(user) =>
|
||||
user.name.toLocaleLowerCase().startsWith(selectUser) ||
|
||||
user.username.toLocaleLowerCase().startsWith(selectUser),
|
||||
);
|
||||
if (matchingUsers.length === 1) {
|
||||
// Appens the user to the filter
|
||||
return matchingUsers[0].id;
|
||||
}
|
||||
return null;
|
||||
this.delete();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +1,48 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import User from './User';
|
||||
import Project from './Project';
|
||||
import ProjectManager from './ProjectManager';
|
||||
import BackgroundImage from './BackgroundImage';
|
||||
import BaseCustomFieldGroup from './BaseCustomFieldGroup';
|
||||
import Board from './Board';
|
||||
import BoardMembership from './BoardMembership';
|
||||
import Label from './Label';
|
||||
import List from './List';
|
||||
import Card from './Card';
|
||||
import TaskList from './TaskList';
|
||||
import Task from './Task';
|
||||
import Attachment from './Attachment';
|
||||
import CustomFieldGroup from './CustomFieldGroup';
|
||||
import CustomField from './CustomField';
|
||||
import CustomFieldValue from './CustomFieldValue';
|
||||
import Comment from './Comment';
|
||||
import Activity from './Activity';
|
||||
import Notification from './Notification';
|
||||
import NotificationService from './NotificationService';
|
||||
|
||||
export {
|
||||
User,
|
||||
Project,
|
||||
ProjectManager,
|
||||
BackgroundImage,
|
||||
BaseCustomFieldGroup,
|
||||
Board,
|
||||
BoardMembership,
|
||||
Label,
|
||||
List,
|
||||
Card,
|
||||
TaskList,
|
||||
Task,
|
||||
Attachment,
|
||||
CustomFieldGroup,
|
||||
CustomField,
|
||||
CustomFieldValue,
|
||||
Comment,
|
||||
Activity,
|
||||
Notification,
|
||||
NotificationService,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user