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
{
"name": "Your plugin name",
"description": "Your plugin description",
"code": "plugin-file.js",
"icon": "your-icon.png",
"permissions": [
"content:read",
"content:write",
"library:read",
"library:write",
"user:read"
]
"name": "Your plugin name",
"description": "Your plugin description",
"code": "plugin-file.js",
"icon": "your-icon.png",
"permissions": ["content:read", "content:write", "library:read", "library:write", "user:read"]
}
```

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

@ -2,81 +2,81 @@ penpot.ui.open("Penpot MCP Plugin", `?theme=${penpot.theme}`);
// Handle both legacy string messages and new task-based messages
penpot.ui.onMessage<string | { task: string; params: any }>((message) => {
// Legacy string-based message handling
if (typeof message === "string") {
if (message === "create-text") {
const text = penpot.createText("Hello world!");
// Legacy string-based message handling
if (typeof message === "string") {
if (message === "create-text") {
const text = penpot.createText("Hello world!");
if (text) {
text.x = penpot.viewport.center.x;
text.y = penpot.viewport.center.y;
if (text) {
text.x = penpot.viewport.center.x;
text.y = penpot.viewport.center.y;
penpot.selection = [text];
}
penpot.selection = [text];
}
}
return;
}
return;
}
// New task-based message handling
if (typeof message === "object" && message.task) {
handlePluginTask(message);
}
// New task-based message handling
if (typeof message === "object" && message.task) {
handlePluginTask(message);
}
});
/**
* Handles plugin tasks received from the MCP server via WebSocket.
*
*
* @param taskMessage - The task message containing task type and parameters
*/
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) {
case "printText":
handlePrintTextTask(taskMessage.params);
break;
default:
console.warn("Unknown plugin task:", taskMessage.task);
}
switch (taskMessage.task) {
case "printText":
handlePrintTextTask(taskMessage.params);
break;
default:
console.warn("Unknown plugin task:", taskMessage.task);
}
}
/**
* Handles the printText task by creating text in Penpot.
*
*
* @param params - The parameters containing the text to create
*/
function handlePrintTextTask(params: { text: string }): void {
if (!params.text) {
console.error("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);
} else {
console.error("Failed to create text element");
if (!params.text) {
console.error("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);
} else {
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
penpot.on("themechange", (theme) => {
penpot.ui.sendMessage({
source: "penpot",
type: "themechange",
theme,
});
penpot.ui.sendMessage({
source: "penpot",
type: "themechange",
theme,
});
});

View File

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

View File

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

View File

@ -2,29 +2,29 @@ import { defineConfig } from "vite";
import livePreview from "vite-live-preview";
export default defineConfig({
plugins: [
livePreview({
reload: true,
config: {
build: {
sourcemap: true,
plugins: [
livePreview({
reload: true,
config: {
build: {
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: {
port: 4400,
cors: true,
},
preview: {
port: 4400,
cors: true,
},
});