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