From 1380a07a7143d3f1a618de6cd802c996c0225af1 Mon Sep 17 00:00:00 2001 From: Xiao Gui <xgui3783@gmail.com> Date: Fri, 25 Mar 2022 07:58:09 +0100 Subject: [PATCH] chore: remove used var/fn chore: add explicit typing feat: UI indicating parc/space is supported in current selection bugfix: parcellation view stories provide mock store feat: parc tile: allow tmpl inj to be used for grped parcs chore: add box sizing border box to tile component bugfix: on select tmpl/parc, auto switch if curr parc/tmpl unsuitable bugfix: resize directive GC --- src/atlasComponents/sapi/stories.base.ts | 7 +- .../sapiViews/core/atlas/module.ts | 2 + .../tmplParcSelector.component.ts | 8 +- .../tmplParcSelector.stories.ts | 71 ++++++++--- .../tmplParcSelector.template.html | 20 ++- .../chip/parcellation.chip.stories.ts | 2 + .../parcellation.smartChip.stories.ts | 2 + .../tile/parcellation.tile.component.ts | 14 ++- .../tile/parcellation.tile.stories.ts | 24 ++++ .../tile/parcellation.tile.template.html | 34 ++++-- .../parcellation/tile/singleTile.stories.ts | 14 ++- src/atlasComponents/sapiViews/util/module.ts | 9 ++ ...arcellationSupportedInCurrentSpace.pipe.ts | 37 ++++++ .../util/parcellationSupportedInSpace.pipe.ts | 42 +++++++ ...paceSupportedInCurrentParcellation.pipe.ts | 38 ++++++ src/components/tile/tile.style.css | 1 + src/overwrite.scss | 8 ++ src/state/atlasSelection/actions.ts | 24 +++- src/state/atlasSelection/const.ts | 27 +++++ src/state/atlasSelection/effects.ts | 114 ++++++++++++++++-- src/state/atlasSelection/index.ts | 4 +- src/state/atlasSelection/selectors.ts | 3 +- src/state/atlasSelection/store.ts | 53 +------- .../windowResize/windowResize.directive.ts | 8 +- 24 files changed, 456 insertions(+), 110 deletions(-) create mode 100644 src/atlasComponents/sapiViews/util/parcellationSupportedInCurrentSpace.pipe.ts create mode 100644 src/atlasComponents/sapiViews/util/parcellationSupportedInSpace.pipe.ts create mode 100644 src/atlasComponents/sapiViews/util/spaceSupportedInCurrentParcellation.pipe.ts diff --git a/src/atlasComponents/sapi/stories.base.ts b/src/atlasComponents/sapi/stories.base.ts index becc12536..f4162b72d 100644 --- a/src/atlasComponents/sapi/stories.base.ts +++ b/src/atlasComponents/sapi/stories.base.ts @@ -46,7 +46,8 @@ export const atlasId = { export const spaceId = { human: { - mni152: 'minds/core/referencespace/v1.0.0/dafcffc5-4826-4bf1-8ff6-46b8a31ff8e2' + mni152: 'minds/core/referencespace/v1.0.0/dafcffc5-4826-4bf1-8ff6-46b8a31ff8e2', + bigbrain: 'minds/core/referencespace/v1.0.0/a1655b99-82f1-420f-a3c2-fe80fd4c8588' }, rat: { waxholm: "minds/core/referencespace/v1.0.0/d5717c4a-0fa1-46e6-918c-b8003069ade8" @@ -56,7 +57,9 @@ export const spaceId = { export const parcId = { human: { jba29: "minds/core/parcellationatlas/v1.0.0/94c1125b-b87e-45e4-901c-00daee7f2579-290", - longBundle: "juelich/iav/atlas/v1.0.0/5" + longBundle: "juelich/iav/atlas/v1.0.0/5", + difumo256: "minds/core/parcellationatlas/v1.0.0/141d510f-0342-4f94-ace7-c97d5f160235", + corticalLayers: "juelich/iav/atlas/v1.0.0/3" }, rat: { v4: 'minds/core/parcellationatlas/v1.0.0/ebb923ba-b4d5-4b82-8088-fa9215c2e1fe-v4' diff --git a/src/atlasComponents/sapiViews/core/atlas/module.ts b/src/atlasComponents/sapiViews/core/atlas/module.ts index 770a8e9e1..a41cdb25c 100644 --- a/src/atlasComponents/sapiViews/core/atlas/module.ts +++ b/src/atlasComponents/sapiViews/core/atlas/module.ts @@ -3,6 +3,7 @@ import { NgModule } from "@angular/core"; import { SpinnerModule } from "src/components/spinner"; import { AngularMaterialModule } from "src/sharedModules"; import { QuickTourModule } from "src/ui/quickTour"; +import { SapiViewsUtilModule } from "../../util"; import { SapiViewsCoreParcellationModule } from "../parcellation"; import { SapiViewsCoreSpaceModule } from "../space"; import { SapiViewsCoreAtlasAtlasDropdownSelector } from "./dropdownAtlasSelector/dropdownAtlasSelector.component"; @@ -16,6 +17,7 @@ import { SapiViewsCoreAtlasAtlasTmplParcSelector } from "./tmplParcSelector/tmpl SapiViewsCoreParcellationModule, QuickTourModule, SpinnerModule, + SapiViewsUtilModule, ], declarations: [ SapiViewsCoreAtlasAtlasDropdownSelector, diff --git a/src/atlasComponents/sapiViews/core/atlas/tmplParcSelector/tmplParcSelector.component.ts b/src/atlasComponents/sapiViews/core/atlas/tmplParcSelector/tmplParcSelector.component.ts index 721f6508c..be8b5bfc5 100644 --- a/src/atlasComponents/sapiViews/core/atlas/tmplParcSelector/tmplParcSelector.component.ts +++ b/src/atlasComponents/sapiViews/core/atlas/tmplParcSelector/tmplParcSelector.component.ts @@ -1,6 +1,6 @@ import { animate, state, style, transition, trigger } from "@angular/animations"; import { ChangeDetectionStrategy, Component, ElementRef, HostBinding, QueryList, ViewChild, ViewChildren } from "@angular/core"; -import { select, Store } from "@ngrx/store"; +import { Store } from "@ngrx/store"; import { combineLatest, forkJoin, merge, Observable, Subject, Subscription } from "rxjs"; import { distinctUntilChanged, map, mapTo, shareReplay, switchMap, tap } from "rxjs/operators"; import { SAPI } from "src/atlasComponents/sapi"; @@ -170,11 +170,7 @@ export class SapiViewsCoreAtlasAtlasTmplParcSelector { } - trackbyAtId(t) { + trackTmpl(t:SapiSpaceModel) { return t['@id'] } - - trackKeyVal(obj: { key: string, value: any }) { - return obj.key - } } \ No newline at end of file diff --git a/src/atlasComponents/sapiViews/core/atlas/tmplParcSelector/tmplParcSelector.stories.ts b/src/atlasComponents/sapiViews/core/atlas/tmplParcSelector/tmplParcSelector.stories.ts index 8121569be..c5bae1bbd 100644 --- a/src/atlasComponents/sapiViews/core/atlas/tmplParcSelector/tmplParcSelector.stories.ts +++ b/src/atlasComponents/sapiViews/core/atlas/tmplParcSelector/tmplParcSelector.stories.ts @@ -4,7 +4,7 @@ import { provideMockStore } from "@ngrx/store/testing" import { action } from "@storybook/addon-actions" import { Meta, moduleMetadata, Story } from "@storybook/angular" import { SAPI } from "src/atlasComponents/sapi" -import { spaceId, provideDarkTheme, getHumanAtlas, getMni152, getJba29 } from "src/atlasComponents/sapi/stories.base" +import { spaceId, provideDarkTheme, getHumanAtlas, getMni152, getJba29, getSpace, atlasId, getParc, parcId } from "src/atlasComponents/sapi/stories.base" import { AngularMaterialModule } from "src/sharedModules" import { atlasSelection } from "src/state" import { SapiViewsCoreAtlasModule } from "../module" @@ -67,29 +67,49 @@ Template.loaders = [ ] -const asyncLoader = async () => { - const atlas = await getHumanAtlas() - const template = await getMni152() - const parcellation = await getJba29() - return { - atlas, - template, - parcellation, - } +export const MNI152JBA29 = Template.bind({}) +MNI152JBA29.args = { + } +MNI152JBA29.loaders = [ + async () => { + const atlas = await getHumanAtlas() + const template = await getMni152() + const parcellation = await getJba29() + return { + atlas, + template, + parcellation, + } + } +] -export const Default = Template.bind({}) -Default.args = { - selected: spaceId.human.mni152 +export const BigBrainJBA29 = Template.bind({}) +BigBrainJBA29.args = { + } -Default.loaders = [ - +BigBrainJBA29.loaders = [ async () => { - const { + const atlas = await getHumanAtlas() + const template = await getSpace(atlasId.human, spaceId.human.bigbrain) + const parcellation = await getJba29() + return { atlas, template, parcellation, - } = await asyncLoader() + } + } +] + +export const BigBrainCorticalLayers = Template.bind({}) +BigBrainCorticalLayers.args = { + +} +BigBrainCorticalLayers.loaders = [ + async () => { + const atlas = await getHumanAtlas() + const template = await getSpace(atlasId.human, spaceId.human.bigbrain) + const parcellation = await getParc(atlasId.human, parcId.human.corticalLayers) return { atlas, template, @@ -97,3 +117,20 @@ Default.loaders = [ } } ] + +export const MNI152LongBundle = Template.bind({}) +MNI152LongBundle.args = { + +} +MNI152LongBundle.loaders = [ + async () => { + const atlas = await getHumanAtlas() + const template = await getMni152() + const parcellation = await getParc(atlasId.human, parcId.human.longBundle) + return { + atlas, + template, + parcellation, + } + } +] \ 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 944f2bc95..db9ba08d8 100644 --- a/src/atlasComponents/sapiViews/core/atlas/tmplParcSelector/tmplParcSelector.template.html +++ b/src/atlasComponents/sapiViews/core/atlas/tmplParcSelector/tmplParcSelector.template.html @@ -18,10 +18,13 @@ rowHeight="2:3" gutterSize="16"> - <mat-grid-tile *ngFor="let template of availableTemplates$ | async; trackBy: trackbyAtId" + <mat-grid-tile *ngFor="let template of availableTemplates$ | async; trackBy: trackTmpl" [attr.aria-checked]="(selectedTemplate$ | async)?.['@id'] === template['@id']"> <sxplr-sapiviews-core-space-tile + [ngClass]="{ + 'sxplr-extra-muted': !(template | spaceSupportedInCurrentParcellation | async) + }" [sxplr-sapiviews-core-space-tile-space]="template" [sxplr-sapiviews-core-space-tile-selected]="(selectedTemplate$ | async)?.['@id'] === template['@id']" (click)="selectTemplate(template)"> @@ -40,16 +43,29 @@ rowHeight="2:3" gutterSize="16"> - <mat-grid-tile *ngFor="let parc of availableParcellations$ | async | filterUnsupportedParc | filterGroupedParcs"> + <!-- using single parc template, since it's reused in non individual parcellation and tmpl for grp parcellation --> + <ng-template #singleParcTmpl let-parc> <sxplr-sapiviews-core-parcellation-tile + [ngClass]="{ + 'sxplr-extra-muted': !(parc | parcellationSupportedInCurrentSpace | async) + }" + [sxplr-sapiviews-core-parcellation-tile-selected]="(selectedParcellation$ | async)?.['@id'] === parc['@id']" [sxplr-sapiviews-core-parcellation-tile-parcellation]="parc" (sxplr-sapiviews-core-parcellation-tile-onclick-parc)="selectParcellation($event)"> </sxplr-sapiviews-core-parcellation-tile> + </ng-template> + + <mat-grid-tile *ngFor="let parc of availableParcellations$ | async | filterUnsupportedParc | filterGroupedParcs"> + <ng-template + [ngTemplateOutlet]="singleParcTmpl" + [ngTemplateOutletContext]="{ $implicit: parc }"> + </ng-template> </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-groupmenu-parc-tmpl]="singleParcTmpl" [sxplr-sapiviews-core-parcellation-tile-parcellation]="group" (sxplr-sapiviews-core-parcellation-tile-onclick-parc)="selectParcellation($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 index 3210779ec..5e3afe419 100644 --- a/src/atlasComponents/sapiViews/core/parcellation/chip/parcellation.chip.stories.ts +++ b/src/atlasComponents/sapiViews/core/parcellation/chip/parcellation.chip.stories.ts @@ -1,5 +1,6 @@ import { CommonModule } from "@angular/common" import { HttpClientModule } from "@angular/common/http" +import { provideMockStore } from "@ngrx/store/testing" 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" @@ -19,6 +20,7 @@ export default { AngularMaterialModule, ], providers: [ + provideMockStore(), SAPI, ...provideDarkTheme, ], diff --git a/src/atlasComponents/sapiViews/core/parcellation/smartChip/parcellation.smartChip.stories.ts b/src/atlasComponents/sapiViews/core/parcellation/smartChip/parcellation.smartChip.stories.ts index 7f3338a6a..beab85bcc 100644 --- a/src/atlasComponents/sapiViews/core/parcellation/smartChip/parcellation.smartChip.stories.ts +++ b/src/atlasComponents/sapiViews/core/parcellation/smartChip/parcellation.smartChip.stories.ts @@ -7,6 +7,7 @@ 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" +import { provideMockStore } from "@ngrx/store/testing" @Component({ selector: `parc-smart-chip-wrapper`, @@ -54,6 +55,7 @@ export default { AngularMaterialModule, ], providers: [ + provideMockStore(), SAPI, ...provideDarkTheme, ], diff --git a/src/atlasComponents/sapiViews/core/parcellation/tile/parcellation.tile.component.ts b/src/atlasComponents/sapiViews/core/parcellation/tile/parcellation.tile.component.ts index e6052cd0c..839e8791f 100644 --- a/src/atlasComponents/sapiViews/core/parcellation/tile/parcellation.tile.component.ts +++ b/src/atlasComponents/sapiViews/core/parcellation/tile/parcellation.tile.component.ts @@ -1,4 +1,4 @@ -import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from "@angular/core"; +import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges, TemplateRef } from "@angular/core"; import { SapiParcellationModel } from "src/atlasComponents/sapi"; import { GroupedParcellation } from "../groupedParcellation"; @@ -16,8 +16,18 @@ const lightthemeId = [ }) export class SapiViewsCoreParcellationParcellationTile implements OnChanges{ + @Input('sxplr-sapiviews-core-parcellation-tile-groupmenu-parc-tmpl') + singleParcTmpl: TemplateRef<any> + + private _parcellation: SapiParcellationModel | GroupedParcellation @Input('sxplr-sapiviews-core-parcellation-tile-parcellation') - parcellation: SapiParcellationModel | GroupedParcellation + set parcellation(val: SapiParcellationModel | GroupedParcellation) { + this._parcellation = val + this.ngOnChanges() + } + get parcellation(){ + return this._parcellation + } @Input('sxplr-sapiviews-core-parcellation-tile-selected') selected: boolean = false diff --git a/src/atlasComponents/sapiViews/core/parcellation/tile/parcellation.tile.stories.ts b/src/atlasComponents/sapiViews/core/parcellation/tile/parcellation.tile.stories.ts index fc7b39f50..1fad5b1cd 100644 --- a/src/atlasComponents/sapiViews/core/parcellation/tile/parcellation.tile.stories.ts +++ b/src/atlasComponents/sapiViews/core/parcellation/tile/parcellation.tile.stories.ts @@ -1,6 +1,7 @@ import { CommonModule } from "@angular/common" import { HttpClientModule } from "@angular/common/http" import { Component, Input, Output, EventEmitter } from "@angular/core" +import { provideMockStore } from "@ngrx/store/testing" import { Meta, moduleMetadata, Story } from "@storybook/angular" import { SAPI, SapiParcellationModel } from "src/atlasComponents/sapi" import { atlasId, parcId, getAtlas, provideDarkTheme, getParc } from "src/atlasComponents/sapi/stories.base" @@ -10,6 +11,10 @@ import { SapiViewsCoreParcellationModule } from "../module" @Component({ selector: `parc-tile-wrapper`, template: ` + <ng-template #grpParcTmpl let-parc> + {{ parc.name }} + </ng-template> + <mat-accordion> <mat-expansion-panel *ngFor="let item of parcs | keyvalue"> @@ -47,6 +52,24 @@ import { SapiViewsCoreParcellationModule } from "../module" </ng-container> </ng-template> </mat-expansion-panel> + + <mat-expansion-panel> + <mat-expansion-panel-header> + > grouped tmpl + </mat-expansion-panel-header> + + <ng-template matExpansionPanelContent> + <ng-container *ngFor="let item of parcs | keyvalue"> + <sxplr-sapiviews-core-parcellation-tile + *ngFor="let parc of (item.value | filterGroupedParcs : true)" + [sxplr-sapiviews-core-parcellation-tile-groupmenu-parc-tmpl]="grpParcTmpl" + [sxplr-sapiviews-core-parcellation-tile-parcellation]="parc" + class="sxplr-m-2" + (sxplr-sapiviews-core-parcellation-tile-onclick-parc)="parcClicked.emit($event)"> + </sxplr-sapiviews-core-parcellation-tile> + </ng-container> + </ng-template> + </mat-expansion-panel> </mat-accordion> `, styles: [ @@ -76,6 +99,7 @@ export default { AngularMaterialModule, ], providers: [ + provideMockStore(), SAPI, ...provideDarkTheme, ], 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 324120aa0..19cd42479 100644 --- a/src/atlasComponents/sapiViews/core/parcellation/tile/parcellation.tile.template.html +++ b/src/atlasComponents/sapiViews/core/parcellation/tile/parcellation.tile.template.html @@ -1,7 +1,8 @@ <mat-menu hasBackDrop="false" #matMenu="matMenu"> <ng-template matMenuContent let-subParcellations="subParcellations"> - <div class="sxplr-ml-2 sxplr-mr-2"> + + <div class="sxplr-custom-cmp sxplr-ml-2 sxplr-mr-2"> <mat-grid-list cols="1" [rowHeight]="rowHeight" @@ -10,15 +11,28 @@ <mat-grid-tile *ngFor="let parc of subParcellations"> - <tile-cmp *ngIf="parc" - class="sxplr-custom-cmp text" - [tile-text]="parc.name" - [tile-image-src]="parc | previewParcellationUrl" - [tile-selected]="selected" - [tile-image-darktheme]="darktheme" - (click)="clickOnParcellation(parc)"> - - </tile-cmp> + + <!-- if parent component injected template, use injected template --> + <ng-template + [ngIf]="singleParcTmpl" + [ngIfElse]="fallbackGrpParcTmpl" + [ngTemplateOutlet]="singleParcTmpl" + [ngTemplateOutletContext]="{ + $implicit: parc + }"> + </ng-template> + + <ng-template #fallbackGrpParcTmpl> + <tile-cmp *ngIf="parc" + class="sxplr-custom-cmp text" + [tile-text]="parc.name" + [tile-image-src]="parc | previewParcellationUrl" + [tile-selected]="selected" + [tile-image-darktheme]="darktheme" + (click)="clickOnParcellation(parc)"> + + </tile-cmp> + </ng-template> </mat-grid-tile> </mat-grid-list> diff --git a/src/atlasComponents/sapiViews/core/parcellation/tile/singleTile.stories.ts b/src/atlasComponents/sapiViews/core/parcellation/tile/singleTile.stories.ts index 410456e99..920c1cfc8 100644 --- a/src/atlasComponents/sapiViews/core/parcellation/tile/singleTile.stories.ts +++ b/src/atlasComponents/sapiViews/core/parcellation/tile/singleTile.stories.ts @@ -1,9 +1,9 @@ import { CommonModule } from "@angular/common" import { HttpClientModule } from "@angular/common/http" -import { Component, Input, Output, EventEmitter } from "@angular/core" +import { provideMockStore } from "@ngrx/store/testing" import { Meta, moduleMetadata, Story } from "@storybook/angular" import { SAPI, SapiParcellationModel } from "src/atlasComponents/sapi" -import { atlasId, parcId, getAtlas, provideDarkTheme, getParc, getHumanAtlas } from "src/atlasComponents/sapi/stories.base" +import { parcId, provideDarkTheme, getParc, getHumanAtlas } from "src/atlasComponents/sapi/stories.base" import { AngularMaterialModule } from "src/sharedModules" import { FilterGroupedParcellationPipe } from "../filterGroupedParcellations.pipe" import { GroupedParcellation } from "../groupedParcellation" @@ -21,13 +21,14 @@ export default { AngularMaterialModule, ], providers: [ + provideMockStore(), SAPI, ...provideDarkTheme, ], declarations: [ - ] - }) + ], + }), ], } as Meta @@ -45,7 +46,10 @@ const Template: Story<SapiViewsCoreParcellationParcellationTile> = (args: SapiVi gutterSize, rowHeight, parcellation: groups[1] - } + }, + styles: [ + `sxplr-sapiviews-core-parcellation-tile { display: inline-block; max-width: 8rem; }` + ] }) } Template.loaders = [ diff --git a/src/atlasComponents/sapiViews/util/module.ts b/src/atlasComponents/sapiViews/util/module.ts index 4d99281d6..4a9eddaf8 100644 --- a/src/atlasComponents/sapiViews/util/module.ts +++ b/src/atlasComponents/sapiViews/util/module.ts @@ -2,7 +2,10 @@ import { NgModule } from "@angular/core"; import { AddUnitAndJoin } from "./addUnitAndJoin.pipe"; import { IncludesPipe } from "./includes.pipe"; import { NumbersPipe } from "./numbers.pipe"; +import { ParcellationSupportedInCurrentSpace } from "./parcellationSupportedInCurrentSpace.pipe"; +import { ParcellationSupportedInSpacePipe } from "./parcellationSupportedInSpace.pipe"; import { ParseDoiPipe } from "./parseDoi.pipe"; +import { SpaceSupportedInCurrentParcellationPipe } from "./spaceSupportedInCurrentParcellation.pipe"; @NgModule({ declarations: [ @@ -10,12 +13,18 @@ import { ParseDoiPipe } from "./parseDoi.pipe"; NumbersPipe, AddUnitAndJoin, IncludesPipe, + ParcellationSupportedInSpacePipe, + ParcellationSupportedInCurrentSpace, + SpaceSupportedInCurrentParcellationPipe, ], exports: [ ParseDoiPipe, NumbersPipe, AddUnitAndJoin, IncludesPipe, + ParcellationSupportedInSpacePipe, + ParcellationSupportedInCurrentSpace, + SpaceSupportedInCurrentParcellationPipe, ] }) diff --git a/src/atlasComponents/sapiViews/util/parcellationSupportedInCurrentSpace.pipe.ts b/src/atlasComponents/sapiViews/util/parcellationSupportedInCurrentSpace.pipe.ts new file mode 100644 index 000000000..3fd1069f0 --- /dev/null +++ b/src/atlasComponents/sapiViews/util/parcellationSupportedInCurrentSpace.pipe.ts @@ -0,0 +1,37 @@ +import { Pipe, PipeTransform } from "@angular/core"; +import { select, Store } from "@ngrx/store"; +import { Observable } from "rxjs"; +import { switchMap } from "rxjs/operators"; +import { SAPI } from "src/atlasComponents/sapi/sapi.service"; +import { SapiParcellationModel } from "src/atlasComponents/sapi/type"; +import { atlasSelection } from "src/state"; +import { ParcellationSupportedInSpacePipe } from "./parcellationSupportedInSpace.pipe" + +@Pipe({ + name: 'parcellationSupportedInCurrentSpace', + /** + * the pipe is not exactly pure, since it makes http call + * but for the sake of angular change detection, this is suitable + * since the result should only change on input change + */ + pure: true +}) + +export class ParcellationSupportedInCurrentSpace implements PipeTransform{ + + private transformPipe = new ParcellationSupportedInSpacePipe(this.sapi) + + private selectedTemplate$ = this.store.pipe( + select(atlasSelection.selectors.selectedTemplate) + ) + constructor( + private store: Store, + private sapi: SAPI, + ){} + + public transform(parcellation: SapiParcellationModel): Observable<boolean> { + return this.selectedTemplate$.pipe( + switchMap(tmpl => this.transformPipe.transform(parcellation, tmpl)) + ) + } +} diff --git a/src/atlasComponents/sapiViews/util/parcellationSupportedInSpace.pipe.ts b/src/atlasComponents/sapiViews/util/parcellationSupportedInSpace.pipe.ts new file mode 100644 index 000000000..aaafaf90f --- /dev/null +++ b/src/atlasComponents/sapiViews/util/parcellationSupportedInSpace.pipe.ts @@ -0,0 +1,42 @@ +import { Pipe, PipeTransform } from "@angular/core"; +import { Observable } from "rxjs"; +import { map } from "rxjs/operators"; +import { SAPIParcellation } from "src/atlasComponents/sapi/core"; +import { SAPI } from "src/atlasComponents/sapi/sapi.service"; +import { SapiParcellationModel, SapiSpaceModel } from "src/atlasComponents/sapi/type"; + +export const knownExceptions = { + supported: { + /** + * jba29 + */ + 'minds/core/parcellationatlas/v1.0.0/94c1125b-b87e-45e4-901c-00daee7f2579-290': [ + /** + * big brain + */ + 'minds/core/referencespace/v1.0.0/a1655b99-82f1-420f-a3c2-fe80fd4c8588' + ] + } +} + +@Pipe({ + name: 'parcellationSuppportedInSpace', + pure: false, +}) + +export class ParcellationSupportedInSpacePipe implements PipeTransform{ + + constructor(private sapi: SAPI){} + + public transform(parc: SapiParcellationModel|string, tmpl: SapiSpaceModel|string): Observable<boolean> { + const parcId = typeof parc === "string" + ? parc + : parc["@id"] + const tmplId = typeof tmpl === "string" + ? tmpl + : tmpl["@id"] + return this.sapi.registry.get<SAPIParcellation>(parcId).getVolumes().pipe( + map(volumes => volumes.some(v => v.data.space["@id"] === tmplId)) + ) + } +} diff --git a/src/atlasComponents/sapiViews/util/spaceSupportedInCurrentParcellation.pipe.ts b/src/atlasComponents/sapiViews/util/spaceSupportedInCurrentParcellation.pipe.ts new file mode 100644 index 000000000..e462fcc6b --- /dev/null +++ b/src/atlasComponents/sapiViews/util/spaceSupportedInCurrentParcellation.pipe.ts @@ -0,0 +1,38 @@ +import { Pipe, PipeTransform } from "@angular/core"; +import { select, Store } from "@ngrx/store"; +import { Observable } from "rxjs"; +import { switchMap } from "rxjs/operators"; +import { SAPI } from "src/atlasComponents/sapi/sapi.service"; +import { SapiSpaceModel } from "src/atlasComponents/sapi/type"; +import { atlasSelection } from "src/state"; +import { ParcellationSupportedInSpacePipe } from "./parcellationSupportedInSpace.pipe" + +@Pipe({ + name: "spaceSupportedInCurrentParcellation", + /** + * the pipe is not exactly pure, since it makes http call + * but for the sake of angular change detection, this is suitable + * since the result should only change on input change + */ + pure: true +}) + +export class SpaceSupportedInCurrentParcellationPipe implements PipeTransform{ + private supportedPipe = new ParcellationSupportedInSpacePipe(this.sapi) + private selectedParcellation$ = this.store.pipe( + select(atlasSelection.selectors.selectedParcellation) + ) + constructor( + private store: Store, + private sapi: SAPI + ){ + + } + public transform(space: SapiSpaceModel): Observable<boolean> { + return this.selectedParcellation$.pipe( + switchMap(parc => + this.supportedPipe.transform(parc, space) + ) + ) + } +} diff --git a/src/components/tile/tile.style.css b/src/components/tile/tile.style.css index 0fa6bb8a8..40d183034 100644 --- a/src/components/tile/tile.style.css +++ b/src/components/tile/tile.style.css @@ -9,6 +9,7 @@ .tile-selected { border: 2px solid #FED363; + box-sizing: border-box; } :host, diff --git a/src/overwrite.scss b/src/overwrite.scss index 2a2e2468e..a6b3e9f55 100644 --- a/src/overwrite.scss +++ b/src/overwrite.scss @@ -191,6 +191,14 @@ $justify-content-vars: end, center, space-between; opacity: 0.75; } +.#{$nsp}-very-muted { + opacity: 0.5; +} + +.#{$nsp}-extra-muted { + opacity: 0.25; +} + $position-vars: relative, absolute; @each $position-var in $position-vars { .#{$nsp}-position-#{$position-var} { diff --git a/src/state/atlasSelection/actions.ts b/src/state/atlasSelection/actions.ts index 8e8ecf2e8..d51f08383 100644 --- a/src/state/atlasSelection/actions.ts +++ b/src/state/atlasSelection/actions.ts @@ -1,6 +1,6 @@ import { createAction, props } from "@ngrx/store"; import { SapiAtlasModel, SapiParcellationModel, SapiRegionModel, SapiSpaceModel } from "src/atlasComponents/sapi"; -import { BreadCrumb, nameSpace, ViewerMode } from "./const" +import { BreadCrumb, nameSpace, ViewerMode, AtlasSelectionState } from "./const" export const selectAtlas = createAction( `${nameSpace} selectAtlas`, @@ -23,6 +23,28 @@ export const selectParcellation = createAction( }>() ) +/** + * setATP is called as a final step to (potentially) set: + * - selectedAtlas + * - selectedTemplate + * - selectedParcellation + * + * It is up to the dispatcher to ensure that the selection makes sense. + * If any field is unset, it will take the default value from the store. + * It is **specifically** not setup to do **anymore** than atlas, template and parcellation + * + * We may setup post hook for navigation adjustments/etc. + * Probably easier is simply subscribe to store and react to selectedTemplate selector + */ +export const setATP = createAction( + `${nameSpace} setATP`, + props<{ + atlas?: SapiAtlasModel, + template?: SapiSpaceModel, + parcellation?: SapiParcellationModel, + }>() +) + export const setSelectedParcellationAllRegions = createAction( `${nameSpace} setSelectedParcellationAllRegions`, props<{ diff --git a/src/state/atlasSelection/const.ts b/src/state/atlasSelection/const.ts index 5ae1942f7..1499e2472 100644 --- a/src/state/atlasSelection/const.ts +++ b/src/state/atlasSelection/const.ts @@ -1,6 +1,33 @@ +import { SapiAtlasModel, SapiParcellationModel, SapiRegionModel, SapiSpaceModel } from "src/atlasComponents/sapi" + export const nameSpace = `[state.atlasSelection]` export type ViewerMode = 'annotating' | 'key frame' export type BreadCrumb = { id: string name: string } + +export type AtlasSelectionState = { + selectedAtlas: SapiAtlasModel + selectedTemplate: SapiSpaceModel + selectedParcellation: SapiParcellationModel + selectedParcellationAllRegions: SapiRegionModel[] + + selectedRegions: SapiRegionModel[] + standAloneVolumes: string[] + + /** + * the navigation may mean something very different + * depending on if the user is using threesurfer/nehuba view + */ + navigation: { + position: number[] + orientation: number[] + zoom: number + perspectiveOrientation: number[] + perspectiveZoom: number + } + + viewerMode: ViewerMode + breadcrumbs: BreadCrumb[] +} diff --git a/src/state/atlasSelection/effects.ts b/src/state/atlasSelection/effects.ts index 5f64cef83..686c1f25e 100644 --- a/src/state/atlasSelection/effects.ts +++ b/src/state/atlasSelection/effects.ts @@ -1,8 +1,8 @@ import { Injectable } from "@angular/core"; import { Actions, createEffect, ofType } from "@ngrx/effects"; -import { forkJoin, merge, of } from "rxjs"; -import { filter, map, mapTo, switchMap, switchMapTo, withLatestFrom } from "rxjs/operators"; -import { SAPI, SAPIRegion, SapiRegionModel } from "src/atlasComponents/sapi"; +import { concat, forkJoin, merge, of } from "rxjs"; +import { filter, map, mapTo, switchMap, switchMapTo, take, tap, withLatestFrom } from "rxjs/operators"; +import { SAPI, SapiParcellationModel, SAPIRegion, SapiRegionModel, SapiSpaceModel } from "src/atlasComponents/sapi"; import * as mainActions from "../actions" import { select, Store } from "@ngrx/store"; import { selectors, actions } from '.' @@ -10,10 +10,103 @@ import { fromRootStore } from "./util"; import { ParcellationIsBaseLayer } from "src/atlasComponents/sapiViews/core/parcellation/parcellationIsBaseLayer.pipe"; import { OrderParcellationByVersionPipe } from "src/atlasComponents/sapiViews/core/parcellation/parcellationVersion.pipe"; import { atlasAppearance, atlasSelection } from ".."; +import { ParcellationSupportedInSpacePipe } from "src/atlasComponents/sapiViews/util/parcellationSupportedInSpace.pipe"; @Injectable() export class Effect { + parcSupportedInSpacePipe = new ParcellationSupportedInSpacePipe(this.sapiSvc) + + onTemplateParcSelection = createEffect(() => merge<{ template: SapiSpaceModel, parcellation: SapiParcellationModel }>( + this.action.pipe( + ofType(actions.selectTemplate), + map(({ template }) => { + return { + template, + parcellation: null + } + }) + ), + this.action.pipe( + ofType(actions.selectParcellation), + map(({ parcellation }) => { + return { + template: null, + parcellation + } + }) + ) + ).pipe( + withLatestFrom(this.store), + switchMap(([ { template, parcellation }, store ]) => { + const currTmpl = selectors.selectedTemplate(store) + const currParc = selectors.selectedParcellation(store) + const currAtlas = selectors.selectedAtlas(store) + return this.parcSupportedInSpacePipe.transform( + parcellation || currParc, + template || currTmpl + ).pipe( + switchMap(flag => { + /** + * if desired parc is supported in tmpl, emit them + */ + if (flag) { + return of({ + template: template || currTmpl, + parcellation: parcellation || currParc, + }) + } + /** + * if template is defined, find the first parcellation that is supported + */ + if (!!template) { + return concat( + ...currAtlas.parcellations.map( + p => this.parcSupportedInSpacePipe.transform(p["@id"], template).pipe( + filter(flag => flag), + switchMap(() => this.sapiSvc.getParcDetail(currAtlas["@id"], p['@id'])), + ) + ) + ).pipe( + map(parcellation => { + return { + template, + parcellation + } + }) + ) + } + if (!!parcellation) { + return concat( + ...currAtlas.spaces.map( + sp => this.parcSupportedInSpacePipe.transform(parcellation["@id"], sp["@id"]).pipe( + filter(flag => flag), + switchMap(() => this.sapiSvc.getSpaceDetail(currAtlas["@id"], sp['@id'])), + ) + ) + ).pipe( + take(1), + map(template => { + return { + template, + parcellation + } + }) + ) + } + throw new Error(`neither template nor parcellation has been defined!`) + }), + map(({ parcellation, template }) => + actions.setATP({ + parcellation, + template + }) + ) + ) + }), + + )) + onAtlasSelectionSelectTmplParc = createEffect(() => this.action.pipe( ofType(actions.selectAtlas), filter(action => !!action.atlas), @@ -37,16 +130,13 @@ export class Effect { this.sapiSvc.getSpaceDetail(atlas["@id"], spaceId["@id"]) ) ).pipe( - switchMap(spaces => { + map(spaces => { const selectedSpace = spaces.find(s => /152/.test(s.fullName)) || spaces[0] - return of( - actions.selectTemplate({ - template: selectedSpace - }), - actions.selectParcellation({ - parcellation - }) - ) + return actions.setATP({ + atlas, + template: selectedSpace, + parcellation + }) }) ) }), diff --git a/src/state/atlasSelection/index.ts b/src/state/atlasSelection/index.ts index d058f9d50..5ef795456 100644 --- a/src/state/atlasSelection/index.ts +++ b/src/state/atlasSelection/index.ts @@ -1,6 +1,6 @@ export * as selectors from "./selectors" export { fromRootStore } from "./util" -export { nameSpace } from "./const" -export { reducer, AtlasSelectionState, defaultState } from "./store" +export { nameSpace, AtlasSelectionState } from "./const" +export { reducer, defaultState } from "./store" export * as actions from "./actions" export { Effect } from "./effects" \ No newline at end of file diff --git a/src/state/atlasSelection/selectors.ts b/src/state/atlasSelection/selectors.ts index c0a53adc5..93f075350 100644 --- a/src/state/atlasSelection/selectors.ts +++ b/src/state/atlasSelection/selectors.ts @@ -1,6 +1,5 @@ import { createSelector } from "@ngrx/store" -import { nameSpace } from "./const" -import { AtlasSelectionState } from "./store" +import { nameSpace, AtlasSelectionState } from "./const" export const viewerStateHelperStoreName = 'viewerStateHelper' diff --git a/src/state/atlasSelection/store.ts b/src/state/atlasSelection/store.ts index 11ac3f534..45bc4f669 100644 --- a/src/state/atlasSelection/store.ts +++ b/src/state/atlasSelection/store.ts @@ -1,7 +1,7 @@ import { createReducer, on } from "@ngrx/store"; import { SapiAtlasModel, SapiParcellationModel, SapiRegionModel, SapiSpaceModel } from "src/atlasComponents/sapi"; import * as actions from "./actions" -import { ViewerMode, BreadCrumb } from "./const" +import { AtlasSelectionState } from "./const" function getRegionLabelIndex(atlas: SapiAtlasModel, tmpl: SapiSpaceModel, parc: SapiParcellationModel, region: SapiRegionModel) { const lblIdx = Number(region?.hasAnnotation?.internalIdentifier) @@ -9,31 +9,6 @@ function getRegionLabelIndex(atlas: SapiAtlasModel, tmpl: SapiSpaceModel, parc: return lblIdx } -export type AtlasSelectionState = { - selectedAtlas: SapiAtlasModel - selectedTemplate: SapiSpaceModel - selectedParcellation: SapiParcellationModel - selectedParcellationAllRegions: SapiRegionModel[] - - selectedRegions: SapiRegionModel[] - standAloneVolumes: string[] - - /** - * the navigation may mean something very different - * depending on if the user is using threesurfer/nehuba view - */ - navigation: { - position: number[] - orientation: number[] - zoom: number - perspectiveOrientation: number[] - perspectiveZoom: number - } - - viewerMode: ViewerMode - breadcrumbs: BreadCrumb[] -} - export const defaultState: AtlasSelectionState = { selectedAtlas: null, selectedParcellation: null, @@ -49,29 +24,13 @@ export const defaultState: AtlasSelectionState = { const reducer = createReducer( defaultState, on( - actions.selectAtlas, - (state, { atlas }) => { - return { - ...state, - selectedAtlas: atlas - } - } - ), - on( - actions.selectTemplate, - (state, { template }) => { - return { - ...state, - selectedTemplate: template - } - } - ), - on( - actions.selectParcellation, - (state, { parcellation }) => { + actions.setATP, + (state, { atlas, parcellation, template }) => { return { ...state, - selectedParcellation: parcellation + selectedAtlas: atlas || state.selectedAtlas, + selectedTemplate: template || state.selectedTemplate, + selectedParcellation: parcellation || state.selectedParcellation, } } ), diff --git a/src/util/windowResize/windowResize.directive.ts b/src/util/windowResize/windowResize.directive.ts index ce8de3180..97c6534b5 100644 --- a/src/util/windowResize/windowResize.directive.ts +++ b/src/util/windowResize/windowResize.directive.ts @@ -1,4 +1,4 @@ -import { Directive, EventEmitter, Input, OnChanges, OnInit, Output } from "@angular/core"; +import { Directive, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output } from "@angular/core"; import { Subscription } from "rxjs"; import { ResizeObserverService } from "./windowResize.service"; @@ -7,7 +7,7 @@ import { ResizeObserverService } from "./windowResize.service"; exportAs: 'iavWindowResize' }) -export class ResizeObserverDirective implements OnChanges, OnInit { +export class ResizeObserverDirective implements OnChanges, OnInit, OnDestroy { @Input('iav-window-resize-type') type: 'debounce' | 'throttle' = 'throttle' @@ -34,6 +34,10 @@ export class ResizeObserverDirective implements OnChanges, OnInit { this.configure() } + ngOnDestroy(): void { + while(this.sub.length > 0) this.sub.pop().unsubscribe() + } + configure(){ while(this.sub.length > 0) this.sub.pop().unsubscribe() -- GitLab