From 83b7609bfa772968e846c0c4377365dabd6e1ca0 Mon Sep 17 00:00:00 2001 From: Xiao Gui <xgui3783@gmail.com> Date: Thu, 10 Mar 2022 14:38:55 +0100 Subject: [PATCH] wip: migrate to sapiview/core wip: migrate/remove src/service/ --- src/atlasComponents/parcellation/module.ts | 4 +- .../regionSearch/regionSearch.component.ts | 20 +- .../regionSearch/regionSearch.template.html | 9 +- .../parcellationRegion/TODO.md | 3 - .../parcellationRegion/index.ts | 7 - .../parcellationRegion/module.ts | 42 --- .../parcellationRegion/region.base.spec.ts | 333 ----------------- .../parcellationRegion/region.base.ts | 158 -------- .../parcellationRegion/region.directive.ts | 14 - .../regionAccordionTooltipText.pipe.ts | 18 - .../regionInOtherTmpl.pipe.ts | 14 - .../regionSimple/regionSimple.component.ts | 21 -- .../regionSimple/regionSimple.template.html | 26 -- .../parcellationRegion/type.ts | 24 -- src/atlasComponents/sapi/schema.ts | 27 +- src/atlasComponents/sapi/type.ts | 21 +- .../dropdownAtlasSelector.component.ts | 1 - .../tmplParcSelector.style.css | 9 +- .../tmplParcSelector.template.html | 99 ++--- .../core/datasets/dataset/dataset.stories.ts | 2 +- .../chip/parcellation.chip.component.ts | 26 ++ .../chip/parcellation.chip.stories.ts | 118 ++++++ .../chip/parcellation.chip.style.css} | 0 .../chip/parcellation.chip.template.html | 16 + .../filterUnsupportedParc.pipe.ts | 2 +- .../sapiViews/core/parcellation/module.ts | 33 +- .../parcellationIsBaseLayer.pipe.ts | 22 ++ .../parcellation/parcellationVis.service.ts | 21 ++ .../parcellation.smartChip.component.ts | 99 +++++ .../parcellation.smartChip.stories.ts | 108 ++++++ .../parcellation.smartChip.style.css} | 0 .../parcellation.smartChip.template.html | 49 +++ .../tile/parcellation.tile.template.html | 1 + .../region/rich/region.rich.component.ts | 4 +- .../entryListItem/entryListItem.component.ts | 22 +- .../entryListItem/entryListItem.template.html | 4 +- .../features/featureBadgeColor.pipe.ts | 2 +- .../features/featureBadgeName.pipe.ts | 2 +- .../autoradiography/autoradiograph.stories.ts | 2 +- .../sapiViews/features/receptors/base.ts | 4 +- .../features/receptors/entry/entry.stories.ts | 2 +- .../fingerprint/fingerprint.stories.ts | 2 +- .../receptors/profile/profile.stories.ts | 2 +- .../atlasViewer.apiService.service.ts | 13 +- src/atlasViewer/atlasViewer.component.ts | 31 +- src/extra_styles.css | 7 +- .../currentLayout/currentLayout.component.ts | 21 +- .../currentLayout/currentLayout.template.html | 18 +- src/main.module.ts | 68 +--- src/mouseoverModule/mouseover.directive.ts | 41 ++- src/overwrite.scss | 52 ++- src/plugin/pluginCsp/pluginCsp.component.ts | 4 +- src/routerModule/util.ts | 34 -- .../state/ngViewerState.store.helper.ts | 4 +- .../state/ngViewerState.store.spec.ts | 100 ----- src/services/state/ngViewerState.store.ts | 341 ------------------ src/services/state/ngViewerState/actions.ts | 5 - src/services/state/ngViewerState/selectors.ts | 9 - src/services/state/uiState.store.helper.ts | 4 - src/services/state/uiState.store.ts | 223 ------------ src/services/state/uiState/selectors.ts | 17 - src/services/state/uiState/ui.effects.ts | 32 -- src/services/state/userConfigState.store.ts | 143 -------- src/services/state/viewerConfig.store.ts | 79 ---- src/services/state/viewerConfig/selectors.ts | 6 - src/services/stateStore.service.ts | 131 ------- src/state/atlasAppearance/action.ts | 14 + src/state/atlasAppearance/index.ts | 1 + src/state/atlasAppearance/selector.ts | 10 + src/state/atlasAppearance/store.ts | 26 +- src/state/effects/TODO.md | 3 + src/state/index.ts | 55 ++- src/state/userInterface/actions.ts | 34 +- src/state/userInterface/const.ts | 6 +- src/state/userInterface/effects.ts | 75 +++- src/state/userInterface/index.ts | 5 +- src/state/userInterface/selectors.ts | 9 +- src/state/userInterface/store.ts | 26 +- src/state/userPreference/actions.ts | 39 ++ src/state/userPreference/const.ts | 9 + src/state/userPreference/effects.ts | 48 +++ src/state/userPreference/index.ts | 4 + src/state/userPreference/selectors.ts | 35 ++ src/state/userPreference/store.ts | 93 +++++ src/ui/config/configCmp/config.component.ts | 82 ++--- src/ui/config/configCmp/config.stories.ts | 46 +++ src/ui/config/configCmp/config.style.css | 12 + src/ui/config/configCmp/config.template.html | 233 ++++++------ src/ui/config/module.ts | 2 +- src/ui/ui.module.ts | 2 - src/util/constants.ts | 2 +- src/util/pureConstant.service.ts | 4 +- src/viewerModule/module.ts | 2 - .../layerCtrl.service.spec.ts | 289 +-------------- .../layerCtrl.service/layerCtrl.service.ts | 23 +- .../layerCtrl.service/layerCtrl.util.ts | 2 +- .../maximisePanelButton.component.ts | 26 +- .../navigation.service.spec.ts | 9 +- .../navigation.service/navigation.service.ts | 8 +- .../nehubaViewer/nehubaViewer.component.ts | 3 +- .../nehubaViewerGlue.component.spec.ts | 16 +- .../nehubaViewerGlue.component.ts | 7 +- .../nehubaViewerGlue.template.html | 2 +- .../nehubaViewerInterface.directive.spec.ts | 13 +- .../nehubaViewerInterface.directive.ts | 44 +-- src/viewerModule/nehuba/store/actions.ts | 4 +- .../nehuba/touchSideClass.directive.ts | 22 +- .../viewerCtrlCmp.component.spec.ts | 6 +- .../viewerCtrlCmp/viewerCtrlCmp.component.ts | 86 ++--- .../viewerCtrlCmp/viewerCtrlCmp.template.html | 15 - .../viewerCmp/viewerCmp.component.ts | 15 +- .../viewerCmp/viewerCmp.template.html | 164 ++++----- .../breadcrumb/breadcrumb.template.html | 25 +- .../viewerStateBreadCrumb/module.ts | 2 - 114 files changed, 1639 insertions(+), 2795 deletions(-) delete mode 100644 src/atlasComponents/parcellationRegion/TODO.md delete mode 100644 src/atlasComponents/parcellationRegion/index.ts delete mode 100644 src/atlasComponents/parcellationRegion/module.ts delete mode 100644 src/atlasComponents/parcellationRegion/region.base.spec.ts delete mode 100644 src/atlasComponents/parcellationRegion/region.base.ts delete mode 100644 src/atlasComponents/parcellationRegion/region.directive.ts delete mode 100644 src/atlasComponents/parcellationRegion/regionAccordionTooltipText.pipe.ts delete mode 100644 src/atlasComponents/parcellationRegion/regionInOtherTmpl.pipe.ts delete mode 100644 src/atlasComponents/parcellationRegion/regionSimple/regionSimple.component.ts delete mode 100644 src/atlasComponents/parcellationRegion/regionSimple/regionSimple.template.html delete mode 100644 src/atlasComponents/parcellationRegion/type.ts create mode 100644 src/atlasComponents/sapiViews/core/parcellation/chip/parcellation.chip.component.ts create mode 100644 src/atlasComponents/sapiViews/core/parcellation/chip/parcellation.chip.stories.ts rename src/atlasComponents/{parcellationRegion/regionSimple/regionSimple.style.css => sapiViews/core/parcellation/chip/parcellation.chip.style.css} (100%) create mode 100644 src/atlasComponents/sapiViews/core/parcellation/chip/parcellation.chip.template.html create mode 100644 src/atlasComponents/sapiViews/core/parcellation/parcellationIsBaseLayer.pipe.ts create mode 100644 src/atlasComponents/sapiViews/core/parcellation/parcellationVis.service.ts create mode 100644 src/atlasComponents/sapiViews/core/parcellation/smartChip/parcellation.smartChip.component.ts create mode 100644 src/atlasComponents/sapiViews/core/parcellation/smartChip/parcellation.smartChip.stories.ts rename src/{services/state/uiState.store.spec.ts => atlasComponents/sapiViews/core/parcellation/smartChip/parcellation.smartChip.style.css} (100%) create mode 100644 src/atlasComponents/sapiViews/core/parcellation/smartChip/parcellation.smartChip.template.html delete mode 100644 src/services/state/ngViewerState.store.spec.ts delete mode 100644 src/services/state/ngViewerState.store.ts delete mode 100644 src/services/state/uiState.store.ts delete mode 100644 src/services/state/uiState/selectors.ts delete mode 100644 src/services/state/uiState/ui.effects.ts delete mode 100644 src/services/state/userConfigState.store.ts delete mode 100644 src/services/state/viewerConfig.store.ts delete mode 100644 src/services/state/viewerConfig/selectors.ts create mode 100644 src/state/effects/TODO.md create mode 100644 src/state/userPreference/actions.ts create mode 100644 src/state/userPreference/const.ts create mode 100644 src/state/userPreference/effects.ts create mode 100644 src/state/userPreference/index.ts create mode 100644 src/state/userPreference/selectors.ts create mode 100644 src/state/userPreference/store.ts create mode 100644 src/ui/config/configCmp/config.stories.ts diff --git a/src/atlasComponents/parcellation/module.ts b/src/atlasComponents/parcellation/module.ts index 7763c155a..5941ce7af 100644 --- a/src/atlasComponents/parcellation/module.ts +++ b/src/atlasComponents/parcellation/module.ts @@ -1,7 +1,6 @@ import { CommonModule } from "@angular/common"; import { NgModule } from "@angular/core"; import { AngularMaterialModule } from "src/sharedModules"; -import { ParcellationRegionModule } from "src/atlasComponents/parcellationRegion"; import { RegionHierarchy } from "./regionHierachy/regionHierarchy.component"; import { RegionTextSearchAutocomplete } from "./regionSearch/regionSearch.component"; import { FilterNameBySearch } from "./regionHierachy/filterNameBySearch.pipe"; @@ -16,7 +15,6 @@ import { ComponentsModule } from "src/components"; FormsModule, ReactiveFormsModule, AngularMaterialModule, - ParcellationRegionModule, ComponentsModule, ], declarations: [ @@ -29,6 +27,8 @@ import { ComponentsModule } from "src/components"; RegionHierarchy, RegionTextSearchAutocomplete, FilterNameBySearch, + ], + providers: [ ] }) export class AtlasCmpParcellationModule{} \ No newline at end of file diff --git a/src/atlasComponents/parcellation/regionSearch/regionSearch.component.ts b/src/atlasComponents/parcellation/regionSearch/regionSearch.component.ts index 7f98db57d..45b7314c6 100644 --- a/src/atlasComponents/parcellation/regionSearch/regionSearch.component.ts +++ b/src/atlasComponents/parcellation/regionSearch/regionSearch.component.ts @@ -3,7 +3,6 @@ import { FormControl } from "@angular/forms"; import { select, Store } from "@ngrx/store"; import { combineLatest, Observable, Subject, merge } from "rxjs"; import { debounceTime, distinctUntilChanged, filter, map, shareReplay, startWith, take, tap, withLatestFrom } from "rxjs/operators"; -import { getMultiNgIdsRegionsLabelIndexMap } from "src/services/stateStore.service"; import { LoggingService } from "src/logging"; import { MatDialog } from "@angular/material/dialog"; import { MatAutocompleteSelectedEvent } from "@angular/material/autocomplete"; @@ -72,24 +71,7 @@ export class RegionTextSearchAutocomplete { distinctUntilChanged(), filter(p => !!p && p.regions), map(parcellationSelected => { - try { - const returnArray = [] - const ngIdMap = getMultiNgIdsRegionsLabelIndexMap(parcellationSelected, { ngId: 'root', relatedAreas: [], fullId: null }) - for (const [ngId, labelIndexMap] of ngIdMap) { - for (const [labelIndex, region] of labelIndexMap) { - returnArray.push({ - ...region, - ngId, - labelIndex, - labelIndexId: serializeSegment(ngId, labelIndex), - }) - } - } - return returnArray - } catch (e) { - this.log.warn(`getMultiNgIdsRegionsLabelIndexMap error`, e) - return [] - } + return [] }), shareReplay(1), ) diff --git a/src/atlasComponents/parcellation/regionSearch/regionSearch.template.html b/src/atlasComponents/parcellation/regionSearch/regionSearch.template.html index a5db58d62..e7f60fc7a 100644 --- a/src/atlasComponents/parcellation/regionSearch/regionSearch.template.html +++ b/src/atlasComponents/parcellation/regionSearch/regionSearch.template.html @@ -36,11 +36,12 @@ *ngFor="let region of autocompleteList$ | async" [value]="region.labelIndexId"> - <simple-region - [region-base-region]="region"> - <!-- [isSelected]="regionsSelected$ | async | includes : region : compareFn"> --> + <!-- TODO replace with sapiViews/core/region/simple --> + <!-- <simple-region + [region-base-region]="region" + [isSelected]="regionsSelected$ | async | includes : region : compareFn"> - </simple-region> + </simple-region> --> </mat-option> </mat-autocomplete> </form> diff --git a/src/atlasComponents/parcellationRegion/TODO.md b/src/atlasComponents/parcellationRegion/TODO.md deleted file mode 100644 index fbf02893d..000000000 --- a/src/atlasComponents/parcellationRegion/TODO.md +++ /dev/null @@ -1,3 +0,0 @@ -# TODO - -migrate src/atlasComponents/parcellationRegion -> src/sapiViews/core/region \ No newline at end of file diff --git a/src/atlasComponents/parcellationRegion/index.ts b/src/atlasComponents/parcellationRegion/index.ts deleted file mode 100644 index d5869daaa..000000000 --- a/src/atlasComponents/parcellationRegion/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -export { - ParcellationRegionModule, -} from "./module" - -export { RegionDirective } from "./region.directive"; -export { SimpleRegionComponent } from "./regionSimple/regionSimple.component"; -export { RenderViewOriginDatasetLabelPipe } from "./region.base"; \ No newline at end of file diff --git a/src/atlasComponents/parcellationRegion/module.ts b/src/atlasComponents/parcellationRegion/module.ts deleted file mode 100644 index 20f1f4fb3..000000000 --- a/src/atlasComponents/parcellationRegion/module.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { CommonModule } from "@angular/common"; -import { NgModule } from "@angular/core"; -import { ComponentsModule } from "src/components"; -import { AngularMaterialModule } from "src/sharedModules"; -import { UtilModule } from "src/util"; -import { RenderViewOriginDatasetLabelPipe } from "./region.base"; -import { RegionDirective } from "./region.directive"; -import { SimpleRegionComponent } from "./regionSimple/regionSimple.component"; -import { RegionAccordionTooltipTextPipe } from "./regionAccordionTooltipText.pipe"; -import { AtlasCmptConnModule } from "../connectivity"; -import { HttpClientModule } from "@angular/common/http"; -import { RegionInOtherTmplPipe } from "./regionInOtherTmpl.pipe"; -import { SiibraExplorerTemplateModule } from "../template"; - -@NgModule({ - imports: [ - CommonModule, - UtilModule, - AngularMaterialModule, - ComponentsModule, - AtlasCmptConnModule, - HttpClientModule, - SiibraExplorerTemplateModule, - - ], - declarations: [ - SimpleRegionComponent, - - RegionDirective, - RenderViewOriginDatasetLabelPipe, - RegionAccordionTooltipTextPipe, - RegionInOtherTmplPipe, - ], - exports: [ - SimpleRegionComponent, - - RegionDirective, - RenderViewOriginDatasetLabelPipe, - ] -}) - -export class ParcellationRegionModule{} \ No newline at end of file diff --git a/src/atlasComponents/parcellationRegion/region.base.spec.ts b/src/atlasComponents/parcellationRegion/region.base.spec.ts deleted file mode 100644 index d3ead88e5..000000000 --- a/src/atlasComponents/parcellationRegion/region.base.spec.ts +++ /dev/null @@ -1,333 +0,0 @@ -import { TestBed } from '@angular/core/testing' -import { MockStore, provideMockStore } from '@ngrx/store/testing' -import { viewerStateSelectTemplateWithId } from 'src/services/state/viewerState/actions' -import { RegionBase } from './region.base' -import { TSiibraExRegion } from './type' - -// eslint-disable-next-line @typescript-eslint/no-var-requires -const util = require('common/util') - -/** - * regions - */ - -const mr0 = { - labelIndex: 1, - name: 'mr0', - availableIn: [{id: 'fzj/mock/rs/v0.0.0/aaa-bbb'}, {id: 'fzj/mock/rs/v0.0.0/bbb-bbb'}, {id: 'fzj/mock/rs/v0.0.0/ccc-bbb'}], - id: { - kg: { - kgSchema: 'fzj/mock/pr', - kgId: 'aaa-bbb' - } - } -} - -enum EnumParcRegVersion{ - V1_18 = 'V1_18', - V2_4 = "V2_4" -} - -describe('> region.base.ts', () => { - describe('> RegionBase', () => { - let regionBase: RegionBase - let mockStore: MockStore - beforeEach(() => { - TestBed.configureTestingModule({ - providers: [ - provideMockStore() - ] - }) - mockStore = TestBed.inject(MockStore) - }) - describe('> position', () => { - beforeEach(() => { - regionBase = new RegionBase(mockStore) - }) - it('> does not populate if position property is absent', () => { - regionBase.region = { - ...mr0 - } as any - expect(regionBase.position).toBeFalsy() - }) - - describe('> does not populate if position property is malformed', () => { - it('> if region is falsy', () => { - regionBase.region = null - expect(regionBase.position).toBeFalsy() - }) - it('> if props is falsy', () => { - regionBase.region = { - ...mr0, - props: null - } as any - expect(regionBase.position).toBeFalsy() - }) - it('> if props.components is falsy', () => { - regionBase.region = { - ...mr0, - props: { - components: null - } - } as any - expect(regionBase.position).toBeFalsy() - }) - it('> if props.components[0] is falsy', () => { - regionBase.region = { - ...mr0, - props: { - components: [] - } - } as any - expect(regionBase.position).toBeFalsy() - }) - - it('> if props.components[0].centroid is falsy', () => { - - regionBase.region = { - ...mr0, - props: { - components: [{ - centroid: null - }] - } - } as any - expect(regionBase.position).toBeFalsy() - }) - }) - - it('> populates if position property is array with length 3 and non NaN element', () => { - regionBase.region = { - ...mr0, - props: { - components: [{ - centroid: [1, 2, 3] - }] - }, - } as any - expect(regionBase.position).toBeTruthy() - }) - }) - - describe('> rgb', () => { - let strToRgbSpy: jasmine.Spy - let mockStore: MockStore - beforeEach(() => { - strToRgbSpy = spyOn(util, 'strToRgb') - mockStore = TestBed.inject(MockStore) - }) - - afterEach(() => { - strToRgbSpy.calls.reset() - }) - - it('> will take region.rgb if exists', () => { - const regionBase = new RegionBase(mockStore) - regionBase.region = { - rgb: [100, 120, 140] - } as any - expect( - regionBase.rgbString - ).toEqual(`rgb(100,120,140)`) - }) - - it('> if rgb not provided, and labelIndex > 65500, set to white', () => { - - const regionBase = new RegionBase(mockStore) - regionBase.region = { - labelIndex: 65535 - } as any - expect( - regionBase.rgbString - ).toEqual(`rgb(255,255,255)`) - }) - - describe('> if rgb not provided, labelIndex < 65500', () => { - - describe('> arguments for strToRgb', () => { - it('> if ngId is defined, use ngId', () => { - - const regionBase = new RegionBase(mockStore) - regionBase.region = { - ngId: 'foo', - name: 'bar', - labelIndex: 152 - } as any - expect(strToRgbSpy).toHaveBeenCalledWith(`foo152`) - }) - it('> if ngId is not defined, use name', () => { - - const regionBase = new RegionBase(mockStore) - regionBase.region = { - name: 'bar', - labelIndex: 152 - } as any - expect(strToRgbSpy).toHaveBeenCalledWith(`bar152`) - }) - }) - - it('> calls strToRgb, and use return value for rgb', () => { - const getRandomNum = () => Math.floor(255*Math.random()) - const arr = [ - getRandomNum(), - getRandomNum(), - getRandomNum() - ] - strToRgbSpy.and.returnValue(arr) - const regionBase = new RegionBase(mockStore) - regionBase.region = { - foo: 'bar' - } as any - expect( - regionBase.rgbString - ).toEqual(`rgb(${arr.join(',')})`) - }) - - it('> if strToRgb returns falsy, uses fallback', () => { - - strToRgbSpy.and.returnValue(null) - const regionBase = new RegionBase(mockStore) - regionBase.region = { - foo: 'bar' - } as any - expect( - regionBase.rgbString - ).toEqual(`rgb(255,200,200)`) - }) - }) - }) - describe('> changeView', () => { - const fakeTmpl = { - '@id': 'faketmplid', - name: 'fakeTmpl' - } - const fakeParc = { - '@id': 'fakeparcid', - name: 'fakeParc' - } - beforeEach(() => { - regionBase = new RegionBase(mockStore) - }) - - describe('> [tmp] sameRegion to use transform backend', () => { - let dispatchSpy: jasmine.Spy - - beforeEach(() => { - dispatchSpy = spyOn(mockStore, 'dispatch') - }) - afterEach(() => { - dispatchSpy.calls.reset() - }) - - it('> calls viewerStateSelectTemplateWithId', () => { - - const partialRegion = { - context: { - parcellation: fakeParc, - atlas: { - "@id": '', - name: '', - parcellations: [], - templateSpaces: [fakeTmpl] - }, - template: null - } - } as Partial<TSiibraExRegion> - regionBase.region = partialRegion as any - regionBase.changeView(fakeTmpl) - - expect(dispatchSpy).toHaveBeenCalledWith( - viewerStateSelectTemplateWithId({ - payload: { - '@id': fakeTmpl['@id'] - }, - config: { - selectParcellation: { - "@id": fakeParc['@id'] - } - } - }) - ) - }) - }) - - /** - * currently, without position metadata, the navigation is broken - * fix changeView to fetch position metadata. If cannot, fallback to spatial backend - */ - - // describe('> if sameRegion has position attribute', () => { - // let dispatchSpy: jasmine.Spy - - // beforeEach(() => { - // dispatchSpy = spyOn(mockStore, 'dispatch') - // }) - // afterEach(() => { - // dispatchSpy.calls.reset() - // }) - // it('> malformed position is not an array > do not pass position', () => { - - // regionBase.changeView({ - // template: fakeTmpl, - // parcellation: fakeParc, - // region: { - // position: 'hello wolrd' - // } - // }) - - // expect(dispatchSpy).toHaveBeenCalledWith( - // actionViewerStateNewViewer({ - // selectTemplate: fakeTmpl, - // selectParcellation: fakeParc, - // navigation: {} - // }) - // ) - // }) - - // it('> malformed position is an array of incorrect size > do not pass position', () => { - - // regionBase.changeView({ - // template: fakeTmpl, - // parcellation: fakeParc, - // region: { - // position: [] - // } - // }) - - // expect(dispatchSpy).toHaveBeenCalledWith( - // viewerStateSelectTemplateWithId({ - // payload: { - // '@id': fakeTmpl['@id'] - // }, - // config: { - // selectParcellation: { - // "@id": fakeParc['@id'] - // } - // } - // }) - // ) - // }) - - // it('> correct position > pass position', () => { - // regionBase.changeView({ - // template: fakeTmpl, - // parcellation: fakeParc, - // region: { - // position: [1,2,3] - // } - // }) - - // expect(dispatchSpy).toHaveBeenCalledWith( - // actionViewerStateNewViewer({ - // selectTemplate: fakeTmpl, - // selectParcellation: fakeParc, - // navigation: { - // position: [1,2,3] - // } - // }) - // ) - // }) - // }) - }) - }) -}) diff --git a/src/atlasComponents/parcellationRegion/region.base.ts b/src/atlasComponents/parcellationRegion/region.base.ts deleted file mode 100644 index 561ed2ac4..000000000 --- a/src/atlasComponents/parcellationRegion/region.base.ts +++ /dev/null @@ -1,158 +0,0 @@ -import { Directive, EventEmitter, Input, Output, Pipe, PipeTransform } from "@angular/core"; -import { select, Store } from "@ngrx/store"; -import { Observable, BehaviorSubject } from "rxjs"; -import { rgbToHsl, hexToRgb } from 'common/util' -import { strToRgb, verifyPositionArg } from 'common/util' -import { actions } from "src/state/atlasSelection"; -import { SapiAtlasModel, SapiParcellationModel, SapiRegionModel, SapiSpaceModel } from "../sapi"; -import { atlasSelection } from "src/state"; - -@Directive() -export class RegionBase { - - public rgbString: string - public rgbDarkmode: boolean - - private _region: SapiRegionModel - - private _position: number[] - set position(val){ - if (verifyPositionArg(val)) { - this._position = val - } else { - this._position = null - } - } - - get position(){ - return this._position - } - - public dois: string[] = [] - - @Input('region-base-atlas') - atlas: SapiAtlasModel - - @Input('region-base-parcellation') - parcellation: SapiParcellationModel - - @Input('region-base-template') - template: SapiSpaceModel - - @Input('region-base-region') - set region(val) { - this._region = val - this.region$.next(this._region) - this.hasContext$.next(false) - - this.position = null - // bug the centroid returned is currently nonsense - // this.position = val?.props?.centroid_mm - if (!val) return - const pos = val?.hasAnnotation?.bestViewPoint?.coordinates?.map(v => v.value * 1e6) - if (pos) { - this.position = pos - } - - let rgb = [255, 200, 200] - if (val.hasAnnotation?.displayColor) { - rgb = hexToRgb(val?.hasAnnotation?.displayColor) - } else { - rgb = strToRgb(JSON.stringify(val)) - } - this.rgbString = `rgb(${rgb.join(',')})` - const [_h, _s, l] = rgbToHsl(...rgb) - this.rgbDarkmode = l < 0.4 - - this.dois = (val.hasAnnotation?.inspiredBy || []) - .map(insp => insp["@id"] as string) - .filter(id => /^https?:\/\/doi\.org/.test(id)) - } - - get region(){ - return this._region - } - - - public hasContext$: BehaviorSubject<boolean> = new BehaviorSubject(false) - public region$: BehaviorSubject<any> = new BehaviorSubject(null) - - @Input() - public isSelected: boolean = false - - @Input() public hasConnectivity: boolean - - @Output() public closeRegionMenu: EventEmitter<boolean> = new EventEmitter() - - public selectedAtlas$: Observable<any> = this.store$.pipe( - select(atlasSelection.selectors.selectedAtlas) - ) - - - constructor( - private store$: Store<any>, - ) { - - } - - public selectedTemplate$ = this.store$.pipe( - select(atlasSelection.selectors.selectedTemplate), - ) - - public navigateToRegion() { - this.closeRegionMenu.emit() - const { region } = this - this.store$.dispatch( - atlasSelection.actions.navigateToRegion({ - region - }) - ) - } - - public toggleRegionSelected() { - this.closeRegionMenu.emit() - const { region } = this - this.store$.dispatch( - actions.toggleRegionSelect({ - region - }) - ) - } - - public showConnectivity(regionName) { - this.closeRegionMenu.emit() - // ToDo trigger side panel opening with effect - // this.store$.dispatch(uiStateOpenSidePanel()) - // this.store$.dispatch(uiStateExpandSidePanel()) - // this.store$.dispatch(uiActionShowSidePanelConnectivity()) - - // I think we can use viewerMode for this?? - // this.store$.dispatch( - // viewerStateSetConnectivityRegion({ connectivityRegion: regionName }) - // ) - } - - changeView(template: SapiSpaceModel) { - - this.closeRegionMenu.emit() - this.store$.dispatch( - atlasSelection.actions.viewSelRegionInNewSpace({ - region: this._region, - template, - }) - ) - } -} - -@Pipe({ - name: 'renderViewOriginDatasetlabel' -}) - -export class RenderViewOriginDatasetLabelPipe implements PipeTransform{ - public transform(originDatasetlabels: { name: string }[], index: string|number){ - if (!!originDatasetlabels && !!originDatasetlabels[index] && !!originDatasetlabels[index].name) { - return `${originDatasetlabels[index]['name']}` - } - return `origin dataset` - } -} diff --git a/src/atlasComponents/parcellationRegion/region.directive.ts b/src/atlasComponents/parcellationRegion/region.directive.ts deleted file mode 100644 index fb66743eb..000000000 --- a/src/atlasComponents/parcellationRegion/region.directive.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Directive } from "@angular/core"; -import { RegionBase } from "./region.base"; -import { Store } from "@ngrx/store"; - -@Directive({ - selector: '[iav-region]', - exportAs: 'iavRegion' -}) - -export class RegionDirective extends RegionBase{ - constructor(store: Store<any>){ - super(store) - } -} diff --git a/src/atlasComponents/parcellationRegion/regionAccordionTooltipText.pipe.ts b/src/atlasComponents/parcellationRegion/regionAccordionTooltipText.pipe.ts deleted file mode 100644 index c2a92b83d..000000000 --- a/src/atlasComponents/parcellationRegion/regionAccordionTooltipText.pipe.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Pipe, PipeTransform } from "@angular/core" - -@Pipe({ - name: 'regionAccordionTooltipTextPipe', - pure: true -}) - -export class RegionAccordionTooltipTextPipe implements PipeTransform{ - - public transform(length: number, type: string): string{ - switch (type) { - case 'regionInOtherTmpl': return `Region available in ${length} other reference space${length > 1 ? 's' : ''}` - case 'regionalFeatures': return `${length} regional feature${length > 1 ? 's' : ''} found` - case 'connectivity': return `${length} connections found` - default: return `${length} items found` - } - } -} diff --git a/src/atlasComponents/parcellationRegion/regionInOtherTmpl.pipe.ts b/src/atlasComponents/parcellationRegion/regionInOtherTmpl.pipe.ts deleted file mode 100644 index 3a0e81dc8..000000000 --- a/src/atlasComponents/parcellationRegion/regionInOtherTmpl.pipe.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Pipe, PipeTransform } from "@angular/core"; -import { TSiibraExRegion } from "./type"; - -@Pipe({ - name: 'regionInOtherTmpl', - pure: true -}) - -export class RegionInOtherTmplPipe implements PipeTransform{ - public transform(region: TSiibraExRegion){ - const { templateSpaces: allTmpl = [] } = region?.context?.atlas || {} - return allTmpl.filter(t => (region?.availableIn || []).find(availTmpl => availTmpl['id'] === t["@id"])) - } -} diff --git a/src/atlasComponents/parcellationRegion/regionSimple/regionSimple.component.ts b/src/atlasComponents/parcellationRegion/regionSimple/regionSimple.component.ts deleted file mode 100644 index 76859b9e5..000000000 --- a/src/atlasComponents/parcellationRegion/regionSimple/regionSimple.component.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Component } from '@angular/core' - -import { Store } from '@ngrx/store' -import { IavRootStoreInterface } from 'src/services/stateStore.service' -import { RegionBase } from '../region.base' - -@Component({ - selector: 'simple-region', - templateUrl: './regionSimple.template.html', - styleUrls: [ - './regionSimple.style.css', - ], -}) - -export class SimpleRegionComponent extends RegionBase { - constructor( - store$: Store<IavRootStoreInterface>, - ) { - super(store$) - } -} diff --git a/src/atlasComponents/parcellationRegion/regionSimple/regionSimple.template.html b/src/atlasComponents/parcellationRegion/regionSimple/regionSimple.template.html deleted file mode 100644 index b724ab6f2..000000000 --- a/src/atlasComponents/parcellationRegion/regionSimple/regionSimple.template.html +++ /dev/null @@ -1,26 +0,0 @@ -<div class="d-flex flex-row"> - - <small class="text-truncate flex-shrink-1 flex-grow-1"> - {{ region.name }} - </small> - - <div class="flex-grow-0 flex-shrink-0 d-flex flex-row"> - - <!-- if has position defined --> - <button *ngIf="position" - iav-stop="click" - (click)="navigateToRegion()" - mat-icon-button> - <i class="fas fa-map-marked-alt"></i> - </button> - - <!-- region selected --> - <button mat-icon-button - [color]="isSelected ? 'primary' : 'basic'"> - <i class="far" - [ngClass]="{'fa-check-square': isSelected, 'fa-square': !isSelected}"> - </i> - </button> - </div> - -</div> \ No newline at end of file diff --git a/src/atlasComponents/parcellationRegion/type.ts b/src/atlasComponents/parcellationRegion/type.ts deleted file mode 100644 index e5c40a75f..000000000 --- a/src/atlasComponents/parcellationRegion/type.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { IHasId } from "src/util/interfaces"; -import { TRegionSummary } from "src/util/siibraApiConstants/types"; - -type TAny = { - [key: string]: any -} - -export type TSiibraExTemplate = IHasId & TAny -export type TSiibraExParcelation = IHasId & TAny - -export type TSiibraExAtlas = { - name: string - '@id': string - parcellations: TSiibraExParcelation[] - templateSpaces: TSiibraExTemplate[] -} - -export type TSiibraExRegion = TRegionSummary & { - context: { - atlas: TSiibraExAtlas - template: TSiibraExTemplate - parcellation: TSiibraExParcelation - } -} diff --git a/src/atlasComponents/sapi/schema.ts b/src/atlasComponents/sapi/schema.ts index 1b807d7d3..5ba071c50 100644 --- a/src/atlasComponents/sapi/schema.ts +++ b/src/atlasComponents/sapi/schema.ts @@ -156,7 +156,7 @@ export interface components { * Type * @constant */ - type?: "siibra/base-dataset"; + type?: "siibra/core/dataset"; metadata: components["schemas"]["siibra__openminds__core__v4__products__datasetVersion__Model"]; /** Urls */ urls: components["schemas"]["Url"][]; @@ -199,7 +199,7 @@ export interface components { * Type * @constant */ - type?: "siibra/connectivity"; + type?: "siibra/features/connectivity"; /** Name */ name: string; /** Parcellations */ @@ -216,7 +216,7 @@ export interface components { * Type * @constant */ - type?: "siibra/base-dataset"; + type?: "siibra/core/dataset"; metadata: components["schemas"]["siibra__openminds__core__v4__products__datasetVersion__Model"]; /** Urls */ urls: components["schemas"]["Url"][]; @@ -327,6 +327,11 @@ export interface components { IEEGSessionModel: { /** @Id */ "@id": string; + /** + * Type + * @constant + */ + type?: "siibra/features/ieegSession"; dataset: components["schemas"]["DatasetJsonModel"]; /** Sub Id */ sub_id: string; @@ -457,7 +462,7 @@ export interface components { * Type * @constant */ - type?: "siibra/receptor"; + type?: "siibra/features/receptor"; metadata: components["schemas"]["siibra__openminds__core__v4__products__datasetVersion__Model"]; /** Urls */ urls: components["schemas"]["Url"][]; @@ -556,6 +561,7 @@ export interface components { datasets: components["schemas"]["DatasetJsonModel"][]; /** Brainatlasversions */ brainAtlasVersions: components["schemas"]["siibra__openminds__SANDS__v3__atlas__brainAtlasVersion__Model"][]; + version?: components["schemas"]["SiibraParcellationVersionModel"]; }; /** SapiSpaceModel */ SapiSpaceModel: { @@ -650,6 +656,15 @@ export interface components { /** @Id */ "@id": string; }; + /** SiibraParcellationVersionModel */ + SiibraParcellationVersionModel: { + /** Name */ + name: string; + /** Deprecated */ + deprecated?: boolean; + prev?: components["schemas"]["SiibraAtIdModel"]; + next?: components["schemas"]["SiibraAtIdModel"]; + }; /** SpeciesModel */ SpeciesModel: { /** @@ -722,7 +737,7 @@ export interface components { * Type * @constant */ - type?: "siibra/base-dataset"; + type?: "siibra/features/voi"; metadata: components["schemas"]["siibra__openminds__core__v4__products__datasetVersion__Model"]; /** Urls */ urls: components["schemas"]["Url"][]; @@ -772,7 +787,7 @@ export interface components { * Type * @constant */ - type?: "siibra/base-dataset"; + type?: "siibra/core/dataset"; metadata: components["schemas"]["siibra__openminds__core__v4__products__datasetVersion__Model"]; /** Urls */ urls: components["schemas"]["Url"][]; diff --git a/src/atlasComponents/sapi/type.ts b/src/atlasComponents/sapi/type.ts index 42a8047ae..8e645b34e 100644 --- a/src/atlasComponents/sapi/type.ts +++ b/src/atlasComponents/sapi/type.ts @@ -24,9 +24,24 @@ export type SapiDatasetModel = components["schemas"]["DatasetJsonModel"] export type SpyNpArrayDataModel = components["schemas"]["NpArrayDataModel"] -export const guards = { - isSapiVolumeModel: (val: SapiVolumeModel) => val.type === "siibra/base-dataset" - && val.data.detail["neuroglancer/precomputed"] + +export function FeatureTypeGuard(input: SapiFeatureModel) { + if (input.type === "siibra/core/dataset") { + return input as SapiDatasetModel + } + if (input.type === "siibra/features/connectivity") { + return input as SapiParcellationFeatureMatrixModel + } + if (input.type === "siibra/features/receptor") { + return input as SapiRegionalFeatureReceptorModel + } + if (input.type === "siibra/features/voi") { + return input as SapiVOIDataResponse + } + if (input.type === "spy/serialization-error") { + return input as SapiSerializationErrorModel + } + throw new Error(`cannot parse type: ${input}`) } /** diff --git a/src/atlasComponents/sapiViews/core/atlas/dropdownAtlasSelector/dropdownAtlasSelector.component.ts b/src/atlasComponents/sapiViews/core/atlas/dropdownAtlasSelector/dropdownAtlasSelector.component.ts index ec13b3064..0da41ae9d 100644 --- a/src/atlasComponents/sapiViews/core/atlas/dropdownAtlasSelector/dropdownAtlasSelector.component.ts +++ b/src/atlasComponents/sapiViews/core/atlas/dropdownAtlasSelector/dropdownAtlasSelector.component.ts @@ -26,7 +26,6 @@ export class SapiViewsCoreAtlasAtlasDropdownSelector{ private store$: Store<any>, private sapi: SAPI, ){ - this.selectedAtlas$.subscribe(val => console.log('sel atlas changed', val)) } handleChangeAtlas({ value }) { diff --git a/src/atlasComponents/sapiViews/core/atlas/tmplParcSelector/tmplParcSelector.style.css b/src/atlasComponents/sapiViews/core/atlas/tmplParcSelector/tmplParcSelector.style.css index 0d8c1e199..49568298b 100644 --- a/src/atlasComponents/sapiViews/core/atlas/tmplParcSelector/tmplParcSelector.style.css +++ b/src/atlasComponents/sapiViews/core/atlas/tmplParcSelector/tmplParcSelector.style.css @@ -13,7 +13,7 @@ .loading-overlay { - position: absolute; + position: fixed; width: 100%; height: 100%; top: 0; @@ -32,3 +32,10 @@ grid-column: 2; grid-row: 2; } + +/* necessary to align the tiles to the start of grid tile */ +sxplr-sapiviews-core-space-tile, +sxplr-sapiviews-core-parcellation-tile +{ + height: 100%; +} \ No newline at end of file diff --git a/src/atlasComponents/sapiViews/core/atlas/tmplParcSelector/tmplParcSelector.template.html b/src/atlasComponents/sapiViews/core/atlas/tmplParcSelector/tmplParcSelector.template.html index f98de1d0b..944f2bc95 100644 --- a/src/atlasComponents/sapiViews/core/atlas/tmplParcSelector/tmplParcSelector.template.html +++ b/src/atlasComponents/sapiViews/core/atlas/tmplParcSelector/tmplParcSelector.template.html @@ -5,61 +5,68 @@ [@toggleAtlasLayerSelector]="selectorExpanded" (@toggleAtlasLayerSelector.done)="atlasSelectorTour?.attachTo(selectorExpanded ? selectorPanelTemplateRef : null)" #selectorPanelTmpl> + <mat-card-content> - <!-- templates --> - <mat-card-subtitle> - {{ CONST.ATLAS_SELECTOR_LABEL_SPACES }} - </mat-card-subtitle> - - <!-- template grid and tiles --> - <mat-grid-list cols="3" - rowHeight="2:3" - gutterSize="16"> - - <mat-grid-tile *ngFor="let template of availableTemplates$ | async; trackBy: trackbyAtId" - [attr.aria-checked]="(selectedTemplate$ | async)?.['@id'] === template['@id']"> - <sxplr-sapiviews-core-space-tile - [sxplr-sapiviews-core-space-tile-space]="template" - [sxplr-sapiviews-core-space-tile-selected]="(selectedTemplate$ | async)?.['@id'] === template['@id']" - (click)="selectTemplate(template)"> - </sxplr-sapiviews-core-space-tile> - </mat-grid-tile> - </mat-grid-list> - - <mat-divider></mat-divider> - - <!-- parcellations --> - <mat-card-subtitle class="mt-2"> - {{ CONST.ATLAS_SELECTOR_LABEL_PARC_MAPS }} - </mat-card-subtitle> - - <mat-grid-list cols="3" + <!-- templates --> + <mat-card-subtitle> + {{ CONST.ATLAS_SELECTOR_LABEL_SPACES }} + </mat-card-subtitle> + + <!-- template grid and tiles --> + <mat-grid-list cols="3" rowHeight="2:3" gutterSize="16"> - <mat-grid-tile *ngFor="let parc of availableParcellations$ | async | filterUnsupportedParc | filterGroupedParcs"> - <sxplr-sapiviews-core-parcellation-tile - [sxplr-sapiviews-core-parcellation-tile-parcellation]="parc" - (sxplr-sapiviews-core-parcellation-tile-onclick-parc)="selectParcellation($event)"> - - </sxplr-sapiviews-core-parcellation-tile> + <mat-grid-tile *ngFor="let template of availableTemplates$ | async; trackBy: trackbyAtId" + [attr.aria-checked]="(selectedTemplate$ | async)?.['@id'] === template['@id']"> + + <sxplr-sapiviews-core-space-tile + [sxplr-sapiviews-core-space-tile-space]="template" + [sxplr-sapiviews-core-space-tile-selected]="(selectedTemplate$ | async)?.['@id'] === template['@id']" + (click)="selectTemplate(template)"> + </sxplr-sapiviews-core-space-tile> </mat-grid-tile> + </mat-grid-list> - <mat-grid-tile *ngFor="let group of availableParcellations$ | async | filterUnsupportedParc | filterGroupedParcs : true | filterUnsupportedParc"> - <sxplr-sapiviews-core-parcellation-tile - [sxplr-sapiviews-core-parcellation-tile-parcellation]="group" - (sxplr-sapiviews-core-parcellation-tile-onclick-parc)="selectParcellation($event)"> + <mat-divider></mat-divider> - </sxplr-sapiviews-core-parcellation-tile> - </mat-grid-tile> - </mat-grid-list> -</mat-card-content> + <!-- parcellations --> + <mat-card-subtitle class="mt-2"> + {{ CONST.ATLAS_SELECTOR_LABEL_PARC_MAPS }} + </mat-card-subtitle> + + <mat-grid-list cols="3" + rowHeight="2:3" + gutterSize="16"> + + <mat-grid-tile *ngFor="let parc of availableParcellations$ | async | filterUnsupportedParc | filterGroupedParcs"> + <sxplr-sapiviews-core-parcellation-tile + [sxplr-sapiviews-core-parcellation-tile-parcellation]="parc" + (sxplr-sapiviews-core-parcellation-tile-onclick-parc)="selectParcellation($event)"> + + </sxplr-sapiviews-core-parcellation-tile> + </mat-grid-tile> + + <mat-grid-tile *ngFor="let group of availableParcellations$ | async | filterUnsupportedParc | filterGroupedParcs : true | filterUnsupportedParc"> + <sxplr-sapiviews-core-parcellation-tile + [sxplr-sapiviews-core-parcellation-tile-parcellation]="group" + (sxplr-sapiviews-core-parcellation-tile-onclick-parc)="selectParcellation($event)"> + + </sxplr-sapiviews-core-parcellation-tile> + </mat-grid-tile> + </mat-grid-list> + + </mat-card-content> + + <div [ngClass]="{ + 'sxplr-d-none': !(showLoadingOverlay$ | async) + }" + class="loading-overlay"> + <spinner-cmp class="spinner"></spinner-cmp> + </div> + -<div [hidden]="!(showLoadingOverlay$ | async)" - class="loading-overlay"> - <spinner-cmp class="spinner"></spinner-cmp> -</div> </mat-card> <!-- place holder when not expanded --> diff --git a/src/atlasComponents/sapiViews/core/datasets/dataset/dataset.stories.ts b/src/atlasComponents/sapiViews/core/datasets/dataset/dataset.stories.ts index 0a288e0f9..ff2a04062 100644 --- a/src/atlasComponents/sapiViews/core/datasets/dataset/dataset.stories.ts +++ b/src/atlasComponents/sapiViews/core/datasets/dataset/dataset.stories.ts @@ -36,7 +36,7 @@ const Template: Story<DatasetView> = (args: DatasetView, { loaded }) => { const loadFeat = async () => { const features = await getHoc1Features() - const receptorfeat = features.find(f => f.type === "siibra/receptor") + const receptorfeat = features.find(f => f.type === "siibra/core/dataset") const feature = await getHoc1FeatureDetail(receptorfeat["@id"]) return { feature diff --git a/src/atlasComponents/sapiViews/core/parcellation/chip/parcellation.chip.component.ts b/src/atlasComponents/sapiViews/core/parcellation/chip/parcellation.chip.component.ts new file mode 100644 index 000000000..13358ff46 --- /dev/null +++ b/src/atlasComponents/sapiViews/core/parcellation/chip/parcellation.chip.component.ts @@ -0,0 +1,26 @@ +import { Component, Input, Output, EventEmitter } from "@angular/core"; +import { SapiParcellationModel } from "src/atlasComponents/sapi/type"; + +@Component({ + selector: `sxplr-sapiviews-core-parcellation-chip`, + templateUrl: './parcellation.chip.template.html', + styleUrls: [ + `./parcellation.chip.style.css` + ], +}) + +export class SapiViewsCoreParcellationParcellationChip { + + @Input('sxplr-sapiviews-core-parcellation-chip-parcellation') + parcellation: SapiParcellationModel + + @Input('sxplr-sapiviews-core-parcellation-chip-color') + color: 'default' | 'primary' | 'accent' | 'warn' = "default" + + @Output('sxplr-sapiviews-core-parcellation-chip-onclick') + onClick = new EventEmitter<MouseEvent>() + + click(event: MouseEvent) { + this.onClick.emit(event) + } +} diff --git a/src/atlasComponents/sapiViews/core/parcellation/chip/parcellation.chip.stories.ts b/src/atlasComponents/sapiViews/core/parcellation/chip/parcellation.chip.stories.ts new file mode 100644 index 000000000..3210779ec --- /dev/null +++ b/src/atlasComponents/sapiViews/core/parcellation/chip/parcellation.chip.stories.ts @@ -0,0 +1,118 @@ +import { CommonModule } from "@angular/common" +import { HttpClientModule } from "@angular/common/http" +import { Meta, moduleMetadata, Story } from "@storybook/angular" +import { SAPI, SapiParcellationModel } from "src/atlasComponents/sapi" +import { atlasId, getAtlas, provideDarkTheme, getParc } from "src/atlasComponents/sapi/stories.base" +import { AngularMaterialModule } from "src/sharedModules" +import { SapiViewsCoreParcellationModule } from "../module" +import { SapiViewsCoreParcellationParcellationChip } from "./parcellation.chip.component" + + +export default { + component: SapiViewsCoreParcellationParcellationChip, + decorators: [ + moduleMetadata({ + imports: [ + CommonModule, + HttpClientModule, + SapiViewsCoreParcellationModule, + AngularMaterialModule, + ], + providers: [ + SAPI, + ...provideDarkTheme, + ], + declarations: [] + }) + ], +} as Meta + +const Template: Story<SapiViewsCoreParcellationParcellationChip> = (args: SapiViewsCoreParcellationParcellationChip, { loaded, parameters }) => { + const { + parcellation + } = loaded + const { + contentProjection + } = parameters + + return ({ + props: { + ...args, + parcellation + }, + template: ` + <sxplr-sapiviews-core-parcellation-chip> + ${contentProjection || ''} + </sxplr-sapiviews-core-parcellation-chip> + ` + }) +} +Template.loaders = [] + +const asyncLoader = async (_atlasId: string) => { + const parcs: SapiParcellationModel[] = [] + const atlasDetail = await getAtlas(_atlasId) + + for (const parc of atlasDetail.parcellations) { + const parcDetail = await getParc(atlasDetail['@id'], parc['@id']) + parcs.push(parcDetail) + } + return { + parcs + } +} + +const getContentProjection = ({ prefix = null, suffix = null }) => { + let returnVal = `` + if (prefix) { + returnVal += `<div prefix>${prefix}</div>` + } + if (suffix) { + returnVal += `<div suffix>${suffix}</div>` + } + return returnVal +} + +export const Default = Template.bind({}) +Default.loaders = [ + async () => { + const { + parcs + } = await asyncLoader(atlasId.human) + return { + parcellation: parcs[0] + } + } +] + +export const Prefix = Template.bind({}) +Prefix.loaders = [ + ...Default.loaders +] +Prefix.parameters = { + contentProjection: getContentProjection({ + prefix: `PREFIX`, + }) +} + +export const Suffix = Template.bind({}) +Suffix.loaders = [ + ...Default.loaders +] +Suffix.parameters = { + contentProjection: getContentProjection({ + suffix: `SUFFIX`, + }) +} + + +export const PrefixSuffix = Template.bind({}) +PrefixSuffix.loaders = [ + ...Default.loaders +] +PrefixSuffix.parameters = { + contentProjection: getContentProjection({ + prefix: `PREFIX`, + suffix: `SUFFIX`, + }) +} diff --git a/src/atlasComponents/parcellationRegion/regionSimple/regionSimple.style.css b/src/atlasComponents/sapiViews/core/parcellation/chip/parcellation.chip.style.css similarity index 100% rename from src/atlasComponents/parcellationRegion/regionSimple/regionSimple.style.css rename to src/atlasComponents/sapiViews/core/parcellation/chip/parcellation.chip.style.css diff --git a/src/atlasComponents/sapiViews/core/parcellation/chip/parcellation.chip.template.html b/src/atlasComponents/sapiViews/core/parcellation/chip/parcellation.chip.template.html new file mode 100644 index 000000000..417575bf6 --- /dev/null +++ b/src/atlasComponents/sapiViews/core/parcellation/chip/parcellation.chip.template.html @@ -0,0 +1,16 @@ +<mat-chip-list *ngIf="parcellation"> + <mat-chip [selected]="color !== 'default'" + (click)="click($event)" + [color]="color"> + + <ng-content select="[prefix]"> + </ng-content> + + <span class="mat-body sxplr-white-space-nowrap"> + {{ parcellation.name }} + </span> + + <ng-content select="[suffix]"> + </ng-content> + </mat-chip> +</mat-chip-list> \ No newline at end of file diff --git a/src/atlasComponents/sapiViews/core/parcellation/filterUnsupportedParc.pipe.ts b/src/atlasComponents/sapiViews/core/parcellation/filterUnsupportedParc.pipe.ts index 188a83b0a..5d2412cdd 100644 --- a/src/atlasComponents/sapiViews/core/parcellation/filterUnsupportedParc.pipe.ts +++ b/src/atlasComponents/sapiViews/core/parcellation/filterUnsupportedParc.pipe.ts @@ -20,7 +20,7 @@ const hideGroup = [ export class FilterUnsupportedParcPipe implements PipeTransform{ public transform<T extends Filterables>(parcs: T[]): T[] { - return parcs.filter(p => { + return (parcs || []).filter(p => { if (p instanceof GroupedParcellation) { return hideGroup.indexOf(p.name) < 0 } diff --git a/src/atlasComponents/sapiViews/core/parcellation/module.ts b/src/atlasComponents/sapiViews/core/parcellation/module.ts index 2ef93bef5..fca236799 100644 --- a/src/atlasComponents/sapiViews/core/parcellation/module.ts +++ b/src/atlasComponents/sapiViews/core/parcellation/module.ts @@ -1,10 +1,17 @@ import { CommonModule } from "@angular/common"; -import { NgModule } from "@angular/core"; +import { APP_INITIALIZER, NgModule } from "@angular/core"; +import { Store } from "@ngrx/store"; import { ComponentsModule } from "src/components"; import { AngularMaterialModule } from "src/sharedModules"; +import { atlasAppearance } from "src/state"; +import { UtilModule } from "src/util"; +import { SapiViewsCoreParcellationParcellationChip } from "./chip/parcellation.chip.component"; import { FilterGroupedParcellationPipe } from "./filterGroupedParcellations.pipe"; import { FilterUnsupportedParcPipe } from "./filterUnsupportedParc.pipe"; +import { ParcellationIsBaseLayer } from "./parcellationIsBaseLayer.pipe"; +import { ParcellationVisibilityService } from "./parcellationVis.service"; import { PreviewParcellationUrlPipe } from "./previewParcellationUrl.pipe"; +import { SapiViewsCoreParcellationParcellationSmartChip } from "./smartChip/parcellation.smartChip.component"; import { SapiViewsCoreParcellationParcellationTile } from "./tile/parcellation.tile.component"; @NgModule({ @@ -12,17 +19,41 @@ import { SapiViewsCoreParcellationParcellationTile } from "./tile/parcellation.t CommonModule, ComponentsModule, AngularMaterialModule, + UtilModule, ], declarations: [ SapiViewsCoreParcellationParcellationTile, + SapiViewsCoreParcellationParcellationChip, + SapiViewsCoreParcellationParcellationSmartChip, PreviewParcellationUrlPipe, FilterGroupedParcellationPipe, FilterUnsupportedParcPipe, + ParcellationIsBaseLayer, ], exports: [ SapiViewsCoreParcellationParcellationTile, + SapiViewsCoreParcellationParcellationChip, + SapiViewsCoreParcellationParcellationSmartChip, FilterGroupedParcellationPipe, FilterUnsupportedParcPipe, + ], + providers: [ + ParcellationVisibilityService, + { + provide: APP_INITIALIZER, + useFactory: (store: Store, svc: ParcellationVisibilityService) => { + svc.visibility$.subscribe(val => { + store.dispatch( + atlasAppearance.actions.setShowDelineation({ + flag: val + }) + ) + }) + return () => Promise.resolve() + }, + multi: true, + deps: [ Store, ParcellationVisibilityService ] + } ] }) diff --git a/src/atlasComponents/sapiViews/core/parcellation/parcellationIsBaseLayer.pipe.ts b/src/atlasComponents/sapiViews/core/parcellation/parcellationIsBaseLayer.pipe.ts new file mode 100644 index 000000000..7bd6f31f1 --- /dev/null +++ b/src/atlasComponents/sapiViews/core/parcellation/parcellationIsBaseLayer.pipe.ts @@ -0,0 +1,22 @@ +import { Pipe, PipeTransform } from "@angular/core"; +import { SapiParcellationModel } from "src/atlasComponents/sapi/type"; + +const baseLayerIds = [ + "minds/core/parcellationatlas/v1.0.0/94c1125b-b87e-45e4-901c-00daee7f2579-290", + "minds/core/parcellationatlas/v1.0.0/94c1125b-b87e-45e4-901c-00daee7f2579-25", + "minds/core/parcellationatlas/v1.0.0/94c1125b-b87e-45e4-901c-00daee7f2579", +] + +@Pipe({ + name: 'parcellationIsBaseLayer', + pure: true +}) + +export class ParcellationIsBaseLayer implements PipeTransform{ + public transform(parc: SapiParcellationModel): boolean { + /** + * currently, the only base layer is cyto maps + */ + return baseLayerIds.includes(parc["@id"]) + } +} diff --git a/src/atlasComponents/sapiViews/core/parcellation/parcellationVis.service.ts b/src/atlasComponents/sapiViews/core/parcellation/parcellationVis.service.ts new file mode 100644 index 000000000..3eb329a9b --- /dev/null +++ b/src/atlasComponents/sapiViews/core/parcellation/parcellationVis.service.ts @@ -0,0 +1,21 @@ +import { Injectable } from "@angular/core"; +import { BehaviorSubject } from "rxjs"; + +@Injectable({ + providedIn: 'root' +}) + +export class ParcellationVisibilityService { + private _visibility$ = new BehaviorSubject<boolean>(true) + public readonly visibility$ = this._visibility$.asObservable() + + setVisibility(flag: boolean) { + this._visibility$.next(flag) + } + + toggleVisibility(){ + this.setVisibility( + !this._visibility$.getValue() + ) + } +} diff --git a/src/atlasComponents/sapiViews/core/parcellation/smartChip/parcellation.smartChip.component.ts b/src/atlasComponents/sapiViews/core/parcellation/smartChip/parcellation.smartChip.component.ts new file mode 100644 index 000000000..e28c7bec0 --- /dev/null +++ b/src/atlasComponents/sapiViews/core/parcellation/smartChip/parcellation.smartChip.component.ts @@ -0,0 +1,99 @@ +import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from "@angular/core"; +import { Observable } from "rxjs"; +import { SapiParcellationModel } from "src/atlasComponents/sapi/type"; +import { ParcellationVisibilityService } from "../parcellationVis.service"; + +@Component({ + selector: `sxplr-sapiviews-core-parcellation-smartchip`, + templateUrl: `./parcellation.smartChip.template.html`, + styleUrls: [ + `./parcellation.smartChip.style.css` + ] +}) + +export class SapiViewsCoreParcellationParcellationSmartChip implements OnChanges{ + + @Input('sxplr-sapiviews-core-parcellation-smartchip-parcellation') + parcellation: SapiParcellationModel + + @Input('sxplr-sapiviews-core-parcellation-smartchip-all-parcellations') + parcellations: SapiParcellationModel[] + + @Output('sxplr-sapiviews-core-parcellation-smartchip-dismiss-nonbase-layer') + onDismiss = new EventEmitter<SapiParcellationModel>() + + @Output('sxplr-sapiviews-core-parcellation-smartchip-select-parcellation') + onSelectParcellation = new EventEmitter<SapiParcellationModel>() + + constructor( + private svc: ParcellationVisibilityService + ){ + + } + + otherVersions: SapiParcellationModel[] + + ngOnChanges() { + this.otherVersions = [] + if (!this.parcellation) { + return + } + this.otherVersions = [ this.parcellation ] + if (!this.parcellations || this.parcellations.length === 0) { + return + } + if (!this.parcellation.version) { + return + } + + this.otherVersions = [] + const getTraverse = (key: 'prev' | 'next') => (parc: SapiParcellationModel) => { + if (!parc.version) { + throw new Error(`parcellation ${parc.name} does not have version defined!`) + } + if (!parc.version[key]) { + return null + } + const found = this.parcellations.find(p => p["@id"] === parc.version[key]["@id"]) + if (!found) { + throw new Error(`parcellation ${parc.name} references ${parc.version[key]['@id']} as ${key} version, but it cannot be found.`) + } + return found + } + + const findNewer = getTraverse('next') + const findOlder = getTraverse('prev') + + const newest = (() => { + let cursor = this.parcellation + let newest: SapiParcellationModel + while (cursor) { + newest = cursor + cursor = findNewer(cursor) + } + return newest + })() + + let cursor: SapiParcellationModel = newest + while (cursor) { + this.otherVersions.push(cursor) + cursor = findOlder(cursor) + } + } + + parcellationVisibility$: Observable<boolean> = this.svc.visibility$ + + toggleParcellationVisibility(){ + this.svc.toggleVisibility() + } + + dismiss(){ + this.onDismiss.emit(this.parcellation) + } + + selectParcellation(parc: SapiParcellationModel){ + if (parc === this.parcellation) return + + console.log('select parcellation', parc) + } +} diff --git a/src/atlasComponents/sapiViews/core/parcellation/smartChip/parcellation.smartChip.stories.ts b/src/atlasComponents/sapiViews/core/parcellation/smartChip/parcellation.smartChip.stories.ts new file mode 100644 index 000000000..7f3338a6a --- /dev/null +++ b/src/atlasComponents/sapiViews/core/parcellation/smartChip/parcellation.smartChip.stories.ts @@ -0,0 +1,108 @@ +import { CommonModule } from "@angular/common" +import { HttpClientModule } from "@angular/common/http" +import { Component, EventEmitter, Input, Output } from "@angular/core" +import { Meta, moduleMetadata, Story } from "@storybook/angular" +import { action } from "@storybook/addon-actions" +import { SAPI, SapiParcellationModel } from "src/atlasComponents/sapi" +import { atlasId, getAtlas, provideDarkTheme, getParc, getAtlases } from "src/atlasComponents/sapi/stories.base" +import { AngularMaterialModule } from "src/sharedModules" +import { SapiViewsCoreParcellationModule } from "../module" + +@Component({ + selector: `parc-smart-chip-wrapper`, + template: ` + <mat-accordion> + <mat-expansion-panel *ngFor="let item of parcRecords | keyvalue"> + <mat-expansion-panel-header> + {{ item.key }} + </mat-expansion-panel-header> + + <div class="sxplr-of-x-scroll sxplr-white-space-nowrap"> + <sxplr-sapiviews-core-parcellation-smartchip *ngFor="let parc of item.value | filterUnsupportedParc" + [sxplr-sapiviews-core-parcellation-smartchip-parcellation]="parc" + [sxplr-sapiviews-core-parcellation-smartchip-all-parcellations]="item.value" + (sxplr-sapiviews-core-parcellation-smartchip-select-parcellation)="selectParcellation($event)"> + </sxplr-sapiviews-core-parcellation-smartchip> + </div> + + </mat-expansion-panel> + </mat-accordion> + `, + styles: [ + `sxplr-sapiviews-core-parcellation-chip { display: block; }` + ] +}) + +class ParcSmartChipWrapper{ + @Input() + parcRecords: Record<string, SapiParcellationModel[]> = {} + + selectParcellation(parc: SapiParcellationModel){ + + } +} + + +export default { + component: ParcSmartChipWrapper, + decorators: [ + moduleMetadata({ + imports: [ + CommonModule, + HttpClientModule, + SapiViewsCoreParcellationModule, + AngularMaterialModule, + ], + providers: [ + SAPI, + ...provideDarkTheme, + ], + declarations: [] + }) + ], +} as Meta + +const Template: Story<ParcSmartChipWrapper> = (args: ParcSmartChipWrapper, { loaded, parameters }) => { + const { + parcRecords + } = loaded + + return ({ + props: { + ...args, + selectParcellation: action("selectParcellation"), + parcRecords + }, + }) +} +Template.loaders = [] + +const asyncLoader = async () => { + const parcRecords: Record<string, SapiParcellationModel[]> = {} + + for (const species in atlasId) { + + const atlasDetail = await getAtlas(atlasId[species]) + parcRecords[species] = [] + for (const parc of atlasDetail.parcellations) { + const parcDetail = await getParc(atlasDetail['@id'], parc['@id']) + parcRecords[species].push(parcDetail) + } + } + + return { + parcRecords + } +} + +export const Default = Template.bind({}) +Default.loaders = [ + async () => { + const { + parcRecords + } = await asyncLoader() + return { + parcRecords + } + } +] diff --git a/src/services/state/uiState.store.spec.ts b/src/atlasComponents/sapiViews/core/parcellation/smartChip/parcellation.smartChip.style.css similarity index 100% rename from src/services/state/uiState.store.spec.ts rename to src/atlasComponents/sapiViews/core/parcellation/smartChip/parcellation.smartChip.style.css diff --git a/src/atlasComponents/sapiViews/core/parcellation/smartChip/parcellation.smartChip.template.html b/src/atlasComponents/sapiViews/core/parcellation/smartChip/parcellation.smartChip.template.html new file mode 100644 index 000000000..0473218f9 --- /dev/null +++ b/src/atlasComponents/sapiViews/core/parcellation/smartChip/parcellation.smartChip.template.html @@ -0,0 +1,49 @@ +<mat-menu #otherParcMenu="matMenu" + [hasBackdrop]="false" + class="sxplr-bg-none sxplr-of-x-hidden sxplr-box-shadow-none overwrite-max-width-80vw"> + <div (iav-outsideClick)="menuTrigger.closeMenu()"> + + <sxplr-sapiviews-core-parcellation-chip *ngFor="let parc of otherVersions" + [sxplr-sapiviews-core-parcellation-chip-parcellation]="parc" + [sxplr-sapiviews-core-parcellation-chip-color]="parcellation === parc ? 'primary' : 'default'" + (click)="selectParcellation(parc)"> + + </sxplr-sapiviews-core-parcellation-chip> + </div> + +</mat-menu> + +<sxplr-sapiviews-core-parcellation-chip + [ngClass]="{ + 'sxplr-muted': !(parcellationVisibility$ | async) + }" + class="sxplr-d-inline-block" + [sxplr-sapiviews-core-parcellation-chip-parcellation]="parcellation" + [sxplr-sapiviews-core-parcellation-chip-color]="(parcellation | parcellationIsBaseLayer) ? 'default' : 'primary'" + (sxplr-sapiviews-core-parcellation-chip-onclick)="menuTrigger.toggleMenu()" + [matMenuTriggerFor]="otherParcMenu" + #menuTrigger="matMenuTrigger" + > + + <div prefix class="sxplr-scale-70"> + <button mat-mini-fab + [color]="(parcellationVisibility$ | async) ? 'primary' : 'default'" + iav-stop="mousedown click" + (click)="toggleParcellationVisibility()"> + <i class="fas" + [ngClass]="(parcellationVisibility$ | async) ? 'fa-eye': 'fa-eye-slash'"> + </i> + </button> + </div> + + <div *ngIf="!(parcellation | parcellationIsBaseLayer)" + class="sxplr-scale-70" + suffix> + <button mat-mini-fab + color="primary" + iav-stop="mousedown click" + (click)="dismiss()"> + <i class="fas fa-times"></i> + </button> + </div> +</sxplr-sapiviews-core-parcellation-chip> \ No newline at end of file diff --git a/src/atlasComponents/sapiViews/core/parcellation/tile/parcellation.tile.template.html b/src/atlasComponents/sapiViews/core/parcellation/tile/parcellation.tile.template.html index 774814d73..10a48ac5e 100644 --- a/src/atlasComponents/sapiViews/core/parcellation/tile/parcellation.tile.template.html +++ b/src/atlasComponents/sapiViews/core/parcellation/tile/parcellation.tile.template.html @@ -11,6 +11,7 @@ *ngFor="let parc of subParcellations"> <tile-cmp *ngIf="parc" + class="iv-custom-comp text" [tile-text]="parc.name" [tile-image-src]="parc | previewParcellationUrl" [tile-selected]="selected" diff --git a/src/atlasComponents/sapiViews/core/region/region/rich/region.rich.component.ts b/src/atlasComponents/sapiViews/core/region/region/rich/region.rich.component.ts index 60eb0e497..1d44db4e8 100644 --- a/src/atlasComponents/sapiViews/core/region/region/rich/region.rich.component.ts +++ b/src/atlasComponents/sapiViews/core/region/region/rich/region.rich.component.ts @@ -37,11 +37,11 @@ export class SapiViewsCoreRegionRegionRich extends SapiViewsCoreRegionRegionBase } handleExpansionPanelClosedEv(title: string){ - console.log("title", title) + } handleExpansionPanelAfterExpandEv(title: string) { - console.log("title", title) + } activePanelTitles$: Observable<string[]> = new Subject() diff --git a/src/atlasComponents/sapiViews/features/entryListItem/entryListItem.component.ts b/src/atlasComponents/sapiViews/features/entryListItem/entryListItem.component.ts index a7c5f4a84..d7fcb6758 100644 --- a/src/atlasComponents/sapiViews/features/entryListItem/entryListItem.component.ts +++ b/src/atlasComponents/sapiViews/features/entryListItem/entryListItem.component.ts @@ -1,6 +1,6 @@ import { Component, Input } from "@angular/core"; import { SapiFeatureModel, SapiRegionalFeatureModel, SapiSpatialFeatureModel, SapiParcellationFeatureModel } from "src/atlasComponents/sapi"; -import { SapiParcellationFeatureMatrixModel } from "src/atlasComponents/sapi/type"; +import { SapiDatasetModel, SapiParcellationFeatureMatrixModel, SapiRegionalFeatureReceptorModel, SapiSerializationErrorModel, SapiVOIDataResponse } from "src/atlasComponents/sapi/type"; @Component({ selector: `sxplr-sapiviews-features-entry-list-item`, @@ -19,11 +19,21 @@ export class SapiViewsFeaturesEntryListItem{ get label(): string{ if (!this.feature) return null - if (this.feature.type === "siibra/base-dataset" || this.feature.type === "siibra/receptor") { - return (this.feature as (SapiRegionalFeatureModel | SapiSpatialFeatureModel)).metadata.fullName + const { type } = this.feature + if ( + type === "siibra/core/dataset" || + type === "siibra/features/receptor" || + type === "siibra/features/voi" + ) { + return (this.feature as (SapiDatasetModel | SapiRegionalFeatureReceptorModel | SapiVOIDataResponse) ).metadata.fullName } - return (this.feature as SapiParcellationFeatureMatrixModel).name - } - constructor(){ + + if (type === "siibra/features/connectivity") { + return (this.feature as SapiParcellationFeatureMatrixModel).name + } + if (type === "spy/serialization-error") { + return (this.feature as SapiSerializationErrorModel).message + } + return "Unknown type" } } diff --git a/src/atlasComponents/sapiViews/features/entryListItem/entryListItem.template.html b/src/atlasComponents/sapiViews/features/entryListItem/entryListItem.template.html index b6cfc81d0..5bbc24ee2 100644 --- a/src/atlasComponents/sapiViews/features/entryListItem/entryListItem.template.html +++ b/src/atlasComponents/sapiViews/features/entryListItem/entryListItem.template.html @@ -1,8 +1,8 @@ <div matRipple [matRippleDisabled]="!ripple" class="sxplr-p-2"> - <mat-chip-list [ngSwitch]="feature.type" class="scale-80 transform-origin-left-center"> - <mat-chip *ngSwitchCase="'siibra/receptor'" + <mat-chip-list [ngSwitch]="feature.type" class="sxplr-scale-80 transform-origin-left-center"> + <mat-chip *ngSwitchCase="'siibra/features/receptor'" [color]="feature | featureBadgeColour" selected> {{ feature | featureBadgeName }} diff --git a/src/atlasComponents/sapiViews/features/featureBadgeColor.pipe.ts b/src/atlasComponents/sapiViews/features/featureBadgeColor.pipe.ts index 9d8be8736..d3caf6b13 100644 --- a/src/atlasComponents/sapiViews/features/featureBadgeColor.pipe.ts +++ b/src/atlasComponents/sapiViews/features/featureBadgeColor.pipe.ts @@ -8,7 +8,7 @@ import { SapiFeatureModel } from "src/atlasComponents/sapi"; export class FeatureBadgeColourPipe implements PipeTransform{ public transform(regionalFeature: SapiFeatureModel) { - if (regionalFeature.type === "siibra/receptor") { + if (regionalFeature.type === "siibra/features/receptor") { return "accent" } return "default" diff --git a/src/atlasComponents/sapiViews/features/featureBadgeName.pipe.ts b/src/atlasComponents/sapiViews/features/featureBadgeName.pipe.ts index 3b0faa80c..34e4f7dc6 100644 --- a/src/atlasComponents/sapiViews/features/featureBadgeName.pipe.ts +++ b/src/atlasComponents/sapiViews/features/featureBadgeName.pipe.ts @@ -8,7 +8,7 @@ import { SapiFeatureModel } from "src/atlasComponents/sapi"; export class FeatureBadgeNamePipe implements PipeTransform{ public transform(regionalFeature: SapiFeatureModel) { - if (regionalFeature.type === "siibra/receptor") { + if (regionalFeature.type === "siibra/features/receptor") { return "receptor density" } return null diff --git a/src/atlasComponents/sapiViews/features/receptors/autoradiography/autoradiograph.stories.ts b/src/atlasComponents/sapiViews/features/receptors/autoradiography/autoradiograph.stories.ts index 281d26289..5ebfdce98 100644 --- a/src/atlasComponents/sapiViews/features/receptors/autoradiography/autoradiograph.stories.ts +++ b/src/atlasComponents/sapiViews/features/receptors/autoradiography/autoradiograph.stories.ts @@ -109,7 +109,7 @@ const loadFeat = async () => { const region = await getHoc1Left() const space = await getMni152() const features = await getHoc1Features() - const receptorfeat = features.find(f => f.type === "siibra/receptor") + const receptorfeat = features.find(f => f.type === "siibra/features/receptor") const feature = await getHoc1FeatureDetail(receptorfeat["@id"]) return { atlas, diff --git a/src/atlasComponents/sapiViews/features/receptors/base.ts b/src/atlasComponents/sapiViews/features/receptors/base.ts index bfb7b9413..b393969c2 100644 --- a/src/atlasComponents/sapiViews/features/receptors/base.ts +++ b/src/atlasComponents/sapiViews/features/receptors/base.ts @@ -75,8 +75,8 @@ export abstract class BaseReceptor{ return } const result = await this.sapi.getRegion(this.atlas["@id"], this.parcellation["@id"], this.region.name).getFeatureInstance(this.featureId, this.template["@id"]) - if (result.type !== "siibra/receptor") { - throw new Error(`BaseReceptor Error. Expected .type to be "siibra/receptor", but was "${result.type}"`) + if (result.type !== "siibra/features/receptor") { + throw new Error(`BaseReceptor Error. Expected .type to be "siibra/features/receptor", but was "${result.type}"`) } return result } diff --git a/src/atlasComponents/sapiViews/features/receptors/entry/entry.stories.ts b/src/atlasComponents/sapiViews/features/receptors/entry/entry.stories.ts index 3adb01e1d..0c69f8216 100644 --- a/src/atlasComponents/sapiViews/features/receptors/entry/entry.stories.ts +++ b/src/atlasComponents/sapiViews/features/receptors/entry/entry.stories.ts @@ -92,7 +92,7 @@ const loadFeat = async () => { const region = await getHoc1Left() const space = await getMni152() const features = await getHoc1Features() - const receptorfeat = features.find(f => f.type === "siibra/receptor") + const receptorfeat = features.find(f => f.type === "siibra/features/receptor") const feature = await getHoc1FeatureDetail(receptorfeat["@id"]) return { atlas, diff --git a/src/atlasComponents/sapiViews/features/receptors/fingerprint/fingerprint.stories.ts b/src/atlasComponents/sapiViews/features/receptors/fingerprint/fingerprint.stories.ts index ae8a161cf..7c5da4f82 100644 --- a/src/atlasComponents/sapiViews/features/receptors/fingerprint/fingerprint.stories.ts +++ b/src/atlasComponents/sapiViews/features/receptors/fingerprint/fingerprint.stories.ts @@ -91,7 +91,7 @@ const loadFeat = async () => { const region = await getHoc1Left() const space = await getMni152() const features = await getHoc1Features() - const receptorfeat = features.find(f => f.type === "siibra/receptor") + const receptorfeat = features.find(f => f.type === "siibra/features/receptor") const feature = await getHoc1FeatureDetail(receptorfeat["@id"]) return { atlas, diff --git a/src/atlasComponents/sapiViews/features/receptors/profile/profile.stories.ts b/src/atlasComponents/sapiViews/features/receptors/profile/profile.stories.ts index 39b199ef3..f4dc8b4bd 100644 --- a/src/atlasComponents/sapiViews/features/receptors/profile/profile.stories.ts +++ b/src/atlasComponents/sapiViews/features/receptors/profile/profile.stories.ts @@ -118,7 +118,7 @@ const loadFeat = async () => { const region = await getHoc1Left() const space = await getMni152() const features = await getHoc1Features() - const receptorfeat = features.find(f => f.type === "siibra/receptor") + const receptorfeat = features.find(f => f.type === "siibra/features/receptor") const feature = await getHoc1FeatureDetail(receptorfeat["@id"]) return { atlas, diff --git a/src/atlasViewer/atlasViewer.apiService.service.ts b/src/atlasViewer/atlasViewer.apiService.service.ts index f4529026d..eae9a9c68 100644 --- a/src/atlasViewer/atlasViewer.apiService.service.ts +++ b/src/atlasViewer/atlasViewer.apiService.service.ts @@ -5,11 +5,7 @@ import { select, Store } from "@ngrx/store"; import { Observable, Subject, Subscription, from, race, of, } from "rxjs"; import { distinctUntilChanged, map, filter, startWith, switchMap, catchError, mapTo, take, shareReplay } from "rxjs/operators"; import { DialogService } from "src/services/dialogService.service"; -import { - getLabelIndexMap, - getMultiNgIdsRegionsLabelIndexMap, - IavRootStoreInterface, -} from "src/services/stateStore.service"; + import { ClickInterceptor, CLICK_INTERCEPTOR_INJECTOR } from "src/util"; import { FRAGMENT_EMIT_RED } from "src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component"; import { IPluginManifest, PluginServices } from "src/plugin"; @@ -149,7 +145,7 @@ export class AtlasViewerAPIServices implements OnDestroy{ } constructor( - private store: Store<IavRootStoreInterface>, + private store: Store<any>, private dialogService: DialogService, private snackbar: MatSnackBar, private zone: NgZone, @@ -327,8 +323,9 @@ export class AtlasViewerAPIServices implements OnDestroy{ filter(p => !!p && p.regions), distinctUntilChanged() ).subscribe(parcellation => { - this.interactiveViewer.metadata.regionsLabelIndexMap = getLabelIndexMap(parcellation.regions) - this.interactiveViewer.metadata.layersRegionLabelIndexMap = getMultiNgIdsRegionsLabelIndexMap(parcellation) + // TODO rework plugin metadata + // this.interactiveViewer.metadata.regionsLabelIndexMap = getLabelIndexMap(parcellation.regions) + // this.interactiveViewer.metadata.layersRegionLabelIndexMap = getMultiNgIdsRegionsLabelIndexMap(parcellation) }) this.s.push( diff --git a/src/atlasViewer/atlasViewer.component.ts b/src/atlasViewer/atlasViewer.component.ts index 341b6c268..cca817c6b 100644 --- a/src/atlasViewer/atlasViewer.component.ts +++ b/src/atlasViewer/atlasViewer.component.ts @@ -12,14 +12,8 @@ import { } from "@angular/core"; import { Store, select } from "@ngrx/store"; import { Observable, Subscription, merge, timer, fromEvent } from "rxjs"; -import { map, filter, delay, switchMapTo, take, startWith } from "rxjs/operators"; +import { filter, delay, switchMapTo, take, startWith } from "rxjs/operators"; -import { - IavRootStoreInterface, - isDefined, -} from "../services/stateStore.service"; - -import { AGREE_COOKIE } from "src/services/state/uiState.store"; import { colorAnimation } from "./atlasViewer.animation" import { MouseHoverDirective } from "src/mouseoverModule"; import {MatSnackBar, MatSnackBarRef} from "@angular/material/snack-bar"; @@ -31,6 +25,7 @@ import { PureContantService } from "src/util"; import { ClickInterceptorService } from "src/glue"; import { environment } from 'src/environments/environment' import { DOCUMENT } from "@angular/common"; +import { userPreference } from "src/state" /** * TODO @@ -66,8 +61,6 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit { public ismobile: boolean = false public meetsRequirement: boolean = true - public sidePanelView$: Observable<string|null> - private snackbarRef: MatSnackBarRef<any> public onhoverLandmark$: Observable<{landmarkName: string, datasets: any} | null> @@ -79,7 +72,7 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit { private cookieDialogRef: MatDialogRef<any> constructor( - private store: Store<IavRootStoreInterface>, + private store: Store<any>, private pureConstantService: PureContantService, private matDialog: MatDialog, private rd: Renderer2, @@ -90,13 +83,6 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit { @Inject(DOCUMENT) private document, ) { - this.sidePanelView$ = this.store.pipe( - select('uiState'), - filter(state => isDefined(state)), - map(state => state.focusedSidePanel), - ) - - const error = this.el.nativeElement.getAttribute('data-error') if (error) { @@ -163,9 +149,8 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit { * TODO avoid creating new views in lifecycle hooks in general */ this.store.pipe( - select('uiState'), - select('agreedCookies'), - filter(agreed => !agreed), + select(userPreference.selectors.agreedToCookie), + filter(val => !val), delay(0), ).subscribe(() => { this.cookieDialogRef = this.matDialog.open(this.cookieAgreementComponent) @@ -207,9 +192,9 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit { public cookieClickedOk() { if (this.cookieDialogRef) { this.cookieDialogRef.close() } - this.store.dispatch({ - type: AGREE_COOKIE, - }) + this.store.dispatch( + userPreference.actions.agreeCookie() + ) } public quickTourFinale = { diff --git a/src/extra_styles.css b/src/extra_styles.css index 8b18c626e..3c9598b3f 100644 --- a/src/extra_styles.css +++ b/src/extra_styles.css @@ -874,4 +874,9 @@ quick-tour-unit svg stroke: rgb(255, 255, 255); stroke-linecap: round; stroke-linejoin: round; -} \ No newline at end of file +} + +.overwrite-max-width-80vw +{ + max-width: 80vw!important; +} diff --git a/src/layouts/currentLayout/currentLayout.component.ts b/src/layouts/currentLayout/currentLayout.component.ts index be31bf003..c5445d2cd 100644 --- a/src/layouts/currentLayout/currentLayout.component.ts +++ b/src/layouts/currentLayout/currentLayout.component.ts @@ -1,9 +1,7 @@ import { Component } from "@angular/core"; import { select, Store } from "@ngrx/store"; import { Observable } from "rxjs"; -import { startWith } from "rxjs/operators"; -import { SUPPORTED_PANEL_MODES } from "src/services/state/ngViewerState.store"; -import { ngViewerSelectorPanelMode } from "src/services/state/ngViewerState/selectors"; +import { userInterface } from "src/state" @Component({ selector: 'current-layout', @@ -15,15 +13,20 @@ import { ngViewerSelectorPanelMode } from "src/services/state/ngViewerState/sele export class CurrentLayout { - public supportedPanelModes = SUPPORTED_PANEL_MODES - public panelMode$: Observable<string> + public FOUR_PANEL: userInterface.PanelMode = "FOUR_PANEL" + + public panelModes: Record<string, userInterface.PanelMode> = { + FOUR_PANEL: "FOUR_PANEL", + H_ONE_THREE: "H_ONE_THREE", + SINGLE_PANEL: "SINGLE_PANEL", + V_ONE_THREE: "V_ONE_THREE" + } + public panelMode$: Observable<userInterface.PanelMode> = this.store$.pipe( + select(userInterface.selectors.panelMode) + ) constructor( private store$: Store<any>, ) { - this.panelMode$ = this.store$.pipe( - select(ngViewerSelectorPanelMode), - startWith(SUPPORTED_PANEL_MODES[0]), - ) } } diff --git a/src/layouts/currentLayout/currentLayout.template.html b/src/layouts/currentLayout/currentLayout.template.html index 9575932ed..9b52769e2 100644 --- a/src/layouts/currentLayout/currentLayout.template.html +++ b/src/layouts/currentLayout/currentLayout.template.html @@ -1,7 +1,7 @@ <ng-container [ngSwitch]="panelMode$ | async"> <layout-four-panel - *ngSwitchCase="supportedPanelModes[0]" - class="d-block w-100 h-100"> + *ngSwitchCase="panelModes.FOUR_PANEL" + class="d-block sxplr-w-100 sxplr-h-100"> <ng-container cell-i> <ng-content *ngTemplateOutlet="celli"></ng-content> </ng-container> @@ -16,8 +16,8 @@ </ng-container> </layout-four-panel> <layout-horizontal-one-three - *ngSwitchCase="supportedPanelModes[1]" - class="d-block w-100 h-100"> + *ngSwitchCase="panelModes.H_ONE_THREE" + class="d-block sxplr-w-100 sxplr-h-100"> <ng-container cell-i> <ng-content *ngTemplateOutlet="celli"></ng-content> </ng-container> @@ -32,8 +32,8 @@ </ng-container> </layout-horizontal-one-three> <layout-vertical-one-three - *ngSwitchCase="supportedPanelModes[2]" - class="d-block w-100 h-100"> + *ngSwitchCase="panelModes.V_ONE_THREE" + class="d-block sxplr-w-100 sxplr-h-100"> <ng-container cell-i> <ng-content *ngTemplateOutlet="celli"></ng-content> </ng-container> @@ -48,8 +48,8 @@ </ng-container> </layout-vertical-one-three> <layout-single-panel - *ngSwitchCase="supportedPanelModes[3]" - class="d-block w-100 h-100"> + *ngSwitchCase="panelModes.SINGLE_PANEL" + class="d-block sxplr-w-100 sxplr-h-100"> <ng-container cell-i> <ng-content *ngTemplateOutlet="celli"></ng-content> </ng-container> @@ -64,7 +64,7 @@ </ng-container> </layout-single-panel> <div *ngSwitchDefault> - A panel mode which I have never seen before ... + A panel mode which I have never seen before ... {{ panelMode$ | async }} </div> </ng-container> diff --git a/src/main.module.ts b/src/main.module.ts index 99766310f..329b27c1f 100644 --- a/src/main.module.ts +++ b/src/main.module.ts @@ -1,31 +1,27 @@ import { DragDropModule } from '@angular/cdk/drag-drop' import { CommonModule } from "@angular/common"; -import { NgModule } from "@angular/core"; +import { APP_INITIALIZER, NgModule } from "@angular/core"; import { FormsModule } from "@angular/forms"; -import { StoreModule, ActionReducer, Store, select } from "@ngrx/store"; +import { Store, select } from "@ngrx/store"; import { AngularMaterialModule } from 'src/sharedModules' import { AtlasViewer } from "./atlasViewer/atlasViewer.component"; import { ComponentsModule } from "./components/components.module"; import { LayoutModule } from "./layouts/layout.module"; -import { ngViewerState, uiState, userConfigState, UserConfigStateUseEffect, viewerConfigState } from "./services/stateStore.service"; import { UIModule } from "./ui/ui.module"; import { HttpClientModule } from "@angular/common/http"; -import { EffectsModule } from "@ngrx/effects"; import { AtlasWorkerService } from "./atlasViewer/atlasViewer.workerService.service"; import { WINDOW_MESSAGING_HANDLER_TOKEN } from 'src/messaging/types' import { ConfirmDialogComponent } from "./components/confirmDialog/confirmDialog.component"; import { DialogComponent } from "./components/dialog/dialog.component"; import { DialogService } from "./services/dialogService.service"; -import { NgViewerUseEffect } from "./services/state/ngViewerState.store"; import { UIService } from "./services/uiService.service"; import { FloatingContainerDirective } from "./util/directives/floatingContainer.directive"; import { FloatingMouseContextualContainerDirective } from "./util/directives/floatingMouseContextualContainer.directive"; import { ClickInterceptor, CLICK_INTERCEPTOR_INJECTOR, PureContantService, UtilModule } from "src/util"; import { SpotLightModule } from 'src/spotlight/spot-light.module' import { TryMeComponent } from "./ui/tryme/tryme.component"; -import { UiStateUseEffect } from "src/services/state/uiState.store"; import { TemplateCoordinatesTransformation } from "src/services/templateCoordinatesTransformation.service"; import { WidgetModule } from 'src/widget'; import { PluginModule } from './plugin/plugin.module'; @@ -35,9 +31,7 @@ import { AuthService } from './auth' import 'src/theme.scss' import { ClickInterceptorService } from './glue'; import { TOS_OBS_INJECTION_TOKEN } from './ui/kgtos'; -import { UiEffects } from './services/state/uiState/ui.effects'; import { MesssagingModule } from './messaging/module'; -import { ParcellationRegionModule } from './atlasComponents/parcellationRegion'; import { ViewerModule, VIEWERMODULE_DARKTHEME } from './viewerModule'; import { CookieModule } from './ui/cookieAgreement/module'; import { KgTosModule } from './ui/kgtos/module'; @@ -54,22 +48,12 @@ import { NotSupportedCmp } from './notSupportedCmp/notSupported.component'; import { atlasSelection, - annotation, - userInterface, - userInteraction, - plugins, + RootEffecsModule, + RootStoreModule, } from "./state" import { DARKTHEME } from './util/injectionTokens'; import { map } from 'rxjs/operators'; -export function debug(reducer: ActionReducer<any>): ActionReducer<any> { - return function(state, action) { - console.log('state', state); - console.log('action', action); - - return reducer(state, action); - }; -} @NgModule({ imports : [ @@ -88,36 +72,14 @@ export function debug(reducer: ActionReducer<any>): ActionReducer<any> { MesssagingModule, ViewerModule, SpotLightModule, - ParcellationRegionModule, CookieModule, KgTosModule, MouseoverModule, AtlasViewerRouterModule, QuickTourModule, - EffectsModule.forRoot([ - UserConfigStateUseEffect, - NgViewerUseEffect, - plugins.Effects, - UiStateUseEffect, - UiEffects, - atlasSelection.Effect, - ]), - StoreModule.forRoot({ - viewerConfigState, - ngViewerState, - uiState, - userConfigState, - [atlasSelection.nameSpace]: atlasSelection.reducer, - [userInterface.nameSpace]: userInterface.reducer, - [userInteraction.nameSpace]: userInteraction.reducer, - [annotation.nameSpace]: annotation.reducer, - [plugins.nameSpace]: plugins.reducer, - },{ - metaReducers: [ - // debug, - ] - }), + RootEffecsModule, + RootStoreModule, HttpClientModule, ], declarations : [ @@ -224,6 +186,15 @@ export function debug(reducer: ActionReducer<any>): ActionReducer<any> { map(tmpl => !!(tmpl && tmpl["@id"] !== 'minds/core/referencespace/v1.0.0/a1655b99-82f1-420f-a3c2-fe80fd4c8588')), ), deps: [ Store ] + }, + { + provide: APP_INITIALIZER, + useFactory: (authSvc: AuthService) => { + authSvc.authReloadState() + return () => Promise.resolve() + }, + multi: true, + deps: [ AuthService ] } ], bootstrap : [ @@ -231,11 +202,4 @@ export function debug(reducer: ActionReducer<any>): ActionReducer<any> { ], }) -export class MainModule { - - constructor( - authServce: AuthService, - ) { - authServce.authReloadState() - } -} +export class MainModule {} diff --git a/src/mouseoverModule/mouseover.directive.ts b/src/mouseoverModule/mouseover.directive.ts index b05625a94..9e0055301 100644 --- a/src/mouseoverModule/mouseover.directive.ts +++ b/src/mouseoverModule/mouseover.directive.ts @@ -1,9 +1,8 @@ import { Directive } from "@angular/core" import { select, Store } from "@ngrx/store" -import { merge, Observable } from "rxjs" +import { merge, NEVER, Observable, of } from "rxjs" import { distinctUntilChanged, map, scan, shareReplay } from "rxjs/operators" import { LoggingService } from "src/logging" -import { uiStateMouseOverLandmarkSelector, uiStateMouseoverUserLandmark } from "src/services/state/uiState/selectors" import { TOnHoverObj, temporalPositveScanFn } from "./util" import { ModularUserAnnotationToolService } from "src/atlasComponents/userAnnotations/tools/service"; import { userInteraction } from "src/state" @@ -26,24 +25,26 @@ export class MouseHoverDirective { // TODO consider moving these into a single obs serviced by a DI service // can potentially net better performance - const onHoverUserLandmark$ = this.store$.pipe( - select(uiStateMouseoverUserLandmark) - ) - - const onHoverLandmark$ = this.store$.pipe( - select(uiStateMouseOverLandmarkSelector) - ).pipe( - map(landmark => { - if (landmark === null) { return null } - const idx = Number(landmark.replace('label=', '')) - if (isNaN(idx)) { - this.log.warn(`Landmark index could not be parsed as a number: ${landmark}`) - return { - landmarkName: idx, - } - } - }), - ) + const onHoverUserLandmark$ = NEVER + // this.store$.pipe( + // select(uiStateMouseoverUserLandmark) + // ) + + const onHoverLandmark$ = NEVER + // this.store$.pipe( + // select(uiStateMouseOverLandmarkSelector) + // ).pipe( + // map(landmark => { + // if (landmark === null) { return null } + // const idx = Number(landmark.replace('label=', '')) + // if (isNaN(idx)) { + // this.log.warn(`Landmark index could not be parsed as a number: ${landmark}`) + // return { + // landmarkName: idx, + // } + // } + // }), + // ) const onHoverSegments$ = this.store$.pipe( select(userInteraction.selectors.mousingOverRegions), diff --git a/src/overwrite.scss b/src/overwrite.scss index c955e1032..7db898b3e 100644 --- a/src/overwrite.scss +++ b/src/overwrite.scss @@ -56,22 +56,21 @@ $media-map: ( $overflow-directive: hidden, scroll, auto, visible; @each $directive in $overflow-directive { .#{$nsp}-of-x-#{$directive} { - overflow-x: $directive + overflow-x: $directive!important; } .#{$nsp}-of-y-#{$directive} { - overflow-y: $directive + overflow-y: $directive!important; } .#{$nsp}-of-#{$directive} { - overflow: $directive + overflow: $directive!important; } } @for $scale from 5 through 10 { $scale-var: $scale * 10; - .scale-#{$scale-var} + .#{$nsp}-scale-#{$scale-var} { - color: red; transform: scale($scale * 0.1); } } @@ -135,7 +134,7 @@ $transform-origin-maps: ( } } -$display-vars: block, inline-block, flex, inline-flex; +$display-vars: none, block, inline-block, flex, inline-flex; @each $display-var in $display-vars { .d-#{$display-var} { @@ -147,7 +146,7 @@ $display-vars: block, inline-block, flex, inline-flex; } } -$align-items-vars: center, stretch; +$align-items-vars: center, stretch, start; @each $align-items-var in $align-items-vars { .align-items-#{$align-items-var} { align-items: $align-items-var; @@ -179,3 +178,42 @@ $position-vars: relative, absolute; position: $position-var; } } + +.#{$nsp}-bg-none +{ + background: none!important; +} + +.#{$nsp}-box-shadow-none +{ + box-shadow: none!important; +} + + +$white-space-vars: nowrap; +@each $white-space-var in $white-space-vars { + .#{$nsp}-white-space-#{$white-space-var} + { + white-space: $white-space-var!important; + } +} + +$pointer-events-vars: all, none; +@each $pointer-events-var in $pointer-events-vars { + .#{$nsp}-pointer-events-#{$pointer-events-var} + { + pointer-events: $pointer-events-var!important; + } +} + +$width-pc-vars: 100; +@each $width-pc-var in $width-pc-vars { + .#{$nsp}-w-#{$width-pc-var} { + width: $width-pc-var * 1%; + } +} + +.#{$nsp}-border +{ + border-width: 1px; +} diff --git a/src/plugin/pluginCsp/pluginCsp.component.ts b/src/plugin/pluginCsp/pluginCsp.component.ts index 7ff3c8a1f..ff1026172 100644 --- a/src/plugin/pluginCsp/pluginCsp.component.ts +++ b/src/plugin/pluginCsp/pluginCsp.component.ts @@ -2,7 +2,7 @@ import { Component } from "@angular/core"; import { select, Store } from "@ngrx/store"; import { map } from "rxjs/operators"; import { PluginServices } from "../atlasViewer.pluginService.service"; -import { selectorAllPluginsCspPermission } from "src/services/state/userConfigState.store"; +import { userPreference } from "src/state" @Component({ selector: 'plugin-csp-controller', @@ -15,7 +15,7 @@ import { selectorAllPluginsCspPermission } from "src/services/state/userConfigSt export class PluginCspCtrlCmp{ public pluginCsp$ = this.store$.pipe( - select(selectorAllPluginsCspPermission), + select(userPreference.selectors.userCsp), map(pluginCsp => Object.keys(pluginCsp).map(key => ({ pluginKey: key, pluginCsp: pluginCsp[key] }))), ) diff --git a/src/routerModule/util.ts b/src/routerModule/util.ts index 127c59347..ad87e1fd8 100644 --- a/src/routerModule/util.ts +++ b/src/routerModule/util.ts @@ -1,8 +1,6 @@ import { encodeNumber, decodeToNumber, separator, encodeURIFull } from './cipher' import { UrlSegment, UrlTree } from "@angular/router" import { getShader, PMAP_DEFAULT_CONFIG } from "src/util/constants" -import { mixNgLayers } from "src/services/state/ngViewerState.store" -import { uiStatePreviewingDatasetFilesSelector } from "src/services/state/uiState/selectors" import { Component } from "@angular/core" import { atlasSelection, plugins } from "src/state" @@ -61,25 +59,6 @@ export const cvtFullRouteToState = (fullPath: UrlTree, state: any, _warnCb?: (ar returnObj[key] = val } - // TODO deprecate - // but ensure bkwd compat? - const niftiLayers = fullPath.queryParams['niftiLayers'] - if (niftiLayers) { - const layers = niftiLayers - .split('__') - .map(layer => { - return { - name : layer, - source : `nifti://${layer}`, - mixability : 'nonmixable', - shader : getShader(PMAP_DEFAULT_CONFIG), - } as any - }) - const { ngViewerState } = returnState - ngViewerState.layers = mixNgLayers(ngViewerState.layers, layers) - } - // -- end deprecate - // logical assignment. Use instead of above after typescript > v4.0.0 // returnState['viewerState'] ||= {} if (!returnState['viewerState']) { @@ -214,22 +193,9 @@ export const cvtStateToHashedRoutes = (state): string => { const standaloneVolumes = atlasSelection.selectors.standaloneVolumes(state) const navigation = atlasSelection.selectors.navigation(state) - const previewingDatasetFiles = uiStatePreviewingDatasetFilesSelector(state) let dsPrvString: string const searchParam = new URLSearchParams() - if (previewingDatasetFiles && Array.isArray(previewingDatasetFiles)) { - const dsPrvArr = [] - const datasetPreviews = (previewingDatasetFiles as {datasetId: string, filename: string}[]) - for (const preview of datasetPreviews) { - dsPrvArr.push(preview) - } - - if (dsPrvArr.length === 1) { - dsPrvString = `${dsPrvArr[0].datasetId}::${dsPrvArr[0].filename}` - } - } - let cNavString: string if (navigation) { const { orientation, perspectiveOrientation, perspectiveZoom, position, zoom } = navigation diff --git a/src/services/state/ngViewerState.store.helper.ts b/src/services/state/ngViewerState.store.helper.ts index 6f23c74d1..2898db9c7 100644 --- a/src/services/state/ngViewerState.store.helper.ts +++ b/src/services/state/ngViewerState.store.helper.ts @@ -4,7 +4,7 @@ export { INgLayerInterface, PANELS } from './ngViewerState/constants' export { ngViewerActionAddNgLayer, ngViewerActionRemoveNgLayer, - ngViewerActionSetPerspOctantRemoval, + ngViewerActionToggleMax, ngViewerActionClearView, ngViewerActionSetPanelOrder, @@ -15,8 +15,6 @@ export { ngViewerSelectorClearView, ngViewerSelectorClearViewEntries, ngViewerSelectorNehubaReady, - ngViewerSelectorOctantRemoval, - ngViewerSelectorPanelMode, ngViewerSelectorPanelOrder, ngViewerSelectorLayers, } from './ngViewerState/selectors' diff --git a/src/services/state/ngViewerState.store.spec.ts b/src/services/state/ngViewerState.store.spec.ts deleted file mode 100644 index 21173833c..000000000 --- a/src/services/state/ngViewerState.store.spec.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { HttpClientTestingModule, HttpTestingController } from "@angular/common/http/testing" -import { TestBed } from "@angular/core/testing" -import { provideMockActions } from "@ngrx/effects/testing" -import { Action } from "@ngrx/store" -import { provideMockStore } from "@ngrx/store/testing" -import { Observable, of } from "rxjs" -import { PureContantService } from "src/util" -import { generalApplyState } from "../stateStore.helper" -import { NgViewerUseEffect } from "./ngViewerState.store" - -const action$: Observable<Action> = of({ type: 'TEST'}) -const initState = {} -describe('> ngViewerState.store.ts', () => { - describe('> NgViewerUseEffect', () => { - let ef: NgViewerUseEffect - beforeEach(() => { - - TestBed.configureTestingModule({ - imports: [ - HttpClientTestingModule, - ], - providers: [ - provideMockActions(() => action$), - provideMockStore({ initialState: initState }), - { - provide: PureContantService, - useValue: { - useTouchUI$: of(false), - backendUrl: `http://localhost:3000/` - } - } - ] - }) - }) - - it('> shoudl be insable', () => { - ef = TestBed.inject(NgViewerUseEffect) - expect(ef).toBeTruthy() - }) - - describe('> applySavedUserConfig$', () => { - - let ctrl: HttpTestingController - beforeEach(() => { - ctrl = TestBed.inject(HttpTestingController) - ef = TestBed.inject(NgViewerUseEffect) - }) - - afterEach(() => { - ctrl.verify() - }) - - it('> if http response errors, user$ should be stream of null', () => { - - ef.applySavedUserConfig$.subscribe(_action => { - // should not emit - expect(false).toEqual(true) - }) - const resp1 = ctrl.expectOne('http://localhost:3000/user/config') - resp1.error(null, { - status: 404, - }) - }) - - it('> if http response contains truthy error key, user should return stream of null', () => { - - ef.applySavedUserConfig$.subscribe(_action => { - // should not emit - expect(false).toEqual(true) - }) - const resp = ctrl.expectOne('http://localhost:3000/user/config') - resp.flush({ - error: true - }) - }) - - it('> if http response does not contain error key, should return the resp', () => { - - const mUserState = { - foo: 'baz', - baz: 'pineablle' - } - ef.applySavedUserConfig$.subscribe(action => { - expect(action).toEqual(generalApplyState({ - state: { - ...initState, - ngViewerState: { - ...(initState['ngViewerState'] || {}), - ...mUserState, - } - } - })) - }) - const resp = ctrl.expectOne('http://localhost:3000/user/config') - resp.flush({ ngViewerState: mUserState}) - - }) - }) - }) -}) diff --git a/src/services/state/ngViewerState.store.ts b/src/services/state/ngViewerState.store.ts deleted file mode 100644 index a27495313..000000000 --- a/src/services/state/ngViewerState.store.ts +++ /dev/null @@ -1,341 +0,0 @@ -import { Injectable, OnDestroy } from '@angular/core'; -import { Observable, combineLatest, fromEvent, Subscription, of } from 'rxjs'; -import { Effect, Actions, ofType } from '@ngrx/effects'; -import { withLatestFrom, map, distinctUntilChanged, scan, shareReplay, filter, mapTo, debounceTime, catchError, skip, throttleTime } from 'rxjs/operators'; -import { getNgIds } from 'src/util/fn'; -import { Action, select, Store, createReducer, on } from '@ngrx/store' -import { CYCLE_PANEL_MESSAGE } from 'src/util/constants'; -import { HttpClient } from '@angular/common/http'; -import { INgLayerInterface, ngViewerActionAddNgLayer, ngViewerActionRemoveNgLayer, ngViewerActionSetPerspOctantRemoval } from './ngViewerState.store.helper' -import { PureContantService } from 'src/util'; -import { PANELS } from './ngViewerState.store.helper' -import { ngViewerActionToggleMax, ngViewerActionClearView, ngViewerActionSetPanelOrder, ngViewerActionSwitchPanelMode, ngViewerActionForceShowSegment, ngViewerActionNehubaReady, ngViewerActionCycleViews } from './ngViewerState/actions'; -import { generalApplyState } from '../stateStore.helper'; -import { ngViewerSelectorPanelMode, ngViewerSelectorPanelOrder } from './ngViewerState/selectors'; -import { uiActionSnackbarMessage } from './uiState/actions'; -import { TUserRouteError } from 'src/auth/auth.service'; - -export function mixNgLayers(oldLayers: INgLayerInterface[], newLayers: INgLayerInterface|INgLayerInterface[]): INgLayerInterface[] { - if (newLayers instanceof Array) { - return oldLayers.concat(newLayers) - } else { - return oldLayers.concat({ - ...newLayers, - }) - } -} - -export interface StateInterface { - layers: INgLayerInterface[] - forceShowSegment: boolean | null - nehubaReady: boolean - panelMode: string - panelOrder: string - - octantRemoval: boolean - showSubstrate: boolean - showZoomlevel: boolean - - clearViewQueue: { - [key: string]: boolean - } -} - -export interface ActionInterface extends Action { - layer: INgLayerInterface - layers: INgLayerInterface[] - forceShowSegment: boolean - nehubaReady: boolean - payload: any -} - -export const defaultState: StateInterface = { - layers: [], - forceShowSegment: null, - nehubaReady: false, - panelMode: PANELS.FOUR_PANEL, - panelOrder: `0123`, - - octantRemoval: true, - showSubstrate: null, - showZoomlevel: null, - - clearViewQueue: {} -} - -export const ngViewerStateReducer = createReducer( - defaultState, - on(ngViewerActionClearView, (state, { payload }) => { - const { clearViewQueue } = state - const clearViewQueueUpdated = {...clearViewQueue} - for (const key in payload) { - clearViewQueueUpdated[key] = payload[key] - } - return { - ...state, - clearViewQueue: clearViewQueueUpdated - } - }), - on(ngViewerActionSetPerspOctantRemoval, (state, { octantRemovalFlag }) => { - return { - ...state, - octantRemoval: octantRemovalFlag - } - }), - on(ngViewerActionAddNgLayer, (state, { layer }) => { - return { - ...state, - layers: mixNgLayers(state.layers, layer) - } - }), - on(ngViewerActionSetPanelOrder, (state, { payload }) => { - const { panelOrder } = payload - return { - ...state, - panelOrder - } - }), - on(ngViewerActionSwitchPanelMode, (state, { payload }) => { - const { panelMode } = payload - if (SUPPORTED_PANEL_MODES.indexOf(panelMode as any) < 0) { return state } - return { - ...state, - panelMode - } - }), - on(ngViewerActionRemoveNgLayer, (state, { layer }) => { - - const newLayers = Array.isArray(layer) - ? (() => { - const layerNameSet = new Set(layer.map(l => l.name)) - return state.layers.filter(l => !layerNameSet.has(l.name)) - })() - : state.layers.filter(l => l.name !== layer.name) - return { - ...state, - layers: newLayers - } - }), - on(ngViewerActionForceShowSegment, (state, { forceShowSegment }) => { - return { - ...state, - forceShowSegment - } - }), - on(ngViewerActionNehubaReady, (state, { nehubaReady }) => { - return { - ...state, - nehubaReady - } - }), - on(generalApplyState, (_, { state }) => { - const { ngViewerState } = state - return ngViewerState - }) -) - - -// must export a named function for aot compilation -// see https://github.com/angular/angular/issues/15587 -// https://github.com/amcdnl/ngrx-actions/issues/23 -// or just google for: -// -// angular function expressions are not supported in decorators - -export function stateStore(state, action) { - return ngViewerStateReducer(state, action) -} - -type TUserConfig = { - -} - -type TUserConfigResp = TUserConfig & TUserRouteError - -@Injectable({ - providedIn: 'root', -}) - -export class NgViewerUseEffect implements OnDestroy { - @Effect() - public toggleMaximiseMode$: Observable<any> - - @Effect() - public unmaximiseOrder$: Observable<any> - - @Effect() - public maximiseOrder$: Observable<any> - - @Effect() - public toggleMaximiseCycleMessage$: Observable<any> - - @Effect() - public cycleViews$: Observable<any> - - private panelOrder$: Observable<string> - private panelMode$: Observable<string> - - private subscriptions: Subscription[] = [] - - @Effect() - public applySavedUserConfig$: Observable<any> - - constructor( - private actions: Actions, - private store$: Store<any>, - private pureConstantService: PureContantService, - private http: HttpClient, - ){ - - this.applySavedUserConfig$ = this.http.get<TUserConfigResp>(`${this.pureConstantService.backendUrl}user/config`).pipe( - map(json => { - if (json.error) { - throw new Error(json.message || 'User not loggedin.') - } - return json - }), - catchError((err,caught) => of(null)), - filter(v => !!v), - withLatestFrom(this.store$), - map(([{ ngViewerState: fetchedNgViewerState }, state]) => { - const { ngViewerState } = state - return generalApplyState({ - state: { - ...state, - ngViewerState: { - ...ngViewerState, - ...fetchedNgViewerState - }} - }) - }) - ) - - const toggleMaxmimise$ = this.actions.pipe( - ofType(ngViewerActionToggleMax.type), - shareReplay(1), - ) - - this.panelOrder$ = this.store$.pipe( - select(ngViewerSelectorPanelOrder), - distinctUntilChanged(), - ) - - this.panelMode$ = this.store$.pipe( - select(ngViewerSelectorPanelMode), - distinctUntilChanged(), - ) - - this.cycleViews$ = this.actions.pipe( - ofType(ngViewerActionCycleViews.type), - withLatestFrom(this.panelOrder$), - map(([_, panelOrder]) => { - return ngViewerActionSetPanelOrder({ - payload: { - panelOrder: [...panelOrder.slice(1), ...panelOrder.slice(0, 1)].join(''), - } - }) - }), - ) - - this.maximiseOrder$ = toggleMaxmimise$.pipe( - withLatestFrom( - combineLatest([ - this.panelOrder$, - this.panelMode$, - ]), - ), - filter(([_action, [_panelOrder, panelMode]]) => panelMode !== PANELS.SINGLE_PANEL), - map(([ action, [ oldPanelOrder ] ]) => { - const { payload } = action as ActionInterface - const { index = 0 } = payload - - const panelOrder = [...oldPanelOrder.slice(index), ...oldPanelOrder.slice(0, index)].join('') - return ngViewerActionSetPanelOrder({ - payload: { panelOrder }, - }) - }), - ) - - this.unmaximiseOrder$ = toggleMaxmimise$.pipe( - withLatestFrom( - combineLatest([ - this.panelOrder$, - this.panelMode$, - ]), - ), - scan((acc, curr) => { - const [action, [panelOrders, panelMode]] = curr - return [{ - action, - panelOrders, - panelMode, - }, ...acc.slice(0, 1)] - }, [] as any[]), - filter(([ { panelMode } ]) => panelMode === PANELS.SINGLE_PANEL), - map(arr => { - const { - action, - panelOrders, - } = arr[0] - - const { - panelOrders: panelOrdersPrev = null, - } = arr[1] || {} - - const { payload } = action as ActionInterface - const { index = 0 } = payload - - const panelOrder = panelOrdersPrev || [...panelOrders.slice(index), ...panelOrders.slice(0, index)].join('') - - return ngViewerActionSetPanelOrder({ - payload: { panelOrder } - }) - }), - ) - - const scanFn = (acc: string[], curr: string): string[] => [curr, ...acc.slice(0, 1)] - - this.toggleMaximiseMode$ = toggleMaxmimise$.pipe( - withLatestFrom(this.panelMode$.pipe( - scan(scanFn, []), - )), - map(([ _, panelModes ]) => { - return ngViewerActionSwitchPanelMode({ - payload: { - panelMode: panelModes[0] === PANELS.SINGLE_PANEL - ? (panelModes[1] || PANELS.FOUR_PANEL) - : PANELS.SINGLE_PANEL, - }, - }) - }), - ) - - this.toggleMaximiseCycleMessage$ = combineLatest([ - this.toggleMaximiseMode$, - this.pureConstantService.useTouchUI$, - ]).pipe( - filter(([_, useMobileUI]) => !useMobileUI), - map(([toggleMaximiseMode, _]) => toggleMaximiseMode), - filter(({ payload }) => payload.panelMode && payload.panelMode === PANELS.SINGLE_PANEL), - mapTo(uiActionSnackbarMessage({ - snackbarMessage: CYCLE_PANEL_MESSAGE - })), - ) - } - - public ngOnDestroy() { - while (this.subscriptions.length > 0) { - this.subscriptions.pop().unsubscribe() - } - } -} - -export { INgLayerInterface } - -export const SUPPORTED_PANEL_MODES = [ - PANELS.FOUR_PANEL, - PANELS.H_ONE_THREE, - PANELS.V_ONE_THREE, - PANELS.SINGLE_PANEL, -] - diff --git a/src/services/state/ngViewerState/actions.ts b/src/services/state/ngViewerState/actions.ts index c504a085a..858602e07 100644 --- a/src/services/state/ngViewerState/actions.ts +++ b/src/services/state/ngViewerState/actions.ts @@ -11,11 +11,6 @@ export const ngViewerActionRemoveNgLayer = createAction( props<{ layer: Partial<INgLayerInterface>|Partial<INgLayerInterface>[] }>() ) -export const ngViewerActionSetPerspOctantRemoval = createAction( - `[ngViewerAction] setPerspectiveOctant`, - props<{ octantRemovalFlag: boolean }>() -) - export const ngViewerActionToggleMax = createAction( `[ngViewerAction] toggleMax`, props<{ payload: { index: number } }>() diff --git a/src/services/state/ngViewerState/selectors.ts b/src/services/state/ngViewerState/selectors.ts index 7222296d4..e5940a77d 100644 --- a/src/services/state/ngViewerState/selectors.ts +++ b/src/services/state/ngViewerState/selectors.ts @@ -21,15 +21,6 @@ export const ngViewerSelectorPanelOrder = createSelector( ngViewerState => ngViewerState.panelOrder ) -export const ngViewerSelectorPanelMode = createSelector( - state => state['ngViewerState'], - ngViewerState => ngViewerState.panelMode -) - -export const ngViewerSelectorOctantRemoval = createSelector( - state => state['ngViewerState'], - ngViewerState => ngViewerState.octantRemoval -) export const ngViewerSelectorNehubaReady = createSelector( state => state['ngViewerState'], diff --git a/src/services/state/uiState.store.helper.ts b/src/services/state/uiState.store.helper.ts index 2a89567a3..b2201ef0b 100644 --- a/src/services/state/uiState.store.helper.ts +++ b/src/services/state/uiState.store.helper.ts @@ -13,10 +13,6 @@ export { uiActionMouseoverSegments, } from './uiState/actions' -export { - uiStatePreviewingDatasetFilesSelector, - uiStateMouseoverUserLandmark, -} from './uiState/selectors' export enum EnumWidgetTypes{ DATASET_PREVIEW, diff --git a/src/services/state/uiState.store.ts b/src/services/state/uiState.store.ts deleted file mode 100644 index 1df46f1de..000000000 --- a/src/services/state/uiState.store.ts +++ /dev/null @@ -1,223 +0,0 @@ -import { Injectable, TemplateRef, OnDestroy } from '@angular/core'; -import { Action, select, Store } from '@ngrx/store' - -import { Effect, Actions, ofType } from "@ngrx/effects"; -import { Observable, Subscription } from "rxjs"; -import { filter, map, mapTo, scan, startWith, take } from "rxjs/operators"; -import { COOKIE_VERSION, KG_TOS_VERSION, LOCAL_STORAGE_CONST } from 'src/util/constants' -import { IavRootStoreInterface, GENERAL_ACTION_TYPES } from '../stateStore.service' -import { MatBottomSheetRef, MatBottomSheet } from '@angular/material/bottom-sheet'; -import { uiStateCloseSidePanel, uiStateOpenSidePanel, uiStateCollapseSidePanel, uiStateExpandSidePanel, uiActionSetPreviewingDatasetFiles, uiStateShowBottomSheet, uiActionShowSidePanelConnectivity } from './uiState.store.helper'; -import { viewerStateMouseOverCustomLandmark } from './viewerState/actions'; -import { IUiState } from './uiState/common' -import { uiActionMouseoverLandmark, uiActionMouseoverSegments, uiActionSnackbarMessage } from './uiState/actions'; -export const defaultState: IUiState = { - - previewingDatasetFiles: [], - - mouseOverSegments: [], - mouseOverSegment: null, - - mouseOverLandmark: null, - mouseOverUserLandmark: null, - - focusedSidePanel: null, - sidePanelIsOpen: false, - sidePanelExploreCurrentViewIsOpen: false, - - snackbarMessage: null, - - /** - * replace with server side logic (?) - */ - agreedCookies: localStorage.getItem(LOCAL_STORAGE_CONST.AGREE_COOKIE) === COOKIE_VERSION, - agreedKgTos: localStorage.getItem(LOCAL_STORAGE_CONST.AGREE_KG_TOS) === KG_TOS_VERSION, -} - -export { IUiState } - -export const getStateStore = ({ state = defaultState } = {}) => (prevState: IUiState = state, action: ActionInterface) => { - switch (action.type) { - - case uiActionSetPreviewingDatasetFiles.type: { - const { previewingDatasetFiles } = action as any - return { - ...prevState, - previewingDatasetFiles - } - } - case uiActionMouseoverSegments.type: { - const { segments } = action - return { - ...prevState, - mouseOverSegments: segments, - } - } - case MOUSE_OVER_SEGMENT: - return { - ...prevState, - mouseOverSegment : action.segment, - } - case viewerStateMouseOverCustomLandmark.type: { - const { payload = {} } = action - const { userLandmark: mouseOverUserLandmark = null } = payload - return { - ...prevState, - mouseOverUserLandmark, - } - } - case uiActionMouseoverLandmark.type: - return { - ...prevState, - mouseOverLandmark : action.landmark, - } - case uiActionSnackbarMessage.type: - case SNACKBAR_MESSAGE: { - const { snackbarMessage } = action - /** - * Need to use symbol here, or repeated snackbarMessage will not trigger new event - */ - return { - ...prevState, - snackbarMessage: Symbol(snackbarMessage), - } - } - case uiStateOpenSidePanel.type: - case OPEN_SIDE_PANEL: - return { - ...prevState, - sidePanelIsOpen: true, - } - // src/state/userInteraction/actions.closeSidePanel - case uiStateCloseSidePanel.type: - case CLOSE_SIDE_PANEL: - return { - ...prevState, - sidePanelIsOpen: false, - } - case uiActionShowSidePanelConnectivity.type: - case uiStateExpandSidePanel.type: - case EXPAND_SIDE_PANEL_CURRENT_VIEW: - return { - ...prevState, - sidePanelExploreCurrentViewIsOpen: true, - } - case uiStateCollapseSidePanel.type: - case COLLAPSE_SIDE_PANEL_CURRENT_VIEW: - return { - ...prevState, - sidePanelExploreCurrentViewIsOpen: false, - } - - case AGREE_COOKIE: { - /** - * TODO replace with server side logic - */ - localStorage.setItem(LOCAL_STORAGE_CONST.AGREE_COOKIE, COOKIE_VERSION) - return { - ...prevState, - agreedCookies: true, - } - } - case AGREE_KG_TOS: { - /** - * TODO replace with server side logic - */ - localStorage.setItem(LOCAL_STORAGE_CONST.AGREE_KG_TOS, KG_TOS_VERSION) - return { - ...prevState, - agreedKgTos: true, - } - } - case GENERAL_ACTION_TYPES.APPLY_STATE: { - const { uiState } = (action as any).state - return uiState - } - default: return prevState - } -} - -// must export a named function for aot compilation -// see https://github.com/angular/angular/issues/15587 -// https://github.com/amcdnl/ngrx-actions/issues/23 -// or just google for: -// -// angular function expressions are not supported in decorators - -const defaultStateStore = getStateStore() - -export function stateStore(state, action) { - return defaultStateStore(state, action) -} - -export interface ActionInterface extends Action { - segment: any | number - landmark: any - focusedSidePanel?: string - segments?: Array<{ - layer: { - name: string - } - segment: any | null - }> - snackbarMessage: string - - bottomSheetTemplate: TemplateRef<any> - - payload: any -} - -@Injectable({ - providedIn: 'root', -}) - -export class UiStateUseEffect implements OnDestroy{ - - private subscriptions: Subscription[] = [] - - private bottomSheetRef: MatBottomSheetRef - - constructor( - store$: Store<IavRootStoreInterface>, - actions$: Actions, - bottomSheet: MatBottomSheet - ) { - - this.subscriptions.push( - actions$.pipe( - ofType(uiStateShowBottomSheet.type) - ).subscribe(({ bottomSheetTemplate, config }) => { - if (!bottomSheetTemplate) { - if (this.bottomSheetRef) { - this.bottomSheetRef.dismiss() - this.bottomSheetRef = null - } - } else { - this.bottomSheetRef = bottomSheet.open(bottomSheetTemplate, config) - this.bottomSheetRef.afterDismissed().subscribe(() => { - this.bottomSheetRef = null - }) - } - }) - ) - } - - ngOnDestroy(){ - while(this.subscriptions.length > 0) { - this.subscriptions.pop().unsubscribe() - } - } -} - -export const MOUSE_OVER_SEGMENT = `MOUSE_OVER_SEGMENT` - -export const CLOSE_SIDE_PANEL = `CLOSE_SIDE_PANEL` -export const OPEN_SIDE_PANEL = `OPEN_SIDE_PANEL` -export const COLLAPSE_SIDE_PANEL_CURRENT_VIEW = `COLLAPSE_SIDE_PANEL_CURRENT_VIEW` -export const EXPAND_SIDE_PANEL_CURRENT_VIEW = `EXPAND_SIDE_PANEL_CURRENT_VIEW` - -export const AGREE_COOKIE = `AGREE_COOKIE` -export const AGREE_KG_TOS = `AGREE_KG_TOS` - -export const SNACKBAR_MESSAGE = uiActionSnackbarMessage.type -export const SHOW_BOTTOM_SHEET = `SHOW_BOTTOM_SHEET` diff --git a/src/services/state/uiState/selectors.ts b/src/services/state/uiState/selectors.ts deleted file mode 100644 index 9d48faded..000000000 --- a/src/services/state/uiState/selectors.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { createSelector } from "@ngrx/store"; -import { IUiState } from './common' - -export const uiStatePreviewingDatasetFilesSelector = createSelector( - state => state['uiState'], - (uiState: IUiState) => uiState['previewingDatasetFiles'] -) - -export const uiStateMouseOverLandmarkSelector = createSelector( - state => state['uiState'], - uiState => uiState['mouseOverLandmark'] as string -) - -export const uiStateMouseoverUserLandmark = createSelector( - state => state['uiState'], - uiState => uiState['mouseOverUserLandmark'] -) diff --git a/src/services/state/uiState/ui.effects.ts b/src/services/state/uiState/ui.effects.ts deleted file mode 100644 index 128655ad5..000000000 --- a/src/services/state/uiState/ui.effects.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Injectable, OnDestroy } from "@angular/core"; -import { MatSnackBar } from "@angular/material/snack-bar"; -import { Actions, ofType } from "@ngrx/effects"; -import { Subscription } from "rxjs"; -import { generalActionError } from "src/services/stateStore.helper"; - -@Injectable({ - providedIn: 'root' -}) - -export class UiEffects implements OnDestroy{ - - private subscriptions: Subscription[] = [] - - constructor( - private actions$: Actions, - snackBar: MatSnackBar - ){ - this.subscriptions.push( - this.actions$.pipe( - ofType(generalActionError.type) - ).subscribe((payload: any) => { - if (!payload.message) console.log(payload) - snackBar.open(payload.message || `Error: cannot complete your action.`, 'Dismiss', { duration: 5000 }) - }) - ) - } - - ngOnDestroy(){ - while (this.subscriptions.length > 0) this.subscriptions.pop().unsubscribe() - } -} diff --git a/src/services/state/userConfigState.store.ts b/src/services/state/userConfigState.store.ts deleted file mode 100644 index f5906c02c..000000000 --- a/src/services/state/userConfigState.store.ts +++ /dev/null @@ -1,143 +0,0 @@ -import { Injectable, OnDestroy } from "@angular/core"; -import { Actions, createEffect, Effect, ofType } from "@ngrx/effects"; -import { Action, createAction, createReducer, props, select, Store, on, createSelector } from "@ngrx/store"; -import { of, Subscription } from "rxjs"; -import { catchError, filter, map } from "rxjs/operators"; -import { LOCAL_STORAGE_CONST } from "src/util//constants"; -// Get around the problem of importing duplicated string (ACTION_TYPES), even using ES6 alias seems to trip up the compiler -// TODO file bug and reverse -import { HttpClient } from "@angular/common/http"; -import { PureContantService } from "src/util"; -import * as stateCtrl from "src/state" - -interface ICsp{ - 'connect-src'?: string[] - 'script-src'?: string[] -} - -export interface StateInterface { - savedRegionsSelection: RegionSelection[] - /** - * plugin csp - currently store in localStorage - * if user log in, store in user profile - */ - pluginCsp: { - /** - * key === plugin version id - */ - [key: string]: ICsp - } -} - -export interface RegionSelection { - templateSelected: any - parcellationSelected: any - regionsSelected: any[] - name: string - id: string -} - -/** - * for serialisation into local storage/database - */ -interface SimpleRegionSelection { - id: string - name: string - tName: string - pName: string - rSelected: string[] -} - -interface UserConfigAction extends Action { - config?: Partial<StateInterface> - payload?: any -} - -export const defaultState: StateInterface = { - savedRegionsSelection: [], - pluginCsp: {} -} - -export const selectorAllPluginsCspPermission = createSelector( - (state: any) => state.userConfigState, - userConfigState => userConfigState.pluginCsp -) - -export const actionUpdatePluginCsp = createAction( - `[userConfig] updatePluginCspPermission`, - props<{ - payload: { - [key: string]: ICsp - } - }>() -) - -export const ACTION_TYPES = { - UPDATE_REGIONS_SELECTION: 'UPDATE_REGIONS_SELECTION', -} - - -export const userConfigReducer = createReducer( - defaultState, - on(actionUpdatePluginCsp, (state, { payload }) => { - return { - ...state, - pluginCsp: payload - } - }) -) - -@Injectable({ - providedIn: 'root', -}) -export class UserConfigStateUseEffect implements OnDestroy { - - private subscriptions: Subscription[] = [] - - storeUseMobileInLocalStorage = createEffect(() => this.actions$.pipe( - ofType(stateCtrl.userInterface.actions.useModileUi), - map(({ flag }) => { - window.localStorage.setItem(LOCAL_STORAGE_CONST.MOBILE_UI, JSON.stringify(flag)) - }) - ), { dispatch: false }) - - constructor( - private actions$: Actions, - private store$: Store<any>, - private http: HttpClient, - private constantSvc: PureContantService, - ) { - - this.subscriptions.push( - this.store$.pipe( - select('viewerConfigState'), - ).subscribe(({ gpuLimit, animation }) => { - - if (gpuLimit) { - window.localStorage.setItem(LOCAL_STORAGE_CONST.GPU_LIMIT, gpuLimit.toString()) - } - if (typeof animation !== 'undefined' && animation !== null) { - window.localStorage.setItem(LOCAL_STORAGE_CONST.ANIMATION, animation.toString()) - } - }), - ) - } - - public ngOnDestroy() { - while (this.subscriptions.length > 0) { - this.subscriptions.pop().unsubscribe() - } - } - - - @Effect() - public setInitPluginPermission$ = this.http.get(`${this.constantSvc.backendUrl}user/pluginPermissions`, { - responseType: 'json' - }).pipe( - /** - * TODO show warning? - */ - catchError(() => of({})), - map((json: any) => actionUpdatePluginCsp({ payload: json })) - ) -} diff --git a/src/services/state/viewerConfig.store.ts b/src/services/state/viewerConfig.store.ts deleted file mode 100644 index 650ba69c8..000000000 --- a/src/services/state/viewerConfig.store.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { Action } from "@ngrx/store"; -import { LOCAL_STORAGE_CONST } from "src/util/constants"; - -import { IViewerConfigState as StateInterface } from './viewerConfig.store.helper' -export { StateInterface } - -interface ViewerConfigurationAction extends Action { - config: Partial<StateInterface> - payload: any -} - -export const CONFIG_CONSTANTS = { - /** - * byets - */ - gpuLimitMin: 1e8, - gpuLimitMax: 1e9, - defaultGpuLimit: 1e9, - defaultAnimation: true, -} - -export const VIEWER_CONFIG_ACTION_TYPES = { - SET_ANIMATION: `SET_ANIMATION`, - UPDATE_CONFIG: `UPDATE_CONFIG`, -} - -// get gpu limit -const lsGpuLimit = localStorage.getItem(LOCAL_STORAGE_CONST.GPU_LIMIT) -const lsAnimationFlag = localStorage.getItem(LOCAL_STORAGE_CONST.ANIMATION) -const gpuLimit = lsGpuLimit && !isNaN(Number(lsGpuLimit)) - ? Number(lsGpuLimit) - : CONFIG_CONSTANTS.defaultGpuLimit - -// get animation flag -const animation = lsAnimationFlag && lsAnimationFlag === 'true' - ? true - : lsAnimationFlag === 'false' - ? false - : CONFIG_CONSTANTS.defaultAnimation - -// get mobile ui setting -// UA sniff only if not useMobileUI not explicitly set -const getIsMobile = () => { - // https://developer.mozilla.org/en-US/docs/Web/API/Navigator/maxTouchPoints - // CC0 or MIT - // msMaxTouchPoints is not needed, since IE is not supported - return 'maxTouchPoints' in navigator && navigator.maxTouchPoints > 0 -} -const useMobileUIStroageValue = window && window.localStorage && window.localStorage.getItem(LOCAL_STORAGE_CONST.MOBILE_UI) - -export const defaultState: StateInterface = { - animation, - gpuLimit, - useMobileUI: (useMobileUIStroageValue && useMobileUIStroageValue === 'true') || getIsMobile(), -} - -export const getStateStore = ({ state = defaultState } = {}) => (prevState: StateInterface = state, action: ViewerConfigurationAction) => { - switch (action.type) { - case VIEWER_CONFIG_ACTION_TYPES.UPDATE_CONFIG: - return { - ...prevState, - ...action.config, - } - default: return prevState - } -} - -// must export a named function for aot compilation -// see https://github.com/angular/angular/issues/15587 -// https://github.com/amcdnl/ngrx-actions/issues/23 -// or just google for: -// -// angular function expressions are not supported in decorators - -const defaultStateStore = getStateStore() - -export function stateStore(state, action) { - return defaultStateStore(state, action) -} diff --git a/src/services/state/viewerConfig/selectors.ts b/src/services/state/viewerConfig/selectors.ts deleted file mode 100644 index 4b69c0e58..000000000 --- a/src/services/state/viewerConfig/selectors.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { createSelector } from "@ngrx/store"; - -export const selectViewerConfigAnimationFlag = createSelector( - state => state['viewerConfigState'], - viewerConfigState => viewerConfigState['animation'] -) diff --git a/src/services/stateStore.service.ts b/src/services/stateStore.service.ts index ceef16525..e69de29bb 100644 --- a/src/services/stateStore.service.ts +++ b/src/services/stateStore.service.ts @@ -1,131 +0,0 @@ -import { filter } from 'rxjs/operators'; - -export { getNgIds } from 'src/util/fn' - -import { - ActionInterface as NgViewerActionInterface, - defaultState as ngViewerDefaultState, - StateInterface as NgViewerStateInterface, - stateStore as ngViewerState, -} from './state/ngViewerState.store' -import { - ActionInterface as UIActionInterface, - defaultState as uiDefaultState, - IUiState, - stateStore as uiState, -} from './state/uiState.store' -import { - ACTION_TYPES as USER_CONFIG_ACTION_TYPES, - defaultState as userConfigDefaultState, - StateInterface as UserConfigStateInterface, - userConfigReducer as userConfigState, -} from './state/userConfigState.store' -import { - defaultState as viewerConfigDefaultState, - StateInterface as ViewerConfigStateInterface, - stateStore as viewerConfigState, -} from './state/viewerConfig.store' - - -export { viewerConfigState } -export { NgViewerStateInterface, NgViewerActionInterface, ngViewerState } -export { IUiState, UIActionInterface, uiState } -export { userConfigState, USER_CONFIG_ACTION_TYPES} - -export { MOUSE_OVER_SEGMENT, OPEN_SIDE_PANEL, COLLAPSE_SIDE_PANEL_CURRENT_VIEW, EXPAND_SIDE_PANEL_CURRENT_VIEW } from './state/uiState.store' -export { UserConfigStateUseEffect } from './state/userConfigState.store' - -export { GENERAL_ACTION_TYPES, generalActionError } from './stateStore.helper' - -// TODO deprecate -export function safeFilter(key: string) { - return filter((state: any) => - (typeof state !== 'undefined' && state !== null) && - typeof state[key] !== 'undefined' && state[key] !== null) -} - -export function getMultiNgIdsRegionsLabelIndexMap(parcellation: any = {}, inheritAttrsOpt: any = { ngId: 'root' }): Map<string, Map<number, any>> { - const map: Map<string, Map<number, any>> = new Map() - - const inheritAttrs = Object.keys(inheritAttrsOpt) - if (inheritAttrs.indexOf('children') >=0 ) throw new Error(`children attr cannot be inherited`) - - const processRegion = (region: any) => { - const { ngId: rNgId } = region - const existingMap = map.get(rNgId) - const labelIndex = Number(region.labelIndex) - if (labelIndex) { - if (!existingMap) { - const newMap = new Map() - newMap.set(labelIndex, region) - map.set(rNgId, newMap) - } else { - existingMap.set(labelIndex, region) - } - } - - if (region.children && Array.isArray(region.children)) { - for (const r of region.children) { - const copiedRegion = { ...r } - for (const attr of inheritAttrs){ - copiedRegion[attr] = copiedRegion[attr] || region[attr] || parcellation[attr] - } - processRegion(copiedRegion) - } - } - } - - if (!parcellation) throw new Error(`parcellation needs to be defined`) - if (!parcellation.regions) throw new Error(`parcellation.regions needs to be defined`) - if (!Array.isArray(parcellation.regions)) throw new Error(`parcellation.regions needs to be an array`) - - for (const region of parcellation.regions){ - const copiedregion = { ...region } - for (const attr of inheritAttrs){ - copiedregion[attr] = copiedregion[attr] || parcellation[attr] - } - processRegion(copiedregion) - } - - return map -} - -/** - * labelIndexMap maps label index to region - * @TODO deprecate - */ -export function getLabelIndexMap(regions: any[]): Map<number, any> { - const returnMap = new Map() - - const reduceRegions = (rs: any[]) => { - rs.forEach(region => { - if ( region.labelIndex ) { returnMap.set(Number(region.labelIndex), - Object.assign({}, region, {labelIndex : Number(region.labelIndex)})) - } - if ( region.children && region.children.constructor === Array ) { reduceRegions(region.children) } - }) - } - - if (regions && regions.forEach) { reduceRegions(regions) } - return returnMap -} - - -// @TODO deprecate -export function isDefined(obj) { - return typeof obj !== 'undefined' && obj !== null -} - -export interface IavRootStoreInterface { - viewerConfigState: ViewerConfigStateInterface - ngViewerState: NgViewerStateInterface - uiState: IUiState - userConfigState: UserConfigStateInterface -} - -export const defaultRootState: any = { - ngViewerState: ngViewerDefaultState, - uiState: uiDefaultState, - userConfigState: userConfigDefaultState, - viewerConfigState: viewerConfigDefaultState, -} diff --git a/src/state/atlasAppearance/action.ts b/src/state/atlasAppearance/action.ts index d9065e07c..27461e622 100644 --- a/src/state/atlasAppearance/action.ts +++ b/src/state/atlasAppearance/action.ts @@ -7,3 +7,17 @@ export const overwriteColorMap = createAction( colormap: Record<string, number[]> }>() ) + +export const setOctantRemoval = createAction( + `${nameSpace} setOctantRemoval`, + props<{ + flag: boolean + }>() +) + +export const setShowDelineation = createAction( + `${nameSpace} setShowDelineation`, + props<{ + flag: boolean + }>() +) \ No newline at end of file diff --git a/src/state/atlasAppearance/index.ts b/src/state/atlasAppearance/index.ts index cf777e1fe..bbbe62696 100644 --- a/src/state/atlasAppearance/index.ts +++ b/src/state/atlasAppearance/index.ts @@ -1,3 +1,4 @@ export * as actions from "./action" export * as selectors from "./selector" +export { nameSpace } from "./const" export { reducer } from "./store" \ No newline at end of file diff --git a/src/state/atlasAppearance/selector.ts b/src/state/atlasAppearance/selector.ts index c43c61978..842d2db0f 100644 --- a/src/state/atlasAppearance/selector.ts +++ b/src/state/atlasAppearance/selector.ts @@ -7,4 +7,14 @@ const selectStore = state => state[nameSpace] as AtlasAppearanceStore export const getOverwrittenColormap = createSelector( selectStore, state => state.overwrittenColormap +) + +export const octantRemoval = createSelector( + selectStore, + state => state.octantRemoval +) + +export const showDelineation = createSelector( + selectStore, + state => state.showDelineation ) \ No newline at end of file diff --git a/src/state/atlasAppearance/store.ts b/src/state/atlasAppearance/store.ts index 817603c93..476da0558 100644 --- a/src/state/atlasAppearance/store.ts +++ b/src/state/atlasAppearance/store.ts @@ -3,10 +3,14 @@ import * as actions from "./action" export type AtlasAppearanceStore = { overwrittenColormap: Record<string, number[]> + octantRemoval: boolean + showDelineation: boolean } const defaultState: AtlasAppearanceStore = { - overwrittenColormap: null + overwrittenColormap: null, + octantRemoval: true, + showDelineation: true, } export const reducer = createReducer( @@ -19,5 +23,23 @@ export const reducer = createReducer( overwrittenColormap: colormap } } - ) + ), + on( + actions.setOctantRemoval, + (state, { flag }) => { + return { + ...state, + octantRemoval: flag + } + } + ), + on( + actions.setShowDelineation, + (state, { flag }) => { + return { + ...state, + showDelineation: flag + } + } + ), ) diff --git a/src/state/effects/TODO.md b/src/state/effects/TODO.md new file mode 100644 index 000000000..0fd8e3c71 --- /dev/null +++ b/src/state/effects/TODO.md @@ -0,0 +1,3 @@ +# TODO + +migrate ASAP \ No newline at end of file diff --git a/src/state/index.ts b/src/state/index.ts index 856798548..b8523aff3 100644 --- a/src/state/index.ts +++ b/src/state/index.ts @@ -1,8 +1,51 @@ +import { ActionReducer, StoreModule } from "@ngrx/store" + export { StateModule } from "./state.module" -export * as atlasSelection from "./atlasSelection" -export * as annotation from "./annotations" -export * as userInterface from "./userInterface" -export * as atlasAppearance from "./atlasAppearance" -export * as plugins from "./plugins" -export * as userInteraction from "./userInteraction" +import * as atlasSelection from "./atlasSelection" +import * as annotation from "./annotations" +import * as userInterface from "./userInterface" +import * as atlasAppearance from "./atlasAppearance" +import * as plugins from "./plugins" +import * as userInteraction from "./userInteraction" +import * as userPreference from "./userPreference" +import { EffectsModule } from "@ngrx/effects" + +export { + atlasSelection, + annotation, + userInterface, + atlasAppearance, + plugins, + userInteraction, + userPreference, +} + +export function debug(reducer: ActionReducer<any>): ActionReducer<any> { + return function(state, action) { + console.log('state', state); + console.log('action', action); + + return reducer(state, action); + }; +} + +export const RootStoreModule = StoreModule.forRoot({ + [userPreference.nameSpace]: userPreference.reducer, + [atlasSelection.nameSpace]: atlasSelection.reducer, + [userInterface.nameSpace]: userInterface.reducer, + [userInteraction.nameSpace]: userInteraction.reducer, + [annotation.nameSpace]: annotation.reducer, + [plugins.nameSpace]: plugins.reducer, + [atlasAppearance.nameSpace]: atlasAppearance.reducer, +},{ + metaReducers: [ + // debug, + ] +}) + +export const RootEffecsModule = EffectsModule.forRoot([ + plugins.Effects, + atlasSelection.Effect, + userInterface.Effects, +]) \ No newline at end of file diff --git a/src/state/userInterface/actions.ts b/src/state/userInterface/actions.ts index 1e80ac94a..e423f261e 100644 --- a/src/state/userInterface/actions.ts +++ b/src/state/userInterface/actions.ts @@ -2,14 +2,8 @@ import { TemplateRef } from "@angular/core"; import { MatBottomSheetConfig } from "@angular/material/bottom-sheet"; import { MatSnackBarConfig } from "@angular/material/snack-bar"; import { createAction, props } from "@ngrx/store"; -import { nameSpace } from "./const" +import { nameSpace, PanelMode } from "./const" -export const useModileUi = createAction( - `${nameSpace} useMobileUi`, - props<{ - flag: boolean - }>() -) export const openSidePanel = createAction( `${nameSpace} openSidePanel` @@ -38,3 +32,29 @@ export const snackBarMessage = createAction( config?: MatSnackBarConfig }>() ) + + +export const setPanelMode = createAction( + `${nameSpace} setPanelMode`, + props<{ + panelMode: PanelMode + }>() +) + +export const cyclePanelMode = createAction( + `${nameSpace} cyclePanelMode` +) + +export const toggleMaximiseView = createAction( + `${nameSpace} toggleMaximiseView`, + props<{ + targetIndex: number + }>() +) + +export const setPanelOrder = createAction( + `${nameSpace} setPanelOrder`, + props<{ + order: string + }>() +) \ No newline at end of file diff --git a/src/state/userInterface/const.ts b/src/state/userInterface/const.ts index 1033ee368..929102239 100644 --- a/src/state/userInterface/const.ts +++ b/src/state/userInterface/const.ts @@ -1 +1,5 @@ -export const nameSpace = `[state.ui]` \ No newline at end of file +export const nameSpace = `[state.ui]` +export type PanelMode = 'FOUR_PANEL' +| 'V_ONE_THREE' +| 'H_ONE_THREE' +| 'SINGLE_PANEL' \ No newline at end of file diff --git a/src/state/userInterface/effects.ts b/src/state/userInterface/effects.ts index 254cf22a4..d1472dc5d 100644 --- a/src/state/userInterface/effects.ts +++ b/src/state/userInterface/effects.ts @@ -3,7 +3,10 @@ import { MatBottomSheet, MatBottomSheetRef } from "@angular/material/bottom-shee import { MatSnackBar } from "@angular/material/snack-bar"; import { Actions, createEffect, ofType } from "@ngrx/effects"; import { select, Store } from "@ngrx/store"; -import { filter, map, mapTo, pairwise, startWith } from "rxjs/operators"; +import { of } from "rxjs"; +import { filter, map, mapTo, pairwise, startWith, switchMap, tap, withLatestFrom } from "rxjs/operators"; +import { generalActionError } from "src/services/stateStore.helper"; +import { userInterface } from ".."; import { selectors } from "../atlasSelection" import * as actions from "./actions" @@ -26,31 +29,71 @@ export class Effects{ mapTo(actions.expandSidePanelDetailView()) )) - private bottomSheetRef: MatBottomSheetRef - constructor( - private store: Store, - private action: Actions, - bottomsheet: MatBottomSheet, - snackbar: MatSnackBar, - ){ - this.action.pipe( - ofType(actions.showBottomSheet) - ).subscribe(({ template, config }) => { + onGeneralError = createEffect(() => this.action.pipe( + ofType(generalActionError.type), + tap(payload => { + this.snackbar.open( + (payload as any)?.message || `Error: cannot complete your action`, + 'Dismiss', + { duration: 5000 } + ) + }) + ), { dispatch: false }) + + onShowBottomSheet = createEffect(() => this.action.pipe( + ofType(actions.showBottomSheet), + tap(({ template, config }) => { + if (this.bottomSheetRef) { this.bottomSheetRef.dismiss() } - this.bottomSheetRef = bottomsheet.open( + this.bottomSheetRef = this.bottomsheet.open( template, config ) this.bottomSheetRef.afterDismissed().subscribe(() => this.bottomSheetRef = null) }) + ), { dispatch: false }) - this.action.pipe( - ofType(actions.snackBarMessage) - ).subscribe(({ message, config }) => { + onSnackbarMessage = createEffect(() => this.action.pipe( + ofType(actions.snackBarMessage), + tap(({ message, config }) => { const _config = config || { duration: 5000 } - snackbar.open(message, "Dismiss", _config) + this.snackbar.open(message, "Dismiss", _config) + }) + ), { dispatch: false }) + + onMaximiseView = createEffect(() => this.action.pipe( + ofType(actions.toggleMaximiseView), + withLatestFrom( + this.store.pipe( + select(userInterface.selectors.panelMode), + ) + ), + switchMap(([ { targetIndex }, panelMode ]) => { + const newMode: userInterface.PanelMode = panelMode === "FOUR_PANEL" + ? "SINGLE_PANEL" + : "FOUR_PANEL" + const newOrder = newMode === "FOUR_PANEL" + ? "0123" + : "0123".split("").map(v => ((Number(v) + targetIndex) % 4).toString()).join("") + return of( + userInterface.actions.setPanelMode({ + panelMode: newMode + }), + userInterface.actions.setPanelOrder({ + order: newOrder + }) + ) }) + )) + + private bottomSheetRef: MatBottomSheetRef + constructor( + private store: Store, + private action: Actions, + private bottomsheet: MatBottomSheet, + private snackbar: MatSnackBar, + ){ } } diff --git a/src/state/userInterface/index.ts b/src/state/userInterface/index.ts index f0b980136..cff6971a4 100644 --- a/src/state/userInterface/index.ts +++ b/src/state/userInterface/index.ts @@ -1,4 +1,5 @@ export * as actions from "./actions" export * as selectors from "./selectors" -export { nameSpace } from "./const" -export { reducer } from "./store" \ No newline at end of file +export { nameSpace, PanelMode } from "./const" +export { reducer } from "./store" +export { Effects } from "./effects" diff --git a/src/state/userInterface/selectors.ts b/src/state/userInterface/selectors.ts index 15705846f..b9b3001d1 100644 --- a/src/state/userInterface/selectors.ts +++ b/src/state/userInterface/selectors.ts @@ -4,7 +4,12 @@ import { UiStore } from "./store" const selectStore = state => state[nameSpace] as UiStore -export const useMobileUi = createSelector( +export const panelMode = createSelector( selectStore, - state => state.useMobileUi + state => state.panelMode +) + +export const panelOrder = createSelector( + selectStore, + state => state.panelOrder ) diff --git a/src/state/userInterface/store.ts b/src/state/userInterface/store.ts index aabe2f556..3fcea7d7e 100644 --- a/src/state/userInterface/store.ts +++ b/src/state/userInterface/store.ts @@ -1,23 +1,39 @@ import { createReducer, on } from "@ngrx/store"; import * as actions from "./actions" +import { PanelMode } from "./const" export type UiStore = { - useMobileUi: boolean + panelMode: PanelMode + panelOrder: string // permutation of 0123 + octantRemoval: boolean + showDelineation: boolean } const defaultStore: UiStore = { - useMobileUi: false + panelMode: 'FOUR_PANEL', + panelOrder: '0123', + octantRemoval: false, + showDelineation: true, } export const reducer = createReducer( defaultStore, on( - actions.useModileUi, - (state, { flag }) => { + actions.setPanelMode, + (state, { panelMode }) => { return { ...state, - useMobileUi: flag + panelMode } } ), + on( + actions.setPanelOrder, + (state, { order }) => { + return { + ...state, + panelOrder: order + } + } + ) ) diff --git a/src/state/userPreference/actions.ts b/src/state/userPreference/actions.ts new file mode 100644 index 000000000..ee6f8f306 --- /dev/null +++ b/src/state/userPreference/actions.ts @@ -0,0 +1,39 @@ +import { createAction, props } from "@ngrx/store" +import { nameSpace, CSP } from "./const" + +export const setAnimationFlag = createAction( + `${nameSpace} setAnimationFlag`, + props<{ + flag: boolean + }>() +) + +export const setGpuLimit = createAction( + `${nameSpace} setGpuLimit`, + props<{ + limit: number + }>() +) + +export const useMobileUi = createAction( + `${nameSpace} setUseMobileUi`, + props<{ + flag: boolean + }>() +) + +export const agreeCookie = createAction( + `${nameSpace} agreeCookie` +) + +export const agreeKgTos = createAction( + `${nameSpace} agreeKgTos` +) + +export const updateCsp = createAction( + `${nameSpace} updateCsp`, + props<{ + name: string + csp: CSP + }>() +) \ No newline at end of file diff --git a/src/state/userPreference/const.ts b/src/state/userPreference/const.ts new file mode 100644 index 000000000..c9a9daba8 --- /dev/null +++ b/src/state/userPreference/const.ts @@ -0,0 +1,9 @@ +export const nameSpace = `[userPreference]` + +export const maxGpuLimit = 1e9 +export const minGpuLimit = 1e8 + +export interface CSP{ + 'connect-src'?: string[] + 'script-src'?: string[] +} \ No newline at end of file diff --git a/src/state/userPreference/effects.ts b/src/state/userPreference/effects.ts new file mode 100644 index 000000000..29aaf3710 --- /dev/null +++ b/src/state/userPreference/effects.ts @@ -0,0 +1,48 @@ +import { HttpClient } from "@angular/common/http"; +import { Injectable } from "@angular/core"; +import { Actions, createEffect, ofType } from "@ngrx/effects"; +import { map } from "rxjs/operators"; +import { COOKIE_VERSION, KG_TOS_VERSION, LOCAL_STORAGE_CONST } from "src/util/constants"; +import * as actions from "./actions" + +@Injectable() +export class Effects{ + + onUseMobileUi = createEffect(() => this.actions$.pipe( + ofType(actions.useMobileUi), + map(({ flag }) => { + window.localStorage.setItem(LOCAL_STORAGE_CONST.MOBILE_UI, JSON.stringify(flag)) + }) + ), { dispatch: false }) + + onSetGpuLimit = createEffect(() => this.actions$.pipe( + ofType(actions.setGpuLimit), + map(({ limit }) => { + localStorage.setItem(LOCAL_STORAGE_CONST.GPU_LIMIT, limit.toString()) + }) + ), { dispatch: false }) + + onAgreeCookie = createEffect(() => this.actions$.pipe( + ofType(actions.agreeCookie), + map(() => { + localStorage.setItem(LOCAL_STORAGE_CONST.AGREE_COOKIE, COOKIE_VERSION) + }) + ), { dispatch: false }) + + onAgreeKgTos = createEffect(() => this.actions$.pipe( + ofType(actions.agreeKgTos), + map(() => { + localStorage.setItem(LOCAL_STORAGE_CONST.AGREE_KG_TOS, KG_TOS_VERSION) + }) + ), { dispatch: false }) + + // TODO setup on startup get user csp + // this.http.get(`${this.constantSvc.backendUrl}user/pluginPermissions`) + // onStartUpGetCsp + + constructor( + private actions$: Actions, + private http: HttpClient, + ){ + } +} diff --git a/src/state/userPreference/index.ts b/src/state/userPreference/index.ts new file mode 100644 index 000000000..e8e485b4d --- /dev/null +++ b/src/state/userPreference/index.ts @@ -0,0 +1,4 @@ +export { UserPreference, defaultUserPreferenceStore, reducer } from "./store" +export { nameSpace } from "./const" +export * as actions from "./actions" +export * as selectors from "./selectors" diff --git a/src/state/userPreference/selectors.ts b/src/state/userPreference/selectors.ts new file mode 100644 index 000000000..608264fc6 --- /dev/null +++ b/src/state/userPreference/selectors.ts @@ -0,0 +1,35 @@ +import { createSelector } from "@ngrx/store" +import { nameSpace } from "./const" +import { UserPreference } from "./store" + +const storeSelector = store => store[nameSpace] as UserPreference + +export const useAnimation = createSelector( + storeSelector, + state => state.useAnimation +) + +export const gpuLimit = createSelector( + storeSelector, + state => state.gpuLimit +) + +export const useMobileUi = createSelector( + storeSelector, + state => state.useMobileUi +) + +export const agreedToCookie = createSelector( + storeSelector, + store => store.agreeCookie +) + +export const agreedToKgTos = createSelector( + storeSelector, + store => store.agreeKgTos +) + +export const userCsp = createSelector( + storeSelector, + store => store.pluginCSP +) diff --git a/src/state/userPreference/store.ts b/src/state/userPreference/store.ts new file mode 100644 index 000000000..b80d510d8 --- /dev/null +++ b/src/state/userPreference/store.ts @@ -0,0 +1,93 @@ +import { createReducer, on } from "@ngrx/store" +import { COOKIE_VERSION, KG_TOS_VERSION, LOCAL_STORAGE_CONST } from "src/util/constants" +import * as actions from "./actions" +import { maxGpuLimit, CSP } from "./const" + +export const defaultGpuLimit = maxGpuLimit + +export type UserPreference = { + useMobileUi: boolean + gpuLimit: number + useAnimation: boolean + pluginCSP: Record<string, CSP> + + agreeCookie: boolean + agreeKgTos: boolean +} + +export const defaultUserPreferenceStore: UserPreference = { + useMobileUi: JSON.parse(localStorage.getItem(LOCAL_STORAGE_CONST.MOBILE_UI)), + gpuLimit: Number(localStorage.getItem(LOCAL_STORAGE_CONST.GPU_LIMIT)) || defaultGpuLimit, + useAnimation: !localStorage.getItem(LOCAL_STORAGE_CONST.ANIMATION), + pluginCSP: {}, + + agreeCookie: localStorage.getItem(LOCAL_STORAGE_CONST.AGREE_COOKIE) === COOKIE_VERSION, + agreeKgTos: localStorage.getItem(LOCAL_STORAGE_CONST.AGREE_KG_TOS) === KG_TOS_VERSION, +} + +export const reducer = createReducer( + defaultUserPreferenceStore, + on( + actions.setAnimationFlag, + (state, { flag }) => { + if (flag) { + localStorage.removeItem(LOCAL_STORAGE_CONST.ANIMATION) + } else { + localStorage.setItem(LOCAL_STORAGE_CONST.ANIMATION, "false") + } + + return { + ...state, + useAnimation: flag + } + } + ), + on( + actions.setGpuLimit, + (state, { limit }) => { + return { + ...state, + gpuLimit: limit + } + } + ), + on( + actions.useMobileUi, + (state, { flag }) => { + return { + ...state, + useMobileUi: flag + } + } + ), + on( + actions.agreeCookie, + state => { + return { + ...state, + agreeCookie: true + } + } + ), + on( + actions.agreeKgTos, + state => { + return { + ...state, + agreeKgTos: true + } + } + ), + on( + actions.updateCsp, + (state, { name, csp }) => { + return { + ...state, + pluginCSP: { + ...state.pluginCSP, + [name]: csp + } + } + } + ) +) diff --git a/src/ui/config/configCmp/config.component.ts b/src/ui/config/configCmp/config.component.ts index ffc01128f..494403b61 100644 --- a/src/ui/config/configCmp/config.component.ts +++ b/src/ui/config/configCmp/config.component.ts @@ -2,18 +2,11 @@ import { Component, OnDestroy, OnInit } from '@angular/core' import { select, Store } from '@ngrx/store'; import { combineLatest, Observable, Subscription } from 'rxjs'; import { debounceTime, distinctUntilChanged, map, startWith } from 'rxjs/operators'; -import { SUPPORTED_PANEL_MODES } from 'src/services/state/ngViewerState.store'; -import { ngViewerActionSetPanelOrder } from 'src/services/state/ngViewerState.store.helper'; -import { VIEWER_CONFIG_ACTION_TYPES, StateInterface as ViewerConfiguration } from 'src/services/state/viewerConfig.store' -import { IavRootStoreInterface } from 'src/services/stateStore.service'; import { isIdentityQuat } from 'src/viewerModule/nehuba/util'; -import {MatSlideToggleChange} from "@angular/material/slide-toggle"; -import {MatSliderChange} from "@angular/material/slider"; -import { PureContantService } from 'src/util'; -import { ngViewerActionSwitchPanelMode } from 'src/services/state/ngViewerState/actions'; -import { ngViewerSelectorPanelMode, ngViewerSelectorPanelOrder } from 'src/services/state/ngViewerState/selectors'; -import { atlasSelection } from 'src/state'; -import * as stateCtrl from "src/state" +import { MatSlideToggleChange } from "@angular/material/slide-toggle"; +import { MatSliderChange } from "@angular/material/slider"; +import { atlasSelection, userPreference, userInterface } from 'src/state'; +import { environment } from "src/environments/environment" const GPU_TOOLTIP = `Higher GPU usage can cause crashes on lower end machines` const ANIMATION_TOOLTIP = `Animation can cause slowdowns in lower end machines` @@ -34,14 +27,25 @@ export class ConfigComponent implements OnInit, OnDestroy { public GPU_TOOLTIP = GPU_TOOLTIP public ANIMATION_TOOLTIP = ANIMATION_TOOLTIP public MOBILE_UI_TOOLTIP = MOBILE_UI_TOOLTIP - public supportedPanelModes = SUPPORTED_PANEL_MODES + + public experimentalFlag = environment.EXPERIMENTAL_FEATURE_FLAG + + public panelModes: Record<string, userInterface.PanelMode> = { + FOUR_PANEL: "FOUR_PANEL", + H_ONE_THREE: "H_ONE_THREE", + SINGLE_PANEL: "SINGLE_PANEL", + V_ONE_THREE: "V_ONE_THREE", + } + /** * in MB */ public gpuLimit$: Observable<number> - public useMobileUI$: Observable<boolean> + public useMobileUI$: Observable<boolean> = this.store.pipe( + select(userPreference.selectors.useMobileUi) + ) public animationFlag$: Observable<boolean> private subscriptions: Subscription[] = [] @@ -57,31 +61,24 @@ export class ConfigComponent implements OnInit, OnDestroy { private viewerObliqueRotated$: Observable<boolean> constructor( - private store: Store<IavRootStoreInterface>, - private pureConstantService: PureContantService, + private store: Store<any>, ) { - this.useMobileUI$ = this.pureConstantService.useTouchUI$ - this.gpuLimit$ = this.store.pipe( - select('viewerConfigState'), - map((config: ViewerConfiguration) => config.gpuLimit), - distinctUntilChanged(), + select(userPreference.selectors.gpuLimit), map(v => v / 1e6), ) this.animationFlag$ = this.store.pipe( - select('viewerConfigState'), - map((config: ViewerConfiguration) => config.animation), + select(userPreference.selectors.useAnimation) ) this.panelMode$ = this.store.pipe( - select(ngViewerSelectorPanelMode), - startWith(SUPPORTED_PANEL_MODES[0]), + select(userInterface.selectors.panelMode) ) this.panelOrder$ = this.store.pipe( - select(ngViewerSelectorPanelOrder), + select(userInterface.selectors.panelOrder), ) this.viewerObliqueRotated$ = this.store.pipe( @@ -117,7 +114,7 @@ export class ConfigComponent implements OnInit, OnDestroy { public toggleMobileUI(ev: MatSlideToggleChange) { const { checked } = ev this.store.dispatch( - stateCtrl.userInterface.actions.useModileUi({ + userPreference.actions.useMobileUi({ flag: checked }) ) @@ -125,26 +122,25 @@ export class ConfigComponent implements OnInit, OnDestroy { public toggleAnimationFlag(ev: MatSlideToggleChange ) { const { checked } = ev - this.store.dispatch({ - type: VIEWER_CONFIG_ACTION_TYPES.UPDATE_CONFIG, - config: { - animation: checked, - }, - }) + this.store.dispatch( + userPreference.actions.setAnimationFlag({ + flag: checked + }) + ) } public handleMatSliderChange(ev: MatSliderChange) { - this.store.dispatch({ - type: VIEWER_CONFIG_ACTION_TYPES.UPDATE_CONFIG, - config: { - gpuLimit: ev.value * 1e6, - }, - }) + this.store.dispatch( + userPreference.actions.setGpuLimit({ + limit: ev.value * 1e6 + }) + ) } - public usePanelMode(panelMode: string) { + public usePanelMode(panelMode: userInterface.PanelMode) { + this.store.dispatch( - ngViewerActionSwitchPanelMode({ - payload: { panelMode } + userInterface.actions.setPanelMode({ + panelMode }) ) } @@ -160,8 +156,8 @@ export class ConfigComponent implements OnInit, OnDestroy { [arr[idx1], arr[idx2]] = [arr[idx2], arr[idx1]] this.store.dispatch( - ngViewerActionSetPanelOrder({ - payload: { panelOrder: arr.join('') } + userInterface.actions.setPanelOrder({ + order: arr.join('') }) ) } diff --git a/src/ui/config/configCmp/config.stories.ts b/src/ui/config/configCmp/config.stories.ts new file mode 100644 index 000000000..f1aa0a63f --- /dev/null +++ b/src/ui/config/configCmp/config.stories.ts @@ -0,0 +1,46 @@ +import { CommonModule } from "@angular/common" +import { HttpClientModule } from "@angular/common/http" +import { Meta, moduleMetadata, Story } from "@storybook/angular" +import { provideDarkTheme } from "src/atlasComponents/sapi/stories.base" +import { ConfigModule } from "../module" +import { ConfigComponent } from "./config.component" +import { userPreference, userInterface, atlasSelection } from "src/state" +import { StoreModule } from "@ngrx/store" + +export default { + component: ConfigComponent, + decorators: [ + moduleMetadata({ + imports: [ + CommonModule, + HttpClientModule, + ConfigModule, + StoreModule.forRoot({ + [userPreference.nameSpace]: userPreference.reducer, + [userInterface.nameSpace]: userInterface.reducer, + [atlasSelection.nameSpace]: atlasSelection.reducer, + }) + ], + providers: [ + ...provideDarkTheme, + ], + declarations: [] + }) + ], +} as Meta + +const Template: Story<ConfigComponent> = (args: ConfigComponent, { loaded }) => { + const { experimentalFlag } = args + return ({ + props: { + experimentalFlag + }, + }) +} + +export const Default = Template.bind({}) +Default.args = { + experimentalFlag: true +} +Default.loaders = [ +] \ No newline at end of file diff --git a/src/ui/config/configCmp/config.style.css b/src/ui/config/configCmp/config.style.css index 907391412..9fccc534f 100644 --- a/src/ui/config/configCmp/config.style.css +++ b/src/ui/config/configCmp/config.style.css @@ -11,4 +11,16 @@ .onDragOver { background-color: rgba(128,128,128,0.2); +} + +.chunky +{ + width: 100%; + height: 100%; +} + +.uncollapsable +{ + width: 10em; + height: 7em; } \ No newline at end of file diff --git a/src/ui/config/configCmp/config.template.html b/src/ui/config/configCmp/config.template.html index 7241ef02a..1972951cd 100644 --- a/src/ui/config/configCmp/config.template.html +++ b/src/ui/config/configCmp/config.template.html @@ -1,8 +1,65 @@ <mat-tab-group> + + <!-- hard ware --> + <mat-tab label="Hardware"> + <!-- wrapper + margin control --> + <div class="sxplr-m-4"> + + <!-- use mobile UI --> + <div class="d-flex mb-2 align-items-center"> + <mat-slide-toggle + [checked]="useMobileUI$ | async" + (change)="toggleMobileUI($event)"> + Enable Mobile UI + </mat-slide-toggle> + <small iav-stop="click mousedown mouseup" [matTooltip]="MOBILE_UI_TOOLTIP" class="ml-2 fas fa-question"></small> + </div> + + <!-- animation toggle --> + <div class="d-flex mb-2 align-items-center"> + <mat-slide-toggle + [checked]="animationFlag$ | async" + (change)="toggleAnimationFlag($event)"> + Enable Animation + </mat-slide-toggle> + <small iav-stop="click mousedown mouseup" [matTooltip]="ANIMATION_TOOLTIP" class="ml-2 fas fa-question"></small> + </div> + + <!-- GPU limit --> + <div class="d-flex flex-row align-items-center justify-content start"> + <label + class="sxplr-m-0 d-inline-block flex-grow-0 flex-shrink-0" + for="gpuLimitSlider"> + GPU Limit + <small iav-stop="click mousedown mouseup" [matTooltip]="GPU_TOOLTIP" class="ml-2 fas fa-question"></small> + </label> + <mat-slider + class="flex-grow-1 flex-shrink-1 ml-2 mr-2" + id="gpuLimitSlider" + name="gpuLimitSlider" + thumbLabel="true" + min="100" + max="1000" + [step]="stepSize" + (change)="handleMatSliderChange($event)" + [value]="gpuLimit$ | async"> + </mat-slider> + <span class="d-inline-block flex-grow-0 flex-shrink-0 w-10em"> + {{ gpuLimit$ | async }} MB + </span> + </div> + </div> + </mat-tab> + + <!-- plugin csp --> + <!-- <mat-tab label="Plugin Permission"> + <plugin-csp-controller></plugin-csp-controller> + </mat-tab> --> + <!-- viewer preference --> - <mat-tab *ngIf="false" label="Viewer Preference"> - - <div class="m-2"> + <mat-tab *ngIf="experimentalFlag" label="Viewer Preference"> + + <div class="iv-custom-comp text sxplr-m-2"> <div class="mat-h2"> Rearrange Viewports </div> @@ -17,11 +74,11 @@ (dragleave)="handleDragLeave($event)" (dragend)="handleDragend($event)" (drop)="handleDrop($event)" - class="w-100 h-100 config-transition" + class="chunky config-transition" cell-i> <div [attr.panel-order]="0" - class="config-transition w-100 h-100 d-flex align-items-center justify-content-center border" + class="config-transition chunky d-flex align-items-center justify-content-center border" draggable="true"> {{ (panelTexts$ | async)[0] }} </div> @@ -33,11 +90,11 @@ (dragleave)="handleDragLeave($event)" (dragend)="handleDragend($event)" (drop)="handleDrop($event)" - class="w-100 h-100 config-transition" + class="chunky config-transition" cell-ii> <div [attr.panel-order]="1" - class="config-transition w-100 h-100 d-flex align-items-center justify-content-center border" + class="config-transition chunky d-flex align-items-center justify-content-center border" draggable="true"> {{ (panelTexts$ | async)[1] }} </div> @@ -49,11 +106,11 @@ (dragleave)="handleDragLeave($event)" (dragend)="handleDragend($event)" (drop)="handleDrop($event)" - class="w-100 h-100 config-transition" + class="chunky config-transition" cell-iii> <div [attr.panel-order]="2" - class="config-transition w-100 h-100 d-flex align-items-center justify-content-center border" + class="config-transition chunky d-flex align-items-center justify-content-center border" draggable="true"> {{ (panelTexts$ | async)[2] }} </div> @@ -65,146 +122,112 @@ (dragleave)="handleDragLeave($event)" (dragend)="handleDragend($event)" (drop)="handleDrop($event)" - class="w-100 h-100 config-transition" + class="chunky config-transition" cell-iv> <div [attr.panel-order]="3" - class="config-transition w-100 h-100 d-flex align-items-center justify-content-center border" + class="config-transition chunky d-flex align-items-center justify-content-center border" draggable="true"> {{ (panelTexts$ | async)[3] }} </div> </div> </current-layout> - <div class="mat-body text-muted font-italic"> + <div class="iv-custom-comp text text-muted font-italic"> Plane designation refers to default orientation (without oblique rotation). </div> </div> <!-- scroll window --> - <div class="m-2"> + <div class="sxplr-m-2 iv-custom-comp text"> <div class="mat-h2"> Select a viewports configuration </div> </div> <div class="d-flex flex-row flex-nowrap sxplr-p-2"> + BLA? + <!-- main template --> + <ng-template #panelModeBtnTmpl + let-panelMode="panelMode" + let-previewTmpl="previewTmpl"> + <button + class="sxplr-m-2 sxplr-p-2" + mat-flat-button + (click)="usePanelMode(panelMode)" + [color]="(panelMode$ | async) === panelMode ? 'primary' : null"> + + <div class="uncollapsable"> + + <ng-template [ngTemplateOutlet]="previewTmpl"> + </ng-template> + </div> + </button> + </ng-template> <!-- Four Panel Card --> - <button - class="m-2 sxplr-p-2" - mat-flat-button - (click)="usePanelMode(supportedPanelModes[0])" - [color]="(panelMode$ | async) === supportedPanelModes[0] ? 'primary' : null"> - <layout-four-panel class="d-block w-10em h-7em"> - <div class="border w-100 h-100" cell-i></div> - <div class="border w-100 h-100" cell-ii></div> - <div class="border w-100 h-100" cell-iii></div> - <div class="border w-100 h-100" cell-iv></div> + <ng-template #layoutFourPanelTmpl> + <layout-four-panel class="d-block chunky"> + <div class="sxplr-border chunky" cell-i></div> + <div class="sxplr-border chunky" cell-ii></div> + <div class="sxplr-border chunky" cell-iii></div> + <div class="sxplr-border chunky" cell-iv></div> </layout-four-panel> - </button> + </ng-template> + <ng-template [ngTemplateOutlet]="panelModeBtnTmpl" + [ngTemplateOutletContext]="{ + panelMode: panelModes.FOUR_PANEL, + previewTmpl: layoutFourPanelTmpl + }"> + </ng-template> <!-- temporarily disabling 1-3 layout --> <!-- horizontal 1 3 card --> <!-- <button - class="m-2 sxplr-p-2" + class="sxplr-m-2 sxplr-p-2" mat-flat-button - (click)="usePanelMode(supportedPanelModes[1])" - [color]="(panelMode$ | async) === supportedPanelModes[1] ? 'primary' : null"> + (click)="usePanelMode(panelModes.H_ONE_THREE)" + [color]="(panelMode$ | async) === panelModes.H_ONE_THREE ? 'primary' : null"> <layout-horizontal-one-three class="d-block w-10em h-7em"> - <div class="border w-100 h-100" cell-i></div> - <div class="border w-100 h-100" cell-ii></div> - <div class="border w-100 h-100" cell-iii></div> - <div class="border w-100 h-100" cell-iv></div> + <div class="border chunky" cell-i></div> + <div class="border chunky" cell-ii></div> + <div class="border chunky" cell-iii></div> + <div class="border chunky" cell-iv></div> </layout-horizontal-one-three> </button> --> - + <!-- vertical 1 3 card --> <!-- <button - class="m-2 sxplr-p-2" + class="sxplr-m-2 sxplr-p-2" mat-flat-button - (click)="usePanelMode(supportedPanelModes[2])" - [color]="(panelMode$ | async) === supportedPanelModes[2] ? 'primary' : null"> + (click)="usePanelMode(panelModes.V_ONE_THREE)" + [color]="(panelMode$ | async) === panelModes.V_ONE_THREE ? 'primary' : null"> <layout-vertical-one-three class="d-block w-10em h-7em"> - <div class="border w-100 h-100" cell-i></div> - <div class="border w-100 h-100" cell-ii></div> - <div class="border w-100 h-100" cell-iii></div> - <div class="border w-100 h-100" cell-iv></div> + <div class="border chunky" cell-i></div> + <div class="border chunky" cell-ii></div> + <div class="border chunky" cell-iii></div> + <div class="border chunky" cell-iv></div> </layout-vertical-one-three> </button> --> <!-- single --> - <button - class="m-2 sxplr-p-2" - mat-flat-button - (click)="usePanelMode(supportedPanelModes[3])" - [color]="(panelMode$ | async) === supportedPanelModes[3] ? 'primary' : null"> + <ng-template #singlePanelTmpl> <layout-single-panel class="d-block w-10em h-7em"> - <div class="border w-100 h-100" cell-i></div> - <div class="border w-100 h-100" cell-ii></div> - <div class="border w-100 h-100" cell-iii></div> - <div class="border w-100 h-100" cell-iv></div> + <div class="border chunky" cell-i></div> + <div class="border chunky" cell-ii></div> + <div class="border chunky" cell-iii></div> + <div class="border chunky" cell-iv></div> </layout-single-panel> - </button> - </div> - </mat-tab> - - <!-- hard ware --> - <mat-tab label="Hardware"> - <!-- wrapper + margin control --> - <div class="m-4"> - - <!-- use mobile UI --> - <div class="d-flex mb-2 align-items-center"> - <mat-slide-toggle - [checked]="useMobileUI$ | async" - (change)="toggleMobileUI($event)"> - Enable Mobile UI - </mat-slide-toggle> - <small iav-stop="click mousedown mouseup" [matTooltip]="MOBILE_UI_TOOLTIP" class="ml-2 fas fa-question"></small> - </div> - - <!-- animation toggle --> - <div class="d-flex mb-2 align-items-center"> - <mat-slide-toggle - [checked]="animationFlag$ | async" - (change)="toggleAnimationFlag($event)"> - Enable Animation - </mat-slide-toggle> - <small iav-stop="click mousedown mouseup" [matTooltip]="ANIMATION_TOOLTIP" class="ml-2 fas fa-question"></small> - </div> - - <!-- GPU limit --> - <div class="d-flex flex-row align-items-center justify-content start"> - <label - class="m-0 d-inline-block flex-grow-0 flex-shrink-0" - for="gpuLimitSlider"> - GPU Limit - <small iav-stop="click mousedown mouseup" [matTooltip]="GPU_TOOLTIP" class="ml-2 fas fa-question"></small> - </label> - <mat-slider - class="flex-grow-1 flex-shrink-1 ml-2 mr-2" - id="gpuLimitSlider" - name="gpuLimitSlider" - thumbLabel="true" - min="100" - max="1000" - [step]="stepSize" - (change)="handleMatSliderChange($event)" - [value]="gpuLimit$ | async"> - </mat-slider> - <span class="d-inline-block flex-grow-0 flex-shrink-0 w-10em"> - {{ gpuLimit$ | async }} MB - </span> - </div> + </ng-template> + <ng-template [ngTemplateOutlet]="panelModeBtnTmpl" + [ngTemplateOutletContext]="{ + panelMode: panelModes.SINGLE_PANEL, + previewTmpl: singlePanelTmpl + }"> + </ng-template> </div> </mat-tab> - - <!-- plugin csp --> - <mat-tab label="Plugin Permission"> - <plugin-csp-controller></plugin-csp-controller> - </mat-tab> </mat-tab-group> diff --git a/src/ui/config/module.ts b/src/ui/config/module.ts index f52b5ffb9..55fcded2c 100644 --- a/src/ui/config/module.ts +++ b/src/ui/config/module.ts @@ -9,7 +9,7 @@ import { ConfigComponent } from "./configCmp/config.component"; imports: [ CommonModule, AngularMaterialModule, - PluginModule, + // PluginModule, LayoutModule, ], declarations: [ diff --git a/src/ui/ui.module.ts b/src/ui/ui.module.ts index 92ecdd79f..3e389e958 100644 --- a/src/ui/ui.module.ts +++ b/src/ui/ui.module.ts @@ -28,7 +28,6 @@ import { DOCUMENT } from "@angular/common"; import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; import { Landmark2DModule } from "./nehubaContainer/2dLandmarks/module"; import { HANDLE_SCREENSHOT_PROMISE, TypeHandleScrnShotPromise } from "../screenshot"; -import { ParcellationRegionModule } from "src/atlasComponents/parcellationRegion"; import { AtlasCmpParcellationModule } from "src/atlasComponents/parcellation"; import { DialogInfoModule } from "./dialogInfo" @@ -46,7 +45,6 @@ import { DialogInfoModule } from "./dialogInfo" ShareModule, AuthModule, Landmark2DModule, - ParcellationRegionModule, AtlasCmpParcellationModule, DialogInfoModule, ], diff --git a/src/util/constants.ts b/src/util/constants.ts index ee179b2d4..fadf58112 100644 --- a/src/util/constants.ts +++ b/src/util/constants.ts @@ -3,7 +3,7 @@ import { environment } from 'src/environments/environment' export const LOCAL_STORAGE_CONST = { GPU_LIMIT: 'fzj.xg.iv.GPU_LIMIT', - ANIMATION: 'fzj.xg.iv.ANIMATION_FLAG', + ANIMATION: 'fzj.xg.iv.DISABLE_ANIMATION_FLAG', MOBILE_UI: 'fzj.xg.iv.MOBILE_UI', AGREE_COOKIE: 'fzj.xg.iv.AGREE_COOKIE', AGREE_KG_TOS: 'fzj.xg.iv.AGREE_KG_TOS', diff --git a/src/util/pureConstant.service.ts b/src/util/pureConstant.service.ts index 34fa6e3bf..68360a593 100644 --- a/src/util/pureConstant.service.ts +++ b/src/util/pureConstant.service.ts @@ -9,7 +9,7 @@ import { TId, TParc, TRegionDetail, TRegionSummary, TSpaceFull, TSpaceSummary } import { MultiDimMap, recursiveMutate, mutateDeepMerge } from "./fn"; import { patchRegions } from './patchPureConstants' import { MatSnackBar } from "@angular/material/snack-bar"; -import { atlasSelection, userInterface } from "src/state"; +import { atlasSelection, userPreference } from "src/state"; const validVolumeType = new Set([ 'neuroglancer/precomputed', @@ -305,7 +305,7 @@ Raise/track issues at github repo: <a target = "_blank" href = "${this.repoUrl}" ) this.useTouchUI$ = this.store.pipe( - select(userInterface.selectors.useMobileUi), + select(userPreference.selectors.useMobileUi), shareReplay(1) ) diff --git a/src/viewerModule/module.ts b/src/viewerModule/module.ts index 58cc6ae77..1e12da494 100644 --- a/src/viewerModule/module.ts +++ b/src/viewerModule/module.ts @@ -2,7 +2,6 @@ import { CommonModule } from "@angular/common"; import { NgModule } from "@angular/core"; import { Observable } from "rxjs"; import { AtlasCmpParcellationModule } from "src/atlasComponents/parcellation"; -import { ParcellationRegionModule } from "src/atlasComponents/parcellationRegion"; import { SplashUiModule } from "src/atlasComponents/splashScreen"; import { ComponentsModule } from "src/components"; import { ContextMenuModule, ContextMenuService, TContextMenuReg } from "src/contextMenuModule"; @@ -38,7 +37,6 @@ import { SapiViewsModule, SapiViewsUtilModule } from "src/atlasComponents/sapiVi AngularMaterialModule, SplashUiModule, TopMenuModule, - ParcellationRegionModule, UtilModule, AtlasCmpParcellationModule, ComponentsModule, diff --git a/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.service.spec.ts b/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.service.spec.ts index 7e41087b1..5ef1deb33 100644 --- a/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.service.spec.ts +++ b/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.service.spec.ts @@ -1,12 +1,14 @@ import { fakeAsync, TestBed, tick } from "@angular/core/testing" import { MockStore, provideMockStore } from "@ngrx/store/testing" -import { viewerStateCustomLandmarkSelector, viewerStateSelectedParcellationSelector, viewerStateSelectedRegionsSelector, viewerStateSelectedTemplateSelector } from "src/services/state/viewerState/selectors" import { NehubaLayerControlService } from "./layerCtrl.service" import * as layerCtrlUtil from '../constants' import { hot } from "jasmine-marbles" import { IColorMap } from "./layerCtrl.util" import { debounceTime } from "rxjs/operators" import { ngViewerSelectorClearView, ngViewerSelectorLayers } from "src/services/state/ngViewerState.store.helper" +import { + atlasSelection +} from "src/state" describe('> layerctrl.service.ts', () => { describe('> NehubaLayerControlService', () => { @@ -27,10 +29,9 @@ describe('> layerctrl.service.ts', () => { layerCtrlUtil, 'getMultiNgIdsRegionsLabelIndexMap' ).and.returnValue(() => getMultiNgIdsRegionsLabelIndexMapReturnVal) - mockStore.overrideSelector(viewerStateCustomLandmarkSelector, []) - mockStore.overrideSelector(viewerStateSelectedRegionsSelector, []) - mockStore.overrideSelector(viewerStateSelectedTemplateSelector, {}) - mockStore.overrideSelector(viewerStateSelectedParcellationSelector, {}) + mockStore.overrideSelector(atlasSelection.selectors.selectedRegions, []) + mockStore.overrideSelector(atlasSelection.selectors.selectedTemplate, {} as any) + mockStore.overrideSelector(atlasSelection.selectors.selectedParcellation, {} as any) }) it('> can be init', () => { @@ -43,132 +44,27 @@ describe('> layerctrl.service.ts', () => { describe('> template/parc has no aux meshes', () => { it('> calls getMultiNgIdsRegionsLabelIndexMapReturn', () => { - const service = TestBed.inject(NehubaLayerControlService) - service.setColorMap$.subscribe() - expect(getMultiNgIdsRegionsLabelIndexMapSpy).toHaveBeenCalled() + }) it('> emitted value is as expected', fakeAsync(() => { - const map = new Map<number, layerCtrlUtil.IRegion>() - getMultiNgIdsRegionsLabelIndexMapReturnVal.set( - 'foo-bar', - map - ) - map.set(1, { - ngId: 'foo-bar', - rgb: [100, 200, 255] - }) - map.set(2, { - ngId: 'foo-bar', - rgb: [15, 15, 15] - }) - - const service = TestBed.inject(NehubaLayerControlService) - let v: any - service.setColorMap$.subscribe(val => { - v = val - }) - tick(32) - const expectedVal = { - 'foo-bar': { - 1: { red: 100, green: 200, blue: 255 }, - 2: { red: 15, green: 15, blue: 15} - } - } - expect(v).toEqual(expectedVal) + })) }) describe('> template/parc has aux meshes', () => { - let tmplAuxMeshes = [{ - name: 'foo-bar', - ngId: 'bazz', - labelIndicies: [1,2,3], - rgb: [100, 100, 100] - }, { - name: 'hello-world', - ngId: 'hello-world', - labelIndicies: [4,5,6], - rgb: [200, 200, 200] - }] - let parcAuxMeshes = [{ - name: 'hello-world', - ngId: 'hello-world', - labelIndicies: [10,11,12], - rgb: [255, 255, 255] - }] + beforeEach(() => { - mockStore.overrideSelector(viewerStateSelectedTemplateSelector, { - auxMeshes: tmplAuxMeshes - }) - mockStore.overrideSelector(viewerStateSelectedParcellationSelector, { - auxMeshes: parcAuxMeshes - }) + }) it('> should inherit values from tmpl and parc', fakeAsync(() => { - const service = TestBed.inject(NehubaLayerControlService) - let val - service.setColorMap$.subscribe(v => { - val = v - }) - - tick(32) - - expect(val).toEqual({ - 'bazz': { - 1: { red: 100, green: 100, blue: 100 }, - 2: { red: 100, green: 100, blue: 100 }, - 3: { red: 100, green: 100, blue: 100 }, - }, - 'hello-world': { - 4: { red: 200, green: 200, blue: 200 }, - 5: { red: 200, green: 200, blue: 200 }, - 6: { red: 200, green: 200, blue: 200 }, - 10: { red: 255, green: 255, blue: 255 }, - 11: { red: 255, green: 255, blue: 255 }, - 12: { red: 255, green: 255, blue: 255 }, - } - }) })) it('> should overwrite any value if at all, from region', fakeAsync(() => { - const map = new Map<number, layerCtrlUtil.IRegion>() - map.set(10, { - ngId: 'hello-world', - rgb: [0, 0, 0] - }) - map.set(15, { - ngId: 'hello-world', - rgb: [0, 0, 0] - }) - getMultiNgIdsRegionsLabelIndexMapReturnVal.set('hello-world', map) - - const service = TestBed.inject(NehubaLayerControlService) - let val - service.setColorMap$.subscribe(v => { - val = v - }) - - tick(32) - expect(val).toEqual({ - 'bazz': { - 1: { red: 100, green: 100, blue: 100 }, - 2: { red: 100, green: 100, blue: 100 }, - 3: { red: 100, green: 100, blue: 100 }, - }, - 'hello-world': { - 4: { red: 200, green: 200, blue: 200 }, - 5: { red: 200, green: 200, blue: 200 }, - 6: { red: 200, green: 200, blue: 200 }, - 10: { red: 255, green: 255, blue: 255 }, - 11: { red: 255, green: 255, blue: 255 }, - 12: { red: 255, green: 255, blue: 255 }, - 15: { red: 0, green: 0, blue: 0 }, - } - }) + })) }) }) @@ -187,66 +83,13 @@ describe('> layerctrl.service.ts', () => { describe('> overwriteColorMap$ firing', () => { beforeEach(() => { - mockStore.overrideSelector(viewerStateSelectedTemplateSelector, {}) - mockStore.overrideSelector(viewerStateSelectedParcellationSelector, {}) - const map = new Map<number, layerCtrlUtil.IRegion>() - getMultiNgIdsRegionsLabelIndexMapReturnVal.set( - 'foo-bar', - map - ) - map.set(1, { - ngId: 'foo-bar', - rgb: [100, 200, 255] - }) - map.set(2, { - ngId: 'foo-bar', - rgb: [15, 15, 15] - }) }) it('> should overwrite existing colormap', () => { - const service = TestBed.inject(NehubaLayerControlService) - service.overwriteColorMap$.next(foobar2) - - expect(service.setColorMap$).toBeObservable( - hot('(b)', { - a: foobar1, - b: foobar2 - }) - ) + }) it('> unsub/resub should not result in overwritecolormap last emitted value', fakeAsync(() => { - const service = TestBed.inject(NehubaLayerControlService) - - let subscrbiedVal: IColorMap - const sub = service.setColorMap$.pipe( - debounceTime(16), - ).subscribe(val => { - subscrbiedVal = val - }) - - // see TODO this is a dirty fix - tick(32) - service.overwriteColorMap$.next(foobar2) - tick(32) - expect(subscrbiedVal).toEqual(foobar2) - tick(16) - sub.unsubscribe() - subscrbiedVal = null - - // mock emit selectParc etc... - mockStore.overrideSelector(viewerStateSelectedParcellationSelector, {}) - mockStore.setState({}) - const sub2 = service.setColorMap$.pipe( - debounceTime(16), - ).subscribe(val => { - subscrbiedVal = val - }) - - tick(32) - expect(subscrbiedVal).toEqual(foobar1) - sub2.unsubscribe() })) }) @@ -255,48 +98,10 @@ describe('> layerctrl.service.ts', () => { describe('> visibleLayer$', () => { beforeEach(() => { - mockStore.overrideSelector(viewerStateSelectedTemplateSelector, { - ngId: 'tmplNgId', - auxMeshes: [{ - ngId: 'tmplAuxId1', - labelIndicies: [1,2,3] - },{ - ngId: 'tmplAuxId2', - labelIndicies: [1,2,3] - }] - }) - - mockStore.overrideSelector(viewerStateSelectedParcellationSelector, { - auxMeshes: [{ - ngId: 'parcAuxId1', - labelIndicies: [1,2,3], - },{ - ngId: 'parcAuxId2', - labelIndicies: [1,2,3] - }] - }) - getMultiNgIdsRegionsLabelIndexMapReturnVal.set( - 'regionsNgId1', null - ) - - getMultiNgIdsRegionsLabelIndexMapReturnVal.set( - 'regionsNgId2', null - ) }) it('> combines ngId of template, aux mesh and regions', () => { - const service = TestBed.inject(NehubaLayerControlService) - expect(service.visibleLayer$).toBeObservable(hot('a', { - a: [ - 'tmplNgId', - 'tmplAuxId1', - 'tmplAuxId2', - 'parcAuxId1', - 'parcAuxId2', - 'regionsNgId1', - 'regionsNgId2', - ] - })) + }) }) @@ -310,106 +115,48 @@ describe('> layerctrl.service.ts', () => { labelIndex: 2 } beforeEach(() => { - mockStore.overrideSelector(viewerStateSelectedRegionsSelector, []) - mockStore.overrideSelector(ngViewerSelectorLayers, []) - mockStore.overrideSelector(ngViewerSelectorClearView, false) - mockStore.overrideSelector(viewerStateSelectedParcellationSelector, {}) }) it('> by default, should return []', () => { - const service = TestBed.inject(NehubaLayerControlService) - expect(service.segmentVis$).toBeObservable( - hot('a', { - a: [] - }) - ) + }) describe('> if sel regions exist', () => { beforeEach(() => { - mockStore.overrideSelector(viewerStateSelectedRegionsSelector, [ - region1, region2 - ]) + }) it('> default, should return encoded strings', () => { - mockStore.overrideSelector(viewerStateSelectedRegionsSelector, [ - region1, region2 - ]) - const service = TestBed.inject(NehubaLayerControlService) - expect(service.segmentVis$).toBeObservable( - hot('a', { - a: [`ngid#1`, `ngid#2`] - }) - ) + }) it('> if clearflag is true, then return []', () => { - mockStore.overrideSelector(ngViewerSelectorClearView, true) - const service = TestBed.inject(NehubaLayerControlService) - expect(service.segmentVis$).toBeObservable( - hot('a', { - a: [] - }) - ) }) }) describe('> if non mixable layer exist', () => { beforeEach(() => { - mockStore.overrideSelector(ngViewerSelectorLayers, [{ - mixability: 'nonmixable' - }]) }) it('> default, should return null', () => { - const service = TestBed.inject(NehubaLayerControlService) - expect(service.segmentVis$).toBeObservable( - hot('a', { - a: null - }) - ) + }) it('> if regions selected, should still return null', () => { - mockStore.overrideSelector(viewerStateSelectedRegionsSelector, [ - region1, region2 - ]) - const service = TestBed.inject(NehubaLayerControlService) - expect(service.segmentVis$).toBeObservable( - hot('a', { - a: null - }) - ) }) describe('> if clear flag is set', () => { beforeEach(() => { - mockStore.overrideSelector(ngViewerSelectorClearView, true) + }) it('> default, should return []', () => { - const service = TestBed.inject(NehubaLayerControlService) - expect(service.segmentVis$).toBeObservable( - hot('a', { - a: [] - }) - ) }) it('> if reg selected, should return []', () => { - mockStore.overrideSelector(viewerStateSelectedRegionsSelector, [ - region1, region2 - ]) - const service = TestBed.inject(NehubaLayerControlService) - expect(service.segmentVis$).toBeObservable( - hot('a', { - a: [] - }) - ) }) }) }) diff --git a/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.service.ts b/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.service.ts index 156410ee6..8cb8fed1e 100644 --- a/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.service.ts +++ b/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.service.ts @@ -11,7 +11,7 @@ import { SAPI, SapiParcellationModel } from "src/atlasComponents/sapi"; import { SAPISpace } from "src/atlasComponents/sapi/core"; import { getParcNgId, fromRootStore as nehubaConfigSvcFromRootStore } from "../config.service" import { getRegionLabelIndex } from "../config.service/util"; -import { annotation, atlasSelection } from "src/state"; +import { annotation, atlasAppearance, atlasSelection } from "src/state"; import { serializeSegment } from "../util"; export const BACKUP_COLOR = { @@ -160,6 +160,27 @@ export class NehubaLayerControlService implements OnDestroy{ ){ this.sub.push( + + /** + * on store showdelin + * toggle parcnglayers visibility + */ + this.store$.pipe( + select(atlasAppearance.selectors.showDelineation), + withLatestFrom(this.defaultNgLayers$) + ).subscribe(([flag, { parcNgLayers }]) => { + const layerObj = {} + for (const key in parcNgLayers) { + layerObj[key] = { + visible: flag + } + } + + this.manualNgLayersControl$.next({ + type: 'update', + payload: layerObj + }) + }), this.store$.pipe( select(atlasSelection.selectors.selectedRegions) ).subscribe(() => { diff --git a/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.util.ts b/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.util.ts index f6fc08184..0b76bb270 100644 --- a/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.util.ts +++ b/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.util.ts @@ -45,7 +45,7 @@ export interface INgLayerCtrl { [key: string]: INgLayerInterface } update: { - [key: string]: INgLayerInterface + [key: string]: Partial<INgLayerInterface> } setLayerTransparency: { [key: string]: number diff --git a/src/viewerModule/nehuba/maximisePanelButton/maximisePanelButton.component.ts b/src/viewerModule/nehuba/maximisePanelButton/maximisePanelButton.component.ts index 7aab44ac4..16cecc54b 100644 --- a/src/viewerModule/nehuba/maximisePanelButton/maximisePanelButton.component.ts +++ b/src/viewerModule/nehuba/maximisePanelButton/maximisePanelButton.component.ts @@ -4,7 +4,7 @@ import { Observable } from "rxjs"; import { distinctUntilChanged, map } from "rxjs/operators"; import { PANELS } from 'src/services/state/ngViewerState.store.helper' import { ARIA_LABELS } from 'common/constants' -import { ngViewerSelectorPanelMode, ngViewerSelectorPanelOrder } from "src/services/state/ngViewerState/selectors"; +import { userInterface } from "src/state" const { MAXIMISE_VIEW, @@ -26,26 +26,18 @@ export class MaximisePanelButton { @Input() public panelIndex: number - private panelMode$: Observable<string> - private panelOrder$: Observable<string> + private panelMode$ = this.store$.pipe( + select(userInterface.selectors.panelMode), + distinctUntilChanged(), + ) - public isMaximised$: Observable<boolean> + public isMaximised$ = this.panelMode$.pipe( + map(panelMode => panelMode === "SINGLE_PANEL"), + ) constructor( private store$: Store<any>, ) { - this.panelMode$ = this.store$.pipe( - select(ngViewerSelectorPanelMode), - distinctUntilChanged(), - ) - - this.panelOrder$ = this.store$.pipe( - select(ngViewerSelectorPanelOrder), - distinctUntilChanged(), - ) - - this.isMaximised$ = this.panelMode$.pipe( - map(panelMode => panelMode === PANELS.SINGLE_PANEL), - ) + } } diff --git a/src/viewerModule/nehuba/navigation.service/navigation.service.spec.ts b/src/viewerModule/nehuba/navigation.service/navigation.service.spec.ts index 9fdde1ebb..9180048b0 100644 --- a/src/viewerModule/nehuba/navigation.service/navigation.service.spec.ts +++ b/src/viewerModule/nehuba/navigation.service/navigation.service.spec.ts @@ -1,12 +1,11 @@ import { discardPeriodicTasks, fakeAsync, TestBed, tick } from '@angular/core/testing' import { MockStore, provideMockStore } from '@ngrx/store/testing' import { BehaviorSubject, of, Subject } from 'rxjs' -import { selectViewerConfigAnimationFlag } from 'src/services/state/viewerConfig/selectors' -import { viewerStateSelectorNavigation } from 'src/services/state/viewerState/selectors' import * as NavUtil from './navigation.util' import { NehubaViewerUnit } from '../nehubaViewer/nehubaViewer.component' import { NEHUBA_INSTANCE_INJTKN } from '../util' import { NehubaNavigationService } from './navigation.service' +import { userPreference, atlasSelection } from "src/state" const nav1 = { position: [1,2,3], @@ -63,11 +62,11 @@ describe('> navigation.service.ts', () => { const mockStore = TestBed.inject(MockStore) mockStore.overrideSelector( - viewerStateSelectorNavigation, + atlasSelection.selectors.navigation, nav1 ) mockStore.overrideSelector( - selectViewerConfigAnimationFlag, + userPreference.selectors.useAnimation, true ) }) @@ -108,7 +107,7 @@ describe('> navigation.service.ts', () => { service['nehubaViewerInstance'] = nehubaInst as NehubaViewerUnit const mockStore = TestBed.inject(MockStore) - mockStore.overrideSelector(viewerStateSelectorNavigation, nav1) + mockStore.overrideSelector(atlasSelection.selectors.navigation, nav1) dispatchSpy = spyOn(mockStore, 'dispatch').and.callFake(() => {}) }) diff --git a/src/viewerModule/nehuba/navigation.service/navigation.service.ts b/src/viewerModule/nehuba/navigation.service/navigation.service.ts index 7836d2797..17dd91789 100644 --- a/src/viewerModule/nehuba/navigation.service/navigation.service.ts +++ b/src/viewerModule/nehuba/navigation.service/navigation.service.ts @@ -2,13 +2,11 @@ import { Inject, Injectable, OnDestroy, Optional } from "@angular/core"; import { select, Store } from "@ngrx/store"; import { Observable, ReplaySubject, Subscription } from "rxjs"; import { debounceTime } from "rxjs/operators"; -import { selectViewerConfigAnimationFlag } from "src/services/state/viewerConfig/selectors"; import { NehubaViewerUnit } from "../nehubaViewer/nehubaViewer.component"; import { NEHUBA_INSTANCE_INJTKN } from "../util"; -import { timedValues } from 'src/util/generator' -import { INavObj, navAdd, navMul, navObjEqual } from './navigation.util' +import { INavObj, navObjEqual } from './navigation.util' import { actions } from "src/state/atlasSelection"; -import { atlasSelection } from "src/state"; +import { atlasSelection, userPreference } from "src/state"; @Injectable() export class NehubaNavigationService implements OnDestroy{ @@ -33,7 +31,7 @@ export class NehubaNavigationService implements OnDestroy{ ){ this.subscriptions.push( this.store$.pipe( - select(selectViewerConfigAnimationFlag) + select(userPreference.selectors.useAnimation) ).subscribe(flag => this.globalAnimationFlag = flag) ) diff --git a/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.ts b/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.ts index c3b14f7b9..9627bd89b 100644 --- a/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.ts +++ b/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.ts @@ -2,7 +2,6 @@ import { Component, ElementRef, EventEmitter, OnDestroy, OnInit, Output, Inject, import { fromEvent, Subscription, ReplaySubject, BehaviorSubject, Observable, race, timer, Subject } from 'rxjs' import { debounceTime, filter, map, scan, startWith, mapTo, switchMap, take, skip, tap, distinctUntilChanged } from "rxjs/operators"; import { AtlasWorkerService } from "src/atlasViewer/atlasViewer.workerService.service"; -import { StateInterface as ViewerConfiguration } from "src/services/state/viewerConfig.store"; import { LoggingService } from "src/logging"; import { bufferUntil, getExportNehuba, getViewer, setNehubaViewer, switchMapWaitFor } from "src/util/fn"; import { deserializeSegment, NEHUBA_INSTANCE_INJTKN, scanSliceViewRenderFn } from "../util"; @@ -370,7 +369,7 @@ export class NehubaViewerUnit implements OnInit, OnDestroy { public numMeshesToBeLoaded: number = 0 - public applyPerformanceConfig({ gpuLimit }: Partial<ViewerConfiguration>) { + public applyGpuLimit(gpuLimit: number) { if (gpuLimit && this.nehubaViewer) { const limit = this.nehubaViewer.ngviewer.state.children.get('gpuMemoryLimit') if (limit && limit.restoreState) { diff --git a/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.spec.ts b/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.spec.ts index faa78cb3d..c3882f02f 100644 --- a/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.spec.ts +++ b/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.spec.ts @@ -7,9 +7,6 @@ import { NEVER, Subject } from "rxjs" import { ComponentsModule } from "src/components" import { ClickInterceptorService } from "src/glue" import { LayoutModule } from "src/layouts/layout.module" -import { PANELS } from "src/services/state/ngViewerState/constants" -import { ngViewerSelectorOctantRemoval, ngViewerSelectorPanelMode, ngViewerSelectorPanelOrder } from "src/services/state/ngViewerState/selectors" -import { viewerStateCustomLandmarkSelector, viewerStateNavigationStateSelector, viewerStateSelectedRegionsSelector } from "src/services/state/viewerState/selectors" import { Landmark2DModule } from "src/ui/nehubaContainer/2dLandmarks/module" import { QuickTourModule } from "src/ui/quickTour" import { AngularMaterialModule } from "src/sharedModules/angularMaterial.module" @@ -24,7 +21,7 @@ import { TouchSideClass } from "../touchSideClass.directive" import { NehubaGlueCmp } from "./nehubaViewerGlue.component" import { HarnessLoader } from "@angular/cdk/testing" import { AtlasWorkerService } from "src/atlasViewer/atlasViewer.workerService.service" - +import { userInterface, atlasSelection, userPreference, atlasAppearance } from "src/state" @Component({ selector: 'viewer-ctrl-component', @@ -132,12 +129,11 @@ describe('> nehubaViewerGlue.component.ts', () => { beforeEach(() => { mockStore = TestBed.inject(MockStore) - mockStore.overrideSelector(ngViewerSelectorPanelMode, PANELS.FOUR_PANEL) - mockStore.overrideSelector(ngViewerSelectorPanelOrder, '0123') - mockStore.overrideSelector(ngViewerSelectorOctantRemoval, true) - mockStore.overrideSelector(viewerStateCustomLandmarkSelector, []) - mockStore.overrideSelector(viewerStateSelectedRegionsSelector, []) - mockStore.overrideSelector(viewerStateNavigationStateSelector, null) + mockStore.overrideSelector(userInterface.selectors.panelMode, "FOUR_PANEL") + mockStore.overrideSelector(userInterface.selectors.panelOrder, '0123') + mockStore.overrideSelector(atlasAppearance.selectors.octantRemoval, true) + mockStore.overrideSelector(atlasSelection.selectors.selectedRegions, []) + mockStore.overrideSelector(atlasSelection.selectors.navigation, null) mockStore.overrideSelector(selectorAuxMeshes, []) }) diff --git a/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts b/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts index 55ae60bb3..66d4e77a7 100644 --- a/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts +++ b/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts @@ -5,7 +5,6 @@ import { ngViewerActionCycleViews, ngViewerActionToggleMax } from "src/services/ import { ClickInterceptor, CLICK_INTERCEPTOR_INJECTOR } from "src/util"; import { debounceTime, distinctUntilChanged, filter, map, mapTo, scan, shareReplay, startWith, switchMap, switchMapTo, take, tap, throttleTime } from "rxjs/operators"; import { viewerStateAddUserLandmarks, viewerStateMouseOverCustomLandmark } from "src/services/state/viewerState/actions"; -import { ngViewerSelectorPanelOrder, ngViewerSelectorPanelMode } from "src/services/state/ngViewerState/selectors"; import { ARIA_LABELS, IDS, QUICKTOUR_DESC } from 'common/constants' import { PANELS } from "src/services/state/ngViewerState/constants"; import { LoggingService } from "src/logging"; @@ -31,7 +30,7 @@ import { NehubaConfig, getNehubaConfig, fromRootStore, NgLayerSpec, NgPrecompMes import { generalActionError } from "src/services/stateStore.helper"; import { SET_MESHES_TO_LOAD } from "../constants"; import { actions } from "src/state/atlasSelection"; -import { annotation, atlasSelection, userInteraction } from "src/state"; +import { annotation, atlasSelection, userInteraction, userInterface } from "src/state"; export const INVALID_FILE_INPUT = `Exactly one (1) nifti file is required!` @@ -169,7 +168,7 @@ export class NehubaGlueCmp implements IViewer<'nehuba'>, OnDestroy, AfterViewIni } public panelOrder$ = this.store$.pipe( - select(ngViewerSelectorPanelOrder), + select(userInterface.selectors.panelOrder), distinctUntilChanged(), shareReplay(1), ) @@ -392,7 +391,7 @@ export class NehubaGlueCmp implements IViewer<'nehuba'>, OnDestroy, AfterViewIni */ const redrawLayoutSub = combineLatest([ this.store$.pipe( - select(ngViewerSelectorPanelMode), + select(userInterface.selectors.panelMode), distinctUntilChanged(), shareReplay(1), ), diff --git a/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.template.html b/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.template.html index 6c544745f..3f9b846f7 100644 --- a/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.template.html +++ b/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.template.html @@ -99,7 +99,7 @@ </layout-floating-container> <!-- panel controller --> - <div iavLayoutFourCornersBottomRight class="position-relative"> + <div iavLayoutFourCornersBottomRight class="position-relative honing"> <ng-container *ngTemplateOutlet="panelCtrlTmpl; context: { panelIndex: panelIndex, diff --git a/src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerInterface.directive.spec.ts b/src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerInterface.directive.spec.ts index 84a54b7e2..baab9f9d2 100644 --- a/src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerInterface.directive.spec.ts +++ b/src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerInterface.directive.spec.ts @@ -1,16 +1,13 @@ import { Component } from "@angular/core" import { TestBed, async, ComponentFixture, fakeAsync, tick } from "@angular/core/testing" import { By } from "@angular/platform-browser" -import { BrowserDynamicTestingModule } from "@angular/platform-browser-dynamic/testing" import { MockStore, provideMockStore } from "@ngrx/store/testing" -import { ngViewerSelectorOctantRemoval } from "src/services/state/ngViewerState/selectors" import { NehubaViewerUnit } from "../nehubaViewer/nehubaViewer.component" import { NehubaViewerContainerDirective } from "./nehubaViewerInterface.directive" -import { viewerStateSelectorNavigation, viewerStateStandAloneVolumes } from "src/services/state/viewerState/selectors"; import { Subject } from "rxjs" import { ngViewerActionNehubaReady } from "src/services/state/ngViewerState/actions" import { viewerStateMouseOverCustomLandmarkInPerspectiveView } from "src/services/state/viewerState/actions" -import { selectViewerConfigAnimationFlag } from "src/services/state/viewerConfig/selectors" +import { userPreference, atlasSelection, atlasAppearance } from "src/state" describe('> nehubaViewerInterface.directive.ts', () => { describe('> NehubaViewerContainerDirective', () => { @@ -46,10 +43,10 @@ describe('> nehubaViewerInterface.directive.ts', () => { beforeEach(() => { const mockStore = TestBed.inject(MockStore) - mockStore.overrideSelector(ngViewerSelectorOctantRemoval, true) - mockStore.overrideSelector(viewerStateStandAloneVolumes, []) - mockStore.overrideSelector(viewerStateSelectorNavigation, null) - mockStore.overrideSelector(selectViewerConfigAnimationFlag, false) + mockStore.overrideSelector(atlasAppearance.selectors.octantRemoval, true) + mockStore.overrideSelector(atlasSelection.selectors.standaloneVolumes, []) + mockStore.overrideSelector(atlasSelection.selectors.navigation, null) + mockStore.overrideSelector(userPreference.selectors.useAnimation, false) }) it('> can be inited', () => { diff --git a/src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerInterface.directive.ts b/src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerInterface.directive.ts index 59062511f..e476b8055 100644 --- a/src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerInterface.directive.ts +++ b/src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerInterface.directive.ts @@ -5,14 +5,12 @@ import { Subscription, Observable, fromEvent, asyncScheduler, combineLatest } fr import { distinctUntilChanged, filter, debounceTime, scan, map, throttleTime, switchMapTo } from "rxjs/operators"; import { serializeSegment, takeOnePipe } from "../util"; import { ngViewerActionNehubaReady } from "src/services/state/ngViewerState/actions"; -import { ngViewerSelectorOctantRemoval } from "src/services/state/ngViewerState/selectors"; import { LoggingService } from "src/logging"; -import { uiActionMouseoverLandmark, uiActionMouseoverSegments } from "src/services/state/uiState/actions"; -import { IViewerConfigState } from "src/services/state/viewerConfig.store.helper"; +import { uiActionMouseoverLandmark } from "src/services/state/uiState/actions"; import { arrayOfPrimitiveEqual } from 'src/util/fn' import { INavObj, NehubaNavigationService } from "../navigation.service"; import { NehubaConfig, defaultNehubaConfig } from "../config.service"; -import { atlasSelection } from "src/state"; +import { atlasAppearance, atlasSelection, userPreference } from "src/state"; const determineProtocol = (url: string) => { @@ -162,25 +160,16 @@ export class NehubaViewerContainerDirective implements OnInit, OnDestroy{ @Optional() private log: LoggingService, ){ this.nehubaViewerFactory = this.cfr.resolveComponentFactory(NehubaViewerUnit) - - 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 - */ - distinctUntilChanged(), - ) - - this.nehubaViewerPerspectiveOctantRemoval$ = this.store$.pipe( - select(ngViewerSelectorOctantRemoval), - ) } - private nehubaViewerPerspectiveOctantRemoval$: Observable<boolean> + private nehubaViewerPerspectiveOctantRemoval$ = this.store$.pipe( + select(atlasAppearance.selectors.octantRemoval), + ) - private viewerPerformanceConfig$: Observable<IViewerConfigState> - private viewerConfig: Partial<IViewerConfigState> = {} + private gpuLimit$: Observable<number> = this.store$.pipe( + select(userPreference.selectors.gpuLimit) + ) + private gpuLimit: number = null private nehubaViewerSubscriptions: Subscription[] = [] private subscriptions: Subscription[] = [] @@ -218,12 +207,12 @@ export class NehubaViewerContainerDirective implements OnInit, OnDestroy{ this.createNehubaInstance(copiedNehubaConfig, { onInit }) }), - this.viewerPerformanceConfig$.pipe( - debounceTime(200) - ).subscribe(config => { - this.viewerConfig = config + this.gpuLimit$.pipe( + debounceTime(200), + ).subscribe(limit => { + this.gpuLimit = limit if (this.nehubaViewerInstance && this.nehubaViewerInstance.nehubaViewer) { - this.nehubaViewerInstance.applyPerformanceConfig(config) + this.nehubaViewerInstance.applyGpuLimit(limit) } }), this.navService.viewerNav$.subscribe(v => { @@ -261,15 +250,14 @@ export class NehubaViewerContainerDirective implements OnInit, OnDestroy{ /** * apply viewer config such as gpu limit */ - const { gpuLimit = null } = this.viewerConfig this.nehubaViewerInstance.config = nehubaConfig this.nehubaViewerInstance.lifecycle = lifeCycle - if (gpuLimit) { + if (this.gpuLimit) { const initialNgState = nehubaConfig && nehubaConfig.dataset && nehubaConfig.dataset.initialNgState // the correct key is gpuMemoryLimit - initialNgState.gpuMemoryLimit = gpuLimit + initialNgState.gpuMemoryLimit = this.gpuLimit } this.nehubaViewerSubscriptions.push( diff --git a/src/viewerModule/nehuba/store/actions.ts b/src/viewerModule/nehuba/store/actions.ts index 38c6572e1..03e4821f5 100644 --- a/src/viewerModule/nehuba/store/actions.ts +++ b/src/viewerModule/nehuba/store/actions.ts @@ -1,12 +1,12 @@ import { createAction, props } from "@ngrx/store"; -import { INgLayerInterface } from "src/services/state/ngViewerState.store"; + import { NEHUBA_VIEWER_FEATURE_KEY } from "../constants"; import { IAuxMesh } from "./type"; export const actionAddNgLayer = createAction( `[${NEHUBA_VIEWER_FEATURE_KEY}] [addNgLayer]`, props<{ - layers: INgLayerInterface[] + layers: any[] }>() ) diff --git a/src/viewerModule/nehuba/touchSideClass.directive.ts b/src/viewerModule/nehuba/touchSideClass.directive.ts index 44cdac937..eee66a8a1 100644 --- a/src/viewerModule/nehuba/touchSideClass.directive.ts +++ b/src/viewerModule/nehuba/touchSideClass.directive.ts @@ -1,11 +1,9 @@ import { Directive, ElementRef, Input, OnDestroy, OnInit } from "@angular/core"; import { select, Store } from "@ngrx/store"; import { Observable, Subscription } from "rxjs"; -import { distinctUntilChanged, tap } from "rxjs/operators"; -import { ngViewerSelectorPanelMode } from "src/services/state/ngViewerState/selectors"; -import { IavRootStoreInterface } from "src/services/stateStore.service"; +import { distinctUntilChanged } from "rxjs/operators"; import { addTouchSideClasses, removeTouchSideClasses } from "src/viewerModule/nehuba/util"; - +import { userInterface } from "src/state" @Directive({ selector: '[touch-side-class]', @@ -16,25 +14,23 @@ export class TouchSideClass implements OnDestroy, OnInit { @Input('touch-side-class') public panelNativeIndex: number - public panelMode: string - private panelMode$: Observable<string> + public panelMode: userInterface.PanelMode + private panelMode$: Observable<userInterface.PanelMode> = this.store$.pipe( + select(userInterface.selectors.panelMode), + distinctUntilChanged(), + ) private subscriptions: Subscription[] = [] constructor( - private store$: Store<IavRootStoreInterface>, + private store$: Store<any>, private el: ElementRef, ) { - - this.panelMode$ = this.store$.pipe( - select(ngViewerSelectorPanelMode), - distinctUntilChanged(), - tap(mode => this.panelMode = mode), - ) } public ngOnInit() { this.subscriptions.push( + this.panelMode$.subscribe(panelMode => this.panelMode = panelMode), this.panelMode$.subscribe(panelMode => { removeTouchSideClasses(this.el.nativeElement) diff --git a/src/viewerModule/nehuba/viewerCtrl/viewerCtrlCmp/viewerCtrlCmp.component.spec.ts b/src/viewerModule/nehuba/viewerCtrl/viewerCtrlCmp/viewerCtrlCmp.component.spec.ts index 54b6e08fa..b9476bf4f 100644 --- a/src/viewerModule/nehuba/viewerCtrl/viewerCtrlCmp/viewerCtrlCmp.component.spec.ts +++ b/src/viewerModule/nehuba/viewerCtrl/viewerCtrlCmp/viewerCtrlCmp.component.spec.ts @@ -4,8 +4,6 @@ import { FormsModule, ReactiveFormsModule } from "@angular/forms" import { MockStore, provideMockStore } from "@ngrx/store/testing" import { BehaviorSubject, of } from "rxjs" import { ComponentsModule } from "src/components" -import { ngViewerSelectorOctantRemoval } from "src/services/state/ngViewerState.store.helper" -import { viewerStateCustomLandmarkSelector, viewerStateSelectedTemplatePureSelector } from "src/services/state/viewerState/selectors" import { AngularMaterialModule } from "src/sharedModules" import {PureContantService, UtilModule} from "src/util" import { actionSetAuxMeshes, selectorAuxMeshes } from "../../store" @@ -14,6 +12,8 @@ import { ViewerCtrlCmp } from "./viewerCtrlCmp.component" import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed' import { HarnessLoader } from "@angular/cdk/testing" import { MatSlideToggleHarness } from '@angular/material/slide-toggle/testing' +import { atlasAppearance } from "src/state" + describe('> viewerCtrlCmp.component.ts', () => { describe('> ViewerCtrlCmp', () => { @@ -81,7 +81,7 @@ describe('> viewerCtrlCmp.component.ts', () => { beforeEach(() => { mockStore = TestBed.inject(MockStore) mockStore.overrideSelector(viewerStateSelectedTemplatePureSelector, {}) - mockStore.overrideSelector(ngViewerSelectorOctantRemoval, true) + mockStore.overrideSelector(atlasAppearance.selectors.octantRemoval, true) mockStore.overrideSelector(viewerStateCustomLandmarkSelector, []) mockStore.overrideSelector(selectorAuxMeshes, []) }) diff --git a/src/viewerModule/nehuba/viewerCtrl/viewerCtrlCmp/viewerCtrlCmp.component.ts b/src/viewerModule/nehuba/viewerCtrl/viewerCtrlCmp/viewerCtrlCmp.component.ts index cd4385139..6df4e2704 100644 --- a/src/viewerModule/nehuba/viewerCtrl/viewerCtrlCmp/viewerCtrlCmp.component.ts +++ b/src/viewerModule/nehuba/viewerCtrl/viewerCtrlCmp/viewerCtrlCmp.component.ts @@ -1,16 +1,14 @@ import { Component, HostBinding, Inject, Optional } from "@angular/core"; import { select, Store } from "@ngrx/store"; -import { combineLatest, merge, Observable, of, Subscription } from "rxjs"; -import {filter, map, pairwise, withLatestFrom} from "rxjs/operators"; -import { ngViewerActionSetPerspOctantRemoval } from "src/services/state/ngViewerState/actions"; -import { ngViewerSelectorOctantRemoval } from "src/services/state/ngViewerState/selectors"; +import { merge, Observable, of, Subscription } from "rxjs"; +import { pairwise, withLatestFrom} from "rxjs/operators"; import { NehubaViewerUnit } from "src/viewerModule/nehuba"; import { NEHUBA_INSTANCE_INJTKN } from "src/viewerModule/nehuba/util"; import { ARIA_LABELS } from 'common/constants' import { actionSetAuxMeshes, selectorAuxMeshes } from "../../store"; import { FormBuilder, FormControl, FormGroup } from "@angular/forms"; -import {PureContantService} from "src/util"; -import { atlasSelection } from "src/state"; +import { PureContantService } from "src/util"; +import { atlasSelection, atlasAppearance } from "src/state"; @Component({ selector: 'viewer-ctrl-component', @@ -31,14 +29,6 @@ export class ViewerCtrlCmp{ private selectedAtlasId: string private selectedTemplateId: string - private _flagDelin = true - get flagDelin(){ - return this._flagDelin - } - set flagDelin(flag){ - this._flagDelin = flag - this.toggleParcVsbl() - } private sub: Subscription[] = [] private hiddenLayerNames: string[] = [] @@ -54,7 +44,7 @@ export class ViewerCtrlCmp{ } public nehubaViewerPerspectiveOctantRemoval$ = this.store$.pipe( - select(ngViewerSelectorOctantRemoval), + select(atlasAppearance.selectors.octantRemoval), ) public auxMeshFormGroup: FormGroup @@ -80,24 +70,25 @@ export class ViewerCtrlCmp{ // TODO move this to... nehubadirective? - // if (this.nehubaInst$) { - // this.sub.push( - // combineLatest([ - // this.customLandmarks$, - // this.nehubaInst$, - // ]).pipe( - // filter(([_, nehubaInst]) => !!nehubaInst), - // ).subscribe(([landmarks, nehubainst]) => { - // this.setOctantRemoval(landmarks.length === 0) - // nehubainst.updateUserLandmarks(landmarks) - // }), - // this.nehubaInst$.subscribe(nehubaInst => this.nehubaInst = nehubaInst) - // ) - // } else { - // console.warn(`NEHUBA_INSTANCE_INJTKN not provided`) - // } + if (this.nehubaInst$) { + this.sub.push( + // combineLatest([ + // this.customLandmarks$, + // this.nehubaInst$, + // ]).pipe( + // filter(([_, nehubaInst]) => !!nehubaInst), + // ).subscribe(([landmarks, nehubainst]) => { + // this.setOctantRemoval(landmarks.length === 0) + // nehubainst.updateUserLandmarks(landmarks) + // }), + this.nehubaInst$.subscribe(nehubaInst => this.nehubaInst = nehubaInst) + ) + } else { + console.warn(`NEHUBA_INSTANCE_INJTKN not provided`) + } this.sub.push( + this.store$.pipe( select(atlasSelection.selectors.selectedATP) ).subscribe(({ atlas, parcellation, template }) => { @@ -158,39 +149,10 @@ export class ViewerCtrlCmp{ ) } - private async toggleParcVsbl(){ - const viewerConfig = await this.pureConstantService.getViewerConfig(this.selectedAtlasId, this.selectedTemplateId, null) - - if (this.flagDelin) { - for (const name of this.hiddenLayerNames) { - const l = this.ngViewer.layerManager.getLayerByName(name) - l && l.setVisible(true) - } - this.hiddenLayerNames = [] - } else { - this.hiddenLayerNames = [] - const segLayerNames: string[] = [] - for (const layer of this.ngViewer.layerManager.managedLayers) { - if (layer.visible && layer.name in viewerConfig) { - segLayerNames.push(layer.name) - } - } - for (const name of segLayerNames) { - const l = this.ngViewer.layerManager.getLayerByName(name) - l && l.setVisible(false) - this.hiddenLayerNames.push( name ) - } - } - - requestAnimationFrame(() => { - this.ngViewer.display.scheduleRedraw() - }) - } - public setOctantRemoval(octantRemovalFlag: boolean) { this.store$.dispatch( - ngViewerActionSetPerspOctantRemoval({ - octantRemovalFlag + atlasAppearance.actions.setOctantRemoval({ + flag: octantRemovalFlag }) ) } diff --git a/src/viewerModule/nehuba/viewerCtrl/viewerCtrlCmp/viewerCtrlCmp.template.html b/src/viewerModule/nehuba/viewerCtrl/viewerCtrlCmp/viewerCtrlCmp.template.html index 76aa348a8..f029408a9 100644 --- a/src/viewerModule/nehuba/viewerCtrl/viewerCtrlCmp/viewerCtrlCmp.template.html +++ b/src/viewerModule/nehuba/viewerCtrl/viewerCtrlCmp/viewerCtrlCmp.template.html @@ -1,18 +1,3 @@ -<h3 class="iv-custom-comp text mat-h3"> - Volumes -</h3> - -<mat-slide-toggle [(ngModel)]="flagDelin" - #delinToggle="matSlideToggle" - [iav-key-listener]="[{ type: 'keydown', key: 'q', target: 'document', capture: true }]" - (iav-key-event)="delinToggle.toggle()" - name="toggle-delineation"> - - <markdown-dom class="d-inline-block iv-custom-comp text" - markdown="Show delineations `[q]`"> - </markdown-dom> -</mat-slide-toggle> - <mat-divider class="mt-2 mb-2"></mat-divider> <h3 class="iv-custom-comp text mat-h3"> diff --git a/src/viewerModule/viewerCmp/viewerCmp.component.ts b/src/viewerModule/viewerCmp/viewerCmp.component.ts index edbdcda41..14ae775e5 100644 --- a/src/viewerModule/viewerCmp/viewerCmp.component.ts +++ b/src/viewerModule/viewerCmp/viewerCmp.component.ts @@ -11,9 +11,9 @@ import { ContextMenuService, TContextMenuReg } from "src/contextMenuModule"; import { ComponentStore } from "../componentStore"; import { DialogService } from "src/services/dialogService.service"; import { SAPI, SapiRegionModel } from "src/atlasComponents/sapi"; -import { actions } from "src/state/atlasSelection"; +import { actions, fromRootStore } from "src/state/atlasSelection"; import { atlasSelection, userInteraction } from "src/state"; -import { SapiSpatialFeatureModel, SapiFeatureModel } from "src/atlasComponents/sapi/type"; +import { SapiSpatialFeatureModel, SapiFeatureModel, SapiParcellationModel } from "src/atlasComponents/sapi/type"; type TCStoreViewerCmp = { overlaySideNav: any @@ -116,6 +116,10 @@ export class ViewerCmp implements OnDestroy { map(({ parcellation }) => parcellation) ) + public allAvailableParcellations$ = this.store$.pipe( + fromRootStore.allAvailParcs(this.sapi) + ) + public selectedRegions$ = this.store$.pipe( select(atlasSelection.selectors.selectedRegions), ) @@ -383,4 +387,11 @@ export class ViewerCmp implements OnDestroy { userInteraction.actions.clearShownFeature() ) } + + onDismissNonbaseLayer(){ + + } + onSelectParcellation(parc: SapiParcellationModel){ + + } } diff --git a/src/viewerModule/viewerCmp/viewerCmp.template.html b/src/viewerModule/viewerCmp/viewerCmp.template.html index dc657df12..103d28ebc 100644 --- a/src/viewerModule/viewerCmp/viewerCmp.template.html +++ b/src/viewerModule/viewerCmp/viewerCmp.template.html @@ -205,25 +205,21 @@ <!-- such a gross implementation --> <!-- TODO fix this --> <div class="mt-1-n w-100 sxplr-pl-2 sxplr-pr-2 m-1px"> + + <!-- TODO use sapiViews/core/region/base and fix the rest --> <button mat-raised-button *ngIf="!(onlyShowMiniTray$ | async)" [attr.aria-label]="ARIA_LABELS.EXPAND" (click)="showFullSidenav()" class="explore-btn pe-all w-100" [ngClass]="{ - 'darktheme': iavRegion.rgbDarkmode === true, - 'lighttheme': iavRegion.rgbDarkmode === false + 'darktheme': true, + 'lighttheme': false }" - [style.backgroundColor]="iavRegion?.rgbString || 'accent'"> + [style.backgroundColor]="'accent'"> <span class="text iv-custom-comp"> Explore </span> - - <div class="hidden" - iav-region - [region-base-region]="(selectedRegions$ | async) && (selectedRegions$ | async)[0]" - #iavRegion="iavRegion"> - </div> </button> </div> @@ -362,19 +358,23 @@ <sxplr-sapiviews-core-atlas-tmplparcselector *ngIf="viewerLoaded && !(isStandaloneVolumes$ | async)"> </sxplr-sapiviews-core-atlas-tmplparcselector> - <!-- <atlas-layer-selector *ngIf="viewerLoaded && !(isStandaloneVolumes$ | async)" - #alSelector="atlasLayerSelector" - class="d-inline-block flex-grow-0 flex-shrink-0 pe-all" - (iav-outsideClick)="alSelector.selectorExpanded = false"> - </atlas-layer-selector> --> + <!-- selected parcellation chip --> + <sxplr-sapiviews-core-parcellation-smartchip + class="sxplr-pointer-events-all" + [sxplr-sapiviews-core-parcellation-smartchip-parcellation]="parcellationSelected$ | async" + [sxplr-sapiviews-core-parcellation-smartchip-all-parcellations]="allAvailableParcellations$ | async" + (sxplr-sapiviews-core-parcellation-smartchip-dismiss-nonbase-layer)="onDismissNonbaseLayer()" + (sxplr-sapiviews-core-parcellation-smartchip-select-parcellation)="onSelectParcellation($event)" + > + </sxplr-sapiviews-core-parcellation-smartchip> <!-- chips --> - <div *ngIf="parcellationSelected$ | async" + <!-- <div *ngIf="parcellationSelected$ | async" class="d-inline-block flex-grow-1 flex-shrink-1 pe-none overflow-x-auto overflow-y-hidden"> <viewer-state-breadcrumb class="d-inline-block pe-all" (on-item-click)="showFullSideNav()"> </viewer-state-breadcrumb> - </div> + </div> --> </ng-template> @@ -471,13 +471,10 @@ <!-- if region selected > 0 --> <ng-template [ngIf]="regionSelected?.length > 0" [ngIfElse]="tabTmpl_nothingSelected"> - <div class="hidden" - iav-region - [region-base-region]="regionSelected[0]" - #tabTmpl_iavRegion="iavRegion"> - </div> - <ng-container *ngTemplateOutlet="tabTmpl_defaultTmpl; context: { + + <!-- TODO fix with sapiView/core/region directive --> + <!-- <ng-container *ngTemplateOutlet="tabTmpl_defaultTmpl; context: { matColor: 'accent', customColor: tabTmpl_iavRegion.rgbString, customColorDarkmode: tabTmpl_iavRegion.rgbDarkmode, @@ -486,7 +483,7 @@ click: click }"> - </ng-container> + </ng-container> --> </ng-template> <!-- nothing is selected --> @@ -558,8 +555,16 @@ <ng-template [ngIf]="selectedRegions.length === 1" [ngIfElse]="multiRegionWrapperTmpl"> <!-- a series of bugs result in requiring this hacky --> <!-- see https://github.com/HumanBrainProject/interactive-viewer/issues/698 --> - <ng-container *ngTemplateOutlet="singleRegionTmpl; context: { region: selectedRegions[0] }"> - </ng-container> + + <sxplr-sapiviews-core-region-region-rich + [sxplr-sapiviews-core-region-atlas]="selectedAtlas$ | async" + [sxplr-sapiviews-core-region-template]="templateSelected$ | async" + [sxplr-sapiviews-core-region-parcellation]="parcellationSelected$ | async" + [sxplr-sapiviews-core-region-region]="selectedRegions[0]" + (sxplr-sapiviews-core-region-region-rich-feature-clicked)="showDataset($event)" + > + <div class="sapi-container" header></div> + </sxplr-sapiviews-core-region-region-rich> </ng-template> <!-- multi region wrapper --> @@ -573,8 +578,7 @@ <!-- place holder if length === 0 --> <ng-container *ngIf="selectedRegions.length === 0"> - <ng-container *ngTemplateOutlet="singleRegionTmpl; context: { region: false }"> - </ng-container> + no region selected </ng-container> </ng-container> @@ -591,21 +595,6 @@ </ng-template> -<!-- single region tmpl --> -<ng-template #singleRegionTmpl let-region="region"> - <!-- region detail --> - <sxplr-sapiviews-core-region-region-rich - [sxplr-sapiviews-core-region-atlas]="selectedAtlas$ | async" - [sxplr-sapiviews-core-region-template]="templateSelected$ | async" - [sxplr-sapiviews-core-region-parcellation]="parcellationSelected$ | async" - [sxplr-sapiviews-core-region-region]="region" - (sxplr-sapiviews-core-region-region-rich-feature-clicked)="showDataset($event)" - > - <div class="sapi-container" header></div> - </sxplr-sapiviews-core-region-region-rich> -</ng-template> - - <!-- expansion tmpl --> <ng-template #ngMatAccordionTmpl let-title="title" @@ -614,37 +603,6 @@ let-iconTooltip="iconTooltip" let-iavNgIf="iavNgIf" let-content="content"> - <mat-expansion-panel - [attr.data-opened]="expansionPanel.expanded" - [attr.data-mat-expansion-title]="title" - hideToggle - *ngIf="iavNgIf" - #expansionPanel="matExpansionPanel"> - - <mat-expansion-panel-header> - - <!-- title --> - <mat-panel-title> - {{ title }} - </mat-panel-title> - - <!-- desc + icon --> - <mat-panel-description class="d-flex align-items-center justify-content-end" - [matTooltip]="iconTooltip"> - <span class="mr-3">{{ desc }}</span> - <span class="accordion-icon d-inline-flex justify-content-center"> - <i [class]="iconClass"></i> - </span> - </mat-panel-description> - - </mat-expansion-panel-header> - - <!-- content --> - <ng-template matExpansionPanelContent> - <ng-container *ngTemplateOutlet="content; context: { expansionPanel: expansionPanel }"> - </ng-container> - </ng-template> - </mat-expansion-panel> </ng-template> <!-- select region error... for whatever reason --> @@ -656,42 +614,42 @@ <!-- multi region tmpl --> <ng-template #multiRegionTmpl let-regions="regions"> <ng-template [ngIf]="regions.length > 0" [ngIfElse]="regionPlaceholderTmpl"> - <!-- <region-menu - [region-base-region]="{ name: CONST.MULTI_REGION_SELECTION }" - class="bs-border-box ml-15px-n mr-15px-n mat-elevation-z4"> - </region-menu> --> - MULTI_REGION_SELECTION <!-- other regions detail accordion --> <mat-accordion class="bs-border-box ml-15px-n mr-15px-n mt-2"> <!-- Multi regions include --> - <ng-template #multiRegionInclTmpl> - - <mat-chip *ngFor="let r of regions" - iav-region - [region-base-region]="r" - class="m-1" - [ngClass]="{ - 'darktheme':regionDirective.rgbDarkmode === true, - 'lighttheme': regionDirective.rgbDarkmode === false - }" - [style.backgroundColor]="regionDirective.rgbString" - #regionDirective="iavRegion"> - <span class="iv-custom-comp text text-truncate d-inline"> - {{ r.name }} - </span> - </mat-chip> - </ng-template> - <ng-container *ngTemplateOutlet="ngMatAccordionTmpl; context: { - title: 'Brain regions', - desc: regions.length, - iconClass: 'fas fa-brain', - iavNgIf: true, - content: multiRegionInclTmpl - }"> - </ng-container> + <mat-expansion-panel + [attr.data-opened]="expansionPanel.expanded" + [attr.data-mat-expansion-title]="'Brain regions'" + hideToggle + #expansionPanel="matExpansionPanel"> + + <mat-expansion-panel-header> + + <!-- title --> + <mat-panel-title> + Brain regions + </mat-panel-title> + + <!-- desc + icon --> + <mat-panel-description class="d-flex align-items-center justify-content-end"> + <span class="mr-3">{{ regions.length }}</span> + <span class="accordion-icon d-inline-flex justify-content-center"> + <i class="fas fa-brain"></i> + </span> + </mat-panel-description> + + </mat-expansion-panel-header> + + <!-- content --> + <ng-template matExpansionPanelContent> + + <!-- TODO use actual region chip in sapiViews/core/region/chip --> + SOMETHING + </ng-template> + </mat-expansion-panel> </mat-accordion> </ng-template> diff --git a/src/viewerModule/viewerStateBreadCrumb/breadcrumb/breadcrumb.template.html b/src/viewerModule/viewerStateBreadCrumb/breadcrumb/breadcrumb.template.html index 89dc12e40..d1338f81b 100644 --- a/src/viewerModule/viewerStateBreadCrumb/breadcrumb/breadcrumb.template.html +++ b/src/viewerModule/viewerStateBreadCrumb/breadcrumb/breadcrumb.template.html @@ -86,28 +86,9 @@ <ng-container *ngFor="let r of selectedRegions"> <!-- region chip for discrete map --> - <mat-chip - (click)="handleChipClick()" - [region-base-region]="r" - class="pe-all position-relative z-index-1 ml-8-n" - [ngClass]="{ - 'darktheme':regionDirective.rgbDarkmode === true, - 'lighttheme': regionDirective.rgbDarkmode === false - }" - [style.backgroundColor]="regionDirective.rgbString" - iav-region - #regionDirective="iavRegion"> - <span class="iv-custom-comp text text-truncate d-inline sxplr-pl-4"> - {{ r.name }} - </span> - <mat-icon - class="iv-custom-comp text" - (click)="clearSelectedRegions()" - fontSet="fas" - iav-stop="click" - fontIcon="fa-times"> - </mat-icon> - </mat-chip> + + <!-- TODO use actual region chip --> + <mat-chip>REGION PLACE HOLDER</mat-chip> <!-- chips for previewing origin datasets/continous map --> <ng-container *ngFor="let originDataset of (r.originDatasets || []); let index = index"> diff --git a/src/viewerModule/viewerStateBreadCrumb/module.ts b/src/viewerModule/viewerStateBreadCrumb/module.ts index 28de784c3..c331dbbae 100644 --- a/src/viewerModule/viewerStateBreadCrumb/module.ts +++ b/src/viewerModule/viewerStateBreadCrumb/module.ts @@ -1,6 +1,5 @@ import { CommonModule } from "@angular/common"; import { NgModule } from "@angular/core"; -import { ParcellationRegionModule } from "src/atlasComponents/parcellationRegion"; import { QuickTourModule } from "src/ui/quickTour"; import { AngularMaterialModule } from "src/sharedModules"; import { UtilModule } from "src/util"; @@ -13,7 +12,6 @@ import { DialogInfoModule } from "src/ui/dialogInfo"; CommonModule, AngularMaterialModule, QuickTourModule, - ParcellationRegionModule, UtilModule, DialogInfoModule, ], -- GitLab