From 29c31f414446df8de15bde79c8e5588a7bd89e5f Mon Sep 17 00:00:00 2001 From: Xiao Gui <xgui3783@gmail.com> Date: Fri, 30 Oct 2020 13:19:37 +0100 Subject: [PATCH] bugfix: status panel causes nav errors (#707) --- src/services/state/viewerState.store.ts | 4 +- src/services/state/viewerState/actions.ts | 4 + src/services/state/viewerState/selectors.ts | 13 ++ .../statusCard/statusCard.component.spec.ts | 160 +++++++++++++----- .../statusCard/statusCard.component.ts | 105 +++++------- 5 files changed, 180 insertions(+), 106 deletions(-) diff --git a/src/services/state/viewerState.store.ts b/src/services/state/viewerState.store.ts index 63fefee54..daa6fe5e7 100644 --- a/src/services/state/viewerState.store.ts +++ b/src/services/state/viewerState.store.ts @@ -25,6 +25,7 @@ import { viewerStateNewViewer } from './viewerState.store.helper'; import { cvtNehubaConfigToNavigationObj } from 'src/ui/viewerStateController/viewerState.useEffect'; +import { viewerStateChangeNavigation } from './viewerState/actions'; export interface StateInterface { fetchedTemplates: any[] @@ -162,6 +163,7 @@ export const getStateStore = ({ state = defaultState } = {}) => (prevState: Part fetchedTemplates: prevState.fetchedTemplates.concat(action.fetchedTemplate), } } + case viewerStateChangeNavigation.type: case CHANGE_NAVIGATION : { return { ...prevState, @@ -271,7 +273,7 @@ export const UNLOAD_DEDICATED_LAYER = 'UNLOAD_DEDICATED_LAYER' export const NEWVIEWER = viewerStateNewViewer.type export const FETCHED_TEMPLATE = 'FETCHED_TEMPLATE' -export const CHANGE_NAVIGATION = 'CHANGE_NAVIGATION' +export const CHANGE_NAVIGATION = viewerStateChangeNavigation.type export const SELECT_PARCELLATION = viewerStateSelectParcellation.type diff --git a/src/services/state/viewerState/actions.ts b/src/services/state/viewerState/actions.ts index 4da844663..1b2efd6f7 100644 --- a/src/services/state/viewerState/actions.ts +++ b/src/services/state/viewerState/actions.ts @@ -105,3 +105,7 @@ export const viewerStateMouseOverCustomLandmarkInPerspectiveView = createAction( props<{ payload: { label: string } }>() ) +export const viewerStateChangeNavigation = createAction( + `[viewerState] changeNavigation`, + props<{ navigation: any }>() +) diff --git a/src/services/state/viewerState/selectors.ts b/src/services/state/viewerState/selectors.ts index 44c4c8db7..778310c1e 100644 --- a/src/services/state/viewerState/selectors.ts +++ b/src/services/state/viewerState/selectors.ts @@ -1,4 +1,5 @@ import { createSelector } from "@ngrx/store" +import { create } from "domain" import { viewerStateHelperStoreName } from "../viewerState.store.helper" export const viewerStateSelectedRegionsSelector = createSelector( @@ -32,6 +33,18 @@ export const viewerStateSelectedTemplateSelector = createSelector( viewerState => viewerState['templateSelected'] ) +/** + * viewerStateSelectedTemplateSelector may have it navigation mutated to allow for initiliasation of viewer at the correct navigation + * in some circumstances, it may be required to get the original navigation object + */ +export const viewerStateSelectedTemplatePureSelector = createSelector( + viewerStateFetchedTemplatesSelector, + viewerStateSelectedTemplateSelector, + (fetchedTemplates, selectedTemplate) => { + return fetchedTemplates.find(t => t['@id'] === selectedTemplate('@id')) + } +) + export const viewerStateSelectedParcellationSelector = createSelector( state => state['viewerState'], viewerState => viewerState['parcellationSelected'] diff --git a/src/ui/nehubaContainer/statusCard/statusCard.component.spec.ts b/src/ui/nehubaContainer/statusCard/statusCard.component.spec.ts index 5931d994a..8db6d1a4e 100644 --- a/src/ui/nehubaContainer/statusCard/statusCard.component.spec.ts +++ b/src/ui/nehubaContainer/statusCard/statusCard.component.spec.ts @@ -1,9 +1,9 @@ -import { async, TestBed } from "@angular/core/testing" +import { async, ComponentFixture, TestBed } from "@angular/core/testing" import { CommonModule } from "@angular/common" import { AngularMaterialModule } from "src/ui/sharedModules/angularMaterial.module" import { StatusCardComponent } from "./statusCard.component" import { Directive, Component } from "@angular/core" -import { of } from "rxjs" +import { Observable, of } from "rxjs" import { ShareModule } from "src/share" import { StateModule } from "src/state" import { MockStore, provideMockStore } from "@ngrx/store/testing" @@ -13,6 +13,9 @@ import { NoopAnimationsModule } from "@angular/platform-browser/animations" import { FormsModule, ReactiveFormsModule } from "@angular/forms" import { UtilModule } from "src/util" import { viewerConfigSelectorUseMobileUi } from "src/services/state/viewerConfig.store.helper" +import { viewerStateNavigationStateSelector, viewerStateSelectedTemplatePureSelector } from "src/services/state/viewerState/selectors" +import * as util from '../util' +import { viewerStateChangeNavigation } from "src/services/state/viewerState/actions" @Directive({ selector: '[iav-auth-authState]', @@ -73,55 +76,126 @@ describe('> statusCard.component.ts', () => { expect(fixture.debugElement.nativeElement).toBeTruthy() }) - it('> toggle can be found if showFull is set to true', () => { - - const fixture = TestBed.createComponent(StatusCardComponent) - fixture.detectChanges() - fixture.componentInstance.showFull = true - fixture.detectChanges() - - const slider = fixture.debugElement.query( By.directive(MatSlideToggle) ) - expect(slider).toBeTruthy() - }) + describe('> in full mode, UIs are visible', () => { + let fixture: ComponentFixture<StatusCardComponent> - it('> toggling voxel/real toggle also toggles statusPanelRealSpace flag', () => { + beforeEach(() => { - const fixture = TestBed.createComponent(StatusCardComponent) - fixture.detectChanges() - fixture.componentInstance.showFull = true - fixture.detectChanges() + fixture = TestBed.createComponent(StatusCardComponent) + fixture.detectChanges() + fixture.componentInstance.showFull = true + fixture.detectChanges() + }) + + it('> toggle can be found', () => { - const prevFlag = fixture.componentInstance.statusPanelRealSpace - const sliderEl = fixture.debugElement.query( By.directive(MatSlideToggle) ) - const slider = sliderEl.injector.get(MatSlideToggle) - slider.toggle() - fixture.detectChanges() - expect(fixture.componentInstance.statusPanelRealSpace).toEqual(!prevFlag) + const slider = fixture.debugElement.query( By.directive(MatSlideToggle) ) + expect(slider).toBeTruthy() + }) + + it('> toggling voxel/real toggle also toggles statusPanelRealSpace flag', () => { + + const prevFlag = fixture.componentInstance.statusPanelRealSpace + const sliderEl = fixture.debugElement.query( By.directive(MatSlideToggle) ) + const slider = sliderEl.injector.get(MatSlideToggle) + slider.toggle() + fixture.detectChanges() + expect(fixture.componentInstance.statusPanelRealSpace).toEqual(!prevFlag) + }) + + describe('> textNavigationTo', () => { + it('> takes into account of statusPanelRealSpace panel', () => { + const setNavigationStateSpy = jasmine.createSpy('setNavigationState') + fixture.componentInstance.nehubaViewer = { + setNavigationState: setNavigationStateSpy + } as any + + fixture.componentInstance.statusPanelRealSpace = true + fixture.componentInstance.textNavigateTo('1, 0, 0') + expect(setNavigationStateSpy).toHaveBeenCalledWith({ + position: [1e6, 0, 0], + positionReal: true + }) + + fixture.componentInstance.statusPanelRealSpace = false + fixture.componentInstance.textNavigateTo('1, 0, 0') + expect(setNavigationStateSpy).toHaveBeenCalledWith({ + position: [1, 0, 0], + positionReal: false + }) + }) + }) }) - describe('> textNavigationTo', () => { - it('> takes into account of statusPanelRealSpace panel', () => { - const fixture = TestBed.createComponent(StatusCardComponent) + describe('> resetNavigation', () => { + let fixture: ComponentFixture<StatusCardComponent> + const mockCurrNavigation = { + orientation: [1,0,0,0], + position: [100,200,300], + perspectiveZoom: 1e9, + zoom: 1e9, + perspectiveOrientation: [1,0,0,0] + } + + const mockNavState = { + orientation: [0,0,0,1], + position: [10,20,30], + perspectiveZoom: 1e6, + zoom: 1e6, + perspectiveOrientation: [0,0,0,1] + } + const mockTemplate = { foo:'bar', nehubaConfig: { foo2: 'bar2' } } + + let getNavigationStateFromConfigSpy: jasmine.Spy = jasmine.createSpy('getNavigationStateFromConfig').and.returnValue(mockNavState) + + beforeEach(() => { + const mockStore = TestBed.inject(MockStore) + mockStore.overrideSelector(viewerStateSelectedTemplatePureSelector, mockTemplate) + mockStore.overrideSelector(viewerStateNavigationStateSelector, mockCurrNavigation) + + spyOnProperty(util, 'getNavigationStateFromConfig').and.returnValue(getNavigationStateFromConfigSpy) + + fixture = TestBed.createComponent(StatusCardComponent) fixture.detectChanges() - const setNavigationStateSpy = jasmine.createSpy('setNavigationState') - fixture.componentInstance.nehubaViewer = { - setNavigationState: setNavigationStateSpy - } as any - - fixture.componentInstance.statusPanelRealSpace = true - fixture.componentInstance.textNavigateTo('1, 0, 0') - expect(setNavigationStateSpy).toHaveBeenCalledWith({ - position: [1e6, 0, 0], - positionReal: true - }) + fixture.componentInstance.showFull = true + fixture.detectChanges() + }) + + for (const method of ['rotation', 'position', 'zoom' ]) { + describe(`> method: ${method}`, () => { + + it(`> resetNavigation call calls getNavigationStateFromConfig`, () => { + fixture.componentInstance.resetNavigation({ [method]: true, }) + fixture.detectChanges() + expect(getNavigationStateFromConfigSpy).toHaveBeenCalled() + }) - fixture.componentInstance.statusPanelRealSpace = false - fixture.componentInstance.textNavigateTo('1, 0, 0') - expect(setNavigationStateSpy).toHaveBeenCalledWith({ - position: [1, 0, 0], - positionReal: false + it('> resetNavigation dispatches correct action', () => { + + const mockStore = TestBed.inject(MockStore) + const idspatchSpy = spyOn(mockStore, 'dispatch') + fixture.componentInstance.resetNavigation({ [method]: true, }) + fixture.detectChanges() + + const overrideObj = {} + if (method === 'rotation') overrideObj['orientation'] = mockNavState['orientation'] + if (method === 'position') overrideObj['position'] = mockNavState['position'] + if (method === 'zoom') overrideObj['zoom'] = mockNavState['zoom'] + expect(idspatchSpy).toHaveBeenCalledWith( + viewerStateChangeNavigation({ + navigation: { + ...mockCurrNavigation, + ...overrideObj, + positionReal: false, + animation: {}, + } + }) + ) + }) }) - }) + } }) + + }) }) diff --git a/src/ui/nehubaContainer/statusCard/statusCard.component.ts b/src/ui/nehubaContainer/statusCard/statusCard.component.ts index b89bc87da..8f14652f7 100644 --- a/src/ui/nehubaContainer/statusCard/statusCard.component.ts +++ b/src/ui/nehubaContainer/statusCard/statusCard.component.ts @@ -1,15 +1,18 @@ import { Component, Input, OnInit, OnChanges, TemplateRef, HostBinding } from "@angular/core"; import { select, Store } from "@ngrx/store"; import { LoggingService } from "src/logging"; -import { CHANGE_NAVIGATION, IavRootStoreInterface, ViewerStateInterface } from "src/services/stateStore.service"; +import { IavRootStoreInterface } from "src/services/stateStore.service"; import { NehubaViewerUnit } from "../nehubaViewer/nehubaViewer.component"; -import { Observable, Subscription, of, combineLatest, BehaviorSubject } from "rxjs"; -import { distinctUntilChanged, shareReplay, map, filter, startWith } from "rxjs/operators"; +import { Observable, Subscription, of, combineLatest } from "rxjs"; +import { map, filter, startWith } from "rxjs/operators"; import { MatBottomSheet } from "@angular/material/bottom-sheet"; import { MatDialog } from "@angular/material/dialog"; import { ARIA_LABELS } from 'common/constants' import { PureContantService } from "src/util"; import { FormControl } from "@angular/forms"; +import { viewerStateNavigationStateSelector, viewerStateSelectedTemplatePureSelector } from "src/services/state/viewerState/selectors"; +import { getNavigationStateFromConfig } from "../util"; +import { viewerStateChangeNavigation } from "src/services/state/viewerState/actions"; @Component({ selector : 'ui-status-card', @@ -25,8 +28,8 @@ export class StatusCardComponent implements OnInit, OnChanges{ public arialabel = ARIA_LABELS.STATUS_PANEL public showFull = false - private selectedTemplateRoot$: Observable<any> - private selectedTemplateRoot: any + private selectedTemplatePure: any + private currentNavigation: any private subscriptions: Subscription[] = [] public navVal$: Observable<string> @@ -41,36 +44,32 @@ export class StatusCardComponent implements OnInit, OnChanges{ public SHOW_FULL_STATUS_PANEL_ARIA_LABEL = ARIA_LABELS.SHOW_FULL_STATUS_PANEL public HIDE_FULL_STATUS_PANEL_ARIA_LABEL = ARIA_LABELS.HIDE_FULL_STATUS_PANEL constructor( - private store: Store<ViewerStateInterface>, - private log: LoggingService, private store$: Store<IavRootStoreInterface>, + private log: LoggingService, private bottomSheet: MatBottomSheet, private dialog: MatDialog, private pureConstantService: PureContantService ) { - const viewerState$ = this.store$.pipe( - select('viewerState'), - shareReplay(1), - ) - this.selectedTemplateRoot$ = viewerState$.pipe( - select('fetchedTemplates'), - distinctUntilChanged(), - ) - this.useTouchInterface$ = this.pureConstantService.useTouchUI$ } ngOnInit(): void { this.subscriptions.push( - this.selectedTemplateRoot$.subscribe(template => { - this.selectedTemplateRoot = template.find(t => t.name === this.selectedTemplateName) + this.statusPanelFormCtrl.valueChanges.subscribe(val => { + this.statusPanelRealSpace = val }) ) this.subscriptions.push( - this.statusPanelFormCtrl.valueChanges.subscribe(val => { - this.statusPanelRealSpace = val - }) + this.store$.pipe( + select(viewerStateSelectedTemplatePureSelector) + ).subscribe(n => this.selectedTemplatePure = n) + ) + + this.subscriptions.push( + this.store$.pipe( + select(viewerStateNavigationStateSelector) + ).subscribe(nav => this.currentNavigation = nav) ) } @@ -80,7 +79,7 @@ export class StatusCardComponent implements OnInit, OnChanges{ this.mouseVal$ = of(`neubaViewer is undefined`) return } - this.navVal$ = combineLatest( + this.navVal$ = combineLatest([ this.statusPanelRealSpace$, this.nehubaViewer.viewerPosInReal$.pipe( filter(v => !!v) @@ -88,14 +87,14 @@ export class StatusCardComponent implements OnInit, OnChanges{ this.nehubaViewer.viewerPosInVoxel$.pipe( filter(v => !!v) ) - ).pipe( + ]).pipe( map(([realFlag, real, voxel]) => realFlag ? real.map(v => `${ (v / 1e6).toFixed(3) }mm`).join(', ') : voxel.map(v => v.toFixed(3)).join(', ') ), startWith(`nehubaViewer initialising`) ) - this.mouseVal$ = combineLatest( + this.mouseVal$ = combineLatest([ this.statusPanelRealSpace$, this.nehubaViewer.mousePosInReal$.pipe( filter(v => !!v) @@ -103,7 +102,7 @@ export class StatusCardComponent implements OnInit, OnChanges{ this.nehubaViewer.mousePosInVoxel$.pipe( filter(v => !!v) ) - ).pipe( + ]).pipe( map(([realFlag, real, voxel]) => realFlag ? real.map(v => `${ (v/1e6).toFixed(3) }mm`).join(', ') : voxel.map(v => v.toFixed(3)).join(', ')), @@ -111,7 +110,7 @@ export class StatusCardComponent implements OnInit, OnChanges{ ) } - statusPanelFormCtrl = new FormControl(true, []) + public statusPanelFormCtrl = new FormControl(true, []) public statusPanelRealSpace = true public statusPanelRealSpace$ = this.statusPanelFormCtrl.valueChanges.pipe( startWith(true) @@ -143,44 +142,26 @@ export class StatusCardComponent implements OnInit, OnChanges{ * the info re: nehubaViewer can stay there, too */ public resetNavigation({rotation: rotationFlag = false, position: positionFlag = false, zoom : zoomFlag = false}: {rotation?: boolean, position?: boolean, zoom?: boolean}) { - const initialNgState = this.selectedTemplateRoot.nehubaConfig.dataset.initialNgState // d sa dsa - - const perspectiveZoom = initialNgState ? initialNgState.perspectiveZoom : undefined - const perspectiveOrientation = initialNgState ? initialNgState.perspectiveOrientation : undefined - const zoom = (zoomFlag - && initialNgState - && initialNgState.navigation - && initialNgState.navigation.zoomFactor) - || undefined - - const position = (positionFlag - && initialNgState - && initialNgState.navigation - && initialNgState.navigation.pose - && initialNgState.navigation.pose.position.voxelCoordinates - && initialNgState.navigation.pose.position.voxelCoordinates) - || undefined - - const orientation = rotationFlag - ? [0, 0, 0, 1] - : undefined - - this.store.dispatch({ - type : CHANGE_NAVIGATION, - navigation : { - ...{ - perspectiveZoom, - perspectiveOrientation, - zoom, - position, - orientation, - }, - ...{ + const { + orientation, + perspectiveOrientation, + perspectiveZoom, + position, + zoom + } = getNavigationStateFromConfig(this.selectedTemplatePure.nehubaConfig) + + this.store$.dispatch( + viewerStateChangeNavigation({ + navigation: { + ...this.currentNavigation, + ...(rotationFlag ? { orientation: orientation } : {}), + ...(positionFlag ? { position: position } : {}), + ...(zoomFlag ? { zoom: zoom } : {}), positionReal : false, animation : {}, - }, - }, - }) + } + }) + ) } openDialog(tmpl: TemplateRef<any>, options) { -- GitLab