diff --git a/Dockerfile b/Dockerfile
index a20f2d36324fee46ce3a90f029ba9361839ab550..67876d05bf57addb564b5e6c48622c12719ef1ab 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,4 +1,4 @@
-FROM node:8 as builder
+FROM node:10 as builder
 
 ARG BACKEND_URL
 ENV BACKEND_URL=$BACKEND_URL
@@ -8,12 +8,23 @@ WORKDIR /iv
 
 ENV VERSION=devNext
 
+RUN apt update && apt upgrade -y && apt install brotli
+
 RUN npm i
 RUN npm run build-aot
 
+# gzipping container
+FROM ubuntu:18.10 as compressor
+RUN apt upgrade -y && apt update && apt install brotli
+
+RUN mkdir /iv
+COPY --from=builder /iv/dist/aot /iv
+WORKDIR /iv
+
+RUN for f in $(find . -type f); do gzip < $f > $f.gz && brotli < $f > $f.br; done
 
 # prod container
-FROM node:8-alpine 
+FROM node:10-alpine 
 
 ARG PORT
 ENV PORT=$PORT
@@ -23,14 +34,15 @@ RUN apk --no-cache add ca-certificates
 RUN mkdir /iv-app
 WORKDIR /iv-app
 
-# Copy built interactive viewer
-COPY --from=builder /iv/dist/aot ./public
-
 # Copy the express server
 COPY --from=builder /iv/deploy .
 
+# Copy built interactive viewer
+COPY --from=compressor /iv ./public
+
 # Copy the resources files needed to respond to queries
-COPY --from=builder /iv/src/res/ext ./res
+# is this even necessary any more?
+COPY --from=compressor /iv/res/json ./res
 RUN npm i
 
 EXPOSE $PORT
diff --git a/deploy/app.js b/deploy/app.js
index 8b815a3607c836f6810190d68ffb632916bc2ba5..210e2ac9a32f39229d1f377025c8a1373760603a 100644
--- a/deploy/app.js
+++ b/deploy/app.js
@@ -3,6 +3,7 @@ const express = require('express')
 const app = express()
 const session = require('express-session')
 const MemoryStore = require('memorystore')(session)
+const crypto = require('crypto')
 
 app.disable('x-powered-by')
 
@@ -10,14 +11,17 @@ if (process.env.NODE_ENV !== 'production') {
   app.use(require('cors')())
 }
 
+const hash = string => crypto.createHash('sha256').update(string).digest('hex')
+
 app.use((req, _, next) => {
   if (/main\.bundle\.js$/.test(req.originalUrl)){
     const xForwardedFor = req.headers['x-forwarded-for']
     const ip = req.connection.remoteAddress
     console.log({
       type: 'visitorLog',
-      xForwardedFor,
-      ip
+      method: 'main.bundle.js',
+      xForwardedFor: xForwardedFor.replace(/\ /g, '').split(',').map(hash),
+      ip: hash(ip)
     })
   }
   next()
@@ -62,12 +66,18 @@ const PUBLIC_PATH = process.env.NODE_ENV === 'production'
  */
 app.use('/.well-known', express.static(path.join(__dirname, 'well-known')))
 
-app.use(express.static(PUBLIC_PATH))
+/**
+ * only use compression for production
+ * this allows locally built aot to be served without errors
+ */
+
+const { compressionMiddleware } = require('./compression')
+app.use(compressionMiddleware, express.static(PUBLIC_PATH))
 
-app.use((req, res, next) => {
+const jsonMiddleware = (req, res, next) => {
   res.set('Content-Type', 'application/json')
   next()
-})
+}
 
 const templateRouter = require('./templates')
 const nehubaConfigRouter = require('./nehubaConfig')
@@ -75,11 +85,11 @@ const datasetRouter = require('./datasets')
 const pluginRouter = require('./plugins')
 const previewRouter = require('./preview')
 
-app.use('/templates', templateRouter)
-app.use('/nehubaConfig', nehubaConfigRouter)
-app.use('/datasets', datasetRouter)
-app.use('/plugins', pluginRouter)
-app.use('/preview', previewRouter)
+app.use('/templates', jsonMiddleware, templateRouter)
+app.use('/nehubaConfig', jsonMiddleware, nehubaConfigRouter)
+app.use('/datasets', jsonMiddleware, datasetRouter)
+app.use('/plugins', jsonMiddleware, pluginRouter)
+app.use('/preview', jsonMiddleware, previewRouter)
 
 const catchError = require('./catchError')
 app.use(catchError)
diff --git a/deploy/compression/index.js b/deploy/compression/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..bc5d2acb4b69d219d1a6c32bb0148296c4d020c4
--- /dev/null
+++ b/deploy/compression/index.js
@@ -0,0 +1,59 @@
+const BROTLI = `br`
+const GZIP = `gzip`
+
+const detEncoding = (acceptEncoding = '') => {
+  if (process.env.NODE_ENV !== 'production') return null
+
+  return /br/i.test(acceptEncoding)
+    ? BROTLI
+    : /gzip/i.test(acceptEncoding)
+      ? GZIP
+      : null
+}
+
+const mimeMap = new Map([
+  ['.png', 'image/png'],
+  ['.gif', 'image/gif'],
+  ['.jpg', 'image/jpeg'],
+  ['.jpeg', 'image/jpeg'],
+  ['.css', 'text/css'],
+  ['.html', 'text/html'],
+  ['.js', 'text/javascript']
+])
+
+exports.BROTLI = BROTLI
+
+exports.GZIP = GZIP
+
+exports.detEncoding = detEncoding
+
+exports.compressionMiddleware = (req, res, next) => {
+  const acceptEncoding = req.get('Accept-Encoding')
+  const encoding = detEncoding(acceptEncoding)
+
+  // if no encoding is accepted
+  // or in dev mode, do not use compression
+  if (!encoding) return next()
+  
+  const ext = /(\.\w*?)$/.exec(req.url)
+
+  // if cannot determine mime-type, do not use encoding
+  // as Content-Type header is required for browser to understand response
+  if (!ext || !mimeMap.get(ext[1])) return next()
+  
+  res.set('Content-Type', mimeMap.get(ext[1]))
+
+  if (encoding === BROTLI) {
+    req.url = req.url + '.br'
+    res.set('Content-Encoding', encoding)
+    return next()
+  }
+
+  if (encoding === GZIP) {
+    req.url = req.url + '.gz'
+    res.set('Content-Encoding', encoding)
+    return next()
+  }
+
+  next()
+}
\ No newline at end of file
diff --git a/deploy/compression/index.spec.js b/deploy/compression/index.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..3c1a3826fb3b8151a73c8d180b8d31b49f82c6c4
--- /dev/null
+++ b/deploy/compression/index.spec.js
@@ -0,0 +1,39 @@
+const mocha = require('mocha')
+const chai = require('chai')
+const expect = chai.expect
+
+const { detEncoding, GZIP, BROTLI } = require('./index')
+
+const gzip = 'gzip'
+const gzipDeflate = 'gzip, deflate'
+const gzipDeflateBr = 'gzip, deflate, br'
+
+describe('compression/index.js', () => {
+  let nodeEnv
+  
+  before(() => {
+    nodeEnv = process.env.NODE_ENV
+  })
+
+  after(() => {
+    process.env.NODE_ENV = nodeEnv
+  })
+  
+  describe('#detEncoding', () => {
+    it('When NODE_ENV is set to production, returns appropriate encoding', () => {
+      process.env.NODE_ENV = 'production'
+      expect(detEncoding(null)).to.equal(null)
+      expect(detEncoding(gzip)).to.equal(GZIP)
+      expect(detEncoding(gzipDeflate)).to.equal(GZIP)
+      expect(detEncoding(gzipDeflateBr)).to.equal(BROTLI)
+    })
+
+    it('When NODE_ENV is set to non production, returns null always', () => {
+      process.env.NODE_ENV = 'development'
+      expect(detEncoding(null)).to.equal(null)
+      expect(detEncoding(gzip)).to.equal(null)
+      expect(detEncoding(gzipDeflate)).to.equal(null)
+      expect(detEncoding(gzipDeflateBr)).to.equal(null)
+    })
+  })
+})
\ No newline at end of file
diff --git a/deploy/datasets/index.js b/deploy/datasets/index.js
index aae9728421904d2487af620abc6a51e18159529d..cb69686463aa6cd2240a183f3c09340a5d0e5b02 100644
--- a/deploy/datasets/index.js
+++ b/deploy/datasets/index.js
@@ -136,7 +136,7 @@ datasetsRouter.get('/kgInfo', checkKgQuery, cacheMaxAge24Hr, async (req, res) =>
   stream.pipe(res)
 })
 
-datasetsRouter.get('/downloadKgFiles', checkKgQuery, cacheMaxAge24Hr, async (req, res) => {
+datasetsRouter.get('/downloadKgFiles', checkKgQuery, async (req, res) => {
   const { kgId } = req.query
   const { user } = req
   try {
diff --git a/deploy/nehubaConfig/index.js b/deploy/nehubaConfig/index.js
index a5072c82865d230eb8d5320c028d5f9505ec1e38..145441ee8d730b5a0df2cabccf6e2612d86f2cb4 100644
--- a/deploy/nehubaConfig/index.js
+++ b/deploy/nehubaConfig/index.js
@@ -1,19 +1,18 @@
 const express = require('express')
-const path = require('path')
-const fs = require('fs')
 const { getTemplateNehubaConfig } = require('./query')
+const { detEncoding } = require('../compression')
 
 const nehubaConfigRouter = express.Router()
 
 nehubaConfigRouter.get('/:configId', (req, res, next) => {
+
+  const header = req.get('Accept-Encoding')
+  const acceptedEncoding = detEncoding(header)
+
   const { configId } = req.params
-  getTemplateNehubaConfig(configId)
-    .then(data => res.status(200).send(data))
-    .catch(error => next({
-      code: 500,
-      error,
-      trace: 'nehubaConfigRouter#getTemplateNehubaConfig'
-    }))
+  if (acceptedEncoding) res.set('Content-Encoding', acceptedEncoding)
+
+  getTemplateNehubaConfig({ configId, acceptedEncoding, returnAsStream:true}).pipe(res)
 })
 
 module.exports = nehubaConfigRouter
\ No newline at end of file
diff --git a/deploy/nehubaConfig/query.js b/deploy/nehubaConfig/query.js
index 972c3d59633cd3748d3678a636f8bcecdf7ed0d9..23a6132ba39c72a83f47159e3ed106a296ac7250 100644
--- a/deploy/nehubaConfig/query.js
+++ b/deploy/nehubaConfig/query.js
@@ -1,15 +1,31 @@
 const fs = require('fs')
 const path = require('path')
+const { BROTLI, GZIP } = require('../compression')
 
-exports.getTemplateNehubaConfig = (configId) => new Promise((resolve, reject) => {
-  let filepath
+const getFileAsPromise = filepath => new Promise((resolve, reject) => {
+  fs.readFile(filepath, 'utf-8', (err, data) => {
+    if (err) return reject(err)
+    resolve(data)
+  })
+})
+
+exports.getTemplateNehubaConfig = ({configId, acceptedEncoding, returnAsStream}) => {
   if (process.env.NODE_ENV === 'production') {
     filepath = path.join(__dirname, '..', 'res', `${configId}.json`)
   } else {
     filepath = path.join(__dirname, '..', '..', 'src', 'res', 'ext', `${configId}.json`)
   }
-  fs.readFile(filepath, 'utf-8', (err, data) => {
-    if (err) return reject(err)
-    resolve(data)
-  })
-})
\ No newline at end of file
+
+  if (acceptedEncoding === BROTLI) {
+    if (returnAsStream) return fs.createReadStream(`${filepath}.br`)
+    else return getFileAsPromise(`${filepath}.br`)
+  }
+
+  if (acceptedEncoding === GZIP) {
+    if (returnAsStream) return fs.createReadStream(`${filepath}.gz`)
+    else return getFileAsPromise(`${filepath}.gz`)
+  }
+
+  if (returnAsStream) return fs.createReadStream(filepath)
+  else return getFileAsPromise(filepath)
+} 
\ No newline at end of file
diff --git a/deploy/plugins/index.js b/deploy/plugins/index.js
index 9f15b4687a85be1f07b76284694132fd0fb799eb..f0216dea9b1aa14f7a85dfc955a11b530e9f740e 100644
--- a/deploy/plugins/index.js
+++ b/deploy/plugins/index.js
@@ -5,15 +5,11 @@
 
 const express = require('express')
 const router = express.Router()
-const PLUGIN_URLS = process.env.PLUGIN_URLS && JSON.stringify(process.env.PLUGIN_URLS.split(';'))
+const PLUGIN_URLS = (process.env.PLUGIN_URLS && process.env.PLUGIN_URLS.split(';'))
+  || []
 
 router.get('', (_req, res) => {
-
-  if (PLUGIN_URLS) {
-    return res.status(200).send(PLUGIN_URLS)
-  } else {
-    return res.status(200).send('[]')
-  }
+  return res.status(200).json(PLUGIN_URLS)
 })
 
 module.exports = router
\ No newline at end of file
diff --git a/deploy/server.js b/deploy/server.js
index 4256b418128deae6033e4f2bfe29aa94a6c33008..9045664cf8a90542b482513e2026eacf291ea016 100644
--- a/deploy/server.js
+++ b/deploy/server.js
@@ -51,7 +51,7 @@ if (process.env.FLUENT_HOST) {
   console.warn = function () {
     emitWarn([...arguments])
   }
-  console.emitError = function () {
+  console.error = function () {
     emitError([...arguments])
   }
 }
diff --git a/deploy/templates/index.js b/deploy/templates/index.js
index 1ed84e544f0754e84ee8f48855d7b30975361de4..d9fbeb8313a14464177a2c00f47fbda8392d707b 100644
--- a/deploy/templates/index.js
+++ b/deploy/templates/index.js
@@ -1,6 +1,8 @@
 const router = require('express').Router()
 const query = require('./query')
 const path = require('path')
+const { detEncoding } = require('../compression')
+
 /**
  * root path fetches all templates
  */
@@ -20,6 +22,10 @@ router.get('/', (req, res, next) => {
 
 router.get('/:template', (req, res, next) => {
   const { template } = req.params
+
+  const header = req.get('Accept-Encoding')
+  const acceptedEncoding = detEncoding(header)
+
   query.getAllTemplates()
     .then(templates => {
       if (templates.indexOf(template) < 0) 
@@ -27,11 +33,9 @@ router.get('/:template', (req, res, next) => {
           code : 404,
           error: 'template not in the list supported'
         })
-      return query.getTemplate(template)
-    })
-    .then(data => {
-      if (data)
-        res.status(200).send(data)
+
+      if (acceptedEncoding) res.set('Content-Encoding', acceptedEncoding)
+      query.getTemplate({ template, acceptedEncoding, returnAsStream:true }).pipe(res)
     })
     .catch(error => next({
       code: 500,
diff --git a/deploy/templates/query.js b/deploy/templates/query.js
index 1ecebe476dce91c21819342ad6031f46643b1db8..c1886db02efd03033514ec5a33c113b60b5b8842 100644
--- a/deploy/templates/query.js
+++ b/deploy/templates/query.js
@@ -1,5 +1,6 @@
 const fs = require('fs')
 const path = require('path')
+const { BROTLI, GZIP } = require('../compression')
 
 exports.getAllTemplates = () => new Promise((resolve, reject) => {
   
@@ -17,15 +18,32 @@ exports.getAllTemplates = () => new Promise((resolve, reject) => {
   resolve(templates)
 })
 
-exports.getTemplate = (template) => new Promise((resolve, reject) => {
+const getFileAsPromise = filepath => new Promise((resolve, reject) => {
+  fs.readFile(filepath, 'utf-8', (err, data) => {
+    if (err) return reject(err)
+    resolve(data)
+  })
+})
+
+exports.getTemplate = ({ template, acceptedEncoding, returnAsStream }) => {
+
   let filepath
   if (process.env.NODE_ENV === 'production') {
     filepath = path.join(__dirname, '..', 'res', `${template}.json`)
   } else {
     filepath = path.join(__dirname, '..', '..', 'src', 'res', 'ext', `${template}.json`)
   }
-  fs.readFile(filepath, 'utf-8', (err, data) => {
-    if (err) reject(err)
-    resolve(data)
-  })
-})
\ No newline at end of file
+
+  if (acceptedEncoding === BROTLI) {
+    if (returnAsStream) return fs.createReadStream(`${filepath}.br`)
+    else return getFileAsPromise(`${filepath}.br`)
+  }
+
+  if (acceptedEncoding === GZIP) {
+    if (returnAsStream) return fs.createReadStream(`${filepath}.gz`)
+    else return getFileAsPromise(`${filepath}.gz`)
+  }
+
+  if (returnAsStream) return fs.createReadStream(filepath)
+  else return getFileAsPromise(filepath)
+} 
\ No newline at end of file
diff --git a/deploy/test/mocha.test.js b/deploy/test/mocha.test.js
index 8f55b54de1f2566b8d2348adef651303b0ef1fca..366f5af4a874c10e907272ba0ece2b686248078a 100644
--- a/deploy/test/mocha.test.js
+++ b/deploy/test/mocha.test.js
@@ -1 +1,2 @@
-require('../auth/util.spec')
\ No newline at end of file
+require('../auth/util.spec')
+require('../compression/index.spec')
\ No newline at end of file
diff --git a/package.json b/package.json
index 606c5a8660664fd4388fe152db45482345e010d7..a2ed139c5d747a35ef3c5ed5d8db25138d6831cb 100644
--- a/package.json
+++ b/package.json
@@ -48,6 +48,7 @@
     "chart.js": "^2.7.2",
     "codelyzer": "^5.0.1",
     "core-js": "^3.0.1",
+    "css-loader": "^3.2.0",
     "file-loader": "^1.1.11",
     "hammerjs": "^2.0.8",
     "html-webpack-plugin": "^3.2.0",
@@ -61,12 +62,15 @@
     "karma-typescript": "^3.0.13",
     "karma-webpack": "^3.0.0",
     "lodash.merge": "^4.6.1",
+    "mini-css-extract-plugin": "^0.8.0",
     "ng2-charts": "^1.6.0",
     "ngx-bootstrap": "3.0.1",
+    "node-sass": "^4.12.0",
     "protractor": "^5.4.2",
     "raw-loader": "^0.5.1",
     "reflect-metadata": "^0.1.12",
     "rxjs": "6.5.1",
+    "sass-loader": "^7.2.0",
     "showdown": "^1.8.6",
     "ts-loader": "^4.3.0",
     "ts-node": "^8.1.0",
diff --git a/src/atlasViewer/atlasViewer.apiService.service.ts b/src/atlasViewer/atlasViewer.apiService.service.ts
index c77f929e75c57a05e1fb0c1ae086681fd809baa7..475cb5c09879a2d1fc134e753752428d2884b706 100644
--- a/src/atlasViewer/atlasViewer.apiService.service.ts
+++ b/src/atlasViewer/atlasViewer.apiService.service.ts
@@ -6,6 +6,7 @@ import { map, distinctUntilChanged, filter } from "rxjs/operators";
 import { ModalHandler } from "../util/pluginHandlerClasses/modalHandler";
 import { ToastHandler } from "../util/pluginHandlerClasses/toastHandler";
 import { PluginManifest } from "./atlasViewer.pluginService.service";
+import { DialogService } from "src/services/dialogService.service";
 
 declare var window
 
@@ -22,7 +23,8 @@ export class AtlasViewerAPIServices{
   public loadedLibraries : Map<string,{counter:number,src:HTMLElement|null}> = new Map()
 
   constructor(
-    private store : Store<ViewerStateInterface>
+    private store : Store<ViewerStateInterface>,
+    private dialogService: DialogService,
   ){
 
     this.loadedTemplates$ = this.store.pipe(
@@ -107,7 +109,10 @@ export class AtlasViewerAPIServices{
          */
         launchNewWidget: (manifest) => {
           return Promise.reject('Needs to be overwritted')
-        }
+        },
+
+        getUserInput: config => this.dialogService.getUserInput(config),
+        getUserConfirmation: config => this.dialogService.getUserConfirm(config)
       },
       pluginControl : {
         loadExternalLibraries : ()=>Promise.reject('load External Library method not over written')
@@ -175,6 +180,8 @@ export interface InteractiveViewerInterface{
     getModalHandler: () => ModalHandler
     getToastHandler: () => ToastHandler
     launchNewWidget: (manifest:PluginManifest) => Promise<any>
+    getUserInput: (config:GetUserInputConfig) => Promise<string>
+    getUserConfirmation: (config: GetUserConfirmation) => Promise<any>
   }
 
   pluginControl : {
@@ -184,6 +191,16 @@ export interface InteractiveViewerInterface{
   }
 }
 
+interface GetUserConfirmation{
+  title?: string
+  message?: string
+}
+
+interface GetUserInputConfig extends GetUserConfirmation{
+  placeholder?: string
+  defaultValue?: string
+}
+
 export interface UserLandmark{
   name : string
   position : [number, number, number]
diff --git a/src/atlasViewer/atlasViewer.component.ts b/src/atlasViewer/atlasViewer.component.ts
index d012da787f89856adb803548c86fd5be2799a558..c2b66ac5731e0d3805b5d54eb27ad21743cbaf9c 100644
--- a/src/atlasViewer/atlasViewer.component.ts
+++ b/src/atlasViewer/atlasViewer.component.ts
@@ -10,8 +10,6 @@ import { AtlasViewerConstantsServices, UNSUPPORTED_PREVIEW, UNSUPPORTED_INTERVAL
 import { AtlasViewerURLService } from "./atlasViewer.urlService.service";
 import { AtlasViewerAPIServices } from "./atlasViewer.apiService.service";
 
-import '@angular/material/prebuilt-themes/indigo-pink.css'
-import '../res/css/extra_styles.css'
 import { NehubaContainer } from "../ui/nehubaContainer/nehubaContainer.component";
 import { colorAnimation } from "./atlasViewer.animation"
 import { FixedMouseContextualContainerDirective } from "src/util/directives/FixedMouseContextualContainerDirective.directive";
@@ -317,6 +315,12 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit {
         filter(() => typeof this.layoutMainSide !== 'undefined')
       ).subscribe(v => this.layoutMainSide.showSide =  isDefined(v))
     )
+
+    this.subscriptions.push(
+      this.constantsService.darktheme$.subscribe(flag => {
+        this.rd.setAttribute(document.body,'darktheme', flag.toString())
+      })
+    )
   }
 
   ngAfterViewInit() {
diff --git a/src/atlasViewer/atlasViewer.constantService.service.spec.ts b/src/atlasViewer/atlasViewer.constantService.service.spec.ts
index 71c9ecd02371900c4e3403e024f9d6e9c648a153..72f7f4c3f146f2fef352a2a3e0c6daa3c91ea224 100644
--- a/src/atlasViewer/atlasViewer.constantService.service.spec.ts
+++ b/src/atlasViewer/atlasViewer.constantService.service.spec.ts
@@ -107,4 +107,26 @@ describe('encodeNumber/decodeToNumber', () => {
 
     expect(floatNums.map(v => v.toFixed(FLOAT_PRECISION))).toEqual(decodedNumber.map(n => n.toFixed(FLOAT_PRECISION)))
   })
+
+  it('poisoned hash should throw', () => {
+    const illegialCharacters = './\\?#!@#^%&*()+={}[]\'"\n\t;:'
+    for (let char of illegialCharacters.split('')) {
+      expect(function (){
+        decodeToNumber(char)
+      }).toThrow()
+    }
+  })
+
+  it('poisoned hash can be caught', () => {
+
+    const testArray = ['abc', './\\', 'Cde']
+    const decodedNum = testArray.map(v => {
+      try {
+        return decodeToNumber(v)
+      } catch (e) {
+        return null
+      }
+    }).filter(v => !!v)
+    expect(decodedNum.length).toEqual(2)
+  })
 })
\ No newline at end of file
diff --git a/src/atlasViewer/atlasViewer.constantService.service.ts b/src/atlasViewer/atlasViewer.constantService.service.ts
index 958026caa32850fd8fe5f87fbcb979ec2766db7f..bb1d4183e203ac97f3c9d54c52a49d18e5e660e1 100644
--- a/src/atlasViewer/atlasViewer.constantService.service.ts
+++ b/src/atlasViewer/atlasViewer.constantService.service.ts
@@ -1,8 +1,9 @@
 import { Injectable } from "@angular/core";
-import { Store } from "@ngrx/store";
-import { ViewerStateInterface, Property } from "../services/stateStore.service";
-import { Subject } from "rxjs";
+import { Store, select } from "@ngrx/store";
+import { ViewerStateInterface } from "../services/stateStore.service";
+import { Subject, Observable } from "rxjs";
 import { ACTION_TYPES, ViewerConfiguration } from 'src/services/state/viewerConfig.store'
+import { map, shareReplay, filter } from "rxjs/operators";
 
 export const CM_THRESHOLD = `0.05`
 export const CM_MATLAB_JET = `float r;if( x < 0.7 ){r = 4.0 * x - 1.5;} else {r = -4.0 * x + 4.5;}float g;if (x < 0.5) {g = 4.0 * x - 0.5;} else {g = -4.0 * x + 3.5;}float b;if (x < 0.3) {b = 4.0 * x + 0.5;} else {b = -4.0 * x + 2.5;}float a = 1.0;`
@@ -14,6 +15,7 @@ export const CM_MATLAB_JET = `float r;if( x < 0.7 ){r = 4.0 * x - 1.5;} else {r
 export class AtlasViewerConstantsServices{
 
   public darktheme: boolean = false
+  public darktheme$: Observable<boolean>
   public mobile: boolean
   public loadExportNehubaPromise : Promise<boolean>
 
@@ -246,6 +248,14 @@ Interactive atlas viewer requires **webgl2.0**, and the \`EXT_color_buffer_float
     /* https://stackoverflow.com/a/25394023/6059235 */
     this.mobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini|Mobile|mobile|CriOS/i.test(ua)
 
+    this.darktheme$ = this.store.pipe(
+      select('viewerState'),
+      select('templateSelected'),
+      filter(v => !!v),
+      map(({useTheme}) => useTheme === 'dark'),
+      shareReplay(1)
+    )
+
     /**
      * set gpu limit if user is on mobile
      */
@@ -258,6 +268,14 @@ Interactive atlas viewer requires **webgl2.0**, and the \`EXT_color_buffer_float
       })  
     }
   }
+
+  catchError(e: Error | string){
+    /**
+     * DO NOT REMOVE
+     * general catch all & reflect in UI
+     */
+    console.warn(e)
+  }
 }
 
 const parseURLToElement = (url:string):HTMLElement=>{
@@ -312,8 +330,7 @@ const negString = '~'
 
 const encodeInt = (number: number) => {
   if (number % 1 !== 0) throw 'cannot encodeInt on a float. Ensure float flag is set'
-  if (isNaN(Number(number)) || number === null || number === Number.POSITIVE_INFINITY)
-    throw 'The input is not valid'
+  if (isNaN(Number(number)) || number === null || number === Number.POSITIVE_INFINITY) throw 'The input is not valid'
 
   let rixit // like 'digit', only in some non-decimal radix 
   let residual
@@ -370,7 +387,9 @@ const decodetoInt = (encodedString: string) => {
     _encodedString = encodedString
   }
   return (negFlag ? -1 : 1) * [..._encodedString].reduce((acc,curr) => {
-    return acc * 64 + cipher.indexOf(curr)
+    const index = cipher.indexOf(curr)
+    if (index < 0) throw new Error(`Poisoned b64 encoding ${encodedString}`)
+    return acc * 64 + index
   }, 0)
 }
 
diff --git a/src/atlasViewer/atlasViewer.pluginService.service.spec.ts b/src/atlasViewer/atlasViewer.pluginService.service.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a040e3f2360184ecb8df8e08eef7111667012a3c
--- /dev/null
+++ b/src/atlasViewer/atlasViewer.pluginService.service.spec.ts
@@ -0,0 +1,108 @@
+import { PluginServices } from "./atlasViewer.pluginService.service";
+import { TestBed, inject } from "@angular/core/testing";
+import { MainModule } from "src/main.module";
+import { HttpTestingController, HttpClientTestingModule } from '@angular/common/http/testing'
+
+const MOCK_PLUGIN_MANIFEST = {
+  name: 'fzj.xg.MOCK_PLUGIN_MANIFEST',
+  templateURL: 'http://localhost:10001/template.html',
+  scriptURL: 'http://localhost:10001/script.js'
+}
+
+describe('PluginServices', () => {
+  let pluginService: PluginServices
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [
+        HttpClientTestingModule,
+        MainModule
+      ]
+    }).compileComponents()
+
+    pluginService = TestBed.get(PluginServices)
+  })
+
+  it(
+    'is instantiated in test suite OK',
+    () => expect(TestBed.get(PluginServices)).toBeTruthy()
+  )
+
+  it(
+    'expectOne is working as expected',
+    inject([HttpTestingController], (httpMock: HttpTestingController) => {
+      expect(httpMock.match('test').length).toBe(0)
+      pluginService.fetch('test')
+      expect(httpMock.match('test').length).toBe(1)
+      pluginService.fetch('test')
+      pluginService.fetch('test')
+      expect(httpMock.match('test').length).toBe(2)
+    })
+  )
+
+  describe('#launchPlugin', () => {
+
+    describe('basic fetching functionality', () => {
+      it(
+        'fetches templateURL and scriptURL properly',
+        inject([HttpTestingController], (httpMock: HttpTestingController) => {
+  
+          pluginService.launchPlugin(MOCK_PLUGIN_MANIFEST)
+    
+          const mockTemplate = httpMock.expectOne(MOCK_PLUGIN_MANIFEST.templateURL)
+          const mockScript = httpMock.expectOne(MOCK_PLUGIN_MANIFEST.scriptURL)
+    
+          expect(mockTemplate).toBeTruthy()
+          expect(mockScript).toBeTruthy()
+        })
+      )
+  
+      it(
+        'template overrides templateURL',
+        inject([HttpTestingController], (httpMock: HttpTestingController) => {
+          pluginService.launchPlugin({
+            ...MOCK_PLUGIN_MANIFEST,
+            template: ''
+          })
+          
+          httpMock.expectNone(MOCK_PLUGIN_MANIFEST.templateURL)
+          const mockScript = httpMock.expectOne(MOCK_PLUGIN_MANIFEST.scriptURL)
+    
+          expect(mockScript).toBeTruthy()
+        })
+      )
+  
+      it(
+        'script overrides scriptURL',
+  
+        inject([HttpTestingController], (httpMock: HttpTestingController) => {
+          pluginService.launchPlugin({
+            ...MOCK_PLUGIN_MANIFEST,
+            script: ''
+          })
+          
+          const mockTemplate = httpMock.expectOne(MOCK_PLUGIN_MANIFEST.templateURL)
+          httpMock.expectNone(MOCK_PLUGIN_MANIFEST.scriptURL)
+    
+          expect(mockTemplate).toBeTruthy()
+        })
+      )
+    })
+
+    describe('racing slow cconnection when launching plugin', () => {
+      it(
+        'when template/script has yet been fetched, repeated launchPlugin should not result in repeated fetching',
+        inject([HttpTestingController], (httpMock:HttpTestingController) => {
+
+          expect(pluginService.pluginIsLaunching(MOCK_PLUGIN_MANIFEST.name)).toBeFalsy()
+          pluginService.launchPlugin(MOCK_PLUGIN_MANIFEST)
+          pluginService.launchPlugin(MOCK_PLUGIN_MANIFEST)
+          expect(httpMock.match(MOCK_PLUGIN_MANIFEST.scriptURL).length).toBe(1)
+          expect(httpMock.match(MOCK_PLUGIN_MANIFEST.templateURL).length).toBe(1)
+
+          expect(pluginService.pluginIsLaunching(MOCK_PLUGIN_MANIFEST.name)).toBeTruthy()
+        })
+      )
+    })
+  })
+})
\ No newline at end of file
diff --git a/src/atlasViewer/atlasViewer.pluginService.service.ts b/src/atlasViewer/atlasViewer.pluginService.service.ts
index 46da82439242c06a5a491c78b1a8b639fe28de3c..84f5e30d687124fd8ebc5b457c70929385de1a62 100644
--- a/src/atlasViewer/atlasViewer.pluginService.service.ts
+++ b/src/atlasViewer/atlasViewer.pluginService.service.ts
@@ -1,13 +1,14 @@
 import { Injectable, ViewContainerRef, ComponentFactoryResolver, ComponentFactory } from "@angular/core";
 import { PluginInitManifestInterface, PLUGIN_STATE_ACTION_TYPES } from "src/services/state/pluginState.store";
+import { HttpClient } from '@angular/common/http'
 import { isDefined } from 'src/services/stateStore.service'
 import { AtlasViewerAPIServices } from "./atlasViewer.apiService.service";
 import { PluginUnit } from "./pluginUnit/pluginUnit.component";
 import { WidgetServices } from "./widgetUnit/widgetService.service";
 
 import '../res/css/plugin_styles.css'
-import { interval } from "rxjs";
-import { take, takeUntil } from "rxjs/operators";
+import { BehaviorSubject, Observable, merge, of } from "rxjs";
+import { map, shareReplay } from "rxjs/operators";
 import { Store } from "@ngrx/store";
 import { WidgetUnit } from "./widgetUnit/widgetUnit.component";
 import { AtlasViewerConstantsServices } from "./atlasViewer.constantService.service";
@@ -23,13 +24,20 @@ export class PluginServices{
   public appendSrc : (script:HTMLElement)=>void
   public removeSrc: (script:HTMLElement) => void
   private pluginUnitFactory : ComponentFactory<PluginUnit>
+  public minimisedPlugins$ : Observable<Set<string>>
+
+  /**
+   * TODO remove polyfil and convert all calls to this.fetch to http client
+   */
+  public fetch: (url:string, httpOption?: any) => Promise<any> = (url, httpOption = {}) => this.http.get(url, httpOption).toPromise()
 
   constructor(
     private apiService : AtlasViewerAPIServices,
     private constantService : AtlasViewerConstantsServices,
     private widgetService : WidgetServices,
     private cfr : ComponentFactoryResolver,
-    private store : Store<PluginInitManifestInterface>
+    private store : Store<PluginInitManifestInterface>,
+    private http: HttpClient
   ){
 
     this.pluginUnitFactory = this.cfr.resolveComponentFactory( PluginUnit )
@@ -44,30 +52,30 @@ export class PluginServices{
          * PLUGINDEV should return an array of 
          */
         PLUGINDEV
-          ? fetch(PLUGINDEV).then(res => res.json())
+          ? this.fetch(PLUGINDEV).then(res => res.json())
           : Promise.resolve([]),
         new Promise(resolve => {
-          fetch(`${this.constantService.backendUrl}plugins`)
-            .then(res => res.json())
+          this.fetch(`${this.constantService.backendUrl}plugins`)
             .then(arr => Promise.all(
               arr.map(url => new Promise(rs => 
                 /**
                  * instead of failing all promises when fetching manifests, only fail those that fails to fetch
                  */
-                fetch(url).then(res => res.json()).then(rs).catch(e => (console.log('fetching manifest error', e), rs(null))))
+                this.fetch(url).then(rs).catch(e => (this.constantService.catchError(`fetching manifest error: ${e.toString()}`), rs(null))))
               )
             ))
             .then(manifests => resolve(
               manifests.filter(m => !!m)
             ))
             .catch(e => {
+              this.constantService.catchError(e)
               resolve([])
             })
         }),
         Promise.all(
           BUNDLEDPLUGINS
             .filter(v => typeof v === 'string')
-            .map(v => fetch(`res/plugin_examples/${v}/manifest.json`).then(res => res.json()))
+            .map(v => this.fetch(`res/plugin_examples/${v}/manifest.json`).then(res => res.json()))
         )
           .then(arr => arr.reduce((acc,curr) => acc.concat(curr) ,[]))
       ])
@@ -79,6 +87,24 @@ export class PluginServices{
       .then(arr=>
         this.fetchedPluginManifests = arr)
       .catch(console.error)
+
+    this.minimisedPlugins$ = merge(
+      of(new Set()),
+      this.widgetService.minimisedWindow$
+    ).pipe(
+      map(set => {
+        const returnSet = new Set<string>()
+        for (let [pluginName, wu] of this.mapPluginNameToWidgetUnit) {
+          if (set.has(wu)) {
+            returnSet.add(pluginName)
+          }
+        }
+        return returnSet
+      }),
+      shareReplay(1)
+    )
+
+    this.launchedPlugins$ = new BehaviorSubject(new Set())
   }
 
   launchNewWidget = (manifest) => this.launchPlugin(manifest)
@@ -94,37 +120,74 @@ export class PluginServices{
         isDefined(plugin.template) ?
           Promise.resolve('template already provided') :
           isDefined(plugin.templateURL) ?
-            fetch(plugin.templateURL)
-              .then(res=>res.text())
+            this.fetch(plugin.templateURL, {responseType: 'text'})
               .then(template=>plugin.template = template) :
             Promise.reject('both template and templateURL are not defined') ,
         isDefined(plugin.script) ?
           Promise.resolve('script already provided') :
           isDefined(plugin.scriptURL) ?
-            fetch(plugin.scriptURL)
-              .then(res=>res.text())
+            this.fetch(plugin.scriptURL, {responseType: 'text'})
               .then(script=>plugin.script = script) :
             Promise.reject('both script and scriptURL are not defined') 
       ])
   }
 
-  public launchedPlugins: Set<string> = new Set()
+  private launchedPlugins: Set<string> = new Set()
+  public launchedPlugins$: BehaviorSubject<Set<string>>
+  pluginHasLaunched(pluginName:string) {
+    return this.launchedPlugins.has(pluginName)
+  }
+  addPluginToLaunchedSet(pluginName:string){
+    this.launchedPlugins.add(pluginName)
+    this.launchedPlugins$.next(this.launchedPlugins)
+  }
+  removePluginFromLaunchedSet(pluginName:string){
+    this.launchedPlugins.delete(pluginName)
+    this.launchedPlugins$.next(this.launchedPlugins)
+  }
+
+  
+  pluginIsLaunching(pluginName:string){
+    return this.launchingPlugins.has(pluginName)
+  }
+  addPluginToIsLaunchingSet(pluginName:string) {
+    this.launchingPlugins.add(pluginName)
+  }
+  removePluginFromIsLaunchingSet(pluginName:string){
+    this.launchedPlugins.delete(pluginName)
+  }
+
   private mapPluginNameToWidgetUnit: Map<string, WidgetUnit> = new Map()
 
-  pluginMinimised(pluginManifest:PluginManifest){
-    return this.widgetService.minimisedWindow.has( this.mapPluginNameToWidgetUnit.get(pluginManifest.name) )
+  pluginIsMinimised(pluginName:string) {
+    return this.widgetService.isMinimised( this.mapPluginNameToWidgetUnit.get(pluginName) )
   }
 
+  private launchingPlugins: Set<string> = new Set()
   public orphanPlugins: Set<PluginManifest> = new Set()
   launchPlugin(plugin:PluginManifest){
-    if(this.apiService.interactiveViewer.pluginControl[plugin.name])
-    {
-      console.warn('plugin already launched. blinking for 10s.')
-      this.apiService.interactiveViewer.pluginControl[plugin.name].blink(10)
+    if (this.pluginIsLaunching(plugin.name)) {
+      // plugin launching please be patient
+      // TODO add visual feedback
+      return
+    }
+    if ( this.pluginHasLaunched(plugin.name)) {
+      // plugin launched
+      // TODO add visual feedback
+
+      // if widget window is minimized, maximize it
+      
       const wu = this.mapPluginNameToWidgetUnit.get(plugin.name)
-      this.widgetService.minimisedWindow.delete(wu)
-      return Promise.reject('plugin already launched')
+      if (this.widgetService.isMinimised(wu)) {
+        this.widgetService.unminimise(wu)
+      } else {
+        this.widgetService.minimise(wu)
+      }
+      return
     }
+
+    this.addPluginToIsLaunchingSet(plugin.name)
+    
     return this.readyPlugin(plugin)
       .then(()=>{
         const pluginUnit = this.pluginViewContainerRef.createComponent( this.pluginUnitFactory )
@@ -163,7 +226,7 @@ export class PluginServices{
 
         const shutdownCB = [
           () => {
-            this.launchedPlugins.delete(plugin.name)
+            this.removePluginFromLaunchedSet(plugin.name)
           }
         ]
 
@@ -191,28 +254,18 @@ export class PluginServices{
           title : plugin.displayName || plugin.name
         })
 
-        this.launchedPlugins.add(plugin.name)
+        this.addPluginToLaunchedSet(plugin.name)
+        this.removePluginFromIsLaunchingSet(plugin.name)
+
         this.mapPluginNameToWidgetUnit.set(plugin.name, widgetCompRef.instance)
 
         const unsubscribeOnPluginDestroy = []
 
         handler.blink = (sec?:number)=>{
-          if(typeof sec !== 'number')
-            console.warn(`sec is not a number, default blink interval used`)
-          widgetCompRef.instance.containerClass = ''
-          interval(typeof sec === 'number' ? sec * 1000 : 500).pipe(
-            take(11),
-            takeUntil(widgetCompRef.instance.clickedEmitter)
-          ).subscribe(()=>
-            widgetCompRef.instance.containerClass = widgetCompRef.instance.containerClass === 'panel-success' ? 
-              '' : 
-              'panel-success')
+          widgetCompRef.instance.blinkOn = true
         }
 
-        unsubscribeOnPluginDestroy.push(
-          widgetCompRef.instance.clickedEmitter.subscribe(()=>
-            widgetCompRef.instance.containerClass = '')
-          )
+        handler.setProgressIndicator = (val) => widgetCompRef.instance.progressIndicator = val
 
         handler.shutdown = ()=>{
           widgetCompRef.instance.exit()
@@ -244,6 +297,8 @@ export class PluginHandler{
   initStateUrl? : string
 
   setInitManifestUrl : (url:string|null)=>void
+
+  setProgressIndicator: (progress:number) => void
 }
 
 export interface PluginManifest{
diff --git a/src/atlasViewer/atlasViewer.urlService.service.ts b/src/atlasViewer/atlasViewer.urlService.service.ts
index 8506cf25f696ab09fbe69dd4647c8d650b5dc6de..1008c7a50e424d52c4c1bc32fc072163d8bd852e 100644
--- a/src/atlasViewer/atlasViewer.urlService.service.ts
+++ b/src/atlasViewer/atlasViewer.urlService.service.ts
@@ -57,8 +57,12 @@ export class AtlasViewerURLService{
      */
     this.additionalNgLayers$ = combineLatest(
       this.changeQueryObservable$.pipe(
-        map(state => state.templateSelected)
+        select('templateSelected'),
+        filter(v => !!v)
       ),
+      /**
+       * TODO duplicated with viewerState.loadedNgLayers ?
+       */
       this.store.pipe(
         select('ngViewerState'),
         select('layers')
@@ -170,7 +174,16 @@ export class AtlasViewerURLService{
   
             for (let ngId in json) {
               const val = json[ngId]
-              const labelIndicies = val.split(separator).map(n =>decodeToNumber(n))
+              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 (let labelIndex of labelIndicies) {
                 selectRegionIds.push(`${ngId}#${labelIndex}`)
               }
@@ -208,22 +221,29 @@ export class AtlasViewerURLService{
 
       const cViewerState = searchparams.get('cNavigation')
       if (cViewerState) {
-        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)
-        this.store.dispatch({
-          type : CHANGE_NAVIGATION,
-          navigation : {
-            orientation: o,
-            perspectiveOrientation: po,
-            perspectiveZoom: pz,
-            position: p,
-            zoom: z
-          }
-        })
+        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)
+          this.store.dispatch({
+            type : CHANGE_NAVIGATION,
+            navigation : {
+              orientation: o,
+              perspectiveOrientation: po,
+              perspectiveZoom: pz,
+              position: p,
+              zoom: z
+            }
+          })
+        } catch (e) {
+          /**
+           * TODO Poisoned encoded char
+           * send error message
+           */
+        }
       }
 
       const niftiLayers = searchparams.get('niftiLayers')
diff --git a/src/atlasViewer/widgetUnit/widgetService.service.ts b/src/atlasViewer/widgetUnit/widgetService.service.ts
index 521d69ac8356087702807ec34ebeec44204f7888..7f1cc48355967bb9bfec97c60f3ee3713cb62ee0 100644
--- a/src/atlasViewer/widgetUnit/widgetService.service.ts
+++ b/src/atlasViewer/widgetUnit/widgetService.service.ts
@@ -1,9 +1,7 @@
 import { ComponentRef, ComponentFactory, Injectable, ViewContainerRef, ComponentFactoryResolver, Injector } from "@angular/core";
-
 import { WidgetUnit } from "./widgetUnit.component";
 import { AtlasViewerConstantsServices } from "../atlasViewer.constantService.service";
-import { Subscription } from "rxjs";
-
+import { Subscription, BehaviorSubject } from "rxjs";
 
 @Injectable({
   providedIn : 'root'
@@ -20,7 +18,8 @@ export class WidgetServices{
 
   private clickedListener : Subscription[] = []
 
-  public minimisedWindow: Set<WidgetUnit> = new Set()
+  public minimisedWindow$: BehaviorSubject<Set<WidgetUnit>>
+  private minimisedWindow: Set<WidgetUnit> = new Set() 
 
   constructor(
     private cfr:ComponentFactoryResolver,
@@ -28,6 +27,7 @@ export class WidgetServices{
     private injector : Injector
     ){
     this.widgetUnitFactory = this.cfr.resolveComponentFactory(WidgetUnit)
+    this.minimisedWindow$ = new BehaviorSubject(this.minimisedWindow)
   }
 
   clearAllWidgets(){
@@ -38,8 +38,26 @@ export class WidgetServices{
     this.clickedListener.forEach(s=>s.unsubscribe())
   }
 
+  rename(wu:WidgetUnit, {title, titleHTML}: {title: string, titleHTML: string}){
+    /**
+     * WARNING: always sanitize before pass to rename fn!
+     */
+    wu.title = title
+    wu.titleHTML = titleHTML
+  }
+
   minimise(wu:WidgetUnit){
     this.minimisedWindow.add(wu)
+    this.minimisedWindow$.next(new Set(this.minimisedWindow))
+  }
+  
+  isMinimised(wu:WidgetUnit){
+    return this.minimisedWindow.has(wu)
+  }
+
+  unminimise(wu:WidgetUnit){
+    this.minimisedWindow.delete(wu)
+    this.minimisedWindow$.next(new Set(this.minimisedWindow))
   }
 
   addNewWidget(guestComponentRef:ComponentRef<any>,options?:Partial<WidgetOptionsInterface>):ComponentRef<WidgetUnit>{
@@ -93,9 +111,12 @@ export class WidgetServices{
 
       this.clickedListener.push(
         _component.instance.clickedEmitter.subscribe((widgetUnit:WidgetUnit)=>{
+          /**
+           * TODO this operation 
+           */
           if(widgetUnit.state !== 'floating')
             return
-          const widget = [...this.widgetComponentRefs].find(widget=>widget.instance===widgetUnit)
+          const widget = [...this.widgetComponentRefs].find(widget=>widget.instance === widgetUnit)
           if(!widget)
             return
           const idx = this.floatingContainer.indexOf(widget.hostView)
@@ -103,7 +124,6 @@ export class WidgetServices{
             return
           this.floatingContainer.detach(idx)
           this.floatingContainer.insert(widget.hostView)
-          
         })
       )
 
diff --git a/src/atlasViewer/widgetUnit/widgetUnit.component.ts b/src/atlasViewer/widgetUnit/widgetUnit.component.ts
index fc32210cd0774a8b1b75126c25f9e10b2de9195a..35463f92e9d75f4804964dae65439e52c9984a35 100644
--- a/src/atlasViewer/widgetUnit/widgetUnit.component.ts
+++ b/src/atlasViewer/widgetUnit/widgetUnit.component.ts
@@ -1,6 +1,9 @@
-import { Component, ViewChild, ViewContainerRef,ComponentRef, HostBinding, HostListener, Output, EventEmitter, Input, ElementRef, OnInit } from "@angular/core";
+import { Component, ViewChild, ViewContainerRef,ComponentRef, HostBinding, HostListener, Output, EventEmitter, Input, ElementRef, OnInit, OnDestroy } from "@angular/core";
+
 import { WidgetServices } from "./widgetService.service";
 import { AtlasViewerConstantsServices } from "../atlasViewer.constantService.service";
+import { Subscription, Observable } from "rxjs";
+import { map } from "rxjs/operators";
 
 
 @Component({
@@ -10,9 +13,8 @@ import { AtlasViewerConstantsServices } from "../atlasViewer.constantService.ser
   ]
 })
 
-export class WidgetUnit implements OnInit{
+export class WidgetUnit implements OnInit, OnDestroy{
   @ViewChild('container',{read:ViewContainerRef}) container : ViewContainerRef
-  @ViewChild('emptyspan',{read:ElementRef}) emtpy : ElementRef
 
   @HostBinding('attr.state')
   public state : 'docked' | 'floating' = 'docked'
@@ -24,29 +26,61 @@ export class WidgetUnit implements OnInit{
   height : string = this.state === 'docked' ? null : '0px'
 
   @HostBinding('style.display')
-  get isMinimised(){
-    return this.widgetServices.minimisedWindow.has(this) ? 'none' : null
+  isMinimised: string
+
+  isMinimised$: Observable<boolean>
+
+  /**
+   * Timed alternates of blinkOn property should result in attention grabbing blink behaviour
+   */
+  private _blinkOn: boolean = false
+  get blinkOn(){
+    return this._blinkOn
   }
+
+  set blinkOn(val: boolean) {
+    this._blinkOn = !!val
+  }
+
+  get showProgress(){
+    return this.progressIndicator !== null
+  }
+
   /**
-   * TODO
-   * upgrade to angular>=7, and use cdk to handle draggable components
+   * Some plugins may like to show progress indicator for long running processes
+   * If null, no progress is running
+   * This value should be between 0 and 1
    */
-  get transform(){
-    return this.state === 'floating' ?
-      `translate(${this.position[0]}px, ${this.position[1]}px)` :
-      `translate(0 , 0)`
+  private _progressIndicator: number = null
+  get progressIndicator(){
+    return this._progressIndicator
+  }
+
+  set progressIndicator(val:number) {
+    if (isNaN(val)) {
+      this._progressIndicator = null
+      return
+    }
+    if (val < 0) {
+      this._progressIndicator = 0
+      return
+    }
+    if (val > 1) {
+      this._progressIndicator = 1
+      return
+    }
+    this._progressIndicator = val
   }
 
   public canBeDocked: boolean = false
   @HostListener('mousedown')
   clicked(){
     this.clickedEmitter.emit(this)
+    this.blinkOn = false
   }
 
   @Input() title : string = 'Untitled'
 
-  @Input() containerClass : string = ''
-
   @Output()
   clickedEmitter : EventEmitter<WidgetUnit> = new EventEmitter()
 
@@ -59,16 +93,30 @@ export class WidgetUnit implements OnInit{
   public guestComponentRef : ComponentRef<any>
   public widgetServices:WidgetServices
   public cf : ComponentRef<WidgetUnit>
+  private subscriptions: Subscription[] = []
 
   public id: string 
   constructor(
     private constantsService: AtlasViewerConstantsServices
-    ){
+  ){
     this.id = Date.now().toString()
   }
 
   ngOnInit(){
     this.canBeDocked = typeof this.widgetServices.dockedContainer !== 'undefined'
+
+    this.isMinimised$ = this.widgetServices.minimisedWindow$.pipe(
+      map(set => set.has(this))
+    )
+    this.subscriptions.push(
+      this.isMinimised$.subscribe(flag => this.isMinimised = flag ? 'none' : null)
+    )
+  }
+
+  ngOnDestroy(){
+    while(this.subscriptions.length > 0){
+      this.subscriptions.pop().unsubscribe()
+    }
   }
 
   /**
diff --git a/src/atlasViewer/widgetUnit/widgetUnit.style.css b/src/atlasViewer/widgetUnit/widgetUnit.style.css
index 10289f924763603c034766cd15e23a819cb0e857..9ed85f20e0fc549bc2c9ab0ac31f8ae156dc48e8 100644
--- a/src/atlasViewer/widgetUnit/widgetUnit.style.css
+++ b/src/atlasViewer/widgetUnit/widgetUnit.style.css
@@ -41,7 +41,62 @@ panel-component[widgetUnitPanel]
   cursor : move;
 }
 
-[emptyspan]
+:host > panel-component
 {
-  opacity:0.01;
+  max-width: 100%;
+  width: 300px;
+  border-width: 1px !important;
+  border: solid;
+  border-color: rgba(0, 0, 0, 0);
+  box-sizing: border-box;
 }
+
+@keyframes blinkDark
+{
+  0% {
+    border-color: rgba(128, 128, 200, 0.0);
+  }
+
+  100% {
+    border-color: rgba(128, 128, 200, 1.0);
+  }
+}
+
+@keyframes blink
+{
+  0% {
+    border-color: rgba(128, 128, 255, 0.0);
+  }
+
+  100% {
+    border-color: rgba(128, 128, 255, 1.0);
+  }
+}
+
+:host-context([darktheme="true"]) .blinkOn
+{
+  animation: 0.5s blinkDark ease-in-out 9 alternate; 
+  border: 1px solid rgba(128, 128, 200, 1.0) !important;
+}
+
+:host-context([darktheme="false"]) .blinkOn
+{
+  animation: 0.5s blink ease-in-out 9 alternate; 
+  border: 1px solid rgba(128, 128, 255, 1.0) !important;
+}
+
+[heading]
+{
+  position:relative;
+}
+
+[heading] > [progressBar]
+{
+  position: absolute;
+  width: 100%;
+  height: 100%;
+  left: 0;
+  top: 0;
+  opacity: 0.4;
+  pointer-events: none;
+}
\ No newline at end of file
diff --git a/src/atlasViewer/widgetUnit/widgetUnit.template.html b/src/atlasViewer/widgetUnit/widgetUnit.template.html
index 62600cf8ba9b22a3901d84fdb50517fe9ab7badf..84a9b851949d66beed27043783c46130c0832157 100644
--- a/src/atlasViewer/widgetUnit/widgetUnit.template.html
+++ b/src/atlasViewer/widgetUnit/widgetUnit.template.html
@@ -1,17 +1,13 @@
 <panel-component
-  [style.transform] = "transform"
-  [containerClass] = "containerClass"
   widgetUnitPanel
+  [ngClass]="{'blinkOn': blinkOn}"
   [bodyCollapsable] = "state === 'docked'"
   [cdkDragDisabled]="state === 'docked'"
-  cdkDrag
-  [ngStyle]="{'max-width': isMobile? '100%' : '300px',
-              'margin-bottom': isMobile? '5px': '0'}">
+  cdkDrag>
   <div 
     widgetUnitHeading
     heading
     cdkDragHandle>
-    <div #emptyspan emptyspan>.</div>
     <div title>
       <div *ngIf="!titleHTML">
         {{ title }}
@@ -41,6 +37,9 @@
         class = "fas fa-times" 
         [hoverable] ="{translateY: -1}"></i>
     </div>
+    <progress-bar [progress]="progressIndicator" *ngIf="showProgress" progressBar>
+
+    </progress-bar>
   </div>
   <div widgetUnitBody body>
     <ng-template #container>
diff --git a/src/components/components.module.ts b/src/components/components.module.ts
index e435687fd0541445a7989f8f87f1bb3eb392bae9..8ddb291d165ab8771f9e6b1f6e36ea8ea465f2d4 100644
--- a/src/components/components.module.ts
+++ b/src/components/components.module.ts
@@ -29,6 +29,10 @@ import { CommonModule } from '@angular/common';
 import { RadioList } from './radiolist/radiolist.component';
 import { AngularMaterialModule } from 'src/ui/sharedModules/angularMaterial.module';
 import { FilterCollapsePipe } from './flatTree/filterCollapse.pipe';
+import { ProgressBar } from './progress/progress.component';
+import { SleightOfHand } from './sleightOfHand/soh.component';
+import { DialogComponent } from './dialog/dialog.component';
+import { ConfirmDialogComponent } from './confirmDialog/confirmDialog.component';
 
 
 @NgModule({
@@ -52,6 +56,10 @@ import { FilterCollapsePipe } from './flatTree/filterCollapse.pipe';
     TimerComponent,
     PillComponent,
     RadioList,
+    ProgressBar,
+    SleightOfHand,
+    DialogComponent,
+    ConfirmDialogComponent,
 
     /* directive */
     HoverableBlockDirective,
@@ -83,6 +91,10 @@ import { FilterCollapsePipe } from './flatTree/filterCollapse.pipe';
     TimerComponent,
     PillComponent,
     RadioList,
+    ProgressBar,
+    SleightOfHand,
+    DialogComponent,
+    ConfirmDialogComponent,
 
     SearchResultPaginationPipe,
     TreeSearchPipe,
diff --git a/src/components/confirmDialog/confirmDialog.component.ts b/src/components/confirmDialog/confirmDialog.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6c16434ac9910839c345d0a5fe9a132411627ba4
--- /dev/null
+++ b/src/components/confirmDialog/confirmDialog.component.ts
@@ -0,0 +1,24 @@
+import { Component, Inject, Input } from "@angular/core";
+import { MAT_DIALOG_DATA, MatDialogRef } from "@angular/material";
+
+@Component({
+  selector: 'confirm-dialog-component',
+  templateUrl: './confirmDialog.template.html',
+  styleUrls: [
+    './confirmDialog.style.css'
+  ]
+})
+export class ConfirmDialogComponent{
+
+  @Input()
+  public title: string = 'Confirm'
+
+  @Input()
+  public message: string = 'Would you like to proceed?'
+
+  constructor(@Inject(MAT_DIALOG_DATA) data: any){
+    const { title = null, message  = null} = data || {}
+    if (title) this.title = title
+    if (message) this.message = message
+  }
+}
\ No newline at end of file
diff --git a/src/components/confirmDialog/confirmDialog.style.css b/src/components/confirmDialog/confirmDialog.style.css
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/components/confirmDialog/confirmDialog.template.html b/src/components/confirmDialog/confirmDialog.template.html
new file mode 100644
index 0000000000000000000000000000000000000000..eb0c76fffdca11eda3487eb259b2be3cbbaa032e
--- /dev/null
+++ b/src/components/confirmDialog/confirmDialog.template.html
@@ -0,0 +1,16 @@
+<h1 mat-dialog-title>
+  {{ title }}
+</h1>
+
+<mat-dialog-content>
+  <p>
+    {{ message }}
+  </p>
+</mat-dialog-content>
+
+<mat-divider></mat-divider>
+
+<mat-dialog-actions class="justify-content-start flex-row-reverse">
+    <button [mat-dialog-close]="true" mat-raised-button color="primary">OK</button>
+  <button [mat-dialog-close]="false" mat-button>Cancel</button>
+</mat-dialog-actions>
\ No newline at end of file
diff --git a/src/components/dialog/dialog.component.ts b/src/components/dialog/dialog.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7001bbf092dd04bf1b36cdf3ffd31d5304d146bb
--- /dev/null
+++ b/src/components/dialog/dialog.component.ts
@@ -0,0 +1,70 @@
+import { Component, Input, ChangeDetectionStrategy, ViewChild, ElementRef, OnInit, OnDestroy, Inject } from "@angular/core";
+import { MatDialogRef, MAT_DIALOG_DATA } from "@angular/material";
+import { Subscription, Observable, fromEvent } from "rxjs";
+import { filter, share } from "rxjs/operators";
+
+@Component({
+  selector: 'dialog-component',
+  templateUrl: './dialog.template.html',
+  styleUrls: [
+    './dialog.style.css'
+  ],
+  changeDetection: ChangeDetectionStrategy.OnPush
+})
+
+export class DialogComponent implements OnInit, OnDestroy {
+
+  private subscrptions: Subscription[] = []
+  
+  @Input() title: string = 'Message'
+  @Input() placeholder: string = "Type your response here"
+  @Input() defaultValue: string = ''
+  @Input() message: string = ''
+  @ViewChild('inputField', {read: ElementRef}) private inputField: ElementRef
+
+  public value: string = ''
+  private keyListener$: Observable<any>
+
+  constructor(
+    @Inject(MAT_DIALOG_DATA) public data:any,
+    private dialogRef: MatDialogRef<DialogComponent>
+  ){
+    const { title, placeholder, defaultValue, message } = this.data
+    if (title) this.title = title
+    if (placeholder) this.placeholder = placeholder
+    if (defaultValue) this.value = defaultValue
+    if (message) this.message = message
+  }
+
+  ngOnInit(){
+
+    this.keyListener$ = fromEvent(this.inputField.nativeElement, 'keyup').pipe(
+      filter((ev: KeyboardEvent) => ev.key === 'Enter' || ev.key === 'Esc' || ev.key === 'Escape'),
+      share()
+    )
+    this.subscrptions.push(
+      this.keyListener$.subscribe(ev => {
+        if (ev.key === 'Enter') {
+          this.dialogRef.close(this.value)
+        }
+        if (ev.key === 'Esc' || ev.key === 'Escape') {
+          this.dialogRef.close(null)
+        }
+      })
+    )
+  }
+
+  confirm(){
+    this.dialogRef.close(this.value)
+  }
+
+  cancel(){
+    this.dialogRef.close(null)
+  }
+
+  ngOnDestroy(){
+    while(this.subscrptions.length > 0) {
+      this.subscrptions.pop().unsubscribe()
+    }
+  }
+}
\ No newline at end of file
diff --git a/src/components/dialog/dialog.style.css b/src/components/dialog/dialog.style.css
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/components/dialog/dialog.template.html b/src/components/dialog/dialog.template.html
new file mode 100644
index 0000000000000000000000000000000000000000..311b6f30f1582e1cb04b789782a27c87519c0e78
--- /dev/null
+++ b/src/components/dialog/dialog.template.html
@@ -0,0 +1,38 @@
+<h1 mat-dialog-title>
+  {{ title }}
+</h1>
+
+<div>
+  {{ message }}
+</div>
+
+<div mat-dialog-content>
+  <mat-form-field>
+    <input
+      tabindex="0"
+      [(ngModel)]="value"
+      matInput
+      [placeholder]="placeholder"
+      #inputField>
+  </mat-form-field>
+</div>
+
+<mat-divider></mat-divider>
+
+<div class="mt-2 d-flex flex-row justify-content-end">
+  <button 
+    (click)="cancel()"
+    color="primary"
+    mat-button>
+    Cancel
+  </button>
+  
+  <button
+    (click)="confirm()"
+    class="ml-1"
+    mat-raised-button
+    color="primary">
+    <i class="fas fa-save mr-1"></i>
+    Confirm
+  </button>
+</div>
\ No newline at end of file
diff --git a/src/components/flatTree/flatTree.component.ts b/src/components/flatTree/flatTree.component.ts
index ba6c13546925efa8aab616454280e3402c488230..60e0ba44fd00d214d3fb0e9a5997b78d813ffa69 100644
--- a/src/components/flatTree/flatTree.component.ts
+++ b/src/components/flatTree/flatTree.component.ts
@@ -98,4 +98,10 @@ export class FlatTreeComponent implements AfterViewChecked {
       .some(id => this.isCollapsedById(id))
   }
 
+  handleTreeNodeClick(event:MouseEvent, inputItem: any){
+    this.treeNodeClick.emit({
+      event,
+      inputItem
+    })
+  }
 }
\ No newline at end of file
diff --git a/src/components/flatTree/flatTree.template.html b/src/components/flatTree/flatTree.template.html
index 48ff87a252308c179c0c207b244a9cbb9009a58c..893c539a9ff029933606880e3e37776e3d7fa0c1 100644
--- a/src/components/flatTree/flatTree.template.html
+++ b/src/components/flatTree/flatTree.template.html
@@ -27,7 +27,7 @@
       <i [ngClass]="isCollapsed(flattenedItem) ? 'r-270' : ''" class="fas fa-chevron-down"></i>
     </span>
     <span
-      (click)="treeNodeClick.emit({event:$event,inputItem:flattenedItem})"
+      (click)="handleTreeNodeClick($event, flattenedItem)"
       class="render-node-text"
       [innerHtml]="flattenedItem | renderPipe : renderNode ">
     </span>
@@ -63,7 +63,7 @@
         <i [ngClass]="isCollapsed(flattenedItem) ? 'r-270' : ''" class="fas fa-chevron-down"></i>
       </span>
       <span
-        (click)="treeNodeClick.emit({event:$event,inputItem:flattenedItem})"
+        (click)="handleTreeNodeClick($event, flattenedItem)"
         class="render-node-text"
         [innerHtml]="flattenedItem | renderPipe : renderNode ">
       </span>
diff --git a/src/components/panel/panel.component.ts b/src/components/panel/panel.component.ts
index 6b2c5095c1f04c39d1a85123e0f793163290d845..ccfc382fb8297e7b2671bec08bfffb7b694d1a11 100644
--- a/src/components/panel/panel.component.ts
+++ b/src/components/panel/panel.component.ts
@@ -1,5 +1,4 @@
-import { Component, Input, ViewChild, ElementRef, AfterContentChecked, ChangeDetectionStrategy, ChangeDetectorRef, OnChanges, SimpleChanges, HostBinding, ApplicationRef } from "@angular/core";
-import { panelAnimations } from "./panel.animation";
+import { Component, Input, ViewChild, ElementRef, ChangeDetectionStrategy } from "@angular/core";
 import { ParseAttributeDirective } from "../parseAttribute.directive";
 
 @Component({
@@ -8,9 +7,6 @@ import { ParseAttributeDirective } from "../parseAttribute.directive";
   styleUrls : [
     `./panel.style.css`
   ],
-  host: {
-    '[class]': 'getClassNames'
-  },
   changeDetection:ChangeDetectionStrategy.OnPush
 })
 
@@ -23,8 +19,6 @@ export class PanelComponent extends ParseAttributeDirective {
   @Input() collapseBody : boolean = false
   @Input() bodyCollapsable : boolean = false
 
-  @Input() containerClass : string = ''
-
   @ViewChild('panelBody',{ read : ElementRef }) efPanelBody : ElementRef
   @ViewChild('panelFooter',{ read : ElementRef }) efPanelFooter : ElementRef
 
@@ -32,10 +26,6 @@ export class PanelComponent extends ParseAttributeDirective {
     super()
   }
 
-  get getClassNames(){
-    return `panel ${this.containerClass === '' ? 'panel-default' : this.containerClass}`
-  }
-
   toggleCollapseBody(_event:Event){
     if(this.bodyCollapsable){
       this.collapseBody = !this.collapseBody
diff --git a/src/components/panel/panel.template.html b/src/components/panel/panel.template.html
index d2c55e22b6daed1c0b223e86492e6429e34a143d..84ca6a9f7a786cc51b3ba4a32e9ff8e1b3ff416d 100644
--- a/src/components/panel/panel.template.html
+++ b/src/components/panel/panel.template.html
@@ -1,10 +1,3 @@
-<div
-  *ngIf = "showHeading"
-  class = "panel-heading"
-  hoverable>
-
-</div>
-
 <div class="l-card">
   <div class="l-card-body">
     <div
diff --git a/src/components/progress/progress.component.ts b/src/components/progress/progress.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..03e34f9f304f5c6a0b86016c5a73760392434053
--- /dev/null
+++ b/src/components/progress/progress.component.ts
@@ -0,0 +1,44 @@
+import { Component, Input, ChangeDetectionStrategy } from "@angular/core";
+
+
+@Component({
+  selector: 'progress-bar',
+  templateUrl: './progress.template.html',
+  styleUrls: [
+    './progress.style.css'
+  ],
+  changeDetection: ChangeDetectionStrategy.OnPush
+})
+
+export class ProgressBar{
+  @Input() progressStyle: any
+
+  private _progress: number = 0
+  /**
+   * between 0 and 1
+   */
+  @Input() 
+  set progress(val: number) {
+    if (isNaN(val)) {
+      this._progress = 0
+      return
+    }
+    if (val < 0 || val === null) {
+      this._progress = 0
+      return
+    }
+    if (val > 1) {
+      this._progress = 1
+      return
+    }
+    this._progress = val
+  }
+
+  get progress(){
+    return this._progress
+  }
+
+  get progressPercent(){
+    return `${this.progress * 100}%`
+  }
+}
\ No newline at end of file
diff --git a/src/components/progress/progress.style.css b/src/components/progress/progress.style.css
new file mode 100644
index 0000000000000000000000000000000000000000..36858a282cdd6c793041f515aa8c4cd7e0cab97a
--- /dev/null
+++ b/src/components/progress/progress.style.css
@@ -0,0 +1,41 @@
+.progress
+{
+  height: 100%;
+  width: 100%;
+  position:relative;
+  overflow:hidden;
+  background-color:rgba(255,255,255,0.5);
+}
+
+:host-context([darktheme="true"]) .progress
+{
+  background-color:rgba(0,0,0,0.5);
+}
+
+@keyframes moveRight
+{
+  from {
+    transform: translateX(-105%);
+  }
+  to {
+    transform: translateX(205%);
+  }
+}
+
+.progress::after
+{
+  content: '';
+  width: 100%;
+  height: 100%;
+  position:absolute;
+  border-left-width: 10em;
+  border-right-width:0;
+  border-style: solid;
+  border-image: linear-gradient(
+    to right,
+    rgba(128, 200, 128, 0.0),
+    rgba(128, 200, 128, 0.5),
+    rgba(128, 200, 128, 0.0)
+  ) 0 100%;
+  animation: moveRight 2000ms linear infinite;
+}
\ No newline at end of file
diff --git a/src/components/progress/progress.template.html b/src/components/progress/progress.template.html
new file mode 100644
index 0000000000000000000000000000000000000000..7df81f0819667454c5aa5127a780a94a557d1ae6
--- /dev/null
+++ b/src/components/progress/progress.template.html
@@ -0,0 +1,3 @@
+<div class="progress rounded-0">
+  <div [style.width]="progressPercent" class="progress-bar bg-success rounded-0" role="progressbar"></div>
+</div>
\ No newline at end of file
diff --git a/src/components/sleightOfHand/soh.component.ts b/src/components/sleightOfHand/soh.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4b6fbe5fdb989088e847e10716703dfb4c044c5a
--- /dev/null
+++ b/src/components/sleightOfHand/soh.component.ts
@@ -0,0 +1,33 @@
+import { Component, Input, HostBinding, ChangeDetectionStrategy, HostListener } from "@angular/core";
+
+@Component({
+  selector: 'sleight-of-hand',
+  templateUrl: './soh.template.html',
+  styleUrls: [
+    './soh.style.css'
+  ],
+  changeDetection: ChangeDetectionStrategy.OnPush
+})
+
+export class SleightOfHand{
+
+  @HostBinding('class.do-not-close')
+  get doNotCloseClass(){
+    return this.doNotClose || this.focusInStatus
+  }
+
+  @HostListener('focusin')
+  focusInHandler(){
+    this.focusInStatus = true
+  }
+
+  @HostListener('focusout')
+  focusOutHandler(){
+    this.focusInStatus = false
+  }
+
+  private focusInStatus: boolean = false
+
+  @Input()
+  doNotClose: boolean = false
+}
\ No newline at end of file
diff --git a/src/components/sleightOfHand/soh.style.css b/src/components/sleightOfHand/soh.style.css
new file mode 100644
index 0000000000000000000000000000000000000000..2c4f61aa210ae817635fc0764eb87f5cbe187c50
--- /dev/null
+++ b/src/components/sleightOfHand/soh.style.css
@@ -0,0 +1,28 @@
+:host:not(.do-not-close):not(:hover) > .sleight-of-hand-back,
+:host:not(.do-not-close):hover > .sleight-of-hand-front,
+:host-context(.do-not-close) > .sleight-of-hand-front
+{
+  opacity: 0;
+  pointer-events: none;
+}
+
+:host *
+{
+  transition: opacity 300ms ease-in-out;
+}
+
+:host
+{
+  position: relative;
+}
+
+:host > .sleight-of-hand-front
+{
+  position: relative;
+}
+
+:host > .sleight-of-hand-back
+{
+  position:  absolute;
+  z-index: 1;
+}
\ No newline at end of file
diff --git a/src/components/sleightOfHand/soh.template.html b/src/components/sleightOfHand/soh.template.html
new file mode 100644
index 0000000000000000000000000000000000000000..fca028aa043b8a5c77fdb98261a0e6d5a57dd19c
--- /dev/null
+++ b/src/components/sleightOfHand/soh.template.html
@@ -0,0 +1,9 @@
+<div class="sleight-of-hand-back">
+  <ng-content select="[sleight-of-hand-back]">
+  </ng-content>
+</div>
+
+<div class="sleight-of-hand-front">
+  <ng-content select="[sleight-of-hand-front]">
+  </ng-content>
+</div>
\ No newline at end of file
diff --git a/src/index.html b/src/index.html
index 467e979f0b14b8f6bbaedae248b488a4f72745d6..e8c99ff3b3da1efe140cddeb3466f0de7ba47c1f 100644
--- a/src/index.html
+++ b/src/index.html
@@ -6,9 +6,9 @@
   <meta http-equiv="X-UA-Compatible" content="ie=edge">
   <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
   <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.1/css/all.css" integrity="sha384-50oBUHEmvpQ+1lW4y57PTFmhCaXp0ML5d60M1M7uH2+nqUivzIebhndOJK28anvf" crossorigin="anonymous">
-  <link rel = "stylesheet" href = "extra_styles.css">
-  <link rel = "stylesheet" href = "plugin_styles.css">
-  <link rel = "stylesheet" href = "indigo-pink.css">
+  <link rel="stylesheet" href="extra_styles.css">
+  <link rel="stylesheet" href="plugin_styles.css">
+  <link rel="stylesheet" href="theme.css">
 
   <title>Interactive Atlas Viewer</title>
 </head>
diff --git a/src/main-aot.ts b/src/main-aot.ts
index 171bd6ce7beb8ae5a35e9d43553fdc134235d0e1..7c25c3f1aa849b9ea8265c99ffce474f78142635 100644
--- a/src/main-aot.ts
+++ b/src/main-aot.ts
@@ -4,6 +4,9 @@ import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'
 import { MainModule } from './main.module';
 import { enableProdMode } from '@angular/core';
 
+import './theme.scss'
+import './res/css/extra_styles.css'
+
 const requireAll = (r:any) => {r.keys().forEach(r)}
 requireAll(require.context('./res/ext', false, /\.json$/))
 requireAll(require.context('./res/images', true, /\.jpg|\.png/))
diff --git a/src/main.module.ts b/src/main.module.ts
index d2dc2b916e9ed72f0ea110b6d049b942db581be6..b557d69b45e03f6990b3ea808e6470ba210462e1 100644
--- a/src/main.module.ts
+++ b/src/main.module.ts
@@ -5,7 +5,7 @@ import { UIModule } from "./ui/ui.module";
 import { LayoutModule } from "./layouts/layout.module";
 import { AtlasViewer } from "./atlasViewer/atlasViewer.component";
 import { StoreModule, Store, select } from "@ngrx/store";
-import { viewerState, dataStore,spatialSearchState,uiState, ngViewerState, pluginState, viewerConfigState } from "./services/stateStore.service";
+import { viewerState, dataStore,spatialSearchState,uiState, ngViewerState, pluginState, viewerConfigState, userConfigState, UserConfigStateUseEffect } from "./services/stateStore.service";
 import { GetNamesPipe } from "./util/pipes/getNames.pipe";
 import { CommonModule } from "@angular/common";
 import { GetNamePipe } from "./util/pipes/getName.pipe";
@@ -21,7 +21,6 @@ import { TabsModule } from 'ngx-bootstrap/tabs'
 import { ModalUnit } from "./atlasViewer/modalUnit/modalUnit.component";
 import { AtlasViewerURLService } from "./atlasViewer/atlasViewer.urlService.service";
 import { ToastComponent } from "./components/toast/toast.component";
-import { GetFilenameFromPathnamePipe } from "./util/pipes/getFileNameFromPathName.pipe";
 import { AtlasViewerAPIServices } from "./atlasViewer/atlasViewer.apiService.service";
 import { PluginUnit } from "./atlasViewer/pluginUnit/pluginUnit.component";
 import { NewViewerDisctinctViewToLayer } from "./util/pipes/newViewerDistinctViewToLayer.pipe";
@@ -41,7 +40,12 @@ import { TransformOnhoverSegmentPipe } from "./atlasViewer/onhoverSegment.pipe";
 import {HttpClientModule} from "@angular/common/http";
 import { EffectsModule } from "@ngrx/effects";
 import { UseEffects } from "./services/effect/effect";
-import { MatDialogModule, MatTabsModule } from "@angular/material";
+import { DataBrowserUseEffect } from "./ui/databrowserModule/databrowser.useEffect";
+import { DialogService } from "./services/dialogService.service";
+import { DialogComponent } from "./components/dialog/dialog.component";
+import { ViewerStateControllerUseEffect } from "./ui/viewerStateController/viewerState.useEffect";
+import { ConfirmDialogComponent } from "./components/confirmDialog/confirmDialog.component";
+import { ViewerStateUseEffect } from "./services/state/viewerState.store";
 
 import 'hammerjs'
 
@@ -54,17 +58,15 @@ import 'hammerjs'
     DragDropModule,
     UIModule,
     AngularMaterialModule,
-
-    /**
-     * move to angular material module
-     */
-    MatDialogModule,
-    MatTabsModule,
     
     TooltipModule.forRoot(),
     TabsModule.forRoot(),
     EffectsModule.forRoot([
-      UseEffects
+      DataBrowserUseEffect,
+      UseEffects,
+      UserConfigStateUseEffect,
+      ViewerStateControllerUseEffect,
+      ViewerStateUseEffect,
     ]),
     StoreModule.forRoot({
       pluginState,
@@ -74,6 +76,7 @@ import 'hammerjs'
       dataStore,
       spatialSearchState,
       uiState,
+      userConfigState
     }),
     HttpClientModule
   ],
@@ -103,7 +106,6 @@ import 'hammerjs'
     GetNamesPipe,
     GetNamePipe,
     TransformOnhoverSegmentPipe,
-    GetFilenameFromPathnamePipe,
     NewViewerDisctinctViewToLayer
   ],
   entryComponents : [
@@ -111,6 +113,8 @@ import 'hammerjs'
     ModalUnit,
     ToastComponent,
     PluginUnit,
+    DialogComponent,
+    ConfirmDialogComponent,
   ],
   providers : [
     AtlasViewerDataService,
@@ -120,6 +124,7 @@ import 'hammerjs'
     ToastService,
     AtlasWorkerService,
     AuthService,
+    DialogService,
     
     /**
      * TODO
diff --git a/src/main.ts b/src/main.ts
index 5eec78190f22de8b0a62e4d71eb812efc3a842ab..c1c1f32e340242fc556c097b7351f8b2cd28e277 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -3,6 +3,9 @@ import 'reflect-metadata'
 import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'
 import { MainModule } from './main.module';
 
+import './theme.scss'
+import './res/css/extra_styles.css'
+
 const requireAll = (r:any) => {r.keys().forEach(r)}
 requireAll(require.context('./res/ext',false, /\.json$/))
 requireAll(require.context('./res/images',true,/\.jpg|\.png/))
diff --git a/src/plugin_examples/plugin_api.md b/src/plugin_examples/plugin_api.md
index 131e564e47fc8852553f0815d14ce8c23baca2aa..45aa8ef15d44e3a6330435fba8cd4b36a83ee609 100644
--- a/src/plugin_examples/plugin_api.md
+++ b/src/plugin_examples/plugin_api.md
@@ -141,6 +141,23 @@ window.interactiveViewer
     - timeout : auto hide (in ms). set to 0 for not auto hide.
 
   - *launchNewWidget(manifest)* returns a Promise. expects a JSON object, with the same key value as a plugin manifest. the *name* key must be unique, or the promise will be rejected. 
+
+  - *getUserInput(config)* returns a Promise, resolves when user confirms, rejects when user cancels. expects config object object with the following structure:
+  ```javascript
+  const config = {
+    "title": "Title of the modal", // default: "Message"
+    "message":"Message to be seen by the user.", // default: ""
+    "placeholder": "Start typing here", // default: "Type your response here"
+    "defaultValue": "42" // default: ""
+  }
+  ```
+  - *getUserConfirmation(config)* returns a Promise, resolves when user confirms, rejects when user cancels. expects config object object with the following structure:
+  ```javascript
+  const config = {
+    "title": "Title of the modal", // default: "Message"
+    "message":"Message to be seen by the user." // default: ""
+  }
+  ```
   
 - pluginControl
 
@@ -162,7 +179,8 @@ window.interactiveViewer
   - **[PLUGINNAME]** returns a plugin handler. This would be how to interface with the plugins.
 
     
-    - *blink(sec?:number)* : Function that causes the floating widget to blink, attempt to grab user attention (silently fails if called on startup).
+    - *blink()* : Function that causes the floating widget to blink, attempt to grab user attention (silently fails if called on startup).
+    - *setProgressIndicator(val:number|null)* : Set the progress of the plugin. Useful for giving user feedbacks on the progress of a long running process. Call the function with null to unset the progress.
     - *shutdown()* : Function that causes the widget to shutdown dynamically. (triggers onShutdown callback, silently fails if called on startup)
     - *onShutdown(callback)* : Attaches a callback function, which is called when the plugin is shutdown.
     - *initState* : passed from `manifest.json`. Useful for setting initial state of the plugin. Can be any JSON valid value (array, object, string).
diff --git a/src/res/css/extra_styles.css b/src/res/css/extra_styles.css
index 61564e98a21122542f1bdad2118b7d0ca1374992..d5b2c296496bf6c94cd3127c620dcc1812176f36 100644
--- a/src/res/css/extra_styles.css
+++ b/src/res/css/extra_styles.css
@@ -137,10 +137,14 @@ span.regionSelected
 span.regionNotSelected,
 span.regionSelected
 {
-  cursor : default;
   user-select: none;
 }
 
+.cursor-default
+{
+  cursor: default;
+}
+
 markdown-dom pre code
 {
   white-space:pre;
@@ -290,9 +294,10 @@ markdown-dom pre code
 {
   width: 10em!important;
 }
-.w-20em
+
+.mw-400px
 {
-  width: 20em!important;
+  max-width: 400px!important;
 }
 
 .mw-100
@@ -310,11 +315,26 @@ markdown-dom pre code
   max-width: 60%!important;
 }
 
+.mw-20em
+{
+  max-width: 20em!important;
+}
+
+.w-20em
+{
+  width: 20em!important;
+}
+
 .mh-20em
 {
   max-height: 20em;
 }
 
+.mh-10em
+{
+  max-height: 10em;
+}
+
 .pe-all
 {
   pointer-events: all;
@@ -359,7 +379,7 @@ markdown-dom pre code
 
 .overflow-x-hidden
 {
-  overflow-x:hidden;
+  overflow-x:hidden!important;
 }
 
 .muted
@@ -378,6 +398,21 @@ markdown-dom pre code
   border:none;
 }
 
+.w-1em
+{
+  width: 1em;
+}
+
+.bs-content-box
+{
+  box-sizing: content-box;
+}
+
+/* required to hide  */
+.cdk-global-scrollblock
+{
+  overflow-y:hidden !important;
+}
 .h-90vh
 {
   height: 90vh!important;
diff --git a/src/res/ext/colinNehubaConfig.json b/src/res/ext/colinNehubaConfig.json
index 7b40174f13aa5e7e65e074d58372b3bc8e56e7c0..6028ab6b9200aec69c249512ad70e6783ceb05bf 100644
--- a/src/res/ext/colinNehubaConfig.json
+++ b/src/res/ext/colinNehubaConfig.json
@@ -1 +1,179 @@
-{"globals":{"hideNullImageValues":true,"useNehubaLayout":true,"useNehubaMeshLayer":true,"useCustomSegmentColors":true},"zoomWithoutCtrl":true,"hideNeuroglancerUI":true,"rightClickWithCtrl":true,"rotateAtViewCentre":true,"zoomAtViewCentre":true,"enableMeshLoadingControl":true,"layout":{"useNehubaPerspective":{"fixedZoomPerspectiveSlices":{"sliceViewportWidth":300,"sliceViewportHeight":300,"sliceZoom":724698.1843689409,"sliceViewportSizeMultiplier":2},"centerToOrigin":true,"mesh":{"removeBasedOnNavigation":true,"flipRemovedOctant":true,"surfaceParcellation":false},"removePerspectiveSlicesBackground":{"mode":"=="},"waitForMesh":false,"drawSubstrates":{"color":[0.5,0.5,1,0.2]},"drawZoomLevels":{"cutOff":150000},"restrictZoomLevel":{"minZoom":2500000,"maxZoom":3500000}}},"dataset":{"imageBackground":[0,0,0,1],"initialNgState":{"showDefaultAnnotations":false,"layers":{"colin":{"type":"image","visible":true,"source":"precomputed://https://neuroglancer.humanbrainproject.org/precomputed/JuBrain/v2.2c/colin27_seg","transform":[[1,0,0,-75500000],[0,1,0,-111500000],[0,0,1,-67500000],[0,0,0,1]]},"jubrain v2_2c":{"type":"segmentation","source":"precomputed://https://neuroglancer.humanbrainproject.org/precomputed/JuBrain/v2.2c/MPM","transform":[[1,0,0,-75500000],[0,1,0,-111500000],[0,0,1,-67500000],[0,0,0,1]]},"jubrain colin v17 left":{"type":"segmentation","visible":true,"source":"precomputed://https://neuroglancer.humanbrainproject.org/precomputed/JuBrain/17/colin27/left","transform":[[1,0,0,-128500000],[0,1,0,-148500000],[0,0,1,-110500000],[0,0,0,1]]},"jubrain colin v17 right":{"type":"segmentation","visible":true,"source":"precomputed://https://neuroglancer.humanbrainproject.org/precomputed/JuBrain/17/colin27/right","transform":[[1,0,0,-128500000],[0,1,0,-148500000],[0,0,1,-110500000],[0,0,0,1]]}},"navigation":{"pose":{"position":{"voxelSize":[1000000,1000000,1000000],"voxelCoordinates":[0,-32,0]}},"zoomFactor":1000000},"perspectiveOrientation":[-0.2753947079181671,0.6631333827972412,-0.6360703706741333,0.2825356423854828],"perspectiveZoom":3000000}}}
\ No newline at end of file
+{
+  "globals": {
+    "hideNullImageValues": true,
+    "useNehubaLayout": true,
+    "useNehubaMeshLayer": true,
+    "useCustomSegmentColors": true
+  },
+  "zoomWithoutCtrl": true,
+  "hideNeuroglancerUI": true,
+  "rightClickWithCtrl": true,
+  "rotateAtViewCentre": true,
+  "zoomAtViewCentre": true,
+  "enableMeshLoadingControl": true,
+  "layout": {
+    "useNehubaPerspective": {
+      "fixedZoomPerspectiveSlices": {
+        "sliceViewportWidth": 300,
+        "sliceViewportHeight": 300,
+        "sliceZoom": 724698.1843689409,
+        "sliceViewportSizeMultiplier": 2
+      },
+      "centerToOrigin": true,
+      "mesh": {
+        "removeBasedOnNavigation": true,
+        "flipRemovedOctant": true,
+        "surfaceParcellation": false
+      },
+      "removePerspectiveSlicesBackground": {
+        "mode": "=="
+      },
+      "waitForMesh": false,
+      "drawSubstrates": {
+        "color": [
+          0.5,
+          0.5,
+          1,
+          0.2
+        ]
+      },
+      "drawZoomLevels": {
+        "cutOff": 150000
+      },
+      "restrictZoomLevel": {
+        "minZoom": 2500000,
+        "maxZoom": 3500000
+      }
+    }
+  },
+  "dataset": {
+    "imageBackground": [
+      0,
+      0,
+      0,
+      1
+    ],
+    "initialNgState": {
+      "showDefaultAnnotations": false,
+      "layers": {
+        "colin": {
+          "type": "image",
+          "visible": true,
+          "source": "precomputed://https://neuroglancer.humanbrainproject.org/precomputed/JuBrain/v2.2c/colin27_seg",
+          "transform": [
+            [
+              1,
+              0,
+              0,
+              -75500000
+            ],
+            [
+              0,
+              1,
+              0,
+              -111500000
+            ],
+            [
+              0,
+              0,
+              1,
+              -67500000
+            ],
+            [
+              0,
+              0,
+              0,
+              1
+            ]
+          ]
+        },
+        "jubrain colin v17 left": {
+          "type": "segmentation",
+          "visible": true,
+          "source": "precomputed://https://neuroglancer.humanbrainproject.org/precomputed/JuBrain/17/colin27/left",
+          "transform": [
+            [
+              1,
+              0,
+              0,
+              -128500000
+            ],
+            [
+              0,
+              1,
+              0,
+              -148500000
+            ],
+            [
+              0,
+              0,
+              1,
+              -110500000
+            ],
+            [
+              0,
+              0,
+              0,
+              1
+            ]
+          ]
+        },
+        "jubrain colin v17 right": {
+          "type": "segmentation",
+          "visible": true,
+          "source": "precomputed://https://neuroglancer.humanbrainproject.org/precomputed/JuBrain/17/colin27/right",
+          "transform": [
+            [
+              1,
+              0,
+              0,
+              -128500000
+            ],
+            [
+              0,
+              1,
+              0,
+              -148500000
+            ],
+            [
+              0,
+              0,
+              1,
+              -110500000
+            ],
+            [
+              0,
+              0,
+              0,
+              1
+            ]
+          ]
+        }
+      },
+      "navigation": {
+        "pose": {
+          "position": {
+            "voxelSize": [
+              1000000,
+              1000000,
+              1000000
+            ],
+            "voxelCoordinates": [
+              0,
+              -32,
+              0
+            ]
+          }
+        },
+        "zoomFactor": 1000000
+      },
+      "perspectiveOrientation": [
+        -0.2753947079181671,
+        0.6631333827972412,
+        -0.6360703706741333,
+        0.2825356423854828
+      ],
+      "perspectiveZoom": 3000000
+    }
+  }
+}
\ No newline at end of file
diff --git a/src/res/images/1-100.png b/src/res/images/1-100.png
new file mode 100644
index 0000000000000000000000000000000000000000..66f27f2321c73cb215e28a03ca48efd4fa8f813d
Binary files /dev/null and b/src/res/images/1-100.png differ
diff --git a/src/res/images/1-200.png b/src/res/images/1-200.png
new file mode 100644
index 0000000000000000000000000000000000000000..75435265e1f04a145d264f4b69010e03cc0f75d4
Binary files /dev/null and b/src/res/images/1-200.png differ
diff --git a/src/res/images/1-300.png b/src/res/images/1-300.png
new file mode 100644
index 0000000000000000000000000000000000000000..a990036ac12431e51d442bcaa76614df273e32cd
Binary files /dev/null and b/src/res/images/1-300.png differ
diff --git a/src/res/images/1-400.png b/src/res/images/1-400.png
new file mode 100644
index 0000000000000000000000000000000000000000..fb8880161ac1364984184e796a0d896b08549a45
Binary files /dev/null and b/src/res/images/1-400.png differ
diff --git a/src/res/images/2-100.png b/src/res/images/2-100.png
new file mode 100644
index 0000000000000000000000000000000000000000..62817e68db8cd9bf570e3923f3fc92b294df2afa
Binary files /dev/null and b/src/res/images/2-100.png differ
diff --git a/src/res/images/2-200.png b/src/res/images/2-200.png
new file mode 100644
index 0000000000000000000000000000000000000000..1036281b27359d0021be9e843758fa8c5db3270c
Binary files /dev/null and b/src/res/images/2-200.png differ
diff --git a/src/res/images/2-300.png b/src/res/images/2-300.png
new file mode 100644
index 0000000000000000000000000000000000000000..e1718b621a1ea092afea438e1bfc5fbcfa68eeaf
Binary files /dev/null and b/src/res/images/2-300.png differ
diff --git a/src/res/images/2-400.png b/src/res/images/2-400.png
new file mode 100644
index 0000000000000000000000000000000000000000..fb349bd8a361bed5ce75cf2daea616cf20306ed6
Binary files /dev/null and b/src/res/images/2-400.png differ
diff --git a/src/res/images/3-100.png b/src/res/images/3-100.png
new file mode 100644
index 0000000000000000000000000000000000000000..aec2e7b0297a557dee3843504b41cf3775f9341b
Binary files /dev/null and b/src/res/images/3-100.png differ
diff --git a/src/res/images/3-200.png b/src/res/images/3-200.png
new file mode 100644
index 0000000000000000000000000000000000000000..72665ecaa83a501b02e96e0f77701dae192147d7
Binary files /dev/null and b/src/res/images/3-200.png differ
diff --git a/src/res/images/3-300.png b/src/res/images/3-300.png
new file mode 100644
index 0000000000000000000000000000000000000000..f2d5f9795d69557b267039774e072b98dfc53845
Binary files /dev/null and b/src/res/images/3-300.png differ
diff --git a/src/res/images/3-400.png b/src/res/images/3-400.png
new file mode 100644
index 0000000000000000000000000000000000000000..f41ecd0ec5dc922177a95e7713f541cb55e17d8f
Binary files /dev/null and b/src/res/images/3-400.png differ
diff --git a/src/res/images/AllenadultmousebrainreferenceatlasV3BrainAtlas-100.png b/src/res/images/AllenadultmousebrainreferenceatlasV3BrainAtlas-100.png
new file mode 100644
index 0000000000000000000000000000000000000000..f9a9fb0e291661a457f9492df96a50462999969d
Binary files /dev/null and b/src/res/images/AllenadultmousebrainreferenceatlasV3BrainAtlas-100.png differ
diff --git a/src/res/images/AllenadultmousebrainreferenceatlasV3BrainAtlas-200.png b/src/res/images/AllenadultmousebrainreferenceatlasV3BrainAtlas-200.png
new file mode 100644
index 0000000000000000000000000000000000000000..36c143ae11e00675c45d16c5a95457a0b5880b43
Binary files /dev/null and b/src/res/images/AllenadultmousebrainreferenceatlasV3BrainAtlas-200.png differ
diff --git a/src/res/images/AllenadultmousebrainreferenceatlasV3BrainAtlas-300.png b/src/res/images/AllenadultmousebrainreferenceatlasV3BrainAtlas-300.png
new file mode 100644
index 0000000000000000000000000000000000000000..52f57e5412d618286dfd716fce13b13cef10741d
Binary files /dev/null and b/src/res/images/AllenadultmousebrainreferenceatlasV3BrainAtlas-300.png differ
diff --git a/src/res/images/AllenadultmousebrainreferenceatlasV3BrainAtlas-400.png b/src/res/images/AllenadultmousebrainreferenceatlasV3BrainAtlas-400.png
new file mode 100644
index 0000000000000000000000000000000000000000..df6e4acd8be3ae0cccca705dc824932e70eefd05
Binary files /dev/null and b/src/res/images/AllenadultmousebrainreferenceatlasV3BrainAtlas-400.png differ
diff --git a/src/res/images/BigBrainHistology-100.png b/src/res/images/BigBrainHistology-100.png
new file mode 100644
index 0000000000000000000000000000000000000000..50559bfb8359d5a71eb2a82dc276f72f80951f7a
Binary files /dev/null and b/src/res/images/BigBrainHistology-100.png differ
diff --git a/src/res/images/BigBrainHistology-200.png b/src/res/images/BigBrainHistology-200.png
new file mode 100644
index 0000000000000000000000000000000000000000..a17a7f83393e0a57dfe80afad199150b16e605d4
Binary files /dev/null and b/src/res/images/BigBrainHistology-200.png differ
diff --git a/src/res/images/BigBrainHistology-300.png b/src/res/images/BigBrainHistology-300.png
new file mode 100644
index 0000000000000000000000000000000000000000..f7cd0b28ec4cbbc78488799057878e1e8225215f
Binary files /dev/null and b/src/res/images/BigBrainHistology-300.png differ
diff --git a/src/res/images/BigBrainHistology-400.png b/src/res/images/BigBrainHistology-400.png
new file mode 100644
index 0000000000000000000000000000000000000000..7d8fea17c6d8dd5b3075df22c1f3ce9054275abb
Binary files /dev/null and b/src/res/images/BigBrainHistology-400.png differ
diff --git a/src/res/images/ICBM2009cNonlinearAsymmetric-100.png b/src/res/images/ICBM2009cNonlinearAsymmetric-100.png
new file mode 100644
index 0000000000000000000000000000000000000000..c8912fe70f594f0a0d60ad7596802ad690166c56
Binary files /dev/null and b/src/res/images/ICBM2009cNonlinearAsymmetric-100.png differ
diff --git a/src/res/images/ICBM2009cNonlinearAsymmetric-200.png b/src/res/images/ICBM2009cNonlinearAsymmetric-200.png
new file mode 100644
index 0000000000000000000000000000000000000000..7f38858407d751d4e5b1a200ed310d2c81cb6e07
Binary files /dev/null and b/src/res/images/ICBM2009cNonlinearAsymmetric-200.png differ
diff --git a/src/res/images/ICBM2009cNonlinearAsymmetric-300.png b/src/res/images/ICBM2009cNonlinearAsymmetric-300.png
new file mode 100644
index 0000000000000000000000000000000000000000..481339124d296586ec68455fd0298aed2b392e2b
Binary files /dev/null and b/src/res/images/ICBM2009cNonlinearAsymmetric-300.png differ
diff --git a/src/res/images/ICBM2009cNonlinearAsymmetric-400.png b/src/res/images/ICBM2009cNonlinearAsymmetric-400.png
new file mode 100644
index 0000000000000000000000000000000000000000..0ce95ddc0988de4f48ce8cdcda9c6b0d98b66a79
Binary files /dev/null and b/src/res/images/ICBM2009cNonlinearAsymmetric-400.png differ
diff --git a/src/res/images/MNI152ICBM2009cNonlinearAsymmetric-100.png b/src/res/images/MNI152ICBM2009cNonlinearAsymmetric-100.png
new file mode 100644
index 0000000000000000000000000000000000000000..859167bec29489f7e787d3820081e8d21d69564d
Binary files /dev/null and b/src/res/images/MNI152ICBM2009cNonlinearAsymmetric-100.png differ
diff --git a/src/res/images/MNI152ICBM2009cNonlinearAsymmetric-200.png b/src/res/images/MNI152ICBM2009cNonlinearAsymmetric-200.png
new file mode 100644
index 0000000000000000000000000000000000000000..5c9e3d78e789eefef7e608d5807713f7d3a59769
Binary files /dev/null and b/src/res/images/MNI152ICBM2009cNonlinearAsymmetric-200.png differ
diff --git a/src/res/images/MNI152ICBM2009cNonlinearAsymmetric-300.png b/src/res/images/MNI152ICBM2009cNonlinearAsymmetric-300.png
new file mode 100644
index 0000000000000000000000000000000000000000..29c1ea8f17d80fc7c850fcca49964791dc96ea15
Binary files /dev/null and b/src/res/images/MNI152ICBM2009cNonlinearAsymmetric-300.png differ
diff --git a/src/res/images/MNI152ICBM2009cNonlinearAsymmetric-400.png b/src/res/images/MNI152ICBM2009cNonlinearAsymmetric-400.png
new file mode 100644
index 0000000000000000000000000000000000000000..07b8475e00616e3235955d65cbc3598f0bd82e82
Binary files /dev/null and b/src/res/images/MNI152ICBM2009cNonlinearAsymmetric-400.png differ
diff --git a/src/res/images/MNIColin27-100.png b/src/res/images/MNIColin27-100.png
new file mode 100644
index 0000000000000000000000000000000000000000..d4748838a9e7d341efe95a9823fa7edca0b6126a
Binary files /dev/null and b/src/res/images/MNIColin27-100.png differ
diff --git a/src/res/images/MNIColin27-200.png b/src/res/images/MNIColin27-200.png
new file mode 100644
index 0000000000000000000000000000000000000000..683341c65daaa5170e77197e614acbcaa3e48f5b
Binary files /dev/null and b/src/res/images/MNIColin27-200.png differ
diff --git a/src/res/images/MNIColin27-300.png b/src/res/images/MNIColin27-300.png
new file mode 100644
index 0000000000000000000000000000000000000000..d55090287e9925d795fbf6772024e6cb81100563
Binary files /dev/null and b/src/res/images/MNIColin27-300.png differ
diff --git a/src/res/images/MNIColin27-400.png b/src/res/images/MNIColin27-400.png
new file mode 100644
index 0000000000000000000000000000000000000000..6bd9fd61b6ab6bde92c61cd035bb0999ecf1ed3b
Binary files /dev/null and b/src/res/images/MNIColin27-400.png differ
diff --git a/src/res/images/WaxholmSpaceratbrainatlasv20-100.png b/src/res/images/WaxholmSpaceratbrainatlasv20-100.png
new file mode 100644
index 0000000000000000000000000000000000000000..e1eddc7bfbc2e46b4c4bcf1146558a27d277c57b
Binary files /dev/null and b/src/res/images/WaxholmSpaceratbrainatlasv20-100.png differ
diff --git a/src/res/images/WaxholmSpaceratbrainatlasv20-200.png b/src/res/images/WaxholmSpaceratbrainatlasv20-200.png
new file mode 100644
index 0000000000000000000000000000000000000000..938d3c00746f6c4a1eefa6468715a8af0e205cf0
Binary files /dev/null and b/src/res/images/WaxholmSpaceratbrainatlasv20-200.png differ
diff --git a/src/res/images/WaxholmSpaceratbrainatlasv20-300.png b/src/res/images/WaxholmSpaceratbrainatlasv20-300.png
new file mode 100644
index 0000000000000000000000000000000000000000..37e7635b5b55e317455e12d727654b89d872067a
Binary files /dev/null and b/src/res/images/WaxholmSpaceratbrainatlasv20-300.png differ
diff --git a/src/res/images/WaxholmSpaceratbrainatlasv20-400.png b/src/res/images/WaxholmSpaceratbrainatlasv20-400.png
new file mode 100644
index 0000000000000000000000000000000000000000..57840e545f0f1804104c93cd09fb65bb16813e4b
Binary files /dev/null and b/src/res/images/WaxholmSpaceratbrainatlasv20-400.png differ
diff --git a/src/services/dialogService.service.ts b/src/services/dialogService.service.ts
new file mode 100644
index 0000000000000000000000000000000000000000..110edf6663f748866b952af474d56291e2229149
--- /dev/null
+++ b/src/services/dialogService.service.ts
@@ -0,0 +1,62 @@
+import { Injectable } from "@angular/core";
+import { MatDialog, MatDialogRef } from "@angular/material";
+import { DialogComponent } from "src/components/dialog/dialog.component";
+import { ConfirmDialogComponent } from "src/components/confirmDialog/confirmDialog.component";
+
+
+@Injectable({
+  providedIn: 'root'
+})
+
+export class DialogService{
+
+  private dialogRef: MatDialogRef<DialogComponent>
+  private confirmDialogRef: MatDialogRef<ConfirmDialogComponent>
+
+  constructor(private dialog:MatDialog){
+
+  }
+
+  public getUserConfirm(config: Partial<DialogConfig> = {}): Promise<string>{
+    this.confirmDialogRef = this.dialog.open(ConfirmDialogComponent, {
+      data: config
+    })
+    return new Promise((resolve, reject) => this.confirmDialogRef.afterClosed()
+      .subscribe(val => {
+        if (val) resolve()
+        else reject('User cancelled')
+      },
+      reject,
+      () => this.confirmDialogRef = null))
+  }
+
+  public getUserInput(config: Partial<DialogConfig> = {}):Promise<string>{
+    const { defaultValue = '', placeholder = 'Type your response here', title = 'Message', message = '' } = config
+    this.dialogRef = this.dialog.open(DialogComponent, {
+      data: {
+        title,
+        placeholder,
+        defaultValue,
+        message
+      }
+    })
+    return new Promise((resolve, reject) => {
+      /**
+       * nb: one one value is ever emitted, then the subscription ends
+       * Should not result in leak
+       */
+      this.dialogRef.afterClosed().subscribe(value => {
+        if (value) resolve(value)
+        else reject('User cancelled input')
+        this.dialogRef = null
+      })
+    })
+  }
+}
+
+export interface DialogConfig{
+  title: string
+  placeholder: string
+  defaultValue: string
+  message: string
+}
\ No newline at end of file
diff --git a/src/services/effect/effect.ts b/src/services/effect/effect.ts
index 399bcae7652b8bb8b1ec3becba2188d341dfaf2c..603ed7be86372dc6b93d4922979882c219da09f3 100644
--- a/src/services/effect/effect.ts
+++ b/src/services/effect/effect.ts
@@ -1,9 +1,9 @@
 import { Injectable, OnDestroy } from "@angular/core";
 import { Effect, Actions, ofType } from "@ngrx/effects";
-import { Subscription, merge, fromEvent, combineLatest } from "rxjs";
-import { withLatestFrom, map, filter } from "rxjs/operators";
+import { Subscription, merge, fromEvent, combineLatest, Observable } from "rxjs";
+import { withLatestFrom, map, filter, shareReplay, tap, switchMap, take } from "rxjs/operators";
 import { Store, select } from "@ngrx/store";
-import { SELECT_PARCELLATION, SELECT_REGIONS, NEWVIEWER, UPDATE_PARCELLATION, SELECT_REGIONS_WITH_ID } from "../state/viewerState.store";
+import { SELECT_PARCELLATION, SELECT_REGIONS, NEWVIEWER, UPDATE_PARCELLATION, SELECT_REGIONS_WITH_ID, DESELECT_REGIONS, ADD_TO_REGIONS_SELECTION_WITH_IDS } from "../state/viewerState.store";
 import { worker } from 'src/atlasViewer/atlasViewer.workerService.service'
 import { getNgIdLabelIndexFromId, generateLabelIndexId, recursiveFindRegionWithLabelIndexId } from '../stateStore.service';
 
@@ -24,8 +24,51 @@ export class UseEffects implements OnDestroy{
         })
       })
     )
+
+    this.regionsSelected$ = this.store$.pipe(
+      select('viewerState'),
+      select('regionsSelected'),
+      shareReplay(1)
+    )
+
+    this.onDeselectRegions = this.actions$.pipe(
+      ofType(DESELECT_REGIONS),
+      withLatestFrom(this.regionsSelected$),
+      map(([action, regionsSelected]) => {
+        const { deselectRegions } = action
+        const deselectSet = new Set((deselectRegions as any[]).map(r => r.name))
+        const selectRegions = regionsSelected.filter(r => !deselectSet.has(r.name))
+        return {
+          type: SELECT_REGIONS,
+          selectRegions
+        }
+      })
+    )
+
+    this.addToSelectedRegions$ = this.actions$.pipe(
+      ofType(ADD_TO_REGIONS_SELECTION_WITH_IDS),
+      map(action => {
+        const { selectRegionIds } = action
+        return selectRegionIds
+      }),
+      switchMap(selectRegionIds => this.updatedParcellation$.pipe(
+        filter(p => !!p),
+        take(1),
+        map(p => [selectRegionIds, p])
+      )),
+      map(this.convertRegionIdsToRegion),
+      withLatestFrom(this.regionsSelected$),
+      map(([ selectedRegions, alreadySelectedRegions ]) => {
+        return {
+          type: SELECT_REGIONS,
+          selectRegions: this.removeDuplicatedRegions(selectedRegions, alreadySelectedRegions)
+        }
+      })
+    )
   }
 
+  private regionsSelected$: Observable<any[]>
+
   ngOnDestroy(){
     while(this.subscriptions.length > 0) {
       this.subscriptions.pop().unsubscribe()
@@ -53,45 +96,76 @@ export class UseEffects implements OnDestroy{
   private updatedParcellation$ = this.store$.pipe(
     select('viewerState'),
     select('parcellationSelected'),
-    filter(p => !!p && !!p.regions)
+    map(p => p.updated ? p : null),
+    shareReplay(1)
   )
 
+  @Effect()
+  onDeselectRegions: Observable<any> 
+
+  private convertRegionIdsToRegion = ([selectRegionIds, parcellation]) => {
+    const { ngId: defaultNgId } = parcellation
+    return (<any[]>selectRegionIds)
+      .map(labelIndexId => getNgIdLabelIndexFromId({ labelIndexId }))
+      .map(({ ngId, labelIndex }) => {
+        return {
+          labelIndexId: generateLabelIndexId({
+            ngId: ngId || defaultNgId,
+            labelIndex 
+          })
+        }
+      })
+      .map(({ labelIndexId }) => {
+        return recursiveFindRegionWithLabelIndexId({ 
+          regions: parcellation.regions,
+          labelIndexId,
+          inheritedNgId: defaultNgId
+        })
+      })
+      .filter(v => {
+        if (!v) {
+          console.log(`SELECT_REGIONS_WITH_ID, some ids cannot be parsed intto label index`)
+        }
+        return !!v
+      })
+  }
+
+  private removeDuplicatedRegions = (...args) => {
+    const set = new Set()
+    const returnArr = []
+    for (const regions of args){
+      for (const region of regions){
+        if (!set.has(region.name)) {
+          returnArr.push(region)
+          set.add(region.name)
+        }
+      }
+    }
+    return returnArr
+  }
+
+  @Effect()
+  addToSelectedRegions$: Observable<any>
+  
+
   /**
    * for backwards compatibility.
    * older versions of atlas viewer may only have labelIndex as region identifier
    */
   @Effect()
-  onSelectRegionWithId = combineLatest(
-    this.actions$.pipe(
-      ofType(SELECT_REGIONS_WITH_ID)
-    ),
-    this.updatedParcellation$
-  ).pipe(
-    map(([action, parcellation]) => {
+  onSelectRegionWithId = this.actions$.pipe(
+    ofType(SELECT_REGIONS_WITH_ID),
+    map(action => {
       const { selectRegionIds } = action
-      const { ngId: defaultNgId } = parcellation
-
-      const selectRegions = (<any[]>selectRegionIds)
-        .map(labelIndexId => getNgIdLabelIndexFromId({ labelIndexId }))
-        .map(({ ngId, labelIndex }) => {
-          return {
-            labelIndexId: generateLabelIndexId({
-              ngId: ngId || defaultNgId,
-              labelIndex 
-            })
-          }
-        })
-        .map(({ labelIndexId }) => {
-          return recursiveFindRegionWithLabelIndexId({ 
-            regions: parcellation.regions,
-            labelIndexId,
-            inheritedNgId: defaultNgId
-          })
-        })
-        .filter(v => {
-          if (!v) console.log(`SELECT_REGIONS_WITH_ID, some ids cannot be parsed intto label index`)
-          return !!v
-        })
+      return selectRegionIds
+    }),
+    switchMap(selectRegionIds => this.updatedParcellation$.pipe(
+      filter(p => !!p),
+      take(1),
+      map(parcellation => [selectRegionIds, parcellation])
+    )),
+    map(this.convertRegionIdsToRegion),
+    map(selectRegions => {
       return {
         type: SELECT_REGIONS,
         selectRegions
@@ -118,7 +192,14 @@ export class UseEffects implements OnDestroy{
     filter((message: MessageEvent) => message && message.data && message.data.type === 'UPDATE_PARCELLATION_REGIONS'),
     map(({data}) => data.parcellation),
     withLatestFrom(this.newParcellationSelected$),
-    filter(([ propagatedP, selectedP ] : [any, any]) => propagatedP.name === selectedP.name),
+    filter(([ propagatedP, selectedP ] : [any, any]) => {
+      /**
+       * TODO 
+       * use id
+       * but jubrain may have same id for different template spaces
+       */
+      return propagatedP.name === selectedP.name
+    }),
     map(([ propagatedP, _ ]) => propagatedP),
     map(parcellation => ({
       type: UPDATE_PARCELLATION,
diff --git a/src/services/state/dataStore.store.ts b/src/services/state/dataStore.store.ts
index 4b25d5eae3b134ab7f48c07cf9d290eee06e1d8a..44f21f0bb825e10cdb63e51c23813c7f3fa7a0ec 100644
--- a/src/services/state/dataStore.store.ts
+++ b/src/services/state/dataStore.store.ts
@@ -1,6 +1,22 @@
 import { Action } from '@ngrx/store'
 
-export function dataStore(state:any,action:DatasetAction){
+/**
+ * TODO merge with databrowser.usereffect.ts
+ */
+
+interface DataEntryState{
+  fetchedDataEntries: DataEntry[]
+  favDataEntries: DataEntry[]
+  fetchedSpatialData: DataEntry[]
+}
+
+const defaultState = {
+  fetchedDataEntries: [],
+  favDataEntries: [],
+  fetchedSpatialData: []
+}
+
+export function dataStore(state:DataEntryState = defaultState, action:Partial<DatasetAction>){
   switch (action.type){
     case FETCHED_DATAENTRIES: {
       return {
@@ -14,12 +30,19 @@ export function dataStore(state:any,action:DatasetAction){
         fetchedSpatialData : action.fetchedDataEntries
       }
     }
-    default:
-      return state
+    case ACTION_TYPES.UPDATE_FAV_DATASETS: {
+      const { favDataEntries = [] } = action
+      return {
+        ...state,
+        favDataEntries
+      }
+    }
+    default: return state
   }
 }
 
 export interface DatasetAction extends Action{
+  favDataEntries: DataEntry[]
   fetchedDataEntries : DataEntry[]
   fetchedSpatialData : DataEntry[]
 }
@@ -57,6 +80,9 @@ export interface DataEntry{
    * TODO typo, should be kgReferences
    */
   kgReference: string[]
+
+  id: string
+  fullId: string
 }
 
 export interface ParcellationRegion {
@@ -133,4 +159,12 @@ export interface ViewerPreviewFile{
 
 export interface FileSupplementData{
   data: any
-}
\ No newline at end of file
+}
+
+const ACTION_TYPES = {
+  FAV_DATASET: `FAV_DATASET`,
+  UPDATE_FAV_DATASETS: `UPDATE_FAV_DATASETS`,
+  UNFAV_DATASET: 'UNFAV_DATASET'
+}
+
+export const DATASETS_ACTIONS_TYPES = ACTION_TYPES
\ No newline at end of file
diff --git a/src/services/state/userConfigState.store.ts b/src/services/state/userConfigState.store.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a4af6c3420f635d37fe38c79cbaf40b0c5534598
--- /dev/null
+++ b/src/services/state/userConfigState.store.ts
@@ -0,0 +1,340 @@
+import { Action, Store, select } from "@ngrx/store";
+import { Injectable, OnDestroy } from "@angular/core";
+import { Actions, Effect, ofType } from "@ngrx/effects";
+import { Observable, combineLatest, Subscription, from, of } from "rxjs";
+import { shareReplay, withLatestFrom, map, distinctUntilChanged, filter, take, tap, switchMap, catchError, share } from "rxjs/operators";
+import { generateLabelIndexId, recursiveFindRegionWithLabelIndexId } from "../stateStore.service";
+import { SELECT_REGIONS, NEWVIEWER, SELECT_PARCELLATION } from "./viewerState.store";
+import { DialogService } from "../dialogService.service";
+
+interface UserConfigState{
+  savedRegionsSelection: RegionSelection[]
+}
+
+export interface RegionSelection{
+  templateSelected: any
+  parcellationSelected: any
+  regionsSelected: any[]
+  name: string
+  id: string
+}
+
+/**
+ * for serialisation into local storage/database
+ */
+interface SimpleRegionSelection{
+  id: string,
+  name: string,
+  tName: string,
+  pName: string,
+  rSelected: string[]
+}
+
+interface UserConfigAction extends Action{
+  config?: Partial<UserConfigState>
+  payload?: any
+}
+
+const defaultUserConfigState: UserConfigState = {
+  savedRegionsSelection: []
+}
+
+const ACTION_TYPES = {
+  UPDATE_REGIONS_SELECTIONS: `UPDATE_REGIONS_SELECTIONS`,
+  UPDATE_REGIONS_SELECTION:'UPDATE_REGIONS_SELECTION',
+  SAVE_REGIONS_SELECTION: `SAVE_REGIONS_SELECTIONN`,
+  DELETE_REGIONS_SELECTION: 'DELETE_REGIONS_SELECTION',
+
+  LOAD_REGIONS_SELECTION: 'LOAD_REGIONS_SELECTION'
+}
+
+export const USER_CONFIG_ACTION_TYPES = ACTION_TYPES
+
+export function userConfigState(prevState: UserConfigState = defaultUserConfigState, action: UserConfigAction) {
+  switch(action.type) {
+    case ACTION_TYPES.UPDATE_REGIONS_SELECTIONS:
+      const { config = {} } = action
+      const { savedRegionsSelection } = config
+      return {
+        ...prevState,
+        savedRegionsSelection
+      }
+    default:
+      return {
+        ...prevState
+      }
+  }
+}
+
+@Injectable({
+  providedIn: 'root'
+})
+export class UserConfigStateUseEffect implements OnDestroy{
+
+  private subscriptions: Subscription[] = []
+
+  constructor(
+    private actions$: Actions,
+    private store$: Store<any>,
+    private dialogService: DialogService
+  ){
+    const viewerState$ = this.store$.pipe(
+      select('viewerState'),
+      shareReplay(1)
+    )
+
+    this.parcellationSelected$ = viewerState$.pipe(
+      select('parcellationSelected'),
+      distinctUntilChanged(),
+      share()
+    )
+
+    this.tprSelected$ = combineLatest(
+      viewerState$.pipe(
+        select('templateSelected'),
+        distinctUntilChanged()
+      ),
+      this.parcellationSelected$,
+      viewerState$.pipe(
+        select('regionsSelected'),
+        /**
+         * TODO
+         * distinct selectedRegions
+         */
+      )
+    ).pipe(
+      map(([ templateSelected, parcellationSelected, regionsSelected ]) => {
+        return {
+          templateSelected, parcellationSelected, regionsSelected
+        }
+      }),
+      shareReplay(1)
+    )
+
+    this.savedRegionsSelections$ = this.store$.pipe(
+      select('userConfigState'),
+      select('savedRegionsSelection'),
+      shareReplay(1)
+    )
+
+    this.onSaveRegionsSelection$ = this.actions$.pipe(
+      ofType(ACTION_TYPES.SAVE_REGIONS_SELECTION),
+      withLatestFrom(this.tprSelected$),
+      withLatestFrom(this.savedRegionsSelections$),
+      
+      map(([[action, tprSelected], savedRegionsSelection]) => {
+        const { payload = {} } = action as UserConfigAction
+        const { name = 'Untitled' } = payload
+
+        const { templateSelected, parcellationSelected, regionsSelected } = tprSelected
+        const newSavedRegionSelection: RegionSelection = {
+          id: Date.now().toString(),
+          name,
+          templateSelected,
+          parcellationSelected,
+          regionsSelected
+        }
+        return {
+          type: ACTION_TYPES.UPDATE_REGIONS_SELECTIONS,
+          config: {
+            savedRegionsSelection: savedRegionsSelection.concat([newSavedRegionSelection])
+          }
+        } as UserConfigAction
+      })
+    )
+
+    this.onDeleteRegionsSelection$ = this.actions$.pipe(
+      ofType(ACTION_TYPES.DELETE_REGIONS_SELECTION),
+      withLatestFrom(this.savedRegionsSelections$),
+      map(([ action, savedRegionsSelection ]) => {
+        const { payload = {} } = action as UserConfigAction
+        const { id } = payload
+        return {
+          type: ACTION_TYPES.UPDATE_REGIONS_SELECTIONS,
+          config: {
+            savedRegionsSelection: savedRegionsSelection.filter(srs => srs.id !== id)
+          }
+        }
+      })
+    )
+
+    this.onUpdateRegionsSelection$ = this.actions$.pipe(
+      ofType(ACTION_TYPES.UPDATE_REGIONS_SELECTION),
+      withLatestFrom(this.savedRegionsSelections$),
+      map(([ action, savedRegionsSelection]) => {
+        const { payload = {} } = action as UserConfigAction
+        const { id, ...rest } = payload
+        return {
+          type: ACTION_TYPES.UPDATE_REGIONS_SELECTIONS,
+          config: {
+            savedRegionsSelection: savedRegionsSelection
+              .map(srs => srs.id === id
+                ? { ...srs, ...rest }
+                : { ...srs })
+          }
+        }
+      })
+    )
+
+    this.subscriptions.push(
+      this.actions$.pipe(
+        ofType(ACTION_TYPES.LOAD_REGIONS_SELECTION),
+        map(action => {
+          const { payload = {}} = action as UserConfigAction
+          const { savedRegionsSelection } : {savedRegionsSelection : RegionSelection} = payload
+          return savedRegionsSelection
+        }),
+        filter(val => !!val),
+        withLatestFrom(this.tprSelected$),
+        switchMap(([savedRegionsSelection, { parcellationSelected, templateSelected, regionsSelected }]) => 
+          from(this.dialogService.getUserConfirm({
+            title: `Load region selection: ${savedRegionsSelection.name}`,
+            message: `This action would cause the viewer to navigate away from the current view. Proceed?`
+          })).pipe(
+            catchError((e, obs) => of(null)),
+            map(() => {
+              return {
+                savedRegionsSelection,
+                parcellationSelected,
+                templateSelected,
+                regionsSelected
+              }
+            }),
+            filter(val => !!val)
+          )
+        ),
+        switchMap(({ savedRegionsSelection, parcellationSelected, templateSelected, regionsSelected }) => {
+          if (templateSelected.name !== savedRegionsSelection.templateSelected.name ) {
+            /**
+             * template different, dispatch NEWVIEWER
+             */
+            this.store$.dispatch({
+              type: NEWVIEWER,
+              selectParcellation: savedRegionsSelection.parcellationSelected,
+              selectTemplate: savedRegionsSelection.templateSelected
+            })
+            return this.parcellationSelected$.pipe(
+              filter(p => p.updated),
+              take(1),
+              map(() => {
+                return {
+                  regionsSelected: savedRegionsSelection.regionsSelected
+                }
+              })
+            )
+          }
+  
+          if (parcellationSelected.name !== savedRegionsSelection.parcellationSelected.name) {
+            /**
+             * parcellation different, dispatch SELECT_PARCELLATION
+             */
+  
+             this.store$.dispatch({
+               type: SELECT_PARCELLATION,
+               selectParcellation: savedRegionsSelection.parcellationSelected
+             })
+            return this.parcellationSelected$.pipe(
+              filter(p => p.updated),
+              take(1),
+              map(() => {
+                return {
+                  regionsSelected: savedRegionsSelection.regionsSelected
+                }
+              })
+            )
+          }
+
+          return of({ 
+            regionsSelected: savedRegionsSelection.regionsSelected
+          })
+        })
+      ).subscribe(({ regionsSelected }) => {
+        this.store$.dispatch({
+          type: SELECT_REGIONS,
+          selectRegions: regionsSelected
+        })
+      })
+    )
+
+    this.subscriptions.push(
+      this.actions$.pipe(
+        ofType(ACTION_TYPES.UPDATE_REGIONS_SELECTIONS)
+      ).subscribe(action => {
+        const { config = {} } = action as UserConfigAction
+        const { savedRegionsSelection } = config
+        const simpleSRSs = savedRegionsSelection.map(({ id, name, templateSelected, parcellationSelected, regionsSelected }) => {
+          return {
+            id,
+            name,
+            tName: templateSelected.name,
+            pName: parcellationSelected.name,
+            rSelected: regionsSelected.map(({ ngId, labelIndex }) => generateLabelIndexId({ ngId, labelIndex }))
+          } as SimpleRegionSelection
+        })
+
+        /**
+         * TODO save server side on per user basis
+         */
+        window.localStorage.setItem(LOCAL_STORAGE_KEY.SAVED_REGION_SELECTIONS, JSON.stringify(simpleSRSs))
+      })
+    )
+
+    const savedSRSsString = window.localStorage.getItem(LOCAL_STORAGE_KEY.SAVED_REGION_SELECTIONS)
+    const savedSRSs:SimpleRegionSelection[] = savedSRSsString && JSON.parse(savedSRSsString)
+
+    this.restoreSRSsFromStorage$ = viewerState$.pipe(
+      filter(() => !!savedSRSs),
+      select('fetchedTemplates'),
+      distinctUntilChanged(),
+      map(fetchedTemplates => savedSRSs.map(({ id, name, tName, pName, rSelected }) => {
+        const templateSelected = fetchedTemplates.find(t => t.name === tName)
+        const parcellationSelected = templateSelected && templateSelected.parcellations.find(p => p.name === pName)
+        const regionsSelected = parcellationSelected && rSelected.map(labelIndexId => recursiveFindRegionWithLabelIndexId({ regions: parcellationSelected.regions, labelIndexId, inheritedNgId: parcellationSelected.ngId }))
+        return {
+          templateSelected,
+          parcellationSelected,
+          id,
+          name,
+          regionsSelected
+        } as RegionSelection
+      })),
+      filter(RSs => RSs.every(rs => rs.regionsSelected && rs.regionsSelected.every(r => !!r))),
+      take(1),
+      map(savedRegionsSelection => {
+        return {
+          type: ACTION_TYPES.UPDATE_REGIONS_SELECTIONS,
+          config: { savedRegionsSelection }
+        }
+      })
+    )
+  }
+
+  ngOnDestroy(){
+    while(this.subscriptions.length > 0) {
+      this.subscriptions.pop().unsubscribe()
+    }
+  }
+
+  /**
+   * Temmplate Parcellation Regions selected
+   */
+  private tprSelected$: Observable<{templateSelected:any, parcellationSelected: any, regionsSelected: any[]}>
+  private savedRegionsSelections$: Observable<any[]>
+  private parcellationSelected$: Observable<any>
+
+  @Effect()
+  public onSaveRegionsSelection$: Observable<any>
+
+  @Effect()
+  public onDeleteRegionsSelection$: Observable<any>
+
+  @Effect()
+  public onUpdateRegionsSelection$: Observable<any>
+
+  @Effect()
+  public restoreSRSsFromStorage$: Observable<any>
+}
+
+const LOCAL_STORAGE_KEY = {
+  SAVED_REGION_SELECTIONS: 'fzj.xg.iv.SAVED_REGION_SELECTIONS'
+}
\ No newline at end of file
diff --git a/src/services/state/viewerState.store.ts b/src/services/state/viewerState.store.ts
index 0b1edc38bcf23d9001d9554392d83c9e26789432..359cf2be6547ee998f73cf05b36b9909ed1e1ae4 100644
--- a/src/services/state/viewerState.store.ts
+++ b/src/services/state/viewerState.store.ts
@@ -1,6 +1,10 @@
-import { Action } from '@ngrx/store'
+import { Action, Store, select } from '@ngrx/store'
 import { UserLandmark } from 'src/atlasViewer/atlasViewer.apiService.service';
 import { NgLayerInterface } from 'src/atlasViewer/atlasViewer.component';
+import { Injectable } from '@angular/core';
+import { Actions, Effect, ofType } from '@ngrx/effects';
+import { withLatestFrom, map, shareReplay, startWith, tap } from 'rxjs/operators';
+import { Observable } from 'rxjs';
 
 export interface ViewerStateInterface{
   fetchedTemplates : any[]
@@ -34,13 +38,17 @@ export interface AtlasAction extends Action{
   deselectLandmarks : UserLandmark[]
 
   navigation? : any
+
+  payload: any
 }
 
 export function viewerState(
   state:Partial<ViewerStateInterface> = {
     landmarksSelected : [],
     fetchedTemplates : [],
-    loadedNgLayers: []
+    loadedNgLayers: [],
+    regionsSelected: [],
+    userLandmarks: []
   },
   action:AtlasAction
 ){
@@ -106,7 +114,10 @@ export function viewerState(
       const { updatedParcellation } = action
       return {
         ...state,
-        parcellationSelected: updatedParcellation
+        parcellationSelected: {
+          ...updatedParcellation,
+          updated: true
+        }
       }
     }
     case SELECT_REGIONS:
@@ -133,6 +144,10 @@ export function viewerState(
         userLandmarks: action.landmarks
       } 
     }
+    /**
+     * TODO
+     * duplicated with ngViewerState.layers ?
+     */
     case NEHUBA_LAYER_CHANGED: {
       if (!window['viewer']) {
         return {
@@ -167,10 +182,88 @@ export const CHANGE_NAVIGATION = 'CHANGE_NAVIGATION'
 export const SELECT_PARCELLATION = `SELECT_PARCELLATION`
 export const UPDATE_PARCELLATION = `UPDATE_PARCELLATION`
 
+export const DESELECT_REGIONS = `DESELECT_REGIONS`
 export const SELECT_REGIONS = `SELECT_REGIONS`
 export const SELECT_REGIONS_WITH_ID = `SELECT_REGIONS_WITH_ID`
 export const SELECT_LANDMARKS = `SELECT_LANDMARKS`
 export const DESELECT_LANDMARKS = `DESELECT_LANDMARKS`
 export const USER_LANDMARKS = `USER_LANDMARKS`
 
+export const ADD_TO_REGIONS_SELECTION_WITH_IDS = `ADD_TO_REGIONS_SELECTION_WITH_IDS`
+
 export const NEHUBA_LAYER_CHANGED = `NEHUBA_LAYER_CHANGED`
+
+@Injectable({
+  providedIn: 'root'
+})
+
+export class ViewerStateUseEffect{
+  constructor(
+    private actions$: Actions,
+    private store$: Store<any>
+  ){
+    this.currentLandmarks$ = this.store$.pipe(
+      select('viewerState'),
+      select('userLandmarks'),
+      shareReplay(1),
+    )
+
+    this.removeUserLandmarks = this.actions$.pipe(
+      ofType(ACTION_TYPES.REMOVE_USER_LANDMARKS),
+      withLatestFrom(this.currentLandmarks$),
+      map(([action, currentLandmarks]) => {
+        const { landmarkIds } = (action as AtlasAction).payload
+        for ( const rmId of landmarkIds ){
+          const idx = currentLandmarks.findIndex(({ id }) => id === rmId)
+          if (idx < 0) console.warn(`remove userlandmark with id ${rmId} does not exist`)
+        }
+        const removeSet = new Set(landmarkIds)
+        return {
+          type: USER_LANDMARKS,
+          landmarks: currentLandmarks.filter(({ id }) => !removeSet.has(id))
+        }
+      })
+    )
+
+    this.addUserLandmarks$ = this.actions$.pipe(
+      ofType(ACTION_TYPES.ADD_USERLANDMARKS),
+      withLatestFrom(this.currentLandmarks$),
+      map(([action, currentLandmarks]) => {
+        const { landmarks } = action as AtlasAction
+        const landmarkMap = new Map()
+        for (const landmark of currentLandmarks) {
+          const { id } = landmark
+          landmarkMap.set(id, landmark)
+        }
+        for (const landmark of landmarks) {
+          const { id } = landmark
+          if (landmarkMap.has(id)) {
+            console.warn(`Attempting to add a landmark that already exists, id: ${id}`)
+          } else {
+            landmarkMap.set(id, landmark)
+          }
+        }
+        const userLandmarks = Array.from(landmarkMap).map(([id, landmark]) => landmark)
+        return {
+          type: USER_LANDMARKS,
+          landmarks: userLandmarks
+        }
+      })
+    )
+  }
+
+  private currentLandmarks$: Observable<any[]>
+
+  @Effect()
+  removeUserLandmarks: Observable<any>
+
+  @Effect()
+  addUserLandmarks$: Observable<any>
+}
+
+const ACTION_TYPES = {
+  ADD_USERLANDMARKS: `ADD_USERLANDMARKS`,
+  REMOVE_USER_LANDMARKS: 'REMOVE_USER_LANDMARKS'
+}
+
+export const VIEWERSTATE_ACTION_TYPES = ACTION_TYPES
\ No newline at end of file
diff --git a/src/services/stateStore.service.ts b/src/services/stateStore.service.ts
index 96048ec2192d311c34a01528e4e24ee52ffa5dce..842499c18bcb4d3083a9494fab4f71ca85ca96ad 100644
--- a/src/services/stateStore.service.ts
+++ b/src/services/stateStore.service.ts
@@ -7,6 +7,11 @@ export { CHANGE_NAVIGATION, AtlasAction, DESELECT_LANDMARKS, FETCHED_TEMPLATE, N
 export { DataEntry, ParcellationRegion, DataStateInterface, DatasetAction, FETCHED_DATAENTRIES, FETCHED_SPATIAL_DATA, Landmark, OtherLandmarkGeometry, PlaneLandmarkGeometry, PointLandmarkGeometry, Property, Publication, ReferenceSpace, dataStore, File, FileSupplementData } from './state/dataStore.store'
 export { CLOSE_SIDE_PANEL, MOUSE_OVER_LANDMARK, MOUSE_OVER_SEGMENT, OPEN_SIDE_PANEL, TOGGLE_SIDE_PANEL, UIAction, UIStateInterface, uiState } from './state/uiState.store'
 export { SPATIAL_GOTO_PAGE, SpatialDataEntries, SpatialDataStateInterface, UPDATE_SPATIAL_DATA, spatialSearchState } from './state/spatialSearchState.store'
+export { userConfigState, UserConfigStateUseEffect, USER_CONFIG_ACTION_TYPES } from './state/userConfigState.store'
+
+export const GENERAL_ACTION_TYPES = {
+  ERROR: 'ERROR'
+}
 
 export function safeFilter(key:string){
   return filter((state:any)=>
diff --git a/src/theme.scss b/src/theme.scss
new file mode 100644
index 0000000000000000000000000000000000000000..e85cfdce26366235f96280ccae6f742f46218b35
--- /dev/null
+++ b/src/theme.scss
@@ -0,0 +1,21 @@
+@import '~@angular/material/theming';
+
+@include  mat-core();
+
+$iv-theme-primary: mat-palette($mat-indigo);
+$iv-theme-accent: mat-palette($mat-amber);
+$iv-theme-warn: mat-palette($mat-red);
+
+$iv-theme: mat-light-theme($iv-theme-primary, $iv-theme-accent, $iv-theme-warn);
+
+@include angular-material-theme($iv-theme);
+
+$iv-dark-theme-primary: mat-palette($mat-blue);
+$iv-dark-theme-accent:  mat-palette($mat-amber, A200, A100, A400);
+$iv-dark-theme-warn:    mat-palette($mat-red);
+$iv-dark-theme:   mat-dark-theme($iv-dark-theme-primary, $iv-dark-theme-accent, $iv-dark-theme-warn);
+
+[darktheme=true]
+{
+  @include angular-material-theme($iv-dark-theme);
+}
diff --git a/src/ui/databrowserModule/databrowser.module.ts b/src/ui/databrowserModule/databrowser.module.ts
index d272cf211d8f2b0648453c12ddccd88d7f094ce1..6c3ee1c3504db297134d6795e199cb778c18555e 100644
--- a/src/ui/databrowserModule/databrowser.module.ts
+++ b/src/ui/databrowserModule/databrowser.module.ts
@@ -25,6 +25,8 @@ import { KgSingleDatasetService } from "./kgSingleDatasetService.service"
 import { SingleDatasetView } from './singleDataset/singleDataset.component'
 import { AngularMaterialModule } from 'src/ui/sharedModules/angularMaterial.module'
 import { DoiParserPipe } from "src/util/pipes/doiPipe.pipe";
+import { DatasetIsFavedPipe } from "./util/datasetIsFaved.pipe";
+import { RegionBackgroundToRgbPipe } from "./util/regionBackgroundToRgb.pipe";
 
 @NgModule({
   imports:[
@@ -56,7 +58,9 @@ import { DoiParserPipe } from "src/util/pipes/doiPipe.pipe";
     FilterDataEntriesbyMethods,
     FilterDataEntriesByRegion,
     AggregateArrayIntoRootPipe,
-    DoiParserPipe
+    DoiParserPipe,
+    DatasetIsFavedPipe,
+    RegionBackgroundToRgbPipe
   ],
   exports:[
     DataBrowser,
diff --git a/src/ui/databrowserModule/databrowser.service.ts b/src/ui/databrowserModule/databrowser.service.ts
index 5baa496dc446a07906ffb41a99358a134d98a357..65f9a241b4e1640b4e6cdffc8938a83e84bf38c8 100644
--- a/src/ui/databrowserModule/databrowser.service.ts
+++ b/src/ui/databrowserModule/databrowser.service.ts
@@ -4,7 +4,7 @@ import { ViewerConfiguration } from "src/services/state/viewerConfig.store";
 import { select, Store } from "@ngrx/store";
 import { AtlasViewerConstantsServices } from "src/atlasViewer/atlasViewer.constantService.service";
 import { ADD_NG_LAYER, REMOVE_NG_LAYER, DataEntry, safeFilter, FETCHED_DATAENTRIES, FETCHED_SPATIAL_DATA, UPDATE_SPATIAL_DATA } from "src/services/stateStore.service";
-import { map, distinctUntilChanged, debounceTime, filter, tap, switchMap, catchError } from "rxjs/operators";
+import { map, distinctUntilChanged, debounceTime, filter, tap, switchMap, catchError, shareReplay } from "rxjs/operators";
 import { AtlasWorkerService } from "src/atlasViewer/atlasViewer.workerService.service";
 import { FilterDataEntriesByRegion } from "./util/filterDataEntriesByRegion.pipe";
 import { NO_METHODS } from "./util/filterDataEntriesByMethods.pipe";
@@ -13,6 +13,7 @@ import { DataBrowser } from "./databrowser/databrowser.component";
 import { WidgetUnit } from "src/atlasViewer/widgetUnit/widgetUnit.component";
 import { SHOW_KG_TOS } from "src/services/state/uiState.store";
 import { regionFlattener } from "src/util/regionFlattener";
+import { DATASETS_ACTIONS_TYPES } from "src/services/state/dataStore.store";
 
 const noMethodDisplayName = 'No methods described'
 
@@ -45,6 +46,8 @@ function generateToken() {
 })
 export class DatabrowserService implements OnDestroy{
 
+  public favedDataentries$: Observable<DataEntry[]>
+
   public darktheme: boolean = false
 
   public instantiatedWidgetUnits: WidgetUnit[] = []
@@ -80,6 +83,12 @@ export class DatabrowserService implements OnDestroy{
     private store: Store<ViewerConfiguration>
   ){
 
+    this.favedDataentries$ = this.store.pipe(
+      select('dataStore'),
+      select('favDataEntries'),
+      shareReplay(1)
+    )
+
     this.subscriptions.push(
       this.store.pipe(
         select('ngViewerState')
@@ -130,6 +139,9 @@ export class DatabrowserService implements OnDestroy{
       return from(fetch(`${this.constantService.backendUrl}datasets/spatialSearch/templateName/${encodedTemplateName}/bbox/${pt1.join('_')}__${pt2.join("_")}`)
         .then(res => res.json()))
     }),
+    /**
+     * TODO pipe to constantService.catchError
+     */
     catchError((err) => (console.log(err), of([])))
   )
 
@@ -194,6 +206,20 @@ export class DatabrowserService implements OnDestroy{
     this.subscriptions.forEach(s => s.unsubscribe())
   }
 
+  public saveToFav(dataentry: DataEntry){
+    this.store.dispatch({
+      type: DATASETS_ACTIONS_TYPES.FAV_DATASET,
+      payload: dataentry
+    })
+  }
+
+  public removeFromFav(dataentry: DataEntry){
+    this.store.dispatch({
+      type: DATASETS_ACTIONS_TYPES.UNFAV_DATASET,
+      payload: dataentry
+    })
+  }
+
   public fetchPreviewData(datasetName: string){
     const encodedDatasetName = encodeURI(datasetName)
     return new Promise((resolve, reject) => {
@@ -306,12 +332,6 @@ export class DatabrowserService implements OnDestroy{
   }
 
   public getModalityFromDE = getModalityFromDE
-
-  public getBackgroundColorStyleFromRegion(region:any = null){
-    return region && region.rgb
-      ? `rgb(${region.rgb.join(',')})`
-      : `white`
-  }
 }
 
 
@@ -346,6 +366,12 @@ export function getModalityFromDE(dataentries:DataEntry[]):CountedDataModality[]
   return dataentries.reduce((acc, de) => reduceDataentry(acc, de), [])
 }
 
+export function getIdFromDataEntry(dataentry: DataEntry){
+  const { id, fullId } = dataentry
+  const regex = /\/([a-zA-Z0-9\-]*?)$/.exec(fullId)
+  return (regex && regex[1]) || id
+}
+
 
 export interface CountedDataModality{
   name: string
diff --git a/src/ui/databrowserModule/databrowser.useEffect.ts b/src/ui/databrowserModule/databrowser.useEffect.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f9a57b51905c88e841669aa192463b0947d3c6be
--- /dev/null
+++ b/src/ui/databrowserModule/databrowser.useEffect.ts
@@ -0,0 +1,146 @@
+import { Injectable, OnDestroy } from "@angular/core";
+import { Store, select } from "@ngrx/store";
+import { Actions, ofType, Effect } from "@ngrx/effects";
+import { DATASETS_ACTIONS_TYPES, DataEntry } from "src/services/state/dataStore.store";
+import { Observable, of, from, merge, Subscription } from "rxjs";
+import { withLatestFrom, map, catchError, filter, switchMap, scan, share, switchMapTo, shareReplay } from "rxjs/operators";
+import { KgSingleDatasetService } from "./kgSingleDatasetService.service";
+import { getIdFromDataEntry } from "./databrowser.service";
+
+@Injectable({
+  providedIn: 'root'
+})
+
+export class DataBrowserUseEffect implements OnDestroy{
+
+  private subscriptions: Subscription[] = []
+
+  constructor(
+    private store$: Store<any>,
+    private actions$: Actions<any>,
+    private kgSingleDatasetService: KgSingleDatasetService
+    
+  ){
+    this.favDataEntries$ = this.store$.pipe(
+      select('dataStore'),
+      select('favDataEntries')
+    )
+
+    this.unfavDataset$ = this.actions$.pipe(
+      ofType(DATASETS_ACTIONS_TYPES.UNFAV_DATASET),
+      withLatestFrom(this.favDataEntries$),
+      map(([action, prevFavDataEntries]) => {
+
+        const { payload = {} } = action as any
+        const { id } = payload
+        return {
+          type: DATASETS_ACTIONS_TYPES.UPDATE_FAV_DATASETS,
+          favDataEntries: prevFavDataEntries.filter(ds => ds.id !== id)
+        }
+      })
+    )
+
+    this.favDataset$ = this.actions$.pipe(
+      ofType(DATASETS_ACTIONS_TYPES.FAV_DATASET),
+      withLatestFrom(this.favDataEntries$),
+      map(([ action, prevFavDataEntries ]) => {
+        const { payload } = action as any
+    
+        /**
+         * check duplicate
+         */
+        const favDataEntries = prevFavDataEntries.find(favDEs => favDEs.id === payload.id)
+          ? prevFavDataEntries
+          : prevFavDataEntries.concat(payload)
+    
+        return {
+          type: DATASETS_ACTIONS_TYPES.UPDATE_FAV_DATASETS,
+          favDataEntries
+        }
+      })
+    )
+
+
+    this.subscriptions.push(
+      merge(
+        this.favDataset$,
+        this.unfavDataset$
+      ).pipe(
+        switchMapTo(this.favDataEntries$)
+      ).subscribe(favDataEntries => {
+        /**
+         * only store the minimal data in localstorage/db, hydrate when needed
+         * for now, only save id 
+         * 
+         * do not save anything else on localstorage. This could potentially be leaking sensitive information
+         */
+        const serialisedFavDataentries = favDataEntries.map(dataentry => {
+          const id = getIdFromDataEntry(dataentry)
+          return { id }
+        })
+        window.localStorage.setItem(LOCAL_STORAGE_CONST.FAV_DATASET, JSON.stringify(serialisedFavDataentries))
+      })
+    )
+
+    this.savedFav$ = of(window.localStorage.getItem(LOCAL_STORAGE_CONST.FAV_DATASET)).pipe(
+      map(string => JSON.parse(string)),
+      map(arr => {
+        if (arr.every(item => item.id )) return arr
+        throw new Error('Not every item has id and/or name defined')
+      }),
+      catchError(err => {
+        /**
+         * TODO emit proper error
+         * possibly wipe corrupted local stoage here?
+         */
+        return null
+      })
+    )
+
+    this.onInitGetFav$ = this.savedFav$.pipe(
+      filter(v => !!v),
+      switchMap(arr => 
+        merge(
+          ...arr.map(({ id: kgId }) => 
+            from( this.kgSingleDatasetService.getInfoFromKg({ kgId }))
+              .pipe(catchError(err => {
+                  console.log(`fetchInfoFromKg error`, err)
+                  return null
+              })))
+        ).pipe(
+          filter(v => !!v),
+          scan((acc, curr) => acc.concat(curr), [])
+        )
+      ),
+      map(favDataEntries => {
+        return {
+          type: DATASETS_ACTIONS_TYPES.UPDATE_FAV_DATASETS,
+          favDataEntries
+        }
+      })
+    )
+  }
+
+  ngOnDestroy(){
+    while(this.subscriptions.length > 0) {
+      this.subscriptions.pop().unsubscribe()
+    }
+  }
+
+  private savedFav$: Observable<{id: string, name: string}[] | null>
+
+  @Effect()
+  public onInitGetFav$: Observable<any>
+
+  private favDataEntries$: Observable<DataEntry[]>
+
+  @Effect()
+  public favDataset$: Observable<any>
+
+  @Effect()
+  public unfavDataset$: Observable<any>
+}
+
+const LOCAL_STORAGE_CONST = {
+  FAV_DATASET: 'fzj.xg.iv.FAV_DATASET'
+}
\ No newline at end of file
diff --git a/src/ui/databrowserModule/databrowser/databrowser.component.ts b/src/ui/databrowserModule/databrowser/databrowser.component.ts
index b3568e039e3be5c046e2571e2d014ed1cac1877d..3b9c6ace0ddeecca61f7fd57fadb5eaa940576d7 100644
--- a/src/ui/databrowserModule/databrowser/databrowser.component.ts
+++ b/src/ui/databrowserModule/databrowser/databrowser.component.ts
@@ -1,6 +1,6 @@
 import { Component, OnDestroy, OnInit, ViewChild, Input } from "@angular/core";
 import { DataEntry } from "src/services/stateStore.service";
-import { Subscription, merge } from "rxjs";
+import { Subscription, merge, Observable } from "rxjs";
 import { DatabrowserService, CountedDataModality } from "../databrowser.service";
 import { ModalityPicker } from "../modalityPicker/modalityPicker.component";
 
@@ -14,6 +14,8 @@ import { ModalityPicker } from "../modalityPicker/modalityPicker.component";
 
 export class DataBrowser implements OnDestroy,OnInit{
 
+  public favedDataentries$: Observable<DataEntry[]>
+
   @Input()
   public regions: any[] = []
 
@@ -55,7 +57,7 @@ export class DataBrowser implements OnDestroy,OnInit{
   constructor(
     private dbService: DatabrowserService
   ){
-
+    this.favedDataentries$ = this.dbService.favedDataentries$
   }
 
   ngOnInit(){
@@ -136,6 +138,14 @@ export class DataBrowser implements OnDestroy,OnInit{
     this.dbService.manualFetchDataset$.next(null)
   }
 
+  saveToFavourite(dataset: DataEntry){
+    this.dbService.saveToFav(dataset)
+  }
+
+  removeFromFavourite(dataset: DataEntry){
+    this.dbService.removeFromFav(dataset)
+  }
+
   public showParcellationList: boolean = false
   
   public filePreviewName: string
@@ -155,10 +165,6 @@ export class DataBrowser implements OnDestroy,OnInit{
   resetFilters(event?:MouseEvent){
     this.clearAll()
   }
-
-  getBackgroundColorStyleFromRegion(region:any) {
-    return this.dbService.getBackgroundColorStyleFromRegion(region)
-  }
 }
 
 export interface DataEntryFilter{
diff --git a/src/ui/databrowserModule/databrowser/databrowser.template.html b/src/ui/databrowserModule/databrowser/databrowser.template.html
index 515a94d8331c6235c7ef253d927c7d159ba597ea..060a49ad118bbaea29a100e86db11bc0b128d144 100644
--- a/src/ui/databrowserModule/databrowser/databrowser.template.html
+++ b/src/ui/databrowserModule/databrowser/databrowser.template.html
@@ -16,7 +16,7 @@
           <span
             *ngFor="let region of regions"
             class="badge badge-secondary mr-1 mw-100">
-            <span [ngStyle]="{backgroundColor:getBackgroundColorStyleFromRegion(region)}" class="dot">
+            <span [ngStyle]="{backgroundColor: (region | regionBackgroundToRgbPipe)}" class="dot">
               
             </span>
             <span class="d-inline-block mw-100 overflow-hidden text-truncate">
@@ -114,15 +114,18 @@
         <dataset-viewer
           class="mt-1"
           *ngFor="let dataset of filteredDataEntry | searchResultPagination : currentPage : hitsPerPage"
+          (saveToFavourite)="saveToFavourite(dataset)"
+          (removeFromFavourite)="removeFromFavourite(dataset)"
           (showPreviewDataset)="onShowPreviewDataset($event)"
-          [dataset]="dataset">
+          [dataset]="dataset"
+          [isFaved]="favedDataentries$ | async | datasetIsFaved : dataset">
           <div regionTagsContainer>
 
             <!-- TODO may want to separate the region badge into a separate component -->
             <span
               *ngFor="let region of dataset.parcellationRegion"
               class="badge badge-secondary mr-1 mw-100">
-              <span [ngStyle]="{backgroundColor:getBackgroundColorStyleFromRegion(region)}" class="dot">
+              <span [ngStyle]="{backgroundColor:(region | regionBackgroundToRgbPipe)}" class="dot">
                 
               </span>
               <span class="d-inline-block mw-100 overflow-hidden text-truncate">
diff --git a/src/ui/databrowserModule/datasetViewer/datasetViewer.component.ts b/src/ui/databrowserModule/datasetViewer/datasetViewer.component.ts
index 7be8d563234f7e854b1bda067e07a1a758471718..aea172ffcc434d3fef4bc11c479ef2333a0e448c 100644
--- a/src/ui/databrowserModule/datasetViewer/datasetViewer.component.ts
+++ b/src/ui/databrowserModule/datasetViewer/datasetViewer.component.ts
@@ -9,6 +9,7 @@ import { DataEntry } from "src/services/stateStore.service";
 
 export class DatasetViewerComponent{
   @Input() dataset : DataEntry
+  @Input() isFaved: boolean
   
   @Output() showPreviewDataset: EventEmitter<{datasetName:string, event:MouseEvent}> = new EventEmitter()
   @ViewChild('kgrRef', {read:ElementRef}) kgrRef: ElementRef
@@ -39,4 +40,21 @@ export class DatasetViewerComponent{
   get kgReference(): string[] {
     return this.dataset.kgReference.map(ref => `https://doi.org/${ref}`)
   }
+
+  /**
+   * Dummy functions, the store.dispatch is the important function
+   */
+  @Output()
+  saveToFavourite: EventEmitter<boolean> = new EventEmitter()
+
+  @Output()
+  removeFromFavourite: EventEmitter<boolean> = new EventEmitter()
+
+  saveToFav(){
+    this.saveToFavourite.emit()
+  }
+
+  removeFromFav(){
+    this.removeFromFavourite.emit()
+  }
 }
\ No newline at end of file
diff --git a/src/ui/databrowserModule/datasetViewer/datasetViewer.template.html b/src/ui/databrowserModule/datasetViewer/datasetViewer.template.html
index c1248259e4ac13b17b3395b63ef41d1d3396b277..6b19fd16aa4248e039dfdb96a9ef8ca7af1f8fc5 100644
--- a/src/ui/databrowserModule/datasetViewer/datasetViewer.template.html
+++ b/src/ui/databrowserModule/datasetViewer/datasetViewer.template.html
@@ -30,6 +30,13 @@
     [hoverable]="{translateY:-3}">
     <i class="fas fa-eye"></i>
   </div>
+
+  <div
+    (click)="isFaved ? removeFromFav() : saveToFav()"
+    [class]="(isFaved ? 'text-primary' : 'text-muted') + ' ds-container ml-1 p-2 preview-container d-flex align-items-center'"
+    [hoverable]="{translateY:-3}">
+    <i class="fas fa-star"></i>
+  </div>
 </div>
 
 <ng-template #defaultDisplay>
diff --git a/src/ui/databrowserModule/kgSingleDatasetService.service.ts b/src/ui/databrowserModule/kgSingleDatasetService.service.ts
index 31738eee7c4f55724f35c36dae69372b629b2b18..551b2cf34d1b2c7f70b93e085371c910c0ef35bf 100644
--- a/src/ui/databrowserModule/kgSingleDatasetService.service.ts
+++ b/src/ui/databrowserModule/kgSingleDatasetService.service.ts
@@ -7,7 +7,7 @@ export class KgSingleDatasetService {
   constructor(private constantService: AtlasViewerConstantsServices) {
   }
 
-  public getInfoFromKg({ kgId, kgSchema }: KgQueryInterface) {
+  public getInfoFromKg({ kgId, kgSchema = 'minds/core/dataset/v1.0.0' }: Partial<KgQueryInterface>) {
     const _url = new URL(`${this.constantService.backendUrl}datasets/kgInfo`)
     const searchParam = _url.searchParams
     searchParam.set('kgSchema', kgSchema)
@@ -19,7 +19,7 @@ export class KgSingleDatasetService {
       })
   }
 
-  public downloadZipFromKg({ kgSchema, kgId } : KgQueryInterface, filename = 'download'){
+  public downloadZipFromKg({ kgSchema = 'minds/core/dataset/v1.0.0', kgId } : Partial<KgQueryInterface>, filename = 'download'){
     const _url = new URL(`${this.constantService.backendUrl}datasets/downloadKgFiles`)
     const searchParam = _url.searchParams
     searchParam.set('kgSchema', kgSchema)
diff --git a/src/ui/databrowserModule/util/datasetIsFaved.pipe.ts b/src/ui/databrowserModule/util/datasetIsFaved.pipe.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2befe6fed9e1be922ab987d998a8a274ceb2d3d6
--- /dev/null
+++ b/src/ui/databrowserModule/util/datasetIsFaved.pipe.ts
@@ -0,0 +1,11 @@
+import { PipeTransform, Pipe } from "@angular/core";
+import { DataEntry } from "src/services/stateStore.service";
+
+@Pipe({
+  name: 'datasetIsFaved'
+})
+export class DatasetIsFavedPipe implements PipeTransform{
+  public transform(favedDataEntry: DataEntry[], dataentry: DataEntry):boolean{
+    return favedDataEntry.findIndex(ds => ds.id === dataentry.id) >= 0
+  }
+}
\ No newline at end of file
diff --git a/src/ui/databrowserModule/util/regionBackgroundToRgb.pipe.ts b/src/ui/databrowserModule/util/regionBackgroundToRgb.pipe.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4a03cd9dc7230bfbab69b5a4f19866e249914c80
--- /dev/null
+++ b/src/ui/databrowserModule/util/regionBackgroundToRgb.pipe.ts
@@ -0,0 +1,13 @@
+import { Pipe, PipeTransform } from "@angular/core";
+
+@Pipe({
+  name: 'regionBackgroundToRgbPipe'
+})
+
+export class RegionBackgroundToRgbPipe implements PipeTransform{
+  public transform(region = null): string{
+    return region && region.rgb
+      ? `rgb(${region.rgb.join(',')})`
+      : 'white'
+  }
+}
\ No newline at end of file
diff --git a/src/ui/layerbrowser/layerbrowser.component.ts b/src/ui/layerbrowser/layerbrowser.component.ts
index 18c2cb75b7971ea8e4a7e3a15c9fa4787e49308f..35178d86ea0c6322ccf0f799cc2eee64c7c701ae 100644
--- a/src/ui/layerbrowser/layerbrowser.component.ts
+++ b/src/ui/layerbrowser/layerbrowser.component.ts
@@ -1,9 +1,9 @@
-import { Component,  OnDestroy } from "@angular/core";
+import { Component,  OnDestroy, Input, Pipe, PipeTransform } from "@angular/core";
 import { NgLayerInterface } from "../../atlasViewer/atlasViewer.component";
 import { Store, select } from "@ngrx/store";
 import { ViewerStateInterface, isDefined, REMOVE_NG_LAYER, FORCE_SHOW_SEGMENT, safeFilter, getNgIds } from "../../services/stateStore.service";
-import { Subscription, Observable } from "rxjs";
-import { filter, map } from "rxjs/operators";
+import { Subscription, Observable, combineLatest } from "rxjs";
+import { filter, map, shareReplay, tap } from "rxjs/operators";
 import { AtlasViewerConstantsServices } from "src/atlasViewer/atlasViewer.constantService.service";
 
 @Component({
@@ -20,13 +20,15 @@ export class LayerBrowser implements OnDestroy{
   /**
    * TODO make untangle nglayernames and its dependency on ng
    */
-  loadedNgLayers$: Observable<NgLayerInterface[]>
-  lockedLayers : string[] = []
+  public loadedNgLayers$: Observable<NgLayerInterface[]>
+  public lockedLayers : string[] = []
+
+  public nonBaseNgLayers$: Observable<NgLayerInterface[]>
 
   public forceShowSegmentCurrentState : boolean | null = null
   public forceShowSegment$ : Observable<boolean|null>
   
-  public ngLayers$: Observable<any>
+  public ngLayers$: Observable<string[]>
   public advancedMode: boolean = false
 
   private subscriptions : Subscription[] = []
@@ -35,6 +37,11 @@ export class LayerBrowser implements OnDestroy{
   /* TODO temporary measure. when datasetID can be used, will use  */
   public fetchedDataEntries$ : Observable<any>
 
+  @Input()
+  showPlaceholder: boolean = true
+
+  darktheme$: Observable<boolean>
+
   constructor(
     private store : Store<ViewerStateInterface>,
     private constantsService: AtlasViewerConstantsServices){
@@ -64,6 +71,22 @@ export class LayerBrowser implements OnDestroy{
        */
       map(arr => arr.filter(v => !!v))
     )
+
+    this.loadedNgLayers$ = this.store.pipe(
+      select('viewerState'),
+      select('loadedNgLayers')
+    )
+
+    this.nonBaseNgLayers$ = combineLatest(
+      this.ngLayers$,
+      this.loadedNgLayers$
+    ).pipe(
+      map(([baseNgLayerNames, loadedNgLayers]) => {
+        const baseNameSet = new Set(baseNgLayerNames)
+        return loadedNgLayers.filter(l => !baseNameSet.has(l.name))
+      })
+    )
+
     /**
      * TODO
      * this is no longer populated
@@ -80,9 +103,9 @@ export class LayerBrowser implements OnDestroy{
       map(state => state.forceShowSegment)
     )
 
-    this.loadedNgLayers$ = this.store.pipe(
-      select('viewerState'),
-      select('loadedNgLayers')
+
+    this.darktheme$ = this.constantsService.darktheme$.pipe(
+      shareReplay(1)
     )
 
     this.subscriptions.push(
@@ -128,6 +151,9 @@ export class LayerBrowser implements OnDestroy{
       return
     }
 
+    /**
+     * TODO perhaps useEffects ?
+     */
     this.store.dispatch({
       type : FORCE_SHOW_SEGMENT,
       forceShowSegment : this.forceShowSegmentCurrentState === null
@@ -151,6 +177,9 @@ export class LayerBrowser implements OnDestroy{
     })
   }
 
+  /**
+   * TODO use observable and pipe to make this more perf
+   */
   segmentationTooltip(){
     return `toggle segments visibility: 
     ${this.forceShowSegmentCurrentState === true ? 'always show' : this.forceShowSegmentCurrentState === false ? 'always hide' : 'auto'}`
@@ -169,4 +198,16 @@ export class LayerBrowser implements OnDestroy{
   get isMobile(){
     return this.constantsService.mobile
   }
+
+  public matTooltipPosition: string = 'below'
 }
+
+@Pipe({
+  name: 'lockedLayerBtnClsPipe'
+})
+
+export class LockedLayerBtnClsPipe implements PipeTransform{
+  public transform(ngLayer:NgLayerInterface, lockedLayers?: string[]): boolean{
+    return (lockedLayers && new Set(lockedLayers).has(ngLayer.name)) || false
+  }
+}
\ No newline at end of file
diff --git a/src/ui/layerbrowser/layerbrowser.style.css b/src/ui/layerbrowser/layerbrowser.style.css
index 83bf14bb66993088b1d86f607e24784f07926bd4..495211b5c91f0746e1c4d522c18a8d3dfdb3a835 100644
--- a/src/ui/layerbrowser/layerbrowser.style.css
+++ b/src/ui/layerbrowser/layerbrowser.style.css
@@ -16,11 +16,6 @@ div[body]
   background-color:rgba(0, 0, 0, 0.1);
 }
 
-.muted-text
-{
-  text-decoration: line-through;
-}
-
 .layerContainer
 {
   display: flex;
diff --git a/src/ui/layerbrowser/layerbrowser.template.html b/src/ui/layerbrowser/layerbrowser.template.html
index 6ac015c163020f210e8adce0b33e9da34d9be011..b5d5c8e45f37ff1e3ca3fe0de68e1edce3a14c1c 100644
--- a/src/ui/layerbrowser/layerbrowser.template.html
+++ b/src/ui/layerbrowser/layerbrowser.template.html
@@ -1,71 +1,59 @@
-<ng-container *ngIf="ngLayers$ | async | filterNgLayer : (loadedNgLayers$ | async) as filteredNgLayers; else noLayerPlaceHolder">
-  <ng-container *ngIf="filteredNgLayers.length > 0; else noLayerPlaceHolder">
-    <div
-      class="layerContainer overflow-hidden"
-      *ngFor = "let ngLayer of filteredNgLayers">
-    
+<ng-container *ngIf="nonBaseNgLayers$ | async as nonBaseNgLayers; else noLayerPlaceHolder">
+  <mat-list *ngIf="nonBaseNgLayers.length > 0; else noLayerPlaceHolder">
+    <mat-list-item *ngFor="let ngLayer of nonBaseNgLayers">
+
       <!-- toggle visibility -->
-      <div class="btnWrapper">
-        <div
-          container = "body"
-          placement = "bottom"
-          [tooltip] = "checkLocked(ngLayer) ? 'base layer cannot be hidden' : 'toggle visibility'"
-          (click) = "checkLocked(ngLayer) ? null : toggleVisibility(ngLayer)"
-          class="btn btn-sm btn-outline-secondary rounded-circle">
-          <i [ngClass] = "checkLocked(ngLayer) ? 'fas fa-lock muted' :ngLayer.visible ? 'far fa-eye' : 'far fa-eye-slash'">
-          </i>
-        </div>
-      </div>
+      
+      <button
+        [matTooltipPosition]="matTooltipPosition"
+        [matTooltip]="(ngLayer | lockedLayerBtnClsPipe : lockedLayers) ? 'base layer cannot be hidden' : 'toggle visibility'"
+        (click)="toggleVisibility(ngLayer)"
+        mat-icon-button
+        [disabled]="ngLayer | lockedLayerBtnClsPipe : lockedLayers"
+        [color]="ngLayer.visible ? 'primary' : null">
+        <i [ngClass]="(ngLayer | lockedLayerBtnClsPipe : lockedLayers) ? 'fas fa-lock muted' : ngLayer.visible ? 'far fa-eye' : 'far fa-eye-slash'">
+        </i>
+      </button>
 
       <!-- advanced mode only: toggle force show segmentation -->
-      <div class="btnWrapper">
-        <div
-          *ngIf="advancedMode"
-          container="body"
-          placement="bottom"
-          [tooltip]="ngLayer.type === 'segmentation' ? segmentationTooltip() : 'only segmentation layer can hide/show segments'"
-          #forceSegment="bs-tooltip"
-          (click)="forceSegment.hide();toggleForceShowSegment(ngLayer)"
-          class="btn btn-sm btn-outline-secondary rounded-circle">
-          <i 
-            class="fas" 
-            [ngClass]="ngLayer.type === 'segmentation' ? ('fa-th-large ' + segmentationAdditionalClass) : 'fa-lock muted' ">
-      
-          </i>
-        </div>
-      </div>
+      <button
+        *ngIf="advancedMode"
+        [matTooltipPosition]="matTooltipPosition"
+        [matTooltip]="ngLayer.type === 'segmentation' ? segmentationTooltip() : 'only segmentation layer can hide/show segments'"
+        (click)="toggleForceShowSegment(ngLayer)"
+        mat-icon-button>
+        <i 
+          class="fas" 
+          [ngClass]="ngLayer.type === 'segmentation' ? ('fa-th-large ' + segmentationAdditionalClass) : 'fa-lock muted' ">
+    
+        </i>
+      </button>
 
       <!-- remove layer -->
-      <div class="btnWrapper">
-        <div
-          container="body"
-          placement="bottom"
-          [tooltip]="checkLocked(ngLayer) ? 'base layers cannot be removed' : 'remove layer'"
-          (click)="removeLayer(ngLayer)"
-          class="btn btn-sm btn-outline-secondary rounded-circle">
-          <i [ngClass]="checkLocked(ngLayer) ? 'fas fa-lock muted' : 'far fa-times-circle'">
-          </i>
-        </div>
-      </div>
+      <button
+        color="warn"
+        mat-icon-button
+        (click)="removeLayer(ngLayer)"
+        [disabled]="ngLayer | lockedLayerBtnClsPipe : lockedLayers"
+        [matTooltip]="(ngLayer | lockedLayerBtnClsPipe : lockedLayers) ? 'base layers cannot be removed' : 'remove layer'">
+        <i [class]="(ngLayer | lockedLayerBtnClsPipe : lockedLayers) ? 'fas fa-lock muted' : 'fas fa-trash'">
+        </i>
+      </button>
 
       <!-- layer description -->
-      <panel-component [ngClass]="{'muted-text muted' : !classVisible(ngLayer)}">
-    
-        <div heading>
-          {{ ngLayer.name | getLayerNameFromDatasets : (fetchedDataEntries$ | async) }}
-        </div>
-    
-        <div bodyy>
-          {{ ngLayer.source }}
-        </div>
-      </panel-component>
-    </div>
-  </ng-container>
+      <div
+        [matTooltipPosition]="matTooltipPosition"
+        [matTooltip]="ngLayer.name | getFilenamePipe "
+        [class]="((darktheme$ | async) ? 'text-light' : 'text-dark') + ' text-truncate'">
+        {{ ngLayer.name | getFilenamePipe | getFileExtension }}
+      </div>
+    </mat-list-item>
+  </mat-list>
 </ng-container>
 
 <!-- fall back when no layers are showing -->
 <ng-template #noLayerPlaceHolder>
-  <h5 class="noLayerPlaceHolder text-muted">
+  <small *ngIf="showPlaceholder" class="noLayerPlaceHolder text-muted">
     No additional layers added.
-  </h5>
+  </small>
 </ng-template>
\ No newline at end of file
diff --git a/src/ui/menuicons/menuicons.component.ts b/src/ui/menuicons/menuicons.component.ts
index c488a4e5253d88b9e1b5bea7a3abfa438952ac6a..3ff7f8b1db939b83b0fb91d7f057aa84841c0a04 100644
--- a/src/ui/menuicons/menuicons.component.ts
+++ b/src/ui/menuicons/menuicons.component.ts
@@ -1,15 +1,17 @@
-import { Component, ComponentRef, Injector, ComponentFactory, ComponentFactoryResolver, AfterViewInit } from "@angular/core";
+import { Component, ComponentRef, Injector, ComponentFactory, ComponentFactoryResolver } from "@angular/core";
 
 import { WidgetServices } from "src/atlasViewer/widgetUnit/widgetService.service";
 import { WidgetUnit } from "src/atlasViewer/widgetUnit/widgetUnit.component";
-import { LayerBrowser } from "src/ui/layerbrowser/layerbrowser.component";
 import { DataBrowser } from "src/ui/databrowserModule/databrowser/databrowser.component";
 import { PluginBannerUI } from "../pluginBanner/pluginBanner.component";
 import { AtlasViewerConstantsServices } from "src/atlasViewer/atlasViewer.constantService.service";
 import { DatabrowserService } from "../databrowserModule/databrowser.service";
-import { PluginServices } from "src/atlasViewer/atlasViewer.pluginService.service";
+import { PluginServices, PluginManifest } from "src/atlasViewer/atlasViewer.pluginService.service";
 import { Store, select } from "@ngrx/store";
-import { Observable } from "rxjs";
+import { Observable, BehaviorSubject, combineLatest, merge, of } from "rxjs";
+import { map, shareReplay, startWith } from "rxjs/operators";
+import { DESELECT_REGIONS, SELECT_REGIONS, CHANGE_NAVIGATION } from "src/services/state/viewerState.store";
+import { ToastService } from "src/services/toastService.service";
 
 @Component({
   selector: 'menu-icons',
@@ -22,6 +24,8 @@ import { Observable } from "rxjs";
 
 export class MenuIconsBar{
 
+  public badgetPosition: string = 'above before'
+
   /**
    * databrowser
    */
@@ -29,13 +33,6 @@ export class MenuIconsBar{
   dataBrowser: ComponentRef<DataBrowser> = null
   dbWidget: ComponentRef<WidgetUnit> = null
 
-  /**
-   * layerBrowser
-   */
-  lbcf: ComponentFactory<LayerBrowser>
-  layerBrowser: ComponentRef<LayerBrowser> = null
-  lbWidget: ComponentRef<WidgetUnit> = null
-
   /**
    * pluginBrowser
    */
@@ -43,11 +40,24 @@ export class MenuIconsBar{
   pluginBanner: ComponentRef<PluginBannerUI> = null
   pbWidget: ComponentRef<WidgetUnit> = null
 
-  get isMobile(){
-    return this.constantService.mobile
+  isMobile: boolean = false
+  mobileRespBtnClass: string
+
+  public darktheme$: Observable<boolean>
+
+  public themedBtnClass$: Observable<string>
+
+  public skeletonBtnClass$: Observable<string>
+
+  public toolBtnClass$: Observable<string>
+  public getKgSearchBtnCls$: Observable<[Set<WidgetUnit>, string]>
+  
+  get darktheme(){
+    return this.constantService.darktheme
   }
 
   public selectedTemplate$: Observable<any>
+  public selectedRegions$: Observable<any[]>
 
   constructor(
     private widgetServices:WidgetServices,
@@ -56,19 +66,63 @@ export class MenuIconsBar{
     public dbService: DatabrowserService,
     cfr: ComponentFactoryResolver,
     public pluginServices:PluginServices,
-    store: Store<any>
+    private store: Store<any>,
+    private toastService: ToastService
   ){
 
+    this.isMobile = this.constantService.mobile
+    this.mobileRespBtnClass = this.constantService.mobile ? 'btn-lg' : 'btn-sm'
+
     this.dbService.createDatabrowser = this.clickSearch.bind(this)
 
     this.dbcf = cfr.resolveComponentFactory(DataBrowser)
-    this.lbcf = cfr.resolveComponentFactory(LayerBrowser)
     this.pbcf = cfr.resolveComponentFactory(PluginBannerUI)
 
     this.selectedTemplate$ = store.pipe(
       select('viewerState'),
       select('templateSelected')
     )
+
+    this.selectedRegions$ = store.pipe(
+      select('viewerState'),
+      select('regionsSelected'),
+      startWith([]),
+      shareReplay(1)
+    )
+
+    this.themedBtnClass$ = this.constantService.darktheme$.pipe(
+      map(flag => flag ? 'btn-dark' : 'btn-light' ),
+      shareReplay(1)
+    )
+
+    this.skeletonBtnClass$ = this.constantService.darktheme$.pipe(
+      map(flag => `${this.mobileRespBtnClass} ${flag ? 'text-light' : 'text-dark'}`),
+      shareReplay(1)
+    )
+
+    this.launchedPlugins$ = this.pluginServices.launchedPlugins$.pipe(
+      map(set => Array.from(set)),
+      shareReplay(1)
+    )
+
+    /**
+     * TODO remove dependency on themedBtnClass$
+     */
+    this.getPluginBtnClass$ = combineLatest(
+      this.pluginServices.launchedPlugins$,
+      this.pluginServices.minimisedPlugins$,
+      this.themedBtnClass$
+    )
+
+    this.darktheme$ = this.constantService.darktheme$
+
+    /**
+     * TODO remove dependency on themedBtnClass$
+     */
+    this.getKgSearchBtnCls$ = combineLatest(
+      this.widgetServices.minimisedWindow$,
+      this.themedBtnClass$
+    )
   }
 
   /**
@@ -98,36 +152,8 @@ export class MenuIconsBar{
   }
 
   public catchError(e) {
-    
+    this.constantService.catchError(e)
   }
-
-  public clickLayer(event: MouseEvent){
-
-    if (this.lbWidget) {
-      this.lbWidget.destroy()
-      this.lbWidget = null
-      return
-    }
-    this.layerBrowser = this.lbcf.create(this.injector)
-    this.lbWidget = this.widgetServices.addNewWidget(this.layerBrowser, {
-      exitable: true,
-      persistency: true,
-      state: 'floating',
-      title: 'Layer Browser',
-      titleHTML: '<i class="fas fa-layer-group"></i> Layer Browser'
-    })
-
-    this.lbWidget.onDestroy(() => {
-      this.layerBrowser = null
-      this.lbWidget = null
-    })
-
-    const el = event.currentTarget as HTMLElement
-    const top = el.offsetTop
-    const left = el.offsetLeft + 50
-    this.lbWidget.instance.position = [left, top]
-  }
-
   public clickPlugins(event: MouseEvent){
     if(this.pbWidget) {
       this.pbWidget.destroy()
@@ -154,19 +180,32 @@ export class MenuIconsBar{
     this.pbWidget.instance.position = [left, top]
   }
 
-  get databrowserIsShowing() {
-    return this.dataBrowser !== null
+  public clickPluginIcon(manifest: PluginManifest){
+    this.pluginServices.launchPlugin(manifest)
+      .catch(this.constantService.catchError)
   }
 
-  get layerbrowserIsShowing() {
-    return this.layerBrowser !== null
+  public searchIconClickHandler(wu: WidgetUnit){
+    if (this.widgetServices.isMinimised(wu)) {
+      this.widgetServices.unminimise(wu)
+    } else {
+      this.widgetServices.minimise(wu)
+    }
   }
 
-  get pluginbrowserIsShowing() {
-    return this.pluginBanner !== null
+  public closeWidget(event: MouseEvent, wu:WidgetUnit){
+    event.stopPropagation()
+    this.widgetServices.exitWidget(wu)
   }
 
-  get dataBrowserTitle() {
-    return `Browse`
+  public renameKgSearchWidget(event:MouseEvent, wu: WidgetUnit) {
+    event.stopPropagation()
   }
+
+  public favKgSearch(event: MouseEvent, wu: WidgetUnit) {
+    event.stopPropagation()
+  }
+
+  public getPluginBtnClass$: Observable<[Set<string>, Set<string>, string]>
+  public launchedPlugins$: Observable<string[]>
 }
\ No newline at end of file
diff --git a/src/ui/menuicons/menuicons.style.css b/src/ui/menuicons/menuicons.style.css
index 8a1663369fecdd67c4c7c70ee73774316f49e430..c76c706c7151f5276eaba98168489ee4bfc82dc5 100644
--- a/src/ui/menuicons/menuicons.style.css
+++ b/src/ui/menuicons/menuicons.style.css
@@ -20,4 +20,19 @@
 :host >>> .tooltip.right .tooltip-arrow
 {
   border-right-color: rgba(128, 128, 128, 0.5);
+}
+
+.soh-row > *:not(:first-child)
+{
+  margin-left: 0.1em;
+}
+
+.soh-column > *:not(:first-child)
+{
+  margin-top: 0.1em;
+}
+
+layer-browser
+{
+  max-width: 20em;
 }
\ No newline at end of file
diff --git a/src/ui/menuicons/menuicons.template.html b/src/ui/menuicons/menuicons.template.html
index 214281632fa62839e0a9d3d0fa0b27e10ec18cbf..00b4065a9219b171ebc38c8df0dce6d503eaa460 100644
--- a/src/ui/menuicons/menuicons.template.html
+++ b/src/ui/menuicons/menuicons.template.html
@@ -3,94 +3,309 @@
 
 <!-- hide icons when templates has yet been selected -->
 <ng-template [ngIf]="selectedTemplate$ | async">
-  <div
-    *ngIf="false"
-    [ngClass]="isMobile ? 'btnWrapper-lg' : ''"
-    class="btnWrapper">
+
+  <!-- layer browser -->
+  <sleight-of-hand>
+    <div sleight-of-hand-front>
+      <button
+        [matBadge]="layerBrowser && (layerBrowser.nonBaseNgLayers$ | async)?.length > 0 ? (layerBrowser.nonBaseNgLayers$ | async)?.length : null"
+        [matBadgePosition]="badgetPosition"
+        matBadgeColor="accent"
+        color="primary"
+        mat-icon-button>
+        <i class="fas fa-layer-group"></i>
+      </button>
+    </div>
     <div
-      [tooltip]="dataBrowserTitle"
-      placement="right"
-      (click)="clickSearch($event)"
-      [ngClass]="databrowserIsShowing ? 'btn-primary' : 'btn-secondary'"
-      class="shadow btn btn-sm rounded-circle">
-      <i class="fas fa-search">
-        
-      </i>
+      class="d-flex flex-row align-items-center soh-row"
+      sleight-of-hand-back>
+
+      <button
+        [matBadge]="layerBrowser && (layerBrowser.nonBaseNgLayers$ | async)?.length > 0 ? (layerBrowser.nonBaseNgLayers$ | async)?.length : null"
+        [matBadgePosition]="badgetPosition"
+        matBadgeColor="accent"
+        color="primary"
+        mat-icon-button>
+        <i class="fas fa-layer-group"></i>
+      </button>
+
+      <div class="position-relative">
+
+        <div [ngClass]="{'invisible pe-none': (layerBrowser.nonBaseNgLayers$ | async).length === 0}" class="position-absolute">
+          <mat-card>
+            <layer-browser #layerBrowser>
+            </layer-browser>
+          </mat-card>
+        </div>
+
+        <ng-container *ngIf="(layerBrowser.nonBaseNgLayers$ | async).length === 0" #noNonBaseNgLayerTemplate>
+          <small [class]="((darktheme$ | async) ? 'bg-dark text-light' : 'bg-light text-dark') + ' muted pl-2 pr-2 p-1 text-nowrap'">
+            No additional layers added
+          </small>
+        </ng-container>
+
+        <!-- invisible button to prop up the size of parent block -->
+        <!-- otherwise, sibling block position will be wonky -->
+        <button
+          color="primary"
+          class="invisible pe-none"
+          mat-icon-button>
+          <i class="fas fa-layer-group"></i>
+        </button>
+
+      </div>
+
     </div>
-  </div>
+  </sleight-of-hand>
+  
+  <!-- tools -->
+  <sleight-of-hand>
 
-  <div
-    [ngClass]="isMobile ? 'btnWrapper-lg' : ''"
-    class="btnWrapper">
-    <div
-      tooltip="Layer"
-      placement="right"
-      (click)="clickLayer($event)"
-      [ngClass]="layerbrowserIsShowing ? 'btn-primary' : 'btn-secondary'"
-      class="shadow btn btn-sm rounded-circle">
-      <i class="fas fa-layer-group">
-        
-      </i>
+    <!-- shown icon prior to mouse over -->
+    <div sleight-of-hand-front>
+      <button
+        [matBadgePosition]="badgetPosition"
+        matBadgeColor="accent"
+        [matBadge]="(launchedPlugins$ | async)?.length > 0 ? (launchedPlugins$ | async)?.length : null"
+        mat-icon-button
+        color="primary">
+        <i class="fas fa-tools"></i>
+      </button>
     </div>
-  </div>
 
-  <div
-    *ngIf="false"
-    [ngClass]="isMobile ? 'btnWrapper-lg' : ''"
-    class="btnWrapper">
+    <!-- shown after mouse over -->
     <div
-      tooltip="Plugins"
-      (click)="clickPlugins($event)"
-      placement="right"
-      [ngClass]="pluginbrowserIsShowing ? 'btn-primary' : 'btn-secondary'"
-      class="shadow btn btn-sm rounded-circle">
-      <i class="fas fa-tools">
+      class="d-flex flex-row soh-row align-items-start"
+      sleight-of-hand-back>
+
+      <!-- placeholder icon -->
+      <button
+        [matBadgePosition]="badgetPosition"  
+        matBadgeColor="accent"
+        [matBadge]="(launchedPlugins$ | async)?.length > 0 ? (launchedPlugins$ | async)?.length : null"
+        mat-icon-button
+        color="primary">
+        <i class="fas fa-tools"></i>
+      </button>
+
+      <!-- render all fetched tools -->
+      <div class="d-flex flex-row soh-row">
+
+        <!-- add new tool btn -->
+        <button
+          matTooltip="Add new plugin"
+          matTooltipPosition="below"
+          mat-icon-button
+          color="primary">
+          <i class="fas fa-plus"></i>
+        </button>
         
-      </i>
+        <button
+          *ngFor="let manifest of pluginServices.fetchedPluginManifests"
+          mat-mini-fab
+          matTooltipPosition="below"
+          [matTooltip]="manifest.displayName || manifest.name"
+          [color]="getPluginBtnClass$ | async | pluginBtnFabColorPipe : manifest.name"
+          (click)="clickPluginIcon(manifest)">
+          {{ (manifest.displayName || manifest.name).slice(0, 1) }}
+        </button>
+      </div>
     </div>
-  </div>
+  </sleight-of-hand>
 
-  <div
-    *ngFor="let manifest of pluginServices.fetchedPluginManifests"
-    [tooltip]="manifest.displayName || manifest.name"
-    placement="right"
-    [ngClass]="isMobile ? 'btnWrapper-lg' : ''"
-    class="btnWrapper">
+  <!-- search kg -->
+  <sleight-of-hand>
+    
+    <!-- shown icon prior to mouse over -->
+    <div sleight-of-hand-front>
+      <button
+        mat-icon-button
+        color="primary"
+        [matBadgePosition]="badgetPosition"
+        matBadgeColor="accent"
+        [matBadge]="dbService.instantiatedWidgetUnits.length > 0 ? dbService.instantiatedWidgetUnits.length : null">
+        <i class="fas fa-search"></i>
+      </button>
+    </div>
 
+    <!-- shown after mouse over -->
     <div
-      (click)="pluginServices.launchPlugin(manifest).catch(catchError)"
-      [ngClass]="!pluginServices.launchedPlugins.has(manifest.name) ? 'btn-outline-secondary' : pluginServices.pluginMinimised(manifest) ? 'btn-outline-info' : 'btn-info'"
-      class="shadow btn btn-sm rounded-circle">
-      {{ (manifest.displayName || manifest.name).slice(0, 1) }}
+      sleight-of-hand-back
+      class="d-flex flex-row align-items-center soh-row pe-none">
+
+      <!-- placeholder icon -->
+      <button
+        mat-icon-button
+        color="primary"
+        matBadgeColor="accent"
+        [matBadgePosition]="badgetPosition"
+        [matBadge]="dbService.instantiatedWidgetUnits.length > 0 ? dbService.instantiatedWidgetUnits.length : null">
+        <i class="fas fa-search"></i>
+      </button>
+
+      <!-- only renders if there is at least one search result -->
+      <div
+        *ngIf="dbService.instantiatedWidgetUnits.length > 0; else noKgSearchTemplate"
+        class="position-relative pe-all">
+
+        <div class="position-absolute d-flex flex-column soh-column">
+
+          <!-- render all searched kg -->
+          <sleight-of-hand
+            *ngFor="let wu of dbService.instantiatedWidgetUnits"
+            (click)="searchIconClickHandler(wu)">
+
+            <!-- shown prior to mouseover -->
+            <div sleight-of-hand-front>
+              <button
+                mat-mini-fab
+                [color]="getKgSearchBtnCls$ | async | kgSearchBtnColorPipe : wu">
+                <i class="fas fa-search"></i>  
+              </button>
+            </div>
+
+            <!-- shown on mouse over -->
+            <!-- showing additional information. in this case, name of the kg search -->
+            <div class="d-flex flex-row align-items-center" sleight-of-hand-back>
+
+              <div sleight-of-hand-front>
+                <button
+                  mat-mini-fab
+                  [color]="getKgSearchBtnCls$ | async | kgSearchBtnColorPipe : wu">
+                  <i class="fas fa-search"></i>  
+                </button>
+              </div>
+              
+              <!-- on hover, show full name and action possible: rename, close -->
+              <div [class]="((darktheme$ | async) ? 'text-light' : 'text-dark' ) + ' h-0 d-flex flex-row align-items-center'">
+
+                <sleight-of-hand class="ml-1 h-0">
+                  <!-- prior mouse over -->
+                  <div class="h-0 d-flex align-items-center flex-row" sleight-of-hand-front>
+                    <div [class]="((darktheme$ | async) ? 'bg-dark' : 'bg-light' ) + ' muted d-flex flex-row align-items-center'">
+                  
+                      <small class="cursor-default ml-2 text-nowrap">
+                        {{ wu.title }}
+                      </small>
+  
+                      <!-- dummy class to keep height -->
+                      <div
+                        matTooltip="Rename"
+                        matTooltipPosition="below"
+                        [class]="(skeletonBtnClass$ | async) + ' invisible w-0 pe-none'">
+                        <i class="fas fa-edit"></i>
+                      </div>
+                    </div>
+                  </div>
+
+                  <!-- on mouse over -->
+                  <div class="h-0 d-flex align-items-center flex-row" sleight-of-hand-back>
+                    <div [class]="((darktheme$ | async) ? 'bg-dark' : 'bg-light' ) + ' d-flex flex-row align-items-center'">
+                      
+                      <small class="cursor-default ml-2 text-nowrap">
+                        {{ wu.title }}
+                      </small>
+  
+                      <!-- rename -->
+                      <div
+                        (click)="renameKgSearchWidget($event, wu)"
+                        matTooltip="Rename (NYI)"
+                        matTooltipPosition="below"
+                        [class]="(skeletonBtnClass$ | async) + ' text-muted'">
+                        <i class="fas fa-edit"></i>
+                      </div>
+
+                      <!-- star -->
+                      <div
+                        (click)="favKgSearch($event, wu)"
+                        matTooltip="Favourite (NYI)"
+                        matTooltipPosition="below"
+                        [class]="(skeletonBtnClass$ | async) + ' text-muted'">
+                        <i class="far fa-star"></i>
+                      </div>
+  
+                      <!-- close -->
+                      <div
+                        (click)="closeWidget($event, wu)"
+                        matTooltip="Close"
+                        matTooltipPosition="below"
+                        [class]="skeletonBtnClass$ | async">
+                        <i class="fas fa-times"></i>
+                      </div>
+                    </div>
+                  </div>
+                </sleight-of-hand> 
+              </div>
+            </div>
+          </sleight-of-hand>
+        </div>
+
+        <!-- invisible icon to keep height of the otherwise unstable flex block -->
+        <div class="invisible pe-none">
+          <button mat-icon-button>
+            <i class="fas fa-search"></i>
+          </button>
+        </div>
+      </div>
+
+      <!-- displayed when no search is visible -->
+      <ng-template #noKgSearchTemplate>
+        <small [class]="((darktheme$ | async) ? 'bg-dark text-light' : 'bg-light text-dark') + ' muted pl-2 pr-2 p-1 text-nowrap'">
+          Right click any area to search
+        </small>
+      </ng-template>
     </div>
-  </div>
 
-  <div
-    *ngFor="let manifest of pluginServices.orphanPlugins"
-    [tooltip]="manifest.displayName || manifest.name"
-    placement="right"
-    [ngClass]="isMobile ? 'btnWrapper-lg' : ''"
-    class="btnWrapper">
+  </sleight-of-hand>
 
-    <div
-      (click)="pluginServices.launchPlugin(manifest).catch(catchError)"
-      [ngClass]="pluginServices.pluginMinimised(manifest) ? 'btn-outline-info' : 'btn-info'"
-      class="shadow btn btn-sm rounded-circle">
-      {{ (manifest.displayName || manifest.name).slice(0, 1) }}
+  <!-- selected regions -->
+  <sleight-of-hand
+    [doNotClose]="viewerStateController.focused">
+
+    <!-- shown prior to mouse over -->
+    <div sleight-of-hand-front>
+      <button
+        [matBadge]="(selectedRegions$ | async).length > 0 ? (selectedRegions$ | async).length : null"
+        [matBadgePosition]="badgetPosition"
+        matBadgeColor="accent"
+        mat-icon-button
+        color="primary">
+        <i class="fas fa-brain"></i>
+      </button>
     </div>
-  </div>
-
-  <div
-    *ngFor="let wu of dbService.instantiatedWidgetUnits"
-    [ngClass]="isMobile ? 'btnWrapper-lg' : ''"
-    placement="right"
-    [tooltip]="wu.title"
-    class="btnWrapper">
+
+    <!-- shown upon mouseover -->
     <div
-      (click)="widgetServices.minimisedWindow.delete(wu)"
-      [ngClass]="widgetServices.minimisedWindow.has(wu) ? 'btn-outline-info' : 'btn-info'"
-      class="shadow btn btn-sm rounded-circle">
-      <i class="fas fa-search"></i>
+      sleight-of-hand-back
+      class="d-flex flex-row align-items-center soh-row">
+
+      <!-- place holder icon -->
+      <button
+        [matBadge]="(selectedRegions$ | async).length > 0 ? (selectedRegions$ | async).length : null"
+        [matBadgePosition]="badgetPosition"
+        matBadgeColor="accent"
+        mat-icon-button
+        color="primary">
+        <i class="fas fa-brain"></i>
+      </button>
+
+      <div class="position-relative">
+
+        <div [class]="((darktheme$ | async) ? 'bg-dark' : 'bg-light') + ' position-absolute card'">
+          <viewer-state-controller #viewerStateController></viewer-state-controller>
+        </div>
+        
+        <!-- invisible icon to keep height of the otherwise unstable flex block -->
+        <div class="invisible pe-none">
+          <i class="fas fa-brain"></i>
+        </div>
+      </div>
+
+      <ng-template #noBrainRegionSelected>
+        <small [class]="((darktheme$ | async) ? 'bg-dark text-light' : 'bg-light text-dark') + ' muted pl-2 pr-2 p-1 text-nowrap'">
+          Double click any brain region to select it.
+        </small>
+      </ng-template>
     </div>
-  </div>
+  </sleight-of-hand>
 </ng-template>
\ No newline at end of file
diff --git a/src/ui/nehubaContainer/nehubaContainer.component.ts b/src/ui/nehubaContainer/nehubaContainer.component.ts
index 85e32cf11f9a5f03ae084a54ff0386e4928ec45b..3112fa91a944a0766c871af90034af33c989a715 100644
--- a/src/ui/nehubaContainer/nehubaContainer.component.ts
+++ b/src/ui/nehubaContainer/nehubaContainer.component.ts
@@ -1,9 +1,9 @@
 import { Component, ViewChild, ViewContainerRef, ComponentFactoryResolver, ComponentFactory, ComponentRef, OnInit, OnDestroy, ElementRef } from "@angular/core";
 import { NehubaViewerUnit } from "./nehubaViewer/nehubaViewer.component";
 import { Store, select } from "@ngrx/store";
-import { ViewerStateInterface, safeFilter, CHANGE_NAVIGATION, isDefined, USER_LANDMARKS, ADD_NG_LAYER, REMOVE_NG_LAYER, NgViewerStateInterface, MOUSE_OVER_LANDMARK, SELECT_LANDMARKS, Landmark, PointLandmarkGeometry, PlaneLandmarkGeometry, OtherLandmarkGeometry, getNgIds, getMultiNgIdsRegionsLabelIndexMap, generateLabelIndexId } from "../../services/stateStore.service";
+import { ViewerStateInterface, safeFilter, CHANGE_NAVIGATION, isDefined, USER_LANDMARKS, ADD_NG_LAYER, REMOVE_NG_LAYER, NgViewerStateInterface, MOUSE_OVER_LANDMARK, SELECT_LANDMARKS, Landmark, PointLandmarkGeometry, PlaneLandmarkGeometry, OtherLandmarkGeometry, getNgIds, getMultiNgIdsRegionsLabelIndexMap, generateLabelIndexId, DataEntry } from "../../services/stateStore.service";
 import { Observable, Subscription, fromEvent, combineLatest, merge } from "rxjs";
-import { filter,map, take, scan, debounceTime, distinctUntilChanged, switchMap, skip, withLatestFrom, buffer, tap, switchMapTo, shareReplay } from "rxjs/operators";
+import { filter,map, take, scan, debounceTime, distinctUntilChanged, switchMap, skip, withLatestFrom, buffer, tap, switchMapTo, shareReplay, throttleTime, bufferTime, startWith } from "rxjs/operators";
 import { AtlasViewerAPIServices, UserLandmark } from "../../atlasViewer/atlasViewer.apiService.service";
 import { timedValues } from "../../util/generator";
 import { AtlasViewerConstantsServices } from "../../atlasViewer/atlasViewer.constantService.service";
@@ -11,8 +11,12 @@ import { ViewerConfiguration } from "src/services/state/viewerConfig.store";
 import { pipeFromArray } from "rxjs/internal/util/pipe";
 import { NEHUBA_READY, H_ONE_THREE, V_ONE_THREE, FOUR_PANEL, SINGLE_PANEL } from "src/services/state/ngViewerState.store";
 import { MOUSE_OVER_SEGMENTS } from "src/services/state/uiState.store";
-import { SELECT_REGIONS_WITH_ID, NEHUBA_LAYER_CHANGED } from "src/services/state/viewerState.store";
 import { getHorizontalOneThree, getVerticalOneThree, getFourPanel, getSinglePanel } from "./util";
+import { SELECT_REGIONS_WITH_ID, NEHUBA_LAYER_CHANGED, VIEWERSTATE_ACTION_TYPES } from "src/services/state/viewerState.store";
+import { MatBottomSheet, MatButton } from "@angular/material";
+import { DATASETS_ACTIONS_TYPES } from "src/services/state/dataStore.store";
+import { KgSingleDatasetService } from "../databrowserModule/kgSingleDatasetService.service";
+import { getIdFromDataEntry } from "../databrowserModule/databrowser.service";
 
 const getProxyUrl = (ngUrl) => `nifti://${BACKEND_URL}preview/file?fileUrl=${encodeURIComponent(ngUrl.replace(/^nifti:\/\//,''))}`
 const getProxyOther = ({source}) => /AUTH_227176556f3c4bb38df9feea4b91200c/.test(source)
@@ -132,8 +136,6 @@ export class NehubaContainer implements OnInit, OnDestroy{
   private landmarksLabelIndexMap : Map<number, any> = new Map()
   private landmarksNameMap : Map<string,number> = new Map()
   
-  private userLandmarks : UserLandmark[] = []
-  
   private subscriptions : Subscription[] = []
   private nehubaViewerSubscriptions : Subscription[] = []
 
@@ -143,14 +145,22 @@ export class NehubaContainer implements OnInit, OnDestroy{
   private viewPanels: [HTMLElement, HTMLElement, HTMLElement, HTMLElement] = [null, null, null, null]
   public panelMode$: Observable<string>
   private redrawLayout$: Observable<[string, string]>
+  public favDataEntries$: Observable<DataEntry[]>
 
   constructor(
     private constantService : AtlasViewerConstantsServices,
     private apiService :AtlasViewerAPIServices,
     private csf:ComponentFactoryResolver,
     private store : Store<ViewerStateInterface>,
-    private elementRef : ElementRef
+    private elementRef : ElementRef,
+    public bottomSheet: MatBottomSheet,
+    private kgSingleDataset: KgSingleDatasetService
   ){
+    this.favDataEntries$ = this.store.pipe(
+      select('dataStore'),
+      select('favDataEntries')
+    )
+
     this.viewerPerformanceConfig$ = this.store.pipe(
       select('viewerConfigState'),
       /**
@@ -240,13 +250,9 @@ export class NehubaContainer implements OnInit, OnDestroy{
     )
 
     this.userLandmarks$ = this.store.pipe(
-      /* TODO: distinct until changed */
       select('viewerState'),
-      // filter(state => isDefined(state) && isDefined(state.userLandmarks)),
-      map(state => isDefined(state) && isDefined(state.userLandmarks)
-        ? state.userLandmarks
-        : []),
-      distinctUntilChanged(userLmUnchanged)
+      select('userLandmarks'),
+      distinctUntilChanged()
     )
 
     this.onHoverSegments$ = this.store.pipe(
@@ -536,10 +542,7 @@ export class NehubaContainer implements OnInit, OnDestroy{
     )
 
     this.subscriptions.push(
-      this.userLandmarks$.pipe(
-        // distinctUntilChanged((old,new) => )
-      ).subscribe(landmarks => {
-        this.userLandmarks = landmarks
+      this.userLandmarks$.subscribe(landmarks => {
         if(this.nehubaViewer){
           this.nehubaViewer.updateUserLandmarks(landmarks)
         }
@@ -772,11 +775,13 @@ export class NehubaContainer implements OnInit, OnDestroy{
 
     this.subscriptions.push(
       this.selectedLandmarks$.pipe(
-        map(lms => lms.map(lm => this.landmarksNameMap.get(lm.name)))
+        map(lms => lms.map(lm => this.landmarksNameMap.get(lm.name))),
+        debounceTime(16)
       ).subscribe(indices => {
         const filteredIndices = indices.filter(v => typeof v !== 'undefined' && v !== null)
-        if(this.nehubaViewer)
+        if(this.nehubaViewer) {
           this.nehubaViewer.spatialLandmarkSelectionChanged(filteredIndices)
+        }
       })
     )
   }
@@ -1050,14 +1055,16 @@ export class NehubaContainer implements OnInit, OnDestroy{
         if(!landmarks.every(l => l.position.constructor === Array) || !landmarks.every(l => l.position.every(v => !isNaN(v))) || !landmarks.every(l => l.position.length == 3))
           throw new Error('position needs to be a length 3 tuple of numbers ')
         this.store.dispatch({
-          type: USER_LANDMARKS,
+          type: VIEWERSTATE_ACTION_TYPES.ADD_USERLANDMARKS,
           landmarks : landmarks
         })
       },
-      remove3DLandmarks : ids => {
+      remove3DLandmarks : landmarkIds => {
         this.store.dispatch({
-          type : USER_LANDMARKS,
-          landmarks : this.userLandmarks.filter(l => ids.findIndex(id => id === l.id) < 0)
+          type: VIEWERSTATE_ACTION_TYPES.REMOVE_USER_LANDMARKS,
+          payload: {
+            landmarkIds
+          }
         })
       },
       hideSegment : (labelIndex) => {
@@ -1236,6 +1243,19 @@ export class NehubaContainer implements OnInit, OnDestroy{
     }
   }
 
+  removeFav(event: MouseEvent, ds: DataEntry){
+    this.store.dispatch({
+      type: DATASETS_ACTIONS_TYPES.UNFAV_DATASET,
+      payload: ds
+    })
+  }
+
+  downloadDs(event: MouseEvent, ds: DataEntry, downloadBtn: MatButton){
+    downloadBtn.disabled = true
+    const id = getIdFromDataEntry(ds)
+    this.kgSingleDataset.downloadZipFromKg({kgId: id})
+      .finally(() => downloadBtn.disabled = false)
+  }
 }
 
 export const identifySrcElement = (element:HTMLElement) => {
diff --git a/src/ui/nehubaContainer/nehubaContainer.style.css b/src/ui/nehubaContainer/nehubaContainer.style.css
index 2d9e1542749aa6d838e172434fd3205e6beda3d3..57a6ad380921978025fdb9692967d716743d5eab 100644
--- a/src/ui/nehubaContainer/nehubaContainer.style.css
+++ b/src/ui/nehubaContainer/nehubaContainer.style.css
@@ -172,3 +172,8 @@ div#scratch-pad
   pointer-events: none;
 }
 
+.load-fav-dataentries-fab
+{
+  right: 0;
+  bottom: 0;
+}
\ No newline at end of file
diff --git a/src/ui/nehubaContainer/nehubaContainer.template.html b/src/ui/nehubaContainer/nehubaContainer.template.html
index 4a66e0fa605fccbeac175d89aea500954c9cd31b..6ba9a1443ce3e189057eac4e8c347a48fd0a6b52 100644
--- a/src/ui/nehubaContainer/nehubaContainer.template.html
+++ b/src/ui/nehubaContainer/nehubaContainer.template.html
@@ -23,6 +23,22 @@
 </current-layout>
 
 <layout-floating-container *ngIf="viewerLoaded && !isMobile">
+  
+  <!-- tmp fab -->
+  <div class="m-3 load-fav-dataentries-fab position-absolute pe-all">
+    <button
+      (click)="bottomSheet.open(savedDatasets)"
+      [matBadge]="(favDataEntries$ | async)?.length > 0 ? (favDataEntries$ | async)?.length : null "
+      matBadgeColor="accent"
+      matBadgePosition="above before"
+      matTooltip="Favourite datasets"
+      matTooltipPosition="before"
+      mat-fab
+      color="primary">
+      <i class="fas fa-star"></i>
+    </button>
+  </div>
+
   <!-- StatusCard container-->
   <ui-status-card
     [selectedTemplate]="selectedTemplate"
@@ -106,6 +122,7 @@
       </div>
     </layout-floating-container>
 </ng-template>
+
 <ng-template #overlayiii>
   <layout-floating-container pos10 landmarkContainer>
     <nehuba-2dlandmark-unit *ngFor="let spatialData of (selectedPtLandmarks$ | async)"
@@ -123,6 +140,7 @@
     </div>
   </layout-floating-container>
 </ng-template>
+
 <ng-template #overlayiv>
   <layout-floating-container pos11 landmarkContainer>
     <div *ngIf="perspectiveViewLoading$ | async" class="loadingIndicator">
@@ -132,4 +150,50 @@
       </div>
     </div>
   </layout-floating-container>
+</ng-template>
+
+<ng-template #savedDatasets>
+  <mat-list rol="list">
+    <h3 mat-subheader>Favourite Datasets</h3>
+
+    <!-- place holder when no fav data is available -->
+    <mat-card *ngIf="(!(favDataEntries$ | async)) || (favDataEntries$ | async).length === 0">
+      <mat-card-content class="muted">
+        No dataset favourited... yet.
+      </mat-card-content>
+    </mat-card>
+
+    <!-- render all fav dataset as mat list -->
+    <mat-list-item
+      class="align-items-center"
+      *ngFor="let ds of (favDataEntries$ | async)"
+      role="listitem">
+      <span class="flex-grow-1 flex-shrink-1">
+        {{ ds.name }}
+      </span>
+
+      <!-- download -->
+      <button
+        #downloadBtn="matButton"
+        (click)="downloadDs($event, ds, downloadBtn)"
+        matTooltip="Download Dataset"
+        matTooltipPosition="after"
+        color="primary"
+        class="flex-grow-0 flex-shrink-0"
+        mat-icon-button>
+        <i class="fas fa-download"></i>
+      </button>
+
+      <!-- remove from fav -->
+      <button
+        (click)="removeFav($event, ds)"
+        matTooltip="Remove Favourite"
+        matTooltipPosition="after"
+        color="warn"
+        class="flex-grow-0 flex-shrink-0"
+        mat-icon-button>
+        <i class="fas fa-trash"></i>
+      </button>
+    </mat-list-item>
+  </mat-list>
 </ng-template>
\ No newline at end of file
diff --git a/src/ui/nehubaContainer/splashScreen/splashScreen.component.ts b/src/ui/nehubaContainer/splashScreen/splashScreen.component.ts
index eb27393a77f1c284743cc34314d09db21040946f..22fd3655e72c0553f62a90716301cbac92f26723 100644
--- a/src/ui/nehubaContainer/splashScreen/splashScreen.component.ts
+++ b/src/ui/nehubaContainer/splashScreen/splashScreen.component.ts
@@ -1,10 +1,11 @@
-import { Component } from "@angular/core";
-import { Observable } from "rxjs";
+import { Component, Pipe, PipeTransform, ElementRef, ViewChild, AfterViewInit } from "@angular/core";
+import { Observable, fromEvent, Subscription, Subject } from "rxjs";
 import { Store, select } from "@ngrx/store";
-import { filter,map } from 'rxjs/operators'
+import { switchMap, bufferTime, take, filter, withLatestFrom, map, tap } from 'rxjs/operators'
 import { ViewerStateInterface, NEWVIEWER } from "../../../services/stateStore.service";
 import { AtlasViewerConstantsServices } from "../../../atlasViewer/atlasViewer.constantService.service";
 
+
 @Component({
   selector : 'ui-splashscreen',
   templateUrl : './splashScreen.template.html',
@@ -13,17 +14,52 @@ import { AtlasViewerConstantsServices } from "../../../atlasViewer/atlasViewer.c
   ]
 })
 
-export class SplashScreen{
-  loadedTemplate$ : Observable<any[]>
+export class SplashScreen implements AfterViewInit{
+
+  public loadedTemplate$ : Observable<any[]>
+  @ViewChild('parentContainer', {read:ElementRef}) 
+  private parentContainer: ElementRef
+  private activatedTemplate$: Subject<any> = new Subject()
+
+  private subscriptions: Subscription[] = []
+
   constructor(
     private store:Store<ViewerStateInterface>,
     private constanceService: AtlasViewerConstantsServices,
     private constantsService: AtlasViewerConstantsServices,
-){
+  ){
     this.loadedTemplate$ = this.store.pipe(
       select('viewerState'),
-      filter((state:ViewerStateInterface)=> typeof state !== 'undefined' && typeof state.fetchedTemplates !== 'undefined' && state.fetchedTemplates !== null),
-      map(state=>state.fetchedTemplates))
+      select('fetchedTemplates')
+    )
+  }
+
+  ngAfterViewInit(){
+
+    /**
+     * instead of blindly listening to click event, this event stream waits to see if user mouseup within 200ms
+     * if yes, it is interpreted as a click
+     * if no, user may want to select a text
+     */
+    this.subscriptions.push(
+      fromEvent(this.parentContainer.nativeElement, 'mousedown').pipe(
+        switchMap(() => fromEvent(this.parentContainer.nativeElement, 'mouseup').pipe(
+          bufferTime(200),
+          take(1)
+        )),
+        filter(arr => arr.length > 0),
+        withLatestFrom(this.activatedTemplate$),
+        map(([_, template]) => template)
+      ).subscribe(template => this.selectTemplate(template))
+    )
+  }
+
+  selectTemplateParcellation(template, parcellation){
+    this.store.dispatch({
+      type : NEWVIEWER,
+      selectTemplate : template,
+      selectParcellation : parcellation
+    })
   }
 
   selectTemplate(template:any){
@@ -38,11 +74,31 @@ export class SplashScreen{
     return this.constanceService.templateUrls.length
   }
 
-  correctString(name){
-    return name.replace(/[|&;$%@()+,\s./]/g, '')
-  }
-
   get isMobile(){
     return this.constantsService.mobile
   }
-}
\ No newline at end of file
+}
+
+@Pipe({
+  name: 'getTemplateImageSrcPipe'
+})
+
+export class GetTemplateImageSrcPipe implements PipeTransform{
+  public transform(name:string):string{
+    return `./res/image/${name.replace(/[|&;$%@()+,\s./]/g, '')}.png`
+  }
+}
+
+@Pipe({
+  name: 'imgSrcSetPipe'
+})
+
+export class ImgSrcSetPipe implements PipeTransform{
+  public transform(src:string):string{
+    const regex = /^(.*?)(\.\w*?)$/.exec(src)
+    if (!regex) throw new Error(`cannot find filename, ext ${src}`)
+    const filename = regex[1]
+    const ext = regex[2]
+    return [100, 200, 300, 400].map(val => `${filename}-${val}${ext} ${val}w`).join(',')
+  }
+} 
\ No newline at end of file
diff --git a/src/ui/nehubaContainer/splashScreen/splashScreen.style.css b/src/ui/nehubaContainer/splashScreen/splashScreen.style.css
index cea1195130e1a729c44170d5e47fe742595ff3c3..49774d56632717713c75fc404f681049c0e06e5c 100644
--- a/src/ui/nehubaContainer/splashScreen/splashScreen.style.css
+++ b/src/ui/nehubaContainer/splashScreen/splashScreen.style.css
@@ -1,61 +1,11 @@
-.appendMargin
+:host
 {
-  padding-top:10em;
+  display: block;
+  overflow: auto;
+  height: 100%;
 }
 
-.splashScreenHeaderTitle {
-  font-size: 45px;
-}
-
-div[splashScreenTemplateItem] {
-  max-width: 600px;
-  width: 400px;
-  margin: 20px 20px;
-  display: flex;
-  flex-direction: row;
-  flex-wrap: wrap;
-  justify-content: center;
-
-}
-
-div[splashScreenTemplateHeader] {
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  height: 70px;
-  align-self: center;
-  text-align: center;
-  font-size: 30px;
-  margin: 5px 0;
-
-}
-
-.template-image {
-  width: 100%;
-  height: auto;
-}
-
-.template-card {
-  width: 100%;
-  cursor: pointer;
-  background: #fff;
-  border-radius: 2px;
-  box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
-  transition: all 0.3s cubic-bezier(.25,.8,.25,1);
-}
-
-.template-card:hover {
-  box-shadow: 0 14px 28px rgba(0,0,0,0.25), 0 5px 5px rgba(0,0,0,0.22);
-}
-
-@media screen and (max-width: 670px) {
-  .splashScreenHeaderTitle {
-    visibility: hidden;
-  }
-  div[splashScreenTemplate] {
-    margin: 20px 20px;
-  }
-  div[splashScreenTemplateBody] {
-    flex-direction: column;
-  }
+.font-stretch
+{
+  font-stretch: extra-condensed;
 }
\ No newline at end of file
diff --git a/src/ui/nehubaContainer/splashScreen/splashScreen.template.html b/src/ui/nehubaContainer/splashScreen/splashScreen.template.html
index 28ec10df8e43de608f367da127f8ee292524c48a..a63adbeb71157ff8282a9abda4d5e30e58d79924 100644
--- a/src/ui/nehubaContainer/splashScreen/splashScreen.template.html
+++ b/src/ui/nehubaContainer/splashScreen/splashScreen.template.html
@@ -1,20 +1,43 @@
-<div [ngClass]="isMobile ? '' : 'appendMargin'" class="h-100 d-flex flex-column justify-content-start align-items-center overflow-auto">
+<div
+  #parentContainer
+  class="m-5 d-flex flex-row flex-wrap justify-content-center align-items-stretch pe-none">
+  <mat-card
+    (mousedown)="activatedTemplate$.next(template)"
+    matRipple
+    *ngFor="let template of loadedTemplate$ | async | filterNull"
+    class="m-3 col-md-12 col-lg-6 pe-all mw-400px">
+    <mat-card-header>
+      <mat-card-title class="text-nowrap font-stretch">
+        {{ template.properties.name }}
+      </mat-card-title>
+    </mat-card-header>
+    <img
+      [src]="template.properties.name | getTemplateImageSrcPipe"
+      [srcset]="template.properties.name | getTemplateImageSrcPipe | imgSrcSetPipe"
+      sizes="(max-width:576px) 90vw;(max-width: 768px) 50vw; 400px"
+      [alt]="'Screenshot of ' + template.properties.name"
+      mat-card-image />
+    <mat-card-content>
+      {{ template.properties.description }}
+    </mat-card-content>
 
-  <div class="d-flex w-100 flex-wrap justify-content-center">
-    <div *ngFor="let template of loadedTemplate$ | async | filterNull" splashScreenTemplateItem>
-      <div class="template-card" (click) = "selectTemplate(template)">
-        <div splashScreenTemplateHeader>
-          {{template.properties.name}}
-        </div>
-        <div class="d-flex flex-column">
-          <div class="flex-grow-1">
-            <img class="template-image" [src]="'./res/image/' + correctString(template.properties.name) + '.png'">
-          </div>
-          <div class="flex-grow-1 text-justify ml-2 mr-2 mb-2 mt-0">
-            {{template.properties.description}}
-          </div>
-        </div>
-      </div>
-    </div>
-  </div>
+    <mat-card-content>
+      <mat-card-subtitle class="text-nowrap">
+        Parcellations available
+      </mat-card-subtitle>
+      <button
+        (mousedown)="$event.stopPropagation()"
+        (click)="$event.stopPropagation(); selectTemplateParcellation(template, parcellation)"
+        *ngFor="let parcellation of template.parcellations"
+        mat-button
+        color="primary">
+        {{ parcellation.name }}
+      </button>
+    </mat-card-content>
+
+    <!-- required... or on ripple, angular adds 16px margin to the bottom -->
+    <!-- see https://github.com/angular/components/issues/10898 -->
+    <mat-card-footer>
+    </mat-card-footer>
+  </mat-card>
 </div>
\ No newline at end of file
diff --git a/src/ui/regionHierachy/regionHierarchy.style.css b/src/ui/regionHierachy/regionHierarchy.style.css
deleted file mode 100644
index a4aa507f4db1a32886dc1309934dc8481d49a425..0000000000000000000000000000000000000000
--- a/src/ui/regionHierachy/regionHierarchy.style.css
+++ /dev/null
@@ -1,65 +0,0 @@
-
-div[treeContainer]
-{
-  padding:1em;
-  z-index: 3;
-
-  height:20em;
-  width: calc(100% + 4em);
-  overflow-y:auto;
-  overflow-x:hidden;
-
-  /* color:white;
-  background-color:rgba(12,12,12,0.8); */
-}
-
-:host-context([darktheme="false"]) div[treeContainer]
-{
-  background-color:rgba(240,240,240,0.8);
-}
-
-:host-context([darktheme="true"]) div[treeContainer]
-{
-  background-color:rgba(30,30,30,0.8);
-  color:rgba(255,255,255,1.0);
-}
-
-div[hideScrollbarcontainer]
-{
-  width: 20em;
-  overflow:hidden;
-  margin-top:2px;
-}
-
-input[type="text"]
-{
-  border:none;
-}
-
-:host-context([darktheme="false"]) input[type="text"]
-{
-  background-color:rgba(245,245,245,0.85);
-  box-shadow: inset 0 4px 6px 0 rgba(5,5,5,0.1);
-}
-
-:host-context([darktheme="true"]) input[type="text"]
-{
-  background-color:rgba(30,30,30,0.85);
-  box-shadow: inset 0 4px 6px 0 rgba(0,0,0,0.2);
-  color:rgba(255,255,255,0.8);
-}
-
-.regionSearch
-{
-  width:20em;
-}
-
-.tree-header
-{
-  flex: 0 0 auto;
-}
-
-.tree-body
-{
-  flex: 1 1 auto;
-}
\ No newline at end of file
diff --git a/src/ui/sharedModules/angularMaterial.module.ts b/src/ui/sharedModules/angularMaterial.module.ts
index 7c501dee8f70df574ef14e530791f88694130683..9967e80d64279a7ef9e9b760a1c784593926c3b3 100644
--- a/src/ui/sharedModules/angularMaterial.module.ts
+++ b/src/ui/sharedModules/angularMaterial.module.ts
@@ -5,13 +5,63 @@ import {
   MatCardModule,
   MatTabsModule,
   MatTooltipModule,
+  MatBadgeModule,
+  MatDividerModule,
+  MatSelectModule,
+  MatChipsModule,
+  MatAutocompleteModule,
+  MatDialogModule,
+  MatInputModule,
+  MatBottomSheetModule,
+  MatListModule,
   MatSlideToggleModule,
-  MatDialogModule, 
+  MatRippleModule
+  
 } from '@angular/material';
 import { NgModule } from '@angular/core';
 
+/**
+ * TODO should probably be in src/util
+ */
+
 @NgModule({
-  imports: [MatDialogModule, MatSlideToggleModule, MatButtonModule, MatCheckboxModule, MatSidenavModule, MatCardModule, MatTabsModule, MatTooltipModule],
-  exports: [MatDialogModule, MatSlideToggleModule, MatButtonModule, MatCheckboxModule, MatSidenavModule, MatCardModule, MatTabsModule, MatTooltipModule],
+  imports: [
+    MatButtonModule,
+    MatCheckboxModule,
+    MatSidenavModule,
+    MatCardModule,
+    MatTabsModule,
+    MatTooltipModule,
+    MatBadgeModule,
+    MatDividerModule,
+    MatSelectModule,
+    MatChipsModule,
+    MatAutocompleteModule,
+    MatDialogModule,
+    MatInputModule,
+    MatBottomSheetModule,
+    MatListModule,
+    MatSlideToggleModule,
+    MatRippleModule
+  ],
+  exports: [
+    MatButtonModule,
+    MatCheckboxModule,
+    MatSidenavModule,
+    MatCardModule,
+    MatTabsModule,
+    MatTooltipModule,
+    MatBadgeModule,
+    MatDividerModule,
+    MatSelectModule,
+    MatChipsModule,
+    MatAutocompleteModule,
+    MatDialogModule,
+    MatInputModule,
+    MatBottomSheetModule,
+    MatListModule,
+    MatSlideToggleModule,
+    MatRippleModule
+  ],
 })
 export class AngularMaterialModule { }
\ No newline at end of file
diff --git a/src/ui/signinBanner/signinBanner.components.ts b/src/ui/signinBanner/signinBanner.components.ts
index e5db9cc39536c5d28338f794005dc9e296d91599..abd1eb3397f1c1a3e7ada69d3ead390f29bfbed7 100644
--- a/src/ui/signinBanner/signinBanner.components.ts
+++ b/src/ui/signinBanner/signinBanner.components.ts
@@ -1,19 +1,8 @@
-import {Component, ChangeDetectionStrategy, OnDestroy, OnInit, Input, ViewChild, TemplateRef, ElementRef } from "@angular/core";
+import {Component, ChangeDetectionStrategy, Input, TemplateRef } from "@angular/core";
 import { AtlasViewerConstantsServices } from "src/atlasViewer/atlasViewer.constantService.service";
 import { AuthService, User } from "src/services/auth.service";
-import { Store, select } from "@ngrx/store";
-import { ViewerConfiguration } from "src/services/state/viewerConfig.store";
-import { Subscription, Observable, merge, Subject, combineLatest } from "rxjs";
-import { safeFilter, isDefined, NEWVIEWER, SELECT_REGIONS, SELECT_PARCELLATION, CHANGE_NAVIGATION } from "src/services/stateStore.service";
-import { map, filter, distinctUntilChanged, bufferTime, delay, share, tap, withLatestFrom } from "rxjs/operators";
-import { regionFlattener } from "src/util/regionFlattener";
-import { ToastService } from "src/services/toastService.service";
-import { getSchemaIdFromName } from "src/util/pipes/templateParcellationDecoration.pipe";
 import { MatDialog } from "@angular/material";
 
-const compareParcellation = (o, n) => !o || !n
-  ? false
-  : o.name === n.name
 
 @Component({
   selector: 'signin-banner',
@@ -25,198 +14,18 @@ const compareParcellation = (o, n) => !o || !n
   changeDetection: ChangeDetectionStrategy.OnPush
 })
 
-export class SigninBanner implements OnInit, OnDestroy{
+export class SigninBanner{
 
-  public compareParcellation = compareParcellation
-
-  private subscriptions: Subscription[] = []
-  
-  public loadedTemplates$: Observable<any[]>
-
-  public selectedTemplate$: Observable<any>
-  public selectedParcellation$: Observable<any>
-
-  public selectedRegions$: Observable<any[]>
-  private selectedRegions: any[] = []
   @Input() darktheme: boolean
 
-  @ViewChild('publicationTemplate', {read:TemplateRef}) publicationTemplate: TemplateRef<any>
-  @ViewChild('settingBtn', {read: ElementRef}) settingBtn: ElementRef
-
-  public focusedDatasets$: Observable<any[]>
-  private userFocusedDataset$: Subject<any> = new Subject()
-  public focusedDatasets: any[] = []
-  private dismissToastHandler: () => void
+  public isMobile: boolean
 
   constructor(
     private constantService: AtlasViewerConstantsServices,
     private authService: AuthService,
-    private store: Store<ViewerConfiguration>,
-    private toastService: ToastService,
     private dialog: MatDialog
   ){
-    this.loadedTemplates$ = this.store.pipe(
-      select('viewerState'),
-      safeFilter('fetchedTemplates'),
-      map(state => state.fetchedTemplates)
-    )
-
-    this.selectedTemplate$ = this.store.pipe(
-      select('viewerState'),
-      select('templateSelected'),
-      filter(v => !!v),
-      distinctUntilChanged((o, n) => o.name === n.name),
-    )
-
-    this.selectedParcellation$ = this.store.pipe(
-      select('viewerState'),
-      select('parcellationSelected'),
-      filter(v => !!v)
-    )
-
-    this.selectedRegions$ = this.store.pipe(
-      select('viewerState'),
-      safeFilter('regionsSelected'),
-      map(state => state.regionsSelected),
-      distinctUntilChanged((arr1, arr2) => arr1.length === arr2.length && (arr1 as any[]).every((item, index) => item.name === arr2[index].name))
-    )
-
-    this.focusedDatasets$ = this.userFocusedDataset$.pipe(
-      filter(v => !!v),
-      withLatestFrom(
-        combineLatest(this.selectedTemplate$, this.selectedParcellation$)
-      ),
-    ).pipe(
-      map(([userFocusedDataset, [selectedTemplate, selectedParcellation]]) => {
-        const { type, ...rest } = userFocusedDataset
-        if (type === 'template') return { ...selectedTemplate,  ...rest}
-        if (type === 'parcellation') return { ...selectedParcellation, ...rest }
-        return { ...rest }
-      }),
-      bufferTime(100),
-      filter(arr => arr.length > 0),
-      /**
-       * merge properties field with the root level
-       * with the prop in properties taking priority
-       */
-      map(arr => arr.map(item => {
-        const { properties } = item
-        return {
-          ...item,
-          ...properties
-        }
-      })),
-      share()
-    )
-  }
-
-  ngOnInit(){
-
-    this.subscriptions.push(
-      this.selectedRegions$.subscribe(regions => {
-        this.selectedRegions = regions
-      })
-    )
-
-    this.subscriptions.push(
-      this.focusedDatasets$.subscribe(() => {
-        if (this.dismissToastHandler) this.dismissToastHandler()
-      })
-    )
-
-    this.subscriptions.push(
-      this.focusedDatasets$.pipe(
-        /**
-         * creates the illusion that the toast complete disappears before reappearing
-         */
-        delay(100)
-      ).subscribe(arr => {
-        this.focusedDatasets = arr
-        this.dismissToastHandler = this.toastService.showToast(this.publicationTemplate, {
-          dismissable: true,
-          timeout:7000
-        })
-      })
-    )
-  }
-
-  ngOnDestroy(){
-    this.subscriptions.forEach(s => s.unsubscribe())
-  }
-
-  changeTemplate({ current, previous }){
-    if (previous && current && current.name === previous.name) return
-    this.store.dispatch({
-      type: NEWVIEWER,
-      selectTemplate: current,
-      selectParcellation: current.parcellations[0]
-    })
-  }
-
-  changeParcellation({ current, previous }){
-    const { ngId: prevNgId} = previous
-    const { ngId: currNgId} = current
-    if (prevNgId === currNgId) return
-    this.store.dispatch({
-      type: SELECT_PARCELLATION,
-      selectParcellation: current
-    })
-  }
-
-  // TODO handle mobile
-  handleRegionClick({ mode = 'single', region }){
-    if (!region) return
-    
-    /**
-     * single click on region hierarchy => toggle selection
-     */
-    if (mode === 'single') {
-      const flattenedRegion = regionFlattener(region).filter(r => isDefined(r.labelIndex))
-      const flattenedRegionNames = new Set(flattenedRegion.map(r => r.name))
-      const selectedRegionNames = new Set(this.selectedRegions.map(r => r.name))
-      const selectAll = flattenedRegion.every(r => !selectedRegionNames.has(r.name))
-      this.store.dispatch({
-        type: SELECT_REGIONS,
-        selectRegions: selectAll
-          ? this.selectedRegions.concat(flattenedRegion)
-          : this.selectedRegions.filter(r => !flattenedRegionNames.has(r.name))
-      })
-    }
-
-    /**
-     * double click on region hierarchy => navigate to region area if it exists
-     */
-    if (mode === 'double') {
-
-      /**
-       * if position is defined, go to position (in nm)
-       * if not, show error messagea s toast
-       * 
-       * nb: currently, only supports a single triplet
-       */
-      if (region.position) {
-        this.store.dispatch({
-          type: CHANGE_NAVIGATION,
-          navigation: {
-            position: region.position,
-            animation: {}
-          }
-        })
-      } else {
-        this.toastService.showToast(`${region.name} does not have a position defined`, {
-          timeout: 5000,
-          dismissable: true
-        })
-      }
-    }
-  }
-
-  displayActiveParcellation(parcellation:any){
-    return `<div class="d-flex"><small>Parcellation</small> <small class = "flex-grow-1 mute-text">${parcellation ? '(' + parcellation.name + ')' : ''}</small> <span class = "fas fa-caret-down"></span></div>`
-  }
-
-  displayActiveTemplate(template: any) {
-    return `<div class="d-flex"><small>Template</small> <small class = "flex-grow-1 mute-text">${template ? '(' + template.name + ')' : ''}</small> <span class = "fas fa-caret-down"></span></div>`
+    this.isMobile = this.constantService.mobile
   }
 
   /**
@@ -242,56 +51,7 @@ export class SigninBanner implements OnInit, OnDestroy{
     this.constantService.showSigninSubject$.next(this.user)
   }
 
-  clearAllRegions(){
-    this.store.dispatch({
-      type: SELECT_REGIONS,
-      selectRegions: []
-    })
-  }
-
-  handleActiveDisplayBtnClicked(event, type: 'parcellation' | 'template'){
-    const { 
-      extraBtn,
-      event: extraBtnClickEvent
-    } = event
-
-    const { name } = extraBtn
-    const { kgSchema, kgId } = getSchemaIdFromName(name)
-    
-    this.userFocusedDataset$.next({
-      kgSchema,
-      kgId,
-      type
-    })
-  }
-
-  handleExtraBtnClicked(event, toastType: 'parcellation' | 'template'){
-    const { 
-      extraBtn,
-      inputItem,
-      event: extraBtnClickEvent
-    } = event
-
-    const { name } = extraBtn
-    const { kgSchema, kgId } = getSchemaIdFromName(name)
-
-    this.userFocusedDataset$.next({
-      ...inputItem,
-      kgSchema,
-      kgId
-    })
-
-    extraBtnClickEvent.stopPropagation()
-  }
-
-  get isMobile(){
-    return this.constantService.mobile
-  }
-
   get user() : User | null {
     return this.authService.user
   }
-
-  public flexItemIsMobileClass = 'mt-2'
-  public flexItemIsDesktopClass = 'mr-2'
 }
\ No newline at end of file
diff --git a/src/ui/signinBanner/signinBanner.template.html b/src/ui/signinBanner/signinBanner.template.html
index 00cdd6cf385fab29210487a5868c1fcd85ea7324..1da0fb01da873392fbe79901047520aaff3bf699 100644
--- a/src/ui/signinBanner/signinBanner.template.html
+++ b/src/ui/signinBanner/signinBanner.template.html
@@ -2,48 +2,6 @@
   class="d-flex"  
   [ngClass]="{ 'flex-column w-100 align-items-stretch' : isMobile}" >
 
-  <dropdown-component
-    (itemSelected)="changeTemplate($event)"
-    [checkSelected]="compareParcellation"
-    [activeDisplay]="displayActiveTemplate"
-    [selectedItem]="selectedTemplate$ | async"
-    [inputArray]="loadedTemplates$ | async | filterNull | appendTooltipTextPipe"
-    [ngClass]="isMobile ? flexItemIsMobileClass : flexItemIsDesktopClass"
-    (extraBtnClicked)="handleExtraBtnClicked($event, 'template')"
-    [activeDisplayBtns]="(selectedTemplate$ | async | templateParcellationsDecorationPipe)?.extraButtons"
-    (activeDisplayBtnClicked)="handleActiveDisplayBtnClicked($event, 'template')"
-    (listItemButtonClicked)="handleExtraBtnClicked($event, 'template')">
-  </dropdown-component>
-
-  <ng-container *ngIf="selectedTemplate$ | async as selectedTemplate">
-    <dropdown-component
-      *ngIf="selectedParcellation$ | async as selectedParcellation"
-      (itemSelected)="changeParcellation($event)"
-      [checkSelected]="compareParcellation"
-      [activeDisplay]="displayActiveParcellation"
-      [selectedItem]="selectedParcellation"
-      [inputArray]="selectedTemplate.parcellations | appendTooltipTextPipe"
-      [ngClass]="isMobile ? flexItemIsMobileClass : flexItemIsDesktopClass"
-      (extraBtnClicked)="handleExtraBtnClicked($event, 'parcellation')"
-      [activeDisplayBtns]="(selectedParcellation | templateParcellationsDecorationPipe)?.extraButtons"
-      (activeDisplayBtnClicked)="handleActiveDisplayBtnClicked($event, 'parcellation')"
-      (listItemButtonClicked)="handleExtraBtnClicked($event, 'parcellation')">
-
-    </dropdown-component>
-    <region-hierarchy
-      [selectedRegions]="selectedRegions$ | async | filterNull"
-      (singleClickRegion)="handleRegionClick({ mode: 'single', region: $event })"
-      (doubleClickRegion)="handleRegionClick({ mode: 'double', region: $event })"
-      (clearAllRegions)="clearAllRegions()"
-      [isMobile] = "isMobile"
-      *ngIf="selectedParcellation$ | async as selectedParcellation"
-      class="h-0"
-      [selectedParcellation]="selectedParcellation"
-      [ngClass]="isMobile ? flexItemIsMobileClass : flexItemIsDesktopClass">
-
-    </region-hierarchy>
-  </ng-container>
-
   <!-- help btn -->
   <div class="btnWrapper">
     <button
@@ -84,27 +42,3 @@
     </button>
   </div>
 </div>
-
-<!-- TODO somehow, using async pipe here does not work -->
-<!-- maybe have something to do with bufferTime, and that it replays from the beginning?  -->
-<ng-template #publicationTemplate>
-  <single-dataset-view
-    *ngFor="let focusedDataset of focusedDatasets"
-    [name]="focusedDataset.name"
-    [description]="focusedDataset.description"
-    [publications]="focusedDataset.publications"
-    [kgSchema]="focusedDataset.kgSchema"
-    [kgId]="focusedDataset.kgId">
-    
-  </single-dataset-view>
-</ng-template>
-
-<ng-template #settingTemplate>
-  <h2 mat-dialog-title>Settings</h2>
-  <mat-dialog-content>
-    <!-- required to avoid showing an ugly vertical scroll bar -->
-    <!-- TODO investigate why, then remove the friller class -->
-    <config-component class="mb-4 d-block">
-    </config-component>
-  </mat-dialog-content>
-</ng-template>
\ No newline at end of file
diff --git a/src/ui/ui.module.ts b/src/ui/ui.module.ts
index 52cedbda807d3d8a655e346495d87829fbf26b90..ae129d6220403450c0ebc00cff530c85e1cf842c 100644
--- a/src/ui/ui.module.ts
+++ b/src/ui/ui.module.ts
@@ -3,9 +3,9 @@ import { ComponentsModule } from "../components/components.module";
 
 import { NehubaViewerUnit } from "./nehubaContainer/nehubaViewer/nehubaViewer.component";
 import { NehubaContainer } from "./nehubaContainer/nehubaContainer.component";
-import { SplashScreen } from "./nehubaContainer/splashScreen/splashScreen.component";
+import { SplashScreen, GetTemplateImageSrcPipe, ImgSrcSetPipe } from "./nehubaContainer/splashScreen/splashScreen.component";
 import { LayoutModule } from "../layouts/layout.module";
-import { FormsModule } from "@angular/forms";
+import { FormsModule, ReactiveFormsModule } from "@angular/forms";
 
 import { GroupDatasetByRegion } from "../util/pipes/groupDataEntriesByRegion.pipe";
 import { filterRegionDataEntries } from "../util/pipes/filterRegionDataEntries.pipe";
@@ -17,7 +17,7 @@ import { LandmarkUnit } from "./nehubaContainer/landmarkUnit/landmarkUnit.compon
 import { SafeStylePipe } from "../util/pipes/safeStyle.pipe";
 import { PluginBannerUI } from "./pluginBanner/pluginBanner.component";
 import { CitationsContainer } from "./citation/citations.component";
-import { LayerBrowser } from "./layerbrowser/layerbrowser.component";
+import { LayerBrowser, LockedLayerBtnClsPipe } from "./layerbrowser/layerbrowser.component";
 import { TooltipModule } from "ngx-bootstrap/tooltip";
 import { KgEntryViewer } from "./kgEntryViewer/kgentry.component";
 import { SubjectViewer } from "./kgEntryViewer/subjectViewer/subjectViewer.component";
@@ -38,42 +38,44 @@ import { PopoverModule } from 'ngx-bootstrap/popover'
 import { DatabrowserModule } from "./databrowserModule/databrowser.module";
 import { SigninBanner } from "./signinBanner/signinBanner.components";
 import { SigninModal } from "./signinModal/signinModal.component";
-import { FilterNgLayer } from "src/util/pipes/filterNgLayer.pipe";
 import { UtilModule } from "src/util/util.module";
-import { RegionHierarchy } from "./regionHierachy/regionHierarchy.component";
-import { FilterNameBySearch } from "./regionHierachy/filterNameBySearch.pipe";
+import { RegionHierarchy } from "./viewerStateController/regionHierachy/regionHierarchy.component";
+import { FilterNameBySearch } from "./viewerStateController/regionHierachy/filterNameBySearch.pipe";
 import { StatusCardComponent } from "./nehubaContainer/statusCard/statusCard.component";
 import { CookieAgreement } from "./cookieAgreement/cookieAgreement.component";
 import { KGToS } from "./kgtos/kgtos.component";
 import { AngularMaterialModule } from 'src/ui/sharedModules/angularMaterial.module'
 import { TemplateParcellationsDecorationPipe } from "src/util/pipes/templateParcellationDecoration.pipe";
 import { AppendtooltipTextPipe } from "src/util/pipes/appendTooltipText.pipe";
-import { MatSliderModule, MatRippleModule } from "@angular/material";
 import { FourPanelLayout } from "./config/layouts/fourPanel/fourPanel.component";
 import { HorizontalOneThree } from "./config/layouts/h13/h13.component";
 import { VerticalOneThree } from "./config/layouts/v13/v13.component";
 import { SinglePanel } from "./config/layouts/single/single.component";
-import { DragDropModule } from "@angular/cdk/drag-drop";
 import { CurrentLayout } from "./config/currentLayout/currentLayout.component";
 import { MobileControlNubStylePipe } from "./nehubaContainer/pipes/mobileControlNubStyle.pipe";
+import { ScrollingModule } from "@angular/cdk/scrolling"
+import { HttpClientModule } from "@angular/common/http";
+import { GetFilenamePipe } from "src/util/pipes/getFilename.pipe";
+import { GetFileExtension } from "src/util/pipes/getFileExt.pipe";
+import { ViewerStateController } from "./viewerStateController/viewerState.component";
+import { BinSavedRegionsSelectionPipe, SavedRegionsSelectionBtnDisabledPipe } from "./viewerStateController/viewerState.pipes";
+import { RegionTextSearchAutocomplete } from "./viewerStateController/regionSearch/regionSearch.component";
+import { PluginBtnFabColorPipe } from "src/util/pipes/pluginBtnFabColor.pipe";
+import { KgSearchBtnColorPipe } from "src/util/pipes/kgSearchBtnColor.pipe";
 
 
 @NgModule({
   imports : [
+    HttpClientModule,
     FormsModule,
+    ReactiveFormsModule,
     LayoutModule,
     ComponentsModule,
     DatabrowserModule,
     UtilModule,
+    ScrollingModule,
     AngularMaterialModule,
 
-    /**
-     * move to angular material module
-     */
-    MatSliderModule,
-    DragDropModule,
-    MatRippleModule,
-
     PopoverModule.forRoot(),
     TooltipModule.forRoot()
   ],
@@ -104,6 +106,8 @@ import { MobileControlNubStylePipe } from "./nehubaContainer/pipes/mobileControl
     VerticalOneThree,
     SinglePanel,
     CurrentLayout,
+    ViewerStateController,
+    RegionTextSearchAutocomplete,
 
     /* pipes */
     GroupDatasetByRegion,
@@ -115,11 +119,19 @@ import { MobileControlNubStylePipe } from "./nehubaContainer/pipes/mobileControl
     SortDataEntriesToRegion,
     SpatialLandmarksToDataBrowserItemPipe,
     FilterNullPipe,
-    FilterNgLayer,
     FilterNameBySearch,
     TemplateParcellationsDecorationPipe,
     AppendtooltipTextPipe,
     MobileControlNubStylePipe,
+    GetTemplateImageSrcPipe,
+    ImgSrcSetPipe,
+    PluginBtnFabColorPipe,
+    KgSearchBtnColorPipe,
+    LockedLayerBtnClsPipe,
+    GetFilenamePipe,
+    GetFileExtension,
+    BinSavedRegionsSelectionPipe,
+    SavedRegionsSelectionBtnDisabledPipe,
 
     /* directive */
     DownloadDirective,
@@ -130,7 +142,7 @@ import { MobileControlNubStylePipe } from "./nehubaContainer/pipes/mobileControl
     /* dynamically created components needs to be declared here */
     NehubaViewerUnit,
     LayerBrowser,
-    PluginBannerUI
+    PluginBannerUI,
   ],
   exports : [
     SubjectViewer,
diff --git a/src/ui/regionHierachy/filterNameBySearch.pipe.ts b/src/ui/viewerStateController/regionHierachy/filterNameBySearch.pipe.ts
similarity index 100%
rename from src/ui/regionHierachy/filterNameBySearch.pipe.ts
rename to src/ui/viewerStateController/regionHierachy/filterNameBySearch.pipe.ts
diff --git a/src/ui/regionHierachy/regionHierarchy.component.ts b/src/ui/viewerStateController/regionHierachy/regionHierarchy.component.ts
similarity index 75%
rename from src/ui/regionHierachy/regionHierarchy.component.ts
rename to src/ui/viewerStateController/regionHierachy/regionHierarchy.component.ts
index f8b4fd35dba949a3b4fc7620ad67309a41f0f7c4..2fd52a746bd58437785353e315e68bcdf2b6f2a9 100644
--- a/src/ui/regionHierachy/regionHierarchy.component.ts
+++ b/src/ui/viewerStateController/regionHierachy/regionHierarchy.component.ts
@@ -1,6 +1,6 @@
 import { EventEmitter, Component, ElementRef, ViewChild, HostListener, OnInit, ChangeDetectionStrategy, ChangeDetectorRef, Input, Output, AfterViewInit } from "@angular/core";
 import {  Subscription, Subject, fromEvent } from "rxjs";
-import { buffer, debounceTime } from "rxjs/operators";
+import { buffer, debounceTime, tap } from "rxjs/operators";
 import { FilterNameBySearch } from "./filterNameBySearch.pipe";
 import { generateLabelIndexId } from "src/services/stateStore.service";
 
@@ -17,8 +17,8 @@ const getDisplayTreeNode : (searchTerm:string, selectedRegions:any[]) => (item:a
     && selectedRegions.findIndex(re =>
       generateLabelIndexId({ labelIndex: re.labelIndex, ngId: re.ngId }) === generateLabelIndexId({ ngId, labelIndex })
     ) >= 0
-      ? `<span class="regionSelected">${insertHighlight(name, searchTerm)}</span>` + (status ? ` <span class="text-muted">(${insertHighlight(status, searchTerm)})</span>` : ``)
-      : `<span class="regionNotSelected">${insertHighlight(name, searchTerm)}</span>` + (status ? ` <span class="text-muted">(${insertHighlight(status, searchTerm)})</span>` : ``)
+      ? `<span class="cursor-default regionSelected">${insertHighlight(name, searchTerm)}</span>` + (status ? ` <span class="text-muted">(${insertHighlight(status, searchTerm)})</span>` : ``)
+      : `<span class="cursor-default regionNotSelected">${insertHighlight(name, searchTerm)}</span>` + (status ? ` <span class="text-muted">(${insertHighlight(status, searchTerm)})</span>` : ``)
 }
 
 const getFilterTreeBySearch = (pipe:FilterNameBySearch, searchTerm:string) => (node:any) => pipe.transform([node.name, node.status], searchTerm)
@@ -38,9 +38,7 @@ export class RegionHierarchy implements OnInit, AfterViewInit{
   public selectedRegions: any[] = []
 
   @Input()
-  public selectedParcellation: any
-
-  @Input() isMobile: boolean;
+  public parcellationSelected: any
 
   private _showRegionTree: boolean = false
 
@@ -54,7 +52,7 @@ export class RegionHierarchy implements OnInit, AfterViewInit{
   private doubleClickRegion: EventEmitter<any> = new EventEmitter()
 
   @Output()
-  private clearAllRegions: EventEmitter<null> = new EventEmitter()
+  private clearAllRegions: EventEmitter<MouseEvent> = new EventEmitter()
 
   public searchTerm: string = ''
   private subscriptions: Subscription[] = []
@@ -62,6 +60,8 @@ export class RegionHierarchy implements OnInit, AfterViewInit{
   @ViewChild('searchTermInput', {read: ElementRef})
   private searchTermInput: ElementRef
 
+  public placeHolderText: string = `Start by selecting a template and a parcellation.`
+
   /**
    * set the height to max, bound by max-height
    */
@@ -95,17 +95,19 @@ export class RegionHierarchy implements OnInit, AfterViewInit{
   }
 
   ngOnChanges(){
-    this.aggregatedRegionTree = {
-      name: this.selectedParcellation.name,
-      children: this.selectedParcellation.regions
+    if (this.parcellationSelected) {
+      this.placeHolderText = `Search region in ${this.parcellationSelected.name}`
+      this.aggregatedRegionTree = {
+        name: this.parcellationSelected.name,
+        children: this.parcellationSelected.regions
+      }
     }
     this.displayTreeNode = getDisplayTreeNode(this.searchTerm, this.selectedRegions)
     this.filterTreeBySearch = getFilterTreeBySearch(this.filterNameBySearchPipe, this.searchTerm)
   }
 
   clearRegions(event:MouseEvent){
-    event.stopPropagation()
-    this.clearAllRegions.emit()
+    this.clearAllRegions.emit(event)
   }
 
   get showRegionTree(){
@@ -133,20 +135,6 @@ export class RegionHierarchy implements OnInit, AfterViewInit{
   }
 
   ngAfterViewInit(){
-    /**
-     * TODO
-     * bandaid fix on
-     * when region search loses focus, the searchTerm is cleared,
-     * but hierarchy filter does not reset
-     */
-    this.subscriptions.push(
-      fromEvent(this.searchTermInput.nativeElement, 'focus').pipe(
-        
-      ).subscribe(() => {
-        this.displayTreeNode = getDisplayTreeNode(this.searchTerm, this.selectedRegions)
-        this.filterTreeBySearch = getFilterTreeBySearch(this.filterNameBySearchPipe, this.searchTerm)
-      })
-    )
     this.subscriptions.push(
       fromEvent(this.searchTermInput.nativeElement, 'input').pipe(
         debounceTime(200)
@@ -156,13 +144,6 @@ export class RegionHierarchy implements OnInit, AfterViewInit{
     )
   }
 
-  getInputPlaceholder(parcellation:any) {
-    if (parcellation)
-      return `Search region in ${parcellation.name}`
-    else
-      return `Start by selecting a template and a parcellation.`
-  }
-
   escape(event:KeyboardEvent){
     this.showRegionTree = false
     this.searchTerm = '';
@@ -182,27 +163,12 @@ export class RegionHierarchy implements OnInit, AfterViewInit{
     })
   }
 
-  focusInput(event?:MouseEvent){
-    if (event) {
-      /**
-       * need to stop propagation, or @closeRegion will be triggered
-       */
-      event.stopPropagation()
-    }
-    this.searchTermInput.nativeElement.focus()
-    this.showRegionTree = true
-  }
-
   /* NB need to bind two way data binding like this. Or else, on searchInput blur, the flat tree will be rebuilt,
     resulting in first click to be ignored */
 
   changeSearchTerm(event: any) {
-    if (event.target.value === this.searchTerm)
-      return
+    if (event.target.value === this.searchTerm) return
     this.searchTerm = event.target.value
-    /**
-     * TODO maybe introduce debounce
-     */
     this.ngOnChanges()
     this.cdr.markForCheck()
   }
@@ -214,18 +180,17 @@ export class RegionHierarchy implements OnInit, AfterViewInit{
     /**
      * TODO figure out why @closeRegion gets triggered, but also, contains returns false
      */
-    if (event)
+    if (event) {
       event.stopPropagation()
+    }
     this.handleRegionTreeClickSubject.next(obj)
   }
 
   /* single click selects/deselects region(s) */
   private singleClick(obj: any) {
-    if (!obj)
-      return
+    if (!obj) return
     const { inputItem : region } = obj
-    if (!region)
-      return
+    if (!region) return
     this.singleClickRegion.emit(region)
   }
 
diff --git a/src/ui/viewerStateController/regionHierachy/regionHierarchy.style.css b/src/ui/viewerStateController/regionHierachy/regionHierarchy.style.css
new file mode 100644
index 0000000000000000000000000000000000000000..372068bbf85ed6be2b7f1f2ae06fb467f7f4a701
--- /dev/null
+++ b/src/ui/viewerStateController/regionHierachy/regionHierarchy.style.css
@@ -0,0 +1,57 @@
+
+div[treeContainer]
+{
+  padding:1em;
+  z-index: 3;
+
+  height:20em;
+  width: calc(100% + 4em);
+  overflow-y:auto;
+  overflow-x:hidden;
+
+  /* color:white;
+  background-color:rgba(12,12,12,0.8); */
+}
+
+div[hideScrollbarcontainer]
+{
+  overflow:hidden;
+  margin-top:2px;
+}
+
+input[type="text"]
+{
+  border:none;
+}
+
+
+.regionSearch
+{
+  width:20em;
+}
+
+.tree-header
+{
+  flex: 0 0 auto;
+}
+
+.tree-body
+{
+  flex: 1 1 auto;
+}
+
+:host
+{
+  display: flex;
+  flex-direction: column;
+}
+
+:host > mat-form-field
+{
+  flex: 0 0 auto;
+}
+
+:host > [hideScrollbarContainer]
+{
+  flex: 1 1 0;
+}
\ No newline at end of file
diff --git a/src/ui/regionHierachy/regionHierarchy.template.html b/src/ui/viewerStateController/regionHierachy/regionHierarchy.template.html
similarity index 69%
rename from src/ui/regionHierachy/regionHierarchy.template.html
rename to src/ui/viewerStateController/regionHierachy/regionHierarchy.template.html
index 91d9f552cb194227a8e4462ed6e9b82868e3f40c..5a55fdc8c328c231048001c9340687ebdf890f3b 100644
--- a/src/ui/regionHierachy/regionHierarchy.template.html
+++ b/src/ui/viewerStateController/regionHierachy/regionHierarchy.template.html
@@ -1,24 +1,18 @@
-<div class="input-group regionSearch">
+<mat-form-field class="w-100">
   <input 
     #searchTermInput
-    tabindex="0"
+    matInput
     (keydown.esc)="escape($event)"
-    (focus)="showRegionTree = true && !isMobile"
+    (focus)="showRegionTree = true"
     [value]="searchTerm"
-    class="form-control form-control-sm"
     type="text" 
     autocomplete="off"
-    [placeholder]="getInputPlaceholder(selectedParcellation)"/>
-
-</div>
+    [placeholder]="placeHolderText"/>
+</mat-form-field>
   
-<div
-  *ngIf="showRegionTree" 
-  hideScrollbarContainer>
-
+<div hideScrollbarContainer>
   <div
-    [ngStyle]="regionHierarchyHeight()"
-    class="d-flex flex-column"
+    class="d-flex flex-column h-100"
     treeContainer
     #treeContainer>
     <div class="tree-header d-inline-flex align-items-center">
@@ -34,7 +28,7 @@
     </div>
     
     <div
-      *ngIf="selectedParcellation && selectedParcellation.regions as regions"
+      *ngIf="parcellationSelected && parcellationSelected.regions as regions"
       class="tree-body">
       <flat-tree-component
         (treeNodeClick)="handleClickRegion($event)"
diff --git a/src/ui/viewerStateController/regionSearch/regionSearch.component.ts b/src/ui/viewerStateController/regionSearch/regionSearch.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c0e1783234cccfc6c9101b1f0fdc3290d8dea209
--- /dev/null
+++ b/src/ui/viewerStateController/regionSearch/regionSearch.component.ts
@@ -0,0 +1,149 @@
+import { Component, EventEmitter, Output, ViewChild, ElementRef, TemplateRef } from "@angular/core";
+import { Store, select } from "@ngrx/store";
+import { Observable } from "rxjs";
+import { map, distinctUntilChanged, startWith, withLatestFrom, filter, debounceTime, tap, share, shareReplay, take } from "rxjs/operators";
+import { getMultiNgIdsRegionsLabelIndexMap, generateLabelIndexId } from "src/services/stateStore.service";
+import { FormControl } from "@angular/forms";
+import { MatAutocompleteSelectedEvent, MatAutocompleteTrigger, MatDialog } from "@angular/material";
+import { ADD_TO_REGIONS_SELECTION_WITH_IDS, SELECT_REGIONS } from "src/services/state/viewerState.store";
+import { VIEWERSTATE_ACTION_TYPES } from "../viewerState.component";
+
+const filterRegionBasedOnText = searchTerm => region => region.name.toLowerCase().includes(searchTerm.toLowerCase())
+
+@Component({
+  selector: 'region-text-search-autocomplete',
+  templateUrl: './regionSearch.template.html',
+  styleUrls: [
+    './regionSearch.style.css'
+  ]
+})
+
+export class RegionTextSearchAutocomplete{
+
+  @ViewChild('autoTrigger', {read: ElementRef}) autoTrigger: ElementRef 
+  @ViewChild('regionHierarchy', {read:TemplateRef}) regionHierarchyTemplate: TemplateRef<any>
+  constructor(
+    private store$: Store<any>,
+    private dialog: MatDialog,
+  ){
+
+    const viewerState$ = this.store$.pipe(
+      select('viewerState'),
+      shareReplay(1)
+    )
+
+    this.regionsWithLabelIndex$ = viewerState$.pipe(
+      select('parcellationSelected'),
+      distinctUntilChanged(),
+      map(parcellationSelected => {
+        const returnArray = []
+        const ngIdMap = getMultiNgIdsRegionsLabelIndexMap(parcellationSelected)
+        for (const [ngId, labelIndexMap] of ngIdMap) {
+          for (const [labelIndex, region] of labelIndexMap){
+            returnArray.push({
+              ...region,
+              ngId,
+              labelIndex,
+              labelIndexId: generateLabelIndexId({ ngId, labelIndex })
+            })
+          }
+        }
+        return returnArray
+      })
+    ) 
+
+    this.autocompleteList$ = this.formControl.valueChanges.pipe(
+      startWith(''),
+      debounceTime(200),
+      withLatestFrom(this.regionsWithLabelIndex$.pipe(
+        startWith([])
+      )),
+      map(([searchTerm, regionsWithLabelIndex]) => regionsWithLabelIndex.filter(filterRegionBasedOnText(searchTerm))),
+      map(arr => arr.slice(0, 5))
+    )
+
+    this.regionsSelected$ = viewerState$.pipe(
+      select('regionsSelected'),
+      distinctUntilChanged(),
+      shareReplay(1)
+    )
+
+    this.parcellationSelected$ = viewerState$.pipe(
+      select('parcellationSelected'),
+      distinctUntilChanged(),
+      shareReplay(1)
+    )
+  }
+
+  public optionSelected(ev: MatAutocompleteSelectedEvent){
+    const id = ev.option.value
+    this.store$.dispatch({
+      type: ADD_TO_REGIONS_SELECTION_WITH_IDS,
+      selectRegionIds : [id]
+    })
+
+    this.autoTrigger.nativeElement.value = ''
+    this.autoTrigger.nativeElement.focus()
+  }
+
+  private regionsWithLabelIndex$: Observable<any[]>
+  public autocompleteList$: Observable<any[]>
+  public formControl = new FormControl()
+
+  public regionsSelected$: Observable<any>
+  public parcellationSelected$: Observable<any>
+
+
+  @Output()
+  public focusedStateChanged: EventEmitter<boolean> = new EventEmitter()
+
+  private _focused: boolean = false
+  set focused(val: boolean){
+    this._focused = val
+    this.focusedStateChanged.emit(val)
+  }
+  get focused(){
+    return this._focused
+  }
+
+  public deselectAllRegions(event: MouseEvent){
+    this.store$.dispatch({
+      type: SELECT_REGIONS,
+      selectRegions: []
+    })
+  }
+
+  // TODO handle mobile
+  handleRegionClick({ mode = null, region = null } = {}){
+    const type = mode === 'single'
+      ? VIEWERSTATE_ACTION_TYPES.SINGLE_CLICK_ON_REGIONHIERARCHY
+      : mode === 'double'
+        ? VIEWERSTATE_ACTION_TYPES.DOUBLE_CLICK_ON_REGIONHIERARCHY
+        : ''
+    this.store$.dispatch({
+      type,
+      payload: { region }
+    })
+  }
+
+  showHierarchy(event:MouseEvent){
+    const dialog = this.dialog.open(this.regionHierarchyTemplate, {
+      height: '90vh',
+      width: '90vw'
+    })
+
+    /**
+     * keep sleight of hand shown while modal is shown
+     * 
+     */
+    this.focused = true
+    
+    /**
+     * take 1 to avoid memory leak
+     */
+    dialog.afterClosed().pipe(
+      take(1)
+    ).subscribe(() => this.focused = false)
+  }
+
+}
\ No newline at end of file
diff --git a/src/ui/viewerStateController/regionSearch/regionSearch.style.css b/src/ui/viewerStateController/regionSearch/regionSearch.style.css
new file mode 100644
index 0000000000000000000000000000000000000000..17cda15a41ee862383f3f4e802121021525894f9
--- /dev/null
+++ b/src/ui/viewerStateController/regionSearch/regionSearch.style.css
@@ -0,0 +1,4 @@
+region-hierarchy
+{
+  height: 100%;
+}
\ No newline at end of file
diff --git a/src/ui/viewerStateController/regionSearch/regionSearch.template.html b/src/ui/viewerStateController/regionSearch/regionSearch.template.html
new file mode 100644
index 0000000000000000000000000000000000000000..ef828ac2e265761736d416066215e137c39b3053
--- /dev/null
+++ b/src/ui/viewerStateController/regionSearch/regionSearch.template.html
@@ -0,0 +1,45 @@
+<div class="d-flex flex-row align-items-center">
+
+  <form class="flex-grow-1 flex-shrink-1">
+    <mat-form-field class="w-100">
+      <input
+        placeholder="Regions"
+        #autoTrigger
+        #trigger="matAutocompleteTrigger"
+        type="text"
+        matInput
+        [formControl]="formControl"
+        [matAutocomplete]="auto">
+    </mat-form-field>
+    <mat-autocomplete
+      (opened)="focused = true"
+      (closed)="focused = false"
+      (optionSelected)="optionSelected($event)"
+      autoActiveFirstOption
+      #auto="matAutocomplete">
+      <mat-option
+        *ngFor="let region of autocompleteList$ | async"
+        [value]="region.labelIndexId">
+        {{ region.name }}
+      </mat-option>
+    </mat-autocomplete>
+  </form>
+    
+  <button
+    class="flex-grow-0 flex-shrink-0"
+    (click)="showHierarchy($event)"
+    mat-icon-button color="primary">
+    <i class="fas fa-sitemap"></i>
+  </button>
+</div>
+
+<ng-template #regionHierarchy>
+  <region-hierarchy
+    [selectedRegions]="regionsSelected$ | async | filterNull"
+    (singleClickRegion)="handleRegionClick({ mode: 'single', region: $event })"
+    (doubleClickRegion)="handleRegionClick({ mode: 'double', region: $event })"
+    (clearAllRegions)="deselectAllRegions($event)"
+    [parcellationSelected]="parcellationSelected$ | async">
+  
+  </region-hierarchy>
+</ng-template>
diff --git a/src/ui/viewerStateController/viewerState.component.ts b/src/ui/viewerStateController/viewerState.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..726c1e895802beea84b8fe44fa72ca0685ae159e
--- /dev/null
+++ b/src/ui/viewerStateController/viewerState.component.ts
@@ -0,0 +1,304 @@
+import { Component, ViewChild, TemplateRef, OnInit } from "@angular/core";
+import { Store, select } from "@ngrx/store";
+import { Observable, Subject, combineLatest, Subscription } from "rxjs";
+import { distinctUntilChanged, shareReplay, bufferTime, filter, map, withLatestFrom, delay, take, tap } from "rxjs/operators";
+import { SELECT_REGIONS, USER_CONFIG_ACTION_TYPES } from "src/services/stateStore.service";
+import { DESELECT_REGIONS, CHANGE_NAVIGATION } from "src/services/state/viewerState.store";
+import { ToastService } from "src/services/toastService.service";
+import { getSchemaIdFromName } from "src/util/pipes/templateParcellationDecoration.pipe";
+import { MatDialog, MatSelectChange, MatBottomSheet, MatBottomSheetRef } from "@angular/material";
+import { ExtraButton } from "src/components/radiolist/radiolist.component";
+import { DialogService } from "src/services/dialogService.service";
+import { RegionSelection } from "src/services/state/userConfigState.store";
+
+const compareWith = (o, n) => !o || !n
+  ? false
+  : o.name === n.name
+
+@Component({
+  selector: 'viewer-state-controller',
+  templateUrl: './viewerState.template.html',
+  styleUrls: [
+    './viewerState.style.css'
+  ]
+})
+
+export class ViewerStateController implements OnInit{
+
+  @ViewChild('publicationTemplate', {read:TemplateRef}) publicationTemplate: TemplateRef<any>
+  @ViewChild('savedRegionBottomSheetTemplate', {read:TemplateRef}) savedRegionBottomSheetTemplate: TemplateRef<any>
+
+  public focused: boolean = false
+
+  private subscriptions: Subscription[] = []
+
+  public availableTemplates$: Observable<any[]>
+  public availableParcellations$: Observable<any[]>
+
+  public templateSelected$: Observable<any>
+  public parcellationSelected$: Observable<any>
+  public regionsSelected$: Observable<any>
+
+  public savedRegionsSelections$: Observable<any[]>
+
+  public focusedDatasets$: Observable<any[]>
+  private userFocusedDataset$: Subject<any> = new Subject()
+  private dismissToastHandler: () => void
+
+  public compareWith = compareWith
+
+  private savedRegionBottomSheetRef: MatBottomSheetRef
+
+  constructor(
+    private store$: Store<any>,
+    private toastService: ToastService,
+    private dialogService: DialogService,
+    private bottomSheet: MatBottomSheet
+  ){
+    const viewerState$ = this.store$.pipe(
+      select('viewerState'),
+      shareReplay(1)
+    )
+
+    this.savedRegionsSelections$ = this.store$.pipe(
+      select('userConfigState'),
+      select('savedRegionsSelection'),
+      shareReplay(1)
+    )
+
+    this.templateSelected$ = viewerState$.pipe(
+      select('templateSelected'),
+      distinctUntilChanged()
+    )
+
+    this.parcellationSelected$ = viewerState$.pipe(
+      select('parcellationSelected'),
+      distinctUntilChanged(),
+      shareReplay(1)
+    )
+
+    this.regionsSelected$ = viewerState$.pipe(
+      select('regionsSelected'),
+      distinctUntilChanged(),
+      shareReplay(1)
+    )
+
+    this.availableTemplates$ = viewerState$.pipe(
+      select('fetchedTemplates'),
+      distinctUntilChanged()
+    )
+
+    this.availableParcellations$ = this.templateSelected$.pipe(
+      select('parcellations')
+    )
+    
+    this.focusedDatasets$ = this.userFocusedDataset$.pipe(
+      filter(v => !!v),
+      withLatestFrom(
+        combineLatest(this.templateSelected$, this.parcellationSelected$)
+      ),
+    ).pipe(
+      map(([userFocusedDataset, [selectedTemplate, selectedParcellation]]) => {
+        const { type, ...rest } = userFocusedDataset
+        if (type === 'template') return { ...selectedTemplate,  ...rest}
+        if (type === 'parcellation') return { ...selectedParcellation, ...rest }
+        return { ...rest }
+      }),
+      bufferTime(100),
+      filter(arr => arr.length > 0),
+      /**
+       * merge properties field with the root level
+       * with the prop in properties taking priority
+       */
+      map(arr => arr.map(item => {
+        const { properties } = item
+        return {
+          ...item,
+          ...properties
+        }
+      })),
+      shareReplay(1)
+    )
+  }
+
+  ngOnInit(){
+    this.subscriptions.push(
+      this.savedRegionsSelections$.pipe(
+        filter(srs => srs.length === 0)
+      ).subscribe(() => this.savedRegionBottomSheetRef && this.savedRegionBottomSheetRef.dismiss())
+    )
+    this.subscriptions.push(
+      this.focusedDatasets$.subscribe(() => this.dismissToastHandler && this.dismissToastHandler())
+    )
+    this.subscriptions.push(
+      this.focusedDatasets$.pipe(
+        /**
+         * creates the illusion that the toast complete disappears before reappearing
+         */
+        delay(100)
+      ).subscribe(() => this.dismissToastHandler = this.toastService.showToast(this.publicationTemplate, {
+        dismissable: true,
+        timeout:7000
+      }))
+    )
+  }
+
+  handleActiveDisplayBtnClicked(event: MouseEvent, type: 'parcellation' | 'template', extraBtn: ExtraButton, inputItem:any = {}){
+    const { name } = extraBtn
+    const { kgSchema, kgId } = getSchemaIdFromName(name)
+    this.userFocusedDataset$.next({
+      ...inputItem,
+      kgSchema,
+      kgId
+    })
+  }
+
+  handleTemplateChange(event:MatSelectChange){
+    
+    this.store$.dispatch({
+      type: ACTION_TYPES.SELECT_TEMPLATE_WITH_NAME,
+      payload: {
+        name: event.value
+      }
+    })
+  }
+
+  handleParcellationChange(event:MatSelectChange){
+    if (!event.value) return
+    this.store$.dispatch({
+      type: ACTION_TYPES.SELECT_PARCELLATION_WITH_NAME,
+      payload: {
+        name: event.value
+      }
+    })
+  }
+
+  loadSavedRegion(event:MouseEvent, savedRegionsSelection:RegionSelection){
+    this.store$.dispatch({
+      type: USER_CONFIG_ACTION_TYPES.LOAD_REGIONS_SELECTION,
+      payload: {
+        savedRegionsSelection
+      }
+    })
+  }
+
+  public editSavedRegion(event: MouseEvent, savedRegionsSelection: RegionSelection){
+    event.preventDefault()
+    event.stopPropagation()
+    this.dialogService.getUserInput({
+      defaultValue: savedRegionsSelection.name,
+      placeholder: `Enter new name`,
+      title: 'Edit name'
+    }).then(name => {
+      if (!name) throw new Error('user cancelled')
+      this.store$.dispatch({
+        type: USER_CONFIG_ACTION_TYPES.UPDATE_REGIONS_SELECTION,
+        payload: {
+          ...savedRegionsSelection,
+          name
+        }
+      })
+    }).catch(e => {
+      // TODO catch user cancel
+    })
+  }
+  public removeSavedRegion(event: MouseEvent, savedRegionsSelection: RegionSelection){
+    event.preventDefault()
+    event.stopPropagation()
+    this.store$.dispatch({
+      type: USER_CONFIG_ACTION_TYPES.DELETE_REGIONS_SELECTION,
+      payload: {
+        ...savedRegionsSelection
+      }
+    })
+  }
+
+
+  displayActiveParcellation(parcellation:any){
+    return `<div class="d-flex"><small>Parcellation</small> <small class = "flex-grow-1 mute-text">${parcellation ? '(' + parcellation.name + ')' : ''}</small> <span class = "fas fa-caret-down"></span></div>`
+  }
+
+  displayActiveTemplate(template: any) {
+    return `<div class="d-flex"><small>Template</small> <small class = "flex-grow-1 mute-text">${template ? '(' + template.name + ')' : ''}</small> <span class = "fas fa-caret-down"></span></div>`
+  }
+
+  public loadSelection(event: MouseEvent){
+    this.focused = true
+    
+    this.savedRegionBottomSheetRef = this.bottomSheet.open(this.savedRegionBottomSheetTemplate)
+    this.savedRegionBottomSheetRef.afterDismissed()
+      .subscribe(val => {
+        
+      }, error => {
+
+      }, () => {
+        this.focused = false
+        this.savedRegionBottomSheetRef = null
+      })
+  }
+
+  public saveSelection(event: MouseEvent){
+    this.focused = true
+    this.dialogService.getUserInput({
+      defaultValue: `Saved Region`,
+      placeholder: `Name the selection`,
+      title: 'Save region selection'
+    })
+      .then(name => {
+        if (!name) throw new Error('User cancelled')
+        this.store$.dispatch({
+          type: USER_CONFIG_ACTION_TYPES.SAVE_REGIONS_SELECTION,
+          payload: { name }
+        })
+      })
+      .catch(e => {
+        /**
+         * USER CANCELLED, HANDLE
+         */
+      })
+      .finally(() => this.focused = false)
+  }
+
+  public deselectAllRegions(event: MouseEvent){
+    this.store$.dispatch({
+      type: SELECT_REGIONS,
+      selectRegions: []
+    })
+  }
+
+  public deselectRegion(event: MouseEvent, region: any){
+    this.store$.dispatch({
+      type: DESELECT_REGIONS,
+      deselectRegions: [region]
+    })
+  }
+
+  public gotoRegion(event: MouseEvent, region:any){
+    if (region.position) {
+      this.store$.dispatch({
+        type: CHANGE_NAVIGATION,
+        navigation: {
+          position: region.position,
+          animation: {}
+        }
+      })
+    } else {
+      /**
+       * TODO convert to snack bar
+       */
+      this.toastService.showToast(`${region.name} does not have a position defined`, {
+        timeout: 5000,
+        dismissable: true
+      })
+    }
+  }
+}
+
+const ACTION_TYPES = {
+  SINGLE_CLICK_ON_REGIONHIERARCHY: 'SINGLE_CLICK_ON_REGIONHIERARCHY',
+  DOUBLE_CLICK_ON_REGIONHIERARCHY: 'DOUBLE_CLICK_ON_REGIONHIERARCHY',
+  SELECT_TEMPLATE_WITH_NAME: 'SELECT_TEMPLATE_WITH_NAME',
+  SELECT_PARCELLATION_WITH_NAME: 'SELECT_PARCELLATION_WITH_NAME',
+}
+
+export const VIEWERSTATE_ACTION_TYPES = ACTION_TYPES
diff --git a/src/ui/viewerStateController/viewerState.pipes.ts b/src/ui/viewerStateController/viewerState.pipes.ts
new file mode 100644
index 0000000000000000000000000000000000000000..659d35778f3378966cb58f82d47f789e9ca95d89
--- /dev/null
+++ b/src/ui/viewerStateController/viewerState.pipes.ts
@@ -0,0 +1,38 @@
+import { Pipe, PipeTransform } from "@angular/core";
+import { RegionSelection } from "src/services/state/userConfigState.store";
+
+@Pipe({
+  name: 'binSavedRegionsSelectionPipe'
+})
+
+export class BinSavedRegionsSelectionPipe implements PipeTransform{
+  public transform(regionSelections:RegionSelection[]):{parcellationSelected:any, templateSelected:any, regionSelections: RegionSelection[]}[]{
+    const returnMap = new Map()
+    for (let regionSelection of regionSelections){
+      const key = `${regionSelection.templateSelected.name}\n${regionSelection.parcellationSelected.name}`
+      const existing = returnMap.get(key)
+      if (existing) existing.push(regionSelection)
+      else returnMap.set(key, [regionSelection])
+    }
+    return Array.from(returnMap)
+      .map(([_, regionSelections]) => {
+        const {parcellationSelected = null, templateSelected = null} = regionSelections[0] || {}
+        return {
+          regionSelections,
+          parcellationSelected,
+          templateSelected
+        }
+      })
+  }
+}
+
+@Pipe({
+  name: 'savedRegionsSelectionBtnDisabledPipe'
+})
+
+export class SavedRegionsSelectionBtnDisabledPipe implements PipeTransform{
+  public transform(regionSelection: RegionSelection, templateSelected: any, parcellationSelected: any): boolean{
+    return regionSelection.parcellationSelected.name !== parcellationSelected.name
+      || regionSelection.templateSelected.name !== templateSelected.name
+  }
+}
\ No newline at end of file
diff --git a/src/ui/viewerStateController/viewerState.style.css b/src/ui/viewerStateController/viewerState.style.css
new file mode 100644
index 0000000000000000000000000000000000000000..0da6d0ea50d5d1df72cd96e1c3bd1273b6c9741b
--- /dev/null
+++ b/src/ui/viewerStateController/viewerState.style.css
@@ -0,0 +1,35 @@
+.virtual-scroll-viewport-container
+{
+  height: 20em;
+  width: 20em;
+  overflow: hidden;
+}
+
+.virtual-scroll-viewport-container > cdk-virtual-scroll-viewport
+{
+  width: 100%;
+  height: 100%;
+  box-sizing: content-box;
+  padding-right: 3em;
+}
+
+.virtual-scroll-row
+{
+  width: 20em;
+}
+
+/* required to match virtual scroll itemSize property */
+.virtual-scroll-unit
+{
+  height: 26px
+}
+
+.selected-region-container
+{
+  flex: 1 1 auto;
+}
+
+.selected-region-actionbtn
+{
+  flex: 0 0 auto;
+}
diff --git a/src/ui/viewerStateController/viewerState.template.html b/src/ui/viewerStateController/viewerState.template.html
new file mode 100644
index 0000000000000000000000000000000000000000..9e1718231d4037b57fefcd2155df8bc51e12e362
--- /dev/null
+++ b/src/ui/viewerStateController/viewerState.template.html
@@ -0,0 +1,202 @@
+<mat-card>
+
+  <!-- template selection -->
+  <mat-form-field>
+    <mat-label>
+      Template
+    </mat-label>
+    <mat-select
+      [value]="(templateSelected$ | async)?.name"
+      (selectionChange)="handleTemplateChange($event)"
+      (openedChange)="focused = $event">
+      <mat-option
+        *ngFor="let template of (availableTemplates$ | async)"
+        [value]="template.name">
+        {{ template.name }}
+      </mat-option>
+    </mat-select>
+  </mat-form-field>
+  <ng-container *ngIf="templateSelected$ | async as templateSelected">
+    <ng-container *ngIf="(templateSelected | templateParcellationsDecorationPipe)?.extraButtons as extraButtons">
+      <button
+        *ngFor="let extraBtn of extraButtons"
+        (click)="handleActiveDisplayBtnClicked($event, 'template', extraBtn, templateSelected)"
+        mat-icon-button>
+        <i [class]="extraBtn.faIcon"></i>
+      </button>
+    </ng-container>
+  </ng-container>
+
+  <!-- parcellation selection -->
+  <mat-form-field *ngIf="templateSelected$ | async as templateSelected">
+    <mat-label>
+      Parcellation
+    </mat-label>
+    <mat-select
+      (selectionChange)="handleParcellationChange($event)"
+      [value]="(parcellationSelected$ | async)?.name"
+      (openedChange)="focused = $event">
+      <mat-option
+        *ngFor="let parcellation of (templateSelected.parcellations | appendTooltipTextPipe)"
+        [value]="parcellation.name">
+        {{ parcellation.name }}
+      </mat-option>
+    </mat-select>
+  </mat-form-field>
+
+  <ng-container *ngIf="parcellationSelected$ | async as parcellationSelected">
+    <ng-container *ngIf="(parcellationSelected | templateParcellationsDecorationPipe)?.extraButtons as extraButtons">
+      <button
+        *ngFor="let extraBtn of extraButtons"
+        (click)="handleActiveDisplayBtnClicked($event, 'parcellation', extraBtn, parcellationSelected)"
+        mat-icon-button>
+        <i [class]="extraBtn.faIcon"></i>
+      </button>
+    </ng-container>
+  </ng-container>
+
+  <!-- divider -->
+  <mat-divider></mat-divider>
+
+  <!-- selected regions -->
+
+  <div class="d-flex">
+    <region-text-search-autocomplete
+      (focusedStateChanged)="focused = $event">
+    </region-text-search-autocomplete>
+  </div>
+
+  <!-- chips -->
+  <mat-card class="w-20em mh-10em overflow-auto overflow-x-hidden">
+    <mat-chip-list class="mat-chip-list-stacked" #selectedRegionsChipList>
+      <mat-chip class="w-100" *ngFor="let region of (regionsSelected$ | async)">
+        <span class="flex-grow-1 flex-shrink-1 text-truncate">
+          {{ region.name }}
+        </span>
+        <button
+          *ngIf="region.position"
+          (click)="gotoRegion($event, region)"
+          mat-icon-button>
+          <i class="fas fa-map-marked-alt"></i>
+        </button>
+        <button
+          (click)="deselectRegion($event, region)"
+          mat-icon-button>
+          <i class="fas fa-trash"></i>
+        </button>
+      </mat-chip>
+    </mat-chip-list>
+
+    <!-- place holder when no regions has been selected -->
+    <span class="muted"  *ngIf="(regionsSelected$ | async).length === 0">
+      No regions selected. Double click on any regions in the viewer, or use the search tool to select regions of interest.
+    </span>
+  </mat-card>
+
+  <!-- control btns -->
+  <div class="mt-2 mb-2 d-flex justify-content-between">
+    <div class="d-flex">
+
+      <!-- save  -->
+      <button
+        matTooltip="Save this selection of regions"
+        matTooltipPosition="below"
+        mat-button
+        (click)="saveSelection($event)"
+        color="primary">
+        <i class="fas fa-save"></i>
+        
+      </button>
+
+      <!-- load -->
+      <button
+        (click)="loadSelection($event)"
+        matTooltip="Load a selection of regions"
+        matTooltipPosition="below"
+        mat-button
+        color="primary"
+        [disabled]="(savedRegionsSelections$ | async)?.length === 0">
+        <i
+          matBadgeColor="accent"
+          [matBadgeOverlap]="false"
+          [matBadge]="(savedRegionsSelections$ | async)?.length > 0 ? (savedRegionsSelections$ | async)?.length : null"
+          class="fas fa-folder-open"></i>
+        
+      </button>
+    </div>
+
+    <!-- deselect all  -->
+    <button
+      (click)="deselectAllRegions($event)"
+      matTooltip="Deselect all selected regions"
+      matTooltipPosition="below"
+      mat-raised-button
+      color="warn"
+      [disabled]="(regionsSelected$ | async)?.length === 0">
+      <i class="fas fa-trash"></i>
+    </button>
+  </div>
+</mat-card>
+
+<ng-template #publicationTemplate>
+  <single-dataset-view
+    *ngFor="let focusedDataset of (focusedDatasets$ | async)"
+    [name]="focusedDataset.name"
+    [description]="focusedDataset.description"
+    [publications]="focusedDataset.publications"
+    [kgSchema]="focusedDataset.kgSchema"
+    [kgId]="focusedDataset.kgId">
+    
+  </single-dataset-view>
+</ng-template>
+
+<!-- bottom sheet for saved regions  -->
+<ng-template #savedRegionBottomSheetTemplate>
+  <mat-action-list>
+
+    <!-- separated (binned) by template/parcellation -->
+    <ng-container *ngFor="let binnedRS of (savedRegionsSelections$ | async | binSavedRegionsSelectionPipe); let index = index">
+
+      <!-- only render divider if it is not the leading element -->
+      <mat-divider *ngIf="index !== 0"></mat-divider>
+
+      <!-- header -->
+      <h3 mat-subheader>
+        {{ binnedRS.templateSelected.name }} / {{ binnedRS.parcellationSelected.name }}
+      </h3>
+
+      <!-- ng for all saved regions -->
+      <button
+        *ngFor="let savedRegionsSelection of binnedRS.regionSelections"
+        (click)="loadSavedRegion($event, savedRegionsSelection)"
+        mat-list-item>
+        <!-- [class]="savedRegionsSelection | savedRegionsSelectionBtnDisabledPipe : (templateSelected$ | async) : (parcellationSelected$ | async) ? 'text-muted' : ''" -->
+        <!-- [disabled]="savedRegionsSelection | savedRegionsSelectionBtnDisabledPipe : (templateSelected$ | async) : (parcellationSelected$ | async)" -->
+        <!-- main content -->
+        <span class="flex-grow-0 flex-shrink-1">
+          {{ savedRegionsSelection.name }}
+        </span>
+        <small class="ml-1 mr-1 text-muted flex-grow-1 flex-shrink-0">
+          ({{ savedRegionsSelection.regionsSelected.length }} selected regions)
+        </small>
+
+        <!-- edit btn -->
+        <button
+          (mousedown)="$event.stopPropagation()"
+          (click)="editSavedRegion($event, savedRegionsSelection)"
+          mat-icon-button>
+          <i class="fas fa-edit"></i>
+        </button>
+
+        <!-- trash btn -->
+        <button
+          (mousedown)="$event.stopPropagation()"
+          (click)="removeSavedRegion($event, savedRegionsSelection)"
+          mat-icon-button
+          color="warn">
+          <i class="fas fa-trash"></i>
+        </button>
+      </button>
+    </ng-container>
+  </mat-action-list>
+</ng-template>
\ No newline at end of file
diff --git a/src/ui/viewerStateController/viewerState.useEffect.ts b/src/ui/viewerStateController/viewerState.useEffect.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4c8232e1c4509088bf1ac287bdd5057508505bef
--- /dev/null
+++ b/src/ui/viewerStateController/viewerState.useEffect.ts
@@ -0,0 +1,180 @@
+import { Subscription, Observable } from "rxjs";
+import { Injectable, OnInit, OnDestroy } from "@angular/core";
+import { Actions, ofType, Effect } from "@ngrx/effects";
+import { Store, select, Action } from "@ngrx/store";
+import { ToastService } from "src/services/toastService.service";
+import { shareReplay, distinctUntilChanged, map, withLatestFrom, filter } from "rxjs/operators";
+import { VIEWERSTATE_ACTION_TYPES } from "./viewerState.component";
+import { CHANGE_NAVIGATION, SELECT_REGIONS, NEWVIEWER, GENERAL_ACTION_TYPES, SELECT_PARCELLATION, isDefined } from "src/services/stateStore.service";
+import { regionFlattener } from "src/util/regionFlattener";
+
+@Injectable({
+  providedIn: 'root'
+})
+
+export class ViewerStateControllerUseEffect implements OnInit, OnDestroy{
+
+  private subscriptions: Subscription[] = []
+
+  private selectedRegions$: Observable<any[]>
+
+  @Effect()
+  singleClickOnHierarchy$: Observable<any>
+
+  @Effect()
+  selectTemplateWithName$: Observable<any>
+  
+  @Effect()
+  selectParcellationWithName$: Observable<any>
+
+  doubleClickOnHierarchy$: Observable<any>
+
+  constructor(
+    private actions$: Actions,
+    private store$: Store<any>,
+    private toastService: ToastService
+  ){
+    const viewerState$ = this.store$.pipe(
+      select('viewerState'),
+      shareReplay(1)
+    )
+
+    this.selectedRegions$ = viewerState$.pipe(
+      select('regionsSelected'),
+      distinctUntilChanged()
+    )
+
+    this.selectParcellationWithName$ = this.actions$.pipe(
+      ofType(VIEWERSTATE_ACTION_TYPES.SELECT_PARCELLATION_WITH_NAME),
+      map(action => {
+        const { payload = {} } = action as ViewerStateAction
+        const { name } = payload
+        return name
+      }),
+      filter(name => !!name),
+      withLatestFrom(viewerState$.pipe(
+        select('parcellationSelected')
+      )),
+      filter(([name,  parcellationSelected]) => {
+        if (parcellationSelected && parcellationSelected.name === name) return false
+        return true
+      }),
+      map(([name,  _]) => name),
+      withLatestFrom(viewerState$.pipe(
+        select('templateSelected')
+      )),
+      map(([name, templateSelected]) => {
+
+        const { parcellations: availableParcellations } = templateSelected
+        const newParcellation = availableParcellations.find(t => t.name === name)
+        if (!newParcellation) {
+          return {
+            type: GENERAL_ACTION_TYPES.ERROR,
+            payload: {
+              message: 'Selected parcellation not found.'
+            }
+          }
+        }
+        return {
+          type: SELECT_PARCELLATION,
+          selectParcellation: newParcellation
+        }
+      })
+    )
+    
+    this.selectTemplateWithName$ = this.actions$.pipe(
+      ofType(VIEWERSTATE_ACTION_TYPES.SELECT_TEMPLATE_WITH_NAME),
+      map(action => {
+        const { payload = {} } = action as ViewerStateAction
+        const { name } = payload
+        return name
+      }),
+      filter(name => !!name),
+      withLatestFrom(viewerState$.pipe(
+        select('templateSelected')
+      )),
+      filter(([name,  templateSelected]) => {
+        if (templateSelected && templateSelected.name === name) return false
+        return true
+      }),
+      map(([name,  templateSelected]) => name),
+      withLatestFrom(viewerState$.pipe(
+        select('fetchedTemplates')
+      )),
+      map(([name, availableTemplates]) => {
+        const newTemplateTobeSelected = availableTemplates.find(t => t.name === name)
+        if (!newTemplateTobeSelected) {
+          return {
+            type: GENERAL_ACTION_TYPES.ERROR,
+            payload: {
+              message: 'Selected template not found.'
+            }
+          }
+        }
+        return {
+          type: NEWVIEWER,
+          selectTemplate: newTemplateTobeSelected,
+          selectParcellation: newTemplateTobeSelected.parcellations[0]
+        }
+      })
+    )
+
+    this.doubleClickOnHierarchy$ = this.actions$.pipe(
+      ofType(VIEWERSTATE_ACTION_TYPES.DOUBLE_CLICK_ON_REGIONHIERARCHY)
+    )
+
+    this.singleClickOnHierarchy$ = this.actions$.pipe(
+      ofType(VIEWERSTATE_ACTION_TYPES.SINGLE_CLICK_ON_REGIONHIERARCHY),
+      withLatestFrom(this.selectedRegions$),
+      map(([action, regionsSelected]) => {
+
+        const {payload = {}} = action as ViewerStateAction
+        const { region } = payload
+
+        const flattenedRegion = regionFlattener(region).filter(r => isDefined(r.labelIndex))
+        const flattenedRegionNames = new Set(flattenedRegion.map(r => r.name))
+        const selectedRegionNames = new Set(regionsSelected.map(r => r.name))
+        const selectAll = flattenedRegion.every(r => !selectedRegionNames.has(r.name))
+        return {
+          type: SELECT_REGIONS,
+          selectRegions: selectAll
+            ? regionsSelected.concat(flattenedRegion)
+            : regionsSelected.filter(r => !flattenedRegionNames.has(r.name))
+        }
+      })
+    )
+  }
+
+  ngOnInit(){
+    this.subscriptions.push(
+      this.doubleClickOnHierarchy$.subscribe(({ region } = {}) => {
+        const { position } = region
+        if (position) {
+          this.store$.dispatch({
+            type: CHANGE_NAVIGATION,
+            navigation: {
+              position,
+              animation: {}
+            }
+          })
+        } else {
+          this.toastService.showToast(`${region.name} does not have a position defined`, {
+            timeout: 5000,
+            dismissable: true
+          })
+        }
+      })
+    )
+  }
+
+  ngOnDestroy(){
+    while(this.subscriptions.length > 0) {
+      this.subscriptions.pop().unsubscribe()
+    }
+  }
+}
+
+interface ViewerStateAction extends Action{
+  payload: any
+  config: any
+}
\ No newline at end of file
diff --git a/src/util/pipes/filterNgLayer.pipe.ts b/src/util/pipes/filterNgLayer.pipe.ts
deleted file mode 100644
index 798075159645feed923f9cca4ac750afc8ac4ebc..0000000000000000000000000000000000000000
--- a/src/util/pipes/filterNgLayer.pipe.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import { Pipe, PipeTransform } from "@angular/core";
-import { NgLayerInterface } from "src/atlasViewer/atlasViewer.component";
-
-/**
- * TODO deprecate
- * use regular pipe to achieve the same effect
- */
-
-@Pipe({
-  name: 'filterNgLayer'
-})
-
-export class FilterNgLayer implements PipeTransform{
-  public transform(excludedLayers: string[] = [], ngLayers: NgLayerInterface[]): NgLayerInterface[] {
-    const set = new Set(excludedLayers)
-    return ngLayers.filter(l => !set.has(l.name))
-  }
-}
\ No newline at end of file
diff --git a/src/util/pipes/getFileExt.pipe.ts b/src/util/pipes/getFileExt.pipe.ts
new file mode 100644
index 0000000000000000000000000000000000000000..aea77ceba51c36451836366656529eef661a852c
--- /dev/null
+++ b/src/util/pipes/getFileExt.pipe.ts
@@ -0,0 +1,36 @@
+import { PipeTransform, Pipe } from "@angular/core";
+
+const NIFTI = `NIFTI Volume`
+const VTK = `VTK Mesh`
+
+const extMap = new Map([
+  ['.nii', NIFTI],
+  ['.nii.gz', NIFTI],
+  ['.vtk', VTK]
+])
+
+@Pipe({
+  name: 'getFileExtension'
+})
+
+export class GetFileExtension implements PipeTransform{
+  private regex: RegExp = new RegExp('(\\.[\\w\\.]*?)$')
+
+  private getRegexp(ext){
+    return new RegExp(`${ext.replace(/\./g, '\\.')}$`, 'i')
+  }
+
+  private detFileExt(filename:string):string{
+    for (let [key, val] of extMap){
+      if(this.getRegexp(key).test(filename)){
+        return val
+      }
+    }
+    return filename
+  }
+
+  public transform(filename:string):string{
+    return this.detFileExt(filename)
+  }
+}
+
diff --git a/src/util/pipes/getFileNameFromPathName.pipe.ts b/src/util/pipes/getFileNameFromPathName.pipe.ts
deleted file mode 100644
index d64a96c1c9de83ef55c9efa7f3f43e0181b9fe21..0000000000000000000000000000000000000000
--- a/src/util/pipes/getFileNameFromPathName.pipe.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import { Pipe, PipeTransform } from "@angular/core";
-
-
-@Pipe({
-  name : 'getFilenameFromPathname'
-})
-
-export class GetFilenameFromPathnamePipe implements PipeTransform{
-  public transform(pathname:string):string{
-    return pathname.split('/')[pathname.split('/').length - 1]
-  }
-}
\ No newline at end of file
diff --git a/src/util/pipes/getFilename.pipe.ts b/src/util/pipes/getFilename.pipe.ts
new file mode 100644
index 0000000000000000000000000000000000000000..76afea5627e1ce05a7f527727f99084f30557291
--- /dev/null
+++ b/src/util/pipes/getFilename.pipe.ts
@@ -0,0 +1,14 @@
+import { PipeTransform, Pipe } from "@angular/core";
+
+@Pipe({
+  name: 'getFilenamePipe'
+})
+
+export class GetFilenamePipe implements PipeTransform{
+  private regex: RegExp = new RegExp('[\\/\\\\]([\\w\\.]*?)$')
+  public transform(fullname: string): string{
+    return this.regex.test(fullname)
+      ? this.regex.exec(fullname)[1]
+      : fullname
+  }
+}
\ No newline at end of file
diff --git a/src/util/pipes/kgSearchBtnColor.pipe.ts b/src/util/pipes/kgSearchBtnColor.pipe.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c9bbb25bf655bbe4d5e90ef08768f058fae915f2
--- /dev/null
+++ b/src/util/pipes/kgSearchBtnColor.pipe.ts
@@ -0,0 +1,14 @@
+import { Pipe, PipeTransform } from "@angular/core";
+import { WidgetUnit } from "src/atlasViewer/widgetUnit/widgetUnit.component";
+
+@Pipe({
+  name: 'kgSearchBtnColorPipe'
+})
+
+export class KgSearchBtnColorPipe implements PipeTransform{
+  public transform([minimisedWidgetUnit, themedBtnCls]: [Set<WidgetUnit>, string], wu: WidgetUnit ){
+    return minimisedWidgetUnit.has(wu)
+      ? 'primary'
+      : 'accent'
+  }
+}
\ No newline at end of file
diff --git a/src/util/pipes/pluginBtnFabColor.pipe.ts b/src/util/pipes/pluginBtnFabColor.pipe.ts
new file mode 100644
index 0000000000000000000000000000000000000000..bfb11d4ae59e41107e8fa258be73a122c1b1de7c
--- /dev/null
+++ b/src/util/pipes/pluginBtnFabColor.pipe.ts
@@ -0,0 +1,15 @@
+import { Pipe, PipeTransform } from "@angular/core";
+
+@Pipe({
+  name: 'pluginBtnFabColorPipe'
+})
+
+export class PluginBtnFabColorPipe implements PipeTransform{
+  public transform([launchedSet, minimisedSet, themedBtnCls], pluginName){
+    return minimisedSet.has(pluginName)
+      ? 'primary'
+      : launchedSet.has(pluginName)
+        ? 'accent'
+        : 'basic'
+  }
+}
\ No newline at end of file
diff --git a/webpack.staticassets.js b/webpack.staticassets.js
index cad3e8a0d4b3861ba397d125e8c1f3276dacb1b5..05119ba58f3c746b113b673b7abb41849d63bfab 100644
--- a/webpack.staticassets.js
+++ b/webpack.staticassets.js
@@ -1,8 +1,19 @@
 const webpack = require('webpack')
+const MiniCssExtractPlugin = require('mini-css-extract-plugin')
 
 module.exports = {
   module : {
     rules : [
+      {
+        test: /\.scss$/,
+        use: [
+          {
+            loader: MiniCssExtractPlugin.loader
+          },
+          'css-loader',
+          'sass-loader'
+        ]
+      },
       {
         test : /jpg|png/,
         exclude : /export\_nehuba|index/,
@@ -49,6 +60,9 @@ module.exports = {
     ]
   },
   plugins : [
+    new MiniCssExtractPlugin({
+      filename: 'theme.css'
+    }),
     new webpack.DefinePlugin({
       PLUGINDEV : process.env.PLUGINDEV
         ? JSON.stringify(process.env.PLUGINDEV)
@@ -67,5 +81,10 @@ module.exports = {
       BACKEND_URL: JSON.stringify(process.env.BACKEND_URL || 'http://localhost:3000/')
     })
     // ...ignoreArr.map(dirname => new webpack.IgnorePlugin(/\.\/plugin_examples/))
-  ]
+  ],
+  resolve: {
+    extensions: [
+      '.scss'
+    ]
+  }
 }
\ No newline at end of file