From f8a2ddadc93b73b41c79cd50e0643dccdd6677cb Mon Sep 17 00:00:00 2001
From: Niklas Kiefer <niklas.kiefer@camunda.com>
Date: Tue, 30 Oct 2018 13:05:33 +0100
Subject: [PATCH] feat(selection): add marker to related elements

wip


iu
---
 assets/diagram-js.css                         |  19 ++-
 lib/features/selection/HighlightRelated.js    |  79 ++++++++++
 lib/features/selection/index.js               |   6 +-
 .../selection/HighlightRelatedSpec.js         | 138 ++++++++++++++++++
 4 files changed, 239 insertions(+), 3 deletions(-)
 create mode 100644 lib/features/selection/HighlightRelated.js
 create mode 100644 test/spec/features/selection/HighlightRelatedSpec.js

diff --git a/assets/diagram-js.css b/assets/diagram-js.css
index 7c6f320dd..62e69f65b 100644
--- a/assets/diagram-js.css
+++ b/assets/diagram-js.css
@@ -8,7 +8,9 @@
 }
 
 .djs-element.hover .djs-outline,
-.djs-element.selected .djs-outline {
+.djs-element.selected .djs-outline,
+.djs-element.related-selected .djs-outline, 
+.djs-element.related-hover .djs-outline {
   visibility: visible;
   shape-rendering: crispEdges;
   stroke-dasharray: 3,3;
@@ -16,14 +18,29 @@
 
 .djs-element.selected .djs-outline {
   stroke: #8888FF;
+  opacity: 1;
   stroke-width: 1px;
 }
 
 .djs-element.hover .djs-outline {
+  stroke: #FF8888 !important;
+  opacity: 1 !important;
+  stroke-width: 1px;
+}
+
+.djs-element.related-selected .djs-outline {
+  stroke: #8888FF;
+  opacity: 0.5;
+  stroke-width: 1px;
+}
+
+.djs-element.related-hover .djs-outline {
   stroke: #FF8888;
+  opacity: 0.5;
   stroke-width: 1px;
 }
 
+
 .djs-shape.connect-ok .djs-visual > :nth-child(1) {
   fill: #DCFECC /* light-green */ !important;
 }
diff --git a/lib/features/selection/HighlightRelated.js b/lib/features/selection/HighlightRelated.js
new file mode 100644
index 000000000..347dfe8f9
--- /dev/null
+++ b/lib/features/selection/HighlightRelated.js
@@ -0,0 +1,79 @@
+import {
+  forEach
+} from 'min-dash';
+
+import {
+  getType
+} from '../../util/Elements';
+
+var MARKER_RELATED_SELECTED = 'related-selected',
+    MARKER_RELATED_HOVER = 'related-hover';
+
+
+/**
+   * A plugin that adds a visible selection UI to related elements after an element
+   * was selected by appending the <code>related-selected</code> and
+   * <code>related-hover</code> classes to them.
+   *
+   * @class
+   *
+   * Creates outline on related elements after selecting an element
+   *
+   * @param {EventBus} events
+   * @param {Canvas} canvas
+   */
+export default function HighlightRelated(events, canvas) {
+
+  function applyToRelatedElements(e, cls, fn) {
+
+    // shape, connection -> mark related labels
+    if (getType(e) === 'shape' || getType(e) === 'connection') {
+      forEach(e.labels, function(label) {
+        fn(label, cls);
+      });
+    }
+
+    // label -> mark related shape, connection
+    if (e.labelTarget) {
+      fn(e.labelTarget, cls);
+    }
+  }
+
+  function addMarkerToRelated(e, cls) {
+    applyToRelatedElements(e, cls, canvas.addMarker.bind(canvas));
+  }
+
+  function removeMarkerFromRelated(e, cls) {
+    applyToRelatedElements(e, cls, canvas.removeMarker.bind(canvas));
+  }
+
+  events.on('element.hover', function(event) {
+    addMarkerToRelated(event.element, MARKER_RELATED_HOVER);
+  });
+
+  events.on('element.out', function(event) {
+    removeMarkerFromRelated(event.element, MARKER_RELATED_HOVER);
+  });
+
+  events.on('selection.changed', function(event) {
+    var oldSelection = event.oldSelection,
+        newSelection = event.newSelection;
+
+    forEach(oldSelection, function(e) {
+      if (newSelection.indexOf(e) === -1) {
+        removeMarkerFromRelated(e, MARKER_RELATED_SELECTED);
+      }
+    });
+
+    forEach(newSelection, function(e) {
+      if (oldSelection.indexOf(e) === -1) {
+        addMarkerToRelated(e, MARKER_RELATED_SELECTED);
+      }
+    });
+  });
+}
+
+HighlightRelated.$inject = [
+  'eventBus',
+  'canvas',
+];
\ No newline at end of file
diff --git a/lib/features/selection/index.js b/lib/features/selection/index.js
index 0ce1223e6..37071349d 100644
--- a/lib/features/selection/index.js
+++ b/lib/features/selection/index.js
@@ -4,15 +4,17 @@ import OutlineModule from '../outline';
 import Selection from './Selection';
 import SelectionVisuals from './SelectionVisuals';
 import SelectionBehavior from './SelectionBehavior';
+import HighlightRelated from './HighlightRelated';
 
 
 export default {
-  __init__: [ 'selectionVisuals', 'selectionBehavior' ],
+  __init__: [ 'selectionVisuals', 'selectionBehavior', 'highlightRelated' ],
   __depends__: [
     InteractionEventsModule,
     OutlineModule
   ],
   selection: [ 'type', Selection ],
   selectionVisuals: [ 'type', SelectionVisuals ],
-  selectionBehavior: [ 'type', SelectionBehavior ]
+  selectionBehavior: [ 'type', SelectionBehavior ],
+  highlightRelated: [ 'type', HighlightRelated ]
 };
diff --git a/test/spec/features/selection/HighlightRelatedSpec.js b/test/spec/features/selection/HighlightRelatedSpec.js
new file mode 100644
index 000000000..6ee99cf4b
--- /dev/null
+++ b/test/spec/features/selection/HighlightRelatedSpec.js
@@ -0,0 +1,138 @@
+import {
+  bootstrapDiagram,
+  inject
+} from 'test/TestHelper';
+
+import selectionModule from 'lib/features/selection';
+
+import {
+  matches
+} from 'min-dom';
+
+
+describe('features/selection/HighlightRelated', function() {
+
+  beforeEach(bootstrapDiagram({ modules: [ selectionModule ] }));
+
+  describe('bootstrap', function() {
+
+    beforeEach(bootstrapDiagram({ modules: [ selectionModule ] }));
+
+    it('should bootstrap diagram with component', inject(function() {
+
+    }));
+
+  });
+
+  describe('selection box on related elements', function() {
+
+    var shape, shape2, connection, label, label2;
+
+    beforeEach(inject(function(elementFactory, canvas) {
+
+      // given
+      shape = elementFactory.createShape({
+        id: 'child',
+        x: 100, y: 100, width: 100, height: 100
+      });
+
+      canvas.addShape(shape);
+
+      shape2 = elementFactory.createShape({
+        id: 'child2',
+        x: 300, y: 100, width: 100, height: 100
+      });
+
+      canvas.addShape(shape2);
+
+      connection = elementFactory.createConnection({
+        id: 'connection',
+        waypoints: [ { x: 150, y: 150 }, { x: 150, y: 200 }, { x: 350, y: 150 } ],
+        source: shape,
+        target: shape2
+      });
+
+      canvas.addConnection(connection);
+
+      label = elementFactory.createLabel({
+        id: 'label',
+        x: 100, y: 200, width: 20, height: 20,
+        labelTarget: shape
+      });
+
+      canvas.addShape(label);
+
+      label2 = elementFactory.createLabel({
+        id: 'label2',
+        x: 200, y: 200, width: 20, height: 20,
+        labelTarget: connection
+      });
+
+      canvas.addShape(label2);
+    }));
+
+    describe('shapes', function() {
+
+      it('should show box on related label on select',
+        inject(function(selection, canvas) {
+
+          // when
+          selection.select(shape);
+
+          // then
+          var gfx = canvas.getGraphics(label),
+              hasOutline = matches(gfx, '.related-selected');
+
+          expect(hasOutline).to.be.true;
+        }));
+
+
+      it('should show box on shape on selecting label',
+        inject(function(selection, canvas) {
+
+          // when
+          selection.select(label);
+
+          // then
+          var gfx = canvas.getGraphics(shape),
+              hasOutline = matches(gfx, '.related-selected');
+
+          expect(hasOutline).to.be.true;
+        }));
+    });
+
+
+    describe('connection', function() {
+
+      it('should show box on related label on select',
+        inject(function(selection, canvas) {
+
+          // when
+          selection.select(connection);
+
+          // then
+          var gfx = canvas.getGraphics(label2),
+              hasOutline = matches(gfx, '.related-selected');
+
+          expect(hasOutline).to.be.true;
+        }));
+
+
+      it('should paler box on connection on selecting label',
+        inject(function(selection, canvas) {
+
+          // when
+          selection.select(label2);
+
+          // then
+          var gfx = canvas.getGraphics(connection),
+              hasOutline = matches(gfx, '.related-selected');
+
+          expect(hasOutline).to.be.true;
+        }));
+
+    });
+
+  });
+
+});