diff --git a/src/services/state/ngViewerState.store.helper.ts b/src/services/state/ngViewerState.store.helper.ts index 834c5d5d53d4168a08a65c348ffbcf5e51eb5fa0..2d0ca1500648a3654e7108cc7a22588055ac50ba 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 8a9136dc4c78be7652c60b5d5a4ace514562e5e2..bc3b7fb8ce0abfcf2c3c1799a9fccf28238b71e3 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 ddfa9f306f2af4ec89a14033028ef1c7f9b60615..db84dcdb944e8e9f3d07b80a8d02464a492d4c3b 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 b7a091bdcb30bd333cca026e685b46516cbbd71c..8b5b442e3d11d50fd577328099057ac4af89a249 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 36f9e9e0d2c44c374078745bad9d1f8fd7386c23..4127b3bf990e732f1fccd4d94b69a5a0306d18f2 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 8d3828f0b5cf21b0c9105aeacddc0b1fd62667d9..875daac2488ed97b858083629eb13a6da0269d0b 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 319d2be8b85ceb2140836dc64a09b34e15183640..fd0c2ba97aacc9b0ecd1d474d93e94173a6a0dac 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 0000000000000000000000000000000000000000..91c8e5847a0b025b882057e7a2255fcad7bb5f7a --- /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 c4748e0900f9c6328a42ffbab69e4f0b2f6c446d..49c2e9316963e3e19932f0da6e7af6c868ac91ca 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 7efb0abcc211bfe381190467509e4d18ecbd2b0b..49d7136412e6d19d67bdf6774d7e6f81e9832988 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), + ) +}