From 44cac355cad12f6a209977f08e1bf3a27914292d Mon Sep 17 00:00:00 2001
From: Xiao Gui <xgui3783@gmail.com>
Date: Fri, 17 Mar 2023 14:59:29 +0100
Subject: [PATCH] fix: revert atlas/parc/tmpl chip fix: only show connectivity
 in human atlas fix: flatten list of list

---
 src/atlasComponents/sapi/sxplrTypes.ts        |  3 +
 src/atlasComponents/sapi/translateV3.ts       |  5 +-
 .../pureDumb/pureATPSelector.template.html    |  8 +-
 src/features/category-acc.directive.ts        |  6 +-
 .../connectivity/excludeConnectivity.pipe.ts  | 16 ---
 src/features/connectivity/module.ts           |  3 -
 src/features/entry/entry.component.ts         | 10 +-
 .../entry/entry.flattened.component.html      | 97 +++++++++++++++++++
 .../entry/entry.flattened.component.scss      | 10 ++
 .../entry/entry.nestedExpPanel.component.html | 94 ++++++++++++++----
 src/features/filterCategories.pipe.ts         | 15 +++
 src/features/list/list.component.ts           | 56 +----------
 src/features/list/list.directive.ts           | 61 ++++++++++++
 src/features/module.ts                        |  5 +
 14 files changed, 290 insertions(+), 99 deletions(-)
 delete mode 100644 src/features/connectivity/excludeConnectivity.pipe.ts
 create mode 100644 src/features/entry/entry.flattened.component.html
 create mode 100644 src/features/entry/entry.flattened.component.scss
 create mode 100644 src/features/filterCategories.pipe.ts
 create mode 100644 src/features/list/list.directive.ts

diff --git a/src/atlasComponents/sapi/sxplrTypes.ts b/src/atlasComponents/sapi/sxplrTypes.ts
index 175f27ed0..0fcf7abfe 100644
--- a/src/atlasComponents/sapi/sxplrTypes.ts
+++ b/src/atlasComponents/sapi/sxplrTypes.ts
@@ -23,6 +23,7 @@ export type SxplrParcellation = {
   type: 'SxplrParcellation'
   id: string
   name: string
+  shortName: string
   modality?: string
   prevId?: string
 } & Partial<AdditionalInfo>
@@ -31,12 +32,14 @@ export type SxplrTemplate = {
   type: 'SxplrTemplate'
   id: string
   name: string
+  shortName: string
 } & Partial<AdditionalInfo>
 
 export type SxplrAtlas = {
   type: 'SxplrAtlas'
   id: string
   name: string
+  species: string
 } & Partial<AdditionalInfo>
 
 export type AdditionalInfo = {
diff --git a/src/atlasComponents/sapi/translateV3.ts b/src/atlasComponents/sapi/translateV3.ts
index 5ee1ef8af..e083557a1 100644
--- a/src/atlasComponents/sapi/translateV3.ts
+++ b/src/atlasComponents/sapi/translateV3.ts
@@ -18,7 +18,8 @@ class TranslateV3 {
     return {
       id: atlas["@id"],
       type: "SxplrAtlas",
-      name: atlas.name
+      name: atlas.name,
+      species: atlas.species
     }
   }
 
@@ -47,6 +48,7 @@ class TranslateV3 {
       modality: parcellation.modality,
       type: "SxplrParcellation",
       prevId,
+      shortName: parcellation.name,
       ...rest
     }
   }
@@ -62,6 +64,7 @@ class TranslateV3 {
     const tmpl = {
       id: template["@id"],
       name: template.fullName,
+      shortName: template.shortName,
       type: "SxplrTemplate" as const
     }
     this.#sxplrTmplMap.set(tmpl.id, tmpl)
diff --git a/src/atlasComponents/sapiViews/core/rich/ATPSelector/pureDumb/pureATPSelector.template.html b/src/atlasComponents/sapiViews/core/rich/ATPSelector/pureDumb/pureATPSelector.template.html
index 3928304fa..dc0aca6a9 100644
--- a/src/atlasComponents/sapiViews/core/rich/ATPSelector/pureDumb/pureATPSelector.template.html
+++ b/src/atlasComponents/sapiViews/core/rich/ATPSelector/pureDumb/pureATPSelector.template.html
@@ -11,7 +11,7 @@
 
     <ng-template sxplrSmartChipContent>
       <span class="chip-text">
-        Parcellation
+        {{ ATP.parcellation.shortName }}
       </span>
       <span class="sxplr-ml-1 text-muted">
         ({{ parcellations.length }})
@@ -56,7 +56,7 @@
 
     <ng-template sxplrSmartChipContent>
       <span class="chip-text">
-        Template
+        {{ ATP.template.shortName }}
       </span>
       <span class="sxplr-ml-1 text-muted">
         ({{ availableTemplates.length }})
@@ -77,7 +77,7 @@
     
     <ng-template sxplrSmartChipContent>
       <span class="chip-text">
-        Atlas
+        {{ ATP.atlas.name }}
       </span>
       <span class="sxplr-ml-1 text-muted">
         ({{ allAtlases.length}})
@@ -121,7 +121,7 @@
   <span *ngIf="item" class="full-sized-button"
     [matTooltip]="item.version?.name || item.name || item.fullName"
     [matTooltipPosition]="'above'">
-    {{ item.version?.name || item.name || item.fullName }}
+    {{ item.version?.name || item.shortName || item.name || item.fullName }}
     <ng-template [ngIf]="suffixText">
       <span class="text-muted">
         {{ suffixText }}
diff --git a/src/features/category-acc.directive.ts b/src/features/category-acc.directive.ts
index 661896191..353b405dc 100644
--- a/src/features/category-acc.directive.ts
+++ b/src/features/category-acc.directive.ts
@@ -1,8 +1,8 @@
 import { AfterContentInit, ContentChildren, Directive, OnDestroy, QueryList } from '@angular/core';
 import { BehaviorSubject, combineLatest, Subscription } from 'rxjs';
 import { map } from 'rxjs/operators';
-import { ListComponent } from './list/list.component';
 import { Feature } from "src/atlasComponents/sapi/sxplrTypes"
+import { ListDirective } from './list/list.directive';
 
 @Directive({
   selector: '[sxplrCategoryAcc]',
@@ -14,8 +14,8 @@ export class CategoryAccDirective implements AfterContentInit, OnDestroy {
   public total$ = new BehaviorSubject<number>(0)
   public features$ = new BehaviorSubject<Feature[]>([])
 
-  @ContentChildren(ListComponent, { read: ListComponent, descendants: true })
-  listCmps: QueryList<ListComponent>
+  @ContentChildren(ListDirective, { read: ListDirective, descendants: true })
+  listCmps: QueryList<ListDirective>
 
   #changeSub: Subscription
   ngAfterContentInit(): void {
diff --git a/src/features/connectivity/excludeConnectivity.pipe.ts b/src/features/connectivity/excludeConnectivity.pipe.ts
deleted file mode 100644
index 736e5d56a..000000000
--- a/src/features/connectivity/excludeConnectivity.pipe.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { Pipe, PipeTransform } from "@angular/core"
-import { PathReturn } from "src/atlasComponents/sapi/typeV3"
-import { KeyValue } from "@angular/common"
-
-type DS = KeyValue<string, PathReturn<"/feature/_types">["items"]>
-
-@Pipe({
-    name: 'isConnectivity',
-    pure: true
-})
-export class ExcludeConnectivityPipe implements PipeTransform {
-
-    public transform(datasets: DS[], filterForConnectivityFlag: boolean): DS[] {
-        return (datasets || []).filter(d => (d.key === 'connectivity') === filterForConnectivityFlag)
-    }
-}
diff --git a/src/features/connectivity/module.ts b/src/features/connectivity/module.ts
index 96f346ac8..97a7793ff 100644
--- a/src/features/connectivity/module.ts
+++ b/src/features/connectivity/module.ts
@@ -7,7 +7,6 @@ import {FormsModule} from "@angular/forms";
 import { DialogModule } from "src/ui/dialogInfo";
 
 import { ConnectivityBrowserComponent } from "./connectivityBrowser/connectivityBrowser.component";
-import { ExcludeConnectivityPipe } from "./excludeConnectivity.pipe";
 
 @NgModule({
   imports: [
@@ -18,11 +17,9 @@ import { ExcludeConnectivityPipe } from "./excludeConnectivity.pipe";
   ],
   declarations: [
     ConnectivityBrowserComponent,
-    ExcludeConnectivityPipe
   ],
   exports: [
     ConnectivityBrowserComponent,
-    ExcludeConnectivityPipe
   ],
   providers: [
     SAPI,
diff --git a/src/features/entry/entry.component.ts b/src/features/entry/entry.component.ts
index c288748da..8d3160291 100644
--- a/src/features/entry/entry.component.ts
+++ b/src/features/entry/entry.component.ts
@@ -1,5 +1,5 @@
 import { AfterViewInit, Component, OnDestroy, QueryList, ViewChildren } from '@angular/core';
-import { Store } from '@ngrx/store';
+import { select, Store } from '@ngrx/store';
 import { map, scan, switchMap, tap } from 'rxjs/operators';
 import { SAPI } from 'src/atlasComponents/sapi';
 import { Feature } from 'src/atlasComponents/sapi/sxplrTypes';
@@ -25,8 +25,8 @@ const categoryAcc = <T extends Record<string, unknown>>(categories: T[]) => {
 
 @Component({
   selector: 'sxplr-feature-entry',
-  templateUrl: './entry.nestedExpPanel.component.html',
-  styleUrls: ['./entry.nestedExpPanel.component.scss'],
+  templateUrl: './entry.flattened.component.html',
+  styleUrls: ['./entry.flattened.component.scss'],
   exportAs: 'featureEntryCmp'
 })
 export class EntryComponent extends FeatureBase implements AfterViewInit, OnDestroy {
@@ -87,7 +87,9 @@ export class EntryComponent extends FeatureBase implements AfterViewInit, OnDest
     )
   }
 
-  public atlas = this.store.select(atlasSelection.selectors.selectedAtlas)
+  public selectedAtlas$ = this.store.pipe(
+    select(atlasSelection.selectors.selectedAtlas)
+  )
 
   private featureTypes$ = this.sapi.v3Get("/feature/_types", {}).pipe(
     switchMap(resp => 
diff --git a/src/features/entry/entry.flattened.component.html b/src/features/entry/entry.flattened.component.html
new file mode 100644
index 000000000..03bf62d88
--- /dev/null
+++ b/src/features/entry/entry.flattened.component.html
@@ -0,0 +1,97 @@
+<mat-accordion>
+
+    <!-- show everything except for connectivity and dataset -->
+    <ng-template ngFor [ngForOf]="cateogryCollections$ | async | keyvalue | filterCategory : ['connectivity', 'dataset', 'other'] : false"  let-keyvalue>
+        <ng-template [ngTemplateOutlet]="featureCategoryFeatureTmpl"
+        [ngTemplateOutletContext]="{
+            $implicit: keyvalue
+        }">
+        </ng-template>
+    </ng-template>
+    
+
+    <!-- only show connectivity in human atlas for now -->
+    <ng-template [ngIf]="(selectedAtlas$ | async)?.species === 'Homo sapiens'">
+        <ng-template [ngIf]="cateogryCollections$ | async | keyvalue | filterCategory : ['connectivity']" let-connectivity>
+            <ng-template ngFor [ngForOf]="connectivity" let-conn>
+                <mat-expansion-panel sxplr-sapiviews-features-connectivity-check
+                    #connectivityAccordion
+                    *ngIf="conn">
+                    <mat-expansion-panel-header>
+                        <mat-panel-title>
+                            {{ conn.key }}
+                        </mat-panel-title>
+                    </mat-expansion-panel-header>
+    
+                    <sxplr-features-connectivity-browser class="pe-all flex-shrink-1" 
+                        [region]="region"
+                        [sxplr-features-connectivity-browser-atlas]="selectedAtlas$ | async"
+                        [sxplr-features-connectivity-browser-template]="template"
+                        [sxplr-features-connectivity-browser-parcellation]="parcellation"
+                        [accordionExpanded]="connectivityAccordion.expanded"
+                        [types]="conn.value">
+                    </sxplr-features-connectivity-browser>
+    
+                </mat-expansion-panel>
+            </ng-template>
+        </ng-template>
+    </ng-template>
+
+    <!-- show dataset/other at the very bottom -->
+    <ng-template ngFor [ngForOf]="cateogryCollections$ | async | keyvalue | filterCategory : ['dataset', 'other']"  let-keyvalue>
+        <ng-template [ngTemplateOutlet]="featureCategoryFeatureTmpl"
+        [ngTemplateOutletContext]="{
+            $implicit: keyvalue
+        }">
+        </ng-template>
+    </ng-template>
+</mat-accordion>
+
+<!-- template for collected category -->
+<ng-template #featureCategoryFeatureTmpl let-keyvalue>
+    <mat-expansion-panel
+        sxplrCategoryAcc
+        #categoryAcc="categoryAcc"
+        [ngClass]="{
+            'sxplr-d-none': !(categoryAcc.isBusy$ | async) && (categoryAcc.total$ | async) === 0
+        }">
+        <mat-expansion-panel-header>
+            <mat-panel-title>
+                {{ keyvalue.key }}
+            </mat-panel-title>
+            <mat-panel-description>
+                <spinner-cmp *ngIf="categoryAcc.isBusy$ | async"></spinner-cmp>
+                <ng-template [ngIf]="categoryAcc.total$ | async" let-total>
+                    <span>
+                        {{ total }}
+                    </span>
+                </ng-template>
+            </mat-panel-description>
+        </mat-expansion-panel-header>
+
+        <ng-template ngFor [ngForOf]="keyvalue.value" let-feature>
+
+            <div sxplr-feature-list-directive
+                [template]="template"
+                [parcellation]="parcellation"
+                [region]="region"
+                [bbox]="bbox"
+                [queryParams]="queryParams | mergeObj : { type: (feature.name | featureNamePipe) }"
+                [featureRoute]="feature.path"
+                #featureListDirective="featureListDirective">
+            </div>
+        </ng-template>
+        
+        <cdk-virtual-scroll-viewport itemSize="36"
+            class="virtual-scroll-viewport">
+            <button *cdkVirtualFor="let feature of categoryAcc.features$ | async"
+                mat-button
+                class="virtual-scroll-item sxplr-w-100"
+                [matTooltip]="feature.name"
+                matTooltipPosition="right"
+                (click)="onClickFeature(feature)">
+                {{ feature.name }}
+            </button>
+        </cdk-virtual-scroll-viewport>
+    </mat-expansion-panel>
+</ng-template>
\ No newline at end of file
diff --git a/src/features/entry/entry.flattened.component.scss b/src/features/entry/entry.flattened.component.scss
new file mode 100644
index 000000000..24438a43b
--- /dev/null
+++ b/src/features/entry/entry.flattened.component.scss
@@ -0,0 +1,10 @@
+cdk-virtual-scroll-viewport
+{
+    height: 10rem;
+}
+
+cdk-virtual-scroll-viewport button
+{
+    text-align: left;
+    height: 36px;
+}
\ No newline at end of file
diff --git a/src/features/entry/entry.nestedExpPanel.component.html b/src/features/entry/entry.nestedExpPanel.component.html
index 3522c1f48..9c82b50fe 100644
--- a/src/features/entry/entry.nestedExpPanel.component.html
+++ b/src/features/entry/entry.nestedExpPanel.component.html
@@ -1,5 +1,5 @@
 <mat-accordion>
-    <mat-expansion-panel *ngFor="let keyvalue of (cateogryCollections$ | async | keyvalue | isConnectivity : false)"
+    <mat-expansion-panel *ngFor="let keyvalue of (cateogryCollections$ | async | keyvalue | filterCategory : ['connectivity', 'dataset', 'other'] : false)"
         sxplrCategoryAcc
         #categoryAcc="categoryAcc"
         [ngClass]="{
@@ -56,29 +56,89 @@
     </mat-expansion-panel>
     
 
-    <ng-template [ngIf]="cateogryCollections$ | async | keyvalue | isConnectivity : true" let-connectivity>
-        <ng-template ngFor [ngForOf]="connectivity" let-conn>
-            <mat-expansion-panel sxplr-sapiviews-features-connectivity-check
-                #connectivityAccordion
-                *ngIf="conn">
+    <!-- only show connectivity in human atlas for now -->
+    <ng-template [ngIf]="(selectedAtlas$ | async)?.species === 'Homo sapiens'">
+        <ng-template [ngIf]="cateogryCollections$ | async | keyvalue | filterCategory : ['connectivity']" let-connectivity>
+            <ng-template ngFor [ngForOf]="connectivity" let-conn>
+                <mat-expansion-panel sxplr-sapiviews-features-connectivity-check
+                    #connectivityAccordion
+                    *ngIf="conn">
+                    <mat-expansion-panel-header>
+                        <mat-panel-title>
+                            {{ conn.key }}
+                        </mat-panel-title>
+                    </mat-expansion-panel-header>
+    
+                    <sxplr-features-connectivity-browser class="pe-all flex-shrink-1" 
+                        [region]="region"
+                        [sxplr-features-connectivity-browser-atlas]="selectedAtlas$ | async"
+                        [sxplr-features-connectivity-browser-template]="template"
+                        [sxplr-features-connectivity-browser-parcellation]="parcellation"
+                        [accordionExpanded]="connectivityAccordion.expanded"
+                        [types]="conn.value">
+                    </sxplr-features-connectivity-browser>
+    
+                </mat-expansion-panel>
+            </ng-template>
+        </ng-template>
+    </ng-template>
+
+    
+    <mat-expansion-panel *ngFor="let keyvalue of (cateogryCollections$ | async | keyvalue | filterCategory : ['dataset', 'other'])"
+        sxplrCategoryAcc
+        #categoryAcc="categoryAcc"
+        [ngClass]="{
+            'sxplr-d-none': !(categoryAcc.isBusy$ | async) && (categoryAcc.total$ | async) === 0
+        }">
+
+        <mat-expansion-panel-header>
+
+            <mat-panel-title>
+                {{ keyvalue.key }}
+            </mat-panel-title>
+            
+            <mat-panel-description>
+                <spinner-cmp *ngIf="categoryAcc.isBusy$ | async"></spinner-cmp>
+                <ng-template [ngIf]="categoryAcc.total$ | async" let-total>
+                    <span>
+                        {{ total }}
+                    </span>
+                </ng-template>
+            </mat-panel-description>
+        </mat-expansion-panel-header>
+
+        <mat-accordion>
+            <mat-expansion-panel class="mat-elevation-z4"
+                *ngFor="let feature of keyvalue.value"
+                [ngClass]="{
+                    'sxplr-d-none': (list.state$ | async) === 'noresult'
+                }">
+
                 <mat-expansion-panel-header>
                     <mat-panel-title>
-                        {{ conn.key }}
+                        <span class="sxplr-white-space-nowrap">
+                            {{ feature.name | featureNamePipe }}
+                        </span>
                     </mat-panel-title>
                 </mat-expansion-panel-header>
-
-                <sxplr-features-connectivity-browser class="pe-all flex-shrink-1" 
+                
+                <spinner-cmp *ngIf="(list.state$ | async) === 'busy'"></spinner-cmp>
+                <sxplr-feature-list
+                    [template]="template"
+                    [parcellation]="parcellation"
                     [region]="region"
-                    [sxplr-features-connectivity-browser-atlas]="atlas | async"
-                    [sxplr-features-connectivity-browser-template]="template"
-                    [sxplr-features-connectivity-browser-parcellation]="parcellation"
-                    [accordionExpanded]="connectivityAccordion.expanded"
-                    [types]="conn.value">
-                </sxplr-features-connectivity-browser>
+                    [bbox]="bbox"
+                    [queryParams]="queryParams | mergeObj : { type: (feature.name | featureNamePipe) }"
+                    [featureRoute]="feature.path"
+                    (onClickFeature)="onClickFeature($event)"
+                    #list="featureList"
+                    >
+                </sxplr-feature-list>
 
             </mat-expansion-panel>
-        </ng-template>
-    </ng-template>
+        </mat-accordion>
 
+    </mat-expansion-panel>
+    
 
 </mat-accordion>
diff --git a/src/features/filterCategories.pipe.ts b/src/features/filterCategories.pipe.ts
new file mode 100644
index 000000000..8f1033287
--- /dev/null
+++ b/src/features/filterCategories.pipe.ts
@@ -0,0 +1,15 @@
+import { KeyValue } from "@angular/common"
+import { Pipe, PipeTransform } from "@angular/core"
+import { PathReturn } from "src/atlasComponents/sapi/typeV3"
+
+type DS = KeyValue<string, PathReturn<"/feature/_types">["items"]>
+
+@Pipe({
+  name: 'filterCategory',
+  pure: true
+})
+export class FilterCategoriesPipe implements PipeTransform{
+  public transform(datasets: DS[], keys: string[], inclFlag: boolean=true) {
+    return (datasets || []).filter(d => inclFlag === keys.includes(d.key) )
+  }
+}
diff --git a/src/features/list/list.component.ts b/src/features/list/list.component.ts
index 52dbcc1ff..29500aca7 100644
--- a/src/features/list/list.component.ts
+++ b/src/features/list/list.component.ts
@@ -1,10 +1,7 @@
-import { Component, EventEmitter, Input, Output, SimpleChanges } from '@angular/core';
-import { BehaviorSubject, combineLatest, Observable, of, throwError } from 'rxjs';
-import { catchError, switchMap, tap } from 'rxjs/operators';
+import { Component, EventEmitter, Output } from '@angular/core';
 import { SAPI } from 'src/atlasComponents/sapi';
 import { Feature } from 'src/atlasComponents/sapi/sxplrTypes';
-import { FeatureType } from 'src/atlasComponents/sapi/typeV3';
-import { AllFeatures, FeatureBase } from '../base';
+import { ListDirective } from './list.directive';
 
 @Component({
   selector: 'sxplr-feature-list',
@@ -12,58 +9,15 @@ import { AllFeatures, FeatureBase } from '../base';
   styleUrls: ['./list.component.scss'],
   exportAs: "featureList"
 })
-export class ListComponent extends FeatureBase {
+export class ListComponent extends ListDirective {
 
   @Output()
   onClickFeature = new EventEmitter<Feature>()
 
-  @Input()
-  featureRoute: string
-  private guardedRoute$ = new BehaviorSubject<FeatureType>(null)
-
-  public state$ = new BehaviorSubject<'busy'|'noresult'|'result'>('noresult')
-
-  constructor(private sapi: SAPI) {
-    super()
+  constructor(sapi: SAPI) {
+    super(sapi)
   }
 
-  ngOnChanges(sc: SimpleChanges): void {
-    super.ngOnChanges(sc)
-    const { featureRoute } = sc
-    if (featureRoute) {
-      const featureType = (featureRoute.currentValue || '').split("/").slice(-1)[0]
-      this.guardedRoute$.next(AllFeatures[featureType])
-    }
-  }
-
-  public features$: Observable<Feature[]> = combineLatest([
-    this.guardedRoute$,
-    this.TPRBbox$,
-  ]).pipe(
-    tap(() => this.state$.next('busy')),
-    switchMap(([route, { template, parcellation, region, bbox }]) => {
-      if (!route) {
-        return throwError("noresult")
-      }
-      const query = {}
-      if (template) query['space_id'] = template.id
-      if (parcellation) query['parcellation_id'] = parcellation.id
-      if (region) query['region_id'] = region.name
-      if (bbox) query['bbox'] = JSON.stringify(bbox)
-      return this.sapi.getV3Features(route, {
-        query: {
-          ...this.queryParams,
-          ...query,
-        } as any
-      })
-    }),
-    catchError(() => {
-      this.state$.next("noresult")
-      return of([] as Feature[])
-    }),
-    tap(result => this.state$.next(result.length > 0 ? 'result' : 'noresult')),
-  )
-
   onClickItem(feature: Feature){
     this.onClickFeature.emit(feature)
   }
diff --git a/src/features/list/list.directive.ts b/src/features/list/list.directive.ts
new file mode 100644
index 000000000..c5c4734d9
--- /dev/null
+++ b/src/features/list/list.directive.ts
@@ -0,0 +1,61 @@
+import { Input, Directive, SimpleChanges } from "@angular/core";
+import { BehaviorSubject, combineLatest, Observable, of, throwError } from "rxjs";
+import { catchError, switchMap, tap } from "rxjs/operators";
+import { SAPI } from "src/atlasComponents/sapi";
+import { Feature } from "src/atlasComponents/sapi/sxplrTypes";
+import { FeatureType } from "src/atlasComponents/sapi/typeV3";
+import { AllFeatures, FeatureBase } from "../base";
+
+@Directive({
+  selector: '[sxplr-feature-list-directive]',
+  exportAs: 'featureListDirective'
+})
+export class ListDirective extends FeatureBase{
+
+  @Input()
+  featureRoute: string
+  private guardedRoute$ = new BehaviorSubject<FeatureType>(null)
+
+  public state$ = new BehaviorSubject<'busy'|'noresult'|'result'>('noresult')
+
+  constructor(private sapi: SAPI) {
+    super()
+  }
+
+  ngOnChanges(sc: SimpleChanges): void {
+    super.ngOnChanges(sc)
+    const { featureRoute } = sc
+    if (featureRoute) {
+      const featureType = (featureRoute.currentValue || '').split("/").slice(-1)[0]
+      this.guardedRoute$.next(AllFeatures[featureType])
+    }
+  }
+
+  public features$: Observable<Feature[]> = combineLatest([
+    this.guardedRoute$,
+    this.TPRBbox$,
+  ]).pipe(
+    tap(() => this.state$.next('busy')),
+    switchMap(([route, { template, parcellation, region, bbox }]) => {
+      if (!route) {
+        return throwError("noresult")
+      }
+      const query = {}
+      if (template) query['space_id'] = template.id
+      if (parcellation) query['parcellation_id'] = parcellation.id
+      if (region) query['region_id'] = region.name
+      if (bbox) query['bbox'] = JSON.stringify(bbox)
+      return this.sapi.getV3Features(route, {
+        query: {
+          ...this.queryParams,
+          ...query,
+        } as any
+      })
+    }),
+    catchError(() => {
+      this.state$.next("noresult")
+      return of([] as Feature[])
+    }),
+    tap(result => this.state$.next(result.length > 0 ? 'result' : 'noresult')),
+  )
+}
diff --git a/src/features/module.ts b/src/features/module.ts
index 7341ca0f9..48bf3848f 100644
--- a/src/features/module.ts
+++ b/src/features/module.ts
@@ -22,6 +22,8 @@ import { FeatureViewComponent } from "./feature-view/feature-view.component";
 import { TransformPdToDsPipe } from "./transform-pd-to-ds.pipe";
 import { NgLayerCtlModule } from "src/viewerModule/nehuba/ngLayerCtlModule/module";
 import { VoiBboxDirective } from "./voi-bbox.directive";
+import { FilterCategoriesPipe } from "./filterCategories.pipe";
+import { ListDirective } from "./list/list.directive";
 
 @NgModule({
   imports: [
@@ -46,6 +48,8 @@ import { VoiBboxDirective } from "./voi-bbox.directive";
     EntryComponent,
     ListComponent,
     FeatureViewComponent,
+    FilterCategoriesPipe,
+    ListDirective,
 
     CategoryAccDirective,
     VoiBboxDirective,
@@ -57,6 +61,7 @@ import { VoiBboxDirective } from "./voi-bbox.directive";
     EntryComponent,
     FeatureViewComponent,
     VoiBboxDirective,
+    ListDirective,
   ],
   schemas: [
     CUSTOM_ELEMENTS_SCHEMA,
-- 
GitLab