convert flowDb to class.

This commit is contained in:
Saurabh Gore
2024-12-31 17:40:52 +05:30
parent df636c6d0a
commit 9ef6090c8c
19 changed files with 991 additions and 1008 deletions

View File

@@ -1,9 +1,11 @@
import flowDb from './flowDb.js'; import { FlowDb } from './flowDb.js';
import type { FlowSubGraph } from './types.js'; import type { FlowSubGraph } from './types.js';
describe('flow db subgraphs', () => { describe('flow db subgraphs', () => {
let flowDb: FlowDb;
let subgraphs: FlowSubGraph[]; let subgraphs: FlowSubGraph[];
beforeEach(() => { beforeEach(() => {
flowDb = new FlowDb();
subgraphs = [ subgraphs = [
{ nodes: ['a', 'b', 'c', 'e'] }, { nodes: ['a', 'b', 'c', 'e'] },
{ nodes: ['f', 'g', 'h'] }, { nodes: ['f', 'g', 'h'] },
@@ -44,8 +46,9 @@ describe('flow db subgraphs', () => {
}); });
describe('flow db addClass', () => { describe('flow db addClass', () => {
let flowDb: FlowDb;
beforeEach(() => { beforeEach(() => {
flowDb.clear(); flowDb = new FlowDb();
}); });
it('should detect many classes', () => { it('should detect many classes', () => {
flowDb.addClass('a,b', ['stroke-width: 8px']); flowDb.addClass('a,b', ['stroke-width: 8px']);

View File

@@ -27,32 +27,43 @@ import type {
import type { NodeMetaData } from '../../types.js'; import type { NodeMetaData } from '../../types.js';
const MERMAID_DOM_ID_PREFIX = 'flowchart-'; const MERMAID_DOM_ID_PREFIX = 'flowchart-';
let vertexCounter = 0;
let config = getConfig();
let vertices = new Map<string, FlowVertex>();
let edges: FlowEdge[] & { defaultInterpolate?: string; defaultStyle?: string[] } = [];
let classes = new Map<string, FlowClass>();
let subGraphs: FlowSubGraph[] = [];
let subGraphLookup = new Map<string, FlowSubGraph>();
let tooltips = new Map<string, string>();
let subCount = 0;
let firstGraphFlag = true;
let direction: string;
let version: string; // As in graph export class FlowDb {
private vertexCounter = 0;
private config = getConfig();
private vertices = new Map<string, FlowVertex>();
private edges: FlowEdge[] & { defaultInterpolate?: string; defaultStyle?: string[] } = [];
private classes = new Map<string, FlowClass>();
private subGraphs: FlowSubGraph[] = [];
private subGraphLookup = new Map<string, FlowSubGraph>();
private tooltips = new Map<string, string>();
private subCount = 0;
private firstGraphFlag = true;
private direction: string | undefined;
private version: string | undefined; // As in graph
private secCount = -1;
private posCrossRef: number[] = [];
// Functions to be run after graph rendering // Functions to be run after graph rendering
let funs: ((element: Element) => void)[] = []; // cspell:ignore funs private funs: ((element: Element) => void)[] = []; // cspell:ignore funs
const sanitizeText = (txt: string) => common.sanitizeText(txt, config); constructor() {
this.funs.push(this.setupToolTips);
this.clear();
this.setGen('gen-2');
}
private sanitizeText = (txt: string) => common.sanitizeText(txt, this.config);
/** /**
* Function to lookup domId from id in the graph definition. * Function to lookup domId from id in the graph definition.
* *
* @param id - id of the node * @param id - id of the node
*/ */
export const lookUpDomId = function (id: string) { public lookUpDomId = (id: string) => {
for (const vertex of vertices.values()) { for (const vertex of this.vertices.values()) {
if (vertex.id === id) { if (vertex.id === id) {
return vertex.domId; return vertex.domId;
} }
@@ -63,7 +74,7 @@ export const lookUpDomId = function (id: string) {
/** /**
* Function called by parser when a node definition has been found * Function called by parser when a node definition has been found
*/ */
export const addVertex = function ( public addVertex = (
id: string, id: string,
textObj: FlowText, textObj: FlowText,
type: FlowVertexTypeParam, type: FlowVertexTypeParam,
@@ -72,29 +83,29 @@ export const addVertex = function (
dir: string, dir: string,
props = {}, props = {},
shapeData: any shapeData: any
) { ) => {
// console.log('addVertex', id, shapeData); // console.log('addVertex', id, shapeData);
if (!id || id.trim().length === 0) { if (!id || id.trim().length === 0) {
return; return;
} }
let txt; let txt;
let vertex = vertices.get(id); let vertex = this.vertices.get(id);
if (vertex === undefined) { if (vertex === undefined) {
vertex = { vertex = {
id, id,
labelType: 'text', labelType: 'text',
domId: MERMAID_DOM_ID_PREFIX + id + '-' + vertexCounter, domId: MERMAID_DOM_ID_PREFIX + id + '-' + this.vertexCounter,
styles: [], styles: [],
classes: [], classes: [],
}; };
vertices.set(id, vertex); this.vertices.set(id, vertex);
} }
vertexCounter++; this.vertexCounter++;
if (textObj !== undefined) { if (textObj !== undefined) {
config = getConfig(); this.config = getConfig();
txt = sanitizeText(textObj.text.trim()); txt = this.sanitizeText(textObj.text.trim());
vertex.labelType = textObj.type; vertex.labelType = textObj.type;
// strip quotes if string starts and ends with a quote // strip quotes if string starts and ends with a quote
if (txt.startsWith('"') && txt.endsWith('"')) { if (txt.startsWith('"') && txt.endsWith('"')) {
@@ -110,12 +121,12 @@ export const addVertex = function (
vertex.type = type; vertex.type = type;
} }
if (style !== undefined && style !== null) { if (style !== undefined && style !== null) {
style.forEach(function (s) { style.forEach((s) => {
vertex.styles.push(s); vertex.styles.push(s);
}); });
} }
if (classes !== undefined && classes !== null) { if (classes !== undefined && classes !== null) {
classes.forEach(function (s) { classes.forEach((s) => {
vertex.classes.push(s); vertex.classes.push(s);
}); });
} }
@@ -187,7 +198,7 @@ export const addVertex = function (
* Function called by parser when a link/edge definition has been found * Function called by parser when a link/edge definition has been found
* *
*/ */
export const addSingleLink = function (_start: string, _end: string, type: any) { private addSingleLink = (_start: string, _end: string, type: any) => {
const start = _start; const start = _start;
const end = _end; const end = _end;
@@ -196,7 +207,7 @@ export const addSingleLink = function (_start: string, _end: string, type: any)
const linkTextObj = type.text; const linkTextObj = type.text;
if (linkTextObj !== undefined) { if (linkTextObj !== undefined) {
edge.text = sanitizeText(linkTextObj.text.trim()); edge.text = this.sanitizeText(linkTextObj.text.trim());
// strip quotes if string starts and ends with a quote // strip quotes if string starts and ends with a quote
if (edge.text.startsWith('"') && edge.text.endsWith('"')) { if (edge.text.startsWith('"') && edge.text.endsWith('"')) {
@@ -211,12 +222,12 @@ export const addSingleLink = function (_start: string, _end: string, type: any)
edge.length = type.length > 10 ? 10 : type.length; edge.length = type.length > 10 ? 10 : type.length;
} }
if (edges.length < (config.maxEdges ?? 500)) { if (this.edges.length < (this.config.maxEdges ?? 500)) {
log.info('Pushing edge...'); log.info('Pushing edge...');
edges.push(edge); this.edges.push(edge);
} else { } else {
throw new Error( throw new Error(
`Edge limit exceeded. ${edges.length} edges found, but the limit is ${config.maxEdges}. `Edge limit exceeded. ${this.edges.length} edges found, but the limit is ${this.config.maxEdges}.
Initialize mermaid with maxEdges set to a higher number to allow more edges. Initialize mermaid with maxEdges set to a higher number to allow more edges.
You cannot set this config via configuration inside the diagram as it is a secure config. You cannot set this config via configuration inside the diagram as it is a secure config.
@@ -225,11 +236,11 @@ You have to call mermaid.initialize.`
} }
}; };
export const addLink = function (_start: string[], _end: string[], type: unknown) { public addLink = (_start: string[], _end: string[], type: unknown) => {
log.info('addLink', _start, _end, type); log.info('addLink', _start, _end, type);
for (const start of _start) { for (const start of _start) {
for (const end of _end) { for (const end of _end) {
addSingleLink(start, end, type); this.addSingleLink(start, end, type);
} }
} }
}; };
@@ -238,15 +249,12 @@ export const addLink = function (_start: string[], _end: string[], type: unknown
* Updates a link's line interpolation algorithm * Updates a link's line interpolation algorithm
* *
*/ */
export const updateLinkInterpolate = function ( public updateLinkInterpolate = (positions: ('default' | number)[], interpolate: string) => {
positions: ('default' | number)[], positions.forEach((pos) => {
interpolate: string
) {
positions.forEach(function (pos) {
if (pos === 'default') { if (pos === 'default') {
edges.defaultInterpolate = interpolate; this.edges.defaultInterpolate = interpolate;
} else { } else {
edges[pos].interpolate = interpolate; this.edges[pos].interpolate = interpolate;
} }
}); });
}; };
@@ -255,43 +263,43 @@ export const updateLinkInterpolate = function (
* Updates a link with a style * Updates a link with a style
* *
*/ */
export const updateLink = function (positions: ('default' | number)[], style: string[]) { public updateLink = (positions: ('default' | number)[], style: string[]) => {
positions.forEach(function (pos) { positions.forEach((pos) => {
if (typeof pos === 'number' && pos >= edges.length) { if (typeof pos === 'number' && pos >= this.edges.length) {
throw new Error( throw new Error(
`The index ${pos} for linkStyle is out of bounds. Valid indices for linkStyle are between 0 and ${ `The index ${pos} for linkStyle is out of bounds. Valid indices for linkStyle are between 0 and ${
edges.length - 1 this.edges.length - 1
}. (Help: Ensure that the index is within the range of existing edges.)` }. (Help: Ensure that the index is within the range of existing edges.)`
); );
} }
if (pos === 'default') { if (pos === 'default') {
edges.defaultStyle = style; this.edges.defaultStyle = style;
} else { } else {
// if (utils.isSubstringInArray('fill', style) === -1) { // if (utils.isSubstringInArray('fill', style) === -1) {
// style.push('fill:none'); // style.push('fill:none');
// } // }
edges[pos].style = style; this.edges[pos].style = style;
// if edges[pos].style does have fill not set, set it to none // if edges[pos].style does have fill not set, set it to none
if ( if (
(edges[pos]?.style?.length ?? 0) > 0 && (this.edges[pos]?.style?.length ?? 0) > 0 &&
!edges[pos]?.style?.some((s) => s?.startsWith('fill')) !this.edges[pos]?.style?.some((s) => s?.startsWith('fill'))
) { ) {
edges[pos]?.style?.push('fill:none'); this.edges[pos]?.style?.push('fill:none');
} }
} }
}); });
}; };
export const addClass = function (ids: string, style: string[]) { public addClass = (ids: string, style: string[]) => {
ids.split(',').forEach(function (id) { ids.split(',').forEach((id) => {
let classNode = classes.get(id); let classNode = this.classes.get(id);
if (classNode === undefined) { if (classNode === undefined) {
classNode = { id, styles: [], textStyles: [] }; classNode = { id, styles: [], textStyles: [] };
classes.set(id, classNode); this.classes.set(id, classNode);
} }
if (style !== undefined && style !== null) { if (style !== undefined && style !== null) {
style.forEach(function (s) { style.forEach((s) => {
if (/color/.exec(s)) { if (/color/.exec(s)) {
const newStyle = s.replace('fill', 'bgFill'); // .replace('color', 'fill'); const newStyle = s.replace('fill', 'bgFill'); // .replace('color', 'fill');
classNode.textStyles.push(newStyle); classNode.textStyles.push(newStyle);
@@ -306,22 +314,22 @@ export const addClass = function (ids: string, style: string[]) {
* Called by parser when a graph definition is found, stores the direction of the chart. * Called by parser when a graph definition is found, stores the direction of the chart.
* *
*/ */
export const setDirection = function (dir: string) { public setDirection = (dir: string) => {
direction = dir; this.direction = dir;
if (/.*</.exec(direction)) { if (/.*</.exec(this.direction)) {
direction = 'RL'; this.direction = 'RL';
} }
if (/.*\^/.exec(direction)) { if (/.*\^/.exec(this.direction)) {
direction = 'BT'; this.direction = 'BT';
} }
if (/.*>/.exec(direction)) { if (/.*>/.exec(this.direction)) {
direction = 'LR'; this.direction = 'LR';
} }
if (/.*v/.exec(direction)) { if (/.*v/.exec(this.direction)) {
direction = 'TB'; this.direction = 'TB';
} }
if (direction === 'TD') { if (this.direction === 'TD') {
direction = 'TB'; this.direction = 'TB';
} }
}; };
@@ -331,31 +339,31 @@ export const setDirection = function (dir: string) {
* @param ids - Comma separated list of ids * @param ids - Comma separated list of ids
* @param className - Class to add * @param className - Class to add
*/ */
export const setClass = function (ids: string, className: string) { public setClass = (ids: string, className: string) => {
for (const id of ids.split(',')) { for (const id of ids.split(',')) {
const vertex = vertices.get(id); const vertex = this.vertices.get(id);
if (vertex) { if (vertex) {
vertex.classes.push(className); vertex.classes.push(className);
} }
const subGraph = subGraphLookup.get(id); const subGraph = this.subGraphLookup.get(id);
if (subGraph) { if (subGraph) {
subGraph.classes.push(className); subGraph.classes.push(className);
} }
} }
}; };
const setTooltip = function (ids: string, tooltip: string) { public setTooltip = (ids: string, tooltip: string) => {
if (tooltip === undefined) { if (tooltip === undefined) {
return; return;
} }
tooltip = sanitizeText(tooltip); tooltip = this.sanitizeText(tooltip);
for (const id of ids.split(',')) { for (const id of ids.split(',')) {
tooltips.set(version === 'gen-1' ? lookUpDomId(id) : id, tooltip); this.tooltips.set(this.version === 'gen-1' ? this.lookUpDomId(id) : id, tooltip);
} }
}; };
const setClickFun = function (id: string, functionName: string, functionArgs: string) { private setClickFun = (id: string, functionName: string, functionArgs: string) => {
const domId = lookUpDomId(id); const domId = this.lookUpDomId(id);
// if (_id[0].match(/\d/)) id = MERMAID_DOM_ID_PREFIX + id; // if (_id[0].match(/\d/)) id = MERMAID_DOM_ID_PREFIX + id;
if (getConfig().securityLevel !== 'loose') { if (getConfig().securityLevel !== 'loose') {
return; return;
@@ -383,15 +391,15 @@ const setClickFun = function (id: string, functionName: string, functionArgs: st
argList.push(id); argList.push(id);
} }
const vertex = vertices.get(id); const vertex = this.vertices.get(id);
if (vertex) { if (vertex) {
vertex.haveCallback = true; vertex.haveCallback = true;
funs.push(function () { this.funs.push(() => {
const elem = document.querySelector(`[id="${domId}"]`); const elem = document.querySelector(`[id="${domId}"]`);
if (elem !== null) { if (elem !== null) {
elem.addEventListener( elem.addEventListener(
'click', 'click',
function () { () => {
utils.runFunc(functionName, ...argList); utils.runFunc(functionName, ...argList);
}, },
false false
@@ -408,19 +416,19 @@ const setClickFun = function (id: string, functionName: string, functionArgs: st
* @param linkStr - URL to create a link for * @param linkStr - URL to create a link for
* @param target - Target attribute for the link * @param target - Target attribute for the link
*/ */
export const setLink = function (ids: string, linkStr: string, target: string) { public setLink = (ids: string, linkStr: string, target: string) => {
ids.split(',').forEach(function (id) { ids.split(',').forEach((id) => {
const vertex = vertices.get(id); const vertex = this.vertices.get(id);
if (vertex !== undefined) { if (vertex !== undefined) {
vertex.link = utils.formatUrl(linkStr, config); vertex.link = utils.formatUrl(linkStr, this.config);
vertex.linkTarget = target; vertex.linkTarget = target;
} }
}); });
setClass(ids, 'clickable'); this.setClass(ids, 'clickable');
}; };
export const getTooltip = function (id: string) { public getTooltip = (id: string) => {
return tooltips.get(id); return this.tooltips.get(id);
}; };
/** /**
@@ -430,66 +438,69 @@ export const getTooltip = function (id: string) {
* @param functionName - Function to be called on click * @param functionName - Function to be called on click
* @param functionArgs - Arguments to be passed to the function * @param functionArgs - Arguments to be passed to the function
*/ */
export const setClickEvent = function (ids: string, functionName: string, functionArgs: string) { public setClickEvent = (ids: string, functionName: string, functionArgs: string) => {
ids.split(',').forEach(function (id) { ids.split(',').forEach((id) => {
setClickFun(id, functionName, functionArgs); this.setClickFun(id, functionName, functionArgs);
}); });
setClass(ids, 'clickable'); this.setClass(ids, 'clickable');
}; };
export const bindFunctions = function (element: Element) { public bindFunctions = (element: Element) => {
funs.forEach(function (fun) { this.funs.forEach((fun) => {
fun(element); fun(element);
}); });
}; };
export const getDirection = function () { public getDirection = () => {
return direction.trim(); return this.direction?.trim();
}; };
/** /**
* Retrieval function for fetching the found nodes after parsing has completed. * Retrieval function for fetching the found nodes after parsing has completed.
* *
*/ */
export const getVertices = function () { public getVertices = () => {
return vertices; return this.vertices;
}; };
/** /**
* Retrieval function for fetching the found links after parsing has completed. * Retrieval function for fetching the found links after parsing has completed.
* *
*/ */
export const getEdges = function () { public getEdges = () => {
return edges; return this.edges;
}; };
/** /**
* Retrieval function for fetching the found class definitions after parsing has completed. * Retrieval function for fetching the found class definitions after parsing has completed.
* *
*/ */
export const getClasses = function () { public getClasses = () => {
return classes; return this.classes;
}; };
const setupToolTips = function (element: Element) { private setupToolTips = (element: Element) => {
let tooltipElem = select('.mermaidTooltip'); let tooltipElem = select('.mermaidTooltip');
// @ts-ignore TODO: fix this // @ts-ignore TODO: fix this
if ((tooltipElem._groups || tooltipElem)[0][0] === null) { if ((tooltipElem._groups || tooltipElem)[0][0] === null) {
// @ts-ignore TODO: fix this // @ts-ignore TODO: fix this
tooltipElem = select('body').append('div').attr('class', 'mermaidTooltip').style('opacity', 0); tooltipElem = select('body')
.append('div')
.attr('class', 'mermaidTooltip')
.style('opacity', 0);
} }
const svg = select(element).select('svg'); const svg = select(element).select('svg');
const nodes = svg.selectAll('g.node'); const nodes = svg.selectAll('g.node');
nodes nodes
.on('mouseover', function () { .on('mouseover', (e: MouseEvent) => {
const el = select(this); const el = select(e.currentTarget as Element);
const title = el.attr('title'); const title = el.attr('title');
// Don't try to draw a tooltip if no data is provided // Don't try to draw a tooltip if no data is provided
if (title === null) { if (title === null) {
return; return;
} }
const rect = (this as Element)?.getBoundingClientRect(); const rect = (e.currentTarget as Element)?.getBoundingClientRect();
tooltipElem.transition().duration(200).style('opacity', '.9'); tooltipElem.transition().duration(200).style('opacity', '.9');
tooltipElem tooltipElem
@@ -499,53 +510,52 @@ const setupToolTips = function (element: Element) {
tooltipElem.html(tooltipElem.html().replace(/&lt;br\/&gt;/g, '<br/>')); tooltipElem.html(tooltipElem.html().replace(/&lt;br\/&gt;/g, '<br/>'));
el.classed('hover', true); el.classed('hover', true);
}) })
.on('mouseout', function () { .on('mouseout', (e: MouseEvent) => {
tooltipElem.transition().duration(500).style('opacity', 0); tooltipElem.transition().duration(500).style('opacity', 0);
const el = select(this); const el = select(e.currentTarget as Element);
el.classed('hover', false); el.classed('hover', false);
}); });
}; };
funs.push(setupToolTips);
/** /**
* Clears the internal graph db so that a new graph can be parsed. * Clears the internal graph db so that a new graph can be parsed.
* *
*/ */
export const clear = function (ver = 'gen-1') { public clear = (ver = 'gen-1') => {
vertices = new Map(); this.vertices = new Map();
classes = new Map(); this.classes = new Map();
edges = []; this.edges = [];
funs = [setupToolTips]; this.funs = [this.setupToolTips];
subGraphs = []; this.subGraphs = [];
subGraphLookup = new Map(); this.subGraphLookup = new Map();
subCount = 0; this.subCount = 0;
tooltips = new Map(); this.tooltips = new Map();
firstGraphFlag = true; this.firstGraphFlag = true;
version = ver; this.version = ver;
config = getConfig(); this.config = getConfig();
commonClear(); commonClear();
}; };
export const setGen = (ver: string) => { public setGen = (ver: string) => {
version = ver || 'gen-2'; this.version = ver || 'gen-2';
}; };
export const defaultStyle = function () { public defaultStyle = () => {
return 'fill:#ffa;stroke: #f66; stroke-width: 3px; stroke-dasharray: 5, 5;fill:#ffa;stroke: #666;'; return 'fill:#ffa;stroke: #f66; stroke-width: 3px; stroke-dasharray: 5, 5;fill:#ffa;stroke: #666;';
}; };
export const addSubGraph = function ( public addSubGraph = (
_id: { text: string }, _id: { text: string },
list: string[], list: string[],
_title: { text: string; type: string } _title: { text: string; type: string }
) { ) => {
let id: string | undefined = _id.text.trim(); let id: string | undefined = _id.text.trim();
let title = _title.text; let title = _title.text;
if (_id === _title && /\s/.exec(_title.text)) { if (_id === _title && /\s/.exec(_title.text)) {
id = undefined; id = undefined;
} }
function uniq(a: any[]) { const uniq = (a: any[]) => {
const prims: any = { boolean: {}, number: {}, string: {} }; const prims: any = { boolean: {}, number: {}, string: {} };
const objs: any[] = []; const objs: any[] = [];
@@ -566,19 +576,19 @@ export const addSubGraph = function (
} }
}); });
return { nodeList, dir }; return { nodeList, dir };
} };
const { nodeList, dir } = uniq(list.flat()); const { nodeList, dir } = uniq(list.flat());
if (version === 'gen-1') { if (this.version === 'gen-1') {
for (let i = 0; i < nodeList.length; i++) { for (let i = 0; i < nodeList.length; i++) {
nodeList[i] = lookUpDomId(nodeList[i]); nodeList[i] = this.lookUpDomId(nodeList[i]);
} }
} }
id = id ?? 'subGraph' + subCount; id = id ?? 'subGraph' + this.subCount;
title = title || ''; title = title || '';
title = sanitizeText(title); title = this.sanitizeText(title);
subCount = subCount + 1; this.subCount = this.subCount + 1;
const subGraph = { const subGraph = {
id: id, id: id,
nodes: nodeList, nodes: nodeList,
@@ -591,34 +601,33 @@ export const addSubGraph = function (
log.info('Adding', subGraph.id, subGraph.nodes, subGraph.dir); log.info('Adding', subGraph.id, subGraph.nodes, subGraph.dir);
// Remove the members in the new subgraph if they already belong to another subgraph // Remove the members in the new subgraph if they already belong to another subgraph
subGraph.nodes = makeUniq(subGraph, subGraphs).nodes; subGraph.nodes = this.makeUniq(subGraph, this.subGraphs).nodes;
subGraphs.push(subGraph); this.subGraphs.push(subGraph);
subGraphLookup.set(id, subGraph); this.subGraphLookup.set(id, subGraph);
return id; return id;
}; };
const getPosForId = function (id: string) { private getPosForId = (id: string) => {
for (const [i, subGraph] of subGraphs.entries()) { for (const [i, subGraph] of this.subGraphs.entries()) {
if (subGraph.id === id) { if (subGraph.id === id) {
return i; return i;
} }
} }
return -1; return -1;
}; };
let secCount = -1;
const posCrossRef: number[] = []; private indexNodes2 = (id: string, pos: number): { result: boolean; count: number } => {
const indexNodes2 = function (id: string, pos: number): { result: boolean; count: number } { const nodes = this.subGraphs[pos].nodes;
const nodes = subGraphs[pos].nodes; this.secCount = this.secCount + 1;
secCount = secCount + 1; if (this.secCount > 2000) {
if (secCount > 2000) {
return { return {
result: false, result: false,
count: 0, count: 0,
}; };
} }
posCrossRef[secCount] = pos; this.posCrossRef[this.secCount] = pos;
// Check if match // Check if match
if (subGraphs[pos].id === id) { if (this.subGraphs[pos].id === id) {
return { return {
result: true, result: true,
count: 0, count: 0,
@@ -628,10 +637,10 @@ const indexNodes2 = function (id: string, pos: number): { result: boolean; count
let count = 0; let count = 0;
let posCount = 1; let posCount = 1;
while (count < nodes.length) { while (count < nodes.length) {
const childPos = getPosForId(nodes[count]); const childPos = this.getPosForId(nodes[count]);
// Ignore regular nodes (pos will be -1) // Ignore regular nodes (pos will be -1)
if (childPos >= 0) { if (childPos >= 0) {
const res = indexNodes2(id, childPos); const res = this.indexNodes2(id, childPos);
if (res.result) { if (res.result) {
return { return {
result: true, result: true,
@@ -650,29 +659,29 @@ const indexNodes2 = function (id: string, pos: number): { result: boolean; count
}; };
}; };
export const getDepthFirstPos = function (pos: number) { public getDepthFirstPos = (pos: number) => {
return posCrossRef[pos]; return this.posCrossRef[pos];
}; };
export const indexNodes = function () { public indexNodes = () => {
secCount = -1; this.secCount = -1;
if (subGraphs.length > 0) { if (this.subGraphs.length > 0) {
indexNodes2('none', subGraphs.length - 1); this.indexNodes2('none', this.subGraphs.length - 1);
} }
}; };
export const getSubGraphs = function () { public getSubGraphs = () => {
return subGraphs; return this.subGraphs;
}; };
export const firstGraph = () => { public firstGraph = () => {
if (firstGraphFlag) { if (this.firstGraphFlag) {
firstGraphFlag = false; this.firstGraphFlag = false;
return true; return true;
} }
return false; return false;
}; };
const destructStartLink = (_str: string): FlowLink => { private destructStartLink = (_str: string): FlowLink => {
let str = _str.trim(); let str = _str.trim();
let type = 'arrow_open'; let type = 'arrow_open';
@@ -704,7 +713,7 @@ const destructStartLink = (_str: string): FlowLink => {
return { type, stroke }; return { type, stroke };
}; };
const countChar = (char: string, str: string) => { private countChar = (char: string, str: string) => {
const length = str.length; const length = str.length;
let count = 0; let count = 0;
for (let i = 0; i < length; ++i) { for (let i = 0; i < length; ++i) {
@@ -715,7 +724,7 @@ const countChar = (char: string, str: string) => {
return count; return count;
}; };
const destructEndLink = (_str: string) => { private destructEndLink = (_str: string) => {
const str = _str.trim(); const str = _str.trim();
let line = str.slice(0, -1); let line = str.slice(0, -1);
let type = 'arrow_open'; let type = 'arrow_open';
@@ -755,7 +764,7 @@ const destructEndLink = (_str: string) => {
stroke = 'invisible'; stroke = 'invisible';
} }
const dots = countChar('.', line); const dots = this.countChar('.', line);
if (dots) { if (dots) {
stroke = 'dotted'; stroke = 'dotted';
@@ -765,11 +774,11 @@ const destructEndLink = (_str: string) => {
return { type, stroke, length }; return { type, stroke, length };
}; };
export const destructLink = (_str: string, _startStr: string) => { public destructLink = (_str: string, _startStr: string) => {
const info = destructEndLink(_str); const info = this.destructEndLink(_str);
let startInfo; let startInfo;
if (_startStr) { if (_startStr) {
startInfo = destructStartLink(_startStr); startInfo = this.destructStartLink(_startStr);
if (startInfo.stroke !== info.stroke) { if (startInfo.stroke !== info.stroke) {
return { type: 'INVALID', stroke: 'INVALID' }; return { type: 'INVALID', stroke: 'INVALID' };
@@ -799,7 +808,7 @@ export const destructLink = (_str: string, _startStr: string) => {
}; };
// Todo optimizer this by caching existing nodes // Todo optimizer this by caching existing nodes
const exists = (allSgs: FlowSubGraph[], _id: string) => { public exists = (allSgs: FlowSubGraph[], _id: string) => {
for (const sg of allSgs) { for (const sg of allSgs) {
if (sg.nodes.includes(_id)) { if (sg.nodes.includes(_id)) {
return true; return true;
@@ -811,21 +820,21 @@ const exists = (allSgs: FlowSubGraph[], _id: string) => {
* Deletes an id from all subgraphs * Deletes an id from all subgraphs
* *
*/ */
const makeUniq = (sg: FlowSubGraph, allSubgraphs: FlowSubGraph[]) => { public makeUniq = (sg: FlowSubGraph, allSubgraphs: FlowSubGraph[]) => {
const res: string[] = []; const res: string[] = [];
sg.nodes.forEach((_id, pos) => { sg.nodes.forEach((_id, pos) => {
if (!exists(allSubgraphs, _id)) { if (!this.exists(allSubgraphs, _id)) {
res.push(sg.nodes[pos]); res.push(sg.nodes[pos]);
} }
}); });
return { nodes: res }; return { nodes: res };
}; };
export const lex = { public lex = {
firstGraph, firstGraph: this.firstGraph,
}; };
const getTypeFromVertex = (vertex: FlowVertex): ShapeID => { private getTypeFromVertex = (vertex: FlowVertex): ShapeID => {
if (vertex.img) { if (vertex.img) {
return 'imageSquare'; return 'imageSquare';
} }
@@ -855,8 +864,8 @@ const getTypeFromVertex = (vertex: FlowVertex): ShapeID => {
} }
}; };
const findNode = (nodes: Node[], id: string) => nodes.find((node) => node.id === id); private findNode = (nodes: Node[], id: string) => nodes.find((node) => node.id === id);
const destructEdgeType = (type: string | undefined) => { private destructEdgeType = (type: string | undefined) => {
let arrowTypeStart = 'none'; let arrowTypeStart = 'none';
let arrowTypeEnd = 'arrow_point'; let arrowTypeEnd = 'arrow_point';
switch (type) { switch (type) {
@@ -876,7 +885,7 @@ const destructEdgeType = (type: string | undefined) => {
return { arrowTypeStart, arrowTypeEnd }; return { arrowTypeStart, arrowTypeEnd };
}; };
const addNodeFromVertex = ( private addNodeFromVertex = (
vertex: FlowVertex, vertex: FlowVertex,
nodes: Node[], nodes: Node[],
parentDB: Map<string, string>, parentDB: Map<string, string>,
@@ -887,10 +896,10 @@ const addNodeFromVertex = (
const parentId = parentDB.get(vertex.id); const parentId = parentDB.get(vertex.id);
const isGroup = subGraphDB.get(vertex.id) ?? false; const isGroup = subGraphDB.get(vertex.id) ?? false;
const node = findNode(nodes, vertex.id); const node = this.findNode(nodes, vertex.id);
if (node) { if (node) {
node.cssStyles = vertex.styles; node.cssStyles = vertex.styles;
node.cssCompiledStyles = getCompiledStyles(vertex.classes); node.cssCompiledStyles = this.getCompiledStyles(vertex.classes);
node.cssClasses = vertex.classes.join(' '); node.cssClasses = vertex.classes.join(' ');
} else { } else {
const baseNode = { const baseNode = {
@@ -900,14 +909,14 @@ const addNodeFromVertex = (
parentId, parentId,
padding: config.flowchart?.padding || 8, padding: config.flowchart?.padding || 8,
cssStyles: vertex.styles, cssStyles: vertex.styles,
cssCompiledStyles: getCompiledStyles(['default', 'node', ...vertex.classes]), cssCompiledStyles: this.getCompiledStyles(['default', 'node', ...vertex.classes]),
cssClasses: 'default ' + vertex.classes.join(' '), cssClasses: 'default ' + vertex.classes.join(' '),
dir: vertex.dir, dir: vertex.dir,
domId: vertex.domId, domId: vertex.domId,
look, look,
link: vertex.link, link: vertex.link,
linkTarget: vertex.linkTarget, linkTarget: vertex.linkTarget,
tooltip: getTooltip(vertex.id), tooltip: this.getTooltip(vertex.id),
icon: vertex.icon, icon: vertex.icon,
pos: vertex.pos, pos: vertex.pos,
img: vertex.img, img: vertex.img,
@@ -925,16 +934,16 @@ const addNodeFromVertex = (
nodes.push({ nodes.push({
...baseNode, ...baseNode,
isGroup: false, isGroup: false,
shape: getTypeFromVertex(vertex), shape: this.getTypeFromVertex(vertex),
}); });
} }
} }
}; };
function getCompiledStyles(classDefs: string[]) { private getCompiledStyles = (classDefs: string[]) => {
let compiledStyles: string[] = []; let compiledStyles: string[] = [];
for (const customClass of classDefs) { for (const customClass of classDefs) {
const cssClass = classes.get(customClass); const cssClass = this.classes.get(customClass);
if (cssClass?.styles) { if (cssClass?.styles) {
compiledStyles = [...compiledStyles, ...(cssClass.styles ?? [])].map((s) => s.trim()); compiledStyles = [...compiledStyles, ...(cssClass.styles ?? [])].map((s) => s.trim());
} }
@@ -943,14 +952,14 @@ function getCompiledStyles(classDefs: string[]) {
} }
} }
return compiledStyles; return compiledStyles;
} };
export const getData = () => { public getData = () => {
const config = getConfig(); const config = getConfig();
const nodes: Node[] = []; const nodes: Node[] = [];
const edges: Edge[] = []; const edges: Edge[] = [];
const subGraphs = getSubGraphs(); const subGraphs = this.getSubGraphs();
const parentDB = new Map<string, string>(); const parentDB = new Map<string, string>();
const subGraphDB = new Map<string, boolean>(); const subGraphDB = new Map<string, boolean>();
@@ -974,7 +983,7 @@ export const getData = () => {
labelStyle: '', labelStyle: '',
parentId: parentDB.get(subGraph.id), parentId: parentDB.get(subGraph.id),
padding: 8, padding: 8,
cssCompiledStyles: getCompiledStyles(subGraph.classes), cssCompiledStyles: this.getCompiledStyles(subGraph.classes),
cssClasses: subGraph.classes.join(' '), cssClasses: subGraph.classes.join(' '),
shape: 'rect', shape: 'rect',
dir: subGraph.dir, dir: subGraph.dir,
@@ -983,14 +992,14 @@ export const getData = () => {
}); });
} }
const n = getVertices(); const n = this.getVertices();
n.forEach((vertex) => { n.forEach((vertex) => {
addNodeFromVertex(vertex, nodes, parentDB, subGraphDB, config, config.look || 'classic'); this.addNodeFromVertex(vertex, nodes, parentDB, subGraphDB, config, config.look || 'classic');
}); });
const e = getEdges(); const e = this.getEdges();
e.forEach((rawEdge, index) => { e.forEach((rawEdge, index) => {
const { arrowTypeStart, arrowTypeEnd } = destructEdgeType(rawEdge.type); const { arrowTypeStart, arrowTypeEnd } = this.destructEdgeType(rawEdge.type);
const styles = [...(e.defaultStyle ?? [])]; const styles = [...(e.defaultStyle ?? [])];
if (rawEdge.style) { if (rawEdge.style) {
@@ -1023,41 +1032,11 @@ export const getData = () => {
return { nodes, edges, other: {}, config }; return { nodes, edges, other: {}, config };
}; };
export default { public defaultConfig = () => defaultConfig.flowchart;
defaultConfig: () => defaultConfig.flowchart, public setAccTitle = setAccTitle;
setAccTitle, public setAccDescription = setAccDescription;
getAccTitle, public setDiagramTitle = setDiagramTitle;
getAccDescription, public getAccTitle = getAccTitle;
getData, public getAccDescription = getAccDescription;
setAccDescription, public getDiagramTitle = getDiagramTitle;
addVertex, }
lookUpDomId,
addLink,
updateLinkInterpolate,
updateLink,
addClass,
setDirection,
setClass,
setTooltip,
getTooltip,
setClickEvent,
setLink,
bindFunctions,
getDirection,
getVertices,
getEdges,
getClasses,
clear,
setGen,
defaultStyle,
addSubGraph,
getDepthFirstPos,
indexNodes,
getSubGraphs,
destructLink,
lex,
exists,
makeUniq,
setDiagramTitle,
getDiagramTitle,
};

View File

@@ -1,6 +1,6 @@
import type { MermaidConfig } from '../../config.type.js'; import type { MermaidConfig } from '../../config.type.js';
import { setConfig } from '../../diagram-api/diagramAPI.js'; import { setConfig } from '../../diagram-api/diagramAPI.js';
import flowDb from './flowDb.js'; import { FlowDb } from './flowDb.js';
import renderer from './flowRenderer-v3-unified.js'; import renderer from './flowRenderer-v3-unified.js';
// @ts-ignore: JISON doesn't support types // @ts-ignore: JISON doesn't support types
import flowParser from './parser/flow.jison'; import flowParser from './parser/flow.jison';
@@ -8,7 +8,9 @@ import flowStyles from './styles.js';
export const diagram = { export const diagram = {
parser: flowParser, parser: flowParser,
db: flowDb, get db() {
return new FlowDb();
},
renderer, renderer,
styles: flowStyles, styles: flowStyles,
init: (cnf: MermaidConfig) => { init: (cnf: MermaidConfig) => {
@@ -20,7 +22,5 @@ export const diagram = {
} }
cnf.flowchart.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute; cnf.flowchart.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
setConfig({ flowchart: { arrowMarkerAbsolute: cnf.arrowMarkerAbsolute } }); setConfig({ flowchart: { arrowMarkerAbsolute: cnf.arrowMarkerAbsolute } });
flowDb.clear();
flowDb.setGen('gen-2');
}, },
}; };

View File

@@ -7,7 +7,6 @@ import { getRegisteredLayoutAlgorithm, render } from '../../rendering-util/rende
import { setupViewPortForSVG } from '../../rendering-util/setupViewPortForSVG.js'; import { setupViewPortForSVG } from '../../rendering-util/setupViewPortForSVG.js';
import type { LayoutData } from '../../rendering-util/types.js'; import type { LayoutData } from '../../rendering-util/types.js';
import utils from '../../utils.js'; import utils from '../../utils.js';
import { getDirection } from './flowDb.js';
export const getClasses = function ( export const getClasses = function (
text: string, text: string,
@@ -37,7 +36,7 @@ export const draw = async function (text: string, id: string, _version: string,
log.debug('Data: ', data4Layout); log.debug('Data: ', data4Layout);
// Create the root SVG // Create the root SVG
const svg = getDiagramElement(id, securityLevel); const svg = getDiagramElement(id, securityLevel);
const direction = getDirection(); const direction = diag.db.getDirection();
data4Layout.type = diag.type; data4Layout.type = diag.type;
data4Layout.layoutAlgorithm = getRegisteredLayoutAlgorithm(layout); data4Layout.layoutAlgorithm = getRegisteredLayoutAlgorithm(layout);

View File

@@ -1,4 +1,4 @@
import flowDb from '../flowDb.js'; import { FlowDb } from '../flowDb.js';
import flow from './flow.jison'; import flow from './flow.jison';
import { setConfig } from '../../../config.js'; import { setConfig } from '../../../config.js';
@@ -8,7 +8,7 @@ setConfig({
describe('[Arrows] when parsing', () => { describe('[Arrows] when parsing', () => {
beforeEach(function () { beforeEach(function () {
flow.parser.yy = flowDb; flow.parser.yy = new FlowDb();
flow.parser.yy.clear(); flow.parser.yy.clear();
}); });

View File

@@ -1,4 +1,4 @@
import flowDb from '../flowDb.js'; import { FlowDb } from '../flowDb.js';
import flow from './flow.jison'; import flow from './flow.jison';
import { setConfig } from '../../../config.js'; import { setConfig } from '../../../config.js';
import { cleanupComments } from '../../../diagram-api/comments.js'; import { cleanupComments } from '../../../diagram-api/comments.js';
@@ -9,7 +9,7 @@ setConfig({
describe('[Comments] when parsing', () => { describe('[Comments] when parsing', () => {
beforeEach(function () { beforeEach(function () {
flow.parser.yy = flowDb; flow.parser.yy = new FlowDb();
flow.parser.yy.clear(); flow.parser.yy.clear();
}); });

View File

@@ -1,4 +1,4 @@
import flowDb from '../flowDb.js'; import { FlowDb } from '../flowDb.js';
import flow from './flow.jison'; import flow from './flow.jison';
import { setConfig } from '../../../config.js'; import { setConfig } from '../../../config.js';
@@ -8,7 +8,7 @@ setConfig({
describe('when parsing directions', function () { describe('when parsing directions', function () {
beforeEach(function () { beforeEach(function () {
flow.parser.yy = flowDb; flow.parser.yy = new FlowDb();
flow.parser.yy.clear(); flow.parser.yy.clear();
flow.parser.yy.setGen('gen-2'); flow.parser.yy.setGen('gen-2');
}); });

View File

@@ -1,4 +1,4 @@
import flowDb from '../flowDb.js'; import { FlowDb } from '../flowDb.js';
import flow from './flow.jison'; import flow from './flow.jison';
import { setConfig } from '../../../config.js'; import { setConfig } from '../../../config.js';
@@ -42,7 +42,7 @@ const doubleEndedEdges = [
describe('[Edges] when parsing', () => { describe('[Edges] when parsing', () => {
beforeEach(function () { beforeEach(function () {
flow.parser.yy = flowDb; flow.parser.yy = new FlowDb();
flow.parser.yy.clear(); flow.parser.yy.clear();
}); });

View File

@@ -1,4 +1,4 @@
import flowDb from '../flowDb.js'; import { FlowDb } from '../flowDb.js';
import flow from './flow.jison'; import flow from './flow.jison';
import { setConfig } from '../../../config.js'; import { setConfig } from '../../../config.js';
@@ -8,7 +8,7 @@ setConfig({
describe('[Text] when parsing', () => { describe('[Text] when parsing', () => {
beforeEach(function () { beforeEach(function () {
flow.parser.yy = flowDb; flow.parser.yy = new FlowDb();
flow.parser.yy.clear(); flow.parser.yy.clear();
}); });

View File

@@ -1,4 +1,4 @@
import flowDb from '../flowDb.js'; import { FlowDb } from '../flowDb.js';
import flow from './flow.jison'; import flow from './flow.jison';
import { setConfig } from '../../../config.js'; import { setConfig } from '../../../config.js';
import { vi } from 'vitest'; import { vi } from 'vitest';
@@ -9,7 +9,9 @@ setConfig({
}); });
describe('[Interactions] when parsing', () => { describe('[Interactions] when parsing', () => {
let flowDb;
beforeEach(function () { beforeEach(function () {
flowDb = new FlowDb();
flow.parser.yy = flowDb; flow.parser.yy = flowDb;
flow.parser.yy.clear(); flow.parser.yy.clear();
}); });

View File

@@ -1,4 +1,4 @@
import flowDb from '../flowDb.js'; import { FlowDb } from '../flowDb.js';
import flow from './flow.jison'; import flow from './flow.jison';
import { setConfig } from '../../../config.js'; import { setConfig } from '../../../config.js';
@@ -8,7 +8,7 @@ setConfig({
describe('[Lines] when parsing', () => { describe('[Lines] when parsing', () => {
beforeEach(function () { beforeEach(function () {
flow.parser.yy = flowDb; flow.parser.yy = new FlowDb();
flow.parser.yy.clear(); flow.parser.yy.clear();
}); });

View File

@@ -1,4 +1,4 @@
import flowDb from '../flowDb.js'; import { FlowDb } from '../flowDb.js';
import flow from './flow.jison'; import flow from './flow.jison';
import { setConfig } from '../../../config.js'; import { setConfig } from '../../../config.js';
@@ -8,7 +8,7 @@ setConfig({
describe('parsing a flow chart with markdown strings', function () { describe('parsing a flow chart with markdown strings', function () {
beforeEach(function () { beforeEach(function () {
flow.parser.yy = flowDb; flow.parser.yy = new FlowDb();
flow.parser.yy.clear(); flow.parser.yy.clear();
}); });

View File

@@ -1,4 +1,4 @@
import flowDb from '../flowDb.js'; import { FlowDb } from '../flowDb.js';
import flow from './flow.jison'; import flow from './flow.jison';
import { setConfig } from '../../../config.js'; import { setConfig } from '../../../config.js';
@@ -8,7 +8,7 @@ setConfig({
describe('when parsing directions', function () { describe('when parsing directions', function () {
beforeEach(function () { beforeEach(function () {
flow.parser.yy = flowDb; flow.parser.yy = new FlowDb();
flow.parser.yy.clear(); flow.parser.yy.clear();
flow.parser.yy.setGen('gen-2'); flow.parser.yy.setGen('gen-2');
}); });

View File

@@ -1,4 +1,4 @@
import flowDb from '../flowDb.js'; import { FlowDb } from '../flowDb.js';
import flow from './flow.jison'; import flow from './flow.jison';
import { setConfig } from '../../../config.js'; import { setConfig } from '../../../config.js';
@@ -31,7 +31,7 @@ const specialChars = ['#', ':', '0', '&', ',', '*', '.', '\\', 'v', '-', '/', '_
describe('[Singlenodes] when parsing', () => { describe('[Singlenodes] when parsing', () => {
beforeEach(function () { beforeEach(function () {
flow.parser.yy = flowDb; flow.parser.yy = new FlowDb();
flow.parser.yy.clear(); flow.parser.yy.clear();
}); });

View File

@@ -1,4 +1,4 @@
import flowDb from '../flowDb.js'; import { FlowDb } from '../flowDb.js';
import flow from './flow.jison'; import flow from './flow.jison';
import { setConfig } from '../../../config.js'; import { setConfig } from '../../../config.js';
@@ -8,7 +8,7 @@ setConfig({
describe('[Style] when parsing', () => { describe('[Style] when parsing', () => {
beforeEach(function () { beforeEach(function () {
flow.parser.yy = flowDb; flow.parser.yy = new FlowDb();
flow.parser.yy.clear(); flow.parser.yy.clear();
flow.parser.yy.setGen('gen-2'); flow.parser.yy.setGen('gen-2');
}); });

View File

@@ -1,4 +1,4 @@
import flowDb from '../flowDb.js'; import { FlowDb } from '../flowDb.js';
import flow from './flow.jison'; import flow from './flow.jison';
import { setConfig } from '../../../config.js'; import { setConfig } from '../../../config.js';
@@ -8,7 +8,7 @@ setConfig({
describe('[Text] when parsing', () => { describe('[Text] when parsing', () => {
beforeEach(function () { beforeEach(function () {
flow.parser.yy = flowDb; flow.parser.yy = new FlowDb();
flow.parser.yy.clear(); flow.parser.yy.clear();
}); });

View File

@@ -1,4 +1,4 @@
import flowDb from '../flowDb.js'; import { FlowDb } from '../flowDb.js';
import flow from './flow.jison'; import flow from './flow.jison';
import { setConfig } from '../../../config.js'; import { setConfig } from '../../../config.js';
@@ -8,7 +8,7 @@ setConfig({
describe('when parsing flowcharts', function () { describe('when parsing flowcharts', function () {
beforeEach(function () { beforeEach(function () {
flow.parser.yy = flowDb; flow.parser.yy = new FlowDb();
flow.parser.yy.clear(); flow.parser.yy.clear();
flow.parser.yy.setGen('gen-2'); flow.parser.yy.setGen('gen-2');
}); });

View File

@@ -1,4 +1,4 @@
import flowDb from '../flowDb.js'; import { FlowDb } from '../flowDb.js';
import flow from './flow.jison'; import flow from './flow.jison';
import { cleanupComments } from '../../../diagram-api/comments.js'; import { cleanupComments } from '../../../diagram-api/comments.js';
import { setConfig } from '../../../config.js'; import { setConfig } from '../../../config.js';
@@ -9,7 +9,7 @@ setConfig({
describe('parsing a flow chart', function () { describe('parsing a flow chart', function () {
beforeEach(function () { beforeEach(function () {
flow.parser.yy = flowDb; flow.parser.yy = new FlowDb();
flow.parser.yy.clear(); flow.parser.yy.clear();
}); });

View File

@@ -1,4 +1,4 @@
import flowDb from '../flowDb.js'; import { FlowDb } from '../flowDb.js';
import flow from './flow.jison'; import flow from './flow.jison';
import { setConfig } from '../../../config.js'; import { setConfig } from '../../../config.js';
@@ -8,7 +8,7 @@ setConfig({
describe('when parsing subgraphs', function () { describe('when parsing subgraphs', function () {
beforeEach(function () { beforeEach(function () {
flow.parser.yy = flowDb; flow.parser.yy = new FlowDb();
flow.parser.yy.clear(); flow.parser.yy.clear();
flow.parser.yy.setGen('gen-2'); flow.parser.yy.setGen('gen-2');
}); });