Thomas Vachuska
Committed by Simon Hunt

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
...@@ -72,9 +72,9 @@ ...@@ -72,9 +72,9 @@
72 <!-- Initialize the UI...--> 72 <!-- Initialize the UI...-->
73 <script type="text/javascript"> 73 <script type="text/javascript">
74 var ONOS = $.onos({ 74 var ONOS = $.onos({
75 - comment: "configuration options", 75 + comment: 'configuration options',
76 + theme: 'light',
76 startVid: 'topo', 77 startVid: 'topo',
77 -// startVid: 'sampleKeys',
78 trace: false 78 trace: false
79 }); 79 });
80 </script> 80 </script>
......
...@@ -23,8 +23,15 @@ ...@@ -23,8 +23,15 @@
23 #mast { 23 #mast {
24 height: 36px; 24 height: 36px;
25 padding: 4px; 25 padding: 4px;
26 - background-color: #bbb;
27 vertical-align: baseline; 26 vertical-align: baseline;
27 +}
28 +
29 +.light #mast {
30 + background-color: #bbb;
31 + box-shadow: 0px 2px 8px #777;
32 +}
33 +.dark #mast {
34 + background-color: #444;
28 box-shadow: 0px 2px 8px #777; 35 box-shadow: 0px 2px 8px #777;
29 } 36 }
30 37
...@@ -35,12 +42,18 @@ ...@@ -35,12 +42,18 @@
35 } 42 }
36 43
37 #mast span.title { 44 #mast span.title {
38 - color: #369;
39 font-size: 14pt; 45 font-size: 14pt;
40 font-style: italic; 46 font-style: italic;
41 vertical-align: 12px; 47 vertical-align: 12px;
42 } 48 }
43 49
50 +.light #mast span.title {
51 + color: #369;
52 +}
53 +.dark #mast span.title {
54 + color: #78a;
55 +}
56 +
44 #mast span.right { 57 #mast span.right {
45 padding-top: 8px; 58 padding-top: 8px;
46 padding-right: 16px; 59 padding-right: 16px;
...@@ -50,16 +63,31 @@ ...@@ -50,16 +63,31 @@
50 #mast span.radio { 63 #mast span.radio {
51 font-size: 10pt; 64 font-size: 10pt;
52 margin: 4px 2px; 65 margin: 4px 2px;
53 - border: 1px dotted #222;
54 padding: 1px 6px; 66 padding: 1px 6px;
55 - color: #eee;
56 cursor: pointer; 67 cursor: pointer;
57 } 68 }
58 69
70 +.light #mast span.radio {
71 + border: 1px dotted #222;
72 + color: #eee;
73 +}
74 +.dark #mast span.radio {
75 + border: 1px dotted #bbb;
76 + color: #888;
77 +}
78 +
59 #mast span.radio.active { 79 #mast span.radio.active {
80 + padding: 1px 6px;
81 + font-weight: bold;
82 +}
83 +
84 +.light #mast span.radio.active {
60 background-color: #bbb; 85 background-color: #bbb;
61 border: 1px solid #eee; 86 border: 1px solid #eee;
62 - padding: 1px 6px;
63 color: #666; 87 color: #666;
64 - font-weight: bold; 88 +}
89 +.dark #mast span.radio.active {
90 + background-color: #222;
91 + border: 1px solid #eee;
92 + color: #aaf;
65 } 93 }
......
...@@ -32,7 +32,7 @@ div.onosView.currentView { ...@@ -32,7 +32,7 @@ div.onosView.currentView {
32 display: block; 32 display: block;
33 } 33 }
34 34
35 -div#alerts { 35 +#alerts {
36 display: none; 36 display: none;
37 position: absolute; 37 position: absolute;
38 z-index: 2000; 38 z-index: 2000;
...@@ -45,21 +45,28 @@ div#alerts { ...@@ -45,21 +45,28 @@ div#alerts {
45 box-shadow: 4px 6px 12px #777; 45 box-shadow: 4px 6px 12px #777;
46 } 46 }
47 47
48 -div#alerts pre { 48 +#alerts pre {
49 margin: 0.2em 6px; 49 margin: 0.2em 6px;
50 } 50 }
51 51
52 -div#alerts span.close { 52 +#alerts span.close {
53 color: #6af; 53 color: #6af;
54 float: right; 54 float: right;
55 right: 2px; 55 right: 2px;
56 cursor: pointer; 56 cursor: pointer;
57 } 57 }
58 58
59 -div#alerts span.close:hover { 59 +#alerts span.close:hover {
60 color: #fff; 60 color: #fff;
61 } 61 }
62 62
63 +#alerts p.footnote {
64 + text-align: right;
65 + font-size: 8pt;
66 + margin: 8px 0 0 0;
67 + color: #66d;
68 +}
69 +
63 /* 70 /*
64 * ============================================================== 71 * ==============================================================
65 * END OF NEW ONOS.JS file 72 * END OF NEW ONOS.JS file
......
...@@ -37,23 +37,34 @@ ...@@ -37,23 +37,34 @@
37 37
38 var defaultOptions = { 38 var defaultOptions = {
39 trace: false, 39 trace: false,
40 + theme: 'light',
40 startVid: defaultVid 41 startVid: defaultVid
41 }; 42 };
42 43
43 // compute runtime settings 44 // compute runtime settings
44 var settings = $.extend({}, defaultOptions, options); 45 var settings = $.extend({}, defaultOptions, options);
45 46
47 + // set the selected theme
48 + d3.select('body').classed(settings.theme, true);
49 +
46 // internal state 50 // internal state
47 var views = {}, 51 var views = {},
48 current = { 52 current = {
49 view: null, 53 view: null,
50 - ctx: '' 54 + ctx: '',
55 + theme: settings.theme
51 }, 56 },
52 built = false, 57 built = false,
53 errorCount = 0, 58 errorCount = 0,
54 keyHandler = { 59 keyHandler = {
55 - fn: null, 60 + globalKeys: {},
56 - map: {} 61 + maskedKeys: {},
62 + viewKeys: {},
63 + viewFn: null
64 + },
65 + alerts = {
66 + open: false,
67 + count: 0
57 }; 68 };
58 69
59 // DOM elements etc. 70 // DOM elements etc.
...@@ -240,8 +251,8 @@ ...@@ -240,8 +251,8 @@
240 251
241 // detach radio buttons, key handlers, etc. 252 // detach radio buttons, key handlers, etc.
242 $('#mastRadio').children().detach(); 253 $('#mastRadio').children().detach();
243 - keyHandler.fn = null; 254 + keyHandler.viewKeys = {};
244 - keyHandler.map = {}; 255 + keyHandler.viewFn = null;
245 } 256 }
246 257
247 // cache new view and context 258 // cache new view and context
...@@ -322,20 +333,74 @@ ...@@ -322,20 +333,74 @@
322 $mastRadio.node().appendChild(btnG.node()); 333 $mastRadio.node().appendChild(btnG.node());
323 } 334 }
324 335
336 + function setupGlobalKeys() {
337 + keyHandler.globalKeys = {
338 + esc: escapeKey,
339 + T: toggleTheme
340 + };
341 + // Masked keys are global key handlers that always return true.
342 + // That is, the view will never see the event for that key.
343 + keyHandler.maskedKeys = {
344 + T: true
345 + };
346 + }
347 +
348 + function escapeKey(view, key, code, ev) {
349 + if (alerts.open) {
350 + closeAlerts();
351 + return true;
352 + }
353 + return false;
354 + }
355 +
356 + function toggleTheme(view, key, code, ev) {
357 + var body = d3.select('body');
358 + current.theme = (current.theme === 'light') ? 'dark' : 'light';
359 + body.classed('light dark', false);
360 + body.classed(current.theme, true);
361 + return true;
362 + }
363 +
325 function setKeyBindings(keyArg) { 364 function setKeyBindings(keyArg) {
365 + var viewKeys,
366 + masked = [];
367 +
326 if ($.isFunction(keyArg)) { 368 if ($.isFunction(keyArg)) {
327 // set general key handler callback 369 // set general key handler callback
328 - keyHandler.fn = keyArg; 370 + keyHandler.viewFn = keyArg;
329 } else { 371 } else {
330 // set specific key filter map 372 // set specific key filter map
331 - keyHandler.map = keyArg; 373 + viewKeys = d3.map(keyArg).keys();
374 + viewKeys.forEach(function (key) {
375 + if (keyHandler.maskedKeys[key]) {
376 + masked.push(' Key "' + key + '" is reserved');
377 + }
378 + });
379 +
380 + if (masked.length) {
381 + doAlert('WARNING...\n\nsetKeys():\n' + masked.join('\n'));
382 + }
383 + keyHandler.viewKeys = keyArg;
332 } 384 }
333 } 385 }
334 386
335 - var alerts = { 387 + function keyIn() {
336 - open: false, 388 + var event = d3.event,
337 - count: 0 389 + keyCode = event.keyCode,
338 - }; 390 + key = whatKey(keyCode),
391 + gcb = isF(keyHandler.globalKeys[key]),
392 + vcb = isF(keyHandler.viewKeys[key]) || isF(keyHandler.viewFn);
393 +
394 + // global callback?
395 + if (gcb && gcb(current.view.token(), key, keyCode, event)) {
396 + // if the event was 'handled', we are done
397 + return;
398 + }
399 + // otherwise, let the view callback have a shot
400 + if (vcb) {
401 + vcb(current.view.token(), key, keyCode, event);
402 + }
403 + }
339 404
340 function createAlerts() { 405 function createAlerts() {
341 var al = d3.select('#alerts') 406 var al = d3.select('#alerts')
...@@ -345,15 +410,16 @@ ...@@ -345,15 +410,16 @@
345 .text('X') 410 .text('X')
346 .on('click', closeAlerts); 411 .on('click', closeAlerts);
347 al.append('pre'); 412 al.append('pre');
413 + al.append('p').attr('class', 'footnote')
414 + .text('Press ESCAPE to close');
348 alerts.open = true; 415 alerts.open = true;
349 alerts.count = 0; 416 alerts.count = 0;
350 } 417 }
351 418
352 function closeAlerts() { 419 function closeAlerts() {
353 d3.select('#alerts') 420 d3.select('#alerts')
354 - .style('display', 'none'); 421 + .style('display', 'none')
355 - d3.select('#alerts span').remove(); 422 + .html('');
356 - d3.select('#alerts pre').remove();
357 alerts.open = false; 423 alerts.open = false;
358 } 424 }
359 425
...@@ -384,17 +450,6 @@ ...@@ -384,17 +450,6 @@
384 addAlert(msg); 450 addAlert(msg);
385 } 451 }
386 452
387 - function keyIn() {
388 - var event = d3.event,
389 - keyCode = event.keyCode,
390 - key = whatKey(keyCode),
391 - cb = isF(keyHandler.map[key]) || isF(keyHandler.fn);
392 -
393 - if (cb) {
394 - cb(current.view.token(), key, keyCode, event);
395 - }
396 - }
397 -
398 function resize(e) { 453 function resize(e) {
399 d3.selectAll('.onosView').call(setViewDimensions); 454 d3.selectAll('.onosView').call(setViewDimensions);
400 // allow current view to react to resize event... 455 // allow current view to react to resize event...
...@@ -683,6 +738,7 @@ ...@@ -683,6 +738,7 @@
683 $(window).on('resize', resize); 738 $(window).on('resize', resize);
684 739
685 d3.select('body').on('keydown', keyIn); 740 d3.select('body').on('keydown', keyIn);
741 + setupGlobalKeys();
686 742
687 // Invoke hashchange callback to navigate to content 743 // Invoke hashchange callback to navigate to content
688 // indicated by the window location hash. 744 // indicated by the window location hash.
......
...@@ -117,7 +117,7 @@ ...@@ -117,7 +117,7 @@
117 S: injectStartupEvents, // TODO: remove (testing only) 117 S: injectStartupEvents, // TODO: remove (testing only)
118 space: injectTestEvent, // TODO: remove (testing only) 118 space: injectTestEvent, // TODO: remove (testing only)
119 119
120 - B: toggleBg, 120 + B: toggleBg, // TODO: do we really need this?
121 L: cycleLabels, 121 L: cycleLabels,
122 P: togglePorts, 122 P: togglePorts,
123 U: unpin, 123 U: unpin,
...@@ -135,7 +135,6 @@ ...@@ -135,7 +135,6 @@
135 webSock, 135 webSock,
136 labelIdx = 0, 136 labelIdx = 0,
137 137
138 - //selected = {},
139 selectOrder = [], 138 selectOrder = [],
140 selections = {}, 139 selections = {},
141 140
...@@ -788,8 +787,9 @@ ...@@ -788,8 +787,9 @@
788 if (d.class === 'device') { 787 if (d.class === 'device') {
789 d.fixed = true; 788 d.fixed = true;
790 d3.select(self).classed('fixed', true); 789 d3.select(self).classed('fixed', true);
790 + if (config.useLiveData) {
791 tellServerCoords(d); 791 tellServerCoords(d);
792 - // TODO: send new [x,y] back to server, via websocket. 792 + }
793 } 793 }
794 } 794 }
795 795
......