From d08f16dc9d91e6d0273cb784724980427101aa86 Mon Sep 17 00:00:00 2001 From: fsdavid <daviti1@mail.com> Date: Tue, 5 Nov 2019 17:16:31 +0100 Subject: [PATCH] First Version of connectivity Add Left Click Region Tool Menu Add tools menu items Add select and navigate buttons to toolbar menu First Version for region tool menu Corrections of region tools menu Changes region tools menu Corrections of names in region menu make connectivity service variables public make connectivity service public connectivity matrix with npm web component (plus open sidebar state management (fixed context menu overflow and CaptureclickListener subscriptions corrections after rebase --- package.json | 4 +- src/atlasViewer/atlasViewer.component.ts | 103 ++++++++++---- src/atlasViewer/atlasViewer.template.html | 79 ++++------- src/main.module.ts | 10 +- src/main.ts | 5 +- src/res/css/extra_styles.css | 10 +- src/services/state/uiState.store.ts | 43 ++++-- src/services/stateStore.service.ts | 2 +- .../connectivityBrowser.component.ts | 131 ++++++++++++++++++ .../connectivityBrowser.template.html | 63 +++++++++ .../regionToolsMenu/regionMenu.component.ts | 58 ++++++++ src/ui/regionToolsMenu/regionMenu.style.css | 19 +++ .../regionToolsMenu/regionMenu.template.html | 53 +++++++ .../searchSideNav/searchSideNav.component.ts | 70 ++++++++-- .../searchSideNav/searchSideNav.template.html | 17 ++- src/ui/ui.module.ts | 14 +- ...eContextualContainerDirective.directive.ts | 25 ++-- .../captureClickListener.directive.ts | 52 +++++++ 18 files changed, 629 insertions(+), 129 deletions(-) create mode 100644 src/ui/connectivityBrowser/connectivityBrowser.component.ts create mode 100644 src/ui/connectivityBrowser/connectivityBrowser.template.html create mode 100644 src/ui/regionToolsMenu/regionMenu.component.ts create mode 100644 src/ui/regionToolsMenu/regionMenu.style.css create mode 100644 src/ui/regionToolsMenu/regionMenu.template.html create mode 100644 src/util/directives/captureClickListener.directive.ts diff --git a/package.json b/package.json index f8dbd7d7e..7a308a217 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 13248f4a0..49cb9ca2e 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 20a455ef3..7637a7d4c 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 6dd663fae..772d6b75b 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 479dbb344..831701b2a 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 2781c6cad..774f1adeb 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 396ebfe8e..e31af30dd 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 842499c18..92950ea16 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 000000000..0f85701a0 --- /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 000000000..31dba5728 --- /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 000000000..86d8dade3 --- /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 000000000..fc2023444 --- /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 000000000..0d7afd988 --- /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 4d12f4cc8..a35562c89 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 1230a7232..1c512d7d4 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 9b8f23772..d33fa5fa4 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 bf53ed235..458d01069 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 000000000..bd92d61fe --- /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 -- GitLab