From f9d10c9d6bed738fe9b7bcf15100f276dca2f1aa Mon Sep 17 00:00:00 2001
From: Xiao Gui <xgui3783@gmail.com>
Date: Fri, 16 Jul 2021 17:06:03 +0200
Subject: [PATCH] bugfix: hide pmap on clear view flag present

---
 src/glue.spec.ts                              |   4 +-
 src/util/fn.spec.ts                           |  55 ++++++-
 src/util/fn.ts                                |  32 +++-
 .../nehuba/layerCtrl.service/index.ts         |   3 +-
 .../layerCtrl.service.spec.ts                 | 124 +++++++++++++-
 .../layerCtrl.service/layerCtrl.service.ts    | 152 +++++++++++++++++-
 .../layerCtrl.service/layerCtrl.util.ts       |  31 ++++
 .../nehubaViewer/nehubaViewer.component.ts    |  73 ++++++++-
 .../nehubaViewerGlue.component.ts             | 140 ++--------------
 9 files changed, 469 insertions(+), 145 deletions(-)

diff --git a/src/glue.spec.ts b/src/glue.spec.ts
index 73f6013e0..0124829a8 100644
--- a/src/glue.spec.ts
+++ b/src/glue.spec.ts
@@ -2,15 +2,13 @@ import { TestBed, tick, fakeAsync, discardPeriodicTasks } from "@angular/core/te
 import { DatasetPreviewGlue, glueSelectorGetUiStatePreviewingFiles, glueActionRemoveDatasetPreview, datasetPreviewMetaReducer, glueActionAddDatasetPreview, GlueEffects, ClickInterceptorService } from "./glue"
 import { ACTION_TO_WIDGET_TOKEN, EnumActionToWidget } from "./widget"
 import { provideMockStore, MockStore } from "@ngrx/store/testing"
-import { getRandomHex } from 'common/util'
+import { getRandomHex, getIdObj } from 'common/util'
 import { EnumWidgetTypes, TypeOpenedWidget, uiActionSetPreviewingDatasetFiles, uiStatePreviewingDatasetFilesSelector } from "./services/state/uiState.store.helper"
 import { hot } from "jasmine-marbles"
 import { HttpClientTestingModule, HttpTestingController } from "@angular/common/http/testing"
 import { glueActionToggleDatasetPreview } from './glue'
-import { getIdObj } from 'common/util'
 import { DS_PREVIEW_URL } from 'src/util/constants'
 import { NgLayersService } from "./ui/layerbrowser/ngLayerService.service"
-import { EnumColorMapName } from "./util/colorMaps"
 import { GET_KGDS_PREVIEW_INFO_FROM_ID_FILENAME } from "./atlasComponents/databrowserModule/pure"
 import { viewerStateSelectedTemplateSelector } from "./services/state/viewerState/selectors"
 import { generalActionError } from "./services/stateStore.helper"
diff --git a/src/util/fn.spec.ts b/src/util/fn.spec.ts
index 1fe2e6469..bbdbff610 100644
--- a/src/util/fn.spec.ts
+++ b/src/util/fn.spec.ts
@@ -1,9 +1,9 @@
 import { fakeAsync, tick } from '@angular/core/testing'
 import {} from 'jasmine'
-import { cold, hot } from 'jasmine-marbles'
-import { of } from 'rxjs'
+import { hot } from 'jasmine-marbles'
+import { Observable, of } from 'rxjs'
 import { switchMap } from 'rxjs/operators'
-import { isSame, getGetRegionFromLabelIndexId, switchMapWaitFor } from './fn'
+import { isSame, getGetRegionFromLabelIndexId, switchMapWaitFor, bufferUntil } from './fn'
 
 describe(`> util/fn.ts`, () => {
 
@@ -78,4 +78,53 @@ describe(`> util/fn.ts`, () => {
       }))
     })
   })
+
+  describe('> #bufferUntil', () => {
+    let src: Observable<number>
+    beforeEach(() => {
+      src = hot('a-b-c|', {
+        a: 1,
+        b: 2,
+        c: 3,
+      })
+    })
+    it('> outputs array of original emitted value', () => {
+
+      expect(
+        src.pipe(
+          bufferUntil({
+            condition: () => true,
+            leading: true,
+          })
+        )
+      ).toBeObservable(
+        hot('a-b-c|', {
+          a: [1],
+          b: [2],
+          c: [3],
+        })
+      )
+    })
+
+    it('> on condition success, emit all in array', () => {
+
+      let counter = 0
+      expect(
+        src.pipe(
+          bufferUntil({
+            condition: () => {
+              counter ++
+              return counter > 2
+            },
+            leading: true,
+            interval: 60000,
+          })
+        )
+      ).toBeObservable(
+        hot('----c|', {
+          c: [1,2,3],
+        })
+      )
+    })
+  })
 })
diff --git a/src/util/fn.ts b/src/util/fn.ts
index cb92708d5..f4eb08ac4 100644
--- a/src/util/fn.ts
+++ b/src/util/fn.ts
@@ -1,5 +1,5 @@
 import { deserialiseParcRegionId } from 'common/util'
-import { interval, of } from 'rxjs'
+import { interval, Observable, of } from 'rxjs'
 import { filter, mapTo, take } from 'rxjs/operators'
 
 export function isSame(o, n) {
@@ -268,3 +268,33 @@ export function recursiveMutate<T>(arr: T[], getChildren: (obj: T) => T[], mutat
     )
   }
 }
+
+export function bufferUntil<T>(opts: ISwitchMapWaitFor) {
+  const { condition, leading, interval: int = 160 } = opts
+  let buffer: T[] = []
+  return (src: Observable<T>) => new Observable<T[]>(obs => {
+    const sub = interval(int).pipe(
+      filter(() => buffer.length > 0)
+    ).subscribe(() => {
+      if (condition()) {
+        obs.next(buffer)
+        buffer = []
+      }
+    })
+    src.subscribe(
+      val => {
+        if (leading && condition()) {
+          obs.next([...buffer, val])
+          buffer = []
+        } else {
+          buffer.push(val)
+        }
+      },
+      err => obs.error(err),
+      () => {
+        obs.complete()
+        sub.unsubscribe()
+      }
+    )
+  })
+}
diff --git a/src/viewerModule/nehuba/layerCtrl.service/index.ts b/src/viewerModule/nehuba/layerCtrl.service/index.ts
index 61975bcfe..05cbb34fe 100644
--- a/src/viewerModule/nehuba/layerCtrl.service/index.ts
+++ b/src/viewerModule/nehuba/layerCtrl.service/index.ts
@@ -1,5 +1,5 @@
 export {
-  NehubaLayerControlService
+  NehubaLayerControlService,
 } from './layerCtrl.service'
 
 export {
@@ -7,4 +7,5 @@ export {
   SET_COLORMAP_OBS,
   SET_LAYER_VISIBILITY,
   getRgb,
+  INgLayerInterface,
 } from './layerCtrl.util'
diff --git a/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.service.spec.ts b/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.service.spec.ts
index ffb2443d9..3d2ae0f4f 100644
--- a/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.service.spec.ts
+++ b/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.service.spec.ts
@@ -1,12 +1,13 @@
 import { fakeAsync, TestBed, tick } from "@angular/core/testing"
 import { MockStore, provideMockStore } from "@ngrx/store/testing"
-import { viewerStateSelectedParcellationSelector, viewerStateSelectedTemplateSelector } from "src/services/state/viewerState/selectors"
+import { viewerStateSelectedParcellationSelector, viewerStateSelectedRegionsSelector, viewerStateSelectedTemplateSelector } from "src/services/state/viewerState/selectors"
 import { NehubaLayerControlService } from "./layerCtrl.service"
 import * as layerCtrlUtil from '../constants'
 import { hot } from "jasmine-marbles"
 import { IColorMap } from "./layerCtrl.util"
 import { debounceTime } from "rxjs/operators"
-
+import { ngViewerSelectorClearView, ngViewerSelectorLayers } from "src/services/state/ngViewerState.store.helper"
+const  util = require('common/util')
 
 describe('> layerctrl.service.ts', () => {
   describe('> NehubaLayerControlService', () => {
@@ -296,5 +297,124 @@ describe('> layerctrl.service.ts', () => {
         }))
       })
     })
+
+    describe('> segmentVis$', () => {
+      const region1= {
+        ngId: 'ngid',
+        labelIndex: 1
+      }
+      const region2= {
+        ngId: 'ngid',
+        labelIndex: 2
+      }
+      beforeEach(() => {
+        mockStore.overrideSelector(viewerStateSelectedRegionsSelector, [])
+        mockStore.overrideSelector(ngViewerSelectorLayers, [])
+        mockStore.overrideSelector(ngViewerSelectorClearView, false)
+        mockStore.overrideSelector(viewerStateSelectedParcellationSelector, {})
+      })
+
+      it('> by default, should return []', () => {
+        const service = TestBed.inject(NehubaLayerControlService)
+        expect(service.segmentVis$).toBeObservable(
+          hot('a', {
+            a: []
+          })
+        )
+      })
+
+      describe('> if sel regions exist', () => {
+        beforeEach(() => {
+          mockStore.overrideSelector(viewerStateSelectedRegionsSelector, [
+            region1, region2
+          ])
+        })
+
+        it('> default, should return encoded strings', () => {
+          mockStore.overrideSelector(viewerStateSelectedRegionsSelector, [
+            region1, region2
+          ])
+          const service = TestBed.inject(NehubaLayerControlService)
+          expect(service.segmentVis$).toBeObservable(
+            hot('a', {
+              a: [`ngid#1`, `ngid#2`]
+            })
+          )
+        })
+
+        it('> if clearflag is true, then return []', () => {
+
+          mockStore.overrideSelector(ngViewerSelectorClearView, true)
+          const service = TestBed.inject(NehubaLayerControlService)
+          expect(service.segmentVis$).toBeObservable(
+            hot('a', {
+              a: []
+            })
+          )
+        })        
+      })
+
+      describe('> if non mixable layer exist', () => {
+        beforeEach(() => {
+          mockStore.overrideSelector(ngViewerSelectorLayers, [{
+            mixability: 'nonmixable'
+          }])
+        })
+
+        it('> default, should return null', () => {
+          const service = TestBed.inject(NehubaLayerControlService)
+          expect(service.segmentVis$).toBeObservable(
+            hot('a', {
+              a: null
+            })
+          )
+        })
+
+        it('> if regions selected, should still return null', () => {
+
+          mockStore.overrideSelector(viewerStateSelectedRegionsSelector, [
+            region1, region2
+          ])
+          const service = TestBed.inject(NehubaLayerControlService)
+          expect(service.segmentVis$).toBeObservable(
+            hot('a', {
+              a: null
+            })
+          )
+        })
+
+        describe('> if clear flag is set', () => {
+          beforeEach(() => {
+            mockStore.overrideSelector(ngViewerSelectorClearView, true)
+          })
+
+          it('> default, should return []', () => {
+            const service = TestBed.inject(NehubaLayerControlService)
+            expect(service.segmentVis$).toBeObservable(
+              hot('a', {
+                a: []
+              })
+            )
+          })
+
+          it('> if reg selected, should return []', () => {
+
+            mockStore.overrideSelector(viewerStateSelectedRegionsSelector, [
+              region1, region2
+            ])
+            const service = TestBed.inject(NehubaLayerControlService)
+            expect(service.segmentVis$).toBeObservable(
+              hot('a', {
+                a: []
+              })
+            )
+          })
+        })
+      })
+    })
+
+    describe('> ngLayersController$', () => {
+      
+    })
   })
 })
diff --git a/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.service.ts b/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.service.ts
index f0d0d7257..a7df04ef1 100644
--- a/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.service.ts
+++ b/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.service.ts
@@ -1,16 +1,17 @@
 import { Inject, Injectable, OnDestroy, Optional } from "@angular/core";
 import { select, Store } from "@ngrx/store";
 import { BehaviorSubject, combineLatest, from, merge, Observable, of, Subject, Subscription } from "rxjs";
-import { filter, map, shareReplay, switchMap, tap } from "rxjs/operators";
-import { viewerStateSelectedParcellationSelector, viewerStateSelectedTemplateSelector } from "src/services/state/viewerState/selectors";
-import { getRgb, IColorMap } from "./layerCtrl.util";
+import { distinctUntilChanged, filter, map, shareReplay, switchMap, withLatestFrom } from "rxjs/operators";
+import { viewerStateSelectedParcellationSelector, viewerStateSelectedRegionsSelector, viewerStateSelectedTemplateSelector } from "src/services/state/viewerState/selectors";
+import { getRgb, IColorMap, INgLayerCtrl, INgLayerInterface, TNgLayerCtrl } from "./layerCtrl.util";
 import { getMultiNgIdsRegionsLabelIndexMap } from "../constants";
 import { IAuxMesh } from '../store'
 import { REGION_OF_INTEREST } from "src/util/interfaces";
 import { TRegionDetail } from "src/util/siibraApiConstants/types";
 import { EnumColorMapName } from "src/util/colorMaps";
 import { getShader, PMAP_DEFAULT_CONFIG } from "src/util/constants";
-import { ngViewerActionAddNgLayer, ngViewerActionRemoveNgLayer } from "src/services/state/ngViewerState.store.helper";
+import { ngViewerActionAddNgLayer, ngViewerActionRemoveNgLayer, ngViewerSelectorClearView, ngViewerSelectorLayers } from "src/services/state/ngViewerState.store.helper";
+import { serialiseParcellationRegion } from 'common/util'
 
 export function getAuxMeshesAndReturnIColor(auxMeshes: IAuxMesh[]): IColorMap{
   const returnVal: IColorMap = {}
@@ -182,6 +183,31 @@ export class NehubaLayerControlService implements OnDestroy{
         })
       )
     }
+
+    this.sub.push(
+      this.ngLayers$.subscribe(({ ngLayers }) => {
+        this.ngLayersRegister.layers = ngLayers
+      })
+    )
+
+    this.sub.push(
+      this.store$.pipe(
+        select(ngViewerSelectorClearView),
+        distinctUntilChanged()
+      ).subscribe(flag => {
+        const pmapLayer = this.ngLayersRegister.layers.find(l => l.name === NehubaLayerControlService.PMAP_LAYER_NAME)
+        if (!pmapLayer) return
+        const payload = {
+          type: 'update',
+          payload: {
+            [NehubaLayerControlService.PMAP_LAYER_NAME]: {
+              visible: !flag
+            }
+          }
+        } as TNgLayerCtrl<'update'>
+        this.manualNgLayersControl$.next(payload)
+      })
+    )
   }
 
   public activeColorMap: IColorMap
@@ -216,4 +242,122 @@ export class NehubaLayerControlService implements OnDestroy{
       return Array.from(ngIdSet)
     })
   )
+
+  /**
+   * define when shown segments should be updated
+   */
+  public segmentVis$: Observable<string[]> = combineLatest([
+    /**
+     * selectedRegions
+     */
+    this.store$.pipe(
+      select(viewerStateSelectedRegionsSelector)
+    ),
+    /**
+     * if layer contains non mixable layer
+     */
+    this.store$.pipe(
+      select(ngViewerSelectorLayers),
+      map(layers => layers.findIndex(l => l.mixability === 'nonmixable') >= 0),
+    ),
+    /**
+     * clearviewqueue, indicating something is controlling colour map
+     * show all seg
+     */
+    this.store$.pipe(
+      select(ngViewerSelectorClearView),
+      distinctUntilChanged()
+    )
+  ]).pipe(
+    withLatestFrom(this.selectedParcellation$),
+    map(([[ regions, nonmixableLayerExists, clearViewFlag ], selParc]) => {
+      if (nonmixableLayerExists && !clearViewFlag) {
+        return null
+      }
+      const { ngId: defaultNgId } = selParc || {}
+  
+      /* selectedregionindexset needs to be updated regardless of forceshowsegment */
+      const selectedRegionIndexSet = new Set<string>(regions.map(({ngId = defaultNgId, labelIndex}) => serialiseParcellationRegion({ ngId, labelIndex })))
+      if (selectedRegionIndexSet.size > 0 && !clearViewFlag) {
+        return [...selectedRegionIndexSet]
+      } else {
+        return []
+      }
+    })
+  )
+
+  /**
+   * ngLayers controller
+   */
+
+  private ngLayersRegister: {layers: INgLayerInterface[]} = {
+    layers: []
+  }
+  public removeNgLayers(layerNames: string[]) {
+    this.ngLayersRegister.layers
+      .filter(layer => layerNames?.findIndex(l => l === layer.name) >= 0)
+      .map(l => l.name)
+      .forEach(layerName => {
+        this.store$.dispatch(ngViewerActionRemoveNgLayer({
+          layer: {
+            name: layerName
+          }
+        }))
+      })
+  }
+  public addNgLayer(layers: INgLayerInterface[]){
+    this.store$.dispatch(ngViewerActionAddNgLayer({
+      layer: layers
+    }))
+  }
+  private ngLayers$ = this.store$.pipe(
+    select(ngViewerSelectorLayers),
+    map((ngLayers: INgLayerInterface[]) => {
+      const newLayers = ngLayers.filter(l => {
+        const registeredLayerNames = this.ngLayersRegister.layers.map(l => l.name)
+        return !registeredLayerNames.includes(l.name)
+      })
+      const removeLayers = this.ngLayersRegister.layers.filter(l => {
+        const stateLayerNames = ngLayers.map(l => l.name)
+        return !stateLayerNames.includes(l.name)
+      })
+      return { newLayers, removeLayers, ngLayers }
+    }),
+    shareReplay(1)
+  )
+  private manualNgLayersControl$ = new Subject<TNgLayerCtrl<keyof INgLayerCtrl>>()
+  ngLayersController$: Observable<TNgLayerCtrl<keyof INgLayerCtrl>> = merge(
+    this.ngLayers$.pipe(
+      map(({ newLayers }) => newLayers),
+      filter(layers => layers.length > 0),
+      map(newLayers => {
+
+        const newLayersObj: any = {}
+        newLayers.forEach(({ name, source, ...rest }) => newLayersObj[name] = {
+          ...rest,
+          source,
+          // source: getProxyUrl(source),
+          // ...getProxyOther({source})
+        })
+  
+        return {
+          type: 'add',
+          payload: newLayersObj
+        } as TNgLayerCtrl<'add'>
+      })
+    ),
+    this.ngLayers$.pipe(
+      map(({ removeLayers }) => removeLayers),
+      filter(layers => layers.length > 0),
+      map(removeLayers => {
+        const removeLayerNames = removeLayers.map(v => v.name)
+        return {
+          type: 'remove',
+          payload: { names: removeLayerNames }
+        } as TNgLayerCtrl<'remove'>
+      })
+    ),
+    this.manualNgLayersControl$,
+  ).pipe(
+  )
 }
diff --git a/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.util.ts b/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.util.ts
index 9ad22f97d..a815d3c68 100644
--- a/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.util.ts
+++ b/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.util.ts
@@ -36,5 +36,36 @@ export function getRgb(labelIndex: number, region: { rgb?: [number, number, numb
   }
 }
 
+
+export interface INgLayerCtrl {
+  remove: {
+    names: string[]
+  }
+  add: {
+    [key: string]: INgLayerInterface
+  }
+  update: {
+    [key: string]: INgLayerInterface
+  }
+}
+
+export type TNgLayerCtrl<T extends keyof INgLayerCtrl> = {
+  type: T
+  payload: INgLayerCtrl[T]
+}
+
 export const SET_COLORMAP_OBS = new InjectionToken<Observable<IColorMap>>('SET_COLORMAP_OBS')
 export const SET_LAYER_VISIBILITY = new InjectionToken<Observable<string[]>>('SET_LAYER_VISIBILITY')
+export const SET_SEGMENT_VISIBILITY = new InjectionToken<Observable<string[]>>('SET_SEGMENT_VISIBILITY')
+export const NG_LAYER_CONTROL = new InjectionToken<TNgLayerCtrl<keyof INgLayerCtrl>>('NG_LAYER_CONTROL')
+
+export interface INgLayerInterface {
+  name: string // displayName
+  source: string
+  mixability: string // base | mixable | nonmixable
+  annotation?: string //
+  id?: string // unique identifier
+  visible?: boolean
+  shader?: string
+  transform?: any
+}
diff --git a/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.ts b/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.ts
index d6e317036..23adf8d1c 100644
--- a/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.ts
+++ b/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.ts
@@ -4,7 +4,7 @@ import { debounceTime, filter, map, scan, startWith, mapTo, switchMap, take, ski
 import { AtlasWorkerService } from "src/atlasViewer/atlasViewer.workerService.service";
 import { StateInterface as ViewerConfiguration } from "src/services/state/viewerConfig.store";
 import { LoggingService } from "src/logging";
-import { getExportNehuba, getViewer, setNehubaViewer, switchMapWaitFor } from "src/util/fn";
+import { bufferUntil, getExportNehuba, getViewer, setNehubaViewer, switchMapWaitFor } from "src/util/fn";
 import { NEHUBA_INSTANCE_INJTKN, scanSliceViewRenderFn } from "../util";
 import { deserialiseParcRegionId } from 'common/util'
 import { IMeshesToLoad, SET_MESHES_TO_LOAD } from "../constants";
@@ -12,6 +12,7 @@ import { IColorMap, SET_COLORMAP_OBS, SET_LAYER_VISIBILITY } from "../layerCtrl.
 
 import '!!file-loader?context=third_party&name=main.bundle.js!export-nehuba/dist/min/main.bundle.js'
 import '!!file-loader?context=third_party&name=chunk_worker.bundle.js!export-nehuba/dist/min/chunk_worker.bundle.js'
+import { INgLayerCtrl, NG_LAYER_CONTROL, SET_SEGMENT_VISIBILITY, TNgLayerCtrl } from "../layerCtrl.service/layerCtrl.util";
 
 const NG_LANDMARK_LAYER_NAME = 'spatial landmark layer'
 const NG_USER_LANDMARK_LAYER_NAME = 'user landmark layer'
@@ -151,6 +152,8 @@ export class NehubaViewerUnit implements OnInit, OnDestroy {
     @Optional() @Inject(SET_MESHES_TO_LOAD) private injSetMeshesToLoad$: Observable<IMeshesToLoad>,
     @Optional() @Inject(SET_COLORMAP_OBS) private setColormap$: Observable<IColorMap>,
     @Optional() @Inject(SET_LAYER_VISIBILITY) private layerVis$: Observable<string[]>,
+    @Optional() @Inject(SET_SEGMENT_VISIBILITY) private segVis$: Observable<string[]>,
+    @Optional() @Inject(NG_LAYER_CONTROL) private layerCtrl$: Observable<TNgLayerCtrl<keyof INgLayerCtrl>>,
   ) {
 
     if (this.nehubaViewer$) {
@@ -328,6 +331,56 @@ export class NehubaViewerUnit implements OnInit, OnDestroy {
     } else {
       this.log.error(`SET_LAYER_VISIBILITY not provided`)
     }
+
+    if (this.segVis$) {
+      this.ondestroySubscriptions.push(
+        this.segVis$.pipe().subscribe(val => {
+          // null === hide all seg
+          if (val === null) {
+            this.hideAllSeg()
+            return
+          }
+          // empty array === show all seg
+          if (val.length === 0) {
+            this.showAllSeg()
+            return
+          }
+          // show limited seg
+          this.showSegs(val)
+        })
+      )
+    } else {
+      this.log.error(`SET_SEGMENT_VISIBILITY not provided`)
+    }
+
+    if (this.layerCtrl$) {
+      this.ondestroySubscriptions.push(
+        this.layerCtrl$.pipe(
+          bufferUntil(({
+            condition: () => !!this.nehubaViewer?.ngviewer
+          }))
+        ).subscribe(messages => {
+          for (const message of messages) {
+            if (message.type === 'add') {
+              const p = message as TNgLayerCtrl<'add'>
+              this.loadLayer(p.payload)
+            }
+            if (message.type === 'remove') {
+              const p = message as TNgLayerCtrl<'remove'>
+              for (const name of p.payload.names){
+                this.removeLayer({ name })
+              }
+            }
+            if (message.type === 'update') {
+              const p = message as TNgLayerCtrl<'update'>
+              this.updateLayer(p.payload)
+            }
+          }
+        })
+      )
+    } else {
+      this.log.error(`NG_LAYER_CONTROL not provided`)
+    }
   }
 
   public numMeshesToBeLoaded: number = 0
@@ -391,12 +444,6 @@ export class NehubaViewerUnit implements OnInit, OnDestroy {
   }
 
   private loadMeshes$: ReplaySubject<{labelIndicies: number[], layer: { name: string }}> = new ReplaySubject()
-  private loadMeshes(labelIndicies: number[], { name }) {
-    this.loadMeshes$.next({
-      labelIndicies,
-      layer: { name },
-    })
-  }
 
   public mouseOverSegment: number | null
   public mouseOverLayer: {name: string, url: string}| null
@@ -658,6 +705,18 @@ export class NehubaViewerUnit implements OnInit, OnDestroy {
       })
   }
 
+  public updateLayer(layerObj: INgLayerCtrl['update']) {
+
+    const viewer = this.nehubaViewer.ngviewer
+
+    for (const layerName in layerObj) {
+      const layer = viewer.layerManager.getLayerByName(layerName)
+      if (!layer) continue
+      const { visible } = layerObj[layerName]
+      layer.setVisible(!!visible)
+    }
+  }
+
   public hideAllSeg() {
     if (!this.nehubaViewer) { return }
     Array.from(this.multiNgIdsLabelIndexMap.keys()).forEach(ngId => {
diff --git a/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts b/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts
index 370eb9242..2ac10c76f 100644
--- a/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts
+++ b/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts
@@ -2,13 +2,13 @@
 import { AfterViewInit, Component, ElementRef, EventEmitter, Inject, Input, OnChanges, OnDestroy, Optional, Output, SimpleChanges, ViewChild } from "@angular/core";
 import { select, Store } from "@ngrx/store";
 import { asyncScheduler, combineLatest, fromEvent, merge, Observable, of, Subject } from "rxjs";
-import { ngViewerActionAddNgLayer, ngViewerActionRemoveNgLayer, ngViewerActionToggleMax } from "src/services/state/ngViewerState/actions";
+import { ngViewerActionToggleMax } from "src/services/state/ngViewerState/actions";
 import { ClickInterceptor, CLICK_INTERCEPTOR_INJECTOR } from "src/util";
 import { uiStateMouseOverSegmentsSelector } from "src/services/state/uiState/selectors";
 import { debounceTime, distinctUntilChanged, filter, map, mapTo, scan, shareReplay, startWith, switchMap, switchMapTo, take, tap, throttleTime } from "rxjs/operators";
 import { viewerStateAddUserLandmarks, viewerStateChangeNavigation, viewerStateMouseOverCustomLandmark, viewerStateSelectRegionWithIdDeprecated, viewerStateSetSelectedRegions, viewreStateRemoveUserLandmarks } from "src/services/state/viewerState/actions";
-import { ngViewerSelectorLayers, ngViewerSelectorClearView, ngViewerSelectorPanelOrder, ngViewerSelectorPanelMode } from "src/services/state/ngViewerState/selectors";
-import { viewerStateCustomLandmarkSelector, viewerStateNavigationStateSelector, viewerStateSelectedRegionsSelector } from "src/services/state/viewerState/selectors";
+import { ngViewerSelectorPanelOrder, ngViewerSelectorPanelMode } from "src/services/state/ngViewerState/selectors";
+import { viewerStateCustomLandmarkSelector, viewerStateNavigationStateSelector } from "src/services/state/viewerState/selectors";
 import { serialiseParcellationRegion } from 'common/util'
 import { ARIA_LABELS, IDS, QUICKTOUR_DESC } from 'common/constants'
 import { PANELS } from "src/services/state/ngViewerState/constants";
@@ -26,17 +26,7 @@ import { IQuickTourData } from "src/ui/quickTour/constrants";
 import { NehubaLayerControlService, IColorMap, SET_COLORMAP_OBS, SET_LAYER_VISIBILITY } from "../layerCtrl.service";
 import { switchMapWaitFor } from "src/util/fn";
 import { INavObj } from "../navigation.service";
-
-interface INgLayerInterface {
-  name: string // displayName
-  source: string
-  mixability: string // base | mixable | nonmixable
-  annotation?: string //
-  id?: string // unique identifier
-  visible?: boolean
-  shader?: string
-  transform?: any
-}
+import { NG_LAYER_CONTROL, SET_SEGMENT_VISIBILITY } from "../layerCtrl.service/layerCtrl.util";
 
 @Component({
   selector: 'iav-cmp-viewer-nehuba-glue',
@@ -62,6 +52,16 @@ interface INgLayerInterface {
       useFactory: (layerCtrl: NehubaLayerControlService) => layerCtrl.visibleLayer$,
       deps: [ NehubaLayerControlService ]
     },
+    {
+      provide: SET_SEGMENT_VISIBILITY,
+      useFactory: (layerCtrl: NehubaLayerControlService) => layerCtrl.segmentVis$,
+      deps: [ NehubaLayerControlService ]
+    },
+    {
+      provide: NG_LAYER_CONTROL,
+      useFactory: (layerCtrl: NehubaLayerControlService) => layerCtrl.ngLayersController$,
+      deps: [ NehubaLayerControlService ]
+    },
     NehubaLayerControlService
   ]
 })
@@ -82,9 +82,6 @@ export class NehubaGlueCmp implements IViewer<'nehuba'>, OnChanges, OnDestroy, A
   private onhoverSegments = []
   private onDestroyCb: Function[] = []
   private viewerUnit: NehubaViewerUnit
-  private ngLayersRegister: {layers: INgLayerInterface[]} = {
-    layers: []
-  }
   private multiNgIdsRegionsLabelIndexMap: Map<string, Map<number, any>>
 
   @Input()
@@ -223,16 +220,7 @@ export class NehubaGlueCmp implements IViewer<'nehuba'>, OnChanges, OnDestroy, A
 
     /* on selecting of new template, remove additional nglayers */
     const baseLayerNames = Object.keys(tmpl.nehubaConfig.dataset.initialNgState.layers)
-    this.ngLayersRegister.layers
-      .filter(layer => baseLayerNames?.findIndex(l => l === layer.name) >= 0)
-      .map(l => l.name)
-      .forEach(layerName => {
-        this.store$.dispatch(ngViewerActionRemoveNgLayer({
-          layer: {
-            name: layerName
-          }
-        }))
-      })
+    this.layerCtrlService.removeNgLayers(baseLayerNames)
   }
 
   private async loadTmpl(_template: any, parcellation: any) {
@@ -296,14 +284,11 @@ export class NehubaGlueCmp implements IViewer<'nehuba'>, OnChanges, OnDestroy, A
           ? null
           : layers[key].transform,
       }
-      this.ngLayersRegister.layers.push(layer)
       return layer
     })
 
+    this.layerCtrlService.addNgLayer(dispatchLayers)
     this.newViewer$.next(true)
-    this.store$.dispatch(ngViewerActionAddNgLayer({
-      layer: dispatchLayers
-    }))
   }
 
   @Output()
@@ -401,99 +386,6 @@ export class NehubaGlueCmp implements IViewer<'nehuba'>, OnChanges, OnDestroy, A
     })
     this.onDestroyCb.push(() => onhovSegSub.unsubscribe())
 
-    /**
-     * subscribe to when ngLayer gets updated, and add/remove layer as necessary
-     */
-    const addRemoveAdditionalLayerSub = this.store$.pipe(
-      select(ngViewerSelectorLayers),
-      switchMap(this.waitForNehuba.bind(this)),
-    ).subscribe((ngLayers: INgLayerInterface[]) => {
-
-      const newLayers = ngLayers.filter(l => this.ngLayersRegister.layers?.findIndex(ol => ol.name === l.name) < 0)
-      const removeLayers = this.ngLayersRegister.layers.filter(l => ngLayers?.findIndex(nl => nl.name === l.name) < 0)
-
-      if (newLayers?.length > 0) {
-        const newLayersObj: any = {}
-        newLayers.forEach(({ name, source, ...rest }) => newLayersObj[name] = {
-          ...rest,
-          source,
-          // source: getProxyUrl(source),
-          // ...getProxyOther({source})
-        })
-
-        this.nehubaContainerDirective.nehubaViewerInstance.loadLayer(newLayersObj)
-
-        /**
-         * previous miplementation... if nehubaViewer has not yet be instantiated, add it to the queue
-         * may no longer be necessary
-         * or implement proper queue'ing rather than ... this... half assed queue'ing
-         */
-        // if (!this.nehubaViewer.nehubaViewer || !this.nehubaViewer.nehubaViewer.ngviewer) {
-        //   this.nehubaViewer.initNiftiLayers.push(newLayersObj)
-        // } else {
-        //   this.nehubaViewer.loadLayer(newLayersObj)
-        // }
-        this.ngLayersRegister.layers = this.ngLayersRegister.layers.concat(newLayers)
-      }
-
-      if (removeLayers?.length > 0) {
-        removeLayers.forEach(l => {
-          if (this.nehubaContainerDirective.nehubaViewerInstance.removeLayer({
-            name : l.name,
-          })) {
-            this.ngLayersRegister.layers = this.ngLayersRegister.layers.filter(rl => rl.name !== l.name)
-          }
-        })
-      }
-    })
-    this.onDestroyCb.push(() => addRemoveAdditionalLayerSub.unsubscribe())
-
-    /**
-     * define when shown segments should be updated
-     * TODO move to layerCtrl.service.ts
-     */
-    const regSelectSub = combineLatest([
-      /**
-       * selectedRegions
-       */
-      this.store$.pipe(
-        select(viewerStateSelectedRegionsSelector)
-      ),
-      /**
-       * if layer contains non mixable layer
-       */
-      this.store$.pipe(
-        select(ngViewerSelectorLayers),
-        map(layers => layers.findIndex(l => l.mixability === 'nonmixable') >= 0),
-      ),
-      /**
-       * clearviewqueue, indicating something is controlling colour map
-       * show all seg
-       */
-      this.store$.pipe(
-        select(ngViewerSelectorClearView),
-        distinctUntilChanged()
-      )
-    ]).pipe(
-      switchMap(this.waitForNehuba.bind(this)),
-    ).subscribe(([ regions, nonmixableLayerExists, clearViewFlag ]) => {
-      if (nonmixableLayerExists) {
-        this.nehubaContainerDirective.nehubaViewerInstance.hideAllSeg()
-        return
-      }
-      const { ngId: defaultNgId } = this.selectedParcellation || {}
-
-      /* selectedregionindexset needs to be updated regardless of forceshowsegment */
-      const selectedRegionIndexSet = new Set<string>(regions.map(({ngId = defaultNgId, labelIndex}) => serialiseParcellationRegion({ ngId, labelIndex })))
-
-      if (selectedRegionIndexSet.size > 0 && !clearViewFlag) {
-        this.nehubaContainerDirective.nehubaViewerInstance.showSegs([...selectedRegionIndexSet])
-      } else {
-        this.nehubaContainerDirective.nehubaViewerInstance.showAllSeg()
-      }
-    })
-    this.onDestroyCb.push(() => regSelectSub.unsubscribe())
-
     const perspectiveRenderEvSub = this.newViewer$.pipe(
       switchMapTo(fromEvent<CustomEvent>(this.el.nativeElement, 'perpspectiveRenderEvent').pipe(
         take(1)
-- 
GitLab