diff --git a/src/atlasViewer/widgetUnit/widgetUnit.component.ts b/src/atlasViewer/widgetUnit/widgetUnit.component.ts index 84b1655d64036495646ffde9ba4fe4238e9d7b2a..e4f112439a75a831707cfa8aa6fe6ceb5301f0bc 100644 --- a/src/atlasViewer/widgetUnit/widgetUnit.component.ts +++ b/src/atlasViewer/widgetUnit/widgetUnit.component.ts @@ -121,38 +121,4 @@ export class WidgetUnit implements OnInit{ /* floating widget specific functionalities */ position : [number,number] = [400,100] - reposStartViewPos : [number,number] = [0,0] - reposStartMousePos : [number,number] = [0,0] - repositionFlag : boolean = false - - /* nb FF does not provide event.clientX / event.clientY on drag event. So will have to attach dragover event listener */ - /* ref: https://bugzilla.mozilla.org/show_bug.cgi?id=505521 */ - @HostListener('document:dragover',['$event']) - mousemove(ev:MouseEvent){ - if(this.repositionFlag){ - this.position[0] = this.reposStartViewPos[0] - this.reposStartMousePos[0] + ev.clientX - this.position[1] = this.reposStartViewPos[1] - this.reposStartMousePos[1] + ev.clientY - } - } - - dragend(ev:DragEvent){ - this.repositionFlag = false - } - - dragstart(ev:DragEvent){ - this.reposStartMousePos[0] = ev.clientX - this.reposStartMousePos[1] = ev.clientY - - this.reposStartViewPos[0] = this.position[0] - this.reposStartViewPos[1] = this.position[1] - - this.repositionFlag = true - - /* nb FF requires dataTransfer.setData in order to fire the drag or dragover event */ - ev.dataTransfer.setData('application/node type', '') - - /* nb FF will render any invisible DOM element as a file icon. */ - ev.dataTransfer.setDragImage(this.emtpy.nativeElement, 0, 0) - } - } \ No newline at end of file diff --git a/src/atlasViewer/widgetUnit/widgetUnit.template.html b/src/atlasViewer/widgetUnit/widgetUnit.template.html index 2b32cf50dba13f635d9bd4bb33b57d95b4af23be..bc19ac9535b79282e1a85b3f5110f467818a5839 100644 --- a/src/atlasViewer/widgetUnit/widgetUnit.template.html +++ b/src/atlasViewer/widgetUnit/widgetUnit.template.html @@ -2,13 +2,13 @@ [style.transform] = "transform" [containerClass] = "containerClass" widgetUnitPanel - [bodyCollapsable] = "state === 'docked'"> + [bodyCollapsable] = "state === 'docked'" + [cdkDragDisabled]="state === 'docked'" + cdkDrag> <div - draggable = "true" - (dragend) = "dragend($event)" - (dragstart) = "dragstart($event)" - widgetUnitHeading - heading> + widgetUnitHeading + heading + cdkDragHandle> <div #emptyspan emptyspan>.</div> <div title> <div *ngIf="!titleHTML"> diff --git a/src/components/components.module.ts b/src/components/components.module.ts index 43c42025391e52757ddb3340904ecab4cab9b9f0..deeed608e8648a00074b0aae7edc17119613e5b0 100644 --- a/src/components/components.module.ts +++ b/src/components/components.module.ts @@ -1,5 +1,6 @@ import { NgModule } from '@angular/core' import { FormsModule } from '@angular/forms' +import { ScrollingModule } from '@angular/cdk/scrolling' import { BrowserAnimationsModule } from '@angular/platform-browser/animations' import { MarkdownDom } from './markdown/markdown.component'; @@ -31,6 +32,7 @@ import { RadioList } from './radiolist/radiolist.component'; @NgModule({ imports : [ CommonModule, + ScrollingModule, FormsModule, BrowserAnimationsModule, ], diff --git a/src/components/flatTree/flatTree.component.ts b/src/components/flatTree/flatTree.component.ts index b82ec4f5b1c5ac9d720bf762a74852f7bcdcd2d4..1ff00222081d946cee2fa22491f57af0d4c519dd 100644 --- a/src/components/flatTree/flatTree.component.ts +++ b/src/components/flatTree/flatTree.component.ts @@ -1,4 +1,4 @@ -import { EventEmitter, Component, Input, Output, ChangeDetectionStrategy, ElementRef, OnDestroy, ChangeDetectorRef, ViewChildren, QueryList, AfterViewChecked, AfterViewInit, OnInit } from "@angular/core"; +import { EventEmitter, Component, Input, Output, ChangeDetectionStrategy } from "@angular/core"; import { FlattenedTreeInterface } from "./flattener.pipe"; /** @@ -14,7 +14,7 @@ import { FlattenedTreeInterface } from "./flattener.pipe"; changeDetection:ChangeDetectionStrategy.OnPush }) -export class FlatTreeComponent implements AfterViewChecked, AfterViewInit, OnInit, OnDestroy{ +export class FlatTreeComponent{ @Input() inputItem : any = { name : 'Untitled', children : [] @@ -31,60 +31,7 @@ export class FlatTreeComponent implements AfterViewChecked, AfterViewInit, OnIni @Input() findChildren : (item:any)=>any[] = (item)=>item.children ? item.children : [] @Input() searchFilter : (item:any)=>boolean | null = ()=>true - @Input() flatTreeViewPort : HTMLElement - - @ViewChildren('flatTreeStart',{read : ElementRef}) flatTreeStartCollection : QueryList<ElementRef> - @ViewChildren('flatTreeEnd',{read : ElementRef}) flatTreeEndCollection : QueryList<ElementRef> - - intersectionObserver : IntersectionObserver - - constructor( - private cdr:ChangeDetectorRef - ){ - - } - - ngAfterViewChecked(){ - if(this.intersectionObserver){ - this.intersectionObserver.disconnect() - this.flatTreeStartCollection.forEach(er => this.intersectionObserver.observe(er.nativeElement)) - this.flatTreeEndCollection.forEach(er => this.intersectionObserver.observe(er.nativeElement)) - } - } - - ngOnInit(){ - if(this.flatTreeViewPort){ - this.clusterNumber = 50 - } - } - - ngAfterViewInit(){ - - if(this.flatTreeViewPort){ - this.intersectionObserver = new IntersectionObserver(entries => { - const currPos = entries - .filter(entry => entry.isIntersecting) - .filter(entry => Number(entry.target.getAttribute('clusterindex')) !== NaN ) - .map(entry => Number(entry.target.getAttribute('clusterindex'))) - .reduce((acc, clusterindex, i, array) => acc + (clusterindex / array.length), 0) - - if( currPos - this._currentPos >= 1 ){ - this._currentPos = Math.round(currPos) - this.cdr.markForCheck() - } - },{ - root: this.flatTreeViewPort, - rootMargin : '0px', - threshold : 0.1 - }) - } - } - - ngOnDestroy(){ - if(this.intersectionObserver){ - this.intersectionObserver.disconnect() - } - } + public flattenedItems : any[] = [] getClass(level:number){ return [...Array(level+1)].map((v,idx) => `render-node-level-${idx}`).join(' ') @@ -121,10 +68,4 @@ export class FlatTreeComponent implements AfterViewChecked, AfterViewInit, OnIni .some(id => this.isCollapsedById(id)) } - private _currentPos : number = 0 - public clusterNumber : number = Number.POSITIVE_INFINITY - - showCluster(index:number){ - return index <= this._currentPos + 1 - } } \ No newline at end of file diff --git a/src/components/flatTree/flatTree.style.css b/src/components/flatTree/flatTree.style.css index 1eb8e5e11f385e08de61989c548ccce1c1d03a0a..d71418cc8ef2641f95bd5b11b263462bbf600960 100644 --- a/src/components/flatTree/flatTree.style.css +++ b/src/components/flatTree/flatTree.style.css @@ -1,33 +1,14 @@ :host { - display: block; + display: flex; + flex-direction: column; height:100%; } -[clusterContainer] +cdk-virtual-scroll-viewport { - position:relative; -} - -[flatTreeStart] -{ - position: absolute; - top: 0; - height:50%; - width:1em; - opacity:0; - background-color:rgba(200,250,200,0.2); -} - -[flatTreeEnd] -{ - position: absolute; - top: 0; - transform: translateY(100%); - width:1em; - opacity:0; - background-color:rgba(250,200,200,0.2); - height:50%; + flex: 1 0 auto; + overflow-x: hidden; } .render-node-text @@ -191,4 +172,9 @@ span[renderText] .r-270 { transform: rotate(270deg); +} + +[renderNode] +{ + height: 15px; } \ No newline at end of file diff --git a/src/components/flatTree/flatTree.template.html b/src/components/flatTree/flatTree.template.html index ef02ebb8faaabf71254b1505ab6b88ab0380d3fb..2e4b723a351d22609131e5da81430a6b9e51a8b4 100644 --- a/src/components/flatTree/flatTree.template.html +++ b/src/components/flatTree/flatTree.template.html @@ -1,47 +1,37 @@ -<div *ngFor="let flattenedItems of (inputItem | flattenTreePipe : findChildren | filterRowsByVisbilityPipe : findChildren : searchFilter | appendSiblingFlagPipe | clusteringPipe : clusterNumber ); let index = index" clusterContainer> +<cdk-virtual-scroll-viewport + (wheel)="$event.stopPropagation()" + itemSize="15"> <div - class="pe-none" - [attr.clusterindex]="index" - flatTreeStart - #flatTreeStart> - </div> + *cdkVirtualFor="let flattenedItem of (inputItem | flattenTreePipe : findChildren | filterRowsByVisbilityPipe : findChildren : searchFilter | appendSiblingFlagPipe ); let index = index" + [ngClass]="getClass(flattenedItem.flattenedTreeLevel)" + class="text-nowrap" + [attr.flattenedtreelevel]="flattenedItem.flattenedTreeLevel" + [attr.collapsed]="flattenedItem.collapsed ? flattenedItem.collapsed : false" + [attr.lvlId]="flattenedItem.lvlId" + [hidden]="collapseRow(flattenedItem) " + renderNode> - <div *ngIf="showCluster(index)"> - <div - *ngFor="let flattenedItem of flattenedItems" - [ngClass]="getClass(flattenedItem.flattenedTreeLevel)" - class="text-nowrap" - [attr.flattenedtreelevel]="flattenedItem.flattenedTreeLevel" - [attr.collapsed]="flattenedItem.collapsed ? flattenedItem.collapsed : false" - [attr.lvlId]="flattenedItem.lvlId" - [hidden]="collapseRow(flattenedItem) " - renderNode> - - <span class="padding-block-container"> - <span - *ngFor="let block of flattenedItem.siblingFlags" - [attr.hidemargin]="block" - class="padding-block"> - - </span> - </span> - <span - *ngIf="findChildren(flattenedItem).length > 0; else noChildren" - (click)="$event.stopPropagation(); toggleCollapse(flattenedItem)" > - <i [ngClass]="isCollapsed(flattenedItem) ? 'r-270' : ''" class="fas fa-chevron-down"></i> - </span> + <span class="padding-block-container"> <span - (click)="treeNodeClick.emit({event:$event,inputItem:flattenedItem})" - class="render-node-text" - [innerHtml]="flattenedItem | renderPipe : renderNode "> - </span> - </div> - </div> + *ngFor="let block of flattenedItem.siblingFlags" + [attr.hidemargin]="block" + class="padding-block"> - <div [attr.clusterindex]="index" flatTreeEnd #flatTreeEnd> + </span> + </span> + <span + *ngIf="findChildren(flattenedItem).length > 0; else noChildren" + (click)="$event.stopPropagation(); toggleCollapse(flattenedItem)" > + <i [ngClass]="isCollapsed(flattenedItem) ? 'r-270' : ''" class="fas fa-chevron-down"></i> + </span> + <span + (click)="treeNodeClick.emit({event:$event,inputItem:flattenedItem})" + class="render-node-text" + [innerHtml]="flattenedItem | renderPipe : renderNode "> + </span> </div> -</div> +</cdk-virtual-scroll-viewport> <ng-template #noChildren> <i class="fas fa-none"> diff --git a/src/main.module.ts b/src/main.module.ts index 6e8c6547e41b6b3e61e3fc6af8205123acc36957..78bbdebe0dc0280d92f9154caa701ce51879a321 100644 --- a/src/main.module.ts +++ b/src/main.module.ts @@ -1,5 +1,6 @@ import { NgModule } from "@angular/core"; import { ComponentsModule } from "./components/components.module"; +import { DragDropModule } from '@angular/cdk/drag-drop' import { UIModule } from "./ui/ui.module"; import { LayoutModule } from "./layouts/layout.module"; import { AtlasViewer } from "./atlasViewer/atlasViewer.component"; @@ -43,6 +44,7 @@ import { DatabrowserService } from "./ui/databrowserModule/databrowser.service"; CommonModule, LayoutModule, ComponentsModule, + DragDropModule, UIModule, ModalModule.forRoot(), diff --git a/src/res/css/extra_styles.css b/src/res/css/extra_styles.css index dcd4278bd4fe34378217ad9ef28e2c40d3a469fc..6b557d4719eb2bb309afa53c0033dbf7ef779581 100644 --- a/src/res/css/extra_styles.css +++ b/src/res/css/extra_styles.css @@ -319,4 +319,13 @@ markdown-dom pre code .pe-none { pointer-events: none; +} + +.h-100 +{ + height:100%; +} +.overflow-x-hidden +{ + overflow-x:hidden; } \ No newline at end of file diff --git a/src/services/state/dataStore.store.ts b/src/services/state/dataStore.store.ts index 1c8999e78981de8af165a4b656da1182172a6ebd..1534b0232f4cc91b11420c8661cc920f334df22d 100644 --- a/src/services/state/dataStore.store.ts +++ b/src/services/state/dataStore.store.ts @@ -53,6 +53,9 @@ export interface DataEntry{ publications: Publication[] embargoStatus: string[] + methods: string[] + protocols: string[] + preview?: boolean /** diff --git a/src/ui/databrowserModule/databrowser.service.ts b/src/ui/databrowserModule/databrowser.service.ts index d8a2dc8b8cb761660b3f190dd5c13464abb4a4ce..6b446437d43cd1e8aad8db28b9868f30065a6dfc 100644 --- a/src/ui/databrowserModule/databrowser.service.ts +++ b/src/ui/databrowserModule/databrowser.service.ts @@ -299,8 +299,7 @@ export class DatabrowserService implements OnDestroy{ export function reduceDataentry(accumulator:{name:string, occurance:number}[], dataentry: DataEntry) { - const methods = dataentry.activity - .map(a => a.methods) + const methods = dataentry.methods .reduce((acc, item) => acc.concat( item.length > 0 ? item diff --git a/src/ui/regionHierachy/regionHierarchy.component.ts b/src/ui/regionHierachy/regionHierarchy.component.ts index 1d5e272814aa05bdd4d636ed0b0ee338ac308e65..9fcc8df3b55060b15d4b625c1093009f43aebc00 100644 --- a/src/ui/regionHierachy/regionHierarchy.component.ts +++ b/src/ui/regionHierachy/regionHierarchy.component.ts @@ -1,8 +1,23 @@ -import { EventEmitter, Component, ElementRef, ViewChild, HostListener, OnInit, ChangeDetectionStrategy, ChangeDetectorRef, Input, Output } from "@angular/core"; -import { Subscription, Subject } from "rxjs"; +import { EventEmitter, Component, ElementRef, ViewChild, HostListener, OnInit, ChangeDetectionStrategy, ChangeDetectorRef, Input, Output, AfterViewInit } from "@angular/core"; +import { Subscription, Subject, fromEvent } from "rxjs"; import { buffer, debounceTime } from "rxjs/operators"; import { FilterNameBySearch } from "./filterNameBySearch.pipe"; +const insertHighlight :(name:string, searchTerm:string) => string = (name:string, searchTerm:string = '') => { + const regex = new RegExp(searchTerm, 'gi') + return searchTerm === '' ? + name : + name.replace(regex, (s) => `<span class = "highlight">${s}</span>`) +} + +const getDisplayTreeNode : (searchTerm:string, selectedRegions:any[]) => (item:any) => string = (searchTerm:string = '', selectedRegions:any[] = []) => (item:any) => { + return typeof item.labelIndex !== 'undefined' && selectedRegions.findIndex(re => re.labelIndex === Number(item.labelIndex)) >= 0 ? + `<span class = "regionSelected">${insertHighlight(item.name, searchTerm)}</span>` : + `<span class = "regionNotSelected">${insertHighlight(item.name, searchTerm)}</span>` +} + +const getFilterTreeBySearch = (pipe:FilterNameBySearch, searchTerm:string) => (node:any) => pipe.transform([node.name], searchTerm) + @Component({ selector: 'region-hierarchy', templateUrl: './regionHierarchy.template.html', @@ -12,8 +27,7 @@ import { FilterNameBySearch } from "./filterNameBySearch.pipe"; changeDetection: ChangeDetectionStrategy.OnPush }) -export class RegionHierarchy implements OnInit{ - +export class RegionHierarchy implements OnInit, AfterViewInit{ @Input() public selectedRegions: any[] = [] @@ -57,6 +71,12 @@ export class RegionHierarchy implements OnInit{ private cdr:ChangeDetectorRef, private el:ElementRef ){ + + } + + ngOnChanges(){ + this.displayTreeNode = getDisplayTreeNode(this.searchTerm, this.selectedRegions) + this.filterTreeBySearch = getFilterTreeBySearch(this.filterNameBySearchPipe, this.searchTerm) } clearRegions(event:MouseEvent){ @@ -74,6 +94,11 @@ export class RegionHierarchy implements OnInit{ } ngOnInit(){ + this.aggregatedRegionTree = { + name: this.selectedParcellation.name, + children: this.selectedParcellation.regions + } + this.subscriptions.push( this.handleRegionTreeClickSubject.pipe( buffer( @@ -85,6 +110,16 @@ export class RegionHierarchy implements OnInit{ ) } + ngAfterViewInit(){ + this.subscriptions.push( + fromEvent(this.searchTermInput.nativeElement, 'input').pipe( + debounceTime(200) + ).subscribe(ev => { + this.changeSearchTerm(ev) + }) + ) + } + getInputPlaceholder(parcellation:any) { if (parcellation) return `Search region in ${parcellation.name}` @@ -120,6 +155,7 @@ export class RegionHierarchy implements OnInit{ /** * TODO maybe introduce debounce */ + this.ngOnChanges() this.cdr.markForCheck() } @@ -155,28 +191,11 @@ export class RegionHierarchy implements OnInit{ this.doubleClickRegion.emit(region) } - 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>`) - } + public displayTreeNode: (item:any) => string - 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>` - } + private filterNameBySearchPipe = new FilterNameBySearch() + public filterTreeBySearch: (node:any) => boolean - filterNameBySearchPipe = new FilterNameBySearch() - filterTreeBySearch(node: any): boolean { - return this.filterNameBySearchPipe.transform([node.name], this.searchTerm) - } + public aggregatedRegionTree: any - get aggregatedRegionTree() { - return { - name: this.selectedParcellation.name, - children: this.selectedParcellation.regions - } - } } \ No newline at end of file diff --git a/src/ui/regionHierachy/regionHierarchy.style.css b/src/ui/regionHierachy/regionHierarchy.style.css index e6ada8b938dec920681ade22eb945c51608c9642..a4aa507f4db1a32886dc1309934dc8481d49a425 100644 --- a/src/ui/regionHierachy/regionHierarchy.style.css +++ b/src/ui/regionHierachy/regionHierarchy.style.css @@ -4,8 +4,8 @@ div[treeContainer] padding:1em; z-index: 3; - max-height:20em; - width: calc(100% + 2em); + height:20em; + width: calc(100% + 4em); overflow-y:auto; overflow-x:hidden; @@ -52,4 +52,14 @@ input[type="text"] .regionSearch { width:20em; +} + +.tree-header +{ + flex: 0 0 auto; +} + +.tree-body +{ + flex: 1 1 auto; } \ No newline at end of file diff --git a/src/ui/regionHierachy/regionHierarchy.template.html b/src/ui/regionHierachy/regionHierarchy.template.html index 2c46bb630314cee5c93a683835eb48b4f0f1b53e..669e0dc85c8b056a0b14bac89770165939e26eef 100644 --- a/src/ui/regionHierachy/regionHierarchy.template.html +++ b/src/ui/regionHierachy/regionHierarchy.template.html @@ -5,7 +5,6 @@ (keydown.esc)="escape($event)" (focus)="showRegionTree = true" [value]="searchTerm" - (input)="changeSearchTerm($event)" class="form-control form-control-sm" type="text" [placeholder]="getInputPlaceholder(selectedParcellation)"/> @@ -16,8 +15,11 @@ *ngIf="showRegionTree" hideScrollbarContainer> - <div treeContainer #treeContainer> - <div class="d-inline-flex align-items-center"> + <div + class="d-flex flex-column" + treeContainer + #treeContainer> + <div class="tree-header d-inline-flex align-items-center"> <div> {{ selectedRegions.length }} {{ selectedRegions.length > 1 ? 'regions' : 'region' }} selected </div> @@ -29,17 +31,16 @@ </div> </div> - <ng-container *ngIf="selectedParcellation && selectedParcellation.regions as regions"> - <!-- TODO deprecate flat tree component, opt for material cdk infinit scroll component --> + <div + *ngIf="selectedParcellation && selectedParcellation.regions as regions" + class="tree-body"> <flat-tree-component - *ngFor="let region of regions" - [flatTreeViewPort]="treeContainer" (treeNodeClick)="handleClickRegion($event)" - [inputItem]="region" - [renderNode]="displayTreeNode.bind(this)" - [searchFilter]="filterTreeBySearch.bind(this)"> + [inputItem]="aggregatedRegionTree" + [renderNode]="displayTreeNode" + [searchFilter]="filterTreeBySearch"> </flat-tree-component> - </ng-container> + </div> </div> </div> \ No newline at end of file diff --git a/src/ui/ui.module.ts b/src/ui/ui.module.ts index 4c494358055930abc18b0f2ea387d4ba0d327aee..0ba8a52700192af3789d8474a89fb11fc9ae525c 100644 --- a/src/ui/ui.module.ts +++ b/src/ui/ui.module.ts @@ -1,5 +1,4 @@ import { NgModule } from "@angular/core"; -import { BrowserModule } from "@angular/platform-browser"; import { ComponentsModule } from "../components/components.module"; import { NehubaViewerUnit } from "./nehubaContainer/nehubaViewer/nehubaViewer.component"; @@ -50,7 +49,6 @@ import { CookieAgreement } from "./cookieAgreement/cookieAgreement.component"; @NgModule({ imports : [ FormsModule, - BrowserModule, LayoutModule, ComponentsModule, DatabrowserModule,