diff --git a/package.json b/package.json index f8dbd7d7edb68224bb627c46b88a387f4c461459..7a308a21776af00a0f18f4f60594abd9c917e363 100644 --- a/package.json +++ b/package.json @@ -85,5 +85,7 @@ "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 13248f4a0d39349ba8f58b45cbc309246b1c9788..49cb9ca2e258628fb3f952a8a0ebd039c2ca468b 100644 --- a/src/atlasViewer/atlasViewer.component.ts +++ b/src/atlasViewer/atlasViewer.component.ts @@ -1,8 +1,36 @@ -import { Component, HostBinding, ViewChild, ViewContainerRef, OnDestroy, OnInit, TemplateRef, AfterViewInit, Renderer2 } from "@angular/core"; +import { + Component, + HostBinding, + ViewChild, + OnDestroy, + OnInit, + TemplateRef, + AfterViewInit, + Renderer2, + ElementRef +} from "@angular/core"; import { Store, select, ActionsSubject } from "@ngrx/store"; -import { ViewerStateInterface, isDefined, FETCHED_SPATIAL_DATA, UPDATE_SPATIAL_DATA, safeFilter } from "../services/stateStore.service"; -import { Observable, Subscription, combineLatest, interval, merge, of } from "rxjs"; -import { map, filter, distinctUntilChanged, delay, concatMap, withLatestFrom } from "rxjs/operators"; +import { + ViewerStateInterface, + isDefined, + FETCHED_SPATIAL_DATA, + UPDATE_SPATIAL_DATA, + safeFilter, + CHANGE_NAVIGATION, + generateLabelIndexId, + UIStateInterface, + SHOW_SIDE_PANEL_CONNECTIVITY, + EXPAND_SIDE_PANEL_CURRENT_VIEW +} from "../services/stateStore.service"; +import {Observable, Subscription, combineLatest, interval, merge, of, Observer} from "rxjs"; +import { + map, + filter, + distinctUntilChanged, + delay, + concatMap, + withLatestFrom, +} from "rxjs/operators"; import { AtlasViewerDataService } from "./atlasViewer.dataService.service"; import { WidgetServices } from "./widgetUnit/widgetService.service"; import { LayoutMainSide } from "../layouts/mainside/mainside.component"; @@ -17,6 +45,12 @@ 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 {SearchSideNav} from "src/ui/searchSideNav/searchSideNav.component"; +import {CaptureClickListenerDirective} from "src/util/directives/captureClickListener.directive"; +import { + CLOSE_SIDE_PANEL, + OPEN_SIDE_PANEL +} from "src/services/state/uiState.store"; /** * TODO @@ -44,9 +78,12 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit { @ViewChild(NehubaContainer) nehubaContainer: NehubaContainer @ViewChild(FixedMouseContextualContainerDirective) rClContextualMenu: FixedMouseContextualContainerDirective + @ViewChild(CaptureClickListenerDirective) captureClickListenerDirective: CaptureClickListenerDirective @ViewChild('mobileMenuTabs') mobileMenuTabs: TabsetComponent + @ViewChild('searchSideNav') searchSideNav: SearchSideNav + /** * required for styling of all child components */ @@ -55,7 +92,6 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit { @HostBinding('attr.ismobile') public ismobile: boolean = false - meetsRequirement: boolean = true public sidePanelView$: Observable<string|null> @@ -71,8 +107,7 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit { public dedicatedView$: Observable<string | null> public onhoverSegments$: Observable<string[]> - public onhoverSegmentsForFixed$: Observable<string[]> - + public onhoverLandmark$ : Observable<{landmarkName: string, datasets: any} | null> private subscriptions: Subscription[] = [] @@ -90,10 +125,12 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit { public sidePanelOpen$: Observable<boolean> - public toggleMessage = this.constantsService.toggleMessage + + onhoverSegmentsForFixed$: Observable<string[]> + regionToolsMenuVisible = false constructor( - private store: Store<ViewerStateInterface>, + private store: Store<ViewerStateInterface | UIStateInterface>, public dataService: AtlasViewerDataService, private widgetServices: WidgetServices, private constantsService: AtlasViewerConstantsServices, @@ -354,7 +391,6 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit { } ngAfterViewInit() { - /** * preload the main bundle after atlas viewer has been loaded. * This should speed up where user first navigate to the home page, @@ -397,9 +433,38 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit { }) this.onhoverSegmentsForFixed$ = this.rClContextualMenu.onShow.pipe( - withLatestFrom(this.onhoverSegments$), - map(([_flag, onhoverSegments]) => onhoverSegments || []) + withLatestFrom(this.onhoverSegments$), + map(([_flag, onhoverSegments]) => onhoverSegments || []) ) + + } + + mouseDownNehuba(event) { + this.regionToolsMenuVisible = false + this.rClContextualMenu.hide() + } + + mouseUpNehuba(event) { + // if (this.mouseUpLeftPosition === event.pageX && this.mouseUpTopPosition === event.pageY) {} + this.regionToolsMenuVisible = true + if (!this.rClContextualMenu) return + this.rClContextualMenu.mousePos = [ + event.clientX, + event.clientY + ] + this.rClContextualMenu.show() + } + + toggleSideNavMenu(opened) { + this.store.dispatch({type: opened? CLOSE_SIDE_PANEL : OPEN_SIDE_PANEL}) + } + + showConnectivity(event) { + // setTimeout(() => ) + this.toggleSideNavMenu(false) + this.store.dispatch({type: EXPAND_SIDE_PANEL_CURRENT_VIEW}) + this.store.dispatch({type: SHOW_SIDE_PANEL_CONNECTIVITY}) + this.searchSideNav.connectivityActive.next(event) } /** @@ -458,20 +523,6 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit { }) } - nehubaClickHandler(event:MouseEvent){ - if (!this.rClContextualMenu) return - this.rClContextualMenu.mousePos = [ - event.clientX, - event.clientY - ] - this.rClContextualMenu.show() - } - - openLandmarkUrl(dataset) { - this.rClContextualMenu.hide() - window.open(dataset.externalLink, "_blank") - } - @HostBinding('attr.version') public _version : string = VERSION } diff --git a/src/atlasViewer/atlasViewer.template.html b/src/atlasViewer/atlasViewer.template.html index 20a455ef34dff06334cf514684e342d04a098451..7637a7d4c44ef8498f19110c014a0d1da6c958d8 100644 --- a/src/atlasViewer/atlasViewer.template.html +++ b/src/atlasViewer/atlasViewer.template.html @@ -41,7 +41,9 @@ <ui-nehuba-container iav-mouse-hover #iavMouseHoverEl="iavMouseHover" [currentOnHoverObs$]="iavMouseHoverEl.currentOnHoverObs$" [currentOnHover]="iavMouseHoverEl.currentOnHoverObs$ | async" - (contextmenu)="$event.stopPropagation(); $event.preventDefault();"> + iav-captureClickListenerDirective + (mouseDownEmitter)="mouseDownNehuba($event)" + (mapClicked)="mouseUpNehuba($event)"> </ui-nehuba-container> <div class="z-index-10 position-absolute pe-none w-100 h-100"> @@ -51,8 +53,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]="sidePanelOpen$ | async" #sideNavDrawer> + <search-side-nav (dismiss)="toggleSideNavMenu(true)" class="h-100 d-block overflow-visible" #searchSideNav> </search-side-nav> @@ -65,7 +67,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> @@ -102,53 +104,6 @@ </ng-container> </div> - <!-- TODO document fixedMouseContextualContainerDirective , then deprecate this --> - <!-- TODO move to nehuba overlay container --> - <panel-component class="shadow" fixedMouseContextualContainerDirective #rClContextMenu> - <div heading> - <h5 class="pe-all p-2 m-0"> - What's here? - </h5> - </div> - <div body> - - <div *ngIf="(onhoverSegmentsForFixed$ | async)?.length > 0 || (selectedRegions$ | async)?.length > 0" - class="p-2"> - Search for data relating to: - </div> - - <div *ngFor="let onhoverSegmentFixed of (onhoverSegmentsForFixed$ | async)" - (click)="searchRegion([onhoverSegmentFixed])" - class="ws-no-wrap text-left pe-all btn btn-sm btn-secondary btn-block mt-0" data-toggle="tooltip" - data-placement="top" [title]="onhoverSegmentFixed.name"> - <small class="text-semi-transparent">(hovering)</small> {{ onhoverSegmentFixed.name }} - </div> - - <div *ngIf="(selectedRegions$ | async)?.length > 0 && (selectedRegions$ | async); let selectedRegions" - (click)="searchRegion(selectedRegions)" - class="ws-no-wrap text-left pe-all mt-0 btn btn-sm btn-secondary btn-block"> - <ng-container *ngIf="selectedRegions.length > 1"> - <small class="text-semi-transparent">(selected)</small> {{ selectedRegions.length }} selected regions - </ng-container> - <ng-container *ngIf="selectedRegions.length === 1"> - <small class="text-semi-transparent">(selected)</small> {{ selectedRegions[0].name }} - </ng-container> - </div> - - <div class="p-2 text-muted" - *ngIf="(onhoverSegmentsForFixed$ | async)?.length === 0 && (selectedRegions$ | async)?.length === 0 && (onhoverLandmarksForFixed$ | async)?.length === 0"> - Right click on a parcellation region or select parcellation regions to search KG for associated datasets. - </div> - - <ng-template #noRegionSelected> - <div (click)="searchRegion()" class="ws-no-wrap text-left pe-all mt-0 btn btn-sm btn-secondary btn-block"> - No region selected. Search KG for all datasets in this template space. - </div> - </ng-template> - - </div> - </panel-component> - <div floatingMouseContextualContainerDirective> <div class="d-inline-block" iav-mouse-hover #iavMouseHoverConetxtualBlock="iavMouseHover" contextualBlock> @@ -174,6 +129,26 @@ <!-- TODO Potentially implementing plugin contextual info --> </div> + <panel-component class="shadow p-0 m-0" fixedMouseContextualContainerDirective [style.width]="selectedParcellation && selectedParcellation.name.includes('JuBrain Cytoarchitectonic Atlas')? '18rem' : '15rem'"> + <div body class="pe-all" *ngIf="(onhoverSegmentsForFixed$ | async) as onHoverSegments"> + <mat-card *ngIf="onHoverSegments.length > 0 && regionToolsMenuVisible" class="d-flex flex-column p-0"> + <div *ngFor="let onHoverRegion of onHoverSegments; let first = first" + class="border-dark rounded"> + <mat-divider *ngIf="!first"></mat-divider> + + <region-menu + [selectedRegions$]="selectedRegions$" + [region]="onHoverRegion" + [hasConnectivity]="selectedParcellation && selectedParcellation.name.includes('JuBrain Cytoarchitectonic Atlas')" + (exploreConnectivity) = "showConnectivity($event);"> + </region-menu> + + </div> + </mat-card> + </div> + + </panel-component> + </layout-floating-container> <!-- required for manufacturing plugin templates --> @@ -222,4 +197,4 @@ <!-- logo tmpl --> <ng-template #logoTmpl> <logo-container></logo-container> -</ng-template> \ No newline at end of file +</ng-template> diff --git a/src/main.module.ts b/src/main.module.ts index 6dd663fae585edfc1048ea2c5c8d5dc5beece6f9..772d6b75b57655b51ee70f90d3c4343537de7666 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"; @@ -30,7 +30,6 @@ import { FloatingContainerDirective } from "./util/directives/floatingContainer. import { PluginFactoryDirective } from "./util/directives/pluginFactory.directive"; import { FloatingMouseContextualContainerDirective } from "./util/directives/floatingMouseContextualContainer.directive"; import { AuthService } from "./services/auth.service"; -import { FixedMouseContextualContainerDirective } from "./util/directives/FixedMouseContextualContainerDirective.directive"; import { DatabrowserService } from "./ui/databrowserModule/databrowser.service"; import { TransformOnhoverSegmentPipe } from "./atlasViewer/onhoverSegment.pipe"; import {HttpClientModule} from "@angular/common/http"; @@ -54,6 +53,8 @@ import 'hammerjs' import 'src/res/css/version.css' import 'src/theme.scss' import 'src/res/css/extra_styles.css' +import {CaptureClickListenerDirective} from "src/util/directives/captureClickListener.directive"; + @NgModule({ imports : [ @@ -107,8 +108,8 @@ import 'src/res/css/extra_styles.css' FloatingContainerDirective, PluginFactoryDirective, FloatingMouseContextualContainerDirective, - FixedMouseContextualContainerDirective, DragDropDirective, + CaptureClickListenerDirective, /* pipes */ GetNamesPipe, @@ -143,6 +144,9 @@ import 'src/res/css/extra_styles.css' ], bootstrap : [ AtlasViewer + ], + schemas: [ + CUSTOM_ELEMENTS_SCHEMA ] }) diff --git a/src/main.ts b/src/main.ts index 479dbb34491927cd7ed00952948534c70cdc698f..831701b2a5093f578acf33da526701c76011cee9 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/)) 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/css/extra_styles.css b/src/res/css/extra_styles.css index 2781c6cad4bac7c850d42f3e07cecb7d374d9f66..774f1adebd5fe25aee189b0b48c594e0234ff8af 100644 --- a/src/res/css/extra_styles.css +++ b/src/res/css/extra_styles.css @@ -679,4 +679,12 @@ body::after padding: 0!important; overflow: hidden; margin-top: 0.25rem; -} \ No newline at end of file +} + +.linear-gradient-fade:before { + content:''; + width:100%; + height:100%; + position:absolute; + background:linear-gradient(transparent 50px, #424242); +} diff --git a/src/services/state/uiState.store.ts b/src/services/state/uiState.store.ts index 396ebfe8ea69d2ccce217f87f6ace0de7536421a..e31af30dd3b68114374449487c61475592c3dbcc 100644 --- a/src/services/state/uiState.store.ts +++ b/src/services/state/uiState.store.ts @@ -11,7 +11,9 @@ const defaultState : UIStateInterface = { mouseOverUserLandmark: null, focusedSidePanel: null, - sidePanelOpen: false, + sidePanelOpen: true, + sidePanelManualCollapsibleView: '', + sidePanelCurrentViewOpened: false, snackbarMessage: null, @@ -58,15 +60,6 @@ export function uiState(state:UIStateInterface = defaultState,action:UIAction){ ...state, snackbarMessage: Symbol(snackbarMessage) } - /** - * TODO deprecated - * remove ASAP - */ - case TOGGLE_SIDE_PANEL: - return { - ...state, - sidePanelOpen: !state.sidePanelOpen - } case OPEN_SIDE_PANEL: return { ...state, @@ -77,6 +70,29 @@ export function uiState(state:UIStateInterface = defaultState,action:UIAction){ ...state, sidePanelOpen: false } + + case EXPAND_SIDE_PANEL_CURRENT_VIEW: + return { + ...state, + sidePanelCurrentViewOpened: true + } + case COLLAPSE_SIDE_PANEL_CURRENT_VIEW: + return { + ...state, + sidePanelCurrentViewOpened: false + } + + case SHOW_SIDE_PANEL_CONNECTIVITY: + return { + ...state, + sidePanelManualCollapsibleView: 'Connectivity' + } + + case HIDE_SIDE_PANEL_CONNECTIVITY: + return { + ...state, + sidePanelManualCollapsibleView: '' + } case AGREE_COOKIE: /** * TODO replace with server side logic @@ -114,6 +130,8 @@ export interface UIStateInterface{ segment: any | null }[] sidePanelOpen: boolean + sidePanelManualCollapsibleView: 'Connectivity' | '' | null + sidePanelCurrentViewOpened: boolean mouseOverSegment: any | number mouseOverLandmark: any @@ -151,9 +169,12 @@ 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_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/stateStore.service.ts b/src/services/stateStore.service.ts index 842499c18bcb4d3083a9494fab4f71ca85ca96ad..92950ea166434227b84fd62151ae5f24ada4461f 100644 --- a/src/services/stateStore.service.ts +++ b/src/services/stateStore.service.ts @@ -5,7 +5,7 @@ export { pluginState } from './state/pluginState.store' export { NgViewerAction, NgViewerStateInterface, ngViewerState, ADD_NG_LAYER, FORCE_SHOW_SEGMENT, HIDE_NG_LAYER, REMOVE_NG_LAYER, SHOW_NG_LAYER } from './state/ngViewerState.store' export { CHANGE_NAVIGATION, AtlasAction, DESELECT_LANDMARKS, FETCHED_TEMPLATE, NEWVIEWER, SELECT_LANDMARKS, SELECT_PARCELLATION, SELECT_REGIONS, USER_LANDMARKS, ViewerStateInterface, viewerState } from './state/viewerState.store' export { DataEntry, ParcellationRegion, DataStateInterface, DatasetAction, FETCHED_DATAENTRIES, FETCHED_SPATIAL_DATA, Landmark, OtherLandmarkGeometry, PlaneLandmarkGeometry, PointLandmarkGeometry, Property, Publication, ReferenceSpace, dataStore, File, FileSupplementData } from './state/dataStore.store' -export { CLOSE_SIDE_PANEL, MOUSE_OVER_LANDMARK, MOUSE_OVER_SEGMENT, OPEN_SIDE_PANEL, TOGGLE_SIDE_PANEL, UIAction, UIStateInterface, uiState } 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, UIAction, UIStateInterface, uiState } from './state/uiState.store' export { SPATIAL_GOTO_PAGE, SpatialDataEntries, SpatialDataStateInterface, UPDATE_SPATIAL_DATA, spatialSearchState } from './state/spatialSearchState.store' export { userConfigState, UserConfigStateUseEffect, USER_CONFIG_ACTION_TYPES } from './state/userConfigState.store' diff --git a/src/ui/connectivityBrowser/connectivityBrowser.component.ts b/src/ui/connectivityBrowser/connectivityBrowser.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..0f85701a01c24f7325f274ad89afcec9e2691af9 --- /dev/null +++ b/src/ui/connectivityBrowser/connectivityBrowser.component.ts @@ -0,0 +1,131 @@ +import { + AfterViewInit, + Component, + ElementRef, + EventEmitter, + Inject, + Input, + OnDestroy, + Output, + ViewChild +} from "@angular/core"; +import {AtlasViewerConstantsServices} from "src/atlasViewer/atlasViewer.constantService.service"; +import {Observable, Subject, Subscription} from "rxjs"; +import {select, Store} from "@ngrx/store"; +import {HIDE_SIDE_PANEL_CONNECTIVITY, safeFilter} from "src/services/stateStore.service"; +import {distinctUntilChanged, map} from "rxjs/operators"; + +@Component({ + selector: 'connectivity-browser', + templateUrl: './connectivityBrowser.template.html', +}) +export class ConnectivityBrowserComponent implements AfterViewInit, OnDestroy { + + @Input() region: string = '' + private connectedAreas = [] + + + private selectedParcellation$: Observable<any> + private subscriptions: Subscription[] = [] + private selectedParcellation: any + public collapseMenu = -1 + public allRegions = [] + public defaultColorMap + + math = Math + + @ViewChild('connectivityComponent', {read: ElementRef}) connectivityComponentElement: ElementRef + + constructor(private constantService: AtlasViewerConstantsServices, private store$: Store<any> ){ + this.selectedParcellation$ = this.store$.pipe( + select('viewerState'), + safeFilter('parcellationSelected'), + map(state=>state.parcellationSelected), + distinctUntilChanged(), + ) + } + + ngAfterViewInit(): void { + this.subscriptions.push( + this.selectedParcellation$.subscribe(parcellation => { + this.selectedParcellation = parcellation + this.getAllRegionsFromParcellation(parcellation.regions) + }) + ) + + this.connectivityComponentElement.nativeElement.addEventListener('connectivityDataReceived', e => { + this.connectedAreas = e.detail + if (this.connectedAreas.length > 0) this.saveAndDisableExistingColorTemplate() + }) + + this.connectivityComponentElement.nativeElement.addEventListener('collapsedMenuChanged', e => { + this.collapseMenu = e.detail + }) + } + + ngOnDestroy(): void { + this.subscriptions.forEach(s => s.unsubscribe()) + } + + public closeConnectivityView() { + + this.allRegions.forEach(r => { + if (r && r.ngId && r.rgb) { + // @ts-ignore + 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) + }) + + this.store$.dispatch({ + type: HIDE_SIDE_PANEL_CONNECTIVITY, + }) + } + + saveAndDisableExistingColorTemplate() { + + const hemisphere = this.region.includes('left hemisphere')? ' - left hemisphere' : ' - right hemisphere' + + this.defaultColorMap = new Map(getWindow().interactiveViewer.viewerHandle.getLayersSegmentColourMap()) + + const existingMap = (getWindow().interactiveViewer.viewerHandle.getLayersSegmentColourMap()) + + const map = new Map(existingMap) + + this.allRegions.forEach(r => { + + if (r.ngId) { + // @ts-ignore + 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 + hemisphere) + .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..31dba5728019e20c2ae408b7060e1cc7c1bebcfd --- /dev/null +++ b/src/ui/connectivityBrowser/connectivityBrowser.template.html @@ -0,0 +1,63 @@ +<div class="w-100 h-100 overflow-auto d-block d-flex flex-column pb-2" #connectivityComponent> + <hbp-connectivity-matrix-row + [region]="region" + theme="dark" + loadurl="https://connectivityquery-connectivity.apps-dev.hbp.eu/connectivity" + show-description="true" + 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="collapseMenu >= 0"> + <mat-divider></mat-divider> + <span class="mt-2 mr-2 ml-2"> + <small>Region: {{connectedAreas[collapseMenu].name}}</small> + <br> + <small>Number of Connection: {{connectedAreas[collapseMenu].numberOfConnections}}</small> + <br> + <small>Log(123) = {{math.log10(connectedAreas[collapseMenu].numberOfConnections)}}</small> + </span> + <div class="d-flex align-items-center justify-content-around"> + <small class="d-flex flex-column align-items-center w-100"> + + <!-- *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> + <!-- <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)="region = connectedAreas[collapseMenu].name"> + <!--(click)="changeConnectivityRegion('connection.name')"--> + <i class="fab fa-connectdevelop mt-n1"></i> + </button> + Connectivity + </small> + </div> + </div> + + + </div> + </hbp-connectivity-matrix-row> +</div> \ No newline at end of file diff --git a/src/ui/regionToolsMenu/regionMenu.component.ts b/src/ui/regionToolsMenu/regionMenu.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..86d8dade3297f78df6c619b1005eb6f4d01b6c0b --- /dev/null +++ b/src/ui/regionToolsMenu/regionMenu.component.ts @@ -0,0 +1,58 @@ +import {AfterViewInit, Component, EventEmitter, Input, Output, ViewChild} from "@angular/core"; +import {Observable} from "rxjs"; +import {map, withLatestFrom} from "rxjs/operators"; +import {FixedMouseContextualContainerDirective} from "src/util/directives/FixedMouseContextualContainerDirective.directive"; +import {VIEWER_STATE_ACTION_TYPES} from "src/services/effect/effect"; +import {CHANGE_NAVIGATION, generateLabelIndexId} from "src/services/stateStore.service"; +import {ADD_TO_REGIONS_SELECTION_WITH_IDS} from "src/services/state/viewerState.store"; +import {Store} from "@ngrx/store"; +import {SearchSideNav} from "src/ui/searchSideNav/searchSideNav.component"; + +@Component({ + selector: 'region-menu', + templateUrl: './regionMenu.template.html', + styleUrls: ['./regionMenu.style.css'] +}) +export class RegionMenuComponent { + @Input() selectedRegions$: any + @Input() region: any + @Input() hasConnectivity: boolean + + @Output() exploreConnectivity: EventEmitter<string> = new EventEmitter() + + @ViewChild('searchSideNav') searchSideNav: SearchSideNav + + regionToolsMenuVisible = true + collapsedRegionDescription = false + + constructor(private store$: Store<any>) {} + + + toggleRegionWithId(ngId, labelIndex, removeFlag: any){ + if (removeFlag) { + this.store$.dispatch({ + type: VIEWER_STATE_ACTION_TYPES.DESELECT_REGIONS_WITH_ID, + deselecRegionIds: [generateLabelIndexId({ ngId, labelIndex })] + }) + } else { + this.store$.dispatch({ + type: ADD_TO_REGIONS_SELECTION_WITH_IDS, + selectRegionIds : [generateLabelIndexId({ ngId, labelIndex })] + }) + } + } + + regionIsSelected(selectedRegions, ngId, labelIndex) { + return selectedRegions.map(sr => generateLabelIndexId({ ngId: sr.ngId, labelIndex: sr.labelIndex })).includes(generateLabelIndexId({ ngId, labelIndex })) + } + + navigateTo(position){ + this.store$.dispatch({ + type: CHANGE_NAVIGATION, + navigation: { + position, + animation: {} + } + }) + } +} \ No newline at end of file diff --git a/src/ui/regionToolsMenu/regionMenu.style.css b/src/ui/regionToolsMenu/regionMenu.style.css new file mode 100644 index 0000000000000000000000000000000000000000..fc202344466f16355a9dd257690a17f7e56c151c --- /dev/null +++ b/src/ui/regionToolsMenu/regionMenu.style.css @@ -0,0 +1,19 @@ +.regionDescriptionTextClass{ + max-height:100px; + transition: max-height 0.15s ease-out; +} +.regionDescriptionTextOpened { + max-height: 310px; + transition: max-height 0.25s ease-in; +} + +[fixedMouseContextualContainerDirective] +{ + width: 15rem; +} + +[fixedMouseContextualContainerDirective] div[body] +{ + overflow: hidden; +} + diff --git a/src/ui/regionToolsMenu/regionMenu.template.html b/src/ui/regionToolsMenu/regionMenu.template.html new file mode 100644 index 0000000000000000000000000000000000000000..0d7afd988ce28b6e91b0426986b677b5e0797aae --- /dev/null +++ b/src/ui/regionToolsMenu/regionMenu.template.html @@ -0,0 +1,53 @@ +<div class="text-nowrap text-truncate mt-2 ml-2 mr-2 overflow-hidden" [matTooltip]="region.name" + matTooltipShowDelay="1000">{{region.name}}</div> +<!-- ToDo implement it with Descriptions--> + <div > <!--*ngIf="!region.description && !region.description.length"--> + <div class="row m-2 position-relative overflow-hidden" + [class.regionDescriptionTextOpened] = "collapsedRegionDescription" + [class.overflow-y-auto] = "collapsedRegionDescription" + [class.regionDescriptionTextClass] = "!collapsedRegionDescription" + [class.linear-gradient-fade] = "(+regionDescriptionText.scrollHeight > +regionDescriptionText.clientHeight) && !collapsedRegionDescription" + #regionDescriptionText> + {{region.description}} + </div> + <div (click)="collapsedRegionDescription = true" + *ngIf="+regionDescriptionText.scrollHeight > +regionDescriptionText.clientHeight && !collapsedRegionDescription" + class="w-100 d-flex justify-content-center align-items-center mt-n2 cursorPointer"><i + class="fas fa-angle-down m-1"></i> Read More + </div> + <div (click)="collapsedRegionDescription = false" + *ngIf="collapsedRegionDescription" + class="w-100 d-flex justify-content-center align-items-center mt-n2 cursorPointer"><i + class="fas fa-angle-up m-1"></i> Collapse + </div> + </div> +<div class="d-flex align-items-center justify-content-between"> + <button mat-button + style="font-size: 12px" + class="pt-0 pb-0 br-1 pl-1" + [ngClass]="!hasConnectivity && 'w-100'" + *ngIf="(selectedRegions$ | async) as selectedRegions" + (click)="toggleRegionWithId(region.ngId, region.labelIndex, regionIsSelected(selectedRegions, region.ngId, region.labelIndex)); regionToolsMenuVisible = false; collapsedRegionDescription = false"> + <span class="fa-stack fa-1x"> + <i class="fas fa-hand-pointer fa-stack-1x"></i> + <i class="fas fa-slash fa-stack-1x fa-inverse" + *ngIf="regionIsSelected(selectedRegions, region.ngId, region.labelIndex)"></i> + </span> + <span [innerText]="regionIsSelected(selectedRegions, region.ngId, region.labelIndex)? 'Deselect' : 'Select'"></span> + </button> + <button mat-button + style="font-size: 12px" + class="pt-0 pb-0 br-1 pl-1" + [ngClass]="!hasConnectivity && 'w-100'" + (click)="navigateTo(region.position); regionToolsMenuVisible = false; collapsedRegionDescription = false"> + <i class="fas fa-crosshairs"></i> Navigate + </button> + + <button mat-button + *ngIf="hasConnectivity" + style="font-size: 12px" + class="pt-0 pb-0 br-1 pl-1 mr-1" + (click)="exploreConnectivity.emit(region.name); regionToolsMenuVisible = false; collapsedRegionDescription = false"> + <i class="fab fa-connectdevelop"></i> Connectivity + </button> +</div> \ No newline at end of file diff --git a/src/ui/searchSideNav/searchSideNav.component.ts b/src/ui/searchSideNav/searchSideNav.component.ts index 4d12f4cc8665955fadc4e23026cd14048ffc8a62..a35562c89612cea5d3618447bd1e1c9fa01f3e2d 100644 --- a/src/ui/searchSideNav/searchSideNav.component.ts +++ b/src/ui/searchSideNav/searchSideNav.component.ts @@ -2,12 +2,19 @@ import { Component, Output, EventEmitter, OnInit, OnDestroy, ViewChild, Template 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, Subject, Subscription} from "rxjs"; import { Store, select } from "@ngrx/store"; import { map, startWith, scan, filter, mapTo } from "rxjs/operators"; import { VIEWERSTATE_CONTROLLER_ACTION_TYPES } from "../viewerStateController/viewerState.base"; 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, + OPEN_SIDE_PANEL +} from "src/services/state/uiState.store"; +import {ConnectivityBrowserComponent} from "src/ui/connectivityBrowser/connectivityBrowser.component"; @Component({ selector: 'search-side-nav', @@ -18,26 +25,31 @@ import { AtlasViewerConstantsServices } from "src/atlasViewer/atlasViewer.consta }) export class SearchSideNav implements OnInit, OnDestroy { - public showDataset: boolean = false public availableDatasets: number = 0 + public connectivityActive = new Subject<string>() + public connectivityRegion = '' + 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> + @ViewChild('connectivityBrowser') connectivityBrowser: ConnectivityBrowserComponent + + + public autoOpenSideNavDataset$: Observable<any> - public autoOpenSideNav$: Observable<any> + sidebarMenuState$: Observable<any> constructor( public dialog: MatDialog, private store$: Store<any>, 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 +58,49 @@ export class SearchSideNav implements OnInit, OnDestroy { filter(([curr, prev]) => prev === 0 && curr > 0), mapTo(true) ) + + this.sidebarMenuState$ = this.store$.pipe( + select('uiState'), + map(state => { + return { + sidePanelOpen: state.sidePanelOpen, + sidePanelCurrentViewOpened: state.sidePanelCurrentViewOpened, + sidePanelManualCollapsibleView: state.sidePanelManualCollapsibleView + } + }) + ) } ngOnInit(){ this.subscriptions.push( - this.autoOpenSideNav$.subscribe(() => { - this.open.emit(true) - this.showDataset = true + this.connectivityActive.asObservable().subscribe(r => { + // this.connectivityService.getConnectivityByRegion(r) + this.connectivityRegion = r + }), + + this.autoOpenSideNavDataset$.subscribe(() => { + this.store$.dispatch({ + type: OPEN_SIDE_PANEL, + }) + this.expandSidePanelCurrentView() }) ) } + 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 +114,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.template.html b/src/ui/searchSideNav/searchSideNav.template.html index 1230a72321a950f562ca9a3ea778a930b0316ae7..1c512d7d4b7fbf558a327f8e0d42db926c5314c0 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="!(sidebarMenuState$ | async)?.sidePanelCurrentViewOpened" + (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,14 @@ </div> </viewer-state-controller> - <ng-container *ngIf="showDataset"> + <ng-container *ngIf="(sidebarMenuState$ | async) as sideBarMenu"> + <connectivity-browser class="pe-all flex-grow-5 flex-shrink-1" style="max-height: calc(100% - 220px)" + [region]="connectivityRegion" + #connectivityBrowser + *ngIf="sideBarMenu.sidePanelCurrentViewOpened && sideBarMenu.sidePanelManualCollapsibleView === 'Connectivity'"> - <data-browser + </connectivity-browser> + <data-browser *ngIf="sideBarMenu.sidePanelCurrentViewOpened && !sideBarMenu.sidePanelManualCollapsibleView" class="pe-all flex-grow-5 flex-shrink-1" [template]="viewerStateController.templateSelected$ | async" [parcellation]="viewerStateController.parcellationSelected$ | async" @@ -39,12 +44,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 9b8f237729d1bcafe310faab30160358639d16b2..d33fa5fa406eb544ed9a3bdab2caa93ecea12b62 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"; @@ -72,6 +72,9 @@ import { CurrentlySelectedRegions } from './viewerStateController/regionsListVie import { RegionTextSearchAutocomplete } from "./viewerStateController/regionSearch/regionSearch.component"; import { RegionsListView } from "./viewerStateController/regionsListView/simpleRegionsListView/regionListView.component"; import {TakeScreenshotComponent} from "src/ui/takeScreenshot/takeScreenshot.component"; +import {RegionMenuComponent} from "src/ui/regionToolsMenu/regionMenu.component"; +import {FixedMouseContextualContainerDirective} from "src/util/directives/FixedMouseContextualContainerDirective.directive"; +import {ConnectivityBrowserComponent} from "src/ui/connectivityBrowser/connectivityBrowser.component"; @NgModule({ imports : [ @@ -119,6 +122,8 @@ import {TakeScreenshotComponent} from "src/ui/takeScreenshot/takeScreenshot.comp RegionTextSearchAutocomplete, RegionsListView, TakeScreenshotComponent, + RegionMenuComponent, + ConnectivityBrowserComponent, /* pipes */ GroupDatasetByRegion, @@ -146,10 +151,12 @@ import {TakeScreenshotComponent} from "src/ui/takeScreenshot/takeScreenshot.comp HumanReadableFileSizePipe, ReorderPanelIndexPipe, + /* directive */ DownloadDirective, TouchSideClass, ElementOutClickDirective, + FixedMouseContextualContainerDirective ], entryComponents : [ @@ -179,6 +186,11 @@ import {TakeScreenshotComponent} from "src/ui/takeScreenshot/takeScreenshot.comp ElementOutClickDirective, SearchSideNav, ViewerStateMini, + RegionMenuComponent, + FixedMouseContextualContainerDirective + ], + schemas: [ + CUSTOM_ELEMENTS_SCHEMA ] }) diff --git a/src/util/directives/FixedMouseContextualContainerDirective.directive.ts b/src/util/directives/FixedMouseContextualContainerDirective.directive.ts index bf53ed23584df960562ea335dc983a961e102aa0..458d01069897e2654e32b6b51d966711380e6ff1 100644 --- a/src/util/directives/FixedMouseContextualContainerDirective.directive.ts +++ b/src/util/directives/FixedMouseContextualContainerDirective.directive.ts @@ -25,10 +25,17 @@ export class FixedMouseContextualContainerDirective { } public show(){ - if ((window.innerWidth - this.mousePos[0]) < 220) { - this.mousePos[0] = window.innerWidth-220 - } - this.transform = `translate(${this.mousePos.map(v => v.toString() + 'px').join(', ')})` + setTimeout(() => { + if (window.innerHeight - this.mousePos[1] < this.el.nativeElement.clientHeight) { + this.mousePos[1] = window.innerHeight - this.el.nativeElement.clientHeight + } + + if ((window.innerWidth - this.mousePos[0]) < this.el.nativeElement.clientWidth) { + this.mousePos[0] = window.innerWidth-this.el.nativeElement.clientWidth + } + + this.transform = `translate(${this.mousePos.map(v => v.toString() + 'px').join(', ')})` + }) this.styleDisplay = 'block' this.isShown = true this.onShow.emit() @@ -47,14 +54,4 @@ export class FixedMouseContextualContainerDirective { @HostBinding('style.transform') public transform = `translate(${this.mousePos.map(v => v.toString() + 'px').join(', ')})` - @HostListener('document:click', ['$event']) - documentClick(event: MouseEvent){ - if (event.button !== 2) { - if (this.styleDisplay === 'none') - return - if (this.el.nativeElement.contains(event.target)) - return - this.hide() - } - } } \ No newline at end of file diff --git a/src/util/directives/captureClickListener.directive.ts b/src/util/directives/captureClickListener.directive.ts new file mode 100644 index 0000000000000000000000000000000000000000..bd92d61fe6cf9944e5142078b964c4e31044cd55 --- /dev/null +++ b/src/util/directives/captureClickListener.directive.ts @@ -0,0 +1,52 @@ +import {Directive, ElementRef, EventEmitter, Input, OnDestroy, OnInit, Output} from "@angular/core"; +import {Observable, Observer, Subscription} from "rxjs"; +import {switchMapTo, takeUntil} from "rxjs/operators"; +import {RegionMenuComponent} from "src/ui/regionToolsMenu/regionMenu.component"; + +@Directive({ + selector: '[iav-captureClickListenerDirective]' +}) + +export class CaptureClickListenerDirective implements OnInit, OnDestroy { + + private subscriptions: Subscription[] = [] + @Output() mapClicked: EventEmitter<any> = new EventEmitter() + @Output() mouseDownEmitter: EventEmitter<any> = new EventEmitter() + + + constructor(private el: ElementRef){} + + ngOnInit(): void { + + // Listen click Events + const mouseDownObs$ = new Observable((observer: Observer<any>) => { + this.el.nativeElement.addEventListener('mousedown', event => observer.next({eventName: 'mousedown', event}), true) + }) as Observable<{eventName: string, event: MouseEvent}> + const mouseMoveObs$ = new Observable((observer: Observer<any>) => { + this.el.nativeElement.addEventListener('mousemove', event => observer.next({eventName: 'mousemove', event}), true) + }) as Observable<{eventName: string, event: MouseEvent}> + const mouseUpObs$ = new Observable((observer: Observer<any>) => { + this.el.nativeElement.addEventListener('mouseup', event => observer.next({eventName: 'mouseup', event}), true) + }) as Observable<{eventName: string, event: MouseEvent}> + + this.subscriptions.push( + mouseDownObs$.subscribe(e => { + this.mouseDownEmitter.emit(e.event) + }), + mouseDownObs$.pipe( + switchMapTo( + mouseUpObs$.pipe( + takeUntil(mouseMoveObs$) + ) + ) + ).subscribe(e => { + this.mapClicked.emit(e.event) + }) + ) + } + + ngOnDestroy(): void { + this.subscriptions.forEach(s=> s.unsubscribe()) + } + +} \ No newline at end of file