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

Feature/5950 Architecture: Icon loading in config #6211

Draft
wants to merge 4 commits into
base: develop
Choose a base branch
from
Draft
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
19 changes: 19 additions & 0 deletions demos/architecture.html
Original file line number Diff line number Diff line change
Expand Up @@ -252,5 +252,24 @@ <h2>External Icons Demo</h2>
},
]);
</script>
<hr />

<h2>Config Loading External Icons Demo</h2>
<pre class="mermaid">
---
title: Config Loading External Icons Demo
config:
architecture:
fontSize: 32
icons:
- name: urllogos
url: 'https://unpkg.com/@iconify-json/logos/icons.json'
---
architecture-beta
service s3(urllogos:aws-s3)[Cloud Store]
service ec2(urllogos:aws-ec2)[Server]
service api(urllogos:aws-api-gateway)[Api Gateway]
</pre>
<hr />
</body>
</html>
2 changes: 1 addition & 1 deletion docs/config/setup/modules/defaultConfig.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

#### Defined in

[packages/mermaid/src/defaultConfig.ts:270](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L270)
[packages/mermaid/src/defaultConfig.ts:281](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L281)

---

Expand Down
42 changes: 42 additions & 0 deletions docs/syntax/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -225,3 +225,45 @@ architecture-beta
disk1:T -- B:server
disk2:T -- B:db
```

### Config Loading

It is also possible to load icons hosted at a URL by declaring them in the config:

```mermaid-example
---
icons:
- name: logos
url: https://unpkg.com/@iconify-json/logos@1/icons.json
---
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
```

```mermaid
---
icons:
- name: logos
url: https://unpkg.com/@iconify-json/logos@1/icons.json
---
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
```
4 changes: 4 additions & 0 deletions packages/mermaid/src/config.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1012,6 +1012,10 @@ export interface RequirementDiagramConfig extends BaseDiagramConfig {
*/
export interface ArchitectureDiagramConfig extends BaseDiagramConfig {
padding?: number;
icons?: {
name: string;
url: string;
}[];
iconSize?: number;
fontSize?: number;
}
Expand Down
15 changes: 13 additions & 2 deletions packages/mermaid/src/defaultConfig.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { RequiredDeep } from 'type-fest';

import theme from './themes/index.js';
import type { MermaidConfig } from './config.type.js';
import theme from './themes/index.js';

// Uses our custom Vite jsonSchemaPlugin to load only the default values from
// our JSON Schema
Expand Down Expand Up @@ -255,12 +255,23 @@ const config: RequiredDeep<MermaidConfig> = {
packet: {
...defaultConfigJson.packet,
},
architecture: {
padding: 40,
fontSize: 16,
iconSize: 80,
icons: [
{
name: 'iconify-logos',
url: 'https://unpkg.com/@iconify-json/logos/icons.json',
},
],
},
};

const keyify = (obj: any, prefix = ''): string[] =>
Object.keys(obj).reduce((res: string[], el): string[] => {
if (Array.isArray(obj[el])) {
return res;
return [...res, prefix + el, ...obj[el].flatMap((it: any) => keyify([it], ''))];
} else if (typeof obj[el] === 'object' && obj[el] !== null) {
return [...res, prefix + el, ...keyify(obj[el], '')];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,6 @@ export const db: ArchitectureDB = {
getAccTitle,
setAccDescription,
getAccDescription,

addService,
getServices,
addJunction,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import type { Architecture } from '@mermaid-js/parser';
import { parse } from '@mermaid-js/parser';
import { log } from '../../logger.js';
import type { ParserDefinition } from '../../diagram-api/types.js';
import { log } from '../../logger.js';
import { populateCommonDb } from '../common/populateCommonDb.js';
import type { ArchitectureDB } from './architectureTypes.js';
import { db } from './architectureDb.js';
import type { ArchitectureDB } from './architectureTypes.js';

const populateDb = (ast: Architecture, db: ArchitectureDB) => {
populateCommonDb(ast, db);
Expand Down
26 changes: 18 additions & 8 deletions packages/mermaid/src/diagrams/architecture/architectureRenderer.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { registerIconPacks } from '../../rendering-util/icons.js';
import type { Position } from 'cytoscape';
import cytoscape from 'cytoscape';
import type { FcoseLayoutOptions } from 'cytoscape-fcose';
import fcose from 'cytoscape-fcose';
import { select } from 'd3';
import { getConfig } from '../../config.js';
import type { MermaidConfig } from '../../config.type.js';
import type { DrawDefinition, SVG } from '../../diagram-api/types.js';
import type { Diagram } from '../../Diagram.js';
import { log } from '../../logger.js';
import { registerIconPacks } from '../../rendering-util/icons.js';
import { selectSvgElement } from '../../rendering-util/selectSvgElement.js';
import { setupGraphViewbox } from '../../setupGraphViewbox.js';
import { getConfigField } from './architectureDb.js';
Expand Down Expand Up @@ -36,12 +38,6 @@ import {
} from './architectureTypes.js';
import { drawEdges, drawGroups, drawJunctions, drawServices } from './svgDraw.js';

registerIconPacks([
{
name: architectureIcons.prefix,
icons: architectureIcons,
},
]);
cytoscape.use(fcose);

function addServices(services: ArchitectureService[], cy: cytoscape.Core) {
Expand Down Expand Up @@ -499,7 +495,21 @@ function layoutArchitecture(
});
}

const loadIcons = (config: MermaidConfig) => {
registerIconPacks([
{
name: architectureIcons.prefix,
icons: architectureIcons,
},
]);

registerIconPacks(config.architecture?.icons ?? []);
};

export const draw: DrawDefinition = async (text, id, _version, diagObj: Diagram) => {
const config = getConfig();
loadIcons(config);

const db = diagObj.db as ArchitectureDB;

const services = db.getServices();
Expand Down Expand Up @@ -528,7 +538,7 @@ export const draw: DrawDefinition = async (text, id, _version, diagObj: Diagram)
await drawGroups(groupElem, cy);
positionNodes(db, cy);

setupGraphViewbox(undefined, svg, getConfigField('padding'), getConfigField('useMaxWidth'));
setupGraphViewbox(undefined, svg, config.architecture?.padding, config.architecture?.useMaxWidth);
};

export const renderer = { draw };
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { DiagramDB } from '../../diagram-api/types.js';
import type cytoscape from 'cytoscape';
import type { ArchitectureDiagramConfig } from '../../config.type.js';
import type { DiagramDB } from '../../diagram-api/types.js';
import type { D3Element } from '../../types.js';
import type cytoscape from 'cytoscape';

/*=======================================*\
| Architecture Diagram Types |
Expand Down
23 changes: 23 additions & 0 deletions packages/mermaid/src/docs/syntax/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,3 +173,26 @@ architecture-beta
disk1:T -- B:server
disk2:T -- B:db
```

### Config Loading

It is also possible to load icons hosted at a URL by declaring them in the config:

```mermaid-example
---
icons:
- name: logos
url: https://unpkg.com/@iconify-json/logos@1/icons.json
---
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
```
24 changes: 23 additions & 1 deletion packages/mermaid/src/preprocess.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,29 @@ export function preprocessDiagram(code: string) {
const cleanedCode = cleanupText(code);
const frontMatterResult = processFrontmatter(cleanedCode);
const directiveResult = processDirectives(frontMatterResult.text);
const config = cleanAndMerge(frontMatterResult.config, directiveResult.directive);
const mergedConfig = cleanAndMerge(frontMatterResult.config, directiveResult.directive);

const hasArchitecture =
frontMatterResult.config.architecture || directiveResult.directive.architecture;

const icons = [
...(frontMatterResult.config.architecture?.icons ?? []),
...(directiveResult.directive.architecture?.icons ?? []),
];

const architecture = {
...(frontMatterResult.config.architecture ?? {}),
...(directiveResult.directive.architecture ?? {}),
icons,
};

const config = hasArchitecture
? {
...mergedConfig,
architecture,
}
: mergedConfig;

code = cleanupComments(directiveResult.text);
return {
code,
Expand Down
12 changes: 9 additions & 3 deletions packages/mermaid/src/rendering-util/icons.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { log } from '../logger.js';
import type { ExtendedIconifyIcon, IconifyIcon, IconifyJSON } from '@iconify/types';
import type { IconifyIconCustomisations } from '@iconify/utils';
import { getIconData, iconToHTML, iconToSVG, replaceIDs, stringToIcon } from '@iconify/utils';
import { log } from '../logger.js';

interface AsyncIconLoader {
name: string;
Expand All @@ -13,7 +13,12 @@ interface SyncIconLoader {
icons: IconifyJSON;
}

export type IconLoader = AsyncIconLoader | SyncIconLoader;
export interface UrlIconLoader {
name: string;
url: string;
}

export type IconLoader = AsyncIconLoader | SyncIconLoader | UrlIconLoader;

export const unknownIcon: IconifyIcon = {
body: '<g><rect width="80" height="80" style="fill: #087ebf; stroke-width: 0px;"/><text transform="translate(21.16 64.67)" style="fill: #fff; font-family: ArialMT, Arial; font-size: 67.75px;"><tspan x="0" y="0">?</tspan></text></g>',
Expand All @@ -36,8 +41,9 @@ export const registerIconPacks = (iconLoaders: IconLoader[]) => {
loaderStore.set(iconLoader.name, iconLoader.loader);
} else if ('icons' in iconLoader) {
iconsStore.set(iconLoader.name, iconLoader.icons);
} else if ('url' in iconLoader) {
loaderStore.set(iconLoader.name, () => fetch(iconLoader.url).then((res) => res.json()));
} else {
log.error('Invalid icon loader:', iconLoader);
throw new Error('Invalid icon loader. Must have either "icons" or "loader" property.');
}
}
Expand Down
20 changes: 12 additions & 8 deletions packages/mermaid/src/schemas/config.schema.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -933,21 +933,25 @@ $defs: # JSON Schema definition (maybe we should move these to a separate file)
description: The object containing configurations specific for architecture diagrams
type: object
unevaluatedProperties: false
required:
- useMaxWidth
- padding
- iconSize
- fontSize
properties:
padding:
type: number
default: 40
icons:
type: array
items:
type: object
required:
- name
- url
properties:
name:
type: string
url:
type: string
iconSize:
type: number
default: 80
fontSize:
type: number
default: 16

MindmapDiagramConfig:
title: Mindmap Diagram Config
Expand Down
16 changes: 8 additions & 8 deletions packages/mermaid/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ import {
curveBumpX,
curveBumpY,
curveBundle,
curveCardinal,
curveCardinalClosed,
curveCardinalOpen,
curveCardinal,
curveCatmullRom,
curveCatmullRomClosed,
curveCatmullRomOpen,
curveCatmullRom,
curveLinear,
curveLinearClosed,
curveMonotoneX,
Expand All @@ -23,16 +23,16 @@ import {
curveStepBefore,
select,
} from 'd3';
import common from './diagrams/common/common.js';
import { sanitizeDirective } from './utils/sanitizeDirective.js';
import { log } from './logger.js';
import { detectType } from './diagram-api/detectType.js';
import assignWithDepth from './assignWithDepth.js';
import type { MermaidConfig } from './config.type.js';
import memoize from 'lodash-es/memoize.js';
import merge from 'lodash-es/merge.js';
import assignWithDepth from './assignWithDepth.js';
import type { MermaidConfig } from './config.type.js';
import { detectType } from './diagram-api/detectType.js';
import { directiveRegex } from './diagram-api/regexes.js';
import common from './diagrams/common/common.js';
import { log } from './logger.js';
import type { D3Element, Point, TextDimensionConfig, TextDimensions } from './types.js';
import { sanitizeDirective } from './utils/sanitizeDirective.js';

export const ZERO_WIDTH_SPACE = '\u200b';

Expand Down
1 change: 0 additions & 1 deletion packages/mermaid/src/utils/sanitizeDirective.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ export const sanitizeDirective = (args: any): void => {
!configKeys.has(key) ||
args[key] == null
) {
log.debug('sanitize deleting key: ', key);
delete args[key];
continue;
}
Expand Down
Loading