From d4fafe3eff5b9bd4596d4d92ca7335fa434c4097 Mon Sep 17 00:00:00 2001 From: LitoMore Date: Thu, 4 Apr 2024 01:12:43 +0800 Subject: [PATCH] feat(logos): support auto-sizing mode --- badge-maker/lib/constants.js | 5 ++++ badge-maker/lib/make-badge.js | 5 +++- core/base-service/base.spec.js | 1 + core/base-service/coalesce-badge.js | 22 ++++++++++++++-- core/base-service/coalesce-badge.spec.js | 14 ++++++++++ core/base-service/openapi.js | 12 +++++++++ core/base-service/openapi.spec.js | 13 ++++++++++ lib/load-simple-icons.js | 9 +++---- lib/load-simple-icons.spec.js | 2 +- lib/logos.js | 31 ++++++++++++++++------ lib/svg-helpers.js | 33 +++++++++++++++++++++++- package-lock.json | 18 +++++++++++++ package.json | 1 + services/endpoint/endpoint.service.js | 9 ++++--- services/endpoint/endpoint.tester.js | 17 ------------ 15 files changed, 154 insertions(+), 38 deletions(-) create mode 100644 badge-maker/lib/constants.js diff --git a/badge-maker/lib/constants.js b/badge-maker/lib/constants.js new file mode 100644 index 0000000000000..82f8b1d301ab5 --- /dev/null +++ b/badge-maker/lib/constants.js @@ -0,0 +1,5 @@ +const DEFAULT_LOGO_HEIGHT = 14 + +module.exports = { + DEFAULT_LOGO_HEIGHT, +} diff --git a/badge-maker/lib/make-badge.js b/badge-maker/lib/make-badge.js index fd68199e64be9..bb96c62867c9c 100644 --- a/badge-maker/lib/make-badge.js +++ b/badge-maker/lib/make-badge.js @@ -3,6 +3,7 @@ const { normalizeColor, toSvgColor } = require('./color') const badgeRenderers = require('./badge-renderers') const { stripXmlWhitespace } = require('./xml') +const { DEFAULT_LOGO_HEIGHT } = require('./constants') /* note: makeBadge() is fairly thinly wrapped so if we are making changes here @@ -17,6 +18,7 @@ module.exports = function makeBadge({ labelColor, logo, logoPosition, + logoSize, logoWidth, links = ['', ''], }) { @@ -45,7 +47,7 @@ module.exports = function makeBadge({ throw new Error(`Unknown badge style: '${style}'`) } - logoWidth = +logoWidth || (logo ? 14 : 0) + logoWidth = +logoWidth || (logo ? DEFAULT_LOGO_HEIGHT : 0) return stripXmlWhitespace( render({ @@ -55,6 +57,7 @@ module.exports = function makeBadge({ logo, logoPosition, logoWidth, + logoSize, logoPadding: logo && label.length ? 3 : 0, color: toSvgColor(color), labelColor: toSvgColor(labelColor), diff --git a/core/base-service/base.spec.js b/core/base-service/base.spec.js index 575f0386cc9db..e5878c7729438 100644 --- a/core/base-service/base.spec.js +++ b/core/base-service/base.spec.js @@ -377,6 +377,7 @@ describe('BaseService', function () { namedLogo: undefined, logo: undefined, logoWidth: undefined, + logoSize: undefined, logoPosition: undefined, links: [], labelColor: undefined, diff --git a/core/base-service/coalesce-badge.js b/core/base-service/coalesce-badge.js index d07214b61d8e5..325745c498ed7 100644 --- a/core/base-service/coalesce-badge.js +++ b/core/base-service/coalesce-badge.js @@ -2,7 +2,8 @@ import { decodeDataUrlFromQueryParam, prepareNamedLogo, } from '../../lib/logos.js' -import { svg2base64 } from '../../lib/svg-helpers.js' +import { svg2base64, getIconSize } from '../../lib/svg-helpers.js' +import { DEFAULT_LOGO_HEIGHT } from '../../badge-maker/lib/constants.js' import coalesce from './coalesce.js' import toArray from './to-array.js' @@ -56,6 +57,7 @@ export default function coalesceBadge( let { logoWidth: overrideLogoWidth, logoPosition: overrideLogoPosition, + logoSize: overrideLogoSize, color: overrideColor, labelColor: overrideLabelColor, } = overrides @@ -87,6 +89,7 @@ export default function coalesceBadge( logoSvg: serviceLogoSvg, namedLogo: serviceNamedLogo, logoColor: serviceLogoColor, + logoSize: serviceLogoSize, logoWidth: serviceLogoWidth, logoPosition: serviceLogoPosition, link: serviceLink, @@ -119,7 +122,12 @@ export default function coalesceBadge( style = 'flat' } - let namedLogo, namedLogoColor, logoWidth, logoPosition, logoSvgBase64 + let namedLogo, + namedLogoColor, + logoSize, + logoWidth, + logoPosition, + logoSvgBase64 if (overrideLogo) { // `?logo=` could be a named logo or encoded svg. const overrideLogoSvgBase64 = decodeDataUrlFromQueryParam(overrideLogo) @@ -133,6 +141,7 @@ export default function coalesceBadge( } // If the logo has been overridden it does not make sense to inherit the // original width or position. + logoSize = overrideLogoSize logoWidth = overrideLogoWidth logoPosition = overrideLogoPosition } else { @@ -145,13 +154,21 @@ export default function coalesceBadge( ) namedLogoColor = coalesce(overrideLogoColor, serviceLogoColor) } + logoSize = coalesce(overrideLogoSize, serviceLogoSize) logoWidth = coalesce(overrideLogoWidth, serviceLogoWidth) logoPosition = coalesce(overrideLogoPosition, serviceLogoPosition) } if (namedLogo) { + const iconSize = getIconSize(String(namedLogo).toLowerCase()) + + if (!logoWidth && iconSize && logoSize === 'auto') { + logoWidth = (iconSize.width / iconSize.height) * DEFAULT_LOGO_HEIGHT + } + logoSvgBase64 = prepareNamedLogo({ name: namedLogo, color: namedLogoColor, + size: logoSize, style, }) } @@ -179,6 +196,7 @@ export default function coalesceBadge( logo: logoSvgBase64, logoWidth, logoPosition, + logoSize, links: toArray(overrideLink || serviceLink), cacheLengthSeconds: coalesce(serviceCacheSeconds, defaultCacheSeconds), } diff --git a/core/base-service/coalesce-badge.spec.js b/core/base-service/coalesce-badge.spec.js index 9cf4a7624f77a..27969c6e0a1a2 100644 --- a/core/base-service/coalesce-badge.spec.js +++ b/core/base-service/coalesce-badge.spec.js @@ -260,6 +260,20 @@ describe('coalesceBadge', function () { }) }) + describe('Logo size', function () { + it('overrides the logoSize', function () { + expect(coalesceBadge({ logoSize: 'auto' }, {}, {})).to.include({ + logoSize: 'auto', + }) + }) + + it('applies the logo size', function () { + expect( + coalesceBadge({}, { namedLogo: 'npm', logoSize: 'auto' }, {}), + ).to.include({ logoSize: 'auto' }) + }) + }) + describe('Logo width', function () { it('overrides the logoWidth', function () { expect(coalesceBadge({ logoWidth: 20 }, {}, {})).to.include({ diff --git a/core/base-service/openapi.js b/core/base-service/openapi.js index 3eabf9cb99f4f..7ba3d8696750b 100644 --- a/core/base-service/openapi.js +++ b/core/base-service/openapi.js @@ -9,6 +9,7 @@ const globalParamRefs = [ { $ref: '#/components/parameters/style' }, { $ref: '#/components/parameters/logo' }, { $ref: '#/components/parameters/logoColor' }, + { $ref: '#/components/parameters/logoSize' }, { $ref: '#/components/parameters/label' }, { $ref: '#/components/parameters/labelColor' }, { $ref: '#/components/parameters/color' }, @@ -140,6 +141,17 @@ function category2openapi({ category, services, sort = false }) { }, example: 'violet', }, + logoSize: { + name: 'logoSize', + in: 'query', + required: false, + description: + "Make icons adaptively resize by setting `auto`. It's useful for some wider logos like `amd` and `amg`.", + schema: { + type: 'string', + }, + example: 'auto', + }, label: { name: 'label', in: 'query', diff --git a/core/base-service/openapi.spec.js b/core/base-service/openapi.spec.js index ca075f65bf88c..10565f1a9ab7d 100644 --- a/core/base-service/openapi.spec.js +++ b/core/base-service/openapi.spec.js @@ -93,6 +93,17 @@ const expected = { schema: { type: 'string' }, example: 'violet', }, + logoSize: { + name: 'logoSize', + in: 'query', + required: false, + description: + "Make icons adaptively resize by setting `auto`. It's useful for some wider logos like `amd` and `amg`.", + schema: { + type: 'string', + }, + example: 'auto', + }, label: { name: 'label', in: 'query', @@ -158,6 +169,7 @@ const expected = { { $ref: '#/components/parameters/style' }, { $ref: '#/components/parameters/logo' }, { $ref: '#/components/parameters/logoColor' }, + { $ref: '#/components/parameters/logoSize' }, { $ref: '#/components/parameters/label' }, { $ref: '#/components/parameters/labelColor' }, { $ref: '#/components/parameters/color' }, @@ -213,6 +225,7 @@ const expected = { { $ref: '#/components/parameters/style' }, { $ref: '#/components/parameters/logo' }, { $ref: '#/components/parameters/logoColor' }, + { $ref: '#/components/parameters/logoSize' }, { $ref: '#/components/parameters/label' }, { $ref: '#/components/parameters/labelColor' }, { $ref: '#/components/parameters/color' }, diff --git a/lib/load-simple-icons.js b/lib/load-simple-icons.js index b2fa89b1c457b..e60e74a106b1d 100644 --- a/lib/load-simple-icons.js +++ b/lib/load-simple-icons.js @@ -1,5 +1,4 @@ import * as originalSimpleIcons from 'simple-icons/icons' -import { svg2base64 } from './svg-helpers.js' function loadSimpleIcons() { const simpleIcons = {} @@ -16,10 +15,10 @@ function loadSimpleIcons() { const icon = originalSimpleIcons[key] const { title, slug, hex } = icon - icon.base64 = { - default: svg2base64(icon.svg.replace(' iconHeight) { + const path = resetIconPosition(simpleIcons[key].path) + iconSvg = iconSvg + .replace('viewBox="0 0 24 24"', `viewBox="0 0 24 ${iconHeight}"`) + .replace(//, ``) + } } + + return svg2base64(iconSvg) } -function prepareNamedLogo({ name, color, style }) { +function prepareNamedLogo({ name, color, style, size }) { if (typeof name !== 'string') { return undefined } @@ -118,7 +131,8 @@ function prepareNamedLogo({ name, color, style }) { } return ( - getShieldsIcon({ name, color }) || getSimpleIcon({ name, color, style }) + getShieldsIcon({ name, color }) || + getSimpleIcon({ name, color, style, size }) ) } @@ -131,6 +145,7 @@ function makeLogo(defaultNamedLogo, overrides) { name: coalesce(overrides.logo, defaultNamedLogo), color: overrides.logoColor, style: overrides.style, + size: overrides.logoSize, }) } } diff --git a/lib/svg-helpers.js b/lib/svg-helpers.js index e36bf10e1981a..f564ab3555049 100644 --- a/lib/svg-helpers.js +++ b/lib/svg-helpers.js @@ -1,7 +1,38 @@ +import SVGPathCommander from 'svg-path-commander' +import loadSimpleIcons from './load-simple-icons.js' + function svg2base64(svg) { return `data:image/svg+xml;base64,${Buffer.from(svg.trim()).toString( 'base64', )}` } -export { svg2base64 } +function getIconSize(iconKey) { + const simpleIcons = loadSimpleIcons() + + if (!(iconKey in simpleIcons)) { + return undefined + } + + const { + width, + height, + x: x0, + y: y0, + x2: x1, + y2: y1, + } = SVGPathCommander.getPathBBox(simpleIcons[iconKey].path) + + return { width, height, x0, y0, x1, y1 } +} + +function resetIconPosition(path) { + const { x: offsetX, y: offsetY } = SVGPathCommander.getPathBBox(path) + const pathReset = new SVGPathCommander(path) + .transform({ translate: [-offsetX, -offsetY] }) + .toString() + + return pathReset +} + +export { svg2base64, getIconSize, resetIconPosition } diff --git a/package-lock.json b/package-lock.json index bb58d66183d03..3151ae311d6e9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -53,6 +53,7 @@ "semver": "~7.6.0", "simple-icons": "11.10.0", "smol-toml": "1.1.4", + "svg-path-commander": "^2.0.9", "webextension-store-meta": "^1.1.0", "xpath": "~0.0.34" }, @@ -5081,6 +5082,11 @@ "node": ">=14.16" } }, + "node_modules/@thednp/dommatrix": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@thednp/dommatrix/-/dommatrix-2.0.6.tgz", + "integrity": "sha512-DXQq4Rs/akYzeXYGkNy3KiJ4JoD8+SYr1QRWTXtAGoZ0+vJcyBt0aeqA1K4CxPaBaIfKdOTE+Te1HV9sAQ4I4A==" + }, "node_modules/@tokenizer/token": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", @@ -27483,6 +27489,18 @@ "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==", "dev": true }, + "node_modules/svg-path-commander": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/svg-path-commander/-/svg-path-commander-2.0.9.tgz", + "integrity": "sha512-VfRLznHewlpQvuahtBK0MT/PlWAapbTx8RSytqgaVwD3US2keKcc3WYYlBBk4vIOR+jB3nQu/NAVlWHKlo0Fjw==", + "dependencies": { + "@thednp/dommatrix": "^2.0.6" + }, + "engines": { + "node": ">=16", + "pnpm": ">=8.6.0" + } + }, "node_modules/svgo": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz", diff --git a/package.json b/package.json index b19362b0e51a0..ae9847c522015 100644 --- a/package.json +++ b/package.json @@ -65,6 +65,7 @@ "semver": "~7.6.0", "simple-icons": "11.10.0", "smol-toml": "1.1.4", + "svg-path-commander": "^2.0.9", "webextension-store-meta": "^1.1.0", "xpath": "~0.0.34" }, diff --git a/services/endpoint/endpoint.service.js b/services/endpoint/endpoint.service.js index 80a0664f89c19..88b7f72601e46 100644 --- a/services/endpoint/endpoint.service.js +++ b/services/endpoint/endpoint.service.js @@ -96,10 +96,11 @@ The endpoint badge takes a single required query param: url, which - logoWidth + logoSize - Default: none. Same meaning as the query string. Can be overridden by - the query string. + Default: none. Same meaning as the query string. + Make icons adaptively resize by setting auto. + It's useful for some wider logos like amd and amg. @@ -156,6 +157,7 @@ export default class Endpoint extends BaseJsonService { namedLogo, logoSvg, logoColor, + logoSize, logoWidth, logoPosition, style, @@ -170,6 +172,7 @@ export default class Endpoint extends BaseJsonService { namedLogo, logoSvg, logoColor, + logoSize, logoWidth, logoPosition, style, diff --git a/services/endpoint/endpoint.tester.js b/services/endpoint/endpoint.tester.js index 2fb43865b1a7a..0a596e6120b4c 100644 --- a/services/endpoint/endpoint.tester.js +++ b/services/endpoint/endpoint.tester.js @@ -102,23 +102,6 @@ t.create('custom svg logo') expect(body).to.include(getShieldsIcon({ name: 'npm' })) }) -t.create('logoWidth') - .get('.json?url=https://example.com/badge') - .intercept(nock => - nock('https://example.com/').get('/badge').reply(200, { - schemaVersion: 1, - label: 'hey', - message: 'yo', - logoSvg, - logoWidth: 30, - }), - ) - .expectBadge({ - label: 'hey', - message: 'yo', - logoWidth: 30, - }) - t.create('Invalid schema') .get('.json?url=https://example.com/badge') .intercept(nock =>