GUI -- TopoView - re-implemented Quick Help panel.
Change-Id: I92edeb570a97eff87a5f9b08373ff0517849bf24
diff --git a/web/gui/src/main/webapp/app/fw/layer/quickhelp.css b/web/gui/src/main/webapp/app/fw/layer/quickhelp.css
new file mode 100644
index 0000000..bb806d8
--- /dev/null
+++ b/web/gui/src/main/webapp/app/fw/layer/quickhelp.css
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2014,2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ ONOS GUI -- Quick Help Service -- CSS file
+ */
+
+#quickhelp {
+ z-index: 1300;
+}
+
+#quickhelp svg {
+ position: absolute;
+ top: 180px;
+ opacity: 1;
+}
+
+#quickhelp svg g.help rect {
+ fill: black;
+ opacity: 0.7;
+}
+
+#quickhelp svg text.title {
+ font-size: 10pt;
+ font-style: italic;
+ text-anchor: middle;
+ fill: #999;
+}
+
+#quickhelp svg g.keyItem {
+ fill: white;
+}
+
+#quickhelp svg g line.qhrowsep {
+ stroke: #888;
+ stroke-dasharray: 2 2;
+}
+
+#quickhelp svg text {
+ font-size: 7pt;
+ alignment-baseline: middle;
+}
+
+#quickhelp svg text.key {
+ fill: #add;
+}
+
+#quickhelp svg text.desc {
+ fill: #ddd;
+}
+
diff --git a/web/gui/src/main/webapp/app/fw/layer/quickhelp.js b/web/gui/src/main/webapp/app/fw/layer/quickhelp.js
new file mode 100644
index 0000000..8d533c7
--- /dev/null
+++ b/web/gui/src/main/webapp/app/fw/layer/quickhelp.js
@@ -0,0 +1,371 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ ONOS GUI -- Layer -- Quick Help Service
+
+ Provides a mechanism to display key bindings and mouse gesture notes.
+ */
+(function () {
+ 'use strict';
+
+ // injected references
+ var $log, fs, sus;
+
+ // configuration
+ var defaultSettings = {
+ fade: 500
+ },
+ w = '100%',
+ h = '80%',
+ vbox = '-200 0 400 400',
+ pad = 10,
+ offy = 45,
+ sepYDelta = 20,
+ colXDelta = 16,
+ yTextSpc = 12,
+ offDesc = 8;
+
+ // internal state
+ var settings,
+ data = [],
+ yCount;
+
+ // DOM elements
+ var qhdiv, svg, pane, rect, items;
+
+ // key-logical-name to key-display lookup..
+ var keyDisp = {
+ equals: '=',
+ dash: '-',
+ slash: '/',
+ backSlash: '\\',
+ backQuote: '`',
+ leftArrow: 'L-arrow',
+ upArrow: 'U-arrow',
+ rightArrow: 'R-arrow',
+ downArrow: 'D-arrow'
+ };
+
+ // ===========================================
+ // === Function Definitions ===
+
+
+ // TODO: move this to FnService.
+ function cap(s) {
+ return s.replace(/^[a-z]/, function (m) { return m.toUpperCase(); });
+ }
+
+
+ function mkKeyDisp(id) {
+ var v = keyDisp[id] || id;
+ return cap(v);
+ }
+
+ function addSeparator(el, i) {
+ var y = sepYDelta/2 - 5;
+ el.append('line')
+ .attr({ 'class': 'qhrowsep', x1: 0, y1: y, x2: 0, y2: y });
+ }
+
+ function addContent(el, data, ri) {
+ var xCount = 0,
+ clsPfx = 'qh-r' + ri + '-c';
+
+ function addColumn(el, c, i) {
+ var cls = clsPfx + i,
+ oy = 0,
+ aggKey = el.append('g').attr('visibility', 'hidden'),
+ gcol = el.append('g').attr({
+ 'class': cls,
+ transform: sus.translate(xCount, 0)
+ });
+
+ c.forEach(function (j) {
+ var k = j[0],
+ v = j[1];
+
+ if (k !== '-') {
+ aggKey.append('text').text(k);
+
+ gcol.append('text').text(k)
+ .attr({
+ 'class': 'key',
+ y: oy
+ });
+ gcol.append('text').text(v)
+ .attr({
+ 'class': 'desc',
+ y: oy
+ });
+ }
+
+ oy += yTextSpc;
+ });
+
+ // adjust position of descriptions, based on widest key
+ var kbox = aggKey.node().getBBox(),
+ ox = kbox.width + offDesc;
+ gcol.selectAll('.desc').attr('x', ox);
+ aggKey.remove();
+
+ // now update x-offset for next column
+ var bbox = gcol.node().getBBox();
+ xCount += bbox.width + colXDelta;
+ }
+
+ data.forEach(function (d, i) {
+ addColumn(el, d, i);
+ });
+
+ // finally, return the height of the row..
+ return el.node().getBBox().height;
+ }
+
+ function updateKeyItems() {
+ var rows = items.selectAll('.qhRow').data(data);
+
+ yCount = offy;
+
+ var entering = rows.enter()
+ .append('g')
+ .attr({
+ 'class': 'qhrow'
+ });
+
+ entering.each(function (r, i) {
+ var el = d3.select(this),
+ sep = r.type === 'sep',
+ dy;
+
+ el.attr('transform', sus.translate(0, yCount));
+
+ if (sep) {
+ addSeparator(el, i);
+ yCount += sepYDelta;
+ } else {
+ dy = addContent(el, r.data, i);
+ yCount += dy;
+ }
+ });
+
+ // size the backing rectangle
+ var ibox = items.node().getBBox(),
+ paneW = ibox.width + pad * 2,
+ paneH = ibox.height + offy;
+
+ items.selectAll('.qhrowsep').attr('x2', ibox.width);
+ items.attr('transform', sus.translate(-paneW/2, -pad));
+ rect.attr({
+ width: paneW,
+ height: paneH,
+ transform: sus.translate(-paneW/2-pad, 0)
+ });
+
+ }
+
+ function checkFmt(fmt) {
+ // should be a single array of keys,
+ // or array of arrays of keys (one per column).
+ // return null if there is a problem.
+ var a = fs.isA(fmt),
+ n = a && a.length,
+ ns = 0,
+ na = 0;
+
+ if (n) {
+ // it is an array which has some content
+ a.forEach(function (d) {
+ fs.isA(d) && na++;
+ fs.isS(d) && ns++;
+ });
+ if (na === n || ns === n) {
+ // all arrays or all strings...
+ return a;
+ }
+ }
+ return null;
+ }
+
+ function buildBlock(map, fmt) {
+ var b = [];
+ fmt.forEach(function (k) {
+ var v = map.get(k),
+ a = fs.isA(v),
+ d = (a && a[1]);
+
+ // '-' marks a separator; d is the description
+ if (k === '-' || d) {
+ b.push([mkKeyDisp(k), d]);
+ }
+ });
+ return b;
+ }
+
+ function emptyRow() {
+ return { type: 'row', data: [] };
+ }
+
+ function mkArrRow(fmt) {
+ var d = emptyRow();
+ d.data.push(fmt);
+ return d;
+ }
+
+ function mkColumnarRow(map, fmt) {
+ var d = emptyRow();
+ fmt.forEach(function (a) {
+ d.data.push(buildBlock(map, a));
+ });
+ return d;
+ }
+
+ function mkMapRow(map, fmt) {
+ var d = emptyRow();
+ d.data.push(buildBlock(map, fmt));
+ return d;
+ }
+
+ function addRow(row) {
+ var d = row || { type: 'sep' };
+ data.push(d);
+ }
+
+ function aggregateData(bindings) {
+ var hf = '_helpFormat',
+ gmap = d3.map(bindings.globalKeys),
+ gfmt = bindings.globalFormat,
+ vmap = d3.map(bindings.viewKeys),
+ vgest = bindings.viewGestures,
+ vfmt, vkeys;
+
+ // filter out help format entry
+ vfmt = checkFmt(vmap.get(hf));
+ vmap.remove(hf);
+
+ // if bad (or no) format, fallback to sorted keys
+ if (!vfmt) {
+ vkeys = vmap.keys();
+ vfmt = vkeys.sort();
+ }
+
+ data = [];
+
+ addRow(mkMapRow(gmap, gfmt));
+ addRow();
+ addRow(fs.isA(vfmt[0]) ? mkColumnarRow(vmap, vfmt) : mkMapRow(vmap, vfmt));
+ addRow();
+ addRow(mkArrRow(vgest));
+ }
+
+
+ function popBind(bindings) {
+ pane = svg.append('g')
+ .attr({
+ class: 'help',
+ opacity: 0
+ });
+
+ rect = pane.append('rect')
+ .attr('rx', 8);
+
+ pane.append('text')
+ .text('Quick Help')
+ .attr({
+ class: 'title',
+ dy: '1.2em',
+ transform: sus.translate(-pad,0)
+ });
+
+ items = pane.append('g');
+ aggregateData(bindings);
+ updateKeyItems();
+
+ _fade(1);
+ }
+
+ function fadeBindings() {
+ _fade(0);
+ }
+
+ function _fade(o) {
+ svg.selectAll('g.help')
+ .transition()
+ .duration(settings.fade)
+ .attr('opacity', o);
+ }
+
+ function addSvg() {
+ svg = qhdiv.append('svg')
+ .attr({
+ width: w,
+ height: h,
+ viewBox: vbox
+ });
+ }
+
+ function removeSvg() {
+ svg.transition()
+ .delay(settings.fade + 20)
+ .remove();
+ }
+
+
+ // ===========================================
+ // === Module Definition ===
+
+ angular.module('onosLayer')
+ .factory('QuickHelpService',
+ ['$log', 'FnService', 'SvgUtilService',
+
+ function (_$log_, _fs_, _sus_) {
+ $log = _$log_;
+ fs = _fs_;
+ sus = _sus_;
+
+ function initQuickHelp(opts) {
+ settings = angular.extend({}, defaultSettings, opts);
+ qhdiv = d3.select('#quickhelp');
+ }
+
+ function showQuickHelp(bindings) {
+ svg = qhdiv.select('svg');
+ if (svg.empty()) {
+ addSvg();
+ popBind(bindings);
+ } else {
+ hideQuickHelp();
+ }
+ }
+
+ function hideQuickHelp() {
+ svg = qhdiv.select('svg');
+ if (!svg.empty()) {
+ fadeBindings();
+ removeSvg();
+ return true;
+ }
+ return false;
+ }
+
+ return {
+ initQuickHelp: initQuickHelp,
+ showQuickHelp: showQuickHelp,
+ hideQuickHelp: hideQuickHelp
+ };
+ }]);
+
+}());
diff --git a/web/gui/src/main/webapp/app/fw/util/keys.js b/web/gui/src/main/webapp/app/fw/util/keys.js
index d451885..1cdb044 100644
--- a/web/gui/src/main/webapp/app/fw/util/keys.js
+++ b/web/gui/src/main/webapp/app/fw/util/keys.js
@@ -21,7 +21,7 @@
'use strict';
// references to injected services
- var $log, fs, ts;
+ var $log, fs, ts, qhs;
// internal state
var enabled = true,
@@ -115,22 +115,13 @@
}
function quickHelp(view, key, code, ev) {
- // TODO: show quick help
- // delegate to QuickHelp service.
- //libApi.quickHelp.show(keyHandler);
- console.log('QUICK-HELP');
+ qhs.showQuickHelp(keyHandler);
return true;
}
// returns true if we 'consumed' the ESC keypress, false otherwise
function escapeKey(view, key, code, ev) {
- // TODO: plumb in handling of quick help dismissal
-/*
- if (qh.hide()) {
- return true;
- }
-*/
- return false;
+ return qhs.hideQuickHelp();
}
function toggleTheme(view, key, code, ev) {
@@ -176,13 +167,18 @@
}
angular.module('onosUtil')
- .factory('KeyService', ['$log', 'FnService', 'ThemeService',
+ .factory('KeyService',
+ ['$log', 'FnService', 'ThemeService',
+
function (_$log_, _fs_, _ts_) {
$log = _$log_;
fs = _fs_;
ts = _ts_;
return {
+ bindQhs: function (_qhs_) {
+ qhs = _qhs_;
+ },
installOn: function (elem) {
elem.on('keydown', keyIn);
setupGlobalKeys();
diff --git a/web/gui/src/main/webapp/app/index.html b/web/gui/src/main/webapp/app/index.html
index dc8b51c..79bbd45 100644
--- a/web/gui/src/main/webapp/app/index.html
+++ b/web/gui/src/main/webapp/app/index.html
@@ -63,6 +63,7 @@
<script src="fw/layer/layer.js"></script>
<script src="fw/layer/panel.js"></script>
<script src="fw/layer/flash.js"></script>
+ <script src="fw/layer/quickhelp.js"></script>
<script src="fw/layer/veil.js"></script>
<!-- Framework and library stylesheets included here -->
@@ -74,6 +75,7 @@
<link rel="stylesheet" href="fw/svg/icon.css">
<link rel="stylesheet" href="fw/layer/panel.css">
<link rel="stylesheet" href="fw/layer/flash.css">
+ <link rel="stylesheet" href="fw/layer/quickhelp.css">
<link rel="stylesheet" href="fw/layer/veil.css">
<link rel="stylesheet" href="fw/nav/nav.css">
diff --git a/web/gui/src/main/webapp/app/onos.js b/web/gui/src/main/webapp/app/onos.js
index 43ce845..55c7b05 100644
--- a/web/gui/src/main/webapp/app/onos.js
+++ b/web/gui/src/main/webapp/app/onos.js
@@ -65,9 +65,10 @@
.controller('OnosCtrl', [
'$log', '$route', '$routeParams', '$location',
'KeyService', 'ThemeService', 'GlyphService', 'PanelService',
- 'FlashService',
+ 'FlashService', 'QuickHelpService',
- function ($log, $route, $routeParams, $location, ks, ts, gs, ps, flash) {
+ function ($log, $route, $routeParams, $location,
+ ks, ts, gs, ps, flash, qhs) {
var self = this;
self.$route = $route;
@@ -78,9 +79,11 @@
// initialize services...
ts.init();
ks.installOn(d3.select('body'));
+ ks.bindQhs(qhs);
gs.init();
ps.init();
flash.initFlash();
+ qhs.initQuickHelp();
$log.log('OnosCtrl has been created');
diff --git a/web/gui/src/main/webapp/app/view/topo/topo.js b/web/gui/src/main/webapp/app/view/topo/topo.js
index ca24d8a..44856f3 100644
--- a/web/gui/src/main/webapp/app/view/topo/topo.js
+++ b/web/gui/src/main/webapp/app/view/topo/topo.js
@@ -75,14 +75,13 @@
]
});
- // TODO: // mouse gestures
- var gestures = [
+ ks.gestureNotes([
['click', 'Select the item and show details'],
['shift-click', 'Toggle selection state'],
['drag', 'Reposition (and pin) device / host'],
['cmd-scroll', 'Zoom in / out'],
['cmd-drag', 'Pan']
- ];
+ ]);
}
// --- Keystroke functions -------------------------------------------