From 89bec6ee0e35c37ea6f168cdf107e23eaacfe061 Mon Sep 17 00:00:00 2001
From: Xiao Gui <xgui3783@gmail.com>
Date: Wed, 21 Nov 2018 15:28:47 +0100
Subject: [PATCH] feat: toast for template and atlas citations

---
 .../atlasViewer.constantService.service.ts    |   2 +
 src/components/components.module.ts           |   3 +
 src/components/timer/timer.component.ts       |  52 +++++
 src/components/timer/timer.style.css          |  14 ++
 src/components/timer/timer.template.html      |   2 +
 src/components/toast/toast.component.ts       |  15 +-
 src/components/toast/toast.style.css          |   8 +-
 src/components/toast/toast.template.html      |   4 +-
 src/ui/banner/banner.component.ts             | 218 ++++++++++--------
 src/ui/banner/banner.style.css                |  17 +-
 src/ui/banner/banner.template.html            |  34 +++
 .../nehubaContainer/nehubaContainer.style.css |  13 --
 .../nehubaContainer.template.html             |  16 --
 src/util/directives/showToast.directive.ts    |   6 +-
 src/util/generator.ts                         |   6 +-
 15 files changed, 270 insertions(+), 140 deletions(-)
 create mode 100644 src/components/timer/timer.component.ts
 create mode 100644 src/components/timer/timer.style.css
 create mode 100644 src/components/timer/timer.template.html

diff --git a/src/atlasViewer/atlasViewer.constantService.service.ts b/src/atlasViewer/atlasViewer.constantService.service.ts
index aaf0da3b8..c1dfbfb16 100644
--- a/src/atlasViewer/atlasViewer.constantService.service.ts
+++ b/src/atlasViewer/atlasViewer.constantService.service.ts
@@ -15,6 +15,8 @@ export class AtlasViewerConstantsServices{
   public ngLandmarkLayerName = 'spatial landmark layer'
   public ngUserLandmarkLayerName = 'user landmark layer'
 
+  public citationToastDuration = 7000
+
   /**
    * optimized for nehubaConfig.layout.useNehubaPerspective.fixedZoomPerspectiveSlices
    *  sliceZoom
diff --git a/src/components/components.module.ts b/src/components/components.module.ts
index 8e3c9a588..7f7f4f19c 100644
--- a/src/components/components.module.ts
+++ b/src/components/components.module.ts
@@ -23,6 +23,7 @@ import { HighlightPipe } from './flatTree/highlight.pipe';
 import { FitlerRowsByVisibilityPipe } from './flatTree/filterRowsByVisibility.pipe';
 import { AppendSiblingFlagPipe } from './flatTree/appendSiblingFlag.pipe';
 import { ClusteringPipe } from './flatTree/clustering.pipe';
+import { TimerComponent } from './timer/timer.component';
 
 
 @NgModule({
@@ -41,6 +42,7 @@ import { ClusteringPipe } from './flatTree/clustering.pipe';
     PaginationComponent,
     ToastComponent,
     FlatTreeComponent,
+    TimerComponent,
 
     /* directive */
     HoverableBlockDirective,
@@ -68,6 +70,7 @@ import { ClusteringPipe } from './flatTree/clustering.pipe';
     PaginationComponent,
     ToastComponent,
     FlatTreeComponent,
+    TimerComponent,
 
     SearchResultPaginationPipe,
     TreeSearchPipe,
diff --git a/src/components/timer/timer.component.ts b/src/components/timer/timer.component.ts
new file mode 100644
index 000000000..4c5e6b971
--- /dev/null
+++ b/src/components/timer/timer.component.ts
@@ -0,0 +1,52 @@
+import { Component, OnInit, Input, OnDestroy, EventEmitter, Output, HostBinding } from "@angular/core";
+import { timedValues } from "../../util/generator"
+
+@Component({
+  selector: 'timer-component',
+  templateUrl: './timer.template.html',
+  styleUrls: [
+    './timer.style.css'
+  ]
+})
+
+export class TimerComponent implements OnInit, OnDestroy{
+  @Input() private timeout:number = 500
+  @Input() private pause:boolean = false
+  @Output() timerEnd : EventEmitter<boolean> = new EventEmitter()
+
+  private generator : IterableIterator<any> = null
+  public progress:number = 0
+  private baseProgress:number = 0
+
+  private rafCbId:number
+  private rafCb = () => {
+    if(this.pause){
+      this.generator = null
+      this.baseProgress = this.progress
+    }else{
+      if(this.generator === null){
+        this.generator = timedValues(this.timeout * (1 - this.baseProgress), 'linear')
+      }else{
+        const next = this.generator.next()
+        this.progress = this.baseProgress + (1 - this.baseProgress) * next.value
+        if(next.done){
+          this.timerEnd.emit(true)
+          return
+        }
+      }
+    }
+    this.rafCbId = requestAnimationFrame(this.rafCb)
+  }
+
+  get transform(){ 
+    return `translateX(${this.progress * 100}%)`
+  }
+
+  ngOnInit(){
+    this.rafCbId = requestAnimationFrame(this.rafCb)
+  }
+
+  ngOnDestroy(){
+    if(this.rafCbId) cancelAnimationFrame(this.rafCbId)
+  }
+}
\ No newline at end of file
diff --git a/src/components/timer/timer.style.css b/src/components/timer/timer.style.css
new file mode 100644
index 000000000..ad5611e32
--- /dev/null
+++ b/src/components/timer/timer.style.css
@@ -0,0 +1,14 @@
+:host
+{
+  display: block;
+  width: 100%;
+  height: 100%;
+  overflow: hidden;
+}
+
+div
+{
+  background-color:rgba(128, 128, 128, 0.2);
+  height:100%;
+  width:100%;
+}
\ No newline at end of file
diff --git a/src/components/timer/timer.template.html b/src/components/timer/timer.template.html
new file mode 100644
index 000000000..6e13c9405
--- /dev/null
+++ b/src/components/timer/timer.template.html
@@ -0,0 +1,2 @@
+<div [style.transform] = "transform">
+</div>
\ No newline at end of file
diff --git a/src/components/toast/toast.component.ts b/src/components/toast/toast.component.ts
index a2d4599f7..c1836b689 100644
--- a/src/components/toast/toast.component.ts
+++ b/src/components/toast/toast.component.ts
@@ -1,4 +1,4 @@
-import { Component, Input, ViewContainerRef, ViewChild, Output, EventEmitter, HostBinding, ElementRef, ChangeDetectionStrategy, OnInit } from "@angular/core";
+import { Component, Input, ViewContainerRef, ViewChild, Output, EventEmitter, HostBinding, ElementRef, ChangeDetectionStrategy, OnInit, HostListener, NgZone } from "@angular/core";
 import { toastAnimation } from "./toast.animation";
 
 @Component({
@@ -7,34 +7,27 @@ import { toastAnimation } from "./toast.animation";
   styleUrls : ['./toast.style.css'],
   animations : [
     toastAnimation
-  ],
-  changeDetection: ChangeDetectionStrategy.OnPush
+  ]
 })
 
-export class ToastComponent implements OnInit{
+export class ToastComponent{
   @Input() message : string 
   @Input() timeout : number = 0
   @Input() dismissable : boolean = true
 
   @Output() dismissed : EventEmitter<boolean> = new EventEmitter()
 
-  private timeoutId : any
+  public progress: number = 0
 
   @HostBinding('@exists')
   exists : boolean = true
 
   @ViewChild('messageContainer',{read:ViewContainerRef}) messageContainer : ViewContainerRef
-  
-  ngOnInit(){
-    if(this.timeout > 0) this.timeoutId = setTimeout(() => this.dismissed.emit(false), this.timeout)
-  }
 
   dismiss(event:MouseEvent){
     event.preventDefault()
     event.stopPropagation()
 
-    if(this.timeoutId) clearTimeout(this.timeoutId)
-
     this.dismissed.emit(true)
   }
 }
\ No newline at end of file
diff --git a/src/components/toast/toast.style.css b/src/components/toast/toast.style.css
index d3e835e46..50624d5c5 100644
--- a/src/components/toast/toast.style.css
+++ b/src/components/toast/toast.style.css
@@ -10,7 +10,7 @@ div[container]
 {
   display : inline-block;
   align-items: center;
-  padding : 0.3em 1em;
+  padding : 0.3em 1em 0em 1em;
   pointer-events: all;
 }
 
@@ -35,4 +35,10 @@ div[message],
 div[close]
 {
   display:inline-block;
+}
+timer-component
+{
+  margin: 0 -1em;
+  height:0.5em;
+  width: calc(100% + 2em);
 }
\ No newline at end of file
diff --git a/src/components/toast/toast.template.html b/src/components/toast/toast.template.html
index cf5619bd2..17e173c70 100644
--- a/src/components/toast/toast.template.html
+++ b/src/components/toast/toast.template.html
@@ -1,4 +1,4 @@
-<div container>
+<div (mouseenter) = "hover = true" (mouseleave)="hover = false" container>
   <div message>
     <ng-template #messageContainer>
 
@@ -10,4 +10,6 @@
   <div (click) = "dismiss($event)" *ngIf = "dismissable" close>
     <i class = "glyphicon glyphicon-remove"></i>
   </div>
+  <timer-component (timerEnd)="dismissed.emit(false)" [pause] = "hover" [timeout] = "timeout" timer>
+  </timer-component>
 </div>
\ No newline at end of file
diff --git a/src/ui/banner/banner.component.ts b/src/ui/banner/banner.component.ts
index 654748941..1bec07a84 100644
--- a/src/ui/banner/banner.component.ts
+++ b/src/ui/banner/banner.component.ts
@@ -1,81 +1,108 @@
-import { Component, OnDestroy, ChangeDetectionStrategy, HostListener, ViewChild, ElementRef } from "@angular/core";
+import { Component, OnDestroy, ChangeDetectionStrategy, HostListener, ViewChild, ElementRef, OnInit } from "@angular/core";
 import { Store, select } from "@ngrx/store";
 import { ViewerStateInterface, safeFilter, SELECT_PARCELLATION, extractLabelIdx, SELECT_REGIONS, NEWVIEWER, getLabelIndexMap, isDefined, CHANGE_NAVIGATION } from "../../services/stateStore.service";
 import { Observable, Subscription, merge, Subject } from "rxjs";
-import { map, filter, debounceTime, buffer } from "rxjs/operators";
+import { map, filter, debounceTime, buffer, distinctUntilChanged } from "rxjs/operators";
 import { FilterNameBySearch } from "../../util/pipes/filterNameBySearch.pipe";
 import { regionAnimation } from "./regionPopover.animation";
+import { AtlasViewerConstantsServices } from "../../atlasViewer/atlasViewer.constantService.service"
 
 @Component({
-  selector : 'atlas-banner',
-  templateUrl : './banner.template.html',
-  styleUrls : [
+  selector: 'atlas-banner',
+  templateUrl: './banner.template.html',
+  styleUrls: [
     `./banner.style.css`
   ],
-  animations:[
+  animations: [
     regionAnimation
   ],
-  changeDetection : ChangeDetectionStrategy.OnPush
+  changeDetection: ChangeDetectionStrategy.OnPush
 })
 
-export class AtlasBanner implements OnDestroy{
+export class AtlasBanner implements OnDestroy, OnInit {
 
-  public loadedTemplates$ : Observable<any[]>
-  public newViewer$ : Observable<any>
-  public selectedParcellation$ : Observable<any>
-  public selectedRegions$ : Observable<any[]>
+  public loadedTemplates$: Observable<any[]>
+  public newViewer$: Observable<any>
+  public selectedParcellation$: Observable<any>
+  public selectedRegions$: Observable<any[]>
 
-  private regionsLabelIndexMap : Map<number,any> = new Map()
+  private regionsLabelIndexMap: Map<number, any> = new Map()
 
-  public selectedTemplate : any
-  public selectedParcellation : any
-  public selectedRegions : any[] = []
-  private navigation : {position : [number,number,number]} = {position : [0,0,0]}
+  public selectedTemplate: any
+  public selectedParcellation: any
+  public selectedRegions: any[] = []
+  private navigation: { position: [number, number, number] } = { position: [0, 0, 0] }
 
-  private subscriptions : Subscription[] = []
+  private subscriptions: Subscription[] = []
 
-  searchTerm : string = ''
+  @ViewChild('templateCitationAnchor', { read: ElementRef }) templateCitationAnchor: ElementRef
+  @ViewChild('parcellationCitationAnchor', { read: ElementRef }) parcellationCitationAnchor: ElementRef
 
-  constructor(private store:Store<ViewerStateInterface>){
+  searchTerm: string = ''
+
+  constructor(
+    private store: Store<ViewerStateInterface>,
+    private constantService: AtlasViewerConstantsServices
+  ) {
     this.loadedTemplates$ = this.store.pipe(
       select('viewerState'),
       safeFilter('fetchedTemplates'),
-      map(state=>state.fetchedTemplates))
+      map(state => state.fetchedTemplates))
 
     this.newViewer$ = this.store.pipe(
       select('viewerState'),
-      filter(state=>isDefined(state) && isDefined(state.templateSelected)),
-      filter(state=>
-        !isDefined(this.selectedTemplate) || 
-        state.templateSelected.name !== this.selectedTemplate.name) 
+      filter(state => isDefined(state) && isDefined(state.templateSelected)),
+      distinctUntilChanged((o, n) => o.templateSelected.name === n.templateSelected.name)
     )
 
     this.selectedParcellation$ = this.store.pipe(
       select('viewerState'),
       safeFilter('parcellationSelected'),
-      map(state=>state.parcellationSelected)
+      map(state => state.parcellationSelected)
     )
 
     this.selectedRegions$ = merge(
       this.store.pipe(
         select('viewerState'),
-        filter(state=>isDefined(state)&&isDefined(state.regionsSelected)),
-        map(state=>state.regionsSelected)
+        filter(state => isDefined(state) && isDefined(state.regionsSelected)),
+        map(state => state.regionsSelected)
       )
-    ) 
+    )
+  }
+
+  ngOnInit() {
     this.subscriptions.push(
-      this.newViewer$.subscribe((state)=>{
-        
+      this.newViewer$.subscribe((state) => {
         this.selectedTemplate = state.templateSelected
         const selectedParcellation = state.parcellationSelected ? state.parcellationSelected : this.selectedTemplate.parcellations[0]
         this.handleParcellationChange(selectedParcellation)
       })
     )
+
+    this.subscriptions.push(
+      this.newViewer$.pipe(
+        debounceTime(250)
+      ).subscribe(() => {
+        if (this.templateCitationAnchor)
+          this.templateCitationAnchor.nativeElement.click()
+      })
+    )
+
     this.subscriptions.push(
       this.selectedParcellation$.subscribe((this.handleParcellationChange).bind(this))
     )
+
     this.subscriptions.push(
-      this.selectedRegions$.subscribe((ev)=>{
+      this.selectedParcellation$.pipe(
+        debounceTime(250)
+      ).subscribe(() => {
+        if (this.parcellationCitationAnchor)
+          this.parcellationCitationAnchor.nativeElement.click()
+      })
+    )
+
+    this.subscriptions.push(
+      this.selectedRegions$.subscribe((ev) => {
         this.selectedRegions = ev
       })
     )
@@ -87,51 +114,52 @@ export class AtlasBanner implements OnDestroy{
             debounceTime(200)
           )
         )
-      ).subscribe(arr=>arr.length > 1 ? this.doubleClick(arr[0]) : this.singleClick(arr[0]))  
+      ).subscribe(arr => arr.length > 1 ? this.doubleClick(arr[0]) : this.singleClick(arr[0]))
     )
 
-    
     this.subscriptions.push(
       this.store.pipe(
         select('viewerState'),
         safeFilter('navigation'),
-        map(obj=>obj.navigation)
-      ).subscribe((navigation:any)=>this.navigation = navigation)
+        map(obj => obj.navigation)
+      ).subscribe((navigation: any) => this.navigation = navigation)
     )
   }
 
-
-  ngOnDestroy(){
-    this.subscriptions.forEach(s=>s.unsubscribe())
+  ngOnDestroy() {
+    this.subscriptions.forEach(s => s.unsubscribe())
   }
 
-  handleParcellationChange(parcellation){
+  handleParcellationChange(parcellation) {
     this.selectedParcellation = parcellation
     this.regionsLabelIndexMap = getLabelIndexMap(parcellation.regions)
   }
 
-  selectTemplate(template:any){
-    if(this.selectedTemplate === template){
+  selectTemplate(template: any) {
+    if (this.selectedTemplate === template) {
       return
     }
 
     this.store.dispatch({
-      type : NEWVIEWER,
-      selectTemplate : template,
-      selectParcellation : template.parcellations[0]
+      type: NEWVIEWER,
+      selectTemplate: template,
+      selectParcellation: template.parcellations[0]
     })
   }
 
-  selectParcellation(parcellation:any){
+  selectParcellation(parcellation: any) {
+    if (this.selectedParcellation === parcellation) {
+      return
+    }
     this.store.dispatch({
-      type : SELECT_PARCELLATION,
-      selectParcellation : parcellation
+      type: SELECT_PARCELLATION,
+      selectParcellation: parcellation
     })
   }
 
   /* double click navigate to the interested area */
-  private doubleClick(obj:any){
-    if( !(obj && obj.inputItem && (obj.inputItem.position || obj.inputItem.POIs)) ){
+  private doubleClick(obj: any) {
+    if (!(obj && obj.inputItem && (obj.inputItem.position || obj.inputItem.POIs))) {
       return
     }
 
@@ -142,10 +170,10 @@ export class AtlasBanner implements OnDestroy{
         : null
 
     this.store.dispatch({
-      type : CHANGE_NAVIGATION,
-      navigation : {
-        position : newPos,
-        animation : {
+      type: CHANGE_NAVIGATION,
+      navigation: {
+        position: newPos,
+        animation: {
           /* empty object is enough to be truthy */
         }
       },
@@ -153,99 +181,107 @@ export class AtlasBanner implements OnDestroy{
   }
 
   /* single click selects/deselects region(s) */
-  private singleClick(obj:any){
+  private singleClick(obj: any) {
     const region = obj.inputItem
     const selectedSet = new Set(extractLabelIdx(region))
-    const intersection = new Set([...this.selectedRegions.map(r=>r.labelIndex)].filter(v=>selectedSet.has(v)))
+    const intersection = new Set([...this.selectedRegions.map(r => r.labelIndex)].filter(v => selectedSet.has(v)))
     this.store.dispatch({
-      type : SELECT_REGIONS,
-      selectRegions : intersection.size > 0 ? 
-        this.selectedRegions.filter(v=>!intersection.has(v.labelIndex)) :
-        this.selectedRegions.concat([...selectedSet].map(idx=>this.regionsLabelIndexMap.get(idx)))
+      type: SELECT_REGIONS,
+      selectRegions: intersection.size > 0 ?
+        this.selectedRegions.filter(v => !intersection.has(v.labelIndex)) :
+        this.selectedRegions.concat([...selectedSet].map(idx => this.regionsLabelIndexMap.get(idx)))
     })
   }
 
-  private handleRegionTreeClickSubject : Subject<any> = new Subject()
+  private handleRegionTreeClickSubject: Subject<any> = new Subject()
 
   /* NB need to bind two way data binding like this. Or else, on searchInput blur, the flat tree will be rebuilt,
     resulting in first click to be ignored */
-  changeSearchTerm(event:any){
-    if(event.target.value === this.searchTerm)
+  changeSearchTerm(event: any) {
+    if (event.target.value === this.searchTerm)
       return
     this.searchTerm = event.target.value
   }
 
-  @ViewChild('searchRegionPopover', {read:ElementRef}) inputRegionPopover : ElementRef
+  @ViewChild('searchRegionPopover', { read: ElementRef }) inputRegionPopover: ElementRef
   public showRegionTree: boolean
 
-  @HostListener('document:click',['$event'])
-  closeRegion(event:MouseEvent){
+  @HostListener('document:click', ['$event'])
+  closeRegion(event: MouseEvent) {
 
     /* region popover may not always be rendered */
-    if(!this.inputRegionPopover)
+    if (!this.inputRegionPopover)
       return
-      
+
     /* FF < 62 does not implement event.srcElement so use event.originalTarget to polyfill for FF */
-    
-    const contains = event.srcElement 
+
+    const contains = event.srcElement
       ? this.inputRegionPopover.nativeElement.contains(event.srcElement)
       : this.inputRegionPopover.nativeElement.contains((event as any).originalTarget)
-    if(contains)
+    if (contains)
       this.showRegionTree = true
     else
       this.showRegionTree = false
   }
 
-  handleClickRegion(obj:any){
+  handleClickRegion(obj: any) {
     obj.event.stopPropagation()
     this.handleRegionTreeClickSubject.next(obj)
   }
 
-  displayActiveTemplate(template:any){
+  displayActiveTemplate(template: any) {
     return `<small>Template</small> <small class = "mute-text">${template ? '(' + template.name + ')' : ''}</small> <span class = "caret"></span>`
   }
 
-  displayActiveParcellation(parcellation:any){
+  displayActiveParcellation(parcellation: any) {
     return `<small>Parcellation</small> <small class = "mute-text">${parcellation ? '(' + parcellation.name + ')' : ''}</small> <span class = "caret"></span>`
   }
 
-  private insertHighlight(name:string,searchTerm:string):string{
-    const regex = new RegExp(searchTerm,'gi')
-    return searchTerm === '' ? 
+  private insertHighlight(name: string, searchTerm: string): string {
+    const regex = new RegExp(searchTerm, 'gi')
+    return searchTerm === '' ?
       name :
-      name.replace(regex,(s)=>`<span class = "highlight">${s}</span>`)
+      name.replace(regex, (s) => `<span class = "highlight">${s}</span>`)
   }
 
-  displayTreeNode(item:any){
-    return typeof item.labelIndex !== 'undefined' && this.selectedRegions.findIndex(re=>re.labelIndex === Number(item.labelIndex)) >= 0 ? 
-      `<span class = "regionSelected">${this.insertHighlight(item.name,this.searchTerm)}</span>` :
-      `<span class = "regionNotSelected">${this.insertHighlight(item.name,this.searchTerm)}</span>`
+  displayTreeNode(item: any) {
+    return typeof item.labelIndex !== 'undefined' && this.selectedRegions.findIndex(re => re.labelIndex === Number(item.labelIndex)) >= 0 ?
+      `<span class = "regionSelected">${this.insertHighlight(item.name, this.searchTerm)}</span>` :
+      `<span class = "regionNotSelected">${this.insertHighlight(item.name, this.searchTerm)}</span>`
   }
 
-  getChildren(item:any){
+  getChildren(item: any) {
     return item.children
   }
 
-  filterTreeBySearch(node:any):boolean{
-    return this.filterNameBySearchPipe.transform([node.name],this.searchTerm)
+  filterTreeBySearch(node: any): boolean {
+    return this.filterNameBySearchPipe.transform([node.name], this.searchTerm)
   }
 
-  clearRegions(event:Event){
+  clearRegions(event: Event) {
     event.stopPropagation()
     event.preventDefault()
     this.store.dispatch({
-      type : SELECT_REGIONS,
-      selectRegions : []
+      type: SELECT_REGIONS,
+      selectRegions: []
     })
   }
 
   filterNameBySearchPipe = new FilterNameBySearch()
 
-  get aggregatedRegionTree(){
+  get aggregatedRegionTree() {
     return {
-      name : this.selectedParcellation.name,
-      children : this.selectedParcellation.regions
+      name: this.selectedParcellation.name,
+      children: this.selectedParcellation.regions
     }
   }
+
+  citationExists(obj: any) {
+    return obj && obj.properties && obj.properties.publications && obj.properties.publications.length > 0
+  }
+
+  get toastDuration() {
+    return this.constantService.citationToastDuration
+  }
 }
 
diff --git a/src/ui/banner/banner.style.css b/src/ui/banner/banner.style.css
index 3cb4c6ce4..14fd16850 100644
--- a/src/ui/banner/banner.style.css
+++ b/src/ui/banner/banner.style.css
@@ -10,8 +10,8 @@
 dropdown-component,
 :host > div[searchRegionPopover]
 {
-  margin-left:1em;
-  margin-bottom: 0.4em;
+  margin:auto 0 auto 1em;
+  vertical-align: middle;
 }
 
 div[treeHeader]
@@ -90,3 +90,16 @@ input[type="text"]
   box-shadow: inset 0 4px 6px 0 rgba(0,0,0,0.2);
   color:rgba(255,255,255,0.8);
 }
+
+i
+{
+  font-size: 150%;
+  margin:auto 0;
+}
+
+citations-component
+{
+  width: 500px;
+  max-width: 100%;
+  display:block;
+}
\ No newline at end of file
diff --git a/src/ui/banner/banner.template.html b/src/ui/banner/banner.template.html
index 44201ed94..4a0805af5 100644
--- a/src/ui/banner/banner.template.html
+++ b/src/ui/banner/banner.template.html
@@ -6,6 +6,24 @@
 
 </dropdown-component>
 
+<i 
+  *ngIf = "citationExists(selectedTemplate)" 
+  [toastLength] = "toastDuration"
+  [showToast] = "citation"
+  class="glyphicon glyphicon-info-sign"
+  #templateCitationAnchor>
+
+  <ng-template #citation>
+    <h4>
+      Citations for {{ selectedTemplate ? selectedTemplate.name : '' }} 
+    </h4>
+    <citations-component
+      [properties] = "selectedTemplate.properties">
+    </citations-component>
+  </ng-template>
+
+</i>
+
 <dropdown-component
   *ngIf = "selectedTemplate"
   (itemSelected) = "selectParcellation($event)"
@@ -14,6 +32,22 @@
   [inputArray] = "selectedTemplate.parcellations">
 </dropdown-component>
 
+<i 
+  *ngIf = "citationExists(selectedParcellation)" 
+  [toastLength] = "toastDuration"
+  [showToast] = "citation"
+  class="glyphicon glyphicon-info-sign"
+  #parcellationCitationAnchor>
+  <ng-template #citation>
+    <h4>
+      Citations for {{ selectedParcellation ? selectedParcellation.name : '' }} 
+    </h4>
+    <citations-component
+      [properties] = "selectedParcellation.properties">
+    </citations-component>
+  </ng-template>
+</i>
+
 <div 
   *ngIf = "selectedTemplate"
   #searchRegionPopover
diff --git a/src/ui/nehubaContainer/nehubaContainer.style.css b/src/ui/nehubaContainer/nehubaContainer.style.css
index 73d82c86d..2e3528179 100644
--- a/src/ui/nehubaContainer/nehubaContainer.style.css
+++ b/src/ui/nehubaContainer/nehubaContainer.style.css
@@ -138,12 +138,6 @@ div.loadingIndicator div.spinnerAnimationCircle
   color:rgba(255,255,255,0.8);
 }
 
-[desktopTemplateCitation]
-{
-  padding: 0.6em 0 0 0.6em;
-  font-size:200%;
-}
-
 div[mobileObliqueCtrl]
 {
   font-size: 200%;
@@ -178,10 +172,3 @@ div[mobileObliqueGuide]
   flex-direction: column;
   align-items: center;
 }
-
-template-parcellation-citation-container
-{
-  width: 500px;
-  max-width: 100%;
-  display:block;
-}
\ No newline at end of file
diff --git a/src/ui/nehubaContainer/nehubaContainer.template.html b/src/ui/nehubaContainer/nehubaContainer.template.html
index 57b4f9eb7..2d484c3c7 100644
--- a/src/ui/nehubaContainer/nehubaContainer.template.html
+++ b/src/ui/nehubaContainer/nehubaContainer.template.html
@@ -91,14 +91,6 @@
   <!-- TODO export status card to its own container -->
   <div statusCard>
 
-    <div desktopTemplateCitation *ngIf = "!isMobile">
-      <i 
-        [toastLength] = "7000"
-        [showToast] = 
-        "templateAtlasCitations" 
-        class="glyphicon glyphicon-info-sign"></i>
-    </div>
-
     <hr *ngIf = "showCitation && !isMobile" />
 
     <div linksContainer>
@@ -151,11 +143,3 @@
     <i class="glyphicon glyphicon-globe"></i>
   </div>
 </mobile-overlay>
-
-<ng-template #templateAtlasCitations>
-  <h4>
-    Citations for {{ selectedTemplate ? selectedTemplate.name : '' }} and {{ selectedParcellation ? selectedParcellation.name : '' }}
-  </h4>
-  <template-parcellation-citation-container>
-  </template-parcellation-citation-container>
-</ng-template>
\ No newline at end of file
diff --git a/src/util/directives/showToast.directive.ts b/src/util/directives/showToast.directive.ts
index 0062b5737..20642c146 100644
--- a/src/util/directives/showToast.directive.ts
+++ b/src/util/directives/showToast.directive.ts
@@ -28,10 +28,12 @@ export class ShowToastDirective{
     return this._toastLength
   }
 
+  private dismissHandler : () => void
+
   @HostListener('click', ['$event.target'])
   click(ev:MouseEvent){
-    
-    this.toastService.showToast(this.showToast, {
+    if(this.dismissHandler) this.dismissHandler()
+    this.dismissHandler = this.toastService.showToast(this.showToast, {
       dismissable: true,
       timeout: this.toastLength
     })
diff --git a/src/util/generator.ts b/src/util/generator.ts
index 6409a61fe..90992f8fd 100644
--- a/src/util/generator.ts
+++ b/src/util/generator.ts
@@ -1,4 +1,4 @@
-export function* timedValues(sec:number = 500,mode:string = 'linear'){
+export function* timedValues(ms:number = 500,mode:string = 'linear'){
   const startTime = Date.now()
 
   const getValue = (fraction) =>{
@@ -8,8 +8,8 @@ export function* timedValues(sec:number = 500,mode:string = 'linear'){
         return fraction < 1 ? fraction : 1
     }
   }
-  while((Date.now() - startTime) < sec){
-    yield getValue( (Date.now() - startTime) / sec )
+  while((Date.now() - startTime) < ms){
+    yield getValue( (Date.now() - startTime) / ms )
   }
   return 1
 }
\ No newline at end of file
-- 
GitLab