Refactor task handling (introducing abstraction TaskHandler)

This commit is contained in:
Dominik Jain 2025-09-16 17:38:34 +02:00
parent 23d2270df0
commit 8275735999
3 changed files with 124 additions and 69 deletions

View File

@ -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<TParams = any> {
/** 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;
}

View File

@ -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<string | { id: string; task: string; params: any }>((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) => {

View File

@ -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<PrintTextTaskParams> {
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}`);
}
}
}