export type FormState = {
neoboards: {
id: string;
+ name?: string;
neoboard: Whiteboard;
}[];
message: string;
+ url?: string;
}
const mapColorNameToHex = (color: string | undefined): string => {
}
}
+/**
+ * Gets the ID from the URL
+ *
+ * A miro looks like this: https://miro.com/app/board/<id>/
+ *
+ * @param url The URL to parse
+ * @returns The ID from the URL
+ * @throws If the URL is not valid
+ */
+function parseUrl(url: string): string {
+ const url_parsed = new URL(url);
+ const path = url_parsed.pathname.split('/');
+ // Note that we might have something behind the slash of the board ID or no slash at all at the end
+ if (path.length < 3) {
+ throw new Error('Invalid URL');
+ }
+
+ return path[3];
+}
+
export async function importBoard(prevState: FormState, formData: FormData): Promise<FormState> {
const boardIdsRaw = formData.getAll('board');
if (!boardIdsRaw) {
}
const boardIds: string[] = Array.isArray(boardIdsRaw) ? boardIdsRaw as string[] : [boardIdsRaw] as string[];
+ if (formData.has("url")) {
+ const url = formData.get("url") as string;
+ if (url && url.length > 0) {
+ const boardId = parseUrl(url);
+ boardIds.push(boardId);
+ }
+ }
+
// Get board from Miro API
const { miro, userId } = initMiroAPI();
const resultingBoards: {
id: string;
+ name: string;
neoboard: Whiteboard;
}[] = [];
for (const boardId of boardIds) {
const startItem = frameData.children.find(c => c.id === connector.startItem?.id);
const endItem = frameData.children.find(c => c.id === connector.endItem?.id);
if (startItem && endItem) {
- const startItemX = startItem.position?.x ?? 0;
- const startItemY = startItem.position?.y ?? 0;
- const endItemX = endItem.position?.x ?? 0;
- const endItemY = endItem.position?.y ?? 0;
+ let startItemX = startItem.position?.x ?? 0;
+ let startItemY = startItem.position?.y ?? 0;
+ let endItemX = endItem.position?.x ?? 0;
+ let endItemY = endItem.position?.y ?? 0;
+
+ // Adjust based on the connector item's position which contains a relative offset where x=0% and y=0% equals the top left corner of the item
+ const startRelativeOffset = connector.startItem?.position;
+ const endRelativeOffset = connector.endItem?.position;
+
+ // Note that the startItemX and startItemY are the center of the item and not the top left corner
+ // Same goes for endItemX and endItemY
+
+ if (startRelativeOffset && startRelativeOffset.x && startRelativeOffset.y) {
+ // We need to remove the % sign from the string and parse it as a number
+ const offsetXNumber = parseFloat(startRelativeOffset.x.replace('%', ''));
+ const offsetYNumber = parseFloat(startRelativeOffset.y.replace('%', ''));
+
+ // Note that startItemX and startItemY are the center of the item and not the top left corner so offset x of 50% is no change to the x position and not half of the width
+
+ // 1. Move the x corrdinate to the top left corner by subtracting half of the width
+ // 2. Add the offset in percentage
+
+ startItemX = (startItemX - (startItem.geometry?.width ?? 1) / 2) + (offsetXNumber / 100) * (startItem.geometry?.width ?? 1);
+ startItemY = (startItemY - (startItem.geometry?.height ?? 1) / 2) + (offsetYNumber / 100) * (startItem.geometry?.height ?? 1);
+ }
+
+ if (endRelativeOffset && endRelativeOffset.x && endRelativeOffset.y) {
+ // We need to remove the % sign from the string and parse it as a number
+ const offsetXNumber = parseFloat(endRelativeOffset.x.replace('%', ''));
+ const offsetYNumber = parseFloat(endRelativeOffset.y.replace('%', ''));
+
+ // Note that endItemX and endItemY are the center of the item and not the top left corner so offset x of 50% is no change to the x position and not half of the width
+
+ // 1. Move the x corrdinate to the top left corner by subtracting half of the width
+ // 2. Add the offset in percentage
+
+ endItemX = (endItemX - (endItem.geometry?.width ?? 1) / 2) + (offsetXNumber / 100) * (endItem.geometry?.width ?? 1);
+ endItemY = (endItemY - (endItem.geometry?.height ?? 1) / 2) + (offsetYNumber / 100) * (endItem.geometry?.height ?? 1);
+ }
const points = [
{
// Transform the board into neoboard coordinates
const fittedNeoboard = fitItemsBestIntoFrame(neoboard);
- resultingBoards.push({ id: boardId, neoboard: fittedNeoboard });
+ resultingBoards.push({ id: boardId, neoboard: fittedNeoboard, name: board.name });
}
return {
y: item.position?.y ?? 0,
},
width: item.geometry?.width ?? 1,
- height: item.geometry?.height ?? 1,
+ // We remove the shadow size from the height as miro adds the shadow to the height
+ height: (item.geometry?.height ?? 1) - 43,
fillColor: fillColor ?? "#ffffff",
strokeColor: 'transparent',
strokeWidth: 0,