From 3c1f01b616eb402c6f9d32b8184932873051734a Mon Sep 17 00:00:00 2001
From: Xiao Gui <xgui3783@gmail.com>
Date: Mon, 17 Sep 2018 12:17:11 +0200
Subject: [PATCH] bugfix: fixed how flat tree render dotted guiding line,
 removed dependency on ngx-popover

---
 src/components/components.module.ts           |  6 +--
 .../flatTree/appendSiblingFlag.pipe.ts        | 24 +++++++++
 src/components/flatTree/flatTree.component.ts | 15 ++----
 .../flatTree/flatTree.template.html           |  8 ++-
 src/components/flatTree/flattener.pipe.ts     | 14 +++--
 .../flatTree/hasVisibleChildren.pipe.ts       | 11 ----
 src/ui/banner/banner.component.ts             | 25 ++++++++-
 src/ui/banner/banner.style.css                |  1 +
 src/ui/banner/banner.template.html            | 51 +++++++++----------
 src/ui/banner/regionPopover.animation.ts      |  9 +++-
 src/ui/ui.module.ts                           |  4 +-
 11 files changed, 96 insertions(+), 72 deletions(-)
 create mode 100644 src/components/flatTree/appendSiblingFlag.pipe.ts
 delete mode 100644 src/components/flatTree/hasVisibleChildren.pipe.ts

diff --git a/src/components/components.module.ts b/src/components/components.module.ts
index 0e2ff62e8..edefaf871 100644
--- a/src/components/components.module.ts
+++ b/src/components/components.module.ts
@@ -20,8 +20,8 @@ import { FlatTreeComponent } from './flatTree/flatTree.component';
 import { FlattenTreePipe } from './flatTree/flattener.pipe';
 import { RenderPipe } from './flatTree/render.pipe';
 import { HighlightPipe } from './flatTree/highlight.pipe';
-import { HasVisibleChildrenPipe } from './flatTree/hasVisibleChildren.pipe';
 import { FitlerRowsByVisibilityPipe } from './flatTree/filterRowsByVisibility.pipe';
+import { AppendSiblingFlagPipe } from './flatTree/appendSiblingFlag.pipe';
 
 
 @NgModule({
@@ -52,8 +52,8 @@ import { FitlerRowsByVisibilityPipe } from './flatTree/filterRowsByVisibility.pi
     FlattenTreePipe,
     RenderPipe,
     HighlightPipe,
-    HasVisibleChildrenPipe,
-    FitlerRowsByVisibilityPipe
+    FitlerRowsByVisibilityPipe,
+    AppendSiblingFlagPipe
   ],
   exports : [
     BrowserAnimationsModule,
diff --git a/src/components/flatTree/appendSiblingFlag.pipe.ts b/src/components/flatTree/appendSiblingFlag.pipe.ts
new file mode 100644
index 000000000..bcec2058f
--- /dev/null
+++ b/src/components/flatTree/appendSiblingFlag.pipe.ts
@@ -0,0 +1,24 @@
+import { Pipe, PipeTransform } from "@angular/core";
+
+@Pipe({
+  name : 'appendSiblingFlagPipe'
+})
+
+export class AppendSiblingFlagPipe implements PipeTransform{
+  public transform(objs:any[]):any[]{
+    const idSet = new Set(objs.map(obj => obj.lvlId))
+    return objs.map(obj => 
+      Object.assign({}, obj, {
+        siblingFlags : obj.lvlId
+          .split('_')
+          .reduce((acc,curr) => acc.concat(acc.length === 0 ? curr : acc[acc.length -1].concat(`_${curr}`)), [])
+          .map(prevIds => this.isLast(prevIds,idSet))
+          .slice(1)
+      })
+    )
+  }
+
+  private isLast(id:string, set:Set<string>):boolean{
+    return !set.has(id.split('_').slice(0,-1).concat( (Number(id.split('_').reverse()[0]) + 1).toString() ).join('_'))
+  }
+}
\ No newline at end of file
diff --git a/src/components/flatTree/flatTree.component.ts b/src/components/flatTree/flatTree.component.ts
index cea993bcb..e31656057 100644
--- a/src/components/flatTree/flatTree.component.ts
+++ b/src/components/flatTree/flatTree.component.ts
@@ -18,19 +18,16 @@ export class FlatTreeComponent{
   @Input() childrenExpanded : boolean = true
 
   @Output() treeNodeClick : EventEmitter<any> = new EventEmitter()
-  @Output() treeNodeEnter : EventEmitter<any> = new EventEmitter()
-  @Output() treeNodeLeave : EventEmitter<any> = new EventEmitter()
+
+  /* highly non-performant. rerenders each time on mouseover or mouseout */
+  // @Output() treeNodeEnter : EventEmitter<any> = new EventEmitter()
+  // @Output() treeNodeLeave : EventEmitter<any> = new EventEmitter()
 
   @Input() renderNode : (item:any)=>string = (item)=>item.name
   @Input() findChildren : (item:any)=>any[] = (item)=>item.children ? item.children : [] 
   @Input() searchFilter : (item:any)=>boolean | null = ()=>true
 
-  constructor(){
-    // this.searchedVisibleSet = this.searchFilter()
-  }
-
   getClass(level:number){
-    // return `render-node-level-${level}`
     return [...Array(level+1)].map((v,idx) => `render-node-level-${idx}`).join(' ')
   }
 
@@ -58,8 +55,4 @@ export class FlatTreeComponent{
         ? true
         : false
   }
-
-  filterRows(items:any[], ){
-
-  }
 }
\ No newline at end of file
diff --git a/src/components/flatTree/flatTree.template.html b/src/components/flatTree/flatTree.template.html
index e7ff8460f..33f7b3c95 100644
--- a/src/components/flatTree/flatTree.template.html
+++ b/src/components/flatTree/flatTree.template.html
@@ -1,5 +1,5 @@
 <div 
-  *ngFor = "let flattenedItem of (inputItem | flattenTreePipe : findChildren | filterRowsByVisbilityPipe : findChildren : searchFilter)"
+  *ngFor = "let flattenedItem of (inputItem | flattenTreePipe : findChildren | filterRowsByVisbilityPipe : findChildren : searchFilter | appendSiblingFlagPipe)"
   [ngClass] = "getClass(flattenedItem.flattenedTreeLevel)"
   [attr.flattenedtreelevel] = "flattenedItem.flattenedTreeLevel" 
   [attr.collapsed] = "flattenedItem.collapsed ? flattenedItem.collapsed : false"
@@ -17,7 +17,7 @@
   </span>
   <span 
     *ngIf = "findChildren(flattenedItem).length > 0; else noChildren"
-    (click) = "toggleCollapse(flattenedItem)" >
+    (click) = "$event.stopPropagation(); toggleCollapse(flattenedItem)" >
     <i [ngClass] = "isCollapsed(flattenedItem) ? 'glyphicon-chevron-right' : 'glyphicon-chevron-down'" class="glyphicon"></i>
   </span>
   <ng-template #noChildren>
@@ -26,9 +26,7 @@
     </i>
   </ng-template>
   <span
-    (mouseenter) = "treeNodeEnter.emit(flattenedItem)"
-    (mouseleave) = "treeNodeLeave.emit(flattenedItem)"
-    (click) = "treeNodeClick.emit(flattenedItem)"
+    (click) = "treeNodeClick.emit({event:$event,inputItem:flattenedItem})"
     class = "render-node-text"
     [innerHtml] = "flattenedItem | renderPipe : renderNode ">
   </span>
diff --git a/src/components/flatTree/flattener.pipe.ts b/src/components/flatTree/flattener.pipe.ts
index fea454001..3be8cb5c9 100644
--- a/src/components/flatTree/flattener.pipe.ts
+++ b/src/components/flatTree/flattener.pipe.ts
@@ -6,29 +6,27 @@ import { Pipe, PipeTransform } from "@angular/core";
 
 export class FlattenTreePipe implements PipeTransform{
   public transform(root:any, findChildren: (root:any) => any[]):any&FlattenedTreeInterface[]{
-    return this.recursiveFlatten(root,findChildren,0, '0', [])
+    return this.recursiveFlatten(root,findChildren,0, '0')
   }
 
-  private recursiveFlatten(obj, findChildren, flattenedTreeLevel, lvlId, siblingFlags){
+  private recursiveFlatten(obj, findChildren, flattenedTreeLevel, lvlId){
     return [
         this.attachLvlAndLvlIdAndSiblingFlag(
           obj,
           flattenedTreeLevel, 
-          lvlId,
-          siblingFlags
+          lvlId
         )
       ].concat(
       ...findChildren(obj)
-        .map((c,idx,arr) => this.recursiveFlatten(c,findChildren,flattenedTreeLevel + 1, `${lvlId}_${idx}`, siblingFlags.concat( idx === (arr.length - 1)) ))
+        .map((c,idx) => this.recursiveFlatten(c,findChildren,flattenedTreeLevel + 1, `${lvlId}_${idx}` ))
     )
   }
 
-  private attachLvlAndLvlIdAndSiblingFlag(obj:any, flattenedTreeLevel:number, lvlId:string, siblingFlags : boolean[]){
+  private attachLvlAndLvlIdAndSiblingFlag(obj:any, flattenedTreeLevel:number, lvlId:string){
     return Object.assign({}, obj,{
       flattenedTreeLevel, 
       collapsed : typeof obj.collapsed === 'undefined' ? false : true,
-      lvlId,
-      siblingFlags
+      lvlId
     })
   }
 
diff --git a/src/components/flatTree/hasVisibleChildren.pipe.ts b/src/components/flatTree/hasVisibleChildren.pipe.ts
deleted file mode 100644
index 3da6b3e1c..000000000
--- a/src/components/flatTree/hasVisibleChildren.pipe.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import { Pipe, PipeTransform } from "@angular/core";
-
-@Pipe({
-  name : 'hasVisibleChildrenPipe'
-})
-
-export class HasVisibleChildrenPipe implements PipeTransform{
-  public transform(item:any, getChildren: (item:any)=>any[], filterFn: (item:any)=>boolean){
-    return filterFn(item) || getChildren(item).some(c => this.transform(c, getChildren, filterFn))
-  }
-}
\ No newline at end of file
diff --git a/src/ui/banner/banner.component.ts b/src/ui/banner/banner.component.ts
index 41bfbc270..1467c912c 100644
--- a/src/ui/banner/banner.component.ts
+++ b/src/ui/banner/banner.component.ts
@@ -1,4 +1,4 @@
-import { Component, OnDestroy, ChangeDetectionStrategy } from "@angular/core";
+import { Component, OnDestroy, ChangeDetectionStrategy, HostListener, ViewChild, ElementRef } 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";
@@ -106,7 +106,6 @@ export class AtlasBanner implements OnDestroy{
   }
 
   handleParcellationChange(parcellation){
-    // const selectedParcellation = this.selectedTemplate.parcellations.find(p=>p.ngId === parcellation.ngId)
     this.selectedParcellation = parcellation
     this.regionsLabelIndexMap = getLabelIndexMap(parcellation.regions)
   }
@@ -168,7 +167,29 @@ export class AtlasBanner implements OnDestroy{
 
   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)
+      return
+    this.searchTerm = event.target.value
+  }
+
+  @ViewChild('searchRegionPopover', {read:ElementRef}) inputRegionPopover : ElementRef
+  public showRegionTree: boolean
+
+  @HostListener('document:click',['$event'])
+  closeRegion(event:MouseEvent){
+    /* FF does not implement event.srcElement so use event.originalTarget to polyfill for FF */
+    const contains = this.inputRegionPopover.nativeElement.contains(event.srcElement) || this.inputRegionPopover.nativeElement.contains((event as any).originalTarget)
+    if(contains)
+      this.showRegionTree = true
+    else
+      this.showRegionTree = false
+  }
+
   handleClickRegion(obj:any){
+    obj.event.stopPropagation()
     this.handleRegionTreeClickSubject.next(obj)
   }
 
diff --git a/src/ui/banner/banner.style.css b/src/ui/banner/banner.style.css
index 14efb0cfb..8c748fa42 100644
--- a/src/ui/banner/banner.style.css
+++ b/src/ui/banner/banner.style.css
@@ -59,6 +59,7 @@ div[hideScrollbarcontainer]
 {
   width: 20em;
   overflow:hidden;
+  margin-top:2px;
 }
 
 div[searchRegionPopover]
diff --git a/src/ui/banner/banner.template.html b/src/ui/banner/banner.template.html
index 89aa93ae8..0a4fa76fb 100644
--- a/src/ui/banner/banner.template.html
+++ b/src/ui/banner/banner.template.html
@@ -1,4 +1,3 @@
-
 <span hbpLogoContainer>
   <img src = "res/image/HBP_Primary_RGB_WhiteText.png" />
 </span>
@@ -21,40 +20,38 @@
 
   <div 
     *ngIf = "selectedTemplate"
-    placement = "bottom"
-    [outsideClick] = "true"
-    [popover]="searchRegionTemplate"
-    containerClass = "inputSearchContainer"
-    triggers = ""
-    #inputSearchRegionPopover = "bs-popover"
+    #searchRegionPopover
     searchRegionPopover>
 
     <input 
-      (keydown.esc)="inputSearchRegionPopover.hide();$event.target.blur();searchTerm=''"
-      [(ngModel)]="searchTerm"
+      (keydown.esc) = "showRegionTree = false; $event.target.blur(); searchTerm = ''"
+      (focus) = "showRegionTree = true"
+      [value] = "searchTerm"
+      (input) = "changeSearchTerm($event)"
       class = "form-control" 
-      (click)="inputSearchRegionPopover.show()"
       type="text" 
       placeholder="Regions"/>
       
-  </div>
-</div>
+    <div 
+      [@showState]
+      *ngIf = "showRegionTree" 
+      hideScrollbarContainer>
 
-<ng-template #searchRegionTemplate>
-  <div [@showState] hideScrollbarContainer>
-    <div treeContainer>
-      <div treeHeader>
-        <span>{{ selectedRegions.length }} {{ selectedRegions.length > 1 ? 'regions' : 'region' }} selected</span> 
-        <span (click) = "clearRegions($event)" *ngIf = "selectedRegions.length > 0" class = "btn btn-link">clear all</span>
-      </div>
-      <flat-tree-component 
-        *ngFor = "let child of selectedParcellation.regions "
-        (treeNodeClick) = "handleClickRegion({inputItem:$event})"
-        [inputItem] = "child"
-        [renderNode] = "displayTreeNode.bind(this)"
-        [searchFilter] = "filterTreeBySearch.bind(this)">
+      <div treeContainer>
+        <div treeHeader>
+          <span>{{ selectedRegions.length }} {{ selectedRegions.length > 1 ? 'regions' : 'region' }} selected</span> 
+          <span (click) = "clearRegions($event)" *ngIf = "selectedRegions.length > 0" class = "btn btn-link">clear all</span>
+        </div>
         
-      </flat-tree-component>
+        <flat-tree-component 
+          *ngFor = "let child of selectedParcellation.regions "
+          (treeNodeClick) = "handleClickRegion($event)"
+          [inputItem] = "child"
+          [renderNode] = "displayTreeNode.bind(this)"
+          [searchFilter] = "filterTreeBySearch.bind(this)">
+          
+        </flat-tree-component>
+      </div>
     </div>
   </div>
-</ng-template>
\ No newline at end of file
+</div>
diff --git a/src/ui/banner/regionPopover.animation.ts b/src/ui/banner/regionPopover.animation.ts
index e20157ca9..ecf95a134 100644
--- a/src/ui/banner/regionPopover.animation.ts
+++ b/src/ui/banner/regionPopover.animation.ts
@@ -14,9 +14,14 @@ export const regionAnimation = trigger('showState',[
     })
   ),
   transition('* => void', [
-    animate('2300ms ease-in')
+    animate('230ms ease-in')
   ]),
   transition('void => *',[
-    animate('230ms ease-out')
+    style({
+      opacity : '0.0',
+    }),
+    animate('420ms ease-in', style({
+      opacity : '1.0'
+    }))
   ])
 ])
\ No newline at end of file
diff --git a/src/ui/ui.module.ts b/src/ui/ui.module.ts
index 424a75b8c..e599b3263 100644
--- a/src/ui/ui.module.ts
+++ b/src/ui/ui.module.ts
@@ -24,7 +24,6 @@ import { LandmarkUnit } from "./nehubaContainer/landmarkUnit/landmarkUnit.compon
 import { SafeStylePipe } from "../util/pipes/safeStyle.pipe";
 import { PluginBannerUI } from "./pluginBanner/pluginBanner.component";
 import { AtlasBanner } from "./banner/banner.component";
-import { PopoverModule } from "ngx-bootstrap/popover";
 import { CitationsContainer } from "./citation/citations.component";
 import { LayerBrowser } from "./layerbrowser/layerbrowser.component";
 import { TooltipModule } from "ngx-bootstrap/tooltip";
@@ -42,8 +41,7 @@ import { SortDataEntriesToRegion } from "../util/pipes/sortDataEntriesIntoRegion
     LayoutModule,
     ComponentsModule,
 
-    TooltipModule.forRoot(),
-    PopoverModule.forRoot()
+    TooltipModule.forRoot()
   ],
   declarations : [
     NehubaContainer,
-- 
GitLab