@biolab/talk-to-figma 0.6.0 → 0.8.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 +0 -125
- package/dist/cli.cjs +31 -2582
- package/dist/cli.js +31 -2562
- package/dist/relay.cjs +4 -158
- package/dist/relay.js +4 -135
- package/dist/talk_to_figma_mcp/server.cjs +28 -2422
- package/dist/talk_to_figma_mcp/server.js +28 -2400
- package/figma-plugin/code.js +489 -3
- package/figma-plugin/figma-plugin.zip +0 -0
- package/package.json +1 -1
- package/dist/cli.cjs.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/relay.cjs.map +0 -1
- package/dist/relay.js.map +0 -1
- package/dist/talk_to_figma_mcp/server.cjs.map +0 -1
- package/dist/talk_to_figma_mcp/server.js.map +0 -1
package/figma-plugin/code.js
CHANGED
|
@@ -263,6 +263,20 @@ async function handleCommand(command, params) {
|
|
|
263
263
|
return await createComponent(params);
|
|
264
264
|
case "create_component_set":
|
|
265
265
|
return await createComponentSet(params);
|
|
266
|
+
case "set_component_text_property":
|
|
267
|
+
return await setComponentTextProperty(params);
|
|
268
|
+
case "rename_node":
|
|
269
|
+
return await renameNode(params);
|
|
270
|
+
case "rename_nodes":
|
|
271
|
+
return await renameNodes(params);
|
|
272
|
+
case "add_boolean_property":
|
|
273
|
+
return await addBooleanProperty(params);
|
|
274
|
+
case "add_instance_swap_property":
|
|
275
|
+
return await addInstanceSwapProperty(params);
|
|
276
|
+
case "sync_layer_fills":
|
|
277
|
+
return await syncLayerFills(params);
|
|
278
|
+
case "move_child_to_index":
|
|
279
|
+
return await moveChildToIndex(params);
|
|
266
280
|
default:
|
|
267
281
|
throw new Error(`Unknown command: ${command}`);
|
|
268
282
|
}
|
|
@@ -1515,20 +1529,42 @@ async function setVariableBinding(params) {
|
|
|
1515
1529
|
}
|
|
1516
1530
|
|
|
1517
1531
|
async function createComponentInstance(params) {
|
|
1518
|
-
const { componentKey, x = 0, y = 0 } = params || {};
|
|
1532
|
+
const { componentKey, x = 0, y = 0, parentId } = params || {};
|
|
1519
1533
|
|
|
1520
1534
|
if (!componentKey) {
|
|
1521
1535
|
throw new Error("Missing componentKey parameter");
|
|
1522
1536
|
}
|
|
1523
1537
|
|
|
1524
1538
|
try {
|
|
1525
|
-
|
|
1539
|
+
var component;
|
|
1540
|
+
// If componentKey contains ":", treat as node ID (local component)
|
|
1541
|
+
if (componentKey.indexOf(":") !== -1) {
|
|
1542
|
+
var localNode = await figma.getNodeByIdAsync(componentKey);
|
|
1543
|
+
if (!localNode || localNode.type !== "COMPONENT") {
|
|
1544
|
+
throw new Error("Local component not found with ID: " + componentKey);
|
|
1545
|
+
}
|
|
1546
|
+
component = localNode;
|
|
1547
|
+
} else {
|
|
1548
|
+
// Try import by key (published library components)
|
|
1549
|
+
component = await figma.importComponentByKeyAsync(componentKey);
|
|
1550
|
+
}
|
|
1526
1551
|
const instance = component.createInstance();
|
|
1527
1552
|
|
|
1528
1553
|
instance.x = x;
|
|
1529
1554
|
instance.y = y;
|
|
1530
1555
|
|
|
1531
|
-
|
|
1556
|
+
if (parentId) {
|
|
1557
|
+
const parentNode = await figma.getNodeByIdAsync(parentId);
|
|
1558
|
+
if (!parentNode) {
|
|
1559
|
+
throw new Error(`Parent node not found: ${parentId}`);
|
|
1560
|
+
}
|
|
1561
|
+
if (!("appendChild" in parentNode)) {
|
|
1562
|
+
throw new Error(`Parent node does not support children: ${parentId}`);
|
|
1563
|
+
}
|
|
1564
|
+
parentNode.appendChild(instance);
|
|
1565
|
+
} else {
|
|
1566
|
+
figma.currentPage.appendChild(instance);
|
|
1567
|
+
}
|
|
1532
1568
|
|
|
1533
1569
|
return {
|
|
1534
1570
|
id: instance.id,
|
|
@@ -1646,6 +1682,69 @@ async function createComponentSet(params) {
|
|
|
1646
1682
|
}
|
|
1647
1683
|
}
|
|
1648
1684
|
|
|
1685
|
+
async function renameNode(params) {
|
|
1686
|
+
var nodeId = (params || {}).nodeId;
|
|
1687
|
+
var name = (params || {}).name;
|
|
1688
|
+
|
|
1689
|
+
if (!nodeId) {
|
|
1690
|
+
throw new Error("Missing nodeId parameter");
|
|
1691
|
+
}
|
|
1692
|
+
if (!name && name !== "") {
|
|
1693
|
+
throw new Error("Missing name parameter");
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
var node = await figma.getNodeByIdAsync(nodeId);
|
|
1697
|
+
if (!node) {
|
|
1698
|
+
throw new Error("Node not found with ID: " + nodeId);
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
node.name = name;
|
|
1702
|
+
|
|
1703
|
+
return {
|
|
1704
|
+
id: node.id,
|
|
1705
|
+
name: node.name,
|
|
1706
|
+
type: node.type,
|
|
1707
|
+
};
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1710
|
+
async function renameNodes(params) {
|
|
1711
|
+
var nodes = (params || {}).nodes;
|
|
1712
|
+
|
|
1713
|
+
if (!nodes || !Array.isArray(nodes) || nodes.length === 0) {
|
|
1714
|
+
throw new Error("Missing or empty nodes array. Provide [{nodeId, name}].");
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
var results = [];
|
|
1718
|
+
var errors = [];
|
|
1719
|
+
|
|
1720
|
+
for (var i = 0; i < nodes.length; i++) {
|
|
1721
|
+
var item = nodes[i];
|
|
1722
|
+
var nodeId = item.nodeId;
|
|
1723
|
+
var name = item.name;
|
|
1724
|
+
|
|
1725
|
+
if (!nodeId || (!name && name !== "")) {
|
|
1726
|
+
errors.push("Invalid entry at index " + i + ": missing nodeId or name");
|
|
1727
|
+
continue;
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
var node = await figma.getNodeByIdAsync(nodeId);
|
|
1731
|
+
if (!node) {
|
|
1732
|
+
errors.push("Node not found: " + nodeId);
|
|
1733
|
+
continue;
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1736
|
+
node.name = name;
|
|
1737
|
+
results.push({ id: node.id, name: node.name, type: node.type });
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1740
|
+
return {
|
|
1741
|
+
renamed: results.length,
|
|
1742
|
+
errors: errors.length,
|
|
1743
|
+
results: results,
|
|
1744
|
+
errorMessages: errors,
|
|
1745
|
+
};
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1649
1748
|
async function exportNodeAsImage(params) {
|
|
1650
1749
|
const { nodeId, scale = 1 } = params || {};
|
|
1651
1750
|
|
|
@@ -4561,3 +4660,390 @@ async function setSelections(params) {
|
|
|
4561
4660
|
message: `Selected ${nodes.length} nodes${notFoundIds.length > 0 ? ` (${notFoundIds.length} not found)` : ''}`
|
|
4562
4661
|
};
|
|
4563
4662
|
}
|
|
4663
|
+
|
|
4664
|
+
async function setComponentTextProperty(params) {
|
|
4665
|
+
const { componentId, propertyName, defaultValue, textNodeIds } = params;
|
|
4666
|
+
|
|
4667
|
+
if (!componentId) throw new Error("Missing componentId parameter");
|
|
4668
|
+
if (!propertyName) throw new Error("Missing propertyName parameter");
|
|
4669
|
+
if (!textNodeIds || !Array.isArray(textNodeIds) || textNodeIds.length === 0) {
|
|
4670
|
+
throw new Error("Missing or empty textNodeIds parameter");
|
|
4671
|
+
}
|
|
4672
|
+
|
|
4673
|
+
const componentNode = await figma.getNodeByIdAsync(componentId);
|
|
4674
|
+
if (!componentNode) {
|
|
4675
|
+
throw new Error(`Node not found with ID: ${componentId}`);
|
|
4676
|
+
}
|
|
4677
|
+
|
|
4678
|
+
if (componentNode.type !== "COMPONENT" && componentNode.type !== "COMPONENT_SET") {
|
|
4679
|
+
throw new Error(`Node ${componentId} is type ${componentNode.type}, expected COMPONENT or COMPONENT_SET`);
|
|
4680
|
+
}
|
|
4681
|
+
|
|
4682
|
+
const results = [];
|
|
4683
|
+
const errors = [];
|
|
4684
|
+
|
|
4685
|
+
// Determine the property owner (ComponentSet or standalone Component)
|
|
4686
|
+
var propertyOwner = componentNode;
|
|
4687
|
+
|
|
4688
|
+
// For ComponentSet, property must be added on the set itself, not variants
|
|
4689
|
+
var propDefault = defaultValue || "Button";
|
|
4690
|
+
|
|
4691
|
+
// Check if property already exists on the property owner
|
|
4692
|
+
var existingProps = propertyOwner.componentPropertyDefinitions;
|
|
4693
|
+
var propertyKey = null;
|
|
4694
|
+
|
|
4695
|
+
for (var key in existingProps) {
|
|
4696
|
+
if (existingProps.hasOwnProperty(key)) {
|
|
4697
|
+
var def = existingProps[key];
|
|
4698
|
+
if (def.type === "TEXT" && key.startsWith(propertyName + "#")) {
|
|
4699
|
+
propertyKey = key;
|
|
4700
|
+
break;
|
|
4701
|
+
}
|
|
4702
|
+
}
|
|
4703
|
+
}
|
|
4704
|
+
|
|
4705
|
+
// Add property on the owner if it doesn't exist
|
|
4706
|
+
if (!propertyKey) {
|
|
4707
|
+
propertyKey = propertyOwner.addComponentProperty(propertyName, "TEXT", propDefault);
|
|
4708
|
+
}
|
|
4709
|
+
|
|
4710
|
+
// Get all component nodes that contain the text nodes
|
|
4711
|
+
var components = [];
|
|
4712
|
+
if (componentNode.type === "COMPONENT_SET") {
|
|
4713
|
+
components = componentNode.children.filter(function(c) { return c.type === "COMPONENT"; });
|
|
4714
|
+
} else {
|
|
4715
|
+
components = [componentNode];
|
|
4716
|
+
}
|
|
4717
|
+
|
|
4718
|
+
for (var i = 0; i < components.length; i++) {
|
|
4719
|
+
var comp = components[i];
|
|
4720
|
+
try {
|
|
4721
|
+
// Find text nodes in this component that match the requested IDs
|
|
4722
|
+
for (var j = 0; j < textNodeIds.length; j++) {
|
|
4723
|
+
var textNodeId = textNodeIds[j];
|
|
4724
|
+
var textNode = comp.findOne(function(n) { return n.id === textNodeId; });
|
|
4725
|
+
if (textNode && textNode.type === "TEXT") {
|
|
4726
|
+
// Load font for the text node
|
|
4727
|
+
await figma.loadFontAsync(textNode.fontName);
|
|
4728
|
+
|
|
4729
|
+
var existingRefs = textNode.componentPropertyReferences || {};
|
|
4730
|
+
var newRefs = Object.assign({}, existingRefs);
|
|
4731
|
+
newRefs.characters = propertyKey;
|
|
4732
|
+
textNode.componentPropertyReferences = newRefs;
|
|
4733
|
+
results.push({
|
|
4734
|
+
componentId: comp.id,
|
|
4735
|
+
componentName: comp.name,
|
|
4736
|
+
textNodeId: textNode.id,
|
|
4737
|
+
propertyKey: propertyKey,
|
|
4738
|
+
});
|
|
4739
|
+
}
|
|
4740
|
+
}
|
|
4741
|
+
} catch (err) {
|
|
4742
|
+
errors.push({
|
|
4743
|
+
componentId: comp.id,
|
|
4744
|
+
componentName: comp.name,
|
|
4745
|
+
error: err.message || String(err),
|
|
4746
|
+
});
|
|
4747
|
+
}
|
|
4748
|
+
}
|
|
4749
|
+
|
|
4750
|
+
return {
|
|
4751
|
+
success: true,
|
|
4752
|
+
linked: results.length,
|
|
4753
|
+
errors: errors.length,
|
|
4754
|
+
propertyKey: propertyKey,
|
|
4755
|
+
results: results,
|
|
4756
|
+
errorDetails: errors,
|
|
4757
|
+
message: "Linked " + results.length + " text nodes to \"" + propertyName + "\" property" + (errors.length > 0 ? " (" + errors.length + " errors)" : ""),
|
|
4758
|
+
};
|
|
4759
|
+
}
|
|
4760
|
+
|
|
4761
|
+
async function addBooleanProperty(params) {
|
|
4762
|
+
var componentSetId = (params || {}).componentSetId;
|
|
4763
|
+
var propertyName = (params || {}).propertyName;
|
|
4764
|
+
var defaultValue = (params || {}).defaultValue;
|
|
4765
|
+
var layerName = (params || {}).layerName;
|
|
4766
|
+
|
|
4767
|
+
if (!componentSetId) throw new Error("Missing componentSetId parameter");
|
|
4768
|
+
if (!propertyName) throw new Error("Missing propertyName parameter");
|
|
4769
|
+
if (defaultValue === undefined) defaultValue = true;
|
|
4770
|
+
if (!layerName) throw new Error("Missing layerName parameter");
|
|
4771
|
+
|
|
4772
|
+
var componentSet = await figma.getNodeByIdAsync(componentSetId);
|
|
4773
|
+
if (!componentSet) throw new Error("Node not found: " + componentSetId);
|
|
4774
|
+
if (componentSet.type !== "COMPONENT_SET" && componentSet.type !== "COMPONENT") {
|
|
4775
|
+
throw new Error("Node must be COMPONENT_SET or COMPONENT, got " + componentSet.type);
|
|
4776
|
+
}
|
|
4777
|
+
|
|
4778
|
+
// Check if property already exists
|
|
4779
|
+
var existingProps = componentSet.componentPropertyDefinitions;
|
|
4780
|
+
var propertyKey = null;
|
|
4781
|
+
for (var key in existingProps) {
|
|
4782
|
+
if (existingProps.hasOwnProperty(key)) {
|
|
4783
|
+
var def = existingProps[key];
|
|
4784
|
+
if (def.type === "BOOLEAN" && key.startsWith(propertyName + "#")) {
|
|
4785
|
+
propertyKey = key;
|
|
4786
|
+
break;
|
|
4787
|
+
}
|
|
4788
|
+
}
|
|
4789
|
+
}
|
|
4790
|
+
|
|
4791
|
+
if (!propertyKey) {
|
|
4792
|
+
propertyKey = componentSet.addComponentProperty(propertyName, "BOOLEAN", defaultValue);
|
|
4793
|
+
}
|
|
4794
|
+
|
|
4795
|
+
// Find layers matching layerName across all variants and link visibility
|
|
4796
|
+
var components = [];
|
|
4797
|
+
if (componentSet.type === "COMPONENT_SET") {
|
|
4798
|
+
components = componentSet.children.filter(function(c) { return c.type === "COMPONENT"; });
|
|
4799
|
+
} else {
|
|
4800
|
+
components = [componentSet];
|
|
4801
|
+
}
|
|
4802
|
+
|
|
4803
|
+
var linked = 0;
|
|
4804
|
+
var errors = [];
|
|
4805
|
+
for (var i = 0; i < components.length; i++) {
|
|
4806
|
+
var comp = components[i];
|
|
4807
|
+
try {
|
|
4808
|
+
var layer = comp.findOne(function(n) { return n.name === layerName; });
|
|
4809
|
+
if (layer) {
|
|
4810
|
+
var existingRefs = layer.componentPropertyReferences || {};
|
|
4811
|
+
var newRefs = Object.assign({}, existingRefs);
|
|
4812
|
+
newRefs.visible = propertyKey;
|
|
4813
|
+
layer.componentPropertyReferences = newRefs;
|
|
4814
|
+
linked++;
|
|
4815
|
+
}
|
|
4816
|
+
} catch (err) {
|
|
4817
|
+
errors.push(comp.id + ": " + (err.message || String(err)));
|
|
4818
|
+
}
|
|
4819
|
+
}
|
|
4820
|
+
|
|
4821
|
+
return {
|
|
4822
|
+
success: true,
|
|
4823
|
+
propertyKey: propertyKey,
|
|
4824
|
+
linked: linked,
|
|
4825
|
+
totalVariants: components.length,
|
|
4826
|
+
errors: errors,
|
|
4827
|
+
message: "Added BOOLEAN property '" + propertyName + "', linked " + linked + " layers",
|
|
4828
|
+
};
|
|
4829
|
+
}
|
|
4830
|
+
|
|
4831
|
+
async function addInstanceSwapProperty(params) {
|
|
4832
|
+
var componentSetId = (params || {}).componentSetId;
|
|
4833
|
+
var propertyName = (params || {}).propertyName;
|
|
4834
|
+
var defaultComponentId = (params || {}).defaultComponentId;
|
|
4835
|
+
var layerName = (params || {}).layerName;
|
|
4836
|
+
|
|
4837
|
+
if (!componentSetId) throw new Error("Missing componentSetId parameter");
|
|
4838
|
+
if (!propertyName) throw new Error("Missing propertyName parameter");
|
|
4839
|
+
if (!defaultComponentId) throw new Error("Missing defaultComponentId parameter");
|
|
4840
|
+
if (!layerName) throw new Error("Missing layerName parameter");
|
|
4841
|
+
|
|
4842
|
+
var componentSet = await figma.getNodeByIdAsync(componentSetId);
|
|
4843
|
+
if (!componentSet) throw new Error("Node not found: " + componentSetId);
|
|
4844
|
+
if (componentSet.type !== "COMPONENT_SET" && componentSet.type !== "COMPONENT") {
|
|
4845
|
+
throw new Error("Node must be COMPONENT_SET or COMPONENT, got " + componentSet.type);
|
|
4846
|
+
}
|
|
4847
|
+
|
|
4848
|
+
// Get the default component for the instance swap
|
|
4849
|
+
var defaultComponent = await figma.getNodeByIdAsync(defaultComponentId);
|
|
4850
|
+
if (!defaultComponent || defaultComponent.type !== "COMPONENT") {
|
|
4851
|
+
throw new Error("Default component not found or not a COMPONENT: " + defaultComponentId);
|
|
4852
|
+
}
|
|
4853
|
+
|
|
4854
|
+
// Check if property already exists
|
|
4855
|
+
var existingProps = componentSet.componentPropertyDefinitions;
|
|
4856
|
+
var propertyKey = null;
|
|
4857
|
+
for (var key in existingProps) {
|
|
4858
|
+
if (existingProps.hasOwnProperty(key)) {
|
|
4859
|
+
var def = existingProps[key];
|
|
4860
|
+
if (def.type === "INSTANCE_SWAP" && key.startsWith(propertyName + "#")) {
|
|
4861
|
+
propertyKey = key;
|
|
4862
|
+
break;
|
|
4863
|
+
}
|
|
4864
|
+
}
|
|
4865
|
+
}
|
|
4866
|
+
|
|
4867
|
+
if (!propertyKey) {
|
|
4868
|
+
propertyKey = componentSet.addComponentProperty(propertyName, "INSTANCE_SWAP", defaultComponent.id);
|
|
4869
|
+
}
|
|
4870
|
+
|
|
4871
|
+
// Find instance layers matching layerName across all variants and link
|
|
4872
|
+
var components = [];
|
|
4873
|
+
if (componentSet.type === "COMPONENT_SET") {
|
|
4874
|
+
components = componentSet.children.filter(function(c) { return c.type === "COMPONENT"; });
|
|
4875
|
+
} else {
|
|
4876
|
+
components = [componentSet];
|
|
4877
|
+
}
|
|
4878
|
+
|
|
4879
|
+
var linked = 0;
|
|
4880
|
+
var errors = [];
|
|
4881
|
+
for (var i = 0; i < components.length; i++) {
|
|
4882
|
+
var comp = components[i];
|
|
4883
|
+
try {
|
|
4884
|
+
var layer = comp.findOne(function(n) { return n.name === layerName; });
|
|
4885
|
+
if (layer && layer.type === "INSTANCE") {
|
|
4886
|
+
var existingRefs = layer.componentPropertyReferences || {};
|
|
4887
|
+
var newRefs = Object.assign({}, existingRefs);
|
|
4888
|
+
newRefs.mainComponent = propertyKey;
|
|
4889
|
+
layer.componentPropertyReferences = newRefs;
|
|
4890
|
+
linked++;
|
|
4891
|
+
}
|
|
4892
|
+
} catch (err) {
|
|
4893
|
+
errors.push(comp.id + ": " + (err.message || String(err)));
|
|
4894
|
+
}
|
|
4895
|
+
}
|
|
4896
|
+
|
|
4897
|
+
return {
|
|
4898
|
+
success: true,
|
|
4899
|
+
propertyKey: propertyKey,
|
|
4900
|
+
linked: linked,
|
|
4901
|
+
totalVariants: components.length,
|
|
4902
|
+
errors: errors,
|
|
4903
|
+
message: "Added INSTANCE_SWAP property '" + propertyName + "', linked " + linked + " instances",
|
|
4904
|
+
};
|
|
4905
|
+
}
|
|
4906
|
+
|
|
4907
|
+
async function syncLayerFills(params) {
|
|
4908
|
+
var componentSetId = (params || {}).componentSetId;
|
|
4909
|
+
var sourceLayerName = (params || {}).sourceLayerName;
|
|
4910
|
+
var targetLayerName = (params || {}).targetLayerName;
|
|
4911
|
+
|
|
4912
|
+
if (!componentSetId) throw new Error("Missing componentSetId parameter");
|
|
4913
|
+
if (!sourceLayerName) throw new Error("Missing sourceLayerName parameter");
|
|
4914
|
+
if (!targetLayerName) throw new Error("Missing targetLayerName parameter");
|
|
4915
|
+
|
|
4916
|
+
var componentSet = await figma.getNodeByIdAsync(componentSetId);
|
|
4917
|
+
if (!componentSet) throw new Error("Node not found: " + componentSetId);
|
|
4918
|
+
if (componentSet.type !== "COMPONENT_SET" && componentSet.type !== "COMPONENT") {
|
|
4919
|
+
throw new Error("Node must be COMPONENT_SET or COMPONENT, got " + componentSet.type);
|
|
4920
|
+
}
|
|
4921
|
+
|
|
4922
|
+
var components = [];
|
|
4923
|
+
if (componentSet.type === "COMPONENT_SET") {
|
|
4924
|
+
components = componentSet.children.filter(function(c) { return c.type === "COMPONENT"; });
|
|
4925
|
+
} else {
|
|
4926
|
+
components = [componentSet];
|
|
4927
|
+
}
|
|
4928
|
+
|
|
4929
|
+
function applyFillsRecursive(node, fills, boundVarFills) {
|
|
4930
|
+
// Apply fills to nodes that support fills (not groups, not pages)
|
|
4931
|
+
if ("fills" in node && node.type !== "GROUP") {
|
|
4932
|
+
try {
|
|
4933
|
+
node.fills = fills;
|
|
4934
|
+
// Copy variable bindings for fills if available
|
|
4935
|
+
if (boundVarFills && boundVarFills.length > 0) {
|
|
4936
|
+
for (var fi = 0; fi < boundVarFills.length; fi++) {
|
|
4937
|
+
var bv = boundVarFills[fi];
|
|
4938
|
+
if (bv && bv.id) {
|
|
4939
|
+
figma.variables.getVariableByIdAsync(bv.id).then(function(variable) {
|
|
4940
|
+
if (variable) {
|
|
4941
|
+
try { node.setBoundVariable("fills", fi, variable); } catch(e) {}
|
|
4942
|
+
}
|
|
4943
|
+
});
|
|
4944
|
+
}
|
|
4945
|
+
}
|
|
4946
|
+
}
|
|
4947
|
+
} catch (e) {
|
|
4948
|
+
// Some nodes may not support setting fills
|
|
4949
|
+
}
|
|
4950
|
+
}
|
|
4951
|
+
// Recurse into children
|
|
4952
|
+
if ("children" in node) {
|
|
4953
|
+
for (var ci = 0; ci < node.children.length; ci++) {
|
|
4954
|
+
applyFillsRecursive(node.children[ci], fills, boundVarFills);
|
|
4955
|
+
}
|
|
4956
|
+
}
|
|
4957
|
+
}
|
|
4958
|
+
|
|
4959
|
+
var synced = 0;
|
|
4960
|
+
var errors = [];
|
|
4961
|
+
for (var i = 0; i < components.length; i++) {
|
|
4962
|
+
var comp = components[i];
|
|
4963
|
+
try {
|
|
4964
|
+
var sourceLayer = comp.findOne(function(n) { return n.name === sourceLayerName; });
|
|
4965
|
+
var targetLayer = comp.findOne(function(n) { return n.name === targetLayerName; });
|
|
4966
|
+
if (!sourceLayer) {
|
|
4967
|
+
errors.push(comp.id + ": source layer '" + sourceLayerName + "' not found");
|
|
4968
|
+
continue;
|
|
4969
|
+
}
|
|
4970
|
+
if (!targetLayer) {
|
|
4971
|
+
errors.push(comp.id + ": target layer '" + targetLayerName + "' not found");
|
|
4972
|
+
continue;
|
|
4973
|
+
}
|
|
4974
|
+
if (!("fills" in sourceLayer)) {
|
|
4975
|
+
errors.push(comp.id + ": source layer has no fills");
|
|
4976
|
+
continue;
|
|
4977
|
+
}
|
|
4978
|
+
|
|
4979
|
+
var sourceFills = JSON.parse(JSON.stringify(sourceLayer.fills));
|
|
4980
|
+
// Get bound variable references for fills
|
|
4981
|
+
var boundVarFills = [];
|
|
4982
|
+
try {
|
|
4983
|
+
var bv = sourceLayer.boundVariables;
|
|
4984
|
+
if (bv && bv.fills) {
|
|
4985
|
+
boundVarFills = bv.fills;
|
|
4986
|
+
}
|
|
4987
|
+
} catch (e) {}
|
|
4988
|
+
|
|
4989
|
+
applyFillsRecursive(targetLayer, sourceFills, boundVarFills);
|
|
4990
|
+
synced++;
|
|
4991
|
+
} catch (err) {
|
|
4992
|
+
errors.push(comp.id + ": " + (err.message || String(err)));
|
|
4993
|
+
}
|
|
4994
|
+
}
|
|
4995
|
+
|
|
4996
|
+
return {
|
|
4997
|
+
success: true,
|
|
4998
|
+
synced: synced,
|
|
4999
|
+
totalVariants: components.length,
|
|
5000
|
+
errors: errors,
|
|
5001
|
+
message: "Synced fills from '" + sourceLayerName + "' to '" + targetLayerName + "' in " + synced + " variants",
|
|
5002
|
+
};
|
|
5003
|
+
}
|
|
5004
|
+
|
|
5005
|
+
async function moveChildToIndex(params) {
|
|
5006
|
+
var componentSetId = (params || {}).componentSetId;
|
|
5007
|
+
var layerName = (params || {}).layerName;
|
|
5008
|
+
var targetIndex = (params || {}).targetIndex;
|
|
5009
|
+
|
|
5010
|
+
if (!componentSetId) throw new Error("Missing componentSetId parameter");
|
|
5011
|
+
if (!layerName) throw new Error("Missing layerName parameter");
|
|
5012
|
+
if (targetIndex === undefined) throw new Error("Missing targetIndex parameter");
|
|
5013
|
+
|
|
5014
|
+
var componentSet = await figma.getNodeByIdAsync(componentSetId);
|
|
5015
|
+
if (!componentSet) throw new Error("Node not found: " + componentSetId);
|
|
5016
|
+
if (componentSet.type !== "COMPONENT_SET" && componentSet.type !== "COMPONENT") {
|
|
5017
|
+
throw new Error("Node must be COMPONENT_SET or COMPONENT, got " + componentSet.type);
|
|
5018
|
+
}
|
|
5019
|
+
|
|
5020
|
+
var components = [];
|
|
5021
|
+
if (componentSet.type === "COMPONENT_SET") {
|
|
5022
|
+
components = componentSet.children.filter(function(c) { return c.type === "COMPONENT"; });
|
|
5023
|
+
} else {
|
|
5024
|
+
components = [componentSet];
|
|
5025
|
+
}
|
|
5026
|
+
|
|
5027
|
+
var moved = 0;
|
|
5028
|
+
var errors = [];
|
|
5029
|
+
for (var i = 0; i < components.length; i++) {
|
|
5030
|
+
var comp = components[i];
|
|
5031
|
+
try {
|
|
5032
|
+
var layer = comp.findOne(function(n) { return n.name === layerName; });
|
|
5033
|
+
if (layer) {
|
|
5034
|
+
comp.insertChild(targetIndex, layer);
|
|
5035
|
+
moved++;
|
|
5036
|
+
}
|
|
5037
|
+
} catch (err) {
|
|
5038
|
+
errors.push(comp.id + ": " + (err.message || String(err)));
|
|
5039
|
+
}
|
|
5040
|
+
}
|
|
5041
|
+
|
|
5042
|
+
return {
|
|
5043
|
+
success: true,
|
|
5044
|
+
moved: moved,
|
|
5045
|
+
totalVariants: components.length,
|
|
5046
|
+
errors: errors,
|
|
5047
|
+
message: "Moved '" + layerName + "' to index " + targetIndex + " in " + moved + " variants",
|
|
5048
|
+
};
|
|
5049
|
+
}
|
|
Binary file
|