From 3c7dc1a4a58ee37e91003623d91069d4a61909a8 Mon Sep 17 00:00:00 2001 From: Beatriz Mendes Date: Tue, 31 Oct 2023 21:12:16 +0100 Subject: [PATCH] feat: custom selection outline Closes #799 --- packages/dmn-js-drd/src/Modeler.js | 2 + .../src/features/outline/OutlineProvider.js | 125 ++++++++++++++++++ .../src/features/outline/OutlineUtil.js | 50 +++++++ .../dmn-js-drd/src/features/outline/index.js | 10 ++ .../spec/features/outline/OutlineProvider.dmn | 32 +++++ .../features/outline/OutlineProviderSpec.js | 76 +++++++++++ 6 files changed, 295 insertions(+) create mode 100644 packages/dmn-js-drd/src/features/outline/OutlineProvider.js create mode 100644 packages/dmn-js-drd/src/features/outline/OutlineUtil.js create mode 100644 packages/dmn-js-drd/src/features/outline/index.js create mode 100644 packages/dmn-js-drd/test/spec/features/outline/OutlineProvider.dmn create mode 100644 packages/dmn-js-drd/test/spec/features/outline/OutlineProviderSpec.js diff --git a/packages/dmn-js-drd/src/Modeler.js b/packages/dmn-js-drd/src/Modeler.js index 3b91d0191..db823eb79 100644 --- a/packages/dmn-js-drd/src/Modeler.js +++ b/packages/dmn-js-drd/src/Modeler.js @@ -19,6 +19,7 @@ import KeyboardMoveSelectionModule from 'diagram-js/lib/features/keyboard-move-s import LabelEditingModule from './features/label-editing'; import ModelingModule from './features/modeling'; import MoveModule from 'diagram-js/lib/features/move'; +import OutlineProvider from './features/outline'; import PaletteModule from './features/palette'; import ResizeModule from 'diagram-js/lib/features/resize'; import SnappingModule from './features/snapping'; @@ -131,6 +132,7 @@ Modeler.prototype._modelingModules = [ LabelEditingModule, ModelingModule, MoveModule, + OutlineProvider, PaletteModule, ResizeModule, SnappingModule diff --git a/packages/dmn-js-drd/src/features/outline/OutlineProvider.js b/packages/dmn-js-drd/src/features/outline/OutlineProvider.js new file mode 100644 index 000000000..5a53a1ed0 --- /dev/null +++ b/packages/dmn-js-drd/src/features/outline/OutlineProvider.js @@ -0,0 +1,125 @@ +import { assign } from 'min-dash'; + +import { + attr as svgAttr, + create as svgCreate +} from 'tiny-svg'; + +import { + is, + isAny +} from 'dmn-js-shared/lib/util/ModelUtil'; + +import { + BUSINESS_KNOWLEDGE_MODEL_OUTLINE_PATH, + KNOWLEDGE_SOURCE_OUTLINE_PATH, + BUSINESS_KNOWLEDGE_MODEL_STANDARD_SIZE, + KNOWLEDGE_SOURCE_STANDARD_SIZE, + createPath +} from './OutlineUtil'; + +const DEFAULT_OFFSET = 5; + +/** + * DMN-specific outline provider. + * + * @implements {BaseOutlineProvider} + * + * @param {Outline} outline + * @param {Styles} styles + */ +export default function OutlineProvider(outline, styles) { + + this._styles = styles; + + outline.registerProvider(this); +} + +OutlineProvider.$inject = [ + 'outline', + 'styles' +]; + +/** + * Returns outline for a given element. + * + * @param {Element} element + * + * @return {Outline} + */ +OutlineProvider.prototype.getOutline = function(element) { + + const OUTLINE_STYLE = this._styles.cls('djs-outline', [ 'no-fill' ]); + + var outline; + + if (is(element, 'dmn:InputData')) { + outline = svgCreate('rect'); + + svgAttr(outline, assign({ + x: -DEFAULT_OFFSET, + y: -DEFAULT_OFFSET, + rx: 27, + width: element.width + DEFAULT_OFFSET * 2, + height: element.height + DEFAULT_OFFSET * 2, + }, OUTLINE_STYLE)); + + } else if ( + is(element, 'dmn:BusinessKnowledgeModel') + && isStandardSize(element, 'dmn:BusinessKnowledgeModel') + ) { + outline = createPath( + BUSINESS_KNOWLEDGE_MODEL_OUTLINE_PATH, + { x: -6, y:-6 }, + OUTLINE_STYLE + ); + + } else if ( + is(element, 'dmn:KnowledgeSource') + && isStandardSize(element, 'dmn:KnowledgeSource')) { + outline = createPath( + KNOWLEDGE_SOURCE_OUTLINE_PATH, + { x: -6, y:-1.5 }, + OUTLINE_STYLE + ); + } + + return outline; +}; + +/** + * Updates the outline for a given element. + * Returns true if the update for the given element was handled by this provider. + * + * @param {Element} element + * @param {Outline} outline + * @returns {boolean} + */ +OutlineProvider.prototype.updateOutline = function(element) { + + if (isAny(element, [ + 'dmn:InputData', + 'dmn:BusinessKnowledgeModel', + 'dmn:KnowledgeSource' + ])) { + return true; + } + + return false; +}; + + +// helpers ////////// + +function isStandardSize(element, type) { + var standardSize; + + if (type === 'dmn:BusinessKnowledgeModel') { + standardSize = BUSINESS_KNOWLEDGE_MODEL_STANDARD_SIZE; + } else if (type === 'dmn:KnowledgeSource') { + standardSize = KNOWLEDGE_SOURCE_STANDARD_SIZE; + } + + return element.width === standardSize.width + && element.height === standardSize.height; +} \ No newline at end of file diff --git a/packages/dmn-js-drd/src/features/outline/OutlineUtil.js b/packages/dmn-js-drd/src/features/outline/OutlineUtil.js new file mode 100644 index 000000000..52f18501a --- /dev/null +++ b/packages/dmn-js-drd/src/features/outline/OutlineUtil.js @@ -0,0 +1,50 @@ +import { + create as svgCreate +} from 'tiny-svg'; + +export const BUSINESS_KNOWLEDGE_MODEL_OUTLINE_PATH = ` +M2.45221 16.0068L18.7175 1.74436L18.0615 0.996331L18.7175 1.74436C19.2646 1.26455 19.9676 +1 20.6953 1H140.926C142.583 1 143.926 2.34295 143.926 3.99967L143.93 37.5457C143.93 +38.3668 143.593 39.152 142.999 39.7183L143.689 40.4424L142.999 39.7183L127.299 +54.6723C126.741 55.2036 126 55.5 125.23 55.5H4.53787C2.88599 55.5 1.54491 54.1646 1.5379 +52.5127L1.43066 27.229L1.43065 27.2248L1.4301 18.2626C1.43005 +17.3986 1.80255 16.5765 2.45221 16.0068Z +`; + + +export const KNOWLEDGE_SOURCE_OUTLINE_PATH = ` +M1.79494 63.0032L1.2941 63.6423L1.79493 63.0032C1.71778 62.9427 1.6428 62.8741 1.57288 +62.8L1.01736 63.3241L1.57288 62.8C1.20495 62.41 1 61.8941 1 61.3579V2.65067C1 1.74066 +1.73867 1.00345 2.64868 1.00526L108.006 1.2145C109.66 1.21778 111 2.55996 111 +4.21449V21.7015V61.6762C111 63.3188 109.679 64.656 108.037 64.676L107.06 64.6879C106.66 +64.6927 106.26 64.6165 105.89 64.4652C105.04 64.1176 104.222 63.78 103.43 +63.4531C93.4866 59.3517 87.6891 56.9603 76.3991 58.1057C71.7035 58.5821 66.8747 60.74 +61.5721 63.1744C61.3243 63.2881 61.0753 63.4026 60.825 63.5177C55.6712 65.8868 49.9696 +68.5077 43.2365 70.3468C31.101 73.6617 15.2135 73.5179 1.79494 63.0032Z +`; + +/** + * @type {Dimensions} + */ +export const BUSINESS_KNOWLEDGE_MODEL_STANDARD_SIZE = { width: 135, height: 46 }; + +/** + * @type {Dimensions} + */ +export const KNOWLEDGE_SOURCE_STANDARD_SIZE = { width: 100, height: 63 }; + +/** + * Create a path element with given attributes. + * @param {string} path + * @param {Object} attrs + * @param {Object} OUTLINE_STYLE + * @return {SVGElement} + */ +export function createPath(path, attrs, OUTLINE_STYLE) { + return svgCreate('path', { + d: path, + strokeWidth: 2, + transform: `translate(${attrs.x}, ${attrs.y})`, + ...OUTLINE_STYLE + }); +} \ No newline at end of file diff --git a/packages/dmn-js-drd/src/features/outline/index.js b/packages/dmn-js-drd/src/features/outline/index.js new file mode 100644 index 000000000..8a018a32e --- /dev/null +++ b/packages/dmn-js-drd/src/features/outline/index.js @@ -0,0 +1,10 @@ +import Outline from 'diagram-js/lib/features/outline'; +import OutlineProvider from './OutlineProvider'; + +export default { + __depends__: [ + Outline + ], + __init__: [ 'outlineProvider' ], + outlineProvider: [ 'type', OutlineProvider ] +}; \ No newline at end of file diff --git a/packages/dmn-js-drd/test/spec/features/outline/OutlineProvider.dmn b/packages/dmn-js-drd/test/spec/features/outline/OutlineProvider.dmn new file mode 100644 index 000000000..54de48f37 --- /dev/null +++ b/packages/dmn-js-drd/test/spec/features/outline/OutlineProvider.dmn @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/dmn-js-drd/test/spec/features/outline/OutlineProviderSpec.js b/packages/dmn-js-drd/test/spec/features/outline/OutlineProviderSpec.js new file mode 100644 index 000000000..5efbabace --- /dev/null +++ b/packages/dmn-js-drd/test/spec/features/outline/OutlineProviderSpec.js @@ -0,0 +1,76 @@ +import { + bootstrapModeler, + inject +} from 'test/TestHelper'; + +import coreModule from 'src/core'; +import modelingModule from 'src/features/modeling'; +import outlineProviderModule from 'src/features/outline'; + +import diagramXml from './OutlineProvider.dmn'; + +import { + KNOWLEDGE_SOURCE_OUTLINE_PATH, + BUSINESS_KNOWLEDGE_MODEL_OUTLINE_PATH +} from 'src/features/outline/OutlineUtil'; + + +describe('features/outline - outline provider', function() { + var testModules = [ + coreModule, + modelingModule, + outlineProviderModule + ]; + + + beforeEach(bootstrapModeler(diagramXml, { modules: testModules })); + + describe('should provide outline for', function() { + + it('input data', inject(function(elementRegistry, outline) { + + // given + var inputData = elementRegistry.get('InputData'); + + // when + var outlineShape = outline.getOutline(inputData); + + // then + expect(outlineShape).to.exist; + expect(outlineShape.tagName).to.eql('rect'); + })); + + + it('knowledge source', inject(function(elementRegistry, outline) { + + // given + var knowledgeSource = elementRegistry.get('KnowledgeSource'); + + // when + var outlineShape = outline.getOutline(knowledgeSource); + + // then + expect(outlineShape).to.exist; + expect(outlineShape.tagName).to.eql('path'); + expect(outlineShape.getAttribute('d')).to.eql(KNOWLEDGE_SOURCE_OUTLINE_PATH); + })); + + + it('business knowledge model', inject(function(elementRegistry, outline) { + + // given + var businessKnowledgeModel = elementRegistry.get('BusinessKnowledgeModel'); + + // when + var outlineShape = outline.getOutline(businessKnowledgeModel); + + // then + expect(outlineShape).to.exist; + expect(outlineShape.tagName).to.eql('path'); + expect(outlineShape.getAttribute('d')) + .to.eql(BUSINESS_KNOWLEDGE_MODEL_OUTLINE_PATH); + })); + + }); + +}); \ No newline at end of file