yellow team mailing list archive
-
yellow team
-
Mailing list archive
-
Message #01437
[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