From 43743899283cd2e39fb5488eb5ea6231d9a2eb97 Mon Sep 17 00:00:00 2001 From: Xiao Gui <xgui3783@gmail.com> Date: Wed, 30 Jun 2021 18:35:05 +0200 Subject: [PATCH] feat: support data info --- .../databrowserModule/databrowser.module.ts | 3 - .../sideNavView/sDsSideNavView.component.ts | 50 ---- .../sideNavView/sDsSideNavView.style.css | 4 - .../sideNavView/sDsSideNavView.template.html | 169 ------------- .../regionMenu/regionMenu.component.spec.ts | 2 + .../regionMenu/regionMenu.template.html | 21 ++ .../showDataset/showDataset.directive.spec.ts | 2 + .../showDataset/showDataset.directive.ts | 17 +- .../kgRegDetail/kgRegDetail.component.ts | 29 ++- .../kgRegDetail/kgRegDetail.template.html | 9 +- .../bsFeatures/kgRegionalFeature/module.ts | 22 +- .../receptor/hasReceptor.directive.ts | 8 +- .../atlasLayerSelector.component.ts | 11 +- .../atlasLayerSelector.template.html | 23 +- src/res/css/extra_styles.css | 9 +- src/util/pureConstant.service.ts | 10 +- src/util/siibraApiConstants/types.ts | 12 + src/viewerModule/module.ts | 2 + .../threeSurferGlue/threeSurfer.component.ts | 2 +- .../viewerCmp/viewerCmp.component.ts | 82 +------ .../viewerCmp/viewerCmp.template.html | 227 +----------------- .../breadcrumb/breadcrumb.component.ts | 115 +++++++++ .../breadcrumb/breadcrumb.style.css | 0 .../breadcrumb/breadcrumb.template.html | 206 ++++++++++++++++ .../viewerStateBreadCrumb/module.ts | 30 +++ 25 files changed, 508 insertions(+), 557 deletions(-) delete mode 100644 src/atlasComponents/databrowserModule/singleDataset/sideNavView/sDsSideNavView.component.ts delete mode 100644 src/atlasComponents/databrowserModule/singleDataset/sideNavView/sDsSideNavView.style.css delete mode 100644 src/atlasComponents/databrowserModule/singleDataset/sideNavView/sDsSideNavView.template.html create mode 100644 src/viewerModule/viewerStateBreadCrumb/breadcrumb/breadcrumb.component.ts create mode 100644 src/viewerModule/viewerStateBreadCrumb/breadcrumb/breadcrumb.style.css create mode 100644 src/viewerModule/viewerStateBreadCrumb/breadcrumb/breadcrumb.template.html create mode 100644 src/viewerModule/viewerStateBreadCrumb/module.ts diff --git a/src/atlasComponents/databrowserModule/databrowser.module.ts b/src/atlasComponents/databrowserModule/databrowser.module.ts index 1b5614744..b1faaa923 100644 --- a/src/atlasComponents/databrowserModule/databrowser.module.ts +++ b/src/atlasComponents/databrowserModule/databrowser.module.ts @@ -37,7 +37,6 @@ import { LayerBrowserModule } from "../../ui/layerbrowser"; import { ContributorModule } from "./contributor"; import { DatabrowserService } from "./databrowser.service"; import { ShownDatasetDirective } from "./shownDataset.directive"; -import { SingleDatasetSideNavView } from "./singleDataset/sideNavView/sDsSideNavView.component"; import { RegionalFeaturesModule } from "../regionalFeatures"; import { SingleDatasetDirective } from "./singleDataset/singleDataset.directive"; import { KgDatasetModule } from "../regionalFeatures/bsFeatures/kgDataset"; @@ -70,7 +69,6 @@ const previewEmitFactory = ( overrideFn: (file: any, dataset: any) => void) => { DatasetPreviewList, PreviewComponentWrapper, BulkDownloadBtn, - SingleDatasetSideNavView, /** * Directives @@ -109,7 +107,6 @@ const previewEmitFactory = ( overrideFn: (file: any, dataset: any) => void) => { PreviewFileTypePipe, ShownPreviewsDirective, ShownDatasetDirective, - SingleDatasetSideNavView, ], entryComponents: [ PreviewComponentWrapper diff --git a/src/atlasComponents/databrowserModule/singleDataset/sideNavView/sDsSideNavView.component.ts b/src/atlasComponents/databrowserModule/singleDataset/sideNavView/sDsSideNavView.component.ts deleted file mode 100644 index fe8eb871c..000000000 --- a/src/atlasComponents/databrowserModule/singleDataset/sideNavView/sDsSideNavView.component.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, OnChanges, OnDestroy, Optional, Output, EventEmitter } from "@angular/core"; -import { MatSnackBar } from "@angular/material/snack-bar"; -import { Observable } from "rxjs"; -import { REGION_OF_INTEREST, TRegionOfInterest } from "src/util/interfaces"; -import { DatabrowserService } from "../../databrowser.service"; -import { KgSingleDatasetService } from "../../kgSingleDatasetService.service"; -import { SingleDatasetBase } from "../singleDataset.base"; -import { CONST, ARIA_LABELS } from 'common/constants' - -@Component({ - selector: 'single-dataset-sidenav-view', - templateUrl: './sDsSideNavView.template.html', - styleUrls: [ - './sDsSideNavView.style.css' - ], - changeDetection: ChangeDetectionStrategy.OnPush, -}) - -export class SingleDatasetSideNavView extends SingleDatasetBase implements OnChanges, OnDestroy{ - public BACK_BTN_ARIA_TXT = ARIA_LABELS.CLOSE - @Output() - clear: EventEmitter<null> = new EventEmitter() - - public GDPR_TEXT = CONST.GDPR_TEXT - - constructor( - dbService: DatabrowserService, - sDsService: KgSingleDatasetService, - private _cdr: ChangeDetectorRef, - snackBar: MatSnackBar, - @Optional() @Inject(REGION_OF_INTEREST) public region$: Observable<TRegionOfInterest> - ){ - super( - dbService, - sDsService, - _cdr, - snackBar, - ) - } - ngOnDestroy(){ - super.ngOnDestroy() - } - ngOnChanges(){ - super.ngOnChanges() - } - - detectChange(){ - this._cdr.detectChanges() - } -} diff --git a/src/atlasComponents/databrowserModule/singleDataset/sideNavView/sDsSideNavView.style.css b/src/atlasComponents/databrowserModule/singleDataset/sideNavView/sDsSideNavView.style.css deleted file mode 100644 index 910596713..000000000 --- a/src/atlasComponents/databrowserModule/singleDataset/sideNavView/sDsSideNavView.style.css +++ /dev/null @@ -1,4 +0,0 @@ -:host -{ - position: relative; -} diff --git a/src/atlasComponents/databrowserModule/singleDataset/sideNavView/sDsSideNavView.template.html b/src/atlasComponents/databrowserModule/singleDataset/sideNavView/sDsSideNavView.template.html deleted file mode 100644 index 2acd81667..000000000 --- a/src/atlasComponents/databrowserModule/singleDataset/sideNavView/sDsSideNavView.template.html +++ /dev/null @@ -1,169 +0,0 @@ -<button mat-button - [attr.aria-label]="BACK_BTN_ARIA_TXT" - class="position-absolute z-index-10 m-2" - (click)="clear.emit()"> - <i class="fas fa-chevron-left"></i> - <span class="ml-1"> - Back - </span> -</button> - -<mat-card class="mat-elevation-z4"> - <div class="sidenav-cover-header-container bg-50-grey-20"> - <mat-card-title> - <ng-content select="[region-of-interest]"></ng-content> - <div *ngIf="!fetchFlag; else isLoadingTmpl"> - {{ name }} - </div> - </mat-card-title> - - <mat-card-subtitle class="d-inline-flex align-items-center"> - <mat-icon fontSet="fas" fontIcon="fa-database"></mat-icon> - <span> - Dataset - </span> - - <button *ngIf="isGdprProtected" - [matTooltip]="GDPR_TEXT" - mat-icon-button color="warn"> - <i class="fas fa-exclamation-triangle"></i> - </button> - - <mat-divider [vertical]="true" class="ml-2 h-2rem"></mat-divider> - - <!-- explore btn --> - <a *ngFor="let kgRef of kgReference" - [href]="kgRef | doiParserPipe" - class="color-inherit" - mat-icon-button - [matTooltip]="EXPLORE_DATASET_IN_KG_ARIA_LABEL" - target="_blank"> - <i class="fas fa-external-link-alt"></i> - </a> - - <!-- in case no doi is available, directly link to KG --> - <ng-template [ngIf]="kgReference.length === 0"> - <a [href]="directLinkToKg" - class="color-inherit" - mat-icon-button - [matTooltip]="EXPLORE_DATASET_IN_KG_ARIA_LABEL" - target="_blank"> - <i class="fas fa-external-link-alt"></i> - </a> - </ng-template> - - <!-- fav btn --> - <ng-container *ngTemplateOutlet="favDatasetBtn"> - </ng-container> - - </mat-card-subtitle> - </div> - - <mat-card-content class="mt-2 ml-15px-n mr-15px-n pb-4"> - <mat-accordion> - - <!-- Description --> - <mat-expansion-panel> - <mat-expansion-panel-header> - <mat-panel-title> - Description - </mat-panel-title> - </mat-expansion-panel-header> - <ng-template matExpansionPanelContent> - <small *ngIf="!fetchFlag; else isLoadingTmpl" class="m-1"> - - <!-- desc --> - <markdown-dom [markdown]="description"> - </markdown-dom> - - <mat-divider></mat-divider> - - <!-- citations --> - <div class="d-block mb-2 " - [ngClass]="{'mt-2': first}" - *ngFor="let publication of publications; let first = first"> - <a *ngIf="publication.doi; else plainText" - iav-stop="click mousedown" - [href]="publication.doi | doiParserPipe" - target="_blank"> - {{ publication.cite }} - </a> - <ng-template #plainText> - {{ publication.cite }} - </ng-template> - </div> - - <!-- contributors, if publications not available --> - <ng-container *ngIf="publications && publications.length == 0 && contributors && contributors.length > 0"> - <ng-container *ngFor="let contributor of contributors; let lastFlag = last;"> - <a [href]="contributor | getContributorKgLink" class="iv-custom-comp" target="_blank"> - {{ contributor['schema.org/shortName'] || contributor['shortName'] || contributor['name'] }} - </a> - <span *ngIf="!lastFlag">,</span> - </ng-container> - </ng-container> - - </small> - </ng-template> - </mat-expansion-panel> - - <!-- Features --> - <div class="hidden" - [region]="region$ | async" - (loadingStateChanged)="detectChange()" - region-get-all-features-directive - #rfGetAllFeatures="rfGetAllFeatures"> - </div> - - <!-- loading tmpl --> - <ng-template [ngIf]="rfGetAllFeatures.isLoading$ | async" [ngIfElse]="featureTmpl"> - <div class="d-flex justify-content-center"> - <ng-container *ngTemplateOutlet="isLoadingTmpl"></ng-container> - </div> - </ng-template> - - <!-- feature tmpl --> - <ng-template #featureTmpl> - <ng-container *ngFor="let feature of (rfGetAllFeatures.features | filterRegionFeaturesById : fullId)"> - <mat-expansion-panel #matExpansionPanel> - <mat-expansion-panel-header> - <mat-panel-title> - {{ feature.type }} - </mat-panel-title> - </mat-expansion-panel-header> - - <ng-template [ngIf]="matExpansionPanel.expanded"> - <feature-container - [feature]="feature" - [region]="region$ | async" - (viewChanged)="detectChange()"> - </feature-container> - </ng-template> - </mat-expansion-panel> - </ng-container> - </ng-template> - </mat-accordion> - - </mat-card-content> -</mat-card> - -<ng-template #favDatasetBtn> - <ng-container *ngTemplateOutlet="isFavCtxTmpl; context: { - isFav: (favedDataentries$ | async | datasetIsFaved : ({ fullId: fullId })) - }"> - </ng-container> - - <ng-template #isFavCtxTmpl let-isFav="isFav"> - <button mat-icon-button - (click)="isFav ? undoableRemoveFav() : undoableAddFav()" - [attr.aria-label]="PIN_DATASET_ARIA_LABEL" - [matTooltip]="PIN_DATASET_ARIA_LABEL" - [color]="isFav ? 'primary' : 'basic'"> - <i class="fas fa-thumbtack"></i> - </button> - </ng-template> -</ng-template> - -<ng-template #isLoadingTmpl> - <spinner-cmp></spinner-cmp> -</ng-template> \ No newline at end of file diff --git a/src/atlasComponents/parcellationRegion/regionMenu/regionMenu.component.spec.ts b/src/atlasComponents/parcellationRegion/regionMenu/regionMenu.component.spec.ts index fa2cc46ef..1467b4e26 100644 --- a/src/atlasComponents/parcellationRegion/regionMenu/regionMenu.component.spec.ts +++ b/src/atlasComponents/parcellationRegion/regionMenu/regionMenu.component.spec.ts @@ -8,6 +8,7 @@ import { RenderViewOriginDatasetLabelPipe } from '../region.base' import { Directive, Input } from "@angular/core" import { NoopAnimationsModule } from "@angular/platform-browser/animations" import { ComponentsModule } from "src/components" +import { BSFeatureReceptorModule } from "src/atlasComponents/regionalFeatures/bsFeatures/receptor" const mt0 = { name: 'mt0' @@ -104,6 +105,7 @@ describe('> regionMenu.component.ts', () => { CommonModule, NoopAnimationsModule, ComponentsModule, + BSFeatureReceptorModule, ], declarations: [ RegionMenuComponent, diff --git a/src/atlasComponents/parcellationRegion/regionMenu/regionMenu.template.html b/src/atlasComponents/parcellationRegion/regionMenu/regionMenu.template.html index d972b2651..20baf5d26 100644 --- a/src/atlasComponents/parcellationRegion/regionMenu/regionMenu.template.html +++ b/src/atlasComponents/parcellationRegion/regionMenu/regionMenu.template.html @@ -51,8 +51,29 @@ <mat-accordion class="d-block mt-2"> + <!-- description --> + <ng-template [ngIf]="(region.originDatainfos || []).length > 0"> + <ng-container *ngFor="let originData of region.originDatainfos"> + <ng-template #descTmpl> + <markdown-dom [markdown]="originData.description" + class="text-muted text-sm"> + </markdown-dom> + </ng-template> + + <ng-container *ngTemplateOutlet="ngMatAccordionTmpl; context: { + title: 'Description', + iconClass: 'fas fa-info', + iavNgIf: true, + content: descTmpl + }"> + + </ng-container> + </ng-container> + </ng-template> + <!-- receptor --> <div bs-features-receptor-directive + (bs-features-receptor-directive-fetching-flag$)="handleBusySignal('receptor', $event)" [region]="region" #bsFeatureReceptorDirective="bsFeatureReceptorDirective"> </div> diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/kgDataset/showDataset/showDataset.directive.spec.ts b/src/atlasComponents/regionalFeatures/bsFeatures/kgDataset/showDataset/showDataset.directive.spec.ts index 7a8d1bb62..b2e76a977 100644 --- a/src/atlasComponents/regionalFeatures/bsFeatures/kgDataset/showDataset/showDataset.directive.spec.ts +++ b/src/atlasComponents/regionalFeatures/bsFeatures/kgDataset/showDataset/showDataset.directive.spec.ts @@ -115,6 +115,7 @@ describe('ShowDatasetDialogDirective', () => { expect(args[0]).toEqual(DummyDialogCmp) expect(args[1]).toEqual({ ...ShowDatasetDialogDirective.defaultDialogConfig, + panelClass: ['no-padding-dialog'], data: { fullId: `minds/core/dataset/v1.0.0/aaa-bbb` } @@ -151,6 +152,7 @@ describe('ShowDatasetDialogDirective', () => { expect(args[0]).toEqual(DummyDialogCmp) expect(args[1]).toEqual({ ...ShowDatasetDialogDirective.defaultDialogConfig, + panelClass: ['no-padding-dialog'], data: { fullId: `abc/ccc-ddd` } diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/kgDataset/showDataset/showDataset.directive.ts b/src/atlasComponents/regionalFeatures/bsFeatures/kgDataset/showDataset/showDataset.directive.ts index 9c32e19ff..e8052369b 100644 --- a/src/atlasComponents/regionalFeatures/bsFeatures/kgDataset/showDataset/showDataset.directive.ts +++ b/src/atlasComponents/regionalFeatures/bsFeatures/kgDataset/showDataset/showDataset.directive.ts @@ -1,5 +1,5 @@ -import { Directive, HostListener, Inject, Input, Optional } from "@angular/core"; -import { MatDialog } from "@angular/material/dialog"; +import { Component, Directive, HostListener, Inject, Input, Optional } from "@angular/core"; +import { MatDialog, MatDialogConfig } from "@angular/material/dialog"; import { MatSnackBar } from "@angular/material/snack-bar"; import { OVERWRITE_SHOW_DATASET_DIALOG_TOKEN, TOverwriteShowDatasetDialog } from "src/util/interfaces"; @@ -12,7 +12,7 @@ export const IAV_DATASET_SHOW_DATASET_DIALOG_CONFIG = `IAV_DATASET_SHOW_DATASET_ }) export class ShowDatasetDialogDirective{ - static defaultDialogConfig = { + static defaultDialogConfig: MatDialogConfig = { autoFocus: false } @@ -31,6 +31,12 @@ export class ShowDatasetDialogDirective{ @Input('iav-dataset-show-dataset-dialog-fullid') fullId: string + @Input('iav-dataset-show-dataset-dialog-urls') + urls: { + cite: string + doi: string + }[] = [] + constructor( private matDialog: MatDialog, private snackbar: MatSnackBar, @@ -47,8 +53,8 @@ export class ShowDatasetDialogDirective{ } } if (this.name || this.description) { - const { name, description } = this - return { name, description } + const { name, description, urls } = this + return { name, description, urls } } })() @@ -63,6 +69,7 @@ export class ShowDatasetDialogDirective{ if (!this.dialogCmp) throw new Error(`IAV_DATASET_SHOW_DATASET_DIALOG_CMP not provided!`) this.matDialog.open(this.dialogCmp, { ...ShowDatasetDialogDirective.defaultDialogConfig, + panelClass: ['no-padding-dialog'], data }) } diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/kgRegionalFeature/kgRegDetail/kgRegDetail.component.ts b/src/atlasComponents/regionalFeatures/bsFeatures/kgRegionalFeature/kgRegDetail/kgRegDetail.component.ts index 4a7d79ddd..f59ba7947 100644 --- a/src/atlasComponents/regionalFeatures/bsFeatures/kgRegionalFeature/kgRegDetail/kgRegDetail.component.ts +++ b/src/atlasComponents/regionalFeatures/bsFeatures/kgRegionalFeature/kgRegDetail/kgRegDetail.component.ts @@ -1,9 +1,11 @@ -import { Component, Input, OnChanges } from "@angular/core"; +import { Component, Inject, Input, OnChanges, Optional } from "@angular/core"; import { BsRegionInputBase } from "../../bsRegionInputBase"; import { KG_REGIONAL_FEATURE_KEY, TBSDetail, UNDER_REVIEW } from "../type"; import { ARIA_LABELS, CONST } from 'common/constants' import { TBSSummary } from "../../kgDataset"; import { BsFeatureService } from "../../service"; +import { MAT_DIALOG_DATA } from "@angular/material/dialog"; +import { TDatainfos } from "src/util/siibraApiConstants/types"; /** * this component is specifically used to render side panel ebrains dataset view @@ -36,8 +38,24 @@ export class KgRegDetailCmp extends BsRegionInputBase implements OnChanges { public descriptionFallback = `[This dataset cannot be fetched right now]` - constructor(svc: BsFeatureService){ + public description: string + public name: string + public urls: { + cite: string + doi: string + }[] + + constructor( + svc: BsFeatureService, + @Optional() @Inject(MAT_DIALOG_DATA) data: TDatainfos + ){ super(svc) + if (data) { + const { description, name, urls } = data + this.description = description + this.name = name + this.urls = urls + } } ngOnChanges(){ @@ -48,6 +66,13 @@ export class KgRegDetailCmp extends BsRegionInputBase implements OnChanges { this.getFeatureInstance(KG_REGIONAL_FEATURE_KEY, this.summary['@id']).subscribe( detail => { this.detail = detail + + this.name = this.detail.src_name + this.description = this.detail.__detail?.description + this.urls = this.detail.__detail.kgReference.map(url => { + return { cite: null, doi: url } + }) + this.isGdprProtected = detail.__detail.embargoStatus && detail.__detail.embargoStatus.some(status => status["@id"] === UNDER_REVIEW["@id"]) }, err => { diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/kgRegionalFeature/kgRegDetail/kgRegDetail.template.html b/src/atlasComponents/regionalFeatures/bsFeatures/kgRegionalFeature/kgRegDetail/kgRegDetail.template.html index 7df120ded..bcde6503f 100644 --- a/src/atlasComponents/regionalFeatures/bsFeatures/kgRegionalFeature/kgRegDetail/kgRegDetail.template.html +++ b/src/atlasComponents/regionalFeatures/bsFeatures/kgRegionalFeature/kgRegDetail/kgRegDetail.template.html @@ -1,12 +1,11 @@ <!-- header --> - <mat-card class="mat-elevation-z4"> <div class="sidenav-cover-header-container bg-50-grey-20"> <mat-card-title> <ng-content select="[region-of-interest]"></ng-content> <div *ngIf="!loadingFlag; else isLoadingTmpl"> - {{ (detail && detail.src_name) || nameFallback }} + {{ name || nameFallback }} </div> </mat-card-title> @@ -25,8 +24,8 @@ <mat-divider [vertical]="true" class="ml-2 h-2rem"></mat-divider> <!-- explore btn --> - <a *ngFor="let kgRef of (detail?.__detail?.kgReference || [])" - [href]="kgRef | doiParserPipe" + <a *ngFor="let kgRef of (urls || [])" + [href]="kgRef.doi | doiParserPipe" class="color-inherit" mat-icon-button [matTooltip]="ARIA_LABELS.EXPLORE_DATASET_IN_KG" @@ -42,7 +41,7 @@ <!-- description --> <markdown-dom class="text-muted d-block mat-body m-4" *ngIf="!loadingFlag" - [markdown]="detail?.__detail?.description || descriptionFallback"> + [markdown]="description || descriptionFallback"> </markdown-dom> <ng-template #isLoadingTmpl> diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/kgRegionalFeature/module.ts b/src/atlasComponents/regionalFeatures/bsFeatures/kgRegionalFeature/module.ts index 73bbbf881..0698f46c7 100644 --- a/src/atlasComponents/regionalFeatures/bsFeatures/kgRegionalFeature/module.ts +++ b/src/atlasComponents/regionalFeatures/bsFeatures/kgRegionalFeature/module.ts @@ -1,5 +1,5 @@ import { CommonModule } from "@angular/common"; -import { NgModule } from "@angular/core"; +import { Component, NgModule } from "@angular/core"; import { AngularMaterialModule } from "src/ui/sharedModules/angularMaterial.module"; import { KgRegSummaryCmp } from "./kgRegSummary/kgRegSummary.component"; import { KgRegionalFeaturesList } from "./kgRegList/kgRegList.component"; @@ -10,6 +10,23 @@ import { IAV_DATASET_SHOW_DATASET_DIALOG_CMP } from "../kgDataset/showDataset/sh import { UtilModule } from "src/util"; import { ComponentsModule } from "src/components"; +@Component({ + selector: 'blabla', + template: ` +<mat-dialog-content class="m-0 p-0"> + <kg-regional-feature-detail></kg-regional-feature-detail> +</mat-dialog-content> + +<mat-dialog-actions align="center"> + <button mat-button mat-dialog-close> + Close + </button> +</mat-dialog-actions> +` +}) + +export class ShowDsDialogCmp{} + @NgModule({ imports: [ CommonModule, @@ -23,6 +40,7 @@ import { ComponentsModule } from "src/components"; KgRegionalFeaturesList, KgRegionalFeaturesListDirective, KgRegDetailCmp, + ShowDsDialogCmp, ], exports:[ KgRegSummaryCmp, @@ -33,7 +51,7 @@ import { ComponentsModule } from "src/components"; providers: [ { provide: IAV_DATASET_SHOW_DATASET_DIALOG_CMP, - useValue: KgRegDetailCmp + useValue: ShowDsDialogCmp } ] }) diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/receptor/hasReceptor.directive.ts b/src/atlasComponents/regionalFeatures/bsFeatures/receptor/hasReceptor.directive.ts index ae64c281f..73ec965c3 100644 --- a/src/atlasComponents/regionalFeatures/bsFeatures/receptor/hasReceptor.directive.ts +++ b/src/atlasComponents/regionalFeatures/bsFeatures/receptor/hasReceptor.directive.ts @@ -1,4 +1,4 @@ -import { Directive, OnDestroy } from "@angular/core"; +import { Directive, EventEmitter, OnDestroy, Output } from "@angular/core"; import { merge, of, Subscription } from "rxjs"; import { catchError, map, mapTo, switchMap } from "rxjs/operators"; import { BsRegionInputBase } from "../bsRegionInputBase"; @@ -35,5 +35,11 @@ export class BsFeatureReceptorDirective extends BsRegionInputBase implements OnD svc: BsFeatureService ){ super(svc) + this.sub.push( + this.fetching$.subscribe(flag => this.fetchingFlagEmitter.emit(flag)) + ) } + + @Output('bs-features-receptor-directive-fetching-flag') + public fetchingFlagEmitter = new EventEmitter<boolean>() } \ No newline at end of file diff --git a/src/atlasComponents/uiSelectors/atlasLayerSelector/atlasLayerSelector.component.ts b/src/atlasComponents/uiSelectors/atlasLayerSelector/atlasLayerSelector.component.ts index 591cb8367..4624470fb 100644 --- a/src/atlasComponents/uiSelectors/atlasLayerSelector/atlasLayerSelector.component.ts +++ b/src/atlasComponents/uiSelectors/atlasLayerSelector/atlasLayerSelector.component.ts @@ -1,6 +1,6 @@ import { Component, OnInit, ViewChildren, QueryList, HostBinding, ViewChild, TemplateRef, ElementRef, Pipe, PipeTransform } from "@angular/core"; import { select, Store } from "@ngrx/store"; -import { distinctUntilChanged, map, withLatestFrom, shareReplay, groupBy, mergeMap, toArray, switchMap, scan, filter, startWith } from "rxjs/operators"; +import { distinctUntilChanged, map, withLatestFrom, shareReplay, groupBy, mergeMap, toArray, switchMap, scan, filter, tap } from "rxjs/operators"; import { Observable, Subscription, from, zip, of, combineLatest } from "rxjs"; import { viewerStateSelectTemplateWithId, viewerStateToggleLayer } from "src/services/state/viewerState.store.helper"; import { MatMenuTrigger } from "@angular/material/menu"; @@ -33,6 +33,12 @@ import { animate, state, style, transition, trigger } from "@angular/animations" animate('200ms cubic-bezier(0.35, 0, 0.25, 1)') ]) ]) + ], + providers:[ + { + provide: OVERWRITE_SHOW_DATASET_DIALOG_TOKEN, + useValue: null + } ] }) export class AtlasLayerSelector implements OnInit { @@ -122,7 +128,7 @@ export class AtlasLayerSelector implements OnInit { } public availableTemplates$ = this.store$.pipe<any[]>( - select(viewerStateSelectedTemplateFullInfoSelector) + select(viewerStateSelectedTemplateFullInfoSelector), ) public containerMaxWidth: number @@ -273,6 +279,7 @@ import 'src/res/images/atlas-selection/waxholm-v2.png' import 'src/res/images/atlas-selection/waxholm-v1.png' import 'src/res/images/atlas-selection/short-bundle-hcp.png' import 'src/res/images/atlas-selection/freesurfer.png' +import { OVERWRITE_SHOW_DATASET_DIALOG_TOKEN } from "src/util/interfaces"; const previewImgMap = new Map([ ['minds/core/referencespace/v1.0.0/a1655b99-82f1-420f-a3c2-fe80fd4c8588', 'bigbrain.png'], diff --git a/src/atlasComponents/uiSelectors/atlasLayerSelector/atlasLayerSelector.template.html b/src/atlasComponents/uiSelectors/atlasLayerSelector/atlasLayerSelector.template.html index 21aaafd37..d4af6f260 100644 --- a/src/atlasComponents/uiSelectors/atlasLayerSelector/atlasLayerSelector.template.html +++ b/src/atlasComponents/uiSelectors/atlasLayerSelector/atlasLayerSelector.template.html @@ -150,29 +150,20 @@ </ng-template> <ng-template #infoBtn let-tileSrc="tileSrc"> - <div *ngIf="tileSrc.originDatasets?.length > 0; else plainBtn" - mat-icon-button - iav-stop="click" - class="iv-custom-comp d-flex justify-content-center infoButton" - [ngStyle]="{backgroundColor: tileSrc.darktheme ? 'white': 'black', color: tileSrc.darktheme ? 'black': 'white' }" - iav-dataset-show-dataset-dialog - [iav-dataset-show-dataset-dialog-kgid]="tileSrc.originDatasets[0].kgId" - [iav-dataset-show-dataset-dialog-kgschema]="tileSrc.originDatasets[0].kgSchema"> - <small><i class="fas fa-info"></i></small> - </div> + <ng-container *ngFor="let originDatainfo of tileSrc.originDatainfos"> - <ng-template #plainBtn> - <div *ngIf="tileSrc.properties?.name || tileSrc.properties?.description" - mat-icon-button + <div mat-icon-button iav-stop="click" class="iv-custom-comp d-flex justify-content-center align-items-center infoButton" [ngStyle]="{backgroundColor: tileSrc.darktheme ? 'white': 'black', color: tileSrc.darktheme ? 'black': 'white' }" iav-dataset-show-dataset-dialog - [iav-dataset-show-dataset-dialog-name]="tileSrc.properties.name" - [iav-dataset-show-dataset-dialog-description]="tileSrc.properties.description"> + [iav-dataset-show-dataset-dialog-name]="originDatainfo.name" + [iav-dataset-show-dataset-dialog-description]="originDatainfo.description" + [iav-dataset-show-dataset-dialog-urls]="originDatainfo.urls"> <small><i class="fas fa-info"></i></small> </div> - </ng-template> + </ng-container> + </ng-template> <!-- mat menu for grouped layer --> diff --git a/src/res/css/extra_styles.css b/src/res/css/extra_styles.css index d55624cb7..9be9a689c 100644 --- a/src/res/css/extra_styles.css +++ b/src/res/css/extra_styles.css @@ -849,4 +849,11 @@ mat-list.sm mat-list-item padding: 16px; margin: -16px!important; padding-top: 6rem; -} \ No newline at end of file +} + +.no-padding-dialog > mat-dialog-container +{ + padding-top:0 !important; + padding-right:0 !important; + padding-left:0 !important; +} diff --git a/src/util/pureConstant.service.ts b/src/util/pureConstant.service.ts index f0248afed..f948ad7d5 100644 --- a/src/util/pureConstant.service.ts +++ b/src/util/pureConstant.service.ts @@ -330,7 +330,8 @@ Raise/track issues at github repo: <a target = "_blank" href = "${this.repoUrl}" '@id': parc.id, name: parc.name } - }) + }), + originDatainfos: tmpl.originDatainfos || [] } }), parcellations: parcellations.map(parc => { @@ -356,7 +357,8 @@ Raise/track issues at github repo: <a target = "_blank" href = "${this.repoUrl}" // name: "probability map" // }] } - }) + }), + originDatainfos: parc.originDatainfos || [] } }) } @@ -611,12 +613,14 @@ Raise/track issues at github repo: <a target = "_blank" href = "${this.repoUrl}" parcellations: tmpl.availableParcellations.filter( p => parcellations.some(p2 => parseId(p2.id) === p.id) ).map(parc => { + const fullParcInfo = parcellations.find(p => parseId(p.id) === parc.id) const regions = this.atlasParcSpcRegionMap.get(atlas['@id'], tmpl.id, parc.id) || [] return { fullId: parc.id, '@id': parc.id, name: parc.name, - regions + regions, + originDatainfos: fullParcInfo?.originDatainfos || [] } }), ...threeSurferConfig diff --git a/src/util/siibraApiConstants/types.ts b/src/util/siibraApiConstants/types.ts index 216945575..32c79048e 100644 --- a/src/util/siibraApiConstants/types.ts +++ b/src/util/siibraApiConstants/types.ts @@ -76,6 +76,15 @@ export type TParcSummary = { name: string } +export type TDatainfos = { + name: string + description: string + urls: { + cite: string + doi: string + }[] +} + export type TSpaceFull = { id: string name: string @@ -90,6 +99,7 @@ export type TSpaceFull = { templates: THref parcellation_maps: THref } + originDatainfos: TDatainfos[] } export type TParc = { @@ -112,6 +122,7 @@ export type TParc = { [key: string]: TVolumeSrc<keyof IVolumeTypeDetail>[] } } + originDatainfos: TDatainfos[] } export type TRegionDetail = { @@ -139,6 +150,7 @@ export type TRegionDetail = { links: { [key: string]: string } + originDatainfos: TDatainfos[] } export type TRegion = { diff --git a/src/viewerModule/module.ts b/src/viewerModule/module.ts index 91620bb87..566471f47 100644 --- a/src/viewerModule/module.ts +++ b/src/viewerModule/module.ts @@ -23,6 +23,7 @@ import { INJ_ANNOT_TARGET } from "src/atlasComponents/userAnnotations/tools/type import { NEHUBA_INSTANCE_INJTKN } from "./nehuba/util"; import { map } from "rxjs/operators"; import { TContextArg } from "./viewer.interface"; +import { ViewerStateBreadCrumbModule } from "./viewerStateBreadCrumb/module"; @NgModule({ imports: [ @@ -43,6 +44,7 @@ import { TContextArg } from "./viewer.interface"; UserAnnotationsModule, QuickTourModule, ContextMenuModule, + ViewerStateBreadCrumbModule, ], declarations: [ ViewerCmp, diff --git a/src/viewerModule/threeSurfer/threeSurferGlue/threeSurfer.component.ts b/src/viewerModule/threeSurfer/threeSurferGlue/threeSurfer.component.ts index e4cc4fa88..69f638277 100644 --- a/src/viewerModule/threeSurfer/threeSurferGlue/threeSurfer.component.ts +++ b/src/viewerModule/threeSurfer/threeSurferGlue/threeSurfer.component.ts @@ -72,7 +72,7 @@ export class ThreeSurferGlueCmp implements IViewer<'threeSurfer'>, OnChanges, Af await retry(async () => { for (const singleMesh of meshes) { const { hemisphere } = singleMesh - if (!this.regionMap.has(hemisphere)) throw new Error(`regionmap does not have hemisphere defined!`) + if (!this.regionMap.has(hemisphere)) throw new Error(`regionmap does not have hemisphere defined!`) } }, { timeout: 32, diff --git a/src/viewerModule/viewerCmp/viewerCmp.component.ts b/src/viewerModule/viewerCmp/viewerCmp.component.ts index d7109e888..3d7d37f69 100644 --- a/src/viewerModule/viewerCmp/viewerCmp.component.ts +++ b/src/viewerModule/viewerCmp/viewerCmp.component.ts @@ -1,20 +1,16 @@ -import { Component, ElementRef, Inject, Input, OnDestroy, Optional, TemplateRef, ViewChild, ViewContainerRef } from "@angular/core"; +import { Component, ElementRef, Inject, Input, OnDestroy, Optional, TemplateRef, ViewChild } from "@angular/core"; import { select, Store } from "@ngrx/store"; import {combineLatest, Observable, of, Subject, Subscription} from "rxjs"; -import {distinctUntilChanged, filter, map, startWith, switchMap } from "rxjs/operators"; -import { viewerStateHelperSelectParcellationWithId, viewerStateRemoveAdditionalLayer, viewerStateSetSelectedRegions } from "src/services/state/viewerState/actions"; +import {distinctUntilChanged, filter, map, shareReplay, startWith, switchMap } from "rxjs/operators"; +import { viewerStateSetSelectedRegions } from "src/services/state/viewerState/actions"; import { viewerStateContextedSelectedRegionsSelector, - viewerStateGetOverlayingAdditionalParcellations, - viewerStateParcVersionSelector, viewerStateSelectedParcellationSelector, viewerStateSelectedTemplateSelector, viewerStateStandAloneVolumes, viewerStateViewerModeSelector } from "src/services/state/viewerState/selectors" import { CONST, ARIA_LABELS, QUICKTOUR_DESC } from 'common/constants' -import { ngViewerActionClearView } from "src/services/state/ngViewerState/actions"; -import { ngViewerSelectorClearViewEntries } from "src/services/state/ngViewerState/selectors"; import { uiActionHideAllDatasets, uiActionHideDatasetWithId, uiActionShowDatasetWtihId } from "src/services/state/uiState/actions"; import { OVERWRITE_SHOW_DATASET_DIALOG_TOKEN, REGION_OF_INTEREST } from "src/util/interfaces"; import { animate, state, style, transition, trigger } from "@angular/animations"; @@ -74,7 +70,15 @@ import { ContextMenuService, TContextMenuReg } from "src/contextMenuModule"; if (!r[0]) return of(null) const { context } = r[0] const { atlas, template, parcellation } = context || {} - return svc.getRegionDetail(atlas['@id'], parcellation['@id'], template['@id'], r[0]) + return svc.getRegionDetail(atlas['@id'], parcellation['@id'], template['@id'], r[0]).pipe( + map(det => { + return { + ...det, + context + } + }), + shareReplay(1), + ) }) ), deps: [ Store, PureContantService ] @@ -117,11 +121,6 @@ export class ViewerCmp implements OnDestroy { order: 0, description: QUICKTOUR_DESC.ATLAS_SELECTOR, } - public quickTourChips: IQuickTourData = { - order: 5, - description: QUICKTOUR_DESC.CHIPS, - } - @Input() ismobile = false @@ -168,24 +167,6 @@ export class ViewerCmp implements OnDestroy { }) ) - public selectedLayerVersions$ = this.store$.pipe( - select(viewerStateParcVersionSelector), - map(arr => arr.map(item => { - const overwrittenName = item['@version'] && item['@version']['name'] - return overwrittenName - ? { ...item, displayName: overwrittenName } - : item - })) - ) - - public selectedAdditionalLayers$ = this.store$.pipe( - select(viewerStateGetOverlayingAdditionalParcellations), - ) - - public clearViewKeys$ = this.store$.pipe( - select(ngViewerSelectorClearViewEntries) - ) - /** * TODO may need to be deprecated * in favour of regional feature/data feature @@ -314,22 +295,6 @@ export class ViewerCmp implements OnDestroy { while (this.onDestroyCb.length > 0) this.onDestroyCb.pop()() } - public bindFns(fns){ - return () => { - for (const [ fn, ...arg] of fns) { - fn(...arg) - } - } - } - - public clearAdditionalLayer(layer: { ['@id']: string }){ - this.store$.dispatch( - viewerStateRemoveAdditionalLayer({ - payload: layer - }) - ) - } - public selectRoi(roi: any) { this.store$.dispatch( viewerStateSetSelectedRegions({ @@ -338,22 +303,6 @@ export class ViewerCmp implements OnDestroy { ) } - public clearSelectedRegions(){ - this.store$.dispatch( - viewerStateSetSelectedRegions({ - selectRegions: [] - }) - ) - } - - public selectParcellation(parc: any) { - this.store$.dispatch( - viewerStateHelperSelectParcellationWithId({ - payload: parc - }) - ) - } - public handleChipClick(){ this.openSideNavs() } @@ -363,13 +312,6 @@ export class ViewerCmp implements OnDestroy { this.sidenavTopSwitch && this.sidenavTopSwitch.open() } - public unsetClearViewByKey(key: string){ - this.store$.dispatch( - ngViewerActionClearView({ payload: { - [key]: false - }}) - ) - } public clearPreviewingDataset(id?: string){ /** * clear all preview diff --git a/src/viewerModule/viewerCmp/viewerCmp.template.html b/src/viewerModule/viewerCmp/viewerCmp.template.html index 4bcccb6e1..e323caf14 100644 --- a/src/viewerModule/viewerCmp/viewerCmp.template.html +++ b/src/viewerModule/viewerCmp/viewerCmp.template.html @@ -261,25 +261,9 @@ <!-- chips --> <div class="flex-grow-0 p-1 flex-shrink-1 overflow-y-hidden overflow-x-auto pe-all"> - <mat-chip-list class="d-inline-block" - quick-tour - [quick-tour-description]="quickTourChips.description" - [quick-tour-order]="quickTourChips.order"> - <!-- additional layer --> - - <ng-container> - <ng-container *ngTemplateOutlet="currParcellationTmpl; context: { addParc: (selectedAdditionalLayers$ | async), parc: (parcellationSelected$ | async) }"> - </ng-container> - </ng-container> - - <!-- any selected region(s) --> - <ng-container> - <ng-container *ngTemplateOutlet="selectedRegionTmpl"> - </ng-container> - </ng-container> - - - </mat-chip-list> + <viewer-state-breadcrumb + (on-item-click)="handleChipClick()"> + </viewer-state-breadcrumb> </div> </div> @@ -334,206 +318,6 @@ </iav-layout-fourcorners> </ng-template> -<!-- parcellation chip / region chip --> -<ng-template #currParcellationTmpl let-parc="parc" let-addParc="addParc"> - <div [matMenuTriggerFor]="layerVersionMenu" - [matMenuTriggerData]="{ layerVersionMenuTrigger: layerVersionMenuTrigger }" - #layerVersionMenuTrigger="matMenuTrigger"> - - <ng-template [ngIf]="addParc.length > 0" [ngIfElse]="defaultParcTmpl"> - <ng-container *ngFor="let p of addParc"> - <ng-container *ngTemplateOutlet="chipTmpl; context: { - parcel: p, - selected: true, - dismissable: true, - ariaLabel: ARIA_LABELS.PARC_VER_SELECT, - onclick: layerVersionMenuTrigger.toggleMenu.bind(layerVersionMenuTrigger) - }"> - </ng-container> - </ng-container> - </ng-template> - <ng-template #defaultParcTmpl> - <ng-template [ngIf]="parc"> - - <ng-container *ngTemplateOutlet="chipTmpl; context: { - parcel: parc, - selected: false, - dismissable: false, - ariaLabel: ARIA_LABELS.PARC_VER_SELECT, - onclick: layerVersionMenuTrigger.toggleMenu.bind(layerVersionMenuTrigger) - }"> - </ng-container> - </ng-template> - </ng-template> - </div> -</ng-template> - - -<!-- layer version selector --> -<mat-menu #layerVersionMenu - class="bg-none box-shadow-none" - [aria-label]="ARIA_LABELS.PARC_VER_CONTAINER" - [hasBackdrop]="false"> - <ng-template matMenuContent let-layerVersionMenuTrigger="layerVersionMenuTrigger"> - <div (iav-outsideClick)="layerVersionMenuTrigger.closeMenu()"> - <ng-container *ngFor="let parcVer of selectedLayerVersions$ | async"> - <ng-container *ngIf="parcellationSelected$ | async as selectedParcellation"> - - <ng-container *ngTemplateOutlet="chipTmpl; context: { - parcel: parcVer, - selected: selectedParcellation['@id'] === parcVer['@id'], - dismissable: false, - class: 'w-100', - ariaLabel: parcVer.displayName || parcVer.name, - onclick: bindFns([ - [ selectParcellation.bind(this), parcVer ], - [ layerVersionMenuTrigger.closeMenu.bind(layerVersionMenuTrigger) ] - ]) - }"> - </ng-container> - </ng-container> - <div class="mt-1"></div> - </ng-container> - </div> - </ng-template> -</mat-menu> - -<!-- chip tmpl --> -<ng-template #chipTmpl - let-parcel="parcel" - let-selected="selected" - let-dismissable="dismissable" - let-chipClass="class" - let-ariaLabel="ariaLabel" - let-onclick="onclick"> - <mat-chip class="pe-all position-relative z-index-2 d-inline-flex justify-content-between" - [ngClass]="chipClass" - [attr.aria-label]="ariaLabel" - (click)="onclick && onclick()" - [selected]="selected"> - - <span class="ws-no-wrap"> - {{ parcel?.groupName ? (parcel?.groupName + ' - ') : '' }}{{ parcel && (parcel.displayName || parcel.name) }} - </span> - - <!-- info icon --> - <ng-template [ngIf]="parcel?.originDatasets?.length > 0" [ngIfElse]="infoIconBasic"> - - <mat-icon - *ngFor="let ds of parcel.originDatasets" - fontSet="fas" - fontIcon="fa-info-circle" - iav-stop="click" - iav-dataset-show-dataset-dialog - [iav-dataset-show-dataset-dialog-kgid]="ds['kgId']" - [iav-dataset-show-dataset-dialog-kgschema]="ds['kgSchema']" - [iav-dataset-show-dataset-dialog-name]="parcel?.properties?.name" - [iav-dataset-show-dataset-dialog-description]="parcel?.properties?.description"> - </mat-icon> - - </ng-template> - - <ng-template #infoIconBasic> - <mat-icon *ngIf="parcel?.properties?.name && parcel?.properties?.description" - fontSet="fas" - fontIcon="fa-info-circle" - iav-stop="click" - iav-dataset-show-dataset-dialog - [iav-dataset-show-dataset-dialog-name]="parcel.properties.name" - [iav-dataset-show-dataset-dialog-description]="parcel.properties.description"> - - </mat-icon> - </ng-template> - - <!-- dismiss icon --> - <mat-icon - *ngIf="dismissable" - (click)="clearAdditionalLayer(parcel); $event.stopPropagation()" - fontSet="fas" - fontIcon="fa-times"> - </mat-icon> - </mat-chip> -</ng-template> - - -<ng-template #selectedRegionTmpl> - - <!-- regions chip --> - <ng-template [ngIf]="selectedRegions$ | async" let-selectedRegions="ngIf"> - <!-- if regions.length > 1 --> - <!-- use group chip --> - <ng-template [ngIf]="selectedRegions.length > 1" [ngIfElse]="singleRegionChipTmpl"> - <mat-chip - color="primary" - selected - (click)="handleChipClick()" - class="pe-all position-relative z-index-1 ml-8-n"> - <span class="iv-custom-comp text text-truncate d-inline pl-4"> - {{ CONST.MULTI_REGION_SELECTION }} - </span> - <mat-icon - (click)="clearSelectedRegions()" - fontSet="fas" - iav-stop="click" - fontIcon="fa-times"> - </mat-icon> - </mat-chip> - </ng-template> - - <!-- if reginos.lengt === 1 --> - <!-- use single region chip --> - <ng-template #singleRegionChipTmpl> - <ng-container *ngFor="let r of selectedRegions"> - - <!-- region chip for discrete map --> - <mat-chip - iav-region - (click)="handleChipClick()" - [region]="r" - class="pe-all position-relative z-index-1 ml-8-n" - [ngClass]="{ - 'darktheme':regionDirective.rgbDarkmode === true, - 'lighttheme': regionDirective.rgbDarkmode === false - }" - [style.backgroundColor]="regionDirective.rgbString" - #regionDirective="iavRegion"> - <span class="iv-custom-comp text text-truncate d-inline pl-4"> - {{ r.name }} - </span> - <mat-icon - class="iv-custom-comp text" - (click)="clearSelectedRegions()" - fontSet="fas" - iav-stop="click" - fontIcon="fa-times"> - </mat-icon> - </mat-chip> - - <!-- chips for previewing origin datasets/continous map --> - <ng-container *ngFor="let originDataset of (r.originDatasets || []); let index = index"> - - - <mat-chip *ngFor="let key of clearViewKeys$ | async" - (click)="handleChipClick()" - class="pe-all position-relative ml-8-n"> - <span class="pl-4"> - {{ key }} - </span> - <mat-icon (click)="unsetClearViewByKey(key)" - fontSet="fas" - iav-stop="click" - fontIcon="fa-times"> - - </mat-icon> - </mat-chip> - </ng-container> - - </ng-container> - </ng-template> - </ng-template> - -</ng-template> - <!-- auto complete search box --> <ng-template #autocompleteTmpl let-showTour="showTour"> @@ -548,7 +332,6 @@ </div> </ng-template> - <!-- template for rendering tab --> <ng-template #tabTmpl let-isOpen="isOpen" @@ -658,9 +441,7 @@ <ng-template [ngIf]="selectedRegions.length === 1" [ngIfElse]="multiRegionWrapperTmpl"> <!-- a series of bugs result in requiring this hacky --> <!-- see https://github.com/HumanBrainProject/interactive-viewer/issues/698 --> - <ng-container *ngFor="let region of selectedRegions"> - <ng-container *ngTemplateOutlet="singleRegionTmpl; context: { region: region }"> - </ng-container> + <ng-container *ngTemplateOutlet="singleRegionTmpl; context: { region: (regionOfInterest$ | async) }"> </ng-container> </ng-template> diff --git a/src/viewerModule/viewerStateBreadCrumb/breadcrumb/breadcrumb.component.ts b/src/viewerModule/viewerStateBreadCrumb/breadcrumb/breadcrumb.component.ts new file mode 100644 index 000000000..ed62809f3 --- /dev/null +++ b/src/viewerModule/viewerStateBreadCrumb/breadcrumb/breadcrumb.component.ts @@ -0,0 +1,115 @@ +import { Component, EventEmitter, Output } from "@angular/core"; +import { IQuickTourData } from "src/ui/quickTour"; +import { CONST, ARIA_LABELS, QUICKTOUR_DESC } from 'common/constants' +import { select, Store } from "@ngrx/store"; +import { viewerStateContextedSelectedRegionsSelector, viewerStateGetOverlayingAdditionalParcellations, viewerStateParcVersionSelector, viewerStateSelectedParcellationSelector } from "src/services/state/viewerState/selectors"; +import { distinctUntilChanged, map } from "rxjs/operators"; +import { viewerStateHelperSelectParcellationWithId, viewerStateRemoveAdditionalLayer, viewerStateSetSelectedRegions } from "src/services/state/viewerState.store.helper"; +import { ngViewerActionClearView, ngViewerSelectorClearViewEntries } from "src/services/state/ngViewerState.store.helper"; +import { OVERWRITE_SHOW_DATASET_DIALOG_TOKEN } from "src/util/interfaces"; + +@Component({ + selector: 'viewer-state-breadcrumb', + templateUrl: './breadcrumb.template.html', + styleUrls: [ + './breadcrumb.style.css' + ], + providers: [ + { + provide: OVERWRITE_SHOW_DATASET_DIALOG_TOKEN, + useValue: null + } + ] +}) + +export class ViewerStateBreadCrumb { + + public CONST = CONST + public ARIA_LABELS = ARIA_LABELS + + @Output('on-item-click') + onChipClick = new EventEmitter() + + public quickTourChips: IQuickTourData = { + order: 5, + description: QUICKTOUR_DESC.CHIPS, + } + + public clearViewKeys$ = this.store$.pipe( + select(ngViewerSelectorClearViewEntries) + ) + + public selectedAdditionalLayers$ = this.store$.pipe( + select(viewerStateGetOverlayingAdditionalParcellations), + ) + + public parcellationSelected$ = this.store$.pipe( + select(viewerStateSelectedParcellationSelector), + distinctUntilChanged(), + ) + + public selectedRegions$ = this.store$.pipe( + select(viewerStateContextedSelectedRegionsSelector), + distinctUntilChanged(), + ) + + public selectedLayerVersions$ = this.store$.pipe( + select(viewerStateParcVersionSelector), + map(arr => arr.map(item => { + const overwrittenName = item['@version'] && item['@version']['name'] + return overwrittenName + ? { ...item, displayName: overwrittenName } + : item + })) + ) + + + constructor(private store$: Store<any>){ + + } + + handleChipClick(){ + this.onChipClick.emit(null) + } + + public clearSelectedRegions(){ + this.store$.dispatch( + viewerStateSetSelectedRegions({ + selectRegions: [] + }) + ) + } + + public unsetClearViewByKey(key: string){ + this.store$.dispatch( + ngViewerActionClearView({ payload: { + [key]: false + }}) + ) + } + + public clearAdditionalLayer(layer: { ['@id']: string }){ + this.store$.dispatch( + viewerStateRemoveAdditionalLayer({ + payload: layer + }) + ) + } + + public selectParcellation(parc: any) { + this.store$.dispatch( + viewerStateHelperSelectParcellationWithId({ + payload: parc + }) + ) + } + + public bindFns(fns){ + return () => { + for (const [ fn, ...arg] of fns) { + fn(...arg) + } + } + } + +} \ No newline at end of file diff --git a/src/viewerModule/viewerStateBreadCrumb/breadcrumb/breadcrumb.style.css b/src/viewerModule/viewerStateBreadCrumb/breadcrumb/breadcrumb.style.css new file mode 100644 index 000000000..e69de29bb diff --git a/src/viewerModule/viewerStateBreadCrumb/breadcrumb/breadcrumb.template.html b/src/viewerModule/viewerStateBreadCrumb/breadcrumb/breadcrumb.template.html new file mode 100644 index 000000000..c36eb235e --- /dev/null +++ b/src/viewerModule/viewerStateBreadCrumb/breadcrumb/breadcrumb.template.html @@ -0,0 +1,206 @@ +<mat-chip-list + quick-tour + [quick-tour-description]="quickTourChips.description" + [quick-tour-order]="quickTourChips.order"> + + <!-- additional layer --> + + <ng-container> + <ng-container *ngTemplateOutlet="currParcellationTmpl; context: { + addParc: (selectedAdditionalLayers$ | async), + parc: (parcellationSelected$ | async) + }"> + </ng-container> + </ng-container> + + <!-- any selected region(s) --> + <ng-container> + <ng-container *ngTemplateOutlet="selectedRegionTmpl"> + </ng-container> + </ng-container> +</mat-chip-list> + + +<!-- parcellation chip / region chip --> +<ng-template #currParcellationTmpl let-parc="parc" let-addParc="addParc"> + <div [matMenuTriggerFor]="layerVersionMenu" + [matMenuTriggerData]="{ layerVersionMenuTrigger: layerVersionMenuTrigger }" + #layerVersionMenuTrigger="matMenuTrigger"> + + <ng-template [ngIf]="addParc.length > 0" [ngIfElse]="defaultParcTmpl"> + <ng-container *ngFor="let p of addParc"> + <ng-container *ngTemplateOutlet="chipTmpl; context: { + parcel: p, + selected: true, + dismissable: true, + ariaLabel: ARIA_LABELS.PARC_VER_SELECT, + onclick: layerVersionMenuTrigger.toggleMenu.bind(layerVersionMenuTrigger) + }"> + </ng-container> + </ng-container> + </ng-template> + <ng-template #defaultParcTmpl> + <ng-template [ngIf]="parc"> + + <ng-container *ngTemplateOutlet="chipTmpl; context: { + parcel: parc, + selected: false, + dismissable: false, + ariaLabel: ARIA_LABELS.PARC_VER_SELECT, + onclick: layerVersionMenuTrigger.toggleMenu.bind(layerVersionMenuTrigger) + }"> + </ng-container> + </ng-template> + </ng-template> + </div> +</ng-template> + + +<ng-template #selectedRegionTmpl> + + <!-- regions chip --> + <ng-template [ngIf]="selectedRegions$ | async" let-selectedRegions="ngIf"> + <!-- if regions.length > 1 --> + <!-- use group chip --> + <ng-template [ngIf]="selectedRegions.length > 1" [ngIfElse]="singleRegionChipTmpl"> + <mat-chip + color="primary" + selected + (click)="handleChipClick()" + class="pe-all position-relative z-index-1 ml-8-n"> + <span class="iv-custom-comp text text-truncate d-inline pl-4"> + {{ CONST.MULTI_REGION_SELECTION }} + </span> + <mat-icon + (click)="clearSelectedRegions()" + fontSet="fas" + iav-stop="click" + fontIcon="fa-times"> + </mat-icon> + </mat-chip> + </ng-template> + + <!-- if reginos.lengt === 1 --> + <!-- use single region chip --> + <ng-template #singleRegionChipTmpl> + <ng-container *ngFor="let r of selectedRegions"> + + <!-- region chip for discrete map --> + <mat-chip + (click)="handleChipClick()" + [region]="r" + class="pe-all position-relative z-index-1 ml-8-n" + [ngClass]="{ + 'darktheme':regionDirective.rgbDarkmode === true, + 'lighttheme': regionDirective.rgbDarkmode === false + }" + [style.backgroundColor]="regionDirective.rgbString" + iav-region + #regionDirective="iavRegion"> + <span class="iv-custom-comp text text-truncate d-inline pl-4"> + {{ r.name }} + </span> + <mat-icon + class="iv-custom-comp text" + (click)="clearSelectedRegions()" + fontSet="fas" + iav-stop="click" + fontIcon="fa-times"> + </mat-icon> + </mat-chip> + + <!-- chips for previewing origin datasets/continous map --> + <ng-container *ngFor="let originDataset of (r.originDatasets || []); let index = index"> + + <mat-chip *ngFor="let key of clearViewKeys$ | async" + (click)="handleChipClick()" + class="pe-all position-relative ml-8-n"> + <span class="pl-4"> + {{ key }} + </span> + <mat-icon (click)="unsetClearViewByKey(key)" + fontSet="fas" + iav-stop="click" + fontIcon="fa-times"> + + </mat-icon> + </mat-chip> + </ng-container> + + </ng-container> + </ng-template> + </ng-template> + +</ng-template> + +<!-- layer version selector --> +<mat-menu #layerVersionMenu + class="bg-none box-shadow-none" + [aria-label]="ARIA_LABELS.PARC_VER_CONTAINER" + [hasBackdrop]="false"> + <ng-template matMenuContent let-layerVersionMenuTrigger="layerVersionMenuTrigger"> + <div (iav-outsideClick)="layerVersionMenuTrigger.closeMenu()"> + <ng-container *ngFor="let parcVer of selectedLayerVersions$ | async"> + <ng-container *ngIf="parcellationSelected$ | async as selectedParcellation"> + + <ng-container *ngTemplateOutlet="chipTmpl; context: { + parcel: parcVer, + selected: selectedParcellation['@id'] === parcVer['@id'], + dismissable: false, + class: 'w-100', + ariaLabel: parcVer.displayName || parcVer.name, + onclick: bindFns([ + [ selectParcellation.bind(this), parcVer ], + [ layerVersionMenuTrigger.closeMenu.bind(layerVersionMenuTrigger) ] + ]) + }"> + </ng-container> + </ng-container> + <div class="mt-1"></div> + </ng-container> + </div> + </ng-template> +</mat-menu> + + +<ng-template #chipTmpl + let-parcel="parcel" + let-selected="selected" + let-dismissable="dismissable" + let-chipClass="class" + let-ariaLabel="ariaLabel" + let-onclick="onclick"> + <mat-chip class="pe-all position-relative z-index-2 d-inline-flex justify-content-between" + [ngClass]="chipClass" + [attr.aria-label]="ariaLabel" + (click)="onclick && onclick()" + [selected]="selected"> + + <span class="ws-no-wrap"> + {{ parcel?.groupName ? (parcel?.groupName + ' - ') : '' }}{{ parcel && (parcel.displayName || parcel.name) }} + </span> + + <!-- info icon --> + <ng-container *ngFor="let originDatainfo of (parcel?.originDatainfos || [])"> + + <mat-icon + fontSet="fas" + fontIcon="fa-info-circle" + iav-stop="click" + iav-dataset-show-dataset-dialog + [iav-dataset-show-dataset-dialog-name]="originDatainfo.name" + [iav-dataset-show-dataset-dialog-description]="originDatainfo.description" + [iav-dataset-show-dataset-dialog-urls]="originDatainfo.urls"> + </mat-icon> + + </ng-container> + + <!-- dismiss icon --> + <mat-icon + *ngIf="dismissable" + (click)="clearAdditionalLayer(parcel); $event.stopPropagation()" + fontSet="fas" + fontIcon="fa-times"> + </mat-icon> + </mat-chip> +</ng-template> diff --git a/src/viewerModule/viewerStateBreadCrumb/module.ts b/src/viewerModule/viewerStateBreadCrumb/module.ts new file mode 100644 index 000000000..faae35e4d --- /dev/null +++ b/src/viewerModule/viewerStateBreadCrumb/module.ts @@ -0,0 +1,30 @@ +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { ParcellationRegionModule } from "src/atlasComponents/parcellationRegion"; +import { KgDatasetModule } from "src/atlasComponents/regionalFeatures/bsFeatures/kgDataset"; +import { QuickTourModule } from "src/ui/quickTour"; +import { AngularMaterialModule } from "src/ui/sharedModules/angularMaterial.module"; +import { UtilModule } from "src/util"; +import { OVERWRITE_SHOW_DATASET_DIALOG_TOKEN } from "src/util/interfaces"; +import { ViewerStateBreadCrumb } from "./breadcrumb/breadcrumb.component"; + +@NgModule({ + imports: [ + CommonModule, + AngularMaterialModule, + QuickTourModule, + ParcellationRegionModule, + KgDatasetModule, + UtilModule, + ], + declarations: [ + ViewerStateBreadCrumb, + ], + exports: [ + ViewerStateBreadCrumb, + ], + providers:[ + ] +}) + +export class ViewerStateBreadCrumbModule{} \ No newline at end of file -- GitLab