diff --git a/common/constants.js b/common/constants.js index e92bdb8be98ac6aba5a3e1bb33607e348fb83e83..bdc389ddccba9f4b6227cec7357fc6def00443cb 100644 --- a/common/constants.js +++ b/common/constants.js @@ -13,6 +13,10 @@ // overlay specific CONTEXT_MENU: `Viewer context menu`, + ZOOM_IN: 'Zoom in', + ZOOM_OUT: 'Zoom out', + MAXIMISE_VIEW: 'Maximise this view', + UNMAXIMISE_VIEW: 'Undo maximise', // sharing module SHARE_BTN: `Share this view`, diff --git a/docs/releases/v2.3.0.md b/docs/releases/v2.3.0.md index 396db42327544c2bc94914ac71ad524ec242fbdd..da2150d30807dcd67059fe4e77df3391ffb976c2 100644 --- a/docs/releases/v2.3.0.md +++ b/docs/releases/v2.3.0.md @@ -7,3 +7,4 @@ - parse min and max, if these metadata are provided - allowing for color maps other than jet - Updated `README.md` +- introduced zoom buttons diff --git a/e2e/chromeOpts.js b/e2e/chromeOpts.js index 8af7d2b44f19fe2945dca7fc63b8dcd7c5139c11..f5447a0912e3ef936136ed0cbc91b17ba00b3f2e 100644 --- a/e2e/chromeOpts.js +++ b/e2e/chromeOpts.js @@ -1,9 +1,11 @@ +const { width, height } = require('./opts') + module.exports = [ ...(process.env.DISABLE_CHROME_HEADLESS ? [] : ['--headless']), '--no-sandbox', '--disable-gpu', '--disable-setuid-sandbox', "--disable-extensions", - '--window-size=800,796', + `--window-size=${width},${height}`, '--disable-application-cache' ] \ No newline at end of file diff --git a/e2e/opts.js b/e2e/opts.js new file mode 100644 index 0000000000000000000000000000000000000000..9b75d287f6ec4ffa0100562d033d65b1eb9c4eb6 --- /dev/null +++ b/e2e/opts.js @@ -0,0 +1,4 @@ +module.exports = { + width: 800, + height: 796 +} diff --git a/e2e/src/navigating/btnZoomInOut.prod.e2e-spec.js b/e2e/src/navigating/btnZoomInOut.prod.e2e-spec.js new file mode 100644 index 0000000000000000000000000000000000000000..e54ee6f0d684ee588297745de1c36296f172aec6 --- /dev/null +++ b/e2e/src/navigating/btnZoomInOut.prod.e2e-spec.js @@ -0,0 +1,141 @@ +const { AtlasPage } = require('../util') +const { width, height } = require('../../opts') +const { ARIA_LABELS } = require('../../../common/constants') + +describe('> zoom in btns on panels', () => { + + let iavPage + const url = '/?templateSelected=MNI+152+ICBM+2009c+Nonlinear+Asymmetric&parcellationSelected=JuBrain+Cytoarchitectonic+Atlas' + + describe('> on hover overlay elements', () => { + beforeEach(async () => { + iavPage = new AtlasPage() + await iavPage.init() + await iavPage.goto(url) + }) + it('> panel btns do not show', async () => { + + await iavPage.cursorMoveTo({ + position: [width / 4, height/4] + }) + + await iavPage.wait(500) + + const visibleFlags = await iavPage.areVisible(`[aria-label="${ARIA_LABELS.ZOOM_IN}"]`) + expect(visibleFlags.length).toBe(4) + expect(visibleFlags).toEqual([false, false, false, false]) + await iavPage.clickSideNavTab() + }) + }) + + const delta = 30 + const arr = [ + [ width / 2 - delta, height / 2 - delta ], + [ width / 2 + delta, height / 2 - delta ], + [ width / 2 - delta, height / 2 + delta ], + [ width / 2 + delta, height / 2 + delta ], + ] + + for (const key of [ ARIA_LABELS.ZOOM_IN, ARIA_LABELS.ZOOM_OUT ]) { + describe(`> testing ${key}`, () => { + + for (const idx in arr) { + describe(`> panel ${idx}`, () => { + beforeAll(async () => { + iavPage = new AtlasPage() + await iavPage.init() + await iavPage.goto(url) + }) + it('> mouse over should show zoom btns', async () => { + + await iavPage.cursorMoveTo({ + position: arr[idx] + }) + + await iavPage.wait(500) + const visibleFlags = await iavPage.areVisible(`[aria-label="${key}"]`) + expect(visibleFlags.length).toBe(4) + + const expectedFlags = [false, false, false, false] + expectedFlags[idx] = true + expect(visibleFlags).toEqual(expectedFlags) + }) + + + describe(`> "${key}" btns btn works`, () => { + let o, po, pz, z, p, + no2, npo2, npz2, nz2, np2 + beforeAll(async () => { + + const navState = await iavPage.getNavigationState() + + o = navState.orientation + po = navState.perspectiveOrientation + pz = navState.perspectiveZoom + z = navState.zoom + p = navState.position + + const arrOut = await iavPage.areAt(`[aria-label="${key}"]`) + + const positionOut = [ + arrOut[idx].x + arrOut[idx].width / 2, + arrOut[idx].y + arrOut[idx].height / 2 + ] + await iavPage.cursorMoveToAndClick({ + position: positionOut + }) + await iavPage.wait(2000) + const newNavState2 = await iavPage.getNavigationState() + no2 = newNavState2.orientation + npo2 = newNavState2.perspectiveOrientation + npz2 = newNavState2.perspectiveZoom + nz2 = newNavState2.zoom + np2 = newNavState2.position + + }) + + it('> expect orientation to be unchanged', () => { + expect(o).toEqual(no2) + }) + + it('> expects perspective orientation to be unchanged', () => { + expect(po).toEqual(npo2) + }) + + it('> expect position to be unchanged', () => { + expect(p).toEqual(np2) + }) + + /** + * when iterating over array accessor, the key are actually string + */ + if (Number(idx) === 3) { + it('> expects zoom to be unchanged', () => { + expect(z).toEqual(nz2) + }) + it('> expects perspectivezoom to increase with zoom out btn', () => { + if (key === ARIA_LABELS.ZOOM_OUT) { + expect(pz).toBeLessThan(npz2) + } else { + expect(pz).toBeGreaterThan(npz2) + } + }) + } else { + it('> expects perspective zoom to be unchanged', () => { + expect(pz).toEqual(npz2) + }) + + it('> expect zoom to increase with zoom out btn', () => { + if (key === ARIA_LABELS.ZOOM_OUT) { + expect(z).toBeLessThan(nz2) + } else { + expect(z).toBeGreaterThan(nz2) + } + }) + } + }) + }) + } + }) + } +}) diff --git a/e2e/src/util.js b/e2e/src/util.js index c96a61803847c02574306e31ca6f9b5dbaece2d7..5f346536bbb0e53e041f00153d59d7061e88cd6e 100644 --- a/e2e/src/util.js +++ b/e2e/src/util.js @@ -174,12 +174,36 @@ class WdBase{ return isDisplayed } + async areVisible(cssSelector){ + if (!cssSelector) throw new Error(`getText needs to define css selector`) + const els = await this._browser.findElements( By.css( cssSelector ) ) + const returnArr = [] + + for (const el of els) { + returnArr.push(await el.isDisplayed()) + } + return returnArr + } + async isAt(cssSelector){ if (!cssSelector) throw new Error(`getText needs to define css selector`) const { x, y, width, height } = await this._browser.findElement( By.css(cssSelector) ).getRect() return { x, y, width, height } } + async areAt(cssSelector){ + + if (!cssSelector) throw new Error(`getText needs to define css selector`) + const els = await this._browser.findElements( By.css( cssSelector ) ) + const returnArr = [] + + for (const el of els) { + const { x, y, width, height } = await el.getRect() + returnArr.push({ x, y, width, height }) + } + return returnArr + } + historyBack() { return this._browser.navigate().back() } @@ -328,6 +352,7 @@ class WdBase{ // it seems if you set intercept http to be true, you might also want ot set do not automat to be true async goto(url = '/', { interceptHttp, doNotAutomate, forceTimeout } = {}){ + this.__trackingNavigationState__ = false const actualUrl = getActualUrl(url) if (interceptHttp) { this._browser.get(actualUrl) @@ -920,29 +945,33 @@ class WdIavPage extends WdLayoutPage{ } async getNavigationState() { - const actualNav = await this._browser.executeScript(async () => { - let returnObj, sub - const getPr = () => new Promise(rs => { - - sub = nehubaViewer.navigationState.all - .subscribe(({ orientation, perspectiveOrientation, perspectiveZoom, position, zoom }) => { - returnObj = { - orientation: Array.from(orientation), - perspectiveOrientation: Array.from(perspectiveOrientation), - perspectiveZoom, - zoom, - position: Array.from(position) - } - rs() - }) + if (!this.__trackingNavigationState__) { + await this._browser.executeScript(async () => { + window.__iavE2eNavigationState__ = {} + + const getPr = () => new Promise(rs => { + + window.__iavE2eNavigationStateSubptn__ = nehubaViewer.navigationState.all + .subscribe(({ orientation, perspectiveOrientation, perspectiveZoom, position, zoom }) => { + window.__iavE2eNavigationState__ = { + orientation: Array.from(orientation), + perspectiveOrientation: Array.from(perspectiveOrientation), + perspectiveZoom, + zoom, + position: Array.from(position) + } + rs() + }) + }) + + await getPr() }) - await getPr() - sub.unsubscribe() + this.__trackingNavigationState__ = true + } - return returnObj - }) - return actualNav + const returnVal = await this._browser.executeScript(() => window.__iavE2eNavigationState__) + return returnVal } } diff --git a/src/atlasViewer/atlasViewer.component.ts b/src/atlasViewer/atlasViewer.component.ts index 1f3066bb2843b1853060526c7b1bc90932fd0a7a..ac40aad9cae7d3cb847eae167a8f698b6564ef67 100644 --- a/src/atlasViewer/atlasViewer.component.ts +++ b/src/atlasViewer/atlasViewer.component.ts @@ -43,6 +43,7 @@ export const NEHUBA_CLICK_OVERRIDE = 'NEHUBA_CLICK_OVERRIDE' import { MIN_REQ_EXPLAINER } from 'src/util/constants' import { SlServiceService } from "src/spotlight/sl-service.service"; +import { PureContantService } from "src/util"; /** * TODO @@ -119,6 +120,7 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit { private store: Store<IavRootStoreInterface>, private widgetServices: WidgetServices, private constantsService: AtlasViewerConstantsServices, + private pureConstantService: PureContantService, private matDialog: MatDialog, private dispatcher$: ActionsSubject, private rd: Renderer2, @@ -272,7 +274,7 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit { } this.subscriptions.push( - this.constantsService.useMobileUI$.subscribe(bool => this.ismobile = bool), + this.pureConstantService.useTouchUI$.subscribe(bool => this.ismobile = bool), ) this.subscriptions.push( diff --git a/src/atlasViewer/atlasViewer.constantService.service.ts b/src/atlasViewer/atlasViewer.constantService.service.ts index c52d47324a1e515b89bc12d884f2386f9b2a5976..c49765262f89ea15c05488414cdc66eccd2ecc5b 100644 --- a/src/atlasViewer/atlasViewer.constantService.service.ts +++ b/src/atlasViewer/atlasViewer.constantService.service.ts @@ -7,8 +7,7 @@ import { LoggingService } from "src/logging"; import { SNACKBAR_MESSAGE } from "src/services/state/uiState.store"; import { IavRootStoreInterface } from "../services/stateStore.service"; import { AtlasWorkerService } from "./atlasViewer.workerService.service"; - -export const CM_THRESHOLD = `0.05` +import { PureContantService } from "src/util"; const getUniqueId = () => Math.round(Math.random() * 1e16).toString(16) @@ -21,8 +20,6 @@ export class AtlasViewerConstantsServices implements OnDestroy { public darktheme: boolean = false public darktheme$: Observable<boolean> - public useMobileUI$: Observable<boolean> - public citationToastDuration = 7e3 /** @@ -30,13 +27,6 @@ export class AtlasViewerConstantsServices implements OnDestroy { */ private TIMEOUT = 16000 - /* TODO to be replaced by @id: Landmark/UNIQUE_ID in KG in the future */ - public testLandmarksChanged: (prevLandmarks: any[], newLandmarks: any[]) => boolean = (prevLandmarks: any[], newLandmarks: any[]) => { - return prevLandmarks.every(lm => typeof lm.name !== 'undefined') && - newLandmarks.every(lm => typeof lm.name !== 'undefined') && - prevLandmarks.length === newLandmarks.length - } - // instead of using window.location.href, which includes query param etc public backendUrl = (BACKEND_URL && `${BACKEND_URL}/`.replace(/\/\/$/, '/')) || `${window.location.origin}${window.location.pathname}` @@ -270,7 +260,8 @@ Send us an email: <a target = "_blank" href = "mailto:${this.supportEmailAddress private store$: Store<IavRootStoreInterface>, private http: HttpClient, private log: LoggingService, - private workerService: AtlasWorkerService + private workerService: AtlasWorkerService, + private pureConstantService: PureContantService ) { this.darktheme$ = this.store$.pipe( @@ -283,18 +274,12 @@ Send us an email: <a target = "_blank" href = "mailto:${this.supportEmailAddress shareReplay(1), ) - this.useMobileUI$ = this.store$.pipe( - select('viewerConfigState'), - select('useMobileUI'), - shareReplay(1), - ) - this.subscriptions.push( this.darktheme$.subscribe(flag => this.darktheme = flag), ) this.subscriptions.push( - this.useMobileUI$.subscribe(bool => { + this.pureConstantService.useTouchUI$.subscribe(bool => { if (bool) { this.showHelpSliceViewMap = this.showHelpSliceViewMobile this.showHelpGeneralMap = this.showHelpGeneralMobile @@ -325,8 +310,6 @@ Send us an email: <a target = "_blank" href = "mailto:${this.supportEmailAddress }) } - public cyclePanelMessage: string = `[spacebar] to cycle through views` - private dissmissUserLayerSnackbarMessageDesktop = `You can dismiss extra layers with [ESC]` private dissmissUserLayerSnackbarMessageMobile = `You can dismiss extra layers in the 🌠menu` public dissmissUserLayerSnackbarMessage: string = this.dissmissUserLayerSnackbarMessageDesktop diff --git a/src/components/components.module.ts b/src/components/components.module.ts index f3c9c22e3ae90235868d04fb125dbb98ff0984fe..7125e8b0f63c309f2a4c985b7fedb0cce971ddd4 100644 --- a/src/components/components.module.ts +++ b/src/components/components.module.ts @@ -7,7 +7,7 @@ import { MarkdownDom } from './markdown/markdown.component'; import { CommonModule } from '@angular/common'; import { AngularMaterialModule } from 'src/ui/sharedModules/angularMaterial.module'; -import { UtilModule } from 'src/util/util.module'; +import { UtilModule } from 'src/util'; import { SearchResultPaginationPipe } from '../util/pipes/pagination.pipe'; import { SafeHtmlPipe } from '../util/pipes/safeHtml.pipe' import { TreeSearchPipe } from '../util/pipes/treeSearch.pipe'; diff --git a/src/main.module.ts b/src/main.module.ts index c402df53fcadec932037d8cba0d05cfb40605bb9..9db1a64985d3929e55f47a4fb91cddb4e49f8d18 100644 --- a/src/main.module.ts +++ b/src/main.module.ts @@ -34,7 +34,7 @@ import { DragDropDirective } from "./util/directives/dragDrop.directive"; import { FloatingContainerDirective } from "./util/directives/floatingContainer.directive"; import { FloatingMouseContextualContainerDirective } from "./util/directives/floatingMouseContextualContainer.directive"; import { NewViewerDisctinctViewToLayer } from "./util/pipes/newViewerDistinctViewToLayer.pipe"; -import { UtilModule } from "./util/util.module"; +import { UtilModule } from "src/util"; import { SpotLightModule } from 'src/spotlight/spot-light.module' import { TryMeComponent } from "./ui/tryme/tryme.component"; diff --git a/src/services/state/ngViewerState.store.ts b/src/services/state/ngViewerState.store.ts index 1a85cdcb3d3d3aa03c6dd4d7a57f569710acc27d..d721aa6feffda9c762c2acecead9e7deb17164ad 100644 --- a/src/services/state/ngViewerState.store.ts +++ b/src/services/state/ngViewerState.store.ts @@ -2,13 +2,13 @@ import { Injectable, OnDestroy } from '@angular/core'; import { Observable, combineLatest, fromEvent, Subscription, from, of } from 'rxjs'; import { Effect, Actions, ofType } from '@ngrx/effects'; import { withLatestFrom, map, distinctUntilChanged, scan, shareReplay, filter, mapTo, debounceTime, catchError, skip, throttleTime } from 'rxjs/operators'; -import { AtlasViewerConstantsServices } from 'src/atlasViewer/atlasViewer.constantService.service'; import { SNACKBAR_MESSAGE } from './uiState.store'; import { getNgIds, IavRootStoreInterface, GENERAL_ACTION_TYPES } from '../stateStore.service'; import { Action, select, Store } from '@ngrx/store' -import { BACKENDURL } from 'src/util/constants'; +import { BACKENDURL, CYCLE_PANEL_MESSAGE } from 'src/util/constants'; import { HttpClient } from '@angular/common/http'; import { INgLayerInterface, ngViewerActionAddNgLayer, ngViewerActionRemoveNgLayer } from './ngViewerState.store.helper' +import { PureContantService } from 'src/util'; export const FOUR_PANEL = 'FOUR_PANEL' export const V_ONE_THREE = 'V_ONE_THREE' @@ -185,7 +185,7 @@ export class NgViewerUseEffect implements OnDestroy { constructor( private actions: Actions, private store$: Store<IavRootStoreInterface>, - private constantService: AtlasViewerConstantsServices, + private pureConstantService: PureContantService, private http: HttpClient, ){ @@ -343,14 +343,14 @@ export class NgViewerUseEffect implements OnDestroy { this.toggleMaximiseCycleMessage$ = combineLatest( this.toggleMaximiseMode$, - this.constantService.useMobileUI$, + this.pureConstantService.useTouchUI$, ).pipe( filter(([_, useMobileUI]) => !useMobileUI), map(([toggleMaximiseMode, _]) => toggleMaximiseMode), filter(({ payload }) => payload.panelMode && payload.panelMode === SINGLE_PANEL), mapTo({ type: SNACKBAR_MESSAGE, - snackbarMessage: this.constantService.cyclePanelMessage, + snackbarMessage: CYCLE_PANEL_MESSAGE, }), ) diff --git a/src/services/state/viewerConfig.store.helper.ts b/src/services/state/viewerConfig.store.helper.ts new file mode 100644 index 0000000000000000000000000000000000000000..98977be73c31198196532a55f6e58dcf8b369b2f --- /dev/null +++ b/src/services/state/viewerConfig.store.helper.ts @@ -0,0 +1,6 @@ +export const VIEWER_CONFIG_FEATURE_KEY = 'viewerConfigState' +export interface IViewerConfigState { + gpuLimit: number + animation: boolean + useMobileUI: boolean +} diff --git a/src/services/state/viewerConfig.store.ts b/src/services/state/viewerConfig.store.ts index 5df3cfdcba9ebfcfea66f43a5787122729a562da..74d2a657288cab2f4469a5eb8eb6c6f85dddf47b 100644 --- a/src/services/state/viewerConfig.store.ts +++ b/src/services/state/viewerConfig.store.ts @@ -1,11 +1,8 @@ import { Action } from "@ngrx/store"; import { LOCAL_STORAGE_CONST } from "src/util/constants"; -export interface StateInterface { - gpuLimit: number - animation: boolean - useMobileUI: boolean -} +import { IViewerConfigState as StateInterface } from './viewerConfig.store.helper' +export { StateInterface } interface ViewerConfigurationAction extends Action { config: Partial<StateInterface> diff --git a/src/ui/config/config.component.ts b/src/ui/config/config.component.ts index 4eef2b4403cc18034229655a65f8befc97d866a2..78526889780b6a5be9ec9905cd8dc10ffdf71b94 100644 --- a/src/ui/config/config.component.ts +++ b/src/ui/config/config.component.ts @@ -2,13 +2,13 @@ import { Component, OnDestroy, OnInit } from '@angular/core' import { select, Store } from '@ngrx/store'; import { combineLatest, Observable, Subscription } from 'rxjs'; import { debounceTime, distinctUntilChanged, map, startWith } from 'rxjs/operators'; -import { AtlasViewerConstantsServices } from 'src/atlasViewer/atlasViewer.constantService.service'; import { NG_VIEWER_ACTION_TYPES, SUPPORTED_PANEL_MODES } from 'src/services/state/ngViewerState.store'; import { VIEWER_CONFIG_ACTION_TYPES, StateInterface as ViewerConfiguration } from 'src/services/state/viewerConfig.store' import { IavRootStoreInterface } from 'src/services/stateStore.service'; import { isIdentityQuat } from '../nehubaContainer/util'; import {MatSlideToggleChange} from "@angular/material/slide-toggle"; import {MatSliderChange} from "@angular/material/slider"; +import { PureContantService } from 'src/util'; const GPU_TOOLTIP = `Higher GPU usage can cause crashes on lower end machines` const ANIMATION_TOOLTIP = `Animation can cause slowdowns in lower end machines` @@ -53,10 +53,10 @@ export class ConfigComponent implements OnInit, OnDestroy { constructor( private store: Store<IavRootStoreInterface>, - private constantService: AtlasViewerConstantsServices, + private pureConstantService: PureContantService, ) { - this.useMobileUI$ = this.constantService.useMobileUI$ + this.useMobileUI$ = this.pureConstantService.useTouchUI$ this.gpuLimit$ = this.store.pipe( select('viewerConfigState'), diff --git a/src/ui/databrowserModule/databrowser.module.ts b/src/ui/databrowserModule/databrowser.module.ts index 2db92219d762414eb9d6caadf68c44c7321c638f..0463b9b61ac5447782159317ade99c2d59a02f7c 100644 --- a/src/ui/databrowserModule/databrowser.module.ts +++ b/src/ui/databrowserModule/databrowser.module.ts @@ -4,7 +4,7 @@ import { FormsModule } from "@angular/forms"; import { ComponentsModule } from "src/components/components.module"; import { AngularMaterialModule } from 'src/ui/sharedModules/angularMaterial.module' import { DoiParserPipe } from "src/util/pipes/doiPipe.pipe"; -import { UtilModule } from "src/util/util.module"; +import { UtilModule } from "src/util"; import { DataBrowser } from "./databrowser/databrowser.component"; import { KgSingleDatasetService } from "./kgSingleDatasetService.service" import { ModalityPicker, SortModalityAlphabeticallyPipe } from "./modalityPicker/modalityPicker.component"; diff --git a/src/ui/nehubaContainer/maximisePanelButton/maximisePanelButton.component.ts b/src/ui/nehubaContainer/maximisePanelButton/maximisePanelButton.component.ts index 7187071b9950bcfc6df08d956f3fbd7dbecb67f2..6b74bfde7680c71da8cc14b92a14119a78564a92 100644 --- a/src/ui/nehubaContainer/maximisePanelButton/maximisePanelButton.component.ts +++ b/src/ui/nehubaContainer/maximisePanelButton/maximisePanelButton.component.ts @@ -3,7 +3,12 @@ import { select, Store } from "@ngrx/store"; import { Observable } from "rxjs"; import { distinctUntilChanged, map } from "rxjs/operators"; import { SINGLE_PANEL } from "src/services/state/ngViewerState.store"; -import { IavRootStoreInterface } from "src/services/stateStore.service"; +import { ARIA_LABELS } from 'common/constants' + +const { + MAXIMISE_VIEW, + UNMAXIMISE_VIEW, +} = ARIA_LABELS @Component({ selector: 'maximise-panel-button', @@ -15,6 +20,9 @@ import { IavRootStoreInterface } from "src/services/stateStore.service"; export class MaximmisePanelButton { + public ARIA_LABEL_MAXIMISE_VIEW = MAXIMISE_VIEW + public ARIA_LABEL_UNMAXIMISE_VIEW = UNMAXIMISE_VIEW + @Input() public panelIndex: number private panelMode$: Observable<string> @@ -23,7 +31,7 @@ export class MaximmisePanelButton { public isMaximised$: Observable<boolean> constructor( - private store$: Store<IavRootStoreInterface>, + private store$: Store<any>, ) { this.panelMode$ = this.store$.pipe( select('ngViewerState'), diff --git a/src/ui/nehubaContainer/maximisePanelButton/maximisePanelButton.template.html b/src/ui/nehubaContainer/maximisePanelButton/maximisePanelButton.template.html index 62e7289790c7ab96f694036a9ec94706fa0dff50..03cf3cd01e57071b42cb3234dfadfe6eab5f34b1 100644 --- a/src/ui/nehubaContainer/maximisePanelButton/maximisePanelButton.template.html +++ b/src/ui/nehubaContainer/maximisePanelButton/maximisePanelButton.template.html @@ -1,10 +1,17 @@ -<button - [matTooltip]="(isMaximised$ | async) ? 'Restore four panel view' : 'Maximise this panel'" - mat-icon-button - color="primary"> - <i *ngIf="isMaximised$ | async; else expandIconTemplate" class="fas fa-compress"></i> -</button> +<ng-content *ngTemplateOutlet="maximiseBtnTmpl; context: { $implicit: (isMaximised$ | async) }"> +</ng-content> -<ng-template #expandIconTemplate> - <i class="fas fa-expand"></i> -</ng-template> \ No newline at end of file +<ng-template #maximiseBtnTmpl let-ismaximised> + + <button + [matTooltip]="ismaximised ? 'Restore four panel view' : 'Maximise this panel'" + mat-icon-button + [attr.aria-label]="ismaximised ? ARIA_LABEL_UNMAXIMISE_VIEW : ARIA_LABEL_MAXIMISE_VIEW" + color="primary"> + <i *ngIf="ismaximised; else expandIconTemplate" class="fas fa-compress"></i> + </button> + + <ng-template #expandIconTemplate> + <i class="fas fa-expand"></i> + </ng-template> +</ng-template> diff --git a/src/ui/nehubaContainer/nehubaContainer.component.spec.ts b/src/ui/nehubaContainer/nehubaContainer.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..8724619c41adae065f2fa0a7f865d077ccd5ce37 --- /dev/null +++ b/src/ui/nehubaContainer/nehubaContainer.component.spec.ts @@ -0,0 +1,52 @@ +import { TestBed } from "@angular/core/testing" +import { NehubaContainer } from "./nehubaContainer.component" +import { provideMockStore } from "@ngrx/store/testing" + +/** + * access defaultState before initialization error + * TODO figure out why + */ + +describe('> nehubaContainer.component.ts', () => { + // describe('> NehubaContainer', () => { + // beforeEach(() => { + // TestBed.configureTestingModule({ + // imports: [ + + // ], + // declarations: [ + // NehubaContainer + // ], + // providers: [ + // provideMockStore({ + // initialState: {} + // }) + // ] + // }).compileComponents() + // }) + // it('> can be init', () => { + // const fixture = TestBed.createComponent(NehubaContainer) + // }) + // describe('> loading indicator', () => { + // it('> appears on init', () => { + // const fixture = TestBed.createComponent(NehubaContainer) + // fixture.componentInstance.viewerLoaded = true + // fixture.detectChanges() + // const els = fixture.debugElement.queryAll( By.css(`.loadingIndicator`) ) + // expect(els.length).toBe(3) + // }) + // }) + + // describe('> panel control', () => { + // it('> appears on mouse over', () => { + // /** + // * TODO implement mouse over + // */ + // }) + + // it('> on click of zoom btns, calls appropriate fn', () => { + + // }) + // }) + // }) +}) \ No newline at end of file diff --git a/src/ui/nehubaContainer/nehubaContainer.component.ts b/src/ui/nehubaContainer/nehubaContainer.component.ts index 1ee340abf89b2ba8295496df670f52e0bb0c9c33..11f4c43b96e4f78ff48254ad76d2fd6632175472 100644 --- a/src/ui/nehubaContainer/nehubaContainer.component.ts +++ b/src/ui/nehubaContainer/nehubaContainer.component.ts @@ -23,14 +23,21 @@ import { import { LoggingService } from "src/logging"; import { FOUR_PANEL, H_ONE_THREE, NG_VIEWER_ACTION_TYPES, SINGLE_PANEL, V_ONE_THREE } from "src/services/state/ngViewerState.store"; import { SELECT_REGIONS_WITH_ID, VIEWERSTATE_ACTION_TYPES } from "src/services/state/viewerState.store"; -import { ADD_NG_LAYER, generateLabelIndexId, getMultiNgIdsRegionsLabelIndexMap, getNgIds, ILandmark, IOtherLandmarkGeometry, IPlaneLandmarkGeometry, IPointLandmarkGeometry, isDefined, MOUSE_OVER_LANDMARK, NgViewerStateInterface, REMOVE_NG_LAYER, safeFilter, ViewerStateInterface, IavRootStoreInterface } from "src/services/stateStore.service"; +import { ADD_NG_LAYER, generateLabelIndexId, getMultiNgIdsRegionsLabelIndexMap, getNgIds, ILandmark, IOtherLandmarkGeometry, IPlaneLandmarkGeometry, IPointLandmarkGeometry, isDefined, MOUSE_OVER_LANDMARK, NgViewerStateInterface, REMOVE_NG_LAYER, safeFilter } from "src/services/stateStore.service"; import { getExportNehuba, isSame } from "src/util/fn"; import { AtlasViewerAPIServices, IUserLandmark } from "src/atlasViewer/atlasViewer.apiService.service"; -import { AtlasViewerConstantsServices } from "src/atlasViewer/atlasViewer.constantService.service"; import { NehubaViewerUnit } from "./nehubaViewer/nehubaViewer.component"; import { getFourPanel, getHorizontalOneThree, getSinglePanel, getVerticalOneThree, calculateSliceZoomFactor } from "./util"; import { NehubaViewerContainerDirective } from "./nehubaViewerInterface/nehubaViewerInterface.directive"; import { ITunableProp } from "./mobileOverlay/mobileOverlay.component"; +import { compareLandmarksChanged } from "src/util/constants"; +import { PureContantService } from "src/util"; +import { ARIA_LABELS } from 'common/constants' + +const { + ZOOM_IN, + ZOOM_OUT, +} = ARIA_LABELS const isFirstRow = (cell: HTMLElement) => { const { parentElement: row } = cell @@ -74,6 +81,9 @@ const scanFn: (acc: [boolean, boolean, boolean], curr: CustomEvent) => [boolean, export class NehubaContainer implements OnInit, OnChanges, OnDestroy { + public ARIA_LABEL_ZOOM_IN = ZOOM_IN + public ARIA_LABEL_ZOOM_OUT = ZOOM_OUT + @ViewChild(NehubaViewerContainerDirective,{static: true}) public nehubaContainerDirective: NehubaViewerContainerDirective @@ -87,10 +97,7 @@ export class NehubaContainer implements OnInit, OnChanges, OnDestroy { public viewerLoaded: boolean = false - private sliceViewLoadingMain$: Observable<[boolean, boolean, boolean]> - public sliceViewLoading0$: Observable<boolean> - public sliceViewLoading1$: Observable<boolean> - public sliceViewLoading2$: Observable<boolean> + public sliceViewLoadingMain$: Observable<[boolean, boolean, boolean]> public perspectiveViewLoading$: Observable<string|null> private templateSelected$: Observable<any> @@ -135,22 +142,21 @@ export class NehubaContainer implements OnInit, OnChanges, OnDestroy { public viewPanels: [HTMLElement, HTMLElement, HTMLElement, HTMLElement] = [null, null, null, null] public panelMode$: Observable<string> - private panelOrder: string public panelOrder$: Observable<string> private redrawLayout$: Observable<[string, string]> public hoveredPanelIndices$: Observable<number> constructor( - private constantService: AtlasViewerConstantsServices, + private pureConstantService: PureContantService, private apiService: AtlasViewerAPIServices, - private store: Store<IavRootStoreInterface>, + private store: Store<any>, private elementRef: ElementRef, private log: LoggingService, private cdr: ChangeDetectorRef ) { - this.useMobileUI$ = this.constantService.useMobileUI$ + this.useMobileUI$ = this.pureConstantService.useTouchUI$ this.panelMode$ = this.store.pipe( select('ngViewerState'), @@ -164,7 +170,6 @@ export class NehubaContainer implements OnInit, OnChanges, OnDestroy { select('panelOrder'), distinctUntilChanged(), shareReplay(1), - tap(panelOrder => this.panelOrder = panelOrder), ) this.redrawLayout$ = this.store.pipe( @@ -215,7 +220,7 @@ export class NehubaContainer implements OnInit, OnChanges, OnDestroy { select('dataStore'), safeFilter('fetchedSpatialData'), map(state => state.fetchedSpatialData), - distinctUntilChanged(this.constantService.testLandmarksChanged), + distinctUntilChanged(compareLandmarksChanged), debounceTime(300), ) @@ -227,24 +232,10 @@ export class NehubaContainer implements OnInit, OnChanges, OnDestroy { this.sliceViewLoadingMain$ = fromEvent(this.elementRef.nativeElement, 'sliceRenderEvent').pipe( scan(scanFn, [null, null, null]), + startWith([true, true, true] as [boolean, boolean, boolean]), shareReplay(1), ) - this.sliceViewLoading0$ = this.sliceViewLoadingMain$ - .pipe( - map(arr => arr[0]), - ) - - this.sliceViewLoading1$ = this.sliceViewLoadingMain$ - .pipe( - map(arr => arr[1]), - ) - - this.sliceViewLoading2$ = this.sliceViewLoadingMain$ - .pipe( - map(arr => arr[2]), - ) - /* missing chunk perspective view */ this.perspectiveViewLoading$ = fromEvent(this.elementRef.nativeElement, 'perpspectiveRenderEvent') .pipe( @@ -719,10 +710,6 @@ export class NehubaContainer implements OnInit, OnChanges, OnDestroy { this.subscriptions.forEach(s => s.unsubscribe()) } - public test(){ - console.log('test') - } - public toggleMaximiseMinimise(index: number) { this.store.dispatch({ type: NG_VIEWER_ACTION_TYPES.TOGGLE_MAXIMISE, @@ -944,6 +931,22 @@ export class NehubaContainer implements OnInit, OnChanges, OnDestroy { } } + public zoomNgView(panelIndex: number, factor: number) { + const ngviewer = this.nehubaViewer?.nehubaViewer?.ngviewer + if (!ngviewer) throw new Error(`ngviewer not defined!`) + + /** + * panelIndex < 3 === slice view + */ + if (panelIndex < 3) { + /** + * factor > 1 === zoom out + */ + ngviewer.navigationState.zoomBy(factor) + } else { + ngviewer.perspectiveNavigationState.zoomBy(factor) + } + } } export const identifySrcElement = (element: HTMLElement) => { diff --git a/src/ui/nehubaContainer/nehubaContainer.style.css b/src/ui/nehubaContainer/nehubaContainer.style.css index 795c4084b41770f293712671d0709ccb87c23938..14afcb960a6748c0f06936e8fc2cc569cad470dc 100644 --- a/src/ui/nehubaContainer/nehubaContainer.style.css +++ b/src/ui/nehubaContainer/nehubaContainer.style.css @@ -19,6 +19,9 @@ current-layout { top: 0; left: 0; + /** z index of current layout has to be higher than that of layout-floating-container, or status container will cover panel control */ + /** TODO decide which behaviour is more nature */ + z-index: 6; } div[landmarkMasterContainer] @@ -117,36 +120,41 @@ div#scratch-pad bottom: 0; } -maximise-panel-button +.overlay-btn-container { - transition: opacity 170ms ease-in-out, - transform 250ms ease-in-out; - position: absolute; bottom: 0; right: 0; } +.status-card-container +{ + position:absolute; + left:1em; + bottom:1em; + width : 20em; + pointer-events: all; +} + /* if not mobile, then show on hover */ -maximise-panel-button + +.opacity-crossfade +{ + transition: opacity 170ms ease-in-out, + transform 250ms ease-in-out; +} + +.opacity-crossfade { + opacity: 0.0; pointer-events: none; } -maximise-panel-button.onHover, -maximise-panel-button:hover, -:host-context([ismobile="true"]) maximise-panel-button +.opacity-crossfade.onHover, +.opacity-crossfade:hover, +:host-context([ismobile="true"]) .opacity-crossfade { opacity: 1.0 !important; pointer-events: all !important; } - -.status-card-container -{ - position:absolute; - left:1em; - bottom:1em; - width : 20em; - pointer-events: all; -} diff --git a/src/ui/nehubaContainer/nehubaContainer.template.html b/src/ui/nehubaContainer/nehubaContainer.template.html index 5d4fd613d2ea4e032bfba9a1deb596a81bcc3392..01662e13500a7fd8d5f2704f6f63670ab213db75 100644 --- a/src/ui/nehubaContainer/nehubaContainer.template.html +++ b/src/ui/nehubaContainer/nehubaContainer.template.html @@ -10,20 +10,20 @@ <current-layout *ngIf="viewerLoaded" class="position-absolute w-100 h-100 d-block pe-none"> <div class="w-100 h-100 position-relative" cell-i> - <ng-content *ngTemplateOutlet="overlayi"></ng-content> + <ng-content *ngTemplateOutlet="ngPanelOverlayTmpl; context: { panelIndex: 0 }"></ng-content> </div> <div class="w-100 h-100 position-relative" cell-ii> - <ng-content *ngTemplateOutlet="overlayii"></ng-content> + <ng-content *ngTemplateOutlet="ngPanelOverlayTmpl; context: { panelIndex: 1 }"></ng-content> </div> <div class="w-100 h-100 position-relative" cell-iii> - <ng-content *ngTemplateOutlet="overlayiii"></ng-content> + <ng-content *ngTemplateOutlet="ngPanelOverlayTmpl; context: { panelIndex: 2 }"></ng-content> </div> <div class="w-100 h-100 position-relative" cell-iv> - <ng-content *ngTemplateOutlet="overlayiv"></ng-content> + <ng-content *ngTemplateOutlet="overlayPerspectiveTmpl"></ng-content> </div> </current-layout> -<layout-floating-container *ngIf="viewerLoaded"> +<layout-floating-container *ngIf="viewerLoaded" [zIndex]="5"> <!-- StatusCard container--> <div class="status-card-container muted-7"> @@ -65,84 +65,48 @@ </ng-template> <!-- overlay templates --> -<!-- inserted using ngTemplateOutlet --> -<ng-template #overlayi> - <layout-floating-container pos00 landmarkContainer> - <nehuba-2dlandmark-unit *ngFor="let spatialData of (selectedPtLandmarks$ | async)" - (mouseenter)="handleMouseEnterLandmark(spatialData)" - (mouseleave)="handleMouseLeaveLandmark(spatialData)" - [highlight]="spatialData.highlight ? spatialData.highlight : false" - [fasClass]="spatialData.type === 'userLandmark' ? 'fa-chevron-down' : 'fa-map-marker'" - [positionX]="getPositionX(0,spatialData)" - [positionY]="getPositionY(0,spatialData)" - [positionZ]="getPositionZ(0,spatialData)"> - </nehuba-2dlandmark-unit> + +<!-- perspective view tmpl --> +<ng-template #overlayPerspectiveTmpl> + <layout-floating-container landmarkContainer> <!-- maximise/minimise button --> - <maximise-panel-button - (click)="toggleMaximiseMinimise(0)" - [ngClass]="{ onHover: (panelOrder$ | async | reorderPanelIndexPipe : ( hoveredPanelIndices$ | async )) === 0 }" - [touch-side-class]="0" - class="pe-all"> - </maximise-panel-button> + <ng-container *ngTemplateOutlet="panelCtrlTmpl; context: { panelIndex: 3, visible: (panelOrder$ | async | reorderPanelIndexPipe : ( hoveredPanelIndices$ | async )) === 3 }"> + </ng-container> - <div *ngIf="sliceViewLoading0$ | async" class="loadingIndicator"> - <div class="spinnerAnimationCircle"> + <div *ngIf="perspectiveViewLoading$ | async" class="loadingIndicator"> + <div class="spinnerAnimationCircle"></div> + <div perspectiveLoadingText> + {{ perspectiveViewLoading$ | async }} </div> </div> </layout-floating-container> </ng-template> -<ng-template #overlayii> - <layout-floating-container pos01 landmarkContainer> - <nehuba-2dlandmark-unit *ngFor="let spatialData of (selectedPtLandmarks$ | async)" - (mouseenter)="handleMouseEnterLandmark(spatialData)" - (mouseleave)="handleMouseLeaveLandmark(spatialData)" - [highlight]="spatialData.highlight ? spatialData.highlight : false" - [fasClass]="spatialData.type === 'userLandmark' ? 'fa-chevron-down' : 'fa-map-marker'" - [positionX]="getPositionX(1,spatialData)" - [positionY]="getPositionY(1,spatialData)" - [positionZ]="getPositionZ(1,spatialData)"> - </nehuba-2dlandmark-unit> - - <!-- maximise/minimise button --> - <maximise-panel-button - (click)="toggleMaximiseMinimise(1)" - [ngClass]="{ onHover: (panelOrder$ | async | reorderPanelIndexPipe : ( hoveredPanelIndices$ | async )) === 1 }" - [touch-side-class]="1" - class="pe-all"> - </maximise-panel-button> +<!-- slice view overlay tmpl --> +<ng-template #ngPanelOverlayTmpl let-panelIndex="panelIndex"> - <div *ngIf="sliceViewLoading1$ | async" class="loadingIndicator"> - <div class="spinnerAnimationCircle"> + <!-- nb this slice view is not suitable for perspective view! --> + <layout-floating-container *ngIf="panelIndex < 3" landmarkContainer> - </div> - </div> - </layout-floating-container> -</ng-template> + <!-- only show landmarks in slice views --> -<ng-template #overlayiii> - <layout-floating-container pos10 landmarkContainer> <nehuba-2dlandmark-unit *ngFor="let spatialData of (selectedPtLandmarks$ | async)" (mouseenter)="handleMouseEnterLandmark(spatialData)" (mouseleave)="handleMouseLeaveLandmark(spatialData)" [highlight]="spatialData.highlight ? spatialData.highlight : false" [fasClass]="spatialData.type === 'userLandmark' ? 'fa-chevron-down' : 'fa-map-marker'" - [positionX]="getPositionX(2,spatialData)" - [positionY]="getPositionY(2,spatialData)" - [positionZ]="getPositionZ(2,spatialData)"> + [positionX]="getPositionX(panelIndex, spatialData)" + [positionY]="getPositionY(panelIndex, spatialData)" + [positionZ]="getPositionZ(panelIndex, spatialData)"> </nehuba-2dlandmark-unit> <!-- maximise/minimise button --> - <maximise-panel-button - (click)="toggleMaximiseMinimise(2)" - [ngClass]="{ onHover: (panelOrder$ | async | reorderPanelIndexPipe : ( hoveredPanelIndices$ | async )) === 2 }" - [touch-side-class]="2" - class="pe-all"> - </maximise-panel-button> + <ng-container *ngTemplateOutlet="panelCtrlTmpl; context: { panelIndex: panelIndex, visible: (panelOrder$ | async | reorderPanelIndexPipe : ( hoveredPanelIndices$ | async )) === panelIndex }"> + </ng-container> - <div *ngIf="sliceViewLoading2$ | async" class="loadingIndicator"> + <div *ngIf="(sliceViewLoadingMain$ | async)[panelIndex]" class="loadingIndicator"> <div class="spinnerAnimationCircle"> </div> @@ -150,23 +114,30 @@ </layout-floating-container> </ng-template> -<ng-template #overlayiv> - <layout-floating-container pos11 landmarkContainer> +<!-- panel control template --> +<ng-template #panelCtrlTmpl let-panelIndex="panelIndex" let-visible="visible"> + + <div class="opacity-crossfade pe-all overlay-btn-container" + [ngClass]="{ onHover: visible }"> + + <!-- factor < 1.0 === zoom in --> + <button mat-icon-button color="primary" + (click)="zoomNgView(panelIndex, 0.9)" + [attr.aria-label]="ARIA_LABEL_ZOOM_IN"> + <i class="fas fa-search-plus"></i> + </button> + + <!-- factor > 1.0 === zoom out --> + <button mat-icon-button color="primary" + (click)="zoomNgView(panelIndex, 1.1)" + [attr.aria-label]="ARIA_LABEL_ZOOM_OUT"> + <i class="fas fa-search-minus"></i> + </button> - <!-- maximise/minimise button --> <maximise-panel-button - (click)="toggleMaximiseMinimise(3)" - [ngClass]="{ onHover: (panelOrder$ | async | reorderPanelIndexPipe : ( hoveredPanelIndices$ | async )) === 3 }" - [touch-side-class]="3" - class="pe-all"> + (click)="toggleMaximiseMinimise(panelIndex)" + [touch-side-class]="panelIndex"> </maximise-panel-button> - - <div *ngIf="perspectiveViewLoading$ | async" class="loadingIndicator"> - <div class="spinnerAnimationCircle"></div> + </div> - <div perspectiveLoadingText> - {{ perspectiveViewLoading$ | async }} - </div> - </div> - </layout-floating-container> </ng-template> diff --git a/src/ui/searchSideNav/searchSideNav.component.spec.ts b/src/ui/searchSideNav/searchSideNav.component.spec.ts index cb7d73c8762e5563526ee66839e877b2106f6460..95dd9421966970e7504bf6d85f6e9562485d53c1 100644 --- a/src/ui/searchSideNav/searchSideNav.component.spec.ts +++ b/src/ui/searchSideNav/searchSideNav.component.spec.ts @@ -1,16 +1,11 @@ import { async, TestBed } from '@angular/core/testing' import { AngularMaterialModule } from '../../ui/sharedModules/angularMaterial.module' -// import { UIModule } from '../ui.module' import { SearchSideNav } from './searchSideNav.component' import { provideMockStore } from '@ngrx/store/testing' -// import { defaultRootState } from 'src/services/stateStore.service' -import { By } from '@angular/platform-browser' -import { CdkFixedSizeVirtualScroll } from '@angular/cdk/scrolling' import { COLIN, JUBRAIN_COLIN_CH123_LEFT, JUBRAIN_COLIN_CH123_RIGHT, JUBRAIN_COLIN, HttpMockRequestInterceptor } from 'spec/util' import { HTTP_INTERCEPTORS } from '@angular/common/http' import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core' -import { UtilModule } from 'src/util/util.module' -// import { ViewerStateController } from '../viewerStateController/viewerStateCFull/viewerState.component' +import { UtilModule } from 'src/util' import { TemplateParcellationHasMoreInfo } from 'src/util/pipes/templateParcellationHasMoreInfo.pipe' import { AppendtooltipTextPipe } from 'src/util/pipes/appendTooltipText.pipe' import { BinSavedRegionsSelectionPipe } from '../viewerStateController/viewerState.pipes' diff --git a/src/ui/ui.module.ts b/src/ui/ui.module.ts index b58d1b66231b6966622ac15e7fd9f6227a310053..a112f1863088c36bff712de6cdc72ae49b7a7498 100644 --- a/src/ui/ui.module.ts +++ b/src/ui/ui.module.ts @@ -29,7 +29,7 @@ import { AppendtooltipTextPipe } from "src/util/pipes/appendTooltipText.pipe"; import { FlatmapArrayPipe } from "src/util/pipes/flatMapArray.pipe"; import { GetFileExtension } from "src/util/pipes/getFileExt.pipe"; import { GetFilenamePipe } from "src/util/pipes/getFilename.pipe"; -import { UtilModule } from "src/util/util.module"; +import { UtilModule } from "src/util"; import { DownloadDirective } from "../util/directives/download.directive"; import { SpatialLandmarksToDataBrowserItemPipe } from "../util/pipes/spatialLandmarksToDatabrowserItem.pipe"; import { ConfigComponent } from './config/config.component' diff --git a/src/ui/viewerStateController/regionSearch/regionSearch.component.ts b/src/ui/viewerStateController/regionSearch/regionSearch.component.ts index 0c33aa73b4406e23c3053c188d314a0979ea3e4a..eb73aa54adbbd2cd1a40dd6a66f74af18501950e 100644 --- a/src/ui/viewerStateController/regionSearch/regionSearch.component.ts +++ b/src/ui/viewerStateController/regionSearch/regionSearch.component.ts @@ -3,7 +3,6 @@ import { FormControl } from "@angular/forms"; import { select, Store } from "@ngrx/store"; import { combineLatest, Observable } from "rxjs"; import { debounceTime, distinctUntilChanged, filter, map, shareReplay, startWith, take, tap } from "rxjs/operators"; -import { AtlasViewerConstantsServices } from "src/atlasViewer/atlasViewer.constantService.service"; import { VIEWER_STATE_ACTION_TYPES } from "src/services/effect/effect"; import { ADD_TO_REGIONS_SELECTION_WITH_IDS, CHANGE_NAVIGATION, SELECT_REGIONS } from "src/services/state/viewerState.store"; import { generateLabelIndexId, getMultiNgIdsRegionsLabelIndexMap, IavRootStoreInterface } from "src/services/stateStore.service"; @@ -11,6 +10,7 @@ import { VIEWERSTATE_CONTROLLER_ACTION_TYPES } from "../viewerState.base"; import { LoggingService } from "src/logging"; import { MatDialog } from "@angular/material/dialog"; import { MatAutocompleteSelectedEvent } from "@angular/material/autocomplete"; +import { PureContantService } from "src/util"; const filterRegionBasedOnText = searchTerm => region => region.name.toLowerCase().includes(searchTerm.toLowerCase()) || (region.relatedAreas && region.relatedAreas.some(relatedArea => relatedArea.name && relatedArea.name.toLowerCase().includes(searchTerm.toLowerCase()))) @@ -44,11 +44,11 @@ export class RegionTextSearchAutocomplete { constructor( private store$: Store<IavRootStoreInterface>, private dialog: MatDialog, - private constantService: AtlasViewerConstantsServices, + private pureConstantService: PureContantService, private log: LoggingService ) { - this.useMobileUI$ = this.constantService.useMobileUI$ + this.useMobileUI$ = this.pureConstantService.useTouchUI$ const viewerState$ = this.store$.pipe( select('viewerState'), diff --git a/src/util/constants.ts b/src/util/constants.ts index 0a0b993c6cfa54b69687f37479bf4ea704ace688..91a7cc447b197bc7d90160e9932eca612487d41b 100644 --- a/src/util/constants.ts +++ b/src/util/constants.ts @@ -106,3 +106,11 @@ export const PMAP_DEFAULT_CONFIG = { lowThreshold: 0.05, removeBg: true } + +export const compareLandmarksChanged: (prevLandmarks: any[], newLandmarks: any[]) => boolean = (prevLandmarks: any[], newLandmarks: any[]) => { + return prevLandmarks.every(lm => typeof lm.name !== 'undefined') && + newLandmarks.every(lm => typeof lm.name !== 'undefined') && + prevLandmarks.length === newLandmarks.length +} + +export const CYCLE_PANEL_MESSAGE = `[spacebar] to cycle through views` diff --git a/src/util/index.ts b/src/util/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..c2a2a8efd93d7369454b862d11509288af3c9994 --- /dev/null +++ b/src/util/index.ts @@ -0,0 +1,2 @@ +export { UtilModule } from './util.module' +export { PureContantService } from './pureConstant.service' diff --git a/src/util/pureConstant.service.ts b/src/util/pureConstant.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..ff91b97cc921743853eb2d3052feaa77c9fdffd0 --- /dev/null +++ b/src/util/pureConstant.service.ts @@ -0,0 +1,23 @@ +import { Injectable } from "@angular/core"; +import { Store, createSelector, select } from "@ngrx/store"; +import { Observable } from "rxjs"; +import { VIEWER_CONFIG_FEATURE_KEY, IViewerConfigState } from "src/services/state/viewerConfig.store.helper"; +import { shareReplay } from "rxjs/operators"; + +@Injectable({ + providedIn: 'root' +}) + +export class PureContantService{ + public useTouchUI$: Observable<boolean> + private useTouchUiSelector = createSelector( + state => state[VIEWER_CONFIG_FEATURE_KEY], + (state: IViewerConfigState) => state.useMobileUI + ) + constructor(private store: Store<any>){ + this.useTouchUI$ = this.store.pipe( + select(this.useTouchUiSelector), + shareReplay(1) + ) + } +}