diff --git a/src/atlasViewer/atlasViewer.component.ts b/src/atlasViewer/atlasViewer.component.ts index 30d2353b5ca0b71dbccca9ab7f1ebc0135ab4aad..1b2a8c6005c7d5747a1ad6b288f59892501dd5e8 100644 --- a/src/atlasViewer/atlasViewer.component.ts +++ b/src/atlasViewer/atlasViewer.component.ts @@ -42,6 +42,8 @@ import { SlServiceService } from "src/spotlight/sl-service.service"; import { PureContantService } from "src/util"; import { viewerStateSetSelectedRegions, viewerStateRemoveAdditionalLayer, viewerStateSelectParcellation } from "src/services/state/viewerState.store.helper"; import { viewerStateGetOverlayingAdditionalParcellations, viewerStateParcVersionSelector } from "src/services/state/viewerState/selectors"; +import { ngViewerSelectorClearViewEntries } from "src/services/state/ngViewerState/selectors"; +import { ngViewerActionClearView } from "src/services/state/ngViewerState/actions"; /** * TODO @@ -114,6 +116,16 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit { select(viewerStateParcVersionSelector), ) + private selectedParcellation$: Observable<any> + public selectedParcellation: any + + private cookieDialogRef: MatDialogRef<any> + private kgTosDialogRef: MatDialogRef<any> + + public clearViewKeys$ = this.store.pipe( + select(ngViewerSelectorClearViewEntries) + ) + constructor( private store: Store<IavRootStoreInterface>, private widgetServices: WidgetServices, @@ -224,12 +236,6 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit { } } - private selectedParcellation$: Observable<any> - public selectedParcellation: any - - private cookieDialogRef: MatDialogRef<any> - private kgTosDialogRef: MatDialogRef<any> - public ngOnInit() { this.meetsRequirement = this.meetsRequirements() @@ -366,6 +372,21 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit { }) } + /** + * For completeness sake. Root element should never be destroyed. + */ + public ngOnDestroy() { + this.subscriptions.forEach(s => s.unsubscribe()) + } + + public unsetClearViewByKey(key: string){ + this.store.dispatch( + ngViewerActionClearView({ payload: { + [key]: false + }}) + ) + } + public selectParcellation(parc: any) { this.store.dispatch( viewerStateSelectParcellation({ @@ -413,13 +434,6 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit { } - /** - * For completeness sake. Root element should never be destroyed. - */ - public ngOnDestroy() { - this.subscriptions.forEach(s => s.unsubscribe()) - } - /** * perhaps move this to constructor? */ diff --git a/src/atlasViewer/atlasViewer.template.html b/src/atlasViewer/atlasViewer.template.html index d9e76c0a2f551b455974e73ef675dfee90d1f5cb..5a000c2f493a8de1f60684d85d23b70f4c6e21e8 100644 --- a/src/atlasViewer/atlasViewer.template.html +++ b/src/atlasViewer/atlasViewer.template.html @@ -84,8 +84,7 @@ <atlas-layer-selector #alSelector="atlasLayerSelector" class="pe-all" - (iav-outsideClick)="alSelector.selectorExpanded = false" - (closeAccordion)="uiNehubaContainer.accordionOpened = ''"> + (iav-outsideClick)="alSelector.selectorExpanded = false"> </atlas-layer-selector> <mat-chip-list class="mb-2"> <!-- additional layer --> @@ -259,17 +258,17 @@ </mat-icon> </mat-chip> - <mat-chip *ngIf="(overwrittenColorMap$ | async) === 'connectivity' && !previewDirective.active" - (click)="uiNehubaContainer.matDrawerMinor.open() && uiNehubaContainer.navSideDrawerMainSwitch.open()" - class="pe-all position-relative ml-8-n"> + <mat-chip *ngFor="let key of clearViewKeys$ | async" + (click)="uiNehubaContainer.matDrawerMinor.open() && uiNehubaContainer.navSideDrawerMainSwitch.open()" + class="pe-all position-relative ml-8-n"> <span class="pl-4"> - Connectivity + {{ key }} </span> - <mat-icon - (click)="uiNehubaContainer.accordionOpened = ''" - fontSet="fas" - iav-stop="click" - fontIcon="fa-times"> + <mat-icon (click)="unsetClearViewByKey(key)" + fontSet="fas" + iav-stop="click" + fontIcon="fa-times"> + </mat-icon> </mat-chip> </ng-container> diff --git a/src/glue.spec.ts b/src/glue.spec.ts index 884bd388c157828242473ec9fbc49772aa398345..60fc226caabed63a8d90fdf7202cc7866ac8036a 100644 --- a/src/glue.spec.ts +++ b/src/glue.spec.ts @@ -1,4 +1,4 @@ -import { TestBed, tick, fakeAsync } from "@angular/core/testing" +import { TestBed, tick, fakeAsync, discardPeriodicTasks, flush } from "@angular/core/testing" import { DatasetPreviewGlue, glueSelectorGetUiStatePreviewingFiles, glueActionRemoveDatasetPreview, datasetPreviewMetaReducer, glueActionAddDatasetPreview, GlueEffects } from "./glue" import { ACTION_TO_WIDGET_TOKEN, EnumActionToWidget } from "./widget" import { provideMockStore, MockStore } from "@ngrx/store/testing" @@ -262,6 +262,8 @@ describe('> glue.ts', () => { // debounce at 100ms tick(200) ctrl.expectNone({}) + + discardPeriodicTasks() })) it('> on set state, calls end point to fetch full data', fakeAsync(() => { @@ -281,6 +283,7 @@ describe('> glue.ts', () => { const req = ctrl.expectOne(`${DS_PREVIEW_URL}/${encodeURIComponent('minds/core/dataset/v1.0.0')}/${datasetId}/${encodeURIComponent(filename)}`) req.flush(nifti) + discardPeriodicTasks() })) it('> on previewing nifti, thresholds, colormap and remove bg flag set properly', fakeAsync(() => { @@ -309,12 +312,14 @@ describe('> glue.ts', () => { const req = ctrl.expectOne(`${DS_PREVIEW_URL}/${encodeURIComponent('minds/core/dataset/v1.0.0')}/${datasetId}/${encodeURIComponent(filename)}`) req.flush(nifti) + tick(200) const { name, volumeMetadata } = nifti const { min, max } = volumeMetadata expect(highThresholdMapSpy).toHaveBeenCalledWith(name, max) expect(lowThresholdMapSpy).toHaveBeenCalledWith(name, min) expect(colorMapMapSpy).toHaveBeenCalledWith(name, EnumColorMapName.VIRIDIS) expect(bgFlagSpy).toHaveBeenCalledWith(name, true) + discardPeriodicTasks() })) it('> if returns 404, should be handled gracefully', fakeAsync(() => { @@ -333,6 +338,7 @@ describe('> glue.ts', () => { req.flush(null, { status: 404, statusText: 'Not found' }) expect(expectedVal).toBeNull() + discardPeriodicTasks() })) }) @@ -351,6 +357,7 @@ describe('> glue.ts', () => { }) tick(200) expect(actionOnWidgetSpy).not.toHaveBeenCalled() + discardPeriodicTasks() })) it('> correctly calls actionOnWidgetSpy on create', fakeAsync(() => { @@ -377,7 +384,7 @@ describe('> glue.ts', () => { const [ type, cmp, option, ...rest ] = args[0] expect(type).toEqual(EnumActionToWidget.OPEN) - + discardPeriodicTasks() })) it('> correctly calls actionOnWidgetSpy twice when needed', fakeAsync(() => { @@ -423,6 +430,7 @@ describe('> glue.ts', () => { expect(cmp0).toBeTruthy() expect(cmp0).toBe(cmp1) + discardPeriodicTasks() })) it('> correctly calls actionOnWidgetSpy on change of state', fakeAsync(() => { @@ -472,6 +480,7 @@ describe('> glue.ts', () => { expect(type1).toEqual(EnumActionToWidget.CLOSE) expect(cmp1).toBe(null) expect(option1.id).toEqual(mockActionOnSpyReturnVal1.id) + discardPeriodicTasks() })) @@ -493,6 +502,7 @@ describe('> glue.ts', () => { req.flush(nifti) expect(actionOnWidgetSpy).not.toHaveBeenCalled() + discardPeriodicTasks() })) }) @@ -520,6 +530,8 @@ describe('> glue.ts', () => { a: region1.originDatasets }) ) + + discardPeriodicTasks() })) it('> when regions are selected without originDatasets, emits empty array', () => { @@ -575,6 +587,8 @@ describe('> glue.ts', () => { a: expectedOriginDatasets }) ) + + discardPeriodicTasks() })) it('> if regions with multiple originDatasets are selected, emit array containing all origindatasets', fakeAsync(() => { @@ -627,6 +641,7 @@ describe('> glue.ts', () => { a: expectedOriginDatasets }) ) + discardPeriodicTasks() })) }) @@ -672,6 +687,8 @@ describe('> glue.ts', () => { } ] }) ) + + discardPeriodicTasks() })) }) @@ -721,6 +738,7 @@ describe('> glue.ts', () => { ) tick(200) + discardPeriodicTasks() })) }) @@ -757,6 +775,7 @@ describe('> glue.ts', () => { ) tick(200) + discardPeriodicTasks() })) }) @@ -765,7 +784,10 @@ describe('> glue.ts', () => { const store = TestBed.inject(MockStore) const overridenSelector = store.overrideSelector(ngViewerSelectorClearView, true) - const overridenSelector$ = hot('ab', { + /** + * skips first false + */ + const overridenSelector$ = hot('bab', { a: true, b: false }) @@ -792,7 +814,7 @@ describe('> glue.ts', () => { }) expect(glue.onClearviewAddPreview$).toBeObservable( - hot('-a', { + hot('--a', { a: [{ ...nifti, filename: region1.originDatasets[0].filename, @@ -803,6 +825,7 @@ describe('> glue.ts', () => { ) tick(200) + discardPeriodicTasks() })) }) }) diff --git a/src/glue.ts b/src/glue.ts index baeec8147471140a1d996df961d774387cdc1ad0..4cac5d819d134b19a9fcf0a74bcb22983772a8ca 100644 --- a/src/glue.ts +++ b/src/glue.ts @@ -3,7 +3,7 @@ import { OnDestroy, Injectable, Optional, Inject, InjectionToken } from "@angula import { PreviewComponentWrapper, DatasetPreview, determinePreviewFileType, EnumPreviewFileTypes, IKgDataEntry, getKgSchemaIdFromFullId } from "./ui/databrowserModule/pure" import { Subscription, Observable, forkJoin, of, merge } from "rxjs" import { select, Store, ActionReducer, createAction, props, createSelector, Action } from "@ngrx/store" -import { startWith, map, shareReplay, pairwise, debounceTime, distinctUntilChanged, tap, switchMap, withLatestFrom, mapTo, switchMapTo, filter, skip, catchError } from "rxjs/operators" +import { startWith, map, shareReplay, pairwise, debounceTime, distinctUntilChanged, tap, switchMap, withLatestFrom, mapTo, switchMapTo, filter, skip, catchError, bufferTime } from "rxjs/operators" import { TypeActionToWidget, EnumActionToWidget, ACTION_TO_WIDGET_TOKEN } from "./widget" import { getIdObj } from 'common/util' import { MatDialogRef } from "@angular/material/dialog" @@ -16,6 +16,7 @@ import { EnumColorMapName } from "./util/colorMaps" import { Effect } from "@ngrx/effects" import { viewerStateSelectedRegionsSelector, viewerStateSelectedTemplateSelector, viewerStateSelectedParcellationSelector } from "./services/state/viewerState/selectors" import { ngViewerSelectorClearView } from "./services/state/ngViewerState/selectors" +import { ngViewerActionClearView } from './services/state/ngViewerState/actions' const PREVIEW_FILE_TYPES_NO_UI = [ EnumPreviewFileTypes.NIFTI, @@ -99,6 +100,20 @@ export class GlueEffects { })) ) + @Effect() + resetConnectivityMode: Observable<any> = this.store$.pipe( + select(viewerStateSelectedRegionsSelector), + pairwise(), + filter(([o, n]) => o.length > 0 && n.length === 0), + mapTo( + ngViewerActionClearView({ + payload: { + 'Connectivity': false + } + }) + ) + ) + constructor( private store$: Store<any> ){ @@ -248,6 +263,7 @@ export class DatasetPreviewGlue implements IDatasetPreviewGlue, OnDestroy{ select(ngViewerSelectorClearView), distinctUntilChanged(), filter(val => !val), + skip(1), mapTo(arr) )) ) @@ -350,6 +366,38 @@ export class DatasetPreviewGlue implements IDatasetPreviewGlue, OnDestroy{ ) ).pipe( map(prvFilterNull), + bufferTime(15), + map(arr => { + const prvToDismiss = [] + const prvToShow = [] + + const showPrvIds = new Set() + const dismissPrvIds = new Set() + + for (const { prvToDismiss: dismisses, prvToShow: shows } of arr) { + for (const dismiss of dismisses) { + + const id = DatasetPreviewGlue.GetDatasetPreviewId(dismiss) + if (!dismissPrvIds.has(id)) { + dismissPrvIds.add(id) + prvToDismiss.push(dismiss) + } + } + + for (const show of shows) { + const id = DatasetPreviewGlue.GetDatasetPreviewId(show) + if (!dismissPrvIds.has(id) && !showPrvIds.has(id)) { + showPrvIds.add(id) + prvToShow.push(show) + } + } + } + + return { + prvToDismiss, + prvToShow + } + }), withLatestFrom(this.store$.pipe( select(state => state?.viewerState?.templateSelected || null), distinctUntilChanged(), diff --git a/src/services/state/ngViewerState/selectors.spec.ts b/src/services/state/ngViewerState/selectors.spec.ts index 0ad0df3afbb2a740f7eb82250c9c2b6426243bb0..c44f676f86802ee790f4b7e291e7e79c50ccb849 100644 --- a/src/services/state/ngViewerState/selectors.spec.ts +++ b/src/services/state/ngViewerState/selectors.spec.ts @@ -1,36 +1,27 @@ -import { ngViewerSelectorClearView } from './selectors' +import { ngViewerSelectorClearViewEntries } from './selectors' let clearViewQueue = {} describe('> ngViewerState/selectors.ts', () => { - describe('> ngViewerSelectorClearView', () => { + describe('> ngViewerSelectorClearViewEntries', () => { beforeEach(() => { clearViewQueue = {} }) describe('> when prop is not provided', () => { it('> if clearViewQueue is empty (on startup)', () => { - const result = ngViewerSelectorClearView.projector(clearViewQueue) - expect(result).toEqual(false) + const result = ngViewerSelectorClearViewEntries.projector(clearViewQueue) + expect(result).toEqual([]) }) it('> if clearViewQueue is non empty, but falsy, should return false', () => { clearViewQueue['hello - world'] = null clearViewQueue['oo bar'] = false - const result = ngViewerSelectorClearView.projector(clearViewQueue) - expect(result).toEqual(false) + const result = ngViewerSelectorClearViewEntries.projector(clearViewQueue) + expect(result).toEqual([]) }) it('> if clearViewQueue is non empty and truthy, should return true', () => { clearViewQueue['hello - world'] = 1 - const result = ngViewerSelectorClearView.projector(clearViewQueue) - expect(result).toEqual(true) - }) - }) - describe('> when prop is provided', () => { - it('> only provides clear view status of the required', () => { - clearViewQueue['hello - world'] = 1 - const result0 = ngViewerSelectorClearView.projector(clearViewQueue, { id: 'hello - world' }) - const result1 = ngViewerSelectorClearView.projector(clearViewQueue, { id: 'foo bar' }) - expect(result0).toEqual(true) - expect(result1).toEqual(false) + const result = ngViewerSelectorClearViewEntries.projector(clearViewQueue) + expect(result).toEqual(['hello - world']) }) }) }) diff --git a/src/services/state/ngViewerState/selectors.ts b/src/services/state/ngViewerState/selectors.ts index 34b0305f1a88ac7055be0e9220d3a2fd860d4406..85b992c44c585e5d75b997ba69f8ea908a935275 100644 --- a/src/services/state/ngViewerState/selectors.ts +++ b/src/services/state/ngViewerState/selectors.ts @@ -1,16 +1,17 @@ import { createSelector } from "@ngrx/store"; -export const ngViewerSelectorClearView = createSelector( +export const ngViewerSelectorClearViewEntries = createSelector( (state: any) => state?.ngViewerState?.clearViewQueue, - (clearViewQueue, props) => { - - if (!!props && !!props.id) { - for (const key in clearViewQueue) { - if (key === props.id) return !!clearViewQueue[key] - } - return false - } else { - return Object.keys(clearViewQueue).some(key => !!clearViewQueue[key]) + (clearViewQueue = {}) => { + const returnKeys = [] + for (const key in clearViewQueue) { + if (!!clearViewQueue[key]) returnKeys.push(key) } + return returnKeys } ) + +export const ngViewerSelectorClearView = createSelector( + ngViewerSelectorClearViewEntries, + keys => keys.length > 0 +) diff --git a/src/services/state/viewerState/selectors.ts b/src/services/state/viewerState/selectors.ts index 8c6bd83804c95354905e3e07d0d3516edde66404..60ad71c1ad1a288fe351c76d02935760a63f5ca3 100644 --- a/src/services/state/viewerState/selectors.ts +++ b/src/services/state/viewerState/selectors.ts @@ -27,6 +27,22 @@ export const viewerStateSelectedParcellationSelector = createSelector( viewerState => viewerState['parcellationSelected'] ) +export const viewerStateAllRegionsFlattenedRegionSelector = createSelector( + viewerStateSelectedParcellationSelector, + parc => { + const returnArr = [] + const processRegion = region => { + const { children, ...rest } = region + returnArr.push({ ...rest }) + region.children && Array.isArray(region.children) && region.children.forEach(processRegion) + } + if (parc && parc.regions && Array.isArray(parc.regions)) { + parc.regions.forEach(processRegion) + } + return returnArr + } +) + export const viewerStateGetOverlayingAdditionalParcellations = createSelector( state => state[viewerStateHelperStoreName], state => state['viewerState'], diff --git a/src/ui/atlasLayerSelector/atlasLayerSelector.component.ts b/src/ui/atlasLayerSelector/atlasLayerSelector.component.ts index d103ef97f9e2e9e64e374cdcb7f24d854a325abe..af2558291cf788adc461fee55246ee886dcb36a0 100644 --- a/src/ui/atlasLayerSelector/atlasLayerSelector.component.ts +++ b/src/ui/atlasLayerSelector/atlasLayerSelector.component.ts @@ -37,8 +37,6 @@ export class AtlasLayerSelector implements OnInit { public containerMaxWidth: number - @Output() public closeAccordion: EventEmitter<boolean> = new EventEmitter() - constructor(private store$: Store<any>) { this.selectedAtlas$ = this.store$.pipe( select(viewerStateGetSelectedAtlas), @@ -160,9 +158,6 @@ export class AtlasLayerSelector implements OnInit { } selectTemplateWithName(template) { - this.closeAccordion.emit() - this.store$.dispatch({type: CLEAR_CONNECTIVITY_REGION}) - this.store$.dispatch({type: SET_CONNECTIVITY_VISIBLE, payload: null}) this.store$.dispatch( viewerStateSelectTemplateWithId({ payload: template }) ) diff --git a/src/ui/connectivityBrowser/connectivityBrowser.component.ts b/src/ui/connectivityBrowser/connectivityBrowser.component.ts index c966045c87c6f81c94cdafafcc0df78b39edab6e..682aed5c210552eab1a18a75dd38c9bd6552978e 100644 --- a/src/ui/connectivityBrowser/connectivityBrowser.component.ts +++ b/src/ui/connectivityBrowser/connectivityBrowser.component.ts @@ -1,71 +1,102 @@ import { - AfterContentChecked, AfterViewInit, ChangeDetectorRef, Component, - ElementRef, EventEmitter, Input, - OnDestroy, Output, + ElementRef, + EventEmitter, + OnDestroy, + Output, ViewChild, + Input, } from "@angular/core"; import {select, Store} from "@ngrx/store"; -import {fromEvent, Observable, Subscription} from "rxjs"; -import {distinctUntilChanged, filter, map, shareReplay} from "rxjs/operators"; +import {fromEvent, Observable, Subscription, Subject, combineLatest} from "rxjs"; +import {distinctUntilChanged, map } from "rxjs/operators"; import {CLEAR_CONNECTIVITY_REGION, SELECT_REGIONS, SET_CONNECTIVITY_VISIBLE} from "src/services/state/viewerState.store"; -import {isDefined, safeFilter} from "src/services/stateStore.service"; +import { safeFilter} from "src/services/stateStore.service"; import { viewerStateNavigateToRegion } from "src/services/state/viewerState.store.helper"; +import { ngViewerActionClearView } from "src/services/state/ngViewerState/actions"; +import { ngViewerSelectorClearViewEntries } from "src/services/state/ngViewerState/selectors"; +import { viewerStateAllRegionsFlattenedRegionSelector } from "src/services/state/viewerState/selectors"; + +const CONNECTIVITY_NAME_PLATE = 'Connectivity' @Component({ selector: 'connectivity-browser', templateUrl: './connectivityBrowser.template.html', }) -export class ConnectivityBrowserComponent implements AfterViewInit, OnDestroy, AfterContentChecked { +export class ConnectivityBrowserComponent implements AfterViewInit, OnDestroy{ + + private setColorMap$: Subject<boolean> = new Subject() + + /** + * accordion expansion should only toggle the clearviewqueue state + * which should be the single source of truth + * setcolormaps$ is set by the presence/absence of clearviewqueue[CONNECTIVITY_NAME_PLATE] + */ + private _isFirstUpdate = true + + @Input() + set accordionExpanded(flag: boolean){ + /** + * ignore first update + */ + if (this._isFirstUpdate) { + this._isFirstUpdate = false + return + } + this.store$.dispatch( + ngViewerActionClearView({ payload: { + [CONNECTIVITY_NAME_PLATE]: flag + }}) + ) + } + + @Output() + setOpenState: EventEmitter<boolean> = new EventEmitter() + + @Input() + set region(val){ + const newRegionName = val && val.name - public region: string + if (!val) { + this.store$.dispatch({ + type: SET_CONNECTIVITY_VISIBLE, + payload: false, + }) + return + } + + if (newRegionName !== this.regionName && this.defaultColorMap) { + this.restoreDefaultColormap() + } + this.regionName = newRegionName + + // TODO may not be necessary + this.changeDetectionRef.detectChanges() + } + + public regionName: string public datasetList: any[] = [] public selectedDataset: any public connectedAreas = [] - public componentHeight: any - public showConnectivityToggle: boolean = false - - private connectivityRegion$: Observable<any> - private templateSelected$: Observable<any> - private selectedParcellation$: Observable<any> + + private selectedParcellationFlatRegions$ = this.store$.pipe( + select(viewerStateAllRegionsFlattenedRegionSelector) + ) public overwrittenColorMap$: Observable<any> private subscriptions: Subscription[] = [] public expandMenuIndex = -1 public allRegions = [] public defaultColorMap: Map<string, Map<number, {red: number, green: number, blue: number}>> - public math = Math @ViewChild('connectivityComponent', {read: ElementRef}) public connectivityComponentElement: ElementRef<HTMLHbpConnectivityMatrixRowElement> @ViewChild('fullConnectivityGrid') public fullConnectivityGridElement: ElementRef<HTMLFullConnectivityGridElement> - @Output() public closeConnectivity: EventEmitter<boolean> = new EventEmitter() - @Output() public connectedAreaCount: EventEmitter<number> = new EventEmitter() - constructor( private store$: Store<any>, private changeDetectionRef: ChangeDetectorRef, ) { - this.selectedParcellation$ = this.store$.pipe( - select('viewerState'), - filter(state => isDefined(state) && isDefined(state.parcellationSelected)), - map(state => state.parcellationSelected), - distinctUntilChanged(), - ) - - this.connectivityRegion$ = this.store$.pipe( - select('viewerState'), - safeFilter('connectivityRegion'), - map(state => state.connectivityRegion), - distinctUntilChanged() - ) - - this.templateSelected$ = this.store$.pipe( - select('viewerState'), - select('templateSelected'), - shareReplay(1) - ) this.overwrittenColorMap$ = this.store$.pipe( select('viewerState'), @@ -75,49 +106,66 @@ export class ConnectivityBrowserComponent implements AfterViewInit, OnDestroy, A ) } - public ngAfterContentChecked(): void { - this.componentHeight = this.connectivityComponentElement?.nativeElement.clientHeight - } - public ngAfterViewInit(): void { + this.subscriptions.push( - this.selectedParcellation$.subscribe(parcellation => { - this.closeConnectivityView(false) - if (parcellation && parcellation.hasAdditionalViewMode && parcellation.hasAdditionalViewMode.includes('connectivity')) { - if (parcellation.regions && parcellation.regions.length) { - this.allRegions = [] - this.getAllRegionsFromParcellation(parcellation.regions) - } - } + this.store$.pipe( + select(ngViewerSelectorClearViewEntries), + ).subscribe(keys => { + this.setColorMap$.next(keys.includes(CONNECTIVITY_NAME_PLATE)) + }) + ) + + this.subscriptions.push( + this.store$.pipe( + select(ngViewerSelectorClearViewEntries), + ).subscribe(keys => { + this.setOpenState.emit(keys.includes(CONNECTIVITY_NAME_PLATE)) + }) + ) + + this.subscriptions.push( + this.selectedParcellationFlatRegions$.subscribe(flattenedRegions => { + this.defaultColorMap = null + this.allRegions = flattenedRegions }), - this.connectivityRegion$.subscribe(cr => { - if (cr && cr.length) { - if (this.region !== cr && this.defaultColorMap) { - this.closeConnectivityView() - } - this.region = cr - this.changeDetectionRef.detectChanges() - } else { + ) + + /** + * setting/restoring colormap + */ + this.subscriptions.push( + combineLatest( + this.setColorMap$.pipe( + distinctUntilChanged() + ), + fromEvent(this.connectivityComponentElement.nativeElement, 'connectivityDataReceived').pipe( + map((e: CustomEvent) => e.detail) + ) + ).subscribe(([flag, connectedAreas]) => { + + this.connectedAreas = connectedAreas + + if (flag) { + this.addNewColorMap() this.store$.dispatch({ type: SET_CONNECTIVITY_VISIBLE, - payload: false, + payload: 'connectivity', }) + } else { + this.restoreDefaultColormap() + + /** + * TODO + * may no longer be necessary + */ + this.store$.dispatch({type: CLEAR_CONNECTIVITY_REGION}) + this.store$.dispatch({type: SET_CONNECTIVITY_VISIBLE, payload: null}) } - }), + }) ) - this.subscriptions.push(this.overwrittenColorMap$.subscribe(ocm => { - this.showConnectivityToggle = ocm === 'connectivity'? true : false - })) + this.subscriptions.push( - fromEvent(this.connectivityComponentElement?.nativeElement, 'connectivityDataReceived', { capture: true }) - .subscribe((e: CustomEvent) => { - this.connectedAreas = e.detail - this.connectedAreaCount.emit(this.connectedAreas.length) - if (this.connectedAreas.length > 0 && this.showConnectivityToggle) { - this.addNewColorMap() - } else { - this.defaultColorMap = new Map(getWindow().interactiveViewer.viewerHandle.getLayersSegmentColourMap()) - } }), fromEvent(this.connectivityComponentElement?.nativeElement, 'collapsedMenuChanged', { capture: true }) .subscribe((e: CustomEvent) => { this.expandMenuIndex = e.detail @@ -133,26 +181,14 @@ export class ConnectivityBrowserComponent implements AfterViewInit, OnDestroy, A // ToDo Fix in future to use component const a = document.querySelector('hbp-connectivity-matrix-row') a.downloadCSV() - } else if (e.detail.name === 'Apply colors to viewer') { - this.defaultColorMap && this.toggleConnectivityOnViewer( {checked: this.showConnectivityToggle? false : true}) } }), ) - this.showConnectivityToggle = true } - toggleConnectivityOnViewer(event) { - if (event.checked) { - this.showConnectivityToggle = true - this.addNewColorMap() - } else { - this.showConnectivityToggle = false - if (this.defaultColorMap) this.setDefaultMap() - this.store$.dispatch({ - type: SET_CONNECTIVITY_VISIBLE, - payload: null, - }) - } + public ngOnDestroy(): void { + this.restoreDefaultColormap() + this.subscriptions.forEach(s => s.unsubscribe()) } // ToDo Affect on component @@ -160,10 +196,6 @@ export class ConnectivityBrowserComponent implements AfterViewInit, OnDestroy, A this.selectedDataset = event.value } - public ngOnDestroy(): void { - this.subscriptions.forEach(s => s.unsubscribe()) - } - navigateToRegion(region) { this.store$.dispatch( viewerStateNavigateToRegion({ @@ -183,29 +215,12 @@ export class ConnectivityBrowserComponent implements AfterViewInit, OnDestroy, A return this.allRegions.find(ar => ar.name === region) } - public closeConnectivityView(setDefault = true) { - if (this.defaultColorMap && setDefault) this.setDefaultMap() - this.closeConnectivity.emit() - this.store$.dispatch({ - type: SET_CONNECTIVITY_VISIBLE, - payload: null, - }) - } - - public setDefaultMap() { - this.allRegions.forEach(r => { - if (r && r.ngId && r.rgb && this.defaultColorMap) { - this.defaultColorMap.get(r.ngId).set(r.labelIndex, {red: r.rgb[0], green: r.rgb[1], blue: r.rgb[2]}) - } - }) + public restoreDefaultColormap() { + if (!this.defaultColorMap) return getWindow().interactiveViewer.viewerHandle.applyLayersColourMap(this.defaultColorMap) } public addNewColorMap() { - this.store$.dispatch({ - type: SET_CONNECTIVITY_VISIBLE, - payload: 'connectivity', - }) this.defaultColorMap = new Map(getWindow().interactiveViewer.viewerHandle.getLayersSegmentColourMap()) const existingMap: Map<string, Map<number, {red: number, green: number, blue: number}>> = (getWindow().interactiveViewer.viewerHandle.getLayersSegmentColourMap()) @@ -229,15 +244,6 @@ export class ConnectivityBrowserComponent implements AfterViewInit, OnDestroy, A getWindow().interactiveViewer.viewerHandle.applyLayersColourMap(colorMap) } - public getAllRegionsFromParcellation = (regions) => { - for (const region of regions) { - if (region.children && region.children.length) { - this.getAllRegionsFromParcellation(region.children) - } else { - this.allRegions.push(region) - } - } - } exportConnectivityProfile() { const a = document.querySelector('hbp-connectivity-matrix-row') a.downloadCSV() diff --git a/src/ui/connectivityBrowser/connectivityBrowser.template.html b/src/ui/connectivityBrowser/connectivityBrowser.template.html index 65d3fbf62b44f6ee3e218b58e4328cdc0255c693..776848eba9f58ff48085acc0402ba7f77b197d0f 100644 --- a/src/ui/connectivityBrowser/connectivityBrowser.template.html +++ b/src/ui/connectivityBrowser/connectivityBrowser.template.html @@ -1,8 +1,8 @@ <div class="w-100 h-100 d-block d-flex flex-column pb-2"> <hbp-connectivity-matrix-row #connectivityComponent - *ngIf="region" - [region]="region" + *ngIf="regionName" + [region]="regionName" theme="dark" loadurl="https://connectivityquery-connectivity.apps-dev.hbp.eu/connectivity" dataset-url="https://connectivityquery-connectivity.apps-dev.hbp.eu/studies" diff --git a/src/ui/nehubaContainer/nehubaContainer.component.ts b/src/ui/nehubaContainer/nehubaContainer.component.ts index 7a56478cd392f44b3ec629538d6645af3a3f5923..ac456e0dbd3440614d9fb8c00c2cdbba343fbb6b 100644 --- a/src/ui/nehubaContainer/nehubaContainer.component.ts +++ b/src/ui/nehubaContainer/nehubaContainer.component.ts @@ -14,19 +14,17 @@ import { NehubaViewerUnit } from "./nehubaViewer/nehubaViewer.component"; import { compareLandmarksChanged } from "src/util/constants"; import { PureContantService } from "src/util"; import { ARIA_LABELS, IDS } from 'common/constants' -import { ngViewerActionSetPerspOctantRemoval, PANELS, ngViewerActionToggleMax, ngViewerActionAddNgLayer, ngViewerActionRemoveNgLayer, ngViewerActionClearView } from "src/services/state/ngViewerState.store.helper"; +import { ngViewerActionSetPerspOctantRemoval, PANELS, ngViewerActionToggleMax, ngViewerActionAddNgLayer, ngViewerActionRemoveNgLayer } from "src/services/state/ngViewerState.store.helper"; import { viewerStateSelectRegionWithIdDeprecated, viewerStateAddUserLandmarks, viewreStateRemoveUserLandmarks } from 'src/services/state/viewerState.store.helper' import { SwitchDirective } from "src/util/directives/switch.directive"; import { - viewerStateSetConnectivityRegion, viewerStateDblClickOnViewer, } from "src/services/state/viewerState.store.helper"; -import { getFourPanel, getHorizontalOneThree, getSinglePanel, getVerticalOneThree, calculateSliceZoomFactor, scanSliceViewRenderFn as scanFn, isFirstRow, isFirstCell } from "./util"; +import { getFourPanel, getHorizontalOneThree, getSinglePanel, getVerticalOneThree, calculateSliceZoomFactor, scanSliceViewRenderFn as scanFn } from "./util"; import { NehubaViewerContainerDirective } from "./nehubaViewerInterface/nehubaViewerInterface.directive"; import { ITunableProp } from "./mobileOverlay/mobileOverlay.component"; import {ConnectivityBrowserComponent} from "src/ui/connectivityBrowser/connectivityBrowser.component"; -import {CLEAR_CONNECTIVITY_REGION, SET_CONNECTIVITY_VISIBLE} from "src/services/state/viewerState.store"; const { MESH_LOADING_STATUS } = IDS @@ -252,7 +250,6 @@ export class NehubaContainer implements OnInit, OnChanges, OnDestroy { public hoveredPanelIndices$: Observable<number> @ViewChild('connectivityComponent') public connectivityComponent: ConnectivityBrowserComponent - public accordionOpened: string = '' constructor( private pureConstantService: PureContantService, @@ -739,10 +736,6 @@ export class NehubaContainer implements OnInit, OnChanges, OnDestroy { ) this.subscriptions.push(this.selectedRegions$.subscribe(sr => { - if (sr?.length >= 1) this.setConnectivityRegion(sr[0].name) - else { - this.disableConnectivity() - } this.selectedRegions = sr })) @@ -809,41 +802,6 @@ export class NehubaContainer implements OnInit, OnChanges, OnDestroy { this.currOnHoverObsSub = this.currentOnHoverObs$.subscribe(({ segments }) => this.onHoverSegments$.next(segments)) } } - - setConnectivityRegion(regionName) { - this.store.dispatch(viewerStateSetConnectivityRegion({ connectivityRegion: regionName })) - } - - public disableConnectivity() { - this.store.dispatch({type: CLEAR_CONNECTIVITY_REGION}) - this.store.dispatch({type: SET_CONNECTIVITY_VISIBLE, payload: null}) - if (this.accordionOpened === 'Connectivity') this.connectivityComponent.toggleConnectivityOnViewer(false) - } - - sidebarAccordionOpened(title) { - switch (title) { - case 'Connectivity': { - this.connectivityComponent?.toggleConnectivityOnViewer( {checked: true}) - break - }} - this.store.dispatch( - ngViewerActionClearView({ payload: { - ['connectivity-shown']: true - } }) - ) - } - sidebarAccordionClosed(title) { - switch (title) { - case 'Connectivity': { - this.connectivityComponent?.toggleConnectivityOnViewer( {checked: false}) - break - }} - this.store.dispatch( - ngViewerActionClearView({ payload: { - ['connectivity-shown']: false - } }) - ) - } public ngOnDestroy() { this.subscriptions.forEach(s => s.unsubscribe()) @@ -1027,7 +985,18 @@ export class NehubaContainer implements OnInit, OnChanges, OnDestroy { })) }, segmentColourMap : new Map(), - getLayersSegmentColourMap: () => this.nehubaViewer.multiNgIdColorMap, + getLayersSegmentColourMap: () => { + const newMainMap = new Map() + for (const [key, colormap] of this.nehubaViewer.multiNgIdColorMap.entries()) { + const newColormap = new Map() + newMainMap.set(key, newColormap) + + for (const [lableIndex, entry] of colormap.entries()) { + newColormap.set(lableIndex, JSON.parse(JSON.stringify(entry))) + } + } + return newMainMap + }, applyColourMap : (_map) => { throw new Error(`apply color map has been deprecated. use applyLayersColourMap instead`) }, diff --git a/src/ui/nehubaContainer/nehubaContainer.template.html b/src/ui/nehubaContainer/nehubaContainer.template.html index a101fd063b4a31ef79e4d91c9b36bf46cdd2c061..25bd2d65b53985793e488a4dc2de224e05a1e2ea 100644 --- a/src/ui/nehubaContainer/nehubaContainer.template.html +++ b/src/ui/nehubaContainer/nehubaContainer.template.html @@ -308,11 +308,9 @@ let-iavNgIf="iavNgIf" let-content="content"> <mat-expansion-panel class="mt-1 mb-1" - [expanded]="accordionOpened === title" - (opened)="sidebarAccordionOpened(title); accordionOpened = title" - (closed)="sidebarAccordionClosed(title); accordionOpened === title? accordionOpened = '' : null" hideToggle - *ngIf="iavNgIf"> + *ngIf="iavNgIf" + #expansionPanel="matExpansionPanel"> <mat-expansion-panel-header> @@ -333,10 +331,8 @@ </mat-expansion-panel-header> <!-- content --> - <ng-template matExpansionPanelContent> - <ng-container *ngTemplateOutlet="content"> - </ng-container> - </ng-template> + <ng-container *ngTemplateOutlet="content; context: { expansionPanel: expansionPanel }"> + </ng-container> </mat-expansion-panel> </ng-template> @@ -394,10 +390,16 @@ </ng-container> <!-- Connectivity --> - <ng-template #connectivityContentTmpl> + <ng-template #connectivityContentTmpl let-expansionPanel="expansionPanel"> <mat-card-content class="flex-grow-1 flex-shrink-1 w-100"> - <connectivity-browser class="pe-all flex-shrink-1" #connectivityComponent> - </connectivity-browser> + <ng-container *ngFor="let region of selectedRegions$ | async"> + <connectivity-browser class="pe-all flex-shrink-1" + [region]="region" + (setOpenState)="expansionPanel.expanded = $event" + [accordionExpanded]="expansionPanel.expanded" + #connectivityComponent> + </connectivity-browser> + </ng-container> </mat-card-content> </ng-template> diff --git a/src/ui/viewerStateController/viewerState.useEffect.ts b/src/ui/viewerStateController/viewerState.useEffect.ts index 260b562b6a9988c1b4f4ddddeaccee93ac30d06f..d64b634afac2105eb2f34199e43b5b1259dae2ec 100644 --- a/src/ui/viewerStateController/viewerState.useEffect.ts +++ b/src/ui/viewerStateController/viewerState.useEffect.ts @@ -4,11 +4,13 @@ import { Action, select, Store } from "@ngrx/store"; import { Observable, Subscription, of, merge } from "rxjs"; import {distinctUntilChanged, filter, map, shareReplay, withLatestFrom, switchMap, mapTo } from "rxjs/operators"; import { AtlasViewerConstantsServices } from "src/atlasViewer/atlasViewer.constantService.service"; -import { CHANGE_NAVIGATION, FETCHED_TEMPLATE, GENERAL_ACTION_TYPES, IavRootStoreInterface, NEWVIEWER, SELECT_PARCELLATION, SELECT_REGIONS } from "src/services/stateStore.service"; +import { CHANGE_NAVIGATION, FETCHED_TEMPLATE, GENERAL_ACTION_TYPES, IavRootStoreInterface, NEWVIEWER, SELECT_PARCELLATION, SELECT_REGIONS, viewerState } from "src/services/stateStore.service"; import { VIEWERSTATE_CONTROLLER_ACTION_TYPES } from "./viewerState.base"; import {TemplateCoordinatesTransformation} from "src/services/templateCoordinatesTransformation.service"; import { CLEAR_STANDALONE_VOLUMES } from "src/services/state/viewerState.store"; -import { viewerStateToggleRegionSelect, viewerStateHelperSelectParcellationWithId, viewerStateSelectTemplateWithId, viewerStateNavigateToRegion, viewerStateHelperStoreName } from "src/services/state/viewerState.store.helper"; +import { viewerStateToggleRegionSelect, viewerStateHelperSelectParcellationWithId, viewerStateSelectTemplateWithId, viewerStateNavigateToRegion, viewerStateHelperStoreName, viewerStateSelectedTemplateSelector } from "src/services/state/viewerState.store.helper"; +import { ngViewerSelectorClearViewEntries } from "src/services/state/ngViewerState/selectors"; +import { ngViewerActionClearView } from "src/services/state/ngViewerState/actions"; @Injectable({ providedIn: 'root', @@ -42,10 +44,26 @@ export class ViewerStateControllerUseEffect implements OnDestroy { @Effect() public navigateToRegion$: Observable<any> - @Effect() public onTemplateSelectClearStandAloneVolumes$: Observable<any> + @Effect() + public onTemplateSelectUnsetAllClearQueues$: Observable<any> = this.store$.pipe( + select(viewerStateSelectedTemplateSelector), + withLatestFrom(this.store$.pipe( + select(ngViewerSelectorClearViewEntries) + )), + map(([_, clearViewQueue]) => { + const newVal = {} + for (const key of clearViewQueue) { + newVal[key] = false + } + return ngViewerActionClearView({ + payload: newVal + }) + }) + ) + constructor( private actions$: Actions, private store$: Store<IavRootStoreInterface>, @@ -62,8 +80,8 @@ export class ViewerStateControllerUseEffect implements OnDestroy { distinctUntilChanged(), ) - this.onTemplateSelectClearStandAloneVolumes$ = viewerState$.pipe( - select('templateSelected'), + this.onTemplateSelectClearStandAloneVolumes$ = this.store$.pipe( + select(viewerStateSelectedTemplateSelector), distinctUntilChanged(), mapTo({ type: CLEAR_STANDALONE_VOLUMES }) )