GUI2 added in details panel, updated docs

Change-Id: I49a874deeb4de1082f190ea5d0d985c986a978f8
diff --git a/web/gui2/src/main/webapp/app/view/topology/topology/topology.component.html b/web/gui2/src/main/webapp/app/view/topology/topology/topology.component.html
index 2a654b7..6fe0cb1 100644
--- a/web/gui2/src/main/webapp/app/view/topology/topology/topology.component.html
+++ b/web/gui2/src/main/webapp/app/view/topology/topology/topology.component.html
@@ -1,5 +1,5 @@
 <!--
-~ Copyright 2018-present Open Networking Foundation
+~ Copyright 2019-present Open Networking Foundation
 ~
 ~ Licensed under the Apache License, Version 2.0 (the "License");
 ~ you may not use this file except in compliance with the License.
@@ -13,17 +13,42 @@
 ~ See the License for the specific language governing permissions and
 ~ limitations under the License.
 -->
+<!-- Template explaination - Add in the flash message component - and link it to
+the local variable - this is used to display messages when keyboard shortcuts are pressed
+-->
 <onos-flash id="topoMsgFlash" message="{{ flashMsg }}" (closed)="flashMsg = ''"></onos-flash>
 
+<!-- Template explanation - Add in the Panel components for the Topology view
+    These are referenced inside the typescript by @ViewChild and their label
+-->
 <onos-instance #instance [divTopPx]="80" (mastershipEvent)="force.onosInstMastership = $event"></onos-instance>
 <onos-summary #summary></onos-summary>
-<onos-toolbar #toolbar></onos-toolbar>
+<onos-toolbar #toolbar (buttonEvent)="toolbarButtonClicked($event)"></onos-toolbar>
 <onos-details #details></onos-details>
 
 <div id="ov-topo2">
+    <!-- Template explanation -
+    Line 0) This is the root of the whole SVG canvas of the Topology View - all
+        components beneath it are SVG components only (no HTML)
+    line 1) the No Devices Connected banner is shown if the force component
+        (from line 4) does not contain any devices
+    line 2) Create an SVG Grouping and apply the onosZoomableOf directive to it,
+        passing in the whole SVG canvas (#svgZoom)
+    line 3) Add in the Background Svg Component (if showBackground is true - toggled
+        by toolbar and by keyboard shortcut 'B'
+    line 4) Add in the layer of the Force Svg Component. If any item is selected on it, pass
+        to the details view and deselect all others. This is node and line graph
+        whose contents are supplied through the Topology Service, and whose positions
+        are driven by the d3.force engine
+    -->
     <svg:svg #svgZoom xmlns:svg="http://www.w3.org/2000/svg" viewBox="0 0 1000 1000" id="topo2">
-        <svg:g *ngIf="force.regionData?.devices.length === 0" onos-nodeviceconnected />
+        <svg:desc>The main SVG canvas of the Topology View</svg:desc>
+        <svg:g *ngIf="force.regionData?.devices[0].length +
+                        force.regionData?.devices[1].length +
+                        force.regionData?.devices[2].length=== 0"
+               onos-nodeviceconnected />
         <svg:g id="topo-zoomlayer" onosZoomableOf [zoomableOf]="svgZoom">
+            <svg:desc>A logical layer that allows the main SVG canvas to be zoomed and panned</svg:desc>
             <svg:g *ngIf="showBackground" onos-backgroundsvg/>
             <svg:g #force onos-forcesvg (selectedNodeEvent)="nodeSelected($event)"/>
         </svg:g>
diff --git a/web/gui2/src/main/webapp/app/view/topology/topology/topology.component.spec.ts b/web/gui2/src/main/webapp/app/view/topology/topology/topology.component.spec.ts
index a9c6194..23fb257 100644
--- a/web/gui2/src/main/webapp/app/view/topology/topology/topology.component.spec.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/topology/topology.component.spec.ts
@@ -32,9 +32,11 @@
 import {
     FlashComponent,
     FnService,
-    LogService
+    LogService,
+    IconService, IconComponent
 } from 'gui2-fw-lib';
 import {ZoomableDirective} from '../layer/zoomable.directive';
+import {RouterTestingModule} from '@angular/router/testing';
 
 
 class MockActivatedRoute extends ActivatedRoute {
@@ -72,6 +74,10 @@
     destroy() {}
 }
 
+class MockIconService {
+    loadIconDef() { }
+}
+
 /**
  * ONOS GUI -- Topology View -- Unit Tests
  */
@@ -101,7 +107,7 @@
         fs = new FnService(ar, logSpy, windowMock);
 
         TestBed.configureTestingModule({
-            imports: [ BrowserAnimationsModule ],
+            imports: [ BrowserAnimationsModule, RouterTestingModule ],
             declarations: [
                 TopologyComponent,
                 InstanceComponent,
@@ -109,7 +115,8 @@
                 ToolbarComponent,
                 DetailsComponent,
                 FlashComponent,
-                ZoomableDirective
+                ZoomableDirective,
+                IconComponent
             ],
             providers: [
                 { provide: FnService, useValue: fs },
@@ -117,6 +124,7 @@
                 { provide: 'Window', useValue: windowMock },
                 { provide: HttpClient, useClass: MockHttpClient },
                 { provide: TopologyService, useClass: MockTopologyService },
+                { provide: IconService, useClass: MockIconService },
             ]
         }).compileComponents();
         logServiceSpy = TestBed.get(LogService);
diff --git a/web/gui2/src/main/webapp/app/view/topology/topology/topology.component.ts b/web/gui2/src/main/webapp/app/view/topology/topology/topology.component.ts
index 7365310..3e1e1a6 100644
--- a/web/gui2/src/main/webapp/app/view/topology/topology/topology.component.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/topology/topology.component.ts
@@ -1,5 +1,5 @@
 /*
- * Copyright 2018-present Open Networking Foundation
+ * Copyright 2019-present Open Networking Foundation
  *
  * Licensed under the Apache License, Version 2.0 (the 'License');
  * you may not use this file except in compliance with the License.
@@ -21,9 +21,9 @@
 } from '@angular/core';
 import * as d3 from 'd3';
 import {
-    FnService,
+    FnService, IconService,
     KeysService,
-    KeysToken,
+    KeysToken, LionService,
     LogService,
     PrefsService,
     SvgUtilService,
@@ -36,9 +36,14 @@
 import {BackgroundSvgComponent} from '../layer/backgroundsvg/backgroundsvg.component';
 import {ForceSvgComponent} from '../layer/forcesvg/forcesvg.component';
 import {TopologyService} from '../topology.service';
-import {HostLabelToggle, LabelToggle, Node} from '../layer/forcesvg/models';
+import {
+    HostLabelToggle,
+    LabelToggle,
+    UiElement
+} from '../layer/forcesvg/models';
 import {ToolbarComponent} from '../panel/toolbar/toolbar.component';
 import {TrafficService} from '../traffic.service';
+import {ZoomableDirective} from '../layer/zoomable.directive';
 
 /**
  * ONOS GUI Topology View
@@ -77,11 +82,13 @@
     @ViewChild(ToolbarComponent) toolbar: ToolbarComponent;
     @ViewChild(BackgroundSvgComponent) background: BackgroundSvgComponent;
     @ViewChild(ForceSvgComponent) force: ForceSvgComponent;
+    @ViewChild(ZoomableDirective) zoomDirective: ZoomableDirective;
 
     flashMsg: string = '';
     prefsState = {};
     hostLabelIdx: number = 1;
     showBackground: boolean = false;
+    lionFn; // Function
 
     constructor(
         protected log: LogService,
@@ -92,29 +99,69 @@
         protected wss: WebSocketService,
         protected zs: ZoomService,
         protected ts: TopologyService,
-        protected trs: TrafficService
+        protected trs: TrafficService,
+        protected is: IconService,
+        private lion: LionService,
     ) {
+        if (this.lion.ubercache.length === 0) {
+            this.lionFn = this.dummyLion;
+            this.lion.loadCbs.set('topo-toolbar', () => this.doLion());
+        } else {
+            this.doLion();
+        }
 
+        this.is.loadIconDef('bird');
+        this.is.loadIconDef('active');
+        this.is.loadIconDef('uiAttached');
+        this.is.loadIconDef('m_switch');
+        this.is.loadIconDef('m_roadm');
+        this.is.loadIconDef('m_router');
+        this.is.loadIconDef('m_uiAttached');
+        this.is.loadIconDef('m_endstation');
+        this.is.loadIconDef('m_ports');
+        this.is.loadIconDef('m_summary');
+        this.is.loadIconDef('m_details');
+        this.is.loadIconDef('m_map');
+        this.is.loadIconDef('m_cycleLabels');
+        this.is.loadIconDef('m_resetZoom');
+        this.is.loadIconDef('m_eqMaster');
+        this.is.loadIconDef('m_unknown');
+        this.is.loadIconDef('m_allTraffic');
+        this.is.loadIconDef('deviceTable');
+        this.is.loadIconDef('flowTable');
+        this.is.loadIconDef('portTable');
+        this.is.loadIconDef('groupTable');
+        this.is.loadIconDef('meterTable');
+        this.is.loadIconDef('triangleUp');
         this.log.debug('Topology component constructed');
     }
 
+    /**
+     * Static functions must come before member variables
+     * @param index
+     */
     private static deviceLabelFlashMessage(index: number): string {
         switch (index) {
-            case 0: return 'Hide device labels';
-            case 1: return 'Show friendly device labels';
-            case 2: return 'Show device ID labels';
+            case 0: return 'fl_device_labels_hide';
+            case 1: return 'fl_device_labels_show_friendly';
+            case 2: return 'fl_device_labels_show_id';
         }
     }
 
     private static hostLabelFlashMessage(index: number): string {
         switch (index) {
-            case 0: return 'Hide host labels';
-            case 1: return 'Show friendly host labels';
-            case 2: return 'Show host IP labels';
-            case 3: return 'Show host MAC Address labels';
+            case 0: return 'fl_host_labels_hide';
+            case 1: return 'fl_host_labels_show_friendly';
+            case 2: return 'fl_host_labels_show_ip';
+            case 3: return 'fl_host_labels_show_mac';
         }
     }
 
+    /**
+     * Pass the list of Key Commands to the KeyService, and initialize the Topology
+     * Service - which communicates with through the WebSocket to the ONOS server
+     * to get the nodes and links.
+     */
     ngOnInit() {
         this.bindCommands();
         // The components from the template are handed over to TopologyService here
@@ -125,11 +172,72 @@
         this.log.debug('Topology component initialized');
     }
 
+    /**
+     * When this component is being stopped, disconnect the TopologyService from
+     * the WebSocket
+     */
     ngOnDestroy() {
         this.ts.destroy();
         this.log.debug('Topology component destroyed');
     }
 
+    /**
+     * When ever a toolbar button is clicked, an event is sent up from toolbar
+     * component which is caught and passed on to here.
+     * @param name The name of the button that was clicked
+     */
+    toolbarButtonClicked(name: string) {
+        switch (name) {
+            case 'instance-tog':
+                this.toggleInstancePanel();
+                break;
+            case 'summary-tog':
+                this.toggleSummary();
+                break;
+            case 'details-tog':
+                this.toggleDetails();
+                break;
+            case 'hosts-tog':
+                this.toggleHosts();
+                break;
+            case 'offline-tog':
+                this.toggleOfflineDevices();
+                break;
+            case 'ports-tog':
+                this.togglePorts();
+                break;
+            case 'bkgrnd-tog':
+                this.toggleBackground();
+                break;
+            case 'cycleLabels-btn':
+                this.cycleDeviceLabels();
+                break;
+            case 'cycleHostLabel-btn':
+                this.cycleHostLabels();
+                break;
+            case 'resetZoom-btn':
+                this.resetZoom();
+                break;
+            case 'eqMaster-btn':
+                this.equalizeMasters();
+                break;
+            case 'cancel-traffic':
+                this.cancelTraffic();
+                break;
+            case 'all-traffic':
+                this.monitorAllTraffic();
+                break;
+            default:
+                this.log.warn('Unhandled Toolbar action', name);
+        }
+    }
+
+    /**
+     * The list of key strokes that will be active in the Topology View.
+     *
+     * This action map is passed to the KeyService through the bindCommands()
+     * when this component is being initialized
+     */
     actionMap() {
         return {
             A: [() => {this.monitorAllTraffic(); }, 'Monitor all traffic'],
@@ -226,7 +334,7 @@
         const next = LabelToggle.next(old);
         this.force.ngOnChanges({'deviceLabelToggle':
                 new SimpleChange(old, next, false)});
-        this.flashMsg = TopologyComponent.deviceLabelFlashMessage(next);
+        this.flashMsg = this.lionFn(TopologyComponent.deviceLabelFlashMessage(next));
         this.log.debug('Cycling device labels', old, next);
     }
 
@@ -235,108 +343,112 @@
         const next = HostLabelToggle.next(old);
         this.force.ngOnChanges({'hostLabelToggle':
                 new SimpleChange(old, next, false)});
-        this.flashMsg = TopologyComponent.hostLabelFlashMessage(next);
+        this.flashMsg = this.lionFn(TopologyComponent.hostLabelFlashMessage(next));
         this.log.debug('Cycling host labels', old, next);
     }
 
-    protected toggleBackground(token: KeysToken) {
-        this.flashMsg = 'Toggling background';
+    protected toggleBackground(token?: KeysToken) {
         this.showBackground = !this.showBackground;
+        this.flashMsg = this.lionFn(this.showBackground ? 'show' : 'hide') +
+            ' ' + this.lionFn('fl_background_map');
+        this.toolbar.backgroundVisible = this.showBackground;
         this.log.debug('Toggling background', token);
-        // TODO: Reinstate with components
-        // t2bgs.toggle(x);
     }
 
-    protected toggleDetails(token: KeysToken) {
+    protected toggleDetails(token?: KeysToken) {
         if (this.details.selectedNode) {
-            this.flashMsg = 'Toggling details';
-            this.details.togglePanel(() => {
+            const on: boolean = this.details.togglePanel(() => {
             });
+            this.flashMsg = this.lionFn(on ? 'show' : 'hide') +
+                ' ' + this.lionFn('fl_panel_details');
+            this.toolbar.detailsVisible = on;
+
             this.log.debug('Toggling details', token);
         }
     }
 
-    protected toggleInstancePanel(token: KeysToken) {
-        this.flashMsg = 'Toggling instances';
-        this.instance.togglePanel(() => {});
-        this.log.debug('Toggling instances', token);
-        // TODO: Reinstate with components
-        // this.updatePrefsState('insts', t2is.toggle(x));
+    protected toggleInstancePanel(token?: KeysToken) {
+        const on: boolean = this.instance.togglePanel(() => {});
+        this.flashMsg = this.lionFn(on ? 'show' : 'hide') +
+            ' ' + this.lionFn('fl_panel_instances');
+        this.toolbar.instancesVisible = on;
+        this.log.debug('Toggling instances', token, on);
     }
 
     protected toggleSummary() {
-        this.flashMsg = 'Toggling summary';
-        this.summary.togglePanel(() => {});
+        const on: boolean = this.summary.togglePanel(() => {});
+        this.flashMsg = this.lionFn(on ? 'show' : 'hide') +
+            ' ' + this.lionFn('fl_panel_summary');
+        this.toolbar.summaryVisible = on;
     }
 
     protected resetZoom() {
-        // this.zoomer.reset();
-        this.log.debug('resetting zoom');
-        // TODO: Reinstate with components
-        // t2bgs.resetZoom();
-        // flash.flash('Pan and zoom reset');
+        this.zoomDirective.resetZoom();
+        this.flashMsg = this.lionFn('fl_pan_zoom_reset');
     }
 
-    protected togglePorts(token: KeysToken) {
-        this.log.debug('Toggling ports');
-        // TODO: Reinstate with components
-        // this.updatePrefsState('porthl', t2vs.togglePortHighlights(x));
-        // t2fs.updateLinks();
+    protected togglePorts(token?: KeysToken) {
+        const old: boolean = this.force.highlightPorts;
+        const current: boolean = !this.force.highlightPorts;
+        this.force.ngOnChanges({'highlightPorts': new SimpleChange(old, current, false)});
+        this.flashMsg = this.lionFn(current ? 'enable' : 'disable') +
+            ' ' + this.lionFn('fl_port_highlighting');
+        this.toolbar.portsVisible = current;
+        this.log.debug(current ? 'Enable' : 'Disable', 'port highlighting');
     }
 
     protected equalizeMasters() {
         this.wss.sendEvent('equalizeMasters', null);
-
+        this.flashMsg = this.lionFn('fl_eq_masters');
         this.log.debug('equalizing masters');
-        // TODO: Reinstate with components
-        // flash.flash('Equalizing master roles');
     }
 
     protected resetNodeLocation() {
+        // TODO: Implement reset locations
+        this.flashMsg = this.lionFn('fl_reset_node_locations');
         this.log.debug('resetting node location');
-        // TODO: Reinstate with components
-        // t2fs.resetNodeLocation();
-        // flash.flash('Reset node locations');
     }
 
     protected unpinNode() {
+        // TODO: Implement this
         this.log.debug('unpinning node');
-        // TODO: Reinstate with components
-        // t2fs.unpin();
-        // flash.flash('Unpin node');
     }
 
     protected toggleToolbar() {
         this.log.debug('toggling toolbar');
-        this.flashMsg = ('Toggle toolbar');
         this.toolbar.on = !this.toolbar.on;
     }
 
-    protected actionedFlashed(action, message) {
-        this.log.debug('action flashed');
-        // TODO: Reinstate with components
-        // this.flash.flash(action + ' ' + message);
-    }
-
     protected toggleHosts() {
         const old: boolean = this.force.showHosts;
         const current = !this.force.showHosts;
         this.force.ngOnChanges({'showHosts': new SimpleChange(old, current, false)});
-        this.flashMsg = (this.force.showHosts ? 'Show' : 'Hide') + ' Hosts';
+        this.flashMsg = this.lionFn('hosts') + ' ' +
+                        this.lionFn(this.force.showHosts ? 'visible' : 'hidden');
+        this.toolbar.hostsVisible = current;
         this.log.debug('toggling hosts: ', this.force.showHosts ? 'Show' : 'Hide');
     }
 
     protected toggleOfflineDevices() {
+        // TODO: Implement toggle offline visibility
+        const on: boolean = true;
+        this.flashMsg = this.lionFn(on ? 'show' : 'hide') +
+            ' ' + this.lionFn('fl_offline_devices');
         this.log.debug('toggling offline devices');
-        // TODO: Reinstate with components
-        // let on = t2rs.toggleOfflineDevices();
-        // this.actionedFlashed(on ? 'Show': 'Hide', 'offline devices');
     }
 
+    /**
+     * Check to see if this is needed anymore
+     * @param what
+     */
     protected notValid(what) {
         this.log.warn('topo.js getActionEntry(): Not a valid ' + what);
     }
 
+    /**
+     * Check to see if this is needed anymore
+     * @param what
+     */
     getActionEntry(key) {
         let entry;
 
@@ -354,15 +466,23 @@
         return this.fs.isA(entry) || [entry, ''];
     }
 
-    nodeSelected(node: Node) {
-        this.details.selectedNode = node;
-        this.details.on = Boolean(node);
+    /**
+     * An event handler that updates the details panel as items are
+     * selected in the forcesvg layer
+     * @param nodeOrLink the item to display details of
+     */
+    nodeSelected(nodeOrLink: UiElement) {
+        this.details.ngOnChanges({'selectedNode':
+            new SimpleChange(undefined, nodeOrLink, true)});
+        this.details.on = Boolean(nodeOrLink);
     }
 
     /**
      * Enable traffic monitoring
      */
     monitorAllTraffic() {
+        // TODO: Implement support for toggling between bits, packets and octets
+        this.flashMsg = this.lionFn('tr_fl_pstats_bits');
         this.trs.init(this.force);
     }
 
@@ -370,6 +490,22 @@
      * Cancel traffic monitoring
      */
     cancelTraffic() {
+        this.flashMsg = this.lionFn('fl_monitoring_canceled');
         this.trs.destroy();
     }
+
+    /**
+     * Read the LION bundle for Toolbar and set up the lionFn
+     */
+    doLion() {
+        this.lionFn = this.lion.bundle('core.view.Topo');
+    }
+
+    /**
+     * A dummy implementation of the lionFn until the response is received and the LION
+     * bundle is received from the WebSocket
+     */
+    dummyLion(key: string): string {
+        return '%' + key + '%';
+    }
 }