Simon Hunt

GUI -- Link selection showing link details implemented.

- note: basic link data shown for now. will need enhancing.

Change-Id: I067edec6f336b5ea5c83c610622346d5fcedce38
......@@ -432,9 +432,15 @@
opacity: .9;
}
#ov-topo svg .link.selected,
#ov-topo svg .link.enhanced {
stroke-width: 4.5px;
}
.light #ov-topo svg .link.selected,
.light #ov-topo svg .link.enhanced {
filter: url(#blue-glow);
}
.dark #ov-topo svg .link.selected,
.dark #ov-topo svg .link.enhanced {
filter: url(#yellow-glow);
}
......
......@@ -29,7 +29,7 @@
// references to injected services etc.
var $log, fs, ks, zs, gs, ms, sus, flash, wss,
tes, tfs, tps, tis, tss, tts, tos, ttbs;
tes, tfs, tps, tis, tss, tls, tts, tos, ttbs;
// DOM elements
var ovtopo, svg, defs, zoomLayer, mapG, forceG, noDevsLayer;
......@@ -45,7 +45,7 @@
actionMap = {
I: [toggleInstances, 'Toggle ONOS instances pane'],
O: [tps.toggleSummary, 'Toggle ONOS summary pane'],
D: [tss.toggleDetails, 'Disable / enable details pane'],
D: [tps.toggleDetails, 'Disable / enable details pane'],
H: [tfs.toggleHosts, 'Toggle host visibility'],
M: [tfs.toggleOffline, 'Toggle offline visibility'],
......@@ -117,9 +117,13 @@
// if an instance is selected, cancel the affinity mapping
tis.cancelAffinity()
} else if (tss.haveDetails()) {
} else if (tss.deselectAll()) {
// else if we have node selections, deselect them all
tss.deselectAll();
// (work already done)
} else if (tls.deselectLink()) {
// else if we have a link selected, deselect it
// (work already done)
} else if (tis.isVisible()) {
// else if the Instance Panel is visible, hide it
......@@ -238,12 +242,12 @@
'GlyphService', 'MapService', 'SvgUtilService', 'FlashService',
'WebSocketService',
'TopoEventService', 'TopoForceService', 'TopoPanelService',
'TopoInstService', 'TopoSelectService', 'TopoTrafficService',
'TopoObliqueService', 'TopoToolbarService',
'TopoInstService', 'TopoSelectService', 'TopoLinkService',
'TopoTrafficService', 'TopoObliqueService', 'TopoToolbarService',
function ($scope, _$log_, $loc, $timeout, _fs_, mast,
_ks_, _zs_, _gs_, _ms_, _sus_, _flash_, _wss_,
_tes_, _tfs_, _tps_, _tis_, _tss_, _tts_, _tos_, _ttbs_) {
function ($scope, _$log_, $loc, $timeout, _fs_, mast, _ks_, _zs_,
_gs_, _ms_, _sus_, _flash_, _wss_, _tes_, _tfs_, _tps_,
_tis_, _tss_, _tls_, _tts_, _tos_, _ttbs_) {
var self = this,
projection,
dim,
......@@ -273,6 +277,7 @@
tps = _tps_;
tis = _tis_;
tss = _tss_;
tls = _tls_;
tts = _tts_;
tos = _tos_;
ttbs = _ttbs_;
......
......@@ -615,6 +615,7 @@
d.fixed = true;
d3.select(this).classed('fixed', true);
sendUpdateMeta(d);
tss.clickConsumed(true);
}
// predicate that indicates when dragging is active
......@@ -692,7 +693,8 @@
return {
node: function () { return node; },
zoomingOrPanning: zoomingOrPanning,
updateDeviceColors: td3.updateDeviceColors
updateDeviceColors: td3.updateDeviceColors,
deselectLink: tls.deselectLink
};
}
......
......@@ -23,34 +23,35 @@
'use strict';
// injected refs
var $log, fs, sus, ts, flash;
var $log, fs, sus, ts, flash, tss, tps;
// internal state
var api,
td3,
network,
enhancedLink = null; // the link which the mouse is hovering over
showPorts = true, // enable port highlighting by default
enhancedLink = null, // the link over which the mouse is hovering
selectedLink = null; // the link which is currently selected
// SVG elements;
var svg;
// internal state
var showPorts = true; // enable port highlighting by default
// ======== ALGORITHM TO FIND LINK CLOSEST TO MOUSE ========
function mouseMoveHandler() {
var m = d3.mouse(this),
function getLogicalMousePosition(container) {
var m = d3.mouse(container),
sc = api.zoomer.scale(),
tr = api.zoomer.translate(),
mx = (m[0] - tr[0]) / sc,
my = (m[1] - tr[1]) / sc;
computeNearestLink({x: mx, y: my});
return {x: mx, y: my};
}
function computeNearestLink(mouse) {
var proximity = 30 / api.zoomer.scale(),
nearest, minDist;
nearest = null,
minDist;
function sq(x) { return x * x; }
......@@ -91,7 +92,6 @@
}
if (network.links.length) {
nearest = null;
minDist = proximity * 2;
network.links.forEach(function (d) {
......@@ -112,13 +112,11 @@
}
}
});
enhanceNearestLink(nearest);
}
return nearest;
}
function enhanceNearestLink(ldata) {
function enhanceLink(ldata) {
// if the new link is same as old link, do nothing
if (enhancedLink && ldata && enhancedLink.key === ldata.key) return;
......@@ -148,7 +146,6 @@
if (!d.el) return;
d.el.classed('enhanced', true);
$log.debug('[' + (d.srcPort || 'H') + '] ---> [' + d.tgtPort + ']', d.key);
// Define port label data objects.
// NOTE: src port is absent in the case of host-links.
......@@ -188,6 +185,62 @@
return {x: k * dx + ln.x, y: k * dy + ln.y};
}
function selectLink(ldata) {
// if the new link is same as old link, do nothing
if (selectedLink && ldata && selectedLink.key === ldata.key) return;
// make sure no nodes are selected
tss.deselectAll();
// first, unenhance the currently enhanced link
if (selectedLink) {
unselLink(selectedLink);
}
selectedLink = ldata;
if (selectedLink) {
selLink(selectedLink);
}
}
function unselLink(d) {
// guard against link element not set
if (d.el) {
d.el.classed('selected', false);
}
}
function selLink(d) {
// guard against link element not set
if (!d.el) return;
d.el.classed('selected', true);
tps.displayLink(d);
tps.displaySomething();
}
// ====== MOUSE EVENT HANDLERS ======
function mouseMoveHandler() {
var mp = getLogicalMousePosition(this),
link = computeNearestLink(mp);
enhanceLink(link);
}
function mouseClickHandler() {
var mp, link;
if (!tss.clickConsumed()) {
mp = getLogicalMousePosition(this);
link = computeNearestLink(mp);
selectLink(link);
}
}
// ======================
function togglePorts() {
showPorts = !showPorts;
......@@ -195,25 +248,37 @@
handler = showPorts ? mouseMoveHandler : null;
if (!showPorts) {
enhanceNearestLink(null);
enhanceLink(null);
}
svg.on('mousemove', handler);
flash.flash(what + ' port highlighting');
}
function deselectLink() {
if (selectedLink) {
unselLink(selectedLink);
selectedLink = null;
return true;
}
return false;
}
// ==========================
// Module definition
angular.module('ovTopo')
.factory('TopoLinkService',
['$log', 'FnService', 'SvgUtilService', 'ThemeService', 'FlashService',
'TopoSelectService', 'TopoPanelService',
function (_$log_, _fs_, _sus_, _ts_, _flash_) {
function (_$log_, _fs_, _sus_, _ts_, _flash_, _tss_, _tps_) {
$log = _$log_;
fs = _fs_;
sus = _sus_;
ts = _ts_;
flash = _flash_;
tss = _tss_;
tps = _tps_;
function initLink(_api_, _td3_) {
api = _api_;
......@@ -223,17 +288,20 @@
if (showPorts) {
svg.on('mousemove', mouseMoveHandler);
}
svg.on('click', mouseClickHandler);
}
function destroyLink() {
// unconditionally remove any mousemove event handler
// unconditionally remove any event handlers
svg.on('mousemove', null);
svg.on('click', null);
}
return {
initLink: initLink,
destroyLink: destroyLink,
togglePorts: togglePorts
togglePorts: togglePorts,
deselectLink: deselectLink
};
}]);
}());
......
......@@ -23,7 +23,7 @@
'use strict';
// injected refs
var $log, fs, ps, gs, wss;
var $log, fs, ps, gs, flash, wss;
// constants
var pCls = 'topo-p',
......@@ -37,6 +37,9 @@
var summaryPanel,
detailPanel;
// internal state
var useDetails = true, // should we show details if we have 'em?
haveDetails = false; // do we have details that we could show?
// === -----------------------------------------------------
// Utility functions
......@@ -129,6 +132,42 @@
.on('click', cb);
}
function displayLink(data) {
detailPanel.empty();
var svg = dpa('svg'),
title = dpa('h2'),
table = dpa('table'),
tbody = table.append('tbody');
gs.addGlyph(svg, 'ports', 40);
title.text('Link');
listProps(tbody, {
propOrder: [
'type', '-', 'src', 'srcPort', '-', 'tgt', 'tgtPort'
],
props: {
type: data.type(),
src: data.source.id,
srcPort: data.srcPort,
tgt: data.target.id,
tgtPort: data.tgtPort
}
});
}
function displayNothing() {
haveDetails = false;
hideDetailPanel();
}
function displaySomething() {
haveDetails = true;
if (useDetails) {
showDetailPanel();
}
}
// === -----------------------------------------------------
// Event Handlers
......@@ -201,6 +240,19 @@
dp.up = function (cb) { dp._move(dp.ypos.up, cb); };
}
function toggleDetails() {
useDetails = !useDetails;
if (useDetails) {
flash.flash('Enable details panel');
if (haveDetails) {
showDetailPanel();
}
} else {
flash.flash('Disable details panel');
hideDetailPanel();
}
}
// ==========================
function initPanels() {
......@@ -223,13 +275,15 @@
angular.module('ovTopo')
.factory('TopoPanelService',
['$log', 'FnService', 'PanelService', 'GlyphService', 'WebSocketService',
['$log', 'FnService', 'PanelService', 'GlyphService',
'FlashService', 'WebSocketService',
function (_$log_, _fs_, _ps_, _gs_, _wss_) {
function (_$log_, _fs_, _ps_, _gs_, _flash_, _wss_) {
$log = _$log_;
fs = _fs_;
ps = _ps_;
gs = _gs_;
flash = _flash_;
wss = _wss_;
return {
......@@ -239,13 +293,15 @@
showSummary: showSummary,
toggleSummary: toggleSummary,
toggleDetails: toggleDetails,
displaySingle: displaySingle,
displayMulti: displayMulti,
addAction: addAction,
displayLink: displayLink,
displayNothing: displayNothing,
displaySomething: displaySomething,
hideSummaryPanel: hideSummaryPanel,
showDetailPanel: showDetailPanel,
hideDetailPanel: hideDetailPanel,
detailVisible: function () { return detailPanel.isVisible(); },
summaryVisible: function () { return summaryPanel.isVisible(); }
......
......@@ -23,7 +23,7 @@
'use strict';
// injected refs
var $log, fs, flash, wss, tps, tts;
var $log, fs, wss, tps, tts;
// api to topoForce
var api;
......@@ -31,14 +31,14 @@
node() // get ref to D3 selection of nodes
zoomingOrPanning( ev )
updateDeviceColors( [dev] )
deselectLink()
*/
// internal state
var hovered, // the node over which the mouse is hovering
selections = {}, // currently selected nodes (by id)
selectOrder = [], // the order in which we made selections
haveDetails = false, // do we have details of one or more nodes?
useDetails = true; // should we show details if we have 'em?
consumeClick = false; // used to coordinate with SVG click handler
// ==========================
......@@ -101,6 +101,9 @@
}
if (!n) return;
consumeClick = true;
api.deselectLink();
if (ev.shiftKey && n.classed('selected')) {
deselectObject(obj.id);
updateDetail();
......@@ -130,12 +133,17 @@
}
function deselectAll() {
var something = (selectOrder.length > 0);
// deselect all nodes in the network...
api.node().classed('selected', false);
selections = {};
selectOrder = [];
api.updateDeviceColors();
updateDetail();
// return true if something was selected
return something;
}
// === -----------------------------------------------------
......@@ -162,9 +170,8 @@
}
function emptySelect() {
haveDetails = false;
tps.hideDetailPanel();
tts.cancelTraffic();
tps.displayNothing();
}
function singleSelect() {
......@@ -175,8 +182,6 @@
}
function multiSelect() {
haveDetails = true;
// display the selected nodes in the detail panel
tps.displayMulti(selectOrder);
......@@ -192,6 +197,7 @@
tts.cancelTraffic();
tts.requestTrafficForMode();
tps.displaySomething();
}
......@@ -199,8 +205,6 @@
// Event Handlers
function showDetails(data) {
haveDetails = true;
// display the data for the single selected node
tps.displaySingle(data);
......@@ -212,23 +216,7 @@
tps.addAction('Show Device Flows', tts.showDeviceLinkFlowsAction);
}
// only show the details panel if the user hasn't "hidden" it
if (useDetails) {
tps.showDetailPanel();
}
}
function toggleDetails() {
useDetails = !useDetails;
if (useDetails) {
flash.flash('Enable details panel');
if (haveDetails) {
tps.showDetailPanel();
}
} else {
flash.flash('Disable details panel');
tps.hideDetailPanel();
}
tps.displaySomething();
}
function validateSelectionContext() {
......@@ -239,18 +227,23 @@
return true;
}
function clickConsumed(x) {
var cc = consumeClick;
consumeClick = !!x;
return cc;
}
// === -----------------------------------------------------
// === MODULE DEFINITION ===
angular.module('ovTopo')
.factory('TopoSelectService',
['$log', 'FnService', 'FlashService', 'WebSocketService',
['$log', 'FnService', 'WebSocketService',
'TopoPanelService', 'TopoTrafficService',
function (_$log_, _fs_, _flash_, _wss_, _tps_, _tts_) {
function (_$log_, _fs_, _wss_, _tps_, _tts_) {
$log = _$log_;
fs = _fs_;
flash = _flash_;
wss = _wss_;
tps = _tps_;
tts = _tts_;
......@@ -266,7 +259,6 @@
destroySelect: destroySelect,
showDetails: showDetails,
toggleDetails: toggleDetails,
nodeMouseOver: nodeMouseOver,
nodeMouseOut: nodeMouseOut,
......@@ -275,9 +267,10 @@
deselectAll: deselectAll,
hovered: function () { return hovered; },
haveDetails: function () { return haveDetails; },
selectOrder: function () { return selectOrder; },
validateSelectionContext: validateSelectionContext
validateSelectionContext: validateSelectionContext,
clickConsumed: clickConsumed
};
}]);
}());
......