GUI2 Extract Topology view in to its own library

Change-Id: I45597d0902c99b5b3d606966866cc518011c54a0
diff --git a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/topology/topology.component.ts b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/topology/topology.component.ts
new file mode 100644
index 0000000..d7055a4
--- /dev/null
+++ b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/topology/topology.component.ts
@@ -0,0 +1,674 @@
+/*
+ * 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.
+ * 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.
+ */
+import {
+    AfterContentInit,
+    Component, HostListener, Inject, Input,
+    OnDestroy,
+    OnInit, SimpleChange,
+    ViewChild
+} from '@angular/core';
+import * as d3 from 'd3';
+import {
+    FnService, IconService,
+    KeysService,
+    KeysToken, LionService,
+    LogService,
+    PrefsService,
+    SvgUtilService,
+    WebSocketService,
+    TopoZoomPrefs, ZoomUtils
+} from 'gui2-fw-lib';
+import {InstanceComponent} from '../panel/instance/instance.component';
+import {DetailsComponent} from '../panel/details/details.component';
+import {BackgroundSvgComponent} from '../layer/backgroundsvg/backgroundsvg.component';
+import {ForceSvgComponent} from '../layer/forcesvg/forcesvg.component';
+import {TopologyService} from '../topology.service';
+import {
+    GridDisplayToggle,
+    HostLabelToggle,
+    LabelToggle,
+    UiElement
+} from '../layer/forcesvg/models';
+import {
+    INSTANCE_TOGGLE, SUMMARY_TOGGLE, DETAILS_TOGGLE,
+    HOSTS_TOGGLE, OFFLINE_TOGGLE, PORTS_TOGGLE,
+    BKGRND_TOGGLE, CYCLELABELS_BTN, CYCLEHOSTLABEL_BTN,
+    CYCLEGRIDDISPLAY_BTN, RESETZOOM_BTN, EQMASTER_BTN,
+    CANCEL_TRAFFIC, ALL_TRAFFIC, QUICKHELP_BTN, BKGRND_SELECT
+} from '../panel/toolbar/toolbar.component';
+import {TrafficService} from '../traffic.service';
+import {ZoomableDirective} from '../layer/zoomable.directive';
+import {MapObject} from '../layer/maputils';
+
+const TOPO2_PREFS = 'topo2_prefs';
+const TOPO_MAPID_PREFS = 'topo_mapid';
+
+const PREF_BG = 'bg';
+const PREF_DETAIL = 'detail';
+const PREF_DLBLS = 'dlbls';
+const PREF_HLBLS = 'hlbls';
+const PREF_GRID = 'grid';
+const PREF_HOSTS = 'hosts';
+const PREF_INSTS = 'insts';
+const PREF_OFFDEV = 'offdev';
+const PREF_PORTHL = 'porthl';
+const PREF_SUMMARY = 'summary';
+const PREF_TOOLBAR = 'toolbar';
+
+/**
+ * Model of the topo2_prefs object - this is a subset of the overall Prefs returned
+ * by the server
+ */
+export interface Topo2Prefs {
+    bg: number;
+    detail: number;
+    dlbls: number;
+    hlbls: number;
+    hosts: number;
+    insts: number;
+    offdev: number;
+    porthl: number;
+    spr: number;
+    ovid: string;
+    summary: number;
+    toolbar: number;
+    grid: number;
+}
+
+/**
+ * ONOS GUI Topology View
+ *
+ * This Topology View component is the top level component in a hierarchy that
+ * comprises the whole Topology View
+ *
+ * There are three main parts (panels, graphical and breadcrumbs)
+ * The panel hierarchy
+ * |-- Instances Panel (shows ONOS instances)
+ * |-- Summary Panel (summary of ONOS)
+ * |-- Toolbar Panel (the toolbar)
+ * |-- Details Panel (when a node is selected in the Force graphical view (see below))
+ *
+ * The graphical hierarchy contains
+ * Topology (this)
+ *  |-- No Devices Connected (only of there are no nodes to show)
+ *  |-- Zoom Layer (everything beneath this can be zoomed and panned)
+ *      |-- Background (container for any backgrounds - can be toggled on and off)
+ *          |-- Map
+ *      |-- Forces (all of the nodes and links laid out by a d3.force simulation)
+ *
+ * The breadcrumbs
+ * |-- Breadcrumb (in region view a way of navigating back up through regions)
+ */
+@Component({
+  selector: 'onos-topology',
+  templateUrl: './topology.component.html',
+  styleUrls: ['./topology.component.css']
+})
+export class TopologyComponent implements AfterContentInit, OnInit, OnDestroy {
+    @Input() bannerHeight: number = 48;
+    // These are references to the components inserted in the template
+    @ViewChild(InstanceComponent) instance: InstanceComponent;
+    @ViewChild(DetailsComponent) details: DetailsComponent;
+    @ViewChild(BackgroundSvgComponent) background: BackgroundSvgComponent;
+    @ViewChild(ForceSvgComponent) force: ForceSvgComponent;
+    @ViewChild(ZoomableDirective) zoomDirective: ZoomableDirective;
+
+    flashMsg: string = '';
+    // These are used as defaults if nothing is set on the server
+    prefsState: Topo2Prefs = <Topo2Prefs>{
+        bg: 0,
+        detail: 1,
+        dlbls: 0,
+        hlbls: 2,
+        hosts: 0,
+        insts: 1,
+        offdev: 1,
+        ovid: 'traffic', // default to traffic overlay
+        porthl: 1,
+        spr: 0,
+        summary: 1,
+        toolbar: 0,
+        grid: 0
+    };
+
+    mapIdState: MapObject = <MapObject>{
+        id: undefined,
+        scale: 1.0
+    };
+    mapSelShown: boolean = false;
+    lionFn; // Function
+
+    gridShown: boolean = true;
+    geoGridShown: boolean = true;
+
+    constructor(
+        protected log: LogService,
+        protected fs: FnService,
+        protected ks: KeysService,
+        protected sus: SvgUtilService,
+        protected ps: PrefsService,
+        protected wss: WebSocketService,
+        protected ts: TopologyService,
+        protected trs: TrafficService,
+        protected is: IconService,
+        private lion: LionService,
+        @Inject('Window') public window: any,
+    ) {
+        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_selectMap');
+        this.is.loadIconDef('m_cycleLabels');
+        this.is.loadIconDef('m_cycleGridDisplay');
+        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 Corresponds to LabelToggle.Enum index
+     */
+    private static deviceLabelFlashMessage(index: number): string {
+        switch (index) {
+            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 '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';
+        }
+    }
+
+    private static gridDisplayFlashMessage(index: number): string {
+        switch (index) {
+            case 0: return 'fl_grid_display_hide';
+            case 1: return 'fl_grid_display_1000';
+            case 2: return 'fl_grid_display_geo';
+            case 3: return 'fl_grid_display_both';
+        }
+    }
+
+    /**
+     * 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
+        // so that WebSocket responses can be passed back in to them
+        // The handling of the WebSocket call is delegated out to the Topology
+        // Service just to compartmentalize things a bit
+        this.ts.init(this.instance, this.background, this.force);
+
+        this.ps.addListener((data) => this.prefsUpdateHandler(data));
+        this.prefsState = this.ps.getPrefs(TOPO2_PREFS, this.prefsState);
+        this.mapIdState = this.ps.getPrefs(TOPO_MAPID_PREFS, this.mapIdState);
+
+        this.log.debug('Topology component initialized');
+    }
+
+    ngAfterContentInit(): void {
+        // Scale the window initially - then after resize
+        const zoomMapExtents = ZoomUtils.zoomToWindowSize(
+            this.bannerHeight, this.window.innerWidth, this.window.innerHeight);
+        this.zoomDirective.changeZoomLevel(zoomMapExtents, true);
+        this.log.debug('Topology zoom initialized',
+            this.bannerHeight, this.window.innerWidth, this.window.innerHeight,
+            zoomMapExtents);
+    }
+
+    /**
+     * Callback function that's called whenever new Prefs are received from WebSocket
+     *
+     * Note: At present the backend server does not filter updated by logged in user,
+     * so you might get updates pertaining to a different user
+     */
+    prefsUpdateHandler(data: any): void {
+        // Extract the TOPO2 prefs from it
+        if (data[TOPO2_PREFS]) {
+            this.prefsState = data[TOPO2_PREFS];
+        }
+        this.log.debug('Updated topo2 prefs', this.prefsState, this.mapIdState);
+    }
+
+    /**
+     * When this component is being stopped, disconnect the TopologyService from
+     * the WebSocket
+     */
+    ngOnDestroy() {
+        this.ts.destroy();
+        this.ps.removeListener((data) => this.prefsUpdateHandler(data));
+        this.log.debug('Topology component destroyed');
+    }
+
+    @HostListener('window:resize', ['$event'])
+    onResize(event) {
+        const zoomMapExtents = ZoomUtils.zoomToWindowSize(
+                this.bannerHeight, event.target.innerWidth, event.target.innerHeight);
+        this.zoomDirective.changeZoomLevel(zoomMapExtents, true);
+        this.log.debug('Topology window resize',
+            event.target.innerWidth, event.target.innerHeight, this.bannerHeight, zoomMapExtents);
+    }
+
+    /**
+     * 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_TOGGLE:
+                this.toggleInstancePanel();
+                break;
+            case SUMMARY_TOGGLE:
+                this.toggleSummary();
+                break;
+            case DETAILS_TOGGLE:
+                this.toggleDetails();
+                break;
+            case HOSTS_TOGGLE:
+                this.toggleHosts();
+                break;
+            case OFFLINE_TOGGLE:
+                this.toggleOfflineDevices();
+                break;
+            case PORTS_TOGGLE:
+                this.togglePorts();
+                break;
+            case BKGRND_TOGGLE:
+                this.toggleBackground();
+                break;
+            case BKGRND_SELECT:
+                this.mapSelShown = !this.mapSelShown;
+                break;
+            case CYCLELABELS_BTN:
+                this.cycleDeviceLabels();
+                break;
+            case CYCLEHOSTLABEL_BTN:
+                this.cycleHostLabels();
+                break;
+            case CYCLEGRIDDISPLAY_BTN:
+                this.cycleGridDisplay();
+                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;
+            case QUICKHELP_BTN:
+                this.ks.quickHelpShown = true;
+                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'],
+            B: [(token) => {this.toggleBackground(token); }, 'Toggle background'],
+            D: [(token) => {this.toggleDetails(token); }, 'Toggle details panel'],
+            E: [() => {this.equalizeMasters(); }, 'Equalize mastership roles'],
+            H: [() => {this.toggleHosts(); }, 'Toggle host visibility'],
+            I: [(token) => {this.toggleInstancePanel(token); }, 'Toggle ONOS Instance Panel'],
+            G: [() => {this.mapSelShown = !this.mapSelShown; }, 'Show map selection dialog'],
+            L: [() => {this.cycleDeviceLabels(); }, 'Cycle device labels'],
+            M: [() => {this.toggleOfflineDevices(); }, 'Toggle offline visibility'],
+            O: [() => {this.toggleSummary(); }, 'Toggle the Summary Panel'],
+            P: [(token) => {this.togglePorts(token); }, 'Toggle Port Highlighting'],
+            Q: [() => {this.cycleGridDisplay(); }, 'Cycle grid display'],
+            R: [() => {this.resetZoom(); }, 'Reset pan / zoom'],
+            U: [() => {this.unpinNode(); }, 'Unpin node (mouse over)'],
+            X: [() => {this.resetNodeLocation(); }, 'Reset Node Location'],
+            dot: [() => {this.toggleToolbar(); }, 'Toggle Toolbar'],
+            0: [() => {this.cancelTraffic(); }, 'Cancel traffic monitoring'],
+            'shift-L': [() => {this.cycleHostLabels(); }, 'Cycle host labels'],
+
+            // -- instance color palette debug
+            9: () => {
+                this.sus.cat7().testCard(d3.select('svg#topo2'));
+            },
+
+            esc: [() => {this.handleEscape(); }, 'Cancel commands'],
+
+            // TODO update after adding in Background Service
+            // topology overlay selections
+            // F1: function () { t2tbs.fnKey(0); },
+            // F2: function () { t2tbs.fnKey(1); },
+            // F3: function () { t2tbs.fnKey(2); },
+            // F4: function () { t2tbs.fnKey(3); },
+            // F5: function () { t2tbs.fnKey(4); },
+            //
+            // _keyListener: t2tbs.keyListener.bind(t2tbs),
+
+            _helpFormat: [
+                ['I', 'O', 'D', 'H', 'M', 'P', 'dash', 'B'],
+                ['X', 'Z', 'N', 'L', 'shift-L', 'U', 'R', 'E', 'dot'],
+                [], // this column reserved for overlay actions
+            ],
+        };
+    }
+
+
+    bindCommands(additional?: any) {
+
+        const am = this.actionMap();
+        const add = this.fs.isO(additional);
+
+        this.ks.keyBindings(am);
+
+        this.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'],
+        ]);
+    }
+
+    handleEscape() {
+
+        if (false) {
+            // TODO: Cancel show mastership
+            // TODO: Cancel Active overlay
+            // TODO: Reinstate with components
+        } else {
+            this.nodeSelected(undefined);
+            this.log.debug('Handling escape');
+            // } else if (t2rs.deselectAllNodes()) {
+            //     // else if we have node selections, deselect them all
+            //     // (work already done)
+            // } else if (t2rs.deselectLink()) {
+            //     // else if we have a link selection, deselect it
+            //     // (work already done)
+            // } else if (t2is.isVisible()) {
+            //     // If the instance panel is visible, close it
+            //     t2is.toggle();
+            // } else if (t2sp.isVisible()) {
+            //     // If the summary panel is visible, close it
+            //     t2sp.toggle();
+        }
+    }
+
+    /**
+     * Updates the cache of preferences locally and onwards to the PrefsService
+     * @param what The attribute of the local topo2-prefs cache to update
+     * @param b the value to update it with
+     */
+    updatePrefsState(what: string, b: number) {
+        this.prefsState[what] = b;
+        this.ps.setPrefs(TOPO2_PREFS, this.prefsState);
+    }
+
+    /**
+     * When the button is clicked on the toolbar or the L key is pressed
+     * 1) cycle through options
+     * 2) flash up a message
+     * 3a) Update the local prefs cache
+     * 3b) And passes on to the global prefs service which sends back to the server
+     * 3c) It also has a knock on effect of passing it on to ForceSvgComponent
+     *      because prefsState.dlbls is given as an input to it
+     * 3d) This will in turn pass it down to the DeviceSvgComponent which
+     *       displays the label
+     */
+    protected cycleDeviceLabels() {
+        const old: LabelToggle.Enum = this.prefsState.dlbls;
+        const next = LabelToggle.next(old);
+        this.flashMsg = this.lionFn(TopologyComponent.deviceLabelFlashMessage(next));
+        this.updatePrefsState(PREF_DLBLS, next);
+        this.log.debug('Cycling device labels', old, next);
+    }
+
+    protected cycleHostLabels() {
+        const old: HostLabelToggle.Enum = this.prefsState.hlbls;
+        const next = HostLabelToggle.next(old);
+        this.flashMsg = this.lionFn(TopologyComponent.hostLabelFlashMessage(next));
+        this.updatePrefsState(PREF_HLBLS, next);
+        this.log.debug('Cycling host labels', old, next);
+    }
+
+    protected cycleGridDisplay() {
+        const old: GridDisplayToggle.Enum = this.prefsState.grid;
+        const next = GridDisplayToggle.next(old);
+        this.flashMsg = this.lionFn(TopologyComponent.gridDisplayFlashMessage(next));
+        this.updatePrefsState(PREF_GRID, next);
+        this.log.debug('Cycling grid display', old, next);
+    }
+
+    /**
+     * When the button is clicked on the toolbar or the B key is pressed
+     * 1) Find the inverse of the current state (held as 1 or 0)
+     * 2) Flash up a message on screen
+     * 3b) And passes on to the global prefs service which sends back to the server
+     * 3c) It also has a knock on effect of passing it on to ToolbarComponent
+     *      because prefsState.bg is given as an input to it
+     * @param token not currently used
+     */
+    protected toggleBackground(token?: KeysToken) {
+        const bg: boolean = !Boolean(this.prefsState.bg);
+        this.flashMsg = this.lionFn(bg ? 'show' : 'hide') +
+            ' ' + this.lionFn('fl_background_map');
+        this.updatePrefsState(PREF_BG, bg ? 1 : 0);
+        this.log.debug('Toggling background', token, bg ? 'shown' : 'hidden');
+    }
+
+    protected toggleDetails(token?: KeysToken) {
+        const on: boolean = !Boolean(this.prefsState.detail);
+        this.flashMsg = this.lionFn(on ? 'show' : 'hide') +
+            ' ' + this.lionFn('fl_panel_details');
+        this.updatePrefsState(PREF_DETAIL, on ? 1 : 0);
+        this.log.debug('Toggling details', token);
+    }
+
+    protected toggleInstancePanel(token?: KeysToken) {
+        const on: boolean = !Boolean(this.prefsState.insts);
+        this.flashMsg = this.lionFn(on ? 'show' : 'hide') +
+            ' ' + this.lionFn('fl_panel_instances');
+        this.updatePrefsState(PREF_INSTS, on ? 1 : 0);
+        this.log.debug('Toggling instances', token, on);
+    }
+
+    protected toggleSummary() {
+        const on: boolean = !Boolean(this.prefsState.summary);
+        this.flashMsg = this.lionFn(on ? 'show' : 'hide') +
+            ' ' + this.lionFn('fl_panel_summary');
+        this.updatePrefsState(PREF_SUMMARY, on ? 1 : 0);
+    }
+
+    protected togglePorts(token?: KeysToken) {
+        const current: boolean = !Boolean(this.prefsState.porthl);
+        this.flashMsg = this.lionFn(current ? 'enable' : 'disable') +
+            ' ' + this.lionFn('fl_port_highlighting');
+        this.updatePrefsState(PREF_PORTHL, current ? 1 : 0);
+        this.log.debug(current ? 'Enable' : 'Disable', 'port highlighting');
+    }
+
+    protected toggleToolbar() {
+        const on: boolean = !Boolean(this.prefsState.toolbar);
+        this.updatePrefsState(PREF_TOOLBAR, on ? 1 : 0);
+        this.log.debug('toggling toolbar', on ? 'shown' : 'hidden');
+    }
+
+    protected toggleHosts() {
+        const current: boolean = !Boolean(this.prefsState.hosts);
+        this.flashMsg = this.lionFn('hosts') + ' ' +
+                        this.lionFn(this.force.showHosts ? 'visible' : 'hidden');
+        this.updatePrefsState(PREF_HOSTS, current ? 1 : 0);
+        this.log.debug('toggling hosts: ', this.prefsState.hosts ? 'Show' : 'Hide');
+    }
+
+    protected toggleOfflineDevices() {
+        const on: boolean = !Boolean(this.prefsState.offdev);
+        this.flashMsg = this.lionFn(on ? 'show' : 'hide') +
+            ' ' + this.lionFn('fl_offline_devices');
+        this.updatePrefsState(PREF_OFFDEV, on ? 1 : 0);
+        this.log.debug('toggling offline devices', this.prefsState.offdev);
+    }
+
+    protected resetZoom() {
+        const zoomMapExtents = ZoomUtils.zoomToWindowSize(
+            this.bannerHeight, this.window.innerWidth, this.window.innerHeight);
+        this.zoomDirective.changeZoomLevel(zoomMapExtents, false);
+        this.flashMsg = this.lionFn('fl_pan_zoom_reset');
+    }
+
+    protected equalizeMasters() {
+        this.wss.sendEvent('equalizeMasters', null);
+        this.flashMsg = this.lionFn('fl_eq_masters');
+        this.log.debug('equalizing masters');
+    }
+
+    protected resetNodeLocation() {
+        // TODO: Implement reset locations
+        this.force.resetNodeLocations();
+        this.flashMsg = this.lionFn('fl_reset_node_locations');
+        this.log.debug('resetting node location');
+    }
+
+    protected unpinNode() {
+        // TODO: Implement this
+        this.log.debug('unpinning node');
+    }
+
+    /**
+     * Check to see if this is needed anymore
+     * @param what - a key stroke
+     */
+    protected notValid(what) {
+        this.log.warn('topo.js getActionEntry(): Not a valid ' + what);
+    }
+
+    /**
+     * Check to see if this is needed anymore
+     * @param key - a key stroke
+     */
+    getActionEntry(key) {
+        let entry;
+
+        if (!key) {
+            this.notValid('key');
+            return null;
+        }
+
+        entry = this.actionMap()[key];
+
+        if (!entry) {
+            this.notValid('actionMap (' + key + ') entry');
+            return null;
+        }
+        return this.fs.isA(entry) || [entry, ''];
+    }
+
+    /**
+     * 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)});
+    }
+
+    /**
+     * 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);
+    }
+
+    /**
+     * Cancel traffic monitoring
+     */
+    cancelTraffic() {
+        this.flashMsg = this.lionFn('fl_monitoring_canceled');
+        this.trs.destroy();
+    }
+
+    changeMap(map: MapObject) {
+        this.mapSelShown = false; // Hide the MapSelector component
+        this.mapIdState = map;
+        this.ps.setPrefs(TOPO_MAPID_PREFS, this.mapIdState);
+        this.log.debug('Map has been changed to ', map);
+    }
+
+    mapExtentsZoom(zoomMapExtents: TopoZoomPrefs) {
+        // this.zoomDirective.updateZoomState(zoomPrefs.tx, zoomPrefs.ty, zoomPrefs.sc);
+        this.zoomDirective.changeZoomLevel(zoomMapExtents);
+        this.log.debug('Map zoom prefs updated', zoomMapExtents);
+    }
+
+    /**
+     * 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 + '%';
+    }
+}