mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-11-17 19:24:10 +01:00
migrate mindmap to class based approach
This commit is contained in:
@@ -1,12 +1,14 @@
|
|||||||
// @ts-ignore: JISON doesn't support types
|
// @ts-ignore: JISON doesn't support types
|
||||||
import parser from './parser/mindmap.jison';
|
import parser from './parser/mindmap.jison';
|
||||||
import db from './mindmapDb.js';
|
import { MindmapDB } from './mindmapDb.js';
|
||||||
import renderer from './mindmapRenderer.js';
|
import renderer from './mindmapRenderer.js';
|
||||||
import styles from './styles.js';
|
import styles from './styles.js';
|
||||||
import type { DiagramDefinition } from '../../diagram-api/types.js';
|
import type { DiagramDefinition } from '../../diagram-api/types.js';
|
||||||
|
|
||||||
export const diagram: DiagramDefinition = {
|
export const diagram: DiagramDefinition = {
|
||||||
db,
|
get db() {
|
||||||
|
return new MindmapDB();
|
||||||
|
},
|
||||||
renderer,
|
renderer,
|
||||||
parser,
|
parser,
|
||||||
styles,
|
styles,
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
// @ts-expect-error No types available for JISON
|
// @ts-expect-error No types available for JISON
|
||||||
import { parser as mindmap } from './parser/mindmap.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
|
// Todo fix utils functions for tests
|
||||||
import { setLogLevel } from '../../diagram-api/diagramAPI.js';
|
import { setLogLevel } from '../../diagram-api/diagramAPI.js';
|
||||||
|
|
||||||
describe('when parsing a mindmap ', function () {
|
describe('when parsing a mindmap ', function () {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
mindmap.yy = mindmapDB;
|
mindmap.yy = new MindmapDB();
|
||||||
mindmap.yy.clear();
|
mindmap.yy.clear();
|
||||||
setLogLevel('trace');
|
setLogLevel('trace');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,70 +5,6 @@ import { log } from '../../logger.js';
|
|||||||
import type { MindmapNode } from './mindmapTypes.js';
|
import type { MindmapNode } from './mindmapTypes.js';
|
||||||
import defaultConfig from '../../defaultConfig.js';
|
import defaultConfig from '../../defaultConfig.js';
|
||||||
|
|
||||||
let nodes: MindmapNode[] = [];
|
|
||||||
let cnt = 0;
|
|
||||||
let elements: Record<number, D3Element> = {};
|
|
||||||
|
|
||||||
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 = {
|
const nodeType = {
|
||||||
DEFAULT: 0,
|
DEFAULT: 0,
|
||||||
NO_BORDER: 0,
|
NO_BORDER: 0,
|
||||||
@@ -78,82 +14,149 @@ const nodeType = {
|
|||||||
CLOUD: 4,
|
CLOUD: 4,
|
||||||
BANG: 5,
|
BANG: 5,
|
||||||
HEXAGON: 6,
|
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;
|
} as const;
|
||||||
|
|
||||||
export default db;
|
export class MindmapDB {
|
||||||
|
private nodes: MindmapNode[] = [];
|
||||||
|
private cnt = 0;
|
||||||
|
private elements: Record<number, D3Element> = {};
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,10 +9,10 @@ import { log } from '../../logger.js';
|
|||||||
import type { D3Element } from '../../types.js';
|
import type { D3Element } from '../../types.js';
|
||||||
import { selectSvgElement } from '../../rendering-util/selectSvgElement.js';
|
import { selectSvgElement } from '../../rendering-util/selectSvgElement.js';
|
||||||
import { setupGraphViewbox } from '../../setupGraphViewbox.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 { drawNode, positionNode } from './svgDraw.js';
|
||||||
import defaultConfig from '../../defaultConfig.js';
|
import defaultConfig from '../../defaultConfig.js';
|
||||||
|
import type { MindmapDB } from './mindmapDb.js';
|
||||||
// Inject the layout algorithm into cytoscape
|
// Inject the layout algorithm into cytoscape
|
||||||
cytoscape.use(coseBilkent);
|
cytoscape.use(coseBilkent);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import type { RequiredDeep } from 'type-fest';
|
import type { RequiredDeep } from 'type-fest';
|
||||||
import type mindmapDb from './mindmapDb.js';
|
|
||||||
|
|
||||||
export interface MindmapNode {
|
export interface MindmapNode {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -19,4 +18,3 @@ export interface MindmapNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type FilledMindMapNode = RequiredDeep<MindmapNode>;
|
export type FilledMindMapNode = RequiredDeep<MindmapNode>;
|
||||||
export type MindmapDB = typeof mindmapDb;
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { createText } from '../../rendering-util/createText.js';
|
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 type { Point, D3Element } from '../../types.js';
|
||||||
import { parseFontSize } from '../../utils.js';
|
import { parseFontSize } from '../../utils.js';
|
||||||
import type { MermaidConfig } from '../../config.type.js';
|
import type { MermaidConfig } from '../../config.type.js';
|
||||||
|
import type { MindmapDB } from './mindmapDb.js';
|
||||||
|
|
||||||
const MAX_SECTIONS = 12;
|
const MAX_SECTIONS = 12;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user