From 22e7cd79fc5f1724df085238c64a0f4d633233d3 Mon Sep 17 00:00:00 2001 From: Xiao Gui <xgui3783@gmail.com> Date: Mon, 25 Mar 2019 09:27:51 +0100 Subject: [PATCH] feat: store viewer config in local storage refactor: store --- src/main.module.ts | 18 +- src/services/state/dataStore.store.ts | 120 +++++ src/services/state/ngViewerState.store.ts | 76 +++ .../state/spatialSearchState.store.ts | 42 ++ src/services/state/uiState.store.ts | 51 ++ src/services/state/viewerConfig.store.ts | 8 +- src/services/state/viewerState.store.ts | 125 +++++ src/services/stateStore.service.ts | 453 +----------------- src/ui/config/config.component.ts | 33 +- src/ui/config/config.template.html | 4 + .../dedicated/dedicated.component.ts | 3 + .../nehubaContainer.component.ts | 22 +- 12 files changed, 494 insertions(+), 461 deletions(-) create mode 100644 src/services/state/dataStore.store.ts create mode 100644 src/services/state/ngViewerState.store.ts create mode 100644 src/services/state/spatialSearchState.store.ts create mode 100644 src/services/state/uiState.store.ts create mode 100644 src/services/state/viewerState.store.ts diff --git a/src/main.module.ts b/src/main.module.ts index 94d90d087..daf70e99d 100644 --- a/src/main.module.ts +++ b/src/main.module.ts @@ -3,8 +3,8 @@ import { ComponentsModule } from "./components/components.module"; 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, exportedStates } from "./services/stateStore.service"; +import { StoreModule, Store, select } from "@ngrx/store"; +import { viewerState, dataStore,spatialSearchState,uiState, ngViewerState, pluginState, viewerConfigState } from "./services/stateStore.service"; import { GetNamesPipe } from "./util/pipes/getNames.pipe"; import { CommonModule } from "@angular/common"; import { GetNamePipe } from "./util/pipes/getName.pipe"; @@ -33,6 +33,7 @@ import { FloatingContainerDirective } from "./util/directives/floatingContainer. import { PluginFactoryDirective } from "./util/directives/pluginFactory.directive"; import { FloatingMouseContextualContainerDirective } from "./util/directives/floatingMouseContextualContainer.directive"; import { AuthService } from "./services/auth.service"; +import { ViewerConfiguration } from "./services/state/viewerConfig.store"; @NgModule({ imports : [ @@ -45,8 +46,8 @@ import { AuthService } from "./services/auth.service"; ModalModule.forRoot(), TooltipModule.forRoot(), StoreModule.forRoot({ - pluginState: exportedStates.pluginState, - viewerConfigState: exportedStates.viewerConfigState, + pluginState, + viewerConfigState, ngViewerState, viewerState, dataStore, @@ -105,8 +106,15 @@ import { AuthService } from "./services/auth.service"; export class MainModule{ constructor( - authServce: AuthService + authServce: AuthService, + store: Store<ViewerConfiguration> ){ authServce.authReloadState() + store.pipe( + select('viewerConfigState') + ).subscribe(({ gpuLimit }) => { + if (gpuLimit) + window.localStorage.setItem('iv-gpulimit', gpuLimit.toString()) + }) } } \ No newline at end of file diff --git a/src/services/state/dataStore.store.ts b/src/services/state/dataStore.store.ts new file mode 100644 index 000000000..7242028e9 --- /dev/null +++ b/src/services/state/dataStore.store.ts @@ -0,0 +1,120 @@ +import { Action } from '@ngrx/store' + +export function dataStore(state:any,action:DatasetAction){ + switch (action.type){ + case FETCHED_DATAENTRIES: { + return Object.assign({},state,{ + fetchedDataEntries : action.fetchedDataEntries + }) + } + case FETCHED_SPATIAL_DATA :{ + return Object.assign({},state,{ + fetchedSpatialData : action.fetchedDataEntries + }) + } + case FETCHED_METADATA : { + return Object.assign({},state,{ + fetchedMetadataMap : action.fetchedMetadataMap + }) + } + default: + return state + } +} + +export interface DatasetAction extends Action{ + fetchedDataEntries : DataEntry[] + fetchedSpatialData : DataEntry[] + fetchedMetadataMap : Map<string,Map<string,{properties:Property}>> +} + +export const FETCHED_DATAENTRIES = 'FETCHED_DATAENTRIES' +export const FETCHED_METADATA = 'FETCHED_METADATA' +export const FETCHED_SPATIAL_DATA = `FETCHED_SPATIAL_DATA` + +export interface DataEntry{ + name: string + description: string + license: string[] + licenseInfo: string[] + parcellationRegion: ParcellationRegion[] + formats: string[] + custodians: string[] + contributors: string[] + referenceSpaces: ReferenceSpace[] + files : File[] + publications: Publication[] + embargoStatus: string[] + + /** + * TODO typo, should be kgReferences + */ + kgReference: string[] +} + +export interface ParcellationRegion { + name: string +} + +export interface ReferenceSpace { + name: string +} + +export interface Publication{ + name: string + doi : string + cite : string +} + +export interface Property{ + description : string + publications : Publication[] +} + +export interface Landmark{ + type : string //e.g. sEEG recording site, etc + name : string + templateSpace : string // possibily inherited from LandmarkBundle (?) + geometry : PointLandmarkGeometry | PlaneLandmarkGeometry | OtherLandmarkGeometry + properties : Property + files : File[] +} + +export interface DataStateInterface{ + fetchedDataEntries : DataEntry[] + + /** + * Map that maps parcellation name to a Map, which maps datasetname to Property Object + */ + fetchedMetadataMap : Map<string,Map<string,{properties:Property}>> +} + +export interface PointLandmarkGeometry extends LandmarkGeometry{ + position : [number, number, number] +} + +export interface PlaneLandmarkGeometry extends LandmarkGeometry{ + // corners have to be CW or CCW (no zigzag) + corners : [[number, number, number],[number, number, number],[number, number, number],[number, number, number]] +} + +export interface OtherLandmarkGeometry extends LandmarkGeometry{ + vertices: [number, number, number][] + meshIdx: [number,number,number][] +} + +interface LandmarkGeometry{ + type : 'point' | 'plane' + space? : 'voxel' | 'real' +} + +export interface File{ + name: string + absolutePath: string + byteSize: number + contentType: string +} + +export interface FileSupplementData{ + data: any +} \ No newline at end of file diff --git a/src/services/state/ngViewerState.store.ts b/src/services/state/ngViewerState.store.ts new file mode 100644 index 000000000..d4626cbff --- /dev/null +++ b/src/services/state/ngViewerState.store.ts @@ -0,0 +1,76 @@ +import { Action } from '@ngrx/store' + +export interface NgViewerStateInterface{ + layers : NgLayerInterface[] + forceShowSegment : boolean | null +} + +export interface NgViewerAction extends Action{ + layer : NgLayerInterface + forceShowSegment : boolean +} + +export function ngViewerState(prevState:NgViewerStateInterface = {layers:[], forceShowSegment:null}, action:NgViewerAction):NgViewerStateInterface{ + switch(action.type){ + case ADD_NG_LAYER: + return Object.assign({}, prevState, { + /* this configration hides the layer if a non mixable layer already present */ + layers : action.layer.constructor === Array + ? prevState.layers.concat(action.layer) + : prevState.layers.concat( + Object.assign({}, action.layer, + action.layer.mixability === 'nonmixable' && prevState.layers.findIndex(l => l.mixability === 'nonmixable') >= 0 + ? {visible: false} + : {})) + + /* this configuration does not the addition of multiple non mixable layers */ + // layers : action.layer.mixability === 'nonmixable' && prevState.layers.findIndex(l => l.mixability === 'nonmixable') >= 0 + // ? prevState.layers + // : prevState.layers.concat(action.layer) + + /* this configuration allows the addition of multiple non mixables */ + // layers : prevState.layers.map(l => mapLayer(l, action.layer)).concat(action.layer) + }) + case REMOVE_NG_LAYER: + return Object.assign({}, prevState, { + layers : prevState.layers.filter(l => l.name !== action.layer.name) + } as NgViewerStateInterface) + case SHOW_NG_LAYER: + return Object.assign({}, prevState, { + layers : prevState.layers.map(l => l.name === action.layer.name + ? Object.assign({}, l, { + visible : true + } as NgLayerInterface) + : l) + }) + case HIDE_NG_LAYER: + return Object.assign({}, prevState, { + layers : prevState.layers.map(l => l.name === action.layer.name + ? Object.assign({}, l, { + visible : false + } as NgLayerInterface) + : l) + }) + case FORCE_SHOW_SEGMENT: + return Object.assign({}, prevState, { + forceShowSegment : action.forceShowSegment + }) as NgViewerStateInterface + default: + return prevState + } +} + +export const ADD_NG_LAYER = 'ADD_NG_LAYER' +export const REMOVE_NG_LAYER = 'REMOVE_NG_LAYER' +export const SHOW_NG_LAYER = 'SHOW_NG_LAYER' +export const HIDE_NG_LAYER = 'HIDE_NG_LAYER' +export const FORCE_SHOW_SEGMENT = `FORCE_SHOW_SEGMENT` + +interface NgLayerInterface{ + name : string + source : string + mixability : string // base | mixable | nonmixable + visible : boolean + shader? : string + transform? : any +} \ No newline at end of file diff --git a/src/services/state/spatialSearchState.store.ts b/src/services/state/spatialSearchState.store.ts new file mode 100644 index 000000000..c3a5bd4c7 --- /dev/null +++ b/src/services/state/spatialSearchState.store.ts @@ -0,0 +1,42 @@ +import { Action } from '@ngrx/store' + +export function spatialSearchState(state:SpatialDataStateInterface = initSpatialDataState, action:SpatialDataEntries){ + switch (action.type){ + case SPATIAL_GOTO_PAGE: + return Object.assign({},state,{ + spatialSearchPagination : action.pageNo + }) + case UPDATE_SPATIAL_DATA: + return Object.assign({},state,{ + spatialSearchTotalResults : action.totalResults + }) + case UPDATE_SPATIAL_DATA_VISIBLE: + return Object.assign({},state,{ + spatialDataVisible : action.visible + }) + default : + return state + } +} + +export interface SpatialDataStateInterface{ + spatialSearchPagination : number + spatialSearchTotalResults : number + spatialDataVisible : boolean +} + +const initSpatialDataState : SpatialDataStateInterface = { + spatialDataVisible : true, + spatialSearchPagination : 0, + spatialSearchTotalResults : 0 +} + +export interface SpatialDataEntries extends Action{ + pageNo? : number + totalResults? : number + visible? : boolean +} + +export const SPATIAL_GOTO_PAGE = `SPATIAL_GOTO_PAGE` +export const UPDATE_SPATIAL_DATA = `UPDATE_SPATIAL_DATA` +export const UPDATE_SPATIAL_DATA_VISIBLE = `UPDATE_SPATIAL_DATA_VISIBLE ` diff --git a/src/services/state/uiState.store.ts b/src/services/state/uiState.store.ts new file mode 100644 index 000000000..ebbacfb09 --- /dev/null +++ b/src/services/state/uiState.store.ts @@ -0,0 +1,51 @@ +import { Action } from '@ngrx/store' + +export function uiState(state:UIStateInterface = {mouseOverSegment:null, mouseOverLandmark : null, focusedSidePanel:null, sidePanelOpen: false},action:UIAction){ + switch(action.type){ + case MOUSE_OVER_SEGMENT: + return Object.assign({},state,{ + mouseOverSegment : action.segment + }) + case MOUSE_OVER_LANDMARK: + return Object.assign({}, state, { + mouseOverLandmark : action.landmark + }) + case TOGGLE_SIDE_PANEL: + return Object.assign({}, state, { + focusedSidePanel : typeof action.focusedSidePanel === 'undefined' || state.focusedSidePanel === action.focusedSidePanel + ? null + : action.focusedSidePanel, + sidePanelOpen : !(typeof action.focusedSidePanel === 'undefined' || state.focusedSidePanel === action.focusedSidePanel) + } as Partial<UIStateInterface>) + case OPEN_SIDE_PANEL : + return Object.assign({},state,{ + sidePanelOpen : true + }) + case CLOSE_SIDE_PANEL : + return Object.assign({},state,{ + sidePanelOpen : false + }) + default : + return state + } +} + +export interface UIStateInterface{ + sidePanelOpen : boolean + mouseOverSegment : any | number + mouseOverLandmark : any + focusedSidePanel : string | null +} + +export interface UIAction extends Action{ + segment : any | number + landmark : any + focusedSidePanel? : string +} + +export const MOUSE_OVER_SEGMENT = `MOUSE_OVER_SEGMENT` +export const MOUSE_OVER_LANDMARK = `MOUSE_OVER_LANDMARK` + +export const TOGGLE_SIDE_PANEL = 'TOGGLE_SIDE_PANEL' +export const CLOSE_SIDE_PANEL = `CLOSE_SIDE_PANEL` +export const OPEN_SIDE_PANEL = `OPEN_SIDE_PANEL` \ No newline at end of file diff --git a/src/services/state/viewerConfig.store.ts b/src/services/state/viewerConfig.store.ts index 230fb9941..eb634fee9 100644 --- a/src/services/state/viewerConfig.store.ts +++ b/src/services/state/viewerConfig.store.ts @@ -25,7 +25,12 @@ export const ACTION_TYPES = { CHANGE_GPU_LIMIT: `CHANGE_GPU_LIMIT` } -export function viewerConfigState(prevState:ViewerConfiguration = {animation: CONFIG_CONSTANTS.defaultAnimation, gpuLimit: CONFIG_CONSTANTS.defaultGpuLimit}, action:ViewerConfigurationAction) { +const lsGpuLimit = localStorage.getItem('iv-gpulimit') +const gpuLimit = lsGpuLimit && !isNaN(Number(lsGpuLimit)) + ? Number(lsGpuLimit) + : CONFIG_CONSTANTS.defaultGpuLimit + +export function viewerConfigState(prevState:ViewerConfiguration = {animation: CONFIG_CONSTANTS.defaultAnimation, gpuLimit}, action:ViewerConfigurationAction) { switch (action.type) { case ACTION_TYPES.UPDATE_CONFIG: return { @@ -39,7 +44,6 @@ export function viewerConfigState(prevState:ViewerConfiguration = {animation: CO (prevState.gpuLimit || CONFIG_CONSTANTS.defaultGpuLimit) + action.payload.delta, CONFIG_CONSTANTS.gpuLimitMin )) - return { ...prevState, gpuLimit: newGpuLimit diff --git a/src/services/state/viewerState.store.ts b/src/services/state/viewerState.store.ts new file mode 100644 index 000000000..ce5b4c7f1 --- /dev/null +++ b/src/services/state/viewerState.store.ts @@ -0,0 +1,125 @@ +import { Action } from '@ngrx/store' +import { UserLandmark } from 'src/atlasViewer/atlasViewer.apiService.service'; + +export interface ViewerStateInterface{ + fetchedTemplates : any[] + + templateSelected : any | null + parcellationSelected : any | null + regionsSelected : any[] + + landmarksSelected : any[] + userLandmarks : UserLandmark[] + + navigation : any | null + dedicatedView : string[] +} + +export interface AtlasAction extends Action{ + fetchedTemplate? : any[] + + selectTemplate? : any + selectParcellation? : any + selectRegions? : any[] + deselectRegions? : any[] + dedicatedView? : string + + landmarks : UserLandmark[] + deselectLandmarks : UserLandmark[] + + navigation? : any +} + +export function viewerState( + state:Partial<ViewerStateInterface> = { + landmarksSelected : [], + fetchedTemplates : [] + }, + action:AtlasAction +){ + switch(action.type){ + /** + * TODO may be obsolete. test when nifti become available + */ + case LOAD_DEDICATED_LAYER: + const dedicatedView = state.dedicatedView + ? state.dedicatedView.concat(action.dedicatedView) + : [action.dedicatedView] + return Object.assign({},state,{ + dedicatedView + }) + case UNLOAD_DEDICATED_LAYER: + return Object.assign({},state,{ + dedicatedView : state.dedicatedView + ? state.dedicatedView.filter(dv => dv !== action.dedicatedView) + : [] + }) + case NEWVIEWER: + return Object.assign({},state,{ + templateSelected : action.selectTemplate, + parcellationSelected : action.selectParcellation, + regionsSelected : [], + landmarksSelected : [], + navigation : {}, + dedicatedView : null + }) + case FETCHED_TEMPLATE : { + return Object.assign({}, state, { + fetchedTemplates: state.fetchedTemplates.concat(action.fetchedTemplate) + }) + } + case CHANGE_NAVIGATION : { + return Object.assign({},state,{navigation : action.navigation}) + } + case SELECT_PARCELLATION : { + return Object.assign({},state,{ + parcellationSelected : action.selectParcellation, + regionsSelected : [] + }) + } + case DESELECT_REGIONS : { + return Object.assign({}, state, { + regionsSelected : state.regionsSelected.filter(re => action.deselectRegions.findIndex(dRe => dRe.name === re.name) < 0) + }) + } + case SELECT_REGIONS : { + return Object.assign({},state,{ + regionsSelected : action.selectRegions.map(region=>Object.assign({},region,{ + labelIndex : Number(region.labelIndex) + })) + }) + } + case DESELECT_LANDMARKS : { + return Object.assign({}, state, { + landmarksSelected : state.landmarksSelected.filter(lm => action.deselectLandmarks.findIndex(dLm => dLm.name === lm.name) < 0) + }) + } + case SELECT_LANDMARKS : { + return Object.assign({}, state, { + landmarksSelected : action.landmarks + }) + } + case USER_LANDMARKS : { + return Object.assign({}, state, { + userLandmarks : action.landmarks + }) + } + default : + return state + } +} + +export const LOAD_DEDICATED_LAYER = 'LOAD_DEDICATED_LAYER' +export const UNLOAD_DEDICATED_LAYER = 'UNLOAD_DEDICATED_LAYER' + +export const NEWVIEWER = 'NEWVIEWER' + +export const FETCHED_TEMPLATE = 'FETCHED_TEMPLATE' +export const CHANGE_NAVIGATION = 'CHANGE_NAVIGATION' + +export const SELECT_PARCELLATION = `SELECT_PARCELLATION` +export const SELECT_REGIONS = `SELECT_REGIONS` +export const DESELECT_REGIONS = `DESELECT_REGIONS` +export const SELECT_LANDMARKS = `SELECT_LANDMARKS` +export const DESELECT_LANDMARKS = `DESELECT_LANDMARKS` +export const USER_LANDMARKS = `USER_LANDMARKS` diff --git a/src/services/stateStore.service.ts b/src/services/stateStore.service.ts index c39292834..68eadf1c5 100644 --- a/src/services/stateStore.service.ts +++ b/src/services/stateStore.service.ts @@ -1,334 +1,12 @@ -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 NEWVIEWER = 'NEWVIEWER' - -export const FETCHED_TEMPLATE = 'FETCHED_TEMPLATE' -export const SELECT_PARCELLATION = `SELECT_PARCELLATION` -export const SELECT_REGIONS = `SELECT_REGIONS` -export const DESELECT_REGIONS = `DESELECT_REGIONS` -export const SELECT_LANDMARKS = `SELECT_LANDMARKS` -export const DESELECT_LANDMARKS = `DESELECT_LANDMARKS` -export const USER_LANDMARKS = `USER_LANDMARKS` - -export const CHANGE_NAVIGATION = 'CHANGE_NAVIGATION' - -export const FETCHED_DATAENTRIES = 'FETCHED_DATAENTRIES' -export const FETCHED_METADATA = 'FETCHED_METADATA' -export const FETCHED_SPATIAL_DATA = `FETCHED_SPATIAL_DATA` - -export const LOAD_DEDICATED_LAYER = 'LOAD_DEDICATED_LAYER' -export const UNLOAD_DEDICATED_LAYER = 'UNLOAD_DEDICATED_LAYER' - -export const SPATIAL_GOTO_PAGE = `SPATIAL_GOTO_PAGE` -export const UPDATE_SPATIAL_DATA = `UPDATE_SPATIAL_DATA` -export const UPDATE_SPATIAL_DATA_VISIBLE = `UPDATE_SPATIAL_DATA_VISIBLE ` - -export const TOGGLE_SIDE_PANEL = 'TOGGLE_SIDE_PANEL' -export const CLOSE_SIDE_PANEL = `CLOSE_SIDE_PANEL` -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 FETCHED_PLUGIN_MANIFESTS = `FETCHED_PLUGIN_MANIFESTS` -export const LAUNCH_PLUGIN = `LAUNCH_PLUGIN` - - - -export interface ViewerStateInterface{ - fetchedTemplates : any[] - - templateSelected : any | null - parcellationSelected : any | null - regionsSelected : any[] - - landmarksSelected : any[] - userLandmarks : UserLandmark[] - - navigation : any | null - dedicatedView : string[] -} - -export interface AtlasAction extends Action{ - fetchedTemplate? : any[] - - selectTemplate? : any - selectParcellation? : any - selectRegions? : any[] - deselectRegions? : any[] - dedicatedView? : string - - landmarks : UserLandmark[] - deselectLandmarks : UserLandmark[] - - navigation? : any -} - -export interface NewViewerAction extends Action{ - selectTemplate : any, - selectParcellation :any -} - -export interface DatasetAction extends Action{ - fetchedDataEntries : DataEntry[] - fetchedSpatialData : DataEntry[] - fetchedMetadataMap : Map<string,Map<string,{properties:Property}>> -} - -export interface DataStateInterface{ - fetchedDataEntries : DataEntry[] - - /** - * Map that maps parcellation name to a Map, which maps datasetname to Property Object - */ - fetchedMetadataMap : Map<string,Map<string,{properties:Property}>> -} - -interface NgLayerInterface{ - name : string - source : string - mixability : string // base | mixable | nonmixable - visible : boolean - shader? : string - transform? : any -} - -export const ADD_NG_LAYER = 'ADD_NG_LAYER' -export const REMOVE_NG_LAYER = 'REMOVE_NG_LAYER' -export const SHOW_NG_LAYER = 'SHOW_NG_LAYER' -export const HIDE_NG_LAYER = 'HIDE_NG_LAYER' -export const FORCE_SHOW_SEGMENT = `FORCE_SHOW_SEGMENT` - -export interface NgViewerStateInterface{ - layers : NgLayerInterface[] - forceShowSegment : boolean | null -} - -export interface NgViewerAction extends Action{ - layer : NgLayerInterface - forceShowSegment : boolean -} - -export function ngViewerState(prevState:NgViewerStateInterface = {layers:[], forceShowSegment:null}, action:NgViewerAction):NgViewerStateInterface{ - switch(action.type){ - case ADD_NG_LAYER: - return Object.assign({}, prevState, { - /* this configration hides the layer if a non mixable layer already present */ - layers : action.layer.constructor === Array - ? prevState.layers.concat(action.layer) - : prevState.layers.concat( - Object.assign({}, action.layer, - action.layer.mixability === 'nonmixable' && prevState.layers.findIndex(l => l.mixability === 'nonmixable') >= 0 - ? {visible: false} - : {})) - - /* this configuration does not the addition of multiple non mixable layers */ - // layers : action.layer.mixability === 'nonmixable' && prevState.layers.findIndex(l => l.mixability === 'nonmixable') >= 0 - // ? prevState.layers - // : prevState.layers.concat(action.layer) - - /* this configuration allows the addition of multiple non mixables */ - // layers : prevState.layers.map(l => mapLayer(l, action.layer)).concat(action.layer) - }) - case REMOVE_NG_LAYER: - return Object.assign({}, prevState, { - layers : prevState.layers.filter(l => l.name !== action.layer.name) - } as NgViewerStateInterface) - case SHOW_NG_LAYER: - return Object.assign({}, prevState, { - layers : prevState.layers.map(l => l.name === action.layer.name - ? Object.assign({}, l, { - visible : true - } as NgLayerInterface) - : l) - }) - case HIDE_NG_LAYER: - return Object.assign({}, prevState, { - layers : prevState.layers.map(l => l.name === action.layer.name - ? Object.assign({}, l, { - visible : false - } as NgLayerInterface) - : l) - }) - case FORCE_SHOW_SEGMENT: - return Object.assign({}, prevState, { - forceShowSegment : action.forceShowSegment - }) as NgViewerStateInterface - default: - return prevState - } -} - -export function uiState(state:UIStateInterface = {mouseOverSegment:null, mouseOverLandmark : null, focusedSidePanel:null, sidePanelOpen: false},action:UIAction){ - switch(action.type){ - case MOUSE_OVER_SEGMENT: - return Object.assign({},state,{ - mouseOverSegment : action.segment - }) - case MOUSE_OVER_LANDMARK: - return Object.assign({}, state, { - mouseOverLandmark : action.landmark - }) - case TOGGLE_SIDE_PANEL: - return Object.assign({}, state, { - focusedSidePanel : typeof action.focusedSidePanel === 'undefined' || state.focusedSidePanel === action.focusedSidePanel - ? null - : action.focusedSidePanel, - sidePanelOpen : !(typeof action.focusedSidePanel === 'undefined' || state.focusedSidePanel === action.focusedSidePanel) - } as Partial<UIStateInterface>) - case OPEN_SIDE_PANEL : - return Object.assign({},state,{ - sidePanelOpen : true - }) - case CLOSE_SIDE_PANEL : - return Object.assign({},state,{ - sidePanelOpen : false - }) - default : - return state - } -} - -export function viewerState( - state:Partial<ViewerStateInterface> = { - landmarksSelected : [], - fetchedTemplates : [] - }, - action:AtlasAction -){ - switch(action.type){ - case LOAD_DEDICATED_LAYER: - const dedicatedView = state.dedicatedView - ? state.dedicatedView.concat(action.dedicatedView) - : [action.dedicatedView] - return Object.assign({},state,{ - dedicatedView - }) - case UNLOAD_DEDICATED_LAYER: - return Object.assign({},state,{ - dedicatedView : state.dedicatedView - ? state.dedicatedView.filter(dv => dv !== action.dedicatedView) - : [] - }) - case NEWVIEWER: - return Object.assign({},state,{ - templateSelected : action.selectTemplate, - parcellationSelected : action.selectParcellation, - regionsSelected : [], - landmarksSelected : [], - navigation : {}, - dedicatedView : null - }) - case FETCHED_TEMPLATE : { - return Object.assign({}, state, { - fetchedTemplates: state.fetchedTemplates.concat(action.fetchedTemplate) - }) - } - case CHANGE_NAVIGATION : { - return Object.assign({},state,{navigation : action.navigation}) - } - case SELECT_PARCELLATION : { - return Object.assign({},state,{ - parcellationSelected : action.selectParcellation, - regionsSelected : [] - }) - } - case DESELECT_REGIONS : { - return Object.assign({}, state, { - regionsSelected : state.regionsSelected.filter(re => action.deselectRegions.findIndex(dRe => dRe.name === re.name) < 0) - }) - } - case SELECT_REGIONS : { - return Object.assign({},state,{ - regionsSelected : action.selectRegions.map(region=>Object.assign({},region,{ - labelIndex : Number(region.labelIndex) - })) - }) - } - case DESELECT_LANDMARKS : { - return Object.assign({}, state, { - landmarksSelected : state.landmarksSelected.filter(lm => action.deselectLandmarks.findIndex(dLm => dLm.name === lm.name) < 0) - }) - } - case SELECT_LANDMARKS : { - return Object.assign({}, state, { - landmarksSelected : action.landmarks - }) - } - case USER_LANDMARKS : { - return Object.assign({}, state, { - userLandmarks : action.landmarks - }) - } - default : - return state - } -} - -export function dataStore(state:any,action:DatasetAction){ - switch (action.type){ - case FETCHED_DATAENTRIES: { - return Object.assign({},state,{ - fetchedDataEntries : action.fetchedDataEntries - }) - } - case FETCHED_SPATIAL_DATA :{ - return Object.assign({},state,{ - fetchedSpatialData : action.fetchedDataEntries - }) - } - case FETCHED_METADATA : { - return Object.assign({},state,{ - fetchedMetadataMap : action.fetchedMetadataMap - }) - } - default: - return state - } -} - -export interface SpatialDataEntries extends Action -{ - pageNo? : number - totalResults? : number - visible? : boolean -} - -export interface SpatialDataStateInterface{ - spatialSearchPagination : number - spatialSearchTotalResults : number - spatialDataVisible : boolean -} - -const initSpatialDataState : SpatialDataStateInterface = { - spatialDataVisible : true, - spatialSearchPagination : 0, - spatialSearchTotalResults : 0 -} - -export function spatialSearchState(state:SpatialDataStateInterface = initSpatialDataState, action:SpatialDataEntries){ - switch (action.type){ - case SPATIAL_GOTO_PAGE: - return Object.assign({},state,{ - spatialSearchPagination : action.pageNo - }) - case UPDATE_SPATIAL_DATA: - return Object.assign({},state,{ - spatialSearchTotalResults : action.totalResults - }) - case UPDATE_SPATIAL_DATA_VISIBLE: - return Object.assign({},state,{ - spatialDataVisible : action.visible - }) - default : - return state - } -} +export { viewerConfigState } from './state/viewerConfig.store' +export { pluginState } from './state/pluginState.store' +export { NgViewerAction, NgViewerStateInterface, ngViewerState, ADD_NG_LAYER, FORCE_SHOW_SEGMENT, HIDE_NG_LAYER, REMOVE_NG_LAYER, SHOW_NG_LAYER } from './state/ngViewerState.store' +export { CHANGE_NAVIGATION, AtlasAction, DESELECT_LANDMARKS, DESELECT_REGIONS, FETCHED_TEMPLATE, NEWVIEWER, SELECT_LANDMARKS, SELECT_PARCELLATION, SELECT_REGIONS, USER_LANDMARKS, ViewerStateInterface, viewerState } from './state/viewerState.store' +export { DataEntry, ParcellationRegion, DataStateInterface, DatasetAction, FETCHED_DATAENTRIES, FETCHED_METADATA, FETCHED_SPATIAL_DATA, Landmark, OtherLandmarkGeometry, PlaneLandmarkGeometry, PointLandmarkGeometry, Property, Publication, ReferenceSpace, dataStore, File, FileSupplementData } from './state/dataStore.store' +export { CLOSE_SIDE_PANEL, MOUSE_OVER_LANDMARK, MOUSE_OVER_SEGMENT, OPEN_SIDE_PANEL, TOGGLE_SIDE_PANEL, UIAction, UIStateInterface, uiState } from './state/uiState.store' +export { SPATIAL_GOTO_PAGE, SpatialDataEntries, SpatialDataStateInterface, UPDATE_SPATIAL_DATA, UPDATE_SPATIAL_DATA_VISIBLE, spatialSearchState } from './state/spatialSearchState.store' export function safeFilter(key:string){ return filter((state:any)=> @@ -366,123 +44,6 @@ export interface DedicatedViewState{ dedicatedView : string | null } -export interface DedicatedViewAction extends Action{ - dedicatedView : string | null -} - -export interface KgInfo{ - kgId?: string - descrption? : string - /** - * perhaps have contributors as a separate interface? - */ - contributors? : string[] - publications?: Publication[] - preparations?: string[] - methods?: string[] - license?: string - files? : File[] - - kgUri? : string -} - -export interface DataEntry{ - name: string - description: string - license: string[] - licenseInfo: string[] - parcellationRegion: ParcellationRegion[] - formats: string[] - custodians: string[] - contributors: string[] - referenceSpaces: ReferenceSpace[] - files : File[] - publications: Publication[] - embargoStatus: string[] - - /** - * TODO typo, should be kgReferences - */ - kgReference: string[] -} - -export interface File{ - name: string - absolutePath: string - byteSize: number - contentType: string -} - -export interface FileSupplementData{ - data: any -} - -export interface Property{ - description : string - publications : Publication[] -} - -export interface Publication{ - name: string - doi : string - cite : string -} - -export interface UIStateInterface{ - sidePanelOpen : boolean - mouseOverSegment : any | number - mouseOverLandmark : any - focusedSidePanel : string | null -} - -export interface UIAction extends Action{ - segment : any | number - landmark : any - focusedSidePanel? : string -} - -export interface Landmark{ - type : string //e.g. sEEG recording site, etc - name : string - templateSpace : string // possibily inherited from LandmarkBundle (?) - geometry : PointLandmarkGeometry | PlaneLandmarkGeometry | OtherLandmarkGeometry - properties : Property - files : File[] -} - -export interface LandmarkGeometry{ - type : 'point' | 'plane' - space? : 'voxel' | 'real' -} - -export interface PointLandmarkGeometry extends LandmarkGeometry{ - position : [number, number, number] -} - -export interface PlaneLandmarkGeometry extends LandmarkGeometry{ - // corners have to be CW or CCW (no zigzag) - corners : [[number, number, number],[number, number, number],[number, number, number],[number, number, number]] -} - -export interface OtherLandmarkGeometry extends LandmarkGeometry{ - vertices: [number, number, number][] - meshIdx: [number,number,number][] -} - export function isDefined(obj){ return typeof obj !== 'undefined' && obj !== null } - - -export const exportedStates = { - pluginState, - viewerConfigState -} - -export interface ParcellationRegion { - name: string -} - -export interface ReferenceSpace { - name: string -} \ No newline at end of file diff --git a/src/ui/config/config.component.ts b/src/ui/config/config.component.ts index 51769165b..7061bb228 100644 --- a/src/ui/config/config.component.ts +++ b/src/ui/config/config.component.ts @@ -1,8 +1,8 @@ -import { Component } from '@angular/core' +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 } from 'rxjs'; -import { map, distinctUntilChanged } from 'rxjs/operators'; +import { Observable, Subject, Subscription } from 'rxjs'; +import { map, distinctUntilChanged, debounce, debounceTime } from 'rxjs/operators'; @Component({ selector: 'config-component', @@ -12,12 +12,17 @@ import { map, distinctUntilChanged } from 'rxjs/operators'; ] }) -export class ConfigComponent{ +export class ConfigComponent implements OnInit, OnDestroy{ /** * in MB */ public gpuLimit$: Observable<number> + public keydown$: Subject<Event> = new Subject() + private subscriptions: Subscription[] = [] + + public gpuMin : number = 100 + public gpuMax : number = 1000 constructor(private store: Store<ViewerConfiguration>) { this.gpuLimit$ = this.store.pipe( @@ -28,6 +33,26 @@ export class ConfigComponent{ ) } + ngOnInit(){ + this.subscriptions.push( + this.keydown$.pipe( + debounceTime(250) + ).subscribe(ev => { + const val = (<HTMLInputElement>ev.srcElement).value + const numVal = val && Number(val) + if (isNaN(numVal) || numVal < this.gpuMin || numVal > this.gpuMax ) + return + this.setGpuPreset({ + value: numVal + }) + }) + ) + } + + ngOnDestroy(){ + this.subscriptions.forEach(s => s.unsubscribe()) + } + public wheelEvent(ev:WheelEvent) { const delta = ev.deltaY * -1e5 this.store.dispatch({ diff --git a/src/ui/config/config.template.html b/src/ui/config/config.template.html index 34b6ef179..ab92e4430 100644 --- a/src/ui/config/config.template.html +++ b/src/ui/config/config.template.html @@ -5,7 +5,11 @@ <input (wheel)="wheelEvent($event)" type="number" + [min]="100" + [max]="1000" + [step]="0.1" class="form-control" + (input)="keydown$.next($event)" [value]="gpuLimit$ | async "> <div class="input-group-btn"> <div (click)="setGpuPreset({ value: 100 })" class="btn btn-default"> diff --git a/src/ui/fileviewer/dedicated/dedicated.component.ts b/src/ui/fileviewer/dedicated/dedicated.component.ts index 41eafd3a5..94af64024 100644 --- a/src/ui/fileviewer/dedicated/dedicated.component.ts +++ b/src/ui/fileviewer/dedicated/dedicated.component.ts @@ -5,6 +5,9 @@ import { Observable, Subscription } from "rxjs"; import { getActiveColorMapFragmentMain } from "../../nehubaContainer/nehubaContainer.component"; import { ToastService } from "../../../services/toastService.service"; +/** + * TODO maybe obsolete + */ @Component({ selector : 'dedicated-viewer', diff --git a/src/ui/nehubaContainer/nehubaContainer.component.ts b/src/ui/nehubaContainer/nehubaContainer.component.ts index 8f46c8c7e..135b23f00 100644 --- a/src/ui/nehubaContainer/nehubaContainer.component.ts +++ b/src/ui/nehubaContainer/nehubaContainer.component.ts @@ -3,7 +3,7 @@ import { NehubaViewerUnit } from "./nehubaViewer/nehubaViewer.component"; import { Store, select } from "@ngrx/store"; import { ViewerStateInterface, safeFilter, SELECT_REGIONS, getLabelIndexMap, CHANGE_NAVIGATION, isDefined, MOUSE_OVER_SEGMENT, USER_LANDMARKS, ADD_NG_LAYER, REMOVE_NG_LAYER, NgViewerStateInterface, MOUSE_OVER_LANDMARK, SELECT_LANDMARKS, Landmark, PointLandmarkGeometry, PlaneLandmarkGeometry, OtherLandmarkGeometry } from "../../services/stateStore.service"; import { Observable, Subscription, fromEvent, combineLatest, merge } from "rxjs"; -import { filter,map, take, scan, debounceTime, distinctUntilChanged, switchMap, skip, withLatestFrom, buffer } from "rxjs/operators"; +import { filter,map, take, scan, debounceTime, distinctUntilChanged, switchMap, skip, withLatestFrom, buffer, tap } from "rxjs/operators"; import { AtlasViewerAPIServices, UserLandmark } from "../../atlasViewer/atlasViewer.apiService.service"; import { timedValues } from "../../util/generator"; import { AtlasViewerDataService } from "../../atlasViewer/atlasViewer.dataService.service"; @@ -75,6 +75,7 @@ export class NehubaContainer implements OnInit, OnDestroy{ private nehubaViewerSubscriptions : Subscription[] = [] public nanometersToOffsetPixelsFn : Function[] = [] + private viewerConfig : Partial<ViewerConfiguration> = {} constructor( private constantService : AtlasViewerConstantsServices, @@ -90,9 +91,10 @@ export class NehubaContainer implements OnInit, OnDestroy{ * 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) + debounceTime(200), + tap(viewerConfig => this.viewerConfig = viewerConfig ), + filter(() => isDefined(this.nehubaViewer) && isDefined(this.nehubaViewer.nehubaViewer)) ) this.nehubaViewerFactory = this.csf.resolveComponentFactory(NehubaViewerUnit) @@ -701,7 +703,19 @@ export class NehubaContainer implements OnInit, OnDestroy{ this.container.clear() this.cr = this.container.createComponent(this.nehubaViewerFactory) this.nehubaViewer = this.cr.instance - this.nehubaViewer.config = template.nehubaConfig + + /** + * apply viewer config such as gpu limit + */ + const { gpuLimit = null } = this.viewerConfig + const { nehubaConfig } = template + + if (gpuLimit) { + const initialNgState = nehubaConfig && nehubaConfig.dataset && nehubaConfig.dataset.initialNgState + initialNgState['gpuLimit'] = gpuLimit + } + + this.nehubaViewer.config = nehubaConfig /* TODO replace with id from KG */ this.nehubaViewer.templateId = template.name -- GitLab