diff --git a/deploy/regionalFeatures/index.js b/deploy/regionalFeatures/index.js index dc844d7874e8561e053d9392e0d6e4155b85b5b5..ad46b1a4ef7d5f56c49fc5299fb1f3ee3ea1d37b 100644 --- a/deploy/regionalFeatures/index.js +++ b/deploy/regionalFeatures/index.js @@ -87,7 +87,11 @@ Promise.all( for (const [ datasetId, arrRegionIds ] of map.entries()) { additionalDatasets = additionalDatasets.concat({ fullId: datasetId, - parcellationRegion: arrRegionIds.map(id => ({ fullId: id })) + parcellationRegion: arrRegionIds.map(id => ({ fullId: id })), + species: [], + kgReference: [ + `https://kg.ebrains.eu/search/instances/Dataset/${datasetId}` + ] }) } diff --git a/src/main.module.ts b/src/main.module.ts index b0ca199211e7e8476df25182d2dc1c195a24373b..2ad478a6ac13ac3cd2f214e4bdd1f3ef2a4ef1b2 100644 --- a/src/main.module.ts +++ b/src/main.module.ts @@ -55,9 +55,10 @@ import 'src/res/css/version.css' import 'src/theme.scss' import { DatasetPreviewGlue, datasetPreviewMetaReducer, IDatasetPreviewGlue, GlueEffects, ClickInterceptorService } from './glue'; import { viewerStateHelperReducer, viewerStateFleshOutDetail, viewerStateMetaReducers, ViewerStateHelperEffect } from './services/state/viewerState.store.helper'; -import { take } from 'rxjs/operators'; import { TOS_OBS_INJECTION_TOKEN } from './ui/kgtos/kgtos.component'; import { UiEffects } from './services/state/uiState/ui.effects'; +import { OVERWRITE_SHOW_DATASET_DIALOG_TOKEN, TOverwriteShowDatasetDialog } from './util/interfaces'; +import { uiActionShowDatasetWtihId } from './services/state/uiState/actions'; export function debug(reducer: ActionReducer<any>): ActionReducer<any> { return function(state, action) { @@ -242,6 +243,23 @@ export function debug(reducer: ActionReducer<any>): ActionReducer<any> { deps: [ ClickInterceptorService ] + }, + { + provide: OVERWRITE_SHOW_DATASET_DIALOG_TOKEN, + useFactory: (store: Store<any>) => { + return function overwriteShowDatasetDialog( arg: { fullId?: string, name: string, description: string } ){ + if (arg.fullId) { + store.dispatch( + uiActionShowDatasetWtihId({ + id: arg.fullId + }) + ) + } + } as TOverwriteShowDatasetDialog + }, + deps: [ + Store + ] } ], bootstrap : [ diff --git a/src/services/state/uiState.store.helper.ts b/src/services/state/uiState.store.helper.ts index 2be5ec57d5d375a41ceba840cc58f145cd1c14e2..94e404cb5359acaf491e7b321e60a57d1a77e0a6 100644 --- a/src/services/state/uiState.store.helper.ts +++ b/src/services/state/uiState.store.helper.ts @@ -7,7 +7,9 @@ import { uiStateCollapseSidePanel, uiStateExpandSidePanel, uiStateOpenSidePanel, - uiStateShowBottomSheet + uiStateShowBottomSheet, + uiActionHideDatasetWithId, + uiActionShowDatasetWtihId, } from './uiState/actions' export { @@ -17,15 +19,23 @@ export { uiStateCollapseSidePanel, uiStateExpandSidePanel, uiStateOpenSidePanel, - uiStateShowBottomSheet + uiStateShowBottomSheet, + uiActionHideDatasetWithId, + uiActionShowDatasetWtihId, } import { - uiStatePreviewingDatasetFilesSelector + uiStatePreviewingDatasetFilesSelector, + uiStateMouseOverSegmentsSelector, + uiStateMouseoverUserLandmark, + uiStateShownDatasetIdSelector, } from './uiState/selectors' export { - uiStatePreviewingDatasetFilesSelector + uiStatePreviewingDatasetFilesSelector, + uiStateMouseOverSegmentsSelector, + uiStateMouseoverUserLandmark, + uiStateShownDatasetIdSelector, } export enum EnumWidgetTypes{ diff --git a/src/services/state/uiState.store.ts b/src/services/state/uiState.store.ts index 94d4d765e88e835e974fb08a45410bf466b588a3..5769426bfa2f5124e278d46fcdfbcc67af528650 100644 --- a/src/services/state/uiState.store.ts +++ b/src/services/state/uiState.store.ts @@ -10,7 +10,10 @@ import { MatBottomSheetRef, MatBottomSheet } from '@angular/material/bottom-shee import { uiStateCloseSidePanel, uiStateOpenSidePanel, uiStateCollapseSidePanel, uiStateExpandSidePanel, uiActionSetPreviewingDatasetFiles, uiStateShowBottomSheet, uiActionShowSidePanelConnectivity } from './uiState.store.helper'; import { viewerStateMouseOverCustomLandmark } from './viewerState/actions'; import { IUiState } from './uiState/common' +import { uiActionHideAllDatasets, uiActionHideDatasetWithId, uiActionShowDatasetWtihId } from './uiState/actions'; export const defaultState: IUiState = { + shownDatasetId: [], + previewingDatasetFiles: [], mouseOverSegments: [], @@ -36,6 +39,26 @@ export { IUiState } export const getStateStore = ({ state = defaultState } = {}) => (prevState: IUiState = state, action: ActionInterface) => { switch (action.type) { + case uiActionHideDatasetWithId.type:{ + return { + ...prevState, + shownDatasetId: prevState.shownDatasetId.filter(id => id !== (action as any).id) + } + } + case uiActionHideAllDatasets.type:{ + return { + ...prevState, + shownDatasetId: [] + } + } + case uiActionShowDatasetWtihId.type: { + return { + ...prevState, + shownDatasetId: prevState.shownDatasetId.concat( + (action as any).id + ) + } + } case uiActionSetPreviewingDatasetFiles.type: { const { previewingDatasetFiles } = action as any @@ -91,6 +114,7 @@ export const getStateStore = ({ state = defaultState } = {}) => (prevState: IUiS ...prevState, sidePanelIsOpen: false, } + case uiActionShowSidePanelConnectivity.type: case uiStateExpandSidePanel.type: case EXPAND_SIDE_PANEL_CURRENT_VIEW: return { @@ -104,7 +128,6 @@ export const getStateStore = ({ state = defaultState } = {}) => (prevState: IUiS sidePanelExploreCurrentViewIsOpen: false, } - case uiActionShowSidePanelConnectivity.type: case AGREE_COOKIE: { /** * TODO replace with server side logic diff --git a/src/services/state/uiState/actions.ts b/src/services/state/uiState/actions.ts index 9db6183506e44a67930ee0801f0c4d3c9d8339f4..bfd0548766e1ce870b3747b155805657826316dc 100644 --- a/src/services/state/uiState/actions.ts +++ b/src/services/state/uiState/actions.ts @@ -32,3 +32,17 @@ export const uiActionSetPreviewingDatasetFiles = createAction( export const uiActionShowSidePanelConnectivity = createAction( `[uiState] showSidePanelConnectivity` ) + +export const uiActionShowDatasetWtihId = createAction( + `[uiState] showDatasetWithId`, + props<{ id: string }>() +) + +export const uiActionHideDatasetWithId = createAction( + `[uiState] hideDatasetWithId`, + props<{ id: string }>() +) + +export const uiActionHideAllDatasets = createAction( + `[uiState] hideAllDatasets` +) diff --git a/src/services/state/uiState/common.ts b/src/services/state/uiState/common.ts index 5416ff23e75ef56a934aeb514865596b121fbe8f..a5c833a42fc0c1a91fc2afeade927012e54f8d8b 100644 --- a/src/services/state/uiState/common.ts +++ b/src/services/state/uiState/common.ts @@ -1,4 +1,6 @@ export interface IUiState{ + shownDatasetId: string[] + previewingDatasetFiles: {datasetId: string, filename: string}[] mouseOverSegments: Array<{ diff --git a/src/services/state/uiState/selectors.ts b/src/services/state/uiState/selectors.ts index f72757b436adbf77e19becde70cff55213484504..62064da340cce049eec2236c739bb7bd7eb15ab7 100644 --- a/src/services/state/uiState/selectors.ts +++ b/src/services/state/uiState/selectors.ts @@ -26,3 +26,8 @@ export const uiStateMouseoverUserLandmark = createSelector( state => state['uiState'], uiState => uiState['mouseOverUserLandmark'] ) + +export const uiStateShownDatasetIdSelector = createSelector( + state => state['uiState'], + uiState => uiState['shownDatasetId'] +) diff --git a/src/ui/databrowserModule/databrowser.module.ts b/src/ui/databrowserModule/databrowser.module.ts index 36896a9ef523193b0f5e1a7a29326a0dc2db4eec..056b5973025be34026c80474aeef5707928ccf19 100644 --- a/src/ui/databrowserModule/databrowser.module.ts +++ b/src/ui/databrowserModule/databrowser.module.ts @@ -45,6 +45,9 @@ import { LayerBrowserModule } from "../layerbrowser"; import { DatabrowserDirective } from "./databrowser/databrowser.directive"; 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"; const previewEmitFactory = ( overrideFn: (file: any, dataset: any) => void) => { @@ -62,6 +65,7 @@ const previewEmitFactory = ( overrideFn: (file: any, dataset: any) => void) => { AngularMaterialModule, LayerBrowserModule, ContributorModule, + RegionalFeaturesModule, ], declarations: [ DataBrowser, @@ -72,6 +76,7 @@ const previewEmitFactory = ( overrideFn: (file: any, dataset: any) => void) => { PreviewComponentWrapper, BulkDownloadBtn, PreviewCardComponent, + SingleDatasetSideNavView, /** * Directives @@ -80,6 +85,7 @@ const previewEmitFactory = ( overrideFn: (file: any, dataset: any) => void) => { PreviewDatasetFile, ShownPreviewsDirective, DatabrowserDirective, + ShownDatasetDirective, /** * pipes @@ -120,6 +126,8 @@ const previewEmitFactory = ( overrideFn: (file: any, dataset: any) => void) => { FilterPreviewByType, PreviewCardComponent, DatabrowserDirective, + ShownDatasetDirective, + SingleDatasetSideNavView, ], entryComponents: [ DataBrowser, diff --git a/src/ui/databrowserModule/preview/previewCard/previewCard.component.ts b/src/ui/databrowserModule/preview/previewCard/previewCard.component.ts index ffba858ee0dc3d617bae212869ad8f33a0742924..73ddae99dc17d0574fccbd2ac4890d7a44ec4b96 100644 --- a/src/ui/databrowserModule/preview/previewCard/previewCard.component.ts +++ b/src/ui/databrowserModule/preview/previewCard/previewCard.component.ts @@ -2,6 +2,8 @@ import { Component, Optional, Inject } from "@angular/core"; import { PreviewBase } from "../preview.base"; import { GET_KGDS_PREVIEW_INFO_FROM_ID_FILENAME } from "../../pure"; +// TODO deprecate in favour of datafeature/region feature + @Component({ selector: 'preview-card', templateUrl: './previewCard.template.html', @@ -16,4 +18,4 @@ export class PreviewCardComponent extends PreviewBase { ){ super(getDatasetPreviewFromId) } -} \ No newline at end of file +} diff --git a/src/ui/databrowserModule/preview/shownPreviews.directive.ts b/src/ui/databrowserModule/preview/shownPreviews.directive.ts index c90350c64a1a2a39aebe631c1896371e97d24691..7fefaf7ea473ccc35846c9f14ead0d4d65ac16aa 100644 --- a/src/ui/databrowserModule/preview/shownPreviews.directive.ts +++ b/src/ui/databrowserModule/preview/shownPreviews.directive.ts @@ -2,7 +2,7 @@ import { Directive, Optional, Inject, Output, EventEmitter, OnDestroy } from "@a import { Store, select } from "@ngrx/store"; import { uiStatePreviewingDatasetFilesSelector } from "src/services/state/uiState/selectors"; import { EnumPreviewFileTypes } from "../pure"; -import { switchMap, map, startWith, withLatestFrom } from "rxjs/operators"; +import { switchMap, map, startWith } from "rxjs/operators"; import { forkJoin, of, Subscription } from "rxjs"; import { GET_KGDS_PREVIEW_INFO_FROM_ID_FILENAME } from "../pure"; import { viewerStateSelectedTemplateSelector } from "src/services/state/viewerState/selectors"; diff --git a/src/ui/databrowserModule/shownDataset.directive.ts b/src/ui/databrowserModule/shownDataset.directive.ts new file mode 100644 index 0000000000000000000000000000000000000000..dd46b310241b7f35cefb95637b3174bfb27457a1 --- /dev/null +++ b/src/ui/databrowserModule/shownDataset.directive.ts @@ -0,0 +1,20 @@ +import { Directive } from "@angular/core"; +import { select, Store } from "@ngrx/store"; +import { Observable } from "rxjs"; +import { uiStateShownDatasetIdSelector } from "src/services/state/uiState/selectors"; + +@Directive({ + selector: '[iav-shown-dataset]', + exportAs: 'iavShownDataset' +}) + +export class ShownDatasetDirective{ + public shownDatasetId$: Observable<string[]> + constructor( + store$: Store<any> + ){ + this.shownDatasetId$ = store$.pipe( + select(uiStateShownDatasetIdSelector) + ) + } +} diff --git a/src/ui/databrowserModule/singleDataset/sideNavView/sDsSideNavView.component.ts b/src/ui/databrowserModule/singleDataset/sideNavView/sDsSideNavView.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..bc7ecdd969511e0c44a559a1fe1d33ddfa72a352 --- /dev/null +++ b/src/ui/databrowserModule/singleDataset/sideNavView/sDsSideNavView.component.ts @@ -0,0 +1,45 @@ +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, OnChanges, OnDestroy, Optional, SimpleChange, SimpleChanges } 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"; + + +@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{ + + 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/ui/databrowserModule/singleDataset/sideNavView/sDsSideNavView.style.css b/src/ui/databrowserModule/singleDataset/sideNavView/sDsSideNavView.style.css new file mode 100644 index 0000000000000000000000000000000000000000..8d0266fe77710f71d9e50865274cd336aaf8cecc --- /dev/null +++ b/src/ui/databrowserModule/singleDataset/sideNavView/sDsSideNavView.style.css @@ -0,0 +1,6 @@ +.header-container +{ + padding: 16px; + margin: -16px!important; + padding-top: 6rem; +} diff --git a/src/ui/databrowserModule/singleDataset/sideNavView/sDsSideNavView.template.html b/src/ui/databrowserModule/singleDataset/sideNavView/sDsSideNavView.template.html new file mode 100644 index 0000000000000000000000000000000000000000..91dd2aa8d349b4c6a51f816838729f8b6c95e783 --- /dev/null +++ b/src/ui/databrowserModule/singleDataset/sideNavView/sDsSideNavView.template.html @@ -0,0 +1,95 @@ +<mat-card class="mat-elevation-z4"> + <div class="header-container"> + <mat-card-title> + <span> + {{ name }} + </span> + </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> + + <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" + target="_blank"> + <button mat-icon-button + [matTooltip]="EXPLORE_DATASET_IN_KG_ARIA_LABEL"> + <i class="fas fa-external-link-alt"></i> + </button> + </a> + + <!-- fav btn --> + <ng-container *ngTemplateOutlet="favDatasetBtn"> + </ng-container> + + </mat-card-subtitle> + </div> +</mat-card> + +<mat-card class="mt-4"> + <mat-card-content> + <mat-tab-group> + + <!-- details --> + <mat-tab> + <ng-template mat-tab-label> + Details + </ng-template> + <ng-template matTabContent> + <small class="m-1"> + <markdown-dom [markdown]="description"> + </markdown-dom> + </small> + </ng-template> + </mat-tab> + + <!-- features --> + <div class="hidden" + [region]="region$ | async" + (loadingStateChanged)="detectChange()" + region-get-all-features-directive + #rfGetAllFeatures="rfGetAllFeatures"> + </div> + + <ng-container *ngFor="let feature of (rfGetAllFeatures.features | filterRegionFeaturesById : fullId)"> + <mat-tab> + <ng-template mat-tab-label> + {{ feature.type }} + </ng-template> + <ng-template matTabContent> + <feature-explorer [feature]="feature"> + + </feature-explorer> + </ng-template> + </mat-tab> + </ng-container> + + </mat-tab-group> + </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> + diff --git a/src/ui/databrowserModule/singleDataset/singleDataset.base.ts b/src/ui/databrowserModule/singleDataset/singleDataset.base.ts index be8f58cb8caf4339f7e4fc8eac166da51f94525d..9111a1264dcbfb944a6852758116add55cc8f27e 100644 --- a/src/ui/databrowserModule/singleDataset/singleDataset.base.ts +++ b/src/ui/databrowserModule/singleDataset/singleDataset.base.ts @@ -42,7 +42,13 @@ export class SingleDatasetBase implements OnChanges, OnDestroy { @Input() set fullId(val){ - this._fullId = val + if (val === this._fullId) return + const re = getKgSchemaIdFromFullId(val) + if (re) { + this._fullId = val + this.kgSchema = re[0] + this.kgId = re[1] + } } get fullId(){ @@ -125,31 +131,35 @@ export class SingleDatasetBase implements OnChanges, OnDestroy { this.cdr.markForCheck() return this.singleDatasetService.getInfoFromKg({ kgSchema, kgId }) }) - ).subscribe(dataset => { - if (!dataset) return - const { kgSchema, kgId } = this - - const { name, description, publications, fullId, kgReference, files, contributors, ...rest } = dataset - this.name = name - this.description = description - this.publications = publications - this.contributors = contributors - this.files = files - this.fullId = fullId - - this.kgReference = kgReference - - this.dlFromKgHref = this.singleDatasetService.getDownloadZipFromKgHref({ kgSchema, kgId }) - - this.fetchFlag = false - this.cdr.markForCheck() - }, - err => { - this.fetchFlag = false - this.name = `[This dataset cannot be fetched right now]` - this.description = ` ` - this.cdr.markForCheck() - }) + ).subscribe( + dataset => { + if (!dataset) return + + const { kgSchema, kgId } = this + + const { name, description, publications, fullId, kgReference, files, contributors, ...rest } = dataset + this.name = name + this.description = description + this.publications = publications + this.contributors = contributors + this.files = files + this.fullId = fullId + + this.kgReference = kgReference + + this.dlFromKgHref = this.singleDatasetService.getDownloadZipFromKgHref({ kgSchema, kgId }) + + this.fetchFlag = false + this.cdr.markForCheck() + }, + err => { + this.fetchFlag = false + + this.name = `[This dataset cannot be fetched right now]` + this.description = ` ` + this.cdr.markForCheck() + } + ) ) /** diff --git a/src/ui/databrowserModule/util/datasetIsFaved.pipe.ts b/src/ui/databrowserModule/util/datasetIsFaved.pipe.ts index 1071413ba9342cf8d0f7abfe89aab06ce999b187..ca06f0246e9a1ef811f1f705a408d138f2d073e9 100644 --- a/src/ui/databrowserModule/util/datasetIsFaved.pipe.ts +++ b/src/ui/databrowserModule/util/datasetIsFaved.pipe.ts @@ -1,12 +1,13 @@ import { Pipe, PipeTransform } from "@angular/core"; import { IDataEntry } from "src/services/stateStore.service"; +import { IHasFullId } from "src/util/interfaces"; import { getKgSchemaIdFromFullId } from "./getKgSchemaIdFromFullId.pipe"; @Pipe({ name: 'datasetIsFaved', }) export class DatasetIsFavedPipe implements PipeTransform { - public transform(favedDataEntry: Partial<IDataEntry>[], dataentry: IDataEntry): boolean { + public transform(favedDataEntry: (Partial<IDataEntry>|IHasFullId)[], dataentry: IHasFullId): boolean { if (!dataentry) { return false } const re2 = getKgSchemaIdFromFullId(dataentry.fullId) if (!re2) return false diff --git a/src/ui/nehubaContainer/nehubaContainer.component.spec.ts b/src/ui/nehubaContainer/nehubaContainer.component.spec.ts index 3e5017b97f3c820dd1637442e736a6505f4edf04..f14ef99c49fe88828f14cfb3bb25c39dcc2f744b 100644 --- a/src/ui/nehubaContainer/nehubaContainer.component.spec.ts +++ b/src/ui/nehubaContainer/nehubaContainer.component.spec.ts @@ -37,10 +37,10 @@ import { ARIA_LABELS } from 'common/constants' import { NoopAnimationsModule } from '@angular/platform-browser/animations' import { RegionAccordionTooltipTextPipe } from '../util' import { hot } from 'jasmine-marbles' -import { of } from 'rxjs' -import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing' +import { HttpClientTestingModule } from '@angular/common/http/testing' import { ngViewerSelectorPanelMode, ngViewerSelectorPanelOrder } from 'src/services/state/ngViewerState/selectors' import { PANELS } from 'src/services/state/ngViewerState/constants' +import { RegionalFeaturesModule } from '../regionalFeatures' const { TOGGLE_SIDE_PANEL, @@ -81,6 +81,7 @@ describe('> nehubaContainer.component.ts', () => { ReactiveFormsModule, HttpClientModule, CommonModule, + RegionalFeaturesModule, /** * because the change done to pureconstant service, need to intercept http call to avoid crypto error message @@ -117,7 +118,6 @@ describe('> nehubaContainer.component.ts', () => { useValue: importNehubaSpy }, PureContantService, - ], schemas: [ CUSTOM_ELEMENTS_SCHEMA diff --git a/src/ui/nehubaContainer/nehubaContainer.component.ts b/src/ui/nehubaContainer/nehubaContainer.component.ts index 1522f205a305f3fb6be661dfdac6927ee24d6579..a8d205d0455570436c88d3506f5b3dce0abab365 100644 --- a/src/ui/nehubaContainer/nehubaContainer.component.ts +++ b/src/ui/nehubaContainer/nehubaContainer.component.ts @@ -39,6 +39,8 @@ import { ITunableProp } from "./mobileOverlay/mobileOverlay.component"; import {ConnectivityBrowserComponent} from "src/ui/connectivityBrowser/connectivityBrowser.component"; import { viewerStateMouseOverCustomLandmark } from "src/services/state/viewerState/actions"; import { ngViewerSelectorNehubaReady, ngViewerSelectorOctantRemoval, ngViewerSelectorPanelMode, ngViewerSelectorPanelOrder } from "src/services/state/ngViewerState/selectors"; +import { REGION_OF_INTEREST } from "src/util/interfaces"; +import { uiActionHideAllDatasets, uiActionHideDatasetWithId } from "src/services/state/uiState/actions"; const { MESH_LOADING_STATUS } = IDS @@ -144,6 +146,18 @@ const { ]), ], exportAs: 'uiNehubaContainer', + providers: [ + { + provide: REGION_OF_INTEREST, + useFactory: (store: Store<any>) => store.pipe( + select(viewerStateSelectedRegionsSelector), + map(rs => rs[0] || null) + ), + deps: [ + Store + ] + } + ] }) export class NehubaContainer implements OnInit, OnChanges, OnDestroy { @@ -1098,4 +1112,13 @@ export class NehubaContainer implements OnInit, OnChanges, OnDestroy { ngviewer.perspectiveNavigationState.zoomBy(factor) } } + + public clearPreviewingDataset(){ + /** + * clear all preview + */ + this.store.dispatch( + uiActionHideAllDatasets() + ) + } } diff --git a/src/ui/nehubaContainer/nehubaContainer.template.html b/src/ui/nehubaContainer/nehubaContainer.template.html index 16e175cdd67c18abd4b4c1733a8dcc8e0922e1c2..3cc1fdd470830deb315376f68bbf29ceec226d60 100644 --- a/src/ui/nehubaContainer/nehubaContainer.template.html +++ b/src/ui/nehubaContainer/nehubaContainer.template.html @@ -131,31 +131,52 @@ <div class="position-relative d-flex flex-column h-100"> - <!-- region search autocomplete --> + <!-- TODO dataset preview will become deprecated in the future. + Regional feature/data feature will replace it --> - <div class="h-0 w-100 region-text-search-autocomplete-position" - [@openCloseAnchor]="sideNavSwitch.switchState ? 'open' : 'closed'"> - <ng-container *ngTemplateOutlet="autocompleteTmpl"> - </ng-container> + <div class="hidden" + iav-shown-dataset + #iavShownDataset="iavShownDataset"> </div> - + <div class="hidden" iav-shown-previews (emitter)="iavAdditionalLayers$.next($event)" #previews="iavShownPreviews"> </div> - <!-- preview volumes --> - <ng-container *ngIf="previews.iavAdditionalLayers$ | async | filterPreviewByType : [previews.FILETYPES.VOLUMES] as volumePreviews"> - <ng-template [ngIf]="volumePreviews.length > 0" [ngIfElse]="sidenavRegionTmpl"> - <ng-container *ngFor="let vPreview of volumePreviews"> - <ng-container *ngTemplateOutlet="sidenavDsPreviewTmpl; context: vPreview"> - - </ng-container> - </ng-container> + <!-- sidenav datasets --> + <ng-container *ngIf="iavShownDataset.shownDatasetId$ | async as shownDatasetId"> + <ng-template [ngIf]="shownDatasetId.length > 0" [ngIfElse]="sideNavVolumePreview"> + <!-- backbtn --> + <button mat-button class="position-fixed mt-4 z-index-10" + (click)="clearPreviewingDataset()"> + <i class="fas fa-chevron-left"></i> + <span> + Back + </span> + </button> + <!-- single dataset side nav panel --> + <single-dataset-sidenav-view *ngFor="let id of shownDatasetId" + [fullId]="id" + class="side-nav-cover"> + </single-dataset-sidenav-view> </ng-template> </ng-container> + <!-- preview volumes --> + <ng-template #sideNavVolumePreview> + <ng-container *ngIf="previews.iavAdditionalLayers$ | async | filterPreviewByType : [previews.FILETYPES.VOLUMES] as volumePreviews"> + <ng-template [ngIf]="volumePreviews.length > 0" [ngIfElse]="sidenavRegionTmpl"> + <ng-container *ngFor="let vPreview of volumePreviews"> + <ng-container *ngTemplateOutlet="sidenavDsPreviewTmpl; context: vPreview"> + + </ng-container> + </ng-container> + </ng-template> + </ng-container> + </ng-template> + </div> </mat-drawer> @@ -192,6 +213,14 @@ <!-- region sidenav tmpl --> <ng-template #sidenavRegionTmpl> + <!-- region search autocomplete --> + + <div class="h-0 w-100 region-text-search-autocomplete-position" + [@openCloseAnchor]="sideNavSwitch.switchState ? 'open' : 'closed'"> + <ng-container *ngTemplateOutlet="autocompleteTmpl"> + </ng-container> + </div> + <div class="flex-shrink-1 flex-grow-1 d-flex flex-column" [ngClass]="{'region-populated': (selectedRegions$ | async).length > 0 }"> <!-- region detail --> @@ -459,8 +488,12 @@ <!-- regional features--> <ng-template #regionalFeaturesTmpl let-expansionPanel="expansionPanel"> - <regional-features *ngIf="expansionPanel.expanded" [region]="region"> - </regional-features> + + <data-browser + *ngIf="expansionPanel.expanded" + [disableVirtualScroll]="true" + [regions]="[region]"> + </data-browser> </ng-template> <div class="hidden" iav-databrowser-directive diff --git a/src/ui/regionalFeatures/featureExplorer/featureExplorer.component.ts b/src/ui/regionalFeatures/featureExplorer/featureExplorer.component.ts index 8be301cd949c843d6722f1c3e3d201695af3b564..fa75b061364ed658f11a9febcde1fb04275ac4e6 100644 --- a/src/ui/regionalFeatures/featureExplorer/featureExplorer.component.ts +++ b/src/ui/regionalFeatures/featureExplorer/featureExplorer.component.ts @@ -1,9 +1,8 @@ -import { Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges } from "@angular/core"; +import { Component, Input, OnDestroy, OnInit } from "@angular/core"; import { BehaviorSubject, forkJoin, Observable, Subject, Subscription } from "rxjs"; -import { filter, shareReplay, switchMap, switchMapTo, tap } from "rxjs/operators"; +import { shareReplay, switchMap, tap } from "rxjs/operators"; import { IHasId } from "src/util/interfaces"; import { IFeature, RegionalFeaturesService } from "../regionalFeature.service"; -import { RegionFeatureBase } from "../regionFeature.base"; const selectedColor = [ 255, 0, 0 ] @@ -15,7 +14,7 @@ const selectedColor = [ 255, 0, 0 ] ] }) -export class FeatureExplorer extends RegionFeatureBase implements OnChanges, OnInit, OnDestroy{ +export class FeatureExplorer implements OnInit, OnDestroy{ private landmarksLoaded: IHasId[] = [] private onDestroyCb: Function[] = [] @@ -29,19 +28,19 @@ export class FeatureExplorer extends RegionFeatureBase implements OnChanges, OnI this.feature$.next(val) } + @Input() + private region: any + public data$: Observable<IHasId[]> constructor( private regionFeatureService: RegionalFeaturesService, ){ - super(regionFeatureService) /** * once feature stops loading, watch for input feature */ - this.data$ = this.isLoading$.pipe( - filter(v => !v), + this.data$ = this.feature$.pipe( tap(() => this.dataIsLoading = true), - switchMapTo(this.feature$), switchMap((feature: IFeature) => forkJoin( feature.data.map(datum => this.regionFeatureService.getFeatureData(this.region, feature, datum))) ), @@ -95,10 +94,6 @@ export class FeatureExplorer extends RegionFeatureBase implements OnChanges, OnI return this._dataIsLoading } - ngOnChanges(changes: SimpleChanges){ - super.ngOnChanges(changes) - } - ngOnDestroy(){ while(this.onDestroyCb.length > 0) this.onDestroyCb.pop()() while(this.sub.length > 0) this.sub.pop().unsubscribe() diff --git a/src/ui/regionalFeatures/module.ts b/src/ui/regionalFeatures/module.ts index 0849b2fba8e8b19e0f9b8205522bddf4cbb2a48d..60cf760553927deafe08f244a51269ab271f7543 100644 --- a/src/ui/regionalFeatures/module.ts +++ b/src/ui/regionalFeatures/module.ts @@ -1,42 +1,44 @@ 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"; import { FilterRegionalFeaturesByTypePipe } from "./pipes/filterRegionalFeaturesByType.pipe"; +import { FilterRegionFeaturesById } from "./pipes/filterRegionFeaturesById.pipe"; import { FindRegionFEatureById } from "./pipes/findRegionFeatureById.pipe"; import { RegionalFeaturesService } from "./regionalFeature.service"; -import { RegionalFeaturesCmp } from "./regionalFeaturesCmp/regionalFeaturesCmp.component"; +import { RegionGetAllFeaturesDirective } from "./regionGetAllFeatures.directive"; @NgModule({ imports: [ CommonModule, UtilModule, AngularMaterialModule, - DatabrowserModule, ], declarations: [ /** * components */ - RegionalFeaturesCmp, FeatureExplorer, /** * Directives */ RegionalFeatureInteractivity, + RegionGetAllFeaturesDirective, /** * pipes */ FilterRegionalFeaturesByTypePipe, FindRegionFEatureById, + FilterRegionFeaturesById, ], exports: [ - RegionalFeaturesCmp, + FeatureExplorer, + RegionGetAllFeaturesDirective, + FilterRegionFeaturesById, ], providers: [ RegionalFeaturesService, diff --git a/src/ui/regionalFeatures/pipes/filterRegionFeaturesById.pipe.ts b/src/ui/regionalFeatures/pipes/filterRegionFeaturesById.pipe.ts new file mode 100644 index 0000000000000000000000000000000000000000..ed20ef67cfc96dcf3f21b1789cc7cdf82ac2fa01 --- /dev/null +++ b/src/ui/regionalFeatures/pipes/filterRegionFeaturesById.pipe.ts @@ -0,0 +1,14 @@ +import { Pipe, PipeTransform } from "@angular/core"; +import { IFeature } from "../regionalFeature.service"; +import { getIdFromFullId } from 'common/util' +@Pipe({ + name: 'filterRegionFeaturesById', + pure: true +}) + +export class FilterRegionFeaturesById implements PipeTransform{ + public transform(features: IFeature[], id: string){ + const filterId = getIdFromFullId(id) + return features.filter(f => getIdFromFullId(f['@id']) === filterId) + } +} diff --git a/src/ui/regionalFeatures/regionGetAllFeatures.directive.ts b/src/ui/regionalFeatures/regionGetAllFeatures.directive.ts new file mode 100644 index 0000000000000000000000000000000000000000..b78c741f91e1ebea5b04078a4e1d7a11d4ef35b7 --- /dev/null +++ b/src/ui/regionalFeatures/regionGetAllFeatures.directive.ts @@ -0,0 +1,26 @@ +import { Directive, EventEmitter, OnDestroy, OnInit, Output } from "@angular/core"; +import { Subscription } from "rxjs"; +import { RegionFeatureBase } from "./regionFeature.base"; + +@Directive({ + selector: '[region-get-all-features-directive]', + exportAs: 'rfGetAllFeatures' +}) + +export class RegionGetAllFeaturesDirective extends RegionFeatureBase implements OnDestroy, OnInit{ + @Output() + loadingStateChanged: EventEmitter<boolean> = new EventEmitter() + + private subscriptions: Subscription[] = [] + + ngOnInit(){ + this.subscriptions.push( + this.isLoading$.subscribe(val => { + this.loadingStateChanged.emit(val) + }) + ) + } + ngOnDestroy(){ + while (this.subscriptions.length > 0) this.subscriptions.pop().unsubscribe() + } +} diff --git a/src/ui/regionalFeatures/regionalFeaturesCmp/regionalFeaturesCmp.component.ts b/src/ui/regionalFeatures/regionalFeaturesCmp/regionalFeaturesCmp.component.ts deleted file mode 100644 index b5da906cbde16da4b74bddbbc4db278d100d5db9..0000000000000000000000000000000000000000 --- a/src/ui/regionalFeatures/regionalFeaturesCmp/regionalFeaturesCmp.component.ts +++ /dev/null @@ -1,63 +0,0 @@ -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"; - -@Component({ - selector: 'regional-features', - templateUrl: './regionalFeaturesCmp.template.html', - 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 OnDestroy, OnChanges{ - - @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 deleted file mode 100644 index 7f7d5772a8caeb709f16efccb88c912bc6a464ac..0000000000000000000000000000000000000000 --- a/src/ui/regionalFeatures/regionalFeaturesCmp/regionalFeaturesCmp.template.html +++ /dev/null @@ -1,54 +0,0 @@ -<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/ui/regionalFeatures/regionalFeaturesCmp/regionalFeaturesCmp.style.css b/src/ui/regionalFeatures/util.ts similarity index 100% rename from src/ui/regionalFeatures/regionalFeaturesCmp/regionalFeaturesCmp.style.css rename to src/ui/regionalFeatures/util.ts diff --git a/src/util/interfaces.ts b/src/util/interfaces.ts index 4c7613093e61f1043ac8247cfe9ac2aef884b80b..0486e1159e61f8280456a32abd44a76df37f5051 100644 --- a/src/util/interfaces.ts +++ b/src/util/interfaces.ts @@ -3,12 +3,21 @@ */ import { InjectionToken } from "@angular/core" +import { Observable } from "rxjs" export interface IHasId{ ['@id']: string } +export interface IHasFullId{ + ['fullId']: 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') + +export type TRegionOfInterest = { ['fullId']: string } + +export const REGION_OF_INTEREST = new InjectionToken<Observable<TRegionOfInterest>>('RegionOfInterest')