diff --git a/src/main.module.ts b/src/main.module.ts index 7c0b4504e7ac41bf2e358fea9c0f30777e595880..4084dfff617d595e92159587f68503c29aff8d3e 100644 --- a/src/main.module.ts +++ b/src/main.module.ts @@ -42,6 +42,7 @@ import {HttpClientModule} from "@angular/common/http"; import { EffectsModule } from "@ngrx/effects"; import { UseEffects } from "./services/effect/effect"; import { MatDialogModule, MatTabsModule } from "@angular/material"; +import { ViewerStateUseEffect } from "./services/state/viewerState.store"; @NgModule({ imports : [ @@ -62,6 +63,7 @@ import { MatDialogModule, MatTabsModule } from "@angular/material"; TooltipModule.forRoot(), TabsModule.forRoot(), EffectsModule.forRoot([ + ViewerStateUseEffect, UseEffects ]), StoreModule.forRoot({ diff --git a/src/services/state/viewerState.store.ts b/src/services/state/viewerState.store.ts index 0b1edc38bcf23d9001d9554392d83c9e26789432..90f7e42b8d52edd4ec6da8f396024b1f5ef9ee44 100644 --- a/src/services/state/viewerState.store.ts +++ b/src/services/state/viewerState.store.ts @@ -1,6 +1,10 @@ -import { Action } from '@ngrx/store' +import { Action, Store, select } from '@ngrx/store' import { UserLandmark } from 'src/atlasViewer/atlasViewer.apiService.service'; import { NgLayerInterface } from 'src/atlasViewer/atlasViewer.component'; +import { Injectable } from '@angular/core'; +import { Actions, Effect, ofType } from '@ngrx/effects'; +import { withLatestFrom, map, shareReplay, startWith, tap } from 'rxjs/operators'; +import { Observable } from 'rxjs'; export interface ViewerStateInterface{ fetchedTemplates : any[] @@ -34,13 +38,16 @@ export interface AtlasAction extends Action{ deselectLandmarks : UserLandmark[] navigation? : any + + payload: any } export function viewerState( state:Partial<ViewerStateInterface> = { landmarksSelected : [], fetchedTemplates : [], - loadedNgLayers: [] + loadedNgLayers: [], + userLandmarks: [] }, action:AtlasAction ){ @@ -174,3 +181,78 @@ export const DESELECT_LANDMARKS = `DESELECT_LANDMARKS` export const USER_LANDMARKS = `USER_LANDMARKS` export const NEHUBA_LAYER_CHANGED = `NEHUBA_LAYER_CHANGED` + +@Injectable({ + providedIn: 'root' +}) + +export class ViewerStateUseEffect{ + constructor( + private actions$: Actions, + private store$: Store<any> + ){ + this.currentLandmarks$ = this.store$.pipe( + select('viewerState'), + select('userLandmarks'), + shareReplay(1), + ) + + this.removeUserLandmarks = this.actions$.pipe( + ofType(ACTION_TYPES.REMOVE_USER_LANDMARKS), + withLatestFrom(this.currentLandmarks$), + map(([action, currentLandmarks]) => { + const { landmarkIds } = (action as AtlasAction).payload + for ( const rmId of landmarkIds ){ + const idx = currentLandmarks.findIndex(({ id }) => id === rmId) + if (idx < 0) console.warn(`remove userlandmark with id ${rmId} does not exist`) + } + const removeSet = new Set(landmarkIds) + return { + type: USER_LANDMARKS, + landmarks: currentLandmarks.filter(({ id }) => !removeSet.has(id)) + } + }) + ) + + this.addUserLandmarks$ = this.actions$.pipe( + ofType(ACTION_TYPES.ADD_USERLANDMARKS), + withLatestFrom(this.currentLandmarks$), + map(([action, currentLandmarks]) => { + const { landmarks } = action as AtlasAction + const landmarkMap = new Map() + for (const landmark of currentLandmarks) { + const { id } = landmark + landmarkMap.set(id, landmark) + } + for (const landmark of landmarks) { + const { id } = landmark + if (landmarkMap.has(id)) { + console.warn(`Attempting to add a landmark that already exists, id: ${id}`) + } else { + landmarkMap.set(id, landmark) + } + } + const userLandmarks = Array.from(landmarkMap).map(([id, landmark]) => landmark) + return { + type: USER_LANDMARKS, + landmarks: userLandmarks + } + }) + ) + } + + private currentLandmarks$: Observable<any[]> + + @Effect() + removeUserLandmarks: Observable<any> + + @Effect() + addUserLandmarks$: Observable<any> +} + +const ACTION_TYPES = { + ADD_USERLANDMARKS: `ADD_USERLANDMARKS`, + REMOVE_USER_LANDMARKS: 'REMOVE_USER_LANDMARKS' +} + +export const VIEWERSTATE_ACTION_TYPES = ACTION_TYPES \ No newline at end of file diff --git a/src/ui/nehubaContainer/nehubaContainer.component.ts b/src/ui/nehubaContainer/nehubaContainer.component.ts index 44dfc715979eabe3d800db61441c919171ecb669..08a9ae3a927d92347388693db595f1d99b63de86 100644 --- a/src/ui/nehubaContainer/nehubaContainer.component.ts +++ b/src/ui/nehubaContainer/nehubaContainer.component.ts @@ -3,7 +3,7 @@ import { NehubaViewerUnit } from "./nehubaViewer/nehubaViewer.component"; import { Store, select } from "@ngrx/store"; 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, throttleTime, bufferTime } from "rxjs/operators"; +import { filter,map, take, scan, debounceTime, distinctUntilChanged, switchMap, skip, withLatestFrom, buffer, tap, throttleTime, bufferTime, startWith } from "rxjs/operators"; import { AtlasViewerAPIServices, UserLandmark } from "../../atlasViewer/atlasViewer.apiService.service"; import { timedValues } from "../../util/generator"; import { AtlasViewerConstantsServices } from "../../atlasViewer/atlasViewer.constantService.service"; @@ -11,7 +11,7 @@ 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, NEHUBA_LAYER_CHANGED } from "src/services/state/viewerState.store"; +import { SELECT_REGIONS_WITH_ID, NEHUBA_LAYER_CHANGED, VIEWERSTATE_ACTION_TYPES } from "src/services/state/viewerState.store"; const getProxyUrl = (ngUrl) => `nifti://${BACKEND_URL}preview/file?fileUrl=${encodeURIComponent(ngUrl.replace(/^nifti:\/\//,''))}` const getProxyOther = ({source}) => /AUTH_227176556f3c4bb38df9feea4b91200c/.test(source) @@ -135,8 +135,6 @@ export class NehubaContainer implements OnInit, OnDestroy{ private landmarksLabelIndexMap : Map<number, any> = new Map() private landmarksNameMap : Map<string,number> = new Map() - private userLandmarks : UserLandmark[] = [] - private subscriptions : Subscription[] = [] private nehubaViewerSubscriptions : Subscription[] = [] @@ -220,13 +218,9 @@ export class NehubaContainer implements OnInit, OnDestroy{ ) this.userLandmarks$ = this.store.pipe( - /* TODO: distinct until changed */ select('viewerState'), - // filter(state => isDefined(state) && isDefined(state.userLandmarks)), - map(state => isDefined(state) && isDefined(state.userLandmarks) - ? state.userLandmarks - : []), - distinctUntilChanged(userLmUnchanged) + select('userLandmarks'), + distinctUntilChanged() ) this.onHoverSegments$ = this.store.pipe( @@ -454,10 +448,7 @@ export class NehubaContainer implements OnInit, OnDestroy{ ) this.subscriptions.push( - this.userLandmarks$.pipe( - // distinctUntilChanged((old,new) => ) - ).subscribe(landmarks => { - this.userLandmarks = landmarks + this.userLandmarks$.subscribe(landmarks => { if(this.nehubaViewer){ this.nehubaViewer.updateUserLandmarks(landmarks) } @@ -968,14 +959,16 @@ export class NehubaContainer implements OnInit, OnDestroy{ if(!landmarks.every(l => l.position.constructor === Array) || !landmarks.every(l => l.position.every(v => !isNaN(v))) || !landmarks.every(l => l.position.length == 3)) throw new Error('position needs to be a length 3 tuple of numbers ') this.store.dispatch({ - type: USER_LANDMARKS, + type: VIEWERSTATE_ACTION_TYPES.ADD_USERLANDMARKS, landmarks : landmarks }) }, - remove3DLandmarks : ids => { + remove3DLandmarks : landmarkIds => { this.store.dispatch({ - type : USER_LANDMARKS, - landmarks : this.userLandmarks.filter(l => ids.findIndex(id => id === l.id) < 0) + type: VIEWERSTATE_ACTION_TYPES.REMOVE_USER_LANDMARKS, + payload: { + landmarkIds + } }) }, hideSegment : (labelIndex) => {