On 06/13/2016 10:48 AM, Pavel Vomacka wrote:
Hello,

please review attached patches which extend topology graph functionality. First two add possibility to create agreement using mouse and the third one adds 'Autogenerated' placeholder.

0047,48: https://fedorahosted.org/freeipa/ticket/5648

0050: https://fedorahosted.org/freeipa/ticket/5867

--

Pavel^3 Vomacka



Updated patch 48 attached. Added FontAwesome to css style of plus icon and set lower stabilization timeout.

--
Pavel^3 Vomacka
From 31c22e520df96a357cec56d2d809889f18cefe6f Mon Sep 17 00:00:00 2001
From: Pavel Vomacka <pvoma...@redhat.com>
Date: Mon, 13 Jun 2016 10:21:25 +0200
Subject: [PATCH 1/3] Add creating a segment using mouse

Create new semicircles around the node after mouseover. These work as buttons
to create arrow and after clicking on another node the Add topology segment dialog
is opened. Also selecting segment works, if the segment already exists then
the segment is selected instead of opening the dialog.

https://fedorahosted.org/freeipa/ticket/5648
---
 install/ui/less/widgets.less             |  59 ++++--
 install/ui/src/freeipa/topology_graph.js | 347 ++++++++++++++++++++++++++++++-
 2 files changed, 382 insertions(+), 24 deletions(-)

diff --git a/install/ui/less/widgets.less b/install/ui/less/widgets.less
index 0f9bc8c171d5c35d955c6b55d2e95ee105c35159..686131d3222da556dffb91870f179f8270ff1419 100644
--- a/install/ui/less/widgets.less
+++ b/install/ui/less/widgets.less
@@ -150,41 +150,60 @@ tbody:empty { display: none; }
 
 .topology-view {
     svg {
-      background-color: #FFF;
-      cursor: default;
-      -webkit-user-select: none;
-      -moz-user-select: none;
-      -ms-user-select: none;
-      -o-user-select: none;
-      user-select: none;
+        background-color: #FFF;
+        cursor: default;
+        -webkit-user-select: none;
+        -moz-user-select: none;
+        -ms-user-select: none;
+        -o-user-select: none;
+        user-select: none;
     }
 
+
     path.link {
-      fill: none;
-      stroke-width: 4px;
-      cursor: pointer;
+        fill: none;
+        stroke: #000;
+        stroke-width: 4px;
+        cursor: pointer;
+        .dragline {
+            pointer-events: none;
+        }
+    }
+
+    .plus {
+        font-size: .9em;
+        font-family: FontAwesome;
+        fill: #fff;
+    }
+
+    .adder_label {
+        font-weight: bold;
+    }
+
+    path.adder {
+        cursor: pointer;
+    }
+
+    .selected {
+        stroke-dasharray: 10,2;
     }
 
     .marker {
         stroke: rgba(0, 0, 0);
     }
 
-    path.link.selected {
-      stroke-dasharray: 10,2;
-    }
-
     circle.node {
-      stroke-width: 1.5px;
-      cursor: pointer;
+        stroke-width: 1.5px;
+        cursor: pointer;
     }
 
     text {
-      font: 16px sans-serif;
-      pointer-events: none;
+        font: 16px sans-serif;
+        pointer-events: none;
     }
 
     text.id {
-      text-anchor: middle;
-      font-weight: bold;
+        text-anchor: middle;
+        font-weight: bold;
     }
 }
diff --git a/install/ui/src/freeipa/topology_graph.js b/install/ui/src/freeipa/topology_graph.js
index 332411f943770a8e1833b698ee28026e2382bb76..ce2ebeaff611987ae27f2655b5da80bdcd1b4f8a 100644
--- a/install/ui/src/freeipa/topology_graph.js
+++ b/install/ui/src/freeipa/topology_graph.js
@@ -29,13 +29,21 @@ var topology_graph = {
 topology_graph.TopoGraph = declare([Evented], {
     width: 960,
     height: 500,
+    _adder_anim_duration: 200,
+    _adder_inner_radius: 15,
+    _adder_outer_radius: 30,
     _colors: d3.scale.category10(),
     _svg : null,
     _path: null,
     _circle: null,
 
+    _create_agreement: null,
     _selected_link: null,
     _mousedown_link: null,
+    _source_node: null,
+    _source_node_html: null,
+    _target_node: null,
+    _drag_line: null,
 
     /**
      * Nodes - IPA servers
@@ -115,31 +123,89 @@ topology_graph.TopoGraph = declare([Evented], {
                 node.x = Number(this._get_local_storage_attr(node.id, 'x'));
                 node.y = Number(this._get_local_storage_attr(node.id, 'y'));
             }
+            node.ca_adder = d3.svg.arc()
+                .innerRadius(this._adder_inner_radius)
+                .outerRadius(this._adder_inner_radius)
+                .startAngle(2 * (Math.PI/180))
+                .endAngle(178 * (Math.PI/180));
+
+            node.domain_adder = d3.svg.arc()
+                .innerRadius(this._adder_inner_radius)
+                .outerRadius(this._adder_inner_radius)
+                .startAngle(182 * (Math.PI/180))
+                .endAngle(358 * (Math.PI/180));
+
+            node.drag_mode = false;
         }
 
         this._init_layout();
         this._define_shapes();
 
         // handles to link and node element groups
+        // the order of adding shapes is important because of order of showing
+        // them
         this._path = this._svg.append('svg:g')
                               .classed('shapes', true)
                               .selectAll('path');
 
+        this._drag_line = this._svg.append('svg:g')
+            .classed('shapes', true)
+            .append('path')
+            .style('marker-end', 'url(#end-arrow)')
+            .attr('class', 'link dragline hidden')
+            .attr('d', 'M0,0L0,0')
+            .on('click', function() {
+                d3.event.preventDefault();
+                d3.event.stopPropagation();
+
+                this._create_agreement = false;
+                this.reset_mouse_vars();
+
+                this._drag_line
+                    .classed('hidden', true)
+                    .style('marker-end', '');
+
+                this.restart();
+
+            }.bind(this));
+
         this._circle = this._svg.append('svg:g')
                            .classed('shapes', true)
                            .selectAll('g');
 
         this._selected_link = null;
-        this._mouseup_node = null;
         this._mousedown_link = null;
+        this._selected_node = null;
+        this._source_node = null;
+        this._target_node = null;
         this.restart();
     },
 
     _create_svg: function(container) {
+        var self = this;
+
         this._svg = d3.select(container[0]).
             append('svg').
             attr('width', this.width).
-            attr('height', this.height);
+            attr('height', this.height).
+            on('mousemove', mousemove);
+
+            function mousemove(d) {
+                if (!self._source_node && !self._create_agreement) return;
+
+                var translate = self._get_stored_transformation();
+                var x = self._source_node.x;
+                var y = self._source_node.y;
+
+                var mouse_x = x + d3.mouse(self._source_node_html)[0];
+                var mouse_y = y + d3.mouse(self._source_node_html)[1];
+
+                // update drag line
+                self._drag_line.attr('d', 'M' + x + ',' + y + 'L' + mouse_x + ',' + mouse_y);
+
+                self.restart();
+            }
+
     },
 
     _init_layout: function() {
@@ -342,11 +408,28 @@ topology_graph.TopoGraph = declare([Evented], {
             this._append_suffix_hint(suffix, x, y);
             y += 30;
         }
-
-        this._circle_color = this._colors(1);
     },
 
     /**
+     * Returns lenght of string with set class in pixels
+     */
+     _count_string_size: function(str, cls) {
+         if (!str) return 0;
+
+         cls = cls || '';
+
+         var node = this._svg.append('text')
+            .classed(cls, true)
+            .text(str);
+
+         var length = node.node().getComputedTextLength();
+
+         node.remove();
+
+         return length;
+     },
+
+    /**
      * Restart the simulation to reflect changes in data/state
      */
     restart: function() {
@@ -422,6 +505,8 @@ topology_graph.TopoGraph = declare([Evented], {
             .on("dragend", dragended);
 
         function dragstarted(d) {
+            d.drag_mode = true;
+            hide_semicircles.bind(this, d)();
             d3.event.sourceEvent.stopPropagation();
             // Store the original value of fixed and set the node fixed.
             d.fixed = d.fixed << 1;
@@ -437,11 +522,213 @@ topology_graph.TopoGraph = declare([Evented], {
         }
 
         function dragended(d) {
+            d.drag_mode = false;
             // Restore old value of fixed.
             d.fixed = d.fixed >> 1;
             self._layout.resume();
         }
 
+        function add_labels(type, color, adder_group) {
+            var label_radius = 3;
+
+            var plus = adder_group
+                .append('text')
+                .classed('plus', true)
+                .classed(type + '_plus', true)
+                .text('\uf067');
+
+            var label = adder_group.append('path')
+                    .attr('id', type + '_label');
+
+            if (type === 'ca') {
+                plus.attr('dx', '18')
+                    .attr('dy', '4');
+                var adder_label = adder_group.append('text')
+                    .append('textPath')
+                        .classed('adder_label', true)
+                        .style('fill', color)
+                        .attr('xlink:href', '#' + type + '_label')
+                        .text(type);
+
+                var str_size = self._count_string_size(type, 'adder_label');
+                var str_translate = str_size + self._adder_outer_radius + 3;
+                label.attr('d', 'M 33 3 L ' + str_translate + ' 3');
+
+                adder_group.insert('rect', 'text')
+                    .attr('x', '33')
+                    .attr('y', '-11')
+                    .attr('rx', label_radius)
+                    .attr('ry', label_radius)
+                    .attr('width', str_size)
+                    .attr('height', '18')
+                    .style("fill", "white");
+            }
+            else {
+                plus.attr('dx', '-26')
+                    .attr('dy', '4');
+                adder_label = adder_group.append('text')
+                    .append('textPath')
+                        .classed('adder_label', true)
+                        .style('fill', color)
+                        .attr('xlink:href', '#' + type + '_label')
+                        .text(type);
+
+                str_size = self._count_string_size(type, 'adder_label');
+                str_translate = str_size + self._adder_outer_radius + 3;
+                label.attr('d', 'M -' + str_translate + ' 3 L -33 3');
+
+                adder_group.insert('rect', 'text')
+                    .attr('x', '-'+str_translate)
+                    .attr('y', '-11')
+                    .attr('rx', label_radius)
+                    .attr('ry', label_radius)
+                    .attr('width', str_size)
+                    .attr('height', '18')
+                    .style('fill', 'white');
+            }
+        }
+
+        function create_semicircle(d, type) {
+            var color = d3.rgb(self._colors(type)).toString();
+            var adder_group = d3.select(this).select('g');
+            var scale = '1.05';
+
+            adder_group.append("path")
+                .classed(type+'_adder', true)
+                .classed('adder', true)
+                .attr("d", d[type + '_adder'])
+                .attr("fill", color)
+                .on('mouseover', function(d) {
+                    window.clearTimeout(d._timeout_hide);
+
+                    d3.select(this).attr('transform', 'scale('+scale+')');
+                    adder_group.select('text.' + type + '_plus')
+                        .attr('transform', 'scale('+scale+')');
+                })
+                .on('mouseout', function(d) {
+                    d3.select(this).attr('transform', '');
+                    adder_group.select('text.' + type + '_plus')
+                        .attr('transform', '');
+                })
+                .on('click', function(d) {
+                    d3.event.preventDefault();
+                    d3.event.stopPropagation();
+                    self.emit('link-selected', { link: null });
+
+                    hide_semicircles.bind(this, d)();
+
+                    // select node
+                    if (!self._source_node) {
+                        self._source_node = d;
+                        self._source_node_html = d3.select(this)
+                                                    .select('circle').node();
+                        self._create_agreement = true;
+                    }
+
+                    self._selected_link = null;
+
+                    var translate = self._get_stored_transformation();
+                    var x = self._source_node.x;
+                    var y = self._source_node.y;
+
+                    // add position of node + translation of whole graph + relative
+                    // position of the mouse
+                    var mouse_x = d.x + d3.mouse(this)[0];
+                    var mouse_y = d.y + d3.mouse(this)[1];
+
+                    // reposition drag line
+                    self._drag_line
+                        .style('marker-end', 'url(#' + type + '-end-arrow)')
+                        .style('stroke', color)
+                        .classed('hidden', false)
+                        .attr('suffix', type)
+                        .attr('d', 'M' + x + ',' + y +
+                                'L' + mouse_x + ',' + mouse_y);
+
+                    self.restart();
+                }.bind(this))
+                .on('mousedown.drag', function() {
+                    d3.event.preventDefault();
+                    d3.event.stopPropagation();
+                })
+                .transition()
+                    .duration(self._adder_anim_duration)
+                    .attr("d", d[type + '_adder']
+                        .outerRadius(self._adder_outer_radius))
+                    .each('end', function() {
+                        add_labels(type, color, adder_group);
+                    });
+        }
+
+        function show_semicircles(d) {
+
+            if(!d3.select(this).select('g path').empty()) return;
+
+            if (!d.drag_mode && !self._create_agreement) {
+
+                // append invisible circle which covers spaces between node
+                // and adders it prevents hiding adders when mouse is on the space
+                d3.select(this).append('g')
+                    .append('circle')
+                    .attr('r', self._adder_outer_radius)
+                    .style('opacity', 0);
+                create_semicircle.bind(this, d, 'ca')();
+
+                create_semicircle.bind(this, d, 'domain')();
+
+                //move the identification text
+                d3.select(this).select('text')
+                    .transition()
+                        .duration(self._adder_anim_duration)
+                        .attr('dy', '45');
+            }
+        }
+
+        function hide_semicircles(d) {
+            var curr_nod = d3.select(this);
+            curr_nod.selectAll('.plus,.adder_label,rect')
+                .transition()
+                    .ease('exp')
+                    .duration(100)
+                    .style('font-size', '0px')
+                    .remove();
+            curr_nod.select('path.domain_adder')
+                .transition()
+                    .attr("d", d.domain_adder
+                        .outerRadius(self._adder_inner_radius))
+                    .duration(self._adder_anim_duration);
+            curr_nod.select('path.ca_adder')
+                .transition()
+                    .attr("d", d.ca_adder
+                        .outerRadius(self._adder_inner_radius))
+                    .duration(self._adder_anim_duration);
+            curr_nod.select('g')
+                .transition()
+                    .duration(self._adder_anim_duration)
+                    .remove();
+            curr_nod.select('text')
+                .transition()
+                    .attr('dy', '30')
+                    .duration(self._adder_anim_duration);
+        }
+
+        function is_suffix_shown(suffix) {
+            var links = self._source_node.targets[self._target_node.id];
+
+            if (!links) return false;
+
+            for (var i=0, l=links.length; i<l; i++) {
+                var link = links[i];
+                if (link.suffix.cn[0] === suffix) {
+                    self._selected_link = link;
+                    self.emit('link-selected', { link: link });
+                    return true;
+                }
+            }
+
+            return false;
+        }
+
         // add new nodes
         var g = this._circle.enter()
             .append('svg:g')
@@ -453,6 +740,49 @@ topology_graph.TopoGraph = declare([Evented], {
                 d.fixed = d.fixed ^ 1;
                 self._layout.resume();
             })
+            .on('mouseover', function(d) {
+                window.clearTimeout(d._timeout_hide);
+                show_semicircles.bind(this, d)();
+                d3.select('circle.cover').classed('cover', true);
+            })
+            .on('mouseout', function(d) {
+                d._timeout_hide = window.setTimeout(hide_semicircles
+                                                        .bind(this, d), 50);
+            })
+            .on('click', function(d) {
+                if (!self._create_agreement) return;
+
+                d3.event.preventDefault();
+                d3.event.stopPropagation();
+
+                if (self._source_node !== d) {
+                    self._target_node = d;
+                    var source = self._source_node;
+                    var target = self._target_node;
+                    var suffix = self._drag_line.attr('suffix');
+                    var direction = 'left';
+                    var link = {
+                        source: source,
+                        target: target,
+                        suffix: suffix,
+                        left: false,
+                        right: false
+                    };
+
+                    if (!is_suffix_shown(suffix)) {
+                        link[direction] = true;
+                        self.emit('add-agreement', link);
+                    }
+                }
+
+                self._drag_line
+                    .classed('hidden', true)
+                    .attr('suffix', '')
+                    .style('marker-end', '');
+
+                self.restart();
+                self.reset_mouse_vars();
+            })
             .call(drag);
 
         g.append('svg:circle')
@@ -488,6 +818,15 @@ topology_graph.TopoGraph = declare([Evented], {
             .attr("transform", transform);
     },
 
+    reset_mouse_vars: function() {
+        this._source_node = null;
+        this._source_node_html = null;
+        this._target_node = null;
+        this._mousedown_link = null;
+        this._create_agreement = null;
+
+    },
+
     resize: function(height, width) {
         if (!(isNaN(height) || isNaN(width))) {
             this.height = height < 0 ? 0 : height;
-- 
2.5.5

-- 
Manage your subscription for the Freeipa-devel mailing list:
https://www.redhat.com/mailman/listinfo/freeipa-devel
Contribute to FreeIPA: http://www.freeipa.org/page/Contribute/Code

Reply via email to