← Back to team overview

yellow team mailing list archive

[Merge] lp:~benji/juju-gui/add-rel-improvements-3 into lp:juju-gui

 

Benji York has proposed merging lp:~benji/juju-gui/add-rel-improvements-3 into lp:juju-gui.

Requested reviews:
  Juju GUI Hackers (juju-gui)

For more details, see:
https://code.launchpad.net/~benji/juju-gui/add-rel-improvements-3/+merge/133104

Improve dragline behavior.


-- 
https://code.launchpad.net/~benji/juju-gui/add-rel-improvements-3/+merge/133104
Your team Juju GUI Hackers is requested to review the proposed merge of lp:~benji/juju-gui/add-rel-improvements-3 into lp:juju-gui.
=== modified file 'app/views/environment.js'
--- app/views/environment.js	2012-11-01 13:21:53 +0000
+++ app/views/environment.js	2012-11-06 16:58:29 +0000
@@ -20,17 +20,19 @@
           },
           // Menu/Controls
           '.add-relation': {
+            /** The user clicked on the "Build Relation" menu item. */
             click: function() {
               var box = this.get('active_service'),
                   service = this.serviceForBox(box),
                   context = this.get('active_context');
+              this.addRelationDragStart.call(this, box, context);
               this.service_click_actions
                         .toggleControlPanel(box, this, context);
-              this.service_click_actions
-                        .addRelationStart(box, this, context);
+              this.service_click_actions.addRelationStart(box, this, context);
             }
           },
           '.view-service': {
+            /** The user clicked on the "View" menu item. */
             click: function() {
               // Get the service element
               var box = this.get('active_service'),
@@ -42,6 +44,7 @@
             }
           },
           '.destroy-service': {
+            /** The user clicked on the "Destroy" menu item. */
             click: function() {
               // Get the service element
               var box = this.get('active_service'),
@@ -72,6 +75,12 @@
               if (!d.containsPoint(mouse_coords, self.zoom)) {
                 return;
               }
+
+              // Do not fire if we're on the same service.
+              if (d === self.get('addRelationStart_service')) {
+                return;
+              }
+
               self.set('potential_drop_point_service', d);
               self.set('potential_drop_point_rect', rect);
               self.addSVGClass(rect, 'hover');
@@ -112,6 +121,17 @@
                 self.dragline.attr('class',
                     'relation pending-relation dragline dragging');
               }
+            },
+            /**
+             * If the mouse moves over a service and we are adding a relation,
+             * then the dragline needs to be updated.
+             */
+            mousemove: function(d, self) {
+              if (self.clickAddRelation) {
+                var container = self.get('container'),
+                    node = container.one('#canvas rect:first-child');
+                self.rectMousemove.call(node.getDOMNode(), d, self);
+              }
             }
           },
           '.sub-rel-block': {
@@ -143,16 +163,50 @@
 
           // Relation Related
           '.rel-label': {
-            click: 'relationClick'
+            /** The user clicked on the relation label. */
+            click: 'relationClick',
+            /**
+             * If the mouse moves over a relation label and we are adding a
+             * relation, then the dragline needs to be updated.
+             */
+            mousemove: function(d, self) {
+              if (self.clickAddRelation) {
+                var container = self.get('container'),
+                    node = container.one('#canvas rect:first-child');
+                self.rectMousemove.call(node.getDOMNode(), d, self);
+              }
+            }
           },
 
-          // Canvas related
           '#canvas rect:first-child': {
+            /**
+             * If the user clicks on the background we cancel any active add
+             * relation.
+             */
             click: function(d, self) {
               var container = self.get('container');
               container.all('.environment-menu.active').removeClass('active');
               self.service_click_actions.toggleControlPanel(null, self);
               self.cancelRelationBuild();
+            },
+            /**
+             * If the mouse moves over the background and we are adding a
+             * relation, then the dragline needs to be updated.
+             */
+            mousemove: function(d, self) {
+              if (self.clickAddRelation) {
+                var container = self.get('container'),
+                    node = container.one('#canvas rect:first-child');
+                self.rectMousemove.call(node.getDOMNode(), d, self);
+              }
+            }
+          },
+          '.dragline': {
+            /** The user clicked while the dragline was active. */
+            click: function(d, self) {
+              // It was technically the dragline that was clicked, but the
+              // intent was to click on the background, so...
+              self.backgroundClicked.call(self);
             }
           }
         },
@@ -168,26 +222,10 @@
                   return;
                 }
 
-                // set a flag on the view that we're building a relation
-                self.buildingRelation = true;
-
                 // Sometimes mouseover is fired after the mousedown, so ensure
                 // we have the correct event in d3.event for d3.mouse().
                 d3.event = e;
 
-                // Flash an indicator around the center of the service block.
-                var center = d.getCenter();
-                self.vis.append('circle')
-                  .attr('cx', center[0])
-                  .attr('cy', center[1])
-                  .attr('r', 100)
-                  .attr('class', 'mouse-down-indicator')
-                  .transition()
-                  .duration(750)
-                  .ease('bounce')
-                  .attr('r', 0)
-                  .remove();
-
                 // Start the process of adding a relation
                 self.addRelationDragStart.call(self, d, this);
               }, [d, evt], false);
@@ -400,6 +438,19 @@
           self.removeRelationConfirm(d, this, self);
         },
 
+        /**
+         * Update the dragline to follow the mouse.
+         *
+         * @method rectMousemove
+         */
+        rectMousemove: function(d, self) {
+          var mouse = d3.mouse(this);
+          d3.event.x = mouse[0];
+          d3.event.y = mouse[1];
+          self.addRelationDrag
+            .call(self, self.get('addRelationStart_service'), this);
+        },
+
         /*
          * Sync view models with current db.models.
          */
@@ -1021,22 +1072,25 @@
 
         addRelationDragStart: function(d, context) {
           // Create a pending drag-line behind services.
-          var dragline = this.vis.insert('line', '.service')
+          var dragline = this.vis.append('line')
               .attr('class', 'relation pending-relation dragline dragging'),
               self = this;
 
-          // Start the line in the middle of the service.
-          var point = d.getCenter();
-          dragline.attr('x1', point[0])
-              .attr('y1', point[1])
-              .attr('x2', point[0])
-              .attr('y2', point[1]);
+          // Start the line between the cursor and the nearest connector
+          // point on the service.
+          var mouse = d3.mouse(Y.one('svg').getDOMNode());
+          self.cursorBox = views.BoundingBox();
+          self.cursorBox.pos = {x: mouse[0], y: mouse[1], w: 0, h: 0};
+          var point = self.cursorBox.getConnectorPair(d);
+          dragline.attr('x1', point[0][0])
+              .attr('y1', point[0][1])
+              .attr('x2', point[1][0])
+              .attr('y2', point[1][1]);
           self.dragline = dragline;
-          self.cursorBox = views.BoundingBox();
 
           // Start the add-relation process.
           self.service_click_actions
-          .addRelationStart(d, self, context);
+            .addRelationStart(d, self, context);
         },
 
         addRelationDrag: function(d, context) {
@@ -1133,11 +1187,79 @@
             this.dragline.remove();
             this.dragline = null;
           }
+          this.clickAddRelation = null;
           this.set('currentServiceClickAction', 'toggleControlPanel');
+          this.buildingRelation = false;
           this.show(this.vis.selectAll('.service'))
                   .classed('selectable-service', false);
         },
 
+        /**
+         * The user clicked on the environment view background.
+         *
+         * If we are in the middle of adding a relation, cancel the relation
+         * adding.
+         *
+         * @method backgroundClicked
+         */
+        backgroundClicked: function() {
+          if (this.clickAddRelation) {
+            this.cancelRelationBuild();
+          }
+        },
+
+        /**
+         * An "add relation" action has been initiated by the user.
+         *
+         * @method startRelation
+         */
+        startRelation: function(service) {
+          // Set flags on the view that indicate we are building a relation.
+          this.buildingRelation = true;
+          this.clickAddRelation = true;
+
+          this.show(this.vis.selectAll('.service'));
+
+          var db = this.get('db'),
+              getServiceEndpoints = this.get('getServiceEndpoints'),
+              endpoints = models.getEndpoints(
+              service, getServiceEndpoints(), db),
+
+              /* Transform endpoints into a list of
+                 * relatable services (to the service)
+                 */
+              possible_relations = Y.Array.map(
+              Y.Array.flatten(Y.Object.values(
+              endpoints)),
+              function(ep) {return ep.service;}),
+              invalidRelationTargets = {};
+
+          // Iterate services and invert the possibles list.
+          db.services.each(function(s) {
+            if (Y.Array.indexOf(possible_relations,
+                s.get('id')) === -1) {
+              invalidRelationTargets[s.get('id')] = true;
+            }
+          });
+
+          // Fade elements to which we can't relate.
+          // Rather than two loops this marks
+          // all services as selectable and then
+          // removes the invalid ones.
+          this.fade(this.vis.selectAll('.service')
+              .classed('selectable-service', true)
+              .filter(function(d) {
+                return (d.id in invalidRelationTargets &&
+                          d.id !== service.id);
+              }))
+              .classed('selectable-service', false);
+
+          // Store possible endpoints.
+          this.set('addRelationStart_possibleEndpoints', endpoints);
+          // Set click action.
+          this.set('currentServiceClickAction', 'ambiguousAddRelationCheck');
+        },
+
 
         /*
          * Zoom in event handler.
@@ -1403,51 +1525,10 @@
            * flow.
            */
           addRelationStart: function(m, view, context) {
-            view.show(view.vis.selectAll('.service'));
-
-            var db = view.get('db'),
-                getServiceEndpoints = view.get('getServiceEndpoints'),
-                service = view.serviceForBox(m),
-                endpoints = models.getEndpoints(
-                    service, getServiceEndpoints(), db),
-
-                /* Transform endpoints into a list of
-                 * relatable services (to the service in m)
-                 */
-                possible_relations = Y.Array.map(
-                    Y.Array.flatten(Y.Object.values(
-                        endpoints)),
-                    function(ep) {return ep.service;}),
-                invalidRelationTargets = {};
-
-            // Iterate services and invert the possibles list.
-            db.services.each(function(s) {
-              if (Y.Array.indexOf(possible_relations,
-                  s.get('id')) === -1) {
-                invalidRelationTargets[s.get('id')] = true;
-              }
-            });
-
-            // Fade elements to which we can't relate.
-            // Rather than two loops this marks
-            // all services as selecable and then
-            // removes the invalid ones
-            view.fade(view.vis.selectAll('.service')
-              .classed('selectable-service', true)
-              .filter(function(d) {
-                  return (d.id in invalidRelationTargets &&
-                          d.id !== m.id);
-                }))
-              .classed('selectable-service', false);
-
-
-
+            var service = view.serviceForBox(m);
+            view.startRelation.call(view, service);
             // Store start service in attrs.
             view.set('addRelationStart_service', m);
-            // Store possible endpoints.
-            view.set('addRelationStart_possibleEndpoints', endpoints);
-            // Set click action.
-            view.set('currentServiceClickAction', 'ambiguousAddRelationCheck');
           },
 
           /*
@@ -1479,19 +1560,8 @@
               return a[0].name + a[1].name < b[0].name + b[1].name;
             });
 
-            // Create a pending line if it doesn't exist, as in the case of
-            // clicking to add relation.
-            if (!view.dragline) {
-              var dragline = view.vis.insert('line', '.service')
-                  .attr('class', 'relation pending-relation dragline'),
-                  points = m.getConnectorPair(
-                      view.get('addRelationStart_service'));
-              dragline.attr('x1', points[0][0])
-                .attr('y1', points[0][1])
-                .attr('x2', points[1][0])
-                .attr('y2', points[1][1]);
-              view.dragline = dragline;
-            }
+            // Stop rubberbanding on mousemove.
+            view.clickAddRelation = null;
 
             // Display menu with available endpoints.
             var menu = container.one('#ambiguous-relation-menu');

=== modified file 'test/test_environment_view.js'
--- test/test_environment_view.js	2012-11-01 13:21:53 +0000
+++ test/test_environment_view.js	2012-11-06 16:58:29 +0000
@@ -340,6 +340,9 @@
          container.all('.selectable-service')
                .size()
                .should.equal(2);
+         container.all('.dragline')
+               .size()
+               .should.equal(1);
          service.next().simulate('click');
          container.all('.selectable-service').size()
             .should.equal(0);
@@ -370,6 +373,28 @@
          view.get('rmrelation_dialog').hide();
        });
 
+    it('should stop creating a relation if the background is clicked',
+        function() {
+          var db = new models.Database(),
+              endpoint_map = {'service-1': {requires: [], provides: []}},
+              view = new views.environment(
+              { container: container,
+                db: db,
+                env: env,
+                getServiceEndpoints: function() {return endpoint_map;}}),
+              service = new models.Service({ id: 'service-1'});
+
+          db.services.add([service]);
+          view.render();
+
+          // If the user has clicked on the "Add Relation" menu item...
+          view.startRelation(service);
+          assert.isTrue(view.buildingRelation);
+          // ...clicking on the background causes the relation drag to stop.
+          view.backgroundClicked();
+          assert.isFalse(view.buildingRelation);
+        });
+
     // TODO: This will be fully testable once we have specification on the
     // list view itself.  Skipped until then.
     it.skip('must be able to switch between graph and list views',
@@ -390,6 +415,7 @@
         }
     );
   });
+
   describe('view model support infrastructure', function() {
     var Y, views, models;
 

=== modified file 'undocumented'
--- undocumented	2012-11-01 14:35:58 +0000
+++ undocumented	2012-11-06 16:58:29 +0000
@@ -113,7 +113,6 @@
 app/views/environment.js attachSceneEvents
 app/views/environment.js buildScene
 app/views/environment.js cancelRelationBuild
-app/views/environment.js click
 app/views/environment.js destroyService
 app/views/environment.js destroyServiceConfirm
 app/views/environment.js detachSceneEvents


Follow ups