Skip to content

Commit 96cce8c

Browse files
virgilchiriacodalys-dataportluejoh
authored
BC-10395 - duplicate card (#3919)
--- Co-authored-by: Odalys Adam <[email protected]> Co-authored-by: Johannes Lueder <[email protected]>
1 parent 52197f6 commit 96cce8c

28 files changed

+498
-41
lines changed

src/components/error-handling/ErrorHandler.composable.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { useNotificationStore } from "@data-app";
44
import { logger } from "@util-logger";
55
import { useI18n } from "vue-i18n";
66

7-
export type ErrorType = "notCreated" | "notLoaded" | "notUpdated" | "notDeleted" | "notMoved";
7+
export type ErrorType = "notCreated" | "notLoaded" | "notUpdated" | "notDeleted" | "notDuplicated" | "notMoved";
88

99
export type BoardObjectType = "board" | "boardColumn" | "boardRow" | "boardCard" | "boardElement";
1010

src/locales/de.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,14 +453,19 @@ export default {
453453
"Die angehängte Datei überschreitet die maximal zulässige Größe von {maxFileSizeWithUnit}.",
454454
"components.board.notifications.errors.notCreated": "{type} konnte nicht erstellt werden.",
455455
"components.board.notifications.errors.notDeleted": "{type} konnte nicht gelöscht werden.",
456+
"components.board.notifications.errors.notDuplicated": "{type} konnte nicht dupliziert werden.",
456457
"components.board.notifications.errors.notLoaded": "{type} konnte nicht geladen werden.",
457458
"components.board.notifications.errors.notUpdated": "Die Änderungen konnten nicht gespeichert werden.",
459+
"components.board.notifications.info.cardDuplicated":
460+
"Inhalte aus Etherpads und Whiteboards sowie geschützte Einstellungen externer Tools werden nicht übernommen.",
458461
"components.board.screenReader.notification.cardCreated.success":
459462
"Eine Karte wurde von einem anderen Benutzer in Abschnitt {columnPosition} erstellt.",
460463
"components.board.screenReader.notification.columnCreated.success":
461464
"Ein Abschnitt wurde von einem anderen Benutzer erstellt.",
462465
"components.board.screenReader.notification.cardDeleted.success":
463466
"Eine Karte wurde von einem anderen Benutzer gelöscht.",
467+
"components.board.screenReader.notification.cardDuplicated.success":
468+
"Eine Karte wurde von einem anderen Benutzer dupliziert.",
464469
"components.board.screenReader.notification.columnDeleted.success":
465470
"Ein Abschnitt wurde von einem anderen Benutzer gelöscht.",
466471
"components.board.screenReader.notification.cardMovedInSameColumn.success":

src/locales/en.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,12 +449,16 @@ export default {
449449
"The attached file exceeds the maximum permitted size of {maxFileSizeWithUnit}.",
450450
"components.board.notifications.errors.notCreated": "{type} could not be created.",
451451
"components.board.notifications.errors.notDeleted": "{type} could not be deleted.",
452+
"components.board.notifications.errors.notDuplicated": "{type} could not be duplicated.",
452453
"components.board.notifications.errors.notLoaded": "{type} could not be loaded.",
453454
"components.board.notifications.errors.notUpdated": "Your changes could not be saved.",
455+
"components.board.notifications.info.cardDuplicated":
456+
"Content from Etherpads and whiteboards as well as protected settings from external tools will not be transferred.",
454457
"components.board.screenReader.notification.cardCreated.success":
455458
"A card was created by another user in column {columnPosition}.",
456459
"components.board.screenReader.notification.columnCreated.success": "A column was created by another user.",
457460
"components.board.screenReader.notification.cardDeleted.success": "A card was deleted by another user.",
461+
"components.board.screenReader.notification.cardDuplicated.success": "A card was duplicated by another user.",
458462
"components.board.screenReader.notification.columnDeleted.success": "A column was deleted by another user.",
459463
"components.board.screenReader.notification.cardMovedInSameColumn.success":
460464
"In column {columnPosition}, a card was moved to position {newPosition}.",

src/locales/es.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,12 +457,16 @@ export default {
457457
"Los archivos adjuntos superan el tamaño máximo permitido de {maxFileSizeWithUnit}.",
458458
"components.board.notifications.errors.notCreated": "{type} no se ha podido crear.",
459459
"components.board.notifications.errors.notDeleted": "{type} no se ha podido eliminar.",
460+
"components.board.notifications.errors.notDuplicated": "{type} no se ha podido duplicar.",
460461
"components.board.notifications.errors.notLoaded": "{type} no se ha podido cargar.",
461462
"components.board.notifications.errors.notUpdated": "No se han podido guardar los cambios.",
463+
"components.board.notifications.info.cardDuplicated":
464+
"No se transferirán los contenidos de Etherpads y pizarras blancas, ni tampoco los ajustes protegidos de herramientas externas.",
462465
"components.board.screenReader.notification.cardCreated.success":
463466
"Otro usuario ha creado una tarjeta en la columna {columnPosition}.",
464467
"components.board.screenReader.notification.columnCreated.success": "Una columna fue creada por otro usuario.",
465468
"components.board.screenReader.notification.cardDeleted.success": "Una tarjeta fue eliminada por otro usuario.",
469+
"components.board.screenReader.notification.cardDuplicated.success": "Una tarjeta fue duplicada por otro usuario.",
466470
"components.board.screenReader.notification.columnDeleted.success": "Una columna fue eliminada por otro usuario.",
467471
"components.board.screenReader.notification.cardMovedInSameColumn.success":
468472
"En la columna {columnPosition}, se ha movido una carta a la posición {newPosition}.",

src/locales/uk.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,12 +453,16 @@ export default {
453453
"Вкладені файли перевищують максимально дозволений розмір {maxFileSizeWithUnit}.",
454454
"components.board.notifications.errors.notCreated": "{type}: Не вдалося створити.",
455455
"components.board.notifications.errors.notDeleted": "{type}: Не вдалося видалити.",
456+
"components.board.notifications.errors.notDuplicated": "{type} не вдалося дублікувати.",
456457
"components.board.notifications.errors.notLoaded": "{type}: не вдалося завантажити.",
457458
"components.board.notifications.errors.notUpdated": "Зберегти зміни не вдалося.",
459+
"components.board.notifications.info.cardDuplicated":
460+
"IВміст з Etherpads і Whiteboards, а також захищені налаштування зовнішніх інструментів не переносяться.",
458461
"components.board.screenReader.notification.cardCreated.success":
459462
"Картку було створено іншим користувачем у колонці {columnPosition}.",
460463
"components.board.screenReader.notification.columnCreated.success": "Колонку створив інший користувач.",
461464
"components.board.screenReader.notification.cardDeleted.success": "Картка була видалена іншим користувачем.",
465+
"components.board.screenReader.notification.cardDuplicated.success": "Картка була дуплікована іншим користувачем.",
462466
"components.board.screenReader.notification.columnDeleted.success": "Стовпець був видалений іншим користувачем.",
463467
"components.board.screenReader.notification.cardMovedInSameColumn.success":
464468
"У колонці {columnPosition} картку було переміщено на позицію {newPosition}.",

src/modules/data/board/Board.store.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import { useBoardRestApi } from "./boardActions/boardRestApi.composable";
2828
import { useBoardSocketApi } from "./boardActions/boardSocketApi.composable";
2929
import { useBoardFocusHandler } from "./BoardFocusHandler.composable";
3030
import { useCardStore } from "./Card.store";
31-
import { DeleteCardSuccessPayload } from "./cardActions/cardActionPayload.types";
31+
import { DeleteCardSuccessPayload, DuplicateCardSuccessPayload } from "./cardActions/cardActionPayload.types";
3232
import { ColumnResponse } from "@/serverApi/v3";
3333
import { HttpStatusCode } from "@/store/types/http-status-code.enum";
3434
import { Board } from "@/types/board/Board";
@@ -134,6 +134,18 @@ export const useBoardStore = defineStore("boardStore", () => {
134134
}
135135
};
136136

137+
const duplicateCardSuccess = (payload: DuplicateCardSuccessPayload) => {
138+
if (!board.value) return;
139+
140+
const { cardId, duplicatedCard } = payload;
141+
const { columnIndex, cardIndex } = getCardLocation(cardId) ?? { columnIndex: 0, cardIndex: 0 };
142+
143+
board.value?.columns?.[columnIndex]?.cards?.splice(cardIndex + 1, 0, {
144+
cardId: duplicatedCard.id,
145+
height: duplicatedCard.height,
146+
});
147+
};
148+
137149
const deleteCardSuccess = (payload: DeleteCardSuccessPayload) => {
138150
if (!board.value) return;
139151
const cardId = payload.cardId;
@@ -380,6 +392,7 @@ export const useBoardStore = defineStore("boardStore", () => {
380392
createColumnSuccess,
381393
deleteBoardRequest,
382394
deleteBoardSuccess,
395+
duplicateCardSuccess,
383396
deleteCardSuccess,
384397
deleteColumnRequest,
385398
deleteColumnSuccess,

src/modules/data/board/Board.store.unit.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,39 @@ describe("BoardStore", () => {
419419
});
420420
});
421421

422+
describe("duplicateCardSuccess", () => {
423+
it("should not duplicate card when a board is undefined", () => {
424+
const { boardStore, cards } = setup({ createBoard: false });
425+
426+
boardStore.duplicateCardSuccess({
427+
cardId: cards[0].cardId,
428+
duplicatedCard: cardResponseFactory.build(),
429+
isOwnAction: true,
430+
});
431+
432+
expect(boardStore.board).toBe(undefined);
433+
});
434+
435+
it("should duplicate a card", () => {
436+
const { boardStore, cards } = setup();
437+
const firstCardId = cards[0].cardId;
438+
439+
const duplicatedCard = cardResponseFactory.build();
440+
boardStore.duplicateCardSuccess({
441+
cardId: firstCardId,
442+
duplicatedCard,
443+
isOwnAction: true,
444+
});
445+
446+
const secondCard = boardStore.board?.columns[0].cards[1];
447+
448+
expect(secondCard).toEqual({
449+
cardId: duplicatedCard.id,
450+
height: duplicatedCard.height,
451+
});
452+
});
453+
});
454+
422455
describe("deleteColumnSuccess", () => {
423456
it("should not delete a column when board value is undefined", () => {
424457
const { boardStore, firstColumn } = setup({ createBoard: false });

src/modules/data/board/BoardApi.composable.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,11 @@ export const useBoardApi = () => {
219219
return response.data;
220220
};
221221

222+
const duplicateCardCall = async (cardId: string) => {
223+
const response = await cardsApi.cardControllerCopyCard(cardId);
224+
return response.data;
225+
};
226+
222227
const moveCardCall = async (cardId: string, toColumnId: string, toPosition: number): Promise<void> => {
223228
await cardsApi.cardControllerMoveCard(cardId, {
224229
toColumnId,
@@ -304,6 +309,7 @@ export const useBoardApi = () => {
304309
updateColumnTitleCall,
305310
updateElementCall,
306311
createCardCall,
312+
duplicateCardCall,
307313
getContextInfo,
308314
updateBoardLayoutCall,
309315
getElementWithParentHierarchyCall,

src/modules/data/board/BoardApi.composable.unit.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,18 @@ describe("BoardApi.composable", () => {
125125
});
126126
});
127127

128+
describe("duplicateCardCall", () => {
129+
it("should call cardControllerCopyCard api", async () => {
130+
const { duplicateCardCall } = useBoardApi();
131+
132+
const id = "duplicate-card-id";
133+
134+
await duplicateCardCall(id);
135+
136+
expect(cardApi.cardControllerCopyCard).toHaveBeenCalledWith(id);
137+
});
138+
});
139+
128140
describe("updateColumnTitleCall", () => {
129141
it("should call columnControllerUpdateColumnTitle api", async () => {
130142
const { updateColumnTitleCall } = useBoardApi();

src/modules/data/board/Card.store.ts

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
CreateElementSuccessPayload,
66
DeleteCardSuccessPayload,
77
DeleteElementSuccessPayload,
8+
DuplicateCardSuccessPayload,
89
FetchCardSuccessPayload,
910
MoveElementSuccessPayload,
1011
UpdateCardHeightSuccessPayload,
@@ -14,6 +15,7 @@ import {
1415
import { useCardRestApi } from "./cardActions/cardRestApi.composable";
1516
import { useCardSocketApi } from "./cardActions/cardSocketApi.composable";
1617
import { CardResponse, ContentElementType, PreferredToolResponse, ToolContextType } from "@/serverApi/v3";
18+
import { notifyInfo } from "@data-app";
1719
import { useEnvConfig } from "@data-env";
1820
import { useSharedEditMode, useSharedLastCreatedElement } from "@util-board";
1921
import { defineStore } from "pinia";
@@ -56,7 +58,7 @@ export const useCardStore = defineStore("cardStore", () => {
5658

5759
const updateCardTitleRequest = socketOrRest.updateCardTitleRequest;
5860

59-
const updateCardTitleSuccess = async (payload: UpdateCardTitleSuccessPayload) => {
61+
const updateCardTitleSuccess = (payload: UpdateCardTitleSuccessPayload) => {
6062
const card = cards.value[payload.cardId];
6163
if (card === undefined) return;
6264

@@ -65,16 +67,25 @@ export const useCardStore = defineStore("cardStore", () => {
6567

6668
const updateCardHeightRequest = socketOrRest.updateCardHeightRequest;
6769

68-
const updateCardHeightSuccess = async (payload: UpdateCardHeightSuccessPayload) => {
70+
const updateCardHeightSuccess = (payload: UpdateCardHeightSuccessPayload) => {
6971
const card = cards.value[payload.cardId];
7072
if (card === undefined) return;
7173

7274
card.height = payload.newHeight;
7375
};
7476

77+
const duplicateCard = socketOrRest.duplicateCardRequest;
78+
79+
const duplicateCardSuccess = (payload: DuplicateCardSuccessPayload) => {
80+
if (payload.duplicatedCard.id) {
81+
cards.value[payload.duplicatedCard.id] = payload.duplicatedCard;
82+
notifyInfo("components.board.notifications.info.cardDuplicated");
83+
}
84+
};
85+
7586
const deleteCardRequest = socketOrRest.deleteCardRequest;
7687

77-
const deleteCardSuccess = async (payload: DeleteCardSuccessPayload) => {
88+
const deleteCardSuccess = (payload: DeleteCardSuccessPayload) => {
7889
const card = cards.value[payload.cardId];
7990
if (card === undefined) return;
8091

@@ -86,11 +97,11 @@ export const useCardStore = defineStore("cardStore", () => {
8697

8798
const createElementRequest = socketOrRest.createElementRequest;
8899

89-
const createPreferredElement = async (payload: CreateElementRequestPayload, tool: PreferredToolResponse) => {
100+
const createPreferredElement = (payload: CreateElementRequestPayload, tool: PreferredToolResponse) => {
90101
restApi.createPreferredElement(payload, tool);
91102
};
92103

93-
const createElementSuccess = async (payload: CreateElementSuccessPayload) => {
104+
const createElementSuccess = (payload: CreateElementSuccessPayload) => {
94105
const card = cards.value[payload.cardId];
95106
if (card === undefined) return;
96107

@@ -128,7 +139,7 @@ export const useCardStore = defineStore("cardStore", () => {
128139
if (toPosition < 0) return;
129140
if (toPosition >= card.elements.length) return;
130141

131-
socketOrRest.moveElementRequest({
142+
await socketOrRest.moveElementRequest({
132143
elementId,
133144
toCardId: cardId,
134145
toPosition,
@@ -151,7 +162,7 @@ export const useCardStore = defineStore("cardStore", () => {
151162

152163
const deleteElementRequest = socketOrRest.deleteElementRequest;
153164

154-
const deleteElementSuccess = async (payload: DeleteElementSuccessPayload): Promise<void> => {
165+
const deleteElementSuccess = (payload: DeleteElementSuccessPayload) => {
155166
const card = cards.value[payload.cardId];
156167
if (card === undefined) return;
157168

@@ -172,7 +183,7 @@ export const useCardStore = defineStore("cardStore", () => {
172183

173184
const updateElementRequest = socketOrRest.updateElementRequest;
174185

175-
const updateElementSuccess = async (payload: UpdateElementSuccessPayload) => {
186+
const updateElementSuccess = (payload: UpdateElementSuccessPayload) => {
176187
const cardToUpdate = Object.values(cards.value).find((c) => c.elements.some((e) => e.id === payload.elementId));
177188
if (cardToUpdate === undefined) return;
178189
const cardId = cardToUpdate.id;
@@ -201,7 +212,7 @@ export const useCardStore = defineStore("cardStore", () => {
201212
return previousElement.id;
202213
};
203214

204-
const loadPreferredTools = async (contextType: ToolContextType): Promise<void> => {
215+
const loadPreferredTools = async (contextType: ToolContextType) => {
205216
isPreferredToolsLoading.value = true;
206217

207218
preferredTools.value = (await restApi.getPreferredTools(contextType)) || [];
@@ -226,6 +237,8 @@ export const useCardStore = defineStore("cardStore", () => {
226237
fetchCardRequest,
227238
fetchCardSuccess,
228239
cards,
240+
duplicateCard,
241+
duplicateCardSuccess,
229242
deleteCardRequest,
230243
deleteCardSuccess,
231244
getCard,

0 commit comments

Comments
 (0)