Skip to content

Commit 8608080

Browse files
committed
Fix
1 parent 3555e96 commit 8608080

File tree

1 file changed

+53
-41
lines changed

1 file changed

+53
-41
lines changed

packages/backend/src/altNodes/iconDetection.ts

Lines changed: 53 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,14 @@ const ICON_COMPLEX_VECTOR_TYPES: ReadonlySet<NodeType> = new Set([
1919
"BOOLEAN_OPERATION",
2020
]);
2121

22+
// Types that are considered icons regardless of size if they are top-level
23+
const ICON_TYPES_IGNORE_SIZE: ReadonlySet<NodeType> = new Set([
24+
"VECTOR",
25+
"BOOLEAN_OPERATION",
26+
"POLYGON",
27+
"STAR",
28+
]);
29+
2230
const ICON_CONTAINER_TYPES: ReadonlySet<NodeType> = new Set([
2331
"FRAME",
2432
"GROUP",
@@ -58,32 +66,20 @@ const DISALLOWED_CHILD_TYPES: ReadonlySet<NodeType> = new Set([
5866
// ========================================================================
5967

6068
/**
61-
* Checks if a node's dimensions fall within a typical size range for icons.
69+
* Checks if a node's dimensions fall within a typical *maximum* size for icons.
70+
* Simplified to only check max size.
6271
*/
6372
function isTypicalIconSize(
6473
node: SceneNode,
65-
minSize = 8,
6674
maxSize = 64, // Standard max size
67-
aspectRatioTolerance = 0.5, // Allow slightly non-square icons
6875
): boolean {
6976
if (
7077
!("width" in node && "height" in node && node.width > 0 && node.height > 0)
7178
) {
7279
return false; // Needs dimensions
7380
}
74-
if (
75-
node.width < minSize ||
76-
node.height < minSize ||
77-
node.width > maxSize ||
78-
node.height > maxSize
79-
) {
80-
return false; // Outside size limits
81-
}
82-
const aspectRatio = node.width / node.height;
83-
return (
84-
aspectRatio >= 1 - aspectRatioTolerance &&
85-
aspectRatio <= 1 + aspectRatioTolerance // Check aspect ratio
86-
);
81+
// Only check if dimensions exceed the maximum allowed size
82+
return node.width <= maxSize && node.height <= maxSize;
8783
}
8884

8985
/**
@@ -146,6 +142,9 @@ function checkChildrenRecursively(children: ReadonlyArray<SceneNode>): {
146142
/**
147143
* Analyzes a Figma SceneNode using simplified structural rules to determine if it's likely an icon.
148144
* v5.1: Added rule to always consider nodes with SVG export settings as icons.
145+
* v5.2: Always consider VECTOR nodes as icons, regardless of size.
146+
* v5.3: Always consider VECTOR, BOOLEAN_OPERATION, POLYGON, STAR as icons regardless of size. Simplified size check to max dimension only.
147+
* v5.4: Check for disallowed types *before* checking SVG export settings.
149148
*
150149
* @param node The Figma SceneNode to evaluate.
151150
* @param logDetails Set to true to print debug information to the console.
@@ -156,49 +155,58 @@ export function isLikelyIcon(node: SceneNode, logDetails = false): boolean {
156155
let result = false;
157156
let reason = "";
158157

159-
// --- 1. Check for SVG Export Settings ---
160-
if (hasSvgExportSettings(node)) {
158+
// --- 1. Initial Filtering (Disallowed Types First) ---
159+
if (DISALLOWED_ICON_TYPES.has(node.type)) {
160+
reason = `Disallowed Type: ${node.type}`;
161+
result = false;
162+
}
163+
// --- 2. Check for SVG Export Settings (Only if not disallowed) ---
164+
else if (hasSvgExportSettings(node)) {
161165
reason = "Has SVG export settings";
162166
result = true;
163167
}
164-
// --- 2. Initial Filtering ---
165-
else if (node.visible === false) {
166-
reason = "Invisible";
167-
result = false;
168-
} else if (DISALLOWED_ICON_TYPES.has(node.type)) {
169-
reason = `Disallowed Type: ${node.type}`;
170-
result = false;
171-
} else if (
168+
// --- 3. Dimension Check ---
169+
else if (
172170
!("width" in node && "height" in node && node.width > 0 && node.height > 0)
173171
) {
174-
reason = "No dimensions";
175-
result = false;
172+
// Exception: Allow specific types even without dimensions initially.
173+
if (ICON_TYPES_IGNORE_SIZE.has(node.type)) {
174+
reason = `Direct ${node.type} type (no dimensions check needed)`;
175+
result = true;
176+
} else {
177+
reason = "No dimensions";
178+
result = false;
179+
}
176180
} else {
177-
// --- 3. Direct Vector/Boolean/Primitive ---
178-
if (
179-
ICON_COMPLEX_VECTOR_TYPES.has(node.type) ||
180-
ICON_PRIMITIVE_TYPES.has(node.type)
181-
) {
181+
// --- 4. Direct Vector/Boolean/Primitive ---
182+
// Special case: VECTOR, BOOLEAN_OPERATION, POLYGON, STAR are always icons
183+
if (ICON_TYPES_IGNORE_SIZE.has(node.type)) {
184+
reason = `Direct ${node.type} type (size ignored)`;
185+
result = true;
186+
}
187+
// Check other primitives (ELLIPSE, RECTANGLE, LINE) with size constraint
188+
else if (ICON_PRIMITIVE_TYPES.has(node.type)) {
182189
if (isTypicalIconSize(node)) {
183190
reason = `Direct ${node.type} with typical size`;
184191
result = true;
185192
} else {
186-
reason = `Direct ${node.type} but wrong size (${Math.round(node.width)}x${Math.round(node.height)})`;
193+
reason = `Direct ${node.type} but too large (${Math.round(node.width)}x${Math.round(node.height)})`;
187194
result = false;
188195
}
189196
}
190-
// --- 4. Container Logic ---
197+
// --- 5. Container Logic ---
191198
else if (ICON_CONTAINER_TYPES.has(node.type) && "children" in node) {
199+
// Container size check still uses the simplified isTypicalIconSize
192200
if (!isTypicalIconSize(node)) {
193-
reason = `Container but wrong size (${Math.round(node.width)}x${Math.round(node.height)})`;
201+
reason = `Container but too large (${Math.round(node.width)}x${Math.round(node.height)})`;
194202
result = false;
195203
} else {
196204
const visibleChildren = node.children.filter(
197205
(child) => child.visible !== false,
198206
);
199207

200208
if (visibleChildren.length === 0) {
201-
// Check for styling on empty containers
209+
// Check for styling on empty containers (size already checked)
202210
const hasVisibleFill =
203211
"fills" in node &&
204212
Array.isArray(node.fills) &&
@@ -215,21 +223,25 @@ export function isLikelyIcon(node: SceneNode, logDetails = false): boolean {
215223
node.strokes.some((s) => s.visible !== false);
216224

217225
if (hasVisibleFill || hasVisibleStroke) {
218-
reason = "Empty container with visible fill/stroke";
226+
reason =
227+
"Empty container with visible fill/stroke and typical size";
219228
result = true;
220229
} else {
221230
reason = "Empty container with no visible style";
222-
result = false;
231+
result = false; // Size is okay, but no content or style
223232
}
224233
} else {
225-
// Check content of non-empty containers
234+
// Check content of non-empty containers (size already checked)
226235
const checkResult = checkChildrenRecursively(visibleChildren);
227236

228237
if (checkResult.hasDisallowedChild) {
229238
reason =
230239
"Container has disallowed child type (Text, Frame, Component, Instance, etc.)";
231240
result = false;
232241
} else if (!checkResult.hasValidContent) {
242+
// Allow containers if they *only* contain other groups,
243+
// as long as those groups eventually contain valid content.
244+
// The checkResult.hasValidContent handles this.
233245
reason = "Container has no vector or primitive content";
234246
result = false;
235247
} else {
@@ -239,7 +251,7 @@ export function isLikelyIcon(node: SceneNode, logDetails = false): boolean {
239251
}
240252
}
241253
}
242-
// --- 5. Default ---
254+
// --- 6. Default ---
243255
else {
244256
reason =
245257
"Not a recognized icon structure (Vector, Primitive, or valid Container)";

0 commit comments

Comments
 (0)