diff --git a/Dockerfile b/Dockerfile
index a20f2d36324fee46ce3a90f029ba9361839ab550..67876d05bf57addb564b5e6c48622c12719ef1ab 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,4 +1,4 @@
-FROM node:8 as builder
+FROM node:10 as builder
 
 ARG BACKEND_URL
 ENV BACKEND_URL=$BACKEND_URL
@@ -8,12 +8,23 @@ WORKDIR /iv
 
 ENV VERSION=devNext
 
+RUN apt update && apt upgrade -y && apt install brotli
+
 RUN npm i
 RUN npm run build-aot
 
+# gzipping container
+FROM ubuntu:18.10 as compressor
+RUN apt upgrade -y && apt update && apt install brotli
+
+RUN mkdir /iv
+COPY --from=builder /iv/dist/aot /iv
+WORKDIR /iv
+
+RUN for f in $(find . -type f); do gzip < $f > $f.gz && brotli < $f > $f.br; done
 
 # prod container
-FROM node:8-alpine 
+FROM node:10-alpine 
 
 ARG PORT
 ENV PORT=$PORT
@@ -23,14 +34,15 @@ RUN apk --no-cache add ca-certificates
 RUN mkdir /iv-app
 WORKDIR /iv-app
 
-# Copy built interactive viewer
-COPY --from=builder /iv/dist/aot ./public
-
 # Copy the express server
 COPY --from=builder /iv/deploy .
 
+# Copy built interactive viewer
+COPY --from=compressor /iv ./public
+
 # Copy the resources files needed to respond to queries
-COPY --from=builder /iv/src/res/ext ./res
+# is this even necessary any more?
+COPY --from=compressor /iv/res/json ./res
 RUN npm i
 
 EXPOSE $PORT
diff --git a/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..210e2ac9a32f39229d1f377025c8a1373760603a 100644
--- a/deploy/app.js
+++ b/deploy/app.js
@@ -3,6 +3,7 @@ const express = require('express')
 const app = express()
 const session = require('express-session')
 const MemoryStore = require('memorystore')(session)
+const crypto = require('crypto')
 
 app.disable('x-powered-by')
 
@@ -10,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,12 +61,23 @@ const PUBLIC_PATH = process.env.NODE_ENV === 'production'
   ? path.join(__dirname, 'public')
   : path.join(__dirname, '..', 'dist', 'aot')
 
-app.use(express.static(PUBLIC_PATH))
+/**
+ * well known path
+ */
+app.use('/.well-known', express.static(path.join(__dirname, 'well-known')))
+
+/**
+ * only use compression for production
+ * this allows locally built aot to be served without errors
+ */
+
+const { compressionMiddleware } = require('./compression')
+app.use(compressionMiddleware, express.static(PUBLIC_PATH))
 
-app.use((req, res, next) => {
+const jsonMiddleware = (req, res, next) => {
   res.set('Content-Type', 'application/json')
   next()
-})
+}
 
 const templateRouter = require('./templates')
 const nehubaConfigRouter = require('./nehubaConfig')
@@ -57,11 +85,11 @@ const datasetRouter = require('./datasets')
 const pluginRouter = require('./plugins')
 const previewRouter = require('./preview')
 
-app.use('/templates', templateRouter)
-app.use('/nehubaConfig', nehubaConfigRouter)
-app.use('/datasets', datasetRouter)
-app.use('/plugins', pluginRouter)
-app.use('/preview', previewRouter)
+app.use('/templates', jsonMiddleware, templateRouter)
+app.use('/nehubaConfig', jsonMiddleware, nehubaConfigRouter)
+app.use('/datasets', jsonMiddleware, datasetRouter)
+app.use('/plugins', jsonMiddleware, pluginRouter)
+app.use('/preview', jsonMiddleware, previewRouter)
 
 const catchError = require('./catchError')
 app.use(catchError)
diff --git a/deploy/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/compression/index.js b/deploy/compression/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..bc5d2acb4b69d219d1a6c32bb0148296c4d020c4
--- /dev/null
+++ b/deploy/compression/index.js
@@ -0,0 +1,59 @@
+const BROTLI = `br`
+const GZIP = `gzip`
+
+const detEncoding = (acceptEncoding = '') => {
+  if (process.env.NODE_ENV !== 'production') return null
+
+  return /br/i.test(acceptEncoding)
+    ? BROTLI
+    : /gzip/i.test(acceptEncoding)
+      ? GZIP
+      : null
+}
+
+const mimeMap = new Map([
+  ['.png', 'image/png'],
+  ['.gif', 'image/gif'],
+  ['.jpg', 'image/jpeg'],
+  ['.jpeg', 'image/jpeg'],
+  ['.css', 'text/css'],
+  ['.html', 'text/html'],
+  ['.js', 'text/javascript']
+])
+
+exports.BROTLI = BROTLI
+
+exports.GZIP = GZIP
+
+exports.detEncoding = detEncoding
+
+exports.compressionMiddleware = (req, res, next) => {
+  const acceptEncoding = req.get('Accept-Encoding')
+  const encoding = detEncoding(acceptEncoding)
+
+  // if no encoding is accepted
+  // or in dev mode, do not use compression
+  if (!encoding) return next()
+  
+  const ext = /(\.\w*?)$/.exec(req.url)
+
+  // if cannot determine mime-type, do not use encoding
+  // as Content-Type header is required for browser to understand response
+  if (!ext || !mimeMap.get(ext[1])) return next()
+  
+  res.set('Content-Type', mimeMap.get(ext[1]))
+
+  if (encoding === BROTLI) {
+    req.url = req.url + '.br'
+    res.set('Content-Encoding', encoding)
+    return next()
+  }
+
+  if (encoding === GZIP) {
+    req.url = req.url + '.gz'
+    res.set('Content-Encoding', encoding)
+    return next()
+  }
+
+  next()
+}
\ No newline at end of file
diff --git a/deploy/compression/index.spec.js b/deploy/compression/index.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..3c1a3826fb3b8151a73c8d180b8d31b49f82c6c4
--- /dev/null
+++ b/deploy/compression/index.spec.js
@@ -0,0 +1,39 @@
+const mocha = require('mocha')
+const chai = require('chai')
+const expect = chai.expect
+
+const { detEncoding, GZIP, BROTLI } = require('./index')
+
+const gzip = 'gzip'
+const gzipDeflate = 'gzip, deflate'
+const gzipDeflateBr = 'gzip, deflate, br'
+
+describe('compression/index.js', () => {
+  let nodeEnv
+  
+  before(() => {
+    nodeEnv = process.env.NODE_ENV
+  })
+
+  after(() => {
+    process.env.NODE_ENV = nodeEnv
+  })
+  
+  describe('#detEncoding', () => {
+    it('When NODE_ENV is set to production, returns appropriate encoding', () => {
+      process.env.NODE_ENV = 'production'
+      expect(detEncoding(null)).to.equal(null)
+      expect(detEncoding(gzip)).to.equal(GZIP)
+      expect(detEncoding(gzipDeflate)).to.equal(GZIP)
+      expect(detEncoding(gzipDeflateBr)).to.equal(BROTLI)
+    })
+
+    it('When NODE_ENV is set to non production, returns null always', () => {
+      process.env.NODE_ENV = 'development'
+      expect(detEncoding(null)).to.equal(null)
+      expect(detEncoding(gzip)).to.equal(null)
+      expect(detEncoding(gzipDeflate)).to.equal(null)
+      expect(detEncoding(gzipDeflateBr)).to.equal(null)
+    })
+  })
+})
\ No newline at end of file
diff --git a/deploy/datasets/index.js b/deploy/datasets/index.js
index 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/nehubaConfig/index.js b/deploy/nehubaConfig/index.js
index a5072c82865d230eb8d5320c028d5f9505ec1e38..145441ee8d730b5a0df2cabccf6e2612d86f2cb4 100644
--- a/deploy/nehubaConfig/index.js
+++ b/deploy/nehubaConfig/index.js
@@ -1,19 +1,18 @@
 const express = require('express')
-const path = require('path')
-const fs = require('fs')
 const { getTemplateNehubaConfig } = require('./query')
+const { detEncoding } = require('../compression')
 
 const nehubaConfigRouter = express.Router()
 
 nehubaConfigRouter.get('/:configId', (req, res, next) => {
+
+  const header = req.get('Accept-Encoding')
+  const acceptedEncoding = detEncoding(header)
+
   const { configId } = req.params
-  getTemplateNehubaConfig(configId)
-    .then(data => res.status(200).send(data))
-    .catch(error => next({
-      code: 500,
-      error,
-      trace: 'nehubaConfigRouter#getTemplateNehubaConfig'
-    }))
+  if (acceptedEncoding) res.set('Content-Encoding', acceptedEncoding)
+
+  getTemplateNehubaConfig({ configId, acceptedEncoding, returnAsStream:true}).pipe(res)
 })
 
 module.exports = nehubaConfigRouter
\ No newline at end of file
diff --git a/deploy/nehubaConfig/query.js b/deploy/nehubaConfig/query.js
index 972c3d59633cd3748d3678a636f8bcecdf7ed0d9..23a6132ba39c72a83f47159e3ed106a296ac7250 100644
--- a/deploy/nehubaConfig/query.js
+++ b/deploy/nehubaConfig/query.js
@@ -1,15 +1,31 @@
 const fs = require('fs')
 const path = require('path')
+const { BROTLI, GZIP } = require('../compression')
 
-exports.getTemplateNehubaConfig = (configId) => new Promise((resolve, reject) => {
-  let filepath
+const getFileAsPromise = filepath => new Promise((resolve, reject) => {
+  fs.readFile(filepath, 'utf-8', (err, data) => {
+    if (err) return reject(err)
+    resolve(data)
+  })
+})
+
+exports.getTemplateNehubaConfig = ({configId, acceptedEncoding, returnAsStream}) => {
   if (process.env.NODE_ENV === 'production') {
     filepath = path.join(__dirname, '..', 'res', `${configId}.json`)
   } else {
     filepath = path.join(__dirname, '..', '..', 'src', 'res', 'ext', `${configId}.json`)
   }
-  fs.readFile(filepath, 'utf-8', (err, data) => {
-    if (err) return reject(err)
-    resolve(data)
-  })
-})
\ No newline at end of file
+
+  if (acceptedEncoding === BROTLI) {
+    if (returnAsStream) return fs.createReadStream(`${filepath}.br`)
+    else return getFileAsPromise(`${filepath}.br`)
+  }
+
+  if (acceptedEncoding === GZIP) {
+    if (returnAsStream) return fs.createReadStream(`${filepath}.gz`)
+    else return getFileAsPromise(`${filepath}.gz`)
+  }
+
+  if (returnAsStream) return fs.createReadStream(filepath)
+  else return getFileAsPromise(filepath)
+} 
\ No newline at end of file
diff --git a/deploy/plugins/index.js b/deploy/plugins/index.js
index 9f15b4687a85be1f07b76284694132fd0fb799eb..f0216dea9b1aa14f7a85dfc955a11b530e9f740e 100644
--- a/deploy/plugins/index.js
+++ b/deploy/plugins/index.js
@@ -5,15 +5,11 @@
 
 const express = require('express')
 const router = express.Router()
-const PLUGIN_URLS = process.env.PLUGIN_URLS && JSON.stringify(process.env.PLUGIN_URLS.split(';'))
+const PLUGIN_URLS = (process.env.PLUGIN_URLS && process.env.PLUGIN_URLS.split(';'))
+  || []
 
 router.get('', (_req, res) => {
-
-  if (PLUGIN_URLS) {
-    return res.status(200).send(PLUGIN_URLS)
-  } else {
-    return res.status(200).send('[]')
-  }
+  return res.status(200).json(PLUGIN_URLS)
 })
 
 module.exports = router
\ No newline at end of file
diff --git a/deploy/server.js b/deploy/server.js
index 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/templates/index.js b/deploy/templates/index.js
index 1ed84e544f0754e84ee8f48855d7b30975361de4..d9fbeb8313a14464177a2c00f47fbda8392d707b 100644
--- a/deploy/templates/index.js
+++ b/deploy/templates/index.js
@@ -1,6 +1,8 @@
 const router = require('express').Router()
 const query = require('./query')
 const path = require('path')
+const { detEncoding } = require('../compression')
+
 /**
  * root path fetches all templates
  */
@@ -20,6 +22,10 @@ router.get('/', (req, res, next) => {
 
 router.get('/:template', (req, res, next) => {
   const { template } = req.params
+
+  const header = req.get('Accept-Encoding')
+  const acceptedEncoding = detEncoding(header)
+
   query.getAllTemplates()
     .then(templates => {
       if (templates.indexOf(template) < 0) 
@@ -27,11 +33,9 @@ router.get('/:template', (req, res, next) => {
           code : 404,
           error: 'template not in the list supported'
         })
-      return query.getTemplate(template)
-    })
-    .then(data => {
-      if (data)
-        res.status(200).send(data)
+
+      if (acceptedEncoding) res.set('Content-Encoding', acceptedEncoding)
+      query.getTemplate({ template, acceptedEncoding, returnAsStream:true }).pipe(res)
     })
     .catch(error => next({
       code: 500,
diff --git a/deploy/templates/query.js b/deploy/templates/query.js
index 1ecebe476dce91c21819342ad6031f46643b1db8..c1886db02efd03033514ec5a33c113b60b5b8842 100644
--- a/deploy/templates/query.js
+++ b/deploy/templates/query.js
@@ -1,5 +1,6 @@
 const fs = require('fs')
 const path = require('path')
+const { BROTLI, GZIP } = require('../compression')
 
 exports.getAllTemplates = () => new Promise((resolve, reject) => {
   
@@ -17,15 +18,32 @@ exports.getAllTemplates = () => new Promise((resolve, reject) => {
   resolve(templates)
 })
 
-exports.getTemplate = (template) => new Promise((resolve, reject) => {
+const getFileAsPromise = filepath => new Promise((resolve, reject) => {
+  fs.readFile(filepath, 'utf-8', (err, data) => {
+    if (err) return reject(err)
+    resolve(data)
+  })
+})
+
+exports.getTemplate = ({ template, acceptedEncoding, returnAsStream }) => {
+
   let filepath
   if (process.env.NODE_ENV === 'production') {
     filepath = path.join(__dirname, '..', 'res', `${template}.json`)
   } else {
     filepath = path.join(__dirname, '..', '..', 'src', 'res', 'ext', `${template}.json`)
   }
-  fs.readFile(filepath, 'utf-8', (err, data) => {
-    if (err) reject(err)
-    resolve(data)
-  })
-})
\ No newline at end of file
+
+  if (acceptedEncoding === BROTLI) {
+    if (returnAsStream) return fs.createReadStream(`${filepath}.br`)
+    else return getFileAsPromise(`${filepath}.br`)
+  }
+
+  if (acceptedEncoding === GZIP) {
+    if (returnAsStream) return fs.createReadStream(`${filepath}.gz`)
+    else return getFileAsPromise(`${filepath}.gz`)
+  }
+
+  if (returnAsStream) return fs.createReadStream(filepath)
+  else return getFileAsPromise(filepath)
+} 
\ No newline at end of file
diff --git a/deploy/test/mocha.test.js b/deploy/test/mocha.test.js
index 8f55b54de1f2566b8d2348adef651303b0ef1fca..366f5af4a874c10e907272ba0ece2b686248078a 100644
--- a/deploy/test/mocha.test.js
+++ b/deploy/test/mocha.test.js
@@ -1 +1,2 @@
-require('../auth/util.spec')
\ No newline at end of file
+require('../auth/util.spec')
+require('../compression/index.spec')
\ No newline at end of file
diff --git a/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..a2ed139c5d747a35ef3c5ed5d8db25138d6831cb 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"
   },
@@ -24,6 +23,7 @@
   "license": "ISC",
   "devDependencies": {
     "@angular/animations": "^7.2.15",
+    "@angular/cdk": "^7.3.7",
     "@angular/common": "^7.2.15",
     "@angular/compiler": "^7.2.15",
     "@angular/compiler-cli": "^7.2.15",
@@ -32,8 +32,10 @@
     "@angular/forms": "^7.2.15",
     "@angular/http": "^7.2.15",
     "@angular/language-service": "^7.2.15",
+    "@angular/material": "^7.3.7",
     "@angular/platform-browser": "^7.2.15",
     "@angular/platform-browser-dynamic": "^7.2.15",
+    "@angular/router": "^7.2.15",
     "@ngrx/effects": "^7.4.0",
     "@ngrx/store": "^6.0.1",
     "@ngtools/webpack": "^6.0.5",
@@ -46,7 +48,9 @@
     "chart.js": "^2.7.2",
     "codelyzer": "^5.0.1",
     "core-js": "^3.0.1",
+    "css-loader": "^3.2.0",
     "file-loader": "^1.1.11",
+    "hammerjs": "^2.0.8",
     "html-webpack-plugin": "^3.2.0",
     "jasmine": "^3.1.0",
     "jasmine-core": "^3.4.0",
@@ -58,12 +62,15 @@
     "karma-typescript": "^3.0.13",
     "karma-webpack": "^3.0.0",
     "lodash.merge": "^4.6.1",
+    "mini-css-extract-plugin": "^0.8.0",
     "ng2-charts": "^1.6.0",
     "ngx-bootstrap": "3.0.1",
+    "node-sass": "^4.12.0",
     "protractor": "^5.4.2",
     "raw-loader": "^0.5.1",
     "reflect-metadata": "^0.1.12",
     "rxjs": "6.5.1",
+    "sass-loader": "^7.2.0",
     "showdown": "^1.8.6",
     "ts-loader": "^4.3.0",
     "ts-node": "^8.1.0",
@@ -74,12 +81,8 @@
     "webpack-cli": "^3.3.2",
     "webpack-closure-compiler": "^2.1.6",
     "webpack-dev-server": "^3.1.4",
-    "webpack-merge": "^4.1.2"
-  },
-  "dependencies": {
-    "@angular/cdk": "^7.3.7",
-    "@angular/material": "^7.3.7",
-    "@angular/router": "^7.2.15",
+    "webpack-merge": "^4.1.2",
     "zone.js": "^0.9.1"
-  }
+  },
+  "dependencies": {}
 }
diff --git a/src/atlasViewer/atlasViewer.apiService.service.ts b/src/atlasViewer/atlasViewer.apiService.service.ts
index 5173ec7a7825790bb058a09b56aa5cf0cb8c6c11..475cb5c09879a2d1fc134e753752428d2884b706 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,13 @@ 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 dialogService: DialogService,
   ){
 
     this.loadedTemplates$ = this.store.pipe(
@@ -36,13 +33,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 +73,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 +109,10 @@ export class AtlasViewerAPIServices{
          */
         launchNewWidget: (manifest) => {
           return Promise.reject('Needs to be overwritted')
-        }
+        },
+
+        getUserInput: config => this.dialogService.getUserInput(config),
+        getUserConfirmation: config => this.dialogService.getUserConfirm(config)
       },
       pluginControl : {
         loadExternalLibraries : ()=>Promise.reject('load External Library method not over written')
@@ -137,7 +134,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 +180,8 @@ export interface InteractiveViewerInterface{
     getModalHandler: () => ModalHandler
     getToastHandler: () => ToastHandler
     launchNewWidget: (manifest:PluginManifest) => Promise<any>
+    getUserInput: (config:GetUserInputConfig) => Promise<string>
+    getUserConfirmation: (config: GetUserConfirmation) => Promise<any>
   }
 
   pluginControl : {
@@ -193,6 +191,16 @@ export interface InteractiveViewerInterface{
   }
 }
 
+interface GetUserConfirmation{
+  title?: string
+  message?: string
+}
+
+interface GetUserInputConfig extends GetUserConfirmation{
+  placeholder?: string
+  defaultValue?: string
+}
+
 export interface UserLandmark{
   name : string
   position : [number, number, number]
diff --git a/src/atlasViewer/atlasViewer.component.ts b/src/atlasViewer/atlasViewer.component.ts
index 9abbd42776480899103ff4b228616cccd1fd7f80..f58775aa9923e70c4e83ee5496e3cb2c21705335 100644
--- a/src/atlasViewer/atlasViewer.component.ts
+++ b/src/atlasViewer/atlasViewer.component.ts
@@ -7,13 +7,9 @@ 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";
@@ -21,6 +17,7 @@ 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 { LocalFileService } from "src/services/localFile.service";
+import { MatDialog, MatDialogRef } from "@angular/material";
 
 /**
  * TODO
@@ -107,7 +104,7 @@ 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 rd: Renderer2,
@@ -245,6 +242,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()
 
@@ -266,25 +268,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
         })
       })
     )
@@ -329,6 +325,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() {
@@ -358,12 +360,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(
@@ -376,12 +373,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(
@@ -427,12 +419,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
   }
@@ -452,14 +448,14 @@ 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
     })
@@ -521,27 +517,16 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit {
     })
   }
 
-  /**
-   * TODO deprecated
-   */
-
-  toggleSidePanel(panelName:string){
-    this.store.dispatch({
-      type : TOGGLE_SIDE_PANEL,
-      focusedSidePanel :panelName
-    })
-  }
+  closeModal(mode){
+    if (mode === 'help') {
+      this.helpDialogRef && this.helpDialogRef.close()
+    }
 
-  /**
-   * TODO deprecated
-   */
-  panelAnimationEnd(){
-    if( this.nehubaContainer && this.nehubaContainer.nehubaViewer && this.nehubaContainer.nehubaViewer.nehubaViewer ) {
-      this.nehubaContainer.nehubaViewer.nehubaViewer.redraw()
+    if (mode === 'login') {
+      this.loginDialogRef && this.loginDialogRef.close()
     }
   }
 
-
   closeMenuWithSwipe(documentToSwipe: ElementRef) {
     if (documentToSwipe && documentToSwipe.nativeElement) {
       const swipeDistance = 150; // swipe distance
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 a452c83b0d81b948ff923994f2fa5952ab9e9146..bb1d4183e203ac97f3c9d54c52a49d18e5e660e1 100644
--- a/src/atlasViewer/atlasViewer.constantService.service.ts
+++ b/src/atlasViewer/atlasViewer.constantService.service.ts
@@ -1,8 +1,9 @@
 import { Injectable } from "@angular/core";
-import { Store } from "@ngrx/store";
-import { ViewerStateInterface, Property } from "../services/stateStore.service";
-import { Subject } from "rxjs";
+import { Store, select } from "@ngrx/store";
+import { ViewerStateInterface } from "../services/stateStore.service";
+import { Subject, Observable } from "rxjs";
 import { ACTION_TYPES, ViewerConfiguration } from 'src/services/state/viewerConfig.store'
+import { map, shareReplay, filter } from "rxjs/operators";
 
 export const CM_THRESHOLD = `0.05`
 export const CM_MATLAB_JET = `float r;if( x < 0.7 ){r = 4.0 * x - 1.5;} else {r = -4.0 * x + 4.5;}float g;if (x < 0.5) {g = 4.0 * x - 0.5;} else {g = -4.0 * x + 3.5;}float b;if (x < 0.3) {b = 4.0 * x + 0.5;} else {b = -4.0 * x + 2.5;}float a = 1.0;`
@@ -14,6 +15,7 @@ export const CM_MATLAB_JET = `float r;if( x < 0.7 ){r = 4.0 * x - 1.5;} else {r
 export class AtlasViewerConstantsServices{
 
   public darktheme: boolean = false
+  public darktheme$: Observable<boolean>
   public mobile: boolean
   public loadExportNehubaPromise : Promise<boolean>
 
@@ -177,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'],
@@ -247,6 +248,14 @@ Interactive atlas viewer requires **webgl2.0**, and the \`EXT_color_buffer_float
     /* https://stackoverflow.com/a/25394023/6059235 */
     this.mobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini|Mobile|mobile|CriOS/i.test(ua)
 
+    this.darktheme$ = this.store.pipe(
+      select('viewerState'),
+      select('templateSelected'),
+      filter(v => !!v),
+      map(({useTheme}) => useTheme === 'dark'),
+      shareReplay(1)
+    )
+
     /**
      * set gpu limit if user is on mobile
      */
@@ -259,6 +268,14 @@ Interactive atlas viewer requires **webgl2.0**, and the \`EXT_color_buffer_float
       })  
     }
   }
+
+  catchError(e: Error | string){
+    /**
+     * DO NOT REMOVE
+     * general catch all & reflect in UI
+     */
+    console.warn(e)
+  }
 }
 
 const parseURLToElement = (url:string):HTMLElement=>{
@@ -313,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
@@ -371,7 +387,9 @@ const decodetoInt = (encodedString: string) => {
     _encodedString = encodedString
   }
   return (negFlag ? -1 : 1) * [..._encodedString].reduce((acc,curr) => {
-    return acc * 64 + cipher.indexOf(curr)
+    const index = cipher.indexOf(curr)
+    if (index < 0) throw new Error(`Poisoned b64 encoding ${encodedString}`)
+    return acc * 64 + index
   }, 0)
 }
 
diff --git a/src/atlasViewer/atlasViewer.pluginService.service.spec.ts b/src/atlasViewer/atlasViewer.pluginService.service.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a040e3f2360184ecb8df8e08eef7111667012a3c
--- /dev/null
+++ b/src/atlasViewer/atlasViewer.pluginService.service.spec.ts
@@ -0,0 +1,108 @@
+import { PluginServices } from "./atlasViewer.pluginService.service";
+import { TestBed, inject } from "@angular/core/testing";
+import { MainModule } from "src/main.module";
+import { HttpTestingController, HttpClientTestingModule } from '@angular/common/http/testing'
+
+const MOCK_PLUGIN_MANIFEST = {
+  name: 'fzj.xg.MOCK_PLUGIN_MANIFEST',
+  templateURL: 'http://localhost:10001/template.html',
+  scriptURL: 'http://localhost:10001/script.js'
+}
+
+describe('PluginServices', () => {
+  let pluginService: PluginServices
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [
+        HttpClientTestingModule,
+        MainModule
+      ]
+    }).compileComponents()
+
+    pluginService = TestBed.get(PluginServices)
+  })
+
+  it(
+    'is instantiated in test suite OK',
+    () => expect(TestBed.get(PluginServices)).toBeTruthy()
+  )
+
+  it(
+    'expectOne is working as expected',
+    inject([HttpTestingController], (httpMock: HttpTestingController) => {
+      expect(httpMock.match('test').length).toBe(0)
+      pluginService.fetch('test')
+      expect(httpMock.match('test').length).toBe(1)
+      pluginService.fetch('test')
+      pluginService.fetch('test')
+      expect(httpMock.match('test').length).toBe(2)
+    })
+  )
+
+  describe('#launchPlugin', () => {
+
+    describe('basic fetching functionality', () => {
+      it(
+        'fetches templateURL and scriptURL properly',
+        inject([HttpTestingController], (httpMock: HttpTestingController) => {
+  
+          pluginService.launchPlugin(MOCK_PLUGIN_MANIFEST)
+    
+          const mockTemplate = httpMock.expectOne(MOCK_PLUGIN_MANIFEST.templateURL)
+          const mockScript = httpMock.expectOne(MOCK_PLUGIN_MANIFEST.scriptURL)
+    
+          expect(mockTemplate).toBeTruthy()
+          expect(mockScript).toBeTruthy()
+        })
+      )
+  
+      it(
+        'template overrides templateURL',
+        inject([HttpTestingController], (httpMock: HttpTestingController) => {
+          pluginService.launchPlugin({
+            ...MOCK_PLUGIN_MANIFEST,
+            template: ''
+          })
+          
+          httpMock.expectNone(MOCK_PLUGIN_MANIFEST.templateURL)
+          const mockScript = httpMock.expectOne(MOCK_PLUGIN_MANIFEST.scriptURL)
+    
+          expect(mockScript).toBeTruthy()
+        })
+      )
+  
+      it(
+        'script overrides scriptURL',
+  
+        inject([HttpTestingController], (httpMock: HttpTestingController) => {
+          pluginService.launchPlugin({
+            ...MOCK_PLUGIN_MANIFEST,
+            script: ''
+          })
+          
+          const mockTemplate = httpMock.expectOne(MOCK_PLUGIN_MANIFEST.templateURL)
+          httpMock.expectNone(MOCK_PLUGIN_MANIFEST.scriptURL)
+    
+          expect(mockTemplate).toBeTruthy()
+        })
+      )
+    })
+
+    describe('racing slow cconnection when launching plugin', () => {
+      it(
+        'when template/script has yet been fetched, repeated launchPlugin should not result in repeated fetching',
+        inject([HttpTestingController], (httpMock:HttpTestingController) => {
+
+          expect(pluginService.pluginIsLaunching(MOCK_PLUGIN_MANIFEST.name)).toBeFalsy()
+          pluginService.launchPlugin(MOCK_PLUGIN_MANIFEST)
+          pluginService.launchPlugin(MOCK_PLUGIN_MANIFEST)
+          expect(httpMock.match(MOCK_PLUGIN_MANIFEST.scriptURL).length).toBe(1)
+          expect(httpMock.match(MOCK_PLUGIN_MANIFEST.templateURL).length).toBe(1)
+
+          expect(pluginService.pluginIsLaunching(MOCK_PLUGIN_MANIFEST.name)).toBeTruthy()
+        })
+      )
+    })
+  })
+})
\ No newline at end of file
diff --git a/src/atlasViewer/atlasViewer.pluginService.service.ts b/src/atlasViewer/atlasViewer.pluginService.service.ts
index 7adc864521c39d581871bd6be3b1a8172e065ed4..84f5e30d687124fd8ebc5b457c70929385de1a62 100644
--- a/src/atlasViewer/atlasViewer.pluginService.service.ts
+++ b/src/atlasViewer/atlasViewer.pluginService.service.ts
@@ -1,13 +1,14 @@
 import { Injectable, ViewContainerRef, ComponentFactoryResolver, ComponentFactory } from "@angular/core";
-import { PluginInitManifestInterface, ACTION_TYPES } from "src/services/state/pluginState.store";
+import { PluginInitManifestInterface, PLUGIN_STATE_ACTION_TYPES } from "src/services/state/pluginState.store";
+import { HttpClient } from '@angular/common/http'
 import { isDefined } from 'src/services/stateStore.service'
 import { AtlasViewerAPIServices } from "./atlasViewer.apiService.service";
 import { PluginUnit } from "./pluginUnit/pluginUnit.component";
 import { WidgetServices } from "./widgetUnit/widgetService.service";
 
 import '../res/css/plugin_styles.css'
-import { interval } from "rxjs";
-import { take, takeUntil } from "rxjs/operators";
+import { BehaviorSubject, Observable, merge, of } from "rxjs";
+import { map, shareReplay } from "rxjs/operators";
 import { Store } from "@ngrx/store";
 import { WidgetUnit } from "./widgetUnit/widgetUnit.component";
 import { AtlasViewerConstantsServices } from "./atlasViewer.constantService.service";
@@ -23,13 +24,20 @@ export class PluginServices{
   public appendSrc : (script:HTMLElement)=>void
   public removeSrc: (script:HTMLElement) => void
   private pluginUnitFactory : ComponentFactory<PluginUnit>
+  public minimisedPlugins$ : Observable<Set<string>>
+
+  /**
+   * TODO remove polyfil and convert all calls to this.fetch to http client
+   */
+  public fetch: (url:string, httpOption?: any) => Promise<any> = (url, httpOption = {}) => this.http.get(url, httpOption).toPromise()
 
   constructor(
     private apiService : AtlasViewerAPIServices,
     private constantService : AtlasViewerConstantsServices,
     private widgetService : WidgetServices,
     private cfr : ComponentFactoryResolver,
-    private store : Store<PluginInitManifestInterface>
+    private store : Store<PluginInitManifestInterface>,
+    private http: HttpClient
   ){
 
     this.pluginUnitFactory = this.cfr.resolveComponentFactory( PluginUnit )
@@ -44,30 +52,30 @@ export class PluginServices{
          * PLUGINDEV should return an array of 
          */
         PLUGINDEV
-          ? fetch(PLUGINDEV).then(res => res.json())
+          ? this.fetch(PLUGINDEV).then(res => res.json())
           : Promise.resolve([]),
         new Promise(resolve => {
-          fetch(`${this.constantService.backendUrl}plugins`)
-            .then(res => res.json())
+          this.fetch(`${this.constantService.backendUrl}plugins`)
             .then(arr => Promise.all(
               arr.map(url => new Promise(rs => 
                 /**
                  * instead of failing all promises when fetching manifests, only fail those that fails to fetch
                  */
-                fetch(url).then(res => res.json()).then(rs).catch(e => (console.log('fetching manifest error', e), rs(null))))
+                this.fetch(url).then(rs).catch(e => (this.constantService.catchError(`fetching manifest error: ${e.toString()}`), rs(null))))
               )
             ))
             .then(manifests => resolve(
               manifests.filter(m => !!m)
             ))
             .catch(e => {
+              this.constantService.catchError(e)
               resolve([])
             })
         }),
         Promise.all(
           BUNDLEDPLUGINS
             .filter(v => typeof v === 'string')
-            .map(v => fetch(`res/plugin_examples/${v}/manifest.json`).then(res => res.json()))
+            .map(v => this.fetch(`res/plugin_examples/${v}/manifest.json`).then(res => res.json()))
         )
           .then(arr => arr.reduce((acc,curr) => acc.concat(curr) ,[]))
       ])
@@ -79,6 +87,24 @@ export class PluginServices{
       .then(arr=>
         this.fetchedPluginManifests = arr)
       .catch(console.error)
+
+    this.minimisedPlugins$ = merge(
+      of(new Set()),
+      this.widgetService.minimisedWindow$
+    ).pipe(
+      map(set => {
+        const returnSet = new Set<string>()
+        for (let [pluginName, wu] of this.mapPluginNameToWidgetUnit) {
+          if (set.has(wu)) {
+            returnSet.add(pluginName)
+          }
+        }
+        return returnSet
+      }),
+      shareReplay(1)
+    )
+
+    this.launchedPlugins$ = new BehaviorSubject(new Set())
   }
 
   launchNewWidget = (manifest) => this.launchPlugin(manifest)
@@ -94,37 +120,74 @@ export class PluginServices{
         isDefined(plugin.template) ?
           Promise.resolve('template already provided') :
           isDefined(plugin.templateURL) ?
-            fetch(plugin.templateURL)
-              .then(res=>res.text())
+            this.fetch(plugin.templateURL, {responseType: 'text'})
               .then(template=>plugin.template = template) :
             Promise.reject('both template and templateURL are not defined') ,
         isDefined(plugin.script) ?
           Promise.resolve('script already provided') :
           isDefined(plugin.scriptURL) ?
-            fetch(plugin.scriptURL)
-              .then(res=>res.text())
+            this.fetch(plugin.scriptURL, {responseType: 'text'})
               .then(script=>plugin.script = script) :
             Promise.reject('both script and scriptURL are not defined') 
       ])
   }
 
-  public launchedPlugins: Set<string> = new Set()
+  private launchedPlugins: Set<string> = new Set()
+  public launchedPlugins$: BehaviorSubject<Set<string>>
+  pluginHasLaunched(pluginName:string) {
+    return this.launchedPlugins.has(pluginName)
+  }
+  addPluginToLaunchedSet(pluginName:string){
+    this.launchedPlugins.add(pluginName)
+    this.launchedPlugins$.next(this.launchedPlugins)
+  }
+  removePluginFromLaunchedSet(pluginName:string){
+    this.launchedPlugins.delete(pluginName)
+    this.launchedPlugins$.next(this.launchedPlugins)
+  }
+
+  
+  pluginIsLaunching(pluginName:string){
+    return this.launchingPlugins.has(pluginName)
+  }
+  addPluginToIsLaunchingSet(pluginName:string) {
+    this.launchingPlugins.add(pluginName)
+  }
+  removePluginFromIsLaunchingSet(pluginName:string){
+    this.launchedPlugins.delete(pluginName)
+  }
+
   private mapPluginNameToWidgetUnit: Map<string, WidgetUnit> = new Map()
 
-  pluginMinimised(pluginManifest:PluginManifest){
-    return this.widgetService.minimisedWindow.has( this.mapPluginNameToWidgetUnit.get(pluginManifest.name) )
+  pluginIsMinimised(pluginName:string) {
+    return this.widgetService.isMinimised( this.mapPluginNameToWidgetUnit.get(pluginName) )
   }
 
+  private launchingPlugins: Set<string> = new Set()
   public orphanPlugins: Set<PluginManifest> = new Set()
   launchPlugin(plugin:PluginManifest){
-    if(this.apiService.interactiveViewer.pluginControl[plugin.name])
-    {
-      console.warn('plugin already launched. blinking for 10s.')
-      this.apiService.interactiveViewer.pluginControl[plugin.name].blink(10)
+    if (this.pluginIsLaunching(plugin.name)) {
+      // plugin launching please be patient
+      // TODO add visual feedback
+      return
+    }
+    if ( this.pluginHasLaunched(plugin.name)) {
+      // plugin launched
+      // TODO add visual feedback
+
+      // if widget window is minimized, maximize it
+      
       const wu = this.mapPluginNameToWidgetUnit.get(plugin.name)
-      this.widgetService.minimisedWindow.delete(wu)
-      return Promise.reject('plugin already launched')
+      if (this.widgetService.isMinimised(wu)) {
+        this.widgetService.unminimise(wu)
+      } else {
+        this.widgetService.minimise(wu)
+      }
+      return
     }
+
+    this.addPluginToIsLaunchingSet(plugin.name)
+    
     return this.readyPlugin(plugin)
       .then(()=>{
         const pluginUnit = this.pluginViewContainerRef.createComponent( this.pluginUnitFactory )
@@ -154,7 +217,7 @@ export class PluginServices{
           : null
 
         handler.setInitManifestUrl = (url) => this.store.dispatch({
-          type : ACTION_TYPES.SET_INIT_PLUGIN,
+          type : PLUGIN_STATE_ACTION_TYPES.SET_INIT_PLUGIN,
           manifest : {
             name : plugin.name,
             initManifestUrl : url
@@ -163,7 +226,7 @@ export class PluginServices{
 
         const shutdownCB = [
           () => {
-            this.launchedPlugins.delete(plugin.name)
+            this.removePluginFromLaunchedSet(plugin.name)
           }
         ]
 
@@ -191,28 +254,18 @@ export class PluginServices{
           title : plugin.displayName || plugin.name
         })
 
-        this.launchedPlugins.add(plugin.name)
+        this.addPluginToLaunchedSet(plugin.name)
+        this.removePluginFromIsLaunchingSet(plugin.name)
+
         this.mapPluginNameToWidgetUnit.set(plugin.name, widgetCompRef.instance)
 
         const unsubscribeOnPluginDestroy = []
 
         handler.blink = (sec?:number)=>{
-          if(typeof sec !== 'number')
-            console.warn(`sec is not a number, default blink interval used`)
-          widgetCompRef.instance.containerClass = ''
-          interval(typeof sec === 'number' ? sec * 1000 : 500).pipe(
-            take(11),
-            takeUntil(widgetCompRef.instance.clickedEmitter)
-          ).subscribe(()=>
-            widgetCompRef.instance.containerClass = widgetCompRef.instance.containerClass === 'panel-success' ? 
-              '' : 
-              'panel-success')
+          widgetCompRef.instance.blinkOn = true
         }
 
-        unsubscribeOnPluginDestroy.push(
-          widgetCompRef.instance.clickedEmitter.subscribe(()=>
-            widgetCompRef.instance.containerClass = '')
-          )
+        handler.setProgressIndicator = (val) => widgetCompRef.instance.progressIndicator = val
 
         handler.shutdown = ()=>{
           widgetCompRef.instance.exit()
@@ -244,6 +297,8 @@ export class PluginHandler{
   initStateUrl? : string
 
   setInitManifestUrl : (url:string|null)=>void
+
+  setProgressIndicator: (progress:number) => void
 }
 
 export interface PluginManifest{
diff --git a/src/atlasViewer/atlasViewer.template.html b/src/atlasViewer/atlasViewer.template.html
index d77828f903cc1b0856653f37d146e698c21426e5..d22eae5adfd3f8b045326761fc9d7bc2033e233b 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,81 @@
   </ng-template>
 </ng-container>
 
-
 <ng-template #helpComponent>
-  <tabset>
-    <tab heading="Help">
-      <help-component>
-      </help-component>
-    </tab>
-    <tab heading="Settings">
-      <div class="mt-2">
-        <config-component>
-        </config-component>
-      </div>
-    </tab>
-    <tab heading="Privacy Policy">
-      <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="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 118d5123f19854425acaa5a8f65eec612c08a584..72a5ed8886d1d34c518ec48a674d32b9c9c9d388 100644
--- a/src/atlasViewer/atlasViewer.urlService.service.ts
+++ b/src/atlasViewer/atlasViewer.urlService.service.ts
@@ -57,8 +57,12 @@ export class AtlasViewerURLService{
      */
     this.additionalNgLayers$ = combineLatest(
       this.changeQueryObservable$.pipe(
-        map(state => state.templateSelected)
+        select('templateSelected'),
+        filter(v => !!v)
       ),
+      /**
+       * TODO duplicated with viewerState.loadedNgLayers ?
+       */
       this.store.pipe(
         select('ngViewerState'),
         select('layers')
@@ -170,7 +174,16 @@ export class AtlasViewerURLService{
   
             for (let ngId in json) {
               const val = json[ngId]
-              const labelIndicies = val.split(separator).map(n =>decodeToNumber(n))
+              const labelIndicies = val.split(separator).map(n =>{
+                try{
+                  return decodeToNumber(n)
+                } catch (e) {
+                  /**
+                   * TODO poisonsed encoded char, send error message
+                   */
+                  return null
+                }
+              }).filter(v => !!v)
               for (let labelIndex of labelIndicies) {
                 selectRegionIds.push(`${ngId}#${labelIndex}`)
               }
@@ -208,22 +221,29 @@ export class AtlasViewerURLService{
 
       const cViewerState = searchparams.get('cNavigation')
       if (cViewerState) {
-        const [ cO, cPO, cPZ, cP, cZ ] = cViewerState.split(`${separator}${separator}`)
-        const o = cO.split(separator).map(s => decodeToNumber(s, {float: true}))
-        const po = cPO.split(separator).map(s => decodeToNumber(s, {float: true}))
-        const pz = decodeToNumber(cPZ)
-        const p = cP.split(separator).map(s => decodeToNumber(s))
-        const z = decodeToNumber(cZ)
-        this.store.dispatch({
-          type : CHANGE_NAVIGATION,
-          navigation : {
-            orientation: o,
-            perspectiveOrientation: po,
-            perspectiveZoom: pz,
-            position: p,
-            zoom: z
-          }
-        })
+        try {
+          const [ cO, cPO, cPZ, cP, cZ ] = cViewerState.split(`${separator}${separator}`)
+          const o = cO.split(separator).map(s => decodeToNumber(s, {float: true}))
+          const po = cPO.split(separator).map(s => decodeToNumber(s, {float: true}))
+          const pz = decodeToNumber(cPZ)
+          const p = cP.split(separator).map(s => decodeToNumber(s))
+          const z = decodeToNumber(cZ)
+          this.store.dispatch({
+            type : CHANGE_NAVIGATION,
+            navigation : {
+              orientation: o,
+              perspectiveOrientation: po,
+              perspectiveZoom: pz,
+              position: p,
+              zoom: z
+            }
+          })
+        } catch (e) {
+          /**
+           * TODO Poisoned encoded char
+           * send error message
+           */
+        }
       }
 
       const niftiLayers = searchparams.get('niftiLayers')
diff --git a/src/atlasViewer/widgetUnit/widgetService.service.ts b/src/atlasViewer/widgetUnit/widgetService.service.ts
index 521d69ac8356087702807ec34ebeec44204f7888..7f1cc48355967bb9bfec97c60f3ee3713cb62ee0 100644
--- a/src/atlasViewer/widgetUnit/widgetService.service.ts
+++ b/src/atlasViewer/widgetUnit/widgetService.service.ts
@@ -1,9 +1,7 @@
 import { ComponentRef, ComponentFactory, Injectable, ViewContainerRef, ComponentFactoryResolver, Injector } from "@angular/core";
-
 import { WidgetUnit } from "./widgetUnit.component";
 import { AtlasViewerConstantsServices } from "../atlasViewer.constantService.service";
-import { Subscription } from "rxjs";
-
+import { Subscription, BehaviorSubject } from "rxjs";
 
 @Injectable({
   providedIn : 'root'
@@ -20,7 +18,8 @@ export class WidgetServices{
 
   private clickedListener : Subscription[] = []
 
-  public minimisedWindow: Set<WidgetUnit> = new Set()
+  public minimisedWindow$: BehaviorSubject<Set<WidgetUnit>>
+  private minimisedWindow: Set<WidgetUnit> = new Set() 
 
   constructor(
     private cfr:ComponentFactoryResolver,
@@ -28,6 +27,7 @@ export class WidgetServices{
     private injector : Injector
     ){
     this.widgetUnitFactory = this.cfr.resolveComponentFactory(WidgetUnit)
+    this.minimisedWindow$ = new BehaviorSubject(this.minimisedWindow)
   }
 
   clearAllWidgets(){
@@ -38,8 +38,26 @@ export class WidgetServices{
     this.clickedListener.forEach(s=>s.unsubscribe())
   }
 
+  rename(wu:WidgetUnit, {title, titleHTML}: {title: string, titleHTML: string}){
+    /**
+     * WARNING: always sanitize before pass to rename fn!
+     */
+    wu.title = title
+    wu.titleHTML = titleHTML
+  }
+
   minimise(wu:WidgetUnit){
     this.minimisedWindow.add(wu)
+    this.minimisedWindow$.next(new Set(this.minimisedWindow))
+  }
+  
+  isMinimised(wu:WidgetUnit){
+    return this.minimisedWindow.has(wu)
+  }
+
+  unminimise(wu:WidgetUnit){
+    this.minimisedWindow.delete(wu)
+    this.minimisedWindow$.next(new Set(this.minimisedWindow))
   }
 
   addNewWidget(guestComponentRef:ComponentRef<any>,options?:Partial<WidgetOptionsInterface>):ComponentRef<WidgetUnit>{
@@ -93,9 +111,12 @@ export class WidgetServices{
 
       this.clickedListener.push(
         _component.instance.clickedEmitter.subscribe((widgetUnit:WidgetUnit)=>{
+          /**
+           * TODO this operation 
+           */
           if(widgetUnit.state !== 'floating')
             return
-          const widget = [...this.widgetComponentRefs].find(widget=>widget.instance===widgetUnit)
+          const widget = [...this.widgetComponentRefs].find(widget=>widget.instance === widgetUnit)
           if(!widget)
             return
           const idx = this.floatingContainer.indexOf(widget.hostView)
@@ -103,7 +124,6 @@ export class WidgetServices{
             return
           this.floatingContainer.detach(idx)
           this.floatingContainer.insert(widget.hostView)
-          
         })
       )
 
diff --git a/src/atlasViewer/widgetUnit/widgetUnit.component.ts b/src/atlasViewer/widgetUnit/widgetUnit.component.ts
index fc32210cd0774a8b1b75126c25f9e10b2de9195a..35463f92e9d75f4804964dae65439e52c9984a35 100644
--- a/src/atlasViewer/widgetUnit/widgetUnit.component.ts
+++ b/src/atlasViewer/widgetUnit/widgetUnit.component.ts
@@ -1,6 +1,9 @@
-import { Component, ViewChild, ViewContainerRef,ComponentRef, HostBinding, HostListener, Output, EventEmitter, Input, ElementRef, OnInit } from "@angular/core";
+import { Component, ViewChild, ViewContainerRef,ComponentRef, HostBinding, HostListener, Output, EventEmitter, Input, ElementRef, OnInit, OnDestroy } from "@angular/core";
+
 import { WidgetServices } from "./widgetService.service";
 import { AtlasViewerConstantsServices } from "../atlasViewer.constantService.service";
+import { Subscription, Observable } from "rxjs";
+import { map } from "rxjs/operators";
 
 
 @Component({
@@ -10,9 +13,8 @@ import { AtlasViewerConstantsServices } from "../atlasViewer.constantService.ser
   ]
 })
 
-export class WidgetUnit implements OnInit{
+export class WidgetUnit implements OnInit, OnDestroy{
   @ViewChild('container',{read:ViewContainerRef}) container : ViewContainerRef
-  @ViewChild('emptyspan',{read:ElementRef}) emtpy : ElementRef
 
   @HostBinding('attr.state')
   public state : 'docked' | 'floating' = 'docked'
@@ -24,29 +26,61 @@ export class WidgetUnit implements OnInit{
   height : string = this.state === 'docked' ? null : '0px'
 
   @HostBinding('style.display')
-  get isMinimised(){
-    return this.widgetServices.minimisedWindow.has(this) ? 'none' : null
+  isMinimised: string
+
+  isMinimised$: Observable<boolean>
+
+  /**
+   * Timed alternates of blinkOn property should result in attention grabbing blink behaviour
+   */
+  private _blinkOn: boolean = false
+  get blinkOn(){
+    return this._blinkOn
   }
+
+  set blinkOn(val: boolean) {
+    this._blinkOn = !!val
+  }
+
+  get showProgress(){
+    return this.progressIndicator !== null
+  }
+
   /**
-   * TODO
-   * upgrade to angular>=7, and use cdk to handle draggable components
+   * Some plugins may like to show progress indicator for long running processes
+   * If null, no progress is running
+   * This value should be between 0 and 1
    */
-  get transform(){
-    return this.state === 'floating' ?
-      `translate(${this.position[0]}px, ${this.position[1]}px)` :
-      `translate(0 , 0)`
+  private _progressIndicator: number = null
+  get progressIndicator(){
+    return this._progressIndicator
+  }
+
+  set progressIndicator(val:number) {
+    if (isNaN(val)) {
+      this._progressIndicator = null
+      return
+    }
+    if (val < 0) {
+      this._progressIndicator = 0
+      return
+    }
+    if (val > 1) {
+      this._progressIndicator = 1
+      return
+    }
+    this._progressIndicator = val
   }
 
   public canBeDocked: boolean = false
   @HostListener('mousedown')
   clicked(){
     this.clickedEmitter.emit(this)
+    this.blinkOn = false
   }
 
   @Input() title : string = 'Untitled'
 
-  @Input() containerClass : string = ''
-
   @Output()
   clickedEmitter : EventEmitter<WidgetUnit> = new EventEmitter()
 
@@ -59,16 +93,30 @@ export class WidgetUnit implements OnInit{
   public guestComponentRef : ComponentRef<any>
   public widgetServices:WidgetServices
   public cf : ComponentRef<WidgetUnit>
+  private subscriptions: Subscription[] = []
 
   public id: string 
   constructor(
     private constantsService: AtlasViewerConstantsServices
-    ){
+  ){
     this.id = Date.now().toString()
   }
 
   ngOnInit(){
     this.canBeDocked = typeof this.widgetServices.dockedContainer !== 'undefined'
+
+    this.isMinimised$ = this.widgetServices.minimisedWindow$.pipe(
+      map(set => set.has(this))
+    )
+    this.subscriptions.push(
+      this.isMinimised$.subscribe(flag => this.isMinimised = flag ? 'none' : null)
+    )
+  }
+
+  ngOnDestroy(){
+    while(this.subscriptions.length > 0){
+      this.subscriptions.pop().unsubscribe()
+    }
   }
 
   /**
diff --git a/src/atlasViewer/widgetUnit/widgetUnit.style.css b/src/atlasViewer/widgetUnit/widgetUnit.style.css
index 10289f924763603c034766cd15e23a819cb0e857..9ed85f20e0fc549bc2c9ab0ac31f8ae156dc48e8 100644
--- a/src/atlasViewer/widgetUnit/widgetUnit.style.css
+++ b/src/atlasViewer/widgetUnit/widgetUnit.style.css
@@ -41,7 +41,62 @@ panel-component[widgetUnitPanel]
   cursor : move;
 }
 
-[emptyspan]
+:host > panel-component
 {
-  opacity:0.01;
+  max-width: 100%;
+  width: 300px;
+  border-width: 1px !important;
+  border: solid;
+  border-color: rgba(0, 0, 0, 0);
+  box-sizing: border-box;
 }
+
+@keyframes blinkDark
+{
+  0% {
+    border-color: rgba(128, 128, 200, 0.0);
+  }
+
+  100% {
+    border-color: rgba(128, 128, 200, 1.0);
+  }
+}
+
+@keyframes blink
+{
+  0% {
+    border-color: rgba(128, 128, 255, 0.0);
+  }
+
+  100% {
+    border-color: rgba(128, 128, 255, 1.0);
+  }
+}
+
+:host-context([darktheme="true"]) .blinkOn
+{
+  animation: 0.5s blinkDark ease-in-out 9 alternate; 
+  border: 1px solid rgba(128, 128, 200, 1.0) !important;
+}
+
+:host-context([darktheme="false"]) .blinkOn
+{
+  animation: 0.5s blink ease-in-out 9 alternate; 
+  border: 1px solid rgba(128, 128, 255, 1.0) !important;
+}
+
+[heading]
+{
+  position:relative;
+}
+
+[heading] > [progressBar]
+{
+  position: absolute;
+  width: 100%;
+  height: 100%;
+  left: 0;
+  top: 0;
+  opacity: 0.4;
+  pointer-events: none;
+}
\ No newline at end of file
diff --git a/src/atlasViewer/widgetUnit/widgetUnit.template.html b/src/atlasViewer/widgetUnit/widgetUnit.template.html
index 62600cf8ba9b22a3901d84fdb50517fe9ab7badf..84a9b851949d66beed27043783c46130c0832157 100644
--- a/src/atlasViewer/widgetUnit/widgetUnit.template.html
+++ b/src/atlasViewer/widgetUnit/widgetUnit.template.html
@@ -1,17 +1,13 @@
 <panel-component
-  [style.transform] = "transform"
-  [containerClass] = "containerClass"
   widgetUnitPanel
+  [ngClass]="{'blinkOn': blinkOn}"
   [bodyCollapsable] = "state === 'docked'"
   [cdkDragDisabled]="state === 'docked'"
-  cdkDrag
-  [ngStyle]="{'max-width': isMobile? '100%' : '300px',
-              'margin-bottom': isMobile? '5px': '0'}">
+  cdkDrag>
   <div 
     widgetUnitHeading
     heading
     cdkDragHandle>
-    <div #emptyspan emptyspan>.</div>
     <div title>
       <div *ngIf="!titleHTML">
         {{ title }}
@@ -41,6 +37,9 @@
         class = "fas fa-times" 
         [hoverable] ="{translateY: -1}"></i>
     </div>
+    <progress-bar [progress]="progressIndicator" *ngIf="showProgress" progressBar>
+
+    </progress-bar>
   </div>
   <div widgetUnitBody body>
     <ng-template #container>
diff --git a/src/components/components.module.ts b/src/components/components.module.ts
index e435687fd0541445a7989f8f87f1bb3eb392bae9..8ddb291d165ab8771f9e6b1f6e36ea8ea465f2d4 100644
--- a/src/components/components.module.ts
+++ b/src/components/components.module.ts
@@ -29,6 +29,10 @@ import { CommonModule } from '@angular/common';
 import { RadioList } from './radiolist/radiolist.component';
 import { AngularMaterialModule } from 'src/ui/sharedModules/angularMaterial.module';
 import { FilterCollapsePipe } from './flatTree/filterCollapse.pipe';
+import { ProgressBar } from './progress/progress.component';
+import { SleightOfHand } from './sleightOfHand/soh.component';
+import { DialogComponent } from './dialog/dialog.component';
+import { ConfirmDialogComponent } from './confirmDialog/confirmDialog.component';
 
 
 @NgModule({
@@ -52,6 +56,10 @@ import { FilterCollapsePipe } from './flatTree/filterCollapse.pipe';
     TimerComponent,
     PillComponent,
     RadioList,
+    ProgressBar,
+    SleightOfHand,
+    DialogComponent,
+    ConfirmDialogComponent,
 
     /* directive */
     HoverableBlockDirective,
@@ -83,6 +91,10 @@ import { FilterCollapsePipe } from './flatTree/filterCollapse.pipe';
     TimerComponent,
     PillComponent,
     RadioList,
+    ProgressBar,
+    SleightOfHand,
+    DialogComponent,
+    ConfirmDialogComponent,
 
     SearchResultPaginationPipe,
     TreeSearchPipe,
diff --git a/src/components/confirmDialog/confirmDialog.component.ts b/src/components/confirmDialog/confirmDialog.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6c16434ac9910839c345d0a5fe9a132411627ba4
--- /dev/null
+++ b/src/components/confirmDialog/confirmDialog.component.ts
@@ -0,0 +1,24 @@
+import { Component, Inject, Input } from "@angular/core";
+import { MAT_DIALOG_DATA, MatDialogRef } from "@angular/material";
+
+@Component({
+  selector: 'confirm-dialog-component',
+  templateUrl: './confirmDialog.template.html',
+  styleUrls: [
+    './confirmDialog.style.css'
+  ]
+})
+export class ConfirmDialogComponent{
+
+  @Input()
+  public title: string = 'Confirm'
+
+  @Input()
+  public message: string = 'Would you like to proceed?'
+
+  constructor(@Inject(MAT_DIALOG_DATA) data: any){
+    const { title = null, message  = null} = data || {}
+    if (title) this.title = title
+    if (message) this.message = message
+  }
+}
\ No newline at end of file
diff --git a/src/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 d8cd1d832244fca9882eff9ecc18889a7d9bac79..60e0ba44fd00d214d3fb0e9a5997b78d813ffa69 100644
--- a/src/components/flatTree/flatTree.component.ts
+++ b/src/components/flatTree/flatTree.component.ts
@@ -48,6 +48,12 @@ export class FlatTreeComponent implements AfterViewChecked {
   uncollapsedLevels : Set<string> = new Set()
 
   ngAfterViewChecked(){
+    /**
+     * if useDefaultList is true, virtualscrollViewPort will be undefined
+     */
+    if (!this.virtualScrollViewPort) {
+      return
+    }
     const currentTotalDataLength = this.virtualScrollViewPort.getDataLength()
     const previousDataLength = this.totalDataLength
 
@@ -92,4 +98,10 @@ export class FlatTreeComponent implements AfterViewChecked {
       .some(id => this.isCollapsedById(id))
   }
 
+  handleTreeNodeClick(event:MouseEvent, inputItem: any){
+    this.treeNodeClick.emit({
+      event,
+      inputItem
+    })
+  }
 }
\ No newline at end of file
diff --git a/src/components/flatTree/flatTree.template.html b/src/components/flatTree/flatTree.template.html
index 48ff87a252308c179c0c207b244a9cbb9009a58c..893c539a9ff029933606880e3e37776e3d7fa0c1 100644
--- a/src/components/flatTree/flatTree.template.html
+++ b/src/components/flatTree/flatTree.template.html
@@ -27,7 +27,7 @@
       <i [ngClass]="isCollapsed(flattenedItem) ? 'r-270' : ''" class="fas fa-chevron-down"></i>
     </span>
     <span
-      (click)="treeNodeClick.emit({event:$event,inputItem:flattenedItem})"
+      (click)="handleTreeNodeClick($event, flattenedItem)"
       class="render-node-text"
       [innerHtml]="flattenedItem | renderPipe : renderNode ">
     </span>
@@ -63,7 +63,7 @@
         <i [ngClass]="isCollapsed(flattenedItem) ? 'r-270' : ''" class="fas fa-chevron-down"></i>
       </span>
       <span
-        (click)="treeNodeClick.emit({event:$event,inputItem:flattenedItem})"
+        (click)="handleTreeNodeClick($event, flattenedItem)"
         class="render-node-text"
         [innerHtml]="flattenedItem | renderPipe : renderNode ">
       </span>
diff --git a/src/components/panel/panel.component.ts b/src/components/panel/panel.component.ts
index 6b2c5095c1f04c39d1a85123e0f793163290d845..ccfc382fb8297e7b2671bec08bfffb7b694d1a11 100644
--- a/src/components/panel/panel.component.ts
+++ b/src/components/panel/panel.component.ts
@@ -1,5 +1,4 @@
-import { Component, Input, ViewChild, ElementRef, AfterContentChecked, ChangeDetectionStrategy, ChangeDetectorRef, OnChanges, SimpleChanges, HostBinding, ApplicationRef } from "@angular/core";
-import { panelAnimations } from "./panel.animation";
+import { Component, Input, ViewChild, ElementRef, ChangeDetectionStrategy } from "@angular/core";
 import { ParseAttributeDirective } from "../parseAttribute.directive";
 
 @Component({
@@ -8,9 +7,6 @@ import { ParseAttributeDirective } from "../parseAttribute.directive";
   styleUrls : [
     `./panel.style.css`
   ],
-  host: {
-    '[class]': 'getClassNames'
-  },
   changeDetection:ChangeDetectionStrategy.OnPush
 })
 
@@ -23,8 +19,6 @@ export class PanelComponent extends ParseAttributeDirective {
   @Input() collapseBody : boolean = false
   @Input() bodyCollapsable : boolean = false
 
-  @Input() containerClass : string = ''
-
   @ViewChild('panelBody',{ read : ElementRef }) efPanelBody : ElementRef
   @ViewChild('panelFooter',{ read : ElementRef }) efPanelFooter : ElementRef
 
@@ -32,10 +26,6 @@ export class PanelComponent extends ParseAttributeDirective {
     super()
   }
 
-  get getClassNames(){
-    return `panel ${this.containerClass === '' ? 'panel-default' : this.containerClass}`
-  }
-
   toggleCollapseBody(_event:Event){
     if(this.bodyCollapsable){
       this.collapseBody = !this.collapseBody
diff --git a/src/components/panel/panel.template.html b/src/components/panel/panel.template.html
index d2c55e22b6daed1c0b223e86492e6429e34a143d..84ca6a9f7a786cc51b3ba4a32e9ff8e1b3ff416d 100644
--- a/src/components/panel/panel.template.html
+++ b/src/components/panel/panel.template.html
@@ -1,10 +1,3 @@
-<div
-  *ngIf = "showHeading"
-  class = "panel-heading"
-  hoverable>
-
-</div>
-
 <div class="l-card">
   <div class="l-card-body">
     <div
diff --git a/src/components/progress/progress.component.ts b/src/components/progress/progress.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..03e34f9f304f5c6a0b86016c5a73760392434053
--- /dev/null
+++ b/src/components/progress/progress.component.ts
@@ -0,0 +1,44 @@
+import { Component, Input, ChangeDetectionStrategy } from "@angular/core";
+
+
+@Component({
+  selector: 'progress-bar',
+  templateUrl: './progress.template.html',
+  styleUrls: [
+    './progress.style.css'
+  ],
+  changeDetection: ChangeDetectionStrategy.OnPush
+})
+
+export class ProgressBar{
+  @Input() progressStyle: any
+
+  private _progress: number = 0
+  /**
+   * between 0 and 1
+   */
+  @Input() 
+  set progress(val: number) {
+    if (isNaN(val)) {
+      this._progress = 0
+      return
+    }
+    if (val < 0 || val === null) {
+      this._progress = 0
+      return
+    }
+    if (val > 1) {
+      this._progress = 1
+      return
+    }
+    this._progress = val
+  }
+
+  get progress(){
+    return this._progress
+  }
+
+  get progressPercent(){
+    return `${this.progress * 100}%`
+  }
+}
\ No newline at end of file
diff --git a/src/components/progress/progress.style.css b/src/components/progress/progress.style.css
new file mode 100644
index 0000000000000000000000000000000000000000..36858a282cdd6c793041f515aa8c4cd7e0cab97a
--- /dev/null
+++ b/src/components/progress/progress.style.css
@@ -0,0 +1,41 @@
+.progress
+{
+  height: 100%;
+  width: 100%;
+  position:relative;
+  overflow:hidden;
+  background-color:rgba(255,255,255,0.5);
+}
+
+:host-context([darktheme="true"]) .progress
+{
+  background-color:rgba(0,0,0,0.5);
+}
+
+@keyframes moveRight
+{
+  from {
+    transform: translateX(-105%);
+  }
+  to {
+    transform: translateX(205%);
+  }
+}
+
+.progress::after
+{
+  content: '';
+  width: 100%;
+  height: 100%;
+  position:absolute;
+  border-left-width: 10em;
+  border-right-width:0;
+  border-style: solid;
+  border-image: linear-gradient(
+    to right,
+    rgba(128, 200, 128, 0.0),
+    rgba(128, 200, 128, 0.5),
+    rgba(128, 200, 128, 0.0)
+  ) 0 100%;
+  animation: moveRight 2000ms linear infinite;
+}
\ No newline at end of file
diff --git a/src/components/progress/progress.template.html b/src/components/progress/progress.template.html
new file mode 100644
index 0000000000000000000000000000000000000000..7df81f0819667454c5aa5127a780a94a557d1ae6
--- /dev/null
+++ b/src/components/progress/progress.template.html
@@ -0,0 +1,3 @@
+<div class="progress rounded-0">
+  <div [style.width]="progressPercent" class="progress-bar bg-success rounded-0" role="progressbar"></div>
+</div>
\ No newline at end of file
diff --git a/src/components/sleightOfHand/soh.component.ts b/src/components/sleightOfHand/soh.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4b6fbe5fdb989088e847e10716703dfb4c044c5a
--- /dev/null
+++ b/src/components/sleightOfHand/soh.component.ts
@@ -0,0 +1,33 @@
+import { Component, Input, HostBinding, ChangeDetectionStrategy, HostListener } from "@angular/core";
+
+@Component({
+  selector: 'sleight-of-hand',
+  templateUrl: './soh.template.html',
+  styleUrls: [
+    './soh.style.css'
+  ],
+  changeDetection: ChangeDetectionStrategy.OnPush
+})
+
+export class SleightOfHand{
+
+  @HostBinding('class.do-not-close')
+  get doNotCloseClass(){
+    return this.doNotClose || this.focusInStatus
+  }
+
+  @HostListener('focusin')
+  focusInHandler(){
+    this.focusInStatus = true
+  }
+
+  @HostListener('focusout')
+  focusOutHandler(){
+    this.focusInStatus = false
+  }
+
+  private focusInStatus: boolean = false
+
+  @Input()
+  doNotClose: boolean = false
+}
\ No newline at end of file
diff --git a/src/components/sleightOfHand/soh.style.css b/src/components/sleightOfHand/soh.style.css
new file mode 100644
index 0000000000000000000000000000000000000000..2c4f61aa210ae817635fc0764eb87f5cbe187c50
--- /dev/null
+++ b/src/components/sleightOfHand/soh.style.css
@@ -0,0 +1,28 @@
+:host:not(.do-not-close):not(:hover) > .sleight-of-hand-back,
+:host:not(.do-not-close):hover > .sleight-of-hand-front,
+:host-context(.do-not-close) > .sleight-of-hand-front
+{
+  opacity: 0;
+  pointer-events: none;
+}
+
+:host *
+{
+  transition: opacity 300ms ease-in-out;
+}
+
+:host
+{
+  position: relative;
+}
+
+:host > .sleight-of-hand-front
+{
+  position: relative;
+}
+
+:host > .sleight-of-hand-back
+{
+  position:  absolute;
+  z-index: 1;
+}
\ No newline at end of file
diff --git a/src/components/sleightOfHand/soh.template.html b/src/components/sleightOfHand/soh.template.html
new file mode 100644
index 0000000000000000000000000000000000000000..fca028aa043b8a5c77fdb98261a0e6d5a57dd19c
--- /dev/null
+++ b/src/components/sleightOfHand/soh.template.html
@@ -0,0 +1,9 @@
+<div class="sleight-of-hand-back">
+  <ng-content select="[sleight-of-hand-back]">
+  </ng-content>
+</div>
+
+<div class="sleight-of-hand-front">
+  <ng-content select="[sleight-of-hand-front]">
+  </ng-content>
+</div>
\ No newline at end of file
diff --git a/src/index.html b/src/index.html
index 467e979f0b14b8f6bbaedae248b488a4f72745d6..e8c99ff3b3da1efe140cddeb3466f0de7ba47c1f 100644
--- a/src/index.html
+++ b/src/index.html
@@ -6,9 +6,9 @@
   <meta http-equiv="X-UA-Compatible" content="ie=edge">
   <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
   <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.1/css/all.css" integrity="sha384-50oBUHEmvpQ+1lW4y57PTFmhCaXp0ML5d60M1M7uH2+nqUivzIebhndOJK28anvf" crossorigin="anonymous">
-  <link rel = "stylesheet" href = "extra_styles.css">
-  <link rel = "stylesheet" href = "plugin_styles.css">
-  <link rel = "stylesheet" href = "indigo-pink.css">
+  <link rel="stylesheet" href="extra_styles.css">
+  <link rel="stylesheet" href="plugin_styles.css">
+  <link rel="stylesheet" href="theme.css">
 
   <title>Interactive Atlas Viewer</title>
 </head>
diff --git a/src/main-aot.ts b/src/main-aot.ts
index 171bd6ce7beb8ae5a35e9d43553fdc134235d0e1..7c25c3f1aa849b9ea8265c99ffce474f78142635 100644
--- a/src/main-aot.ts
+++ b/src/main-aot.ts
@@ -4,6 +4,9 @@ import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'
 import { MainModule } from './main.module';
 import { enableProdMode } from '@angular/core';
 
+import './theme.scss'
+import './res/css/extra_styles.css'
+
 const requireAll = (r:any) => {r.keys().forEach(r)}
 requireAll(require.context('./res/ext', false, /\.json$/))
 requireAll(require.context('./res/images', true, /\.jpg|\.png/))
diff --git a/src/main.module.ts b/src/main.module.ts
index 336718e137af62d3ec860a5c80cc6018876b6810..ad4bd2ea47da47dd16e7e149f1d0f0c07f507951 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,7 +33,7 @@ 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";
@@ -44,6 +42,14 @@ import { EffectsModule } from "@ngrx/effects";
 import { UseEffects } from "./services/effect/effect";
 import { DragDropDirective } from "./util/directives/dragDrop.directive";
 import { LocalFileService } from "./services/localFile.service";
+import { DataBrowserUseEffect } from "./ui/databrowserModule/databrowser.useEffect";
+import { DialogService } from "./services/dialogService.service";
+import { DialogComponent } from "./components/dialog/dialog.component";
+import { ViewerStateControllerUseEffect } from "./ui/viewerStateController/viewerState.useEffect";
+import { ConfirmDialogComponent } from "./components/confirmDialog/confirmDialog.component";
+import { ViewerStateUseEffect } from "./services/state/viewerState.store";
+
+import 'hammerjs'
 
 @NgModule({
   imports : [
@@ -55,11 +61,14 @@ import { LocalFileService } from "./services/localFile.service";
     UIModule,
     AngularMaterialModule,
     
-    ModalModule.forRoot(),
     TooltipModule.forRoot(),
     TabsModule.forRoot(),
     EffectsModule.forRoot([
-      UseEffects
+      DataBrowserUseEffect,
+      UseEffects,
+      UserConfigStateUseEffect,
+      ViewerStateControllerUseEffect,
+      ViewerStateUseEffect,
     ]),
     StoreModule.forRoot({
       pluginState,
@@ -69,6 +78,7 @@ import { LocalFileService } from "./services/localFile.service";
       dataStore,
       spatialSearchState,
       uiState,
+      userConfigState
     }),
     HttpClientModule
   ],
@@ -99,7 +109,6 @@ import { LocalFileService } from "./services/localFile.service";
     GetNamesPipe,
     GetNamePipe,
     TransformOnhoverSegmentPipe,
-    GetFilenameFromPathnamePipe,
     NewViewerDisctinctViewToLayer
   ],
   entryComponents : [
@@ -107,6 +116,8 @@ import { LocalFileService } from "./services/localFile.service";
     ModalUnit,
     ToastComponent,
     PluginUnit,
+    DialogComponent,
+    ConfirmDialogComponent,
   ],
   providers : [
     AtlasViewerDataService,
@@ -117,6 +128,7 @@ import { LocalFileService } from "./services/localFile.service";
     AtlasWorkerService,
     AuthService,
     LocalFileService,
+    DialogService,
     
     /**
      * TODO
@@ -145,9 +157,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 d780236c080f653df3db2846abf3e6ad34afdd16..d5b2c296496bf6c94cd3127c620dcc1812176f36 100644
--- a/src/res/css/extra_styles.css
+++ b/src/res/css/extra_styles.css
@@ -137,10 +137,14 @@ span.regionSelected
 span.regionNotSelected,
 span.regionSelected
 {
-  cursor : default;
   user-select: none;
 }
 
+.cursor-default
+{
+  cursor: default;
+}
+
 markdown-dom pre code
 {
   white-space:pre;
@@ -282,6 +286,20 @@ markdown-dom pre code
   white-space: initial!important;
 }
 
+.w-5em
+{
+  width: 5em!important;
+}
+.w-10em
+{
+  width: 10em!important;
+}
+
+.mw-400px
+{
+  max-width: 400px!important;
+}
+
 .mw-100
 {
   max-width: 100%!important;
@@ -297,11 +315,26 @@ markdown-dom pre code
   max-width: 60%!important;
 }
 
+.mw-20em
+{
+  max-width: 20em!important;
+}
+
+.w-20em
+{
+  width: 20em!important;
+}
+
 .mh-20em
 {
   max-height: 20em;
 }
 
+.mh-10em
+{
+  max-height: 10em;
+}
+
 .pe-all
 {
   pointer-events: all;
@@ -322,14 +355,31 @@ markdown-dom pre code
   pointer-events: none;
 }
 
-.h-100
+.h-5em
 {
-  height:100%;
+  height: 5em!important;
+}
+
+.h-7em
+{
+  height:7em!important;
+}
+.h-10em
+{
+  height:10em!important;
+}
+.h-15em
+{
+  height:15em!important;
+}
+.h-20em
+{
+  height:20em!important;
 }
 
 .overflow-x-hidden
 {
-  overflow-x:hidden;
+  overflow-x:hidden!important;
 }
 
 .muted
@@ -346,4 +396,42 @@ markdown-dom pre code
 {
   background:none;
   border:none;
+}
+
+.w-1em
+{
+  width: 1em;
+}
+
+.bs-content-box
+{
+  box-sizing: content-box;
+}
+
+/* required to hide  */
+.cdk-global-scrollblock
+{
+  overflow-y:hidden !important;
+}
+.h-90vh
+{
+  height: 90vh!important;
+}
+
+.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/res/images/1-100.png b/src/res/images/1-100.png
new file mode 100644
index 0000000000000000000000000000000000000000..66f27f2321c73cb215e28a03ca48efd4fa8f813d
Binary files /dev/null and b/src/res/images/1-100.png differ
diff --git a/src/res/images/1-200.png b/src/res/images/1-200.png
new file mode 100644
index 0000000000000000000000000000000000000000..75435265e1f04a145d264f4b69010e03cc0f75d4
Binary files /dev/null and b/src/res/images/1-200.png differ
diff --git a/src/res/images/1-300.png b/src/res/images/1-300.png
new file mode 100644
index 0000000000000000000000000000000000000000..a990036ac12431e51d442bcaa76614df273e32cd
Binary files /dev/null and b/src/res/images/1-300.png differ
diff --git a/src/res/images/1-400.png b/src/res/images/1-400.png
new file mode 100644
index 0000000000000000000000000000000000000000..fb8880161ac1364984184e796a0d896b08549a45
Binary files /dev/null and b/src/res/images/1-400.png differ
diff --git a/src/res/images/2-100.png b/src/res/images/2-100.png
new file mode 100644
index 0000000000000000000000000000000000000000..62817e68db8cd9bf570e3923f3fc92b294df2afa
Binary files /dev/null and b/src/res/images/2-100.png differ
diff --git a/src/res/images/2-200.png b/src/res/images/2-200.png
new file mode 100644
index 0000000000000000000000000000000000000000..1036281b27359d0021be9e843758fa8c5db3270c
Binary files /dev/null and b/src/res/images/2-200.png differ
diff --git a/src/res/images/2-300.png b/src/res/images/2-300.png
new file mode 100644
index 0000000000000000000000000000000000000000..e1718b621a1ea092afea438e1bfc5fbcfa68eeaf
Binary files /dev/null and b/src/res/images/2-300.png differ
diff --git a/src/res/images/2-400.png b/src/res/images/2-400.png
new file mode 100644
index 0000000000000000000000000000000000000000..fb349bd8a361bed5ce75cf2daea616cf20306ed6
Binary files /dev/null and b/src/res/images/2-400.png differ
diff --git a/src/res/images/3-100.png b/src/res/images/3-100.png
new file mode 100644
index 0000000000000000000000000000000000000000..aec2e7b0297a557dee3843504b41cf3775f9341b
Binary files /dev/null and b/src/res/images/3-100.png differ
diff --git a/src/res/images/3-200.png b/src/res/images/3-200.png
new file mode 100644
index 0000000000000000000000000000000000000000..72665ecaa83a501b02e96e0f77701dae192147d7
Binary files /dev/null and b/src/res/images/3-200.png differ
diff --git a/src/res/images/3-300.png b/src/res/images/3-300.png
new file mode 100644
index 0000000000000000000000000000000000000000..f2d5f9795d69557b267039774e072b98dfc53845
Binary files /dev/null and b/src/res/images/3-300.png differ
diff --git a/src/res/images/3-400.png b/src/res/images/3-400.png
new file mode 100644
index 0000000000000000000000000000000000000000..f41ecd0ec5dc922177a95e7713f541cb55e17d8f
Binary files /dev/null and b/src/res/images/3-400.png differ
diff --git a/src/res/images/AllenadultmousebrainreferenceatlasV3BrainAtlas-100.png b/src/res/images/AllenadultmousebrainreferenceatlasV3BrainAtlas-100.png
new file mode 100644
index 0000000000000000000000000000000000000000..f9a9fb0e291661a457f9492df96a50462999969d
Binary files /dev/null and b/src/res/images/AllenadultmousebrainreferenceatlasV3BrainAtlas-100.png differ
diff --git a/src/res/images/AllenadultmousebrainreferenceatlasV3BrainAtlas-200.png b/src/res/images/AllenadultmousebrainreferenceatlasV3BrainAtlas-200.png
new file mode 100644
index 0000000000000000000000000000000000000000..36c143ae11e00675c45d16c5a95457a0b5880b43
Binary files /dev/null and b/src/res/images/AllenadultmousebrainreferenceatlasV3BrainAtlas-200.png differ
diff --git a/src/res/images/AllenadultmousebrainreferenceatlasV3BrainAtlas-300.png b/src/res/images/AllenadultmousebrainreferenceatlasV3BrainAtlas-300.png
new file mode 100644
index 0000000000000000000000000000000000000000..52f57e5412d618286dfd716fce13b13cef10741d
Binary files /dev/null and b/src/res/images/AllenadultmousebrainreferenceatlasV3BrainAtlas-300.png differ
diff --git a/src/res/images/AllenadultmousebrainreferenceatlasV3BrainAtlas-400.png b/src/res/images/AllenadultmousebrainreferenceatlasV3BrainAtlas-400.png
new file mode 100644
index 0000000000000000000000000000000000000000..df6e4acd8be3ae0cccca705dc824932e70eefd05
Binary files /dev/null and b/src/res/images/AllenadultmousebrainreferenceatlasV3BrainAtlas-400.png differ
diff --git a/src/res/images/BigBrainHistology-100.png b/src/res/images/BigBrainHistology-100.png
new file mode 100644
index 0000000000000000000000000000000000000000..50559bfb8359d5a71eb2a82dc276f72f80951f7a
Binary files /dev/null and b/src/res/images/BigBrainHistology-100.png differ
diff --git a/src/res/images/BigBrainHistology-200.png b/src/res/images/BigBrainHistology-200.png
new file mode 100644
index 0000000000000000000000000000000000000000..a17a7f83393e0a57dfe80afad199150b16e605d4
Binary files /dev/null and b/src/res/images/BigBrainHistology-200.png differ
diff --git a/src/res/images/BigBrainHistology-300.png b/src/res/images/BigBrainHistology-300.png
new file mode 100644
index 0000000000000000000000000000000000000000..f7cd0b28ec4cbbc78488799057878e1e8225215f
Binary files /dev/null and b/src/res/images/BigBrainHistology-300.png differ
diff --git a/src/res/images/BigBrainHistology-400.png b/src/res/images/BigBrainHistology-400.png
new file mode 100644
index 0000000000000000000000000000000000000000..7d8fea17c6d8dd5b3075df22c1f3ce9054275abb
Binary files /dev/null and b/src/res/images/BigBrainHistology-400.png differ
diff --git a/src/res/images/ICBM2009cNonlinearAsymmetric-100.png b/src/res/images/ICBM2009cNonlinearAsymmetric-100.png
new file mode 100644
index 0000000000000000000000000000000000000000..c8912fe70f594f0a0d60ad7596802ad690166c56
Binary files /dev/null and b/src/res/images/ICBM2009cNonlinearAsymmetric-100.png differ
diff --git a/src/res/images/ICBM2009cNonlinearAsymmetric-200.png b/src/res/images/ICBM2009cNonlinearAsymmetric-200.png
new file mode 100644
index 0000000000000000000000000000000000000000..7f38858407d751d4e5b1a200ed310d2c81cb6e07
Binary files /dev/null and b/src/res/images/ICBM2009cNonlinearAsymmetric-200.png differ
diff --git a/src/res/images/ICBM2009cNonlinearAsymmetric-300.png b/src/res/images/ICBM2009cNonlinearAsymmetric-300.png
new file mode 100644
index 0000000000000000000000000000000000000000..481339124d296586ec68455fd0298aed2b392e2b
Binary files /dev/null and b/src/res/images/ICBM2009cNonlinearAsymmetric-300.png differ
diff --git a/src/res/images/ICBM2009cNonlinearAsymmetric-400.png b/src/res/images/ICBM2009cNonlinearAsymmetric-400.png
new file mode 100644
index 0000000000000000000000000000000000000000..0ce95ddc0988de4f48ce8cdcda9c6b0d98b66a79
Binary files /dev/null and b/src/res/images/ICBM2009cNonlinearAsymmetric-400.png differ
diff --git a/src/res/images/MNI152ICBM2009cNonlinearAsymmetric-100.png b/src/res/images/MNI152ICBM2009cNonlinearAsymmetric-100.png
new file mode 100644
index 0000000000000000000000000000000000000000..859167bec29489f7e787d3820081e8d21d69564d
Binary files /dev/null and b/src/res/images/MNI152ICBM2009cNonlinearAsymmetric-100.png differ
diff --git a/src/res/images/MNI152ICBM2009cNonlinearAsymmetric-200.png b/src/res/images/MNI152ICBM2009cNonlinearAsymmetric-200.png
new file mode 100644
index 0000000000000000000000000000000000000000..5c9e3d78e789eefef7e608d5807713f7d3a59769
Binary files /dev/null and b/src/res/images/MNI152ICBM2009cNonlinearAsymmetric-200.png differ
diff --git a/src/res/images/MNI152ICBM2009cNonlinearAsymmetric-300.png b/src/res/images/MNI152ICBM2009cNonlinearAsymmetric-300.png
new file mode 100644
index 0000000000000000000000000000000000000000..29c1ea8f17d80fc7c850fcca49964791dc96ea15
Binary files /dev/null and b/src/res/images/MNI152ICBM2009cNonlinearAsymmetric-300.png differ
diff --git a/src/res/images/MNI152ICBM2009cNonlinearAsymmetric-400.png b/src/res/images/MNI152ICBM2009cNonlinearAsymmetric-400.png
new file mode 100644
index 0000000000000000000000000000000000000000..07b8475e00616e3235955d65cbc3598f0bd82e82
Binary files /dev/null and b/src/res/images/MNI152ICBM2009cNonlinearAsymmetric-400.png differ
diff --git a/src/res/images/MNIColin27-100.png b/src/res/images/MNIColin27-100.png
new file mode 100644
index 0000000000000000000000000000000000000000..d4748838a9e7d341efe95a9823fa7edca0b6126a
Binary files /dev/null and b/src/res/images/MNIColin27-100.png differ
diff --git a/src/res/images/MNIColin27-200.png b/src/res/images/MNIColin27-200.png
new file mode 100644
index 0000000000000000000000000000000000000000..683341c65daaa5170e77197e614acbcaa3e48f5b
Binary files /dev/null and b/src/res/images/MNIColin27-200.png differ
diff --git a/src/res/images/MNIColin27-300.png b/src/res/images/MNIColin27-300.png
new file mode 100644
index 0000000000000000000000000000000000000000..d55090287e9925d795fbf6772024e6cb81100563
Binary files /dev/null and b/src/res/images/MNIColin27-300.png differ
diff --git a/src/res/images/MNIColin27-400.png b/src/res/images/MNIColin27-400.png
new file mode 100644
index 0000000000000000000000000000000000000000..6bd9fd61b6ab6bde92c61cd035bb0999ecf1ed3b
Binary files /dev/null and b/src/res/images/MNIColin27-400.png differ
diff --git a/src/res/images/WaxholmSpaceratbrainatlasv20-100.png b/src/res/images/WaxholmSpaceratbrainatlasv20-100.png
new file mode 100644
index 0000000000000000000000000000000000000000..e1eddc7bfbc2e46b4c4bcf1146558a27d277c57b
Binary files /dev/null and b/src/res/images/WaxholmSpaceratbrainatlasv20-100.png differ
diff --git a/src/res/images/WaxholmSpaceratbrainatlasv20-200.png b/src/res/images/WaxholmSpaceratbrainatlasv20-200.png
new file mode 100644
index 0000000000000000000000000000000000000000..938d3c00746f6c4a1eefa6468715a8af0e205cf0
Binary files /dev/null and b/src/res/images/WaxholmSpaceratbrainatlasv20-200.png differ
diff --git a/src/res/images/WaxholmSpaceratbrainatlasv20-300.png b/src/res/images/WaxholmSpaceratbrainatlasv20-300.png
new file mode 100644
index 0000000000000000000000000000000000000000..37e7635b5b55e317455e12d727654b89d872067a
Binary files /dev/null and b/src/res/images/WaxholmSpaceratbrainatlasv20-300.png differ
diff --git a/src/res/images/WaxholmSpaceratbrainatlasv20-400.png b/src/res/images/WaxholmSpaceratbrainatlasv20-400.png
new file mode 100644
index 0000000000000000000000000000000000000000..57840e545f0f1804104c93cd09fb65bb16813e4b
Binary files /dev/null and b/src/res/images/WaxholmSpaceratbrainatlasv20-400.png differ
diff --git a/src/services/dialogService.service.ts b/src/services/dialogService.service.ts
new file mode 100644
index 0000000000000000000000000000000000000000..110edf6663f748866b952af474d56291e2229149
--- /dev/null
+++ b/src/services/dialogService.service.ts
@@ -0,0 +1,62 @@
+import { Injectable } from "@angular/core";
+import { MatDialog, MatDialogRef } from "@angular/material";
+import { DialogComponent } from "src/components/dialog/dialog.component";
+import { ConfirmDialogComponent } from "src/components/confirmDialog/confirmDialog.component";
+
+
+@Injectable({
+  providedIn: 'root'
+})
+
+export class DialogService{
+
+  private dialogRef: MatDialogRef<DialogComponent>
+  private confirmDialogRef: MatDialogRef<ConfirmDialogComponent>
+
+  constructor(private dialog:MatDialog){
+
+  }
+
+  public getUserConfirm(config: Partial<DialogConfig> = {}): Promise<string>{
+    this.confirmDialogRef = this.dialog.open(ConfirmDialogComponent, {
+      data: config
+    })
+    return new Promise((resolve, reject) => this.confirmDialogRef.afterClosed()
+      .subscribe(val => {
+        if (val) resolve()
+        else reject('User cancelled')
+      },
+      reject,
+      () => this.confirmDialogRef = null))
+  }
+
+  public getUserInput(config: Partial<DialogConfig> = {}):Promise<string>{
+    const { defaultValue = '', placeholder = 'Type your response here', title = 'Message', message = '' } = config
+    this.dialogRef = this.dialog.open(DialogComponent, {
+      data: {
+        title,
+        placeholder,
+        defaultValue,
+        message
+      }
+    })
+    return new Promise((resolve, reject) => {
+      /**
+       * nb: one one value is ever emitted, then the subscription ends
+       * Should not result in leak
+       */
+      this.dialogRef.afterClosed().subscribe(value => {
+        if (value) resolve(value)
+        else reject('User cancelled input')
+        this.dialogRef = null
+      })
+    })
+  }
+}
+
+export interface DialogConfig{
+  title: string
+  placeholder: string
+  defaultValue: string
+  message: string
+}
\ No newline at end of file
diff --git a/src/services/effect/effect.ts b/src/services/effect/effect.ts
index 399bcae7652b8bb8b1ec3becba2188d341dfaf2c..603ed7be86372dc6b93d4922979882c219da09f3 100644
--- a/src/services/effect/effect.ts
+++ b/src/services/effect/effect.ts
@@ -1,9 +1,9 @@
 import { Injectable, OnDestroy } from "@angular/core";
 import { Effect, Actions, ofType } from "@ngrx/effects";
-import { Subscription, merge, fromEvent, combineLatest } from "rxjs";
-import { withLatestFrom, map, filter } from "rxjs/operators";
+import { Subscription, merge, fromEvent, combineLatest, Observable } from "rxjs";
+import { withLatestFrom, map, filter, shareReplay, tap, switchMap, take } from "rxjs/operators";
 import { Store, select } from "@ngrx/store";
-import { SELECT_PARCELLATION, SELECT_REGIONS, NEWVIEWER, UPDATE_PARCELLATION, SELECT_REGIONS_WITH_ID } from "../state/viewerState.store";
+import { SELECT_PARCELLATION, SELECT_REGIONS, NEWVIEWER, UPDATE_PARCELLATION, SELECT_REGIONS_WITH_ID, DESELECT_REGIONS, ADD_TO_REGIONS_SELECTION_WITH_IDS } from "../state/viewerState.store";
 import { worker } from 'src/atlasViewer/atlasViewer.workerService.service'
 import { getNgIdLabelIndexFromId, generateLabelIndexId, recursiveFindRegionWithLabelIndexId } from '../stateStore.service';
 
@@ -24,8 +24,51 @@ export class UseEffects implements OnDestroy{
         })
       })
     )
+
+    this.regionsSelected$ = this.store$.pipe(
+      select('viewerState'),
+      select('regionsSelected'),
+      shareReplay(1)
+    )
+
+    this.onDeselectRegions = this.actions$.pipe(
+      ofType(DESELECT_REGIONS),
+      withLatestFrom(this.regionsSelected$),
+      map(([action, regionsSelected]) => {
+        const { deselectRegions } = action
+        const deselectSet = new Set((deselectRegions as any[]).map(r => r.name))
+        const selectRegions = regionsSelected.filter(r => !deselectSet.has(r.name))
+        return {
+          type: SELECT_REGIONS,
+          selectRegions
+        }
+      })
+    )
+
+    this.addToSelectedRegions$ = this.actions$.pipe(
+      ofType(ADD_TO_REGIONS_SELECTION_WITH_IDS),
+      map(action => {
+        const { selectRegionIds } = action
+        return selectRegionIds
+      }),
+      switchMap(selectRegionIds => this.updatedParcellation$.pipe(
+        filter(p => !!p),
+        take(1),
+        map(p => [selectRegionIds, p])
+      )),
+      map(this.convertRegionIdsToRegion),
+      withLatestFrom(this.regionsSelected$),
+      map(([ selectedRegions, alreadySelectedRegions ]) => {
+        return {
+          type: SELECT_REGIONS,
+          selectRegions: this.removeDuplicatedRegions(selectedRegions, alreadySelectedRegions)
+        }
+      })
+    )
   }
 
+  private regionsSelected$: Observable<any[]>
+
   ngOnDestroy(){
     while(this.subscriptions.length > 0) {
       this.subscriptions.pop().unsubscribe()
@@ -53,45 +96,76 @@ export class UseEffects implements OnDestroy{
   private updatedParcellation$ = this.store$.pipe(
     select('viewerState'),
     select('parcellationSelected'),
-    filter(p => !!p && !!p.regions)
+    map(p => p.updated ? p : null),
+    shareReplay(1)
   )
 
+  @Effect()
+  onDeselectRegions: Observable<any> 
+
+  private convertRegionIdsToRegion = ([selectRegionIds, parcellation]) => {
+    const { ngId: defaultNgId } = parcellation
+    return (<any[]>selectRegionIds)
+      .map(labelIndexId => getNgIdLabelIndexFromId({ labelIndexId }))
+      .map(({ ngId, labelIndex }) => {
+        return {
+          labelIndexId: generateLabelIndexId({
+            ngId: ngId || defaultNgId,
+            labelIndex 
+          })
+        }
+      })
+      .map(({ labelIndexId }) => {
+        return recursiveFindRegionWithLabelIndexId({ 
+          regions: parcellation.regions,
+          labelIndexId,
+          inheritedNgId: defaultNgId
+        })
+      })
+      .filter(v => {
+        if (!v) {
+          console.log(`SELECT_REGIONS_WITH_ID, some ids cannot be parsed intto label index`)
+        }
+        return !!v
+      })
+  }
+
+  private removeDuplicatedRegions = (...args) => {
+    const set = new Set()
+    const returnArr = []
+    for (const regions of args){
+      for (const region of regions){
+        if (!set.has(region.name)) {
+          returnArr.push(region)
+          set.add(region.name)
+        }
+      }
+    }
+    return returnArr
+  }
+
+  @Effect()
+  addToSelectedRegions$: Observable<any>
+  
+
   /**
    * for backwards compatibility.
    * older versions of atlas viewer may only have labelIndex as region identifier
    */
   @Effect()
-  onSelectRegionWithId = combineLatest(
-    this.actions$.pipe(
-      ofType(SELECT_REGIONS_WITH_ID)
-    ),
-    this.updatedParcellation$
-  ).pipe(
-    map(([action, parcellation]) => {
+  onSelectRegionWithId = this.actions$.pipe(
+    ofType(SELECT_REGIONS_WITH_ID),
+    map(action => {
       const { selectRegionIds } = action
-      const { ngId: defaultNgId } = parcellation
-
-      const selectRegions = (<any[]>selectRegionIds)
-        .map(labelIndexId => getNgIdLabelIndexFromId({ labelIndexId }))
-        .map(({ ngId, labelIndex }) => {
-          return {
-            labelIndexId: generateLabelIndexId({
-              ngId: ngId || defaultNgId,
-              labelIndex 
-            })
-          }
-        })
-        .map(({ labelIndexId }) => {
-          return recursiveFindRegionWithLabelIndexId({ 
-            regions: parcellation.regions,
-            labelIndexId,
-            inheritedNgId: defaultNgId
-          })
-        })
-        .filter(v => {
-          if (!v) console.log(`SELECT_REGIONS_WITH_ID, some ids cannot be parsed intto label index`)
-          return !!v
-        })
+      return selectRegionIds
+    }),
+    switchMap(selectRegionIds => this.updatedParcellation$.pipe(
+      filter(p => !!p),
+      take(1),
+      map(parcellation => [selectRegionIds, parcellation])
+    )),
+    map(this.convertRegionIdsToRegion),
+    map(selectRegions => {
       return {
         type: SELECT_REGIONS,
         selectRegions
@@ -118,7 +192,14 @@ export class UseEffects implements OnDestroy{
     filter((message: MessageEvent) => message && message.data && message.data.type === 'UPDATE_PARCELLATION_REGIONS'),
     map(({data}) => data.parcellation),
     withLatestFrom(this.newParcellationSelected$),
-    filter(([ propagatedP, selectedP ] : [any, any]) => propagatedP.name === selectedP.name),
+    filter(([ propagatedP, selectedP ] : [any, any]) => {
+      /**
+       * TODO 
+       * use id
+       * but jubrain may have same id for different template spaces
+       */
+      return propagatedP.name === selectedP.name
+    }),
     map(([ propagatedP, _ ]) => propagatedP),
     map(parcellation => ({
       type: UPDATE_PARCELLATION,
diff --git a/src/services/state/dataStore.store.ts b/src/services/state/dataStore.store.ts
index 4b25d5eae3b134ab7f48c07cf9d290eee06e1d8a..44f21f0bb825e10cdb63e51c23813c7f3fa7a0ec 100644
--- a/src/services/state/dataStore.store.ts
+++ b/src/services/state/dataStore.store.ts
@@ -1,6 +1,22 @@
 import { Action } from '@ngrx/store'
 
-export function dataStore(state:any,action:DatasetAction){
+/**
+ * TODO merge with databrowser.usereffect.ts
+ */
+
+interface DataEntryState{
+  fetchedDataEntries: DataEntry[]
+  favDataEntries: DataEntry[]
+  fetchedSpatialData: DataEntry[]
+}
+
+const defaultState = {
+  fetchedDataEntries: [],
+  favDataEntries: [],
+  fetchedSpatialData: []
+}
+
+export function dataStore(state:DataEntryState = defaultState, action:Partial<DatasetAction>){
   switch (action.type){
     case FETCHED_DATAENTRIES: {
       return {
@@ -14,12 +30,19 @@ export function dataStore(state:any,action:DatasetAction){
         fetchedSpatialData : action.fetchedDataEntries
       }
     }
-    default:
-      return state
+    case ACTION_TYPES.UPDATE_FAV_DATASETS: {
+      const { favDataEntries = [] } = action
+      return {
+        ...state,
+        favDataEntries
+      }
+    }
+    default: return state
   }
 }
 
 export interface DatasetAction extends Action{
+  favDataEntries: DataEntry[]
   fetchedDataEntries : DataEntry[]
   fetchedSpatialData : DataEntry[]
 }
@@ -57,6 +80,9 @@ export interface DataEntry{
    * TODO typo, should be kgReferences
    */
   kgReference: string[]
+
+  id: string
+  fullId: string
 }
 
 export interface ParcellationRegion {
@@ -133,4 +159,12 @@ export interface ViewerPreviewFile{
 
 export interface FileSupplementData{
   data: any
-}
\ No newline at end of file
+}
+
+const ACTION_TYPES = {
+  FAV_DATASET: `FAV_DATASET`,
+  UPDATE_FAV_DATASETS: `UPDATE_FAV_DATASETS`,
+  UNFAV_DATASET: 'UNFAV_DATASET'
+}
+
+export const DATASETS_ACTIONS_TYPES = ACTION_TYPES
\ No newline at end of file
diff --git a/src/services/state/ngViewerState.store.ts b/src/services/state/ngViewerState.store.ts
index d1dd8b27a79daa1b2a73ad56df959cac71f87636..289489d7e549bd4266aac0a57ffd67c877aa2804 100644
--- a/src/services/state/ngViewerState.store.ts
+++ b/src/services/state/ngViewerState.store.ts
@@ -1,32 +1,58 @@
 import { Action } from '@ngrx/store'
 
+export const FOUR_PANEL = 'FOUR_PANEL'
+export const V_ONE_THREE = 'V_ONE_THREE'
+export const H_ONE_THREE = 'H_ONE_THREE'
+export const SINGLE_PANEL = 'SINGLE_PANEL'
+
 export interface NgViewerStateInterface{
   layers : NgLayerInterface[]
   forceShowSegment : boolean | null
   nehubaReady: boolean
+  panelMode: string
+  panelOrder: string
 }
 
 export interface NgViewerAction extends Action{
   layer : NgLayerInterface
   forceShowSegment : boolean
   nehubaReady: boolean
+  payload: any
 }
 
-const defaultState:NgViewerStateInterface = {layers:[], forceShowSegment:null, nehubaReady: false}
+const defaultState:NgViewerStateInterface = {
+  layers:[],
+  forceShowSegment:null,
+  nehubaReady: false,
+  panelMode: FOUR_PANEL,
+  panelOrder: `0123`
+}
 
 export function ngViewerState(prevState:NgViewerStateInterface = defaultState, action:NgViewerAction):NgViewerStateInterface{
   switch(action.type){
+    case ACTION_TYPES.SET_PANEL_ORDER: {
+      const { payload } = action
+      const { panelOrder } = payload
+      return {
+        ...prevState,
+        panelOrder
+      }
+    }
+    case ACTION_TYPES.SWITCH_PANEL_MODE: {
+      const { payload } = action
+      const { panelMode } = payload
+      if (SUPPORTED_PANEL_MODES.indexOf(panelMode) < 0) return prevState
+      return {
+        ...prevState,
+        panelMode
+      }
+    }
     case ADD_NG_LAYER:
-      return Object.assign({}, prevState, {
+      return {
+        ...prevState,
+
         /* this configration hides the layer if a non mixable layer already present */
-        layers : action.layer.constructor === Array 
-          ? prevState.layers.concat(action.layer)
-          : prevState.layers.concat(
-              Object.assign({}, action.layer, 
-                action.layer.mixability === 'nonmixable' && prevState.layers.findIndex(l => l.mixability === 'nonmixable') >= 0
-                  ? {visible: false}
-                  : {}))
-        
+
         /* this configuration does not the addition of multiple non mixable layers */
         // layers : action.layer.mixability === 'nonmixable' && prevState.layers.findIndex(l => l.mixability === 'nonmixable') >= 0
         //   ? prevState.layers
@@ -34,39 +60,47 @@ export function ngViewerState(prevState:NgViewerStateInterface = defaultState, a
 
         /* this configuration allows the addition of multiple non mixables */
         // layers : prevState.layers.map(l => mapLayer(l, action.layer)).concat(action.layer)
-      })
+        layers : action.layer.constructor === Array 
+          ? prevState.layers.concat(action.layer)
+          : prevState.layers.concat({
+            ...action.layer,
+            ...( action.layer.mixability === 'nonmixable' && prevState.layers.findIndex(l => l.mixability === 'nonmixable') >= 0
+                  ? {visible: false}
+                  : {})
+          })
+      } 
     case REMOVE_NG_LAYER:
-      return Object.assign({}, prevState, {
+      return {
+        ...prevState,
         layers : prevState.layers.filter(l => l.name !== action.layer.name)
-      } as NgViewerStateInterface)
+      }
     case SHOW_NG_LAYER:
-      return Object.assign({}, prevState, {
+      return {
+        ...prevState,
         layers : prevState.layers.map(l => l.name === action.layer.name
-          ? Object.assign({}, l, {
-              visible : true
-            } as NgLayerInterface)
+          ? { ...l, visible: true }
           : l)
-      })
+      }
     case HIDE_NG_LAYER:
-      return Object.assign({}, prevState, {
+      return {
+        ...prevState,
+
         layers : prevState.layers.map(l => l.name === action.layer.name
-          ? Object.assign({}, l, {
-              visible : false
-            } as NgLayerInterface)
+          ? { ...l, visible: false }
           : l)
-        })
+      }
     case FORCE_SHOW_SEGMENT:
-      return Object.assign({}, prevState, {
+      return {
+        ...prevState,
         forceShowSegment : action.forceShowSegment
-      }) as NgViewerStateInterface
+      }
     case NEHUBA_READY: 
       const { nehubaReady } = action
       return {
         ...prevState,
         nehubaReady
       }
-    default:
-      return prevState
+    default: return prevState
   }
 }
 
@@ -84,4 +118,19 @@ interface NgLayerInterface{
   visible : boolean
   shader? : string
   transform? : any
-}
\ No newline at end of file
+}
+
+const ACTION_TYPES = {
+  SWITCH_PANEL_MODE: 'SWITCH_PANEL_MODE',
+  SET_PANEL_ORDER: 'SET_PANEL_ORDER'
+}
+
+export const SUPPORTED_PANEL_MODES = [
+  FOUR_PANEL,
+  H_ONE_THREE,
+  V_ONE_THREE,
+  SINGLE_PANEL,
+]
+
+
+export const NG_VIEWER_ACTION_TYPES = ACTION_TYPES
\ No newline at end of file
diff --git a/src/services/state/pluginState.store.ts b/src/services/state/pluginState.store.ts
index 93d6b1e9678128b624fbe146c26afb08f494306b..e9a80cc8d3b180b847ec8e6b86294093e8a7f903 100644
--- a/src/services/state/pluginState.store.ts
+++ b/src/services/state/pluginState.store.ts
@@ -12,10 +12,12 @@ export interface PluginInitManifestActionInterface extends Action{
   }
 }
 
-export const ACTION_TYPES = {
+const ACTION_TYPES = {
   SET_INIT_PLUGIN: `SET_INIT_PLUGIN`
 }
 
+export const PLUGIN_STATE_ACTION_TYPES = ACTION_TYPES
+
 export function pluginState(prevState:PluginInitManifestInterface = {initManifests : new Map()}, action:PluginInitManifestActionInterface):PluginInitManifestInterface{
   switch(action.type){
     case ACTION_TYPES.SET_INIT_PLUGIN:
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 0b1edc38bcf23d9001d9554392d83c9e26789432..359cf2be6547ee998f73cf05b36b9909ed1e1ae4 100644
--- a/src/services/state/viewerState.store.ts
+++ b/src/services/state/viewerState.store.ts
@@ -1,6 +1,10 @@
-import { Action } from '@ngrx/store'
+import { Action, Store, select } from '@ngrx/store'
 import { UserLandmark } from 'src/atlasViewer/atlasViewer.apiService.service';
 import { NgLayerInterface } from 'src/atlasViewer/atlasViewer.component';
+import { Injectable } from '@angular/core';
+import { Actions, Effect, ofType } from '@ngrx/effects';
+import { withLatestFrom, map, shareReplay, startWith, tap } from 'rxjs/operators';
+import { Observable } from 'rxjs';
 
 export interface ViewerStateInterface{
   fetchedTemplates : any[]
@@ -34,13 +38,17 @@ export interface AtlasAction extends Action{
   deselectLandmarks : UserLandmark[]
 
   navigation? : any
+
+  payload: any
 }
 
 export function viewerState(
   state:Partial<ViewerStateInterface> = {
     landmarksSelected : [],
     fetchedTemplates : [],
-    loadedNgLayers: []
+    loadedNgLayers: [],
+    regionsSelected: [],
+    userLandmarks: []
   },
   action:AtlasAction
 ){
@@ -106,7 +114,10 @@ export function viewerState(
       const { updatedParcellation } = action
       return {
         ...state,
-        parcellationSelected: updatedParcellation
+        parcellationSelected: {
+          ...updatedParcellation,
+          updated: true
+        }
       }
     }
     case SELECT_REGIONS:
@@ -133,6 +144,10 @@ export function viewerState(
         userLandmarks: action.landmarks
       } 
     }
+    /**
+     * TODO
+     * duplicated with ngViewerState.layers ?
+     */
     case NEHUBA_LAYER_CHANGED: {
       if (!window['viewer']) {
         return {
@@ -167,10 +182,88 @@ export const CHANGE_NAVIGATION = 'CHANGE_NAVIGATION'
 export const SELECT_PARCELLATION = `SELECT_PARCELLATION`
 export const UPDATE_PARCELLATION = `UPDATE_PARCELLATION`
 
+export const DESELECT_REGIONS = `DESELECT_REGIONS`
 export const SELECT_REGIONS = `SELECT_REGIONS`
 export const SELECT_REGIONS_WITH_ID = `SELECT_REGIONS_WITH_ID`
 export const SELECT_LANDMARKS = `SELECT_LANDMARKS`
 export const DESELECT_LANDMARKS = `DESELECT_LANDMARKS`
 export const USER_LANDMARKS = `USER_LANDMARKS`
 
+export const ADD_TO_REGIONS_SELECTION_WITH_IDS = `ADD_TO_REGIONS_SELECTION_WITH_IDS`
+
 export const NEHUBA_LAYER_CHANGED = `NEHUBA_LAYER_CHANGED`
+
+@Injectable({
+  providedIn: 'root'
+})
+
+export class ViewerStateUseEffect{
+  constructor(
+    private actions$: Actions,
+    private store$: Store<any>
+  ){
+    this.currentLandmarks$ = this.store$.pipe(
+      select('viewerState'),
+      select('userLandmarks'),
+      shareReplay(1),
+    )
+
+    this.removeUserLandmarks = this.actions$.pipe(
+      ofType(ACTION_TYPES.REMOVE_USER_LANDMARKS),
+      withLatestFrom(this.currentLandmarks$),
+      map(([action, currentLandmarks]) => {
+        const { landmarkIds } = (action as AtlasAction).payload
+        for ( const rmId of landmarkIds ){
+          const idx = currentLandmarks.findIndex(({ id }) => id === rmId)
+          if (idx < 0) console.warn(`remove userlandmark with id ${rmId} does not exist`)
+        }
+        const removeSet = new Set(landmarkIds)
+        return {
+          type: USER_LANDMARKS,
+          landmarks: currentLandmarks.filter(({ id }) => !removeSet.has(id))
+        }
+      })
+    )
+
+    this.addUserLandmarks$ = this.actions$.pipe(
+      ofType(ACTION_TYPES.ADD_USERLANDMARKS),
+      withLatestFrom(this.currentLandmarks$),
+      map(([action, currentLandmarks]) => {
+        const { landmarks } = action as AtlasAction
+        const landmarkMap = new Map()
+        for (const landmark of currentLandmarks) {
+          const { id } = landmark
+          landmarkMap.set(id, landmark)
+        }
+        for (const landmark of landmarks) {
+          const { id } = landmark
+          if (landmarkMap.has(id)) {
+            console.warn(`Attempting to add a landmark that already exists, id: ${id}`)
+          } else {
+            landmarkMap.set(id, landmark)
+          }
+        }
+        const userLandmarks = Array.from(landmarkMap).map(([id, landmark]) => landmark)
+        return {
+          type: USER_LANDMARKS,
+          landmarks: userLandmarks
+        }
+      })
+    )
+  }
+
+  private currentLandmarks$: Observable<any[]>
+
+  @Effect()
+  removeUserLandmarks: Observable<any>
+
+  @Effect()
+  addUserLandmarks$: Observable<any>
+}
+
+const ACTION_TYPES = {
+  ADD_USERLANDMARKS: `ADD_USERLANDMARKS`,
+  REMOVE_USER_LANDMARKS: 'REMOVE_USER_LANDMARKS'
+}
+
+export const VIEWERSTATE_ACTION_TYPES = ACTION_TYPES
\ No newline at end of file
diff --git a/src/services/stateStore.service.ts b/src/services/stateStore.service.ts
index 96048ec2192d311c34a01528e4e24ee52ffa5dce..842499c18bcb4d3083a9494fab4f71ca85ca96ad 100644
--- a/src/services/stateStore.service.ts
+++ b/src/services/stateStore.service.ts
@@ -7,6 +7,11 @@ export { CHANGE_NAVIGATION, AtlasAction, DESELECT_LANDMARKS, FETCHED_TEMPLATE, N
 export { DataEntry, ParcellationRegion, DataStateInterface, DatasetAction, FETCHED_DATAENTRIES, FETCHED_SPATIAL_DATA, Landmark, OtherLandmarkGeometry, PlaneLandmarkGeometry, PointLandmarkGeometry, Property, Publication, ReferenceSpace, dataStore, File, FileSupplementData } from './state/dataStore.store'
 export { CLOSE_SIDE_PANEL, MOUSE_OVER_LANDMARK, MOUSE_OVER_SEGMENT, OPEN_SIDE_PANEL, TOGGLE_SIDE_PANEL, UIAction, UIStateInterface, uiState } from './state/uiState.store'
 export { SPATIAL_GOTO_PAGE, SpatialDataEntries, SpatialDataStateInterface, UPDATE_SPATIAL_DATA, spatialSearchState } from './state/spatialSearchState.store'
+export { userConfigState, UserConfigStateUseEffect, USER_CONFIG_ACTION_TYPES } from './state/userConfigState.store'
+
+export const GENERAL_ACTION_TYPES = {
+  ERROR: 'ERROR'
+}
 
 export function safeFilter(key:string){
   return filter((state:any)=>
diff --git a/src/theme.scss b/src/theme.scss
new file mode 100644
index 0000000000000000000000000000000000000000..e85cfdce26366235f96280ccae6f742f46218b35
--- /dev/null
+++ b/src/theme.scss
@@ -0,0 +1,21 @@
+@import '~@angular/material/theming';
+
+@include  mat-core();
+
+$iv-theme-primary: mat-palette($mat-indigo);
+$iv-theme-accent: mat-palette($mat-amber);
+$iv-theme-warn: mat-palette($mat-red);
+
+$iv-theme: mat-light-theme($iv-theme-primary, $iv-theme-accent, $iv-theme-warn);
+
+@include angular-material-theme($iv-theme);
+
+$iv-dark-theme-primary: mat-palette($mat-blue);
+$iv-dark-theme-accent:  mat-palette($mat-amber, A200, A100, A400);
+$iv-dark-theme-warn:    mat-palette($mat-red);
+$iv-dark-theme:   mat-dark-theme($iv-dark-theme-primary, $iv-dark-theme-accent, $iv-dark-theme-warn);
+
+[darktheme=true]
+{
+  @include angular-material-theme($iv-dark-theme);
+}
diff --git a/src/ui/config/config.component.ts b/src/ui/config/config.component.ts
index e0104405a1b4bc8efda4e8d5b8e18af4598b7b06..6f6f0b4f4a06b7110d9481dc9324b3947bb2106d 100644
--- a/src/ui/config/config.component.ts
+++ b/src/ui/config/config.component.ts
@@ -1,8 +1,14 @@
 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 { Observable, Subscription } from 'rxjs';
+import { map, distinctUntilChanged, startWith, shareReplay } from 'rxjs/operators';
+import { MatSlideToggleChange, MatSliderChange } from '@angular/material';
+import { NG_VIEWER_ACTION_TYPES, SUPPORTED_PANEL_MODES } from 'src/services/state/ngViewerState.store';
+
+const GPU_TOOLTIP = `GPU TOOLTIP`
+const ANIMATION_TOOLTIP = `ANIMATION_TOOLTIP`
+const ROOT_TEXT_ORDER = ['Coronal', 'Sagittal', 'Axial', '3D']
 
 @Component({
   selector: 'config-component',
@@ -14,16 +20,27 @@ import { map, distinctUntilChanged, debounce, debounceTime } from 'rxjs/operator
 
 export class ConfigComponent implements OnInit, OnDestroy{
 
+  public GPU_TOOLTIP = GPU_TOOLTIP
+  public ANIMATION_TOOLTIP = ANIMATION_TOOLTIP
+  public supportedPanelModes = SUPPORTED_PANEL_MODES
+
   /**
    * in MB
    */
   public gpuLimit$: Observable<number>
-  public keydown$: Subject<Event> = new Subject()
+
+  public animationFlag$: Observable<boolean>
   private subscriptions: Subscription[] = []
 
   public gpuMin : number = 100
   public gpuMax : number = 1000
+
+  public panelMode$: Observable<string>
   
+  private panelOrder: string
+  private panelOrder$: Observable<string>
+  public panelTexts$: Observable<[string, string, string, string]>
+
   constructor(private store: Store<ViewerConfiguration>) {
     this.gpuLimit$ = this.store.pipe(
       select('viewerConfigState'),
@@ -31,24 +48,32 @@ export class ConfigComponent implements OnInit, OnDestroy{
       distinctUntilChanged(),
       map(v => v / 1e6)
     )
+
+    this.animationFlag$ = this.store.pipe(
+      select('viewerConfigState'),
+      map((config:ViewerConfiguration) => config.animation),
+    )
+
+    this.panelMode$ = this.store.pipe(
+      select('ngViewerState'),
+      select('panelMode'),
+      startWith(SUPPORTED_PANEL_MODES[0])
+    )
+
+    this.panelOrder$ = this.store.pipe(
+      select('ngViewerState'),
+      select('panelOrder')
+    )
+    
+    this.panelTexts$ = this.panelOrder$.pipe(
+      map(string => string.split('').map(s => Number(s))),
+      map(arr => arr.map(idx => ROOT_TEXT_ORDER[idx]) as [string, string, string, string])
+    )
   }
 
   ngOnInit(){
     this.subscriptions.push(
-      this.keydown$.pipe(
-        debounceTime(250)
-      ).subscribe(ev => {
-        /**
-         * maybe greak in FF. ev.srcElement is IE non standard property
-         */
-        const val = (<HTMLInputElement>ev.srcElement).value
-        const numVal = val && Number(val)
-        if (isNaN(numVal) || numVal < this.gpuMin || numVal > this.gpuMax )
-          return
-        this.setGpuPreset({
-          value: numVal
-        })
-      })
+      this.panelOrder$.subscribe(panelOrder => this.panelOrder = panelOrder)
     )
   }
 
@@ -56,20 +81,63 @@ export class ConfigComponent implements OnInit, OnDestroy{
     this.subscriptions.forEach(s => s.unsubscribe())
   }
 
-  public wheelEvent(ev:WheelEvent) {
-    const delta = ev.deltaY * -1e5
+  public toggleAnimationFlag(ev: MatSlideToggleChange ){
+    const { checked } = ev
     this.store.dispatch({
-      type: ACTION_TYPES.CHANGE_GPU_LIMIT,
-      payload: { delta }
+      type: ACTION_TYPES.UPDATE_CONFIG,
+      config: {
+        animation: checked
+      }
     })
   }
 
-  public setGpuPreset({value}: {value: number}) {
+  public handleMatSliderChange(ev:MatSliderChange){
     this.store.dispatch({
       type: ACTION_TYPES.UPDATE_CONFIG,
       config: {
-        gpuLimit: value * 1e6
+        gpuLimit: ev.value * 1e6
       }
     })
   }
+  usePanelMode(panelMode: string){
+    this.store.dispatch({
+      type: NG_VIEWER_ACTION_TYPES.SWITCH_PANEL_MODE,
+      payload: { panelMode }
+    })
+  }
+
+  handleDrop(event:DragEvent){
+    const droppedAttri = (event.target as HTMLElement).getAttribute('panel-order')
+    const draggedAttri = event.dataTransfer.getData('text/plain')
+    if (droppedAttri === draggedAttri) return
+    const idx1 = Number(droppedAttri)
+    const idx2 = Number(draggedAttri)
+    const arr = this.panelOrder.split('');
+
+    [arr[idx1], arr[idx2]] = [arr[idx2], arr[idx1]]
+    this.store.dispatch({
+      type: NG_VIEWER_ACTION_TYPES.SET_PANEL_ORDER,
+      payload: { panelOrder: arr.join('') }
+    })
+  }
+  handleDragOver(event:DragEvent){
+    event.preventDefault()
+    const target = (event.target as HTMLElement)
+    target.classList.add('onDragOver')
+  }
+  handleDragLeave(event:DragEvent){
+    (event.target as HTMLElement).classList.remove('onDragOver')
+  }
+  handleDragStart(event:DragEvent){
+    const target = (event.target as HTMLElement)
+    const attri = target.getAttribute('panel-order')
+    event.dataTransfer.setData('text/plain', attri)
+    
+  }
+  handleDragend(event:DragEvent){
+    const target = (event.target as HTMLElement)
+    target.classList.remove('onDragOver')
+  }
+
+  public stepSize: number = 10
 }
\ No newline at end of file
diff --git a/src/ui/config/config.style.css b/src/ui/config/config.style.css
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..907391412ce3868cca03f2b38599f2f551238085 100644
--- a/src/ui/config/config.style.css
+++ b/src/ui/config/config.style.css
@@ -0,0 +1,14 @@
+.config-transition
+{
+  transition: background-color ease-in-out 200ms;
+}
+
+.config-transition:hover
+{
+  background-color: rgba(128,128,128,0.1);
+}
+
+.onDragOver
+{
+  background-color: rgba(128,128,128,0.2);
+}
\ No newline at end of file
diff --git a/src/ui/config/config.template.html b/src/ui/config/config.template.html
index 18f84d0b297f9ef71422960695f032c567faafb1..20927120dd97ec7f77e783ae3fcabfbcdc6243b6 100644
--- a/src/ui/config/config.template.html
+++ b/src/ui/config/config.template.html
@@ -1,32 +1,190 @@
-<div class="input-group">
-  <span class="input-group-prepend">
-    <span class="input-group-text">
-      GPU Limit
-    </span>
-  </span>
-  <input
-    (wheel)="wheelEvent($event)"
-    type="number"
-    [min]="100"
-    [max]="1000"
-    [step]="0.1"
-    class="form-control"
-    (input)="keydown$.next($event)"
-    [value]="gpuLimit$ | async ">
-
-  <div class="input-group-append">
-
-    <div (click)="setGpuPreset({ value: 100 })" class="btn btn-outline-secondary">
-        100
+<mat-tab-group>
+
+  <!-- viewer preference -->
+  <mat-tab label="Viewer Preference">
+    
+    <div class="m-2">
+      <div class="mat-h2">
+        Rearrange Viewports
       </div>
-      <div (click)="setGpuPreset({ value: 500 })" class="btn btn-outline-secondary">
-        500
+      <div class="mat-h4 text-muted">
+        Click and drag to rearrange viewport positions
       </div>
-      <div (click)="setGpuPreset({ value: 1000 })" class="btn btn-outline-secondary">
-        1000
+      <current-layout class="d-flex w-20em h-15em p-2">
+        <div
+          matRipple
+          (dragstart)="handleDragStart($event)"
+          (dragover)="handleDragOver($event)"
+          (dragleave)="handleDragLeave($event)"
+          (dragend)="handleDragend($event)"
+          (drop)="handleDrop($event)"
+          class="w-100 h-100 config-transition"
+          cell-i>
+          <div
+            [attr.panel-order]="0"
+            class="config-transition w-100 h-100 d-flex align-items-center justify-content-center border"
+            draggable="true">
+            {{ (panelTexts$ | async)[0] }}
+          </div>
+        </div>
+        <div
+          matRipple
+          (dragstart)="handleDragStart($event)"
+          (dragover)="handleDragOver($event)"
+          (dragleave)="handleDragLeave($event)"
+          (dragend)="handleDragend($event)"
+          (drop)="handleDrop($event)"
+          class="w-100 h-100 config-transition"
+          cell-ii>
+          <div
+            [attr.panel-order]="1"
+            class="config-transition w-100 h-100 d-flex align-items-center justify-content-center border"
+            draggable="true">
+            {{ (panelTexts$ | async)[1] }}
+          </div>
+        </div>
+        <div
+          matRipple
+          (dragstart)="handleDragStart($event)"
+          (dragover)="handleDragOver($event)"
+          (dragleave)="handleDragLeave($event)"
+          (dragend)="handleDragend($event)"
+          (drop)="handleDrop($event)"
+          class="w-100 h-100 config-transition"
+          cell-iii>
+          <div
+            [attr.panel-order]="2"
+            class="config-transition w-100 h-100 d-flex align-items-center justify-content-center border"
+            draggable="true">
+            {{ (panelTexts$ | async)[2] }}
+          </div>
+        </div>
+        <div
+          matRipple
+          (dragstart)="handleDragStart($event)"
+          (dragover)="handleDragOver($event)"
+          (dragleave)="handleDragLeave($event)"
+          (dragend)="handleDragend($event)"
+          (drop)="handleDrop($event)"
+          class="w-100 h-100 config-transition"
+          cell-iv>
+          <div
+            [attr.panel-order]="3"
+            class="config-transition w-100 h-100 d-flex align-items-center justify-content-center border"
+            draggable="true">
+            {{ (panelTexts$ | async)[3] }}
+          </div>
+        </div>
+      </current-layout>
+
+      <div class="mat-body text-muted font-italic">
+        Plane designation refers to default orientation (without oblique rotation).
       </div>
-      <span class="input-group-text">
-        MB
+    </div>
+
+    <!-- scroll window -->
+
+    <div class="m-2">
+      <div class="mat-h2">
+        Select a viewports configuration
+      </div>
+    </div>
+
+    <div class="d-flex flex-row flex-nowrap p-2">
+
+      <!-- Four Panel Card -->
+      <button
+        class="m-2 p-2"
+        mat-flat-button
+        (click)="usePanelMode(supportedPanelModes[0])"
+        [color]="(panelMode$ | async) === supportedPanelModes[0] ? 'primary' : null">
+        <layout-four-panel class="d-block w-10em h-7em">
+          <div class="border w-100 h-100" cell-i></div>
+          <div class="border w-100 h-100" cell-ii></div>
+          <div class="border w-100 h-100" cell-iii></div>
+          <div class="border w-100 h-100" cell-iv></div>
+        </layout-four-panel>
+      </button>
+
+      <!-- horizontal 1 3 card -->
+      <button
+        class="m-2 p-2"
+        mat-flat-button
+        (click)="usePanelMode(supportedPanelModes[1])"
+        [color]="(panelMode$ | async) === supportedPanelModes[1] ? 'primary' : null">
+        <layout-horizontal-one-three class="d-block w-10em h-7em">
+          <div class="border w-100 h-100" cell-i></div>
+          <div class="border w-100 h-100" cell-ii></div>
+          <div class="border w-100 h-100" cell-iii></div>
+          <div class="border w-100 h-100" cell-iv></div>
+        </layout-horizontal-one-three>
+      </button>
+  
+      <!-- vertical 1 3 card -->
+      <button
+        class="m-2 p-2"
+        mat-flat-button
+        (click)="usePanelMode(supportedPanelModes[2])"
+        [color]="(panelMode$ | async) === supportedPanelModes[2] ? 'primary' : null">
+        <layout-vertical-one-three class="d-block w-10em h-7em">
+          <div class="border w-100 h-100" cell-i></div>
+          <div class="border w-100 h-100" cell-ii></div>
+          <div class="border w-100 h-100" cell-iii></div>
+          <div class="border w-100 h-100" cell-iv></div>
+        </layout-vertical-one-three>
+      </button>
+
+      <!-- single -->
+      <button
+        class="m-2 p-2"
+        mat-flat-button
+        (click)="usePanelMode(supportedPanelModes[3])"
+        [color]="(panelMode$ | async) === supportedPanelModes[3] ? 'primary' : null">
+        <layout-single-panel class="d-block w-10em h-7em">
+          <div class="border w-100 h-100" cell-i></div>
+          <div class="border w-100 h-100" cell-ii></div>
+          <div class="border w-100 h-100" cell-iii></div>
+          <div class="border w-100 h-100" cell-iv></div>
+        </layout-single-panel>
+      </button>
+    </div>
+  </mat-tab>
+
+  <!-- hard ware -->
+  <mat-tab label="Hardware">
+    <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>
+    <div class="d-flex flex-row align-items-center justify-content start">
+      <label
+        class="m-0 d-inline-block flex-grow-0 flex-shrink-0"
+        for="gpuLimitSlider">
+        GPU Limit
+        <small [matTooltip]="GPU_TOOLTIP" class="ml-2 fas fa-question"></small>
+      </label>
+      <mat-slider
+        class="flex-grow-1 flex-shrink-1 ml-2 mr-2"
+        id="gpuLimitSlider"
+        name="gpuLimitSlider"
+        thumbLabel="true"
+        min="100"
+        max="1000"
+        [step]="stepSize"
+        (change)="handleMatSliderChange($event)"
+        [value]="gpuLimit$ | async">
+      </mat-slider>
+      <span class="d-inline-block flex-grow-0 flex-shrink-0 w-10em">
+        {{ gpuLimit$ | async }} MB
       </span>
     </div>
-  </div>
\ No newline at end of file
+
+    
+  </mat-tab>
+
+</mat-tab-group>
+
diff --git a/src/ui/config/currentLayout/currentLayout.component.ts b/src/ui/config/currentLayout/currentLayout.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..df40022f719efb0d9cce84fa1a8bcdfbe6512b06
--- /dev/null
+++ b/src/ui/config/currentLayout/currentLayout.component.ts
@@ -0,0 +1,27 @@
+import { Component } from "@angular/core";
+import { Store, select } from "@ngrx/store";
+import { Observable } from "rxjs";
+import { SUPPORTED_PANEL_MODES } from "src/services/state/ngViewerState.store";
+import { startWith } from "rxjs/operators";
+
+@Component({
+  selector: 'current-layout',
+  templateUrl: './currentLayout.template.html',
+  styleUrls: [
+    './currentLayout.style.css'
+  ]
+})
+
+export class CurrentLayout{
+
+  public supportedPanelModes = SUPPORTED_PANEL_MODES
+  public panelMode$: Observable<string>
+
+  constructor(private store$: Store<any>){
+    this.panelMode$ = this.store$.pipe(
+      select('ngViewerState'),
+      select('panelMode'),
+      startWith(SUPPORTED_PANEL_MODES[0])
+    )
+  }
+}
\ No newline at end of file
diff --git a/src/ui/config/currentLayout/currentLayout.style.css b/src/ui/config/currentLayout/currentLayout.style.css
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/ui/config/currentLayout/currentLayout.template.html b/src/ui/config/currentLayout/currentLayout.template.html
new file mode 100644
index 0000000000000000000000000000000000000000..f3d04c104239b9e81f3ff0552c892e86c444ba0d
--- /dev/null
+++ b/src/ui/config/currentLayout/currentLayout.template.html
@@ -0,0 +1,82 @@
+<div [ngSwitch]="panelMode$ | async" class="w-100 h-100 d-flex flex-row">
+  <layout-four-panel
+    *ngSwitchCase="supportedPanelModes[0]"
+    class="d-block w-100 h-100">
+    <div class="w-100 h-100" cell-i>
+      <ng-content *ngTemplateOutlet="celli"></ng-content>
+    </div>
+    <div class="w-100 h-100" cell-ii>
+      <ng-content *ngTemplateOutlet="cellii"></ng-content>
+    </div>
+    <div class="w-100 h-100" cell-iii>
+      <ng-content *ngTemplateOutlet="celliii"></ng-content>
+    </div>
+    <div class="w-100 h-100" cell-iv>
+      <ng-content *ngTemplateOutlet="celliv"></ng-content>
+    </div>
+  </layout-four-panel>
+  <layout-horizontal-one-three
+    *ngSwitchCase="supportedPanelModes[1]"
+    class="d-block w-100 h-100">
+    <div class="w-100 h-100" cell-i>
+      <ng-content *ngTemplateOutlet="celli"></ng-content>
+    </div>
+    <div class="w-100 h-100" cell-ii>
+      <ng-content *ngTemplateOutlet="cellii"></ng-content>
+    </div>
+    <div class="w-100 h-100" cell-iii>
+      <ng-content *ngTemplateOutlet="celliii"></ng-content>
+    </div>
+    <div class="w-100 h-100" cell-iv>
+      <ng-content *ngTemplateOutlet="celliv"></ng-content>
+    </div>
+  </layout-horizontal-one-three>
+  <layout-vertical-one-three
+    *ngSwitchCase="supportedPanelModes[2]"
+    class="d-block w-100 h-100">
+    <div class="w-100 h-100" cell-i>
+      <ng-content *ngTemplateOutlet="celli"></ng-content>
+    </div>
+    <div class="w-100 h-100" cell-ii>
+      <ng-content *ngTemplateOutlet="cellii"></ng-content>
+    </div>
+    <div class="w-100 h-100" cell-iii>
+      <ng-content *ngTemplateOutlet="celliii"></ng-content>
+    </div>
+    <div class="w-100 h-100" cell-iv>
+      <ng-content *ngTemplateOutlet="celliv"></ng-content>
+    </div>
+  </layout-vertical-one-three>
+  <layout-single-panel
+    *ngSwitchCase="supportedPanelModes[3]"
+    class="d-block w-100 h-100">
+    <div class="w-100 h-100" cell-i>
+      <ng-content *ngTemplateOutlet="celli"></ng-content>
+    </div>
+    <div class="w-100 h-100" cell-ii>
+      <ng-content *ngTemplateOutlet="cellii"></ng-content>
+    </div>
+    <div class="w-100 h-100" cell-iii>
+      <ng-content *ngTemplateOutlet="celliii"></ng-content>
+    </div>
+    <div class="w-100 h-100" cell-iv>
+      <ng-content *ngTemplateOutlet="celliv"></ng-content>
+    </div>
+  </layout-single-panel>
+  <div *ngSwitchDefault>
+    A panel mode which I have never seen before ...
+  </div>
+</div>
+
+<ng-template #celli>
+  <ng-content select="[cell-i]"></ng-content>
+</ng-template>
+<ng-template #cellii>
+  <ng-content select="[cell-ii]"></ng-content>
+</ng-template>
+<ng-template #celliii>
+  <ng-content select="[cell-iii]"></ng-content>
+</ng-template>
+<ng-template #celliv>
+  <ng-content select="[cell-iv]"></ng-content>
+</ng-template>
\ No newline at end of file
diff --git a/src/ui/config/layouts/fourPanel/fourPanel.component.ts b/src/ui/config/layouts/fourPanel/fourPanel.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c7a22a241e08a3862385b7e7704bf20f0adfded7
--- /dev/null
+++ b/src/ui/config/layouts/fourPanel/fourPanel.component.ts
@@ -0,0 +1,13 @@
+import { Component } from "@angular/core";
+
+@Component({
+  selector: 'layout-four-panel',
+  templateUrl: './fourPanel.template.html',
+  styleUrls: [
+    './fourPanel.style.css'
+  ]
+})
+
+export class FourPanelLayout{
+  
+}
\ No newline at end of file
diff --git a/src/ui/config/layouts/fourPanel/fourPanel.style.css b/src/ui/config/layouts/fourPanel/fourPanel.style.css
new file mode 100644
index 0000000000000000000000000000000000000000..03169dfb4b9564f62c86ca9250303c5337927861
--- /dev/null
+++ b/src/ui/config/layouts/fourPanel/fourPanel.style.css
@@ -0,0 +1,4 @@
+.four-panel-cell
+{
+  flex: 0 0 50%;
+}
\ No newline at end of file
diff --git a/src/ui/config/layouts/fourPanel/fourPanel.template.html b/src/ui/config/layouts/fourPanel/fourPanel.template.html
new file mode 100644
index 0000000000000000000000000000000000000000..ddb10f1f6a34adda68f578625dd843baf536dfa0
--- /dev/null
+++ b/src/ui/config/layouts/fourPanel/fourPanel.template.html
@@ -0,0 +1,18 @@
+<div class="w-100 h-100 d-flex flex-column justify-content-center align-items-stretch">
+  <div class="d-flex flex-row flex-grow-1 flex-shrink-1">
+    <div class="d-flex flex-row four-panel-cell align-items-center justify-content-center">
+      <ng-content select="[cell-i]"></ng-content>
+    </div>
+    <div class="d-flex flex-row four-panel-cell align-items-center justify-content-center">
+      <ng-content select="[cell-ii]"></ng-content>
+    </div>
+  </div>
+  <div class="d-flex flex-row flex-grow-1 flex-shrink-1">
+    <div class="d-flex flex-row four-panel-cell align-items-center justify-content-center">
+      <ng-content select="[cell-iii]"></ng-content>
+    </div>
+    <div class="d-flex flex-row four-panel-cell align-items-center justify-content-center">
+      <ng-content select="[cell-iv]"></ng-content>
+    </div>
+  </div>
+</div>
diff --git a/src/ui/config/layouts/h13/h13.component.ts b/src/ui/config/layouts/h13/h13.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..eccf98f96c6e49993db2a2ac609bfbc05e9a6bc7
--- /dev/null
+++ b/src/ui/config/layouts/h13/h13.component.ts
@@ -0,0 +1,13 @@
+import { Component } from "@angular/core";
+
+@Component({
+  selector: 'layout-horizontal-one-three',
+  templateUrl: './h13.template.html',
+  styleUrls: [
+    './h13.style.css'
+  ]
+})
+
+export class HorizontalOneThree{
+  
+}
\ No newline at end of file
diff --git a/src/ui/config/layouts/h13/h13.style.css b/src/ui/config/layouts/h13/h13.style.css
new file mode 100644
index 0000000000000000000000000000000000000000..be83c538297487ff9c330ba551ac85e2badfd309
--- /dev/null
+++ b/src/ui/config/layouts/h13/h13.style.css
@@ -0,0 +1,12 @@
+.major-column
+{
+  flex: 0 0 67%;
+}
+.minor-column
+{
+  flex: 0 0 33%;
+}
+.layout-31-cell
+{
+  flex: 0 0 33.33%;
+}
\ No newline at end of file
diff --git a/src/ui/config/layouts/h13/h13.template.html b/src/ui/config/layouts/h13/h13.template.html
new file mode 100644
index 0000000000000000000000000000000000000000..d389ce304f6671f2610920c5c6c3d7780b63ac1d
--- /dev/null
+++ b/src/ui/config/layouts/h13/h13.template.html
@@ -0,0 +1,18 @@
+<div class="w-100 h-100 d-flex flex-row justify-content-center align-items-stretch">
+  <div class="d-flex flex-column major-column">
+    <div class="overflow-hidden flex-grow-1 d-flex align-items-center justify-content-center">
+      <ng-content select="[cell-i]"></ng-content>
+    </div>
+  </div>
+  <div class="d-flex flex-column minor-column">
+    <div class="overflow-hidden layout-31-cell d-flex align-items-center justify-content-center">
+        <ng-content select="[cell-ii]"></ng-content>
+      </div>
+      <div class="overflow-hidden layout-31-cell d-flex align-items-center justify-content-center">
+        <ng-content select="[cell-iii]"></ng-content>
+      </div>
+      <div class="overflow-hidden layout-31-cell d-flex align-items-center justify-content-center">
+        <ng-content select="[cell-iv]"></ng-content>
+      </div>
+  </div>
+</div>
\ No newline at end of file
diff --git a/src/ui/config/layouts/single/single.component.ts b/src/ui/config/layouts/single/single.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..25101d27a9462b37e4072a912bd281126fdb7500
--- /dev/null
+++ b/src/ui/config/layouts/single/single.component.ts
@@ -0,0 +1,13 @@
+import { Component } from "@angular/core";
+
+@Component({
+  selector: 'layout-single-panel',
+  templateUrl: './single.template.html',
+  styleUrls: [
+    './single.style.css'
+  ]
+})
+
+export class SinglePanel{
+
+}
\ No newline at end of file
diff --git a/src/ui/config/layouts/single/single.style.css b/src/ui/config/layouts/single/single.style.css
new file mode 100644
index 0000000000000000000000000000000000000000..19615b37060256eef0194131b901b11e89b01b49
--- /dev/null
+++ b/src/ui/config/layouts/single/single.style.css
@@ -0,0 +1,12 @@
+.major-column
+{
+  flex: 0 0 100%;
+}
+.minor-column
+{
+  flex: 0 0 0%;
+}
+.layout-31-cell
+{
+  flex: 0 0 33%;
+}
\ No newline at end of file
diff --git a/src/ui/config/layouts/single/single.template.html b/src/ui/config/layouts/single/single.template.html
new file mode 100644
index 0000000000000000000000000000000000000000..561a6363b701518ec5d27b1f0d78507e02afe30a
--- /dev/null
+++ b/src/ui/config/layouts/single/single.template.html
@@ -0,0 +1,18 @@
+<div class="w-100 h-100 d-flex flex-row justify-content-center align-items-stretch">
+    <div class="d-flex flex-column major-column">
+      <div class="overflow-hidden flex-grow-1 d-flex align-items-center justify-content-center">
+        <ng-content select="[cell-i]"></ng-content>
+      </div>
+    </div>
+    <div class="d-flex flex-column minor-column">
+      <div class="overflow-hidden layout-31-cell d-flex align-items-center justify-content-center">
+          <ng-content select="[cell-ii]"></ng-content>
+        </div>
+        <div class="overflow-hidden layout-31-cell d-flex align-items-center justify-content-center">
+          <ng-content select="[cell-iii]"></ng-content>
+        </div>
+        <div class="overflow-hidden layout-31-cell d-flex align-items-center justify-content-center">
+          <ng-content select="[cell-iv]"></ng-content>
+        </div>
+    </div>
+  </div>
\ No newline at end of file
diff --git a/src/ui/config/layouts/v13/v13.component.ts b/src/ui/config/layouts/v13/v13.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c650ac701de3fff14118a9487403410ae89e72ad
--- /dev/null
+++ b/src/ui/config/layouts/v13/v13.component.ts
@@ -0,0 +1,13 @@
+import { Component } from "@angular/core";
+
+@Component({
+  selector: 'layout-vertical-one-three',
+  templateUrl: './v13.template.html',
+  styleUrls: [
+    './v13.style.css'
+  ]
+})
+
+export class VerticalOneThree{
+  
+}
\ No newline at end of file
diff --git a/src/ui/config/layouts/v13/v13.style.css b/src/ui/config/layouts/v13/v13.style.css
new file mode 100644
index 0000000000000000000000000000000000000000..be83c538297487ff9c330ba551ac85e2badfd309
--- /dev/null
+++ b/src/ui/config/layouts/v13/v13.style.css
@@ -0,0 +1,12 @@
+.major-column
+{
+  flex: 0 0 67%;
+}
+.minor-column
+{
+  flex: 0 0 33%;
+}
+.layout-31-cell
+{
+  flex: 0 0 33.33%;
+}
\ No newline at end of file
diff --git a/src/ui/config/layouts/v13/v13.template.html b/src/ui/config/layouts/v13/v13.template.html
new file mode 100644
index 0000000000000000000000000000000000000000..148f70cb596d2df0183f25d012bb9d948b610f60
--- /dev/null
+++ b/src/ui/config/layouts/v13/v13.template.html
@@ -0,0 +1,18 @@
+<div class="w-100 h-100 d-flex flex-column justify-content-center align-items-stretch">
+  <div class="d-flex flex-column major-column">
+    <div class="flex-grow-1 d-flex align-items-center justify-content-center">
+      <ng-content select="[cell-i]"></ng-content>
+    </div>
+  </div>
+  <div class="d-flex flex-row minor-column">
+    <div class="layout-31-cell d-flex align-items-center justify-content-center">
+        <ng-content select="[cell-ii]"></ng-content>
+      </div>
+      <div class="layout-31-cell d-flex align-items-center justify-content-center">
+        <ng-content select="[cell-iii]"></ng-content>
+      </div>
+      <div class="layout-31-cell d-flex align-items-center justify-content-center">
+        <ng-content select="[cell-iv]"></ng-content>
+      </div>
+  </div>
+</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..6c3ee1c3504db297134d6795e199cb778c18555e 100644
--- a/src/ui/databrowserModule/databrowser.module.ts
+++ b/src/ui/databrowserModule/databrowser.module.ts
@@ -25,6 +25,8 @@ import { KgSingleDatasetService } from "./kgSingleDatasetService.service"
 import { SingleDatasetView } from './singleDataset/singleDataset.component'
 import { AngularMaterialModule } from 'src/ui/sharedModules/angularMaterial.module'
 import { DoiParserPipe } from "src/util/pipes/doiPipe.pipe";
+import { DatasetIsFavedPipe } from "./util/datasetIsFaved.pipe";
+import { RegionBackgroundToRgbPipe } from "./util/regionBackgroundToRgb.pipe";
 
 @NgModule({
   imports:[
@@ -56,7 +58,9 @@ import { DoiParserPipe } from "src/util/pipes/doiPipe.pipe";
     FilterDataEntriesbyMethods,
     FilterDataEntriesByRegion,
     AggregateArrayIntoRootPipe,
-    DoiParserPipe
+    DoiParserPipe,
+    DatasetIsFavedPipe,
+    RegionBackgroundToRgbPipe
   ],
   exports:[
     DataBrowser,
diff --git a/src/ui/databrowserModule/databrowser.service.ts b/src/ui/databrowserModule/databrowser.service.ts
index 5baa496dc446a07906ffb41a99358a134d98a357..65f9a241b4e1640b4e6cdffc8938a83e84bf38c8 100644
--- a/src/ui/databrowserModule/databrowser.service.ts
+++ b/src/ui/databrowserModule/databrowser.service.ts
@@ -4,7 +4,7 @@ import { ViewerConfiguration } from "src/services/state/viewerConfig.store";
 import { select, Store } from "@ngrx/store";
 import { AtlasViewerConstantsServices } from "src/atlasViewer/atlasViewer.constantService.service";
 import { ADD_NG_LAYER, REMOVE_NG_LAYER, DataEntry, safeFilter, FETCHED_DATAENTRIES, FETCHED_SPATIAL_DATA, UPDATE_SPATIAL_DATA } from "src/services/stateStore.service";
-import { map, distinctUntilChanged, debounceTime, filter, tap, switchMap, catchError } from "rxjs/operators";
+import { map, distinctUntilChanged, debounceTime, filter, tap, switchMap, catchError, shareReplay } from "rxjs/operators";
 import { AtlasWorkerService } from "src/atlasViewer/atlasViewer.workerService.service";
 import { FilterDataEntriesByRegion } from "./util/filterDataEntriesByRegion.pipe";
 import { NO_METHODS } from "./util/filterDataEntriesByMethods.pipe";
@@ -13,6 +13,7 @@ import { DataBrowser } from "./databrowser/databrowser.component";
 import { WidgetUnit } from "src/atlasViewer/widgetUnit/widgetUnit.component";
 import { SHOW_KG_TOS } from "src/services/state/uiState.store";
 import { regionFlattener } from "src/util/regionFlattener";
+import { DATASETS_ACTIONS_TYPES } from "src/services/state/dataStore.store";
 
 const noMethodDisplayName = 'No methods described'
 
@@ -45,6 +46,8 @@ function generateToken() {
 })
 export class DatabrowserService implements OnDestroy{
 
+  public favedDataentries$: Observable<DataEntry[]>
+
   public darktheme: boolean = false
 
   public instantiatedWidgetUnits: WidgetUnit[] = []
@@ -80,6 +83,12 @@ export class DatabrowserService implements OnDestroy{
     private store: Store<ViewerConfiguration>
   ){
 
+    this.favedDataentries$ = this.store.pipe(
+      select('dataStore'),
+      select('favDataEntries'),
+      shareReplay(1)
+    )
+
     this.subscriptions.push(
       this.store.pipe(
         select('ngViewerState')
@@ -130,6 +139,9 @@ export class DatabrowserService implements OnDestroy{
       return from(fetch(`${this.constantService.backendUrl}datasets/spatialSearch/templateName/${encodedTemplateName}/bbox/${pt1.join('_')}__${pt2.join("_")}`)
         .then(res => res.json()))
     }),
+    /**
+     * TODO pipe to constantService.catchError
+     */
     catchError((err) => (console.log(err), of([])))
   )
 
@@ -194,6 +206,20 @@ export class DatabrowserService implements OnDestroy{
     this.subscriptions.forEach(s => s.unsubscribe())
   }
 
+  public saveToFav(dataentry: DataEntry){
+    this.store.dispatch({
+      type: DATASETS_ACTIONS_TYPES.FAV_DATASET,
+      payload: dataentry
+    })
+  }
+
+  public removeFromFav(dataentry: DataEntry){
+    this.store.dispatch({
+      type: DATASETS_ACTIONS_TYPES.UNFAV_DATASET,
+      payload: dataentry
+    })
+  }
+
   public fetchPreviewData(datasetName: string){
     const encodedDatasetName = encodeURI(datasetName)
     return new Promise((resolve, reject) => {
@@ -306,12 +332,6 @@ export class DatabrowserService implements OnDestroy{
   }
 
   public getModalityFromDE = getModalityFromDE
-
-  public getBackgroundColorStyleFromRegion(region:any = null){
-    return region && region.rgb
-      ? `rgb(${region.rgb.join(',')})`
-      : `white`
-  }
 }
 
 
@@ -346,6 +366,12 @@ export function getModalityFromDE(dataentries:DataEntry[]):CountedDataModality[]
   return dataentries.reduce((acc, de) => reduceDataentry(acc, de), [])
 }
 
+export function getIdFromDataEntry(dataentry: DataEntry){
+  const { id, fullId } = dataentry
+  const regex = /\/([a-zA-Z0-9\-]*?)$/.exec(fullId)
+  return (regex && regex[1]) || id
+}
+
 
 export interface CountedDataModality{
   name: string
diff --git a/src/ui/databrowserModule/databrowser.useEffect.ts b/src/ui/databrowserModule/databrowser.useEffect.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f9a57b51905c88e841669aa192463b0947d3c6be
--- /dev/null
+++ b/src/ui/databrowserModule/databrowser.useEffect.ts
@@ -0,0 +1,146 @@
+import { Injectable, OnDestroy } from "@angular/core";
+import { Store, select } from "@ngrx/store";
+import { Actions, ofType, Effect } from "@ngrx/effects";
+import { DATASETS_ACTIONS_TYPES, DataEntry } from "src/services/state/dataStore.store";
+import { Observable, of, from, merge, Subscription } from "rxjs";
+import { withLatestFrom, map, catchError, filter, switchMap, scan, share, switchMapTo, shareReplay } from "rxjs/operators";
+import { KgSingleDatasetService } from "./kgSingleDatasetService.service";
+import { getIdFromDataEntry } from "./databrowser.service";
+
+@Injectable({
+  providedIn: 'root'
+})
+
+export class DataBrowserUseEffect implements OnDestroy{
+
+  private subscriptions: Subscription[] = []
+
+  constructor(
+    private store$: Store<any>,
+    private actions$: Actions<any>,
+    private kgSingleDatasetService: KgSingleDatasetService
+    
+  ){
+    this.favDataEntries$ = this.store$.pipe(
+      select('dataStore'),
+      select('favDataEntries')
+    )
+
+    this.unfavDataset$ = this.actions$.pipe(
+      ofType(DATASETS_ACTIONS_TYPES.UNFAV_DATASET),
+      withLatestFrom(this.favDataEntries$),
+      map(([action, prevFavDataEntries]) => {
+
+        const { payload = {} } = action as any
+        const { id } = payload
+        return {
+          type: DATASETS_ACTIONS_TYPES.UPDATE_FAV_DATASETS,
+          favDataEntries: prevFavDataEntries.filter(ds => ds.id !== id)
+        }
+      })
+    )
+
+    this.favDataset$ = this.actions$.pipe(
+      ofType(DATASETS_ACTIONS_TYPES.FAV_DATASET),
+      withLatestFrom(this.favDataEntries$),
+      map(([ action, prevFavDataEntries ]) => {
+        const { payload } = action as any
+    
+        /**
+         * check duplicate
+         */
+        const favDataEntries = prevFavDataEntries.find(favDEs => favDEs.id === payload.id)
+          ? prevFavDataEntries
+          : prevFavDataEntries.concat(payload)
+    
+        return {
+          type: DATASETS_ACTIONS_TYPES.UPDATE_FAV_DATASETS,
+          favDataEntries
+        }
+      })
+    )
+
+
+    this.subscriptions.push(
+      merge(
+        this.favDataset$,
+        this.unfavDataset$
+      ).pipe(
+        switchMapTo(this.favDataEntries$)
+      ).subscribe(favDataEntries => {
+        /**
+         * only store the minimal data in localstorage/db, hydrate when needed
+         * for now, only save id 
+         * 
+         * do not save anything else on localstorage. This could potentially be leaking sensitive information
+         */
+        const serialisedFavDataentries = favDataEntries.map(dataentry => {
+          const id = getIdFromDataEntry(dataentry)
+          return { id }
+        })
+        window.localStorage.setItem(LOCAL_STORAGE_CONST.FAV_DATASET, JSON.stringify(serialisedFavDataentries))
+      })
+    )
+
+    this.savedFav$ = of(window.localStorage.getItem(LOCAL_STORAGE_CONST.FAV_DATASET)).pipe(
+      map(string => JSON.parse(string)),
+      map(arr => {
+        if (arr.every(item => item.id )) return arr
+        throw new Error('Not every item has id and/or name defined')
+      }),
+      catchError(err => {
+        /**
+         * TODO emit proper error
+         * possibly wipe corrupted local stoage here?
+         */
+        return null
+      })
+    )
+
+    this.onInitGetFav$ = this.savedFav$.pipe(
+      filter(v => !!v),
+      switchMap(arr => 
+        merge(
+          ...arr.map(({ id: kgId }) => 
+            from( this.kgSingleDatasetService.getInfoFromKg({ kgId }))
+              .pipe(catchError(err => {
+                  console.log(`fetchInfoFromKg error`, err)
+                  return null
+              })))
+        ).pipe(
+          filter(v => !!v),
+          scan((acc, curr) => acc.concat(curr), [])
+        )
+      ),
+      map(favDataEntries => {
+        return {
+          type: DATASETS_ACTIONS_TYPES.UPDATE_FAV_DATASETS,
+          favDataEntries
+        }
+      })
+    )
+  }
+
+  ngOnDestroy(){
+    while(this.subscriptions.length > 0) {
+      this.subscriptions.pop().unsubscribe()
+    }
+  }
+
+  private savedFav$: Observable<{id: string, name: string}[] | null>
+
+  @Effect()
+  public onInitGetFav$: Observable<any>
+
+  private favDataEntries$: Observable<DataEntry[]>
+
+  @Effect()
+  public favDataset$: Observable<any>
+
+  @Effect()
+  public unfavDataset$: Observable<any>
+}
+
+const LOCAL_STORAGE_CONST = {
+  FAV_DATASET: 'fzj.xg.iv.FAV_DATASET'
+}
\ No newline at end of file
diff --git a/src/ui/databrowserModule/databrowser/databrowser.component.ts b/src/ui/databrowserModule/databrowser/databrowser.component.ts
index b3568e039e3be5c046e2571e2d014ed1cac1877d..3b9c6ace0ddeecca61f7fd57fadb5eaa940576d7 100644
--- a/src/ui/databrowserModule/databrowser/databrowser.component.ts
+++ b/src/ui/databrowserModule/databrowser/databrowser.component.ts
@@ -1,6 +1,6 @@
 import { Component, OnDestroy, OnInit, ViewChild, Input } from "@angular/core";
 import { DataEntry } from "src/services/stateStore.service";
-import { Subscription, merge } from "rxjs";
+import { Subscription, merge, Observable } from "rxjs";
 import { DatabrowserService, CountedDataModality } from "../databrowser.service";
 import { ModalityPicker } from "../modalityPicker/modalityPicker.component";
 
@@ -14,6 +14,8 @@ import { ModalityPicker } from "../modalityPicker/modalityPicker.component";
 
 export class DataBrowser implements OnDestroy,OnInit{
 
+  public favedDataentries$: Observable<DataEntry[]>
+
   @Input()
   public regions: any[] = []
 
@@ -55,7 +57,7 @@ export class DataBrowser implements OnDestroy,OnInit{
   constructor(
     private dbService: DatabrowserService
   ){
-
+    this.favedDataentries$ = this.dbService.favedDataentries$
   }
 
   ngOnInit(){
@@ -136,6 +138,14 @@ export class DataBrowser implements OnDestroy,OnInit{
     this.dbService.manualFetchDataset$.next(null)
   }
 
+  saveToFavourite(dataset: DataEntry){
+    this.dbService.saveToFav(dataset)
+  }
+
+  removeFromFavourite(dataset: DataEntry){
+    this.dbService.removeFromFav(dataset)
+  }
+
   public showParcellationList: boolean = false
   
   public filePreviewName: string
@@ -155,10 +165,6 @@ export class DataBrowser implements OnDestroy,OnInit{
   resetFilters(event?:MouseEvent){
     this.clearAll()
   }
-
-  getBackgroundColorStyleFromRegion(region:any) {
-    return this.dbService.getBackgroundColorStyleFromRegion(region)
-  }
 }
 
 export interface DataEntryFilter{
diff --git a/src/ui/databrowserModule/databrowser/databrowser.template.html b/src/ui/databrowserModule/databrowser/databrowser.template.html
index 515a94d8331c6235c7ef253d927c7d159ba597ea..060a49ad118bbaea29a100e86db11bc0b128d144 100644
--- a/src/ui/databrowserModule/databrowser/databrowser.template.html
+++ b/src/ui/databrowserModule/databrowser/databrowser.template.html
@@ -16,7 +16,7 @@
           <span
             *ngFor="let region of regions"
             class="badge badge-secondary mr-1 mw-100">
-            <span [ngStyle]="{backgroundColor:getBackgroundColorStyleFromRegion(region)}" class="dot">
+            <span [ngStyle]="{backgroundColor: (region | regionBackgroundToRgbPipe)}" class="dot">
               
             </span>
             <span class="d-inline-block mw-100 overflow-hidden text-truncate">
@@ -114,15 +114,18 @@
         <dataset-viewer
           class="mt-1"
           *ngFor="let dataset of filteredDataEntry | searchResultPagination : currentPage : hitsPerPage"
+          (saveToFavourite)="saveToFavourite(dataset)"
+          (removeFromFavourite)="removeFromFavourite(dataset)"
           (showPreviewDataset)="onShowPreviewDataset($event)"
-          [dataset]="dataset">
+          [dataset]="dataset"
+          [isFaved]="favedDataentries$ | async | datasetIsFaved : dataset">
           <div regionTagsContainer>
 
             <!-- TODO may want to separate the region badge into a separate component -->
             <span
               *ngFor="let region of dataset.parcellationRegion"
               class="badge badge-secondary mr-1 mw-100">
-              <span [ngStyle]="{backgroundColor:getBackgroundColorStyleFromRegion(region)}" class="dot">
+              <span [ngStyle]="{backgroundColor:(region | regionBackgroundToRgbPipe)}" class="dot">
                 
               </span>
               <span class="d-inline-block mw-100 overflow-hidden text-truncate">
diff --git a/src/ui/databrowserModule/datasetViewer/datasetViewer.component.ts b/src/ui/databrowserModule/datasetViewer/datasetViewer.component.ts
index 7be8d563234f7e854b1bda067e07a1a758471718..aea172ffcc434d3fef4bc11c479ef2333a0e448c 100644
--- a/src/ui/databrowserModule/datasetViewer/datasetViewer.component.ts
+++ b/src/ui/databrowserModule/datasetViewer/datasetViewer.component.ts
@@ -9,6 +9,7 @@ import { DataEntry } from "src/services/stateStore.service";
 
 export class DatasetViewerComponent{
   @Input() dataset : DataEntry
+  @Input() isFaved: boolean
   
   @Output() showPreviewDataset: EventEmitter<{datasetName:string, event:MouseEvent}> = new EventEmitter()
   @ViewChild('kgrRef', {read:ElementRef}) kgrRef: ElementRef
@@ -39,4 +40,21 @@ export class DatasetViewerComponent{
   get kgReference(): string[] {
     return this.dataset.kgReference.map(ref => `https://doi.org/${ref}`)
   }
+
+  /**
+   * Dummy functions, the store.dispatch is the important function
+   */
+  @Output()
+  saveToFavourite: EventEmitter<boolean> = new EventEmitter()
+
+  @Output()
+  removeFromFavourite: EventEmitter<boolean> = new EventEmitter()
+
+  saveToFav(){
+    this.saveToFavourite.emit()
+  }
+
+  removeFromFav(){
+    this.removeFromFavourite.emit()
+  }
 }
\ No newline at end of file
diff --git a/src/ui/databrowserModule/datasetViewer/datasetViewer.template.html b/src/ui/databrowserModule/datasetViewer/datasetViewer.template.html
index c1248259e4ac13b17b3395b63ef41d1d3396b277..6b19fd16aa4248e039dfdb96a9ef8ca7af1f8fc5 100644
--- a/src/ui/databrowserModule/datasetViewer/datasetViewer.template.html
+++ b/src/ui/databrowserModule/datasetViewer/datasetViewer.template.html
@@ -30,6 +30,13 @@
     [hoverable]="{translateY:-3}">
     <i class="fas fa-eye"></i>
   </div>
+
+  <div
+    (click)="isFaved ? removeFromFav() : saveToFav()"
+    [class]="(isFaved ? 'text-primary' : 'text-muted') + ' ds-container ml-1 p-2 preview-container d-flex align-items-center'"
+    [hoverable]="{translateY:-3}">
+    <i class="fas fa-star"></i>
+  </div>
 </div>
 
 <ng-template #defaultDisplay>
diff --git a/src/ui/databrowserModule/kgSingleDatasetService.service.ts b/src/ui/databrowserModule/kgSingleDatasetService.service.ts
index 31738eee7c4f55724f35c36dae69372b629b2b18..551b2cf34d1b2c7f70b93e085371c910c0ef35bf 100644
--- a/src/ui/databrowserModule/kgSingleDatasetService.service.ts
+++ b/src/ui/databrowserModule/kgSingleDatasetService.service.ts
@@ -7,7 +7,7 @@ export class KgSingleDatasetService {
   constructor(private constantService: AtlasViewerConstantsServices) {
   }
 
-  public getInfoFromKg({ kgId, kgSchema }: KgQueryInterface) {
+  public getInfoFromKg({ kgId, kgSchema = 'minds/core/dataset/v1.0.0' }: Partial<KgQueryInterface>) {
     const _url = new URL(`${this.constantService.backendUrl}datasets/kgInfo`)
     const searchParam = _url.searchParams
     searchParam.set('kgSchema', kgSchema)
@@ -19,7 +19,7 @@ export class KgSingleDatasetService {
       })
   }
 
-  public downloadZipFromKg({ kgSchema, kgId } : KgQueryInterface, filename = 'download'){
+  public downloadZipFromKg({ kgSchema = 'minds/core/dataset/v1.0.0', kgId } : Partial<KgQueryInterface>, filename = 'download'){
     const _url = new URL(`${this.constantService.backendUrl}datasets/downloadKgFiles`)
     const searchParam = _url.searchParams
     searchParam.set('kgSchema', kgSchema)
diff --git a/src/ui/databrowserModule/util/datasetIsFaved.pipe.ts b/src/ui/databrowserModule/util/datasetIsFaved.pipe.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2befe6fed9e1be922ab987d998a8a274ceb2d3d6
--- /dev/null
+++ b/src/ui/databrowserModule/util/datasetIsFaved.pipe.ts
@@ -0,0 +1,11 @@
+import { PipeTransform, Pipe } from "@angular/core";
+import { DataEntry } from "src/services/stateStore.service";
+
+@Pipe({
+  name: 'datasetIsFaved'
+})
+export class DatasetIsFavedPipe implements PipeTransform{
+  public transform(favedDataEntry: DataEntry[], dataentry: DataEntry):boolean{
+    return favedDataEntry.findIndex(ds => ds.id === dataentry.id) >= 0
+  }
+}
\ No newline at end of file
diff --git a/src/ui/databrowserModule/util/regionBackgroundToRgb.pipe.ts b/src/ui/databrowserModule/util/regionBackgroundToRgb.pipe.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4a03cd9dc7230bfbab69b5a4f19866e249914c80
--- /dev/null
+++ b/src/ui/databrowserModule/util/regionBackgroundToRgb.pipe.ts
@@ -0,0 +1,13 @@
+import { Pipe, PipeTransform } from "@angular/core";
+
+@Pipe({
+  name: 'regionBackgroundToRgbPipe'
+})
+
+export class RegionBackgroundToRgbPipe implements PipeTransform{
+  public transform(region = null): string{
+    return region && region.rgb
+      ? `rgb(${region.rgb.join(',')})`
+      : 'white'
+  }
+}
\ No newline at end of file
diff --git a/src/ui/layerbrowser/layerbrowser.component.ts b/src/ui/layerbrowser/layerbrowser.component.ts
index 18c2cb75b7971ea8e4a7e3a15c9fa4787e49308f..35178d86ea0c6322ccf0f799cc2eee64c7c701ae 100644
--- a/src/ui/layerbrowser/layerbrowser.component.ts
+++ b/src/ui/layerbrowser/layerbrowser.component.ts
@@ -1,9 +1,9 @@
-import { Component,  OnDestroy } from "@angular/core";
+import { Component,  OnDestroy, Input, Pipe, PipeTransform } from "@angular/core";
 import { NgLayerInterface } from "../../atlasViewer/atlasViewer.component";
 import { Store, select } from "@ngrx/store";
 import { ViewerStateInterface, isDefined, REMOVE_NG_LAYER, FORCE_SHOW_SEGMENT, safeFilter, getNgIds } from "../../services/stateStore.service";
-import { Subscription, Observable } from "rxjs";
-import { filter, map } from "rxjs/operators";
+import { Subscription, Observable, combineLatest } from "rxjs";
+import { filter, map, shareReplay, tap } from "rxjs/operators";
 import { AtlasViewerConstantsServices } from "src/atlasViewer/atlasViewer.constantService.service";
 
 @Component({
@@ -20,13 +20,15 @@ export class LayerBrowser implements OnDestroy{
   /**
    * TODO make untangle nglayernames and its dependency on ng
    */
-  loadedNgLayers$: Observable<NgLayerInterface[]>
-  lockedLayers : string[] = []
+  public loadedNgLayers$: Observable<NgLayerInterface[]>
+  public lockedLayers : string[] = []
+
+  public nonBaseNgLayers$: Observable<NgLayerInterface[]>
 
   public forceShowSegmentCurrentState : boolean | null = null
   public forceShowSegment$ : Observable<boolean|null>
   
-  public ngLayers$: Observable<any>
+  public ngLayers$: Observable<string[]>
   public advancedMode: boolean = false
 
   private subscriptions : Subscription[] = []
@@ -35,6 +37,11 @@ export class LayerBrowser implements OnDestroy{
   /* TODO temporary measure. when datasetID can be used, will use  */
   public fetchedDataEntries$ : Observable<any>
 
+  @Input()
+  showPlaceholder: boolean = true
+
+  darktheme$: Observable<boolean>
+
   constructor(
     private store : Store<ViewerStateInterface>,
     private constantsService: AtlasViewerConstantsServices){
@@ -64,6 +71,22 @@ export class LayerBrowser implements OnDestroy{
        */
       map(arr => arr.filter(v => !!v))
     )
+
+    this.loadedNgLayers$ = this.store.pipe(
+      select('viewerState'),
+      select('loadedNgLayers')
+    )
+
+    this.nonBaseNgLayers$ = combineLatest(
+      this.ngLayers$,
+      this.loadedNgLayers$
+    ).pipe(
+      map(([baseNgLayerNames, loadedNgLayers]) => {
+        const baseNameSet = new Set(baseNgLayerNames)
+        return loadedNgLayers.filter(l => !baseNameSet.has(l.name))
+      })
+    )
+
     /**
      * TODO
      * this is no longer populated
@@ -80,9 +103,9 @@ export class LayerBrowser implements OnDestroy{
       map(state => state.forceShowSegment)
     )
 
-    this.loadedNgLayers$ = this.store.pipe(
-      select('viewerState'),
-      select('loadedNgLayers')
+
+    this.darktheme$ = this.constantsService.darktheme$.pipe(
+      shareReplay(1)
     )
 
     this.subscriptions.push(
@@ -128,6 +151,9 @@ export class LayerBrowser implements OnDestroy{
       return
     }
 
+    /**
+     * TODO perhaps useEffects ?
+     */
     this.store.dispatch({
       type : FORCE_SHOW_SEGMENT,
       forceShowSegment : this.forceShowSegmentCurrentState === null
@@ -151,6 +177,9 @@ export class LayerBrowser implements OnDestroy{
     })
   }
 
+  /**
+   * TODO use observable and pipe to make this more perf
+   */
   segmentationTooltip(){
     return `toggle segments visibility: 
     ${this.forceShowSegmentCurrentState === true ? 'always show' : this.forceShowSegmentCurrentState === false ? 'always hide' : 'auto'}`
@@ -169,4 +198,16 @@ export class LayerBrowser implements OnDestroy{
   get isMobile(){
     return this.constantsService.mobile
   }
+
+  public matTooltipPosition: string = 'below'
 }
+
+@Pipe({
+  name: 'lockedLayerBtnClsPipe'
+})
+
+export class LockedLayerBtnClsPipe implements PipeTransform{
+  public transform(ngLayer:NgLayerInterface, lockedLayers?: string[]): boolean{
+    return (lockedLayers && new Set(lockedLayers).has(ngLayer.name)) || false
+  }
+}
\ No newline at end of file
diff --git a/src/ui/layerbrowser/layerbrowser.style.css b/src/ui/layerbrowser/layerbrowser.style.css
index 83bf14bb66993088b1d86f607e24784f07926bd4..495211b5c91f0746e1c4d522c18a8d3dfdb3a835 100644
--- a/src/ui/layerbrowser/layerbrowser.style.css
+++ b/src/ui/layerbrowser/layerbrowser.style.css
@@ -16,11 +16,6 @@ div[body]
   background-color:rgba(0, 0, 0, 0.1);
 }
 
-.muted-text
-{
-  text-decoration: line-through;
-}
-
 .layerContainer
 {
   display: flex;
diff --git a/src/ui/layerbrowser/layerbrowser.template.html b/src/ui/layerbrowser/layerbrowser.template.html
index 6ac015c163020f210e8adce0b33e9da34d9be011..b5d5c8e45f37ff1e3ca3fe0de68e1edce3a14c1c 100644
--- a/src/ui/layerbrowser/layerbrowser.template.html
+++ b/src/ui/layerbrowser/layerbrowser.template.html
@@ -1,71 +1,59 @@
-<ng-container *ngIf="ngLayers$ | async | filterNgLayer : (loadedNgLayers$ | async) as filteredNgLayers; else noLayerPlaceHolder">
-  <ng-container *ngIf="filteredNgLayers.length > 0; else noLayerPlaceHolder">
-    <div
-      class="layerContainer overflow-hidden"
-      *ngFor = "let ngLayer of filteredNgLayers">
-    
+<ng-container *ngIf="nonBaseNgLayers$ | async as nonBaseNgLayers; else noLayerPlaceHolder">
+  <mat-list *ngIf="nonBaseNgLayers.length > 0; else noLayerPlaceHolder">
+    <mat-list-item *ngFor="let ngLayer of nonBaseNgLayers">
+
       <!-- toggle visibility -->
-      <div class="btnWrapper">
-        <div
-          container = "body"
-          placement = "bottom"
-          [tooltip] = "checkLocked(ngLayer) ? 'base layer cannot be hidden' : 'toggle visibility'"
-          (click) = "checkLocked(ngLayer) ? null : toggleVisibility(ngLayer)"
-          class="btn btn-sm btn-outline-secondary rounded-circle">
-          <i [ngClass] = "checkLocked(ngLayer) ? 'fas fa-lock muted' :ngLayer.visible ? 'far fa-eye' : 'far fa-eye-slash'">
-          </i>
-        </div>
-      </div>
+      
+      <button
+        [matTooltipPosition]="matTooltipPosition"
+        [matTooltip]="(ngLayer | lockedLayerBtnClsPipe : lockedLayers) ? 'base layer cannot be hidden' : 'toggle visibility'"
+        (click)="toggleVisibility(ngLayer)"
+        mat-icon-button
+        [disabled]="ngLayer | lockedLayerBtnClsPipe : lockedLayers"
+        [color]="ngLayer.visible ? 'primary' : null">
+        <i [ngClass]="(ngLayer | lockedLayerBtnClsPipe : lockedLayers) ? 'fas fa-lock muted' : ngLayer.visible ? 'far fa-eye' : 'far fa-eye-slash'">
+        </i>
+      </button>
 
       <!-- advanced mode only: toggle force show segmentation -->
-      <div class="btnWrapper">
-        <div
-          *ngIf="advancedMode"
-          container="body"
-          placement="bottom"
-          [tooltip]="ngLayer.type === 'segmentation' ? segmentationTooltip() : 'only segmentation layer can hide/show segments'"
-          #forceSegment="bs-tooltip"
-          (click)="forceSegment.hide();toggleForceShowSegment(ngLayer)"
-          class="btn btn-sm btn-outline-secondary rounded-circle">
-          <i 
-            class="fas" 
-            [ngClass]="ngLayer.type === 'segmentation' ? ('fa-th-large ' + segmentationAdditionalClass) : 'fa-lock muted' ">
-      
-          </i>
-        </div>
-      </div>
+      <button
+        *ngIf="advancedMode"
+        [matTooltipPosition]="matTooltipPosition"
+        [matTooltip]="ngLayer.type === 'segmentation' ? segmentationTooltip() : 'only segmentation layer can hide/show segments'"
+        (click)="toggleForceShowSegment(ngLayer)"
+        mat-icon-button>
+        <i 
+          class="fas" 
+          [ngClass]="ngLayer.type === 'segmentation' ? ('fa-th-large ' + segmentationAdditionalClass) : 'fa-lock muted' ">
+    
+        </i>
+      </button>
 
       <!-- remove layer -->
-      <div class="btnWrapper">
-        <div
-          container="body"
-          placement="bottom"
-          [tooltip]="checkLocked(ngLayer) ? 'base layers cannot be removed' : 'remove layer'"
-          (click)="removeLayer(ngLayer)"
-          class="btn btn-sm btn-outline-secondary rounded-circle">
-          <i [ngClass]="checkLocked(ngLayer) ? 'fas fa-lock muted' : 'far fa-times-circle'">
-          </i>
-        </div>
-      </div>
+      <button
+        color="warn"
+        mat-icon-button
+        (click)="removeLayer(ngLayer)"
+        [disabled]="ngLayer | lockedLayerBtnClsPipe : lockedLayers"
+        [matTooltip]="(ngLayer | lockedLayerBtnClsPipe : lockedLayers) ? 'base layers cannot be removed' : 'remove layer'">
+        <i [class]="(ngLayer | lockedLayerBtnClsPipe : lockedLayers) ? 'fas fa-lock muted' : 'fas fa-trash'">
+        </i>
+      </button>
 
       <!-- layer description -->
-      <panel-component [ngClass]="{'muted-text muted' : !classVisible(ngLayer)}">
-    
-        <div heading>
-          {{ ngLayer.name | getLayerNameFromDatasets : (fetchedDataEntries$ | async) }}
-        </div>
-    
-        <div bodyy>
-          {{ ngLayer.source }}
-        </div>
-      </panel-component>
-    </div>
-  </ng-container>
+      <div
+        [matTooltipPosition]="matTooltipPosition"
+        [matTooltip]="ngLayer.name | getFilenamePipe "
+        [class]="((darktheme$ | async) ? 'text-light' : 'text-dark') + ' text-truncate'">
+        {{ ngLayer.name | getFilenamePipe | getFileExtension }}
+      </div>
+    </mat-list-item>
+  </mat-list>
 </ng-container>
 
 <!-- fall back when no layers are showing -->
 <ng-template #noLayerPlaceHolder>
-  <h5 class="noLayerPlaceHolder text-muted">
+  <small *ngIf="showPlaceholder" class="noLayerPlaceHolder text-muted">
     No additional layers added.
-  </h5>
+  </small>
 </ng-template>
\ No newline at end of file
diff --git a/src/ui/menuicons/menuicons.component.ts b/src/ui/menuicons/menuicons.component.ts
index c488a4e5253d88b9e1b5bea7a3abfa438952ac6a..3ff7f8b1db939b83b0fb91d7f057aa84841c0a04 100644
--- a/src/ui/menuicons/menuicons.component.ts
+++ b/src/ui/menuicons/menuicons.component.ts
@@ -1,15 +1,17 @@
-import { Component, ComponentRef, Injector, ComponentFactory, ComponentFactoryResolver, AfterViewInit } from "@angular/core";
+import { Component, ComponentRef, Injector, ComponentFactory, ComponentFactoryResolver } from "@angular/core";
 
 import { WidgetServices } from "src/atlasViewer/widgetUnit/widgetService.service";
 import { WidgetUnit } from "src/atlasViewer/widgetUnit/widgetUnit.component";
-import { LayerBrowser } from "src/ui/layerbrowser/layerbrowser.component";
 import { DataBrowser } from "src/ui/databrowserModule/databrowser/databrowser.component";
 import { PluginBannerUI } from "../pluginBanner/pluginBanner.component";
 import { AtlasViewerConstantsServices } from "src/atlasViewer/atlasViewer.constantService.service";
 import { DatabrowserService } from "../databrowserModule/databrowser.service";
-import { PluginServices } from "src/atlasViewer/atlasViewer.pluginService.service";
+import { PluginServices, PluginManifest } from "src/atlasViewer/atlasViewer.pluginService.service";
 import { Store, select } from "@ngrx/store";
-import { Observable } from "rxjs";
+import { Observable, BehaviorSubject, combineLatest, merge, of } from "rxjs";
+import { map, shareReplay, startWith } from "rxjs/operators";
+import { DESELECT_REGIONS, SELECT_REGIONS, CHANGE_NAVIGATION } from "src/services/state/viewerState.store";
+import { ToastService } from "src/services/toastService.service";
 
 @Component({
   selector: 'menu-icons',
@@ -22,6 +24,8 @@ import { Observable } from "rxjs";
 
 export class MenuIconsBar{
 
+  public badgetPosition: string = 'above before'
+
   /**
    * databrowser
    */
@@ -29,13 +33,6 @@ export class MenuIconsBar{
   dataBrowser: ComponentRef<DataBrowser> = null
   dbWidget: ComponentRef<WidgetUnit> = null
 
-  /**
-   * layerBrowser
-   */
-  lbcf: ComponentFactory<LayerBrowser>
-  layerBrowser: ComponentRef<LayerBrowser> = null
-  lbWidget: ComponentRef<WidgetUnit> = null
-
   /**
    * pluginBrowser
    */
@@ -43,11 +40,24 @@ export class MenuIconsBar{
   pluginBanner: ComponentRef<PluginBannerUI> = null
   pbWidget: ComponentRef<WidgetUnit> = null
 
-  get isMobile(){
-    return this.constantService.mobile
+  isMobile: boolean = false
+  mobileRespBtnClass: string
+
+  public darktheme$: Observable<boolean>
+
+  public themedBtnClass$: Observable<string>
+
+  public skeletonBtnClass$: Observable<string>
+
+  public toolBtnClass$: Observable<string>
+  public getKgSearchBtnCls$: Observable<[Set<WidgetUnit>, string]>
+  
+  get darktheme(){
+    return this.constantService.darktheme
   }
 
   public selectedTemplate$: Observable<any>
+  public selectedRegions$: Observable<any[]>
 
   constructor(
     private widgetServices:WidgetServices,
@@ -56,19 +66,63 @@ export class MenuIconsBar{
     public dbService: DatabrowserService,
     cfr: ComponentFactoryResolver,
     public pluginServices:PluginServices,
-    store: Store<any>
+    private store: Store<any>,
+    private toastService: ToastService
   ){
 
+    this.isMobile = this.constantService.mobile
+    this.mobileRespBtnClass = this.constantService.mobile ? 'btn-lg' : 'btn-sm'
+
     this.dbService.createDatabrowser = this.clickSearch.bind(this)
 
     this.dbcf = cfr.resolveComponentFactory(DataBrowser)
-    this.lbcf = cfr.resolveComponentFactory(LayerBrowser)
     this.pbcf = cfr.resolveComponentFactory(PluginBannerUI)
 
     this.selectedTemplate$ = store.pipe(
       select('viewerState'),
       select('templateSelected')
     )
+
+    this.selectedRegions$ = store.pipe(
+      select('viewerState'),
+      select('regionsSelected'),
+      startWith([]),
+      shareReplay(1)
+    )
+
+    this.themedBtnClass$ = this.constantService.darktheme$.pipe(
+      map(flag => flag ? 'btn-dark' : 'btn-light' ),
+      shareReplay(1)
+    )
+
+    this.skeletonBtnClass$ = this.constantService.darktheme$.pipe(
+      map(flag => `${this.mobileRespBtnClass} ${flag ? 'text-light' : 'text-dark'}`),
+      shareReplay(1)
+    )
+
+    this.launchedPlugins$ = this.pluginServices.launchedPlugins$.pipe(
+      map(set => Array.from(set)),
+      shareReplay(1)
+    )
+
+    /**
+     * TODO remove dependency on themedBtnClass$
+     */
+    this.getPluginBtnClass$ = combineLatest(
+      this.pluginServices.launchedPlugins$,
+      this.pluginServices.minimisedPlugins$,
+      this.themedBtnClass$
+    )
+
+    this.darktheme$ = this.constantService.darktheme$
+
+    /**
+     * TODO remove dependency on themedBtnClass$
+     */
+    this.getKgSearchBtnCls$ = combineLatest(
+      this.widgetServices.minimisedWindow$,
+      this.themedBtnClass$
+    )
   }
 
   /**
@@ -98,36 +152,8 @@ export class MenuIconsBar{
   }
 
   public catchError(e) {
-    
+    this.constantService.catchError(e)
   }
-
-  public clickLayer(event: MouseEvent){
-
-    if (this.lbWidget) {
-      this.lbWidget.destroy()
-      this.lbWidget = null
-      return
-    }
-    this.layerBrowser = this.lbcf.create(this.injector)
-    this.lbWidget = this.widgetServices.addNewWidget(this.layerBrowser, {
-      exitable: true,
-      persistency: true,
-      state: 'floating',
-      title: 'Layer Browser',
-      titleHTML: '<i class="fas fa-layer-group"></i> Layer Browser'
-    })
-
-    this.lbWidget.onDestroy(() => {
-      this.layerBrowser = null
-      this.lbWidget = null
-    })
-
-    const el = event.currentTarget as HTMLElement
-    const top = el.offsetTop
-    const left = el.offsetLeft + 50
-    this.lbWidget.instance.position = [left, top]
-  }
-
   public clickPlugins(event: MouseEvent){
     if(this.pbWidget) {
       this.pbWidget.destroy()
@@ -154,19 +180,32 @@ export class MenuIconsBar{
     this.pbWidget.instance.position = [left, top]
   }
 
-  get databrowserIsShowing() {
-    return this.dataBrowser !== null
+  public clickPluginIcon(manifest: PluginManifest){
+    this.pluginServices.launchPlugin(manifest)
+      .catch(this.constantService.catchError)
   }
 
-  get layerbrowserIsShowing() {
-    return this.layerBrowser !== null
+  public searchIconClickHandler(wu: WidgetUnit){
+    if (this.widgetServices.isMinimised(wu)) {
+      this.widgetServices.unminimise(wu)
+    } else {
+      this.widgetServices.minimise(wu)
+    }
   }
 
-  get pluginbrowserIsShowing() {
-    return this.pluginBanner !== null
+  public closeWidget(event: MouseEvent, wu:WidgetUnit){
+    event.stopPropagation()
+    this.widgetServices.exitWidget(wu)
   }
 
-  get dataBrowserTitle() {
-    return `Browse`
+  public renameKgSearchWidget(event:MouseEvent, wu: WidgetUnit) {
+    event.stopPropagation()
   }
+
+  public favKgSearch(event: MouseEvent, wu: WidgetUnit) {
+    event.stopPropagation()
+  }
+
+  public getPluginBtnClass$: Observable<[Set<string>, Set<string>, string]>
+  public launchedPlugins$: Observable<string[]>
 }
\ No newline at end of file
diff --git a/src/ui/menuicons/menuicons.style.css b/src/ui/menuicons/menuicons.style.css
index 8a1663369fecdd67c4c7c70ee73774316f49e430..c76c706c7151f5276eaba98168489ee4bfc82dc5 100644
--- a/src/ui/menuicons/menuicons.style.css
+++ b/src/ui/menuicons/menuicons.style.css
@@ -20,4 +20,19 @@
 :host >>> .tooltip.right .tooltip-arrow
 {
   border-right-color: rgba(128, 128, 128, 0.5);
+}
+
+.soh-row > *:not(:first-child)
+{
+  margin-left: 0.1em;
+}
+
+.soh-column > *:not(:first-child)
+{
+  margin-top: 0.1em;
+}
+
+layer-browser
+{
+  max-width: 20em;
 }
\ No newline at end of file
diff --git a/src/ui/menuicons/menuicons.template.html b/src/ui/menuicons/menuicons.template.html
index 214281632fa62839e0a9d3d0fa0b27e10ec18cbf..00b4065a9219b171ebc38c8df0dce6d503eaa460 100644
--- a/src/ui/menuicons/menuicons.template.html
+++ b/src/ui/menuicons/menuicons.template.html
@@ -3,94 +3,309 @@
 
 <!-- hide icons when templates has yet been selected -->
 <ng-template [ngIf]="selectedTemplate$ | async">
-  <div
-    *ngIf="false"
-    [ngClass]="isMobile ? 'btnWrapper-lg' : ''"
-    class="btnWrapper">
+
+  <!-- layer browser -->
+  <sleight-of-hand>
+    <div sleight-of-hand-front>
+      <button
+        [matBadge]="layerBrowser && (layerBrowser.nonBaseNgLayers$ | async)?.length > 0 ? (layerBrowser.nonBaseNgLayers$ | async)?.length : null"
+        [matBadgePosition]="badgetPosition"
+        matBadgeColor="accent"
+        color="primary"
+        mat-icon-button>
+        <i class="fas fa-layer-group"></i>
+      </button>
+    </div>
     <div
-      [tooltip]="dataBrowserTitle"
-      placement="right"
-      (click)="clickSearch($event)"
-      [ngClass]="databrowserIsShowing ? 'btn-primary' : 'btn-secondary'"
-      class="shadow btn btn-sm rounded-circle">
-      <i class="fas fa-search">
-        
-      </i>
+      class="d-flex flex-row align-items-center soh-row"
+      sleight-of-hand-back>
+
+      <button
+        [matBadge]="layerBrowser && (layerBrowser.nonBaseNgLayers$ | async)?.length > 0 ? (layerBrowser.nonBaseNgLayers$ | async)?.length : null"
+        [matBadgePosition]="badgetPosition"
+        matBadgeColor="accent"
+        color="primary"
+        mat-icon-button>
+        <i class="fas fa-layer-group"></i>
+      </button>
+
+      <div class="position-relative">
+
+        <div [ngClass]="{'invisible pe-none': (layerBrowser.nonBaseNgLayers$ | async).length === 0}" class="position-absolute">
+          <mat-card>
+            <layer-browser #layerBrowser>
+            </layer-browser>
+          </mat-card>
+        </div>
+
+        <ng-container *ngIf="(layerBrowser.nonBaseNgLayers$ | async).length === 0" #noNonBaseNgLayerTemplate>
+          <small [class]="((darktheme$ | async) ? 'bg-dark text-light' : 'bg-light text-dark') + ' muted pl-2 pr-2 p-1 text-nowrap'">
+            No additional layers added
+          </small>
+        </ng-container>
+
+        <!-- invisible button to prop up the size of parent block -->
+        <!-- otherwise, sibling block position will be wonky -->
+        <button
+          color="primary"
+          class="invisible pe-none"
+          mat-icon-button>
+          <i class="fas fa-layer-group"></i>
+        </button>
+
+      </div>
+
     </div>
-  </div>
+  </sleight-of-hand>
+  
+  <!-- tools -->
+  <sleight-of-hand>
 
-  <div
-    [ngClass]="isMobile ? 'btnWrapper-lg' : ''"
-    class="btnWrapper">
-    <div
-      tooltip="Layer"
-      placement="right"
-      (click)="clickLayer($event)"
-      [ngClass]="layerbrowserIsShowing ? 'btn-primary' : 'btn-secondary'"
-      class="shadow btn btn-sm rounded-circle">
-      <i class="fas fa-layer-group">
-        
-      </i>
+    <!-- shown icon prior to mouse over -->
+    <div sleight-of-hand-front>
+      <button
+        [matBadgePosition]="badgetPosition"
+        matBadgeColor="accent"
+        [matBadge]="(launchedPlugins$ | async)?.length > 0 ? (launchedPlugins$ | async)?.length : null"
+        mat-icon-button
+        color="primary">
+        <i class="fas fa-tools"></i>
+      </button>
     </div>
-  </div>
 
-  <div
-    *ngIf="false"
-    [ngClass]="isMobile ? 'btnWrapper-lg' : ''"
-    class="btnWrapper">
+    <!-- shown after mouse over -->
     <div
-      tooltip="Plugins"
-      (click)="clickPlugins($event)"
-      placement="right"
-      [ngClass]="pluginbrowserIsShowing ? 'btn-primary' : 'btn-secondary'"
-      class="shadow btn btn-sm rounded-circle">
-      <i class="fas fa-tools">
+      class="d-flex flex-row soh-row align-items-start"
+      sleight-of-hand-back>
+
+      <!-- placeholder icon -->
+      <button
+        [matBadgePosition]="badgetPosition"  
+        matBadgeColor="accent"
+        [matBadge]="(launchedPlugins$ | async)?.length > 0 ? (launchedPlugins$ | async)?.length : null"
+        mat-icon-button
+        color="primary">
+        <i class="fas fa-tools"></i>
+      </button>
+
+      <!-- render all fetched tools -->
+      <div class="d-flex flex-row soh-row">
+
+        <!-- add new tool btn -->
+        <button
+          matTooltip="Add new plugin"
+          matTooltipPosition="below"
+          mat-icon-button
+          color="primary">
+          <i class="fas fa-plus"></i>
+        </button>
         
-      </i>
+        <button
+          *ngFor="let manifest of pluginServices.fetchedPluginManifests"
+          mat-mini-fab
+          matTooltipPosition="below"
+          [matTooltip]="manifest.displayName || manifest.name"
+          [color]="getPluginBtnClass$ | async | pluginBtnFabColorPipe : manifest.name"
+          (click)="clickPluginIcon(manifest)">
+          {{ (manifest.displayName || manifest.name).slice(0, 1) }}
+        </button>
+      </div>
     </div>
-  </div>
+  </sleight-of-hand>
 
-  <div
-    *ngFor="let manifest of pluginServices.fetchedPluginManifests"
-    [tooltip]="manifest.displayName || manifest.name"
-    placement="right"
-    [ngClass]="isMobile ? 'btnWrapper-lg' : ''"
-    class="btnWrapper">
+  <!-- search kg -->
+  <sleight-of-hand>
+    
+    <!-- shown icon prior to mouse over -->
+    <div sleight-of-hand-front>
+      <button
+        mat-icon-button
+        color="primary"
+        [matBadgePosition]="badgetPosition"
+        matBadgeColor="accent"
+        [matBadge]="dbService.instantiatedWidgetUnits.length > 0 ? dbService.instantiatedWidgetUnits.length : null">
+        <i class="fas fa-search"></i>
+      </button>
+    </div>
 
+    <!-- shown after mouse over -->
     <div
-      (click)="pluginServices.launchPlugin(manifest).catch(catchError)"
-      [ngClass]="!pluginServices.launchedPlugins.has(manifest.name) ? 'btn-outline-secondary' : pluginServices.pluginMinimised(manifest) ? 'btn-outline-info' : 'btn-info'"
-      class="shadow btn btn-sm rounded-circle">
-      {{ (manifest.displayName || manifest.name).slice(0, 1) }}
+      sleight-of-hand-back
+      class="d-flex flex-row align-items-center soh-row pe-none">
+
+      <!-- placeholder icon -->
+      <button
+        mat-icon-button
+        color="primary"
+        matBadgeColor="accent"
+        [matBadgePosition]="badgetPosition"
+        [matBadge]="dbService.instantiatedWidgetUnits.length > 0 ? dbService.instantiatedWidgetUnits.length : null">
+        <i class="fas fa-search"></i>
+      </button>
+
+      <!-- only renders if there is at least one search result -->
+      <div
+        *ngIf="dbService.instantiatedWidgetUnits.length > 0; else noKgSearchTemplate"
+        class="position-relative pe-all">
+
+        <div class="position-absolute d-flex flex-column soh-column">
+
+          <!-- render all searched kg -->
+          <sleight-of-hand
+            *ngFor="let wu of dbService.instantiatedWidgetUnits"
+            (click)="searchIconClickHandler(wu)">
+
+            <!-- shown prior to mouseover -->
+            <div sleight-of-hand-front>
+              <button
+                mat-mini-fab
+                [color]="getKgSearchBtnCls$ | async | kgSearchBtnColorPipe : wu">
+                <i class="fas fa-search"></i>  
+              </button>
+            </div>
+
+            <!-- shown on mouse over -->
+            <!-- showing additional information. in this case, name of the kg search -->
+            <div class="d-flex flex-row align-items-center" sleight-of-hand-back>
+
+              <div sleight-of-hand-front>
+                <button
+                  mat-mini-fab
+                  [color]="getKgSearchBtnCls$ | async | kgSearchBtnColorPipe : wu">
+                  <i class="fas fa-search"></i>  
+                </button>
+              </div>
+              
+              <!-- on hover, show full name and action possible: rename, close -->
+              <div [class]="((darktheme$ | async) ? 'text-light' : 'text-dark' ) + ' h-0 d-flex flex-row align-items-center'">
+
+                <sleight-of-hand class="ml-1 h-0">
+                  <!-- prior mouse over -->
+                  <div class="h-0 d-flex align-items-center flex-row" sleight-of-hand-front>
+                    <div [class]="((darktheme$ | async) ? 'bg-dark' : 'bg-light' ) + ' muted d-flex flex-row align-items-center'">
+                  
+                      <small class="cursor-default ml-2 text-nowrap">
+                        {{ wu.title }}
+                      </small>
+  
+                      <!-- dummy class to keep height -->
+                      <div
+                        matTooltip="Rename"
+                        matTooltipPosition="below"
+                        [class]="(skeletonBtnClass$ | async) + ' invisible w-0 pe-none'">
+                        <i class="fas fa-edit"></i>
+                      </div>
+                    </div>
+                  </div>
+
+                  <!-- on mouse over -->
+                  <div class="h-0 d-flex align-items-center flex-row" sleight-of-hand-back>
+                    <div [class]="((darktheme$ | async) ? 'bg-dark' : 'bg-light' ) + ' d-flex flex-row align-items-center'">
+                      
+                      <small class="cursor-default ml-2 text-nowrap">
+                        {{ wu.title }}
+                      </small>
+  
+                      <!-- rename -->
+                      <div
+                        (click)="renameKgSearchWidget($event, wu)"
+                        matTooltip="Rename (NYI)"
+                        matTooltipPosition="below"
+                        [class]="(skeletonBtnClass$ | async) + ' text-muted'">
+                        <i class="fas fa-edit"></i>
+                      </div>
+
+                      <!-- star -->
+                      <div
+                        (click)="favKgSearch($event, wu)"
+                        matTooltip="Favourite (NYI)"
+                        matTooltipPosition="below"
+                        [class]="(skeletonBtnClass$ | async) + ' text-muted'">
+                        <i class="far fa-star"></i>
+                      </div>
+  
+                      <!-- close -->
+                      <div
+                        (click)="closeWidget($event, wu)"
+                        matTooltip="Close"
+                        matTooltipPosition="below"
+                        [class]="skeletonBtnClass$ | async">
+                        <i class="fas fa-times"></i>
+                      </div>
+                    </div>
+                  </div>
+                </sleight-of-hand> 
+              </div>
+            </div>
+          </sleight-of-hand>
+        </div>
+
+        <!-- invisible icon to keep height of the otherwise unstable flex block -->
+        <div class="invisible pe-none">
+          <button mat-icon-button>
+            <i class="fas fa-search"></i>
+          </button>
+        </div>
+      </div>
+
+      <!-- displayed when no search is visible -->
+      <ng-template #noKgSearchTemplate>
+        <small [class]="((darktheme$ | async) ? 'bg-dark text-light' : 'bg-light text-dark') + ' muted pl-2 pr-2 p-1 text-nowrap'">
+          Right click any area to search
+        </small>
+      </ng-template>
     </div>
-  </div>
 
-  <div
-    *ngFor="let manifest of pluginServices.orphanPlugins"
-    [tooltip]="manifest.displayName || manifest.name"
-    placement="right"
-    [ngClass]="isMobile ? 'btnWrapper-lg' : ''"
-    class="btnWrapper">
+  </sleight-of-hand>
 
-    <div
-      (click)="pluginServices.launchPlugin(manifest).catch(catchError)"
-      [ngClass]="pluginServices.pluginMinimised(manifest) ? 'btn-outline-info' : 'btn-info'"
-      class="shadow btn btn-sm rounded-circle">
-      {{ (manifest.displayName || manifest.name).slice(0, 1) }}
+  <!-- selected regions -->
+  <sleight-of-hand
+    [doNotClose]="viewerStateController.focused">
+
+    <!-- shown prior to mouse over -->
+    <div sleight-of-hand-front>
+      <button
+        [matBadge]="(selectedRegions$ | async).length > 0 ? (selectedRegions$ | async).length : null"
+        [matBadgePosition]="badgetPosition"
+        matBadgeColor="accent"
+        mat-icon-button
+        color="primary">
+        <i class="fas fa-brain"></i>
+      </button>
     </div>
-  </div>
-
-  <div
-    *ngFor="let wu of dbService.instantiatedWidgetUnits"
-    [ngClass]="isMobile ? 'btnWrapper-lg' : ''"
-    placement="right"
-    [tooltip]="wu.title"
-    class="btnWrapper">
+
+    <!-- shown upon mouseover -->
     <div
-      (click)="widgetServices.minimisedWindow.delete(wu)"
-      [ngClass]="widgetServices.minimisedWindow.has(wu) ? 'btn-outline-info' : 'btn-info'"
-      class="shadow btn btn-sm rounded-circle">
-      <i class="fas fa-search"></i>
+      sleight-of-hand-back
+      class="d-flex flex-row align-items-center soh-row">
+
+      <!-- place holder icon -->
+      <button
+        [matBadge]="(selectedRegions$ | async).length > 0 ? (selectedRegions$ | async).length : null"
+        [matBadgePosition]="badgetPosition"
+        matBadgeColor="accent"
+        mat-icon-button
+        color="primary">
+        <i class="fas fa-brain"></i>
+      </button>
+
+      <div class="position-relative">
+
+        <div [class]="((darktheme$ | async) ? 'bg-dark' : 'bg-light') + ' position-absolute card'">
+          <viewer-state-controller #viewerStateController></viewer-state-controller>
+        </div>
+        
+        <!-- invisible icon to keep height of the otherwise unstable flex block -->
+        <div class="invisible pe-none">
+          <i class="fas fa-brain"></i>
+        </div>
+      </div>
+
+      <ng-template #noBrainRegionSelected>
+        <small [class]="((darktheme$ | async) ? 'bg-dark text-light' : 'bg-light text-dark') + ' muted pl-2 pr-2 p-1 text-nowrap'">
+          Double click any brain region to select it.
+        </small>
+      </ng-template>
     </div>
-  </div>
+  </sleight-of-hand>
 </ng-template>
\ No newline at end of file
diff --git a/src/ui/nehubaContainer/nehubaContainer.component.ts b/src/ui/nehubaContainer/nehubaContainer.component.ts
index 44dfc715979eabe3d800db61441c919171ecb669..3112fa91a944a0766c871af90034af33c989a715 100644
--- a/src/ui/nehubaContainer/nehubaContainer.component.ts
+++ b/src/ui/nehubaContainer/nehubaContainer.component.ts
@@ -1,17 +1,22 @@
 import { Component, ViewChild, ViewContainerRef, ComponentFactoryResolver, ComponentFactory, ComponentRef, OnInit, OnDestroy, ElementRef } from "@angular/core";
 import { NehubaViewerUnit } from "./nehubaViewer/nehubaViewer.component";
 import { Store, select } from "@ngrx/store";
-import { ViewerStateInterface, safeFilter, CHANGE_NAVIGATION, isDefined, USER_LANDMARKS, ADD_NG_LAYER, REMOVE_NG_LAYER, NgViewerStateInterface, MOUSE_OVER_LANDMARK, SELECT_LANDMARKS, Landmark, PointLandmarkGeometry, PlaneLandmarkGeometry, OtherLandmarkGeometry, getNgIds, getMultiNgIdsRegionsLabelIndexMap, generateLabelIndexId } from "../../services/stateStore.service";
+import { ViewerStateInterface, safeFilter, CHANGE_NAVIGATION, isDefined, USER_LANDMARKS, ADD_NG_LAYER, REMOVE_NG_LAYER, NgViewerStateInterface, MOUSE_OVER_LANDMARK, SELECT_LANDMARKS, Landmark, PointLandmarkGeometry, PlaneLandmarkGeometry, OtherLandmarkGeometry, getNgIds, getMultiNgIdsRegionsLabelIndexMap, generateLabelIndexId, DataEntry } from "../../services/stateStore.service";
 import { Observable, Subscription, fromEvent, combineLatest, merge } from "rxjs";
-import { filter,map, take, scan, debounceTime, distinctUntilChanged, switchMap, skip, withLatestFrom, buffer, tap, throttleTime, bufferTime } from "rxjs/operators";
+import { filter,map, take, scan, debounceTime, distinctUntilChanged, switchMap, skip, withLatestFrom, buffer, tap, switchMapTo, shareReplay, throttleTime, bufferTime, startWith } from "rxjs/operators";
 import { AtlasViewerAPIServices, UserLandmark } from "../../atlasViewer/atlasViewer.apiService.service";
 import { timedValues } from "../../util/generator";
 import { AtlasViewerConstantsServices } from "../../atlasViewer/atlasViewer.constantService.service";
 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 { NEHUBA_READY, H_ONE_THREE, V_ONE_THREE, FOUR_PANEL, SINGLE_PANEL } from "src/services/state/ngViewerState.store";
 import { MOUSE_OVER_SEGMENTS } from "src/services/state/uiState.store";
-import { SELECT_REGIONS_WITH_ID, NEHUBA_LAYER_CHANGED } from "src/services/state/viewerState.store";
+import { getHorizontalOneThree, getVerticalOneThree, getFourPanel, getSinglePanel } from "./util";
+import { SELECT_REGIONS_WITH_ID, NEHUBA_LAYER_CHANGED, VIEWERSTATE_ACTION_TYPES } from "src/services/state/viewerState.store";
+import { MatBottomSheet, MatButton } from "@angular/material";
+import { DATASETS_ACTIONS_TYPES } from "src/services/state/dataStore.store";
+import { KgSingleDatasetService } from "../databrowserModule/kgSingleDatasetService.service";
+import { getIdFromDataEntry } from "../databrowserModule/databrowser.service";
 
 const getProxyUrl = (ngUrl) => `nifti://${BACKEND_URL}preview/file?fileUrl=${encodeURIComponent(ngUrl.replace(/^nifti:\/\//,''))}`
 const getProxyOther = ({source}) => /AUTH_227176556f3c4bb38df9feea4b91200c/.test(source)
@@ -85,10 +90,6 @@ const scanFn : (acc:[boolean, boolean, boolean], curr: CustomEvent) => [boolean,
 export class NehubaContainer implements OnInit, OnDestroy{
 
   @ViewChild('container',{read:ViewContainerRef}) container : ViewContainerRef
-  @ViewChild('[pos00]',{read:ElementRef}) topleft : ElementRef
-  @ViewChild('[pos01]',{read:ElementRef}) topright : ElementRef
-  @ViewChild('[pos10]',{read:ElementRef}) bottomleft : ElementRef
-  @ViewChild('[pos11]',{read:ElementRef}) bottomright : ElementRef
 
   private nehubaViewerFactory : ComponentFactory<NehubaViewerUnit>
 
@@ -135,21 +136,31 @@ 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[] = []
 
   public nanometersToOffsetPixelsFn : Function[] = []
   private viewerConfig : Partial<ViewerConfiguration> = {}
 
+  private viewPanels: [HTMLElement, HTMLElement, HTMLElement, HTMLElement] = [null, null, null, null]
+  public panelMode$: Observable<string>
+  private redrawLayout$: Observable<[string, string]>
+  public favDataEntries$: Observable<DataEntry[]>
+
   constructor(
     private constantService : AtlasViewerConstantsServices,
     private apiService :AtlasViewerAPIServices,
     private csf:ComponentFactoryResolver,
     private store : Store<ViewerStateInterface>,
-    private elementRef : ElementRef
+    private elementRef : ElementRef,
+    public bottomSheet: MatBottomSheet,
+    private kgSingleDataset: KgSingleDatasetService
   ){
+    this.favDataEntries$ = this.store.pipe(
+      select('dataStore'),
+      select('favDataEntries')
+    )
+
     this.viewerPerformanceConfig$ = this.store.pipe(
       select('viewerConfigState'),
       /**
@@ -162,6 +173,25 @@ export class NehubaContainer implements OnInit, OnDestroy{
       filter(() => isDefined(this.nehubaViewer) && isDefined(this.nehubaViewer.nehubaViewer))
     )
 
+    this.redrawLayout$ = this.store.pipe(
+      select('ngViewerState'),
+      select('nehubaReady'),
+      distinctUntilChanged(),
+      filter(v => !!v),
+      switchMapTo(combineLatest(
+        this.store.pipe(
+          select('ngViewerState'),
+          select('panelMode'),
+          distinctUntilChanged()
+        ),
+        this.store.pipe(
+          select('ngViewerState'),
+          select('panelOrder'),
+          distinctUntilChanged()
+        )
+      ))
+    )
+
     this.nehubaViewerFactory = this.csf.resolveComponentFactory(NehubaViewerUnit)
 
     this.newViewer$ = this.store.pipe(
@@ -220,13 +250,9 @@ export class NehubaContainer implements OnInit, OnDestroy{
     )
 
     this.userLandmarks$ = this.store.pipe(
-      /* TODO: distinct until changed */
       select('viewerState'),
-      // filter(state => isDefined(state) && isDefined(state.userLandmarks)),
-      map(state => isDefined(state) && isDefined(state.userLandmarks)
-        ? state.userLandmarks
-        : []),
-      distinctUntilChanged(userLmUnchanged)
+      select('userLandmarks'),
+      distinctUntilChanged()
     )
 
     this.onHoverSegments$ = this.store.pipe(
@@ -333,24 +359,10 @@ export class NehubaContainer implements OnInit, OnDestroy{
     ).pipe(
       map(results => results[1] === null ? results[0] : '')
     )
-    
-    /* each time a new viewer is initialised, take the first event to get the translation function */
-    this.newViewer$.pipe(
-      // switchMap(() => fromEvent(this.elementRef.nativeElement, 'sliceRenderEvent')
-      //   .pipe(
-      //     ...takeOnePipe
-      //   )
-      // )
-
-      switchMap(() => pipeFromArray([...takeOnePipe])(fromEvent(this.elementRef.nativeElement, 'sliceRenderEvent')))
-
-
-    ).subscribe((events)=>{
-      [0,1,2].forEach(idx=>this.nanometersToOffsetPixelsFn[idx] = (events[idx] as any).detail.nanometersToOffsetPixels)
-    })
 
     this.sliceViewLoadingMain$ = fromEvent(this.elementRef.nativeElement, 'sliceRenderEvent').pipe(
       scan(scanFn, [null, null, null]),
+      shareReplay(1)
     )
 
     this.sliceViewLoading0$ = this.sliceViewLoadingMain$
@@ -401,14 +413,90 @@ export class NehubaContainer implements OnInit, OnDestroy{
         ? state.layers.findIndex(l => l.mixability === 'nonmixable') >= 0
         : false)
     )
+
+    this.panelMode$ = this.store.pipe(
+      select('ngViewerState'),
+      select('panelMode'),
+      distinctUntilChanged(),
+    )
   }
 
   get isMobile(){
     return this.constantService.mobile
   }
 
+  private removeExistingPanels() {
+    const element = this.nehubaViewer.nehubaViewer.ngviewer.layout.container.componentValue.element as HTMLElement
+    while (element.childElementCount > 0) {
+      element.removeChild(element.firstElementChild)
+    }
+    return element
+  }
+
   ngOnInit(){
 
+    /* each time a new viewer is initialised, take the first event to get the translation function */
+    this.subscriptions.push(
+      this.newViewer$.pipe(
+        switchMap(() => pipeFromArray([...takeOnePipe])(fromEvent(this.elementRef.nativeElement, 'sliceRenderEvent')))
+      ).subscribe((events)=>{
+        for (const idx in [0,1,2]) {
+          const ev = events[idx] as CustomEvent
+          this.viewPanels[idx] = ev.target as HTMLElement
+          this.nanometersToOffsetPixelsFn[idx] = ev.detail.nanometersToOffsetPixels
+        }
+      })
+    )
+
+    this.subscriptions.push(
+      this.newViewer$.pipe(
+        switchMapTo(fromEvent(this.elementRef.nativeElement, 'perpspectiveRenderEvent').pipe(
+          take(1)
+        )),
+      ).subscribe(ev => this.viewPanels[3] = ((ev as CustomEvent).target) as HTMLElement)
+    )
+
+    this.subscriptions.push(
+      this.redrawLayout$.subscribe(([mode, panelOrder]) => {
+        const viewPanels = panelOrder.split('').map(v => Number(v)).map(idx => this.viewPanels[idx]) as [HTMLElement, HTMLElement, HTMLElement, HTMLElement]
+        /**
+         * TODO be smarter with event stream
+         */
+        if (!this.nehubaViewer) return
+
+        switch (mode) {
+          case H_ONE_THREE:{
+            const element = this.removeExistingPanels()
+            const newEl = getHorizontalOneThree(viewPanels)
+            element.appendChild(newEl)
+            break;
+          }
+          case V_ONE_THREE:{
+            const element = this.removeExistingPanels()
+            const newEl = getVerticalOneThree(viewPanels)
+            element.appendChild(newEl)
+            break;
+          }
+          case FOUR_PANEL: {
+            const element = this.removeExistingPanels()
+            const newEl = getFourPanel(viewPanels)
+            element.appendChild(newEl)
+            break;
+          }
+          case SINGLE_PANEL: {
+            const element = this.removeExistingPanels()
+            const newEl = getSinglePanel(viewPanels)
+            element.appendChild(newEl)
+            break;
+          }
+          default: 
+        }
+        for (const panel of viewPanels){
+          (panel as HTMLElement).classList.add('neuroglancer-panel')
+        }
+      })
+    )
+
     this.subscriptions.push(
       this.viewerPerformanceConfig$.subscribe(config => {
         this.nehubaViewer.applyPerformanceConfig(config)
@@ -454,10 +542,7 @@ export class NehubaContainer implements OnInit, OnDestroy{
     )
 
     this.subscriptions.push(
-      this.userLandmarks$.pipe(
-        // distinctUntilChanged((old,new) => )
-      ).subscribe(landmarks => {
-        this.userLandmarks = landmarks
+      this.userLandmarks$.subscribe(landmarks => {
         if(this.nehubaViewer){
           this.nehubaViewer.updateUserLandmarks(landmarks)
         }
@@ -690,11 +775,13 @@ export class NehubaContainer implements OnInit, OnDestroy{
 
     this.subscriptions.push(
       this.selectedLandmarks$.pipe(
-        map(lms => lms.map(lm => this.landmarksNameMap.get(lm.name)))
+        map(lms => lms.map(lm => this.landmarksNameMap.get(lm.name))),
+        debounceTime(16)
       ).subscribe(indices => {
         const filteredIndices = indices.filter(v => typeof v !== 'undefined' && v !== null)
-        if(this.nehubaViewer)
+        if(this.nehubaViewer) {
           this.nehubaViewer.spatialLandmarkSelectionChanged(filteredIndices)
+        }
       })
     )
   }
@@ -968,14 +1055,16 @@ export class NehubaContainer implements OnInit, OnDestroy{
         if(!landmarks.every(l => l.position.constructor === Array) || !landmarks.every(l => l.position.every(v => !isNaN(v))) || !landmarks.every(l => l.position.length == 3))
           throw new Error('position needs to be a length 3 tuple of numbers ')
         this.store.dispatch({
-          type: USER_LANDMARKS,
+          type: VIEWERSTATE_ACTION_TYPES.ADD_USERLANDMARKS,
           landmarks : landmarks
         })
       },
-      remove3DLandmarks : ids => {
+      remove3DLandmarks : landmarkIds => {
         this.store.dispatch({
-          type : USER_LANDMARKS,
-          landmarks : this.userLandmarks.filter(l => ids.findIndex(id => id === l.id) < 0)
+          type: VIEWERSTATE_ACTION_TYPES.REMOVE_USER_LANDMARKS,
+          payload: {
+            landmarkIds
+          }
         })
       },
       hideSegment : (labelIndex) => {
@@ -1154,6 +1243,19 @@ export class NehubaContainer implements OnInit, OnDestroy{
     }
   }
 
+  removeFav(event: MouseEvent, ds: DataEntry){
+    this.store.dispatch({
+      type: DATASETS_ACTIONS_TYPES.UNFAV_DATASET,
+      payload: ds
+    })
+  }
+
+  downloadDs(event: MouseEvent, ds: DataEntry, downloadBtn: MatButton){
+    downloadBtn.disabled = true
+    const id = getIdFromDataEntry(ds)
+    this.kgSingleDataset.downloadZipFromKg({kgId: id})
+      .finally(() => downloadBtn.disabled = false)
+  }
 }
 
 export const identifySrcElement = (element:HTMLElement) => {
@@ -1181,7 +1283,6 @@ export const takeOnePipe = [
      * 4 ???
      */
     const key = identifySrcElement(target)
-
     const _ = {}
     _[key] = event
     return Object.assign({},acc,_)
diff --git a/src/ui/nehubaContainer/nehubaContainer.style.css b/src/ui/nehubaContainer/nehubaContainer.style.css
index 301c664de0d831b6e848e7f5af27422c27326749..57a6ad380921978025fdb9692967d716743d5eab 100644
--- a/src/ui/nehubaContainer/nehubaContainer.style.css
+++ b/src/ui/nehubaContainer/nehubaContainer.style.css
@@ -15,6 +15,11 @@ input[navigateInput]
   box-shadow: inset 0px 2px 2px 2px rgba(0,0,0,0.05);
 }
 
+current-layout
+{
+  top: 0;
+  left: 0;
+}
 
 div[landmarkMasterContainer]
 {
@@ -66,7 +71,7 @@ hr
 }
 
 
-div[landmarkMasterContainer] > div > [landmarkContainer] > div.loadingIndicator
+div.loadingIndicator
 {
   left: auto;
   top: auto;
@@ -75,6 +80,7 @@ div[landmarkMasterContainer] > div > [landmarkContainer] > div.loadingIndicator
   margin-right: 0.2em;
   margin-bottom: 0.2em;
   width: 100%;
+  position:absolute;
   height:2em;
   display: flex;
   flex-direction: row-reverse;
@@ -166,3 +172,8 @@ div#scratch-pad
   pointer-events: none;
 }
 
+.load-fav-dataentries-fab
+{
+  right: 0;
+  bottom: 0;
+}
\ No newline at end of file
diff --git a/src/ui/nehubaContainer/nehubaContainer.template.html b/src/ui/nehubaContainer/nehubaContainer.template.html
index c453d302cb382720296e778641c7ef05465fab9f..6ba9a1443ce3e189057eac4e8c347a48fd0a6b52 100644
--- a/src/ui/nehubaContainer/nehubaContainer.template.html
+++ b/src/ui/nehubaContainer/nehubaContainer.template.html
@@ -4,75 +4,47 @@
 <ui-splashscreen (contextmenu)="$event.stopPropagation();" *ngIf="!viewerLoaded">
 </ui-splashscreen>
 
-<div landmarkMasterContainer>
+<!-- spatial landmarks overlay -->
+<!-- loading indicator -->
 
-  <div>
-    <layout-floating-container pos00 landmarkContainer>
-      <nehuba-2dlandmark-unit *ngFor="let spatialData of (selectedPtLandmarks$ | async)"
-        (mouseenter)="handleMouseEnterLandmark(spatialData)" (mouseleave)="handleMouseLeaveLandmark(spatialData)"
-        [highlight]="spatialData.highlight ? spatialData.highlight : false"
-        [fasClass]="spatialData.type === 'userLandmark' ? 'fa-chevron-down' : 'fa-map-marker'"
-        [positionX]="getPositionX(0,spatialData)" [positionY]="getPositionY(0,spatialData)"
-        [positionZ]="getPositionZ(0,spatialData)">
-      </nehuba-2dlandmark-unit>
-
-      <div *ngIf="sliceViewLoading0$ | async" class="loadingIndicator">
-        <div class="spinnerAnimationCircle">
-
-        </div>
-      </div>
-    </layout-floating-container>
+<current-layout class="position-absolute w-100 h-100 d-block pe-none">
+  <div class="w-100 h-100 position-relative" cell-i>
+    <ng-content *ngTemplateOutlet="overlayi"></ng-content>
   </div>
-  <div>
-    <layout-floating-container pos01 landmarkContainer>
-      <nehuba-2dlandmark-unit *ngFor="let spatialData of (selectedPtLandmarks$ | async)"
-        (mouseenter)="handleMouseEnterLandmark(spatialData)" (mouseleave)="handleMouseLeaveLandmark(spatialData)"
-        [highlight]="spatialData.highlight ? spatialData.highlight : false"
-        [fasClass]="spatialData.type === 'userLandmark' ? 'fa-chevron-down' : 'fa-map-marker'"
-        [positionX]="getPositionX(1,spatialData)" [positionY]="getPositionY(1,spatialData)"
-        [positionZ]="getPositionZ(1,spatialData)">
-      </nehuba-2dlandmark-unit>
-
-      <div *ngIf="sliceViewLoading1$ | async" class="loadingIndicator">
-        <div class="spinnerAnimationCircle">
-
-        </div>
-      </div>
-    </layout-floating-container>
+  <div class="w-100 h-100 position-relative" cell-ii>
+    <ng-content *ngTemplateOutlet="overlayii"></ng-content>
   </div>
-  <div>
-    <layout-floating-container pos10 landmarkContainer>
-      <nehuba-2dlandmark-unit *ngFor="let spatialData of (selectedPtLandmarks$ | async)"
-        (mouseenter)="handleMouseEnterLandmark(spatialData)" (mouseleave)="handleMouseLeaveLandmark(spatialData)"
-        [highlight]="spatialData.highlight ? spatialData.highlight : false"
-        [fasClass]="spatialData.type === 'userLandmark' ? 'fa-chevron-down' : 'fa-map-marker'"
-        [positionX]="getPositionX(2,spatialData)" [positionY]="getPositionY(2,spatialData)"
-        [positionZ]="getPositionZ(2,spatialData)">
-      </nehuba-2dlandmark-unit>
-
-      <div *ngIf="sliceViewLoading2$ | async" class="loadingIndicator">
-        <div class="spinnerAnimationCircle">
-
-        </div>
-      </div>
-    </layout-floating-container>
+  <div class="w-100 h-100 position-relative" cell-iii>
+    <ng-content *ngTemplateOutlet="overlayiii"></ng-content>
   </div>
-  <div>
-    <layout-floating-container pos11 landmarkContainer>
-      <div *ngIf="perspectiveViewLoading$ | async" class="loadingIndicator">
-        <div class="spinnerAnimationCircle"></div>
-        <div perspectiveLoadingText>
-          {{ perspectiveViewLoading$ | async }}
-        </div>
-      </div>
-    </layout-floating-container>
+  <div class="w-100 h-100 position-relative" cell-iv>
+    <ng-content *ngTemplateOutlet="overlayiv"></ng-content>
   </div>
-</div>
+</current-layout>
 
 <layout-floating-container *ngIf="viewerLoaded && !isMobile">
+  
+  <!-- tmp fab -->
+  <div class="m-3 load-fav-dataentries-fab position-absolute pe-all">
+    <button
+      (click)="bottomSheet.open(savedDatasets)"
+      [matBadge]="(favDataEntries$ | async)?.length > 0 ? (favDataEntries$ | async)?.length : null "
+      matBadgeColor="accent"
+      matBadgePosition="above before"
+      matTooltip="Favourite datasets"
+      matTooltipPosition="before"
+      mat-fab
+      color="primary">
+      <i class="fas fa-star"></i>
+    </button>
+  </div>
+
   <!-- StatusCard container-->
-  <ui-status-card [selectedTemplate]="selectedTemplate" [isMobile]="isMobile"
-    [onHoverSegmentName]="onHoverSegmentName$ | async" [nehubaViewer]="nehubaViewer">
+  <ui-status-card
+    [selectedTemplate]="selectedTemplate"
+    [isMobile]="isMobile"
+    [onHoverSegmentName]="onHoverSegmentName$ | async"
+    [nehubaViewer]="nehubaViewer">
   </ui-status-card>
 </layout-floating-container>
 
@@ -80,7 +52,10 @@
 
 </div>
 
-<mobile-overlay *ngIf="isMobile && viewerLoaded" [tunableProperties]="tunableMobileProperties"
+<!-- mobile nub, allowing for ooblique slicing in mobile -->
+<mobile-overlay
+  *ngIf="isMobile && viewerLoaded"
+  [tunableProperties]="tunableMobileProperties"
   (deltaValue)="handleMobileOverlayEvent($event)">
   <div class="base" delta>
     <div mobileObliqueGuide class="p-2 mb-4 shadow">
@@ -88,7 +63,9 @@
     </div>
   </div>
   <div class="base" guide>
-    <div mobileObliqueGuide class="p-2 mb-4 shadow">
+    <div
+      mobileObliqueGuide
+      class="p-2 mb-4 shadow">
       <div>
         <i class="fas fa-arrows-alt-v"></i> oblique mode
       </div>
@@ -97,7 +74,126 @@
       </div>
     </div>
   </div>
-  <div (contextmenu)="$event.stopPropagation(); $event.preventDefaul();" mobileObliqueCtrl initiator>
-    <i class="fas fa-globe"></i>
+  <div
+    (contextmenu)="$event.stopPropagation(); $event.preventDefaul();"
+    [ngStyle]="panelMode$ | async | mobileControlNubStylePipe"
+    mobileObliqueCtrl
+    initiator>
+    <button mat-mini-fab color="primary">
+      <i class="fas fa-globe"></i>
+    </button>
   </div>
-</mobile-overlay>
\ No newline at end of file
+</mobile-overlay>
+
+<!-- overlay templates -->
+<!-- inserted using ngTemplateOutlet -->
+<ng-template #overlayi>
+  <layout-floating-container pos00 landmarkContainer>
+    <nehuba-2dlandmark-unit *ngFor="let spatialData of (selectedPtLandmarks$ | async)"
+      (mouseenter)="handleMouseEnterLandmark(spatialData)" (mouseleave)="handleMouseLeaveLandmark(spatialData)"
+      [highlight]="spatialData.highlight ? spatialData.highlight : false"
+      [fasClass]="spatialData.type === 'userLandmark' ? 'fa-chevron-down' : 'fa-map-marker'"
+      [positionX]="getPositionX(0,spatialData)" [positionY]="getPositionY(0,spatialData)"
+      [positionZ]="getPositionZ(0,spatialData)">
+    </nehuba-2dlandmark-unit>
+
+    <div *ngIf="sliceViewLoading0$ | async" class="loadingIndicator">
+      <div class="spinnerAnimationCircle">
+
+      </div>
+    </div>
+  </layout-floating-container>
+</ng-template>
+
+<ng-template #overlayii>
+  <layout-floating-container pos01 landmarkContainer>
+      <nehuba-2dlandmark-unit *ngFor="let spatialData of (selectedPtLandmarks$ | async)"
+        (mouseenter)="handleMouseEnterLandmark(spatialData)" (mouseleave)="handleMouseLeaveLandmark(spatialData)"
+        [highlight]="spatialData.highlight ? spatialData.highlight : false"
+        [fasClass]="spatialData.type === 'userLandmark' ? 'fa-chevron-down' : 'fa-map-marker'"
+        [positionX]="getPositionX(1,spatialData)" [positionY]="getPositionY(1,spatialData)"
+        [positionZ]="getPositionZ(1,spatialData)">
+      </nehuba-2dlandmark-unit>
+
+      <div *ngIf="sliceViewLoading1$ | async" class="loadingIndicator">
+        <div class="spinnerAnimationCircle">
+
+        </div>
+      </div>
+    </layout-floating-container>
+</ng-template>
+
+<ng-template #overlayiii>
+  <layout-floating-container pos10 landmarkContainer>
+    <nehuba-2dlandmark-unit *ngFor="let spatialData of (selectedPtLandmarks$ | async)"
+      (mouseenter)="handleMouseEnterLandmark(spatialData)" (mouseleave)="handleMouseLeaveLandmark(spatialData)"
+      [highlight]="spatialData.highlight ? spatialData.highlight : false"
+      [fasClass]="spatialData.type === 'userLandmark' ? 'fa-chevron-down' : 'fa-map-marker'"
+      [positionX]="getPositionX(2,spatialData)" [positionY]="getPositionY(2,spatialData)"
+      [positionZ]="getPositionZ(2,spatialData)">
+    </nehuba-2dlandmark-unit>
+
+    <div *ngIf="sliceViewLoading2$ | async" class="loadingIndicator">
+      <div class="spinnerAnimationCircle">
+
+      </div>
+    </div>
+  </layout-floating-container>
+</ng-template>
+
+<ng-template #overlayiv>
+  <layout-floating-container pos11 landmarkContainer>
+    <div *ngIf="perspectiveViewLoading$ | async" class="loadingIndicator">
+      <div class="spinnerAnimationCircle"></div>
+      <div perspectiveLoadingText>
+        {{ perspectiveViewLoading$ | async }}
+      </div>
+    </div>
+  </layout-floating-container>
+</ng-template>
+
+<ng-template #savedDatasets>
+  <mat-list rol="list">
+    <h3 mat-subheader>Favourite Datasets</h3>
+
+    <!-- place holder when no fav data is available -->
+    <mat-card *ngIf="(!(favDataEntries$ | async)) || (favDataEntries$ | async).length === 0">
+      <mat-card-content class="muted">
+        No dataset favourited... yet.
+      </mat-card-content>
+    </mat-card>
+
+    <!-- render all fav dataset as mat list -->
+    <mat-list-item
+      class="align-items-center"
+      *ngFor="let ds of (favDataEntries$ | async)"
+      role="listitem">
+      <span class="flex-grow-1 flex-shrink-1">
+        {{ ds.name }}
+      </span>
+
+      <!-- download -->
+      <button
+        #downloadBtn="matButton"
+        (click)="downloadDs($event, ds, downloadBtn)"
+        matTooltip="Download Dataset"
+        matTooltipPosition="after"
+        color="primary"
+        class="flex-grow-0 flex-shrink-0"
+        mat-icon-button>
+        <i class="fas fa-download"></i>
+      </button>
+
+      <!-- remove from fav -->
+      <button
+        (click)="removeFav($event, ds)"
+        matTooltip="Remove Favourite"
+        matTooltipPosition="after"
+        color="warn"
+        class="flex-grow-0 flex-shrink-0"
+        mat-icon-button>
+        <i class="fas fa-trash"></i>
+      </button>
+    </mat-list-item>
+  </mat-list>
+</ng-template>
\ No newline at end of file
diff --git a/src/ui/nehubaContainer/pipes/mobileControlNubStyle.pipe.ts b/src/ui/nehubaContainer/pipes/mobileControlNubStyle.pipe.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4566c470506588e39edc79f005843a24be80c748
--- /dev/null
+++ b/src/ui/nehubaContainer/pipes/mobileControlNubStyle.pipe.ts
@@ -0,0 +1,30 @@
+import { PipeTransform, Pipe } from "@angular/core";
+import { FOUR_PANEL, H_ONE_THREE, V_ONE_THREE, SINGLE_PANEL } from "src/services/state/ngViewerState.store";
+
+@Pipe({
+  name: 'mobileControlNubStylePipe'
+})
+
+export class MobileControlNubStylePipe implements PipeTransform{
+  public transform(panelMode: string): any{
+    switch (panelMode) {
+      case SINGLE_PANEL:
+        return {
+          top: '80%',
+          left: '95%'
+        }
+      case V_ONE_THREE:
+      case H_ONE_THREE:
+        return {
+          top: '66.66%',
+          left: '66.66%'
+        }
+      case FOUR_PANEL: 
+      default:
+        return {
+          top: '50%',
+          left: '50%'
+        }
+    }
+  }
+}
\ No newline at end of file
diff --git a/src/ui/nehubaContainer/splashScreen/splashScreen.component.ts b/src/ui/nehubaContainer/splashScreen/splashScreen.component.ts
index eb27393a77f1c284743cc34314d09db21040946f..22fd3655e72c0553f62a90716301cbac92f26723 100644
--- a/src/ui/nehubaContainer/splashScreen/splashScreen.component.ts
+++ b/src/ui/nehubaContainer/splashScreen/splashScreen.component.ts
@@ -1,10 +1,11 @@
-import { Component } from "@angular/core";
-import { Observable } from "rxjs";
+import { Component, Pipe, PipeTransform, ElementRef, ViewChild, AfterViewInit } from "@angular/core";
+import { Observable, fromEvent, Subscription, Subject } from "rxjs";
 import { Store, select } from "@ngrx/store";
-import { filter,map } from 'rxjs/operators'
+import { switchMap, bufferTime, take, filter, withLatestFrom, map, tap } from 'rxjs/operators'
 import { ViewerStateInterface, NEWVIEWER } from "../../../services/stateStore.service";
 import { AtlasViewerConstantsServices } from "../../../atlasViewer/atlasViewer.constantService.service";
 
+
 @Component({
   selector : 'ui-splashscreen',
   templateUrl : './splashScreen.template.html',
@@ -13,17 +14,52 @@ import { AtlasViewerConstantsServices } from "../../../atlasViewer/atlasViewer.c
   ]
 })
 
-export class SplashScreen{
-  loadedTemplate$ : Observable<any[]>
+export class SplashScreen implements AfterViewInit{
+
+  public loadedTemplate$ : Observable<any[]>
+  @ViewChild('parentContainer', {read:ElementRef}) 
+  private parentContainer: ElementRef
+  private activatedTemplate$: Subject<any> = new Subject()
+
+  private subscriptions: Subscription[] = []
+
   constructor(
     private store:Store<ViewerStateInterface>,
     private constanceService: AtlasViewerConstantsServices,
     private constantsService: AtlasViewerConstantsServices,
-){
+  ){
     this.loadedTemplate$ = this.store.pipe(
       select('viewerState'),
-      filter((state:ViewerStateInterface)=> typeof state !== 'undefined' && typeof state.fetchedTemplates !== 'undefined' && state.fetchedTemplates !== null),
-      map(state=>state.fetchedTemplates))
+      select('fetchedTemplates')
+    )
+  }
+
+  ngAfterViewInit(){
+
+    /**
+     * instead of blindly listening to click event, this event stream waits to see if user mouseup within 200ms
+     * if yes, it is interpreted as a click
+     * if no, user may want to select a text
+     */
+    this.subscriptions.push(
+      fromEvent(this.parentContainer.nativeElement, 'mousedown').pipe(
+        switchMap(() => fromEvent(this.parentContainer.nativeElement, 'mouseup').pipe(
+          bufferTime(200),
+          take(1)
+        )),
+        filter(arr => arr.length > 0),
+        withLatestFrom(this.activatedTemplate$),
+        map(([_, template]) => template)
+      ).subscribe(template => this.selectTemplate(template))
+    )
+  }
+
+  selectTemplateParcellation(template, parcellation){
+    this.store.dispatch({
+      type : NEWVIEWER,
+      selectTemplate : template,
+      selectParcellation : parcellation
+    })
   }
 
   selectTemplate(template:any){
@@ -38,11 +74,31 @@ export class SplashScreen{
     return this.constanceService.templateUrls.length
   }
 
-  correctString(name){
-    return name.replace(/[|&;$%@()+,\s./]/g, '')
-  }
-
   get isMobile(){
     return this.constantsService.mobile
   }
-}
\ No newline at end of file
+}
+
+@Pipe({
+  name: 'getTemplateImageSrcPipe'
+})
+
+export class GetTemplateImageSrcPipe implements PipeTransform{
+  public transform(name:string):string{
+    return `./res/image/${name.replace(/[|&;$%@()+,\s./]/g, '')}.png`
+  }
+}
+
+@Pipe({
+  name: 'imgSrcSetPipe'
+})
+
+export class ImgSrcSetPipe implements PipeTransform{
+  public transform(src:string):string{
+    const regex = /^(.*?)(\.\w*?)$/.exec(src)
+    if (!regex) throw new Error(`cannot find filename, ext ${src}`)
+    const filename = regex[1]
+    const ext = regex[2]
+    return [100, 200, 300, 400].map(val => `${filename}-${val}${ext} ${val}w`).join(',')
+  }
+} 
\ No newline at end of file
diff --git a/src/ui/nehubaContainer/splashScreen/splashScreen.style.css b/src/ui/nehubaContainer/splashScreen/splashScreen.style.css
index cea1195130e1a729c44170d5e47fe742595ff3c3..49774d56632717713c75fc404f681049c0e06e5c 100644
--- a/src/ui/nehubaContainer/splashScreen/splashScreen.style.css
+++ b/src/ui/nehubaContainer/splashScreen/splashScreen.style.css
@@ -1,61 +1,11 @@
-.appendMargin
+:host
 {
-  padding-top:10em;
+  display: block;
+  overflow: auto;
+  height: 100%;
 }
 
-.splashScreenHeaderTitle {
-  font-size: 45px;
-}
-
-div[splashScreenTemplateItem] {
-  max-width: 600px;
-  width: 400px;
-  margin: 20px 20px;
-  display: flex;
-  flex-direction: row;
-  flex-wrap: wrap;
-  justify-content: center;
-
-}
-
-div[splashScreenTemplateHeader] {
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  height: 70px;
-  align-self: center;
-  text-align: center;
-  font-size: 30px;
-  margin: 5px 0;
-
-}
-
-.template-image {
-  width: 100%;
-  height: auto;
-}
-
-.template-card {
-  width: 100%;
-  cursor: pointer;
-  background: #fff;
-  border-radius: 2px;
-  box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
-  transition: all 0.3s cubic-bezier(.25,.8,.25,1);
-}
-
-.template-card:hover {
-  box-shadow: 0 14px 28px rgba(0,0,0,0.25), 0 5px 5px rgba(0,0,0,0.22);
-}
-
-@media screen and (max-width: 670px) {
-  .splashScreenHeaderTitle {
-    visibility: hidden;
-  }
-  div[splashScreenTemplate] {
-    margin: 20px 20px;
-  }
-  div[splashScreenTemplateBody] {
-    flex-direction: column;
-  }
+.font-stretch
+{
+  font-stretch: extra-condensed;
 }
\ No newline at end of file
diff --git a/src/ui/nehubaContainer/splashScreen/splashScreen.template.html b/src/ui/nehubaContainer/splashScreen/splashScreen.template.html
index 28ec10df8e43de608f367da127f8ee292524c48a..a63adbeb71157ff8282a9abda4d5e30e58d79924 100644
--- a/src/ui/nehubaContainer/splashScreen/splashScreen.template.html
+++ b/src/ui/nehubaContainer/splashScreen/splashScreen.template.html
@@ -1,20 +1,43 @@
-<div [ngClass]="isMobile ? '' : 'appendMargin'" class="h-100 d-flex flex-column justify-content-start align-items-center overflow-auto">
+<div
+  #parentContainer
+  class="m-5 d-flex flex-row flex-wrap justify-content-center align-items-stretch pe-none">
+  <mat-card
+    (mousedown)="activatedTemplate$.next(template)"
+    matRipple
+    *ngFor="let template of loadedTemplate$ | async | filterNull"
+    class="m-3 col-md-12 col-lg-6 pe-all mw-400px">
+    <mat-card-header>
+      <mat-card-title class="text-nowrap font-stretch">
+        {{ template.properties.name }}
+      </mat-card-title>
+    </mat-card-header>
+    <img
+      [src]="template.properties.name | getTemplateImageSrcPipe"
+      [srcset]="template.properties.name | getTemplateImageSrcPipe | imgSrcSetPipe"
+      sizes="(max-width:576px) 90vw;(max-width: 768px) 50vw; 400px"
+      [alt]="'Screenshot of ' + template.properties.name"
+      mat-card-image />
+    <mat-card-content>
+      {{ template.properties.description }}
+    </mat-card-content>
 
-  <div class="d-flex w-100 flex-wrap justify-content-center">
-    <div *ngFor="let template of loadedTemplate$ | async | filterNull" splashScreenTemplateItem>
-      <div class="template-card" (click) = "selectTemplate(template)">
-        <div splashScreenTemplateHeader>
-          {{template.properties.name}}
-        </div>
-        <div class="d-flex flex-column">
-          <div class="flex-grow-1">
-            <img class="template-image" [src]="'./res/image/' + correctString(template.properties.name) + '.png'">
-          </div>
-          <div class="flex-grow-1 text-justify ml-2 mr-2 mb-2 mt-0">
-            {{template.properties.description}}
-          </div>
-        </div>
-      </div>
-    </div>
-  </div>
+    <mat-card-content>
+      <mat-card-subtitle class="text-nowrap">
+        Parcellations available
+      </mat-card-subtitle>
+      <button
+        (mousedown)="$event.stopPropagation()"
+        (click)="$event.stopPropagation(); selectTemplateParcellation(template, parcellation)"
+        *ngFor="let parcellation of template.parcellations"
+        mat-button
+        color="primary">
+        {{ parcellation.name }}
+      </button>
+    </mat-card-content>
+
+    <!-- required... or on ripple, angular adds 16px margin to the bottom -->
+    <!-- see https://github.com/angular/components/issues/10898 -->
+    <mat-card-footer>
+    </mat-card-footer>
+  </mat-card>
 </div>
\ No newline at end of file
diff --git a/src/ui/nehubaContainer/util.ts b/src/ui/nehubaContainer/util.ts
new file mode 100644
index 0000000000000000000000000000000000000000..edd3a98f8fea9b48e40fe7cd16702f469a95ca93
--- /dev/null
+++ b/src/ui/nehubaContainer/util.ts
@@ -0,0 +1,74 @@
+const flexContCmnCls = ['w-100', 'h-100', 'd-flex', 'justify-content-center', 'align-items-stretch']
+
+const makeRow = (...els:HTMLElement[]) => {
+  const container = document.createElement('div')
+  container.classList.add(...flexContCmnCls, 'flex-row')
+  for (const el of els){
+    container.appendChild(el)
+  }
+  return container
+}
+
+const makeCol = (...els:HTMLElement[]) => {
+  const container = document.createElement('div')
+  container.classList.add(...flexContCmnCls, 'flex-column')
+  for (const el of els){
+    container.appendChild(el)
+  }
+  return container
+}
+
+export const getHorizontalOneThree = (panels:[HTMLElement, HTMLElement, HTMLElement, HTMLElement]) => {
+  for (let panel of panels){
+    panel.className = ''
+  }
+  const majorContainer = makeCol(panels[0])
+  const minorContainer = makeCol(panels[1], panels[2], panels[3])
+
+  majorContainer.style.flexBasis = '67%'
+  minorContainer.style.flexBasis = '33%'
+  
+  return makeRow(majorContainer, minorContainer)
+}
+
+export const getVerticalOneThree = (panels:[HTMLElement, HTMLElement, HTMLElement, HTMLElement]) => {
+  for (let panel of panels){
+    panel.className = ''
+  }
+  const majorContainer = makeRow(panels[0])
+  const minorContainer = makeRow(panels[1], panels[2], panels[3])
+
+  majorContainer.style.flexBasis = '67%'
+  minorContainer.style.flexBasis = '33%'
+  
+  return makeCol(majorContainer, minorContainer)
+}
+
+
+export const getFourPanel = (panels:[HTMLElement, HTMLElement, HTMLElement, HTMLElement]) => {
+  for (let panel of panels){
+    panel.className = ''
+  }
+  const majorContainer = makeRow(panels[0], panels[1])
+  const minorContainer = makeRow(panels[2], panels[3])
+
+  majorContainer.style.flexBasis = '50%'
+  minorContainer.style.flexBasis = '50%'
+  
+  return makeCol(majorContainer, minorContainer)
+}
+
+export const getSinglePanel = (panels:[HTMLElement, HTMLElement, HTMLElement, HTMLElement]) => {
+  for (let panel of panels){
+    panel.className = ''
+  }
+  const majorContainer = makeRow(panels[0])
+  const minorContainer = makeRow(panels[1], panels[2], panels[3])
+
+  majorContainer.style.flexBasis = '100%'
+  minorContainer.style.flexBasis = '0%'
+
+  minorContainer.className = ''
+  minorContainer.style.height = '0px'
+  return makeCol(majorContainer, minorContainer)
+}
\ No newline at end of file
diff --git a/src/ui/regionHierachy/regionHierarchy.style.css b/src/ui/regionHierachy/regionHierarchy.style.css
deleted file mode 100644
index a4aa507f4db1a32886dc1309934dc8481d49a425..0000000000000000000000000000000000000000
--- a/src/ui/regionHierachy/regionHierarchy.style.css
+++ /dev/null
@@ -1,65 +0,0 @@
-
-div[treeContainer]
-{
-  padding:1em;
-  z-index: 3;
-
-  height:20em;
-  width: calc(100% + 4em);
-  overflow-y:auto;
-  overflow-x:hidden;
-
-  /* color:white;
-  background-color:rgba(12,12,12,0.8); */
-}
-
-:host-context([darktheme="false"]) div[treeContainer]
-{
-  background-color:rgba(240,240,240,0.8);
-}
-
-:host-context([darktheme="true"]) div[treeContainer]
-{
-  background-color:rgba(30,30,30,0.8);
-  color:rgba(255,255,255,1.0);
-}
-
-div[hideScrollbarcontainer]
-{
-  width: 20em;
-  overflow:hidden;
-  margin-top:2px;
-}
-
-input[type="text"]
-{
-  border:none;
-}
-
-:host-context([darktheme="false"]) input[type="text"]
-{
-  background-color:rgba(245,245,245,0.85);
-  box-shadow: inset 0 4px 6px 0 rgba(5,5,5,0.1);
-}
-
-:host-context([darktheme="true"]) input[type="text"]
-{
-  background-color:rgba(30,30,30,0.85);
-  box-shadow: inset 0 4px 6px 0 rgba(0,0,0,0.2);
-  color:rgba(255,255,255,0.8);
-}
-
-.regionSearch
-{
-  width:20em;
-}
-
-.tree-header
-{
-  flex: 0 0 auto;
-}
-
-.tree-body
-{
-  flex: 1 1 auto;
-}
\ No newline at end of file
diff --git a/src/ui/sharedModules/angularMaterial.module.ts b/src/ui/sharedModules/angularMaterial.module.ts
index 707c71a085245706c04898b9b2935b34432b3b0a..22ab6307004196d3b182682848014717adb67a8c 100644
--- a/src/ui/sharedModules/angularMaterial.module.ts
+++ b/src/ui/sharedModules/angularMaterial.module.ts
@@ -5,12 +5,66 @@ import {
   MatCardModule,
   MatTabsModule,
   MatTooltipModule,
-  MatSnackBarModule
+  MatSnackBarModule,
+  MatBadgeModule,
+  MatDividerModule,
+  MatSelectModule,
+  MatChipsModule,
+  MatAutocompleteModule,
+  MatDialogModule,
+  MatInputModule,
+  MatBottomSheetModule,
+  MatListModule,
+  MatSlideToggleModule,
+  MatRippleModule
+  
 } from '@angular/material';
 import { NgModule } from '@angular/core';
 
+/**
+ * TODO should probably be in src/util
+ */
+
 @NgModule({
-  imports: [MatSnackBarModule, MatButtonModule, MatCheckboxModule, MatSidenavModule, MatCardModule, MatTabsModule, MatTooltipModule],
-  exports: [MatSnackBarModule, MatButtonModule, MatCheckboxModule, MatSidenavModule, MatCardModule, MatTabsModule, MatTooltipModule],
+  imports: [
+    MatButtonModule,
+    MatSnackBarModule,
+    MatCheckboxModule,
+    MatSidenavModule,
+    MatCardModule,
+    MatTabsModule,
+    MatTooltipModule,
+    MatBadgeModule,
+    MatDividerModule,
+    MatSelectModule,
+    MatChipsModule,
+    MatAutocompleteModule,
+    MatDialogModule,
+    MatInputModule,
+    MatBottomSheetModule,
+    MatListModule,
+    MatSlideToggleModule,
+    MatRippleModule
+  ],
+  exports: [
+    MatButtonModule,
+    MatCheckboxModule,
+    MatSnackBarModule,
+    MatSidenavModule,
+    MatCardModule,
+    MatTabsModule,
+    MatTooltipModule,
+    MatBadgeModule,
+    MatDividerModule,
+    MatSelectModule,
+    MatChipsModule,
+    MatAutocompleteModule,
+    MatDialogModule,
+    MatInputModule,
+    MatBottomSheetModule,
+    MatListModule,
+    MatSlideToggleModule,
+    MatRippleModule
+  ],
 })
 export class AngularMaterialModule { }
\ No newline at end of file
diff --git a/src/ui/signinBanner/signinBanner.components.ts b/src/ui/signinBanner/signinBanner.components.ts
index 592e3101d2f6bc897fdee15efdabbefd948011c5..abd1eb3397f1c1a3e7ada69d3ead390f29bfbed7 100644
--- a/src/ui/signinBanner/signinBanner.components.ts
+++ b/src/ui/signinBanner/signinBanner.components.ts
@@ -1,18 +1,8 @@
-import {Component, ChangeDetectionStrategy, OnDestroy, OnInit, Input, ViewChild, TemplateRef } from "@angular/core";
+import {Component, ChangeDetectionStrategy, Input, TemplateRef } from "@angular/core";
 import { AtlasViewerConstantsServices } from "src/atlasViewer/atlasViewer.constantService.service";
 import { AuthService, User } from "src/services/auth.service";
-import { Store, select } from "@ngrx/store";
-import { ViewerConfiguration } from "src/services/state/viewerConfig.store";
-import { Subscription, Observable, merge, Subject, combineLatest } from "rxjs";
-import { safeFilter, isDefined, NEWVIEWER, SELECT_REGIONS, SELECT_PARCELLATION, CHANGE_NAVIGATION } from "src/services/stateStore.service";
-import { map, filter, distinctUntilChanged, bufferTime, delay, share, tap, withLatestFrom } from "rxjs/operators";
-import { regionFlattener } from "src/util/regionFlattener";
-import { ToastService } from "src/services/toastService.service";
-import { getSchemaIdFromName } from "src/util/pipes/templateParcellationDecoration.pipe";
+import { MatDialog } from "@angular/material";
 
-const compareParcellation = (o, n) => !o || !n
-  ? false
-  : o.name === n.name
 
 @Component({
   selector: 'signin-banner',
@@ -24,256 +14,44 @@ const compareParcellation = (o, n) => !o || !n
   changeDetection: ChangeDetectionStrategy.OnPush
 })
 
-export class SigninBanner implements OnInit, OnDestroy{
+export class SigninBanner{
 
-  public compareParcellation = compareParcellation
-
-  private subscriptions: Subscription[] = []
-  
-  public loadedTemplates$: Observable<any[]>
-
-  public selectedTemplate$: Observable<any>
-  public selectedParcellation$: Observable<any>
-
-  public selectedRegions$: Observable<any[]>
-  private selectedRegions: any[] = []
   @Input() darktheme: boolean
 
-  @ViewChild('publicationTemplate', {read:TemplateRef}) publicationTemplate: TemplateRef<any>
-
-  public focusedDatasets$: Observable<any[]>
-  private userFocusedDataset$: Subject<any> = new Subject()
-  public focusedDatasets: any[] = []
-  private dismissToastHandler: () => void
+  public isMobile: boolean
 
   constructor(
     private constantService: AtlasViewerConstantsServices,
     private authService: AuthService,
-    private store: Store<ViewerConfiguration>,
-    private toastService: ToastService
+    private dialog: MatDialog
   ){
-    this.loadedTemplates$ = this.store.pipe(
-      select('viewerState'),
-      safeFilter('fetchedTemplates'),
-      map(state => state.fetchedTemplates)
-    )
-
-    this.selectedTemplate$ = this.store.pipe(
-      select('viewerState'),
-      select('templateSelected'),
-      filter(v => !!v),
-      distinctUntilChanged((o, n) => o.name === n.name),
-    )
-
-    this.selectedParcellation$ = this.store.pipe(
-      select('viewerState'),
-      select('parcellationSelected'),
-      filter(v => !!v)
-    )
-
-    this.selectedRegions$ = this.store.pipe(
-      select('viewerState'),
-      safeFilter('regionsSelected'),
-      map(state => state.regionsSelected),
-      distinctUntilChanged((arr1, arr2) => arr1.length === arr2.length && (arr1 as any[]).every((item, index) => item.name === arr2[index].name))
-    )
-
-    this.focusedDatasets$ = this.userFocusedDataset$.pipe(
-      filter(v => !!v),
-      withLatestFrom(
-        combineLatest(this.selectedTemplate$, this.selectedParcellation$)
-      ),
-    ).pipe(
-      map(([userFocusedDataset, [selectedTemplate, selectedParcellation]]) => {
-        const { type, ...rest } = userFocusedDataset
-        if (type === 'template') return { ...selectedTemplate,  ...rest}
-        if (type === 'parcellation') return { ...selectedParcellation, ...rest }
-        return { ...rest }
-      }),
-      bufferTime(100),
-      filter(arr => arr.length > 0),
-      /**
-       * merge properties field with the root level
-       * with the prop in properties taking priority
-       */
-      map(arr => arr.map(item => {
-        const { properties } = item
-        return {
-          ...item,
-          ...properties
-        }
-      })),
-      share()
-    )
-  }
-
-  ngOnInit(){
-
-    this.subscriptions.push(
-      this.selectedRegions$.subscribe(regions => {
-        this.selectedRegions = regions
-      })
-    )
-
-    this.subscriptions.push(
-      this.focusedDatasets$.subscribe(() => {
-        if (this.dismissToastHandler) this.dismissToastHandler()
-      })
-    )
-
-    this.subscriptions.push(
-      this.focusedDatasets$.pipe(
-        /**
-         * creates the illusion that the toast complete disappears before reappearing
-         */
-        delay(100)
-      ).subscribe(arr => {
-        this.focusedDatasets = arr
-        this.dismissToastHandler = this.toastService.showToast(this.publicationTemplate, {
-          dismissable: true,
-          timeout:7000
-        })
-      })
-    )
-  }
-
-  ngOnDestroy(){
-    this.subscriptions.forEach(s => s.unsubscribe())
-  }
-
-  changeTemplate({ current, previous }){
-    if (previous && current && current.name === previous.name) return
-    this.store.dispatch({
-      type: NEWVIEWER,
-      selectTemplate: current,
-      selectParcellation: current.parcellations[0]
-    })
-  }
-
-  changeParcellation({ current, previous }){
-    const { ngId: prevNgId} = previous
-    const { ngId: currNgId} = current
-    if (prevNgId === currNgId) return
-    this.store.dispatch({
-      type: SELECT_PARCELLATION,
-      selectParcellation: current
-    })
-  }
-
-  // TODO handle mobile
-  handleRegionClick({ mode = 'single', region }){
-    if (!region) return
-    
-    /**
-     * single click on region hierarchy => toggle selection
-     */
-    if (mode === 'single') {
-      const flattenedRegion = regionFlattener(region).filter(r => isDefined(r.labelIndex))
-      const flattenedRegionNames = new Set(flattenedRegion.map(r => r.name))
-      const selectedRegionNames = new Set(this.selectedRegions.map(r => r.name))
-      const selectAll = flattenedRegion.every(r => !selectedRegionNames.has(r.name))
-      this.store.dispatch({
-        type: SELECT_REGIONS,
-        selectRegions: selectAll
-          ? this.selectedRegions.concat(flattenedRegion)
-          : this.selectedRegions.filter(r => !flattenedRegionNames.has(r.name))
-      })
-    }
-
-    /**
-     * double click on region hierarchy => navigate to region area if it exists
-     */
-    if (mode === 'double') {
-
-      /**
-       * if position is defined, go to position (in nm)
-       * if not, show error messagea s toast
-       * 
-       * nb: currently, only supports a single triplet
-       */
-      if (region.position) {
-        this.store.dispatch({
-          type: CHANGE_NAVIGATION,
-          navigation: {
-            position: region.position,
-            animation: {}
-          }
-        })
-      } else {
-        this.toastService.showToast(`${region.name} does not have a position defined`, {
-          timeout: 5000,
-          dismissable: true
-        })
-      }
-    }
-  }
-
-  displayActiveParcellation(parcellation:any){
-    return `<div class="d-flex"><small>Parcellation</small> <small class = "flex-grow-1 mute-text">${parcellation ? '(' + parcellation.name + ')' : ''}</small> <span class = "fas fa-caret-down"></span></div>`
-  }
-
-  displayActiveTemplate(template: any) {
-    return `<div class="d-flex"><small>Template</small> <small class = "flex-grow-1 mute-text">${template ? '(' + template.name + ')' : ''}</small> <span class = "fas fa-caret-down"></span></div>`
+    this.isMobile = this.constantService.mobile
   }
 
+  /**
+   * move the templates to signin banner when pluginprettify is merged
+   */
   showHelp() {
     this.constantService.showHelpSubject$.next()
   }
 
-  showSignin() {
-    this.constantService.showSigninSubject$.next(this.user)
-  }
-
-  clearAllRegions(){
-    this.store.dispatch({
-      type: SELECT_REGIONS,
-      selectRegions: []
+  /**
+   * move the templates to signin banner when pluginprettify is merged
+   */
+  showSetting(settingTemplate:TemplateRef<any>){
+    this.dialog.open(settingTemplate, {
+      autoFocus: false
     })
   }
 
-  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
+  /**
+   * move the templates to signin banner when pluginprettify is merged
+   */
+  showSignin() {
+    this.constantService.showSigninSubject$.next(this.user)
   }
 
   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..1da0fb01da873392fbe79901047520aaff3bf699 100644
--- a/src/ui/signinBanner/signinBanner.template.html
+++ b/src/ui/signinBanner/signinBanner.template.html
@@ -2,96 +2,43 @@
   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 -->
-
+  <!-- setting btn -->
   <div class="btnWrapper">
-
-    <div 
-      *ngIf="!isMobile"
-      (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>
+    <button
+      #settingBtn
+      matTooltip="Settings"
+      matTooltipPosition="below"
+      (click)="showSetting(settingTemplate)" 
+      mat-icon-button
+      color="basic">
+      <i class="fas fa-cog"></i>
+    </button>
   </div>
 
-  <div *ngIf="isMobile" class="login-button-panel-mobile">
-    <div
+  <!-- signin -->
+  <div class="btnWrapper">
+    <button
+      [matTooltip]="user && user.name ? ('Logged in as ' + (user && user.name)) : 'Not logged in'"
+      matTooltipPosition="below"
       (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 a7c08e93c200ab6454d88efa8a618687ca049dec..ae129d6220403450c0ebc00cff530c85e1cf842c 100644
--- a/src/ui/ui.module.ts
+++ b/src/ui/ui.module.ts
@@ -3,9 +3,9 @@ import { ComponentsModule } from "../components/components.module";
 
 import { NehubaViewerUnit } from "./nehubaContainer/nehubaViewer/nehubaViewer.component";
 import { NehubaContainer } from "./nehubaContainer/nehubaContainer.component";
-import { SplashScreen } from "./nehubaContainer/splashScreen/splashScreen.component";
+import { SplashScreen, GetTemplateImageSrcPipe, ImgSrcSetPipe } from "./nehubaContainer/splashScreen/splashScreen.component";
 import { LayoutModule } from "../layouts/layout.module";
-import { FormsModule } from "@angular/forms";
+import { FormsModule, ReactiveFormsModule } from "@angular/forms";
 
 import { GroupDatasetByRegion } from "../util/pipes/groupDataEntriesByRegion.pipe";
 import { filterRegionDataEntries } from "../util/pipes/filterRegionDataEntries.pipe";
@@ -17,7 +17,7 @@ import { LandmarkUnit } from "./nehubaContainer/landmarkUnit/landmarkUnit.compon
 import { SafeStylePipe } from "../util/pipes/safeStyle.pipe";
 import { PluginBannerUI } from "./pluginBanner/pluginBanner.component";
 import { CitationsContainer } from "./citation/citations.component";
-import { LayerBrowser } from "./layerbrowser/layerbrowser.component";
+import { LayerBrowser, LockedLayerBtnClsPipe } from "./layerbrowser/layerbrowser.component";
 import { TooltipModule } from "ngx-bootstrap/tooltip";
 import { KgEntryViewer } from "./kgEntryViewer/kgentry.component";
 import { SubjectViewer } from "./kgEntryViewer/subjectViewer/subjectViewer.component";
@@ -38,25 +38,42 @@ 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 { FourPanelLayout } from "./config/layouts/fourPanel/fourPanel.component";
+import { HorizontalOneThree } from "./config/layouts/h13/h13.component";
+import { VerticalOneThree } from "./config/layouts/v13/v13.component";
+import { SinglePanel } from "./config/layouts/single/single.component";
+import { CurrentLayout } from "./config/currentLayout/currentLayout.component";
+import { MobileControlNubStylePipe } from "./nehubaContainer/pipes/mobileControlNubStyle.pipe";
+import { ScrollingModule } from "@angular/cdk/scrolling"
+import { HttpClientModule } from "@angular/common/http";
+import { GetFilenamePipe } from "src/util/pipes/getFilename.pipe";
+import { GetFileExtension } from "src/util/pipes/getFileExt.pipe";
+import { ViewerStateController } from "./viewerStateController/viewerState.component";
+import { BinSavedRegionsSelectionPipe, SavedRegionsSelectionBtnDisabledPipe } from "./viewerStateController/viewerState.pipes";
+import { RegionTextSearchAutocomplete } from "./viewerStateController/regionSearch/regionSearch.component";
+import { PluginBtnFabColorPipe } from "src/util/pipes/pluginBtnFabColor.pipe";
+import { KgSearchBtnColorPipe } from "src/util/pipes/kgSearchBtnColor.pipe";
 
 
 @NgModule({
   imports : [
+    HttpClientModule,
     FormsModule,
+    ReactiveFormsModule,
     LayoutModule,
     ComponentsModule,
     DatabrowserModule,
     UtilModule,
+    ScrollingModule,
     AngularMaterialModule,
 
     PopoverModule.forRoot(),
@@ -84,6 +101,13 @@ import { AppendtooltipTextPipe } from "src/util/pipes/appendTooltipText.pipe";
     StatusCardComponent,
     CookieAgreement,
     KGToS,
+    FourPanelLayout,
+    HorizontalOneThree,
+    VerticalOneThree,
+    SinglePanel,
+    CurrentLayout,
+    ViewerStateController,
+    RegionTextSearchAutocomplete,
 
     /* pipes */
     GroupDatasetByRegion,
@@ -95,10 +119,19 @@ import { AppendtooltipTextPipe } from "src/util/pipes/appendTooltipText.pipe";
     SortDataEntriesToRegion,
     SpatialLandmarksToDataBrowserItemPipe,
     FilterNullPipe,
-    FilterNgLayer,
     FilterNameBySearch,
     TemplateParcellationsDecorationPipe,
     AppendtooltipTextPipe,
+    MobileControlNubStylePipe,
+    GetTemplateImageSrcPipe,
+    ImgSrcSetPipe,
+    PluginBtnFabColorPipe,
+    KgSearchBtnColorPipe,
+    LockedLayerBtnClsPipe,
+    GetFilenamePipe,
+    GetFileExtension,
+    BinSavedRegionsSelectionPipe,
+    SavedRegionsSelectionBtnDisabledPipe,
 
     /* directive */
     DownloadDirective,
@@ -109,7 +142,7 @@ import { AppendtooltipTextPipe } from "src/util/pipes/appendTooltipText.pipe";
     /* dynamically created components needs to be declared here */
     NehubaViewerUnit,
     LayerBrowser,
-    PluginBannerUI
+    PluginBannerUI,
   ],
   exports : [
     SubjectViewer,
diff --git a/src/ui/regionHierachy/filterNameBySearch.pipe.ts b/src/ui/viewerStateController/regionHierachy/filterNameBySearch.pipe.ts
similarity index 100%
rename from src/ui/regionHierachy/filterNameBySearch.pipe.ts
rename to src/ui/viewerStateController/regionHierachy/filterNameBySearch.pipe.ts
diff --git a/src/ui/regionHierachy/regionHierarchy.component.ts b/src/ui/viewerStateController/regionHierachy/regionHierarchy.component.ts
similarity index 75%
rename from src/ui/regionHierachy/regionHierarchy.component.ts
rename to src/ui/viewerStateController/regionHierachy/regionHierarchy.component.ts
index f8b4fd35dba949a3b4fc7620ad67309a41f0f7c4..2fd52a746bd58437785353e315e68bcdf2b6f2a9 100644
--- a/src/ui/regionHierachy/regionHierarchy.component.ts
+++ b/src/ui/viewerStateController/regionHierachy/regionHierarchy.component.ts
@@ -1,6 +1,6 @@
 import { EventEmitter, Component, ElementRef, ViewChild, HostListener, OnInit, ChangeDetectionStrategy, ChangeDetectorRef, Input, Output, AfterViewInit } from "@angular/core";
 import {  Subscription, Subject, fromEvent } from "rxjs";
-import { buffer, debounceTime } from "rxjs/operators";
+import { buffer, debounceTime, tap } from "rxjs/operators";
 import { FilterNameBySearch } from "./filterNameBySearch.pipe";
 import { generateLabelIndexId } from "src/services/stateStore.service";
 
@@ -17,8 +17,8 @@ const getDisplayTreeNode : (searchTerm:string, selectedRegions:any[]) => (item:a
     && selectedRegions.findIndex(re =>
       generateLabelIndexId({ labelIndex: re.labelIndex, ngId: re.ngId }) === generateLabelIndexId({ ngId, labelIndex })
     ) >= 0
-      ? `<span class="regionSelected">${insertHighlight(name, searchTerm)}</span>` + (status ? ` <span class="text-muted">(${insertHighlight(status, searchTerm)})</span>` : ``)
-      : `<span class="regionNotSelected">${insertHighlight(name, searchTerm)}</span>` + (status ? ` <span class="text-muted">(${insertHighlight(status, searchTerm)})</span>` : ``)
+      ? `<span class="cursor-default regionSelected">${insertHighlight(name, searchTerm)}</span>` + (status ? ` <span class="text-muted">(${insertHighlight(status, searchTerm)})</span>` : ``)
+      : `<span class="cursor-default regionNotSelected">${insertHighlight(name, searchTerm)}</span>` + (status ? ` <span class="text-muted">(${insertHighlight(status, searchTerm)})</span>` : ``)
 }
 
 const getFilterTreeBySearch = (pipe:FilterNameBySearch, searchTerm:string) => (node:any) => pipe.transform([node.name, node.status], searchTerm)
@@ -38,9 +38,7 @@ export class RegionHierarchy implements OnInit, AfterViewInit{
   public selectedRegions: any[] = []
 
   @Input()
-  public selectedParcellation: any
-
-  @Input() isMobile: boolean;
+  public parcellationSelected: any
 
   private _showRegionTree: boolean = false
 
@@ -54,7 +52,7 @@ export class RegionHierarchy implements OnInit, AfterViewInit{
   private doubleClickRegion: EventEmitter<any> = new EventEmitter()
 
   @Output()
-  private clearAllRegions: EventEmitter<null> = new EventEmitter()
+  private clearAllRegions: EventEmitter<MouseEvent> = new EventEmitter()
 
   public searchTerm: string = ''
   private subscriptions: Subscription[] = []
@@ -62,6 +60,8 @@ export class RegionHierarchy implements OnInit, AfterViewInit{
   @ViewChild('searchTermInput', {read: ElementRef})
   private searchTermInput: ElementRef
 
+  public placeHolderText: string = `Start by selecting a template and a parcellation.`
+
   /**
    * set the height to max, bound by max-height
    */
@@ -95,17 +95,19 @@ export class RegionHierarchy implements OnInit, AfterViewInit{
   }
 
   ngOnChanges(){
-    this.aggregatedRegionTree = {
-      name: this.selectedParcellation.name,
-      children: this.selectedParcellation.regions
+    if (this.parcellationSelected) {
+      this.placeHolderText = `Search region in ${this.parcellationSelected.name}`
+      this.aggregatedRegionTree = {
+        name: this.parcellationSelected.name,
+        children: this.parcellationSelected.regions
+      }
     }
     this.displayTreeNode = getDisplayTreeNode(this.searchTerm, this.selectedRegions)
     this.filterTreeBySearch = getFilterTreeBySearch(this.filterNameBySearchPipe, this.searchTerm)
   }
 
   clearRegions(event:MouseEvent){
-    event.stopPropagation()
-    this.clearAllRegions.emit()
+    this.clearAllRegions.emit(event)
   }
 
   get showRegionTree(){
@@ -133,20 +135,6 @@ export class RegionHierarchy implements OnInit, AfterViewInit{
   }
 
   ngAfterViewInit(){
-    /**
-     * TODO
-     * bandaid fix on
-     * when region search loses focus, the searchTerm is cleared,
-     * but hierarchy filter does not reset
-     */
-    this.subscriptions.push(
-      fromEvent(this.searchTermInput.nativeElement, 'focus').pipe(
-        
-      ).subscribe(() => {
-        this.displayTreeNode = getDisplayTreeNode(this.searchTerm, this.selectedRegions)
-        this.filterTreeBySearch = getFilterTreeBySearch(this.filterNameBySearchPipe, this.searchTerm)
-      })
-    )
     this.subscriptions.push(
       fromEvent(this.searchTermInput.nativeElement, 'input').pipe(
         debounceTime(200)
@@ -156,13 +144,6 @@ export class RegionHierarchy implements OnInit, AfterViewInit{
     )
   }
 
-  getInputPlaceholder(parcellation:any) {
-    if (parcellation)
-      return `Search region in ${parcellation.name}`
-    else
-      return `Start by selecting a template and a parcellation.`
-  }
-
   escape(event:KeyboardEvent){
     this.showRegionTree = false
     this.searchTerm = '';
@@ -182,27 +163,12 @@ export class RegionHierarchy implements OnInit, AfterViewInit{
     })
   }
 
-  focusInput(event?:MouseEvent){
-    if (event) {
-      /**
-       * need to stop propagation, or @closeRegion will be triggered
-       */
-      event.stopPropagation()
-    }
-    this.searchTermInput.nativeElement.focus()
-    this.showRegionTree = true
-  }
-
   /* NB need to bind two way data binding like this. Or else, on searchInput blur, the flat tree will be rebuilt,
     resulting in first click to be ignored */
 
   changeSearchTerm(event: any) {
-    if (event.target.value === this.searchTerm)
-      return
+    if (event.target.value === this.searchTerm) return
     this.searchTerm = event.target.value
-    /**
-     * TODO maybe introduce debounce
-     */
     this.ngOnChanges()
     this.cdr.markForCheck()
   }
@@ -214,18 +180,17 @@ export class RegionHierarchy implements OnInit, AfterViewInit{
     /**
      * TODO figure out why @closeRegion gets triggered, but also, contains returns false
      */
-    if (event)
+    if (event) {
       event.stopPropagation()
+    }
     this.handleRegionTreeClickSubject.next(obj)
   }
 
   /* single click selects/deselects region(s) */
   private singleClick(obj: any) {
-    if (!obj)
-      return
+    if (!obj) return
     const { inputItem : region } = obj
-    if (!region)
-      return
+    if (!region) return
     this.singleClickRegion.emit(region)
   }
 
diff --git a/src/ui/viewerStateController/regionHierachy/regionHierarchy.style.css b/src/ui/viewerStateController/regionHierachy/regionHierarchy.style.css
new file mode 100644
index 0000000000000000000000000000000000000000..372068bbf85ed6be2b7f1f2ae06fb467f7f4a701
--- /dev/null
+++ b/src/ui/viewerStateController/regionHierachy/regionHierarchy.style.css
@@ -0,0 +1,57 @@
+
+div[treeContainer]
+{
+  padding:1em;
+  z-index: 3;
+
+  height:20em;
+  width: calc(100% + 4em);
+  overflow-y:auto;
+  overflow-x:hidden;
+
+  /* color:white;
+  background-color:rgba(12,12,12,0.8); */
+}
+
+div[hideScrollbarcontainer]
+{
+  overflow:hidden;
+  margin-top:2px;
+}
+
+input[type="text"]
+{
+  border:none;
+}
+
+
+.regionSearch
+{
+  width:20em;
+}
+
+.tree-header
+{
+  flex: 0 0 auto;
+}
+
+.tree-body
+{
+  flex: 1 1 auto;
+}
+
+:host
+{
+  display: flex;
+  flex-direction: column;
+}
+
+:host > mat-form-field
+{
+  flex: 0 0 auto;
+}
+
+:host > [hideScrollbarContainer]
+{
+  flex: 1 1 0;
+}
\ No newline at end of file
diff --git a/src/ui/regionHierachy/regionHierarchy.template.html b/src/ui/viewerStateController/regionHierachy/regionHierarchy.template.html
similarity index 69%
rename from src/ui/regionHierachy/regionHierarchy.template.html
rename to src/ui/viewerStateController/regionHierachy/regionHierarchy.template.html
index 91d9f552cb194227a8e4462ed6e9b82868e3f40c..5a55fdc8c328c231048001c9340687ebdf890f3b 100644
--- a/src/ui/regionHierachy/regionHierarchy.template.html
+++ b/src/ui/viewerStateController/regionHierachy/regionHierarchy.template.html
@@ -1,24 +1,18 @@
-<div class="input-group regionSearch">
+<mat-form-field class="w-100">
   <input 
     #searchTermInput
-    tabindex="0"
+    matInput
     (keydown.esc)="escape($event)"
-    (focus)="showRegionTree = true && !isMobile"
+    (focus)="showRegionTree = true"
     [value]="searchTerm"
-    class="form-control form-control-sm"
     type="text" 
     autocomplete="off"
-    [placeholder]="getInputPlaceholder(selectedParcellation)"/>
-
-</div>
+    [placeholder]="placeHolderText"/>
+</mat-form-field>
   
-<div
-  *ngIf="showRegionTree" 
-  hideScrollbarContainer>
-
+<div hideScrollbarContainer>
   <div
-    [ngStyle]="regionHierarchyHeight()"
-    class="d-flex flex-column"
+    class="d-flex flex-column h-100"
     treeContainer
     #treeContainer>
     <div class="tree-header d-inline-flex align-items-center">
@@ -34,7 +28,7 @@
     </div>
     
     <div
-      *ngIf="selectedParcellation && selectedParcellation.regions as regions"
+      *ngIf="parcellationSelected && parcellationSelected.regions as regions"
       class="tree-body">
       <flat-tree-component
         (treeNodeClick)="handleClickRegion($event)"
diff --git a/src/ui/viewerStateController/regionSearch/regionSearch.component.ts b/src/ui/viewerStateController/regionSearch/regionSearch.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c0e1783234cccfc6c9101b1f0fdc3290d8dea209
--- /dev/null
+++ b/src/ui/viewerStateController/regionSearch/regionSearch.component.ts
@@ -0,0 +1,149 @@
+import { Component, EventEmitter, Output, ViewChild, ElementRef, TemplateRef } from "@angular/core";
+import { Store, select } from "@ngrx/store";
+import { Observable } from "rxjs";
+import { map, distinctUntilChanged, startWith, withLatestFrom, filter, debounceTime, tap, share, shareReplay, take } from "rxjs/operators";
+import { getMultiNgIdsRegionsLabelIndexMap, generateLabelIndexId } from "src/services/stateStore.service";
+import { FormControl } from "@angular/forms";
+import { MatAutocompleteSelectedEvent, MatAutocompleteTrigger, MatDialog } from "@angular/material";
+import { ADD_TO_REGIONS_SELECTION_WITH_IDS, SELECT_REGIONS } from "src/services/state/viewerState.store";
+import { VIEWERSTATE_ACTION_TYPES } from "../viewerState.component";
+
+const filterRegionBasedOnText = searchTerm => region => region.name.toLowerCase().includes(searchTerm.toLowerCase())
+
+@Component({
+  selector: 'region-text-search-autocomplete',
+  templateUrl: './regionSearch.template.html',
+  styleUrls: [
+    './regionSearch.style.css'
+  ]
+})
+
+export class RegionTextSearchAutocomplete{
+
+  @ViewChild('autoTrigger', {read: ElementRef}) autoTrigger: ElementRef 
+  @ViewChild('regionHierarchy', {read:TemplateRef}) regionHierarchyTemplate: TemplateRef<any>
+  constructor(
+    private store$: Store<any>,
+    private dialog: MatDialog,
+  ){
+
+    const viewerState$ = this.store$.pipe(
+      select('viewerState'),
+      shareReplay(1)
+    )
+
+    this.regionsWithLabelIndex$ = viewerState$.pipe(
+      select('parcellationSelected'),
+      distinctUntilChanged(),
+      map(parcellationSelected => {
+        const returnArray = []
+        const ngIdMap = getMultiNgIdsRegionsLabelIndexMap(parcellationSelected)
+        for (const [ngId, labelIndexMap] of ngIdMap) {
+          for (const [labelIndex, region] of labelIndexMap){
+            returnArray.push({
+              ...region,
+              ngId,
+              labelIndex,
+              labelIndexId: generateLabelIndexId({ ngId, labelIndex })
+            })
+          }
+        }
+        return returnArray
+      })
+    ) 
+
+    this.autocompleteList$ = this.formControl.valueChanges.pipe(
+      startWith(''),
+      debounceTime(200),
+      withLatestFrom(this.regionsWithLabelIndex$.pipe(
+        startWith([])
+      )),
+      map(([searchTerm, regionsWithLabelIndex]) => regionsWithLabelIndex.filter(filterRegionBasedOnText(searchTerm))),
+      map(arr => arr.slice(0, 5))
+    )
+
+    this.regionsSelected$ = viewerState$.pipe(
+      select('regionsSelected'),
+      distinctUntilChanged(),
+      shareReplay(1)
+    )
+
+    this.parcellationSelected$ = viewerState$.pipe(
+      select('parcellationSelected'),
+      distinctUntilChanged(),
+      shareReplay(1)
+    )
+  }
+
+  public optionSelected(ev: MatAutocompleteSelectedEvent){
+    const id = ev.option.value
+    this.store$.dispatch({
+      type: ADD_TO_REGIONS_SELECTION_WITH_IDS,
+      selectRegionIds : [id]
+    })
+
+    this.autoTrigger.nativeElement.value = ''
+    this.autoTrigger.nativeElement.focus()
+  }
+
+  private regionsWithLabelIndex$: Observable<any[]>
+  public autocompleteList$: Observable<any[]>
+  public formControl = new FormControl()
+
+  public regionsSelected$: Observable<any>
+  public parcellationSelected$: Observable<any>
+
+
+  @Output()
+  public focusedStateChanged: EventEmitter<boolean> = new EventEmitter()
+
+  private _focused: boolean = false
+  set focused(val: boolean){
+    this._focused = val
+    this.focusedStateChanged.emit(val)
+  }
+  get focused(){
+    return this._focused
+  }
+
+  public deselectAllRegions(event: MouseEvent){
+    this.store$.dispatch({
+      type: SELECT_REGIONS,
+      selectRegions: []
+    })
+  }
+
+  // TODO handle mobile
+  handleRegionClick({ mode = null, region = null } = {}){
+    const type = mode === 'single'
+      ? VIEWERSTATE_ACTION_TYPES.SINGLE_CLICK_ON_REGIONHIERARCHY
+      : mode === 'double'
+        ? VIEWERSTATE_ACTION_TYPES.DOUBLE_CLICK_ON_REGIONHIERARCHY
+        : ''
+    this.store$.dispatch({
+      type,
+      payload: { region }
+    })
+  }
+
+  showHierarchy(event:MouseEvent){
+    const dialog = this.dialog.open(this.regionHierarchyTemplate, {
+      height: '90vh',
+      width: '90vw'
+    })
+
+    /**
+     * keep sleight of hand shown while modal is shown
+     * 
+     */
+    this.focused = true
+    
+    /**
+     * take 1 to avoid memory leak
+     */
+    dialog.afterClosed().pipe(
+      take(1)
+    ).subscribe(() => this.focused = false)
+  }
+
+}
\ No newline at end of file
diff --git a/src/ui/viewerStateController/regionSearch/regionSearch.style.css b/src/ui/viewerStateController/regionSearch/regionSearch.style.css
new file mode 100644
index 0000000000000000000000000000000000000000..17cda15a41ee862383f3f4e802121021525894f9
--- /dev/null
+++ b/src/ui/viewerStateController/regionSearch/regionSearch.style.css
@@ -0,0 +1,4 @@
+region-hierarchy
+{
+  height: 100%;
+}
\ No newline at end of file
diff --git a/src/ui/viewerStateController/regionSearch/regionSearch.template.html b/src/ui/viewerStateController/regionSearch/regionSearch.template.html
new file mode 100644
index 0000000000000000000000000000000000000000..ef828ac2e265761736d416066215e137c39b3053
--- /dev/null
+++ b/src/ui/viewerStateController/regionSearch/regionSearch.template.html
@@ -0,0 +1,45 @@
+<div class="d-flex flex-row align-items-center">
+
+  <form class="flex-grow-1 flex-shrink-1">
+    <mat-form-field class="w-100">
+      <input
+        placeholder="Regions"
+        #autoTrigger
+        #trigger="matAutocompleteTrigger"
+        type="text"
+        matInput
+        [formControl]="formControl"
+        [matAutocomplete]="auto">
+    </mat-form-field>
+    <mat-autocomplete
+      (opened)="focused = true"
+      (closed)="focused = false"
+      (optionSelected)="optionSelected($event)"
+      autoActiveFirstOption
+      #auto="matAutocomplete">
+      <mat-option
+        *ngFor="let region of autocompleteList$ | async"
+        [value]="region.labelIndexId">
+        {{ region.name }}
+      </mat-option>
+    </mat-autocomplete>
+  </form>
+    
+  <button
+    class="flex-grow-0 flex-shrink-0"
+    (click)="showHierarchy($event)"
+    mat-icon-button color="primary">
+    <i class="fas fa-sitemap"></i>
+  </button>
+</div>
+
+<ng-template #regionHierarchy>
+  <region-hierarchy
+    [selectedRegions]="regionsSelected$ | async | filterNull"
+    (singleClickRegion)="handleRegionClick({ mode: 'single', region: $event })"
+    (doubleClickRegion)="handleRegionClick({ mode: 'double', region: $event })"
+    (clearAllRegions)="deselectAllRegions($event)"
+    [parcellationSelected]="parcellationSelected$ | async">
+  
+  </region-hierarchy>
+</ng-template>
diff --git a/src/ui/viewerStateController/viewerState.component.ts b/src/ui/viewerStateController/viewerState.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..726c1e895802beea84b8fe44fa72ca0685ae159e
--- /dev/null
+++ b/src/ui/viewerStateController/viewerState.component.ts
@@ -0,0 +1,304 @@
+import { Component, ViewChild, TemplateRef, OnInit } from "@angular/core";
+import { Store, select } from "@ngrx/store";
+import { Observable, Subject, combineLatest, Subscription } from "rxjs";
+import { distinctUntilChanged, shareReplay, bufferTime, filter, map, withLatestFrom, delay, take, tap } from "rxjs/operators";
+import { SELECT_REGIONS, USER_CONFIG_ACTION_TYPES } from "src/services/stateStore.service";
+import { DESELECT_REGIONS, CHANGE_NAVIGATION } from "src/services/state/viewerState.store";
+import { ToastService } from "src/services/toastService.service";
+import { getSchemaIdFromName } from "src/util/pipes/templateParcellationDecoration.pipe";
+import { MatDialog, MatSelectChange, MatBottomSheet, MatBottomSheetRef } from "@angular/material";
+import { ExtraButton } from "src/components/radiolist/radiolist.component";
+import { DialogService } from "src/services/dialogService.service";
+import { RegionSelection } from "src/services/state/userConfigState.store";
+
+const compareWith = (o, n) => !o || !n
+  ? false
+  : o.name === n.name
+
+@Component({
+  selector: 'viewer-state-controller',
+  templateUrl: './viewerState.template.html',
+  styleUrls: [
+    './viewerState.style.css'
+  ]
+})
+
+export class ViewerStateController implements OnInit{
+
+  @ViewChild('publicationTemplate', {read:TemplateRef}) publicationTemplate: TemplateRef<any>
+  @ViewChild('savedRegionBottomSheetTemplate', {read:TemplateRef}) savedRegionBottomSheetTemplate: TemplateRef<any>
+
+  public focused: boolean = false
+
+  private subscriptions: Subscription[] = []
+
+  public availableTemplates$: Observable<any[]>
+  public availableParcellations$: Observable<any[]>
+
+  public templateSelected$: Observable<any>
+  public parcellationSelected$: Observable<any>
+  public regionsSelected$: Observable<any>
+
+  public savedRegionsSelections$: Observable<any[]>
+
+  public focusedDatasets$: Observable<any[]>
+  private userFocusedDataset$: Subject<any> = new Subject()
+  private dismissToastHandler: () => void
+
+  public compareWith = compareWith
+
+  private savedRegionBottomSheetRef: MatBottomSheetRef
+
+  constructor(
+    private store$: Store<any>,
+    private toastService: ToastService,
+    private dialogService: DialogService,
+    private bottomSheet: MatBottomSheet
+  ){
+    const viewerState$ = this.store$.pipe(
+      select('viewerState'),
+      shareReplay(1)
+    )
+
+    this.savedRegionsSelections$ = this.store$.pipe(
+      select('userConfigState'),
+      select('savedRegionsSelection'),
+      shareReplay(1)
+    )
+
+    this.templateSelected$ = viewerState$.pipe(
+      select('templateSelected'),
+      distinctUntilChanged()
+    )
+
+    this.parcellationSelected$ = viewerState$.pipe(
+      select('parcellationSelected'),
+      distinctUntilChanged(),
+      shareReplay(1)
+    )
+
+    this.regionsSelected$ = viewerState$.pipe(
+      select('regionsSelected'),
+      distinctUntilChanged(),
+      shareReplay(1)
+    )
+
+    this.availableTemplates$ = viewerState$.pipe(
+      select('fetchedTemplates'),
+      distinctUntilChanged()
+    )
+
+    this.availableParcellations$ = this.templateSelected$.pipe(
+      select('parcellations')
+    )
+    
+    this.focusedDatasets$ = this.userFocusedDataset$.pipe(
+      filter(v => !!v),
+      withLatestFrom(
+        combineLatest(this.templateSelected$, this.parcellationSelected$)
+      ),
+    ).pipe(
+      map(([userFocusedDataset, [selectedTemplate, selectedParcellation]]) => {
+        const { type, ...rest } = userFocusedDataset
+        if (type === 'template') return { ...selectedTemplate,  ...rest}
+        if (type === 'parcellation') return { ...selectedParcellation, ...rest }
+        return { ...rest }
+      }),
+      bufferTime(100),
+      filter(arr => arr.length > 0),
+      /**
+       * merge properties field with the root level
+       * with the prop in properties taking priority
+       */
+      map(arr => arr.map(item => {
+        const { properties } = item
+        return {
+          ...item,
+          ...properties
+        }
+      })),
+      shareReplay(1)
+    )
+  }
+
+  ngOnInit(){
+    this.subscriptions.push(
+      this.savedRegionsSelections$.pipe(
+        filter(srs => srs.length === 0)
+      ).subscribe(() => this.savedRegionBottomSheetRef && this.savedRegionBottomSheetRef.dismiss())
+    )
+    this.subscriptions.push(
+      this.focusedDatasets$.subscribe(() => this.dismissToastHandler && this.dismissToastHandler())
+    )
+    this.subscriptions.push(
+      this.focusedDatasets$.pipe(
+        /**
+         * creates the illusion that the toast complete disappears before reappearing
+         */
+        delay(100)
+      ).subscribe(() => this.dismissToastHandler = this.toastService.showToast(this.publicationTemplate, {
+        dismissable: true,
+        timeout:7000
+      }))
+    )
+  }
+
+  handleActiveDisplayBtnClicked(event: MouseEvent, type: 'parcellation' | 'template', extraBtn: ExtraButton, inputItem:any = {}){
+    const { name } = extraBtn
+    const { kgSchema, kgId } = getSchemaIdFromName(name)
+    this.userFocusedDataset$.next({
+      ...inputItem,
+      kgSchema,
+      kgId
+    })
+  }
+
+  handleTemplateChange(event:MatSelectChange){
+    
+    this.store$.dispatch({
+      type: ACTION_TYPES.SELECT_TEMPLATE_WITH_NAME,
+      payload: {
+        name: event.value
+      }
+    })
+  }
+
+  handleParcellationChange(event:MatSelectChange){
+    if (!event.value) return
+    this.store$.dispatch({
+      type: ACTION_TYPES.SELECT_PARCELLATION_WITH_NAME,
+      payload: {
+        name: event.value
+      }
+    })
+  }
+
+  loadSavedRegion(event:MouseEvent, savedRegionsSelection:RegionSelection){
+    this.store$.dispatch({
+      type: USER_CONFIG_ACTION_TYPES.LOAD_REGIONS_SELECTION,
+      payload: {
+        savedRegionsSelection
+      }
+    })
+  }
+
+  public editSavedRegion(event: MouseEvent, savedRegionsSelection: RegionSelection){
+    event.preventDefault()
+    event.stopPropagation()
+    this.dialogService.getUserInput({
+      defaultValue: savedRegionsSelection.name,
+      placeholder: `Enter new name`,
+      title: 'Edit name'
+    }).then(name => {
+      if (!name) throw new Error('user cancelled')
+      this.store$.dispatch({
+        type: USER_CONFIG_ACTION_TYPES.UPDATE_REGIONS_SELECTION,
+        payload: {
+          ...savedRegionsSelection,
+          name
+        }
+      })
+    }).catch(e => {
+      // TODO catch user cancel
+    })
+  }
+  public removeSavedRegion(event: MouseEvent, savedRegionsSelection: RegionSelection){
+    event.preventDefault()
+    event.stopPropagation()
+    this.store$.dispatch({
+      type: USER_CONFIG_ACTION_TYPES.DELETE_REGIONS_SELECTION,
+      payload: {
+        ...savedRegionsSelection
+      }
+    })
+  }
+
+
+  displayActiveParcellation(parcellation:any){
+    return `<div class="d-flex"><small>Parcellation</small> <small class = "flex-grow-1 mute-text">${parcellation ? '(' + parcellation.name + ')' : ''}</small> <span class = "fas fa-caret-down"></span></div>`
+  }
+
+  displayActiveTemplate(template: any) {
+    return `<div class="d-flex"><small>Template</small> <small class = "flex-grow-1 mute-text">${template ? '(' + template.name + ')' : ''}</small> <span class = "fas fa-caret-down"></span></div>`
+  }
+
+  public loadSelection(event: MouseEvent){
+    this.focused = true
+    
+    this.savedRegionBottomSheetRef = this.bottomSheet.open(this.savedRegionBottomSheetTemplate)
+    this.savedRegionBottomSheetRef.afterDismissed()
+      .subscribe(val => {
+        
+      }, error => {
+
+      }, () => {
+        this.focused = false
+        this.savedRegionBottomSheetRef = null
+      })
+  }
+
+  public saveSelection(event: MouseEvent){
+    this.focused = true
+    this.dialogService.getUserInput({
+      defaultValue: `Saved Region`,
+      placeholder: `Name the selection`,
+      title: 'Save region selection'
+    })
+      .then(name => {
+        if (!name) throw new Error('User cancelled')
+        this.store$.dispatch({
+          type: USER_CONFIG_ACTION_TYPES.SAVE_REGIONS_SELECTION,
+          payload: { name }
+        })
+      })
+      .catch(e => {
+        /**
+         * USER CANCELLED, HANDLE
+         */
+      })
+      .finally(() => this.focused = false)
+  }
+
+  public deselectAllRegions(event: MouseEvent){
+    this.store$.dispatch({
+      type: SELECT_REGIONS,
+      selectRegions: []
+    })
+  }
+
+  public deselectRegion(event: MouseEvent, region: any){
+    this.store$.dispatch({
+      type: DESELECT_REGIONS,
+      deselectRegions: [region]
+    })
+  }
+
+  public gotoRegion(event: MouseEvent, region:any){
+    if (region.position) {
+      this.store$.dispatch({
+        type: CHANGE_NAVIGATION,
+        navigation: {
+          position: region.position,
+          animation: {}
+        }
+      })
+    } else {
+      /**
+       * TODO convert to snack bar
+       */
+      this.toastService.showToast(`${region.name} does not have a position defined`, {
+        timeout: 5000,
+        dismissable: true
+      })
+    }
+  }
+}
+
+const ACTION_TYPES = {
+  SINGLE_CLICK_ON_REGIONHIERARCHY: 'SINGLE_CLICK_ON_REGIONHIERARCHY',
+  DOUBLE_CLICK_ON_REGIONHIERARCHY: 'DOUBLE_CLICK_ON_REGIONHIERARCHY',
+  SELECT_TEMPLATE_WITH_NAME: 'SELECT_TEMPLATE_WITH_NAME',
+  SELECT_PARCELLATION_WITH_NAME: 'SELECT_PARCELLATION_WITH_NAME',
+}
+
+export const VIEWERSTATE_ACTION_TYPES = ACTION_TYPES
diff --git a/src/ui/viewerStateController/viewerState.pipes.ts b/src/ui/viewerStateController/viewerState.pipes.ts
new file mode 100644
index 0000000000000000000000000000000000000000..659d35778f3378966cb58f82d47f789e9ca95d89
--- /dev/null
+++ b/src/ui/viewerStateController/viewerState.pipes.ts
@@ -0,0 +1,38 @@
+import { Pipe, PipeTransform } from "@angular/core";
+import { RegionSelection } from "src/services/state/userConfigState.store";
+
+@Pipe({
+  name: 'binSavedRegionsSelectionPipe'
+})
+
+export class BinSavedRegionsSelectionPipe implements PipeTransform{
+  public transform(regionSelections:RegionSelection[]):{parcellationSelected:any, templateSelected:any, regionSelections: RegionSelection[]}[]{
+    const returnMap = new Map()
+    for (let regionSelection of regionSelections){
+      const key = `${regionSelection.templateSelected.name}\n${regionSelection.parcellationSelected.name}`
+      const existing = returnMap.get(key)
+      if (existing) existing.push(regionSelection)
+      else returnMap.set(key, [regionSelection])
+    }
+    return Array.from(returnMap)
+      .map(([_, regionSelections]) => {
+        const {parcellationSelected = null, templateSelected = null} = regionSelections[0] || {}
+        return {
+          regionSelections,
+          parcellationSelected,
+          templateSelected
+        }
+      })
+  }
+}
+
+@Pipe({
+  name: 'savedRegionsSelectionBtnDisabledPipe'
+})
+
+export class SavedRegionsSelectionBtnDisabledPipe implements PipeTransform{
+  public transform(regionSelection: RegionSelection, templateSelected: any, parcellationSelected: any): boolean{
+    return regionSelection.parcellationSelected.name !== parcellationSelected.name
+      || regionSelection.templateSelected.name !== templateSelected.name
+  }
+}
\ No newline at end of file
diff --git a/src/ui/viewerStateController/viewerState.style.css b/src/ui/viewerStateController/viewerState.style.css
new file mode 100644
index 0000000000000000000000000000000000000000..0da6d0ea50d5d1df72cd96e1c3bd1273b6c9741b
--- /dev/null
+++ b/src/ui/viewerStateController/viewerState.style.css
@@ -0,0 +1,35 @@
+.virtual-scroll-viewport-container
+{
+  height: 20em;
+  width: 20em;
+  overflow: hidden;
+}
+
+.virtual-scroll-viewport-container > cdk-virtual-scroll-viewport
+{
+  width: 100%;
+  height: 100%;
+  box-sizing: content-box;
+  padding-right: 3em;
+}
+
+.virtual-scroll-row
+{
+  width: 20em;
+}
+
+/* required to match virtual scroll itemSize property */
+.virtual-scroll-unit
+{
+  height: 26px
+}
+
+.selected-region-container
+{
+  flex: 1 1 auto;
+}
+
+.selected-region-actionbtn
+{
+  flex: 0 0 auto;
+}
diff --git a/src/ui/viewerStateController/viewerState.template.html b/src/ui/viewerStateController/viewerState.template.html
new file mode 100644
index 0000000000000000000000000000000000000000..9e1718231d4037b57fefcd2155df8bc51e12e362
--- /dev/null
+++ b/src/ui/viewerStateController/viewerState.template.html
@@ -0,0 +1,202 @@
+<mat-card>
+
+  <!-- template selection -->
+  <mat-form-field>
+    <mat-label>
+      Template
+    </mat-label>
+    <mat-select
+      [value]="(templateSelected$ | async)?.name"
+      (selectionChange)="handleTemplateChange($event)"
+      (openedChange)="focused = $event">
+      <mat-option
+        *ngFor="let template of (availableTemplates$ | async)"
+        [value]="template.name">
+        {{ template.name }}
+      </mat-option>
+    </mat-select>
+  </mat-form-field>
+  <ng-container *ngIf="templateSelected$ | async as templateSelected">
+    <ng-container *ngIf="(templateSelected | templateParcellationsDecorationPipe)?.extraButtons as extraButtons">
+      <button
+        *ngFor="let extraBtn of extraButtons"
+        (click)="handleActiveDisplayBtnClicked($event, 'template', extraBtn, templateSelected)"
+        mat-icon-button>
+        <i [class]="extraBtn.faIcon"></i>
+      </button>
+    </ng-container>
+  </ng-container>
+
+  <!-- parcellation selection -->
+  <mat-form-field *ngIf="templateSelected$ | async as templateSelected">
+    <mat-label>
+      Parcellation
+    </mat-label>
+    <mat-select
+      (selectionChange)="handleParcellationChange($event)"
+      [value]="(parcellationSelected$ | async)?.name"
+      (openedChange)="focused = $event">
+      <mat-option
+        *ngFor="let parcellation of (templateSelected.parcellations | appendTooltipTextPipe)"
+        [value]="parcellation.name">
+        {{ parcellation.name }}
+      </mat-option>
+    </mat-select>
+  </mat-form-field>
+
+  <ng-container *ngIf="parcellationSelected$ | async as parcellationSelected">
+    <ng-container *ngIf="(parcellationSelected | templateParcellationsDecorationPipe)?.extraButtons as extraButtons">
+      <button
+        *ngFor="let extraBtn of extraButtons"
+        (click)="handleActiveDisplayBtnClicked($event, 'parcellation', extraBtn, parcellationSelected)"
+        mat-icon-button>
+        <i [class]="extraBtn.faIcon"></i>
+      </button>
+    </ng-container>
+  </ng-container>
+
+  <!-- divider -->
+  <mat-divider></mat-divider>
+
+  <!-- selected regions -->
+
+  <div class="d-flex">
+    <region-text-search-autocomplete
+      (focusedStateChanged)="focused = $event">
+    </region-text-search-autocomplete>
+  </div>
+
+  <!-- chips -->
+  <mat-card class="w-20em mh-10em overflow-auto overflow-x-hidden">
+    <mat-chip-list class="mat-chip-list-stacked" #selectedRegionsChipList>
+      <mat-chip class="w-100" *ngFor="let region of (regionsSelected$ | async)">
+        <span class="flex-grow-1 flex-shrink-1 text-truncate">
+          {{ region.name }}
+        </span>
+        <button
+          *ngIf="region.position"
+          (click)="gotoRegion($event, region)"
+          mat-icon-button>
+          <i class="fas fa-map-marked-alt"></i>
+        </button>
+        <button
+          (click)="deselectRegion($event, region)"
+          mat-icon-button>
+          <i class="fas fa-trash"></i>
+        </button>
+      </mat-chip>
+    </mat-chip-list>
+
+    <!-- place holder when no regions has been selected -->
+    <span class="muted"  *ngIf="(regionsSelected$ | async).length === 0">
+      No regions selected. Double click on any regions in the viewer, or use the search tool to select regions of interest.
+    </span>
+  </mat-card>
+
+  <!-- control btns -->
+  <div class="mt-2 mb-2 d-flex justify-content-between">
+    <div class="d-flex">
+
+      <!-- save  -->
+      <button
+        matTooltip="Save this selection of regions"
+        matTooltipPosition="below"
+        mat-button
+        (click)="saveSelection($event)"
+        color="primary">
+        <i class="fas fa-save"></i>
+        
+      </button>
+
+      <!-- load -->
+      <button
+        (click)="loadSelection($event)"
+        matTooltip="Load a selection of regions"
+        matTooltipPosition="below"
+        mat-button
+        color="primary"
+        [disabled]="(savedRegionsSelections$ | async)?.length === 0">
+        <i
+          matBadgeColor="accent"
+          [matBadgeOverlap]="false"
+          [matBadge]="(savedRegionsSelections$ | async)?.length > 0 ? (savedRegionsSelections$ | async)?.length : null"
+          class="fas fa-folder-open"></i>
+        
+      </button>
+    </div>
+
+    <!-- deselect all  -->
+    <button
+      (click)="deselectAllRegions($event)"
+      matTooltip="Deselect all selected regions"
+      matTooltipPosition="below"
+      mat-raised-button
+      color="warn"
+      [disabled]="(regionsSelected$ | async)?.length === 0">
+      <i class="fas fa-trash"></i>
+    </button>
+  </div>
+</mat-card>
+
+<ng-template #publicationTemplate>
+  <single-dataset-view
+    *ngFor="let focusedDataset of (focusedDatasets$ | async)"
+    [name]="focusedDataset.name"
+    [description]="focusedDataset.description"
+    [publications]="focusedDataset.publications"
+    [kgSchema]="focusedDataset.kgSchema"
+    [kgId]="focusedDataset.kgId">
+    
+  </single-dataset-view>
+</ng-template>
+
+<!-- bottom sheet for saved regions  -->
+<ng-template #savedRegionBottomSheetTemplate>
+  <mat-action-list>
+
+    <!-- separated (binned) by template/parcellation -->
+    <ng-container *ngFor="let binnedRS of (savedRegionsSelections$ | async | binSavedRegionsSelectionPipe); let index = index">
+
+      <!-- only render divider if it is not the leading element -->
+      <mat-divider *ngIf="index !== 0"></mat-divider>
+
+      <!-- header -->
+      <h3 mat-subheader>
+        {{ binnedRS.templateSelected.name }} / {{ binnedRS.parcellationSelected.name }}
+      </h3>
+
+      <!-- ng for all saved regions -->
+      <button
+        *ngFor="let savedRegionsSelection of binnedRS.regionSelections"
+        (click)="loadSavedRegion($event, savedRegionsSelection)"
+        mat-list-item>
+        <!-- [class]="savedRegionsSelection | savedRegionsSelectionBtnDisabledPipe : (templateSelected$ | async) : (parcellationSelected$ | async) ? 'text-muted' : ''" -->
+        <!-- [disabled]="savedRegionsSelection | savedRegionsSelectionBtnDisabledPipe : (templateSelected$ | async) : (parcellationSelected$ | async)" -->
+        <!-- main content -->
+        <span class="flex-grow-0 flex-shrink-1">
+          {{ savedRegionsSelection.name }}
+        </span>
+        <small class="ml-1 mr-1 text-muted flex-grow-1 flex-shrink-0">
+          ({{ savedRegionsSelection.regionsSelected.length }} selected regions)
+        </small>
+
+        <!-- edit btn -->
+        <button
+          (mousedown)="$event.stopPropagation()"
+          (click)="editSavedRegion($event, savedRegionsSelection)"
+          mat-icon-button>
+          <i class="fas fa-edit"></i>
+        </button>
+
+        <!-- trash btn -->
+        <button
+          (mousedown)="$event.stopPropagation()"
+          (click)="removeSavedRegion($event, savedRegionsSelection)"
+          mat-icon-button
+          color="warn">
+          <i class="fas fa-trash"></i>
+        </button>
+      </button>
+    </ng-container>
+  </mat-action-list>
+</ng-template>
\ No newline at end of file
diff --git a/src/ui/viewerStateController/viewerState.useEffect.ts b/src/ui/viewerStateController/viewerState.useEffect.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4c8232e1c4509088bf1ac287bdd5057508505bef
--- /dev/null
+++ b/src/ui/viewerStateController/viewerState.useEffect.ts
@@ -0,0 +1,180 @@
+import { Subscription, Observable } from "rxjs";
+import { Injectable, OnInit, OnDestroy } from "@angular/core";
+import { Actions, ofType, Effect } from "@ngrx/effects";
+import { Store, select, Action } from "@ngrx/store";
+import { ToastService } from "src/services/toastService.service";
+import { shareReplay, distinctUntilChanged, map, withLatestFrom, filter } from "rxjs/operators";
+import { VIEWERSTATE_ACTION_TYPES } from "./viewerState.component";
+import { CHANGE_NAVIGATION, SELECT_REGIONS, NEWVIEWER, GENERAL_ACTION_TYPES, SELECT_PARCELLATION, isDefined } from "src/services/stateStore.service";
+import { regionFlattener } from "src/util/regionFlattener";
+
+@Injectable({
+  providedIn: 'root'
+})
+
+export class ViewerStateControllerUseEffect implements OnInit, OnDestroy{
+
+  private subscriptions: Subscription[] = []
+
+  private selectedRegions$: Observable<any[]>
+
+  @Effect()
+  singleClickOnHierarchy$: Observable<any>
+
+  @Effect()
+  selectTemplateWithName$: Observable<any>
+  
+  @Effect()
+  selectParcellationWithName$: Observable<any>
+
+  doubleClickOnHierarchy$: Observable<any>
+
+  constructor(
+    private actions$: Actions,
+    private store$: Store<any>,
+    private toastService: ToastService
+  ){
+    const viewerState$ = this.store$.pipe(
+      select('viewerState'),
+      shareReplay(1)
+    )
+
+    this.selectedRegions$ = viewerState$.pipe(
+      select('regionsSelected'),
+      distinctUntilChanged()
+    )
+
+    this.selectParcellationWithName$ = this.actions$.pipe(
+      ofType(VIEWERSTATE_ACTION_TYPES.SELECT_PARCELLATION_WITH_NAME),
+      map(action => {
+        const { payload = {} } = action as ViewerStateAction
+        const { name } = payload
+        return name
+      }),
+      filter(name => !!name),
+      withLatestFrom(viewerState$.pipe(
+        select('parcellationSelected')
+      )),
+      filter(([name,  parcellationSelected]) => {
+        if (parcellationSelected && parcellationSelected.name === name) return false
+        return true
+      }),
+      map(([name,  _]) => name),
+      withLatestFrom(viewerState$.pipe(
+        select('templateSelected')
+      )),
+      map(([name, templateSelected]) => {
+
+        const { parcellations: availableParcellations } = templateSelected
+        const newParcellation = availableParcellations.find(t => t.name === name)
+        if (!newParcellation) {
+          return {
+            type: GENERAL_ACTION_TYPES.ERROR,
+            payload: {
+              message: 'Selected parcellation not found.'
+            }
+          }
+        }
+        return {
+          type: SELECT_PARCELLATION,
+          selectParcellation: newParcellation
+        }
+      })
+    )
+    
+    this.selectTemplateWithName$ = this.actions$.pipe(
+      ofType(VIEWERSTATE_ACTION_TYPES.SELECT_TEMPLATE_WITH_NAME),
+      map(action => {
+        const { payload = {} } = action as ViewerStateAction
+        const { name } = payload
+        return name
+      }),
+      filter(name => !!name),
+      withLatestFrom(viewerState$.pipe(
+        select('templateSelected')
+      )),
+      filter(([name,  templateSelected]) => {
+        if (templateSelected && templateSelected.name === name) return false
+        return true
+      }),
+      map(([name,  templateSelected]) => name),
+      withLatestFrom(viewerState$.pipe(
+        select('fetchedTemplates')
+      )),
+      map(([name, availableTemplates]) => {
+        const newTemplateTobeSelected = availableTemplates.find(t => t.name === name)
+        if (!newTemplateTobeSelected) {
+          return {
+            type: GENERAL_ACTION_TYPES.ERROR,
+            payload: {
+              message: 'Selected template not found.'
+            }
+          }
+        }
+        return {
+          type: NEWVIEWER,
+          selectTemplate: newTemplateTobeSelected,
+          selectParcellation: newTemplateTobeSelected.parcellations[0]
+        }
+      })
+    )
+
+    this.doubleClickOnHierarchy$ = this.actions$.pipe(
+      ofType(VIEWERSTATE_ACTION_TYPES.DOUBLE_CLICK_ON_REGIONHIERARCHY)
+    )
+
+    this.singleClickOnHierarchy$ = this.actions$.pipe(
+      ofType(VIEWERSTATE_ACTION_TYPES.SINGLE_CLICK_ON_REGIONHIERARCHY),
+      withLatestFrom(this.selectedRegions$),
+      map(([action, regionsSelected]) => {
+
+        const {payload = {}} = action as ViewerStateAction
+        const { region } = payload
+
+        const flattenedRegion = regionFlattener(region).filter(r => isDefined(r.labelIndex))
+        const flattenedRegionNames = new Set(flattenedRegion.map(r => r.name))
+        const selectedRegionNames = new Set(regionsSelected.map(r => r.name))
+        const selectAll = flattenedRegion.every(r => !selectedRegionNames.has(r.name))
+        return {
+          type: SELECT_REGIONS,
+          selectRegions: selectAll
+            ? regionsSelected.concat(flattenedRegion)
+            : regionsSelected.filter(r => !flattenedRegionNames.has(r.name))
+        }
+      })
+    )
+  }
+
+  ngOnInit(){
+    this.subscriptions.push(
+      this.doubleClickOnHierarchy$.subscribe(({ region } = {}) => {
+        const { position } = region
+        if (position) {
+          this.store$.dispatch({
+            type: CHANGE_NAVIGATION,
+            navigation: {
+              position,
+              animation: {}
+            }
+          })
+        } else {
+          this.toastService.showToast(`${region.name} does not have a position defined`, {
+            timeout: 5000,
+            dismissable: true
+          })
+        }
+      })
+    )
+  }
+
+  ngOnDestroy(){
+    while(this.subscriptions.length > 0) {
+      this.subscriptions.pop().unsubscribe()
+    }
+  }
+}
+
+interface ViewerStateAction extends Action{
+  payload: any
+  config: any
+}
\ No newline at end of file
diff --git a/src/util/pipes/filterNgLayer.pipe.ts b/src/util/pipes/filterNgLayer.pipe.ts
deleted file mode 100644
index 798075159645feed923f9cca4ac750afc8ac4ebc..0000000000000000000000000000000000000000
--- a/src/util/pipes/filterNgLayer.pipe.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import { Pipe, PipeTransform } from "@angular/core";
-import { NgLayerInterface } from "src/atlasViewer/atlasViewer.component";
-
-/**
- * TODO deprecate
- * use regular pipe to achieve the same effect
- */
-
-@Pipe({
-  name: 'filterNgLayer'
-})
-
-export class FilterNgLayer implements PipeTransform{
-  public transform(excludedLayers: string[] = [], ngLayers: NgLayerInterface[]): NgLayerInterface[] {
-    const set = new Set(excludedLayers)
-    return ngLayers.filter(l => !set.has(l.name))
-  }
-}
\ No newline at end of file
diff --git a/src/util/pipes/getFileExt.pipe.ts b/src/util/pipes/getFileExt.pipe.ts
new file mode 100644
index 0000000000000000000000000000000000000000..aea77ceba51c36451836366656529eef661a852c
--- /dev/null
+++ b/src/util/pipes/getFileExt.pipe.ts
@@ -0,0 +1,36 @@
+import { PipeTransform, Pipe } from "@angular/core";
+
+const NIFTI = `NIFTI Volume`
+const VTK = `VTK Mesh`
+
+const extMap = new Map([
+  ['.nii', NIFTI],
+  ['.nii.gz', NIFTI],
+  ['.vtk', VTK]
+])
+
+@Pipe({
+  name: 'getFileExtension'
+})
+
+export class GetFileExtension implements PipeTransform{
+  private regex: RegExp = new RegExp('(\\.[\\w\\.]*?)$')
+
+  private getRegexp(ext){
+    return new RegExp(`${ext.replace(/\./g, '\\.')}$`, 'i')
+  }
+
+  private detFileExt(filename:string):string{
+    for (let [key, val] of extMap){
+      if(this.getRegexp(key).test(filename)){
+        return val
+      }
+    }
+    return filename
+  }
+
+  public transform(filename:string):string{
+    return this.detFileExt(filename)
+  }
+}
+
diff --git a/src/util/pipes/getFileNameFromPathName.pipe.ts b/src/util/pipes/getFileNameFromPathName.pipe.ts
deleted file mode 100644
index d64a96c1c9de83ef55c9efa7f3f43e0181b9fe21..0000000000000000000000000000000000000000
--- a/src/util/pipes/getFileNameFromPathName.pipe.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import { Pipe, PipeTransform } from "@angular/core";
-
-
-@Pipe({
-  name : 'getFilenameFromPathname'
-})
-
-export class GetFilenameFromPathnamePipe implements PipeTransform{
-  public transform(pathname:string):string{
-    return pathname.split('/')[pathname.split('/').length - 1]
-  }
-}
\ No newline at end of file
diff --git a/src/util/pipes/getFilename.pipe.ts b/src/util/pipes/getFilename.pipe.ts
new file mode 100644
index 0000000000000000000000000000000000000000..76afea5627e1ce05a7f527727f99084f30557291
--- /dev/null
+++ b/src/util/pipes/getFilename.pipe.ts
@@ -0,0 +1,14 @@
+import { PipeTransform, Pipe } from "@angular/core";
+
+@Pipe({
+  name: 'getFilenamePipe'
+})
+
+export class GetFilenamePipe implements PipeTransform{
+  private regex: RegExp = new RegExp('[\\/\\\\]([\\w\\.]*?)$')
+  public transform(fullname: string): string{
+    return this.regex.test(fullname)
+      ? this.regex.exec(fullname)[1]
+      : fullname
+  }
+}
\ No newline at end of file
diff --git a/src/util/pipes/kgSearchBtnColor.pipe.ts b/src/util/pipes/kgSearchBtnColor.pipe.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c9bbb25bf655bbe4d5e90ef08768f058fae915f2
--- /dev/null
+++ b/src/util/pipes/kgSearchBtnColor.pipe.ts
@@ -0,0 +1,14 @@
+import { Pipe, PipeTransform } from "@angular/core";
+import { WidgetUnit } from "src/atlasViewer/widgetUnit/widgetUnit.component";
+
+@Pipe({
+  name: 'kgSearchBtnColorPipe'
+})
+
+export class KgSearchBtnColorPipe implements PipeTransform{
+  public transform([minimisedWidgetUnit, themedBtnCls]: [Set<WidgetUnit>, string], wu: WidgetUnit ){
+    return minimisedWidgetUnit.has(wu)
+      ? 'primary'
+      : 'accent'
+  }
+}
\ No newline at end of file
diff --git a/src/util/pipes/pluginBtnFabColor.pipe.ts b/src/util/pipes/pluginBtnFabColor.pipe.ts
new file mode 100644
index 0000000000000000000000000000000000000000..bfb11d4ae59e41107e8fa258be73a122c1b1de7c
--- /dev/null
+++ b/src/util/pipes/pluginBtnFabColor.pipe.ts
@@ -0,0 +1,15 @@
+import { Pipe, PipeTransform } from "@angular/core";
+
+@Pipe({
+  name: 'pluginBtnFabColorPipe'
+})
+
+export class PluginBtnFabColorPipe implements PipeTransform{
+  public transform([launchedSet, minimisedSet, themedBtnCls], pluginName){
+    return minimisedSet.has(pluginName)
+      ? 'primary'
+      : launchedSet.has(pluginName)
+        ? 'accent'
+        : 'basic'
+  }
+}
\ No newline at end of file
diff --git a/webpack.staticassets.js b/webpack.staticassets.js
index cad3e8a0d4b3861ba397d125e8c1f3276dacb1b5..05119ba58f3c746b113b673b7abb41849d63bfab 100644
--- a/webpack.staticassets.js
+++ b/webpack.staticassets.js
@@ -1,8 +1,19 @@
 const webpack = require('webpack')
+const MiniCssExtractPlugin = require('mini-css-extract-plugin')
 
 module.exports = {
   module : {
     rules : [
+      {
+        test: /\.scss$/,
+        use: [
+          {
+            loader: MiniCssExtractPlugin.loader
+          },
+          'css-loader',
+          'sass-loader'
+        ]
+      },
       {
         test : /jpg|png/,
         exclude : /export\_nehuba|index/,
@@ -49,6 +60,9 @@ module.exports = {
     ]
   },
   plugins : [
+    new MiniCssExtractPlugin({
+      filename: 'theme.css'
+    }),
     new webpack.DefinePlugin({
       PLUGINDEV : process.env.PLUGINDEV
         ? JSON.stringify(process.env.PLUGINDEV)
@@ -67,5 +81,10 @@ module.exports = {
       BACKEND_URL: JSON.stringify(process.env.BACKEND_URL || 'http://localhost:3000/')
     })
     // ...ignoreArr.map(dirname => new webpack.IgnorePlugin(/\.\/plugin_examples/))
-  ]
+  ],
+  resolve: {
+    extensions: [
+      '.scss'
+    ]
+  }
 }
\ No newline at end of file