diff --git a/src/atlasViewer/atlasViewer.component.ts b/src/atlasViewer/atlasViewer.component.ts index 59d7751b8340a5bfa5004369d4f3adeebadfae4a..014c570dc56d6da85efc41d9d315c8eca5e79925 100644 --- a/src/atlasViewer/atlasViewer.component.ts +++ b/src/atlasViewer/atlasViewer.component.ts @@ -33,6 +33,7 @@ export class AtlasViewer implements OnDestroy, OnInit { @ViewChild('databrowser', { read: ElementRef }) databrowser: ElementRef @ViewChild('floatingMouseContextualContainer', { read: ViewContainerRef }) floatingMouseContextualContainer: ViewContainerRef @ViewChild('helpComponent', {read: TemplateRef}) helpComponent : TemplateRef<any> + @ViewChild('viewerConfigComponent', {read: TemplateRef}) viewerConfigComponent : TemplateRef<any> @ViewChild(LayoutMainSide) layoutMainSide: LayoutMainSide @ViewChild(NehubaContainer) nehubaContainer: NehubaContainer @@ -49,7 +50,8 @@ export class AtlasViewer implements OnDestroy, OnInit { private newViewer$: Observable<any> public selectedPOI$ : Observable<any[]> - public showHelp$: Observable<any> + private showHelp$: Observable<any> + private showConfig$: Observable<any> public dedicatedView$: Observable<string | null> public onhoverSegment$: Observable<string> @@ -95,6 +97,10 @@ export class AtlasViewer implements OnDestroy, OnInit { debounceTime(170) ) + this.showConfig$ = this.constantsService.showConfigSubject$.pipe( + debounceTime(170) + ) + this.selectedPOI$ = combineLatest( this.store.pipe( select('viewerState'), @@ -182,6 +188,17 @@ export class AtlasViewer implements OnDestroy, OnInit { ) ) + this.subscriptions.push( + this.showConfig$.subscribe(() => { + this.modalService.show(ModalUnit, { + initialState: { + title: this.constantsService.showConfigTitle, + template: this.viewerConfigComponent + } + }) + }) + ) + this.subscriptions.push( this.ngLayerNames$.pipe( concatMap(data => this.constantsService.loadExportNehubaPromise.then(data)) diff --git a/src/atlasViewer/atlasViewer.constantService.service.ts b/src/atlasViewer/atlasViewer.constantService.service.ts index cc1e6b0bf5c315b7b927c2caf49ec45cbbc3a2a0..ead53d9e364375cbea6786064a70614751f329ae 100644 --- a/src/atlasViewer/atlasViewer.constantService.service.ts +++ b/src/atlasViewer/atlasViewer.constantService.service.ts @@ -2,7 +2,7 @@ import { Injectable } from "@angular/core"; import { Store } from "@ngrx/store"; import { ViewerStateInterface, Property, FETCHED_METADATA } from "../services/stateStore.service"; import { Subject } from "rxjs"; - +import { ACTION_TYPES, ViewerConfiguration, viewerConfigState } from 'src/services/state/viewerConfig.store' @Injectable({ providedIn : 'root' @@ -137,6 +137,11 @@ Interactive atlas viewer requires **webgl2.0**, and the \`EXT_color_buffer_float */ public toggleMessage: string = 'double click to toggle select' + /** + * Observable for showing config modal + */ + public showConfigSubject$: Subject<null> = new Subject() + public showConfigTitle: String = 'Settings' /** * Observable for showing help modal */ @@ -210,6 +215,18 @@ Interactive atlas viewer requires **webgl2.0**, and the \`EXT_color_buffer_float /* https://stackoverflow.com/a/25394023/6059235 */ this.mobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini|Mobile|mobile|CriOS/i.test(ua) + /** + * set gpu limit if user is on mobile + */ + if (this.mobile) { + this.store.dispatch({ + type: ACTION_TYPES.UPDATE_CONFIG, + config: { + gpuLimit: 2e8 + } as Partial<ViewerConfiguration> + }) + } + const meta = 'res/json/allAggregatedData.json' fetch(meta) diff --git a/src/atlasViewer/atlasViewer.pluginService.service.ts b/src/atlasViewer/atlasViewer.pluginService.service.ts index 390fd9156c82ba4864244d6cd1f71c13cea9fa8f..8bf27a5f3a7dee3cbcda9be7ba4a62b1acee0ee2 100644 --- a/src/atlasViewer/atlasViewer.pluginService.service.ts +++ b/src/atlasViewer/atlasViewer.pluginService.service.ts @@ -1,6 +1,7 @@ import { Injectable, ViewContainerRef, ComponentFactoryResolver, ComponentFactory } from "@angular/core"; import { AtlasViewerDataService } from "./atlasViewer.dataService.service"; -import { isDefined, PluginInitManifestInterface, SET_INIT_PLUGIN } from "../services/stateStore.service"; +import { PluginInitManifestInterface, ACTION_TYPES } from "src/services/state/pluginState.store"; +import { isDefined } from 'src/services/stateStore.service' import { AtlasViewerAPIServices } from "./atlasViewer.apiService.service"; import { PluginUnit } from "./pluginUnit/pluginUnit.component"; import { WidgetServices } from "./widgetUnit/widgetService.service"; @@ -87,7 +88,7 @@ export class PluginServices{ : null handler.setInitManifestUrl = (url) => this.store.dispatch({ - type : SET_INIT_PLUGIN, + type : ACTION_TYPES.SET_INIT_PLUGIN, manifest : { name : plugin.name, initManifestUrl : url diff --git a/src/atlasViewer/atlasViewer.template.html b/src/atlasViewer/atlasViewer.template.html index 85b14f2aff0545c8f5551cb6eb73fa4733dc5da0..fbdb2f07935e84a824861b7b6adcc8ff22b0d2ce 100644 --- a/src/atlasViewer/atlasViewer.template.html +++ b/src/atlasViewer/atlasViewer.template.html @@ -179,7 +179,12 @@ </div> <ng-template #helpComponent> - <help-component></help-component> + <help-component> + </help-component> +</ng-template> +<ng-template #viewerConfigComponent> + <config-component> + </config-component> </ng-template> <markdown-dom minReqMd *ngIf = "!meetsRequirement" [markdown] = "constantsService.minReqMD" > diff --git a/src/atlasViewer/atlasViewer.urlService.service.ts b/src/atlasViewer/atlasViewer.urlService.service.ts index 5aa63426f2d52de25b01b7bcf3d5a2a226d8eec6..52408d9c10662c60503d7c5474475c556bf27728 100644 --- a/src/atlasViewer/atlasViewer.urlService.service.ts +++ b/src/atlasViewer/atlasViewer.urlService.service.ts @@ -1,6 +1,7 @@ import { Injectable } from "@angular/core"; import { Store, select } from "@ngrx/store"; -import { ViewerStateInterface, isDefined, NEWVIEWER, getLabelIndexMap, SELECT_REGIONS, CHANGE_NAVIGATION, LOAD_DEDICATED_LAYER, ADD_NG_LAYER, PluginInitManifestInterface } from "../services/stateStore.service"; +import { ViewerStateInterface, isDefined, NEWVIEWER, getLabelIndexMap, SELECT_REGIONS, CHANGE_NAVIGATION, ADD_NG_LAYER } from "../services/stateStore.service"; +import { PluginInitManifestInterface } from 'src/services/state/pluginState.store' import { Observable,combineLatest } from "rxjs"; import { filter, map, scan, distinctUntilChanged, skipWhile, take } from "rxjs/operators"; import { getActiveColorMapFragmentMain } from "../ui/nehubaContainer/nehubaContainer.component"; diff --git a/src/main.module.ts b/src/main.module.ts index e04add7c374f89020a29e6d011139fd397433b64..65c41e5591377a13780af5e1d0555b8dae0d2939 100644 --- a/src/main.module.ts +++ b/src/main.module.ts @@ -4,7 +4,7 @@ import { UIModule } from "./ui/ui.module"; import { LayoutModule } from "./layouts/layout.module"; import { AtlasViewer } from "./atlasViewer/atlasViewer.component"; import { StoreModule } from "@ngrx/store"; -import { viewerState, dataStore,spatialSearchState,uiState, ngViewerState, pluginState } from "./services/stateStore.service"; +import { viewerState, dataStore,spatialSearchState,uiState, ngViewerState, states } from "./services/stateStore.service"; import { GetNamesPipe } from "./util/pipes/getNames.pipe"; import { CommonModule } from "@angular/common"; import { GetNamePipe } from "./util/pipes/getName.pipe"; @@ -49,7 +49,7 @@ import { FloatingMouseContextualContainerDirective } from "./util/directives/flo spatialSearchState, uiState, ngViewerState, - pluginState + ...states }) ], declarations : [ diff --git a/src/services/state/pluginState.store.ts b/src/services/state/pluginState.store.ts new file mode 100644 index 0000000000000000000000000000000000000000..35cc5dd1dcdfadda1493a7d6edd87e6f8bc7436a --- /dev/null +++ b/src/services/state/pluginState.store.ts @@ -0,0 +1,30 @@ +import { Action } from '@ngrx/store' + + +export interface PluginInitManifestInterface{ + initManifests : Map<string,string|null> +} + +export interface PluginInitManifestActionInterface extends Action{ + manifest: { + name : string, + initManifestUrl : string | null + } +} + +export const ACTION_TYPES = { + SET_INIT_PLUGIN: `SET_INIT_PLUGIN` +} + +export function pluginState(prevState:PluginInitManifestInterface = {initManifests : new Map()}, action:PluginInitManifestActionInterface):PluginInitManifestInterface{ + switch(action.type){ + case ACTION_TYPES.SET_INIT_PLUGIN: + const newMap = new Map(prevState.initManifests) + return { + ...prevState, + initManifests: newMap.set(action.manifest.name, action.manifest.initManifestUrl) + } + default: + return prevState + } +} diff --git a/src/services/state/viewerConfig.store.ts b/src/services/state/viewerConfig.store.ts new file mode 100644 index 0000000000000000000000000000000000000000..631def221573e6bfad560dce0f6d08cb830c99b0 --- /dev/null +++ b/src/services/state/viewerConfig.store.ts @@ -0,0 +1,48 @@ +import { Action } from "@ngrx/store"; + +export interface ViewerConfiguration{ + gpuLimit: number + animation: boolean +} + +interface ViewerConfigurationAction extends Action{ + config: Partial<ViewerConfiguration>, + payload: any +} + +export const CONFIG_CONSTANTS = { + /** + * byets + */ + gpuLimitMin: 1e8, + gpuLimitMax: 1e9 +} + +export const ACTION_TYPES = { + UPDATE_CONFIG: `UPDATE_CONFIG`, + CHANGE_GPU_LIMIT: `CHANGE_GPU_LIMIT` +} + +export function viewerConfigState(prevState:ViewerConfiguration = {gpuLimit: 1e9, animation: true}, action:ViewerConfigurationAction) { + switch (action.type) { + case ACTION_TYPES.UPDATE_CONFIG: + return { + ...prevState, + ...action.config + } + case ACTION_TYPES.CHANGE_GPU_LIMIT: + const newGpuLimit = Math.min( + CONFIG_CONSTANTS.gpuLimitMax, + Math.max( + prevState.gpuLimit + action.payload.delta, + CONFIG_CONSTANTS.gpuLimitMin + )) + + return { + ...prevState, + gpuLimit: newGpuLimit + } + default: + return prevState + } +} \ No newline at end of file diff --git a/src/services/stateStore.service.ts b/src/services/stateStore.service.ts index 52e4df7e6c6db7039ac980ddde421e4a8af56e44..d33ea4df51702ed06d6187db37cd00e15e3f8883 100644 --- a/src/services/stateStore.service.ts +++ b/src/services/stateStore.service.ts @@ -1,6 +1,13 @@ import { Action } from '@ngrx/store' import { filter } from 'rxjs/operators'; import { UserLandmark } from '../atlasViewer/atlasViewer.apiService.service'; +import { pluginState } from './state/pluginState.store' +import { viewerConfigState } from './state/viewerConfig.store' + +export const states = { + pluginState, + viewerConfigState +} export const NEWVIEWER = 'NEWVIEWER' @@ -32,20 +39,10 @@ export const OPEN_SIDE_PANEL = `OPEN_SIDE_PANEL` export const MOUSE_OVER_SEGMENT = `MOUSE_OVER_SEGMENT` export const MOUSE_OVER_LANDMARK = `MOUSE_OVER_LANDMARK` -export const SET_INIT_PLUGIN = `SET_INIT_PLUGIN` export const FETCHED_PLUGIN_MANIFESTS = `FETCHED_PLUGIN_MANIFESTS` export const LAUNCH_PLUGIN = `LAUNCH_PLUGIN` -export interface PluginInitManifestInterface{ - initManifests : Map<string,string|null> -} -export interface PluginInitManifestActionInterface extends Action{ - manifest: { - name : string, - initManifestUrl : string | null - } -} export interface ViewerStateInterface{ fetchedTemplates : any[] @@ -121,39 +118,6 @@ export interface NgViewerAction extends Action{ forceShowSegment : boolean } - -/** - * TODO unused function, remove - */ -const mapLayer = (existingLayer:NgLayerInterface, incomingLayer:NgLayerInterface):NgLayerInterface => { - return incomingLayer.mixability === 'base' - ? existingLayer - : incomingLayer.mixability === 'mixable' - ? existingLayer.mixability === 'nonmixable' - ? Object.assign({}, existingLayer, { - visible : false - } as NgLayerInterface) - : existingLayer - /* incomingLayer.mixability === 'nonmixable' */ - : existingLayer.mixability === 'base' - ? existingLayer - : Object.assign({}, existingLayer, { - visible : false - } as NgLayerInterface) -} - -export function pluginState(prevState:PluginInitManifestInterface = {initManifests : new Map()}, action:PluginInitManifestActionInterface):PluginInitManifestInterface{ - switch(action.type){ - case SET_INIT_PLUGIN: - const newMap = new Map(prevState.initManifests) - return Object.assign({}, prevState, { - initManifests : newMap.set(action.manifest.name, action.manifest.initManifestUrl) - } as PluginInitManifestInterface) - default: - return prevState - } -} - export function ngViewerState(prevState:NgViewerStateInterface = {layers:[], forceShowSegment:null}, action:NgViewerAction):NgViewerStateInterface{ switch(action.type){ case ADD_NG_LAYER: diff --git a/src/ui/banner/banner.component.ts b/src/ui/banner/banner.component.ts index fc75046e2c26bedc769d8f8624e20543b3bf48f5..fde833e0f2aad51685d1d13db292a67ad33bb916 100644 --- a/src/ui/banner/banner.component.ts +++ b/src/ui/banner/banner.component.ts @@ -288,6 +288,10 @@ export class AtlasBanner implements OnDestroy, OnInit { this.constantService.showHelpSubject$.next() } + showConfig() { + this.constantService.showConfigSubject$.next() + } + get toastDuration() { return this.constantService.citationToastDuration } diff --git a/src/ui/banner/banner.template.html b/src/ui/banner/banner.template.html index 7dfc5fb9b87bd2092053e5ab6c5fc170f1aad457..7028ad6406b541413ff62a852eaa928f77a8b478 100644 --- a/src/ui/banner/banner.template.html +++ b/src/ui/banner/banner.template.html @@ -1,15 +1,15 @@ <dropdown-component - (itemSelected) = "selectTemplate($event)" - [activeDisplay] = "displayActiveTemplate" - [selectedItem] = "selectedTemplate" - [inputArray] = "loadedTemplates$ | async | filterNull"> + (itemSelected)="selectTemplate($event)" + [activeDisplay]="displayActiveTemplate" + [selectedItem]="selectedTemplate" + [inputArray]="loadedTemplates$ | async | filterNull"> </dropdown-component> <i - *ngIf = "citationExists(selectedTemplate) && !isMobile" - [toastLength] = "toastDuration" - [showToast] = "citation" + *ngIf="citationExists(selectedTemplate) && !isMobile" + [toastLength]="toastDuration" + [showToast]="citation" class="glyphicon glyphicon-info-sign" #templateCitationAnchor> @@ -18,24 +18,24 @@ Citations for {{ selectedTemplate ? selectedTemplate.name : '' }} </h4> <citations-component - [properties] = "selectedTemplate.properties"> + [properties]="selectedTemplate.properties"> </citations-component> </ng-template> </i> <dropdown-component - *ngIf = "selectedTemplate" - (itemSelected) = "selectParcellation($event)" - [activeDisplay] = "displayActiveParcellation" - [selectedItem] = "selectedParcellation" - [inputArray] = "selectedTemplate.parcellations"> + *ngIf="selectedTemplate" + (itemSelected)="selectParcellation($event)" + [activeDisplay]="displayActiveParcellation" + [selectedItem]="selectedParcellation" + [inputArray]="selectedTemplate.parcellations"> </dropdown-component> <i - *ngIf = "citationExists(selectedParcellation) && !isMobile" - [toastLength] = "toastDuration" - [showToast] = "citation" + *ngIf="citationExists(selectedParcellation) && !isMobile" + [toastLength]="toastDuration" + [showToast]="citation" class="glyphicon glyphicon-info-sign" #parcellationCitationAnchor> <ng-template #citation> @@ -43,49 +43,56 @@ Citations for {{ selectedParcellation ? selectedParcellation.name : '' }} </h4> <citations-component - [properties] = "selectedParcellation.properties"> + [properties]="selectedParcellation.properties"> </citations-component> </ng-template> </i> <div - *ngIf = "selectedTemplate" + *ngIf="selectedTemplate" #searchRegionPopover searchRegionPopover> <input - (keydown.esc) = "showRegionTree = false; $event.target.blur(); searchTerm = ''" - (focus) = "showRegionTree = true" - [value] = "searchTerm" - (input) = "changeSearchTerm($event)" - class = "form-control" + (keydown.esc)="showRegionTree=false; $event.target.blur(); searchTerm=''" + (focus)="showRegionTree=true" + [value]="searchTerm" + (input)="changeSearchTerm($event)" + class="form-control" type="text" placeholder="Regions"/> <div [@showState] - *ngIf = "showRegionTree" + *ngIf="showRegionTree" hideScrollbarContainer> <div treeContainer #treeContainer> <div treeHeader> <span>{{ selectedRegions.length }} {{ selectedRegions.length > 1 ? 'regions' : 'region' }} selected</span> - <span (click) = "clearRegions($event)" *ngIf = "selectedRegions.length > 0" class = "btn btn-link">clear all</span> + <span (click)="clearRegions($event)" *ngIf="selectedRegions.length > 0" class="btn btn-link">clear all</span> </div> <flat-tree-component - [flatTreeViewPort] = "treeContainer" - (treeNodeClick) = "handleClickRegion($event)" - [inputItem] = "aggregatedRegionTree" - [renderNode] = "displayTreeNode.bind(this)" - [searchFilter] = "filterTreeBySearch.bind(this)"> + [flatTreeViewPort]="treeContainer" + (treeNodeClick)="handleClickRegion($event)" + [inputItem]="aggregatedRegionTree" + [renderNode]="displayTreeNode.bind(this)" + [searchFilter]="filterTreeBySearch.bind(this)"> </flat-tree-component> </div> </div> </div> -<div *ngIf = "isMobile" class = "help-container"> - <i (click) = "showHelp()" class="glyphicon glyphicon-question-sign"></i> +<!-- help btn --> +<div *ngIf="isMobile" class="help-container"> + <i (click)="showHelp()" class="glyphicon glyphicon-question-sign"></i> </div> -<i *ngIf = "!isMobile" (click) = "showHelp()" class="glyphicon glyphicon-question-sign"></i> +<i *ngIf="!isMobile" (click)="showHelp()" class="glyphicon glyphicon-question-sign"></i> + +<!-- config btn --> +<div *ngIf="isMobile" class="help-container"> + <i (click)="showConfig()" class="glyphicon glyphicon-cog"></i> +</div> +<i *ngIf="!isMobile" (click)="showConfig()" class="glyphicon glyphicon-cog"></i> diff --git a/src/ui/config/config.component.ts b/src/ui/config/config.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..51769165babb28cb9a41a0e3451df81633b3add1 --- /dev/null +++ b/src/ui/config/config.component.ts @@ -0,0 +1,47 @@ +import { Component } from '@angular/core' +import { Store, select } from '@ngrx/store'; +import { ViewerConfiguration, ACTION_TYPES } from 'src/services/state/viewerConfig.store' +import { Observable } from 'rxjs'; +import { map, distinctUntilChanged } from 'rxjs/operators'; + +@Component({ + selector: 'config-component', + templateUrl: './config.template.html', + styleUrls: [ + './config.style.css' + ] +}) + +export class ConfigComponent{ + + /** + * in MB + */ + public gpuLimit$: Observable<number> + + constructor(private store: Store<ViewerConfiguration>) { + this.gpuLimit$ = this.store.pipe( + select('viewerConfigState'), + map((config:ViewerConfiguration) => config.gpuLimit), + distinctUntilChanged(), + map(v => v / 1e6) + ) + } + + public wheelEvent(ev:WheelEvent) { + const delta = ev.deltaY * -1e5 + this.store.dispatch({ + type: ACTION_TYPES.CHANGE_GPU_LIMIT, + payload: { delta } + }) + } + + public setGpuPreset({value}: {value: number}) { + this.store.dispatch({ + type: ACTION_TYPES.UPDATE_CONFIG, + config: { + gpuLimit: value * 1e6 + } + }) + } +} \ No newline at end of file diff --git a/src/ui/config/config.style.css b/src/ui/config/config.style.css new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/ui/config/config.template.html b/src/ui/config/config.template.html new file mode 100644 index 0000000000000000000000000000000000000000..34b6ef179db65e509e67e9cb300cebd14344e450 --- /dev/null +++ b/src/ui/config/config.template.html @@ -0,0 +1,24 @@ +<div class="input-group"> + <span class="input-group-addon"> + GPU Limit + </span> + <input + (wheel)="wheelEvent($event)" + type="number" + class="form-control" + [value]="gpuLimit$ | async "> + <div class="input-group-btn"> + <div (click)="setGpuPreset({ value: 100 })" class="btn btn-default"> + 100 + </div> + <div (click)="setGpuPreset({ value: 500 })" class="btn btn-default"> + 500 + </div> + <div (click)="setGpuPreset({ value: 1000 })" class="btn btn-default"> + 1000 + </div> + </div> + <span class="input-group-addon"> + MB + </span> +</div> \ No newline at end of file diff --git a/src/ui/nehubaContainer/nehubaContainer.component.ts b/src/ui/nehubaContainer/nehubaContainer.component.ts index f97f445181d8f8f357c72bbcaabf86b5d5310072..8f46c8c7ee11c09a61e2ea9fb8a4f9a81bdc0fc1 100644 --- a/src/ui/nehubaContainer/nehubaContainer.component.ts +++ b/src/ui/nehubaContainer/nehubaContainer.component.ts @@ -8,6 +8,7 @@ import { AtlasViewerAPIServices, UserLandmark } from "../../atlasViewer/atlasVie import { timedValues } from "../../util/generator"; import { AtlasViewerDataService } from "../../atlasViewer/atlasViewer.dataService.service"; import { AtlasViewerConstantsServices } from "../../atlasViewer/atlasViewer.constantService.service"; +import { ViewerConfiguration } from "src/services/state/viewerConfig.store"; @Component({ selector : 'ui-nehuba-container', @@ -29,6 +30,8 @@ export class NehubaContainer implements OnInit, OnDestroy{ public viewerLoaded : boolean = false + private viewerPerformanceConfig$: Observable<ViewerConfiguration> + public sliceViewLoading0$: Observable<boolean> public sliceViewLoading1$: Observable<boolean> public sliceViewLoading2$: Observable<boolean> @@ -81,6 +84,17 @@ export class NehubaContainer implements OnInit, OnDestroy{ private store : Store<ViewerStateInterface>, private elementRef : ElementRef ){ + this.viewerPerformanceConfig$ = this.store.pipe( + select('viewerConfigState'), + /** + * TODO: this is only a bandaid fix. Technically, we should also implement + * logic to take the previously set config to apply oninit + */ + filter(() => isDefined(this.nehubaViewer) && isDefined(this.nehubaViewer.nehubaViewer)), + distinctUntilChanged(), + debounceTime(200) + ) + this.nehubaViewerFactory = this.csf.resolveComponentFactory(NehubaViewerUnit) this.newViewer$ = this.store.pipe( @@ -274,6 +288,12 @@ export class NehubaContainer implements OnInit, OnDestroy{ ngOnInit(){ + this.subscriptions.push( + this.viewerPerformanceConfig$.subscribe(config => { + this.nehubaViewer.applyPerformanceConfig(config) + }) + ) + this.subscriptions.push( this.fetchedSpatialDatasets$.subscribe(datasets => { this.landmarksLabelIndexMap = new Map(datasets.map((v,idx) => [idx, v]) as [number, any][]) @@ -882,6 +902,11 @@ export class NehubaContainer implements OnInit, OnDestroy{ /* extract the animation object */ const { animation, ..._navigation } = navigation + /** + * remove keys that are falsy + */ + Object.keys(_navigation).forEach(key => (!_navigation[key]) && delete _navigation[key]) + if( animation ){ /* animated */ @@ -981,28 +1006,28 @@ export class NehubaContainer implements OnInit, OnDestroy{ return this.selectedTemplate && this.selectedTemplate.properties && this.selectedTemplate.properties.publications && this.selectedTemplate.properties.publications.constructor === Array } - resetNavigation(){ + resetNavigation({rotation: rotationFlag = false, position: positionFlag = false, zoom : zoomFlag = false} : {rotation: boolean, position: boolean, zoom: boolean}){ const initialNgState = this.selectedTemplate.nehubaConfig.dataset.initialNgState const perspectiveZoom = initialNgState ? initialNgState.perspectiveZoom : undefined const perspectiveOrientation = initialNgState ? initialNgState.perspectiveOrientation : undefined - const zoom = initialNgState ? - initialNgState.navigation ? - initialNgState.navigation.zoomFactor : - undefined : - undefined - - const position = initialNgState ? - initialNgState.navigation ? - initialNgState.navigation.pose ? - initialNgState.navigation.pose.position.voxelCoordinates ? - initialNgState.navigation.pose.position.voxelCoordinates : - undefined : - undefined : - undefined : - undefined - - const orientation = [0,0,0,1] + const zoom = (zoomFlag + && initialNgState + && initialNgState.navigation + && initialNgState.navigation.zoomFactor) + || undefined + + const position = (positionFlag + && initialNgState + && initialNgState.navigation + && initialNgState.navigation.pose + && initialNgState.navigation.pose.position.voxelCoordinates + && initialNgState.navigation.pose.position.voxelCoordinates) + || undefined + + const orientation = rotationFlag + ? [0,0,0,1] + : undefined this.store.dispatch({ type : CHANGE_NAVIGATION, diff --git a/src/ui/nehubaContainer/nehubaContainer.template.html b/src/ui/nehubaContainer/nehubaContainer.template.html index 7c945595b5357e1568941e801dd52b10de5d1cd7..3334c087fa64293a2acbfd78fc9bd8994e67ff94 100644 --- a/src/ui/nehubaContainer/nehubaContainer.template.html +++ b/src/ui/nehubaContainer/nehubaContainer.template.html @@ -1,7 +1,7 @@ <ng-template #container> </ng-template> -<ui-splashscreen *ngIf = "!viewerLoaded"> +<ui-splashscreen *ngIf="!viewerLoaded"> </ui-splashscreen> <div landmarkMasterContainer> @@ -11,18 +11,18 @@ pos00 landmarkContainer> <nehuba-2dlandmark-unit - *ngFor = "let spatialData of (selectedPtLandmarks$ | async)" - (mouseenter) = "handleMouseEnterLandmark(spatialData)" - (mouseleave) = "handleMouseLeaveLandmark(spatialData)" - [highlight] = "spatialData.highlight ? spatialData.highlight : false" - [glyphiconClass] = "spatialData.type === 'userLandmark' ? 'glyphicon-chevron-down' : 'glyphicon-map-marker'" - [positionX] = "getPositionX(0,spatialData)" - [positionY] = "getPositionY(0,spatialData)" - [positionZ] = "getPositionZ(0,spatialData)"> + *ngFor="let spatialData of (selectedPtLandmarks$ | async)" + (mouseenter)="handleMouseEnterLandmark(spatialData)" + (mouseleave)="handleMouseLeaveLandmark(spatialData)" + [highlight]="spatialData.highlight ? spatialData.highlight : false" + [glyphiconClass]="spatialData.type === 'userLandmark' ? 'glyphicon-chevron-down' : 'glyphicon-map-marker'" + [positionX]="getPositionX(0,spatialData)" + [positionY]="getPositionY(0,spatialData)" + [positionZ]="getPositionZ(0,spatialData)"> </nehuba-2dlandmark-unit> - <div *ngIf = "sliceViewLoading0$ | async" class = "loadingIndicator"> - <div class = "spinnerAnimationCircle"> + <div *ngIf="sliceViewLoading0$ | async" class="loadingIndicator"> + <div class="spinnerAnimationCircle"> </div> </div> @@ -33,18 +33,18 @@ pos01 landmarkContainer> <nehuba-2dlandmark-unit - *ngFor = "let spatialData of (selectedPtLandmarks$ | async)" - (mouseenter) = "handleMouseEnterLandmark(spatialData)" - (mouseleave) = "handleMouseLeaveLandmark(spatialData)" - [highlight] = "spatialData.highlight ? spatialData.highlight : false" - [glyphiconClass] = "spatialData.type === 'userLandmark' ? 'glyphicon-chevron-down' : 'glyphicon-map-marker'" - [positionX] = "getPositionX(1,spatialData)" - [positionY] = "getPositionY(1,spatialData)" - [positionZ] = "getPositionZ(1,spatialData)"> + *ngFor="let spatialData of (selectedPtLandmarks$ | async)" + (mouseenter)="handleMouseEnterLandmark(spatialData)" + (mouseleave)="handleMouseLeaveLandmark(spatialData)" + [highlight]="spatialData.highlight ? spatialData.highlight : false" + [glyphiconClass]="spatialData.type === 'userLandmark' ? 'glyphicon-chevron-down' : 'glyphicon-map-marker'" + [positionX]="getPositionX(1,spatialData)" + [positionY]="getPositionY(1,spatialData)" + [positionZ]="getPositionZ(1,spatialData)"> </nehuba-2dlandmark-unit> - <div *ngIf = "sliceViewLoading1$ | async" class = "loadingIndicator"> - <div class = "spinnerAnimationCircle"> + <div *ngIf="sliceViewLoading1$ | async" class="loadingIndicator"> + <div class="spinnerAnimationCircle"> </div> </div> @@ -55,18 +55,18 @@ pos10 landmarkContainer> <nehuba-2dlandmark-unit - *ngFor = "let spatialData of (selectedPtLandmarks$ | async)" - (mouseenter) = "handleMouseEnterLandmark(spatialData)" - (mouseleave) = "handleMouseLeaveLandmark(spatialData)" - [highlight] = "spatialData.highlight ? spatialData.highlight : false" - [glyphiconClass] = "spatialData.type === 'userLandmark' ? 'glyphicon-chevron-down' : 'glyphicon-map-marker'" - [positionX] = "getPositionX(2,spatialData)" - [positionY] = "getPositionY(2,spatialData)" - [positionZ] = "getPositionZ(2,spatialData)"> + *ngFor="let spatialData of (selectedPtLandmarks$ | async)" + (mouseenter)="handleMouseEnterLandmark(spatialData)" + (mouseleave)="handleMouseLeaveLandmark(spatialData)" + [highlight]="spatialData.highlight ? spatialData.highlight : false" + [glyphiconClass]="spatialData.type === 'userLandmark' ? 'glyphicon-chevron-down' : 'glyphicon-map-marker'" + [positionX]="getPositionX(2,spatialData)" + [positionY]="getPositionY(2,spatialData)" + [positionZ]="getPositionZ(2,spatialData)"> </nehuba-2dlandmark-unit> - <div *ngIf = "sliceViewLoading2$ | async" class = "loadingIndicator"> - <div class = "spinnerAnimationCircle"> + <div *ngIf="sliceViewLoading2$ | async" class="loadingIndicator"> + <div class="spinnerAnimationCircle"> </div> </div> @@ -76,8 +76,8 @@ <layout-floating-container pos11 landmarkContainer> - <div *ngIf = "perspectiveViewLoading$ | async" class = "loadingIndicator"> - <div class = "spinnerAnimationCircle"></div> + <div *ngIf="perspectiveViewLoading$ | async" class="loadingIndicator"> + <div class="spinnerAnimationCircle"></div> <div perspectiveLoadingText> {{ perspectiveViewLoading$ | async }} </div> @@ -86,40 +86,63 @@ </div> </div> -<layout-floating-container *ngIf = "viewerLoaded"> +<layout-floating-container *ngIf="viewerLoaded"> <!-- TODO export status card to its own container --> <div statusCard> - <hr *ngIf = "showCitation && !isMobile" /> + <hr *ngIf="showCitation && !isMobile" /> <div linksContainer> - <a href = "#" (click)="$event.preventDefault();statusPanelRealSpace = !statusPanelRealSpace"> - {{statusPanelRealSpace ? 'real space' : 'voxel space'}} + <span> + reset: + </span> + <a + href="#" + (click)="$event.preventDefault();resetNavigation({position:true})"> + position </a> - - <a href = "#" (click) = "$event.preventDefault();resetNavigation()"> - reset navigation + + <a + href="#" + (click)="$event.preventDefault();resetNavigation({rotation:true})"> + rotation </a> + + <a + href="#" + (click)="$event.preventDefault();resetNavigation({zoom:true})"> + zoom + </a> + + <br /> + <span> + space: + </span> + + <a href="#" (click)="$event.preventDefault();statusPanelRealSpace=!statusPanelRealSpace"> + {{statusPanelRealSpace ? 'physical' : 'voxel'}} + </a> + </div> <br /> <div textContainer> <small>Navigation: </small> <input - (keydown.enter) = "textNavigateTo(navigateInput.value)" - (keydown.tab) = "textNavigateTo(navigateInput.value)" - [ngModel] = "navigationValue()" - spellcheck = "false" + (keydown.enter)="textNavigateTo(navigateInput.value)" + (keydown.tab)="textNavigateTo(navigateInput.value)" + [ngModel]="navigationValue()" + spellcheck="false" #navigateInput navigateInput/> <br /> - <small *ngIf = "!isMobile">Mouse: </small> - <small *ngIf = "!isMobile"> + <small *ngIf="!isMobile">Mouse: </small> + <small *ngIf="!isMobile"> {{ mouseCoord }} </small> - <br *ngIf = "!isMobile" /> + <br *ngIf="!isMobile" /> <small onHoverSegment> {{ onHoverSegmentName$ | async }} </small> @@ -132,9 +155,9 @@ </div> <mobile-overlay - *ngIf = "isMobile && viewerLoaded" - [tunableProperties] = "tunableMobileProperties" - (deltaValue) = "handleMobileOverlayEvent($event)"> + *ngIf="isMobile && viewerLoaded" + [tunableProperties]="tunableMobileProperties" + (deltaValue)="handleMobileOverlayEvent($event)"> <div mobileObliqueGuide guide> <div> <i class="glyphicon glyphicon-resize-vertical"></i> oblique mode diff --git a/src/ui/nehubaContainer/nehubaViewer/nehubaViewer.component.ts b/src/ui/nehubaContainer/nehubaViewer/nehubaViewer.component.ts index b20c2edb87a12a326546cb1258408859e0dca424..518cf2516ad16a335c1d404385627f4af89153df 100644 --- a/src/ui/nehubaContainer/nehubaViewer/nehubaViewer.component.ts +++ b/src/ui/nehubaContainer/nehubaViewer/nehubaViewer.component.ts @@ -6,6 +6,7 @@ import { AtlasWorkerService } from "../../../atlasViewer/atlasViewer.workerServi import { buffer, map, filter, debounceTime, take, takeUntil, scan, switchMap, takeWhile } from "rxjs/operators"; import { AtlasViewerConstantsServices } from "../../../atlasViewer/atlasViewer.constantService.service"; import { takeOnePipe, identifySrcElement } from "../nehubaContainer.component"; +import { ViewerConfiguration } from "src/services/state/viewerConfig.store"; @Component({ templateUrl : './nehubaViewer.template.html', @@ -232,6 +233,15 @@ export class NehubaViewerUnit implements OnDestroy{ : 0 } + public applyPerformanceConfig ({ gpuLimit }: Partial<ViewerConfiguration>) { + if (gpuLimit && this.nehubaViewer) { + const limit = this.nehubaViewer.ngviewer.state.children.get('gpuMemoryLimit') + if (limit && limit.restoreState) { + limit.restoreState(gpuLimit) + } + } + } + /* required to check if correct landmarks are loaded */ private _templateId : string get templateId(){ diff --git a/src/ui/ui.module.ts b/src/ui/ui.module.ts index 0d8dc8b6cc986088ce7ad1420f68d46772452c57..e744af52480e14be9a7e051cc199042cb7568482 100644 --- a/src/ui/ui.module.ts +++ b/src/ui/ui.module.ts @@ -40,6 +40,7 @@ import { MobileOverlay } from "./nehubaContainer/mobileOverlay/mobileOverlay.com import { FilterNullPipe } from "../util/pipes/filterNull.pipe"; import { ShowToastDirective } from "../util/directives/showToast.directive"; import { HelpComponent } from "./help/help.component"; +import { ConfigComponent } from './config/config.component' @NgModule({ @@ -73,6 +74,7 @@ import { HelpComponent } from "./help/help.component"; TemplateParcellationCitationsContainer, MobileOverlay, HelpComponent, + ConfigComponent, /* pipes */ GroupDatasetByRegion, @@ -114,7 +116,8 @@ import { HelpComponent } from "./help/help.component"; DatasetViewerComponent, TemplateParcellationCitationsContainer, MobileOverlay, - HelpComponent + HelpComponent, + ConfigComponent ] })