Skip to content

Commit ecc9a33

Browse files
committed
feat: refactor BPMN diagram rendering and simplify node representation
1 parent 4ad09ce commit ecc9a33

1 file changed

Lines changed: 23 additions & 207 deletions

File tree

src/components/BpmnDiagram.astro

Lines changed: 23 additions & 207 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
---
22
// Renderiza un proceso BPMN como SVG servido desde el servidor: cero JS en el
3-
// cliente, cero dependencias. La geometría la calcula src/lib/bpmn-layout.ts;
4-
// aquí solo se pintan figuras.
3+
// cliente, cero dependencias. La geometría la calcula src/lib/bpmn-layout.ts y
4+
// cada figura la pinta BpmnShape; aquí se montan la piscina y los flujos.
55
import { GEO, layout, type BpmnProcess } from '../lib/bpmn-layout'
6+
import BpmnShape from './BpmnShape.astro'
67
78
interface Props {
89
proceso: BpmnProcess
@@ -14,20 +15,9 @@ const { width, height, lanes, nodes, edges } = layout(proceso)
1415
// Sufijo único por diagrama: varios SVG en la misma página no pueden compartir
1516
// los ids de <marker>, o el segundo se queda sin puntas de flecha.
1617
const uid = proceso.id
17-
18-
const esTarea = (t: string) => t.startsWith('task')
19-
const esCompuerta = (t: string) => t.startsWith('gateway')
20-
21-
/** Trazo del anillo exterior de cada evento, que es lo que los distingue en BPMN. */
22-
function anilloEvento(type: string) {
23-
if (type === 'intermediateEvent') return { grosor: 1.4, doble: true, color: 'var(--bpmn-line)' }
24-
if (type === 'endEventError') return { grosor: 3.2, doble: false, color: 'var(--bpmn-error)' }
25-
if (type.startsWith('endEvent')) return { grosor: 3.2, doble: false, color: 'var(--bpmn-end)' }
26-
return { grosor: 1.4, doble: false, color: 'var(--bpmn-start)' }
27-
}
2818
---
2919

30-
<figure class="bpmn">
20+
<figure class="bpmn bpmn-scope">
3121
<div class="bpmn-scroll">
3222
<svg
3323
width={width}
@@ -84,16 +74,17 @@ function anilloEvento(type: string) {
8474
>
8575
{lane.label}
8676
</text>
87-
<line
88-
x1="0"
89-
y1={lane.y}
90-
x2={width}
91-
y2={lane.y}
92-
stroke="var(--bpmn-border)"
93-
stroke-width={i === 0 ? 1.2 : 1}
94-
/>
77+
{i > 0 && <line x1="0" y1={lane.y} x2={width} y2={lane.y} stroke="var(--bpmn-border)" stroke-width="1" />}
9578
</g>
9679
))}
80+
<line
81+
x1={GEO.laneHeaderW}
82+
y1="0"
83+
x2={GEO.laneHeaderW}
84+
y2={height}
85+
stroke="var(--bpmn-border)"
86+
stroke-width="1.2"
87+
/>
9788
<rect
9889
x="0.6"
9990
y="0.6"
@@ -104,7 +95,6 @@ function anilloEvento(type: string) {
10495
stroke-width="1.2"
10596
rx="3"
10697
/>
107-
<line x1={GEO.laneHeaderW} y1="0" x2={GEO.laneHeaderW} y2={height} stroke="var(--bpmn-border)" stroke-width="1.2" />
10898
</g>
10999

110100
{/* ── Flujos ─────────────────────────────────────────────────────── */}
@@ -123,13 +113,19 @@ function anilloEvento(type: string) {
123113
<g>
124114
<rect
125115
x={e.labelAt.x - (e.label.length * 5.1) / 2 - 4}
126-
y={e.labelAt.y - 9}
116+
y={e.labelAt.y - 8}
127117
width={e.label.length * 5.1 + 8}
128118
height="15"
129119
rx="3"
130120
fill="var(--bpmn-label-bg)"
131121
/>
132-
<text class="bpmn-flow-label" x={e.labelAt.x} y={e.labelAt.y} text-anchor="middle" dominant-baseline="central">
122+
<text
123+
class="bpmn-flow-label"
124+
x={e.labelAt.x}
125+
y={e.labelAt.y}
126+
text-anchor="middle"
127+
dominant-baseline="central"
128+
>
133129
{e.label}
134130
</text>
135131
</g>
@@ -138,160 +134,21 @@ function anilloEvento(type: string) {
138134
))}
139135
</g>
140136

141-
{/* ── Nodos ──────────────────────────────────────────────────────── */}
137+
{/* ── Figuras ────────────────────────────────────────────────────── */}
142138
<g class="bpmn-nodes">
143-
{nodes.map((n) => (
144-
<g>
145-
{esTarea(n.type) && (
146-
<g>
147-
<rect
148-
x={n.cx - n.w / 2}
149-
y={n.cy - n.h / 2}
150-
width={n.w}
151-
height={n.h}
152-
rx="9"
153-
fill="var(--bpmn-task-fill)"
154-
stroke="var(--bpmn-task-stroke)"
155-
stroke-width="1.3"
156-
/>
157-
{/* Marcador de tipo de tarea, arriba a la izquierda como manda la notación. */}
158-
<g transform={`translate(${n.cx - n.w / 2 + 7} ${n.cy - n.h / 2 + 7})`} class="bpmn-marker">
159-
{n.type === 'taskUser' && (
160-
<g>
161-
<circle cx="5" cy="3.4" r="2.3" />
162-
<path d="M 0.6 10 a 4.4 4.4 0 0 1 8.8 0" />
163-
</g>
164-
)}
165-
{n.type === 'taskService' && (
166-
<g>
167-
<circle cx="5" cy="5.2" r="2.2" />
168-
<path d="M 5 0.6 v 1.6 M 5 8.2 v 1.6 M 0.4 5.2 h 1.6 M 8 5.2 h 1.6 M 1.8 2 l 1.1 1.1 M 7.1 7.3 l 1.1 1.1 M 8.2 2 l -1.1 1.1 M 2.9 7.3 l -1.1 1.1" />
169-
</g>
170-
)}
171-
{n.type === 'taskScript' && (
172-
<g>
173-
<path d="M 2 0.8 h 6.2 a 1.6 1.6 0 0 0 -1.6 1.6 v 6.2 a 1.6 1.6 0 0 1 -1.6 1.6 H 1.6" />
174-
<path d="M 3.2 3.4 h 3.4 M 3.2 5.6 h 3.4" />
175-
</g>
176-
)}
177-
{/* El sobre relleno usa `style` y no el atributo `fill`: una
178-
regla CSS de la clase le gana a un atributo de presentación. */}
179-
{n.type === 'taskSend' && (
180-
<g>
181-
<rect x="0.6" y="1.8" width="9" height="6.8" rx="1" style="fill: var(--bpmn-line)" />
182-
<path d="M 0.9 2.3 L 5.1 5.6 L 9.3 2.3" style="stroke: var(--bpmn-task-fill)" />
183-
</g>
184-
)}
185-
</g>
186-
<text class="bpmn-node-label" x={n.cx} y={n.cy} text-anchor="middle" dominant-baseline="central">
187-
{n.lines.map((line, i) => (
188-
<tspan x={n.cx} dy={i === 0 ? -((n.lines.length - 1) * 13) / 2 : 13}>
189-
{line}
190-
</tspan>
191-
))}
192-
</text>
193-
</g>
194-
)}
195-
196-
{esCompuerta(n.type) && (
197-
<g>
198-
<path
199-
d={`M ${n.cx} ${n.cy - n.h / 2} L ${n.cx + n.w / 2} ${n.cy} L ${n.cx} ${n.cy + n.h / 2} L ${n.cx - n.w / 2} ${n.cy} Z`}
200-
fill="var(--bpmn-gw-fill)"
201-
stroke="var(--bpmn-gw-stroke)"
202-
stroke-width="1.4"
203-
/>
204-
{n.type === 'gatewayExclusive' && (
205-
<path
206-
d={`M ${n.cx - 6} ${n.cy - 6} L ${n.cx + 6} ${n.cy + 6} M ${n.cx + 6} ${n.cy - 6} L ${n.cx - 6} ${n.cy + 6}`}
207-
stroke="var(--bpmn-gw-stroke)"
208-
stroke-width="1.9"
209-
stroke-linecap="round"
210-
/>
211-
)}
212-
{n.type === 'gatewayParallel' && (
213-
<path
214-
d={`M ${n.cx} ${n.cy - 8} V ${n.cy + 8} M ${n.cx - 8} ${n.cy} H ${n.cx + 8}`}
215-
stroke="var(--bpmn-gw-stroke)"
216-
stroke-width="1.9"
217-
stroke-linecap="round"
218-
/>
219-
)}
220-
</g>
221-
)}
222-
223-
{!esTarea(n.type) && !esCompuerta(n.type) && (
224-
<g>
225-
<circle
226-
cx={n.cx}
227-
cy={n.cy}
228-
r={n.w / 2}
229-
fill="var(--bpmn-event-fill)"
230-
stroke={anilloEvento(n.type).color}
231-
stroke-width={anilloEvento(n.type).grosor}
232-
/>
233-
{anilloEvento(n.type).doble && (
234-
<circle cx={n.cx} cy={n.cy} r={n.w / 2 - 3.4} fill="none" stroke="var(--bpmn-line)" stroke-width="1.4" />
235-
)}
236-
<g class="bpmn-event-icon" transform={`translate(${n.cx} ${n.cy})`}>
237-
{(n.type === 'messageStart' || n.type === 'intermediateEvent') && (
238-
<g>
239-
<rect x="-6.5" y="-4.5" width="13" height="9" rx="1" />
240-
<path d="M -6.1 -4.1 L 0 0.6 L 6.1 -4.1" />
241-
</g>
242-
)}
243-
{n.type === 'timerStart' && (
244-
<g>
245-
<circle cx="0" cy="0" r="6.4" />
246-
<path d="M 0 -3.6 V 0 L 2.6 1.7" />
247-
</g>
248-
)}
249-
{n.type === 'endEventError' && (
250-
<path d="M -5 5 L -1.4 -1.6 L 1.4 1.6 L 5 -5" style="stroke: var(--bpmn-error)" />
251-
)}
252-
</g>
253-
<text class="bpmn-node-label bpmn-outside" x={n.cx} y={n.cy + n.h / 2 + 13} text-anchor="middle">
254-
{n.lines.map((line, i) => (
255-
<tspan x={n.cx} dy={i === 0 ? 0 : 12}>
256-
{line}
257-
</tspan>
258-
))}
259-
</text>
260-
</g>
261-
)}
262-
</g>
263-
))}
139+
{nodes.map((n) => <BpmnShape node={n} />)}
264140
</g>
265141
</svg>
266142
</div>
267143
</figure>
268144

269145
<style>
270146
.bpmn {
271-
--bpmn-lane-a: #13131a;
272-
--bpmn-lane-b: #101017;
273-
--bpmn-header: #171720;
274-
--bpmn-border: #2e2e3a;
275-
--bpmn-line: #a6a6b2;
276-
--bpmn-msg: #85858f;
277-
--bpmn-task-fill: #1c1c26;
278-
--bpmn-task-stroke: #4d4d5d;
279-
--bpmn-gw-fill: #1c1c26;
280-
--bpmn-gw-stroke: #00b8c4;
281-
--bpmn-event-fill: #13131a;
282-
--bpmn-start: #c9ff5b;
283-
--bpmn-end: #e7e7ec;
284-
--bpmn-error: #ff6b3d;
285-
--bpmn-label-bg: #13131a;
286-
287147
margin: 0;
288148
border: 1px solid rgb(255 255 255 / 0.14);
289149
border-radius: 0.75rem;
290150
background: var(--bpmn-lane-a);
291151
overflow: hidden;
292-
/* Sombra hacia dentro en el borde derecho: insinúa que hay más diagrama
293-
del que cabe, sin añadir una barra de scroll falsa. */
294-
position: relative;
295152
}
296153

297154
.bpmn-scroll {
@@ -304,45 +161,4 @@ function anilloEvento(type: string) {
304161
svg {
305162
display: block;
306163
}
307-
308-
.bpmn-node-label {
309-
font-size: 11.5px;
310-
fill: #e7e7ec;
311-
font-weight: 450;
312-
}
313-
314-
.bpmn-outside {
315-
font-size: 10.5px;
316-
fill: #c4c4cc;
317-
}
318-
319-
.bpmn-lane-label {
320-
font-size: 10.5px;
321-
fill: #c4c4cc;
322-
font-weight: 600;
323-
letter-spacing: 0.06em;
324-
text-transform: uppercase;
325-
}
326-
327-
.bpmn-flow-label {
328-
font-size: 10px;
329-
fill: #c4c4cc;
330-
font-weight: 500;
331-
}
332-
333-
.bpmn-marker {
334-
fill: none;
335-
stroke: var(--bpmn-line);
336-
stroke-width: 1.1;
337-
stroke-linecap: round;
338-
stroke-linejoin: round;
339-
}
340-
341-
.bpmn-event-icon {
342-
fill: none;
343-
stroke: var(--bpmn-line);
344-
stroke-width: 1.3;
345-
stroke-linecap: round;
346-
stroke-linejoin: round;
347-
}
348164
</style>

0 commit comments

Comments
 (0)