From 76cb5dcf99e457e75f1d7a0eb856b3320acebf0b Mon Sep 17 00:00:00 2001 From: Xiao Gui <xgui3783@gmail.com> Date: Thu, 22 Oct 2020 15:54:38 +0200 Subject: [PATCH] chore: retain panel order and mode on switch tmpl fixes #681 relevant #682 --- .../state/ngViewerState.store.helper.ts | 18 +++ src/services/state/ngViewerState.store.ts | 8 +- src/services/state/ngViewerState/selectors.ts | 5 + src/services/state/viewerState/selectors.ts | 5 + src/ui/config/config.component.ts | 4 +- .../databrowserModule/databrowser.service.ts | 4 +- .../nehubaContainer.component.ts | 41 +---- .../nehubaViewerInterface.directive.spec.ts | 148 ++++++++++++++++++ .../nehubaViewerInterface.directive.ts | 27 ++-- src/ui/nehubaContainer/util.ts | 35 +++++ 10 files changed, 237 insertions(+), 58 deletions(-) create mode 100644 src/ui/nehubaContainer/nehubaViewerInterface/nehubaViewerInterface.directive.spec.ts diff --git a/src/services/state/ngViewerState.store.helper.ts b/src/services/state/ngViewerState.store.helper.ts index 834c5d5d5..2d0ca1500 100644 --- a/src/services/state/ngViewerState.store.helper.ts +++ b/src/services/state/ngViewerState.store.helper.ts @@ -22,3 +22,21 @@ export { ngViewerActionSetPanelOrder, ngViewerActionForceShowSegment, } + +import { + ngViewerSelectorClearView, + ngViewerSelectorClearViewEntries, + ngViewerSelectorNehubaReady, + ngViewerSelectorOctantRemoval, + ngViewerSelectorPanelMode, + ngViewerSelectorPanelOrder, +} from './ngViewerState/selectors' + +export { + ngViewerSelectorClearView, + ngViewerSelectorClearViewEntries, + ngViewerSelectorNehubaReady, + ngViewerSelectorOctantRemoval, + ngViewerSelectorPanelMode, + ngViewerSelectorPanelOrder, +} \ No newline at end of file diff --git a/src/services/state/ngViewerState.store.ts b/src/services/state/ngViewerState.store.ts index 8a9136dc4..bc3b7fb8c 100644 --- a/src/services/state/ngViewerState.store.ts +++ b/src/services/state/ngViewerState.store.ts @@ -276,10 +276,10 @@ export class NgViewerUseEffect implements OnDestroy { this.unmaximiseOrder$ = toggleMaxmimise$.pipe( withLatestFrom( - combineLatest( + combineLatest([ this.panelOrder$, this.panelMode$, - ), + ]), ), scan((acc, curr) => { const [action, [panelOrders, panelMode]] = curr @@ -328,10 +328,10 @@ export class NgViewerUseEffect implements OnDestroy { }), ) - this.toggleMaximiseCycleMessage$ = combineLatest( + this.toggleMaximiseCycleMessage$ = combineLatest([ this.toggleMaximiseMode$, this.pureConstantService.useTouchUI$, - ).pipe( + ]).pipe( filter(([_, useMobileUI]) => !useMobileUI), map(([toggleMaximiseMode, _]) => toggleMaximiseMode), filter(({ payload }) => payload.panelMode && payload.panelMode === PANELS.SINGLE_PANEL), diff --git a/src/services/state/ngViewerState/selectors.ts b/src/services/state/ngViewerState/selectors.ts index ddfa9f306..db84dcdb9 100644 --- a/src/services/state/ngViewerState/selectors.ts +++ b/src/services/state/ngViewerState/selectors.ts @@ -30,3 +30,8 @@ export const ngViewerSelectorOctantRemoval = createSelector( state => state['ngViewerState'], ngViewerState => ngViewerState.octantRemoval ) + +export const ngViewerSelectorNehubaReady = createSelector( + state => state['ngViewerState'], + ngViewerState => ngViewerState.nehubaReady +) diff --git a/src/services/state/viewerState/selectors.ts b/src/services/state/viewerState/selectors.ts index b7a091bdc..8b5b442e3 100644 --- a/src/services/state/viewerState/selectors.ts +++ b/src/services/state/viewerState/selectors.ts @@ -53,6 +53,11 @@ export const viewerStateStandAloneVolumes = createSelector( viewerState => viewerState['standaloneVolumes'] ) +export const viewerStateSelectorNavigation = createSelector( + state => state['viewerState'], + viewerState => viewerState['navigation'] +) + export const viewerStateGetOverlayingAdditionalParcellations = createSelector( state => state[viewerStateHelperStoreName], state => state['viewerState'], diff --git a/src/ui/config/config.component.ts b/src/ui/config/config.component.ts index 36f9e9e0d..4127b3bf9 100644 --- a/src/ui/config/config.component.ts +++ b/src/ui/config/config.component.ts @@ -12,6 +12,7 @@ import {MatSliderChange} from "@angular/material/slider"; import { PureContantService } from 'src/util'; import { ngViewerActionSwitchPanelMode } from 'src/services/state/ngViewerState/actions'; import { ngViewerSelectorPanelMode, ngViewerSelectorPanelOrder } from 'src/services/state/ngViewerState/selectors'; +import { viewerStateSelectorNavigation } from 'src/services/state/viewerState/selectors'; const GPU_TOOLTIP = `Higher GPU usage can cause crashes on lower end machines` const ANIMATION_TOOLTIP = `Animation can cause slowdowns in lower end machines` @@ -83,8 +84,7 @@ export class ConfigComponent implements OnInit, OnDestroy { ) this.viewerObliqueRotated$ = this.store.pipe( - select('viewerState'), - select('navigation'), + select(viewerStateSelectorNavigation), map(navigation => (navigation && navigation.orientation) || [0, 0, 0, 1]), debounceTime(100), map(isIdentityQuat), diff --git a/src/ui/databrowserModule/databrowser.service.ts b/src/ui/databrowserModule/databrowser.service.ts index 8d3828f0b..875daac24 100644 --- a/src/ui/databrowserModule/databrowser.service.ts +++ b/src/ui/databrowserModule/databrowser.service.ts @@ -18,6 +18,7 @@ import { FilterDataEntriesByRegion } from "./util/filterDataEntriesByRegion.pipe import { datastateActionToggleFav, datastateActionUnfavDataset, datastateActionFavDataset } from "src/services/state/dataState/actions"; import { getIdFromFullId } from 'common/util' +import { viewerStateSelectorNavigation } from "src/services/state/viewerState/selectors"; const noMethodDisplayName = 'No methods described' @@ -113,8 +114,7 @@ export class DatabrowserService implements OnDestroy { ) this.viewportBoundingBox$ = this.store.pipe( - select('viewerState'), - select('navigation'), + select(viewerStateSelectorNavigation), distinctUntilChanged(), debounceTime(SPATIAL_SEARCH_DEBOUNCE), filter(v => !!v && !!v.position && !!v.zoom), diff --git a/src/ui/nehubaContainer/nehubaContainer.component.ts b/src/ui/nehubaContainer/nehubaContainer.component.ts index 319d2be8b..fd0c2ba97 100644 --- a/src/ui/nehubaContainer/nehubaContainer.component.ts +++ b/src/ui/nehubaContainer/nehubaContainer.component.ts @@ -20,7 +20,7 @@ import { MOUSE_OVER_LANDMARK, NgViewerStateInterface } from "src/services/stateStore.service"; -import { getExportNehuba, isSame, getViewer } from "src/util/fn"; +import { getExportNehuba, isSame } from "src/util/fn"; import { AtlasViewerAPIServices, IUserLandmark } from "src/atlasViewer/atlasViewer.apiService.service"; import { NehubaViewerUnit } from "./nehubaViewer/nehubaViewer.component"; import { compareLandmarksChanged } from "src/util/constants"; @@ -33,12 +33,12 @@ import { viewerStateDblClickOnViewer, } from "src/services/state/viewerState.store.helper"; -import { getFourPanel, getHorizontalOneThree, getSinglePanel, getVerticalOneThree, calculateSliceZoomFactor, scanSliceViewRenderFn as scanFn } from "./util"; +import { getFourPanel, getHorizontalOneThree, getSinglePanel, getVerticalOneThree, calculateSliceZoomFactor, scanSliceViewRenderFn as scanFn, takeOnePipe } from "./util"; import { NehubaViewerContainerDirective } from "./nehubaViewerInterface/nehubaViewerInterface.directive"; import { ITunableProp } from "./mobileOverlay/mobileOverlay.component"; import {ConnectivityBrowserComponent} from "src/ui/connectivityBrowser/connectivityBrowser.component"; import { viewerStateMouseOverCustomLandmark } from "src/services/state/viewerState/actions"; -import { ngViewerSelectorOctantRemoval, ngViewerSelectorPanelMode, ngViewerSelectorPanelOrder } from "src/services/state/ngViewerState/selectors"; +import { ngViewerSelectorNehubaReady, ngViewerSelectorOctantRemoval, ngViewerSelectorPanelMode, ngViewerSelectorPanelOrder } from "src/services/state/ngViewerState/selectors"; const { MESH_LOADING_STATUS } = IDS @@ -318,8 +318,7 @@ export class NehubaContainer implements OnInit, OnChanges, OnDestroy { ) this.redrawLayout$ = this.store.pipe( - select('ngViewerState'), - select('nehubaReady'), + select(ngViewerSelectorNehubaReady), distinctUntilChanged(), filter(v => !!v), switchMapTo(combineLatest([ @@ -1098,35 +1097,3 @@ export class NehubaContainer implements OnInit, OnChanges, OnDestroy { } } } - -export const takeOnePipe = () => { - - return pipe( - scan((acc: Event[], event: Event) => { - const target = (event as Event).target as HTMLElement - /** - * 0 | 1 - * 2 | 3 - * - * 4 ??? - */ - const panels = getViewer()['display']['panels'] - const panelEls = Array.from(panels).map(({ element }) => element) - - const identifySrcElement = (element: HTMLElement) => { - const idx = panelEls.indexOf(element) - return idx - } - - const key = identifySrcElement(target) - const _ = {} - _[key] = event - return Object.assign({}, acc, _) - }, []), - filter(v => { - const isdefined = (obj) => typeof obj !== 'undefined' && obj !== null - return (isdefined(v[0]) && isdefined(v[1]) && isdefined(v[2])) - }), - take(1), - ) -} diff --git a/src/ui/nehubaContainer/nehubaViewerInterface/nehubaViewerInterface.directive.spec.ts b/src/ui/nehubaContainer/nehubaViewerInterface/nehubaViewerInterface.directive.spec.ts new file mode 100644 index 000000000..91c8e5847 --- /dev/null +++ b/src/ui/nehubaContainer/nehubaViewerInterface/nehubaViewerInterface.directive.spec.ts @@ -0,0 +1,148 @@ +import { CommonModule } from "@angular/common" +import { Component } from "@angular/core" +import { TestBed, async, ComponentFixture } from "@angular/core/testing" +import { By } from "@angular/platform-browser" +import { BrowserDynamicTestingModule } from "@angular/platform-browser-dynamic/testing" +import { MockStore, provideMockStore } from "@ngrx/store/testing" +import { ngViewerSelectorOctantRemoval } from "src/services/state/ngViewerState/selectors" +import { NehubaViewerUnit } from "../nehubaViewer/nehubaViewer.component" +import { NehubaViewerContainerDirective } from "./nehubaViewerInterface.directive" +import { viewerStateSelectorNavigation, viewerStateStandAloneVolumes } from "src/services/state/viewerState/selectors"; +import { Subject } from "rxjs" +import { ngViewerActionNehubaReady } from "src/services/state/ngViewerState/actions" + +describe('> nehubaViewerInterface.directive.ts', () => { + describe('> NehubaViewerContainerDirective', () => { + + @Component({ + template: '' + }) + class DummyCmp{} + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + + ], + declarations: [ + NehubaViewerContainerDirective, + DummyCmp, + NehubaViewerUnit, + ], + providers: [ + provideMockStore({ initialState: {} }) + ] + }).overrideModule(BrowserDynamicTestingModule,{ + set: { + entryComponents: [ + NehubaViewerUnit + ] + } + }).overrideComponent(DummyCmp, { + set: { + template: ` + <div iav-nehuba-viewer-container> + </div> + ` + } + }).compileComponents() + + })) + + beforeEach(() => { + const mockStore = TestBed.inject(MockStore) + mockStore.overrideSelector(ngViewerSelectorOctantRemoval, true) + mockStore.overrideSelector(viewerStateStandAloneVolumes, []) + mockStore.overrideSelector(viewerStateSelectorNavigation, null) + }) + + it('> can be inited', () => { + + const fixture = TestBed.createComponent(DummyCmp) + fixture.detectChanges() + const directive = fixture.debugElement.query( + By.directive(NehubaViewerContainerDirective) + ) + + expect(directive).toBeTruthy() + }) + + describe('> on createNehubaInstance', () => { + let fixture: ComponentFixture<DummyCmp> + let directiveInstance: NehubaViewerContainerDirective + let nehubaViewerInstanceSpy: jasmine.Spy + let elClearSpy: jasmine.Spy + let elCreateComponentSpy: jasmine.Spy + const spyNehubaViewerInstance = { + config: null, + lifecycle: null, + templateId: null, + errorEmitter: new Subject(), + debouncedViewerPositionChange: new Subject(), + layersChanged: new Subject(), + nehubaReady: new Subject(), + mouseoverSegmentEmitter: new Subject(), + mouseoverLandmarkEmitter: new Subject(), + mouseoverUserlandmarkEmitter: new Subject(), + elementRef: { + nativeElement: {} + } + } + const spyComRef = { + destroy: jasmine.createSpy('destroy') + } + + beforeEach(() => { + fixture = TestBed.createComponent(DummyCmp) + const directive = fixture.debugElement.query( + By.directive(NehubaViewerContainerDirective) + ) + + directiveInstance = directive.injector.get(NehubaViewerContainerDirective) + + nehubaViewerInstanceSpy = spyOnProperty(directiveInstance, 'nehubaViewerInstance').and.returnValue(spyNehubaViewerInstance) + elClearSpy = spyOn(directiveInstance['el'], 'clear') + // casting return value to any is not perfect, but since only 2 methods and 1 property is used, it's a quick way + // rather than allow component to be created + elCreateComponentSpy = spyOn(directiveInstance['el'], 'createComponent').and.returnValue(spyComRef as any) + }) + + describe('> on createNehubaInstance called', () => { + const template = {} + const lifecycle = {} + beforeEach(() => { + directiveInstance.createNehubaInstance(template, lifecycle) + }) + it('> method el.clear gets called before el.createComponent', () => { + expect(elClearSpy).toHaveBeenCalledBefore(elCreateComponentSpy) + }) + }) + + describe('> on clear called', () => { + it('> dispatches nehubaReady: false action', () => { + const mockStore = TestBed.inject(MockStore) + const mockStoreDispatchSpy = spyOn(mockStore, 'dispatch') + directiveInstance.clear() + expect( + mockStoreDispatchSpy + ).toHaveBeenCalledWith( + ngViewerActionNehubaReady({ + nehubaReady: false + }) + ) + }) + + it('> iavNehubaViewerContainerViewerLoading emits false', () => { + const emitSpy = spyOn(directiveInstance.iavNehubaViewerContainerViewerLoading, 'emit') + directiveInstance.clear() + expect(emitSpy).toHaveBeenCalledWith(false) + }) + + it('> elClear called', () => { + directiveInstance.clear() + expect(elClearSpy).toHaveBeenCalled() + }) + }) + }) + }) +}) diff --git a/src/ui/nehubaContainer/nehubaViewerInterface/nehubaViewerInterface.directive.ts b/src/ui/nehubaContainer/nehubaViewerInterface/nehubaViewerInterface.directive.ts index c4748e090..49c2e9316 100644 --- a/src/ui/nehubaContainer/nehubaViewerInterface/nehubaViewerInterface.directive.ts +++ b/src/ui/nehubaContainer/nehubaViewerInterface/nehubaViewerInterface.directive.ts @@ -5,14 +5,13 @@ import { IavRootStoreInterface } from "src/services/stateStore.service"; import { Subscription, Observable, fromEvent } from "rxjs"; import { distinctUntilChanged, filter, debounceTime, shareReplay, scan, map, throttleTime, switchMapTo } from "rxjs/operators"; import { StateInterface as ViewerConfigStateInterface } from "src/services/state/viewerConfig.store"; -import { getNavigationStateFromConfig } from "../util"; -import { NEHUBA_LAYER_CHANGED, CHANGE_NAVIGATION, VIEWERSTATE_ACTION_TYPES } from "src/services/state/viewerState.store"; +import { getNavigationStateFromConfig, takeOnePipe } from "../util"; +import { NEHUBA_LAYER_CHANGED, CHANGE_NAVIGATION } from "src/services/state/viewerState.store"; import { timedValues } from "src/util/generator"; import { MOUSE_OVER_SEGMENTS, MOUSE_OVER_LANDMARK } from "src/services/state/uiState.store"; -import { takeOnePipe } from "../nehubaContainer.component"; import { ngViewerActionNehubaReady } from "src/services/state/ngViewerState/actions"; import { viewerStateMouseOverCustomLandmarkInPerspectiveView } from "src/services/state/viewerState/actions"; -import { viewerStateStandAloneVolumes } from "src/services/state/viewerState/selectors"; +import { viewerStateStandAloneVolumes, viewerStateSelectorNavigation } from "src/services/state/viewerState/selectors"; import { ngViewerSelectorOctantRemoval } from "src/services/state/ngViewerState/selectors"; const defaultNehubaConfig = { @@ -284,15 +283,10 @@ export class NehubaViewerContainerDirective implements OnInit, OnDestroy{ */ distinctUntilChanged(), ) - - const viewerState$ = this.store$.pipe( - select('viewerState'), - shareReplay(1) - ) - - this.navigationChanges$ = viewerState$.pipe( - select('navigation'), - filter(v => !!v) + + this.navigationChanges$ = this.store$.pipe( + select(viewerStateSelectorNavigation), + filter(v => !!v), ) this.nehubaViewerPerspectiveOctantRemoval$ = this.store$.pipe( @@ -482,6 +476,13 @@ export class NehubaViewerContainerDirective implements OnInit, OnDestroy{ while(this.nehubaViewerSubscriptions.length > 0) { this.nehubaViewerSubscriptions.pop().unsubscribe() } + + this.store$.dispatch( + ngViewerActionNehubaReady({ + nehubaReady: false, + }) + ) + this.iavNehubaViewerContainerViewerLoading.emit(false) if(this.cr) this.cr.destroy() this.el.clear() diff --git a/src/ui/nehubaContainer/util.ts b/src/ui/nehubaContainer/util.ts index 7efb0abcc..49d713641 100644 --- a/src/ui/nehubaContainer/util.ts +++ b/src/ui/nehubaContainer/util.ts @@ -1,4 +1,7 @@ +import { pipe } from 'rxjs' +import { filter, scan, take } from 'rxjs/operators' import { PANELS } from 'src/services/state/ngViewerState.store.helper' +import { getViewer } from 'src/util/fn' const flexContCmnCls = ['w-100', 'h-100', 'd-flex', 'justify-content-center', 'align-items-stretch'] @@ -250,3 +253,35 @@ export const scanSliceViewRenderFn: (acc: [boolean, boolean, boolean], curr: Cus } return returnAcc as [boolean, boolean, boolean] } + +export const takeOnePipe = () => { + + return pipe( + scan((acc: Event[], event: Event) => { + const target = (event as Event).target as HTMLElement + /** + * 0 | 1 + * 2 | 3 + * + * 4 ??? + */ + const panels = getViewer()['display']['panels'] + const panelEls = Array.from(panels).map(({ element }) => element) + + const identifySrcElement = (element: HTMLElement) => { + const idx = panelEls.indexOf(element) + return idx + } + + const key = identifySrcElement(target) + const _ = {} + _[key] = event + return Object.assign({}, acc, _) + }, []), + filter(v => { + const isdefined = (obj) => typeof obj !== 'undefined' && obj !== null + return (isdefined(v[0]) && isdefined(v[1]) && isdefined(v[2])) + }), + take(1), + ) +} -- GitLab