@zsviczian/mermaid-to-excalidraw
Advanced tools
Comparing version 0.1.2-obsidian-3 to 0.2.0-obsidian-1
@@ -5,1 +5,10 @@ export declare const DEFAULT_FONT_SIZE = 20; | ||
}; | ||
export declare const MERMAID_CONFIG: { | ||
startOnLoad: boolean; | ||
flowchart: { | ||
curve: string; | ||
}; | ||
themeVariables: { | ||
fontSize: string; | ||
}; | ||
}; |
@@ -6,1 +6,8 @@ export const DEFAULT_FONT_SIZE = 20; | ||
}; | ||
export const MERMAID_CONFIG = { | ||
startOnLoad: false, | ||
flowchart: { curve: "linear" }, | ||
themeVariables: { | ||
fontSize: `${DEFAULT_FONT_SIZE * 1.25}px`, | ||
}, | ||
}; |
@@ -0,118 +1,4 @@ | ||
import { nanoid } from "nanoid"; | ||
import { GraphConverter } from "../GraphConverter.js"; | ||
import { nanoid } from "nanoid"; | ||
// Arrow mapper for the supported sequence arrow types | ||
const EXCALIDRAW_STROKE_STYLE_FOR_ARROW = { | ||
SOLID: "solid", | ||
DOTTED: "dotted", | ||
SOLID_CROSS: "solid", | ||
DOTTED_CROSS: "dotted", | ||
SOLID_OPEN: "solid", | ||
DOTTED_OPEN: "dotted", | ||
SOLID_POINT: "solid", | ||
DOTTED_POINT: "dotted", | ||
}; | ||
const createLine = (line) => { | ||
const lineElement = { | ||
type: "line", | ||
x: line.startX, | ||
y: line.startY, | ||
points: [ | ||
[0, 0], | ||
[line.endX - line.startX, line.endY - line.startY], | ||
], | ||
width: line.endX - line.startX, | ||
height: line.endY - line.startY, | ||
strokeStyle: line.strokeStyle || "solid", | ||
strokeColor: line.strokeColor || "#000", | ||
strokeWidth: line.strokeWidth || 1, | ||
}; | ||
if (line.groupId) { | ||
Object.assign(lineElement, { groupIds: [line.groupId] }); | ||
} | ||
if (line.id) { | ||
Object.assign(lineElement, { id: line.id }); | ||
} | ||
return lineElement; | ||
}; | ||
const createText = (element) => { | ||
const textElement = { | ||
type: "text", | ||
x: element.x, | ||
y: element.y, | ||
width: element.width, | ||
height: element.height, | ||
text: element.text || "", | ||
fontSize: element.fontSize, | ||
verticalAlign: "middle", | ||
}; | ||
if (element.groupId) { | ||
Object.assign(textElement, { groupIds: [element.groupId] }); | ||
} | ||
if (element.id) { | ||
Object.assign(textElement, { id: element.id }); | ||
} | ||
return textElement; | ||
}; | ||
const createContainer = (element) => { | ||
let extraProps = {}; | ||
if (element.type === "rectangle" && element.subtype === "activation") { | ||
extraProps = { | ||
backgroundColor: "#e9ecef", | ||
fillStyle: "solid", | ||
}; | ||
} | ||
const container = { | ||
id: element.id, | ||
type: element.type, | ||
x: element.x, | ||
y: element.y, | ||
width: element.width, | ||
height: element.height, | ||
label: { | ||
text: element?.label?.text || "", | ||
fontSize: element?.label?.fontSize, | ||
verticalAlign: "middle", | ||
strokeColor: element.label?.color || "#000", | ||
}, | ||
strokeStyle: element?.strokeStyle, | ||
strokeWidth: element?.strokeWidth, | ||
strokeColor: element?.strokeColor, | ||
backgroundColor: element?.bgColor, | ||
fillStyle: "solid", | ||
...extraProps, | ||
}; | ||
if (element.groupId) { | ||
Object.assign(container, { groupIds: [element.groupId] }); | ||
} | ||
return container; | ||
}; | ||
const createArrow = (arrow) => { | ||
const strokeStyle = EXCALIDRAW_STROKE_STYLE_FOR_ARROW[arrow.strokeStyle]; | ||
const arrowElement = { | ||
type: "arrow", | ||
x: arrow.startX, | ||
y: arrow.startY, | ||
points: arrow.points || [ | ||
[0, 0], | ||
[arrow.endX - arrow.startX, arrow.endY - arrow.startY], | ||
], | ||
width: arrow.endX - arrow.startX, | ||
height: arrow.endY - arrow.startY, | ||
strokeStyle, | ||
endArrowhead: arrow.strokeStyle === "SOLID_OPEN" || arrow.strokeStyle === "DOTTED_OPEN" | ||
? null | ||
: "arrow", | ||
label: { | ||
text: arrow?.label?.text || "", | ||
fontSize: 16, | ||
}, | ||
roundness: { | ||
type: 2, | ||
}, | ||
}; | ||
if (arrow.groupId) { | ||
Object.assign(arrowElement, { groupIds: [arrow.groupId] }); | ||
} | ||
return arrowElement; | ||
}; | ||
import { transformToExcalidrawLineSkeleton, transformToExcalidrawTextSkeleton, transformToExcalidrawContainerSkeleton, transformToExcalidrawArrowSkeleton, } from "../transformToExcalidrawSkeleton.js"; | ||
export const SequenceToExcalidrawSkeletonConvertor = new GraphConverter({ | ||
@@ -130,10 +16,10 @@ converter: (chart) => { | ||
case "line": | ||
excalidrawElement = createLine(element); | ||
excalidrawElement = transformToExcalidrawLineSkeleton(element); | ||
break; | ||
case "rectangle": | ||
case "ellipse": | ||
excalidrawElement = createContainer(element); | ||
excalidrawElement = transformToExcalidrawContainerSkeleton(element); | ||
break; | ||
case "text": | ||
excalidrawElement = createText(element); | ||
excalidrawElement = transformToExcalidrawTextSkeleton(element); | ||
break; | ||
@@ -156,3 +42,3 @@ default: | ||
} | ||
elements.push(createLine(line)); | ||
elements.push(transformToExcalidrawLineSkeleton(line)); | ||
}); | ||
@@ -163,5 +49,5 @@ Object.values(chart.arrows).forEach((arrow) => { | ||
} | ||
elements.push(createArrow(arrow)); | ||
elements.push(transformToExcalidrawArrowSkeleton(arrow)); | ||
if (arrow.sequenceNumber) { | ||
elements.push(createContainer(arrow.sequenceNumber)); | ||
elements.push(transformToExcalidrawContainerSkeleton(arrow.sequenceNumber)); | ||
} | ||
@@ -174,9 +60,9 @@ }); | ||
lines.forEach((line) => { | ||
elements.push(createLine(line)); | ||
elements.push(transformToExcalidrawLineSkeleton(line)); | ||
}); | ||
texts.forEach((text) => { | ||
elements.push(createText(text)); | ||
elements.push(transformToExcalidrawTextSkeleton(text)); | ||
}); | ||
nodes.forEach((node) => { | ||
elements.push(createContainer(node)); | ||
elements.push(transformToExcalidrawContainerSkeleton(node)); | ||
}); | ||
@@ -220,3 +106,3 @@ } | ||
const groupRectId = nanoid(); | ||
const groupRect = createContainer({ | ||
const groupRect = transformToExcalidrawContainerSkeleton({ | ||
type: "rectangle", | ||
@@ -223,0 +109,0 @@ x: groupRectX, |
@@ -5,2 +5,3 @@ import { MermaidOptions } from "./index.js"; | ||
import { Flowchart } from "./parser/flowchart.js"; | ||
export declare const graphToExcalidraw: (graph: Flowchart | GraphImage | Sequence, options?: MermaidOptions) => MermaidToExcalidrawResult; | ||
import { Class } from "./parser/class.js"; | ||
export declare const graphToExcalidraw: (graph: Flowchart | GraphImage | Sequence | Class, options?: MermaidOptions) => MermaidToExcalidrawResult; |
import { FlowchartToExcalidrawSkeletonConverter } from "./converter/types/flowchart.js"; | ||
import { GraphImageConverter } from "./converter/types/graphImage.js"; | ||
import { SequenceToExcalidrawSkeletonConvertor } from "./converter/types/sequence.js"; | ||
import { classToExcalidrawSkeletonConvertor } from "./converter/types/class.js"; | ||
export const graphToExcalidraw = (graph, options = {}) => { | ||
@@ -15,2 +16,5 @@ switch (graph.type) { | ||
} | ||
case "class": { | ||
return classToExcalidrawSkeletonConvertor.convert(graph, options); | ||
} | ||
default: { | ||
@@ -17,0 +21,0 @@ throw new Error(`graphToExcalidraw: unknown graph type "${graph.type}, only flowcharts are supported!"`); |
import { GraphImage } from "./interfaces.js"; | ||
import { Flowchart } from "./parser/flowchart.js"; | ||
import { Sequence } from "./parser/sequence.js"; | ||
import { Class } from "./parser/class.js"; | ||
import { MermaidOptions } from "./index.js"; | ||
@@ -10,2 +11,2 @@ declare global { | ||
} | ||
export declare const parseMermaid: (definition: string, forceSVG?: boolean, options?: MermaidOptions) => Promise<Flowchart | GraphImage | Sequence>; | ||
export declare const parseMermaid: (definition: string, forceSVG?: boolean, options?: MermaidOptions) => Promise<Flowchart | GraphImage | Sequence | Class>; |
@@ -5,2 +5,3 @@ //import { DEFAULT_FONT_SIZE } from "./constants.js"; //zsviczian | ||
import { parseMermaidSequenceDiagram } from "./parser/sequence.js"; | ||
import { parseMermaidClassDiagram } from "./parser/class.js"; | ||
import { replaceSVGStyle } from "./obsidianUtil.js"; //zsviczian | ||
@@ -51,4 +52,4 @@ import { DEFAULT_FONT_SIZE } from "./constants.js"; | ||
}); | ||
const diagram = await window.mermaid.mermaidAPI.getDiagramFromText(encodeEntities(definition) //zsviczian | ||
); | ||
const diagram = await window.mermaid.mermaidAPI.getDiagramFromText(//zsviczian | ||
encodeEntities(definition)); | ||
// Render the SVG diagram | ||
@@ -75,2 +76,6 @@ const { svg } = await window.mermaid.render("mermaid-to-excalidraw", definition); //zsviczian | ||
} | ||
case "classDiagram": { | ||
data = parseMermaidClassDiagram(diagram, svgContainer); | ||
break; | ||
} | ||
// fallback to image if diagram type not-supported | ||
@@ -77,0 +82,0 @@ default: { |
@@ -1,2 +0,2 @@ | ||
import { entityCodesToText, getTransformAttr } from "../utils.js"; | ||
import { computeEdgePositions, entityCodesToText, getTransformAttr, } from "../utils.js"; | ||
const parseSubGraph = (data, containerEl) => { | ||
@@ -140,49 +140,2 @@ // Extract only node id for better reference | ||
}; | ||
const computeEdgePositions = (pathElement, offset = { x: 0, y: 0 }) => { | ||
if (pathElement.tagName.toLowerCase() !== "path") { | ||
throw new Error(`Invalid input: Expected an HTMLElement of tag "path", got ${pathElement.tagName}`); | ||
} | ||
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 | ||
const commands = dAttr.split(/(?=[LM])/); | ||
const startPosition = commands[0] | ||
.substring(1) | ||
.split(",") | ||
.map((coord) => parseFloat(coord)); | ||
const endPosition = commands[commands.length - 1] | ||
.substring(1) | ||
.split(",") | ||
.map((coord) => parseFloat(coord)); | ||
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) => { | ||
if (index === array.length - 1) { | ||
return true; | ||
} | ||
const prevPoint = array[index - 1]; | ||
return (index === 0 || (point.x !== prevPoint.x && point.y !== prevPoint.y)); | ||
}) | ||
.map((p) => { | ||
return { | ||
x: p.x + offset.x, | ||
y: p.y + offset.y, | ||
}; | ||
}); | ||
return { | ||
startX: startPosition[0] + offset.x, | ||
startY: startPosition[1] + offset.y, | ||
endX: endPosition[0] + offset.x, | ||
endY: endPosition[1] + offset.y, | ||
reflectionPoints, | ||
}; | ||
}; | ||
export const parseMermaidFlowChartDiagram = (diagram, containerEl) => { | ||
@@ -189,0 +142,0 @@ // This does some cleanup and initialization making sure |
@@ -1,57 +0,3 @@ | ||
import { Diagram } from "mermaid/dist/Diagram.js"; | ||
import { ExcalidrawLinearElement } from "@excalidraw/excalidraw/types/element/types.js"; | ||
export type Line = { | ||
id?: string; | ||
startX: number; | ||
startY: number; | ||
endX: number; | ||
endY: number; | ||
strokeColor: string | null; | ||
strokeWidth: number | null; | ||
strokeStyle: ExcalidrawLinearElement["strokeStyle"] | null; | ||
type: "line"; | ||
groupId?: string; | ||
}; | ||
type ARROW_KEYS = keyof typeof SEQUENCE_ARROW_TYPES; | ||
export type Arrow = Omit<Line, "type" | "strokeStyle"> & { | ||
type: "arrow"; | ||
label?: { | ||
text: string | null; | ||
fontSize: number; | ||
}; | ||
strokeStyle: (typeof SEQUENCE_ARROW_TYPES)[ARROW_KEYS]; | ||
points?: number[][]; | ||
sequenceNumber: Container; | ||
}; | ||
export type Text = { | ||
id?: string; | ||
type: "text"; | ||
text: string; | ||
x: number; | ||
y: number; | ||
width: number; | ||
height: number; | ||
fontSize: number; | ||
groupId?: string; | ||
}; | ||
export type Container = { | ||
id?: string; | ||
type: "rectangle" | "ellipse"; | ||
label?: { | ||
text: string | null; | ||
fontSize: number; | ||
color?: string; | ||
}; | ||
x: number; | ||
y: number; | ||
width?: number; | ||
height?: number; | ||
strokeStyle?: "dashed" | "solid"; | ||
strokeWidth?: number; | ||
strokeColor?: string; | ||
bgColor?: string; | ||
subtype?: "actor" | "activation" | "highlight" | "note" | "sequence"; | ||
groupId?: string; | ||
}; | ||
export type Node = Container | Line | Arrow | Text; | ||
import { Arrow, Container, Line, Node, Text } from "../elementSkeleton.js"; | ||
import type { Diagram } from "mermaid/dist/Diagram.js"; | ||
type Loop = { | ||
@@ -75,13 +21,3 @@ lines: Line[]; | ||
} | ||
declare const SEQUENCE_ARROW_TYPES: { | ||
0: string; | ||
1: string; | ||
3: string; | ||
4: string; | ||
5: string; | ||
6: string; | ||
24: string; | ||
25: string; | ||
}; | ||
export declare const parseMermaidSequenceDiagram: (diagram: Diagram, containerEl: Element) => Sequence; | ||
export {}; |
import { SVG_TO_SHAPE_MAPPER } from "../constants.js"; | ||
import { nanoid } from "nanoid"; | ||
import { entityCodesToText } from "../utils.js"; | ||
import { createArrowSkeletonFromSVG, createContainerSkeletonFromSVG, createLineSkeletonFromSVG, createTextSkeletonFromSVG, } from "../elementSkeleton.js"; | ||
// Currently mermaid supported these 6 arrow types, the names are taken from mermaidParser.LINETYPE | ||
@@ -47,127 +47,27 @@ const SEQUENCE_ARROW_TYPES = { | ||
}; | ||
const createContainerElement = (node, type, opts = {}) => { | ||
const container = {}; | ||
container.type = type; | ||
const { text, subtype, id, groupId } = opts; | ||
container.id = id; | ||
if (groupId) { | ||
container.groupId = groupId; | ||
} | ||
if (text) { | ||
container.label = { | ||
text: entityCodesToText(text), | ||
fontSize: 16, | ||
}; | ||
} | ||
const boundingBox = node.getBBox(); | ||
container.x = boundingBox.x; | ||
container.y = boundingBox.y; | ||
container.width = boundingBox.width; | ||
container.height = boundingBox.height; | ||
container.subtype = subtype; | ||
switch (subtype) { | ||
case "highlight": | ||
const bgColor = node.getAttribute("fill"); | ||
if (bgColor) { | ||
container.bgColor = bgColor; | ||
} | ||
const getStrokeStyle = (type) => { | ||
let strokeStyle; | ||
switch (type) { | ||
case MESSAGE_TYPE.SOLID: | ||
case MESSAGE_TYPE.SOLID_CROSS: | ||
case MESSAGE_TYPE.SOLID_OPEN: | ||
case MESSAGE_TYPE.SOLID_POINT: | ||
strokeStyle = "solid"; | ||
break; | ||
case "note": | ||
container.strokeStyle = "dashed"; | ||
case MESSAGE_TYPE.DOTTED: | ||
case MESSAGE_TYPE.DOTTED_CROSS: | ||
case MESSAGE_TYPE.DOTTED_OPEN: | ||
case MESSAGE_TYPE.DOTTED_POINT: | ||
strokeStyle = "dotted"; | ||
break; | ||
default: | ||
strokeStyle = "solid"; | ||
break; | ||
} | ||
return container; | ||
return strokeStyle; | ||
}; | ||
const createTextElement = (textNode, text, opts) => { | ||
const node = {}; | ||
const x = Number(textNode.getAttribute("x")); | ||
const y = Number(textNode.getAttribute("y")); | ||
node.type = "text"; | ||
node.text = entityCodesToText(text); | ||
if (opts?.id) { | ||
node.id = opts.id; | ||
} | ||
if (opts?.groupId) { | ||
node.groupId = opts.groupId; | ||
} | ||
const boundingBox = textNode.getBBox(); | ||
node.width = boundingBox.width; | ||
node.height = boundingBox.height; | ||
node.x = x - boundingBox.width / 2; | ||
node.y = y; | ||
const fontSize = parseInt(getComputedStyle(textNode).fontSize); | ||
node.fontSize = fontSize; | ||
return node; | ||
}; | ||
const createLineElement = (lineNode, startX, startY, endX, endY, opts) => { | ||
const line = {}; | ||
line.startX = startX; | ||
line.startY = startY; | ||
line.endX = endX; | ||
if (opts?.groupId) { | ||
line.groupId = opts.groupId; | ||
} | ||
if (opts?.id) { | ||
line.id = opts.id; | ||
} | ||
// Make sure lines don't overlap with the nodes, in mermaid it overlaps but isn't visible as its pushed back and containers are non transparent | ||
line.endY = endY; | ||
line.strokeColor = lineNode.getAttribute("stroke"); | ||
line.strokeWidth = Number(lineNode.getAttribute("stroke-width")); | ||
line.type = "line"; | ||
return line; | ||
}; | ||
const createArrowElement = (arrowNode, message) => { | ||
const arrow = {}; | ||
arrow.label = { text: entityCodesToText(message.message), fontSize: 16 }; | ||
const tagName = arrowNode.tagName; | ||
if (tagName === "line") { | ||
arrow.startX = Number(arrowNode.getAttribute("x1")); | ||
arrow.startY = Number(arrowNode.getAttribute("y1")); | ||
arrow.endX = Number(arrowNode.getAttribute("x2")); | ||
arrow.endY = Number(arrowNode.getAttribute("y2")); | ||
} | ||
else if (tagName === "path") { | ||
const dAttr = arrowNode.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 C (Curve) commands | ||
const commands = dAttr.split(/(?=[LC])/); | ||
const startPosition = commands[0] | ||
.substring(1) | ||
.split(",") | ||
.map((coord) => parseFloat(coord)); | ||
const points = []; | ||
commands.forEach((command) => { | ||
const currPoints = command | ||
.substring(1) | ||
.trim() | ||
.split(" ") | ||
.map((pos) => { | ||
const [x, y] = pos.split(","); | ||
return [ | ||
parseFloat(x) - startPosition[0], | ||
parseFloat(y) - startPosition[1], | ||
]; | ||
}); | ||
points.push(...currPoints); | ||
}); | ||
const endPosition = points[points.length - 1]; | ||
arrow.startX = startPosition[0]; | ||
arrow.startY = startPosition[1]; | ||
arrow.endX = endPosition[0]; | ||
arrow.endY = endPosition[1]; | ||
arrow.points = points; | ||
} | ||
if (message) { | ||
// In mermaid the text is positioned above arrow but in Excalidraw | ||
// its postioned on the arrow hence the elements below it might look cluttered so shifting the arrow by an offset of 10px | ||
const offset = 10; | ||
arrow.startY = arrow.startY - offset; | ||
arrow.endY = arrow.endY - offset; | ||
} | ||
const showSequenceNumber = !!arrowNode.nextElementSibling?.classList.contains("sequenceNumber"); | ||
const attachSequenceNumberToArrow = (node, arrow) => { | ||
const showSequenceNumber = !!node.nextElementSibling?.classList.contains("sequenceNumber"); | ||
if (showSequenceNumber) { | ||
const text = arrowNode.nextElementSibling?.textContent; | ||
const text = node.nextElementSibling?.textContent; | ||
if (!text) { | ||
@@ -188,9 +88,4 @@ throw new Error("sequence number not present"); | ||
}; | ||
arrow.sequenceNumber = sequenceNumber; | ||
Object.assign(arrow, { sequenceNumber }); | ||
} | ||
arrow.strokeColor = arrowNode.getAttribute("stroke"); | ||
arrow.strokeWidth = Number(arrowNode.getAttribute("stroke-width")); | ||
arrow.type = "arrow"; | ||
arrow.strokeStyle = SEQUENCE_ARROW_TYPES[message.type]; | ||
return arrow; | ||
}; | ||
@@ -213,10 +108,13 @@ const createActorSymbol = (rootNode, text, opts) => { | ||
const endY = Number(child.getAttribute("y2")); | ||
ele = createLineElement(child, startX, startY, endX, endY, { groupId, id }); | ||
ele = createLineSkeletonFromSVG(child, startX, startY, endX, endY, { groupId, id }); | ||
break; | ||
case "text": | ||
ele = createTextElement(child, text, { groupId, id }); | ||
ele = createTextSkeletonFromSVG(child, text, { | ||
groupId, | ||
id, | ||
}); | ||
break; | ||
case "circle": | ||
ele = createContainerElement(child, "ellipse", { | ||
text: child.textContent || undefined, | ||
ele = createContainerSkeletonFromSVG(child, "ellipse", { | ||
label: child.textContent ? { text: child.textContent } : undefined, | ||
groupId, | ||
@@ -226,3 +124,7 @@ id, | ||
default: | ||
ele = createContainerElement(child, SVG_TO_SHAPE_MAPPER[child.tagName], { text: child.textContent || undefined, groupId, id }); | ||
ele = createContainerSkeletonFromSVG(child, SVG_TO_SHAPE_MAPPER[child.tagName], { | ||
label: child.textContent ? { text: child.textContent } : undefined, | ||
groupId, | ||
id, | ||
}); | ||
} | ||
@@ -236,3 +138,3 @@ nodeElements.push(ele); | ||
.filter((node) => node.tagName === "text") | ||
.map((actor) => actor.tagName === "text" && actor.parentElement); | ||
.map((actor) => actor.parentElement); | ||
const nodes = []; | ||
@@ -242,8 +144,6 @@ const lines = []; | ||
Object.values(actors).forEach((actor, index) => { | ||
//@ts-ignore | ||
// For each actor there are two nodes top and bottom which is connected by a line | ||
const topRootNode = actorRootNodes[index]; | ||
//@ts-ignore | ||
const bottomRootNode = actorRootNodes[actorsLength + index]; | ||
if (!topRootNode) { | ||
if (!topRootNode || !bottomRootNode) { | ||
throw "root not found"; | ||
@@ -254,3 +154,3 @@ } | ||
// creating top actor node element | ||
const topNodeElement = createContainerElement(topRootNode.firstChild, "rectangle", { id: `${actor.name}-top`, text, subtype: "actor" }); | ||
const topNodeElement = createContainerSkeletonFromSVG(topRootNode.firstChild, "rectangle", { id: `${actor.name}-top`, label: { text }, subtype: "actor" }); | ||
if (!topNodeElement) { | ||
@@ -261,3 +161,3 @@ throw "Top Node element not found!"; | ||
// creating bottom actor node element | ||
const bottomNodeElement = createContainerElement(bottomRootNode.firstChild, "rectangle", { id: `${actor.name}-bottom`, text, subtype: "actor" }); | ||
const bottomNodeElement = createContainerSkeletonFromSVG(bottomRootNode.firstChild, "rectangle", { id: `${actor.name}-bottom`, label: { text }, subtype: "actor" }); | ||
nodes.push([bottomNodeElement]); | ||
@@ -277,3 +177,3 @@ // Get the line connecting the top and bottom nodes. As per the DOM, the line is rendered as first child of parent element | ||
const endX = Number(lineNode.getAttribute("x2")); | ||
const line = createLineElement(lineNode, startX, startY, endX, endY); | ||
const line = createLineSkeletonFromSVG(lineNode, startX, startY, endX, endY); | ||
lines.push(line); | ||
@@ -302,3 +202,3 @@ } | ||
const endY = bottomEllipseNode.y; | ||
const line = createLineElement(lineNode, startX, startY, endX, endY); | ||
const line = createLineSkeletonFromSVG(lineNode, startX, startY, endX, endY); | ||
lines.push(line); | ||
@@ -311,3 +211,3 @@ } | ||
const parseActor = (actors, containerEl) => { | ||
//@ts-ignore | ||
//@ts-ignore //zsviczian | ||
if (!window.ExcalidrawAutomate.obsidian.requireApiVersion("1.5.0")) | ||
@@ -317,3 +217,3 @@ return parseActor_old(actors, containerEl); | ||
.filter((node) => node.tagName === "text") | ||
.map((actor) => actor.tagName === "text" && actor.parentElement); | ||
.map((actor) => actor.parentElement); | ||
const lineNodes = containerEl.querySelectorAll("line"); //zsviczian | ||
@@ -324,8 +224,6 @@ const nodes = []; | ||
Object.values(actors).forEach((actor, index) => { | ||
//@ts-ignore | ||
// For each actor there are two nodes top and bottom which is connected by a line | ||
const topRootNode = actorRootNodes[index]; | ||
//@ts-ignore | ||
const bottomRootNode = actorRootNodes[actorsLength + index]; | ||
if (!topRootNode) { | ||
if (!topRootNode || !bottomRootNode) { | ||
throw "root not found"; | ||
@@ -336,3 +234,3 @@ } | ||
// creating top actor node element | ||
const topNodeElement = createContainerElement(topRootNode.firstChild, "rectangle", { id: `${actor.name}-top`, text, subtype: "actor" }); | ||
const topNodeElement = createContainerSkeletonFromSVG(topRootNode.firstChild, "rectangle", { id: `${actor.name}-top`, label: { text }, subtype: "actor" }); | ||
if (!topNodeElement) { | ||
@@ -343,6 +241,7 @@ throw "Top Node element not found!"; | ||
// creating bottom actor node element | ||
const bottomNodeElement = createContainerElement(bottomRootNode.firstChild, "rectangle", { id: `${actor.name}-bottom`, text, subtype: "actor" }); | ||
const bottomNodeElement = createContainerSkeletonFromSVG(bottomRootNode.firstChild, "rectangle", { id: `${actor.name}-bottom`, label: { text }, subtype: "actor" }); | ||
nodes.push([bottomNodeElement]); | ||
// Get the line connecting the top and bottom nodes. As per the DOM, the line is rendered as first child of parent element | ||
const lineNode = lineNodes[index]; //zsviczian | ||
//const lineNode = topRootNode.previousElementSibling as SVGLineElement; | ||
if (lineNode?.tagName !== "line") { | ||
@@ -355,7 +254,7 @@ throw "Line not found"; | ||
} | ||
const startY = topNodeElement.y; | ||
const startY = topNodeElement.y; //zsviczian | ||
// Make sure lines don't overlap with the nodes, in mermaid it overlaps but isn't visible as its pushed back and containers are non transparent | ||
const endY = bottomNodeElement.y + bottomNodeElement.height; //zsviczian | ||
const endX = Number(lineNode.getAttribute("x2")); | ||
const line = createLineElement(lineNode, startX, startY, endX, endY); | ||
const line = createLineSkeletonFromSVG(lineNode, startX, startY, endX, endY); | ||
lines.push(line); | ||
@@ -384,3 +283,3 @@ } | ||
const endY = bottomEllipseNode.y; | ||
const line = createLineElement(lineNode, startX, startY, endX, endY); | ||
const line = createLineSkeletonFromSVG(lineNode, startX, startY, endX, endY); | ||
lines.push(line); | ||
@@ -399,3 +298,11 @@ } | ||
const message = arrowMessages[index]; | ||
const arrow = createArrowElement(arrowNode, message); | ||
const messageType = SEQUENCE_ARROW_TYPES[message.type]; | ||
const arrow = createArrowSkeletonFromSVG(arrowNode, { | ||
label: message?.message, | ||
strokeStyle: getStrokeStyle(message.type), | ||
endArrowhead: messageType === "SOLID_OPEN" || messageType === "DOTTED_OPEN" | ||
? null | ||
: "arrow", | ||
}); | ||
attachSequenceNumberToArrow(arrowNode, arrow); | ||
arrows.push(arrow); | ||
@@ -415,4 +322,4 @@ }); | ||
const text = noteText[index].message; | ||
const note = createContainerElement(rect, "rectangle", { | ||
text, | ||
const note = createContainerSkeletonFromSVG(rect, "rectangle", { | ||
label: { text }, | ||
subtype: "note", | ||
@@ -428,4 +335,4 @@ }); | ||
activationNodes.forEach((node) => { | ||
const rect = createContainerElement(node, "rectangle", { | ||
text: "", | ||
const rect = createContainerSkeletonFromSVG(node, "rectangle", { | ||
label: { text: "" }, | ||
subtype: "activation", | ||
@@ -447,3 +354,3 @@ }); | ||
const endY = Number(node.getAttribute("y2")); | ||
const line = createLineElement(node, startX, startY, endX, endY); | ||
const line = createLineSkeletonFromSVG(node, startX, startY, endX, endY); | ||
line.strokeStyle = "dotted"; | ||
@@ -460,3 +367,3 @@ line.strokeColor = "#adb5bd"; | ||
const text = node.textContent || ""; | ||
const textElement = createTextElement(node, text); | ||
const textElement = createTextSkeletonFromSVG(node, text); | ||
// The text is rendered between [ ] in DOM hence getting the text excluding the [ ] | ||
@@ -475,5 +382,5 @@ const rawText = text.match(/\[(.*?)\]/)?.[1] || ""; | ||
labelBoxes.forEach((labelBox, index) => { | ||
const labelText = labelTextNode[index]?.textContent || ""; | ||
const container = createContainerElement(labelBox, "rectangle", { | ||
text: labelText, | ||
const text = labelTextNode[index]?.textContent || ""; | ||
const container = createContainerSkeletonFromSVG(labelBox, "rectangle", { | ||
label: { text }, | ||
}); | ||
@@ -495,4 +402,4 @@ container.strokeColor = "#adb5bd"; | ||
rects.forEach((rect) => { | ||
const node = createContainerElement(rect, "rectangle", { | ||
text: "", | ||
const node = createContainerSkeletonFromSVG(rect, "rectangle", { | ||
label: { text: "" }, | ||
subtype: "highlight", | ||
@@ -499,0 +406,0 @@ }); |
@@ -0,1 +1,2 @@ | ||
import { Position } from "./interfaces.js"; | ||
export declare const entityCodesToText: (input: string) => string; | ||
@@ -8,1 +9,10 @@ export declare const getTransformAttr: (el: Element) => { | ||
export declare const decodeEntities: (text: string) => string; | ||
interface EdgePositionData { | ||
startX: number; | ||
startY: number; | ||
endX: number; | ||
endY: number; | ||
reflectionPoints: Position[]; | ||
} | ||
export declare const computeEdgePositions: (pathElement: SVGPathElement, offset?: Position) => EdgePositionData; | ||
export {}; |
@@ -13,3 +13,3 @@ // Convert mermaid entity codes to text e.g. "#9829;" to "♥" | ||
const transformAttr = el.getAttribute("transform"); | ||
const translateMatch = transformAttr?.match(/translate\(([\d.-]+),\s*([\d.-]+)\)/); | ||
const translateMatch = transformAttr?.match(/translate\(([ \d.-]+),\s*([\d.-]+)\)/); | ||
let transformX = 0; | ||
@@ -45,1 +45,48 @@ let transformY = 0; | ||
}; | ||
export const computeEdgePositions = (pathElement, offset = { x: 0, y: 0 }) => { | ||
if (pathElement.tagName.toLowerCase() !== "path") { | ||
throw new Error(`Invalid input: Expected an HTMLElement of tag "path", got ${pathElement.tagName}`); | ||
} | ||
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 | ||
const commands = dAttr.split(/(?=[LM])/); | ||
const startPosition = commands[0] | ||
.substring(1) | ||
.split(",") | ||
.map((coord) => parseFloat(coord)); | ||
const endPosition = commands[commands.length - 1] | ||
.substring(1) | ||
.split(",") | ||
.map((coord) => parseFloat(coord)); | ||
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) => { | ||
if (index === array.length - 1) { | ||
return true; | ||
} | ||
const prevPoint = array[index - 1]; | ||
return (index === 0 || (point.x !== prevPoint.x && point.y !== prevPoint.y)); | ||
}) | ||
.map((p) => { | ||
return { | ||
x: p.x + offset.x, | ||
y: p.y + offset.y, | ||
}; | ||
}); | ||
return { | ||
startX: startPosition[0] + offset.x, | ||
startY: startPosition[1] + offset.y, | ||
endX: endPosition[0] + offset.x, | ||
endY: endPosition[1] + offset.y, | ||
reflectionPoints, | ||
}; | ||
}; |
{ | ||
"name": "@zsviczian/mermaid-to-excalidraw", | ||
"version": "0.1.2-obsidian-3", | ||
"version": "0.2.0-obsidian-1", | ||
"description": "Mermaid to Excalidraw Diagrams", | ||
@@ -21,3 +21,3 @@ "main": "dist/index.js", | ||
"build": "rimraf ./dist && cross-env tsc -b src", | ||
"start": "parcel playground/index.html --open --dist-dir ./public", | ||
"start": "rimraf .parcel-cache && parcel playground/index.html --open --dist-dir ./public", | ||
"build:playground": "rimraf ./public && parcel build playground/index.html --no-scope-hoist --dist-dir ./public --public-url /", | ||
@@ -34,3 +34,3 @@ "test:code": "eslint --max-warnings=0 --ext .js,.ts,.tsx ." | ||
"@excalidraw/eslint-config": "1.0.3", | ||
"@excalidraw/excalidraw": "0.16.1-6920-d3d0bd0", | ||
"@excalidraw/excalidraw": "0.17.1-7381-cdf6d3e", | ||
"@parcel/transformer-sass": "2.9.1", | ||
@@ -37,0 +37,0 @@ "@types/mermaid": "9.2.0", |
144621
41
2497