GUI: Javascript cleanup and additional utility functions.

Change-Id: Ia16dd7eecedfd116e9d0a65d60d724657e87b8d3
diff --git a/web/gui/src/main/webapp/app/fw/layer/flash.js b/web/gui/src/main/webapp/app/fw/layer/flash.js
index 0d95b77..3139f2c 100644
--- a/web/gui/src/main/webapp/app/fw/layer/flash.js
+++ b/web/gui/src/main/webapp/app/fw/layer/flash.js
@@ -143,6 +143,13 @@
         enabled = !!b;
     }
 
+    function tempDiv(ms) {
+        var div = d3.select('body').append('div').classed('centered', true),
+            delay = (ms === undefined || ms < 100) ? 3000 : ms;
+        $timeout(function () { div.remove(); }, delay);
+        return div;
+    }
+
     angular.module('onosLayer')
         .factory('FlashService', ['$log', '$timeout',
         function (_$log_, _$timeout_) {
@@ -158,7 +165,8 @@
             return {
                 initFlash: initFlash,
                 flash: flash,
-                enable: enable
+                enable: enable,
+                tempDiv: tempDiv
             };
         }]);
 
diff --git a/web/gui/src/main/webapp/app/fw/svg/svgUtil.js b/web/gui/src/main/webapp/app/fw/svg/svgUtil.js
index cb67ae8..5132587 100644
--- a/web/gui/src/main/webapp/app/fw/svg/svgUtil.js
+++ b/web/gui/src/main/webapp/app/fw/svg/svgUtil.js
@@ -28,267 +28,275 @@
     // injected references
     var $log, fs;
 
+    // TODO: change 'force' ref to be 'force.alpha' ref.
+    function createDragBehavior(force, selectCb, atDragEnd,
+                                dragEnabled, clickEnabled) {
+        var draggedThreshold = d3.scale.linear()
+            .domain([0, 0.1])
+            .range([5, 20])
+            .clamp(true),
+            drag,
+            fSel = fs.isF(selectCb),
+            fEnd = fs.isF(atDragEnd),
+            fDEn = fs.isF(dragEnabled),
+            fCEn = fs.isF(clickEnabled),
+            bad = [];
+
+        function naf(what) {
+            return 'SvgUtilService: createDragBehavior(): ' + what +
+                ' is not a function';
+        }
+
+        if (!force) {
+            bad.push('SvgUtilService: createDragBehavior(): ' +
+                'Bad force reference');
+        }
+        if (!fSel) {
+            bad.push(naf('selectCb'));
+        }
+        if (!fEnd) {
+            bad.push(naf('atDragEnd'));
+        }
+        if (!fDEn) {
+            bad.push(naf('dragEnabled'));
+        }
+        if (!fCEn) {
+            bad.push(naf('clickEnabled'));
+        }
+
+        if (bad.length) {
+            $log.error(bad.join('\n'));
+            return null;
+        }
+
+        function dragged(d) {
+            var threshold = draggedThreshold(force.alpha()),
+                dx = d.oldX - d.px,
+                dy = d.oldY - d.py;
+            if (Math.abs(dx) >= threshold || Math.abs(dy) >= threshold) {
+                d.dragged = true;
+            }
+            return d.dragged;
+        }
+
+        drag = d3.behavior.drag()
+            .origin(function(d) { return d; })
+            .on('dragstart', function(d) {
+                if (clickEnabled() || dragEnabled()) {
+                    d3.event.sourceEvent.stopPropagation();
+
+                    d.oldX = d.x;
+                    d.oldY = d.y;
+                    d.dragged = false;
+                    d.fixed |= 2;
+                    d.dragStarted = true;
+                }
+            })
+            .on('drag', function(d) {
+                if (dragEnabled()) {
+                    d.px = d3.event.x;
+                    d.py = d3.event.y;
+                    if (dragged(d)) {
+                        if (!force.alpha()) {
+                            force.alpha(.025);
+                        }
+                    }
+                }
+            })
+            .on('dragend', function(d) {
+                if (d.dragStarted) {
+                    d.dragStarted = false;
+                    if (!dragged(d)) {
+                        // consider this the same as a 'click'
+                        // (selection of a node)
+                        if (clickEnabled()) {
+                            selectCb.call(this, d);
+                        }
+                    }
+                    d.fixed &= ~6;
+
+                    // hook at the end of a drag gesture
+                    if (dragEnabled()) {
+                        atDragEnd.call(this, d);
+                    }
+                }
+            });
+
+        return drag;
+    }
+
+
+    function loadGlow(defs, r, g, b, id) {
+        var glow = defs.append('filter')
+            .attr('x', '-50%')
+            .attr('y', '-50%')
+            .attr('width', '200%')
+            .attr('height', '200%')
+            .attr('id', id);
+
+        glow.append('feColorMatrix')
+            .attr('type', 'matrix')
+            .attr('values',
+                '0 0 0 0  ' + r + ' ' +
+                '0 0 0 0  ' + g + ' ' +
+                '0 0 0 0  ' + b + ' ' +
+                '0 0 0 1  0 ');
+
+        glow.append('feGaussianBlur')
+            .attr('stdDeviation', 3)
+            .attr('result', 'coloredBlur');
+
+        glow.append('feMerge').selectAll('feMergeNode')
+            .data(['coloredBlur', 'SourceGraphic'])
+            .enter().append('feMergeNode')
+            .attr('in', String);
+    }
+
+    function loadGlowDefs(defs) {
+        loadGlow(defs, 0.0, 0.0, 0.7, 'blue-glow');
+        loadGlow(defs, 1.0, 1.0, 0.3, 'yellow-glow');
+    }
+
+    // --- Ordinal scales for 7 values.
+
+    //               blue       brown      brick red  sea green  purple     dark teal  lime
+    var lightNorm = ['#3E5780', '#78533B', '#CB4D28', '#018D61', '#8A2979', '#006D73', '#56AF00'],
+        lightMute = ['#A8B8CC', '#CCB3A8', '#FFC2BD', '#96D6BF', '#D19FCE', '#8FCCCA', '#CAEAA4'],
+
+        darkNorm  = ['#304860', '#664631', '#A8391B', '#00754B', '#77206D', '#005959', '#428700'],
+        darkMute  = ['#304860', '#664631', '#A8391B', '#00754B', '#77206D', '#005959', '#428700'];
+
+    var colors= {
+        light: {
+            norm: d3.scale.ordinal().range(lightNorm),
+            mute: d3.scale.ordinal().range(lightMute)
+        },
+        dark: {
+            norm: d3.scale.ordinal().range(darkNorm),
+            mute: d3.scale.ordinal().range(darkMute)
+        }
+    };
+
+    function cat7() {
+        var tcid = 'd3utilTestCard';
+
+        function getColor(id, muted, theme) {
+            // NOTE: since we are lazily assigning domain ids, we need to
+            //       get the color from all 4 scales, to keep the domains
+            //       in sync.
+            var ln = colors.light.norm(id),
+                lm = colors.light.mute(id),
+                dn = colors.dark.norm(id),
+                dm = colors.dark.mute(id);
+            if (theme === 'dark') {
+                return muted ? dm : dn;
+            } else {
+                return muted ? lm : ln;
+            }
+        }
+
+        function testCard(svg) {
+            var g = svg.select('g#' + tcid),
+                dom = d3.range(7),
+                k, muted, theme, what;
+
+            if (!g.empty()) {
+                g.remove();
+
+            } else {
+                g = svg.append('g')
+                    .attr('id', tcid)
+                    .attr('transform', 'scale(4)translate(20,20)');
+
+                for (k=0; k<4; k++) {
+                    muted = k%2;
+                    what = muted ? ' muted' : ' normal';
+                    theme = k < 2 ? 'light' : 'dark';
+                    dom.forEach(function (id, i) {
+                        var x = i * 20,
+                            y = k * 20,
+                            f = get(id, muted, theme);
+                        g.append('circle').attr({
+                            cx: x,
+                            cy: y,
+                            r: 5,
+                            fill: f
+                        });
+                    });
+                    g.append('rect').attr({
+                        x: 140,
+                        y: k * 20 - 5,
+                        width: 32,
+                        height: 10,
+                        rx: 2,
+                        fill: '#888'
+                    });
+                    g.append('text').text(theme + what)
+                        .attr({
+                            x: 142,
+                            y: k * 20 + 2,
+                            fill: 'white'
+                        })
+                        .style('font-size', '4pt');
+                }
+            }
+        }
+
+        return {
+            testCard: testCard,
+            getColor: getColor
+        };
+    }
+
+    function translate(x, y) {
+        if (fs.isA(x) && x.length === 2 && !y) {
+            return 'translate(' + x[0] + ',' + x[1] + ')';
+        }
+        return 'translate(' + x + ',' + y + ')';
+    }
+
+    function scale(x, y) {
+        return 'scale(' + x + ',' + y + ')';
+    }
+
+    function skewX(x) {
+        return 'skewX(' + x + ')';
+    }
+
+    function rotate(deg) {
+        return 'rotate(' + deg + ')';
+    }
+
+    function stripPx(s) {
+        return s.replace(/px$/,'');
+    }
+
+    function safeId(s) {
+        return s.replace(/[^a-z0-9]/gi, '-');
+    }
+
+    function makeVisible(el, b) {
+        el.style('visibility', (b ? 'visible' : 'hidden'));
+    }
+
+    function isVisible(el) {
+        return el.style('visibility') === 'visible';
+    }
+
+    function visible(el, x) {
+        if (x === undefined) {
+            return isVisible(el);
+        } else {
+            makeVisible(el, x);
+        }
+    }
+
     angular.module('onosSvg')
-        .factory('SvgUtilService', ['$log', 'FnService',
+    .factory('SvgUtilService', ['$log', 'FnService',
         function (_$log_, _fs_) {
             $log = _$log_;
             fs = _fs_;
 
-            // TODO: change 'force' ref to be 'force.alpha' ref.
-            function createDragBehavior(force, selectCb, atDragEnd,
-                                        dragEnabled, clickEnabled) {
-                var draggedThreshold = d3.scale.linear()
-                        .domain([0, 0.1])
-                        .range([5, 20])
-                        .clamp(true),
-                    drag,
-                    fSel = fs.isF(selectCb),
-                    fEnd = fs.isF(atDragEnd),
-                    fDEn = fs.isF(dragEnabled),
-                    fCEn = fs.isF(clickEnabled),
-                    bad = [];
-
-                function naf(what) {
-                    return 'SvgUtilService: createDragBehavior(): ' + what +
-                        ' is not a function';
-                }
-
-                if (!force) {
-                    bad.push('SvgUtilService: createDragBehavior(): ' +
-                    'Bad force reference');
-                }
-                if (!fSel) {
-                    bad.push(naf('selectCb'));
-                }
-                if (!fEnd) {
-                    bad.push(naf('atDragEnd'));
-                }
-                if (!fDEn) {
-                    bad.push(naf('dragEnabled'));
-                }
-                if (!fCEn) {
-                    bad.push(naf('clickEnabled'));
-                }
-
-                if (bad.length) {
-                    $log.error(bad.join('\n'));
-                    return null;
-                }
-
-                function dragged(d) {
-                    var threshold = draggedThreshold(force.alpha()),
-                        dx = d.oldX - d.px,
-                        dy = d.oldY - d.py;
-                    if (Math.abs(dx) >= threshold || Math.abs(dy) >= threshold) {
-                        d.dragged = true;
-                    }
-                    return d.dragged;
-                }
-
-                drag = d3.behavior.drag()
-                    .origin(function(d) { return d; })
-                    .on('dragstart', function(d) {
-                        if (clickEnabled() || dragEnabled()) {
-                            d3.event.sourceEvent.stopPropagation();
-
-                            d.oldX = d.x;
-                            d.oldY = d.y;
-                            d.dragged = false;
-                            d.fixed |= 2;
-                            d.dragStarted = true;
-                        }
-                    })
-                    .on('drag', function(d) {
-                        if (dragEnabled()) {
-                            d.px = d3.event.x;
-                            d.py = d3.event.y;
-                            if (dragged(d)) {
-                                if (!force.alpha()) {
-                                    force.alpha(.025);
-                                }
-                            }
-                        }
-                    })
-                    .on('dragend', function(d) {
-                        if (d.dragStarted) {
-                            d.dragStarted = false;
-                            if (!dragged(d)) {
-                                // consider this the same as a 'click'
-                                // (selection of a node)
-                                if (clickEnabled()) {
-                                    selectCb.call(this, d);
-                                }
-                            }
-                            d.fixed &= ~6;
-
-                            // hook at the end of a drag gesture
-                            if (dragEnabled()) {
-                                atDragEnd.call(this, d);
-                            }
-                        }
-                    });
-
-                return drag;
-            }
-
-
-            function loadGlow(defs, r, g, b, id) {
-                var glow = defs.append('filter')
-                    .attr('x', '-50%')
-                    .attr('y', '-50%')
-                    .attr('width', '200%')
-                    .attr('height', '200%')
-                    .attr('id', id);
-
-                glow.append('feColorMatrix')
-                    .attr('type', 'matrix')
-                    .attr('values',
-                    '0 0 0 0  ' + r + ' ' +
-                    '0 0 0 0  ' + g + ' ' +
-                    '0 0 0 0  ' + b + ' ' +
-                    '0 0 0 1  0 ');
-
-                glow.append('feGaussianBlur')
-                    .attr('stdDeviation', 3)
-                    .attr('result', 'coloredBlur');
-
-                glow.append('feMerge').selectAll('feMergeNode')
-                    .data(['coloredBlur', 'SourceGraphic'])
-                    .enter().append('feMergeNode')
-                    .attr('in', String);
-            }
-
-            function loadGlowDefs(defs) {
-                loadGlow(defs, 0.0, 0.0, 0.7, 'blue-glow');
-                loadGlow(defs, 1.0, 1.0, 0.3, 'yellow-glow');
-            }
-
-            // --- Ordinal scales for 7 values.
-
-            //               blue       brown      brick red  sea green  purple     dark teal  lime
-            var lightNorm = ['#3E5780', '#78533B', '#CB4D28', '#018D61', '#8A2979', '#006D73', '#56AF00'],
-                lightMute = ['#A8B8CC', '#CCB3A8', '#FFC2BD', '#96D6BF', '#D19FCE', '#8FCCCA', '#CAEAA4'],
-
-                darkNorm  = ['#304860', '#664631', '#A8391B', '#00754B', '#77206D', '#005959', '#428700'],
-                darkMute  = ['#304860', '#664631', '#A8391B', '#00754B', '#77206D', '#005959', '#428700'];
-
-            var colors= {
-                light: {
-                    norm: d3.scale.ordinal().range(lightNorm),
-                    mute: d3.scale.ordinal().range(lightMute)
-                },
-                dark: {
-                    norm: d3.scale.ordinal().range(darkNorm),
-                    mute: d3.scale.ordinal().range(darkMute)
-                }
-            };
-
-            function cat7() {
-                var tcid = 'd3utilTestCard';
-
-                function getColor(id, muted, theme) {
-                    // NOTE: since we are lazily assigning domain ids, we need to
-                    //       get the color from all 4 scales, to keep the domains
-                    //       in sync.
-                    var ln = colors.light.norm(id),
-                        lm = colors.light.mute(id),
-                        dn = colors.dark.norm(id),
-                        dm = colors.dark.mute(id);
-                    if (theme === 'dark') {
-                        return muted ? dm : dn;
-                    } else {
-                        return muted ? lm : ln;
-                    }
-                }
-
-                function testCard(svg) {
-                    var g = svg.select('g#' + tcid),
-                        dom = d3.range(7),
-                        k, muted, theme, what;
-
-                    if (!g.empty()) {
-                        g.remove();
-
-                    } else {
-                        g = svg.append('g')
-                            .attr('id', tcid)
-                            .attr('transform', 'scale(4)translate(20,20)');
-
-                        for (k=0; k<4; k++) {
-                            muted = k%2;
-                            what = muted ? ' muted' : ' normal';
-                            theme = k < 2 ? 'light' : 'dark';
-                            dom.forEach(function (id, i) {
-                                var x = i * 20,
-                                    y = k * 20,
-                                    f = get(id, muted, theme);
-                                g.append('circle').attr({
-                                    cx: x,
-                                    cy: y,
-                                    r: 5,
-                                    fill: f
-                                });
-                            });
-                            g.append('rect').attr({
-                                x: 140,
-                                y: k * 20 - 5,
-                                width: 32,
-                                height: 10,
-                                rx: 2,
-                                fill: '#888'
-                            });
-                            g.append('text').text(theme + what)
-                                .attr({
-                                    x: 142,
-                                    y: k * 20 + 2,
-                                    fill: 'white'
-                                })
-                                .style('font-size', '4pt');
-                        }
-                    }
-                }
-
-                return {
-                    testCard: testCard,
-                    getColor: getColor
-                };
-            }
-
-            function translate(x, y) {
-                if (fs.isA(x) && x.length === 2 && !y) {
-                    return 'translate(' + x[0] + ',' + x[1] + ')';
-                }
-                return 'translate(' + x + ',' + y + ')';
-            }
-
-            function scale(x, y) {
-                return 'scale(' + x + ',' + y + ')';
-            }
-
-            function skewX(x) {
-                return 'skewX(' + x + ')';
-            }
-
-            function rotate(deg) {
-                return 'rotate(' + deg + ')';
-            }
-
-            function stripPx(s) {
-                return s.replace(/px$/,'');
-            }
-
-            function safeId(s) {
-                return s.replace(/[^a-z0-9]/gi, '-');
-            }
-
-            function makeVisible(el, b) {
-                el.style('visibility', (b ? 'visible' : 'hidden'));
-            }
-
-            function isVisible(el) {
-                return el.style('visibility') === 'visible';
-            }
-
             return {
                 createDragBehavior: createDragBehavior,
                 loadGlowDefs: loadGlowDefs,
@@ -299,13 +307,7 @@
                 rotate: rotate,
                 stripPx: stripPx,
                 safeId: safeId,
-                visible: function (el, x) {
-                            if (x === undefined) {
-                                return isVisible(el);
-                            } else {
-                                makeVisible(el, x);
-                            }
-                         }
+                visible: visible
             };
         }]);
 }());
diff --git a/web/gui/src/main/webapp/app/fw/util/ee.js b/web/gui/src/main/webapp/app/fw/util/ee.js
index 43455c2..78f6a9e 100644
--- a/web/gui/src/main/webapp/app/fw/util/ee.js
+++ b/web/gui/src/main/webapp/app/fw/util/ee.js
@@ -21,11 +21,26 @@
     'use strict';
 
     // injected services
-    var fs;
+    var $log, fs, flash;
 
     // function references
     var fcc = String.fromCharCode;
 
+    // magic beans
+    var beans = [
+        //'umpxwnwcw'
+        //'eufdvexoc',
+        //'egpdytgv',
+        //'xcjvte',
+        //'bgvest',
+        //'sevlr',
+        'ias'
+    ];
+
+    function pickBean() {
+        return beans[Math.floor(Math.random() * beans.length)] + '.foo';
+    }
+
     function computeTransform(x) {
         var m = x.split(':'),
             h = Number(m[0]),
@@ -45,18 +60,39 @@
 
         data.forEach(function (x) {
             var r = computeTransform(x);
-            map['shift' + r.e] = r.o.toLowerCase() + '.bin';
+            map[r.e] = r.o.toLowerCase() + '.foo';
         });
         return map;
     }
 
-    angular.module('onosUtil')
-    .factory('EeService',
-    ['FnService', function (_fs_) {
-        fs = _fs_;
+    function cluck(foo) {
+        var f = fs.isF(foo),
+            s = fs.isS(foo);
 
-        return {
-            genMap: genMap
+        $log.debug('>>> CLUCK! <<<', foo);
+
+        if (s === 'fgfb.foo') {
+            s = pickBean();
+            $log.debug('bean picked:', s);
         }
-    }]);
+
+        if (s && fs.endsWith(s, '.foo')) {
+            flash.tempDiv().append('img').attr('src', 'raw/'+s);
+        }
+
+        f && f();
+    }
+
+    angular.module('onosUtil')
+    .factory('EeService', ['$log', 'FnService', 'FlashService',
+        function (_$log_, _fs_, _flash_) {
+            $log = _$log_;
+            fs = _fs_;
+            flash = _flash_;
+
+            return {
+                genMap: genMap,
+                cluck: cluck
+            }
+        }]);
 }());
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 393f660..9c60e83 100644
--- a/web/gui/src/main/webapp/app/fw/util/keys.js
+++ b/web/gui/src/main/webapp/app/fw/util/keys.js
@@ -21,11 +21,7 @@
     'use strict';
 
     // references to injected services
-    var $log, $timeout, fs, ts, ns, qhs;
-
-    // constants
-    var eeggMin = 'shiftO',
-        eeggMax = 'shiftONOS';
+    var $log, $timeout, fs, ts, ns, ee, qhs;
 
     // internal state
     var enabled = true,
@@ -37,23 +33,30 @@
             viewFn: null,
             viewGestures: []
         },
-        eegg = '';
+        seq = {},
+        matching = false,
+        matched = '',
+        lookup;
 
-    function layEgg(key) {
-        eegg += key;
-        if (eeggMax.indexOf(eegg) === 0) {
-            if (eegg === eeggMax) {
-                d3.select('body').append('div').attr('id', 'eegg')
-                    .append('img').attr('src', 'raw/ewo.foo');
-                $timeout(function () { d3.select('#eegg').remove(); }, 3000);
-                eegg = '';
-            }
+    function matchSeq(key) {
+        if (!matching && key === 'shift') {
+            matching = true;
             return true;
         }
-        if (eegg !== eeggMin) {
-            eegg = '';
+        if (matching) {
+            matched += key;
+            lookup = fs.trieLookup(seq, matched);
+            if (lookup === -1) {
+                return true;
+            }
+            matching = false;
+            matched = '';
+            if (!lookup) {
+                return;
+            }
+            ee.cluck(lookup);
+            return true;
         }
-        return false;
     }
 
     function whatKey(code) {
@@ -109,7 +112,7 @@
         d3.event.stopPropagation();
 
         if (enabled) {
-            if (layEgg(key)) return;
+            if (matchSeq(key)) return;
 
             // global callback?
             if (gcb && gcb(token, key, keyCode, event)) {
@@ -230,13 +233,15 @@
     angular.module('onosUtil')
     .factory('KeyService',
         ['$log', '$timeout', 'FnService', 'ThemeService', 'NavService',
+            'EeService',
 
-        function (_$log_, _$timeout_, _fs_, _ts_, _ns_) {
+        function (_$log_, _$timeout_, _fs_, _ts_, _ns_, _ee_) {
             $log = _$log_;
             $timeout = _$timeout_;
             fs = _fs_;
             ts = _ts_;
             ns = _ns_;
+            ee = _ee_;
 
             return {
                 bindQhs: function (_qhs_) {
@@ -254,6 +259,12 @@
                     }
                 },
                 unbindKeys: unbindKeys,
+                addSeq: function (word, data) {
+                    fs.addToTrie(seq, word, data);
+                },
+                remSeq: function (word) {
+                    fs.removeFromTrie(seq, word);
+                },
                 gestureNotes: function (g) {
                     if (g === undefined) {
                         return keyHandler.viewGestures;
diff --git a/web/gui/src/main/webapp/app/onos.css b/web/gui/src/main/webapp/app/onos.css
index b82a6e5..0a6cd90 100644
--- a/web/gui/src/main/webapp/app/onos.css
+++ b/web/gui/src/main/webapp/app/onos.css
@@ -59,7 +59,7 @@
     color: #CE5650;
 }
 
-#eegg {
+.centered {
     position: fixed;
     top: 50%;
     left: 50%;