refactor: Convert flowDB to TS

This commit is contained in:
Sidharth Vinod
2023-11-27 10:57:04 +05:30
parent fd0f51926e
commit b5e58f4076

View File

@@ -12,39 +12,90 @@ import {
setDiagramTitle, setDiagramTitle,
getDiagramTitle, getDiagramTitle,
} from '../common/commonDb.js'; } from '../common/commonDb.js';
import errorDiagram from '../error/errorDiagram.js';
const MAX_EDGE_COUNT = 280;
interface FlowVertex {
id: string;
labelType: 'text';
dir?: string;
props?: any;
type?: string;
text?: string;
link?: string;
linkTarget?: string;
haveCallback?: boolean;
domId: string;
styles: any[];
classes: any[];
}
interface FlowText {
text: string;
type: 'text';
}
interface FlowEdge {
start: string;
end: string;
type?: string;
stroke?: string;
length?: number;
text: string;
labelType: 'text';
}
interface FlowClass {
id: string;
styles: string[];
textStyles: string[];
}
interface FlowSubGraph {
id: string;
nodes: string[];
title: string;
classes: string[];
dir?: string;
labelType: string;
}
interface FlowLink {
type: string;
stroke: string;
length?: number;
}
const MERMAID_DOM_ID_PREFIX = 'flowchart-'; const MERMAID_DOM_ID_PREFIX = 'flowchart-';
let vertexCounter = 0; let vertexCounter = 0;
let config = getConfig(); let config = getConfig();
let vertices = {}; let vertices: Record<string, FlowVertex> = {};
let edges = []; let edges: any[] & { defaultInterpolate?: string; defaultStyle?: string } = [];
let classes = {}; let classes: Record<string, FlowClass> = {};
let subGraphs = []; let subGraphs: FlowSubGraph[] = [];
let subGraphLookup = {}; let subGraphLookup: Record<string, FlowSubGraph> = {};
let tooltips = {}; let tooltips: Record<string, string> = {};
let subCount = 0; let subCount = 0;
let firstGraphFlag = true; let firstGraphFlag = true;
let direction; let direction: string;
let version; // As in graph let version: string; // As in graph
// Functions to be run after graph rendering // Functions to be run after graph rendering
let funs = []; let funs: ((element: HTMLElement) => void)[] = [];
const sanitizeText = (txt) => common.sanitizeText(txt, config); const sanitizeText = (txt: string) => common.sanitizeText(txt, config);
/** /**
* Function to lookup domId from id in the graph definition. * Function to lookup domId from id in the graph definition.
* *
* @param id * @param id - id of the node
* @public
*/ */
export const lookUpDomId = function (id) { export const lookUpDomId = function (id: string) {
const veritceKeys = Object.keys(vertices); const vertexKeys = Object.keys(vertices);
for (const veritceKey of veritceKeys) { for (const vertexKey of vertexKeys) {
if (vertices[veritceKey].id === id) { if (vertices[vertexKey].id === id) {
return vertices[veritceKey].domId; return vertices[vertexKey].domId;
} }
} }
return id; return id;
@@ -53,18 +104,18 @@ export const lookUpDomId = function (id) {
/** /**
* Function called by parser when a node definition has been found * Function called by parser when a node definition has been found
* *
* @param _id
* @param text
* @param textObj
* @param type
* @param style
* @param classes
* @param dir
* @param props
*/ */
export const addVertex = function (_id, textObj, type, style, classes, dir, props = {}) { export const addVertex = function (
_id: string,
textObj: FlowText,
type: 'group',
style: any[],
classes: any[],
dir: string,
props = {}
) {
let txt; let txt;
let id = _id; const id = _id;
if (id === undefined) { if (id === undefined) {
return; return;
} }
@@ -72,8 +123,6 @@ export const addVertex = function (_id, textObj, type, style, classes, dir, prop
return; return;
} }
// if (id[0].match(/\d/)) id = MERMAID_DOM_ID_PREFIX + id;
if (vertices[id] === undefined) { if (vertices[id] === undefined) {
vertices[id] = { vertices[id] = {
id: id, id: id,
@@ -125,20 +174,15 @@ export const addVertex = function (_id, textObj, type, style, classes, dir, prop
/** /**
* Function called by parser when a link/edge definition has been found * Function called by parser when a link/edge definition has been found
* *
* @param _start
* @param _end
* @param type
* @param linkText
* @param linkTextObj
*/ */
export const addSingleLink = function (_start, _end, type) { export const addSingleLink = function (_start: string, _end: string, type: any) {
let start = _start; const start = _start;
let end = _end; const end = _end;
// if (start[0].match(/\d/)) start = MERMAID_DOM_ID_PREFIX + start; // if (start[0].match(/\d/)) start = MERMAID_DOM_ID_PREFIX + start;
// if (end[0].match(/\d/)) end = MERMAID_DOM_ID_PREFIX + end; // if (end[0].match(/\d/)) end = MERMAID_DOM_ID_PREFIX + end;
// log.info('Got edge...', start, end); // log.info('Got edge...', start, end);
const edge = { start: start, end: end, type: undefined, text: '', labelType: 'text' }; const edge: FlowEdge = { start: start, end: end, type: undefined, text: '', labelType: 'text' };
log.info('abc78 Got edge...', edge); log.info('abc78 Got edge...', edge);
const linkTextObj = type.text; const linkTextObj = type.text;
@@ -155,24 +199,22 @@ export const addSingleLink = function (_start, _end, type) {
if (type !== undefined) { if (type !== undefined) {
edge.type = type.type; edge.type = type.type;
edge.stroke = type.stroke; edge.stroke = type.stroke;
edge.length = type.length; edge.length = type.length > 10 ? 10 : type.length;
} }
if (edge?.length > 10) {
edge.length = 10; if (edges.length > MAX_EDGE_COUNT) {
}
if (edges.length < 280) {
log.info('abc78 pushing edge...');
edges.push(edge);
} else {
throw new Error('Too many edges'); throw new Error('Too many edges');
} }
log.info('Pushing edge...');
edges.push(edge);
}; };
export const addLink = function (_start, _end, type) {
log.info('addLink (abc78)', _start, _end, type); export const addLink = function (_start: string[], _end: string[], type: any) {
let i, j; log.info('addLink', _start, _end, type);
for (i = 0; i < _start.length; i++) { for (const start of _start) {
for (j = 0; j < _end.length; j++) { for (const end of _end) {
addSingleLink(_start[i], _end[j], type); addSingleLink(start, end, type);
} }
} }
}; };
@@ -180,15 +222,16 @@ export const addLink = function (_start, _end, type) {
/** /**
* Updates a link's line interpolation algorithm * Updates a link's line interpolation algorithm
* *
* @param positions
* @param interp
*/ */
export const updateLinkInterpolate = function (positions, interp) { export const updateLinkInterpolate = function (
positions: ('default' | number)[],
interpolate: string
) {
positions.forEach(function (pos) { positions.forEach(function (pos) {
if (pos === 'default') { if (pos === 'default') {
edges.defaultInterpolate = interp; edges.defaultInterpolate = interpolate;
} else { } else {
edges[pos].interpolate = interp; edges[pos].interpolate = interpolate;
} }
}); });
}; };
@@ -196,12 +239,10 @@ export const updateLinkInterpolate = function (positions, interp) {
/** /**
* Updates a link with a style * Updates a link with a style
* *
* @param positions
* @param style
*/ */
export const updateLink = function (positions, style) { export const updateLink = function (positions: ('default' | number)[], style: any) {
positions.forEach(function (pos) { positions.forEach(function (pos) {
if (pos >= edges.length) { if (typeof pos === 'number' && pos >= 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 edges.length - 1
@@ -219,7 +260,7 @@ export const updateLink = function (positions, style) {
}); });
}; };
export const addClass = function (ids, style) { export const addClass = function (ids: string, style: string[]) {
ids.split(',').forEach(function (id) { ids.split(',').forEach(function (id) {
if (classes[id] === undefined) { if (classes[id] === undefined) {
classes[id] = { id, styles: [], textStyles: [] }; classes[id] = { id, styles: [], textStyles: [] };
@@ -240,9 +281,8 @@ export const addClass = function (ids, style) {
/** /**
* 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.
* *
* @param dir
*/ */
export const setDirection = function (dir) { export const setDirection = function (dir: string) {
direction = dir; direction = dir;
if (direction.match(/.*</)) { if (direction.match(/.*</)) {
direction = 'RL'; direction = 'RL';
@@ -264,14 +304,12 @@ export const setDirection = function (dir) {
/** /**
* Called by parser when a special node is found, e.g. a clickable element. * Called by parser when a special node is found, e.g. a clickable element.
* *
* @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, className) { export const setClass = function (ids: string, className: string) {
ids.split(',').forEach(function (_id) { ids.split(',').forEach(function (_id) {
// let id = version === 'gen-2' ? lookUpDomId(_id) : _id; const id = _id;
let id = _id;
// if (_id[0].match(/\d/)) id = MERMAID_DOM_ID_PREFIX + id;
if (vertices[id] !== undefined) { if (vertices[id] !== undefined) {
vertices[id].classes.push(className); vertices[id].classes.push(className);
} }
@@ -282,7 +320,7 @@ export const setClass = function (ids, className) {
}); });
}; };
const setTooltip = function (ids, tooltip) { const setTooltip = function (ids: string, tooltip: string) {
ids.split(',').forEach(function (id) { ids.split(',').forEach(function (id) {
if (tooltip !== undefined) { if (tooltip !== undefined) {
tooltips[version === 'gen-1' ? lookUpDomId(id) : id] = sanitizeText(tooltip); tooltips[version === 'gen-1' ? lookUpDomId(id) : id] = sanitizeText(tooltip);
@@ -290,8 +328,8 @@ const setTooltip = function (ids, tooltip) {
}); });
}; };
const setClickFun = function (id, functionName, functionArgs) { const setClickFun = function (id: string, functionName: string, functionArgs: string) {
let domId = lookUpDomId(id); const domId = 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;
@@ -299,7 +337,7 @@ const setClickFun = function (id, functionName, functionArgs) {
if (functionName === undefined) { if (functionName === undefined) {
return; return;
} }
let argList = []; let argList: string[] = [];
if (typeof functionArgs === 'string') { if (typeof functionArgs === 'string') {
/* Splits functionArgs by ',', ignoring all ',' in double quoted strings */ /* Splits functionArgs by ',', ignoring all ',' in double quoted strings */
argList = functionArgs.split(/,(?=(?:(?:[^"]*"){2})*[^"]*$)/); argList = functionArgs.split(/,(?=(?:(?:[^"]*"){2})*[^"]*$)/);
@@ -339,11 +377,11 @@ const setClickFun = function (id, functionName, functionArgs) {
/** /**
* Called by parser when a link is found. Adds the URL to the vertex data. * Called by parser when a link is found. Adds the URL to the vertex data.
* *
* @param ids Comma separated list of ids * @param ids - Comma separated list of ids
* @param linkStr URL to create a link for * @param linkStr - URL to create a link for
* @param target * @param target - Target attribute for the link
*/ */
export const setLink = function (ids, linkStr, target) { export const setLink = function (ids: string, linkStr: string, target: string) {
ids.split(',').forEach(function (id) { ids.split(',').forEach(function (id) {
if (vertices[id] !== undefined) { if (vertices[id] !== undefined) {
vertices[id].link = utils.formatUrl(linkStr, config); vertices[id].link = utils.formatUrl(linkStr, config);
@@ -352,7 +390,8 @@ export const setLink = function (ids, linkStr, target) {
}); });
setClass(ids, 'clickable'); setClass(ids, 'clickable');
}; };
export const getTooltip = function (id) {
export const getTooltip = function (id: string) {
if (tooltips.hasOwnProperty(id)) { if (tooltips.hasOwnProperty(id)) {
return tooltips[id]; return tooltips[id];
} }
@@ -362,18 +401,18 @@ export const getTooltip = function (id) {
/** /**
* Called by parser when a click definition is found. Registers an event handler. * Called by parser when a click definition is found. Registers an event handler.
* *
* @param ids Comma separated list of ids * @param ids - Comma separated list of ids
* @param functionName Function to be called on click * @param functionName - Function to be called on click
* @param functionArgs * @param functionArgs - Arguments to be passed to the function
*/ */
export const setClickEvent = function (ids, functionName, functionArgs) { export const setClickEvent = function (ids: string, functionName: string, functionArgs: string) {
ids.split(',').forEach(function (id) { ids.split(',').forEach(function (id) {
setClickFun(id, functionName, functionArgs); setClickFun(id, functionName, functionArgs);
}); });
setClass(ids, 'clickable'); setClass(ids, 'clickable');
}; };
export const bindFunctions = function (element) { export const bindFunctions = function (element: HTMLElement) {
funs.forEach(function (fun) { funs.forEach(function (fun) {
fun(element); fun(element);
}); });
@@ -384,7 +423,6 @@ export const getDirection = function () {
/** /**
* Retrieval function for fetching the found nodes after parsing has completed. * Retrieval function for fetching the found nodes after parsing has completed.
* *
* @returns {{} | any | vertices}
*/ */
export const getVertices = function () { export const getVertices = function () {
return vertices; return vertices;
@@ -393,7 +431,6 @@ export const getVertices = function () {
/** /**
* Retrieval function for fetching the found links after parsing has completed. * Retrieval function for fetching the found links after parsing has completed.
* *
* @returns {{} | any | edges}
*/ */
export const getEdges = function () { export const getEdges = function () {
return edges; return edges;
@@ -402,15 +439,16 @@ export const getEdges = function () {
/** /**
* Retrieval function for fetching the found class definitions after parsing has completed. * Retrieval function for fetching the found class definitions after parsing has completed.
* *
* @returns {{} | any | classes}
*/ */
export const getClasses = function () { export const getClasses = function () {
return classes; return classes;
}; };
const setupToolTips = function (element) { const setupToolTips = function (element: HTMLElement) {
let tooltipElem = select('.mermaidTooltip'); let tooltipElem = select('.mermaidTooltip');
// @ts-ignore TODO: fix this
if ((tooltipElem._groups || tooltipElem)[0][0] === null) { if ((tooltipElem._groups || tooltipElem)[0][0] === null) {
// @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);
} }
@@ -426,8 +464,9 @@ const setupToolTips = function (element) {
if (title === null) { if (title === null) {
return; return;
} }
const rect = this.getBoundingClientRect(); const rect = (this as Element)?.getBoundingClientRect();
// @ts-ignore TODO: fix this
tooltipElem.transition().duration(200).style('opacity', '.9'); tooltipElem.transition().duration(200).style('opacity', '.9');
tooltipElem tooltipElem
.text(el.attr('title')) .text(el.attr('title'))
@@ -437,6 +476,7 @@ const setupToolTips = function (element) {
el.classed('hover', true); el.classed('hover', true);
}) })
.on('mouseout', function () { .on('mouseout', function () {
// @ts-ignore TODO: fix this
tooltipElem.transition().duration(500).style('opacity', 0); tooltipElem.transition().duration(500).style('opacity', 0);
const el = select(this); const el = select(this);
el.classed('hover', false); el.classed('hover', false);
@@ -447,7 +487,6 @@ 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.
* *
* @param ver
*/ */
export const clear = function (ver = 'gen-1') { export const clear = function (ver = 'gen-1') {
vertices = {}; vertices = {};
@@ -462,31 +501,29 @@ export const clear = function (ver = 'gen-1') {
version = ver; version = ver;
commonClear(); commonClear();
}; };
export const setGen = (ver) => {
export const setGen = (ver: string) => {
version = ver || 'gen-2'; version = ver || 'gen-2';
}; };
/** @returns {string} */
export const defaultStyle = function () { export const defaultStyle = function () {
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 (
* Clears the internal graph db so that a new graph can be parsed. _id: { text: string },
* list: string[],
* @param _id _title: { text: string; type: string }
* @param list ) {
* @param _title let id: string | undefined = _id.text.trim();
*/
export const addSubGraph = function (_id, list, _title) {
let id = _id.text.trim();
let title = _title.text; let title = _title.text;
if (_id === _title && _title.text.match(/\s/)) { if (_id === _title && _title.text.match(/\s/)) {
id = undefined; id = undefined;
} }
/** @param a */
function uniq(a) { function uniq(a: any[]) {
const prims = { boolean: {}, number: {}, string: {} }; const prims: any = { boolean: {}, number: {}, string: {} };
const objs = []; const objs: any[] = [];
let dir; // = undefined; direction.trim(); let dir; // = undefined; direction.trim();
const nodeList = a.filter(function (item) { const nodeList = a.filter(function (item) {
@@ -507,9 +544,9 @@ export const addSubGraph = function (_id, list, _title) {
return { nodeList, dir }; return { nodeList, dir };
} }
let nodeList = []; let nodeList: string[] = [];
const { nodeList: nl, dir } = uniq(nodeList.concat.apply(nodeList, list)); const { nodeList: nl, dir } = uniq([...nodeList, ...list]);
nodeList = nl; nodeList = nl;
if (version === 'gen-1') { if (version === 'gen-1') {
for (let i = 0; i < nodeList.length; i++) { for (let i = 0; i < nodeList.length; i++) {
@@ -518,7 +555,6 @@ export const addSubGraph = function (_id, list, _title) {
} }
id = id || 'subGraph' + subCount; id = id || 'subGraph' + subCount;
// if (id[0].match(/\d/)) id = lookUpDomId(id);
title = title || ''; title = title || '';
title = sanitizeText(title); title = sanitizeText(title);
subCount = subCount + 1; subCount = subCount + 1;
@@ -533,19 +569,6 @@ export const addSubGraph = function (_id, list, _title) {
log.info('Adding', subGraph.id, subGraph.nodes, subGraph.dir); log.info('Adding', subGraph.id, subGraph.nodes, subGraph.dir);
/** Deletes an id from all subgraphs */
// const del = _id => {
// subGraphs.forEach(sg => {
// const pos = sg.nodes.indexOf(_id);
// if (pos >= 0) {
// sg.nodes.splice(pos, 1);
// }
// });
// };
// // Removes the members of this subgraph from any other subgraphs, a node only belong to one subgraph
// subGraph.nodes.forEach(_id => del(_id));
// 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 = makeUniq(subGraph, subGraphs).nodes;
subGraphs.push(subGraph); subGraphs.push(subGraph);
@@ -553,7 +576,7 @@ export const addSubGraph = function (_id, list, _title) {
return id; return id;
}; };
const getPosForId = function (id) { const getPosForId = function (id: string) {
for (const [i, subGraph] of subGraphs.entries()) { for (const [i, subGraph] of subGraphs.entries()) {
if (subGraph.id === id) { if (subGraph.id === id) {
return i; return i;
@@ -562,12 +585,15 @@ const getPosForId = function (id) {
return -1; return -1;
}; };
let secCount = -1; let secCount = -1;
const posCrossRef = []; const posCrossRef: number[] = [];
const indexNodes2 = function (id, pos) { const indexNodes2 = function (id: string, pos: number): { result: boolean; count: number } {
const nodes = subGraphs[pos].nodes; const nodes = subGraphs[pos].nodes;
secCount = secCount + 1; secCount = secCount + 1;
if (secCount > 2000) { if (secCount > 2000) {
return; return {
result: false,
count: 0,
};
} }
posCrossRef[secCount] = pos; posCrossRef[secCount] = pos;
// Check if match // Check if match
@@ -603,13 +629,13 @@ const indexNodes2 = function (id, pos) {
}; };
}; };
export const getDepthFirstPos = function (pos) { export const getDepthFirstPos = function (pos: number) {
return posCrossRef[pos]; return posCrossRef[pos];
}; };
export const indexNodes = function () { export const indexNodes = function () {
secCount = -1; secCount = -1;
if (subGraphs.length > 0) { if (subGraphs.length > 0) {
indexNodes2('none', subGraphs.length - 1, 0); indexNodes2('none', subGraphs.length - 1);
} }
}; };
@@ -625,7 +651,7 @@ export const firstGraph = () => {
return false; return false;
}; };
const destructStartLink = (_str) => { const destructStartLink = (_str: string): FlowLink => {
let str = _str.trim(); let str = _str.trim();
let type = 'arrow_open'; let type = 'arrow_open';
@@ -657,7 +683,7 @@ const destructStartLink = (_str) => {
return { type, stroke }; return { type, stroke };
}; };
const countChar = (char, str) => { const 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) {
@@ -668,7 +694,7 @@ const countChar = (char, str) => {
return count; return count;
}; };
const destructEndLink = (_str) => { const 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';
@@ -708,7 +734,7 @@ const destructEndLink = (_str) => {
stroke = 'invisible'; stroke = 'invisible';
} }
let dots = countChar('.', line); const dots = countChar('.', line);
if (dots) { if (dots) {
stroke = 'dotted'; stroke = 'dotted';
@@ -718,7 +744,7 @@ const destructEndLink = (_str) => {
return { type, stroke, length }; return { type, stroke, length };
}; };
export const destructLink = (_str, _startStr) => { export const destructLink = (_str: string, _startStr: string) => {
const info = destructEndLink(_str); const info = destructEndLink(_str);
let startInfo; let startInfo;
if (_startStr) { if (_startStr) {
@@ -752,7 +778,7 @@ export const destructLink = (_str, _startStr) => {
}; };
// Todo optimizer this by caching existing nodes // Todo optimizer this by caching existing nodes
const exists = (allSgs, _id) => { const exists = (allSgs: FlowSubGraph[], _id: string) => {
let res = false; let res = false;
allSgs.forEach((sg) => { allSgs.forEach((sg) => {
const pos = sg.nodes.indexOf(_id); const pos = sg.nodes.indexOf(_id);
@@ -765,11 +791,9 @@ const exists = (allSgs, _id) => {
/** /**
* Deletes an id from all subgraphs * Deletes an id from all subgraphs
* *
* @param sg
* @param allSubgraphs
*/ */
const makeUniq = (sg, allSubgraphs) => { const makeUniq = (sg: FlowSubGraph, allSubgraphs: FlowSubGraph[]) => {
const res = []; const res: string[] = [];
sg.nodes.forEach((_id, pos) => { sg.nodes.forEach((_id, pos) => {
if (!exists(allSubgraphs, _id)) { if (!exists(allSubgraphs, _id)) {
res.push(sg.nodes[pos]); res.push(sg.nodes[pos]);
@@ -781,6 +805,7 @@ const makeUniq = (sg, allSubgraphs) => {
export const lex = { export const lex = {
firstGraph, firstGraph,
}; };
export default { export default {
defaultConfig: () => defaultConfig.flowchart, defaultConfig: () => defaultConfig.flowchart,
setAccTitle, setAccTitle,