// Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT // cSpell: ignore codegen nodechange import { listenTS, dispatchTS } from "./utils/code-utils.js"; import { generateSlintSnippet } from "./utils/property-parsing.js"; import { exportFigmaVariablesToSeparateFiles } from "./utils/export-variables.js"; if (figma.editorType === "dev" && figma.mode === "codegen") { figma.codegen.on("generate", async ({ node }: { node: SceneNode }) => { const useVariablesForCodegen = figma.codegen.preferences.customSettings.useVariables === "true" ? true : false; const slintSnippet = await generateSlintSnippet( node, useVariablesForCodegen, ); return slintSnippet ? [ { title: "Slint Code: " + node.name, // Use "CSS" as Figma doesn't support "SLINT" as a language option language: "CSS", code: slintSnippet, }, ] : []; }); } if (figma.editorType === "figma" && figma.mode === "default") { figma.showUI(__html__, { themeColors: true, width: 500, height: 320, }); } listenTS("generateSnippetRequest", async (payload) => { const useVariables = payload.useVariables ?? false; // <-- You likely already have this // Listen for node changes as property changes don't trigger a selectionChanged update const node = figma.currentPage; node.on("nodechange", () => { dispatchTS("nodeChanged", {}); }); const selection = figma.currentPage.selection; let title = "Figma Inspector"; let slintSnippet: string | null = "// Select a single component to inspect"; if (selection.length === 1) { const node = selection[0]; title = node.name; // --- Pass the useVariables value received from UI --- slintSnippet = await generateSlintSnippet(node, useVariables); if (slintSnippet === null) { slintSnippet = `// Unsupported node type: ${node.type}`; } } else if (selection.length > 1) { slintSnippet = "// Select a single component to inspect"; title = "Multiple Items Selected"; } // Send result back to UI using the correct message type dispatchTS("updatePropertiesCallback", { title: title, slintSnippet: slintSnippet, }); }); listenTS("copyToClipboard", ({ result }) => { if (result) { figma.notify("Copied!"); } else { figma.notify("Failed to copy"); } }); figma.on("selectionchange", () => { if (figma.editorType === "figma" && figma.mode === "default") { dispatchTS("selectionChangedInFigma", {}); } }); listenTS("exportToFiles", async (message) => { try { const files = await exportFigmaVariablesToSeparateFiles( message.exportAsSingleFile, ); // Send to UI for downloading figma.ui.postMessage({ type: "exportedFiles", files: files, }); figma.notify(`${files.length} collection files ready for download!`); } catch (error) { console.error("Error exporting to files:", error); figma.notify("Failed to export to files", { error: true }); } }); // Resize window handler listenTS("resizeWindow", ({ width, height }) => { figma.ui.resize(width, height); }); // Define state variables outside any function (at module level) const variableMonitoring: { initialized: boolean; lastSnapshot: string | null; lastChange: number; lastEventTime: number; } = { initialized: false, lastSnapshot: null, lastChange: 0, lastEventTime: 0, }; listenTS("monitorVariableChanges", () => { figma.ui.postMessage({ type: "variableMonitoringActive", // Keep this confirmation timestamp: Date.now(), }); }); listenTS("checkVariableChanges", async () => { await checkVariableChanges(); // Call the main async function }); // Replace your checkVariableChanges handler async function checkVariableChanges(isInitialRun = false) { try { const collections = await figma.variables.getLocalVariableCollectionsAsync(); const detailedSnapshotData: Record = {}; let variableFetchError = false; for (const collection of collections) { detailedSnapshotData[collection.id] = { id: collection.id, name: collection.name, modes: collection.modes.map((m) => ({ id: m.modeId, name: m.name, })), variables: {}, // Store variable details here }; // Fetch details for each variable in the collection // NOTE: This can be slow for *very* large numbers of variables for (const variableId of collection.variableIds) { try { const variable = await figma.variables.getVariableByIdAsync(variableId); if (variable) { // Store relevant value data (e.g., valuesByMode) detailedSnapshotData[collection.id].variables[ variable.id ] = { id: variable.id, name: variable.name, resolvedType: variable.resolvedType, // Include valuesByMode to detect value changes valuesByMode: variable.valuesByMode, }; } } catch (err) { console.error( `[Backend] Error fetching variable ${variableId}:`, err, ); variableFetchError = true; // Mark that an error occurred // Optionally add placeholder data or skip detailedSnapshotData[collection.id].variables[variableId] = { error: `Failed to fetch: ${err}` }; } } } const currentSnapshot = JSON.stringify(detailedSnapshotData); const now = Date.now(); // Handle initial run or forced update if (isInitialRun || !variableMonitoring.initialized) { variableMonitoring.lastSnapshot = currentSnapshot; variableMonitoring.initialized = true; variableMonitoring.lastChange = now; // Set initial timestamp // Optionally notify UI that it's initialized, maybe reset its state figma.ui.postMessage({ type: "snapshotInitialized", timestamp: now, }); return; // Don't compare on the very first run } // Compare with the stored detailed snapshot const hasChanged = variableMonitoring.lastSnapshot !== currentSnapshot; if (hasChanged) { variableMonitoring.lastSnapshot = currentSnapshot; variableMonitoring.lastChange = now; // Post a message indicating changes were found via snapshot figma.ui.postMessage({ type: "documentSnapshot", // Use the existing type the UI listens for timestamp: now, hasChanges: true, // Indicate changes found details: variableFetchError ? "Snapshot updated (some variable errors)" : "Snapshot updated", }); } } catch (error) { console.error("[Backend] Error during checkVariableChanges:", error); // Notify UI of the error figma.ui.postMessage({ type: "documentSnapshot", // Use existing type timestamp: Date.now(), error: `Error checking variables: ${String(error)}`, hasChanges: false, // Indicate no confirmed change due to error }); } }