// Claude Code Bridge - Main Plugin Code
// Receives commands from UI and executes them via Figma API
figma.showUI(__html__, { width: 400, height: 300 });
// Store created nodes by ID for later reference
const nodeMap = new Map<string, SceneNode>();
figma.ui.onmessage = async (msg: any) => {
try {
switch (msg.type) {
case 'create_rect': {
const rect = figma.createRectangle();
rect.x = msg.x !== undefined ? msg.x : 0;
rect.y = msg.y !== undefined ? msg.y : 0;
rect.resize(msg.width || 100, msg.height || 100);
if (msg.color) {
rect.fills = [{
type: 'SOLID',
color: { r: msg.color.r, g: msg.color.g, b: msg.color.b }
}];
}
figma.currentPage.appendChild(rect);
nodeMap.set(rect.id, rect);
figma.ui.postMessage({ type: 'result', success: true, nodeId: rect.id, commandId: msg.commandId });
figma.notify('Rectangle created');
break;
}
case 'create_text': {
const text = figma.createText();
const fontFamily = msg.fontFamily || 'Pretendard';
const fontStyle = msg.fontStyle || 'Regular';
// Load font and set before any text operations
try {
await figma.loadFontAsync({ family: fontFamily, style: fontStyle });
text.fontName = { family: fontFamily, style: fontStyle };
} catch (fontError: any) {
figma.notify('폰트 로드 실패: ' + fontFamily + ' - ' + fontError.message, { error: true });
}
// Set font size before characters (requires font loaded)
text.fontSize = msg.fontSize || 16;
const content = msg.text || msg.content || 'Text';
figma.notify('받은 텍스트: ' + content);
text.characters = content;
text.x = msg.x !== undefined ? msg.x : 0;
text.y = msg.y !== undefined ? msg.y : 0;
if (msg.color) {
text.fills = [{
type: 'SOLID',
color: { r: msg.color.r, g: msg.color.g, b: msg.color.b }
}];
}
figma.currentPage.appendChild(text);
nodeMap.set(text.id, text);
figma.ui.postMessage({ type: 'result', success: true, nodeId: text.id, commandId: msg.commandId });
figma.notify('Text created');
break;
}
case 'create_frame': {
const frame = figma.createFrame();
frame.name = msg.name || 'Frame';
frame.x = msg.x !== undefined ? msg.x : 0;
frame.y = msg.y !== undefined ? msg.y : 0;
frame.resize(msg.width || 100, msg.height || 100);
if (msg.color) {
frame.fills = [{
type: 'SOLID',
color: { r: msg.color.r, g: msg.color.g, b: msg.color.b }
}];
}
figma.currentPage.appendChild(frame);
nodeMap.set(frame.id, frame);
figma.ui.postMessage({ type: 'result', success: true, nodeId: frame.id, commandId: msg.commandId });
figma.notify('Frame "' + frame.name + '" created');
break;
}
case 'set_fill': {
const node = figma.getNodeById(msg.nodeId) as GeometryMixin | null;
if (node && 'fills' in node) {
node.fills = [{
type: 'SOLID',
color: { r: msg.color.r, g: msg.color.g, b: msg.color.b }
}];
figma.ui.postMessage({ type: 'result', success: true, commandId: msg.commandId });
figma.notify('Fill color updated');
} else {
figma.ui.postMessage({ type: 'result', success: false, error: 'Node not found', commandId: msg.commandId });
}
break;
}
case 'move_node': {
const node = figma.getNodeById(msg.nodeId) as SceneNode | null;
if (node && 'x' in node) {
node.x = msg.x;
node.y = msg.y;
figma.ui.postMessage({ type: 'result', success: true, commandId: msg.commandId });
figma.notify('Node moved');
} else {
figma.ui.postMessage({ type: 'result', success: false, error: 'Node not found', commandId: msg.commandId });
}
break;
}
case 'delete_node': {
const node = figma.getNodeById(msg.nodeId);
if (node) {
node.remove();
nodeMap.delete(msg.nodeId);
figma.ui.postMessage({ type: 'result', success: true, commandId: msg.commandId });
figma.notify('Node deleted');
} else {
figma.ui.postMessage({ type: 'result', success: false, error: 'Node not found', commandId: msg.commandId });
}
break;
}
case 'create_image': {
// UI에서 이미지 데이터를 받아서 프레임에 채우기
if (msg.imageData) {
const imageBytes = new Uint8Array(msg.imageData);
const image = figma.createImage(imageBytes);
const frame = figma.createFrame();
frame.name = msg.name || 'Image Frame';
frame.x = msg.x !== undefined ? msg.x : 0;
frame.y = msg.y !== undefined ? msg.y : 0;
frame.resize(msg.width || 100, msg.height || 100);
frame.fills = [{
type: 'IMAGE',
imageHash: image.hash,
scaleMode: msg.scaleMode || 'FILL'
}];
figma.currentPage.appendChild(frame);
nodeMap.set(frame.id, frame);
figma.ui.postMessage({ type: 'result', success: true, nodeId: frame.id, commandId: msg.commandId });
figma.notify('이미지 프레임 생성 완료');
} else {
figma.ui.postMessage({ type: 'result', success: false, error: '이미지 데이터 없음', commandId: msg.commandId });
}
break;
}
case 'set_image_fill': {
// 기존 노드에 이미지 fill 적용
const targetNode = figma.getNodeById(msg.nodeId) as GeometryMixin | null;
if (targetNode && 'fills' in targetNode && msg.imageData) {
const imageBytes = new Uint8Array(msg.imageData);
const image = figma.createImage(imageBytes);
targetNode.fills = [{
type: 'IMAGE',
imageHash: image.hash,
scaleMode: msg.scaleMode || 'FILL'
}];
figma.ui.postMessage({ type: 'result', success: true, nodeId: msg.nodeId, commandId: msg.commandId });
figma.notify('이미지 fill 적용 완료');
} else if (!msg.imageData) {
figma.ui.postMessage({ type: 'result', success: false, error: '이미지 데이터 없음', commandId: msg.commandId });
} else {
figma.ui.postMessage({ type: 'result', success: false, error: '노드를 찾을 수 없거나 fill을 지원하지 않음', commandId: msg.commandId });
}
break;
}
case 'status': {
// Just a status update from UI, no action needed
break;
}
default:
figma.ui.postMessage({ type: 'result', success: false, error: 'Unknown command: ' + msg.type, commandId: msg.commandId });
}
} catch (error: any) {
figma.ui.postMessage({ type: 'result', success: false, error: error.message, commandId: msg.commandId });
figma.notify('Error: ' + error.message, { error: true });
}
};