diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/ieeg/ieegCmp/ieeg.component.ts b/src/atlasComponents/regionalFeatures/bsFeatures/ieeg/ieegCmp/ieeg.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..8bb282c6f4759cf201d2742def25f449e37cd090 --- /dev/null +++ b/src/atlasComponents/regionalFeatures/bsFeatures/ieeg/ieegCmp/ieeg.component.ts @@ -0,0 +1,137 @@ +import { Component, Inject, OnDestroy, Optional } from "@angular/core"; +import { Store } from "@ngrx/store"; +import { BehaviorSubject, forkJoin, merge, Observable, of, Subscription } from "rxjs"; +import { catchError, mapTo, switchMap } from "rxjs/operators"; +import { viewerStateAddUserLandmarks, viewerStateChangeNavigation, viewreStateRemoveUserLandmarks } from "src/services/state/viewerState/actions"; +import { BsRegionInputBase } from "../../bsRegionInputBase"; +import { REGISTERED_FEATURE_INJECT_DATA } from "../../constants"; +import { BsFeatureService, TFeatureCmpInput } from "../../service"; +import { TBSDEtail, TBSSummary, SIIBRA_FEATURE_KEY, TContactPoint } from '../type' +import { ARIA_LABELS, CONST } from 'common/constants' + +@Component({ + selector: 'bs-feature-ieeg.cmp', + templateUrl: './ieeg.template.html', + styleUrls: [ + './ieeg.style.css' + ] +}) + +export class BsFeatureIEEGCmp extends BsRegionInputBase implements OnDestroy{ + + public ARIA_LABELS = ARIA_LABELS + public CONST = CONST + + private results: (TBSSummary & TBSDEtail)[] = [] + constructor( + private store: Store<any>, + svc: BsFeatureService, + @Optional() @Inject(REGISTERED_FEATURE_INJECT_DATA) data: TFeatureCmpInput, + ){ + super(svc, data) + + this.subs.push( + this.results$.subscribe(results => { + this.results = results + console.log(this.results[0]) + this.loadLandmarks() + }) + ) + } + + public results$: Observable<(TBSSummary & TBSDEtail)[]> = this.region$.pipe( + switchMap(() => this.getFeatureInstancesList(SIIBRA_FEATURE_KEY).pipe( + switchMap(arr => forkJoin(arr.map(it => this.getFeatureInstance(SIIBRA_FEATURE_KEY, it["@id"])))), + catchError(() => of([])) + )), + ) + + public busy$ = this.region$.pipe( + switchMap(() => merge( + of(true), + this.results$.pipe( + mapTo(false) + ) + )), + ) + + private subs: Subscription[] = [] + ngOnDestroy(){ + while(this.subs.length) this.subs.pop().unsubscribe() + } + private openElectrodeIdSet = new Set<string>() + public openElectrodeId$ = new BehaviorSubject<string[]>([]) + handleDatumExpansion(id: string, state: boolean) { + if (state) this.openElectrodeIdSet.add(id) + else this.openElectrodeIdSet.delete(id) + this.openElectrodeId$.next(Array.from(this.openElectrodeIdSet)) + this.loadLandmarks() + } + + private loadedLms: { + '@id': string + id: string + name: string + position: [number, number, number] + color: [number, number, number] + showInSliceView: boolean + }[] = [] + loadLandmarks(){ + + /** + * unload all the landmarks first + */ + this.store.dispatch( + viewreStateRemoveUserLandmarks({ + payload: { + landmarkIds: this.loadedLms.map(l => l['@id']) + } + }) + ) + this.loadedLms = [] + + const lms = [] as { + '@id': string + id: string + name: string + position: [number, number, number] + color: [number, number, number] + showInSliceView: boolean + }[] + + for (const detail of this.results) { + for (const key in detail.__contact_points){ + const cp = detail.__contact_points[key] + lms.push({ + "@id": `${detail["@id"]}#${key}`, + id: `${detail["@id"]}#${key}`, + name: `${detail.name}#${key}`, + position: cp.coord, + color: cp.inRoi ? [255, 100, 100]: [255,255,255], + showInSliceView: this.openElectrodeIdSet.has(detail["@id"]) + }) + } + } + + this.loadedLms = lms + + this.store.dispatch( + viewerStateAddUserLandmarks({ + landmarks: lms + }) + ) + } + + handleContactPtClk(cp: TContactPoint) { + const { coord } = cp + this.store.dispatch( + viewerStateChangeNavigation({ + navigation: { + position: coord.map(v => v * 1e6), + positionReal: true, + animation: {} + }, + }) + ) + } +} diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/ieeg/ieegCmp/ieeg.style.css b/src/atlasComponents/regionalFeatures/bsFeatures/ieeg/ieegCmp/ieeg.style.css new file mode 100644 index 0000000000000000000000000000000000000000..d049e6fac05e2f3a611adf9a5f4fa4a731ed62d4 --- /dev/null +++ b/src/atlasComponents/regionalFeatures/bsFeatures/ieeg/ieegCmp/ieeg.style.css @@ -0,0 +1,5 @@ +mat-expansion-panel +{ + margin-left: -24px; + margin-right: -24px; +} diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/ieeg/ieegCmp/ieeg.template.html b/src/atlasComponents/regionalFeatures/bsFeatures/ieeg/ieegCmp/ieeg.template.html new file mode 100644 index 0000000000000000000000000000000000000000..575b6632f9ca47bb9f908d7334255db4c00ecbdb --- /dev/null +++ b/src/atlasComponents/regionalFeatures/bsFeatures/ieeg/ieegCmp/ieeg.template.html @@ -0,0 +1,71 @@ +<ng-template [ngIf]="busy$ | async" [ngIfElse]="contenttmpl"> + <spinner-cmp></spinner-cmp> +</ng-template> + +<ng-template #contenttmpl> + <mat-expansion-panel *ngFor="let datum of (results$ | async)" + [expanded]="openElectrodeId$ | async | arrayContains : datum['@id']" + (opened)="handleDatumExpansion(datum['@id'], true)" + (closed)="handleDatumExpansion(datum['@id'], false)" + togglePosition="before"> + <mat-expansion-panel-header> + <mat-panel-title> + Electrode + </mat-panel-title> + <mat-panel-description class="text-nowrap"> + {{ datum['@id'] }} + </mat-panel-description> + </mat-expansion-panel-header> + + + <!-- <label for="task-list" class="d-block mat-h4 mt-4 text-muted"> + Tasks + </label> + <section class="d-flex align-items-center mt-1"> + <section id="task-list" class="flex-grow-1 flex-shrink-1 overflow-x-auto"> + <div role="list"> + <mat-chip *ngFor="let task of datum['tasks']" class="ml-1"> + {{ task }} + </mat-chip> + </div> + </section> + </section> --> + + <h4 class="mat-h4"> + {{ datum['name'] }} + </h4> + + <button + [matTooltip]="CONST.GDPR_TEXT" + mat-icon-button color="warn"> + <i class="fas fa-exclamation-triangle"></i> + </button> + + <a [href]="'https://search.kg.ebrains.eu/instances/' + datum.__kg_id" + mat-icon-button + color="primary" + target="_blank" + [matTooltip]="ARIA_LABELS.EXPLORE_DATASET_IN_KG" + [attr.aria-labels]="ARIA_LABELS.EXPLORE_DATASET_IN_KG"> + <i class="fas fa-external-link-alt"></i> + </a> + <mat-divider></mat-divider> + + <label for="contact-points-list" class="d-block mat-h4 mt-4 text-muted"> + Contact Points + </label> + <section class="d-flex align-items-center mt-1"> + <section id="contact-points-list" class="flex-grow-1 flex-shrink-1 overflow-x-auto"> + <div role="list"> + <mat-chip *ngFor="let item of datum['__contact_points'] | keyvalue" + [matTooltip]="item['value']['coord']" + (click)="handleContactPtClk(item['value'])" + class="ml-1"> + {{ item['key'] }} + </mat-chip> + </div> + </section> + </section> + + </mat-expansion-panel> +</ng-template> diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/ieeg/ieegCtrl.directive.ts b/src/atlasComponents/regionalFeatures/bsFeatures/ieeg/ieegCtrl.directive.ts new file mode 100644 index 0000000000000000000000000000000000000000..2fa03bf46adda065f6bf3121289464a2f988b93f --- /dev/null +++ b/src/atlasComponents/regionalFeatures/bsFeatures/ieeg/ieegCtrl.directive.ts @@ -0,0 +1,44 @@ +import { Directive, Inject, OnDestroy, Optional } from "@angular/core"; +import { merge, Observable, of, Subscription } from "rxjs"; +import { catchError, mapTo, switchMap } from "rxjs/operators"; +import { BsRegionInputBase } from "../bsRegionInputBase"; +import { REGISTERED_FEATURE_INJECT_DATA } from "../constants"; +import { BsFeatureService, TFeatureCmpInput } from "../service"; +import { IBSSummaryResponse, IRegionalFeatureReadyDirective } from "../type"; + +@Directive({ + selector: '[bs-features-ieeg-directive]', + exportAs: 'bsFeatureIeegDirective' +}) + +export class BsFeatureIEEGDirective extends BsRegionInputBase implements IRegionalFeatureReadyDirective, OnDestroy{ + + public results$: Observable<IBSSummaryResponse['IEEG_Electrode'][]> = this.region$.pipe( + switchMap(() => merge( + of([]), + this.getFeatureInstancesList('IEEG_Electrode').pipe( + catchError(() => of([])) + ) + )), + ) + public busy$ = this.region$.pipe( + switchMap(() => merge( + of(true), + this.results$.pipe( + mapTo(false) + ) + )) + ) + + constructor( + svc: BsFeatureService, + @Optional() @Inject(REGISTERED_FEATURE_INJECT_DATA) data: TFeatureCmpInput, + ){ + super(svc, data) + } + + private sub: Subscription[] = [] + ngOnDestroy(){ + while(this.sub.length) this.sub.pop().unsubscribe() + } +} diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/ieeg/module.ts b/src/atlasComponents/regionalFeatures/bsFeatures/ieeg/module.ts new file mode 100644 index 0000000000000000000000000000000000000000..7da09541463cd5b556f710948142d2d31b7a2ec5 --- /dev/null +++ b/src/atlasComponents/regionalFeatures/bsFeatures/ieeg/module.ts @@ -0,0 +1,32 @@ +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { ComponentsModule } from "src/components"; +import { AngularMaterialModule } from "src/ui/sharedModules/angularMaterial.module"; +import { UtilModule } from "src/util"; +import { BsFeatureService } from "../service"; +import { BsFeatureIEEGCmp } from "./ieegCmp/ieeg.component"; +import { BsFeatureIEEGDirective } from "./ieegCtrl.directive"; + +@NgModule({ + imports: [ + CommonModule, + ComponentsModule, + UtilModule, + AngularMaterialModule, + ], + declarations: [ + BsFeatureIEEGCmp, + BsFeatureIEEGDirective + ] +}) + +export class BsFeatureIEEGModule{ + constructor(svc: BsFeatureService){ + svc.registerFeature({ + name: 'iEEG recordings', + icon: 'fas fa-info', + View: BsFeatureIEEGCmp, + Ctrl: BsFeatureIEEGDirective + }) + } +} diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/ieeg/type.ts b/src/atlasComponents/regionalFeatures/bsFeatures/ieeg/type.ts new file mode 100644 index 0000000000000000000000000000000000000000..37454f35bdf675c887e0fe55526ab7a16d2e69bc --- /dev/null +++ b/src/atlasComponents/regionalFeatures/bsFeatures/ieeg/type.ts @@ -0,0 +1,19 @@ +export type TBSSummary = { + '@id': string + 'name': string +} + +export type TContactPoint = { + 'id': string + 'coord': [number, number, number] + 'inRoi'?: boolean +} + +export type TBSDEtail = { + '__kg_id': string + '__contact_points': { + [key: string]: TContactPoint + } +} + +export const SIIBRA_FEATURE_KEY = 'IEEG_Electrode' diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/module.ts b/src/atlasComponents/regionalFeatures/bsFeatures/module.ts index 1c104136c9457d109335164fa8c39ab524411e69..b9ac4e077667db3b29f21bb6150d12f283561de5 100644 --- a/src/atlasComponents/regionalFeatures/bsFeatures/module.ts +++ b/src/atlasComponents/regionalFeatures/bsFeatures/module.ts @@ -2,6 +2,7 @@ import { CommonModule } from "@angular/common"; import { NgModule } from "@angular/core"; import { ComponentsModule } from "src/components"; import { AngularMaterialModule } from "src/ui/sharedModules/angularMaterial.module"; +import { BsFeatureIEEGModule } from "./ieeg/module"; import { KgRegionalFeatureModule } from "./kgRegionalFeature"; import { BSFeatureReceptorModule } from "./receptor"; import { RegionalFeatureWrapperCmp } from "./regionalFeatureWrapper/regionalFeatureWrapper.component"; @@ -13,6 +14,7 @@ import { BsFeatureService } from "./service"; CommonModule, KgRegionalFeatureModule, BSFeatureReceptorModule, + BsFeatureIEEGModule, ComponentsModule, ], declarations: [ diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/service.ts b/src/atlasComponents/regionalFeatures/bsFeatures/service.ts index 84ea01bc96978dbc38c330664d182fafad11bcba..cfd479da5cb5db86ae3a563a60b813ff293a7b8e 100644 --- a/src/atlasComponents/regionalFeatures/bsFeatures/service.ts +++ b/src/atlasComponents/regionalFeatures/bsFeatures/service.ts @@ -5,6 +5,7 @@ import { shareReplay } from "rxjs/operators"; import { CachedFunction } from "src/util/fn"; import { BS_ENDPOINT } from "./constants"; import { IBSSummaryResponse, IBSDetailResponse, TRegion, IFeatureList, IRegionalFeatureReadyDirective } from './type' +import { SIIBRA_FEATURE_KEY as IEEG_FEATURE_KEY } from '../bsFeatures/ieeg/type' function processRegion(region: TRegion) { return `${region.name} ${region.status ? region.status : '' }` @@ -26,6 +27,10 @@ export type TRegisteredFeature<V = any> = { }) export class BsFeatureService{ + static SpaceFeatureSet = new Set([ + IEEG_FEATURE_KEY + ]) + public registeredFeatures: TRegisteredFeature[] = [] public registeredFeatures$ = new BehaviorSubject<TRegisteredFeature[]>(this.registeredFeatures) public getAllFeatures$ = this.http.get(`${this.bsEndpoint}/features`).pipe( @@ -40,13 +45,51 @@ export class BsFeatureService{ ) } + private getUrl(arg: { + atlasId: string + parcId: string + spaceId: string + region: TRegion + featureName: string + featureId?: string + }){ + const { + atlasId, + parcId, + spaceId, + region, + featureName, + featureId, + } = arg + + if (BsFeatureService.SpaceFeatureSet.has(featureName)) { + + const url = new URL(`${this.bsEndpoint}/atlases/${encodeURIComponent(atlasId)}/spaces/${encodeURIComponent(spaceId)}/features/${encodeURIComponent(featureName)}${ featureId ? ('/' + encodeURIComponent(featureId)) : '' }`) + url.searchParams.set('parcellation_id', parcId) + url.searchParams.set('region', processRegion(region)) + + return url.toString() + } + + if (!featureId) { + return `${this.bsEndpoint}/atlases/${encodeURIComponent(atlasId)}/parcellations/${encodeURIComponent(parcId)}/regions/${encodeURIComponent(processRegion(region))}/features/${encodeURIComponent(featureName)}` + } + return `${this.bsEndpoint}/atlases/${encodeURIComponent(atlasId)}/parcellations/${encodeURIComponent(parcId)}/regions/${encodeURIComponent(processRegion(region))}/features/${encodeURIComponent(featureName)}/${encodeURIComponent(featureId)}` + } + @CachedFunction({ serialization: (featureName, region) => `${featureName}::${processRegion(region)}` }) public getFeatures<T extends keyof IBSSummaryResponse>(featureName: T, region: TRegion){ const { context } = region - const { atlas, parcellation } = context - const url = `${this.bsEndpoint}/atlases/${encodeURIComponent(atlas["@id"])}/parcellations/${encodeURIComponent(parcellation['@id'])}/regions/${encodeURIComponent(processRegion(region))}/features/${encodeURIComponent(featureName)}` + const { atlas, parcellation, template } = context + const url = this.getUrl({ + atlasId: atlas['@id'], + parcId: parcellation['@id'], + region, + featureName, + spaceId: template['@id'] + }) return this.http.get<IBSSummaryResponse[T][]>( url @@ -60,10 +103,16 @@ export class BsFeatureService{ }) public getFeature<T extends keyof IBSDetailResponse>(featureName: T, region: TRegion, featureId: string) { const { context } = region - const { atlas, parcellation } = context - return this.http.get<IBSDetailResponse[T]>( - `${this.bsEndpoint}/atlases/${encodeURIComponent(atlas["@id"])}/parcellations/${encodeURIComponent(parcellation['@id'])}/regions/${encodeURIComponent(processRegion(region))}/features/${encodeURIComponent(featureName)}/${encodeURIComponent(featureId)}` - ).pipe( + const { atlas, parcellation, template } = context + const url = this.getUrl({ + atlasId: atlas['@id'], + parcId: parcellation['@id'], + spaceId: template['@id'], + region, + featureName, + featureId + }) + return this.http.get<IBSSummaryResponse[T]&IBSDetailResponse[T]>(url).pipe( shareReplay(1) ) } diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/type.ts b/src/atlasComponents/regionalFeatures/bsFeatures/type.ts index e3ef2050e376fad43346482b99d55d8878078722..ffc9751c79df694c76c45a30a717f4c2a191b7c8 100644 --- a/src/atlasComponents/regionalFeatures/bsFeatures/type.ts +++ b/src/atlasComponents/regionalFeatures/bsFeatures/type.ts @@ -1,6 +1,7 @@ import { IHasId } from "src/util/interfaces"; import { TBSDetail as TReceptorDetail, TBSSummary as TReceptorSummary } from "./receptor/type"; import { KG_REGIONAL_FEATURE_KEY, TBSDetail as TKGDetail, TBSSummary as TKGSummary } from './kgRegionalFeature/type' +import { SIIBRA_FEATURE_KEY, TBSSummary as TIEEGSummary, TBSDEtail as TIEEGDetail } from './ieeg/type' import { Observable } from "rxjs"; /** @@ -10,11 +11,13 @@ import { Observable } from "rxjs"; export interface IBSSummaryResponse { 'ReceptorDistribution': TReceptorSummary [KG_REGIONAL_FEATURE_KEY]: TKGSummary + [SIIBRA_FEATURE_KEY]: TIEEGSummary } export interface IBSDetailResponse { 'ReceptorDistribution': TReceptorDetail [KG_REGIONAL_FEATURE_KEY]: TKGDetail + [SIIBRA_FEATURE_KEY]: TIEEGDetail } export type TRegion = { diff --git a/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.service.spec.ts b/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.service.spec.ts index 3d2ae0f4fbaa47b75eb2f24d21dc155a9b481a89..e8e06c8522ff1a9009178d715be695c839fca8af 100644 --- a/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.service.spec.ts +++ b/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.service.spec.ts @@ -1,13 +1,12 @@ import { fakeAsync, TestBed, tick } from "@angular/core/testing" import { MockStore, provideMockStore } from "@ngrx/store/testing" -import { viewerStateSelectedParcellationSelector, viewerStateSelectedRegionsSelector, viewerStateSelectedTemplateSelector } from "src/services/state/viewerState/selectors" +import { viewerStateCustomLandmarkSelector, viewerStateSelectedParcellationSelector, viewerStateSelectedRegionsSelector, viewerStateSelectedTemplateSelector } from "src/services/state/viewerState/selectors" import { NehubaLayerControlService } from "./layerCtrl.service" import * as layerCtrlUtil from '../constants' import { hot } from "jasmine-marbles" import { IColorMap } from "./layerCtrl.util" import { debounceTime } from "rxjs/operators" import { ngViewerSelectorClearView, ngViewerSelectorLayers } from "src/services/state/ngViewerState.store.helper" -const util = require('common/util') describe('> layerctrl.service.ts', () => { describe('> NehubaLayerControlService', () => { @@ -28,6 +27,10 @@ describe('> layerctrl.service.ts', () => { layerCtrlUtil, 'getMultiNgIdsRegionsLabelIndexMap' ).and.returnValue(() => getMultiNgIdsRegionsLabelIndexMapReturnVal) + mockStore.overrideSelector(viewerStateCustomLandmarkSelector, []) + + mockStore.overrideSelector(viewerStateSelectedTemplateSelector, {}) + mockStore.overrideSelector(viewerStateSelectedParcellationSelector, {}) }) it('> can be init', () => { @@ -38,10 +41,6 @@ describe('> layerctrl.service.ts', () => { describe('> setColorMap$', () => { describe('> overwriteColorMap$ not firing', () => { describe('> template/parc has no aux meshes', () => { - beforeEach(() => { - mockStore.overrideSelector(viewerStateSelectedTemplateSelector, {}) - mockStore.overrideSelector(viewerStateSelectedParcellationSelector, {}) - }) it('> calls getMultiNgIdsRegionsLabelIndexMapReturn', () => { const service = TestBed.inject(NehubaLayerControlService) diff --git a/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.service.ts b/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.service.ts index a7df04ef14769d32ed0d0e92ebe8fc6a8cc6478c..6f2bcc2397fe99f410b9f4f0f96a1d068dff56b9 100644 --- a/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.service.ts +++ b/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.service.ts @@ -2,7 +2,7 @@ import { Inject, Injectable, OnDestroy, Optional } from "@angular/core"; import { select, Store } from "@ngrx/store"; import { BehaviorSubject, combineLatest, from, merge, Observable, of, Subject, Subscription } from "rxjs"; import { distinctUntilChanged, filter, map, shareReplay, switchMap, withLatestFrom } from "rxjs/operators"; -import { viewerStateSelectedParcellationSelector, viewerStateSelectedRegionsSelector, viewerStateSelectedTemplateSelector } from "src/services/state/viewerState/selectors"; +import { viewerStateCustomLandmarkSelector, viewerStateSelectedParcellationSelector, viewerStateSelectedRegionsSelector, viewerStateSelectedTemplateSelector } from "src/services/state/viewerState/selectors"; import { getRgb, IColorMap, INgLayerCtrl, INgLayerInterface, TNgLayerCtrl } from "./layerCtrl.util"; import { getMultiNgIdsRegionsLabelIndexMap } from "../constants"; import { IAuxMesh } from '../store' @@ -64,13 +64,13 @@ export class NehubaLayerControlService implements OnDestroy{ ), this.selectedTemplateSelector$.pipe( map(template => { - const { auxMeshes = [] } = template + const { auxMeshes = [] } = template || {} return getAuxMeshesAndReturnIColor(auxMeshes) }) ), this.selectedParcellation$.pipe( map(parc => { - const { auxMeshes = [] } = parc + const { auxMeshes = [] } = parc || {} return getAuxMeshesAndReturnIColor(auxMeshes) }) ), @@ -102,8 +102,8 @@ export class NehubaLayerControlService implements OnDestroy{ this.selectedParcellation$, ]).pipe( map(([ tmpl, parc ]) => { - const { auxMeshes: tmplAuxMeshes = [] as IAuxMesh[] } = tmpl - const { auxMeshes: parclAuxMeshes = [] as IAuxMesh[] } = parc + const { auxMeshes: tmplAuxMeshes = [] as IAuxMesh[] } = tmpl || {} + const { auxMeshes: parclAuxMeshes = [] as IAuxMesh[] } = parc || {} return [...tmplAuxMeshes, ...parclAuxMeshes] }) ) @@ -208,6 +208,32 @@ export class NehubaLayerControlService implements OnDestroy{ this.manualNgLayersControl$.next(payload) }) ) + + /** + * on custom landmarks loaded, set mesh transparency + */ + this.sub.push( + this.store$.pipe( + select(viewerStateCustomLandmarkSelector), + withLatestFrom(this.auxMeshes$) + ).subscribe(([landmarks, auxMeshes]) => { + + const payload: { + [key: string]: number + } = {} + const alpha = landmarks.length > 0 + ? 0.2 + : 1.0 + for (const auxMesh of auxMeshes) { + payload[auxMesh.ngId] = alpha + } + + this.manualNgLayersControl$.next({ + type: 'setLayerTransparency', + payload + }) + }) + ) } public activeColorMap: IColorMap diff --git a/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.util.ts b/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.util.ts index a815d3c682e50eb1b4797076cf238eb7917e0c83..f6fc08184e0d31d4ed9229ca6840b9981ac8c4f2 100644 --- a/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.util.ts +++ b/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.util.ts @@ -47,6 +47,9 @@ export interface INgLayerCtrl { update: { [key: string]: INgLayerInterface } + setLayerTransparency: { + [key: string]: number + } } export type TNgLayerCtrl<T extends keyof INgLayerCtrl> = { diff --git a/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.ts b/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.ts index 23adf8d1c91fae693f223a0c4cc2bc71eda542a8..3dfad4e558d68a7ca22f97c78d6c6985b8dc8157 100644 --- a/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.ts +++ b/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.ts @@ -375,6 +375,12 @@ export class NehubaViewerUnit implements OnInit, OnDestroy { const p = message as TNgLayerCtrl<'update'> this.updateLayer(p.payload) } + if (message.type === 'setLayerTransparency') { + const p = message as TNgLayerCtrl<'setLayerTransparency'> + for (const key in p.payload) { + this.setLayerTransparency(key, p.payload[key]) + } + } } }) ) @@ -862,6 +868,12 @@ export class NehubaViewerUnit implements OnInit, OnDestroy { } } + private setLayerTransparency(layerName: string, alpha: number) { + const layer = this.nehubaViewer.ngviewer.layerManager.getLayerByName(layerName) + if (!layer) return + layer.layer.displayState.objectAlpha.restoreState(alpha) + } + public setMeshTransparency(flag: boolean){ /** diff --git a/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.spec.ts b/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.spec.ts index 0686ddaaefa6239e2c35db18f7e038dab4b3c3d5..0afa1fda6c3ad59fab421629150d6afa5c5aab81 100644 --- a/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.spec.ts +++ b/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.spec.ts @@ -1,6 +1,7 @@ import { CommonModule } from "@angular/common" import { TestBed } from "@angular/core/testing" import { MockStore, provideMockStore } from "@ngrx/store/testing" +import { Subject } from "rxjs" import { ClickInterceptorService } from "src/glue" import { PANELS } from "src/services/state/ngViewerState/constants" import { ngViewerSelectorOctantRemoval, ngViewerSelectorPanelMode, ngViewerSelectorPanelOrder } from "src/services/state/ngViewerState/selectors" @@ -9,6 +10,7 @@ import { viewerStateSetSelectedRegions } from "src/services/state/viewerState/ac import { viewerStateCustomLandmarkSelector, viewerStateNavigationStateSelector, viewerStateSelectedRegionsSelector } from "src/services/state/viewerState/selectors" import { ClickInterceptor, CLICK_INTERCEPTOR_INJECTOR } from "src/util" import { NehubaLayerControlService } from "../layerCtrl.service" +import { NehubaMeshService } from "../mesh.service" import { NehubaGlueCmp } from "./nehubaViewerGlue.component" describe('> nehubaViewerGlue.component.ts', () => { @@ -22,7 +24,15 @@ describe('> nehubaViewerGlue.component.ts', () => { NehubaGlueCmp ], providers: [ - provideMockStore(), + /** + * TODO, figureout which dependency is selecting viewerState.parcellationSelected + * then remove the inital state + */ + provideMockStore({ + initialState: { + viewerState: {} + } + }), { provide: CLICK_INTERCEPTOR_INJECTOR, useFactory: (clickIntService: ClickInterceptorService) => { @@ -34,6 +44,19 @@ describe('> nehubaViewerGlue.component.ts', () => { deps: [ ClickInterceptorService ] + },{ + provide: NehubaLayerControlService, + useValue: { + setColorMap$: new Subject(), + visibleLayer$: new Subject(), + segmentVis$: new Subject(), + ngLayersController$: new Subject(), + } + }, { + provide: NehubaMeshService, + useValue: { + loadMeshes$: new Subject() + } } ] }).overrideComponent(NehubaGlueCmp, { diff --git a/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts b/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts index 2ac10c76f98958eb624e23b9f923b8f506e647a3..c071875bb5faba2dd631d73585b30e4f8a748fd5 100644 --- a/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts +++ b/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts @@ -1,4 +1,3 @@ -// import { Component, ElementRef, EventEmitter, Inject, Input, OnChanges, OnDestroy, Optional, Output, SimpleChanges, ViewChild } from "@angular/core"; import { AfterViewInit, Component, ElementRef, EventEmitter, Inject, Input, OnChanges, OnDestroy, Optional, Output, SimpleChanges, ViewChild } from "@angular/core"; import { select, Store } from "@ngrx/store"; import { asyncScheduler, combineLatest, fromEvent, merge, Observable, of, Subject } from "rxjs";