Simon Hunt

GUI -- TopoView - Implemented much of the node selection logic. (WIP)

- introduced topoSelect.js.

Change-Id: Ic843c7d8dc2249fe0cb8c33de60dce12c07aea44
...@@ -77,6 +77,7 @@ ...@@ -77,6 +77,7 @@
77 width: panelWidth, 77 width: panelWidth,
78 height: panelHeight, 78 height: panelHeight,
79 isVisible: panelIsVisible, 79 isVisible: panelIsVisible,
80 + classed: classed,
80 el: panelEl 81 el: panelEl
81 }; 82 };
82 83
...@@ -146,6 +147,10 @@ ...@@ -146,6 +147,10 @@
146 return p.on; 147 return p.on;
147 } 148 }
148 149
150 + function classed(cls, bool) {
151 + return p.el.classed(cls, bool);
152 + }
153 +
149 function panelEl() { 154 function panelEl() {
150 return p.el; 155 return p.el;
151 } 156 }
......
...@@ -34,6 +34,7 @@ ...@@ -34,6 +34,7 @@
34 $log = _$log_; 34 $log = _$log_;
35 fs = _fs_; 35 fs = _fs_;
36 36
37 + // TODO: change 'force' ref to be 'force.alpha' ref.
37 function createDragBehavior(force, selectCb, atDragEnd, 38 function createDragBehavior(force, selectCb, atDragEnd,
38 dragEnabled, clickEnabled) { 39 dragEnabled, clickEnabled) {
39 var draggedThreshold = d3.scale.linear() 40 var draggedThreshold = d3.scale.linear()
......
...@@ -82,9 +82,10 @@ ...@@ -82,9 +82,10 @@
82 <script src="view/topo/topo.js"></script> 82 <script src="view/topo/topo.js"></script>
83 <script src="view/topo/topoEvent.js"></script> 83 <script src="view/topo/topoEvent.js"></script>
84 <script src="view/topo/topoForce.js"></script> 84 <script src="view/topo/topoForce.js"></script>
85 + <script src="view/topo/topoInst.js"></script>
85 <script src="view/topo/topoModel.js"></script> 86 <script src="view/topo/topoModel.js"></script>
86 <script src="view/topo/topoPanel.js"></script> 87 <script src="view/topo/topoPanel.js"></script>
87 - <script src="view/topo/topoInst.js"></script> 88 + <script src="view/topo/topoSelect.js"></script>
88 <script src="view/device/device.js"></script> 89 <script src="view/device/device.js"></script>
89 <!-- TODO: inject javascript refs server-side --> 90 <!-- TODO: inject javascript refs server-side -->
90 91
......
...@@ -72,71 +72,121 @@ ...@@ -72,71 +72,121 @@
72 72
73 #topo-p-summary { 73 #topo-p-summary {
74 /* Base css from panel.css */ 74 /* Base css from panel.css */
75 -
76 } 75 }
77 76
78 -#topo-p-summary svg { 77 +/* --- Topo Detail Panel --- */
78 +
79 +#topo-p-detail {
80 + /* Base css from panel.css */
81 + top: 320px;
82 +}
83 +
84 +/* --- general topo-panel styling --- */
85 +
86 +.topo-p svg {
79 display: inline-block; 87 display: inline-block;
80 width: 42px; 88 width: 42px;
81 height: 42px; 89 height: 42px;
82 } 90 }
83 91
84 -#topo-p-summary h2 { 92 +.light .topo-p svg .glyph {
93 + fill: #222;
94 +}
95 +
96 +.dark .topo-p svg .glyph.overlay {
97 + fill: #222;
98 +}
99 +
100 +.dark .topo-p svg .glyph {
101 + fill: #ddd;
102 +}
103 +.light .topo-p svg .glyph.overlay {
104 + fill: #fff;
105 +}
106 +
107 +
108 +.topo-p h2 {
85 position: absolute; 109 position: absolute;
86 margin: 0 4px; 110 margin: 0 4px;
87 top: 20px; 111 top: 20px;
88 left: 50px; 112 left: 50px;
89 } 113 }
90 -.light #topo-p-summary h2 { 114 +.light .topo-p h2 {
91 color: black; 115 color: black;
92 } 116 }
93 -.dark #topo-p-summary h2 { 117 +.dark .topo-p h2 {
94 color: #ddd; 118 color: #ddd;
95 } 119 }
96 120
97 -#topo-p-summary h3 { 121 +.topo-p h3 {
98 margin: 0 4px; 122 margin: 0 4px;
99 top: 20px; 123 top: 20px;
100 left: 50px; 124 left: 50px;
101 } 125 }
102 -.light #topo-p-summary h3 { 126 +.light .topo-p h3 {
103 color: black; 127 color: black;
104 } 128 }
105 -.dark #topo-p-summary h3 { 129 +.dark .topo-p h3 {
106 color: #ddd; 130 color: #ddd;
107 } 131 }
108 132
109 -#topo-p-summary p, table { 133 +.topo-p p, table {
110 margin: 4px 4px; 134 margin: 4px 4px;
111 } 135 }
112 136
113 -#topo-p-summary td.label { 137 +.topo-p td.label {
114 font-style: italic; 138 font-style: italic;
115 padding-right: 12px; 139 padding-right: 12px;
116 /* works for both light and dark themes ... */ 140 /* works for both light and dark themes ... */
117 color: #777; 141 color: #777;
118 } 142 }
119 143
120 -#topo-p-summary td.value { 144 +.topo-p td.value {
121 } 145 }
122 146
123 -#topo-p-summary hr { 147 +.topo-p hr {
124 height: 1px; 148 height: 1px;
125 border: 0; 149 border: 0;
126 } 150 }
127 -.light #topo-p-summary hr { 151 +.light .topo-p hr {
128 background-color: #ccc; 152 background-color: #ccc;
129 color: #ccc; 153 color: #ccc;
130 } 154 }
131 -.dark #topo-p-summary hr { 155 +.dark .topo-p hr {
132 background-color: #888; 156 background-color: #888;
133 color: #888; 157 color: #888;
134 } 158 }
135 159
136 160
137 -/* --- Topo Detail Panel --- */ 161 +.topo-p .actionBtn {
162 + margin: 6px 12px;
163 + padding: 2px 6px;
164 + font-size: 9pt;
165 + cursor: pointer;
166 + width: 200px;
167 + text-align: center;
168 + border-radius: 4px;
169 +}
170 +.light .topo-p .actionBtn {
171 + border: 2px solid #ddd;
172 + color: #eee;
173 + background: #888;
174 +}
175 +.dark .topo-p .actionBtn {
176 + border: 2px solid #222;
177 + color: #888;
178 + background: #444;
179 +}
180 +
181 +.light .topo-p .actionBtn:hover {
182 + color: #eee;
183 + background: #444;
184 +}
185 +.dark .topo-p .actionBtn:hover {
186 + color: #eee;
187 + background: #666;
188 +}
138 189
139 -/* TODO: add CSS rules */
140 190
141 191
142 /* --- Topo Instance Panel --- */ 192 /* --- Topo Instance Panel --- */
......
...@@ -66,7 +66,7 @@ ...@@ -66,7 +66,7 @@
66 66
67 //E: [equalizeMasters, 'Equalize mastership roles'], 67 //E: [equalizeMasters, 'Equalize mastership roles'],
68 68
69 - //esc: handleEscape, 69 + esc: handleEscape,
70 70
71 _helpFormat: [ 71 _helpFormat: [
72 ['O', 'I', 'D', '-', 'H', 'M', 'B', 'P' ], 72 ['O', 'I', 'D', '-', 'H', 'M', 'B', 'P' ],
...@@ -85,12 +85,29 @@ ...@@ -85,12 +85,29 @@
85 ]; 85 ];
86 } 86 }
87 87
88 + // --- Keystroke functions -------------------------------------------
88 89
89 function toggleInstances() { 90 function toggleInstances() {
90 tis.toggle(); 91 tis.toggle();
91 tfs.updateDeviceColors(); 92 tfs.updateDeviceColors();
92 } 93 }
93 94
95 + function resetZoom() {
96 + zoomer.reset();
97 + }
98 +
99 + function handleEscape() {
100 + $log.debug("TODO: handle-ESCAPE...");
101 + // if showingAffinity: cancelAffinity
102 +
103 + // else if showingDetails: deselectAll
104 +
105 + // else if oiBox visible: hide oiBox
106 +
107 + // else if summary panel visible: cancel Summary
108 +
109 + // else: hoverMode = hoverModeNone
110 + }
94 111
95 // --- Glyphs, Icons, and the like ----------------------------------- 112 // --- Glyphs, Icons, and the like -----------------------------------
96 113
...@@ -124,10 +141,6 @@ ...@@ -124,10 +141,6 @@
124 }); 141 });
125 } 142 }
126 143
127 - function resetZoom() {
128 - zoomer.reset();
129 - }
130 -
131 144
132 // callback invoked when the SVG view has been resized.. 145 // callback invoked when the SVG view has been resized..
133 function svgResized(s) { 146 function svgResized(s) {
......
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
27 'use strict'; 27 'use strict';
28 28
29 // injected refs 29 // injected refs
30 - var $log, wss, wes, tps, tis, tfs; 30 + var $log, wss, wes, tps, tis, tfs, tss;
31 31
32 // internal state 32 // internal state
33 var wsock, evApis; 33 var wsock, evApis;
...@@ -37,9 +37,13 @@ ...@@ -37,9 +37,13 @@
37 function bindApis() { 37 function bindApis() {
38 evApis = { 38 evApis = {
39 showSummary: tps, 39 showSummary: tps,
40 +
41 + showDetails: tss,
42 +
40 addInstance: tis, 43 addInstance: tis,
41 updateInstance: tis, 44 updateInstance: tis,
42 removeInstance: tis, 45 removeInstance: tis,
46 +
43 addDevice: tfs, 47 addDevice: tfs,
44 updateDevice: tfs, 48 updateDevice: tfs,
45 removeDevice: tfs, 49 removeDevice: tfs,
...@@ -100,14 +104,16 @@ ...@@ -100,14 +104,16 @@
100 .factory('TopoEventService', 104 .factory('TopoEventService',
101 ['$log', '$location', 'WebSocketService', 'WsEventService', 105 ['$log', '$location', 'WebSocketService', 'WsEventService',
102 'TopoPanelService', 'TopoInstService', 'TopoForceService', 106 'TopoPanelService', 'TopoInstService', 'TopoForceService',
107 + 'TopoSelectService',
103 108
104 - function (_$log_, $loc, _wss_, _wes_, _tps_, _tis_, _tfs_) { 109 + function (_$log_, $loc, _wss_, _wes_, _tps_, _tis_, _tfs_, _tss_) {
105 $log = _$log_; 110 $log = _$log_;
106 wss = _wss_; 111 wss = _wss_;
107 wes = _wes_; 112 wes = _wes_;
108 tps = _tps_; 113 tps = _tps_;
109 tis = _tis_; 114 tis = _tis_;
110 tfs = _tfs_; 115 tfs = _tfs_;
116 + tss = _tss_;
111 117
112 bindApis(); 118 bindApis();
113 119
......
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
23 'use strict'; 23 'use strict';
24 24
25 // injected refs 25 // injected refs
26 - var $log, fs, sus, is, ts, flash, tis, tms, icfg, uplink; 26 + var $log, fs, sus, is, ts, flash, tis, tms, tss, icfg, uplink;
27 27
28 // configuration 28 // configuration
29 var labelConfig = { 29 var labelConfig = {
...@@ -77,10 +77,7 @@ ...@@ -77,10 +77,7 @@
77 showOffline = true, // whether offline devices are displayed 77 showOffline = true, // whether offline devices are displayed
78 oblique = false, // whether we are in the oblique view 78 oblique = false, // whether we are in the oblique view
79 nodeLock = false, // whether nodes can be dragged or not (locked) 79 nodeLock = false, // whether nodes can be dragged or not (locked)
80 - dim, // the dimensions of the force layout [w,h] 80 + dim; // the dimensions of the force layout [w,h]
81 - hovered, // the node over which the mouse is hovering
82 - selections = {}, // what is currently selected
83 - selectOrder = []; // the order in which we made selections
84 81
85 // SVG elements; 82 // SVG elements;
86 var linkG, linkLabelG, nodeG; 83 var linkG, linkLabelG, nodeG;
...@@ -311,8 +308,6 @@ ...@@ -311,8 +308,6 @@
311 .attr('stroke', linkConfig[th].baseColor); 308 .attr('stroke', linkConfig[th].baseColor);
312 } 309 }
313 310
314 -
315 -
316 function removeLinkElement(d) { 311 function removeLinkElement(d) {
317 var idx = fs.find(d.key, network.links, 'key'), 312 var idx = fs.find(d.key, network.links, 'key'),
318 removed; 313 removed;
...@@ -418,34 +413,10 @@ ...@@ -418,34 +413,10 @@
418 }); 413 });
419 } 414 }
420 415
421 - function requestTrafficForMode() {
422 - $log.debug('TODO: requestTrafficForMode()...');
423 - }
424 -
425 416
426 // ========================== 417 // ==========================
427 // === Devices and hosts - D3 rendering 418 // === Devices and hosts - D3 rendering
428 419
429 - function nodeMouseOver(m) {
430 - if (!m.dragStarted) {
431 - $log.debug("MouseOver()...", m);
432 - if (hovered != m) {
433 - hovered = m;
434 - requestTrafficForMode();
435 - }
436 - }
437 - }
438 -
439 - function nodeMouseOut(m) {
440 - if (!m.dragStarted) {
441 - if (hovered) {
442 - hovered = null;
443 - requestTrafficForMode();
444 - }
445 - $log.debug("MouseOut()...", m);
446 - }
447 - }
448 -
449 420
450 // Returns the newly computed bounding box of the rectangle 421 // Returns the newly computed bounding box of the rectangle
451 function adjustRectToFitText(n) { 422 function adjustRectToFitText(n) {
...@@ -568,10 +539,11 @@ ...@@ -568,10 +539,11 @@
568 } 539 }
569 540
570 function unpin() { 541 function unpin() {
571 - if (hovered) { 542 + var hov = tss.hovered();
572 - sendUpdateMeta(hovered, true); 543 + if (hov) {
573 - hovered.fixed = false; 544 + sendUpdateMeta(hov, true);
574 - hovered.el.classed('fixed', false); 545 + hov.fixed = false;
546 + hov.el.classed('fixed', false);
575 fResume(); 547 fResume();
576 } 548 }
577 } 549 }
...@@ -668,8 +640,8 @@ ...@@ -668,8 +640,8 @@
668 opacity: 0 640 opacity: 0
669 }) 641 })
670 .call(drag) 642 .call(drag)
671 - .on('mouseover', nodeMouseOver) 643 + .on('mouseover', tss.nodeMouseOver)
672 - .on('mouseout', nodeMouseOut) 644 + .on('mouseout', tss.nodeMouseOut)
673 .transition() 645 .transition()
674 .attr('opacity', 1); 646 .attr('opacity', 1);
675 647
...@@ -998,72 +970,6 @@ ...@@ -998,72 +970,6 @@
998 } 970 }
999 971
1000 972
1001 - function updateDetailPanel() {
1002 - // TODO update detail panel
1003 - $log.debug("TODO: updateDetailPanel() ...");
1004 - }
1005 -
1006 -
1007 - // ==========================
1008 - // === SELECTION / DESELECTION
1009 -
1010 - function selectObject(obj) {
1011 - var el = this,
1012 - ev = d3.event.sourceEvent,
1013 - n;
1014 -
1015 - if (zoomingOrPanning(ev)) {
1016 - return;
1017 - }
1018 -
1019 - if (el) {
1020 - n = d3.select(el);
1021 - } else {
1022 - node.each(function (d) {
1023 - if (d == obj) {
1024 - n = d3.select(el = this);
1025 - }
1026 - });
1027 - }
1028 - if (!n) return;
1029 -
1030 - if (ev.shiftKey && n.classed('selected')) {
1031 - deselectObject(obj.id);
1032 - updateDetailPanel();
1033 - return;
1034 - }
1035 -
1036 - if (!ev.shiftKey) {
1037 - deselectAll();
1038 - }
1039 -
1040 - selections[obj.id] = { obj: obj, el: el };
1041 - selectOrder.push(obj.id);
1042 -
1043 - n.classed('selected', true);
1044 - updateDeviceColors(obj);
1045 - updateDetailPanel();
1046 - }
1047 -
1048 - function deselectObject(id) {
1049 - var obj = selections[id];
1050 - if (obj) {
1051 - d3.select(obj.el).classed('selected', false);
1052 - delete selections[id];
1053 - fs.removeFromArray(id, selectOrder);
1054 - updateDeviceColors(obj.obj);
1055 - }
1056 - }
1057 -
1058 - function deselectAll() {
1059 - // deselect all nodes in the network...
1060 - node.classed('selected', false);
1061 - selections = {};
1062 - selectOrder = [];
1063 - updateDeviceColors();
1064 - updateDetailPanel();
1065 - }
1066 -
1067 // ========================== 973 // ==========================
1068 // === MOUSE GESTURE HANDLERS 974 // === MOUSE GESTURE HANDLERS
1069 975
...@@ -1103,12 +1009,22 @@ ...@@ -1103,12 +1009,22 @@
1103 }; 1009 };
1104 } 1010 }
1105 1011
1012 + function mkSelectApi(uplink) {
1013 + return {
1014 + node: function () { return node; },
1015 + zoomingOrPanning: zoomingOrPanning,
1016 + updateDeviceColors: updateDeviceColors,
1017 + sendEvent: uplink.sendEvent
1018 + };
1019 + }
1020 +
1106 angular.module('ovTopo') 1021 angular.module('ovTopo')
1107 .factory('TopoForceService', 1022 .factory('TopoForceService',
1108 ['$log', 'FnService', 'SvgUtilService', 'IconService', 'ThemeService', 1023 ['$log', 'FnService', 'SvgUtilService', 'IconService', 'ThemeService',
1109 'FlashService', 'TopoInstService', 'TopoModelService', 1024 'FlashService', 'TopoInstService', 'TopoModelService',
1025 + 'TopoSelectService',
1110 1026
1111 - function (_$log_, _fs_, _sus_, _is_, _ts_, _flash_, _tis_, _tms_) { 1027 + function (_$log_, _fs_, _sus_, _is_, _ts_, _flash_, _tis_, _tms_, _tss_) {
1112 $log = _$log_; 1028 $log = _$log_;
1113 fs = _fs_; 1029 fs = _fs_;
1114 sus = _sus_; 1030 sus = _sus_;
...@@ -1117,6 +1033,7 @@ ...@@ -1117,6 +1033,7 @@
1117 flash = _flash_; 1033 flash = _flash_;
1118 tis = _tis_; 1034 tis = _tis_;
1119 tms = _tms_; 1035 tms = _tms_;
1036 + tss = _tss_;
1120 1037
1121 icfg = is.iconConfig(); 1038 icfg = is.iconConfig();
1122 1039
...@@ -1131,6 +1048,7 @@ ...@@ -1131,6 +1048,7 @@
1131 $log.debug('initForce().. dim = ' + dim); 1048 $log.debug('initForce().. dim = ' + dim);
1132 1049
1133 tms.initModel(mkModelApi(uplink), dim); 1050 tms.initModel(mkModelApi(uplink), dim);
1051 + tss.initSelect(mkSelectApi(uplink));
1134 1052
1135 settings = angular.extend({}, defaultSettings, opts); 1053 settings = angular.extend({}, defaultSettings, opts);
1136 1054
...@@ -1154,7 +1072,7 @@ ...@@ -1154,7 +1072,7 @@
1154 .on('tick', tick); 1072 .on('tick', tick);
1155 1073
1156 drag = sus.createDragBehavior(force, 1074 drag = sus.createDragBehavior(force,
1157 - selectObject, atDragEnd, dragEnabled, clickEnabled); 1075 + tss.selectObject, atDragEnd, dragEnabled, clickEnabled);
1158 } 1076 }
1159 1077
1160 function newDim(_dim_) { 1078 function newDim(_dim_) {
......
...@@ -24,18 +24,21 @@ ...@@ -24,18 +24,21 @@
24 'use strict'; 24 'use strict';
25 25
26 // injected refs 26 // injected refs
27 - var $log, fs, rnd, api; 27 + var $log, fs, rnd;
28 +
29 + // api to topoForce
30 + var api;
31 + /*
32 + projection()
33 + network {...}
34 + restyleLinkElement( ldata )
35 + removeLinkElement( ldata )
36 + */
28 37
29 // shorthand 38 // shorthand
30 var lu, rlk, nodes, links; 39 var lu, rlk, nodes, links;
31 40
32 - // api: 41 + var dim; // dimensions of layout [w,h]
33 - // projection: func()
34 - // network {...}
35 - // restyleLinkElement: func(ldata)
36 - // removeLinkElement: func(ldata)
37 -
38 - var dim; // dimensions of layout, as [w,h]
39 42
40 // configuration 'constants' 43 // configuration 'constants'
41 var defaultLinkType = 'direct', 44 var defaultLinkType = 'direct',
......
...@@ -26,7 +26,8 @@ ...@@ -26,7 +26,8 @@
26 var $log, ps, gs; 26 var $log, ps, gs;
27 27
28 // constants 28 // constants
29 - var idSum = 'topo-p-summary', 29 + var pCls = 'topo-p',
30 + idSum = 'topo-p-summary',
30 idDet = 'topo-p-detail', 31 idDet = 'topo-p-detail',
31 panelOpts = { 32 panelOpts = {
32 width: 260 33 width: 260
...@@ -36,60 +37,134 @@ ...@@ -36,60 +37,134 @@
36 var summaryPanel, 37 var summaryPanel,
37 detailPanel; 38 detailPanel;
38 39
39 - // ==========================
40 - // *** SHOW SUMMARY ***
41 40
42 - function showSummary(data) { 41 + // === -----------------------------------------------------
43 - populateSummary(data); 42 + // Utility functions
44 - showSummaryPanel(); 43 +
44 + function addSep(tbody) {
45 + tbody.append('tr').append('td').attr('colspan', 2).append('hr');
45 } 46 }
46 47
48 + function addProp(tbody, label, value) {
49 + var tr = tbody.append('tr');
50 +
51 + function addCell(cls, txt) {
52 + tr.append('td').attr('class', cls).text(txt);
53 + }
54 + addCell('label', label + ' :');
55 + addCell('value', value);
56 + }
57 +
58 + function listProps(tbody, data) {
59 + data.propOrder.forEach(function(p) {
60 + if (p === '-') {
61 + addSep(tbody);
62 + } else {
63 + addProp(tbody, p, data.props[p]);
64 + }
65 + });
66 + }
67 +
68 + function dpa(x) {
69 + return detailPanel.append(x);
70 + }
71 +
72 + function spa(x) {
73 + return summaryPanel.append(x);
74 + }
75 +
76 + // === -----------------------------------------------------
77 + // Functions for populating the summary panel
78 +
47 function populateSummary(data) { 79 function populateSummary(data) {
48 summaryPanel.empty(); 80 summaryPanel.empty();
49 81
50 - var svg = summaryPanel.append('svg'), 82 + var svg = spa('svg'),
51 - title = summaryPanel.append('h2'), 83 + title = spa('h2'),
52 - table = summaryPanel.append('table'), 84 + table = spa('table'),
53 tbody = table.append('tbody'); 85 tbody = table.append('tbody');
54 86
55 gs.addGlyph(svg, 'node', 40); 87 gs.addGlyph(svg, 'node', 40);
56 gs.addGlyph(svg, 'bird', 24, true, [8,12]); 88 gs.addGlyph(svg, 'bird', 24, true, [8,12]);
57 89
58 title.text(data.id); 90 title.text(data.id);
91 + listProps(tbody, data);
92 + }
59 93
60 - data.propOrder.forEach(function(p) { 94 + // === -----------------------------------------------------
61 - if (p === '-') { 95 + // Functions for populating the detail panel
62 - addSep(tbody); 96 +
63 - } else { 97 + function displaySingle(data) {
64 - addProp(tbody, p, data.props[p]); 98 + detailPanel.empty();
65 - } 99 +
100 + var svg = dpa('svg'),
101 + title = dpa('h2'),
102 + table = dpa('table'),
103 + tbody = table.append('tbody');
104 +
105 + gs.addGlyph(svg, (data.type || 'unknown'), 40);
106 + title.text(data.id);
107 + listProps(tbody, data);
108 + dpa('hr');
109 + }
110 +
111 + function displayMulti(ids) {
112 + detailPanel.empty();
113 +
114 + var title = dpa('h3'),
115 + table = dpa('table'),
116 + tbody = table.append('tbody');
117 +
118 + title.text('Selected Nodes');
119 + ids.forEach(function (d, i) {
120 + addProp(tbody, i+1, d);
66 }); 121 });
122 + dpa('hr');
67 } 123 }
68 124
69 - function addSep(tbody) { 125 + function addAction(text, cb) {
70 - tbody.append('tr').append('td').attr('colspan', 2).append('hr'); 126 + dpa('div')
127 + .classed('actionBtn', true)
128 + .text(text)
129 + .on('click', cb);
71 } 130 }
72 131
73 - function addProp(tbody, label, value) { 132 + // === -----------------------------------------------------
74 - var tr = tbody.append('tr'); 133 + // Event Handlers
75 134
76 - function addCell(cls, txt) { 135 + function showSummary(data) {
77 - tr.append('td').attr('class', cls).text(txt); 136 + populateSummary(data);
78 - } 137 + showSummaryPanel();
79 - addCell('label', label + ' :');
80 - addCell('value', value);
81 } 138 }
82 139
140 +
141 + // === -----------------------------------------------------
142 + // === LOGIC For showing/hiding summary and detail panels...
143 +
83 function showSummaryPanel() { 144 function showSummaryPanel() {
84 summaryPanel.show(); 145 summaryPanel.show();
85 // TODO: augment, once we have the details pane also 146 // TODO: augment, once we have the details pane also
86 } 147 }
87 148
149 + function showDetailPanel() {
150 + // TODO: augment with summary-accomodation-logic
151 + detailPanel.show();
152 + }
153 +
154 + function hideDetailPanel() {
155 + detailPanel.hide();
156 + }
157 +
158 +
159 +
88 // ========================== 160 // ==========================
89 161
90 function initPanels() { 162 function initPanels() {
91 summaryPanel = ps.createPanel(idSum, panelOpts); 163 summaryPanel = ps.createPanel(idSum, panelOpts);
92 detailPanel = ps.createPanel(idDet, panelOpts); 164 detailPanel = ps.createPanel(idDet, panelOpts);
165 +
166 + summaryPanel.classed(pCls, true);
167 + detailPanel.classed(pCls, true);
93 } 168 }
94 169
95 function destroyPanels() { 170 function destroyPanels() {
...@@ -112,7 +187,18 @@ ...@@ -112,7 +187,18 @@
112 return { 187 return {
113 initPanels: initPanels, 188 initPanels: initPanels,
114 destroyPanels: destroyPanels, 189 destroyPanels: destroyPanels,
115 - showSummary: showSummary 190 +
191 + showSummary: showSummary,
192 +
193 + displaySingle: displaySingle,
194 + displayMulti: displayMulti,
195 + addAction: addAction,
196 +
197 + showDetailPanel: showDetailPanel,
198 + hideDetailPanel: hideDetailPanel,
199 +
200 + detailVisible: function () { return detailPanel.isVisible(); },
201 + summaryVisible: function () { return summaryPanel.isVisible(); }
116 }; 202 };
117 }]); 203 }]);
118 }()); 204 }());
......
1 +/*
2 + * Copyright 2015 Open Networking Laboratory
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +
17 +/*
18 + ONOS GUI -- Topology Selection Module.
19 + Defines behavior when selecting nodes.
20 + */
21 +
22 +(function () {
23 + 'use strict';
24 +
25 + // injected refs
26 + var $log, fs, tps;
27 +
28 + // api to topoForce
29 + var api;
30 + /*
31 + node() // get ref to D3 selection of nodes
32 + zoomingOrPanning( ev )
33 + updateDeviceColors( [dev] )
34 + sendEvent( type, {payload} )
35 + */
36 +
37 + // internal state
38 + var hovered, // the node over which the mouse is hovering
39 + selections = {}, // currently selected nodes (by id)
40 + selectOrder = [], // the order in which we made selections
41 + haveDetails = false, // do we have details of one or more nodes?
42 + useDetails = true; // should we show details if we have 'em?
43 +
44 + // ==========================
45 +
46 + function nSel() {
47 + return selectOrder.length;
48 + }
49 + function getSel(idx) {
50 + return selections[selectOrder[idx]];
51 + }
52 + function allSelectionsClass(cls) {
53 + for (var i=0, n=nSel(); i<n; i++) {
54 + if (getSel(i).obj.class !== cls) {
55 + return false;
56 + }
57 + }
58 + return true;
59 + }
60 +
61 + // ==========================
62 +
63 + function nodeMouseOver(m) {
64 + if (!m.dragStarted) {
65 + $log.debug("MouseOver()...", m);
66 + if (hovered != m) {
67 + hovered = m;
68 + requestTrafficForMode();
69 + }
70 + }
71 + }
72 +
73 + function nodeMouseOut(m) {
74 + if (!m.dragStarted) {
75 + if (hovered) {
76 + hovered = null;
77 + requestTrafficForMode();
78 + }
79 + $log.debug("MouseOut()...", m);
80 + }
81 + }
82 +
83 + // ==========================
84 +
85 + function selectObject(obj) {
86 + var el = this,
87 + ev = d3.event.sourceEvent,
88 + n;
89 +
90 + if (api.zoomingOrPanning(ev)) {
91 + return;
92 + }
93 +
94 + if (el) {
95 + n = d3.select(el);
96 + } else {
97 + api.node().each(function (d) {
98 + if (d == obj) {
99 + n = d3.select(el = this);
100 + }
101 + });
102 + }
103 + if (!n) return;
104 +
105 + if (ev.shiftKey && n.classed('selected')) {
106 + deselectObject(obj.id);
107 + updateDetail();
108 + return;
109 + }
110 +
111 + if (!ev.shiftKey) {
112 + deselectAll();
113 + }
114 +
115 + selections[obj.id] = { obj: obj, el: el };
116 + selectOrder.push(obj.id);
117 +
118 + n.classed('selected', true);
119 + api.updateDeviceColors(obj);
120 + updateDetail();
121 +
122 + debugSel();
123 + }
124 +
125 + function deselectObject(id) {
126 + var obj = selections[id];
127 + if (obj) {
128 + d3.select(obj.el).classed('selected', false);
129 + delete selections[id];
130 + fs.removeFromArray(id, selectOrder);
131 + api.updateDeviceColors(obj.obj);
132 + }
133 +
134 + debugSel();
135 + }
136 +
137 + function deselectAll() {
138 + // deselect all nodes in the network...
139 + api.node().classed('selected', false);
140 + selections = {};
141 + selectOrder = [];
142 + api.updateDeviceColors();
143 + updateDetail();
144 +
145 + debugSel();
146 + }
147 +
148 + function debugSel() {
149 + $log.debug(' ..... Selected now >> ', selectOrder);
150 + }
151 +
152 + // === -----------------------------------------------------
153 +
154 + function requestDetails() {
155 + var data = getSel(0).obj;
156 + api.sendEvent('requestDetails', {
157 + id: data.id,
158 + class: data.class
159 + });
160 + }
161 +
162 + // === -----------------------------------------------------
163 +
164 + function updateDetail() {
165 + var nSel = selectOrder.length;
166 + if (!nSel) {
167 + emptySelect();
168 + } else if (nSel === 1) {
169 + singleSelect();
170 + } else {
171 + multiSelect();
172 + }
173 + }
174 +
175 + function emptySelect() {
176 + haveDetails = false;
177 + tps.hideDetailPanel();
178 + cancelTraffic();
179 + }
180 +
181 + function singleSelect() {
182 + // NOTE: detail is shown from 'showDetails' event callback
183 + requestDetails();
184 + cancelTraffic();
185 + requestTrafficForMode();
186 + }
187 +
188 + function multiSelect() {
189 + haveDetails = true;
190 +
191 + // display the selected nodes in the detail panel
192 + tps.displayMulti(selectOrder);
193 +
194 + // always add the 'show traffic' action
195 + tps.addAction('Show Related Traffic', showRelatedIntentsAction);
196 +
197 + // add other actions, based on what is selected...
198 + if (nSel() === 2 && allSelectionsClass('host')) {
199 + tps.addAction('Create Host-to-Host Flow', addHostIntentAction);
200 + } else if (nSel() >= 2 && allSelectionsClass('host')) {
201 + tps.addAction('Create Multi-Source Flow', addMultiSourceIntentAction);
202 + }
203 +
204 + cancelTraffic();
205 + requestTrafficForMode();
206 + }
207 +
208 +
209 + // === -----------------------------------------------------
210 + // Event Handlers
211 +
212 + function showDetails(data) {
213 + haveDetails = true;
214 +
215 + // display the data for the single selected node
216 + tps.displaySingle(data);
217 +
218 + // always add the 'show traffic' action
219 + tps.addAction('Show Related Traffic', showRelatedIntentsAction);
220 +
221 + // add other actions, based on what is selected...
222 + if (data.type === 'switch') {
223 + tps.addAction('Show Device Flows', showDeviceLinkFlowsAction);
224 + }
225 +
226 + // only show the details panel if the user hasn't "hidden" it
227 + if (useDetails) {
228 + tps.showDetailPanel();
229 + }
230 + }
231 +
232 + // === -----------------------------------------------------
233 + // TODO: migrate these to topoTraffic.js
234 +
235 + function cancelTraffic() {
236 + $log.debug('TODO: cancelTraffic');
237 +
238 + }
239 + function requestTrafficForMode() {
240 + $log.debug('TODO: requestTrafficForMode');
241 +
242 + }
243 + function showRelatedIntentsAction () {
244 + $log.debug('TODO: showRelatedIntentsAction');
245 +
246 + }
247 + function addHostIntentAction () {
248 + $log.debug('TODO: addHostIntentAction');
249 +
250 + }
251 + function addMultiSourceIntentAction () {
252 + $log.debug('TODO: addMultiSourceIntentAction');
253 +
254 + }
255 + function showDeviceLinkFlowsAction () {
256 + $log.debug('TODO: showDeviceLinkFlowsAction');
257 +
258 + }
259 +
260 +
261 + // === -----------------------------------------------------
262 + // === MODULE DEFINITION ===
263 +
264 + angular.module('ovTopo')
265 + .factory('TopoSelectService',
266 + ['$log', 'FnService', 'TopoPanelService',
267 +
268 + function (_$log_, _fs_, _tps_) {
269 + $log = _$log_;
270 + fs = _fs_;
271 + tps = _tps_;
272 +
273 + function initSelect(_api_) {
274 + api = _api_;
275 + }
276 +
277 + function destroySelect() { }
278 +
279 + return {
280 + initSelect: initSelect,
281 + destroySelect: destroySelect,
282 +
283 + showDetails: showDetails,
284 +
285 + nodeMouseOver: nodeMouseOver,
286 + nodeMouseOut: nodeMouseOut,
287 + selectObject: selectObject,
288 + deselectObject: deselectObject,
289 + deselectAll: deselectAll,
290 + hovered: function () { return hovered; }
291 + };
292 + }]);
293 +}());
...@@ -88,7 +88,7 @@ describe('factory: fw/layer/panel.js', function () { ...@@ -88,7 +88,7 @@ describe('factory: fw/layer/panel.js', function () {
88 var p = ps.createPanel('foo'); 88 var p = ps.createPanel('foo');
89 expect(fs.areFunctions(p, [ 89 expect(fs.areFunctions(p, [
90 'show', 'hide', 'toggle', 'empty', 'append', 90 'show', 'hide', 'toggle', 'empty', 'append',
91 - 'width', 'height', 'isVisible', 'el' 91 + 'width', 'height', 'isVisible', 'classed', 'el'
92 ])).toBeTruthy(); 92 ])).toBeTruthy();
93 }); 93 });
94 94
......
...@@ -34,7 +34,16 @@ describe('factory: view/topo/topoPanel.js', function() { ...@@ -34,7 +34,16 @@ describe('factory: view/topo/topoPanel.js', function() {
34 34
35 it('should define api functions', function () { 35 it('should define api functions', function () {
36 expect(fs.areFunctions(tps, [ 36 expect(fs.areFunctions(tps, [
37 - 'initPanels', 'destroyPanels', 'showSummary' 37 + 'initPanels',
38 + 'destroyPanels',
39 + 'showSummary',
40 + 'displaySingle',
41 + 'displayMulti',
42 + 'addAction',
43 + 'showDetailPanel',
44 + 'hideDetailPanel',
45 + 'detailVisible',
46 + 'summaryVisible'
38 ])).toBeTruthy(); 47 ])).toBeTruthy();
39 }); 48 });
40 49
......
1 +/*
2 + * Copyright 2015 Open Networking Laboratory
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +
17 +/*
18 + ONOS GUI -- Topo View -- Topo Selection Service - Unit Tests
19 + */
20 +describe('factory: view/topo/topoSelect.js', function() {
21 + var $log, fs, tss;
22 +
23 + beforeEach(module('ovTopo', 'onosUtil', 'onosLayer'));
24 +
25 + beforeEach(inject(function (_$log_, FnService, TopoSelectService) {
26 + $log = _$log_;
27 + fs = FnService;
28 + tss = TopoSelectService;
29 + }));
30 +
31 + it('should define TopoSelectService', function () {
32 + expect(tss).toBeDefined();
33 + });
34 +
35 + it('should define api functions', function () {
36 + expect(fs.areFunctions(tss, [
37 + 'initSelect', 'destroySelect', 'showDetails',
38 + 'nodeMouseOver', 'nodeMouseOut', 'selectObject', 'deselectObject',
39 + 'deselectAll', 'hovered'
40 + ])).toBeTruthy();
41 + });
42 +
43 + // TODO: more tests...
44 +});