/// /* eslint-disable @typescript-eslint/no-var-requires */ const { optimize } = require('svgo'); const fs = require('fs'); const { collectFilenames } = require('./fs'); const path = require('path'); const svgRE = /\.svg$/; const ICONS_DIRNAME = path.join(__dirname, '../app/svg/icons'); const UI_DIRNAME = path.join(__dirname, '../app/components/ui'); const icons = collectFilenames(ICONS_DIRNAME, (n) => svgRE.test(n)); const getDirectories = (source) => fs .readdirSync(source, { withFileTypes: true }) .filter((dirent) => dirent.isDirectory()) .map((dirent) => dirent.name); const titleCase = (string) => { return string[0].toUpperCase() + string.slice(1).toLowerCase(); }; const plugins = (removeFill = true) => { return { plugins: [ { name: 'preset-default', params: { overrides: { removeViewBox: false, inlineStyles: { onlyMatchedOnce: false, }, }, }, }, { name: 'removeAttrs', params: { attrs: [ 'xml', 'class', 'style', 'data-name', 'dataName', 'svg:width', 'svg:height', 'fill-rule', 'clip-path', ], }, }, { name: 'addAttributesToSVGElement', params: { attributes: [ 'width={ `${ width }px` }', 'height={ `${ height }px` }', !removeFill ? 'fill={ `${ fill }` }' : '', ], }, }, { name: 'removeXMLNS' }, ], }; }; const iconPaths = []; const dirs = getDirectories(ICONS_DIRNAME); fs.mkdirSync(`${UI_DIRNAME}/Icons`, { recursive: true }); dirs.forEach((dir) => { fs.mkdirSync(`${UI_DIRNAME}/Icons/${dir.replaceAll('-', '_')}`, { recursive: true, }); }); icons.forEach((icon) => { const fileName = icon.slice(0, -4).replaceAll('-', '_').replaceAll('/', '_'); const name = fileName; const path = `${UI_DIRNAME}/Icons/${name}.tsx`; iconPaths.push({ path: `./Icons/${name}`, name, oldName: icon.slice(0, -4), fileName, }); const svg = fs.readFileSync(`${ICONS_DIRNAME}/${icon}`, 'utf-8'); const canOptimize = !icon.includes('integrations'); const keepOriginal = icon.includes('color'); const { data } = keepOriginal ? { data: svg } : optimize(svg, plugins(canOptimize)); fs.writeFileSync( path, `/* Auto-generated, do not edit */ import React from 'react'; interface Props { size?: number | string; width?: number | string; height?: number | string; fill?: string; } function ${titleCase(fileName)}(props: Props) { const { size = 14, width = size, height = size, fill = '' } = props; return ( ${data .replaceAll(/xlink\:href/g, 'xlinkHref') .replaceAll(/xmlns\:xlink/g, 'xmlnsXlink') .replaceAll(/clip\-path/g, 'clipPath') .replaceAll(/clip\-rule/g, 'clipRule') // hack to keep fill rule for some icons like stop recording square .replaceAll( /clipRule="evenoddCustomFill"/g, 'clipRule="evenodd" fillRule="evenodd"' ) .replaceAll(/fill-rule/g, 'fillRule') .replaceAll(/fill-opacity/g, 'fillOpacity') .replaceAll(/stop-color/g, 'stopColor') .replaceAll(/stroke-width/g, 'strokeWidth') .replaceAll(/stroke-linecap/g, 'strokeLinecap') .replaceAll(/stroke-linejoin/g, 'strokeLinejoin') .replaceAll(/stroke-miterlimit/g, 'strokeMiterlimit') .replaceAll(/xml:space="preserve"/g, '') .replaceAll(/flood-opacity/g, 'floodOpacity') .replaceAll(/stop-opacity/g, 'stopOpacity') .replaceAll( /color-interpolation-filters/g, 'colorInterpolationFilters' )} ); } export default ${titleCase(fileName)}; ` ); }); fs.writeFileSync( `${UI_DIRNAME}/Icons/index.ts`, ` /* Auto-generated, do not edit */ ${iconPaths .map( (icon) => `export { default as ${titleCase(icon.fileName)} } from './${ icon.fileName }';` ) .join('\n')} ` ); // MAIN FILE fs.writeFileSync( `${UI_DIRNAME}/SVG.tsx`, ` /* Auto-generated, do not edit */ import React from 'react'; import { ${iconPaths.map((icon) => ` ${titleCase(icon.fileName)}`).join(',\n')} } from './Icons' export type IconNames = ${icons .map((icon, i) => `'${icon.slice(0, -4)}'`) .join(' | ')}; interface Props { name: IconNames; size?: number | string; width?: number | string; height?: number | string; fill?: string; } /* Auto-generated, do not edit */ const SVG = (props: Props) => { const { name, size = 14, width = size, height = size, fill = '' } = props; switch (name) { ${iconPaths .map((icon) => { return ` ${icon.oldName !== icon.name ? `// case '${icon.oldName}':` : ''} case '${icon.oldName}': return <${titleCase( icon.fileName )} width={ width } height={ height } fill={ fill } />; `; }) .join('')} default: console.trace('Unknown icon name ' + name); } } SVG.displayName = 'SVG'; export default SVG; ` );