import fs from 'fs' import path, { resolve, basename } from 'path' import { fileURLToPath } from 'url' import svgParse from 'parse-svg-path' import svgpath from 'svgpath' import cheerio from 'cheerio'; import { minify } from 'html-minifier'; import { parseSync } from 'svgson' import { optimize } from 'svgo' import cp from 'child_process' import minimist from 'minimist' import glob from 'glob' import matter from 'gray-matter' const defaultProps = { xmlns: "http://www.w3.org/2000/svg", className: "icon icon-tabler", width: "24", height: "24", viewBox: "0 0 24 24", strokeWidth: "2", stroke: "currentColor", fill: "none", strokeLinecap: "round", strokeLinejoin: "round" } export const getCurrentDirPath = () => { return path.dirname(fileURLToPath(import.meta.url)); } export const HOME_DIR = resolve(getCurrentDirPath(), '..') export const ICONS_SRC_DIR = resolve(HOME_DIR, 'src/_icons') export const ICONS_DIR = resolve(HOME_DIR, 'icons') export const PACKAGES_DIR = resolve(HOME_DIR, 'packages') export const getArgvs = () => { return minimist(process.argv.slice(2)) } export const getPackageDir = (packageName) => { return `${PACKAGES_DIR}/${packageName}` } /** * Return project package.json * @returns {any} */ export const getPackageJson = () => { return JSON.parse(fs.readFileSync(resolve(HOME_DIR, 'package.json'), 'utf-8')) } /** * Reads SVGs from directory * * @param directory * @returns {string[]} */ export const readSvgDirectory = (directory) => { return fs.readdirSync(directory).filter((file) => path.extname(file) === '.svg') } export const readSvgs = () => { const svgFiles = readSvgDirectory(ICONS_DIR) return svgFiles.map(svgFile => { const name = basename(svgFile, '.svg'), namePascal = toPascalCase(`icon ${name}`), contents = readSvg(svgFile, ICONS_DIR).trim(), path = resolve(ICONS_DIR, svgFile), obj = parseSync(contents.replace('', '')); return { name, namePascal, contents, obj, path }; }); } /** * Read SVG * * @param fileName * @param directory * @returns {string} */ export const readSvg = (fileName, directory) => { return fs.readFileSync(path.join(directory, fileName), 'utf-8') } /** * Create directory if not exists * @param dir */ export const createDirectory = (dir) => { if (!fs.existsSync(dir)) { fs.mkdirSync(dir); } }; /** * Get SVG name * @param fileName * @returns {string} */ export const getSvgName = (fileName) => { return path.basename(fileName, '.svg') } /** * Convert string to CamelCase * @param string * @returns {*} */ export const toCamelCase = (string) => { return string.replace(/^([A-Z])|[\s-_]+(\w)/g, (match, p1, p2) => p2 ? p2.toUpperCase() : p1.toLowerCase()) } export const toPascalCase = (string) => { const camelCase = toCamelCase(string); return camelCase.charAt(0).toUpperCase() + camelCase.slice(1); } export const addFloats = function(n1, n2) { return Math.round((parseFloat(n1) + parseFloat(n2)) * 1000) / 1000 } export const optimizePath = function(path) { let transformed = svgpath(path).rel().round(3).toString() return svgParse(transformed).map(function(a) { return a.join(' ') }).join('') } export const optimizeSVG = (data) => { return optimize(data, { js2svg: { indent: 2, pretty: true }, plugins: [ { name: 'preset-default', params: { overrides: { mergePaths: false } } }] }).data } export function buildIconsObject(svgFiles, getSvg) { return svgFiles .map(svgFile => { const name = path.basename(svgFile, '.svg'); const svg = getSvg(svgFile); const contents = getSvgContents(svg); return { name, contents }; }) .reduce((icons, icon) => { icons[icon.name] = icon.contents; return icons; }, {}); } function getSvgContents(svg) { const $ = cheerio.load(svg); return minify($('svg').html(), { collapseWhitespace: true }); } export const asyncForEach = async (array, callback) => { for (let index = 0; index < array.length; index++) { await callback(array[index], index, array) } } export const createScreenshot = async (filePath) => { await cp.exec(`rsvg-convert -x 2 -y 2 ${filePath} > ${filePath.replace('.svg', '.png')}`) await cp.exec(`rsvg-convert -x 4 -y 4 ${filePath} > ${filePath.replace('.svg', '@2x.png')}`) } export const generateIconsPreview = async function(files, destFile, { columnsCount = 19, paddingOuter = 7, color = '#354052', background = '#fff' } = {}) { const padding = 20, iconSize = 24 const iconsCount = files.length, rowsCount = Math.ceil(iconsCount / columnsCount), width = columnsCount * (iconSize + padding) + 2 * paddingOuter - padding, height = rowsCount * (iconSize + padding) + 2 * paddingOuter - padding let svgContentSymbols = '', svgContentIcons = '', x = paddingOuter, y = paddingOuter files.forEach(function(file, i) { let name = path.basename(file, '.svg') let svgFile = fs.readFileSync(file), svgFileContent = svgFile.toString() svgFileContent = svgFileContent.replace('', '') .replace(/\n\s+/g, '') svgContentSymbols += `\t${svgFileContent}\n` svgContentIcons += `\t\n` x += padding + iconSize if (i % columnsCount === columnsCount - 1) { x = paddingOuter y += padding + iconSize } }) const svgContent = `\n${svgContentSymbols}\n${svgContentIcons}\n` fs.writeFileSync(destFile, svgContent) await createScreenshot(destFile) } export const printChangelog = function(newIcons, modifiedIcons, renamedIcons, pretty = false) { if (newIcons.length > 0) { if (pretty) { console.log(`### ${newIcons.length} new icons:\n`) newIcons.forEach(function(icon, i) { console.log(`- \`${icon}\``) }) } else { let str = '' str += `${newIcons.length} new icons: ` newIcons.forEach(function(icon, i) { str += `\`${icon}\`` if ((i + 1) <= newIcons.length - 1) { str += ', ' } }) console.log(str) } console.log('') } if (modifiedIcons.length > 0) { let str = '' str += `Fixed icons: ` modifiedIcons.forEach(function(icon, i) { str += `\`${icon}\`` if ((i + 1) <= modifiedIcons.length - 1) { str += ', ' } }) console.log(str) console.log('') } if (renamedIcons.length > 0) { console.log(`Renamed icons: `) renamedIcons.forEach(function(icon, i) { console.log(`- \`${icon[0]}\` renamed to \`${icon[1]}\``) }) } } export const getCompileOptions = () => { const compileOptions = { includeIcons: [], strokeWidth: null, fontForge: 'fontforge' } if (fs.existsSync('../compile-options.json')) { try { const tempOptions = require('../compile-options.json') if (typeof tempOptions !== 'object') { throw 'Compile options file does not contain an json object' } if (typeof tempOptions.includeIcons !== 'undefined') { if (!Array.isArray(tempOptions.includeIcons)) { throw 'property inludeIcons is not an array' } compileOptions.includeIcons = tempOptions.includeIcons } if (typeof tempOptions.includeCategories !== 'undefined') { if (typeof tempOptions.includeCategories === 'string') { tempOptions.includeCategories = tempOptions.includeCategories.split(' ') } if (!Array.isArray(tempOptions.includeCategories)) { throw 'property includeCategories is not an array or string' } const tags = Object.entries(require('./tags.json')) tempOptions.includeCategories.forEach(function(category) { category = category.charAt(0).toUpperCase() + category.slice(1) for (const [icon, data] of tags) { if (data.category === category && compileOptions.includeIcons.indexOf(icon) === -1) { compileOptions.includeIcons.push(icon) } } }) } if (typeof tempOptions.excludeIcons !== 'undefined') { if (!Array.isArray(tempOptions.excludeIcons)) { throw 'property excludeIcons is not an array' } compileOptions.includeIcons = compileOptions.includeIcons.filter(function(icon) { return tempOptions.excludeIcons.indexOf(icon) === -1 }) } if (typeof tempOptions.excludeOffIcons !== 'undefined' && tempOptions.excludeOffIcons) { // Exclude `*-off` icons compileOptions.includeIcons = compileOptions.includeIcons.filter(function(icon) { return !icon.endsWith('-off') }) } if (typeof tempOptions.strokeWidth !== 'undefined') { if (typeof tempOptions.strokeWidth !== 'string' && typeof tempOptions.strokeWidth !== 'number') { throw 'property strokeWidth is not a string or number' } compileOptions.strokeWidth = tempOptions.strokeWidth.toString() } if (typeof tempOptions.fontForge !== 'undefined') { if (typeof tempOptions.fontForge !== 'string') { throw 'property fontForge is not a string' } compileOptions.fontForge = tempOptions.fontForge } } catch (error) { throw `Error reading compile-options.json: ${error}` } } return compileOptions }