Left: | ||
Right: |
OLD | NEW |
---|---|
1 'use strict'; | 1 'use strict'; |
2 | 2 |
3 /** | 3 /** |
4 * IMPORTANT | 4 * IMPORTANT |
5 * | 5 * |
6 * This module represents a single step in the refactor of the environment | 6 * This module represents a single step in the refactor of the environment |
7 * view. This module is THROW AWAY CODE. Each forthcoming branch should | 7 * view. This module is THROW AWAY CODE. Each forthcoming branch should |
8 * begin by moving relevant code to the proper module, binding that | 8 * begin by moving relevant code to the proper module, binding that |
9 * module to Topo and removing code from here. | 9 * module to Topo and removing code from here. |
10 * | 10 * |
(...skipping 15 matching lines...) Expand all Loading... | |
26 * @class MegaModule | 26 * @class MegaModule |
27 * @namespace views | 27 * @namespace views |
28 **/ | 28 **/ |
29 var MegaModule = Y.Base.create('MegaModule', d3ns.Module, [], { | 29 var MegaModule = Y.Base.create('MegaModule', d3ns.Module, [], { |
30 events: { | 30 events: { |
31 scene: { | 31 scene: { |
32 '.service': { | 32 '.service': { |
33 click: 'serviceClick', | 33 click: 'serviceClick', |
34 dblclick: 'serviceDblClick', | 34 dblclick: 'serviceDblClick', |
35 mouseenter: 'serviceMouseEnter', | 35 mouseenter: 'serviceMouseEnter', |
36 mouseleave: 'mousemove' | 36 mouseleave: 'serviceMouseLeave' |
37 //mouseleave: 'mousemove' | |
bac
2013/01/03 14:10:37
Delete commented out code.
matthew.scott
2013/01/03 20:44:16
Done.
| |
37 }, | 38 }, |
38 | 39 |
39 '.sub-rel-block': { | |
40 mouseenter: 'subRelBlockMouseEnter', | |
41 mouseleave: 'subRelBlockMouseLeave', | |
42 click: 'subRelBlockClick' | |
43 }, | |
44 '.service-status': { | 40 '.service-status': { |
45 mouseover: {callback: function(d, self) { | 41 mouseover: {callback: function(d, self) { |
46 d3.select(this) | 42 d3.select(this) |
47 .select('.unit-count') | 43 .select('.unit-count') |
48 .attr('class', 'unit-count show-count'); | 44 .attr('class', 'unit-count show-count'); |
49 }}, | 45 }}, |
50 mouseout: {callback: function(d, self) { | 46 mouseout: {callback: function(d, self) { |
51 d3.select(this) | 47 d3.select(this) |
52 .select('.unit-count') | 48 .select('.unit-count') |
53 .attr('class', 'unit-count hide-count'); | 49 .attr('class', 'unit-count hide-count'); |
54 }} | 50 }} |
55 }, | 51 }, |
56 '.rel-label': { | 52 '.rel-label': { |
57 click: 'relationClick', | |
58 mousemove: 'mousemove' | 53 mousemove: 'mousemove' |
59 }, | 54 }, |
60 '.topology .crosshatch-background rect:first-child': { | 55 '.topology .crosshatch-background rect:first-child': { |
61 /** | 56 /** |
62 * If the user clicks on the background we cancel any active add | 57 * If the user clicks on the background we cancel any active add |
63 * relation. | 58 * relation. |
64 */ | 59 */ |
65 click: {callback: function(d, self) { | 60 click: {callback: function(d, self) { |
66 var container = self.get('container'); | 61 var container = self.get('container'), |
62 topo = self.get('component'); | |
67 container.all('.environment-menu.active').removeClass('active'); | 63 container.all('.environment-menu.active').removeClass('active'); |
68 self.service_click_actions.toggleControlPanel(null, self); | 64 self.service_click_actions.toggleControlPanel(null, self); |
69 self.cancelRelationBuild(); | 65 topo.fire('cancelRelationBuild'); |
70 self.hideSubordinateRelations(); | 66 topo.fire('hideSubordinateRelations'); |
bcsaller
2013/01/02 21:53:07
This is a place the patterns need some thought fro
matthew.scott
2013/01/03 20:44:16
This makes sense, and I even had an appropriate ev
| |
71 }}, | 67 }}, |
72 mousemove: 'mousemove' | 68 mousemove: 'mousemove' |
73 }, | 69 }, |
74 '.dragline': { | |
75 /** The user clicked while the dragline was active. */ | |
76 click: {callback: function(d, self) { | |
77 // It was technically the dragline that was clicked, but the | |
78 // intent was to click on the background, so... | |
79 self.backgroundClicked(); | |
80 }} | |
81 }, | |
82 '.graph-list-picker .picker-button': { | 70 '.graph-list-picker .picker-button': { |
83 click: 'showGraphListPicker' | 71 click: 'showGraphListPicker' |
84 }, | 72 }, |
85 '.graph-list-picker .picker-expanded': { | 73 '.graph-list-picker .picker-expanded': { |
86 click: 'hideGraphListPicker' | 74 click: 'hideGraphListPicker' |
87 }, | 75 }, |
88 // Menu/Controls | 76 // Menu/Controls |
89 '.add-relation': { | |
90 /** The user clicked on the "Build Relation" menu item. */ | |
91 click: { | |
92 callback: function(data, context) { | |
93 var box = context.get('active_service'), | |
94 service = context.serviceForBox(box), | |
95 origin = context.get('active_context'); | |
96 context.addRelationDragStart(box, context); | |
97 context.service_click_actions | |
98 .toggleControlPanel(box, context, origin); | |
99 context.service_click_actions.addRelationStart( | |
100 box, context, origin); | |
101 }} | |
102 }, | |
103 '.view-service': { | 77 '.view-service': { |
104 /** The user clicked on the "View" menu item. */ | 78 /** The user clicked on the "View" menu item. */ |
105 click: {callback: function(data, context) { | 79 click: {callback: function(data, context) { |
106 // Get the service element | 80 // Get the service element |
107 var box = context.get('active_service'), | 81 var topo = context.get('component'); |
108 service = context.serviceForBox(box); | 82 var box = topo.get('active_service'); |
83 var service = topo.serviceForBox(box); | |
109 context.service_click_actions | 84 context.service_click_actions |
110 .toggleControlPanel(box, context); | 85 .toggleControlPanel(box, context); |
111 context.service_click_actions | 86 context.service_click_actions |
112 .show_service(service, context); | 87 .show_service(service, context); |
113 }} | 88 }} |
114 }, | 89 }, |
115 '.destroy-service': { | 90 '.destroy-service': { |
116 /** The user clicked on the "Destroy" menu item. */ | 91 /** The user clicked on the "Destroy" menu item. */ |
117 click: {callback: function(data, context) { | 92 click: {callback: function(data, context) { |
118 // Get the service element | 93 // Get the service element |
119 var box = context.get('active_service'), | 94 var topo = context.get('component'); |
120 service = context.serviceForBox(box); | 95 var box = topo.get('active_service'); |
96 var service = topo.serviceForBox(box); | |
121 context.service_click_actions | 97 context.service_click_actions |
122 .toggleControlPanel(box, context); | 98 .toggleControlPanel(box, context); |
123 context.service_click_actions | 99 context.service_click_actions |
124 .destroyServiceConfirm(service, context); | 100 .destroyServiceConfirm(service, context); |
125 }} | 101 }} |
126 } | 102 } |
127 }, | 103 }, |
128 d3: { | 104 d3: { |
129 '.service': { | 105 '.service': { |
130 'mousedown.addrel': {callback: function(d, context) { | 106 'mousedown.addrel': {callback: function(d, context) { |
131 var evt = d3.event; | 107 var evt = d3.event; |
108 var topo = context.get('component'); | |
132 context.longClickTimer = Y.later(750, this, function(d, e) { | 109 context.longClickTimer = Y.later(750, this, function(d, e) { |
133 // Provide some leeway for accidental dragging. | 110 // Provide some leeway for accidental dragging. |
134 if ((Math.abs(d.x - d.oldX) + Math.abs(d.y - d.oldY)) / | 111 if ((Math.abs(d.x - d.oldX) + Math.abs(d.y - d.oldY)) / |
135 2 > 5) { | 112 2 > 5) { |
136 return; | 113 return; |
137 } | 114 } |
138 | 115 |
139 // Sometimes mouseover is fired after the mousedown, so ensure | 116 // Sometimes mouseover is fired after the mousedown, so ensure |
140 // we have the correct event in d3.event for d3.mouse(). | 117 // we have the correct event in d3.event for d3.mouse(). |
141 d3.event = e; | 118 d3.event = e; |
142 | 119 |
143 // Start the process of adding a relation | 120 // Start the process of adding a relation |
144 context.addRelationDragStart(d, context); | 121 topo.fire('addRelationDragStart', {service: d}); |
145 }, [d, evt], false); | 122 }, [d, evt], false); |
146 }}, | 123 }}, |
147 'mouseup.addrel': {callback: function(d, context) { | 124 'mouseup.addrel': {callback: function(d, context) { |
148 // Cancel the long-click timer if it exists. | 125 // Cancel the long-click timer if it exists. |
149 if (context.longClickTimer) { | 126 if (context.longClickTimer) { |
150 context.longClickTimer.cancel(); | 127 context.longClickTimer.cancel(); |
151 } | 128 } |
152 }} | 129 }} |
153 } | 130 } |
154 }, | 131 }, |
155 yui: { | 132 yui: { |
156 windowresize: { | 133 windowresize: { |
157 callback: 'setSizesFromViewport', | 134 callback: 'setSizesFromViewport', |
158 context: 'module'}, | 135 context: 'module'}, |
159 rendered: 'renderedHandler' | 136 rendered: 'renderedHandler', |
137 show: 'show', | |
138 hide: 'hide', | |
139 fade: 'fade', | |
140 toggleControlPanel: {callback: function() { | |
141 this.service_click_actions.toggleControlPanel(null, this); | |
142 }}, | |
143 rescaled: 'updateServiceMenuLocation' | |
160 } | 144 } |
161 }, | 145 }, |
162 | 146 |
163 initializer: function(options) { | 147 initializer: function(options) { |
164 MegaModule.superclass.constructor.apply(this, arguments); | 148 MegaModule.superclass.constructor.apply(this, arguments); |
165 | 149 |
166 // Build a service.id -> BoundingBox map for services. | 150 // Build a service.id -> BoundingBox map for services. |
167 this.service_boxes = {}; | 151 this.service_boxes = {}; |
168 | 152 |
169 // Set a default | 153 // Set a default |
(...skipping 13 matching lines...) Expand all Loading... | |
183 // Fire the action named in the following scheme: | 167 // Fire the action named in the following scheme: |
184 // service_click_action.<action> | 168 // service_click_action.<action> |
185 // with the service, the SVG node, and the view | 169 // with the service, the SVG node, and the view |
186 // as arguments. | 170 // as arguments. |
187 (context.service_click_actions[curr_click_action])( | 171 (context.service_click_actions[curr_click_action])( |
188 d, context, this); | 172 d, context, this); |
189 }, | 173 }, |
190 | 174 |
191 serviceDblClick: function(d, self) { | 175 serviceDblClick: function(d, self) { |
192 // Just show the service on double-click. | 176 // Just show the service on double-click. |
193 var service = self.serviceForBox(d); | 177 var topo = self.get('component'), |
178 service = topo.serviceForBox(d); | |
194 (self.service_click_actions.show_service)(service, self); | 179 (self.service_click_actions.show_service)(service, self); |
195 }, | 180 }, |
196 | 181 |
197 relationClick: function(d, self) { | 182 serviceMouseEnter: function(d, context) { |
198 if (d.scope === 'container') { | 183 var rect = Y.one(this); |
199 var subRelDialog = views.createModalPanel( | 184 // Do not fire if this service isn't selectable. |
200 'You may not remove a subordinate relation.', | 185 if (!utils.hasSVGClass(rect, 'selectable-service')) { |
201 '#rmsubrelation-modal-panel'); | 186 return; |
202 subRelDialog.addButton( | |
203 { value: 'Cancel', | |
204 section: Y.WidgetStdMod.FOOTER, | |
205 /** | |
206 * @method action Hides the dialog on click. | |
207 * @param {object} e The click event. | |
208 * @return {undefined} nothing. | |
209 */ | |
210 action: function(e) { | |
211 e.preventDefault(); | |
212 subRelDialog.hide(); | |
213 subRelDialog.destroy(); | |
214 }, | |
215 classNames: ['btn'] | |
216 }); | |
217 subRelDialog.get('boundingBox').all('.yui3-button') | |
218 .removeClass('yui3-button'); | |
219 } else { | |
220 self.removeRelationConfirm(d, this, self); | |
221 } | 187 } |
188 | |
189 // Do not fire unless we're within the service box. | |
190 var topo = context.get('component'); | |
191 var container = context.get('container'); | |
192 var mouse_coords = d3.mouse(container.one('svg').getDOMNode()); | |
193 if (!d.containsPoint(mouse_coords, topo.zoom)) { | |
194 return; | |
195 } | |
196 | |
197 topo.fire('snapToService', { service: d, rect: rect }); | |
198 }, | |
199 | |
200 serviceMouseLeave: function(d, context) { | |
201 // Do not fire if we're within the service box. | |
202 var topo = context.get('component'); | |
203 var container = context.get('container'); | |
204 var mouse_coords = d3.mouse(container.one('svg').getDOMNode()); | |
205 if (d.containsPoint(mouse_coords, topo.zoom)) { | |
206 return; | |
207 } | |
208 var rect = Y.one(this).one('.service-border'); | |
209 utils.removeSVGClass(rect, 'hover'); | |
210 | |
211 topo.fire('snapOutOfService'); | |
222 }, | 212 }, |
223 | 213 |
224 /** | 214 /** |
225 * If the mouse moves and we are adding a relation, then the dragline | 215 * If the mouse moves and we are adding a relation, then the dragline |
226 * needs to be updated. | 216 * needs to be updated. |
227 * | 217 * |
228 * @method mousemove | 218 * @method mousemove |
229 * @param {object} d Unused. | 219 * @param {object} d Unused. |
230 * @param {object} self The environment view itself. | 220 * @param {object} self The environment view itself. |
231 * @return {undefined} Side effects only. | 221 * @return {undefined} Side effects only. |
(...skipping 11 matching lines...) Expand all Loading... | |
243 }, | 233 }, |
244 | 234 |
245 /* | 235 /* |
246 * Sync view models with current db.models. | 236 * Sync view models with current db.models. |
247 */ | 237 */ |
248 updateData: function() { | 238 updateData: function() { |
249 //model data | 239 //model data |
250 var topo = this.get('component'), | 240 var topo = this.get('component'), |
251 vis = topo.vis, | 241 vis = topo.vis, |
252 db = topo.get('db'), | 242 db = topo.get('db'), |
253 relations = db.relations.toArray(), | |
254 services = db.services.map(views.toBoundingBox); | 243 services = db.services.map(views.toBoundingBox); |
255 | 244 |
256 this.services = services; | 245 this.services = services; |
257 | 246 |
258 Y.each(services, function(service) { | 247 Y.each(services, function(service) { |
259 // Update services with existing positions. | 248 // Update services with existing positions. |
260 var existing = this.service_boxes[service.id]; | 249 var existing = this.service_boxes[service.id]; |
261 if (existing) { | 250 if (existing) { |
262 service.pos = existing.pos; | 251 service.pos = existing.pos; |
263 } | 252 } |
264 service.margins(service.subordinate ? | 253 service.margins(service.subordinate ? |
265 { | 254 { |
266 top: 0.05, | 255 top: 0.05, |
267 bottom: 0.1, | 256 bottom: 0.1, |
268 left: 0.084848, | 257 left: 0.084848, |
269 right: 0.084848} : | 258 right: 0.084848} : |
270 { | 259 { |
271 top: 0, | 260 top: 0, |
272 bottom: 0.1667, | 261 bottom: 0.1667, |
273 left: 0.086758, | 262 left: 0.086758, |
274 right: 0.086758}); | 263 right: 0.086758}); |
275 this.service_boxes[service.id] = service; | 264 this.service_boxes[service.id] = service; |
276 }, this); | 265 }, this); |
277 this.rel_pairs = this.processRelations(relations); | 266 topo.service_boxes = this.service_boxes; |
278 | 267 |
279 // Nodes are mapped by modelId tuples. | 268 // Nodes are mapped by modelId tuples. |
280 this.node = vis.selectAll('.service') | 269 this.node = vis.selectAll('.service') |
281 .data(services, function(d) { | 270 .data(services, function(d) { |
282 return d.modelId();}); | 271 return d.modelId();}); |
283 }, | 272 }, |
284 | 273 |
285 /* | 274 /* |
286 * Attempt to reuse as much of the existing graph and view models | 275 * Attempt to reuse as much of the existing graph and view models |
287 * as possible to re-render the graph. | 276 * as possible to re-render the graph. |
(...skipping 24 matching lines...) Expand all Loading... | |
312 | 301 |
313 var drag = d3.behavior.drag() | 302 var drag = d3.behavior.drag() |
314 .on('dragstart', function(d) { | 303 .on('dragstart', function(d) { |
315 d.oldX = d.x; | 304 d.oldX = d.x; |
316 d.oldY = d.y; | 305 d.oldY = d.y; |
317 self.get('container').all('.environment-menu.active') | 306 self.get('container').all('.environment-menu.active') |
318 .removeClass('active'); | 307 .removeClass('active'); |
319 self.service_click_actions.toggleControlPanel(null, self); | 308 self.service_click_actions.toggleControlPanel(null, self); |
320 }) | 309 }) |
321 .on('drag', function(d, i) { | 310 .on('drag', function(d, i) { |
322 if (self.buildingRelation) { | 311 if (topo.buildingRelation) { |
323 self.addRelationDrag(d, this); | 312 topo.fire('addRelationDrag', { box: d }); |
324 } else { | 313 } else { |
325 if (self.longClickTimer) { | 314 if (self.longClickTimer) { |
326 self.longClickTimer.cancel(); | 315 self.longClickTimer.cancel(); |
327 } | 316 } |
328 | 317 |
329 // Translate the service (and, potentially, menu). | 318 // Translate the service (and, potentially, menu). |
330 d.x += d3.event.dx; | 319 d.x += d3.event.dx; |
331 d.y += d3.event.dy; | 320 d.y += d3.event.dy; |
332 d3.select(this).attr('transform', function(d, i) { | 321 d3.select(this).attr('transform', function(d, i) { |
333 return d.translateStr(); | 322 return d.translateStr(); |
334 }); | 323 }); |
335 if (self.get('active_service') === d) { | 324 if (topo.get('active_service') === d) { |
336 self.updateServiceMenuLocation(); | 325 self.updateServiceMenuLocation(); |
337 } | 326 } |
338 | 327 |
339 // Clear any state while dragging. | 328 // Clear any state while dragging. |
340 self.get('container').all('.environment-menu.active') | 329 self.get('container').all('.environment-menu.active') |
341 .removeClass('active'); | 330 .removeClass('active'); |
342 self.cancelRelationBuild(); | 331 topo.fire('cancelRelationBuild'); |
343 | 332 |
344 // Update relation lines for just this service. | 333 // Update relation lines for just this service. |
345 updateLinkEndpoints(d); | 334 topo.fire('serviceMoved', { service: d }); |
346 } | 335 } |
347 }) | 336 }) |
348 .on('dragend', function(d, i) { | 337 .on('dragend', function(d, i) { |
349 if (self.buildingRelation) { | 338 if (topo.buildingRelation) { |
350 self.addRelationDragEnd(); | 339 topo.fire('addRelationDragEnd'); |
351 } | 340 } |
352 }); | 341 }); |
353 | 342 |
354 /** | |
355 * Update relation line endpoints for a given service. | |
356 * | |
357 * @method updateLinkEndpoints | |
358 * @param {Object} service The service module that has been moved. | |
359 */ | |
360 function updateLinkEndpoints(service) { | |
361 Y.each(Y.Array.filter(self.rel_pairs, function(relation) { | |
362 return relation.source() === service || | |
363 relation.target() === service; | |
364 }), function(relation) { | |
365 var rel_group = d3.select('#' + relation.id), | |
366 connectors = relation.source() | |
367 .getConnectorPair(relation.target()), | |
368 s = connectors[0], | |
369 t = connectors[1]; | |
370 rel_group.select('line') | |
371 .attr('x1', s[0]) | |
372 .attr('y1', s[1]) | |
373 .attr('x2', t[0]) | |
374 .attr('y2', t[1]); | |
375 rel_group.select('.rel-label') | |
376 .attr('transform', function(d) { | |
377 return 'translate(' + | |
378 [Math.max(s[0], t[0]) - | |
379 Math.abs((s[0] - t[0]) / 2), | |
380 Math.max(s[1], t[1]) - | |
381 Math.abs((s[1] - t[1]) / 2)] + ')'; | |
382 }); | |
383 }); | |
384 } | |
385 | |
386 // Generate a node for each service, draw it as a rect with | 343 // Generate a node for each service, draw it as a rect with |
387 // labels for service and charm. | 344 // labels for service and charm. |
388 var node = this.node; | 345 var node = this.node; |
389 | 346 |
390 // Rerun the pack layout. | 347 // Rerun the pack layout. |
391 // Pack doesn't honor existing positions and will | 348 // Pack doesn't honor existing positions and will |
392 // re-layout the entire graph. As a short term work | 349 // re-layout the entire graph. As a short term work |
393 // around we layout only new nodes. This has the side | 350 // around we layout only new nodes. This has the side |
394 // effect that node nodes can overlap and will | 351 // effect that node nodes can overlap and will |
395 // be fixed later. | 352 // be fixed later. |
(...skipping 12 matching lines...) Expand all Loading... | |
408 .attr('transform', function(d) { | 365 .attr('transform', function(d) { |
409 return d.translateStr(); | 366 return d.translateStr(); |
410 }); | 367 }); |
411 | 368 |
412 // Update | 369 // Update |
413 this.drawService(node); | 370 this.drawService(node); |
414 | 371 |
415 // Exit | 372 // Exit |
416 node.exit() | 373 node.exit() |
417 .remove(); | 374 .remove(); |
418 | |
419 function updateLinks() { | |
420 // Enter. | |
421 var g = self.drawRelationGroup(), | |
422 link = g.selectAll('line.relation'); | |
423 | |
424 // Update (+ enter selection). | |
425 link.each(self.drawRelation); | |
426 | |
427 // Exit | |
428 g.exit().remove(); | |
429 } | |
430 | |
431 // Draw or schedule redraw of links. | |
432 updateLinks(); | |
433 | |
434 }, | |
435 | |
436 /* | |
437 * Draw a new relation link with label and controls. | |
438 */ | |
439 drawRelationGroup: function() { | |
440 // Add a labelgroup. | |
441 var self = this, | |
442 vis = this.get('component').vis, | |
443 g = vis.selectAll('g.rel-group') | |
444 .data(self.rel_pairs, function(r) { | |
445 return r.modelIds(); | |
446 }); | |
447 | |
448 var enter = g.enter(); | |
449 | |
450 enter.insert('g', 'g.service') | |
451 .attr('id', function(d) { | |
452 return d.id; | |
453 }) | |
454 .attr('class', function(d) { | |
455 // Mark the rel-group as a subordinate relation if need be. | |
456 return (d.scope === 'container' ? | |
457 'subordinate-rel-group ' : '') + | |
458 'rel-group'; | |
459 }) | |
460 .append('svg:line', 'g.service') | |
461 .attr('class', function(d) { | |
462 // Style relation lines differently depending on status. | |
463 return (d.pending ? 'pending-relation ' : '') + | |
464 (d.scope === 'container' ? 'subordinate-relation ' : '') + | |
465 'relation'; | |
466 }); | |
467 | |
468 g.selectAll('.rel-label').remove(); | |
469 g.selectAll('text').remove(); | |
470 g.selectAll('rect').remove(); | |
471 var label = g.append('g') | |
472 .attr('class', 'rel-label') | |
473 .attr('transform', function(d) { | |
474 // XXX: This has to happen on update, not enter | |
475 var connectors = d.source().getConnectorPair(d.target()), | |
476 s = connectors[0], | |
477 t = connectors[1]; | |
478 return 'translate(' + | |
479 [Math.max(s[0], t[0]) - | |
480 Math.abs((s[0] - t[0]) / 2), | |
481 Math.max(s[1], t[1]) - | |
482 Math.abs((s[1] - t[1]) / 2)] + ')'; | |
483 }); | |
484 label.append('text') | |
485 .append('tspan') | |
486 .text(function(d) {return d.display_name; }); | |
487 label.insert('rect', 'text') | |
488 .attr('width', function(d) { | |
489 return d.display_name.length * 10 + 10; | |
490 }) | |
491 .attr('height', 20) | |
492 .attr('x', function() { | |
493 return -parseInt(d3.select(this).attr('width'), 10) / 2; | |
494 }) | |
495 .attr('y', -10) | |
496 .attr('rx', 10) | |
497 .attr('ry', 10); | |
498 | |
499 return g; | |
500 }, | |
501 | |
502 /* | |
503 * Draw a relation between services. | |
504 */ | |
505 drawRelation: function(relation) { | |
506 var connectors = relation.source() | |
507 .getConnectorPair(relation.target()), | |
508 s = connectors[0], | |
509 t = connectors[1], | |
510 link = d3.select(this); | |
511 | |
512 link | |
513 .attr('x1', s[0]) | |
514 .attr('y1', s[1]) | |
515 .attr('x2', t[0]) | |
516 .attr('y2', t[1]); | |
517 return link; | |
518 }, | 375 }, |
519 | 376 |
520 // Called to draw a service in the 'update' phase | 377 // Called to draw a service in the 'update' phase |
521 drawService: function(node) { | 378 drawService: function(node) { |
522 var self = this, | 379 var self = this, |
380 topo = this.get('component'), | |
523 service_scale = this.service_scale, | 381 service_scale = this.service_scale, |
524 service_scale_width = this.service_scale_width, | 382 service_scale_width = this.service_scale_width, |
525 service_scale_height = this.service_scale_height; | 383 service_scale_height = this.service_scale_height; |
526 | 384 |
527 // Size the node for drawing. | 385 // Size the node for drawing. |
528 node | 386 node |
529 .attr('width', function(d) { | 387 .attr('width', function(d) { |
530 // NB: if a service has zero units, as is possible with | 388 // NB: if a service has zero units, as is possible with |
531 // subordinates, then default to 1 for proper scaling, as | 389 // subordinates, then default to 1 for proper scaling, as |
532 // a value of 0 will return a scale of 0 (this does not | 390 // a value of 0 will return a scale of 0 (this does not |
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
566 return 'translate(' + [d.w, d.h / 2 - 26] + ')'; | 424 return 'translate(' + [d.w, d.h / 2 - 26] + ')'; |
567 }); | 425 }); |
568 | 426 |
569 sub_relation.append('image') | 427 sub_relation.append('image') |
570 .attr('xlink:href', '/juju-ui/assets/svgs/sub_relation.svg') | 428 .attr('xlink:href', '/juju-ui/assets/svgs/sub_relation.svg') |
571 .attr('width', 87) | 429 .attr('width', 87) |
572 .attr('height', 47); | 430 .attr('height', 47); |
573 sub_relation.append('text').append('tspan') | 431 sub_relation.append('text').append('tspan') |
574 .attr('class', 'sub-rel-count') | 432 .attr('class', 'sub-rel-count') |
575 .attr('x', 64) | 433 .attr('x', 64) |
576 .attr('y', 47 * 0.8) | 434 .attr('y', 47 * 0.8); |
577 .text(function(d) { | |
578 return self.subordinateRelationsForService(d).length; | |
579 }); | |
580 // Draw non-subordinate services services | 435 // Draw non-subordinate services services |
581 node.filter(function(d) { | 436 node.filter(function(d) { |
582 return !d.subordinate; | 437 return !d.subordinate; |
583 }) | 438 }) |
584 .append('image') | 439 .append('image') |
585 .attr('xlink:href', '/juju-ui/assets/svgs/service_module.svg') | 440 .attr('xlink:href', '/juju-ui/assets/svgs/service_module.svg') |
586 .attr('width', function(d) { | 441 .attr('width', function(d) { |
587 return d.w; | 442 return d.w; |
588 }) | 443 }) |
589 .attr('height', function(d) { | 444 .attr('height', function(d) { |
(...skipping 143 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
733 // Add the unit counts, visible only on hover. | 588 // Add the unit counts, visible only on hover. |
734 var unit_count = status_chart.append('text') | 589 var unit_count = status_chart.append('text') |
735 .attr('class', 'unit-count hide-count') | 590 .attr('class', 'unit-count hide-count') |
736 .text(function(d) { | 591 .text(function(d) { |
737 return utils.humanizeNumber(d.unit_count); | 592 return utils.humanizeNumber(d.unit_count); |
738 }); | 593 }); |
739 | 594 |
740 | 595 |
741 }, | 596 }, |
742 | 597 |
743 processRelation: function(r) { | |
744 var self = this, | |
745 endpoints = r.get('endpoints'), | |
746 rel_services = []; | |
747 | |
748 Y.each(endpoints, function(ep) { | |
749 rel_services.push([ep[1].name, self.service_boxes[ep[0]]]); | |
750 }); | |
751 return rel_services; | |
752 }, | |
753 | |
754 processRelations: function(rels) { | |
755 var self = this, | |
756 pairs = []; | |
757 Y.each(rels, function(rel) { | |
758 var pair = self.processRelation(rel); | |
759 | |
760 // skip peer for now | |
761 if (pair.length === 2) { | |
762 var bpair = views.BoxPair() | |
763 .model(rel) | |
764 .source(pair[0][1]) | |
765 .target(pair[1][1]); | |
766 // Copy the relation type to the box. | |
767 if (bpair.display_name === undefined) { | |
768 bpair.display_name = pair[0][0]; | |
769 } | |
770 pairs.push(bpair); | |
771 } | |
772 }); | |
773 return pairs; | |
774 }, | |
775 | |
776 /* | |
777 * Utility function to get subordinate relations for a service. | |
778 */ | |
779 subordinateRelationsForService: function(service) { | |
780 return this.rel_pairs.filter(function(p) { | |
781 return p.modelIds().indexOf(service.modelId()) !== -1 && | |
782 p.scope === 'container'; | |
783 }); | |
784 }, | |
785 /* | |
786 * Utility method to get a service object from the DB | |
787 * given a BoundingBox. | |
788 */ | |
789 serviceForBox: function(boundingBox) { | |
790 var db = this.get('component').get('db'); | |
791 return db.services.getById(boundingBox.id); | |
792 }, | |
793 | |
794 | 598 |
795 /* | 599 /* |
796 * Show/hide/fade selection. | 600 * Show/hide/fade selection. |
797 */ | 601 */ |
798 show: function(selection) { | 602 show: function(evt) { |
603 var selection = evt.selection; | |
799 selection.attr('opacity', '1.0') | 604 selection.attr('opacity', '1.0') |
800 .style('display', 'block'); | 605 .style('display', 'block'); |
801 return selection; | |
802 }, | 606 }, |
803 | 607 |
804 hide: function(selection) { | 608 hide: function(evt) { |
609 var selection = evt.selection; | |
805 selection.attr('opacity', '0') | 610 selection.attr('opacity', '0') |
806 .style('display', 'none'); | 611 .style('display', 'none'); |
807 return selection; | |
808 }, | 612 }, |
809 | 613 |
810 fade: function(selection, alpha) { | 614 fade: function(evt) { |
615 var selection = evt.selection, | |
616 alpha = evt.alpha; | |
811 selection.transition() | 617 selection.transition() |
812 .duration(400) | 618 .duration(400) |
813 .attr('opacity', alpha !== undefined && alpha || '0.2'); | 619 .attr('opacity', alpha !== undefined && alpha || '0.2'); |
814 return selection; | |
815 }, | 620 }, |
816 | 621 |
817 /* | 622 /* |
818 * Finish DOM-dependent rendering | 623 * Finish DOM-dependent rendering |
819 * | 624 * |
820 * Some portions of the visualization require information pulled | 625 * Some portions of the visualization require information pulled |
821 * from the DOM, such as the clientRects used for sizing relation | 626 * from the DOM, such as the clientRects used for sizing relation |
822 * labels and the viewport size used for sizing the whole graph. This | 627 * labels and the viewport size used for sizing the whole graph. This |
823 * is called after the view is attached to the DOM in order to | 628 * is called after the view is attached to the DOM in order to |
824 * perform all of that work. In the app, it's called as a callback | 629 * perform all of that work. In the app, it's called as a callback |
(...skipping 12 matching lines...) Expand all Loading... | |
837 container.all('.rel-label').each(function(label) { | 642 container.all('.rel-label').each(function(label) { |
838 var width = label.one('text').getClientRect().width + 10; | 643 var width = label.one('text').getClientRect().width + 10; |
839 label.one('rect').setAttribute('width', width) | 644 label.one('rect').setAttribute('width', width) |
840 .setAttribute('x', -width / 2); | 645 .setAttribute('x', -width / 2); |
841 }); | 646 }); |
842 | 647 |
843 // Chainable method. | 648 // Chainable method. |
844 return this; | 649 return this; |
845 }, | 650 }, |
846 | 651 |
847 /* | |
848 * Event handler for the add relation button. | |
849 */ | |
850 addRelation: function(evt) { | |
851 var curr_action = this.get('currentServiceClickAction'), | |
852 container = this.get('container'); | |
853 if (curr_action === 'show_service') { | |
854 this.set('currentServiceClickAction', 'addRelationStart'); | |
855 } else if (curr_action === 'addRelationStart' || | |
856 curr_action === 'ambiguousAddRelationCheck') { | |
857 this.set('currentServiceClickAction', 'toggleControlPanel'); | |
858 } // Otherwise do nothing. | |
859 }, | |
860 | |
861 addRelationDragStart: function(d, context) { | |
862 // Create a pending drag-line. | |
863 var vis = this.get('component').vis, | |
864 dragline = vis.append('line') | |
865 .attr('class', | |
866 'relation pending-relation dragline dragging'), | |
867 self = this; | |
868 | |
869 // Start the line between the cursor and the nearest connector | |
870 // point on the service. | |
871 var mouse = d3.mouse(Y.one('.topology svg').getDOMNode()); | |
872 self.cursorBox = new views.BoundingBox(); | |
873 self.cursorBox.pos = {x: mouse[0], y: mouse[1], w: 0, h: 0}; | |
874 var point = self.cursorBox.getConnectorPair(d); | |
875 dragline.attr('x1', point[0][0]) | |
876 .attr('y1', point[0][1]) | |
877 .attr('x2', point[1][0]) | |
878 .attr('y2', point[1][1]); | |
879 self.dragline = dragline; | |
880 | |
881 // Start the add-relation process. | |
882 context.service_click_actions | |
883 .addRelationStart(d, self, context); | |
884 }, | |
885 | |
886 addRelationDrag: function(d, context) { | |
887 // Rubberband our potential relation line if we're not currently | |
888 // hovering over a potential drop-point. | |
889 if (!this.get('potential_drop_point_service')) { | |
890 // Create a BoundingBox for our cursor. | |
891 this.cursorBox.pos = {x: d3.event.x, y: d3.event.y, w: 0, h: 0}; | |
892 | |
893 // Draw the relation line from the connector point nearest the | |
894 // cursor to the cursor itself. | |
895 var connectors = this.cursorBox.getConnectorPair(d), | |
896 s = connectors[1]; | |
897 this.dragline.attr('x1', s[0]) | |
898 .attr('y1', s[1]) | |
899 .attr('x2', d3.event.x) | |
900 .attr('y2', d3.event.y); | |
901 } | |
902 }, | |
903 | |
904 addRelationDragEnd: function() { | |
905 // Get the line, the endpoint service, and the target <rect>. | |
906 var self = this; | |
907 var rect = self.get('potential_drop_point_rect'); | |
908 var endpoint = self.get('potential_drop_point_service'); | |
909 | |
910 self.buildingRelation = false; | |
911 self.cursorBox = null; | |
912 | |
913 // If we landed on a rect, add relation, otherwise, cancel. | |
914 if (rect) { | |
915 self.service_click_actions | |
916 .ambiguousAddRelationCheck(endpoint, self, rect); | |
917 } else { | |
918 // TODO clean up, abstract | |
919 self.cancelRelationBuild(); | |
920 self.addRelation(); // Will clear the state. | |
921 } | |
922 }, | |
923 removeRelation: function(d, context, view, confirmButton) { | |
924 var env = this.get('component').get('env'), | |
925 endpoints = d.endpoints, | |
926 relationElement = Y.one(context.parentNode).one('.relation'); | |
927 utils.addSVGClass(relationElement, 'to-remove pending-relation'); | |
928 env.remove_relation( | |
929 endpoints[0][0] + ':' + endpoints[0][1].name, | |
930 endpoints[1][0] + ':' + endpoints[1][1].name, | |
931 Y.bind(this._removeRelationCallback, this, view, | |
932 relationElement, d.relation_id, confirmButton)); | |
933 }, | |
934 | |
935 _removeRelationCallback: function(view, | |
936 relationElement, relationId, confirmButton, ev) { | |
937 var db = this.get('component').get('db'), | |
938 service = this.get('model'); | |
939 if (ev.err) { | |
940 db.notifications.add( | |
941 new models.Notification({ | |
942 title: 'Error deleting relation', | |
943 message: 'Relation ' + ev.endpoint_a + ' to ' + ev.endpoint_b, | |
944 level: 'error' | |
945 }) | |
946 ); | |
947 utils.removeSVGClass(this.relationElement, | |
948 'to-remove pending-relation'); | |
949 } else { | |
950 // Remove the relation from the DB. | |
951 db.relations.remove(db.relations.getById(relationId)); | |
952 // Redraw the graph and reattach events. | |
953 db.fire('update'); | |
954 } | |
955 view.get('rmrelation_dialog').hide(); | |
956 view.get('rmrelation_dialog').destroy(); | |
957 confirmButton.set('disabled', false); | |
958 }, | |
959 | |
960 removeRelationConfirm: function(d, context, view) { | |
961 // Destroy the dialog if it already exists to prevent cluttering | |
962 // up the DOM. | |
963 if (!Y.Lang.isUndefined(view.get('rmrelation_dialog'))) { | |
964 view.get('rmrelation_dialog').destroy(); | |
965 } | |
966 view.set('rmrelation_dialog', views.createModalPanel( | |
967 'Are you sure you want to remove this relation? ' + | |
968 'This cannot be undone.', | |
969 '#rmrelation-modal-panel', | |
970 'Remove Relation', | |
971 Y.bind(function(ev) { | |
972 ev.preventDefault(); | |
973 var confirmButton = ev.target; | |
974 confirmButton.set('disabled', true); | |
975 view.removeRelation(d, context, view, confirmButton); | |
976 }, | |
977 this))); | |
978 }, | |
979 | |
980 cancelRelationBuild: function() { | |
981 var vis = this.get('component').vis; | |
982 if (this.dragline) { | |
983 // Get rid of our drag line | |
984 this.dragline.remove(); | |
985 this.dragline = null; | |
986 } | |
987 this.clickAddRelation = null; | |
988 this.set('currentServiceClickAction', 'toggleControlPanel'); | |
989 this.buildingRelation = false; | |
990 this.show(vis.selectAll('.service')) | |
991 .classed('selectable-service', false); | |
992 }, | |
993 | |
994 /** | 652 /** |
995 * The user clicked on the environment view background. | 653 * The user clicked on the environment view background. |
996 * | 654 * |
997 * If we are in the middle of adding a relation, cancel the relation | 655 * If we are in the middle of adding a relation, cancel the relation |
998 * adding. | 656 * adding. |
999 * | 657 * |
1000 * @method backgroundClicked | 658 * @method backgroundClicked |
1001 * @return {undefined} Side effects only. | 659 * @return {undefined} Side effects only. |
1002 */ | 660 */ |
1003 backgroundClicked: function() { | 661 backgroundClicked: function() { |
1004 if (this.clickAddRelation) { | 662 var topo = this.get('component'); |
1005 this.cancelRelationBuild(); | 663 topo.fire('clearState'); |
1006 } | |
1007 }, | |
1008 | |
1009 /** | |
1010 * An "add relation" action has been initiated by the user. | |
1011 * | |
1012 * @method startRelation | |
1013 * @param {object} service The service that is the source of the | |
1014 * relation. | |
1015 * @return {undefined} Side effects only. | |
1016 */ | |
1017 startRelation: function(service) { | |
1018 // Set flags on the view that indicate we are building a relation. | |
1019 var vis = this.get('component').vis; | |
1020 | |
1021 this.buildingRelation = true; | |
1022 this.clickAddRelation = true; | |
1023 | |
1024 this.show(vis.selectAll('.service')); | |
1025 | |
1026 var db = this.get('component').get('db'), | |
1027 getServiceEndpoints = this.get('component') | |
1028 .get('getServiceEndpoints'), | |
1029 endpoints = models.getEndpoints( | |
1030 service, getServiceEndpoints(), db), | |
1031 // Transform endpoints into a list of relatable services (to the | |
1032 // service). | |
1033 possible_relations = Y.Array.map( | |
1034 Y.Array.flatten(Y.Object.values(endpoints)), | |
1035 function(ep) {return ep.service;}), | |
1036 invalidRelationTargets = {}; | |
1037 | |
1038 // Iterate services and invert the possibles list. | |
1039 db.services.each(function(s) { | |
1040 if (Y.Array.indexOf(possible_relations, | |
1041 s.get('id')) === -1) { | |
1042 invalidRelationTargets[s.get('id')] = true; | |
1043 } | |
1044 }); | |
1045 | |
1046 // Fade elements to which we can't relate. | |
1047 // Rather than two loops this marks | |
1048 // all services as selectable and then | |
1049 // removes the invalid ones. | |
1050 this.fade(vis.selectAll('.service') | |
1051 .classed('selectable-service', true) | |
1052 .filter(function(d) { | |
1053 return (d.id in invalidRelationTargets && | |
1054 d.id !== service.id); | |
1055 })) | |
1056 .classed('selectable-service', false); | |
1057 | |
1058 // Store possible endpoints. | |
1059 this.set('addRelationStart_possibleEndpoints', endpoints); | |
1060 // Set click action. | |
1061 this.set('currentServiceClickAction', 'ambiguousAddRelationCheck'); | |
1062 }, | 664 }, |
1063 | 665 |
1064 /* | 666 /* |
1065 * Event handler to show the graph-list picker | 667 * Event handler to show the graph-list picker |
1066 */ | 668 */ |
1067 showGraphListPicker: function(evt) { | 669 showGraphListPicker: function(evt) { |
1068 var container = this.get('container'), | 670 var container = this.get('container'), |
1069 picker = container.one('.graph-list-picker'); | 671 picker = container.one('.graph-list-picker'); |
1070 picker.addClass('inactive'); | 672 picker.addClass('inactive'); |
1071 picker.one('.picker-expanded').addClass('active'); | 673 picker.one('.picker-expanded').addClass('active'); |
1072 }, | 674 }, |
1073 | 675 |
1074 /* | 676 /* |
1075 * Event handler to hide the graph-list picker | 677 * Event handler to hide the graph-list picker |
1076 */ | 678 */ |
1077 hideGraphListPicker: function(evt) { | 679 hideGraphListPicker: function(evt) { |
1078 var container = this.get('container'), | 680 var container = this.get('container'), |
1079 picker = container.one('.graph-list-picker'); | 681 picker = container.one('.graph-list-picker'); |
1080 picker.removeClass('inactive'); | 682 picker.removeClass('inactive'); |
1081 picker.one('.picker-expanded').removeClass('active'); | 683 picker.one('.picker-expanded').removeClass('active'); |
1082 }, | 684 }, |
1083 | 685 |
1084 /** | |
1085 * Show subordinate relations for a service. | |
1086 * | |
1087 * @method showSubordinateRelations | |
1088 * @param {Object} subordinate The sub-rel-block g element in the form | |
1089 * of a DOM node. | |
1090 * @return {undefined} nothing. | |
1091 */ | |
1092 showSubordinateRelations: function(subordinate) { | |
1093 this.keepSubRelationsVisible = true; | |
1094 utils.addSVGClass(Y.one(subordinate).one('.sub-rel-count'), 'active'); | |
1095 }, | |
1096 | |
1097 /** | |
1098 * Hide subordinate relations. | |
1099 * | |
1100 * @method hideSubordinateRelations | |
1101 * @return {undefined} nothing. | |
1102 */ | |
1103 hideSubordinateRelations: function() { | |
1104 var container = this.get('container'); | |
1105 utils.removeSVGClass('.subordinate-rel-group', 'active'); | |
1106 this.keepSubRelationsVisible = false; | |
1107 utils.removeSVGClass(container.one('.sub-rel-count.active'), | |
1108 'active'); | |
1109 }, | |
1110 | |
1111 /* | 686 /* |
1112 * Set the visualization size based on the viewport | 687 * Set the visualization size based on the viewport |
1113 */ | 688 */ |
1114 setSizesFromViewport: function() { | 689 setSizesFromViewport: function() { |
1115 // This event allows other page components that may unintentionally | 690 // This event allows other page components that may unintentionally |
1116 // affect the page size, such as the charm panel, to get out of the | 691 // affect the page size, such as the charm panel, to get out of the |
1117 // way before we compute sizes. Note the | 692 // way before we compute sizes. Note the |
1118 // "afterPageSizeRecalculation" event at the end of this function. | 693 // "afterPageSizeRecalculation" event at the end of this function. |
1119 // start with some reasonable defaults | 694 // start with some reasonable defaults |
1120 console.log('setSizesFromViewPort', this, arguments); | 695 console.log('setSizesFromViewPort', this, arguments); |
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1156 topo.fire('afterPageSizeRecalculation'); | 731 topo.fire('afterPageSizeRecalculation'); |
1157 }, | 732 }, |
1158 | 733 |
1159 /* | 734 /* |
1160 * Update the location of the active service panel | 735 * Update the location of the active service panel |
1161 */ | 736 */ |
1162 updateServiceMenuLocation: function() { | 737 updateServiceMenuLocation: function() { |
1163 var topo = this.get('component'), | 738 var topo = this.get('component'), |
1164 container = this.get('container'), | 739 container = this.get('container'), |
1165 cp = container.one('.environment-menu.active'), | 740 cp = container.one('.environment-menu.active'), |
1166 service = this.get('active_service'), | 741 service = topo.get('active_service'), |
1167 tr = topo.get('translate'), | 742 tr = topo.get('translate'), |
1168 z = topo.get('scale'); | 743 z = topo.get('scale'); |
1169 | 744 |
1170 if (service && cp) { | 745 if (service && cp) { |
1171 var cp_width = cp.getClientRect().width, | 746 var cp_width = cp.getClientRect().width, |
1172 menu_left = service.x * z + service.w * z / 2 < | 747 menu_left = service.x * z + service.w * z / 2 < |
1173 this.width * z / 2, | 748 this.width * z / 2, |
1174 service_center = service.getRelativeCenter(); | 749 service_center = service.getRelativeCenter(); |
1175 if (menu_left) { | 750 if (menu_left) { |
1176 cp.removeClass('left') | 751 cp.removeClass('left') |
(...skipping 10 matching lines...) Expand all Loading... | |
1187 // service is left of the midline, display it to the | 762 // service is left of the midline, display it to the |
1188 // right, and vice versa. | 763 // right, and vice versa. |
1189 cp.setStyles({ | 764 cp.setStyles({ |
1190 'top': service.y * z + tr[1] + (service_center[1] * z) - 68, | 765 'top': service.y * z + tr[1] + (service_center[1] * z) - 68, |
1191 'left': service.x * z + | 766 'left': service.x * z + |
1192 (menu_left ? service.w * z : -(cp_width)) + tr[0] | 767 (menu_left ? service.w * z : -(cp_width)) + tr[0] |
1193 }); | 768 }); |
1194 } | 769 } |
1195 }, | 770 }, |
1196 | 771 |
1197 serviceMouseEnter: function(d, context) { | |
1198 var rect = Y.one(this); | |
1199 // Do not fire if this service isn't selectable. | |
1200 if (!utils.hasSVGClass(rect, 'selectable-service')) { | |
1201 return; | |
1202 } | |
1203 | |
1204 // Do not fire unless we're within the service box. | |
1205 var topo = context.get('component'), | |
1206 container = context.get('container'), | |
1207 mouse_coords = d3.mouse(container.one('svg').getDOMNode()); | |
1208 if (!d.containsPoint(mouse_coords, topo.zoom)) { | |
1209 return; | |
1210 } | |
1211 | |
1212 // Do not fire if we're on the same service. | |
1213 if (d === context.get('addRelationStart_service')) { | |
1214 return; | |
1215 } | |
1216 | |
1217 context.set('potential_drop_point_service', d); | |
1218 context.set('potential_drop_point_rect', rect); | |
1219 utils.addSVGClass(rect, 'hover'); | |
1220 | |
1221 // If we have an active dragline, stop redrawing it on mousemove | |
1222 // and draw the line between the two nearest connector points of | |
1223 // the two services. | |
1224 if (context.dragline) { | |
1225 var connectors = d.getConnectorPair( | |
1226 context.get('addRelationStart_service')), | |
1227 s = connectors[0], | |
1228 t = connectors[1]; | |
1229 context.dragline.attr('x1', t[0]) | |
1230 .attr('y1', t[1]) | |
1231 .attr('x2', s[0]) | |
1232 .attr('y2', s[1]) | |
1233 .attr('class', 'relation pending-relation dragline'); | |
1234 } | |
1235 }, | |
1236 | |
1237 serviceMouseLeave: function(d, self) { | |
1238 // Do not fire if we aren't looking for a relation endpoint. | |
1239 if (!self.get('potential_drop_point_rect')) { | |
1240 return; | |
1241 } | |
1242 | |
1243 // Do not fire if we're within the service box. | |
1244 var topo = this.get('component'), | |
1245 container = self.get('container'), | |
1246 mouse_coords = d3.mouse(container.one('svg').getDOMNode()); | |
1247 if (d.containsPoint(mouse_coords, topo.zoom)) { | |
1248 return; | |
1249 } | |
1250 var rect = Y.one(this).one('.service-border'); | |
1251 self.set('potential_drop_point_service', null); | |
1252 self.set('potential_drop_point_rect', null); | |
1253 utils.removeSVGClass(rect, 'hover'); | |
1254 | |
1255 if (self.dragline) { | |
1256 self.dragline.attr('class', | |
1257 'relation pending-relation dragline dragging'); | |
1258 } | |
1259 }, | |
1260 | |
1261 subRelBlockMouseEnter: function(d, self) { | |
1262 // Add an 'active' class to all of the subordinate relations | |
1263 // belonging to this service. | |
1264 self.subordinateRelationsForService(d) | |
1265 .forEach(function(p) { | |
1266 utils.addSVGClass('#' + p.id, 'active'); | |
1267 }); | |
1268 }, | |
1269 | |
1270 subRelBlockMouseLeave: function(d, self) { | |
1271 // Remove 'active' class from all subordinate relations. | |
1272 if (!self.keepSubRelationsVisible) { | |
1273 utils.removeSVGClass('.subordinate-rel-group', 'active'); | |
1274 } | |
1275 }, | |
1276 | |
1277 /** | |
1278 * Toggle the visibility of subordinate relations for visibility | |
1279 * or removal. | |
1280 * @param {object} d The data-bound object (the subordinate). | |
1281 * @param {object} self The view. | |
1282 **/ | |
1283 subRelBlockClick: function(d, self) { | |
1284 if (self.keepSubRelationsVisible) { | |
1285 self.hideSubordinateRelations(); | |
1286 } else { | |
1287 self.showSubordinateRelations(this); | |
1288 } | |
1289 }, | |
1290 | |
1291 /* | 772 /* |
1292 * Actions to be called on clicking a service. | 773 * Actions to be called on clicking a service. |
1293 */ | 774 */ |
1294 service_click_actions: { | 775 service_click_actions: { |
1295 /* | 776 /* |
1296 * Default action: show or hide control panel. | 777 * Default action: show or hide control panel. |
1297 */ | 778 */ |
1298 toggleControlPanel: function(m, view, context) { | 779 toggleControlPanel: function(m, view, context) { |
1299 var container = view.get('container'), | 780 var container = view.get('container'), |
781 topo = view.get('component'), | |
1300 cp = container.one('#service-menu'); | 782 cp = container.one('#service-menu'); |
1301 | 783 |
1302 if (cp.hasClass('active') || !m) { | 784 if (cp.hasClass('active') || !m) { |
1303 cp.removeClass('active'); | 785 cp.removeClass('active'); |
1304 view.set('active_service', null); | 786 topo.set('active_service', null); |
1305 view.set('active_context', null); | 787 topo.set('active_context', null); |
1306 } else { | 788 } else { |
1307 view.set('active_service', m); | 789 topo.set('active_service', m); |
1308 view.set('active_context', context); | 790 topo.set('active_context', context); |
1309 cp.addClass('active'); | 791 cp.addClass('active'); |
1310 view.updateServiceMenuLocation(); | 792 view.updateServiceMenuLocation(); |
1311 } | 793 } |
1312 }, | 794 }, |
1313 | 795 |
1314 /* | 796 /* |
1315 * View a service | 797 * View a service |
1316 */ | 798 */ |
1317 show_service: function(m, context) { | 799 show_service: function(m, context) { |
1318 var topo = context.get('component'); | 800 var topo = context.get('component'); |
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1371 } else { | 853 } else { |
1372 var relations = db.relations.get_relations_for_service(service); | 854 var relations = db.relations.get_relations_for_service(service); |
1373 Y.each(relations, function(relation) { | 855 Y.each(relations, function(relation) { |
1374 relation.destroy(); | 856 relation.destroy(); |
1375 }); | 857 }); |
1376 service.destroy(); | 858 service.destroy(); |
1377 view.get('destroy_dialog').hide(); | 859 view.get('destroy_dialog').hide(); |
1378 db.fire('update'); | 860 db.fire('update'); |
1379 } | 861 } |
1380 btn.set('disabled', false); | 862 btn.set('disabled', false); |
1381 }, | 863 } |
1382 | 864 |
1383 | 865 |
1384 /* | |
1385 * Fired when clicking the first service in the add relation | |
1386 * flow. | |
1387 */ | |
1388 addRelationStart: function(m, view, context) { | |
1389 var service = view.serviceForBox(m); | |
1390 view.startRelation(service); | |
1391 // Store start service in attrs. | |
1392 view.set('addRelationStart_service', m); | |
1393 }, | |
1394 | |
1395 /* | |
1396 * Test if the pending relation is ambiguous. Display a menu if so, | |
1397 * create the relation if not. | |
1398 */ | |
1399 ambiguousAddRelationCheck: function(m, view, context) { | |
1400 var endpoints = view.get( | |
1401 'addRelationStart_possibleEndpoints')[m.id], | |
1402 container = view.get('container'), | |
1403 topo = view.get('component'); | |
1404 | |
1405 if (endpoints && endpoints.length === 1) { | |
1406 // Create a relation with the only available endpoint. | |
1407 var ep = endpoints[0], | |
1408 endpoints_item = [ | |
1409 [ep[0].service, { | |
1410 name: ep[0].name, | |
1411 role: 'server' }], | |
1412 [ep[1].service, { | |
1413 name: ep[1].name, | |
1414 role: 'client' }]]; | |
1415 view.service_click_actions | |
1416 .addRelationEnd(endpoints_item, view, context); | |
1417 return; | |
1418 } | |
1419 | |
1420 // Sort the endpoints alphabetically by relation name. | |
1421 endpoints = endpoints.sort(function(a, b) { | |
1422 return a[0].name + a[1].name < b[0].name + b[1].name; | |
1423 }); | |
1424 | |
1425 // Stop rubberbanding on mousemove. | |
1426 view.clickAddRelation = null; | |
1427 | |
1428 // Display menu with available endpoints. | |
1429 var menu = container.one('#ambiguous-relation-menu'); | |
1430 if (menu.one('.menu')) { | |
1431 menu.one('.menu').remove(true); | |
1432 } | |
1433 | |
1434 menu.append(Templates | |
1435 .ambiguousRelationList({endpoints: endpoints})); | |
1436 | |
1437 // For each endpoint choice, bind an an event to 'click' to | |
1438 // add the specified relation. | |
1439 menu.all('li').on('click', function(evt) { | |
1440 if (evt.currentTarget.hasClass('cancel')) { | |
1441 return; | |
1442 } | |
1443 var el = evt.currentTarget, | |
1444 endpoints_item = [ | |
1445 [el.getData('startservice'), { | |
1446 name: el.getData('startname'), | |
1447 role: 'server' }], | |
1448 [el.getData('endservice'), { | |
1449 name: el.getData('endname'), | |
1450 role: 'client' }]]; | |
1451 menu.removeClass('active'); | |
1452 view.service_click_actions | |
1453 .addRelationEnd(endpoints_item, view, context); | |
1454 }); | |
1455 | |
1456 // Add a cancel item. | |
1457 menu.one('.cancel').on('click', function(evt) { | |
1458 menu.removeClass('active'); | |
1459 view.cancelRelationBuild(); | |
1460 }); | |
1461 | |
1462 // Display the menu at the service endpoint. | |
1463 var tr = topo.zoom.translate(), | |
1464 z = topo.zoom.scale(); | |
1465 menu.setStyle('top', m.y * z + tr[1]); | |
1466 menu.setStyle('left', m.x * z + m.w * z + tr[0]); | |
1467 menu.addClass('active'); | |
1468 view.set('active_service', m); | |
1469 view.set('active_context', context); | |
1470 view.updateServiceMenuLocation(); | |
1471 }, | |
1472 | |
1473 /* | |
1474 * Fired when clicking the second service is clicked in the | |
1475 * add relation flow. | |
1476 * | |
1477 * :param endpoints: array of two endpoints, each in the form | |
1478 * ['service name', { | |
1479 * name: 'endpoint type', | |
1480 * role: 'client or server' | |
1481 * }] | |
1482 */ | |
1483 addRelationEnd: function(endpoints, view, context) { | |
1484 // Redisplay all services | |
1485 view.cancelRelationBuild(); | |
1486 | |
1487 // Get the vis, and links, build the new relation. | |
1488 var vis = view.get('component').vis, | |
1489 env = view.get('component').get('env'), | |
1490 db = view.get('component').get('db'), | |
1491 source = view.get('addRelationStart_service'), | |
1492 relation_id = 'pending:' + endpoints[0][0] + endpoints[1][0]; | |
1493 | |
1494 if (endpoints[0][0] === endpoints[1][0]) { | |
1495 view.set('currentServiceClickAction', 'toggleControlPanel'); | |
1496 return; | |
1497 } | |
1498 | |
1499 // Create a pending relation in the database between the | |
1500 // two services. | |
1501 db.relations.create({ | |
1502 relation_id: relation_id, | |
1503 display_name: 'pending', | |
1504 endpoints: endpoints, | |
1505 pending: true | |
1506 }); | |
1507 | |
1508 // Firing the update event on the db will properly redraw the | |
1509 // graph and reattach events. | |
1510 //db.fire('update'); | |
1511 view.get('component').bindAllD3Events(); | |
1512 view.update(); | |
1513 | |
1514 // Fire event to add relation in juju. | |
1515 // This needs to specify interface in the future. | |
1516 env.add_relation( | |
1517 endpoints[0][0] + ':' + endpoints[0][1].name, | |
1518 endpoints[1][0] + ':' + endpoints[1][1].name, | |
1519 Y.bind(this._addRelationCallback, this, view, relation_id) | |
1520 ); | |
1521 view.set('currentServiceClickAction', 'toggleControlPanel'); | |
1522 }, | |
1523 | |
1524 _addRelationCallback: function(view, relation_id, ev) { | |
1525 var db = view.get('component').get('db'); | |
1526 // Remove our pending relation from the DB, error or no. | |
1527 db.relations.remove( | |
1528 db.relations.getById(relation_id)); | |
1529 if (ev.err) { | |
1530 db.notifications.add( | |
1531 new models.Notification({ | |
1532 title: 'Error adding relation', | |
1533 message: 'Relation ' + ev.endpoint_a + | |
1534 ' to ' + ev.endpoint_b, | |
1535 level: 'error' | |
1536 }) | |
1537 ); | |
1538 } else { | |
1539 // Create a relation in the database between the two services. | |
1540 var result = ev.result, | |
1541 endpoints = Y.Array.map(result.endpoints, function(item) { | |
1542 var id = Y.Object.keys(item)[0]; | |
1543 return [id, item[id]]; | |
1544 }); | |
1545 db.relations.create({ | |
1546 relation_id: ev.result.id, | |
1547 type: result['interface'], | |
1548 endpoints: endpoints, | |
1549 pending: false, | |
1550 scope: result.scope, | |
1551 // endpoints[1][1].name should be the same | |
1552 display_name: endpoints[0][1].name | |
1553 }); | |
1554 } | |
1555 // Redraw the graph and reattach events. | |
1556 db.fire('update'); | |
1557 } | |
1558 } | 866 } |
1559 }, { | 867 }, { |
1560 ATTRS: {} | 868 ATTRS: {} |
1561 | 869 |
1562 }); | 870 }); |
1563 views.MegaModule = MegaModule; | 871 views.MegaModule = MegaModule; |
1564 }, '0.1.0', { | 872 }, '0.1.0', { |
1565 requires: [ | 873 requires: [ |
1566 'd3', | 874 'd3', |
1567 'd3-components', | 875 'd3-components', |
1568 'juju-templates', | 876 'juju-templates', |
1569 'node', | 877 'node', |
1570 'event', | 878 'event', |
1571 'juju-models', | 879 'juju-models', |
1572 'juju-env' | 880 'juju-env' |
1573 ] | 881 ] |
1574 }); | 882 }); |
OLD | NEW |