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