Added mechanism for apps to easily add their own custom link/node/host highlighting wihout having to create a new UI extensions

Change-Id: Iefa21d76190c60db79a4b07a8b22e301d29fe58e
diff --git a/core/api/src/main/java/org/onosproject/ui/UiExtensionService.java b/core/api/src/main/java/org/onosproject/ui/UiExtensionService.java
index 9e03a51..f1ea508 100644
--- a/core/api/src/main/java/org/onosproject/ui/UiExtensionService.java
+++ b/core/api/src/main/java/org/onosproject/ui/UiExtensionService.java
@@ -56,6 +56,22 @@
     }
 
     /**
+     * Registers the specified topo hilighter factory.
+     *
+     * @param factory UI topo higlighter factory to register
+     */
+    default void register(UiTopoHighlighterFactory factory) {
+    }
+
+    /**
+     * Unregisters the specified user interface extension.
+     *
+     * @param factory UI topo higlighter factory to unregister
+     */
+    default void unregister(UiTopoHighlighterFactory factory) {
+    }
+
+    /**
      * Returns the list of registered user interface extensions.
      *
      * @return list of extensions
@@ -80,6 +96,15 @@
     }
 
     /**
+     * Returns the list of registered topo highlighter factories.
+     *
+     * @return list of highlighter factories
+     */
+    default List<UiTopoHighlighterFactory> getTopoHighlighterFactories() {
+        return new ArrayList<UiTopoHighlighterFactory>();
+    }
+
+    /**
      * Returns the navigation pane localization bundle.
      *
      * @return the navigation localization bundle
diff --git a/core/api/src/main/java/org/onosproject/ui/UiTopoHighlighter.java b/core/api/src/main/java/org/onosproject/ui/UiTopoHighlighter.java
new file mode 100644
index 0000000..9c12603
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/ui/UiTopoHighlighter.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2021-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.
+ */
+
+package org.onosproject.ui;
+
+import org.onosproject.ui.topo.Highlights;
+
+/**
+ * Abstraction of an entity capable of generating a set of topology highlights
+ * for devices, hosts and links.
+ */
+public interface UiTopoHighlighter {
+
+    /**
+     * Returns the self-assigned name of the hilighter.
+     *
+     * @return highlighter name
+     */
+    String name();
+
+    /**
+     * Generate a set of highlights.
+     *
+     * @return topology highlights
+     */
+    Highlights createHighlights();
+
+}
diff --git a/core/api/src/main/java/org/onosproject/ui/UiTopoHighlighterFactory.java b/core/api/src/main/java/org/onosproject/ui/UiTopoHighlighterFactory.java
new file mode 100644
index 0000000..8a2d976
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/ui/UiTopoHighlighterFactory.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2021-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.
+ */
+package org.onosproject.ui;
+
+/**
+ * Abstraction of an entity capable of producing a a new topology highlighter.
+ */
+public interface UiTopoHighlighterFactory {
+
+    /**
+     * Produces a new topology highlighter.
+     *
+     * @return newly created topology highlighter
+     */
+    UiTopoHighlighter newTopoHighlighter();
+
+}
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/SampleHighlighterFactory.java b/web/gui/src/main/java/org/onosproject/ui/impl/SampleHighlighterFactory.java
new file mode 100644
index 0000000..509d817
--- /dev/null
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/SampleHighlighterFactory.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2021-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.
+ */
+package org.onosproject.ui.impl;
+
+import org.onosproject.net.EdgeLink;
+import org.onosproject.net.Host;
+import org.onosproject.net.Link;
+import org.onosproject.net.host.HostService;
+import org.onosproject.net.link.LinkService;
+import org.onosproject.ui.UiExtensionService;
+import org.onosproject.ui.UiTopoHighlighter;
+import org.onosproject.ui.UiTopoHighlighterFactory;
+import org.onosproject.ui.topo.BaseLink;
+import org.onosproject.ui.topo.BaseLinkMap;
+import org.onosproject.ui.topo.Highlights;
+import org.onosproject.ui.topo.HostHighlight;
+import org.onosproject.ui.topo.LinkHighlight;
+import org.onosproject.ui.topo.Mod;
+import org.onosproject.ui.topo.NodeBadge;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+
+import static org.onosproject.net.DefaultEdgeLink.createEdgeLinks;
+import static org.onosproject.ui.topo.NodeBadge.text;
+
+@Component(immediate = false)
+public class SampleHighlighterFactory {
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected UiExtensionService uiExtensionService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected LinkService linkService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected HostService hostService;
+
+    private UiTopoHighlighterFactory foo = () ->
+            new TestHighlighter("foo", new Mod("style=\"stroke: #0ff; stroke-width: 10px;\""));
+    private UiTopoHighlighterFactory bar = () ->
+            new TestHighlighter("bar", new Mod("style=\"stroke: #f0f; stroke-width: 4px; stroke-dasharray: 5 2;\""));
+
+    @Activate
+    protected void activate() {
+        uiExtensionService.register(foo);
+        uiExtensionService.register(bar);
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        uiExtensionService.unregister(foo);
+        uiExtensionService.unregister(bar);
+    }
+
+    private final class TestHighlighter implements UiTopoHighlighter {
+
+        private final String name;
+        private final Mod mod;
+
+        private TestHighlighter(String name, Mod mod) {
+            this.name = name;
+            this.mod = mod;
+        }
+
+        @Override
+        public String name() {
+            return name;
+        }
+
+        @Override
+        public Highlights createHighlights() {
+            Highlights highlights = new Highlights();
+            BaseLinkMap linkMap = new BaseLinkMap();
+
+            // Create a map of base bi-links from the set of active links first.
+            for (Link link : linkService.getActiveLinks()) {
+                linkMap.add(link);
+            }
+
+            for (Host host : hostService.getHosts()) {
+                for (EdgeLink link : createEdgeLinks(host, false)) {
+                    linkMap.add(link);
+                }
+
+                // Also add a host badge for kicks.
+                HostHighlight hostHighlight = new HostHighlight(host.id().toString());
+                hostHighlight.setBadge(text(NodeBadge.Status.WARN, name));
+                highlights.add(hostHighlight);
+            }
+
+            // Now scan through the links and annotate them with desired highlights
+            for (BaseLink link : linkMap.biLinks()) {
+                highlights.add(new LinkHighlight(link.linkId(), LinkHighlight.Flavor.PRIMARY_HIGHLIGHT)
+                                       .addMod(mod).setLabel(name + "-" + link.one().src().port()));
+            }
+
+            return highlights;
+        }
+    }
+
+}
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandler.java b/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandler.java
index 455b0f3..cae4760 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandler.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandler.java
@@ -113,6 +113,7 @@
     private static final String REQ_SEL_INTENT_TRAFFIC = "requestSelectedIntentTraffic";
     private static final String SEL_INTENT = "selectIntent";
     private static final String REQ_ALL_TRAFFIC = "requestAllTraffic";
+    private static final String REQ_CUSTOM_TRAFFIC = "requestCustomTraffic";
     private static final String REQ_DEV_LINK_FLOWS = "requestDeviceLinkFlows";
     private static final String CANCEL_TRAFFIC = "cancelTraffic";
     private static final String REQ_SUMMARY = "requestSummary";
@@ -246,6 +247,7 @@
                 new RemoveIntents(),
 
                 new ReqAllTraffic(),
+                new ReqCustomTraffic(),
                 new ReqDevLinkFlows(),
                 new ReqRelatedIntents(),
                 new ReqNextIntent(),
@@ -623,6 +625,17 @@
         }
     }
 
+    private final class ReqCustomTraffic extends RequestHandler {
+        private ReqCustomTraffic() {
+            super(REQ_CUSTOM_TRAFFIC);
+        }
+
+        @Override
+        public void process(ObjectNode payload) {
+            traffic.monitor((int) number(payload, "index"));
+        }
+    }
+
     private NodeSelection makeNodeSelection(ObjectNode payload) {
         return new NodeSelection(payload, services.device(), services.host(),
                                  services.link());
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/TrafficMonitor.java b/web/gui/src/main/java/org/onosproject/ui/impl/TrafficMonitor.java
index 9d9b3c3..b585ffc 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/TrafficMonitor.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/TrafficMonitor.java
@@ -151,6 +151,14 @@
     }
 
     @Override
+    protected void sendCustomTraffic() {
+        log.debug("sendCustomTraffic");
+        if (topoHighlighter != null) {
+            msgHandler.sendHighlights(topoHighlighter.createHighlights());
+        }
+    }
+
+    @Override
     protected void sendClearHighlights() {
         log.debug("sendClearHighlights");
         msgHandler.sendHighlights(new Highlights());
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/TrafficMonitorBase.java b/web/gui/src/main/java/org/onosproject/ui/impl/TrafficMonitorBase.java
index 2f14ac4..ae3c909 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/TrafficMonitorBase.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/TrafficMonitorBase.java
@@ -38,6 +38,9 @@
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.Link;
 import org.onosproject.net.statistic.Load;
+import org.onosproject.ui.UiExtensionService;
+import org.onosproject.ui.UiTopoHighlighter;
+import org.onosproject.ui.UiTopoHighlighterFactory;
 import org.onosproject.ui.impl.topo.TopoologyTrafficMessageHandlerAbstract;
 import org.onosproject.ui.impl.topo.util.IntentSelection;
 import org.onosproject.ui.impl.topo.util.ServicesBundle;
@@ -69,8 +72,7 @@
 import static org.onosproject.net.statistic.PortStatisticsService.MetricType.BYTES;
 import static org.onosproject.net.statistic.PortStatisticsService.MetricType.PACKETS;
 import static org.onosproject.net.DefaultEdgeLink.createEdgeLinks;
-import static org.onosproject.ui.impl.TrafficMonitorBase.Mode.IDLE;
-import static org.onosproject.ui.impl.TrafficMonitorBase.Mode.SELECTED_INTENT;
+import static org.onosproject.ui.impl.TrafficMonitorBase.Mode.*;
 
 /**
  * Base superclass for traffic monitor (both 'classic' and 'topo2' versions).
@@ -85,6 +87,7 @@
     protected IntentSelection selectedIntents = null;
     protected final TopoologyTrafficMessageHandlerAbstract msgHandler;
     protected NodeSelection selectedNodes = null;
+    protected UiTopoHighlighter topoHighlighter = null;
 
     protected void sendSelectedIntents() {
         log.debug("sendSelectedIntents: {}", selectedIntents);
@@ -285,7 +288,8 @@
         ALL_PORT_TRAFFIC_PKT_PS,
         DEV_LINK_FLOWS,
         RELATED_INTENTS,
-        SELECTED_INTENT
+        SELECTED_INTENT,
+        CUSTOM_TRAFFIC_MONITOR
     }
 
     /**
@@ -375,6 +379,22 @@
         }
     }
 
+
+    public synchronized void monitor(int index) {
+        mode = CUSTOM_TRAFFIC_MONITOR;
+        List<UiTopoHighlighterFactory> factories = services.get(UiExtensionService.class)
+                .getTopoHighlighterFactories();
+        if (factories.isEmpty()) {
+            return;
+        }
+
+        UiTopoHighlighterFactory factory = factories.get(index % factories.size());
+        topoHighlighter = factory.newTopoHighlighter();
+        clearSelection();
+        scheduleTask();
+        sendCustomTraffic();
+    }
+
     /**
      * Monitor for traffic data to be sent back to the web client, under
      * the given mode, using the given selection of devices and hosts.
@@ -474,6 +494,12 @@
     protected abstract void sendSelectedIntentTraffic();
 
     /**
+     * Subclass should compile and send appropriate highlights data showing
+     * custom traffic on links.
+     */
+    protected abstract void sendCustomTraffic();
+
+    /**
      * Subclass should send a "clear highlights" event.
      */
     protected abstract void sendClearHighlights();
@@ -712,6 +738,9 @@
                     case SELECTED_INTENT:
                         sendSelectedIntentTraffic();
                         break;
+                    case CUSTOM_TRAFFIC_MONITOR:
+                        sendCustomTraffic();
+                        break;
 
                     default:
                         // RELATED_INTENTS and IDLE modes should never invoke
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/UiExtensionManager.java b/web/gui/src/main/java/org/onosproject/ui/impl/UiExtensionManager.java
index 7d5c324..73de4c1 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/UiExtensionManager.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/UiExtensionManager.java
@@ -49,6 +49,7 @@
 import org.onosproject.ui.UiSessionToken;
 import org.onosproject.ui.UiTokenService;
 import org.onosproject.ui.UiTopo2OverlayFactory;
+import org.onosproject.ui.UiTopoHighlighterFactory;
 import org.onosproject.ui.UiTopoMap;
 import org.onosproject.ui.UiTopoMapFactory;
 import org.onosproject.ui.UiTopoOverlayFactory;
@@ -140,6 +141,7 @@
     private final List<UiExtension> extensions = Lists.newArrayList();
 
     private final List<UiGlyph> glyphs = Lists.newArrayList();
+    private final List<UiTopoHighlighterFactory> highlighterFactories = Lists.newArrayList();
 
     // Map of views to extensions
     private final Map<String, UiExtension> views = Maps.newHashMap();
@@ -366,6 +368,22 @@
     }
 
     @Override
+    public synchronized void register(UiTopoHighlighterFactory factory) {
+        checkPermission(UI_WRITE);
+        if (!highlighterFactories.contains(factory)) {
+            highlighterFactories.add(factory);
+            UiWebSocketServlet.sendToAll(GUI_ADDED, null);
+        }
+    }
+
+    @Override
+    public synchronized void unregister(UiTopoHighlighterFactory factory) {
+        checkPermission(UI_WRITE);
+        highlighterFactories.remove(factory);
+        UiWebSocketServlet.sendToAll(GUI_REMOVED, null);
+    }
+
+    @Override
     public synchronized List<UiExtension> getExtensions() {
         checkPermission(UI_READ);
         return ImmutableList.copyOf(extensions);
@@ -378,6 +396,12 @@
     }
 
     @Override
+    public synchronized List<UiTopoHighlighterFactory> getTopoHighlighterFactories() {
+        checkPermission(UI_READ);
+        return ImmutableList.copyOf(highlighterFactories);
+    }
+
+    @Override
     public synchronized UiExtension getViewExtension(String viewId) {
         checkPermission(UI_READ);
         return views.get(viewId);
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/topo/Traffic2Monitor.java b/web/gui/src/main/java/org/onosproject/ui/impl/topo/Traffic2Monitor.java
index 76b9dc5..6a41cf2 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/topo/Traffic2Monitor.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/topo/Traffic2Monitor.java
@@ -65,6 +65,10 @@
     }
 
     @Override
+    protected void sendCustomTraffic() {
+    }
+
+    @Override
     protected void sendAllPortTrafficBits() {
         log.debug("TOPO-2-TRAFFIC: sendAllPortTrafficBits");
         msgHandler.sendHighlights(trafficSummary(TrafficLink.StatsType.PORT_STATS));
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/topo/util/ServicesBundle.java b/web/gui/src/main/java/org/onosproject/ui/impl/topo/util/ServicesBundle.java
index e58b0f2..1567718 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/topo/util/ServicesBundle.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/topo/util/ServicesBundle.java
@@ -37,6 +37,8 @@
  */
 public class ServicesBundle {
 
+    private ServiceDirectory directory;
+
     private ClusterService clusterService;
 
     private TopologyService topologyService;
@@ -60,6 +62,7 @@
      */
     public ServicesBundle(ServiceDirectory directory) {
         checkNotNull(directory, "Directory cannot be null");
+        this.directory = directory;
 
         clusterService = directory.get(ClusterService.class);
 
@@ -184,4 +187,15 @@
     public PortStatisticsService portStats() {
         return portStatsService;
     }
+
+    /**
+     * Returns the implementation of the specified service class.
+     *
+     * @param serviceClass service class
+     * @param <T>          class of service
+     * @return implementation of the service class
+     */
+    public <T> T get(Class<T> serviceClass) {
+        return directory.get(serviceClass);
+    }
 }
diff --git a/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/Topo.properties b/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/Topo.properties
index c2cdfae..8f9334e 100644
--- a/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/Topo.properties
+++ b/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/Topo.properties
@@ -103,6 +103,7 @@
 tr_btn_show_related_traffic=Show Related Traffic
 tr_btn_cancel_monitoring=Cancel traffic monitoring
 tr_btn_monitor_all=Monitor all traffic
+tr_btn_monitor_custom_all=Custom traffic monitor
 tr_btn_show_dev_link_flows=Show device link flows
 tr_btn_show_all_rel_intents=Show all related intents
 tr_btn_show_prev_rel_intent=Show previous related intent
diff --git a/web/gui/src/main/webapp/app/view/topo/topoForce.js b/web/gui/src/main/webapp/app/view/topo/topoForce.js
index 8819107..912e851 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoForce.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoForce.js
@@ -1023,6 +1023,7 @@
 
     function clearLinkTrafficStyle() {
         link.style('stroke-width', null)
+            .style('stroke', null)
             .classed(allTrafficClasses, false);
     }
 
diff --git a/web/gui/src/main/webapp/app/view/topo/topoOverlay.js b/web/gui/src/main/webapp/app/view/topo/topoOverlay.js
index 4e9133a..7cf47ff 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoOverlay.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoOverlay.js
@@ -398,6 +398,8 @@
             }
         });
 
+        const stylePattern = /style=\"[^\"]*\"/g;
+
         data.links.forEach(function (link) {
             var ldata = api.findLinkById(link.id);
 
@@ -405,6 +407,14 @@
                 if (!link.subdue) {
                     api.unsupLink(ldata.key, less);
                 }
+                var styleFound = link.css.match(stylePattern);
+                if (styleFound) {
+                    link.css = link.css.replace(stylePattern, '');
+                    var style = styleFound[0].replace('style="', '').replace('"$', '')
+                    ldata.el.attr('style', style);
+                } else {
+                    ldata.el.attr('style', '');
+                }
                 ldata.el.classed(link.css, true);
                 ldata.label = link.label;
 
diff --git a/web/gui/src/main/webapp/app/view/topo/topoTraffic.js b/web/gui/src/main/webapp/app/view/topo/topoTraffic.js
index 640a233..c643494 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoTraffic.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoTraffic.js
@@ -47,7 +47,8 @@
     // internal state
     var trafficMode = null,
         hoverMode = null,
-        allTrafficIndex = 0;
+        allTrafficIndex = 0,
+        customTrafficIndex = 0;
 
 
     // === -----------------------------------------------------
@@ -137,6 +138,16 @@
         allTrafficIndex = (allTrafficIndex + 1) % 3;
     }
 
+    function showCustomTraffic() {
+        trafficMode = 'allCustom';
+        hoverMode = null;
+        wss.sendEvent('requestCustomTraffic', {
+            index: customTrafficIndex,
+        });
+        flash.flash('Custom Traffic');
+        customTrafficIndex = customTrafficIndex + 1;
+    }
+
     function showDeviceLinkFlows() {
         trafficMode = hoverMode = 'flows';
         requestDeviceLinkFlows();
@@ -270,6 +281,7 @@
                 showNextIntent: showNextIntent,
                 showSelectedIntentTraffic: showSelectedIntentTraffic,
                 selectIntent: selectIntent,
+                showCustomTraffic: showCustomTraffic,
 
                 // invoked from mouseover/mouseout and selection change
                 requestTrafficForMode: requestTrafficForMode,
diff --git a/web/gui/src/main/webapp/app/view/topo/topoTrafficNew.js b/web/gui/src/main/webapp/app/view/topo/topoTrafficNew.js
index 143aa30..2057f7b 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoTrafficNew.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoTrafficNew.js
@@ -109,9 +109,14 @@
                 tt: function () { return topoLion('tr_btn_monitor_sel_intent'); },
                 gid: 'm_intentTraffic',
             },
+            C: {
+                cb: function () { tts.showCustomTraffic(); },
+                tt: function () { return topoLion('tr_btn_monitor_custom_all'); },
+                gid: 'm_allTraffic',
+            },
 
             _keyOrder: [
-                '0', 'A', 'F', 'V', 'leftArrow', 'rightArrow', 'W',
+                '0', 'A', 'F', 'V', 'leftArrow', 'rightArrow', 'W', 'C'
             ],
         },