diff --git a/src/atlasViewer/atlasViewer.component.ts b/src/atlasViewer/atlasViewer.component.ts index f58775aa9923e70c4e83ee5496e3cb2c21705335..e62861864810e27237ce38c9fec1dae40f19aeaa 100644 --- a/src/atlasViewer/atlasViewer.component.ts +++ b/src/atlasViewer/atlasViewer.component.ts @@ -1,6 +1,6 @@ import { Component, HostBinding, ViewChild, ViewContainerRef, OnDestroy, OnInit, TemplateRef, AfterViewInit, ElementRef, Renderer2 } from "@angular/core"; import { Store, select, ActionsSubject } from "@ngrx/store"; -import { ViewerStateInterface, isDefined, FETCHED_SPATIAL_DATA, UPDATE_SPATIAL_DATA, TOGGLE_SIDE_PANEL, safeFilter, UIStateInterface, OPEN_SIDE_PANEL, CLOSE_SIDE_PANEL } from "../services/stateStore.service"; +import { ViewerStateInterface, isDefined, FETCHED_SPATIAL_DATA, UPDATE_SPATIAL_DATA, TOGGLE_SIDE_PANEL, safeFilter, OPEN_SIDE_PANEL, CLOSE_SIDE_PANEL } from "../services/stateStore.service"; import { Observable, Subscription, combineLatest, interval, merge, of, fromEvent } from "rxjs"; import { map, filter, distinctUntilChanged, delay, concatMap, debounceTime, withLatestFrom, switchMap, takeUntil, scan, takeLast } from "rxjs/operators"; import { AtlasViewerDataService } from "./atlasViewer.dataService.service"; @@ -17,7 +17,7 @@ import { DatabrowserService } from "src/ui/databrowserModule/databrowser.service import { AGREE_COOKIE, AGREE_KG_TOS, SHOW_KG_TOS } from "src/services/state/uiState.store"; import { TabsetComponent } from "ngx-bootstrap/tabs"; import { LocalFileService } from "src/services/localFile.service"; -import { MatDialog, MatDialogRef } from "@angular/material"; +import { MatDialog, MatDialogRef, MatSnackBar, MatSnackBarRef } from "@angular/material"; /** * TODO @@ -71,6 +71,9 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit { public selectedRegions$: Observable<any[]> public selectedPOI$ : Observable<any[]> private showHelp$: Observable<any> + + private snackbarRef: MatSnackBarRef<any> + public snackbarMessage$: Observable<string> public dedicatedView$: Observable<string | null> public onhoverSegments$: Observable<string[]> @@ -108,9 +111,15 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit { private databrowserService: DatabrowserService, private dispatcher$: ActionsSubject, private rd: Renderer2, - public localFileService: LocalFileService + public localFileService: LocalFileService, + private snackbar: MatSnackBar ) { + this.snackbarMessage$ = this.store.pipe( + select('uiState'), + select("snackbarMessage") + ) + /** * TODO deprecated */ @@ -267,6 +276,25 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit { }) } + this.subscriptions.push( + this.snackbarMessage$.pipe( + // angular material issue + // see https://github.com/angular/angular/issues/15634 + // and https://github.com/angular/components/issues/11357 + delay(0), + ).subscribe(messageSymbol => { + this.snackbarRef && this.snackbarRef.dismiss() + + if (!messageSymbol) return + + // https://stackoverflow.com/a/48191056/6059235 + const message = messageSymbol.toString().slice(7, -1) + this.snackbarRef = this.snackbar.open(message, 'Dismiss', { + duration: 5000 + }) + }) + ) + this.subscriptions.push( this.showHelp$.subscribe(() => { this.helpDialogRef = this.matDialog.open(this.helpComponent, { diff --git a/src/atlasViewer/atlasViewer.constantService.service.ts b/src/atlasViewer/atlasViewer.constantService.service.ts index 7b9026aec1fed0984ebc59361fdc0c8be26423d1..eaf6726edd26e175da90580dac24a77e1f5d2ba6 100644 --- a/src/atlasViewer/atlasViewer.constantService.service.ts +++ b/src/atlasViewer/atlasViewer.constantService.service.ts @@ -4,7 +4,7 @@ import { ViewerStateInterface } from "../services/stateStore.service"; import { Subject, Observable } from "rxjs"; import { ACTION_TYPES, ViewerConfiguration } from 'src/services/state/viewerConfig.store' import { map, shareReplay, filter } from "rxjs/operators"; -import { MatSnackBar } from "@angular/material"; +import { SNACKBAR_MESSAGE } from "src/services/state/uiState.store"; export const CM_THRESHOLD = `0.05` export const CM_MATLAB_JET = `float r;if( x < 0.7 ){r = 4.0 * x - 1.5;} else {r = -4.0 * x + 4.5;}float g;if (x < 0.5) {g = 4.0 * x - 0.5;} else {g = -4.0 * x + 3.5;}float b;if (x < 0.3) {b = 4.0 * x + 0.5;} else {b = -4.0 * x + 2.5;}float a = 1.0;` @@ -241,8 +241,7 @@ Interactive atlas viewer requires **webgl2.0**, and the \`EXT_color_buffer_float private repoUrl = `https://github.com/HumanBrainProject/interactive-viewer` constructor( - private store : Store<ViewerStateInterface>, - private snackbar: MatSnackBar + private store : Store<ViewerStateInterface> ){ const ua = window && window.navigator && window.navigator.userAgent @@ -274,10 +273,13 @@ Interactive atlas viewer requires **webgl2.0**, and the \`EXT_color_buffer_float } catchError(e: Error | string){ - this.snackbar.open(e.toString(), 'Dismiss', { - duration: 3000 + this.store.dispatch({ + type: SNACKBAR_MESSAGE, + snackbarMessage: e.toString() }) } + + public cyclePanelMessage: string = `[spacebar] to cycle through views` } const parseURLToElement = (url:string):HTMLElement=>{ diff --git a/src/main.module.ts b/src/main.module.ts index ad4bd2ea47da47dd16e7e149f1d0f0c07f507951..4e9e7854e6e871b415982336706d6af6fd7d34cc 100644 --- a/src/main.module.ts +++ b/src/main.module.ts @@ -48,6 +48,7 @@ import { DialogComponent } from "./components/dialog/dialog.component"; import { ViewerStateControllerUseEffect } from "./ui/viewerStateController/viewerState.useEffect"; import { ConfirmDialogComponent } from "./components/confirmDialog/confirmDialog.component"; import { ViewerStateUseEffect } from "./services/state/viewerState.store"; +import { NgViewerUseEffect } from "./services/state/ngViewerState.store"; import 'hammerjs' @@ -69,6 +70,7 @@ import 'hammerjs' UserConfigStateUseEffect, ViewerStateControllerUseEffect, ViewerStateUseEffect, + NgViewerUseEffect ]), StoreModule.forRoot({ pluginState, diff --git a/src/services/localFile.service.ts b/src/services/localFile.service.ts index d38450012e3d7a8e4ea680f483c27c52a4ea4013..8ef82668dd4ec7e237a5e1e2e6de43d8b4983dac 100644 --- a/src/services/localFile.service.ts +++ b/src/services/localFile.service.ts @@ -1,6 +1,8 @@ import { Injectable } from "@angular/core"; import { MatSnackBar } from "@angular/material"; import { DatabrowserService } from "src/ui/databrowserModule/databrowser.service"; +import { Store } from "@ngrx/store"; +import { SNACKBAR_MESSAGE } from "./state/uiState.store"; /** * experimental service handling local user files such as nifti and gifti @@ -15,8 +17,8 @@ export class LocalFileService { private supportedExtSet = new Set(SUPPORTED_EXT) constructor( - private snackBar: MatSnackBar, - private dbService: DatabrowserService + private dbService: DatabrowserService , + private store: Store<any> ){ } @@ -38,10 +40,10 @@ export class LocalFileService { } } } catch (e) { - this.snackBar.open(e, `Dismiss`, { - duration: 5000 + this.store.dispatch({ + type: SNACKBAR_MESSAGE, + snackbarMessage: `Opening local NIFTI error: ${e.toString()}` }) - console.error(e) } } @@ -76,8 +78,9 @@ export class LocalFileService { } private showLocalWarning() { - this.snackBar.open(`Warning: sharing URL will not share the loaded local file`, 'Dismiss', { - duration: 5000 + this.store.dispatch({ + type: SNACKBAR_MESSAGE, + snackbarMessage: `Warning: sharing URL will not share the loaded local file` }) } } diff --git a/src/services/state/ngViewerState.store.ts b/src/services/state/ngViewerState.store.ts index 289489d7e549bd4266aac0a57ffd67c877aa2804..4464aa288cd5605334ccf261e12f4b5dc8457cdb 100644 --- a/src/services/state/ngViewerState.store.ts +++ b/src/services/state/ngViewerState.store.ts @@ -1,4 +1,10 @@ -import { Action } from '@ngrx/store' +import { Action, Store, select } from '@ngrx/store' +import { Injectable, OnDestroy } from '@angular/core'; +import { Observable, combineLatest, fromEvent, Subscription } from 'rxjs'; +import { Effect, Actions, ofType } from '@ngrx/effects'; +import { withLatestFrom, map, distinctUntilChanged, scan, shareReplay, filter, mapTo, tap, delay } from 'rxjs/operators'; +import { AtlasViewerConstantsServices } from 'src/atlasViewer/atlasViewer.constantService.service'; +import { SNACKBAR_MESSAGE } from './uiState.store'; export const FOUR_PANEL = 'FOUR_PANEL' export const V_ONE_THREE = 'V_ONE_THREE' @@ -104,6 +110,136 @@ export function ngViewerState(prevState:NgViewerStateInterface = defaultState, a } } +@Injectable({ + providedIn: 'root' +}) + +export class NgViewerUseEffect implements OnDestroy{ + @Effect() + public toggleMaximiseMode$: Observable<any> + + @Effect() + public toggleMaximiseOrder$: Observable<any> + + @Effect() + public toggleMaximiseCycleMessage$: Observable<any> + + @Effect() + public cycleViews$: Observable<any> + + @Effect() + public spacebarListener$: Observable<any> + + private panelOrder$: Observable<string> + private panelMode$: Observable<string> + + private subscriptions: Subscription[] = [] + + constructor( + private actions: Actions, + private store$: Store<any>, + private constantService: AtlasViewerConstantsServices + ){ + const toggleMaxmimise$ = this.actions.pipe( + ofType(ACTION_TYPES.TOGGLE_MAXIMISE), + shareReplay(1) + ) + + this.panelOrder$ = this.store$.pipe( + select('ngViewerState'), + select('panelOrder'), + distinctUntilChanged(), + ) + + this.panelMode$ = this.store$.pipe( + select('ngViewerState'), + select('panelMode'), + distinctUntilChanged(), + ) + + this.cycleViews$ = this.actions.pipe( + ofType(ACTION_TYPES.CYCLE_VIEWS), + withLatestFrom(this.panelOrder$), + map(([_, panelOrder]) => { + return { + type: ACTION_TYPES.SET_PANEL_ORDER, + payload: { + panelOrder: [...panelOrder.slice(1), ...panelOrder.slice(0,1)].join('') + } + } + }) + ) + + this.toggleMaximiseOrder$ = toggleMaxmimise$.pipe( + withLatestFrom( + combineLatest( + this.panelOrder$.pipe( + scan((acc, curr: string) => [curr, ...acc.slice(0,1)], []), + ), + this.panelMode$ + ) + ), + map(([action, [panelOrders, panelMode]]) => { + const { payload } = action as NgViewerAction + const { index = 0 } = payload + + const panelOrder = panelMode === SINGLE_PANEL && !!panelOrders[1] + ? panelOrders[1] + : [...panelOrders[0].slice(index), ...panelOrders[0].slice(0, index)].join('') + return { + type: ACTION_TYPES.SET_PANEL_ORDER, + payload: { + panelOrder + } + } + }) + ) + + this.toggleMaximiseMode$ = toggleMaxmimise$.pipe( + withLatestFrom(this.panelMode$.pipe( + scan((acc, curr: string) => [curr, ...acc.slice(0,1)], []) + )), + map(([ _, panelModes ]) => { + return { + type: ACTION_TYPES.SWITCH_PANEL_MODE, + payload: { + panelMode: panelModes[0] === SINGLE_PANEL + ? (panelModes[1] || FOUR_PANEL) + : SINGLE_PANEL + } + } + }) + ) + + this.toggleMaximiseCycleMessage$ = this.toggleMaximiseMode$.pipe( + filter(() => !this.constantService.mobile), + filter(({ payload }) => payload.panelMode && payload.panelMode === SINGLE_PANEL), + mapTo({ + type: SNACKBAR_MESSAGE, + snackbarMessage: this.constantService.cyclePanelMessage + }) + ) + + this.spacebarListener$ = combineLatest( + fromEvent(document.body, 'keydown', { capture: true }).pipe( + filter((ev: KeyboardEvent) => ev.key === ' ') + ), + this.panelMode$ + ).pipe( + filter(([_ , panelMode]) => panelMode === SINGLE_PANEL), + mapTo({ + type: ACTION_TYPES.CYCLE_VIEWS + }) + ) + } + + ngOnDestroy(){ + while(this.subscriptions.length > 0) { + this.subscriptions.pop().unsubscribe() + } + } +} + export const ADD_NG_LAYER = 'ADD_NG_LAYER' export const REMOVE_NG_LAYER = 'REMOVE_NG_LAYER' export const SHOW_NG_LAYER = 'SHOW_NG_LAYER' @@ -122,7 +258,10 @@ interface NgLayerInterface{ const ACTION_TYPES = { SWITCH_PANEL_MODE: 'SWITCH_PANEL_MODE', - SET_PANEL_ORDER: 'SET_PANEL_ORDER' + SET_PANEL_ORDER: 'SET_PANEL_ORDER', + + TOGGLE_MAXIMISE: 'TOGGLE_MAXIMISE', + CYCLE_VIEWS: 'CYCLE_VIEWS' } export const SUPPORTED_PANEL_MODES = [ diff --git a/src/services/state/uiState.store.ts b/src/services/state/uiState.store.ts index fadd5b87fcc506e504ff011a429675648d85c303..09639a50a5c50bbb9bab8f8f2d2fefcdecd09005 100644 --- a/src/services/state/uiState.store.ts +++ b/src/services/state/uiState.store.ts @@ -10,6 +10,8 @@ const defaultState : UIStateInterface = { focusedSidePanel: null, sidePanelOpen: false, + snackbarMessage: null, + /** * replace with server side logic (?) */ @@ -35,6 +37,15 @@ export function uiState(state:UIStateInterface = defaultState,action:UIAction){ ...state, mouseOverLandmark : action.landmark } + case SNACKBAR_MESSAGE: + const { snackbarMessage } = action + /** + * Need to use symbol here, or repeated snackbarMessage will not trigger new event + */ + return { + ...state, + snackbarMessage: Symbol(snackbarMessage) + } /** * TODO deprecated * remove ASAP @@ -89,6 +100,8 @@ export interface UIStateInterface{ mouseOverLandmark : any focusedSidePanel : string | null + snackbarMessage: Symbol + agreedCookies: boolean agreedKgTos: boolean } @@ -102,7 +115,8 @@ export interface UIAction extends Action{ name: string } segment: any | null - }[] + }[], + snackbarMessage: string } export const MOUSE_OVER_SEGMENT = `MOUSE_OVER_SEGMENT` @@ -115,4 +129,6 @@ export const OPEN_SIDE_PANEL = `OPEN_SIDE_PANEL` export const AGREE_COOKIE = `AGREE_COOKIE` export const AGREE_KG_TOS = `AGREE_KG_TOS` -export const SHOW_KG_TOS = `SHOW_KG_TOS` \ No newline at end of file +export const SHOW_KG_TOS = `SHOW_KG_TOS` + +export const SNACKBAR_MESSAGE = `SNACKBAR_MESSAGE` \ No newline at end of file diff --git a/src/ui/config/config.component.ts b/src/ui/config/config.component.ts index 5d7d25e44e0905c6ee932d202994684842ed8ef6..8f5083ee1affacc11e13e910888e1ec04ecc7be2 100644 --- a/src/ui/config/config.component.ts +++ b/src/ui/config/config.component.ts @@ -1,14 +1,16 @@ import { Component, OnInit, OnDestroy } from '@angular/core' import { Store, select } from '@ngrx/store'; import { ViewerConfiguration, ACTION_TYPES } from 'src/services/state/viewerConfig.store' -import { Observable, Subscription } from 'rxjs'; -import { map, distinctUntilChanged, startWith, shareReplay } from 'rxjs/operators'; +import { Observable, Subscription, combineLatest } from 'rxjs'; +import { map, distinctUntilChanged, startWith, debounceTime } from 'rxjs/operators'; import { MatSlideToggleChange, MatSliderChange } from '@angular/material'; import { NG_VIEWER_ACTION_TYPES, SUPPORTED_PANEL_MODES } from 'src/services/state/ngViewerState.store'; +import { isIdentityQuat } from '../nehubaContainer/util'; const GPU_TOOLTIP = `GPU TOOLTIP` const ANIMATION_TOOLTIP = `ANIMATION_TOOLTIP` -const ROOT_TEXT_ORDER = ['Coronal', 'Sagittal', 'Axial', '3D'] +const ROOT_TEXT_ORDER : [string, string, string, string] = ['Coronal', 'Sagittal', 'Axial', '3D'] +const OBLIQUE_ROOT_TEXT_ORDER : [string, string, string, string] = ['Slice View 1', 'Slice View 2', 'Slice View 3', '3D'] @Component({ selector: 'config-component', @@ -41,6 +43,8 @@ export class ConfigComponent implements OnInit, OnDestroy{ private panelOrder$: Observable<string> public panelTexts$: Observable<[string, string, string, string]> + private viewerObliqueRotated$: Observable<boolean> + constructor(private store: Store<ViewerConfiguration>) { this.gpuLimit$ = this.store.pipe( select('viewerConfigState'), @@ -65,9 +69,24 @@ export class ConfigComponent implements OnInit, OnDestroy{ select('panelOrder') ) - this.panelTexts$ = this.panelOrder$.pipe( - map(string => string.split('').map(s => Number(s))), - map(arr => arr.map(idx => ROOT_TEXT_ORDER[idx]) as [string, string, string, string]) + this.viewerObliqueRotated$ = this.store.pipe( + select('viewerState'), + select('navigation'), + map(navigation => (navigation && navigation.orientation) || [0, 0, 0, 1]), + debounceTime(100), + map(isIdentityQuat), + map(flag => !flag), + distinctUntilChanged(), + ) + + this.panelTexts$ = combineLatest( + this.panelOrder$.pipe( + map(string => string.split('').map(s => Number(s))), + ), + this.viewerObliqueRotated$ + ).pipe( + map(([arr, isObliqueRotated]) => arr.map(idx => (isObliqueRotated ? OBLIQUE_ROOT_TEXT_ORDER : ROOT_TEXT_ORDER)[idx]) as [string, string, string, string]), + startWith(ROOT_TEXT_ORDER) ) } diff --git a/src/ui/config/config.template.html b/src/ui/config/config.template.html index 20927120dd97ec7f77e783ae3fcabfbcdc6243b6..91f0dde64b0b2dc1d442f4bdacc616c284ad1237 100644 --- a/src/ui/config/config.template.html +++ b/src/ui/config/config.template.html @@ -106,8 +106,10 @@ </layout-four-panel> </button> + <!-- temporarily disabling 1-3 layout --> + <!-- horizontal 1 3 card --> - <button + <!-- <button class="m-2 p-2" mat-flat-button (click)="usePanelMode(supportedPanelModes[1])" @@ -118,10 +120,10 @@ <div class="border w-100 h-100" cell-iii></div> <div class="border w-100 h-100" cell-iv></div> </layout-horizontal-one-three> - </button> + </button> --> <!-- vertical 1 3 card --> - <button + <!-- <button class="m-2 p-2" mat-flat-button (click)="usePanelMode(supportedPanelModes[2])" @@ -132,7 +134,7 @@ <div class="border w-100 h-100" cell-iii></div> <div class="border w-100 h-100" cell-iv></div> </layout-vertical-one-three> - </button> + </button> --> <!-- single --> <button diff --git a/src/ui/nehubaContainer/maximisePanelButton/maximisePanelButton.component.ts b/src/ui/nehubaContainer/maximisePanelButton/maximisePanelButton.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..f8cef8eca8656d0d31fa3c1c2dba418c133cd77e --- /dev/null +++ b/src/ui/nehubaContainer/maximisePanelButton/maximisePanelButton.component.ts @@ -0,0 +1,43 @@ +import { Component, Input } from "@angular/core"; +import { Store, select } from "@ngrx/store"; +import { Observable } from "rxjs"; +import { distinctUntilChanged, map } from "rxjs/operators"; +import { SINGLE_PANEL } from "src/services/state/ngViewerState.store"; + +@Component({ + selector: 'maximise-panel-button', + templateUrl: './maximisePanelButton.template.html', + styleUrls: [ + './maximisePanelButton.style.css' + ] +}) + +export class MaximmisePanelButton{ + + @Input() panelIndex: number + + private panelMode$: Observable<string> + private panelOrder$: Observable<string> + + public isMaximised$: Observable<boolean> + + constructor( + private store$: Store<any> + ){ + this.panelMode$ = this.store$.pipe( + select('ngViewerState'), + select('panelMode'), + distinctUntilChanged() + ) + + this.panelOrder$ = this.store$.pipe( + select('ngViewerState'), + select('panelOrder'), + distinctUntilChanged() + ) + + this.isMaximised$ = this.panelMode$.pipe( + map(panelMode => panelMode === SINGLE_PANEL) + ) + } +} \ No newline at end of file diff --git a/src/ui/nehubaContainer/maximisePanelButton/maximisePanelButton.style.css b/src/ui/nehubaContainer/maximisePanelButton/maximisePanelButton.style.css new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/ui/nehubaContainer/maximisePanelButton/maximisePanelButton.template.html b/src/ui/nehubaContainer/maximisePanelButton/maximisePanelButton.template.html new file mode 100644 index 0000000000000000000000000000000000000000..02d7cdeaa40ea2c519cca54fcd0c49d6eab4e365 --- /dev/null +++ b/src/ui/nehubaContainer/maximisePanelButton/maximisePanelButton.template.html @@ -0,0 +1,10 @@ +<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-arrows-alt"></i> +</button> + +<ng-template #expandIconTemplate> + <i class="fas fa-expand-arrows-alt"></i> +</ng-template> \ No newline at end of file diff --git a/src/ui/nehubaContainer/nehubaContainer.component.ts b/src/ui/nehubaContainer/nehubaContainer.component.ts index daf61453f1c3c18e6e0d176d0f72d55f965241c3..241907ecd138682823b992c2e405c0326997729c 100644 --- a/src/ui/nehubaContainer/nehubaContainer.component.ts +++ b/src/ui/nehubaContainer/nehubaContainer.component.ts @@ -1,15 +1,15 @@ import { Component, ViewChild, ViewContainerRef, ComponentFactoryResolver, ComponentFactory, ComponentRef, OnInit, OnDestroy, ElementRef } from "@angular/core"; -import { NehubaViewerUnit } from "./nehubaViewer/nehubaViewer.component"; +import { NehubaViewerUnit, computeDistance } from "./nehubaViewer/nehubaViewer.component"; import { Store, select } from "@ngrx/store"; import { ViewerStateInterface, safeFilter, CHANGE_NAVIGATION, isDefined, USER_LANDMARKS, ADD_NG_LAYER, REMOVE_NG_LAYER, NgViewerStateInterface, MOUSE_OVER_LANDMARK, SELECT_LANDMARKS, Landmark, PointLandmarkGeometry, PlaneLandmarkGeometry, OtherLandmarkGeometry, getNgIds, getMultiNgIdsRegionsLabelIndexMap, generateLabelIndexId, DataEntry } from "../../services/stateStore.service"; -import { Observable, Subscription, fromEvent, combineLatest, merge } from "rxjs"; -import { filter,map, take, scan, debounceTime, distinctUntilChanged, switchMap, skip, withLatestFrom, buffer, tap, switchMapTo, shareReplay, throttleTime, bufferTime, startWith } from "rxjs/operators"; +import { Observable, Subscription, fromEvent, combineLatest, merge, of } from "rxjs"; +import { filter,map, take, scan, debounceTime, distinctUntilChanged, switchMap, skip, withLatestFrom, buffer, tap, switchMapTo, shareReplay, mapTo, takeUntil } from "rxjs/operators"; import { AtlasViewerAPIServices, UserLandmark } from "../../atlasViewer/atlasViewer.apiService.service"; import { timedValues } from "../../util/generator"; import { AtlasViewerConstantsServices } from "../../atlasViewer/atlasViewer.constantService.service"; import { ViewerConfiguration } from "src/services/state/viewerConfig.store"; import { pipeFromArray } from "rxjs/internal/util/pipe"; -import { NEHUBA_READY, H_ONE_THREE, V_ONE_THREE, FOUR_PANEL, SINGLE_PANEL } from "src/services/state/ngViewerState.store"; +import { NEHUBA_READY, H_ONE_THREE, V_ONE_THREE, FOUR_PANEL, SINGLE_PANEL, NG_VIEWER_ACTION_TYPES } from "src/services/state/ngViewerState.store"; import { MOUSE_OVER_SEGMENTS } from "src/services/state/uiState.store"; import { getHorizontalOneThree, getVerticalOneThree, getFourPanel, getSinglePanel } from "./util"; import { SELECT_REGIONS_WITH_ID, NEHUBA_LAYER_CHANGED, VIEWERSTATE_ACTION_TYPES } from "src/services/state/viewerState.store"; @@ -144,9 +144,16 @@ export class NehubaContainer implements OnInit, OnDestroy{ private 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 favDataEntries$: Observable<DataEntry[]> + public hoveredPanelIndices$: Observable<number> + + private ngPanelTouchMove$: Observable<any> + constructor( private constantService : AtlasViewerConstantsServices, private apiService :AtlasViewerAPIServices, @@ -173,22 +180,29 @@ export class NehubaContainer implements OnInit, OnDestroy{ filter(() => isDefined(this.nehubaViewer) && isDefined(this.nehubaViewer.nehubaViewer)) ) + this.panelMode$ = this.store.pipe( + select('ngViewerState'), + select('panelMode'), + distinctUntilChanged(), + shareReplay(1) + ) + + this.panelOrder$ = this.store.pipe( + select('ngViewerState'), + select('panelOrder'), + distinctUntilChanged(), + shareReplay(1), + tap(panelOrder => this.panelOrder = panelOrder) + ) + this.redrawLayout$ = this.store.pipe( select('ngViewerState'), select('nehubaReady'), distinctUntilChanged(), filter(v => !!v), switchMapTo(combineLatest( - this.store.pipe( - select('ngViewerState'), - select('panelMode'), - distinctUntilChanged() - ), - this.store.pipe( - select('ngViewerState'), - select('panelOrder'), - distinctUntilChanged() - ) + this.panelMode$, + this.panelOrder$ )) ) @@ -414,10 +428,20 @@ export class NehubaContainer implements OnInit, OnDestroy{ : false) ) - this.panelMode$ = this.store.pipe( - select('ngViewerState'), - select('panelMode'), - distinctUntilChanged(), + this.ngPanelTouchMove$ = fromEvent(this.elementRef.nativeElement, 'touchstart').pipe( + switchMap((touchStartEv:TouchEvent) => fromEvent(this.elementRef.nativeElement, 'touchmove').pipe( + tap((ev: TouchEvent) => ev.preventDefault()), + scan((acc, curr: TouchEvent) => [curr, ...acc.slice(0,1)], []), + map((touchMoveEvs:TouchEvent[]) => { + return { + touchStartEv, + touchMoveEvs + } + }), + takeUntil(fromEvent(this.elementRef.nativeElement, 'touchend').pipe( + filter((ev: TouchEvent) => ev.touches.length === 0)) + ) + )) ) } @@ -433,8 +457,100 @@ export class NehubaContainer implements OnInit, OnDestroy{ return element } + private findPanelIndex = (panel: HTMLElement) => this.viewPanels.findIndex(p => p === panel) + ngOnInit(){ + // translation on mobile + this.subscriptions.push( + this.ngPanelTouchMove$.pipe( + filter(({ touchMoveEvs }) => touchMoveEvs.length > 1 && (touchMoveEvs as TouchEvent[]).every(ev => ev.touches.length === 1)), + ).subscribe(({ touchMoveEvs, touchStartEv }) => { + + // get deltaX and deltaY of touchmove + const deltaX = touchMoveEvs[1].touches[0].screenX - touchMoveEvs[0].touches[0].screenX + const deltaY = touchMoveEvs[1].touches[0].screenY - touchMoveEvs[0].touches[0].screenY + + // figure out the target of touch start + const panelIdx = this.findPanelIndex(touchStartEv.target as HTMLElement) + + // translate if panelIdx < 3 + if (panelIdx >=0 && panelIdx < 3) { + const { position } = this.nehubaViewer.nehubaViewer.ngviewer.navigationState + const pos = position.spatialCoordinates + window['export_nehuba'].vec3.set(pos, deltaX, deltaY, 0) + window['export_nehuba'].vec3.transformMat4(pos, pos, this.nehubaViewer.viewportToDatas[panelIdx]) + position.changed.dispatch() + } + + // rotate 3D if panelIdx === 3 + + else if (panelIdx === 3) { + const {perspectiveNavigationState} = this.nehubaViewer.nehubaViewer.ngviewer + const { vec3 } = window['export_nehuba'] + perspectiveNavigationState.pose.rotateRelative(vec3.fromValues(0, 1, 0), -deltaX / 4.0 * Math.PI / 180.0) + perspectiveNavigationState.pose.rotateRelative(vec3.fromValues(1, 0, 0), deltaY / 4.0 * Math.PI / 180.0) + this.nehubaViewer.nehubaViewer.ngviewer.perspectiveNavigationState.changed.dispatch() + } + + // this shoudn't happen? + else { + console.warn(`panelIdx not found`) + } + }) + ) + + // perspective reorientation on mobile + this.subscriptions.push( + this.ngPanelTouchMove$.pipe( + filter(({ touchMoveEvs }) => touchMoveEvs.length > 1 && (touchMoveEvs as TouchEvent[]).every(ev => ev.touches.length === 2)), + ).subscribe(({ touchMoveEvs, touchStartEv }) => { + + const d1 = computeDistance( + [touchMoveEvs[1].touches[0].screenX, touchMoveEvs[1].touches[0].screenY], + [touchMoveEvs[1].touches[1].screenX, touchMoveEvs[1].touches[1].screenY] + ) + const d2 = computeDistance( + [touchMoveEvs[0].touches[0].screenX, touchMoveEvs[0].touches[0].screenY], + [touchMoveEvs[0].touches[1].screenX, touchMoveEvs[0].touches[1].screenY] + ) + const factor = d1/d2 + + // figure out the target of touch start + const panelIdx = this.findPanelIndex(touchStartEv.target as HTMLElement) + + // zoom slice view if slice + if (panelIdx >=0 && panelIdx < 3) { + this.nehubaViewer.nehubaViewer.ngviewer.navigationState.zoomBy(factor) + } + + // zoom perspective view if on perspective + else if (panelIdx === 3) { + const { minZoom = null, maxZoom = null } = (this.selectedTemplate.nehubaConfig + && this.selectedTemplate.nehubaConfig.layout + && this.selectedTemplate.nehubaConfig.layout.useNehubaPerspective + && this.selectedTemplate.nehubaConfig.layout.useNehubaPerspective.restrictZoomLevel) + || {} + + const { zoomFactor } = this.nehubaViewer.nehubaViewer.ngviewer.perspectiveNavigationState + if (!!minZoom && zoomFactor.value * factor < minZoom) return + if (!!maxZoom && zoomFactor.value * factor > maxZoom) return + zoomFactor.zoomBy(factor) + } + }) + ) + + this.hoveredPanelIndices$ = fromEvent(this.elementRef.nativeElement, 'mouseover').pipe( + switchMap((ev:MouseEvent) => merge( + of(this.findPanelIndex(ev.target as HTMLElement)), + fromEvent(this.elementRef.nativeElement, 'mouseout').pipe( + mapTo(null) + ) + )), + debounceTime(20), + shareReplay(1) + ) + /* each time a new viewer is initialised, take the first event to get the translation function */ this.subscriptions.push( this.newViewer$.pipe( @@ -803,6 +919,15 @@ export class NehubaContainer implements OnInit, OnDestroy{ this.subscriptions.forEach(s=>s.unsubscribe()) } + toggleMaximiseMinimise(index: number){ + this.store.dispatch({ + type: NG_VIEWER_ACTION_TYPES.TOGGLE_MAXIMISE, + payload: { + index + } + }) + } + public tunableMobileProperties = ['Oblique Rotate X', 'Oblique Rotate Y', 'Oblique Rotate Z'] public selectedProp = null diff --git a/src/ui/nehubaContainer/nehubaContainer.style.css b/src/ui/nehubaContainer/nehubaContainer.style.css index 57a6ad380921978025fdb9692967d716743d5eab..160d701048c08d585d06629e40fbeb3ca00dec3e 100644 --- a/src/ui/nehubaContainer/nehubaContainer.style.css +++ b/src/ui/nehubaContainer/nehubaContainer.style.css @@ -176,4 +176,37 @@ div#scratch-pad { right: 0; bottom: 0; -} \ No newline at end of file +} + +maximise-panel-button +{ + transition: opacity 170ms ease-in-out, + transform 250ms ease-in-out; + + position: absolute; + top: 0; + right: 0; +} + +/* if not mobile, then show on hover */ +maximise-panel-button +{ + opacity: 0.0; + pointer-events: none; +} + +maximise-panel-button.onHover, +maximise-panel-button:hover, +:host-context([ismobile="true"]) maximise-panel-button +{ + opacity: 1.0 !important; + pointer-events: all !important; +} + + +maximise-panel-button.touch-top.touch-right.onHover, +maximise-panel-button.touch-top.touch-right:hover, +:host-context(:not([ismobile="true"])) maximise-panel-button.touch-top.touch-right +{ + transform: translate(-3em, 3em) +} diff --git a/src/ui/nehubaContainer/nehubaContainer.template.html b/src/ui/nehubaContainer/nehubaContainer.template.html index f9bf6b9798bff3e47b2178ce6fa08c750c5ff9ba..f8b623cfe8929c49e12c92f13afe4b6cde1a6777 100644 --- a/src/ui/nehubaContainer/nehubaContainer.template.html +++ b/src/ui/nehubaContainer/nehubaContainer.template.html @@ -7,7 +7,7 @@ <!-- spatial landmarks overlay --> <!-- loading indicator --> -<current-layout class="position-absolute w-100 h-100 d-block pe-none"> +<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> </div> @@ -49,7 +49,6 @@ </layout-floating-container> <div id="scratch-pad"> - </div> <!-- mobile nub, allowing for ooblique slicing in mobile --> @@ -97,6 +96,13 @@ [positionZ]="getPositionZ(0,spatialData)"> </nehuba-2dlandmark-unit> + <!-- 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> + <div *ngIf="sliceViewLoading0$ | async" class="loadingIndicator"> <div class="spinnerAnimationCircle"> @@ -115,6 +121,13 @@ [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> + <div *ngIf="sliceViewLoading1$ | async" class="loadingIndicator"> <div class="spinnerAnimationCircle"> @@ -133,6 +146,13 @@ [positionZ]="getPositionZ(2,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> + <div *ngIf="sliceViewLoading2$ | async" class="loadingIndicator"> <div class="spinnerAnimationCircle"> @@ -143,8 +163,17 @@ <ng-template #overlayiv> <layout-floating-container pos11 landmarkContainer> + + <!-- maximise/minimise button --> + <maximise-panel-button + (click)="toggleMaximiseMinimise(3)" + [ngClass]="{ onHover: (panelOrder$ | async | reorderPanelIndexPipe : ( hoveredPanelIndices$ | async )) === 3 }" + [touch-side-class]="3 " class="pe-all"> + </maximise-panel-button> + <div *ngIf="perspectiveViewLoading$ | async" class="loadingIndicator"> <div class="spinnerAnimationCircle"></div> + <div perspectiveLoadingText> {{ perspectiveViewLoading$ | async }} </div> diff --git a/src/ui/nehubaContainer/nehubaViewer/nehubaViewer.component.ts b/src/ui/nehubaContainer/nehubaViewer/nehubaViewer.component.ts index 75311ee37a95cec91e1437cf79cfa8fca8ca1b4a..632afe95980a6da36b072b5a8c40fa86f020b981 100644 --- a/src/ui/nehubaContainer/nehubaViewer/nehubaViewer.component.ts +++ b/src/ui/nehubaContainer/nehubaViewer/nehubaViewer.component.ts @@ -77,8 +77,6 @@ export class NehubaViewerUnit implements OnDestroy{ ondestroySubscriptions: Subscription[] = [] - touchStart$ : Observable<any> - constructor( private rd: Renderer2, public elementRef:ElementRef, @@ -352,7 +350,7 @@ export class NehubaViewerUnit implements OnDestroy{ public mouseOverSegment: number | null public mouseOverLayer: {name:string,url:string}| null - private viewportToDatas : [any, any, any] = [null, null, null] + public viewportToDatas : [any, any, any] = [null, null, null] public getNgHash : () => string = () => window['export_nehuba'] ? window['export_nehuba'].getNgHash() @@ -394,6 +392,11 @@ export class NehubaViewerUnit implements OnDestroy{ this.onDestroyCb.push(() => window['nehubaViewer'] = null) + /** + * TODO + * move this to nehubaContainer + * do NOT use position logic to determine idx + */ this.ondestroySubscriptions.push( // fromEvent(this.elementRef.nativeElement, 'viewportToData').pipe( // ...takeOnePipe @@ -405,89 +408,6 @@ export class NehubaViewerUnit implements OnDestroy{ [0,1,2].forEach(idx => this.viewportToDatas[idx] = events[idx].detail.viewportToData) }) ) - - this.touchStart$ = fromEvent(this.elementRef.nativeElement, 'touchstart').pipe( - map((ev:TouchEvent) => { - const srcElement : HTMLElement = ev.srcElement || (ev as any).originalTarget - return { - startPos: [ev.touches[0].screenX, ev.touches[0].screenY], - elementId: identifySrcElement(srcElement), - srcElement, - event: ev - } - }) - ) - - this.ondestroySubscriptions.push( - - this.touchStart$.pipe( - switchMap(({startPos, elementId, srcElement}) => fromEvent(this.elementRef.nativeElement,'touchmove').pipe( - map((ev: TouchEvent) => (ev.stopPropagation(), ev.preventDefault(), ev)), - filter((ev:TouchEvent) => ev.touches.length === 1), - map((event:TouchEvent) => ({ - startPos, - event, - elementId, - srcElement - })), - scan((acc,ev:any) => { - return acc.length < 2 - ? acc.concat(ev) - : acc.slice(1).concat(ev) - },[]), - map(double => ({ - elementId: double[0].elementId, - deltaX: double.length === 1 - ? null // startPos[0] - (double[0].event as TouchEvent).touches[0].screenX - : double.length === 2 - ? (double[0].event as TouchEvent).touches[0].screenX - (double[1].event as TouchEvent).touches[0].screenX - : null, - deltaY: double.length === 1 - ? null // startPos[0] - (double[0].event as TouchEvent).touches[0].screenY - : double.length === 2 - ? (double[0].event as TouchEvent).touches[0].screenY - (double[1].event as TouchEvent).touches[0].screenY - : null - })), - takeUntil(fromEvent(this.elementRef.nativeElement, 'touchend').pipe(filter((ev: TouchEvent) => ev.touches.length === 0))) - )) - ).subscribe(({ elementId, deltaX, deltaY }) => { - if(deltaX === null || deltaY === null){ - console.warn('deltax/y is null') - return - } - if(elementId === 0 || elementId === 1 || elementId === 2){ - const {position} = this.nehubaViewer.ngviewer.navigationState - const pos = position.spatialCoordinates - window['export_nehuba'].vec3.set(pos, deltaX, deltaY, 0) - window['export_nehuba'].vec3.transformMat4(pos, pos, this.viewportToDatas[elementId]) - position.changed.dispatch() - }else if(elementId === 3){ - const {perspectiveNavigationState} = this.nehubaViewer.ngviewer - perspectiveNavigationState.pose.rotateRelative(this.vec3([0, 1, 0]), -deltaX / 4.0 * Math.PI / 180.0) - perspectiveNavigationState.pose.rotateRelative(this.vec3([1, 0, 0]), deltaY / 4.0 * Math.PI / 180.0) - this.nehubaViewer.ngviewer.perspectiveNavigationState.changed.dispatch() - } - }) - ) - - this.ondestroySubscriptions.push( - this.touchStart$.pipe( - switchMap(() => - fromEvent(this.elementRef.nativeElement, 'touchmove').pipe( - takeWhile((ev:TouchEvent) => ev.touches.length === 2), - map((ev:TouchEvent) => computeDistance( - [ev.touches[0].screenX, ev.touches[0].screenY], - [ev.touches[1].screenX, ev.touches[1].screenY] - )), - scan((acc, curr:number) => acc.length < 2 - ? acc.concat(curr) - : acc.slice(1).concat(curr), []), - filter(dist => dist.length > 1), - map(dist => dist[0] / dist[1]) - )) - ).subscribe(factor => - this.nehubaViewer.ngviewer.navigationState.zoomBy(factor)) - ) } ngOnDestroy(){ diff --git a/src/ui/nehubaContainer/reorderPanelIndex.pipe.ts b/src/ui/nehubaContainer/reorderPanelIndex.pipe.ts new file mode 100644 index 0000000000000000000000000000000000000000..3077fb30211614615da0e98120c22576aa07b209 --- /dev/null +++ b/src/ui/nehubaContainer/reorderPanelIndex.pipe.ts @@ -0,0 +1,14 @@ +import { Pipe, PipeTransform } from "@angular/core"; + + +@Pipe({ + name: 'reorderPanelIndexPipe' +}) + +export class ReorderPanelIndexPipe implements PipeTransform{ + public transform(panelOrder: string, uncorrectedIndex: number){ + return uncorrectedIndex === null + ? null + : panelOrder.indexOf(uncorrectedIndex.toString()) + } +} \ No newline at end of file diff --git a/src/ui/nehubaContainer/touchSideClass.directive.ts b/src/ui/nehubaContainer/touchSideClass.directive.ts new file mode 100644 index 0000000000000000000000000000000000000000..8d19fd677f226ba580c442633380feba694e91fb --- /dev/null +++ b/src/ui/nehubaContainer/touchSideClass.directive.ts @@ -0,0 +1,49 @@ +import { Directive, Input, ElementRef, OnDestroy, OnInit } from "@angular/core"; +import { Store, select } from "@ngrx/store"; +import { Observable, Subscription } from "rxjs"; +import { distinctUntilChanged, tap } from "rxjs/operators"; +import { removeTouchSideClasses, addTouchSideClasses } from "./util"; + +@Directive({ + selector: '[touch-side-class]', + exportAs: 'touchSideClass' +}) + +export class TouchSideClass implements OnDestroy, OnInit{ + @Input('touch-side-class') + public panelNativeIndex: number + + public panelMode: string + private panelMode$: Observable<string> + + private subscriptions: Subscription[] = [] + + constructor( + private store$: Store<any>, + private el: ElementRef + ){ + + this.panelMode$ = this.store$.pipe( + select('ngViewerState'), + select('panelMode'), + distinctUntilChanged(), + tap(mode => this.panelMode = mode) + ) + } + + ngOnInit(){ + this.subscriptions.push( + + this.panelMode$.subscribe(panelMode => { + removeTouchSideClasses(this.el.nativeElement) + addTouchSideClasses(this.el.nativeElement, this.panelNativeIndex, panelMode) + }) + ) + } + + ngOnDestroy(){ + while(this.subscriptions.length > 0) { + this.subscriptions.pop().unsubscribe() + } + } +} \ No newline at end of file diff --git a/src/ui/nehubaContainer/util.ts b/src/ui/nehubaContainer/util.ts index d16c41ed10083b19ec7d49783e3db04e8a6cc1a3..d3d1f78e8b7e1761a51f611b3a77b635e19343f9 100644 --- a/src/ui/nehubaContainer/util.ts +++ b/src/ui/nehubaContainer/util.ts @@ -1,3 +1,5 @@ +import { FOUR_PANEL, SINGLE_PANEL, H_ONE_THREE, V_ONE_THREE } from "src/services/state/ngViewerState.store"; + const flexContCmnCls = ['w-100', 'h-100', 'd-flex', 'justify-content-center', 'align-items-stretch'] const makeRow = (...els:HTMLElement[]) => { @@ -25,10 +27,54 @@ const washPanels = (panels: [HTMLElement, HTMLElement, HTMLElement, HTMLElement] return panels } +const top = true +const left = true +const right = true +const bottom = true + +const mapModeIdxClass = new Map() + +mapModeIdxClass.set(FOUR_PANEL, new Map([ + [0, { top, left }], + [1, { top, right }], + [2, { bottom, left }], + [3, { right, bottom }] +])) + +mapModeIdxClass.set(SINGLE_PANEL, new Map([ + [0, { top, left, right, bottom }], + [1, {}], + [2, {}], + [3, {}] +])) + +mapModeIdxClass.set(H_ONE_THREE, new Map([ + [0, { top, left, bottom }], + [1, { top, right }], + [2, { right }], + [3, { bottom, right }] +])) + +mapModeIdxClass.set(V_ONE_THREE, new Map([ + [0, { top, left, right }], + [1, { bottom, left }], + [2, { bottom }], + [3, { bottom, right }] +])) + +export const removeTouchSideClasses = (panel: HTMLElement) => { + panel.classList.remove( + `touch-top`, + `touch-left`, + `touch-right`, + `touch-bottom`) + return panel +} + /** * gives a clue of the approximate location of the panel, allowing position of checkboxes/scale bar to be placed in unobtrustive places */ -const panelTouchSide = (panel: HTMLElement, { top, left, right, bottom }: any) => { +export const panelTouchSide = (panel: HTMLElement, { top, left, right, bottom }: any) => { if (top) panel.classList.add(`touch-top`) if (left) panel.classList.add(`touch-left`) if (right) panel.classList.add(`touch-right`) @@ -36,18 +82,23 @@ const panelTouchSide = (panel: HTMLElement, { top, left, right, bottom }: any) = return panel } -const top = true -const left = true -const right = true -const bottom = true +export const addTouchSideClasses = (panel: HTMLElement, actualOrderIndex: number, panelMode: string) => { + + if (actualOrderIndex < 0) return panel + + const mapIdxClass = mapModeIdxClass.get(panelMode) + if (!mapIdxClass) return panel + + const classArg = mapIdxClass.get(actualOrderIndex) + if (!classArg) return panel + + return panelTouchSide(panel, classArg) +} export const getHorizontalOneThree = (panels:[HTMLElement, HTMLElement, HTMLElement, HTMLElement]) => { washPanels(panels) - panelTouchSide(panels[0], { top, left, bottom }) - panelTouchSide(panels[1], { top, right }) - panelTouchSide(panels[2], { right }) - panelTouchSide(panels[3], { right, bottom }) + panels.forEach((panel, idx) => addTouchSideClasses(panel, idx, H_ONE_THREE)) const majorContainer = makeCol(panels[0]) const minorContainer = makeCol(panels[1], panels[2], panels[3]) @@ -61,10 +112,7 @@ export const getHorizontalOneThree = (panels:[HTMLElement, HTMLElement, HTMLElem export const getVerticalOneThree = (panels:[HTMLElement, HTMLElement, HTMLElement, HTMLElement]) => { washPanels(panels) - panelTouchSide(panels[0], { top, left, right }) - panelTouchSide(panels[1], { bottom, left }) - panelTouchSide(panels[2], { bottom }) - panelTouchSide(panels[3], { right, bottom }) + panels.forEach((panel, idx) => addTouchSideClasses(panel, idx, V_ONE_THREE)) const majorContainer = makeRow(panels[0]) const minorContainer = makeRow(panels[1], panels[2], panels[3]) @@ -79,10 +127,7 @@ export const getVerticalOneThree = (panels:[HTMLElement, HTMLElement, HTMLElemen export const getFourPanel = (panels:[HTMLElement, HTMLElement, HTMLElement, HTMLElement]) => { washPanels(panels) - panelTouchSide(panels[0], { top, left }) - panelTouchSide(panels[1], { top, right }) - panelTouchSide(panels[2], { bottom, left }) - panelTouchSide(panels[3], { right, bottom }) + panels.forEach((panel, idx) => addTouchSideClasses(panel, idx, FOUR_PANEL)) const majorContainer = makeRow(panels[0], panels[1]) const minorContainer = makeRow(panels[2], panels[3]) @@ -96,7 +141,7 @@ export const getFourPanel = (panels:[HTMLElement, HTMLElement, HTMLElement, HTML export const getSinglePanel = (panels:[HTMLElement, HTMLElement, HTMLElement, HTMLElement]) => { washPanels(panels) - panelTouchSide(panels[0], { top, left, bottom, right }) + panels.forEach((panel, idx) => addTouchSideClasses(panel, idx, SINGLE_PANEL)) const majorContainer = makeRow(panels[0]) const minorContainer = makeRow(panels[1], panels[2], panels[3]) @@ -106,5 +151,11 @@ export const getSinglePanel = (panels:[HTMLElement, HTMLElement, HTMLElement, HT minorContainer.className = '' minorContainer.style.height = '0px' - return makeCol(majorContainer, minorContainer) -} \ No newline at end of file + return makeRow(majorContainer, minorContainer) +} + +export const isIdentityQuat = ori => Math.abs(ori[0]) < 1e-6 + && Math.abs(ori[1]) < 1e-6 + && Math.abs(ori[2]) < 1e-6 + && Math.abs(ori[3] - 1) < 1e-6 + \ No newline at end of file diff --git a/src/ui/ui.module.ts b/src/ui/ui.module.ts index 075cf4f3c831b324c6b9f9ff33568af7b83cd35f..e0582147e81c8dd9274aa6fda046f1e2eb0d868e 100644 --- a/src/ui/ui.module.ts +++ b/src/ui/ui.module.ts @@ -61,6 +61,9 @@ import { PluginBtnFabColorPipe } from "src/util/pipes/pluginBtnFabColor.pipe"; import { KgSearchBtnColorPipe } from "src/util/pipes/kgSearchBtnColor.pipe"; import { TemplateParcellationHasMoreInfo } from "src/util/pipes/templateParcellationHasMoreInfo.pipe"; import { HumanReadableFileSizePipe } from "src/util/pipes/humanReadableFileSize.pipe"; +import { MaximmisePanelButton } from "./nehubaContainer/maximisePanelButton/maximisePanelButton.component"; +import { TouchSideClass } from "./nehubaContainer/touchSideClass.directive"; +import { ReorderPanelIndexPipe } from "./nehubaContainer/reorderPanelIndex.pipe"; @NgModule({ @@ -104,6 +107,7 @@ import { HumanReadableFileSizePipe } from "src/util/pipes/humanReadableFileSize. CurrentLayout, ViewerStateController, RegionTextSearchAutocomplete, + MaximmisePanelButton, /* pipes */ GroupDatasetByRegion, @@ -129,10 +133,12 @@ import { HumanReadableFileSizePipe } from "src/util/pipes/humanReadableFileSize. SavedRegionsSelectionBtnDisabledPipe, TemplateParcellationHasMoreInfo, HumanReadableFileSizePipe, + ReorderPanelIndexPipe, /* directive */ DownloadDirective, - ShowToastDirective + ShowToastDirective, + TouchSideClass ], entryComponents : [