diff --git a/docs/releases/v2.1.0.md b/docs/releases/v2.1.0.md new file mode 100644 index 0000000000000000000000000000000000000000..d3f114ce393da92b5ab3a3cd5f05d7979ced638c --- /dev/null +++ b/docs/releases/v2.1.0.md @@ -0,0 +1,4 @@ +# v2.1.0 + +New features: +- connectivity browsing for JuBrain atlas \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 1d9b5ce428db8408c502f3377fbabafbdd4df581..17acd21e513232f6d3c21b33a7e37ed16e85a786 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -24,7 +24,8 @@ pages: - Keyboard shortcuts: 'advanced/keyboard.md' - URL parsing: 'advanced/url.md' - Release notes: - - v2.0.2 (latest): 'releases/v2.0.2.md' + - v2.1.0: 'releases/v2.1.0.md' + - v2.0.2: 'releases/v2.0.2.md' - v2.0.1: 'releases/v2.0.1.md' - v2.0.0: 'releases/v2.0.0.md' - v0.3.0-beta: 'releases/v0.3.0-beta.md' diff --git a/package.json b/package.json index a0dd920987c7769ec25db9f5d9258fb416948396..4b7091820d2ac94843a987492c5e7ab77635c88c 100644 --- a/package.json +++ b/package.json @@ -79,12 +79,14 @@ "tslint": "^5.16.0", "typescript": "3.2", "uglifyjs-webpack-plugin": "^1.2.5", - "webpack": "^4.25.0", + "webpack": "^4.41.2", "webpack-cli": "^3.3.2", "webpack-closure-compiler": "^2.1.6", "webpack-dev-server": "^3.1.4", "webpack-merge": "^4.1.2", "zone.js": "^0.9.1" }, - "dependencies": {} + "dependencies": { + "hbp-connectivity-component": "0.0.17" + } } diff --git a/src/atlasViewer/atlasViewer.component.ts b/src/atlasViewer/atlasViewer.component.ts index bbffae56c49aff6519c9724364975e0eb63e377e..8932257fbcf4ee364c4cc101fd1e1828c58119de 100644 --- a/src/atlasViewer/atlasViewer.component.ts +++ b/src/atlasViewer/atlasViewer.component.ts @@ -6,16 +6,15 @@ import { OnInit, TemplateRef, AfterViewInit, - Renderer2 + Renderer2, } from "@angular/core"; import { Store, select, ActionsSubject } from "@ngrx/store"; import { isDefined, - FETCHED_SPATIAL_DATA, safeFilter, IavRootStoreInterface } from "../services/stateStore.service"; -import {Observable, Subscription, combineLatest, interval, merge, of } from "rxjs"; +import {Observable, Subscription, combineLatest, interval, merge, of} from "rxjs"; import { map, filter, @@ -36,6 +35,10 @@ import { AGREE_COOKIE, AGREE_KG_TOS, SHOW_KG_TOS, SHOW_BOTTOM_SHEET } from "src/ import { TabsetComponent } from "ngx-bootstrap/tabs"; import { LocalFileService } from "src/services/localFile.service"; import { MatDialog, MatDialogRef, MatSnackBar, MatSnackBarRef, MatBottomSheet, MatBottomSheetRef } from "@angular/material"; +import { + CLOSE_SIDE_PANEL, + OPEN_SIDE_PANEL +} from "src/services/state/uiState.store"; import { isSame } from "src/util/fn"; @@ -60,7 +63,7 @@ const compareFn = (it, item) => it.name === item.name export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit { public compareFn = compareFn - + @ViewChild('cookieAgreementComponent', {read: TemplateRef}) cookieAgreementComponent : TemplateRef<any> @ViewChild('kgToS', {read: TemplateRef}) kgTosComponent: TemplateRef<any> @ViewChild(LayoutMainSide) layoutMainSide: LayoutMainSide @@ -79,7 +82,6 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit { @HostBinding('attr.ismobile') public ismobile: boolean = false - meetsRequirement: boolean = true public sidePanelView$: Observable<string|null> @@ -111,7 +113,7 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit { public unsupportedPreviewIdx: number = 0 public unsupportedPreviews: any[] = UNSUPPORTED_PREVIEW - public sidePanelOpen$: Observable<boolean> + public sidePanelIsOpen$: Observable<boolean> onhoverSegmentsForFixed$: Observable<string[]> @@ -158,10 +160,10 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit { map(state => state.focusedSidePanel) ) - this.sidePanelOpen$ = this.store.pipe( + this.sidePanelIsOpen$ = this.store.pipe( select('uiState'), filter(state => isDefined(state)), - map(state => state.sidePanelOpen) + map(state => state.sidePanelIsOpen) ) this.selectedRegions$ = this.store.pipe( @@ -424,6 +426,10 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit { this.rClContextualMenu.show() } + toggleSideNavMenu(opened) { + this.store.dispatch({type: opened? CLOSE_SIDE_PANEL : OPEN_SIDE_PANEL}) + } + /** * For completeness sake. Root element should never be destroyed. */ diff --git a/src/atlasViewer/atlasViewer.template.html b/src/atlasViewer/atlasViewer.template.html index 1b9074a3760022cf3993fbef5d243dc984de52ec..7c483ae0ea6237611503867dd3cc00f04f9e52a9 100644 --- a/src/atlasViewer/atlasViewer.template.html +++ b/src/atlasViewer/atlasViewer.template.html @@ -43,11 +43,11 @@ <div class="atlas-container" (drag-drop)="localFileService.handleFileDrop($event)"> <ui-nehuba-container iav-mouse-hover #iavMouseHoverEl="iavMouseHover" - [currentOnHoverObs$]="iavMouseHoverEl.currentOnHoverObs$" - [currentOnHover]="iavMouseHoverEl.currentOnHoverObs$ | async" - iav-captureClickListenerDirective - (iav-captureClickListenerDirective-onMousedown)="mouseDownNehuba($event)" - (iav-captureClickListenerDirective-onClick)="mouseUpNehuba($event)"> + [currentOnHoverObs$]="iavMouseHoverEl.currentOnHoverObs$" + [currentOnHover]="iavMouseHoverEl.currentOnHoverObs$ | async" + iav-captureClickListenerDirective + (iav-captureClickListenerDirective-onMousedown)="mouseDownNehuba($event)" + (iav-captureClickListenerDirective-onClick)="mouseUpNehuba($event)"> </ui-nehuba-container> <div class="z-index-10 position-absolute pe-none w-100 h-100"> @@ -57,8 +57,8 @@ class="w-100 h-100 bg-none mat-drawer-content-overflow-visible"> <mat-drawer mode="push" class="col-10 col-sm-10 col-md-4 col-lg-3 col-xl-2 p-2 bg-none box-shadow-none overflow-visible" - [disableClose]="true" [autoFocus]="false" [opened]="true" #sideNavDrawer> - <search-side-nav (dismiss)="sideNavDrawer.close()" (open)="sideNavDrawer.open()" + [disableClose]="true" [autoFocus]="false" [opened]="sidePanelIsOpen$ | async" #sideNavDrawer> + <search-side-nav (dismiss)="toggleSideNavMenu(true)" class="h-100 d-block overflow-visible" #searchSideNav> </search-side-nav> @@ -71,7 +71,7 @@ [matBadge]="!sideNavDrawer.opened && (selectedRegions$ | async)?.length ? (selectedRegions$ | async)?.length : null" [matTooltip]="!sideNavDrawer.opened ? (selectedRegions$ | async)?.length ? ('Explore ' + (selectedRegions$ | async)?.length + ' selected regions.') : 'Explore current view' : null" [ngClass]="{'translate-x-6-n': !sideNavDrawer.opened, 'translate-x-7-n': sideNavDrawer.opened}" - class="pe-all mt-5" (click)="sideNavDrawer.toggle()"> + class="pe-all mt-5" (click)="toggleSideNavMenu(sideNavDrawer.opened)"> <i [ngClass]="{'fa-chevron-left': sideNavDrawer.opened, 'fa-chevron-right': !sideNavDrawer.opened}" class="fas translate-x-3"></i> @@ -141,16 +141,18 @@ <ng-container *ngIf="(onhoverSegmentsForFixed$ | async) as onHoverSegments"> <ng-container *ngFor="let onHoverRegion of onHoverSegments; let first = first"> + <!-- ToDo it should change - we should get information about connectivity existence from API--> <region-menu class="pe-all" [region]="onHoverRegion" - [isSelected]="selectedRegions$ | async | includes : onHoverRegion : compareFn"> + [isSelected]="selectedRegions$ | async | includes : onHoverRegion : compareFn" + [hasConnectivity]="selectedParcellation && selectedParcellation.hasAdditionalViewMode && selectedParcellation.hasAdditionalViewMode.includes('connectivity')" + > </region-menu> </ng-container> </ng-container> </div> - </layout-floating-container> <!-- required for manufacturing plugin templates --> @@ -199,4 +201,4 @@ <!-- logo tmpl --> <ng-template #logoTmpl> <logo-container></logo-container> -</ng-template> +</ng-template> \ No newline at end of file diff --git a/src/main.module.ts b/src/main.module.ts index 352b25fa853b788a8e7e1a1f3a74ebe5a6c25cbb..d1abd75d386310cb6f49c2a0b6766eb66dc98283 100644 --- a/src/main.module.ts +++ b/src/main.module.ts @@ -1,4 +1,4 @@ -import { NgModule } from "@angular/core"; +import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from "@angular/core"; import { ComponentsModule } from "./components/components.module"; import { DragDropModule } from '@angular/cdk/drag-drop' import { UIModule } from "./ui/ui.module"; @@ -55,6 +55,7 @@ import 'src/res/css/version.css' import 'src/theme.scss' import 'src/res/css/extra_styles.css' import { AtlasViewerHistoryUseEffect } from "./atlasViewer/atlasViewer.history.service"; +import {UiStateUseEffect} from "src/services/state/uiState.store"; @NgModule({ imports : [ @@ -78,7 +79,8 @@ import { AtlasViewerHistoryUseEffect } from "./atlasViewer/atlasViewer.history.s ViewerStateUseEffect, NgViewerUseEffect, PluginServiceuseEffect, - AtlasViewerHistoryUseEffect + AtlasViewerHistoryUseEffect, + UiStateUseEffect ]), StoreModule.forRoot({ pluginState, @@ -143,6 +145,9 @@ import { AtlasViewerHistoryUseEffect } from "./atlasViewer/atlasViewer.history.s ], bootstrap : [ AtlasViewer + ], + schemas: [ + CUSTOM_ELEMENTS_SCHEMA ] }) diff --git a/src/main.ts b/src/main.ts index 048f8fae6222ccba576f1547b0a1ab72d1423f64..0e62fe97032b9fbd3d520ba27f36b299fbb9904f 100644 --- a/src/main.ts +++ b/src/main.ts @@ -5,10 +5,13 @@ import 'third_party/testSafari.js' import { platformBrowserDynamic } from '@angular/platform-browser-dynamic' import { MainModule } from './main.module'; +import {defineCustomElements} from 'hbp-connectivity-component/dist/loader' const requireAll = (r:any) => {r.keys().forEach(r)} requireAll(require.context('./res/ext',false, /\.json$/)) requireAll(require.context('./res/images',true,/\.jpg$|\.png$|\.svg$/)) requireAll(require.context(`./plugin_examples`, true)) -platformBrowserDynamic().bootstrapModule(MainModule) \ No newline at end of file +platformBrowserDynamic().bootstrapModule(MainModule) + +defineCustomElements(window) \ No newline at end of file diff --git a/src/res/ext/MNI152.json b/src/res/ext/MNI152.json index 202a4b61ec03a960b382762fd5498b4d41887908..3ce8bc85cc9c1bd30b2f6465c5cf2cb870390f18 100644 --- a/src/res/ext/MNI152.json +++ b/src/res/ext/MNI152.json @@ -10,6 +10,9 @@ "name": "JuBrain Cytoarchitectonic Atlas", "ngId": "jubrain mni152 v18 left", "auxillaryMeshIndices": [ 65535 ], + "hasAdditionalViewMode": [ + "connectivity" + ], "originDatasets":[{ "kgSchema": "minds/core/dataset/v1.0.0", "kgId": "4ac9f0bc-560d-47e0-8916-7b24da9bb0ce" diff --git a/src/res/ext/colin.json b/src/res/ext/colin.json index 30320fa120475c8464f62a60268d5e97db6c5f8d..9094492116566be46ff2869c02b27d01e40198d9 100644 --- a/src/res/ext/colin.json +++ b/src/res/ext/colin.json @@ -12,6 +12,9 @@ "auxillaryMeshIndices": [ 65535 ], + "hasAdditionalViewMode": [ + "connectivity" + ], "originDatasets": [ { "kgSchema": "minds/core/dataset/v1.0.0", diff --git a/src/services/state/uiState.store.ts b/src/services/state/uiState.store.ts index 865f49b38a63ea81d8946c920da355c104e4cf44..8e962f7bd0d2075eee12e39b8a89bc5feaca19a3 100644 --- a/src/services/state/uiState.store.ts +++ b/src/services/state/uiState.store.ts @@ -1,8 +1,11 @@ -import { Action } from '@ngrx/store' -import { TemplateRef } from '@angular/core'; +import {Action, select, Store} from '@ngrx/store' +import {Injectable, TemplateRef} from '@angular/core'; import { LOCAL_STORAGE_CONST, COOKIE_VERSION, KG_TOS_VERSION } from 'src/util/constants' -import { GENERAL_ACTION_TYPES } from '../stateStore.service' +import {GENERAL_ACTION_TYPES, IavRootStoreInterface} from '../stateStore.service' +import {Effect} from "@ngrx/effects"; +import {Observable} from "rxjs"; +import {filter, map, mapTo, scan, startWith} from "rxjs/operators"; export const defaultState: StateInterface = { mouseOverSegments: [], @@ -12,7 +15,9 @@ export const defaultState: StateInterface = { mouseOverUserLandmark: null, focusedSidePanel: null, - sidePanelOpen: false, + sidePanelIsOpen: true, + sidePanelCurrentViewContent: 'Dataset', + sidePanelExploreCurrentViewIsOpen: false, snackbarMessage: null, @@ -59,24 +64,43 @@ export const getStateStore = ({ state = defaultState } = {}) => (prevState:State ...prevState, snackbarMessage: Symbol(snackbarMessage) } - /** - * TODO deprecated - * remove ASAP - */ - case TOGGLE_SIDE_PANEL: + case OPEN_SIDE_PANEL: return { ...prevState, - sidePanelOpen: !prevState.sidePanelOpen + sidePanelIsOpen: true } - case OPEN_SIDE_PANEL: + case CLOSE_SIDE_PANEL: return { ...prevState, - sidePanelOpen: true + sidePanelIsOpen: false } - case CLOSE_SIDE_PANEL: + + case EXPAND_SIDE_PANEL_CURRENT_VIEW: + return { + ...prevState, + sidePanelExploreCurrentViewIsOpen: true + } + case COLLAPSE_SIDE_PANEL_CURRENT_VIEW: + return { + ...prevState, + sidePanelExploreCurrentViewIsOpen: false + } + + case SHOW_SIDE_PANEL_DATASET_LIST: + return { + ...prevState, + sidePanelCurrentViewContent: 'Dataset' + } + + case SHOW_SIDE_PANEL_CONNECTIVITY: return { ...prevState, - sidePanelOpen: false + sidePanelCurrentViewContent: 'Connectivity' + } + case HIDE_SIDE_PANEL_CONNECTIVITY: + return { + ...prevState, + sidePanelCurrentViewContent: 'Dataset' } case AGREE_COOKIE: /** @@ -109,7 +133,7 @@ export const getStateStore = ({ state = defaultState } = {}) => (prevState:State // must export a named function for aot compilation // see https://github.com/angular/angular/issues/15587 -// https://github.com/amcdnl/ngrx-actions/issues/23 +// https://github.com/amcdnl/ngrx-actions/issues/23 // or just google for: // // angular function expressions are not supported in decorators @@ -127,7 +151,9 @@ export interface StateInterface{ } segment: any | null }[] - sidePanelOpen: boolean + sidePanelIsOpen: boolean + sidePanelCurrentViewContent: 'Connectivity' | 'Dataset' | null + sidePanelExploreCurrentViewIsOpen: boolean mouseOverSegment: any | number mouseOverLandmark: any @@ -160,14 +186,57 @@ export interface ActionInterface extends Action{ payload: any } +@Injectable({ + providedIn:'root' +}) + +export class UiStateUseEffect{ + + private numRegionSelectedWithHistory$: Observable<any[]> + + @Effect() + public sidePanelOpen$: Observable<any> + + @Effect() + public viewCurrentOpen$: Observable<any> + + constructor(store$: Store<IavRootStoreInterface>) { + this.numRegionSelectedWithHistory$ = store$.pipe( + select('viewerState'), + select('regionsSelected'), + map(arr => arr.length), + startWith(0), + scan((acc, curr) => [curr, ...acc], []) + ) + + this.sidePanelOpen$ = this.numRegionSelectedWithHistory$.pipe( + filter(([curr, prev]) => prev === 0 && curr > 0), + mapTo({ + type: OPEN_SIDE_PANEL + }) + ) + + this.viewCurrentOpen$ = this.numRegionSelectedWithHistory$.pipe( + filter(([curr, prev]) => prev === 0 && curr > 0), + mapTo({ + type: EXPAND_SIDE_PANEL_CURRENT_VIEW + }) + ) + } +} + export const MOUSE_OVER_SEGMENT = `MOUSE_OVER_SEGMENT` export const MOUSE_OVER_SEGMENTS = `MOUSE_OVER_SEGMENTS` export const MOUSE_OVER_LANDMARK = `MOUSE_OVER_LANDMARK` export const MOUSEOVER_USER_LANDMARK = `MOUSEOVER_USER_LANDMARK` -export const TOGGLE_SIDE_PANEL = 'TOGGLE_SIDE_PANEL' export const CLOSE_SIDE_PANEL = `CLOSE_SIDE_PANEL` export const OPEN_SIDE_PANEL = `OPEN_SIDE_PANEL` +export const SHOW_SIDE_PANEL_DATASET_LIST = `SHOW_SIDE_PANEL_DATASET_LIST` +export const SHOW_SIDE_PANEL_CONNECTIVITY = `SHOW_SIDE_PANEL_CONNECTIVITY` +export const HIDE_SIDE_PANEL_CONNECTIVITY = `HIDE_SIDE_PANEL_CONNECTIVITY` +export const COLLAPSE_SIDE_PANEL_CURRENT_VIEW = `COLLAPSE_SIDE_PANEL_CURRENT_VIEW` +export const EXPAND_SIDE_PANEL_CURRENT_VIEW = `EXPAND_SIDE_PANEL_CURRENT_VIEW` export const AGREE_COOKIE = `AGREE_COOKIE` export const AGREE_KG_TOS = `AGREE_KG_TOS` diff --git a/src/services/state/viewerState.store.ts b/src/services/state/viewerState.store.ts index c30a83fac4e55d9627f53b7b5f9415fd65254d07..dc19ff5870977ce4d65c90158b3c5b462f27550a 100644 --- a/src/services/state/viewerState.store.ts +++ b/src/services/state/viewerState.store.ts @@ -23,6 +23,7 @@ export interface StateInterface{ dedicatedView : string[] loadedNgLayers: NgLayerInterface[] + connectivityRegion: string | null } export interface ActionInterface extends Action{ @@ -43,6 +44,8 @@ export interface ActionInterface extends Action{ navigation? : any payload: any + + connectivityRegion?: string } export const defaultState:StateInterface = { @@ -55,7 +58,8 @@ export const defaultState:StateInterface = { dedicatedView: null, navigation: null, parcellationSelected: null, - templateSelected: null + templateSelected: null, + connectivityRegion: '' } export const getStateStore = ({ state = defaultState } = {}) => (prevState:Partial<StateInterface> = state, action:ActionInterface) => { @@ -176,6 +180,16 @@ export const getStateStore = ({ state = defaultState } = {}) => (prevState:Parti case GENERAL_ACTION_TYPES.APPLY_STATE: const { viewerState } = (action as any).state return viewerState + case SET_CONNECTIVITY_REGION: + return { + ...prevState, + connectivityRegion: action.connectivityRegion + } + case CLEAR_CONNECTIVITY_REGION: + return { + ...prevState, + connectivityRegion: '' + } default : return prevState } @@ -215,6 +229,8 @@ export const USER_LANDMARKS = `USER_LANDMARKS` export const ADD_TO_REGIONS_SELECTION_WITH_IDS = `ADD_TO_REGIONS_SELECTION_WITH_IDS` export const NEHUBA_LAYER_CHANGED = `NEHUBA_LAYER_CHANGED` +export const SET_CONNECTIVITY_REGION = `SET_CONNECTIVITY_REGION` +export const CLEAR_CONNECTIVITY_REGION = `CLEAR_CONNECTIVITY_REGION` @Injectable({ providedIn: 'root' diff --git a/src/services/stateStore.service.ts b/src/services/stateStore.service.ts index 9ce8ea1bec76f1f0972888942b56f12c11e74196..15a2c0016ae8e96b13d63a001878818c2cf10503 100644 --- a/src/services/stateStore.service.ts +++ b/src/services/stateStore.service.ts @@ -1,18 +1,18 @@ import { filter } from 'rxjs/operators'; -import { +import { StateInterface as PluginStateInterface, stateStore as pluginState, defaultState as pluginDefaultState, getStateStore as pluginGetStateStore } from './state/pluginState.store' -import { +import { StateInterface as ViewerConfigStateInterface, stateStore as viewerConfigState, defaultState as viewerConfigDefaultState, getStateStore as getViewerConfigStateStore } from './state/viewerConfig.store' -import { +import { StateInterface as NgViewerStateInterface, ActionInterface as NgViewerActionInterface, stateStore as ngViewerState, @@ -60,7 +60,7 @@ export { userConfigState, USER_CONFIG_ACTION_TYPES} export { ADD_NG_LAYER, FORCE_SHOW_SEGMENT, HIDE_NG_LAYER, REMOVE_NG_LAYER, SHOW_NG_LAYER } from './state/ngViewerState.store' export { CHANGE_NAVIGATION, DESELECT_LANDMARKS, FETCHED_TEMPLATE, NEWVIEWER, SELECT_LANDMARKS, SELECT_PARCELLATION, SELECT_REGIONS, USER_LANDMARKS } from './state/viewerState.store' export { DataEntry, ParcellationRegion, FETCHED_DATAENTRIES, FETCHED_SPATIAL_DATA, Landmark, OtherLandmarkGeometry, PlaneLandmarkGeometry, PointLandmarkGeometry, Property, Publication, ReferenceSpace, File, FileSupplementData } from './state/dataStore.store' -export { CLOSE_SIDE_PANEL, MOUSE_OVER_LANDMARK, MOUSE_OVER_SEGMENT, OPEN_SIDE_PANEL, TOGGLE_SIDE_PANEL } from './state/uiState.store' +export { CLOSE_SIDE_PANEL, MOUSE_OVER_LANDMARK, MOUSE_OVER_SEGMENT, OPEN_SIDE_PANEL, SHOW_SIDE_PANEL_CONNECTIVITY, HIDE_SIDE_PANEL_CONNECTIVITY, COLLAPSE_SIDE_PANEL_CURRENT_VIEW, EXPAND_SIDE_PANEL_CURRENT_VIEW } from './state/uiState.store' export { UserConfigStateUseEffect } from './state/userConfigState.store' export const GENERAL_ACTION_TYPES = { diff --git a/src/ui/connectivityBrowser/connectivityBrowser.component.ts b/src/ui/connectivityBrowser/connectivityBrowser.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..761a9fc0139d6c1fef7580020ef307705dff9786 --- /dev/null +++ b/src/ui/connectivityBrowser/connectivityBrowser.component.ts @@ -0,0 +1,166 @@ +import { + AfterViewInit, ChangeDetectorRef, + Component, + ElementRef, + OnDestroy, + ViewChild +} from "@angular/core"; +import {AtlasViewerConstantsServices} from "src/atlasViewer/atlasViewer.constantService.service"; +import {fromEvent, Observable, Subscription} from "rxjs"; +import {select, Store} from "@ngrx/store"; +import {HIDE_SIDE_PANEL_CONNECTIVITY, isDefined, safeFilter} from "src/services/stateStore.service"; +import {distinctUntilChanged, filter, map} from "rxjs/operators"; +import {CLEAR_CONNECTIVITY_REGION, SET_CONNECTIVITY_REGION} from "src/services/state/viewerState.store"; + +@Component({ + selector: 'connectivity-browser', + templateUrl: './connectivityBrowser.template.html', +}) +export class ConnectivityBrowserComponent implements AfterViewInit, OnDestroy { + + public region: string + private connectedAreas = [] + + + private connectivityRegion$: Observable<any> + private selectedParcellation$: Observable<any> + private subscriptions: Subscription[] = [] + public expandMenuIndex = -1 + public allRegions = [] + public defaultColorMap: Map<string, Map<number, {red: number, green: number, blue: number}>> + public parcellationHasConnectivityData = true + private areaHemisphere: string + + math = Math + + @ViewChild('connectivityComponent', {read: ElementRef}) connectivityComponentElement: ElementRef + + constructor(private constantService: AtlasViewerConstantsServices, private store$: Store<any> , private changeDetectionRef : ChangeDetectorRef + ){ + this.selectedParcellation$ = this.store$.pipe( + select('viewerState'), + filter(state=>isDefined(state)&&isDefined(state.parcellationSelected)), + map(state=>state.parcellationSelected), + distinctUntilChanged(), + ) + + this.connectivityRegion$ = this.store$.pipe( + select('viewerState'), + safeFilter('connectivityRegion'), + map(state => state.connectivityRegion) + ) + + } + + ngAfterViewInit(): void { + this.subscriptions.push( + this.selectedParcellation$.subscribe(parcellation => { + if (parcellation && parcellation.hasAdditionalViewMode && parcellation.hasAdditionalViewMode.includes('connectivity')) { + this.parcellationHasConnectivityData = true + if (parcellation.regions && parcellation.regions.length) { + this.allRegions = [] + this.getAllRegionsFromParcellation(parcellation.regions) + if (this.defaultColorMap) { + this.addNewColorMap() + } + } + } else { + this.parcellationHasConnectivityData = false + } + }), + this.connectivityRegion$.subscribe(cr => { + this.region = cr + this.areaHemisphere = cr.includes('left hemisphere')? ' - left hemisphere' : ' - right hemisphere' + this.changeDetectionRef.detectChanges() + }) + ) + + this.subscriptions.push( + fromEvent(this.connectivityComponentElement.nativeElement, 'connectivityDataReceived', { capture: true }) + .subscribe((e: CustomEvent) => { + this.connectedAreas = e.detail + if (this.connectedAreas.length > 0) this.addNewColorMap() + }), + fromEvent(this.connectivityComponentElement.nativeElement, 'collapsedMenuChanged', { capture: true }) + .subscribe((e: CustomEvent) => { + this.expandMenuIndex = e.detail + }), + + ) + } + + ngOnDestroy(): void { + this.setDefaultMap() + this.subscriptions.forEach(s => s.unsubscribe()) + } + + updateConnevtivityRegion(regionName) { + this.store$.dispatch({ + type: SET_CONNECTIVITY_REGION, + connectivityRegion: regionName + }) + } + + public closeConnectivityView() { + this.setDefaultMap() + + this.store$.dispatch({ + type: HIDE_SIDE_PANEL_CONNECTIVITY, + }) + this.store$.dispatch({ + type: CLEAR_CONNECTIVITY_REGION + }) + } + + setDefaultMap() { + this.allRegions.forEach(r => { + if (r && r.ngId && r.rgb) { + this.defaultColorMap.get(r.ngId).set(r.labelIndex, {red: r.rgb[0], green: r.rgb[1], blue: r.rgb[2]}) + } + }) + getWindow().interactiveViewer.viewerHandle.applyLayersColourMap(this.defaultColorMap) + } + + addNewColorMap() { + + this.defaultColorMap = new Map(getWindow().interactiveViewer.viewerHandle.getLayersSegmentColourMap()) + + const existingMap: Map<string, Map<number, {red: number, green: number, blue: number}>> = (getWindow().interactiveViewer.viewerHandle.getLayersSegmentColourMap()) + + const map = new Map(existingMap) + + this.allRegions.forEach(r => { + + if (r.ngId) { + map.get(r.ngId).set(r.labelIndex, {red: 255, green: 255, blue: 255}) + } + }) + + this.connectedAreas.forEach(area => { + const areaAsRegion = this.allRegions + .filter(r => r.name === area.name + this.areaHemisphere) + .map(r => r) + + if (areaAsRegion && areaAsRegion.length && areaAsRegion[0].ngId) + // @ts-ignore + map.get(areaAsRegion[0].ngId).set(areaAsRegion[0].labelIndex, {red: area.color.r, green: area.color.g, blue: area.color.b}) + }) + getWindow().interactiveViewer.viewerHandle.applyLayersColourMap(map) + } + + getAllRegionsFromParcellation = (regions) => { + for (let i = 0; i<regions.length; i ++) { + if (regions[i].children && regions[i].children.length) { + this.getAllRegionsFromParcellation(regions[i].children) + } else { + this.allRegions.push(regions[i]) + } + } + } + + +} + +function getWindow (): any { + return window +} \ No newline at end of file diff --git a/src/ui/connectivityBrowser/connectivityBrowser.template.html b/src/ui/connectivityBrowser/connectivityBrowser.template.html new file mode 100644 index 0000000000000000000000000000000000000000..cbde664d53a0824ca2dcc029559d12c9f7f5854e --- /dev/null +++ b/src/ui/connectivityBrowser/connectivityBrowser.template.html @@ -0,0 +1,77 @@ +<div class="w-100 h-100 overflow-auto d-block d-flex flex-column pb-2" #connectivityComponent *ngIf="parcellationHasConnectivityData; else noConnectivity"> + <!--ToDo set show-description="true" when data will available--> + <hbp-connectivity-matrix-row + [region]="region" + theme="dark" + loadurl="https://connectivityquery-connectivity.apps-dev.hbp.eu/connectivity" + show-export="true" + show-source="true" + show-title="true" + show-toolbar="true"> + <div slot="header" class="w-100 d-flex justify-content-end mt-3"><span + class="cursorPointer" (click)="closeConnectivityView()">X</span></div> + + <div slot="connectedRegionMenu"> + + <div class="d-flex flex-column" *ngIf="expandMenuIndex >= 0"> + <mat-divider></mat-divider> + <span class="mt-2 mr-2 ml-2"> + <small>Region: {{connectedAreas[expandMenuIndex].name}}</small> + <br> + <small>Number of Connection: {{connectedAreas[expandMenuIndex].numberOfConnections}}</small> + <br> + <small>Log(123) = {{math.log10(connectedAreas[expandMenuIndex].numberOfConnections)}}</small> + </span> + <div class="d-flex align-items-center justify-content-around"> + <small class="d-flex flex-column align-items-center w-100"> + <!--ToDo Implement function when Hemisphere information will be available --> + <!-- *ngIf="(selectedRegions$ | async) as selectedRegions"--> + <!-- (click)="toggleRegionWithId(region.ngId, region.labelIndex, regionIsSelected(selectedRegions, region.ngId, region.labelIndex))">--> + + <button mat-icon-button class="border"> + <!-- <i class="fas fa-hand-pointer mt-n1"></i>--> + <span class="fa-stack fa-1x "> + <i class="fas fa-hand-pointer fa-stack-1x"></i> + <!--ToDo Implement function when Hemisphere information will be available --> + <!-- <i class="fas fa-slash fa-stack-1x fa-inverse"--> + <!-- *ngIf="regionIsSelected(selectedRegions, region.ngId, region.labelIndex)"></i>--> + </span> + </button> + <!-- <span [innerText]="regionIsSelected(selectedRegions, region.ngId, region.labelIndex)? 'Deselect' : 'Select'"></span>--> + <span>Select</span> + </small> + + <small class="d-flex flex-column align-items-center w-100"> + <button mat-icon-button class="border"> + <i class="fas fa-crosshairs mt-n1"></i> + </button> + Navigate + </small> + + <small class="d-flex flex-column align-items-center w-100"> + <button mat-icon-button class="border" (click)="updateConnevtivityRegion(connectedAreas[expandMenuIndex].name)"> + <i class="fab fa-connectdevelop mt-n1"></i> + </button> + Connectivity + </small> + </div> + </div> + + + </div> + </hbp-connectivity-matrix-row> +</div> + +<ng-template #noConnectivity> + <mat-card class="p-2 w-100 h-100 overflow-auto d-block"> + <div class="w-100 d-flex justify-content-end mt-3"><span + class="cursorPointer" (click)="closeConnectivityView()">X</span></div> + <h5> + Connectivity Matrix Browser + </h5> + <div> + No Connectivity for selected Parcellation! + </div> + + </mat-card> +</ng-template> \ No newline at end of file diff --git a/src/ui/parcellationRegion/region.base.ts b/src/ui/parcellationRegion/region.base.ts index 9e2163f8c03d14e41924c90c0cd198eafd57a550..db802685088ca59b985f41ba0eb7423fc2efbcac 100644 --- a/src/ui/parcellationRegion/region.base.ts +++ b/src/ui/parcellationRegion/region.base.ts @@ -1,7 +1,12 @@ import { Store } from "@ngrx/store"; -import { Input } from "@angular/core"; +import {EventEmitter, Input, Output} from "@angular/core"; import { VIEWERSTATE_CONTROLLER_ACTION_TYPES } from "../viewerStateController/viewerState.base"; -import { IavRootStoreInterface } from "src/services/stateStore.service"; +import { + EXPAND_SIDE_PANEL_CURRENT_VIEW, + IavRootStoreInterface, OPEN_SIDE_PANEL, + SHOW_SIDE_PANEL_CONNECTIVITY +} from "src/services/stateStore.service"; +import {SET_CONNECTIVITY_REGION} from "src/services/state/viewerState.store"; export class RegionBase{ @@ -11,6 +16,9 @@ export class RegionBase{ @Input() public isSelected: boolean = false + @Input() hasConnectivity: boolean + @Output() exploreConnectivity: EventEmitter<string> = new EventEmitter() + constructor( private store$: Store<IavRootStoreInterface>, ){ @@ -32,4 +40,16 @@ export class RegionBase{ payload: { region } }) } + + showConnectivity(regionName) { + //ToDo trigger side panel opening with effect + this.store$.dispatch({type: OPEN_SIDE_PANEL}) + this.store$.dispatch({type: EXPAND_SIDE_PANEL_CURRENT_VIEW}) + this.store$.dispatch({type: SHOW_SIDE_PANEL_CONNECTIVITY}) + + this.store$.dispatch({ + type: SET_CONNECTIVITY_REGION, + connectivityRegion: regionName + }) + } } \ No newline at end of file diff --git a/src/ui/parcellationRegion/regionMenu/regionMenu.template.html b/src/ui/parcellationRegion/regionMenu/regionMenu.template.html index f0716128fb236519d9e025a0f8d099928bb85fa1..965a0af09638ac098a63e39109c4e90d801406d3 100644 --- a/src/ui/parcellationRegion/regionMenu/regionMenu.template.html +++ b/src/ui/parcellationRegion/regionMenu/regionMenu.template.html @@ -5,7 +5,7 @@ <mat-card-content> {{ region.description }} </mat-card-content> - <mat-card-actions class="d-flex flex-row"> + <mat-card-actions class="d-flex flex-row flex-wrap"> <button mat-button (click)="toggleRegionSelected()" [color]="isSelected ? 'primary': 'basic'"> @@ -20,5 +20,11 @@ Navigate </span> </button> + <button *ngIf="hasConnectivity" mat-button (click)="showConnectivity(region.name)"> + <i class="fab fa-connectdevelop"></i> + <span> + Connectivity + </span> + </button> </mat-card-actions> </mat-card> \ No newline at end of file diff --git a/src/ui/searchSideNav/searchSideNav.component.ts b/src/ui/searchSideNav/searchSideNav.component.ts index fa91bf1fa3490e0b7b7de93adb2e6db0af2dcaa5..949da2a546f735ac2951d4ad26d1965bb57c710d 100644 --- a/src/ui/searchSideNav/searchSideNav.component.ts +++ b/src/ui/searchSideNav/searchSideNav.component.ts @@ -1,12 +1,17 @@ -import { Component, Output, EventEmitter, OnInit, OnDestroy, ViewChild, TemplateRef } from "@angular/core"; +import { Component, Output, EventEmitter, OnDestroy, ViewChild, TemplateRef } from "@angular/core"; import { MatDialogRef, MatDialog, MatSnackBar } from "@angular/material"; import { NgLayerInterface } from "src/atlasViewer/atlasViewer.component"; import { LayerBrowser } from "../layerbrowser/layerbrowser.component"; -import { Observable, Subscription } from "rxjs"; +import {Observable, Subscription} from "rxjs"; import { Store, select } from "@ngrx/store"; import { map, startWith, scan, filter, mapTo } from "rxjs/operators"; import { trackRegionBy } from '../viewerStateController/regionHierachy/regionHierarchy.component' import { AtlasViewerConstantsServices } from "src/atlasViewer/atlasViewer.constantService.service"; +import { + CLOSE_SIDE_PANEL, + COLLAPSE_SIDE_PANEL_CURRENT_VIEW, + EXPAND_SIDE_PANEL_CURRENT_VIEW, +} from "src/services/state/uiState.store"; import { SELECT_REGIONS, IavRootStoreInterface } from "src/services/stateStore.service"; @Component({ @@ -17,27 +22,28 @@ import { SELECT_REGIONS, IavRootStoreInterface } from "src/services/stateStore.s ] }) -export class SearchSideNav implements OnInit, OnDestroy { - public showDataset: boolean = false +export class SearchSideNav implements OnDestroy { public availableDatasets: number = 0 private subscriptions: Subscription[] = [] private layerBrowserDialogRef: MatDialogRef<any> @Output() dismiss: EventEmitter<any> = new EventEmitter() - @Output() open: EventEmitter<any> = new EventEmitter() @ViewChild('layerBrowserTmpl', {read: TemplateRef}) layerBrowserTmpl: TemplateRef<any> - public autoOpenSideNav$: Observable<any> + public autoOpenSideNavDataset$: Observable<any> + + public sidePanelExploreCurrentViewIsOpen$: Observable<any> + public sidePanelCurrentViewContent: Observable<any> constructor( public dialog: MatDialog, private store$: Store<IavRootStoreInterface>, private snackBar: MatSnackBar, - private constantService: AtlasViewerConstantsServices + private constantService: AtlasViewerConstantsServices, ){ - this.autoOpenSideNav$ = this.store$.pipe( + this.autoOpenSideNavDataset$ = this.store$.pipe( select('viewerState'), select('regionsSelected'), map(arr => arr.length), @@ -46,17 +52,32 @@ export class SearchSideNav implements OnInit, OnDestroy { filter(([curr, prev]) => prev === 0 && curr > 0), mapTo(true) ) - } - ngOnInit(){ - this.subscriptions.push( - this.autoOpenSideNav$.subscribe(() => { - this.open.emit(true) - this.showDataset = true - }) + this.sidePanelExploreCurrentViewIsOpen$ = this.store$.pipe( + select('uiState'), + select("sidePanelExploreCurrentViewIsOpen") + ) + + this.sidePanelCurrentViewContent = this.store$.pipe( + select('uiState'), + select("sidePanelCurrentViewContent") ) } + collapseSidePanelCurrentView() { + this.store$.dispatch({ + type: COLLAPSE_SIDE_PANEL_CURRENT_VIEW, + }) + } + + expandSidePanelCurrentView() { + this.store$.dispatch({ + type: EXPAND_SIDE_PANEL_CURRENT_VIEW, + }) + } + + + ngOnDestroy(){ while(this.subscriptions.length > 0) { this.subscriptions.pop().unsubscribe() @@ -70,9 +91,11 @@ export class SearchSideNav implements OnInit, OnDestroy { return } if (this.layerBrowserDialogRef) return - - this.dismiss.emit(true) - + + this.store$.dispatch({ + type: CLOSE_SIDE_PANEL, + }) + const dialogToOpen = this.layerBrowserTmpl || LayerBrowser this.layerBrowserDialogRef = this.dialog.open(dialogToOpen, { hasBackdrop: false, diff --git a/src/ui/searchSideNav/searchSideNav.style.css b/src/ui/searchSideNav/searchSideNav.style.css index 373ba5c0daa9043482912ff058a7e05df980a96d..4abec449498d3a8586f333d9003d001a100ec6f4 100644 --- a/src/ui/searchSideNav/searchSideNav.style.css +++ b/src/ui/searchSideNav/searchSideNav.style.css @@ -17,3 +17,7 @@ margin-left:-1.5rem; width: calc(100% + 3rem); } + +connectivity-browser { + max-height: calc(100% - 220px); +} \ No newline at end of file diff --git a/src/ui/searchSideNav/searchSideNav.template.html b/src/ui/searchSideNav/searchSideNav.template.html index 4f1402c684e8cfc058f76b54e85d1d2cdc3ed076..8331fba31b1645fcc3f8bb2a3935509165db40a2 100644 --- a/src/ui/searchSideNav/searchSideNav.template.html +++ b/src/ui/searchSideNav/searchSideNav.template.html @@ -12,8 +12,8 @@ <!-- footer content --> <div class="d-flex flex-row justify-content-center" card-footer> <button mat-stroked-button - *ngIf="!showDataset" - (click)="showDataset = true" + *ngIf="!(sidePanelExploreCurrentViewIsOpen$ | async)" + (click)="expandSidePanelCurrentView()" class="m-1 flex-grow-1 overflow-hidden" > <i class="fas fa-chevron-down"></i> <ng-container *ngIf="viewerStateController.regionsSelected$ | async as regionsSelected"> @@ -23,9 +23,12 @@ </div> </viewer-state-controller> - <ng-container *ngIf="showDataset"> + <ng-container *ngIf="(sidePanelExploreCurrentViewIsOpen$ | async)" [ngSwitch]="(sidePanelCurrentViewContent | async)"> + <connectivity-browser class="pe-all flex-grow-5 flex-shrink-1" + *ngSwitchCase = "'Connectivity'"> - <data-browser + </connectivity-browser> + <data-browser *ngSwitchCase = "'Dataset'" class="pe-all flex-grow-5 flex-shrink-1" [template]="viewerStateController.templateSelected$ | async" [parcellation]="viewerStateController.parcellationSelected$ | async" @@ -39,12 +42,12 @@ <mat-divider class="position-relative mt-2 mb-2 mat-divider-full-width"></mat-divider> </ng-container> - + <!-- footer content --> <div class="d-flex flex-row justify-content-center" card-footer> <button mat-stroked-button class="m-1" - (click)="showDataset = false" > + (click)="collapseSidePanelCurrentView()" > <i class="fas fa-chevron-up"></i> </button> </div> diff --git a/src/ui/ui.module.ts b/src/ui/ui.module.ts index e902fbd55d692a24c0aa60db91724efabe0e5f1b..8c5f780858399753504ef6ebba24b903fb1e28b1 100644 --- a/src/ui/ui.module.ts +++ b/src/ui/ui.module.ts @@ -1,4 +1,4 @@ -import { NgModule } from "@angular/core"; +import {CUSTOM_ELEMENTS_SCHEMA, NgModule} from "@angular/core"; import { ComponentsModule } from "src/components/components.module"; import { NehubaViewerUnit } from "./nehubaContainer/nehubaViewer/nehubaViewer.component"; @@ -77,6 +77,7 @@ import {FixedMouseContextualContainerDirective} from "src/util/directives/FixedM import { RegionMenuComponent } from 'src/ui/parcellationRegion/regionMenu/regionMenu.component' import { SimpleRegionComponent } from "./parcellationRegion/regionSimple/regionSimple.component"; import { RegionListSimpleViewComponent } from "./parcellationRegion/regionListSimpleView/regionListSimpleView.component"; +import {ConnectivityBrowserComponent} from "src/ui/connectivityBrowser/connectivityBrowser.component"; @NgModule({ imports : [ @@ -125,6 +126,7 @@ import { RegionListSimpleViewComponent } from "./parcellationRegion/regionListSi RegionsListView, TakeScreenshotComponent, RegionMenuComponent, + ConnectivityBrowserComponent, SimpleRegionComponent, RegionListSimpleViewComponent, @@ -191,6 +193,9 @@ import { RegionListSimpleViewComponent } from "./parcellationRegion/regionListSi ViewerStateMini, RegionMenuComponent, FixedMouseContextualContainerDirective + ], + schemas: [ + CUSTOM_ELEMENTS_SCHEMA ] }) diff --git a/src/util/directives/FixedMouseContextualContainerDirective.directive.ts b/src/util/directives/FixedMouseContextualContainerDirective.directive.ts index 81736ed6d79d46f9b45971e2f3d1fe13507e1205..458d01069897e2654e32b6b51d966711380e6ff1 100644 --- a/src/util/directives/FixedMouseContextualContainerDirective.directive.ts +++ b/src/util/directives/FixedMouseContextualContainerDirective.directive.ts @@ -35,11 +35,10 @@ export class FixedMouseContextualContainerDirective { } this.transform = `translate(${this.mousePos.map(v => v.toString() + 'px').join(', ')})` - this.styleDisplay = 'block' - this.isShown = true - this.onShow.emit() - }) + this.styleDisplay = 'block' + this.isShown = true + this.onShow.emit() } public hide(){