diff --git a/src/atlasComponents/sapiViews/core/atlas/module.ts b/src/atlasComponents/sapiViews/core/atlas/module.ts index 691d41303..52055dc93 100644 --- a/src/atlasComponents/sapiViews/core/atlas/module.ts +++ b/src/atlasComponents/sapiViews/core/atlas/module.ts @@ -9,6 +9,11 @@ import { SapiViewsCoreSpaceModule } from "../space"; import { SapiViewsCoreAtlasAtlasDropdownSelector } from "./dropdownAtlasSelector/dropdownAtlasSelector.component"; import { SapiViewsCoreAtlasSplashScreen } from "./splashScreen/splashScreen.component"; import { SapiViewsCoreAtlasAtlasTmplParcSelector } from "./tmplParcSelector/tmplParcSelector.component"; +import {DialogModule} from "src/ui/dialogInfo/module"; +import { + SapiViewCoreAtlasSmartChip +} from "src/atlasComponents/sapiViews/core/atlas/smartChip/atlas.smartChip.components"; +import {UtilModule} from "src/util"; @NgModule({ imports: [ @@ -19,17 +24,21 @@ import { SapiViewsCoreAtlasAtlasTmplParcSelector } from "./tmplParcSelector/tmpl QuickTourModule, SpinnerModule, SapiViewsUtilModule, + DialogModule, + UtilModule ], declarations: [ SapiViewsCoreAtlasAtlasDropdownSelector, SapiViewsCoreAtlasAtlasTmplParcSelector, SapiViewsCoreAtlasSplashScreen, + SapiViewCoreAtlasSmartChip, ], exports: [ SapiViewsCoreAtlasAtlasDropdownSelector, SapiViewsCoreAtlasAtlasTmplParcSelector, SapiViewsCoreAtlasSplashScreen, + SapiViewCoreAtlasSmartChip, ] }) -export class SapiViewsCoreAtlasModule{} \ No newline at end of file +export class SapiViewsCoreAtlasModule{} diff --git a/src/atlasComponents/sapiViews/core/atlas/smartChip/atlas.smartChip.components.ts b/src/atlasComponents/sapiViews/core/atlas/smartChip/atlas.smartChip.components.ts new file mode 100644 index 000000000..55bc9a10d --- /dev/null +++ b/src/atlasComponents/sapiViews/core/atlas/smartChip/atlas.smartChip.components.ts @@ -0,0 +1,36 @@ +import {Component, EventEmitter, Input, Output} from "@angular/core"; +import {SapiAtlasModel} from "src/atlasComponents/sapi"; + +@Component({ + selector: 'sxplr-sapiviews-core-atlas-smartchip', + templateUrl: './atlas.smartChip.template.html', + styleUrls: ['./atlas.smartChip.style.css'] +}) + +export class SapiViewCoreAtlasSmartChip { + + @Input('sxplr-sapiviews-core-atlas-smartchip-atlas') + atlas: SapiAtlasModel + + @Input('sxplr-sapiviews-core-atlas-smartchip-all-atlases') + atlases: SapiAtlasModel[] + + @Input('sxplr-sapiviews-core-atlas-smartchip-custom-color') + customColor: string + + @Output('sxplr-sapiviews-core-atlas-smartchip-select-atlas') + onSelectAtlas = new EventEmitter() + + // constructor() {} + + + selectAtlas(atlas: SapiAtlasModel){ + if (this.trackByFn(atlas) === this.trackByFn(this.atlas)) return + this.onSelectAtlas.emit(atlas) + } + + trackByFn(atlas: SapiAtlasModel){ + return atlas["@id"] + } + +} diff --git a/src/atlasComponents/sapiViews/core/parcellation/chip/parcellation.chip.style.css b/src/atlasComponents/sapiViews/core/atlas/smartChip/atlas.smartChip.stories.ts similarity index 100% rename from src/atlasComponents/sapiViews/core/parcellation/chip/parcellation.chip.style.css rename to src/atlasComponents/sapiViews/core/atlas/smartChip/atlas.smartChip.stories.ts diff --git a/src/atlasComponents/sapiViews/core/atlas/smartChip/atlas.smartChip.style.css b/src/atlasComponents/sapiViews/core/atlas/smartChip/atlas.smartChip.style.css new file mode 100644 index 000000000..514406d32 --- /dev/null +++ b/src/atlasComponents/sapiViews/core/atlas/smartChip/atlas.smartChip.style.css @@ -0,0 +1,34 @@ +.otherversion-wrapper +{ + position: relative; + overflow: hidden; + margin: 0.5rem; +} + +.otherversion-wrapper.loading > .spinner-container +{ + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + + display: flex; + align-items: center; +} + +.otherversion-wrapper.loading > .spinner-container > spinner-cmp +{ + margin: 0.5rem; +} + +.icons-container +{ + /*transform: scale(0.7);*/ + margin-right: -1.5rem; +} + +.icons-container > * +{ + margin: auto 0.2rem; +} diff --git a/src/atlasComponents/sapiViews/core/atlas/smartChip/atlas.smartChip.template.html b/src/atlasComponents/sapiViews/core/atlas/smartChip/atlas.smartChip.template.html new file mode 100644 index 000000000..71f475622 --- /dev/null +++ b/src/atlasComponents/sapiViews/core/atlas/smartChip/atlas.smartChip.template.html @@ -0,0 +1,23 @@ +
+
+ + {{ atlas.name }} + +
+ + + +
+ +
+
+
\ No newline at end of file diff --git a/src/atlasComponents/sapiViews/core/parcellation/chip/parcellation.chip.component.ts b/src/atlasComponents/sapiViews/core/parcellation/chip/parcellation.chip.component.ts deleted file mode 100644 index 13358ff46..000000000 --- a/src/atlasComponents/sapiViews/core/parcellation/chip/parcellation.chip.component.ts +++ /dev/null @@ -1,26 +0,0 @@ -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() - - 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 deleted file mode 100644 index 5e3afe419..000000000 --- a/src/atlasComponents/sapiViews/core/parcellation/chip/parcellation.chip.stories.ts +++ /dev/null @@ -1,120 +0,0 @@ -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" -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: [ - provideMockStore(), - SAPI, - ...provideDarkTheme, - ], - declarations: [] - }) - ], -} as Meta - -const Template: Story = (args: SapiViewsCoreParcellationParcellationChip, { loaded, parameters }) => { - const { - parcellation - } = loaded - const { - contentProjection - } = parameters - - return ({ - props: { - ...args, - parcellation - }, - template: ` - - ${contentProjection || ''} - - ` - }) -} -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 += `
${prefix}
` - } - if (suffix) { - returnVal += `
${suffix}
` - } - 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/sapiViews/core/parcellation/chip/parcellation.chip.template.html b/src/atlasComponents/sapiViews/core/parcellation/chip/parcellation.chip.template.html deleted file mode 100644 index 417575bf6..000000000 --- a/src/atlasComponents/sapiViews/core/parcellation/chip/parcellation.chip.template.html +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - {{ parcellation.name }} - - - - - - \ No newline at end of file diff --git a/src/atlasComponents/sapiViews/core/parcellation/getSpaceById.pipe.ts b/src/atlasComponents/sapiViews/core/parcellation/getSpaceById.pipe.ts new file mode 100644 index 000000000..7112adcea --- /dev/null +++ b/src/atlasComponents/sapiViews/core/parcellation/getSpaceById.pipe.ts @@ -0,0 +1,28 @@ +import {Pipe, PipeTransform} from "@angular/core"; +import {Store} from "@ngrx/store"; +import {atlasSelection} from "src/state"; +import {SAPI, SapiSpaceModel,} from "src/atlasComponents/sapi"; +import {Observable} from "rxjs"; +import {filter, map} from "rxjs/operators"; + +@Pipe({ + name: 'getSpaceById', + pure: false, +}) + +export class GetSpaceByIdPipe implements PipeTransform { + + private allAvailableSpaces$ = this.store$.pipe( + atlasSelection.fromRootStore.allAvailSpaces(this.sapi) + ) + + constructor(private store$: Store, private sapi: SAPI){} + + public transform(spaceId: string) + : Observable { + return this.allAvailableSpaces$.pipe( + filter(s => s && s.length > 0), + map(res => res.filter(t => t.fullName).find(t => t['@id'] === spaceId)) + ) + } +} \ No newline at end of file diff --git a/src/atlasComponents/sapiViews/core/parcellation/module.ts b/src/atlasComponents/sapiViews/core/parcellation/module.ts index 1fe2e70c0..9f4ba762b 100644 --- a/src/atlasComponents/sapiViews/core/parcellation/module.ts +++ b/src/atlasComponents/sapiViews/core/parcellation/module.ts @@ -8,15 +8,18 @@ import { StrictLocalModule } from "src/strictLocal"; import { DialogModule } from "src/ui/dialogInfo/module"; import { UtilModule } from "src/util"; import { SapiViewsUtilModule } from "../../util"; -import { SapiViewsCoreParcellationParcellationChip } from "./chip/parcellation.chip.component"; import { FilterGroupedParcellationPipe } from "./filterGroupedParcellations.pipe"; import { FilterUnsupportedParcPipe } from "./filterUnsupportedParc.pipe"; import { ParcellationDoiPipe } from "./parcellationDoi.pipe"; import { ParcellationIsBaseLayer } from "./parcellationIsBaseLayer.pipe"; import { ParcellationVisibilityService } from "./parcellationVis.service"; import { PreviewParcellationUrlPipe } from "./previewParcellationUrl.pipe"; -import { SapiViewsCoreParcellationParcellationSmartChip } from "./smartChip/parcellation.smartChip.component"; +import { + SapiViewsCoreParcellationParcellationSmartChip, + TemplateNotAvailableDialog +} from "./smartChip/parcellation.smartChip.component"; import { SapiViewsCoreParcellationParcellationTile } from "./tile/parcellation.tile.component"; +import {GetSpaceByIdPipe} from "src/atlasComponents/sapiViews/core/parcellation/getSpaceById.pipe"; @NgModule({ imports: [ @@ -30,17 +33,17 @@ import { SapiViewsCoreParcellationParcellationTile } from "./tile/parcellation.t ], declarations: [ SapiViewsCoreParcellationParcellationTile, - SapiViewsCoreParcellationParcellationChip, SapiViewsCoreParcellationParcellationSmartChip, PreviewParcellationUrlPipe, FilterGroupedParcellationPipe, FilterUnsupportedParcPipe, ParcellationIsBaseLayer, ParcellationDoiPipe, + GetSpaceByIdPipe, + TemplateNotAvailableDialog ], exports: [ SapiViewsCoreParcellationParcellationTile, - SapiViewsCoreParcellationParcellationChip, SapiViewsCoreParcellationParcellationSmartChip, FilterGroupedParcellationPipe, FilterUnsupportedParcPipe, diff --git a/src/atlasComponents/sapiViews/core/parcellation/smartChip/parcellation.smartChip.component.ts b/src/atlasComponents/sapiViews/core/parcellation/smartChip/parcellation.smartChip.component.ts index 27d4a53cc..2f7c8ed60 100644 --- a/src/atlasComponents/sapiViews/core/parcellation/smartChip/parcellation.smartChip.component.ts +++ b/src/atlasComponents/sapiViews/core/parcellation/smartChip/parcellation.smartChip.component.ts @@ -1,10 +1,11 @@ -import { Component, EventEmitter, Input, OnChanges, Output, SimpleChange, SimpleChanges } from "@angular/core"; +import {Component, EventEmitter, Inject, Input, OnChanges, Output, SimpleChanges} from "@angular/core"; import { BehaviorSubject, concat, Observable, of, timer } from "rxjs"; -import { SapiParcellationModel } from "src/atlasComponents/sapi/type"; +import {SapiParcellationModel, SapiSpaceModel} from "src/atlasComponents/sapi/type"; import { ParcellationVisibilityService } from "../parcellationVis.service"; import { ARIA_LABELS } from "common/constants" import { getTraverseFunctions } from "../parcellationVersion.pipe"; -import { mapTo, shareReplay, switchMap } from "rxjs/operators"; +import {filter, mapTo, shareReplay, switchMap, take} from "rxjs/operators"; +import {MAT_DIALOG_DATA, MatDialog, MatDialogRef} from "@angular/material/dialog"; @Component({ selector: `sxplr-sapiviews-core-parcellation-smartchip`, @@ -18,24 +19,39 @@ export class SapiViewsCoreParcellationParcellationSmartChip implements OnChanges public ARIA_LABELS = ARIA_LABELS + @Input('sxplr-sapiviews-core-parcellation-smartchip-selected-space') + selectedSpace: SapiSpaceModel + + @Input('sxplr-sapiviews-core-parcellation-smartchip-selected-all-spaces') + allAvailableSpaces: SapiSpaceModel[] + @Input('sxplr-sapiviews-core-parcellation-smartchip-parcellation') parcellation: SapiParcellationModel @Input('sxplr-sapiviews-core-parcellation-smartchip-all-parcellations') parcellations: SapiParcellationModel[] + @Input('sxplr-sapiviews-core-parcellation-smartchip-custom-color') + customColor: string + @Output('sxplr-sapiviews-core-parcellation-smartchip-dismiss-nonbase-layer') onDismiss = new EventEmitter() @Output('sxplr-sapiviews-core-parcellation-smartchip-select-parcellation') onSelectParcellation = new EventEmitter() + @Output('sxplr-sapiviews-core-parcellation-smartchip-select-space-parcellation') + onSelectSpaceParcellation = new EventEmitter<{space: SapiSpaceModel, parcellation: SapiParcellationModel}>() + constructor( - private svc: ParcellationVisibilityService + private svc: ParcellationVisibilityService, + public dialog: MatDialog ){ } + private templateNotAvailableDialog: MatDialogRef + otherVersions: SapiParcellationModel[] ngOnChanges(changes: SimpleChanges) { @@ -49,10 +65,10 @@ export class SapiViewsCoreParcellationParcellationSmartChip implements OnChanges } this.otherVersions = [ this.parcellation ] if (!this.parcellations || this.parcellations.length === 0) { - return + return } if (!this.parcellation.version) { - return + return } this.otherVersions = [] @@ -100,5 +116,55 @@ export class SapiViewsCoreParcellationParcellationSmartChip implements OnChanges return parc["@id"] } + selectSpaceAndParcellation(space, parcellation) { + if (this.trackByFn(parcellation) === this.trackByFn(this.parcellation) + && space['@id'] === this.selectedSpace['@id']) return + + this.onSelectSpaceParcellation.emit({space, parcellation}) + } + + openChangeTemplateModal(supportedSpaces, parc) { + const spaces = this.allAvailableSpaces.filter(s => supportedSpaces.includes(s['@id'])) + this.templateNotAvailableDialog = this.dialog.open(TemplateNotAvailableDialog, { + data: spaces + }) + + this.templateNotAvailableDialog.afterClosed().pipe( + take(1), + filter(r => !!r) + ).subscribe(res => { + this.selectSpaceAndParcellation(res, parc) + }) + } + onDismissClicked$ = new BehaviorSubject(false) } + + +@Component({ + selector: 'template-not-available-dialog', + template: ` +
+ +
+

Parcellation is not available for the current template space. Please select template space to explore + the parcellation:

+
+ +
+
+ +
+ `, +}) +export class TemplateNotAvailableDialog { + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public availableSpaces: SapiSpaceModel[], + ) {} + + selectTemplate(space) { + this.dialogRef.close(space) + } +} \ No newline at end of file diff --git a/src/atlasComponents/sapiViews/core/parcellation/smartChip/parcellation.smartChip.template.html b/src/atlasComponents/sapiViews/core/parcellation/smartChip/parcellation.smartChip.template.html index 4dac11789..fdf7b4d94 100644 --- a/src/atlasComponents/sapiViews/core/parcellation/smartChip/parcellation.smartChip.template.html +++ b/src/atlasComponents/sapiViews/core/parcellation/smartChip/parcellation.smartChip.template.html @@ -1,136 +1,149 @@ - -
- -
- - - - -
- - - - - - - +
+ + +
+ +
+ +
+ + + +
- -
- - -
+
+ + + +
+ + + + +
+
+
+ +
+ + {{ parcellation.name }} + +
-
- - - - - -
- - - - +
- + + +

+ {{ parc.name }} +

+
+ + +
- + - + +
- - - - + + + + + + - -
-
+ + + + + + + + {{ ARIA_LABELS.TOGGLE_DELINEATION }} + + - + - -

- {{ parc.name }} -

-
- - -
- - - - - - Dataset Detail - - + + + + - - +
+ + + + + + + + + + \ No newline at end of file diff --git a/src/atlasComponents/sapiViews/core/space/module.ts b/src/atlasComponents/sapiViews/core/space/module.ts index 35b96ce90..5fbcccb2f 100644 --- a/src/atlasComponents/sapiViews/core/space/module.ts +++ b/src/atlasComponents/sapiViews/core/space/module.ts @@ -4,21 +4,36 @@ import { ComponentsModule } from "src/components"; import { SapiViewsCoreSpaceBoundingBox } from "./boundingBox.directive"; import { PreviewSpaceUrlPipe } from "./previewSpaceUrl.pipe"; import { SapiViewsCoreSpaceSpaceTile } from "./tile/space.tile.component"; +import { + SapiViewCoreSpaceSmartChip +} from "src/atlasComponents/sapiViews/core/space/smartChip/space.smartChip.components"; +import { AngularMaterialModule } from "src/sharedModules"; +import { DialogModule } from "src/ui/dialogInfo/module"; +import { SapiViewsUtilModule } from "../../util"; +import {UtilModule} from "src/util"; +import {ReactiveFormsModule} from "@angular/forms"; @NgModule({ imports: [ CommonModule, ComponentsModule, + AngularMaterialModule, + ReactiveFormsModule, + DialogModule, + SapiViewsUtilModule, + UtilModule ], declarations: [ SapiViewsCoreSpaceSpaceTile, PreviewSpaceUrlPipe, SapiViewsCoreSpaceBoundingBox, + SapiViewCoreSpaceSmartChip, ], exports: [ SapiViewsCoreSpaceSpaceTile, SapiViewsCoreSpaceBoundingBox, + SapiViewCoreSpaceSmartChip, ] }) -export class SapiViewsCoreSpaceModule{} \ No newline at end of file +export class SapiViewsCoreSpaceModule{} diff --git a/src/atlasComponents/sapiViews/core/space/smartChip/space.smartChip.components.ts b/src/atlasComponents/sapiViews/core/space/smartChip/space.smartChip.components.ts new file mode 100644 index 000000000..1b74d12ec --- /dev/null +++ b/src/atlasComponents/sapiViews/core/space/smartChip/space.smartChip.components.ts @@ -0,0 +1,108 @@ +import {ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output} from "@angular/core"; +import { SapiSpaceModel } from "src/atlasComponents/sapi/type"; +import {select, Store} from "@ngrx/store"; +import {actionSetAuxMeshes, selectorAuxMeshes} from "src/viewerModule/nehuba/store"; +import {merge, of, Subscription} from "rxjs"; +import {pairwise, withLatestFrom} from "rxjs/operators"; +import {FormBuilder, FormControl, FormGroup} from "@angular/forms"; + +@Component({ + selector: 'sxplr-sapiviews-core-space-smartchip', + templateUrl: './space.smartChip.template.html', + styleUrls: ['./space.smartChip.style.css'] +}) + +export class SapiViewCoreSpaceSmartChip implements OnInit { + + @Input('sxplr-sapiviews-core-space-smartchip-space') + space: SapiSpaceModel + + @Input('sxplr-sapiviews-core-space-smartchip-all-spaces') + spaces: SapiSpaceModel[] + + @Input('sxplr-sapiviews-core-space-smartchip-custom-color') + customColor: string + + @Output('sxplr-sapiviews-core-space-smartchip-select-space') + onSelectSpace = new EventEmitter() + + public spaceSettingEnabled: boolean + private sub: Subscription[] = [] + + public auxMeshFormGroup: FormGroup = this.formBuilder.group({}) + private auxMeshesNamesSet: Set = new Set() + + constructor( + private store: Store, + private formBuilder: FormBuilder, + private cdr: ChangeDetectorRef,) { + } + + ngOnInit() { + this.sub.push( + merge( + of(null), + this.auxMeshes$, + ).pipe( + pairwise() + ).subscribe(([oldMeshes, meshes]) => { + if (!!oldMeshes) { + for (const mesh of oldMeshes) { + this.auxMeshFormGroup.removeControl(mesh['@id']) + } + } + if (meshes === null) { + return + } + this.auxMeshesNamesSet.clear() + for (const mesh of meshes) { + this.auxMeshesNamesSet.add(mesh.ngId) + this.auxMeshFormGroup.addControl(mesh['@id'], new FormControl(mesh.visible)) + } + this.cdr.detectChanges() + }), + + this.auxMeshFormGroup.valueChanges.pipe( + withLatestFrom(this.auxMeshes$) + ).subscribe(([v, auxMeshes]) => { + if (!auxMeshes) return + + let changed = false + const auxMeshesCopy = JSON.parse(JSON.stringify(auxMeshes)) + for (const key in v) { + const found = auxMeshesCopy.find(mesh => mesh['@id'] === key) + if (found && found.visible !== v[key]) { + changed = true + found.visible = v[key] + } + } + + if (changed) { + this.store.dispatch( + actionSetAuxMeshes({ + payload: auxMeshesCopy + }) + ) + } + this.cdr.detectChanges() + }) + ) + } + + selectSpace(space: SapiSpaceModel){ + if (this.trackByFn(space) === this.trackByFn(this.space)) return + this.onSelectSpace.emit(space) + } + + trackByFn(space: SapiSpaceModel){ + return space["@id"] + } + + public auxMeshes$ = this.store.pipe( + select(selectorAuxMeshes), + ) + public trackByAtId(_idx: number, obj: { ['@id']: string }): string { + return obj['@id'] + } + +} diff --git a/src/atlasComponents/sapiViews/core/space/smartChip/space.smartChip.stories.ts b/src/atlasComponents/sapiViews/core/space/smartChip/space.smartChip.stories.ts new file mode 100644 index 000000000..e69de29bb diff --git a/src/atlasComponents/sapiViews/core/space/smartChip/space.smartChip.style.css b/src/atlasComponents/sapiViews/core/space/smartChip/space.smartChip.style.css new file mode 100644 index 000000000..e69de29bb diff --git a/src/atlasComponents/sapiViews/core/space/smartChip/space.smartChip.template.html b/src/atlasComponents/sapiViews/core/space/smartChip/space.smartChip.template.html new file mode 100644 index 000000000..be7689bf6 --- /dev/null +++ b/src/atlasComponents/sapiViews/core/space/smartChip/space.smartChip.template.html @@ -0,0 +1,49 @@ +
+
+ + {{ space.shortName }} + +
+ + + +
+ + +
+ + + + +
+ + + {{ auxMesh.displayName || auxMesh.name }} + + +
+
+
+
+
+
+
+
+ diff --git a/src/atlasComponents/sapiViews/util/parcellationSupportedInCurrentSpace.pipe.ts b/src/atlasComponents/sapiViews/util/parcellationSupportedInCurrentSpace.pipe.ts index 3fd1069f0..f69e2e660 100644 --- a/src/atlasComponents/sapiViews/util/parcellationSupportedInCurrentSpace.pipe.ts +++ b/src/atlasComponents/sapiViews/util/parcellationSupportedInCurrentSpace.pipe.ts @@ -1,7 +1,7 @@ import { Pipe, PipeTransform } from "@angular/core"; import { select, Store } from "@ngrx/store"; import { Observable } from "rxjs"; -import { switchMap } from "rxjs/operators"; +import {switchMap, tap} from "rxjs/operators"; import { SAPI } from "src/atlasComponents/sapi/sapi.service"; import { SapiParcellationModel } from "src/atlasComponents/sapi/type"; import { atlasSelection } from "src/state"; @@ -29,7 +29,8 @@ export class ParcellationSupportedInCurrentSpace implements PipeTransform{ private sapi: SAPI, ){} - public transform(parcellation: SapiParcellationModel): Observable { + public transform(parcellation: SapiParcellationModel) + : Observable<{supported: boolean, spaces?: Array}> { 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 index 42eec193e..cb99c2801 100644 --- a/src/atlasComponents/sapiViews/util/parcellationSupportedInSpace.pipe.ts +++ b/src/atlasComponents/sapiViews/util/parcellationSupportedInSpace.pipe.ts @@ -28,7 +28,8 @@ export class ParcellationSupportedInSpacePipe implements PipeTransform{ constructor(private sapi: SAPI){} - public transform(parc: SapiParcellationModel|string, tmpl: SapiSpaceModel|string): Observable { + public transform(parc: SapiParcellationModel|string, tmpl: SapiSpaceModel|string) + : Observable<{supported: boolean, spaces?: (Array)}> { if (!parc) return NEVER const parcId = typeof parc === "string" ? parc @@ -38,11 +39,17 @@ export class ParcellationSupportedInSpacePipe implements PipeTransform{ : tmpl["@id"] for (const key in knownExceptions.supported) { if (key === parcId && knownExceptions.supported[key].indexOf(tmplId) >= 0) { - return of(true) + return of({supported: true}) } } return this.sapi.registry.get(parcId).getVolumes().pipe( - map(volumes => volumes.some(v => v.data.space["@id"] === tmplId)) + map(volumes => { + const supported = volumes.some(v => v.data.space["@id"] === tmplId) + return { + supported, + spaces: [...new Set(volumes.map(v => v.data.space["@id"]))] + } + }) ) } } diff --git a/src/atlasComponents/sapiViews/util/spaceSupportedInCurrentParcellation.pipe.ts b/src/atlasComponents/sapiViews/util/spaceSupportedInCurrentParcellation.pipe.ts index e462fcc6b..ba5a056e5 100644 --- a/src/atlasComponents/sapiViews/util/spaceSupportedInCurrentParcellation.pipe.ts +++ b/src/atlasComponents/sapiViews/util/spaceSupportedInCurrentParcellation.pipe.ts @@ -28,7 +28,7 @@ export class SpaceSupportedInCurrentParcellationPipe implements PipeTransform{ ){ } - public transform(space: SapiSpaceModel): Observable { + public transform(space: SapiSpaceModel): Observable<{ supported: boolean, spaces?: string[] }> { return this.selectedParcellation$.pipe( switchMap(parc => this.supportedPipe.transform(parc, space) diff --git a/src/extra_styles.css b/src/extra_styles.css index 643ab1e50..63063f6aa 100644 --- a/src/extra_styles.css +++ b/src/extra_styles.css @@ -876,3 +876,27 @@ how-to-cite img { max-width: 100vw; } + +.custom-chip { + padding: 5px 10px; + border-radius: 15px; + cursor: pointer; +} + +.custom-chip:hover { + filter: brightness(1.1); +} + +.layer-chip-overflowed { + margin-left: -2.5rem !important; + padding-left: 3rem !important; +} + +.small-icon-button { + font-size: 12px; + min-width: 20px; + text-align: center; + height: 20px; + padding: 0 5px; + border-radius: 50%; +} \ No newline at end of file diff --git a/src/sharedModules/angularMaterial.module.ts b/src/sharedModules/angularMaterial.module.ts index 6f29b8913..e8aebc6e2 100644 --- a/src/sharedModules/angularMaterial.module.ts +++ b/src/sharedModules/angularMaterial.module.ts @@ -31,6 +31,7 @@ import { ClipboardModule } from '@angular/cdk/clipboard' import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; import {MatProgressBarModule} from "@angular/material/progress-bar"; import {MatProgressSpinnerModule} from "@angular/material/progress-spinner"; +import {OverlayModule} from "@angular/cdk/overlay"; const defaultDialogOption: MatDialogConfig = new MatDialogConfig() @@ -67,6 +68,7 @@ const defaultDialogOption: MatDialogConfig = new MatDialogConfig() ClipboardModule, MatProgressBarModule, MatProgressSpinnerModule, + OverlayModule, ], exports: [ MatButtonModule, @@ -98,6 +100,7 @@ const defaultDialogOption: MatDialogConfig = new MatDialogConfig() ClipboardModule, MatProgressBarModule, MatProgressSpinnerModule, + OverlayModule, ], providers: [{ provide: MAT_DIALOG_DEFAULT_OPTIONS, diff --git a/src/state/atlasSelection/effects.ts b/src/state/atlasSelection/effects.ts index 2c95b7560..d5cd89ecc 100644 --- a/src/state/atlasSelection/effects.ts +++ b/src/state/atlasSelection/effects.ts @@ -140,7 +140,7 @@ export class Effect { return concat( ...currAtlas.parcellations.map( p => this.parcSupportedInSpacePipe.transform(p["@id"], template).pipe( - filter(flag => flag), + filter(flag => flag.supported), switchMap(() => this.sapiSvc.getParcDetail(currAtlas["@id"], p['@id'])), ) ) @@ -159,7 +159,7 @@ export class Effect { return concat( ...currAtlas.spaces.map( sp => this.parcSupportedInSpacePipe.transform(parcellation["@id"], sp["@id"]).pipe( - filter(flag => flag), + filter(flag => flag.supported), switchMap(() => this.sapiSvc.getSpaceDetail(currAtlas["@id"], sp['@id'])), ) ) diff --git a/src/ui/config/configCmp/config.stories.ts b/src/ui/config/configCmp/config.stories.ts deleted file mode 100644 index f1aa0a63f..000000000 --- a/src/ui/config/configCmp/config.stories.ts +++ /dev/null @@ -1,46 +0,0 @@ -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 = (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 deleted file mode 100644 index 9fccc534f..000000000 --- a/src/ui/config/configCmp/config.style.css +++ /dev/null @@ -1,26 +0,0 @@ -.config-transition -{ - transition: background-color ease-in-out 200ms; -} - -.config-transition:hover -{ - background-color: rgba(128,128,128,0.1); -} - -.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 deleted file mode 100644 index eab836a14..000000000 --- a/src/ui/config/configCmp/config.template.html +++ /dev/null @@ -1,228 +0,0 @@ - - - - - -
- - -
- - Enable Mobile UI - - -
- - -
- - Enable Animation - - -
- - -
- - - - - {{ gpuLimit$ | async }} MB - -
-
-
- - - - -
-
- Rearrange Viewports -
-
- Click and drag to rearrange viewport positions -
- -
-
- {{ (panelTexts$ | async)[0] }} -
-
-
-
- {{ (panelTexts$ | async)[1] }} -
-
-
-
- {{ (panelTexts$ | async)[2] }} -
-
-
-
- {{ (panelTexts$ | async)[3] }} -
-
-
- -
- Plane designation refers to default orientation (without oblique rotation). -
-
- - - -
-
- Select a viewports configuration -
-
- -
- BLA? - - - - - - - - -
-
-
-
-
-
- - - - - - - - - - - - - - -
-
-
-
-
-
- - -
-
-
- diff --git a/src/ui/config/hardwareConfig/hardwareConfig.component.ts b/src/ui/config/hardwareConfig/hardwareConfig.component.ts new file mode 100644 index 000000000..4b4b75bf3 --- /dev/null +++ b/src/ui/config/hardwareConfig/hardwareConfig.component.ts @@ -0,0 +1,79 @@ +import {Component, OnDestroy, OnInit} from "@angular/core"; +import {Observable, Subscription} from "rxjs"; +import {select, Store} from "@ngrx/store"; +import {userPreference} from "src/state"; +import {map} from "rxjs/operators"; +import {MatSlideToggleChange} from "@angular/material/slide-toggle"; +import {MatSliderChange} from "@angular/material/slider"; + +const GPU_TOOLTIP = `Higher GPU usage can cause crashes on lower end machines` +const ANIMATION_TOOLTIP = `Animation can cause slowdowns in lower end machines` +const MOBILE_UI_TOOLTIP = `Mobile UI enables touch controls` + +@Component({ + selector: 'hardware-config-component', + templateUrl: './hardwareConfig.template.html', + styleUrls: [ + './hardwareConfig.style.css', + ], +}) + +export class HardwareConfigComponent { + + public GPU_TOOLTIP = GPU_TOOLTIP + public ANIMATION_TOOLTIP = ANIMATION_TOOLTIP + public MOBILE_UI_TOOLTIP = MOBILE_UI_TOOLTIP + + + /** + * in MB + */ + public gpuLimit$: Observable + + public useMobileUI$: Observable = this.store.pipe( + select(userPreference.selectors.useMobileUi) + ) + public animationFlag$: Observable + + public gpuMin: number = 100 + public gpuMax: number = 1000 + public stepSize: number = 10 + + constructor(private store: Store,) { + this.gpuLimit$ = this.store.pipe( + select(userPreference.selectors.gpuLimit), + map(v => v / 1e6), + ) + + this.animationFlag$ = this.store.pipe( + select(userPreference.selectors.useAnimation) + ) + } + + public toggleMobileUI(ev: MatSlideToggleChange) { + const { checked } = ev + this.store.dispatch( + userPreference.actions.useMobileUi({ + flag: checked + }) + ) + } + + public toggleAnimationFlag(ev: MatSlideToggleChange ) { + const { checked } = ev + this.store.dispatch( + userPreference.actions.setAnimationFlag({ + flag: checked + }) + ) + } + + public handleMatSliderChange(ev: MatSliderChange) { + this.store.dispatch( + userPreference.actions.setGpuLimit({ + limit: ev.value * 1e6 + }) + ) + } + +} \ No newline at end of file diff --git a/src/ui/config/hardwareConfig/hardwareConfig.style.css b/src/ui/config/hardwareConfig/hardwareConfig.style.css new file mode 100644 index 000000000..e69de29bb diff --git a/src/ui/config/hardwareConfig/hardwareConfig.template.html b/src/ui/config/hardwareConfig/hardwareConfig.template.html new file mode 100644 index 000000000..7cec8bd02 --- /dev/null +++ b/src/ui/config/hardwareConfig/hardwareConfig.template.html @@ -0,0 +1,46 @@ +
+ + +
+ + Enable Mobile UI + + +
+ + +
+ + Enable Animation + + +
+ + +
+ + + + + {{ gpuLimit$ | async }} MB + +
+
\ No newline at end of file diff --git a/src/ui/config/module.ts b/src/ui/config/module.ts index 55fcded2c..a03a79501 100644 --- a/src/ui/config/module.ts +++ b/src/ui/config/module.ts @@ -3,7 +3,8 @@ import { NgModule } from "@angular/core"; import { LayoutModule } from "src/layouts/layout.module"; import { PluginModule } from "src/plugin"; import { AngularMaterialModule } from "src/sharedModules"; -import { ConfigComponent } from "./configCmp/config.component"; +import {HardwareConfigComponent} from "src/ui/config/hardwareConfig/hardwareConfig.component"; +import {ViewerPreferencesComponent} from "src/ui/config/viewerPreferences/viewerPreferences.component"; @NgModule({ imports: [ @@ -13,10 +14,12 @@ import { ConfigComponent } from "./configCmp/config.component"; LayoutModule, ], declarations: [ - ConfigComponent, + HardwareConfigComponent, + ViewerPreferencesComponent ], exports: [ - ConfigComponent, + HardwareConfigComponent, + ViewerPreferencesComponent ] }) export class ConfigModule{} \ No newline at end of file diff --git a/src/ui/config/configCmp/config.component.ts b/src/ui/config/viewerPreferences/viewerPreferences.component.ts similarity index 68% rename from src/ui/config/configCmp/config.component.ts rename to src/ui/config/viewerPreferences/viewerPreferences.component.ts index 494403b61..41aaa4030 100644 --- a/src/ui/config/configCmp/config.component.ts +++ b/src/ui/config/viewerPreferences/viewerPreferences.component.ts @@ -3,30 +3,22 @@ import { select, Store } from '@ngrx/store'; import { combineLatest, Observable, Subscription } from 'rxjs'; import { debounceTime, distinctUntilChanged, map, startWith } from 'rxjs/operators'; import { isIdentityQuat } from 'src/viewerModule/nehuba/util'; -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` -const MOBILE_UI_TOOLTIP = `Mobile UI enables touch controls` const ROOT_TEXT_ORDER: [string, string, string, string] = ['Coronal', 'Sagittal', 'Axial', '3D'] const OBLIQUE_ROOT_TEXT_ORDER: [string, string, string, string] = ['Slice View 1', 'Slice View 2', 'Slice View 3', '3D'] @Component({ - selector: 'config-component', - templateUrl: './config.template.html', + selector: 'viewer-preferences-component', + templateUrl: './viewerPreferences.template.html', styleUrls: [ - './config.style.css', + './viewerPreferences.style.css', ], }) -export class ConfigComponent implements OnInit, OnDestroy { +export class ViewerPreferencesComponent implements OnInit, OnDestroy { - public GPU_TOOLTIP = GPU_TOOLTIP - public ANIMATION_TOOLTIP = ANIMATION_TOOLTIP - public MOBILE_UI_TOOLTIP = MOBILE_UI_TOOLTIP public experimentalFlag = environment.EXPERIMENTAL_FEATURE_FLAG @@ -38,20 +30,8 @@ export class ConfigComponent implements OnInit, OnDestroy { } - /** - * in MB - */ - public gpuLimit$: Observable - - public useMobileUI$: Observable = this.store.pipe( - select(userPreference.selectors.useMobileUi) - ) - public animationFlag$: Observable private subscriptions: Subscription[] = [] - public gpuMin: number = 100 - public gpuMax: number = 1000 - public panelMode$: Observable private panelOrder: string @@ -64,15 +44,6 @@ export class ConfigComponent implements OnInit, OnDestroy { private store: Store, ) { - this.gpuLimit$ = this.store.pipe( - select(userPreference.selectors.gpuLimit), - map(v => v / 1e6), - ) - - this.animationFlag$ = this.store.pipe( - select(userPreference.selectors.useAnimation) - ) - this.panelMode$ = this.store.pipe( select(userInterface.selectors.panelMode) ) @@ -111,31 +82,6 @@ export class ConfigComponent implements OnInit, OnDestroy { this.subscriptions.forEach(s => s.unsubscribe()) } - public toggleMobileUI(ev: MatSlideToggleChange) { - const { checked } = ev - this.store.dispatch( - userPreference.actions.useMobileUi({ - flag: checked - }) - ) - } - - public toggleAnimationFlag(ev: MatSlideToggleChange ) { - const { checked } = ev - this.store.dispatch( - userPreference.actions.setAnimationFlag({ - flag: checked - }) - ) - } - - public handleMatSliderChange(ev: MatSliderChange) { - this.store.dispatch( - userPreference.actions.setGpuLimit({ - limit: ev.value * 1e6 - }) - ) - } public usePanelMode(panelMode: userInterface.PanelMode) { this.store.dispatch( @@ -180,5 +126,5 @@ export class ConfigComponent implements OnInit, OnDestroy { target.classList.remove('onDragOver') } - public stepSize: number = 10 + // public stepSize: number = 10 } diff --git a/src/ui/config/viewerPreferences/viewerPreferences.style.css b/src/ui/config/viewerPreferences/viewerPreferences.style.css new file mode 100644 index 000000000..eaf8aab27 --- /dev/null +++ b/src/ui/config/viewerPreferences/viewerPreferences.style.css @@ -0,0 +1,26 @@ +.config-transition +{ + transition: background-color ease-in-out 200ms; +} + +.config-transition:hover +{ + background-color: rgba(128,128,128,0.1); +} + +.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/viewerPreferences/viewerPreferences.template.html b/src/ui/config/viewerPreferences/viewerPreferences.template.html new file mode 100644 index 000000000..7850cf1d6 --- /dev/null +++ b/src/ui/config/viewerPreferences/viewerPreferences.template.html @@ -0,0 +1,169 @@ +
+
+ Rearrange Viewports +
+
+ Click and drag to rearrange viewport positions +
+ +
+
+ {{ (panelTexts$ | async)[0] }} +
+
+
+
+ {{ (panelTexts$ | async)[1] }} +
+
+
+
+ {{ (panelTexts$ | async)[2] }} +
+
+
+
+ {{ (panelTexts$ | async)[3] }} +
+
+
+ +
+ Plane designation refers to default orientation (without oblique rotation). +
+
+ + + +
+
+ Select a viewports configuration +
+
+ +
+ BLA? + + + + + + + + +
+
+
+
+
+
+ + + + + + + + + + + + + + +
+
+
+
+
+
+ + +
\ No newline at end of file diff --git a/src/ui/topMenu/topMenuCmp/topMenu.components.ts b/src/ui/topMenu/topMenuCmp/topMenu.components.ts index 0d464d59a..6ed01d3f0 100644 --- a/src/ui/topMenu/topMenuCmp/topMenu.components.ts +++ b/src/ui/topMenu/topMenuCmp/topMenu.components.ts @@ -33,6 +33,8 @@ export class TopMenuCmp { public matBtnStyle = 'mat-icon-button' public matBtnColor = 'primary' + public openedSettingsMenu: undefined | 'hardware' | 'viewer' + private _ismobile = false @Input() set ismobile(val) { diff --git a/src/ui/topMenu/topMenuCmp/topMenu.template.html b/src/ui/topMenu/topMenuCmp/topMenu.template.html index f652afdd6..b0bbe61c4 100644 --- a/src/ui/topMenu/topMenuCmp/topMenu.template.html +++ b/src/ui/topMenu/topMenuCmp/topMenu.template.html @@ -20,6 +20,12 @@
+ +
+ + +
+
@@ -53,6 +59,10 @@ + + + + @@ -88,6 +98,22 @@
+ + +
+ + + + +
+
+
- - + + + + + + + + + + + + + + + + + + + diff --git a/src/viewerModule/nehuba/layoutOverlay/nehuba.layoutOverlay/nehuba.layoutOverlay.template.html b/src/viewerModule/nehuba/layoutOverlay/nehuba.layoutOverlay/nehuba.layoutOverlay.template.html index 245fb9f07..6738987e7 100644 --- a/src/viewerModule/nehuba/layoutOverlay/nehuba.layoutOverlay/nehuba.layoutOverlay.template.html +++ b/src/viewerModule/nehuba/layoutOverlay/nehuba.layoutOverlay/nehuba.layoutOverlay.template.html @@ -81,7 +81,7 @@ diff --git a/src/viewerModule/nehuba/viewerCtrl/viewerCtrlCmp/viewerCtrlCmp.component.ts b/src/viewerModule/nehuba/viewerCtrl/viewerCtrlCmp/viewerCtrlCmp.component.ts index 2a44980c9..5c87d8a22 100644 --- a/src/viewerModule/nehuba/viewerCtrl/viewerCtrlCmp/viewerCtrlCmp.component.ts +++ b/src/viewerModule/nehuba/viewerCtrl/viewerCtrlCmp/viewerCtrlCmp.component.ts @@ -1,12 +1,9 @@ -import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, OnInit, Optional } from "@angular/core"; -import { select, Store } from "@ngrx/store"; -import { merge, Observable, of, Subscription } from "rxjs"; -import { pairwise, withLatestFrom} from "rxjs/operators"; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, Optional } from "@angular/core"; +import { Store } from "@ngrx/store"; +import { Observable } from "rxjs"; 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 { atlasAppearance } from "src/state"; @Component({ @@ -19,12 +16,10 @@ import { atlasAppearance } from "src/state"; changeDetection: ChangeDetectionStrategy.OnPush, }) -export class ViewerCtrlCmp implements OnInit{ +export class ViewerCtrlCmp { public ARIA_LABELS = ARIA_LABELS - private sub: Subscription[] = [] - private _removeOctantFlag: boolean = true get removeOctantFlag(): boolean{ return this._removeOctantFlag @@ -36,76 +31,10 @@ export class ViewerCtrlCmp implements OnInit{ this.cdr.detectChanges() } - public nehubaViewerPerspectiveOctantRemoval$ = this.store$.pipe( - select(atlasAppearance.selectors.octantRemoval), - ) - - public auxMeshFormGroup: FormGroup = this.formBuilder.group({}) - private auxMeshesNamesSet: Set = new Set() - public auxMeshes$ = this.store$.pipe( - select(selectorAuxMeshes), - ) - - ngOnInit(): void { - - this.sub.push( - - this.nehubaViewerPerspectiveOctantRemoval$.subscribe( - flag => this.removeOctantFlag = flag - ), - - merge( - of(null), - this.auxMeshes$, - ).pipe( - pairwise() - ).subscribe(([oldMeshes, meshes]) => { - if (!!oldMeshes) { - for (const mesh of oldMeshes) { - this.auxMeshFormGroup.removeControl(mesh['@id']) - } - } - if (meshes === null) { - return - } - this.auxMeshesNamesSet.clear() - for (const mesh of meshes) { - this.auxMeshesNamesSet.add(mesh.ngId) - this.auxMeshFormGroup.addControl(mesh['@id'], new FormControl(mesh.visible)) - } - this.cdr.detectChanges() - }), - this.auxMeshFormGroup.valueChanges.pipe( - withLatestFrom(this.auxMeshes$) - ).subscribe(([v, auxMeshes]) => { - if (!auxMeshes) return - - let changed = false - const auxMeshesCopy = JSON.parse(JSON.stringify(auxMeshes)) - for (const key in v) { - const found = auxMeshesCopy.find(mesh => mesh['@id'] === key) - if (found && found.visible !== v[key]) { - changed = true - found.visible = v[key] - } - } - - if (changed) { - this.store$.dispatch( - actionSetAuxMeshes({ - payload: auxMeshesCopy - }) - ) - } - this.cdr.detectChanges() - }) - ) - } constructor( private store$: Store, - private formBuilder: FormBuilder, private cdr: ChangeDetectorRef, @Optional() @Inject(NEHUBA_INSTANCE_INJTKN) private nehubaInst$: Observable, ){ @@ -120,7 +49,4 @@ export class ViewerCtrlCmp implements OnInit{ ) } - public trackByAtId(_idx: number, obj: { ['@id']: string }): string { - return obj['@id'] - } } diff --git a/src/viewerModule/nehuba/viewerCtrl/viewerCtrlCmp/viewerCtrlCmp.template.html b/src/viewerModule/nehuba/viewerCtrl/viewerCtrlCmp/viewerCtrlCmp.template.html index 50f326abf..3255915fe 100644 --- a/src/viewerModule/nehuba/viewerCtrl/viewerCtrlCmp/viewerCtrlCmp.template.html +++ b/src/viewerModule/nehuba/viewerCtrl/viewerCtrlCmp/viewerCtrlCmp.template.html @@ -10,19 +10,5 @@

- - -
- - - {{ auxMesh.displayName || auxMesh.name }} - - -
-
- diff --git a/src/viewerModule/viewerCmp/viewerCmp.component.ts b/src/viewerModule/viewerCmp/viewerCmp.component.ts index f0b32efea..894e0611a 100644 --- a/src/viewerModule/viewerCmp/viewerCmp.component.ts +++ b/src/viewerModule/viewerCmp/viewerCmp.component.ts @@ -8,7 +8,7 @@ import { IQuickTourData } from "src/ui/quickTour"; import { EnumViewerEvt, TContextArg, TSupportedViewers, TViewerEvent } from "../viewer.interface"; import { ContextMenuService, TContextMenuReg } from "src/contextMenuModule"; import { DialogService } from "src/services/dialogService.service"; -import { SAPI, SapiRegionModel } from "src/atlasComponents/sapi"; +import {SAPI, SapiAtlasModel, SapiRegionModel} from "src/atlasComponents/sapi"; import { atlasSelection, userInteraction, } from "src/state"; import { SapiSpatialFeatureModel, SapiFeatureModel, SapiParcellationModel, SapiSpaceModel } from "src/atlasComponents/sapi/type"; import { getUuid } from "src/util/fn"; @@ -94,6 +94,8 @@ export class ViewerCmp implements OnDestroy { shareReplay(1) ) + public fetchedAtlases$: Observable = this.sapi.atlases$ + public selectedAtlas$ = this.selectedATP.pipe( map(({ atlas }) => atlas) ) @@ -104,6 +106,10 @@ export class ViewerCmp implements OnDestroy { map(({ parcellation }) => parcellation) ) + public allAvailableSpaces$ = this.store$.pipe( + atlasSelection.fromRootStore.allAvailSpaces(this.sapi) + ) + public allAvailableParcellations$ = this.store$.pipe( atlasSelection.fromRootStore.allAvailParcs(this.sapi) ) @@ -405,6 +411,20 @@ export class ViewerCmp implements OnDestroy { atlasSelection.actions.clearNonBaseParcLayer() ) } + onSelectAtlas(atlas: SapiAtlasModel): void{ + this.store$.dispatch( + atlasSelection.actions.selectAtlas({ + atlas + }) + ) + } + onSelectSpace(template: SapiSpaceModel): void{ + this.store$.dispatch( + atlasSelection.actions.selectTemplate({ + template + }) + ) + } onSelectParcellation(parcellation: SapiParcellationModel): void{ this.store$.dispatch( atlasSelection.actions.selectParcellation({ @@ -412,6 +432,16 @@ export class ViewerCmp implements OnDestroy { }) ) } + onSelectSpaceAndParcellation(e): void{ + const {space, parcellation} = e + this.store$.dispatch( + atlasSelection.actions.setAtlasSelectionState({ + selectedTemplate: space, + selectedParcellation: parcellation + }) + ) + } + navigateTo(position: number[]): void { this.store$.dispatch( atlasSelection.actions.navigateTo({ diff --git a/src/viewerModule/viewerCmp/viewerCmp.style.css b/src/viewerModule/viewerCmp/viewerCmp.style.css index e5a409a28..10966ff61 100644 --- a/src/viewerModule/viewerCmp/viewerCmp.style.css +++ b/src/viewerModule/viewerCmp/viewerCmp.style.css @@ -140,3 +140,13 @@ mat-list[dense].contextual-block align-items: flex-end; justify-content: flex-end; } + +.arrow-between-chips { + margin: 0 -30px; + padding: 0 30px; +} + +.hide-chip-corner { + margin-left: -2.5rem; + padding-left: 3rem; +} diff --git a/src/viewerModule/viewerCmp/viewerCmp.template.html b/src/viewerModule/viewerCmp/viewerCmp.template.html index b0e06fdaa..b17d590f4 100644 --- a/src/viewerModule/viewerCmp/viewerCmp.template.html +++ b/src/viewerModule/viewerCmp/viewerCmp.template.html @@ -376,13 +376,6 @@ [viewerLoaded]="viewerLoaded"> - - - @@ -403,15 +396,6 @@ - - - -
+ + + + + + + + + + (sxplr-sapiviews-core-parcellation-smartchip-select-space-parcellation)="onSelectSpaceAndParcellation($event)" + [sxplr-sapiviews-core-parcellation-smartchip-custom-color]="'darkgrey'" + >