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';
|
2023-07-05 12:01:37 +02:00
|
|
|
import * as configApi from '../../config.js';
|
2024-01-29 16:22:48 +01:00
|
|
|
import { clear as commonClear } from '../common/commonDb.js';
|
2023-08-28 12:51:49 +02:00
|
|
|
import { log } from '../../logger.js';
|
2023-10-20 12:30:25 +02:00
|
|
|
import clone from 'lodash-es/clone.js';
|
2023-07-05 12:01:37 +02:00
|
|
|
|
2023-08-28 12:51:49 +02:00
|
|
|
// 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
|
|
|
|
2024-01-05 15:13:15 +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)
|
2024-01-05 15:13:15 +01:00
|
|
|
*/
|
|
|
|
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);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2024-01-05 20:56:57 +01:00
|
|
|
/**
|
2024-01-29 16:22:48 +01:00
|
|
|
* Called when the parser comes across a style definition
|
|
|
|
* @example style my-block-id fill:#f96;
|
2024-01-05 20:56:57 +01:00
|
|
|
*
|
2024-01-29 16:22:48 +01:00
|
|
|
* @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)
|
2024-01-05 20:56:57 +01:00
|
|
|
*/
|
|
|
|
export const addStyle2Node = function (id: string, styles = '') {
|
2024-01-18 15:44:16 +01:00
|
|
|
const foundBlock = blockDatabase[id];
|
2024-01-05 20:56:57 +01:00
|
|
|
if (styles !== undefined && styles !== null) {
|
|
|
|
foundBlock.styles = styles.split(STYLECLASS_SEP);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2024-01-05 15:13:15 +01:00
|
|
|
/**
|
2024-01-29 16:22:48 +01:00
|
|
|
* 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-05 15:13:15 +01:00
|
|
|
*
|
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
|
2024-01-05 15:13:15 +01:00
|
|
|
*/
|
|
|
|
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();
|
2023-09-01 16:22:23 +02:00
|
|
|
const children = [];
|
2023-09-01 15:33:38 +02:00
|
|
|
for (const block of blockList) {
|
2024-01-05 15:13:15 +01:00
|
|
|
if (block.type === 'classDef') {
|
|
|
|
addStyleClass(block.id, block.css);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (block.type === 'applyClass') {
|
2024-01-05 20:56:57 +01:00
|
|
|
setCssClass(block.id, block?.styleClass || '');
|
|
|
|
continue;
|
|
|
|
}
|
2024-01-29 16:22:48 +01:00
|
|
|
if (block.type === 'applyStyles') {
|
|
|
|
if (block?.stylesStr) {
|
|
|
|
addStyle2Node(block.id, block?.stylesStr);
|
|
|
|
}
|
2024-01-18 15:44:16 +01:00
|
|
|
continue;
|
2024-01-05 15:13:15 +01:00
|
|
|
}
|
2023-09-01 15:33:38 +02:00
|
|
|
if (block.type === 'column-setting') {
|
2023-10-26 22:02:16 +02:00
|
|
|
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);
|
|
|
|
}
|
2023-10-20 12:30:25 +02:00
|
|
|
if (block.type === 'space') {
|
2024-01-18 14:28:14 +01:00
|
|
|
// log.debug('abc95 space', block);
|
2023-10-26 22:02:16 +02:00
|
|
|
const w = block.width || 1;
|
|
|
|
for (let j = 0; j < w; j++) {
|
2023-10-20 12:30:25 +02:00
|
|
|
const newBlock = clone(block);
|
|
|
|
newBlock.id = newBlock.id + '-' + j;
|
|
|
|
blockDatabase[newBlock.id] = newBlock;
|
|
|
|
children.push(newBlock);
|
|
|
|
}
|
2024-01-29 16:22:48 +01:00
|
|
|
} else if (newBlock) {
|
|
|
|
children.push(block);
|
2023-10-20 12:30:25 +02:00
|
|
|
}
|
2023-09-01 15:33:38 +02:00
|
|
|
}
|
|
|
|
}
|
2023-09-01 16:22:23 +02:00
|
|
|
parent.children = children;
|
2023-09-01 15:33:38 +02:00
|
|
|
};
|
2023-07-10 22:33:11 +02:00
|
|
|
|
2023-07-05 12:01:37 +02:00
|
|
|
let blocks: Block[] = [];
|
2023-09-01 15:33:38 +02:00
|
|
|
let rootBlock = { id: 'root', type: 'composite', children: [], columns: -1 } as Block;
|
2023-07-05 12:01:37 +02:00
|
|
|
|
|
|
|
const clear = (): void => {
|
2024-01-18 14:28:14 +01:00
|
|
|
log.debug('Clear called');
|
2023-07-05 12:01:37 +02:00
|
|
|
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[];
|
2024-01-05 15:13:15 +01:00
|
|
|
classes = {} as Record<string, ClassDef>;
|
2024-01-04 14:15:30 +01:00
|
|
|
|
|
|
|
edgeList = [];
|
|
|
|
edgeCount = {};
|
2023-07-05 12:01:37 +02:00
|
|
|
};
|
|
|
|
|
2024-01-29 16:22:48 +01:00
|
|
|
export function typeStr2Type(typeStr: string) {
|
2023-10-20 12:30:25 +02:00
|
|
|
log.debug('typeStr2Type', typeStr);
|
2023-08-28 12:51:49 +02:00
|
|
|
switch (typeStr) {
|
|
|
|
case '[]':
|
|
|
|
return 'square';
|
|
|
|
case '()':
|
2023-10-20 12:30:25 +02:00
|
|
|
log.debug('we have a round');
|
2023-08-28 12:51:49 +02:00
|
|
|
return 'round';
|
2023-10-20 12:30:25 +02:00
|
|
|
case '(())':
|
|
|
|
return 'circle';
|
|
|
|
case '>]':
|
|
|
|
return 'rect_left_inv_arrow';
|
|
|
|
case '{}':
|
2023-11-20 14:00:25 +01:00
|
|
|
return 'diamond';
|
2023-10-20 12:30:25 +02:00
|
|
|
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';
|
2023-10-26 22:01:44 +02:00
|
|
|
case '<[]>':
|
|
|
|
return 'block_arrow';
|
2023-08-28 12:51:49 +02:00
|
|
|
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-29 16:22:48 +01:00
|
|
|
|
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-08-28 12:51:49 +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 => {
|
2023-10-15 22:21:25 +02:00
|
|
|
rootBlock.children = block;
|
2023-09-01 15:33:38 +02:00
|
|
|
populateBlockDatabase(block, rootBlock);
|
2023-10-15 22:21:25 +02:00
|
|
|
blocks = rootBlock.children;
|
2023-09-01 14:06:13 +02:00
|
|
|
};
|
|
|
|
|
2024-02-14 23:17:44 +01:00
|
|
|
const getColumns = (blockId: string): number => {
|
|
|
|
const block = blockDatabase[blockId];
|
2023-08-08 15:56:02 +02:00
|
|
|
if (!block) {
|
|
|
|
return -1;
|
|
|
|
}
|
2023-08-28 12:51:49 +02:00
|
|
|
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
|
|
|
|
*/
|
2024-01-29 16:22:48 +01:00
|
|
|
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
|
|
|
|
*/
|
2024-01-29 16:22:48 +01:00
|
|
|
const getBlocks = () => {
|
2023-09-01 14:06:13 +02:00
|
|
|
return blocks || [];
|
|
|
|
};
|
2024-01-30 14:37:06 +01:00
|
|
|
|
2024-01-29 16:22:48 +01:00
|
|
|
const getEdges = () => {
|
2024-01-04 14:15:30 +01:00
|
|
|
return edgeList;
|
|
|
|
};
|
2024-01-29 16:22:48 +01:00
|
|
|
const getBlock = (id: string) => {
|
2023-09-05 11:13:27 +02:00
|
|
|
return blockDatabase[id];
|
|
|
|
};
|
2024-01-29 16:22:48 +01:00
|
|
|
|
|
|
|
const setBlock = (block: Block) => {
|
2023-09-05 11:13:27 +02:00
|
|
|
blockDatabase[block.id] = block;
|
|
|
|
};
|
2023-07-11 01:51:10 +02:00
|
|
|
|
2024-01-29 16:22:48 +01:00
|
|
|
const getLogger = () => console;
|
2023-07-11 01:51:10 +02:00
|
|
|
|
2024-01-05 15:13:15 +01:00
|
|
|
/**
|
|
|
|
* Return all of the style classes
|
|
|
|
*/
|
|
|
|
export const getClasses = function () {
|
|
|
|
return classes;
|
|
|
|
};
|
2023-07-10 22:33:11 +02:00
|
|
|
|
2024-01-29 16:22:48 +01:00
|
|
|
const db = {
|
2023-07-07 12:58:30 +02:00
|
|
|
getConfig: () => configApi.getConfig().block,
|
2023-08-28 12:51:49 +02:00
|
|
|
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,
|
2023-09-05 11:13:27 +02:00
|
|
|
getBlock,
|
|
|
|
setBlock,
|
2023-08-08 15:56:02 +02:00
|
|
|
getColumns,
|
2024-01-05 15:13:15 +01:00
|
|
|
getClasses,
|
2023-07-05 12:01:37 +02:00
|
|
|
clear,
|
2023-09-01 15:33:38 +02:00
|
|
|
generateId,
|
2024-01-29 16:22:48 +01:00
|
|
|
} as const;
|
2023-07-07 13:12:18 +02:00
|
|
|
|
2024-01-29 16:22:48 +01:00
|
|
|
export type BlockDB = typeof db & DiagramDB;
|
2023-07-10 22:33:11 +02:00
|
|
|
export default db;
|