diff --git a/package.json b/package.json index 461fde5d6a38a9176f6a7c387ae2e1f8d2b317bb..770ce3ae85a694e2154ec4ba013bb98430eb11c5 100644 --- a/package.json +++ b/package.json @@ -3,15 +3,8 @@ "version": "2.4.0", "description": "HBP interactive atlas viewer. Integrating KG query, dataset previews & more. Based on humanbrainproject/nehuba & google/neuroglancer. Built with angular", "scripts": { - "dev-server-export": "webpack-dev-server --config webpack.export.js", - "build-export": "webpack --config webpack.export.js", - "build-export-min": "webpack --config webpack.export.min.js", - "build-export-aot": "webpack --config webpack.export.aot.js", - "build-aot": "PRODUCTION=true GIT_HASH=`jq -r '.version' package.json` webpack --config webpack.aot.js && node ./third_party/matomo/processMatomo.js", - "plugin-server": "node ./src/plugin_examples/server.js", - "dev-server": "BACKEND_URL=${BACKEND_URL:-http://localhost:3000/} webpack-dev-server --config webpack.dev.js --mode development", - "dev-server-aot": "BACKEND_URL=${BACKEND_URL:-http://localhost:3000/} PRODUCTION=true GIT_HASH=`jq -r '.version' package.json` webpack-dev-server --config webpack.dev-aot.js", - "dev-server-all-interfaces": "webpack-dev-server --config webpack.dev.js --mode development --hot --host 0.0.0.0", + "build-aot": "PRODUCTION=true GIT_HASH=`jq -r '.version' package.json` webpack --config ./webpack/webpack.aot.js && node ./third_party/matomo/processMatomo.js", + "dev-server-aot": "BACKEND_URL=${BACKEND_URL:-http://localhost:3000/} PRODUCTION=true GIT_HASH=`jq -r '.version' package.json` webpack-dev-server --config ./webpack/webpack.dev-aot.js", "test": "karma start spec/karma.conf.js", "e2e": "protractor e2e/protractor.conf", "lint": "eslint src --ext .ts", diff --git a/spec/karma.conf.js b/spec/karma.conf.js index 4f4eb985a36341172d86343a1b95e194e9300bbc..cff56013dafa5ae119154af750913ee51078c5ac 100644 --- a/spec/karma.conf.js +++ b/spec/karma.conf.js @@ -2,8 +2,8 @@ // Generated on Mon Aug 06 2018 12:37:42 GMT+0200 (CEST) const merge = require('webpack-merge') -const webpackTest = require('../webpack.test') -const webpackConfig = require('../webpack.dev') +const webpackTest = require('../webpack/webpack.test') +const webpackConfig = require('../webpack/webpack.dev') const fullWebpack = merge(webpackTest, webpackConfig) const singleRun = process.env.NODE_ENV === 'test' diff --git a/src/ui/connectivityBrowser/connectivityBrowser.component.ts b/src/atlasComponents/connectivity/connectivityBrowser/connectivityBrowser.component.ts similarity index 97% rename from src/ui/connectivityBrowser/connectivityBrowser.component.ts rename to src/atlasComponents/connectivity/connectivityBrowser/connectivityBrowser.component.ts index 03f768c8a7b9968f5e5bef33d15a1ff812f89a4b..9f454d3e1d958832ff0e2e7a195d03ba99480521 100644 --- a/src/ui/connectivityBrowser/connectivityBrowser.component.ts +++ b/src/atlasComponents/connectivity/connectivityBrowser/connectivityBrowser.component.ts @@ -71,6 +71,9 @@ export class ConnectivityBrowserComponent implements OnInit, AfterViewInit, OnDe }) } + @Output() + connectivityDataReceived = new EventEmitter<any>() + @Output() setOpenState: EventEmitter<boolean> = new EventEmitter() @@ -132,7 +135,7 @@ export class ConnectivityBrowserComponent implements OnInit, AfterViewInit, OnDe constructor( private store$: Store<any>, private changeDetectionRef: ChangeDetectorRef, - private httpClient: HttpClient + private httpClient: HttpClient, ) { this.overwrittenColorMap$ = this.store$.pipe( @@ -149,8 +152,8 @@ export class ConnectivityBrowserComponent implements OnInit, AfterViewInit, OnDe ngOnInit(): void { this.httpClient.get<[]>(this.connectivityUrl).subscribe(res => { this.datasetList = res.filter(dl => dl['parcellation id'] === this.parcellationId) - this.selectedDataset = this.datasetList[0].name - this.selectedDatasetDescription = this.datasetList[0].description + this.selectedDataset = this.datasetList[0]?.name + this.selectedDatasetDescription = this.datasetList[0]?.description this.changeDataset() }) @@ -209,7 +212,7 @@ export class ConnectivityBrowserComponent implements OnInit, AfterViewInit, OnDe this.setColorMap$.pipe( distinctUntilChanged() ), - fromEvent(this.connectivityComponentElement.nativeElement, 'connectivityDataReceived').pipe( + fromEvent(this.connectivityComponentElement?.nativeElement, 'connectivityDataReceived').pipe( map((e: CustomEvent) => e.detail) ) ).subscribe(([flag, connectedAreas]) => { @@ -225,7 +228,6 @@ export class ConnectivityBrowserComponent implements OnInit, AfterViewInit, OnDe }) ) this.noDataReceived = false - this.connectivityNumberReceived.emit(connectedAreas.length) this.connectedAreas = connectedAreas diff --git a/src/ui/connectivityBrowser/connectivityBrowser.template.html b/src/atlasComponents/connectivity/connectivityBrowser/connectivityBrowser.template.html similarity index 98% rename from src/ui/connectivityBrowser/connectivityBrowser.template.html rename to src/atlasComponents/connectivity/connectivityBrowser/connectivityBrowser.template.html index 3f69aa9c292f8d61a236f30bad6f9f963938b84e..fa47192a66062082a8d29c557871a35bb6726a49 100644 --- a/src/ui/connectivityBrowser/connectivityBrowser.template.html +++ b/src/atlasComponents/connectivity/connectivityBrowser/connectivityBrowser.template.html @@ -1,5 +1,6 @@ <div class="w-100 h-100 d-block d-flex flex-column pb-2"> <hbp-connectivity-matrix-row + (connectivityDataReceived)="connectivityDataReceived.emit($event)" #connectivityComponent *ngIf="regionName" [region]="regionName + (regionHemisphere? ' - ' + regionHemisphere : '')" diff --git a/src/atlasComponents/connectivity/index.ts b/src/atlasComponents/connectivity/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..c9a2e808fc6290283e77ea4bccff8f199eea3ca0 --- /dev/null +++ b/src/atlasComponents/connectivity/index.ts @@ -0,0 +1,2 @@ +export { ConnectivityBrowserComponent } from "./connectivityBrowser/connectivityBrowser.component"; +export { AtlasCmptConnModule } from "./module"; \ No newline at end of file diff --git a/src/atlasComponents/connectivity/module.ts b/src/atlasComponents/connectivity/module.ts new file mode 100644 index 0000000000000000000000000000000000000000..8d629f66cfeb00b9f7b1f38fa45780725cac8f87 --- /dev/null +++ b/src/atlasComponents/connectivity/module.ts @@ -0,0 +1,24 @@ +import { CommonModule } from "@angular/common"; +import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from "@angular/core"; +import { AngularMaterialModule } from "src/ui/sharedModules/angularMaterial.module"; +import { DatabrowserModule } from "../databrowserModule"; +import { ConnectivityBrowserComponent } from "./connectivityBrowser/connectivityBrowser.component"; + +@NgModule({ + imports: [ + CommonModule, + DatabrowserModule, + AngularMaterialModule, + ], + declarations: [ + ConnectivityBrowserComponent, + ], + exports: [ + ConnectivityBrowserComponent, + ], + schemas: [ + CUSTOM_ELEMENTS_SCHEMA, + ], +}) + +export class AtlasCmptConnModule{} \ No newline at end of file diff --git a/src/ui/databrowserModule/bulkDownload/bulkDownloadBtn.component.ts b/src/atlasComponents/databrowserModule/bulkDownload/bulkDownloadBtn.component.ts similarity index 100% rename from src/ui/databrowserModule/bulkDownload/bulkDownloadBtn.component.ts rename to src/atlasComponents/databrowserModule/bulkDownload/bulkDownloadBtn.component.ts diff --git a/src/ui/databrowserModule/bulkDownload/bulkDownloadBtn.template.html b/src/atlasComponents/databrowserModule/bulkDownload/bulkDownloadBtn.template.html similarity index 100% rename from src/ui/databrowserModule/bulkDownload/bulkDownloadBtn.template.html rename to src/atlasComponents/databrowserModule/bulkDownload/bulkDownloadBtn.template.html diff --git a/src/ui/databrowserModule/constants.ts b/src/atlasComponents/databrowserModule/constants.ts similarity index 100% rename from src/ui/databrowserModule/constants.ts rename to src/atlasComponents/databrowserModule/constants.ts diff --git a/src/ui/databrowserModule/contributor/index.ts b/src/atlasComponents/databrowserModule/contributor/index.ts similarity index 100% rename from src/ui/databrowserModule/contributor/index.ts rename to src/atlasComponents/databrowserModule/contributor/index.ts diff --git a/src/ui/databrowserModule/contributor/kgLink.pipe.ts b/src/atlasComponents/databrowserModule/contributor/kgLink.pipe.ts similarity index 100% rename from src/ui/databrowserModule/contributor/kgLink.pipe.ts rename to src/atlasComponents/databrowserModule/contributor/kgLink.pipe.ts diff --git a/src/ui/databrowserModule/contributor/module.ts b/src/atlasComponents/databrowserModule/contributor/module.ts similarity index 100% rename from src/ui/databrowserModule/contributor/module.ts rename to src/atlasComponents/databrowserModule/contributor/module.ts diff --git a/src/ui/databrowserModule/contributor/util.ts b/src/atlasComponents/databrowserModule/contributor/util.ts similarity index 100% rename from src/ui/databrowserModule/contributor/util.ts rename to src/atlasComponents/databrowserModule/contributor/util.ts diff --git a/src/ui/databrowserModule/databrowser.module.ts b/src/atlasComponents/databrowserModule/databrowser.module.ts similarity index 99% rename from src/ui/databrowserModule/databrowser.module.ts rename to src/atlasComponents/databrowserModule/databrowser.module.ts index b03aec66953c43b10a40dca97a1f308b85f4cfcd..60aa012d2ee3cb06d4f9364125156a8f2fc0ada1 100644 --- a/src/ui/databrowserModule/databrowser.module.ts +++ b/src/atlasComponents/databrowserModule/databrowser.module.ts @@ -40,7 +40,7 @@ import { import { ShownPreviewsDirective } from "./preview/shownPreviews.directive"; import { FilterPreviewByType } from "./preview/filterPreview.pipe"; import { PreviewCardComponent } from "./preview/previewCard/previewCard.component"; -import { LayerBrowserModule } from "../layerbrowser"; +import { LayerBrowserModule } from "../../ui/layerbrowser"; import { DatabrowserDirective } from "./databrowser/databrowser.directive"; import { ContributorModule } from "./contributor"; import { DatabrowserService } from "./databrowser.service"; diff --git a/src/ui/databrowserModule/databrowser.service.spec.ts b/src/atlasComponents/databrowserModule/databrowser.service.spec.ts similarity index 100% rename from src/ui/databrowserModule/databrowser.service.spec.ts rename to src/atlasComponents/databrowserModule/databrowser.service.spec.ts diff --git a/src/ui/databrowserModule/databrowser.service.ts b/src/atlasComponents/databrowserModule/databrowser.service.ts similarity index 100% rename from src/ui/databrowserModule/databrowser.service.ts rename to src/atlasComponents/databrowserModule/databrowser.service.ts diff --git a/src/ui/databrowserModule/databrowser.useEffect.spec.ts b/src/atlasComponents/databrowserModule/databrowser.useEffect.spec.ts similarity index 100% rename from src/ui/databrowserModule/databrowser.useEffect.spec.ts rename to src/atlasComponents/databrowserModule/databrowser.useEffect.spec.ts diff --git a/src/ui/databrowserModule/databrowser.useEffect.ts b/src/atlasComponents/databrowserModule/databrowser.useEffect.ts similarity index 100% rename from src/ui/databrowserModule/databrowser.useEffect.ts rename to src/atlasComponents/databrowserModule/databrowser.useEffect.ts diff --git a/src/ui/databrowserModule/databrowser/databrowser.base.ts b/src/atlasComponents/databrowserModule/databrowser/databrowser.base.ts similarity index 100% rename from src/ui/databrowserModule/databrowser/databrowser.base.ts rename to src/atlasComponents/databrowserModule/databrowser/databrowser.base.ts diff --git a/src/ui/databrowserModule/databrowser/databrowser.component.ts b/src/atlasComponents/databrowserModule/databrowser/databrowser.component.ts similarity index 100% rename from src/ui/databrowserModule/databrowser/databrowser.component.ts rename to src/atlasComponents/databrowserModule/databrowser/databrowser.component.ts diff --git a/src/ui/databrowserModule/databrowser/databrowser.directive.ts b/src/atlasComponents/databrowserModule/databrowser/databrowser.directive.ts similarity index 100% rename from src/ui/databrowserModule/databrowser/databrowser.directive.ts rename to src/atlasComponents/databrowserModule/databrowser/databrowser.directive.ts diff --git a/src/ui/databrowserModule/databrowser/databrowser.style.css b/src/atlasComponents/databrowserModule/databrowser/databrowser.style.css similarity index 100% rename from src/ui/databrowserModule/databrowser/databrowser.style.css rename to src/atlasComponents/databrowserModule/databrowser/databrowser.style.css diff --git a/src/ui/databrowserModule/databrowser/databrowser.template.html b/src/atlasComponents/databrowserModule/databrowser/databrowser.template.html similarity index 100% rename from src/ui/databrowserModule/databrowser/databrowser.template.html rename to src/atlasComponents/databrowserModule/databrowser/databrowser.template.html diff --git a/src/ui/databrowserModule/index.ts b/src/atlasComponents/databrowserModule/index.ts similarity index 100% rename from src/ui/databrowserModule/index.ts rename to src/atlasComponents/databrowserModule/index.ts diff --git a/src/ui/databrowserModule/kgSingleDatasetService.service.ts b/src/atlasComponents/databrowserModule/kgSingleDatasetService.service.ts similarity index 100% rename from src/ui/databrowserModule/kgSingleDatasetService.service.ts rename to src/atlasComponents/databrowserModule/kgSingleDatasetService.service.ts diff --git a/src/ui/databrowserModule/modalityPicker/modalityPicker.component.spec.ts b/src/atlasComponents/databrowserModule/modalityPicker/modalityPicker.component.spec.ts similarity index 100% rename from src/ui/databrowserModule/modalityPicker/modalityPicker.component.spec.ts rename to src/atlasComponents/databrowserModule/modalityPicker/modalityPicker.component.spec.ts diff --git a/src/ui/databrowserModule/modalityPicker/modalityPicker.component.ts b/src/atlasComponents/databrowserModule/modalityPicker/modalityPicker.component.ts similarity index 100% rename from src/ui/databrowserModule/modalityPicker/modalityPicker.component.ts rename to src/atlasComponents/databrowserModule/modalityPicker/modalityPicker.component.ts diff --git a/src/ui/databrowserModule/modalityPicker/modalityPicker.style.css b/src/atlasComponents/databrowserModule/modalityPicker/modalityPicker.style.css similarity index 100% rename from src/ui/databrowserModule/modalityPicker/modalityPicker.style.css rename to src/atlasComponents/databrowserModule/modalityPicker/modalityPicker.style.css diff --git a/src/ui/databrowserModule/modalityPicker/modalityPicker.template.html b/src/atlasComponents/databrowserModule/modalityPicker/modalityPicker.template.html similarity index 100% rename from src/ui/databrowserModule/modalityPicker/modalityPicker.template.html rename to src/atlasComponents/databrowserModule/modalityPicker/modalityPicker.template.html diff --git a/src/ui/databrowserModule/preview/datasetPreviews/datasetPreviewsList/datasetPreviewList.component.ts b/src/atlasComponents/databrowserModule/preview/datasetPreviews/datasetPreviewsList/datasetPreviewList.component.ts similarity index 100% rename from src/ui/databrowserModule/preview/datasetPreviews/datasetPreviewsList/datasetPreviewList.component.ts rename to src/atlasComponents/databrowserModule/preview/datasetPreviews/datasetPreviewsList/datasetPreviewList.component.ts diff --git a/src/ui/databrowserModule/preview/datasetPreviews/datasetPreviewsList/datasetPreviewList.template.html b/src/atlasComponents/databrowserModule/preview/datasetPreviews/datasetPreviewsList/datasetPreviewList.template.html similarity index 100% rename from src/ui/databrowserModule/preview/datasetPreviews/datasetPreviewsList/datasetPreviewList.template.html rename to src/atlasComponents/databrowserModule/preview/datasetPreviews/datasetPreviewsList/datasetPreviewList.template.html diff --git a/src/ui/databrowserModule/preview/filterPreview.pipe.ts b/src/atlasComponents/databrowserModule/preview/filterPreview.pipe.ts similarity index 100% rename from src/ui/databrowserModule/preview/filterPreview.pipe.ts rename to src/atlasComponents/databrowserModule/preview/filterPreview.pipe.ts diff --git a/src/ui/databrowserModule/preview/preview.base.ts b/src/atlasComponents/databrowserModule/preview/preview.base.ts similarity index 100% rename from src/ui/databrowserModule/preview/preview.base.ts rename to src/atlasComponents/databrowserModule/preview/preview.base.ts diff --git a/src/ui/databrowserModule/preview/previewCard/previewCard.component.ts b/src/atlasComponents/databrowserModule/preview/previewCard/previewCard.component.ts similarity index 100% rename from src/ui/databrowserModule/preview/previewCard/previewCard.component.ts rename to src/atlasComponents/databrowserModule/preview/previewCard/previewCard.component.ts diff --git a/src/ui/databrowserModule/preview/previewCard/previewCard.style.css b/src/atlasComponents/databrowserModule/preview/previewCard/previewCard.style.css similarity index 100% rename from src/ui/databrowserModule/preview/previewCard/previewCard.style.css rename to src/atlasComponents/databrowserModule/preview/previewCard/previewCard.style.css diff --git a/src/ui/databrowserModule/preview/previewCard/previewCard.template.html b/src/atlasComponents/databrowserModule/preview/previewCard/previewCard.template.html similarity index 100% rename from src/ui/databrowserModule/preview/previewCard/previewCard.template.html rename to src/atlasComponents/databrowserModule/preview/previewCard/previewCard.template.html diff --git a/src/ui/databrowserModule/preview/previewComponentWrapper/previewCW.component.ts b/src/atlasComponents/databrowserModule/preview/previewComponentWrapper/previewCW.component.ts similarity index 100% rename from src/ui/databrowserModule/preview/previewComponentWrapper/previewCW.component.ts rename to src/atlasComponents/databrowserModule/preview/previewComponentWrapper/previewCW.component.ts diff --git a/src/ui/databrowserModule/preview/previewComponentWrapper/previewCW.style.css b/src/atlasComponents/databrowserModule/preview/previewComponentWrapper/previewCW.style.css similarity index 100% rename from src/ui/databrowserModule/preview/previewComponentWrapper/previewCW.style.css rename to src/atlasComponents/databrowserModule/preview/previewComponentWrapper/previewCW.style.css diff --git a/src/ui/databrowserModule/preview/previewComponentWrapper/previewCW.template.html b/src/atlasComponents/databrowserModule/preview/previewComponentWrapper/previewCW.template.html similarity index 100% rename from src/ui/databrowserModule/preview/previewComponentWrapper/previewCW.template.html rename to src/atlasComponents/databrowserModule/preview/previewComponentWrapper/previewCW.template.html diff --git a/src/ui/databrowserModule/preview/previewDatasetFile.directive.spec.ts b/src/atlasComponents/databrowserModule/preview/previewDatasetFile.directive.spec.ts similarity index 100% rename from src/ui/databrowserModule/preview/previewDatasetFile.directive.spec.ts rename to src/atlasComponents/databrowserModule/preview/previewDatasetFile.directive.spec.ts diff --git a/src/ui/databrowserModule/preview/previewDatasetFile.directive.ts b/src/atlasComponents/databrowserModule/preview/previewDatasetFile.directive.ts similarity index 100% rename from src/ui/databrowserModule/preview/previewDatasetFile.directive.ts rename to src/atlasComponents/databrowserModule/preview/previewDatasetFile.directive.ts diff --git a/src/ui/databrowserModule/preview/previewFileIcon.pipe.ts b/src/atlasComponents/databrowserModule/preview/previewFileIcon.pipe.ts similarity index 100% rename from src/ui/databrowserModule/preview/previewFileIcon.pipe.ts rename to src/atlasComponents/databrowserModule/preview/previewFileIcon.pipe.ts diff --git a/src/ui/databrowserModule/preview/previewFileType.pipe.ts b/src/atlasComponents/databrowserModule/preview/previewFileType.pipe.ts similarity index 100% rename from src/ui/databrowserModule/preview/previewFileType.pipe.ts rename to src/atlasComponents/databrowserModule/preview/previewFileType.pipe.ts diff --git a/src/ui/databrowserModule/preview/shownPreviews.directive.ts b/src/atlasComponents/databrowserModule/preview/shownPreviews.directive.ts similarity index 100% rename from src/ui/databrowserModule/preview/shownPreviews.directive.ts rename to src/atlasComponents/databrowserModule/preview/shownPreviews.directive.ts diff --git a/src/ui/databrowserModule/pure.spec.ts b/src/atlasComponents/databrowserModule/pure.spec.ts similarity index 100% rename from src/ui/databrowserModule/pure.spec.ts rename to src/atlasComponents/databrowserModule/pure.spec.ts diff --git a/src/ui/databrowserModule/pure.ts b/src/atlasComponents/databrowserModule/pure.ts similarity index 100% rename from src/ui/databrowserModule/pure.ts rename to src/atlasComponents/databrowserModule/pure.ts diff --git a/src/ui/databrowserModule/showDatasetDialog.directive.spec.ts b/src/atlasComponents/databrowserModule/showDatasetDialog.directive.spec.ts similarity index 98% rename from src/ui/databrowserModule/showDatasetDialog.directive.spec.ts rename to src/atlasComponents/databrowserModule/showDatasetDialog.directive.spec.ts index 18fa8349d210f0f86fdc82ecd8a2af4efaf57db4..4dbac0cb6856c0bacb1218aef34d2e66a456d666 100644 --- a/src/ui/databrowserModule/showDatasetDialog.directive.spec.ts +++ b/src/atlasComponents/databrowserModule/showDatasetDialog.directive.spec.ts @@ -1,6 +1,6 @@ import { Component } from "@angular/core"; import { async, TestBed } from "@angular/core/testing"; -import { AngularMaterialModule } from "../sharedModules/angularMaterial.module"; +import { AngularMaterialModule } from "../../ui/sharedModules/angularMaterial.module"; import { ShowDatasetDialogDirective, IAV_DATASET_SHOW_DATASET_DIALOG_CMP } from "./showDatasetDialog.directive"; import { By } from "@angular/platform-browser"; import { MatDialog } from "@angular/material/dialog"; diff --git a/src/ui/databrowserModule/showDatasetDialog.directive.ts b/src/atlasComponents/databrowserModule/showDatasetDialog.directive.ts similarity index 100% rename from src/ui/databrowserModule/showDatasetDialog.directive.ts rename to src/atlasComponents/databrowserModule/showDatasetDialog.directive.ts diff --git a/src/ui/databrowserModule/shownDataset.directive.ts b/src/atlasComponents/databrowserModule/shownDataset.directive.ts similarity index 100% rename from src/ui/databrowserModule/shownDataset.directive.ts rename to src/atlasComponents/databrowserModule/shownDataset.directive.ts diff --git a/src/ui/databrowserModule/singleDataset/detailedView/singleDataset.component.spec.ts b/src/atlasComponents/databrowserModule/singleDataset/detailedView/singleDataset.component.spec.ts similarity index 100% rename from src/ui/databrowserModule/singleDataset/detailedView/singleDataset.component.spec.ts rename to src/atlasComponents/databrowserModule/singleDataset/detailedView/singleDataset.component.spec.ts diff --git a/src/ui/databrowserModule/singleDataset/detailedView/singleDataset.component.ts b/src/atlasComponents/databrowserModule/singleDataset/detailedView/singleDataset.component.ts similarity index 100% rename from src/ui/databrowserModule/singleDataset/detailedView/singleDataset.component.ts rename to src/atlasComponents/databrowserModule/singleDataset/detailedView/singleDataset.component.ts diff --git a/src/ui/databrowserModule/singleDataset/detailedView/singleDataset.style.css b/src/atlasComponents/databrowserModule/singleDataset/detailedView/singleDataset.style.css similarity index 100% rename from src/ui/databrowserModule/singleDataset/detailedView/singleDataset.style.css rename to src/atlasComponents/databrowserModule/singleDataset/detailedView/singleDataset.style.css diff --git a/src/ui/databrowserModule/singleDataset/detailedView/singleDataset.template.html b/src/atlasComponents/databrowserModule/singleDataset/detailedView/singleDataset.template.html similarity index 100% rename from src/ui/databrowserModule/singleDataset/detailedView/singleDataset.template.html rename to src/atlasComponents/databrowserModule/singleDataset/detailedView/singleDataset.template.html diff --git a/src/ui/databrowserModule/singleDataset/listView/singleDatasetListView.component.ts b/src/atlasComponents/databrowserModule/singleDataset/listView/singleDatasetListView.component.ts similarity index 100% rename from src/ui/databrowserModule/singleDataset/listView/singleDatasetListView.component.ts rename to src/atlasComponents/databrowserModule/singleDataset/listView/singleDatasetListView.component.ts diff --git a/src/ui/databrowserModule/singleDataset/listView/singleDatasetListView.style.css b/src/atlasComponents/databrowserModule/singleDataset/listView/singleDatasetListView.style.css similarity index 100% rename from src/ui/databrowserModule/singleDataset/listView/singleDatasetListView.style.css rename to src/atlasComponents/databrowserModule/singleDataset/listView/singleDatasetListView.style.css diff --git a/src/ui/databrowserModule/singleDataset/listView/singleDatasetListView.template.html b/src/atlasComponents/databrowserModule/singleDataset/listView/singleDatasetListView.template.html similarity index 100% rename from src/ui/databrowserModule/singleDataset/listView/singleDatasetListView.template.html rename to src/atlasComponents/databrowserModule/singleDataset/listView/singleDatasetListView.template.html diff --git a/src/ui/databrowserModule/singleDataset/sideNavView/sDsSideNavView.component.ts b/src/atlasComponents/databrowserModule/singleDataset/sideNavView/sDsSideNavView.component.ts similarity index 100% rename from src/ui/databrowserModule/singleDataset/sideNavView/sDsSideNavView.component.ts rename to src/atlasComponents/databrowserModule/singleDataset/sideNavView/sDsSideNavView.component.ts diff --git a/src/ui/databrowserModule/singleDataset/sideNavView/sDsSideNavView.style.css b/src/atlasComponents/databrowserModule/singleDataset/sideNavView/sDsSideNavView.style.css similarity index 100% rename from src/ui/databrowserModule/singleDataset/sideNavView/sDsSideNavView.style.css rename to src/atlasComponents/databrowserModule/singleDataset/sideNavView/sDsSideNavView.style.css diff --git a/src/ui/databrowserModule/singleDataset/sideNavView/sDsSideNavView.template.html b/src/atlasComponents/databrowserModule/singleDataset/sideNavView/sDsSideNavView.template.html similarity index 100% rename from src/ui/databrowserModule/singleDataset/sideNavView/sDsSideNavView.template.html rename to src/atlasComponents/databrowserModule/singleDataset/sideNavView/sDsSideNavView.template.html diff --git a/src/ui/databrowserModule/singleDataset/singleDataset.base.spec.ts b/src/atlasComponents/databrowserModule/singleDataset/singleDataset.base.spec.ts similarity index 100% rename from src/ui/databrowserModule/singleDataset/singleDataset.base.spec.ts rename to src/atlasComponents/databrowserModule/singleDataset/singleDataset.base.spec.ts diff --git a/src/ui/databrowserModule/singleDataset/singleDataset.base.ts b/src/atlasComponents/databrowserModule/singleDataset/singleDataset.base.ts similarity index 100% rename from src/ui/databrowserModule/singleDataset/singleDataset.base.ts rename to src/atlasComponents/databrowserModule/singleDataset/singleDataset.base.ts diff --git a/src/ui/databrowserModule/singleDataset/singleDataset.directive.ts b/src/atlasComponents/databrowserModule/singleDataset/singleDataset.directive.ts similarity index 100% rename from src/ui/databrowserModule/singleDataset/singleDataset.directive.ts rename to src/atlasComponents/databrowserModule/singleDataset/singleDataset.directive.ts diff --git a/src/ui/databrowserModule/store.module.ts b/src/atlasComponents/databrowserModule/store.module.ts similarity index 100% rename from src/ui/databrowserModule/store.module.ts rename to src/atlasComponents/databrowserModule/store.module.ts diff --git a/src/ui/databrowserModule/util/aggregateArrayIntoRoot.pipe.ts b/src/atlasComponents/databrowserModule/util/aggregateArrayIntoRoot.pipe.ts similarity index 100% rename from src/ui/databrowserModule/util/aggregateArrayIntoRoot.pipe.ts rename to src/atlasComponents/databrowserModule/util/aggregateArrayIntoRoot.pipe.ts diff --git a/src/ui/databrowserModule/util/appendFilterModality.pipe.ts b/src/atlasComponents/databrowserModule/util/appendFilterModality.pipe.ts similarity index 100% rename from src/ui/databrowserModule/util/appendFilterModality.pipe.ts rename to src/atlasComponents/databrowserModule/util/appendFilterModality.pipe.ts diff --git a/src/ui/databrowserModule/util/copyProperty.pipe.spec.ts b/src/atlasComponents/databrowserModule/util/copyProperty.pipe.spec.ts similarity index 100% rename from src/ui/databrowserModule/util/copyProperty.pipe.spec.ts rename to src/atlasComponents/databrowserModule/util/copyProperty.pipe.spec.ts diff --git a/src/ui/databrowserModule/util/copyProperty.pipe.ts b/src/atlasComponents/databrowserModule/util/copyProperty.pipe.ts similarity index 100% rename from src/ui/databrowserModule/util/copyProperty.pipe.ts rename to src/atlasComponents/databrowserModule/util/copyProperty.pipe.ts diff --git a/src/ui/databrowserModule/util/datasetIsFaved.pipe.ts b/src/atlasComponents/databrowserModule/util/datasetIsFaved.pipe.ts similarity index 100% rename from src/ui/databrowserModule/util/datasetIsFaved.pipe.ts rename to src/atlasComponents/databrowserModule/util/datasetIsFaved.pipe.ts diff --git a/src/ui/databrowserModule/util/filterDataEntriesByMethods.pipe.ts b/src/atlasComponents/databrowserModule/util/filterDataEntriesByMethods.pipe.ts similarity index 100% rename from src/ui/databrowserModule/util/filterDataEntriesByMethods.pipe.ts rename to src/atlasComponents/databrowserModule/util/filterDataEntriesByMethods.pipe.ts diff --git a/src/ui/databrowserModule/util/filterDataEntriesByRegion.pipe.spec.ts b/src/atlasComponents/databrowserModule/util/filterDataEntriesByRegion.pipe.spec.ts similarity index 100% rename from src/ui/databrowserModule/util/filterDataEntriesByRegion.pipe.spec.ts rename to src/atlasComponents/databrowserModule/util/filterDataEntriesByRegion.pipe.spec.ts diff --git a/src/ui/databrowserModule/util/filterDataEntriesByRegion.pipe.ts b/src/atlasComponents/databrowserModule/util/filterDataEntriesByRegion.pipe.ts similarity index 100% rename from src/ui/databrowserModule/util/filterDataEntriesByRegion.pipe.ts rename to src/atlasComponents/databrowserModule/util/filterDataEntriesByRegion.pipe.ts diff --git a/src/ui/databrowserModule/util/getKgSchemaIdFromFullId.pipe.ts b/src/atlasComponents/databrowserModule/util/getKgSchemaIdFromFullId.pipe.ts similarity index 100% rename from src/ui/databrowserModule/util/getKgSchemaIdFromFullId.pipe.ts rename to src/atlasComponents/databrowserModule/util/getKgSchemaIdFromFullId.pipe.ts diff --git a/src/ui/databrowserModule/util/pathToNestedChildren.pipe.spec.ts b/src/atlasComponents/databrowserModule/util/pathToNestedChildren.pipe.spec.ts similarity index 100% rename from src/ui/databrowserModule/util/pathToNestedChildren.pipe.spec.ts rename to src/atlasComponents/databrowserModule/util/pathToNestedChildren.pipe.spec.ts diff --git a/src/ui/databrowserModule/util/pathToNestedChildren.pipe.ts b/src/atlasComponents/databrowserModule/util/pathToNestedChildren.pipe.ts similarity index 100% rename from src/ui/databrowserModule/util/pathToNestedChildren.pipe.ts rename to src/atlasComponents/databrowserModule/util/pathToNestedChildren.pipe.ts diff --git a/src/ui/databrowserModule/util/previewFileDisabledByReferenceSpace.pipe.ts b/src/atlasComponents/databrowserModule/util/previewFileDisabledByReferenceSpace.pipe.ts similarity index 100% rename from src/ui/databrowserModule/util/previewFileDisabledByReferenceSpace.pipe.ts rename to src/atlasComponents/databrowserModule/util/previewFileDisabledByReferenceSpace.pipe.ts diff --git a/src/ui/databrowserModule/util/regionBackgroundToRgb.pipe.ts b/src/atlasComponents/databrowserModule/util/regionBackgroundToRgb.pipe.ts similarity index 100% rename from src/ui/databrowserModule/util/regionBackgroundToRgb.pipe.ts rename to src/atlasComponents/databrowserModule/util/regionBackgroundToRgb.pipe.ts diff --git a/src/ui/databrowserModule/util/resetCounterModality.pipe.ts b/src/atlasComponents/databrowserModule/util/resetCounterModality.pipe.ts similarity index 100% rename from src/ui/databrowserModule/util/resetCounterModality.pipe.ts rename to src/atlasComponents/databrowserModule/util/resetCounterModality.pipe.ts diff --git a/src/atlasComponents/parcellation/index.ts b/src/atlasComponents/parcellation/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..4af8252e223c89fce580454087e7038563e1fab4 --- /dev/null +++ b/src/atlasComponents/parcellation/index.ts @@ -0,0 +1,4 @@ +export { FilterNameBySearch } from "./regionHierachy/filterNameBySearch.pipe"; +export { AtlasCmpParcellationModule } from "./module"; +export { RegionHierarchy } from "./regionHierachy/regionHierarchy.component"; +export { RegionTextSearchAutocomplete } from "./regionSearch/regionSearch.component"; diff --git a/src/atlasComponents/parcellation/module.ts b/src/atlasComponents/parcellation/module.ts new file mode 100644 index 0000000000000000000000000000000000000000..78821146430138aa7afe3998ce3959c09f3c099f --- /dev/null +++ b/src/atlasComponents/parcellation/module.ts @@ -0,0 +1,34 @@ +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { AngularMaterialModule } from "src/ui/sharedModules/angularMaterial.module"; +import { ParcellationRegionModule } from "src/atlasComponents/parcellationRegion"; +import { RegionHierarchy } from "./regionHierachy/regionHierarchy.component"; +import { RegionTextSearchAutocomplete } from "./regionSearch/regionSearch.component"; +import { FilterNameBySearch } from "./regionHierachy/filterNameBySearch.pipe"; +import { UtilModule } from "src/util"; +import { FormsModule, ReactiveFormsModule } from "@angular/forms"; +import { ComponentsModule } from "src/components"; + +@NgModule({ + imports: [ + CommonModule, + UtilModule, + FormsModule, + ReactiveFormsModule, + AngularMaterialModule, + ParcellationRegionModule, + ComponentsModule, + ], + declarations: [ + RegionHierarchy, + RegionTextSearchAutocomplete, + + FilterNameBySearch, + ], + exports: [ + RegionHierarchy, + RegionTextSearchAutocomplete, + FilterNameBySearch, + ] +}) +export class AtlasCmpParcellationModule{} \ No newline at end of file diff --git a/src/ui/viewerStateController/regionHierachy/filterNameBySearch.pipe.ts b/src/atlasComponents/parcellation/regionHierachy/filterNameBySearch.pipe.ts similarity index 100% rename from src/ui/viewerStateController/regionHierachy/filterNameBySearch.pipe.ts rename to src/atlasComponents/parcellation/regionHierachy/filterNameBySearch.pipe.ts diff --git a/src/ui/viewerStateController/regionHierachy/regionHierarchy.component.ts b/src/atlasComponents/parcellation/regionHierachy/regionHierarchy.component.ts similarity index 97% rename from src/ui/viewerStateController/regionHierachy/regionHierarchy.component.ts rename to src/atlasComponents/parcellation/regionHierachy/regionHierarchy.component.ts index 2534f5548129666f3201ea821e1319c6b2bbc572..715f000757552e41ee4e67ace06e9de56006e69e 100644 --- a/src/ui/viewerStateController/regionHierachy/regionHierarchy.component.ts +++ b/src/atlasComponents/parcellation/regionHierachy/regionHierarchy.component.ts @@ -1,8 +1,8 @@ import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, HostListener, Input, OnInit, Output, ViewChild } from "@angular/core"; import { fromEvent, Subject, Subscription } from "rxjs"; import { buffer, debounceTime } from "rxjs/operators"; -import { generateLabelIndexId } from "src/services/stateStore.service"; import { FilterNameBySearch } from "./filterNameBySearch.pipe"; +import { serialiseParcellationRegion } from "common/util" const insertHighlight: (name: string, searchTerm: string) => string = (name: string, searchTerm: string = '') => { const regex = new RegExp(searchTerm, 'gi') @@ -15,7 +15,7 @@ const getDisplayTreeNode: (searchTerm: string, selectedRegions: any[]) => (item: return !!labelIndex && !!ngId && selectedRegions.findIndex(re => - generateLabelIndexId({ labelIndex: re.labelIndex, ngId: re.ngId }) === generateLabelIndexId({ ngId, labelIndex }), + serialiseParcellationRegion({ labelIndex: re.labelIndex, ngId: re.ngId }) === serialiseParcellationRegion({ ngId, labelIndex }), ) >= 0 ? `<span class="cursor-default regionSelected">${insertHighlight(name, searchTerm)}</span>` + (status ? ` <span class="text-muted">(${insertHighlight(status, searchTerm)})</span>` : ``) : `<span class="cursor-default regionNotSelected">${insertHighlight(name, searchTerm)}</span>` + (status ? ` <span class="text-muted">(${insertHighlight(status, searchTerm)})</span>` : ``) diff --git a/src/ui/viewerStateController/regionHierachy/regionHierarchy.style.css b/src/atlasComponents/parcellation/regionHierachy/regionHierarchy.style.css similarity index 100% rename from src/ui/viewerStateController/regionHierachy/regionHierarchy.style.css rename to src/atlasComponents/parcellation/regionHierachy/regionHierarchy.style.css diff --git a/src/ui/viewerStateController/regionHierachy/regionHierarchy.template.html b/src/atlasComponents/parcellation/regionHierachy/regionHierarchy.template.html similarity index 100% rename from src/ui/viewerStateController/regionHierachy/regionHierarchy.template.html rename to src/atlasComponents/parcellation/regionHierachy/regionHierarchy.template.html diff --git a/src/ui/viewerStateController/regionSearch/regionSearch.component.ts b/src/atlasComponents/parcellation/regionSearch/regionSearch.component.ts similarity index 95% rename from src/ui/viewerStateController/regionSearch/regionSearch.component.ts rename to src/atlasComponents/parcellation/regionSearch/regionSearch.component.ts index c5989bbb4f29ac46ba5ce1d05fc87974f95c95da..87a600839de215e162a3baf9dd6ba82d7446c8eb 100644 --- a/src/ui/viewerStateController/regionSearch/regionSearch.component.ts +++ b/src/atlasComponents/parcellation/regionSearch/regionSearch.component.ts @@ -5,13 +5,14 @@ import { combineLatest, Observable, Subject, merge } from "rxjs"; import { debounceTime, distinctUntilChanged, filter, map, shareReplay, startWith, take, tap, withLatestFrom } from "rxjs/operators"; import { VIEWER_STATE_ACTION_TYPES } from "src/services/effect/effect"; import { ADD_TO_REGIONS_SELECTION_WITH_IDS, CHANGE_NAVIGATION, SELECT_REGIONS } from "src/services/state/viewerState.store"; -import { generateLabelIndexId, getMultiNgIdsRegionsLabelIndexMap, IavRootStoreInterface } from "src/services/stateStore.service"; +import { getMultiNgIdsRegionsLabelIndexMap } from "src/services/stateStore.service"; import { LoggingService } from "src/logging"; import { MatDialog } from "@angular/material/dialog"; import { MatAutocompleteSelectedEvent } from "@angular/material/autocomplete"; import { PureContantService } from "src/util"; import { viewerStateToggleRegionSelect, viewerStateNavigateToRegion, viewerStateSetSelectedRegions, viewerStateSetSelectedRegionsWithIds } from "src/services/state/viewerState.store.helper"; import { ARIA_LABELS, CONST } from 'common/constants' +import { serialiseParcellationRegion } from "common/util" const filterRegionBasedOnText = searchTerm => region => `${region.name.toLowerCase()}${region.status? ' (' + region.status + ')' : null}`.includes(searchTerm.toLowerCase()) || (region.relatedAreas && region.relatedAreas.some(relatedArea => relatedArea.name && relatedArea.name.toLowerCase().includes(searchTerm.toLowerCase()))) @@ -53,7 +54,7 @@ export class RegionTextSearchAutocomplete { public selectedRegionLabelIndexSet: Set<string> = new Set() constructor( - private store$: Store<IavRootStoreInterface>, + private store$: Store<any>, private dialog: MatDialog, private pureConstantService: PureContantService, private log: LoggingService @@ -80,7 +81,7 @@ export class RegionTextSearchAutocomplete { ...region, ngId, labelIndex, - labelIndexId: generateLabelIndexId({ ngId, labelIndex }), + labelIndexId: serialiseParcellationRegion({ ngId, labelIndex }), }) } } @@ -97,7 +98,7 @@ export class RegionTextSearchAutocomplete { select('regionsSelected'), distinctUntilChanged(), tap(regions => { - const arrLabelIndexId = regions.map(({ ngId, labelIndex }) => generateLabelIndexId({ ngId, labelIndex })) + const arrLabelIndexId = regions.map(({ ngId, labelIndex }) => serialiseParcellationRegion({ ngId, labelIndex })) this.selectedRegionLabelIndexSet = new Set(arrLabelIndexId) }), startWith([]), diff --git a/src/ui/viewerStateController/regionSearch/regionSearch.style.css b/src/atlasComponents/parcellation/regionSearch/regionSearch.style.css similarity index 100% rename from src/ui/viewerStateController/regionSearch/regionSearch.style.css rename to src/atlasComponents/parcellation/regionSearch/regionSearch.style.css diff --git a/src/ui/viewerStateController/regionSearch/regionSearch.template.html b/src/atlasComponents/parcellation/regionSearch/regionSearch.template.html similarity index 100% rename from src/ui/viewerStateController/regionSearch/regionSearch.template.html rename to src/atlasComponents/parcellation/regionSearch/regionSearch.template.html diff --git a/src/atlasComponents/parcellationRegion/index.ts b/src/atlasComponents/parcellationRegion/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..b3f4198bc8c372d02e1c43b01cd9809d18de3d1c --- /dev/null +++ b/src/atlasComponents/parcellationRegion/index.ts @@ -0,0 +1,10 @@ +export { + ParcellationRegionModule, +} from "./module" + + +export { RegionDirective } from "./region.directive"; +export { RegionListSimpleViewComponent } from "./regionListSimpleView/regionListSimpleView.component"; +export { RegionMenuComponent } from "./regionMenu/regionMenu.component"; +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 new file mode 100644 index 0000000000000000000000000000000000000000..1e400b5e25835f7ae6cee44a939f993b2e1f2253 --- /dev/null +++ b/src/atlasComponents/parcellationRegion/module.ts @@ -0,0 +1,39 @@ +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { ComponentsModule } from "src/components"; +import { DatabrowserModule } from "src/atlasComponents/databrowserModule"; +import { AngularMaterialModule } from "src/ui/sharedModules/angularMaterial.module"; +import { UtilModule } from "src/util"; +import { RenderViewOriginDatasetLabelPipe } from "./region.base"; +import { RegionDirective } from "./region.directive"; +import { RegionListSimpleViewComponent } from "./regionListSimpleView/regionListSimpleView.component"; +import { RegionMenuComponent } from "./regionMenu/regionMenu.component"; +import { SimpleRegionComponent } from "./regionSimple/regionSimple.component"; + +@NgModule({ + imports: [ + CommonModule, + UtilModule, + DatabrowserModule, + AngularMaterialModule, + ComponentsModule, + ], + declarations: [ + RegionMenuComponent, + RegionListSimpleViewComponent, + SimpleRegionComponent, + + RegionDirective, + RenderViewOriginDatasetLabelPipe, + ], + exports: [ + RegionMenuComponent, + RegionListSimpleViewComponent, + SimpleRegionComponent, + + RegionDirective, + RenderViewOriginDatasetLabelPipe, + ] +}) + +export class ParcellationRegionModule{} \ No newline at end of file diff --git a/src/ui/parcellationRegion/region.base.spec.ts b/src/atlasComponents/parcellationRegion/region.base.spec.ts similarity index 100% rename from src/ui/parcellationRegion/region.base.spec.ts rename to src/atlasComponents/parcellationRegion/region.base.spec.ts diff --git a/src/ui/parcellationRegion/region.base.ts b/src/atlasComponents/parcellationRegion/region.base.ts similarity index 100% rename from src/ui/parcellationRegion/region.base.ts rename to src/atlasComponents/parcellationRegion/region.base.ts diff --git a/src/ui/parcellationRegion/region.directive.ts b/src/atlasComponents/parcellationRegion/region.directive.ts similarity index 100% rename from src/ui/parcellationRegion/region.directive.ts rename to src/atlasComponents/parcellationRegion/region.directive.ts diff --git a/src/ui/parcellationRegion/regionListSimpleView/regionListSimpleView.component.ts b/src/atlasComponents/parcellationRegion/regionListSimpleView/regionListSimpleView.component.ts similarity index 100% rename from src/ui/parcellationRegion/regionListSimpleView/regionListSimpleView.component.ts rename to src/atlasComponents/parcellationRegion/regionListSimpleView/regionListSimpleView.component.ts diff --git a/src/ui/parcellationRegion/regionListSimpleView/regionListSimpleView.style.css b/src/atlasComponents/parcellationRegion/regionListSimpleView/regionListSimpleView.style.css similarity index 100% rename from src/ui/parcellationRegion/regionListSimpleView/regionListSimpleView.style.css rename to src/atlasComponents/parcellationRegion/regionListSimpleView/regionListSimpleView.style.css diff --git a/src/ui/parcellationRegion/regionListSimpleView/regionListSimpleView.template.html b/src/atlasComponents/parcellationRegion/regionListSimpleView/regionListSimpleView.template.html similarity index 100% rename from src/ui/parcellationRegion/regionListSimpleView/regionListSimpleView.template.html rename to src/atlasComponents/parcellationRegion/regionListSimpleView/regionListSimpleView.template.html diff --git a/src/ui/parcellationRegion/regionMenu/regionMenu.component.spec.ts b/src/atlasComponents/parcellationRegion/regionMenu/regionMenu.component.spec.ts similarity index 100% rename from src/ui/parcellationRegion/regionMenu/regionMenu.component.spec.ts rename to src/atlasComponents/parcellationRegion/regionMenu/regionMenu.component.spec.ts diff --git a/src/ui/parcellationRegion/regionMenu/regionMenu.component.ts b/src/atlasComponents/parcellationRegion/regionMenu/regionMenu.component.ts similarity index 100% rename from src/ui/parcellationRegion/regionMenu/regionMenu.component.ts rename to src/atlasComponents/parcellationRegion/regionMenu/regionMenu.component.ts diff --git a/src/ui/parcellationRegion/regionMenu/regionMenu.style.css b/src/atlasComponents/parcellationRegion/regionMenu/regionMenu.style.css similarity index 100% rename from src/ui/parcellationRegion/regionMenu/regionMenu.style.css rename to src/atlasComponents/parcellationRegion/regionMenu/regionMenu.style.css diff --git a/src/ui/parcellationRegion/regionMenu/regionMenu.template.html b/src/atlasComponents/parcellationRegion/regionMenu/regionMenu.template.html similarity index 93% rename from src/ui/parcellationRegion/regionMenu/regionMenu.template.html rename to src/atlasComponents/parcellationRegion/regionMenu/regionMenu.template.html index 65f9d1b40ad7a03247fd7248a62e396e05d281ba..ac243dc5cf13882cadeafd73c0ddb4ce3700fc8b 100644 --- a/src/ui/parcellationRegion/regionMenu/regionMenu.template.html +++ b/src/atlasComponents/parcellationRegion/regionMenu/regionMenu.template.html @@ -108,18 +108,6 @@ </div> </mat-card> -<!-- ToDo make dynamic with AVAILABLE CONNECTIVITY DATASETS data - get info from atlas viewer core --> -<mat-menu - #connectivitySourceDatasets="matMenu" - xPosition="before" - hasBackdrop="false"> - <div> - <button mat-menu-item (mousedown)="showConnectivity(region.name)"> - <span>1000 Brain Study - DTI connectivity</span> - </button> - </div> -</mat-menu> - <!-- template for switching template --> <mat-menu #regionInOtherTemplatesMenu="matMenu" [aria-label]="SHOW_IN_OTHER_REF_SPACE"> diff --git a/src/ui/parcellationRegion/regionSimple/regionSimple.component.ts b/src/atlasComponents/parcellationRegion/regionSimple/regionSimple.component.ts similarity index 100% rename from src/ui/parcellationRegion/regionSimple/regionSimple.component.ts rename to src/atlasComponents/parcellationRegion/regionSimple/regionSimple.component.ts diff --git a/src/ui/parcellationRegion/regionSimple/regionSimple.style.css b/src/atlasComponents/parcellationRegion/regionSimple/regionSimple.style.css similarity index 100% rename from src/ui/parcellationRegion/regionSimple/regionSimple.style.css rename to src/atlasComponents/parcellationRegion/regionSimple/regionSimple.style.css diff --git a/src/ui/parcellationRegion/regionSimple/regionSimple.template.html b/src/atlasComponents/parcellationRegion/regionSimple/regionSimple.template.html similarity index 100% rename from src/ui/parcellationRegion/regionSimple/regionSimple.template.html rename to src/atlasComponents/parcellationRegion/regionSimple/regionSimple.template.html diff --git a/src/ui/regionalFeatures/featureContainer/featureContainer.component.ts b/src/atlasComponents/regionalFeatures/featureContainer/featureContainer.component.ts similarity index 100% rename from src/ui/regionalFeatures/featureContainer/featureContainer.component.ts rename to src/atlasComponents/regionalFeatures/featureContainer/featureContainer.component.ts diff --git a/src/ui/regionalFeatures/index.ts b/src/atlasComponents/regionalFeatures/index.ts similarity index 100% rename from src/ui/regionalFeatures/index.ts rename to src/atlasComponents/regionalFeatures/index.ts diff --git a/src/ui/regionalFeatures/module.ts b/src/atlasComponents/regionalFeatures/module.ts similarity index 94% rename from src/ui/regionalFeatures/module.ts rename to src/atlasComponents/regionalFeatures/module.ts index f90b3a6ee0593d324d30394b8ba746541f8fa2c0..d18d51d9002d2a764c460b1da5a65ffad3ed8e79 100644 --- a/src/ui/regionalFeatures/module.ts +++ b/src/atlasComponents/regionalFeatures/module.ts @@ -1,7 +1,7 @@ import { CommonModule } from "@angular/common"; import { NgModule } from "@angular/core"; import { UtilModule } from "src/util"; -import { AngularMaterialModule } from "../sharedModules/angularMaterial.module"; +import { AngularMaterialModule } from "../../ui/sharedModules/angularMaterial.module"; import { FeatureContainer } from "./featureContainer/featureContainer.component"; import { FilterRegionalFeaturesByTypePipe } from "./pipes/filterRegionalFeaturesByType.pipe"; import { FilterRegionFeaturesById } from "./pipes/filterRegionFeaturesById.pipe"; diff --git a/src/ui/regionalFeatures/pipes/filterRegionFeaturesById.pipe.ts b/src/atlasComponents/regionalFeatures/pipes/filterRegionFeaturesById.pipe.ts similarity index 100% rename from src/ui/regionalFeatures/pipes/filterRegionFeaturesById.pipe.ts rename to src/atlasComponents/regionalFeatures/pipes/filterRegionFeaturesById.pipe.ts diff --git a/src/ui/regionalFeatures/pipes/filterRegionalFeaturesByType.pipe.ts b/src/atlasComponents/regionalFeatures/pipes/filterRegionalFeaturesByType.pipe.ts similarity index 100% rename from src/ui/regionalFeatures/pipes/filterRegionalFeaturesByType.pipe.ts rename to src/atlasComponents/regionalFeatures/pipes/filterRegionalFeaturesByType.pipe.ts diff --git a/src/ui/regionalFeatures/pipes/findRegionFeatureById.pipe.ts b/src/atlasComponents/regionalFeatures/pipes/findRegionFeatureById.pipe.ts similarity index 100% rename from src/ui/regionalFeatures/pipes/findRegionFeatureById.pipe.ts rename to src/atlasComponents/regionalFeatures/pipes/findRegionFeatureById.pipe.ts diff --git a/src/ui/regionalFeatures/regionGetAllFeatures.directive.ts b/src/atlasComponents/regionalFeatures/regionGetAllFeatures.directive.ts similarity index 100% rename from src/ui/regionalFeatures/regionGetAllFeatures.directive.ts rename to src/atlasComponents/regionalFeatures/regionGetAllFeatures.directive.ts diff --git a/src/ui/regionalFeatures/regionalFeature.service.ts b/src/atlasComponents/regionalFeatures/regionalFeature.service.ts similarity index 100% rename from src/ui/regionalFeatures/regionalFeature.service.ts rename to src/atlasComponents/regionalFeatures/regionalFeature.service.ts diff --git a/src/ui/regionalFeatures/singleFeatures/base/regionFeature.base.ts b/src/atlasComponents/regionalFeatures/singleFeatures/base/regionFeature.base.ts similarity index 100% rename from src/ui/regionalFeatures/singleFeatures/base/regionFeature.base.ts rename to src/atlasComponents/regionalFeatures/singleFeatures/base/regionFeature.base.ts diff --git a/src/ui/regionalFeatures/singleFeatures/iEEGRecordings/iEEGRecordings/iEEGRecordings.component.ts b/src/atlasComponents/regionalFeatures/singleFeatures/iEEGRecordings/iEEGRecordings/iEEGRecordings.component.ts similarity index 97% rename from src/ui/regionalFeatures/singleFeatures/iEEGRecordings/iEEGRecordings/iEEGRecordings.component.ts rename to src/atlasComponents/regionalFeatures/singleFeatures/iEEGRecordings/iEEGRecordings/iEEGRecordings.component.ts index 37c0fdc07a32a9d479d8f1fa85c804f8ad6b2f5b..af0dcab8913fa06d2f7ad55f6ffadc309aae69dc 100644 --- a/src/ui/regionalFeatures/singleFeatures/iEEGRecordings/iEEGRecordings/iEEGRecordings.component.ts +++ b/src/atlasComponents/regionalFeatures/singleFeatures/iEEGRecordings/iEEGRecordings/iEEGRecordings.component.ts @@ -3,7 +3,7 @@ import { Store } from "@ngrx/store"; import { merge, Subject, Subscription } from "rxjs"; import { debounceTime, map, scan, take } from "rxjs/operators"; import { viewerStateChangeNavigation } from "src/services/state/viewerState/actions"; -import { RegionalFeaturesService } from "src/ui/regionalFeatures/regionalFeature.service"; +import { RegionalFeaturesService } from "src/atlasComponents/regionalFeatures/regionalFeature.service"; import { ClickInterceptor, CLICK_INTERCEPTOR_INJECTOR } from "src/util"; import { IHasId } from "src/util/interfaces"; import { RegionFeatureBase } from "../../base/regionFeature.base"; diff --git a/src/ui/regionalFeatures/singleFeatures/iEEGRecordings/iEEGRecordings/iEEGRecordings.style.css b/src/atlasComponents/regionalFeatures/singleFeatures/iEEGRecordings/iEEGRecordings/iEEGRecordings.style.css similarity index 100% rename from src/ui/regionalFeatures/singleFeatures/iEEGRecordings/iEEGRecordings/iEEGRecordings.style.css rename to src/atlasComponents/regionalFeatures/singleFeatures/iEEGRecordings/iEEGRecordings/iEEGRecordings.style.css diff --git a/src/ui/regionalFeatures/singleFeatures/iEEGRecordings/iEEGRecordings/iEEGRecordings.template.html b/src/atlasComponents/regionalFeatures/singleFeatures/iEEGRecordings/iEEGRecordings/iEEGRecordings.template.html similarity index 100% rename from src/ui/regionalFeatures/singleFeatures/iEEGRecordings/iEEGRecordings/iEEGRecordings.template.html rename to src/atlasComponents/regionalFeatures/singleFeatures/iEEGRecordings/iEEGRecordings/iEEGRecordings.template.html diff --git a/src/ui/regionalFeatures/singleFeatures/iEEGRecordings/module.ts b/src/atlasComponents/regionalFeatures/singleFeatures/iEEGRecordings/module.ts similarity index 100% rename from src/ui/regionalFeatures/singleFeatures/iEEGRecordings/module.ts rename to src/atlasComponents/regionalFeatures/singleFeatures/iEEGRecordings/module.ts diff --git a/src/ui/regionalFeatures/singleFeatures/interfaces.ts b/src/atlasComponents/regionalFeatures/singleFeatures/interfaces.ts similarity index 100% rename from src/ui/regionalFeatures/singleFeatures/interfaces.ts rename to src/atlasComponents/regionalFeatures/singleFeatures/interfaces.ts diff --git a/src/ui/regionalFeatures/singleFeatures/receptorDensity/filterReceptorBytype.pipe.ts b/src/atlasComponents/regionalFeatures/singleFeatures/receptorDensity/filterReceptorBytype.pipe.ts similarity index 100% rename from src/ui/regionalFeatures/singleFeatures/receptorDensity/filterReceptorBytype.pipe.ts rename to src/atlasComponents/regionalFeatures/singleFeatures/receptorDensity/filterReceptorBytype.pipe.ts diff --git a/src/ui/regionalFeatures/singleFeatures/receptorDensity/getAllReceptors.pipe.ts b/src/atlasComponents/regionalFeatures/singleFeatures/receptorDensity/getAllReceptors.pipe.ts similarity index 100% rename from src/ui/regionalFeatures/singleFeatures/receptorDensity/getAllReceptors.pipe.ts rename to src/atlasComponents/regionalFeatures/singleFeatures/receptorDensity/getAllReceptors.pipe.ts diff --git a/src/ui/regionalFeatures/singleFeatures/receptorDensity/getId.pipe.ts b/src/atlasComponents/regionalFeatures/singleFeatures/receptorDensity/getId.pipe.ts similarity index 100% rename from src/ui/regionalFeatures/singleFeatures/receptorDensity/getId.pipe.ts rename to src/atlasComponents/regionalFeatures/singleFeatures/receptorDensity/getId.pipe.ts diff --git a/src/ui/regionalFeatures/singleFeatures/receptorDensity/getUrl.pipe.ts b/src/atlasComponents/regionalFeatures/singleFeatures/receptorDensity/getUrl.pipe.ts similarity index 100% rename from src/ui/regionalFeatures/singleFeatures/receptorDensity/getUrl.pipe.ts rename to src/atlasComponents/regionalFeatures/singleFeatures/receptorDensity/getUrl.pipe.ts diff --git a/src/ui/regionalFeatures/singleFeatures/receptorDensity/module.ts b/src/atlasComponents/regionalFeatures/singleFeatures/receptorDensity/module.ts similarity index 100% rename from src/ui/regionalFeatures/singleFeatures/receptorDensity/module.ts rename to src/atlasComponents/regionalFeatures/singleFeatures/receptorDensity/module.ts diff --git a/src/ui/regionalFeatures/singleFeatures/receptorDensity/receptorDensity/receptorDensity.component.ts b/src/atlasComponents/regionalFeatures/singleFeatures/receptorDensity/receptorDensity/receptorDensity.component.ts similarity index 95% rename from src/ui/regionalFeatures/singleFeatures/receptorDensity/receptorDensity/receptorDensity.component.ts rename to src/atlasComponents/regionalFeatures/singleFeatures/receptorDensity/receptorDensity/receptorDensity.component.ts index 1ecf5f8d6c8b1f60bcb2520039666fe0b4a3f17d..a9b5f3652f04a53961f3a334f7a951ba36fa5407 100644 --- a/src/ui/regionalFeatures/singleFeatures/receptorDensity/receptorDensity/receptorDensity.component.ts +++ b/src/atlasComponents/regionalFeatures/singleFeatures/receptorDensity/receptorDensity/receptorDensity.component.ts @@ -1,6 +1,6 @@ import { Component, ElementRef, EventEmitter, HostListener, OnDestroy, Optional } from "@angular/core"; import { fromEvent, Observable, of, Subscription } from "rxjs"; -import { RegionalFeaturesService } from "src/ui/regionalFeatures/regionalFeature.service"; +import { RegionalFeaturesService } from "src/atlasComponents/regionalFeatures/regionalFeature.service"; import { PureContantService } from "src/util"; import { RegionFeatureBase } from "../../base/regionFeature.base"; import { ISingleFeature } from "../../interfaces"; diff --git a/src/ui/regionalFeatures/singleFeatures/receptorDensity/receptorDensity/receptorDensity.style.css b/src/atlasComponents/regionalFeatures/singleFeatures/receptorDensity/receptorDensity/receptorDensity.style.css similarity index 100% rename from src/ui/regionalFeatures/singleFeatures/receptorDensity/receptorDensity/receptorDensity.style.css rename to src/atlasComponents/regionalFeatures/singleFeatures/receptorDensity/receptorDensity/receptorDensity.style.css diff --git a/src/ui/regionalFeatures/singleFeatures/receptorDensity/receptorDensity/receptorDensity.template.html b/src/atlasComponents/regionalFeatures/singleFeatures/receptorDensity/receptorDensity/receptorDensity.template.html similarity index 100% rename from src/ui/regionalFeatures/singleFeatures/receptorDensity/receptorDensity/receptorDensity.template.html rename to src/atlasComponents/regionalFeatures/singleFeatures/receptorDensity/receptorDensity/receptorDensity.template.html diff --git a/src/ui/regionalFeatures/util.ts b/src/atlasComponents/regionalFeatures/util.ts similarity index 100% rename from src/ui/regionalFeatures/util.ts rename to src/atlasComponents/regionalFeatures/util.ts diff --git a/src/atlasComponents/splashScreen/index.ts b/src/atlasComponents/splashScreen/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..182552e5a83684d6b8b762e7ad6a5debda9e1d0f --- /dev/null +++ b/src/atlasComponents/splashScreen/index.ts @@ -0,0 +1,2 @@ +export { GetTemplateImageSrcPipe, ImgSrcSetPipe, SplashScreen } from "./splashScreen/splashScreen.component"; +export { SplashUiModule } from './module' \ No newline at end of file diff --git a/src/atlasComponents/splashScreen/module.ts b/src/atlasComponents/splashScreen/module.ts new file mode 100644 index 0000000000000000000000000000000000000000..e5817edb31543ba8c3680bc010129436df4774bf --- /dev/null +++ b/src/atlasComponents/splashScreen/module.ts @@ -0,0 +1,27 @@ +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { ComponentsModule } from "src/components"; +import { KgTosModule } from "src/ui/kgtos/module"; +import { AngularMaterialModule } from "src/ui/sharedModules/angularMaterial.module"; +import { UtilModule } from "src/util"; +import { GetTemplateImageSrcPipe, SplashScreen, ImgSrcSetPipe } from "./splashScreen/splashScreen.component"; + +@NgModule({ + imports: [ + AngularMaterialModule, + CommonModule, + UtilModule, + KgTosModule, + ComponentsModule, + ], + declarations: [ + SplashScreen, + GetTemplateImageSrcPipe, + ImgSrcSetPipe, + ], + exports: [ + SplashScreen, + ] +}) + +export class SplashUiModule{} \ No newline at end of file diff --git a/src/ui/nehubaContainer/splashScreen/splashScreen.component.ts b/src/atlasComponents/splashScreen/splashScreen/splashScreen.component.ts similarity index 100% rename from src/ui/nehubaContainer/splashScreen/splashScreen.component.ts rename to src/atlasComponents/splashScreen/splashScreen/splashScreen.component.ts diff --git a/src/ui/nehubaContainer/splashScreen/splashScreen.style.css b/src/atlasComponents/splashScreen/splashScreen/splashScreen.style.css similarity index 100% rename from src/ui/nehubaContainer/splashScreen/splashScreen.style.css rename to src/atlasComponents/splashScreen/splashScreen/splashScreen.style.css diff --git a/src/ui/nehubaContainer/splashScreen/splashScreen.template.html b/src/atlasComponents/splashScreen/splashScreen/splashScreen.template.html similarity index 100% rename from src/ui/nehubaContainer/splashScreen/splashScreen.template.html rename to src/atlasComponents/splashScreen/splashScreen/splashScreen.template.html diff --git a/src/ui/atlasDropdown/atlasDropdown.component.spec.ts b/src/atlasComponents/uiSelectors/atlasDropdown/atlasDropdown.component.spec.ts similarity index 100% rename from src/ui/atlasDropdown/atlasDropdown.component.spec.ts rename to src/atlasComponents/uiSelectors/atlasDropdown/atlasDropdown.component.spec.ts diff --git a/src/ui/atlasDropdown/atlasDropdown.component.ts b/src/atlasComponents/uiSelectors/atlasDropdown/atlasDropdown.component.ts similarity index 100% rename from src/ui/atlasDropdown/atlasDropdown.component.ts rename to src/atlasComponents/uiSelectors/atlasDropdown/atlasDropdown.component.ts diff --git a/src/ui/atlasDropdown/atlasDropdown.style.css b/src/atlasComponents/uiSelectors/atlasDropdown/atlasDropdown.style.css similarity index 100% rename from src/ui/atlasDropdown/atlasDropdown.style.css rename to src/atlasComponents/uiSelectors/atlasDropdown/atlasDropdown.style.css diff --git a/src/ui/atlasDropdown/atlasDropdown.template.html b/src/atlasComponents/uiSelectors/atlasDropdown/atlasDropdown.template.html similarity index 100% rename from src/ui/atlasDropdown/atlasDropdown.template.html rename to src/atlasComponents/uiSelectors/atlasDropdown/atlasDropdown.template.html diff --git a/src/ui/atlasLayerSelector/atlasLayerSelector.component.ts b/src/atlasComponents/uiSelectors/atlasLayerSelector/atlasLayerSelector.component.ts similarity index 70% rename from src/ui/atlasLayerSelector/atlasLayerSelector.component.ts rename to src/atlasComponents/uiSelectors/atlasLayerSelector/atlasLayerSelector.component.ts index 2ee14f1e192dfa35eee58ac0f168e7f8071c5461..088c98dbefb4b85f4eb19b0f88ff9efcf59cf263 100644 --- a/src/ui/atlasLayerSelector/atlasLayerSelector.component.ts +++ b/src/atlasComponents/uiSelectors/atlasLayerSelector/atlasLayerSelector.component.ts @@ -20,35 +20,94 @@ export class AtlasLayerSelector implements OnInit { @ViewChildren(MatMenuTrigger) matMenuTriggers: QueryList<MatMenuTrigger> public atlas: any - public nonGroupedLayers$: Observable<any[]> - public groupedLayers$: Observable<any[]> + public selectedAtlas$: Observable<any> = this.store$.pipe( + select(viewerStateGetSelectedAtlas), + distinctUntilChanged(), + shareReplay(1) + ) + private layersGroupBy$ = this.selectedAtlas$.pipe( + switchMap(selectedAtlas => from((selectedAtlas?.parcellations) || []).pipe( + /** + * do not show base layers + */ + filter(p => !(p as any).baseLayer), + groupBy((parcellation: any) => parcellation.groupName, p => p), + mergeMap(group => zip( + of(group.key), + group.pipe(toArray())) + ), + scan((acc, curr) => acc.concat([ curr ]), []), + shareReplay(1), + )) + ) + + private atlasLayersLatest$ = this.store$.pipe( + select(viewerStateAtlasLatestParcellationSelector), + shareReplay(1), + ) + + public nonGroupedLayers$: Observable<any[]> = this.atlasLayersLatest$.pipe( + map(allParcellations => + allParcellations + .filter(p => !p['groupName']) + .filter(p => !p['baseLayer']) + ), + ) + + public groupedLayers$: Observable<any[]> = combineLatest([ + this.atlasLayersLatest$.pipe( + map(allParcellations => + allParcellations.filter(p => !p['baseLayer']) + ), + ), + this.layersGroupBy$ + ]).pipe( + map(([ allParcellations, arr]) => arr + .filter(([ key ]) => !!key ) + .map(([key, parcellations]) => ({ + name: key, + previewUrl: parcellations[0].previewUrl, + parcellations: parcellations.map(p => { + const fullInfo = allParcellations.find(fullP => fullP['@id'] === p['@id']) || {} + return { + ...fullInfo, + ...p, + darktheme: (fullInfo || {}).useTheme === 'dark' + } + }) + })) + ), + ) public selectedTemplateSpaceId: string public selectedLayers = [] public selectedTemplate$: Observable<any> private selectedParcellation$: Observable<any> - public selectedAtlas$: Observable<any> + private subscriptions: Subscription[] = [] @HostBinding('attr.data-opened') public selectorExpanded: boolean = false public selectedTemplatePreviewUrl: string = '' - public availableTemplates$: Observable<any[]> + public availableTemplates$ = this.store$.pipe<any[]>( + select(viewerStateSelectedTemplateFullInfoSelector) + ) public containerMaxWidth: number - constructor(private store$: Store<any>) { - this.selectedAtlas$ = this.store$.pipe( - select(viewerStateGetSelectedAtlas), - distinctUntilChanged(), - shareReplay(1) - ) + public shouldShowRenderPlaceHolder$ = combineLatest([ + this.availableTemplates$, + this.groupedLayers$, + this.nonGroupedLayers$, + ]).pipe( + map(([ availTmpl, grpL, ungrpL ]) => { + return availTmpl?.length > 0 || (grpL?.length || 0) + (ungrpL?.length || 0) > 0 + }) + ) - this.availableTemplates$ = this.store$.pipe( - select(viewerStateSelectedTemplateFullInfoSelector) - ) + constructor(private store$: Store<any>) { this.selectedTemplate$ = this.store$.pipe( select(viewerStateSelectedTemplatePureSelector), @@ -64,59 +123,6 @@ export class AtlasLayerSelector implements OnInit { select(viewerStateSelectedParcellationSelector) ) - const layersGroupBy$ = this.selectedAtlas$.pipe( - switchMap(selectedAtlas => from((selectedAtlas?.parcellations) || []).pipe( - /** - * do not show base layers - */ - filter(p => !(p as any).baseLayer), - groupBy((parcellation: any) => parcellation.groupName, p => p), - mergeMap(group => zip( - of(group.key), - group.pipe(toArray())) - ), - scan((acc, curr) => acc.concat([ curr ]), []), - shareReplay(1), - )) - ) - - const atlasLayersLatest$ = this.store$.pipe( - select(viewerStateAtlasLatestParcellationSelector), - shareReplay(1), - ) - - this.nonGroupedLayers$ = atlasLayersLatest$.pipe( - map(allParcellations => - allParcellations - .filter(p => !p['groupName']) - .filter(p => !p['baseLayer']) - ), - ) - - this.groupedLayers$ = combineLatest( - atlasLayersLatest$.pipe( - map(allParcellations => - allParcellations.filter(p => !p['baseLayer']) - ), - ), - layersGroupBy$ - ).pipe( - map(([ allParcellations, arr]) => arr - .filter(([ key ]) => !!key ) - .map(([key, parcellations]) => ({ - name: key, - previewUrl: parcellations[0].previewUrl, - parcellations: parcellations.map(p => { - const fullInfo = allParcellations.find(fullP => fullP['@id'] === p['@id']) || {} - return { - ...fullInfo, - ...p, - darktheme: (fullInfo || {}).useTheme === 'dark' - } - }) - })) - ), - ) } ngOnInit(): void { @@ -183,6 +189,7 @@ export class AtlasLayerSelector implements OnInit { } getTooltipText(layer) { + if (!this.atlas) return if (this.atlas.templateSpaces.map(tmpl => tmpl['@id']).includes(layer['@id'])) return layer.name if (layer.availableIn) { if (this.currentTemplateIncludesLayer(layer)) return layer.name diff --git a/src/ui/atlasLayerSelector/atlasLayerSelector.style.css b/src/atlasComponents/uiSelectors/atlasLayerSelector/atlasLayerSelector.style.css similarity index 100% rename from src/ui/atlasLayerSelector/atlasLayerSelector.style.css rename to src/atlasComponents/uiSelectors/atlasLayerSelector/atlasLayerSelector.style.css diff --git a/src/ui/atlasLayerSelector/atlasLayerSelector.template.html b/src/atlasComponents/uiSelectors/atlasLayerSelector/atlasLayerSelector.template.html similarity index 98% rename from src/ui/atlasLayerSelector/atlasLayerSelector.template.html rename to src/atlasComponents/uiSelectors/atlasLayerSelector/atlasLayerSelector.template.html index a4e8883b6769fce8f473aa2bd9b4ced768425114..d73fda145cd10295d6f6494643a289e723a11066 100644 --- a/src/ui/atlasLayerSelector/atlasLayerSelector.template.html +++ b/src/atlasComponents/uiSelectors/atlasLayerSelector/atlasLayerSelector.template.html @@ -64,7 +64,7 @@ <button color="primary" matTooltip="Select layer" mat-mini-fab - *ngIf="((availableTemplates$ | async).length > 1) || ((groupedLayers$ | async).length + (nonGroupedLayers$ | async).length > 1)" + *ngIf="shouldShowRenderPlaceHolder$ | async" [attr.aria-label]="TOGGLE_ATLAS_LAYER_SELECTOR" (click)="selectorExpanded = !selectorExpanded"> <i class="fas fa-layer-group"></i> diff --git a/src/atlasComponents/uiSelectors/index.ts b/src/atlasComponents/uiSelectors/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..28925e7dac73fcc4f6b8896a1026ec6c556a24e4 --- /dev/null +++ b/src/atlasComponents/uiSelectors/index.ts @@ -0,0 +1,3 @@ +export { AtlasCmpUiSelectorsModule } from "./module" +export { AtlasDropdownSelector } from "./atlasDropdown/atlasDropdown.component" +export { AtlasLayerSelector } from "./atlasLayerSelector/atlasLayerSelector.component" \ No newline at end of file diff --git a/src/atlasComponents/uiSelectors/module.ts b/src/atlasComponents/uiSelectors/module.ts new file mode 100644 index 0000000000000000000000000000000000000000..034f9445353f4e43e0a2f7e8e1b62055eccdb419 --- /dev/null +++ b/src/atlasComponents/uiSelectors/module.ts @@ -0,0 +1,26 @@ +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { AngularMaterialModule } from "src/ui/sharedModules/angularMaterial.module"; +import { UtilModule } from "src/util"; +import { DatabrowserModule } from "src/atlasComponents/databrowserModule"; +import { AtlasDropdownSelector } from "./atlasDropdown/atlasDropdown.component"; +import { AtlasLayerSelector } from "./atlasLayerSelector/atlasLayerSelector.component"; + +@NgModule({ + imports: [ + CommonModule, + AngularMaterialModule, + UtilModule, + DatabrowserModule, + ], + declarations: [ + AtlasDropdownSelector, + AtlasLayerSelector, + ], + exports: [ + AtlasDropdownSelector, + AtlasLayerSelector, + ] +}) + +export class AtlasCmpUiSelectorsModule{} \ No newline at end of file diff --git a/src/atlasViewer/atlasViewer.apiService.service.spec.ts b/src/atlasViewer/atlasViewer.apiService.service.spec.ts index f8dd6f5a8eb71361a90e4dbf07069205808d6235..290bb068a1845621d8f8940d39537d5386b487f6 100644 --- a/src/atlasViewer/atlasViewer.apiService.service.spec.ts +++ b/src/atlasViewer/atlasViewer.apiService.service.spec.ts @@ -5,7 +5,7 @@ import { defaultRootState } from "src/services/stateStore.service"; import { AngularMaterialModule } from "src/ui/sharedModules/angularMaterial.module"; import { WidgetModule } from 'src/widget'; import { HttpClientTestingModule, HttpTestingController } from "@angular/common/http/testing"; -import { PluginServices } from "./pluginUnit"; +import { PluginServices } from "src/plugin"; describe('atlasViewer.apiService.service.ts', () => { diff --git a/src/atlasViewer/atlasViewer.apiService.service.ts b/src/atlasViewer/atlasViewer.apiService.service.ts index 05eba0de7e6c56770513349a68046df851987d77..fed71fcf1bb89b9093b3719aeb7a268094ec0a5a 100644 --- a/src/atlasViewer/atlasViewer.apiService.service.ts +++ b/src/atlasViewer/atlasViewer.apiService.service.ts @@ -13,9 +13,9 @@ import { IavRootStoreInterface, safeFilter } from "src/services/stateStore.service"; -import { FRAGMENT_EMIT_RED } from "src/ui/nehubaContainer/nehubaViewer/nehubaViewer.component"; import { ClickInterceptor, CLICK_INTERCEPTOR_INJECTOR } from "src/util"; -import { IPluginManifest, PluginServices } from "./pluginUnit"; +import { FRAGMENT_EMIT_RED } from "src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component"; +import { IPluginManifest, PluginServices } from "src/plugin"; declare let window @@ -385,39 +385,7 @@ export interface IInteractiveViewerInterface { datasetsBSubject: Observable<any[]> } - viewerHandle?: { - setNavigationLoc: (coordinates: [number, number, number], realSpace?: boolean) => void - moveToNavigationLoc: (coordinates: [number, number, number], realSpace?: boolean) => void - setNavigationOri: (quat: [number, number, number, number]) => void - moveToNavigationOri: (quat: [number, number, number, number]) => void - showSegment: (labelIndex: number) => void - hideSegment: (labelIndex: number) => void - showAllSegments: () => void - hideAllSegments: () => void - - // TODO deprecate - segmentColourMap: Map<number, {red: number, green: number, blue: number}> - - getLayersSegmentColourMap: () => Map<string, Map<number, {red: number, green: number, blue: number}>> - - // TODO deprecate - applyColourMap: (newColourMap: Map<number, {red: number, green: number, blue: number}>) => void - - applyLayersColourMap: (newLayerColourMap: Map<string, Map<number, {red: number, green: number, blue: number}>>) => void - - loadLayer: (layerobj: any) => any - removeLayer: (condition: {name: string | RegExp}) => string[] - setLayerVisibility: (condition: {name: string|RegExp}, visible: boolean) => void - - add3DLandmarks: (landmarks: IUserLandmark[]) => void - remove3DLandmarks: (ids: string[]) => void - - mouseEvent: Observable<{eventName: string, event: MouseEvent}> - mouseOverNehuba: Observable<{labelIndex: number, foundRegion: any | null}> - mouseOverNehubaLayers: Observable<Array<{layer: {name: string}, segment: any | number }>> - mouseOverNehubaUI: Observable<{ segments: any, landmark: any, customLandmark: any }> - getNgHash: () => string - } + viewerHandle?: IVIewerHandle uiHandle: { getModalHandler: () => void @@ -464,8 +432,45 @@ export interface ICustomRegionSpec{ type: string // type of EnumCustomRegion } -export const API_SERVICE_SET_VIEWER_HANDLE_TOKEN = new InjectionToken<(viewerHandle) => void>('API_SERVICE_SET_VIEWER_HANDLE_TOKEN') +export interface IVIewerHandle { + + setNavigationLoc: (coordinates: [number, number, number], realSpace?: boolean) => void + moveToNavigationLoc: (coordinates: [number, number, number], realSpace?: boolean) => void + setNavigationOri: (quat: [number, number, number, number]) => void + moveToNavigationOri: (quat: [number, number, number, number]) => void + showSegment: (labelIndex: number) => void + hideSegment: (labelIndex: number) => void + showAllSegments: () => void + hideAllSegments: () => void + + // TODO deprecate + segmentColourMap: Map<number, {red: number, green: number, blue: number}> + + getLayersSegmentColourMap: () => Map<string, Map<number, {red: number, green: number, blue: number}>> + + // TODO deprecate + applyColourMap: (newColourMap: Map<number, {red: number, green: number, blue: number}>) => void + + applyLayersColourMap: (newLayerColourMap: Map<string, Map<number, {red: number, green: number, blue: number}>>) => void + + loadLayer: (layerobj: any) => any + removeLayer: (condition: {name: string | RegExp}) => string[] + setLayerVisibility: (condition: {name: string|RegExp}, visible: boolean) => void + + add3DLandmarks: (landmarks: IUserLandmark[]) => void + remove3DLandmarks: (ids: string[]) => void + + mouseEvent: Observable<{eventName: string, event: MouseEvent}> + mouseOverNehuba: Observable<{labelIndex: number, foundRegion: any | null}> + mouseOverNehubaLayers: Observable<Array<{layer: {name: string}, segment: any | number }>> + mouseOverNehubaUI: Observable<{ segments: any, landmark: any, customLandmark: any }> + getNgHash: () => string +} + +export type TSetViewerHandle = (viewerHandle: IVIewerHandle) => void + +export const API_SERVICE_SET_VIEWER_HANDLE_TOKEN = new InjectionToken<TSetViewerHandle>('API_SERVICE_SET_VIEWER_HANDLE_TOKEN') export const setViewerHandleFactory = (apiService: AtlasViewerAPIServices) => { - return viewerHandle => apiService.interactiveViewer.viewerHandle = viewerHandle + return (viewerHandle: IVIewerHandle) => apiService.interactiveViewer.viewerHandle = viewerHandle } diff --git a/src/atlasViewer/atlasViewer.component.ts b/src/atlasViewer/atlasViewer.component.ts index 8f1410127afa88217bd84cb22b2708121a4c795f..0c17df576656ab199a6fb6f820c9a6132eb06d23 100644 --- a/src/atlasViewer/atlasViewer.component.ts +++ b/src/atlasViewer/atlasViewer.component.ts @@ -8,12 +8,9 @@ import { TemplateRef, ViewChild, ElementRef, - Inject, - Optional, - InjectionToken, } from "@angular/core"; import { Store, select, ActionsSubject } from "@ngrx/store"; -import { Observable, Subscription, combineLatest, interval, merge, of, timer, fromEvent } from "rxjs"; +import { Observable, Subscription, interval, merge, of, timer, fromEvent } from "rxjs"; import { map, filter, distinctUntilChanged, delay, withLatestFrom, switchMapTo, take, startWith } from "rxjs/operators"; import { LayoutMainSide } from "../layouts/mainside/mainside.component"; @@ -28,9 +25,9 @@ import { WidgetServices } from "src/widget"; import { LocalFileService } from "src/services/localFile.service"; import { AGREE_COOKIE, AGREE_KG_TOS, SHOW_KG_TOS } from "src/services/state/uiState.store"; import { isSame } from "src/util/fn"; -import { NehubaContainer } from "../ui/nehubaContainer/nehubaContainer.component"; +// import { NehubaContainer } from "../ui/nehubaContainer/nehubaContainer.component"; import { colorAnimation } from "./atlasViewer.animation" -import { MouseHoverDirective } from "src/atlasViewer/mouseOver.directive"; +import { MouseHoverDirective } from "src/mouseoverModule"; import {MatSnackBar, MatSnackBarRef} from "@angular/material/snack-bar"; import {MatDialog, MatDialogRef} from "@angular/material/dialog"; import { ARIA_LABELS, CONST } from 'common/constants' @@ -38,13 +35,8 @@ import { ARIA_LABELS, CONST } from 'common/constants' import { MIN_REQ_EXPLAINER } from 'src/util/constants' import { SlServiceService } from "src/spotlight/sl-service.service"; import { PureContantService } from "src/util"; -import { viewerStateSetSelectedRegions, viewerStateRemoveAdditionalLayer, viewerStateHelperSelectParcellationWithId } from "src/services/state/viewerState.store.helper"; -import { viewerStateGetOverlayingAdditionalParcellations, viewerStateParcVersionSelector, viewerStateStandAloneVolumes } from "src/services/state/viewerState/selectors"; -import { ngViewerSelectorClearViewEntries } from "src/services/state/ngViewerState/selectors"; -import { ngViewerActionClearView } from "src/services/state/ngViewerState/actions"; import { uiStateMouseOverSegmentsSelector } from "src/services/state/uiState/selectors"; import { ClickInterceptorService } from "src/glue"; -import {SET_OVERWRITTEN_COLOR_MAP} from "src/services/state/viewerState.store"; /** * TODO @@ -75,7 +67,7 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit { @ViewChild('kgToS', {read: TemplateRef}) public kgTosComponent: TemplateRef<any> @ViewChild(LayoutMainSide) public layoutMainSide: LayoutMainSide - @ViewChild(NehubaContainer) public nehubaContainer: NehubaContainer + // @ViewChild(NehubaContainer) public nehubaContainer: NehubaContainer @ViewChild(MouseHoverDirective) private mouseOverNehuba: MouseHoverDirective @@ -88,9 +80,6 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit { public sidePanelView$: Observable<string|null> private newViewer$: Observable<any> - public selectedRegions$: Observable<any[]> - public selectedPOI$: Observable<any[]> - private snackbarRef: MatSnackBarRef<any> public snackbarMessage$: Observable<string> @@ -109,35 +98,12 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit { public MIN_REQ_EXPLAINER = MIN_REQ_EXPLAINER - public isStandaloneVolumes$ = this.store.pipe( - select(viewerStateStandAloneVolumes), - map(v => v.length > 0) - ) - - public selectedAdditionalLayers$ = this.store.pipe( - select(viewerStateGetOverlayingAdditionalParcellations), - ) - - public selectedLayerVersions$ = this.store.pipe( - select(viewerStateParcVersionSelector), - map(arr => arr.map(item => { - const overwrittenName = item['@version'] && item['@version']['name'] - return overwrittenName - ? { ...item, displayName: overwrittenName } - : item - })) - ) - private selectedParcellation$: Observable<any> public selectedParcellation: any private cookieDialogRef: MatDialogRef<any> private kgTosDialogRef: MatDialogRef<any> - public clearViewKeys$ = this.store.pipe( - select(ngViewerSelectorClearViewEntries) - ) - constructor( private store: Store<IavRootStoreInterface>, private widgetServices: WidgetServices, @@ -164,25 +130,6 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit { map(state => state.focusedSidePanel), ) - this.selectedRegions$ = this.store.pipe( - select('viewerState'), - filter(state => isDefined(state) && isDefined(state.regionsSelected)), - map(state => state.regionsSelected), - distinctUntilChanged(), - ) - - this.selectedPOI$ = combineLatest( - this.selectedRegions$, - this.store.pipe( - select('viewerState'), - filter(state => isDefined(state) && isDefined(state.landmarksSelected)), - map(state => state.landmarksSelected), - distinctUntilChanged(), - ), - ).pipe( - map(results => [...results[0], ...results[1]]), - ) - this.newViewer$ = this.store.pipe( select('viewerState'), select('templateSelected'), @@ -249,7 +196,6 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit { public ngOnInit() { this.meetsRequirement = this.meetsRequirements() - this.clickIntService.addInterceptor(this.selectHoveredRegion.bind(this), true) if (KIOSK_MODE) { @@ -351,9 +297,9 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit { this.rd.appendChild(document.head, prefecthMainBundle) } - this.onhoverLandmark$ = this.mouseOverNehuba.currentOnHoverObs$.pipe( - select('landmark') - ) + // this.onhoverLandmark$ = this.mouseOverNehuba.currentOnHoverObs$.pipe( + // select('landmark') + // ) /** * Show Cookie disclaimer if not yet agreed @@ -389,58 +335,6 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit { */ public ngOnDestroy() { this.subscriptions.forEach(s => s.unsubscribe()) - this.clickIntService.removeInterceptor(this.selectHoveredRegion.bind(this)) - } - - private selectHoveredRegion(ev: any, next: Function){ - if (!this.onhoverSegments) return - - this.store.dispatch( - viewerStateSetSelectedRegions({ - selectRegions: this.onhoverSegments.slice(0, 1) - }) - ) - next() - } - - public unsetClearViewByKey(key: string){ - this.store.dispatch( - ngViewerActionClearView({ payload: { - [key]: false - }}) - ) - } - - public selectParcellation(parc: any) { - this.store.dispatch( - viewerStateHelperSelectParcellationWithId({ - payload: parc - }) - ) - } - - public bindFns(fns){ - return () => { - for (const [ fn, ...arg] of fns) { - fn(...arg) - } - } - } - - public clearAdditionalLayer(layer: { ['@id']: string }){ - this.store.dispatch( - viewerStateRemoveAdditionalLayer({ - payload: layer - }) - ) - } - - public clearSelectedRegions(){ - this.store.dispatch( - viewerStateSetSelectedRegions({ - selectRegions: [] - }) - ) } public mouseClickDocument(_event: MouseEvent) { diff --git a/src/atlasViewer/atlasViewer.constantService.service.spec.ts b/src/atlasViewer/atlasViewer.constantService.service.spec.ts index e9fe3faeeb0dae335c04d2e0b28d840966b87d5a..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 --- a/src/atlasViewer/atlasViewer.constantService.service.spec.ts +++ b/src/atlasViewer/atlasViewer.constantService.service.spec.ts @@ -1,128 +0,0 @@ -import {} from 'jasmine' -import { decodeToNumber, encodeNumber } from './atlasViewer.constantService.service' - -const FLOAT_PRECISION = 6 - -describe('encodeNumber/decodeToNumber', () => { - - const getCompareOriginal = (original: number[]) => (element: string, index: number) => - original[index].toString().length >= element.length - - const lengthShortened = (original: number[], encodedString: string[]) => - encodedString.every(getCompareOriginal(original)) - - it('should encode/decode positive integer as expected', () => { - - const positiveInt = [ - 0, - 1, - 99999999999, - 12347, - ] - - const encodedString = positiveInt.map(n => encodeNumber(n)) - const decodedString = encodedString.map(s => decodeToNumber(s)) - expect(decodedString).toEqual(positiveInt) - - expect(lengthShortened(positiveInt, encodedString)).toBe(true) - }) - - it('should encode/decode ANY positive integer as expected', () => { - const posInt = Array(1000).fill(null).map(() => { - const numDig = Math.ceil(Math.random() * 7) - return Math.floor(Math.random() * Math.pow(10, numDig)) - }) - const encodedString = posInt.map(n => encodeNumber(n)) - const decodedNumber = encodedString.map(s => decodeToNumber(s)) - expect(decodedNumber).toEqual(posInt) - - expect(lengthShortened(posInt, encodedString)).toBe(true) - }) - - it('should encode/decode signed integer as expected', () => { - - const signedInt = [ - 0, - -0, - -1, - 1, - 128, - -54, - ] - - const encodedString = signedInt.map(n => encodeNumber(n)) - const decodedNumber = encodedString.map(s => decodeToNumber(s)) - - /** - * -0 will be converted to 0 by the encode/decode process, but does not deep equal, according to jasmine - */ - expect(decodedNumber).toEqual(signedInt.map(v => v === 0 ? 0 : v)) - - expect(lengthShortened(signedInt, encodedString)).toBe(true) - }) - - it('should encode/decode ANY signed integer as expected', () => { - - const signedInt = Array(1000).fill(null).map(() => { - const numDig = Math.ceil(Math.random() * 7) - return Math.floor(Math.random() * Math.pow(10, numDig)) * (Math.random() > 0.5 ? 1 : -1) - }) - const encodedString = signedInt.map(n => encodeNumber(n)) - const decodedNumber = encodedString.map(s => decodeToNumber(s)) - - /** - * -0 will be converted to 0 by the encode/decode process, but does not deep equal, according to jasmine - */ - expect(decodedNumber).toEqual(signedInt.map(v => v === 0 ? 0 : v)) - - expect(lengthShortened(signedInt, encodedString)).toBe(true) - }) - - it('should encode/decode float as expected', () => { - const floatNum = [ - 0.111, - 12.23, - 1723.0, - ] - - const encodedString = floatNum.map(f => encodeNumber(f, { float: true })) - const decodedNumber = encodedString.map(s => decodeToNumber(s, { float: true })) - expect(decodedNumber.map(n => n.toFixed(FLOAT_PRECISION))).toEqual(floatNum.map(n => n.toFixed(FLOAT_PRECISION))) - }) - - it('should encode/decode ANY float as expected', () => { - const floatNums = Array(1000).fill(null).map(() => { - const numDig = Math.ceil(Math.random() * 7) - return (Math.random() > 0.5 ? 1 : -1) * Math.floor( - Math.random() * Math.pow(10, numDig), - ) - }) - - const encodedString = floatNums.map(f => encodeNumber(f, { float: true })) - const decodedNumber = encodedString.map(s => decodeToNumber(s, { float: true })) - - expect(floatNums.map(v => v.toFixed(FLOAT_PRECISION))).toEqual(decodedNumber.map(n => n.toFixed(FLOAT_PRECISION))) - }) - - it('poisoned hash should throw', () => { - const illegialCharacters = './\\?#!@#^%&*()+={}[]\'"\n\t;:' - for (const char of illegialCharacters.split('')) { - expect(() => { - decodeToNumber(char) - }).toThrow() - } - }) - - it('poisoned hash can be caught', () => { - - const testArray = ['abc', './\\', 'Cde'] - const decodedNum = testArray.map(v => { - try { - return decodeToNumber(v) - } catch (e) { - return null - } - }).filter(v => !!v) - expect(decodedNum.length).toEqual(2) - }) -}) diff --git a/src/atlasViewer/atlasViewer.constantService.service.ts b/src/atlasViewer/atlasViewer.constantService.service.ts index b6e8358f72470566bf4350d61a78860038edf30c..c0ca5d31ccc0075d008fa626b0e884941ee60230 100644 --- a/src/atlasViewer/atlasViewer.constantService.service.ts +++ b/src/atlasViewer/atlasViewer.constantService.service.ts @@ -257,103 +257,3 @@ export const UNSUPPORTED_PREVIEW = [{ export const UNSUPPORTED_INTERVAL = 7000 - -/** - * First attempt at encoding int (e.g. selected region, navigation location) from number (loc info density) to b64 (higher info density) - * The constraint is that the cipher needs to be commpatible with URI encoding - * and a URI compatible separator is required. - * - * The implementation below came from - * https://stackoverflow.com/a/6573119/6059235 - * - * While a faster solution exist in the same post, this operation is expected to be done: - * - once per 1 sec frequency - * - on < 1000 numbers - * - * So performance is not really that important (Also, need to learn bitwise operation) - */ - -const cipher = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_-' -export const separator = "." -const negString = '~' - -const encodeInt = (number: number) => { - if (number % 1 !== 0) { throw new Error('cannot encodeInt on a float. Ensure float flag is set') } - if (isNaN(Number(number)) || number === null || number === Number.POSITIVE_INFINITY) { throw new Error('The input is not valid') } - - let rixit // like 'digit', only in some non-decimal radix - let residual - let result = '' - - if (number < 0) { - result += negString - residual = Math.floor(number * -1) - } else { - residual = Math.floor(number) - } - - /* eslint-disable-next-line no-constant-condition */ - while (true) { - rixit = residual % 64 - // this.log.log("rixit : " + rixit) - // this.log.log("result before : " + result) - result = cipher.charAt(rixit) + result - // this.log.log("result after : " + result) - // this.log.log("residual before : " + residual) - residual = Math.floor(residual / 64) - // this.log.log("residual after : " + residual) - - if (residual === 0) { - break; - } - } - return result -} - -interface IB64EncodingOption { - float: boolean -} - -const defaultB64EncodingOption = { - float: false, -} - -export const encodeNumber: - (number: number, option?: IB64EncodingOption) => string = - (number: number, { float = false }: IB64EncodingOption = defaultB64EncodingOption) => { - if (!float) { return encodeInt(number) } else { - const floatArray = new Float32Array(1) - floatArray[0] = number - const intArray = new Uint32Array(floatArray.buffer) - const castedInt = intArray[0] - return encodeInt(castedInt) - } - } - -const decodetoInt = (encodedString: string) => { - let _encodedString - let negFlag = false - if (encodedString.slice(-1) === negString) { - negFlag = true - _encodedString = encodedString.slice(0, -1) - } else { - _encodedString = encodedString - } - return (negFlag ? -1 : 1) * [..._encodedString].reduce((acc, curr) => { - const index = cipher.indexOf(curr) - if (index < 0) { throw new Error(`Poisoned b64 encoding ${encodedString}`) } - return acc * 64 + index - }, 0) -} - -export const decodeToNumber: - (encodedString: string, option?: IB64EncodingOption) => number = - (encodedString: string, {float = false} = defaultB64EncodingOption) => { - if (!float) { return decodetoInt(encodedString) } else { - const _int = decodetoInt(encodedString) - const intArray = new Uint32Array(1) - intArray[0] = _int - const castedFloat = new Float32Array(intArray.buffer) - return castedFloat[0] - } - } diff --git a/src/atlasViewer/atlasViewer.style.css b/src/atlasViewer/atlasViewer.style.css index c5024d44227a8cb0e0594637e583f392418efcbb..1a6915506e6049e60f33fca36781b9fa6e29ba80 100644 --- a/src/atlasViewer/atlasViewer.style.css +++ b/src/atlasViewer/atlasViewer.style.css @@ -68,7 +68,6 @@ mat-sidenav { box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23); } -landmark-ui, region-menu { display:inline-block; diff --git a/src/atlasViewer/atlasViewer.template.html b/src/atlasViewer/atlasViewer.template.html index a45ee29bc896c9957348da909d03410244d579b8..fcf8a02d79b6cdbec9f10fbc29572426a9f566fe 100644 --- a/src/atlasViewer/atlasViewer.template.html +++ b/src/atlasViewer/atlasViewer.template.html @@ -40,312 +40,17 @@ <!-- atlas template --> <ng-template #viewerBody> - <div class="atlas-container" + <div class="atlas-container w-100 h-100" iav-media-query #media="iavMediaQuery"> <!-- prevent default is required so that user do not zoom in on UI or scroll on mobile UI --> - <ui-nehuba-container - class="z-index-10" - #uiNehubaContainer="uiNehubaContainer" - iav-mouse-hover - #iavMouseHoverEl="iavMouseHover" - [currentOnHoverObs$]="iavMouseHoverEl.currentOnHoverObs$" - [currentOnHover]="iavMouseHoverEl.currentOnHoverObs$ | async" + <iav-cmp-viewer-container + class="w-100 h-100 d-block" + [ismobile]="(media.mediaBreakPoint$ | async) > 3" iav-captureClickListenerDirective [iav-captureClickListenerDirective-captureDocument]="true" - (iav-captureClickListenerDirective-onUnmovedClick)="mouseClickDocument($event)" - (drag-drop)="localFileService.handleFileDrop($event)"> - - <!-- top right content transclusion --> - <div ui-nehuba-container-overlay-top-right class="d-inline-flex flex-row justify-content-end align-items-start z-index-6 position-absolute pe-none w-100 h-100"> - - <signin-banner - class="mt-3 mr-2" - [parcellationIsSelected]="!!selectedParcellation" - [ismobile]="(media.mediaBreakPoint$ | async) > 3"> - </signin-banner> - - <!-- atlas selector --> - <div *ngIf="uiNehubaContainer.viewerLoaded" - class="iv-custom-comp bg card m-2 mat-elevation-z2"> - <atlas-dropdown-selector class="pe-all mt-2"> - </atlas-dropdown-selector> - </div> - - </div> - - <!-- bottom left content transclusion --> - <div ui-nehuba-container-overlay-bottom-left class="d-inline-flex pe-none w-100 align-items-end m-2 mb-4"> - - <!-- only load atlas layer selector and chips if viewer is loaded --> - <ng-template [ngIf]="uiNehubaContainer.viewerLoaded && !(isStandaloneVolumes$ | async)"> - - <!-- Viewer Selector Container--> - <atlas-layer-selector - #alSelector="atlasLayerSelector" - class="pe-all" - (iav-outsideClick)="alSelector.selectorExpanded = false"> - </atlas-layer-selector> - <mat-chip-list class="mb-2"> - <!-- additional layer --> - - <ng-container> - <ng-container *ngTemplateOutlet="currParcellationTmpl; context: { addParc: (selectedAdditionalLayers$ | async), parc: selectedParcellation }"> - </ng-container> - </ng-container> - - <!-- any selected region(s) --> - <ng-container> - <ng-container *ngTemplateOutlet="selectedRegionTmpl"> - </ng-container> - </ng-container> - - <!-- controls for iav volumes --> - <div class="hidden" iav-shown-previews #previews="iavShownPreviews"></div> - <ng-container *ngTemplateOutlet="selectedDatasetPreview; context: { layers: previews.iavAdditionalLayers$ | async | filterPreviewByType : [previews.FILETYPES.VOLUMES] }"> - </ng-container> - - </mat-chip-list> - - <!-- current layer tmpl --> - - <ng-template #currParcellationTmpl let-parc="parc" let-addParc="addParc"> - - <div [matMenuTriggerFor]="layerVersionMenu" - [matMenuTriggerData]="{ layerVersionMenuTrigger: layerVersionMenuTrigger }" - #layerVersionMenuTrigger="matMenuTrigger"> - - <ng-template [ngIf]="addParc.length > 0" [ngIfElse]="defaultParcTmpl"> - <ng-container *ngFor="let p of addParc"> - <ng-container *ngTemplateOutlet="chipTmpl; context: { - parcel: p, - selected: true, - dismissable: true, - onclick: layerVersionMenuTrigger.toggleMenu.bind(layerVersionMenuTrigger) - }"> - </ng-container> - </ng-container> - </ng-template> - <ng-template #defaultParcTmpl> - <ng-container *ngTemplateOutlet="chipTmpl; context: { - parcel: parc, - selected: false, - dismissable: false, - onclick: layerVersionMenuTrigger.toggleMenu.bind(layerVersionMenuTrigger) - }"> - </ng-container> - </ng-template> - </div> - </ng-template> - - <!-- render parc templ --> - <ng-template #chipTmpl - let-parcel="parcel" - let-selected="selected" - let-dismissable="dismissable" - let-chipClass="class" - let-onclick="onclick"> - <mat-chip class="pe-all position-relative z-index-2 d-inline-flex justify-content-between" - [ngClass]="chipClass" - (click)="onclick && onclick()" - [selected]="selected"> - - <span> - {{ parcel?.groupName ? (parcel?.groupName + ' - ') : '' }}{{ parcel && (parcel.displayName || parcel.name) }} - </span> - - <!-- info icon --> - <ng-template [ngIf]="parcel?.originDatasets?.length > 0" [ngIfElse]="infoIconBasic"> - - <mat-icon - *ngFor="let ds of parcel.originDatasets" - fontSet="fas" - fontIcon="fa-info-circle" - iav-stop="click" - iav-dataset-show-dataset-dialog - [iav-dataset-show-dataset-dialog-kgid]="ds['kgId']" - [iav-dataset-show-dataset-dialog-kgschema]="ds['kgSchema']" - [iav-dataset-show-dataset-dialog-name]="parcel?.properties?.name" - [iav-dataset-show-dataset-dialog-description]="parcel?.properties?.description"> - </mat-icon> - - </ng-template> - - <ng-template #infoIconBasic> - <mat-icon *ngIf="parcel?.properties?.name && parcel?.properties?.description" - fontSet="fas" - fontIcon="fa-info-circle" - iav-stop="click" - iav-dataset-show-dataset-dialog - [iav-dataset-show-dataset-dialog-name]="parcel.properties.name" - [iav-dataset-show-dataset-dialog-description]="parcel.properties.description"> - - </mat-icon> - </ng-template> - - <!-- dismiss icon --> - <mat-icon - *ngIf="dismissable" - (click)="clearAdditionalLayer(parcel); $event.stopPropagation()" - fontSet="fas" - fontIcon="fa-times"> - </mat-icon> - </mat-chip> - </ng-template> - - <!-- layer version selector --> - <mat-menu #layerVersionMenu - class="bg-none box-shadow-none" - [hasBackdrop]="false"> - <ng-template matMenuContent let-layerVersionMenuTrigger="layerVersionMenuTrigger"> - <div (iav-outsideClick)="layerVersionMenuTrigger.closeMenu()"> - <ng-container *ngFor="let parcVer of selectedLayerVersions$ | async"> - <ng-container *ngTemplateOutlet="chipTmpl; context: { - parcel: parcVer, - selected: selectedParcellation && selectedParcellation['@id'] === parcVer['@id'], - dismissable: false, - class: 'w-100', - onclick: bindFns([ - [ selectParcellation.bind(this), parcVer ], - [ layerVersionMenuTrigger.closeMenu.bind(layerVersionMenuTrigger) ] - ]) - }"> - </ng-container> - <div class="mt-1"></div> - </ng-container> - </div> - </ng-template> - </mat-menu> - - <ng-template #selectedRegionTmpl> - - <!-- regions chip --> - <ng-template [ngIf]="selectedRegions$ | async" let-selectedRegions="ngIf"> - <!-- if regions.length > 1 --> - <!-- use group chip --> - <ng-template [ngIf]="selectedRegions.length > 1" [ngIfElse]="singleRegionTmpl"> - <mat-chip - color="primary" - selected - (click)="uiNehubaContainer.matDrawerMinor.open() && uiNehubaContainer.navSideDrawerMainSwitch.open()" - class="pe-all position-relative z-index-1 ml-8-n"> - <span class="iv-custom-comp text text-truncate d-inline pl-4"> - {{ CONST.MULTI_REGION_SELECTION }} - </span> - <mat-icon - (click)="clearSelectedRegions()" - fontSet="fas" - iav-stop="click" - fontIcon="fa-times"> - </mat-icon> - </mat-chip> - </ng-template> - - <!-- if reginos.lengt === 1 --> - <!-- use single region chip --> - <ng-template #singleRegionTmpl> - <ng-container *ngFor="let r of selectedRegions"> - - <!-- region chip for discrete map --> - <mat-chip - iav-region - (click)="uiNehubaContainer.matDrawerMinor.open() && uiNehubaContainer.navSideDrawerMainSwitch.open()" - [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" - #regionDirective="iavRegion"> - <span class="iv-custom-comp text text-truncate d-inline 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> - - <!-- chips for previewing origin datasets/continous map --> - <ng-container *ngFor="let originDataset of (r.originDatasets || []); let index = index"> - <div class="hidden" - iav-dataset-preview-dataset-file - [iav-dataset-preview-dataset-file-kgid]="originDataset.kgId" - [iav-dataset-preview-dataset-file-filename]="originDataset.filename" - #previewDirective="iavDatasetPreviewDatasetFile"> - </div> - <mat-chip *ngIf="previewDirective.active" - (click)="uiNehubaContainer.matDrawerMinor.open() && uiNehubaContainer.navSideDrawerMainSwitch.open()" - class="pe-all position-relative ml-8-n"> - <span class="pl-4"> - {{ regionDirective.regionOriginDatasetLabels$ | async | renderViewOriginDatasetlabel : index }} - </span> - <mat-icon (click)="previewDirective.onClick()" - fontSet="fas" - iav-stop="click" - fontIcon="fa-times"> - </mat-icon> - </mat-chip> - - <mat-chip *ngFor="let key of clearViewKeys$ | async" - (click)="uiNehubaContainer.matDrawerMinor.open() && uiNehubaContainer.navSideDrawerMainSwitch.open()" - class="pe-all position-relative ml-8-n"> - <span class="pl-4"> - {{ key }} - </span> - <mat-icon (click)="unsetClearViewByKey(key)" - fontSet="fas" - iav-stop="click" - fontIcon="fa-times"> - - </mat-icon> - </mat-chip> - </ng-container> - - </ng-container> - </ng-template> - </ng-template> - - </ng-template> - - <ng-template #selectedDatasetPreview let-layers="layers"> - - <ng-container *ngFor="let layer of layers"> - <div class="hidden" - iav-dataset-preview-dataset-file - [iav-dataset-preview-dataset-file-kgid]="layer.datasetId" - [iav-dataset-preview-dataset-file-filename]="layer.filename" - #preview="iavDatasetPreviewDatasetFile"> - - </div> - <mat-chip class="pe-all" - (click)="uiNehubaContainer.matDrawerMinor.open() && uiNehubaContainer.navSideDrawerMainSwitch.open()"> - {{ layer.file?.name || layer.filename || 'Unknown data preview' }} - <mat-icon fontSet="fas" fontIcon="fa-times" - (click)="preview.onClick()" - iav-stop="click"> - </mat-icon> - </mat-chip> - </ng-container> - </ng-template> - - </ng-template> - </div> - - <!-- top left content transclusion --> - <div ui-nehuba-container-overlay-top-left class="d-inline-flex pe-none w-100 align-items-start m-2"> - <ui-status-card - *ngIf="uiNehubaContainer.viewerLoaded" - class="pe-all muted-7" - [selectedTemplateName]="uiNehubaContainer?.selectedTemplate?.name" - [nehubaViewer]="uiNehubaContainer?.nehubaViewer"> - </ui-status-card> - </div> - </ui-nehuba-container> + (iav-captureClickListenerDirective-onUnmovedClick)="mouseClickDocument($event)"> + </iav-cmp-viewer-container> <layout-floating-container zIndex="13" diff --git a/src/atlasViewer/atlasViewer.urlUtil.spec.ts b/src/atlasViewer/atlasViewer.urlUtil.spec.ts index c018f8718488fad6cc61f7508bda4cca3bae8332..bc47b72d3c868418c2563489d3ec9bf89e99d59f 100644 --- a/src/atlasViewer/atlasViewer.urlUtil.spec.ts +++ b/src/atlasViewer/atlasViewer.urlUtil.spec.ts @@ -2,7 +2,7 @@ import {} from 'jasmine' import { defaultRootState } from 'src/services/stateStore.service' -import { cvtSearchParamToState, cvtStateToSearchParam } from './atlasViewer.urlUtil' +import { cvtSearchParamToState, cvtStateToSearchParam, decodeToNumber, encodeNumber } from './atlasViewer.urlUtil' const bigbrainJson = require('!json-loader!src/res/ext/bigbrain.json') const colin = require('!json-loader!src/res/ext/colin.json') @@ -207,4 +207,131 @@ describe('atlasViewer.urlUtil.ts', () => { expect(stringified).toBe('templateSelected=Big+Brain+%28Histology%29&parcellationSelected=Cytoarchitectonic+Maps+-+v2.4') }) }) + + const FLOAT_PRECISION = 6 + + describe('encodeNumber/decodeToNumber', () => { + + const getCompareOriginal = (original: number[]) => (element: string, index: number) => + original[index].toString().length >= element.length + + const lengthShortened = (original: number[], encodedString: string[]) => + encodedString.every(getCompareOriginal(original)) + + it('should encode/decode positive integer as expected', () => { + + const positiveInt = [ + 0, + 1, + 99999999999, + 12347, + ] + + const encodedString = positiveInt.map(n => encodeNumber(n)) + const decodedString = encodedString.map(s => decodeToNumber(s)) + expect(decodedString).toEqual(positiveInt) + + expect(lengthShortened(positiveInt, encodedString)).toBe(true) + }) + + it('should encode/decode ANY positive integer as expected', () => { + const posInt = Array(1000).fill(null).map(() => { + const numDig = Math.ceil(Math.random() * 7) + return Math.floor(Math.random() * Math.pow(10, numDig)) + }) + const encodedString = posInt.map(n => encodeNumber(n)) + const decodedNumber = encodedString.map(s => decodeToNumber(s)) + expect(decodedNumber).toEqual(posInt) + + expect(lengthShortened(posInt, encodedString)).toBe(true) + }) + + it('should encode/decode signed integer as expected', () => { + + const signedInt = [ + 0, + -0, + -1, + 1, + 128, + -54, + ] + + const encodedString = signedInt.map(n => encodeNumber(n)) + const decodedNumber = encodedString.map(s => decodeToNumber(s)) + + /** + * -0 will be converted to 0 by the encode/decode process, but does not deep equal, according to jasmine + */ + expect(decodedNumber).toEqual(signedInt.map(v => v === 0 ? 0 : v)) + + expect(lengthShortened(signedInt, encodedString)).toBe(true) + }) + + it('should encode/decode ANY signed integer as expected', () => { + + const signedInt = Array(1000).fill(null).map(() => { + const numDig = Math.ceil(Math.random() * 7) + return Math.floor(Math.random() * Math.pow(10, numDig)) * (Math.random() > 0.5 ? 1 : -1) + }) + const encodedString = signedInt.map(n => encodeNumber(n)) + const decodedNumber = encodedString.map(s => decodeToNumber(s)) + + /** + * -0 will be converted to 0 by the encode/decode process, but does not deep equal, according to jasmine + */ + expect(decodedNumber).toEqual(signedInt.map(v => v === 0 ? 0 : v)) + + expect(lengthShortened(signedInt, encodedString)).toBe(true) + }) + + it('should encode/decode float as expected', () => { + const floatNum = [ + 0.111, + 12.23, + 1723.0, + ] + + const encodedString = floatNum.map(f => encodeNumber(f, { float: true })) + const decodedNumber = encodedString.map(s => decodeToNumber(s, { float: true })) + expect(decodedNumber.map(n => n.toFixed(FLOAT_PRECISION))).toEqual(floatNum.map(n => n.toFixed(FLOAT_PRECISION))) + }) + + it('should encode/decode ANY float as expected', () => { + const floatNums = Array(1000).fill(null).map(() => { + const numDig = Math.ceil(Math.random() * 7) + return (Math.random() > 0.5 ? 1 : -1) * Math.floor( + Math.random() * Math.pow(10, numDig), + ) + }) + + const encodedString = floatNums.map(f => encodeNumber(f, { float: true })) + const decodedNumber = encodedString.map(s => decodeToNumber(s, { float: true })) + + expect(floatNums.map(v => v.toFixed(FLOAT_PRECISION))).toEqual(decodedNumber.map(n => n.toFixed(FLOAT_PRECISION))) + }) + + it('poisoned hash should throw', () => { + const illegialCharacters = './\\?#!@#^%&*()+={}[]\'"\n\t;:' + for (const char of illegialCharacters.split('')) { + expect(() => { + decodeToNumber(char) + }).toThrow() + } + }) + + it('poisoned hash can be caught', () => { + + const testArray = ['abc', './\\', 'Cde'] + const decodedNum = testArray.map(v => { + try { + return decodeToNumber(v) + } catch (e) { + return null + } + }).filter(v => !!v) + expect(decodedNum.length).toEqual(2) + }) + }) + }) diff --git a/src/atlasViewer/atlasViewer.urlUtil.ts b/src/atlasViewer/atlasViewer.urlUtil.ts index 59640205e71446281e4329b776b7f1b3bfbb133b..690094788ba8320c0af070611cf3b62daa3943bb 100644 --- a/src/atlasViewer/atlasViewer.urlUtil.ts +++ b/src/atlasViewer/atlasViewer.urlUtil.ts @@ -1,10 +1,9 @@ -import { getGetRegionFromLabelIndexId } from "src/services/effect/effect"; +import { getGetRegionFromLabelIndexId } from 'src/util/fn' import { mixNgLayers } from "src/services/state/ngViewerState.store"; import { PLUGINSTORE_CONSTANTS } from 'src/services/state/pluginState.store' -import { generateLabelIndexId, getNgIdLabelIndexFromRegion, IavRootStoreInterface } from "../services/stateStore.service"; -import { decodeToNumber, encodeNumber, separator } from "./atlasViewer.constantService.service"; import { getShader, PMAP_DEFAULT_CONFIG } from "src/util/constants"; import { viewerStateHelperStoreName } from "src/services/state/viewerState.store.helper"; +import { serialiseParcellationRegion } from "common/util" export const PARSING_SEARCHPARAM_ERROR = { TEMPALTE_NOT_SET: 'TEMPALTE_NOT_SET', TEMPLATE_NOT_FOUND: 'TEMPLATE_NOT_FOUND', @@ -20,6 +19,107 @@ export const CVT_STATE_TO_SEARCHPARAM_ERROR = { TEMPLATE_NOT_SELECTED: 'TEMPLATE_NOT_SELECTED', } +/** + * First attempt at encoding int (e.g. selected region, navigation location) from number (loc info density) to b64 (higher info density) + * The constraint is that the cipher needs to be commpatible with URI encoding + * and a URI compatible separator is required. + * + * The implementation below came from + * https://stackoverflow.com/a/6573119/6059235 + * + * While a faster solution exist in the same post, this operation is expected to be done: + * - once per 1 sec frequency + * - on < 1000 numbers + * + * So performance is not really that important (Also, need to learn bitwise operation) + */ + +const cipher = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_-' +export const separator = "." +const negString = '~' + +const encodeInt = (number: number) => { + if (number % 1 !== 0) { throw new Error('cannot encodeInt on a float. Ensure float flag is set') } + if (isNaN(Number(number)) || number === null || number === Number.POSITIVE_INFINITY) { throw new Error('The input is not valid') } + + let rixit // like 'digit', only in some non-decimal radix + let residual + let result = '' + + if (number < 0) { + result += negString + residual = Math.floor(number * -1) + } else { + residual = Math.floor(number) + } + + /* eslint-disable-next-line no-constant-condition */ + while (true) { + rixit = residual % 64 + // this.log.log("rixit : " + rixit) + // this.log.log("result before : " + result) + result = cipher.charAt(rixit) + result + // this.log.log("result after : " + result) + // this.log.log("residual before : " + residual) + residual = Math.floor(residual / 64) + // this.log.log("residual after : " + residual) + + if (residual === 0) { + break; + } + } + return result +} + +interface IB64EncodingOption { + float: boolean +} + +const defaultB64EncodingOption = { + float: false, +} + +export const encodeNumber: + (number: number, option?: IB64EncodingOption) => string = + (number: number, { float = false }: IB64EncodingOption = defaultB64EncodingOption) => { + if (!float) { return encodeInt(number) } else { + const floatArray = new Float32Array(1) + floatArray[0] = number + const intArray = new Uint32Array(floatArray.buffer) + const castedInt = intArray[0] + return encodeInt(castedInt) + } + } + +const decodetoInt = (encodedString: string) => { + let _encodedString + let negFlag = false + if (encodedString.slice(-1) === negString) { + negFlag = true + _encodedString = encodedString.slice(0, -1) + } else { + _encodedString = encodedString + } + return (negFlag ? -1 : 1) * [..._encodedString].reduce((acc, curr) => { + const index = cipher.indexOf(curr) + if (index < 0) { throw new Error(`Poisoned b64 encoding ${encodedString}`) } + return acc * 64 + index + }, 0) +} + +export const decodeToNumber: + (encodedString: string, option?: IB64EncodingOption) => number = + (encodedString: string, {float = false} = defaultB64EncodingOption) => { + if (!float) { return decodetoInt(encodedString) } else { + const _int = decodetoInt(encodedString) + const intArray = new Uint32Array(1) + intArray[0] = _int + const castedFloat = new Float32Array(intArray.buffer) + return castedFloat[0] + } + } + + export const cvtStateToSearchParam = (state: any): URLSearchParams => { const searchParam = new URLSearchParams() @@ -38,7 +138,7 @@ export const cvtStateToSearchParam = (state: any): URLSearchParams => { // encoding selected regions const accumulatorMap = new Map<string, number[]>() for (const region of regionsSelected) { - const { ngId, labelIndex } = getNgIdLabelIndexFromRegion({ region }) + const { ngId, labelIndex } = region const existingEntry = accumulatorMap.get(ngId) if (existingEntry) { existingEntry.push(labelIndex) } else { accumulatorMap.set(ngId, [ labelIndex ]) } } @@ -92,7 +192,7 @@ export const cvtStateToSearchParam = (state: any): URLSearchParams => { const { TEMPLATE_NOT_FOUND, TEMPALTE_NOT_SET, PARCELLATION_NOT_UPDATED } = PARSING_SEARCHPARAM_ERROR const { UNKNOWN_PARCELLATION, DECODE_CIPHER_ERROR, ID_ERROR } = PARSING_SEARCHPARAM_WARNING -const parseSearchParamForTemplateParcellationRegion = (searchparams: URLSearchParams, state: IavRootStoreInterface, cb?: (arg: any) => void) => { +const parseSearchParamForTemplateParcellationRegion = (searchparams: URLSearchParams, state: any, cb?: (arg: any) => void) => { /** @@ -169,7 +269,7 @@ const parseSearchParamForTemplateParcellationRegion = (searchparams: URLSearchPa } }).filter(v => !!v) for (const labelIndex of labelIndicies) { - selectRegionIds.push( generateLabelIndexId({ ngId, labelIndex }) ) + selectRegionIds.push( serialiseParcellationRegion({ ngId, labelIndex }) ) } } return selectRegionIds @@ -197,9 +297,9 @@ const parseSearchParamForTemplateParcellationRegion = (searchparams: URLSearchPa } } -export const cvtSearchParamToState = (searchparams: URLSearchParams, state: IavRootStoreInterface, callback?: (error: any) => void): IavRootStoreInterface => { +export const cvtSearchParamToState = (searchparams: URLSearchParams, state: any, callback?: (error: any) => void): any => { - const returnState = JSON.parse(JSON.stringify(state)) as IavRootStoreInterface + const returnState = JSON.parse(JSON.stringify(state)) as any /* eslint-disable-next-line @typescript-eslint/no-empty-function */ const warningCb = callback || (() => {}) diff --git a/src/atlasViewer/mouseOver.directive.ts b/src/atlasViewer/mouseOver.directive.ts deleted file mode 100644 index 7938d4a36c1ef02ce7431565426a34af42af7b62..0000000000000000000000000000000000000000 --- a/src/atlasViewer/mouseOver.directive.ts +++ /dev/null @@ -1,250 +0,0 @@ -import { Directive, Pipe, PipeTransform, SecurityContext } from "@angular/core"; -import { DomSanitizer, SafeHtml } from "@angular/platform-browser"; -import { select, Store } from "@ngrx/store"; -import { combineLatest, merge, Observable } from "rxjs"; -import { distinctUntilChanged, filter, map, scan, shareReplay, startWith, withLatestFrom } from "rxjs/operators"; -import { TransformOnhoverSegmentPipe } from "src/atlasViewer/onhoverSegment.pipe"; -import { LoggingService } from "src/logging"; -import { uiStateMouseOverSegmentsSelector, uiStateMouseoverUserLandmark } from "src/services/state/uiState/selectors"; -import { getNgIdLabelIndexFromId } from "src/services/stateStore.service"; - -/** - * Scan function which prepends newest positive (i.e. defined) value - * - * e.g. const source = new Subject() - * source.pipe( - * scan(temporalPositveScanFn, []) - * ).subscribe(this.log.log) // outputs - * - * - * - */ -export const temporalPositveScanFn = (acc: Array<{segments: any, landmark: any, userLandmark: any}>, curr: {segments: any, landmark: any, userLandmark: any}) => { - - const keys = Object.keys(curr) - - // empty array is truthy - const isPositive = keys.some(key => Array.isArray(curr[key]) - ? curr[key].length > 0 - : !!curr[key] - ) - - return isPositive - ? [curr, ...(acc.filter(item => !keys.some(key => !!item[key])))] as Array<{segments?: any, landmark?: any, userLandmark?: any}> - : acc.filter(item => !keys.some(key => !!item[key])) -} - -@Directive({ - selector: '[iav-mouse-hover]', - exportAs: 'iavMouseHover', -}) - -export class MouseHoverDirective { - - public onHoverObs$: Observable<{segments: any, landmark: any, userLandmark: any}> - public currentOnHoverObs$: Observable<{segments: any, landmark: any, userLandmark: any}> - - constructor( - private store$: Store<any>, - private log: LoggingService, - ) { - - // 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$ = combineLatest( - this.store$.pipe( - select('uiState'), - select('mouseOverLandmark'), - ), - this.store$.pipe( - select('dataStore'), - select('fetchedSpatialData'), - startWith([]), - ), - ).pipe( - map(([landmark, spatialDatas]) => { - if (landmark === null) { return landmark } - 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, - } - } else { - return { - ...spatialDatas[idx], - landmarkName: spatialDatas[idx].name, - } - } - }), - ) - - const onHoverSegments$ = this.store$.pipe( - select(uiStateMouseOverSegmentsSelector), - filter(v => !!v), - withLatestFrom( - this.store$.pipe( - select('viewerState'), - select('parcellationSelected'), - startWith(null), - ), - ), - map(([ arr, parcellationSelected ]) => parcellationSelected && parcellationSelected.auxillaryMeshIndices - ? arr.filter(({ segment }) => { - // if segment is not a string (i.e., not labelIndexId) return true - if (typeof segment !== 'string') { return true } - const { labelIndex } = getNgIdLabelIndexFromId({ labelIndexId: segment }) - return parcellationSelected.auxillaryMeshIndices.indexOf(labelIndex) < 0 - }) - : arr), - distinctUntilChanged((o, n) => o.length === n.length - && n.every(segment => - o.find(oSegment => oSegment.layer.name === segment.layer.name - && oSegment.segment === segment.segment))), - ) - - const mergeObs = merge( - onHoverSegments$.pipe( - distinctUntilChanged(), - map(segments => { - return { segments } - }), - ), - onHoverLandmark$.pipe( - distinctUntilChanged(), - map(landmark => { - return { landmark } - }), - ), - onHoverUserLandmark$.pipe( - distinctUntilChanged(), - map(userLandmark => { - return { userLandmark } - }), - ), - ).pipe( - shareReplay(1), - ) - - this.onHoverObs$ = mergeObs.pipe( - scan((acc, curr) => { - return { - ...acc, - ...curr, - } - }, { segments: null, landmark: null, userLandmark: null }), - shareReplay(1), - ) - - this.currentOnHoverObs$ = mergeObs.pipe( - scan(temporalPositveScanFn, []), - map(arr => { - - let returnObj = { - segments: null, - landmark: null, - userLandmark: null, - } - - for (const val of arr) { - returnObj = { - ...returnObj, - ...val - } - } - - return returnObj - }), - shareReplay(1), - ) - } -} - -@Pipe({ - name: 'mouseOverTextPipe', -}) - -export class MouseOverTextPipe implements PipeTransform { - - private transformOnHoverSegmentPipe: TransformOnhoverSegmentPipe - constructor(private sanitizer: DomSanitizer) { - this.transformOnHoverSegmentPipe = new TransformOnhoverSegmentPipe(this.sanitizer) - } - - private renderText = ({ label, obj }): SafeHtml[] => { - switch (label) { - case 'landmark': { - const { dataset = [] } = obj - return [ - this.sanitizer.sanitize(SecurityContext.HTML, obj.landmarkName), - ...(dataset.map(ds => this.sanitizer.bypassSecurityTrustHtml(` -<span class="text-muted"> - ${this.sanitizer.sanitize(SecurityContext.HTML, ds.name)} -</span> -`))) - ] - } - case 'segments': - return obj.map(({ segment }) => this.transformOnHoverSegmentPipe.transform(segment)) - case 'userLandmark': - return [this.sanitizer.sanitize(SecurityContext.HTML, obj.name)] - default: - // ts-lint:disable-next-line - console.warn(`mouseOver.directive.ts#mouseOverTextPipe: Cannot be displayed: label: ${label}`) - return [this.sanitizer.bypassSecurityTrustHtml(`Cannot be displayed: label: ${label}`)] - } - } - - public transform(inc: {segments: any, landmark: any, userLandmark: any}): Array<{label: string, text: SafeHtml[]}> { - const keys = Object.keys(inc) - return keys - // if is segments, filter out if lengtth === 0 - .filter(key => Array.isArray(inc[key]) ? inc[key].length > 0 : true ) - // for other properties, check if value is defined - .filter(key => !!inc[key]) - .map(key => { - return { - label: key, - text: this.renderText({ label: key, obj: inc[key] }) - } - }) - } -} - -@Pipe({ - name: 'mouseOverIconPipe', -}) - -export class MouseOverIconPipe implements PipeTransform { - - public transform(type: string): {fontSet: string, fontIcon: string} { - - switch (type) { - case 'landmark': - return { - fontSet: 'fas', - fontIcon: 'fa-map-marker-alt', - } - case 'segments': - return { - fontSet: 'fas', - fontIcon: 'fa-brain', - } - case 'userLandmark': - return { - fontSet: 'fas', - fontIcon: 'fa-map-marker-alt', - } - default: - return { - fontSet: 'fas', - fontIcon: 'fa-file', - } - } - } -} diff --git a/src/atlasViewer/pluginUnit/index.ts b/src/atlasViewer/pluginUnit/index.ts deleted file mode 100644 index cc1c5058b8b8133b2d2c1e8547b6c9c51404a752..0000000000000000000000000000000000000000 --- a/src/atlasViewer/pluginUnit/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { PluginServices, IPluginManifest } from './atlasViewer.pluginService.service' \ No newline at end of file diff --git a/src/atlasViewer/pluginUnit/plugin_styles.css b/src/atlasViewer/pluginUnit/plugin_styles.css deleted file mode 100644 index dea00063b288c84bc44229f447abd358aaf82552..0000000000000000000000000000000000000000 --- a/src/atlasViewer/pluginUnit/plugin_styles.css +++ /dev/null @@ -1,69 +0,0 @@ -/* layout */ - -[plugincontainer] .btn, -[plugincontainer] .input-group-addon, -[plugincontainer] input[type="text"], -[plugincontainer] .panel -{ - border-radius:0px; - border:none; -} - -[plugincontainer] .btn -{ - opacity : 0.9; - transition: opacity 0.3s ease, transform 0.3s ease; - box-shadow : rgba(5, 5, 5, 0.1) 0px 4px 6px 0px; -} - -[plugincontainer] .btn:hover -{ - opacity:1.0; - transform:translateY(-5%); - box-shadow : rgba(5, 5, 5, 0.25) 0px 4px 6px 0px; -} - -[plugincontainer] .form-control -{ - background:none; - border:none; -} - -/* colour */ -[darktheme="true"] [plugincontainer] .btn, -[darktheme="true"] [plugincontainer] input[type="text"], -[darktheme="true"] [plugincontainer] input[type="number"], -[darktheme="true"] [plugincontainer] .panel -{ - background-color:rgba(81,81,81,0.8); - color:rgba(255,255,255,0.8); -} - -[darktheme="true"] [plugincontainer] .input-group-addon -{ - background-color:rgba(60,60,60,0.2); - color:rgba(255,255,255,0.8); - /* border-top:1px solid rgba(255,255,255,0.05); - border-left:1px solid rgba(255,255,255,0.05); - border-right:1px solid rgba(255,255,255,0.05); */ -} - -[darktheme="true"] [plugincontainer] hr -{ - border-color:rgba(100,100,100,0.5); -} - -[darktheme="true"] [plugincontainer] .btn.btn-active -{ - background-color:rgba(65,65,65,0.8); - transform: translateY(2px); - color:rgba(255,255,255,1.0); - box-shadow: inset 0 2px 2px -2px rgba(0,0,0,0.2); -} - -[darktheme="true"] .alert.alert-danger -{ - color: #f2dede; - background-color: rgba(169, 68, 66, 0.5); - border-color: rgba(169, 68, 66,0.2) -} diff --git a/src/auth/auth.directive.ts b/src/auth/auth.directive.ts index 982a57b3d0a82945c38079b5bcd21dde0ff2cfc7..94d64d7e6da8f090014553537bca7ca040a48132 100644 --- a/src/auth/auth.directive.ts +++ b/src/auth/auth.directive.ts @@ -3,7 +3,7 @@ import { Observable } from "rxjs"; import { IUser, AuthService } from './auth.service' @Directive({ - selector: '[iav-auth-authState]', + selector: '[iav-auth-auth-state]', exportAs: 'iavAuthAuthState' }) diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts index 12720ca923e6bd1399deeaecdd8475f5ef9c400e..fdec1e6ef5ec008b4d4bb01f8a3897ff4c482934 100644 --- a/src/auth/auth.service.ts +++ b/src/auth/auth.service.ts @@ -1,7 +1,7 @@ import { HttpClient } from "@angular/common/http"; import { Injectable, OnDestroy } from "@angular/core"; import { Observable, of, Subscription } from "rxjs"; -import { catchError, shareReplay, mapTo } from "rxjs/operators"; +import { catchError, shareReplay } from "rxjs/operators"; const IV_REDIRECT_TOKEN = `IV_REDIRECT_TOKEN` diff --git a/src/glue.spec.ts b/src/glue.spec.ts index aba6f15912e2050d8e2a25bc11d58216bd001934..02e5c00049375fd3c0100bfd0f2ca1f86d5ee944 100644 --- a/src/glue.spec.ts +++ b/src/glue.spec.ts @@ -14,7 +14,7 @@ import { EnumColorMapName } from "./util/colorMaps" import { ngViewerSelectorClearView } from "./services/state/ngViewerState/selectors" import { tap, ignoreElements } from "rxjs/operators" import { merge, of } from "rxjs" -import { GET_KGDS_PREVIEW_INFO_FROM_ID_FILENAME } from "./ui/databrowserModule/pure" +import { GET_KGDS_PREVIEW_INFO_FROM_ID_FILENAME } from "./atlasComponents/databrowserModule/pure" import { viewerStateSelectedTemplateSelector } from "./services/state/viewerState/selectors" import { generalActionError } from "./services/stateStore.helper" diff --git a/src/glue.ts b/src/glue.ts index 5d94c0c6a839b9b4c85042123044d6899cd501b3..f0d9430a1a4bf6bf90bad761b6ca7946aaf65fee 100644 --- a/src/glue.ts +++ b/src/glue.ts @@ -1,6 +1,6 @@ import { uiActionSetPreviewingDatasetFiles, IDatasetPreviewData, uiStateShowBottomSheet, uiStatePreviewingDatasetFilesSelector } from "./services/state/uiState.store.helper" import { OnDestroy, Injectable, Optional, Inject, InjectionToken } from "@angular/core" -import { PreviewComponentWrapper, DatasetPreview, determinePreviewFileType, EnumPreviewFileTypes, IKgDataEntry, getKgSchemaIdFromFullId, GET_KGDS_PREVIEW_INFO_FROM_ID_FILENAME } from "./ui/databrowserModule/pure" +import { PreviewComponentWrapper, DatasetPreview, determinePreviewFileType, EnumPreviewFileTypes, IKgDataEntry, getKgSchemaIdFromFullId, GET_KGDS_PREVIEW_INFO_FROM_ID_FILENAME } from "./atlasComponents/databrowserModule/pure" import { Subscription, Observable, forkJoin, of, merge, combineLatest } from "rxjs" import { select, Store, ActionReducer, createAction, props, createSelector, Action } from "@ngrx/store" import { startWith, map, shareReplay, pairwise, debounceTime, distinctUntilChanged, tap, switchMap, withLatestFrom, mapTo, switchMapTo, filter, skip, catchError, bufferTime } from "rxjs/operators" @@ -590,7 +590,6 @@ export class DatasetPreviewGlue implements IDatasetPreviewGlue, OnDestroy{ } private openDatasetPreviewWidget(data: IDatasetPreviewData) { - console.log({ data }) const { datasetId: kgId, filename } = data if (!!this.actionOnWidget) { diff --git a/src/ui/config/currentLayout/currentLayout.component.ts b/src/layouts/currentLayout/currentLayout.component.ts similarity index 100% rename from src/ui/config/currentLayout/currentLayout.component.ts rename to src/layouts/currentLayout/currentLayout.component.ts diff --git a/src/ui/config/currentLayout/currentLayout.style.css b/src/layouts/currentLayout/currentLayout.style.css similarity index 100% rename from src/ui/config/currentLayout/currentLayout.style.css rename to src/layouts/currentLayout/currentLayout.style.css diff --git a/src/ui/config/currentLayout/currentLayout.template.html b/src/layouts/currentLayout/currentLayout.template.html similarity index 100% rename from src/ui/config/currentLayout/currentLayout.template.html rename to src/layouts/currentLayout/currentLayout.template.html diff --git a/src/layouts/fourCorners/fourCorners.component.ts b/src/layouts/fourCorners/fourCorners.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..181bb85285ed389cc4fd59f5bf34d527b52cbc65 --- /dev/null +++ b/src/layouts/fourCorners/fourCorners.component.ts @@ -0,0 +1,11 @@ +import { Component } from "@angular/core"; + +@Component({ + selector: 'iav-layout-fourcorners', + templateUrl: './fourCorners.template.html', + styleUrls: [ + './fourCorners.style.css' + ] +}) + +export class FourCornersCmp{} \ No newline at end of file diff --git a/src/layouts/fourCorners/fourCorners.style.css b/src/layouts/fourCorners/fourCorners.style.css new file mode 100644 index 0000000000000000000000000000000000000000..352b490b2acf42d4d92344bdc0be28d4f8cf80da --- /dev/null +++ b/src/layouts/fourCorners/fourCorners.style.css @@ -0,0 +1,13 @@ +:host +{ + display: block; + width: 100%; + height: 100%; + + position: relative; +} + +.corner-container +{ + z-index: 5; +} \ No newline at end of file diff --git a/src/layouts/fourCorners/fourCorners.template.html b/src/layouts/fourCorners/fourCorners.template.html new file mode 100644 index 0000000000000000000000000000000000000000..c99f8b0a8bf791a3b6efc256bf8b86cf3594c9b4 --- /dev/null +++ b/src/layouts/fourCorners/fourCorners.template.html @@ -0,0 +1,16 @@ +<div class="position-absolute top-0 left-0 w-100 h-100"> + <ng-content select="[iavLayoutFourCornersContent]"></ng-content> +</div> + +<div class="corner-container position-absolute top-0 left-0"> + <ng-content select="[iavLayoutFourCornersTopLeft]"></ng-content> +</div> +<div class="corner-container position-absolute top-0 right-0"> + <ng-content select="[iavLayoutFourCornersTopRight]"></ng-content> +</div> +<div class="corner-container position-absolute bottom-0 left-0"> + <ng-content select="[iavLayoutFourCornersBottomLeft]"></ng-content> +</div> +<div class="corner-container position-absolute bottom-0 right-0"> + <ng-content select="[iavLayoutFourCornersBottomRight]"></ng-content> +</div> \ No newline at end of file diff --git a/src/layouts/layout.module.ts b/src/layouts/layout.module.ts index b10a09b6bb519316c56a22550bc445afc5c5875b..9ddb115a81f3f0588c5dec7933daecd2b53bbb08 100644 --- a/src/layouts/layout.module.ts +++ b/src/layouts/layout.module.ts @@ -2,7 +2,13 @@ import { NgModule } from "@angular/core"; import { BrowserModule } from "@angular/platform-browser"; import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; import { ComponentsModule } from "../components/components.module"; +import { CurrentLayout } from "./currentLayout/currentLayout.component"; import { FloatingLayoutContainer } from "./floating/floating.component"; +import { FourCornersCmp } from "./fourCorners/fourCorners.component"; +import { FourPanelLayout } from "./layouts/fourPanel/fourPanel.component"; +import { HorizontalOneThree } from "./layouts/h13/h13.component"; +import { SinglePanel } from "./layouts/single/single.component"; +import { VerticalOneThree } from "./layouts/v13/v13.component"; import { LayoutsExample } from "./layoutsExample/layoutsExample.component"; import { LayoutMainSide } from "./mainside/mainside.component"; @@ -15,14 +21,25 @@ import { LayoutMainSide } from "./mainside/mainside.component"; declarations : [ LayoutMainSide, FloatingLayoutContainer, + FourCornersCmp, + CurrentLayout, + FourPanelLayout, + HorizontalOneThree, + SinglePanel, + VerticalOneThree, LayoutsExample, ], exports : [ BrowserAnimationsModule, LayoutMainSide, FloatingLayoutContainer, - + FourCornersCmp, + CurrentLayout, + FourPanelLayout, + HorizontalOneThree, + SinglePanel, + VerticalOneThree, LayoutsExample, ], }) diff --git a/src/ui/config/layouts/fourPanel/fourPanel.component.ts b/src/layouts/layouts/fourPanel/fourPanel.component.ts similarity index 100% rename from src/ui/config/layouts/fourPanel/fourPanel.component.ts rename to src/layouts/layouts/fourPanel/fourPanel.component.ts diff --git a/src/ui/config/layouts/fourPanel/fourPanel.style.css b/src/layouts/layouts/fourPanel/fourPanel.style.css similarity index 100% rename from src/ui/config/layouts/fourPanel/fourPanel.style.css rename to src/layouts/layouts/fourPanel/fourPanel.style.css diff --git a/src/ui/config/layouts/fourPanel/fourPanel.template.html b/src/layouts/layouts/fourPanel/fourPanel.template.html similarity index 100% rename from src/ui/config/layouts/fourPanel/fourPanel.template.html rename to src/layouts/layouts/fourPanel/fourPanel.template.html diff --git a/src/ui/config/layouts/h13/h13.component.ts b/src/layouts/layouts/h13/h13.component.ts similarity index 100% rename from src/ui/config/layouts/h13/h13.component.ts rename to src/layouts/layouts/h13/h13.component.ts diff --git a/src/ui/config/layouts/h13/h13.style.css b/src/layouts/layouts/h13/h13.style.css similarity index 100% rename from src/ui/config/layouts/h13/h13.style.css rename to src/layouts/layouts/h13/h13.style.css diff --git a/src/ui/config/layouts/h13/h13.template.html b/src/layouts/layouts/h13/h13.template.html similarity index 100% rename from src/ui/config/layouts/h13/h13.template.html rename to src/layouts/layouts/h13/h13.template.html diff --git a/src/ui/config/layouts/single/single.component.ts b/src/layouts/layouts/single/single.component.ts similarity index 100% rename from src/ui/config/layouts/single/single.component.ts rename to src/layouts/layouts/single/single.component.ts diff --git a/src/ui/config/layouts/single/single.style.css b/src/layouts/layouts/single/single.style.css similarity index 100% rename from src/ui/config/layouts/single/single.style.css rename to src/layouts/layouts/single/single.style.css diff --git a/src/ui/config/layouts/single/single.template.html b/src/layouts/layouts/single/single.template.html similarity index 100% rename from src/ui/config/layouts/single/single.template.html rename to src/layouts/layouts/single/single.template.html diff --git a/src/ui/config/layouts/v13/v13.component.ts b/src/layouts/layouts/v13/v13.component.ts similarity index 100% rename from src/ui/config/layouts/v13/v13.component.ts rename to src/layouts/layouts/v13/v13.component.ts diff --git a/src/ui/config/layouts/v13/v13.style.css b/src/layouts/layouts/v13/v13.style.css similarity index 100% rename from src/ui/config/layouts/v13/v13.style.css rename to src/layouts/layouts/v13/v13.style.css diff --git a/src/ui/config/layouts/v13/v13.template.html b/src/layouts/layouts/v13/v13.template.html similarity index 100% rename from src/ui/config/layouts/v13/v13.template.html rename to src/layouts/layouts/v13/v13.template.html diff --git a/src/main.module.ts b/src/main.module.ts index 11a705ee245213a857e933cd3c82dce5010beb9b..189e7a8a0e20a72f542fbcf9c436d6263f98ad63 100644 --- a/src/main.module.ts +++ b/src/main.module.ts @@ -1,6 +1,6 @@ import { DragDropModule } from '@angular/cdk/drag-drop' import { CommonModule } from "@angular/common"; -import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from "@angular/core"; +import { NgModule } from "@angular/core"; import { FormsModule } from "@angular/forms"; import { StoreModule, ActionReducer } from "@ngrx/store"; import { AngularMaterialModule } from 'src/ui/sharedModules/angularMaterial.module' @@ -16,7 +16,7 @@ import { HttpClientModule } from "@angular/common/http"; import { EffectsModule } from "@ngrx/effects"; import { AtlasViewerAPIServices, CANCELLABLE_DIALOG, API_SERVICE_SET_VIEWER_HANDLE_TOKEN, setViewerHandleFactory, LOAD_MESH_TOKEN, ILoadMesh } from "./atlasViewer/atlasViewer.apiService.service"; import { AtlasWorkerService } from "./atlasViewer/atlasViewer.workerService.service"; -import { TransformOnhoverSegmentPipe } from "./atlasViewer/onhoverSegment.pipe"; + import { ConfirmDialogComponent } from "./components/confirmDialog/confirmDialog.component"; import { DialogComponent } from "./components/dialog/dialog.component"; import { DialogService } from "./services/dialogService.service"; @@ -25,8 +25,8 @@ import { LocalFileService } from "./services/localFile.service"; import { NgViewerUseEffect } from "./services/state/ngViewerState.store"; import { ViewerStateUseEffect } from "./services/state/viewerState.store"; import { UIService } from "./services/uiService.service"; -import { DatabrowserModule, OVERRIDE_IAV_DATASET_PREVIEW_DATASET_FN, DataBrowserFeatureStore, GET_KGDS_PREVIEW_INFO_FROM_ID_FILENAME, DatabrowserService } from "src/ui/databrowserModule"; -import { ViewerStateControllerUseEffect } from "./ui/viewerStateController/viewerState.useEffect"; +import { DatabrowserModule, OVERRIDE_IAV_DATASET_PREVIEW_DATASET_FN, DataBrowserFeatureStore, GET_KGDS_PREVIEW_INFO_FROM_ID_FILENAME, DatabrowserService } from "src/atlasComponents/databrowserModule"; +import { ViewerStateControllerUseEffect } from "src/state"; import { DockedContainerDirective } from "./util/directives/dockedContainer.directive"; import { DragDropDirective } from "./util/directives/dragDrop.directive"; import { FloatingContainerDirective } from "./util/directives/floatingContainer.directive"; @@ -35,18 +35,16 @@ import { NewViewerDisctinctViewToLayer } from "./util/pipes/newViewerDistinctVie import { ClickInterceptor, CLICK_INTERCEPTOR_INJECTOR, UtilModule } from "src/util"; import { SpotLightModule } from 'src/spotlight/spot-light.module' import { TryMeComponent } from "./ui/tryme/tryme.component"; -import { MouseHoverDirective, MouseOverIconPipe, MouseOverTextPipe } from "./atlasViewer/mouseOver.directive"; import { UiStateUseEffect } from "src/services/state/uiState.store"; import { AtlasViewerHistoryUseEffect } from "./atlasViewer/atlasViewer.history.service"; import { PluginServiceUseEffect } from './services/effect/pluginUseEffect'; import { TemplateCoordinatesTransformation } from "src/services/templateCoordinatesTransformation.service"; import { NewTemplateUseEffect } from './services/effect/newTemplate.effect'; import { WidgetModule } from 'src/widget'; -import { PluginModule } from './atlasViewer/pluginUnit/plugin.module'; +import { PluginModule } from './plugin/plugin.module'; import { LoggingModule } from './logging/logging.module'; -import { ShareModule } from './share'; import { AuthService } from './auth' -import { IAV_DATASET_PREVIEW_ACTIVE } from 'src/ui/databrowserModule' +import { IAV_DATASET_PREVIEW_ACTIVE } from 'src/atlasComponents/databrowserModule' import 'hammerjs' import 'src/res/css/extra_styles.css' @@ -54,9 +52,14 @@ import 'src/res/css/version.css' import 'src/theme.scss' import { DatasetPreviewGlue, datasetPreviewMetaReducer, IDatasetPreviewGlue, GlueEffects, ClickInterceptorService } from './glue'; import { viewerStateHelperReducer, viewerStateMetaReducers, ViewerStateHelperEffect } from './services/state/viewerState.store.helper'; -import { TOS_OBS_INJECTION_TOKEN } from './ui/kgtos/kgtos.component'; +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 } from './viewerModule'; +import { CookieModule } from './ui/cookieAgreement/module'; +import { KgTosModule } from './ui/kgtos/module'; +import { MouseoverModule } from './mouseoverModule/mouseover.module'; export function debug(reducer: ActionReducer<any>): ActionReducer<any> { return function(state, action) { @@ -82,10 +85,13 @@ export function debug(reducer: ActionReducer<any>): ActionReducer<any> { WidgetModule, PluginModule, LoggingModule, - ShareModule, MesssagingModule, - + ViewerModule, SpotLightModule, + ParcellationRegionModule, + CookieModule, + KgTosModule, + MouseoverModule, EffectsModule.forRoot([ UseEffects, @@ -127,15 +133,11 @@ export function debug(reducer: ActionReducer<any>): ActionReducer<any> { FloatingContainerDirective, FloatingMouseContextualContainerDirective, DragDropDirective, - MouseHoverDirective, /* pipes */ GetNamesPipe, GetNamePipe, - TransformOnhoverSegmentPipe, NewViewerDisctinctViewToLayer, - MouseOverTextPipe, - MouseOverIconPipe, ], entryComponents : [ DialogComponent, @@ -246,9 +248,6 @@ export function debug(reducer: ActionReducer<any>): ActionReducer<any> { bootstrap : [ AtlasViewer, ], - schemas: [ - CUSTOM_ELEMENTS_SCHEMA, - ], }) export class MainModule { diff --git a/src/mouseoverModule/index.ts b/src/mouseoverModule/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..6e9cd09486d0a2da56b1f28f86dce2cc6382d78d --- /dev/null +++ b/src/mouseoverModule/index.ts @@ -0,0 +1,5 @@ +export { MouseOverIconPipe } from './mouseOverIcon.pipe' +export { MouseOverTextPipe } from './mouseOverText.pipe' +export { MouseHoverDirective } from './mouseover.directive' +export { MouseoverModule } from './mouseover.module' +export { TransformOnhoverSegmentPipe } from './transformOnhoverSegment.pipe' \ No newline at end of file diff --git a/src/mouseoverModule/mouseOverIcon.pipe.ts b/src/mouseoverModule/mouseOverIcon.pipe.ts new file mode 100644 index 0000000000000000000000000000000000000000..ba5f7686798d9417eb365580a84f16955cdf53d7 --- /dev/null +++ b/src/mouseoverModule/mouseOverIcon.pipe.ts @@ -0,0 +1,34 @@ +import { Pipe, PipeTransform } from "@angular/core" + +@Pipe({ + name: 'mouseOverIconPipe', +}) + +export class MouseOverIconPipe implements PipeTransform { + + public transform(type: string): {fontSet: string, fontIcon: string} { + + switch (type) { + case 'landmark': + return { + fontSet: 'fas', + fontIcon: 'fa-map-marker-alt', + } + case 'segments': + return { + fontSet: 'fas', + fontIcon: 'fa-brain', + } + case 'userLandmark': + return { + fontSet: 'fas', + fontIcon: 'fa-map-marker-alt', + } + default: + return { + fontSet: 'fas', + fontIcon: 'fa-file', + } + } + } +} diff --git a/src/mouseoverModule/mouseOverText.pipe.ts b/src/mouseoverModule/mouseOverText.pipe.ts new file mode 100644 index 0000000000000000000000000000000000000000..5ba11425c0e78eb8dee14e9a25ba02a0ed0f9adf --- /dev/null +++ b/src/mouseoverModule/mouseOverText.pipe.ts @@ -0,0 +1,54 @@ +import { Pipe, PipeTransform, SecurityContext } from "@angular/core" +import { DomSanitizer, SafeHtml } from "@angular/platform-browser" +import { TransformOnhoverSegmentPipe } from "./transformOnhoverSegment.pipe" + +@Pipe({ + name: 'mouseOverTextPipe', +}) + +export class MouseOverTextPipe implements PipeTransform { + + private transformOnHoverSegmentPipe: TransformOnhoverSegmentPipe + constructor(private sanitizer: DomSanitizer) { + this.transformOnHoverSegmentPipe = new TransformOnhoverSegmentPipe(this.sanitizer) + } + + private renderText = ({ label, obj }): SafeHtml[] => { + switch (label) { + case 'landmark': { + const { dataset = [] } = obj + return [ + this.sanitizer.sanitize(SecurityContext.HTML, obj.landmarkName), + ...(dataset.map(ds => this.sanitizer.bypassSecurityTrustHtml(` +<span class="text-muted"> + ${this.sanitizer.sanitize(SecurityContext.HTML, ds.name)} +</span> +`))) + ] + } + case 'segments': + return obj.map(({ segment }) => this.transformOnHoverSegmentPipe.transform(segment)) + case 'userLandmark': + return [this.sanitizer.sanitize(SecurityContext.HTML, obj.name)] + default: + // ts-lint:disable-next-line + console.warn(`mouseOver.directive.ts#mouseOverTextPipe: Cannot be displayed: label: ${label}`) + return [this.sanitizer.bypassSecurityTrustHtml(`Cannot be displayed: label: ${label}`)] + } + } + + public transform(inc: {segments: any, landmark: any, userLandmark: any}): Array<{label: string, text: SafeHtml[]}> { + const keys = Object.keys(inc) + return keys + // if is segments, filter out if lengtth === 0 + .filter(key => Array.isArray(inc[key]) ? inc[key].length > 0 : true ) + // for other properties, check if value is defined + .filter(key => !!inc[key]) + .map(key => { + return { + label: key, + text: this.renderText({ label: key, obj: inc[key] }) + } + }) + } +} diff --git a/src/mouseoverModule/mouseover.directive.ts b/src/mouseoverModule/mouseover.directive.ts new file mode 100644 index 0000000000000000000000000000000000000000..25f33743ce536d91150185cfab6c2e52e3764c8e --- /dev/null +++ b/src/mouseoverModule/mouseover.directive.ts @@ -0,0 +1,127 @@ +import { Directive } from "@angular/core" +import { select, Store } from "@ngrx/store" +import { merge, Observable } from "rxjs" +import { distinctUntilChanged, filter, map, scan, shareReplay, startWith, withLatestFrom } from "rxjs/operators" +import { LoggingService } from "src/logging" +import { uiStateMouseOverSegmentsSelector, uiStateMouseoverUserLandmark } from "src/services/state/uiState/selectors" +import { viewerStateSelectedParcellationSelector } from "src/services/state/viewerState/selectors" +import { deserialiseParcRegionId } from "common/util" +import { temporalPositveScanFn } from "./util" + +@Directive({ + selector: '[iav-mouse-hover]', + exportAs: 'iavMouseHover', +}) + +export class MouseHoverDirective { + + public onHoverObs$: Observable<{segments: any, landmark: any, userLandmark: any}> + public currentOnHoverObs$: Observable<{segments: any, landmark: any, userLandmark: any}> + + constructor( + private store$: Store<any>, + private log: LoggingService, + ) { + + // 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('uiState'), + select('mouseOverLandmark'), + ).pipe( + map(landmark => { + if (landmark === null) { return landmark } + 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(uiStateMouseOverSegmentsSelector), + filter(v => !!v), + withLatestFrom( + this.store$.pipe( + select(viewerStateSelectedParcellationSelector), + startWith(null), + ), + ), + map(([ arr, parcellationSelected ]) => parcellationSelected && parcellationSelected.auxillaryMeshIndices + ? arr.filter(({ segment }) => { + // if segment is not a string (i.e., not labelIndexId) return true + if (typeof segment !== 'string') { return true } + const { labelIndex } = deserialiseParcRegionId(segment) + return parcellationSelected.auxillaryMeshIndices.indexOf(labelIndex) < 0 + }) + : arr), + distinctUntilChanged((o, n) => o.length === n.length + && n.every(segment => + o.find(oSegment => oSegment.layer.name === segment.layer.name + && oSegment.segment === segment.segment))), + ) + + const mergeObs = merge( + onHoverSegments$.pipe( + distinctUntilChanged(), + map(segments => { + return { segments } + }), + ), + onHoverLandmark$.pipe( + distinctUntilChanged(), + map(landmark => { + return { landmark } + }), + ), + onHoverUserLandmark$.pipe( + distinctUntilChanged(), + map(userLandmark => { + return { userLandmark } + }), + ), + ).pipe( + shareReplay(1), + ) + + this.onHoverObs$ = mergeObs.pipe( + scan((acc, curr) => { + return { + ...acc, + ...curr, + } + }, { segments: null, landmark: null, userLandmark: null }), + shareReplay(1), + ) + + this.currentOnHoverObs$ = mergeObs.pipe( + scan(temporalPositveScanFn, []), + map(arr => { + + let returnObj = { + segments: null, + landmark: null, + userLandmark: null, + } + + for (const val of arr) { + returnObj = { + ...returnObj, + ...val + } + } + + return returnObj + }), + shareReplay(1), + ) + } +} diff --git a/src/mouseoverModule/mouseover.module.ts b/src/mouseoverModule/mouseover.module.ts new file mode 100644 index 0000000000000000000000000000000000000000..476fc900a2a6a3731b5bae0d7f6a2f60d836dd8a --- /dev/null +++ b/src/mouseoverModule/mouseover.module.ts @@ -0,0 +1,26 @@ +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { TransformOnhoverSegmentPipe } from "src/atlasViewer/onhoverSegment.pipe"; +import { MouseHoverDirective } from "./mouseover.directive"; +import { MouseOverIconPipe } from "./mouseOverIcon.pipe"; +import { MouseOverTextPipe } from "./mouseOverText.pipe"; + +@NgModule({ + imports: [ + CommonModule, + ], + declarations: [ + MouseHoverDirective, + MouseOverTextPipe, + TransformOnhoverSegmentPipe, + MouseOverIconPipe, + ], + exports: [ + MouseHoverDirective, + MouseOverTextPipe, + TransformOnhoverSegmentPipe, + MouseOverIconPipe, + ] +}) + +export class MouseoverModule{} \ No newline at end of file diff --git a/src/mouseoverModule/transformOnhoverSegment.pipe.ts b/src/mouseoverModule/transformOnhoverSegment.pipe.ts new file mode 100644 index 0000000000000000000000000000000000000000..5199a582a1ba2e5084d7097996652a492211a342 --- /dev/null +++ b/src/mouseoverModule/transformOnhoverSegment.pipe.ts @@ -0,0 +1,29 @@ +import { Pipe, PipeTransform, SecurityContext } from "@angular/core"; +import { DomSanitizer, SafeHtml } from "@angular/platform-browser"; + +@Pipe({ + name: 'transformOnhoverSegment', +}) + +export class TransformOnhoverSegmentPipe implements PipeTransform { + constructor(private sanitizer: DomSanitizer) { + + } + + private sanitizeHtml(inc: string): SafeHtml { + return this.sanitizer.sanitize(SecurityContext.HTML, inc) + } + + private getStatus(text: string) { + return ` <span class="text-muted">(${this.sanitizeHtml(text)})</span>` + } + + public transform(segment: any | number): SafeHtml { + return this.sanitizer.bypassSecurityTrustHtml(( + ( this.sanitizeHtml(segment.name) || segment) + + (segment.status + ? this.getStatus(segment.status) + : '') + )) + } +} diff --git a/src/atlasViewer/mouseOver.directive.spec.ts b/src/mouseoverModule/util.spec..ts similarity index 94% rename from src/atlasViewer/mouseOver.directive.spec.ts rename to src/mouseoverModule/util.spec..ts index 613d78ee825f93026770c06378d28e1b9464de9c..07453e0343eb31d5e6b1279b3a249a19b5301c2c 100644 --- a/src/atlasViewer/mouseOver.directive.spec.ts +++ b/src/mouseoverModule/util.spec..ts @@ -1,7 +1,7 @@ import {} from 'jasmine' import { forkJoin, Subject } from 'rxjs'; import { scan, skip, take } from 'rxjs/operators'; -import { temporalPositveScanFn } from './mouseOver.directive' +import { temporalPositveScanFn } from './util' const segmentsPositive = { segments: [{ hello: 'world' }] } as {segments: any} const segmentsNegative = { segments: [] } @@ -42,12 +42,12 @@ describe('temporalPositveScanFn', () => { take(1), ) - forkJoin( + forkJoin([ testFirstEv, testSecondEv, testThirdEv, testFourthEv, - ).pipe( + ]).pipe( take(1), ).subscribe(([ arr1, arr2, arr3, arr4 ]) => { expect(arr1).toEqual([ segmentsPositive ]) diff --git a/src/mouseoverModule/util.ts b/src/mouseoverModule/util.ts new file mode 100644 index 0000000000000000000000000000000000000000..519202f8878440faf249ad3da41b163e5c66fd4b --- /dev/null +++ b/src/mouseoverModule/util.ts @@ -0,0 +1,26 @@ + +/** + * Scan function which prepends newest positive (i.e. defined) value + * + * e.g. const source = new Subject() + * source.pipe( + * scan(temporalPositveScanFn, []) + * ).subscribe(this.log.log) // outputs + * + * + * + */ +export const temporalPositveScanFn = (acc: Array<{segments: any, landmark: any, userLandmark: any}>, curr: {segments: any, landmark: any, userLandmark: any}) => { + + const keys = Object.keys(curr) + + // empty array is truthy + const isPositive = keys.some(key => Array.isArray(curr[key]) + ? curr[key].length > 0 + : !!curr[key] + ) + + return isPositive + ? [curr, ...(acc.filter(item => !keys.some(key => !!item[key])))] as Array<{segments?: any, landmark?: any, userLandmark?: any}> + : acc.filter(item => !keys.some(key => !!item[key])) +} \ No newline at end of file diff --git a/src/atlasViewer/pluginUnit/atlasViewer.pluginService.service.spec.ts b/src/plugin/atlasViewer.pluginService.service.spec.ts similarity index 98% rename from src/atlasViewer/pluginUnit/atlasViewer.pluginService.service.spec.ts rename to src/plugin/atlasViewer.pluginService.service.spec.ts index 9020d72d286d667fe88f174f3d75b9981968f594..fe239f32b6d5121562cd4bd19cc53f1f7bf356f4 100644 --- a/src/atlasViewer/pluginUnit/atlasViewer.pluginService.service.spec.ts +++ b/src/plugin/atlasViewer.pluginService.service.spec.ts @@ -1,7 +1,7 @@ import { CommonModule } from "@angular/common" import { HttpClientTestingModule, HttpTestingController } from "@angular/common/http/testing" import { NgModule } from "@angular/core" -import { async, fakeAsync, flushMicrotasks, TestBed, tick } from "@angular/core/testing" +import { async, fakeAsync, TestBed, tick } from "@angular/core/testing" import { MockStore, provideMockStore } from "@ngrx/store/testing" import { ComponentsModule } from "src/components" import { DialogService } from "src/services/dialogService.service" @@ -10,7 +10,7 @@ import { AngularMaterialModule } from "src/ui/sharedModules/angularMaterial.modu import { APPEND_SCRIPT_TOKEN, REMOVE_SCRIPT_TOKEN } from "src/util/constants" import { WidgetModule, WidgetServices } from "src/widget" import { PluginServices } from "./atlasViewer.pluginService.service" -import { PluginUnit } from "./pluginUnit.component" +import { PluginUnit } from "./pluginUnit/pluginUnit.component" const MOCK_PLUGIN_MANIFEST = { name: 'fzj.xg.MOCK_PLUGIN_MANIFEST', diff --git a/src/atlasViewer/pluginUnit/atlasViewer.pluginService.service.ts b/src/plugin/atlasViewer.pluginService.service.ts similarity index 99% rename from src/atlasViewer/pluginUnit/atlasViewer.pluginService.service.ts rename to src/plugin/atlasViewer.pluginService.service.ts index 8299f6cf59e61024c4f3dceb0898a63a46f301c6..52b100cb55d7c60e0a7b55eeb706d66329eab86f 100644 --- a/src/atlasViewer/pluginUnit/atlasViewer.pluginService.service.ts +++ b/src/plugin/atlasViewer.pluginService.service.ts @@ -1,7 +1,7 @@ import { HttpClient } from '@angular/common/http' import { ComponentFactory, ComponentFactoryResolver, Injectable, ViewContainerRef, Inject, SecurityContext } from "@angular/core"; import { PLUGINSTORE_ACTION_TYPES } from "src/services/state/pluginState.helper"; -import { PluginUnit } from "./pluginUnit.component"; +import { PluginUnit } from "./pluginUnit/pluginUnit.component"; import { select, Store } from "@ngrx/store"; import { BehaviorSubject, from, merge, Observable, of } from "rxjs"; import { catchError, filter, map, mapTo, shareReplay, switchMap, switchMapTo, take, tap } from "rxjs/operators"; diff --git a/src/plugin/index.ts b/src/plugin/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..bae5875b5d19c8b096188c6c8074e42a2ff14674 --- /dev/null +++ b/src/plugin/index.ts @@ -0,0 +1,9 @@ +export { + IPluginManifest, + PluginServices, + registerPluginFactoryDirectiveFactory, +} from './atlasViewer.pluginService.service' + +export { + PluginModule +} from './plugin.module' \ No newline at end of file diff --git a/src/atlasViewer/pluginUnit/plugin.module.ts b/src/plugin/plugin.module.ts similarity index 54% rename from src/atlasViewer/pluginUnit/plugin.module.ts rename to src/plugin/plugin.module.ts index 12763521faa0539bdf7eb38d7b275eba8263dcb7..5ba71ea0297eb761cc9a9fb71ecdeaaf6864ee3e 100644 --- a/src/atlasViewer/pluginUnit/plugin.module.ts +++ b/src/plugin/plugin.module.ts @@ -1,27 +1,36 @@ +import { CommonModule, DOCUMENT } from "@angular/common"; import { NgModule } from "@angular/core"; -import { PluginUnit } from "./pluginUnit.component"; +import { LoggingModule } from "src/logging"; +import { AngularMaterialModule } from "src/ui/sharedModules/angularMaterial.module"; +import { UtilModule } from "src/util"; +import { appendScriptFactory, APPEND_SCRIPT_TOKEN, removeScriptFactory, REMOVE_SCRIPT_TOKEN } from "src/util/constants"; import { PluginServices, registerPluginFactoryDirectiveFactory } from "./atlasViewer.pluginService.service"; +import { PluginBannerUI } from "./pluginBanner/pluginBanner.component"; +import { PluginCspCtrlCmp } from "./pluginCsp/pluginCsp.component"; import { PluginFactoryDirective, REGISTER_PLUGIN_FACTORY_DIRECTIVE } from "./pluginFactory.directive"; -import { LoggingModule } from "src/logging"; -import { APPEND_SCRIPT_TOKEN, appendScriptFactory, REMOVE_SCRIPT_TOKEN, removeScriptFactory } from "src/util/constants"; -import { DOCUMENT } from "@angular/common"; +import { PluginUnit } from "./pluginUnit/pluginUnit.component"; @NgModule({ - imports:[ + imports: [ + CommonModule, LoggingModule, + UtilModule, + AngularMaterialModule, ], declarations: [ + PluginCspCtrlCmp, PluginUnit, - PluginFactoryDirective - ], - entryComponents: [ - PluginUnit + PluginFactoryDirective, + PluginBannerUI, ], exports: [ + PluginCspCtrlCmp, + PluginBannerUI, PluginUnit, - PluginFactoryDirective + PluginFactoryDirective, ], providers: [ + PluginServices, { provide: REGISTER_PLUGIN_FACTORY_DIRECTIVE, @@ -40,5 +49,4 @@ import { DOCUMENT } from "@angular/common"; }, ] }) - export class PluginModule{} \ No newline at end of file diff --git a/src/ui/pluginBanner/pluginBanner.component.ts b/src/plugin/pluginBanner/pluginBanner.component.ts similarity index 90% rename from src/ui/pluginBanner/pluginBanner.component.ts rename to src/plugin/pluginBanner/pluginBanner.component.ts index c154f1b666a4aa1ba19cd4568eef5d91394f1888..59de1360d44b7db00fc1f9e5567371debf03b650 100644 --- a/src/ui/pluginBanner/pluginBanner.component.ts +++ b/src/plugin/pluginBanner/pluginBanner.component.ts @@ -1,5 +1,5 @@ import { Component, ViewChild, TemplateRef } from "@angular/core"; -import { IPluginManifest, PluginServices } from "src/atlasViewer/pluginUnit"; +import { IPluginManifest, PluginServices } from "../atlasViewer.pluginService.service"; import { MatDialog } from "@angular/material/dialog"; @Component({ diff --git a/src/ui/pluginBanner/pluginBanner.style.css b/src/plugin/pluginBanner/pluginBanner.style.css similarity index 100% rename from src/ui/pluginBanner/pluginBanner.style.css rename to src/plugin/pluginBanner/pluginBanner.style.css diff --git a/src/ui/pluginBanner/pluginBanner.template.html b/src/plugin/pluginBanner/pluginBanner.template.html similarity index 100% rename from src/ui/pluginBanner/pluginBanner.template.html rename to src/plugin/pluginBanner/pluginBanner.template.html diff --git a/src/ui/config/pluginCsp/pluginCsp.component.ts b/src/plugin/pluginCsp/pluginCsp.component.ts similarity index 87% rename from src/ui/config/pluginCsp/pluginCsp.component.ts rename to src/plugin/pluginCsp/pluginCsp.component.ts index 73a50c6b4dce3d579a52f110f01b6de31b833913..7ff3c8a1fcfcc7faa96237f9cbb9ad539104812c 100644 --- a/src/ui/config/pluginCsp/pluginCsp.component.ts +++ b/src/plugin/pluginCsp/pluginCsp.component.ts @@ -1,7 +1,7 @@ import { Component } from "@angular/core"; import { select, Store } from "@ngrx/store"; -import { map, tap } from "rxjs/operators"; -import { PluginServices } from "src/atlasViewer/pluginUnit"; +import { map } from "rxjs/operators"; +import { PluginServices } from "../atlasViewer.pluginService.service"; import { selectorAllPluginsCspPermission } from "src/services/state/userConfigState.store"; @Component({ diff --git a/src/ui/config/pluginCsp/pluginCsp.style.css b/src/plugin/pluginCsp/pluginCsp.style.css similarity index 100% rename from src/ui/config/pluginCsp/pluginCsp.style.css rename to src/plugin/pluginCsp/pluginCsp.style.css diff --git a/src/ui/config/pluginCsp/pluginCsp.template.html b/src/plugin/pluginCsp/pluginCsp.template.html similarity index 100% rename from src/ui/config/pluginCsp/pluginCsp.template.html rename to src/plugin/pluginCsp/pluginCsp.template.html diff --git a/src/atlasViewer/pluginUnit/pluginFactory.directive.spec.ts b/src/plugin/pluginFactory.directive.spec.ts similarity index 100% rename from src/atlasViewer/pluginUnit/pluginFactory.directive.spec.ts rename to src/plugin/pluginFactory.directive.spec.ts diff --git a/src/atlasViewer/pluginUnit/pluginFactory.directive.ts b/src/plugin/pluginFactory.directive.ts similarity index 100% rename from src/atlasViewer/pluginUnit/pluginFactory.directive.ts rename to src/plugin/pluginFactory.directive.ts diff --git a/src/atlasViewer/pluginUnit/pluginUnit.component.ts b/src/plugin/pluginUnit/pluginUnit.component.ts similarity index 100% rename from src/atlasViewer/pluginUnit/pluginUnit.component.ts rename to src/plugin/pluginUnit/pluginUnit.component.ts diff --git a/src/atlasViewer/pluginUnit/pluginUnit.template.html b/src/plugin/pluginUnit/pluginUnit.template.html similarity index 100% rename from src/atlasViewer/pluginUnit/pluginUnit.template.html rename to src/plugin/pluginUnit/pluginUnit.template.html diff --git a/src/res/css/extra_styles.css b/src/res/css/extra_styles.css index 081fa25ea672331dbdb2509fcaf98e891028bbf8..88e4a8d10e17d410aa6dcce4fbd3a842cb437766 100644 --- a/src/res/css/extra_styles.css +++ b/src/res/css/extra_styles.css @@ -381,6 +381,11 @@ markdown-dom pre code height: 2rem!important; } +.h-50vh +{ + height: 50vh!important; +} + .h-5em { height: 5em!important; diff --git a/src/services/effect/effect.spec.ts b/src/services/effect/effect.spec.ts index a414cb18e56ecaabce80113fb72a096f2819a4d6..7b58172fd1be56692d8c10c469ca22ba9aae54bd 100644 --- a/src/services/effect/effect.spec.ts +++ b/src/services/effect/effect.spec.ts @@ -1,5 +1,5 @@ import {} from 'jasmine' -import { getGetRegionFromLabelIndexId, UseEffects } from './effect' +import { UseEffects } from './effect' import { TestBed } from '@angular/core/testing' import { Observable } from 'rxjs' import { SELECT_PARCELLATION, NEWVIEWER, SELECT_REGIONS } from '../state/viewerState.store' @@ -8,27 +8,7 @@ import { hot } from 'jasmine-marbles' import { provideMockStore } from '@ngrx/store/testing' import { defaultRootState } from '../stateStore.service' -const colinsJson = require('!json-loader!../../res/ext/colin.json') - -const COLIN_JULICHBRAIN_LAYER_NAME = `COLIN_V25_LEFT_NG_SPLIT_HEMISPHERE` -const COLIN_V25_ID = 'minds/core/parcellationatlas/v1.0.0/94c1125b-b87e-45e4-901c-00daee7f2579-25' - describe('effect.ts', () => { - describe('getGetRegionFromLabelIndexId', () => { - it('translateds hoc1 from labelIndex to region', () => { - - const getRegionFromlabelIndexId = getGetRegionFromLabelIndexId({ - parcellation: { - ...colinsJson.parcellations.find(p => p['@id'] === COLIN_V25_ID), - updated: true, - }, - }) - const fetchedRegion = getRegionFromlabelIndexId({ labelIndexId: `${COLIN_JULICHBRAIN_LAYER_NAME}#116` }) - expect(fetchedRegion).toBeTruthy() - expect(fetchedRegion.fullId.kg.kgId).toEqual('c9753e82-80ca-4074-a704-9dd2c4c0d58b') - - }) - }) describe('UseEffects', () => { let actions$:Observable<any> diff --git a/src/services/effect/effect.ts b/src/services/effect/effect.ts index c206014533f352a8530b8b232b2b96141a05b8e8..965a48e2a9ff15af90efa9379252427e7b6aef0d 100644 --- a/src/services/effect/effect.ts +++ b/src/services/effect/effect.ts @@ -5,8 +5,10 @@ import { merge, Observable, Subscription, combineLatest } from "rxjs"; import { filter, map, shareReplay, switchMap, take, withLatestFrom, mapTo, distinctUntilChanged } from "rxjs/operators"; import { LoggingService } from "src/logging"; import { ADD_TO_REGIONS_SELECTION_WITH_IDS, DESELECT_REGIONS, NEWVIEWER, SELECT_PARCELLATION, SELECT_REGIONS, SELECT_REGIONS_WITH_ID, SELECT_LANDMARKS } from "../state/viewerState.store"; -import { generateLabelIndexId, getNgIdLabelIndexFromId, IavRootStoreInterface, recursiveFindRegionWithLabelIndexId } from '../stateStore.service'; +import { IavRootStoreInterface, recursiveFindRegionWithLabelIndexId } from '../stateStore.service'; import { viewerStateSelectAtlas, viewerStateSetSelectedRegionsWithIds, viewerStateToggleLayer } from "../state/viewerState.store.helper"; +import { deserialiseParcRegionId, serialiseParcellationRegion } from "common/util" +import { getGetRegionFromLabelIndexId } from 'src/util/fn' @Injectable({ providedIn: 'root', @@ -85,7 +87,7 @@ export class UseEffects implements OnDestroy { return { type: SELECT_REGIONS, selectRegions: alreadySelectedRegions - .filter(({ ngId, labelIndex }) => !deselectSet.has(generateLabelIndexId({ ngId, labelIndex }))), + .filter(({ ngId, labelIndex }) => !deselectSet.has(serialiseParcellationRegion({ ngId, labelIndex }))), } }), ) @@ -143,10 +145,10 @@ export class UseEffects implements OnDestroy { private convertRegionIdsToRegion = ([selectRegionIds, parcellation]) => { const { ngId: defaultNgId } = parcellation return (selectRegionIds as any[]) - .map(labelIndexId => getNgIdLabelIndexFromId({ labelIndexId })) + .map(labelIndexId => deserialiseParcRegionId(labelIndexId)) .map(({ ngId, labelIndex }) => { return { - labelIndexId: generateLabelIndexId({ + labelIndexId: serialiseParcellationRegion({ ngId: ngId || defaultNgId, labelIndex, }), @@ -246,13 +248,6 @@ export class UseEffects implements OnDestroy { ) } -export const getGetRegionFromLabelIndexId = ({ parcellation }) => { - const { ngId: defaultNgId, regions } = parcellation - // if (!updated) throw new Error(`parcellation not yet updated`) - return ({ labelIndexId }) => - recursiveFindRegionWithLabelIndexId({ regions, labelIndexId, inheritedNgId: defaultNgId }) -} - export const compareRegions: (r1: any, r2: any) => boolean = (r1, r2) => { if (!r1) { return !r2 } if (!r2) { return !r1 } diff --git a/src/services/effect/pluginUseEffect.spec.ts b/src/services/effect/pluginUseEffect.spec.ts index cda6dccb71cee8d819344a25946cd6c73369922c..693f8235226e9e2cca2514742404ff4b8d16a714 100644 --- a/src/services/effect/pluginUseEffect.spec.ts +++ b/src/services/effect/pluginUseEffect.spec.ts @@ -10,7 +10,7 @@ import { PLUGINSTORE_CONSTANTS } from '../state/pluginState.store' import { PLUGINSTORE_ACTION_TYPES } from '../state/pluginState.helper' import { Injectable } from "@angular/core"; import { getRandomHex } from 'common/util' -import { PluginServices } from "src/atlasViewer/pluginUnit"; +import { PluginServices } from "src/plugin"; import { AngularMaterialModule } from "src/ui/sharedModules/angularMaterial.module"; import { hot } from "jasmine-marbles"; diff --git a/src/services/effect/pluginUseEffect.ts b/src/services/effect/pluginUseEffect.ts index c6d0048d72f60925a8eead672b93a3419e215930..098d2fd6c02ead4545ff47aa1dcda9825150656c 100644 --- a/src/services/effect/pluginUseEffect.ts +++ b/src/services/effect/pluginUseEffect.ts @@ -4,7 +4,7 @@ import { select, Store } from "@ngrx/store" import { Observable, forkJoin } from "rxjs" import { filter, map, startWith, switchMap } from "rxjs/operators" import { AtlasViewerConstantsServices } from "src/atlasViewer/atlasViewer.constantService.service" -import { PluginServices } from "src/atlasViewer/pluginUnit" +import { PluginServices } from "src/plugin/atlasViewer.pluginService.service" import { PLUGINSTORE_CONSTANTS } from 'src/services/state/pluginState.store' import { PLUGINSTORE_ACTION_TYPES } from 'src/services/state/pluginState.helper' import { IavRootStoreInterface } from "../stateStore.service" diff --git a/src/services/localFile.service.ts b/src/services/localFile.service.ts index d4386edeca620ea7fc2ded16cd54623a265b6c97..045ac686b54f7b65e2985f7d75c2bce85ce705c6 100644 --- a/src/services/localFile.service.ts +++ b/src/services/localFile.service.ts @@ -1,6 +1,5 @@ import { Injectable } from "@angular/core"; import { Store } from "@ngrx/store"; -import { KgSingleDatasetService } from "src/ui/databrowserModule/kgSingleDatasetService.service"; import { SNACKBAR_MESSAGE } from "./state/uiState.store"; import { IavRootStoreInterface } from "./stateStore.service"; import { DATASETS_ACTIONS_TYPES } from "./state/dataStore.store"; @@ -19,12 +18,11 @@ export class LocalFileService { constructor( private store: Store<IavRootStoreInterface>, - private singleDsService: KgSingleDatasetService, ) { } - private niiUrl + private niiUrl: string public handleFileDrop(files: File[]) { try { diff --git a/src/services/state/ngViewerState.store.helper.ts b/src/services/state/ngViewerState.store.helper.ts index 2d0ca1500648a3654e7108cc7a22588055ac50ba..6f23c74d1c306a3b74e824fb69c7adc0743d0cc8 100644 --- a/src/services/state/ngViewerState.store.helper.ts +++ b/src/services/state/ngViewerState.store.helper.ts @@ -1,9 +1,7 @@ // TODO to be merged with ng viewer state after refactor -import { INgLayerInterface, PANELS } from './ngViewerState/constants' +export { INgLayerInterface, PANELS } from './ngViewerState/constants' -export { INgLayerInterface, PANELS } - -import { +export { ngViewerActionAddNgLayer, ngViewerActionRemoveNgLayer, ngViewerActionSetPerspOctantRemoval, @@ -14,29 +12,11 @@ import { } from './ngViewerState/actions' export { - ngViewerActionAddNgLayer, - ngViewerActionRemoveNgLayer, - ngViewerActionSetPerspOctantRemoval, - ngViewerActionToggleMax, - ngViewerActionClearView, - ngViewerActionSetPanelOrder, - ngViewerActionForceShowSegment, -} - -import { ngViewerSelectorClearView, ngViewerSelectorClearViewEntries, ngViewerSelectorNehubaReady, ngViewerSelectorOctantRemoval, ngViewerSelectorPanelMode, ngViewerSelectorPanelOrder, + ngViewerSelectorLayers, } from './ngViewerState/selectors' - -export { - ngViewerSelectorClearView, - ngViewerSelectorClearViewEntries, - ngViewerSelectorNehubaReady, - ngViewerSelectorOctantRemoval, - ngViewerSelectorPanelMode, - ngViewerSelectorPanelOrder, -} \ No newline at end of file diff --git a/src/services/state/ngViewerState.store.ts b/src/services/state/ngViewerState.store.ts index bc3b7fb8ce0abfcf2c3c1799a9fccf28238b71e3..1cbec3990ee43f0900b36201154cfd178b5e76a6 100644 --- a/src/services/state/ngViewerState.store.ts +++ b/src/services/state/ngViewerState.store.ts @@ -2,8 +2,7 @@ import { Injectable, OnDestroy } from '@angular/core'; import { Observable, combineLatest, fromEvent, Subscription, from, 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 { SNACKBAR_MESSAGE } from './uiState.store'; -import { getNgIds, IavRootStoreInterface, GENERAL_ACTION_TYPES } from '../stateStore.service'; +import { getNgIds } from 'src/util/fn'; import { Action, select, Store, createReducer, on } from '@ngrx/store' import { BACKENDURL, CYCLE_PANEL_MESSAGE } from 'src/util/constants'; import { HttpClient } from '@angular/common/http'; @@ -13,6 +12,7 @@ import { PANELS } from './ngViewerState.store.helper' import { ngViewerActionToggleMax, ngViewerActionClearView, ngViewerActionSetPanelOrder, ngViewerActionSwitchPanelMode, ngViewerActionForceShowSegment, ngViewerActionNehubaReady } from './ngViewerState/actions'; import { generalApplyState } from '../stateStore.helper'; import { ngViewerSelectorPanelMode, ngViewerSelectorPanelOrder } from './ngViewerState/selectors'; +import { uiActionSnackbarMessage } from './uiState/actions'; export function mixNgLayers(oldLayers: INgLayerInterface[], newLayers: INgLayerInterface|INgLayerInterface[]): INgLayerInterface[] { if (newLayers instanceof Array) { @@ -181,7 +181,7 @@ export class NgViewerUseEffect implements OnDestroy { constructor( private actions: Actions, - private store$: Store<IavRootStoreInterface>, + private store$: Store<any>, private pureConstantService: PureContantService, private http: HttpClient, ){ @@ -215,16 +215,14 @@ export class NgViewerUseEffect implements OnDestroy { withLatestFrom(this.store$), map(([{ngViewerState: fetchedNgViewerState}, state]) => { const { ngViewerState } = state - return { - type: GENERAL_ACTION_TYPES.APPLY_STATE, + return generalApplyState({ state: { ...state, ngViewerState: { ...ngViewerState, ...fetchedNgViewerState - } - } - } + }} + }) }) ) @@ -335,10 +333,9 @@ export class NgViewerUseEffect implements OnDestroy { filter(([_, useMobileUI]) => !useMobileUI), map(([toggleMaximiseMode, _]) => toggleMaximiseMode), filter(({ payload }) => payload.panelMode && payload.panelMode === PANELS.SINGLE_PANEL), - mapTo({ - type: SNACKBAR_MESSAGE, - snackbarMessage: CYCLE_PANEL_MESSAGE, - }), + mapTo(uiActionSnackbarMessage({ + snackbarMessage: CYCLE_PANEL_MESSAGE + })), ) this.spacebarListener$ = fromEvent(document.body, 'keydown', { capture: true }).pipe( diff --git a/src/services/state/ngViewerState/selectors.ts b/src/services/state/ngViewerState/selectors.ts index db84dcdb944e8e9f3d07b80a8d02464a492d4c3b..7222296d4627a32cf20716d05e2ea9e81d898e2e 100644 --- a/src/services/state/ngViewerState/selectors.ts +++ b/src/services/state/ngViewerState/selectors.ts @@ -35,3 +35,8 @@ export const ngViewerSelectorNehubaReady = createSelector( state => state['ngViewerState'], ngViewerState => ngViewerState.nehubaReady ) + +export const ngViewerSelectorLayers = createSelector( + state => state['ngViewerState'], + ngViewerState => ngViewerState?.layers || [] +) \ No newline at end of file diff --git a/src/services/state/pluginState.store.ts b/src/services/state/pluginState.store.ts index ac8ee486240d8bedce71d2b11d0218ec73b8cf5b..e71b49dc076b66bd375dbaddd2ede233d6e90fce 100644 --- a/src/services/state/pluginState.store.ts +++ b/src/services/state/pluginState.store.ts @@ -1,5 +1,5 @@ import { Action } from '@ngrx/store' -import { GENERAL_ACTION_TYPES } from '../stateStore.service' +import { generalApplyState } from '../stateStore.helper' import { PLUGINSTORE_ACTION_TYPES } from './pluginState.helper' export const defaultState: StateInterface = { initManifests: [] @@ -41,7 +41,7 @@ export const getStateStore = ({ state = defaultState } = {}) => (prevState: Stat initManifests: newManifests, } } - case GENERAL_ACTION_TYPES.APPLY_STATE: { + case generalApplyState.type: { const { pluginState } = (action as any).state return pluginState } diff --git a/src/services/state/uiState.store.helper.ts b/src/services/state/uiState.store.helper.ts index 94e404cb5359acaf491e7b321e60a57d1a77e0a6..0c0070f8d074b5ee25ded0830a9b48e9380bf84a 100644 --- a/src/services/state/uiState.store.helper.ts +++ b/src/services/state/uiState.store.helper.ts @@ -1,6 +1,6 @@ // TODO merge with uiState.store.ts after refactor completes -import { +export { uiActionSetPreviewingDatasetFiles, uiActionShowSidePanelConnectivity, uiStateCloseSidePanel, @@ -10,34 +10,16 @@ import { uiStateShowBottomSheet, uiActionHideDatasetWithId, uiActionShowDatasetWtihId, + uiActionSnackbarMessage, } from './uiState/actions' export { - uiActionSetPreviewingDatasetFiles, - uiActionShowSidePanelConnectivity, - uiStateCloseSidePanel, - uiStateCollapseSidePanel, - uiStateExpandSidePanel, - uiStateOpenSidePanel, - uiStateShowBottomSheet, - uiActionHideDatasetWithId, - uiActionShowDatasetWtihId, -} - -import { uiStatePreviewingDatasetFilesSelector, uiStateMouseOverSegmentsSelector, uiStateMouseoverUserLandmark, uiStateShownDatasetIdSelector, } from './uiState/selectors' -export { - uiStatePreviewingDatasetFilesSelector, - uiStateMouseOverSegmentsSelector, - uiStateMouseoverUserLandmark, - uiStateShownDatasetIdSelector, -} - export enum EnumWidgetTypes{ DATASET_PREVIEW, } diff --git a/src/services/state/uiState.store.ts b/src/services/state/uiState.store.ts index 5769426bfa2f5124e278d46fcdfbcc67af528650..a31d256de9126b42201220596aa7985cad63caab 100644 --- a/src/services/state/uiState.store.ts +++ b/src/services/state/uiState.store.ts @@ -10,7 +10,7 @@ import { MatBottomSheetRef, MatBottomSheet } from '@angular/material/bottom-shee import { uiStateCloseSidePanel, uiStateOpenSidePanel, uiStateCollapseSidePanel, uiStateExpandSidePanel, uiActionSetPreviewingDatasetFiles, uiStateShowBottomSheet, uiActionShowSidePanelConnectivity } from './uiState.store.helper'; import { viewerStateMouseOverCustomLandmark } from './viewerState/actions'; import { IUiState } from './uiState/common' -import { uiActionHideAllDatasets, uiActionHideDatasetWithId, uiActionShowDatasetWtihId } from './uiState/actions'; +import { uiActionHideAllDatasets, uiActionHideDatasetWithId, uiActionShowDatasetWtihId, uiActionSnackbarMessage } from './uiState/actions'; export const defaultState: IUiState = { shownDatasetId: [], @@ -92,6 +92,7 @@ export const getStateStore = ({ state = defaultState } = {}) => (prevState: IUiS ...prevState, mouseOverLandmark : action.landmark, } + case uiActionSnackbarMessage.type: case SNACKBAR_MESSAGE: { const { snackbarMessage } = action /** @@ -270,5 +271,5 @@ export const AGREE_COOKIE = `AGREE_COOKIE` export const AGREE_KG_TOS = `AGREE_KG_TOS` export const SHOW_KG_TOS = `SHOW_KG_TOS` -export const SNACKBAR_MESSAGE = `SNACKBAR_MESSAGE` +export const SNACKBAR_MESSAGE = uiActionSnackbarMessage.type export const SHOW_BOTTOM_SHEET = `SHOW_BOTTOM_SHEET` diff --git a/src/services/state/uiState/actions.ts b/src/services/state/uiState/actions.ts index bfd0548766e1ce870b3747b155805657826316dc..961511e110429f46b0a5e850b06b720c44e47a9d 100644 --- a/src/services/state/uiState/actions.ts +++ b/src/services/state/uiState/actions.ts @@ -46,3 +46,8 @@ export const uiActionHideDatasetWithId = createAction( export const uiActionHideAllDatasets = createAction( `[uiState] hideAllDatasets` ) + +export const uiActionSnackbarMessage = createAction( + `[uiState] snackbarMessage`, + props<{snackbarMessage: string}>() +) \ No newline at end of file diff --git a/src/services/state/viewerState.store.ts b/src/services/state/viewerState.store.ts index 25adedb5c6eb7fecba4215550f1376b9379c4d72..543350c50da30bb7cf5c2f0d33a303c3d3322cd3 100644 --- a/src/services/state/viewerState.store.ts +++ b/src/services/state/viewerState.store.ts @@ -7,13 +7,12 @@ import { IUserLandmark } from 'src/atlasViewer/atlasViewer.apiService.service'; import { INgLayerInterface } from 'src/atlasViewer/atlasViewer.component'; import { getViewer } from 'src/util/fn'; import { LoggingService } from 'src/logging'; -import { generateLabelIndexId, IavRootStoreInterface } from '../stateStore.service'; +import { IavRootStoreInterface } from '../stateStore.service'; import { GENERAL_ACTION_TYPES } from '../stateStore.service' import { CLOSE_SIDE_PANEL } from './uiState.store'; import { viewerStateSetSelectedRegions, viewerStateSetConnectivityRegion, - viewerStateSelectAtlas, viewerStateSelectParcellation, viewerStateSelectRegionWithIdDeprecated, viewerStateCustomLandmarkSelector, @@ -24,8 +23,9 @@ import { viewerStateMouseOverCustomLandmarkInPerspectiveView, viewerStateNewViewer } from './viewerState.store.helper'; -import { cvtNehubaConfigToNavigationObj } from 'src/ui/viewerStateController/viewerState.useEffect'; +import { cvtNehubaConfigToNavigationObj } from 'src/state'; import { viewerStateChangeNavigation } from './viewerState/actions'; +import { serialiseParcellationRegion } from "common/util" export interface StateInterface { fetchedTemplates: any[] @@ -378,8 +378,8 @@ export class ViewerStateUseEffect { startWith([]), )), map(([{ segments }, regionsSelected]) => { - const selectedSet = new Set(regionsSelected.map(generateLabelIndexId)) - const toggleArr = segments.map(({ segment, layer }) => generateLabelIndexId({ ngId: layer.name, ...segment })) + const selectedSet = new Set(regionsSelected.map(serialiseParcellationRegion)) + const toggleArr = segments.map(({ segment, layer }) => serialiseParcellationRegion({ ngId: layer.name, ...segment })) const deleteFlag = toggleArr.some(id => selectedSet.has(id)) diff --git a/src/services/stateStore.service.ts b/src/services/stateStore.service.ts index 209c3a2a9fab036f695cda969de68b7514bdacaf..fab546d7876dee4ec5a2d06c7285753715ba0381 100644 --- a/src/services/stateStore.service.ts +++ b/src/services/stateStore.service.ts @@ -1,9 +1,5 @@ import { filter } from 'rxjs/operators'; -export { - serialiseParcellationRegion as generateLabelIndexId, - deserialiseParcRegionId as getNgIdLabelIndexFromId, -} from 'common/util' export { recursiveFindRegionWithLabelIndexId } from 'src/util/fn' @@ -71,12 +67,6 @@ export function safeFilter(key: string) { typeof state[key] !== 'undefined' && state[key] !== null) } -export function getNgIdLabelIndexFromRegion({ region }) { - const { ngId, labelIndex } = region - if (ngId && labelIndex) { return { ngId, labelIndex } } - throw new Error(`ngId: ${ngId} or labelIndex: ${labelIndex} not defined`) -} - export function getMultiNgIdsRegionsLabelIndexMap(parcellation: any = {}, inheritAttrsOpt: any = { ngId: 'root' }): Map<string, Map<number, any>> { const map: Map<string, Map<number, any>> = new Map() @@ -167,7 +157,7 @@ export interface IavRootStoreInterface { userConfigState: UserConfigStateInterface } -import { DATASTORE_DEFAULT_STATE } from 'src/ui/databrowserModule' +import { DATASTORE_DEFAULT_STATE } from 'src/atlasComponents/databrowserModule' export const defaultRootState: any = { pluginState: pluginDefaultState, diff --git a/src/spotlight/sl-service.service.ts b/src/spotlight/sl-service.service.ts index c045ebfdbeb4ef5f2e5f1c682bf814cd7718dda1..241fe068cb335726119d8532d31d3a06ff6351f1 100644 --- a/src/spotlight/sl-service.service.ts +++ b/src/spotlight/sl-service.service.ts @@ -21,6 +21,9 @@ export class SlServiceService implements OnDestroy{ this.cf = cfr.resolveComponentFactory(SpotlightBackdropComponent) } + /** + * TODO use angular cdk overlay + */ public showBackdrop(tmp?: TemplateRef<any>){ this.hideBackdrop() diff --git a/src/ui/viewerStateController/viewerState.useEffect.spec.ts b/src/state/effects/viewerState.useEffect.spec.ts similarity index 99% rename from src/ui/viewerStateController/viewerState.useEffect.spec.ts rename to src/state/effects/viewerState.useEffect.spec.ts index b38a7db90370f82ebe1c53b4811cefb053e0555a..1a82e4220584adcf038f32b8f47049bbdd9f7313 100644 --- a/src/ui/viewerStateController/viewerState.useEffect.spec.ts +++ b/src/state/effects/viewerState.useEffect.spec.ts @@ -7,10 +7,10 @@ import { defaultRootState, generalActionError } from 'src/services/stateStore.se import { Injectable } from '@angular/core' import { TemplateCoordinatesTransformation, ITemplateCoordXformResp } from 'src/services/templateCoordinatesTransformation.service' import { hot } from 'jasmine-marbles' -import { AngularMaterialModule } from '../sharedModules/angularMaterial.module' +import { AngularMaterialModule } from 'src/ui/sharedModules/angularMaterial.module' import { HttpClientModule } from '@angular/common/http' import { WidgetModule } from 'src/widget' -import { PluginModule } from 'src/atlasViewer/pluginUnit/plugin.module' +import { PluginModule } from 'src/plugin' import { viewerStateFetchedTemplatesSelector, viewerStateNavigateToRegion, viewerStateNavigationStateSelector, viewerStateNewViewer, viewerStateSelectAtlas, viewerStateSelectTemplateWithName } from 'src/services/state/viewerState.store.helper' import { viewerStateFetchedAtlasesSelector } from 'src/services/state/viewerState/selectors' import { CONST } from 'common/constants' diff --git a/src/ui/viewerStateController/viewerState.useEffect.ts b/src/state/effects/viewerState.useEffect.ts similarity index 94% rename from src/ui/viewerStateController/viewerState.useEffect.ts rename to src/state/effects/viewerState.useEffect.ts index 18eef3feafbaebc9d4bdf05b60447a6619ce870c..a56794c215c671f56b71abbecd05b55f3226d826 100644 --- a/src/ui/viewerStateController/viewerState.useEffect.ts +++ b/src/state/effects/viewerState.useEffect.ts @@ -3,8 +3,7 @@ import { Actions, Effect, ofType } from "@ngrx/effects"; import { Action, select, Store } from "@ngrx/store"; import { Observable, Subscription, of, merge } from "rxjs"; import { distinctUntilChanged, filter, map, shareReplay, withLatestFrom, switchMap, mapTo, startWith } from "rxjs/operators"; -import { CHANGE_NAVIGATION, FETCHED_TEMPLATE, IavRootStoreInterface, SELECT_PARCELLATION, SELECT_REGIONS, generalActionError } from "src/services/stateStore.service"; -import { VIEWERSTATE_CONTROLLER_ACTION_TYPES } from "./viewerState.base"; +import { FETCHED_TEMPLATE, IavRootStoreInterface, SELECT_PARCELLATION, SELECT_REGIONS, generalActionError } from "src/services/stateStore.service"; import { TemplateCoordinatesTransformation } from "src/services/templateCoordinatesTransformation.service"; import { CLEAR_STANDALONE_VOLUMES } from "src/services/state/viewerState.store"; import { viewerStateToggleRegionSelect, viewerStateHelperSelectParcellationWithId, viewerStateSelectTemplateWithId, viewerStateNavigateToRegion, viewerStateSelectedTemplateSelector, viewerStateFetchedTemplatesSelector, viewerStateNewViewer, viewerStateSelectedParcellationSelector, viewerStateNavigationStateSelector, viewerStateSelectTemplateWithName, viewerStateSelectedRegionsSelector, viewerStateSelectAtlas } from "src/services/state/viewerState.store.helper"; @@ -15,6 +14,7 @@ import { verifyPositionArg } from 'common/util' import { CONST } from 'common/constants' import { uiActionHideAllDatasets } from "src/services/state/uiState/actions"; import { viewerStateFetchedAtlasesSelector } from "src/services/state/viewerState/selectors"; +import { viewerStateChangeNavigation } from "src/services/state/viewerState/actions"; const defaultPerspectiveZoom = 1e6 const defaultZoom = 1e6 @@ -385,22 +385,6 @@ export class ViewerStateControllerUseEffect implements OnDestroy { map(({ payload }) => payload['@id']) ), - /** - * deprecated method... - * finding id from name - */ - this.actions$.pipe( - ofType(VIEWERSTATE_CONTROLLER_ACTION_TYPES.SELECT_PARCELLATION_WITH_NAME), - withLatestFrom(viewerState$.pipe( - select('templateSelected') - )), - map(([ action, templateSelected ]) => { - const parcellationName = (action as any).payload.name - const foundParcellation = templateSelected.parcellations.find(p => p.name === parcellationName) - return foundParcellation && foundParcellation['@id'] - }), - filter(v => !!v) - ) ).pipe( withLatestFrom(viewerState$.pipe( select('templateSelected'), @@ -444,13 +428,12 @@ export class ViewerStateControllerUseEffect implements OnDestroy { }) } - return { - type: CHANGE_NAVIGATION, + return viewerStateChangeNavigation({ navigation: { position, animation: {}, - }, - } + } + }) }), ) diff --git a/src/state/index.ts b/src/state/index.ts index d5efade12f4641c39c4a808d81c7eb7e531ba31b..755a484148037e78de1a8536176c408ddc46b81b 100644 --- a/src/state/index.ts +++ b/src/state/index.ts @@ -1 +1,6 @@ -export { StateModule } from './state.module' +export { StateModule } from "./state.module" +export { + ViewerStateControllerUseEffect, + cvtNavigationObjToNehubaConfig, + cvtNehubaConfigToNavigationObj, +} from "./effects/viewerState.useEffect" \ No newline at end of file diff --git a/src/state/stateAggregator.directive.ts b/src/state/stateAggregator.directive.ts index c9fd436c3b8ee3370b4df72acee3174c18769fae..fdf9bd9b4dce4ddbb4aaf3334305b06f1c2de0fa 100644 --- a/src/state/stateAggregator.directive.ts +++ b/src/state/stateAggregator.directive.ts @@ -2,7 +2,6 @@ import { Directive } from "@angular/core"; import { Store } from "@ngrx/store"; import { Observable } from "rxjs"; import { map, debounceTime, shareReplay } from "rxjs/operators"; -import { IavRootStoreInterface } from "src/services/stateStore.service"; import { cvtStateToSearchParam } from "src/atlasViewer/atlasViewer.urlUtil"; const jsonVersion = '0.0.1' @@ -21,7 +20,7 @@ export class StateAggregator{ public jsonifiedSstate$: Observable<IJsonifiedState> constructor( - private store$: Store<IavRootStoreInterface> + private store$: Store<any> ){ this.jsonifiedSstate$ = this.store$.pipe( debounceTime(100), diff --git a/src/ui/btnShadow.style.css b/src/ui/btnShadow.style.css deleted file mode 100644 index 3d4bb9c2dd3ef38fcaa74e5deb5406132ffbde03..0000000000000000000000000000000000000000 --- a/src/ui/btnShadow.style.css +++ /dev/null @@ -1,23 +0,0 @@ -.btnWrapper -{ - display:flex; - align-items: center; - justify-content: center; -} - - -.btnWrapper > .btn -{ - width: 2.5em; - height: 2.5em; - - display: flex; - align-items: center; - justify-content: center; -} - -.btnWrapper.btnWrapper-lg > .btn -{ - width: 5rem; - height: 5rem; -} \ No newline at end of file diff --git a/src/ui/config/config.component.ts b/src/ui/config/configCmp/config.component.ts similarity index 98% rename from src/ui/config/config.component.ts rename to src/ui/config/configCmp/config.component.ts index 4127b3bf990e732f1fccd4d94b69a5a0306d18f2..15f92277c707e22aeeb25cb64dec3e562f907030 100644 --- a/src/ui/config/config.component.ts +++ b/src/ui/config/configCmp/config.component.ts @@ -6,7 +6,7 @@ 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 '../nehubaContainer/util'; +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'; diff --git a/src/ui/config/config.style.css b/src/ui/config/configCmp/config.style.css similarity index 100% rename from src/ui/config/config.style.css rename to src/ui/config/configCmp/config.style.css diff --git a/src/ui/config/config.template.html b/src/ui/config/configCmp/config.template.html similarity index 100% rename from src/ui/config/config.template.html rename to src/ui/config/configCmp/config.template.html diff --git a/src/ui/helpOnePager/helpOnePager.style.css b/src/ui/config/index.ts similarity index 100% rename from src/ui/helpOnePager/helpOnePager.style.css rename to src/ui/config/index.ts diff --git a/src/ui/config/module.ts b/src/ui/config/module.ts new file mode 100644 index 0000000000000000000000000000000000000000..34ed41cb182f81d22a42905f06a6ceddddf462f0 --- /dev/null +++ b/src/ui/config/module.ts @@ -0,0 +1,22 @@ +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { LayoutModule } from "src/layouts/layout.module"; +import { PluginModule } from "src/plugin"; +import { AngularMaterialModule } from "../sharedModules/angularMaterial.module"; +import { ConfigComponent } from "./configCmp/config.component"; + +@NgModule({ + imports: [ + CommonModule, + AngularMaterialModule, + PluginModule, + LayoutModule, + ], + declarations: [ + ConfigComponent, + ], + exports: [ + ConfigComponent, + ] +}) +export class ConfigModule{} \ No newline at end of file diff --git a/src/ui/cookieAgreement/cookieAgreement.component.ts b/src/ui/cookieAgreement/cookieAgreement/cookieAgreement.component.ts similarity index 81% rename from src/ui/cookieAgreement/cookieAgreement.component.ts rename to src/ui/cookieAgreement/cookieAgreement/cookieAgreement.component.ts index 55f114b49121f2f7bc8c7e8d879a672346eaf4f8..fe1643a45e2a5152199d7aea5211695183b44310 100644 --- a/src/ui/cookieAgreement/cookieAgreement.component.ts +++ b/src/ui/cookieAgreement/cookieAgreement/cookieAgreement.component.ts @@ -1,7 +1,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core' -import info from '!!raw-loader!./data/info.md' -import readmore from '!!raw-loader!./data/readmore.md' -import matomoInfo from '!!raw-loader!./data/aboutMatomo.md' +import info from '!!raw-loader!../data/info.md' +import readmore from '!!raw-loader!../data/readmore.md' +import matomoInfo from '!!raw-loader!../data/aboutMatomo.md' @Component({ selector: 'cookie-agreement', diff --git a/src/ui/cookieAgreement/cookieAgreement.style.css b/src/ui/cookieAgreement/cookieAgreement/cookieAgreement.style.css similarity index 100% rename from src/ui/cookieAgreement/cookieAgreement.style.css rename to src/ui/cookieAgreement/cookieAgreement/cookieAgreement.style.css diff --git a/src/ui/cookieAgreement/cookieAgreement.template.html b/src/ui/cookieAgreement/cookieAgreement/cookieAgreement.template.html similarity index 100% rename from src/ui/cookieAgreement/cookieAgreement.template.html rename to src/ui/cookieAgreement/cookieAgreement/cookieAgreement.template.html diff --git a/src/ui/kgtos/kgtos.style.css b/src/ui/cookieAgreement/index.ts similarity index 100% rename from src/ui/kgtos/kgtos.style.css rename to src/ui/cookieAgreement/index.ts diff --git a/src/ui/cookieAgreement/module.ts b/src/ui/cookieAgreement/module.ts new file mode 100644 index 0000000000000000000000000000000000000000..8ee42e1697f8f932fc6545c8a010bbe5c5cda853 --- /dev/null +++ b/src/ui/cookieAgreement/module.ts @@ -0,0 +1,23 @@ +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { ComponentsModule } from "src/components"; +import { UtilModule } from "src/util"; +import { AngularMaterialModule } from "../sharedModules/angularMaterial.module"; +import { CookieAgreement } from "./cookieAgreement/cookieAgreement.component"; + +@NgModule({ + imports: [ + CommonModule, + AngularMaterialModule, + UtilModule, + ComponentsModule, + ], + declarations: [ + CookieAgreement, + ], + exports: [ + CookieAgreement + ] +}) + +export class CookieModule{} \ No newline at end of file diff --git a/src/ui/help/help.component.ts b/src/ui/help/about/about.component.ts similarity index 89% rename from src/ui/help/help.component.ts rename to src/ui/help/about/about.component.ts index af6c46dff917b60fcf74c86fcb89a5d62eb6de54..f3e35041d0b2dc7a32a98b32438805e0d029d1ff 100644 --- a/src/ui/help/help.component.ts +++ b/src/ui/help/about/about.component.ts @@ -3,14 +3,14 @@ import { DomSanitizer } from '@angular/platform-browser'; import { AtlasViewerConstantsServices } from 'src/atlasViewer/atlasViewer.constantService.service'; @Component({ - selector: 'help-component', - templateUrl: './help.template.html', + selector: 'iav-about', + templateUrl: './about.template.html', styleUrls: [ - './help.style.css', + './about.style.css', ], }) -export class HelpComponent { +export class AboutCmp { public generalHelp = this.constantService.showHelpGeneralMap public sliceviewHelp = this.constantService.showHelpSliceViewMap diff --git a/src/ui/help/help.style.css b/src/ui/help/about/about.style.css similarity index 100% rename from src/ui/help/help.style.css rename to src/ui/help/about/about.style.css diff --git a/src/ui/help/help.template.html b/src/ui/help/about/about.template.html similarity index 100% rename from src/ui/help/help.template.html rename to src/ui/help/about/about.template.html diff --git a/src/ui/helpOnePager/helpOnePager.component.spec.ts b/src/ui/help/helpOnePager/helpOnePager.component.spec.ts similarity index 100% rename from src/ui/helpOnePager/helpOnePager.component.spec.ts rename to src/ui/help/helpOnePager/helpOnePager.component.spec.ts diff --git a/src/ui/helpOnePager/helpOnePager.component.ts b/src/ui/help/helpOnePager/helpOnePager.component.ts similarity index 100% rename from src/ui/helpOnePager/helpOnePager.component.ts rename to src/ui/help/helpOnePager/helpOnePager.component.ts diff --git a/src/ui/nehubaContainer/maximisePanelButton/maximisePanelButton.style.css b/src/ui/help/helpOnePager/helpOnePager.style.css similarity index 100% rename from src/ui/nehubaContainer/maximisePanelButton/maximisePanelButton.style.css rename to src/ui/help/helpOnePager/helpOnePager.style.css diff --git a/src/ui/helpOnePager/helpOnePager.template.html b/src/ui/help/helpOnePager/helpOnePager.template.html similarity index 100% rename from src/ui/helpOnePager/helpOnePager.template.html rename to src/ui/help/helpOnePager/helpOnePager.template.html diff --git a/src/ui/templateParcellationCitations/templateParcellationCitations.style.css b/src/ui/help/index.ts similarity index 100% rename from src/ui/templateParcellationCitations/templateParcellationCitations.style.css rename to src/ui/help/index.ts diff --git a/src/ui/help/module.ts b/src/ui/help/module.ts new file mode 100644 index 0000000000000000000000000000000000000000..4a34ab43a3cf9efa449eb2edcd2150921a53e744 --- /dev/null +++ b/src/ui/help/module.ts @@ -0,0 +1,26 @@ +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { ComponentsModule } from "src/components"; +import { UtilModule } from "src/util"; +import { AngularMaterialModule } from "../sharedModules/angularMaterial.module"; +import { AboutCmp } from './about/about.component' +import { HelpOnePager } from "./helpOnePager/helpOnePager.component"; + +@NgModule({ + imports: [ + CommonModule, + AngularMaterialModule, + ComponentsModule, + UtilModule, + ], + declarations: [ + AboutCmp, + HelpOnePager, + ], + exports: [ + AboutCmp, + HelpOnePager, + ] +}) + +export class HelpModule{} \ No newline at end of file diff --git a/src/ui/kgEntryViewer/kgentry.component.ts b/src/ui/kgEntryViewer/kgentry.component.ts deleted file mode 100644 index fada06ab098aafc41acc8f27848455897c1258e0..0000000000000000000000000000000000000000 --- a/src/ui/kgEntryViewer/kgentry.component.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Component, Input } from "@angular/core"; -import { IDataEntry } from "src/services/stateStore.service"; - -@Component({ - selector : 'kg-entry-viewer', - templateUrl : './kgentry.template.html', - styleUrls : [ - './kgentry.style.css', - ], -}) - -export class KgEntryViewer { - @Input() public dataset: IDataEntry - - public kgData: any = null - public kgError: any = null - - get tableColClass1() { - return `col-xs-4 col-lg-4 tableEntry` - } - - get tableColClass2() { - return `col-xs-8 col-lg-8 tableEntry` - } - - public isArray(v) { - return v.constructor === Array - } -} diff --git a/src/ui/kgEntryViewer/kgentry.style.css b/src/ui/kgEntryViewer/kgentry.style.css deleted file mode 100644 index 4ac3fc6f4bd8f118c0339fff562389ee4d18239c..0000000000000000000000000000000000000000 --- a/src/ui/kgEntryViewer/kgentry.style.css +++ /dev/null @@ -1,34 +0,0 @@ -div[heading], -div[body] -{ - padding: 0.5rem; -} - -div[heading] -{ - background-color:rgba(128,128,128,0.1); -} - -div[body] -{ - font-size: 90%; - max-height:20em; - overflow-y:auto; - overflow-x:hidden; -} - -div[container] -{ - overflow:hidden; -} - -a[link] -{ - display: inline-block; - margin-top: 1rem; -} - -a[link]:hover -{ - text-decoration: none; -} \ No newline at end of file diff --git a/src/ui/kgEntryViewer/kgentry.template.html b/src/ui/kgEntryViewer/kgentry.template.html deleted file mode 100644 index cb309ebd9a5bc6fc307968557c3e7357ef8b0faf..0000000000000000000000000000000000000000 --- a/src/ui/kgEntryViewer/kgentry.template.html +++ /dev/null @@ -1,84 +0,0 @@ -<div *ngIf="dataset" container> - - <!-- description --> - <readmore-component> - {{ dataset.description }} - </readmore-component> - - <!-- other data --> - - <panel-component *ngIf = "false" [collapseBody] = "true" [bodyCollapsable] = "true"> - <div heading> - Contributor(s) - </div> - <div body> - <!-- TODO maybe object instead of array --> - <div *ngFor = "let contributor of dataset.contributors"> - {{ contributor.value }} - </div> - </div> - </panel-component> - - <panel-component [collapseBody] = "true" [bodyCollapsable] = "true"> - <div heading> - Custodian(s) - </div> - <div body> - <!-- TODO Maybe array --> - <div *ngFor="let custodian of dataset.custodians"> - {{ custodian }} - </div> - </div> - </panel-component> - - <panel-component *ngIf = "dataset.publications" [collapseBody] = "true" [bodyCollapsable] = "true"> - <div heading> - Related Publication(s) - </div> - <div body> - <div *ngFor="let publication of dataset.publications"> - <a target="_blank" link [href]="'https://doi.org/' + publication.doi"> - {{ publication.cite }} - <i class = "fas fa-new-window"></i> - </a> - </div> - </div> - </panel-component> - - <panel-component *ngIf = "dataset.licenseInfo || dataset.license" [collapseBody] = "true" [bodyCollapsable] = "true"> - <div heading> - License - </div> - <div body> - <div *ngFor="let l of dataset.license"> - {{ l }} - </div> - <div *ngFor="let l of dataset.licenseInfo"> - {{ l }} - </div> - </div> - </panel-component> - - <panel-component *ngIf = "dataset.files" [collapseBody] = "true" [bodyCollapsable] = "true"> - <div heading> - Files - </div> - <div body> - <div *ngFor="let file of dataset.files"> - <a link target="_blank" [href]="file.absolutePath"> - {{ file.name }} - <i class="fas fa-download-alt"></i> - </a> - </div> - </div> - </panel-component> - - <a - *ngFor="let ref of dataset.kgReference" - [href]="'https://doi.org/' + ref" - target="_blank"> - Open in Knowledge Graph - <i class = "fas fa-new-window"></i> - </a> - -</div> \ No newline at end of file diff --git a/src/ui/kgEntryViewer/subjectViewer/subjectViewer.component.ts b/src/ui/kgEntryViewer/subjectViewer/subjectViewer.component.ts deleted file mode 100644 index 6b0a80cd18cd8dfa92517a54125e5fde870d6a2d..0000000000000000000000000000000000000000 --- a/src/ui/kgEntryViewer/subjectViewer/subjectViewer.component.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { ChangeDetectionStrategy, Component, Input } from "@angular/core"; - -@Component({ - selector : 'kg-entry-viewer-subject-viewer', - templateUrl : './subjectViewer.template.html', - styleUrls : ['./subjectViewer.style.css'], - changeDetection : ChangeDetectionStrategy.OnPush, -}) - -export class SubjectViewer { - @Input() public subjects: any = [] - - get isSingle(): boolean { - return this.subjects.constructor !== Array - } - - get species(): string[] { - return this.isSingle - ? [this.subjects.children.species.value] - : this.subjects.reduce((acc: string[], curr: any) => - acc.findIndex(species => species === curr.children.species.value) >= 0 - ? acc - : acc.concat(curr.children.species.value) - , []) - } - - get groupBySex() { - return this.isSingle - ? [{ - name : this.subjects.children.sex.value, - count : 1, - }] - : this.subjects.reduce((acc: any[], curr) => - acc.findIndex(item => item.name === curr.children.sex.value) >= 0 - ? acc.map(item => item.name === curr.children.sex.value - ? Object.assign({}, item, { count: item.count + 1 }) - : item) - : acc.concat({name: curr.children.sex.value, count: 1}) - , []) - } -} diff --git a/src/ui/kgEntryViewer/subjectViewer/subjectViewer.style.css b/src/ui/kgEntryViewer/subjectViewer/subjectViewer.style.css deleted file mode 100644 index 974639e6c80263ea3c2277365e3bbc06e995db8e..0000000000000000000000000000000000000000 --- a/src/ui/kgEntryViewer/subjectViewer/subjectViewer.style.css +++ /dev/null @@ -1,4 +0,0 @@ -div.row:nth-child(even) -{ - background-color:rgba(128,128,128,0.2); -} \ No newline at end of file diff --git a/src/ui/kgEntryViewer/subjectViewer/subjectViewer.template.html b/src/ui/kgEntryViewer/subjectViewer/subjectViewer.template.html deleted file mode 100644 index e295f00d6711c2dd3902289a8ea6e695b471b6b6..0000000000000000000000000000000000000000 --- a/src/ui/kgEntryViewer/subjectViewer/subjectViewer.template.html +++ /dev/null @@ -1,32 +0,0 @@ -<div> - - <div class = "row"> - <div class = "col-xs-4 col-lg-4"> - Species: - </div> - <div class = "col-xs-8 col-lg-8"> - {{ species.join(',') }} - </div> - </div> - - <div class = "row"> - <div class = "col-xs-4 col-lg-4"> - Number of Subjects: - </div> - <div class = "col-xs-8 col-lg-8"> - {{ subjects.length }} - </div> - </div> - - <div class = "row"> - <div class = "col-xs-4 col-lg-4"> - Sex: - </div> - <div class = "col-xs-8 col-lg-8"> - <div *ngFor = "let sexGroup of groupBySex"> - {{ sexGroup.name }} (n = {{ sexGroup.count }}) - </div> - </div> - </div> - -</div> \ No newline at end of file diff --git a/src/ui/kgtos/index.ts b/src/ui/kgtos/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..d683b099119fbee7cb1f239686943c2fa0923a16 --- /dev/null +++ b/src/ui/kgtos/index.ts @@ -0,0 +1,4 @@ +import { InjectionToken } from "@angular/core"; +import { Observable } from "rxjs"; + +export const TOS_OBS_INJECTION_TOKEN = new InjectionToken<Observable<string>>('TOS_STRING') diff --git a/src/ui/kgtos/kgtos.component.ts b/src/ui/kgtos/kgtos/kgtos.component.ts similarity index 66% rename from src/ui/kgtos/kgtos.component.ts rename to src/ui/kgtos/kgtos/kgtos.component.ts index 52a3a4239f2c5a636fa0becdd1306f9621fa8921..8f968d8ee2269f96124a945cd9f7ddf8725b7804 100644 --- a/src/ui/kgtos/kgtos.component.ts +++ b/src/ui/kgtos/kgtos/kgtos.component.ts @@ -1,7 +1,6 @@ -import { Component, Inject, InjectionToken, Optional } from "@angular/core"; +import { Component, Inject, Optional } from "@angular/core"; import { Observable } from "rxjs"; - -export const TOS_OBS_INJECTION_TOKEN = new InjectionToken<Observable<string>>('TOS_STRING') +import { TOS_OBS_INJECTION_TOKEN } from ".."; @Component({ selector: 'kgtos-component', diff --git a/src/ui/viewerStateController/viewerStateCMini/viewerStateMini.style.css b/src/ui/kgtos/kgtos/kgtos.style.css similarity index 100% rename from src/ui/viewerStateController/viewerStateCMini/viewerStateMini.style.css rename to src/ui/kgtos/kgtos/kgtos.style.css diff --git a/src/ui/kgtos/kgtos.template.html b/src/ui/kgtos/kgtos/kgtos.template.html similarity index 100% rename from src/ui/kgtos/kgtos.template.html rename to src/ui/kgtos/kgtos/kgtos.template.html diff --git a/src/ui/kgtos/module.ts b/src/ui/kgtos/module.ts new file mode 100644 index 0000000000000000000000000000000000000000..dea1b4f60392d590f7210bee4df1a609eaaeb900 --- /dev/null +++ b/src/ui/kgtos/module.ts @@ -0,0 +1,19 @@ +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { ComponentsModule } from "src/components"; +import { KGToS } from "./kgtos/kgtos.component"; + +@NgModule({ + imports: [ + CommonModule, + ComponentsModule, + ], + declarations: [ + KGToS + ], + exports: [ + KGToS + ] +}) + +export class KgTosModule{} \ No newline at end of file diff --git a/src/ui/landmarkUI/landmarkUI.component.ts b/src/ui/landmarkUI/landmarkUI.component.ts deleted file mode 100644 index 9b4635830d5668b7bf6981c7d87c988b0b88ea3e..0000000000000000000000000000000000000000 --- a/src/ui/landmarkUI/landmarkUI.component.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { Component, Input, OnChanges, Output, EventEmitter, ChangeDetectionStrategy, ChangeDetectorRef, AfterContentChecked } from "@angular/core"; -import { IDataEntry } from "src/services/stateStore.service"; -import { GetKgSchemaIdFromFullIdPipe } from 'src/ui/databrowserModule/util/getKgSchemaIdFromFullId.pipe' -import { AtlasViewerConstantsServices } from "src/atlasViewer/atlasViewer.constantService.service"; -import { Observable } from "rxjs"; -import { DS_PREVIEW_URL } from 'src/util/constants' - -@Component({ - selector: 'landmark-ui', - templateUrl: './landmarkUI.template.html', - styleUrls: [ - './landmarkUI.style.css' - ], - changeDetection: ChangeDetectionStrategy.OnPush -}) - -export class LandmarkUIComponent implements OnChanges, AfterContentChecked{ - @Input() name: string - @Input() fullId: string - @Input() datasets: Partial<IDataEntry>[] - - @Output() relayout: EventEmitter<any> = new EventEmitter() - - private pipe = new GetKgSchemaIdFromFullIdPipe() - - public DS_PREVIEW_URL = DS_PREVIEW_URL - - public previewFilesMap: Map<string, any[]> = new Map() - public previewFiles: any[] = [] - - handleKgDsPrvUpdate(event: CustomEvent, datasetKgId: string){ - const { detail } = event - const { datasetFiles } = detail - - this.previewFilesMap.set(datasetKgId, datasetFiles) - - this.previewFiles = [] - - for (const [datasetKgId, previewFiles] of Array.from(this.previewFilesMap)){ - for (const singlePreviewFile of previewFiles){ - this.previewFiles.push({ - ...singlePreviewFile, - datasetKgId - }) - } - } - this.cdr.markForCheck() - } - - public filterCriteria: string - ngOnChanges(){ - this.filterCriteria = null - if (!this.fullId) return - const [kgSchema, kgId] = this.pipe.transform(this.fullId) - this.filterCriteria = JSON.stringify([ `${kgSchema}/${kgId}` ]) - } - - // TODO need to optimise this. This calls way too frequently. - ngAfterContentChecked(){ - this.relayout.emit() - } - - public darktheme$: Observable<boolean> - - constructor( - constantService: AtlasViewerConstantsServices, - private cdr: ChangeDetectorRef - ){ - this.darktheme$ = constantService.darktheme$ - } -} \ No newline at end of file diff --git a/src/ui/landmarkUI/landmarkUI.style.css b/src/ui/landmarkUI/landmarkUI.style.css deleted file mode 100644 index 757b1a7bcba50d2da823755e085849b453bff61a..0000000000000000000000000000000000000000 --- a/src/ui/landmarkUI/landmarkUI.style.css +++ /dev/null @@ -1,21 +0,0 @@ -kg-dataset-previewer -{ - height: 20em; - width: 30em; -} -.scroll-snap-container -{ - scroll-snap-type: x mandatory; - scroll-padding-left: 1em; - scroll-padding-right: 1em; -} - -.scroll-snap-container > .scroll-snap-child -{ - scroll-snap-align: center; -} - -.w-30em -{ - width:30em!important; -} \ No newline at end of file diff --git a/src/ui/landmarkUI/landmarkUI.template.html b/src/ui/landmarkUI/landmarkUI.template.html deleted file mode 100644 index 46085769ecdd1ea45a56ef7cc7f05a6bf690744f..0000000000000000000000000000000000000000 --- a/src/ui/landmarkUI/landmarkUI.template.html +++ /dev/null @@ -1,67 +0,0 @@ -<mat-card> - <mat-card-title> - - {{ name }} - </mat-card-title> - <mat-card-subtitle> - - <i class="fas fa-map-marker-alt"></i> - <span> - spatial landmark - </span> - </mat-card-subtitle> - - <div class="scroll-snap-container w-100 d-flex flex-row w-30em overflow-auto" mat-card-image> - <div *ngFor="let dsPreview of previewFiles" - class="scroll-snap-child w-30em"> - <kg-dataset-previewer - [darkmode]="darktheme$ | async" - [filename]="dsPreview.filename" - [kgId]="dsPreview.datasetKgId" - [backendUrl]="DS_PREVIEW_URL"> - </kg-dataset-previewer> - </div> - - </div> - <mat-card-content> - - <!-- preview --> - <div> - <small class="text-muted"> - Dataset preview - </small> - - <ng-container *ngFor="let dataset of datasets; let index = index"> - <kg-dataset-list - class="d-none" - [backendUrl]="DS_PREVIEW_URL" - [filterCriteriaProp]="filterCriteria" - *ngIf="dataset.fullId | getKgSchemaIdFromFullIdPipe as kgSchemaId" - (kgDsPrvUpdated)="handleKgDsPrvUpdate($event, kgSchemaId[1])" - [kgId]="kgSchemaId[1]"> - - </kg-dataset-list> - </ng-container> - </div> - - <hr class="text-muted"> - <!-- associated datasets --> - <div> - <small class="text-muted"> - Associated datasets - </small> - <div> - <ng-container *ngFor="let dataset of datasets"> - - <single-dataset-list-view - *ngIf="dataset.fullId | getKgSchemaIdFromFullIdPipe as kgSchemaId" - [ripple]="true" - [kgSchema]="kgSchemaId[0]" - [kgId]="kgSchemaId[1]"> - - </single-dataset-list-view> - </ng-container> - </div> - </div> - </mat-card-content> -</mat-card> diff --git a/src/ui/nehubaContainer/2dLandmarks/landmark.base.ts b/src/ui/nehubaContainer/2dLandmarks/landmark.base.ts index 52144fb7a41476a6387914067062fd6af99a3653..9d424edba5344b152eb561d4f8e3b92a0414925e 100644 --- a/src/ui/nehubaContainer/2dLandmarks/landmark.base.ts +++ b/src/ui/nehubaContainer/2dLandmarks/landmark.base.ts @@ -4,4 +4,6 @@ export class LandmarkUnitBase{ @Input() public positionX: number = 0 @Input() public positionY: number = 0 @Input() public positionZ: number = 0 + + @Input() public color: number[] = [255, 255, 255] } diff --git a/src/ui/nehubaContainer/nehuba.module.ts b/src/ui/nehubaContainer/nehuba.module.ts deleted file mode 100644 index 0104d0c011daa059f725a9b218abfac7f3b494cb..0000000000000000000000000000000000000000 --- a/src/ui/nehubaContainer/nehuba.module.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { NgModule } from "@angular/core"; -import { NehubaViewerContainerDirective } from './nehubaViewerInterface/nehubaViewerInterface.directive' -import { NehubaViewerUnit } from "./nehubaViewer/nehubaViewer.component"; -import { CommonModule } from "@angular/common"; -@NgModule({ - imports: [ - CommonModule - ], - declarations: [ - NehubaViewerContainerDirective, - NehubaViewerUnit - ], - exports: [ - NehubaViewerContainerDirective, - NehubaViewerUnit - ], - entryComponents: [ - NehubaViewerUnit - ] -}) - -export class NehubaModule{} diff --git a/src/ui/nehubaContainer/nehubaContainer.component.spec.ts b/src/ui/nehubaContainer/nehubaContainer.component.spec.ts deleted file mode 100644 index 796c6ce287776f4c87d2b24cb3b4de8c524ecef7..0000000000000000000000000000000000000000 --- a/src/ui/nehubaContainer/nehubaContainer.component.spec.ts +++ /dev/null @@ -1,701 +0,0 @@ -import { CUSTOM_ELEMENTS_SCHEMA, DebugElement } from '@angular/core' -import { TestBed, async, ComponentFixture, fakeAsync, tick, flush, discardPeriodicTasks } from "@angular/core/testing" -import { NehubaContainer } from "./nehubaContainer.component" -import { provideMockStore, MockStore } from "@ngrx/store/testing" -import { defaultRootState } from 'src/services/stateStore.service' -import { AngularMaterialModule } from "../sharedModules/angularMaterial.module" -import { TouchSideClass } from "./touchSideClass.directive" -import { MaximmisePanelButton } from "./maximisePanelButton/maximisePanelButton.component" -import { LayoutModule } from 'src/layouts/layout.module' -import { PureContantService, UtilModule } from "src/util" -import { AtlasLayerSelector } from "../atlasLayerSelector/atlasLayerSelector.component" -import { StatusCardComponent } from './statusCard/statusCard.component' -import { NehubaViewerTouchDirective } from './nehubaViewerInterface/nehubaViewerTouch.directive' -import { MobileOverlay } from "./mobileOverlay/mobileOverlay.component" -import { RegionMenuComponent } from "../parcellationRegion/regionMenu/regionMenu.component" -import { DatabrowserModule } from "../databrowserModule" -import { SplashScreen } from "./splashScreen/splashScreen.component" -import { CurrentLayout } from 'src/ui/config/currentLayout/currentLayout.component' -import { RegionDirective } from 'src/ui/parcellationRegion/region.directive' -import { RegionTextSearchAutocomplete } from "../viewerStateController/regionSearch/regionSearch.component" -import { MobileControlNubStylePipe } from './pipes/mobileControlNubStyle.pipe' -import { ReorderPanelIndexPipe } from './reorderPanelIndex.pipe' -import { AuthModule } from 'src/auth' -import { StateModule } from 'src/state' -import { ReactiveFormsModule, FormsModule } from '@angular/forms' -import { HttpClientModule } from '@angular/common/http' -import { WidgetModule } from 'src/widget' -import { NehubaModule } from './nehuba.module' -import { CommonModule } from '@angular/common' -import { IMPORT_NEHUBA_INJECT_TOKEN } from './nehubaViewer/nehubaViewer.component' -import { viewerStateCustomLandmarkSelector, viewerStateHelperStoreName } from 'src/services/state/viewerState.store.helper' -import { RenderViewOriginDatasetLabelPipe } from '../parcellationRegion/region.base' -import { By } from '@angular/platform-browser' -import { ARIA_LABELS } from 'common/constants' -import { NoopAnimationsModule } from '@angular/platform-browser/animations' -import { RegionAccordionTooltipTextPipe } from '../util' -import { hot } from 'jasmine-marbles' -import { HttpClientTestingModule } from '@angular/common/http/testing' -import { ngViewerSelectorOctantRemoval, ngViewerSelectorPanelMode, ngViewerSelectorPanelOrder } from 'src/services/state/ngViewerState/selectors' -import { PANELS } from 'src/services/state/ngViewerState/constants' -import { RegionalFeaturesModule } from '../regionalFeatures' -import { Landmark2DModule } from './2dLandmarks/module' - -const { - TOGGLE_SIDE_PANEL, - EXPAND, - COLLAPSE, - ZOOM_IN, - ZOOM_OUT, - TOGGLE_FRONTAL_OCTANT -} = ARIA_LABELS - -const _bigbrainJson = require('!json-loader!src/res/ext/bigbrain.json') -const _bigbrainNehubaConfigJson = require('!json-loader!src/res/ext/bigbrainNehubaConfig.json') -const bigbrainJson = { - ..._bigbrainJson, - nehubaConfig: _bigbrainNehubaConfigJson -} -const humanAtlas = require('!json-loader!src/res/ext/atlas/atlas_multiLevelHuman.json') -const importNehubaSpy = jasmine.createSpy('importNehubaSpy').and.returnValue(Promise.reject()) - -describe('> nehubaContainer.component.ts', () => { - - describe('> NehubaContainer', () => { - - beforeEach(async(() => { - - TestBed.configureTestingModule({ - imports: [ - NoopAnimationsModule, - WidgetModule, - AngularMaterialModule, - LayoutModule, - UtilModule, - DatabrowserModule, - NehubaModule, - AuthModule, - StateModule, - FormsModule, - ReactiveFormsModule, - HttpClientModule, - CommonModule, - RegionalFeaturesModule, - - /** - * because the change done to pureconstant service, need to intercept http call to avoid crypto error message - * so and so components needs to be compiled first. make sure you call compileComponents - */ - HttpClientTestingModule, - Landmark2DModule, - ], - declarations: [ - NehubaContainer, - TouchSideClass, - MaximmisePanelButton, - AtlasLayerSelector, - StatusCardComponent, - NehubaViewerTouchDirective, - MobileOverlay, - RegionMenuComponent, - SplashScreen, - CurrentLayout, - RegionDirective, - RegionTextSearchAutocomplete, - - // pipes - MobileControlNubStylePipe, - ReorderPanelIndexPipe, - RenderViewOriginDatasetLabelPipe, - RegionAccordionTooltipTextPipe, - ], - providers: [ - provideMockStore({ initialState: defaultRootState }), - { - provide: IMPORT_NEHUBA_INJECT_TOKEN, - useValue: importNehubaSpy - }, - PureContantService, - ], - schemas: [ - CUSTOM_ELEMENTS_SCHEMA - ], - }).compileComponents() - - })) - - it('> component can be created', () => { - const fixture = TestBed.createComponent(NehubaContainer) - fixture.componentInstance.currentOnHoverObs$ = hot('') - const el = fixture.debugElement.componentInstance - expect(el).toBeTruthy() - }) - - describe('> on selectedTemplatechange', () => { - it('> calls importNehubaPr', async () => { - const fixture = TestBed.createComponent(NehubaContainer) - fixture.componentInstance.currentOnHoverObs$ = hot('') - - const mockStore = TestBed.inject(MockStore) - const newState = { - ...defaultRootState, - viewerState: { - ...defaultRootState.viewerState, - fetchedTemplates: [ bigbrainJson ], - templateSelected: bigbrainJson, - parcellationSelected: bigbrainJson.parcellations[0] - }, - [viewerStateHelperStoreName]: { - fetchedAtlases: [ humanAtlas ], - selectedAtlasId: humanAtlas['@id'] - } - } - - mockStore.setState(newState) - fixture.detectChanges() - expect(importNehubaSpy).toHaveBeenCalled() - }) - - /** - * TODO perhaps move this to e2e? - */ - it('> drag handle reattaches properly') - }) - - describe('> on selectedparcellation change', () => { - it('> should set ngId of nehubaViewer', () => { - - const fixture = TestBed.createComponent(NehubaContainer) - fixture.componentInstance.currentOnHoverObs$ = hot('') - const el = fixture.debugElement.componentInstance as NehubaContainer - const mockStore = TestBed.inject(MockStore) - const newState = { - ...defaultRootState, - viewerState: { - ...defaultRootState.viewerState, - fetchedTemplates: [ bigbrainJson ], - templateSelected: bigbrainJson, - parcellationSelected: bigbrainJson.parcellations[0] - }, - [viewerStateHelperStoreName]: { - fetchedAtlases: [ humanAtlas ], - selectedAtlasId: humanAtlas['@id'] - } - } - - mockStore.setState(newState) - fixture.detectChanges() - - const setSpy = spyOnProperty(el.nehubaViewer, 'ngIds', 'set') - - const newState2 = { - ...defaultRootState, - viewerState: { - ...defaultRootState.viewerState, - fetchedTemplates: [ bigbrainJson ], - templateSelected: bigbrainJson, - parcellationSelected: bigbrainJson.parcellations[1] - }, - [viewerStateHelperStoreName]: { - fetchedAtlases: [ humanAtlas ], - selectedAtlasId: humanAtlas['@id'] - } - } - - mockStore.setState(newState2) - fixture.detectChanges() - - expect(setSpy).toHaveBeenCalled() - - }) - }) - - describe('> extended sidepanel hides and shows as expected', () => { - describe('> on start, if nothing is selected', () => { - beforeEach(() => { - const mockStore = TestBed.inject(MockStore) - const newState = { - ...defaultRootState, - viewerState: { - ...defaultRootState.viewerState, - fetchedTemplates: [ bigbrainJson ], - templateSelected: bigbrainJson, - parcellationSelected: bigbrainJson.parcellations[0] - }, - [viewerStateHelperStoreName]: { - fetchedAtlases: [ humanAtlas ], - selectedAtlasId: humanAtlas['@id'] - } - } - - mockStore.setState(newState) - }) - - it('> both should be shut', () => { - const fixture = TestBed.createComponent(NehubaContainer) - fixture.componentInstance.currentOnHoverObs$ = hot('') - fixture.detectChanges() - expect( - fixture.componentInstance.matDrawerMain.opened - ).toEqual(false) - expect( - fixture.componentInstance.matDrawerMinor.opened - ).toEqual(false) - }) - - it('> opening via tab should result in only top drawer open', () => { - - const fixture = TestBed.createComponent(NehubaContainer) - fixture.componentInstance.currentOnHoverObs$ = hot('') - fixture.detectChanges() - const toggleBtn = fixture.debugElement.query( By.css(`[aria-label="${TOGGLE_SIDE_PANEL}"]`) ) - toggleBtn.triggerEventHandler('click', null) - fixture.detectChanges() - - expect( - fixture.componentInstance.matDrawerMain.opened - ).toEqual(true) - expect( - fixture.componentInstance.matDrawerMinor.opened - ).toEqual(false) - }) - - it('> on opening top drawer, explore features should not be present', () => { - - const fixture = TestBed.createComponent(NehubaContainer) - fixture.componentInstance.currentOnHoverObs$ = hot('') - fixture.detectChanges() - const toggleBtn = fixture.debugElement.query( By.css(`[aria-label="${TOGGLE_SIDE_PANEL}"]`) ) - toggleBtn.triggerEventHandler('click', null) - fixture.detectChanges() - const expandRegionFeatureBtn = fixture.debugElement.query( By.css(`mat-drawer[data-mat-drawer-secondary-open="true"] [aria-label="${EXPAND}"]`) ) - expect(expandRegionFeatureBtn).toBeNull() - }) - it('> collapse btn should not be visible', () => { - - const fixture = TestBed.createComponent(NehubaContainer) - fixture.componentInstance.currentOnHoverObs$ = hot('') - fixture.detectChanges() - const toggleBtn = fixture.debugElement.query( By.css(`[aria-label="${TOGGLE_SIDE_PANEL}"]`) ) - toggleBtn.triggerEventHandler('click', null) - fixture.detectChanges() - const expandRegionFeatureBtn = fixture.debugElement.query( By.css(`mat-drawer[data-mat-drawer-secondary-open="true"] [aria-label="${COLLAPSE}"]`) ) - expect(expandRegionFeatureBtn).toBeNull() - }) - }) - - describe('> on start, if something is selected', () => { - beforeEach(() => { - const mockStore = TestBed.inject(MockStore) - const newState = { - ...defaultRootState, - viewerState: { - ...defaultRootState.viewerState, - fetchedTemplates: [ bigbrainJson ], - templateSelected: bigbrainJson, - parcellationSelected: bigbrainJson.parcellations[0], - regionsSelected: [{ - name: "foobar", - ngId: 'untitled', - labelIndex: 15 - }] - }, - [viewerStateHelperStoreName]: { - fetchedAtlases: [ humanAtlas ], - selectedAtlasId: humanAtlas['@id'] - } - } - - mockStore.setState(newState) - }) - it('> both should be open', () => { - const fixture = TestBed.createComponent(NehubaContainer) - fixture.componentInstance.currentOnHoverObs$ = hot('') - fixture.detectChanges() - expect( - fixture.componentInstance.matDrawerMain.opened - ).toEqual(true) - expect( - fixture.componentInstance.matDrawerMinor.opened - ).toEqual(true) - - expect( - fixture.componentInstance.navSideDrawerMainSwitch.switchState - ).toEqual(true) - expect( - fixture.componentInstance.navSideDrawerMinorSwitch.switchState - ).toEqual(true) - }) - - it('> closing main drawer via tag should close both', () => { - const fixture = TestBed.createComponent(NehubaContainer) - fixture.componentInstance.currentOnHoverObs$ = hot('') - fixture.detectChanges() - const toggleBtn = fixture.debugElement.query( By.css(`[aria-label="${TOGGLE_SIDE_PANEL}"]`) ) - toggleBtn.triggerEventHandler('click', null) - fixture.detectChanges() - expect( - fixture.componentInstance.matDrawerMain.opened - ).toEqual(false) - - /** - * TODO investigate why openedStart/closedStart events fail to fire - */ - // expect( - // fixture.componentInstance.matDrawerMinor.opened - // ).toEqual(false) - - // expect( - // fixture.componentInstance.navSideDrawerMainSwitch.switchState - // ).toEqual(false) - // expect( - // fixture.componentInstance.navSideDrawerMinorSwitch.switchState - // ).toEqual(false) - }) - it('> collapse btn should be visible', () => { - - const fixture = TestBed.createComponent(NehubaContainer) - fixture.componentInstance.currentOnHoverObs$ = hot('') - fixture.detectChanges() - const collapseRegionFeatureBtn = fixture.debugElement.query( By.css(`mat-drawer[data-mat-drawer-secondary-open="true"] [aria-label="${COLLAPSE}"]`) ) - expect(collapseRegionFeatureBtn).not.toBeNull() - }) - it('> clicking on collapse btn should minimize 1 drawer', () => { - - const fixture = TestBed.createComponent(NehubaContainer) - fixture.componentInstance.currentOnHoverObs$ = hot('') - fixture.detectChanges() - const collapseRegionFeatureBtn = fixture.debugElement.query( By.css(`mat-drawer[data-mat-drawer-secondary-open="true"] [aria-label="${COLLAPSE}"]`) ) - collapseRegionFeatureBtn.triggerEventHandler('click', null) - fixture.detectChanges() - expect( - fixture.componentInstance.matDrawerMain.opened - ).toEqual(true) - - /** - * TODO investigate why property does not get updated - */ - // expect( - // fixture.componentInstance.matDrawerMinor.opened - // ).toEqual(false) - - expect( - fixture.componentInstance.navSideDrawerMainSwitch.switchState - ).toEqual(true) - expect( - fixture.componentInstance.navSideDrawerMinorSwitch.switchState - ).toEqual(false) - }) - - it('> on minimize drawer, clicking expand btn should expand everything', () => { - const fixture = TestBed.createComponent(NehubaContainer) - fixture.componentInstance.currentOnHoverObs$ = hot('') - fixture.detectChanges() - const collapseRegionFeatureBtn = fixture.debugElement.query( By.css(`mat-drawer[data-mat-drawer-secondary-open="true"] [aria-label="${COLLAPSE}"]`) ) - collapseRegionFeatureBtn.triggerEventHandler('click', null) - fixture.detectChanges() - const expandRegionFeatureBtn = fixture.debugElement.query( By.css(`mat-drawer[data-mat-drawer-primary-open="true"] [aria-label="${EXPAND}"]`) ) - expandRegionFeatureBtn.triggerEventHandler('click', null) - fixture.detectChanges() - - expect( - fixture.componentInstance.matDrawerMain.opened - ).toEqual(true) - expect( - fixture.componentInstance.matDrawerMinor.opened - ).toEqual(true) - - expect( - fixture.componentInstance.navSideDrawerMainSwitch.switchState - ).toEqual(true) - /** - * TODO figoure out why switch state is updated async, and karma can't force update state - */ - // expect( - // fixture.componentInstance.navSideDrawerMinorSwitch.switchState - // ).toEqual(true) - }) - }) - - describe('> side bar content', () => { - - /** - * TODO - */ - it('> if nothing is shown, it should show place holder text') - - /** - * TODO - */ - it('> if something (region features/connectivity) exists, placeh holder text should be hdiden') - }) - }) - - describe('> panelCtrl', () => { - let fixture: ComponentFixture<NehubaContainer> - const setViewerLoaded = () => { - fixture.componentInstance.viewerLoaded = true - } - const ctrlElementIsVisible = (el: DebugElement) => { - const visible = (el.nativeElement as HTMLElement).getAttribute('data-viewer-controller-visible') - return visible === 'true' - } - beforeEach(() => { - fixture = TestBed.createComponent(NehubaContainer) - }) - it('> on start, all four ctrl panels exists', () => { - fixture.detectChanges() - setViewerLoaded() - fixture.detectChanges() - for (const idx of [0, 1, 2, 3]) { - const el = fixture.debugElement.query( - By.css(`[data-viewer-controller-index="${idx}"]`) - ) - expect(el).toBeTruthy() - } - }) - - it('> on start all four ctrl panels are invisible', () => { - - fixture.detectChanges() - setViewerLoaded() - fixture.detectChanges() - for (const idx of [0, 1, 2, 3]) { - const el = fixture.debugElement.query( - By.css(`[data-viewer-controller-index="${idx}"]`) - ) - expect(ctrlElementIsVisible(el)).toBeFalsy() - } - }) - - describe('> on hover, only the hovered panel have ctrl shown', () => { - - for (const idx of [0, 1, 2, 3]) { - - it(`> on hoveredPanelIndices$ emit ${idx}, the panel index ${idx} ctrl becomes visible`, fakeAsync(() => { - fixture.detectChanges() - const findPanelIndexSpy = spyOn<any>(fixture.componentInstance, 'findPanelIndex').and.callFake(() => { - return idx - }) - setViewerLoaded() - fixture.detectChanges() - const nativeElement = fixture.componentInstance['elementRef'].nativeElement - nativeElement.dispatchEvent(new MouseEvent('mouseover', { bubbles: true })) - - /** - * assert findPanelIndex called with event.target, i.e. native element in thsi case - */ - expect(findPanelIndexSpy).toHaveBeenCalledWith(nativeElement) - tick(200) - fixture.detectChanges() - - /** - * every panel index should be non visible - * only when idx matches, it can be visible - * n.b. this does not test visual visibility (which is controlled by extra-style.css) - * (which is also affected by global [ismobile] configuration) - * - * this merely test the unit logic, and sets the flag appropriately - */ - for (const iterativeIdx of [0, 1, 2, 3]) { - const el = fixture.debugElement.query( - By.css(`[data-viewer-controller-index="${iterativeIdx}"]`) - ) - if (iterativeIdx === idx) { - expect(ctrlElementIsVisible(el)).toBeTruthy() - } else { - expect(ctrlElementIsVisible(el)).toBeFalsy() - } - } - discardPeriodicTasks() - })) - } - - }) - - describe('> on maximise top right slice panel (idx 1)', () => { - beforeEach(() => { - const mockStore = TestBed.inject(MockStore) - mockStore.overrideSelector(ngViewerSelectorPanelMode, PANELS.SINGLE_PANEL) - mockStore.overrideSelector(ngViewerSelectorPanelOrder, '1230') - - fixture.detectChanges() - setViewerLoaded() - fixture.detectChanges() - }) - it('> toggle front octant btn not visible', () => { - - const toggleBtn = fixture.debugElement.query( - By.css(`[cell-i] [aria-label="${TOGGLE_FRONTAL_OCTANT}"]`) - ) - expect(toggleBtn).toBeFalsy() - }) - - it('> zoom in and out btns are visible', () => { - - const zoomInBtn = fixture.debugElement.query( - By.css(`[cell-i] [aria-label="${ZOOM_IN}"]`) - ) - - const zoomOutBtn = fixture.debugElement.query( - By.css(`[cell-i] [aria-label="${ZOOM_OUT}"]`) - ) - - expect(zoomInBtn).toBeTruthy() - expect(zoomOutBtn).toBeTruthy() - }) - - it('> zoom in btn calls fn with right param', () => { - const zoomViewSpy = spyOn(fixture.componentInstance, 'zoomNgView') - - const zoomInBtn = fixture.debugElement.query( - By.css(`[cell-i] [aria-label="${ZOOM_IN}"]`) - ) - zoomInBtn.triggerEventHandler('click', null) - expect(zoomViewSpy).toHaveBeenCalled() - const { args } = zoomViewSpy.calls.first() - expect(args[0]).toEqual(1) - /** - * zoom in < 1 - */ - expect(args[1]).toBeLessThan(1) - }) - it('> zoom out btn calls fn with right param', () => { - const zoomViewSpy = spyOn(fixture.componentInstance, 'zoomNgView') - - const zoomOutBtn = fixture.debugElement.query( - By.css(`[cell-i] [aria-label="${ZOOM_OUT}"]`) - ) - zoomOutBtn.triggerEventHandler('click', null) - expect(zoomViewSpy).toHaveBeenCalled() - const { args } = zoomViewSpy.calls.first() - expect(args[0]).toEqual(1) - /** - * zoom out > 1 - */ - expect(args[1]).toBeGreaterThan(1) - }) - }) - describe('> on maximise perspective panel', () => { - beforeEach(() => { - const mockStore = TestBed.inject(MockStore) - mockStore.overrideSelector(ngViewerSelectorPanelMode, PANELS.SINGLE_PANEL) - mockStore.overrideSelector(ngViewerSelectorPanelOrder, '3012') - - fixture.detectChanges() - setViewerLoaded() - fixture.detectChanges() - }) - it('> toggle octant btn visible and functional', () => { - const setOctantRemovalSpy = spyOn(fixture.componentInstance, 'setOctantRemoval') - - const toggleBtn = fixture.debugElement.query( - By.css(`[cell-i] [aria-label="${TOGGLE_FRONTAL_OCTANT}"]`) - ) - expect(toggleBtn).toBeTruthy() - toggleBtn.nativeElement.dispatchEvent( - new MouseEvent('click', { bubbles: true }) - ) - expect(setOctantRemovalSpy).toHaveBeenCalled() - }) - - it('> zoom in btn visible and functional', () => { - const zoomViewSpy = spyOn(fixture.componentInstance, 'zoomNgView') - - const zoomInBtn = fixture.debugElement.query( - By.css(`[cell-i] [aria-label="${ZOOM_IN}"]`) - ) - expect(zoomInBtn).toBeTruthy() - - zoomInBtn.triggerEventHandler('click', null) - expect(zoomViewSpy).toHaveBeenCalled() - const { args } = zoomViewSpy.calls.first() - expect(args[0]).toEqual(3) - /** - * zoom in < 1 - */ - expect(args[1]).toBeLessThan(1) - }) - it('> zoom out btn visible and functional', () => { - const zoomViewSpy = spyOn(fixture.componentInstance, 'zoomNgView') - - const zoomOutBtn = fixture.debugElement.query( - By.css(`[cell-i] [aria-label="${ZOOM_OUT}"]`) - ) - expect(zoomOutBtn).toBeTruthy() - - zoomOutBtn.triggerEventHandler('click', null) - expect(zoomViewSpy).toHaveBeenCalled() - const { args } = zoomViewSpy.calls.first() - expect(args[0]).toEqual(3) - /** - * zoom in < 1 - */ - expect(args[1]).toBeGreaterThan(1) - }) - - }) - }) - - describe('> on userLandmarks change', () => { - const lm1 = { - id: 'test-1', - position: [0, 0, 0] - } - const lm2 = { - id: 'test-2', - position: [1, 1,1 ] - } - it('> calls nehubaViewer.updateUserLandmarks', () => { - const fixture = TestBed.createComponent(NehubaContainer) - - fixture.componentInstance.nehubaViewer = { - updateUserLandmarks: () => {} - } as any - - const updateUserLandmarksSpy = spyOn( - fixture.componentInstance.nehubaViewer, - 'updateUserLandmarks' - ) - - const mockStore = TestBed.inject(MockStore) - mockStore.overrideSelector(viewerStateCustomLandmarkSelector, [ - lm1, - lm2 - ]) - fixture.detectChanges() - expect( - updateUserLandmarksSpy - ).toHaveBeenCalledWith([ - lm1, lm2 - ]) - }) - - it('> calls togglecotantREmoval', () => { - - const fixture = TestBed.createComponent(NehubaContainer) - - fixture.componentInstance.nehubaContainerDirective = { - toggleOctantRemoval: () => {}, - clear: () => {} - } as any - - const toggleOctantRemovalSpy = spyOn( - fixture.componentInstance.nehubaContainerDirective, - 'toggleOctantRemoval' - ) - - const mockStore = TestBed.inject(MockStore) - mockStore.overrideSelector(viewerStateCustomLandmarkSelector, [ - lm1, - lm2 - ]) - mockStore.overrideSelector(ngViewerSelectorOctantRemoval, true) - fixture.detectChanges() - expect( - toggleOctantRemovalSpy - ).toHaveBeenCalledWith(false) - }) - }) - }) -}) diff --git a/src/ui/nehubaContainer/nehubaContainer.component.ts b/src/ui/nehubaContainer/nehubaContainer.component.ts index 31baa10763f7dfb2bffb6266a62a982c57b56131..3582fc984ef9c10db69e3af977c6fed9c9586b25 100644 --- a/src/ui/nehubaContainer/nehubaContainer.component.ts +++ b/src/ui/nehubaContainer/nehubaContainer.component.ts @@ -1,1183 +1,1181 @@ -import { Component, ElementRef, Input, OnChanges, OnDestroy, OnInit, ViewChild, ChangeDetectorRef, Output, EventEmitter, Inject, Optional } from "@angular/core"; -import { select, Store } from "@ngrx/store"; -import { combineLatest, fromEvent, merge, Observable, of, Subscription, timer, asyncScheduler, BehaviorSubject, Subject } from "rxjs"; -import { buffer, debounceTime, distinctUntilChanged, filter, map, mapTo, scan, shareReplay, skip, startWith, switchMap, switchMapTo, take, tap, withLatestFrom, delayWhen, throttleTime } from "rxjs/operators"; -import { trigger, state, style, animate, transition } from '@angular/animations' -import { MatDrawer } from "@angular/material/sidenav"; - -import { LoggingService } from "src/logging"; -import { - CHANGE_NAVIGATION, - generateLabelIndexId, - getMultiNgIdsRegionsLabelIndexMap, - getNgIds, - ILandmark, - IOtherLandmarkGeometry, - IPlaneLandmarkGeometry, - IPointLandmarkGeometry, - isDefined, - MOUSE_OVER_LANDMARK, - NgViewerStateInterface -} from "src/services/stateStore.service"; - -import { getExportNehuba, isSame } from "src/util/fn"; -import { API_SERVICE_SET_VIEWER_HANDLE_TOKEN, IUserLandmark } from "src/atlasViewer/atlasViewer.apiService.service"; -import { NehubaViewerUnit } from "./nehubaViewer/nehubaViewer.component"; -import { compareLandmarksChanged } from "src/util/constants"; -import { PureContantService } from "src/util"; -import { ARIA_LABELS, IDS, CONST } from 'common/constants' -import { ngViewerActionSetPerspOctantRemoval, PANELS, ngViewerActionToggleMax, ngViewerActionAddNgLayer, ngViewerActionRemoveNgLayer } from "src/services/state/ngViewerState.store.helper"; -import { viewerStateSelectRegionWithIdDeprecated, viewerStateAddUserLandmarks, viewreStateRemoveUserLandmarks, viewerStateCustomLandmarkSelector, viewerStateSelectedParcellationSelector, viewerStateSelectedTemplateSelector, viewerStateSelectedRegionsSelector } from 'src/services/state/viewerState.store.helper' -import { SwitchDirective } from "src/util/directives/switch.directive"; -import { - viewerStateDblClickOnViewer, -} from "src/services/state/viewerState.store.helper"; - -import { getFourPanel, getHorizontalOneThree, getSinglePanel, getVerticalOneThree, calculateSliceZoomFactor, scanSliceViewRenderFn as scanFn, takeOnePipe } from "./util"; -import { NehubaViewerContainerDirective } from "./nehubaViewerInterface/nehubaViewerInterface.directive"; -import { ITunableProp } from "./mobileOverlay/mobileOverlay.component"; -import {ConnectivityBrowserComponent} from "src/ui/connectivityBrowser/connectivityBrowser.component"; -import { viewerStateMouseOverCustomLandmark } from "src/services/state/viewerState/actions"; -import { ngViewerSelectorNehubaReady, ngViewerSelectorOctantRemoval, ngViewerSelectorPanelMode, ngViewerSelectorPanelOrder } from "src/services/state/ngViewerState/selectors"; -import { REGION_OF_INTEREST } from "src/util/interfaces"; -import { uiActionHideAllDatasets, uiActionHideDatasetWithId } from "src/services/state/uiState/actions"; - -const { MESH_LOADING_STATUS } = IDS - -const sortByFreshness: (acc: any[], curr: any[]) => any[] = (acc, curr) => { - - const getLayerName = ({layer} = {layer: {}}) => { - const { name } = layer as any - return name - } - - const newEntries = (curr && curr.filter(entry => { - const name = getLayerName(entry) - return acc.map(getLayerName).indexOf(name) < 0 - })) || [] - - const entryChanged: (itemPrevState, newArr) => boolean = (itemPrevState, newArr) => { - const layerName = getLayerName(itemPrevState) - const { segment } = itemPrevState - const foundItem = newArr?.find((_item) => - getLayerName(_item) === layerName) - - if (foundItem) { - const { segment: foundSegment } = foundItem - return segment !== foundSegment - } else { - /** - * if item was not found in the new array, meaning hovering nothing - */ - return segment !== null - } - } - - const getItemFromLayerName = (item, arr) => { - const foundItem = arr?.find(i => getLayerName(i) === getLayerName(item)) - return foundItem - ? foundItem - : { - layer: item.layer, - segment: null, - } - } - - const getReduceExistingLayers = (newArr) => ([changed, unchanged], _curr) => { - const changedFlag = entryChanged(_curr, newArr) - return changedFlag - ? [ changed.concat( getItemFromLayerName(_curr, newArr) ), unchanged ] - : [ changed, unchanged.concat(_curr) ] - } - - /** - * now, for all the previous layers, separate into changed and unchanged layers - */ - const [changed, unchanged] = acc.reduce(getReduceExistingLayers(curr), [[], []]) - return [...newEntries, ...changed, ...unchanged] -} - -const { - ZOOM_IN, - ZOOM_OUT, - TOGGLE_FRONTAL_OCTANT, - TOGGLE_SIDE_PANEL, - EXPAND, - COLLAPSE, - ADDITIONAL_VOLUME_CONTROL, -} = ARIA_LABELS - -@Component({ - selector : 'ui-nehuba-container', - templateUrl : './nehubaContainer.template.html', - styleUrls : [ - `./nehubaContainer.style.css`, - ], - animations: [ - trigger('openClose', [ - state('open', style({ - transform: 'translateY(0)', - opacity: 1 - })), - state('closed', style({ - transform: 'translateY(-100vh)', - opacity: 0 - })), - transition('open => closed', [ - animate('200ms cubic-bezier(0.35, 0, 0.25, 1)') - ]), - transition('closed => open', [ - animate('200ms cubic-bezier(0.35, 0, 0.25, 1)') - ]) - ]), - trigger('openCloseAnchor', [ - state('open', style({ - transform: 'translateY(0)' - })), - state('closed', style({ - transform: 'translateY(100vh)' - })), - transition('open => closed', [ - animate('200ms cubic-bezier(0.35, 0, 0.25, 1)') - ]), - transition('closed => open', [ - animate('200ms cubic-bezier(0.35, 0, 0.25, 1)') - ]) - ]), - ], - exportAs: 'uiNehubaContainer', - providers: [ - { - provide: REGION_OF_INTEREST, - useFactory: (store: Store<any>) => store.pipe( - select(viewerStateSelectedRegionsSelector), - map(rs => rs[0] || null) - ), - deps: [ - Store - ] - } - ] -}) - -export class NehubaContainer implements OnInit, OnChanges, OnDestroy { - - public CONST = CONST - public ARIA_LABEL_ZOOM_IN = ZOOM_IN - public ARIA_LABEL_ZOOM_OUT = ZOOM_OUT - public ARIA_LABEL_TOGGLE_FRONTAL_OCTANT = TOGGLE_FRONTAL_OCTANT - public ARIA_LABEL_TOGGLE_SIDE_PANEL = TOGGLE_SIDE_PANEL - public ARIA_LABEL_EXPAND = EXPAND - public ARIA_LABEL_COLLAPSE = COLLAPSE - public ARIA_LABEL_ADDITIONAL_VOLUME_CONTROL = ADDITIONAL_VOLUME_CONTROL +// import { Component, ElementRef, Input, OnChanges, OnDestroy, OnInit, ViewChild, ChangeDetectorRef, Output, EventEmitter, Inject, Optional } from "@angular/core"; +// import { select, Store } from "@ngrx/store"; +// import { combineLatest, fromEvent, merge, Observable, of, Subscription, timer, asyncScheduler, BehaviorSubject, Subject } from "rxjs"; +// import { buffer, debounceTime, distinctUntilChanged, filter, map, mapTo, scan, shareReplay, skip, startWith, switchMap, switchMapTo, take, tap, withLatestFrom, delayWhen, throttleTime } from "rxjs/operators"; +// import { trigger, state, style, animate, transition } from '@angular/animations' +// import { MatDrawer } from "@angular/material/sidenav"; + +// import { LoggingService } from "src/logging"; +// import { +// CHANGE_NAVIGATION, +// getMultiNgIdsRegionsLabelIndexMap, +// getNgIds, +// ILandmark, +// IOtherLandmarkGeometry, +// IPlaneLandmarkGeometry, +// IPointLandmarkGeometry, +// isDefined, +// MOUSE_OVER_LANDMARK, +// NgViewerStateInterface +// } from "src/services/stateStore.service"; + +// import { getExportNehuba, isSame } from "src/util/fn"; +// import { API_SERVICE_SET_VIEWER_HANDLE_TOKEN, IUserLandmark } from "src/atlasViewer/atlasViewer.apiService.service"; +// import { NehubaViewerUnit } from "./nehubaViewer/nehubaViewer.component"; +// import { compareLandmarksChanged } from "src/util/constants"; +// import { PureContantService } from "src/util"; +// import { ARIA_LABELS, IDS, CONST } from 'common/constants' +// import { serialiseParcellationRegion } from "common/util" +// import { ngViewerActionSetPerspOctantRemoval, PANELS, ngViewerActionToggleMax, ngViewerActionAddNgLayer, ngViewerActionRemoveNgLayer } from "src/services/state/ngViewerState.store.helper"; +// import { viewerStateSelectRegionWithIdDeprecated, viewerStateAddUserLandmarks, viewreStateRemoveUserLandmarks, viewerStateCustomLandmarkSelector, viewerStateSelectedParcellationSelector, viewerStateSelectedTemplateSelector, viewerStateSelectedRegionsSelector } from 'src/services/state/viewerState.store.helper' +// import { SwitchDirective } from "src/util/directives/switch.directive"; +// import { +// viewerStateDblClickOnViewer, +// } from "src/services/state/viewerState.store.helper"; + +// import { getFourPanel, getHorizontalOneThree, getSinglePanel, getVerticalOneThree, calculateSliceZoomFactor, scanSliceViewRenderFn as scanFn, takeOnePipe } from "./util"; +// import { NehubaViewerContainerDirective } from "./nehubaViewerInterface/nehubaViewerInterface.directive"; +// import { ITunableProp } from "./mobileOverlay/mobileOverlay.component"; +// import { viewerStateMouseOverCustomLandmark } from "src/services/state/viewerState/actions"; +// import { ngViewerSelectorNehubaReady, ngViewerSelectorOctantRemoval, ngViewerSelectorPanelMode, ngViewerSelectorPanelOrder } from "src/services/state/ngViewerState/selectors"; +// import { REGION_OF_INTEREST } from "src/util/interfaces"; +// import { uiActionHideAllDatasets, uiActionHideDatasetWithId } from "src/services/state/uiState/actions"; + +// const { MESH_LOADING_STATUS } = IDS + +// const sortByFreshness: (acc: any[], curr: any[]) => any[] = (acc, curr) => { + +// const getLayerName = ({layer} = {layer: {}}) => { +// const { name } = layer as any +// return name +// } + +// const newEntries = (curr && curr.filter(entry => { +// const name = getLayerName(entry) +// return acc.map(getLayerName).indexOf(name) < 0 +// })) || [] + +// const entryChanged: (itemPrevState, newArr) => boolean = (itemPrevState, newArr) => { +// const layerName = getLayerName(itemPrevState) +// const { segment } = itemPrevState +// const foundItem = newArr?.find((_item) => +// getLayerName(_item) === layerName) + +// if (foundItem) { +// const { segment: foundSegment } = foundItem +// return segment !== foundSegment +// } else { +// /** +// * if item was not found in the new array, meaning hovering nothing +// */ +// return segment !== null +// } +// } + +// const getItemFromLayerName = (item, arr) => { +// const foundItem = arr?.find(i => getLayerName(i) === getLayerName(item)) +// return foundItem +// ? foundItem +// : { +// layer: item.layer, +// segment: null, +// } +// } + +// const getReduceExistingLayers = (newArr) => ([changed, unchanged], _curr) => { +// const changedFlag = entryChanged(_curr, newArr) +// return changedFlag +// ? [ changed.concat( getItemFromLayerName(_curr, newArr) ), unchanged ] +// : [ changed, unchanged.concat(_curr) ] +// } + +// /** +// * now, for all the previous layers, separate into changed and unchanged layers +// */ +// const [changed, unchanged] = acc.reduce(getReduceExistingLayers(curr), [[], []]) +// return [...newEntries, ...changed, ...unchanged] +// } + +// const { +// ZOOM_IN, +// ZOOM_OUT, +// TOGGLE_FRONTAL_OCTANT, +// TOGGLE_SIDE_PANEL, +// EXPAND, +// COLLAPSE, +// ADDITIONAL_VOLUME_CONTROL, +// } = ARIA_LABELS + +// @Component({ +// selector : 'ui-nehuba-container', +// templateUrl : './nehubaContainer.template.html', +// styleUrls : [ +// `./nehubaContainer.style.css`, +// ], +// animations: [ +// trigger('openClose', [ +// state('open', style({ +// transform: 'translateY(0)', +// opacity: 1 +// })), +// state('closed', style({ +// transform: 'translateY(-100vh)', +// opacity: 0 +// })), +// transition('open => closed', [ +// animate('200ms cubic-bezier(0.35, 0, 0.25, 1)') +// ]), +// transition('closed => open', [ +// animate('200ms cubic-bezier(0.35, 0, 0.25, 1)') +// ]) +// ]), +// trigger('openCloseAnchor', [ +// state('open', style({ +// transform: 'translateY(0)' +// })), +// state('closed', style({ +// transform: 'translateY(100vh)' +// })), +// transition('open => closed', [ +// animate('200ms cubic-bezier(0.35, 0, 0.25, 1)') +// ]), +// transition('closed => open', [ +// animate('200ms cubic-bezier(0.35, 0, 0.25, 1)') +// ]) +// ]), +// ], +// exportAs: 'uiNehubaContainer', +// providers: [ +// { +// provide: REGION_OF_INTEREST, +// useFactory: (store: Store<any>) => store.pipe( +// select(viewerStateSelectedRegionsSelector), +// map(rs => rs[0] || null) +// ), +// deps: [ +// Store +// ] +// } +// ] +// }) + +// export class NehubaContainer implements OnInit, OnChanges, OnDestroy { + +// public CONST = CONST +// public ARIA_LABEL_ZOOM_IN = ZOOM_IN +// public ARIA_LABEL_ZOOM_OUT = ZOOM_OUT +// public ARIA_LABEL_TOGGLE_FRONTAL_OCTANT = TOGGLE_FRONTAL_OCTANT +// public ARIA_LABEL_TOGGLE_SIDE_PANEL = TOGGLE_SIDE_PANEL +// public ARIA_LABEL_EXPAND = EXPAND +// public ARIA_LABEL_COLLAPSE = COLLAPSE +// public ARIA_LABEL_ADDITIONAL_VOLUME_CONTROL = ADDITIONAL_VOLUME_CONTROL - public ID_MESH_LOADING_STATUS = MESH_LOADING_STATUS - - @ViewChild(NehubaViewerContainerDirective,{static: true}) - public nehubaContainerDirective: NehubaViewerContainerDirective - - @ViewChild('sideNavMasterSwitch', { static: true }) - public navSideDrawerMainSwitch: SwitchDirective - @ViewChild('sideNavSwitch', { static: true }) - public navSideDrawerMinorSwitch: SwitchDirective - - @ViewChild('matDrawerMaster', {static: true}) - public matDrawerMain: MatDrawer - @ViewChild('matDrawerMinor', { static: true }) - public matDrawerMinor: MatDrawer - - @Output() - public nehubaViewerLoaded: EventEmitter<boolean> = new EventEmitter() - - @Output() - public forceUI$: Observable<{ target: 'perspective:octantRemoval', mode: boolean, message?: string }> - - public disableOctantRemoval$: Observable<{ message?: string, mode: boolean }> - - public handleViewerLoadedEvent(flag: boolean){ - this.viewerLoaded = flag - this.nehubaViewerLoaded.emit(flag) - } - - public viewerLoaded: boolean = false - - private sliceRenderEvent$: Observable<CustomEvent> - public sliceViewLoadingMain$: Observable<[boolean, boolean, boolean]> - public perspectiveViewLoading$: Observable<string|null> - public showPerpsectiveScreen$: Observable<string> - - public templateSelected$: Observable<any> = this.store.pipe( - select(viewerStateSelectedTemplateSelector), - distinctUntilChanged(isSame), - ) - - private newViewer$: Observable<any> = this.templateSelected$.pipe( - filter(v => !!v), - ) - - private selectedParcellation$: Observable<any> = this.store.pipe( - select(viewerStateSelectedParcellationSelector), - distinctUntilChanged(), - filter(v => !!v) - ) - public selectedRegions: any[] = [] - public selectedRegions$: Observable<any[]> = this.store.pipe( - select(viewerStateSelectedRegionsSelector), - filter(rs => !!rs), - ) - - public selectedLandmarks$: Observable<any[]> - public selectedPtLandmarks$: Observable<any[]> - public customLandmarks$: Observable<any> = this.store.pipe( - select(viewerStateCustomLandmarkSelector), - map(lms => lms.map(lm => ({ - ...lm, - geometry: { - position: lm.position - } - }))) - ) - private hideSegmentations$: Observable<boolean> - - private fetchedSpatialDatasets$: Observable<ILandmark[]> - private userLandmarks$: Observable<IUserLandmark[]> - - public nehubaViewerPerspectiveOctantRemoval$: Observable<boolean> - - @Input() - private currentOnHover: {segments: any, landmark: any, userLandmark: any} - - @Input() - currentOnHoverObs$: Observable<{segments: any, landmark: any, userLandmark: any}> - - public iavAdditionalLayers$ = new Subject<any[]>() - - public alwaysHideMinorPanel$: Observable<boolean> = combineLatest( - this.selectedRegions$, - this.iavAdditionalLayers$.pipe( - startWith([]) - ) - ).pipe( - map(([ regions, layers ]) => regions.length === 0 && layers.length === 0) - ) - - public onHoverSegments$: BehaviorSubject<any[]> = new BehaviorSubject([]) - public onHoverSegment$: Observable<any> = this.onHoverSegments$.pipe( - scan(sortByFreshness, []), - /** - * take the first element after sort by freshness - */ - map(arr => arr[0]), - /** - * map to the older interface - */ - filter(v => !!v), - map(({ segment }) => { - return { - labelIndex: (isNaN(segment) && Number(segment.labelIndex)) || null, - foundRegion: (isNaN(segment) && segment) || null, - } - }), - ) - - public selectedTemplate: any | null - private selectedRegionIndexSet: Set<string> = new Set() - public fetchedSpatialData: ILandmark[] = [] - - private ngLayersRegister: Partial<NgViewerStateInterface> = {layers : [], forceShowSegment: null} - private ngLayers$: Observable<NgViewerStateInterface> - - public selectedParcellation: any | null - - public nehubaViewer: NehubaViewerUnit = null - private multiNgIdsRegionsLabelIndexMap: Map<string, Map<number, any>> = new Map() - private landmarksLabelIndexMap: Map<number, any> = new Map() - private landmarksNameMap: Map<string, number> = new Map() - - private subscriptions: Subscription[] = [] - - public nanometersToOffsetPixelsFn: Array<(...arg) => any> = [] - - public viewPanels: [HTMLElement, HTMLElement, HTMLElement, HTMLElement] = [null, null, null, null] - public panelMode$: Observable<string> - - public panelOrder$: Observable<string> - private redrawLayout$: Observable<[string, string]> - - public hoveredPanelIndices$: Observable<number> - - public connectivityNumber: string - public connectivityLoadUrl: string - - constructor( - private pureConstantService: PureContantService, - @Optional() @Inject(API_SERVICE_SET_VIEWER_HANDLE_TOKEN) private setViewerHandle: (arg) => void, - private store: Store<any>, - private elementRef: ElementRef, - private log: LoggingService, - private cdr: ChangeDetectorRef, - @Optional() @Inject(REGION_OF_INTEREST) public regionOfInterest$: Observable<any> - ) { - - this.useMobileUI$ = this.pureConstantService.useTouchUI$ - - this.nehubaViewerPerspectiveOctantRemoval$ = this.store.pipe( - select(ngViewerSelectorOctantRemoval), - ) - - this.panelMode$ = this.store.pipe( - select(ngViewerSelectorPanelMode), - distinctUntilChanged(), - shareReplay(1), - ) - - this.panelOrder$ = this.store.pipe( - select(ngViewerSelectorPanelOrder), - distinctUntilChanged(), - shareReplay(1), - ) - - this.redrawLayout$ = this.store.pipe( - select(ngViewerSelectorNehubaReady), - distinctUntilChanged(), - filter(v => !!v), - switchMapTo(combineLatest([ - this.panelMode$, - this.panelOrder$, - ])), - ) - - this.selectedLandmarks$ = this.store.pipe( - select('viewerState'), - select('landmarksSelected'), - ) - - this.selectedPtLandmarks$ = this.selectedLandmarks$.pipe( - map(lms => lms.filter(lm => lm.geometry.type === 'point')), - ) - - this.fetchedSpatialDatasets$ = this.store.pipe( - select('dataStore'), - select('fetchedSpatialData'), - distinctUntilChanged(compareLandmarksChanged), - filter(v => !!v), - startWith([]), - debounceTime(300), - ) - - this.userLandmarks$ = this.store.pipe( - select(viewerStateCustomLandmarkSelector), - distinctUntilChanged(), - ) - - /** - * in future, perhaps add other force UI optinos here - */ - this.forceUI$ = this.userLandmarks$.pipe( - map(lm => { - if (lm.length > 0) { - return { - target: 'perspective:octantRemoval', - mode: false, - message: `octant control disabled: showing landmarks.` - } - } else { - return { - target: 'perspective:octantRemoval', - mode: null - } - } - }) - ) - - this.disableOctantRemoval$ = this.forceUI$.pipe( - filter(({ target }) => target === 'perspective:octantRemoval'), - ) - - this.sliceRenderEvent$ = fromEvent(this.elementRef.nativeElement, 'sliceRenderEvent').pipe( - map(ev => ev as CustomEvent) - ) - - this.sliceViewLoadingMain$ = this.sliceRenderEvent$.pipe( - scan(scanFn, [null, null, null]), - startWith([true, true, true] as [boolean, boolean, boolean]), - shareReplay(1), - ) - - this.showPerpsectiveScreen$ = this.newViewer$.pipe( - switchMapTo(this.sliceRenderEvent$.pipe( - scan((acc, curr) => { - - /** - * if at any point, all chunks have been loaded, always return loaded state - */ - if (acc.every(v => v === 0)) return [0, 0, 0] - const { detail = {}, target } = curr || {} - const { missingChunks = -1, missingImageChunks = -1 } = detail - const idx = this.findPanelIndex(target as HTMLElement) - const returnAcc = [...acc] - if (idx >= 0) { - returnAcc[idx] = missingChunks + missingImageChunks - } - return returnAcc - }, [-1, -1, -1]), - map(arr => { - let sum = 0 - let uncertain = false - for (const num of arr) { - if (num < 0) { - uncertain = true - } else { - sum += num - } - } - return sum > 0 - ? `Loading ${sum}${uncertain ? '+' : ''} chunks ...` - : null - }), - distinctUntilChanged(), - startWith('Loading ...'), - throttleTime(100, asyncScheduler, { leading: true, trailing: true }), - shareReplay(1), - )) - ) - - /* missing chunk perspective view */ - this.perspectiveViewLoading$ = fromEvent(this.elementRef.nativeElement, 'perpspectiveRenderEvent') - .pipe( - filter(event => isDefined(event) && isDefined((event as any).detail) && isDefined((event as any).detail.lastLoadedMeshId) ), - map(event => { - - /** - * TODO dig into event detail to see if the exact mesh loaded - */ - const { meshesLoaded, meshFragmentsLoaded, lastLoadedMeshId } = (event as any).detail - return meshesLoaded >= this.nehubaViewer.numMeshesToBeLoaded - ? null - : 'Loading meshes ...' - }), - distinctUntilChanged() - ) - - this.ngLayers$ = this.store.pipe( - select('ngViewerState'), - ) - - this.hideSegmentations$ = this.ngLayers$.pipe( - map(state => isDefined(state) - ? state.layers?.findIndex(l => l.mixability === 'nonmixable') >= 0 - : false), - ) - - /** - * fixes - * https://github.com/HumanBrainProject/interactive-viewer/issues/800 - */ - this.subscriptions.push( - this.nehubaViewerLoaded.pipe( - debounceTime(500), - filter(v => !v), - ).subscribe(() => { - this.matDrawerMain.close() - this.matDrawerMinor.close() - }) - ) - } - - public useMobileUI$: Observable<boolean> - - private removeExistingPanels() { - const element = this.nehubaViewer.nehubaViewer.ngviewer.layout.container.componentValue.element as HTMLElement - while (element.childElementCount > 0) { - element.removeChild(element.firstElementChild) - } - return element - } - - private findPanelIndex = (panel: HTMLElement) => this.viewPanels?.findIndex(p => p === panel) - - private _exportNehuba: any - get exportNehuba() { - if (!this._exportNehuba) { - this._exportNehuba = getExportNehuba() - } - return this._exportNehuba - } - - public ngOnInit() { - this.hoveredPanelIndices$ = fromEvent(this.elementRef.nativeElement, 'mouseover').pipe( - switchMap((ev: MouseEvent) => merge( - of(this.findPanelIndex(ev.target as HTMLElement)), - fromEvent(this.elementRef.nativeElement, 'mouseout').pipe( - mapTo(null), - ), - )), - debounceTime(20), - shareReplay(1), - ) - - // TODO deprecate - /* each time a new viewer is initialised, take the first event to get the translation function */ - this.subscriptions.push( - this.newViewer$.pipe( - switchMapTo(this.sliceRenderEvent$.pipe( - takeOnePipe() - )) - ).subscribe((events) => { - for (const idx in [0, 1, 2]) { - const ev = events[idx] as CustomEvent - this.viewPanels[idx] = ev.target as HTMLElement - this.nanometersToOffsetPixelsFn[idx] = ev.detail.nanometersToOffsetPixels - } - }), - ) - - this.subscriptions.push( - this.newViewer$.pipe( - switchMapTo(fromEvent(this.elementRef.nativeElement, 'perpspectiveRenderEvent').pipe( - take(1), - )), - ).subscribe(ev => this.viewPanels[3] = ((ev as CustomEvent).target) as HTMLElement), - ) - - this.subscriptions.push( - this.redrawLayout$.subscribe(([mode, panelOrder]) => { - const viewPanels = panelOrder.split('').map(v => Number(v)).map(idx => this.viewPanels[idx]) as [HTMLElement, HTMLElement, HTMLElement, HTMLElement] - /** - * TODO be smarter with event stream - */ - if (!this.nehubaViewer) { return } - - /** - * TODO smarter with event stream - */ - if (!viewPanels.every(v => !!v)) { return } - - switch (mode) { - case PANELS.H_ONE_THREE: { - const element = this.removeExistingPanels() - const newEl = getHorizontalOneThree(viewPanels) - element.appendChild(newEl) - break; - } - case PANELS.V_ONE_THREE: { - const element = this.removeExistingPanels() - const newEl = getVerticalOneThree(viewPanels) - element.appendChild(newEl) - break; - } - case PANELS.FOUR_PANEL: { - const element = this.removeExistingPanels() - const newEl = getFourPanel(viewPanels) - element.appendChild(newEl) - break; - } - case PANELS.SINGLE_PANEL: { - const element = this.removeExistingPanels() - const newEl = getSinglePanel(viewPanels) - element.appendChild(newEl) - break; - } - default: - } - for (const panel of viewPanels) { - (panel as HTMLElement).classList.add('neuroglancer-panel') - } - - // TODO needed to redraw? - // see https://trello.com/c/oJOnlc6v/60-enlarge-panel-allow-user-rearrange-panel-position - // further investigaation required - this.nehubaViewer.redraw() - }), - ) - - this.subscriptions.push( - this.fetchedSpatialDatasets$.subscribe(datasets => { - this.landmarksLabelIndexMap = new Map(datasets.map((v, idx) => [idx, v]) as Array<[number, any]>) - this.landmarksNameMap = new Map(datasets.map((v, idx) => [v.name, idx] as [string, number])) - }), - ) - - /** - * TODO deprecate, but document the method - */ - this.subscriptions.push( - combineLatest( - this.fetchedSpatialDatasets$, - ).subscribe(([fetchedSpatialData]) => { - this.fetchedSpatialData = fetchedSpatialData - - if (this.fetchedSpatialData?.length > 0) { - this.nehubaViewer.addSpatialSearch3DLandmarks( - this.fetchedSpatialData - .map(data => data.geometry.type === 'point' - ? (data.geometry as IPointLandmarkGeometry).position - : data.geometry.type === 'plane' - ? [ - (data.geometry as IPlaneLandmarkGeometry).corners, - [[0, 1, 2], [0, 2, 3]], - ] - : data.geometry.type === 'mesh' - ? [ - (data.geometry as IOtherLandmarkGeometry).vertices, - (data.geometry as IOtherLandmarkGeometry).meshIdx, - ] - : null), - ) - } else { - if (this.nehubaViewer && this.nehubaViewer.removeSpatialSearch3DLandmarks instanceof Function) { - this.nehubaViewer.removeSpatialSearch3DLandmarks() - } - } - }), - ) - - this.subscriptions.push( - this.userLandmarks$.pipe( - withLatestFrom( - this.nehubaViewerPerspectiveOctantRemoval$ - ) - ).subscribe(([landmarks, flag]) => { - if (this.nehubaContainerDirective) { - this.nehubaContainerDirective.toggleOctantRemoval( - landmarks.length > 0 ? false : flag - ) - } - if (this.nehubaViewer) { - this.nehubaViewer.updateUserLandmarks(landmarks) - } - }), - ) - - this.subscriptions.push( - this.newViewer$.pipe( - skip(1), - ).subscribe(() => { - - /* on selecting of new template, remove additional nglayers */ - const baseLayerNames = Object.keys(this.selectedTemplate.nehubaConfig.dataset.initialNgState.layers) - this.ngLayersRegister.layers - .filter(layer => baseLayerNames?.findIndex(l => l === layer.name) < 0) - .map(l => l.name) - .forEach(layerName => { - this.store.dispatch(ngViewerActionRemoveNgLayer({ - layer: { - name: layerName - } - })) - }) - }), - ) - - this.subscriptions.push( - this.templateSelected$.subscribe(() => this.destroynehuba()), - ) - - /* order of subscription will determine the order of execution */ - this.subscriptions.push( - this.newViewer$.pipe( - map(templateSelected => { - const deepCopiedState = JSON.parse(JSON.stringify(templateSelected)) - const navigation = deepCopiedState.nehubaConfig.dataset.initialNgState.navigation - if (!navigation) { - return deepCopiedState - } - navigation.zoomFactor = calculateSliceZoomFactor(navigation.zoomFactor) - deepCopiedState.nehubaConfig.dataset.initialNgState.navigation = navigation - return deepCopiedState - }), - withLatestFrom( - this.selectedParcellation$.pipe( - startWith(null), - ) - ), - ).subscribe(([templateSelected, parcellationSelected]) => { - - this.selectedTemplate = templateSelected - this.createNewNehuba(templateSelected) - const foundParcellation = parcellationSelected - && templateSelected?.parcellations?.find(parcellation => parcellationSelected.name === parcellation.name) - this.handleParcellation(foundParcellation || templateSelected.parcellations[0]) - - const nehubaConfig = templateSelected.nehubaConfig - const initialSpec = nehubaConfig.dataset.initialNgState - const {layers} = initialSpec - - const dispatchLayers = Object.keys(layers).map(key => { - const layer = { - name : key, - source : layers[key].source, - mixability : layers[key].type === 'image' - ? 'base' - : 'mixable', - visible : typeof layers[key].visible === 'undefined' - ? true - : layers[key].visible, - transform : typeof layers[key].transform === 'undefined' - ? null - : layers[key].transform, - } - this.ngLayersRegister.layers.push(layer) - return layer - }) - - this.store.dispatch(ngViewerActionAddNgLayer({ - layer: dispatchLayers - })) - }) - ) - - let prevParcellation = null - - this.subscriptions.push( - - combineLatest([ - this.selectedRegions$.pipe( - distinctUntilChanged(), - ), - this.hideSegmentations$.pipe( - distinctUntilChanged(), - ), - this.ngLayers$.pipe( - map(state => state.forceShowSegment), - distinctUntilChanged(), - ), - this.selectedParcellation$, - this.store.pipe( - select('viewerState'), - select('overwrittenColorMap'), - distinctUntilChanged() - ) - ]).pipe( - delayWhen(() => timer()) - ).subscribe(([regions, hideSegmentFlag, forceShowSegment, selectedParcellation, overwrittenColorMap]) => { - if (!this.nehubaViewer) { return } - - const { ngId: defaultNgId } = selectedParcellation - - /* selectedregionindexset needs to be updated regardless of forceshowsegment */ - this.selectedRegionIndexSet = !prevParcellation || prevParcellation === selectedParcellation? - new Set(regions.map(({ngId = defaultNgId, labelIndex}) => generateLabelIndexId({ ngId, labelIndex }))) : new Set() - - if ( forceShowSegment === false || (forceShowSegment === null && hideSegmentFlag) ) { - this.nehubaViewer.hideAllSeg() - return - } - - this.selectedRegionIndexSet.size > 0 && !overwrittenColorMap - ? this.nehubaViewer.showSegs([...this.selectedRegionIndexSet]) - : this.nehubaViewer.showAllSeg() - - prevParcellation = selectedParcellation - }), - ) - - this.subscriptions.push( - this.ngLayers$.subscribe(ngLayersInterface => { - if (!this.nehubaViewer) { return } - - const newLayers = ngLayersInterface.layers.filter(l => this.ngLayersRegister.layers?.findIndex(ol => ol.name === l.name) < 0) - const removeLayers = this.ngLayersRegister.layers.filter(l => ngLayersInterface.layers?.findIndex(nl => nl.name === l.name) < 0) - - if (newLayers?.length > 0) { - const newLayersObj: any = {} - newLayers.forEach(({ name, source, ...rest }) => newLayersObj[name] = { - ...rest, - source, - // source: getProxyUrl(source), - // ...getProxyOther({source}) - }) - - if (!this.nehubaViewer.nehubaViewer || !this.nehubaViewer.nehubaViewer.ngviewer) { - this.nehubaViewer.initNiftiLayers.push(newLayersObj) - } else { - this.nehubaViewer.loadLayer(newLayersObj) - } - this.ngLayersRegister.layers = this.ngLayersRegister.layers.concat(newLayers) - } - - if (removeLayers?.length > 0) { - removeLayers.forEach(l => { - if (this.nehubaViewer.removeLayer({ - name : l.name, - })) { - this.ngLayersRegister.layers = this.ngLayersRegister.layers.filter(rl => rl.name !== l.name) - } - }) - } - }), - ) - - this.subscriptions.push( - this.selectedParcellation$.subscribe(this.handleParcellation.bind(this)) - ) - - /* setup init view state */ - - this.subscriptions.push( - this.selectedRegions$.pipe( - filter(() => !!this.nehubaViewer), - ).subscribe(regions => { - this.nehubaViewer.initRegions = regions.map(({ ngId, labelIndex }) => generateLabelIndexId({ ngId, labelIndex })) - }) - ) - - this.subscriptions.push(this.selectedRegions$.subscribe(sr => { - this.selectedRegions = sr - })) - - /** switch side nav */ - this.subscriptions.push( - this.alwaysHideMinorPanel$.pipe( - distinctUntilChanged() - ).subscribe(flag => { - if (!flag) { - this.matDrawerMinor && this.matDrawerMinor.open() - this.navSideDrawerMainSwitch && this.navSideDrawerMainSwitch.open() - } - }) - ) - - this.subscriptions.push( - this.selectedRegions$.subscribe(regions => { - this.selectedRegions = regions - }) - ) - - /* handler to open/select landmark */ - const clickObs$ = fromEvent(this.elementRef.nativeElement, 'click') - - this.subscriptions.push( - clickObs$.pipe( - buffer( - clickObs$.pipe( - debounceTime(200), - ), - ), - filter(arr => arr?.length >= 2), - ) - .subscribe(() => { - const { currentOnHover } = this - this.store.dispatch(viewerStateDblClickOnViewer({ - payload: { ...currentOnHover } - })) - }), - ) - - this.subscriptions.push( - this.selectedLandmarks$.pipe( - map(lms => lms.map(lm => this.landmarksNameMap.get(lm.name))), - debounceTime(16), - ).subscribe(indices => { - const filteredIndices = indices.filter(v => typeof v !== 'undefined' && v !== null) - if (this.nehubaViewer) { - this.nehubaViewer.spatialLandmarkSelectionChanged(filteredIndices) - } - }), - ) - } - - // datasetViewerRegistry : Set<string> = new Set() - public showObliqueScreen$: Observable<boolean> - public showObliqueSelection$: Observable<boolean> - public showObliqueRotate$: Observable<boolean> - - private currOnHoverObsSub: Subscription - public ngOnChanges() { - this.currOnHoverObsSub && this.currOnHoverObsSub.unsubscribe() - if (this.currentOnHoverObs$) { - this.currOnHoverObsSub = this.currentOnHoverObs$.subscribe(({ segments }) => this.onHoverSegments$.next(segments)) - } - } - - public ngOnDestroy() { - this.subscriptions.forEach(s => s.unsubscribe()) - } - - public toggleMaximiseMinimise(index: number) { - this.store.dispatch(ngViewerActionToggleMax({ - payload: { index } - })) - } - - public tunableMobileProperties: ITunableProp[] = [] - - - public selectedProp = null - - public returnTruePos(quadrant: number, data: any) { - const pos = quadrant > 2 - ? [0, 0, 0] - : this.nanometersToOffsetPixelsFn && this.nanometersToOffsetPixelsFn[quadrant] - ? this.nanometersToOffsetPixelsFn[quadrant](data.geometry.position.map(n => n * 1e6)) - : [0, 0, 0] - return pos - } - - public getPositionX(quadrant: number, data: any) { - return this.returnTruePos(quadrant, data)[0] - } - public getPositionY(quadrant: number, data: any) { - return this.returnTruePos(quadrant, data)[1] - } - public getPositionZ(quadrant: number, data: any) { - return this.returnTruePos(quadrant, data)[2] - } - - public handleMouseEnterCustomLandmark(lm) { - this.store.dispatch( - viewerStateMouseOverCustomLandmark({ - payload: { userLandmark: lm } - }) - ) - } - - public handleMouseLeaveCustomLandmark(lm) { - this.store.dispatch( - viewerStateMouseOverCustomLandmark({ - payload: { userLandmark: null } - }) - ) - } - - // handles mouse enter/leave landmarks in 2D - public handleMouseEnterLandmark(spatialData: any) { - spatialData.highlight = true - this.store.dispatch({ - type : MOUSE_OVER_LANDMARK, - landmark : spatialData._label, - }) - } - - public handleMouseLeaveLandmark(spatialData: any) { - spatialData.highlight = false - this.store.dispatch({ - type : MOUSE_OVER_LANDMARK, - landmark : null, - }) - } - - private handleParcellation(parcellation: any) { - /** - * parcellaiton may be undefined - */ - if ( !(parcellation && parcellation.regions)) { - return - } - - /** - * first, get all all the ngIds, including parent id from parcellation (if defined) - */ - const ngIds = getNgIds(parcellation.regions).concat( parcellation.ngId ? parcellation.ngId : []) - - this.multiNgIdsRegionsLabelIndexMap = getMultiNgIdsRegionsLabelIndexMap(parcellation) - - this.nehubaViewer.multiNgIdsLabelIndexMap = this.multiNgIdsRegionsLabelIndexMap - this.nehubaViewer.auxilaryMeshIndices = parcellation.auxillaryMeshIndices || [] - - /* TODO replace with proper KG id */ - /** - * need to set unique array of ngIds, or else workers will be overworked - */ - this.nehubaViewer.ngIds = Array.from(new Set(ngIds)) - this.selectedParcellation = parcellation - } - - /* related spatial search */ - public spatialSearchPagination: number = 0 - - private destroynehuba() { - /** - * TODO if plugin subscribes to viewerHandle, and then new template is selected, changes willl not be be sent - * could be considered as a bug. - */ - this.setViewerHandle && this.setViewerHandle(null) - this.nehubaContainerDirective.clear() - - this.nehubaViewer = null - - this.cdr.detectChanges() - } - - private createNewNehuba(template: any) { - - this.nehubaContainerDirective.createNehubaInstance(template) - this.nehubaViewer = this.nehubaContainerDirective.nehubaViewerInstance - - this.setupViewerHandleApi() - } - - private setupViewerHandleApi() { - const viewerHandle = { - setNavigationLoc : (coord, realSpace?) => this.nehubaViewer.setNavigationState({ - position : coord, - positionReal : typeof realSpace !== 'undefined' ? realSpace : true, - }), - /* TODO introduce animation */ - moveToNavigationLoc : (coord, realSpace?) => { - this.store.dispatch({ - type: CHANGE_NAVIGATION, - navigation: { - position: coord, - animation: {}, - }, - }) - }, - setNavigationOri : (quat) => this.nehubaViewer.setNavigationState({ - orientation : quat, - }), - /* TODO introduce animation */ - moveToNavigationOri : (quat) => this.nehubaViewer.setNavigationState({ - orientation : quat, - }), - showSegment : (_labelIndex) => { - /** - * TODO reenable with updated select_regions api - */ - this.log.warn(`showSegment is temporarily disabled`) - - // if(!this.selectedRegionIndexSet.has(labelIndex)) - // this.store.dispatch({ - // type : SELECT_REGIONS, - // selectRegions : [labelIndex, ...this.selectedRegionIndexSet] - // }) - }, - add3DLandmarks : landmarks => { - // TODO check uniqueness of ID - if (!landmarks.every(l => isDefined(l.id))) { - throw new Error('every landmarks needs to be identified with the id field') - } - if (!landmarks.every(l => isDefined(l.position))) { - throw new Error('every landmarks needs to have position defined') - } - if (!landmarks.every(l => l.position.constructor === Array) || !landmarks.every(l => l.position.every(v => !isNaN(v))) || !landmarks.every(l => l.position.length == 3)) { - throw new Error('position needs to be a length 3 tuple of numbers ') - } - - this.store.dispatch(viewerStateAddUserLandmarks({ - landmarks - })) - }, - remove3DLandmarks : landmarkIds => { - this.store.dispatch(viewreStateRemoveUserLandmarks({ - payload: { landmarkIds } - })) - }, - hideSegment : (_labelIndex) => { - /** - * TODO reenable with updated select_regions api - */ - this.log.warn(`hideSegment is temporarily disabled`) - - // if(this.selectedRegionIndexSet.has(labelIndex)){ - // this.store.dispatch({ - // type :SELECT_REGIONS, - // selectRegions : [...this.selectedRegionIndexSet].filter(num=>num!==labelIndex) - // }) - // } - }, - showAllSegments : () => { - const selectRegionIds = [] - this.multiNgIdsRegionsLabelIndexMap.forEach((map, ngId) => { - Array.from(map.keys()).forEach(labelIndex => { - selectRegionIds.push(generateLabelIndexId({ ngId, labelIndex })) - }) - }) - this.store.dispatch(viewerStateSelectRegionWithIdDeprecated({ - selectRegionIds - })) - }, - hideAllSegments : () => { - this.store.dispatch(viewerStateSelectRegionWithIdDeprecated({ - selectRegionIds: [] - })) - }, - segmentColourMap : new Map(), - getLayersSegmentColourMap: () => { - const newMainMap = new Map() - for (const [key, colormap] of this.nehubaViewer.multiNgIdColorMap.entries()) { - const newColormap = new Map() - newMainMap.set(key, newColormap) - - for (const [lableIndex, entry] of colormap.entries()) { - newColormap.set(lableIndex, JSON.parse(JSON.stringify(entry))) - } - } - return newMainMap - }, - applyColourMap : (_map) => { - throw new Error(`apply color map has been deprecated. use applyLayersColourMap instead`) - }, - applyLayersColourMap: (map) => { - this.nehubaViewer.setColorMap(map) - }, - loadLayer : (layerObj) => this.nehubaViewer.loadLayer(layerObj), - removeLayer : (condition) => this.nehubaViewer.removeLayer(condition), - setLayerVisibility : (condition, visible) => this.nehubaViewer.setLayerVisibility(condition, visible), - mouseEvent : merge( - fromEvent(this.elementRef.nativeElement, 'click').pipe( - map((ev: MouseEvent) => ({eventName : 'click', event: ev})), - ), - fromEvent(this.elementRef.nativeElement, 'mousemove').pipe( - map((ev: MouseEvent) => ({eventName : 'mousemove', event: ev})), - ), - /** - * neuroglancer prevents propagation, so use capture instead - */ - Observable.create(observer => { - this.elementRef.nativeElement.addEventListener('mousedown', event => observer.next({eventName: 'mousedown', event}), true) - }) as Observable<{eventName: string, event: MouseEvent}>, - fromEvent(this.elementRef.nativeElement, 'mouseup').pipe( - map((ev: MouseEvent) => ({eventName : 'mouseup', event: ev})), - ), - ) , - mouseOverNehuba : this.onHoverSegment$.pipe( - tap(() => console.warn('mouseOverNehuba observable is becoming deprecated. use mouseOverNehubaLayers instead.')), - ), - mouseOverNehubaLayers: this.onHoverSegments$, - mouseOverNehubaUI: this.currentOnHoverObs$.pipe( - map(({ landmark, segments, userLandmark: customLandmark }) => ({ segments, landmark, customLandmark })), - shareReplay(1), - ), - getNgHash : this.nehubaViewer.getNgHash, - } - - this.setViewerHandle && this.setViewerHandle(viewerHandle) - } - - public setOctantRemoval(octantRemovalFlag: boolean) { - this.store.dispatch( - ngViewerActionSetPerspOctantRemoval({ - octantRemovalFlag - }) - ) - } - - public zoomNgView(panelIndex: number, factor: number) { - const ngviewer = this.nehubaViewer?.nehubaViewer?.ngviewer - if (!ngviewer) throw new Error(`ngviewer not defined!`) - - /** - * panelIndex < 3 === slice view - */ - if (panelIndex < 3) { - /** - * factor > 1 === zoom out - */ - ngviewer.navigationState.zoomBy(factor) - } else { - ngviewer.perspectiveNavigationState.zoomBy(factor) - } - } - - public clearPreviewingDataset(id: string){ - /** - * clear all preview - */ - this.store.dispatch( - id - ? uiActionHideDatasetWithId({ id }) - : uiActionHideAllDatasets() - ) - } -} +// public ID_MESH_LOADING_STATUS = MESH_LOADING_STATUS + +// @ViewChild(NehubaViewerContainerDirective,{static: true}) +// public nehubaContainerDirective: NehubaViewerContainerDirective + +// // @ViewChild('sideNavMasterSwitch', { static: true }) +// // public navSideDrawerMainSwitch: SwitchDirective +// // @ViewChild('sideNavSwitch', { static: true }) +// // public navSideDrawerMinorSwitch: SwitchDirective + +// @ViewChild('matDrawerMaster', {static: true}) +// public matDrawerMain: MatDrawer +// @ViewChild('matDrawerMinor', { static: true }) +// public matDrawerMinor: MatDrawer + +// @Output() +// public nehubaViewerLoaded: EventEmitter<boolean> = new EventEmitter() + +// @Output() +// public forceUI$: Observable<{ target: 'perspective:octantRemoval', mode: boolean, message?: string }> + +// public disableOctantRemoval$: Observable<{ message?: string, mode: boolean }> + +// public handleViewerLoadedEvent(flag: boolean){ +// this.viewerLoaded = flag +// this.nehubaViewerLoaded.emit(flag) +// } + +// public viewerLoaded: boolean = false + +// private sliceRenderEvent$: Observable<CustomEvent> +// public sliceViewLoadingMain$: Observable<[boolean, boolean, boolean]> +// public perspectiveViewLoading$: Observable<string|null> +// public showPerpsectiveScreen$: Observable<string> + +// public templateSelected$: Observable<any> = this.store.pipe( +// select(viewerStateSelectedTemplateSelector), +// distinctUntilChanged(isSame), +// ) + +// private newViewer$: Observable<any> = this.templateSelected$.pipe( +// filter(v => !!v), +// ) + +// private selectedParcellation$: Observable<any> = this.store.pipe( +// select(viewerStateSelectedParcellationSelector), +// distinctUntilChanged(), +// filter(v => !!v) +// ) +// public selectedRegions: any[] = [] +// public selectedRegions$: Observable<any[]> = this.store.pipe( +// select(viewerStateSelectedRegionsSelector), +// filter(rs => !!rs), +// ) + +// public selectedLandmarks$: Observable<any[]> +// public selectedPtLandmarks$: Observable<any[]> +// public customLandmarks$: Observable<any> = this.store.pipe( +// select(viewerStateCustomLandmarkSelector), +// map(lms => lms.map(lm => ({ +// ...lm, +// geometry: { +// position: lm.position +// } +// }))) +// ) +// private hideSegmentations$: Observable<boolean> + +// private fetchedSpatialDatasets$: Observable<ILandmark[]> +// private userLandmarks$: Observable<IUserLandmark[]> + +// public nehubaViewerPerspectiveOctantRemoval$: Observable<boolean> + +// @Input() +// private currentOnHover: {segments: any, landmark: any, userLandmark: any} + +// @Input() +// currentOnHoverObs$: Observable<{segments: any, landmark: any, userLandmark: any}> + +// public iavAdditionalLayers$ = new Subject<any[]>() + +// // public alwaysHideMinorPanel$: Observable<boolean> = combineLatest( +// // this.selectedRegions$, +// // this.iavAdditionalLayers$.pipe( +// // startWith([]) +// // ) +// // ).pipe( +// // map(([ regions, layers ]) => regions.length === 0 && layers.length === 0) +// // ) + +// public onHoverSegments$: BehaviorSubject<any[]> = new BehaviorSubject([]) +// public onHoverSegment$: Observable<any> = this.onHoverSegments$.pipe( +// scan(sortByFreshness, []), +// /** +// * take the first element after sort by freshness +// */ +// map(arr => arr[0]), +// /** +// * map to the older interface +// */ +// filter(v => !!v), +// map(({ segment }) => { +// return { +// labelIndex: (isNaN(segment) && Number(segment.labelIndex)) || null, +// foundRegion: (isNaN(segment) && segment) || null, +// } +// }), +// ) + +// public selectedTemplate: any | null +// private selectedRegionIndexSet: Set<string> = new Set() +// public fetchedSpatialData: ILandmark[] = [] + +// private ngLayersRegister: Partial<NgViewerStateInterface> = {layers : [], forceShowSegment: null} +// private ngLayers$: Observable<NgViewerStateInterface> + +// public selectedParcellation: any | null + +// public nehubaViewer: NehubaViewerUnit = null +// private multiNgIdsRegionsLabelIndexMap: Map<string, Map<number, any>> = new Map() +// private landmarksLabelIndexMap: Map<number, any> = new Map() +// private landmarksNameMap: Map<string, number> = new Map() + +// private subscriptions: Subscription[] = [] + +// public nanometersToOffsetPixelsFn: Array<(...arg) => any> = [] + +// public viewPanels: [HTMLElement, HTMLElement, HTMLElement, HTMLElement] = [null, null, null, null] +// public panelMode$: Observable<string> + +// public panelOrder$: Observable<string> +// private redrawLayout$: Observable<[string, string]> + +// public hoveredPanelIndices$: Observable<number> + +// public connectivityNumber: string + +// constructor( +// private pureConstantService: PureContantService, +// @Optional() @Inject(API_SERVICE_SET_VIEWER_HANDLE_TOKEN) private setViewerHandle: (arg) => void, +// private store: Store<any>, +// private elementRef: ElementRef, +// private log: LoggingService, +// private cdr: ChangeDetectorRef, +// @Optional() @Inject(REGION_OF_INTEREST) public regionOfInterest$: Observable<any> +// ) { + +// this.useMobileUI$ = this.pureConstantService.useTouchUI$ + +// // this.nehubaViewerPerspectiveOctantRemoval$ = this.store.pipe( +// // select(ngViewerSelectorOctantRemoval), +// // ) + +// // this.panelMode$ = this.store.pipe( +// // select(ngViewerSelectorPanelMode), +// // distinctUntilChanged(), +// // shareReplay(1), +// // ) + +// // this.panelOrder$ = this.store.pipe( +// // select(ngViewerSelectorPanelOrder), +// // distinctUntilChanged(), +// // shareReplay(1), +// // ) + +// // this.redrawLayout$ = this.store.pipe( +// // select(ngViewerSelectorNehubaReady), +// // distinctUntilChanged(), +// // filter(v => !!v), +// // switchMapTo(combineLatest([ +// // this.panelMode$, +// // this.panelOrder$, +// // ])), +// // ) + +// this.selectedLandmarks$ = this.store.pipe( +// select('viewerState'), +// select('landmarksSelected'), +// ) + +// this.selectedPtLandmarks$ = this.selectedLandmarks$.pipe( +// map(lms => lms.filter(lm => lm.geometry.type === 'point')), +// ) + +// this.fetchedSpatialDatasets$ = this.store.pipe( +// select('dataStore'), +// select('fetchedSpatialData'), +// distinctUntilChanged(compareLandmarksChanged), +// filter(v => !!v), +// startWith([]), +// debounceTime(300), +// ) + +// // this.userLandmarks$ = this.store.pipe( +// // select(viewerStateCustomLandmarkSelector), +// // distinctUntilChanged(), +// // ) + +// /** +// * in future, perhaps add other force UI optinos here +// */ +// // this.forceUI$ = this.userLandmarks$.pipe( +// // map(lm => { +// // if (lm.length > 0) { +// // return { +// // target: 'perspective:octantRemoval', +// // mode: false, +// // message: `octant control disabled: showing landmarks.` +// // } +// // } else { +// // return { +// // target: 'perspective:octantRemoval', +// // mode: null +// // } +// // } +// // }) +// // ) + +// // this.disableOctantRemoval$ = this.forceUI$.pipe( +// // filter(({ target }) => target === 'perspective:octantRemoval'), +// // ) + +// // this.sliceRenderEvent$ = fromEvent(this.elementRef.nativeElement, 'sliceRenderEvent').pipe( +// // map(ev => ev as CustomEvent) +// // ) + +// // this.sliceViewLoadingMain$ = this.sliceRenderEvent$.pipe( +// // scan(scanFn, [null, null, null]), +// // startWith([true, true, true] as [boolean, boolean, boolean]), +// // shareReplay(1), +// // ) + +// // this.showPerpsectiveScreen$ = this.newViewer$.pipe( +// // switchMapTo(this.sliceRenderEvent$.pipe( +// // scan((acc, curr) => { + +// // /** +// // * if at any point, all chunks have been loaded, always return loaded state +// // */ +// // if (acc.every(v => v === 0)) return [0, 0, 0] +// // const { detail = {}, target } = curr || {} +// // const { missingChunks = -1, missingImageChunks = -1 } = detail +// // const idx = this.findPanelIndex(target as HTMLElement) +// // const returnAcc = [...acc] +// // if (idx >= 0) { +// // returnAcc[idx] = missingChunks + missingImageChunks +// // } +// // return returnAcc +// // }, [-1, -1, -1]), +// // map(arr => { +// // let sum = 0 +// // let uncertain = false +// // for (const num of arr) { +// // if (num < 0) { +// // uncertain = true +// // } else { +// // sum += num +// // } +// // } +// // return sum > 0 +// // ? `Loading ${sum}${uncertain ? '+' : ''} chunks ...` +// // : null +// // }), +// // distinctUntilChanged(), +// // startWith('Loading ...'), +// // throttleTime(100, asyncScheduler, { leading: true, trailing: true }), +// // shareReplay(1), +// // )) +// // ) + +// /* missing chunk perspective view */ +// // this.perspectiveViewLoading$ = fromEvent(this.elementRef.nativeElement, 'perpspectiveRenderEvent') +// // .pipe( +// // filter(event => isDefined(event) && isDefined((event as any).detail) && isDefined((event as any).detail.lastLoadedMeshId) ), +// // map(event => { + +// // /** +// // * TODO dig into event detail to see if the exact mesh loaded +// // */ +// // const { meshesLoaded, meshFragmentsLoaded, lastLoadedMeshId } = (event as any).detail +// // return meshesLoaded >= this.nehubaViewer.numMeshesToBeLoaded +// // ? null +// // : 'Loading meshes ...' +// // }), +// // distinctUntilChanged() +// // ) + +// this.ngLayers$ = this.store.pipe( +// select('ngViewerState'), +// ) + +// this.hideSegmentations$ = this.ngLayers$.pipe( +// map(state => isDefined(state) +// ? state.layers?.findIndex(l => l.mixability === 'nonmixable') >= 0 +// : false), +// ) + +// /** +// * fixes +// * https://github.com/HumanBrainProject/interactive-viewer/issues/800 +// */ +// this.subscriptions.push( +// this.nehubaViewerLoaded.pipe( +// debounceTime(500), +// filter(v => !v), +// ).subscribe(() => { +// this.matDrawerMain.close() +// this.matDrawerMinor.close() +// }) +// ) +// } + +// public useMobileUI$: Observable<boolean> + +// // private removeExistingPanels() { +// // const element = this.nehubaViewer.nehubaViewer.ngviewer.layout.container.componentValue.element as HTMLElement +// // while (element.childElementCount > 0) { +// // element.removeChild(element.firstElementChild) +// // } +// // return element +// // } + +// // private findPanelIndex = (panel: HTMLElement) => this.viewPanels?.findIndex(p => p === panel) + +// private _exportNehuba: any +// get exportNehuba() { +// if (!this._exportNehuba) { +// this._exportNehuba = getExportNehuba() +// } +// return this._exportNehuba +// } + +// public ngOnInit() { +// // this.hoveredPanelIndices$ = fromEvent(this.elementRef.nativeElement, 'mouseover').pipe( +// // switchMap((ev: MouseEvent) => merge( +// // of(this.findPanelIndex(ev.target as HTMLElement)), +// // fromEvent(this.elementRef.nativeElement, 'mouseout').pipe( +// // mapTo(null), +// // ), +// // )), +// // debounceTime(20), +// // shareReplay(1), +// // ) + +// // TODO deprecate +// /* each time a new viewer is initialised, take the first event to get the translation function */ +// // this.subscriptions.push( +// // this.newViewer$.pipe( +// // switchMapTo(this.sliceRenderEvent$.pipe( +// // takeOnePipe() +// // )) +// // ).subscribe((events) => { +// // for (const idx in [0, 1, 2]) { +// // const ev = events[idx] as CustomEvent +// // this.viewPanels[idx] = ev.target as HTMLElement +// // this.nanometersToOffsetPixelsFn[idx] = ev.detail.nanometersToOffsetPixels +// // } +// // }), +// // ) + +// // this.subscriptions.push( +// // this.newViewer$.pipe( +// // switchMapTo(fromEvent(this.elementRef.nativeElement, 'perpspectiveRenderEvent').pipe( +// // take(1), +// // )), +// // ).subscribe(ev => this.viewPanels[3] = ((ev as CustomEvent).target) as HTMLElement), +// // ) + +// // this.subscriptions.push( +// // this.redrawLayout$.subscribe(([mode, panelOrder]) => { +// // const viewPanels = panelOrder.split('').map(v => Number(v)).map(idx => this.viewPanels[idx]) as [HTMLElement, HTMLElement, HTMLElement, HTMLElement] +// // /** +// // * TODO be smarter with event stream +// // */ +// // if (!this.nehubaViewer) { return } + +// // /** +// // * TODO smarter with event stream +// // */ +// // if (!viewPanels.every(v => !!v)) { return } + +// // switch (mode) { +// // case PANELS.H_ONE_THREE: { +// // const element = this.removeExistingPanels() +// // const newEl = getHorizontalOneThree(viewPanels) +// // element.appendChild(newEl) +// // break; +// // } +// // case PANELS.V_ONE_THREE: { +// // const element = this.removeExistingPanels() +// // const newEl = getVerticalOneThree(viewPanels) +// // element.appendChild(newEl) +// // break; +// // } +// // case PANELS.FOUR_PANEL: { +// // const element = this.removeExistingPanels() +// // const newEl = getFourPanel(viewPanels) +// // element.appendChild(newEl) +// // break; +// // } +// // case PANELS.SINGLE_PANEL: { +// // const element = this.removeExistingPanels() +// // const newEl = getSinglePanel(viewPanels) +// // element.appendChild(newEl) +// // break; +// // } +// // default: +// // } +// // for (const panel of viewPanels) { +// // (panel as HTMLElement).classList.add('neuroglancer-panel') +// // } + +// // // TODO needed to redraw? +// // // see https://trello.com/c/oJOnlc6v/60-enlarge-panel-allow-user-rearrange-panel-position +// // // further investigaation required +// // this.nehubaViewer.redraw() +// // }), +// // ) + +// this.subscriptions.push( +// this.fetchedSpatialDatasets$.subscribe(datasets => { +// this.landmarksLabelIndexMap = new Map(datasets.map((v, idx) => [idx, v]) as Array<[number, any]>) +// this.landmarksNameMap = new Map(datasets.map((v, idx) => [v.name, idx] as [string, number])) +// }), +// ) + +// /** +// * TODO deprecate, but document the method +// */ +// this.subscriptions.push( +// combineLatest( +// this.fetchedSpatialDatasets$, +// ).subscribe(([fetchedSpatialData]) => { +// this.fetchedSpatialData = fetchedSpatialData + +// if (this.fetchedSpatialData?.length > 0) { +// this.nehubaViewer.addSpatialSearch3DLandmarks( +// this.fetchedSpatialData +// .map(data => data.geometry.type === 'point' +// ? (data.geometry as IPointLandmarkGeometry).position +// : data.geometry.type === 'plane' +// ? [ +// (data.geometry as IPlaneLandmarkGeometry).corners, +// [[0, 1, 2], [0, 2, 3]], +// ] +// : data.geometry.type === 'mesh' +// ? [ +// (data.geometry as IOtherLandmarkGeometry).vertices, +// (data.geometry as IOtherLandmarkGeometry).meshIdx, +// ] +// : null), +// ) +// } else { +// if (this.nehubaViewer && this.nehubaViewer.removeSpatialSearch3DLandmarks instanceof Function) { +// this.nehubaViewer.removeSpatialSearch3DLandmarks() +// } +// } +// }), +// ) + +// // this.subscriptions.push( +// // this.userLandmarks$.pipe( +// // withLatestFrom( +// // this.nehubaViewerPerspectiveOctantRemoval$ +// // ) +// // ).subscribe(([landmarks, flag]) => { +// // if (this.nehubaContainerDirective) { +// // this.nehubaContainerDirective.toggleOctantRemoval( +// // landmarks.length > 0 ? false : flag +// // ) +// // } +// // if (this.nehubaViewer) { +// // this.nehubaViewer.updateUserLandmarks(landmarks) +// // } +// // }), +// // ) + +// // this.subscriptions.push( +// // this.newViewer$.pipe( +// // skip(1), +// // ).subscribe(() => { + +// // /* on selecting of new template, remove additional nglayers */ +// // const baseLayerNames = Object.keys(this.selectedTemplate.nehubaConfig.dataset.initialNgState.layers) +// // this.ngLayersRegister.layers +// // .filter(layer => baseLayerNames?.findIndex(l => l === layer.name) < 0) +// // .map(l => l.name) +// // .forEach(layerName => { +// // this.store.dispatch(ngViewerActionRemoveNgLayer({ +// // layer: { +// // name: layerName +// // } +// // })) +// // }) +// // }), +// // ) + +// this.subscriptions.push( +// this.templateSelected$.subscribe(() => this.destroynehuba()), +// ) + +// /* order of subscription will determine the order of execution */ +// // this.subscriptions.push( +// // this.newViewer$.pipe( +// // map(templateSelected => { +// // const deepCopiedState = JSON.parse(JSON.stringify(templateSelected)) +// // const navigation = deepCopiedState.nehubaConfig.dataset.initialNgState.navigation +// // if (!navigation) { +// // return deepCopiedState +// // } +// // navigation.zoomFactor = calculateSliceZoomFactor(navigation.zoomFactor) +// // deepCopiedState.nehubaConfig.dataset.initialNgState.navigation = navigation +// // return deepCopiedState +// // }), +// // withLatestFrom( +// // this.selectedParcellation$.pipe( +// // startWith(null), +// // ) +// // ), +// // ).subscribe(([templateSelected, parcellationSelected]) => { + +// // this.selectedTemplate = templateSelected +// // this.createNewNehuba(templateSelected) +// // const foundParcellation = parcellationSelected +// // && templateSelected?.parcellations?.find(parcellation => parcellationSelected.name === parcellation.name) +// // this.handleParcellation(foundParcellation || templateSelected.parcellations[0]) + +// // const nehubaConfig = templateSelected.nehubaConfig +// // const initialSpec = nehubaConfig.dataset.initialNgState +// // const {layers} = initialSpec + +// // const dispatchLayers = Object.keys(layers).map(key => { +// // const layer = { +// // name : key, +// // source : layers[key].source, +// // mixability : layers[key].type === 'image' +// // ? 'base' +// // : 'mixable', +// // visible : typeof layers[key].visible === 'undefined' +// // ? true +// // : layers[key].visible, +// // transform : typeof layers[key].transform === 'undefined' +// // ? null +// // : layers[key].transform, +// // } +// // this.ngLayersRegister.layers.push(layer) +// // return layer +// // }) + +// // this.store.dispatch(ngViewerActionAddNgLayer({ +// // layer: dispatchLayers +// // })) +// // }) +// // ) + +// let prevParcellation = null + +// // this.subscriptions.push( + +// // combineLatest([ +// // this.selectedRegions$.pipe( +// // distinctUntilChanged(), +// // ), +// // this.hideSegmentations$.pipe( +// // distinctUntilChanged(), +// // ), +// // this.ngLayers$.pipe( +// // map(state => state.forceShowSegment), +// // distinctUntilChanged(), +// // ), +// // this.selectedParcellation$, +// // this.store.pipe( +// // select('viewerState'), +// // select('overwrittenColorMap'), +// // distinctUntilChanged() +// // ) +// // ]).pipe( +// // delayWhen(() => timer()) +// // ).subscribe(([regions, hideSegmentFlag, forceShowSegment, selectedParcellation, overwrittenColorMap]) => { +// // if (!this.nehubaViewer) { return } + +// // const { ngId: defaultNgId } = selectedParcellation + +// // /* selectedregionindexset needs to be updated regardless of forceshowsegment */ +// // this.selectedRegionIndexSet = !prevParcellation || prevParcellation === selectedParcellation? +// // new Set(regions.map(({ngId = defaultNgId, labelIndex}) => serialiseParcellationRegion({ ngId, labelIndex }))) : new Set() + +// // if ( forceShowSegment === false || (forceShowSegment === null && hideSegmentFlag) ) { +// // this.nehubaViewer.hideAllSeg() +// // return +// // } + +// // this.selectedRegionIndexSet.size > 0 && !overwrittenColorMap +// // ? this.nehubaViewer.showSegs([...this.selectedRegionIndexSet]) +// // : this.nehubaViewer.showAllSeg() + +// // prevParcellation = selectedParcellation +// // }), +// // ) + +// // this.subscriptions.push( +// // this.ngLayers$.subscribe(ngLayersInterface => { +// // if (!this.nehubaViewer) { return } + +// // const newLayers = ngLayersInterface.layers.filter(l => this.ngLayersRegister.layers?.findIndex(ol => ol.name === l.name) < 0) +// // const removeLayers = this.ngLayersRegister.layers.filter(l => ngLayersInterface.layers?.findIndex(nl => nl.name === l.name) < 0) + +// // if (newLayers?.length > 0) { +// // const newLayersObj: any = {} +// // newLayers.forEach(({ name, source, ...rest }) => newLayersObj[name] = { +// // ...rest, +// // source, +// // // source: getProxyUrl(source), +// // // ...getProxyOther({source}) +// // }) + +// // if (!this.nehubaViewer.nehubaViewer || !this.nehubaViewer.nehubaViewer.ngviewer) { +// // this.nehubaViewer.initNiftiLayers.push(newLayersObj) +// // } else { +// // this.nehubaViewer.loadLayer(newLayersObj) +// // } +// // this.ngLayersRegister.layers = this.ngLayersRegister.layers.concat(newLayers) +// // } + +// // if (removeLayers?.length > 0) { +// // removeLayers.forEach(l => { +// // if (this.nehubaViewer.removeLayer({ +// // name : l.name, +// // })) { +// // this.ngLayersRegister.layers = this.ngLayersRegister.layers.filter(rl => rl.name !== l.name) +// // } +// // }) +// // } +// // }), +// // ) + +// this.subscriptions.push( +// this.selectedParcellation$.subscribe(this.handleParcellation.bind(this)) +// ) + +// /* setup init view state */ + +// this.subscriptions.push( +// this.selectedRegions$.pipe( +// filter(() => !!this.nehubaViewer), +// ).subscribe(regions => { +// this.nehubaViewer.initRegions = regions.map(({ ngId, labelIndex }) => serialiseParcellationRegion({ ngId, labelIndex })) +// }) +// ) + +// this.subscriptions.push(this.selectedRegions$.subscribe(sr => { +// this.selectedRegions = sr +// })) + +// /** switch side nav */ +// // this.subscriptions.push( +// // this.alwaysHideMinorPanel$.pipe( +// // distinctUntilChanged() +// // ).subscribe(flag => { +// // if (!flag) { +// // this.matDrawerMinor && this.matDrawerMinor.open() +// // this.navSideDrawerMainSwitch && this.navSideDrawerMainSwitch.open() +// // } +// // }) +// // ) + +// this.subscriptions.push( +// this.selectedRegions$.subscribe(regions => { +// this.selectedRegions = regions +// }) +// ) + +// /* handler to open/select landmark */ +// const clickObs$ = fromEvent(this.elementRef.nativeElement, 'click') + +// this.subscriptions.push( +// clickObs$.pipe( +// buffer( +// clickObs$.pipe( +// debounceTime(200), +// ), +// ), +// filter(arr => arr?.length >= 2), +// ) +// .subscribe(() => { +// const { currentOnHover } = this +// this.store.dispatch(viewerStateDblClickOnViewer({ +// payload: { ...currentOnHover } +// })) +// }), +// ) + +// // this.subscriptions.push( +// // this.selectedLandmarks$.pipe( +// // map(lms => lms.map(lm => this.landmarksNameMap.get(lm.name))), +// // debounceTime(16), +// // ).subscribe(indices => { +// // const filteredIndices = indices.filter(v => typeof v !== 'undefined' && v !== null) +// // if (this.nehubaViewer) { +// // this.nehubaViewer.spatialLandmarkSelectionChanged(filteredIndices) +// // } +// // }), +// // ) +// } + +// // datasetViewerRegistry : Set<string> = new Set() +// public showObliqueScreen$: Observable<boolean> +// public showObliqueSelection$: Observable<boolean> +// public showObliqueRotate$: Observable<boolean> + +// private currOnHoverObsSub: Subscription +// public ngOnChanges() { +// this.currOnHoverObsSub && this.currOnHoverObsSub.unsubscribe() +// if (this.currentOnHoverObs$) { +// this.currOnHoverObsSub = this.currentOnHoverObs$.subscribe(({ segments }) => this.onHoverSegments$.next(segments)) +// } +// } + +// public ngOnDestroy() { +// this.subscriptions.forEach(s => s.unsubscribe()) +// } + +// // public toggleMaximiseMinimise(index: number) { +// // this.store.dispatch(ngViewerActionToggleMax({ +// // payload: { index } +// // })) +// // } + +// public tunableMobileProperties: ITunableProp[] = [] + + +// public selectedProp = null + +// // public returnTruePos(quadrant: number, data: any) { +// // const pos = quadrant > 2 +// // ? [0, 0, 0] +// // : this.nanometersToOffsetPixelsFn && this.nanometersToOffsetPixelsFn[quadrant] +// // ? this.nanometersToOffsetPixelsFn[quadrant](data.geometry.position.map(n => n * 1e6)) +// // : [0, 0, 0] +// // return pos +// // } + +// // public getPositionX(quadrant: number, data: any) { +// // return this.returnTruePos(quadrant, data)[0] +// // } +// // public getPositionY(quadrant: number, data: any) { +// // return this.returnTruePos(quadrant, data)[1] +// // } +// // public getPositionZ(quadrant: number, data: any) { +// // return this.returnTruePos(quadrant, data)[2] +// // } + +// // public handleMouseEnterCustomLandmark(lm) { +// // this.store.dispatch( +// // viewerStateMouseOverCustomLandmark({ +// // payload: { userLandmark: lm } +// // }) +// // ) +// // } + +// // public handleMouseLeaveCustomLandmark(lm) { +// // this.store.dispatch( +// // viewerStateMouseOverCustomLandmark({ +// // payload: { userLandmark: null } +// // }) +// // ) +// // } + +// // handles mouse enter/leave landmarks in 2D +// public handleMouseEnterLandmark(spatialData: any) { +// spatialData.highlight = true +// this.store.dispatch({ +// type : MOUSE_OVER_LANDMARK, +// landmark : spatialData._label, +// }) +// } + +// public handleMouseLeaveLandmark(spatialData: any) { +// spatialData.highlight = false +// this.store.dispatch({ +// type : MOUSE_OVER_LANDMARK, +// landmark : null, +// }) +// } + +// // private handleParcellation(parcellation: any) { +// // /** +// // * parcellaiton may be undefined +// // */ +// // if ( !(parcellation && parcellation.regions)) { +// // return +// // } + +// // /** +// // * first, get all all the ngIds, including parent id from parcellation (if defined) +// // */ +// // const ngIds = getNgIds(parcellation.regions).concat( parcellation.ngId ? parcellation.ngId : []) + +// // this.multiNgIdsRegionsLabelIndexMap = getMultiNgIdsRegionsLabelIndexMap(parcellation) + +// // this.nehubaViewer.multiNgIdsLabelIndexMap = this.multiNgIdsRegionsLabelIndexMap +// // this.nehubaViewer.auxilaryMeshIndices = parcellation.auxillaryMeshIndices || [] + +// // /* TODO replace with proper KG id */ +// // /** +// // * need to set unique array of ngIds, or else workers will be overworked +// // */ +// // this.nehubaViewer.ngIds = Array.from(new Set(ngIds)) +// // this.selectedParcellation = parcellation +// // } + +// /* related spatial search */ +// public spatialSearchPagination: number = 0 + +// private destroynehuba() { +// /** +// * TODO if plugin subscribes to viewerHandle, and then new template is selected, changes willl not be be sent +// * could be considered as a bug. +// */ +// this.setViewerHandle && this.setViewerHandle(null) +// this.nehubaContainerDirective.clear() + +// this.nehubaViewer = null + +// this.cdr.detectChanges() +// } + +// private createNewNehuba(template: any) { + +// this.nehubaContainerDirective.createNehubaInstance(template) +// this.nehubaViewer = this.nehubaContainerDirective.nehubaViewerInstance + +// this.setupViewerHandleApi() +// } + +// private setupViewerHandleApi() { +// const viewerHandle = { +// setNavigationLoc : (coord, realSpace?) => this.nehubaViewer.setNavigationState({ +// position : coord, +// positionReal : typeof realSpace !== 'undefined' ? realSpace : true, +// }), +// /* TODO introduce animation */ +// moveToNavigationLoc : (coord, realSpace?) => { +// this.store.dispatch({ +// type: CHANGE_NAVIGATION, +// navigation: { +// position: coord, +// animation: {}, +// }, +// }) +// }, +// setNavigationOri : (quat) => this.nehubaViewer.setNavigationState({ +// orientation : quat, +// }), +// /* TODO introduce animation */ +// moveToNavigationOri : (quat) => this.nehubaViewer.setNavigationState({ +// orientation : quat, +// }), +// showSegment : (_labelIndex) => { +// /** +// * TODO reenable with updated select_regions api +// */ +// this.log.warn(`showSegment is temporarily disabled`) + +// // if(!this.selectedRegionIndexSet.has(labelIndex)) +// // this.store.dispatch({ +// // type : SELECT_REGIONS, +// // selectRegions : [labelIndex, ...this.selectedRegionIndexSet] +// // }) +// }, +// add3DLandmarks : landmarks => { +// // TODO check uniqueness of ID +// if (!landmarks.every(l => isDefined(l.id))) { +// throw new Error('every landmarks needs to be identified with the id field') +// } +// if (!landmarks.every(l => isDefined(l.position))) { +// throw new Error('every landmarks needs to have position defined') +// } +// if (!landmarks.every(l => l.position.constructor === Array) || !landmarks.every(l => l.position.every(v => !isNaN(v))) || !landmarks.every(l => l.position.length == 3)) { +// throw new Error('position needs to be a length 3 tuple of numbers ') +// } + +// this.store.dispatch(viewerStateAddUserLandmarks({ +// landmarks +// })) +// }, +// remove3DLandmarks : landmarkIds => { +// this.store.dispatch(viewreStateRemoveUserLandmarks({ +// payload: { landmarkIds } +// })) +// }, +// hideSegment : (_labelIndex) => { +// /** +// * TODO reenable with updated select_regions api +// */ +// this.log.warn(`hideSegment is temporarily disabled`) + +// // if(this.selectedRegionIndexSet.has(labelIndex)){ +// // this.store.dispatch({ +// // type :SELECT_REGIONS, +// // selectRegions : [...this.selectedRegionIndexSet].filter(num=>num!==labelIndex) +// // }) +// // } +// }, +// showAllSegments : () => { +// const selectRegionIds = [] +// this.multiNgIdsRegionsLabelIndexMap.forEach((map, ngId) => { +// Array.from(map.keys()).forEach(labelIndex => { +// selectRegionIds.push(serialiseParcellationRegion({ ngId, labelIndex })) +// }) +// }) +// this.store.dispatch(viewerStateSelectRegionWithIdDeprecated({ +// selectRegionIds +// })) +// }, +// hideAllSegments : () => { +// this.store.dispatch(viewerStateSelectRegionWithIdDeprecated({ +// selectRegionIds: [] +// })) +// }, +// segmentColourMap : new Map(), +// getLayersSegmentColourMap: () => { +// const newMainMap = new Map() +// for (const [key, colormap] of this.nehubaViewer.multiNgIdColorMap.entries()) { +// const newColormap = new Map() +// newMainMap.set(key, newColormap) + +// for (const [lableIndex, entry] of colormap.entries()) { +// newColormap.set(lableIndex, JSON.parse(JSON.stringify(entry))) +// } +// } +// return newMainMap +// }, +// applyColourMap : (_map) => { +// throw new Error(`apply color map has been deprecated. use applyLayersColourMap instead`) +// }, +// applyLayersColourMap: (map) => { +// this.nehubaViewer.setColorMap(map) +// }, +// loadLayer : (layerObj) => this.nehubaViewer.loadLayer(layerObj), +// removeLayer : (condition) => this.nehubaViewer.removeLayer(condition), +// setLayerVisibility : (condition, visible) => this.nehubaViewer.setLayerVisibility(condition, visible), +// mouseEvent : merge( +// fromEvent(this.elementRef.nativeElement, 'click').pipe( +// map((ev: MouseEvent) => ({eventName : 'click', event: ev})), +// ), +// fromEvent(this.elementRef.nativeElement, 'mousemove').pipe( +// map((ev: MouseEvent) => ({eventName : 'mousemove', event: ev})), +// ), +// /** +// * neuroglancer prevents propagation, so use capture instead +// */ +// Observable.create(observer => { +// this.elementRef.nativeElement.addEventListener('mousedown', event => observer.next({eventName: 'mousedown', event}), true) +// }) as Observable<{eventName: string, event: MouseEvent}>, +// fromEvent(this.elementRef.nativeElement, 'mouseup').pipe( +// map((ev: MouseEvent) => ({eventName : 'mouseup', event: ev})), +// ), +// ) , +// mouseOverNehuba : this.onHoverSegment$.pipe( +// tap(() => console.warn('mouseOverNehuba observable is becoming deprecated. use mouseOverNehubaLayers instead.')), +// ), +// mouseOverNehubaLayers: this.onHoverSegments$, +// mouseOverNehubaUI: this.currentOnHoverObs$.pipe( +// map(({ landmark, segments, userLandmark: customLandmark }) => ({ segments, landmark, customLandmark })), +// shareReplay(1), +// ), +// getNgHash : this.nehubaViewer.getNgHash, +// } + +// this.setViewerHandle && this.setViewerHandle(viewerHandle) +// } + +// // public setOctantRemoval(octantRemovalFlag: boolean) { +// // this.store.dispatch( +// // ngViewerActionSetPerspOctantRemoval({ +// // octantRemovalFlag +// // }) +// // ) +// // } + +// // public zoomNgView(panelIndex: number, factor: number) { +// // const ngviewer = this.nehubaViewer?.nehubaViewer?.ngviewer +// // if (!ngviewer) throw new Error(`ngviewer not defined!`) + +// // /** +// // * panelIndex < 3 === slice view +// // */ +// // if (panelIndex < 3) { +// // /** +// // * factor > 1 === zoom out +// // */ +// // ngviewer.navigationState.zoomBy(factor) +// // } else { +// // ngviewer.perspectiveNavigationState.zoomBy(factor) +// // } +// // } + +// // public clearPreviewingDataset(id: string){ +// // /** +// // * clear all preview +// // */ +// // this.store.dispatch( +// // id +// // ? uiActionHideDatasetWithId({ id }) +// // : uiActionHideAllDatasets() +// // ) +// // } +// } diff --git a/src/ui/nehubaContainer/nehubaContainer.template.html b/src/ui/nehubaContainer/nehubaContainer.template.html index b08904a09ce57f37dfaed22204fba0da53d86d38..921151abb70002e45eda44b4264a9fd4585f9b42 100644 --- a/src/ui/nehubaContainer/nehubaContainer.template.html +++ b/src/ui/nehubaContainer/nehubaContainer.template.html @@ -393,7 +393,6 @@ <!-- regional features--> <ng-template #regionalFeaturesTmpl> <data-browser - [parcellation]="selectedParcellation" [disableVirtualScroll]="true" [regions]="regions"> </data-browser> @@ -572,7 +571,6 @@ [parcellationId]="selectedParcellation['@id']" (setOpenState)="expansionPanel.expanded = $event" (connectivityNumberReceived)="connectivityNumber = $event" - (connectivityLoadUrl)="connectivityLoadUrl = $event" [accordionExpanded]="expansionPanel.expanded"> </connectivity-browser> </ng-container> @@ -592,13 +590,16 @@ <div class="w-0 h-0" iav-counter #connectedCounterDir="iavCounter"> - - <hbp-connectivity-matrix-row *ngIf="region && region.name" - [region]="region.name + (region.status? ' - ' + region.status : '')" - (connectivityDataReceived)="connectedCounterDir.value = $event.detail.length" - class="invisible d-block h-0 w-0" - [loadurl]="connectivityLoadUrl"> - </hbp-connectivity-matrix-row> + <!-- TODO figure out why conn browser does not work here --> + <!-- @fsdavid, can you take a look why this component is not emitting connectivityNumberReceived event? --> + <connectivity-browser *ngIf="region && region.name" + class="d-block h-0 w-0 overflow-hidden" + [region]="region" + [parcellationId]="selectedParcellation['@id']" + [accordionExpanded]="true" + (connectivityNumberReceived)="connectedCounterDir.value = $event"> + + </connectivity-browser> </div> </mat-accordion> </ng-template> @@ -667,7 +668,7 @@ <landmark-2d-flat-cmp *ngFor="let spatialData of (selectedPtLandmarks$ | async)" (mouseenter)="handleMouseEnterLandmark(spatialData)" (mouseleave)="handleMouseLeaveLandmark(spatialData)" - [highlight]="spatialData.highlight ? spatialData.highlight : false" + [color]="spatialData.highlight ? [255, 0, 0] : [255, 255, 255]" [positionX]="getPositionX(panelIndex, spatialData)" [positionY]="getPositionY(panelIndex, spatialData)" [positionZ]="getPositionZ(panelIndex, spatialData)"> diff --git a/src/ui/templateParcellationCitations/templateParcellationCitations.component.ts b/src/ui/templateParcellationCitations/templateParcellationCitations.component.ts deleted file mode 100644 index c97715d798fbf1345ee5832cd890d2ebcb0ef477..0000000000000000000000000000000000000000 --- a/src/ui/templateParcellationCitations/templateParcellationCitations.component.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Component } from "@angular/core"; -import { select, Store } from "@ngrx/store"; -import { Observable } from "rxjs"; -import { map, switchMap } from "rxjs/operators"; -import { safeFilter, IavRootStoreInterface } from "../../services/stateStore.service"; - -@Component({ - selector : 'template-parcellation-citation-container', - templateUrl : './templateParcellationCitations.template.html', - styleUrls : [ - './templateParcellationCitations.style.css', - ], -}) - -export class TemplateParcellationCitationsContainer { - public selectedTemplate$: Observable<any> - public selectedParcellation$: Observable<any> - - constructor(private store: Store<IavRootStoreInterface>) { - this.selectedTemplate$ = this.store.pipe( - select('viewerState'), - safeFilter('templateSelected'), - map(state => state.templateSelected), - ) - - this.selectedParcellation$ = this.selectedTemplate$.pipe( - switchMap(() => this.store.pipe( - select('viewerState'), - safeFilter('parcellationSelected'), - map(state => state.parcellationSelected), - )), - ) - } -} diff --git a/src/ui/templateParcellationCitations/templateParcellationCitations.template.html b/src/ui/templateParcellationCitations/templateParcellationCitations.template.html deleted file mode 100644 index 48b54d534f9efe9b53f055ccbae85f689afe67d9..0000000000000000000000000000000000000000 --- a/src/ui/templateParcellationCitations/templateParcellationCitations.template.html +++ /dev/null @@ -1,11 +0,0 @@ -<citations-component - *ngIf = "selectedTemplate$ | async" - [properties] = "(selectedTemplate$ | async).properties" - citationContainer> - -</citations-component> -<citations-component - *ngIf = "selectedParcellation$ | async" - [properties] = "(selectedParcellation$ | async).properties" - citationContainer> -</citations-component> \ No newline at end of file diff --git a/src/ui/topMenu/index.ts b/src/ui/topMenu/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/ui/topMenu/module.ts b/src/ui/topMenu/module.ts new file mode 100644 index 0000000000000000000000000000000000000000..a2c6f8a2e211f2952b5cc4f3975adcc27e4f7217 --- /dev/null +++ b/src/ui/topMenu/module.ts @@ -0,0 +1,39 @@ +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { DatabrowserModule } from "src/atlasComponents/databrowserModule"; +import { AuthModule } from "src/auth"; +import { ComponentsModule } from "src/components"; +import { FabSpeedDialModule } from "src/components/fabSpeedDial"; +import { PluginModule } from "src/plugin"; +import { UtilModule } from "src/util"; +import { ConfigModule } from "../config/module"; +import { CookieModule } from "../cookieAgreement/module"; +import { HelpModule } from "../help/module"; +import { KgTosModule } from "../kgtos/module"; +import { AngularMaterialModule } from "../sharedModules/angularMaterial.module"; +import { TopMenuCmp } from "./topMenuCmp/topMenu.components"; + +@NgModule({ + imports: [ + CommonModule, + UtilModule, + AngularMaterialModule, + DatabrowserModule, + FabSpeedDialModule, + ComponentsModule, + CookieModule, + KgTosModule, + ConfigModule, + HelpModule, + PluginModule, + AuthModule, + ], + declarations: [ + TopMenuCmp + ], + exports: [ + TopMenuCmp + ] +}) + +export class TopMenuModule{} diff --git a/src/ui/signinBanner/signinBanner.components.ts b/src/ui/topMenu/topMenuCmp/topMenu.components.ts similarity index 91% rename from src/ui/signinBanner/signinBanner.components.ts rename to src/ui/topMenu/topMenuCmp/topMenu.components.ts index ef5c4e442357fcd684657bb9fb503888d11d5e30..4a78e0d12fb6231da10f7d954c8e6bdaa4bc850d 100644 --- a/src/ui/signinBanner/signinBanner.components.ts +++ b/src/ui/topMenu/topMenuCmp/topMenu.components.ts @@ -15,20 +15,19 @@ import { MatBottomSheet } from "@angular/material/bottom-sheet"; import { CONST } from 'common/constants' @Component({ - selector: 'signin-banner', - templateUrl: './signinBanner.template.html', + selector: 'top-menu-cmp', + templateUrl: './topMenu.template.html', styleUrls: [ - './signinBanner.style.css', - '../btnShadow.style.css', + './topMenu.style.css', ], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class SigninBanner { +export class TopMenuCmp { public PINNED_DATASETS_BADGE_DESC = CONST.PINNED_DATASETS_BADGE_DESC - public matBtnStyle = '' + public matBtnStyle = 'mat-icon-button' public matBtnColor = 'primary' private _ismobile = false @@ -43,7 +42,7 @@ export class SigninBanner { } @Input() public darktheme: boolean - @Input() public parcellationIsSelected: boolean + @Input() public viewerLoaded: boolean public user$: Observable<any> public userBtnTooltip$: Observable<string> diff --git a/src/ui/signinBanner/signinBanner.style.css b/src/ui/topMenu/topMenuCmp/topMenu.style.css similarity index 100% rename from src/ui/signinBanner/signinBanner.style.css rename to src/ui/topMenu/topMenuCmp/topMenu.style.css diff --git a/src/ui/signinBanner/signinBanner.template.html b/src/ui/topMenu/topMenuCmp/topMenu.template.html similarity index 98% rename from src/ui/signinBanner/signinBanner.template.html rename to src/ui/topMenu/topMenuCmp/topMenu.template.html index c4b925620a6ec51a92bb4293bda290706bd8f6c5..f3ef18322bbb703d96c9fbdf2b52980d50c46401 100644 --- a/src/ui/signinBanner/signinBanner.template.html +++ b/src/ui/topMenu/topMenuCmp/topMenu.template.html @@ -138,7 +138,7 @@ <mat-menu #pluginDropdownMenu [aria-label]="'Tools and plugins menu'"> <button mat-menu-item - [disabled]="!parcellationIsSelected" + [disabled]="!viewerLoaded" screenshot-switch [matTooltip]="screenshotTooltipText"> <mat-icon fontSet="fas" fontIcon="fa-camera"> @@ -179,8 +179,8 @@ <mat-dialog-content> <mat-tab-group> <mat-tab label="About"> - <help-component> - </help-component> + <iav-about> + </iav-about> </mat-tab> <mat-tab label="Privacy Policy"> <!-- TODO make tab container scrollable --> diff --git a/src/ui/ui.module.ts b/src/ui/ui.module.ts index e58bd77ba1c134fad099b3c439e73b425f7cc1c4..ac11d02f3cd101e9033e64ade8d0d2616d7cd556 100644 --- a/src/ui/ui.module.ts +++ b/src/ui/ui.module.ts @@ -1,11 +1,9 @@ -import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from "@angular/core"; +import { NgModule } from "@angular/core"; import { ComponentsModule } from "src/components/components.module"; import { FormsModule, ReactiveFormsModule } from "@angular/forms"; import { LayoutModule } from "src/layouts/layout.module"; -import { NehubaContainer } from "./nehubaContainer/nehubaContainer.component"; -import { IMPORT_NEHUBA_INJECT_TOKEN } from "./nehubaContainer/nehubaViewer/nehubaViewer.component"; -import { GetTemplateImageSrcPipe, ImgSrcSetPipe, SplashScreen } from "./nehubaContainer/splashScreen/splashScreen.component"; +// import { NehubaContainer } from "./nehubaContainer/nehubaContainer.component"; import { FilterRegionDataEntries } from "src/util/pipes/filterRegionDataEntries.pipe"; import { GroupDatasetByRegion } from "src/util/pipes/groupDataEntriesByRegion.pipe"; @@ -13,9 +11,6 @@ import { GroupDatasetByRegion } from "src/util/pipes/groupDataEntriesByRegion.pi import { GetLayerNameFromDatasets } from "../util/pipes/getLayerNamePipe.pipe"; import { SortDataEntriesToRegion } from "../util/pipes/sortDataEntriesIntoRegion.pipe"; import { CitationsContainer } from "./citation/citations.component"; -import { KgEntryViewer } from "./kgEntryViewer/kgentry.component"; -import { SubjectViewer } from "./kgEntryViewer/subjectViewer/subjectViewer.component"; -import { PluginBannerUI } from "./pluginBanner/pluginBanner.component"; import { ScrollingModule } from "@angular/cdk/scrolling" import { HttpClientModule } from "@angular/common/http"; @@ -26,66 +21,36 @@ import { GetFileExtension } from "src/util/pipes/getFileExt.pipe"; import { UtilModule } from "src/util"; import { DownloadDirective } from "../util/directives/download.directive"; import { SpatialLandmarksToDataBrowserItemPipe } from "../util/pipes/spatialLandmarksToDatabrowserItem.pipe"; -import { ConfigComponent } from './config/config.component' -import { CurrentLayout } from "./config/currentLayout/currentLayout.component"; -import { FourPanelLayout } from "./config/layouts/fourPanel/fourPanel.component"; -import { HorizontalOneThree } from "./config/layouts/h13/h13.component"; -import { SinglePanel } from "./config/layouts/single/single.component"; -import { VerticalOneThree } from "./config/layouts/v13/v13.component"; -import { CookieAgreement } from "./cookieAgreement/cookieAgreement.component"; -import { DatabrowserModule } from "./databrowserModule/databrowser.module"; -import { HelpComponent } from "./help/help.component"; -import { KGToS } from "./kgtos/kgtos.component"; + + +import { DatabrowserModule } from "../atlasComponents/databrowserModule/databrowser.module"; + import { LogoContainer } from "./logoContainer/logoContainer.component"; import { MobileOverlay } from "./nehubaContainer/mobileOverlay/mobileOverlay.component"; import { MobileControlNubStylePipe } from "./nehubaContainer/pipes/mobileControlNubStyle.pipe"; -import { StatusCardComponent } from "./nehubaContainer/statusCard/statusCard.component"; -import { SigninBanner } from "./signinBanner/signinBanner.components"; - -import { TemplateParcellationCitationsContainer } from "./templateParcellationCitations/templateParcellationCitations.component"; -import { FilterNameBySearch } from "./viewerStateController/regionHierachy/filterNameBySearch.pipe"; - -import { ViewerStateMini } from 'src/ui/viewerStateController/viewerStateCMini/viewerStateMini.component' +// import { StatusCardComponent } from "./nehubaContainer/statusCard/statusCard.component"; import { HumanReadableFileSizePipe } from "src/util/pipes/humanReadableFileSize.pipe"; import { KgSearchBtnColorPipe } from "src/util/pipes/kgSearchBtnColor.pipe"; import { PluginBtnFabColorPipe } from "src/util/pipes/pluginBtnFabColor.pipe"; import { TemplateParcellationHasMoreInfo } from "src/util/pipes/templateParcellationHasMoreInfo.pipe"; -import { MaximmisePanelButton } from "./nehubaContainer/maximisePanelButton/maximisePanelButton.component"; + import { ReorderPanelIndexPipe } from "./nehubaContainer/reorderPanelIndex.pipe"; -import { TouchSideClass } from "./nehubaContainer/touchSideClass.directive"; -import { BinSavedRegionsSelectionPipe, SavedRegionsSelectionBtnDisabledPipe } from "./viewerStateController/viewerState.pipes"; import { FixedMouseContextualContainerDirective } from "src/util/directives/FixedMouseContextualContainerDirective.directive"; -import { RegionHierarchy } from './viewerStateController/regionHierachy/regionHierarchy.component' -import { RegionTextSearchAutocomplete } from "./viewerStateController/regionSearch/regionSearch.component"; -import { ConnectivityBrowserComponent } from "src/ui/connectivityBrowser/connectivityBrowser.component"; -import { RegionMenuComponent } from 'src/ui/parcellationRegion/regionMenu/regionMenu.component' -import { RegionListSimpleViewComponent } from "./parcellationRegion/regionListSimpleView/regionListSimpleView.component"; -import { SimpleRegionComponent } from "./parcellationRegion/regionSimple/regionSimple.component"; -import { LandmarkUIComponent } from "./landmarkUI/landmarkUI.component"; -import { NehubaModule } from "./nehubaContainer/nehuba.module"; import { ShareModule } from "src/share"; -import { StateModule } from "src/state"; import { AuthModule } from "src/auth"; -import { FabSpeedDialModule } from "src/components/fabSpeedDial"; import { ActionDialog } from "./actionDialog/actionDialog.component"; -import { NehubaViewerTouchDirective } from "./nehubaContainer/nehubaViewerInterface/nehubaViewerTouch.directive"; -import { importNehubaFactory } from "./nehubaContainer/util"; import { APPEND_SCRIPT_TOKEN, appendScriptFactory } from "src/util/constants"; import { DOCUMENT } from "@angular/common"; -import { AtlasDropdownSelector } from './atlasDropdown/atlasDropdown.component' -import { AtlasLayerSelector } from "src/ui/atlasLayerSelector/atlasLayerSelector.component"; import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; -import { RegionDirective } from "./parcellationRegion/region.directive"; -import { RenderViewOriginDatasetLabelPipe } from "./parcellationRegion/region.base"; -import { RegionAccordionTooltipTextPipe } from './util' -import { HelpOnePager } from "./helpOnePager/helpOnePager.component"; -import { RegionalFeaturesModule } from "./regionalFeatures"; +import { RegionalFeaturesModule } from "../atlasComponents/regionalFeatures"; import { Landmark2DModule } from "./nehubaContainer/2dLandmarks/module"; -import { PluginCspCtrlCmp } from "./config/pluginCsp/pluginCsp.component"; import { ScreenshotModule, HANDLE_SCREENSHOT_PROMISE, TypeHandleScrnShotPromise } from "./screenshot"; +import { ParcellationRegionModule } from "src/atlasComponents/parcellationRegion"; +import { AtlasCmpParcellationModule } from "src/atlasComponents/parcellation"; +import { AtlasCmptConnModule } from "src/atlasComponents/connectivity"; @NgModule({ imports : [ @@ -99,52 +64,23 @@ import { ScreenshotModule, HANDLE_SCREENSHOT_PROMISE, TypeHandleScrnShotPromise UtilModule, ScrollingModule, AngularMaterialModule, - NehubaModule, ShareModule, - StateModule, AuthModule, - FabSpeedDialModule, RegionalFeaturesModule, Landmark2DModule, ScreenshotModule, + ParcellationRegionModule, + AtlasCmpParcellationModule, + AtlasCmptConnModule, ], declarations : [ - NehubaContainer, + // NehubaContainer, - SplashScreen, - PluginBannerUI, CitationsContainer, - KgEntryViewer, - SubjectViewer, LogoContainer, - TemplateParcellationCitationsContainer, MobileOverlay, - HelpComponent, - ConfigComponent, - SigninBanner, - AtlasDropdownSelector, - AtlasLayerSelector, - AtlasDropdownSelector, - PluginCspCtrlCmp, - StatusCardComponent, - CookieAgreement, - KGToS, - FourPanelLayout, - HorizontalOneThree, - VerticalOneThree, - SinglePanel, - CurrentLayout, - ViewerStateMini, - RegionHierarchy, - MaximmisePanelButton, - RegionTextSearchAutocomplete, - RegionMenuComponent, - ConnectivityBrowserComponent, - SimpleRegionComponent, - RegionListSimpleViewComponent, - LandmarkUIComponent, - HelpOnePager, + // StatusCardComponent, ActionDialog, @@ -155,36 +91,21 @@ import { ScreenshotModule, HANDLE_SCREENSHOT_PROMISE, TypeHandleScrnShotPromise GetLayerNameFromDatasets, SortDataEntriesToRegion, SpatialLandmarksToDataBrowserItemPipe, - FilterNameBySearch, AppendtooltipTextPipe, MobileControlNubStylePipe, - GetTemplateImageSrcPipe, - ImgSrcSetPipe, PluginBtnFabColorPipe, KgSearchBtnColorPipe, GetFileExtension, - BinSavedRegionsSelectionPipe, - SavedRegionsSelectionBtnDisabledPipe, TemplateParcellationHasMoreInfo, HumanReadableFileSizePipe, ReorderPanelIndexPipe, - RenderViewOriginDatasetLabelPipe, - RegionAccordionTooltipTextPipe, /* directive */ DownloadDirective, - TouchSideClass, FixedMouseContextualContainerDirective, - NehubaViewerTouchDirective, - RegionDirective ], providers: [ - { - provide: IMPORT_NEHUBA_INJECT_TOKEN, - useFactory: importNehubaFactory, - deps: [ APPEND_SCRIPT_TOKEN ] - }, { provide: APPEND_SCRIPT_TOKEN, useFactory: appendScriptFactory, @@ -249,39 +170,18 @@ import { ScreenshotModule, HANDLE_SCREENSHOT_PROMISE, TypeHandleScrnShotPromise /* dynamically created components needs to be declared here */ - PluginBannerUI, ActionDialog, ], exports : [ - SubjectViewer, - KgEntryViewer, CitationsContainer, - PluginBannerUI, - NehubaContainer, + // NehubaContainer, LogoContainer, - TemplateParcellationCitationsContainer, MobileOverlay, - HelpComponent, - ConfigComponent, - SigninBanner, - AtlasLayerSelector, - RegionDirective, - CookieAgreement, - KGToS, - StatusCardComponent, - ViewerStateMini, - RegionMenuComponent, + // StatusCardComponent, FixedMouseContextualContainerDirective, - LandmarkUIComponent, - NehubaViewerTouchDirective, - AtlasDropdownSelector, - RenderViewOriginDatasetLabelPipe, - ], - schemas: [ - CUSTOM_ELEMENTS_SCHEMA, - ], + ] }) export class UIModule { diff --git a/src/ui/viewerStateController/viewerState.base.ts b/src/ui/viewerStateController/viewerState.base.ts deleted file mode 100644 index 70a67c226bec656ba46429e5d9cd203605a5fcd9..0000000000000000000000000000000000000000 --- a/src/ui/viewerStateController/viewerState.base.ts +++ /dev/null @@ -1,201 +0,0 @@ -import { OnInit, TemplateRef, ViewChild } from "@angular/core"; -import { select, Store } from "@ngrx/store"; -import { Observable, Subscription } from "rxjs"; -import { distinctUntilChanged, filter, shareReplay } from "rxjs/operators"; -import { DialogService } from "src/services/dialogService.service"; -import { RegionSelection } from "src/services/state/userConfigState.store"; -import { IavRootStoreInterface, SELECT_REGIONS, USER_CONFIG_ACTION_TYPES } from "src/services/stateStore.service"; -import { MatSelectChange } from "@angular/material/select"; -import { MatBottomSheet, MatBottomSheetRef } from "@angular/material/bottom-sheet"; -import { viewerStateSelectTemplateWithName } from "src/services/state/viewerState/actions"; - -const ACTION_TYPES = { - SELECT_PARCELLATION_WITH_NAME: 'SELECT_PARCELLATION_WITH_NAME', - -} - -export class ViewerStateBase implements OnInit { - - @ViewChild('savedRegionBottomSheetTemplate', {read: TemplateRef}) public savedRegionBottomSheetTemplate: TemplateRef<any> - - public focused: boolean = false - - private subscriptions: Subscription[] = [] - - public standaloneVolumes$: Observable<any[]> - - public availableTemplates$: Observable<any[]> - public availableParcellations$: Observable<any[]> - - public templateSelected$: Observable<any> - public parcellationSelected$: Observable<any> - public regionsSelected$: Observable<any> - - public savedRegionsSelections$: Observable<any[]> - - private savedRegionBottomSheetRef: MatBottomSheetRef - - constructor( - private store$: Store<IavRootStoreInterface>, - private dialogService: DialogService, - private bottomSheet: MatBottomSheet, - ) { - const viewerState$ = this.store$.pipe( - select('viewerState'), - shareReplay(1), - ) - - this.savedRegionsSelections$ = this.store$.pipe( - select('userConfigState'), - select('savedRegionsSelection'), - shareReplay(1), - ) - - this.templateSelected$ = viewerState$.pipe( - select('templateSelected'), - distinctUntilChanged(), - ) - - this.parcellationSelected$ = viewerState$.pipe( - select('parcellationSelected'), - distinctUntilChanged(), - shareReplay(1), - ) - - this.regionsSelected$ = viewerState$.pipe( - select('regionsSelected'), - distinctUntilChanged(), - shareReplay(1), - ) - - this.standaloneVolumes$ = viewerState$.pipe( - select('standaloneVolumes'), - distinctUntilChanged(), - shareReplay(1) - ) - - this.availableTemplates$ = viewerState$.pipe( - select('fetchedTemplates'), - distinctUntilChanged() - ) - - this.availableParcellations$ = this.templateSelected$.pipe( - select('parcellations'), - ) - - } - - public ngOnInit() { - this.subscriptions.push( - this.savedRegionsSelections$.pipe( - filter(srs => srs.length === 0), - ).subscribe(() => this.savedRegionBottomSheetRef && this.savedRegionBottomSheetRef.dismiss()), - ) - } - - public handleTemplateChange(event: MatSelectChange) { - this.store$.dispatch( - viewerStateSelectTemplateWithName({ - payload: { name: event.value } - }) - ) - } - - public handleParcellationChange(event: MatSelectChange) { - if (!event.value) { return } - this.store$.dispatch({ - type: ACTION_TYPES.SELECT_PARCELLATION_WITH_NAME, - payload: { - name: event.value, - }, - }) - } - - public loadSavedRegion(event: MouseEvent, savedRegionsSelection: RegionSelection) { - this.store$.dispatch({ - type: USER_CONFIG_ACTION_TYPES.LOAD_REGIONS_SELECTION, - payload: { - savedRegionsSelection, - }, - }) - } - - public editSavedRegion(event: MouseEvent, savedRegionsSelection: RegionSelection) { - event.preventDefault() - event.stopPropagation() - this.dialogService.getUserInput({ - defaultValue: savedRegionsSelection.name, - placeholder: `Enter new name`, - title: 'Edit name', - iconClass: null, - }).then(name => { - if (!name) { throw new Error('user cancelled') } - this.store$.dispatch({ - type: USER_CONFIG_ACTION_TYPES.UPDATE_REGIONS_SELECTION, - payload: { - ...savedRegionsSelection, - name, - }, - }) - }).catch(e => { - // TODO catch user cancel - }) - } - public removeSavedRegion(event: MouseEvent, savedRegionsSelection: RegionSelection) { - event.preventDefault() - event.stopPropagation() - this.store$.dispatch({ - type: USER_CONFIG_ACTION_TYPES.DELETE_REGIONS_SELECTION, - payload: { - ...savedRegionsSelection, - }, - }) - } - - public trackByFn = ({ name }) => name - - public loadSelection(_event: MouseEvent) { - this.focused = true - - this.savedRegionBottomSheetRef = this.bottomSheet.open(this.savedRegionBottomSheetTemplate) - this.savedRegionBottomSheetRef.afterDismissed() - .subscribe(null, null, () => { - this.focused = false - this.savedRegionBottomSheetRef = null - }) - } - - public saveSelection(_event: MouseEvent) { - this.focused = true - this.dialogService.getUserInput({ - defaultValue: `Saved Region`, - placeholder: `Name the selection`, - title: 'Save region selection', - iconClass: 'far fa-bookmark', - }) - .then(name => { - if (!name) { throw new Error('User cancelled') } - this.store$.dispatch({ - type: USER_CONFIG_ACTION_TYPES.SAVE_REGIONS_SELECTION, - payload: { name }, - }) - }) - .catch(e => { - /** - * TODO USER CANCELLED, HANDLE - */ - }) - .finally(() => this.focused = false) - } - - public deselectAllRegions(_event: MouseEvent) { - this.store$.dispatch({ - type: SELECT_REGIONS, - selectRegions: [], - }) - } - -} - -export const VIEWERSTATE_CONTROLLER_ACTION_TYPES = ACTION_TYPES - diff --git a/src/ui/viewerStateController/viewerState.pipes.ts b/src/ui/viewerStateController/viewerState.pipes.ts deleted file mode 100644 index 400ed875be821b17776f5623d11a3e7ebbed2159..0000000000000000000000000000000000000000 --- a/src/ui/viewerStateController/viewerState.pipes.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { Pipe, PipeTransform } from "@angular/core"; -import { RegionSelection } from "src/services/state/userConfigState.store"; - -@Pipe({ - name: 'binSavedRegionsSelectionPipe', -}) - -export class BinSavedRegionsSelectionPipe implements PipeTransform { - public transform(regionSelections: RegionSelection[]): Array<{parcellationSelected: any, templateSelected: any, regionSelections: RegionSelection[]}> { - const returnMap = new Map() - for (const regionSelection of regionSelections) { - const key = `${regionSelection.templateSelected.name}\n${regionSelection.parcellationSelected.name}` - const existing = returnMap.get(key) - if (existing) { existing.push(regionSelection) } else { returnMap.set(key, [regionSelection]) } - } - return Array.from(returnMap) - .map(([_unused, regionSelections]) => { - const {parcellationSelected = null, templateSelected = null} = regionSelections[0] || {} - return { - regionSelections, - parcellationSelected, - templateSelected, - } - }) - } -} - -@Pipe({ - name: 'savedRegionsSelectionBtnDisabledPipe', -}) - -export class SavedRegionsSelectionBtnDisabledPipe implements PipeTransform { - public transform(regionSelection: RegionSelection, templateSelected: any, parcellationSelected: any): boolean { - return regionSelection.parcellationSelected.name !== parcellationSelected.name - || regionSelection.templateSelected.name !== templateSelected.name - } -} diff --git a/src/ui/viewerStateController/viewerStateCMini/viewerStateMini.component.ts b/src/ui/viewerStateController/viewerStateCMini/viewerStateMini.component.ts deleted file mode 100644 index 66ff71b0c37b93d3ecba2c26980e90b1fa4f22f5..0000000000000000000000000000000000000000 --- a/src/ui/viewerStateController/viewerStateCMini/viewerStateMini.component.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Component } from "@angular/core"; -import { Store } from "@ngrx/store"; -import { DialogService } from "src/services/dialogService.service"; - -import { IavRootStoreInterface } from "src/services/stateStore.service"; -import { ViewerStateBase } from '../viewerState.base' -import {MatBottomSheet} from "@angular/material/bottom-sheet"; - -@Component({ - selector: 'viewer-state-mini', - templateUrl: './viewerStateMini.template.html', - styleUrls: [ - './viewerStateMini.style.css', - ], -}) - -export class ViewerStateMini extends ViewerStateBase { - - constructor( - store$: Store<IavRootStoreInterface>, - dialogService: DialogService, - bottomSheet: MatBottomSheet, - ) { - super(store$, dialogService, bottomSheet) - } -} diff --git a/src/ui/viewerStateController/viewerStateCMini/viewerStateMini.template.html b/src/ui/viewerStateController/viewerStateCMini/viewerStateMini.template.html deleted file mode 100644 index 2604321cb2f7ec6f857ab83acaff064338a19284..0000000000000000000000000000000000000000 --- a/src/ui/viewerStateController/viewerStateCMini/viewerStateMini.template.html +++ /dev/null @@ -1,22 +0,0 @@ -<!-- selected template and parcellation --> -<div *ngIf="templateSelected$ | async as templateSelected"> - {{ templateSelected.name }} -</div> -<div *ngIf="parcellationSelected$ | async as parcellationSelected"> - {{ (parcellationSelected.displayName || parcellationSelected.name) | tmpParcNamePipe }} -</div> - -<!-- selected parcellation regions --> -<ng-container *ngIf="regionsSelected$ | async as regionsSelected"> - <ng-container *ngIf="regionsSelected.length > 0"> - <div class="mt-2"> - {{ regionsSelected.length }} region{{ regionsSelected.length > 1 ? 's' : '' }} selected - </div> - </ng-container> -</ng-container> - -<ng-container *ngIf="standaloneVolumes$ | async as standaloneVolumes"> - <div *ngFor="let vol of standaloneVolumes"> - {{ vol }} - </div> -</ng-container> \ No newline at end of file diff --git a/src/util/fn.spec.ts b/src/util/fn.spec.ts index 98f60a54cb0b71f9e5de4c38db0f8b6a8d95f6e2..f17d211b79c3b363eb7da40c9e517817a66db4a5 100644 --- a/src/util/fn.spec.ts +++ b/src/util/fn.spec.ts @@ -1,7 +1,28 @@ import {} from 'jasmine' -import { isSame } from './fn' +import { isSame, getGetRegionFromLabelIndexId } from './fn' describe(`util/fn.ts`, () => { + + describe('getGetRegionFromLabelIndexId', () => { + const colinsJson = require('!json-loader!../res/ext/colin.json') + + const COLIN_JULICHBRAIN_LAYER_NAME = `COLIN_V25_LEFT_NG_SPLIT_HEMISPHERE` + const COLIN_V25_ID = 'minds/core/parcellationatlas/v1.0.0/94c1125b-b87e-45e4-901c-00daee7f2579-25' + + it('translateds hoc1 from labelIndex to region', () => { + + const getRegionFromlabelIndexId = getGetRegionFromLabelIndexId({ + parcellation: { + ...colinsJson.parcellations.find(p => p['@id'] === COLIN_V25_ID), + updated: true, + }, + }) + const fetchedRegion = getRegionFromlabelIndexId({ labelIndexId: `${COLIN_JULICHBRAIN_LAYER_NAME}#116` }) + expect(fetchedRegion).toBeTruthy() + expect(fetchedRegion.fullId.kg.kgId).toEqual('c9753e82-80ca-4074-a704-9dd2c4c0d58b') + + }) + }) describe(`#isSame`, () => { it('should return true with null, null', () => { expect(isSame(null, null)).toBe(true) diff --git a/src/util/fn.ts b/src/util/fn.ts index 827aec4f0e872df3433aacc123066f6707ce2f72..a62dd546b99b936a4f2a74d6b947b99a1f6b2659 100644 --- a/src/util/fn.ts +++ b/src/util/fn.ts @@ -55,3 +55,10 @@ export function recursiveFindRegionWithLabelIndexId({ regions, labelIndexId, inh export function getUuid(){ return crypto.getRandomValues(new Uint32Array(1))[0].toString(16) } + +export const getGetRegionFromLabelIndexId = ({ parcellation }) => { + const { ngId: defaultNgId, regions } = parcellation + // if (!updated) throw new Error(`parcellation not yet updated`) + return ({ labelIndexId }) => + recursiveFindRegionWithLabelIndexId({ regions, labelIndexId, inheritedNgId: defaultNgId }) +} diff --git a/src/viewerModule/index.ts b/src/viewerModule/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..6e5c74fbe55be040c17b54d98c7b6f29cd66a513 --- /dev/null +++ b/src/viewerModule/index.ts @@ -0,0 +1 @@ +export { ViewerModule } from "./module" \ No newline at end of file diff --git a/src/viewerModule/module.ts b/src/viewerModule/module.ts new file mode 100644 index 0000000000000000000000000000000000000000..b9359cbf6fce7c4d6d204b3bd71e08d0adc27d36 --- /dev/null +++ b/src/viewerModule/module.ts @@ -0,0 +1,349 @@ +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { AtlasCmptConnModule } from "src/atlasComponents/connectivity"; +import { DatabrowserModule } from "src/atlasComponents/databrowserModule"; +import { AtlasCmpParcellationModule } from "src/atlasComponents/parcellation"; +import { ParcellationRegionModule } from "src/atlasComponents/parcellationRegion"; +import { SplashUiModule } from "src/atlasComponents/splashScreen"; +import { AtlasCmpUiSelectorsModule } from "src/atlasComponents/uiSelectors"; +import { ComponentsModule } from "src/components"; +import { LayoutModule } from "src/layouts/layout.module"; +import { AngularMaterialModule } from "src/ui/sharedModules/angularMaterial.module"; +import { TopMenuModule } from "src/ui/topMenu/module"; +import { UtilModule } from "src/util"; +import { NehubaModule } from "./nehuba"; +import { RegionAccordionTooltipTextPipe } from "./util/regionAccordionTooltipText.pipe"; +import { ViewerCmp } from "./viewerCmp/viewerCmp.component"; + +@NgModule({ + imports: [ + CommonModule, + NehubaModule, + LayoutModule, + DatabrowserModule, + AtlasCmpUiSelectorsModule, + AngularMaterialModule, + SplashUiModule, + TopMenuModule, + ParcellationRegionModule, + UtilModule, + AtlasCmpParcellationModule, + AtlasCmptConnModule, + ComponentsModule, + ], + declarations: [ + ViewerCmp, + + RegionAccordionTooltipTextPipe, + ], + exports: [ + ViewerCmp, + ], +}) + +export class ViewerModule{} + +/** + <ui-nehuba-container + class="z-index-10" + #uiNehubaContainer="uiNehubaContainer" + iav-mouse-hover + #iavMouseHoverEl="iavMouseHover" + [currentOnHoverObs$]="iavMouseHoverEl.currentOnHoverObs$" + [currentOnHover]="iavMouseHoverEl.currentOnHoverObs$ | async" + iav-captureClickListenerDirective + [iav-captureClickListenerDirective-captureDocument]="true" + (iav-captureClickListenerDirective-onUnmovedClick)="mouseClickDocument($event)" + (drag-drop)="localFileService.handleFileDrop($event)"> + + <!-- top right content transclusion --> + <div ui-nehuba-container-overlay-top-right class="d-inline-flex flex-row justify-content-end align-items-start z-index-6 position-absolute pe-none w-100 h-100"> + + <top-menu-cmp + class="mt-3 mr-2" + [parcellationIsSelected]="!!selectedParcellation" + [ismobile]="(media.mediaBreakPoint$ | async) > 3"> + </top-menu-cmp> + + <!-- atlas selector --> + <div *ngIf="uiNehubaContainer.viewerLoaded" + class="iv-custom-comp bg card m-2 mat-elevation-z2"> + <atlas-dropdown-selector class="pe-all mt-2"> + </atlas-dropdown-selector> + </div> + + </div> + + <!-- bottom left content transclusion --> + <div ui-nehuba-container-overlay-bottom-left class="d-inline-flex pe-none w-100 align-items-end m-2 mb-4"> + + <!-- only load atlas layer selector and chips if viewer is loaded --> + <ng-template [ngIf]="uiNehubaContainer.viewerLoaded && !(isStandaloneVolumes$ | async)"> + + <!-- Viewer Selector Container--> + <atlas-layer-selector + #alSelector="atlasLayerSelector" + class="pe-all" + (iav-outsideClick)="alSelector.selectorExpanded = false"> + </atlas-layer-selector> + <mat-chip-list class="mb-2"> + <!-- additional layer --> + + <ng-container> + <ng-container *ngTemplateOutlet="currParcellationTmpl; context: { addParc: (selectedAdditionalLayers$ | async), parc: selectedParcellation }"> + </ng-container> + </ng-container> + + <!-- any selected region(s) --> + <ng-container> + <ng-container *ngTemplateOutlet="selectedRegionTmpl"> + </ng-container> + </ng-container> + + <!-- controls for iav volumes --> + <div class="hidden" iav-shown-previews #previews="iavShownPreviews"></div> + <ng-container *ngTemplateOutlet="selectedDatasetPreview; context: { layers: previews.iavAdditionalLayers$ | async | filterPreviewByType : [previews.FILETYPES.VOLUMES] }"> + </ng-container> + + </mat-chip-list> + + <!-- current layer tmpl --> + + <ng-template #currParcellationTmpl let-parc="parc" let-addParc="addParc"> + + <div [matMenuTriggerFor]="layerVersionMenu" + [matMenuTriggerData]="{ layerVersionMenuTrigger: layerVersionMenuTrigger }" + #layerVersionMenuTrigger="matMenuTrigger"> + + <ng-template [ngIf]="addParc.length > 0" [ngIfElse]="defaultParcTmpl"> + <ng-container *ngFor="let p of addParc"> + <ng-container *ngTemplateOutlet="chipTmpl; context: { + parcel: p, + selected: true, + dismissable: true, + onclick: layerVersionMenuTrigger.toggleMenu.bind(layerVersionMenuTrigger) + }"> + </ng-container> + </ng-container> + </ng-template> + <ng-template #defaultParcTmpl> + <ng-container *ngTemplateOutlet="chipTmpl; context: { + parcel: parc, + selected: false, + dismissable: false, + onclick: layerVersionMenuTrigger.toggleMenu.bind(layerVersionMenuTrigger) + }"> + </ng-container> + </ng-template> + </div> + </ng-template> + + <!-- render parc templ --> + <ng-template #chipTmpl + let-parcel="parcel" + let-selected="selected" + let-dismissable="dismissable" + let-chipClass="class" + let-onclick="onclick"> + <mat-chip class="pe-all position-relative z-index-2 d-inline-flex justify-content-between" + [ngClass]="chipClass" + (click)="onclick && onclick()" + [selected]="selected"> + + <span> + {{ parcel?.groupName ? (parcel?.groupName + ' - ') : '' }}{{ parcel && (parcel.displayName || parcel.name) }} + </span> + + <!-- info icon --> + <ng-template [ngIf]="parcel?.originDatasets?.length > 0" [ngIfElse]="infoIconBasic"> + + <mat-icon + *ngFor="let ds of parcel.originDatasets" + fontSet="fas" + fontIcon="fa-info-circle" + iav-stop="click" + iav-dataset-show-dataset-dialog + [iav-dataset-show-dataset-dialog-kgid]="ds['kgId']" + [iav-dataset-show-dataset-dialog-kgschema]="ds['kgSchema']" + [iav-dataset-show-dataset-dialog-name]="parcel?.properties?.name" + [iav-dataset-show-dataset-dialog-description]="parcel?.properties?.description"> + </mat-icon> + + </ng-template> + + <ng-template #infoIconBasic> + <mat-icon *ngIf="parcel?.properties?.name && parcel?.properties?.description" + fontSet="fas" + fontIcon="fa-info-circle" + iav-stop="click" + iav-dataset-show-dataset-dialog + [iav-dataset-show-dataset-dialog-name]="parcel.properties.name" + [iav-dataset-show-dataset-dialog-description]="parcel.properties.description"> + + </mat-icon> + </ng-template> + + <!-- dismiss icon --> + <mat-icon + *ngIf="dismissable" + (click)="clearAdditionalLayer(parcel); $event.stopPropagation()" + fontSet="fas" + fontIcon="fa-times"> + </mat-icon> + </mat-chip> + </ng-template> + + <!-- layer version selector --> + <mat-menu #layerVersionMenu + class="bg-none box-shadow-none" + [hasBackdrop]="false"> + <ng-template matMenuContent let-layerVersionMenuTrigger="layerVersionMenuTrigger"> + <div (iav-outsideClick)="layerVersionMenuTrigger.closeMenu()"> + <ng-container *ngFor="let parcVer of selectedLayerVersions$ | async"> + <ng-container *ngTemplateOutlet="chipTmpl; context: { + parcel: parcVer, + selected: selectedParcellation && selectedParcellation['@id'] === parcVer['@id'], + dismissable: false, + class: 'w-100', + onclick: bindFns([ + [ selectParcellation.bind(this), parcVer ], + [ layerVersionMenuTrigger.closeMenu.bind(layerVersionMenuTrigger) ] + ]) + }"> + </ng-container> + <div class="mt-1"></div> + </ng-container> + </div> + </ng-template> + </mat-menu> + + <ng-template #selectedRegionTmpl> + + <!-- regions chip --> + <ng-template [ngIf]="selectedRegions$ | async" let-selectedRegions="ngIf"> + <!-- if regions.length > 1 --> + <!-- use group chip --> + <ng-template [ngIf]="selectedRegions.length > 1" [ngIfElse]="singleRegionTmpl"> + <mat-chip + color="primary" + selected + (click)="uiNehubaContainer.matDrawerMinor.open() && uiNehubaContainer.navSideDrawerMainSwitch.open()" + class="pe-all position-relative z-index-1 ml-8-n"> + <span class="iv-custom-comp text text-truncate d-inline pl-4"> + {{ CONST.MULTI_REGION_SELECTION }} + </span> + <mat-icon + (click)="clearSelectedRegions()" + fontSet="fas" + iav-stop="click" + fontIcon="fa-times"> + </mat-icon> + </mat-chip> + </ng-template> + + <!-- if reginos.lengt === 1 --> + <!-- use single region chip --> + <ng-template #singleRegionTmpl> + <ng-container *ngFor="let r of selectedRegions"> + + <!-- region chip for discrete map --> + <mat-chip + iav-region + (click)="uiNehubaContainer.matDrawerMinor.open() && uiNehubaContainer.navSideDrawerMainSwitch.open()" + [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" + #regionDirective="iavRegion"> + <span class="iv-custom-comp text text-truncate d-inline 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> + + <!-- chips for previewing origin datasets/continous map --> + <ng-container *ngFor="let originDataset of (r.originDatasets || []); let index = index"> + <div class="hidden" + iav-dataset-preview-dataset-file + [iav-dataset-preview-dataset-file-kgid]="originDataset.kgId" + [iav-dataset-preview-dataset-file-filename]="originDataset.filename" + #previewDirective="iavDatasetPreviewDatasetFile"> + </div> + <mat-chip *ngIf="previewDirective.active" + (click)="uiNehubaContainer.matDrawerMinor.open() && uiNehubaContainer.navSideDrawerMainSwitch.open()" + class="pe-all position-relative ml-8-n"> + <span class="pl-4"> + {{ regionDirective.regionOriginDatasetLabels$ | async | renderViewOriginDatasetlabel : index }} + </span> + <mat-icon (click)="previewDirective.onClick()" + fontSet="fas" + iav-stop="click" + fontIcon="fa-times"> + </mat-icon> + </mat-chip> + + <mat-chip *ngFor="let key of clearViewKeys$ | async" + (click)="uiNehubaContainer.matDrawerMinor.open() && uiNehubaContainer.navSideDrawerMainSwitch.open()" + class="pe-all position-relative ml-8-n"> + <span class="pl-4"> + {{ key }} + </span> + <mat-icon (click)="unsetClearViewByKey(key)" + fontSet="fas" + iav-stop="click" + fontIcon="fa-times"> + + </mat-icon> + </mat-chip> + </ng-container> + + </ng-container> + </ng-template> + </ng-template> + + </ng-template> + + <ng-template #selectedDatasetPreview let-layers="layers"> + + <ng-container *ngFor="let layer of layers"> + <div class="hidden" + iav-dataset-preview-dataset-file + [iav-dataset-preview-dataset-file-kgid]="layer.datasetId" + [iav-dataset-preview-dataset-file-filename]="layer.filename" + #preview="iavDatasetPreviewDatasetFile"> + + </div> + <mat-chip class="pe-all" + (click)="uiNehubaContainer.matDrawerMinor.open() && uiNehubaContainer.navSideDrawerMainSwitch.open()"> + {{ layer.file?.name || layer.filename || 'Unknown data preview' }} + <mat-icon fontSet="fas" fontIcon="fa-times" + (click)="preview.onClick()" + iav-stop="click"> + </mat-icon> + </mat-chip> + </ng-container> + </ng-template> + + </ng-template> + </div> + + <!-- top left content transclusion --> + <div ui-nehuba-container-overlay-top-left class="d-inline-flex pe-none w-100 align-items-start m-2"> + <ui-status-card + *ngIf="uiNehubaContainer.viewerLoaded" + class="pe-all muted-7" + [selectedTemplateName]="uiNehubaContainer?.selectedTemplate?.name" + [nehubaViewer]="uiNehubaContainer?.nehubaViewer"> + </ui-status-card> + </div> + </ui-nehuba-container> + */ \ No newline at end of file diff --git a/src/viewerModule/nehuba/actions.ts b/src/viewerModule/nehuba/actions.ts new file mode 100644 index 0000000000000000000000000000000000000000..979a485f1694ed7586d2326a5a286703d4610522 --- /dev/null +++ b/src/viewerModule/nehuba/actions.ts @@ -0,0 +1,10 @@ +import { createAction, props } from "@ngrx/store"; +import { INgLayerInterface } from "src/services/state/ngViewerState.store"; +import { NEHUBA_VIEWER_FEATURE_KEY } from "./constants"; + +export const actionAddNgLayer = createAction( + `[${NEHUBA_VIEWER_FEATURE_KEY}] [addNgLayer]`, + props<{ + layers: INgLayerInterface[] + }>() +) \ No newline at end of file diff --git a/src/viewerModule/nehuba/constants.ts b/src/viewerModule/nehuba/constants.ts new file mode 100644 index 0000000000000000000000000000000000000000..5dd2cd2c51eb1146d8769bfe0025d93644d33c6c --- /dev/null +++ b/src/viewerModule/nehuba/constants.ts @@ -0,0 +1,59 @@ +export { getNgIds } from 'src/util/fn' +export const NEHUBA_VIEWER_FEATURE_KEY = 'ngViewerFeature' + +export interface INgLayerInterface { + name: string // displayName + source: string + mixability: string // base | mixable | nonmixable + annotation?: string // + id?: string // unique identifier + visible?: boolean + shader?: string + transform?: any +} + +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 +} diff --git a/src/viewerModule/nehuba/index.ts b/src/viewerModule/nehuba/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..65cb900d6d7293034929898b2515f02082d04b37 --- /dev/null +++ b/src/viewerModule/nehuba/index.ts @@ -0,0 +1,4 @@ +export { NehubaGlueCmp } from "./nehubaViewerGlue/nehubaViewerGlue.component" +export { NehubaViewerTouchDirective } from "./nehubaViewerInterface/nehubaViewerTouch.directive" +export { NehubaModule } from "./module" +export { NehubaViewerUnit } from "./nehubaViewer/nehubaViewer.component" diff --git a/src/ui/nehubaContainer/maximisePanelButton/maximisePanelButton.component.ts b/src/viewerModule/nehuba/maximisePanelButton/maximisePanelButton.component.ts similarity index 97% rename from src/ui/nehubaContainer/maximisePanelButton/maximisePanelButton.component.ts rename to src/viewerModule/nehuba/maximisePanelButton/maximisePanelButton.component.ts index b3e5745f92e2ea7e9dbb2d95cc2298375b3a49b3..7aab44ac4156b05454a3d7bce915068cdf24a07e 100644 --- a/src/ui/nehubaContainer/maximisePanelButton/maximisePanelButton.component.ts +++ b/src/viewerModule/nehuba/maximisePanelButton/maximisePanelButton.component.ts @@ -19,7 +19,7 @@ const { ], }) -export class MaximmisePanelButton { +export class MaximisePanelButton { public ARIA_LABEL_MAXIMISE_VIEW = MAXIMISE_VIEW public ARIA_LABEL_UNMAXIMISE_VIEW = UNMAXIMISE_VIEW diff --git a/src/viewerModule/nehuba/maximisePanelButton/maximisePanelButton.style.css b/src/viewerModule/nehuba/maximisePanelButton/maximisePanelButton.style.css new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/ui/nehubaContainer/maximisePanelButton/maximisePanelButton.template.html b/src/viewerModule/nehuba/maximisePanelButton/maximisePanelButton.template.html similarity index 100% rename from src/ui/nehubaContainer/maximisePanelButton/maximisePanelButton.template.html rename to src/viewerModule/nehuba/maximisePanelButton/maximisePanelButton.template.html diff --git a/src/viewerModule/nehuba/module.ts b/src/viewerModule/nehuba/module.ts new file mode 100644 index 0000000000000000000000000000000000000000..061d73c27f32a9a1793d0d398292a02155c44aa3 --- /dev/null +++ b/src/viewerModule/nehuba/module.ts @@ -0,0 +1,79 @@ +import { ComponentRef, NgModule } from "@angular/core"; +import { NehubaViewerContainerDirective } from './nehubaViewerInterface/nehubaViewerInterface.directive' +import { IMPORT_NEHUBA_INJECT_TOKEN, NehubaViewerUnit } from "./nehubaViewer/nehubaViewer.component"; +import { CommonModule } from "@angular/common"; +import { APPEND_SCRIPT_TOKEN } from "src/util/constants"; +import { importNehubaFactory, NEHUBA_INSTANCE_INJTKN } from "./util"; +import { NehubaViewerTouchDirective } from "./nehubaViewerInterface/nehubaViewerTouch.directive"; +import { StoreModule } from "@ngrx/store"; +import { NEHUBA_VIEWER_FEATURE_KEY } from "./constants"; +import { reducer } from "./store"; +import { NehubaGlueCmp } from "./nehubaViewerGlue/nehubaViewerGlue.component"; +import { UtilModule } from "src/util"; +import { LayoutModule } from "src/layouts/layout.module"; +import { TouchSideClass } from "./touchSideClass.directive"; +import { ComponentsModule } from "src/components"; +import { AngularMaterialModule } from "src/ui/sharedModules/angularMaterial.module"; +import { MaximisePanelButton } from "./maximisePanelButton/maximisePanelButton.component"; +import { Landmark2DModule } from "src/ui/nehubaContainer/2dLandmarks/module"; +import { MouseoverModule } from "src/mouseoverModule"; +import { StatusCardComponent } from "./statusCard/statusCard.component"; +import { ShareModule } from "src/share"; +import { FormsModule, ReactiveFormsModule } from "@angular/forms"; +import { Subject } from "rxjs"; +import { StateModule } from "src/state"; +import { AuthModule } from "src/auth"; + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + ReactiveFormsModule, + UtilModule, + LayoutModule, + AngularMaterialModule, + Landmark2DModule, + ComponentsModule, + MouseoverModule, + ShareModule, + + /** + * should probably break this into its own... + * share url module or something? + */ + StateModule, + AuthModule, + StoreModule.forFeature( + NEHUBA_VIEWER_FEATURE_KEY, + reducer + ) + ], + declarations: [ + NehubaViewerContainerDirective, + NehubaViewerUnit, + NehubaViewerTouchDirective, + NehubaGlueCmp, + TouchSideClass, + MaximisePanelButton, + StatusCardComponent, + ], + exports: [ + NehubaViewerUnit, + NehubaViewerTouchDirective, + NehubaGlueCmp, + StatusCardComponent, + ], + providers: [ + { + provide: IMPORT_NEHUBA_INJECT_TOKEN, + useFactory: importNehubaFactory, + deps: [ APPEND_SCRIPT_TOKEN ] + }, + { + provide: NEHUBA_INSTANCE_INJTKN, + useValue: new Subject() + } + ] +}) + +export class NehubaModule{} diff --git a/src/viewerModule/nehuba/nehubaContainer.component.spec.ts b/src/viewerModule/nehuba/nehubaContainer.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..61bb83b5671bbfe3366c0a5ae2450bfa0f4c5711 --- /dev/null +++ b/src/viewerModule/nehuba/nehubaContainer.component.spec.ts @@ -0,0 +1,688 @@ + +// const { +// TOGGLE_SIDE_PANEL, +// EXPAND, +// COLLAPSE, +// ZOOM_IN, +// ZOOM_OUT, +// TOGGLE_FRONTAL_OCTANT +// } = ARIA_LABELS + +// const _bigbrainJson = require('!json-loader!src/res/ext/bigbrain.json') +// const _bigbrainNehubaConfigJson = require('!json-loader!src/res/ext/bigbrainNehubaConfig.json') +// const bigbrainJson = { +// ..._bigbrainJson, +// nehubaConfig: _bigbrainNehubaConfigJson +// } +// const humanAtlas = require('!json-loader!src/res/ext/atlas/atlas_multiLevelHuman.json') +// const importNehubaSpy = jasmine.createSpy('importNehubaSpy').and.returnValue(Promise.reject()) + +describe('> nehubaContainer.component.ts', () => { + + describe('> NehubaContainer', () => { + + // beforeEach(async(() => { + + // TestBed.configureTestingModule({ + // imports: [ + // NoopAnimationsModule, + // WidgetModule, + // AngularMaterialModule, + // LayoutModule, + // UtilModule, + // DatabrowserModule, + // NehubaModule, + // AuthModule, + // StateModule, + // FormsModule, + // ReactiveFormsModule, + // HttpClientModule, + // CommonModule, + // RegionalFeaturesModule, + // ParcellationRegionModule, + // AtlasCmpParcellationModule, + + // /** + // * because the change done to pureconstant service, need to intercept http call to avoid crypto error message + // * so and so components needs to be compiled first. make sure you call compileComponents + // */ + // HttpClientTestingModule, + // Landmark2DModule, + // ], + // declarations: [ + // NehubaContainer, + // TouchSideClass, + // MaximisePanelButton, + // AtlasLayerSelector, + // StatusCardComponent, + // NehubaViewerTouchDirective, + // MobileOverlay, + + // SplashScreen, + // CurrentLayout, + + // // pipes + // MobileControlNubStylePipe, + // ReorderPanelIndexPipe, + + // RegionAccordionTooltipTextPipe, + // ], + // providers: [ + // provideMockStore({ initialState: defaultRootState }), + // { + // provide: IMPORT_NEHUBA_INJECT_TOKEN, + // useValue: importNehubaSpy + // }, + // PureContantService, + // ], + // schemas: [ + // CUSTOM_ELEMENTS_SCHEMA + // ], + // }).compileComponents() + + // })) + + + // const fixture = TestBed.createComponent(NehubaContainer) + // fixture.componentInstance.currentOnHoverObs$ = hot('') + // const el = fixture.debugElement.componentInstance + // expect(el).toBeTruthy() + it('> component can be created') + + describe('> on selectedTemplatechange', () => { + + // const fixture = TestBed.createComponent(NehubaContainer) + // fixture.componentInstance.currentOnHoverObs$ = hot('') + + // const mockStore = TestBed.inject(MockStore) + // const newState = { + // ...defaultRootState, + // viewerState: { + // ...defaultRootState.viewerState, + // fetchedTemplates: [ bigbrainJson ], + // templateSelected: bigbrainJson, + // parcellationSelected: bigbrainJson.parcellations[0] + // }, + // [viewerStateHelperStoreName]: { + // fetchedAtlases: [ humanAtlas ], + // selectedAtlasId: humanAtlas['@id'] + // } + // } + + // mockStore.setState(newState) + // fixture.detectChanges() + // expect(importNehubaSpy).toHaveBeenCalled() + it('> calls importNehubaPr') + + /** + * TODO perhaps move this to e2e? + */ + it('> drag handle reattaches properly') + }) + + describe('> on selectedparcellation change', () => { + + + // const fixture = TestBed.createComponent(NehubaContainer) + // fixture.componentInstance.currentOnHoverObs$ = hot('') + // const el = fixture.debugElement.componentInstance as NehubaContainer + // const mockStore = TestBed.inject(MockStore) + // const newState = { + // ...defaultRootState, + // viewerState: { + // ...defaultRootState.viewerState, + // fetchedTemplates: [ bigbrainJson ], + // templateSelected: bigbrainJson, + // parcellationSelected: bigbrainJson.parcellations[0] + // }, + // [viewerStateHelperStoreName]: { + // fetchedAtlases: [ humanAtlas ], + // selectedAtlasId: humanAtlas['@id'] + // } + // } + + // mockStore.setState(newState) + // fixture.detectChanges() + + // const setSpy = spyOnProperty(el.nehubaViewer, 'ngIds', 'set') + + // const newState2 = { + // ...defaultRootState, + // viewerState: { + // ...defaultRootState.viewerState, + // fetchedTemplates: [ bigbrainJson ], + // templateSelected: bigbrainJson, + // parcellationSelected: bigbrainJson.parcellations[1] + // }, + // [viewerStateHelperStoreName]: { + // fetchedAtlases: [ humanAtlas ], + // selectedAtlasId: humanAtlas['@id'] + // } + // } + + // mockStore.setState(newState2) + // fixture.detectChanges() + + // expect(setSpy).toHaveBeenCalled() + it('> should set ngId of nehubaViewer') + }) + + describe('> extended sidepanel hides and shows as expected', () => { + describe('> on start, if nothing is selected', () => { + // beforeEach(() => { + // const mockStore = TestBed.inject(MockStore) + // const newState = { + // ...defaultRootState, + // viewerState: { + // ...defaultRootState.viewerState, + // fetchedTemplates: [ bigbrainJson ], + // templateSelected: bigbrainJson, + // parcellationSelected: bigbrainJson.parcellations[0] + // }, + // [viewerStateHelperStoreName]: { + // fetchedAtlases: [ humanAtlas ], + // selectedAtlasId: humanAtlas['@id'] + // } + // } + + // mockStore.setState(newState) + // }) + + + + // const fixture = TestBed.createComponent(NehubaContainer) + // fixture.componentInstance.currentOnHoverObs$ = hot('') + // fixture.detectChanges() + // expect( + // fixture.componentInstance.matDrawerMain.opened + // ).toEqual(false) + // expect( + // fixture.componentInstance.matDrawerMinor.opened + // ).toEqual(false) + it('> both should be shut') + + + // const fixture = TestBed.createComponent(NehubaContainer) + // fixture.componentInstance.currentOnHoverObs$ = hot('') + // fixture.detectChanges() + // const toggleBtn = fixture.debugElement.query( By.css(`[aria-label="${TOGGLE_SIDE_PANEL}"]`) ) + // toggleBtn.triggerEventHandler('click', null) + // fixture.detectChanges() + + // expect( + // fixture.componentInstance.matDrawerMain.opened + // ).toEqual(true) + // expect( + // fixture.componentInstance.matDrawerMinor.opened + // ).toEqual(false) + it('> opening via tab should result in only top drawer open') + + + + // const fixture = TestBed.createComponent(NehubaContainer) + // fixture.componentInstance.currentOnHoverObs$ = hot('') + // fixture.detectChanges() + // const toggleBtn = fixture.debugElement.query( By.css(`[aria-label="${TOGGLE_SIDE_PANEL}"]`) ) + // toggleBtn.triggerEventHandler('click', null) + // fixture.detectChanges() + // const expandRegionFeatureBtn = fixture.debugElement.query( By.css(`mat-drawer[data-mat-drawer-secondary-open="true"] [aria-label="${EXPAND}"]`) ) + // expect(expandRegionFeatureBtn).toBeNull() + it('> on opening top drawer, explore features should not be present') + + + // const fixture = TestBed.createComponent(NehubaContainer) + // fixture.componentInstance.currentOnHoverObs$ = hot('') + // fixture.detectChanges() + // const toggleBtn = fixture.debugElement.query( By.css(`[aria-label="${TOGGLE_SIDE_PANEL}"]`) ) + // toggleBtn.triggerEventHandler('click', null) + // fixture.detectChanges() + // const expandRegionFeatureBtn = fixture.debugElement.query( By.css(`mat-drawer[data-mat-drawer-secondary-open="true"] [aria-label="${COLLAPSE}"]`) ) + // expect(expandRegionFeatureBtn).toBeNull() + it('> collapse btn should not be visible') + }) + + describe('> on start, if something is selected', () => { + // beforeEach(() => { + // const mockStore = TestBed.inject(MockStore) + // const newState = { + // ...defaultRootState, + // viewerState: { + // ...defaultRootState.viewerState, + // fetchedTemplates: [ bigbrainJson ], + // templateSelected: bigbrainJson, + // parcellationSelected: bigbrainJson.parcellations[0], + // regionsSelected: [{ + // name: "foobar", + // ngId: 'untitled', + // labelIndex: 15 + // }] + // }, + // [viewerStateHelperStoreName]: { + // fetchedAtlases: [ humanAtlas ], + // selectedAtlasId: humanAtlas['@id'] + // } + // } + + // mockStore.setState(newState) + // }) + + + // const fixture = TestBed.createComponent(NehubaContainer) + // fixture.componentInstance.currentOnHoverObs$ = hot('') + // fixture.detectChanges() + // expect( + // fixture.componentInstance.matDrawerMain.opened + // ).toEqual(true) + // expect( + // fixture.componentInstance.matDrawerMinor.opened + // ).toEqual(true) + + // expect( + // fixture.componentInstance.navSideDrawerMainSwitch.switchState + // ).toEqual(true) + // expect( + // fixture.componentInstance.navSideDrawerMinorSwitch.switchState + // ).toEqual(true) + it('> both should be open') + + + // () => { + // const fixture = TestBed.createComponent(NehubaContainer) + // fixture.componentInstance.currentOnHoverObs$ = hot('') + // fixture.detectChanges() + // const toggleBtn = fixture.debugElement.query( By.css(`[aria-label="${TOGGLE_SIDE_PANEL}"]`) ) + // toggleBtn.triggerEventHandler('click', null) + // fixture.detectChanges() + // expect( + // fixture.componentInstance.matDrawerMain.opened + // ).toEqual(false) + + // /** + // * TODO investigate why openedStart/closedStart events fail to fire + // */ + // // expect( + // // fixture.componentInstance.matDrawerMinor.opened + // // ).toEqual(false) + + // // expect( + // // fixture.componentInstance.navSideDrawerMainSwitch.switchState + // // ).toEqual(false) + // // expect( + // // fixture.componentInstance.navSideDrawerMinorSwitch.switchState + // // ).toEqual(false) + // } + it('> closing main drawer via tag should close both') + + + // () => { + + // const fixture = TestBed.createComponent(NehubaContainer) + // fixture.componentInstance.currentOnHoverObs$ = hot('') + // fixture.detectChanges() + // const collapseRegionFeatureBtn = fixture.debugElement.query( By.css(`mat-drawer[data-mat-drawer-secondary-open="true"] [aria-label="${COLLAPSE}"]`) ) + // expect(collapseRegionFeatureBtn).not.toBeNull() + // } + it('> collapse btn should be visible') + + // () => { + + // const fixture = TestBed.createComponent(NehubaContainer) + // fixture.componentInstance.currentOnHoverObs$ = hot('') + // fixture.detectChanges() + // const collapseRegionFeatureBtn = fixture.debugElement.query( By.css(`mat-drawer[data-mat-drawer-secondary-open="true"] [aria-label="${COLLAPSE}"]`) ) + // collapseRegionFeatureBtn.triggerEventHandler('click', null) + // fixture.detectChanges() + // expect( + // fixture.componentInstance.matDrawerMain.opened + // ).toEqual(true) + + // /** + // * TODO investigate why property does not get updated + // */ + // // expect( + // // fixture.componentInstance.matDrawerMinor.opened + // // ).toEqual(false) + + // expect( + // fixture.componentInstance.navSideDrawerMainSwitch.switchState + // ).toEqual(true) + // expect( + // fixture.componentInstance.navSideDrawerMinorSwitch.switchState + // ).toEqual(false) + // } + it('> clicking on collapse btn should minimize 1 drawer') + + // () => { + // const fixture = TestBed.createComponent(NehubaContainer) + // fixture.componentInstance.currentOnHoverObs$ = hot('') + // fixture.detectChanges() + // const collapseRegionFeatureBtn = fixture.debugElement.query( By.css(`mat-drawer[data-mat-drawer-secondary-open="true"] [aria-label="${COLLAPSE}"]`) ) + // collapseRegionFeatureBtn.triggerEventHandler('click', null) + // fixture.detectChanges() + // const expandRegionFeatureBtn = fixture.debugElement.query( By.css(`mat-drawer[data-mat-drawer-primary-open="true"] [aria-label="${EXPAND}"]`) ) + // expandRegionFeatureBtn.triggerEventHandler('click', null) + // fixture.detectChanges() + + // expect( + // fixture.componentInstance.matDrawerMain.opened + // ).toEqual(true) + // expect( + // fixture.componentInstance.matDrawerMinor.opened + // ).toEqual(true) + + // expect( + // fixture.componentInstance.navSideDrawerMainSwitch.switchState + // ).toEqual(true) + // /** + // * TODO figoure out why switch state is updated async, and karma can't force update state + // */ + // // expect( + // // fixture.componentInstance.navSideDrawerMinorSwitch.switchState + // // ).toEqual(true) + // } + it('> on minimize drawer, clicking expand btn should expand everything') + }) + + describe('> side bar content', () => { + + /** + * TODO + */ + it('> if nothing is shown, it should show place holder text') + + /** + * TODO + */ + it('> if something (region features/connectivity) exists, placeh holder text should be hdiden') + }) + }) + + describe('> panelCtrl', () => { + // let fixture: ComponentFixture<NehubaContainer> + // const setViewerLoaded = () => { + // fixture.componentInstance.viewerLoaded = true + // } + // const ctrlElementIsVisible = (el: DebugElement) => { + // const visible = (el.nativeElement as HTMLElement).getAttribute('data-viewer-controller-visible') + // return visible === 'true' + // } + // beforeEach(() => { + // fixture = TestBed.createComponent(NehubaContainer) + // }) + + // () => { + // fixture.detectChanges() + // setViewerLoaded() + // fixture.detectChanges() + // for (const idx of [0, 1, 2, 3]) { + // const el = fixture.debugElement.query( + // By.css(`[data-viewer-controller-index="${idx}"]`) + // ) + // expect(el).toBeTruthy() + // } + // } + it('> on start, all four ctrl panels exists') + + // () => { + + // fixture.detectChanges() + // setViewerLoaded() + // fixture.detectChanges() + // for (const idx of [0, 1, 2, 3]) { + // const el = fixture.debugElement.query( + // By.css(`[data-viewer-controller-index="${idx}"]`) + // ) + // expect(ctrlElementIsVisible(el)).toBeFalsy() + // } + // } + it('> on start all four ctrl panels are invisible') + + describe('> on hover, only the hovered panel have ctrl shown', () => { + + for (const idx of [0, 1, 2, 3]) { + + // fakeAsync(() => { + // fixture.detectChanges() + // const findPanelIndexSpy = spyOn<any>(fixture.componentInstance, 'findPanelIndex').and.callFake(() => { + // return idx + // }) + // setViewerLoaded() + // fixture.detectChanges() + // const nativeElement = fixture.componentInstance['elementRef'].nativeElement + // nativeElement.dispatchEvent(new MouseEvent('mouseover', { bubbles: true })) + + // /** + // * assert findPanelIndex called with event.target, i.e. native element in thsi case + // */ + // expect(findPanelIndexSpy).toHaveBeenCalledWith(nativeElement) + // tick(200) + // fixture.detectChanges() + + // /** + // * every panel index should be non visible + // * only when idx matches, it can be visible + // * n.b. this does not test visual visibility (which is controlled by extra-style.css) + // * (which is also affected by global [ismobile] configuration) + // * + // * this merely test the unit logic, and sets the flag appropriately + // */ + // for (const iterativeIdx of [0, 1, 2, 3]) { + // const el = fixture.debugElement.query( + // By.css(`[data-viewer-controller-index="${iterativeIdx}"]`) + // ) + // if (iterativeIdx === idx) { + // expect(ctrlElementIsVisible(el)).toBeTruthy() + // } else { + // expect(ctrlElementIsVisible(el)).toBeFalsy() + // } + // } + // discardPeriodicTasks() + // }) + it(`> on hoveredPanelIndices$ emit ${idx}, the panel index ${idx} ctrl becomes visible`) + } + + }) + + describe('> on maximise top right slice panel (idx 1)', () => { + // beforeEach(() => { + // const mockStore = TestBed.inject(MockStore) + // mockStore.overrideSelector(ngViewerSelectorPanelMode, PANELS.SINGLE_PANEL) + // mockStore.overrideSelector(ngViewerSelectorPanelOrder, '1230') + + // fixture.detectChanges() + // setViewerLoaded() + // fixture.detectChanges() + // }) + + + // () => { + + // const toggleBtn = fixture.debugElement.query( + // By.css(`[cell-i] [aria-label="${TOGGLE_FRONTAL_OCTANT}"]`) + // ) + // expect(toggleBtn).toBeFalsy() + // } + it('> toggle front octant btn not visible') + + // () => { + + // const zoomInBtn = fixture.debugElement.query( + // By.css(`[cell-i] [aria-label="${ZOOM_IN}"]`) + // ) + + // const zoomOutBtn = fixture.debugElement.query( + // By.css(`[cell-i] [aria-label="${ZOOM_OUT}"]`) + // ) + + // expect(zoomInBtn).toBeTruthy() + // expect(zoomOutBtn).toBeTruthy() + // } + it('> zoom in and out btns are visible') + + // () => { + // const zoomViewSpy = spyOn(fixture.componentInstance, 'zoomNgView') + + // const zoomInBtn = fixture.debugElement.query( + // By.css(`[cell-i] [aria-label="${ZOOM_IN}"]`) + // ) + // zoomInBtn.triggerEventHandler('click', null) + // expect(zoomViewSpy).toHaveBeenCalled() + // const { args } = zoomViewSpy.calls.first() + // expect(args[0]).toEqual(1) + // /** + // * zoom in < 1 + // */ + // expect(args[1]).toBeLessThan(1) + // } + it('> zoom in btn calls fn with right param') + + // () => { + // const zoomViewSpy = spyOn(fixture.componentInstance, 'zoomNgView') + + // const zoomOutBtn = fixture.debugElement.query( + // By.css(`[cell-i] [aria-label="${ZOOM_OUT}"]`) + // ) + // zoomOutBtn.triggerEventHandler('click', null) + // expect(zoomViewSpy).toHaveBeenCalled() + // const { args } = zoomViewSpy.calls.first() + // expect(args[0]).toEqual(1) + // /** + // * zoom out > 1 + // */ + // expect(args[1]).toBeGreaterThan(1) + // } + it('> zoom out btn calls fn with right param') + }) + + describe('> on maximise perspective panel', () => { + // beforeEach(() => { + // const mockStore = TestBed.inject(MockStore) + // mockStore.overrideSelector(ngViewerSelectorPanelMode, PANELS.SINGLE_PANEL) + // mockStore.overrideSelector(ngViewerSelectorPanelOrder, '3012') + + // fixture.detectChanges() + // setViewerLoaded() + // fixture.detectChanges() + // }) + + + // () => { + // const setOctantRemovalSpy = spyOn(fixture.componentInstance, 'setOctantRemoval') + + // const toggleBtn = fixture.debugElement.query( + // By.css(`[cell-i] [aria-label="${TOGGLE_FRONTAL_OCTANT}"]`) + // ) + // expect(toggleBtn).toBeTruthy() + // toggleBtn.nativeElement.dispatchEvent( + // new MouseEvent('click', { bubbles: true }) + // ) + // expect(setOctantRemovalSpy).toHaveBeenCalled() + // } + it('> toggle octant btn visible and functional') + + // () => { + // const zoomViewSpy = spyOn(fixture.componentInstance, 'zoomNgView') + + // const zoomInBtn = fixture.debugElement.query( + // By.css(`[cell-i] [aria-label="${ZOOM_IN}"]`) + // ) + // expect(zoomInBtn).toBeTruthy() + + // zoomInBtn.triggerEventHandler('click', null) + // expect(zoomViewSpy).toHaveBeenCalled() + // const { args } = zoomViewSpy.calls.first() + // expect(args[0]).toEqual(3) + // /** + // * zoom in < 1 + // */ + // expect(args[1]).toBeLessThan(1) + // } + it('> zoom in btn visible and functional') + + // () => { + // const zoomViewSpy = spyOn(fixture.componentInstance, 'zoomNgView') + + // const zoomOutBtn = fixture.debugElement.query( + // By.css(`[cell-i] [aria-label="${ZOOM_OUT}"]`) + // ) + // expect(zoomOutBtn).toBeTruthy() + + // zoomOutBtn.triggerEventHandler('click', null) + // expect(zoomViewSpy).toHaveBeenCalled() + // const { args } = zoomViewSpy.calls.first() + // expect(args[0]).toEqual(3) + // /** + // * zoom in < 1 + // */ + // expect(args[1]).toBeGreaterThan(1) + // } + it('> zoom out btn visible and functional') + + }) + }) + + describe('> on userLandmarks change', () => { + const lm1 = { + id: 'test-1', + position: [0, 0, 0] + } + const lm2 = { + id: 'test-2', + position: [1, 1,1 ] + } + + // () => { + // const fixture = TestBed.createComponent(NehubaContainer) + + // fixture.componentInstance.nehubaViewer = { + // updateUserLandmarks: () => {} + // } as any + + // const updateUserLandmarksSpy = spyOn( + // fixture.componentInstance.nehubaViewer, + // 'updateUserLandmarks' + // ) + + // const mockStore = TestBed.inject(MockStore) + // mockStore.overrideSelector(viewerStateCustomLandmarkSelector, [ + // lm1, + // lm2 + // ]) + // fixture.detectChanges() + // expect( + // updateUserLandmarksSpy + // ).toHaveBeenCalledWith([ + // lm1, lm2 + // ]) + // } + it('> calls nehubaViewer.updateUserLandmarks') + + // () => { + + // const fixture = TestBed.createComponent(NehubaContainer) + + // fixture.componentInstance.nehubaContainerDirective = { + // toggleOctantRemoval: () => {}, + // clear: () => {} + // } as any + + // const toggleOctantRemovalSpy = spyOn( + // fixture.componentInstance.nehubaContainerDirective, + // 'toggleOctantRemoval' + // ) + + // const mockStore = TestBed.inject(MockStore) + // mockStore.overrideSelector(viewerStateCustomLandmarkSelector, [ + // lm1, + // lm2 + // ]) + // mockStore.overrideSelector(ngViewerSelectorOctantRemoval, true) + // fixture.detectChanges() + // expect( + // toggleOctantRemovalSpy + // ).toHaveBeenCalledWith(false) + // } + it('> calls togglecotantREmoval') + }) + }) +}) diff --git a/src/ui/nehubaContainer/nehubaViewer/nehubaViewer.component.spec.ts b/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.spec.ts similarity index 100% rename from src/ui/nehubaContainer/nehubaViewer/nehubaViewer.component.spec.ts rename to src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.spec.ts diff --git a/src/ui/nehubaContainer/nehubaViewer/nehubaViewer.component.ts b/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.ts similarity index 98% rename from src/ui/nehubaContainer/nehubaViewer/nehubaViewer.component.ts rename to src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.ts index d1edab5e0f9fa80aa6347906b43c5d4ccceef803..57f4f526ff7651ce098ed165f73e7c835b4a7f2d 100644 --- a/src/ui/nehubaContainer/nehubaViewer/nehubaViewer.component.ts +++ b/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.ts @@ -3,7 +3,6 @@ import { fromEvent, Subscription, ReplaySubject, BehaviorSubject, Observable, ra import { debounceTime, filter, map, scan, startWith, mapTo, switchMap, take, skip } from "rxjs/operators"; import { AtlasWorkerService } from "src/atlasViewer/atlasViewer.workerService.service"; import { StateInterface as ViewerConfiguration } from "src/services/state/viewerConfig.store"; -import { getNgIdLabelIndexFromId } from "src/services/stateStore.service"; import { LoggingService } from "src/logging"; import { getExportNehuba, getViewer, setNehubaViewer } from "src/util/fn"; @@ -11,7 +10,7 @@ import { getExportNehuba, getViewer, setNehubaViewer } from "src/util/fn"; import '!!file-loader?context=third_party&name=main.bundle.js!export-nehuba/dist/min/main.bundle.js' import '!!file-loader?context=third_party&name=chunk_worker.bundle.js!export-nehuba/dist/min/chunk_worker.bundle.js' import { scanSliceViewRenderFn } from "../util"; -import { intToRgb as intToColour } from 'common/util' +import { intToRgb as intToColour, deserialiseParcRegionId } from 'common/util' const NG_LANDMARK_LAYER_NAME = 'spatial landmark layer' const NG_USER_LANDMARK_LAYER_NAME = 'user landmark layer' @@ -637,6 +636,8 @@ export class NehubaViewerUnit implements OnInit, OnDestroy { } public hideAllSeg() { + // console.log('hideallseg') + // debugger if (!this.nehubaViewer) { return } Array.from(this.multiNgIdsLabelIndexMap.keys()).forEach(ngId => { @@ -681,9 +682,13 @@ export class NehubaViewerUnit implements OnInit, OnDestroy { const reduceFn: (acc: Map<string, number[]>, curr: string) => Map<string, number[]> = (acc, curr) => { const newMap = new Map(acc) - const { ngId, labelIndex } = getNgIdLabelIndexFromId({ labelIndexId: curr }) + const { ngId, labelIndex } = deserialiseParcRegionId(curr) const exist = newMap.get(ngId) - if (!exist) { newMap.set(ngId, [Number(labelIndex)]) } else { newMap.set(ngId, [...exist, Number(labelIndex)]) } + if (!exist) { + newMap.set(ngId, [Number(labelIndex)]) + } else { + newMap.set(ngId, [...exist, Number(labelIndex)]) + } return newMap } diff --git a/src/ui/nehubaContainer/nehubaViewer/nehubaViewer.style.css b/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.style.css similarity index 100% rename from src/ui/nehubaContainer/nehubaViewer/nehubaViewer.style.css rename to src/viewerModule/nehuba/nehubaViewer/nehubaViewer.style.css diff --git a/src/ui/nehubaContainer/nehubaViewer/nehubaViewer.template.html b/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.template.html similarity index 100% rename from src/ui/nehubaContainer/nehubaViewer/nehubaViewer.template.html rename to src/viewerModule/nehuba/nehubaViewer/nehubaViewer.template.html diff --git a/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts b/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..d9b52d1dc806f239a1638c084256f04577a0a1e1 --- /dev/null +++ b/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts @@ -0,0 +1,793 @@ +import { Component, ElementRef, Inject, Input, OnChanges, OnDestroy, Optional, SimpleChanges, ViewChild } from "@angular/core"; +import { select, Store } from "@ngrx/store"; +import { asyncScheduler, combineLatest, fromEvent, interval, merge, Observable, of, Subject, timer } from "rxjs"; +import { ngViewerActionAddNgLayer, ngViewerActionRemoveNgLayer, ngViewerActionSetPerspOctantRemoval, ngViewerActionToggleMax } from "src/services/state/ngViewerState/actions"; +import { ClickInterceptor, CLICK_INTERCEPTOR_INJECTOR } from "src/util"; +import { uiStateMouseOverSegmentsSelector } from "src/services/state/uiState/selectors"; +import { debounceTime, distinctUntilChanged, filter, map, mapTo, scan, shareReplay, startWith, switchMap, switchMapTo, take, tap, throttleTime, withLatestFrom } from "rxjs/operators"; +import { viewerStateAddUserLandmarks, viewerStateChangeNavigation, viewerStateMouseOverCustomLandmark, viewerStateSelectRegionWithIdDeprecated, viewerStateSetSelectedRegions, viewreStateRemoveUserLandmarks } from "src/services/state/viewerState/actions"; +import { ngViewerSelectorLayers, ngViewerSelectorClearView, ngViewerSelectorPanelOrder, ngViewerSelectorOctantRemoval, ngViewerSelectorPanelMode } from "src/services/state/ngViewerState/selectors"; +import { viewerStateCustomLandmarkSelector, viewerStateSelectedRegionsSelector } from "src/services/state/viewerState/selectors"; +import { serialiseParcellationRegion } from 'common/util' +import { ARIA_LABELS, IDS } from 'common/constants' +import { PANELS } from "src/services/state/ngViewerState/constants"; +import { LoggingService } from "src/logging"; + +import { getNgIds, getMultiNgIdsRegionsLabelIndexMap } from "../constants"; +import { IViewer, TViewerEvent } from "../../viewer.interface"; +import { NehubaViewerUnit } from "../nehubaViewer/nehubaViewer.component"; +import { NehubaViewerContainerDirective } from "../nehubaViewerInterface/nehubaViewerInterface.directive"; +import { calculateSliceZoomFactor, getFourPanel, getHorizontalOneThree, getSinglePanel, getVerticalOneThree, NEHUBA_INSTANCE_INJTKN, scanSliceViewRenderFn, takeOnePipe } from "../util"; +import { API_SERVICE_SET_VIEWER_HANDLE_TOKEN, IUserLandmark, TSetViewerHandle } from "src/atlasViewer/atlasViewer.apiService.service"; +import { MouseHoverDirective } from "src/mouseoverModule"; + +interface INgLayerInterface { + name: string // displayName + source: string + mixability: string // base | mixable | nonmixable + annotation?: string // + id?: string // unique identifier + visible?: boolean + shader?: string + transform?: any +} + +@Component({ + selector: 'iav-cmp-viewer-nehuba-glue', + templateUrl: './nehubaViewerGlue.template.html', + styleUrls: [ + './nehubaViewerGlue.style.css' + ], + exportAs: 'iavCmpViewerNehubaGlue' +}) + +export class NehubaGlueCmp implements IViewer, OnChanges, OnDestroy{ + + public ARIA_LABELS = ARIA_LABELS + public IDS = IDS + + @ViewChild(NehubaViewerContainerDirective, { static: true }) + public nehubaContainerDirective: NehubaViewerContainerDirective + + @ViewChild(MouseHoverDirective, { static: true }) + private mouseoverDirective: MouseHoverDirective + + public viewerEvents$ = new Subject<TViewerEvent>() + public viewerLoaded$ = this.viewerEvents$.pipe( + filter(ev => ev.type === 'VIEWERLOADED'), + map(ev => ev.data) + ) + + private onhoverSegments = [] + private onDestroyCb: Function[] = [] + private viewerUnit: NehubaViewerUnit + private ngLayersRegister: {layers: INgLayerInterface[]} = { + layers: [] + } + private multiNgIdsRegionsLabelIndexMap: Map<string, Map<number, any>> + + @Input() + public selectedParcellation: any + + @Input() + public selectedTemplate: any + + private newViewer$ = new Subject() + + public showPerpsectiveScreen$: Observable<string> + public sliceViewLoadingMain$: Observable<[boolean, boolean, boolean]> + private sliceRenderEvent$: Observable<CustomEvent> + public perspectiveViewLoading$: Observable<string|null> + public hoveredPanelIndices$: Observable<number> + private viewPanelWeakMap = new WeakMap<HTMLElement, number>() + private viewPanels: [HTMLElement, HTMLElement, HTMLElement, HTMLElement] = [null, null, null, null] + private findPanelIndex = (panel: HTMLElement) => this.viewPanelWeakMap.get(panel) + public nanometersToOffsetPixelsFn: Array<(...arg) => any> = [] + + public customLandmarks$: Observable<any> = this.store$.pipe( + select(viewerStateCustomLandmarkSelector), + map(lms => lms.map(lm => ({ + ...lm, + geometry: { + position: lm.position + } + }))), + ) + + private forceUI$ = this.customLandmarks$.pipe( + map(lm => { + if (lm.length > 0) { + return { + target: 'perspective:octantRemoval', + mode: false, + message: `octant control disabled: showing landmarks.` + } + } else { + return { + target: 'perspective:octantRemoval', + mode: null + } + } + }) + ) + + public disableOctantRemovalCtrl$ = this.forceUI$.pipe( + filter(({ target }) => target === 'perspective:octantRemoval'), + ) + + public nehubaViewerPerspectiveOctantRemoval$ = this.store$.pipe( + select(ngViewerSelectorOctantRemoval), + ) + + public panelOrder$ = this.store$.pipe( + select(ngViewerSelectorPanelOrder), + distinctUntilChanged(), + shareReplay(1), + ) + + ngOnChanges(sc: SimpleChanges){ + const { + selectedParcellation, + selectedTemplate + } = sc + if (selectedTemplate?.previousValue) { + this.unloadTmpl(selectedTemplate?.previousValue) + } + if (selectedTemplate && selectedTemplate.currentValue !== selectedTemplate.previousValue) { + this.loadTmpl(selectedTemplate.currentValue, selectedParcellation.currentValue) + } else if (selectedParcellation && selectedParcellation.currentValue !== selectedParcellation.previousValue) { + this.loadParc(selectedParcellation.currentValue) + } + } + + ngOnDestroy() { + while (this.onDestroyCb.length) this.onDestroyCb.pop()() + } + + private loadParc(parcellation: any) { + /** + * parcellaiton may be undefined + */ + if ( !(parcellation && parcellation.regions)) { + return + } + + /** + * first, get all all the ngIds, including parent id from parcellation (if defined) + */ + const ngIds = getNgIds(parcellation.regions).concat( parcellation.ngId ? parcellation.ngId : []) + + this.multiNgIdsRegionsLabelIndexMap = getMultiNgIdsRegionsLabelIndexMap(parcellation) + + this.viewerUnit.multiNgIdsLabelIndexMap = this.multiNgIdsRegionsLabelIndexMap + this.viewerUnit.auxilaryMeshIndices = parcellation.auxillaryMeshIndices || [] + + /* TODO replace with proper KG id */ + /** + * need to set unique array of ngIds, or else workers will be overworked + */ + this.viewerUnit.ngIds = Array.from(new Set(ngIds)) + } + + private unloadTmpl(tmpl: any) { + /** + * clear existing container + */ + this.viewerUnit = null + this.nehubaContainerDirective.clear() + + /* on selecting of new template, remove additional nglayers */ + const baseLayerNames = Object.keys(tmpl.nehubaConfig.dataset.initialNgState.layers) + this.ngLayersRegister.layers + .filter(layer => baseLayerNames?.findIndex(l => l === layer.name) >= 0) + .map(l => l.name) + .forEach(layerName => { + this.store$.dispatch(ngViewerActionRemoveNgLayer({ + layer: { + name: layerName + } + })) + }) + } + + private async loadTmpl(_template: any, parcellation: any) { + + if (!_template) return + /** + * recalcuate zoom + */ + const template = (() => { + + const deepCopiedState = JSON.parse(JSON.stringify(_template)) + const navigation = deepCopiedState.nehubaConfig.dataset.initialNgState.navigation + if (!navigation) { + return deepCopiedState + } + navigation.zoomFactor = calculateSliceZoomFactor(navigation.zoomFactor) + deepCopiedState.nehubaConfig.dataset.initialNgState.navigation = navigation + return deepCopiedState + })() + + this.nehubaContainerDirective.createNehubaInstance(template) + this.viewerUnit = this.nehubaContainerDirective.nehubaViewerInstance + this.sliceRenderEvent$.pipe( + takeOnePipe() + ).subscribe(ev => { + + for (const idx of [0, 1, 2]) { + const e = ev[idx] as CustomEvent + const el = e.target as HTMLElement + this.viewPanelWeakMap.set(el, idx) + this.viewPanels[idx] = el + this.nanometersToOffsetPixelsFn[idx] = e.detail.nanometersToOffsetPixels + } + }) + const foundParcellation = parcellation + && template?.parcellations?.find(p => parcellation.name === p.name) + this.loadParc(foundParcellation || template.parcellations[0]) + + const nehubaConfig = template.nehubaConfig + const initialSpec = nehubaConfig.dataset.initialNgState + const {layers} = initialSpec + + const dispatchLayers = Object.keys(layers).map(key => { + const layer = { + name : key, + source : layers[key].source, + mixability : layers[key].type === 'image' + ? 'base' + : 'mixable', + visible : typeof layers[key].visible === 'undefined' + ? true + : layers[key].visible, + transform : typeof layers[key].transform === 'undefined' + ? null + : layers[key].transform, + } + this.ngLayersRegister.layers.push(layer) + return layer + }) + + this.newViewer$.next(true) + this.store$.dispatch(ngViewerActionAddNgLayer({ + layer: dispatchLayers + })) + } + + constructor( + private store$: Store<any>, + private el: ElementRef, + private log: LoggingService, + @Optional() @Inject(CLICK_INTERCEPTOR_INJECTOR) clickInterceptor: ClickInterceptor, + @Optional() @Inject(API_SERVICE_SET_VIEWER_HANDLE_TOKEN) setViewerHandle: TSetViewerHandle, + @Optional() @Inject(NEHUBA_INSTANCE_INJTKN) nehubaViewer$: Subject<NehubaViewerUnit>, + ){ + this.viewerEvents$.next({ + type: 'MOUSEOVER_ANNOTATION', + data: {} + }) + /** + * define onclick behaviour + */ + if (clickInterceptor) { + const { deregister, register } = clickInterceptor + const selOnhoverRegion = this.selectHoveredRegion.bind(this) + register(selOnhoverRegion) + this.onDestroyCb.push(() => deregister(selOnhoverRegion)) + } + + const nehubaViewerSub = this.newViewer$.pipe( + tap(() => nehubaViewer$.next(null)), + switchMap(this.waitForNehuba.bind(this)), + map(() => this.nehubaContainerDirective.nehubaViewerInstance) + ).subscribe(viewer => nehubaViewer$.next(viewer)) + this.onDestroyCb.push(() => nehubaViewerSub.unsubscribe()) + + /** + * on layout change + */ + const redrawLayoutSub = combineLatest([ + this.store$.pipe( + select(ngViewerSelectorPanelMode), + distinctUntilChanged(), + shareReplay(1), + ), + this.panelOrder$, + ]).pipe( + switchMap(this.waitForNehuba.bind(this)) + ).subscribe(([mode, panelOrder]) => { + const viewPanels = panelOrder.split('').map(v => Number(v)).map(idx => this.viewPanels[idx]) as [HTMLElement, HTMLElement, HTMLElement, HTMLElement] + + /** + * TODO smarter with event stream + */ + if (!viewPanels.every(v => !!v)) { + this.log.error(`on relayout, not every view panel is populated. This should not occur!`) + return + } + + switch (mode) { + case PANELS.H_ONE_THREE: { + const element = this.removeExistingPanels() + const newEl = getHorizontalOneThree(viewPanels) + element.appendChild(newEl) + break; + } + case PANELS.V_ONE_THREE: { + const element = this.removeExistingPanels() + const newEl = getVerticalOneThree(viewPanels) + element.appendChild(newEl) + break; + } + case PANELS.FOUR_PANEL: { + const element = this.removeExistingPanels() + const newEl = getFourPanel(viewPanels) + element.appendChild(newEl) + break; + } + case PANELS.SINGLE_PANEL: { + const element = this.removeExistingPanels() + const newEl = getSinglePanel(viewPanels) + element.appendChild(newEl) + break; + } + default: + } + for (const panel of viewPanels) { + (panel as HTMLElement).classList.add('neuroglancer-panel') + } + + // TODO needed to redraw? + // see https://trello.com/c/oJOnlc6v/60-enlarge-panel-allow-user-rearrange-panel-position + // further investigaation required + this.nehubaContainerDirective.nehubaViewerInstance.redraw() + }) + this.onDestroyCb.push(() => redrawLayoutSub.unsubscribe()) + + /** + * on hover segment + */ + const onhovSegSub = this.store$.pipe( + select(uiStateMouseOverSegmentsSelector), + distinctUntilChanged(), + ).subscribe(arr => { + const segments = arr.map(({ segment }) => segment).filter(v => !!v) + this.onhoverSegments = segments + }) + this.onDestroyCb.push(() => onhovSegSub.unsubscribe()) + + /** + * subscribe to when ngLayer gets updated, and add/remove layer as necessary + */ + const addRemoveAdditionalLayerSub = this.store$.pipe( + select(ngViewerSelectorLayers), + switchMap(this.waitForNehuba.bind(this)), + ).subscribe((ngLayers: INgLayerInterface[]) => { + + const newLayers = ngLayers.filter(l => this.ngLayersRegister.layers?.findIndex(ol => ol.name === l.name) < 0) + const removeLayers = this.ngLayersRegister.layers.filter(l => ngLayers?.findIndex(nl => nl.name === l.name) < 0) + + if (newLayers?.length > 0) { + const newLayersObj: any = {} + newLayers.forEach(({ name, source, ...rest }) => newLayersObj[name] = { + ...rest, + source, + // source: getProxyUrl(source), + // ...getProxyOther({source}) + }) + + this.nehubaContainerDirective.nehubaViewerInstance.loadLayer(newLayersObj) + + /** + * previous miplementation... if nehubaViewer has not yet be instantiated, add it to the queue + * may no longer be necessary + * or implement proper queue'ing rather than ... this... half assed queue'ing + */ + // if (!this.nehubaViewer.nehubaViewer || !this.nehubaViewer.nehubaViewer.ngviewer) { + // this.nehubaViewer.initNiftiLayers.push(newLayersObj) + // } else { + // this.nehubaViewer.loadLayer(newLayersObj) + // } + this.ngLayersRegister.layers = this.ngLayersRegister.layers.concat(newLayers) + } + + if (removeLayers?.length > 0) { + removeLayers.forEach(l => { + if (this.nehubaContainerDirective.nehubaViewerInstance.removeLayer({ + name : l.name, + })) { + this.ngLayersRegister.layers = this.ngLayersRegister.layers.filter(rl => rl.name !== l.name) + } + }) + } + }) + this.onDestroyCb.push(() => addRemoveAdditionalLayerSub.unsubscribe()) + + /** + * define when shown segments should be updated + */ + const regSelectSub = combineLatest([ + /** + * selectedRegions + */ + this.store$.pipe( + select(viewerStateSelectedRegionsSelector) + ), + /** + * if layer contains non mixable layer + */ + this.store$.pipe( + select(ngViewerSelectorLayers), + map(layers => layers.findIndex(l => l.mixability === 'nonmixable') >= 0), + ), + /** + * clearviewqueue, indicating something is controlling colour map + * show all seg + */ + this.store$.pipe( + select(ngViewerSelectorClearView), + distinctUntilChanged() + ) + ]).pipe( + switchMap(this.waitForNehuba.bind(this)), + ).subscribe(([ regions, nonmixableLayerExists, clearViewFlag ]) => { + if (nonmixableLayerExists) { + this.nehubaContainerDirective.nehubaViewerInstance.hideAllSeg() + return + } + const { ngId: defaultNgId } = this.selectedParcellation || {} + + /* selectedregionindexset needs to be updated regardless of forceshowsegment */ + const selectedRegionIndexSet = new Set<string>(regions.map(({ngId = defaultNgId, labelIndex}) => serialiseParcellationRegion({ ngId, labelIndex }))) + + if (selectedRegionIndexSet.size > 0 && !clearViewFlag) { + this.nehubaContainerDirective.nehubaViewerInstance.showSegs([...selectedRegionIndexSet]) + } else { + this.nehubaContainerDirective.nehubaViewerInstance.showAllSeg() + } + }) + this.onDestroyCb.push(() => regSelectSub.unsubscribe()) + + const perspectiveRenderEvSub = this.newViewer$.pipe( + switchMapTo(fromEvent<CustomEvent>(this.el.nativeElement, 'perpspectiveRenderEvent').pipe( + take(1) + )) + ).subscribe(ev => { + const perspPanel = ev.target as HTMLElement + this.viewPanels[3] = perspPanel + this.viewPanelWeakMap.set(perspPanel, 3) + }) + this.onDestroyCb.push(() => perspectiveRenderEvSub.unsubscribe()) + + const perspOctCtrlSub = this.customLandmarks$.pipe( + withLatestFrom( + this.nehubaViewerPerspectiveOctantRemoval$ + ), + switchMap(this.waitForNehuba.bind(this)) + ).subscribe(([ landmarks, flag ]) => { + this.nehubaContainerDirective.toggleOctantRemoval( + landmarks.length > 0 ? false : flag + ) + this.nehubaContainerDirective.nehubaViewerInstance.updateUserLandmarks(landmarks) + }) + this.onDestroyCb.push(() => perspOctCtrlSub.unsubscribe()) + + this.sliceRenderEvent$ = fromEvent<CustomEvent>(this.el.nativeElement, 'sliceRenderEvent') + this.sliceViewLoadingMain$ = this.sliceRenderEvent$.pipe( + scan(scanSliceViewRenderFn, [null, null, null]), + startWith([true, true, true] as [boolean, boolean, boolean]), + shareReplay(1), + ) + + this.perspectiveViewLoading$ = fromEvent(this.el.nativeElement, 'perpspectiveRenderEvent').pipe( + filter((event: CustomEvent) => event?.detail?.lastLoadedMeshId ), + map(event => { + + /** + * TODO dig into event detail to see if the exact mesh loaded + */ + const { meshesLoaded, meshFragmentsLoaded, lastLoadedMeshId } = (event as any).detail + return meshesLoaded >= this.nehubaContainerDirective.nehubaViewerInstance.numMeshesToBeLoaded + ? null + : 'Loading meshes ...' + }), + distinctUntilChanged() + ) + + this.showPerpsectiveScreen$ = this.newViewer$.pipe( + switchMapTo(this.sliceRenderEvent$.pipe( + scan((acc, curr) => { + + /** + * if at any point, all chunks have been loaded, always return loaded state + */ + if (acc.every(v => v === 0)) return [0, 0, 0] + const { detail = {}, target } = curr || {} + const { missingChunks = -1, missingImageChunks = -1 } = detail + const idx = this.findPanelIndex(target as HTMLElement) + const returnAcc = [...acc] + if (idx >= 0) { + returnAcc[idx] = missingChunks + missingImageChunks + } + return returnAcc + }, [-1, -1, -1]), + map(arr => { + let sum = 0 + let uncertain = false + for (const num of arr) { + if (num < 0) { + uncertain = true + } else { + sum += num + } + } + return sum > 0 + ? `Loading ${sum}${uncertain ? '+' : ''} chunks ...` + : null + }), + distinctUntilChanged(), + startWith('Loading ...'), + throttleTime(100, asyncScheduler, { leading: true, trailing: true }), + shareReplay(1), + )) + ) + + this.hoveredPanelIndices$ = fromEvent(this.el.nativeElement, 'mouseover').pipe( + switchMap((ev: MouseEvent) => merge( + of(this.findPanelIndex(ev.target as HTMLElement)), + fromEvent(this.el.nativeElement, 'mouseout').pipe( + mapTo(null), + ), + )), + debounceTime(20), + shareReplay(1), + ) + + const setupViewerApiSub = this.newViewer$.pipe( + tap(() => { + setViewerHandle(null) + }), + switchMap(this.waitForNehuba.bind(this)) + ).subscribe(() => { + setViewerHandle({ + setNavigationLoc : (coord, realSpace?) => this.nehubaContainerDirective.nehubaViewerInstance.setNavigationState({ + position : coord, + positionReal : typeof realSpace !== 'undefined' ? realSpace : true, + }), + /* TODO introduce animation */ + moveToNavigationLoc : (coord, realSpace?) => { + this.store$.dispatch( + viewerStateChangeNavigation({ + navigation: { + position: coord, + animation: {}, + } + }) + ) + }, + setNavigationOri : (quat) => this.nehubaContainerDirective.nehubaViewerInstance.setNavigationState({ + orientation : quat, + }), + /* TODO introduce animation */ + moveToNavigationOri : (quat) => this.nehubaContainerDirective.nehubaViewerInstance.setNavigationState({ + orientation : quat, + }), + showSegment : (_labelIndex) => { + /** + * TODO reenable with updated select_regions api + */ + this.log.warn(`showSegment is temporarily disabled`) + + // if(!this.selectedRegionIndexSet.has(labelIndex)) + // this.store.dispatch({ + // type : SELECT_REGIONS, + // selectRegions : [labelIndex, ...this.selectedRegionIndexSet] + // }) + }, + add3DLandmarks : landmarks => { + // TODO check uniqueness of ID + if (!landmarks.every(l => !!l.id)) { + throw new Error('every landmarks needs to be identified with the id field') + } + if (!landmarks.every(l => !!l.position)) { + throw new Error('every landmarks needs to have position defined') + } + if (!landmarks.every(l => l.position.constructor === Array) || !landmarks.every(l => l.position.every(v => !isNaN(v))) || !landmarks.every(l => l.position.length == 3)) { + throw new Error('position needs to be a length 3 tuple of numbers ') + } + + this.store$.dispatch(viewerStateAddUserLandmarks({ + landmarks + })) + }, + remove3DLandmarks : landmarkIds => { + this.store$.dispatch(viewreStateRemoveUserLandmarks({ + payload: { landmarkIds } + })) + }, + hideSegment : (_labelIndex) => { + /** + * TODO reenable with updated select_regions api + */ + this.log.warn(`hideSegment is temporarily disabled`) + + // if(this.selectedRegionIndexSet.has(labelIndex)){ + // this.store.dispatch({ + // type :SELECT_REGIONS, + // selectRegions : [...this.selectedRegionIndexSet].filter(num=>num!==labelIndex) + // }) + // } + }, + showAllSegments : () => { + const selectRegionIds = [] + this.multiNgIdsRegionsLabelIndexMap.forEach((map, ngId) => { + Array.from(map.keys()).forEach(labelIndex => { + selectRegionIds.push(serialiseParcellationRegion({ ngId, labelIndex })) + }) + }) + this.store$.dispatch(viewerStateSelectRegionWithIdDeprecated({ + selectRegionIds + })) + }, + hideAllSegments : () => { + this.store$.dispatch(viewerStateSelectRegionWithIdDeprecated({ + selectRegionIds: [] + })) + }, + segmentColourMap : new Map(), + getLayersSegmentColourMap: () => { + const newMainMap = new Map() + for (const [key, colormap] of this.nehubaContainerDirective.nehubaViewerInstance.multiNgIdColorMap.entries()) { + const newColormap = new Map() + newMainMap.set(key, newColormap) + + for (const [lableIndex, entry] of colormap.entries()) { + newColormap.set(lableIndex, JSON.parse(JSON.stringify(entry))) + } + } + return newMainMap + }, + applyColourMap : (_map) => { + throw new Error(`apply color map has been deprecated. use applyLayersColourMap instead`) + }, + applyLayersColourMap: (map) => { + this.nehubaContainerDirective.nehubaViewerInstance.setColorMap(map) + }, + loadLayer : (layerObj) => this.nehubaContainerDirective.nehubaViewerInstance.loadLayer(layerObj), + removeLayer : (condition) => this.nehubaContainerDirective.nehubaViewerInstance.removeLayer(condition), + setLayerVisibility : (condition, visible) => this.nehubaContainerDirective.nehubaViewerInstance.setLayerVisibility(condition, visible), + mouseEvent : merge( + fromEvent(this.el.nativeElement, 'click').pipe( + map((ev: MouseEvent) => ({eventName : 'click', event: ev})), + ), + fromEvent(this.el.nativeElement, 'mousemove').pipe( + map((ev: MouseEvent) => ({eventName : 'mousemove', event: ev})), + ), + /** + * neuroglancer prevents propagation, so use capture instead + */ + Observable.create(observer => { + this.el.nativeElement.addEventListener('mousedown', event => observer.next({eventName: 'mousedown', event}), true) + }) as Observable<{eventName: string, event: MouseEvent}>, + fromEvent(this.el.nativeElement, 'mouseup').pipe( + map((ev: MouseEvent) => ({eventName : 'mouseup', event: ev})), + ), + ) , + mouseOverNehuba : of(null).pipe( + tap(() => console.warn('mouseOverNehuba observable is becoming deprecated. use mouseOverNehubaLayers instead.')), + ), + mouseOverNehubaLayers: this.mouseoverDirective.currentOnHoverObs$.pipe( + map(({ segments }) => segments) + ), + mouseOverNehubaUI: this.mouseoverDirective.currentOnHoverObs$.pipe( + map(({ landmark, segments, userLandmark: customLandmark }) => ({ segments, landmark, customLandmark })), + shareReplay(1), + ), + getNgHash : this.nehubaContainerDirective.nehubaViewerInstance.getNgHash, + }) + }) + this.onDestroyCb.push(() => setupViewerApiSub.unsubscribe()) + + } + + handleViewerLoadedEvent(flag: boolean) { + this.viewerEvents$.next({ + type: 'VIEWERLOADED', + data: flag + }) + } + + private selectHoveredRegion(ev: any, next: Function){ + if (!this.onhoverSegments || !(this.onhoverSegments.length)) return next() + this.store$.dispatch( + viewerStateSetSelectedRegions({ + selectRegions: this.onhoverSegments.slice(0, 1) + }) + ) + next() + } + + private waitForNehuba(arg: unknown) { + return interval(16).pipe( + filter(() => !!(this.nehubaContainerDirective?.isReady())), + take(1), + mapTo(arg), + ) + } + + public setOctantRemoval(octantRemovalFlag: boolean) { + this.store$.dispatch( + ngViewerActionSetPerspOctantRemoval({ + octantRemovalFlag + }) + ) + } + + public toggleMaximiseMinimise(index: number) { + this.store$.dispatch(ngViewerActionToggleMax({ + payload: { index } + })) + } + + public zoomNgView(panelIndex: number, factor: number) { + const ngviewer = this.nehubaContainerDirective?.nehubaViewerInstance?.nehubaViewer?.ngviewer + if (!ngviewer) throw new Error(`ngviewer not defined!`) + + /** + * panelIndex < 3 === slice view + */ + if (panelIndex < 3) { + /** + * factor > 1 === zoom out + */ + ngviewer.navigationState.zoomBy(factor) + } else { + ngviewer.perspectiveNavigationState.zoomBy(factor) + } + } + + private removeExistingPanels() { + const element = this.nehubaContainerDirective.nehubaViewerInstance.nehubaViewer.ngviewer.layout.container.componentValue.element as HTMLElement + while (element.childElementCount > 0) { + element.removeChild(element.firstElementChild) + } + return element + } + + + public returnTruePos(quadrant: number, data: any) { + const pos = quadrant > 2 + ? [0, 0, 0] + : this.nanometersToOffsetPixelsFn && this.nanometersToOffsetPixelsFn[quadrant] + ? this.nanometersToOffsetPixelsFn[quadrant](data.geometry.position.map(n => n * 1e6)) + : [0, 0, 0] + return pos + } + + public getPositionX(quadrant: number, data: any) { + return this.returnTruePos(quadrant, data)[0] + } + public getPositionY(quadrant: number, data: any) { + return this.returnTruePos(quadrant, data)[1] + } + public getPositionZ(quadrant: number, data: any) { + return this.returnTruePos(quadrant, data)[2] + } + + public handleMouseEnterCustomLandmark(lm) { + this.store$.dispatch( + viewerStateMouseOverCustomLandmark({ + payload: { userLandmark: lm } + }) + ) + } + + public handleMouseLeaveCustomLandmark(lm) { + this.store$.dispatch( + viewerStateMouseOverCustomLandmark({ + payload: { userLandmark: null } + }) + ) + } + +} \ No newline at end of file diff --git a/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.style.css b/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.style.css new file mode 100644 index 0000000000000000000000000000000000000000..a03d05dd199a30e712eb7062ee253473ae894552 --- /dev/null +++ b/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.style.css @@ -0,0 +1,31 @@ + +.opacity-crossfade +{ + transition: opacity 170ms ease-in-out, + transform 250ms ease-in-out; +} + +.opacity-crossfade +{ + + opacity: 0.0; + pointer-events: none; +} + +.opacity-crossfade.onHover, +.opacity-crossfade:hover, +:host-context([ismobile="true"]) .opacity-crossfade.always-show-touchdevice +{ + opacity: 1.0 !important; + pointer-events: all !important; +} + +.screen-overlay +{ + background-color: rgba(255, 255, 255, 0.7); +} + +:host-context([darktheme="true"]) .screen-overlay +{ + background-color: rgba(0, 0, 0, 0.7); +} diff --git a/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.template.html b/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.template.html new file mode 100644 index 0000000000000000000000000000000000000000..45b20af1f7ffc74a0a2315ccd1e7643b07802ddc --- /dev/null +++ b/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.template.html @@ -0,0 +1,161 @@ +<div class="d-block" + iav-nehuba-viewer-container + iav-mouse-hover + (iavNehubaViewerContainerViewerLoading)="handleViewerLoadedEvent($event)"> + +</div> + +<current-layout *ngIf="viewerLoaded$ | async" class="position-absolute w-100 h-100 d-block pe-none top-0 left-0"> + <div class="w-100 h-100 position-relative" cell-i> + <ng-content *ngTemplateOutlet="ngPanelOverlayTmpl; context: { panelIndex: panelOrder$ | async | getNthElement : 0 | parseAsNumber }"></ng-content> + </div> + <div class="w-100 h-100 position-relative" cell-ii> + <ng-content *ngTemplateOutlet="ngPanelOverlayTmpl; context: { panelIndex: panelOrder$ | async | getNthElement : 1 | parseAsNumber }"></ng-content> + </div> + <div class="w-100 h-100 position-relative" cell-iii> + <ng-content *ngTemplateOutlet="ngPanelOverlayTmpl; context: { panelIndex: panelOrder$ | async | getNthElement : 2 | parseAsNumber }"></ng-content> + </div> + <div class="w-100 h-100 position-relative" cell-iv> + <ng-content *ngTemplateOutlet="ngPanelOverlayTmpl; context: { panelIndex: panelOrder$ | async | getNthElement : 3 | parseAsNumber }"></ng-content> + </div> +</current-layout> + +<!-- slice view overlay tmpl --> +<ng-template #ngPanelOverlayTmpl let-panelIndex="panelIndex"> + + <!-- perspective view tmpl --> + <ng-template #overlayPerspectiveTmpl> + <layout-floating-container landmarkContainer> + + <div class="d-flex flex-column justify-content-center align-items-center w-100 h-100 position-absolute opacity-crossfade screen-overlay pe-none" + [ngClass]="{onHover: !!(showPerpsectiveScreen$ | async)}" + [attr.id]="IDS.MESH_LOADING_STATUS" + role="status"> + + <spinner-cmp *ngIf="showPerpsectiveScreen$ | async"> + </spinner-cmp> + + <mat-list> + <mat-list-item> + {{ showPerpsectiveScreen$ | async }} + </mat-list-item> + </mat-list> + </div> + + <!-- mesh loading is still weird --> + <!-- if the precomputed server does not have the necessary fragment file, then the numberws will not collate --> + <div *ngIf="false && (perspectiveViewLoading$ | async)" class="loadingIndicator"> + <spinner-cmp></spinner-cmp> + + <div *ngIf="false" perspectiveLoadingText> + {{ perspectiveViewLoading$ | async }} + </div> + </div> + </layout-floating-container> + </ng-template> + + <iav-layout-fourcorners class="w-100 h-100 d-block"> + <layout-floating-container *ngIf="panelIndex < 3; else overlayPerspectiveTmpl" + class="overflow-hidden" + iavLayoutFourCornersContent> + <!-- TODO add landmarks here --> + + + <!-- customLandmarks --> + <!-- only show landmarks in slice views --> + <landmark-2d-flat-cmp *ngFor="let lm of (customLandmarks$ | async | filterByProperty : 'showInSliceView')" + (mouseenter)="handleMouseEnterCustomLandmark(lm)" + (mouseleave)="handleMouseLeaveCustomLandmark(lm)" + [color]="lm.color || [255, 255, 255]" + [positionX]="getPositionX(panelIndex, lm)" + [positionY]="getPositionY(panelIndex, lm)" + [positionZ]="getPositionZ(panelIndex, lm)"> + + </landmark-2d-flat-cmp> + </layout-floating-container> + + <!-- panel controller --> + <div iavLayoutFourCornersBottomRight> + + <ng-container *ngTemplateOutlet="panelCtrlTmpl; context: { + panelIndex: panelIndex, + visible: (hoveredPanelIndices$ | async) === panelIndex + }"> + </ng-container> + + <div *ngIf="(sliceViewLoadingMain$ | async)[panelIndex]"> + <spinner-cmp></spinner-cmp> + </div> + </div> + </iav-layout-fourcorners> + +</ng-template> + +<!-- panel control template --> +<ng-template + #panelCtrlTmpl + let-panelIndex="panelIndex" + let-visible="visible"> + + <div class="opacity-crossfade always-show-touchdevice pe-all overlay-btn-container" + [ngClass]="{ onHover: visible }" + [attr.data-viewer-controller-visible]="visible" + [attr.data-viewer-controller-index]="panelIndex"> + + <!-- perspective specific control --> + <ng-container *ngIf="panelIndex === 3"> + <ng-container *ngTemplateOutlet="perspectiveOctantRemovalTmpl; context: { + state: (nehubaViewerPerspectiveOctantRemoval$ | async), + disableOctantRemovalCtrl: disableOctantRemovalCtrl$ | async + }"> + + </ng-container> + </ng-container> + + <!-- factor < 1.0 === zoom in --> + <button mat-icon-button color="primary" + (click)="zoomNgView(panelIndex, 0.9)" + [attr.aria-label]="ARIA_LABELS.ZOOM_IN"> + <i class="fas fa-search-plus"></i> + </button> + + <!-- factor > 1.0 === zoom out --> + <button mat-icon-button color="primary" + (click)="zoomNgView(panelIndex, 1.1)" + [attr.aria-label]="ARIA_LABELS.ZOOM_OUT"> + <i class="fas fa-search-minus"></i> + </button> + + <maximise-panel-button + (click)="toggleMaximiseMinimise(panelIndex)" + [touch-side-class]="panelIndex"> + </maximise-panel-button> + </div> + +</ng-template> + +<!-- tmpl for toggling perspective frontal octant --> +<ng-template #perspectiveOctantRemovalTmpl + let-state="state" + let-disableOctantRemovalCtrl="disableOctantRemovalCtrl"> + <div class="d-inline-block" + [matTooltip]="disableOctantRemovalCtrl?.mode !== null ? disableOctantRemovalCtrl.message : null"> + <button + (click)="setOctantRemoval(!state)" + mat-icon-button + [disabled]="disableOctantRemovalCtrl?.mode !== null" + [attr.aria-label]="ARIA_LABELS.TOGGLE_FRONTAL_OCTANT" + color="primary"> + + <!-- octant removal is true --> + <ng-template [ngIf]="disableOctantRemovalCtrl?.mode !== null ? disableOctantRemovalCtrl.mode : state" [ngIfElse]="octantRemovalOffTmpl"> + <i class="fas fa-eye-slash"></i> + </ng-template> + + <!-- octant removal is false --> + <ng-template #octantRemovalOffTmpl> + <i class="fas fa-eye"></i> + </ng-template> + </button> + </div> +</ng-template> \ No newline at end of file diff --git a/src/ui/nehubaContainer/nehubaViewerInterface/nehubaViewerInterface.directive.spec.ts b/src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerInterface.directive.spec.ts similarity index 100% rename from src/ui/nehubaContainer/nehubaViewerInterface/nehubaViewerInterface.directive.spec.ts rename to src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerInterface.directive.spec.ts diff --git a/src/ui/nehubaContainer/nehubaViewerInterface/nehubaViewerInterface.directive.ts b/src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerInterface.directive.ts similarity index 98% rename from src/ui/nehubaContainer/nehubaViewerInterface/nehubaViewerInterface.directive.ts rename to src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerInterface.directive.ts index f4a1973ae240f4235f441159dc0892456c381f2f..e0f0678a4d20ff05f1dee4ce9f8d9d21e6b5e685 100644 --- a/src/ui/nehubaContainer/nehubaViewerInterface/nehubaViewerInterface.directive.ts +++ b/src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerInterface.directive.ts @@ -3,7 +3,7 @@ import { NehubaViewerUnit, INehubaLifecycleHook } from "../nehubaViewer/nehubaVi import { Store, select } from "@ngrx/store"; import { IavRootStoreInterface } from "src/services/stateStore.service"; import { Subscription, Observable, fromEvent } from "rxjs"; -import { distinctUntilChanged, filter, debounceTime, shareReplay, scan, map, throttleTime, switchMapTo } from "rxjs/operators"; +import { distinctUntilChanged, filter, debounceTime, scan, map, throttleTime, switchMapTo } from "rxjs/operators"; import { StateInterface as ViewerConfigStateInterface } from "src/services/state/viewerConfig.store"; import { getNavigationStateFromConfig, takeOnePipe } from "../util"; import { NEHUBA_LAYER_CHANGED, CHANGE_NAVIGATION } from "src/services/state/viewerState.store"; @@ -437,7 +437,7 @@ export class NehubaViewerContainerDirective implements OnInit, OnDestroy{ this.nehubaViewerInstance.mouseoverSegmentEmitter.pipe( scan(accumulatorFn, new Map()), - map(map => Array.from(map.entries()).filter(([_ngId, { segmentId }]) => segmentId)), + map((map: Map<string, any>) => Array.from(map.entries()).filter(([_ngId, { segmentId }]) => segmentId)), ).subscribe(arrOfArr => { this.store$.dispatch({ type: MOUSE_OVER_SEGMENTS, @@ -501,6 +501,10 @@ export class NehubaViewerContainerDirective implements OnInit, OnDestroy{ return this.cr && this.cr.instance } + isReady() { + return !!(this.cr?.instance?.nehubaViewer?.ngviewer) + } + /* because the navigation can be changed from two sources, either dynamically (e.g. navigation panel in the UI or plugins etc) or actively (via user interaction with the viewer) diff --git a/src/ui/nehubaContainer/nehubaViewerInterface/nehubaViewerTouch.directive.ts b/src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerTouch.directive.ts similarity index 100% rename from src/ui/nehubaContainer/nehubaViewerInterface/nehubaViewerTouch.directive.ts rename to src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerTouch.directive.ts diff --git a/src/ui/nehubaContainer/statusCard/statusCard.component.spec.ts b/src/viewerModule/nehuba/statusCard/statusCard.component.spec.ts similarity index 98% rename from src/ui/nehubaContainer/statusCard/statusCard.component.spec.ts rename to src/viewerModule/nehuba/statusCard/statusCard.component.spec.ts index 8db6d1a4e195ec1c5f7cb08a6b163c4773d232e6..bf9068df5e635e014f21a875fbd9a133cf5db5db 100644 --- a/src/ui/nehubaContainer/statusCard/statusCard.component.spec.ts +++ b/src/viewerModule/nehuba/statusCard/statusCard.component.spec.ts @@ -18,7 +18,7 @@ import * as util from '../util' import { viewerStateChangeNavigation } from "src/services/state/viewerState/actions" @Directive({ - selector: '[iav-auth-authState]', + selector: '[iav-auth-auth-state]', exportAs: 'iavAuthAuthState' }) @@ -107,7 +107,7 @@ describe('> statusCard.component.ts', () => { it('> takes into account of statusPanelRealSpace panel', () => { const setNavigationStateSpy = jasmine.createSpy('setNavigationState') fixture.componentInstance.nehubaViewer = { - setNavigationState: setNavigationStateSpy + setNavigationState: setNavigationStateSpy, } as any fixture.componentInstance.statusPanelRealSpace = true diff --git a/src/ui/nehubaContainer/statusCard/statusCard.component.ts b/src/viewerModule/nehuba/statusCard/statusCard.component.ts similarity index 68% rename from src/ui/nehubaContainer/statusCard/statusCard.component.ts rename to src/viewerModule/nehuba/statusCard/statusCard.component.ts index 8f14652f75394e561bf700fa7a26f006d1129da5..c4158fe7c92ff939940e84ddceb2eb6b5149fb2a 100644 --- a/src/ui/nehubaContainer/statusCard/statusCard.component.ts +++ b/src/viewerModule/nehuba/statusCard/statusCard.component.ts @@ -1,7 +1,6 @@ -import { Component, Input, OnInit, OnChanges, TemplateRef, HostBinding } from "@angular/core"; +import { Component, OnInit, OnChanges, TemplateRef, HostBinding, Optional, Inject } from "@angular/core"; import { select, Store } from "@ngrx/store"; import { LoggingService } from "src/logging"; -import { IavRootStoreInterface } from "src/services/stateStore.service"; import { NehubaViewerUnit } from "../nehubaViewer/nehubaViewer.component"; import { Observable, Subscription, of, combineLatest } from "rxjs"; import { map, filter, startWith } from "rxjs/operators"; @@ -11,18 +10,26 @@ import { ARIA_LABELS } from 'common/constants' import { PureContantService } from "src/util"; import { FormControl } from "@angular/forms"; import { viewerStateNavigationStateSelector, viewerStateSelectedTemplatePureSelector } from "src/services/state/viewerState/selectors"; -import { getNavigationStateFromConfig } from "../util"; + import { viewerStateChangeNavigation } from "src/services/state/viewerState/actions"; +import { getNavigationStateFromConfig, NEHUBA_INSTANCE_INJTKN } from '../util' @Component({ - selector : 'ui-status-card', + selector : 'iav-cmp-viewer-nehuba-status', templateUrl : './statusCard.template.html', styleUrls : ['./statusCard.style.css'], }) export class StatusCardComponent implements OnInit, OnChanges{ - @Input() public selectedTemplateName: string; - @Input() public nehubaViewer: NehubaViewerUnit; + private _nehubaViewer: NehubaViewerUnit; + + get nehubaViewer(){ + return this._nehubaViewer + } + set nehubaViewer(v: NehubaViewerUnit) { + this._nehubaViewer = v + this.ngOnChanges() + } @HostBinding('attr.aria-label') public arialabel = ARIA_LABELS.STATUS_PANEL @@ -44,13 +51,24 @@ export class StatusCardComponent implements OnInit, OnChanges{ public SHOW_FULL_STATUS_PANEL_ARIA_LABEL = ARIA_LABELS.SHOW_FULL_STATUS_PANEL public HIDE_FULL_STATUS_PANEL_ARIA_LABEL = ARIA_LABELS.HIDE_FULL_STATUS_PANEL constructor( - private store$: Store<IavRootStoreInterface>, + private store$: Store<any>, private log: LoggingService, private bottomSheet: MatBottomSheet, private dialog: MatDialog, - private pureConstantService: PureContantService + //private pureConstantService: PureContantService, + @Optional() @Inject(NEHUBA_INSTANCE_INJTKN) nehubaViewer$: Observable<NehubaViewerUnit> ) { - this.useTouchInterface$ = this.pureConstantService.useTouchUI$ + this.useTouchInterface$ = of(true) //this.pureConstantService.useTouchUI$ + + if (nehubaViewer$) { + this.subscriptions.push( + nehubaViewer$.subscribe( + viewer => this.nehubaViewer = viewer + ) + ) + } else { + this.log.warn(`NEHUBA_INSTANCE_INJTKN not injected!`) + } } ngOnInit(): void { @@ -74,40 +92,45 @@ export class StatusCardComponent implements OnInit, OnChanges{ } ngOnChanges() { - if (!this.nehubaViewer) { + if (this.nehubaViewer?.viewerPosInReal$ && this.nehubaViewer?.viewerPosInVoxel$) { + this.navVal$ = combineLatest([ + this.statusPanelRealSpace$, + this.nehubaViewer.viewerPosInReal$.pipe( + filter(v => !!v) + ), + this.nehubaViewer.viewerPosInVoxel$.pipe( + filter(v => !!v) + ) + ]).pipe( + map(([realFlag, real, voxel]) => realFlag + ? real.map(v => `${ (v / 1e6).toFixed(3) }mm`).join(', ') + : voxel.map(v => v.toFixed(3)).join(', ') ), + startWith(`nehubaViewer initialising`) + ) + } else { this.navVal$ = of(`neubaViewer is undefined`) - this.mouseVal$ = of(`neubaViewer is undefined`) - return } - this.navVal$ = combineLatest([ - this.statusPanelRealSpace$, - this.nehubaViewer.viewerPosInReal$.pipe( - filter(v => !!v) - ), - this.nehubaViewer.viewerPosInVoxel$.pipe( - filter(v => !!v) - ) - ]).pipe( - map(([realFlag, real, voxel]) => realFlag - ? real.map(v => `${ (v / 1e6).toFixed(3) }mm`).join(', ') - : voxel.map(v => v.toFixed(3)).join(', ') ), - startWith(`nehubaViewer initialising`) - ) - this.mouseVal$ = combineLatest([ - this.statusPanelRealSpace$, - this.nehubaViewer.mousePosInReal$.pipe( - filter(v => !!v) - ), - this.nehubaViewer.mousePosInVoxel$.pipe( - filter(v => !!v) + if ( this.nehubaViewer?.mousePosInReal$ && this.nehubaViewer?.mousePosInVoxel$ ) { + + this.mouseVal$ = combineLatest([ + this.statusPanelRealSpace$, + this.nehubaViewer.mousePosInReal$.pipe( + filter(v => !!v) + ), + this.nehubaViewer.mousePosInVoxel$.pipe( + filter(v => !!v) + ) + ]).pipe( + map(([realFlag, real, voxel]) => realFlag + ? real.map(v => `${ (v/1e6).toFixed(3) }mm`).join(', ') + : voxel.map(v => v.toFixed(3)).join(', ')), + startWith(``) ) - ]).pipe( - map(([realFlag, real, voxel]) => realFlag - ? real.map(v => `${ (v/1e6).toFixed(3) }mm`).join(', ') - : voxel.map(v => v.toFixed(3)).join(', ')), - startWith(``) - ) + } else { + this.mouseVal$ = of(`neubaViewer is undefined`) + } + } public statusPanelFormCtrl = new FormControl(true, []) diff --git a/src/ui/nehubaContainer/statusCard/statusCard.style.css b/src/viewerModule/nehuba/statusCard/statusCard.style.css similarity index 100% rename from src/ui/nehubaContainer/statusCard/statusCard.style.css rename to src/viewerModule/nehuba/statusCard/statusCard.style.css diff --git a/src/ui/nehubaContainer/statusCard/statusCard.template.html b/src/viewerModule/nehuba/statusCard/statusCard.template.html similarity index 99% rename from src/ui/nehubaContainer/statusCard/statusCard.template.html rename to src/viewerModule/nehuba/statusCard/statusCard.template.html index 17d4fe7cfd9f6c5c18cedd1f3471b320934b1e62..0489d088a7818527404627c3f230661d3dd30408 100644 --- a/src/ui/nehubaContainer/statusCard/statusCard.template.html +++ b/src/viewerModule/nehuba/statusCard/statusCard.template.html @@ -170,7 +170,7 @@ </h2> <div mat-dialog-content> - <div iav-auth-authState + <div iav-auth-auth-state #authState="iavAuthAuthState"> <!-- Logged in. Explain that links will not expire, offer to logout --> diff --git a/src/viewerModule/nehuba/store.ts b/src/viewerModule/nehuba/store.ts new file mode 100644 index 0000000000000000000000000000000000000000..f00a217031ac76ef8bb50ba0a7e30b0e47963e22 --- /dev/null +++ b/src/viewerModule/nehuba/store.ts @@ -0,0 +1,36 @@ +import { createReducer } from "@ngrx/store"; +import { INgLayerInterface } from "src/services/state/ngViewerState.store"; + + +/** + * TODO port from global store to feature store + */ + +enum EnumPanelMode { + FOUR_PANEL = 'FOUR_PANEL', + V_ONE_THREE = 'V_ONE_THREE', + H_ONE_THREE = 'H_ONE_THREE', + SINGLE_PANEL = 'SINGLE_PANEL', +} + +interface INehubaFeature { + layers: INgLayerInterface[] + panelMode: string + panelOrder: string + octantRemoval: boolean + clearViewQueue: { + [key: string]: boolean + } +} + +const defaultState: INehubaFeature = { + layers: [], + panelMode: EnumPanelMode.FOUR_PANEL, + panelOrder: '0123', + octantRemoval: true, + clearViewQueue: {} +} + +export const reducer = createReducer( + defaultState +) \ No newline at end of file diff --git a/src/ui/nehubaContainer/touchSideClass.directive.ts b/src/viewerModule/nehuba/touchSideClass.directive.ts similarity index 93% rename from src/ui/nehubaContainer/touchSideClass.directive.ts rename to src/viewerModule/nehuba/touchSideClass.directive.ts index edaa9823ff6af39756e0f51e16c91cee40d91022..44cdac937990acc2b79bb03a70d545fb8030af1b 100644 --- a/src/ui/nehubaContainer/touchSideClass.directive.ts +++ b/src/viewerModule/nehuba/touchSideClass.directive.ts @@ -4,7 +4,8 @@ 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 { addTouchSideClasses, removeTouchSideClasses } from "./util"; +import { addTouchSideClasses, removeTouchSideClasses } from "src/viewerModule/nehuba/util"; + @Directive({ selector: '[touch-side-class]', diff --git a/src/ui/nehubaContainer/util.ts b/src/viewerModule/nehuba/util.ts similarity index 96% rename from src/ui/nehubaContainer/util.ts rename to src/viewerModule/nehuba/util.ts index 49d7136412e6d19d67bdf6774d7e6f81e9832988..3849713a6681f396d24ab5daf804351c479462b8 100644 --- a/src/ui/nehubaContainer/util.ts +++ b/src/viewerModule/nehuba/util.ts @@ -1,7 +1,9 @@ -import { pipe } from 'rxjs' +import { InjectionToken } from '@angular/core' +import { Observable, pipe } from 'rxjs' import { filter, scan, take } from 'rxjs/operators' import { PANELS } from 'src/services/state/ngViewerState.store.helper' import { getViewer } from 'src/util/fn' +import { NehubaViewerUnit } from './nehubaViewer/nehubaViewer.component' const flexContCmnCls = ['w-100', 'h-100', 'd-flex', 'justify-content-center', 'align-items-stretch'] @@ -285,3 +287,5 @@ export const takeOnePipe = () => { take(1), ) } + +export const NEHUBA_INSTANCE_INJTKN = new InjectionToken<Observable<NehubaViewerUnit>>('NEHUBA_INSTANCE_INJTKN') \ No newline at end of file diff --git a/src/ui/util.ts b/src/viewerModule/util/regionAccordionTooltipText.pipe.ts similarity index 100% rename from src/ui/util.ts rename to src/viewerModule/util/regionAccordionTooltipText.pipe.ts diff --git a/src/viewerModule/viewer.interface.ts b/src/viewerModule/viewer.interface.ts new file mode 100644 index 0000000000000000000000000000000000000000..4e4eada8942a3b563f036b4566b73e42be452d3e --- /dev/null +++ b/src/viewerModule/viewer.interface.ts @@ -0,0 +1,48 @@ +import { Observable } from "rxjs"; + +type TLayersColorMap = Map<string, Map<number, { red: number, green: number, blue: number }>> + +interface IViewerCtrl { + + // navigation control + setNavigationLoc(coord: number[], realSpace?: boolean): void + moveToNavigationLoc(coord: number[], realSpace?: boolean): void + setNavigationOri(quat: number[]): void + moveToNavigationOri(quat: number[]): void + + // segment control + showSegment(segment: any): void + hideSegment(segment: any): void + showAllSegments(): void + hideAllSegments(): void + + // landmark control + addLandmarks(landmarks: any[]): void + removeLandmarks(landmarks: any[]): void + + // layer control + addLayer(layerSpec: any): void + removeLayer(layerId: string): void + applyLayersColourMap(map: TLayersColorMap): void + getLayersColourMap(): TLayersColorMap +} + +type TViewerEventMOAnno = { + type: "MOUSEOVER_ANNOTATION" + data: any +} + +type TViewerEventViewerLoaded = { + type: "VIEWERLOADED" + data: boolean +} + +export type TViewerEvent = TViewerEventMOAnno | TViewerEventViewerLoaded + +export type IViewer = { + + selectedTemplate: any + selectedParcellation: any + viewerCtrlHandler?: IViewerCtrl + viewerEvents$: Observable<TViewerEvent> +} \ No newline at end of file diff --git a/src/viewerModule/viewerCmp/viewerCmp.component.ts b/src/viewerModule/viewerCmp/viewerCmp.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..82d5bf98ea4e58e5b59dd05a47b41a30730167c1 --- /dev/null +++ b/src/viewerModule/viewerCmp/viewerCmp.component.ts @@ -0,0 +1,232 @@ +import { AfterViewInit, Component, Inject, Input, OnDestroy, Optional, ViewChild } from "@angular/core"; +import { select, Store } from "@ngrx/store"; +import { combineLatest, Observable, Subject, Subscription } from "rxjs"; +import { distinctUntilChanged, filter, map, startWith } from "rxjs/operators"; +import { viewerStateHelperSelectParcellationWithId, viewerStateRemoveAdditionalLayer, viewerStateSetSelectedRegions } from "src/services/state/viewerState/actions"; +import { viewerStateGetOverlayingAdditionalParcellations, viewerStateParcVersionSelector, viewerStateSelectedParcellationSelector, viewerStateSelectedRegionsSelector, viewerStateSelectedTemplateSelector, viewerStateStandAloneVolumes } from "src/services/state/viewerState/selectors" +import { NehubaGlueCmp } from "../nehuba"; +import { IViewer } from "../viewer.interface"; +import { CONST, ARIA_LABELS } from 'common/constants' +import { ngViewerActionClearView } from "src/services/state/ngViewerState/actions"; +import { ngViewerSelectorClearViewEntries } from "src/services/state/ngViewerState/selectors"; +import { uiActionHideAllDatasets, uiActionHideDatasetWithId } from "src/services/state/uiState/actions"; +import { REGION_OF_INTEREST } from "src/util/interfaces"; +import { animate, state, style, transition, trigger } from "@angular/animations"; +import { SwitchDirective } from "src/util/directives/switch.directive"; + +@Component({ + selector: 'iav-cmp-viewer-container', + templateUrl: './viewerCmp.template.html', + styleUrls: [ + './viewerCmp.style.css' + ], + exportAs: 'iavCmpViewerCntr', + animations: [ + trigger('openClose', [ + state('open', style({ + transform: 'translateY(0)', + opacity: 1 + })), + state('closed', style({ + transform: 'translateY(-100vh)', + opacity: 0 + })), + transition('open => closed', [ + animate('200ms cubic-bezier(0.35, 0, 0.25, 1)') + ]), + transition('closed => open', [ + animate('200ms cubic-bezier(0.35, 0, 0.25, 1)') + ]) + ]), + trigger('openCloseAnchor', [ + state('open', style({ + transform: 'translateY(0)' + })), + state('closed', style({ + transform: 'translateY(100vh)' + })), + transition('open => closed', [ + animate('200ms cubic-bezier(0.35, 0, 0.25, 1)') + ]), + transition('closed => open', [ + animate('200ms cubic-bezier(0.35, 0, 0.25, 1)') + ]) + ]), + ], + providers: [ + { + provide: REGION_OF_INTEREST, + useFactory: (store: Store<any>) => store.pipe( + select(viewerStateSelectedRegionsSelector), + map(rs => rs[0] || null) + ), + deps: [ + Store + ] + } + ] +}) + +export class ViewerCmp implements OnDestroy, AfterViewInit{ + + public CONST = CONST + public ARIA_LABELS = ARIA_LABELS + + @ViewChild(NehubaGlueCmp) viewerCmp: IViewer + @ViewChild('sideNavTopSwitch', { static: true }) + private sidenavTopSwitch: SwitchDirective + + @ViewChild('sideNavFullLeftSwitch', { static: true }) + private sidenavLeftSwitch: SwitchDirective + + @Input() ismobile = false + + private subscriptions: Subscription[] = [] + public viewerLoaded: boolean = false + + public templateSelected$ = this.store$.pipe( + select(viewerStateSelectedTemplateSelector), + distinctUntilChanged(), + ) + public parcellationSelected$ = this.store$.pipe( + select(viewerStateSelectedParcellationSelector), + distinctUntilChanged(), + ) + + public selectedRegions$ = this.store$.pipe( + select(viewerStateSelectedRegionsSelector), + distinctUntilChanged(), + ) + + public isStandaloneVolumes$ = this.store$.pipe( + select(viewerStateStandAloneVolumes), + map(v => v.length > 0) + ) + + public selectedLayerVersions$ = this.store$.pipe( + select(viewerStateParcVersionSelector), + map(arr => arr.map(item => { + const overwrittenName = item['@version'] && item['@version']['name'] + return overwrittenName + ? { ...item, displayName: overwrittenName } + : item + })) + ) + + public selectedAdditionalLayers$ = this.store$.pipe( + select(viewerStateGetOverlayingAdditionalParcellations), + ) + + public clearViewKeys$ = this.store$.pipe( + select(ngViewerSelectorClearViewEntries) + ) + + /** + * TODO may need to be deprecated + * in favour of regional feature/data feature + */ + public iavAdditionalLayers$ = new Subject<any[]>() + + /** + * if no regions are selected, nor any additional layers (being deprecated) + * then the "explore" btn should not show + * and the full left side bar should not be expandable + * if it is already expanded, it should collapse + */ + public alwaysHideMinorPanel$: Observable<boolean> = combineLatest([ + this.selectedRegions$, + this.iavAdditionalLayers$.pipe( + startWith([]) + ) + ]).pipe( + map(([ regions, layers ]) => regions.length === 0 && layers.length === 0) + ) + + constructor( + private store$: Store<any>, + @Optional() @Inject(REGION_OF_INTEREST) public regionOfInterest$: Observable<any> + ){ + this.subscriptions.push( + this.alwaysHideMinorPanel$.pipe( + distinctUntilChanged(), + filter(flag => !flag), + ).subscribe(() => { + this.openSideNavs() + }) + ) + } + + ngAfterViewInit(){ + this.subscriptions.push( + this.viewerCmp.viewerEvents$.pipe( + filter(ev => ev.type === 'VIEWERLOADED'), + ).subscribe(ev => { + this.viewerLoaded = ev.data + }) + ) + } + + ngOnDestroy() { + while (this.subscriptions.length) this.subscriptions.pop().unsubscribe() + } + + + public bindFns(fns){ + return () => { + for (const [ fn, ...arg] of fns) { + fn(...arg) + } + } + } + + public clearAdditionalLayer(layer: { ['@id']: string }){ + this.store$.dispatch( + viewerStateRemoveAdditionalLayer({ + payload: layer + }) + ) + } + + public clearSelectedRegions(){ + this.store$.dispatch( + viewerStateSetSelectedRegions({ + selectRegions: [] + }) + ) + } + + public selectParcellation(parc: any) { + this.store$.dispatch( + viewerStateHelperSelectParcellationWithId({ + payload: parc + }) + ) + } + + public handleChipClick(){ + this.openSideNavs() + } + + private openSideNavs() { + this.sidenavLeftSwitch && this.sidenavLeftSwitch.open() + this.sidenavTopSwitch && this.sidenavTopSwitch.open() + } + + public unsetClearViewByKey(key: string){ + this.store$.dispatch( + ngViewerActionClearView({ payload: { + [key]: false + }}) + ) + } + public clearPreviewingDataset(id: string){ + /** + * clear all preview + */ + this.store$.dispatch( + id + ? uiActionHideDatasetWithId({ id }) + : uiActionHideAllDatasets() + ) + } +} \ No newline at end of file diff --git a/src/viewerModule/viewerCmp/viewerCmp.style.css b/src/viewerModule/viewerCmp/viewerCmp.style.css new file mode 100644 index 0000000000000000000000000000000000000000..a3960f1d7ea97425a512e78a0d7396801e28cf8b --- /dev/null +++ b/src/viewerModule/viewerCmp/viewerCmp.style.css @@ -0,0 +1,42 @@ +:host +{ + position: relative; +} + +.explore-btn +{ + margin-top: 4.5rem; +} + +.region-text-search-autocomplete-position +{ + z-index: 10; + position: sticky; + top: 0.5rem; +} + +.tab-toggle-container +{ + margin-top: 1.5rem; +} + + +.tab-toggle +{ + margin-left: -2rem; + padding-right: 0.75rem; + margin-right: 1rem; + text-align: right; +} + +.accordion-icon +{ + width:1.5rem; +} + +.collapse-position +{ + z-index: 10; + position: sticky; + bottom: 0.5rem; +} diff --git a/src/viewerModule/viewerCmp/viewerCmp.template.html b/src/viewerModule/viewerCmp/viewerCmp.template.html new file mode 100644 index 0000000000000000000000000000000000000000..2df56a69fd5e00480757e94a6514be539c574790 --- /dev/null +++ b/src/viewerModule/viewerCmp/viewerCmp.template.html @@ -0,0 +1,946 @@ +<div class="position-absolute w-100 h-100"> + <ng-container *ngTemplateOutlet="viewerTmpl"> + </ng-container> +</div> + +<layout-floating-container [zIndex]="10"> + + <!-- top drawer --> + <mat-drawer-container + [iav-switch-initstate]="false" + iav-switch + #sideNavTopSwitch="iavSwitch" + class="mat-drawer-content-overflow-visible w-100 h-100 position-absolute invisible" + [hasBackdrop]="false"> + + <!-- sidenav-content --> + + <!-- (closedStart)="sideNavFullLeftSwitch.switchState && matDrawerLeft.close()" + (openedStart)="sideNavFullLeftSwitch.switchState && matDrawerLeft.open()" --> + <mat-drawer class="box-shadow-none border-0 pe-none bg-none col-10 col-sm-10 col-md-5 col-lg-4 col-xl-3 col-xxl-2" + mode="side" + [attr.data-mat-drawer-top-open]="matDrawerTop.opened" + [opened]="sideNavTopSwitch.switchState" + [autoFocus]="false" + [disableClose]="true" + #matDrawerTop="matDrawer"> + + <div class="h-0 w-100 region-text-search-autocomplete-position"> + <ng-container *ngTemplateOutlet="autocompleteTmpl"> + </ng-container> + </div> + + <button mat-raised-button + *ngIf="!(alwaysHideMinorPanel$ | async)" + [attr.aria-label]="ARIA_LABELS.EXPAND" + (click)="sideNavFullLeftSwitch && sideNavFullLeftSwitch.open()" + class="explore-btn pe-all w-100" + [ngClass]="{ + 'darktheme': iavRegion.rgbDarkmode === true, + 'lighttheme': iavRegion.rgbDarkmode === false + }" + [style.backgroundColor]="iavRegion?.rgbString || 'accent'"> + <span class="text iv-custom-comp"> + Explore + </span> + + <div class="hidden" + iav-region + [region]="(selectedRegions$ | async) && (selectedRegions$ | async)[0]" + #iavRegion="iavRegion"> + </div> + + </button> + </mat-drawer> + + <mat-drawer-content class="visible position-relative pe-none"> + + <iav-layout-fourcorners> + + <!-- pullable tab top right corner --> + <div iavLayoutFourCornersTopLeft class="d-inline-flex"> + + <div *ngIf="viewerLoaded" + class="pe-all tab-toggle-container" + (click)="sideNavTopSwitch && sideNavTopSwitch.toggle()"> + <ng-container *ngTemplateOutlet="tabTmpl; context: { + isOpen: sideNavTopSwitch.switchState, + regionSelected: selectedRegions$ | async, + iavAdditionallayers: iavAdditionalLayers$ | async + }"> + </ng-container> + </div> + + <iav-cmp-viewer-nehuba-status *ngIf="viewerLoaded" + class="pe-all mt-2 muted-7"> + </iav-cmp-viewer-nehuba-status> + </div> + + <!-- signin banner at top right corner --> + <div iavLayoutFourCornersTopRight + class="d-inline-flex align-items-start"> + + <top-menu-cmp class="mt-3 mr-2 d-inline-block" + [ismobile]="ismobile" + [viewerLoaded]="viewerLoaded"> + </top-menu-cmp> + + <div *ngIf="viewerLoaded" + class="iv-custom-comp bg card m-2 mat-elevation-z2"> + <atlas-dropdown-selector class="pe-all mt-2"> + </atlas-dropdown-selector> + </div> + </div> + </iav-layout-fourcorners> + + </mat-drawer-content> + </mat-drawer-container> + + <!-- full left drawer --> + <mat-drawer-container + [iav-switch-initstate]="!(alwaysHideMinorPanel$ | async)" + iav-switch + #sideNavFullLeftSwitch="iavSwitch" + class="mat-drawer-content-overflow-visible w-100 h-100 position-absolute invisible" + [hasBackdrop]="false"> + + <!-- sidenav-content --> + <mat-drawer class="darker-bg iv-custom-comp visible col-10 col-sm-10 col-md-5 col-lg-4 col-xl-3 col-xxl-2 d-flex flex-column pe-all" + mode="push" + [opened]="sideNavTopSwitch.switchState && sideNavFullLeftSwitch.switchState" + [attr.data-mat-drawer-fullleft-open]="matDrawerLeft.opened" + [autoFocus]="false" + #matDrawerLeft="matDrawer" + (openedChange)="$event && sideNavFullLeftSwitch.open()" + [@openClose]="sideNavTopSwitch.switchState && sideNavFullLeftSwitch.switchState ? 'open' : 'closed'" + (@openClose.done)="$event.toState === 'closed' && matDrawerLeft.close()" + [disableClose]="true"> + + <div class="position-relative d-flex flex-column h-100"> + + <!-- TODO dataset preview will become deprecated in the future. + Regional feature/data feature will replace it --> + + <div class="hidden" + iav-shown-dataset + #iavShownDataset="iavShownDataset"> + </div> + + <div class="hidden" + iav-shown-previews + (emitter)="iavAdditionalLayers$.next($event)" + #previews="iavShownPreviews"> + </div> + + <!-- sidenav datasets --> + <ng-container *ngIf="iavShownDataset.shownDatasetId$ | async as shownDatasetId"> + <ng-template [ngIf]="shownDatasetId.length > 0" [ngIfElse]="sideNavVolumePreview"> + + <!-- single dataset side nav panel --> + <single-dataset-sidenav-view *ngFor="let id of shownDatasetId" + (clear)="clearPreviewingDataset(id)" + [fullId]="id" + class="bs-border-box ml-15px-n mr-15px-n"> + <mat-chip *ngIf="regionOfInterest$ && regionOfInterest$ | async as region" + region-of-interest + iav-region + [region]="region" + [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"> + {{ region.name }} + </span> + </mat-chip> + </single-dataset-sidenav-view> + </ng-template> + </ng-container> + + <!-- preview volumes --> + <ng-template #sideNavVolumePreview> + <ng-container *ngIf="previews.iavAdditionalLayers$ | async | filterPreviewByType : [previews.FILETYPES.VOLUMES] as volumePreviews"> + <ng-template [ngIf]="volumePreviews.length > 0" [ngIfElse]="sidenavRegionTmpl"> + <ng-container *ngFor="let vPreview of volumePreviews"> + <ng-container *ngTemplateOutlet="sidenavDsPreviewTmpl; context: vPreview"> + + </ng-container> + </ng-container> + </ng-template> + </ng-container> + </ng-template> + + </div> + </mat-drawer> + + <!-- main-content --> + <mat-drawer-content class="visible position-relative"> + + <iav-layout-fourcorners> + + <!-- bottom left corner (atlas selector and currently selected) --> + <div iavLayoutFourCornersBottomLeft class="d-inline-flex align-items-center mb-4 ml-2"> + + <!-- atlas selector --> + <atlas-layer-selector *ngIf="viewerLoaded && !(isStandaloneVolumes$ | async)" + #alSelector="atlasLayerSelector" + (iav-outsideClick)="alSelector.selectorExpanded = false"> + </atlas-layer-selector> + + <!-- chips --> + <mat-chip-list> + <!-- additional layer --> + + <ng-container> + <ng-container *ngTemplateOutlet="currParcellationTmpl; context: { addParc: (selectedAdditionalLayers$ | async), parc: (parcellationSelected$ | async) }"> + </ng-container> + </ng-container> + + <!-- any selected region(s) --> + <ng-container> + <ng-container *ngTemplateOutlet="selectedRegionTmpl"> + </ng-container> + </ng-container> + + <!-- controls for iav volumes --> + <div class="hidden" iav-shown-previews #previews="iavShownPreviews"></div> + <ng-container *ngTemplateOutlet="selectedDatasetPreview; context: { layers: previews.iavAdditionalLayers$ | async | filterPreviewByType : [previews.FILETYPES.VOLUMES] }"> + </ng-container> + + </mat-chip-list> + </div> + + </iav-layout-fourcorners> + + </mat-drawer-content> + </mat-drawer-container> + +</layout-floating-container> + +<!-- viewer tmpl --> +<ng-template #viewerTmpl> + + <iav-layout-fourcorners> + <div iavLayoutFourCornersContent + class="w-100 h-100 position-absolute"> + <ui-splashscreen *ngIf="!viewerLoaded" + class="position-absolute left-0 top-0"> + </ui-splashscreen> + <div class="h-100 w-100 overflow-hidden position-relative" + [ngClass]="{'pe-none': !viewerLoaded}"> + <iav-cmp-viewer-nehuba-glue class="d-block w-100 h-100 position-absolute left-0 top-0" + [selectedTemplate]="templateSelected$ | async" + [selectedParcellation]="parcellationSelected$ | async" + #iavCmpViewerNehubaGlue="iavCmpViewerNehubaGlue"> + </iav-cmp-viewer-nehuba-glue> + + </div> + </div> + </iav-layout-fourcorners> +</ng-template> + +<!-- parcellation chip / region chip --> +<!-- TODO maybe migrate to atlas cmp... later? --> +<ng-template #currParcellationTmpl let-parc="parc" let-addParc="addParc"> + <div [matMenuTriggerFor]="layerVersionMenu" + [matMenuTriggerData]="{ layerVersionMenuTrigger: layerVersionMenuTrigger }" + #layerVersionMenuTrigger="matMenuTrigger"> + + <ng-template [ngIf]="addParc.length > 0" [ngIfElse]="defaultParcTmpl"> + <ng-container *ngFor="let p of addParc"> + <ng-container *ngTemplateOutlet="chipTmpl; context: { + parcel: p, + selected: true, + dismissable: true, + onclick: layerVersionMenuTrigger.toggleMenu.bind(layerVersionMenuTrigger) + }"> + </ng-container> + </ng-container> + </ng-template> + <ng-template #defaultParcTmpl> + <ng-container *ngTemplateOutlet="chipTmpl; context: { + parcel: parc, + selected: false, + dismissable: false, + onclick: layerVersionMenuTrigger.toggleMenu.bind(layerVersionMenuTrigger) + }"> + </ng-container> + </ng-template> + </div> +</ng-template> + + +<!-- layer version selector --> +<mat-menu #layerVersionMenu + class="bg-none box-shadow-none" + [hasBackdrop]="false"> + <ng-template matMenuContent let-layerVersionMenuTrigger="layerVersionMenuTrigger"> + <div (iav-outsideClick)="layerVersionMenuTrigger.closeMenu()"> + <ng-container *ngFor="let parcVer of selectedLayerVersions$ | async"> + <ng-container *ngIf="parcellationSelected$ | async as selectedParcellation"> + + <ng-container *ngTemplateOutlet="chipTmpl; context: { + parcel: parcVer, + selected: selectedParcellation['@id'] === parcVer['@id'], + dismissable: false, + class: 'w-100', + onclick: bindFns([ + [ selectParcellation.bind(this), parcVer ], + [ layerVersionMenuTrigger.closeMenu.bind(layerVersionMenuTrigger) ] + ]) + }"> + </ng-container> + </ng-container> + <div class="mt-1"></div> + </ng-container> + </div> + </ng-template> +</mat-menu> + +<!-- chip tmpl --> +<ng-template #chipTmpl + let-parcel="parcel" + let-selected="selected" + let-dismissable="dismissable" + let-chipClass="class" + let-onclick="onclick"> + <mat-chip class="pe-all position-relative z-index-2 d-inline-flex justify-content-between" + [ngClass]="chipClass" + (click)="onclick && onclick()" + [selected]="selected"> + + <span> + {{ parcel?.groupName ? (parcel?.groupName + ' - ') : '' }}{{ parcel && (parcel.displayName || parcel.name) }} + </span> + + <!-- info icon --> + <ng-template [ngIf]="parcel?.originDatasets?.length > 0" [ngIfElse]="infoIconBasic"> + + <mat-icon + *ngFor="let ds of parcel.originDatasets" + fontSet="fas" + fontIcon="fa-info-circle" + iav-stop="click" + iav-dataset-show-dataset-dialog + [iav-dataset-show-dataset-dialog-kgid]="ds['kgId']" + [iav-dataset-show-dataset-dialog-kgschema]="ds['kgSchema']" + [iav-dataset-show-dataset-dialog-name]="parcel?.properties?.name" + [iav-dataset-show-dataset-dialog-description]="parcel?.properties?.description"> + </mat-icon> + + </ng-template> + + <ng-template #infoIconBasic> + <mat-icon *ngIf="parcel?.properties?.name && parcel?.properties?.description" + fontSet="fas" + fontIcon="fa-info-circle" + iav-stop="click" + iav-dataset-show-dataset-dialog + [iav-dataset-show-dataset-dialog-name]="parcel.properties.name" + [iav-dataset-show-dataset-dialog-description]="parcel.properties.description"> + + </mat-icon> + </ng-template> + + <!-- dismiss icon --> + <mat-icon + *ngIf="dismissable" + (click)="clearAdditionalLayer(parcel); $event.stopPropagation()" + fontSet="fas" + fontIcon="fa-times"> + </mat-icon> + </mat-chip> +</ng-template> + + +<ng-template #selectedRegionTmpl> + + <!-- regions chip --> + <ng-template [ngIf]="selectedRegions$ | async" let-selectedRegions="ngIf"> + <!-- if regions.length > 1 --> + <!-- use group chip --> + <ng-template [ngIf]="selectedRegions.length > 1" [ngIfElse]="singleRegionChipTmpl"> + <mat-chip + color="primary" + selected + (click)="handleChipClick()" + class="pe-all position-relative z-index-1 ml-8-n"> + <span class="iv-custom-comp text text-truncate d-inline pl-4"> + {{ CONST.MULTI_REGION_SELECTION }} + </span> + <mat-icon + (click)="clearSelectedRegions()" + fontSet="fas" + iav-stop="click" + fontIcon="fa-times"> + </mat-icon> + </mat-chip> + </ng-template> + + <!-- if reginos.lengt === 1 --> + <!-- use single region chip --> + <ng-template #singleRegionChipTmpl> + <ng-container *ngFor="let r of selectedRegions"> + + <!-- region chip for discrete map --> + <mat-chip + iav-region + (click)="handleChipClick()" + [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" + #regionDirective="iavRegion"> + <span class="iv-custom-comp text text-truncate d-inline 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> + + <!-- chips for previewing origin datasets/continous map --> + <ng-container *ngFor="let originDataset of (r.originDatasets || []); let index = index"> + <div class="hidden" + iav-dataset-preview-dataset-file + [iav-dataset-preview-dataset-file-kgid]="originDataset.kgId" + [iav-dataset-preview-dataset-file-filename]="originDataset.filename" + #previewDirective="iavDatasetPreviewDatasetFile"> + </div> + <mat-chip *ngIf="previewDirective.active" + (click)="handleChipClick()" + class="pe-all position-relative ml-8-n"> + <span class="pl-4"> + {{ regionDirective.regionOriginDatasetLabels$ | async | renderViewOriginDatasetlabel : index }} + </span> + <mat-icon (click)="previewDirective.onClick()" + fontSet="fas" + iav-stop="click" + fontIcon="fa-times"> + </mat-icon> + </mat-chip> + + <mat-chip *ngFor="let key of clearViewKeys$ | async" + (click)="handleChipClick()" + class="pe-all position-relative ml-8-n"> + <span class="pl-4"> + {{ key }} + </span> + <mat-icon (click)="unsetClearViewByKey(key)" + fontSet="fas" + iav-stop="click" + fontIcon="fa-times"> + + </mat-icon> + </mat-chip> + </ng-container> + + </ng-container> + </ng-template> + </ng-template> + +</ng-template> + + +<ng-template #selectedDatasetPreview let-layers="layers"> + + <ng-container *ngFor="let layer of layers"> + <div class="hidden" + iav-dataset-preview-dataset-file + [iav-dataset-preview-dataset-file-kgid]="layer.datasetId" + [iav-dataset-preview-dataset-file-filename]="layer.filename" + #preview="iavDatasetPreviewDatasetFile"> + + </div> + <mat-chip class="pe-all" + (click)="handleChipClick()"> + {{ layer.file?.name || layer.filename || 'Unknown data preview' }} + <mat-icon fontSet="fas" fontIcon="fa-times" + (click)="preview.onClick()" + iav-stop="click"> + </mat-icon> + </mat-chip> + </ng-container> +</ng-template> + +<!-- auto complete search box --> + +<ng-template #autocompleteTmpl> + <div class="iv-custom-comp bg card w-100 mat-elevation-z8 pe-all"> + <region-text-search-autocomplete class="w-100 pt-2 flex-shrink-0 flex-grow-0"> + </region-text-search-autocomplete> + </div> +</ng-template> + + +<!-- template for rendering tab --> +<ng-template #tabTmpl + let-isOpen="isOpen" + let-regionSelected="regionSelected" + let-iavAdditionallayers="iavAdditionallayers"> + + <!-- if mat drawer is open --> + <ng-template [ngIf]="isOpen" [ngIfElse]="tabTmpl_closedTmpl"> + <ng-container *ngTemplateOutlet="tabTmpl_defaultTmpl; context: { + matColor: 'basic', + fontIcon: 'fa-chevron-left' + }"> + </ng-container> + </ng-template> + + <!-- if matdrawer is closed --> + <ng-template #tabTmpl_closedTmpl> + + <!-- if additional layers are being shown --> + <ng-template [ngIf]="iavAdditionallayers?.length > 0" [ngIfElse]="tabTmpl_noAdditionalLayers"> + <ng-container *ngTemplateOutlet="tabTmpl_defaultTmpl; context: { + matColor: 'accent', + fontIcon: 'fa-database', + tooltip: 'Explore dataset preview' + }"> + </ng-container> + </ng-template> + + <!-- if additional layers not not being shown --> + <ng-template #tabTmpl_noAdditionalLayers> + + <!-- if region selected > 0 --> + <ng-template [ngIf]="regionSelected?.length > 0" [ngIfElse]="tabTmpl_nothingSelected"> + <div class="hidden" + iav-region + [region]="regionSelected[0]" + #tabTmpl_iavRegion="iavRegion"> + </div> + + <ng-container *ngTemplateOutlet="tabTmpl_defaultTmpl; context: { + matColor: 'accent', + customColor: tabTmpl_iavRegion.rgbString, + customColorDarkmode: tabTmpl_iavRegion.rgbDarkmode, + fontIcon: 'fa-brain', + tooltip: 'Explore ' + tabTmpl_iavRegion.region.name + }"> + + </ng-container> + </ng-template> + + <!-- nothing is selected --> + <ng-template #tabTmpl_nothingSelected> + <ng-container *ngTemplateOutlet="tabTmpl_defaultTmpl; context: { + matColor: 'primary', + fontIcon: 'fa-sitemap', + tooltip: 'Explore regions' + }"> + </ng-container> + </ng-template> + </ng-template> + </ng-template> + + <ng-template #tabTmpl_defaultTmpl + let-matColor="matColor" + let-fontIcon="fontIcon" + let-customColor="customColor" + let-customColorDarkmode="customColorDarkmode" + let-tooltip="tooltip"> + <!-- (click)="sideNavMasterSwitch.toggle()" --> + <button mat-raised-button + [attr.aria-label]="ARIA_LABELS.TOGGLE_SIDE_PANEL" + [matTooltip]="tooltip" + class="pe-all tab-toggle" + [ngClass]="{ + 'darktheme': customColorDarkmode === true, + 'lighttheme': customColorDarkmode === false + }" + [style.backgroundColor]="customColor" + + [color]="(!customColor && matColor) ? matColor : null"> + + <span [ngClass]="{'iv-custom-comp text': !!customColor}"> + <i class="fas" [ngClass]="fontIcon || 'fa-question'"></i> + </span> + </button> + </ng-template> +</ng-template> + + +<!-- region sidenav tmpl --> +<ng-template #sidenavRegionTmpl> + + <!-- region search autocomplete --> + <!-- [@openCloseAnchor]="sideNavFullLeftSwitch.switchState ? 'open' : 'closed'" --> + <div class="h-0 w-100 region-text-search-autocomplete-position"> + <ng-container *ngTemplateOutlet="autocompleteTmpl"> + </ng-container> + </div> + + <div class="flex-shrink-1 flex-grow-1 d-flex flex-column" + [ngClass]="{'region-populated': (selectedRegions$ | async).length > 0 }"> + <!-- region detail --> + <ng-container *ngIf="selectedRegions$ | async as selectedRegions; else selectRegionErrorTmpl"> + + <!-- single-region-wrapper --> + <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 *ngFor="let region of selectedRegions"> + <ng-container *ngTemplateOutlet="singleRegionTmpl; context: { region: region }"> + </ng-container> + </ng-container> + </ng-template> + + <!-- multi region wrapper --> + <ng-template #multiRegionWrapperTmpl> + <ng-container *ngTemplateOutlet="multiRegionTmpl; context: { + regions: selectedRegions + }"> + </ng-container> + <!-- This is a wrapper for multiregion consisting of {{ selectedRegions.length }} regions --> + </ng-template> + + <!-- place holder if length === 0 --> + <ng-container *ngIf="selectedRegions.length === 0"> + <ng-container *ngTemplateOutlet="singleRegionTmpl; context: { region: false }"> + </ng-container> + </ng-container> + </ng-container> + + <div class="spacer"> + </div> + </div> + + <!-- collapse btn --> + <ng-container *ngTemplateOutlet="collapseBtn"> + </ng-container> +</ng-template> + + +<!-- single region tmpl --> +<ng-template #singleRegionTmpl let-region="region"> + <!-- region detail --> + <ng-container *ngIf="region; else regionPlaceholderTmpl"> + <region-menu + [showRegionInOtherTmpl]="false" + [region]="region" + class="bs-border-box ml-15px-n mr-15px-n mat-elevation-z4"> + </region-menu> + </ng-container> + + <!-- other region detail accordion --> + <mat-accordion *ngIf="region" + class="bs-border-box ml-15px-n mr-15px-n mt-2" + iav-region + [region]="region" + #iavRegion="iavRegion"> + + <!-- desc --> + <ng-container *ngFor="let ods of (region.originDatasets || [])"> + <ng-template #regionDescTmpl> + <single-dataset-view + [hideTitle]="true" + [hideExplore]="true" + [hidePreview]="true" + [hidePinBtn]="true" + [hideDownloadBtn]="true" + [kgSchema]="ods.kgSchema" + [kgId]="ods.kgId"> + + </single-dataset-view> + </ng-template> + <ng-container *ngTemplateOutlet="ngMatAccordionTmpl; context: { + title: 'Description', + iconClass: 'fas fa-info', + iavNgIf: true, + content: regionDescTmpl + }"> + + </ng-container> + </ng-container> + + <!-- Explore in other template --> + <ng-container *ngIf="iavRegion.regionInOtherTemplates$ | async as regionInOtherTemplates"> + + <ng-template #exploreInOtherTmpl> + <mat-card *ngFor="let sameRegion of regionInOtherTemplates" + class="p-0 border-0 box-shadow-none mt-1 tb-1 cursor-pointer" + (click)="iavRegion.changeView(sameRegion)" + [matTooltip]="sameRegion.template.name + (sameRegion.hemisphere ? (' - ' + sameRegion.hemisphere) : '')" + mat-ripple> + <small> + {{ sameRegion.template.name + (sameRegion.hemisphere ? (' - ' + sameRegion.hemisphere) : '') }} + </small> + </mat-card> + </ng-template> + + <ng-container *ngTemplateOutlet="ngMatAccordionTmpl; context: { + title: 'Explore in other templates', + desc: regionInOtherTemplates.length, + iconClass: 'fas fa-brain', + iconTooltip: regionInOtherTemplates.length | regionAccordionTooltipTextPipe : 'regionInOtherTmpl', + iavNgIf: regionInOtherTemplates.length, + content: exploreInOtherTmpl + }"> + + + </ng-container> + </ng-container> + + <!-- regional features--> + <ng-template #regionalFeaturesTmpl let-expansionPanel="expansionPanel"> + + <data-browser + *ngIf="expansionPanel.expanded" + [disableVirtualScroll]="true" + [regions]="[region]"> + </data-browser> + </ng-template> + + <div class="hidden" iav-databrowser-directive + [regions]="[region]" + #iavDbDirective="iavDatabrowserDirective"> + </div> + + <!-- if dataset is loading --> + <ng-template + [ngIf]="iavDbDirective?.fetchingFlag" + [ngIfElse]="featureLoadedTmpl"> + <div class="d-flex justify-content-center"> + <spinner-cmp></spinner-cmp> + </div> + </ng-template> + + <ng-template #featureLoadedTmpl> + + <!-- place holder content, if no regional features or connectivity or change ref space options are available --> + <ng-template [ngIf]="iavDbDirective?.dataentries?.length === 0"> + <ng-container *ngIf="parcellationSelected$ | async as selectedParcellation"> + <ng-template [ngIf]="selectedParcellation?.hasAdditionalViewMode?.includes('connectivity')"> + <div class="p-4"> + {{ CONST.NO_ADDIONTAL_INFO_AVAIL }} + </div> + </ng-template> + </ng-container> + </ng-template> + + </ng-template> + + + <ng-container *ngTemplateOutlet="ngMatAccordionTmpl; context: { + title: CONST.REGIONAL_FEATURES, + desc: iavDbDirective?.dataentries?.length, + iconClass: 'fas fa-database', + iconTooltip: iavDbDirective?.dataentries?.length | regionAccordionTooltipTextPipe : 'regionalFeatures', + iavNgIf: iavDbDirective?.dataentries?.length, + content: regionalFeaturesTmpl + }"> + </ng-container> + + <!-- Connectivity --> + <ng-container *ngIf="parcellationSelected$ | async as selectedParcellation"> + + <ng-template #connectivityContentTmpl let-expansionPanel="expansionPanel"> + <mat-card-content class="flex-grow-1 flex-shrink-1 w-100"> + <ng-container *ngFor="let region of selectedRegions$ | async"> + <connectivity-browser class="pe-all flex-shrink-1" + [region]="region" + [parcellationId]="selectedParcellation['@id']" + (setOpenState)="expansionPanel.expanded = $event" + [accordionExpanded]="expansionPanel.expanded"> + </connectivity-browser> + </ng-container> + </mat-card-content> + </ng-template> + + <ng-container *ngTemplateOutlet="ngMatAccordionTmpl; context: { + title: 'Connectivity', + desc: connectedCounterDir.value, + iconClass: 'fas fa-braille', + iconTooltip: connectedCounterDir.value | regionAccordionTooltipTextPipe : 'connectivity', + iavNgIf: selectedParcellation?.hasAdditionalViewMode?.includes('connectivity'), + content: connectivityContentTmpl + }"> + </ng-container> + + <div class="w-0 h-0" + iav-counter + #connectedCounterDir="iavCounter"> + <!-- TODO figure out why conn browser does not work here --> + <!-- @fsdavid, can you take a look why this component is not emitting connectivityNumberReceived event? --> + <connectivity-browser *ngIf="region && region.name" + class="d-block h-0 w-0 overflow-hidden" + [region]="region" + [parcellationId]="selectedParcellation['@id']" + [accordionExpanded]="true" + (connectivityNumberReceived)="connectedCounterDir.value = $event"> + + </connectivity-browser> + </div> + </ng-container> + + </mat-accordion> +</ng-template> + + +<!-- expansion tmpl --> +<ng-template #ngMatAccordionTmpl + let-title="title" + let-desc="desc" + let-iconClass="iconClass" + 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-container *ngTemplateOutlet="content; context: { expansionPanel: expansionPanel }"> + </ng-container> + </mat-expansion-panel> +</ng-template> + +<!-- TODO deprecate in favour of dedicated dataset preview side nav --> +<ng-template #sidenavDsPreviewTmpl let-file="file" let-filename="filename" let-datasetId="datasetId"> + <div class="w-100 flex-grow-1 d-flex flex-column"> + + <preview-card class="d-block bs-border-box ml-15px-n mr-15px-n flex-grow-1" + [attr.aria-label]="ARIA_LABELS.ADDITIONAL_VOLUME_CONTROL" + [datasetId]="datasetId" + [filename]="filename"> + </preview-card> + + <!-- collapse btn --> + <ng-container *ngTemplateOutlet="collapseBtn"> + </ng-container> + </div> +</ng-template> + +<!-- select region error... for whatever reason --> +<ng-template #selectRegionErrorTmpl> + SELECT REGION ERROR +</ng-template> + + +<!-- multi region tmpl --> +<ng-template #multiRegionTmpl let-regions="regions"> + <ng-template [ngIf]="regions.length > 0" [ngIfElse]="regionPlaceholderTmpl"> + <region-menu + [showRegionInOtherTmpl]="false" + [region]="{ + name: CONST.MULTI_REGION_SELECTION + }" + class="bs-border-box ml-15px-n mr-15px-n mat-elevation-z4"> + </region-menu> + + <!-- other regions detail accordion --> + <mat-accordion class="bs-border-box ml-15px-n mr-15px-n mt-2"> + + <!-- regional features--> + <ng-template #regionalFeaturesTmpl> + <data-browser + [disableVirtualScroll]="true" + [regions]="regions"> + </data-browser> + </ng-template> + + <div class="hidden" + iav-databrowser-directive + [regions]="regions" + #iavDbDirective="iavDatabrowserDirective"> + </div> + + <ng-container *ngTemplateOutlet="ngMatAccordionTmpl; context: { + title: CONST.REGIONAL_FEATURES, + desc: iavDbDirective?.dataentries?.length, + iconClass: 'fas fa-database', + iconTooltip: iavDbDirective?.dataentries?.length | regionAccordionTooltipTextPipe : 'regionalFeatures', + iavNgIf: iavDbDirective?.dataentries?.length, + content: regionalFeaturesTmpl + }"> + </ng-container> + + <!-- Multi regions include --> + <ng-template #multiRegionInclTmpl> + <mat-chip-list> + <mat-chip *ngFor="let r of regions" + iav-region + [region]="r" + [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 pl-4"> + {{ r.name }} + </span> + </mat-chip> + </mat-chip-list> + </ng-template> + + <ng-container *ngTemplateOutlet="ngMatAccordionTmpl; context: { + title: 'Brain regions', + desc: regions.length, + iconClass: 'fas fa-brain', + iavNgIf: true, + content: multiRegionInclTmpl + }"> + </ng-container> + + </mat-accordion> + </ng-template> +</ng-template> + +<!-- collapse btn --> +<ng-template #collapseBtn> + + <div class="h-0 w-100 collapse-position d-flex flex-column justify-content-end align-items-center"> + + <button mat-raised-button class="mat-elevation-z8" + [attr.aria-label]="ARIA_LABELS.COLLAPSE" + (click)="sideNavFullLeftSwitch.close()" + color="basic"> + <i class="fas fa-chevron-up"></i> + <span> + collapse + </span> + </button> + </div> +</ng-template> + +<!-- region tmpl placeholder --> +<ng-template #regionPlaceholderTmpl> + <div class="placeholder-region-detail bs-border-box ml-15px-n mr-15px-n mat-elevation-z4"> + <span class="text-muted"> + Select a region by clicking on the viewer or search from above + </span> + </div> +</ng-template> \ No newline at end of file diff --git a/webpack.aot.js b/webpack/webpack.aot.js similarity index 92% rename from webpack.aot.js rename to webpack/webpack.aot.js index f11f39b77e09aed04a563bfc245221c1c4b14491..15b1f95e5e5e1b658e16caf3361cf30b2161bda5 100644 --- a/webpack.aot.js +++ b/webpack/webpack.aot.js @@ -16,7 +16,7 @@ module.exports = merge(staticAssets, { }, output : { filename : '[name].js', - path : path.resolve(__dirname,'dist/aot') + path : path.resolve(__dirname,'../dist/aot') }, module: { rules: [ @@ -83,8 +83,8 @@ module.exports = merge(staticAssets, { '.json' ], alias : { - "third_party" : path.resolve(__dirname,'third_party'), - "src" : path.resolve(__dirname,'src') + "third_party" : path.resolve(__dirname,'../third_party'), + "src" : path.resolve(__dirname,'../src') } } }) \ No newline at end of file diff --git a/webpack.common.js b/webpack/webpack.common.js similarity index 72% rename from webpack.common.js rename to webpack/webpack.common.js index 52e987bddbf7f90d2b16f55ff9b4a16b0b221953..a84b8489c97ef4b52e9350d7bf04f1643f22ae55 100644 --- a/webpack.common.js +++ b/webpack/webpack.common.js @@ -21,7 +21,7 @@ module.exports = { ] }, plugins : [ - new webpack.ContextReplacementPlugin(/@angular(\\|\/)core(\\|\/)/,path.join(__dirname,'src')) + new webpack.ContextReplacementPlugin(/@angular(\\|\/)core(\\|\/)/,path.join(__dirname, '../src')) ], resolve : { extensions : [ @@ -30,10 +30,10 @@ module.exports = { '.json' ], alias : { - "third_party" : path.resolve(__dirname,'third_party'), - "src" : path.resolve(__dirname,'src'), - "common": path.resolve(__dirname, 'common'), - "spec": path.resolve(__dirname, 'spec') + "third_party" : path.resolve(__dirname, '../third_party'), + "src" : path.resolve(__dirname, '../src'), + "common": path.resolve(__dirname, '../common'), + "spec": path.resolve(__dirname, '../spec') } }, } \ No newline at end of file diff --git a/webpack.dev-aot.js b/webpack/webpack.dev-aot.js similarity index 92% rename from webpack.dev-aot.js rename to webpack/webpack.dev-aot.js index 6ddd24edaa01173f6394865aa80b944a11bba63d..ca332572fe1a3347891d966cd05052ec5949987f 100644 --- a/webpack.dev-aot.js +++ b/webpack/webpack.dev-aot.js @@ -16,7 +16,7 @@ module.exports = merge(staticAssets, { }, output : { filename : '[name].js', - path : path.resolve(__dirname,'dist/aot') + path : path.resolve(__dirname,'../dist/aot') }, devtool:'source-map', module: { @@ -77,8 +77,8 @@ module.exports = merge(staticAssets, { '.json' ], alias : { - "third_party" : path.resolve(__dirname,'third_party'), - "src" : path.resolve(__dirname,'src') + "third_party" : path.resolve(__dirname, '../third_party'), + "src" : path.resolve(__dirname, '../src') } } }) diff --git a/webpack.dev.js b/webpack/webpack.dev.js similarity index 92% rename from webpack.dev.js rename to webpack/webpack.dev.js index d32be20fe85174ca5ae969f2d1b99ca43d87558b..2a4fcf203ea57027ecfc6f8c5e0b3ccdb58453cd 100644 --- a/webpack.dev.js +++ b/webpack/webpack.dev.js @@ -12,7 +12,7 @@ module.exports = merge(common,ngAssets,staticAssets,{ mode : 'development', output : { filename : '[name].js', - path : path.resolve(__dirname,'dist/dev') + path : path.resolve(__dirname,'../dist/dev') }, devtool:'source-map', diff --git a/webpack.export.aot.js b/webpack/webpack.export.aot.js similarity index 94% rename from webpack.export.aot.js rename to webpack/webpack.export.aot.js index fda5c27b38cc2f8a9208ea0db0b72110359510ab..2612d09449efa54b3a1e4f3bbbd16aceb4744c4b 100644 --- a/webpack.export.aot.js +++ b/webpack/webpack.export.aot.js @@ -10,7 +10,7 @@ module.exports = { entry : './src/main-aot.ts', output : { filename : 'main.js', - path : path.resolve(__dirname,'dist/export-aot') + path : path.resolve(__dirname,'../dist/export-aot') }, module: { rules: [ diff --git a/webpack.export.js b/webpack/webpack.export.js similarity index 93% rename from webpack.export.js rename to webpack/webpack.export.js index d6cc210c905ec7c59a9a8dd123019e407e3a69c5..1276bc1b5a12de4d22e98f5f17ffed32c6a38c25 100644 --- a/webpack.export.js +++ b/webpack/webpack.export.js @@ -10,7 +10,7 @@ module.exports = merge(common,ngAssets,{ mode : 'development', output : { filename : 'export.js', - path : path.resolve(__dirname,'dist/export') + path : path.resolve(__dirname,'../dist/export') }, devtool:'source-map', plugins : [ diff --git a/webpack.export.min.js b/webpack/webpack.export.min.js similarity index 92% rename from webpack.export.min.js rename to webpack/webpack.export.min.js index eff53e1cc79c6f71a3a5eb7ce80ba546cdaa02c9..deefdbceeb145418c988df256b4fb85606dd4e7a 100644 --- a/webpack.export.min.js +++ b/webpack/webpack.export.min.js @@ -9,7 +9,7 @@ module.exports = merge(common,ngAssets,{ entry : './src/atlasViewerExports/main.export.ts', output : { filename : 'export.js', - path : path.resolve(__dirname,'dist/export-min') + path : path.resolve(__dirname,'../dist/export-min') }, plugins : [ new ClosureCompilerPlugin({ diff --git a/webpack.ngassets.js b/webpack/webpack.ngassets.js similarity index 100% rename from webpack.ngassets.js rename to webpack/webpack.ngassets.js diff --git a/webpack.prod.js b/webpack/webpack.prod.js similarity index 93% rename from webpack.prod.js rename to webpack/webpack.prod.js index 1119c6948c84f0b87eeb626acd7a1b4ba779f97d..325cdd3de8dfd4631952fe887994ab13bd5000f5 100644 --- a/webpack.prod.js +++ b/webpack/webpack.prod.js @@ -11,7 +11,7 @@ module.exports = merge(common,ngAssets,staticAssets,{ entry : './src/main.ts', output : { filename : 'main.js', - path : path.resolve(__dirname,'dist/prod') + path : path.resolve(__dirname,'../dist/prod') }, plugins : [ new ClosureCompilerPlugin({ diff --git a/webpack.staticassets.js b/webpack/webpack.staticassets.js similarity index 100% rename from webpack.staticassets.js rename to webpack/webpack.staticassets.js diff --git a/webpack.test.js b/webpack/webpack.test.js similarity index 100% rename from webpack.test.js rename to webpack/webpack.test.js