Batch insert_icon and search_icons, fetch_icons prefetch tool
This commit is contained in:
parent
733321fbb5
commit
dec0a67e4f
|
|
@ -388,9 +388,10 @@ function configureChildAsLeaf(yogaChild: YogaNode, child: SceneNode, parent: Sce
|
|||
function configureTextLeaf(
|
||||
yogaChild: YogaNode,
|
||||
child: SceneNode,
|
||||
_parent: SceneNode
|
||||
parent: SceneNode
|
||||
): void {
|
||||
const autoResize = child.textAutoResize
|
||||
const isRow = parent.layoutMode === 'HORIZONTAL'
|
||||
|
||||
if (child.layoutGrow > 0) {
|
||||
yogaChild.setFlexGrow(child.layoutGrow)
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import { parseColor } from '../color'
|
||||
import { fetchIcon, fetchIcons, searchIconsBatch } from '../iconify'
|
||||
import { fetchIcons, searchIconsBatch } from '../iconify'
|
||||
import type { SceneNode } from '../scene-graph'
|
||||
|
||||
import { defineTool, nodeSummary } from './schema'
|
||||
|
||||
import type { FigmaNodeProxy } from '../figma-api'
|
||||
import type { FigmaAPI, FigmaNodeProxy } from '../figma-api'
|
||||
import type { Color } from '../types'
|
||||
import type { Stroke, StrokeCap, StrokeJoin } from '../scene-graph'
|
||||
|
||||
export const createShape = defineTool({
|
||||
|
|
@ -229,89 +230,108 @@ export const fetchIconsTool = defineTool({
|
|||
}
|
||||
})
|
||||
|
||||
import type { IconData } from '../iconify'
|
||||
|
||||
function createIconFrame(
|
||||
figma: FigmaAPI,
|
||||
icon: IconData,
|
||||
name: string,
|
||||
size: number,
|
||||
parsedColor: Color,
|
||||
parentId?: string
|
||||
): FigmaNodeProxy {
|
||||
const frame = figma.createFrame()
|
||||
frame.name = `Icon / ${name}`
|
||||
frame.resize(size, size)
|
||||
frame.fills = []
|
||||
|
||||
if (parentId) {
|
||||
const parent = figma.getNodeById(parentId)
|
||||
if (parent) parent.appendChild(frame)
|
||||
}
|
||||
|
||||
for (const path of icon.paths) {
|
||||
const vector = figma.createVector()
|
||||
vector.name = 'path'
|
||||
vector.resize(size, size)
|
||||
frame.appendChild(vector)
|
||||
|
||||
figma.graph.updateNode(vector.id, {
|
||||
vectorNetwork: path.vectorNetwork
|
||||
} as Record<string, unknown>)
|
||||
|
||||
if (path.fill) {
|
||||
vector.fills = [{
|
||||
type: 'SOLID',
|
||||
color: path.fill === 'currentColor' ? parsedColor : parseColor(path.fill),
|
||||
opacity: 1,
|
||||
visible: true
|
||||
}]
|
||||
} else {
|
||||
vector.fills = []
|
||||
}
|
||||
|
||||
if (path.stroke) {
|
||||
const strokeColor = path.stroke === 'currentColor' ? parsedColor : parseColor(path.stroke)
|
||||
const stroke: Stroke = {
|
||||
color: strokeColor,
|
||||
weight: path.strokeWidth,
|
||||
opacity: 1,
|
||||
visible: true,
|
||||
align: 'CENTER',
|
||||
cap: STROKE_CAP_MAP[path.strokeCap] ?? 'NONE',
|
||||
join: STROKE_JOIN_MAP[path.strokeJoin] ?? 'MITER'
|
||||
}
|
||||
vector.strokes = [stroke]
|
||||
}
|
||||
}
|
||||
|
||||
return frame
|
||||
}
|
||||
|
||||
export const insertIcon = defineTool({
|
||||
name: 'insert_icon',
|
||||
mutates: true,
|
||||
description:
|
||||
'Insert a vector icon onto the canvas. If already cached by fetch_icons — instant, no network request. Otherwise fetches on demand.',
|
||||
'Insert one or more vector icons onto the canvas. Pass a single name or multiple names to batch-insert into the same parent. If already cached by fetch_icons — instant, no network request.',
|
||||
params: {
|
||||
name: {
|
||||
type: 'string',
|
||||
description: 'Icon name as prefix:name (e.g. lucide:heart, mdi:home)',
|
||||
names: {
|
||||
type: 'string[]',
|
||||
description: 'Icon names as prefix:name (e.g. ["lucide:heart"] or ["lucide:heart","lucide:home","lucide:star"])',
|
||||
required: true
|
||||
},
|
||||
size: { type: 'number', description: 'Icon size in pixels (default: 24)' },
|
||||
color: { type: 'color', description: 'Icon color hex (replaces currentColor). Default: #000000' },
|
||||
x: { type: 'number', description: 'X position' },
|
||||
y: { type: 'number', description: 'Y position' },
|
||||
parent_id: { type: 'string', description: 'Parent node ID' }
|
||||
parent_id: { type: 'string', description: 'Parent node ID for all icons' }
|
||||
},
|
||||
execute: async (figma, args) => {
|
||||
const size = args.size ?? 24
|
||||
const color = args.color ?? '#000000'
|
||||
const parsedColor = parseColor(color)
|
||||
|
||||
let icon
|
||||
let icons
|
||||
try {
|
||||
icon = await fetchIcon(args.name, size)
|
||||
icons = await fetchIcons(args.names, size)
|
||||
} catch (e) {
|
||||
return { error: (e as Error).message }
|
||||
}
|
||||
|
||||
if (icon.paths.length === 0) {
|
||||
return { error: `Icon "${args.name}" has no path data` }
|
||||
}
|
||||
const inserted: { id: string; name: string; icon: string }[] = []
|
||||
const notFound: string[] = []
|
||||
|
||||
const frame = figma.createFrame()
|
||||
frame.name = `Icon / ${args.name}`
|
||||
frame.resize(size, size)
|
||||
frame.fills = []
|
||||
if (args.x !== undefined) frame.x = args.x
|
||||
if (args.y !== undefined) frame.y = args.y
|
||||
|
||||
if (args.parent_id) {
|
||||
const parent = figma.getNodeById(args.parent_id)
|
||||
if (parent) parent.appendChild(frame)
|
||||
}
|
||||
|
||||
const parsedColor = parseColor(color)
|
||||
|
||||
for (const path of icon.paths) {
|
||||
const vector = figma.createVector()
|
||||
vector.name = 'path'
|
||||
vector.resize(size, size)
|
||||
frame.appendChild(vector)
|
||||
|
||||
figma.graph.updateNode(vector.id, {
|
||||
vectorNetwork: path.vectorNetwork
|
||||
} as Record<string, unknown>)
|
||||
|
||||
if (path.fill) {
|
||||
vector.fills = [{
|
||||
type: 'SOLID',
|
||||
color: path.fill === 'currentColor' ? parsedColor : parseColor(path.fill),
|
||||
opacity: 1,
|
||||
visible: true
|
||||
}]
|
||||
} else {
|
||||
vector.fills = []
|
||||
}
|
||||
|
||||
if (path.stroke) {
|
||||
const strokeColor = path.stroke === 'currentColor' ? parsedColor : parseColor(path.stroke)
|
||||
const stroke: Stroke = {
|
||||
color: strokeColor,
|
||||
weight: path.strokeWidth,
|
||||
opacity: 1,
|
||||
visible: true,
|
||||
align: 'CENTER',
|
||||
cap: STROKE_CAP_MAP[path.strokeCap] ?? 'NONE',
|
||||
join: STROKE_JOIN_MAP[path.strokeJoin] ?? 'MITER'
|
||||
}
|
||||
vector.strokes = [stroke]
|
||||
for (const name of args.names) {
|
||||
const icon = icons.get(name)
|
||||
if (!icon || icon.paths.length === 0) {
|
||||
notFound.push(name)
|
||||
continue
|
||||
}
|
||||
const frame = createIconFrame(figma, icon, name, size, parsedColor, args.parent_id)
|
||||
inserted.push({ id: frame.id, name: frame.name, icon: name })
|
||||
}
|
||||
|
||||
return nodeSummary(frame)
|
||||
const result: Record<string, unknown> = { inserted }
|
||||
if (notFound.length > 0) result.not_found = notFound
|
||||
return result
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -189,21 +189,20 @@ Never place vertical dividers outside their row container. Horizontal dividers (
|
|||
|
||||
## Icons
|
||||
|
||||
Three tools for Iconify icons (200k+ icons):
|
||||
Three tools for Iconify icons (200k+ icons). All batch-friendly — minimize LLM steps:
|
||||
|
||||
- `search_icons` — find icons by keyword. Pass multiple queries at once: `queries=["heart","arrow","settings"]`.
|
||||
- `fetch_icons` — pre-download icons into cache. Batches by prefix (one HTTP request per set). Pass all names you'll need: `names=["lucide:heart","lucide:home","lucide:star"]`.
|
||||
- `insert_icon` — place one icon on the canvas. Instant if already cached by `fetch_icons`.
|
||||
- `fetch_icons` — pre-download icons into cache. Batches by prefix (one HTTP request per set). Pass all names you'll need.
|
||||
- `insert_icon` — place icons on the canvas. Pass one or many `names`. When icons share the same parent/color/size, batch them: `names=["lucide:heart","lucide:home","lucide:star"]`. Instant if cached by `fetch_icons`.
|
||||
|
||||
**Workflow:** search → fetch batch → insert one by one as you build.
|
||||
**Workflow:** search → fetch → insert. Each step handles multiple icons in one call.
|
||||
|
||||
```
|
||||
1. search_icons(queries=["heart","home","settings","star"])
|
||||
2. fetch_icons(names=["lucide:heart","lucide:home","lucide:settings","lucide:star"], size=20)
|
||||
3. render skeleton...
|
||||
4. insert_icon(name="lucide:heart", parent_id=..., color="#FFF")
|
||||
5. insert_icon(name="lucide:home", parent_id=..., color="#FFF")
|
||||
... (each insert_icon is instant — no network wait)
|
||||
3. render skeleton with empty containers...
|
||||
4. insert_icon(names=["lucide:heart","lucide:home","lucide:settings"], parent_id=navId, color="#FFF")
|
||||
5. insert_icon(names=["lucide:star"], parent_id=footerId, color="#999")
|
||||
```
|
||||
|
||||
**Popular icon sets:**
|
||||
|
|
|
|||
Loading…
Reference in a new issue