diff --git a/penpot-plugin/src/TaskHandler.ts b/penpot-plugin/src/TaskHandler.ts new file mode 100644 index 0000000..1ef8972 --- /dev/null +++ b/penpot-plugin/src/TaskHandler.ts @@ -0,0 +1,55 @@ + +/** + * Abstract base class for task handlers in the Penpot MCP plugin. + * + * @template TParams - The type of parameters this handler expects + */ +export abstract class TaskHandler { + /** The task identifier this handler is responsible for */ + abstract readonly task: string; + + /** + * Checks if this handler can process the given task. + * + * @param task - The task identifier to check + * @returns True if this handler applies to the given task + */ + applies(task: string): boolean { + return this.task === task; + } + + /** + * Sends a task response back to the MCP server. + */ + public static sendTaskResponse(taskId: string, success: boolean, data: any = undefined, error: any = undefined): void { + const response = { + type: "task-response", + response: { + id: taskId, + success: success, + data: data, + error: error, + }, + }; + + // Send to main.ts which will forward to MCP server via WebSocket + penpot.ui.sendMessage(response); + console.log("Sent task response:", response); + } + + public static sendTaskSuccess(taskId: string, data: any = undefined): void { + this.sendTaskResponse(taskId, true, data); + } + + public static sendTaskError(taskId: string, error: string): void { + this.sendTaskResponse(taskId, false, undefined, error); + } + + /** + * Handles the task with the provided parameters. + * + * @param taskId - The unique ID of the task request + * @param params - The parameters for the task + */ + abstract handle(taskId: string, params: TParams): void; +} \ No newline at end of file diff --git a/penpot-plugin/src/plugin.ts b/penpot-plugin/src/plugin.ts index 916b9b5..0288db8 100644 --- a/penpot-plugin/src/plugin.ts +++ b/penpot-plugin/src/plugin.ts @@ -1,3 +1,13 @@ +import {PrintTextTaskHandler} from "./task-handlers/PrintTextTaskHandler"; +import {TaskHandler} from "./TaskHandler"; + +/** + * Registry of all available task handlers. + */ +const taskHandlers: TaskHandler[] = [ + new PrintTextTaskHandler(), +]; + penpot.ui.open("Penpot MCP Plugin", `?theme=${penpot.theme}`); // Handle both legacy string messages and new request-based messages @@ -31,80 +41,24 @@ penpot.ui.onMessage((message function handlePluginTaskRequest(request: { id: string; task: string; params: any }): void { console.log("Executing plugin task:", request.task, request.params); - switch (request.task) { - case "printText": - handlePrintTextTask(request.id, request.params); - break; + // Find the appropriate handler + const handler = taskHandlers.find(h => h.applies(request.task)); - default: - console.warn("Unknown plugin task:", request.task); - sendTaskError(request.id, `Unknown task type: ${request.task}`); - } -} - -/** - * Handles the printText task by creating text in Penpot. - * - * @param taskId - The unique ID of the task request - * @param params - The parameters containing the text to create - */ -function handlePrintTextTask(taskId: string, params: { text: string }): void { - if (!params.text) { - console.error("printText task requires 'text' parameter"); - sendTaskError(taskId, "printText task requires 'text' parameter"); - return; - } - - try { - const text = penpot.createText(params.text); - - if (text) { - // Center the text in the viewport - text.x = penpot.viewport.center.x; - text.y = penpot.viewport.center.y; - - // Select the newly created text - penpot.selection = [text]; - - console.log("Successfully created text:", params.text); - sendTaskSuccess(taskId, { textId: text.id }); - } else { - console.error("Failed to create text element"); - sendTaskError(taskId, "Failed to create text element"); + if (handler) { + try { + // Cast the params to the expected type and handle the task + handler.handle(request.id, request.params); + } catch (error) { + console.error(`Error handling task '${request.task}':`, error); + const errorMessage = error instanceof Error ? error.message : "Unknown error"; + TaskHandler.sendTaskError(request.id, `Error handling task: ${errorMessage}`); } - } catch (error) { - console.error("Error creating text:", error); - const errorMessage = error instanceof Error ? error.message : "Unknown error"; - sendTaskError(taskId, `Error creating text: ${errorMessage}`); + } else { + console.warn("Unknown plugin task:", request.task); + TaskHandler.sendTaskError(request.id, `Unknown task type: ${request.task}`); } } -/** - * Sends a task response back to the MCP server. - */ -function sendTaskResponse(taskId: string, success: boolean, data: any = undefined, error: any = undefined): void { - const response = { - type: "task-response", - response: { - id: taskId, - success: success, - data: data, - error: error, - }, - }; - - // Send to main.ts which will forward to MCP server via WebSocket - penpot.ui.sendMessage(response); - console.log("Sent task response:", response); -} - -function sendTaskSuccess(taskId: string, data: any = undefined): void { - sendTaskResponse(taskId, true, data); -} - -function sendTaskError(taskId: string, error: string): void { - sendTaskResponse(taskId, false, undefined, error); -} // Update the theme in the iframe penpot.on("themechange", (theme) => { diff --git a/penpot-plugin/src/task-handlers/PrintTextTaskHandler.ts b/penpot-plugin/src/task-handlers/PrintTextTaskHandler.ts new file mode 100644 index 0000000..220e14e --- /dev/null +++ b/penpot-plugin/src/task-handlers/PrintTextTaskHandler.ts @@ -0,0 +1,46 @@ +import {TaskHandler} from "../TaskHandler"; +import {PrintTextTaskParams} from "../../../common/src"; + +/** + * Task handler for printing text to Penpot. + */ +export class PrintTextTaskHandler extends TaskHandler { + readonly task = "printText"; + + /** + * Handles the printText task by creating text in Penpot. + * + * @param taskId - The unique ID of the task request + * @param params - The parameters containing the text to create + */ + handle(taskId: string, params: PrintTextTaskParams): void { + if (!params.text) { + console.error("printText task requires 'text' parameter"); + TaskHandler.sendTaskError(taskId, "printText task requires 'text' parameter"); + return; + } + + try { + const text = penpot.createText(params.text); + + if (text) { + // Center the text in the viewport + text.x = penpot.viewport.center.x; + text.y = penpot.viewport.center.y; + + // Select the newly created text + penpot.selection = [text]; + + console.log("Successfully created text:", params.text); + TaskHandler.sendTaskSuccess(taskId, { textId: text.id }); + } else { + console.error("Failed to create text element"); + TaskHandler.sendTaskError(taskId, "Failed to create text element"); + } + } catch (error) { + console.error("Error creating text:", error); + const errorMessage = error instanceof Error ? error.message : "Unknown error"; + TaskHandler.sendTaskError(taskId, `Error creating text: ${errorMessage}`); + } + } +} \ No newline at end of file