Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions src/Umbraco.Web.UI.Client/devops/icons/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,36 @@
const iconMapJson = `${moduleDirectory}/icon-dictionary.json`;

const lucideSvgDirectory = 'node_modules/lucide-static/icons';
const lucideTagsJson = 'node_modules/lucide-static/tags.json';
// Snapshot of Lucide's per-icon categories metadata (icon-name -> categories[]),
// extracted from https://github.com/lucide-icons/lucide/tree/main/icons.
// Refresh periodically by re-running the snapshot extraction — see PR history.
const lucideCategoriesJson = 'devops/icons/lucide-categories.json';
const simpleIconsSvgDirectory = 'node_modules/simple-icons/icons';
const customSvgDirectory = `${moduleDirectory}/svgs/custom`;

// Lucide group names that should be normalised to our preferred terms.
const groupRenames = {
'transportation': 'transport',
};

// Case-insensitive dedupe while preserving the order of first occurrence.
const mergeUnique = (...lists) => {
const seen = new Set();
const out = [];
for (const list of lists) {
if (!Array.isArray(list)) continue;
for (const value of list) {
if (typeof value !== 'string') continue;
const key = value.toLowerCase();
if (seen.has(key)) continue;
seen.add(key);
out.push(value);
}
}
return out;
};

Check warning on line 45 in src/Umbraco.Web.UI.Client/devops/icons/index.js

View check run for this annotation

CodeScene Delta Analysis / CodeScene Code Health Review (main)

❌ New issue: Bumpy Road Ahead

mergeUnique has 2 blocks with nested conditional logic. Any nesting of 2 or deeper is considered. Threshold is 2 blocks per function. The Bumpy Road code smell is a function that contains multiple chunks of nested conditional logic. The deeper the nesting and the more bumps, the lower the code health.

const IS_GITHUB_ACTIONS = process.env.GITHUB_ACTIONS === 'true';

const errors = [];
Expand All @@ -36,112 +63,137 @@
const fileRaw = rawData.toString();
const fileJSON = JSON.parse(fileRaw);

// Lucide's own metadata — merged into curated entries so every Lucide-backed
// icon benefits from upstream tags/categories without us having to hand-curate
// each one.
const lucideTags = JSON.parse(readFileSync(lucideTagsJson).toString());
const lucideCategories = JSON.parse(readFileSync(lucideCategoriesJson).toString());

let icons = [];

// Lucide:
fileJSON.lucide.forEach((iconDef) => {
if (iconDef.file && iconDef.name) {
const path = lucideSvgDirectory + '/' + iconDef.file;

try {
const rawData = readFileSync(path);
// For Lucide icons specially we adjust the icons a bit for them to work in our case: [NL]
let svg = rawData.toString().replace(' width="24"\n', '');
svg = svg.replace(' height="24"\n', '');
svg = svg.replace('stroke-width="2"', 'stroke-width="1.75"');
const iconFileName = iconDef.name;

// Merge curated data first (wins on ordering) with Lucide's upstream metadata.
const lucideKey = iconDef.file.replace(/\.svg$/, '');
const keywords = mergeUnique(iconDef.keywords, lucideTags[lucideKey]);
const groups = mergeUnique(iconDef.groups, lucideCategories[lucideKey])
.map((g) => groupRenames[g] ?? g)
.filter((g, i, a) => a.indexOf(g) === i);

const icon = {
name: iconDef.name,
hidden: iconDef.legacy ?? iconDef.internal,
fileName: iconFileName,
svg,
output: `${iconsOutputDirectory}/${iconFileName}.ts`,
keywords,
groups,
related: iconDef.related,
};

icons.push(icon);
} catch {
errors.push(`[Lucide] Could not load file: '${path}'`);
console.log(`[Lucide] Could not load file: '${path}'`);
}
}
});

// SimpleIcons:
fileJSON.simpleIcons.forEach((iconDef) => {
if (iconDef.file && iconDef.name) {
const path = simpleIconsSvgDirectory + '/' + iconDef.file;

try {
const rawData = readFileSync(path);
let svg = rawData.toString();
const iconFileName = iconDef.name;

// SimpleIcons need to use fill="currentColor"
const pattern = /fill=/g;
if (!pattern.test(svg)) {
svg = svg.replace(/<path/g, '<path fill="currentColor"');
}

const icon = {
name: iconDef.name,
legacy: iconDef.legacy,
fileName: iconFileName,
svg,
output: `${iconsOutputDirectory}/${iconFileName}.ts`,
keywords: iconDef.keywords,
groups: iconDef.groups,
related: iconDef.related,
};

icons.push(icon);
} catch {
errors.push(`[SimpleIcons] Could not load file: '${path}'`);
console.log(`[SimpleIcons] Could not load file: '${path}'`);
}
}
});

// Umbraco:
fileJSON.umbraco.forEach((iconDef) => {
if (iconDef.file && iconDef.name) {
const path = umbracoSvgDirectory + '/' + iconDef.file;

try {
const rawData = readFileSync(path);
const svg = rawData.toString();
const iconFileName = iconDef.name;

const icon = {
name: iconDef.name,
legacy: iconDef.legacy,
fileName: iconFileName,
svg,
output: `${iconsOutputDirectory}/${iconFileName}.ts`,
keywords: iconDef.keywords,
groups: iconDef.groups,
related: iconDef.related,
};

icons.push(icon);
} catch {
errors.push(`[Umbraco] Could not load file: '${path}'`);
console.log(`[Umbraco] Could not load file: '${path}'`);
}
}
});

// Custom:
if (fileJSON['custom']) {
fileJSON['custom'].forEach((iconDef) => {
if (iconDef.file && iconDef.name) {
const path = customSvgDirectory + '/' + iconDef.file;

try {
const rawData = readFileSync(path);
const svg = rawData.toString();
const iconFileName = iconDef.name;

const icon = {
name: iconDef.name,
legacy: iconDef.legacy,
fileName: iconFileName,
svg,
output: `${iconsOutputDirectory}/${iconFileName}.ts`,
keywords: iconDef.keywords,
groups: iconDef.groups,
related: iconDef.related,

Check warning on line 196 in src/Umbraco.Web.UI.Client/devops/icons/index.js

View check run for this annotation

CodeScene Delta Analysis / CodeScene Code Health Review (main)

❌ Getting worse: Complex Method

collectDictionaryIcons already has high cyclomatic complexity, and now it increases in Lines of Code from 99 to 118. This function has many conditional statements (e.g. if, for, while), leading to lower code health. Avoid adding more conditionals and code to it without refactoring.
};

icons.push(icon);
Expand Down Expand Up @@ -213,6 +265,9 @@
return `{
name: "${icon.name}",
${icon.hidden || icon.legacy ? 'hidden: true,' : ''}
${icon.keywords?.length ? `keywords: ${JSON.stringify(icon.keywords)},` : ''}
${icon.groups?.length ? `groups: ${JSON.stringify(icon.groups)},` : ''}
${icon.related?.length ? `related: ${JSON.stringify(icon.related)},` : ''}
path: () => import("./icons/${icon.fileName}.js"),
}`
.replace(/\t/g, '') // Regex removes white space [NL]
Expand Down
Loading
Loading