interface GetReactionsParams {
nodeIds: string[];
}
interface SetDefaultConnectorParams {
connectorId?: string;
}
interface CreateConnectionsParams {
connections: Array<{
startNodeId: string;
endNodeId: string;
text?: string;
}>;
}
// Store for default connector
let defaultConnectorNode: ConnectorNode | null = null;
/**
* Get reactions from nodes
*/
export async function getReactions(params: GetReactionsParams): Promise<{
success: boolean;
totalFound: number;
results: Array<{
id: string;
name: string;
type: string;
hasReactions: boolean;
reactions: Reaction[];
path: string;
}>;
}> {
const { nodeIds } = params;
function getNodePath(node: BaseNode): string {
const path: string[] = [];
let current: BaseNode | null = node;
while (current?.parent) {
path.unshift(current.name);
current = current.parent;
}
return path.join(" > ");
}
async function findNodesWithReactions(
node: SceneNode,
processedNodes: Set<string> = new Set(),
results: Array<{
id: string;
name: string;
type: string;
hasReactions: boolean;
reactions: Reaction[];
path: string;
}> = [],
): Promise<typeof results> {
if (processedNodes.has(node.id)) {
return results;
}
processedNodes.add(node.id);
// Check if node has reactions
if ("reactions" in node) {
const reactableNode = node as SceneNode & {
reactions: readonly Reaction[];
};
const filteredReactions = reactableNode.reactions.filter((r) => {
if (
r.action?.type === "NODE" &&
(r.action as { navigation?: string }).navigation === "CHANGE_TO"
) {
return false;
}
return true;
});
if (filteredReactions.length > 0) {
results.push({
id: node.id,
name: node.name,
type: node.type,
hasReactions: true,
reactions: filteredReactions as Reaction[],
path: getNodePath(node),
});
}
}
// Search children
if ("children" in node) {
const parent = node as ChildrenMixin;
for (const child of parent.children as SceneNode[]) {
await findNodesWithReactions(child, processedNodes, results);
}
}
return results;
}
const allResults: Array<{
id: string;
name: string;
type: string;
hasReactions: boolean;
reactions: Reaction[];
path: string;
}> = [];
for (const nodeId of nodeIds) {
const node = await figma.getNodeByIdAsync(nodeId);
if (node && "absoluteBoundingBox" in node) {
const results = await findNodesWithReactions(node as SceneNode);
allResults.push(...results);
}
}
return {
success: true,
totalFound: allResults.length,
results: allResults,
};
}
/**
* Set default connector for creating connections
*/
export async function setDefaultConnector(
params: SetDefaultConnectorParams,
): Promise<{
success: boolean;
message: string;
}> {
const { connectorId } = params;
if (connectorId) {
const node = await figma.getNodeByIdAsync(connectorId);
if (!node || node.type !== "CONNECTOR") {
throw new Error(`Connector not found with ID: ${connectorId}`);
}
defaultConnectorNode = node as ConnectorNode;
return {
success: true,
message: `Default connector set to ${node.name}`,
};
}
// Check if we already have a default
if (defaultConnectorNode) {
return {
success: true,
message: "Default connector is already set",
};
}
return {
success: false,
message:
"No default connector set. Please copy a connector from FigJam, paste it onto the current page, select it, and call this function with the connector's ID.",
};
}
/**
* Create connections between nodes
*/
export async function createConnections(
params: CreateConnectionsParams,
): Promise<{
success: boolean;
createdCount: number;
}> {
const { connections } = params;
if (!defaultConnectorNode) {
throw new Error(
"No default connector set. Please set a default connector first using set_default_connector.",
);
}
let createdCount = 0;
for (const connection of connections) {
try {
const startNode = await figma.getNodeByIdAsync(connection.startNodeId);
const endNode = await figma.getNodeByIdAsync(connection.endNodeId);
if (!startNode || !endNode) {
continue;
}
// Clone the default connector
const newConnector = defaultConnectorNode.clone();
// Set connection endpoints
newConnector.connectorStart = {
endpointNodeId: connection.startNodeId,
magnet: "AUTO",
};
newConnector.connectorEnd = {
endpointNodeId: connection.endNodeId,
magnet: "AUTO",
};
// Set text if provided - need to load font and set characters
if (connection.text && "characters" in newConnector) {
try {
const textNode = newConnector as unknown as TextNode;
await figma.loadFontAsync(textNode.fontName as FontName);
textNode.characters = connection.text;
} catch {
// Ignore text setting errors for connectors
}
}
figma.currentPage.appendChild(newConnector);
createdCount++;
} catch (error) {
console.error(`Error creating connection: ${error}`);
}
}
return {
success: createdCount > 0,
createdCount,
};
}