Skip to content

Commit 292c91b

Browse files
csouchettbouffard
andauthored
[FEAT] Render Catch Message Intermediate Event (#238)
This contains a refactoring to generalize icons management Introduce a declarative way for the bpmn event render icon method selection This uses a more functional way of doing things instead of having several if/else or switch which brings more clarity and will simplify extensions. Simplify how we handle catch/throw icon render This is now hold by the Shape class itself allowing icon render method to easily know if a Throw or Catch icon has to be painted. Prior this change, we had to pass argument to several methods which made the code hard to follow. Introduce a single place to manage events currently displayed with a arbitrary fill color prior we implement their final icon. This was prior done at several places which make the code hard to follow and was error prone: we have broken the render of such BPMN events several times in the past. Co-authored-by: Souchet Céline <[email protected]> Co-authored-by: Thomas Bouffard <[email protected]>
1 parent 9a95a1a commit 292c91b

File tree

3 files changed

+52
-72
lines changed

3 files changed

+52
-72
lines changed

docs/bpmn-support.adoc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,8 @@ The default rendering uses `white` as fill color and `black` as stroke color.
103103
|Stroke fits the BPMN specification but no icon
104104

105105
|Message Intermediate Catch Event
106-
|
107-
|Stroke fits the BPMN specification but no icon
106+
|icon:check-circle-o[]
107+
|The stroke & icon width may be adjusted
108108
|===
109109

110110
[cols="1,1,4", options="header"]

src/component/mxgraph/shape/event-shapes.ts

Lines changed: 43 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -24,44 +24,63 @@ const mxUtils: typeof mxgraph.mxUtils = MxGraphFactoryService.getMxGraphProperty
2424
const mxConstants: typeof mxgraph.mxConstants = MxGraphFactoryService.getMxGraphProperty('mxConstants');
2525

2626
abstract class EventShape extends mxEllipse {
27+
// when all/more event types will be supported, we could move to a Record/MappedType
28+
private iconPainters: Map<ShapeBpmnEventKind, (c: mxgraph.mxXmlCanvas2D, x: number, y: number, w: number, h: number) => void> = new Map([
29+
[ShapeBpmnEventKind.MESSAGE, (c: mxgraph.mxXmlCanvas2D, x: number, y: number, w: number, h: number) => this.paintMessageIcon(c, x, y, w, h)],
30+
[ShapeBpmnEventKind.TERMINATE, (c: mxgraph.mxXmlCanvas2D, x: number, y: number, w: number, h: number) => this.paintTerminateIcon(c, x, y, w, h)],
31+
]);
32+
protected isUsingThrowIcons = false;
33+
2734
protected constructor(bounds: mxgraph.mxRectangle, fill: string, stroke: string, strokewidth: number) {
2835
super(bounds, fill, stroke, strokewidth);
2936
}
3037

3138
public paintVertexShape(c: mxgraph.mxXmlCanvas2D, x: number, y: number, w: number, h: number): void {
39+
this.markNonFullyRenderedEvents(c);
40+
this.paintOuterShape(c, x, y, w, h);
41+
this.paintInnerShape(c, x, y, w, h);
42+
}
43+
44+
// This will be removed when managing the render of all events
45+
private markNonFullyRenderedEvents(c: mxgraph.mxXmlCanvas2D): void {
3246
const eventKind = this.getBpmnEventKind();
33-
// will be removed when managing the timer rendering
3447
if (eventKind == ShapeBpmnEventKind.TIMER) {
3548
c.setFillColor('green');
3649
c.setFillAlpha(0.3);
50+
} // eslint-disable-next-line @typescript-eslint/no-use-before-define
51+
else if (eventKind == ShapeBpmnEventKind.MESSAGE && (this instanceof StartEventShape || this instanceof EndEventShape)) {
52+
c.setFillColor('yellow');
53+
c.setFillAlpha(0.3);
3754
}
38-
39-
this.paintOuterShape(c, x, y, w, h);
40-
this.paintInnerShape(c, x, y, w, h);
4155
}
4256

4357
protected paintOuterShape(c: mxgraph.mxXmlCanvas2D, x: number, y: number, w: number, h: number): void {
4458
super.paintVertexShape(c, x, y, w, h);
4559
}
4660

47-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
4861
protected paintInnerShape(c: mxgraph.mxXmlCanvas2D, x: number, y: number, w: number, h: number): void {
49-
// do nothing by default
62+
const paintIcon =
63+
this.iconPainters.get(this.getBpmnEventKind()) || ((c: mxgraph.mxXmlCanvas2D, x: number, y: number, w: number, h: number) => this.paintEmptyIcon(c, x, y, w, h));
64+
paintIcon(c, x, y, w, h);
5065
}
5166

52-
protected getBpmnEventKind(): ShapeBpmnEventKind {
67+
private getBpmnEventKind(): ShapeBpmnEventKind {
5368
return mxUtils.getValue(this.style, StyleConstant.BPMN_STYLE_EVENT_KIND, ShapeBpmnEventKind.NONE);
5469
}
5570

56-
// TODO: will be removed when managing the message rendering
57-
protected paintOuterMessageShape(c: mxgraph.mxXmlCanvas2D): void {
58-
c.setFillColor('yellow');
59-
c.setFillAlpha(0.3);
71+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
72+
private paintEmptyIcon(c: mxgraph.mxXmlCanvas2D, x: number, y: number, w: number, h: number): void {
73+
// empty by nature
6074
}
6175

6276
// this implementation is adapted from the draw.io BPMN 'message' symbol
6377
// https://github.com/jgraph/drawio/blob/0e19be6b42755790a749af30450c78c0d83be765/src/main/webapp/shapes/bpmn/mxBpmnShape2.js#L465
64-
private paintMessageIcon(c: mxgraph.mxXmlCanvas2D, x: number, y: number, w: number, h: number, isInverse = false): void {
78+
private paintMessageIcon(c: mxgraph.mxXmlCanvas2D, x: number, y: number, w: number, h: number): void {
79+
// eslint-disable-next-line @typescript-eslint/no-use-before-define
80+
if (!(this instanceof IntermediateEventShape)) {
81+
return;
82+
}
83+
const isInverse = this.isUsingThrowIcons;
6584
// Change the coordinate referential
6685
c.translate(x + w * 0.24, y + h * 0.34);
6786
w = w * 0.52;
@@ -111,41 +130,8 @@ abstract class EventShape extends mxEllipse {
111130
c.stroke();
112131
}
113132

114-
protected paintThrowMessageIcon(c: mxgraph.mxXmlCanvas2D, x: number, y: number, w: number, h: number): void {
115-
this.paintMessageIcon(c, x, y, w, h, true);
116-
}
117-
}
118-
119-
export class StartEventShape extends EventShape {
120-
public constructor(bounds: mxgraph.mxRectangle, fill: string, stroke: string, strokewidth: number = StyleConstant.STROKE_WIDTH_THIN) {
121-
super(bounds, fill, stroke, strokewidth);
122-
}
123-
124-
// TODO: will be removed when managing the message rendering
125-
protected paintOuterShape(c: mxgraph.mxXmlCanvas2D, x: number, y: number, w: number, h: number): void {
126-
const eventKind = this.getBpmnEventKind();
127-
if (eventKind == ShapeBpmnEventKind.MESSAGE) {
128-
this.paintOuterMessageShape(c);
129-
}
130-
131-
super.paintOuterShape(c, x, y, w, h);
132-
}
133-
}
134-
135-
export class EndEventShape extends EventShape {
136-
public constructor(bounds: mxgraph.mxRectangle, fill: string, stroke: string, strokewidth: number = StyleConstant.STROKE_WIDTH_THICK) {
137-
super(bounds, fill, stroke, strokewidth);
138-
}
139-
140-
protected paintInnerShape(c: mxgraph.mxXmlCanvas2D, x: number, y: number, w: number, h: number): void {
141-
const eventKind = this.getBpmnEventKind();
142-
if (eventKind == ShapeBpmnEventKind.TERMINATE) {
143-
this.paintTerminateEventIcon(c, x, y, w, h);
144-
}
145-
}
146-
147133
// highly inspired from mxDoubleEllipse
148-
private paintTerminateEventIcon(c: mxgraph.mxXmlCanvas2D, x: number, y: number, w: number, h: number): void {
134+
private paintTerminateIcon(c: mxgraph.mxXmlCanvas2D, x: number, y: number, w: number, h: number): void {
149135
c.setFillColor(this.stroke);
150136
c.setStrokeWidth(0);
151137
const inset = mxUtils.getValue(this.style, mxConstants.STYLE_MARGIN, Math.min(3 + this.strokewidth, Math.min(w / 5, h / 5)));
@@ -160,15 +146,17 @@ export class EndEventShape extends EventShape {
160146

161147
c.fillAndStroke();
162148
}
149+
}
163150

164-
// TODO: will be removed when managing the message rendering
165-
protected paintOuterShape(c: mxgraph.mxXmlCanvas2D, x: number, y: number, w: number, h: number): void {
166-
const eventKind = this.getBpmnEventKind();
167-
if (eventKind == ShapeBpmnEventKind.MESSAGE) {
168-
this.paintOuterMessageShape(c);
169-
}
151+
export class StartEventShape extends EventShape {
152+
public constructor(bounds: mxgraph.mxRectangle, fill: string, stroke: string, strokewidth: number = StyleConstant.STROKE_WIDTH_THIN) {
153+
super(bounds, fill, stroke, strokewidth);
154+
}
155+
}
170156

171-
super.paintOuterShape(c, x, y, w, h);
157+
export class EndEventShape extends EventShape {
158+
public constructor(bounds: mxgraph.mxRectangle, fill: string, stroke: string, strokewidth: number = StyleConstant.STROKE_WIDTH_THICK) {
159+
super(bounds, fill, stroke, strokewidth);
172160
}
173161
}
174162

@@ -193,26 +181,11 @@ export class CatchIntermediateEventShape extends IntermediateEventShape {
193181
public constructor(bounds: mxgraph.mxRectangle, fill: string, stroke: string, strokewidth?: number) {
194182
super(bounds, fill, stroke, strokewidth);
195183
}
196-
197-
// TODO: will be removed when managing the message rendering
198-
protected paintOuterShape(c: mxgraph.mxXmlCanvas2D, x: number, y: number, w: number, h: number): void {
199-
const eventKind = this.getBpmnEventKind();
200-
if (eventKind == ShapeBpmnEventKind.MESSAGE) {
201-
this.paintOuterMessageShape(c);
202-
}
203-
204-
super.paintOuterShape(c, x, y, w, h);
205-
}
206184
}
185+
207186
export class ThrowIntermediateEventShape extends IntermediateEventShape {
208187
public constructor(bounds: mxgraph.mxRectangle, fill: string, stroke: string, strokewidth?: number) {
209188
super(bounds, fill, stroke, strokewidth);
210-
}
211-
212-
protected paintInnerShape(c: mxgraph.mxXmlCanvas2D, x: number, y: number, w: number, h: number): void {
213-
const eventKind = this.getBpmnEventKind();
214-
if (eventKind == ShapeBpmnEventKind.MESSAGE) {
215-
this.paintThrowMessageIcon(c, x, y, w, h);
216-
}
189+
this.isUsingThrowIcons = true;
217190
}
218191
}

test/e2e/View.test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ describe('BPMN Visualization JS', () => {
6060
<semantic:intermediateThrowEvent name="Throw Message Intermediate Event" id="messageIntermediateThrowEvent">
6161
<semantic:messageEventDefinition />
6262
</semantic:intermediateThrowEvent>
63+
<semantic:intermediateCatchEvent name="Catch Message Intermediate Event" id="messageIntermediateCatchEvent">
64+
<semantic:messageEventDefinition />
65+
</semantic:intermediateCatchEvent>
6366
<semantic:intermediateCatchEvent id="IntermediateCatchEvent_Timer_01" name="Timer Intermediate Catch Event">
6467
<semantic:incoming>Flow_028jkgv</semantic:incoming>
6568
<semantic:timerEventDefinition id="TimerEventDefinition_0t6k83a" />
@@ -114,6 +117,9 @@ describe('BPMN Visualization JS', () => {
114117
<bpmndi:BPMNShape bpmnElement="messageIntermediateThrowEvent" id="S1373649849862_messageIntermediateThrowEvent">
115118
<dc:Bounds height="32.0" width="32.0" x="698.0" y="335.0" />
116119
</bpmndi:BPMNShape>
120+
<bpmndi:BPMNShape bpmnElement="messageIntermediateCatchEvent" id="S1373649849862_messageIntermediateCatchEvent">
121+
<dc:Bounds height="32.0" width="32.0" x="98.0" y="335.0" />
122+
</bpmndi:BPMNShape>
117123
<bpmndi:BPMNShape bpmnElement="endEvent_1" id="S1373649849862_endEvent_1">
118124
<dc:Bounds height="32.0" width="32.0" x="648.0" y="335.0"/>
119125
<bpmndi:BPMNLabel labelStyle="LS1373649849858">
@@ -202,6 +208,7 @@ describe('BPMN Visualization JS', () => {
202208
expectModelContainsBpmnEvent('endEvent_1', ShapeBpmnElementKind.EVENT_END, ShapeBpmnEventKind.TERMINATE);
203209
expectModelContainsBpmnEvent('noneIntermediateThrowEvent', ShapeBpmnElementKind.EVENT_INTERMEDIATE_THROW, ShapeBpmnEventKind.NONE);
204210
expectModelContainsBpmnEvent('messageIntermediateThrowEvent', ShapeBpmnElementKind.EVENT_INTERMEDIATE_THROW, ShapeBpmnEventKind.MESSAGE);
211+
expectModelContainsBpmnEvent('messageIntermediateCatchEvent', ShapeBpmnElementKind.EVENT_INTERMEDIATE_CATCH, ShapeBpmnEventKind.MESSAGE);
205212
expectModelContainsBpmnEvent('IntermediateCatchEvent_Timer_01', ShapeBpmnElementKind.EVENT_INTERMEDIATE_CATCH, ShapeBpmnEventKind.TIMER);
206213
expectModelContainsCell('task_1', ShapeBpmnElementKind.TASK);
207214
expectModelContainsCell('serviceTask_2', ShapeBpmnElementKind.TASK_SERVICE);

0 commit comments

Comments
 (0)