@wz927/codedesign 0.2.0 → 0.2.2
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/dist/main.js +1 -1
- package/figma-plugin/dist/code.js +363 -0
- package/package.json +3 -2
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
/// <reference types="@figma/plugin-typings" />
|
|
2
|
+
/**
|
|
3
|
+
* codedesign Figma Plugin — sandbox side.
|
|
4
|
+
*
|
|
5
|
+
* The sandbox cannot open WebSockets directly; ui.html does that, and we
|
|
6
|
+
* relay messages via figma.ui.postMessage / figma.ui.onmessage.
|
|
7
|
+
*
|
|
8
|
+
* Incoming messages from CLI (via UI): { id, op, params }
|
|
9
|
+
* Outgoing responses to CLI (via UI): { id, ok, data?, error? }
|
|
10
|
+
*/
|
|
11
|
+
const PLUGIN_VERSION = '0.1.0';
|
|
12
|
+
figma.showUI(__html__, { width: 360, height: 360, themeColors: true });
|
|
13
|
+
function send(msg) {
|
|
14
|
+
figma.ui.postMessage({ __kind: 'to_cli', msg });
|
|
15
|
+
}
|
|
16
|
+
async function sendHello() {
|
|
17
|
+
var _a;
|
|
18
|
+
const page = figma.currentPage;
|
|
19
|
+
send({
|
|
20
|
+
id: 'hello-' + Date.now(),
|
|
21
|
+
op: 'hello',
|
|
22
|
+
params: {
|
|
23
|
+
fileKey: (_a = figma.fileKey) !== null && _a !== void 0 ? _a : 'unknown',
|
|
24
|
+
fileName: figma.root.name,
|
|
25
|
+
pageId: page.id,
|
|
26
|
+
pageName: page.name,
|
|
27
|
+
pluginVersion: PLUGIN_VERSION,
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
figma.ui.onmessage = async (m) => {
|
|
32
|
+
var _a;
|
|
33
|
+
if (!m)
|
|
34
|
+
return;
|
|
35
|
+
if (m.__kind === 'ws_open') {
|
|
36
|
+
sendHello();
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (m.__kind === 'ws_close')
|
|
40
|
+
return;
|
|
41
|
+
if (m.__kind === 'from_cli' && m.msg) {
|
|
42
|
+
const req = m.msg;
|
|
43
|
+
try {
|
|
44
|
+
const data = await handle(req);
|
|
45
|
+
const res = { id: req.id, ok: true, data };
|
|
46
|
+
send(res);
|
|
47
|
+
}
|
|
48
|
+
catch (e) {
|
|
49
|
+
const res = { id: req.id, ok: false, error: (_a = e === null || e === void 0 ? void 0 : e.message) !== null && _a !== void 0 ? _a : String(e) };
|
|
50
|
+
send(res);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────
|
|
55
|
+
function toPaint(p) {
|
|
56
|
+
var _a, _b;
|
|
57
|
+
if (!p)
|
|
58
|
+
throw new Error('Empty paint');
|
|
59
|
+
if (p.type === 'SOLID') {
|
|
60
|
+
return { type: 'SOLID', color: { r: p.color.r, g: p.color.g, b: p.color.b }, opacity: (_a = p.color.a) !== null && _a !== void 0 ? _a : 1 };
|
|
61
|
+
}
|
|
62
|
+
if (p.type === 'GRADIENT_LINEAR') {
|
|
63
|
+
const stops = ((_b = p.stops) !== null && _b !== void 0 ? _b : []).map((s) => {
|
|
64
|
+
var _a;
|
|
65
|
+
return ({
|
|
66
|
+
position: s.position,
|
|
67
|
+
color: { r: s.color.r, g: s.color.g, b: s.color.b, a: (_a = s.color.a) !== null && _a !== void 0 ? _a : 1 },
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
return {
|
|
71
|
+
type: 'GRADIENT_LINEAR',
|
|
72
|
+
gradientStops: stops,
|
|
73
|
+
gradientTransform: [[1, 0, 0], [0, 1, 0]],
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
throw new Error('Unsupported paint type ' + p.type);
|
|
77
|
+
}
|
|
78
|
+
function getParent(id) {
|
|
79
|
+
if (!id)
|
|
80
|
+
return figma.currentPage;
|
|
81
|
+
const n = figma.getNodeById(id);
|
|
82
|
+
if (!n)
|
|
83
|
+
throw new Error('Parent not found: ' + id);
|
|
84
|
+
if (!('appendChild' in n))
|
|
85
|
+
throw new Error('Node cannot have children: ' + id);
|
|
86
|
+
return n;
|
|
87
|
+
}
|
|
88
|
+
function applyFrameProps(node, p) {
|
|
89
|
+
if (typeof p.x === 'number')
|
|
90
|
+
node.x = p.x;
|
|
91
|
+
if (typeof p.y === 'number')
|
|
92
|
+
node.y = p.y;
|
|
93
|
+
if (typeof p.width === 'number' && typeof p.height === 'number')
|
|
94
|
+
node.resize(p.width, p.height);
|
|
95
|
+
if (typeof p.cornerRadius === 'number')
|
|
96
|
+
node.cornerRadius = p.cornerRadius;
|
|
97
|
+
if (p.fills)
|
|
98
|
+
node.fills = p.fills.map(toPaint);
|
|
99
|
+
if (p.strokes)
|
|
100
|
+
node.strokes = p.strokes.map(toPaint);
|
|
101
|
+
if (typeof p.strokeWeight === 'number')
|
|
102
|
+
node.strokeWeight = p.strokeWeight;
|
|
103
|
+
if (p.layoutMode)
|
|
104
|
+
node.layoutMode = p.layoutMode;
|
|
105
|
+
if (typeof p.itemSpacing === 'number')
|
|
106
|
+
node.itemSpacing = p.itemSpacing;
|
|
107
|
+
if (typeof p.paddingLeft === 'number')
|
|
108
|
+
node.paddingLeft = p.paddingLeft;
|
|
109
|
+
if (typeof p.paddingRight === 'number')
|
|
110
|
+
node.paddingRight = p.paddingRight;
|
|
111
|
+
if (typeof p.paddingTop === 'number')
|
|
112
|
+
node.paddingTop = p.paddingTop;
|
|
113
|
+
if (typeof p.paddingBottom === 'number')
|
|
114
|
+
node.paddingBottom = p.paddingBottom;
|
|
115
|
+
if (p.primaryAxisAlignItems)
|
|
116
|
+
node.primaryAxisAlignItems = p.primaryAxisAlignItems;
|
|
117
|
+
if (p.counterAxisAlignItems)
|
|
118
|
+
node.counterAxisAlignItems = p.counterAxisAlignItems;
|
|
119
|
+
if (typeof p.clipsContent === 'boolean')
|
|
120
|
+
node.clipsContent = p.clipsContent;
|
|
121
|
+
if (typeof p.name === 'string')
|
|
122
|
+
node.name = p.name;
|
|
123
|
+
}
|
|
124
|
+
function nodeSummary(n) {
|
|
125
|
+
const base = { id: n.id, name: n.name, type: n.type };
|
|
126
|
+
if ('x' in n)
|
|
127
|
+
base.x = n.x;
|
|
128
|
+
if ('y' in n)
|
|
129
|
+
base.y = n.y;
|
|
130
|
+
if ('width' in n)
|
|
131
|
+
base.width = n.width;
|
|
132
|
+
if ('height' in n)
|
|
133
|
+
base.height = n.height;
|
|
134
|
+
if ('children' in n)
|
|
135
|
+
base.childCount = n.children.length;
|
|
136
|
+
return base;
|
|
137
|
+
}
|
|
138
|
+
async function loadFontFor(props) {
|
|
139
|
+
var _a, _b;
|
|
140
|
+
const family = (_a = props.fontFamily) !== null && _a !== void 0 ? _a : 'Inter';
|
|
141
|
+
const style = (_b = props.fontStyle) !== null && _b !== void 0 ? _b : 'Regular';
|
|
142
|
+
await figma.loadFontAsync({ family, style });
|
|
143
|
+
return { family, style };
|
|
144
|
+
}
|
|
145
|
+
// ─── Ops ─────────────────────────────────────────────────────────────────
|
|
146
|
+
async function handle(req) {
|
|
147
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
148
|
+
const p = (_a = req.params) !== null && _a !== void 0 ? _a : {};
|
|
149
|
+
switch (req.op) {
|
|
150
|
+
case 'getCurrentPage':
|
|
151
|
+
return { id: figma.currentPage.id, name: figma.currentPage.name };
|
|
152
|
+
case 'getSelection':
|
|
153
|
+
return figma.currentPage.selection.map(nodeSummary);
|
|
154
|
+
case 'getNode': {
|
|
155
|
+
const n = figma.getNodeById(p.id);
|
|
156
|
+
if (!n)
|
|
157
|
+
throw new Error('Not found: ' + p.id);
|
|
158
|
+
const base = nodeSummary(n);
|
|
159
|
+
if ('children' in n)
|
|
160
|
+
base.children = n.children.map(nodeSummary);
|
|
161
|
+
if ('fills' in n)
|
|
162
|
+
base.fills = n.fills;
|
|
163
|
+
if ('characters' in n)
|
|
164
|
+
base.characters = n.characters;
|
|
165
|
+
return base;
|
|
166
|
+
}
|
|
167
|
+
case 'createFrame': {
|
|
168
|
+
const frame = figma.createFrame();
|
|
169
|
+
getParent(p.parentId).appendChild(frame);
|
|
170
|
+
applyFrameProps(frame, p);
|
|
171
|
+
return nodeSummary(frame);
|
|
172
|
+
}
|
|
173
|
+
case 'createText': {
|
|
174
|
+
const font = await loadFontFor(p);
|
|
175
|
+
const t = figma.createText();
|
|
176
|
+
getParent(p.parentId).appendChild(t);
|
|
177
|
+
t.fontName = { family: font.family, style: font.style };
|
|
178
|
+
t.characters = (_b = p.characters) !== null && _b !== void 0 ? _b : '';
|
|
179
|
+
if (typeof p.x === 'number')
|
|
180
|
+
t.x = p.x;
|
|
181
|
+
if (typeof p.y === 'number')
|
|
182
|
+
t.y = p.y;
|
|
183
|
+
if (typeof p.fontSize === 'number')
|
|
184
|
+
t.fontSize = p.fontSize;
|
|
185
|
+
if (typeof p.lineHeight === 'number')
|
|
186
|
+
t.lineHeight = { unit: 'PIXELS', value: p.lineHeight };
|
|
187
|
+
if (typeof p.letterSpacing === 'number')
|
|
188
|
+
t.letterSpacing = { unit: 'PIXELS', value: p.letterSpacing };
|
|
189
|
+
if (p.textAlignHorizontal)
|
|
190
|
+
t.textAlignHorizontal = p.textAlignHorizontal;
|
|
191
|
+
if (p.fills)
|
|
192
|
+
t.fills = p.fills.map(toPaint);
|
|
193
|
+
if (typeof p.width === 'number')
|
|
194
|
+
t.resize(p.width, t.height);
|
|
195
|
+
if (p.autoResize)
|
|
196
|
+
t.textAutoResize = p.autoResize;
|
|
197
|
+
if (typeof p.name === 'string')
|
|
198
|
+
t.name = p.name;
|
|
199
|
+
return nodeSummary(t);
|
|
200
|
+
}
|
|
201
|
+
case 'createRectangle': {
|
|
202
|
+
const r = figma.createRectangle();
|
|
203
|
+
getParent(p.parentId).appendChild(r);
|
|
204
|
+
if (typeof p.x === 'number')
|
|
205
|
+
r.x = p.x;
|
|
206
|
+
if (typeof p.y === 'number')
|
|
207
|
+
r.y = p.y;
|
|
208
|
+
r.resize(p.width, p.height);
|
|
209
|
+
if (typeof p.cornerRadius === 'number')
|
|
210
|
+
r.cornerRadius = p.cornerRadius;
|
|
211
|
+
if (p.fills)
|
|
212
|
+
r.fills = p.fills.map(toPaint);
|
|
213
|
+
if (p.strokes)
|
|
214
|
+
r.strokes = p.strokes.map(toPaint);
|
|
215
|
+
if (typeof p.strokeWeight === 'number')
|
|
216
|
+
r.strokeWeight = p.strokeWeight;
|
|
217
|
+
if (typeof p.name === 'string')
|
|
218
|
+
r.name = p.name;
|
|
219
|
+
return nodeSummary(r);
|
|
220
|
+
}
|
|
221
|
+
case 'createEllipse': {
|
|
222
|
+
const e = figma.createEllipse();
|
|
223
|
+
getParent(p.parentId).appendChild(e);
|
|
224
|
+
if (typeof p.x === 'number')
|
|
225
|
+
e.x = p.x;
|
|
226
|
+
if (typeof p.y === 'number')
|
|
227
|
+
e.y = p.y;
|
|
228
|
+
e.resize(p.width, p.height);
|
|
229
|
+
if (p.fills)
|
|
230
|
+
e.fills = p.fills.map(toPaint);
|
|
231
|
+
if (p.strokes)
|
|
232
|
+
e.strokes = p.strokes.map(toPaint);
|
|
233
|
+
if (typeof p.strokeWeight === 'number')
|
|
234
|
+
e.strokeWeight = p.strokeWeight;
|
|
235
|
+
if (typeof p.name === 'string')
|
|
236
|
+
e.name = p.name;
|
|
237
|
+
return nodeSummary(e);
|
|
238
|
+
}
|
|
239
|
+
case 'setProps': {
|
|
240
|
+
const n = figma.getNodeById(p.id);
|
|
241
|
+
if (!n)
|
|
242
|
+
throw new Error('Not found: ' + p.id);
|
|
243
|
+
const patch = (_c = p.patch) !== null && _c !== void 0 ? _c : {};
|
|
244
|
+
if ('characters' in n && typeof patch.characters === 'string') {
|
|
245
|
+
await figma.loadFontAsync(n.fontName);
|
|
246
|
+
n.characters = patch.characters;
|
|
247
|
+
}
|
|
248
|
+
if ('resize' in n && (typeof patch.width === 'number' || typeof patch.height === 'number')) {
|
|
249
|
+
n.resize((_d = patch.width) !== null && _d !== void 0 ? _d : n.width, (_e = patch.height) !== null && _e !== void 0 ? _e : n.height);
|
|
250
|
+
}
|
|
251
|
+
for (const k of ['x', 'y', 'cornerRadius', 'opacity', 'rotation', 'visible', 'name', 'itemSpacing',
|
|
252
|
+
'paddingLeft', 'paddingRight', 'paddingTop', 'paddingBottom', 'strokeWeight', 'layoutMode',
|
|
253
|
+
'primaryAxisAlignItems', 'counterAxisAlignItems', 'clipsContent', 'textAlignHorizontal', 'fontSize']) {
|
|
254
|
+
if (patch[k] !== undefined)
|
|
255
|
+
n[k] = patch[k];
|
|
256
|
+
}
|
|
257
|
+
if (patch.fills)
|
|
258
|
+
n.fills = patch.fills.map(toPaint);
|
|
259
|
+
if (patch.strokes)
|
|
260
|
+
n.strokes = patch.strokes.map(toPaint);
|
|
261
|
+
return nodeSummary(n);
|
|
262
|
+
}
|
|
263
|
+
case 'setAutoLayout': {
|
|
264
|
+
const n = figma.getNodeById(p.id);
|
|
265
|
+
if (!n)
|
|
266
|
+
throw new Error('Not found: ' + p.id);
|
|
267
|
+
n.layoutMode = p.layoutMode;
|
|
268
|
+
if (typeof p.itemSpacing === 'number')
|
|
269
|
+
n.itemSpacing = p.itemSpacing;
|
|
270
|
+
if (p.padding) {
|
|
271
|
+
n.paddingLeft = p.padding.left;
|
|
272
|
+
n.paddingRight = p.padding.right;
|
|
273
|
+
n.paddingTop = p.padding.top;
|
|
274
|
+
n.paddingBottom = p.padding.bottom;
|
|
275
|
+
}
|
|
276
|
+
if (p.primaryAxisAlignItems)
|
|
277
|
+
n.primaryAxisAlignItems = p.primaryAxisAlignItems;
|
|
278
|
+
if (p.counterAxisAlignItems)
|
|
279
|
+
n.counterAxisAlignItems = p.counterAxisAlignItems;
|
|
280
|
+
return nodeSummary(n);
|
|
281
|
+
}
|
|
282
|
+
case 'appendChild': {
|
|
283
|
+
const par = getParent(p.parentId);
|
|
284
|
+
const child = figma.getNodeById(p.childId);
|
|
285
|
+
if (!child)
|
|
286
|
+
throw new Error('Child not found');
|
|
287
|
+
par.appendChild(child);
|
|
288
|
+
return nodeSummary(child);
|
|
289
|
+
}
|
|
290
|
+
case 'deleteNode': {
|
|
291
|
+
const n = figma.getNodeById(p.id);
|
|
292
|
+
if (!n)
|
|
293
|
+
throw new Error('Not found');
|
|
294
|
+
n.remove();
|
|
295
|
+
return { deleted: p.id };
|
|
296
|
+
}
|
|
297
|
+
case 'cloneNode': {
|
|
298
|
+
const n = figma.getNodeById(p.id);
|
|
299
|
+
if (!n || !('clone' in n))
|
|
300
|
+
throw new Error('Cannot clone');
|
|
301
|
+
const c = n.clone();
|
|
302
|
+
if (p.parentId)
|
|
303
|
+
getParent(p.parentId).appendChild(c);
|
|
304
|
+
else
|
|
305
|
+
(_f = n.parent) === null || _f === void 0 ? void 0 : _f.appendChild(c);
|
|
306
|
+
if (typeof p.dx === 'number')
|
|
307
|
+
c.x += p.dx;
|
|
308
|
+
if (typeof p.dy === 'number')
|
|
309
|
+
c.y += p.dy;
|
|
310
|
+
return nodeSummary(c);
|
|
311
|
+
}
|
|
312
|
+
case 'listStyles':
|
|
313
|
+
return {
|
|
314
|
+
paint: figma.getLocalPaintStyles().map((s) => ({ id: s.id, key: s.key, name: s.name })),
|
|
315
|
+
text: figma.getLocalTextStyles().map((s) => ({ id: s.id, key: s.key, name: s.name })),
|
|
316
|
+
effect: figma.getLocalEffectStyles().map((s) => ({ id: s.id, key: s.key, name: s.name })),
|
|
317
|
+
};
|
|
318
|
+
case 'listComponents':
|
|
319
|
+
return figma.root.findAllWithCriteria({ types: ['COMPONENT', 'COMPONENT_SET'] }).map((c) => ({
|
|
320
|
+
id: c.id, key: c.key, name: c.name, type: c.type,
|
|
321
|
+
}));
|
|
322
|
+
case 'createInstance': {
|
|
323
|
+
const all = figma.root.findAllWithCriteria({ types: ['COMPONENT'] });
|
|
324
|
+
const comp = all.find((c) => c.key === p.componentKey || c.id === p.componentKey);
|
|
325
|
+
if (!comp)
|
|
326
|
+
throw new Error('Component not found: ' + p.componentKey);
|
|
327
|
+
const inst = comp.createInstance();
|
|
328
|
+
getParent(p.parentId).appendChild(inst);
|
|
329
|
+
if (typeof p.x === 'number')
|
|
330
|
+
inst.x = p.x;
|
|
331
|
+
if (typeof p.y === 'number')
|
|
332
|
+
inst.y = p.y;
|
|
333
|
+
return nodeSummary(inst);
|
|
334
|
+
}
|
|
335
|
+
case 'viewport': {
|
|
336
|
+
const n = figma.getNodeById(p.id);
|
|
337
|
+
if (!n)
|
|
338
|
+
throw new Error('Not found');
|
|
339
|
+
figma.viewport.scrollAndZoomIntoView([n]);
|
|
340
|
+
if (typeof p.zoom === 'number')
|
|
341
|
+
figma.viewport.zoom = p.zoom;
|
|
342
|
+
return { ok: true };
|
|
343
|
+
}
|
|
344
|
+
case 'exportNode': {
|
|
345
|
+
const n = figma.getNodeById(p.id);
|
|
346
|
+
if (!n || !('exportAsync' in n))
|
|
347
|
+
throw new Error('Node not exportable');
|
|
348
|
+
const format = ((_g = p.format) !== null && _g !== void 0 ? _g : 'PNG').toUpperCase();
|
|
349
|
+
const settings = { format };
|
|
350
|
+
if (format === 'PNG' || format === 'JPG')
|
|
351
|
+
settings.constraint = { type: 'SCALE', value: (_h = p.scale) !== null && _h !== void 0 ? _h : 2 };
|
|
352
|
+
const bytes = await n.exportAsync(settings);
|
|
353
|
+
const b64 = figma.base64Encode(bytes);
|
|
354
|
+
return { bytesBase64: b64, format };
|
|
355
|
+
}
|
|
356
|
+
case 'notify':
|
|
357
|
+
figma.notify(p.message, { error: !!p.error });
|
|
358
|
+
return { ok: true };
|
|
359
|
+
default:
|
|
360
|
+
throw new Error('Unknown op: ' + req.op);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
// Keep the plugin alive; user closes it manually.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wz927/codedesign",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Terminal AI agent for designers — drive Figma through natural-language conversation.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
"files": [
|
|
10
10
|
"dist/main.js",
|
|
11
11
|
"figma-plugin/manifest.json",
|
|
12
|
-
"figma-plugin/ui.html"
|
|
12
|
+
"figma-plugin/ui.html",
|
|
13
|
+
"figma-plugin/dist/code.js"
|
|
13
14
|
],
|
|
14
15
|
"scripts": {
|
|
15
16
|
"dev": "bun run src/main.tsx",
|