Skip to content

Commit

Permalink
Merge pull request #1743 from SpareBank1/fetch-svg-icons
Browse files Browse the repository at this point in the history
feat(ffe-icons): legg til scripts for å hente svger fra material symbols
  • Loading branch information
HeleneKassandra authored Jan 15, 2024
2 parents 795fb76 + a7f7264 commit 5cbb050
Show file tree
Hide file tree
Showing 184 changed files with 329 additions and 248 deletions.
33 changes: 33 additions & 0 deletions .github/workflows/update-ffe-icons.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: update ffe-icons svgs

on:
workflow_dispatch:

jobs:
build:
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v2

- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: 18

- name: Install dependencies
run: npm ci

- name: Build icons
run: npm run build:icons
working-directory: packages/ffe-icons

- name: Create Pull Request
uses: peter-evans/create-pull-request@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: 'fix(ffe-icons): oppdater ikon SVGer'
title: 'feat(ffe-icons): oppdater ikon SVGer'
body: "Oppdaterer SVG'ene i ffe-icons til å passe med de som er tilgjengelige i Material Symbols"
branch: 'auto-update-icons'
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { InfoMessage } from '@sb1/ffe-message-box-react';
import { Paragraph } from '@sb1/ffe-core-react';
import { HandlevognIkon } from '@sb1/ffe-icons-react';
import Symbol from '@sb1/ffe-symbols-react';

<InfoMessage title="Handlevognen din er tom" icon={<HandlevognIkon title="Handlevogn, ikon" />}>
<InfoMessage title="Handlevognen din er tom" icon={<Symbol ariaLabel="handlevogn" icon="shopping_cart" />}>
<Paragraph>Du har ingenting i handlevognen din.</Paragraph>
</InfoMessage>
7 changes: 2 additions & 5 deletions component-overview/examples/typography/LinkIcon.jsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import { LinkIcon } from '@sb1/ffe-core-react';
import { SnakkebobleIkon } from '@sb1/ffe-icons-react';
import Symbol from '@sb1/ffe-symbols-react';

<LinkIcon href="https://www.sparebank1.no">
<SnakkebobleIkon
title="Snakk med oss"
style={{ height: '80px' }}
/>
<Symbol ariaLabel="chat" icon="chat" />
</LinkIcon>
106 changes: 47 additions & 59 deletions packages/ffe-icons/bin/build.js
Original file line number Diff line number Diff line change
@@ -1,60 +1,48 @@
#!/usr/bin/env node
'use strict'; // eslint-disable-line strict

const fs = require('fs');
const path = require('path');
const svgstore = require('svgstore');
const mkdirp = require('mkdirp');

const ICONS_PATH = path.join(__dirname, '..', 'icons');

// convenience to avoid having file extension in config
const appendSvgExtension = icons =>
icons.map(name => (name.endsWith('.svg') ? name : `${name}.svg`));

const options = require('yargs')
.config('opts')
.options({
icons: {
default: '**/*.svg',
type: 'array',
coerce: appendSvgExtension,
},
projectIcons: {
type: 'array',
coerce: appendSvgExtension,
},
dest: {
default: 'dist',
normalize: true,
coerce: path.resolve,
},
}).argv;

const matchesIcon = icons =>
icons.includes('*.svg') || icons.includes('**/*.svg')
? () => true
: fileName => icons.includes(path.basename(fileName));

const sprite = svgstore();

fs.readdirSync(ICONS_PATH)
.filter(fileName => fileName.match(/\.svg$/))
.filter(matchesIcon(options.icons))
.forEach(fileName => {
const iconPath = path.join(ICONS_PATH, fileName);
const iconName = path.basename(fileName, '.svg');
sprite.add(iconName, fs.readFileSync(iconPath), 'utf-8');
});

if (options.projectIcons) {
options.projectIcons.forEach(fileName => {
const iconPath = path.join(fileName);
const iconName = path.basename(fileName, '.svg');
sprite.add(iconName, fs.readFileSync(iconPath), 'utf-8');
});
}

mkdirp.sync(options.dest);

fs.writeFileSync(path.join(options.dest, 'ffe-icons.svg'), sprite.toString());
const fs = require('fs');
const { makedirs } = require('./utils');
const { getIconNames } = require('./getIconNames');
const { getDownloads, downloadAll } = require('./downloadSvgs');
const {
createListOfRemovedIcons,
deleteRemovedIconsFiles,
} = require('./deleteSvg');

(async () => {
const weights = [300, 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 = [];

for (const weight of weights) {
for (const fillValue of fill) {
const type = fillValue === 0 ? 'filled' : 'open';
for (const size of sizes) {
const dirPath = path.resolve(
__dirname,
`../icons/${weight}/${type}/${size.name}`,
);
if (!fs.existsSync(dirPath)) {
await makedirs(dirPath);
}
if (listOfRemovedIcons.length > 0) {
await deleteRemovedIconsFiles(listOfRemovedIcons, dirPath);
}
downloads = downloads.concat(
getDownloads(iconNames, weight, fillValue, size, dirPath),
);
}
}
}
console.log('Downloading SVG files...');
await downloadAll(downloads);
console.log('All done!');
})();
49 changes: 49 additions & 0 deletions packages/ffe-icons/bin/deleteSvg.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
const fs = require('fs/promises');
const path = require('path');

/* Function: createListOfRemovedIcons
Creates and returns an array of all the filenames of svg-files that exist,
but are no longer mentioned in the Material Symbols Codepoints.
Since we know all the different subfolder / variations of the icons contain the same iconnames,
we only need to check 1 folder.
*/
const createListOfRemovedIcons = async iconNames => {
const directory = path.resolve(__dirname, '../icons/300/filled/lg');
try {
await fs.access(directory);
const filesInDir = await fs.readdir(directory);
const removedIcons = filesInDir.filter(
fileName => !iconNames.includes(fileName.replace('.svg', '')),
);
return removedIcons;
} catch (err) {
console.log('Directory does not exist in check for removed icons');
return [];
}
};

/* Function: deleteSvgFile
Does the actual deleting of the file
*/
const deleteSvgFile = async fileName => {
try {
await fs.unlink(fileName);
console.log(`Deleted file ${fileName}`);
} catch (err) {
console.error(`Failed to delete file ${fileName}: ${err}`);
}
};

/* Function: deleteRemovedIconsFiles
Loop through the list of fileNames that should be deleted in a specific directory
and call the delete function.
*/
const deleteRemovedIconsFiles = async (listOfRemovedIcons, directory) => {
for (const fileName of listOfRemovedIcons) {
const filePath = path.join(directory, fileName);
await deleteSvgFile(filePath);
}
};

module.exports = { createListOfRemovedIcons, deleteRemovedIconsFiles };
78 changes: 78 additions & 0 deletions packages/ffe-icons/bin/downloadSvgs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
const { apply, fileExists } = require('./utils');
const path = require('path');
const fs = require('fs/promises');

/* Function: generateDownloadUrl
Takes the icon name, fill, weight and size, and generate the url to download the svg from.
*/
const generateDownloadUrl = (iconName, fill, weight, size) => {
let style = `wght${weight}${fill === 0 ? '' : 'fill1'}`;
if (fill === 0 && weight === 400) {
style = 'default'; // Can technically be removed since we're not supporting the standard value of 400 font weight. But keeping it in to make sure nothing break if we decide to add it later
}
return `https://fonts.gstatic.com/s/i/short-term/release/materialsymbolsrounded/${iconName}/${style}/${size}px.svg`;
};

/* Function: getDownloads
Returns an array for each variation of the icons with the download url, filename and filepath.
The array is later used to know what to download and where to save the files.
*/
const getDownloads = (iconNames, weight, fill, size, dirPath) => {
const downloads = [];
if (!iconNames || !weight || !size || fill === undefined) {
throw new Error('iconNames, weight, fill or size is not provided');
}
for (const icon of iconNames) {
const safeIconName = icon; // Fix for icons that has a number as the first character - which is not valid const name
downloads.push({
url: generateDownloadUrl(safeIconName, fill, weight, size.opsz),
fileName: `${icon}.svg`,
filePath: dirPath,
});
}
return downloads;
};

/* Function: download
Does the actual downloading of the file
*/
const download = async downloadElement => {
const { url, fileName, filePath } = downloadElement;
const fileLocation = path.resolve(__dirname, `${filePath}/${fileName}`);
try {
console.log(`Downloading ${fileLocation}`);
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.text();
await fs.writeFile(fileLocation, data);
} catch (error) {
console.error(
`Failed to download file from ${url} to ${fileLocation}. Error: ${error.message}`,
);
}
};

/* Function: downloadAll
Takes all the downloads, and apply the download function to each of them.
Also let you set if you want to ignore existing files, this is on by default.
*/
const downloadAll = async (downloads, { ignoreExisting = true } = {}) => {
let allDownloads = [];
if (ignoreExisting) {
for (const file of downloads) {
const fileAlreadyExists = await fileExists(
`${file.filePath}/${file.fileName}`,
);
if (!fileAlreadyExists) {
allDownloads.push(file);
}
}
} else {
allDownloads = downloads;
}
await apply(download, allDownloads);
};

module.exports = { getDownloads, downloadAll };
18 changes: 18 additions & 0 deletions packages/ffe-icons/bin/getIconNames.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/* Fetches all the icon names by using the codepoints in the font */
const getIconNames = async () => {
let iconNames = [];
const url =
'https://raw.githubusercontent.com/google/material-design-icons/master/variablefont/MaterialSymbolsRounded%5BFILL%2CGRAD%2Copsz%2Cwght%5D.codepoints';
const response = await fetch(url);
const data = await response.text();

const lines = data.split('\n');
const names = lines
.filter(line => line.trim() !== '')
.map(line => `${line.split(' ')[0]}`);
iconNames = [...iconNames, ...names];

return iconNames;
};

module.exports = { getIconNames };
53 changes: 53 additions & 0 deletions packages/ffe-icons/bin/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
const fs = require('fs/promises');

/* Create directory */
const makedirs = async dir => {
try {
await fs.mkdir(dir, { recursive: true });
console.log(`Created ${dir}`);
} catch (err) {
throw new Error(`Failed to create directory ${dir}: ${err}`);
}
};

/* Function: apply
Utility function to run an async function on an array of arguments in parallel with a concurrency limit.
Used to download multiple files at once, but with a concurrency limit to avoid overloading the server.
Arguments:
- func: async function to run (download)
- args: array of arguments to pass to the function (array of download objects)
*/
const apply = async (func, args) => {
const concurrency = 8;
const results = [];
let i = 0;
const next = async () => {
const j = i++;
if (j >= args.length) {
return;
}
try {
results[j] = await func(args[j]);
} catch (error) {
console.error(error);
}
await next();
};
await Promise.all(Array.from({ length: concurrency }, next));
return results;
};

/* Function: fileExists
Checks if a file exists at the given path
*/
const fileExists = async filePath => {
try {
await fs.access(filePath);
return true;
} catch (error) {
// The file doesn't exist or there was an error accessing it
return false;
}
};

module.exports = { makedirs, apply, fileExists };
1 change: 0 additions & 1 deletion packages/ffe-icons/icons/atv-ikon.svg

This file was deleted.

1 change: 0 additions & 1 deletion packages/ffe-icons/icons/badekar-dusj-ikon.svg

This file was deleted.

Loading

0 comments on commit 5cbb050

Please sign in to comment.