From b1a8bf11ef65ceb0f977e093d3169dfcfbf4f376 Mon Sep 17 00:00:00 2001 From: Xiao Gui <xgui3783@gmail.com> Date: Tue, 31 Mar 2020 17:51:59 +0200 Subject: [PATCH] fixes menu on context window --- .../navigating/navigateFromRegion.e2e-spec.js | 137 ++++++++++++------ e2e/src/util.js | 26 +++- src/main.module.ts | 2 - .../regionMenu/regionMenu.component.ts | 7 +- .../regionMenu/regionMenu.template.html | 64 ++++---- ...eContextualContainerDirective.directive.ts | 11 +- .../captureClickListener.directive.ts | 22 ++- src/util/util.module.ts | 3 + 8 files changed, 185 insertions(+), 87 deletions(-) diff --git a/e2e/src/navigating/navigateFromRegion.e2e-spec.js b/e2e/src/navigating/navigateFromRegion.e2e-spec.js index 0cbe20e89..11ef9f88f 100644 --- a/e2e/src/navigating/navigateFromRegion.e2e-spec.js +++ b/e2e/src/navigating/navigateFromRegion.e2e-spec.js @@ -51,61 +51,112 @@ const TEST_DATA = [ }, ] +const getBeforeEachFn = iavPage => ({ + url, + templateName, + position, + }) => async () => { -describe('explore same region in different templates', () => { + if (url) { + await iavPage.goto(url) + } else { + await iavPage.goto() + await iavPage.selectTitleTemplateParcellation(templateName) + await iavPage.wait(500) + await iavPage.waitForAsync() + } + + const tag = await iavPage.getSideNavTag() + await tag.click() + await iavPage.wait(1000) + await iavPage.waitUntilAllChunksLoaded() + await iavPage.cursorMoveToAndClick({ position }) + + await iavPage.showOtherTemplateMenu() + await iavPage.wait(500) +} + +describe('> explore same region in different templates', () => { let iavPage - beforeAll(async () => { + beforeEach(async () => { iavPage = new AtlasPage() await iavPage.init() }) - TEST_DATA.forEach(template => { - template.expectedTemplateLabels.forEach(expectedTemplate => { - it (`testing ${template.expectedRegion} exploring at: ${template.name}`, async () => { - if (template.url) { - await iavPage.goto(template.url) - } else { - await iavPage.goto() - await iavPage.selectTitleTemplateParcellation(template.name) - } - const {position} = template + describe('> moving to different reference template works', () => { + for (const template of TEST_DATA) { + const { + url, + templateName, + position, + expectedRegion, + expectedTemplateLabels, + } = template - const tag = await iavPage.getSideNavTag() - await tag.click() - await iavPage.wait(1000) - await iavPage.waitUntilAllChunksLoaded() - await iavPage.cursorMoveToAndClick({ position }) + describe(`> testing ${templateName}`, () => { + beforeEach(getBeforeEachFn(iavPage)(template)) - await iavPage.showOtherTemplateMenu() - await iavPage.wait(500) + for (const tmplLabel of expectedTemplateLabels) { + const { expectedPosition, name, hemisphere } = tmplLabel + describe(`> moving to ${name}`, () => { + it('> works as expected', async () => { - const otherTemplates = await iavPage.getAllOtherTemplates() - const { name, hemisphere, expectedPosition } = expectedTemplate - const idx = otherTemplates.findIndex(template => { - if (hemisphere) { - if (template.indexOf(hemisphere) < 0) return false - } - return template.indexOf(name) >= 0 - }) - - expect(idx).toBeGreaterThanOrEqual(0) - - await iavPage.clickNthItemAllOtherTemplates(idx) - - await iavPage.wait(500) - await iavPage.waitUntilAllChunksLoaded() - - const navState = await iavPage.getNavigationState() - - // somehow there are slight deviations (1nm in most cases) - // giving a tolerance of 0.1um - for (const idx in navState.position) { - expect( - Math.abs(navState.position[idx] - expectedPosition[idx]) - ).toBeLessThanOrEqual(100) + const otherTemplates = await iavPage.getAllOtherTemplates() + const idx = otherTemplates.findIndex(template => { + if (hemisphere) { + if (template.indexOf(hemisphere) < 0) return false + } + return template.indexOf(name) >= 0 + }) + + expect(idx).toBeGreaterThanOrEqual(0) + + await iavPage.clickNthItemAllOtherTemplates(idx) + + await iavPage.wait(500) + await iavPage.waitUntilAllChunksLoaded() + + const navState = await iavPage.getNavigationState() + + // somehow there are slight deviations (1nm in most cases) + // giving a tolerance of 0.1um + for (const idx in navState.position) { + expect( + Math.abs(navState.position[idx] - expectedPosition[idx]) + ).toBeLessThanOrEqual(100) + } + }) + }) } }) + } + }) + + describe('> menu UI', () => { + const data = TEST_DATA[0] + + beforeEach(async () => { + await getBeforeEachFn(iavPage)(data)() + }) + + it('> dismisses when user clicks/drags outside', async () => { + const { expectedRegion, expectedTemplateLabels, position, url, templateName } = data + + await iavPage.cursorMoveToAndDrag({ + position: [position[0], position[1] - 100], + delta: [50, 1] + }) + + await iavPage.wait(1000) + + // should throw + try { + const otherTemplates = await iavPage.getAllOtherTemplates() + expect(true).toBe(false) + } catch(e) { + expect(true).toBe(true) + } }) }) }) diff --git a/e2e/src/util.js b/e2e/src/util.js index 0b8e60e8e..679cbffe2 100644 --- a/e2e/src/util.js +++ b/e2e/src/util.js @@ -23,7 +23,7 @@ async function _getIndexFromArrayOfWebElements(search, webElements) { const regionSearchAriaLabelText = 'Search for any region of interest in the atlas selected' -const vertifyPos = position => { +const verifyPosition = position => { if (!position) throw new Error(`cursorGoto: position must be defined!`) const x = Array.isArray(position) ? position[0] : position.x @@ -93,7 +93,7 @@ class WdBase{ } async cursorMoveTo({ position }) { - const { x, y } = vertifyPos(position) + const { x, y } = verifyPosition(position) return this._driver.actions() .move() .move({ @@ -105,7 +105,7 @@ class WdBase{ } async cursorMoveToAndClick({ position }) { - const { x, y } = vertifyPos(position) + const { x, y } = verifyPosition(position) return this._driver.actions() .move() .move({ @@ -117,6 +117,26 @@ class WdBase{ .perform() } + async cursorMoveToAndDrag({ position, delta }) { + const { x, y } = verifyPosition(position) + const { x: deltaX, y: deltaY } = verifyPosition(delta) + return this._driver.actions() + .move() + .move({ + x, + y, + duration: 1000 + }) + .press() + .move({ + x: x + deltaX, + y: y + deltaY, + duration: 1000 + }) + .release() + .perform() + } + async initHttpInterceptor(){ await this._browser.executeScript(() => { if (window.__isIntercepting__) return diff --git a/src/main.module.ts b/src/main.module.ts index 3b911d31f..e92737b4b 100644 --- a/src/main.module.ts +++ b/src/main.module.ts @@ -14,7 +14,6 @@ import { GetNamesPipe } from "./util/pipes/getNames.pipe"; import { HttpClientModule } from "@angular/common/http"; import { EffectsModule } from "@ngrx/effects"; -import { CaptureClickListenerDirective } from "src/util/directives/captureClickListener.directive"; import { AtlasViewerAPIServices } from "./atlasViewer/atlasViewer.apiService.service"; import { AtlasWorkerService } from "./atlasViewer/atlasViewer.workerService.service"; import { ModalUnit } from "./atlasViewer/modalUnit/modalUnit.component"; @@ -100,7 +99,6 @@ import 'src/theme.scss' FloatingContainerDirective, FloatingMouseContextualContainerDirective, DragDropDirective, - CaptureClickListenerDirective, /* pipes */ GetNamesPipe, diff --git a/src/ui/parcellationRegion/regionMenu/regionMenu.component.ts b/src/ui/parcellationRegion/regionMenu/regionMenu.component.ts index 6d58ff5be..4890920d3 100644 --- a/src/ui/parcellationRegion/regionMenu/regionMenu.component.ts +++ b/src/ui/parcellationRegion/regionMenu/regionMenu.component.ts @@ -1,7 +1,6 @@ -import {Component, ElementRef, OnDestroy, OnInit, ViewChild} from "@angular/core"; +import { Component, OnDestroy, OnInit } from "@angular/core"; import { Store } from "@ngrx/store"; -import {MatMenuTrigger} from "@angular/material/menu"; -import {Subscription} from "rxjs"; +import { Subscription } from "rxjs"; import { IavRootStoreInterface } from "src/services/stateStore.service"; import { RegionBase } from '../region.base' @@ -12,8 +11,6 @@ import { RegionBase } from '../region.base' }) export class RegionMenuComponent extends RegionBase implements OnInit, OnDestroy { - @ViewChild('additionalActionsPanel', {read: ElementRef}) additionalActionsPanelElement: ElementRef - private subscriptions: Subscription[] = [] constructor( diff --git a/src/ui/parcellationRegion/regionMenu/regionMenu.template.html b/src/ui/parcellationRegion/regionMenu/regionMenu.template.html index d5234089f..2490f4637 100644 --- a/src/ui/parcellationRegion/regionMenu/regionMenu.template.html +++ b/src/ui/parcellationRegion/regionMenu/regionMenu.template.html @@ -27,9 +27,12 @@ </span> </button> <button *ngIf="hasConnectivity" - mat-button - [matMenuTriggerFor]="connectivitySourceDatasets" - #connectivityMenuButton="matMenuTrigger"> + mat-button + [matMenuTriggerFor]="connectivitySourceDatasets" + #connectivityMenuButton="matMenuTrigger" + iav-captureClickListenerDirective + [iav-captureClickListenerDirective-captureDocument]="true" + (iav-captureClickListenerDirective-onMousedown)="connectivityMenuButton.closeMenu()"> <i class="fab fa-connectdevelop"></i> <span> Connectivity @@ -37,15 +40,6 @@ <i class="fas fa-angle-right"></i> </button> - <!-- ToDo make dynamic with AVAILABLE CONNECTIVITY DATASETS data - get info from atlas viewer core --> - <mat-menu #connectivitySourceDatasets="matMenu" xPosition="before" (click)="$event.stopPropagation()" hasBackdrop="false"> - <div> - <button mat-menu-item - (click)="showConnectivity(region.name)"> - <span>1000 Brain Study - DTI connectivity</span> - </button> - </div> - </mat-menu> <!-- Menu to navigate between template spaces to explore same region --> @@ -53,7 +47,11 @@ <button mat-button aria-label="Show availability in other reference spaces" *ngIf="sameRegionTemplate.length" - [matMenuTriggerFor]="additionalActions"> + [matMenuTriggerFor]="additionalActions" + #changeTmplTrigger="matMenuTrigger" + iav-captureClickListenerDirective + [iav-captureClickListenerDirective-captureDocument]="true" + (iav-captureClickListenerDirective-onMousedown)="changeTmplTrigger.closeMenu()"> <i class="fas fa-brain"></i> <span> Change template @@ -62,18 +60,32 @@ </button> </div> - <mat-menu - [aria-label]="'Availability in other reference spaces'" - #additionalActions="matMenu" - xPosition="before" - hasBackdrop="false"> - <div> - <button mat-menu-item *ngFor="let sameRegion of sameRegionTemplate; let i = index" (click)="changeView(i)" class="d-flex"> - <span class="overflow-x-hidden text-truncate"> {{sameRegion.template.name}} </span> - <span *ngIf="sameRegion.hemisphere"> - {{sameRegion.hemisphere}}</span> - </button> - </div> - </mat-menu> - </div> </mat-card> + + +<!-- ToDo make dynamic with AVAILABLE CONNECTIVITY DATASETS data - get info from atlas viewer core --> +<mat-menu + #connectivitySourceDatasets="matMenu" + xPosition="before" + hasBackdrop="false"> + <div> + <button mat-menu-item (click)="showConnectivity(region.name)"> + <span>1000 Brain Study - DTI connectivity</span> + </button> + </div> +</mat-menu> + + +<mat-menu + [aria-label]="'Availability in other reference spaces'" + #additionalActions="matMenu" + xPosition="before" + hasBackdrop="false"> + <div> + <button mat-menu-item *ngFor="let sameRegion of sameRegionTemplate; let i = index" (click)="changeView(i)" class="d-flex"> + <span class="overflow-x-hidden text-truncate"> {{sameRegion.template.name}} </span> + <span *ngIf="sameRegion.hemisphere"> - {{sameRegion.hemisphere}}</span> + </button> + </div> +</mat-menu> diff --git a/src/util/directives/FixedMouseContextualContainerDirective.directive.ts b/src/util/directives/FixedMouseContextualContainerDirective.directive.ts index 113bc0650..bf2293466 100644 --- a/src/util/directives/FixedMouseContextualContainerDirective.directive.ts +++ b/src/util/directives/FixedMouseContextualContainerDirective.directive.ts @@ -1,11 +1,11 @@ -import { Directive, ElementRef, EventEmitter, HostBinding, Input, Output } from "@angular/core"; +import { Directive, ElementRef, EventEmitter, HostBinding, Input, Output, AfterContentChecked, ChangeDetectorRef, AfterViewInit } from "@angular/core"; @Directive({ selector: '[fixedMouseContextualContainerDirective]', exportAs: 'iavFixedMouseCtxContainer' }) -export class FixedMouseContextualContainerDirective { +export class FixedMouseContextualContainerDirective implements AfterContentChecked { private defaultPos: [number, number] = [-1e3, -1e3] public isShown: boolean = false @@ -21,6 +21,7 @@ export class FixedMouseContextualContainerDirective { constructor( private el: ElementRef, + private cdr: ChangeDetectorRef, ) { } @@ -41,8 +42,12 @@ export class FixedMouseContextualContainerDirective { this.transform = `translate(${this.mousePos.map(v => v.toString() + 'px').join(', ')})` } + ngAfterContentChecked(){ + this.recalculatePosition() + this.cdr.markForCheck() + } + public show() { - setTimeout(() => this.recalculatePosition()) this.styleDisplay = 'inline-block' this.isShown = true this.onShow.emit() diff --git a/src/util/directives/captureClickListener.directive.ts b/src/util/directives/captureClickListener.directive.ts index 655fb8b72..e10bb4dc9 100644 --- a/src/util/directives/captureClickListener.directive.ts +++ b/src/util/directives/captureClickListener.directive.ts @@ -1,6 +1,7 @@ -import { Directive, ElementRef, EventEmitter, OnDestroy, OnInit, Output } from "@angular/core"; +import { Directive, ElementRef, EventEmitter, OnDestroy, OnInit, Output, Input, Inject } from "@angular/core"; import { fromEvent, Subscription } from "rxjs"; import { switchMapTo, takeUntil } from "rxjs/operators"; +import { DOCUMENT } from "@angular/common"; @Directive({ selector: '[iav-captureClickListenerDirective]', @@ -8,16 +9,27 @@ import { switchMapTo, takeUntil } from "rxjs/operators"; export class CaptureClickListenerDirective implements OnInit, OnDestroy { + @Input('iav-captureClickListenerDirective-captureDocument') captureDocument: boolean = false + private subscriptions: Subscription[] = [] @Output('iav-captureClickListenerDirective-onClick') public mapClicked: EventEmitter<any> = new EventEmitter() @Output('iav-captureClickListenerDirective-onMousedown') public mouseDownEmitter: EventEmitter<any> = new EventEmitter() - constructor(private el: ElementRef) { } + constructor( + private el: ElementRef, + @Inject(DOCUMENT) private document: Document, + ) {} + + get element(){ + return this.captureDocument + ? this.document + : this.el.nativeElement + } public ngOnInit() { - const mouseDownObs$ = fromEvent(this.el.nativeElement, 'mousedown', { capture: true }) - const mouseMoveObs$ = fromEvent(this.el.nativeElement, 'mousemove', { capture: true }) - const mouseUpObs$ = fromEvent(this.el.nativeElement, 'mouseup', { capture: true }) + const mouseDownObs$ = fromEvent(this.element, 'mousedown', { capture: true }) + const mouseMoveObs$ = fromEvent(this.element, 'mousemove', { capture: true }) + const mouseUpObs$ = fromEvent(this.element, 'mouseup', { capture: true }) this.subscriptions.push( mouseDownObs$.subscribe(event => { diff --git a/src/util/util.module.ts b/src/util/util.module.ts index e8af338f0..0675aa46c 100644 --- a/src/util/util.module.ts +++ b/src/util/util.module.ts @@ -7,6 +7,7 @@ import { StopPropagationDirective } from "./directives/stopPropagation.directive import { FilterNullPipe } from "./pipes/filterNull.pipe"; import { IncludesPipe } from "./pipes/includes.pipe"; import { SafeResourcePipe } from "./pipes/safeResource.pipe"; +import { CaptureClickListenerDirective } from "./directives/captureClickListener.directive"; @NgModule({ declarations: [ @@ -20,6 +21,7 @@ import { SafeResourcePipe } from "./pipes/safeResource.pipe"; KeyListner, IncludesPipe, SafeResourcePipe, + CaptureClickListenerDirective, ], exports: [ FilterNullPipe, @@ -32,6 +34,7 @@ import { SafeResourcePipe } from "./pipes/safeResource.pipe"; KeyListner, IncludesPipe, SafeResourcePipe, + CaptureClickListenerDirective, ], providers: [ ] -- GitLab