diff --git a/packages/mermaid/src/diagrams/mindmap/mindmap-definition.ts b/packages/mermaid/src/diagrams/mindmap/mindmap-definition.ts index 66b44b4f9..c02898954 100644 --- a/packages/mermaid/src/diagrams/mindmap/mindmap-definition.ts +++ b/packages/mermaid/src/diagrams/mindmap/mindmap-definition.ts @@ -1,12 +1,14 @@ // @ts-ignore: JISON doesn't support types import parser from './parser/mindmap.jison'; -import db from './mindmapDb.js'; +import { MindmapDB } from './mindmapDb.js'; import renderer from './mindmapRenderer.js'; import styles from './styles.js'; import type { DiagramDefinition } from '../../diagram-api/types.js'; export const diagram: DiagramDefinition = { - db, + get db() { + return new MindmapDB(); + }, renderer, parser, styles, diff --git a/packages/mermaid/src/diagrams/mindmap/mindmap.spec.ts b/packages/mermaid/src/diagrams/mindmap/mindmap.spec.ts index d4f2d316e..b912e1b8c 100644 --- a/packages/mermaid/src/diagrams/mindmap/mindmap.spec.ts +++ b/packages/mermaid/src/diagrams/mindmap/mindmap.spec.ts @@ -1,12 +1,12 @@ // @ts-expect-error No types available for JISON import { parser as mindmap } from './parser/mindmap.jison'; -import mindmapDB from './mindmapDb.js'; +import { MindmapDB } from './mindmapDb.js'; // Todo fix utils functions for tests import { setLogLevel } from '../../diagram-api/diagramAPI.js'; describe('when parsing a mindmap ', function () { beforeEach(function () { - mindmap.yy = mindmapDB; + mindmap.yy = new MindmapDB(); mindmap.yy.clear(); setLogLevel('trace'); }); diff --git a/packages/mermaid/src/diagrams/mindmap/mindmapDb.ts b/packages/mermaid/src/diagrams/mindmap/mindmapDb.ts index e7041e9d6..7a151d9c0 100644 --- a/packages/mermaid/src/diagrams/mindmap/mindmapDb.ts +++ b/packages/mermaid/src/diagrams/mindmap/mindmapDb.ts @@ -5,70 +5,6 @@ import { log } from '../../logger.js'; import type { MindmapNode } from './mindmapTypes.js'; import defaultConfig from '../../defaultConfig.js'; -let nodes: MindmapNode[] = []; -let cnt = 0; -let elements: Record = {}; - -const clear = () => { - nodes = []; - cnt = 0; - elements = {}; -}; - -const getParent = function (level: number) { - for (let i = nodes.length - 1; i >= 0; i--) { - if (nodes[i].level < level) { - return nodes[i]; - } - } - // No parent found - return null; -}; - -const getMindmap = () => { - return nodes.length > 0 ? nodes[0] : null; -}; - -const addNode = (level: number, id: string, descr: string, type: number) => { - log.info('addNode', level, id, descr, type); - const conf = getConfig(); - let padding: number = conf.mindmap?.padding ?? defaultConfig.mindmap.padding; - switch (type) { - case nodeType.ROUNDED_RECT: - case nodeType.RECT: - case nodeType.HEXAGON: - padding *= 2; - } - - const node = { - id: cnt++, - nodeId: sanitizeText(id, conf), - level, - descr: sanitizeText(descr, conf), - type, - children: [], - width: conf.mindmap?.maxNodeWidth ?? defaultConfig.mindmap.maxNodeWidth, - padding, - } satisfies MindmapNode; - - const parent = getParent(level); - if (parent) { - parent.children.push(node); - // Keep all nodes in the list - nodes.push(node); - } else { - if (nodes.length === 0) { - // First node, the root - nodes.push(node); - } else { - // Syntax error ... there can only bee one root - throw new Error( - 'There can be only one root. No parent could be found for ("' + node.descr + '")' - ); - } - } -}; - const nodeType = { DEFAULT: 0, NO_BORDER: 0, @@ -78,82 +14,149 @@ const nodeType = { CLOUD: 4, BANG: 5, HEXAGON: 6, -}; - -const getType = (startStr: string, endStr: string): number => { - log.debug('In get type', startStr, endStr); - switch (startStr) { - case '[': - return nodeType.RECT; - case '(': - return endStr === ')' ? nodeType.ROUNDED_RECT : nodeType.CLOUD; - case '((': - return nodeType.CIRCLE; - case ')': - return nodeType.CLOUD; - case '))': - return nodeType.BANG; - case '{{': - return nodeType.HEXAGON; - default: - return nodeType.DEFAULT; - } -}; - -const setElementForId = (id: number, element: D3Element) => { - elements[id] = element; -}; - -const decorateNode = (decoration?: { class?: string; icon?: string }) => { - if (!decoration) { - return; - } - const config = getConfig(); - const node = nodes[nodes.length - 1]; - if (decoration.icon) { - node.icon = sanitizeText(decoration.icon, config); - } - if (decoration.class) { - node.class = sanitizeText(decoration.class, config); - } -}; - -const type2Str = (type: number) => { - switch (type) { - case nodeType.DEFAULT: - return 'no-border'; - case nodeType.RECT: - return 'rect'; - case nodeType.ROUNDED_RECT: - return 'rounded-rect'; - case nodeType.CIRCLE: - return 'circle'; - case nodeType.CLOUD: - return 'cloud'; - case nodeType.BANG: - return 'bang'; - case nodeType.HEXAGON: - return 'hexgon'; // cspell: disable-line - default: - return 'no-border'; - } -}; - -// Expose logger to grammar -const getLogger = () => log; -const getElementById = (id: number) => elements[id]; - -const db = { - clear, - addNode, - getMindmap, - nodeType, - getType, - setElementForId, - decorateNode, - type2Str, - getLogger, - getElementById, } as const; -export default db; +export class MindmapDB { + private nodes: MindmapNode[] = []; + private cnt = 0; + private elements: Record = {}; + public readonly nodeType: typeof nodeType; + + constructor() { + this.getLogger = this.getLogger.bind(this); + this.nodeType = nodeType; + this.clear(); + this.getType = this.getType.bind(this); + this.getMindmap = this.getMindmap.bind(this); + this.getElementById = this.getElementById.bind(this); + this.getParent = this.getParent.bind(this); + this.getMindmap = this.getMindmap.bind(this); + this.addNode = this.addNode.bind(this); + this.decorateNode = this.decorateNode.bind(this); + } + public clear() { + this.nodes = []; + this.cnt = 0; + this.elements = {}; + } + + public getParent(level: number): MindmapNode | null { + for (let i = this.nodes.length - 1; i >= 0; i--) { + if (this.nodes[i].level < level) { + return this.nodes[i]; + } + } + return null; + } + + public getMindmap(): MindmapNode | null { + return this.nodes.length > 0 ? this.nodes[0] : null; + } + + public addNode(level: number, id: string, descr: string, type: number): void { + log.info('addNode', level, id, descr, type); + + const conf = getConfig(); + let padding = conf.mindmap?.padding ?? defaultConfig.mindmap.padding; + + switch (type) { + case this.nodeType.ROUNDED_RECT: + case this.nodeType.RECT: + case this.nodeType.HEXAGON: + padding *= 2; + break; + } + + const node: MindmapNode = { + id: this.cnt++, + nodeId: sanitizeText(id, conf), + level, + descr: sanitizeText(descr, conf), + type, + children: [], + width: conf.mindmap?.maxNodeWidth ?? defaultConfig.mindmap.maxNodeWidth, + padding, + }; + + const parent = this.getParent(level); + if (parent) { + parent.children.push(node); + this.nodes.push(node); + } else { + if (this.nodes.length === 0) { + this.nodes.push(node); + } else { + throw new Error( + `There can be only one root. No parent could be found for ("${node.descr}")` + ); + } + } + } + + public getType(startStr: string, endStr: string) { + log.debug('In get type', startStr, endStr); + switch (startStr) { + case '[': + return this.nodeType.RECT; + case '(': + return endStr === ')' ? this.nodeType.ROUNDED_RECT : this.nodeType.CLOUD; + case '((': + return this.nodeType.CIRCLE; + case ')': + return this.nodeType.CLOUD; + case '))': + return this.nodeType.BANG; + case '{{': + return this.nodeType.HEXAGON; + default: + return this.nodeType.DEFAULT; + } + } + + public setElementForId(id: number, element: D3Element): void { + this.elements[id] = element; + } + public getElementById(id: number) { + return this.elements[id]; + } + + public decorateNode(decoration?: { class?: string; icon?: string }): void { + if (!decoration) { + return; + } + + const config = getConfig(); + const node = this.nodes[this.nodes.length - 1]; + if (decoration.icon) { + node.icon = sanitizeText(decoration.icon, config); + } + if (decoration.class) { + node.class = sanitizeText(decoration.class, config); + } + } + + type2Str(type: number): string { + switch (type) { + case this.nodeType.DEFAULT: + return 'no-border'; + case this.nodeType.RECT: + return 'rect'; + case this.nodeType.ROUNDED_RECT: + return 'rounded-rect'; + case this.nodeType.CIRCLE: + return 'circle'; + case this.nodeType.CLOUD: + return 'cloud'; + case this.nodeType.BANG: + return 'bang'; + case this.nodeType.HEXAGON: + return 'hexgon'; // cspell: disable-line + default: + return 'no-border'; + } + } + + public getLogger() { + return log; + } +} diff --git a/packages/mermaid/src/diagrams/mindmap/mindmapRenderer.ts b/packages/mermaid/src/diagrams/mindmap/mindmapRenderer.ts index 708b3cc28..ef9be0565 100644 --- a/packages/mermaid/src/diagrams/mindmap/mindmapRenderer.ts +++ b/packages/mermaid/src/diagrams/mindmap/mindmapRenderer.ts @@ -9,10 +9,10 @@ import { log } from '../../logger.js'; import type { D3Element } from '../../types.js'; import { selectSvgElement } from '../../rendering-util/selectSvgElement.js'; import { setupGraphViewbox } from '../../setupGraphViewbox.js'; -import type { FilledMindMapNode, MindmapDB, MindmapNode } from './mindmapTypes.js'; +import type { FilledMindMapNode, MindmapNode } from './mindmapTypes.js'; import { drawNode, positionNode } from './svgDraw.js'; import defaultConfig from '../../defaultConfig.js'; - +import type { MindmapDB } from './mindmapDb.js'; // Inject the layout algorithm into cytoscape cytoscape.use(coseBilkent); diff --git a/packages/mermaid/src/diagrams/mindmap/mindmapTypes.ts b/packages/mermaid/src/diagrams/mindmap/mindmapTypes.ts index e8350477a..be8effab1 100644 --- a/packages/mermaid/src/diagrams/mindmap/mindmapTypes.ts +++ b/packages/mermaid/src/diagrams/mindmap/mindmapTypes.ts @@ -1,5 +1,4 @@ import type { RequiredDeep } from 'type-fest'; -import type mindmapDb from './mindmapDb.js'; export interface MindmapNode { id: number; @@ -19,4 +18,3 @@ export interface MindmapNode { } export type FilledMindMapNode = RequiredDeep; -export type MindmapDB = typeof mindmapDb; diff --git a/packages/mermaid/src/diagrams/mindmap/svgDraw.ts b/packages/mermaid/src/diagrams/mindmap/svgDraw.ts index 209a6a0e1..8aee82e30 100644 --- a/packages/mermaid/src/diagrams/mindmap/svgDraw.ts +++ b/packages/mermaid/src/diagrams/mindmap/svgDraw.ts @@ -1,8 +1,9 @@ import { createText } from '../../rendering-util/createText.js'; -import type { FilledMindMapNode, MindmapDB } from './mindmapTypes.js'; +import type { FilledMindMapNode } from './mindmapTypes.js'; import type { Point, D3Element } from '../../types.js'; import { parseFontSize } from '../../utils.js'; import type { MermaidConfig } from '../../config.type.js'; +import type { MindmapDB } from './mindmapDb.js'; const MAX_SECTIONS = 12;