From fe5763b0fb3278c2b14ede79954c6e7c4304439e Mon Sep 17 00:00:00 2001
From: Xiao Gui <xgui3783@gmail.com>
Date: Fri, 6 Nov 2020 17:23:42 +0100
Subject: [PATCH] expmt: regional features redesign

---
 common/util.js                                |  16 +++
 deploy/datasets/query.js                      |  14 +-
 deploy/regionalFeatures/index.js              |  24 +++-
 .../databrowserModule/databrowser.service.ts  |   4 +-
 .../databrowser/databrowser.base.ts           |  89 ++++++------
 .../databrowser/databrowser.component.ts      |   7 +-
 .../databrowser/databrowser.directive.ts      |   8 +-
 .../showDatasetDialog.directive.ts            |  43 +++---
 .../detailedView/singleDataset.component.ts   |   3 +
 .../detailedView/singleDataset.template.html  |  22 +--
 .../nehubaContainer.template.html             |  17 +--
 src/ui/regionalFeatures/module.ts             |   2 +
 .../regionalFeature.service.ts                |   4 +-
 .../regionalFeaturesCmp.component.ts          |  45 +++++-
 .../regionalFeaturesCmp.template.html         | 130 ++++++++----------
 src/util/interfaces.ts                        |   7 +
 16 files changed, 248 insertions(+), 187 deletions(-)

diff --git a/common/util.js b/common/util.js
index f506a1cfb..b2da27670 100644
--- a/common/util.js
+++ b/common/util.js
@@ -13,6 +13,22 @@
     }
   }
 
+  const setsContain = (set1, set2) => {
+    for (const el of set2){
+      if (!set1.has(el)) return false
+    }
+    return true
+  }
+
+  exports.setsContain = setsContain
+
+  exports.setsEql = (set1, set2) => {
+    if (set1.size !== set2.size) return false
+    if (!setsContain(set1, set2)) return false
+    if (!setsContain(set2, set1)) return false
+    return true
+  }
+
   /**
    *
    * https://stackoverflow.com/a/16348977/6059235
diff --git a/deploy/datasets/query.js b/deploy/datasets/query.js
index 091bc7fcd..2219f2da8 100644
--- a/deploy/datasets/query.js
+++ b/deploy/datasets/query.js
@@ -6,6 +6,7 @@ const archiver = require('archiver')
 const { getPreviewFile, hasPreview } = require('./supplements/previewFile')
 const { constants, init: kgQueryUtilInit, getUserKGRequestParam, filterDatasets, filterDatasetsByRegion } = require('./util')
 const ibc = require('./importIBS')
+const { returnAdditionalDatasets } = require('../regionalFeatures')
 
 let cachedData = null
 
@@ -108,9 +109,16 @@ const getPublicDs = async () => {
 }
 
 
-const getDs = ({ user }) => user
-  ? fetchDatasetFromKg({ user }).then(({ results }) => results)
-  : getPublicDs()
+const getDs = ({ user }) => (user
+    ? fetchDatasetFromKg({ user }).then(({ results }) => results)
+    : getPublicDs()
+  ).then(async datasets => {
+    
+    return [
+      ...datasets,
+      ...(await returnAdditionalDatasets()),
+    ]
+  })
 
 const getExternalSchemaDatasets = (kgId, kgSchema) => {
   if (kgSchema === ibc.IBC_SCHEMA) {
diff --git a/deploy/regionalFeatures/index.js b/deploy/regionalFeatures/index.js
index d505c4282..dc844d787 100644
--- a/deploy/regionalFeatures/index.js
+++ b/deploy/regionalFeatures/index.js
@@ -18,6 +18,8 @@ const regionIdToDataIdMap = new Map()
 const datasetIdToDataMap = new Map()
 const datasetIdDetailMap = new Map()
 
+let additionalDatasets = []
+const returnAdditionalDatasets = async () => additionalDatasets
 let isReady = false
 
 const ITERABLE_KEY_SYMBOL = Symbol('ITERABLE_KEY_SYMBOL')
@@ -27,7 +29,7 @@ const ITERABLE_KEY_SYMBOL = Symbol('ITERABLE_KEY_SYMBOL')
  * async await would mean it is fetched one at a time
  */
 
-const init = Promise.all(
+Promise.all(
   arrayToFetch.map(url =>
     new Promise((rs, rj) => {
       request.get(url, (err, _resp, body) => {
@@ -73,7 +75,24 @@ const init = Promise.all(
       })
     })
   )
-).then(() => isReady = true)
+).then(() => {
+  const map = new Map()
+  for (const [regionId, regionObj] of regionIdToDataIdMap.entries()) {
+    for (const datasetId of regionObj[ITERABLE_KEY_SYMBOL]) {
+      const newArr = (map.get(datasetId) || []).concat(regionId)
+      map.set(datasetId, newArr)
+    }
+  }
+
+  for (const [ datasetId, arrRegionIds ] of map.entries()) {
+    additionalDatasets = additionalDatasets.concat({
+      fullId: datasetId,
+      parcellationRegion: arrRegionIds.map(id => ({ fullId: id }))
+    })
+  }
+
+  isReady = true
+})
 
 const getFeatureMiddleware = (req, res, next) => {
   const { featureFullId } = req.params
@@ -221,4 +240,5 @@ const regionalFeatureIsReady = async () => isReady
 module.exports = {
   router,
   regionalFeatureIsReady,
+  returnAdditionalDatasets,
 }
diff --git a/src/ui/databrowserModule/databrowser.service.ts b/src/ui/databrowserModule/databrowser.service.ts
index 25c601d5e..fb1c8cc09 100644
--- a/src/ui/databrowserModule/databrowser.service.ts
+++ b/src/ui/databrowserModule/databrowser.service.ts
@@ -64,7 +64,7 @@ export class DatabrowserService implements OnDestroy {
     })
   }
   public createDatabrowser: (arg: {regions: any[], template: any, parcellation: any}) => {dataBrowser: ComponentRef<DataBrowser>, widgetUnit: ComponentRef<WidgetUnit>}
-  public getDataByRegion: ({ regions, parcellation, template }: {regions: any[], parcellation: any, template: any}) => Promise<IDataEntry[]> = ({regions, parcellation, template}) => 
+  public getDataByRegion: (arg: {regions: any[] }) => Observable<IDataEntry[]> = ({ regions }) => 
     forkJoin(regions.map(this.getDatasetsByRegion.bind(this))).pipe(
       map(
         (arrOfArr: IDataEntry[][]) => arrOfArr.reduce(
@@ -81,7 +81,7 @@ export class DatabrowserService implements OnDestroy {
           []
         )
       )
-    ).toPromise()
+    )
 
   private filterDEByRegion: FilterDataEntriesByRegion = new FilterDataEntriesByRegion()
   private dataentries: IDataEntry[] = []
diff --git a/src/ui/databrowserModule/databrowser/databrowser.base.ts b/src/ui/databrowserModule/databrowser/databrowser.base.ts
index 04b10b309..a3ec2a4fc 100644
--- a/src/ui/databrowserModule/databrowser/databrowser.base.ts
+++ b/src/ui/databrowserModule/databrowser/databrowser.base.ts
@@ -1,23 +1,32 @@
-import { Input, Output, EventEmitter } from "@angular/core"
+import { Input, Output, EventEmitter, OnDestroy } from "@angular/core"
 import { LoggingService } from "src/logging"
 import { DatabrowserService } from "../singleDataset/singleDataset.base"
-import { Observable } from "rxjs"
+import { Observable, Subject, Subscription } from "rxjs"
 import { IDataEntry } from "src/services/stateStore.service"
-import { getUniqueRegionId } from 'common/util'
+import { getIdFromFullId, setsEql } from 'common/util'
+import { switchMap, tap } from "rxjs/operators"
 
-export class DatabrowserBase{
+export class DatabrowserBase implements OnDestroy{
+
+  private _subscriptions: Subscription[] = []
 
   @Output()
   public dataentriesUpdated: EventEmitter<IDataEntry[]> = new EventEmitter()
 
+  private _regions: any[] = []
+  
+  public regions$ = new Subject<any[]>()
+  get regions(){
+    return this._regions
+  }
   @Input()
-  regions: any[] = []
-
-  @Input()
-  public template: any
-
-  @Input()
-  public parcellation: any
+  set regions(arr: any[]){
+    const currentSet = new Set(this._regions.map(r => getIdFromFullId(r.fullId)))
+    const newSet = new Set(arr.map(r => getIdFromFullId(r.fullId)).filter(v => !!v))
+    if (setsEql(newSet, currentSet)) return
+    this._regions = arr.filter(r => !!getIdFromFullId(r.fullId))
+    this.regions$.next(this._regions)
+  }
 
   public fetchError: boolean = false
   public fetchingFlag = false
@@ -32,47 +41,27 @@ export class DatabrowserBase{
   ){
 
     this.favDataentries$ = this.dbService.favedDataentries$
+    
+    this._subscriptions.push(
+      this.regions$.pipe(
+        tap(() => this.fetchingFlag = true),
+        switchMap(regions => this.dbService.getDataByRegion({ regions })),
+      ).subscribe(
+        de => {
+          this.fetchingFlag = false
+          this.dataentries = de
+          this.dataentriesUpdated.emit(de)
+        },
+        e => {
+          this.log.error(e)
+          this.fetchError = true
+        }
+      )
+    )
   }
 
-  ngOnChanges(){
-
-    const { regions, parcellation, template } = this
-    this.regions = this.regions.map(r => {
-      /**
-       * TODO to be replaced with properly region UUIDs from KG
-       */
-      const uniqueRegionId = getUniqueRegionId(template, parcellation, r)
-      return {
-        fullId: uniqueRegionId,
-        id: uniqueRegionId,
-        ...r,
-      }
-    })
-    this.fetchingFlag = true
-
-    // input may be undefined/null
-    if (!parcellation) { return }
-
-    /**
-     * reconstructing parcellation region is async (done on worker thread)
-     * if parcellation region is not yet defined, return.
-     * parccellation will eventually be updated with the correct region
-     */
-    if (!parcellation.regions) { return }
-
-    this.dbService.getDataByRegion({ regions, parcellation, template })
-      .then(de => {
-        this.dataentries = de
-        return de
-      })
-      .catch(e => {
-        this.log.error(e)
-        this.fetchError = true
-      })
-      .finally(() => {
-        this.fetchingFlag = false
-        this.dataentriesUpdated.emit(this.dataentries)
-      })
+  ngOnDestroy(){
+    while(this._subscriptions.length > 0) this._subscriptions.pop().unsubscribe()
   }
 
   public retryFetchData(event: MouseEvent) {
diff --git a/src/ui/databrowserModule/databrowser/databrowser.component.ts b/src/ui/databrowserModule/databrowser/databrowser.component.ts
index 4ed2d667c..fb9513254 100644
--- a/src/ui/databrowserModule/databrowser/databrowser.component.ts
+++ b/src/ui/databrowserModule/databrowser/databrowser.component.ts
@@ -20,7 +20,7 @@ const { MODALITY_FILTER, LIST_OF_DATASETS } = ARIA_LABELS
   changeDetection: ChangeDetectionStrategy.OnPush,
 })
 
-export class DataBrowser extends DatabrowserBase implements OnChanges, OnDestroy, OnInit {
+export class DataBrowser extends DatabrowserBase implements OnDestroy, OnInit {
 
   @Input()
   disableVirtualScroll: boolean = false
@@ -59,10 +59,6 @@ export class DataBrowser extends DatabrowserBase implements OnChanges, OnDestroy
     super(dataService, log)
   }
 
-  public ngOnChanges() {
-    super.ngOnChanges()
-  }
-
   public ngOnInit() {
 
     this.subscriptions.push(
@@ -100,6 +96,7 @@ export class DataBrowser extends DatabrowserBase implements OnChanges, OnDestroy
   }
 
   public ngOnDestroy() {
+    super.ngOnDestroy()
     this.subscriptions.forEach(s => s.unsubscribe())
   }
 
diff --git a/src/ui/databrowserModule/databrowser/databrowser.directive.ts b/src/ui/databrowserModule/databrowser/databrowser.directive.ts
index 5eeeacf5f..911a8a2b9 100644
--- a/src/ui/databrowserModule/databrowser/databrowser.directive.ts
+++ b/src/ui/databrowserModule/databrowser/databrowser.directive.ts
@@ -1,4 +1,4 @@
-import { Directive } from "@angular/core";
+import { Directive, OnDestroy } from "@angular/core";
 import { DatabrowserBase } from "./databrowser.base";
 import { DatabrowserService } from "../singleDataset/singleDataset.base";
 import { LoggingService } from "src/logging";
@@ -8,11 +8,15 @@ import { LoggingService } from "src/logging";
   exportAs: 'iavDatabrowserDirective'
 })
 
-export class DatabrowserDirective extends DatabrowserBase{
+export class DatabrowserDirective extends DatabrowserBase implements OnDestroy{
   constructor(
     dataService: DatabrowserService,
     log: LoggingService,
   ){
     super(dataService, log)
   }
+
+  ngOnDestroy(){
+    super.ngOnDestroy()
+  }
 }
diff --git a/src/ui/databrowserModule/showDatasetDialog.directive.ts b/src/ui/databrowserModule/showDatasetDialog.directive.ts
index 1542f5139..0cdd393e6 100644
--- a/src/ui/databrowserModule/showDatasetDialog.directive.ts
+++ b/src/ui/databrowserModule/showDatasetDialog.directive.ts
@@ -1,6 +1,7 @@
-import { Directive, Input, HostListener, Inject } from "@angular/core";
+import { Directive, Input, HostListener, Inject, InjectionToken, Optional } from "@angular/core";
 import { MatDialog } from "@angular/material/dialog";
 import { MatSnackBar } from "@angular/material/snack-bar";
+import { OVERWRITE_SHOW_DATASET_DIALOG_TOKEN, TOverwriteShowDatasetDialog } from "src/util/interfaces";
 
 export const IAV_DATASET_SHOW_DATASET_DIALOG_CMP = 'IAV_DATASET_SHOW_DATASET_DIALOG_CMP'
 export const IAV_DATASET_SHOW_DATASET_DIALOG_CONFIG = `IAV_DATASET_SHOW_DATASET_DIALOG_CONFIG`
@@ -34,29 +35,35 @@ export class ShowDatasetDialogDirective{
   constructor(
     private matDialog: MatDialog,
     private snackbar: MatSnackBar,
-    @Inject(IAV_DATASET_SHOW_DATASET_DIALOG_CMP) private dialogCmp: any
+    @Inject(IAV_DATASET_SHOW_DATASET_DIALOG_CMP) private dialogCmp: any,
+    @Optional() @Inject(OVERWRITE_SHOW_DATASET_DIALOG_TOKEN) private overwriteFn: TOverwriteShowDatasetDialog
   ){ }
 
   @HostListener('click')
   onClick(){
-
-    if (this.fullId || (this.kgSchema && this.kgId)) {
-
-      this.matDialog.open(this.dialogCmp, {
-        ...ShowDatasetDialogDirective.defaultDialogConfig,
-        data: {
+    const data = (() => {
+      if (this.fullId || (this.kgSchema && this.kgId)) {
+        return {
           fullId: this.fullId || `${this.kgSchema}/${this.kgId}`
         }
-      })
-
-    } else if (this.name || this.description) {
-      const { name, description } = this
-      this.matDialog.open(this.dialogCmp, {
-        ...ShowDatasetDialogDirective.defaultDialogConfig,
-        data: { name, description }
-      })
-    } else {
-      this.snackbar.open(`Cannot show dataset. Neither fullId nor kgId provided.`)
+      }
+      if (this.name || this.description) {
+        const { name, description } = this
+        return { name, description }
+      }
+    })()
+
+    if (!data) {
+      return this.snackbar.open(`Cannot show dataset. Neither fullId nor kgId provided.`)
     }
+
+    if (this.overwriteFn) {
+      return this.overwriteFn(data)
+    }
+
+    this.matDialog.open(this.dialogCmp, {
+      ...ShowDatasetDialogDirective.defaultDialogConfig,
+      data
+    })
   }
 }
diff --git a/src/ui/databrowserModule/singleDataset/detailedView/singleDataset.component.ts b/src/ui/databrowserModule/singleDataset/detailedView/singleDataset.component.ts
index 2d34d3971..d273ee2f1 100644
--- a/src/ui/databrowserModule/singleDataset/detailedView/singleDataset.component.ts
+++ b/src/ui/databrowserModule/singleDataset/detailedView/singleDataset.component.ts
@@ -42,4 +42,7 @@ export class SingleDatasetView extends SingleDatasetBase {
 
   @Input()
   hideDownloadBtn = false
+
+  @Input()
+  useSmallIcon = false
 }
diff --git a/src/ui/databrowserModule/singleDataset/detailedView/singleDataset.template.html b/src/ui/databrowserModule/singleDataset/detailedView/singleDataset.template.html
index 617182ba4..ac830cd4f 100644
--- a/src/ui/databrowserModule/singleDataset/detailedView/singleDataset.template.html
+++ b/src/ui/databrowserModule/singleDataset/detailedView/singleDataset.template.html
@@ -56,7 +56,9 @@
 
 <!-- footer -->
 <mat-card-actions iav-media-query #iavMediaQuery="iavMediaQuery">
-  <ng-container *ngTemplateOutlet="actionBtns; context: { $implicit: (iavMediaQuery.mediaBreakPoint$ | async) }" >
+  <ng-container *ngTemplateOutlet="actionBtns; context: {
+    $implicit: useSmallIcon || (iavMediaQuery.mediaBreakPoint$ | async) > 1
+  }" >
   </ng-container>
 </mat-card-actions>
 
@@ -70,7 +72,7 @@
 </ng-template>
 
 <!-- using ng template for context binding of media breakpoints -->
-<ng-template #actionBtns let-mediaBreakPoint>
+<ng-template #actionBtns let-useSmallIcon>
 
   <!-- explore -->
   <ng-container *ngIf="!strictLocal && !hideExplore">
@@ -79,10 +81,10 @@
       [href]="kgRef | doiParserPipe"
       target="_blank">
       <iav-dynamic-mat-button
-        [iav-dynamic-mat-button-style]="mediaBreakPoint < 2 ? 'mat-raised-button' : 'mat-icon-button'"
+        [iav-dynamic-mat-button-style]="useSmallIcon ? 'mat-icon-button' : 'mat-raised-button'"
         iav-dynamic-mat-button-color="primary">
 
-        <span *ngIf="mediaBreakPoint < 2">
+        <span *ngIf="!useSmallIcon">
           Explore
         </span>
         <i class="fas fa-external-link-alt"></i>
@@ -102,10 +104,10 @@
         (click)="isFav ? undoableRemoveFav() : undoableAddFav()"
         iav-stop="click mousedown"
         [iav-dynamic-mat-button-aria-label]="PIN_DATASET_ARIA_LABEL"
-        [iav-dynamic-mat-button-style]="mediaBreakPoint < 2 ? 'mat-button' : 'mat-icon-button'"
+        [iav-dynamic-mat-button-style]="useSmallIcon ? 'mat-icon-button' : 'mat-button'"
         [iav-dynamic-mat-button-color]="isFav ? 'primary' : 'basic'">
 
-        <span *ngIf="mediaBreakPoint < 2">
+        <span *ngIf="!useSmallIcon">
           {{ isFav ? 'Unpin this dataset' : 'Pin this dataset' }}
         </span>
         <i class="fas fa-thumbtack"></i>
@@ -122,9 +124,9 @@
       <iav-dynamic-mat-button
         [matTooltip]="tooltipText"
         [disabled]="downloadInProgress"
-        [iav-dynamic-mat-button-style]="mediaBreakPoint < 2 ? 'mat-button' : 'mat-icon-button'">
+        [iav-dynamic-mat-button-style]="useSmallIcon ? 'mat-icon-button' : 'mat-button'">
 
-        <span *ngIf="mediaBreakPoint < 2">
+        <span *ngIf="!useSmallIcon">
           Download Zip
         </span>
         <i class="ml-1 fas" [ngClass]="!downloadInProgress? 'fa-download' :'fa-spinner fa-pulse'"></i>
@@ -149,11 +151,11 @@
     <iav-dynamic-mat-button
       *ngIf="hasPreview"
       mat-dialog-close
-      [iav-dynamic-mat-button-style]="mediaBreakPoint < 2 ? 'mat-button' : 'mat-icon-button'"
+      [iav-dynamic-mat-button-style]="useSmallIcon ? 'mat-icon-button' : 'mat-button'"
       [iav-dynamic-mat-button-aria-label]="SHOW_DATASET_PREVIEW_ARIA_LABEL"
       (click)="showPreviewList(previewFilesListTemplate)">
 
-      <span *ngIf="mediaBreakPoint < 2">
+      <span *ngIf="!useSmallIcon">
         Preview
       </span>
       <i class="ml-1 far fa-eye"></i>
diff --git a/src/ui/nehubaContainer/nehubaContainer.template.html b/src/ui/nehubaContainer/nehubaContainer.template.html
index 479512f7a..16e175cdd 100644
--- a/src/ui/nehubaContainer/nehubaContainer.template.html
+++ b/src/ui/nehubaContainer/nehubaContainer.template.html
@@ -355,16 +355,15 @@
 
       <!--  regional features-->
       <ng-template #regionalFeaturesTmpl>
-        <data-browser [template]="templateSelected$ | async"
+        <data-browser
           [parcellation]="selectedParcellation"
           [disableVirtualScroll]="true"
           [regions]="regions">
         </data-browser>
       </ng-template>
 
-      <div class="hidden" iav-databrowser-directive
-        [template]="templateSelected$ | async"
-        [parcellation]="selectedParcellation"
+      <div class="hidden"
+        iav-databrowser-directive
         [regions]="regions"
         #iavDbDirective="iavDatabrowserDirective">
       </div>
@@ -460,19 +459,11 @@
 
     <!--  regional features-->
     <ng-template #regionalFeaturesTmpl let-expansionPanel="expansionPanel">
-      <regional-features *ngIf="expansionPanel.expanded"
-        [region]="region">
-        <data-browser [template]="templateSelected$ | async"
-          [parcellation]="selectedParcellation"
-          [disableVirtualScroll]="true"
-          [regions]="[region]">
-        </data-browser>
+      <regional-features *ngIf="expansionPanel.expanded" [region]="region">
       </regional-features>
     </ng-template>
 
     <div class="hidden" iav-databrowser-directive
-      [template]="templateSelected$ | async"
-      [parcellation]="selectedParcellation"
       [regions]="[region]"
       #iavDbDirective="iavDatabrowserDirective">
     </div>
diff --git a/src/ui/regionalFeatures/module.ts b/src/ui/regionalFeatures/module.ts
index 813cc93fc..0849b2fba 100644
--- a/src/ui/regionalFeatures/module.ts
+++ b/src/ui/regionalFeatures/module.ts
@@ -1,6 +1,7 @@
 import { CommonModule } from "@angular/common";
 import { NgModule } from "@angular/core";
 import { UtilModule } from "src/util";
+import { DatabrowserModule } from "../databrowserModule";
 import { AngularMaterialModule } from "../sharedModules/angularMaterial.module";
 import { FeatureExplorer } from "./featureExplorer/featureExplorer.component";
 import { RegionalFeatureInteractivity } from "./interactivity.directive";
@@ -14,6 +15,7 @@ import { RegionalFeaturesCmp } from "./regionalFeaturesCmp/regionalFeaturesCmp.c
     CommonModule,
     UtilModule,
     AngularMaterialModule,
+    DatabrowserModule,
   ],
   declarations: [
     /**
diff --git a/src/ui/regionalFeatures/regionalFeature.service.ts b/src/ui/regionalFeatures/regionalFeature.service.ts
index b129bb1e6..e49fd8ff5 100644
--- a/src/ui/regionalFeatures/regionalFeature.service.ts
+++ b/src/ui/regionalFeatures/regionalFeature.service.ts
@@ -2,7 +2,7 @@ import { HttpClient } from "@angular/common/http";
 import { Injectable, OnDestroy } from "@angular/core";
 import { PureContantService } from "src/util";
 import { getIdFromFullId } from 'common/util'
-import { forkJoin, Subscription } from "rxjs";
+import { forkJoin, Subject, Subscription } from "rxjs";
 import { switchMap } from "rxjs/operators";
 import { IHasId } from "src/util/interfaces";
 import { select, Store } from "@ngrx/store";
@@ -119,4 +119,6 @@ export class RegionalFeaturesService implements OnDestroy{
       })
     )
   }
+
+  showDatafeatureInfo$ = new Subject<{ fullId: string } | { name: string, description: string }>()
 }
diff --git a/src/ui/regionalFeatures/regionalFeaturesCmp/regionalFeaturesCmp.component.ts b/src/ui/regionalFeatures/regionalFeaturesCmp/regionalFeaturesCmp.component.ts
index 2cf5ec0e7..b5da906cb 100644
--- a/src/ui/regionalFeatures/regionalFeaturesCmp/regionalFeaturesCmp.component.ts
+++ b/src/ui/regionalFeatures/regionalFeaturesCmp/regionalFeaturesCmp.component.ts
@@ -1,4 +1,8 @@
-import { Component, OnChanges, SimpleChanges } from "@angular/core";
+import { Component, OnChanges, OnDestroy, SimpleChanges, ViewChild } from "@angular/core";
+import { MatSidenav } from "@angular/material/sidenav";
+import { Observable, Subscription } from "rxjs";
+import { shareReplay } from "rxjs/operators";
+import { OVERWRITE_SHOW_DATASET_DIALOG_TOKEN, TOverwriteShowDatasetDialog } from "src/util/interfaces";
 import { RegionalFeaturesService } from "../regionalFeature.service";
 import { RegionFeatureBase } from "../regionFeature.base";
 
@@ -8,21 +12,52 @@ import { RegionFeatureBase } from "../regionFeature.base";
   styleUrls: [
     './regionalFeaturesCmp.style.css'
   ],
+  providers: [
+    {
+      provide: OVERWRITE_SHOW_DATASET_DIALOG_TOKEN,
+      useFactory: (regionalFeatureService: RegionalFeaturesService) => {
+        return function overwriteShowDatasetDialog( arg ){
+          regionalFeatureService.showDatafeatureInfo$.next(arg)
+        } as TOverwriteShowDatasetDialog
+      },
+      deps: [
+        RegionalFeaturesService
+      ]
+    }
+  ]
 })
 
-export class RegionalFeaturesCmp extends RegionFeatureBase implements OnChanges{
+export class RegionalFeaturesCmp extends RegionFeatureBase implements OnDestroy, OnChanges{
 
-  ngOnChanges(changes: SimpleChanges){
-    super.ngOnChanges(changes)
-  }
+  @ViewChild('sideNav', { read: MatSidenav })
+  private sideNav: MatSidenav
 
   constructor(
     regionalFeatureService: RegionalFeaturesService
   ){
     super(regionalFeatureService)
+    this.showDatafeatureInfo$ = regionalFeatureService.showDatafeatureInfo$.pipe(
+      shareReplay(1)
+    )
+    this.subscription.push(
+      this.showDatafeatureInfo$.subscribe(
+        () => this.sideNav.open()
+      )
+    )
+  }
+
+  ngOnChanges(changes: SimpleChanges){
+    super.ngOnChanges(changes)
   }
 
+  private subscription: Subscription[] = []
+  ngOnDestroy(){
+    while (this.subscription.length > 0) this.subscription.pop().unsubscribe()
+  }
+
+
   public showingRegionFeatureId: string
   public showingRegionFeatureIsLoading = false
 
+  public showDatafeatureInfo$: Observable<{fullId: string}|{name: string, description: string}>
 }
diff --git a/src/ui/regionalFeatures/regionalFeaturesCmp/regionalFeaturesCmp.template.html b/src/ui/regionalFeatures/regionalFeaturesCmp/regionalFeaturesCmp.template.html
index 0cac5e159..7f7d5772a 100644
--- a/src/ui/regionalFeatures/regionalFeaturesCmp/regionalFeaturesCmp.template.html
+++ b/src/ui/regionalFeatures/regionalFeaturesCmp/regionalFeaturesCmp.template.html
@@ -1,76 +1,54 @@
-<mat-tab-group *ngIf="!isLoading; else loadingTmpl">
-  <mat-tab *ngFor="let featureType of features | mapToProperty : 'type' | getUniquePipe">
-    <ng-template mat-tab-label>
-      {{ featureType }}
-    </ng-template>
-
-    <!-- lazy loading feature content -->
-    <ng-template matTabContent>
-
-      <!-- selector -->
-      <div>
-        <ng-container *ngTemplateOutlet="selectorTmpl; context: {
-          label: 'Dataset',
-          options: features | filterRegionalFeaturesBytype : featureType
-        }">
-        </ng-container>
-  
-      </div>
-
-      <!-- content -->
-      <ng-template [ngIf]="showingRegionFeatureId">
-        <ng-container *ngTemplateOutlet="featureContentTmpl; context: {
-          region: region,
-          feature: features | findRegionFeaturebyId : showingRegionFeatureId
-        }">
-        </ng-container>
-      </ng-template>
-    </ng-template>
-  </mat-tab>
-
-  <!-- transcluded content -->
-  <mat-tab>
-    <ng-template mat-tab-label>
-      Other
-    </ng-template>
-
-    <!-- lazy loading transcluded content -->
-    <ng-template matTabContent>
-      <ng-content></ng-content>
-    </ng-template>
-  </mat-tab>
-
-</mat-tab-group>
-
-<!-- feature selector -->
-<ng-template #selectorTmpl
-  let-label="label"
-  let-options="options">
-
-  <mat-form-field>
-    <mat-label>
-      {{ label }}
-    </mat-label>
-    <mat-select [(value)]="showingRegionFeatureId">
-      <mat-option *ngFor="let option of options"
-        [value]="option['@id']">
-        {{ option.name }}
-      </mat-option>
-    </mat-select>
-  </mat-form-field>
-</ng-template>
-
-<!-- feature content template -->
-<ng-template #featureContentTmpl
-  let-region="region"
-  let-feature="feature">
-  <feature-explorer
-    [region]="region"
-    [feature]="feature">
-  </feature-explorer>
-</ng-template>
-
-<!-- loading tmpl -->
-<ng-template #loadingTmpl>
-  <div class="spinnerAnimationCircle"></div>
-</ng-template>
+<mat-sidenav-container>
+  <mat-sidenav #sideNav class="w-100">
+    <button mat-button
+      (click)="sideNav.close()">
+      <i class="fas fa-chevron-left"></i>
+      <span>
+        Back
+      </span>
+    </button>
+
+    <!-- TODO fix single dataset view bug
+    does not change render content unless rerender -->
+    <mat-tab-group *ngIf="sideNav.opened">
+
+      <mat-tab>
+        <ng-template mat-tab-label>
+          Overview
+        </ng-template>
+
+        <ng-template matTabContent>
+          <single-dataset-view *ngIf="showDatafeatureInfo$ | async as featureEl"
+            class="m-2 d-inline-block"
+            [fullId]="featureEl.fullId"
+            [name]="featureEl.name"
+            [description]="featureEl.description"
+            [useSmallIcon]="true">
+          </single-dataset-view>
+        </ng-template>
+      </mat-tab>
+
+      <mat-tab *ngFor="let feature of features">
+        <ng-template mat-tab-label>
+          {{ feature.type }}
+        </ng-template>
+        <ng-template matTabContent>
+          <feature-explorer
+            [region]="region"
+            [feature]="feature">
+          </feature-explorer>
+        </ng-template>
+      </mat-tab>
+    </mat-tab-group>
+
+  </mat-sidenav>
+
+  <mat-sidenav-content>
+    <mat-card class="p-0">
+      <data-browser
+        [disableVirtualScroll]="true"
+        [regions]="[region]">
+      </data-browser>
+    </mat-card>
+  </mat-sidenav-content>
+</mat-sidenav-container>
diff --git a/src/util/interfaces.ts b/src/util/interfaces.ts
index d6c414718..4c7613093 100644
--- a/src/util/interfaces.ts
+++ b/src/util/interfaces.ts
@@ -2,6 +2,13 @@
  * Only the most common interfaces should reside here
  */
 
+import { InjectionToken } from "@angular/core"
+
 export interface IHasId{
   ['@id']: string
 }
+
+
+export type TOverwriteShowDatasetDialog = (dataset: { fullId: string } | { name: string, description: string }) => void
+
+export const OVERWRITE_SHOW_DATASET_DIALOG_TOKEN = new InjectionToken<TOverwriteShowDatasetDialog>('OVERWRITE_SHOW_DATASET_DIALOG_TOKEN')
-- 
GitLab