GUI -- Added global key bindings mechanism
- 'T' now toggles theme (light/dark) -- mast CSS done, other CSS to do
- 'ESC' now closes alerts box if it is open
- Minor cleanup to topo2.js
Change-Id: I506a6add4299a6c03dcb717c33394ad94be26997
diff --git a/web/gui/src/main/webapp/index2.html b/web/gui/src/main/webapp/index2.html
index 235c1d7..6168271 100644
--- a/web/gui/src/main/webapp/index2.html
+++ b/web/gui/src/main/webapp/index2.html
@@ -72,9 +72,9 @@
<!-- Initialize the UI...-->
<script type="text/javascript">
var ONOS = $.onos({
- comment: "configuration options",
+ comment: 'configuration options',
+ theme: 'light',
startVid: 'topo',
-// startVid: 'sampleKeys',
trace: false
});
</script>
diff --git a/web/gui/src/main/webapp/mast2.css b/web/gui/src/main/webapp/mast2.css
index 7f094b3..57fede4 100644
--- a/web/gui/src/main/webapp/mast2.css
+++ b/web/gui/src/main/webapp/mast2.css
@@ -23,8 +23,15 @@
#mast {
height: 36px;
padding: 4px;
- background-color: #bbb;
vertical-align: baseline;
+}
+
+.light #mast {
+ background-color: #bbb;
+ box-shadow: 0px 2px 8px #777;
+}
+.dark #mast {
+ background-color: #444;
box-shadow: 0px 2px 8px #777;
}
@@ -35,12 +42,18 @@
}
#mast span.title {
- color: #369;
font-size: 14pt;
font-style: italic;
vertical-align: 12px;
}
+.light #mast span.title {
+ color: #369;
+}
+.dark #mast span.title {
+ color: #78a;
+}
+
#mast span.right {
padding-top: 8px;
padding-right: 16px;
@@ -50,16 +63,31 @@
#mast span.radio {
font-size: 10pt;
margin: 4px 2px;
- border: 1px dotted #222;
padding: 1px 6px;
- color: #eee;
cursor: pointer;
}
+.light #mast span.radio {
+ border: 1px dotted #222;
+ color: #eee;
+}
+.dark #mast span.radio {
+ border: 1px dotted #bbb;
+ color: #888;
+}
+
#mast span.radio.active {
+ padding: 1px 6px;
+ font-weight: bold;
+}
+
+.light #mast span.radio.active {
background-color: #bbb;
border: 1px solid #eee;
- padding: 1px 6px;
color: #666;
- font-weight: bold;
+}
+.dark #mast span.radio.active {
+ background-color: #222;
+ border: 1px solid #eee;
+ color: #aaf;
}
diff --git a/web/gui/src/main/webapp/onos2.css b/web/gui/src/main/webapp/onos2.css
index 748cc97..2073acf 100644
--- a/web/gui/src/main/webapp/onos2.css
+++ b/web/gui/src/main/webapp/onos2.css
@@ -32,7 +32,7 @@
display: block;
}
-div#alerts {
+#alerts {
display: none;
position: absolute;
z-index: 2000;
@@ -45,21 +45,28 @@
box-shadow: 4px 6px 12px #777;
}
-div#alerts pre {
+#alerts pre {
margin: 0.2em 6px;
}
-div#alerts span.close {
+#alerts span.close {
color: #6af;
float: right;
right: 2px;
cursor: pointer;
}
-div#alerts span.close:hover {
+#alerts span.close:hover {
color: #fff;
}
+#alerts p.footnote {
+ text-align: right;
+ font-size: 8pt;
+ margin: 8px 0 0 0;
+ color: #66d;
+}
+
/*
* ==============================================================
* END OF NEW ONOS.JS file
diff --git a/web/gui/src/main/webapp/onos2.js b/web/gui/src/main/webapp/onos2.js
index 31d89fa..4aeb23e 100644
--- a/web/gui/src/main/webapp/onos2.js
+++ b/web/gui/src/main/webapp/onos2.js
@@ -37,23 +37,34 @@
var defaultOptions = {
trace: false,
+ theme: 'light',
startVid: defaultVid
};
// compute runtime settings
var settings = $.extend({}, defaultOptions, options);
+ // set the selected theme
+ d3.select('body').classed(settings.theme, true);
+
// internal state
var views = {},
current = {
view: null,
- ctx: ''
+ ctx: '',
+ theme: settings.theme
},
built = false,
errorCount = 0,
keyHandler = {
- fn: null,
- map: {}
+ globalKeys: {},
+ maskedKeys: {},
+ viewKeys: {},
+ viewFn: null
+ },
+ alerts = {
+ open: false,
+ count: 0
};
// DOM elements etc.
@@ -240,8 +251,8 @@
// detach radio buttons, key handlers, etc.
$('#mastRadio').children().detach();
- keyHandler.fn = null;
- keyHandler.map = {};
+ keyHandler.viewKeys = {};
+ keyHandler.viewFn = null;
}
// cache new view and context
@@ -322,20 +333,74 @@
$mastRadio.node().appendChild(btnG.node());
}
+ function setupGlobalKeys() {
+ keyHandler.globalKeys = {
+ esc: escapeKey,
+ T: toggleTheme
+ };
+ // Masked keys are global key handlers that always return true.
+ // That is, the view will never see the event for that key.
+ keyHandler.maskedKeys = {
+ T: true
+ };
+ }
+
+ function escapeKey(view, key, code, ev) {
+ if (alerts.open) {
+ closeAlerts();
+ return true;
+ }
+ return false;
+ }
+
+ function toggleTheme(view, key, code, ev) {
+ var body = d3.select('body');
+ current.theme = (current.theme === 'light') ? 'dark' : 'light';
+ body.classed('light dark', false);
+ body.classed(current.theme, true);
+ return true;
+ }
+
function setKeyBindings(keyArg) {
+ var viewKeys,
+ masked = [];
+
if ($.isFunction(keyArg)) {
// set general key handler callback
- keyHandler.fn = keyArg;
+ keyHandler.viewFn = keyArg;
} else {
// set specific key filter map
- keyHandler.map = keyArg;
+ viewKeys = d3.map(keyArg).keys();
+ viewKeys.forEach(function (key) {
+ if (keyHandler.maskedKeys[key]) {
+ masked.push(' Key "' + key + '" is reserved');
+ }
+ });
+
+ if (masked.length) {
+ doAlert('WARNING...\n\nsetKeys():\n' + masked.join('\n'));
+ }
+ keyHandler.viewKeys = keyArg;
}
}
- var alerts = {
- open: false,
- count: 0
- };
+ function keyIn() {
+ var event = d3.event,
+ keyCode = event.keyCode,
+ key = whatKey(keyCode),
+ gcb = isF(keyHandler.globalKeys[key]),
+ vcb = isF(keyHandler.viewKeys[key]) || isF(keyHandler.viewFn);
+
+ // global callback?
+ if (gcb && gcb(current.view.token(), key, keyCode, event)) {
+ // if the event was 'handled', we are done
+ return;
+ }
+ // otherwise, let the view callback have a shot
+ if (vcb) {
+ vcb(current.view.token(), key, keyCode, event);
+ }
+ }
function createAlerts() {
var al = d3.select('#alerts')
@@ -345,15 +410,16 @@
.text('X')
.on('click', closeAlerts);
al.append('pre');
+ al.append('p').attr('class', 'footnote')
+ .text('Press ESCAPE to close');
alerts.open = true;
alerts.count = 0;
}
function closeAlerts() {
d3.select('#alerts')
- .style('display', 'none');
- d3.select('#alerts span').remove();
- d3.select('#alerts pre').remove();
+ .style('display', 'none')
+ .html('');
alerts.open = false;
}
@@ -384,17 +450,6 @@
addAlert(msg);
}
- function keyIn() {
- var event = d3.event,
- keyCode = event.keyCode,
- key = whatKey(keyCode),
- cb = isF(keyHandler.map[key]) || isF(keyHandler.fn);
-
- if (cb) {
- cb(current.view.token(), key, keyCode, event);
- }
- }
-
function resize(e) {
d3.selectAll('.onosView').call(setViewDimensions);
// allow current view to react to resize event...
@@ -683,6 +738,7 @@
$(window).on('resize', resize);
d3.select('body').on('keydown', keyIn);
+ setupGlobalKeys();
// Invoke hashchange callback to navigate to content
// indicated by the window location hash.
diff --git a/web/gui/src/main/webapp/topo2.js b/web/gui/src/main/webapp/topo2.js
index fc7c35a..fff829f 100644
--- a/web/gui/src/main/webapp/topo2.js
+++ b/web/gui/src/main/webapp/topo2.js
@@ -117,7 +117,7 @@
S: injectStartupEvents, // TODO: remove (testing only)
space: injectTestEvent, // TODO: remove (testing only)
- B: toggleBg,
+ B: toggleBg, // TODO: do we really need this?
L: cycleLabels,
P: togglePorts,
U: unpin,
@@ -135,7 +135,6 @@
webSock,
labelIdx = 0,
- //selected = {},
selectOrder = [],
selections = {},
@@ -788,8 +787,9 @@
if (d.class === 'device') {
d.fixed = true;
d3.select(self).classed('fixed', true);
- tellServerCoords(d);
- // TODO: send new [x,y] back to server, via websocket.
+ if (config.useLiveData) {
+ tellServerCoords(d);
+ }
}
}