From 4bcdef53c70dbeb7fe7c05654a9dc159bc65fcd5 Mon Sep 17 00:00:00 2001 From: Daniel Destefanis Date: Mon, 3 Apr 2023 12:46:30 -0500 Subject: [PATCH] rewrote design lint to use a generator function and cleaned up additional lint function calls --- src/app/components/App.tsx | 60 ++++++++++-------------- src/plugin/controller.ts | 93 ++++++++++++++++++++++++++++---------- 2 files changed, 93 insertions(+), 60 deletions(-) diff --git a/src/app/components/App.tsx b/src/app/components/App.tsx index 0c63d3e..1eb92c5 100644 --- a/src/app/components/App.tsx +++ b/src/app/components/App.tsx @@ -119,13 +119,6 @@ const App = ({}) => { ); }; - const onRunApp = React.useCallback(() => { - parent.postMessage( - { pluginMessage: { type: "run-app", lintVectors: lintVectors } }, - "*" - ); - }, []); - function updateVisibility() { if (isVisible === true) { setIsVisible(false); @@ -155,32 +148,21 @@ const App = ({}) => { } }, [ignoredErrorArray]); + const onRunApp = React.useCallback(() => { + parent.postMessage( + { pluginMessage: { type: "run-app", lintVectors: lintVectors } }, + "*" + ); + }, []); + React.useEffect(() => { onRunApp(); window.onmessage = event => { const { type, message, errors, storage } = event.data.pluginMessage; - // Plugin code returns this message after we return the first node - // for performance, then we lint the remaining layers. - if (type === "complete") { - let nodeObject = JSON.parse(message); - - updateErrorArray(errors); - - parent.postMessage( - { - pluginMessage: { - type: "fetch-layer-data", - id: nodeObject[0].id, - nodeArray: nodeObject - } - }, - "*" - ); - - setInitialLoad(true); - } else if (type === "first node") { + if (type === "step-1") { + // Lint the very first selected node. let nodeObject = JSON.parse(message); setNodeArray(nodeObject); @@ -196,28 +178,36 @@ const App = ({}) => { return activeNodeIds.concat(nodeObject[0].id); }); - // After we have the first node, we want to - // lint the remaining selection. + // Fetch the properties of the first layers within our selection + // And select them in Figma. parent.postMessage( { pluginMessage: { - type: "lint-all", - nodes: nodeObject + type: "step-2", + id: nodeObject[0].id, + nodeArray: nodeObject } }, "*" ); + } else if (type === "step-2-complete") { + // Grabs the properties of the first layer to display in our UI. + setSelectedNode(() => JSON.parse(message)); + // After we have the first node, we want to + // lint the all the remaining nodes/layers in our original selection. parent.postMessage( { pluginMessage: { - type: "fetch-layer-data", - id: nodeObject[0].id, - nodeArray: nodeObject + type: "step-3" } }, "*" ); + } else if (type === "step-3-complete") { + // Once all layers are linted, we update the error array. + updateErrorArray(errors); + setInitialLoad(true); } else if (type === "fetched storage") { let clientStorage = JSON.parse(storage); @@ -237,7 +227,6 @@ const App = ({}) => { setIgnoreErrorArray([]); parent.postMessage({ pluginMessage: { type: "update-errors" } }, "*"); } else if (type === "fetched layer") { - // Grabs the properties of the first layer. setSelectedNode(() => JSON.parse(message)); // Ask the controller to lint the layers for errors. @@ -286,6 +275,7 @@ const App = ({}) => { ignoredErrors={ignoredErrorArray} onClick={updateVisibility} onSelectedListUpdate={updateSelectedList} + initialLoad={initialLoad} /> )} diff --git a/src/plugin/controller.ts b/src/plugin/controller.ts index 9e73384..70c16e4 100644 --- a/src/plugin/controller.ts +++ b/src/plugin/controller.ts @@ -45,7 +45,7 @@ const documentUUID = getDocumentUUID(); figma.on("documentchange", _event => { // When a change happens in the document - // send a message to the plugin to look for changes. + // send a message to the plugin to look for changes.' figma.ui.postMessage({ type: "change" }); @@ -56,6 +56,54 @@ figma.ui.onmessage = msg => { figma.closePlugin(); } + if (msg.type === "step-2") { + let layer = figma.getNodeById(msg.id); + let layerArray = []; + + // Using figma UI selection and scroll to viewport requires an array. + layerArray.push(layer); + + // Moves the layer into focus and selects so the user can update it. + // uncomment the line below if you want to notify something has been selected. + // figma.notify(`Layer ${layer.name} selected`, { timeout: 750 }); + figma.currentPage.selection = layerArray; + figma.viewport.scrollAndZoomIntoView(layerArray); + + let layerData = JSON.stringify(layer, [ + "id", + "name", + "description", + "fills", + "key", + "type", + "remote", + "paints", + "fontName", + "fontSize", + "font" + ]); + + figma.ui.postMessage({ + type: "step-2-complete", + message: layerData + }); + } + + // if (msg.type === "step-3") { + // // Pass the array back to the UI to be displayed. + // figma.ui.postMessage({ + // type: "step-3-complete", + // errors: lint(originalNodeTree), + // message: serializeNodes(originalNodeTree), + // }); + + // console.log('step 3 complete, legacy lint'); + + // figma.notify(`Design lint is running and will auto refresh for changes`, { + // timeout: 2000 + // }); + // } + // Fetch a specific node by ID. if (msg.type === "fetch-layer-data") { let layer = figma.getNodeById(msg.id); @@ -90,7 +138,8 @@ figma.ui.onmessage = msg => { }); } - // Could this be made less expensive? + // Called when an update in the Figma file happens + // so we can check what changed. if (msg.type === "update-errors") { figma.ui.postMessage({ type: "updated errors", @@ -206,6 +255,7 @@ figma.ui.onmessage = msg => { for (const node of nodes) { // Determine if the layer or its parent is locked. const isLayerLocked = lockedParentNode || node.locked; + const nodeChildren = node.children; // Create a new object. const newObject = { @@ -215,7 +265,7 @@ figma.ui.onmessage = msg => { }; // Check if the node has children. - if (node.children) { + if (nodeChildren) { // Recursively run this function to flatten out children and grandchildren nodes. newObject.children = node.children.map(childNode => childNode.id); errorArray.push(...lint(node.children, isLayerLocked)); @@ -227,29 +277,17 @@ figma.ui.onmessage = msg => { return errorArray; } - // if (msg.type === "lint-all") { - // // Pass the array back to the UI to be displayed. - // figma.ui.postMessage({ - // type: "complete", - // errors: lint(originalNodeTree), - // message: serializeNodes(msg.nodes) - // }); - - // figma.notify(`Design lint is running and will auto refresh for changes`, { - // timeout: 2000 - // }); - // } - - // Generator explorations function delay(time) { return new Promise(resolve => setTimeout(resolve, time)); } + // Counter to keep track of the total number of processed nodes + let nodeCounter = 0; + async function* lintAsync(nodes, lockedParentNode = false) { let errorArray = []; - for (let i = 0; i < nodes.length; i++) { - const node = nodes[i]; + for (const node of nodes) { // Determine if the layer or its parent is locked. const isLayerLocked = lockedParentNode || node.locked; @@ -271,11 +309,16 @@ figma.ui.onmessage = msg => { errorArray.push(newObject); + // Increment the node counter + nodeCounter++; + console.log(nodeCounter); + // Yield the result after processing a certain number of nodes - if (i % 100 === 0) { + if (nodeCounter % 2000 === 0) { + console.log("yield"); yield errorArray; errorArray = []; - await delay(40); // Pause for 40ms every 100 items to allow UI to update + await delay(0); } } @@ -285,7 +328,7 @@ figma.ui.onmessage = msg => { } } - if (msg.type === "lint-all") { + if (msg.type === "step-3") { // Use an async function to handle the asynchronous generator async function processLint() { const finalResult = []; @@ -295,9 +338,9 @@ figma.ui.onmessage = msg => { // Pass the final result back to the UI to be displayed. figma.ui.postMessage({ - type: "complete", + type: "step-3-complete", errors: finalResult, - message: serializeNodes(msg.nodes) + message: serializeNodes(originalNodeTree) }); figma.notify(`Scan Complete`, { @@ -331,7 +374,7 @@ figma.ui.onmessage = msg => { // We want to immediately render the first selection // to avoid freezing up the UI. figma.ui.postMessage({ - type: "first node", + type: "step-1", message: serializeNodes(nodes), errors: lint(firstNode) });