Apply formatter

This commit is contained in:
Dominik Jain 2025-09-11 11:37:25 +02:00
parent f99fedb4f1
commit 4b755e4381
10 changed files with 1366 additions and 1384 deletions

View File

@ -39,17 +39,11 @@ The `manifest.json` file contains the basic information about the plugin. It def
```json ```json
{ {
"name": "Your plugin name", "name": "Your plugin name",
"description": "Your plugin description", "description": "Your plugin description",
"code": "plugin-file.js", "code": "plugin-file.js",
"icon": "your-icon.png", "icon": "your-icon.png",
"permissions": [ "permissions": ["content:read", "content:write", "library:read", "library:write", "user:read"]
"content:read",
"content:write",
"library:read",
"library:write",
"user:read"
]
} }
``` ```

View File

@ -1,33 +1,25 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Penpot plugin example</title> <title>Penpot plugin example</title>
</head> </head>
<body> <body>
<p>Penpot plugin starter template</p> <p>Penpot plugin starter template</p>
<p> <p>
Checkout the Checkout the
<a target="_blank" href="https://help.penpot.app/plugins/" <a target="_blank" href="https://help.penpot.app/plugins/">documentation</a>
>documentation</a to get started.
> </p>
to get started.
</p>
<button type="button" data-appearance="primary" data-handler="create-text"> <button type="button" data-appearance="primary" data-handler="create-text">Create text</button>
Create text
</button>
<button type="button" data-appearance="secondary" data-handler="connect-mcp"> <button type="button" data-appearance="secondary" data-handler="connect-mcp">Connect to MCP server</button>
Connect to MCP server
</button>
<div id="connection-status" style="margin-top: 10px; font-size: 12px; color: #666;"> <div id="connection-status" style="margin-top: 10px; font-size: 12px; color: #666">Not connected</div>
Not connected
</div>
<script type="module" src="/src/main.ts"></script> <script type="module" src="/src/main.ts"></script>
</body> </body>
</html> </html>

File diff suppressed because it is too large Load Diff

View File

@ -1,22 +1,22 @@
{ {
"name": "penpot-plugin-starter-template", "name": "penpot-plugin-starter-template",
"private": true, "private": true,
"version": "0.0.0", "version": "0.0.0",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite build --watch", "dev": "vite build --watch",
"build": "tsc && vite build", "build": "tsc && vite build",
"format": "prettier --write .", "format": "prettier --write .",
"format:check": "prettier --check ." "format:check": "prettier --check ."
}, },
"dependencies": { "dependencies": {
"@penpot/plugin-styles": "1.3.2", "@penpot/plugin-styles": "1.3.2",
"@penpot/plugin-types": "1.3.2" "@penpot/plugin-types": "1.3.2"
}, },
"devDependencies": { "devDependencies": {
"prettier": "^3.0.0", "prettier": "^3.0.0",
"typescript": "^5.8.3", "typescript": "^5.8.3",
"vite": "^7.0.5", "vite": "^7.0.5",
"vite-live-preview": "^0.3.2" "vite-live-preview": "^0.3.2"
} }
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "Penpot MCP Plugin", "name": "Penpot MCP Plugin",
"code": "plugin.js", "code": "plugin.js",
"description": "This plugin enables interaction with the Penpot MCP server", "description": "This plugin enables interaction with the Penpot MCP server",
"permissions": ["content:read", "content:write"] "permissions": ["content:read", "content:write"]
} }

View File

@ -12,71 +12,70 @@ const statusElement = document.getElementById("connection-status");
* Updates the connection status display element. * Updates the connection status display element.
*/ */
function updateConnectionStatus(status: string, isConnectedState: boolean): void { function updateConnectionStatus(status: string, isConnectedState: boolean): void {
if (statusElement) { if (statusElement) {
statusElement.textContent = status; statusElement.textContent = status;
statusElement.style.color = isConnectedState ? "#4CAF50" : "#666"; statusElement.style.color = isConnectedState ? "#4CAF50" : "#666";
} }
} }
/** /**
* Establishes a WebSocket connection to the MCP server. * Establishes a WebSocket connection to the MCP server.
*/ */
function connectToMcpServer(): void { function connectToMcpServer(): void {
if (ws?.readyState === WebSocket.OPEN) { if (ws?.readyState === WebSocket.OPEN) {
updateConnectionStatus("Already connected", true); updateConnectionStatus("Already connected", true);
return; return;
} }
try { try {
ws = new WebSocket("ws://localhost:8080"); ws = new WebSocket("ws://localhost:8080");
updateConnectionStatus("Connecting...", false); updateConnectionStatus("Connecting...", false);
ws.onopen = () => { ws.onopen = () => {
console.log("Connected to MCP server"); console.log("Connected to MCP server");
updateConnectionStatus("Connected to MCP server", true); updateConnectionStatus("Connected to MCP server", true);
}; };
ws.onmessage = (event) => { ws.onmessage = (event) => {
console.log("Received from MCP server:", event.data); console.log("Received from MCP server:", event.data);
try { try {
const message = JSON.parse(event.data); const message = JSON.parse(event.data);
// Forward the task to the plugin for execution // Forward the task to the plugin for execution
parent.postMessage(message, "*"); parent.postMessage(message, "*");
} catch (error) { } catch (error) {
console.error("Failed to parse WebSocket message:", error); console.error("Failed to parse WebSocket message:", error);
} }
}; };
ws.onclose = () => { ws.onclose = () => {
console.log("Disconnected from MCP server"); console.log("Disconnected from MCP server");
updateConnectionStatus("Disconnected", false); updateConnectionStatus("Disconnected", false);
ws = null; ws = null;
}; };
ws.onerror = (error) => { ws.onerror = (error) => {
console.error("WebSocket error:", error); console.error("WebSocket error:", error);
updateConnectionStatus("Connection error", false); updateConnectionStatus("Connection error", false);
}; };
} catch (error) { } catch (error) {
console.error("Failed to connect to MCP server:", error); console.error("Failed to connect to MCP server:", error);
updateConnectionStatus("Connection failed", false); updateConnectionStatus("Connection failed", false);
} }
} }
// Event handlers // Event handlers
document.querySelector("[data-handler='create-text']")?.addEventListener("click", () => { document.querySelector("[data-handler='create-text']")?.addEventListener("click", () => {
// send message to plugin.ts // send message to plugin.ts
parent.postMessage("create-text", "*"); parent.postMessage("create-text", "*");
}); });
document.querySelector("[data-handler='connect-mcp']")?.addEventListener("click", () => { document.querySelector("[data-handler='connect-mcp']")?.addEventListener("click", () => {
connectToMcpServer(); connectToMcpServer();
}); });
// Listen plugin.ts messages // Listen plugin.ts messages
window.addEventListener("message", (event) => { window.addEventListener("message", (event) => {
if (event.data.source === "penpot") { if (event.data.source === "penpot") {
document.body.dataset.theme = event.data.theme; document.body.dataset.theme = event.data.theme;
} }
}); });

View File

@ -2,25 +2,25 @@ penpot.ui.open("Penpot MCP Plugin", `?theme=${penpot.theme}`);
// Handle both legacy string messages and new task-based messages // Handle both legacy string messages and new task-based messages
penpot.ui.onMessage<string | { task: string; params: any }>((message) => { penpot.ui.onMessage<string | { task: string; params: any }>((message) => {
// Legacy string-based message handling // Legacy string-based message handling
if (typeof message === "string") { if (typeof message === "string") {
if (message === "create-text") { if (message === "create-text") {
const text = penpot.createText("Hello world!"); const text = penpot.createText("Hello world!");
if (text) { if (text) {
text.x = penpot.viewport.center.x; text.x = penpot.viewport.center.x;
text.y = penpot.viewport.center.y; text.y = penpot.viewport.center.y;
penpot.selection = [text]; penpot.selection = [text];
} }
}
return;
} }
return;
}
// New task-based message handling // New task-based message handling
if (typeof message === "object" && message.task) { if (typeof message === "object" && message.task) {
handlePluginTask(message); handlePluginTask(message);
} }
}); });
/** /**
@ -29,16 +29,16 @@ penpot.ui.onMessage<string | { task: string; params: any }>((message) => {
* @param taskMessage - The task message containing task type and parameters * @param taskMessage - The task message containing task type and parameters
*/ */
function handlePluginTask(taskMessage: { task: string; params: any }): void { function handlePluginTask(taskMessage: { task: string; params: any }): void {
console.log("Executing plugin task:", taskMessage.task, taskMessage.params); console.log("Executing plugin task:", taskMessage.task, taskMessage.params);
switch (taskMessage.task) { switch (taskMessage.task) {
case "printText": case "printText":
handlePrintTextTask(taskMessage.params); handlePrintTextTask(taskMessage.params);
break; break;
default: default:
console.warn("Unknown plugin task:", taskMessage.task); console.warn("Unknown plugin task:", taskMessage.task);
} }
} }
/** /**
@ -47,36 +47,36 @@ function handlePluginTask(taskMessage: { task: string; params: any }): void {
* @param params - The parameters containing the text to create * @param params - The parameters containing the text to create
*/ */
function handlePrintTextTask(params: { text: string }): void { function handlePrintTextTask(params: { text: string }): void {
if (!params.text) { if (!params.text) {
console.error("printText task requires 'text' parameter"); console.error("printText task requires 'text' parameter");
return; return;
} }
try { try {
const text = penpot.createText(params.text); const text = penpot.createText(params.text);
if (text) { if (text) {
// Center the text in the viewport // Center the text in the viewport
text.x = penpot.viewport.center.x; text.x = penpot.viewport.center.x;
text.y = penpot.viewport.center.y; text.y = penpot.viewport.center.y;
// Select the newly created text // Select the newly created text
penpot.selection = [text]; penpot.selection = [text];
console.log("Successfully created text:", params.text); console.log("Successfully created text:", params.text);
} else { } else {
console.error("Failed to create text element"); console.error("Failed to create text element");
}
} catch (error) {
console.error("Error creating text:", error);
} }
} catch (error) {
console.error("Error creating text:", error);
}
} }
// Update the theme in the iframe // Update the theme in the iframe
penpot.on("themechange", (theme) => { penpot.on("themechange", (theme) => {
penpot.ui.sendMessage({ penpot.ui.sendMessage({
source: "penpot", source: "penpot",
type: "themechange", type: "themechange",
theme, theme,
}); });
}); });

View File

@ -1,10 +1,10 @@
@import "@penpot/plugin-styles/styles.css"; @import "@penpot/plugin-styles/styles.css";
body { body {
line-height: 1.5; line-height: 1.5;
padding: 10px; padding: 10px;
} }
p { p {
margin-block-end: 0.75rem; margin-block-end: 0.75rem;
} }

View File

@ -1,27 +1,24 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "ES2020", "target": "ES2020",
"useDefineForClassFields": true, "useDefineForClassFields": true,
"module": "ESNext", "module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"], "lib": ["ES2020", "DOM", "DOM.Iterable"],
"skipLibCheck": true, "skipLibCheck": true,
"typeRoots": [ "typeRoots": ["./node_modules/@types", "./node_modules/@penpot"],
"./node_modules/@types", "types": ["plugin-types"],
"./node_modules/@penpot" /* Bundler mode */
], "moduleResolution": "bundler",
"types": ["plugin-types"], "allowImportingTsExtensions": true,
/* Bundler mode */ "resolveJsonModule": true,
"moduleResolution": "bundler", "isolatedModules": true,
"allowImportingTsExtensions": true, "noEmit": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
/* Linting */ /* Linting */
"strict": true, "strict": true,
"noUnusedLocals": true, "noUnusedLocals": true,
"noUnusedParameters": true, "noUnusedParameters": true,
"noFallthroughCasesInSwitch": true "noFallthroughCasesInSwitch": true
}, },
"include": ["src"] "include": ["src"]
} }

View File

@ -2,29 +2,29 @@ import { defineConfig } from "vite";
import livePreview from "vite-live-preview"; import livePreview from "vite-live-preview";
export default defineConfig({ export default defineConfig({
plugins: [ plugins: [
livePreview({ livePreview({
reload: true, reload: true,
config: { config: {
build: { build: {
sourcemap: true, sourcemap: true,
},
},
}),
],
build: {
rollupOptions: {
input: {
plugin: "src/plugin.ts",
index: "./index.html",
},
output: {
entryFileNames: "[name].js",
},
}, },
},
}),
],
build: {
rollupOptions: {
input: {
plugin: "src/plugin.ts",
index: "./index.html",
},
output: {
entryFileNames: "[name].js",
},
}, },
}, preview: {
preview: { port: 4400,
port: 4400, cors: true,
cors: true, },
},
}); });