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
Showing
5 changed files
with
132 additions
and
41 deletions
| ... | @@ -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); |
| 791 | - tellServerCoords(d); | 790 | + if (config.useLiveData) { |
| 792 | - // TODO: send new [x,y] back to server, via websocket. | 791 | + tellServerCoords(d); |
| 792 | + } | ||
| 793 | } | 793 | } |
| 794 | } | 794 | } |
| 795 | 795 | ... | ... |
-
Please register or login to post a comment