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
131 additions
and
40 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); |
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 | ... | ... |
-
Please register or login to post a comment