From 1dd613baf20c78ed5646ff359bd07cfbeba3206b Mon Sep 17 00:00:00 2001
From: Xiao Gui <xgui3783@gmail.com>
Date: Thu, 8 Aug 2019 17:50:40 +0200
Subject: [PATCH] feat: prettify menu icons

---
 .../atlasViewer.constantService.service.ts    |  22 ++-
 .../atlasViewer.pluginService.service.ts      |  60 ++++++--
 .../widgetUnit/widgetService.service.ts       |  22 ++-
 .../widgetUnit/widgetUnit.component.ts        |  27 +++-
 src/ui/menuicons/menuicons.component.ts       |  90 ++++++++---
 src/ui/menuicons/menuicons.style.css          |  24 ++-
 src/ui/menuicons/menuicons.template.html      | 141 +++++++++---------
 .../sharedModules/angularMaterial.module.ts   |   7 +-
 src/ui/ui.module.ts                           |   4 +
 src/util/pipes/menuIconKgSearchBtnCls.pipe.ts |  14 ++
 src/util/pipes/menuIconPluginBtnCls.pipe.ts   |  15 ++
 11 files changed, 309 insertions(+), 117 deletions(-)
 create mode 100644 src/util/pipes/menuIconKgSearchBtnCls.pipe.ts
 create mode 100644 src/util/pipes/menuIconPluginBtnCls.pipe.ts

diff --git a/src/atlasViewer/atlasViewer.constantService.service.ts b/src/atlasViewer/atlasViewer.constantService.service.ts
index a452c83b0..4bf66c8f1 100644
--- a/src/atlasViewer/atlasViewer.constantService.service.ts
+++ b/src/atlasViewer/atlasViewer.constantService.service.ts
@@ -1,8 +1,9 @@
 import { Injectable } from "@angular/core";
-import { Store } from "@ngrx/store";
-import { ViewerStateInterface, Property } from "../services/stateStore.service";
-import { Subject } from "rxjs";
+import { Store, select } from "@ngrx/store";
+import { ViewerStateInterface } from "../services/stateStore.service";
+import { Subject, Observable } from "rxjs";
 import { ACTION_TYPES, ViewerConfiguration } from 'src/services/state/viewerConfig.store'
+import { map, shareReplay } from "rxjs/operators";
 
 export const CM_THRESHOLD = `0.05`
 export const CM_MATLAB_JET = `float r;if( x < 0.7 ){r = 4.0 * x - 1.5;} else {r = -4.0 * x + 4.5;}float g;if (x < 0.5) {g = 4.0 * x - 0.5;} else {g = -4.0 * x + 3.5;}float b;if (x < 0.3) {b = 4.0 * x + 0.5;} else {b = -4.0 * x + 2.5;}float a = 1.0;`
@@ -14,6 +15,7 @@ export const CM_MATLAB_JET = `float r;if( x < 0.7 ){r = 4.0 * x - 1.5;} else {r
 export class AtlasViewerConstantsServices{
 
   public darktheme: boolean = false
+  public darktheme$: Observable<boolean>
   public mobile: boolean
   public loadExportNehubaPromise : Promise<boolean>
 
@@ -247,6 +249,13 @@ Interactive atlas viewer requires **webgl2.0**, and the \`EXT_color_buffer_float
     /* https://stackoverflow.com/a/25394023/6059235 */
     this.mobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini|Mobile|mobile|CriOS/i.test(ua)
 
+    this.darktheme$ = this.store.pipe(
+      select('viewerState'),
+      select('templateSelected'),
+      map(({useTheme}) => useTheme === 'dark'),
+      shareReplay(1)
+    )
+
     /**
      * set gpu limit if user is on mobile
      */
@@ -259,6 +268,13 @@ Interactive atlas viewer requires **webgl2.0**, and the \`EXT_color_buffer_float
       })  
     }
   }
+
+  catchError(e: Error | string){
+    /**
+     * DO NOT REMOVE
+     * general catch all & reflect in UI
+     */
+  }
 }
 
 const parseURLToElement = (url:string):HTMLElement=>{
diff --git a/src/atlasViewer/atlasViewer.pluginService.service.ts b/src/atlasViewer/atlasViewer.pluginService.service.ts
index 7adc86452..9d9ac4124 100644
--- a/src/atlasViewer/atlasViewer.pluginService.service.ts
+++ b/src/atlasViewer/atlasViewer.pluginService.service.ts
@@ -6,8 +6,8 @@ import { PluginUnit } from "./pluginUnit/pluginUnit.component";
 import { WidgetServices } from "./widgetUnit/widgetService.service";
 
 import '../res/css/plugin_styles.css'
-import { interval } from "rxjs";
-import { take, takeUntil } from "rxjs/operators";
+import { interval, BehaviorSubject, Observable, merge, of } from "rxjs";
+import { take, takeUntil, map, shareReplay } from "rxjs/operators";
 import { Store } from "@ngrx/store";
 import { WidgetUnit } from "./widgetUnit/widgetUnit.component";
 import { AtlasViewerConstantsServices } from "./atlasViewer.constantService.service";
@@ -79,6 +79,24 @@ export class PluginServices{
       .then(arr=>
         this.fetchedPluginManifests = arr)
       .catch(console.error)
+
+    this.minimisedPlugins$ = merge(
+      of(new Set()),
+      this.widgetService.minimisedWindow$
+    ).pipe(
+      map(set => {
+        const returnSet = new Set()
+        for (let [pluginName, wu] of this.mapPluginNameToWidgetUnit) {
+          if (set.has(wu)) {
+            returnSet.add(pluginName)
+          }
+        }
+        return returnSet
+      }),
+      shareReplay(1)
+    )
+
+    this.launchedPlugins$ = new BehaviorSubject(new Set())
   }
 
   launchNewWidget = (manifest) => this.launchPlugin(manifest)
@@ -108,21 +126,39 @@ export class PluginServices{
       ])
   }
 
-  public launchedPlugins: Set<string> = new Set()
+  private launchedPlugins: Set<string> = new Set()
+  public launchedPlugins$: BehaviorSubject<Set<string>>
+  pluginHasLaunched(pluginName:string) {
+    return this.launchedPlugins.has(pluginName)
+  }
+  addPluginToLaunchedSet(pluginName:string){
+    this.launchedPlugins.add(pluginName)
+    this.launchedPlugins$.next(this.launchedPlugins)
+  }
+  removePluginToLaunchedSet(pluginName:string){
+    this.launchedPlugins.delete(pluginName)
+    this.launchedPlugins$.next(this.launchedPlugins)
+  }
+
   private mapPluginNameToWidgetUnit: Map<string, WidgetUnit> = new Map()
 
-  pluginMinimised(pluginManifest:PluginManifest){
-    return this.widgetService.minimisedWindow.has( this.mapPluginNameToWidgetUnit.get(pluginManifest.name) )
+  pluginIsMinimised(pluginName:string) {
+    return this.widgetService.isMinimised( this.mapPluginNameToWidgetUnit.get(pluginName) )
   }
 
+  public minimisedPlugins$: Observable<Set<string>>
+
   public orphanPlugins: Set<PluginManifest> = new Set()
   launchPlugin(plugin:PluginManifest){
-    if(this.apiService.interactiveViewer.pluginControl[plugin.name])
-    {
-      console.warn('plugin already launched. blinking for 10s.')
-      this.apiService.interactiveViewer.pluginControl[plugin.name].blink(10)
+    if(this.apiService.interactiveViewer.pluginControl[plugin.name]){
+      // if already launched, toggle minimise/restore
+
       const wu = this.mapPluginNameToWidgetUnit.get(plugin.name)
-      this.widgetService.minimisedWindow.delete(wu)
+      if (this.widgetService.isMinimised(wu)) {
+        this.widgetService.unminimise(wu)
+      } else {
+        this.widgetService.minimise(wu)
+      }
       return Promise.reject('plugin already launched')
     }
     return this.readyPlugin(plugin)
@@ -163,7 +199,7 @@ export class PluginServices{
 
         const shutdownCB = [
           () => {
-            this.launchedPlugins.delete(plugin.name)
+            this.removePluginToLaunchedSet(plugin.name)
           }
         ]
 
@@ -191,7 +227,7 @@ export class PluginServices{
           title : plugin.displayName || plugin.name
         })
 
-        this.launchedPlugins.add(plugin.name)
+        this.addPluginToLaunchedSet(plugin.name)
         this.mapPluginNameToWidgetUnit.set(plugin.name, widgetCompRef.instance)
 
         const unsubscribeOnPluginDestroy = []
diff --git a/src/atlasViewer/widgetUnit/widgetService.service.ts b/src/atlasViewer/widgetUnit/widgetService.service.ts
index 521d69ac8..3d45db7d4 100644
--- a/src/atlasViewer/widgetUnit/widgetService.service.ts
+++ b/src/atlasViewer/widgetUnit/widgetService.service.ts
@@ -2,7 +2,7 @@ import { ComponentRef, ComponentFactory, Injectable, ViewContainerRef, Component
 
 import { WidgetUnit } from "./widgetUnit.component";
 import { AtlasViewerConstantsServices } from "../atlasViewer.constantService.service";
-import { Subscription } from "rxjs";
+import { Subscription, BehaviorSubject } from "rxjs";
 
 
 @Injectable({
@@ -20,7 +20,8 @@ export class WidgetServices{
 
   private clickedListener : Subscription[] = []
 
-  public minimisedWindow: Set<WidgetUnit> = new Set()
+  public minimisedWindow$: BehaviorSubject<Set<WidgetUnit>>
+  private minimisedWindow: Set<WidgetUnit> = new Set() 
 
   constructor(
     private cfr:ComponentFactoryResolver,
@@ -28,6 +29,7 @@ export class WidgetServices{
     private injector : Injector
     ){
     this.widgetUnitFactory = this.cfr.resolveComponentFactory(WidgetUnit)
+    this.minimisedWindow$ = new BehaviorSubject(this.minimisedWindow)
   }
 
   clearAllWidgets(){
@@ -40,6 +42,16 @@ export class WidgetServices{
 
   minimise(wu:WidgetUnit){
     this.minimisedWindow.add(wu)
+    this.minimisedWindow$.next(new Set(this.minimisedWindow))
+  }
+  
+  isMinimised(wu:WidgetUnit){
+    return this.minimisedWindow.has(wu)
+  }
+
+  unminimise(wu:WidgetUnit){
+    this.minimisedWindow.delete(wu)
+    this.minimisedWindow$.next(new Set(this.minimisedWindow))
   }
 
   addNewWidget(guestComponentRef:ComponentRef<any>,options?:Partial<WidgetOptionsInterface>):ComponentRef<WidgetUnit>{
@@ -93,9 +105,12 @@ export class WidgetServices{
 
       this.clickedListener.push(
         _component.instance.clickedEmitter.subscribe((widgetUnit:WidgetUnit)=>{
+          /**
+           * TODO this operation 
+           */
           if(widgetUnit.state !== 'floating')
             return
-          const widget = [...this.widgetComponentRefs].find(widget=>widget.instance===widgetUnit)
+          const widget = [...this.widgetComponentRefs].find(widget=>widget.instance === widgetUnit)
           if(!widget)
             return
           const idx = this.floatingContainer.indexOf(widget.hostView)
@@ -103,7 +118,6 @@ export class WidgetServices{
             return
           this.floatingContainer.detach(idx)
           this.floatingContainer.insert(widget.hostView)
-          
         })
       )
 
diff --git a/src/atlasViewer/widgetUnit/widgetUnit.component.ts b/src/atlasViewer/widgetUnit/widgetUnit.component.ts
index fc32210cd..b1667492c 100644
--- a/src/atlasViewer/widgetUnit/widgetUnit.component.ts
+++ b/src/atlasViewer/widgetUnit/widgetUnit.component.ts
@@ -1,6 +1,8 @@
-import { Component, ViewChild, ViewContainerRef,ComponentRef, HostBinding, HostListener, Output, EventEmitter, Input, ElementRef, OnInit } from "@angular/core";
+import { Component, ViewChild, ViewContainerRef,ComponentRef, HostBinding, HostListener, Output, EventEmitter, Input, ElementRef, OnInit, OnDestroy } from "@angular/core";
 import { WidgetServices } from "./widgetService.service";
 import { AtlasViewerConstantsServices } from "../atlasViewer.constantService.service";
+import { Subscription, Observable } from "rxjs";
+import { map } from "rxjs/operators";
 
 
 @Component({
@@ -10,7 +12,7 @@ import { AtlasViewerConstantsServices } from "../atlasViewer.constantService.ser
   ]
 })
 
-export class WidgetUnit implements OnInit{
+export class WidgetUnit implements OnInit, OnDestroy{
   @ViewChild('container',{read:ViewContainerRef}) container : ViewContainerRef
   @ViewChild('emptyspan',{read:ElementRef}) emtpy : ElementRef
 
@@ -24,9 +26,10 @@ export class WidgetUnit implements OnInit{
   height : string = this.state === 'docked' ? null : '0px'
 
   @HostBinding('style.display')
-  get isMinimised(){
-    return this.widgetServices.minimisedWindow.has(this) ? 'none' : null
-  }
+  isMinimised: string
+
+  isMinimised$: Observable<boolean>
+
   /**
    * TODO
    * upgrade to angular>=7, and use cdk to handle draggable components
@@ -59,6 +62,7 @@ export class WidgetUnit implements OnInit{
   public guestComponentRef : ComponentRef<any>
   public widgetServices:WidgetServices
   public cf : ComponentRef<WidgetUnit>
+  private subscriptions: Subscription[] = []
 
   public id: string 
   constructor(
@@ -69,6 +73,19 @@ export class WidgetUnit implements OnInit{
 
   ngOnInit(){
     this.canBeDocked = typeof this.widgetServices.dockedContainer !== 'undefined'
+
+    this.isMinimised$ = this.widgetServices.minimisedWindow$.pipe(
+      map(set => set.has(this))
+    )
+    this.subscriptions.push(
+      this.isMinimised$.subscribe(flag => this.isMinimised = flag ? 'none' : null)
+    )
+  }
+
+  ngOnDestroy(){
+    while(this.subscriptions.length > 0){
+      this.subscriptions.pop().unsubscribe()
+    }
   }
 
   /**
diff --git a/src/ui/menuicons/menuicons.component.ts b/src/ui/menuicons/menuicons.component.ts
index c488a4e52..133a9b68f 100644
--- a/src/ui/menuicons/menuicons.component.ts
+++ b/src/ui/menuicons/menuicons.component.ts
@@ -1,4 +1,4 @@
-import { Component, ComponentRef, Injector, ComponentFactory, ComponentFactoryResolver, AfterViewInit } from "@angular/core";
+import { Component, ComponentRef, Injector, ComponentFactory, ComponentFactoryResolver } from "@angular/core";
 
 import { WidgetServices } from "src/atlasViewer/widgetUnit/widgetService.service";
 import { WidgetUnit } from "src/atlasViewer/widgetUnit/widgetUnit.component";
@@ -7,9 +7,10 @@ import { DataBrowser } from "src/ui/databrowserModule/databrowser/databrowser.co
 import { PluginBannerUI } from "../pluginBanner/pluginBanner.component";
 import { AtlasViewerConstantsServices } from "src/atlasViewer/atlasViewer.constantService.service";
 import { DatabrowserService } from "../databrowserModule/databrowser.service";
-import { PluginServices } from "src/atlasViewer/atlasViewer.pluginService.service";
+import { PluginServices, PluginManifest } from "src/atlasViewer/atlasViewer.pluginService.service";
 import { Store, select } from "@ngrx/store";
-import { Observable } from "rxjs";
+import { Observable, BehaviorSubject, combineLatest } from "rxjs";
+import { map, shareReplay } from "rxjs/operators";
 
 @Component({
   selector: 'menu-icons',
@@ -43,8 +44,23 @@ export class MenuIconsBar{
   pluginBanner: ComponentRef<PluginBannerUI> = null
   pbWidget: ComponentRef<WidgetUnit> = null
 
-  get isMobile(){
-    return this.constantService.mobile
+  isMobile: boolean = false
+  mobileRespBtnClass: string
+
+  public darktheme$: Observable<boolean>
+
+  public themedBtnClass$: Observable<string>
+
+  public skeletonBtnClass$: Observable<string>
+  
+  private layerBrowserExists$: BehaviorSubject<boolean> = new BehaviorSubject(false)
+  public layerBrowserBtnClass$: Observable<string> 
+
+  public toolBtnClass$: Observable<string>
+  public getKgSearchBtnCls$: Observable<[Set<WidgetUnit>, string]>
+  
+  get darktheme(){
+    return this.constantService.darktheme
   }
 
   public selectedTemplate$: Observable<any>
@@ -59,6 +75,9 @@ export class MenuIconsBar{
     store: Store<any>
   ){
 
+    this.isMobile = this.constantService.mobile
+    this.mobileRespBtnClass = this.constantService.mobile ? 'btn-lg' : 'btn-sm'
+
     this.dbService.createDatabrowser = this.clickSearch.bind(this)
 
     this.dbcf = cfr.resolveComponentFactory(DataBrowser)
@@ -69,6 +88,40 @@ export class MenuIconsBar{
       select('viewerState'),
       select('templateSelected')
     )
+
+    this.themedBtnClass$ = this.constantService.darktheme$.pipe(
+      map(flag => flag ? 'btn-dark' : 'btn-light' ),
+      shareReplay(1)
+    )
+
+    this.skeletonBtnClass$ = this.constantService.darktheme$.pipe(
+      map(flag => `${this.mobileRespBtnClass} ${flag ? 'text-light' : 'text-dark'}`),
+      shareReplay(1)
+    )
+
+    this.layerBrowserBtnClass$ = combineLatest(
+      this.layerBrowserExists$,
+      this.themedBtnClass$
+    ).pipe(
+      map(([flag,themedBtnClass]) => `${this.mobileRespBtnClass} ${flag ? 'btn-primary' : themedBtnClass}`)
+    )
+
+    this.launchedPlugins$ = this.pluginServices.launchedPlugins$.pipe(
+      map(set => Array.from(set))
+    )
+
+    this.getPluginBtnClass$ = combineLatest(
+      this.pluginServices.launchedPlugins$,
+      this.pluginServices.minimisedPlugins$,
+      this.themedBtnClass$
+    )
+
+    this.darktheme$ = this.constantService.darktheme$
+
+    this.getKgSearchBtnCls$ = combineLatest(
+      this.widgetServices.minimisedWindow$,
+      this.themedBtnClass$
+    )
   }
 
   /**
@@ -98,7 +151,7 @@ export class MenuIconsBar{
   }
 
   public catchError(e) {
-    
+    this.constantService.catchError(e)
   }
 
   public clickLayer(event: MouseEvent){
@@ -117,7 +170,10 @@ export class MenuIconsBar{
       titleHTML: '<i class="fas fa-layer-group"></i> Layer Browser'
     })
 
+    this.layerBrowserExists$.next(true)
+
     this.lbWidget.onDestroy(() => {
+      this.layerBrowserExists$.next(false)
       this.layerBrowser = null
       this.lbWidget = null
     })
@@ -154,19 +210,19 @@ export class MenuIconsBar{
     this.pbWidget.instance.position = [left, top]
   }
 
-  get databrowserIsShowing() {
-    return this.dataBrowser !== null
-  }
-
-  get layerbrowserIsShowing() {
-    return this.layerBrowser !== null
+  public clickPluginIcon(manifest: PluginManifest){
+    this.pluginServices.launchPlugin(manifest)
+      .catch(this.constantService.catchError)
   }
 
-  get pluginbrowserIsShowing() {
-    return this.pluginBanner !== null
+  public searchIconClickHandler(wu: WidgetUnit){
+    if (this.widgetServices.isMinimised(wu)) {
+      this.widgetServices.unminimise(wu)
+    } else {
+      this.widgetServices.minimise(wu)
+    }
   }
 
-  get dataBrowserTitle() {
-    return `Browse`
-  }
+  public getPluginBtnClass$: Observable<[Set<string>, Set<string>, string]>
+  public launchedPlugins$: Observable<string[]>
 }
\ No newline at end of file
diff --git a/src/ui/menuicons/menuicons.style.css b/src/ui/menuicons/menuicons.style.css
index 8a1663369..e36c074da 100644
--- a/src/ui/menuicons/menuicons.style.css
+++ b/src/ui/menuicons/menuicons.style.css
@@ -20,4 +20,26 @@
 :host >>> .tooltip.right .tooltip-arrow
 {
   border-right-color: rgba(128, 128, 128, 0.5);
-}
\ No newline at end of file
+}
+
+.sleight-of-hand-back > *:not(:first-child)
+{
+  margin-left: 0.1em;
+}
+
+.sleight-of-hand:hover *
+{
+  transition: opacity 300ms ease-in-out;
+}
+
+.sleight-of-hand:not(:hover) .sleight-of-hand-back
+{
+  opacity: 0;
+  pointer-events: none;
+}
+
+.sleight-of-hand:hover .sleight-of-hand-front
+{
+  opacity: 0;
+  pointer-events: none;
+}
diff --git a/src/ui/menuicons/menuicons.template.html b/src/ui/menuicons/menuicons.template.html
index 214281632..eec24e233 100644
--- a/src/ui/menuicons/menuicons.template.html
+++ b/src/ui/menuicons/menuicons.template.html
@@ -3,93 +3,90 @@
 
 <!-- hide icons when templates has yet been selected -->
 <ng-template [ngIf]="selectedTemplate$ | async">
+
+  <!-- layer browser -->
   <div
-    *ngIf="false"
-    [ngClass]="isMobile ? 'btnWrapper-lg' : ''"
-    class="btnWrapper">
-    <div
-      [tooltip]="dataBrowserTitle"
-      placement="right"
-      (click)="clickSearch($event)"
-      [ngClass]="databrowserIsShowing ? 'btn-primary' : 'btn-secondary'"
-      class="shadow btn btn-sm rounded-circle">
-      <i class="fas fa-search">
-        
-      </i>
-    </div>
+    matTooltip="Layer"
+    matTooltipPosition="right"
+    (click)="clickLayer($event)"
+    [class]="layerBrowserBtnClass$ | async"
+    class="btn">
+    <i class="fas fa-layer-group">
+    </i>
   </div>
 
-  <div
-    [ngClass]="isMobile ? 'btnWrapper-lg' : ''"
-    class="btnWrapper">
-    <div
-      tooltip="Layer"
-      placement="right"
-      (click)="clickLayer($event)"
-      [ngClass]="layerbrowserIsShowing ? 'btn-primary' : 'btn-secondary'"
-      class="shadow btn btn-sm rounded-circle">
-      <i class="fas fa-layer-group">
-        
-      </i>
+  <!-- tools -->
+  <div class="position-relative sleight-of-hand">
+
+    <!-- shown after mouse over -->
+    <div class="position-absolute d-flex flex-row sleight-of-hand-back">
+
+      <!-- add new tool btn -->
+      <div
+        matTooltip="Add new plugin"
+        [matTooltipShowDelay]="100"
+        matTooltipPosition="below"
+        [class]="(skeletonBtnClass$ | async)">
+        <i class="fas fa-plus"></i>
+      </div>
+
+      <!-- render all fetched tools -->
+      <div
+        *ngFor="let manifest of pluginServices.fetchedPluginManifests"
+        [matTooltip]="manifest.displayName || manifest.name"
+        matTooltipPosition="below"
+        (click)="clickPluginIcon(manifest)"
+        [class]="(getPluginBtnClass$ | async | menuIconPluginBtnClsPipe : manifest.name) + ' btn w-1em bs-content-box ' + mobileRespBtnClass">
+        {{ (manifest.displayName || manifest.name).slice(0, 1) }}
+      </div>
+
+      <small
+        *ngIf="pluginServices.fetchedPluginManifests.length === 0"
+        [class]="((darktheme$ | async) ? 'bg-dark text-light' : 'bg-light text-dark') + ' muted pl-2 pr-2 p-1 text-nowrap'">
+        No plugins loaded.
+      </small>
     </div>
-  </div>
 
-  <div
-    *ngIf="false"
-    [ngClass]="isMobile ? 'btnWrapper-lg' : ''"
-    class="btnWrapper">
+    <!-- shown icon prior to mouse over -->
     <div
-      tooltip="Plugins"
-      (click)="clickPlugins($event)"
-      placement="right"
-      [ngClass]="pluginbrowserIsShowing ? 'btn-primary' : 'btn-secondary'"
-      class="shadow btn btn-sm rounded-circle">
-      <i class="fas fa-tools">
-        
-      </i>
+      [matBadge]="(launchedPlugins$ | async)?.length > 0 ? (launchedPlugins$ | async)?.length : null"
+      [class]="(skeletonBtnClass$ | async) + ' position-relative btn sleight-of-hand-front'">
+      <i class="fas fa-tools"></i>
     </div>
   </div>
 
-  <div
-    *ngFor="let manifest of pluginServices.fetchedPluginManifests"
-    [tooltip]="manifest.displayName || manifest.name"
-    placement="right"
-    [ngClass]="isMobile ? 'btnWrapper-lg' : ''"
-    class="btnWrapper">
+  <!-- search kg -->
+  <div class="position-relative sleight-of-hand">
 
-    <div
-      (click)="pluginServices.launchPlugin(manifest).catch(catchError)"
-      [ngClass]="!pluginServices.launchedPlugins.has(manifest.name) ? 'btn-outline-secondary' : pluginServices.pluginMinimised(manifest) ? 'btn-outline-info' : 'btn-info'"
-      class="shadow btn btn-sm rounded-circle">
-      {{ (manifest.displayName || manifest.name).slice(0, 1) }}
-    </div>
-  </div>
+    <!-- shown after mouse over -->
+    <div class="position-absolute d-flex flex-row sleight-of-hand-back align-items-center">
 
-  <div
-    *ngFor="let manifest of pluginServices.orphanPlugins"
-    [tooltip]="manifest.displayName || manifest.name"
-    placement="right"
-    [ngClass]="isMobile ? 'btnWrapper-lg' : ''"
-    class="btnWrapper">
+      <div
+        [ngClass]="(skeletonBtnClass$ | async) + ' position-relative btn text-muted'">
+        <i class="fas fa-search"></i>
+      </div>
 
-    <div
-      (click)="pluginServices.launchPlugin(manifest).catch(catchError)"
-      [ngClass]="pluginServices.pluginMinimised(manifest) ? 'btn-outline-info' : 'btn-info'"
-      class="shadow btn btn-sm rounded-circle">
-      {{ (manifest.displayName || manifest.name).slice(0, 1) }}
+      <!-- render all searched kg -->
+      <div
+        *ngFor="let wu of dbService.instantiatedWidgetUnits"
+        [class]="(getKgSearchBtnCls$ | async | menuIconKgSearchBtnClsPipe : wu) + ' btn w-1em bs-content-box ' + mobileRespBtnClass"
+        [matTooltip]="wu.title"
+        (click)="searchIconClickHandler(wu)"
+        matTooltipPosition="below">
+        <i class="fas fa-search"></i>
+      </div>
+
+      <small
+        *ngIf="dbService.instantiatedWidgetUnits.length === 0"
+        [class]="((darktheme$ | async) ? 'bg-dark text-light' : 'bg-light text-dark') + ' muted pl-2 pr-2 p-1 text-nowrap'">
+        Right click any area to search
+      </small>
     </div>
-  </div>
 
-  <div
-    *ngFor="let wu of dbService.instantiatedWidgetUnits"
-    [ngClass]="isMobile ? 'btnWrapper-lg' : ''"
-    placement="right"
-    [tooltip]="wu.title"
-    class="btnWrapper">
+    <!-- shown icon prior to mouse over -->
     <div
-      (click)="widgetServices.minimisedWindow.delete(wu)"
-      [ngClass]="widgetServices.minimisedWindow.has(wu) ? 'btn-outline-info' : 'btn-info'"
-      class="shadow btn btn-sm rounded-circle">
+      [class]="(skeletonBtnClass$ | async) + ' position-relative btn sleight-of-hand-front'"
+      [matBadge]="dbService.instantiatedWidgetUnits.length > 0 ? dbService.instantiatedWidgetUnits.length : null">
       <i class="fas fa-search"></i>
     </div>
   </div>
diff --git a/src/ui/sharedModules/angularMaterial.module.ts b/src/ui/sharedModules/angularMaterial.module.ts
index b63e2dfaa..1efcdd9f3 100644
--- a/src/ui/sharedModules/angularMaterial.module.ts
+++ b/src/ui/sharedModules/angularMaterial.module.ts
@@ -4,12 +4,13 @@ import {
   MatSidenavModule,
   MatCardModule,
   MatTabsModule,
-  MatTooltipModule
+  MatTooltipModule,
+  MatBadgeModule
 } from '@angular/material';
 import { NgModule } from '@angular/core';
 
 @NgModule({
-  imports: [MatButtonModule, MatCheckboxModule, MatSidenavModule, MatCardModule, MatTabsModule, MatTooltipModule],
-  exports: [MatButtonModule, MatCheckboxModule, MatSidenavModule, MatCardModule, MatTabsModule, MatTooltipModule],
+  imports: [MatBadgeModule, MatButtonModule, MatCheckboxModule, MatSidenavModule, MatCardModule, MatTabsModule, MatTooltipModule],
+  exports: [MatBadgeModule, MatButtonModule, MatCheckboxModule, MatSidenavModule, MatCardModule, MatTabsModule, MatTooltipModule],
 })
 export class AngularMaterialModule { }
\ No newline at end of file
diff --git a/src/ui/ui.module.ts b/src/ui/ui.module.ts
index a7c08e93c..d7087401f 100644
--- a/src/ui/ui.module.ts
+++ b/src/ui/ui.module.ts
@@ -48,6 +48,8 @@ import { KGToS } from "./kgtos/kgtos.component";
 import { AngularMaterialModule } from 'src/ui/sharedModules/angularMaterial.module'
 import { TemplateParcellationsDecorationPipe } from "src/util/pipes/templateParcellationDecoration.pipe";
 import { AppendtooltipTextPipe } from "src/util/pipes/appendTooltipText.pipe";
+import { MenuIconPluginBtnClsPipe } from "src/util/pipes/menuIconPluginBtnCls.pipe";
+import { MenuIconKgSearchBtnClsPipe } from "src/util/pipes/menuIconKgSearchBtnCls.pipe";
 
 
 @NgModule({
@@ -99,6 +101,8 @@ import { AppendtooltipTextPipe } from "src/util/pipes/appendTooltipText.pipe";
     FilterNameBySearch,
     TemplateParcellationsDecorationPipe,
     AppendtooltipTextPipe,
+    MenuIconPluginBtnClsPipe,
+    MenuIconKgSearchBtnClsPipe,
 
     /* directive */
     DownloadDirective,
diff --git a/src/util/pipes/menuIconKgSearchBtnCls.pipe.ts b/src/util/pipes/menuIconKgSearchBtnCls.pipe.ts
new file mode 100644
index 000000000..1b6b1d7b5
--- /dev/null
+++ b/src/util/pipes/menuIconKgSearchBtnCls.pipe.ts
@@ -0,0 +1,14 @@
+import { Pipe, PipeTransform } from "@angular/core";
+import { WidgetUnit } from "src/atlasViewer/widgetUnit/widgetUnit.component";
+
+@Pipe({
+  name: 'menuIconKgSearchBtnClsPipe'
+})
+
+export class MenuIconKgSearchBtnClsPipe implements PipeTransform{
+  public transform([minimisedWidgetUnit, themedBtnCls]: [Set<WidgetUnit>, string], wu: WidgetUnit, ){
+    return minimisedWidgetUnit.has(wu)
+      ? themedBtnCls + ' border-primary'
+      : 'btn-primary'
+  }
+}
\ No newline at end of file
diff --git a/src/util/pipes/menuIconPluginBtnCls.pipe.ts b/src/util/pipes/menuIconPluginBtnCls.pipe.ts
new file mode 100644
index 000000000..94df7495c
--- /dev/null
+++ b/src/util/pipes/menuIconPluginBtnCls.pipe.ts
@@ -0,0 +1,15 @@
+import { PipeTransform, Pipe } from "@angular/core";
+
+@Pipe({
+  name: 'menuIconPluginBtnClsPipe'
+})
+
+export class MenuIconPluginBtnClsPipe implements PipeTransform{
+  public transform([launchedSet, minimisedSet, themedBtnCls], pluginName){
+    return `${launchedSet.has(pluginName) 
+      ? minimisedSet.has(pluginName)
+        ? themedBtnCls + ' border-primary'
+        : 'btn-primary' 
+      : themedBtnCls}`
+  }
+}
\ No newline at end of file
-- 
GitLab