#
tokens: 15570/50000 1/63 files (page 3/3)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 3 of 3. Use http://codebase.md/sichang824/mcp-figma?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .env.example
├── .envrc
├── .gitignore
├── .mcp.pid
├── bun.lockb
├── docs
│   ├── 01-overview.md
│   ├── 02-implementation-steps.md
│   ├── 03-components-and-features.md
│   ├── 04-usage-guide.md
│   ├── 05-project-status.md
│   ├── image.png
│   ├── README.md
│   └── widget-tools-guide.md
├── Makefile
├── manifest.json
├── package.json
├── prompt.md
├── README.md
├── README.zh.md
├── src
│   ├── config
│   │   └── env.ts
│   ├── index.ts
│   ├── plugin
│   │   ├── code.js
│   │   ├── code.ts
│   │   ├── creators
│   │   │   ├── componentCreators.ts
│   │   │   ├── containerCreators.ts
│   │   │   ├── elementCreator.ts
│   │   │   ├── imageCreators.ts
│   │   │   ├── shapeCreators.ts
│   │   │   ├── sliceCreators.ts
│   │   │   ├── specialCreators.ts
│   │   │   └── textCreator.ts
│   │   ├── manifest.json
│   │   ├── README.md
│   │   ├── tsconfig.json
│   │   ├── ui.html
│   │   └── utils
│   │       ├── colorUtils.ts
│   │       └── nodeUtils.ts
│   ├── resources.ts
│   ├── services
│   │   ├── figma-api.ts
│   │   ├── websocket.ts
│   │   └── widget-api.ts
│   ├── tools
│   │   ├── canvas.ts
│   │   ├── comment.ts
│   │   ├── component.ts
│   │   ├── file.ts
│   │   ├── frame.ts
│   │   ├── image.ts
│   │   ├── index.ts
│   │   ├── node.ts
│   │   ├── page.ts
│   │   ├── search.ts
│   │   ├── utils
│   │   │   └── widget-utils.ts
│   │   ├── version.ts
│   │   ├── widget
│   │   │   ├── analyze-widget-structure.ts
│   │   │   ├── get-widget-sync-data.ts
│   │   │   ├── get-widget.ts
│   │   │   ├── get-widgets.ts
│   │   │   ├── index.ts
│   │   │   ├── README.md
│   │   │   ├── search-widgets.ts
│   │   │   └── widget-tools.ts
│   │   └── zod-schemas.ts
│   ├── utils
│   │   ├── figma-utils.ts
│   │   └── widget-utils.ts
│   ├── utils.ts
│   ├── widget
│   │   └── utils
│   │       └── widget-tools.ts
│   └── widget-tools.ts
├── tsconfig.json
└── tsconfig.widget.json
```

# Files

--------------------------------------------------------------------------------
/src/plugin/code.js:
--------------------------------------------------------------------------------

```javascript
   1 | // src/plugin/utils/colorUtils.ts
   2 | function hexToRgb(hex) {
   3 |   hex = hex.replace("#", "");
   4 |   const r = parseInt(hex.substring(0, 2), 16) / 255;
   5 |   const g = parseInt(hex.substring(2, 4), 16) / 255;
   6 |   const b = parseInt(hex.substring(4, 6), 16) / 255;
   7 |   return { r, g, b };
   8 | }
   9 | function createSolidPaint(color) {
  10 |   if (typeof color === "string") {
  11 |     return { type: "SOLID", color: hexToRgb(color) };
  12 |   }
  13 |   return { type: "SOLID", color };
  14 | }
  15 | 
  16 | // src/plugin/utils/nodeUtils.ts
  17 | function applyCommonProperties(node, data) {
  18 |   if (data.x !== undefined)
  19 |     node.x = data.x;
  20 |   if (data.y !== undefined)
  21 |     node.y = data.y;
  22 |   if (data.name)
  23 |     node.name = data.name;
  24 |   if (data.opacity !== undefined && "opacity" in node) {
  25 |     node.opacity = data.opacity;
  26 |   }
  27 |   if (data.blendMode && "blendMode" in node) {
  28 |     node.blendMode = data.blendMode;
  29 |   }
  30 |   if (data.effects && "effects" in node) {
  31 |     node.effects = data.effects;
  32 |   }
  33 |   if (data.constraints && "constraints" in node) {
  34 |     node.constraints = data.constraints;
  35 |   }
  36 |   if (data.isMask !== undefined && "isMask" in node) {
  37 |     node.isMask = data.isMask;
  38 |   }
  39 |   if (data.visible !== undefined)
  40 |     node.visible = data.visible;
  41 |   if (data.locked !== undefined)
  42 |     node.locked = data.locked;
  43 | }
  44 | function selectAndFocusNodes(nodes) {
  45 |   const nodesToFocus = Array.isArray(nodes) ? nodes : [nodes];
  46 |   figma.currentPage.selection = nodesToFocus;
  47 |   figma.viewport.scrollAndZoomIntoView(nodesToFocus);
  48 | }
  49 | function buildResultObject(result) {
  50 |   let resultObject = {};
  51 |   if (!result)
  52 |     return resultObject;
  53 |   if (Array.isArray(result)) {
  54 |     resultObject.count = result.length;
  55 |     if (result.length > 0) {
  56 |       resultObject.items = result.map((node) => ({
  57 |         id: node.id,
  58 |         type: node.type,
  59 |         name: node.name
  60 |       }));
  61 |     }
  62 |   } else {
  63 |     const node = result;
  64 |     resultObject.id = node.id;
  65 |     resultObject.type = node.type;
  66 |     resultObject.name = node.name;
  67 |   }
  68 |   return resultObject;
  69 | }
  70 | 
  71 | // src/plugin/creators/shapeCreators.ts
  72 | function createRectangleFromData(data) {
  73 |   const rect = figma.createRectangle();
  74 |   rect.resize(data.width || 100, data.height || 100);
  75 |   if (data.fills) {
  76 |     rect.fills = data.fills;
  77 |   } else if (data.fill) {
  78 |     if (typeof data.fill === "string") {
  79 |       rect.fills = [createSolidPaint(data.fill)];
  80 |     } else {
  81 |       rect.fills = [data.fill];
  82 |     }
  83 |   }
  84 |   if (data.strokes)
  85 |     rect.strokes = data.strokes;
  86 |   if (data.strokeWeight !== undefined)
  87 |     rect.strokeWeight = data.strokeWeight;
  88 |   if (data.strokeAlign)
  89 |     rect.strokeAlign = data.strokeAlign;
  90 |   if (data.strokeCap)
  91 |     rect.strokeCap = data.strokeCap;
  92 |   if (data.strokeJoin)
  93 |     rect.strokeJoin = data.strokeJoin;
  94 |   if (data.dashPattern)
  95 |     rect.dashPattern = data.dashPattern;
  96 |   if (data.cornerRadius !== undefined)
  97 |     rect.cornerRadius = data.cornerRadius;
  98 |   if (data.topLeftRadius !== undefined)
  99 |     rect.topLeftRadius = data.topLeftRadius;
 100 |   if (data.topRightRadius !== undefined)
 101 |     rect.topRightRadius = data.topRightRadius;
 102 |   if (data.bottomLeftRadius !== undefined)
 103 |     rect.bottomLeftRadius = data.bottomLeftRadius;
 104 |   if (data.bottomRightRadius !== undefined)
 105 |     rect.bottomRightRadius = data.bottomRightRadius;
 106 |   return rect;
 107 | }
 108 | function createEllipseFromData(data) {
 109 |   const ellipse = figma.createEllipse();
 110 |   ellipse.resize(data.width || 100, data.height || 100);
 111 |   if (data.fills) {
 112 |     ellipse.fills = data.fills;
 113 |   } else if (data.fill) {
 114 |     if (typeof data.fill === "string") {
 115 |       ellipse.fills = [createSolidPaint(data.fill)];
 116 |     } else {
 117 |       ellipse.fills = [data.fill];
 118 |     }
 119 |   }
 120 |   if (data.arcData) {
 121 |     ellipse.arcData = {
 122 |       startingAngle: data.arcData.startingAngle !== undefined ? data.arcData.startingAngle : 0,
 123 |       endingAngle: data.arcData.endingAngle !== undefined ? data.arcData.endingAngle : 360,
 124 |       innerRadius: data.arcData.innerRadius !== undefined ? data.arcData.innerRadius : 0
 125 |     };
 126 |   }
 127 |   if (data.strokes)
 128 |     ellipse.strokes = data.strokes;
 129 |   if (data.strokeWeight !== undefined)
 130 |     ellipse.strokeWeight = data.strokeWeight;
 131 |   if (data.strokeAlign)
 132 |     ellipse.strokeAlign = data.strokeAlign;
 133 |   if (data.strokeCap)
 134 |     ellipse.strokeCap = data.strokeCap;
 135 |   if (data.strokeJoin)
 136 |     ellipse.strokeJoin = data.strokeJoin;
 137 |   if (data.dashPattern)
 138 |     ellipse.dashPattern = data.dashPattern;
 139 |   return ellipse;
 140 | }
 141 | function createPolygonFromData(data) {
 142 |   const polygon = figma.createPolygon();
 143 |   polygon.resize(data.width || 100, data.height || 100);
 144 |   if (data.pointCount)
 145 |     polygon.pointCount = data.pointCount;
 146 |   if (data.fills) {
 147 |     polygon.fills = data.fills;
 148 |   } else if (data.fill) {
 149 |     if (typeof data.fill === "string") {
 150 |       polygon.fills = [createSolidPaint(data.fill)];
 151 |     } else {
 152 |       polygon.fills = [data.fill];
 153 |     }
 154 |   } else if (data.color) {
 155 |     polygon.fills = [createSolidPaint(data.color)];
 156 |   }
 157 |   if (data.strokes)
 158 |     polygon.strokes = data.strokes;
 159 |   if (data.strokeWeight !== undefined)
 160 |     polygon.strokeWeight = data.strokeWeight;
 161 |   if (data.strokeAlign)
 162 |     polygon.strokeAlign = data.strokeAlign;
 163 |   if (data.strokeCap)
 164 |     polygon.strokeCap = data.strokeCap;
 165 |   if (data.strokeJoin)
 166 |     polygon.strokeJoin = data.strokeJoin;
 167 |   if (data.dashPattern)
 168 |     polygon.dashPattern = data.dashPattern;
 169 |   return polygon;
 170 | }
 171 | function createStarFromData(data) {
 172 |   const star = figma.createStar();
 173 |   star.resize(data.width || 100, data.height || 100);
 174 |   if (data.pointCount)
 175 |     star.pointCount = data.pointCount;
 176 |   if (data.innerRadius)
 177 |     star.innerRadius = data.innerRadius;
 178 |   if (data.fills) {
 179 |     star.fills = data.fills;
 180 |   } else if (data.fill) {
 181 |     if (typeof data.fill === "string") {
 182 |       star.fills = [createSolidPaint(data.fill)];
 183 |     } else {
 184 |       star.fills = [data.fill];
 185 |     }
 186 |   }
 187 |   return star;
 188 | }
 189 | function createLineFromData(data) {
 190 |   const line = figma.createLine();
 191 |   line.resize(data.width || 100, 0);
 192 |   if (data.rotation !== undefined)
 193 |     line.rotation = data.rotation;
 194 |   if (data.strokeWeight)
 195 |     line.strokeWeight = data.strokeWeight;
 196 |   if (data.strokeAlign)
 197 |     line.strokeAlign = data.strokeAlign;
 198 |   if (data.strokeCap)
 199 |     line.strokeCap = data.strokeCap;
 200 |   if (data.strokeJoin)
 201 |     line.strokeJoin = data.strokeJoin;
 202 |   if (data.dashPattern)
 203 |     line.dashPattern = data.dashPattern;
 204 |   if (data.strokes) {
 205 |     line.strokes = data.strokes;
 206 |   } else if (data.stroke) {
 207 |     if (typeof data.stroke === "string") {
 208 |       line.strokes = [createSolidPaint(data.stroke)];
 209 |     } else {
 210 |       line.strokes = [data.stroke];
 211 |     }
 212 |   } else if (data.color) {
 213 |     line.strokes = [createSolidPaint(data.color)];
 214 |   }
 215 |   return line;
 216 | }
 217 | function createVectorFromData(data) {
 218 |   const vector = figma.createVector();
 219 |   try {
 220 |     vector.resize(data.width || 100, data.height || 100);
 221 |     if (data.vectorNetwork) {
 222 |       vector.vectorNetwork = data.vectorNetwork;
 223 |     }
 224 |     if (data.vectorPaths) {
 225 |       vector.vectorPaths = data.vectorPaths;
 226 |     }
 227 |     if (data.handleMirroring) {
 228 |       vector.handleMirroring = data.handleMirroring;
 229 |     }
 230 |     if (data.fills) {
 231 |       vector.fills = data.fills;
 232 |     } else if (data.fill) {
 233 |       if (typeof data.fill === "string") {
 234 |         vector.fills = [createSolidPaint(data.fill)];
 235 |       } else {
 236 |         vector.fills = [data.fill];
 237 |       }
 238 |     } else if (data.color) {
 239 |       vector.fills = [createSolidPaint(data.color)];
 240 |     }
 241 |     if (data.strokes)
 242 |       vector.strokes = data.strokes;
 243 |     if (data.strokeWeight !== undefined)
 244 |       vector.strokeWeight = data.strokeWeight;
 245 |     if (data.strokeAlign)
 246 |       vector.strokeAlign = data.strokeAlign;
 247 |     if (data.strokeCap)
 248 |       vector.strokeCap = data.strokeCap;
 249 |     if (data.strokeJoin)
 250 |       vector.strokeJoin = data.strokeJoin;
 251 |     if (data.dashPattern)
 252 |       vector.dashPattern = data.dashPattern;
 253 |     if (data.strokeMiterLimit)
 254 |       vector.strokeMiterLimit = data.strokeMiterLimit;
 255 |     if (data.cornerRadius !== undefined)
 256 |       vector.cornerRadius = data.cornerRadius;
 257 |     if (data.cornerSmoothing !== undefined)
 258 |       vector.cornerSmoothing = data.cornerSmoothing;
 259 |     if (data.opacity !== undefined)
 260 |       vector.opacity = data.opacity;
 261 |     if (data.blendMode)
 262 |       vector.blendMode = data.blendMode;
 263 |     if (data.isMask !== undefined)
 264 |       vector.isMask = data.isMask;
 265 |     if (data.effects)
 266 |       vector.effects = data.effects;
 267 |     if (data.constraints)
 268 |       vector.constraints = data.constraints;
 269 |     if (data.layoutAlign)
 270 |       vector.layoutAlign = data.layoutAlign;
 271 |     if (data.layoutGrow !== undefined)
 272 |       vector.layoutGrow = data.layoutGrow;
 273 |     if (data.layoutPositioning)
 274 |       vector.layoutPositioning = data.layoutPositioning;
 275 |     if (data.rotation !== undefined)
 276 |       vector.rotation = data.rotation;
 277 |     if (data.layoutSizingHorizontal)
 278 |       vector.layoutSizingHorizontal = data.layoutSizingHorizontal;
 279 |     if (data.layoutSizingVertical)
 280 |       vector.layoutSizingVertical = data.layoutSizingVertical;
 281 |     console.log("Vector created successfully:", vector);
 282 |   } catch (error) {
 283 |     console.error("Error creating vector:", error);
 284 |   }
 285 |   return vector;
 286 | }
 287 | 
 288 | // src/plugin/creators/containerCreators.ts
 289 | function createFrameFromData(data) {
 290 |   const frame = figma.createFrame();
 291 |   frame.resize(data.width || 100, data.height || 100);
 292 |   if (data.fills) {
 293 |     frame.fills = data.fills;
 294 |   } else if (data.fill) {
 295 |     if (typeof data.fill === "string") {
 296 |       frame.fills = [createSolidPaint(data.fill)];
 297 |     } else {
 298 |       frame.fills = [data.fill];
 299 |     }
 300 |   }
 301 |   if (data.layoutMode)
 302 |     frame.layoutMode = data.layoutMode;
 303 |   if (data.primaryAxisSizingMode)
 304 |     frame.primaryAxisSizingMode = data.primaryAxisSizingMode;
 305 |   if (data.counterAxisSizingMode)
 306 |     frame.counterAxisSizingMode = data.counterAxisSizingMode;
 307 |   if (data.primaryAxisAlignItems)
 308 |     frame.primaryAxisAlignItems = data.primaryAxisAlignItems;
 309 |   if (data.counterAxisAlignItems)
 310 |     frame.counterAxisAlignItems = data.counterAxisAlignItems;
 311 |   if (data.paddingLeft !== undefined)
 312 |     frame.paddingLeft = data.paddingLeft;
 313 |   if (data.paddingRight !== undefined)
 314 |     frame.paddingRight = data.paddingRight;
 315 |   if (data.paddingTop !== undefined)
 316 |     frame.paddingTop = data.paddingTop;
 317 |   if (data.paddingBottom !== undefined)
 318 |     frame.paddingBottom = data.paddingBottom;
 319 |   if (data.itemSpacing !== undefined)
 320 |     frame.itemSpacing = data.itemSpacing;
 321 |   if (data.cornerRadius !== undefined)
 322 |     frame.cornerRadius = data.cornerRadius;
 323 |   if (data.topLeftRadius !== undefined)
 324 |     frame.topLeftRadius = data.topLeftRadius;
 325 |   if (data.topRightRadius !== undefined)
 326 |     frame.topRightRadius = data.topRightRadius;
 327 |   if (data.bottomLeftRadius !== undefined)
 328 |     frame.bottomLeftRadius = data.bottomLeftRadius;
 329 |   if (data.bottomRightRadius !== undefined)
 330 |     frame.bottomRightRadius = data.bottomRightRadius;
 331 |   return frame;
 332 | }
 333 | function createComponentFromData(data) {
 334 |   const component = figma.createComponent();
 335 |   component.resize(data.width || 100, data.height || 100);
 336 |   if (data.fills) {
 337 |     component.fills = data.fills;
 338 |   } else if (data.fill) {
 339 |     if (typeof data.fill === "string") {
 340 |       component.fills = [createSolidPaint(data.fill)];
 341 |     } else {
 342 |       component.fills = [data.fill];
 343 |     }
 344 |   }
 345 |   if (data.layoutMode)
 346 |     component.layoutMode = data.layoutMode;
 347 |   if (data.primaryAxisSizingMode)
 348 |     component.primaryAxisSizingMode = data.primaryAxisSizingMode;
 349 |   if (data.counterAxisSizingMode)
 350 |     component.counterAxisSizingMode = data.counterAxisSizingMode;
 351 |   if (data.primaryAxisAlignItems)
 352 |     component.primaryAxisAlignItems = data.primaryAxisAlignItems;
 353 |   if (data.counterAxisAlignItems)
 354 |     component.counterAxisAlignItems = data.counterAxisAlignItems;
 355 |   if (data.paddingLeft !== undefined)
 356 |     component.paddingLeft = data.paddingLeft;
 357 |   if (data.paddingRight !== undefined)
 358 |     component.paddingRight = data.paddingRight;
 359 |   if (data.paddingTop !== undefined)
 360 |     component.paddingTop = data.paddingTop;
 361 |   if (data.paddingBottom !== undefined)
 362 |     component.paddingBottom = data.paddingBottom;
 363 |   if (data.itemSpacing !== undefined)
 364 |     component.itemSpacing = data.itemSpacing;
 365 |   if (data.description)
 366 |     component.description = data.description;
 367 |   return component;
 368 | }
 369 | function createGroupFromData(data, children) {
 370 |   const group = figma.group(children, figma.currentPage);
 371 |   applyCommonProperties(group, data);
 372 |   return group;
 373 | }
 374 | function createInstanceFromData(data) {
 375 |   if (!data.componentId) {
 376 |     console.error("Cannot create instance: componentId is required");
 377 |     return null;
 378 |   }
 379 |   const component = figma.getNodeById(data.componentId);
 380 |   if (!component || component.type !== "COMPONENT") {
 381 |     console.error(`Cannot create instance: component with id ${data.componentId} not found`);
 382 |     return null;
 383 |   }
 384 |   const instance = component.createInstance();
 385 |   applyCommonProperties(instance, data);
 386 |   if (data.componentProperties) {
 387 |     for (const [key, value] of Object.entries(data.componentProperties)) {
 388 |       if (key in instance.componentProperties) {
 389 |         const prop = instance.componentProperties[key];
 390 |         if (prop.type === "BOOLEAN") {
 391 |           instance.setProperties({ [key]: !!value });
 392 |         } else if (prop.type === "TEXT") {
 393 |           instance.setProperties({ [key]: String(value) });
 394 |         } else if (prop.type === "INSTANCE_SWAP") {
 395 |           instance.setProperties({ [key]: String(value) });
 396 |         } else if (prop.type === "VARIANT") {
 397 |           instance.setProperties({ [key]: String(value) });
 398 |         }
 399 |       }
 400 |     }
 401 |   }
 402 |   return instance;
 403 | }
 404 | function createSectionFromData(data) {
 405 |   const section = figma.createSection();
 406 |   if (data.name)
 407 |     section.name = data.name;
 408 |   if (data.sectionContentsHidden !== undefined)
 409 |     section.sectionContentsHidden = data.sectionContentsHidden;
 410 |   if (data.x !== undefined)
 411 |     section.x = data.x;
 412 |   if (data.y !== undefined)
 413 |     section.y = data.y;
 414 |   return section;
 415 | }
 416 | 
 417 | // src/plugin/creators/textCreator.ts
 418 | async function createTextFromData(data) {
 419 |   const text = figma.createText();
 420 |   const fontFamily = data.fontFamily || data.fontName.family || "Inter";
 421 |   const fontStyle = data.fontStyle || data.fontName.style || "Regular";
 422 |   try {
 423 |     await figma.loadFontAsync({ family: fontFamily, style: fontStyle });
 424 |   } catch (error) {
 425 |     console.warn(`Failed to load font ${fontFamily} ${fontStyle}. Falling back to Inter Regular.`);
 426 |     await figma.loadFontAsync({ family: "Inter", style: "Regular" });
 427 |   }
 428 |   text.characters = data.text || data.characters || "Text";
 429 |   if (data.x !== undefined)
 430 |     text.x = data.x;
 431 |   if (data.y !== undefined)
 432 |     text.y = data.y;
 433 |   if (data.fontSize)
 434 |     text.fontSize = data.fontSize;
 435 |   if (data.width)
 436 |     text.resize(data.width, text.height);
 437 |   if (data.fontName)
 438 |     text.fontName = data.fontName;
 439 |   if (data.textAlignHorizontal)
 440 |     text.textAlignHorizontal = data.textAlignHorizontal;
 441 |   if (data.textAlignVertical)
 442 |     text.textAlignVertical = data.textAlignVertical;
 443 |   if (data.textAutoResize)
 444 |     text.textAutoResize = data.textAutoResize;
 445 |   if (data.textTruncation)
 446 |     text.textTruncation = data.textTruncation;
 447 |   if (data.maxLines !== undefined)
 448 |     text.maxLines = data.maxLines;
 449 |   if (data.paragraphIndent)
 450 |     text.paragraphIndent = data.paragraphIndent;
 451 |   if (data.paragraphSpacing)
 452 |     text.paragraphSpacing = data.paragraphSpacing;
 453 |   if (data.listSpacing)
 454 |     text.listSpacing = data.listSpacing;
 455 |   if (data.hangingPunctuation !== undefined)
 456 |     text.hangingPunctuation = data.hangingPunctuation;
 457 |   if (data.hangingList !== undefined)
 458 |     text.hangingList = data.hangingList;
 459 |   if (data.autoRename !== undefined)
 460 |     text.autoRename = data.autoRename;
 461 |   if (data.letterSpacing)
 462 |     text.letterSpacing = data.letterSpacing;
 463 |   if (data.lineHeight)
 464 |     text.lineHeight = data.lineHeight;
 465 |   if (data.leadingTrim)
 466 |     text.leadingTrim = data.leadingTrim;
 467 |   if (data.textCase)
 468 |     text.textCase = data.textCase;
 469 |   if (data.textDecoration)
 470 |     text.textDecoration = data.textDecoration;
 471 |   if (data.textStyleId)
 472 |     text.textStyleId = data.textStyleId;
 473 |   if (data.textDecorationStyle)
 474 |     text.textDecorationStyle = data.textDecorationStyle;
 475 |   if (data.textDecorationOffset)
 476 |     text.textDecorationOffset = data.textDecorationOffset;
 477 |   if (data.textDecorationThickness)
 478 |     text.textDecorationThickness = data.textDecorationThickness;
 479 |   if (data.textDecorationColor)
 480 |     text.textDecorationColor = data.textDecorationColor;
 481 |   if (data.textDecorationSkipInk !== undefined)
 482 |     text.textDecorationSkipInk = data.textDecorationSkipInk;
 483 |   if (data.fills) {
 484 |     text.fills = data.fills;
 485 |   } else if (data.fill) {
 486 |     if (typeof data.fill === "string") {
 487 |       text.fills = [createSolidPaint(data.fill)];
 488 |     } else {
 489 |       text.fills = [data.fill];
 490 |     }
 491 |   }
 492 |   if (data.hyperlink) {
 493 |     text.hyperlink = data.hyperlink;
 494 |   }
 495 |   if (data.layoutAlign)
 496 |     text.layoutAlign = data.layoutAlign;
 497 |   if (data.layoutGrow !== undefined)
 498 |     text.layoutGrow = data.layoutGrow;
 499 |   if (data.layoutSizingHorizontal)
 500 |     text.layoutSizingHorizontal = data.layoutSizingHorizontal;
 501 |   if (data.layoutSizingVertical)
 502 |     text.layoutSizingVertical = data.layoutSizingVertical;
 503 |   if (data.rangeStyles && Array.isArray(data.rangeStyles)) {
 504 |     applyTextRangeStyles(text, data.rangeStyles);
 505 |   }
 506 |   if (data.name)
 507 |     text.name = data.name;
 508 |   if (data.visible !== undefined)
 509 |     text.visible = data.visible;
 510 |   if (data.locked !== undefined)
 511 |     text.locked = data.locked;
 512 |   if (data.opacity !== undefined)
 513 |     text.opacity = data.opacity;
 514 |   if (data.blendMode)
 515 |     text.blendMode = data.blendMode;
 516 |   if (data.effects)
 517 |     text.effects = data.effects;
 518 |   if (data.effectStyleId)
 519 |     text.effectStyleId = data.effectStyleId;
 520 |   if (data.exportSettings)
 521 |     text.exportSettings = data.exportSettings;
 522 |   if (data.constraints)
 523 |     text.constraints = data.constraints;
 524 |   return text;
 525 | }
 526 | function applyTextRangeStyles(textNode, ranges) {
 527 |   for (const range of ranges) {
 528 |     for (const [property, value] of Object.entries(range.style)) {
 529 |       if (property === "fills") {
 530 |         textNode.setRangeFills(range.start, range.end, value);
 531 |       } else if (property === "fillStyleId") {
 532 |         textNode.setRangeFillStyleId(range.start, range.end, value);
 533 |       } else if (property === "fontName") {
 534 |         textNode.setRangeFontName(range.start, range.end, value);
 535 |       } else if (property === "fontSize") {
 536 |         textNode.setRangeFontSize(range.start, range.end, value);
 537 |       } else if (property === "textCase") {
 538 |         textNode.setRangeTextCase(range.start, range.end, value);
 539 |       } else if (property === "textDecoration") {
 540 |         textNode.setRangeTextDecoration(range.start, range.end, value);
 541 |       } else if (property === "textDecorationStyle") {
 542 |         textNode.setRangeTextDecorationStyle(range.start, range.end, value);
 543 |       } else if (property === "textDecorationOffset") {
 544 |         textNode.setRangeTextDecorationOffset(range.start, range.end, value);
 545 |       } else if (property === "textDecorationThickness") {
 546 |         textNode.setRangeTextDecorationThickness(range.start, range.end, value);
 547 |       } else if (property === "textDecorationColor") {
 548 |         textNode.setRangeTextDecorationColor(range.start, range.end, value);
 549 |       } else if (property === "textDecorationSkipInk") {
 550 |         textNode.setRangeTextDecorationSkipInk(range.start, range.end, value);
 551 |       } else if (property === "letterSpacing") {
 552 |         textNode.setRangeLetterSpacing(range.start, range.end, value);
 553 |       } else if (property === "lineHeight") {
 554 |         textNode.setRangeLineHeight(range.start, range.end, value);
 555 |       } else if (property === "hyperlink") {
 556 |         textNode.setRangeHyperlink(range.start, range.end, value);
 557 |       } else if (property === "textStyleId") {
 558 |         textNode.setRangeTextStyleId(range.start, range.end, value);
 559 |       } else if (property === "indentation") {
 560 |         textNode.setRangeIndentation(range.start, range.end, value);
 561 |       } else if (property === "paragraphIndent") {
 562 |         textNode.setRangeParagraphIndent(range.start, range.end, value);
 563 |       } else if (property === "paragraphSpacing") {
 564 |         textNode.setRangeParagraphSpacing(range.start, range.end, value);
 565 |       } else if (property === "listOptions") {
 566 |         textNode.setRangeListOptions(range.start, range.end, value);
 567 |       } else if (property === "listSpacing") {
 568 |         textNode.setRangeListSpacing(range.start, range.end, value);
 569 |       }
 570 |     }
 571 |   }
 572 | }
 573 | 
 574 | // src/plugin/creators/specialCreators.ts
 575 | function createBooleanOperationFromData(data) {
 576 |   if (!data.children || !Array.isArray(data.children) || data.children.length < 2) {
 577 |     console.error("Boolean operation requires at least 2 child nodes");
 578 |     return null;
 579 |   }
 580 |   let childNodes = [];
 581 |   try {
 582 |     for (const childData of data.children) {
 583 |       const node = figma.createRectangle();
 584 |       childNodes.push(node);
 585 |     }
 586 |     const booleanOperation = figma.createBooleanOperation();
 587 |     if (data.booleanOperation) {
 588 |       booleanOperation.booleanOperation = data.booleanOperation;
 589 |     }
 590 |     applyCommonProperties(booleanOperation, data);
 591 |     return booleanOperation;
 592 |   } catch (error) {
 593 |     console.error("Failed to create boolean operation:", error);
 594 |     childNodes.forEach((node) => node.remove());
 595 |     return null;
 596 |   }
 597 | }
 598 | function createConnectorFromData(data) {
 599 |   const connector = figma.createConnector();
 600 |   if (data.connectorStart)
 601 |     connector.connectorStart = data.connectorStart;
 602 |   if (data.connectorEnd)
 603 |     connector.connectorEnd = data.connectorEnd;
 604 |   if (data.connectorStartStrokeCap)
 605 |     connector.connectorStartStrokeCap = data.connectorStartStrokeCap;
 606 |   if (data.connectorEndStrokeCap)
 607 |     connector.connectorEndStrokeCap = data.connectorEndStrokeCap;
 608 |   if (data.connectorLineType)
 609 |     connector.connectorLineType = data.connectorLineType;
 610 |   if (data.strokes)
 611 |     connector.strokes = data.strokes;
 612 |   if (data.strokeWeight)
 613 |     connector.strokeWeight = data.strokeWeight;
 614 |   applyCommonProperties(connector, data);
 615 |   return connector;
 616 | }
 617 | function createShapeWithTextFromData(data) {
 618 |   if (!("createShapeWithText" in figma)) {
 619 |     console.error("ShapeWithText creation is not supported in this Figma version");
 620 |     return null;
 621 |   }
 622 |   try {
 623 |     const shapeWithText = figma.createShapeWithText();
 624 |     if (data.shapeType)
 625 |       shapeWithText.shapeType = data.shapeType;
 626 |     if (data.text || data.characters) {
 627 |       shapeWithText.text.characters = data.text || data.characters;
 628 |     }
 629 |     try {
 630 |       if (data.fontSize)
 631 |         shapeWithText.text.fontSize = data.fontSize;
 632 |       if (data.fontName)
 633 |         shapeWithText.text.fontName = data.fontName;
 634 |       if (data.textAlignHorizontal && "textAlignHorizontal" in shapeWithText.text) {
 635 |         shapeWithText.text.textAlignHorizontal = data.textAlignHorizontal;
 636 |       }
 637 |       if (data.textAlignVertical && "textAlignVertical" in shapeWithText.text) {
 638 |         shapeWithText.text.textAlignVertical = data.textAlignVertical;
 639 |       }
 640 |     } catch (e) {
 641 |       console.warn("Some text properties could not be set on ShapeWithText:", e);
 642 |     }
 643 |     if (data.fills)
 644 |       shapeWithText.fills = data.fills;
 645 |     if (data.strokes)
 646 |       shapeWithText.strokes = data.strokes;
 647 |     applyCommonProperties(shapeWithText, data);
 648 |     return shapeWithText;
 649 |   } catch (error) {
 650 |     console.error("Failed to create shape with text:", error);
 651 |     return null;
 652 |   }
 653 | }
 654 | function createCodeBlockFromData(data) {
 655 |   const codeBlock = figma.createCodeBlock();
 656 |   if (data.code)
 657 |     codeBlock.code = data.code;
 658 |   if (data.codeLanguage)
 659 |     codeBlock.codeLanguage = data.codeLanguage;
 660 |   applyCommonProperties(codeBlock, data);
 661 |   return codeBlock;
 662 | }
 663 | function createTableFromData(data) {
 664 |   const table = figma.createTable(data.numRows || 2, data.numColumns || 2);
 665 |   if (data.fills && "fills" in table) {
 666 |     table.fills = data.fills;
 667 |   }
 668 |   if (data.cells && Array.isArray(data.cells)) {
 669 |     for (const cellData of data.cells) {
 670 |       if (cellData.rowIndex !== undefined && cellData.columnIndex !== undefined) {
 671 |         try {
 672 |           let cell;
 673 |           if ("cellAt" in table) {
 674 |             cell = table.cellAt(cellData.rowIndex, cellData.columnIndex);
 675 |           } else if ("getCellAt" in table) {
 676 |             cell = table.getCellAt(cellData.rowIndex, cellData.columnIndex);
 677 |           }
 678 |           if (cell) {
 679 |             if (cellData.text && cell.text)
 680 |               cell.text.characters = cellData.text;
 681 |             if (cellData.fills && "fills" in cell)
 682 |               cell.fills = cellData.fills;
 683 |             if (cellData.rowSpan && "rowSpan" in cell)
 684 |               cell.rowSpan = cellData.rowSpan;
 685 |             if (cellData.columnSpan && "columnSpan" in cell)
 686 |               cell.columnSpan = cellData.columnSpan;
 687 |           }
 688 |         } catch (e) {
 689 |           console.warn(`Could not set properties for cell at ${cellData.rowIndex}, ${cellData.columnIndex}:`, e);
 690 |         }
 691 |       }
 692 |     }
 693 |   }
 694 |   applyCommonProperties(table, data);
 695 |   return table;
 696 | }
 697 | function createWidgetFromData(data) {
 698 |   if (!("createWidget" in figma)) {
 699 |     console.error("Widget creation is not supported in this Figma version");
 700 |     return null;
 701 |   }
 702 |   if (!data.widgetId) {
 703 |     console.error("Widget creation requires a widgetId");
 704 |     return null;
 705 |   }
 706 |   try {
 707 |     const widget = figma.createWidget(data.widgetId);
 708 |     if (data.widgetData)
 709 |       widget.widgetData = JSON.stringify(data.widgetData);
 710 |     if (data.width && data.height && "resize" in widget)
 711 |       widget.resize(data.width, data.height);
 712 |     applyCommonProperties(widget, data);
 713 |     return widget;
 714 |   } catch (error) {
 715 |     console.error("Failed to create widget:", error);
 716 |     return null;
 717 |   }
 718 | }
 719 | function createMediaFromData(data) {
 720 |   if (!("createMedia" in figma)) {
 721 |     console.error("Media creation is not supported in this Figma version");
 722 |     return null;
 723 |   }
 724 |   if (!data.hash) {
 725 |     console.error("Media creation requires a valid media hash");
 726 |     return null;
 727 |   }
 728 |   try {
 729 |     const media = figma.createMedia(data.hash);
 730 |     applyCommonProperties(media, data);
 731 |     return media;
 732 |   } catch (error) {
 733 |     console.error("Failed to create media:", error);
 734 |     return null;
 735 |   }
 736 | }
 737 | 
 738 | // src/plugin/creators/imageCreators.ts
 739 | function createImageFromData(data) {
 740 |   try {
 741 |     if (!data.hash) {
 742 |       console.error("Image creation requires an image hash");
 743 |       return null;
 744 |     }
 745 |     const image = figma.createImage(data.hash);
 746 |     const rect = figma.createRectangle();
 747 |     if (data.width && data.height) {
 748 |       rect.resize(data.width, data.height);
 749 |     }
 750 |     rect.fills = [{
 751 |       type: "IMAGE",
 752 |       scaleMode: data.scaleMode || "FILL",
 753 |       imageHash: image.hash
 754 |     }];
 755 |     applyCommonProperties(rect, data);
 756 |     return rect;
 757 |   } catch (error) {
 758 |     console.error("Failed to create image:", error);
 759 |     return null;
 760 |   }
 761 | }
 762 | async function createImageFromBytesAsync(data) {
 763 |   try {
 764 |     if (!data.bytes && !data.file) {
 765 |       console.error("Image creation requires image bytes or file");
 766 |       return null;
 767 |     }
 768 |     let image;
 769 |     if (data.bytes) {
 770 |       image = await figma.createImageAsync(data.bytes);
 771 |     } else if (data.file) {
 772 |       image = await figma.createImageAsync(data.file);
 773 |     } else {
 774 |       return null;
 775 |     }
 776 |     const rect = figma.createRectangle();
 777 |     if (data.width && data.height) {
 778 |       rect.resize(data.width, data.height);
 779 |     }
 780 |     rect.fills = [{
 781 |       type: "IMAGE",
 782 |       scaleMode: data.scaleMode || "FILL",
 783 |       imageHash: image.hash
 784 |     }];
 785 |     applyCommonProperties(rect, data);
 786 |     return rect;
 787 |   } catch (error) {
 788 |     console.error("Failed to create image asynchronously:", error);
 789 |     return null;
 790 |   }
 791 | }
 792 | function createGifFromData(data) {
 793 |   console.error("createGif API is not directly available or implemented");
 794 |   return null;
 795 | }
 796 | async function createVideoFromDataAsync(data) {
 797 |   if (!("createVideoAsync" in figma)) {
 798 |     console.error("Video creation is not supported in this Figma version");
 799 |     return null;
 800 |   }
 801 |   try {
 802 |     if (!data.bytes) {
 803 |       console.error("Video creation requires video bytes");
 804 |       return null;
 805 |     }
 806 |     const video = await figma.createVideoAsync(data.bytes);
 807 |     applyCommonProperties(video, data);
 808 |     return video;
 809 |   } catch (error) {
 810 |     console.error("Failed to create video:", error);
 811 |     return null;
 812 |   }
 813 | }
 814 | async function createLinkPreviewFromDataAsync(data) {
 815 |   if (!("createLinkPreviewAsync" in figma)) {
 816 |     console.error("Link preview creation is not supported in this Figma version");
 817 |     return null;
 818 |   }
 819 |   try {
 820 |     if (!data.url) {
 821 |       console.error("Link preview creation requires a URL");
 822 |       return null;
 823 |     }
 824 |     const linkPreview = await figma.createLinkPreviewAsync(data.url);
 825 |     applyCommonProperties(linkPreview, data);
 826 |     return linkPreview;
 827 |   } catch (error) {
 828 |     console.error("Failed to create link preview:", error);
 829 |     return null;
 830 |   }
 831 | }
 832 | 
 833 | // src/plugin/creators/sliceCreators.ts
 834 | function createSliceFromData(data) {
 835 |   const slice = figma.createSlice();
 836 |   if (data.width && data.height) {
 837 |     slice.resize(data.width, data.height);
 838 |   }
 839 |   if (data.x !== undefined)
 840 |     slice.x = data.x;
 841 |   if (data.y !== undefined)
 842 |     slice.y = data.y;
 843 |   if (data.exportSettings && Array.isArray(data.exportSettings)) {
 844 |     slice.exportSettings = data.exportSettings;
 845 |   }
 846 |   if (data.name)
 847 |     slice.name = data.name;
 848 |   if (data.visible !== undefined)
 849 |     slice.visible = data.visible;
 850 |   return slice;
 851 | }
 852 | function createPageFromData(data) {
 853 |   const page = figma.createPage();
 854 |   if (data.name)
 855 |     page.name = data.name;
 856 |   if (data.backgrounds)
 857 |     page.backgrounds = data.backgrounds;
 858 |   return page;
 859 | }
 860 | function createPageDividerFromData(data) {
 861 |   if (!("createPageDivider" in figma)) {
 862 |     console.error("createPageDivider is not supported in this Figma version");
 863 |     return null;
 864 |   }
 865 |   try {
 866 |     const pageDivider = figma.createPageDivider();
 867 |     if (data.name)
 868 |       pageDivider.name = data.name;
 869 |     return pageDivider;
 870 |   } catch (error) {
 871 |     console.error("Failed to create page divider:", error);
 872 |     return null;
 873 |   }
 874 | }
 875 | function createSlideFromData(data) {
 876 |   if (!("createSlide" in figma)) {
 877 |     console.error("createSlide is not supported in this Figma version");
 878 |     return null;
 879 |   }
 880 |   try {
 881 |     const slide = figma.createSlide();
 882 |     if (data.name)
 883 |       slide.name = data.name;
 884 |     applyCommonProperties(slide, data);
 885 |     return slide;
 886 |   } catch (error) {
 887 |     console.error("Failed to create slide:", error);
 888 |     return null;
 889 |   }
 890 | }
 891 | function createSlideRowFromData(data) {
 892 |   if (!("createSlideRow" in figma)) {
 893 |     console.error("createSlideRow is not supported in this Figma version");
 894 |     return null;
 895 |   }
 896 |   try {
 897 |     const slideRow = figma.createSlideRow();
 898 |     if (data.name)
 899 |       slideRow.name = data.name;
 900 |     applyCommonProperties(slideRow, data);
 901 |     return slideRow;
 902 |   } catch (error) {
 903 |     console.error("Failed to create slide row:", error);
 904 |     return null;
 905 |   }
 906 | }
 907 | 
 908 | // src/plugin/creators/componentCreators.ts
 909 | function createComponentFromNodeData(data) {
 910 |   if (!data.sourceNode) {
 911 |     console.error("createComponentFromNode requires a sourceNode");
 912 |     return null;
 913 |   }
 914 |   try {
 915 |     let sourceNode;
 916 |     if (typeof data.sourceNode === "string") {
 917 |       sourceNode = figma.getNodeById(data.sourceNode);
 918 |       if (!sourceNode || !("type" in sourceNode)) {
 919 |         console.error(`Node with ID ${data.sourceNode} not found or is not a valid node`);
 920 |         return null;
 921 |       }
 922 |     } else {
 923 |       sourceNode = data.sourceNode;
 924 |     }
 925 |     const component = figma.createComponentFromNode(sourceNode);
 926 |     if (data.description)
 927 |       component.description = data.description;
 928 |     applyCommonProperties(component, data);
 929 |     return component;
 930 |   } catch (error) {
 931 |     console.error("Failed to create component from node:", error);
 932 |     return null;
 933 |   }
 934 | }
 935 | function createComponentSetFromData(data) {
 936 |   try {
 937 |     if (!data.components || !Array.isArray(data.components) || data.components.length === 0) {
 938 |       console.error("Component set creation requires component nodes");
 939 |       return null;
 940 |     }
 941 |     const componentNodes = [];
 942 |     for (const component of data.components) {
 943 |       let node;
 944 |       if (typeof component === "string") {
 945 |         node = figma.getNodeById(component);
 946 |       } else {
 947 |         node = component;
 948 |       }
 949 |       if (node && node.type === "COMPONENT") {
 950 |         componentNodes.push(node);
 951 |       }
 952 |     }
 953 |     if (componentNodes.length === 0) {
 954 |       console.error("No valid component nodes provided");
 955 |       return null;
 956 |     }
 957 |     const componentSet = figma.combineAsVariants(componentNodes, figma.currentPage);
 958 |     if (data.name)
 959 |       componentSet.name = data.name;
 960 |     applyCommonProperties(componentSet, data);
 961 |     return componentSet;
 962 |   } catch (error) {
 963 |     console.error("Failed to create component set:", error);
 964 |     return null;
 965 |   }
 966 | }
 967 | 
 968 | // src/plugin/creators/elementCreator.ts
 969 | async function createElementFromData(data) {
 970 |   if (!data || !data.type) {
 971 |     console.error("Invalid element data: missing type");
 972 |     return null;
 973 |   }
 974 |   let element = null;
 975 |   try {
 976 |     switch (data.type.toLowerCase()) {
 977 |       case "rectangle":
 978 |         element = createRectangleFromData(data);
 979 |         break;
 980 |       case "ellipse":
 981 |       case "circle":
 982 |         element = createEllipseFromData(data);
 983 |         break;
 984 |       case "polygon":
 985 |         element = createPolygonFromData(data);
 986 |         break;
 987 |       case "star":
 988 |         element = createStarFromData(data);
 989 |         break;
 990 |       case "line":
 991 |         element = createLineFromData(data);
 992 |         break;
 993 |       case "vector":
 994 |         element = createVectorFromData(data);
 995 |         break;
 996 |       case "frame":
 997 |         element = createFrameFromData(data);
 998 |         break;
 999 |       case "component":
1000 |         element = createComponentFromData(data);
1001 |         break;
1002 |       case "componentfromnode":
1003 |         element = createComponentFromNodeData(data);
1004 |         break;
1005 |       case "componentset":
1006 |         element = createComponentSetFromData(data);
1007 |         break;
1008 |       case "instance":
1009 |         element = createInstanceFromData(data);
1010 |         break;
1011 |       case "section":
1012 |         element = createSectionFromData(data);
1013 |         break;
1014 |       case "text":
1015 |         element = await createTextFromData(data);
1016 |         break;
1017 |       case "boolean":
1018 |       case "booleanoperation":
1019 |         element = createBooleanOperationFromData(data);
1020 |         break;
1021 |       case "connector":
1022 |         element = createConnectorFromData(data);
1023 |         break;
1024 |       case "shapewithtext":
1025 |         element = createShapeWithTextFromData(data);
1026 |         break;
1027 |       case "codeblock":
1028 |         element = createCodeBlockFromData(data);
1029 |         break;
1030 |       case "table":
1031 |         element = createTableFromData(data);
1032 |         break;
1033 |       case "widget":
1034 |         element = createWidgetFromData(data);
1035 |         break;
1036 |       case "media":
1037 |         element = createMediaFromData(data);
1038 |         break;
1039 |       case "image":
1040 |         if (data.bytes || data.file) {
1041 |           element = await createImageFromBytesAsync(data);
1042 |         } else {
1043 |           element = createImageFromData(data);
1044 |         }
1045 |         break;
1046 |       case "gif":
1047 |         element = createGifFromData(data);
1048 |         break;
1049 |       case "video":
1050 |         element = await createVideoFromDataAsync(data);
1051 |         break;
1052 |       case "linkpreview":
1053 |         element = await createLinkPreviewFromDataAsync(data);
1054 |         break;
1055 |       case "slice":
1056 |         element = createSliceFromData(data);
1057 |         break;
1058 |       case "page":
1059 |         const page = createPageFromData(data);
1060 |         console.log(`Created page: ${page.name}`);
1061 |         return null;
1062 |       case "pagedivider":
1063 |         element = createPageDividerFromData(data);
1064 |         break;
1065 |       case "slide":
1066 |         element = createSlideFromData(data);
1067 |         break;
1068 |       case "sliderow":
1069 |         element = createSlideRowFromData(data);
1070 |         break;
1071 |       case "group":
1072 |         if (!data.children || !Array.isArray(data.children) || data.children.length < 1) {
1073 |           console.error("Cannot create group: children array is required");
1074 |           return null;
1075 |         }
1076 |         const childNodes = [];
1077 |         for (const childData of data.children) {
1078 |           const child = await createElementFromData(childData);
1079 |           if (child)
1080 |             childNodes.push(child);
1081 |         }
1082 |         if (childNodes.length > 0) {
1083 |           element = createGroupFromData(data, childNodes);
1084 |         } else {
1085 |           console.error("Cannot create group: no valid children were created");
1086 |           return null;
1087 |         }
1088 |         break;
1089 |       default:
1090 |         console.error(`Unsupported element type: ${data.type}`);
1091 |         return null;
1092 |     }
1093 |     if (element) {
1094 |       applyCommonProperties(element, data);
1095 |       if (data.select !== false) {
1096 |         selectAndFocusNodes(element);
1097 |       }
1098 |     }
1099 |     return element;
1100 |   } catch (error) {
1101 |     console.error(`Error creating element: ${error instanceof Error ? error.message : "Unknown error"}`);
1102 |     return null;
1103 |   }
1104 | }
1105 | async function createElementsFromDataArray(dataArray) {
1106 |   const createdNodes = [];
1107 |   for (const data of dataArray) {
1108 |     const node = await createElementFromData(data);
1109 |     if (node)
1110 |       createdNodes.push(node);
1111 |   }
1112 |   if (createdNodes.length > 0) {
1113 |     selectAndFocusNodes(createdNodes);
1114 |   }
1115 |   return createdNodes;
1116 | }
1117 | 
1118 | // src/plugin/code.ts
1119 | figma.showUI(__html__, { width: 320, height: 500 });
1120 | console.log("Figma MCP Plugin loaded");
1121 | var elementCreators = {
1122 |   "create-rectangle": createRectangleFromData,
1123 |   "create-circle": createEllipseFromData,
1124 |   "create-ellipse": createEllipseFromData,
1125 |   "create-polygon": createPolygonFromData,
1126 |   "create-line": createLineFromData,
1127 |   "create-text": createTextFromData,
1128 |   "create-star": createStarFromData,
1129 |   "create-vector": createVectorFromData,
1130 |   "create-arc": (params) => {
1131 |     const ellipse = createEllipseFromData(params);
1132 |     if (params.arcData || params.startAngle !== undefined && params.endAngle !== undefined) {
1133 |       ellipse.arcData = {
1134 |         startingAngle: params.startAngle || params.arcData.startingAngle || 0,
1135 |         endingAngle: params.endAngle || params.arcData.endingAngle || 360,
1136 |         innerRadius: params.innerRadius || params.arcData.innerRadius || 0
1137 |       };
1138 |     }
1139 |     return ellipse;
1140 |   }
1141 | };
1142 | async function createElement(type, params) {
1143 |   console.log(`Creating ${type} with params:`, params);
1144 |   const creator = elementCreators[type];
1145 |   if (!creator) {
1146 |     console.error(`Unknown element type: ${type}`);
1147 |     return null;
1148 |   }
1149 |   try {
1150 |     const element = await Promise.resolve(creator(params));
1151 |     if (element && params) {
1152 |       if (params.x !== undefined)
1153 |         element.x = params.x;
1154 |       if (params.y !== undefined)
1155 |         element.y = params.y;
1156 |     }
1157 |     if (element) {
1158 |       selectAndFocusNodes(element);
1159 |     }
1160 |     return element;
1161 |   } catch (error) {
1162 |     console.error(`Error creating ${type}:`, error);
1163 |     return null;
1164 |   }
1165 | }
1166 | figma.ui.onmessage = async function(msg) {
1167 |   console.log("Received message from UI:", msg);
1168 |   if (elementCreators[msg.type]) {
1169 |     await createElement(msg.type, msg);
1170 |   } else if (msg.type === "create-element") {
1171 |     console.log("Creating element with data:", msg.data);
1172 |     createElementFromData(msg.data);
1173 |   } else if (msg.type === "create-elements") {
1174 |     console.log("Creating multiple elements with data:", msg.data);
1175 |     createElementsFromDataArray(msg.data);
1176 |   } else if (msg.type === "mcp-command") {
1177 |     console.log("Received MCP command:", msg.command, "with params:", msg.params);
1178 |     handleMcpCommand(msg.command, msg.params);
1179 |   } else if (msg.type === "cancel") {
1180 |     console.log("Closing plugin");
1181 |     figma.closePlugin();
1182 |   } else {
1183 |     console.log("Unknown message type:", msg.type);
1184 |   }
1185 | };
1186 | async function handleMcpCommand(command, params) {
1187 |   let result = null;
1188 |   try {
1189 |     const pluginCommand = command.replace(/_/g, "-");
1190 |     switch (pluginCommand) {
1191 |       case "create-rectangle":
1192 |       case "create-circle":
1193 |       case "create-polygon":
1194 |       case "create-line":
1195 |       case "create-arc":
1196 |       case "create-vector":
1197 |         console.log(`MCP command: Creating ${pluginCommand.substring(7)} with params:`, params);
1198 |         result = await createElement(pluginCommand, params);
1199 |         break;
1200 |       case "create-text":
1201 |         console.log("MCP command: Creating text with params:", params);
1202 |         result = await createElement(pluginCommand, params);
1203 |         break;
1204 |       case "create-element":
1205 |         console.log("MCP command: Creating element with params:", params);
1206 |         result = await createElementFromData(params);
1207 |         break;
1208 |       case "create-elements":
1209 |         console.log("MCP command: Creating multiple elements with params:", params);
1210 |         result = await createElementsFromDataArray(params);
1211 |         break;
1212 |       case "get-selection":
1213 |         console.log("MCP command: Getting current selection");
1214 |         result = figma.currentPage.selection;
1215 |         break;
1216 |       case "get-elements":
1217 |         console.log("MCP command: Getting elements with params:", params);
1218 |         const page = params.page_id ? figma.getNodeById(params.page_id) : figma.currentPage;
1219 |         if (!page || page.type !== "PAGE") {
1220 |           throw new Error("Invalid page ID or node is not a page");
1221 |         }
1222 |         const nodeType = params.type || "ALL";
1223 |         const limit = params.limit || 100;
1224 |         const includeHidden = params.include_hidden || false;
1225 |         if (nodeType === "ALL") {
1226 |           result = includeHidden ? page.children.slice(0, limit) : page.children.filter((node2) => node2.visible).slice(0, limit);
1227 |         } else {
1228 |           result = page.findAll((node2) => {
1229 |             const typeMatch = node2.type === nodeType;
1230 |             const visibilityMatch = includeHidden || node2.visible;
1231 |             return typeMatch && visibilityMatch;
1232 |           }).slice(0, limit);
1233 |         }
1234 |         break;
1235 |       case "get-element":
1236 |         console.log("MCP command: Getting element with ID:", params.node_id);
1237 |         const node = figma.getNodeById(params.node_id);
1238 |         if (!node) {
1239 |           throw new Error("Element not found with ID: " + params.node_id);
1240 |         }
1241 |         if (!["DOCUMENT", "PAGE"].includes(node.type)) {
1242 |           if (params.include_children && "children" in node) {
1243 |             result = [node, ...node.children || []];
1244 |           } else {
1245 |             result = node;
1246 |           }
1247 |         } else if (node.type === "PAGE") {
1248 |           result = node;
1249 |         } else {
1250 |           throw new Error("Unsupported node type: " + node.type);
1251 |         }
1252 |         break;
1253 |       case "get-pages":
1254 |         console.log("MCP command: Getting all pages");
1255 |         result = figma.root.children;
1256 |         break;
1257 |       case "get-page":
1258 |         console.log("MCP command: Getting page with ID:", params.page_id);
1259 |         if (!params.page_id) {
1260 |           console.log("No page_id provided, using current page");
1261 |           result = figma.currentPage;
1262 |         } else {
1263 |           const pageNode = figma.getNodeById(params.page_id);
1264 |           if (!pageNode || pageNode.type !== "PAGE")
1265 |             throw new Error("Invalid page ID or node is not a page");
1266 |           result = pageNode;
1267 |         }
1268 |         break;
1269 |       case "create-page":
1270 |         console.log("MCP command: Creating new page with name:", params.name);
1271 |         const newPage = figma.createPage();
1272 |         newPage.name = params.name || "New Page";
1273 |         result = newPage;
1274 |         break;
1275 |       case "switch-page":
1276 |         console.log("MCP command: Switching to page with ID:", params.id);
1277 |         if (!params.id)
1278 |           throw new Error("Page ID is required");
1279 |         const switchPageNode = figma.getNodeById(params.id);
1280 |         if (!switchPageNode || switchPageNode.type !== "PAGE")
1281 |           throw new Error("Invalid page ID");
1282 |         figma.currentPage = switchPageNode;
1283 |         result = switchPageNode;
1284 |         break;
1285 |       case "modify-rectangle":
1286 |         console.log("MCP command: Modifying rectangle with ID:", params.id);
1287 |         if (!params.id)
1288 |           throw new Error("Rectangle ID is required");
1289 |         const modifyNode = figma.getNodeById(params.id);
1290 |         if (!modifyNode || modifyNode.type !== "RECTANGLE")
1291 |           throw new Error("Invalid rectangle ID");
1292 |         const rect = modifyNode;
1293 |         if (params.x !== undefined)
1294 |           rect.x = params.x;
1295 |         if (params.y !== undefined)
1296 |           rect.y = params.y;
1297 |         if (params.width !== undefined && params.height !== undefined)
1298 |           rect.resize(params.width, params.height);
1299 |         if (params.cornerRadius !== undefined)
1300 |           rect.cornerRadius = params.cornerRadius;
1301 |         if (params.color)
1302 |           rect.fills = [{ type: "SOLID", color: hexToRgb(params.color) }];
1303 |         result = rect;
1304 |         break;
1305 |       default:
1306 |         console.log("Unknown MCP command:", command);
1307 |         throw new Error("Unknown command: " + command);
1308 |     }
1309 |     let resultForBuilder = null;
1310 |     if (result === null) {
1311 |       resultForBuilder = null;
1312 |     } else if (Array.isArray(result)) {
1313 |       resultForBuilder = result;
1314 |     } else if ("type" in result && result.type === "PAGE") {
1315 |       resultForBuilder = result;
1316 |     } else {
1317 |       resultForBuilder = result;
1318 |     }
1319 |     const resultObject = buildResultObject(resultForBuilder);
1320 |     console.log("Command result:", resultObject);
1321 |     figma.ui.postMessage({
1322 |       type: "mcp-response",
1323 |       success: true,
1324 |       command,
1325 |       result: resultObject
1326 |     });
1327 |     console.log("Response sent to UI");
1328 |     return resultObject;
1329 |   } catch (error) {
1330 |     console.error("Error handling MCP command:", error);
1331 |     figma.ui.postMessage({
1332 |       type: "mcp-response",
1333 |       success: false,
1334 |       command,
1335 |       error: error instanceof Error ? error.message : "Unknown error"
1336 |     });
1337 |     console.log("Error response sent to UI");
1338 |     throw error;
1339 |   }
1340 | }
1341 | 
```
Page 3/3FirstPrevNextLast