diff --git a/.build/jsonSchema.ts b/.build/jsonSchema.ts index 2e9260d58..50b9ff097 100644 --- a/.build/jsonSchema.ts +++ b/.build/jsonSchema.ts @@ -25,7 +25,7 @@ const MERMAID_CONFIG_DIAGRAM_KEYS = [ 'sankey', 'block', 'packet', - 'architecture' + 'architecture', ] as const; /** diff --git a/packages/mermaid/src/diagrams/architecture/architectureDb.ts b/packages/mermaid/src/diagrams/architecture/architectureDb.ts index 506178513..d3338260f 100644 --- a/packages/mermaid/src/diagrams/architecture/architectureDb.ts +++ b/packages/mermaid/src/diagrams/architecture/architectureDb.ts @@ -40,17 +40,25 @@ const state = new ImperativeState(() => ({ registeredIds: {}, config: DEFAULT_ARCHITECTURE_CONFIG, dataStructures: undefined, - elements: {} -})) + elements: {}, +})); const clear = (): void => { - state.reset() + state.reset(); commonClear(); }; -const addService = function ({ id, icon, in: parent, title, iconText }: Omit) { +const addService = function ({ + id, + icon, + in: parent, + title, + iconText, +}: Omit) { if (state.records.registeredIds[id] !== undefined) { - throw new Error(`The service id [${id}] is already in use by another ${state.records.registeredIds[id]}`); + throw new Error( + `The service id [${id}] is already in use by another ${state.records.registeredIds[id]}` + ); } if (parent !== undefined) { if (id === parent) { @@ -82,7 +90,9 @@ const getServices = (): ArchitectureService[] => Object.values(state.records.ser const addGroup = function ({ id, icon, in: parent, title }: ArchitectureGroup) { if (state.records.registeredIds[id] !== undefined) { - throw new Error(`The group id [${id}] is already in use by another ${state.records.registeredIds[id]}`); + throw new Error( + `The group id [${id}] is already in use by another ${state.records.registeredIds[id]}` + ); } if (parent !== undefined) { if (id === parent) { @@ -111,10 +121,15 @@ const getGroups = (): ArchitectureGroup[] => { return Object.values(state.records.groups); }; -const addEdge = function ( - { lhsId, rhsId, lhsDir, rhsDir, lhsInto, rhsInto, title }: ArchitectureEdge -) { - +const addEdge = function ({ + lhsId, + rhsId, + lhsDir, + rhsDir, + lhsInto, + rhsInto, + title, +}: ArchitectureEdge) { if (!isArchitectureDirection(lhsDir)) { throw new Error( `Invalid direction given for left hand side of edge ${lhsId}--${rhsId}. Expected (L,R,T,B) got ${lhsDir}` @@ -152,7 +167,6 @@ const addEdge = function ( state.records.services[lhsId].edges.push(state.records.edges[state.records.edges.length - 1]); state.records.services[rhsId].edges.push(state.records.edges[state.records.edges.length - 1]); } else if (state.records.groups[lhsId] && state.records.groups[rhsId]) { - } }; @@ -168,28 +182,27 @@ const getDataStructures = () => { // Create an adjacency list of the diagram to perform BFS on // Outer reduce applied on all services // Inner reduce applied on the edges for a service - const adjList = Object.entries(state.records.services).reduce<{ [id: string]: ArchitectureDirectionPairMap }>( - (prevOuter, [id, service]) => { - prevOuter[id] = service.edges.reduce((prevInner, edge) => { - if (edge.lhsId === id) { - // source is LHS - const pair = getArchitectureDirectionPair(edge.lhsDir, edge.rhsDir); - if (pair) { - prevInner[pair] = edge.rhsId; - } - } else { - // source is RHS - const pair = getArchitectureDirectionPair(edge.rhsDir, edge.lhsDir); - if (pair) { - prevInner[pair] = edge.lhsId; - } + const adjList = Object.entries(state.records.services).reduce<{ + [id: string]: ArchitectureDirectionPairMap; + }>((prevOuter, [id, service]) => { + prevOuter[id] = service.edges.reduce((prevInner, edge) => { + if (edge.lhsId === id) { + // source is LHS + const pair = getArchitectureDirectionPair(edge.lhsDir, edge.rhsDir); + if (pair) { + prevInner[pair] = edge.rhsId; } - return prevInner; - }, {}); - return prevOuter; - }, - {} - ); + } else { + // source is RHS + const pair = getArchitectureDirectionPair(edge.rhsDir, edge.lhsDir); + if (pair) { + prevInner[pair] = edge.lhsId; + } + } + return prevInner; + }, {}); + return prevOuter; + }, {}); // Configuration for the initial pass of BFS const firstId = Object.keys(adjList)[0]; diff --git a/packages/mermaid/src/diagrams/architecture/architectureRenderer.ts b/packages/mermaid/src/diagrams/architecture/architectureRenderer.ts index ff97fccff..dd02c2f3d 100644 --- a/packages/mermaid/src/diagrams/architecture/architectureRenderer.ts +++ b/packages/mermaid/src/diagrams/architecture/architectureRenderer.ts @@ -12,7 +12,7 @@ import type { ArchitectureDataStructures, ArchitectureSpatialMap, EdgeSingularData, - EdgeSingular + EdgeSingular, } from './architectureTypes.js'; import { type ArchitectureDB, @@ -25,7 +25,7 @@ import { isArchitectureDirectionXY, isArchitectureDirectionY, nodeData, - edgeData + edgeData, } from './architectureTypes.js'; import { select } from 'd3'; import { setupGraphViewbox } from '../../setupGraphViewbox.js'; @@ -54,8 +54,10 @@ function addServices(services: ArchitectureService[], cy: cytoscape.Core) { function positionServices(db: ArchitectureDB, cy: cytoscape.Core) { cy.nodes().map((node) => { - const data = nodeData(node) - if (data.type === 'group') { return; } + const data = nodeData(node); + if (data.type === 'group') { + return; + } data.x = node.position().x; data.y = node.position().y; @@ -126,8 +128,12 @@ function getAlignments(spatialMaps: ArchitectureSpatialMap[]): fcose.FcoseAlignm const verticalAlignments: Record = {}; // Group service ids in an object with their x and y coordinate as the key Object.entries(spatialMap).forEach(([id, [x, y]]) => { - if (!horizontalAlignments[y]) { horizontalAlignments[y] = []; } - if (!verticalAlignments[x]) { verticalAlignments[x] = []; } + if (!horizontalAlignments[y]) { + horizontalAlignments[y] = []; + } + if (!verticalAlignments[x]) { + verticalAlignments[x] = []; + } horizontalAlignments[y].push(id); verticalAlignments[x].push(id); }); @@ -221,7 +227,7 @@ function layoutArchitecture( selector: 'edge', style: { 'curve-style': 'straight', - 'label': 'data(label)', + label: 'data(label)', 'source-endpoint': 'data(sourceEndpoint)', 'target-endpoint': 'data(targetEndpoint)', }, @@ -300,16 +306,16 @@ function layoutArchitecture( // Hacky fix for: https://github.com/iVis-at-Bilkent/cytoscape.js-fcose/issues/67 idealEdgeLength(edge: EdgeSingular) { const [nodeA, nodeB] = edge.connectedNodes(); - const { parent: parentA } = nodeData(nodeA) - const { parent: parentB } = nodeData(nodeB) + const { parent: parentA } = nodeData(nodeA); + const { parent: parentB } = nodeData(nodeB); const elasticity = parentA === parentB ? 1.5 * getConfigField('iconSize') : 0.5 * getConfigField('iconSize'); return elasticity; }, edgeElasticity(edge: EdgeSingular) { const [nodeA, nodeB] = edge.connectedNodes(); - const { parent: parentA } = nodeData(nodeA) - const { parent: parentB } = nodeData(nodeB) + const { parent: parentA } = nodeData(nodeA); + const { parent: parentB } = nodeData(nodeB); const elasticity = parentA === parentB ? 0.45 : 0.001; return elasticity; }, @@ -374,7 +380,7 @@ function layoutArchitecture( if (sX !== tX && sY !== tY) { const sEP = edge.sourceEndpoint(); const tEP = edge.targetEndpoint(); - const { sourceDir } = edgeData(edge) + const { sourceDir } = edgeData(edge); const [pointX, pointY] = isArchitectureDirectionY(sourceDir) ? [sEP.x, tEP.y] : [tEP.x, sEP.y]; diff --git a/packages/mermaid/src/diagrams/architecture/architectureTypes.ts b/packages/mermaid/src/diagrams/architecture/architectureTypes.ts index a043b8864..34ea04658 100644 --- a/packages/mermaid/src/diagrams/architecture/architectureTypes.ts +++ b/packages/mermaid/src/diagrams/architecture/architectureTypes.ts @@ -21,8 +21,7 @@ export type ArchitectureDirectionPair = Exclude< >; export type ArchitectureDirectionPairXY = Exclude< InvalidArchitectureDirectionPair, - 'LL' | 'RR' | 'TT' | 'BB' - | 'LR' | 'RL' | 'TB' | 'BT' + 'LL' | 'RR' | 'TT' | 'BB' | 'LR' | 'RL' | 'TB' | 'BT' >; export const ArchitectureDirectionName = { @@ -85,7 +84,7 @@ export const isArchitectureDirectionXY = function ( }; export const isArchitecturePairXY = function ( - pair: ArchitectureDirectionPair, + pair: ArchitectureDirectionPair ): pair is ArchitectureDirectionPairXY { const lhs = pair[0] as ArchitectureDirection; const rhs = pair[1] as ArchitectureDirection; @@ -161,13 +160,13 @@ export const getArchitectureDirectionXYFactors = function ( pair: ArchitectureDirectionPairXY ): number[] { if (pair === 'LT' || pair === 'TL') { - return [1, 1] + return [1, 1]; } else if (pair === 'BL' || pair === 'LB') { - return [1, -1] + return [1, -1]; } else if (pair === 'BR' || pair === 'RB') { - return [-1, -1] + return [-1, -1]; } else { - return [-1, 1] + return [-1, 1]; } }; @@ -209,13 +208,11 @@ export interface ArchitectureEdge { export interface ArchitectureDB extends DiagramDB { clear: () => void; - addService: (service: Omit) => void; + addService: (service: Omit) => void; getServices: () => ArchitectureService[]; addGroup: (group: ArchitectureGroup) => void; getGroups: () => ArchitectureGroup[]; - addEdge: ( - edge: ArchitectureEdge - ) => void; + addEdge: (edge: ArchitectureEdge) => void; getEdges: () => ArchitectureEdge[]; setElementForId: (id: string, element: D3Element) => void; getElementById: (id: string) => D3Element; @@ -257,7 +254,7 @@ export type EdgeSingularData = { export const edgeData = (edge: cytoscape.EdgeSingular) => { return edge.data() as EdgeSingularData; -} +}; export interface EdgeSingular extends cytoscape.EdgeSingular { _private: { @@ -275,28 +272,29 @@ export interface EdgeSingular extends cytoscape.EdgeSingular { data(key: T): EdgeSingularData[T]; } -export type NodeSingularData = { - type: 'service'; - id: string; - icon?: string; - label?: string; - parent?: string; - width: number; - height: number; - [key: string]: any; -} +export type NodeSingularData = | { - type: 'group'; - id: string; - icon?: string; - label?: string; - parent?: string; - [key: string]: any; - }; + type: 'service'; + id: string; + icon?: string; + label?: string; + parent?: string; + width: number; + height: number; + [key: string]: any; + } + | { + type: 'group'; + id: string; + icon?: string; + label?: string; + parent?: string; + [key: string]: any; + }; export const nodeData = (node: cytoscape.NodeSingular) => { return node.data() as NodeSingularData; -} +}; export interface NodeSingular extends cytoscape.NodeSingular { _private: { diff --git a/packages/mermaid/src/diagrams/architecture/svgDraw.ts b/packages/mermaid/src/diagrams/architecture/svgDraw.ts index a7c0bed01..5218bf1cc 100644 --- a/packages/mermaid/src/diagrams/architecture/svgDraw.ts +++ b/packages/mermaid/src/diagrams/architecture/svgDraw.ts @@ -227,7 +227,7 @@ export const drawServices = function ( }, getConfig() ); - + textElem .attr('dy', '1em') .attr('alignment-baseline', 'middle') @@ -247,14 +247,24 @@ export const drawServices = function ( } else if (service.iconText) { bkgElem = getIcon('blank')?.(bkgElem, iconSize); const textElemContainer = bkgElem.append('g'); - const fo = textElemContainer.append('foreignObject').attr('width', iconSize).attr('height', iconSize); + const fo = textElemContainer + .append('foreignObject') + .attr('width', iconSize) + .attr('height', iconSize); const divElem = fo .append('div') .attr('class', 'node-icon-text') .attr('style', `height: ${iconSize}px;`) - .append('div').html(service.iconText); - const fontSize = parseInt(window.getComputedStyle(divElem.node(), null).getPropertyValue("font-size").replace(/[^\d]/g, '')) ?? 16; - divElem.attr('style', `-webkit-line-clamp: ${Math.floor((iconSize - 2) / fontSize)};`) + .append('div') + .html(service.iconText); + const fontSize = + parseInt( + window + .getComputedStyle(divElem.node(), null) + .getPropertyValue('font-size') + .replace(/[^\d]/g, '') + ) ?? 16; + divElem.attr('style', `-webkit-line-clamp: ${Math.floor((iconSize - 2) / fontSize)};`); } else { bkgElem .append('path') diff --git a/packages/mermaid/src/docs/syntax/architecture.md b/packages/mermaid/src/docs/syntax/architecture.md index 8394bbb32..369becad6 100644 --- a/packages/mermaid/src/docs/syntax/architecture.md +++ b/packages/mermaid/src/docs/syntax/architecture.md @@ -20,7 +20,7 @@ architecture ## Syntax -The building blocks of an architecture are `groups`, `services`, and `edges`. +The building blocks of an architecture are `groups`, `services`, and `edges`. For supporting components, icons are declared by surrounding the icon name with `()`, while labels are declared by surrounding the text with `[]`. @@ -29,6 +29,7 @@ To begin an architecture diagram, use the keyword `architecture`, followed by yo ### Groups The syntax for declaring a group is: + ``` group {group id}({icon name})[{title}] (in {parent id})? ``` @@ -48,12 +49,15 @@ group private_api(cloud)[Private API] in public_api ``` ### Services + The syntax for declaring a group is: + ``` service {service id}({icon name})[{title}] (in {parent id})? ``` Put together: + ``` service database(db)[Database] ``` @@ -67,6 +71,7 @@ service database(db)[Database] in private_api ``` ### Edges + TODO -## Configuration \ No newline at end of file +## Configuration diff --git a/packages/mermaid/src/mermaid.ts b/packages/mermaid/src/mermaid.ts index a1f9afdae..f621c1233 100644 --- a/packages/mermaid/src/mermaid.ts +++ b/packages/mermaid/src/mermaid.ts @@ -16,7 +16,7 @@ import type { DetailedError } from './utils.js'; import type { ExternalDiagramDefinition } from './diagram-api/types.js'; import type { UnknownDiagramError } from './errors.js'; import type { IconLibrary, IconResolver } from './rendering-util/svgRegister.js'; -import { createIcon } from './rendering-util/svgRegister.js'; +import { createIcon } from './rendering-util/svgRegister.js'; import { addDiagrams } from './diagram-api/diagram-orchestration.js'; export type { @@ -29,10 +29,10 @@ export type { ParseResult, UnknownDiagramError, IconLibrary, - IconResolver + IconResolver, }; -export { createIcon } +export { createIcon }; export interface RunOptions { /** diff --git a/packages/mermaid/src/rendering-util/svg/cloud.ts b/packages/mermaid/src/rendering-util/svg/cloud.ts index 33e1d3452..01cd84537 100644 --- a/packages/mermaid/src/rendering-util/svg/cloud.ts +++ b/packages/mermaid/src/rendering-util/svg/cloud.ts @@ -5,9 +5,9 @@ import { createIcon } from '../svgRegister.js'; export default createIcon( - ` + ` `, - 80 -); \ No newline at end of file + 80 +); diff --git a/packages/mermaid/src/rendering-util/svgRegister.ts b/packages/mermaid/src/rendering-util/svgRegister.ts index ed1290725..e7c16a0bf 100644 --- a/packages/mermaid/src/rendering-util/svgRegister.ts +++ b/packages/mermaid/src/rendering-util/svgRegister.ts @@ -43,4 +43,12 @@ const getIcon = (name: string): IconResolver | null => { return icons['unknown']; }; -export { registerIcon, registerIcons, getIcon, isIconNameInUse, createIcon, IconLibrary, IconResolver }; +export { + registerIcon, + registerIcons, + getIcon, + isIconNameInUse, + createIcon, + IconLibrary, + IconResolver, +}; diff --git a/packages/parser/src/language/architecture/module.ts b/packages/parser/src/language/architecture/module.ts index 65ce7ea80..d6b2b5770 100644 --- a/packages/parser/src/language/architecture/module.ts +++ b/packages/parser/src/language/architecture/module.ts @@ -35,7 +35,10 @@ export type ArchitectureServices = LangiumCoreServices & ArchitectureAddedServic * Dependency injection module that overrides Langium default services and * contributes the declared `Architecture` services. */ -export const ArchitectureModule: Module = { +export const ArchitectureModule: Module< + ArchitectureServices, + PartialLangiumCoreServices & ArchitectureAddedServices +> = { parser: { TokenBuilder: () => new ArchitectureTokenBuilder(), ValueConverter: () => new ArchitectureValueConverter(), @@ -56,7 +59,9 @@ export const ArchitectureModule: Module