Skip to content

Commit

Permalink
feat(ffe-icons): update lg and xl sizes of icons
Browse files Browse the repository at this point in the history
BREAKING CHANGE: The lg size of icons is now 32px, and the xl size is 40px (was 48px).
  • Loading branch information
hagenek committed Jan 20, 2025
1 parent 256d69d commit 1f81bf9
Show file tree
Hide file tree
Showing 56,797 changed files with 57,137 additions and 51,720 deletions.
The diff you're trying to view is too large. We only load the first 3000 changed files.
161 changes: 117 additions & 44 deletions packages/ffe-icons/bin/build.js
Original file line number Diff line number Diff line change
@@ -1,52 +1,125 @@
const path = require('path');
const fs = require('fs');
const fs = require('fs/promises');
const { makedirs } = require('./utils');
const { getIconNames } = require('./getIconNames');
const { getDownloads, downloadAll } = require('./downloadSvgs');
const {
createListOfRemovedIcons,
deleteRemovedIconsFiles,
} = require('./deleteSvg');

(async () => {
const weights = [300, 400, 500];
const sizes = [
{ name: 'sm', opsz: 20 },
{ name: 'md', opsz: 24 },
{ name: 'lg', opsz: 40 },
{ name: 'xl', opsz: 48 },
];
const fill = [0, 1];

const iconNames = await getIconNames();
const listOfRemovedIcons = await createListOfRemovedIcons(iconNames);
let downloads = [];
// eslint-disable-next-line no-unused-vars
for (const weight of weights) {
// eslint-disable-next-line no-unused-vars
for (const fillValue of fill) {
const type = fillValue === 1 ? 'filled' : 'open';

// eslint-disable-next-line no-unused-vars
for (const size of sizes) {
let folderPath = `../icons/${type}/${weight}/${size.name}`;
if (type === 'filled') {
folderPath = `../icons/${type}/${size.name}`;
}
const dirPath = path.resolve(__dirname, folderPath);
if (!fs.existsSync(dirPath)) {
await makedirs(dirPath);
}
if (listOfRemovedIcons.length > 0) {
await deleteRemovedIconsFiles(listOfRemovedIcons, dirPath);
}
downloads = downloads.concat(
getDownloads(iconNames, weight, fillValue, size, dirPath),
const { resizeAllSvgs } = require('./resizeSvg');

/**
* Configuration for icon sizes and their target dimensions
* @type {Array<{name: string, size: number, download: boolean}>}
*/
const SIZES = [
{ name: 'sm', size: 20, download: true },
{ name: 'md', size: 24, download: true },
{ name: 'lg', size: 32, download: false },
{ name: 'xl', size: 40, download: true },
];

const WEIGHTS = [300, 400, 500];
const FILLS = [0, 1]; // 0 = outlined, 1 = filled

/**
* Creates the necessary directory structure for icons
* @param {string} type - 'filled' or 'open'
* @param {number} weight - Font weight
* @param {string} size - Size name
* @returns {Promise<string>} - Path to created directory
*/
async function createIconDirectory(type, weight, size) {
const basePath = path.resolve(__dirname, '../icons');
const dirPath =
type === 'filled'
? path.join(basePath, type, size)
: path.join(basePath, type, weight.toString(), size);

await makedirs(dirPath);
return dirPath;
}

/**
* Downloads and processes icons for a specific configuration
* @param {Array<string>} iconNames - List of icon names to process
* @param {string} type - 'filled' or 'open'
* @param {number} weight - Font weight
* @param {number} fillValue - Fill state (0 or 1)
*/
async function processIconSet(iconNames, type, weight, fillValue) {
console.log(`\n📦 Processing ${type} icons (weight: ${weight})`);

// Download directly for sizes marked for download
const downloadSizes = SIZES.filter(s => s.download);
for (const sizeConfig of downloadSizes) {
const dirPath = await createIconDirectory(
type,
weight,
sizeConfig.name,
);

const downloads = getDownloads(
iconNames,
weight,
fillValue,
{ name: sizeConfig.name, opsz: sizeConfig.size },
dirPath,
);

console.log(`\n⬇️ Downloading ${sizeConfig.size}px SVG files...`);
await downloadAll(downloads);
}

// Create 32px version from 40px
const xlSize = SIZES.find(s => s.name === 'xl');
const lgSize = SIZES.find(s => s.name === 'lg');

if (xlSize && lgSize) {
const xlDirPath = await createIconDirectory(type, weight, xlSize.name);
const lgDirPath = await createIconDirectory(type, weight, lgSize.name);

// Copy 40px files to 32px directory
const files = await fs.readdir(xlDirPath);
for (const file of files) {
if (file.endsWith('.svg')) {
await fs.copyFile(
path.join(xlDirPath, file),
path.join(lgDirPath, file),
);
}
}

// Resize 40px to 32px
console.log('\n📐 Creating 32px version from 40px...');
await resizeAllSvgs(lgDirPath, lgSize.size, {
continueOnError: true,
verbose: true,
});
}
}

/**
* Main build function
*/
async function build() {
try {
console.log('🚀 Starting optimized icon build process...');

console.log('📋 Fetching icon names...');
const iconNames = await getIconNames();
console.log(`✓ Found ${iconNames.length} icons`);

for (const weight of WEIGHTS) {
for (const fillValue of FILLS) {
const type = fillValue === 1 ? 'filled' : 'open';
await processIconSet(iconNames, type, weight, fillValue);
}
}

console.log('\n✨ Build completed successfully!');
} catch (error) {
console.error('\n❌ Build failed:', error);
process.exit(1);
}
console.log('Downloading SVG files...');
await downloadAll(downloads);
console.log('All done!');
})();
}

// Run the build process
build();
224 changes: 224 additions & 0 deletions packages/ffe-icons/bin/resizeSvg.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
const fs = require('fs/promises');
const path = require('path');

/**
* Validates and cleans SVG content
* @param {string} content - SVG content to validate
* @returns {string} - Cleaned SVG content
*/
function validateAndCleanSvg(content) {
if (!content || typeof content !== 'string') {
throw new Error('SVG content must be a non-empty string');
}

// Remove BOM and whitespace
let cleaned = content.trim().replace(/^\uFEFF/, '');

// Handle XML declaration
if (cleaned.startsWith('<?xml')) {
const xmlEnd = cleaned.indexOf('?>');
if (xmlEnd !== -1) {
cleaned = cleaned.slice(xmlEnd + 2).trim();
}
}

// Handle potential doctype
if (cleaned.startsWith('<!DOCTYPE')) {
const doctypeEnd = cleaned.indexOf('>');
if (doctypeEnd !== -1) {
cleaned = cleaned.slice(doctypeEnd + 1).trim();
}
}

// Find SVG tag
const svgStart = cleaned.indexOf('<svg');
if (svgStart === -1) {
throw new Error(`File does not contain an SVG tag`);
}

// Only keep from SVG tag onwards
return cleaned.slice(svgStart);
}

/**
* Updates SVG size while preserving viewBox and aspect ratio
* @param {Buffer|string} svgContent - Original SVG content
* @param {number} size - Target size in pixels
* @returns {string} - Modified SVG content
*/
function updateSvgSize(svgContent, size) {
try {
// Convert Buffer to string if needed
const content = Buffer.isBuffer(svgContent)
? svgContent.toString('utf8')
: svgContent;

// Clean and validate content
const cleanedContent = validateAndCleanSvg(content);

// Find SVG opening tag and its attributes
const svgTagMatch = cleanedContent.match(/<svg([^>]*)>/);
if (!svgTagMatch) {
throw new Error('Could not parse SVG tag');
}

const originalAttrs = svgTagMatch[1];

// Extract existing attributes
const attrs = new Map();
const attrRegex = /(\w+)=["']([^"']+)["']/g;
let match;

// eslint-disable-next-line no-cond-assign
while ((match = attrRegex.exec(originalAttrs)) !== null) {
const [, name, value] = match;
if (name !== 'width' && name !== 'height') {
attrs.set(name, value);
}
}

// Handle viewBox
if (!attrs.has('viewBox')) {
// Try to get original dimensions
const width = originalAttrs.match(/width=["'](\d+)/)?.[1] || size;
const height = originalAttrs.match(/height=["'](\d+)/)?.[1] || size;
attrs.set('viewBox', `0 0 ${width} ${height}`);
}

// Set size attributes
attrs.set('width', size);
attrs.set('height', size);

// Ensure xmlns is present
if (!attrs.has('xmlns')) {
attrs.set('xmlns', 'http://www.w3.org/2000/svg');
}

// Build new SVG tag
const newAttrs = Array.from(attrs.entries())
.map(([name, value]) => `${name}="${value}"`)
.join(' ');

const newTag = `<svg ${newAttrs}>`;

// Replace original tag
return cleanedContent.replace(/<svg[^>]*>/, newTag);
} catch (error) {
throw new Error(`SVG processing failed: ${error.message}`);
}
}

/**
* Processes a single SVG file
* @param {string} filePath - Path to SVG file
* @param {number} targetSize - Desired size in pixels
* @param {Object} options - Processing options
* @returns {Promise<boolean>} - Success status
*/
async function resizeSvg(filePath, targetSize, options = {}) {
const { skipErrors = false, verbose = false } = options;

try {
const content = await fs.readFile(filePath);
const resizedContent = updateSvgSize(content, targetSize);
await fs.writeFile(filePath, resizedContent);

if (verbose) {
console.log(
`✓ Resized ${path.basename(filePath)} to ${targetSize}px`,
);
}
return true;
} catch (error) {
const message = `Failed to process ${path.basename(filePath)}: ${error.message}`;
if (skipErrors) {
console.error(`⚠️ ${message}`);
return false;
}
throw new Error(message);
}
}

/**
* Processes all SVGs in a directory
* @param {string} directory - Directory containing SVGs
* @param {number} targetSize - Desired size in pixels
* @param {Object} options - Processing options
*/
async function resizeAllSvgs(directory, targetSize, options = {}) {
const {
continueOnError = true,
verbose = false,
concurrency = 10,
} = options;

try {
// Ensure directory exists
await fs.access(directory);

// Get SVG files
const files = await fs.readdir(directory);
const svgFiles = files.filter(file =>
file.toLowerCase().endsWith('.svg'),
);

if (svgFiles.length === 0) {
if (verbose) {
console.log(`No SVG files found in ${directory}`);
}
return;
}

if (verbose) {
console.log(`Processing ${svgFiles.length} SVG files...`);
}

// Process files in batches
const batches = [];
for (let i = 0; i < svgFiles.length; i += concurrency) {
const batch = svgFiles.slice(i, i + concurrency);
batches.push(batch);
}

let successful = 0;
let failed = 0;

for (const batch of batches) {
const results = await Promise.all(
batch.map(file =>
resizeSvg(path.join(directory, file), targetSize, {
skipErrors: continueOnError,
verbose,
}),
),
);

successful += results.filter(Boolean).length;
failed += results.filter(r => !r).length;
}

if (verbose) {
console.log(`\nProcessing complete:`);
console.log(`✓ Successfully processed: ${successful} files`);
if (failed > 0) {
console.log(`⚠️ Failed to process: ${failed} files`);
}
}

if (failed > 0 && !continueOnError) {
throw new Error(`Failed to process ${failed} files`);
}
} catch (error) {
if (!continueOnError) {
throw new Error(`Directory processing failed: ${error.message}`);
}
console.error(`⚠️ ${error.message}`);
}
}

module.exports = {
resizeSvg,
resizeAllSvgs,
updateSvgSize,
validateAndCleanSvg,
};
2 changes: 1 addition & 1 deletion packages/ffe-icons/icons/filled/lg/10k.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 1f81bf9

Please sign in to comment.