diff --git a/deploy/datasets/index.js b/deploy/datasets/index.js
index 1ab3efac04ce2a1bb9d4b7e12317830fe61e9905..13dc40ccd9ffbdfa7d4f9f2b8004d1ec1c968960 100644
--- a/deploy/datasets/index.js
+++ b/deploy/datasets/index.js
@@ -6,6 +6,7 @@ const { init, getDatasets, getPreview, getDatasetFromId, getDatasetFileAsZip, ge
 const { retry } = require('./util')
 const url = require('url')
 const qs = require('querystring')
+const archiver = require('archiver')
 
 const bodyParser = require('body-parser')
 
@@ -181,4 +182,34 @@ datasetsRouter.get('/downloadKgFiles', checkKgQuery, async (req, res) => {
   }
 })
 
+datasetsRouter.post('/bulkDownloadKgFiles', bodyParser.urlencoded({ extended: false }), async (req, res) => {
+  try{
+    const { body = {}, user } = req
+    const { kgIds } = body
+    if (!kgIds) throw new Error(`kgIds needs to be populated`)
+    const arrKgIds = JSON.parse(kgIds)
+    if (!Array.isArray(arrKgIds)) {
+      throw new Error(`kgIds needs to be an array`)
+    }
+    if (arrKgIds.length === 0) {
+      throw new Error(`There needs to be at least 1 kgId in kgIds`)
+    }
+    const zip = archiver('zip')
+    for (const kgId of arrKgIds) {
+      zip.append(
+        await getDatasetFileAsZip({ user, kgId }),
+        {
+          name: `${kgId}.zip`
+        }
+      )
+    }
+    zip.finalize()
+    res.setHeader('Content-disposition', `attachment; filename="bulkDsDownload.zip"`)
+    res.setHeader('Content-Type', 'application/zip')
+    zip.pipe(res)
+  }catch(e){
+    res.status(400).send(e.toString())
+  }
+})
+
 module.exports = datasetsRouter
\ No newline at end of file
diff --git a/e2e/src/advanced/favDatasets.e2e-spec.js b/e2e/src/advanced/favDatasets.e2e-spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..0a90936c3bd505f68ed4a34c11da39c4b7a00416
--- /dev/null
+++ b/e2e/src/advanced/favDatasets.e2e-spec.js
@@ -0,0 +1,205 @@
+const { AtlasPage } = require('../util')
+
+const template = 'ICBM 2009c Nonlinear Asymmetric'
+const area = 'Area hOc1 (V1, 17, CalcS)'
+
+const receptorName = `Density measurements of different receptors for Area hOc1`
+const pmapName = `Probabilistic cytoarchitectonic map of Area hOc1 (V1, 17, CalcS) (v2.4)`
+
+// TODO finish writing tests
+describe(`fav'ing dataset`, () => {
+  let iavPage
+  beforeEach(async () => {
+    iavPage = new AtlasPage()
+    await iavPage.init()
+    await iavPage.goto()
+    await iavPage.selectTitleCard(template)
+    await iavPage.wait(500)
+    await iavPage.waitUntilAllChunksLoaded()
+  })
+
+  afterEach(async () => {
+    await iavPage.clearAlerts()
+    await iavPage.wait(500)
+
+    try {
+      await iavPage.clearSearchRegionWithText()
+      await iavPage.wait(500)
+      await iavPage.clearAllSelectedRegions()
+    }catch(e) {
+
+    }
+
+    try {
+      await iavPage.showPinnedDatasetPanel()
+      const textsArr = await iavPage.getPinnedDatasetsFromOpenedPanel()
+      let length = textsArr.length
+      while(length > 0){
+        await iavPage.unpinNthDatasetFromOpenedPanel(0)
+        length --
+      }
+    }catch(e){
+
+    }
+
+    await iavPage.wait(500)
+  })
+
+  it('> dataset can be fav ed from result panel', async () => {
+
+    await iavPage.searchRegionWithText(area)
+    await iavPage.wait(2000)
+    await iavPage.selectSearchRegionAutocompleteWithText()
+    await iavPage.dismissModal()
+    await iavPage.searchRegionWithText('')
+
+    const datasets = await iavPage.getVisibleDatasets()
+
+    const receptorIndex = datasets.indexOf(receptorName)
+    const probMap = datasets.indexOf(pmapName)
+    expect(receptorIndex).toBeGreaterThanOrEqual(0)
+    expect(probMap).toBeGreaterThanOrEqual(0)
+
+    await iavPage.togglePinNthDataset(receptorIndex)
+    await iavPage.wait(500)
+    const txt = await iavPage.getSnackbarMessage()
+    expect(txt).toEqual(`Pinned dataset: ${receptorName}`)
+    
+    await iavPage.togglePinNthDataset(probMap)
+    await iavPage.wait(500)
+    const txt2 = await iavPage.getSnackbarMessage()
+    expect(txt2).toEqual(`Pinned dataset: ${pmapName}`)
+  })
+
+
+  describe('> fav dataset list', () => {
+    beforeEach(async () => {
+
+      await iavPage.searchRegionWithText(area)
+      await iavPage.wait(2000)
+      await iavPage.selectSearchRegionAutocompleteWithText()
+      await iavPage.dismissModal()
+      await iavPage.searchRegionWithText('')
+
+      const datasets = await iavPage.getVisibleDatasets()
+
+      const receptorIndex = datasets.indexOf(receptorName)
+      const probMap = datasets.indexOf(pmapName)
+
+      await iavPage.togglePinNthDataset(receptorIndex)
+      await iavPage.togglePinNthDataset(probMap)
+    })
+
+    it('> fav ed dataset is visible on UI', async () => {
+      const number = await iavPage.getNumberOfFavDataset()
+      expect(number).toEqual(2)
+    })
+
+    it('> clicking pin shows pinned datasets', async () => {
+      await iavPage.showPinnedDatasetPanel()
+      await iavPage.wait(500)
+      const textsArr = await iavPage.getPinnedDatasetsFromOpenedPanel()
+      
+      expect(textsArr.length).toEqual(2)
+      expect(textsArr).toContain(receptorName)
+      expect(textsArr).toContain(pmapName)
+    })
+
+    it('> click unpin in fav data panel unpins, but also allow user to undo', async () => {
+      await iavPage.showPinnedDatasetPanel()
+      await iavPage.wait(1000)
+      const textsArr = await iavPage.getPinnedDatasetsFromOpenedPanel()
+      const idx = textsArr.indexOf(receptorName)
+      if (idx < 0) throw new Error(`index of receptor name not found: ${receptorName}: ${textsArr}`)
+      await iavPage.unpinNthDatasetFromOpenedPanel(idx)
+      await iavPage.wait(500)
+  
+      const txt = await iavPage.getSnackbarMessage()
+      expect(txt).toEqual(`Unpinned dataset: ${receptorName}`)
+  
+      const number = await iavPage.getNumberOfFavDataset()
+      expect(number).toEqual(1)
+
+      await iavPage.clickSnackbarAction()
+      await iavPage.wait(500)
+
+      const textsArr3 = await iavPage.getPinnedDatasetsFromOpenedPanel()
+      const number2 = await iavPage.getNumberOfFavDataset()
+      expect(number2).toEqual(2)
+      expect(
+        textsArr3.indexOf(receptorName)
+      ).toBeGreaterThanOrEqual(0)
+
+    })
+
+    // TODO effectively test the bulk dl button
+    // it('> if fav dataset >=1, bulk dl btn is visible', async () => {
+
+    // })
+  })
+
+  describe('> fav functionality in detailed dataset panel', () => {
+    beforeEach(async () => {
+
+      await iavPage.searchRegionWithText(area)
+      await iavPage.wait(2000)
+      await iavPage.selectSearchRegionAutocompleteWithText()
+      await iavPage.dismissModal()
+      await iavPage.searchRegionWithText('')
+
+
+    })
+    it('> click pin in dataset detail sheet pins fav, but also allows user to undo', async () => {
+      
+      const datasets = await iavPage.getVisibleDatasets()
+
+      const receptorIndex = datasets.indexOf(receptorName)
+      await iavPage.clickNthDataset(receptorIndex)
+      await iavPage.wait(500)
+      await iavPage.clickModalBtnByText(/pin\ this\ dataset/i)
+      await iavPage.wait(500)
+
+      const txt = await iavPage.getSnackbarMessage()
+      expect(txt).toEqual(`Pinned dataset: ${receptorName}`)
+
+      const number = await iavPage.getNumberOfFavDataset()
+      expect(number).toEqual(1)
+
+      await iavPage.clickSnackbarAction()
+      await iavPage.wait(500)
+
+      const number2 = await iavPage.getNumberOfFavDataset()
+      expect(number2).toEqual(0)
+    })
+
+    it('click unpin in dataset detail sheet unpins fav, but also allows user to undo', async () => {
+
+      const datasets = await iavPage.getVisibleDatasets()
+
+      const receptorIndex = datasets.indexOf(receptorName)
+      await iavPage.clickNthDataset(receptorIndex)
+      await iavPage.wait(500)
+      await iavPage.clickModalBtnByText(/pin\ this\ dataset/i)
+      await iavPage.wait(500)
+
+      const numberOfFav = await iavPage.getNumberOfFavDataset()
+      expect(numberOfFav).toEqual(1)
+
+      await iavPage.clickModalBtnByText(/unpin\ this\ dataset/i)
+      await iavPage.wait(500)
+
+      const txt = await iavPage.getSnackbarMessage()
+      expect(txt).toEqual(`Unpinned dataset: ${receptorName}`)
+
+      const numberOfFav1 = await iavPage.getNumberOfFavDataset()
+      expect(numberOfFav1).toEqual(0)
+
+      await iavPage.clickSnackbarAction()
+      await iavPage.wait(500)
+
+
+      const numberOfFav2 = await iavPage.getNumberOfFavDataset()
+      expect(numberOfFav2).toEqual(1)
+    })
+  })
+})
diff --git a/e2e/src/util.js b/e2e/src/util.js
index 281cdcfe5101dc744c1f2a4e1761024f9ad8a40e..d1128db02fe640a53ed542fcb44f20adc0bf3aca 100644
--- a/e2e/src/util.js
+++ b/e2e/src/util.js
@@ -127,6 +127,33 @@ class WdBase{
     if (!ms) throw new Error(`wait duration must be specified!`)
     await this._browser.sleep(ms)
   }
+
+  async getSnackbarMessage(){
+    const txt = await this._driver
+      .findElement( By.tagName('simple-snack-bar') )
+      .findElement( By.tagName('span') )
+      .getText()
+    return txt
+  }
+
+  async clickSnackbarAction(){
+    await this._driver
+      .findElement( By.tagName('simple-snack-bar') )
+      .findElement( By.tagName('button') )
+      .click()
+  }
+
+  async clearAlerts() {
+    await this._driver
+      .actions()
+      .sendKeys(
+        Key.ESCAPE,
+        Key.ESCAPE,
+        Key.ESCAPE,
+        Key.ESCAPE
+      )
+      .perform()
+  }
 }
 
 class WdLayoutPage extends WdBase{
@@ -135,10 +162,13 @@ class WdLayoutPage extends WdBase{
     super()
   }
 
+  _getModal(){
+    return this._browser.findElement( By.tagName('mat-dialog-container') )
+  }
+
   async dismissModal() {
     try {
-      const okBtn = await this._browser
-        .findElement( By.tagName('mat-dialog-container') )
+      const okBtn = await this._getModal()
         .findElement( By.tagName('mat-dialog-actions') )
         .findElement( By.css('button[color="primary"]') )
       await okBtn.click()
@@ -147,6 +177,42 @@ class WdLayoutPage extends WdBase{
     }
   }
 
+  _getModalBtns(){
+    return this._getModal()
+      .findElement( By.tagName('mat-card-actions') )
+      .findElements( By.tagName('button') )
+  }
+
+  async getModalActions(){
+    const btns = await this._getModalBtns()
+
+    const arr = []
+    for (const btn of btns){
+      arr.push(await _getTextFromWebElement(btn))
+    }
+    return arr
+  }
+
+  // text can be instance of regex or string
+  async clickModalBtnByText(text){
+    const btns = await this._getModalBtns()
+    const arr = await this.getModalActions()
+    if (typeof text === 'string') {
+      const idx = arr.indexOf(text)
+      if (idx < 0) throw new Error(`clickModalBtnByText: ${text} not found.`)
+      await btns[idx].click()
+      return
+    }
+    if (text instanceof RegExp) {
+      const idx = arr.findIndex(item => text.test(item))
+      if (idx < 0) throw new Error(`clickModalBtnByText: regexp ${text.toString()} not found`)
+      await btns[idx].click()
+      return
+    }
+
+    throw new Error(`clickModalBtnByText arg must be instance of string or regexp`)
+  }
+
   async _findTitleCard(title) {
     const titleCards = await this._browser
       .findElement( By.tagName('ui-splashscreen') )
@@ -273,6 +339,55 @@ class WdLayoutPage extends WdBase{
       )
       .click()
   }
+
+  // Signin banner
+  _getFavDatasetIcon(){
+    return this._driver
+      .findElement( By.css('[aria-label="Show pinned datasets"]') )
+  }
+
+  async getNumberOfFavDataset(){
+    const attr = await this._getFavDatasetIcon().getAttribute('pinned-datasets-length')
+    return Number(attr)
+  }
+
+  async showPinnedDatasetPanel(){
+    await this._getFavDatasetIcon().click()
+    await this.wait(500)
+  }
+
+  _getPinnedDatasetPanel(){
+    return this._driver
+      .findElement(
+        By.css('[aria-label="Pinned datasets panel"]')
+      )
+  }
+
+  async getPinnedDatasetsFromOpenedPanel(){
+    const list = await this._getPinnedDatasetPanel()
+      .findElements(
+        By.tagName('mat-list-item')
+      )
+
+    const returnArr = []
+    for (const el of list) {
+      const text = await _getTextFromWebElement(el)
+      returnArr.push(text)
+    }
+    return returnArr
+  }
+
+  async unpinNthDatasetFromOpenedPanel(index){
+    const list = await this._getPinnedDatasetPanel()
+      .findElements(
+        By.tagName('mat-list-item')
+      )
+
+    if (!list[index]) throw new Error(`index out of bound: ${index} in list with size ${list.length}`)
+    await list[index]
+      .findElement( By.css('[aria-label="Toggle pinning this dataset"]') )
+      .click()
+  }
 }
 
 class WdIavPage extends WdLayoutPage{
@@ -379,11 +494,15 @@ class WdIavPage extends WdLayoutPage{
     }
   }
 
-  async getVisibleDatasets() {
-    const singleDatasetListView = await this._browser
+  _getSingleDatasetListView(){
+    return this._browser
       .findElement( By.tagName('data-browser') )
       .findElement( By.css('div.cdk-virtual-scroll-content-wrapper') )
       .findElements( By.tagName('single-dataset-list-view') )
+  }
+
+  async getVisibleDatasets() {
+    const singleDatasetListView = await this._getSingleDatasetListView()
 
     const returnArr = []
     for (const item of singleDatasetListView) {
@@ -392,6 +511,21 @@ class WdIavPage extends WdLayoutPage{
     return returnArr
   }
 
+  async clickNthDataset(index){
+    if (!Number.isInteger(index)) throw new Error(`index needs to be an integer`)
+    const list = await this._getSingleDatasetListView()
+    await list[index].click()
+  }
+
+  async togglePinNthDataset(index) {
+    if (!Number.isInteger(index)) throw new Error(`index needs to be an integer`)
+    const list = await this._getSingleDatasetListView()
+    if (!list[index]) throw new Error(`out of bound ${index} in list with length ${list.length}`)
+    await list[index]
+      .findElement( By.css('[aria-label="Toggle pinning this dataset"]') )
+      .click()
+  }
+
   async viewerIsPopulated() {
     const ngContainer = await this._browser.findElement(
       By.id('neuroglancer-container')
diff --git a/src/services/state/dataStore.store.ts b/src/services/state/dataStore.store.ts
index e9a9e301375cc1923fe5833bb3001e79821276f3..5d16c11a604d0d01b6db3913c05c998840dad3bd 100644
--- a/src/services/state/dataStore.store.ts
+++ b/src/services/state/dataStore.store.ts
@@ -1,5 +1,6 @@
 import { Action } from '@ngrx/store'
 import { GENERAL_ACTION_TYPES } from '../stateStore.service'
+import { LOCAL_STORAGE_CONST } from 'src/util/constants'
 
 /**
  * TODO merge with databrowser.usereffect.ts
@@ -12,14 +13,27 @@ export interface DatasetPreview {
 
 export interface IStateInterface {
   fetchedDataEntries: IDataEntry[]
-  favDataEntries: IDataEntry[]
+  favDataEntries: Partial<IDataEntry>[]
   fetchedSpatialData: IDataEntry[]
   datasetPreviews: DatasetPreview[]
 }
 
+
+
 export const defaultState = {
   fetchedDataEntries: [],
-  favDataEntries: [],
+  favDataEntries: (() => {
+    try {
+      const saved = localStorage.getItem(LOCAL_STORAGE_CONST.FAV_DATASET)
+      const arr = JSON.parse(saved) as any[]
+      return arr.every(item => item && !!item.fullId)
+        ? arr
+        : []
+    } catch (e) {
+      // TODO propagate error
+      return []
+    }
+  })(),
   fetchedSpatialData: [],
   datasetPreviews: [],
 }
diff --git a/src/ui/databrowserModule/bulkDownload/bulkDownloadBtn.component.ts b/src/ui/databrowserModule/bulkDownload/bulkDownloadBtn.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..28dbf168600ae2902fba295f54b8d0672319237d
--- /dev/null
+++ b/src/ui/databrowserModule/bulkDownload/bulkDownloadBtn.component.ts
@@ -0,0 +1,53 @@
+import { Component, Input, OnChanges, Pipe, PipeTransform, ChangeDetectionStrategy } from "@angular/core";
+import { AtlasViewerConstantsServices } from "../singleDataset/singleDataset.base";
+import { IDataEntry } from "src/services/stateStore.service";
+import { getKgSchemaIdFromFullId } from "../util/getKgSchemaIdFromFullId.pipe";
+
+const ARIA_LABEL_HAS_DOWNLOAD = `Bulk download all favourited datasets`
+const ARIA_LABEL_HAS_NO_DOWNLOAD = `No favourite datasets to download`
+
+@Component({
+  selector: 'iav-datamodule-bulkdownload-cmp',
+  templateUrl: './bulkDownloadBtn.template.html',
+  changeDetection: ChangeDetectionStrategy.OnPush
+})
+
+export class BulkDownloadBtn implements OnChanges{
+  @Input()
+  kgSchema = 'minds/core/dataset/v1.0.0'
+
+  @Input()
+  kgIds: string[] = []
+
+  public postUrl: string
+  public stringifiedKgIds: string = `[]`
+  public ariaLabel = ARIA_LABEL_HAS_DOWNLOAD
+
+  constructor(
+    constantService: AtlasViewerConstantsServices
+  ){
+    const _url = new URL(`datasets/bulkDownloadKgFiles`, constantService.backendUrl)
+    this.postUrl = _url.toString()
+  }
+
+  ngOnChanges(){
+    this.stringifiedKgIds = JSON.stringify(this.kgIds)
+    this.ariaLabel = this.kgIds.length === 0
+      ? ARIA_LABEL_HAS_NO_DOWNLOAD
+      : ARIA_LABEL_HAS_DOWNLOAD
+  }
+}
+
+@Pipe({
+  name: 'iavDatamoduleTransformDsToIdPipe'
+})
+
+export class TransformDatasetToIdPipe implements PipeTransform{
+  public transform(datasets: IDataEntry[]): string[]{
+    return datasets.map(({ fullId }) => {
+      const re = getKgSchemaIdFromFullId(fullId)
+      if (re) return re[1]
+      else return null
+    })
+  }
+}
diff --git a/src/ui/databrowserModule/bulkDownload/bulkDownloadBtn.template.html b/src/ui/databrowserModule/bulkDownload/bulkDownloadBtn.template.html
new file mode 100644
index 0000000000000000000000000000000000000000..6e2543715588361e3a65bc857c037edce88bdbf4
--- /dev/null
+++ b/src/ui/databrowserModule/bulkDownload/bulkDownloadBtn.template.html
@@ -0,0 +1,20 @@
+<form [action]="postUrl"
+  method="POST"
+  target="_blank"
+  #bulkDlForm>
+  <input
+    hidden
+    [value]="stringifiedKgIds"
+    type="text"
+    name="kgIds"
+    id="kgIds"
+    readonly="readonly">
+
+    <button mat-icon-button
+      [attr.aria-label]="ariaLabel"
+      [ngClass]="{'text-muted': kgIds.length === 0}"
+      [matTooltip]="kgIds.length === 0 ? 'No favourite datasets to download' : 'Bulk download all favourited datasets'"
+      (click)="kgIds.length === 0 ? null : bulkDlForm.submit()">
+      <i class="fas fa-download"></i>
+    </button>
+</form>
\ No newline at end of file
diff --git a/src/ui/databrowserModule/databrowser.module.ts b/src/ui/databrowserModule/databrowser.module.ts
index e2691ed9f9b9088343111934b521167d2a3857b4..c0dd1fd10c3ba6bb94dd8bb61426c9fc09472407 100644
--- a/src/ui/databrowserModule/databrowser.module.ts
+++ b/src/ui/databrowserModule/databrowser.module.ts
@@ -27,6 +27,7 @@ import { ResetCounterModalityPipe } from "./util/resetCounterModality.pipe";
 import { PreviewFileVisibleInSelectedReferenceTemplatePipe } from "./util/previewFileDisabledByReferenceSpace.pipe";
 import { DatasetPreviewList, UnavailableTooltip } from "./singleDataset/datasetPreviews/datasetPreviewsList/datasetPreviewList.component";
 import { PreviewComponentWrapper } from "./preview/previewComponentWrapper/previewCW.component";
+import { BulkDownloadBtn, TransformDatasetToIdPipe } from "./bulkDownload/bulkDownloadBtn.component";
 
 @NgModule({
   imports: [
@@ -44,6 +45,7 @@ import { PreviewComponentWrapper } from "./preview/previewComponentWrapper/previ
     SingleDatasetListView,
     DatasetPreviewList,
     PreviewComponentWrapper,
+    BulkDownloadBtn,
 
     /**
      * pipes
@@ -63,6 +65,7 @@ import { PreviewComponentWrapper } from "./preview/previewComponentWrapper/previ
     ResetCounterModalityPipe,
     PreviewFileVisibleInSelectedReferenceTemplatePipe,
     UnavailableTooltip,
+    TransformDatasetToIdPipe,
   ],
   exports: [
     DataBrowser,
@@ -71,6 +74,8 @@ import { PreviewComponentWrapper } from "./preview/previewComponentWrapper/previ
     ModalityPicker,
     FilterDataEntriesbyMethods,
     GetKgSchemaIdFromFullIdPipe,
+    BulkDownloadBtn,
+    TransformDatasetToIdPipe,
   ],
   entryComponents: [
     DataBrowser,
diff --git a/src/ui/databrowserModule/databrowser.service.ts b/src/ui/databrowserModule/databrowser.service.ts
index b635bd463ad63894356af48e3af1ed94bf72d898..de480e44d03abed477e4df8a4699fd2de6f9a34a 100644
--- a/src/ui/databrowserModule/databrowser.service.ts
+++ b/src/ui/databrowserModule/databrowser.service.ts
@@ -202,21 +202,21 @@ export class DatabrowserService implements OnDestroy {
     this.subscriptions.forEach(s => s.unsubscribe())
   }
 
-  public toggleFav(dataentry: IDataEntry) {
+  public toggleFav(dataentry: Partial<IDataEntry>) {
     this.store.dispatch({
       type: DATASETS_ACTIONS_TYPES.TOGGLE_FAV_DATASET,
       payload: dataentry,
     })
   }
 
-  public saveToFav(dataentry: IDataEntry) {
+  public saveToFav(dataentry: Partial<IDataEntry>) {
     this.store.dispatch({
       type: DATASETS_ACTIONS_TYPES.FAV_DATASET,
       payload: dataentry,
     })
   }
 
-  public removeFromFav(dataentry: IDataEntry) {
+  public removeFromFav(dataentry: Partial<IDataEntry>) {
     this.store.dispatch({
       type: DATASETS_ACTIONS_TYPES.UNFAV_DATASET,
       payload: dataentry,
diff --git a/src/ui/databrowserModule/databrowser.useEffect.ts b/src/ui/databrowserModule/databrowser.useEffect.ts
index 05a4c8d5ba6b58266d7b956ec45ced17189db2ff..f202ab20cd8015d4351e994a5869a7a24f281d18 100644
--- a/src/ui/databrowserModule/databrowser.useEffect.ts
+++ b/src/ui/databrowserModule/databrowser.useEffect.ts
@@ -2,12 +2,11 @@ import { Injectable, OnDestroy } from "@angular/core";
 import { Actions, Effect, ofType } from "@ngrx/effects";
 import { select, Store } from "@ngrx/store";
 import { from, merge, Observable, of, Subscription, forkJoin, combineLatest } from "rxjs";
-import { catchError, filter, map, scan, switchMap, withLatestFrom, mapTo, shareReplay, startWith, distinctUntilChanged } from "rxjs/operators";
+import { filter, map, scan, switchMap, withLatestFrom, mapTo, shareReplay, startWith, distinctUntilChanged } from "rxjs/operators";
 import { LoggingService } from "src/services/logging.service";
 import { DATASETS_ACTIONS_TYPES, IDataEntry, ViewerPreviewFile } from "src/services/state/dataStore.store";
 import { IavRootStoreInterface, ADD_NG_LAYER, CHANGE_NAVIGATION } from "src/services/stateStore.service";
 import { LOCAL_STORAGE_CONST, DS_PREVIEW_URL } from "src/util/constants";
-import { getIdFromDataEntry } from "./databrowser.service";
 import { KgSingleDatasetService } from "./kgSingleDatasetService.service";
 import { determinePreviewFileType, PREVIEW_FILE_TYPES, PREVIEW_FILE_TYPES_NO_UI } from "./preview/previewFileIcon.pipe";
 import { GLSL_COLORMAP_JET } from "src/atlasViewer/atlasViewer.constantService.service";
@@ -15,26 +14,9 @@ import { SHOW_BOTTOM_SHEET } from "src/services/state/uiState.store";
 import { MatSnackBar } from "@angular/material/snack-bar";
 import { MatDialog } from "@angular/material/dialog";
 import { PreviewComponentWrapper } from "./preview/previewComponentWrapper/previewCW.component";
-import { GetKgSchemaIdFromFullIdPipe } from "./util/getKgSchemaIdFromFullId.pipe";
+import { getKgSchemaIdFromFullId } from "./util/getKgSchemaIdFromFullId.pipe";
 import { HttpClient } from "@angular/common/http";
 
-const 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 of(null)
-  }),
-)
-
-const getSchemaIdFromPipe = new GetKgSchemaIdFromFullIdPipe()
-
 @Injectable({
   providedIn: 'root',
 })
@@ -80,7 +62,7 @@ export class DataBrowserUseEffect implements OnDestroy {
         filter(datasetPreviews => datasetPreviews.length > 0),
         map((datasetPreviews) => datasetPreviews[datasetPreviews.length - 1]),
         switchMap(({ datasetId, filename }) =>{
-          const re = getSchemaIdFromPipe.transform(datasetId)
+          const re = getKgSchemaIdFromFullId(datasetId)
           return this.http.get(`${DATASET_PREVIEW_URL}/${re[1]}/${filename}`).pipe(
             filter((file: any) => PREVIEW_FILE_TYPES_NO_UI.indexOf( determinePreviewFileType(file) ) < 0),
             mapTo({
@@ -93,7 +75,7 @@ export class DataBrowserUseEffect implements OnDestroy {
         
         // TODO replace with common/util/getIdFromFullId
         
-        const re = getSchemaIdFromPipe.transform(datasetId)
+        const re = getKgSchemaIdFromFullId(datasetId)
         this.dialog.open(
           PreviewComponentWrapper,
           {
@@ -123,7 +105,7 @@ export class DataBrowserUseEffect implements OnDestroy {
       switchMap((arr: any[]) => {
         return merge(
           ... (arr.map(({ datasetId, filename }) => {
-            const re = getSchemaIdFromPipe.transform(datasetId)
+            const re = getKgSchemaIdFromFullId(datasetId)
             if (!re) throw new Error(`datasetId ${datasetId} does not follow organisation/domain/schema/version/uuid rule`)
   
             return forkJoin(
@@ -184,7 +166,7 @@ export class DataBrowserUseEffect implements OnDestroy {
       )
     ).pipe(
       map(([templateSelected, arr]) => {
-        const re = getSchemaIdFromPipe.transform(
+        const re = getKgSchemaIdFromFullId(
           (templateSelected && templateSelected.fullId) || ''
         )
         const templateId = re && re[1]
@@ -192,7 +174,7 @@ export class DataBrowserUseEffect implements OnDestroy {
           return determinePreviewFileType(file) === PREVIEW_FILE_TYPES.VOLUMES
             && file.referenceSpaces.findIndex(({ fullId }) => {
               if (fullId === '*') return true
-              const regex = getSchemaIdFromPipe.transform(fullId)
+              const regex = getKgSchemaIdFromFullId(fullId)
               const fileReferenceTemplateId = regex && regex[1]
               if (!fileReferenceTemplateId) return false
               return fileReferenceTemplateId === templateId
@@ -244,13 +226,25 @@ export class DataBrowserUseEffect implements OnDestroy {
       withLatestFrom(this.favDataEntries$),
       map(([action, prevFavDataEntries]) => {
         const { payload = {} } = action as any
-        const { id } = payload
+        const { fullId } = payload
+
+        const re1 = getKgSchemaIdFromFullId(fullId)
 
-        const wasFav = prevFavDataEntries.findIndex(ds => ds.id === id) >= 0
+        if (!re1) {
+          return {
+            type: DATASETS_ACTIONS_TYPES.UPDATE_FAV_DATASETS,
+            favDataEntries: DATASETS_ACTIONS_TYPES.UPDATE_FAV_DATASETS,
+          }
+        }
+        const favIdx = prevFavDataEntries.findIndex(ds => {
+          const re2 = getKgSchemaIdFromFullId(ds.fullId)
+          if (!re2) return false
+          return re2[1] === re1[1]
+        })
         return {
           type: DATASETS_ACTIONS_TYPES.UPDATE_FAV_DATASETS,
-          favDataEntries: wasFav
-            ? prevFavDataEntries.filter(ds => ds.id !== id)
+          favDataEntries: favIdx >= 0
+            ? prevFavDataEntries.filter((_, idx) => idx !== favIdx)
             : prevFavDataEntries.concat(payload),
         }
       }),
@@ -262,10 +256,18 @@ export class DataBrowserUseEffect implements OnDestroy {
       map(([action, prevFavDataEntries]) => {
 
         const { payload = {} } = action as any
-        const { id } = payload
+        const { fullId } = payload
+
+        const re1 = getKgSchemaIdFromFullId(fullId)
+
         return {
           type: DATASETS_ACTIONS_TYPES.UPDATE_FAV_DATASETS,
-          favDataEntries: prevFavDataEntries.filter(ds => ds.id !== id),
+          favDataEntries: prevFavDataEntries.filter(ds => {
+            const re2 = getKgSchemaIdFromFullId(ds.fullId)
+            if (!re2) return false
+            if (!re1) return true
+            return re2[1] !== re1[1]
+          }),
         }
       }),
     )
@@ -279,7 +281,21 @@ export class DataBrowserUseEffect implements OnDestroy {
         /**
          * check duplicate
          */
-        const favDataEntries = prevFavDataEntries.find(favDEs => favDEs.id === payload.id)
+        const { fullId } = payload
+        const re1 = getKgSchemaIdFromFullId(fullId)
+        if (!re1) {
+          return {
+            type: DATASETS_ACTIONS_TYPES.UPDATE_FAV_DATASETS,
+            favDataEntries: prevFavDataEntries,
+          }
+        }
+
+        const isDuplicate = prevFavDataEntries.some(favDe => {
+          const re2 = getKgSchemaIdFromFullId(favDe.fullId)
+          if (!re2) return false
+          return re1[1] === re2[1]
+        })
+        const favDataEntries = isDuplicate
           ? prevFavDataEntries
           : prevFavDataEntries.concat(payload)
 
@@ -300,54 +316,12 @@ export class DataBrowserUseEffect implements OnDestroy {
          *
          * 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 }
+        const serialisedFavDataentries = favDataEntries.map(({ fullId }) => {
+          return { fullId }
         })
         window.localStorage.setItem(LOCAL_STORAGE_CONST.FAV_DATASET, JSON.stringify(serialisedFavDataentries))
       }),
     )
-
-    this.savedFav$ = savedFav$
-
-    this.onInitGetFav$ = this.savedFav$.pipe(
-      filter(v => !!v),
-      switchMap(arr =>
-        merge(
-          ...arr.map(({ id: kgId }) =>
-            from( this.kgSingleDatasetService.getInfoFromKg({ kgId })).pipe(
-              catchError(err => {
-                this.log.log(`fetchInfoFromKg error`, err)
-                return of(null)
-              }),
-              switchMap(dataset =>
-                this.kgSingleDatasetService.datasetHasPreview(dataset).pipe(
-                  catchError(err => {
-                    this.log.log(`fetching hasPreview error`, err)
-                    return of({})
-                  }),
-                  map(resp => {
-                    return {
-                      ...dataset,
-                      ...resp,
-                    }
-                  }),
-                ),
-              ),
-            ),
-          ),
-        ).pipe(
-          filter(v => !!v),
-          scan((acc, curr) => acc.concat(curr), []),
-        ),
-      ),
-      map(favDataEntries => {
-        return {
-          type: DATASETS_ACTIONS_TYPES.UPDATE_FAV_DATASETS,
-          favDataEntries,
-        }
-      }),
-    )
   }
 
   public ngOnDestroy() {
@@ -358,9 +332,6 @@ export class DataBrowserUseEffect implements OnDestroy {
 
   private savedFav$: Observable<Array<{id: string, name: string}> | null>
 
-  @Effect()
-  public onInitGetFav$: Observable<any>
-
   private favDataEntries$: Observable<IDataEntry[]>
 
   @Effect()
diff --git a/src/ui/databrowserModule/databrowser/databrowser.style.css b/src/ui/databrowserModule/databrowser/databrowser.style.css
index 0a854b80f0892e5c1f9fd387efb5d16652d140f7..1180d17e0281ed33a5cb1b5ba71240216e38207e 100644
--- a/src/ui/databrowserModule/databrowser/databrowser.style.css
+++ b/src/ui/databrowserModule/databrowser/databrowser.style.css
@@ -1,7 +1,6 @@
 modality-picker
 {
   font-size: 90%;
-  display:inline-block;
 }
 
 radio-list
diff --git a/src/ui/databrowserModule/kgSingleDatasetService.service.ts b/src/ui/databrowserModule/kgSingleDatasetService.service.ts
index 9544ef15f53013ffac9e3d238bb7b5ca2678ef74..0ad31491acbe3797dc07d376842a028381b304ec 100644
--- a/src/ui/databrowserModule/kgSingleDatasetService.service.ts
+++ b/src/ui/databrowserModule/kgSingleDatasetService.service.ts
@@ -15,8 +15,6 @@ export class KgSingleDatasetService implements OnDestroy {
   private subscriptions: Subscription[] = []
   public ngLayers: Set<string> = new Set()
 
-  private getKgSchemaIdFromFullIdPipe: GetKgSchemaIdFromFullIdPipe = new GetKgSchemaIdFromFullIdPipe()
-
   constructor(
     private constantService: AtlasViewerConstantsServices,
     private store$: Store<IavRootStoreInterface>,
@@ -93,14 +91,6 @@ export class KgSingleDatasetService implements OnDestroy {
       },
     })
   }
-
-  public getKgSchemaKgIdFromFullId(fullId: string) {
-    const match = this.getKgSchemaIdFromFullIdPipe.transform(fullId)
-    return match && {
-      kgSchema: match[0],
-      kgId: match[1],
-    }
-  }
 }
 
 interface KgQueryInterface {
diff --git a/src/ui/databrowserModule/modalityPicker/modalityPicker.style.css b/src/ui/databrowserModule/modalityPicker/modalityPicker.style.css
index df41aab07fc807183ce87f68846d37ea65578ec1..85feb59e81b8a38ede569688b605d82e39932cca 100644
--- a/src/ui/databrowserModule/modalityPicker/modalityPicker.style.css
+++ b/src/ui/databrowserModule/modalityPicker/modalityPicker.style.css
@@ -1,3 +1,9 @@
+:host
+{
+  display: flex;
+  flex-direction: column;
+}
+
 div
 {
   white-space: nowrap;
diff --git a/src/ui/databrowserModule/singleDataset/detailedView/singleDataset.component.ts b/src/ui/databrowserModule/singleDataset/detailedView/singleDataset.component.ts
index 6659e3791e4fed9af7cddfc986efb86b75e239db..e76e30ae9ce96d2575ea0013be013a9aa4d5900a 100644
--- a/src/ui/databrowserModule/singleDataset/detailedView/singleDataset.component.ts
+++ b/src/ui/databrowserModule/singleDataset/detailedView/singleDataset.component.ts
@@ -5,6 +5,7 @@ import {
   SingleDatasetBase,
 } from "../singleDataset.base";
 import {MAT_DIALOG_DATA} from "@angular/material/dialog";
+import { MatSnackBar } from "@angular/material/snack-bar";
 
 @Component({
   selector: 'single-dataset-view',
@@ -21,10 +22,10 @@ export class SingleDatasetView extends SingleDatasetBase {
     dbService: DatabrowserService,
     singleDatasetService: KgSingleDatasetService,
     cdr: ChangeDetectorRef,
-
+    snackbar: MatSnackBar,
     @Optional() @Inject(MAT_DIALOG_DATA) data: any,
   ) {
-    super(dbService, singleDatasetService, cdr, data)
+    super(dbService, singleDatasetService, cdr,snackbar, data)
   }
 
 }
diff --git a/src/ui/databrowserModule/singleDataset/detailedView/singleDataset.template.html b/src/ui/databrowserModule/singleDataset/detailedView/singleDataset.template.html
index c473ac7849449ff477d1fc641056f402ef84dff8..993958bec3737b6311782c62075a22da6c962afc 100644
--- a/src/ui/databrowserModule/singleDataset/detailedView/singleDataset.template.html
+++ b/src/ui/databrowserModule/singleDataset/detailedView/singleDataset.template.html
@@ -1,16 +1,28 @@
 
 <!-- title -->
 <mat-card-subtitle>
-  {{ name }}
+  <span *ngIf="name; else nameLoading">
+    {{ name }}
+  </span>
+  <ng-template #nameLoading>
+    <div class="spinnerAnimationCircle"></div>
+  </ng-template>
 </mat-card-subtitle>
 
 <mat-card-content mat-dialog-content>
 
   <!-- description -->
   <small>
-    <markdown-dom class="d-block" [markdown]="description">
+    <markdown-dom
+      *ngIf="description; else descriptionLoading"
+      class="d-block"
+      [markdown]="description">
 
     </markdown-dom>
+
+    <ng-template #descriptionLoading>
+      <div class="d-inline-block spinnerAnimationCircle"></div>
+    </ng-template>
   </small>
 
   <!-- publications -->
@@ -46,15 +58,32 @@
   </a>
 
   <!-- pin data -->
-  <button mat-button
-    *ngIf="downloadEnabled"
-    iav-stop="click mousedown"
-    (click)="toggleFav()"
-    color="primary"
-    [color]="(favedDataentries$ | async | datasetIsFaved : dataset) ? 'primary' : 'basic'">
-    {{ (favedDataentries$ | async | datasetIsFaved : dataset) ? 'Unpin' : 'Pin' }} this dataset
-    <i class="fas fa-thumbtack"></i>
-  </button>
+  <ng-container *ngIf="downloadEnabled && kgId">
+
+    <!-- Is currently fav'ed -->
+    <ng-container *ngIf="favedDataentries$ | async | datasetIsFaved : dataset; else notCurrentlyFav">
+
+      <button mat-button
+        iav-stop="click mousedown"
+        (click)="undoableRemoveFav()"
+        color="primary">
+        Unpin this dataset
+        <i class="fas fa-thumbtack"></i>
+      </button>
+    </ng-container>
+
+    <!-- Is NOT currently fav'ed -->
+    <ng-template #notCurrentlyFav>
+      
+      <button mat-button
+        iav-stop="click mousedown"
+        (click)="undoableAddFav()"
+        color="default">
+        Pin this dataset
+        <i class="fas fa-thumbtack"></i>
+      </button>
+    </ng-template>
+  </ng-container>
 
 
   <!-- download -->
diff --git a/src/ui/databrowserModule/singleDataset/listView/singleDatasetListView.component.ts b/src/ui/databrowserModule/singleDataset/listView/singleDatasetListView.component.ts
index e30630467500748ffd1075ee20066794eeb28542..be8e778626e3f6d81aaa408e0a6c1c4b0a1e94e8 100644
--- a/src/ui/databrowserModule/singleDataset/listView/singleDatasetListView.component.ts
+++ b/src/ui/databrowserModule/singleDataset/listView/singleDatasetListView.component.ts
@@ -19,44 +19,21 @@ import {MatSnackBar} from "@angular/material/snack-bar";
 export class SingleDatasetListView extends SingleDatasetBase {
 
   constructor(
-    private _dbService: DatabrowserService,
+    _dbService: DatabrowserService,
     singleDatasetService: KgSingleDatasetService,
     cdr: ChangeDetectorRef,
     private dialog: MatDialog,
-    private snackBar: MatSnackBar,
+    snackBar: MatSnackBar,
   ) {
-    super(_dbService, singleDatasetService, cdr)
+    super(_dbService, singleDatasetService, cdr, snackBar)
   }
 
   public showDetailInfo() {
     this.dialog.open(SingleDatasetView, {
-      data: this.dataset,
+      autoFocus: false,
+      data: {
+        fullId: this.fullId
+      },
     })
   }
-
-  public undoableRemoveFav() {
-    this.snackBar.open(`Unpinned dataset: ${this.dataset.name}`, 'Undo', {
-      duration: 5000,
-    })
-      .afterDismissed()
-      .subscribe(({ dismissedByAction }) => {
-        if (dismissedByAction) {
-          this._dbService.saveToFav(this.dataset)
-        }
-      })
-    this._dbService.removeFromFav(this.dataset)
-  }
-
-  public undoableAddFav() {
-    this.snackBar.open(`Pin dataset: ${this.dataset.name}`, 'Undo', {
-      duration: 5000,
-    })
-      .afterDismissed()
-      .subscribe(({ dismissedByAction }) => {
-        if (dismissedByAction) {
-          this._dbService.removeFromFav(this.dataset)
-        }
-      })
-    this._dbService.saveToFav(this.dataset)
-  }
 }
diff --git a/src/ui/databrowserModule/singleDataset/listView/singleDatasetListView.template.html b/src/ui/databrowserModule/singleDataset/listView/singleDatasetListView.template.html
index ac442f6602363777ea6a667217a66f2da9f82f46..b8256849438468a91c3b92ff37ffaca0a81faae0 100644
--- a/src/ui/databrowserModule/singleDataset/listView/singleDatasetListView.template.html
+++ b/src/ui/databrowserModule/singleDataset/listView/singleDatasetListView.template.html
@@ -6,9 +6,12 @@
 
   <!-- title -->
   <div class="flex-grow-1 name-container d-flex align-items-start">
-    <small class="flex-grow-1 flex-shrink-1">
+    <small *ngIf="name else loadingName" class="flex-grow-1 flex-shrink-1">
       {{ name }}
     </small>
+    <ng-template #loadingName>
+      <div class="spinnerAnimationCircle"></div>
+    </ng-template>
   </div>
 
 
@@ -19,6 +22,7 @@
       <!-- unpin -->
       <button mat-icon-button
         *ngIf="favedDataentries$ | async | datasetIsFaved : dataset; else pinTmpl"
+        aria-label="Toggle pinning this dataset"
         (click)="undoableRemoveFav()"
         class="no-focus flex-grow-0 flex-shrink-0"
         color="primary">
@@ -28,6 +32,7 @@
       <!-- pin -->
       <ng-template #pinTmpl>
         <button mat-icon-button
+          aria-label="Toggle pinning this dataset"
           (click)="undoableAddFav()"
           class="no-focus flex-grow-0 flex-shrink-0"
           color="basic">
@@ -122,6 +127,7 @@
 
   <!-- pin dataset -->
   <button mat-icon-button
+    aria-label="Toggle pinning this dataset"
     *ngIf="downloadEnabled"
     iav-stop="click mousedown"
     (click)="toggleFav()"
diff --git a/src/ui/databrowserModule/singleDataset/singleDataset.base.ts b/src/ui/databrowserModule/singleDataset/singleDataset.base.ts
index 039a834d70ae4ae0fd78dcee07da90d45c750a71..dfb7f94a34a6bb746d7049611aae6bdd31c67e92 100644
--- a/src/ui/databrowserModule/singleDataset/singleDataset.base.ts
+++ b/src/ui/databrowserModule/singleDataset/singleDataset.base.ts
@@ -1,4 +1,4 @@
-import { ChangeDetectorRef, Input, OnInit, TemplateRef } from "@angular/core";
+import { ChangeDetectorRef, Input, OnInit, TemplateRef, OnChanges } from "@angular/core";
 import { Observable } from "rxjs";
 import { AtlasViewerConstantsServices } from "src/atlasViewer/atlasViewer.constantService.service";
 import { IDataEntry, IFile, IPublication, ViewerPreviewFile } from 'src/services/state/dataStore.store'
@@ -7,6 +7,8 @@ import { DatabrowserService } from "../databrowser.service";
 import { KgSingleDatasetService } from "../kgSingleDatasetService.service";
 
 import { DS_PREVIEW_URL } from 'src/util/constants'
+import { getKgSchemaIdFromFullId } from "../util/getKgSchemaIdFromFullId.pipe";
+import { MatSnackBar } from "@angular/material/snack-bar";
 
 export {
   DatabrowserService,
@@ -15,7 +17,7 @@ export {
   AtlasViewerConstantsServices
 }
 
-export class SingleDatasetBase implements OnInit {
+export class SingleDatasetBase implements OnInit, OnChanges {
 
   @Input() public ripple: boolean = false
 
@@ -27,7 +29,18 @@ export class SingleDatasetBase implements OnInit {
   @Input() public description?: string
   @Input() public publications?: IPublication[]
 
-  @Input() public kgSchema?: string
+  private _fullId: string
+
+  @Input()
+  set fullId(val){
+    this._fullId = val
+  }
+
+  get fullId(){
+    return this._fullId || (this.kgSchema && this.kgId && `${this.kgSchema}/${this.kgId}`) || null
+  }
+
+  @Input() public kgSchema?: string = 'minds/core/dataset/v1.0.0'
   @Input() public kgId?: string
 
   @Input() public dataset: any = null
@@ -59,16 +72,16 @@ export class SingleDatasetBase implements OnInit {
     private dbService: DatabrowserService,
     private singleDatasetService: KgSingleDatasetService,
     private cdr: ChangeDetectorRef,
+    private snackBar: MatSnackBar,
     dataset?: any,
   ) {
-
     this.favedDataentries$ = this.dbService.favedDataentries$
+
     if (dataset) {
-      this.dataset = dataset
       const { fullId } = dataset
-      const obj = this.singleDatasetService.getKgSchemaKgIdFromFullId(fullId)
+      const obj = getKgSchemaIdFromFullId(fullId)
       if (obj) {
-        const { kgSchema, kgId } = obj
+        const [ kgSchema, kgId ] = obj
         this.kgSchema = kgSchema
         this.kgId = kgId
       }
@@ -89,6 +102,38 @@ export class SingleDatasetBase implements OnInit {
     }
   }
 
+  public async fetchDatasetDetail(){
+    try {
+      const { kgId } = this
+      if (!kgId) return
+      const dataset = await this.singleDatasetService.getInfoFromKg({ kgId })
+      
+      const { name, description, publications, fullId } = dataset
+      this.name = name
+      this.description = description
+      this.publications = publications
+      this.fullId = fullId
+
+      this.cdr.detectChanges()
+    } catch (e) {
+      // catch error
+    }
+  }
+
+  public ngOnChanges(){
+    if (!this.kgId) {
+      const fullId = this.fullId || this.dataset?.fullId
+      
+      const re = getKgSchemaIdFromFullId(fullId)
+      if (re) {
+        this.kgSchema = re[0]
+        this.kgId = re[1]
+      }
+    }
+    
+    this.fetchDatasetDetail()
+  }
+
   public ngOnInit() {
     const { kgId, kgSchema, dataset } = this
     this.dlFromKgHref = this.singleDatasetService.getDownloadZipFromKgHref({ kgSchema, kgId })
@@ -160,7 +205,7 @@ export class SingleDatasetBase implements OnInit {
   }
 
   public toggleFav() {
-    this.dbService.toggleFav(this.dataset)
+    this.dbService.toggleFav({ fullId: this.fullId })
   }
 
   public showPreviewList(templateRef: TemplateRef<any>) {
@@ -170,4 +215,32 @@ export class SingleDatasetBase implements OnInit {
   public handlePreviewFile(file: ViewerPreviewFile) {
     this.singleDatasetService.previewFile(file, this.dataset)
   }
+
+  public undoableRemoveFav() {
+    this.snackBar.open(`Unpinned dataset: ${this.name}`, 'Undo', {
+      duration: 5000,
+      politeness: "polite"
+    })
+      .afterDismissed()
+      .subscribe(({ dismissedByAction }) => {
+        if (dismissedByAction) {
+          this.dbService.saveToFav({ fullId: this.fullId})
+        }
+      })
+    this.dbService.removeFromFav({ fullId: this.fullId})
+  }
+
+  public undoableAddFav() {
+    this.snackBar.open(`Pinned dataset: ${this.name}`, 'Undo', {
+      duration: 5000,
+      politeness: "polite"
+    })
+      .afterDismissed()
+      .subscribe(({ dismissedByAction }) => {
+        if (dismissedByAction) {
+          this.dbService.removeFromFav({ fullId: this.fullId})
+        }
+      })
+    this.dbService.saveToFav({ fullId: this.fullId})
+  }
 }
diff --git a/src/ui/databrowserModule/util/datasetIsFaved.pipe.ts b/src/ui/databrowserModule/util/datasetIsFaved.pipe.ts
index a7ca19ef1c0e712e46a895ff73ea06952ab3ff25..dcb578417f01260b7cd00fbcbd6f6930e6ebc247 100644
--- a/src/ui/databrowserModule/util/datasetIsFaved.pipe.ts
+++ b/src/ui/databrowserModule/util/datasetIsFaved.pipe.ts
@@ -1,5 +1,6 @@
 import { Pipe, PipeTransform } from "@angular/core";
 import { IDataEntry } from "src/services/stateStore.service";
+import { getKgSchemaIdFromFullId } from "./getKgSchemaIdFromFullId.pipe";
 
 @Pipe({
   name: 'datasetIsFaved',
@@ -7,6 +8,12 @@ import { IDataEntry } from "src/services/stateStore.service";
 export class DatasetIsFavedPipe implements PipeTransform {
   public transform(favedDataEntry: IDataEntry[], dataentry: IDataEntry): boolean {
     if (!dataentry) { return false }
-    return favedDataEntry.findIndex(ds => ds.id === dataentry.id) >= 0
+    const re2 = getKgSchemaIdFromFullId(dataentry.fullId)
+    if (!re2) return false
+    return favedDataEntry.findIndex(ds => {
+      const re1 = getKgSchemaIdFromFullId(ds.fullId)
+      if (!re1) return false
+      return re1[1] === re2[1]
+    }) >= 0
   }
 }
diff --git a/src/ui/databrowserModule/util/getKgSchemaIdFromFullId.pipe.ts b/src/ui/databrowserModule/util/getKgSchemaIdFromFullId.pipe.ts
index 9c95af439bb65496d8d9cc5956b1839b8edf4fa9..2d9854711410aab6a8cc4959dac6e324bd8f4391 100644
--- a/src/ui/databrowserModule/util/getKgSchemaIdFromFullId.pipe.ts
+++ b/src/ui/databrowserModule/util/getKgSchemaIdFromFullId.pipe.ts
@@ -6,9 +6,13 @@ import { Pipe, PipeTransform } from "@angular/core";
 
 export class GetKgSchemaIdFromFullIdPipe implements PipeTransform {
   public transform(fullId: string): [string, string] {
-    if (!fullId) { return [null, null] }
-    const match = /([\w\-.]*\/[\w\-.]*\/[\w\-.]*\/[\w\-.]*)\/([\w\-.]*)$/.exec(fullId)
-    if (!match) { return [null, null] }
-    return [match[1], match[2]]
+    return getKgSchemaIdFromFullId(fullId)
   }
 }
+
+export function getKgSchemaIdFromFullId(fullId: string): [string, string]{
+  if (!fullId) { return [null, null] }
+  const match = /([\w\-.]*\/[\w\-.]*\/[\w\-.]*\/[\w\-.]*)\/([\w\-.]*)$/.exec(fullId)
+  if (!match) { return [null, null] }
+  return [match[1], match[2]]
+}
\ No newline at end of file
diff --git a/src/ui/signinBanner/signinBanner.template.html b/src/ui/signinBanner/signinBanner.template.html
index 18254bba0fdc1b8d1145e3ad8b46100639d0a806..32deee57d34cd6be014c47040a9471fd681fe133 100644
--- a/src/ui/signinBanner/signinBanner.template.html
+++ b/src/ui/signinBanner/signinBanner.template.html
@@ -6,6 +6,8 @@
 
   <div class="btnWrapper">
     <button mat-icon-button
+      aria-label="Show pinned datasets"
+      [attr.pinned-datasets-length]="(favDataEntries$ | async)?.length"
       (click)="bottomSheet.open(savedDatasets)"
       [matBadge]="(favDataEntries$ | async)?.length > 0 ? (favDataEntries$ | async)?.length : null "
       matBadgeColor="accent"
@@ -127,8 +129,17 @@
 <!-- saved dataset tmpl -->
 
 <ng-template #savedDatasets>
-  <mat-list rol="list">
-    <h3 mat-subheader>Pinned Datasets</h3>
+  <mat-list rol="list"
+    aria-label="Pinned datasets panel">
+    <h3 mat-subheader>
+      <span>
+        Pinned Datasets
+      </span>
+      <iav-datamodule-bulkdownload-cmp
+        [kgIds]="favDataEntries$ | async | iavDatamoduleTransformDsToIdPipe">
+
+      </iav-datamodule-bulkdownload-cmp>
+    </h3>
 
     <!-- place holder when no fav data is available -->
     <mat-card *ngIf="(!(favDataEntries$ | async)) || (favDataEntries$ | async).length === 0">
diff --git a/src/util/constants.ts b/src/util/constants.ts
index e9d16f04d6a432b054b425702525bfbcf6304342..b6f7198ec9bc85453c6a60d4b642ef670564f38a 100644
--- a/src/util/constants.ts
+++ b/src/util/constants.ts
@@ -6,7 +6,7 @@ export const LOCAL_STORAGE_CONST = {
   AGREE_COOKIE: 'fzj.xg.iv.AGREE_COOKIE',
   AGREE_KG_TOS: 'fzj.xg.iv.AGREE_KG_TOS',
 
-  FAV_DATASET: 'fzj.xg.iv.FAV_DATASET',
+  FAV_DATASET: 'fzj.xg.iv.FAV_DATASET_V2',
 }
 
 export const COOKIE_VERSION = '0.3.0'