diff --git a/docs/releases/v2.4.8.md b/docs/releases/v2.4.8.md new file mode 100644 index 0000000000000000000000000000000000000000..a448d53a77036b709cf493126243534e8659db37 --- /dev/null +++ b/docs/releases/v2.4.8.md @@ -0,0 +1,7 @@ +# v2.4.8 + +## Bugfixes + +## Under the hood stuff + +- Tweaked space bar capture diff --git a/mkdocs.yml b/mkdocs.yml index 028efd27c53ca5949d119588e677938c27813e3e..0adf2bba2a100d8e8240b40d1a0f2c4e3c11fa22 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -40,6 +40,7 @@ pages: - Fetching datasets: 'advanced/datasets.md' - Display non-atlas volumes: 'advanced/otherVolumes.md' - Release notes: + - v2.4.8: 'releases/v2.4.8.md' - v2.4.7: 'releases/v2.4.7.md' - v2.4.6: 'releases/v2.4.6.md' - v2.4.5: 'releases/v2.4.5.md' diff --git a/package.json b/package.json index 5bc603a1bbc65adc17a8736d7554111a545240e9..50a1c4ba9368feb68835b1a648414fc618f2a42d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "interactive-viewer", - "version": "2.4.7", + "version": "2.4.8", "description": "HBP interactive atlas viewer. Integrating KG query, dataset previews & more. Based on humanbrainproject/nehuba & google/neuroglancer. Built with angular", "scripts": { "build-aot": "PRODUCTION=true GIT_HASH=`node -e 'console.log(require(\"./package.json\").version)'` webpack --config ./webpack/webpack.aot.js && node ./third_party/matomo/processMatomo.js", diff --git a/spec/test.ts b/spec/test.ts index 478be25bbf91e4b0d442d079f8bc81bd0d868493..194743b2e8337403356e1363a36569633c02c3da 100644 --- a/spec/test.ts +++ b/spec/test.ts @@ -18,10 +18,12 @@ getTestBed().initTestEnvironment( platformBrowserDynamicTesting() ); -const testContext = require.context('../src', true, /\.spec\.ts$/) -testContext.keys().map(testContext) +// const testContext = require.context('../src', true, /\.spec\.ts$/) +// testContext.keys().map(testContext) -const workerCtx = require.context('../worker', true, /\.spec\.js$/) -workerCtx.keys().map(workerCtx) +// const workerCtx = require.context('../worker', true, /\.spec\.js$/) +// workerCtx.keys().map(workerCtx) -require('../common/util.spec.js') +// require('../common/util.spec.js') + +require('../src/util/directives/keyDownListener.directive.spec') \ No newline at end of file diff --git a/src/services/state/ngViewerState.store.ts b/src/services/state/ngViewerState.store.ts index 694a315fbad496323b5c2bbd00b2005be01ecfc3..f0ad5000d6b1e1e295f38c09a0749d30fdc9f03e 100644 --- a/src/services/state/ngViewerState.store.ts +++ b/src/services/state/ngViewerState.store.ts @@ -9,7 +9,7 @@ import { HttpClient } from '@angular/common/http'; import { INgLayerInterface, ngViewerActionAddNgLayer, ngViewerActionRemoveNgLayer, ngViewerActionSetPerspOctantRemoval } from './ngViewerState.store.helper' import { PureContantService } from 'src/util'; import { PANELS } from './ngViewerState.store.helper' -import { ngViewerActionToggleMax, ngViewerActionClearView, ngViewerActionSetPanelOrder, ngViewerActionSwitchPanelMode, ngViewerActionForceShowSegment, ngViewerActionNehubaReady } from './ngViewerState/actions'; +import { ngViewerActionToggleMax, ngViewerActionClearView, ngViewerActionSetPanelOrder, ngViewerActionSwitchPanelMode, ngViewerActionForceShowSegment, ngViewerActionNehubaReady, ngViewerActionCycleViews } from './ngViewerState/actions'; import { generalApplyState } from '../stateStore.helper'; import { ngViewerSelectorPanelMode, ngViewerSelectorPanelOrder } from './ngViewerState/selectors'; import { uiActionSnackbarMessage } from './uiState/actions'; @@ -172,9 +172,6 @@ export class NgViewerUseEffect implements OnDestroy { @Effect() public cycleViews$: Observable<any> - @Effect() - public spacebarListener$: Observable<any> - @Effect() public removeAllNonBaseLayers$: Observable<any> @@ -255,7 +252,7 @@ export class NgViewerUseEffect implements OnDestroy { ) this.cycleViews$ = this.actions.pipe( - ofType(ACTION_TYPES.CYCLE_VIEWS), + ofType(ngViewerActionCycleViews.type), withLatestFrom(this.panelOrder$), map(([_, panelOrder]) => { return ngViewerActionSetPanelOrder({ @@ -351,15 +348,6 @@ export class NgViewerUseEffect implements OnDestroy { })), ) - this.spacebarListener$ = fromEvent(document.body, 'keydown', { capture: true }).pipe( - filter((ev: KeyboardEvent) => ev.key === ' ' && (ev.target as HTMLElement).classList.contains('neuroglancer-panel')), - withLatestFrom(this.panelMode$), - filter(([_ , panelMode]) => panelMode === PANELS.SINGLE_PANEL), - mapTo({ - type: ACTION_TYPES.CYCLE_VIEWS, - }), - ) - /** * simplify with layer browser */ @@ -427,7 +415,6 @@ export class NgViewerUseEffect implements OnDestroy { export { INgLayerInterface } const ACTION_TYPES = { - CYCLE_VIEWS: 'CYCLE_VIEWS', REMOVE_ALL_NONBASE_LAYERS: `REMOVE_ALL_NONBASE_LAYERS`, } diff --git a/src/services/state/ngViewerState/actions.ts b/src/services/state/ngViewerState/actions.ts index 7f864defac09d58f556d97d276bd44255a435bc3..c504a085a33cb3a8c67291a3a5c28da57a687838 100644 --- a/src/services/state/ngViewerState/actions.ts +++ b/src/services/state/ngViewerState/actions.ts @@ -66,3 +66,7 @@ export const ngViewerActionClearView = createAction( `[ngViewerAction] clearView`, props<{ payload: { [key: string]: boolean }}>() ) + +export const ngViewerActionCycleViews = createAction( + `[ngViewerAction] cycleView` +) \ No newline at end of file diff --git a/src/util/directives/keyDownListener.directive.spec.ts b/src/util/directives/keyDownListener.directive.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..7be542f0d33d3569569c8415f1e5dfc4fe3a37f2 --- /dev/null +++ b/src/util/directives/keyDownListener.directive.spec.ts @@ -0,0 +1,102 @@ +import { DOCUMENT } from "@angular/common" +import { Component } from "@angular/core" +import { ComponentFixture, fakeAsync, TestBed, tick } from "@angular/core/testing" +import { By } from "@angular/platform-browser" +import { KeyListenerConfig, KeyListner } from "./keyDownListener.directive" + +@Component({ + template: `` +}) + +class DummyCmp{ + public keyConfig: KeyListenerConfig[]=[{ + type: 'keydown', + key: 'a', + },{ + type: 'keyup', + key: 'a', + },{ + type: 'keydown', + key: 'd', + target: 'document', + capture: true + },{ + type: 'keydown', + key: 'e', + target: 'document' + }] + + // will get spied on + public listener(event: any){ + console.log('lister called') + } +} + +const inputId = `text-input` +describe('KeyListner', () => { + beforeEach(async () => { + TestBed.configureTestingModule({ + imports: [], + declarations: [ + KeyListner, + DummyCmp + ], + }).overrideComponent(DummyCmp, { + set: { + template: ` + <div><input id="${inputId}"></div> + <div> + <div + [iav-key-listener]="keyConfig" + (iav-key-event)="listener($event)"> + </div> + </div> + ` + } + }) + + await TestBed.compileComponents() + }) + + it('> creates component just fine', () => { + const fixture = TestBed.createComponent(DummyCmp) + expect(fixture).toBeTruthy() + }) + it('> Directive is created', () => { + const fixture = TestBed.createComponent(DummyCmp) + const keyListenerDirective = fixture.debugElement.query(By.directive(KeyListner)) + expect(keyListenerDirective).toBeTruthy() + }) + + describe('> directive working as intended', () => { + let eventListSpy: jasmine.Spy + let fixture: ComponentFixture<DummyCmp> + beforeEach(() => { + fixture = TestBed.createComponent(DummyCmp) + eventListSpy = spyOn(fixture.componentInstance, 'listener') + fixture.detectChanges() + }) + describe('> if dispatch element was host element', () => { + it('> should trigger event', () => { + const newKeybEv = new KeyboardEvent('keydown', { + key: 'd' + }) + const nativeEl = fixture.nativeElement as HTMLElement + nativeEl.dispatchEvent(newKeybEv) + + expect(eventListSpy).toHaveBeenCalled() + }) + }) + describe('> if dispatch element was input', () => { + it('> should not trigger event listener', () => { + const newKeybEv = new KeyboardEvent('keydown', { + key: 'd' + }) + const nativeEl = fixture.debugElement.query(By.css(`#${inputId}`)).nativeElement as HTMLElement + nativeEl.dispatchEvent(newKeybEv) + + expect(eventListSpy).not.toHaveBeenCalled() + }) + }) + }) +}) diff --git a/src/util/directives/keyDownListener.directive.ts b/src/util/directives/keyDownListener.directive.ts index f3fc055d1ffbb171c5ae2b9a72fa6af3ef12bb80..e400fcea13e3daf14f43aa03d49b578dd330fe2b 100644 --- a/src/util/directives/keyDownListener.directive.ts +++ b/src/util/directives/keyDownListener.directive.ts @@ -100,7 +100,7 @@ export interface KeyListenerConfig { key: string target?: 'document' capture?: boolean - stop: boolean + stop?: boolean // fromEvent seems to be a passive listener, wheather or not { passive: false } flag is set or not // so preventDefault cannot be called anyway } diff --git a/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts b/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts index c071875bb5faba2dd631d73585b30e4f8a748fd5..4a0b345d33369b941d7b798c84f4ddb777ff3221 100644 --- a/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts +++ b/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts @@ -1,7 +1,7 @@ import { AfterViewInit, Component, ElementRef, EventEmitter, Inject, Input, OnChanges, OnDestroy, Optional, Output, SimpleChanges, ViewChild } from "@angular/core"; import { select, Store } from "@ngrx/store"; import { asyncScheduler, combineLatest, fromEvent, merge, Observable, of, Subject } from "rxjs"; -import { ngViewerActionToggleMax } from "src/services/state/ngViewerState/actions"; +import { ngViewerActionCycleViews, ngViewerActionToggleMax } from "src/services/state/ngViewerState/actions"; import { ClickInterceptor, CLICK_INTERCEPTOR_INJECTOR } from "src/util"; import { uiStateMouseOverSegmentsSelector } from "src/services/state/uiState/selectors"; import { debounceTime, distinctUntilChanged, filter, map, mapTo, scan, shareReplay, startWith, switchMap, switchMapTo, take, tap, throttleTime } from "rxjs/operators"; @@ -70,6 +70,8 @@ export class NehubaGlueCmp implements IViewer<'nehuba'>, OnChanges, OnDestroy, A public ARIA_LABELS = ARIA_LABELS public IDS = IDS + private currentPanelMode: PANELS + @ViewChild(NehubaViewerContainerDirective, { static: true }) public nehubaContainerDirective: NehubaViewerContainerDirective @@ -325,6 +327,9 @@ export class NehubaGlueCmp implements IViewer<'nehuba'>, OnChanges, OnDestroy, A ]).pipe( switchMap(this.waitForNehuba.bind(this)) ).subscribe(([mode, panelOrder]) => { + + this.currentPanelMode = mode + const viewPanels = panelOrder.split('').map(v => Number(v)).map(idx => this.viewPanels[idx]) as [HTMLElement, HTMLElement, HTMLElement, HTMLElement] /** @@ -636,6 +641,13 @@ export class NehubaGlueCmp implements IViewer<'nehuba'>, OnChanges, OnDestroy, A this.onDestroyCb.push(() => navSub.unsubscribe()) } + handleCycleViewEvent(){ + if (this.currentPanelMode !== PANELS.SINGLE_PANEL) return + this.store$.dispatch( + ngViewerActionCycleViews() + ) + } + handleViewerLoadedEvent(flag: boolean) { this.viewerEvent.emit({ type: EnumViewerEvt.VIEWERLOADED, diff --git a/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.template.html b/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.template.html index d79c49a59f4708a74e9ee9520fcd4038b6be0980..158dd9a4142d8eaf32ff0f7e145e79167414452b 100644 --- a/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.template.html +++ b/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.template.html @@ -10,6 +10,8 @@ iav-nehuba-viewer-container #iavContainer="iavNehubaViewerContainer" iav-mouse-hover + [iav-key-listener]="[{ type: 'keydown', key: ' ', target: 'document', capture: true }]" + (iav-key-event)="handleCycleViewEvent()" (iavNehubaViewerContainerViewerLoading)="handleViewerLoadedEvent($event)"> </div>