From b335eee492af1a1eb8ed5d4aa79356861dce1c05 Mon Sep 17 00:00:00 2001 From: Beatriz Mendes Date: Mon, 23 Oct 2023 14:40:12 +0200 Subject: [PATCH 1/2] feat: remove selection outline --- lib/features/outline/Outline.js | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/lib/features/outline/Outline.js b/lib/features/outline/Outline.js index 7320fdc9f..704ddf497 100644 --- a/lib/features/outline/Outline.js +++ b/lib/features/outline/Outline.js @@ -71,18 +71,6 @@ export default function Outline(eventBus, styles) { self.updateShapeOutline(outline, element); }); - eventBus.on([ 'connection.added', 'connection.changed' ], function(event) { - var element = event.element, - gfx = event.gfx; - - var outline = domQuery('.djs-outline', gfx); - - if (!outline) { - outline = createOutline(gfx, element); - } - - self.updateConnectionOutline(outline, element); - }); } From f776adebf3d8b6dcaac7ae2e4620b1d6f5a01839 Mon Sep 17 00:00:00 2001 From: Beatriz Mendes Date: Wed, 25 Oct 2023 11:47:50 +0200 Subject: [PATCH 2/2] feat: support outline providers Related to https://github.com/bpmn-io/bpmn-js/issues/1996 --- lib/features/outline/Outline.js | 110 ++++++++++++----- lib/features/outline/OutlineProvider.spec.ts | 34 ++++++ lib/features/outline/OutlineProvider.ts | 61 ++++++++++ test/spec/features/outline/OutlineSpec.js | 114 ++++++++++++++++-- .../selection/SelectionVisualsSpec.js | 4 +- 5 files changed, 276 insertions(+), 47 deletions(-) create mode 100644 lib/features/outline/OutlineProvider.spec.ts create mode 100644 lib/features/outline/OutlineProvider.ts diff --git a/lib/features/outline/Outline.js b/lib/features/outline/Outline.js index 704ddf497..b4d6a743d 100644 --- a/lib/features/outline/Outline.js +++ b/lib/features/outline/Outline.js @@ -1,5 +1,3 @@ -import { getBBox } from '../../util/Elements'; - var LOW_PRIORITY = 500; import { @@ -13,9 +11,13 @@ import { } from 'min-dom'; import { - assign + assign, + forEach, + isFunction } from 'min-dash'; +var DEFAULT_PRIORITY = 1000; + /** * @typedef {import('../../model/Types').Element} Element * @@ -34,25 +36,25 @@ import { */ export default function Outline(eventBus, styles) { - this.offset = 6; + this._eventBus = eventBus; + + this.offset = 5; var OUTLINE_STYLE = styles.cls('djs-outline', [ 'no-fill' ]); var self = this; - function createOutline(gfx, bounds) { + function createOutline(gfx, element) { var outline = svgCreate('rect'); svgAttr(outline, assign({ - x: 10, - y: 10, + x: - self.offset, + y: - self.offset, rx: 4, - width: 100, - height: 100 + width: element.width + self.offset * 2, + height: element.height + self.offset * 2 }, OUTLINE_STYLE)); - svgAppend(gfx, outline); - return outline; } @@ -65,10 +67,12 @@ export default function Outline(eventBus, styles) { var outline = domQuery('.djs-outline', gfx); if (!outline) { - outline = createOutline(gfx, element); - } + outline = self.getOutline(element) || createOutline(gfx, element); + svgAppend(gfx, outline); - self.updateShapeOutline(outline, element); + } else { + self.updateShapeOutline(outline, element); + } }); } @@ -83,35 +87,77 @@ export default function Outline(eventBus, styles) { */ Outline.prototype.updateShapeOutline = function(outline, element) { - svgAttr(outline, { - x: -this.offset, - y: -this.offset, - width: element.width + this.offset * 2, - height: element.height + this.offset * 2 - }); + var updated = false; + var providers = this._getProviders(); -}; + if (providers.length) { + forEach(providers, function(provider) { + updated = updated || provider.updateOutline(element, outline); + }); + } + if (!updated) { + svgAttr(outline, { + x: -this.offset, + y: -this.offset, + width: element.width + this.offset * 2, + height: element.height + this.offset * 2 + }); + } +}; /** - * Updates the outline of a connection respecting the bounding box of - * the connection and an outline offset. + * Register an outline provider with the given priority. * - * @param {SVGElement} outline - * @param {Element} connection + * @param {number} priority + * @param {OutlineProvider} provider */ -Outline.prototype.updateConnectionOutline = function(outline, connection) { +Outline.prototype.registerProvider = function(priority, provider) { + if (!provider) { + provider = priority; + priority = DEFAULT_PRIORITY; + } - var bbox = getBBox(connection); + this._eventBus.on('outline.getProviders', priority, function(event) { + event.providers.push(provider); + }); +}; - svgAttr(outline, { - x: bbox.x - this.offset, - y: bbox.y - this.offset, - width: bbox.width + this.offset * 2, - height: bbox.height + this.offset * 2 +/** + * Returns the registered outline providers. + * + * @returns {OutlineProvider[]} + */ +Outline.prototype._getProviders = function() { + var event = this._eventBus.createEvent({ + type: 'outline.getProviders', + providers: [] }); + this._eventBus.fire(event); + + return event.providers; }; +/** + * Returns the outline for an element. + * + * @param {Element} element + **/ +Outline.prototype.getOutline = function(element) { + var outline; + var providers = this._getProviders(); + + forEach(providers, function(provider) { + + if (!isFunction(provider.getOutline)) { + return; + } + + outline = outline || provider.getOutline(element); + }); + + return outline; +}; Outline.$inject = [ 'eventBus', 'styles', 'elementRegistry' ]; \ No newline at end of file diff --git a/lib/features/outline/OutlineProvider.spec.ts b/lib/features/outline/OutlineProvider.spec.ts new file mode 100644 index 000000000..c9260c277 --- /dev/null +++ b/lib/features/outline/OutlineProvider.spec.ts @@ -0,0 +1,34 @@ +import { + attr as svgAttr, + create as svgCreate +} from 'tiny-svg'; + +import { Element } from '../../model/Types'; + +import OutlineProvider, { Outline } from './OutlineProvider'; + +export class FooOutlineProvider implements OutlineProvider { + getOutline(element: Element): Outline { + const outline = svgCreate('circle'); + + svgAttr(outline, { + cx: element.width / 2, + cy: element.height / 2, + r: 23 + }); + + return outline; + } + + updateOutline(element: Element, outline: Outline): boolean { + if (element.type === 'Foo') { + svgAttr(outline, { + cx: element.width / 2, + cy: element.height / 2, + r: 23 + }); + } + + return false; + } +} diff --git a/lib/features/outline/OutlineProvider.ts b/lib/features/outline/OutlineProvider.ts new file mode 100644 index 000000000..76a6fa2a2 --- /dev/null +++ b/lib/features/outline/OutlineProvider.ts @@ -0,0 +1,61 @@ +import { Element } from '../../model/Types'; + +export type Outline = SVGSVGElement | SVGCircleElement | SVGRectElement; + +/** + * An interface to be implemented by an outline provider. + */ +export default interface OutlineProvider { + + /** + * Returns outline shape for given element. + * + * @example + * + * ```javascript + * getOutline(element) { + * if (element.type === 'Foo') { + * const outline = svgCreate('circle'); + * + * svgAttr(outline, { + * cx: element.width / 2, + * cy: element.height / 2, + * r: 23 + * }); + * + * return outline; + * } + * } + * ``` + * + * @param element + */ + getOutline: (element: Element) => Outline; + + /** + * Updates outline shape based on element's current size. Returns true if + * update was handled by provider. + * + * @example + * + * ```javascript + * updateOutline(element, outline) { + * if (element.type === 'Foo') { + * svgAttr(outline, { + * cx: element.width / 2, + * cy: element.height / 2, + * r: 23 + * }); + * } else if (element.type === 'Bar') { + * return true; + * } + * + * return false; + * } + * ``` + * + * @param element + * @param outline + */ + updateOutline: (element: Element, outline: Outline) => boolean; +} diff --git a/test/spec/features/outline/OutlineSpec.js b/test/spec/features/outline/OutlineSpec.js index 4e5b9a06e..8466ab922 100755 --- a/test/spec/features/outline/OutlineSpec.js +++ b/test/spec/features/outline/OutlineSpec.js @@ -10,7 +10,8 @@ import { } from 'min-dom'; import { - classes as svgClasses + classes as svgClasses, + create as svgCreate } from 'tiny-svg'; @@ -22,7 +23,6 @@ describe('features/outline/Outline', function() { expect(outline).to.exist; expect(outline.updateShapeOutline).to.exist; - expect(outline.updateConnectionOutline).to.exist; })); @@ -51,7 +51,7 @@ describe('features/outline/Outline', function() { })); - it('should add outline to connection', inject(function(selection, canvas, elementRegistry) { + it('should not add outline to connection', inject(function(selection, canvas, elementRegistry) { // given var connection = canvas.addConnection({ id: 'select1', waypoints: [ { x: 25, y: 25 }, { x: 115, y: 115 } ] }); @@ -63,7 +63,7 @@ describe('features/outline/Outline', function() { var gfx = elementRegistry.getGraphics(connection); var outline = domQuery('.djs-outline', gfx); - expect(outline).to.exist; + expect(outline).to.not.exist; expect(svgClasses(gfx).has('selected')).to.be.true; // Outline class is set })); @@ -94,25 +94,113 @@ describe('features/outline/Outline', function() { expect(outline).to.exist; expect(svgClasses(gfx).has('selected')).to.be.false; // Outline class is not set })); + }); + + + describe('providers', function() { + class Provider { + getOutline(element) { + if (element === 'A') { + return svgCreate('circle'); + } else if (element === 'B') { + return svgCreate('rect'); + } + } + } - it('should remove outline class from connection', inject(function(selection, canvas, elementRegistry) { + it('should register provider', inject(function(outline) { // given - var connection = canvas.addConnection({ id: 'select3', waypoints: [ { x: 25, y: 25 }, { x: 115, y: 115 } ] }); + var provider = new Provider(); // when - selection.select(connection); - selection.deselect(connection); + outline.registerProvider(provider); // then - var gfx = elementRegistry.getGraphics(connection); - var outline = domQuery('.djs-outline', gfx); + expect(function() { + outline.registerProvider(provider); + }).not.to.throw; + })); - expect(outline).to.exist; - expect(svgClasses(gfx).has('selected')).to.be.false; // Outline class is not set + + it('should get outline', inject(function(outline) { + + // given + var provider = new Provider(); + + outline.registerProvider(provider); + + // when + var outlineElement = outline.getOutline('A'); + + // then + expect(outlineElement).to.exist; + expect(outlineElement.tagName).to.equal('circle'); })); - }); + it('missing provider API', inject(function(outline) { + + // given + var provider = {}; + + // when + outline.registerProvider(provider); + + // then + expect(outline.getOutline('FOO')).to.be.undefined; + })); + + + it('should set default outline if not provided', inject(function(outline, canvas, selection, elementRegistry) { + + // given + var provider = new Provider(); + outline.registerProvider(provider); + + var shape = canvas.addShape({ + id: 'test', + x: 10, + y: 10, + width: 100, + height: 100 + }); + + // when + selection.select(shape); + + // then + expect(outline.getOutline(shape)).to.not.exist; + + var gfx = elementRegistry.getGraphics(shape); + var outlineShape = domQuery('.djs-outline', gfx); + expect(outlineShape).to.exist; + expect(svgClasses(gfx).has('selected')).to.be.true; + })); + + + it('multiple providers', inject(function(outline, canvas, selection, elementRegistry) { + class OtherProvider { + getOutline(element) { + if (element === 'A') { + return svgCreate('rect'); + } + } + } + + // given + var provider = new Provider(); + var otherProvider = new OtherProvider(); + + outline.registerProvider(500, provider); + outline.registerProvider(1500, otherProvider); + + // when + var outlineElement = outline.getOutline('A'); + + // then + expect(outlineElement.tagName).to.equal('rect'); + })); + }); }); diff --git a/test/spec/features/selection/SelectionVisualsSpec.js b/test/spec/features/selection/SelectionVisualsSpec.js index dbd0774f9..a105ed7c5 100755 --- a/test/spec/features/selection/SelectionVisualsSpec.js +++ b/test/spec/features/selection/SelectionVisualsSpec.js @@ -69,10 +69,10 @@ describe('features/selection/SelectionVisuals', function() { it('should show box on select', inject(function(selection, canvas) { // when - selection.select(connection); + selection.select(shape); // then - var gfx = canvas.getGraphics(connection), + var gfx = canvas.getGraphics(shape), outline = domQuery('.djs-outline', gfx); expect(outline).to.exist;