From 776e500922043dda09a1c996723c8e4ab4632f01 Mon Sep 17 00:00:00 2001 From: Thomas Bouffard <27200110+tbouffard@users.noreply.github.com> Date: Fri, 1 Mar 2024 15:37:31 +0100 Subject: [PATCH] refactor: directly override `shape.createSvgCanvas` (#3043) Previously, this was done by modifying the shape prototype. The new implementation limits side effects on application that use mxGraph directly, and don't modify code we don't own. The change is validated by integration tests that check that the bpmn-id and the CSS classes in the DOM are correctly set on both shapes/edges and labels. This is also validated with the elements-identification.html demo. The CSS class is correctly applied to the label. --- src/component/mxgraph/BpmnCellRenderer.ts | 7 ++ .../mxgraph/config/ShapeConfigurator.ts | 50 +------------- src/component/mxgraph/shape/utils.ts | 67 +++++++++++++++++++ 3 files changed, 75 insertions(+), 49 deletions(-) create mode 100644 src/component/mxgraph/shape/utils.ts diff --git a/src/component/mxgraph/BpmnCellRenderer.ts b/src/component/mxgraph/BpmnCellRenderer.ts index 11a3cfabe8..d2d27a3313 100644 --- a/src/component/mxgraph/BpmnCellRenderer.ts +++ b/src/component/mxgraph/BpmnCellRenderer.ts @@ -20,6 +20,7 @@ import type { mxCellState, mxImageShape, mxShape } from 'mxgraph'; import { mxgraph, mxRectangle } from './initializer'; import { MxGraphCustomOverlay } from './overlay/custom-overlay'; import { OverlayBadgeShape } from './overlay/shapes'; +import { overrideCreateSvgCanvas } from './shape/utils'; export class BpmnCellRenderer extends mxgraph.mxCellRenderer { constructor(private iconPainter: IconPainter) { @@ -90,6 +91,12 @@ export class BpmnCellRenderer extends mxgraph.mxCellRenderer { if ('iconPainter' in shape) { shape.iconPainter = this.iconPainter; } + overrideCreateSvgCanvas(shape); return shape; } + + override createLabel(state: mxCellState, value: string): void { + super.createLabel(state, value); + overrideCreateSvgCanvas(state.text); + } } diff --git a/src/component/mxgraph/config/ShapeConfigurator.ts b/src/component/mxgraph/config/ShapeConfigurator.ts index 9453cebb27..36e063e887 100644 --- a/src/component/mxgraph/config/ShapeConfigurator.ts +++ b/src/component/mxgraph/config/ShapeConfigurator.ts @@ -17,8 +17,7 @@ limitations under the License. import type { mxShape } from 'mxgraph'; import { ShapeBpmnElementKind } from '../../../model/bpmn/internal'; -import { mxgraph, mxCellRenderer, mxConstants, mxSvgCanvas2D } from '../initializer'; -import { computeAllBpmnClassNamesOfCell } from '../renderer/style-utils'; +import { mxCellRenderer, mxConstants, mxSvgCanvas2D } from '../initializer'; import { BusinessRuleTaskShape, CallActivityShape, @@ -83,7 +82,6 @@ const registerShapes = (): void => { export default class ShapeConfigurator { configureShapes(): void { this.initMxSvgCanvasPrototype(); - this.initMxShapePrototype(); registerShapes(); } @@ -132,50 +130,4 @@ export default class ShapeConfigurator { return css; }; } - - private initMxShapePrototype(): void { - // The following is copied from the mxgraph mxShape implementation then converted to TypeScript and enriched for bpmn-visualization - // It is needed for adding the custom attributes that permits identification of the BPMN elements in the DOM - mxgraph.mxShape.prototype.createSvgCanvas = function () { - const canvas = new mxSvgCanvas2D(this.node, false); - canvas.strokeTolerance = this.pointerEvents ? this.svgStrokeTolerance : 0; - canvas.pointerEventsValue = this.svgPointerEvents; - const off = this.getSvgScreenOffset(); - - if (off == 0) { - this.node.removeAttribute('transform'); - } else { - this.node.setAttribute('transform', 'translate(' + off + ',' + off + ')'); - } - - // START bpmn-visualization CUSTOMIZATION - // add attributes to be able to identify elements in DOM - if (this.state?.cell) { - // 'this.state.style' = the style definition associated with the cell - // 'this.state.cell.style' = the style applied to the cell: 1st element: style name = bpmn shape name - const cell = this.state.cell; - // dialect = strictHtml is set means that current node holds an HTML label - const allBpmnClassNames = computeAllBpmnClassNamesOfCell(cell, this.dialect === mxConstants.DIALECT_STRICTHTML); - const extraCssClasses = this.state.style[BpmnStyleIdentifier.EXTRA_CSS_CLASSES]; - if (typeof extraCssClasses == 'string') { - allBpmnClassNames.push(...extraCssClasses.split(',')); - } - - this.node.setAttribute('class', allBpmnClassNames.join(' ')); - this.node.dataset.bpmnId = this.state.cell.id; - } - // END bpmn-visualization CUSTOMIZATION - canvas.minStrokeWidth = this.minSvgStrokeWidth; - - if (!this.antiAlias) { - // Rounds all numbers in the SVG output to integers - canvas.format = function (value: string) { - // eslint-disable-next-line unicorn/prefer-number-properties -- mxGraph code - return Math.round(parseFloat(value)); - }; - } - - return canvas; - }; - } } diff --git a/src/component/mxgraph/shape/utils.ts b/src/component/mxgraph/shape/utils.ts new file mode 100644 index 0000000000..3684ae7592 --- /dev/null +++ b/src/component/mxgraph/shape/utils.ts @@ -0,0 +1,67 @@ +/* +Copyright 2024 Bonitasoft S.A. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import type { mxShape } from 'mxgraph'; + +import { mxConstants, mxSvgCanvas2D } from '../initializer'; +import { computeAllBpmnClassNamesOfCell } from '../renderer/style-utils'; +import { BpmnStyleIdentifier } from '../style'; + +export const overrideCreateSvgCanvas = function (shape: mxShape): void { + // The following is copied from the mxgraph mxShape implementation then converted to TypeScript and enriched for bpmn-visualization + // It is needed for adding the custom attributes that permits identification of the BPMN elements in the DOM + shape.createSvgCanvas = function () { + const canvas = new mxSvgCanvas2D(this.node, false); + canvas.strokeTolerance = this.pointerEvents ? this.svgStrokeTolerance : 0; + canvas.pointerEventsValue = this.svgPointerEvents; + const off = this.getSvgScreenOffset(); + + if (off == 0) { + this.node.removeAttribute('transform'); + } else { + this.node.setAttribute('transform', 'translate(' + off + ',' + off + ')'); + } + + // START bpmn-visualization CUSTOMIZATION + // add attributes to be able to identify elements in DOM + if (this.state?.cell) { + // 'this.state.style' = the style definition associated with the cell + // 'this.state.cell.style' = the style applied to the cell: 1st element: style name = bpmn shape name + const cell = this.state.cell; + // dialect = strictHtml is set means that current node holds an HTML label + const allBpmnClassNames = computeAllBpmnClassNamesOfCell(cell, this.dialect === mxConstants.DIALECT_STRICTHTML); + const extraCssClasses = this.state.style[BpmnStyleIdentifier.EXTRA_CSS_CLASSES]; + if (typeof extraCssClasses == 'string') { + allBpmnClassNames.push(...extraCssClasses.split(',')); + } + + this.node.setAttribute('class', allBpmnClassNames.join(' ')); + this.node.dataset.bpmnId = this.state.cell.id; + } + // END bpmn-visualization CUSTOMIZATION + canvas.minStrokeWidth = this.minSvgStrokeWidth; + + if (!this.antiAlias) { + // Rounds all numbers in the SVG output to integers + canvas.format = function (value: string) { + // eslint-disable-next-line unicorn/prefer-number-properties -- mxGraph code + return Math.round(parseFloat(value)); + }; + } + + return canvas; + }; +};