diff options
Diffstat (limited to 'tools/generate-svg.js')
-rwxr-xr-x | tools/generate-svg.js | 87 |
1 files changed, 64 insertions, 23 deletions
diff --git a/tools/generate-svg.js b/tools/generate-svg.js index f744162099..12f3db039d 100755 --- a/tools/generate-svg.js +++ b/tools/generate-svg.js @@ -5,33 +5,27 @@ import {parse} from 'node:path'; import {readFile, writeFile, mkdir} from 'node:fs/promises'; import {fileURLToPath} from 'node:url'; import {exit} from 'node:process'; +import * as fs from 'node:fs'; const glob = (pattern) => fastGlob.sync(pattern, { cwd: fileURLToPath(new URL('..', import.meta.url)), absolute: true, }); -function doExit(err) { - if (err) console.error(err); - exit(err ? 1 : 0); -} - -async function processFile(file, {prefix, fullName} = {}) { - let name; - if (fullName) { - name = fullName; - } else { +async function processAssetsSvgFile(file, {prefix, fullName} = {}) { + let name = fullName; + if (!name) { name = parse(file).name; if (prefix) name = `${prefix}-${name}`; if (prefix === 'octicon') name = name.replace(/-[0-9]+$/, ''); // chop of '-16' on octicons } - // Set the `xmlns` attribute so that the files are displayable in standalone documents // The svg backend module will strip the attribute during startup for inline display const {data} = optimize(await readFile(file, 'utf8'), { plugins: [ {name: 'preset-default'}, {name: 'removeDimensions'}, + {name: 'removeTitle'}, {name: 'prefixIds', params: {prefix: () => name}}, {name: 'addClassesToSVGElement', params: {classNames: ['svg', name]}}, { @@ -44,28 +38,75 @@ async function processFile(file, {prefix, fullName} = {}) { }, ], }); - await writeFile(fileURLToPath(new URL(`../public/assets/img/svg/${name}.svg`, import.meta.url)), data); } -function processFiles(pattern, opts) { - return glob(pattern).map((file) => processFile(file, opts)); +function processAssetsSvgFiles(pattern, opts) { + return glob(pattern).map((file) => processAssetsSvgFile(file, opts)); } -async function main() { - try { - await mkdir(fileURLToPath(new URL('../public/assets/img/svg', import.meta.url)), {recursive: true}); - } catch {} +async function processMaterialFileIcons() { + const files = glob('node_modules/material-icon-theme/icons/*.svg'); + const svgSymbols = {}; + for (const file of files) { + // remove all unnecessary attributes, only keep "viewBox" + const {data} = optimize(await readFile(file, 'utf8'), { + plugins: [ + {name: 'preset-default'}, + {name: 'removeDimensions'}, + {name: 'removeXMLNS'}, + {name: 'removeAttrs', params: {attrs: 'xml:space', elemSeparator: ','}}, + ], + }); + const svgName = parse(file).name; + // intentionally use single quote here to avoid escaping + svgSymbols[svgName] = data.replace(/"/g, `'`); + } + fs.writeFileSync(fileURLToPath(new URL(`../options/fileicon/material-icon-svgs.json`, import.meta.url)), JSON.stringify(svgSymbols, null, 2)); + const vscodeExtensionsJson = await readFile(fileURLToPath(new URL(`generate-svg-vscode-extensions.json`, import.meta.url))); + const vscodeExtensions = JSON.parse(vscodeExtensionsJson); + const iconRulesJson = await readFile(fileURLToPath(new URL(`../node_modules/material-icon-theme/dist/material-icons.json`, import.meta.url))); + const iconRules = JSON.parse(iconRulesJson); + // The rules are from VSCode material-icon-theme, we need to adjust them to our needs + // 1. We only use lowercase filenames to match (it should be good enough for most cases and more efficient) + // 2. We do not have a "Language ID" system: + // * https://code.visualstudio.com/docs/languages/identifiers#_known-language-identifiers + // * https://github.com/microsoft/vscode/tree/1.98.0/extensions + delete iconRules.iconDefinitions; + for (const [k, v] of Object.entries(iconRules.fileNames)) iconRules.fileNames[k.toLowerCase()] = v; + for (const [k, v] of Object.entries(iconRules.folderNames)) iconRules.folderNames[k.toLowerCase()] = v; + for (const [k, v] of Object.entries(iconRules.fileExtensions)) iconRules.fileExtensions[k.toLowerCase()] = v; + // Use VSCode's "Language ID" mapping from its extensions + for (const [_, langIdExtMap] of Object.entries(vscodeExtensions)) { + for (const [langId, names] of Object.entries(langIdExtMap)) { + for (const name of names) { + const nameLower = name.toLowerCase(); + if (nameLower[0] === '.') { + iconRules.fileExtensions[nameLower.substring(1)] ??= langId; + } else { + iconRules.fileNames[nameLower] ??= langId; + } + } + } + } + const iconRulesPretty = JSON.stringify(iconRules, null, 2); + fs.writeFileSync(fileURLToPath(new URL(`../options/fileicon/material-icon-rules.json`, import.meta.url)), iconRulesPretty); +} + +async function main() { + await mkdir(fileURLToPath(new URL('../public/assets/img/svg', import.meta.url)), {recursive: true}); await Promise.all([ - ...processFiles('node_modules/@primer/octicons/build/svg/*-16.svg', {prefix: 'octicon'}), - ...processFiles('web_src/svg/*.svg'), - ...processFiles('public/assets/img/gitea.svg', {fullName: 'gitea-gitea'}), + ...processAssetsSvgFiles('node_modules/@primer/octicons/build/svg/*-16.svg', {prefix: 'octicon'}), + ...processAssetsSvgFiles('web_src/svg/*.svg'), + ...processAssetsSvgFiles('public/assets/img/gitea.svg', {fullName: 'gitea-gitea'}), + processMaterialFileIcons(), ]); } try { - doExit(await main()); + await main(); } catch (err) { - doExit(err); + console.error(err); + exit(1); } |