Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Iconify support #827

Merged
merged 8 commits into from
Jan 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions run-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@ for format in "svg" "png"; do
-o "$outputFileName"
done

# Test if passing custom Iconify icons work
outputFileName="/data/$INPUT_DATA/architecture-diagram-logos-with-icons.png"
docker run --rm -v $(pwd):/data $IMAGETAG \
-i /data/$INPUT_DATA/architecture-diagram-logos.mmd \
--iconPacks '@iconify-json/logos' \
-o "$outputFileName"

# Test if a diagram from STDIN can be understood
cat $INPUT_DATA/flowchart1.mmd | docker run --rm -i -v $(pwd):/data $IMAGETAG -o /data/$INPUT_DATA/flowchart1-stdin.png -w 800

Expand Down
8 changes: 8 additions & 0 deletions src-test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -466,5 +466,13 @@ describe("NodeJS API (import ... from '@mermaid-js/mermaid-cli')", () => {
expect(result).toMatchObject({ title: 'Hi', desc: 'World' })
expectBytesAreFormat(result.data, 'svg')
})

test('should show Iconify icon packs', async () => {
const mmdInput = 'architecture-beta\n group aws(logos:aws)[AWS]'
const result = await renderMermaid(browser, mmdInput, 'svg', { iconPacks: ['@iconify-json/logos'] })
expectBytesAreFormat(result.data, 'svg')
const decoder = new TextDecoder()
expect(decoder.decode(result.data)).toContain('<path')
})
})
})
22 changes: 17 additions & 5 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,11 +120,12 @@ async function cli () {
.option('-f, --pdfFit', 'Scale PDF to fit chart')
.option('-q, --quiet', 'Suppress log output')
.option('-p --puppeteerConfigFile [puppeteerConfigFile]', 'JSON configuration file for puppeteer.')
.option('--iconPacks <icons...>', 'Icon packs to use, e.g. @iconify-json/logos. These should be Iconfiy NPM packages that expose a icons.json file, see https://iconify.design/docs/icons/json.html. These will be downloaded from https://unkpg.com when needed.', [])
.parse(process.argv)

const options = commander.opts()

let { theme, width, height, input, output, outputFormat, backgroundColor, configFile, cssFile, svgId, puppeteerConfigFile, scale, pdfFit, quiet } = options
let { theme, width, height, input, output, outputFormat, backgroundColor, configFile, cssFile, svgId, puppeteerConfigFile, scale, pdfFit, quiet, iconPacks } = options

// check input file
if (!input) {
Expand Down Expand Up @@ -205,7 +206,7 @@ async function cli () {
quiet,
outputFormat,
parseMMDOptions: {
mermaidConfig, backgroundColor, myCSS, pdfFit, viewport: { width, height, deviceScaleFactor: scale }, svgId
mermaidConfig, backgroundColor, myCSS, pdfFit, viewport: { width, height, deviceScaleFactor: scale }, svgId, iconPacks
}
}
)
Expand All @@ -219,6 +220,7 @@ async function cli () {
* @property {CSSStyleDeclaration["cssText"]} [myCSS] - Optional CSS text.
* @property {boolean} [pdfFit] - If set, scale PDF to fit chart.
* @property {string} [svgId] - The id attribute for the SVG element to be rendered.
* @property {string[]} [iconPacks] - Icon packages to use.
*/

/**
Expand All @@ -231,7 +233,7 @@ async function cli () {
* @returns {Promise<{title: string | null, desc: string | null, data: Uint8Array}>} The output file in bytes,
* with optional metadata.
*/
async function renderMermaid (browser, definition, outputFormat, { viewport, backgroundColor = 'white', mermaidConfig = {}, myCSS, pdfFit, svgId } = {}) {
async function renderMermaid (browser, definition, outputFormat, { viewport, backgroundColor = 'white', mermaidConfig = {}, myCSS, pdfFit, svgId, iconPacks = [] } = {}) {
const page = await browser.newPage()
page.on('console', (msg) => {
console.warn(msg.text())
Expand All @@ -249,7 +251,7 @@ async function renderMermaid (browser, definition, outputFormat, { viewport, bac
page.addScriptTag({ path: mermaidIIFEPath }),
page.addScriptTag({ path: zenumlIIFEPath })
])
const metadata = await page.$eval('#container', async (container, definition, mermaidConfig, myCSS, backgroundColor, svgId) => {
const metadata = await page.$eval('#container', async (container, definition, mermaidConfig, myCSS, backgroundColor, svgId, iconPacks) => {
await Promise.all(Array.from(document.fonts, (font) => font.load()))

/**
Expand All @@ -264,6 +266,16 @@ async function renderMermaid (browser, definition, outputFormat, { viewport, bac

await mermaid.registerExternalDiagrams([zenuml])
mermaid.registerLayoutLoaders(elkLayouts)
// lazy load icon packs
mermaid.registerIconPacks(
iconPacks.map((icon) => ({
name: icon.split('/')[1],
loader: () =>
fetch(`https://unpkg.com/${icon}/icons.json`)
.then((res) => res.json())
.catch(() => error(`Failed to fetch icon: ${icon}`))
}))
)
mermaid.initialize({ startOnLoad: false, ...mermaidConfig })
// should throw an error if mmd diagram is invalid
const { svg: svgText } = await mermaid.render(svgId || 'my-svg', definition, container)
Expand Down Expand Up @@ -302,7 +314,7 @@ async function renderMermaid (browser, definition, outputFormat, { viewport, bac
return {
title, desc
}
}, definition, mermaidConfig, myCSS, backgroundColor, svgId)
}, definition, mermaidConfig, myCSS, backgroundColor, svgId, iconPacks)

if (outputFormat === 'svg') {
const svgXML = await page.$eval('svg', (svg) => {
Expand Down
11 changes: 11 additions & 0 deletions test-positive/architecture-diagram-logos.mmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
architecture-beta
group api(logos:aws-lambda)[API]

service db(logos:aws-aurora)[Database] in api
service disk1(logos:aws-glacier)[Storage] in api
service disk2(logos:aws-s3)[Storage] in api
service server(logos:aws-ec2)[Server] in api

db:L -- R:server
disk1:T -- B:server
disk2:T -- B:db
Loading