|
1 | 1 | import { getMermaidShapeByValue } from './shapes/flowchartShapes.js';
|
2 |
| -import { Shape, Connector, Page } from './types'; |
| 2 | +import { Shape, Edge, Page, Style } from './types'; |
3 | 3 |
|
4 | 4 | interface NodeRecord {
|
5 | 5 | ID: string;
|
6 | 6 | Shape: Shape;
|
7 | 7 | NodeDef: string;
|
8 | 8 | }
|
9 | 9 |
|
10 |
| -export class Scribe { |
11 |
| - nodes: NodeRecord[] = []; |
12 |
| - edges: Connector[] = []; |
13 |
| - |
14 |
| - constructor() {} |
| 10 | +const shapeToNode = (shape: Shape) => { |
| 11 | + const nodeId = `n0${shape.ID}`; |
| 12 | + const nodeShape = getMermaidShapeByValue(shape.Name); |
| 13 | + const nodeDef = `${nodeId}@{ shape: ${nodeShape}, label: ${shape.Text} }`; |
| 14 | + return { ID: nodeId, Shape: shape, NodeDef: nodeDef }; |
| 15 | +}; |
| 16 | + |
| 17 | +const shapeToConnector = (shape: Shape, connectors: Edge[]) => { |
| 18 | + const edge = shape as Edge; |
| 19 | + const connector = connectors.find((c) => c.ID === shape.ID); |
| 20 | + if (connector) { |
| 21 | + edge.FromNode = `n0${connector.FromNode}`; |
| 22 | + edge.ToNode = `n0${connector.ToNode}`; |
| 23 | + } |
| 24 | + return edge; |
| 25 | +}; |
| 26 | + |
| 27 | +export const writeMermaidCode = (page: Page) => { |
| 28 | + const nodes: NodeRecord[] = []; |
| 29 | + const edges: Edge[] = []; |
| 30 | + const styles: string[] = []; |
| 31 | + |
| 32 | + for (const shape of page.Shapes) { |
| 33 | + if (shape.Type === 'connector') { |
| 34 | + edges.push(shapeToConnector(shape, page.Edges)); |
| 35 | + } else { |
| 36 | + nodes.push(shapeToNode(shape)); |
| 37 | + } |
| 38 | + } |
15 | 39 |
|
16 |
| - private shapeToNode = (shape: Shape) => { |
17 |
| - const nodeId = `n0${shape.ID}`; |
18 |
| - const nodeShape = getMermaidShapeByValue(shape.Name); |
19 |
| - const nodeDef = `${nodeId}@{ shape: ${nodeShape}, label: ${shape.Text} }`; |
20 |
| - this.nodes.push({ ID: nodeId, Shape: shape, NodeDef: nodeDef }); |
21 |
| - }; |
| 40 | + const nodeCount = nodes.length; |
| 41 | + let index = 0; |
22 | 42 |
|
23 |
| - private shapeToConnector = (shape: Shape, connectors: Connector[]) => { |
24 |
| - const edge = shape as Connector; |
25 |
| - const connector = connectors.find((c) => c.ID === shape.ID); |
26 |
| - if (!connector) { |
| 43 | + let mermaidSyntax = 'flowchart TD\r\n'; |
| 44 | + nodes.forEach((node) => { |
| 45 | + if (node.Shape.Type === 'connector') { |
27 | 46 | return;
|
28 | 47 | }
|
29 | 48 |
|
30 |
| - edge.FromNode = `n0${connector.FromNode}`; |
31 |
| - edge.ToNode = `n0${connector.ToNode}`; |
32 |
| - this.edges.push(edge); |
33 |
| - }; |
34 |
| - |
35 |
| - writeMermaidCode = (page: Page) => { |
36 |
| - this.nodes = []; |
37 |
| - this.edges = []; |
38 |
| - for (const shape of page.Shapes) { |
39 |
| - if (shape.Type === 'connector') { |
40 |
| - this.shapeToConnector(shape, page.Connectors); |
41 |
| - } else { |
42 |
| - this.shapeToNode(shape); |
43 |
| - } |
| 49 | + mermaidSyntax += node.NodeDef; |
| 50 | + if (index < nodeCount - 1) { |
| 51 | + mermaidSyntax += '\r\n'; |
| 52 | + } |
| 53 | + const style = getStyleStatement(node.Shape.Style); |
| 54 | + if (style) { |
| 55 | + styles.push(`style ${node.ID} ${style}`); |
44 | 56 | }
|
| 57 | + index++; |
| 58 | + }); |
45 | 59 |
|
46 |
| - const nodeCount = this.nodes.length; |
47 |
| - let index = 0; |
| 60 | + const edgeCount = edges.length; |
| 61 | + index = 0; |
48 | 62 |
|
49 |
| - let mermaidSyntax = 'flowchart TD\r\n'; |
50 |
| - this.nodes.forEach((node) => { |
51 |
| - if (node.Shape.Type === 'connector') { |
52 |
| - return; |
53 |
| - } |
| 63 | + if (edgeCount > 0) { |
| 64 | + mermaidSyntax += '\r\n'; |
| 65 | + edges.forEach((edge) => { |
| 66 | + const edgeStart = `${edge.FromNode}`; |
| 67 | + const edgeEnd = `${edge.ToNode}`; |
| 68 | + |
| 69 | + mermaidSyntax += buildEdgeStatement(edgeStart, edgeEnd, edge.Style, edge.Text); |
54 | 70 |
|
55 |
| - mermaidSyntax += node.NodeDef; |
56 |
| - if (index < nodeCount - 1) { |
| 71 | + if (index < edgeCount - 1) { |
57 | 72 | mermaidSyntax += '\r\n';
|
58 | 73 | }
|
59 | 74 |
|
| 75 | + const style = getStyleStatement(edge.Style); |
| 76 | + if (style) { |
| 77 | + styles.push(`linkStyle ${index} ${style}`); |
| 78 | + } |
60 | 79 | index++;
|
61 | 80 | });
|
| 81 | + } |
62 | 82 |
|
63 |
| - const edgeCount = this.edges.length; |
64 |
| - index = 0; |
65 |
| - |
66 |
| - if (edgeCount > 0) { |
67 |
| - mermaidSyntax += '\r\n'; |
68 |
| - this.edges.forEach((connector) => { |
69 |
| - const edgeStart = `${connector.FromNode}`; |
70 |
| - const edgeEnd = `${connector.ToNode}`; |
| 83 | + const styleCount = styles.length; |
| 84 | + index = 0; |
71 | 85 |
|
72 |
| - mermaidSyntax += `${edgeStart} --> ${edgeEnd}`; |
| 86 | + if (styleCount > 0) { |
| 87 | + mermaidSyntax += '\r\n'; |
| 88 | + styles.forEach((style) => { |
| 89 | + mermaidSyntax += style; |
73 | 90 |
|
74 |
| - if (index < edgeCount - 1) { |
75 |
| - mermaidSyntax += '\r\n'; |
76 |
| - } |
| 91 | + if (index < styleCount - 1) { |
| 92 | + mermaidSyntax += '\r\n'; |
| 93 | + } |
77 | 94 |
|
78 |
| - index++; |
79 |
| - }); |
| 95 | + index++; |
| 96 | + }); |
| 97 | + } |
| 98 | + |
| 99 | + return mermaidSyntax; |
| 100 | +}; |
| 101 | + |
| 102 | +const getStyleStatement = (style: Style): string => { |
| 103 | + let styleStatement = ''; |
| 104 | + |
| 105 | + // Handle fill properties |
| 106 | + if (style.FillForeground && style.FillBackground && style.FillPattern !== undefined) { |
| 107 | + switch (style.FillPattern) { |
| 108 | + case 0: |
| 109 | + // No fill |
| 110 | + styleStatement += `fill: none,`; |
| 111 | + break; |
| 112 | + case 1: |
| 113 | + // Solid fill, foreground color only |
| 114 | + styleStatement += `fill: ${style.FillForeground},`; |
| 115 | + break; |
| 116 | + case 2: |
| 117 | + // Horizontal stripes example (customize based on pattern type) |
| 118 | + styleStatement += `background: repeating-linear-gradient(0deg, ${style.FillForeground}, ${style.FillForeground} 10px, ${style.FillBackground} 10px, ${style.FillBackground} 20px),`; |
| 119 | + break; |
| 120 | + case 6: |
| 121 | + // Crosshatch fill example |
| 122 | + styleStatement += `background: repeating-linear-gradient(45deg, ${style.FillForeground}, ${style.FillForeground} 10px, ${style.FillBackground} 10px, ${style.FillBackground} 20px),`; |
| 123 | + break; |
| 124 | + // Add more cases here for different fill patterns |
| 125 | + default: |
| 126 | + styleStatement += `fill: ${style.FillForeground},`; // Default solid fill |
80 | 127 | }
|
81 |
| - |
82 |
| - return mermaidSyntax; |
83 |
| - }; |
| 128 | + } |
| 129 | + |
| 130 | + if (style.LineWeight && style.LineWeight > 2) { |
| 131 | + styleStatement += `stroke-width: ${Math.round(style.LineWeight)},`; // LineWeight to stroke-width |
| 132 | + } |
| 133 | + |
| 134 | + if (style.LineColor) { |
| 135 | + styleStatement += `stroke: ${style.LineColor},`; |
| 136 | + } |
| 137 | + |
| 138 | + if (style.LinePattern) { |
| 139 | + switch (style.LinePattern) { |
| 140 | + case 1: |
| 141 | + // Dashed line |
| 142 | + styleStatement += `stroke-dasharray: 5, 5,`; // Customizable dash length |
| 143 | + break; |
| 144 | + case 2: |
| 145 | + // Dotted line |
| 146 | + styleStatement += `stroke-dasharray: 1, 5,`; |
| 147 | + break; |
| 148 | + case 3: |
| 149 | + // Dash-dot line |
| 150 | + styleStatement += `stroke-dasharray: 5, 5, 1, 5,`; |
| 151 | + break; |
| 152 | + // Add more cases for other line patterns as needed |
| 153 | + } |
| 154 | + } |
| 155 | + |
| 156 | + if (style.Rounding && style.Rounding > 0) { |
| 157 | + styleStatement += `border-radius: ${style.Rounding}px,`; |
| 158 | + } |
| 159 | + |
| 160 | + // Handle line caps (start and end of lines) |
| 161 | + if (style.LineCap) { |
| 162 | + switch (style.LineCap) { |
| 163 | + case 0: |
| 164 | + styleStatement += `stroke-linecap: butt,`; // Flat ends |
| 165 | + break; |
| 166 | + case 1: |
| 167 | + styleStatement += `stroke-linecap: round,`; // Rounded ends |
| 168 | + break; |
| 169 | + case 2: |
| 170 | + styleStatement += `stroke-linecap: square,`; // Square ends |
| 171 | + break; |
| 172 | + } |
| 173 | + } |
| 174 | + |
| 175 | + if (style.FillForeground && styleStatement.indexOf('fill') === -1) { |
| 176 | + styleStatement += `fill: ${style.FillForeground},`; |
| 177 | + } |
| 178 | + |
| 179 | + return styleStatement.trim().replace(/,$/, ''); |
| 180 | +}; |
| 181 | + |
| 182 | +const buildEdgeStatement = (start: string, end: string, style: Style, text: string): string => { |
| 183 | + let startArrow = getArrow(style.BeginArrow); |
| 184 | + let endArrow = getArrow(style.EndArrow); |
| 185 | + |
| 186 | + if (startArrow === '&') { |
| 187 | + startArrow = '<'; |
| 188 | + } |
| 189 | + if (startArrow === '&') { |
| 190 | + startArrow = '>'; |
| 191 | + } |
| 192 | + |
| 193 | + let { startStroke, endStroke } = getStroke(style.LinePattern); |
| 194 | + |
| 195 | + if (startArrow === '<' && endArrow === '') { |
| 196 | + return `${end} ${endStroke} ${text} ${startStroke}> ${start}`; |
| 197 | + } |
| 198 | + |
| 199 | + return `${start} ${startArrow}${startStroke}${text}${endStroke}${endArrow} ${end}`; |
| 200 | +}; |
| 201 | + |
| 202 | +const getStroke = (linePattern: number) => { |
| 203 | + let startStroke = '--'; |
| 204 | + let endStroke = '--'; |
| 205 | + |
| 206 | + if (linePattern) { |
| 207 | + switch (linePattern) { |
| 208 | + case 2: |
| 209 | + case 3: |
| 210 | + // Dotted line |
| 211 | + startStroke = '-.'; |
| 212 | + endStroke = '.-'; |
| 213 | + break; |
| 214 | + // Add more cases for other line patterns as needed |
| 215 | + } |
| 216 | + } |
| 217 | + |
| 218 | + return { startStroke, endStroke }; |
| 219 | +}; |
| 220 | + |
| 221 | +function getArrow(arrow: number): string { |
| 222 | + switch (arrow) { |
| 223 | + case 0: |
| 224 | + return ''; |
| 225 | + case 6: |
| 226 | + case 7: |
| 227 | + return 'o'; |
| 228 | + default: |
| 229 | + return '&'; |
| 230 | + } |
84 | 231 | }
|
0 commit comments