ONOS-1478 - GUI -- Segment navigation menu into categories.

Change-Id: I54bddcada1541ebf2926a6536e4c14bb8a1d3a66
diff --git a/core/api/src/main/java/org/onosproject/ui/UiView.java b/core/api/src/main/java/org/onosproject/ui/UiView.java
index 6e3a1a2..e406a6d 100644
--- a/core/api/src/main/java/org/onosproject/ui/UiView.java
+++ b/core/api/src/main/java/org/onosproject/ui/UiView.java
@@ -41,7 +41,14 @@
         /**
          * Represents miscellaneous views.
          */
-        OTHER("Other");
+        OTHER("Other"),
+
+        /**
+         * Represents views that do not show in the navigation menu.
+         * This category should not be specified directly; rather, use
+         * the {@link UiViewHidden} constructor instead of {@link UiView}.
+         */
+        HIDDEN("(hidden)");
 
         private final String label;
 
@@ -64,7 +71,8 @@
     private final Category category;
 
     /**
-     * Creates a new user interface view descriptor.
+     * Creates a new user interface view descriptor. The navigation item
+     * will appear in the navigation menu under the specified category.
      *
      * @param category view category
      * @param id       view identifier
diff --git a/core/api/src/main/java/org/onosproject/ui/UiViewHidden.java b/core/api/src/main/java/org/onosproject/ui/UiViewHidden.java
index 072404e..b7fea8f 100644
--- a/core/api/src/main/java/org/onosproject/ui/UiViewHidden.java
+++ b/core/api/src/main/java/org/onosproject/ui/UiViewHidden.java
@@ -29,7 +29,7 @@
      * @param id view identifier
      */
     public UiViewHidden(String id) {
-        super(Category.OTHER, id, null);
+        super(Category.HIDDEN, id, null);
     }
 
     @Override
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/MainNavResource.java b/web/gui/src/main/java/org/onosproject/ui/impl/MainNavResource.java
index 853dd94..7c58250 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/MainNavResource.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/MainNavResource.java
@@ -18,7 +18,6 @@
 import org.onosproject.ui.UiExtension;
 import org.onosproject.ui.UiExtensionService;
 import org.onosproject.ui.UiView;
-import org.onosproject.ui.UiViewHidden;
 
 import javax.ws.rs.GET;
 import javax.ws.rs.Path;
@@ -29,6 +28,9 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.SequenceInputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
 
 import static com.google.common.collect.ImmutableList.of;
 import static com.google.common.io.ByteStreams.toByteArray;
@@ -41,9 +43,13 @@
 
     private static final String NAV_HTML = "nav.html";
 
-    private static final String INJECT_VIEW_ITEMS_START = "<!-- {INJECTED-VIEW-NAV-START} -->";
-    private static final String INJECT_VIEW_ITEMS_END = "<!-- {INJECTED-VIEW-NAV-END} -->";
+    private static final String INJECT_VIEW_ITEMS_START =
+            "<!-- {INJECTED-VIEW-NAV-START} -->";
+    private static final String INJECT_VIEW_ITEMS_END =
+            "<!-- {INJECTED-VIEW-NAV-END} -->";
 
+    private static final String HDR_FORMAT =
+            "<div class=\"nav-hdr\">%s</div>\n";
     private static final String NAV_FORMAT =
             "<a ng-click=\"navCtrl.hideNav()\" href=\"#/%s\">%s</a>\n";
 
@@ -51,31 +57,60 @@
     @Produces(MediaType.TEXT_HTML)
     public Response getNavigation() throws IOException {
         UiExtensionService service = get(UiExtensionService.class);
-        InputStream navTemplate = getClass().getClassLoader().getResourceAsStream(NAV_HTML);
-        String js = new String(toByteArray(navTemplate));
+        InputStream navTemplate =
+                getClass().getClassLoader().getResourceAsStream(NAV_HTML);
+        String html = new String(toByteArray(navTemplate));
 
-        int p1s = split(js, 0, INJECT_VIEW_ITEMS_START);
-        int p1e = split(js, 0, INJECT_VIEW_ITEMS_END);
-        int p2s = split(js, p1e, null);
+        int p1s = split(html, 0, INJECT_VIEW_ITEMS_START);
+        int p1e = split(html, 0, INJECT_VIEW_ITEMS_END);
+        int p2s = split(html, p1e, null);
 
         StreamEnumeration streams =
-                new StreamEnumeration(of(stream(js, 0, p1s),
+                new StreamEnumeration(of(stream(html, 0, p1s),
                                          includeNavItems(service),
-                                         stream(js, p1e, p2s)));
+                                         stream(html, p1e, p2s)));
 
         return Response.ok(new SequenceInputStream(streams)).build();
     }
 
-    // Produces an input stream including nav item injections from all extensions.
+    // Produces an input stream of nav item injections from all extensions.
     private InputStream includeNavItems(UiExtensionService service) {
+        List<UiExtension> extensions = service.getExtensions();
         StringBuilder sb = new StringBuilder("\n");
-        for (UiExtension extension : service.getExtensions()) {
-            for (UiView view : extension.views()) {
-                if (!(view instanceof UiViewHidden)) {
-                    sb.append(String.format(NAV_FORMAT, view.id(), view.label()));
-                }
+
+        for (UiView.Category cat : UiView.Category.values()) {
+            if (cat == UiView.Category.HIDDEN) {
+                continue;
+            }
+
+            List<UiView> catViews = getViewsForCat(extensions, cat);
+            if (!catViews.isEmpty()) {
+                addCatHeader(sb, cat);
+                addCatItems(sb, catViews);
             }
         }
+
         return new ByteArrayInputStream(sb.toString().getBytes());
     }
+
+    private List<UiView> getViewsForCat(List<UiExtension> extensions,
+                                        UiView.Category cat) {
+        List<UiView> views = new ArrayList<>();
+        for (UiExtension extension : extensions) {
+            views.addAll(extension.views().stream().filter(
+                    view -> cat.equals(view.category())
+            ).collect(Collectors.toList()));
+        }
+        return views;
+    }
+
+    private void addCatHeader(StringBuilder sb, UiView.Category cat) {
+        sb.append(String.format(HDR_FORMAT, cat.label()));
+    }
+
+    private void addCatItems(StringBuilder sb, List<UiView> catViews) {
+        for (UiView view : catViews) {
+            sb.append(String.format(NAV_FORMAT, view.id(), view.label()));
+        }
+    }
 }
diff --git a/web/gui/src/main/webapp/app/fw/nav/nav.css b/web/gui/src/main/webapp/app/fw/nav/nav.css
index c36e5f3..7f67745 100644
--- a/web/gui/src/main/webapp/app/fw/nav/nav.css
+++ b/web/gui/src/main/webapp/app/fw/nav/nav.css
@@ -22,8 +22,7 @@
     position: absolute;
     top: 45px;
     left: 1px;
-    Xwidth: 200px;
-    padding: 0px;
+    padding: 0;
     z-index: 3000;
     visibility: hidden;
 }
@@ -37,6 +36,22 @@
     box-shadow: 0 2px 8px #111;
 }
 
+#nav .nav-hdr {
+    font-style: italic;
+    padding: 6px 8px 6px 8px;
+}
+
+.light #nav .nav-hdr {
+    color: #ddd;
+    border-bottom: solid 1px #999;
+    background-color: #aaa;
+}
+.dark #nav .nav-hdr {
+    color: #888;
+    border-bottom: solid 1px #444;
+    background-color: #555;
+}
+
 #nav a {
     text-decoration: none;
     font-size: 14pt;
@@ -48,7 +63,6 @@
     color: #369;
     border-bottom: solid #ccc 1px;
 }
-
 .dark #nav a {
     color: #eee;
     border-bottom: solid #333 1px;
@@ -57,7 +71,6 @@
 .light #nav a:hover {
     background-color: #ddd;
 }
-
 .dark #nav a:hover {
     background-color: #777;
 }