From 2329ea90357704b61f72e2e0698418e49d71f974 Mon Sep 17 00:00:00 2001
From: Xiao Gui <xgui3783@gmail.com>
Date: Fri, 18 Mar 2022 15:47:24 +0100
Subject: [PATCH] feat: use http interceptor to priortise refactor: reworked
 route state conversion

---
 .../sapi/core/sapiParcellation.ts             |  41 +--
 src/atlasComponents/sapi/core/sapiSpace.ts    |  17 +-
 src/atlasComponents/sapi/sapi.service.ts      |  40 +--
 src/atlasComponents/sapi/type.ts              |   4 +
 src/routerModule/module.ts                    |   4 +-
 .../routeStateTransform.service.ts            | 269 ++++++++++++++++++
 src/routerModule/router.service.ts            |  49 ++--
 src/routerModule/type.ts                      |   5 -
 src/routerModule/util.ts                      | 239 +---------------
 src/state/atlasSelection/effects.ts           |  10 +-
 src/state/index.ts                            |  14 +-
 src/util/priority.ts                          |  42 ++-
 src/util/pureConstant.service.ts              | 165 -----------
 src/viewerModule/nehuba/store/util.ts         |   9 +-
 14 files changed, 418 insertions(+), 490 deletions(-)
 create mode 100644 src/routerModule/routeStateTransform.service.ts

diff --git a/src/atlasComponents/sapi/core/sapiParcellation.ts b/src/atlasComponents/sapi/core/sapiParcellation.ts
index 7add33499..00c030313 100644
--- a/src/atlasComponents/sapi/core/sapiParcellation.ts
+++ b/src/atlasComponents/sapi/core/sapiParcellation.ts
@@ -1,24 +1,30 @@
+import { Observable } from "rxjs"
 import { SapiVolumeModel } from ".."
 import { SAPI } from "../sapi.service"
-import { SapiParcellationFeatureModel, SapiParcellationModel, SapiRegionModel } from "../type"
+import { SapiParcellationFeatureModel, SapiParcellationModel, SapiQueryParam, SapiRegionModel } from "../type"
+
+type PaginationQuery = {}
 
 export class SAPIParcellation{
   constructor(private sapi: SAPI, public atlasId: string, public id: string){
 
   }
-  getDetail(): Promise<SapiParcellationModel>{
-    return this.sapi.cachedGet<SapiParcellationModel>(
-      `${this.sapi.bsEndpoint}/atlases/${encodeURIComponent(this.atlasId)}/parcellations/${encodeURIComponent(this.id)}`
+
+  getDetail(queryParam?: SapiQueryParam): Observable<SapiParcellationModel>{
+    return this.sapi.httpGet<SapiParcellationModel>(
+      `${this.sapi.bsEndpoint}/atlases/${encodeURIComponent(this.atlasId)}/parcellations/${encodeURIComponent(this.id)}`,
+      null,
+      queryParam
     )
   }
-  getRegions(spaceId: string): Promise<SapiRegionModel[]> {
-    return this.sapi.cachedGet<SapiRegionModel[]>(
+
+  getRegions(spaceId: string, queryParam?: SapiQueryParam): Observable<SapiRegionModel[]> {
+    return this.sapi.httpGet<SapiRegionModel[]>(
       `${this.sapi.bsEndpoint}/atlases/${encodeURIComponent(this.atlasId)}/parcellations/${encodeURIComponent(this.id)}/regions`,
       {
-        params: {
-          space_id: spaceId
-        }
-      }
+        space_id: spaceId
+      },
+      queryParam
     )
   }
   getVolumes(): Promise<SapiVolumeModel[]>{
@@ -27,16 +33,15 @@ export class SAPIParcellation{
     )
   }
 
-  getFeatures(): Promise<SapiParcellationFeatureModel[]> {
-    return this.sapi.http.get<SapiParcellationFeatureModel[]>(
+  getFeatures(param?: PaginationQuery, queryParam?: SapiQueryParam): Observable<SapiParcellationFeatureModel[]> {
+    return this.sapi.httpGet<SapiParcellationFeatureModel[]>(
       `${this.sapi.bsEndpoint}/atlases/${encodeURIComponent(this.atlasId)}/parcellations/${encodeURIComponent(this.id)}/features`,
       {
-        params: {
-          per_page: 5,
-          page: 0,
-        }
-      }
-    ).toPromise()
+        per_page: '5',
+        page: '0',
+      },
+      queryParam
+    )
   }
 
   getFeatureInstance(instanceId: string): Promise<SapiParcellationFeatureModel> {
diff --git a/src/atlasComponents/sapi/core/sapiSpace.ts b/src/atlasComponents/sapi/core/sapiSpace.ts
index 1b3ef810f..33c3c88b7 100644
--- a/src/atlasComponents/sapi/core/sapiSpace.ts
+++ b/src/atlasComponents/sapi/core/sapiSpace.ts
@@ -2,7 +2,8 @@ import { Observable } from "rxjs"
 import { SAPI } from '../sapi.service'
 import { camelToSnake } from 'common/util'
 import { IVolumeTypeDetail } from "src/util/siibraApiConstants/types"
-import { SapiSpaceModel, SapiSpatialFeatureModel, SapiVolumeModel } from "../type"
+import { SapiQueryParam, SapiSpaceModel, SapiSpatialFeatureModel, SapiVolumeModel } from "../type"
+import { tap } from "rxjs/operators"
 
 type FeatureResponse = {
   features: {
@@ -37,9 +38,11 @@ export class SAPISpace{
 
   constructor(private sapi: SAPI, public atlasId: string, public id: string){}
 
-  getModalities(): Observable<FeatureResponse> {
-    return this.sapi.http.get<FeatureResponse>(
-      `${this.sapi.bsEndpoint}/atlases/${encodeURIComponent(this.atlasId)}/spaces/${encodeURIComponent(this.id)}/features`
+  getModalities(param?: SapiQueryParam): Observable<FeatureResponse> {
+    return this.sapi.httpGet<FeatureResponse>(
+      `${this.sapi.bsEndpoint}/atlases/${encodeURIComponent(this.atlasId)}/spaces/${encodeURIComponent(this.id)}/features`,
+      null,
+      param
     )
   }
 
@@ -56,9 +59,11 @@ export class SAPISpace{
     )
   }
 
-  getDetail(): Promise<SapiSpaceModel>{
-    return this.sapi.cachedGet<SapiSpaceModel>(
+  getDetail(param?: SapiQueryParam): Observable<SapiSpaceModel>{
+    return this.sapi.httpGet<SapiSpaceModel>(
       `${this.sapi.bsEndpoint}/atlases/${encodeURIComponent(this.atlasId)}/spaces/${encodeURIComponent(this.id)}`,
+      null,
+      param
     )
   }
 
diff --git a/src/atlasComponents/sapi/sapi.service.ts b/src/atlasComponents/sapi/sapi.service.ts
index 083bfcf68..be6a9a608 100644
--- a/src/atlasComponents/sapi/sapi.service.ts
+++ b/src/atlasComponents/sapi/sapi.service.ts
@@ -3,13 +3,15 @@ import { HttpClient } from '@angular/common/http';
 import { BS_ENDPOINT } from 'src/util/constants';
 import { map, shareReplay, take, tap } from "rxjs/operators";
 import { SAPIAtlas, SAPISpace } from './core'
-import { SapiAtlasModel, SapiParcellationModel, SapiRegionalFeatureModel, SapiRegionModel, SapiSpaceModel, SpyNpArrayDataModel } from "./type";
+import { SapiAtlasModel, SapiParcellationModel, SapiQueryParam, SapiRegionalFeatureModel, SapiRegionModel, SapiSpaceModel, SpyNpArrayDataModel } from "./type";
 import { CachedFunction, getExportNehuba } from "src/util/fn";
 import { SAPIParcellation } from "./core/sapiParcellation";
 import { SAPIRegion } from "./core/sapiRegion"
 import { MatSnackBar } from "@angular/material/snack-bar";
 import { AtlasWorkerService } from "src/atlasViewer/atlasViewer.workerService.service";
 import { EnumColorMapName } from "src/util/colorMaps";
+import { PRIORITY_HEADER } from "src/util/priority";
+import { Observable } from "rxjs";
 
 export const SIIBRA_API_VERSION_HEADER_KEY='x-siibra-api-version'
 export const SIIBRA_API_VERSION = '0.2.0'
@@ -56,26 +58,17 @@ export class SAPI{
     return new SAPIRegion(this, atlasId, parcId, regionId)
   }
 
-  @CachedFunction({
-    serialization: (atlasId, spaceId, ...args) => `sapi::getSpaceDetail::${atlasId}::${spaceId}`
-  })
-  getSpaceDetail(atlasId: string, spaceId: string, priority = 0): Promise<SapiSpaceModel> {
-    return this.getSpace(atlasId, spaceId).getDetail()
+  getSpaceDetail(atlasId: string, spaceId: string, param?: SapiQueryParam): Observable<SapiSpaceModel> {
+    return this.getSpace(atlasId, spaceId).getDetail(param)
   }
 
-  @CachedFunction({
-    serialization: (atlasId, parcId, ...args) => `sapi::getParcDetail::${atlasId}::${parcId}`
-  })
-  getParcDetail(atlasId: string, parcId: string, priority = 0): Promise<SapiParcellationModel> {
-    return this.getParcellation(atlasId, parcId).getDetail()
+  getParcDetail(atlasId: string, parcId: string, param?: SapiQueryParam): Observable<SapiParcellationModel> {
+    return this.getParcellation(atlasId, parcId).getDetail(param)
   }
 
-  @CachedFunction({
-    serialization: (atlasId, parcId, spaceId, ...args) => `sapi::getRegions::${atlasId}::${parcId}::${spaceId}`
-  })
-  getParcRegions(atlasId: string, parcId: string, spaceId: string, priority = 0): Promise<SapiRegionModel[]> {
+  getParcRegions(atlasId: string, parcId: string, spaceId: string, queryParam?: SapiQueryParam): Observable<SapiRegionModel[]> {
     const parc = this.getParcellation(atlasId, parcId)
-    return parc.getRegions(spaceId)
+    return parc.getRegions(spaceId, queryParam)
   }
 
   @CachedFunction({
@@ -94,6 +87,21 @@ export class SAPI{
     return this.http.get<T>(url, option).toPromise()
   }
 
+  httpGet<T>(url: string, params?: Record<string, string>, sapiParam?: SapiQueryParam){
+    return this.http.get<T>(
+      url,
+      {
+        params: {
+          ...( params || {} ),
+          ...(
+            sapiParam?.priority
+              ? ({ [PRIORITY_HEADER]: sapiParam.priority })
+              : {}
+          )
+        }
+      }
+    )
+  }
 
   public atlases$ = this.http.get<SapiAtlasModel[]>(
     `${this.bsEndpoint}/atlases`,
diff --git a/src/atlasComponents/sapi/type.ts b/src/atlasComponents/sapi/type.ts
index 919649125..77a94d21f 100644
--- a/src/atlasComponents/sapi/type.ts
+++ b/src/atlasComponents/sapi/type.ts
@@ -76,3 +76,7 @@ export function guardPipe<
     })
   )
 }
+
+export type SapiQueryParam = {
+  priority: number
+}
diff --git a/src/routerModule/module.ts b/src/routerModule/module.ts
index 1053deeb9..c5e484500 100644
--- a/src/routerModule/module.ts
+++ b/src/routerModule/module.ts
@@ -2,6 +2,7 @@ import { APP_BASE_HREF } from "@angular/common";
 import { NgModule } from "@angular/core";
 import { RouterModule } from '@angular/router'
 import { RouterService } from "./router.service";
+import { RouteStateTransformSvc } from "./routeStateTransform.service";
 import { routes } from "./util";
 
 
@@ -16,7 +17,8 @@ import { routes } from "./util";
       provide: APP_BASE_HREF,
       useValue: '/'
     },
-    RouterService
+    RouterService,
+    RouteStateTransformSvc,
   ],
   exports:[
     RouterModule
diff --git a/src/routerModule/routeStateTransform.service.ts b/src/routerModule/routeStateTransform.service.ts
new file mode 100644
index 000000000..0efa659ce
--- /dev/null
+++ b/src/routerModule/routeStateTransform.service.ts
@@ -0,0 +1,269 @@
+import { Injectable } from "@angular/core";
+import { UrlSegment, UrlTree } from "@angular/router";
+import { map } from "rxjs/operators";
+import { SAPI } from "src/atlasComponents/sapi";
+import { atlasSelection, defaultState, MainState, plugins } from "src/state";
+import { getParcNgId, getRegionLabelIndex } from "src/viewerModule/nehuba/config.service";
+import { decodeToNumber, encodeNumber, encodeURIFull, separator } from "./cipher";
+import { TUrlAtlas, TUrlPathObj, TUrlStandaloneVolume } from "./type";
+import { decodePath, encodeId, endcodePath } from "./util";
+
+@Injectable()
+export class RouteStateTransformSvc {
+
+  static GetOneAndOnlyOne<T>(arr: T[]): T{
+    if (!arr || arr.length === 0) return null
+    if (arr.length > 1) throw new Error(`expecting exactly 1 item, got ${arr.length}`)
+    return arr[0]
+  }
+
+  constructor(private sapi: SAPI){}
+
+  private async getATPR(obj: TUrlPathObj<string[], TUrlAtlas<string[]>>){
+    const selectedAtlasId = RouteStateTransformSvc.GetOneAndOnlyOne(obj.a)
+    const selectedTemplateId = RouteStateTransformSvc.GetOneAndOnlyOne(obj.t)
+    const selectedParcellationId = RouteStateTransformSvc.GetOneAndOnlyOne(obj.p)
+    const selectedRegionIds = obj.r
+
+    const [
+      selectedAtlas,
+      selectedTemplate,
+      selectedParcellation,
+      allParcellationRegions = []
+    ] = await Promise.all([
+      this.sapi.atlases$.pipe(
+        map(atlases => atlases.find(atlas => atlas["@id"] === selectedAtlasId))
+      ).toPromise(),
+      this.sapi.getSpaceDetail(selectedAtlasId, selectedTemplateId, { priority: 10 }).toPromise(),
+      this.sapi.getParcDetail(selectedAtlasId, selectedParcellationId, { priority: 10 }).toPromise(),
+      this.sapi.getParcRegions(selectedAtlasId, selectedParcellationId, selectedTemplateId, { priority: 10 }).toPromise(),
+    ])
+
+    const latNgIdMap = ["left hemisphere", "right hemisphere", "whole brain"].map(lat => {
+      let regex: RegExp = /./
+      if (lat === "left hemisphere") regex = /left/i
+      if (lat === "right hemisphere") regex = /right/i
+      return {
+        lat,
+        regex,
+        ngId: getParcNgId(selectedAtlas, selectedTemplate, selectedParcellation, lat)
+      }
+    })
+    
+    const selectedRegions = (() => {
+      if (!selectedRegionIds) return []
+      /**
+       * assuming only 1 selected region
+       * if this assumption changes, iterate over array of selectedRegionIds
+       */
+      const json = { [selectedRegionIds[0]]: selectedRegionIds[1] }
+
+      for (const ngId in json) {
+        const matchingMap = latNgIdMap.find(ngIdMap => ngIdMap.ngId === ngId)
+        if (!matchingMap) {
+          console.error(`could not find matching map for ${ngId}`)
+          continue
+        }
+        
+        const val = json[ngId]
+        const labelIndicies = val.split(separator).map(n => {
+          try {
+            return decodeToNumber(n)
+          } catch (e) {
+            /**
+             * TODO poisonsed encoded char, send error message
+             */
+            return null
+          }
+        }).filter(v => !!v)
+  
+        for (const labelIndex of labelIndicies) {
+          /**
+           * currently, only 1 region is expected to be selected at max
+           * this method probably out performs creating a map
+           * but with 2+ regions, creating a map would consistently be faster
+           */
+          for (const r of allParcellationRegions) {
+            const idx = getRegionLabelIndex(selectedAtlas, selectedTemplate, selectedParcellation, r)
+            if (idx === labelIndex) {
+              return [ r ]
+            }
+          }
+        }
+      }
+      return []
+    })()
+
+    return {
+      selectedAtlas,
+      selectedTemplate,
+      selectedParcellation,
+      selectedRegions,
+    }
+  }
+
+  async cvtRouteToState(fullPath: UrlTree) {
+
+    const returnState: MainState = defaultState
+    const pathFragments: UrlSegment[] = fullPath.root.hasChildren()
+    ? fullPath.root.children['primary'].segments
+    : []
+
+    const returnObj: Partial<TUrlPathObj<string[], unknown>> = {}
+    for (const f of pathFragments) {
+      const { key, val } = decodePath(f.path) || {}
+      if (!key || !val) continue
+      returnObj[key] = val
+    }
+
+    // nav obj is almost always defined, regardless if standaloneVolume or not
+    const cViewerState = returnObj['@'] && returnObj['@'][0]
+    let parsedNavObj: MainState['[state.atlasSelection]']['navigation']
+    if (cViewerState) {
+      try {
+        const [ cO, cPO, cPZ, cP, cZ ] = cViewerState.split(`${separator}${separator}`)
+        const o = cO.split(separator).map(s => decodeToNumber(s, {float: true}))
+        const po = cPO.split(separator).map(s => decodeToNumber(s, {float: true}))
+        const pz = decodeToNumber(cPZ)
+        const p = cP.split(separator).map(s => decodeToNumber(s))
+        const z = decodeToNumber(cZ)
+        parsedNavObj = {
+          orientation: o,
+          perspectiveOrientation: po,
+          perspectiveZoom: pz,
+          position: p,
+          zoom: z,
+        }
+      } catch (e) {
+        /**
+         * TODO Poisoned encoded char
+         * send error message
+         */
+        console.error(`cannot parse navigation state`, e)
+      }
+    }
+
+    // pluginState should always be defined, regardless if standalone volume or not
+    const pluginStates = fullPath.queryParams['pl']
+    if (pluginStates) {
+      try {
+        const arrPluginStates = JSON.parse(pluginStates)
+        returnState["[state.plugins]"].initManifests = arrPluginStates.map(url => [plugins.INIT_MANIFEST_SRC, url] as [string, string])
+      } catch (e) {
+        /**
+         * parsing plugin error
+         */
+        console.error(`parse plugin states error`, e, pluginStates)
+      }
+    }
+
+    // If sv (standaloneVolume is defined)
+    // only load sv in state
+    // ignore all other params
+    // /#/sv:%5B%22precomputed%3A%2F%2Fhttps%3A%2F%2Fobject.cscs.ch%2Fv1%2FAUTH_08c08f9f119744cbbf77e216988da3eb%2Fimgsvc-46d9d64f-bdac-418e-a41b-b7f805068c64%22%5D
+    const standaloneVolumes = fullPath.queryParams['standaloneVolumes']
+    try {
+      const parsedArr = JSON.parse(standaloneVolumes)
+      if (!Array.isArray(parsedArr)) throw new Error(`Parsed standalone volumes not of type array`)
+
+      returnState["[state.atlasSelection]"].standAloneVolumes = parsedArr
+      returnState["[state.atlasSelection]"].navigation = parsedNavObj
+      return returnState
+    } catch (e) {
+      // if any error occurs, parse rest per normal
+      console.error(`parse standalone volume error`, e)
+    }
+
+
+    try {
+      const { selectedAtlas, selectedParcellation, selectedRegions, selectedTemplate } = await this.getATPR(returnObj as TUrlPathObj<string[], TUrlAtlas<string[]>>)
+      returnState["[state.atlasSelection]"].selectedAtlas = selectedAtlas
+      returnState["[state.atlasSelection]"].selectedParcellation = selectedParcellation
+      returnState["[state.atlasSelection]"].selectedTemplate = selectedTemplate
+      returnState["[state.atlasSelection]"].selectedRegions = selectedRegions
+      
+    } catch (e) {
+      // if error, show error on UI?
+      console.error(`parse template, parc, region error`, e)
+    }
+    return returnState
+  }
+
+  cvtStateToRoute(state: MainState) {
+    const {
+      atlas: selectedAtlas,
+      parcellation: selectedParcellation,
+      template: selectedTemplate,
+    } = atlasSelection.selectors.selectedATP(state)
+    
+    const selectedRegions = atlasSelection.selectors.selectedRegions(state)
+    const standaloneVolumes = atlasSelection.selectors.standaloneVolumes(state)
+    const navigation = atlasSelection.selectors.navigation(state)
+  
+    let dsPrvString: string
+    const searchParam = new URLSearchParams()
+  
+    let cNavString: string
+    if (navigation) {
+      const { orientation, perspectiveOrientation, perspectiveZoom, position, zoom } = navigation
+      if (orientation && perspectiveOrientation && perspectiveZoom && position && zoom) {
+        cNavString = [
+          orientation.map((n: number) => encodeNumber(n, {float: true})).join(separator),
+          perspectiveOrientation.map(n => encodeNumber(n, {float: true})).join(separator),
+          encodeNumber(Math.floor(perspectiveZoom)),
+          Array.from(position).map((v: number) => Math.floor(v)).map(n => encodeNumber(n)).join(separator),
+          encodeNumber(Math.floor(zoom)),
+        ].join(`${separator}${separator}`)
+      }
+    }
+  
+    // encoding selected regions
+    let selectedRegionsString: string
+    if (selectedRegions.length === 1) {
+      const region = selectedRegions[0]
+      const labelIndex = getRegionLabelIndex(selectedAtlas, selectedTemplate, selectedParcellation, region)
+      
+      const ngId = getParcNgId(selectedAtlas, selectedTemplate, selectedParcellation, region)
+      selectedRegionsString = `${ngId}::${encodeNumber(labelIndex, { float: false })}`
+    }
+    let routes: any
+    
+    routes= {
+      // for atlas
+      a: selectedAtlas && encodeId(selectedAtlas['@id']),
+      // for template
+      t: selectedTemplate && encodeId(selectedTemplate['@id'] || selectedTemplate['fullId']),
+      // for parcellation
+      p: selectedParcellation && encodeId(selectedParcellation['@id'] || selectedParcellation['fullId']),
+      // for regions
+      r: selectedRegionsString && encodeURIFull(selectedRegionsString),
+      // nav
+      ['@']: cNavString,
+      // dataset file preview
+      dsp: dsPrvString && encodeURI(dsPrvString),
+    } as TUrlPathObj<string, TUrlAtlas<string>>
+  
+    /**
+     * if any params needs to overwrite previosu routes, put them here
+     */
+    if (standaloneVolumes && Array.isArray(standaloneVolumes) && standaloneVolumes.length > 0) {
+      searchParam.set('standaloneVolumes', JSON.stringify(standaloneVolumes))
+      routes = {
+        // nav
+        ['@']: cNavString,
+      } as TUrlPathObj<string|string[], TUrlStandaloneVolume<string[]>>
+    }
+  
+    const routesArr: string[] = []
+    for (const key in routes) {
+      if (!!routes[key]) {
+        const segStr = endcodePath(key, routes[key])
+        routesArr.push(segStr)
+      }
+    }
+  
+    return searchParam.toString() === '' 
+      ? routesArr.join('/')
+      : `${routesArr.join('/')}?${searchParam.toString()}`
+  }
+}
diff --git a/src/routerModule/router.service.ts b/src/routerModule/router.service.ts
index 28a8c696b..ea1724ef4 100644
--- a/src/routerModule/router.service.ts
+++ b/src/routerModule/router.service.ts
@@ -4,11 +4,10 @@ import { Inject } from "@angular/core";
 import { NavigationEnd, Router } from '@angular/router'
 import { Store } from "@ngrx/store";
 import { debounceTime, distinctUntilChanged, filter, map, shareReplay, startWith, switchMapTo, take, tap, withLatestFrom } from "rxjs/operators";
-import { PureContantService } from "src/util";
-import { cvtStateToHashedRoutes, cvtFullRouteToState, encodeCustomState, decodeCustomState, verifyCustomState } from "./util";
+import { encodeCustomState, decodeCustomState, verifyCustomState } from "./util";
 import { BehaviorSubject, combineLatest, merge, NEVER, Observable, of } from 'rxjs'
 import { scan } from 'rxjs/operators'
-import { generalActions } from "src/state"
+import { RouteStateTransformSvc } from "./routeStateTransform.service";
 
 @Injectable({
   providedIn: 'root'
@@ -37,7 +36,7 @@ export class RouterService {
 
   constructor(
     router: Router,
-    pureConstantService: PureContantService,
+    routeToStateTransformSvc: RouteStateTransformSvc,
     store$: Store<any>,
     @Inject(APP_BASE_HREF) baseHref: string
   ){
@@ -106,28 +105,28 @@ export class RouterService {
     ).subscribe(arg => {
       const [ev, state, customRoutes] = arg
       
-      const fullPath = ev.urlAfterRedirects
-      const stateFromRoute = cvtFullRouteToState(router.parseUrl(fullPath), state, this.logError)
-      let routeFromState: string
-      try {
-        routeFromState = cvtStateToHashedRoutes(state)
-      } catch (_e) {
-        routeFromState = ``
-      }
+      // const fullPath = ev.urlAfterRedirects
+      // const stateFromRoute = cvtFullRouteToState(router.parseUrl(fullPath), state, this.logError)
+      // let routeFromState: string
+      // try {
+      //   routeFromState = cvtStateToHashedRoutes(state)
+      // } catch (_e) {
+      //   routeFromState = ``
+      // }
 
-      for (const key in customRoutes) {
-        const customStatePath = encodeCustomState(key, customRoutes[key])
-        if (!customStatePath) continue
-        routeFromState += `/${customStatePath}`
-      }
+      // for (const key in customRoutes) {
+      //   const customStatePath = encodeCustomState(key, customRoutes[key])
+      //   if (!customStatePath) continue
+      //   routeFromState += `/${customStatePath}`
+      // }
 
-      if ( fullPath !== `/${routeFromState}`) {
-        store$.dispatch(
-          generalActions.generalApplyState({
-            state: stateFromRoute
-          })
-        )
-      }
+      // if ( fullPath !== `/${routeFromState}`) {
+      //   store$.dispatch(
+      //     generalActions.generalApplyState({
+      //       state: stateFromRoute
+      //     })
+      //   )
+      // }
     })
     
     // TODO this may still be a bit finiky. 
@@ -140,7 +139,7 @@ export class RouterService {
             debounceTime(160),
             map(state => {
               try {
-                return cvtStateToHashedRoutes(state)
+                return `` //cvtStateToHashedRoutes(state)
               } catch (e) {
                 this.logError(e)
                 return ``
diff --git a/src/routerModule/type.ts b/src/routerModule/type.ts
index fa8855ad8..10f7e0e76 100644
--- a/src/routerModule/type.ts
+++ b/src/routerModule/type.ts
@@ -9,10 +9,6 @@ export type TUrlAtlas<T> = {
   r?: T  // region selected
 }
 
-export type TUrlPreviewDs<T> = {
-  dsp: T // dataset preview
-}
-
 export type TUrlPlugin<T> = {
   pl: T  // pluginState
 }
@@ -22,7 +18,6 @@ export type TUrlNav<T> = {
 }
 
 export type TConditional<T> = Partial<
-  TUrlPreviewDs<T> &
   TUrlPlugin<T> &
   TUrlNav<T>
 >
diff --git a/src/routerModule/util.ts b/src/routerModule/util.ts
index 90a8f8ed5..ee8f181f0 100644
--- a/src/routerModule/util.ts
+++ b/src/routerModule/util.ts
@@ -1,28 +1,17 @@
-import { encodeNumber, decodeToNumber, separator, encodeURIFull } from './cipher'
 import { UrlSegment, UrlTree } from "@angular/router"
 import { Component } from "@angular/core"
-import { atlasSelection, plugins } from "src/state"
 
-import {
-  TUrlStandaloneVolume,
-  TUrlAtlas,
-  TUrlPathObj,
-} from './type'
+export const encodeId = (id: string) => id && id.replace(/\//g, ':')
+export const decodeId = (codedId: string) => codedId && codedId.replace(/:/g, '/')
 
-import {
-  parseSearchParamForTemplateParcellationRegion,
-  encodeId,
-} from './parseRouteToTmplParcReg'
-import { spaceMiscInfoMap } from "src/util/pureConstant.service"
-import { getRegionLabelIndex, getParcNgId } from "src/viewerModule/nehuba/config.service"
-
-const endcodePath = (key: string, val: string|string[]) =>
+export const endcodePath = (key: string, val: string|string[]) =>
   key[0] === '?'
     ? `?${key}=${val}`
     : `${key}:${Array.isArray(val)
       ? val.map(v => encodeURI(v)).join('::')
       : encodeURI(val)}`
-const decodePath = (path: string) => {
+
+export const decodePath = (path: string) => {
   const re = /^(.*?):(.*?)$/.exec(path)
   if (!re) return null
   return {
@@ -42,224 +31,6 @@ export const DEFAULT_NAV = {
   position: [0, 0, 0],
 }
 
-export const cvtFullRouteToState = (fullPath: UrlTree, state: any, _warnCb?: (arg: string) => void) => {
-
-  const warnCb = _warnCb || ((...e: any[]) => console.warn(...e))
-  const pathFragments: UrlSegment[] = fullPath.root.hasChildren()
-    ? fullPath.root.children['primary'].segments
-    : []
-
-  const returnState = JSON.parse(JSON.stringify(state))
-
-  const returnObj: Partial<TUrlPathObj<string[], unknown>> = {}
-  for (const f of pathFragments) {
-    const { key, val } = decodePath(f.path) || {}
-    if (!key || !val) continue
-    returnObj[key] = val
-  }
-
-  // logical assignment. Use instead of above after typescript > v4.0.0
-  // returnState['viewerState'] ||= {}
-  if (!returnState['viewerState']) {
-    returnState['viewerState'] = {}
-  }
-  // -- end fix logical assignment
-
-  // nav obj is almost always defined, regardless if standaloneVolume or not
-  const cViewerState = returnObj['@'] && returnObj['@'][0]
-  let parsedNavObj: any
-  if (cViewerState) {
-    try {
-      const [ cO, cPO, cPZ, cP, cZ ] = cViewerState.split(`${separator}${separator}`)
-      const o = cO.split(separator).map(s => decodeToNumber(s, {float: true}))
-      const po = cPO.split(separator).map(s => decodeToNumber(s, {float: true}))
-      const pz = decodeToNumber(cPZ)
-      const p = cP.split(separator).map(s => decodeToNumber(s))
-      const z = decodeToNumber(cZ)
-      parsedNavObj = {
-        orientation: o,
-        perspectiveOrientation: po,
-        perspectiveZoom: pz,
-        position: p,
-        zoom: z,
-
-        // flag to allow for animation when enabled
-        animation: {},
-      }
-    } catch (e) {
-      /**
-       * TODO Poisoned encoded char
-       * send error message
-       */
-      warnCb(`parse nav error`, e)
-    }
-  }
-
-  // pluginState should always be defined, regardless if standalone volume or not
-  const pluginStates = fullPath.queryParams['pl']
-  const { pluginState } = returnState
-  if (pluginStates) {
-    try {
-      const arrPluginStates = JSON.parse(pluginStates)
-      pluginState.initManifests = arrPluginStates.map(url => [plugins.INIT_MANIFEST_SRC, url] as [string, string])
-    } catch (e) {
-      /**
-       * parsing plugin error
-       */
-      warnCb(`parse plugin states error`, e, pluginStates)
-    }
-  }
-
-  // preview dataset can and should be displayed regardless of standalone volume or not
-
-  try {
-    const { uiState } = returnState
-    const arr = returnObj.dsp
-      ? [{
-        datasetId: returnObj.dsp[0],
-        filename: returnObj.dsp[1]
-      }]
-      : fullPath.queryParams['previewingDatasetFiles'] && JSON.parse(fullPath.queryParams['previewingDatasetFiles'])
-    if (arr) {
-      uiState.previewingDatasetFiles = arr.map(({ datasetId, filename }) => {
-        return {
-          datasetId,
-          filename
-        }
-      })
-    }
-  } catch (e) {
-    // parsing previewingDatasetFiles
-    warnCb(`parse dsp error`, e)
-  }
-
-  // If sv (standaloneVolume is defined)
-  // only load sv in state
-  // ignore all other params
-  // /#/sv:%5B%22precomputed%3A%2F%2Fhttps%3A%2F%2Fobject.cscs.ch%2Fv1%2FAUTH_08c08f9f119744cbbf77e216988da3eb%2Fimgsvc-46d9d64f-bdac-418e-a41b-b7f805068c64%22%5D
-  const standaloneVolumes = fullPath.queryParams['standaloneVolumes']
-  if (!!standaloneVolumes) {
-    try {
-      const parsedArr = JSON.parse(standaloneVolumes)
-      if (!Array.isArray(parsedArr)) throw new Error(`Parsed standalone volumes not of type array`)
-
-      returnState['viewerState']['standaloneVolumes'] = parsedArr
-      returnState['viewerState']['navigation'] = parsedNavObj
-      return returnState
-    } catch (e) {
-      // if any error occurs, parse rest per normal
-      warnCb(`parse standalone volume error`, e)
-    }
-  } else {
-    returnState['viewerState']['standaloneVolumes'] = []
-  }
-
-  try {
-    const { parcellationSelected, regionsSelected, templateSelected } = parseSearchParamForTemplateParcellationRegion(returnObj as TUrlPathObj<string[], TUrlAtlas<string[]>>, fullPath, state, warnCb)
-    returnState['viewerState']['parcellationSelected'] = parcellationSelected
-    returnState['viewerState']['regionsSelected'] = regionsSelected
-    returnState['viewerState']['templateSelected'] = templateSelected
-
-    if (templateSelected) {
-      const { scale } = spaceMiscInfoMap.get(templateSelected.id) || { scale: 1 }
-      returnState['viewerState']['navigation'] = parsedNavObj || ({
-        ...DEFAULT_NAV,
-        zoom: 350000 * scale,
-        perspectiveZoom: 1922235.5293810747 * scale
-      })
-    }
-  } catch (e) {
-    // if error, show error on UI?
-    warnCb(`parse template, parc, region error`, e)
-  }
-
-  /**
-   * parsing template to get atlasId
-   */
-  // TODO
-  return returnState
-}
-
-export const cvtStateToHashedRoutes = (state): string => {
-  // TODO check if this causes memleak
-  const {
-    atlas: selectedAtlas,
-    parcellation: selectedParcellation,
-    template: selectedTemplate,
-  } = atlasSelection.selectors.selectedATP(state)
-  
-  const selectedRegions = atlasSelection.selectors.selectedRegions(state)
-  const standaloneVolumes = atlasSelection.selectors.standaloneVolumes(state)
-  const navigation = atlasSelection.selectors.navigation(state)
-
-  let dsPrvString: string
-  const searchParam = new URLSearchParams()
-
-  let cNavString: string
-  if (navigation) {
-    const { orientation, perspectiveOrientation, perspectiveZoom, position, zoom } = navigation
-    if (orientation && perspectiveOrientation && perspectiveZoom && position && zoom) {
-      cNavString = [
-        orientation.map((n: number) => encodeNumber(n, {float: true})).join(separator),
-        perspectiveOrientation.map(n => encodeNumber(n, {float: true})).join(separator),
-        encodeNumber(Math.floor(perspectiveZoom)),
-        Array.from(position).map((v: number) => Math.floor(v)).map(n => encodeNumber(n)).join(separator),
-        encodeNumber(Math.floor(zoom)),
-      ].join(`${separator}${separator}`)
-    }
-  }
-
-  // encoding selected regions
-  let selectedRegionsString: string
-  if (selectedRegions.length === 1) {
-    const region = selectedRegions[0]
-    const labelIndex = getRegionLabelIndex(selectedAtlas, selectedTemplate, selectedParcellation, region)
-    
-    const ngId = getParcNgId(selectedAtlas, selectedTemplate, selectedParcellation, region)
-    selectedRegionsString = `${ngId}::${encodeNumber(labelIndex, { float: false })}`
-  }
-  let routes: any
-  
-  routes= {
-    // for atlas
-    a: selectedAtlas && encodeId(selectedAtlas['@id']),
-    // for template
-    t: selectedTemplate && encodeId(selectedTemplate['@id'] || selectedTemplate['fullId']),
-    // for parcellation
-    p: selectedParcellation && encodeId(selectedParcellation['@id'] || selectedParcellation['fullId']),
-    // for regions
-    r: selectedRegionsString && encodeURIFull(selectedRegionsString),
-    // nav
-    ['@']: cNavString,
-    // dataset file preview
-    dsp: dsPrvString && encodeURI(dsPrvString),
-  } as TUrlPathObj<string, TUrlAtlas<string>>
-
-  /**
-   * if any params needs to overwrite previosu routes, put them here
-   */
-  if (standaloneVolumes && Array.isArray(standaloneVolumes) && standaloneVolumes.length > 0) {
-    searchParam.set('standaloneVolumes', JSON.stringify(standaloneVolumes))
-    routes = {
-      // nav
-      ['@']: cNavString,
-      dsp: dsPrvString && encodeURI(dsPrvString)
-    } as TUrlPathObj<string|string[], TUrlStandaloneVolume<string[]>>
-  }
-
-  const routesArr: string[] = []
-  for (const key in routes) {
-    if (!!routes[key]) {
-      const segStr = endcodePath(key, routes[key])
-      routesArr.push(segStr)
-    }
-  }
-
-  return searchParam.toString() === '' 
-    ? routesArr.join('/')
-    : `${routesArr.join('/')}?${searchParam.toString()}`
-}
-
 export const verifyCustomState = (key: string) => {
   return /^x-/.test(key)
 }
diff --git a/src/state/atlasSelection/effects.ts b/src/state/atlasSelection/effects.ts
index 7620569b1..6454613ea 100644
--- a/src/state/atlasSelection/effects.ts
+++ b/src/state/atlasSelection/effects.ts
@@ -19,10 +19,12 @@ export class Effect {
     filter(action => !!action.atlas),
     switchMap(({ atlas }) => {
       const selectedParc = atlas.parcellations.find(p => /290/.test(p["@id"])) || atlas.parcellations[0]
-      return this.sapiSvc.getParcDetail(atlas["@id"], selectedParc["@id"], 100).then(
-        parcellation => ({
-          parcellation,
-          atlas
+      return this.sapiSvc.getParcDetail(atlas["@id"], selectedParc["@id"], { priority: 10 }).pipe(
+        map(parcellation => {
+          return {
+            parcellation,
+            atlas
+          }
         })
       )
     }),
diff --git a/src/state/index.ts b/src/state/index.ts
index 18f568ae7..034486928 100644
--- a/src/state/index.ts
+++ b/src/state/index.ts
@@ -60,4 +60,16 @@ export function getStoreEffects() {
   ]
 }
 
-export { MainState } from "./const"
+import { MainState } from "./const"
+
+export { MainState }
+
+export const defaultState: MainState = {
+  [userPreference.nameSpace]: userPreference.defaultState,
+  [atlasSelection.nameSpace]: atlasSelection.defaultState,
+  [userInterface.nameSpace]: userInterface.defaultState,
+  [userInteraction.nameSpace]: userInteraction.defaultState,
+  [annotation.nameSpace]: annotation.defaultState,
+  [plugins.nameSpace]: plugins.defaultState,
+  [atlasAppearance.nameSpace]: atlasAppearance.defaultState,
+}
diff --git a/src/util/priority.ts b/src/util/priority.ts
index 441ed5e0a..34673b81e 100644
--- a/src/util/priority.ts
+++ b/src/util/priority.ts
@@ -1,7 +1,7 @@
 import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from "@angular/common/http"
 import { Injectable } from "@angular/core"
 import { interval, merge, Observable, Subject, timer } from "rxjs"
-import { filter, finalize, switchMap, switchMapTo, take } from "rxjs/operators"
+import { filter, finalize, switchMap, switchMapTo, take, takeUntil, takeWhile } from "rxjs/operators"
 
 export const PRIORITY_HEADER = 'x-sxplr-http-priority'
 
@@ -12,10 +12,13 @@ type PriorityReq = {
   next: HttpHandler
 }
 
-@Injectable()
+@Injectable({
+  providedIn: 'root'
+})
 export class PriorityHttpInterceptor implements HttpInterceptor{
 
   private priorityQueue: PriorityReq[] = []
+  private currentJob: Set<string> = new Set()
 
   private priority$: Subject<PriorityReq> = new Subject()
 
@@ -24,10 +27,6 @@ export class PriorityHttpInterceptor implements HttpInterceptor{
   private counter = 0
   private max = 6
 
-  private shouldRun(){
-    return this.counter <= this.max
-  }
-
   constructor(){
     this.forceCheck$.pipe(
       switchMapTo(
@@ -35,17 +34,24 @@ export class PriorityHttpInterceptor implements HttpInterceptor{
           timer(0),
           interval(16)
         ).pipe(
-          filter(() => this.shouldRun())
+          filter(() => {
+            return this.counter <= this.max
+          }),
+          takeWhile(() => this.priorityQueue.length > 0)
         )
       )
     ).subscribe(() => {
-      this.priority$.next(
-        this.priorityQueue.pop()
-      )
+      const job = this.priorityQueue.pop()
+      if (!job) return
+      this.currentJob.add(job.urlWithParams)
+      this.priority$.next(job)
     })
   }
 
   updatePriority(urlWithParams: string, newPriority: number) {
+    
+    if (this.currentJob.has(urlWithParams)) return
+
     const foundIdx = this.priorityQueue.findIndex(v => v.urlWithParams === urlWithParams)
     if (foundIdx < 0) return false
     const [ item ] = this.priorityQueue.splice(foundIdx, 1)
@@ -57,7 +63,17 @@ export class PriorityHttpInterceptor implements HttpInterceptor{
   }
 
   private insert(obj: PriorityReq) {
-    const { priority } = obj
+    const { priority, urlWithParams } = obj
+    
+    if (this.currentJob.has(urlWithParams)) return
+
+    const existing = this.priorityQueue.find(q => q.urlWithParams === urlWithParams)
+    if (existing) {
+      if (existing.priority < priority) {
+        this.updatePriority(urlWithParams, priority)
+      }
+      return
+    }
     const foundIdx = this.priorityQueue.findIndex(q => q.priority <= priority)
     const useIndex = foundIdx >= 0 ? foundIdx : this.priorityQueue.length
     this.priorityQueue.splice(useIndex, 0, obj)
@@ -73,6 +89,8 @@ export class PriorityHttpInterceptor implements HttpInterceptor{
       next,
       urlWithParams
     }
+    return next.handle(req)
+    
 
     this.insert(objToInsert)
     this.forceCheck$.next(true)
@@ -81,10 +99,12 @@ export class PriorityHttpInterceptor implements HttpInterceptor{
       filter(v => v.urlWithParams === urlWithParams),
       take(1),
       switchMap(({ next, req }) => {
+        console.log("handle!!")
         this.counter ++  
         return next.handle(req)
       }),
       finalize(() => {
+        console.log('finalize??')
         this.counter --
       }),
     )
diff --git a/src/util/pureConstant.service.ts b/src/util/pureConstant.service.ts
index 68360a593..477a7c7a6 100644
--- a/src/util/pureConstant.service.ts
+++ b/src/util/pureConstant.service.ts
@@ -125,171 +125,6 @@ Raise/track issues at github repo: <a target = "_blank" href = "${this.repoUrl}"
     )
   }
 
-  private patchRegions$ = forkJoin(
-    patchRegions.map(patch => from(patch))
-  ).pipe(
-    shareReplay(1)
-  )
-
-  private getRegions(atlasId: string, parcId: string, spaceId: string){
-    return this.http.get<TRegionSummary[]>(
-      `${this.bsEndpoint}/atlases/${encodeURIComponent(atlasId)}/parcellations/${encodeURIComponent(parcId)}/regions`,
-      {
-        params: {
-          'space_id': spaceId
-        },
-        responseType: 'json'
-      }
-    ).pipe(
-      switchMap(regions => this.patchRegions$.pipe(
-        map(patchRegions => {
-          for (const p of patchRegions) {
-            if (
-              p.targetParcellation !== '*'
-              && Array.isArray(p.targetParcellation)
-              && p.targetParcellation.every(p => p["@id"] !== parcId)
-            ) {
-              continue
-            }
-            if (
-              p.targetSpace !== '*'
-              && Array.isArray(p.targetSpace)
-              && p.targetSpace.every(sp => sp['@id'] !== spaceId)
-            ) {
-              continue
-            }
-
-            recursiveMutate(
-              regions,
-              r => r.children || [],
-              region => {
-
-                if (p["@type"] === 'julich/siibra/append-region/v0.0.1') {
-                  if (p.parent['name'] === region.name) {
-                    if (!region.children) region.children = []
-                    region.children.push(
-                      p.payload as TRegionSummary
-                    )
-                  }
-                }
-                if (p['@type'] === 'julich/siibra/patch-region/v0.0.1') {
-                  if (p.target['name'] === region.name) {
-                    mutateDeepMerge(
-                      region,
-                      p.payload
-                    )
-                  }
-                }
-              },
-              true
-            )
-          }
-          return regions
-        })
-      ))
-    )
-  }
-
-  private getParcs(atlasId: string){
-    return this.http.get<TParc[]>(
-      `${this.bsEndpoint}/atlases/${encodeURIComponent(atlasId)}/parcellations`,
-      { responseType: 'json' }
-    )
-  }
-
-  private httpCallCache = new Map<string, Observable<any>>()
-
-  private getParcDetail(atlasId: string, parcId: string) {
-    return this.http.get<TParc>(
-      `${this.bsEndpoint}/atlases/${encodeURIComponent(atlasId)}/parcellations/${encodeURIComponent(parcId)}`,
-      { responseType: 'json' }
-    )
-  }
-
-  private getSpaces(atlasId: string){
-    return this.http.get<TSpaceSummary[]>(
-      `${this.bsEndpoint}/atlases/${encodeURIComponent(atlasId)}/spaces`,
-      { responseType: 'json' }
-    )
-  }
-
-  private getSpaceDetail(atlasId: string, spaceId: string) {
-    return this.http.get<TSpaceFull>(
-      `${this.bsEndpoint}/atlases/${encodeURIComponent(atlasId)}/spaces/${encodeURIComponent(spaceId)}`,
-      { responseType: 'json' }
-    )
-  }
-
-  private getSpacesAndParc(atlasId: string): Observable<{ templateSpaces: TSpaceFull[], parcellations: TParc[] }> {
-    const cacheKey = `getSpacesAndParc::${atlasId}`
-    if (this.httpCallCache.has(cacheKey)) return this.httpCallCache.get(cacheKey)
-    
-    const spaces$ = this.getSpaces(atlasId).pipe(
-      switchMap(spaces => spaces.length > 0
-        ? forkJoin(
-          spaces.map(space => this.getSpaceDetail(atlasId, parseId(space.id)))
-        )
-        : of([]))
-    )
-    const parcs$ = this.getParcs(atlasId).pipe(
-      // need not to get full parc data. first level gets all data
-      // switchMap(parcs => forkJoin(
-      //   parcs.map(parc => this.getParcDetail(atlasId, parseId(parc.id)))
-      // ))
-    )
-    const returnObs = forkJoin([
-      spaces$,
-      parcs$,
-    ]).pipe(
-      map(([ templateSpaces, parcellations ]) => {
-        /**
-         * select only parcellations that contain renderable volume(s)
-         */
-        const filteredParcellations = parcellations.filter(p => {
-          return p._dataset_specs.some(spec => spec["@type"] === 'fzj/tmp/volume_type/v0.0.1' && validVolumeType.has(spec.volume_type))
-        })
-
-        /**
-         * remove parcellation versions that are marked as deprecated
-         * and assign prev/next id accordingly
-         */
-        for (const p of filteredParcellations) {
-          if (!p.version) continue
-          if (p.version.deprecated) {
-            const prevId = p.version.prev
-            const nextId = p.version.next
-
-            const prev = prevId && filteredParcellations.find(p => parseId(p.id) === prevId)
-            const next = nextId && filteredParcellations.find(p => parseId(p.id) === nextId)
-
-            const newPrevId = prev && parseId(prev.id)
-            const newNextId = next && parseId(next.id)
-
-            if (!!prev.version) {
-              prev.version.next = newNextId
-            }
-
-            if (!!next.version) {
-              next.version.prev = newPrevId
-            }
-          }
-        }
-        const removeDeprecatedParc = filteredParcellations.filter(p => {
-          if (!p.version) return true
-          return !(p.version.deprecated)
-        })
-
-        return {
-          templateSpaces,
-          parcellations: removeDeprecatedParc
-        }
-      }),
-      shareReplay(1)
-    )
-    this.httpCallCache.set(cacheKey, returnObs)
-    return returnObs
-  }
-
   constructor(
     private store: Store<any>,
     private http: HttpClient,
diff --git a/src/viewerModule/nehuba/store/util.ts b/src/viewerModule/nehuba/store/util.ts
index 8fefc1aea..fdc923b8f 100644
--- a/src/viewerModule/nehuba/store/util.ts
+++ b/src/viewerModule/nehuba/store/util.ts
@@ -43,9 +43,9 @@ export const fromRootStore = {
     filter(({ parcellation }) => !!parcellation),
     switchMap(({ atlas, template, parcellation }) => {
       return forkJoin([
-        sapi.registry.get<SAPIParcellation>(parcellation["@id"])
-          .getRegions(template["@id"])
-          .then(regions => {
+        sapi.registry.get<SAPIParcellation>(parcellation["@id"]).getRegions(template["@id"]).pipe(
+          map(regions => {
+
             const returnArr: ParcVolumeSpec[] = []
             for (const r of regions) {
               const source = r?.hasAnnotation?.visualizedIn?.["@id"]
@@ -71,7 +71,8 @@ export const fromRootStore = {
               })
             }
             return returnArr
-          }),
+          })
+        ),
         sapi.registry.get<SAPIParcellation>(parcellation["@id"]).getVolumes()
       ]).pipe(
         map(([ volumeSrcs, volumes ]) => {
-- 
GitLab