Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 44 additions & 6 deletions src/domain/collaboration/whiteboard/utils/mergeWhiteboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { ExcalidrawElement } from '@alkemio/excalidraw/types/element/types';
import { BinaryFileData, ExcalidrawImperativeAPI } from '@alkemio/excalidraw/types/types';
import { v4 as uuidv4 } from 'uuid';

type ExcalidrawElementWithContainerId = ExcalidrawElement & { containerId: string | null };
class WhiteboardMergeError extends Error {}

interface WhiteboardLike {
Expand Down Expand Up @@ -71,6 +72,43 @@ const calculateInsertionPoint = (whiteboardA: BoundingBox, whiteboardB: Bounding
return { x, y };
};

/**
* Generate new element ids and store them in the idsMap.
* This is done to avoid id collisions when inserting multiple times the same template into a whiteboard.
* @param idsMap
* @returns a function that can be passed to elements.map
*/
const generateNewIds = (idsMap: Record<string, string>) => (element: ExcalidrawElement) => ({
...element,
id: (idsMap[element.id] = uuidv4()), // Replace the id and store it in the map
});

/**
* Returns a function that can be pased to elements.map to replace containerId and boundElements ids
*/
const replaceBoundElementsIds = (idsMap: Record<string, string>) => {
const replace = (id: string | null) => (id ? idsMap[id] || id : id);
const replaceMultiple = (boundElements: ExcalidrawElement['boundElements']) =>
boundElements
? boundElements.map(boundElement => ({ ...boundElement, id: idsMap[boundElement.id] || boundElement.id }))
: boundElements;

return (element: ExcalidrawElement) => ({
...element,
containerId: replace((element as ExcalidrawElementWithContainerId).containerId),
boundElements: replaceMultiple(element.boundElements),
});
};

/**
* Returns a function that can be pased to elements.map, to displace elements by a given displacement
*/
const displaceElements = (displacement: { x: number; y: number }) => (element: ExcalidrawElement) => ({
...element,
x: element.x + displacement.x,
y: element.y + displacement.y,
});

const mergeWhiteboard = (whiteboardApi: ExcalidrawImperativeAPI, whiteboardContent: string) => {
let parsedWhiteboard: unknown;
try {
Expand All @@ -96,12 +134,12 @@ const mergeWhiteboard = (whiteboardApi: ExcalidrawImperativeAPI, whiteboardConte
const insertedWhiteboardBBox = getBoundingBox(parsedWhiteboard.elements);
const displacement = calculateInsertionPoint(currentElementsBBox, insertedWhiteboardBBox);

const insertedElements = parsedWhiteboard.elements?.map(el => ({
...el,
id: uuidv4(),
x: el.x + displacement.x,
y: el.y + displacement.y,
}));
const replacedIds: Record<string, string> = {};

const insertedElements = parsedWhiteboard.elements
?.map(generateNewIds(replacedIds))
.map(replaceBoundElementsIds(replacedIds))
.map(displaceElements(displacement));

const newElements = [...currentElements, ...insertedElements];
whiteboardApi.updateScene({
Expand Down