GUI -- Migrating the add/update device functionality to the Topology View. (WIP)
- still a lot of work to do.

Change-Id: I0453b7e2ec20a8a8149fd9d6440a13a3d43fbfd6
diff --git a/web/gui/src/main/webapp/app/fw/svg/icon.css b/web/gui/src/main/webapp/app/fw/svg/icon.css
index 20d8440..ff1bb5a 100644
--- a/web/gui/src/main/webapp/app/fw/svg/icon.css
+++ b/web/gui/src/main/webapp/app/fw/svg/icon.css
@@ -70,3 +70,7 @@
 .dark svg.embeddedIcon .icon rect {
     stroke: #ccc;
 }
+
+svg .svgIcon {
+    fill-rule: evenodd;
+}
diff --git a/web/gui/src/main/webapp/app/fw/svg/icon.js b/web/gui/src/main/webapp/app/fw/svg/icon.js
index d8c2584..21f9b9d 100644
--- a/web/gui/src/main/webapp/app/fw/svg/icon.js
+++ b/web/gui/src/main/webapp/app/fw/svg/icon.js
@@ -26,15 +26,17 @@
         cornerSize = vboxSize / 10,
         viewBox = '0 0 ' + vboxSize + ' ' + vboxSize;
 
-    // maps icon id to the glyph id it uses.
-    // note: icon id maps to a CSS class for styling that icon
+    // Maps icon ID to the glyph ID it uses.
+    // NOTE: icon ID maps to a CSS class for styling that icon
     var glyphMapping = {
-            deviceOnline: 'checkMark',
-            deviceOffline: 'xMark',
-            tableColSortAsc: 'triangleUp',
-            tableColSortDesc: 'triangleDown',
-            tableColSortNone: '-'
-        };
+        deviceOnline: 'checkMark',
+        deviceOffline: 'xMark',
+        tableColSortAsc: 'triangleUp',
+        tableColSortDesc: 'triangleDown',
+        tableColSortNone: '-'
+    };
+
+
 
     function ensureIconLibDefs() {
         var body = d3.select('body'),
@@ -48,6 +50,108 @@
         return svg.select('defs');
     }
 
+    // div is a D3 selection of the <DIV> element into which icon should load
+    // iconCls is the CSS class used to identify the icon
+    // size is dimension of icon in pixels. Defaults to 20.
+    // installGlyph, if truthy, will cause the glyph to be added to
+    //      well-known defs element. Defaults to false.
+    // svgClass is the CSS class used to identify the SVG layer.
+    //      Defaults to 'embeddedIcon'.
+    function loadIcon(div, iconCls, size, installGlyph, svgClass) {
+        var dim = size || 20,
+            svgCls = svgClass || 'embeddedIcon',
+            gid = glyphMapping[iconCls] || 'unknown',
+            svg, g;
+
+        if (installGlyph) {
+            gs.loadDefs(ensureIconLibDefs(), [gid], true);
+        }
+
+        svg = div.append('svg').attr({
+            'class': svgCls,
+            width: dim,
+            height: dim,
+            viewBox: viewBox
+        });
+
+        g = svg.append('g').attr({
+            'class': 'icon ' + iconCls
+        });
+
+        g.append('rect').attr({
+            width: vboxSize,
+            height: vboxSize,
+            rx: cornerSize
+        });
+
+        if (gid !== '-') {
+            g.append('use').attr({
+                width: vboxSize,
+                height: vboxSize,
+                'class': 'glyph',
+                'xlink:href': '#' + gid
+            });
+        }
+    }
+
+    function loadEmbeddedIcon(div, iconCls, size) {
+        loadIcon(div, iconCls, size, true);
+    }
+
+
+    // configuration for device and host icons in the topology view
+    var config = {
+        device: {
+            dim: 36,
+            rx: 4
+        },
+        host: {
+            radius: {
+                noGlyph: 9,
+                withGlyph: 14
+            },
+            glyphed: {
+                endstation: 1,
+                bgpSpeaker: 1,
+                router: 1
+            }
+        }
+    };
+
+
+    // Adds a device icon to the specified element, using the given glyph.
+    // Returns the D3 selection of the icon.
+    function addDeviceIcon(elem, glyphId) {
+        var cfg = config.device,
+            g = elem.append('g')
+                .attr('class', 'svgIcon deviceIcon');
+
+        g.append('rect').attr({
+            x: 0,
+            y: 0,
+            rx: cfg.rx,
+            width: cfg.dim,
+            height: cfg.dim
+        });
+
+        g.append('use').attr({
+            'xlink:href': '#' + glyphId,
+            width: cfg.dim,
+            height: cfg.dim
+        });
+
+        g.dim = cfg.dim;
+        return g;
+    }
+
+    function addHostIcon(elem, glyphId) {
+        // TODO:
+    }
+
+
+    // =========================
+    // === DEFINE THE MODULE
+
     angular.module('onosSvg')
         .factory('IconService', ['$log', 'FnService', 'GlyphService',
         function (_$log_, _fs_, _gs_) {
@@ -55,57 +159,12 @@
             fs = _fs_;
             gs = _gs_;
 
-            // div is a D3 selection of the <DIV> element into which icon should load
-            // iconCls is the CSS class used to identify the icon
-            // size is dimension of icon in pixels. Defaults to 20.
-            // installGlyph, if truthy, will cause the glyph to be added to
-            //      well-known defs element. Defaults to false.
-            // svgClass is the CSS class used to identify the SVG layer.
-            //      Defaults to 'embeddedIcon'.
-            function loadIcon(div, iconCls, size, installGlyph, svgClass) {
-                var dim = size || 20,
-                    svgCls = svgClass || 'embeddedIcon',
-                    gid = glyphMapping[iconCls] || 'unknown',
-                    svg, g;
-
-                if (installGlyph) {
-                    gs.loadDefs(ensureIconLibDefs(), [gid], true);
-                }
-
-                svg = div.append('svg').attr({
-                        'class': svgCls,
-                        width: dim,
-                        height: dim,
-                        viewBox: viewBox
-                    });
-
-                g = svg.append('g').attr({
-                    'class': 'icon ' + iconCls
-                });
-
-                g.append('rect').attr({
-                    width: vboxSize,
-                    height: vboxSize,
-                    rx: cornerSize
-                });
-
-                if (gid !== '-') {
-                    g.append('use').attr({
-                        width: vboxSize,
-                        height: vboxSize,
-                        'class': 'glyph',
-                        'xlink:href': '#' + gid
-                    });
-                }
-            }
-
-            function loadEmbeddedIcon(div, iconCls, size) {
-                loadIcon(div, iconCls, size, true);
-            }
-
             return {
                 loadIcon: loadIcon,
-                loadEmbeddedIcon: loadEmbeddedIcon
+                loadEmbeddedIcon: loadEmbeddedIcon,
+                addDeviceIcon: addDeviceIcon,
+                addHostIcon: addHostIcon,
+                iconConfig: function () { return config; }
             };
         }]);
 
diff --git a/web/gui/src/main/webapp/app/fw/svg/map.js b/web/gui/src/main/webapp/app/fw/svg/map.js
index f44ffe0..1faf6e2 100644
--- a/web/gui/src/main/webapp/app/fw/svg/map.js
+++ b/web/gui/src/main/webapp/app/fw/svg/map.js
@@ -22,45 +22,53 @@
     The Map Service provides a simple API for loading geographical maps into
     an SVG layer. For example, as a background to the Topology View.
 
-    e.g.  var ok = MapService.loadMapInto(svgLayer, '*continental-us');
+    e.g.  var promise = MapService.loadMapInto(svgLayer, '*continental-us');
 
     The Map Service makes use of the GeoDataService to load the required data
     from the server and to create the appropriate geographical projection.
 
+    A promise is returned to the caller, which is resolved with the
+    map projection once created.
 */
 
 (function () {
     'use strict';
 
     // injected references
-    var $log, fs, gds;
+    var $log, $q, fs, gds;
+
+    function loadMapInto(mapLayer, id, opts) {
+        var promise = gds.fetchTopoData(id),
+            deferredProjection = $q.defer();
+
+        if (!promise) {
+            $log.warn('Failed to load map: ' + id);
+            return false;
+        }
+
+        promise.then(function () {
+            var gen = gds.createPathGenerator(promise.topodata, opts);
+
+            deferredProjection.resolve(gen.settings.projection);
+
+            mapLayer.selectAll('path')
+                .data(gen.geodata.features)
+                .enter()
+                .append('path')
+                .attr('d', gen.pathgen);
+        });
+        return deferredProjection.promise;
+    }
+
 
     angular.module('onosSvg')
-        .factory('MapService', ['$log', 'FnService', 'GeoDataService',
-        function (_$log_, _fs_, _gds_) {
+        .factory('MapService', ['$log', '$q', 'FnService', 'GeoDataService',
+        function (_$log_, _$q_, _fs_, _gds_) {
             $log = _$log_;
+            $q = _$q_;
             fs = _fs_;
             gds = _gds_;
 
-            function loadMapInto(mapLayer, id, opts) {
-                var promise = gds.fetchTopoData(id);
-                if (!promise) {
-                    $log.warn('Failed to load map: ' + id);
-                    return false;
-                }
-
-                promise.then(function () {
-                    var gen = gds.createPathGenerator(promise.topodata, opts);
-
-                    mapLayer.selectAll('path')
-                        .data(gen.geodata.features)
-                        .enter()
-                        .append('path')
-                        .attr('d', gen.pathgen);
-                });
-                return true;
-            }
-
             return {
                 loadMapInto: loadMapInto
             };
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 bef8248..3a35e9f 100644
--- a/web/gui/src/main/webapp/app/fw/svg/svgUtil.js
+++ b/web/gui/src/main/webapp/app/fw/svg/svgUtil.js
@@ -240,13 +240,18 @@
                 el.style('visibility', (b ? 'visible' : 'hidden'));
             }
 
+            function safeId(s) {
+                return s.replace(/[^a-z0-9]/gi, '-');
+            }
+
             return {
                 createDragBehavior: createDragBehavior,
                 loadGlow: loadGlow,
                 cat7: cat7,
                 translate: translate,
                 stripPx: stripPx,
-                makeVisible: makeVisible
+                makeVisible: makeVisible,
+                safeId: safeId
             };
         }]);
 }());