diff --git a/src/atlasViewer/atlasViewer.component.ts b/src/atlasViewer/atlasViewer.component.ts index c220280cf6e8e3619d89bbb45e5f0d6251beae87..aad2b38644b0bc369b83e60917b576fc7adda7c5 100644 --- a/src/atlasViewer/atlasViewer.component.ts +++ b/src/atlasViewer/atlasViewer.component.ts @@ -67,8 +67,8 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit { private showHelp$: Observable<any> public dedicatedView$: Observable<string | null> - public onhoverSegment$: Observable<string> - public onhoverSegmentForFixed$: Observable<string> + public onhoverSegments$: Observable<string[]> + public onhoverSegmentsForFixed$: Observable<string[]> public onhoverLandmark$ : Observable<string | null> private subscriptions: Subscription[] = [] @@ -179,23 +179,23 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit { ) // TODO temporary hack. even though the front octant is hidden, it seems if a mesh is present, hover will select the said mesh - this.onhoverSegment$ = combineLatest( + this.onhoverSegments$ = combineLatest( this.store.pipe( select('uiState'), + select('mouseOverSegments'), + filter(v => !!v), + distinctUntilChanged((o, n) => o.length === n.length && n.every(segment => o.find(oSegment => oSegment.layer.name === segment.layer.name && oSegment.segment === segment.segment) ) ) /* cannot filter by state, as the template expects a default value, or it will throw ExpressionChangedAfterItHasBeenCheckedError */ - map(state => state - && state.mouseOverSegment - && (isNaN(state.mouseOverSegment) - ? state.mouseOverSegment - : state.mouseOverSegment.toString())), - distinctUntilChanged((o, n) => o === n || (o && n && o.name && n.name && o.name === n.name)) + ), this.onhoverLandmark$ ).pipe( - map(([segment, onhoverLandmark]) => onhoverLandmark ? null : segment ) + map(([segments, onhoverLandmark]) => onhoverLandmark ? null : segments ), + map(segments => segments.length === 0 + ? null + : segments.map(s => s.segment) ) ) - this.selectedParcellation$ = this.store.pipe( select('viewerState'), safeFilter('parcellationSelected'), @@ -338,9 +338,9 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit { }) }) - this.onhoverSegmentForFixed$ = this.rClContextualMenu.onShow.pipe( - withLatestFrom(this.onhoverSegment$), - map(([_flag, onhoverSegment]) => onhoverSegment) + this.onhoverSegmentsForFixed$ = this.rClContextualMenu.onShow.pipe( + withLatestFrom(this.onhoverSegments$), + map(([_flag, onhoverSegments]) => onhoverSegments || []) ) } diff --git a/src/atlasViewer/atlasViewer.template.html b/src/atlasViewer/atlasViewer.template.html index c3828d3a89c7e36849915a581753ce76de76ac34..caab40d3ed81ca5eeba00e6160ffaaeac90ed258 100644 --- a/src/atlasViewer/atlasViewer.template.html +++ b/src/atlasViewer/atlasViewer.template.html @@ -142,7 +142,7 @@ </div> <div body> <div - *ngIf="onhoverSegmentForFixed$ | async; let onhoverSegmentFixed" + *ngFor="let onhoverSegmentFixed of (onhoverSegmentsForFixed$ | async)" (click)="searchRegion([onhoverSegmentFixed])" class="ws-no-wrap text-left pe-all btn btn-sm btn-secondary btn-block" data-toggle="tooltip" @@ -160,7 +160,7 @@ <div class="p-2 text-muted" - *ngIf="!(onhoverSegmentForFixed$ | async) && (selectedRegions$ | async)?.length === 0"> + *ngIf="(onhoverSegmentsForFixed$ | async)?.length === 0 && (selectedRegions$ | async)?.length === 0"> Right click on a parcellation region or select parcellation regions to search KG for associated datasets. </div> @@ -182,9 +182,12 @@ {{ onhoverLandmark$ | async }} <i><small class = "mute-text">{{ toggleMessage }}</small></i> </div> <div - *ngIf="onhoverSegment$ | async; let onhoverSegment" + *ngIf="onhoverSegments$ | async; let onhoverSegments" contextualBlock> - {{ onhoverSegment.name }} <i><small class = "mute-text">{{ toggleMessage }}</small></i> + <div *ngFor="let segment of onhoverSegments"> + {{ segment.name || segment }} + </div> + <i><small class = "mute-text">{{ toggleMessage }}</small></i> </div> <!-- TODO Potentially implementing plugin contextual info --> </div> diff --git a/src/atlasViewer/atlasViewer.urlService.service.ts b/src/atlasViewer/atlasViewer.urlService.service.ts index 5cd06b1ba499e34537cce32dc2caa872250c53a3..b548c96f292bfa89fa808beccd7b2c5b16778a2a 100644 --- a/src/atlasViewer/atlasViewer.urlService.service.ts +++ b/src/atlasViewer/atlasViewer.urlService.service.ts @@ -1,12 +1,13 @@ import { Injectable } from "@angular/core"; import { Store, select } from "@ngrx/store"; -import { ViewerStateInterface, isDefined, NEWVIEWER, getLabelIndexMap, SELECT_REGIONS, CHANGE_NAVIGATION, ADD_NG_LAYER } from "../services/stateStore.service"; +import { ViewerStateInterface, isDefined, NEWVIEWER, CHANGE_NAVIGATION, ADD_NG_LAYER, generateLabelIndexId } from "../services/stateStore.service"; import { PluginInitManifestInterface } from 'src/services/state/pluginState.store' import { Observable,combineLatest } from "rxjs"; import { filter, map, scan, distinctUntilChanged, skipWhile, take } from "rxjs/operators"; import { PluginServices } from "./atlasViewer.pluginService.service"; import { AtlasViewerConstantsServices } from "./atlasViewer.constantService.service"; import { ToastService } from "src/services/toastService.service"; +import { SELECT_REGIONS_WITH_ID } from "src/services/state/viewerState.store"; declare var window @@ -147,14 +148,13 @@ export class AtlasViewerURLService{ /** * either or both parcellationToLoad and .regions maybe empty */ - const labelIndexMap = getLabelIndexMap(parcellationToLoad.regions) - const selectedRegions = searchparams.get('regionsSelected') - if(selectedRegions){ + const selectedRegionsParam = searchparams.get('regionsSelected') + if(selectedRegionsParam){ + const ids = selectedRegionsParam.split('_') + this.store.dispatch({ - type : SELECT_REGIONS, - selectRegions : selectedRegions.split('_') - .map(labelIndex=>labelIndexMap.get(Number(labelIndex))) - .filter(region => !!region) + type : SELECT_REGIONS_WITH_ID, + selectRegionIds: ids }) } } @@ -223,7 +223,7 @@ export class AtlasViewerURLService{ } break; case 'regionsSelected': - _[key] = state[key].map(region=>region.labelIndex).join('_') + _[key] = state[key].map(({ ngId, labelIndex })=> generateLabelIndexId({ ngId,labelIndex })).join('_') break; case 'templateSelected': case 'parcellationSelected': diff --git a/src/services/state/uiState.store.ts b/src/services/state/uiState.store.ts index bdf52506daf64fe0cc2d9da4352dad9fe25fbcee..fadd5b87fcc506e504ff011a429675648d85c303 100644 --- a/src/services/state/uiState.store.ts +++ b/src/services/state/uiState.store.ts @@ -4,6 +4,7 @@ const agreedCookieKey = 'agreed-cokies' const aggredKgTosKey = 'agreed-kg-tos' const defaultState : UIStateInterface = { + mouseOverSegments: [], mouseOverSegment: null, mouseOverLandmark: null, focusedSidePanel: null, @@ -18,6 +19,12 @@ const defaultState : UIStateInterface = { export function uiState(state:UIStateInterface = defaultState,action:UIAction){ switch(action.type){ + case MOUSE_OVER_SEGMENTS: + const { segments } = action + return { + ...state, + mouseOverSegments: segments + } case MOUSE_OVER_SEGMENT: return { ...state, @@ -71,6 +78,12 @@ export function uiState(state:UIStateInterface = defaultState,action:UIAction){ } export interface UIStateInterface{ + mouseOverSegments: { + layer: { + name: string + } + segment: any | null + }[] sidePanelOpen : boolean mouseOverSegment : any | number mouseOverLandmark : any @@ -81,12 +94,19 @@ export interface UIStateInterface{ } export interface UIAction extends Action{ - segment : any | number - landmark : any + segment: any | number + landmark: any focusedSidePanel? : string + segments?:{ + layer: { + name: string + } + segment: any | null + }[] } export const MOUSE_OVER_SEGMENT = `MOUSE_OVER_SEGMENT` +export const MOUSE_OVER_SEGMENTS = `MOUSE_OVER_SEGMENTS` export const MOUSE_OVER_LANDMARK = `MOUSE_OVER_LANDMARK` export const TOGGLE_SIDE_PANEL = 'TOGGLE_SIDE_PANEL' diff --git a/src/services/state/viewerState.store.ts b/src/services/state/viewerState.store.ts index a8ff3fdaade1c41dbd7b6f46215f7caabc400526..2911c629efca453118989b70ec484730ccc530dd 100644 --- a/src/services/state/viewerState.store.ts +++ b/src/services/state/viewerState.store.ts @@ -1,5 +1,6 @@ import { Action } from '@ngrx/store' import { UserLandmark } from 'src/atlasViewer/atlasViewer.apiService.service'; +import { getNgIdLabelIndexFromId, generateLabelIndexId, recursiveFindRegionWithLabelIndexId, propagateNgId } from '../stateStore.service'; export interface ViewerStateInterface{ fetchedTemplates : any[] @@ -20,7 +21,8 @@ export interface AtlasAction extends Action{ selectTemplate? : any selectParcellation? : any - selectRegions? : any[] + selectRegions?: any[] + selectRegionIds: string[] deselectRegions? : any[] dedicatedView? : string @@ -57,10 +59,12 @@ export function viewerState( : [] } case NEWVIEWER: + const { selectParcellation } = action + const parcellation = propagateNgId( selectParcellation ) return { ...state, templateSelected : action.selectTemplate, - parcellationSelected : action.selectParcellation, + parcellationSelected : parcellation, regionsSelected : [], landmarksSelected : [], navigation : {}, @@ -79,27 +83,53 @@ export function viewerState( } } case SELECT_PARCELLATION : { + const { selectParcellation } = action + const parcellation = propagateNgId( selectParcellation ) return { ...state, - parcellationSelected : action.selectParcellation, - regionsSelected : [] + parcellationSelected: parcellation, + regionsSelecteds: [] } } - case DESELECT_REGIONS : { + case SELECT_REGIONS: + const { selectRegions } = action return { ...state, - regionsSelected : state.regionsSelected.filter(re => action.deselectRegions.findIndex(dRe => dRe.name === re.name) < 0) + regionsSelected: selectRegions } - } - case SELECT_REGIONS : { - return { - ...state, - regionsSelected : action.selectRegions.map(region => { + case SELECT_REGIONS_WITH_ID : { + const { parcellationSelected } = state + const { selectRegionIds } = action + const { ngId: defaultNgId } = parcellationSelected + + /** + * for backwards compatibility. + * older versions of atlas viewer may only have labelIndex as region identifier + */ + const regionsSelected = selectRegionIds + .map(labelIndexId => getNgIdLabelIndexFromId({ labelIndexId })) + .map(({ ngId, labelIndex }) => { return { - ...region, - labelIndex: Number(region.labelIndex) + labelIndexId: generateLabelIndexId({ + ngId: ngId || defaultNgId, + labelIndex + }) } }) + .map(({ labelIndexId }) => { + return recursiveFindRegionWithLabelIndexId({ + regions: parcellationSelected.regions, + labelIndexId, + inheritedNgId: defaultNgId + }) + }) + .filter(v => { + if (!v) console.log(`SELECT_REGIONS_WITH_ID, some ids cannot be parsed intto label index`) + return !!v + }) + return { + ...state, + regionsSelected } } case DESELECT_LANDMARKS : { @@ -135,7 +165,7 @@ export const CHANGE_NAVIGATION = 'CHANGE_NAVIGATION' export const SELECT_PARCELLATION = `SELECT_PARCELLATION` export const SELECT_REGIONS = `SELECT_REGIONS` -export const DESELECT_REGIONS = `DESELECT_REGIONS` +export const SELECT_REGIONS_WITH_ID = `SELECT_REGIONS_WITH_ID` export const SELECT_LANDMARKS = `SELECT_LANDMARKS` export const DESELECT_LANDMARKS = `DESELECT_LANDMARKS` export const USER_LANDMARKS = `USER_LANDMARKS` diff --git a/src/services/stateStore.service.ts b/src/services/stateStore.service.ts index 18c1a547e5f963ab03db7be7f92e79b0ffc37649..5bcd42ed9acd371ec4f5bc1ce223308ee44bdc9f 100644 --- a/src/services/stateStore.service.ts +++ b/src/services/stateStore.service.ts @@ -3,7 +3,7 @@ import { filter } from 'rxjs/operators'; export { viewerConfigState } from './state/viewerConfig.store' export { pluginState } from './state/pluginState.store' export { NgViewerAction, NgViewerStateInterface, ngViewerState, ADD_NG_LAYER, FORCE_SHOW_SEGMENT, HIDE_NG_LAYER, REMOVE_NG_LAYER, SHOW_NG_LAYER } from './state/ngViewerState.store' -export { CHANGE_NAVIGATION, AtlasAction, DESELECT_LANDMARKS, DESELECT_REGIONS, FETCHED_TEMPLATE, NEWVIEWER, SELECT_LANDMARKS, SELECT_PARCELLATION, SELECT_REGIONS, USER_LANDMARKS, ViewerStateInterface, viewerState } from './state/viewerState.store' +export { CHANGE_NAVIGATION, AtlasAction, DESELECT_LANDMARKS, FETCHED_TEMPLATE, NEWVIEWER, SELECT_LANDMARKS, SELECT_PARCELLATION, SELECT_REGIONS, USER_LANDMARKS, ViewerStateInterface, viewerState } from './state/viewerState.store' export { DataEntry, ParcellationRegion, DataStateInterface, DatasetAction, FETCHED_DATAENTRIES, FETCHED_METADATA, FETCHED_SPATIAL_DATA, Landmark, OtherLandmarkGeometry, PlaneLandmarkGeometry, PointLandmarkGeometry, Property, Publication, ReferenceSpace, dataStore, File, FileSupplementData } from './state/dataStore.store' export { CLOSE_SIDE_PANEL, MOUSE_OVER_LANDMARK, MOUSE_OVER_SEGMENT, OPEN_SIDE_PANEL, TOGGLE_SIDE_PANEL, UIAction, UIStateInterface, uiState } from './state/uiState.store' export { SPATIAL_GOTO_PAGE, SpatialDataEntries, SpatialDataStateInterface, UPDATE_SPATIAL_DATA, spatialSearchState } from './state/spatialSearchState.store' @@ -25,6 +25,63 @@ export function extractLabelIdx(region:any):number[]{ },[]).concat( region.labelIndex ? Number(region.labelIndex) : [] ) } +const inheritNgId = (region:any) => { + const {ngId = 'root', children = []} = region + return { + ngId, + ...region, + ...(children && children.map + ? { + children: children.map(c => inheritNgId({ + ngId, + ...c + })) + } + : {}) + } +} + +export function getMultiNgIdsRegionsLabelIndexMap(parcellation: any = {}):Map<string,Map<number, any>>{ + const map:Map<string,Map<number, any>> = new Map() + const { ngId = 'root'} = parcellation + + const processRegion = (region:any) => { + const { ngId } = region + const existingMap = map.get(ngId) + const labelIndex = Number(region.labelIndex) + if (labelIndex) { + if (!existingMap) { + const newMap = new Map() + newMap.set(labelIndex, region) + map.set(ngId, newMap) + } else { + existingMap.set(labelIndex, region) + } + } + + if (region.children && region.children.forEach) { + region.children.forEach(child => { + processRegion({ + ngId, + ...child + }) + }) + } + } + + if (parcellation && parcellation.regions && parcellation.regions.forEach) { + parcellation.regions.forEach(r => processRegion({ + ngId, + ...r + })) + } + + return map +} + +/** + * labelIndexMap maps label index to region + */ export function getLabelIndexMap(regions:any[]):Map<number,any>{ const returnMap = new Map() @@ -38,7 +95,16 @@ export function getLabelIndexMap(regions:any[]):Map<number,any>{ reduceRegions(regions) return returnMap -} +} + +export function getNgIds(regions: any[]): string[]{ + return regions && regions.map + ? regions + .map(r => [r.ngId, ...getNgIds(r.children)]) + .reduce((acc, item) => acc.concat(item), []) + .filter(ngId => !!ngId) + : [] +} export interface DedicatedViewState{ dedicatedView : string | null @@ -47,3 +113,61 @@ export interface DedicatedViewState{ export function isDefined(obj){ return typeof obj !== 'undefined' && obj !== null } + +export function generateLabelIndexId({ ngId, labelIndex }) { + return `${ngId}#${labelIndex}` +} + +export function getNgIdLabelIndexFromId({ labelIndexId } = {labelIndexId: ''}) { + const _ = labelIndexId && labelIndexId.split && labelIndexId.split('#') || [] + const ngId = _.length > 1 + ? _[0] + : null + const labelIndex = _.length > 1 + ? Number(_[1]) + : _.length === 0 + ? null + : Number(_[0]) + return { ngId, labelIndex } +} + +const recursiveFlatten = (region, {ngId}) => { + return [{ + ngId, + ...region + }].concat( + ...((region.children && region.children.map && region.children.map(c => recursiveFlatten(c, { ngId : region.ngId || ngId })) )|| []) + ) +} + +export function recursiveFindRegionWithLabelIndexId({ regions, labelIndexId, inheritedNgId = 'root' }: {regions: any[], labelIndexId: string, inheritedNgId:string}) { + const { ngId, labelIndex } = getNgIdLabelIndexFromId({ labelIndexId }) + const fr1 = regions.map(r => recursiveFlatten(r,{ ngId: inheritedNgId })) + const fr2 = fr1.reduce((acc, curr) => acc.concat(...curr), []) + const found = fr2.find(r => r.ngId === ngId && Number(r.labelIndex) === Number(labelIndex)) + if (found) return found + debugger + return null +} + +export function propagateNgId(parcellation) { + const recursivePropagateNgId = (region, {ngId}) => { + return { + ngId, + ...region, + ...( region.children && region.children.map + ? { + children: region.children.map(c => recursivePropagateNgId(c, { ngId: region.ngId || ngId })) + } + : {} ) + } + } + const regions = parcellation.regions && parcellation.regions.map + ? parcellation.regions.map(r => recursivePropagateNgId(r, { ngId: parcellation.ngId })) + : [] + + return { + ...parcellation, + regions + } +} \ No newline at end of file diff --git a/src/ui/nehubaContainer/nehubaContainer.component.ts b/src/ui/nehubaContainer/nehubaContainer.component.ts index 146d7a44649df836cdbbc57bf6b93d38e3fcda59..21cf79327d3d41222895e53c0f4e6f737cff494c 100644 --- a/src/ui/nehubaContainer/nehubaContainer.component.ts +++ b/src/ui/nehubaContainer/nehubaContainer.component.ts @@ -1,16 +1,17 @@ import { Component, ViewChild, ViewContainerRef, ComponentFactoryResolver, ComponentFactory, ComponentRef, OnInit, OnDestroy, ElementRef } from "@angular/core"; import { NehubaViewerUnit } from "./nehubaViewer/nehubaViewer.component"; import { Store, select } from "@ngrx/store"; -import { ViewerStateInterface, safeFilter, SELECT_REGIONS, getLabelIndexMap, CHANGE_NAVIGATION, isDefined, MOUSE_OVER_SEGMENT, USER_LANDMARKS, ADD_NG_LAYER, REMOVE_NG_LAYER, NgViewerStateInterface, MOUSE_OVER_LANDMARK, SELECT_LANDMARKS, Landmark, PointLandmarkGeometry, PlaneLandmarkGeometry, OtherLandmarkGeometry } from "../../services/stateStore.service"; +import { ViewerStateInterface, safeFilter, CHANGE_NAVIGATION, isDefined, USER_LANDMARKS, ADD_NG_LAYER, REMOVE_NG_LAYER, NgViewerStateInterface, MOUSE_OVER_LANDMARK, SELECT_LANDMARKS, Landmark, PointLandmarkGeometry, PlaneLandmarkGeometry, OtherLandmarkGeometry, getNgIds, getMultiNgIdsRegionsLabelIndexMap, generateLabelIndexId } from "../../services/stateStore.service"; import { Observable, Subscription, fromEvent, combineLatest, merge } from "rxjs"; import { filter,map, take, scan, debounceTime, distinctUntilChanged, switchMap, skip, withLatestFrom, buffer, tap } from "rxjs/operators"; import { AtlasViewerAPIServices, UserLandmark } from "../../atlasViewer/atlasViewer.apiService.service"; import { timedValues } from "../../util/generator"; -import { AtlasViewerDataService } from "../../atlasViewer/atlasViewer.dataService.service"; import { AtlasViewerConstantsServices } from "../../atlasViewer/atlasViewer.constantService.service"; import { ViewerConfiguration } from "src/services/state/viewerConfig.store"; import { pipeFromArray } from "rxjs/internal/util/pipe"; import { NEHUBA_READY } from "src/services/state/ngViewerState.store"; +import { MOUSE_OVER_SEGMENTS } from "src/services/state/uiState.store"; +import { SELECT_REGIONS_WITH_ID } from "src/services/state/viewerState.store"; @Component({ selector : 'ui-nehuba-container', @@ -57,7 +58,7 @@ export class NehubaContainer implements OnInit, OnDestroy{ private spatialResultsVisible : boolean = false private selectedTemplate : any | null - private selectedRegionIndexSet : Set<number> = new Set() + private selectedRegionIndexSet : Set<string> = new Set() public fetchedSpatialData : Landmark[] = [] private ngLayersRegister : Partial<NgViewerStateInterface> = {layers : [], forceShowSegment: null} @@ -67,7 +68,7 @@ export class NehubaContainer implements OnInit, OnDestroy{ private cr : ComponentRef<NehubaViewerUnit> public nehubaViewer : NehubaViewerUnit - private regionsLabelIndexMap : Map<number,any> = new Map() + private multiNgIdsRegionsLabelIndexMap: Map<string, Map<number, any>> = new Map() private landmarksLabelIndexMap : Map<number, any> = new Map() private landmarksNameMap : Map<string,number> = new Map() @@ -81,7 +82,6 @@ export class NehubaContainer implements OnInit, OnDestroy{ constructor( private constantService : AtlasViewerConstantsServices, - private atlasViewerDataService : AtlasViewerDataService, private apiService :AtlasViewerAPIServices, private csf:ComponentFactoryResolver, private store : Store<ViewerStateInterface>, @@ -118,8 +118,8 @@ export class NehubaContainer implements OnInit, OnDestroy{ this.selectedRegions$ = this.store.pipe( select('viewerState'), - safeFilter('regionsSelected'), - map(state=>state.regionsSelected) + select('regionsSelected'), + filter(rs => !!rs) ) this.selectedLandmarks$ = this.store.pipe( @@ -267,15 +267,18 @@ export class NehubaContainer implements OnInit, OnDestroy{ const e = (event as any) const lastLoadedIdString = e.detail.lastLoadedMeshId.split(',')[0] const lastLoadedIdNum = Number(lastLoadedIdString) + /** + * TODO dig into event detail to see if the exact mesh loaded + */ return e.detail.meshesLoaded >= this.nehubaViewer.numMeshesToBeLoaded ? null : isNaN(lastLoadedIdNum) ? 'Loading unknown chunk' : lastLoadedIdNum >= 65500 ? 'Loading auxiliary chunk' - : this.regionsLabelIndexMap.get(lastLoadedIdNum) - ? `Loading ${this.regionsLabelIndexMap.get(lastLoadedIdNum).name}` - : 'Loading unknown chunk ...' + // : this.regionsLabelIndexMap.get(lastLoadedIdNum) + // ? `Loading ${this.regionsLabelIndexMap.get(lastLoadedIdNum).name}` + : 'Loading meshes ...' }) ) @@ -452,7 +455,7 @@ export class NehubaContainer implements OnInit, OnDestroy{ if(!this.nehubaViewer) return /* selectedregionindexset needs to be updated regardless of forceshowsegment */ - this.selectedRegionIndexSet = new Set(regions.map(r=>Number(r.labelIndex))) + this.selectedRegionIndexSet = new Set(regions.map(({ngId, labelIndex})=>generateLabelIndexId({ ngId, labelIndex }))) if( forceShowSegment === false || (forceShowSegment === null && hideSegmentFlag) ){ this.nehubaViewer.hideAllSeg() @@ -506,7 +509,7 @@ export class NehubaContainer implements OnInit, OnDestroy{ Object.assign({},navigation,{ positionReal : true }) - this.nehubaViewer.initRegions = regions.map(re=>re.labelIndex) + this.nehubaViewer.initRegions = regions.map(({ ngId, labelIndex }) =>generateLabelIndexId({ ngId, labelIndex })) }) this.subscriptions.push( @@ -652,11 +655,18 @@ export class NehubaContainer implements OnInit, OnDestroy{ if ( !(parcellation && parcellation.regions)) { return } - this.regionsLabelIndexMap = getLabelIndexMap(parcellation.regions) - this.nehubaViewer.regionsLabelIndexMap = this.regionsLabelIndexMap + + /** + * first, get all all the ngIds, including parent id from parcellation (if defined) + */ + const ngIds = getNgIds(parcellation.regions).concat( parcellation.ngId ? parcellation.ngId : []) + + this.multiNgIdsRegionsLabelIndexMap = getMultiNgIdsRegionsLabelIndexMap(parcellation) + + this.nehubaViewer.multiNgIdsLabelIndexMap = this.multiNgIdsRegionsLabelIndexMap /* TODO replace with proper KG id */ - this.nehubaViewer.parcellationId = parcellation.ngId + this.nehubaViewer.ngIds = [...ngIds] this.selectedParcellation = parcellation } @@ -718,11 +728,34 @@ export class NehubaContainer implements OnInit, OnDestroy{ ).subscribe(this.handleNavigationPositionAndNavigationZoomChange.bind(this)) ) + const accumulatorFn: ( + acc:Map<string, { segment: string | null, segmentId: number | null }>, + arg: {layer: {name: string}, segmentId: number|null, segment: string | null} + ) => Map<string, {segment: string | null, segmentId: number|null}> + = (acc, arg) => { + const { layer, segment, segmentId } = arg + const { name } = layer + const newMap = new Map(acc) + newMap.set(name, {segment, segmentId}) + return newMap + } + this.nehubaViewerSubscriptions.push( - this.nehubaViewer.mouseoverSegmentEmitter.subscribe(emitted => { + + this.nehubaViewer.mouseoverSegmentEmitter.pipe( + scan(accumulatorFn, new Map()), + map(map => Array.from(map.entries()).filter(([_ngId, { segmentId }]) => segmentId)) + ).subscribe(arrOfArr => { this.store.dispatch({ - type : MOUSE_OVER_SEGMENT, - segment : emitted + type: MOUSE_OVER_SEGMENTS, + segments: arrOfArr.map( ([ngId, {segment, segmentId}]) => { + return { + layer: { + name: ngId, + }, + segment: segment || `${ngId}#${segmentId}` + } + } ) }) }) ) @@ -736,26 +769,41 @@ export class NehubaContainer implements OnInit, OnDestroy{ }) ) + const onhoverSegments$ = this.store.pipe( + select('uiState'), + select('mouseOverSegments'), + filter(v => !!v), + distinctUntilChanged((o, n) => o.length === n.length && n.every(segment => o.find(oSegment => oSegment.layer.name === segment.layer.name && oSegment.segment === segment.segment) ) ) + ) + // TODO hack, even though octant is hidden, it seems with vtk one can mouse on hover this.nehubaViewerSubscriptions.push( this.nehubaViewer.regionSelectionEmitter.pipe( withLatestFrom(this.onHoverLandmark$), filter(results => results[1] === null), - map(results => results[0]) - ).subscribe((region:any) => { - /** - * TODO - * region may have labelIndex as well as children - */ - this.selectedRegionIndexSet.has(region.labelIndex) ? - this.store.dispatch({ - type : SELECT_REGIONS, - selectRegions : [...this.selectedRegionIndexSet].filter(idx=>idx!==region.labelIndex).map(idx=>this.regionsLabelIndexMap.get(idx)) - }) : - this.store.dispatch({ - type : SELECT_REGIONS, - selectRegions : [...this.selectedRegionIndexSet].map(idx=>this.regionsLabelIndexMap.get(idx)).concat(region) + withLatestFrom(onhoverSegments$), + map(results => results[1]), + filter(arr => arr.length > 0), + map(arr => { + return arr.map(({ layer, segment }) => { + const ngId = segment.ngId || layer.name + const labelIndex = segment.labelIndex + return generateLabelIndexId({ ngId, labelIndex }) }) + }) + ).subscribe((ids:string[]) => { + const deselectFlag = ids.some(id => this.selectedRegionIndexSet.has(id)) + + const set = new Set(this.selectedRegionIndexSet) + if (deselectFlag) { + ids.forEach(id => set.delete(id)) + } else { + ids.forEach(id => set.add(id)) + } + this.store.dispatch({ + type: SELECT_REGIONS_WITH_ID, + selectRegionIds: [...set] + }) }) ) @@ -781,11 +829,16 @@ export class NehubaContainer implements OnInit, OnDestroy{ orientation : quat }), showSegment : (labelIndex) => { - if(!this.selectedRegionIndexSet.has(labelIndex)) - this.store.dispatch({ - type : SELECT_REGIONS, - selectRegions : [labelIndex, ...this.selectedRegionIndexSet] - }) + /** + * TODO reenable with updated select_regions api + */ + console.warn(`showSegment is temporarily disabled`) + + // if(!this.selectedRegionIndexSet.has(labelIndex)) + // this.store.dispatch({ + // type : SELECT_REGIONS, + // selectRegions : [labelIndex, ...this.selectedRegionIndexSet] + // }) }, add3DLandmarks : landmarks => { // TODO check uniqueness of ID @@ -807,22 +860,33 @@ export class NehubaContainer implements OnInit, OnDestroy{ }) }, hideSegment : (labelIndex) => { - if(this.selectedRegionIndexSet.has(labelIndex)){ - this.store.dispatch({ - type :SELECT_REGIONS, - selectRegions : [...this.selectedRegionIndexSet].filter(num=>num!==labelIndex) - }) - } + /** + * TODO reenable with updated select_regions api + */ + console.warn(`hideSegment is temporarily disabled`) + + // if(this.selectedRegionIndexSet.has(labelIndex)){ + // this.store.dispatch({ + // type :SELECT_REGIONS, + // selectRegions : [...this.selectedRegionIndexSet].filter(num=>num!==labelIndex) + // }) + // } }, showAllSegments : () => { + const selectRegionIds = [] + this.multiNgIdsRegionsLabelIndexMap.forEach((map, ngId) => { + Array.from(map.keys()).forEach(labelIndex => { + selectRegionIds.push(generateLabelIndexId({ ngId, labelIndex })) + }) + }) this.store.dispatch({ - type : SELECT_REGIONS, - selectRegions : this.regionsLabelIndexMap.keys() + type : SELECT_REGIONS_WITH_ID, + selectRegionIds }) }, hideAllSegments : () => { this.store.dispatch({ - type : SELECT_REGIONS, + type : SELECT_REGIONS_WITH_ID, selectRegions : [] }) }, diff --git a/src/ui/nehubaContainer/nehubaViewer/nehubaViewer.component.ts b/src/ui/nehubaContainer/nehubaViewer/nehubaViewer.component.ts index a15fd44271abe3d06b0db3fcd667ce6ae0a6b6b2..76de48f81207eb0951fc598f322bdfca9f59ce05 100644 --- a/src/ui/nehubaContainer/nehubaViewer/nehubaViewer.component.ts +++ b/src/ui/nehubaContainer/nehubaViewer/nehubaViewer.component.ts @@ -8,7 +8,11 @@ import { AtlasViewerConstantsServices } from "../../../atlasViewer/atlasViewer.c import { takeOnePipe, identifySrcElement } from "../nehubaContainer.component"; import { ViewerConfiguration } from "src/services/state/viewerConfig.store"; import { pipeFromArray } from "rxjs/internal/util/pipe"; +import { getNgIdLabelIndexFromId } from "src/services/stateStore.service"; +/** + * no selector is needed, as currently, nehubaviewer is created dynamically + */ @Component({ templateUrl : './nehubaViewer.template.html', styleUrls : [ @@ -20,9 +24,17 @@ export class NehubaViewerUnit implements OnDestroy{ @Output() nehubaReady: EventEmitter<null> = new EventEmitter() @Output() debouncedViewerPositionChange : EventEmitter<any> = new EventEmitter() - @Output() mouseoverSegmentEmitter : EventEmitter<any | number | null> = new EventEmitter() + @Output() mouseoverSegmentEmitter: + EventEmitter<{ + segmentId: number | null, + segment:string | null, + layer:{ + name?: string, + url?: string + } + }> = new EventEmitter() @Output() mouseoverLandmarkEmitter : EventEmitter<number | null> = new EventEmitter() - @Output() regionSelectionEmitter : EventEmitter<any> = new EventEmitter() + @Output() regionSelectionEmitter : EventEmitter<{segment:number, layer:{name?: string, url?: string}}> = new EventEmitter() @Output() errorEmitter : EventEmitter<any> = new EventEmitter() /* only used to set initial navigation state */ @@ -47,6 +59,7 @@ export class NehubaViewerUnit implements OnDestroy{ _s6$ : any _s7$ : any _s8$ : any + _s9$ : any _s$ : any[] = [ this._s1$, @@ -56,7 +69,8 @@ export class NehubaViewerUnit implements OnDestroy{ this._s5$, this._s6$, this._s7$, - this._s8$ + this._s8$, + this._s9$ ] ondestroySubscriptions: any[] = [] @@ -223,9 +237,11 @@ export class NehubaViewerUnit implements OnDestroy{ } /* if the active parcellation is the current parcellation, load the said mesh */ - if(baseUrl === this._baseUrl){ - this.nehubaViewer.setMeshesToLoad([...this.workerService.safeMeshSet.get(this._baseUrl)], { - name : this.parcellationId + const baseUrlIsInLoadedBaseUrlList = new Set([...this._baseUrls]).has(baseUrl) + const baseUrlParcellationId = this._baseUrlToParcellationIdMap.get(baseUrl) + if( baseUrlIsInLoadedBaseUrlList && baseUrlParcellationId){ + this.nehubaViewer.setMeshesToLoad([...this.workerService.safeMeshSet.get(baseUrl)], { + name : baseUrlParcellationId }) } }) @@ -233,15 +249,17 @@ export class NehubaViewerUnit implements OnDestroy{ ) } - private _baseUrl : string + private _baseUrlToParcellationIdMap:Map<string, string> = new Map() + private _baseUrls: string[] = [] + get numMeshesToBeLoaded():number{ - if(!this._baseUrl) + if(!this._baseUrls || this._baseUrls.length === 0) return 0 - const set = this.workerService.safeMeshSet.get(this._baseUrl) - return set - ? set.size - : 0 + return this._baseUrls.reduce((acc, curr) => { + const mappedSet = this.workerService.safeMeshSet.get(curr) + return acc + ((mappedSet && mappedSet.size) || 0) + } ,0) } public applyPerformanceConfig ({ gpuLimit }: Partial<ViewerConfiguration>) { @@ -262,20 +280,23 @@ export class NehubaViewerUnit implements OnDestroy{ this._templateId = id } - /* required to check if the correct meshes are being loaded */ - private _parcellationId : string - get parcellationId(){ - return this._parcellationId + /** compatible with multiple parcellation id selection */ + private _ngIds: string[] = [] + get ngIds(){ + return this._ngIds } - set parcellationId(id:string){ - if(this._parcellationId && this.nehubaViewer){ - const oldlayer = this.nehubaViewer.ngviewer.layerManager.getLayerByName(this._parcellationId) - if(oldlayer)oldlayer.setVisible(false) - else console.warn('could not find old layer',this.parcellationId) + set ngIds(val:string[]){ + + if(this.nehubaViewer){ + this._ngIds.forEach(id => { + const oldlayer = this.nehubaViewer.ngviewer.layerManager.getLayerByName(id) + if(oldlayer)oldlayer.setVisible(false) + else console.warn('could not find old layer', id) + }) } - this._parcellationId = id + this._ngIds = val if(this.nehubaViewer) this.loadNewParcellation() @@ -302,7 +323,7 @@ export class NehubaViewerUnit implements OnDestroy{ } } - regionsLabelIndexMap : Map<number,any> + multiNgIdsLabelIndexMap: Map<string, Map<number, any>> navPosReal : [number,number,number] = [0,0,0] navPosVoxel : [number,number,number] = [0,0,0] @@ -312,8 +333,9 @@ export class NehubaViewerUnit implements OnDestroy{ viewerState : ViewerState - private defaultColormap : Map<number,{red:number,green:number,blue:number}> - public mouseOverSegment : number | null + private multiNgIdColorMap: Map<string, Map<number, {red: number, green:number, blue: number}>> + public mouseOverSegment: number | null + public mouseOverLayer: {name:string,url:string}| null private viewportToDatas : [any, any, any] = [null, null, null] @@ -327,11 +349,18 @@ export class NehubaViewerUnit implements OnDestroy{ console.log(err) }) - if(this.regionsLabelIndexMap){ - const managedLayers = this.nehubaViewer.ngviewer.layerManager.managedLayers - managedLayers.slice(1).forEach(layer=>layer.setVisible(false)) - this.nehubaViewer.redraw() - } + /** + * Hide all layers except the base layer (template) + * Then show the layers referenced in multiNgIdLabelIndexMap + */ + const managedLayers = this.nehubaViewer.ngviewer.layerManager.managedLayers + managedLayers.slice(1).forEach(layer=>layer.setVisible(false)) + Array.from(this.multiNgIdsLabelIndexMap.keys()).forEach(ngId => { + const layer = this.nehubaViewer.ngviewer.layerManager.getLayerByName(ngId) + if (layer) layer.setVisible(true) + else console.log('layer unavailable', ngId) + }) + this.nehubaViewer.redraw() /* creation of the layout is done on next frame, hence the settimeout */ setTimeout(() => { @@ -470,9 +499,15 @@ export class NehubaViewerUnit implements OnDestroy{ /* TODO find a more permanent fix to disable double click */ LayerManager.prototype.invokeAction = (arg) => { - const region = this.regionsLabelIndexMap.get(this.mouseOverSegment) - if (arg === 'select' && region) { - this.regionSelectionEmitter.emit(region) + + /** + * The emitted value does not affect the region selection + * the region selection is taken care of in nehubaContainer + */ + const map = this.multiNgIdsLabelIndexMap.get(this.mouseOverLayer.name) + const region = map && map.get(this.mouseOverSegment) + if (arg === 'select') { + this.regionSelectionEmitter.emit({ segment: region, layer: this.mouseOverLayer }) } } @@ -582,35 +617,78 @@ export class NehubaViewerUnit implements OnDestroy{ public hideAllSeg(){ if(!this.nehubaViewer) return - Array.from(this.regionsLabelIndexMap.keys()).forEach(idx=> - this.nehubaViewer.hideSegment(idx,{ - name : this.parcellationId - })) - this.nehubaViewer.showSegment(0,{ - name : this.parcellationId + Array.from(this.multiNgIdsLabelIndexMap.keys()).forEach(ngId => { + + Array.from(this.multiNgIdsLabelIndexMap.get(ngId).keys()).forEach(idx => { + this.nehubaViewer.hideSegment(idx, { + name: ngId + }) + }) + this.nehubaViewer.showSegment(0, { + name: ngId + }) }) } public showAllSeg(){ if(!this.nehubaViewer) return this.hideAllSeg() - this.nehubaViewer.hideSegment(0,{ - name : this.parcellationId + Array.from(this.multiNgIdsLabelIndexMap.keys()).forEach(ngId => { + this.nehubaViewer.hideSegment(0,{ + name: ngId + }) }) } - public showSegs(array:number[]){ + public showSegs(array: number[] | string[]){ + if(!this.nehubaViewer) return + this.hideAllSeg() - this.nehubaViewer.hideSegment(0,{ - name : this.parcellationId - }) + + if (array.length === 0) return + + /** + * TODO tobe deprecated + */ + + if (typeof array[0] === 'number') { + console.warn(`show seg with number indices has been deprecated`) + return + } + + const reduceFn:(acc:Map<string,number[]>,curr:string)=>Map<string,number[]> = (acc, curr) => { - array.forEach(idx=> - this.nehubaViewer.showSegment(idx,{ - name : this.parcellationId - })) + const newMap = new Map(acc) + const { ngId, labelIndex } = getNgIdLabelIndexFromId({ labelIndexId: curr }) + const exist = newMap.get(ngId) + if (!exist) newMap.set(ngId, [Number(labelIndex)]) + else newMap.set(ngId, [...exist, Number(labelIndex)]) + return newMap + } + + /** + * TODO + * AAAAAAARG TYPESCRIPT WHY YOU SO MAD + */ + //@ts-ignore + const newMap:Map<string, number[]> = array.reduce(reduceFn, new Map()) + + /** + * TODO + * ugh, ugly code. cleanify + */ + newMap.forEach((segs, ngId) => { + this.nehubaViewer.hideSegment(0, { + name: ngId + }) + segs.forEach(seg => { + this.nehubaViewer.showSegment(seg, { + name: ngId + }) + }) + }) } private vec3(pos:[number,number,number]){ @@ -658,17 +736,22 @@ export class NehubaViewerUnit implements OnDestroy{ this.nehubaViewer.ngviewer.navigationState.pose.rotateRelative(this.vec3([0, 0, 1]), amount / 4.0 * Math.PI / 180.0) } - private updateColorMap(arrayIdx:number[]){ + /** + * + * @param arrayIdx label indices of the shown segment(s) + * @param ngId segmentation layer name + */ + private updateColorMap(arrayIdx:number[], ngId: string){ const set = new Set(arrayIdx) const newColorMap = new Map( - Array.from(this.defaultColormap.entries()) + Array.from(this.multiNgIdColorMap.get(ngId).entries()) .map(v=> set.has(v[0]) || set.size === 0 ? v : [v[0],{red:255,green:255,blue:255}]) as any ) this.nehubaViewer.batchAddAndUpdateSegmentColors(newColorMap,{ - name:this.parcellationId + name: ngId }) } @@ -677,7 +760,10 @@ export class NehubaViewerUnit implements OnDestroy{ /* isn't this layer specific? */ /* TODO this is layer specific. need a way to distinguish between different segmentation layers */ this._s2$ = this.nehubaViewer.mouseOver.segment - .subscribe(obj=>this.mouseOverSegment = obj.segment) + .subscribe(({ segment, layer })=>{ + this.mouseOverSegment = segment + this.mouseOverLayer = { ...layer } + }) if(this.initNav){ this.setNavigationState(this.initNav) @@ -693,12 +779,23 @@ export class NehubaViewerUnit implements OnDestroy{ this.hideAllSeg() } - this._s8$ = this.nehubaViewer.mouseOver.segment.subscribe(({segment, ...rest})=>{ - if( segment && segment < 65500 ) { - const region = this.regionsLabelIndexMap.get(segment) - this.mouseoverSegmentEmitter.emit(region ? region : segment) + this._s8$ = this.nehubaViewer.mouseOver.segment.subscribe(({segment: segmentId, layer, ...rest})=>{ + + const {name = 'unnamed'} = layer + if( segmentId && segmentId < 65500 ) { + const map = this.multiNgIdsLabelIndexMap.get(name) + const region = map && map.get(segmentId) + this.mouseoverSegmentEmitter.emit({ + layer, + segment: region, + segmentId + }) }else{ - this.mouseoverSegmentEmitter.emit(null) + this.mouseoverSegmentEmitter.emit({ + layer, + segment: null, + segmentId + }) } }) @@ -770,72 +867,97 @@ export class NehubaViewerUnit implements OnDestroy{ private loadNewParcellation(){ /* show correct segmentation layer */ - - const newlayer = this.nehubaViewer.ngviewer.layerManager.getLayerByName(this.parcellationId) - if(newlayer)newlayer.setVisible(true) - else console.warn('could not find new layer',this.parcellationId) - - const regex = /^(\S.*?)\:\/\/(.*?)$/.exec(newlayer.sourceUrl) - - if(!regex || !regex[2]){ - console.error('could not parse baseUrl') - return - } - if(regex[1] !== 'precomputed'){ - console.error('sourceUrl is not precomputed') - return - } - - const baseUrl = regex[2] - this._baseUrl = baseUrl - - /* load meshes */ - /* TODO could be a bug where user loads new parcellation, but before the worker could completely populate the list */ - const set = this.workerService.safeMeshSet.get(baseUrl) - if(set){ - this.nehubaViewer.setMeshesToLoad([...set],{ - name : this.parcellationId - }) - }else{ - if(newlayer){ - this.zone.runOutsideAngular(() => - this.workerService.worker.postMessage({ - type : 'CHECK_MESHES', - parcellationId : this.parcellationId, - baseUrl, - indices : [ - ...Array.from(this.regionsLabelIndexMap.keys()), - ...getAuxilliaryLabelIndices() - ] - }) - ) + this._baseUrls = [] + + this.ngIds.map(id => { + const newlayer = this.nehubaViewer.ngviewer.layerManager.getLayerByName(id) + if(newlayer)newlayer.setVisible(true) + else console.warn('could not find new layer',id) + + const regex = /^(\S.*?)\:\/\/(.*?)$/.exec(newlayer.sourceUrl) + + if(!regex || !regex[2]){ + console.error('could not parse baseUrl') + return } - } + if(regex[1] !== 'precomputed'){ + console.error('sourceUrl is not precomputed') + return + } + + const baseUrl = regex[2] + this._baseUrls.push(baseUrl) + this._baseUrlToParcellationIdMap.set(baseUrl, id) + + /* load meshes */ + /* TODO could be a bug where user loads new parcellation, but before the worker could completely populate the list */ + const set = this.workerService.safeMeshSet.get(baseUrl) + if(set){ + this.nehubaViewer.setMeshesToLoad([...set],{ + name : id + }) + }else{ + if(newlayer){ + this.zone.runOutsideAngular(() => + this.workerService.worker.postMessage({ + type : 'CHECK_MESHES', + parcellationId : id, + baseUrl, + indices : [ + ...Array.from(this.multiNgIdsLabelIndexMap.get(id).keys()), + ...getAuxilliaryLabelIndices() + ] + }) + ) + } + } + }) - this.defaultColormap = new Map( - Array.from( - [ - ...this.regionsLabelIndexMap.entries(), - ...getAuxilliaryLabelIndices().map(i=>([ - i, {} - ])) - - ] - ).map((val:[number,any])=>([val[0],this.getRgb(val[0],val[1].rgb)])) as any) + const obj = Array.from(this.multiNgIdsLabelIndexMap.keys()).map(ngId => { + return [ + ngId, + new Map(Array.from( + [ + ...this.multiNgIdsLabelIndexMap.get(ngId).entries(), + ...getAuxilliaryLabelIndices().map(i => { + return [i, {}] + }) + ] + ).map((val:[number,any])=>([val[0],this.getRgb(val[0],val[1].rgb)])) as any) + ] + }) as [string, Map<number, {red:number, green: number, blue: number}>][] + + this.multiNgIdColorMap = new Map(obj) /* load colour maps */ - this.nehubaViewer.batchAddAndUpdateSegmentColors( - this.defaultColormap, - { name : this.parcellationId }) + + Array.from(this.multiNgIdColorMap.entries()).forEach(([ngId, map]) => { + + this.nehubaViewer.batchAddAndUpdateSegmentColors( + map, + { name : ngId }) + }) this._s$.forEach(_s$=>{ if(_s$) _s$.unsubscribe() }) if(this._s1$)this._s1$.unsubscribe() - this._s1$ = this.nehubaViewer.getShownSegmentsObservable({ - name : this.parcellationId - }).subscribe(arrayIdx=>this.updateColorMap(arrayIdx)) + if(this._s9$)this._s9$.unsubscribe() + + const arr = Array.from(this.multiNgIdsLabelIndexMap.keys()).map(ngId => { + return this.nehubaViewer.getShownSegmentsObservable({ + name: ngId + }).subscribe(arrayIdx => this.updateColorMap(arrayIdx, ngId)) + }) + + this._s9$ = { + unsubscribe: () => { + while(arr.length > 0) { + arr.pop().unsubscribe() + } + } + } } private getRgb(labelIndex:number,rgb?:number[]):{red:number,green:number,blue:number}{ diff --git a/src/ui/regionHierachy/regionHierarchy.component.ts b/src/ui/regionHierachy/regionHierarchy.component.ts index 0555327a6837ba8b66db4e4d1d934f06803cc02e..d5992b28c0ed7503d144de66f96fc0de9c211277 100644 --- a/src/ui/regionHierachy/regionHierarchy.component.ts +++ b/src/ui/regionHierachy/regionHierarchy.component.ts @@ -2,6 +2,7 @@ import { EventEmitter, Component, ElementRef, ViewChild, HostListener, OnInit, C import { Subscription, Subject, fromEvent } from "rxjs"; import { buffer, debounceTime } from "rxjs/operators"; import { FilterNameBySearch } from "./filterNameBySearch.pipe"; +import { generateLabelIndexId } from "src/services/stateStore.service"; const insertHighlight :(name:string, searchTerm:string) => string = (name:string, searchTerm:string = '') => { const regex = new RegExp(searchTerm, 'gi') @@ -10,10 +11,14 @@ const insertHighlight :(name:string, searchTerm:string) => string = (name:string 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 getDisplayTreeNode : (searchTerm:string, selectedRegions:any[]) => (item:any) => string = (searchTerm:string = '', selectedRegions:any[] = []) => ({ ngId, name, labelIndex }) => { + return !!labelIndex + && !!ngId + && selectedRegions.findIndex(re => + generateLabelIndexId({ labelIndex: re.labelIndex, ngId: re.ngId }) === generateLabelIndexId({ ngId, labelIndex }) + ) >= 0 + ? `<span class="regionSelected">${insertHighlight(name, searchTerm)}</span>` + : `<span class="regionNotSelected">${insertHighlight(name, searchTerm)}</span>` } const getFilterTreeBySearch = (pipe:FilterNameBySearch, searchTerm:string) => (node:any) => pipe.transform([node.name], searchTerm)