mirror of
https://github.com/penpot/penpot-mcp.git
synced 2026-04-25 11:18:37 +00:00
Move image import code to PenpotUtils
This commit is contained in:
parent
f8f440c7dd
commit
24ffeac2bf
@ -44,7 +44,7 @@ export class ImportImageArgs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tool for importing an image from the local file system into Penpot
|
* Tool for importing a raster image from the local file system into Penpot
|
||||||
*/
|
*/
|
||||||
export class ImportImageTool extends Tool<ImportImageArgs> {
|
export class ImportImageTool extends Tool<ImportImageArgs> {
|
||||||
/**
|
/**
|
||||||
@ -56,7 +56,6 @@ export class ImportImageTool extends Tool<ImportImageArgs> {
|
|||||||
".png": "image/png",
|
".png": "image/png",
|
||||||
".gif": "image/gif",
|
".gif": "image/gif",
|
||||||
".webp": "image/webp",
|
".webp": "image/webp",
|
||||||
".bmp": "image/bmp",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -78,7 +77,7 @@ export class ImportImageTool extends Tool<ImportImageArgs> {
|
|||||||
"that uses the image as a fill. The rectangle has the image's original proportions by default. " +
|
"that uses the image as a fill. The rectangle has the image's original proportions by default. " +
|
||||||
"Optionally accepts position (x, y) and dimensions (width, height) parameters. " +
|
"Optionally accepts position (x, y) and dimensions (width, height) parameters. " +
|
||||||
"If only one dimension is provided, the other is calculated to maintain the image's aspect ratio. " +
|
"If only one dimension is provided, the other is calculated to maintain the image's aspect ratio. " +
|
||||||
"Supported image formats: JPG, PNG, GIF, WEBP, and BMP."
|
"Supported formats: JPEG, PNG, GIF, WEBP."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,76 +106,18 @@ export class ImportImageTool extends Tool<ImportImageArgs> {
|
|||||||
|
|
||||||
// generate and execute JavaScript code to import the image
|
// generate and execute JavaScript code to import the image
|
||||||
const fileName = path.basename(args.filePath);
|
const fileName = path.basename(args.filePath);
|
||||||
const code = this.generateImportCode(fileName, base64Data, mimeType, args);
|
|
||||||
const task = new ExecuteCodePluginTask({ code: code });
|
|
||||||
await this.mcpServer.pluginBridge.executePluginTask(task);
|
|
||||||
|
|
||||||
return new TextResponse(`Image imported successfully from ${args.filePath}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates the JavaScript code to import the image in the Penpot plugin context.
|
|
||||||
*
|
|
||||||
* @param fileName - The name of the file
|
|
||||||
* @param base64Data - The base64-encoded image data
|
|
||||||
* @param mimeType - The MIME type of the image
|
|
||||||
* @param args - The tool arguments containing position and dimension parameters
|
|
||||||
*/
|
|
||||||
protected generateImportCode(
|
|
||||||
fileName: string,
|
|
||||||
base64Data: string,
|
|
||||||
mimeType: string,
|
|
||||||
args: ImportImageArgs
|
|
||||||
): string {
|
|
||||||
// escape the base64 data for use in a JavaScript string
|
|
||||||
const escapedBase64 = base64Data.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
const escapedBase64 = base64Data.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
||||||
const escapedFileName = fileName.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
const escapedFileName = fileName.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
||||||
|
const code = `
|
||||||
|
const rectangle = await penpotUtils.importImage(
|
||||||
|
'${escapedBase64}', '${mimeType}', '${escapedFileName}',
|
||||||
|
${args.x ?? "undefined"}, ${args.y ?? "undefined"},
|
||||||
|
${args.width ?? "undefined"}, ${args.height ?? "undefined"});
|
||||||
|
return { shapeId: rectangle.id };
|
||||||
|
`;
|
||||||
|
const task = new ExecuteCodePluginTask({ code: code });
|
||||||
|
const executionResult = await this.mcpServer.pluginBridge.executePluginTask(task);
|
||||||
|
|
||||||
return `
|
return new TextResponse(JSON.stringify(executionResult.data?.result, null, 2));
|
||||||
// convert base64 to Uint8Array
|
|
||||||
const base64 = '${escapedBase64}';
|
|
||||||
const bytes = penpotUtils.atob(base64);
|
|
||||||
|
|
||||||
// upload the image data to Penpot
|
|
||||||
const imageData = await penpot.uploadMediaData('${escapedFileName}', bytes, '${mimeType}');
|
|
||||||
|
|
||||||
// create a rectangle shape
|
|
||||||
const rect = penpot.createRectangle();
|
|
||||||
rect.name = '${escapedFileName}';
|
|
||||||
|
|
||||||
// calculate dimensions
|
|
||||||
let rectWidth, rectHeight;
|
|
||||||
const hasWidth = ${args.width !== undefined};
|
|
||||||
const hasHeight = ${args.height !== undefined};
|
|
||||||
|
|
||||||
if (hasWidth && hasHeight) {
|
|
||||||
// both width and height provided - use them directly
|
|
||||||
rectWidth = ${args.width ?? 0};
|
|
||||||
rectHeight = ${args.height ?? 0};
|
|
||||||
} else if (hasWidth) {
|
|
||||||
// only width provided - maintain aspect ratio
|
|
||||||
rectWidth = ${args.width ?? 0};
|
|
||||||
rectHeight = rectWidth * (imageData.height / imageData.width);
|
|
||||||
} else if (hasHeight) {
|
|
||||||
// only height provided - maintain aspect ratio
|
|
||||||
rectHeight = ${args.height ?? 0};
|
|
||||||
rectWidth = rectHeight * (imageData.width / imageData.height);
|
|
||||||
} else {
|
|
||||||
// neither provided - use original dimensions
|
|
||||||
rectWidth = imageData.width;
|
|
||||||
rectHeight = imageData.height;
|
|
||||||
}
|
|
||||||
|
|
||||||
// set rectangle dimensions
|
|
||||||
rect.resize(rectWidth, rectHeight);
|
|
||||||
|
|
||||||
// set position if provided
|
|
||||||
${args.x !== undefined ? `rect.x = ${args.x};` : ""}
|
|
||||||
${args.y !== undefined ? `rect.y = ${args.y};` : ""}
|
|
||||||
|
|
||||||
// apply the image as a fill
|
|
||||||
rect.fills = [{ fillOpacity: 1, fillImage: imageData }];
|
|
||||||
|
|
||||||
return { shapeId: rect.id };`;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { Page, Shape } from "@penpot/plugin-types";
|
import { Page, Rectangle, Shape } from "@penpot/plugin-types";
|
||||||
|
|
||||||
export class PenpotUtils {
|
export class PenpotUtils {
|
||||||
/**
|
/**
|
||||||
@ -177,4 +177,79 @@ export class PenpotUtils {
|
|||||||
|
|
||||||
return bytes;
|
return bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Imports an image from base64 data into the Penpot design as a Rectangle shape filled with the image.
|
||||||
|
* The rectangle has the image's original proportions by default.
|
||||||
|
* Optionally accepts position (x, y) and dimensions (width, height) parameters.
|
||||||
|
* If only one dimension is provided, the other is calculated to maintain the image's aspect ratio.
|
||||||
|
*
|
||||||
|
* This function is used internally by the ImportImageTool in the MCP server.
|
||||||
|
*
|
||||||
|
* @param base64 - The base64-encoded image data
|
||||||
|
* @param mimeType - The MIME type of the image (e.g., "image/png")
|
||||||
|
* @param name - The name to assign to the newly created rectangle shape
|
||||||
|
* @param x - The x-coordinate for positioning the rectangle (optional)
|
||||||
|
* @param y - The y-coordinate for positioning the rectangle (optional)
|
||||||
|
* @param width - The desired width of the rectangle (optional)
|
||||||
|
* @param height - The desired height of the rectangle (optional)
|
||||||
|
*/
|
||||||
|
public static async importImage(
|
||||||
|
base64: string,
|
||||||
|
mimeType: string,
|
||||||
|
name: string,
|
||||||
|
x: number | undefined,
|
||||||
|
y: number | undefined,
|
||||||
|
width: number | undefined,
|
||||||
|
height: number | undefined
|
||||||
|
): Promise<Rectangle> {
|
||||||
|
// convert base64 to Uint8Array
|
||||||
|
const bytes = PenpotUtils.atob(base64);
|
||||||
|
|
||||||
|
// upload the image data to Penpot
|
||||||
|
const imageData = await penpot.uploadMediaData(name, bytes, mimeType);
|
||||||
|
|
||||||
|
// create a rectangle shape
|
||||||
|
const rect = penpot.createRectangle();
|
||||||
|
rect.name = "${escapedFileName}";
|
||||||
|
|
||||||
|
// calculate dimensions
|
||||||
|
let rectWidth, rectHeight;
|
||||||
|
const hasWidth = width !== undefined;
|
||||||
|
const hasHeight = height !== undefined;
|
||||||
|
|
||||||
|
if (hasWidth && hasHeight) {
|
||||||
|
// both width and height provided - use them directly
|
||||||
|
rectWidth = width;
|
||||||
|
rectHeight = height;
|
||||||
|
} else if (hasWidth) {
|
||||||
|
// only width provided - maintain aspect ratio
|
||||||
|
rectWidth = width;
|
||||||
|
rectHeight = rectWidth * (imageData.height / imageData.width);
|
||||||
|
} else if (hasHeight) {
|
||||||
|
// only height provided - maintain aspect ratio
|
||||||
|
rectHeight = height;
|
||||||
|
rectWidth = rectHeight * (imageData.width / imageData.height);
|
||||||
|
} else {
|
||||||
|
// neither provided - use original dimensions
|
||||||
|
rectWidth = imageData.width;
|
||||||
|
rectHeight = imageData.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
// set rectangle dimensions
|
||||||
|
rect.resize(rectWidth, rectHeight);
|
||||||
|
|
||||||
|
// set position if provided
|
||||||
|
if (x !== undefined) {
|
||||||
|
rect.x = x;
|
||||||
|
}
|
||||||
|
if (y !== undefined) {
|
||||||
|
rect.y = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
// apply the image as a fill
|
||||||
|
rect.fills = [{ fillOpacity: 1, fillImage: imageData }];
|
||||||
|
|
||||||
|
return rect;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user