migrate mindmap to class based approach

This commit is contained in:
darshanr0107
2025-07-14 20:00:13 +05:30
parent f76e27db70
commit 260a045da0
6 changed files with 154 additions and 150 deletions

View File

@@ -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,

View File

@@ -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');
});

View File

@@ -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<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 = {
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<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;
}
}

View File

@@ -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);

View File

@@ -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<MindmapNode>;
export type MindmapDB = typeof mindmapDb;

View File

@@ -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;