From b90fb1519ac6c75d3752c27943ffb7d1beb1d630 Mon Sep 17 00:00:00 2001 From: MTRNord Date: Sat, 11 Jan 2025 21:14:14 +0100 Subject: [PATCH] Improve the transformation from miro to neoboard Change-Id: Ia3ff34d6a5fc474d94e737c9bd08b08eea916aa2 --- src/app/importActions.ts | 89 ++++++++++++++++++++++------------------ 1 file changed, 50 insertions(+), 39 deletions(-) diff --git a/src/app/importActions.ts b/src/app/importActions.ts index 27d9bec..6033f04 100644 --- a/src/app/importActions.ts +++ b/src/app/importActions.ts @@ -180,9 +180,9 @@ export async function importBoard(prevState: FormState, formData: FormData): Pro } // Transform the board into neoboard coordinates - const transformedNeoboard = transformBoardIntoNeoboardCoordinates(neoboard); + const fittedNeoboard = fitItemsBestIntoFrame(neoboard); - resultingBoards.push({ id: boardId, neoboard: transformedNeoboard }); + resultingBoards.push({ id: boardId, neoboard: fittedNeoboard }); } return { @@ -191,19 +191,40 @@ export async function importBoard(prevState: FormState, formData: FormData): Pro } } -function transformBoardIntoNeoboardCoordinates(board: Whiteboard): Whiteboard { - // The coordinate system in Miro is different from the coordinate system in Neoboard. - // In Neoboard the frames are only neoboardWhiteboardWidth wide and whiteboardHeight tall. - +/** + * The transform doesn't perfectly use the available space in the frame. We need to fit the items into the frame's maximum size. + * + * The algorithm should: + * 1. Find the bounding box of all the items in the slide + * 2. Scale all the items to fit within the bounding box which is the maximum size of the frame minus some padding + * 3. Center the items in the frame + * 4. Return the slides with the transformed items + * + * @param board The board with items which should be fitted into the frame's maximum size + */ +function fitItemsBestIntoFrame(board: Whiteboard): Whiteboard { const neoboardPadding = 10; - // The first step is to find the bounding box of all the slides. - let minX = Number.MAX_SAFE_INTEGER; - let minY = Number.MAX_SAFE_INTEGER; - let maxX = Number.MIN_SAFE_INTEGER; - let maxY = Number.MIN_SAFE_INTEGER; + const transformedBoard: Whiteboard = { + version: board.version, + whiteboard: { + slides: [], + } + }; + + // Note that all the coordinates are relative to the top left corner of the frame and still in the miro coordinate system which is likely larger than the neoboard frame. for (const slide of board.whiteboard.slides) { + const transformedSlide: Slide = { + elements: [], + }; + + // The first step is to find the bounding box of all the slides items. + let minX = Number.MAX_SAFE_INTEGER; + let minY = Number.MAX_SAFE_INTEGER; + let maxX = Number.MIN_SAFE_INTEGER; + let maxY = Number.MIN_SAFE_INTEGER; + for (const element of slide.elements) { if (element.type === 'shape' || element.type === 'image') { const x = element.position.x; @@ -216,39 +237,29 @@ function transformBoardIntoNeoboardCoordinates(board: Whiteboard): Whiteboard { maxX = Math.max(maxX, x + width); maxY = Math.max(maxY, y + height); } else if (element.type === 'path') { + // The points are relative to the position of the path const x = element.position.x; const y = element.position.y; const points = element.points; - minX = Math.min(minX, x, ...points.map(p => p.x)); - minY = Math.min(minY, y, ...points.map(p => p.y)); - maxX = Math.max(maxX, x, ...points.map(p => p.x)); - maxY = Math.max(maxY, y, ...points.map(p => p.y)); + minX = Math.min(minX, x); + minY = Math.min(minY, y); + maxX = Math.max(maxX, x + Math.max(...points.map(p => p.x))); + maxY = Math.max(maxY, y + Math.max(...points.map(p => p.y))); } } - } - - // The second step is to scale all the elements to fit within the bounding box. - const width = maxX - minX; - const height = maxY - minY; - const scale = Math.min((neoboardWhiteboardWidth - neoboardPadding) / width, (neoboardWhiteboardHeight - neoboardPadding) / height); - - const transformedBoard: Whiteboard = { - version: board.version, - whiteboard: { - slides: [], - } - }; - for (const slide of board.whiteboard.slides) { - const transformedSlide: Slide = { - elements: [], - }; + // The second step is to scale all the elements to fill the frame which is (neoboardWhiteboardWidth - neoboardPadding) wide and (neoboardWhiteboardHeight - neoboardPadding) tall. + // We treat the size we want to fit the grouped size of all the items in the slide. + // We also want this group to be centered in the frame. + const width = maxX - minX; + const height = maxY - minY; + const scale = Math.min((neoboardWhiteboardWidth - neoboardPadding) / width, (neoboardWhiteboardHeight - neoboardPadding) / height); for (const element of slide.elements) { if (element.type === 'shape' || element.type === 'image') { - const x = (element.position.x - minX) * scale; - const y = (element.position.y - minY) * scale; + const x = (element.position.x - minX) * scale + neoboardPadding / 2; + const y = (element.position.y - minY) * scale + neoboardPadding / 2; const width = element.width * scale; const height = element.height * scale; @@ -262,11 +273,12 @@ function transformBoardIntoNeoboardCoordinates(board: Whiteboard): Whiteboard { height, }); } else if (element.type === 'path') { - const x = (element.position.x - minX) * scale; - const y = (element.position.y - minY) * scale; + // The points are relative to the position of the path + const x = (element.position.x - minX) * scale + neoboardPadding / 2; + const y = (element.position.y - minY) * scale + neoboardPadding / 2; const points = element.points.map(p => ({ - x: (p.x - minX) * scale, - y: (p.y - minY) * scale, + x: p.x * scale, + y: p.y * scale, })); transformedSlide.elements.push({ @@ -277,7 +289,6 @@ function transformBoardIntoNeoboardCoordinates(board: Whiteboard): Whiteboard { }, points, }); - } else { transformedSlide.elements.push(element); } -- 2.45.2