From 63833c364f139f5269b8c841266406250b9b4868 Mon Sep 17 00:00:00 2001
From: Xiao Gui <xgui3783@gmail.com>
Date: Wed, 3 Apr 2019 17:31:58 +0200
Subject: [PATCH] WIP: adapt to mobile

---
 src/ui/btnShadow.style.css                    |  23 +++
 src/ui/menuicons/menuicons.component.ts       |   9 +-
 src/ui/menuicons/menuicons.template.html      |  80 +++++----
 .../mobileOverlay/mobileOverlay.component.ts  | 153 ++++++++++--------
 .../mobileOverlay/mobileOverlay.style.css     |  20 ++-
 .../mobileOverlay/mobileOverlay.template.html |  31 ++--
 .../nehubaContainer.component.ts              |  41 +----
 .../nehubaContainer/nehubaContainer.style.css |  44 +++--
 .../nehubaContainer.template.html             |  19 ++-
 .../signinBanner/signinBanner.components.ts   |   3 +-
 .../signinBanner/signinBanner.template.html   |  18 ++-
 src/util/generator.ts                         |  12 ++
 12 files changed, 281 insertions(+), 172 deletions(-)
 create mode 100644 src/ui/btnShadow.style.css

diff --git a/src/ui/btnShadow.style.css b/src/ui/btnShadow.style.css
new file mode 100644
index 000000000..817edee68
--- /dev/null
+++ b/src/ui/btnShadow.style.css
@@ -0,0 +1,23 @@
+.btnWrapper
+{
+  display:flex;
+  align-items: center;
+  justify-content: center;
+}
+
+
+.btnWrapper > .btn
+{
+  width: 2.5em;
+  height: 2.5em;
+
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.btnWrapper.btnWrapper-lg > .btn
+{
+  width: 3em;
+  height: 3em;
+}
\ No newline at end of file
diff --git a/src/ui/menuicons/menuicons.component.ts b/src/ui/menuicons/menuicons.component.ts
index f6e80ca7c..0a48cfa4a 100644
--- a/src/ui/menuicons/menuicons.component.ts
+++ b/src/ui/menuicons/menuicons.component.ts
@@ -5,12 +5,14 @@ import { WidgetUnit } from "src/atlasViewer/widgetUnit/widgetUnit.component";
 import { LayerBrowser } from "src/ui/layerbrowser/layerbrowser.component";
 import { DataBrowser } from "src/ui/databrowserModule/databrowser/databrowser.component";
 import { PluginBannerUI } from "../pluginBanner/pluginBanner.component";
+import { AtlasViewerConstantsServices } from "src/atlasViewer/atlasViewer.constantService.service";
 
 @Component({
   selector: 'menu-icons',
   templateUrl: './menuicons.template.html',
   styleUrls: [
-    './menuicons.style.css'
+    './menuicons.style.css',
+    '../btnShadow.style.css'
   ]
 })
 
@@ -37,9 +39,14 @@ export class MenuIconsBar{
   pluginBanner: ComponentRef<PluginBannerUI> = null
   pbWidget: ComponentRef<WidgetUnit> = null
 
+  get isMobile(){
+    return this.constantService.mobile
+  }
+
   constructor(
     private widgetServices:WidgetServices,
     private injector:Injector,
+    private constantService:AtlasViewerConstantsServices,
     cfr: ComponentFactoryResolver
   ){
     this.dbcf = cfr.resolveComponentFactory(DataBrowser)
diff --git a/src/ui/menuicons/menuicons.template.html b/src/ui/menuicons/menuicons.template.html
index 1b6ac141f..0bb0ffb64 100644
--- a/src/ui/menuicons/menuicons.template.html
+++ b/src/ui/menuicons/menuicons.template.html
@@ -1,37 +1,59 @@
 <logo-container>
 </logo-container>
-<div *ngIf="false" class="btn btn-sm btn-secondary rounded-circle">
-  <i class="fas fa-brain">
-    
-  </i>
-</div>
+
 <div
-  [tooltip]="dataBrowserTitle"
-  placement="right"
-  (click)="clickSearch($event)"
-  [ngClass]="databrowserIsShowing ? 'btn-primary' : 'btn-secondary'"
-  class="btn btn-sm rounded-circle">
-  <i class="fas fa-search">
-    
-  </i>
+  *ngIf="isMobile"
+  [ngClass]="isMobile ? 'btnWrapper-lg' : ''"
+  class="btnWrapper">
+  <div
+    class="shadow btn btn-sm btn-outline-secondary rounded-circle">
+    <i class="fas fa-bars">
+      
+    </i>
+  </div>
 </div>
+
 <div
-  tooltip="Layer"
-  placement="right"
-  (click)="clickLayer($event)"
-  [ngClass]="layerbrowserIsShowing ? 'btn-primary' : 'btn-secondary'"
-  class="btn btn-sm rounded-circle">
-  <i class="fas fa-layer-group">
-    
-  </i>
+  [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>
 </div>
+
 <div
-  tooltip="Plugins"
-  (click)="clickPlugins($event)"
-  placement="right"
-  [ngClass]="pluginbrowserIsShowing ? 'btn-primary' : 'btn-secondary'"
-  class="btn btn-sm rounded-circle">
-  <i class="fas fa-tools">
-    
-  </i>
+  [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>
+  </div>
 </div>
+
+<div
+  [ngClass]="isMobile ? 'btnWrapper-lg' : ''"
+  class="btnWrapper">
+  <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>
+  </div>
+</div>
\ No newline at end of file
diff --git a/src/ui/nehubaContainer/mobileOverlay/mobileOverlay.component.ts b/src/ui/nehubaContainer/mobileOverlay/mobileOverlay.component.ts
index 1435c56ec..cd0241392 100644
--- a/src/ui/nehubaContainer/mobileOverlay/mobileOverlay.component.ts
+++ b/src/ui/nehubaContainer/mobileOverlay/mobileOverlay.component.ts
@@ -1,6 +1,7 @@
-import { Component, Input, Output,EventEmitter, ElementRef, ViewChild, AfterViewInit, ChangeDetectionStrategy, OnDestroy } from "@angular/core";
+import { Component, Input, Output,EventEmitter, ElementRef, ViewChild, AfterViewInit, ChangeDetectionStrategy, OnDestroy, OnInit, OnChanges } from "@angular/core";
 import { fromEvent, Subject, Observable, merge, concat, of, combineLatest } from "rxjs";
-import { map, switchMap, takeUntil, filter, scan, take } from "rxjs/operators";
+import { map, switchMap, takeUntil, filter, scan, take, tap } from "rxjs/operators";
+import { clamp } from "src/util/generator";
 
 @Component({
   selector : 'mobile-overlay',
@@ -24,15 +25,15 @@ div:not(.active) > span:before
   display: inline-block;
 }
     `
-  ],
-  changeDetection: ChangeDetectionStrategy.OnPush
+  ]
 })
 
-export class MobileOverlay implements AfterViewInit, OnDestroy{
+export class MobileOverlay implements OnInit, OnDestroy{
   @Input() tunableProperties : string [] = []
   @Output() deltaValue : EventEmitter<{delta:number, selectedProp : string}> = new EventEmitter() 
   @ViewChild('initiator', {read: ElementRef}) initiator : ElementRef
   @ViewChild('mobileMenuContainer', {read: ElementRef}) menuContainer : ElementRef
+  @ViewChild('intersector', {read: ElementRef}) intersector: ElementRef
 
   private _onDestroySubject : Subject<boolean> = new Subject()
 
@@ -42,25 +43,38 @@ export class MobileOverlay implements AfterViewInit, OnDestroy{
       ? this._focusedProperties
       : this.tunableProperties[0]
   }
+  get focusedIndex(){
+    return this._focusedProperties
+      ? this.tunableProperties.findIndex(p => p === this._focusedProperties)
+      : 0
+  }
 
   public showScreen$ : Observable<boolean>
   public showProperties$ : Observable<boolean>
+  public showDelta$: Observable<boolean>
+  public showInitiator$: Observable<boolean>
   private _drag$ : Observable<any>
-
+  private intersectionObserver: IntersectionObserver
+  
   ngOnDestroy(){
     this._onDestroySubject.next(true)
     this._onDestroySubject.complete()
   }
 
-  ngAfterViewInit(){
+  ngOnInit(){
+    const config = {
+      root: this.intersector.nativeElement,
+      threshold: [...Array(10)].map((_, k) => k / 10)
+    }
 
-    this.showScreen$ = merge(
-      fromEvent(this.initiator.nativeElement, 'touchstart'),
-      fromEvent(this.initiator.nativeElement, 'touchend'),
-    ).pipe(
-      map((ev:TouchEvent) => ev.touches.length === 1)
-    )
+    this.intersectionObserver = new IntersectionObserver((arg) => {
+      if (arg[0].isIntersecting) {
+        this.focusItemIndex = 2- Math.floor(arg[0].intersectionRatio * this.tunableProperties.length)
+      }
+    }, config)
 
+    this.intersectionObserver.observe(this.menuContainer.nativeElement)
+  
     this._drag$ = fromEvent(this.initiator.nativeElement, 'touchmove').pipe(
       takeUntil(fromEvent(this.initiator.nativeElement, 'touchend').pipe(
         filter((ev:TouchEvent) => ev.touches.length === 0)
@@ -73,21 +87,58 @@ export class MobileOverlay implements AfterViewInit, OnDestroy{
       filter(ev => ev.length === 2)
     )
 
-    this.showProperties$ = fromEvent(this.initiator.nativeElement, 'touchstart').pipe(    
-      switchMap(() => concat(
-        this._drag$.pipe(
-          map(double => ({
-            deltaX : double[1].touches[0].screenX - double[0].touches[0].screenX,
-            deltaY : double[1].touches[0].screenY - double[0].touches[0].screenY
-          })),
-          scan((acc, _curr) => acc),
-          map(v => v.deltaY ** 2 > v.deltaX ** 2)
-        ),
-        of(false)
-        )
+    this.showProperties$ = concat(
+      of(false),
+      fromEvent(this.initiator.nativeElement, 'touchstart').pipe( 
+        switchMap(() => concat(
+          this._drag$.pipe(
+            map(double => ({
+              deltaX : double[1].touches[0].screenX - double[0].touches[0].screenX,
+              deltaY : double[1].touches[0].screenY - double[0].touches[0].screenY
+            })),
+            scan((acc, _curr) => acc),
+            map(v => v.deltaY ** 2 > v.deltaX ** 2)
+          ),
+          of(false)
+        ))
       )
     )
 
+
+    this.showDelta$ = concat(
+      of(false),
+      fromEvent(this.initiator.nativeElement, 'touchstart').pipe( 
+        switchMap(() => concat(
+          this._drag$.pipe(
+            map(double => ({
+              deltaX : double[1].touches[0].screenX - double[0].touches[0].screenX,
+              deltaY : double[1].touches[0].screenY - double[0].touches[0].screenY
+            })),
+            scan((acc, _curr) => acc),
+            map(v => v.deltaX ** 2 > v.deltaY ** 2)
+          ),
+          of(false)
+        ))
+      )
+    )
+
+    this.showInitiator$ = combineLatest(
+      this.showProperties$,
+      this.showDelta$
+    ).pipe(
+      map(([flag1, flag2]) => !flag1 && !flag2)
+    )
+
+    this.showScreen$ = combineLatest(
+      merge(
+        fromEvent(this.initiator.nativeElement, 'touchstart'),
+        fromEvent(this.initiator.nativeElement, 'touchend')
+      ),
+      this.showInitiator$
+    ).pipe(
+      map(([ev, showInitiator] : [TouchEvent, boolean]) => showInitiator && ev.touches.length === 1)
+    )
+
     fromEvent(this.initiator.nativeElement, 'touchstart').pipe(
       switchMap(() => this._drag$.pipe(
         map(double => ({
@@ -130,7 +181,16 @@ export class MobileOverlay implements AfterViewInit, OnDestroy{
       filter(v => v[0]),
       map(v => v[1]),
       takeUntil(this._onDestroySubject)
-    ).subscribe(v => this.scrollHeight = v.deltaY)
+    ).subscribe(v => {
+      const deltaY = v.deltaY
+      const cellHeight = this.menuContainer && this.tunableProperties && this.tunableProperties.length > 0 && this.menuContainer.nativeElement.offsetHeight / this.tunableProperties.length
+      const adjHeight = - this.focusedIndex * cellHeight - cellHeight * 0.5
+
+      const min = - cellHeight * 0.5
+      const max = - this.tunableProperties.length * cellHeight + cellHeight * 0.5
+      const finalYTranslate = clamp(adjHeight + deltaY, min, max )
+      this.menuTransform = `translate(0px, ${finalYTranslate}px)`
+    })
 
     this.showProperties$.pipe(
       takeUntil(this._onDestroySubject),
@@ -139,47 +199,12 @@ export class MobileOverlay implements AfterViewInit, OnDestroy{
       if(this.focusItemIndex >= 0){
         this._focusedProperties = this.tunableProperties[this.focusItemIndex]
       }
-      this.scrollHeight = 0
     })
+    
   }
 
-  scrollHeight : number = 0
+  public menuTransform = `translate(0px, 0px)`
 
-  get defaultY(){
-    return this.tunableProperties.findIndex(p => p === this.focusedProperty) * this.menuCellHeight
-  }
-
-  get menuTransform(){
-    return `translate(0px, ${this.menuYTranslate}px)`
-  }
+  public focusItemIndex: number = 0
 
-  get menuYTranslate(){
-    return this.menuContainer
-      ? Math.max(
-          Math.min(
-            this.defaultY + this.scrollHeight, 
-            this.menuContainerHeight / 2 - this.menuCellHeight / 2
-            ),
-          - this.menuContainerHeight / 2 + this.menuCellHeight / 2
-          )
-      : this.defaultY
-  }
-
-  get menuContainerHeight(){
-    return this.menuContainer
-      ? this.menuContainer.nativeElement.offsetHeight
-      : 0
-  }
-
-  get menuCellHeight(){
-    return this.tunableProperties.length > 0
-      ? this.menuContainerHeight / this.tunableProperties.length
-      : 0
-  }
-
-  get focusItemIndex():number{
-    return this.menuContainer
-      ? Math.floor((this.menuContainerHeight / 2 - this.menuYTranslate) / this.menuCellHeight)
-      : -1
-  }
 }
\ No newline at end of file
diff --git a/src/ui/nehubaContainer/mobileOverlay/mobileOverlay.style.css b/src/ui/nehubaContainer/mobileOverlay/mobileOverlay.style.css
index 96faf8aea..f1532380e 100644
--- a/src/ui/nehubaContainer/mobileOverlay/mobileOverlay.style.css
+++ b/src/ui/nehubaContainer/mobileOverlay/mobileOverlay.style.css
@@ -18,24 +18,34 @@
   left: 0;
   position: absolute;
 
-  display: flex;
-  justify-content: center;
-  align-items: center;
-
   color : black;
   background-color: rgba(255, 255, 255, 0.5);
 }
 
+
 :host-context([darktheme="true"]) [screen]
 {
   color : white;
   background-color: rgba(0, 0, 0, 0.5);
 }
 
+[intersector]
+{
+  position: absolute;
+  top: 50%;
+  left: 0;
+  width: 100%;
+  height: 50%;
+
+  display: flex;
+  flex-direction: column;
+  justify-content: flex-start;
+  align-items: center;
+}
+
 [mobileMenuContainer]
 {
   z-index: 1000;
-  transform: translate(0, 155px);
 }
 
 .scrollFocus:after
diff --git a/src/ui/nehubaContainer/mobileOverlay/mobileOverlay.template.html b/src/ui/nehubaContainer/mobileOverlay/mobileOverlay.template.html
index 541b687d8..ed89d1001 100644
--- a/src/ui/nehubaContainer/mobileOverlay/mobileOverlay.template.html
+++ b/src/ui/nehubaContainer/mobileOverlay/mobileOverlay.template.html
@@ -1,18 +1,27 @@
-<div screen *ngIf = "showProperties$ | async">
-  <div [style.transform] = "menuTransform" class = "btn-group-vertical" role = "group" mobileMenuContainer #mobileMenuContainer>
-    <div 
-      *ngFor = "let p of tunableProperties; let i = index"
-      [ngClass] = "{'active' : p === focusedProperty, 'scrollFocus' : i === focusItemIndex}" 
-      class = "btn btn-default theme-controlled">
-      <span>
-        {{ p }}
-      </span>
+<div screen [hidden]="!(showProperties$ | async)">
+  <div intersector #intersector>
+    <div [style.transform] = "menuTransform" class = "btn-group-vertical" role = "group" mobileMenuContainer #mobileMenuContainer>
+      <div 
+        *ngFor = "let p of tunableProperties; let i = index"
+        [ngClass] = "{'active' : p === focusedProperty, 'scrollFocus': i === focusItemIndex}" 
+        class = "btn btn-default theme-controlled property scrollFocus">
+        <!-- scrollFocus class -->
+        <span>
+          {{ p }}
+        </span>
+      </div>
     </div>
   </div>
+  {{ menuTransform }} {{ focusItemIndex }}
 </div>
-<ng-content *ngIf = "showScreen$ | async" select = "[guide]" guide>
+
+<ng-content *ngIf="showDelta$ | async" select="[delta]" guide>
 </ng-content>
-<div #initiator>
+
+<ng-content *ngIf="showScreen$ | async" select="[guide]" guide>
+</ng-content>
+
+<div [hidden]="!(showInitiator$ | async)" #initiator>
   <ng-content select="[initiator]">
   </ng-content>
 </div>
\ No newline at end of file
diff --git a/src/ui/nehubaContainer/nehubaContainer.component.ts b/src/ui/nehubaContainer/nehubaContainer.component.ts
index 0cb0bf48b..58a0fe4c1 100644
--- a/src/ui/nehubaContainer/nehubaContainer.component.ts
+++ b/src/ui/nehubaContainer/nehubaContainer.component.ts
@@ -577,53 +577,18 @@ export class NehubaContainer implements OnInit, OnDestroy{
   public showObliqueRotate$ : Observable<boolean>
 
   ngAfterViewInit(){
-    // if(this.isMobile){
-
-    //   this.showObliqueScreen$ = merge(
-    //     fromEvent(this.mobileObliqueCtrl.nativeElement, 'touchstart'),
-    //     fromEvent(this.mobileObliqueCtrl.nativeElement, 'touchend')
-    //   ).pipe(
-    //     map((ev:TouchEvent) => ev.touches.length === 1)
-    //   )
-
-    //   fromEvent(this.mobileObliqueCtrl.nativeElement, 'touchstart').pipe(
-    //     switchMap(() => fromEvent(this.mobileObliqueCtrl.nativeElement, 'touchmove').pipe(
-    //       takeUntil(fromEvent(this.mobileObliqueCtrl.nativeElement, 'touchend').pipe(
-    //         filter((ev:TouchEvent) => ev.touches.length === 0)
-    //       )),
-    //       map((ev:TouchEvent) => (ev.preventDefault(), ev.stopPropagation(), ev)),
-    //       filter((ev:TouchEvent) => ev.touches.length === 1),
-    //       map((ev:TouchEvent) => [ev.touches[0].screenX, ev.touches[0].screenY] ),
-
-    //       /* TODO reduce boiler plate, export */
-    //       scan((acc,curr) => acc.length < 2
-    //         ? acc.concat([curr])
-    //         : acc.slice(1).concat([curr]), []),
-    //       filter(isdouble => isdouble.length === 2),
-    //       map(double => ({
-    //         deltaX : double[1][0] - double[0][0],
-    //         deltaY : double[1][1] - double[0][1]
-    //       }))
-    //     ))
-    //   )
-    //     .subscribe(({deltaX, deltaY}) => {
-          
-          
-          
-    //     })
-    // }
   }
 
   ngOnDestroy(){
     this.subscriptions.forEach(s=>s.unsubscribe())
   }
 
-  get tunableMobileProperties(){
-    return ['Oblique Rotate X', 'Oblique Rotate Y', 'Oblique Rotate Z']
-  }
+  public tunableMobileProperties = ['Oblique Rotate X', 'Oblique Rotate Y', 'Oblique Rotate Z']
+  public selectedProp = null
 
   handleMobileOverlayEvent(obj:any){
     const {delta, selectedProp} = obj
+    this.selectedProp = selectedProp
 
     const idx = this.tunableMobileProperties.findIndex(p => p === selectedProp)
     idx === 0
diff --git a/src/ui/nehubaContainer/nehubaContainer.style.css b/src/ui/nehubaContainer/nehubaContainer.style.css
index 4234ac6d1..ce3d02047 100644
--- a/src/ui/nehubaContainer/nehubaContainer.style.css
+++ b/src/ui/nehubaContainer/nehubaContainer.style.css
@@ -142,13 +142,15 @@ div.loadingIndicator div.spinnerAnimationCircle
 div[mobileObliqueCtrl]
 {
   font-size: 200%;
-  margin-top:-2.5rem;
-  margin-left:-2.5rem;
-  padding-left: 1rem;
-  padding-top: 1rem;
   position: absolute;
   top: 50%;
   left: 50%;
+  width: 0;
+  height: 0;
+
+  display: flex;
+  align-items: center;
+  justify-content: center;
   pointer-events: all;
 }
 
@@ -164,16 +166,39 @@ div[mobileObliqueScreen]
   pointer-events: all;
 }
 
-div[mobileObliqueGuide]
+div.base
 {
   position : absolute;
-  top: 40%;
-  width: 100%;
+  top: 50%;
+  left: 50%;
+  width: 0;
+  height: 0;
   display:flex;
-  flex-direction: column;
+  flex-direction: column-reverse;
   align-items: center;
 }
 
+div[delta]
+{
+  white-space: nowrap
+}
+
+div[mobileObliqueGuide]
+{
+  background-color: rgba(250,250,250,0.8);
+}
+
+div[mobileObliqueGuide] > *
+{
+  white-space: nowrap;
+}
+
+:host-context([darktheme="true"]) div[mobileObliqueGuide]
+{
+
+  background-color: rgba(50,50,50,0.8);
+}
+
 div#scratch-pad
 {
   position: absolute;
@@ -182,4 +207,5 @@ div#scratch-pad
   width: 100%;
   height: 100%;
   pointer-events: none;
-}
\ No newline at end of file
+}
+
diff --git a/src/ui/nehubaContainer/nehubaContainer.template.html b/src/ui/nehubaContainer/nehubaContainer.template.html
index 82b5510ac..dbe225ff5 100644
--- a/src/ui/nehubaContainer/nehubaContainer.template.html
+++ b/src/ui/nehubaContainer/nehubaContainer.template.html
@@ -155,15 +155,22 @@
 </div>
 
 <mobile-overlay 
-  *ngIf="isMobile && viewerLoaded" 
+  *ngIf="isMobile && viewerLoaded"
   [tunableProperties]="tunableMobileProperties" 
   (deltaValue)="handleMobileOverlayEvent($event)">
-  <div mobileObliqueGuide guide>
-    <div>
-      <i class="fas fa-resize-vertical"></i> oblique mode 
+  <div class="base" delta>
+    <div mobileObliqueGuide class="p-2 mb-4 shadow">
+      {{ selectedProp }}
     </div>
-    <div>
-      <i class="fas fa-resize-horizontal"></i> rotate slice
+  </div>
+  <div class="base" guide>
+    <div mobileObliqueGuide class="p-2 mb-4 shadow">
+      <div>
+        <i class="fas fa-arrows-alt-v"></i> oblique mode 
+      </div>
+      <div>
+        <i class="fas fa-arrows-alt-h"></i> rotate slice
+      </div>
     </div>
   </div>
   <div mobileObliqueCtrl initiator>
diff --git a/src/ui/signinBanner/signinBanner.components.ts b/src/ui/signinBanner/signinBanner.components.ts
index eaff2a05a..117e8a019 100644
--- a/src/ui/signinBanner/signinBanner.components.ts
+++ b/src/ui/signinBanner/signinBanner.components.ts
@@ -11,7 +11,8 @@ import { map, filter, distinctUntilChanged } from "rxjs/operators";
   selector: 'signin-banner',
   templateUrl: './signinBanner.template.html',
   styleUrls: [
-    './signinBanner.style.css'
+    './signinBanner.style.css',
+    '../btnShadow.style.css'
   ],
   changeDetection: ChangeDetectionStrategy.OnPush
 })
diff --git a/src/ui/signinBanner/signinBanner.template.html b/src/ui/signinBanner/signinBanner.template.html
index e07fa7365..a5d7389f6 100644
--- a/src/ui/signinBanner/signinBanner.template.html
+++ b/src/ui/signinBanner/signinBanner.template.html
@@ -1,4 +1,5 @@
 <dropdown-component
+  *ngIf="!isMobile"
   (itemSelected)="changeTemplate($event)"
   [activeDisplay]="displayActiveTemplate"
   [selectedItem]="selectedTemplate$ | async"
@@ -23,11 +24,12 @@
 </div>
 
 <!-- signin -->
-<div
-  *ngIf="!isMobile"
-  (click)="showSignin()"
-  class="btn btn-outline-secondary btn-sm rounded-circle">
-  <i
-    [ngClass]="user ? 'fa-user' : 'fa-sign-in-alt'"
-    class="fas"></i>
-</div>
+<div class="btnWrapper">
+  <div
+    (click)="showSignin()"
+    class="btn btn-outline-secondary btn-sm rounded-circle">
+    <i
+      [ngClass]="user ? 'fa-user' : 'fa-sign-in-alt'"
+      class="fas"></i>
+  </div>
+</div>
\ No newline at end of file
diff --git a/src/util/generator.ts b/src/util/generator.ts
index 90992f8fd..5db5d5015 100644
--- a/src/util/generator.ts
+++ b/src/util/generator.ts
@@ -12,4 +12,16 @@ export function* timedValues(ms:number = 500,mode:string = 'linear'){
     yield getValue( (Date.now() - startTime) / ms )
   }
   return 1
+}
+
+export function clamp(val: number, min: number, max: number) {
+  const _min = min < max ? min : max
+  const _max = min < max ? max : min
+  return Math.min(
+    Math.max(
+      val,
+      _min
+    ),
+    _max
+  )
 }
\ No newline at end of file
-- 
GitLab