@drawpro/mcp 0.2.0 → 0.2.1
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 +14 -4
- package/dist/server.js +36 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -85,10 +85,20 @@ containers, and unbound annotation arrows all disappear, and the sheet comes
|
|
|
85
85
|
back as an auto-laid-out graph. Correct content, different diagram.
|
|
86
86
|
|
|
87
87
|
Use `edit_sheet_text` for those sheets. It reads the scene, rewrites only the
|
|
88
|
-
strings you name, and writes every other element back
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
88
|
+
strings you name, and writes every other element back — so coordinates,
|
|
89
|
+
groupings, and annotations are untouched. Edits apply all-or-nothing: if any
|
|
90
|
+
`find` matches nothing, the sheet is left alone rather than half-updated.
|
|
91
|
+
|
|
92
|
+
The one geometry change it makes is growing a box whose text no longer fits.
|
|
93
|
+
Excalidraw regrows a container around text *bound* to it, but hand-drawn
|
|
94
|
+
diagrams usually have text merely sitting on top of a shape, so longer text
|
|
95
|
+
would otherwise spill out the bottom. Boxes are only ever grown, never shrunk,
|
|
96
|
+
and nothing else moves — reflowing neighbours would be the wholesale rewrite
|
|
97
|
+
this tool exists to avoid. Any box it grows is named in the result, so overlap
|
|
98
|
+
with a neighbour is easy to spot and one drag to fix.
|
|
99
|
+
|
|
100
|
+
It cannot add elements. A correction that needs a new box or a new annotation
|
|
101
|
+
line is still a manual edit.
|
|
92
102
|
|
|
93
103
|
`update_diagram` remains the right tool for sheets this package generated, where
|
|
94
104
|
regenerating the layout is the point.
|
package/dist/server.js
CHANGED
|
@@ -1084,8 +1084,33 @@ function validateScene(elements) {
|
|
|
1084
1084
|
return issues;
|
|
1085
1085
|
}
|
|
1086
1086
|
|
|
1087
|
-
// ../diagram/src/
|
|
1087
|
+
// ../diagram/src/fit.ts
|
|
1088
1088
|
var SHAPES = /* @__PURE__ */ new Set(["rectangle", "ellipse", "diamond"]);
|
|
1089
|
+
var PADDING = 16;
|
|
1090
|
+
function growBoxesToFitText(elements, changedTextIds) {
|
|
1091
|
+
const shapes = elements.filter((el) => SHAPES.has(el.type));
|
|
1092
|
+
const resizes = [];
|
|
1093
|
+
for (const text2 of elements) {
|
|
1094
|
+
if (text2.type !== "text" || !changedTextIds.has(text2.id)) continue;
|
|
1095
|
+
if (text2.containerId) continue;
|
|
1096
|
+
const cx = text2.x + text2.width / 2;
|
|
1097
|
+
const cy = text2.y + text2.height / 2;
|
|
1098
|
+
const box = shapes.filter((sh) => cx >= sh.x && cx <= sh.x + sh.width && cy >= sh.y && cy <= sh.y + sh.height).sort((a, b) => a.width * a.height - b.width * b.height)[0];
|
|
1099
|
+
if (!box) continue;
|
|
1100
|
+
const needed = Math.ceil(text2.y - box.y + text2.height + PADDING);
|
|
1101
|
+
if (needed > box.height) {
|
|
1102
|
+
resizes.push({ shapeId: box.id, shapeType: box.type, from: box.height, to: needed });
|
|
1103
|
+
box.height = needed;
|
|
1104
|
+
box.version = (box.version ?? 1) + 1;
|
|
1105
|
+
box.versionNonce = Math.floor(Math.random() * 2 ** 31);
|
|
1106
|
+
box.updated = Date.now();
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
return resizes;
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
// ../diagram/src/describe.ts
|
|
1113
|
+
var SHAPES2 = /* @__PURE__ */ new Set(["rectangle", "ellipse", "diamond"]);
|
|
1089
1114
|
var ARROW_LABEL_RADIUS = 60;
|
|
1090
1115
|
function centre(b) {
|
|
1091
1116
|
return { x: b.x + b.width / 2, y: b.y + b.height / 2 };
|
|
@@ -1126,7 +1151,7 @@ function describeScene(elements) {
|
|
|
1126
1151
|
if (bound) return bound;
|
|
1127
1152
|
const owner = byId.get(id);
|
|
1128
1153
|
if (!owner) return "";
|
|
1129
|
-
if (
|
|
1154
|
+
if (SHAPES2.has(owner.type)) {
|
|
1130
1155
|
const hit = floating.find((t) => !adopted.has(t.id) && contains(owner, centre(t)));
|
|
1131
1156
|
if (hit) {
|
|
1132
1157
|
adopted.add(hit.id);
|
|
@@ -1157,7 +1182,7 @@ function describeScene(elements) {
|
|
|
1157
1182
|
};
|
|
1158
1183
|
const counts = {};
|
|
1159
1184
|
for (const el of elements) counts[el.type] = (counts[el.type] ?? 0) + 1;
|
|
1160
|
-
const shapes = elements.filter((el) =>
|
|
1185
|
+
const shapes = elements.filter((el) => SHAPES2.has(el.type)).map((el) => ({ id: el.id, type: el.type, label: labelFor(el.id) }));
|
|
1161
1186
|
const shapeLabel = new Map(shapes.map((s) => [s.id, s.label]));
|
|
1162
1187
|
const edges = elements.filter((el) => el.type === "arrow").map((el) => {
|
|
1163
1188
|
const start = el.startBinding;
|
|
@@ -1570,6 +1595,7 @@ server.tool(
|
|
|
1570
1595
|
const scene = await api().readSheet(workspace_id, sheet_id, unlocked.key);
|
|
1571
1596
|
const elements = scene.elements;
|
|
1572
1597
|
const counts = /* @__PURE__ */ new Map();
|
|
1598
|
+
const changedTextIds = /* @__PURE__ */ new Set();
|
|
1573
1599
|
for (const el of elements) {
|
|
1574
1600
|
if (el.type !== "text") continue;
|
|
1575
1601
|
const current = (el.originalText ?? el.text)?.trim();
|
|
@@ -1578,14 +1604,18 @@ server.tool(
|
|
|
1578
1604
|
if (!edit) continue;
|
|
1579
1605
|
el.text = edit.replace;
|
|
1580
1606
|
el.originalText = edit.replace;
|
|
1581
|
-
const
|
|
1607
|
+
const fontSize = el.fontSize ?? 20;
|
|
1608
|
+
const wrapWidth = el.containerId ? el.width : 1e3;
|
|
1609
|
+
const metrics = measureText(edit.replace, fontSize, wrapWidth);
|
|
1582
1610
|
el.width = metrics.width;
|
|
1583
1611
|
el.height = metrics.height;
|
|
1612
|
+
changedTextIds.add(el.id);
|
|
1584
1613
|
el.version = (el.version ?? 1) + 1;
|
|
1585
1614
|
el.versionNonce = Math.floor(Math.random() * 2 ** 31);
|
|
1586
1615
|
el.updated = Date.now();
|
|
1587
1616
|
counts.set(edit.find, (counts.get(edit.find) ?? 0) + 1);
|
|
1588
1617
|
}
|
|
1618
|
+
const resizes = growBoxesToFitText(elements, changedTextIds);
|
|
1589
1619
|
const missed = edits.filter((e) => !counts.has(e.find));
|
|
1590
1620
|
if (missed.length > 0) {
|
|
1591
1621
|
return text(
|
|
@@ -1600,9 +1630,10 @@ server.tool(
|
|
|
1600
1630
|
user.publicKey
|
|
1601
1631
|
);
|
|
1602
1632
|
const applied = [...counts.entries()].map(([find, n]) => ` ${JSON.stringify(find)} -> ${n} element${n === 1 ? "" : "s"}`).join("\n");
|
|
1633
|
+
const grewNote = resizes.length ? "\n\nBoxes grown so the new text fits (nothing else moved, so check for overlap):\n" + resizes.map((r) => ` a ${r.shapeType} grew ${Math.round(r.from)}px -> ${Math.round(r.to)}px`).join("\n") : "";
|
|
1603
1634
|
return text(
|
|
1604
1635
|
`Updated "${scene.name}". ${elements.length} elements preserved; only the text below changed.
|
|
1605
|
-
${applied}
|
|
1636
|
+
${applied}${grewNote}
|
|
1606
1637
|
${sheetUrl(workspace_id, sheet_id)}`
|
|
1607
1638
|
);
|
|
1608
1639
|
}
|