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)
+ )
+ }
+}