chore: Add types to stateDB and dataFetcher

This commit is contained in:
Sidharth Vinod
2025-02-17 23:47:56 +05:30
parent 438f388b5c
commit 7ca9242b24
2 changed files with 324 additions and 301 deletions

View File

@@ -1,3 +1,4 @@
import type { MermaidConfig } from '../../config.type.js';
import { getConfig } from '../../diagram-api/diagramAPI.js'; import { getConfig } from '../../diagram-api/diagramAPI.js';
import { log } from '../../logger.js'; import { log } from '../../logger.js';
import common from '../common/common.js'; import common from '../common/common.js';
@@ -33,9 +34,10 @@ import {
STMT_RELATION, STMT_RELATION,
STMT_STATE, STMT_STATE,
} from './stateCommon.js'; } from './stateCommon.js';
import type { Edge, NodeData, State, StateStmt, Stmt, StyleClass } from './stateDb.js';
// List of nodes created from the parsed diagram statement items // List of nodes created from the parsed diagram statement items
let nodeDb = new Map(); const nodeDb = new Map<string, NodeData>();
let graphItemCount = 0; // used to construct ids, etc. let graphItemCount = 0; // used to construct ids, etc.
@@ -43,18 +45,27 @@ let graphItemCount = 0; // used to construct ids, etc.
* Create a standard string for the dom ID of an item. * Create a standard string for the dom ID of an item.
* If a type is given, insert that before the counter, preceded by the type spacer * If a type is given, insert that before the counter, preceded by the type spacer
* *
* @param itemId
* @param counter
* @param {string | null} type
* @param typeSpacer
* @returns {string}
*/ */
export function stateDomId(itemId = '', counter = 0, type = '', typeSpacer = DOMID_TYPE_SPACER) { export function stateDomId(
itemId = '',
counter = 0,
type: string | null = '',
typeSpacer = DOMID_TYPE_SPACER
) {
const typeStr = type !== null && type.length > 0 ? `${typeSpacer}${type}` : ''; const typeStr = type !== null && type.length > 0 ? `${typeSpacer}${type}` : '';
return `${DOMID_STATE}-${itemId}${typeStr}-${counter}`; return `${DOMID_STATE}-${itemId}${typeStr}-${counter}`;
} }
const setupDoc = (parentParsedItem, doc, diagramStates, nodes, edges, altFlag, look, classes) => { const setupDoc = (
parentParsedItem: StateStmt | undefined,
doc: Stmt[],
diagramStates: Map<string, State>,
nodes: NodeData[],
edges: Edge[],
altFlag: boolean,
look: MermaidConfig['look'],
classes: Map<string, StyleClass>
) => {
// graphItemCount = 0; // graphItemCount = 0;
log.trace('items', doc); log.trace('items', doc);
doc.forEach((item) => { doc.forEach((item) => {
@@ -95,7 +106,7 @@ const setupDoc = (parentParsedItem, doc, diagramStates, nodes, edges, altFlag, l
arrowTypeEnd: 'arrow_barb', arrowTypeEnd: 'arrow_barb',
style: G_EDGE_STYLE, style: G_EDGE_STYLE,
labelStyle: '', labelStyle: '',
label: common.sanitizeText(item.description, getConfig()), label: common.sanitizeText(item.description ?? '', getConfig()),
arrowheadStyle: G_EDGE_ARROWHEADSTYLE, arrowheadStyle: G_EDGE_ARROWHEADSTYLE,
labelpos: G_EDGE_LABELPOS, labelpos: G_EDGE_LABELPOS,
labelType: G_EDGE_LABELTYPE, labelType: G_EDGE_LABELTYPE,
@@ -115,11 +126,10 @@ const setupDoc = (parentParsedItem, doc, diagramStates, nodes, edges, altFlag, l
* Get the direction from the statement items. * Get the direction from the statement items.
* Look through all of the documents (docs) in the parsedItems * Look through all of the documents (docs) in the parsedItems
* Because is a _document_ direction, the default direction is not necessarily the same as the overall default _diagram_ direction. * Because is a _document_ direction, the default direction is not necessarily the same as the overall default _diagram_ direction.
* @param {object[]} parsedItem - the parsed statement item to look through * @param parsedItem - the parsed statement item to look through
* @param [defaultDir] - the direction to use if none is found * @param defaultDir - the direction to use if none is found
* @returns {string}
*/ */
const getDir = (parsedItem, defaultDir = DEFAULT_NESTED_DOC_DIR) => { const getDir = (parsedItem: { doc?: Stmt[] }, defaultDir = DEFAULT_NESTED_DOC_DIR) => {
let dir = defaultDir; let dir = defaultDir;
if (parsedItem.doc) { if (parsedItem.doc) {
for (const parsedItemDoc of parsedItem.doc) { for (const parsedItemDoc of parsedItem.doc) {
@@ -131,7 +141,11 @@ const getDir = (parsedItem, defaultDir = DEFAULT_NESTED_DOC_DIR) => {
return dir; return dir;
}; };
function insertOrUpdateNode(nodes, nodeData, classes) { function insertOrUpdateNode(
nodes: NodeData[],
nodeData: NodeData,
classes: Map<string, StyleClass>
) {
if (!nodeData.id || nodeData.id === '</join></fork>' || nodeData.id === '</choice>') { if (!nodeData.id || nodeData.id === '</join></fork>' || nodeData.id === '</choice>') {
return; return;
} }
@@ -143,9 +157,9 @@ function insertOrUpdateNode(nodes, nodeData, classes) {
} }
nodeData.cssClasses.split(' ').forEach((cssClass) => { nodeData.cssClasses.split(' ').forEach((cssClass) => {
if (classes.get(cssClass)) { const classDef = classes.get(cssClass);
const classDef = classes.get(cssClass); if (classDef) {
nodeData.cssCompiledStyles = [...nodeData.cssCompiledStyles, ...classDef.styles]; nodeData.cssCompiledStyles = [...(nodeData.cssCompiledStyles ?? []), ...classDef.styles];
} }
}); });
} }
@@ -162,26 +176,24 @@ function insertOrUpdateNode(nodes, nodeData, classes) {
* If there aren't any or if dbInfoItem isn't defined, return an empty string. * If there aren't any or if dbInfoItem isn't defined, return an empty string.
* Else create 1 string from the list of classes found * Else create 1 string from the list of classes found
* *
* @param {undefined | null | object} dbInfoItem
* @returns {string}
*/ */
function getClassesFromDbInfo(dbInfoItem) { function getClassesFromDbInfo(dbInfoItem?: State): string {
return dbInfoItem?.classes?.join(' ') ?? ''; return dbInfoItem?.classes?.join(' ') ?? '';
} }
function getStylesFromDbInfo(dbInfoItem) { function getStylesFromDbInfo(dbInfoItem?: State): string[] {
return dbInfoItem?.styles ?? []; return dbInfoItem?.styles ?? [];
} }
export const dataFetcher = ( export const dataFetcher = (
parent, parent: StateStmt | undefined,
parsedItem, parsedItem: StateStmt,
diagramStates, diagramStates: Map<string, State>,
nodes, nodes: NodeData[],
edges, edges: Edge[],
altFlag, altFlag: boolean,
look, look: MermaidConfig['look'],
classes classes: Map<string, StyleClass>
) => { ) => {
const itemId = parsedItem.id; const itemId = parsedItem.id;
const dbState = diagramStates.get(itemId); const dbState = diagramStates.get(itemId);
@@ -213,7 +225,7 @@ export const dataFetcher = (
}); });
} }
const newNode = nodeDb.get(itemId); const newNode = nodeDb.get(itemId)!;
// Save data for description and group so that for instance a statement without description overwrites // Save data for description and group so that for instance a statement without description overwrites
// one with description @todo TODO What does this mean? If important, add a test for it // one with description @todo TODO What does this mean? If important, add a test for it
@@ -225,7 +237,7 @@ export const dataFetcher = (
newNode.shape = SHAPE_STATE_WITH_DESC; newNode.shape = SHAPE_STATE_WITH_DESC;
newNode.description.push(parsedItem.description); newNode.description.push(parsedItem.description);
} else { } else {
if (newNode.description?.length > 0) { if (newNode.description?.length && newNode.description.length > 0) {
// if there is a description already transform it to an array // if there is a description already transform it to an array
newNode.shape = SHAPE_STATE_WITH_DESC; newNode.shape = SHAPE_STATE_WITH_DESC;
if (newNode.description === itemId) { if (newNode.description === itemId) {
@@ -262,7 +274,7 @@ export const dataFetcher = (
} }
// This is what will be added to the graph // This is what will be added to the graph
const nodeData = { const nodeData: NodeData = {
labelStyle: '', labelStyle: '',
shape: newNode.shape, shape: newNode.shape,
label: newNode.description, label: newNode.description,
@@ -294,19 +306,19 @@ export const dataFetcher = (
if (parsedItem.note) { if (parsedItem.note) {
// Todo: set random id // Todo: set random id
const noteData = { const noteData: NodeData = {
labelStyle: '', labelStyle: '',
shape: SHAPE_NOTE, shape: SHAPE_NOTE,
label: parsedItem.note.text, label: parsedItem.note.text,
cssClasses: CSS_DIAGRAM_NOTE, cssClasses: CSS_DIAGRAM_NOTE,
// useHtmlLabels: false, // useHtmlLabels: false,
cssStyles: [], cssStyles: [],
cssCompilesStyles: [], cssCompiledStyles: [],
id: itemId + NOTE_ID + '-' + graphItemCount, id: itemId + NOTE_ID + '-' + graphItemCount,
domId: stateDomId(itemId, graphItemCount, NOTE), domId: stateDomId(itemId, graphItemCount, NOTE),
type: newNode.type, type: newNode.type,
isGroup: newNode.type === 'group', isGroup: newNode.type === 'group',
padding: getConfig().flowchart.padding, padding: getConfig().flowchart!.padding,
look, look,
position: parsedItem.note.position, position: parsedItem.note.position,
}; };
@@ -343,7 +355,7 @@ export const dataFetcher = (
let from = itemId; let from = itemId;
let to = noteData.id; let to = noteData.id;
if (parsedItem.note.position === 'left of') { if (parsedItem.note.position === 'left_of') {
from = noteData.id; from = noteData.id;
to = itemId; to = itemId;
} }

View File

@@ -23,40 +23,185 @@ import {
STMT_STATE, STMT_STATE,
STMT_STYLEDEF, STMT_STYLEDEF,
} from './stateCommon.js'; } from './stateCommon.js';
import type { MermaidConfig } from '../../config.type.js';
const START_NODE = '[*]'; const START_NODE = '[*]';
const START_TYPE = 'start'; const START_TYPE = 'start';
const END_NODE = START_NODE; const END_NODE = START_NODE;
const END_TYPE = 'end'; const END_TYPE = 'end';
const COLOR_KEYWORD = 'color'; const COLOR_KEYWORD = 'color';
const FILL_KEYWORD = 'fill'; const FILL_KEYWORD = 'fill';
const BG_FILL = 'bgFill'; const BG_FILL = 'bgFill';
const STYLECLASS_SEP = ','; const STYLECLASS_SEP = ',';
interface BaseStmt {
stmt: 'applyClass' | 'classDef' | 'dir' | 'relation' | 'state' | 'style' | 'root' | 'default';
}
interface ApplyClassStmt extends BaseStmt {
stmt: 'applyClass';
id: string;
styleClass: string;
}
interface ClassDefStmt extends BaseStmt {
stmt: 'classDef';
id: string;
classes: string;
}
interface DirectionStmt extends BaseStmt {
stmt: 'dir';
value: 'TB' | 'BT' | 'RL' | 'LR';
}
interface RelationStmt extends BaseStmt {
stmt: 'relation';
state1: StateStmt;
state2: StateStmt;
description?: string;
}
export interface StateStmt extends BaseStmt {
stmt: 'state' | 'default';
id: string;
type: 'default' | 'fork' | 'join' | 'choice' | 'divider';
description?: string;
doc?: Stmt[];
note?: Note;
start?: boolean;
}
interface StyleStmt extends BaseStmt {
stmt: 'style';
id: string;
styleClass: string;
}
export interface RootStmt {
id: 'root';
stmt: 'root';
doc?: Stmt[];
}
interface Note {
position: 'left_of' | 'right_of';
text: string;
}
export type Stmt =
| ApplyClassStmt
| ClassDefStmt
| DirectionStmt
| RelationStmt
| StateStmt
| StyleStmt
| RootStmt;
export interface State {
id: string;
descriptions: string[];
type: string;
doc: Stmt[] | null;
note: { position?: string; text: string } | null;
classes: string[];
styles: string[];
textStyles: string[];
}
interface DiagramEdge {
id1: string;
id2: string;
relationTitle?: string;
}
interface Document {
relations: DiagramEdge[];
states: Map<string, State>;
documents: Record<string, Document>;
}
export interface StyleClass {
id: string;
styles: string[];
textStyles: string[];
}
export interface NodeData {
labelStyle?: string;
shape: string;
label?: string | string[];
cssClasses: string;
cssCompiledStyles?: string[];
cssStyles: string[];
id: string;
dir?: string;
domId?: string;
type?: string;
isGroup?: boolean;
padding?: number;
rx?: number;
ry?: number;
look?: MermaidConfig['look'];
parentId?: string;
centerLabel?: boolean;
position?: string;
description?: string | string[];
}
export interface Edge {
id: string;
start: string;
end: string;
arrowhead: string;
arrowTypeEnd: string;
style: string;
labelStyle: string;
label?: string;
arrowheadStyle: string;
labelpos: string;
labelType: string;
thickness: string;
classes: string;
look: MermaidConfig['look'];
}
/** /**
* Returns a new list of classes. * Returns a new list of classes.
* In the future, this can be replaced with a class common to all diagrams. * In the future, this can be replaced with a class common to all diagrams.
* ClassDef information = { id: id, styles: [], textStyles: [] } * ClassDef information = \{ id: id, styles: [], textStyles: [] \}
*
* @returns {Map<string, any>}
*/ */
function newClassesList() { function newClassesList(): Map<string, StyleClass> {
return new Map(); return new Map<string, StyleClass>();
} }
const newDoc = () => { const newDoc = (): Document => {
return { return {
/** @type {{ id1: string, id2: string, relationTitle: string }[]} */
relations: [], relations: [],
states: new Map(), states: new Map(),
documents: {}, documents: {},
}; };
}; };
const clone = (o) => JSON.parse(JSON.stringify(o)); const clone = (o: unknown) => JSON.parse(JSON.stringify(o));
export class StateDB { export class StateDB {
private nodes: NodeData[] = [];
private edges: Edge[] = [];
private direction: string = DEFAULT_DIAGRAM_DIRECTION;
private rootDoc: Stmt[] = [];
private classes: Map<string, StyleClass> = newClassesList();
private documents: { root: Document } = { root: newDoc() };
private currentDocument: Document = this.documents.root;
private startEndCount = 0;
private dividerCnt = 0;
static relationType = {
AGGREGATION: 0,
EXTENSION: 1,
COMPOSITION: 2,
DEPENDENCY: 3,
} as const;
constructor() { constructor() {
this.clear(); this.clear();
@@ -67,132 +212,68 @@ export class StateDB {
this.trimColon = this.trimColon.bind(this); this.trimColon = this.trimColon.bind(this);
} }
/** setRootDoc(o: Stmt[]) {
* @private
* @type {Array}
*/
nodes = [];
/**
* @private
* @type {Array}
*/
edges = [];
/**
* @private
* @type {string}
*/
direction = DEFAULT_DIAGRAM_DIRECTION;
/**
* @private
* @type {Array}
*/
rootDoc = [];
/**
* @private
* @type {Map<string, any>}
*/
classes = newClassesList(); // style classes defined by a classDef
/**
* @private
* @type {Object}
*/
documents = {
root: newDoc(),
};
/**
* @private
* @type {Object}
*/
currentDocument = this.documents.root;
/**
* @private
* @type {number}
*/
startEndCount = 0;
/**
* @private
* @type {number}
*/
dividerCnt = 0;
static relationType = {
AGGREGATION: 0,
EXTENSION: 1,
COMPOSITION: 2,
DEPENDENCY: 3,
};
setRootDoc(o) {
log.info('Setting root doc', o); log.info('Setting root doc', o);
// rootDoc = { id: 'root', doc: o };
this.rootDoc = o; this.rootDoc = o;
this.extract(o); this.extract(o);
} }
getRootDoc() { docTranslator(parent: RootStmt | StateStmt, node: Stmt, first: boolean) {
return this.rootDoc;
}
/**
* @private
* @param {Object} parent
* @param {Object} node
* @param {boolean} first
*/
docTranslator(parent, node, first) {
if (node.stmt === STMT_RELATION) { if (node.stmt === STMT_RELATION) {
this.docTranslator(parent, node.state1, true); this.docTranslator(parent, node.state1, true);
this.docTranslator(parent, node.state2, false); this.docTranslator(parent, node.state2, false);
return;
}
if (node.stmt !== STMT_STATE) {
return;
}
if (node.id === '[*]') {
node.id = parent.id + (first ? '_start' : '_end');
node.start = first;
} else { } else {
if (node.stmt === STMT_STATE) { node.id = node.id.trim();
if (node.id === '[*]') { }
node.id = first ? parent.id + '_start' : parent.id + '_end';
node.start = first;
} else {
// This is just a plain state, not a start or end
node.id = node.id.trim();
}
}
if (node.doc) { if (!node.doc) {
const doc = []; return;
// Check for concurrency }
let currentDoc = [];
let i;
for (i = 0; i < node.doc.length; i++) {
if (node.doc[i].type === DIVIDER_TYPE) {
const newNode = clone(node.doc[i]);
newNode.doc = clone(currentDoc);
doc.push(newNode);
currentDoc = [];
} else {
currentDoc.push(node.doc[i]);
}
}
// If any divider was encountered const doc = [];
if (doc.length > 0 && currentDoc.length > 0) { let currentDoc = [];
const newNode = { for (const docItem of node.doc) {
stmt: STMT_STATE, if ('type' in docItem && docItem.type === DIVIDER_TYPE) {
id: generateId(), const newNode = clone(docItem);
type: 'divider', newNode.doc = clone(currentDoc);
doc: clone(currentDoc), doc.push(newNode);
}; currentDoc = [];
doc.push(clone(newNode)); } else {
node.doc = doc; currentDoc.push(docItem);
}
node.doc.forEach((docNode) => this.docTranslator(node, docNode, true));
} }
} }
if (doc.length > 0 && currentDoc.length > 0) {
const newNode = {
stmt: STMT_STATE,
id: generateId(),
type: 'divider',
doc: clone(currentDoc),
};
doc.push(clone(newNode));
node.doc = doc;
}
node.doc.forEach((docNode) => this.docTranslator(node, docNode, true));
} }
getRootDocV2() { getRootDocV2() {
this.docTranslator({ id: 'root' }, { id: 'root', doc: this.rootDoc }, true); this.docTranslator(
{ id: 'root', stmt: 'root' },
{ id: 'root', stmt: 'root', doc: this.rootDoc },
true
);
return { id: 'root', doc: this.rootDoc }; return { id: 'root', doc: this.rootDoc };
// Here
} }
/** /**
@@ -203,40 +284,16 @@ export class StateDB {
* refer to the fork as a whole (document). * refer to the fork as a whole (document).
* See the parser grammar: the definition of a document is a document then a 'line', where a line can be a statement. * See the parser grammar: the definition of a document is a document then a 'line', where a line can be a statement.
* This will push the statement into the list of statements for the current document. * This will push the statement into the list of statements for the current document.
* @private
* @param _doc
*/ */
extract(_doc) { extract(_statements: Stmt[] | { doc: Stmt[] }) {
// const res = { states: [], relations: [] }; // console.trace('Statements', _statements);
let doc;
if (_doc.doc) {
doc = _doc.doc;
} else {
doc = _doc;
}
// let doc = root.doc;
// if (!doc) {
// doc = root;
// }
log.info(doc);
this.clear(true); this.clear(true);
const statements = Array.isArray(_statements) ? _statements : _statements.doc;
log.info('Extract initial document:', doc); statements.forEach((item) => {
log.warn('Statement', item);
doc.forEach((item) => {
log.warn('Statement', item.stmt);
switch (item.stmt) { switch (item.stmt) {
case STMT_STATE: case STMT_STATE:
this.addState( this.addState(item.id.trim(), item.type, item.doc, item.description, item.note);
item.id.trim(),
item.type,
item.doc,
item.description,
item.note,
item.classes,
item.styles,
item.textStyles
);
break; break;
case STMT_RELATION: case STMT_RELATION:
this.addRelation(item.state1, item.state2, item.description); this.addRelation(item.state1, item.state2, item.description);
@@ -255,7 +312,7 @@ export class StateDB {
this.addState(trimmedId); this.addState(trimmedId);
foundState = this.getState(trimmedId); foundState = this.getState(trimmedId);
} }
foundState.styles = styles.map((s) => s.replace(/;/g, '')?.trim()); foundState!.styles = styles.map((s) => s.replace(/;/g, '')?.trim());
}); });
} }
break; break;
@@ -272,7 +329,7 @@ export class StateDB {
resetDataFetching(); resetDataFetching();
dataFetcher( dataFetcher(
undefined, undefined,
this.getRootDocV2(), this.getRootDocV2() as StateStmt,
diagramStates, diagramStates,
this.nodes, this.nodes,
this.edges, this.edges,
@@ -282,7 +339,6 @@ export class StateDB {
); );
this.nodes.forEach((node) => { this.nodes.forEach((node) => {
if (Array.isArray(node.label)) { if (Array.isArray(node.label)) {
// add the rest as description
node.description = node.label.slice(1); node.description = node.label.slice(1);
if (node.isGroup && node.description.length > 0) { if (node.isGroup && node.description.length > 0) {
throw new Error( throw new Error(
@@ -300,27 +356,22 @@ export class StateDB {
/** /**
* Function called by parser when a node definition has been found. * Function called by parser when a node definition has been found.
* *
* @param {null | string} id * @param descr - description for the state. Can be a string or a list or strings
* @param {null | string} type * @param classes - class styles to apply to this state. Can be a string (1 style) or an array of styles. If it's just 1 class, convert it to an array of that 1 class.
* @param {null | string} doc * @param styles - styles to apply to this state. Can be a string (1 style) or an array of styles. If it's just 1 style, convert it to an array of that 1 style.
* @param {null | string | string[]} descr - description for the state. Can be a string or a list or strings * @param textStyles - text styles to apply to this state. Can be a string (1 text test) or an array of text styles. If it's just 1 text style, convert it to an array of that 1 text style.
* @param {null | string} note
* @param {null | string | string[]} classes - class styles to apply to this state. Can be a string (1 style) or an array of styles. If it's just 1 class, convert it to an array of that 1 class.
* @param {null | string | string[]} styles - styles to apply to this state. Can be a string (1 style) or an array of styles. If it's just 1 style, convert it to an array of that 1 style.
* @param {null | string | string[]} textStyles - text styles to apply to this state. Can be a string (1 text test) or an array of text styles. If it's just 1 text style, convert it to an array of that 1 text style.
*/ */
addState( addState(
id, id: string,
type = DEFAULT_STATE_TYPE, type: string = DEFAULT_STATE_TYPE,
doc = null, doc: Stmt[] | null = null,
descr = null, descr: string | string[] | null = null,
note = null, note: { position?: string; text: string } | null = null,
classes = null, classes: string | string[] | null = null,
styles = null, styles: string | string[] | null = null,
textStyles = null textStyles: string | string[] | null = null
) { ) {
const trimmedId = id?.trim(); const trimmedId = id?.trim();
// add the state if needed
if (!this.currentDocument.states.has(trimmedId)) { if (!this.currentDocument.states.has(trimmedId)) {
log.info('Adding state ', trimmedId, descr); log.info('Adding state ', trimmedId, descr);
this.currentDocument.states.set(trimmedId, { this.currentDocument.states.set(trimmedId, {
@@ -334,11 +385,15 @@ export class StateDB {
textStyles: [], textStyles: [],
}); });
} else { } else {
if (!this.currentDocument.states.get(trimmedId).doc) { const state = this.currentDocument.states.get(trimmedId);
this.currentDocument.states.get(trimmedId).doc = doc; if (!state) {
throw new Error(`State not found: ${trimmedId}`);
} }
if (!this.currentDocument.states.get(trimmedId).type) { if (!state.doc) {
this.currentDocument.states.get(trimmedId).type = type; state.doc = doc;
}
if (!state.type) {
state.type = type;
} }
} }
@@ -349,12 +404,15 @@ export class StateDB {
} }
if (typeof descr === 'object') { if (typeof descr === 'object') {
descr.forEach((des) => this.addDescription(trimmedId, des.trim())); descr.forEach((des: string) => this.addDescription(trimmedId, des.trim()));
} }
} }
if (note) { if (note) {
const doc2 = this.currentDocument.states.get(trimmedId); const doc2 = this.currentDocument.states.get(trimmedId);
if (!doc2) {
throw new Error(`State not found: ${trimmedId}`);
}
doc2.note = note; doc2.note = note;
doc2.note.text = common.sanitizeText(doc2.note.text, getConfig()); doc2.note.text = common.sanitizeText(doc2.note.text, getConfig());
} }
@@ -362,23 +420,23 @@ export class StateDB {
if (classes) { if (classes) {
log.info('Setting state classes', trimmedId, classes); log.info('Setting state classes', trimmedId, classes);
const classesList = typeof classes === 'string' ? [classes] : classes; const classesList = typeof classes === 'string' ? [classes] : classes;
classesList.forEach((cssClass) => this.setCssClass(trimmedId, cssClass.trim())); classesList.forEach((cssClass: string) => this.setCssClass(trimmedId, cssClass.trim()));
} }
if (styles) { if (styles) {
log.info('Setting state styles', trimmedId, styles); log.info('Setting state styles', trimmedId, styles);
const stylesList = typeof styles === 'string' ? [styles] : styles; const stylesList = typeof styles === 'string' ? [styles] : styles;
stylesList.forEach((style) => this.setStyle(trimmedId, style.trim())); stylesList.forEach((style: string) => this.setStyle(trimmedId, style.trim()));
} }
if (textStyles) { if (textStyles) {
log.info('Setting state styles', trimmedId, styles); log.info('Setting state styles', trimmedId, styles);
const textStylesList = typeof textStyles === 'string' ? [textStyles] : textStyles; const textStylesList = typeof textStyles === 'string' ? [textStyles] : textStyles;
textStylesList.forEach((textStyle) => this.setTextStyle(trimmedId, textStyle.trim())); textStylesList.forEach((textStyle: string) => this.setTextStyle(trimmedId, textStyle.trim()));
} }
} }
clear(saveCommon) { clear(saveCommon?: boolean) {
this.nodes = []; this.nodes = [];
this.edges = []; this.edges = [];
this.documents = { this.documents = {
@@ -394,15 +452,18 @@ export class StateDB {
} }
} }
getState(id) { getState(id: string) {
return this.currentDocument.states.get(id); return this.currentDocument.states.get(id);
} }
getStates() { getStates() {
return this.currentDocument.states; return this.currentDocument.states;
} }
logDocuments() { logDocuments() {
log.info('Documents = ', this.documents); log.info('Documents = ', this.documents);
} }
getRelations() { getRelations() {
return this.currentDocument.relations; return this.currentDocument.relations;
} }
@@ -411,10 +472,6 @@ export class StateDB {
* If the id is a start node ( [*] ), then return a new id constructed from * If the id is a start node ( [*] ), then return a new id constructed from
* the start node name and the current start node count. * the start node name and the current start node count.
* else return the given id * else return the given id
*
* @param {string} id
* @returns {string} - the id (original or constructed)
* @private
*/ */
startIdIfNeeded(id = '') { startIdIfNeeded(id = '') {
let fixedId = id; let fixedId = id;
@@ -428,11 +485,6 @@ export class StateDB {
/** /**
* If the id is a start node ( [*] ), then return the start type ('start') * If the id is a start node ( [*] ), then return the start type ('start')
* else return the given type * else return the given type
*
* @param {string} id
* @param {string} type
* @returns {string} - the type that should be used
* @private
*/ */
startTypeIfNeeded(id = '', type = DEFAULT_STATE_TYPE) { startTypeIfNeeded(id = '', type = DEFAULT_STATE_TYPE) {
return id === START_NODE ? START_TYPE : type; return id === START_NODE ? START_TYPE : type;
@@ -442,10 +494,6 @@ export class StateDB {
* If the id is an end node ( [*] ), then return a new id constructed from * If the id is an end node ( [*] ), then return a new id constructed from
* the end node name and the current start_end node count. * the end node name and the current start_end node count.
* else return the given id * else return the given id
*
* @param {string} id
* @returns {string} - the id (original or constructed)
* @private
*/ */
endIdIfNeeded(id = '') { endIdIfNeeded(id = '') {
let fixedId = id; let fixedId = id;
@@ -460,47 +508,19 @@ export class StateDB {
* If the id is an end node ( [*] ), then return the end type * If the id is an end node ( [*] ), then return the end type
* else return the given type * else return the given type
* *
* @param {string} id
* @param {string} type
* @returns {string} - the type that should be used
* @private
*/ */
endTypeIfNeeded(id = '', type = DEFAULT_STATE_TYPE) { endTypeIfNeeded(id = '', type = DEFAULT_STATE_TYPE) {
return id === END_NODE ? END_TYPE : type; return id === END_NODE ? END_TYPE : type;
} }
/** addRelationObjs(item1: StateStmt, item2: StateStmt, relationTitle = '') {
* const id1 = this.startIdIfNeeded(item1.id.trim());
* @param item1 const type1 = this.startTypeIfNeeded(item1.id.trim(), item1.type);
* @param item2 const id2 = this.startIdIfNeeded(item2.id.trim());
* @param relationTitle const type2 = this.startTypeIfNeeded(item2.id.trim(), item2.type);
*/
addRelationObjs(item1, item2, relationTitle) {
let id1 = this.startIdIfNeeded(item1.id.trim());
let type1 = this.startTypeIfNeeded(item1.id.trim(), item1.type);
let id2 = this.startIdIfNeeded(item2.id.trim());
let type2 = this.startTypeIfNeeded(item2.id.trim(), item2.type);
this.addState( this.addState(id1, type1, item1.doc, item1.description, item1.note);
id1, this.addState(id2, type2, item2.doc, item2.description, item2.note);
type1,
item1.doc,
item1.description,
item1.note,
item1.classes,
item1.styles,
item1.textStyles
);
this.addState(
id2,
type2,
item2.doc,
item2.description,
item2.note,
item2.classes,
item2.styles,
item2.textStyles
);
this.currentDocument.relations.push({ this.currentDocument.relations.push({
id1, id1,
@@ -511,15 +531,11 @@ export class StateDB {
/** /**
* Add a relation between two items. The items may be full objects or just the string id of a state. * Add a relation between two items. The items may be full objects or just the string id of a state.
*
* @param {string | object} item1
* @param {string | object} item2
* @param {string} title
*/ */
addRelation(item1, item2, title) { addRelation(item1: string | StateStmt, item2: string | StateStmt, title?: string) {
if (typeof item1 === 'object') { if (typeof item1 === 'object' && typeof item2 === 'object') {
this.addRelationObjs(item1, item2, title); this.addRelationObjs(item1, item2, title);
} else { } else if (typeof item1 === 'string' && typeof item2 === 'string') {
const id1 = this.startIdIfNeeded(item1.trim()); const id1 = this.startIdIfNeeded(item1.trim());
const type1 = this.startTypeIfNeeded(item1); const type1 = this.startTypeIfNeeded(item1);
const id2 = this.endIdIfNeeded(item2.trim()); const id2 = this.endIdIfNeeded(item2.trim());
@@ -530,19 +546,19 @@ export class StateDB {
this.currentDocument.relations.push({ this.currentDocument.relations.push({
id1, id1,
id2, id2,
title: common.sanitizeText(title, getConfig()), relationTitle: title ? common.sanitizeText(title, getConfig()) : undefined,
}); });
} }
} }
addDescription(id, descr) { addDescription(id: string, descr: string) {
const theState = this.currentDocument.states.get(id); const theState = this.currentDocument.states.get(id);
const _descr = descr.startsWith(':') ? descr.replace(':', '').trim() : descr; const _descr = descr.startsWith(':') ? descr.replace(':', '').trim() : descr;
theState.descriptions.push(common.sanitizeText(_descr, getConfig())); theState!.descriptions.push(common.sanitizeText(_descr, getConfig()));
} }
cleanupLabel(label) { cleanupLabel(label: string) {
if (label.substring(0, 1) === ':') { if (label.startsWith(':')) {
return label.substr(2).trim(); return label.substr(2).trim();
} else { } else {
return label.trim(); return label.trim();
@@ -558,21 +574,18 @@ export class StateDB {
* Called when the parser comes across a (style) class definition * Called when the parser comes across a (style) class definition
* @example classDef my-style fill:#f96; * @example classDef my-style fill:#f96;
* *
* @param {string} id - the id of this (style) class * @param id - the id of this (style) class
* @param {string | null} styleAttributes - the string with 1 or more style attributes (each separated by a comma) * @param styleAttributes - the string with 1 or more style attributes (each separated by a comma)
*/ */
addStyleClass(id, styleAttributes = '') { addStyleClass(id: string, styleAttributes = '') {
// create a new style class object with this id // create a new style class object with this id
if (!this.classes.has(id)) { if (!this.classes.has(id)) {
this.classes.set(id, { id: id, styles: [], textStyles: [] }); // This is a classDef this.classes.set(id, { id, styles: [], textStyles: [] });
} }
const foundClass = this.classes.get(id); const foundClass = this.classes.get(id);
if (styleAttributes !== undefined && styleAttributes !== null) { if (styleAttributes !== undefined && styleAttributes !== null && foundClass) {
styleAttributes.split(STYLECLASS_SEP).forEach((attrib) => { styleAttributes.split(STYLECLASS_SEP).forEach((attrib: string) => {
// remove any trailing ;
const fixedAttrib = attrib.replace(/([^;]*);/, '$1').trim(); const fixedAttrib = attrib.replace(/([^;]*);/, '$1').trim();
// replace some style keywords
if (RegExp(COLOR_KEYWORD).exec(attrib)) { if (RegExp(COLOR_KEYWORD).exec(attrib)) {
const newStyle1 = fixedAttrib.replace(FILL_KEYWORD, BG_FILL); const newStyle1 = fixedAttrib.replace(FILL_KEYWORD, BG_FILL);
const newStyle2 = newStyle1.replace(COLOR_KEYWORD, FILL_KEYWORD); const newStyle2 = newStyle1.replace(COLOR_KEYWORD, FILL_KEYWORD);
@@ -583,10 +596,6 @@ export class StateDB {
} }
} }
/**
* Return all of the style classes
* @returns {{} | any | classes}
*/
getClasses() { getClasses() {
return this.classes; return this.classes;
} }
@@ -596,18 +605,18 @@ export class StateDB {
* If the state isn't already in the list of known states, add it. * If the state isn't already in the list of known states, add it.
* Might be called by parser when a style class or CSS class should be applied to a state * Might be called by parser when a style class or CSS class should be applied to a state
* *
* @param {string | string[]} itemIds The id or a list of ids of the item(s) to apply the css class to * @param itemIds - The id or a list of ids of the item(s) to apply the css class to
* @param {string} cssClassName CSS class name * @param cssClassName - CSS class name
*/ */
setCssClass(itemIds, cssClassName) { setCssClass(itemIds: string, cssClassName: string) {
itemIds.split(',').forEach((id) => { itemIds.split(',').forEach((id: string) => {
let foundState = this.getState(id); let foundState = this.getState(id);
if (foundState === undefined) { if (foundState === undefined) {
const trimmedId = id.trim(); const trimmedId = id.trim();
this.addState(trimmedId); this.addState(trimmedId);
foundState = this.getState(trimmedId); foundState = this.getState(trimmedId);
} }
foundState.classes.push(cssClassName); foundState!.classes.push(cssClassName);
}); });
} }
@@ -618,10 +627,10 @@ export class StateDB {
* stateId is the id of a state * stateId is the id of a state
* the rest of the string is the styleText (all of the attributes to be applied to the state) * the rest of the string is the styleText (all of the attributes to be applied to the state)
* *
* @param itemId The id of item to apply the style to * @param itemId - The id of item to apply the style to
* @param styleText - the text of the attributes for the style * @param styleText - the text of the attributes for the style
*/ */
setStyle(itemId, styleText) { setStyle(itemId: string, styleText: string) {
const item = this.getState(itemId); const item = this.getState(itemId);
if (item !== undefined) { if (item !== undefined) {
item.styles.push(styleText); item.styles.push(styleText);
@@ -631,10 +640,10 @@ export class StateDB {
/** /**
* Add a text style to a state with the given id * Add a text style to a state with the given id
* *
* @param itemId The id of item to apply the css class to * @param itemId - The id of item to apply the css class to
* @param cssClassName CSS class name * @param cssClassName - CSS class name
*/ */
setTextStyle(itemId, cssClassName) { setTextStyle(itemId: string, cssClassName: string) {
const item = this.getState(itemId); const item = this.getState(itemId);
if (item !== undefined) { if (item !== undefined) {
item.textStyles.push(cssClassName); item.textStyles.push(cssClassName);
@@ -644,12 +653,13 @@ export class StateDB {
getDirection() { getDirection() {
return this.direction; return this.direction;
} }
setDirection(dir) {
setDirection(dir: string) {
this.direction = dir; this.direction = dir;
} }
trimColon(str) { trimColon(str: string) {
return str && str[0] === ':' ? str.substr(1).trim() : str.trim(); return str.startsWith(':') ? str.substr(1).trim() : str.trim();
} }
getData() { getData() {
@@ -666,6 +676,7 @@ export class StateDB {
getConfig() { getConfig() {
return getConfig().state; return getConfig().state;
} }
getAccTitle = getAccTitle; getAccTitle = getAccTitle;
setAccTitle = setAccTitle; setAccTitle = setAccTitle;
getAccDescription = getAccDescription; getAccDescription = getAccDescription;