mermaid/packages/mermaid/src/diagrams/block/blockDB.ts

312 lines
8.1 KiB
TypeScript
Raw Normal View History

2023-07-10 22:33:11 +02:00
import type { DiagramDB } from '../../diagram-api/types.js';
2024-01-30 16:05:16 +01:00
import type { BlockConfig, BlockType, Block, ClassDef } from './blockTypes.js';
import * as configApi from '../../config.js';
import { clear as commonClear } from '../common/commonDb.js';
import { log } from '../../logger.js';
import clone from 'lodash-es/clone.js';
// Initialize the node database for simple lookups
2023-09-01 15:33:38 +02:00
let blockDatabase: Record<string, Block> = {};
2024-01-04 14:15:30 +01:00
let edgeList: Block[] = [];
let edgeCount: Record<string, number> = {};
2024-01-04 16:05:19 +01:00
const COLOR_KEYWORD = 'color';
const FILL_KEYWORD = 'fill';
const BG_FILL = 'bgFill';
const STYLECLASS_SEP = ',';
let classes = {} as Record<string, ClassDef>;
/**
* Called when the parser comes across a (style) class definition
* @example classDef my-style fill:#f96;
*
2024-01-18 15:44:16 +01:00
* @param id - the id of this (style) class
* @param styleAttributes - the string with 1 or more style attributes (each separated by a comma)
*/
export const addStyleClass = function (id: string, styleAttributes = '') {
// create a new style class object with this id
if (classes[id] === undefined) {
classes[id] = { id: id, styles: [], textStyles: [] }; // This is a classDef
}
const foundClass = classes[id];
if (styleAttributes !== undefined && styleAttributes !== null) {
styleAttributes.split(STYLECLASS_SEP).forEach((attrib) => {
// remove any trailing ;
const fixedAttrib = attrib.replace(/([^;]*);/, '$1').trim();
// replace some style keywords
if (attrib.match(COLOR_KEYWORD)) {
const newStyle1 = fixedAttrib.replace(FILL_KEYWORD, BG_FILL);
const newStyle2 = newStyle1.replace(COLOR_KEYWORD, FILL_KEYWORD);
foundClass.textStyles.push(newStyle2);
}
foundClass.styles.push(fixedAttrib);
});
}
};
/**
* Called when the parser comes across a style definition
* @example style my-block-id fill:#f96;
*
* @param id - the id of the block to style
2024-01-18 15:44:16 +01:00
* @param styles - the string with 1 or more style attributes (each separated by a comma)
*/
export const addStyle2Node = function (id: string, styles = '') {
2024-01-18 15:44:16 +01:00
const foundBlock = blockDatabase[id];
if (styles !== undefined && styles !== null) {
foundBlock.styles = styles.split(STYLECLASS_SEP);
}
};
/**
* Add a CSS/style class to the block with the given id.
* If the block isn't already in the list of known blocks, add it.
* Might be called by parser when a CSS/style class should be applied to a block
*
2024-01-18 15:44:16 +01:00
* @param itemIds - The id or a list of ids of the item(s) to apply the css class to
* @param cssClassName - CSS class name
*/
export const setCssClass = function (itemIds: string, cssClassName: string) {
itemIds.split(',').forEach(function (id: string) {
let foundBlock = blockDatabase[id];
if (foundBlock === undefined) {
const trimmedId = id.trim();
blockDatabase[trimmedId] = { id: trimmedId, type: 'na', children: [] } as Block;
foundBlock = blockDatabase[trimmedId];
}
if (!foundBlock.classes) {
foundBlock.classes = [];
}
foundBlock.classes.push(cssClassName);
});
};
2024-01-30 14:37:06 +01:00
const populateBlockDatabase = (_blockList: Block[] | Block[][], parent: Block): void => {
2024-01-04 16:05:19 +01:00
const blockList = _blockList.flat();
const children = [];
2023-09-01 15:33:38 +02:00
for (const block of blockList) {
if (block.type === 'classDef') {
addStyleClass(block.id, block.css);
continue;
}
if (block.type === 'applyClass') {
setCssClass(block.id, block?.styleClass || '');
continue;
}
if (block.type === 'applyStyles') {
if (block?.stylesStr) {
addStyle2Node(block.id, block?.stylesStr);
}
2024-01-18 15:44:16 +01:00
continue;
}
2023-09-01 15:33:38 +02:00
if (block.type === 'column-setting') {
parent.columns = block.columns || -1;
2024-01-04 14:15:30 +01:00
} else if (block.type === 'edge') {
if (edgeCount[block.id]) {
edgeCount[block.id]++;
} else {
edgeCount[block.id] = 1;
}
block.id = edgeCount[block.id] + '-' + block.id;
edgeList.push(block);
2023-09-01 15:33:38 +02:00
} else {
if (!block.label) {
2023-10-03 12:56:47 +02:00
if (block.type === 'composite') {
2023-10-03 14:19:08 +02:00
block.label = '';
2024-01-18 14:28:14 +01:00
// log.debug('abc89 composite', block);
2023-10-03 12:56:47 +02:00
} else {
block.label = block.id;
}
2023-09-01 15:33:38 +02:00
}
2024-01-04 14:15:30 +01:00
const newBlock = !blockDatabase[block.id];
if (newBlock) {
blockDatabase[block.id] = block;
} else {
// Add newer relevant data to aggregated node
if (block.type !== 'na') {
blockDatabase[block.id].type = block.type;
}
if (block.label !== block.id) {
blockDatabase[block.id].label = block.label;
}
}
2023-09-01 15:33:38 +02:00
if (block.children) {
populateBlockDatabase(block.children, block);
}
if (block.type === 'space') {
2024-01-18 14:28:14 +01:00
// log.debug('abc95 space', block);
const w = block.width || 1;
for (let j = 0; j < w; j++) {
const newBlock = clone(block);
newBlock.id = newBlock.id + '-' + j;
blockDatabase[newBlock.id] = newBlock;
children.push(newBlock);
}
} else if (newBlock) {
children.push(block);
}
2023-09-01 15:33:38 +02:00
}
}
parent.children = children;
2023-09-01 15:33:38 +02:00
};
2023-07-10 22:33:11 +02:00
let blocks: Block[] = [];
2023-09-01 15:33:38 +02:00
let rootBlock = { id: 'root', type: 'composite', children: [], columns: -1 } as Block;
const clear = (): void => {
2024-01-18 14:28:14 +01:00
log.debug('Clear called');
commonClear();
2023-09-01 15:33:38 +02:00
rootBlock = { id: 'root', type: 'composite', children: [], columns: -1 } as Block;
blockDatabase = { root: rootBlock };
blocks = [] as Block[];
classes = {} as Record<string, ClassDef>;
2024-01-04 14:15:30 +01:00
edgeList = [];
edgeCount = {};
};
export function typeStr2Type(typeStr: string) {
log.debug('typeStr2Type', typeStr);
switch (typeStr) {
case '[]':
return 'square';
case '()':
log.debug('we have a round');
return 'round';
case '(())':
return 'circle';
case '>]':
return 'rect_left_inv_arrow';
case '{}':
2023-11-20 14:00:25 +01:00
return 'diamond';
case '{{}}':
return 'hexagon';
case '([])':
return 'stadium';
case '[[]]':
return 'subroutine';
case '[()]':
return 'cylinder';
case '((()))':
return 'doublecircle';
case '[//]':
return 'lean_right';
case '[\\\\]':
return 'lean_left';
case '[/\\]':
return 'trapezoid';
case '[\\/]':
return 'inv_trapezoid';
case '<[]>':
return 'block_arrow';
default:
2024-01-04 14:15:30 +01:00
return 'na';
}
}
export function edgeTypeStr2Type(typeStr: string): string {
log.debug('typeStr2Type', typeStr);
switch (typeStr) {
case '==':
return 'thick';
default:
return 'normal';
}
}
2024-01-04 14:15:30 +01:00
export function edgeStrToEdgeData(typeStr: string): string {
switch (typeStr.trim()) {
case '--x':
return 'arrow_cross';
case '--o':
return 'arrow_circle';
default:
return 'arrow_point';
2023-07-11 01:51:10 +02:00
}
}
2023-09-01 14:06:13 +02:00
let cnt = 0;
export const generateId = () => {
cnt++;
return 'id-' + Math.random().toString(36).substr(2, 12) + '-' + cnt;
};
const setHierarchy = (block: Block[]): void => {
rootBlock.children = block;
2023-09-01 15:33:38 +02:00
populateBlockDatabase(block, rootBlock);
blocks = rootBlock.children;
2023-09-01 14:06:13 +02:00
};
const getColumns = (blockId: string): number => {
const block = blockDatabase[blockId];
2023-08-08 15:56:02 +02:00
if (!block) {
return -1;
}
if (block.columns) {
return block.columns;
}
if (!block.children) {
return -1;
}
return block.children.length;
2023-08-08 15:56:02 +02:00
};
2024-01-04 16:05:19 +01:00
/**
* Returns all the blocks as a flat array
* @returns
*/
const getBlocksFlat = () => {
2024-01-30 14:37:06 +01:00
return [...Object.values(blockDatabase)];
2024-01-04 16:05:19 +01:00
};
/**
2024-01-18 15:44:16 +01:00
* Returns the the hierarchy of blocks
2024-01-04 16:05:19 +01:00
* @returns
*/
const getBlocks = () => {
2023-09-01 14:06:13 +02:00
return blocks || [];
};
2024-01-30 14:37:06 +01:00
const getEdges = () => {
2024-01-04 14:15:30 +01:00
return edgeList;
};
const getBlock = (id: string) => {
return blockDatabase[id];
};
const setBlock = (block: Block) => {
blockDatabase[block.id] = block;
};
2023-07-11 01:51:10 +02:00
const getLogger = () => console;
2023-07-11 01:51:10 +02:00
/**
* Return all of the style classes
*/
export const getClasses = function () {
return classes;
};
2023-07-10 22:33:11 +02:00
const db = {
2023-07-07 12:58:30 +02:00
getConfig: () => configApi.getConfig().block,
typeStr2Type: typeStr2Type,
2024-01-04 14:15:30 +01:00
edgeTypeStr2Type: edgeTypeStr2Type,
edgeStrToEdgeData,
2024-01-30 16:05:16 +01:00
getLogger,
2024-01-04 16:05:19 +01:00
getBlocksFlat,
2023-07-11 01:51:10 +02:00
getBlocks,
2024-01-04 14:15:30 +01:00
getEdges,
2023-09-01 14:06:13 +02:00
setHierarchy,
getBlock,
setBlock,
2023-08-08 15:56:02 +02:00
getColumns,
getClasses,
clear,
2023-09-01 15:33:38 +02:00
generateId,
} as const;
2023-07-07 13:12:18 +02:00
export type BlockDB = typeof db & DiagramDB;
2023-07-10 22:33:11 +02:00
export default db;