121 lines
5 KiB
JavaScript
121 lines
5 KiB
JavaScript
// Convert mermaid entity codes to text e.g. "#9829;" to "♥"
|
|
export const entityCodesToText = (input) => {
|
|
input = decodeEntities(input);
|
|
// Append & before the pattern #(\d+); or #([a-z]+); to convert to decimal code
|
|
// so it can be rendered as html character
|
|
// eg #9829; => ♥
|
|
const inputWithDecimalCode = input
|
|
.replace(/#(\d+);/g, "&#$1;")
|
|
.replace(/#([a-z]+);/g, "&$1;");
|
|
// Render the decimal code as html character, eg ♥ => ♥
|
|
const element = document.createElement("textarea");
|
|
element.innerHTML = inputWithDecimalCode;
|
|
return element.value;
|
|
};
|
|
export const getTransformAttr = (el) => {
|
|
const transformAttr = el.getAttribute("transform");
|
|
const translateMatch = transformAttr?.match(/translate\(([ \d.-]+),\s*([\d.-]+)\)/);
|
|
let transformX = 0;
|
|
let transformY = 0;
|
|
if (translateMatch) {
|
|
transformX = Number(translateMatch[1]);
|
|
transformY = Number(translateMatch[2]);
|
|
}
|
|
return { transformX, transformY };
|
|
};
|
|
//TODO Once fixed in mermaid this will be removed
|
|
export const encodeEntities = (text) => {
|
|
let txt = text;
|
|
txt = txt.replace(/style.*:\S*#.*;/g, (s) => {
|
|
return s.substring(0, s.length - 1);
|
|
});
|
|
txt = txt.replace(/classDef.*:\S*#.*;/g, (s) => {
|
|
return s.substring(0, s.length - 1);
|
|
});
|
|
txt = txt.replace(/#\w+;/g, (s) => {
|
|
const innerTxt = s.substring(1, s.length - 1);
|
|
const isInt = /^\+?\d+$/.test(innerTxt);
|
|
if (isInt) {
|
|
return `fl°°${innerTxt}¶ß`;
|
|
}
|
|
return `fl°${innerTxt}¶ß`;
|
|
});
|
|
return txt;
|
|
};
|
|
export const decodeEntities = function (text) {
|
|
return text.replace(/fl°°/g, "#").replace(/fl°/g, "&").replace(/¶ß/g, ";");
|
|
};
|
|
// Compute edge postion start, end and points (reflection points)
|
|
export const computeEdgePositions = (pathElement, offset = { x: 0, y: 0 }) => {
|
|
// Check if the element is a path else throw an error
|
|
if (pathElement.tagName.toLowerCase() !== "path") {
|
|
throw new Error(`Invalid input: Expected an HTMLElement of tag "path", got ${pathElement.tagName}`);
|
|
}
|
|
// Get the d attribute from the path element else throw an error
|
|
const dAttr = pathElement.getAttribute("d");
|
|
if (!dAttr) {
|
|
throw new Error('Path element does not contain a "d" attribute');
|
|
}
|
|
// Split the d attribute based on M (Move To) and L (Line To) commands
|
|
// eg "M29.383,38.5L29.383,63.5L29.383,83.2" => ["M29.383,38.5", "L29.383,63.5", "L29.383,83.2"]
|
|
const commands = dAttr.split(/(?=[LM])/);
|
|
// Get the start position from the first commands element => [29.383,38.5]
|
|
const startPosition = commands[0]
|
|
.substring(1)
|
|
.split(",")
|
|
.map((coord) => parseFloat(coord));
|
|
// Get the last position from the last commands element => [29.383,83.2]
|
|
const endPosition = commands[commands.length - 1]
|
|
.substring(1)
|
|
.split(",")
|
|
.map((coord) => parseFloat(coord));
|
|
// compute the reflection points -> [ {x: 29.383, y: 38.5}, {x: 29.383, y: 83.2} ]
|
|
// These includes the start and end points and also points which are not the same as the previous points
|
|
const reflectionPoints = commands
|
|
.map((command) => {
|
|
const coords = command
|
|
.substring(1)
|
|
.split(",")
|
|
.map((coord) => parseFloat(coord));
|
|
return { x: coords[0], y: coords[1] };
|
|
})
|
|
.filter((point, index, array) => {
|
|
// Always include the last point
|
|
if (index === 0 || index === array.length - 1) {
|
|
return true;
|
|
}
|
|
// Exclude the points which are the same as the previous point
|
|
if (point.x === array[index - 1].x && point.y === array[index - 1].y) {
|
|
return false;
|
|
}
|
|
// The below check is exclusively for second last point
|
|
if (index === array.length - 2 &&
|
|
(array[index - 1].x === point.x || array[index - 1].y === point.y)) {
|
|
const lastPoint = array[array.length - 1];
|
|
// Get the distance between the last point and second last point using Euclidean distance formula
|
|
const distance = Math.hypot(lastPoint.x - point.x, lastPoint.y - point.y);
|
|
// Include the second last point if the distance between the
|
|
// last point and second last point is > 20.
|
|
// This is to ensure we have a distance for render the edge.
|
|
// 20 seems to be a good enough distance to render the edge
|
|
return distance > 20;
|
|
}
|
|
// Always include if the current point is not the same as the previous point
|
|
return point.x !== array[index - 1].x || point.y !== array[index - 1].y;
|
|
})
|
|
.map((p) => {
|
|
// Offset the point by the provided offset
|
|
return {
|
|
x: p.x + offset.x,
|
|
y: p.y + offset.y,
|
|
};
|
|
});
|
|
// Return the edge positions
|
|
return {
|
|
startX: startPosition[0] + offset.x,
|
|
startY: startPosition[1] + offset.y,
|
|
endX: endPosition[0] + offset.x,
|
|
endY: endPosition[1] + offset.y,
|
|
reflectionPoints,
|
|
};
|
|
};
|