@ttsalpha/mirra 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -17
- package/dist/index.js +40913 -577
- package/dist/plugin/main.js +57 -19
- package/package.json +6 -7
package/dist/plugin/main.js
CHANGED
|
@@ -837,6 +837,11 @@
|
|
|
837
837
|
// src/ops/pages.ts
|
|
838
838
|
async function pages(params) {
|
|
839
839
|
await figma.loadAllPagesAsync();
|
|
840
|
+
if (params.create) {
|
|
841
|
+
const page = figma.createPage();
|
|
842
|
+
page.name = params.create;
|
|
843
|
+
await figma.setCurrentPageAsync(page);
|
|
844
|
+
}
|
|
840
845
|
if (params.select) {
|
|
841
846
|
const wanted = figma.root.children.find(
|
|
842
847
|
(page) => page.id === params.select || page.name === params.select
|
|
@@ -895,6 +900,18 @@
|
|
|
895
900
|
touched.push(created);
|
|
896
901
|
break;
|
|
897
902
|
}
|
|
903
|
+
case "component": {
|
|
904
|
+
const key = node.getPluginData(KEY_FIELD);
|
|
905
|
+
const component = figma.createComponentFromNode(node);
|
|
906
|
+
component.name = op.name ?? node.name;
|
|
907
|
+
if (key) {
|
|
908
|
+
component.setPluginData(KEY_FIELD, key);
|
|
909
|
+
keys[key] = component.id;
|
|
910
|
+
}
|
|
911
|
+
index.invalidate();
|
|
912
|
+
touched.push(component);
|
|
913
|
+
break;
|
|
914
|
+
}
|
|
898
915
|
case "move": {
|
|
899
916
|
const parent = op.parent ? await index.resolve(op.parent) : node.parent;
|
|
900
917
|
if (!parent || !("insertChild" in parent)) {
|
|
@@ -932,30 +949,39 @@
|
|
|
932
949
|
return node;
|
|
933
950
|
}
|
|
934
951
|
|
|
952
|
+
// src/version.ts
|
|
953
|
+
var PLUGIN_VERSION = "1.2.0";
|
|
954
|
+
|
|
935
955
|
// src/ops/ping.ts
|
|
936
956
|
function ping() {
|
|
937
957
|
return {
|
|
938
958
|
fileName: figma.root.name,
|
|
939
959
|
fileKey: readFileKey(),
|
|
940
960
|
currentPage: figma.currentPage.name,
|
|
941
|
-
selectionCount: figma.currentPage.selection.length
|
|
961
|
+
selectionCount: figma.currentPage.selection.length,
|
|
962
|
+
pluginVersion: PLUGIN_VERSION
|
|
942
963
|
};
|
|
943
964
|
}
|
|
944
965
|
|
|
945
966
|
// src/figma/read.ts
|
|
946
|
-
function readNode(node, depth, names = EMPTY_NAMES) {
|
|
947
|
-
const
|
|
948
|
-
const tree = toSpec(node, depth,
|
|
949
|
-
return { tree, skipped:
|
|
950
|
-
}
|
|
951
|
-
function toSpec(node, depth,
|
|
967
|
+
function readNode(node, depth, names = EMPTY_NAMES, limit = Number.MAX_SAFE_INTEGER) {
|
|
968
|
+
const budget = { count: 0, left: limit };
|
|
969
|
+
const tree = toSpec(node, depth, budget, names);
|
|
970
|
+
return { tree, skipped: budget.count };
|
|
971
|
+
}
|
|
972
|
+
function toSpec(node, depth, budget, names) {
|
|
973
|
+
if (budget.left <= 0) {
|
|
974
|
+
budget.count += 1;
|
|
975
|
+
return null;
|
|
976
|
+
}
|
|
977
|
+
budget.left -= 1;
|
|
952
978
|
const svg = node.getPluginData(SVG_FIELD);
|
|
953
979
|
if (svg) return { type: "svg", ...common(node), src: svg };
|
|
954
980
|
const src = node.getPluginData(SRC_FIELD);
|
|
955
981
|
if (src) return { type: "image", ...common(node), ...painted(node, names), src };
|
|
956
982
|
switch (node.type) {
|
|
957
983
|
case "FRAME":
|
|
958
|
-
return frameSpec(node, depth,
|
|
984
|
+
return frameSpec(node, depth, budget, names);
|
|
959
985
|
case "TEXT":
|
|
960
986
|
return textSpec(node, names);
|
|
961
987
|
case "RECTANGLE":
|
|
@@ -965,19 +991,19 @@
|
|
|
965
991
|
case "INSTANCE":
|
|
966
992
|
return instanceSpec(node);
|
|
967
993
|
default:
|
|
968
|
-
|
|
994
|
+
budget.count += 1;
|
|
969
995
|
return null;
|
|
970
996
|
}
|
|
971
997
|
}
|
|
972
|
-
function frameSpec(node, depth,
|
|
998
|
+
function frameSpec(node, depth, budget, names) {
|
|
973
999
|
const children = [];
|
|
974
1000
|
if (depth > 0) {
|
|
975
1001
|
for (const child of node.children) {
|
|
976
|
-
const spec = toSpec(child, depth - 1,
|
|
1002
|
+
const spec = toSpec(child, depth - 1, budget, names);
|
|
977
1003
|
if (spec) children.push(spec);
|
|
978
1004
|
}
|
|
979
1005
|
} else if (node.children.length) {
|
|
980
|
-
|
|
1006
|
+
budget.count += node.children.length;
|
|
981
1007
|
}
|
|
982
1008
|
return {
|
|
983
1009
|
type: "frame",
|
|
@@ -1134,6 +1160,7 @@
|
|
|
1134
1160
|
|
|
1135
1161
|
// src/ops/read.ts
|
|
1136
1162
|
var DEFAULT_DEPTH = 6;
|
|
1163
|
+
var DEFAULT_LIMIT2 = 200;
|
|
1137
1164
|
async function read(params) {
|
|
1138
1165
|
const targets = params.nodeId ? [await resolve(params.nodeId)] : [...figma.currentPage.selection];
|
|
1139
1166
|
if (!targets.length) {
|
|
@@ -1143,10 +1170,14 @@
|
|
|
1143
1170
|
const nodes = [];
|
|
1144
1171
|
let skipped = 0;
|
|
1145
1172
|
for (const target of targets) {
|
|
1146
|
-
const outcome = readNode(
|
|
1173
|
+
const outcome = readNode(
|
|
1174
|
+
target,
|
|
1175
|
+
params.depth ?? DEFAULT_DEPTH,
|
|
1176
|
+
names,
|
|
1177
|
+
params.limit ?? DEFAULT_LIMIT2
|
|
1178
|
+
);
|
|
1147
1179
|
skipped += outcome.skipped;
|
|
1148
1180
|
if (outcome.tree) nodes.push({ id: target.id, tree: outcome.tree });
|
|
1149
|
-
else skipped += 1;
|
|
1150
1181
|
}
|
|
1151
1182
|
return { nodes, skipped };
|
|
1152
1183
|
}
|
|
@@ -1194,21 +1225,29 @@
|
|
|
1194
1225
|
}
|
|
1195
1226
|
|
|
1196
1227
|
// src/ops/screenshot.ts
|
|
1228
|
+
var DEFAULT_MAX_EDGE = 1200;
|
|
1229
|
+
function fittingScale(wanted, box, maxEdge) {
|
|
1230
|
+
const longest = Math.max(box.width, box.height) * wanted;
|
|
1231
|
+
if (longest <= maxEdge) return wanted;
|
|
1232
|
+
return Math.max(0.01, wanted * maxEdge / longest);
|
|
1233
|
+
}
|
|
1197
1234
|
async function screenshot(params) {
|
|
1198
1235
|
const node = params.target ? await new NodeIndex().resolve(params.target) : figma.currentPage.selection[0];
|
|
1199
1236
|
if (!node) {
|
|
1200
1237
|
throw new Error("Nothing is selected: select a layer in Figma, or pass a target");
|
|
1201
1238
|
}
|
|
1239
|
+
const box = node;
|
|
1240
|
+
const scale = fittingScale(params.scale ?? 1, box, params.maxEdge ?? DEFAULT_MAX_EDGE);
|
|
1202
1241
|
const bytes = await node.exportAsync({
|
|
1203
1242
|
format: "PNG",
|
|
1204
|
-
constraint: { type: "SCALE", value:
|
|
1243
|
+
constraint: { type: "SCALE", value: scale }
|
|
1205
1244
|
});
|
|
1206
|
-
const box = node;
|
|
1207
1245
|
return {
|
|
1208
1246
|
id: node.id,
|
|
1209
1247
|
name: node.name,
|
|
1210
|
-
width: Math.round(box.width),
|
|
1211
|
-
height: Math.round(box.height),
|
|
1248
|
+
width: Math.round(box.width * scale),
|
|
1249
|
+
height: Math.round(box.height * scale),
|
|
1250
|
+
scale,
|
|
1212
1251
|
png: figma.base64Encode(bytes)
|
|
1213
1252
|
};
|
|
1214
1253
|
}
|
|
@@ -1252,7 +1291,6 @@
|
|
|
1252
1291
|
}
|
|
1253
1292
|
|
|
1254
1293
|
// src/main.ts
|
|
1255
|
-
var PLUGIN_VERSION = "1.0.0";
|
|
1256
1294
|
figma.showUI(__html__, { width: 300, height: 300, themeColors: true });
|
|
1257
1295
|
var queue = Promise.resolve();
|
|
1258
1296
|
figma.ui.onmessage = (message2) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ttsalpha/mirra",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Two-way MCP server for Figma: read a design and render one back through the Figma Plugin API",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"figma",
|
|
@@ -39,17 +39,16 @@
|
|
|
39
39
|
"prepublishOnly": "pnpm -w run build",
|
|
40
40
|
"build:mcpb": "node scripts/build-mcpb.mjs"
|
|
41
41
|
},
|
|
42
|
-
"dependencies": {
|
|
43
|
-
"@modelcontextprotocol/sdk": "1.32.1",
|
|
44
|
-
"ws": "8.22.0",
|
|
45
|
-
"zod": "4.6.7"
|
|
46
|
-
},
|
|
42
|
+
"dependencies": {},
|
|
47
43
|
"devDependencies": {
|
|
48
44
|
"@mirra/plugin": "workspace:*",
|
|
49
45
|
"@mirra/schema": "workspace:*",
|
|
46
|
+
"@modelcontextprotocol/sdk": "1.32.1",
|
|
50
47
|
"@types/node": "24.19.2",
|
|
51
48
|
"@types/ws": "8.18.2",
|
|
52
|
-
"esbuild": "0.25.12"
|
|
49
|
+
"esbuild": "0.25.12",
|
|
50
|
+
"ws": "8.22.0",
|
|
51
|
+
"zod": "4.6.7"
|
|
53
52
|
},
|
|
54
53
|
"publishConfig": {
|
|
55
54
|
"access": "public"
|