diff --git a/README.md b/README.md
index 3481157abf73562adf36db5ae3eb836009a7c064..7e5bf0ced2ed36396ee0d75aaabba9f79996b27c 100644
--- a/README.md
+++ b/README.md
@@ -9,6 +9,8 @@ A live version of the Interactive Atlas Viewer is available at [https://kg.human
 ### General information
 Interactive atlas viewer is built with [Angular (v6.0)](https://angular.io/), [Bootstrap (v4)](http://getbootstrap.com/), and [fontawesome icons](https://fontawesome.com/). Some other notable packages used are: [ng2-charts](https://valor-software.com/ng2-charts/) for charts visualisation, [ngx-bootstrap](https://valor-software.com/ngx-bootstrap/) for UI and [ngrx/store](https://github.com/ngrx/platform) for state management. 
 
+Releases newer than [v0.2.9](https://github.com/HumanBrainProject/interactive-viewer/tree/v0.2.9) also uses a nodejs backend, which uses [passportjs](http://www.passportjs.org/) for user authentication, [express](https://expressjs.com/) as a http framework.
+
 ### Prerequisites
 
 - node > 6
@@ -19,27 +21,48 @@ Interactive atlas viewer is built with [Angular (v6.0)](https://angular.io/), [B
 To run a dev server, run:
 
 ```
-git clone https://github.com/HumanBrainProject/interactive-viewer
-cd interactive-viewer
-npm i
-npm run dev-server
+$ git clone https://github.com/HumanBrainProject/interactive-viewer
+$ cd interactive-viewer
+$ npm i
+$ npm run dev
 ```
 
 ### Develop Plugins
 
-To develop plugins for the interactive viewer, run:
+For releases newer than [v0.2.9](https://github.com/HumanBrainProject/interactive-viewer/tree/v0.2.9), Interactive Atlas Viewer attempts to fetch `GET {BACKEND_URL}/plugins` to retrieve a list of URLs. The interactive atlas viewer will then perform a `GET` request for each of the listed URLs, parsing them as [manifests](src/plugin_examples/README.md#Manifest%20JSON).
+
+The backend reads the environment variable `PLUGIN_URLS` and separate the string with `;` as a delimiter. In order to return a response akin to the following:
+
+```JSON
+["http://localhost:3001/manifest.json","http://localhost:9001/manifest.json"]
+```
+
+Plugin developers may choose to do any of the following:
+
+_shell_
+
+set env var every time
+
+```bash
+$ PLUGIN_URLS=http://localhost:3001/manifest.json;http://localhost:9001/manifest.json npm run dev
+```
+
+_dotenv_
+
+set a `.env` file in `./deploy/` once
+
+```bash
+$ echo `PLUGIN_URLS=http://localhost:3001/manifest.json;http://localhost:9001/manifest.json` > ./deploy/.env
 ```
-git clone https://github.com/HumanBrainProject/interactive-viewer
-cd interactive-viewer
-npm i
-npm run dev-plugin
 
-/* or define your own endpoint that returns string of manifests */
-PLUGINDEV=http://mycustom.io/allPluginmanifest npm run dev-server
+then, simple start the dev process with
 
+```bash
+$ npm run dev
 ```
 
-The contents inside the folder in `./src/plugin_examples` will be automatically fetched by the dev instance of the interactive-viewer on load. 
+Plugin developers can start their own webserver, use [interactive-viewer-plugin-template](https://github.com/HumanBrainProject/interactive-viewer-plugin-template), or (coming soon) provide link to a github repository.
+
 
 [plugin readme](src/plugin_examples/README.md)
 
@@ -48,19 +71,17 @@ The contents inside the folder in `./src/plugin_examples` will be automatically
 [plugin migration guide](src/plugin_examples/migrationGuide.md)
 
 
-## Deployment
+## Compilation
 
 `package.json` provide with two ways of building the interactive atlas viewer, `JIT` or `AOT` compilation. In general, `AOT` compilation produces a smaller package and has better performance. 
 
-## AOT compilation
-
-Define `BUNDLEDPLUGINS` as a comma separated environment variables to bundle the plugins. 
+### AOT compilation
 
 ```
-[BUNDLEDPLUGINS=pluginDir1[,pluginDir2...]] npm run build-aot
+npm run build-aot
 ```
 
-## JIT Compilation
+### JIT Compilation
 ```
 npm run build
 
@@ -69,6 +90,21 @@ npm run build
 npm run build-min
 ```
 
+### Docker
+
+The repository also provides a `Dockerfile`. Here are the environment variables used:
+
+_build time_
+- __BACKEND_URL__ : same as `HOSTNAME` during run time. Needed as root URL when fetching templates / datasets etc. If left empty, will fetch without hostname.
+
+_run time_
+
+- __SESSION_SECRET__ : needed for session
+- __HOSTNAME__ : needed for OIDC redirect
+- __HBP_CLIENTID__ : neded for OIDC authentication
+- __HBP_CLIENTSECRET__ : needed for OIDC authentication
+- __PLUGIN_URLS__ : optional. Allows plugins to be populated
+- __REFRESH_TOKEN__ : needed for access of public data
 
 ## Contributing
 
@@ -80,4 +116,4 @@ Commit history prior to v0.2.0 is available in the [legacy-v0.2.0](https://githu
 
 ## License
 
-MIT
+TO BE DECIDED
\ No newline at end of file
diff --git a/deploy/app.js b/deploy/app.js
index e2260239c9dc507b9079e7a510b2750c20c5d27d..f1e4fdcba43d43f5b1ebd091ac5259de606468b3 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,6 +11,22 @@ 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',
+      method: 'main.bundle.js',
+      xForwardedFor: xForwardedFor.replace(/\ /g, '').split(',').map(hash),
+      ip: hash(ip)
+    })
+  }
+  next()
+})
+
 /**
  * load env first, then load other modules
  */
@@ -44,6 +61,11 @@ const PUBLIC_PATH = process.env.NODE_ENV === 'production'
   ? path.join(__dirname, 'public')
   : path.join(__dirname, '..', 'dist', 'aot')
 
+/**
+ * well known path
+ */
+app.use('/.well-known', express.static(path.join(__dirname, 'well-known')))
+
 app.use(express.static(PUBLIC_PATH))
 
 app.use((req, res, next) => {
diff --git a/deploy/auth/hbp-oidc.js b/deploy/auth/hbp-oidc.js
index cbb7ce83f0d34de5814048a3e2986a660640f40c..a6963750d8469a224c04018c1ef63209cfa9bfc8 100644
--- a/deploy/auth/hbp-oidc.js
+++ b/deploy/auth/hbp-oidc.js
@@ -17,23 +17,27 @@ const cb = (tokenset, {sub, given_name, family_name, ...rest}, done) => {
 }
 
 module.exports = async (app) => {
-  const { oidcStrategy } = await configureAuth({
-    clientId,
-    clientSecret,
-    discoveryUrl,
-    redirectUri,
-    cb,
-    scope: 'openid offline_access',
-    clientConfig: {
-      redirect_uris: [ redirectUri ],
-      response_types: [ 'code' ]
-    }
-  })
-  
-  passport.use('hbp-oidc', oidcStrategy)
-  app.get('/hbp-oidc/auth', passport.authenticate('hbp-oidc'))
-  app.get('/hbp-oidc/cb', passport.authenticate('hbp-oidc', {
-    successRedirect: '/',
-    failureRedirect: '/'
-  }))
+  try {
+    const { oidcStrategy } = await configureAuth({
+      clientId,
+      clientSecret,
+      discoveryUrl,
+      redirectUri,
+      cb,
+      scope: 'openid offline_access',
+      clientConfig: {
+        redirect_uris: [ redirectUri ],
+        response_types: [ 'code' ]
+      }
+    })
+    
+    passport.use('hbp-oidc', oidcStrategy)
+    app.get('/hbp-oidc/auth', passport.authenticate('hbp-oidc'))
+    app.get('/hbp-oidc/cb', passport.authenticate('hbp-oidc', {
+      successRedirect: '/',
+      failureRedirect: '/'
+    }))
+  } catch (e) {
+    console.error(e)
+  }
 }
diff --git a/deploy/auth/index.js b/deploy/auth/index.js
index bfd8fab1724d4a1f052734684b2440baa1374e79..8c3895710418e81e78ef7aa5aea483013a799886 100644
--- a/deploy/auth/index.js
+++ b/deploy/auth/index.js
@@ -14,10 +14,8 @@ module.exports = async (app) => {
 
   passport.deserializeUser((id, done) => {
     const user = objStoreDb.get(id)
-    if (user) 
-      return done(null, user)
-    else
-      return done(null, false)
+    if (user) return done(null, user)
+    else return done(null, false)
   })
 
   await hbpOidc(app)
diff --git a/deploy/catchError.js b/deploy/catchError.js
index 0fdc484348a9e6f8e1f1fc9072eaec5b8de5e1d9..38e1a000f5b78c53d5275bef01caf2ec9cbfb0db 100644
--- a/deploy/catchError.js
+++ b/deploy/catchError.js
@@ -2,7 +2,7 @@ module.exports = ({code = 500, error = 'an error had occured', trace = 'undefine
   /**
    * probably use more elaborate logging?
    */
-  console.log('Catching error', {
+  console.error('Catching error', {
     code,
     error,
     trace
diff --git a/deploy/datasets/index.js b/deploy/datasets/index.js
index e974366263cff811887167510658d9a63938f652..cb69686463aa6cd2240a183f3c09340a5d0e5b02 100644
--- a/deploy/datasets/index.js
+++ b/deploy/datasets/index.js
@@ -25,6 +25,15 @@ const noCacheMiddleWare = (_req, res, next) => {
   next()
 }
 
+const getVary = (headers) => (_req, res, next) => {
+  if (!headers instanceof Array) {
+    console.warn(`getVary arguments needs to be an Array of string`)
+    return next()
+  }
+  res.setHeader('Vary', headers.join(', '))
+  next()
+}
+
 datasetsRouter.use('/spatialSearch', noCacheMiddleWare, require('./spatialRouter'))
 
 datasetsRouter.get('/templateName/:templateName', noCacheMiddleWare, (req, res, next) => {
@@ -59,7 +68,10 @@ datasetsRouter.get('/parcellationName/:parcellationName', noCacheMiddleWare, (re
     })
 })
 
-datasetsRouter.get('/preview/:datasetName', cacheMaxAge24Hr, (req, res, next) => {
+/**
+ * It appears that query param are not 
+ */
+datasetsRouter.get('/preview/:datasetName', getVary(['referer']), cacheMaxAge24Hr, (req, res, next) => {
   const { datasetName } = req.params
   const ref = url.parse(req.headers.referer)
   const { templateSelected, parcellationSelected } = qs.parse(ref.query)
@@ -94,7 +106,7 @@ const PUBLIC_PATH = process.env.NODE_ENV === 'production'
 const RECEPTOR_PATH = path.join(PUBLIC_PATH, 'res', 'image')
 fs.readdir(RECEPTOR_PATH, (err, files) => {
   if (err) {
-    console.log('reading receptor error', err)
+    console.warn('reading receptor error', err)
     return
   }
   files.forEach(file => previewFileMap.set(`res/image/receptor/${file}`, path.join(RECEPTOR_PATH, file)))
@@ -124,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 {
@@ -132,7 +144,7 @@ datasetsRouter.get('/downloadKgFiles', checkKgQuery, cacheMaxAge24Hr, async (req
     res.setHeader('Content-Type', 'application/zip')
     stream.pipe(res)
   } catch (e) {
-    console.log('datasets/index#downloadKgFiles', e)
+    console.warn('datasets/index#downloadKgFiles', e)
     res.status(400).send(e)
   }
 })
diff --git a/deploy/datasets/supplements/previewFile.js b/deploy/datasets/supplements/previewFile.js
index ea178530c1ed118387393dd8754f325b2ff521a2..311ec7d273becf80a84d74615128924cb302551a 100644
--- a/deploy/datasets/supplements/previewFile.js
+++ b/deploy/datasets/supplements/previewFile.js
@@ -13,7 +13,7 @@ let previewMap = new Map(),
 const readFile = (filename) => new Promise((resolve) => {
   fs.readFile(path.join(__dirname, 'data', filename), 'utf-8', (err, data) => {
     if (err){
-      console.log('read file error', err)
+      console.warn('read file error', err)
       return resolve([])
     }
     resolve(JSON.parse(data))
diff --git a/deploy/logging/index.js b/deploy/logging/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..173ec49afb0c3d45332e0c04fbae38283541a3e6
--- /dev/null
+++ b/deploy/logging/index.js
@@ -0,0 +1,43 @@
+const request = require('request')
+const qs = require('querystring')
+
+class Logger {
+  constructor(name, { protocol = 'http', host = 'localhost', port = '24224', username = '', password = '' } = {}){
+    this.name = qs.escape(name)
+    this.protocol = protocol
+    this.host = host
+    this.port = port
+    this.username = username
+    this.password = password
+  }
+
+  emit(logLevel, message, callback){
+    const {
+      name,
+      protocol,
+      host,
+      port,
+      username,
+      password
+    } = this
+    
+    const auth = username !== '' ? `${username}:${password}@` : ''
+    const url = `${protocol}://${auth}${host}:${port}/${name}.${qs.escape(logLevel)}`
+    const formData = {
+      json: JSON.stringify(message)
+    }
+    if (callback) {
+      request.post({
+        url,
+        formData
+      }, callback)
+    } else {
+      return request.post({
+        url,
+        formData
+      })
+    }
+  }
+}
+
+module.exports = Logger
\ No newline at end of file
diff --git a/deploy/server.js b/deploy/server.js
index 85596178cccb7433a77195c447e3efa665cd2583..9045664cf8a90542b482513e2026eacf291ea016 100644
--- a/deploy/server.js
+++ b/deploy/server.js
@@ -5,6 +5,57 @@ if (process.env.NODE_ENV !== 'production') {
   })
 }
 
+if (process.env.FLUENT_HOST) {
+  const Logger = require('./logging')
+
+  const name = process.env.IAV_NAME || 'IAV'
+  const stage = process.env.IAV_STAGE || 'unnamed-stage'
+
+  const protocol = process.env.FLUENT_PROTOCOL || 'http'
+  const host = process.env.FLUENT_HOST || 'localhost'
+  const port = process.env.FLUENT_PORT || 24224
+  
+  const prefix = `${name}.${stage}`
+
+  const log = new Logger(prefix, {
+    protocol,
+    host,
+    port
+  })
+
+  const handleRequestCallback = (err, resp, body) => {
+    if (err) {
+      process.stderr.write(`fluentD logging failed\n`)
+      process.stderr.write(err.toString())
+      process.stderr.write('\n')
+    }
+
+    if (resp && resp.statusCode >= 400) {
+      process.stderr.write(`fluentD logging responded error\n`)
+      process.stderr.write(resp.toString())
+      process.stderr.write('\n')
+    }
+  }
+
+  const emitInfo = message => log.emit('info', { message }, handleRequestCallback)
+
+  const emitWarn = message => log.emit('warn', { message }, handleRequestCallback)
+
+  const emitError = message => log.emit('error', { message }, handleRequestCallback)
+
+  console.log('starting fluentd logging')
+
+  console.log = function () {
+    emitInfo([...arguments])
+  }
+  console.warn = function () {
+    emitWarn([...arguments])
+  }
+  console.error = function () {
+    emitError([...arguments])
+  }
+}
+
 const app = require('./app')
 const PORT = process.env.PORT || 3000
 
diff --git a/deploy/well-known/robot.txt b/deploy/well-known/robot.txt
new file mode 100644
index 0000000000000000000000000000000000000000..4f9540ba358a64607438da92eebe85889fdad50a
--- /dev/null
+++ b/deploy/well-known/robot.txt
@@ -0,0 +1 @@
+User-agent: *
\ No newline at end of file
diff --git a/deploy/well-known/security.txt b/deploy/well-known/security.txt
new file mode 100644
index 0000000000000000000000000000000000000000..42a2d3940dfcc87456b84a6165c9e741068c6b31
--- /dev/null
+++ b/deploy/well-known/security.txt
@@ -0,0 +1,2 @@
+# If you would like to report a security issue, please contact us via:
+Contact: inm1-bda@fz-juelich.de
\ No newline at end of file
diff --git a/package.json b/package.json
index 2acb48676fed72ee9fa9639abe26b48488e7a0f4..491667075a4a27dfec14a0a4693c05ac17bab5d1 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "interactiveviewer",
-  "version": "1.0.0",
+  "version": "0.0.2",
   "description": "",
   "scripts": {
     "dev-server-export": "webpack-dev-server --config webpack.export.js",
@@ -10,12 +10,11 @@
     "build-aot": "PRODUCTION=true GIT_HASH=`git log --pretty=format:'%h' --invert-grep --grep=^.ignore -1` webpack --config webpack.aot.js",
     "build-min": "webpack --config webpack.prod.js",
     "build": "webpack --config webpack.dev.js",
-    "dev-plugin": "PLUGINDEV=http://localhost:10080/allPluginmanifests npm run dev-server & npm run plugin-server",
     "plugin-server": "node ./src/plugin_examples/server.js",
     "dev-server": "webpack-dev-server --config webpack.dev.js --mode development",
+    "dev": "npm run dev-server & (cd deploy; node server.js)",
     "dev-server-aot": "PRODUCTION=true GIT_HASH=`git log --pretty=format:'%h' --invert-grep --grep=^.ignore -1` webpack-dev-server --config webpack.aot.js",
     "dev-server-all-interfaces": "webpack-dev-server --config webpack.dev.js --mode development --hot --host 0.0.0.0",
-    "serve-plugins": "node src/plugin_examples/server.js",
     "test": "karma start spec/karma.conf.js",
     "e2e": "protractor e2e/protractor.conf"
   },
@@ -46,6 +45,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",
     "html-webpack-plugin": "^3.2.0",
     "jasmine": "^3.1.0",
@@ -58,12 +58,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",
@@ -74,12 +77,12 @@
     "webpack-cli": "^3.3.2",
     "webpack-closure-compiler": "^2.1.6",
     "webpack-dev-server": "^3.1.4",
-    "webpack-merge": "^4.1.2"
-  },
-  "dependencies": {
+    "webpack-merge": "^4.1.2",
     "@angular/cdk": "^7.3.7",
     "@angular/material": "^7.3.7",
     "@angular/router": "^7.2.15",
     "zone.js": "^0.9.1"
+  },
+  "dependencies": {
   }
 }
diff --git a/src/atlasViewer/atlasViewer.apiService.service.ts b/src/atlasViewer/atlasViewer.apiService.service.ts
index 5173ec7a7825790bb058a09b56aa5cf0cb8c6c11..1691568f7c53b2d1b59f7d897706c37f45c865f5 100644
--- a/src/atlasViewer/atlasViewer.apiService.service.ts
+++ b/src/atlasViewer/atlasViewer.apiService.service.ts
@@ -3,11 +3,10 @@ import { Store, select } from "@ngrx/store";
 import { ViewerStateInterface, safeFilter, getLabelIndexMap, isDefined } from "src/services/stateStore.service";
 import { Observable } from "rxjs";
 import { map, distinctUntilChanged, filter } from "rxjs/operators";
-import { BsModalService } from "ngx-bootstrap/modal";
-import { ModalUnit } from "./modalUnit/modalUnit.component";
 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
 
@@ -19,15 +18,14 @@ export class AtlasViewerAPIServices{
 
   private loadedTemplates$ : Observable<any>
   private selectParcellation$ : Observable<any>
-  private selectTemplate$ : Observable<any>
-  private darktheme : boolean
   public interactiveViewer : InteractiveViewerInterface
 
   public loadedLibraries : Map<string,{counter:number,src:HTMLElement|null}> = new Map()
 
   constructor(
     private store : Store<ViewerStateInterface>,
-    private modalService: BsModalService
+    private modalService: BsModalService,
+    private dialogService: DialogService,
   ){
 
     this.loadedTemplates$ = this.store.pipe(
@@ -36,13 +34,6 @@ export class AtlasViewerAPIServices{
       map(state=>state.fetchedTemplates)
     )
 
-    this.selectTemplate$ = this.store.pipe(
-      select('viewerState'),
-      filter(state => isDefined(state) && isDefined(state.templateSelected)),
-      map(state => state.templateSelected),
-      distinctUntilChanged((t1, t2) => t1.name === t2.name)
-    )
-
     this.selectParcellation$ = this.store.pipe(
       select('viewerState'),
       safeFilter('parcellationSelected'),
@@ -83,18 +74,22 @@ export class AtlasViewerAPIServices{
           const handler = new ModalHandler()
           let modalRef
           handler.show = () => {
-            modalRef = this.modalService.show(ModalUnit, {
-              initialState : {
-                title : handler.title,
-                body : handler.body
-                  ? handler.body
-                  : 'handler.body has yet been defined ...',
-                footer : handler.footer
-              },
-              class : this.darktheme ? 'darktheme' : 'not-darktheme',
-              backdrop : handler.dismissable ? true : 'static',
-              keyboard : handler.dismissable
-            })
+            /**
+             * TODO enable
+             * temporarily disabled
+             */
+            // modalRef = this.modalService.show(ModalUnit, {
+            //   initialState : {
+            //     title : handler.title,
+            //     body : handler.body
+            //       ? handler.body
+            //       : 'handler.body has yet been defined ...',
+            //     footer : handler.footer
+            //   },
+            //   class : this.darktheme ? 'darktheme' : 'not-darktheme',
+            //   backdrop : handler.dismissable ? true : 'static',
+            //   keyboard : handler.dismissable
+            // })
           }
           handler.hide = () => {
             if(modalRef){
@@ -115,7 +110,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')
@@ -137,7 +135,6 @@ export class AtlasViewerAPIServices{
   private init(){
     this.loadedTemplates$.subscribe(templates=>this.interactiveViewer.metadata.loadedTemplates = templates)
     this.selectParcellation$.subscribe(parcellation => this.interactiveViewer.metadata.regionsLabelIndexMap = getLabelIndexMap(parcellation.regions))
-    this.selectTemplate$.subscribe(template => this.darktheme = template.useTheme === 'dark')
   }
 }
 
@@ -184,6 +181,8 @@ export interface InteractiveViewerInterface{
     getModalHandler: () => ModalHandler
     getToastHandler: () => ToastHandler
     launchNewWidget: (manifest:PluginManifest) => Promise<any>
+    getUserInput: (config:GetUserInputConfig) => Promise<string>
+    getUserConfirmation: (config: GetUserConfirmation) => Promise<any>
   }
 
   pluginControl : {
@@ -193,6 +192,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 a1175088db8d56f6f0a879ef5c2e40b439db917e..c2b66ac5731e0d3805b5d54eb27ad21743cbaf9c 100644
--- a/src/atlasViewer/atlasViewer.component.ts
+++ b/src/atlasViewer/atlasViewer.component.ts
@@ -7,20 +7,16 @@ import { AtlasViewerDataService } from "./atlasViewer.dataService.service";
 import { WidgetServices } from "./widgetUnit/widgetService.service";
 import { LayoutMainSide } from "../layouts/mainside/mainside.component";
 import { AtlasViewerConstantsServices, UNSUPPORTED_PREVIEW, UNSUPPORTED_INTERVAL } from "./atlasViewer.constantService.service";
-import { BsModalService } from "ngx-bootstrap/modal";
-import { ModalUnit } from "./modalUnit/modalUnit.component";
 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";
 import { DatabrowserService } from "src/ui/databrowserModule/databrowser.service";
 import { AGREE_COOKIE, AGREE_KG_TOS, SHOW_KG_TOS } from "src/services/state/uiState.store";
 import { TabsetComponent } from "ngx-bootstrap/tabs";
-import { ToastService } from "src/services/toastService.service";
+import { MatDialog, MatDialogRef } from "@angular/material";
 
 /**
  * TODO
@@ -106,10 +102,9 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit {
     private constantsService: AtlasViewerConstantsServices,
     public urlService: AtlasViewerURLService,
     public apiService: AtlasViewerAPIServices,
-    private modalService: BsModalService,
+    private matDialog: MatDialog,
     private databrowserService: DatabrowserService,
     private dispatcher$: ActionsSubject,
-    private toastService: ToastService,
     private rd: Renderer2
   ) {
     this.ngLayerNames$ = this.store.pipe(
@@ -240,6 +235,11 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit {
   private selectedParcellation$: Observable<any>
   private selectedParcellation: any
 
+  private cookieDialogRef: MatDialogRef<any>
+  private kgTosDialogRef: MatDialogRef<any>
+  private helpDialogRef: MatDialogRef<any>
+  private loginDialogRef: MatDialogRef<any>
+
   ngOnInit() {
     this.meetsRequirement = this.meetsRequirements()
 
@@ -261,25 +261,19 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit {
     }
 
     this.subscriptions.push(
-      this.showHelp$.subscribe(() => 
-        this.modalService.show(ModalUnit, {
-          initialState: {
-            title: this.constantsService.showHelpTitle,
-            template: this.helpComponent
-          }
+      this.showHelp$.subscribe(() => {
+        this.helpDialogRef = this.matDialog.open(this.helpComponent, {
+          autoFocus: false
         })
-      )
+      })
     )
 
     this.subscriptions.push(
       this.constantsService.showSigninSubject$.pipe(
         debounceTime(160)
       ).subscribe(user => {
-        this.modalService.show(ModalUnit, {
-          initialState: {
-            title: user ? 'Logout' : `Login`,
-            template: this.signinModalComponent
-          }
+        this.loginDialogRef = this.matDialog.open(this.signinModalComponent, {
+          autoFocus: false
         })
       })
     )
@@ -321,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() {
@@ -350,12 +350,7 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit {
       filter(agreed => !agreed),
       delay(0)
     ).subscribe(() => {
-      this.modalService.show(ModalUnit, {
-        initialState: {
-          title: 'Cookie Disclaimer',
-          template: this.cookieAgreementComponent
-        }
-      }) 
+      this.cookieDialogRef = this.matDialog.open(this.cookieAgreementComponent)
     })
 
     this.dispatcher$.pipe(
@@ -368,12 +363,7 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit {
       filter(flag => !flag),
       delay(0)
     ).subscribe(val => {
-      this.modalService.show(ModalUnit, {
-        initialState: {
-          title: 'Knowldge Graph ToS',
-          template: this.kgTosComponent
-        }
-      })
+      this.kgTosDialogRef = this.matDialog.open(this.kgTosComponent)
     })
 
     this.onhoverSegmentsForFixed$ = this.rClContextualMenu.onShow.pipe(
@@ -415,12 +405,16 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit {
     }
 
     if(this.constantsService.mobile){
-      this.modalService.show(ModalUnit,{
-        initialState: {
-          title: this.constantsService.mobileWarningHeader,
-          body: this.constantsService.mobileWarning
-        }
-      })
+      /**
+       * TODO change to snack bar in future
+       */
+      
+      // this.modalService.show(ModalUnit,{
+      //   initialState: {
+      //     title: this.constantsService.mobileWarningHeader,
+      //     body: this.constantsService.mobileWarning
+      //   }
+      // })
     }
     return true
   }
@@ -437,23 +431,23 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit {
   }
 
   kgTosClickedOk(){
-    this.modalService.hide(1)
+    this.kgTosDialogRef && this.kgTosDialogRef.close()
     this.store.dispatch({
       type: AGREE_KG_TOS
     })
   }
 
   cookieClickedOk(){
-    this.modalService.hide(1)
+    this.cookieDialogRef && this.cookieDialogRef.close()
     this.store.dispatch({
       type: AGREE_COOKIE
     })
   }
 
   panelAnimationEnd(){
-
-    if( this.nehubaContainer && this.nehubaContainer.nehubaViewer && this.nehubaContainer.nehubaViewer.nehubaViewer )
+    if( this.nehubaContainer && this.nehubaContainer.nehubaViewer && this.nehubaContainer.nehubaViewer.nehubaViewer ) {
       this.nehubaContainer.nehubaViewer.nehubaViewer.redraw()
+    }
   }
 
   nehubaClickHandler(event:MouseEvent){
@@ -508,6 +502,15 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit {
     })
   }
 
+  closeModal(mode){
+    if (mode === 'help') {
+      this.helpDialogRef && this.helpDialogRef.close()
+    }
+
+    if (mode === 'login') {
+      this.loginDialogRef && this.loginDialogRef.close()
+    }
+  }
 
   closeMenuWithSwipe(documentToSwipe: ElementRef) {
     if (documentToSwipe && documentToSwipe.nativeElement) {
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 b1ce0aaca772c8664ff6a185b7e54b322ab37d61..bb1d4183e203ac97f3c9d54c52a49d18e5e660e1 100644
--- a/src/atlasViewer/atlasViewer.constantService.service.ts
+++ b/src/atlasViewer/atlasViewer.constantService.service.ts
@@ -3,7 +3,7 @@ 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 } from "rxjs/operators";
+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;`
@@ -179,7 +179,6 @@ Interactive atlas viewer requires **webgl2.0**, and the \`EXT_color_buffer_float
    * Observable for showing help modal
    */
   public showHelpSubject$: Subject<null> = new Subject()
-  public showHelpTitle: String = 'About'
 
   private showHelpGeneralMobile = [
     ['hold 🌏 + ↕', 'change oblique slice mode'],
@@ -252,6 +251,7 @@ Interactive atlas viewer requires **webgl2.0**, and the \`EXT_color_buffer_float
     this.darktheme$ = this.store.pipe(
       select('viewerState'),
       select('templateSelected'),
+      filter(v => !!v),
       map(({useTheme}) => useTheme === 'dark'),
       shareReplay(1)
     )
@@ -330,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
@@ -388,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.ts b/src/atlasViewer/atlasViewer.pluginService.service.ts
index ff2ff680d645b4744c66ef2f8e44acefba07385c..66d7a3ff527cd84d8c11af520c3bd82cdf3741e3 100644
--- a/src/atlasViewer/atlasViewer.pluginService.service.ts
+++ b/src/atlasViewer/atlasViewer.pluginService.service.ts
@@ -262,22 +262,10 @@ export class PluginServices{
         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()
@@ -309,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.template.html b/src/atlasViewer/atlasViewer.template.html
index 429869cf77846acea64457a1b8cdbf52c32b4ad5..1a4876f532732f6415988deb76849f926ab3377a 100644
--- a/src/atlasViewer/atlasViewer.template.html
+++ b/src/atlasViewer/atlasViewer.template.html
@@ -28,10 +28,10 @@
             <signin-banner [darktheme] = "darktheme" signinWrapper></signin-banner>
             <layout-floating-container *ngIf="this.nehubaContainer && this.nehubaContainer.nehubaViewer">
               <ui-status-card 
-                  [selectedTemplate]="selectedTemplate" 
-                  [isMobile]="isMobile"
-                  [onHoverSegmentName]="this.nehubaContainer.onHoverSegmentName$ | async"
-                  [nehubaViewer]="this.nehubaContainer.nehubaViewer">
+                [selectedTemplate]="selectedTemplate" 
+                [isMobile]="isMobile"
+                [onHoverSegmentName]="this.nehubaContainer.onHoverSegmentName$ | async"
+                [nehubaViewer]="this.nehubaContainer.nehubaViewer">
               </ui-status-card>
             </layout-floating-container>
           </tab>
@@ -70,54 +70,85 @@
   </ng-template>
 </ng-container>
 
-
 <ng-template #helpComponent>
-  <tabset>
-    <tab heading="Help">
-      <help-component>
-      </help-component>
-    </tab>
-    <tab heading="Settings">
-      <div class="mt-2">
+  <h2 mat-dialog-title>About Interactive Viewer</h2>
+  <mat-dialog-content class="h-90vh w-50vw">
+    <mat-tab-group>
+      <mat-tab label="Help">
+        <help-component>
+        </help-component>
+      </mat-tab>
+      <mat-tab label="Settings">
         <config-component>
         </config-component>
-      </div>
-    </tab>
-    <tab heading="Privacy Policy">
-      <div class="mt-2">
+      </mat-tab>
+      <mat-tab label="Privacy Policy">
+        <!-- TODO make tab container scrollable -->
         <cookie-agreement>
         </cookie-agreement>
-      </div>
-    </tab>
-    <tab heading="Terms of Use">
-      <div class="mt-2">
+      </mat-tab>
+      <mat-tab label="Terms of Use">
         <kgtos-component>
         </kgtos-component>
-      </div>
-    </tab>
-  </tabset>
+      </mat-tab>
+    </mat-tab-group>
+  </mat-dialog-content>
+
+  <mat-dialog-actions class="justify-content-center">
+    <button
+      mat-stroked-button
+      (click)="closeModal('help')"
+      cdkFocusInitial>
+      close
+    </button>
+  </mat-dialog-actions>
 </ng-template>
+
+<!-- signin -->
 <ng-template #signinModalComponent>
-  <signin-modal>
-    
-  </signin-modal>
+  <h2 mat-dialog-title>Sign in</h2>
+  <mat-dialog-content>
+    <signin-modal>
+    </signin-modal>
+  </mat-dialog-content>
 </ng-template>
 
+<!-- kg tos -->
 <ng-template #kgToS>
+  <h2 mat-dialog-title>Knowldge Graph ToS</h2>
+  <mat-dialog-content class="w-50vw">
     <kgtos-component>
     </kgtos-component>
-    <div class="modal-footer">
-      <button type="button" class="btn btn-primary" (click)="kgTosClickedOk()">Ok</button>
-    </div>
-  </ng-template>
+  </mat-dialog-content>
 
+  <mat-dialog-actions class="justify-content-end">
+    <button
+      color="primary"
+      mat-raised-button
+      (click)="kgTosClickedOk()"
+      cdkFocusInitial>
+      Ok
+    </button>
+  </mat-dialog-actions>
+</ng-template>
+
+<!-- cookie -->
 <ng-template #cookieAgreementComponent>
-  <cookie-agreement>
-  </cookie-agreement>
+  <h2 mat-dialog-title>Cookie Disclaimer</h2>
+  <mat-dialog-content class="w-50vw">
+    <cookie-agreement>
+    </cookie-agreement>
+  </mat-dialog-content>
 
-  <div class="modal-footer">
-    <button type="button" class="btn btn-primary" (click)="cookieClickedOk()">Ok</button>
-  </div>
+  <mat-dialog-actions class="justify-content-end">
+    <button
+      color="primary"
+      mat-raised-button
+      (click)="cookieClickedOk()"
+      cdkFocusInitial>
+      Ok
+    </button>
+  </mat-dialog-actions>
 </ng-template>
 
 <!-- atlas template -->
diff --git a/src/atlasViewer/atlasViewer.urlService.service.ts b/src/atlasViewer/atlasViewer.urlService.service.ts
index 8f5dbc96c88a11b1b6d9013891cff2f87edceb52..1008c7a50e424d52c4c1bc32fc072163d8bd852e 100644
--- a/src/atlasViewer/atlasViewer.urlService.service.ts
+++ b/src/atlasViewer/atlasViewer.urlService.service.ts
@@ -60,6 +60,9 @@ export class AtlasViewerURLService{
         select('templateSelected'),
         filter(v => !!v)
       ),
+      /**
+       * TODO duplicated with viewerState.loadedNgLayers ?
+       */
       this.store.pipe(
         select('ngViewerState'),
         select('layers')
@@ -171,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}`)
               }
@@ -209,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/widgetUnit.component.ts b/src/atlasViewer/widgetUnit/widgetUnit.component.ts
index b1667492cc7306ce708d7a1dc0393592e9566cb4..35463f92e9d75f4804964dae65439e52c9984a35 100644
--- a/src/atlasViewer/widgetUnit/widgetUnit.component.ts
+++ b/src/atlasViewer/widgetUnit/widgetUnit.component.ts
@@ -1,4 +1,5 @@
 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";
@@ -14,7 +15,6 @@ import { map } from "rxjs/operators";
 
 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'
@@ -31,25 +31,56 @@ export class WidgetUnit implements OnInit, OnDestroy{
   isMinimised$: Observable<boolean>
 
   /**
-   * TODO
-   * upgrade to angular>=7, and use cdk to handle draggable components
+   * Timed alternates of blinkOn property should result in attention grabbing blink behaviour
    */
-  get transform(){
-    return this.state === 'floating' ?
-      `translate(${this.position[0]}px, ${this.position[1]}px)` :
-      `translate(0 , 0)`
+  private _blinkOn: boolean = false
+  get blinkOn(){
+    return this._blinkOn
+  }
+
+  set blinkOn(val: boolean) {
+    this._blinkOn = !!val
+  }
+
+  get showProgress(){
+    return this.progressIndicator !== null
+  }
+
+  /**
+   * 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
+   */
+  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()
 
@@ -67,7 +98,7 @@ export class WidgetUnit implements OnInit, OnDestroy{
   public id: string 
   constructor(
     private constantsService: AtlasViewerConstantsServices
-    ){
+  ){
     this.id = Date.now().toString()
   }
 
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 f6114ed86a8151d84023ee0e283b0dd581034f28..8ddb291d165ab8771f9e6b1f6e36ea8ea465f2d4 100644
--- a/src/components/components.module.ts
+++ b/src/components/components.module.ts
@@ -29,7 +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({
@@ -53,7 +56,10 @@ import { SleightOfHand } from './sleightOfHand/soh.component';
     TimerComponent,
     PillComponent,
     RadioList,
+    ProgressBar,
     SleightOfHand,
+    DialogComponent,
+    ConfirmDialogComponent,
 
     /* directive */
     HoverableBlockDirective,
@@ -85,7 +91,10 @@ import { SleightOfHand } from './sleightOfHand/soh.component';
     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/plugin_examples/samplePlugin/template.html b/src/components/confirmDialog/confirmDialog.style.css
similarity index 100%
rename from src/plugin_examples/samplePlugin/template.html
rename to src/components/confirmDialog/confirmDialog.style.css
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
index ed7d8cfab7f06eb27731ebdbb868476bb2152108..4b6fbe5fdb989088e847e10716703dfb4c044c5a 100644
--- a/src/components/sleightOfHand/soh.component.ts
+++ b/src/components/sleightOfHand/soh.component.ts
@@ -1,13 +1,33 @@
-import { Component } from "@angular/core";
+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
index b862eb7212f10e399d6ac5ee6d8acd46cd865aac..2c4f61aa210ae817635fc0764eb87f5cbe187c50 100644
--- a/src/components/sleightOfHand/soh.style.css
+++ b/src/components/sleightOfHand/soh.style.css
@@ -1,10 +1,6 @@
-:host:not(:hover) > .sleight-of-hand-back
-{
-  opacity: 0;
-  pointer-events: none;
-}
-
-:host:hover > .sleight-of-hand-front
+: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;
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 1fb66d55c46ea77a249b850a7a349d681d4528f2..6640175d0d34b0a0b1d338aaf770bc3ab0f2f92b 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";
@@ -18,11 +18,9 @@ import { WidgetServices } from './atlasViewer/widgetUnit/widgetService.service'
 import { fasTooltipScreenshotDirective,fasTooltipInfoSignDirective,fasTooltipLogInDirective,fasTooltipNewWindowDirective,fasTooltipQuestionSignDirective,fasTooltipRemoveDirective,fasTooltipRemoveSignDirective } from "./util/directives/glyphiconTooltip.directive";
 import { TooltipModule } from "ngx-bootstrap/tooltip";
 import { TabsModule } from 'ngx-bootstrap/tabs'
-import { ModalModule } from 'ngx-bootstrap/modal'
 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";
@@ -35,13 +33,19 @@ import { FloatingContainerDirective } from "./util/directives/floatingContainer.
 import { PluginFactoryDirective } from "./util/directives/pluginFactory.directive";
 import { FloatingMouseContextualContainerDirective } from "./util/directives/floatingMouseContextualContainer.directive";
 import { AuthService } from "./services/auth.service";
-import { ViewerConfiguration } from "./services/state/viewerConfig.store";
+import { ViewerConfiguration, LOCAL_STORAGE_CONST } from "./services/state/viewerConfig.store";
 import { FixedMouseContextualContainerDirective } from "./util/directives/FixedMouseContextualContainerDirective.directive";
 import { DatabrowserService } from "./ui/databrowserModule/databrowser.service";
 import { TransformOnhoverSegmentPipe } from "./atlasViewer/onhoverSegment.pipe";
 import {HttpClientModule} from "@angular/common/http";
 import { EffectsModule } from "@ngrx/effects";
 import { UseEffects } from "./services/effect/effect";
+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 { MatDialogModule, MatTabsModule } from "@angular/material";
+import { ViewerStateUseEffect } from "./services/state/viewerState.store";
 
 @NgModule({
   imports : [
@@ -52,12 +56,20 @@ import { UseEffects } from "./services/effect/effect";
     DragDropModule,
     UIModule,
     AngularMaterialModule,
+
+    /**
+     * move to angular material module
+     */
+    MatDialogModule,
+    MatTabsModule,
     
-    ModalModule.forRoot(),
     TooltipModule.forRoot(),
     TabsModule.forRoot(),
     EffectsModule.forRoot([
-      UseEffects
+      UseEffects,
+      UserConfigStateUseEffect,
+      ViewerStateControllerUseEffect
+      ViewerStateUseEffect,
     ]),
     StoreModule.forRoot({
       pluginState,
@@ -67,6 +79,7 @@ import { UseEffects } from "./services/effect/effect";
       dataStore,
       spatialSearchState,
       uiState,
+      userConfigState
     }),
     HttpClientModule
   ],
@@ -96,7 +109,6 @@ import { UseEffects } from "./services/effect/effect";
     GetNamesPipe,
     GetNamePipe,
     TransformOnhoverSegmentPipe,
-    GetFilenameFromPathnamePipe,
     NewViewerDisctinctViewToLayer
   ],
   entryComponents : [
@@ -104,6 +116,8 @@ import { UseEffects } from "./services/effect/effect";
     ModalUnit,
     ToastComponent,
     PluginUnit,
+    DialogComponent,
+    ConfirmDialogComponent,
   ],
   providers : [
     AtlasViewerDataService,
@@ -113,6 +127,7 @@ import { UseEffects } from "./services/effect/effect";
     ToastService,
     AtlasWorkerService,
     AuthService,
+    DialogService,
     
     /**
      * TODO
@@ -141,9 +156,13 @@ export class MainModule{
     authServce.authReloadState()
     store.pipe(
       select('viewerConfigState')
-    ).subscribe(({ gpuLimit }) => {
-      if (gpuLimit)
-        window.localStorage.setItem('iv-gpulimit', gpuLimit.toString())
+    ).subscribe(({ gpuLimit, animation }) => {
+      if (gpuLimit) {
+        window.localStorage.setItem(LOCAL_STORAGE_CONST.GPU_LIMIT, gpuLimit.toString())
+      }
+      if (typeof animation !== 'undefined' && animation !== null) {
+        window.localStorage.setItem(LOCAL_STORAGE_CONST.ANIMATION, animation.toString())
+      }
     })
   }
 }
\ No newline at end of file
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/README.md b/src/plugin_examples/README.md
index 1549c0963fcea559271259fa0fd121187394e066..2b1b126b3a0166eb2a416a5493705d5be63acd5e 100644
--- a/src/plugin_examples/README.md
+++ b/src/plugin_examples/README.md
@@ -1,5 +1,5 @@
-Plugin README
-======
+# Plugin README
+
 A plugin needs to contain three files. 
 - Manifest JSON
 - template HTML
@@ -9,8 +9,9 @@ A plugin needs to contain three files.
 These files need to be served by GET requests over HTTP with appropriate CORS header. If your application requires a backend, it is strongly recommended to host these three files with your backend. 
 
 ---
-Manifest JSON
-------
+
+## Manifest JSON
+
 The manifest JSON file describes the metadata associated with the plugin. 
 
 ```json
@@ -34,8 +35,9 @@ The manifest JSON file describes the metadata associated with the plugin.
 - the `initState` object and `initStateUrl` will be available prior to the evaluation of `script.js`, and will populate the objects `interactiveViewer.pluginControl[MANIFEST.name].initState` and `interactiveViewer.pluginControl[MANIFEST.name].initStateUrl` respectively. 
 
 ---
-Template HTML
-------
+
+## Template HTML
+
 The template HTML file describes the HTML view that will be rendered in the widget.
 
 
@@ -74,14 +76,17 @@ The template HTML file describes the HTML view that will be rendered in the widg
   </div>
 </form>
 ```
+
 *NB*
 - *bootstrap 3.3.6* css is already included for templating.
 - keep in mind of the widget width restriction (400px) when crafting the template
 - whilst there are no vertical limits on the widget, contents can be rendered outside the viewport. Consider setting the *max-height* attribute.
 - your template and script will interact with each other likely via *element id*. As a result, it is highly recommended that unique id's are used. Please adhere to the convention: **AFFILIATION.AUTHOR.PACKAGENAME.ELEMENTID** 
+
 ---
-Script JS
-------
+
+## Script JS
+
 The script will always be appended **after** the rendering of the template. 
 
 ```javascript
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/plugin_examples/samplePlugin/manifest.json b/src/plugin_examples/samplePlugin/manifest.json
deleted file mode 100644
index 765339e14a65bceeb5e8fea2a4056ebfa868b894..0000000000000000000000000000000000000000
--- a/src/plugin_examples/samplePlugin/manifest.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
-  "name":"fzj.xg.samplePlugin",
-  "displayName":"Sample Plugin Display Name (Optional)",
-  "templateURL":"http://localhost:10080/samplePlugin/template.html",
-  "scriptURL":"http://localhost:10080/samplePlugin/script.js",
-  "initState":{
-    "hello": "world",
-    "foo": "bar"
-  },
-  "initStateUrl":"http://localhost:10080/samplePlugin/optionalInitStateJson.json"
-}
\ No newline at end of file
diff --git a/src/plugin_examples/samplePlugin/optionalInitStateJson.json b/src/plugin_examples/samplePlugin/optionalInitStateJson.json
deleted file mode 100644
index 8b478475478e55bd6855b11061661c5ed524b5c9..0000000000000000000000000000000000000000
--- a/src/plugin_examples/samplePlugin/optionalInitStateJson.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
-  "foo2": "bar2"
-}
\ No newline at end of file
diff --git a/src/plugin_examples/samplePlugin/script.js b/src/plugin_examples/samplePlugin/script.js
deleted file mode 100644
index 1dbd080572707585356a699dc4f76d7f944325aa..0000000000000000000000000000000000000000
--- a/src/plugin_examples/samplePlugin/script.js
+++ /dev/null
@@ -1,9 +0,0 @@
-/**
- * use IIEF to avoid scope poisoning
- */
-(() => {
-  const PLUGIN_NAME = 'fzj.xg.samplePlugin'
-  const initState = window.interactiveViewer.pluginControl[PLUGIN_NAME].initState
-  const initUrl = window.interactiveViewer.pluginControl[PLUGIN_NAME].initStateUrl
-  console.log(initState, initUrl)
-})()
\ No newline at end of file
diff --git a/src/plugin_examples/server.js b/src/plugin_examples/server.js
deleted file mode 100644
index 355a0d40287f3748f6100ef6995e8583f3c2d920..0000000000000000000000000000000000000000
--- a/src/plugin_examples/server.js
+++ /dev/null
@@ -1,44 +0,0 @@
-const express = require('express')
-const fs = require('fs')
-const path = require('path')
-
-const app = express()
-
-const cors = (req, res, next)=>{
-  res.setHeader('Access-Control-Allow-Origin','*')
-  next()
-}
-
-app.get('/allPluginManifests', cors, (req, res) => {
-  try{
-    res.status(200).send(JSON.stringify(
-      fs.readdirSync(__dirname)
-        .filter(file => fs.statSync(path.join(__dirname, file)).isDirectory())
-        .filter(dir => fs.existsSync(path.join(__dirname, dir, 'manifest.json')))
-        .map(dir => JSON.parse(fs.readFileSync(path.join(__dirname, dir, 'manifest.json'), 'utf-8')))
-    ))
-  }catch(e){
-    res.status(500).send(JSON.stringify(e))
-  }
-})
-
-app.get('/test.json', cors, (req, res) => {
-
-  console.log('test.json pinged')
-  res.status(200).send(JSON.stringify({
-    "name": "fzj.xg.mime",
-    "displayName":"Mime",
-    "type": "plugin",
-    "templateURL": "http://localhost:10080/mime/template.html",
-    "scriptURL": "http://localhost:10080/mime/script.js",
-    "initState" : {
-      "test" : "value"
-    }
-  }))
-})
-
-app.use(cors,express.static(__dirname))
-
-app.listen(10080, () => {
-  console.log(`listening on 10080, serving ${__dirname}`)
-})
\ No newline at end of file
diff --git a/src/res/css/extra_styles.css b/src/res/css/extra_styles.css
index bef52b79290f782e09f6f08e67a9ea7a5fc8adc2..f383116251b99198bd07bb5ba980211616b166bf 100644
--- a/src/res/css/extra_styles.css
+++ b/src/res/css/extra_styles.css
@@ -301,11 +301,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;
@@ -333,7 +348,7 @@ markdown-dom pre code
 
 .overflow-x-hidden
 {
-  overflow-x:hidden;
+  overflow-x:hidden!important;
 }
 
 .muted
@@ -360,4 +375,32 @@ markdown-dom pre code
 .bs-content-box
 {
   box-sizing: content-box;
+}
+
+/* required to hide  */
+.cdk-global-scrollblock
+{
+  overflow-y:hidden !important;
+}
+.h-90vh
+{
+  height: 90vh!important;
+}
+
+.w-50vw
+{
+  width: 50vw!important;
+}
+/* TODO fix hack */
+/* ngx boostrap default z index for modal-container is 1050, which is higher than material tooltip 1000 */
+/* when migration away from ngx bootstrap is complete, remove these classes */
+
+modal-container.modal
+{
+  z-index: 950;
+}
+
+bs-modal-backdrop.modal-backdrop
+{
+  z-index: 940;
 }
\ No newline at end of file
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/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 7ab1195be0cd9d02f8522e2bc00688998fc0dc23..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, Observable } from "rxjs";
-import { withLatestFrom, map, filter, shareReplay } from "rxjs/operators";
+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, DESELECT_REGIONS } 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';
 
@@ -44,6 +44,27 @@ export class UseEffects implements OnDestroy{
         }
       })
     )
+
+    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[]>
@@ -75,48 +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
@@ -143,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/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/viewerConfig.store.ts b/src/services/state/viewerConfig.store.ts
index eb634fee9dbeda27a7e5915258048bd43b93c885..e6bf3881472833ec2f2adbf939ecbf41f21ee993 100644
--- a/src/services/state/viewerConfig.store.ts
+++ b/src/services/state/viewerConfig.store.ts
@@ -21,16 +21,29 @@ export const CONFIG_CONSTANTS = {
 }
 
 export const ACTION_TYPES = {
+  SET_ANIMATION: `SET_ANIMATION`,
   UPDATE_CONFIG: `UPDATE_CONFIG`,
   CHANGE_GPU_LIMIT: `CHANGE_GPU_LIMIT`
 }
 
-const lsGpuLimit = localStorage.getItem('iv-gpulimit')
+export const LOCAL_STORAGE_CONST = {
+  GPU_LIMIT: 'iv-gpulimit',
+  ANIMATION: 'iv-animationFlag'
+}
+
+const lsGpuLimit = localStorage.getItem(LOCAL_STORAGE_CONST.GPU_LIMIT)
+const lsAnimationFlag = localStorage.getItem(LOCAL_STORAGE_CONST.ANIMATION)
 const gpuLimit = lsGpuLimit && !isNaN(Number(lsGpuLimit))
   ? Number(lsGpuLimit)
   : CONFIG_CONSTANTS.defaultGpuLimit
 
-export function viewerConfigState(prevState:ViewerConfiguration = {animation: CONFIG_CONSTANTS.defaultAnimation, gpuLimit}, action:ViewerConfigurationAction) {
+const animation = lsAnimationFlag && lsAnimationFlag === 'true'
+  ? true
+  : lsAnimationFlag === 'false'
+    ? false
+    : CONFIG_CONSTANTS.defaultAnimation
+
+export function viewerConfigState(prevState:ViewerConfiguration = {animation, gpuLimit}, action:ViewerConfigurationAction) {
   switch (action.type) {
     case ACTION_TYPES.UPDATE_CONFIG:
       return {
diff --git a/src/services/state/viewerState.store.ts b/src/services/state/viewerState.store.ts
index 874b8e311828accc4fde2e5a73287e869e05eb7a..07702ffc0adbc76c0796193314c8769b8a6db33c 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,6 +38,8 @@ export interface AtlasAction extends Action{
   deselectLandmarks : UserLandmark[]
 
   navigation? : any
+
+  payload: any
 }
 
 export function viewerState(
@@ -42,6 +48,7 @@ export function viewerState(
     fetchedTemplates : [],
     loadedNgLayers: [],
     regionsSelected: []
+    userLandmarks: []
   },
   action:AtlasAction
 ){
@@ -107,7 +114,10 @@ export function viewerState(
       const { updatedParcellation } = action
       return {
         ...state,
-        parcellationSelected: updatedParcellation
+        parcellationSelected: {
+          ...updatedParcellation,
+          updated: true
+        }
       }
     }
     case SELECT_REGIONS:
@@ -134,6 +144,10 @@ export function viewerState(
         userLandmarks: action.landmarks
       } 
     }
+    /**
+     * TODO
+     * duplicated with ngViewerState.layers ?
+     */
     case NEHUBA_LAYER_CHANGED: {
       if (!window['viewer']) {
         return {
@@ -175,4 +189,81 @@ 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/config/config.component.ts b/src/ui/config/config.component.ts
index e0104405a1b4bc8efda4e8d5b8e18af4598b7b06..12a3a700a6f84bdf71fa77699c0be0b772756385 100644
--- a/src/ui/config/config.component.ts
+++ b/src/ui/config/config.component.ts
@@ -2,7 +2,11 @@ import { Component, OnInit, OnDestroy } from '@angular/core'
 import { Store, select } from '@ngrx/store';
 import { ViewerConfiguration, ACTION_TYPES } from 'src/services/state/viewerConfig.store'
 import { Observable, Subject, Subscription } from 'rxjs';
-import { map, distinctUntilChanged, debounce, debounceTime } from 'rxjs/operators';
+import { map, distinctUntilChanged, debounceTime } from 'rxjs/operators';
+import { MatSlideToggleChange } from '@angular/material';
+
+const GPU_TOOLTIP = `GPU TOOLTIP`
+const ANIMATION_TOOLTIP = `ANIMATION_TOOLTIP`
 
 @Component({
   selector: 'config-component',
@@ -14,10 +18,15 @@ import { map, distinctUntilChanged, debounce, debounceTime } from 'rxjs/operator
 
 export class ConfigComponent implements OnInit, OnDestroy{
 
+  public GPU_TOOLTIP = GPU_TOOLTIP
+  public ANIMATION_TOOLTIP = ANIMATION_TOOLTIP
+
   /**
    * in MB
    */
   public gpuLimit$: Observable<number>
+
+  public animationFlag$: Observable<boolean>
   public keydown$: Subject<Event> = new Subject()
   private subscriptions: Subscription[] = []
 
@@ -31,6 +40,11 @@ export class ConfigComponent implements OnInit, OnDestroy{
       distinctUntilChanged(),
       map(v => v / 1e6)
     )
+
+    this.animationFlag$ = this.store.pipe(
+      select('viewerConfigState'),
+      map((config:ViewerConfiguration) => config.animation),
+    )
   }
 
   ngOnInit(){
@@ -64,6 +78,16 @@ export class ConfigComponent implements OnInit, OnDestroy{
     })
   }
 
+  public toggleAnimationFlag(ev: MatSlideToggleChange ){
+    const { checked } = ev
+    this.store.dispatch({
+      type: ACTION_TYPES.UPDATE_CONFIG,
+      config: {
+        animation: checked
+      }
+    })
+  }
+
   public setGpuPreset({value}: {value: number}) {
     this.store.dispatch({
       type: ACTION_TYPES.UPDATE_CONFIG,
diff --git a/src/ui/config/config.template.html b/src/ui/config/config.template.html
index 18f84d0b297f9ef71422960695f032c567faafb1..dca3b164d0fbe6bd4235e50e856073835acb13d0 100644
--- a/src/ui/config/config.template.html
+++ b/src/ui/config/config.template.html
@@ -1,7 +1,8 @@
-<div class="input-group">
+<div class="input-group mb-2">
   <span class="input-group-prepend">
     <span class="input-group-text">
       GPU Limit
+      <small tooltipClass="z-1060" [matTooltip]="GPU_TOOLTIP" class="ml-2 fas fa-question"></small>
     </span>
   </span>
   <input
@@ -17,16 +18,25 @@
   <div class="input-group-append">
 
     <div (click)="setGpuPreset({ value: 100 })" class="btn btn-outline-secondary">
-        100
-      </div>
-      <div (click)="setGpuPreset({ value: 500 })" class="btn btn-outline-secondary">
-        500
-      </div>
-      <div (click)="setGpuPreset({ value: 1000 })" class="btn btn-outline-secondary">
-        1000
-      </div>
-      <span class="input-group-text">
-        MB
-      </span>
+      100
     </div>
-  </div>
\ No newline at end of file
+    <div (click)="setGpuPreset({ value: 500 })" class="btn btn-outline-secondary">
+      500
+    </div>
+    <div (click)="setGpuPreset({ value: 1000 })" class="btn btn-outline-secondary">
+      1000
+    </div>
+    <span class="input-group-text">
+      MB
+    </span>
+  </div>
+</div>
+
+<div class="d-flex align-items-center">
+  <mat-slide-toggle
+    [checked]="animationFlag$ | async"
+    (change)="toggleAnimationFlag($event)">
+    Enable Animation
+  </mat-slide-toggle>
+  <small [matTooltip]="ANIMATION_TOOLTIP" class="ml-2 fas fa-question"></small>
+</div>
\ No newline at end of file
diff --git a/src/ui/cookieAgreement/cookieAgreement.template.html b/src/ui/cookieAgreement/cookieAgreement.template.html
index d0b6acdf5d2a98b0ec64f94640507d22c283cc91..b92a7d8f8c7d592823ad9b7fcd4ff198685f1c1e 100644
--- a/src/ui/cookieAgreement/cookieAgreement.template.html
+++ b/src/ui/cookieAgreement/cookieAgreement.template.html
@@ -11,11 +11,17 @@
   <p>To opt-out of being tracked by Google Analytics across all websites, visit
     <a href="http://tools.google.com/dlpage/gaoptout">http://tools.google.com/dlpage/gaoptout</a> .</p>
 
-  <button
-    class="btn btn-outline-info btn-block mb-2"
-    (click)="showMore = !showMore">
-    Show {{showMore? "less" : "more"}}
-  </button>
+  <div class="d-flex">
+
+    <button
+      mat-stroked-button
+      color="primary"
+      class="d-flex justify-content-center flex-grow-1"
+      (click)="showMore = !showMore">
+      Show {{showMore? "less" : "more"}}
+    </button>
+  
+  </div>
 
   <div *ngIf="showMore">
     <small>
diff --git a/src/ui/databrowserModule/databrowser.module.ts b/src/ui/databrowserModule/databrowser.module.ts
index d272cf211d8f2b0648453c12ddccd88d7f094ce1..42dcfc4305c466b61730dbe75a49b2bc5dddddab 100644
--- a/src/ui/databrowserModule/databrowser.module.ts
+++ b/src/ui/databrowserModule/databrowser.module.ts
@@ -25,6 +25,7 @@ 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 { RegionBackgroundToRgbPipe } from "./util/regionBackgroundToRgb.pipe";
 
 @NgModule({
   imports:[
@@ -56,7 +57,8 @@ import { DoiParserPipe } from "src/util/pipes/doiPipe.pipe";
     FilterDataEntriesbyMethods,
     FilterDataEntriesByRegion,
     AggregateArrayIntoRootPipe,
-    DoiParserPipe
+    DoiParserPipe,
+    RegionBackgroundToRgbPipe
   ],
   exports:[
     DataBrowser,
diff --git a/src/ui/databrowserModule/databrowser.service.ts b/src/ui/databrowserModule/databrowser.service.ts
index 0b3c964f71c4819410a5af212bd0355b4411ec13..1400e7a55213e8a95473c25eae03e8e52b549846 100644
--- a/src/ui/databrowserModule/databrowser.service.ts
+++ b/src/ui/databrowserModule/databrowser.service.ts
@@ -309,12 +309,6 @@ export class DatabrowserService implements OnDestroy{
   }
 
   public getModalityFromDE = getModalityFromDE
-
-  public getBackgroundColorStyleFromRegion(region:any = null){
-    return region && region.rgb
-      ? `rgb(${region.rgb.join(',')})`
-      : `white`
-  }
 }
 
 
diff --git a/src/ui/databrowserModule/databrowser/databrowser.component.ts b/src/ui/databrowserModule/databrowser/databrowser.component.ts
index b3568e039e3be5c046e2571e2d014ed1cac1877d..9a4f538541b7f4301fa6e7ff3d6e4932f9d5dd2c 100644
--- a/src/ui/databrowserModule/databrowser/databrowser.component.ts
+++ b/src/ui/databrowserModule/databrowser/databrowser.component.ts
@@ -155,10 +155,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..f852c03d933f8f7740a696510c058a0e622c9962 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">
@@ -122,7 +122,7 @@
             <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/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 2773794f939241cccee6d4b5457ccee0e0a6511c..3ff7f8b1db939b83b0fb91d7f057aa84841c0a04 100644
--- a/src/ui/menuicons/menuicons.component.ts
+++ b/src/ui/menuicons/menuicons.component.ts
@@ -2,7 +2,6 @@ import { Component, ComponentRef, Injector, ComponentFactory, ComponentFactoryRe
 
 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";
@@ -10,7 +9,7 @@ import { DatabrowserService } from "../databrowserModule/databrowser.service";
 import { PluginServices, PluginManifest } from "src/atlasViewer/atlasViewer.pluginService.service";
 import { Store, select } from "@ngrx/store";
 import { Observable, BehaviorSubject, combineLatest, merge, of } from "rxjs";
-import { map, shareReplay } from "rxjs/operators";
+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";
 
@@ -34,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
    */
@@ -56,9 +48,6 @@ export class MenuIconsBar{
   public themedBtnClass$: Observable<string>
 
   public skeletonBtnClass$: Observable<string>
-  
-  private layerBrowserExists$: BehaviorSubject<boolean> = new BehaviorSubject(false)
-  public layerBrowserBtnClass$: Observable<string> 
 
   public toolBtnClass$: Observable<string>
   public getKgSearchBtnCls$: Observable<[Set<WidgetUnit>, string]>
@@ -87,7 +76,6 @@ export class MenuIconsBar{
     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(
@@ -95,13 +83,10 @@ export class MenuIconsBar{
       select('templateSelected')
     )
 
-    this.selectedRegions$ = merge(
-      of([]),
-      store.pipe(
-        select('viewerState'),
-        select('regionsSelected')
-      )
-    ).pipe(
+    this.selectedRegions$ = store.pipe(
+      select('viewerState'),
+      select('regionsSelected'),
+      startWith([]),
       shareReplay(1)
     )
 
@@ -115,18 +100,14 @@ export class MenuIconsBar{
       shareReplay(1)
     )
 
-    this.layerBrowserBtnClass$ = combineLatest(
-      this.layerBrowserExists$,
-      this.themedBtnClass$
-    ).pipe(
-      map(([flag,themedBtnClass]) => `${this.mobileRespBtnClass} ${flag ? 'btn-primary' : themedBtnClass}`)
-    )
-
     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$,
@@ -135,6 +116,9 @@ export class MenuIconsBar{
 
     this.darktheme$ = this.constantService.darktheme$
 
+    /**
+     * TODO remove dependency on themedBtnClass$
+     */
     this.getKgSearchBtnCls$ = combineLatest(
       this.widgetServices.minimisedWindow$,
       this.themedBtnClass$
@@ -170,37 +154,6 @@ 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.layerBrowserExists$.next(true)
-
-    this.lbWidget.onDestroy(() => {
-      this.layerBrowserExists$.next(false)
-      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()
@@ -245,45 +198,6 @@ export class MenuIconsBar{
     this.widgetServices.exitWidget(wu)
   }
 
-  public deselectRegion(event: MouseEvent, region: any){
-    event.stopPropagation()
-    
-    this.store.dispatch({
-      type: DESELECT_REGIONS,
-      deselectRegions: [region]
-    })
-  }
-
-  public deselectAllRegions(event: MouseEvent){
-    event.stopPropagation()
-    this.store.dispatch({
-      type: SELECT_REGIONS,
-      selectRegions: []
-    })
-  }
-
-  public gotoRegion(event: MouseEvent, region:any){
-    event.stopPropagation()
-
-    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
-      })
-    }
-  }
-
   public renameKgSearchWidget(event:MouseEvent, wu: WidgetUnit) {
     event.stopPropagation()
   }
diff --git a/src/ui/menuicons/menuicons.style.css b/src/ui/menuicons/menuicons.style.css
index 1873b453df3ac917f1f16c1f00300ff381a7a39a..c76c706c7151f5276eaba98168489ee4bfc82dc5 100644
--- a/src/ui/menuicons/menuicons.style.css
+++ b/src/ui/menuicons/menuicons.style.css
@@ -32,38 +32,7 @@
   margin-top: 0.1em;
 }
 
-.virtual-scroll-viewport-container
+layer-browser
 {
-  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;
-}
+  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 796fe089095132054a501c5ec23b3046c1ea0725..00b4065a9219b171ebc38c8df0dce6d503eaa460 100644
--- a/src/ui/menuicons/menuicons.template.html
+++ b/src/ui/menuicons/menuicons.template.html
@@ -5,92 +5,148 @@
 <ng-template [ngIf]="selectedTemplate$ | async">
 
   <!-- layer browser -->
-  <div
-    matTooltip="Layer"
-    matTooltipPosition="right"
-    (click)="clickLayer($event)"
-    [class]="layerBrowserBtnClass$ | async"
-    class="btn">
-    <i class="fas fa-layer-group">
-    </i>
-  </div>
+  <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
+      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>
+  </sleight-of-hand>
+  
   <!-- tools -->
   <sleight-of-hand>
 
     <!-- shown icon prior to mouse over -->
-    <div
-      [matBadgePosition]="badgetPosition"  
-      [matBadge]="(launchedPlugins$ | async)?.length > 0 ? (launchedPlugins$ | async)?.length : null"
-      [class]="(skeletonBtnClass$ | async) + ' btn'"
-      sleight-of-hand-front>
-      <i class="fas fa-tools"></i>
+    <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>
 
     <!-- shown after mouse over -->
     <div
-      class="d-flex flex-row soh-row"
+      class="d-flex flex-row soh-row align-items-start"
       sleight-of-hand-back>
 
       <!-- placeholder icon -->
-      <div
+      <button
         [matBadgePosition]="badgetPosition"  
+        matBadgeColor="accent"
         [matBadge]="(launchedPlugins$ | async)?.length > 0 ? (launchedPlugins$ | async)?.length : null"
-        [class]="(skeletonBtnClass$ | async) + ' btn muted'">
+        mat-icon-button
+        color="primary">
         <i class="fas fa-tools"></i>
-      </div>
+      </button>
 
       <!-- render all fetched tools -->
-      <div class="d-flex flex-column soh-column">
-        <div
-          *ngFor="let manifest of pluginServices.fetchedPluginManifests"
-          [matTooltip]="manifest.displayName || manifest.name"
-          matTooltipPosition="right"
-          (click)="clickPluginIcon(manifest)"
-          [class]="(getPluginBtnClass$ | async | menuIconPluginBtnClsPipe : manifest.name) + ' btn w-1em bs-content-box ' + mobileRespBtnClass">
-          {{ (manifest.displayName || manifest.name).slice(0, 1) }}
-        </div>
+      <div class="d-flex flex-row soh-row">
 
         <!-- add new tool btn -->
-        <div
+        <button
           matTooltip="Add new plugin"
-          [matTooltipShowDelay]="100"
-          matTooltipPosition="right"
-          [class]="(skeletonBtnClass$ | async)">
+          matTooltipPosition="below"
+          mat-icon-button
+          color="primary">
           <i class="fas fa-plus"></i>
-        </div>
+        </button>
+        
+        <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>
   </sleight-of-hand>
 
   <!-- search kg -->
   <sleight-of-hand>
+    
     <!-- shown icon prior to mouse over -->
-    <div
-      sleight-of-hand-front
-      [class]="(skeletonBtnClass$ | async) + ' btn'"
-      [matBadgePosition]="badgetPosition"
-      [matBadge]="dbService.instantiatedWidgetUnits.length > 0 ? dbService.instantiatedWidgetUnits.length : null">
-      <i class="fas fa-search"></i>
+    <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
       sleight-of-hand-back
-      class="d-flex flex-row align-items-center soh-row">
+      class="d-flex flex-row align-items-center soh-row pe-none">
 
       <!-- placeholder icon -->
-      <div
-        [ngClass]="(skeletonBtnClass$ | async) + ' btn muted'"
+      <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>
-      </div>
+      </button>
 
       <!-- only renders if there is at least one search result -->
       <div
         *ngIf="dbService.instantiatedWidgetUnits.length > 0; else noKgSearchTemplate"
-        class="position-relative">
+        class="position-relative pe-all">
 
         <div class="position-absolute d-flex flex-column soh-column">
 
@@ -100,17 +156,24 @@
             (click)="searchIconClickHandler(wu)">
 
             <!-- shown prior to mouseover -->
-            <div
-              [class]="(getKgSearchBtnCls$ | async | menuIconKgSearchBtnClsPipe : wu) + ' position-relative sleight-of-hand btn w-1em bs-content-box ' + mobileRespBtnClass"
-              sleight-of-hand-front>
-              <i class="fas fa-search"></i>
+            <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 [class]="(getKgSearchBtnCls$ | async | menuIconKgSearchBtnClsPipe : wu) + ' btn w-1em bs-content-box ' + mobileRespBtnClass">
-                <i class="fas fa-search"></i>
+
+              <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 -->
@@ -179,7 +242,9 @@
 
         <!-- invisible icon to keep height of the otherwise unstable flex block -->
         <div class="invisible pe-none">
-          <i class="fas fa-search"></i>
+          <button mat-icon-button>
+            <i class="fas fa-search"></i>
+          </button>
         </div>
       </div>
 
@@ -194,16 +259,19 @@
   </sleight-of-hand>
 
   <!-- selected regions -->
-  <sleight-of-hand>
+  <sleight-of-hand
+    [doNotClose]="viewerStateController.focused">
 
     <!-- shown prior to mouse over -->
-    <div
-      sleight-of-hand-front
-      [class]="(skeletonBtnClass$ | async) + ' btn'"
-      [matBadgePosition]="badgetPosition"
-      matBadgeColor="warn"
-      [matBadge]="(selectedRegions$ | async).length > 0 ? (selectedRegions$ | async).length : null">
-      <i class="fas fa-brain"></i>
+    <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>
 
     <!-- shown upon mouseover -->
@@ -212,84 +280,21 @@
       class="d-flex flex-row align-items-center soh-row">
 
       <!-- place holder icon -->
-      <div
-        [class]="(skeletonBtnClass$ | async) + ' btn muted'"
+      <button
+        [matBadge]="(selectedRegions$ | async).length > 0 ? (selectedRegions$ | async).length : null"
         [matBadgePosition]="badgetPosition"
-        matBadgeColor="warn"
-        [matBadge]="(selectedRegions$ | async).length > 0 ? (selectedRegions$ | async).length : null">
+        matBadgeColor="accent"
+        mat-icon-button
+        color="primary">
         <i class="fas fa-brain"></i>
-      </div>
+      </button>
 
-      <div
-        *ngIf="(selectedRegions$ | async).length > 0; else noBrainRegionSelected"
-        class="position-relative">
-
-        <!-- rendering all the selected regions -->
-        <div [class]="((darktheme$ | async) ? 'text-light bg-dark' : 'text-dark bg-light') + ' position-absolute'">
-          <div class="m-1 position-relative">
-            <button
-              mat-raised-button
-              (click)="deselectAllRegions($event)"
-              class="virtual-scroll-row"
-              color="warn">
-              <i class="fas fa-trash"></i>
-              Deselect all {{ (selectedRegions$ | async).length }} region{{ (selectedRegions$ | async).length > 1 ? 's' : '' }}
-            </button>
-          </div>
-          <mat-divider></mat-divider>
-          <div class="virtual-scroll-viewport-container">
-            <cdk-virtual-scroll-viewport
-              class="d-flex-flex-column soh-column"
-              itemSize="26">
-              <div
-                *cdkVirtualFor="let region of (selectedRegions$ | async)"
-                class="virtual-scroll-unit">
-                <sleight-of-hand>
-                  <!-- prior to mouseover -->
-                  <div
-                    [class]="((darktheme$ | async) ? 'text-light bg-dark' : 'text-dark bg-light') + ' d-flex flex-row align-items-center virtual-scroll-row'"
-                    sleight-of-hand-front>
-                    <div class="ml-2 cursor-default text-nowrap">
-                      {{ region.name }}
-                    </div>
-      
-                    <!-- place holder icon to support height  -->
-                    <div [class]="(skeletonBtnClass$ | async) + ' invisible w-0 pe-none'">
-                      <i class="fas fa-trash"></i>
-                    </div>
-                  </div>
-      
-                  <!-- on mouse over -->
-                  <div
-                    [class]="((darktheme$ | async) ? 'text-light bg-dark' : 'text-dark bg-light') + ' d-flex flex-row align-items-center virtual-scroll-row'"
-                    sleight-of-hand-back>
-                    <div class="text-truncate selected-region-container ml-2 cursor-default text-nowrap">
-                      {{ region.name }}
-                    </div>
-                    <div
-                      *ngIf="region.position"
-                      (click)="gotoRegion($event, region)"
-                      matTooltip="Goto"
-                      matTooltipPosition="below"
-                      [class]="(skeletonBtnClass$ | async) + ' selected-region-actionbtn'">
-                      <i class="fas fa-map-marked-alt"></i>
-                    </div>
-                    <div
-                      #trashBtn="matTooltip"
-                      (mousedown)="trashBtn.hide()"
-                      (click)="deselectRegion($event, region)"
-                      matTooltip="Deselect"
-                      matTooltipPosition="below"
-                      [class]="(skeletonBtnClass$ | async) + ' selected-region-actionbtn'">
-                      <i class="fas fa-trash"></i>
-                    </div>
-                  </div>
-                </sleight-of-hand>
-              </div>
-            </cdk-virtual-scroll-viewport>
-          </div>
-        </div>
+      <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>
diff --git a/src/ui/nehubaContainer/nehubaContainer.component.ts b/src/ui/nehubaContainer/nehubaContainer.component.ts
index 00760f639cc3b2ae9e0ebe3f8d8f4523b9ee2352..dc5d758c8617f758244d9e43655ae3c2cbf5a386 100644
--- a/src/ui/nehubaContainer/nehubaContainer.component.ts
+++ b/src/ui/nehubaContainer/nehubaContainer.component.ts
@@ -3,7 +3,7 @@ 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 { Observable, Subscription, fromEvent, combineLatest, merge } from "rxjs";
-import { filter,map, take, scan, debounceTime, distinctUntilChanged, switchMap, skip, withLatestFrom, buffer, tap, throttleTime, bufferTime } from "rxjs/operators";
+import { filter,map, take, scan, debounceTime, distinctUntilChanged, switchMap, skip, withLatestFrom, buffer, tap, 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,7 +11,7 @@ import { ViewerConfiguration } from "src/services/state/viewerConfig.store";
 import { pipeFromArray } from "rxjs/internal/util/pipe";
 import { NEHUBA_READY } 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 { SELECT_REGIONS_WITH_ID, NEHUBA_LAYER_CHANGED, VIEWERSTATE_ACTION_TYPES } from "src/services/state/viewerState.store";
 
 const getProxyUrl = (ngUrl) => `nifti://${BACKEND_URL}preview/file?fileUrl=${encodeURIComponent(ngUrl.replace(/^nifti:\/\//,''))}`
 const getProxyOther = ({source}) => /AUTH_227176556f3c4bb38df9feea4b91200c/.test(source)
@@ -135,8 +135,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[] = []
 
@@ -220,13 +218,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(
@@ -454,10 +448,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)
         }
@@ -970,14 +961,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) => {
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 a1b13144fec592d6f83f785474645a733994f120..32588e77ca2e1baa6fe71018c330d99444c56af4 100644
--- a/src/ui/sharedModules/angularMaterial.module.ts
+++ b/src/ui/sharedModules/angularMaterial.module.ts
@@ -6,12 +6,58 @@ import {
   MatTabsModule,
   MatTooltipModule,
   MatBadgeModule,
-  MatDividerModule
+  MatDividerModule,
+  MatSelectModule,
+  MatChipsModule,
+  MatAutocompleteModule,
+  MatDialogModule,
+  MatInputModule,
+  MatBottomSheetModule,
+  MatListModule,
+  MatSlideToggleModule
 } from '@angular/material';
 import { NgModule } from '@angular/core';
 
+/**
+ * TODO should probably be in src/util
+ */
+
 @NgModule({
-  imports: [MatDividerModule, MatBadgeModule, MatButtonModule, MatCheckboxModule, MatSidenavModule, MatCardModule, MatTabsModule, MatTooltipModule],
-  exports: [MatDividerModule, MatBadgeModule, MatButtonModule, MatCheckboxModule, MatSidenavModule, MatCardModule, MatTabsModule, MatTooltipModule],
+  imports: [
+    MatDividerModule,
+    MatBadgeModule,
+    MatButtonModule,
+    MatCheckboxModule,
+    MatSidenavModule,
+    MatCardModule,
+    MatTabsModule,
+    MatTooltipModule,
+    MatSelectModule,
+    MatChipsModule,
+    MatAutocompleteModule,
+    MatDialogModule,
+    MatInputModule,
+    MatBottomSheetModule,
+    MatListModule,
+    MatSlideToggleModule
+  ],
+  exports: [
+    MatDividerModule,
+    MatBadgeModule,
+    MatButtonModule,
+    MatCheckboxModule,
+    MatSidenavModule,
+    MatCardModule,
+    MatTabsModule,
+    MatTooltipModule,
+    MatSelectModule,
+    MatChipsModule,
+    MatAutocompleteModule,
+    MatDialogModule,
+    MatInputModule,
+    MatBottomSheetModule,
+    MatListModule,
+    MatSlideToggleModule
+  ],
 })
 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 07428cb0c23ac552916b15f323c58a21c574631f..f559248fe9f5b47382e39b0cb7f3d52da6e7b705 100644
--- a/src/ui/signinBanner/signinBanner.components.ts
+++ b/src/ui/signinBanner/signinBanner.components.ts
@@ -1,18 +1,9 @@
 import {Component, ChangeDetectionStrategy, OnDestroy, OnInit, Input, ViewChild, 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 { Store} 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";
-
-const compareParcellation = (o, n) => !o || !n
-  ? false
-  : o.name === n.name
 
 @Component({
   selector: 'signin-banner',
@@ -24,199 +15,18 @@ const compareParcellation = (o, n) => !o || !n
   changeDetection: ChangeDetectionStrategy.OnPush
 })
 
-export class SigninBanner implements OnInit, OnDestroy{
-
-  public compareParcellation = compareParcellation
-
-  private subscriptions: Subscription[] = []
-  
-  public loadedTemplates$: Observable<any[]>
+export class SigninBanner{
 
-  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>
-
-  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 store: Store<ViewerConfiguration>
   ){
-    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 {
-        /**
-         * TODO convert to snack bar
-         */
-        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
   }
 
   showHelp() {
@@ -227,56 +37,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 c3d288680c6670e0fe8c0d555e8895d70427d1e1..5636dc1707484127091201d0d09e7bae552e1152 100644
--- a/src/ui/signinBanner/signinBanner.template.html
+++ b/src/ui/signinBanner/signinBanner.template.html
@@ -2,96 +2,30 @@
   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">
-    <div
-      *ngIf="!isMobile"
+    <button
+      matTooltip="About"
+      matTooltipPosition="below"
       (click)="showHelp()" 
-      class="btn btn-outline-secondary btn-sm rounded-circle login-icon">
-      <i class="fas fa-question-circle"></i>
-    </div>
+      mat-icon-button
+      color="basic">
+      <i class="fas fa-question"></i>
+    </button>
   </div>
 
   <!-- signin -->
-
   <div class="btnWrapper">
-
-    <div 
-      *ngIf="!isMobile"
+    <button
+      [matTooltip]="user && user.name ? ('Logged in as ' + (user && user.name)) : 'Not logged in'"
+      matTooltipPosition="below"
       (click)="showSignin()"
-      class="btn btn-outline-secondary btn-sm rounded-circle login-icon">
-      <i
-        [ngClass]="user ? 'fa-user' : 'fa-sign-in-alt'"
-        class="fas"></i>
-    </div>
-  </div>
-
-  <div *ngIf="isMobile" class="login-button-panel-mobile">
-    <div
-      (click)="showSignin()"
-      class="login-button-mobile">
-      <button mat-button [ngStyle]="{'color': darktheme? '#D7D7D7' : 'black'}">Log In</button>
-    </div>
-
-    <div (click)="showHelp()" class="login-button-mobile">
-      <button mat-button [ngStyle]="{'color': darktheme? '#D7D7D7' : 'black'}">Help</button>
-    </div>
-
+      mat-icon-button
+      color="primary">
+      <i *ngIf="!user; else userInitialTempl" class="fas fa-user"></i>
+      <ng-template #userInitialTempl>
+        {{ (user && user.name || 'Unnamed User').slice(0,1) }}
+      </ng-template>
+    </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>
\ No newline at end of file
diff --git a/src/ui/signinModal/signinModal.template.html b/src/ui/signinModal/signinModal.template.html
index 7d2ce3b21d85ca8840b4bf3592f4067d88507f1c..92feed3d10578773b41cc7c24ede52debef857f4 100644
--- a/src/ui/signinModal/signinModal.template.html
+++ b/src/ui/signinModal/signinModal.template.html
@@ -1,24 +1,29 @@
 <div *ngIf="user; else notLoggedIn">
-  Hi {{ user.name }}.
+  Logged in as {{ user && user.name || 'Unnamed User' }}.
   <a
     (click)="loginBtnOnclick()"
-    [href]="logoutHref"
-    class="btn btn-sm btn-outline-secondary">
-    <i class="fas fa-sign-out-alt"></i> Logout
+    [href]="logoutHref">
+    <button
+      mat-button
+      color="warn">
+      <i class="fas fa-sign-out-alt"></i> Logout
+    </button>
   </a>
 </div>
 
 <ng-template #notLoggedIn>
   <div>
     Not logged in. Login via:
-    <div class="btn-group-vertical">
-      <a
-        *ngFor="let m of loginMethods"
+
+    <a *ngFor="let m of loginMethods"
+      [href]="m.href">
+      <button 
         (click)="loginBtnOnclick()"
-        [href]="m.href"
-        class="btn btn-sm btn-outline-secondary">
+        mat-raised-button
+        color="primary">
         <i class="fas fa-sign-in-alt"></i> {{ m.name }}
-      </a>
-    </div>
+      </button> 
+    </a>
+
   </div>
 </ng-template>
\ No newline at end of file
diff --git a/src/ui/ui.module.ts b/src/ui/ui.module.ts
index 7e69c597b1dfb9e7828b9fcb9dd82d711a090757..a549034fb7e5a4e895b3ed9b96f580ea52be6b28 100644
--- a/src/ui/ui.module.ts
+++ b/src/ui/ui.module.ts
@@ -5,7 +5,7 @@ import { NehubaViewerUnit } from "./nehubaContainer/nehubaViewer/nehubaViewer.co
 import { NehubaContainer } from "./nehubaContainer/nehubaContainer.component";
 import { SplashScreen } 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,26 +38,31 @@ 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 { MenuIconPluginBtnClsPipe } from "src/util/pipes/menuIconPluginBtnCls.pipe";
-import { MenuIconKgSearchBtnClsPipe } from "src/util/pipes/menuIconKgSearchBtnCls.pipe";
+import { AppendtooltipTextPipe } from "src/util/pipes/appendTooltipText.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,
@@ -90,6 +95,8 @@ import { HttpClientModule } from "@angular/common/http";
     StatusCardComponent,
     CookieAgreement,
     KGToS,
+    ViewerStateController,
+    RegionTextSearchAutocomplete,
 
     /* pipes */
     GroupDatasetByRegion,
@@ -101,12 +108,16 @@ import { HttpClientModule } from "@angular/common/http";
     SortDataEntriesToRegion,
     SpatialLandmarksToDataBrowserItemPipe,
     FilterNullPipe,
-    FilterNgLayer,
     FilterNameBySearch,
     TemplateParcellationsDecorationPipe,
     AppendtooltipTextPipe,
-    MenuIconPluginBtnClsPipe,
-    MenuIconKgSearchBtnClsPipe,
+    PluginBtnFabColorPipe,
+    KgSearchBtnColorPipe,
+    LockedLayerBtnClsPipe,
+    GetFilenamePipe,
+    GetFileExtension,
+    BinSavedRegionsSelectionPipe,
+    SavedRegionsSelectionBtnDisabledPipe,
 
     /* directive */
     DownloadDirective,
@@ -117,7 +128,7 @@ import { HttpClientModule } from "@angular/common/http";
     /* 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 80%
rename from src/ui/regionHierachy/regionHierarchy.component.ts
rename to src/ui/viewerStateController/regionHierachy/regionHierarchy.component.ts
index 397d87c947162bc0c0d9a1c96a45b9032054d1cb..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";
 
@@ -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/menuIconKgSearchBtnCls.pipe.ts b/src/util/pipes/kgSearchBtnColor.pipe.ts
similarity index 55%
rename from src/util/pipes/menuIconKgSearchBtnCls.pipe.ts
rename to src/util/pipes/kgSearchBtnColor.pipe.ts
index 1b6b1d7b560f6d5bc8548b911402d51ff446a76f..c9bbb25bf655bbe4d5e90ef08768f058fae915f2 100644
--- a/src/util/pipes/menuIconKgSearchBtnCls.pipe.ts
+++ b/src/util/pipes/kgSearchBtnColor.pipe.ts
@@ -2,13 +2,13 @@ import { Pipe, PipeTransform } from "@angular/core";
 import { WidgetUnit } from "src/atlasViewer/widgetUnit/widgetUnit.component";
 
 @Pipe({
-  name: 'menuIconKgSearchBtnClsPipe'
+  name: 'kgSearchBtnColorPipe'
 })
 
-export class MenuIconKgSearchBtnClsPipe implements PipeTransform{
-  public transform([minimisedWidgetUnit, themedBtnCls]: [Set<WidgetUnit>, string], wu: WidgetUnit, ){
+export class KgSearchBtnColorPipe implements PipeTransform{
+  public transform([minimisedWidgetUnit, themedBtnCls]: [Set<WidgetUnit>, string], wu: WidgetUnit ){
     return minimisedWidgetUnit.has(wu)
-      ? themedBtnCls + ' border-primary'
-      : 'btn-primary'
+      ? 'primary'
+      : 'accent'
   }
 }
\ No newline at end of file
diff --git a/src/util/pipes/menuIconPluginBtnCls.pipe.ts b/src/util/pipes/menuIconPluginBtnCls.pipe.ts
deleted file mode 100644
index 94df7495cd7c8350908243f1850a9fafdc8aeb23..0000000000000000000000000000000000000000
--- a/src/util/pipes/menuIconPluginBtnCls.pipe.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { PipeTransform, Pipe } from "@angular/core";
-
-@Pipe({
-  name: 'menuIconPluginBtnClsPipe'
-})
-
-export class MenuIconPluginBtnClsPipe implements PipeTransform{
-  public transform([launchedSet, minimisedSet, themedBtnCls], pluginName){
-    return `${launchedSet.has(pluginName) 
-      ? minimisedSet.has(pluginName)
-        ? themedBtnCls + ' border-primary'
-        : 'btn-primary' 
-      : themedBtnCls}`
-  }
-}
\ 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