diff --git a/.eslintrc.js b/.eslintrc.js index fe0ec8f038dea3da56ed33293a2a6d6c14e5cd79..54af39fd8e639934ed7c4c2658f668a4facc8536 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -20,8 +20,9 @@ module.exports = { "requireLast": false } }], - "@typescript-eslint/no-unused-vars": ["warn", { - "argsIgnorePattern": "^_" + "@typescript-eslint/no-unused-vars": ["error", { + "argsIgnorePattern": "^_", + "ignoreRestSiblings": true }], "@typescript-eslint/explicit-function-return-type": "off", "@typescript-eslint/no-explicit-any": "off", diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e092ca43f6bf407a44eda7316b426e95b71ceaa2..25a2cd761b2c38a48147a54f79640b34ad0fa7ae 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: # runs-on: ubuntu-latest # steps: - # - uses: actions/checkout@v2 + # - uses: actions/checkout@v3 # - uses: actions/setup-node@v1 # with: # node-version: '16.x' @@ -25,7 +25,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Use Node.js 16.x for lint uses: actions/setup-node@v1 with: @@ -41,7 +41,7 @@ jobs: NODE_ENV: test steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Use Node.js 16.x uses: actions/setup-node@v1 with: @@ -63,7 +63,7 @@ jobs: NODE_ENV: test steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Use Node.js 16.x uses: actions/setup-node@v1 with: diff --git a/.github/workflows/docker_img.yml b/.github/workflows/docker_img.yml index 5dd8db7891e24bf018b1f8e90164c038bf4b7b93..eb5368f03107d568f2157cbee31359bae1f03f6b 100644 --- a/.github/workflows/docker_img.yml +++ b/.github/workflows/docker_img.yml @@ -30,7 +30,7 @@ jobs: build: [ 'local', 'prod' ] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: 'Set matomo env var' # if matrix.build is local, only run if master or dev if: ${{ !(matrix.build == 'local' && github.ref != 'refs/heads/master' && github.ref != 'refs/heads/dev') }} @@ -120,7 +120,7 @@ jobs: needs: build-docker-img steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set env var run: | echo "Using github.ref: $GITHUB_REF" diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 9203ddec11d5972bd515d88cabe518281765a226..ec46c942aebec12804baf7c73da2bd092023d55c 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -59,7 +59,7 @@ jobs: failure-state: ${{ steps.failure-state-step.outputs.failure-state }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: ref: ${{ github.event.ref }} diff --git a/.github/workflows/manual_e2e.yml b/.github/workflows/manual_e2e.yml index 9829d7053bd047b73209192c0effbb5c87715599..a55bd1e16b0aee7d9dc6c91af8aba780b2bc40ec 100644 --- a/.github/workflows/manual_e2e.yml +++ b/.github/workflows/manual_e2e.yml @@ -9,7 +9,7 @@ jobs: hide_previous_if_exists: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: ref: 'master' - uses: actions/github-script@v5 @@ -23,7 +23,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: 'Add checklist comment' uses: actions/github-script@v5 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9f582522c71f68ce8ce171c4b02c4b7f3ae51aea..211fd6d9ed6b726d95894c16366837d1c743392a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -24,6 +24,7 @@ jobs: if: success() runs-on: ubuntu-latest steps: + - uses: actions/checkout@v3 - name: Create Release id: create_release uses: actions/create-release@v1 @@ -32,11 +33,10 @@ jobs: with: tag_name: ${{ needs.check-version.outputs.package-version }} release_name: Release ${{ needs.check-version.outputs.package-version }} - body_path: docs/releases/v${{ needs.check-version.outputs.package-version }}.md + body_path: docs/releases/${{ needs.check-version.outputs.package-version }}.md draft: false prerelease: false - - uses: actions/checkout@v2 - name: Use Node.js 16.x uses: actions/setup-node@v1 with: diff --git a/.github/workflows/repo_sync_ebrains.yml b/.github/workflows/repo_sync_ebrains.yml index 548b768b0cd23b694a323919a7e64c71ee512d92..e29122fa0ac82239f8772e61b0d0b8581135fbd1 100644 --- a/.github/workflows/repo_sync_ebrains.yml +++ b/.github/workflows/repo_sync_ebrains.yml @@ -9,7 +9,7 @@ jobs: sync: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: wei/git-sync@v3 with: source_repo: ${GITHUB_REPOSITORY} diff --git a/common/constants.js b/common/constants.js index 0b92fb901782e568b9c8f2db52f9f7c9a79156b3..d3fe2b2b308520de3e5e9142d58b681c9dbc2a5a 100644 --- a/common/constants.js +++ b/common/constants.js @@ -163,5 +163,6 @@ If you do not accept the Terms & Conditions you are not permitted to access or u } exports.QUICKTOUR_DESC_MD = { + SLICE_VIEW: `The planar views allow you to zoom \`[mouse wheel]\`, pan the view \`[drag]\`, and select oblique sections \`<shift>\` + \`[drag]\`. You can \`[click]\` any brain regions to select them.` } })(typeof exports === 'undefined' ? module.exports : exports) diff --git a/deploy/csp/index.js b/deploy/csp/index.js index 1d67da419baa8f3c60bb7e93893ce5939f9d5ee2..e48f7f155e90e027e0274590f854d7281ca83bad 100644 --- a/deploy/csp/index.js +++ b/deploy/csp/index.js @@ -115,7 +115,7 @@ module.exports = { 'https://unpkg.com/d3@6.2.0/', // required for preview component 'https://unpkg.com/mathjax@3.1.2/', // math jax 'https://unpkg.com/three-surfer@0.0.13/dist/bundle.js', // for threeSurfer (freesurfer support in browser) - 'https://unpkg.com/ng-layer-tune@0.0.6/dist/ng-layer-tune/', // needed for ng layer control + 'https://unpkg.com/ng-layer-tune@0.0.13/dist/ng-layer-tune/', // needed for ng layer control 'https://unpkg.com/hbp-connectivity-component@0.6.6/', // needed for connectivity component (req, res) => res.locals.nonce ? `'nonce-${res.locals.nonce}'` : null, ...SCRIPT_SRC, diff --git a/docs/releases/v2.11.1.md b/docs/releases/v2.11.1.md new file mode 100644 index 0000000000000000000000000000000000000000..3c4b52dceda310dbf1790cef6ff3b245e5875ffa --- /dev/null +++ b/docs/releases/v2.11.1.md @@ -0,0 +1,20 @@ +# v2.11.1 + +## Feature + +- Allow point assignment result to be sorted +- Allow point assignment result to be downloaded as csv +- Informs user when atlas download should be occurring, and check popup blocker +- Restores Julich Brain 2.9 in Colin 27 space full mesh view + +## Bugfixes + +- Fixed point assignment full table not showing +- On template/parcellation/atlas change, clear currently selected feature + +## Behind the scenes + +- Bump siibra-api version dependency. Remove guard for feature type query restrictions +- Removed unused components +- Tweaked context menu, showing on hover effects +- now also attempts to fetch `transform.json` when external layer is populated diff --git a/docs/releases/v2.11.2.md b/docs/releases/v2.11.2.md new file mode 100644 index 0000000000000000000000000000000000000000..f48103d0127710d9c33753d9982c3c004e8488ce --- /dev/null +++ b/docs/releases/v2.11.2.md @@ -0,0 +1,18 @@ +# v2.11.2 + +## Features + +- Allow external layer control to persist state (#1338) + +## Bugfixes + +- Fixed point assignment for maps that do not have statistical maps. Also fixed error messages. +- Fixed neuron display +- Fixed typos in plugin API messages + +## Behind the scenes + +- Using hashed region name to encode selected region, rather than the archaic ngId & label index +- Pass messages to plugin API when not handled +- Fix lint +- Update CI diff --git a/docs/releases/v2.12.0.md b/docs/releases/v2.12.0.md new file mode 100644 index 0000000000000000000000000000000000000000..9af41c221a2b364cb69e74b65365fb5cdbbc1c6c --- /dev/null +++ b/docs/releases/v2.12.0.md @@ -0,0 +1,5 @@ +# v2.12.0 + +## Feature + +- enable rat connectivity diff --git a/e2e/checklist.md b/e2e/checklist.md index 51e3e9e1fa9db1bd058fad045a71503d1acac4f7..50e2c7125d560b2ae8b8491a764a4e18f806f478 100644 --- a/e2e/checklist.md +++ b/e2e/checklist.md @@ -31,16 +31,6 @@ - [ ] `Preview` tab exists and works - [ ] fingerprint is shown, interactable - [ ] profiles can be loaded, interactable - - [ ] GDPR warning triangle - - [ ] `Open in KG` button exists and works - - [ ] perspective view works - - [ ] mesh becomes transparent - - [ ] mesh transparency returns when exit the panel - - [ ] electrodes appear in perspective view - - [ ] some contact points should apepar red (intersect with region) - - [ ] electrode tab - - [ ] show should a number of contact points - - [ ] clicking on electrode should navigate to the contact point location - [ ] `Connectivity` tab exists and works - [ ] on opening tab, PMap disappear, colour mapped segmentation appears - [ ] on closing tab, PMap reappear, segmentation hides @@ -62,7 +52,7 @@ - [ ] saneurl generation functions properly - [ ] try existing key (human), and get unavailable error - [ ] try non existing key, and get available - - [ ] create use key `x-tmp-foo` and new url works + - [ ] create use key `x_tmp_foo` and new url works - [ ] [saneUrl](https://atlases.ebrains.eu/viewer-staging/saneUrl/bigbrainGreyWhite) redirects to big brain - [ ] [saneUrl](https://atlases.ebrains.eu/viewer-staging/saneUrl/julichbrain) redirects to julich brain (colin 27) - [ ] [saneUrl](https://atlases.ebrains.eu/viewer-staging/saneUrl/whs4) redirects to waxholm v4 diff --git a/mkdocs.yml b/mkdocs.yml index 0f21368640d98f4d765b121fe151f77a45ad86c5..fc88fe80be9e51f1591d52834ece4a8698b79ef5 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -33,6 +33,8 @@ nav: - Fetching datasets: 'advanced/datasets.md' - Display non-atlas volumes: 'advanced/otherVolumes.md' - Release notes: + - v2.11.2: 'releases/v2.11.2.md' + - v2.11.1: 'releases/v2.11.1.md' - v2.11.0: 'releases/v2.11.0.md' - v2.10.3: 'releases/v2.10.3.md' - v2.10.2: 'releases/v2.10.2.md' diff --git a/package-lock.json b/package-lock.json index eaa6b864e66c3e6d3556f6482a0816e0eb9e5d0c..792fff775330a11e4a0c83b26158e1694e7836f7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "siibra-explorer", - "version": "2.10.0", + "version": "2.11.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "siibra-explorer", - "version": "2.10.0", + "version": "2.11.2", "license": "apache-2.0", "dependencies": { "@angular/animations": "^14.2.12", diff --git a/package.json b/package.json index 593c06d72221ece38464f61b65e29828639ff594..9414f748771618ee48197f0ef0e3e58c898c0f0c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "siibra-explorer", - "version": "2.11.0", + "version": "2.12.0", "description": "siibra-explorer - explore brain atlases. Based on humanbrainproject/nehuba & google/neuroglancer. Built with angular", "scripts": { "lint": "eslint src --ext .ts", diff --git a/src/api/service.ts b/src/api/service.ts index 6adceb099682923f5306647b857346dca23b1aa3..44520f13f9a2958437463fb45793a925ee5b5aec 100644 --- a/src/api/service.ts +++ b/src/api/service.ts @@ -172,7 +172,7 @@ const broadCastDefault: BroadCastingApiEvents = { export class ApiService implements BoothResponder<ApiBoothEvents>{ - public broadcastCh = createBroadcastingJsonRpcChannel<`${NAMESPACE_TYPE}.on`, BroadCastingApiEvents>(`${namespace}.on`, broadCastDefault) + public broadcastCh = createBroadcastingJsonRpcChannel<`${NAMESPACE_TYPE}.on.`, BroadCastingApiEvents>(`${namespace}.on.`, broadCastDefault) public booth = new Booth<ApiBoothEvents>(this) private requestUserQueue: RequestUser<keyof RequestUserTypes>[] = [] @@ -263,27 +263,27 @@ export class ApiService implements BoothResponder<ApiBoothEvents>{ this.store.pipe( select(atlasSelection.selectors.selectedAtlas) ).subscribe(atlas => { - this.broadcastCh.emit('atlasSelected', translateV3Entities.retrieveAtlas(atlas)) + this.broadcastCh.emit('atlasSelected', atlas && translateV3Entities.retrieveAtlas(atlas)) }) this.store.pipe( select(atlasSelection.selectors.selectedParcellation) ).subscribe(parcellation => { - this.broadcastCh.emit('parcellationSelected', translateV3Entities.retrieveParcellation(parcellation)) + this.broadcastCh.emit('parcellationSelected', parcellation && translateV3Entities.retrieveParcellation(parcellation)) }) this.store.pipe( select(atlasSelection.selectors.selectedTemplate) ).subscribe(template => { - this.broadcastCh.emit('templateSelected', translateV3Entities.retrieveTemplate(template)) + this.broadcastCh.emit('templateSelected', template && translateV3Entities.retrieveTemplate(template)) }) this.store.pipe( select(atlasSelection.selectors.selectedRegions) ).subscribe(regions => { - this.broadcastCh.emit('regionsSelected', regions.map(reg => translateV3Entities.retrieveRegion(reg))) + this.broadcastCh.emit('regionsSelected', regions && regions.map(reg => translateV3Entities.retrieveRegion(reg))) }) this.store.pipe( select(atlasSelection.selectors.selectedParcAllRegions) ).subscribe(regions => { - this.broadcastCh.emit('allRegions', regions.map(reg => translateV3Entities.retrieveRegion(reg))) + this.broadcastCh.emit('allRegions', regions && regions.map(reg => translateV3Entities.retrieveRegion(reg))) }) this.store.pipe( select(atlasSelection.selectors.navigation) diff --git a/src/atlas-download/atlas-download.directive.spec.ts b/src/atlas-download/atlas-download.directive.spec.ts index e81fe2966b376e21eded2b6ec96a79a85faaf616..61c17884a4151cbf5d22cd056e8ca42fc9e242d6 100644 --- a/src/atlas-download/atlas-download.directive.spec.ts +++ b/src/atlas-download/atlas-download.directive.spec.ts @@ -3,7 +3,7 @@ import { AtlasDownloadDirective } from './atlas-download.directive'; describe('AtlasDownloadDirective', () => { it('should create an instance', () => { - const directive = new AtlasDownloadDirective(NEVER as any); + const directive = new AtlasDownloadDirective(NEVER as any, null); expect(directive).toBeTruthy(); }); }); diff --git a/src/atlas-download/atlas-download.directive.ts b/src/atlas-download/atlas-download.directive.ts index ce873301181fa39c1975848bd4a76de9b09adbf4..9b2ba98268b8f52cebec05803ca637a0d75e2353 100644 --- a/src/atlas-download/atlas-download.directive.ts +++ b/src/atlas-download/atlas-download.directive.ts @@ -1,4 +1,5 @@ import { Directive, HostListener } from '@angular/core'; +import { MatSnackBar } from '@angular/material/snack-bar'; import { Store, select } from '@ngrx/store'; import { Subject, concat, of } from 'rxjs'; import { distinctUntilChanged, shareReplay, take } from 'rxjs/operators'; @@ -70,6 +71,7 @@ export class AtlasDownloadDirective { */ window.open(`${endpoint}/atlas_download/${task_id}/download`, "_blank") this.#busy$.next(false) + this.snackbar.open(`Download starting. If it has not, please check your browser's popup blocker.`, 'Dismiss') } catch (e) { this.#busy$.next(false) this.#error$.next(e.toString()) @@ -89,6 +91,6 @@ export class AtlasDownloadDirective { #error$ = new Subject<string>() error$ = this.#error$.pipe() - constructor(private store: Store<MainState>) { } + constructor(private store: Store<MainState>, private snackbar: MatSnackBar) { } } diff --git a/src/atlas-download/atlas-download.module.ts b/src/atlas-download/atlas-download.module.ts index b4181a9cc0526254f2cabd6fbe1aa7b9894acfc8..910154cb4bce732b351da2a27738d85cab6a275a 100644 --- a/src/atlas-download/atlas-download.module.ts +++ b/src/atlas-download/atlas-download.module.ts @@ -1,6 +1,7 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { AtlasDownloadDirective } from './atlas-download.directive'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; @NgModule({ @@ -9,6 +10,7 @@ import { AtlasDownloadDirective } from './atlas-download.directive'; ], imports: [ CommonModule, + MatSnackBarModule, ], exports: [ AtlasDownloadDirective diff --git a/src/atlasComponents/sapi/core/index.ts b/src/atlasComponents/sapi/core/index.ts index a7fc4cbb696db25e9fa2159b5862e23c0bdb260e..ed68003d55ce542a35f5e173aaa173345202d597 100644 --- a/src/atlasComponents/sapi/core/index.ts +++ b/src/atlasComponents/sapi/core/index.ts @@ -1,4 +1,3 @@ export { SAPIAtlas } from "./sapiAtlas" -export { SAPISpace } from "./sapiSpace" export { SAPIParcellation } from "./sapiParcellation" export { SAPIRegion } from "./sapiRegion" diff --git a/src/atlasComponents/sapi/core/sapiRegion.ts b/src/atlasComponents/sapi/core/sapiRegion.ts index 0c5a2d0baf73fa0ba4d6e499abdbe4e3930eeb13..5c9db2f9fa82eed38c7c85afaebb57653bab0cee 100644 --- a/src/atlasComponents/sapi/core/sapiRegion.ts +++ b/src/atlasComponents/sapi/core/sapiRegion.ts @@ -1,7 +1,6 @@ import { SAPI } from ".."; import { SapiRegionModel, RouteParam } from "../typeV3"; -import { strToRgb, hexToRgb } from 'common/util' -import { NEVER, Observable, of } from "rxjs"; +import { Observable, of } from "rxjs"; import { map } from "rxjs/operators"; import { SAPIBase } from "./base"; import { SxplrRegion } from "../sxplrTypes"; @@ -92,16 +91,6 @@ export class SAPIRegion extends SAPIBase<RF>{ ) } - /** - * - * @deprecated - * @param volumeId - * @returns - */ - getVolumeInstance(volumeId: string): Observable<never> { - return NEVER - } - getDetail(spaceId: string): Observable<SapiRegionModel> { return this.sapi.v3Get("/regions/{region_id}", { path: { diff --git a/src/atlasComponents/sapi/core/sapiSpace.ts b/src/atlasComponents/sapi/core/sapiSpace.ts deleted file mode 100644 index b19359e3cbc373699aa332abb3b06d424d7f0742..0000000000000000000000000000000000000000 --- a/src/atlasComponents/sapi/core/sapiSpace.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { Observable, of, throwError } from "rxjs" -import { SAPI } from '../sapi.service' -import { SxplrTemplate } from "../sxplrTypes" -import { map, switchMap } from "rxjs/operators" -import { SAPIBase } from "./base" - -/** - * All valid parcellation features - */ -const SpaceFeatures = { - Image: "Image", -} as const - -export type SF = keyof typeof SpaceFeatures - -type FeatureResponse = { - features: { - [key: string]: string - } -} - -type RegionalSpatialFeatureOpts = { - parcellationId: string - region: string -} - -type BBoxSpatialFEatureOpts = { - bbox: string -} - -type SpatialFeatureOpts = RegionalSpatialFeatureOpts | BBoxSpatialFEatureOpts - -export class SAPISpace extends SAPIBase<SF>{ - - static Features$ = of(Object.keys(SpaceFeatures) as SF[]) - public features$ = SAPISpace.Features$ - - constructor(private sapi: SAPI, public atlasId: string, public id: string){ - super(sapi) - this.prefix$ = SAPI.BsEndpoint$.pipe( - map(endpt => `${endpt}/atlases/${encodeURIComponent(this.atlasId)}/spaces/${encodeURIComponent(this.id)}`) - ) - } - - private prefix$: Observable<string> - - getModalities(): Observable<FeatureResponse> { - return this.prefix$.pipe( - switchMap(prefix => this.sapi.httpGet<FeatureResponse>( - `${prefix}/features`, - null, - )) - ) - } - - getDetail(): Observable<SxplrTemplate>{ - return this.prefix$.pipe( - switchMap(prefix => this.sapi.httpGet<SxplrTemplate>( - `${prefix}`, - null, - )) - ) - } - -} diff --git a/src/atlasComponents/sapi/index.ts b/src/atlasComponents/sapi/index.ts index 356276003c98cafb620eaf5aa67df35acffcff4b..2ec890861ec737a5a1aec4b1f6d4d899fac532c7 100644 --- a/src/atlasComponents/sapi/index.ts +++ b/src/atlasComponents/sapi/index.ts @@ -3,7 +3,6 @@ export { SAPIModule } from './module' export { SAPI } from "./sapi.service" export { SAPIAtlas, - SAPISpace, SAPIParcellation, SAPIRegion } from "./core" diff --git a/src/atlasComponents/sapi/sapi.service.ts b/src/atlasComponents/sapi/sapi.service.ts index faf55163bb5607a3b9214cee6e4b6089e75cfc40..7afa3871728516ca4ebd63c10e08b6cf88526ab7 100644 --- a/src/atlasComponents/sapi/sapi.service.ts +++ b/src/atlasComponents/sapi/sapi.service.ts @@ -1,7 +1,7 @@ import { Injectable } from "@angular/core"; import { HttpClient } from '@angular/common/http'; import { catchError, map, shareReplay, switchMap, take, tap } from "rxjs/operators"; -import { getExportNehuba } from "src/util/fn"; +import { getExportNehuba, noop } from "src/util/fn"; import { MatSnackBar } from "@angular/material/snack-bar"; import { AtlasWorkerService } from "src/atlasViewer/atlasViewer.workerService.service"; import { EnumColorMapName } from "src/util/colorMaps"; @@ -13,7 +13,6 @@ import { import { FeatureType, PathReturn, RouteParam, SapiRoute } from "./typeV3"; import { BoundingBox, SxplrAtlas, SxplrParcellation, SxplrRegion, SxplrTemplate, VoiFeature, Feature } from "./sxplrTypes"; import { parcBanList, speciesOrder } from "src/util/constants"; -import { IDS } from "./constants"; export const useViewer = { THREESURFER: "THREESURFER", @@ -22,7 +21,7 @@ export const useViewer = { } as const export const SIIBRA_API_VERSION_HEADER_KEY='x-siibra-api-version' -export const EXPECTED_SIIBRA_API_VERSION = '0.3.3' +export const EXPECTED_SIIBRA_API_VERSION = '0.3.5' let BS_ENDPOINT_CACHED_VALUE: Observable<string> = null @@ -111,8 +110,7 @@ export class SAPI{ .then(flag => { if (flag) rs(endpt) }) - // eslint-disable-next-line @typescript-eslint/no-empty-function - .catch(e => {}) + .catch(noop) } }) try { @@ -298,7 +296,7 @@ export class SAPI{ switchMap(atlases => forkJoin( atlases.items.map(atlas => translateV3Entities.translateAtlas(atlas)) )), - map(atlases => atlases.sort((a, b) => speciesOrder.indexOf(a.species) - speciesOrder.indexOf(b.species))), + map(atlases => atlases.sort((a, b) => (speciesOrder as string[]).indexOf(a.species) - (speciesOrder as string[]).indexOf(b.species))), tap(() => { const respVersion = SAPI.API_VERSION if (respVersion !== EXPECTED_SIIBRA_API_VERSION) { @@ -351,7 +349,7 @@ export class SAPI{ template.id, "LABELLED" ).pipe( - catchError((err, obs) => of(null as SxplrParcellation)), + catchError(() => of(null as SxplrParcellation)), map(_map => _map && parc) ) ) @@ -429,7 +427,7 @@ export class SAPI{ space.id, "LABELLED" ).pipe( - catchError((err, obs) => of(null as SxplrTemplate)), + catchError(() => of(null as SxplrTemplate)), map(_map => _map && space) ) ) @@ -515,12 +513,12 @@ export class SAPI{ const map = await this.getLabelledMap(parcellation, template) for (const regionname in map.indices) { - if (parcellation.id === IDS.PARCELLATION.CORTICAL_LAYERS) { - if (regionname.includes("left") || regionname.includes("right")) { - continue - } - } - for (const { volume: volumeIdx, fragment, label } of map.indices[regionname]) { + // if (parcellation.id === IDS.PARCELLATION.CORTICAL_LAYERS) { + // if (regionname.includes("left") || regionname.includes("right")) { + // continue + // } + // } + for (const { volume: volumeIdx, fragment } of map.indices[regionname]) { const { providedVolumes } = map.volumes[volumeIdx] if (!("neuroglancer/precomputed" in providedVolumes)) { continue diff --git a/src/atlasComponents/sapi/schemaV3.ts b/src/atlasComponents/sapi/schemaV3.ts index de27935f67b5a381ef1d04069f41fad1733a6b6e..3c10d79b9c849d9aa745879549de94cfe82b45fa 100644 --- a/src/atlasComponents/sapi/schemaV3.ts +++ b/src/atlasComponents/sapi/schemaV3.ts @@ -81,6 +81,10 @@ export interface paths { /** Get Task Id */ get: operations["get_task_id_atlas_download__task_id__get"] } + "/atlas_download/{task_id}/download": { + /** Get Task Id */ + get: operations["get_task_id_atlas_download__task_id__download_get"] + } "/feature/_types": { /** Get All Feature Types */ get: operations["get_all_feature_types_feature__types_get"] @@ -441,12 +445,6 @@ export interface components { */ versionIdentifier: string } - /** - * ConnectivityTypes - * @description An enumeration. - * @enum {unknown} - */ - ConnectivityTypes: "FunctionalConnectivity" | "StreamlineCounts" | "StreamlineLengths" /** CoordinatePointModel */ CoordinatePointModel: { /** @Type */ @@ -494,20 +492,14 @@ export interface components { */ year: string } - /** - * CorticalProfileTypes - * @description An enumeration. - * @enum {unknown} - */ - CorticalProfileTypes: "ReceptorDensityProfile" | "CellDensityProfile" | "BigBrainIntensityProfile" /** DataFrameModel */ DataFrameModel: { /** @Type */ "@type": string /** Index */ - index: unknown[] + index: any[] /** Columns */ - columns: unknown[] + columns: any[] /** Ndim */ ndim: number /** Data */ @@ -650,12 +642,6 @@ export interface components { */ ontologyIdentifier?: (string)[] } - /** - * ImageTypes - * @description An enumeration. - * @enum {unknown} - */ - ImageTypes: "BlockfaceVolumeOfInterest" | "CellBodyStainedVolumeOfInterest" | "CellbodyStainedSection" | "MRIVolumeOfInterest" | "PLIVolumeOfInterest" | "SegmentedVolumeOfInterest" | "XPCTVolumeOfInterest" /** LocationModel */ LocationModel: { /** @Type */ @@ -1245,12 +1231,6 @@ export interface components { /** Max */ max: number } - /** - * TabularTypes - * @description An enumeration. - * @enum {unknown} - */ - TabularTypes: "ReceptorDensityFingerprint" | "LayerwiseBigBrainIntensities" | "LayerwiseCellDensity" | "RegionalBOLD" /** ValidationError */ ValidationError: { /** Location */ @@ -1685,6 +1665,7 @@ export interface operations { parcellation_id: string space_id: string point: string + assignment_type?: string sigma_mm?: number } } @@ -1749,6 +1730,28 @@ export interface operations { } } } + get_task_id_atlas_download__task_id__download_get: { + /** Get Task Id */ + parameters: { + path: { + task_id: string + } + } + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": Record<string, never> + } + } + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"] + } + } + } + } get_all_feature_types_feature__types_get: { /** Get All Feature Types */ parameters?: { @@ -1777,7 +1780,7 @@ export interface operations { parameters: { query: { parcellation_id: string - type?: components["schemas"]["ConnectivityTypes"] + type?: string page?: number size?: number } @@ -1808,7 +1811,7 @@ export interface operations { query: { parcellation_id: string subject?: string - type?: components["schemas"]["ConnectivityTypes"] + type?: string } path: { feature_id: string @@ -1835,7 +1838,7 @@ export interface operations { query: { parcellation_id: string region_id: string - type?: components["schemas"]["CorticalProfileTypes"] + type?: string page?: number size?: number } @@ -1861,7 +1864,7 @@ export interface operations { query: { parcellation_id: string region_id: string - type?: components["schemas"]["CorticalProfileTypes"] + type?: string } path: { feature_id: string @@ -1888,7 +1891,7 @@ export interface operations { query: { parcellation_id: string region_id: string - type?: components["schemas"]["TabularTypes"] + type?: string page?: number size?: number } @@ -1914,7 +1917,7 @@ export interface operations { query: { parcellation_id: string region_id: string - type?: components["schemas"]["TabularTypes"] + type?: string } path: { feature_id: string @@ -1941,7 +1944,7 @@ export interface operations { query: { space_id: string bbox?: string - type?: components["schemas"]["ImageTypes"] + type?: string page?: number size?: number } @@ -1966,7 +1969,7 @@ export interface operations { parameters: { query: { space_id: string - type?: components["schemas"]["ImageTypes"] + type?: string } path: { feature_id: string diff --git a/src/atlasComponents/sapi/sxplrTypes.ts b/src/atlasComponents/sapi/sxplrTypes.ts index ef556f25eb9c77344aa826d7b6d88e1e478c1227..95860602d584abe68740c2bbe9a5a4018e5e8641 100644 --- a/src/atlasComponents/sapi/sxplrTypes.ts +++ b/src/atlasComponents/sapi/sxplrTypes.ts @@ -102,10 +102,6 @@ export type Feature = { category?: string } & Partial<AdditionalInfo> -type DataFrame = { - index: Record<string, string> -} - export type VoiFeature = { bbox: BoundingBox ngVolume: { diff --git a/src/atlasComponents/sapi/translateV3.ts b/src/atlasComponents/sapi/translateV3.ts index 79775ba84f4ca64aa8a03169373ed1049a487c92..da87a784b79e7d07e4b6848bbba43de822dc2138 100644 --- a/src/atlasComponents/sapi/translateV3.ts +++ b/src/atlasComponents/sapi/translateV3.ts @@ -40,7 +40,7 @@ class TranslateV3 { } async translateParcellation(parcellation:PathReturn<"/parcellations/{parcellation_id}">): Promise<SxplrParcellation> { const ds = await Promise.all((parcellation.datasets || []).map(ds => this.translateDs(ds))) - const { name, ...rest } = ds[0] || {} + const { ...rest } = ds[0] || {} const { ['@id']: prevId } = parcellation.version?.prev || {} return { id: parcellation["@id"], @@ -147,7 +147,7 @@ class TranslateV3 { for (const defaultImage of validImages) { const { providedVolumes } = defaultImage - const { "neuroglancer/precomputed": precomputedVol, ...rest } = await this.#extractNgPrecompUnfrag(providedVolumes) + const { "neuroglancer/precomputed": precomputedVol } = await this.#extractNgPrecompUnfrag(providedVolumes) if (!precomputedVol) { console.error(`neuroglancer/precomputed data source has not been found!`) @@ -287,17 +287,7 @@ class TranslateV3 { segLayerSpec.layer.labelIndicies.push(label) segLayerSpec.region.push(region) } - const { ['@id']: mapId } = map for (const regionname in map.indices) { - /** - * temporary fix - * see https://github.com/FZJ-INM1-BDA/siibra-python/issues/317 - */ - if (mapId === "siibra-map-v0.0.1_bigbrain-cortical-labelled") { - if (regionname.includes("left") || regionname.includes("right")) { - continue - } - } for (const index of map.indices[regionname]) { const { volume:volumeIdx=0, fragment, label } = index if (!label) { diff --git a/src/atlasComponents/sapi/typeV3.ts b/src/atlasComponents/sapi/typeV3.ts index d65db66debe2bff385851d785213cec40749b3c5..22fdbcfe328e20fec2328f648be5baae4e1aa615 100644 --- a/src/atlasComponents/sapi/typeV3.ts +++ b/src/atlasComponents/sapi/typeV3.ts @@ -1,4 +1,4 @@ -import { components, paths, operations } from "./schemaV3" +import { components, paths } from "./schemaV3" export type SapiAtlasModel = PathReturn<"/atlases/{atlas_id}"> export type SapiSpaceModel = PathReturn<"/spaces/{space_id}"> @@ -22,6 +22,7 @@ type _FeatureType<FeatureRoute extends SapiRoute> = FeatureRoute extends `/featu ? never : FT extends "{feature_id}" ? never + // eslint-disable-next-line @typescript-eslint/no-unused-vars : FT extends `${infer _FT}/{${infer _FID}}` ? never : FT diff --git a/src/atlasComponents/sapiViews/core/region/module.ts b/src/atlasComponents/sapiViews/core/region/module.ts index 9482528706f278dc726d6a17f8096ac82fed2328..6e9140d0b5968e384f4f08907246dd6b9a621a30 100644 --- a/src/atlasComponents/sapiViews/core/region/module.ts +++ b/src/atlasComponents/sapiViews/core/region/module.ts @@ -9,7 +9,6 @@ import { StrictLocalModule } from "src/strictLocal"; import { SapiViewsUtilModule } from "../../util/module"; import { SapiViewsCoreRegionRegionListItem } from "./region/listItem/region.listItem.component"; import { SapiViewsCoreRegionRegionBase } from "./region/region.base.directive"; -import { SapiViewsCoreRegionRegionalFeatureDirective } from "./region/region.features.directive"; import { SapiViewsCoreRegionRegionRich } from "./region/rich/region.rich.component"; @NgModule({ @@ -27,13 +26,11 @@ import { SapiViewsCoreRegionRegionRich } from "./region/rich/region.rich.compone SapiViewsCoreRegionRegionListItem, SapiViewsCoreRegionRegionRich, SapiViewsCoreRegionRegionBase, - SapiViewsCoreRegionRegionalFeatureDirective, ], exports: [ SapiViewsCoreRegionRegionListItem, SapiViewsCoreRegionRegionRich, SapiViewsCoreRegionRegionBase, - SapiViewsCoreRegionRegionalFeatureDirective, ] }) diff --git a/src/atlasComponents/sapiViews/core/region/region/region.base.directive.ts b/src/atlasComponents/sapiViews/core/region/region/region.base.directive.ts index a2204a080335eb69d7644bf8c383eb4fb3d643f4..59b09ba91bb2e0eddbac28add6ed03fdd4c0cade 100644 --- a/src/atlasComponents/sapiViews/core/region/region/region.base.directive.ts +++ b/src/atlasComponents/sapiViews/core/region/region/region.base.directive.ts @@ -1,4 +1,4 @@ -import { Directive, EventEmitter, Input, Output, SimpleChanges } from "@angular/core"; +import { Directive, EventEmitter, Input, Output } from "@angular/core"; import { SxplrAtlas, SxplrParcellation, SxplrRegion, SxplrTemplate } from "src/atlasComponents/sapi/sxplrTypes"; import { translateV3Entities } from "src/atlasComponents/sapi/translateV3" import { rgbToHsl } from 'common/util' @@ -72,7 +72,7 @@ export class SapiViewsCoreRegionRegionBase { map(([ atp, region ]) => ({ ...atp, region })) ) - ngOnChanges(sc: SimpleChanges): void { + ngOnChanges(): void { const { atlas, template, parcellation } = this this.ATP$.next({ atlas, template, parcellation }) } @@ -97,7 +97,7 @@ export class SapiViewsCoreRegionRegionBase { */ const rgb = SAPIRegion.GetDisplayColor(this.region) || [200, 200, 200] this.regionRgbString = `rgb(${rgb.join(',')})` - const [_h, _s, l] = rgbToHsl(...rgb) + const [ /* _h */, /* _s */, l] = rgbToHsl(...rgb) this.regionDarkmode = l < 0.4 /** diff --git a/src/atlasComponents/sapiViews/core/region/region/region.features.directive.ts b/src/atlasComponents/sapiViews/core/region/region/region.features.directive.ts deleted file mode 100644 index b7019d477953329a64fb471ad620b4f7486b059f..0000000000000000000000000000000000000000 --- a/src/atlasComponents/sapiViews/core/region/region/region.features.directive.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Directive, OnChanges, SimpleChanges } from "@angular/core"; -import { BehaviorSubject, merge, NEVER } from "rxjs"; -import { switchMap, filter, startWith, shareReplay, scan } from "rxjs/operators"; -import { SAPI, SAPIRegion } from "src/atlasComponents/sapi"; -import { SxplrAtlas, SxplrParcellation, SxplrTemplate, SxplrRegion } from "src/atlasComponents/sapi/sxplrTypes" -import { SapiViewsCoreRegionRegionBase } from "./region.base.directive"; - -@Directive({ - selector: '[sxplr-sapiviews-core-region-regional-feature]', - exportAs: 'sapiViewsRegionalFeature' -}) - -export class SapiViewsCoreRegionRegionalFeatureDirective extends SapiViewsCoreRegionRegionBase implements OnChanges{ - - constructor(sapi: SAPI){ - super(sapi) - } - - private features$ = NEVER - - public listOfFeatures$ = this.features$.pipe( - startWith([]), - shareReplay(1), - ) - - public busy$ = new BehaviorSubject<boolean>(false) -} diff --git a/src/atlasComponents/sapiViews/core/region/region/rich/region.rich.component.ts b/src/atlasComponents/sapiViews/core/region/region/rich/region.rich.component.ts index 9db77ddb32c2077ff94f932785ffc5469a50b6a3..c61d33b9d2c25cd406383ed85cc437f6364748ca 100644 --- a/src/atlasComponents/sapiViews/core/region/region/rich/region.rich.component.ts +++ b/src/atlasComponents/sapiViews/core/region/region/rich/region.rich.component.ts @@ -28,8 +28,6 @@ export class SapiViewsCoreRegionRegionRich extends SapiViewsCoreRegionRegionBase @Output('sxplr-sapiviews-core-region-region-rich-feature-clicked') featureClicked = new EventEmitter<Feature>() - public expandedPanel: string - constructor( sapi: SAPI, @Inject(DARKTHEME) public darktheme$: Observable<boolean>, @@ -41,16 +39,6 @@ export class SapiViewsCoreRegionRegionRich extends SapiViewsCoreRegionRegionBase this.featureClicked.emit(feat) } - // eslint-disable-next-line @typescript-eslint/no-empty-function - handleExpansionPanelClosedEv(title: string){ - this.expandedPanel = null - } - - // eslint-disable-next-line @typescript-eslint/no-empty-function - handleExpansionPanelAfterExpandEv(title: string) { - this.expandedPanel = title - } - activePanelTitles$: Observable<string[]> = new Subject() private regionalStatisticalMaps$ = this.ATPR$.pipe( diff --git a/src/atlasComponents/sapiViews/core/rich/ATPSelector/wrapper/wrapper.component.ts b/src/atlasComponents/sapiViews/core/rich/ATPSelector/wrapper/wrapper.component.ts index 6a8a8b546078f57907c0d7bf03bb8443f8d1bcb0..160902ec19a877eb868a8dab7e636f854b84cb2b 100644 --- a/src/atlasComponents/sapiViews/core/rich/ATPSelector/wrapper/wrapper.component.ts +++ b/src/atlasComponents/sapiViews/core/rich/ATPSelector/wrapper/wrapper.component.ts @@ -16,11 +16,6 @@ function isATPGuard(obj: any): obj is Partial<ATP&{ requested: Partial<ATP> }> { return (obj.atlas || obj.template || obj.parcellation) && (!obj.requested || isATPGuard(obj.requested)) } -const banListParcName = new Set([ - "VEP Atlas", - "Desikan-Killiany 2006" -]) - @Component({ selector: 'sxplr-wrapper-atp-selector', templateUrl: './wrapper.template.html', diff --git a/src/atlasComponents/sapiViews/core/space/boundingBox.directive.ts b/src/atlasComponents/sapiViews/core/space/boundingBox.directive.ts index 72d0e2e66fc7ff0b8da0d520a6ec4438432a88df..ed1aa7731dfeda9b771a64cdadfc82236bf9eec1 100644 --- a/src/atlasComponents/sapiViews/core/space/boundingBox.directive.ts +++ b/src/atlasComponents/sapiViews/core/space/boundingBox.directive.ts @@ -1,4 +1,4 @@ -import { Directive, Input, OnChanges } from "@angular/core"; +import { Directive, Input, OnChanges, Output } from "@angular/core"; import { BehaviorSubject, Observable } from "rxjs"; import { distinctUntilChanged } from "rxjs/operators"; import { BoundingBox, SxplrTemplate, SxplrAtlas } from "src/atlasComponents/sapi/sxplrTypes" @@ -60,6 +60,7 @@ export class SapiViewsCoreSpaceBoundingBox implements OnChanges{ bbox: null }) + @Output('sxplr-sapiviews-core-space-boundingbox-changed') public bbox$: Observable<{ atlas: SxplrAtlas space: SxplrTemplate diff --git a/src/atlasComponents/sapiViews/volumes/point-assignment/point-assignment.component.html b/src/atlasComponents/sapiViews/volumes/point-assignment/point-assignment.component.html index 8fc0aa7b4411e39a46e1d30f200f45907eb8d1ee..59694f74a9f84f067a93a56eb0f0d9988895d8e1 100644 --- a/src/atlasComponents/sapiViews/volumes/point-assignment/point-assignment.component.html +++ b/src/atlasComponents/sapiViews/volumes/point-assignment/point-assignment.component.html @@ -1,7 +1,13 @@ -<div class="sxplr-m-2" *ngIf="busy$ | async"> +<div class="sxplr-m-2" *ngIf="busy$ | async as busyMessage"> <spinner-cmp class="sxplr-d-inline-block"></spinner-cmp> <span> - Loading assignment ... + {{ busyMessage }} + </span> +</div> + +<div class="sxplr-m-2" *ngIf="error$ | async"> + <span> + An error occurred when performing map assignment. </span> </div> @@ -13,9 +19,14 @@ </button> <!-- simple table --> - <table mat-table [dataSource]="df | dfToDs" class="sxplr-w-100"> + <table mat-table [dataSource]="df | dfToDs : simpleTblSort" + matSort + class="sxplr-w-100" + #simpleTblSort="matSort" + matSortActive="map value" + matSortDirection="desc"> <ng-container matColumnDef="region"> - <th mat-header-cell *matHeaderCellDef> + <th mat-header-cell *matHeaderCellDef mat-sort-header> region </th> <td mat-cell *matCellDef="let element"> @@ -25,12 +36,12 @@ </button> </td> </ng-container> - <ng-container matColumnDef="intersection over union"> - <th mat-header-cell *matHeaderCellDef> - intersection over union + <ng-container matColumnDef="map value"> + <th mat-header-cell *matHeaderCellDef mat-sort-header> + map value </th> <td mat-cell *matCellDef="let element"> - {{ element['intersection over union'] | prettyPresent }} + {{ element['map value'] | prettyPresent }} </td> </ng-container> @@ -42,10 +53,14 @@ <ng-template #datatableTmpl> <h2 mat-dialog-title>Assignment</h2> <mat-dialog-content> - <table mat-table [dataSource]="df$ | async | dfToDs"> + <table mat-table [dataSource]="df$ | async | dfToDs : comphTableSort" + matSort + #comphTableSort="matSort" + matSortActive="map value" + matSortDirection="desc"> <ng-container *ngFor="let column of columns$ | async" [matColumnDef]="column"> - <th mat-header-cell *matHeaderCellDef> + <th mat-header-cell *matHeaderCellDef mat-sort-header> {{ column }} </th> <td mat-cell *matCellDef="let element"> @@ -58,6 +73,14 @@ </table> </mat-dialog-content> <mat-dialog-actions align="center"> + <button mat-raised-button color="primary" + [zip-files-output]="zipfileConfig$ | async" + zip-files-output-zip-filename="pointassignment.zip"> + <i class="fas fa-download"></i> + <span> + Download CSV + </span> + </button> <button mat-button mat-dialog-close>Close</button> - </mat-dialog-actions> + </mat-dialog-actions> </ng-template> diff --git a/src/atlasComponents/sapiViews/volumes/point-assignment/point-assignment.component.ts b/src/atlasComponents/sapiViews/volumes/point-assignment/point-assignment.component.ts index 2bf281e7816015f0607b4f77122373d7097705c5..a28f5691c45974889ded72e3c28e57015d1051b1 100644 --- a/src/atlasComponents/sapiViews/volumes/point-assignment/point-assignment.component.ts +++ b/src/atlasComponents/sapiViews/volumes/point-assignment/point-assignment.component.ts @@ -1,12 +1,17 @@ import { Component, Input, OnDestroy, Output, TemplateRef, EventEmitter } from '@angular/core'; import { MatDialog } from '@angular/material/dialog'; -import { BehaviorSubject, EMPTY, Subscription, combineLatest, concat, of } from 'rxjs'; -import { map, shareReplay, switchMap, tap } from 'rxjs/operators'; -import { SAPI } from 'src/atlasComponents/sapi/sapi.service'; +import { BehaviorSubject, EMPTY, Observable, Subscription, combineLatest, concat, of } from 'rxjs'; +import { catchError, map, shareReplay, switchMap, tap } from 'rxjs/operators'; +import { SAPI, EXPECTED_SIIBRA_API_VERSION } from 'src/atlasComponents/sapi/sapi.service'; import { SxplrParcellation, SxplrRegion, SxplrTemplate } from 'src/atlasComponents/sapi/sxplrTypes'; import { translateV3Entities } from 'src/atlasComponents/sapi/translateV3'; import { PathReturn } from 'src/atlasComponents/sapi/typeV3'; import { TSandsPoint } from 'src/util/types'; +import { TZipFileConfig } from "src/zipFilesOutput/type" +import { environment } from "src/environments/environment" + +const DOING_PROB_ASGMT = "Performing probabilistic assignment ..." +const DOING_LABEL_ASGMT = "Probabilistic assignment failed. Performing labelled assignment ..." @Component({ selector: 'sxplr-point-assignment', @@ -17,12 +22,15 @@ export class PointAssignmentComponent implements OnDestroy { SIMPLE_COLUMNS = [ "region", - "intersection over union", + "map value", ] - #busy$ = new BehaviorSubject(false) + #busy$ = new BehaviorSubject<string>(null) busy$ = this.#busy$.asObservable() + #error$ = new BehaviorSubject<string>(null) + error$ = this.#error$.asObservable() + #point = new BehaviorSubject<TSandsPoint>(null) @Input() set point(val: TSandsPoint) { @@ -44,12 +52,15 @@ export class PointAssignmentComponent implements OnDestroy { @Output() clickOnRegion = new EventEmitter<{ target: SxplrRegion, event: MouseEvent }>() - df$ = combineLatest([ + df$: Observable<PathReturn<"/map/assign">> = combineLatest([ this.#point, this.#parcellation, this.#template, ]).pipe( switchMap(([ point, parcellation, template ]) => { + + this.#error$.next(null) + if (!point || !parcellation || !template) { return EMPTY } @@ -58,7 +69,7 @@ export class PointAssignmentComponent implements OnDestroy { console.warn(`point coordination space id ${ptSpaceId} is not the same as template id ${template.id}.`) return EMPTY } - this.#busy$.next(true) + this.#busy$.next(DOING_PROB_ASGMT) return concat( of(null), this.sapi.v3Get("/map/assign", { @@ -66,15 +77,31 @@ export class PointAssignmentComponent implements OnDestroy { parcellation_id: parcellation.id, point: point.coordinates.map(v => `${v.value/1e6}mm`).join(','), space_id: template.id, - sigma_mm: 3.0 + sigma_mm: 0 } }).pipe( - tap(() => this.#busy$.next(false)), + catchError(() => { + this.#busy$.next(DOING_LABEL_ASGMT) + return this.sapi.v3Get("/map/assign", { + query: { + parcellation_id: parcellation.id, + point: point.coordinates.map(v => `${v.value/1e6}mm`).join(','), + space_id: template.id, + sigma_mm: 0, + assignment_type: "labelled" + } + }) + }), + catchError((err) => { + this.#busy$.next(null) + this.#error$.next(err.toString()) + return of(null) + }), + tap(() => this.#busy$.next(null)), ) - ).pipe( - shareReplay(1), ) - }) + }), + shareReplay(1), ) columns$ = this.df$.pipe( @@ -95,4 +122,76 @@ export class PointAssignmentComponent implements OnDestroy { const sxplrReg = await translateV3Entities.translateRegion(region) this.clickOnRegion.emit({ target: sxplrReg, event }) } + + zipfileConfig$: Observable<TZipFileConfig[]> = combineLatest([ + this.#point, + this.#parcellation, + this.#template, + this.df$ + ]).pipe( + map(([ pt, parc, tmpl, df ]) => { + return [{ + filename: 'README.md', + filecontent: generateReadMe(pt, parc, tmpl) + }, { + filename: 'pointassignment.csv', + filecontent: generateCsv(df) + }] as TZipFileConfig[] + }) + ) +} + +function generateReadMe(pt: TSandsPoint, parc: SxplrParcellation, tmpl: SxplrTemplate){ + return `# Point assignment exporter + +Exported by siibra-explorer verison \`${environment.VERSION}\` hash: \`${environment.GIT_HASH.trim()}\`. + +On: ${new Date().toString()} + +Data retrieved through siibra-api version \`${EXPECTED_SIIBRA_API_VERSION}\` + +Retrieval parameters: + +Point +- coord: ${pt.coordinates.map(v => v.value/1e6).join(',')} mm + +Parcellation +- name: ${parc.name || parc.shortName} +- id: ${parc.id} + +Space +- name: ${tmpl.name || tmpl.shortName} +- id: ${tmpl.id} +` +} + +function escapeFactory(chars: string[] = []){ + const search = new RegExp(`[${chars.join('').replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}]`, 'g') + return function escape(s: string) { + return s.replace(search, s => `\\${s}`) + } +} + +const escapeDoubleQuotes = escapeFactory(['"']) + +function processRow(v: unknown[]): string{ + const returnValue: string[] = [] + for (const item of v) { + + // region + if (typeof item === "object" && item?.['@type'] === "siibra-0.4/region") { + returnValue.push(item['name']) + continue + } + + returnValue.push(JSON.stringify(item)) + } + return returnValue.map(escapeDoubleQuotes).map(v => `"${v}"`).join(",") +} + +function generateCsv(df: PathReturn<"/map/assign">) { + return [ + df.columns.map(escapeDoubleQuotes).map(v => `"${v}"`).join(","), + ...df.data.map(processRow) + ].join("\n") } diff --git a/src/atlasComponents/sapiViews/volumes/volumes.module.ts b/src/atlasComponents/sapiViews/volumes/volumes.module.ts index eee2503b61afec1b5533c4d265ce178669ee492c..1a5a32d0f5cf07a536f3e559c29ad835ca1abb6f 100644 --- a/src/atlasComponents/sapiViews/volumes/volumes.module.ts +++ b/src/atlasComponents/sapiViews/volumes/volumes.module.ts @@ -6,6 +6,8 @@ import { UtilModule } from 'src/util'; import { SpinnerModule } from 'src/components/spinner'; import { MatDialogModule } from '@angular/material/dialog'; import { MatButtonModule } from '@angular/material/button'; +import { MatSortModule } from '@angular/material/sort'; +import { ZipFilesOutputModule } from 'src/zipFilesOutput/module'; @@ -20,6 +22,8 @@ import { MatButtonModule } from '@angular/material/button'; SpinnerModule, MatDialogModule, MatButtonModule, + MatSortModule, + ZipFilesOutputModule, ], exports: [ PointAssignmentComponent diff --git a/src/atlasComponents/userAnnotations/module.ts b/src/atlasComponents/userAnnotations/module.ts index ec2586e5741ddc2d25ff4c45657154d882386946..f6c3d1682f6979a7fa2446a5f2a67c658f66d722 100644 --- a/src/atlasComponents/userAnnotations/module.ts +++ b/src/atlasComponents/userAnnotations/module.ts @@ -54,7 +54,7 @@ import { RoutedAnnotationService } from "./routedAnnotation.service"; // ... in url correctly { provide: APP_INITIALIZER, - useFactory:(svc: RoutedAnnotationService) => { + useFactory:(_svc: RoutedAnnotationService) => { return () => Promise.resolve() }, deps: [ RoutedAnnotationService ], diff --git a/src/atlasComponents/userAnnotations/singleAnnotationUnit/singleAnnotationUnit.component.ts b/src/atlasComponents/userAnnotations/singleAnnotationUnit/singleAnnotationUnit.component.ts index 15bfc561e3f86608e4b18f0de67970d1391d8af5..09895109507b4eb9baf2283557cabd45ca7a59bc 100644 --- a/src/atlasComponents/userAnnotations/singleAnnotationUnit/singleAnnotationUnit.component.ts +++ b/src/atlasComponents/userAnnotations/singleAnnotationUnit/singleAnnotationUnit.component.ts @@ -44,7 +44,7 @@ export class SingleAnnotationUnit implements OnDestroy, AfterViewInit{ this.chSubs.push( this.formGrp.valueChanges.subscribe(value => { - const { name, desc, spaceId } = value + const { name, desc } = value this.managedAnnotation.name = name this.managedAnnotation.desc = desc }) diff --git a/src/atlasComponents/userAnnotations/tools/delete.ts b/src/atlasComponents/userAnnotations/tools/delete.ts index 2e118bcd3dddb46ab2aa8d6ce702b8b14ccdd054..be375acbc193ad1a6ec52e11c33ea75ee934fa4a 100644 --- a/src/atlasComponents/userAnnotations/tools/delete.ts +++ b/src/atlasComponents/userAnnotations/tools/delete.ts @@ -2,7 +2,7 @@ import { Directive, OnDestroy } from "@angular/core"; import { Observable, Subject, Subscription } from "rxjs"; import { filter, switchMapTo, takeUntil, withLatestFrom } from "rxjs/operators"; import { Point } from "./point"; -import { AbsToolClass, IAnnotationEvents, IAnnotationGeometry, IAnnotationTools, TAnnotationEvent, TCallbackFunction, TNgAnnotationPoint, TToolType } from "./type"; +import { AbsToolClass, IAnnotationEvents, IAnnotationGeometry, IAnnotationTools, TAnnotationEvent, TCallbackFunction, TToolType } from "./type"; @Directive() export class ToolDelete extends AbsToolClass<Point> implements IAnnotationTools, OnDestroy { diff --git a/src/atlasComponents/userAnnotations/tools/point.ts b/src/atlasComponents/userAnnotations/tools/point.ts index a2e10804108682e14e844481094063cfac3fad5c..58d8cdde8acd05863e682814e37f8d667124e36e 100644 --- a/src/atlasComponents/userAnnotations/tools/point.ts +++ b/src/atlasComponents/userAnnotations/tools/point.ts @@ -1,4 +1,4 @@ -import { AbsToolClass, getCoord, IAnnotationEvents, IAnnotationGeometry, IAnnotationTools, INgAnnotationTypes, TAnnotationEvent, TBaseAnnotationGeomtrySpec, TCallbackFunction, TNgAnnotationEv, TSandsPoint, TToolType } from "./type"; +import { AbsToolClass, getCoord, IAnnotationEvents, IAnnotationGeometry, IAnnotationTools, INgAnnotationTypes, TAnnotationEvent, TBaseAnnotationGeomtrySpec, TCallbackFunction, TSandsPoint, TToolType } from "./type"; import { Observable, Subject, Subscription } from "rxjs"; import { Directive, OnDestroy } from "@angular/core"; import { filter, switchMapTo, takeUntil } from "rxjs/operators"; diff --git a/src/atlasComponents/userAnnotations/tools/poly.ts b/src/atlasComponents/userAnnotations/tools/poly.ts index 37a0fdbd397fe43fa136409af1503a2cb2443bbe..244c71a6a2d11163ba70af11727adcefd1ac399f 100644 --- a/src/atlasComponents/userAnnotations/tools/poly.ts +++ b/src/atlasComponents/userAnnotations/tools/poly.ts @@ -1,7 +1,7 @@ -import { IAnnotationTools, IAnnotationGeometry, TAnnotationEvent, IAnnotationEvents, AbsToolClass, INgAnnotationTypes, TNgAnnotationEv, TToolType, TBaseAnnotationGeomtrySpec, TSandsPolyLine, getCoord, TCallbackFunction } from "./type"; +import { IAnnotationTools, IAnnotationGeometry, TAnnotationEvent, IAnnotationEvents, AbsToolClass, INgAnnotationTypes, TToolType, TBaseAnnotationGeomtrySpec, TSandsPolyLine, getCoord, TCallbackFunction } from "./type"; import { Point, TPointJsonSpec } from './point' import { Directive, OnDestroy } from "@angular/core"; -import { merge, Observable, Subject, Subscription } from "rxjs"; +import { Observable, Subject, Subscription } from "rxjs"; import { filter, switchMapTo, takeUntil, withLatestFrom } from "rxjs/operators"; import { getUuid } from "src/util/fn"; @@ -129,7 +129,7 @@ export class Polygon extends IAnnotationGeometry{ } parseNgAnnotationObj(pickedAnnotationId: string, pickedOffset: number): { edge: [number, number], edgeIdx: number, point: Point, pointIdx: number } { - const [ id, edgeIdx, _shouldBeZero ] = pickedAnnotationId.split('_') + const [ id, edgeIdx, /* _shouldBeZero */ ] = pickedAnnotationId.split('_') if (id !== this.id) return null if (pickedOffset === 0) { diff --git a/src/atlasComponents/userAnnotations/tools/select.ts b/src/atlasComponents/userAnnotations/tools/select.ts index 10befd2dfe4b65ade055f2fcc81d57af5e2d4d3b..3653d86efb003f7676018b6adf53a5d7b40c80ef 100644 --- a/src/atlasComponents/userAnnotations/tools/select.ts +++ b/src/atlasComponents/userAnnotations/tools/select.ts @@ -2,7 +2,7 @@ import { Directive, OnDestroy } from "@angular/core"; import { Observable, Subject, Subscription } from "rxjs"; import { filter } from 'rxjs/operators' import { Point } from "./point"; -import { AbsToolClass, IAnnotationEvents, IAnnotationGeometry, IAnnotationTools, TAnnotationEvent, TCallbackFunction, TNgAnnotationPoint, TToolType } from "./type"; +import { AbsToolClass, IAnnotationEvents, IAnnotationGeometry, IAnnotationTools, TAnnotationEvent, TCallbackFunction, TToolType } from "./type"; @Directive() export class ToolSelect extends AbsToolClass<Point> implements IAnnotationTools, OnDestroy { diff --git a/src/atlasComponents/userAnnotations/tools/service.ts b/src/atlasComponents/userAnnotations/tools/service.ts index 4a18bd4f3dc806ea1bef95f1f6339a17b63f6ead..03f0f060e620f05897e380044ef381bea50c5c71 100644 --- a/src/atlasComponents/userAnnotations/tools/service.ts +++ b/src/atlasComponents/userAnnotations/tools/service.ts @@ -6,7 +6,7 @@ import { BehaviorSubject, combineLatest, fromEvent, merge, Observable, of, Subje import {map, switchMap, filter, shareReplay, pairwise, withLatestFrom } from "rxjs/operators"; import { NehubaViewerUnit } from "src/viewerModule/nehuba"; import { NEHUBA_INSTANCE_INJTKN } from "src/viewerModule/nehuba/util"; -import { AbsToolClass, ANNOTATION_EVENT_INJ_TOKEN, IAnnotationEvents, IAnnotationGeometry, INgAnnotationTypes, INJ_ANNOT_TARGET, TAnnotationEvent, ClassInterface, TCallbackFunction, TSands, TGeometryJson, TNgAnnotationLine, TCallback } from "./type"; +import { AbsToolClass, ANNOTATION_EVENT_INJ_TOKEN, IAnnotationEvents, IAnnotationGeometry, INgAnnotationTypes, INJ_ANNOT_TARGET, TAnnotationEvent, ClassInterface, TCallbackFunction, TSands, TGeometryJson, TCallback } from "./type"; import { getExportNehuba, switchMapWaitFor } from "src/util/fn"; import { Polygon } from "./poly"; import { Line } from "./line"; @@ -194,7 +194,7 @@ export class ModularUserAnnotationToolService implements OnDestroy{ }): AbsToolClass<any>{ const { toolCls: Cls, target, editCmp } = arg const newTool = new Cls(this.annotnEvSubj, arg => this.handleToolCallback(arg)) as T & { ngOnDestroy?: () => void } - const { name, iconClass, onMouseMoveRenderPreview } = newTool + const { name, iconClass } = newTool this.moduleAnnotationTypes.push({ instance: newTool, diff --git a/src/atlasViewer/atlasViewer.component.ts b/src/atlasViewer/atlasViewer.component.ts index 9255c9065120627b7998d0c9c297972a69f1c0ff..c28d15f9a80133259294b4de407a543014adcf78 100644 --- a/src/atlasViewer/atlasViewer.component.ts +++ b/src/atlasViewer/atlasViewer.component.ts @@ -18,7 +18,7 @@ import { colorAnimation } from "./atlasViewer.animation" 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' +import { CONST } from 'common/constants' import { SlServiceService } from "src/spotlight/sl-service.service"; import { ClickInterceptorService } from "src/glue"; @@ -28,12 +28,6 @@ import { userPreference } from "src/state" import { DARKTHEME } from "src/util/injectionTokens"; import { EnumQuickTourSeverity } from "src/ui/quickTour/constrants"; -/** - * TODO - * check against auxlillary mesh indicies, to only filter out aux indicies - */ -const filterFn = (segment) => typeof segment.segment !== 'string' -const compareFn = (it, item) => it.name === item.name @Component({ selector: 'atlas-viewer', @@ -49,7 +43,6 @@ const compareFn = (it, item) => it.name === item.name export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit { public CONST = CONST - public compareFn = compareFn @ViewChild('cookieAgreementComponent', {read: TemplateRef}) public cookieAgreementComponent: TemplateRef<any> diff --git a/src/atlasViewer/atlasViewer.workerService.service.ts b/src/atlasViewer/atlasViewer.workerService.service.ts index 67a422714822d25e47fdb55b345116cda9db8eba..32d6c3b05e767da8b43d6c019f3c7e440cdae729 100644 --- a/src/atlasViewer/atlasViewer.workerService.service.ts +++ b/src/atlasViewer/atlasViewer.workerService.service.ts @@ -34,7 +34,7 @@ export class AtlasWorkerService { ).toPromise() const { data: returnData } = message as MessageEvent - const { id, error, ...rest } = returnData + const { error, ...rest } = returnData if (error) { throw new Error(error.message || error) } diff --git a/src/components/fabSpeedDial/fabSpeedDialContainer.directive.ts b/src/components/fabSpeedDial/fabSpeedDialContainer.directive.ts index 2c5967a634ff77bd08cf6b46a920afdd6fc51c74..321e5f9bc6f52dbb1289af7ea04db2084d5a341d 100644 --- a/src/components/fabSpeedDial/fabSpeedDialContainer.directive.ts +++ b/src/components/fabSpeedDial/fabSpeedDialContainer.directive.ts @@ -1,4 +1,4 @@ -import { Directive, OnDestroy, Output, EventEmitter, Input, OnChanges, SimpleChanges, HostListener, ElementRef, HostBinding } from "@angular/core"; +import { Directive, OnDestroy, Output, EventEmitter, Input, OnChanges, SimpleChanges, HostBinding } from "@angular/core"; import { FabSpeedDialService } from "./fabSpeedDial.service"; import { Subscription } from "rxjs"; import { SCALE_ORIGIN } from './fabSpeedDial.service' diff --git a/src/components/flatHierarchy/spacer.pipe.ts b/src/components/flatHierarchy/spacer.pipe.ts index a7533b68c759ed72c1a56b87a762409a4d8a5f2b..73498906a0e6aa52ac93a16027c59e20f30b7c0d 100644 --- a/src/components/flatHierarchy/spacer.pipe.ts +++ b/src/components/flatHierarchy/spacer.pipe.ts @@ -7,7 +7,7 @@ import { TreeNode } from "./const" }) export class FlatHierarchySpacer implements PipeTransform{ - public transform<T>(inputNode: TreeNode<T>, ...args: any[]) { + public transform<T>(inputNode: TreeNode<T>, ..._args: any[]) { return Array(inputNode.level).fill(null) } } diff --git a/src/components/flatHierarchy/treeView/treeControl.ts b/src/components/flatHierarchy/treeView/treeControl.ts index 7471dfbb839cb36c23413d026c2f9c8f607af6dd..d8676d4ff1b7840994f9ac12bebed0ab5c8d8e31 100644 --- a/src/components/flatHierarchy/treeView/treeControl.ts +++ b/src/components/flatHierarchy/treeView/treeControl.ts @@ -13,7 +13,7 @@ export class Tree<T extends Record<string, unknown>>{ return this._nodes } - private _isParent: IsParent<T> = (c, p) => false + private _isParent: IsParent<T> = (_c, _p) => false protected set isParent(fn: IsParent<T>){ if (fn === this._isParent) return this._isParent = fn @@ -81,7 +81,7 @@ export class Tree<T extends Record<string, unknown>>{ constructor( _nodes: T[] = [], - _isParent: IsParent<T> = (c, p) => false + _isParent: IsParent<T> = (_c, _p) => false ){ this._nodes = _nodes this._isParent = _isParent diff --git a/src/components/markdown/markdownCmp/markdown.component.ts b/src/components/markdown/markdownCmp/markdown.component.ts index 7084626a5ed37fb4162d610edfe232fcf3852381..fd6776dec0c312e74c659bff8fb4668e2fbf130a 100644 --- a/src/components/markdown/markdownCmp/markdown.component.ts +++ b/src/components/markdown/markdownCmp/markdown.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, ElementRef, Input, ViewChild, ChangeDetectorRef, AfterViewChecked, OnChanges, SecurityContext } from '@angular/core' +import { ChangeDetectionStrategy, Component, ElementRef, Input, ViewChild, ChangeDetectorRef, OnChanges, SecurityContext } from '@angular/core' import { DomSanitizer } from '@angular/platform-browser' import * as showdown from 'showdown' diff --git a/src/components/parseAttribute.directive.ts b/src/components/parseAttribute.directive.ts deleted file mode 100644 index 34a9b124841c32106a8f590f00d80d69f85d337b..0000000000000000000000000000000000000000 --- a/src/components/parseAttribute.directive.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { Directive, OnChanges, SimpleChanges } from "@angular/core"; - -// TODO deprecate this directive -function parseAttribute(arg: any, expectedType: string) { - - // if( - // typeof arg === expectedType || - // arg === undefined || - // arg === null - // ){ - // return arg - // } - // switch (expectedType){ - // case 'object': - // try{ - // const json = JSON.parse(arg) - // return json - // }catch(e){ - // this.log.warn('parseAttribute error, cannot JSON.parse object') - // return arg - // } - // case 'boolean' : - // return arg === 'true' - // case 'number': - // return isNaN(arg) ? 0 : Number(arg) - - // case 'string': - // default : - // return arg - // } - - /* return if empty string */ - if ( - arg === '' || - arg === undefined || - arg === null - ) { - return arg - } - - if (!isNaN(arg)) { - return Number(arg) - } - - if (arg === 'true') { - return true - } - - if (arg === 'false') { - return false - } - - try { - const json = JSON.parse(arg) - return json - } catch (e) { - // this.log.warn('parseAttribute, parse JSON, not a json') - /* not a json, continue */ - /* probably print in debug mode */ - } - - return arg -} - -@Directive({ - selector : '[ivparseattribute]', -}) - -export class ParseAttributeDirective implements OnChanges { - public ngOnChanges(simpleChanges: SimpleChanges) { - Object.keys(simpleChanges).forEach(key => { - this[key] = parseAttribute(simpleChanges[key].currentValue, typeof simpleChanges[key].previousValue) - }) - } -} diff --git a/src/components/smartChip/component/smartChip.component.ts b/src/components/smartChip/component/smartChip.component.ts index b68cfb42769e31067b42614351e8db04717fdcf7..99eed11983dd317c32c5f4875e93f640caf8bf95 100644 --- a/src/components/smartChip/component/smartChip.component.ts +++ b/src/components/smartChip/component/smartChip.component.ts @@ -13,12 +13,12 @@ const cssColorToHsl = (input: string) => { parseInt(match[2]), parseInt(match[3]), ] - const [_h, _s, l] = rgbToHsl(...rgb) + const [ /* _h */, /* _s */, l] = rgbToHsl(...rgb) return l } if (/hsl/i.test(input)) { const match = /\((.*)\)/.exec(input) - const [h, s, l] = match[1].split(",") + const [ /* _h */, /* _s */, l] = match[1].split(",") const trimmedL = l.trim() if (/%$/.test(trimmedL)) { const match = /^([0-9]+)%/.exec(trimmedL) @@ -27,7 +27,7 @@ const cssColorToHsl = (input: string) => { } if (/^#/i.test(input) && input.length === 7) { const [r, g, b] = hexToRgb(input) - const [_h, _s, l] = rgbToHsl(r, g, b) + const [ /*_h */, /*_s */, l] = rgbToHsl(r, g, b) return l } throw new Error(`Cannot parse css color: ${input}`) diff --git a/src/features/category-acc.directive.ts b/src/features/category-acc.directive.ts index cddba9e69912494aa8a156eb802fe2b25f76ad1e..73156e7af17e6cf4e39e97fca101655eb02b07bb 100644 --- a/src/features/category-acc.directive.ts +++ b/src/features/category-acc.directive.ts @@ -118,7 +118,8 @@ export class CategoryAccDirective implements AfterContentInit, OnDestroy { return this.datasource }), ) - }) + }), + shareReplay(1), ) constructor(){ diff --git a/src/features/connectivity/connectivityBrowser/connectivityBrowser.component.ts b/src/features/connectivity/connectivityBrowser/connectivityBrowser.component.ts index dbc546a7ead3d44685043b5654767b8499debe28..77b69d99dccae4d04870b68d7ac6a223b59548df 100644 --- a/src/features/connectivity/connectivityBrowser/connectivityBrowser.component.ts +++ b/src/features/connectivity/connectivityBrowser/connectivityBrowser.component.ts @@ -1,7 +1,7 @@ import { Component, ElementRef, OnDestroy, ViewChild, Input, SimpleChanges, HostListener, OnChanges } from "@angular/core"; import { Store, select} from "@ngrx/store"; import { Subscription, BehaviorSubject, combineLatest, merge, concat, NEVER} from "rxjs"; -import { switchMap, map, tap, shareReplay, distinctUntilChanged, withLatestFrom, filter, finalize } from "rxjs/operators"; +import { switchMap, map, shareReplay, distinctUntilChanged, withLatestFrom, filter, finalize } from "rxjs/operators"; import { atlasAppearance, atlasSelection } from "src/state"; import { SAPI } from "src/atlasComponents/sapi/sapi.service"; @@ -156,13 +156,15 @@ export class ConnectivityBrowserComponent implements OnChanges, OnDestroy { * on * - accordion update * - colormap change - * - fetching matrix + * - fetching matrix flag is true * remove custom layer */ merge( this.#accordionExpanded$, this.colormap$, - this.#fetchingMatrix$, + this.#fetchingMatrix$.pipe( + filter(flag => !!flag) + ), ).subscribe(() => { this.removeCustomLayer() }), @@ -207,6 +209,7 @@ export class ConnectivityBrowserComponent implements OnChanges, OnDestroy { * on pure connection update, update logchecked box */ this.#pureConnections$.subscribe(v => { + if (!v) return for (const val of Object.values(v)) { if (val > 1) { this.displayForm.get("logChecked").enable() @@ -256,30 +259,32 @@ export class ConnectivityBrowserComponent implements OnChanges, OnDestroy { } const typedName = getType(selectedType.name) - if (!guardType(typedName)) { - throw new Error(`type ${typedName} is not in ${validTypes.join(',')}`) - } const query = { parcellation_id: parc.id, type: typedName } this.busy$.next(true) - return this.sapi.v3Get( - "/feature/RegionalConnectivity", - { query } - ).pipe( - switchMap(resp => - this.sapi.iteratePages( - resp, - page => this.sapi.v3Get( - "/feature/RegionalConnectivity", - { query: { ...query, page } } - ) - ) + return concat( + of( + [] as PathReturn<"/feature/RegionalConnectivity/{feature_id}">[], ), - finalize(() => { - this.busy$.next(false) - }) + this.sapi.v3Get( + "/feature/RegionalConnectivity", + { query } + ).pipe( + switchMap(resp => + this.sapi.iteratePages( + resp, + page => this.sapi.v3Get( + "/feature/RegionalConnectivity", + { query: { ...query, page } } + ) + ) + ), + finalize(() => { + this.busy$.next(false) + }) + ) ) }) )), @@ -328,7 +333,7 @@ export class ConnectivityBrowserComponent implements OnChanges, OnDestroy { displaySubject$ = this.selectedDataset$.pipe( distinctUntilChanged((o, n) => o?.id === n?.id), map(ds => { - return (idx: number) => ds.subjects[idx] + return (idx: number) => ds?.subjects?.[idx] }) ) @@ -336,7 +341,7 @@ export class ConnectivityBrowserComponent implements OnChanges, OnDestroy { map(ds => ds ? ds.datasets : []) ) - #fetchingMatrix$ = new BehaviorSubject<boolean>(true) + #fetchingMatrix$ = new BehaviorSubject<boolean>(false) #matrixInput$ = combineLatest([ this.parcellation$, @@ -346,7 +351,8 @@ export class ConnectivityBrowserComponent implements OnChanges, OnDestroy { map(([ parcellation, form, dss ]) => { const { selectedDatasetIndex: dsIdx, - selectedSubjectIndex: subIdx + selectedSubjectIndex: subIdx, + selectedView } = form const ds = dss[dsIdx] if (!ds) { @@ -360,7 +366,8 @@ export class ConnectivityBrowserComponent implements OnChanges, OnDestroy { return { parcellation, feature_id: ds.id, - subject + subject, + selectedView } }), shareReplay(1), @@ -379,26 +386,33 @@ export class ConnectivityBrowserComponent implements OnChanges, OnDestroy { { query: { parcellation_id: parcellation.id, - subject, + ...(input.selectedView === "average" + ? {} + : { subject }) }, path: { feature_id } } + ).pipe( + finalize(() => { + this.#fetchingMatrix$.next(false) + }) ) }), - tap(() => this.#fetchingMatrix$.next(false)), shareReplay(1), ) #pureConnections$ = this.#matrixInput$.pipe( - filter(v => !!v), - switchMap(({ subject }) => + switchMap(matrixInput => this.#selectedMatrix$.pipe( - filter(v => !!v.matrices[subject]), withLatestFrom(this.region$), map(([ v, region ]) => { - const b = v.matrices[subject] + const matrixKey = matrixInput?.selectedView === "average" ? "_average" : matrixInput?.subject + if (!v || !matrixInput || !v.matrices?.[matrixKey]) { + return null + } + const b = v.matrices[matrixKey] const foundIdx = b.columns.findIndex(v => v['name'] === region.name) if (typeof foundIdx !== 'number') { return null @@ -429,6 +443,7 @@ export class ConnectivityBrowserComponent implements OnChanges, OnDestroy { distinctUntilChanged() ) ]).pipe( + filter(conn => !!conn), map(([ conn, flag ]) => processProfile(conn, flag)) ) )) @@ -545,12 +560,6 @@ function getType(name: string) { return name.split(".").slice(-1)[0] } -const validTypes = ["FunctionalConnectivity", "StreamlineCounts", "StreamlineLengths"] - -function guardType(name: unknown): name is "FunctionalConnectivity" | "StreamlineCounts" | "StreamlineLengths" { - return typeof name === "string" && validTypes.includes(name) -} - type ConnectedArea = { color: {r: number, g: number, b: number} name: string diff --git a/src/features/connectivity/connectivityBrowser/connectivityBrowser.template.html b/src/features/connectivity/connectivityBrowser/connectivityBrowser.template.html index a7061fe7a83fedfb63c776e39deb613d1c5008dd..416763f02dab74f3dd141ab7d954d7155739a5b7 100644 --- a/src/features/connectivity/connectivityBrowser/connectivityBrowser.template.html +++ b/src/features/connectivity/connectivityBrowser/connectivityBrowser.template.html @@ -16,29 +16,37 @@ </mat-select> </mat-form-field> - <mat-form-field *ngIf="!(busy$ | async) && (formValue$ | async | getProperty : 'selectedType')" class="flex-grow-1 flex-shrink-1 w-100"> - <mat-label> - Cohort - </mat-label> - <mat-select formControlName="selectedCohort"> - <mat-option *ngFor="let cohort of cohorts$ | async" - [value]="cohort"> - {{ cohort }} - </mat-option> - </mat-select> - </mat-form-field> - </div> - </ng-template> + <ng-template [ngIf]="cohorts$ | async" let-cohorts> + <ng-template [ngIf]="cohorts.length > 0"> - <ng-template [ngIf]="formValue$ | async | getProperty : 'selectedCohort'"> - <mat-radio-group formControlName="selectedView"> - <mat-radio-button value="average" class="m-2" color="primary"> - Average - </mat-radio-button> - <mat-radio-button value="subject" class="m-2" color="primary"> - Subject - </mat-radio-button> - </mat-radio-group> + <mat-form-field class="flex-grow-1 flex-shrink-1 w-100"> + <mat-label> + Cohort + </mat-label> + <mat-select formControlName="selectedCohort"> + <mat-option *ngFor="let cohort of cohorts$ | async" + [value]="cohort"> + {{ cohort }} + </mat-option> + </mat-select> + </mat-form-field> + + + <ng-template [ngIf]="formValue$ | async | getProperty : 'selectedCohort'"> + <mat-radio-group formControlName="selectedView"> + <mat-radio-button value="average" class="m-2" color="primary"> + Average + </mat-radio-button> + <mat-radio-button value="subject" class="m-2" color="primary"> + Subject + </mat-radio-button> + </mat-radio-group> + </ng-template> + + </ng-template> + </ng-template> + + </div> </ng-template> <ng-template [ngIf]="cohortDatasets$ | async" let-cohortDatasets> @@ -84,7 +92,7 @@ <!-- loading spinner --> <ng-template [ngIf]="view$ | async | getProperty : 'fetchingMatrix'" [ngIfElse]="profileTmpl"> - <div class="d-flex justify-content-center" id = 'blabla'> + <div class="d-flex justify-content-center"> <mat-spinner></mat-spinner> </div> </ng-template> @@ -92,7 +100,13 @@ <!-- profile --> <!-- <pre>{{ view$ | async | json }}</pre> --> <ng-template #profileTmpl> - <ng-template [ngIf]="view$ | async | getProperty : 'connections'" let-conn> + + <ng-template #noConnTmpl> + No connectivity Found + </ng-template> + + <ng-template [ngIf]="view$ | async | getProperty : 'connections'" let-conn + [ngIfElse]="noConnTmpl"> <div class="d-flex align-items-center"> <form [formGroup]="displayForm"> <mat-checkbox diff --git a/src/features/entry/entry.component.ts b/src/features/entry/entry.component.ts index 979fea404ef798b8f0bfc510477bf423782938b0..e4cfbe75e126083681838c1fe815bca654737345 100644 --- a/src/features/entry/entry.component.ts +++ b/src/features/entry/entry.component.ts @@ -1,20 +1,21 @@ import { AfterViewInit, Component, OnDestroy, QueryList, ViewChildren } from '@angular/core'; import { select, Store } from '@ngrx/store'; -import { distinctUntilChanged, map, scan, shareReplay, switchMap, tap } from 'rxjs/operators'; +import { debounceTime, distinctUntilChanged, map, scan, shareReplay, switchMap, withLatestFrom } from 'rxjs/operators'; import { IDS, SAPI } from 'src/atlasComponents/sapi'; import { Feature } from 'src/atlasComponents/sapi/sxplrTypes'; import { FeatureBase } from '../base'; import * as userInteraction from "src/state/userInteraction" import { atlasSelection } from 'src/state'; import { CategoryAccDirective } from "../category-acc.directive" -import { BehaviorSubject, combineLatest, concat, merge, of, Subscription } from 'rxjs'; +import { combineLatest, concat, forkJoin, merge, of, Subject, Subscription } from 'rxjs'; import { DsExhausted, IsAlreadyPulling, PulledDataSource } from 'src/util/pullable'; import { TranslatedFeature } from '../list/list.directive'; +import { SPECIES_ENUM } from 'src/util/constants'; const categoryAcc = <T extends Record<string, unknown>>(categories: T[]) => { const returnVal: Record<string, T[]> = {} for (const item of categories) { - const { category, ...rest } = item + const { category } = item if (!category) continue if (typeof category !== "string") continue if (!returnVal[category]) { @@ -33,56 +34,63 @@ const categoryAcc = <T extends Record<string, unknown>>(categories: T[]) => { }) export class EntryComponent extends FeatureBase implements AfterViewInit, OnDestroy { - private _features$ = new BehaviorSubject<TranslatedFeature[]>([]) - features$ = this._features$.pipe( - shareReplay(1) - ) - @ViewChildren(CategoryAccDirective) catAccDirs: QueryList<CategoryAccDirective> - public busyTallying$ = new BehaviorSubject<boolean>(false) - public totals$ = new BehaviorSubject<number>(null) - constructor(private sapi: SAPI, private store: Store) { super() } #subscriptions: Subscription[] = [] + #catAccDirs = new Subject<CategoryAccDirective[]>() + features$ = this.#catAccDirs.pipe( + switchMap(dirs => concat( + of([] as TranslatedFeature[]), + merge(...dirs.map((dir, idx) => + dir.datasource$.pipe( + switchMap(ds => ds.data$), + map(val => ({ val, idx })) + )) + ).pipe( + map(({ idx, val }) => ({ [idx.toString()]: val })), + scan((acc, curr) => ({ ...acc, ...curr })), + map(record => Object.values(record).flatMap(v => v)) + ) + )), + shareReplay(1), + ) - private _busy$ = new BehaviorSubject<boolean>(false) - busy$ = this._busy$.pipe( + busy$ = this.#catAccDirs.pipe( + switchMap(dirs => combineLatest( + dirs.map(dir => dir.isBusy$) + )), + map(flags => flags.some(flag => flag)), + distinctUntilChanged(), shareReplay(1) ) - ngOnDestroy(): void { - while (this.#subscriptions.length > 0) this.#subscriptions.pop().unsubscribe() - } - ngAfterViewInit(): void { - const catAccDirs$ = merge( - of(null), - this.catAccDirs.changes - ).pipe( - map(() => Array.from(this.catAccDirs)) - ) - this.#subscriptions.push( - catAccDirs$.pipe( - switchMap(dirs => combineLatest( - dirs.map(dir => dir.isBusy$) - )), - map(flags => flags.some(flag => flag)), - distinctUntilChanged(), - ).subscribe(value => { - this._busy$.next(value) - }), - catAccDirs$.pipe( - tap(() => this.busyTallying$.next(true)), - switchMap(catArrDirs => merge( - ...catArrDirs.map((dir, idx) => dir.total$.pipe( - map(val => ({ idx, val })) - )) - )), - + public busyTallying$ = this.#catAccDirs.pipe( + switchMap(arr => concat( + of(true), + forkJoin( + arr.map(dir => dir.total$) + ).pipe( + map(() => false) + ) + )), + shareReplay(1) + ) + + public totals$ = this.#catAccDirs.pipe( + switchMap(arr => concat( + of(0), + merge( + ...arr.map((dir, idx) => + dir.total$.pipe( + map(val => ({ val, idx })) + ) + ) + ).pipe( map(({ idx, val }) => ({ [idx.toString()]: val })), scan((acc, curr) => ({ ...acc, ...curr })), map(record => { @@ -92,11 +100,46 @@ export class EntryComponent extends FeatureBase implements AfterViewInit, OnDest } return tally }), - tap(num => { - this.busyTallying$.next(false) - this.totals$.next(num) - }), - ).subscribe(), + ) + )) + ) + + ngOnDestroy(): void { + while (this.#subscriptions.length > 0) this.#subscriptions.pop().unsubscribe() + } + ngAfterViewInit(): void { + this.#subscriptions.push( + merge( + of(null), + this.catAccDirs.changes + ).pipe( + map(() => Array.from(this.catAccDirs)) + ).subscribe(dirs => this.#catAccDirs.next(dirs)), + + this.#pullAll.pipe( + debounceTime(320), + withLatestFrom(this.#catAccDirs), + switchMap(([_, dirs]) => combineLatest(dirs.map(dir => dir.datasource$))), + ).subscribe(async dss => { + await Promise.all( + dss.map(async ds => { + // eslint-disable-next-line no-constant-condition + while (true) { + try { + await ds.pull() + } catch (e) { + if (e instanceof DsExhausted) { + break + } + if (e instanceof IsAlreadyPulling ) { + continue + } + throw e + } + } + }) + ) + }) ) } @@ -106,10 +149,10 @@ export class EntryComponent extends FeatureBase implements AfterViewInit, OnDest public showConnectivity$ = combineLatest([ this.selectedAtlas$.pipe( - map(atlas => atlas?.species === "Homo sapiens") + map(atlas => atlas?.species === SPECIES_ENUM.HOMO_SAPIENS || atlas?.species === SPECIES_ENUM.RATTUS_NORVEGICUS) ), this.TPRBbox$.pipe( - map(({ parcellation }) => parcellation?.id === IDS.PARCELLATION.JBA29) + map(({ parcellation }) => parcellation?.id === IDS.PARCELLATION.JBA29 || parcellation?.id === IDS.PARCELLATION.WAXHOLMV4) ) ]).pipe( map(flags => flags.every(f => f)) @@ -167,28 +210,8 @@ export class EntryComponent extends FeatureBase implements AfterViewInit, OnDest } } - async pullAll(){ - const dss = Array.from(this.catAccDirs).map(catAcc => catAcc.datasource) - - this._features$.next([]) - await Promise.all( - dss.map(async ds => { - // eslint-disable-next-line no-constant-condition - while (true) { - try { - await ds.pull() - } catch (e) { - if (e instanceof DsExhausted) { - break - } - if (e instanceof IsAlreadyPulling ) { - continue - } - throw e - } - } - }) - ) - this._features$.next(dss.flatMap(ds => ds.finalValue)) + #pullAll = new Subject() + pullAll(){ + this.#pullAll.next(null) } } diff --git a/src/features/voi-bbox.directive.ts b/src/features/voi-bbox.directive.ts index d6ed2e0aac4ca11816b5f214699ed9c07cc8a47d..f150225b9cf66f5ce2f68f26c7a936f1c30f6576 100644 --- a/src/features/voi-bbox.directive.ts +++ b/src/features/voi-bbox.directive.ts @@ -47,7 +47,7 @@ export class VoiBboxDirective implements OnDestroy { @Input() set features(feats: Feature[]){ - this.#voiFeatures = feats.filter(isVoiData) + this.#voiFeatures = (feats || []).filter(isVoiData) this.#features$.next(this.#voiFeatures) } get features(): VoiFeature[]{ diff --git a/src/index.html b/src/index.html index 0f009f4f2d5ab33219754015cfc136a5c8758899..1ed2d8187899e51442bc39c8ab9305ac15c7ea0f 100644 --- a/src/index.html +++ b/src/index.html @@ -14,7 +14,7 @@ <script src="extra_js.js"></script> <script src="https://unpkg.com/kg-dataset-previewer@1.2.0/dist/kg-dataset-previewer/kg-dataset-previewer.js" defer></script> <script src="https://unpkg.com/three-surfer@0.0.13/dist/bundle.js" defer></script> - <script type="module" src="https://unpkg.com/ng-layer-tune@0.0.6/dist/ng-layer-tune/ng-layer-tune.esm.js"></script> + <script type="module" src="https://unpkg.com/ng-layer-tune@0.0.13/dist/ng-layer-tune/ng-layer-tune.esm.js"></script> <script type="module" src="https://unpkg.com/hbp-connectivity-component@0.6.6/dist/connectivity-component/connectivity-component.js" ></script> <script defer src="https://unpkg.com/mathjax@3.1.2/es5/tex-svg.js"></script> <script defer src="https://unpkg.com/d3@6.2.0/dist/d3.min.js"></script> diff --git a/src/keyframesModule/keyframeCtrl/keyframeCtrl.component.ts b/src/keyframesModule/keyframeCtrl/keyframeCtrl.component.ts index 6f909d5280166c3544c8f3ec81ed0610db49fa88..1b39b366a26ad75ffd55b1a2af12fc78679dd20e 100644 --- a/src/keyframesModule/keyframeCtrl/keyframeCtrl.component.ts +++ b/src/keyframesModule/keyframeCtrl/keyframeCtrl.component.ts @@ -171,13 +171,11 @@ export class KeyFrameCtrlCmp implements OnDestroy { ) const startVec = vec1.clone() - const vec1Length = vec1.length() const vec2 = new THREE.Vector3( toPayloadCamera.x, toPayloadCamera.y, toPayloadCamera.z, ) - const vec2Length = vec2.length() vec1.normalize() vec2.normalize() diff --git a/src/messaging/nmvSwc/index.ts b/src/messaging/nmvSwc/index.ts index d7bd4d6a767d013e2e357cb98d79202a7ecfed08..87e4b10ef6a82fee3b44926a4e7e02684274433c 100644 --- a/src/messaging/nmvSwc/index.ts +++ b/src/messaging/nmvSwc/index.ts @@ -86,12 +86,6 @@ const getAtlas = (spaceId: SPACE_ID) => { return DEFAULT_ATLAS[spaceId] } -const getVoxelFromSpace = (spaceId: string) => { - for (const key in NM_IDS){ - if (NM_IDS[key] === spaceId) return IAV_VOXEL_SIZES_NM[key] - } - return null -} export const processJsonLd = (json: { [key: string]: any }): Observable<IMessagingActions<keyof IMessagingActionTmpl>> => { const subject = new Subject<IMessagingActions<keyof IMessagingActionTmpl>>() @@ -156,27 +150,16 @@ export const processJsonLd = (json: { [key: string]: any }): Observable<IMessagi ) const uuid = getUuid() - // NG internal treats skeleton as mm - const voxelSize = getVoxelFromSpace(toSpace) - /** - * swc seem to scale with voxelSize... strangely enough - * voxelSize nm / voxel -> goal is 1 voxel/um - * 1e3 / voxelSize - */ - const scaleUmToVoxelFixed = [ - voxelSize[0], - voxelSize[1], - voxelSize[2], - ] // NG translation works on nm scale const scaleUmToNm = 1e3 const modA = mat3.fromValues( - scaleUmToVoxelFixed[0], 0, 0, - 0, scaleUmToVoxelFixed[1], 0, - 0, 0, scaleUmToVoxelFixed[2] + scaleUmToNm, 0, 0, + 0, scaleUmToNm, 0, + 0, 0, scaleUmToNm ) mat3.mul(modA, modA, [...A[0], ...A[1], ...A[2]]) - const modb = vec3.scale(vec3.create(), b, scaleUmToNm) + const modb = vec3.mul(vec3.create(), b, [ scaleUmToNm, scaleUmToNm, scaleUmToNm]) + vec3.scale(vec3.create(), b, scaleUmToNm) const transform = [ [...modA.slice(0, 3), modb[0]] as TVec4, [...modA.slice(3, 6), modb[1]] as TVec4, diff --git a/src/messaging/service.spec.ts b/src/messaging/service.spec.ts index 8ba8cad4e3cdaf0d776fd5ff2f1e1cc0958c8b08..05da2a3999f7c350a304b3fca3e997029bec624e 100644 --- a/src/messaging/service.spec.ts +++ b/src/messaging/service.spec.ts @@ -1,5 +1,5 @@ import { TestBed } from "@angular/core/testing" -import { MockStore, provideMockStore } from "@ngrx/store/testing" +import { provideMockStore } from "@ngrx/store/testing" import { AtlasWorkerService } from "src/atlasViewer/atlasViewer.workerService.service" import { AngularMaterialModule } from "src/sharedModules" import { getUuid } from "src/util/fn" @@ -9,6 +9,7 @@ import { MANAGED_METHODS } from './service' import { of, Subject } from "rxjs" import { ConfirmDialogComponent } from "src/components/confirmDialog/confirmDialog.component" import { TYPE as NATIVE_TYPE } from './native' +import { ApiService } from "src/api" describe('> service.ts', () => { describe('> MessagingService', () => { @@ -34,6 +35,17 @@ describe('> service.ts', () => { { provide: WINDOW_MESSAGING_HANDLER_TOKEN, useValue: windowMessagehandler + }, + { + provide: ApiService, + useValue: { + booth: { + handshake(){} + }, + broadcastCh: { + addListener(){} + } + } } ] }) @@ -118,7 +130,7 @@ describe('> service.ts', () => { it('> pong', async () => { const result = await mService.handleMessage({ data: { - method: `${IAV_POSTMESSAGE_NAMESPACE}ping` + method: `ping` }, origin: 'foobar' }) diff --git a/src/messaging/service.ts b/src/messaging/service.ts index 365a2d1880bd4513bb088b49ff0b761faa30b36c..80a97ca603746ffd3543761946d211ce11a48ff4 100644 --- a/src/messaging/service.ts +++ b/src/messaging/service.ts @@ -1,29 +1,44 @@ import { Inject, Injectable, Optional } from "@angular/core"; import { Observable } from "rxjs"; import { MatDialog } from "@angular/material/dialog"; -import { MatSnackBar } from "@angular/material/snack-bar"; - -import { getUuid } from "src/util/fn"; -import { AtlasWorkerService } from "src/atlasViewer/atlasViewer.workerService.service"; +import { getUuid, noop } from "src/util/fn"; import { ConfirmDialogComponent } from "src/components/confirmDialog/confirmDialog.component"; import { IMessagingActions, IMessagingActionTmpl, WINDOW_MESSAGING_HANDLER_TOKEN, IWindowMessaging } from './types' import { TYPE as NMV_TYPE, processJsonLd as nmvProcess } from './nmvSwc/index' import { TYPE as NATIVE_TYPE, processJsonLd as nativeProcess } from './native' +import { BoothVisitor, JRPCRequest, ListenerChannel } from "src/api/jsonrpc" +import { ApiService } from "src/api"; +import { ApiBoothEvents, namespace as apiNameSpace } from "src/api/service"; export const IAV_POSTMESSAGE_NAMESPACE = `ebrains:iav:` +const RECOGNISED_NAMESPACES = [ + IAV_POSTMESSAGE_NAMESPACE, + apiNameSpace +] + export const MANAGED_METHODS = [ 'openminds:nmv:loadSwc', 'openminds:nmv:unloadSwc' ] +class WindowOpenerListener implements ListenerChannel { + constructor( + public registerLeaveCb: () => void, + public notify: (payload: JRPCRequest<unknown, unknown>) => void + ){} + +} + @Injectable({ providedIn: 'root' }) export class MessagingService { + private originListenerMap = new Map<string, {listener: WindowOpenerListener, visitor: BoothVisitor<ApiBoothEvents>}>() + private whiteListedOrigins = new Set() private pendingRequests: Map<string, Promise<boolean>> = new Map() private windowName: string @@ -32,8 +47,7 @@ export class MessagingService { constructor( private dialog: MatDialog, - private snackbar: MatSnackBar, - private worker: AtlasWorkerService, + private apiService: ApiService, @Optional() @Inject(WINDOW_MESSAGING_HANDLER_TOKEN) private messagingHandler: IWindowMessaging, ){ @@ -60,11 +74,36 @@ export class MessagingService { window.addEventListener('message', async ({ data, origin, source }) => { if (/^webpack/.test(data.type)) return - if (data === '') return + if (!data) return + const { method } = data + if (RECOGNISED_NAMESPACES.every(namespace => (method || '').indexOf(namespace) !== 0)) { + return + } const src = source as Window const { id } = data try { const result = await this.handleMessage({ data, origin }) + if (!this.originListenerMap.has(origin)) { + const listener = new WindowOpenerListener( + noop, + val => src.postMessage(val, origin) + ) + + const visitor = this.apiService.booth.handshake() + this.originListenerMap.set(origin, {listener, visitor}) + + this.apiService.broadcastCh.addListener(listener) + + + /** + * if result was not yet populated, try populating it with + * siibra-explorer api + */ + } + if (!result) { + const { visitor } = this.originListenerMap.get(origin) + return await visitor.request(data) + } src.postMessage({ id, jsonrpc: '2.0', @@ -94,27 +133,25 @@ export class MessagingService { public async handleMessage({ data, origin }) { const { method, param } = data - - if (!method) return - if (method.indexOf(IAV_POSTMESSAGE_NAMESPACE) !== 0) return - const strippedMethod = method.replace(IAV_POSTMESSAGE_NAMESPACE, '') - /** * if ping method, respond pong method */ - if (strippedMethod === 'ping') { + if (method === 'ping') { return 'pong' } /** * otherwise, check permission */ - const allow = await this.checkOrigin({ origin }) if (!allow) throw ({ code: 403, message: 'User declined' }) + + if (!method) return + if (method.indexOf(IAV_POSTMESSAGE_NAMESPACE) !== 0) return + const strippedMethod = method.replace(IAV_POSTMESSAGE_NAMESPACE, '') // TODO // in future, check if in managed_methods @@ -169,50 +206,11 @@ export class MessagingService { // TODO combine api service and messaging service into one // and implement it properly - // if (method === 'viewerHandle:add3DLandmarks') { - // this.apiService.interactiveViewer.viewerHandle.add3DLandmarks(param) - // return 'OK' - // } - - // if (method === 'viewerHandle:remove3DLandmarks') { - // this.apiService.interactiveViewer.viewerHandle.remove3DLandmarks(param) - // return 'OK' - // } - - /** - * TODO use loadResource in the future - */ - if (method === '_tmp:plotly') { - const isLoadingSnack = this.snackbar.open(`Loading plotly mesh ...`) - const resp = await this.worker.sendMessage({ - method: `PROCESS_PLOTLY`, - param - }) - isLoadingSnack?.dismiss() - const meshId = 'bobby' - /** - * TODO re-enable plotly VTK mesh - */ - - // if (false) { - // const { objectUrl, customFragmentColor } = resp.result || {} - // this.loadMesh({ - // type: 'VTK', - // id: meshId, - // url: objectUrl, - // customFragmentColor - // }) - // } else { - // this.snackbar.open(`Error: loadMesh method not injected.`) - // } - return 'OK' - } - if (MANAGED_METHODS.indexOf(method) >= 0) { return await this.processJsonld(param) } - throw ({ code: 404, message: 'Method not found' }) + return } async checkOrigin({ origin }): Promise<boolean> { diff --git a/src/mouseoverModule/type.ts b/src/mouseoverModule/type.ts deleted file mode 100644 index 38033f903e9047b4164c90137564a19d0f75ae86..0000000000000000000000000000000000000000 --- a/src/mouseoverModule/type.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { TRegionSummary } from "src/util/siibraApiConstants/types"; - -export type TMouseOverVtkLandmark = { - landmarkName: string -} \ No newline at end of file diff --git a/src/overwrite.scss b/src/overwrite.scss index 9fd0bb0b88e2fc3039a03040dfa659a2ec341cf8..5b17076e66aca6d569e24b3e6f5d0b6ab4c65f32 100644 --- a/src/overwrite.scss +++ b/src/overwrite.scss @@ -289,3 +289,38 @@ a[mat-raised-button] { pointer-events: none!important; } + +// list like button +$llb: "#{$nsp}-list-like-button"; +button.#{$llb} +{ + width: 100%; + + > .mat-button-wrapper + { + display: flex; + + > .#{$llb}-icon + { + width: 2rem; + flex: 0 0 auto; + margin: auto; + font-size: 150%; + } + + > .#{$llb}-body + { + flex: 1 1 0px; + display: flex; + flex-direction: column; + margin:1rem; + + .#{$llb}-body-line + { + flex: 0 0 0px; + text-align: start; + line-height: 1.5rem; + } + } + } +} diff --git a/src/plugin/plugin.module.ts b/src/plugin/plugin.module.ts index 998ca4056ecbb7a069133b021bc3e0aab8605fdc..563a7d9dbb010c2e5654ff7631e1168098c52919 100644 --- a/src/plugin/plugin.module.ts +++ b/src/plugin/plugin.module.ts @@ -1,10 +1,9 @@ -import { CommonModule, DOCUMENT } from "@angular/common"; +import { CommonModule } from "@angular/common"; import { HttpClientModule } from "@angular/common/http"; import { NgModule } from "@angular/core"; import { LoggingModule } from "src/logging"; import { AngularMaterialModule } from "src/sharedModules"; import { UtilModule } from "src/util"; -import { appendScriptFactory, APPEND_SCRIPT_TOKEN, removeScriptFactory, REMOVE_SCRIPT_TOKEN } from "src/util/constants"; import { IFrameSrcPipe } from "./iframeSrc.pipe"; import { PluginBannerUI } from "./pluginBanner/pluginBanner.component"; import { PluginPortal } from "./pluginPortal/pluginPortal.component"; diff --git a/src/routerModule/routeStateTransform.service.spec.ts b/src/routerModule/routeStateTransform.service.spec.ts index a341cdd6d405e275beedfd82a29c8f42ef3f36a5..2dee59c937f86923ca06f41c073aca2a22dcc0ba 100644 --- a/src/routerModule/routeStateTransform.service.spec.ts +++ b/src/routerModule/routeStateTransform.service.spec.ts @@ -6,6 +6,7 @@ import { DefaultUrlSerializer } from "@angular/router" import * as nehubaConfigService from "src/viewerModule/nehuba/config.service" import { atlasSelection, userInteraction } from "src/state" import { encodeNumber } from "./cipher" +import { QuickHash } from "src/util/fn" const serializer = new DefaultUrlSerializer() @@ -123,7 +124,7 @@ describe("> routeStateTransform.service.ts", () => { const altasObj = {"@id": 'foo-bar-a'} const templObj = {"@id": 'foo-bar-t'} const parcObj = {"@id": 'foo-bar-p'} - const regions = [{}] + const regions = [{'name': 'selected-region-1'}] const standAloneVolumes = [] const navigation = null @@ -181,9 +182,12 @@ describe("> routeStateTransform.service.ts", () => { const getRegionLabelIndicesSpy = sapi.getRegionLabelIndices as jasmine.Spy getRegionLabelIndicesSpy.and.resolveTo(labelIndex) - const s = await svc.cvtStateToRoute(state as any) - expect(s).toContain(`r:${ngId}::${encodeNumber(labelIndex, { float: false })}`) + + expect(getParcNgId).not.toHaveBeenCalled() + expect(getRegionLabelIndicesSpy).not.toHaveBeenCalled() + + expect(s).toContain(`rn:${QuickHash.GetHash(regions[0].name)}`) }) it('> ngId containing expected value', async () => { diff --git a/src/routerModule/routeStateTransform.service.ts b/src/routerModule/routeStateTransform.service.ts index 1032448ec67ea1c9bf80d3f18f82f901bf7c6057..b01ac54beb7085b01ea548ac2de69b4d0a103fff 100644 --- a/src/routerModule/routeStateTransform.service.ts +++ b/src/routerModule/routeStateTransform.service.ts @@ -9,6 +9,7 @@ import { getParcNgId } from "src/viewerModule/nehuba/config.service"; import { decodeToNumber, encodeNumber, encodeURIFull, separator } from "./cipher"; import { TUrlAtlas, TUrlPathObj, TUrlStandaloneVolume } from "./type"; import { decodePath, encodeId, decodeId, encodePath } from "./util"; +import { QuickHash } from "src/util/fn"; @Injectable() export class RouteStateTransformSvc { @@ -26,6 +27,7 @@ export class RouteStateTransformSvc { const selectedTemplateId = decodeId( RouteStateTransformSvc.GetOneAndOnlyOne(obj.t) ) const selectedParcellationId = decodeId( RouteStateTransformSvc.GetOneAndOnlyOne(obj.p) ) const selectedRegionIds = obj.r + const selectedRegionNames = obj.rn if (!selectedAtlasId || !selectedTemplateId || !selectedParcellationId) { return {} @@ -60,7 +62,11 @@ export class RouteStateTransformSvc { const userViewer = await this.sapi.useViewer(selectedTemplate).toPromise() const selectedRegions = await (async () => { - if (!selectedRegionIds) return [] + if (!selectedRegionIds && !selectedRegionNames) return [] + + if (selectedRegionNames && selectedRegionNames.length > 0) { + return allParcellationRegions.filter(region => selectedRegionNames.includes(QuickHash.GetHash(region.name))) + } /** * should account for @@ -268,16 +274,7 @@ export class RouteStateTransformSvc { } } - // encoding selected regions - let selectedRegionsString: string - if (selectedRegions.length === 1) { - const region = selectedRegions[0] - const labelIndex = await this.sapi.getRegionLabelIndices(selectedTemplate, selectedParcellation, region) - - const ngId = getParcNgId(selectedAtlas, selectedTemplate, selectedParcellation, region) - selectedRegionsString = `${ngId}::${encodeNumber(labelIndex, { float: false })}` - } - let routes: TUrlPathObj<string, TUrlAtlas<string>> | TUrlPathObj<string, TUrlStandaloneVolume<string>> + let routes: TUrlPathObj<string|string[], TUrlAtlas<string|string[]>> | TUrlPathObj<string, TUrlStandaloneVolume<string>> routes = { // for atlas @@ -287,7 +284,8 @@ export class RouteStateTransformSvc { // for parcellation p: selectedParcellation && encodeId(selectedParcellation.id), // for regions - r: selectedRegionsString && encodeURIFull(selectedRegionsString), + // r: selectedRegionsString && encodeURIFull(selectedRegionsString), + rn: selectedRegions[0] && selectedRegions.map(r => QuickHash.GetHash(r.name)), // nav ['@']: cNavString, // showing dataset diff --git a/src/routerModule/type.ts b/src/routerModule/type.ts index 13205ad3e09e0969a8c419a5d9803b2883bc2de3..284b5e7a9441fd52be9281ad3dec5c749e390993 100644 --- a/src/routerModule/type.ts +++ b/src/routerModule/type.ts @@ -7,6 +7,7 @@ export type TUrlAtlas<T> = { t: T // template p: T // parcellation r?: T // region selected + rn?: T } export type TUrlPlugin<T> = { diff --git a/src/screenshot/screenshotCmp/screenshot.component.ts b/src/screenshot/screenshotCmp/screenshot.component.ts index 9af9d6b90a7d60af3438b2784906918b6f74aab0..2ccb1da45087f34c917a7626827ae14759cd48db 100644 --- a/src/screenshot/screenshotCmp/screenshot.component.ts +++ b/src/screenshot/screenshotCmp/screenshot.component.ts @@ -2,7 +2,7 @@ import { Component, HostListener, Inject, OnDestroy, Output, EventEmitter, Optio import { MatDialog } from "@angular/material/dialog"; import { MatSnackBar } from "@angular/material/snack-bar"; import { combineLatest, Observable, Subject, Subscription } from "rxjs"; -import { distinctUntilChanged, map, mapTo, shareReplay, startWith, switchMap, switchMapTo, tap } from "rxjs/operators"; +import { distinctUntilChanged, map, mapTo, shareReplay, startWith, switchMap, switchMapTo } from "rxjs/operators"; import { HANDLE_SCREENSHOT_PROMISE, TypeHandleScrnShotPromise } from "../util"; import { getDateString } from 'common/util' import { getUuid } from "src/util/fn"; diff --git a/src/services/uiService.service.ts b/src/services/uiService.service.ts index cc8774728fb1742e36722135960341c35f7c0599..2e6a580418690c4d3e1182aa352132d853189410 100644 --- a/src/services/uiService.service.ts +++ b/src/services/uiService.service.ts @@ -1,6 +1,6 @@ import { Injectable } from "@angular/core"; import {MatSnackBar, MatSnackBarConfig} from "@angular/material/snack-bar"; -import { MatDialog } from "@angular/material/dialog"; +import { MatDialog, MatDialogConfig } from "@angular/material/dialog"; import { ActionDialog } from "src/ui/actionDialog/actionDialog.component"; @Injectable({ @@ -18,7 +18,7 @@ export class UIService { return this.snackbar.open(message, actionBtnTxt, config) } - public showDialog(data, options){ + public showDialog(data: unknown, options: MatDialogConfig){ return this.dialog.open(ActionDialog, { ...options, data diff --git a/src/share/saneUrl/saneUrl.component.ts b/src/share/saneUrl/saneUrl.component.ts index f7fbed0ea2bb72dcb5500bcf3d2cec5623b51872..0ce99cce3b2ac0d5167380d9f4182455f2de36e5 100644 --- a/src/share/saneUrl/saneUrl.component.ts +++ b/src/share/saneUrl/saneUrl.component.ts @@ -1,6 +1,6 @@ import { Component, OnDestroy, Input } from "@angular/core"; import { Observable, merge, of, Subscription, BehaviorSubject, combineLatest } from "rxjs"; -import { startWith, mapTo, map, debounceTime, switchMap, catchError, shareReplay, filter, tap, takeUntil, distinctUntilChanged } from "rxjs/operators"; +import { startWith, mapTo, map, debounceTime, switchMap, catchError, shareReplay, filter, tap, distinctUntilChanged } from "rxjs/operators"; import { UntypedFormControl } from "@angular/forms"; import { ErrorStateMatcher } from "@angular/material/core"; import { Clipboard } from "@angular/cdk/clipboard"; @@ -88,7 +88,7 @@ export class SaneUrl implements OnDestroy{ ? of(false) : this.svc.getKeyVal(val).pipe( mapTo(false), - catchError((err, obs) => { + catchError((err) => { if (err instanceof NotFoundError) return of(true) return of(false) }) @@ -164,9 +164,7 @@ export class SaneUrl implements OnDestroy{ this.customUrl.value, this.stateTobeSaved ).subscribe( - resp => { - this.savingProgress$.next(ESavingProgress.DONE) - }, + () => this.savingProgress$.next(ESavingProgress.DONE), err => { this.customUrl.enable() diff --git a/src/share/saneUrl/saneUrl.service.ts b/src/share/saneUrl/saneUrl.service.ts index d78a8ac903089062ba3cc86ca009e627947f70c6..d838c72b83c63d084102e9327b93462821caa703 100644 --- a/src/share/saneUrl/saneUrl.service.ts +++ b/src/share/saneUrl/saneUrl.service.ts @@ -25,7 +25,7 @@ export class SaneUrlSvc implements IKeyValStore{ `${this.saneUrlRoot}${key}`, { responseType: 'json' } ).pipe( - catchError((err, obs) => { + catchError((err) => { const { status } = err if (status === 404) { return throwError(new NotFoundError('Not found')) diff --git a/src/state/atlasSelection/util.ts b/src/state/atlasSelection/util.ts index a76fd1f9f2e8fc45335a5b3695c65bb68322dfef..525df7f1fdc00f7528bdfbbc210a547db3b738ac 100644 --- a/src/state/atlasSelection/util.ts +++ b/src/state/atlasSelection/util.ts @@ -1,8 +1,6 @@ import { createSelector, select } from "@ngrx/store"; -import { forkJoin, of, pipe } from "rxjs"; -import { distinctUntilChanged, map, switchMap } from "rxjs/operators"; -import { SAPI } from "src/atlasComponents/sapi"; -import { translateV3Entities } from "src/atlasComponents/sapi/translateV3" +import { pipe } from "rxjs"; +import { distinctUntilChanged, map } from "rxjs/operators"; import { SxplrAtlas, SxplrParcellation, SxplrTemplate } from "src/atlasComponents/sapi/sxplrTypes"; import { jsonEqual } from "src/util/json"; import * as selectors from "./selectors" diff --git a/src/state/index.ts b/src/state/index.ts index 40e84c234c4bca8d7eb7fece2c2c999ab249629f..aa9964ec9c0a69f063874b6a8b65382a72e2025c 100644 --- a/src/state/index.ts +++ b/src/state/index.ts @@ -22,6 +22,7 @@ export { export * as generalActions from "./actions" +// eslint-disable-next-line @typescript-eslint/no-unused-vars function debug(reducer: ActionReducer<MainState>): ActionReducer<MainState> { return function(state, action) { console.log('state', state); @@ -71,6 +72,7 @@ export function getStoreEffects() { plugins.Effects, atlasSelection.Effect, userInterface.Effects, + userInteraction.Effect, ] } diff --git a/src/state/userInteraction/effects.ts b/src/state/userInteraction/effects.ts index 5d4986ad5965117705e6d1aa869407fabdbd8a65..7fd81d8ae1252522deba3c4f589eda32d61513e7 100644 --- a/src/state/userInteraction/effects.ts +++ b/src/state/userInteraction/effects.ts @@ -1,17 +1,33 @@ import { Injectable } from "@angular/core"; import { Actions, createEffect, ofType } from "@ngrx/effects"; -import * as atlasSelectionActions from "../atlasSelection/actions" import * as userInterface from "../userInterface" -import { mapTo } from "rxjs/operators"; +import * as atlasSelection from "../atlasSelection" +import * as actions from "./actions" +import { filter, map, mapTo, skip } from "rxjs/operators"; +import { Store } from "@ngrx/store"; @Injectable() export class Effect { onStandAloneVolumesExistCloseMatDrawer = createEffect(() => this.action.pipe( - ofType(atlasSelectionActions.clearStandAloneVolumes), + ofType(atlasSelection.actions.clearStandAloneVolumes), mapTo(userInterface.actions.closeSidePanel()) )) - constructor(private action: Actions){ + #distinctATP$ = this.store.pipe( + atlasSelection.fromRootStore.distinctATP(), + ) + + onATPUpdateUnselectFeature = createEffect(() => this.#distinctATP$.pipe( + filter(v => !!v.atlas && !!v.parcellation && !!v.template), + /** + * First non empty emit would be from selecting atlas. + * So ignore it. + */ + skip(1), + map(() => actions.clearShownFeature()) + )) + + constructor(private action: Actions, private store: Store){ } -} \ No newline at end of file +} diff --git a/src/ui/actionDialog/actionDialog.component.ts b/src/ui/actionDialog/actionDialog.component.ts index 0275f8cf576cf236cfe6878446347144412711c4..ced5d32d45c3de46755b1ff7af1f14b8331965b7 100644 --- a/src/ui/actionDialog/actionDialog.component.ts +++ b/src/ui/actionDialog/actionDialog.component.ts @@ -22,7 +22,7 @@ export class ActionDialog{ constructor( @Optional() @Inject(MAT_DIALOG_DATA) data: any ){ - const { config, content, template, actions = [] } = data || {} + const { config, content, actions = [] } = data || {} const { sameLine = false } = config || {} this.content = content @@ -30,7 +30,4 @@ export class ActionDialog{ this.actions = actions } - actionHandler(event: MouseEvent, action: IDialogAction){ - // TODO fill in the actionHandler - } -} \ No newline at end of file +} diff --git a/src/ui/actionDialog/actionDialog.template.html b/src/ui/actionDialog/actionDialog.template.html index 095b27ee6c147c69f53ac164bb7c3f865fd2a021..5388f69f0d10a0d887a2dce39164a273f1e200d5 100644 --- a/src/ui/actionDialog/actionDialog.template.html +++ b/src/ui/actionDialog/actionDialog.template.html @@ -28,8 +28,7 @@ <button *ngSwitchCase="'mat-flat-button'" mat-flat-button [color]="action.color || 'default'" - [mat-dialog-close]="action.dismiss && action" - (click)="actionHandler($event, action)"> + [mat-dialog-close]="action.dismiss && action"> <ng-container *ngTemplateOutlet="textNodeTmpl; context: action"></ng-container> </button> @@ -37,8 +36,7 @@ <button *ngSwitchCase="'mat-raised-button'" mat-raised-button [color]="action.color || 'default'" - [mat-dialog-close]="action.dismiss && action" - (click)="actionHandler($event, action)"> + [mat-dialog-close]="action.dismiss && action"> <ng-container *ngTemplateOutlet="textNodeTmpl; context: action"></ng-container> </button> @@ -46,8 +44,7 @@ <button *ngSwitchCase="'mat-stroked-button'" mat-stroked-button [color]="action.color || 'default'" - [mat-dialog-close]="action.dismiss && action" - (click)="actionHandler($event, action)"> + [mat-dialog-close]="action.dismiss && action"> <ng-container *ngTemplateOutlet="textNodeTmpl; context: action"></ng-container> </button> @@ -55,8 +52,7 @@ <button *ngSwitchDefault mat-button [color]="action.color || 'default'" - [mat-dialog-close]="action.dismiss && action" - (click)="actionHandler($event, action)"> + [mat-dialog-close]="action.dismiss && action"> <ng-container *ngTemplateOutlet="textNodeTmpl; context: action"></ng-container> </button> </ng-container> diff --git a/src/ui/config/module.ts b/src/ui/config/module.ts index 55fcded2c2c01aa17ce1bdb3420dc2e6c9634e5e..751aa6f16aca850c8dc9fd3e951d5e1484ba87bb 100644 --- a/src/ui/config/module.ts +++ b/src/ui/config/module.ts @@ -1,7 +1,6 @@ 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 "src/sharedModules"; import { ConfigComponent } from "./configCmp/config.component"; @@ -9,7 +8,6 @@ import { ConfigComponent } from "./configCmp/config.component"; imports: [ CommonModule, AngularMaterialModule, - // PluginModule, LayoutModule, ], declarations: [ diff --git a/src/ui/dialogInfo/const.ts b/src/ui/dialogInfo/const.ts deleted file mode 100644 index 09aa80f6f979e24b8cd407ec54fcbdfa4c8d3154..0000000000000000000000000000000000000000 --- a/src/ui/dialogInfo/const.ts +++ /dev/null @@ -1 +0,0 @@ -import { InjectionToken } from "@angular/core"; diff --git a/src/ui/help/helpOnePager/helpOnePager.component.ts b/src/ui/help/helpOnePager/helpOnePager.component.ts index 570072137ea77c542c3efb56a60a717063cbef64..d1298937d73f2a5483265cc92d043eafb96cc090 100644 --- a/src/ui/help/helpOnePager/helpOnePager.component.ts +++ b/src/ui/help/helpOnePager/helpOnePager.component.ts @@ -1,4 +1,3 @@ -import { MatDialog } from '@angular/material/dialog'; import { Component } from "@angular/core"; import { ARIA_LABELS } from 'common/constants' diff --git a/src/ui/nehubaContainer/mobileOverlay/mobileOverlay.component.ts b/src/ui/nehubaContainer/mobileOverlay/mobileOverlay.component.ts deleted file mode 100644 index 4948b3a38be2f43149491447ec4c79a33237ae36..0000000000000000000000000000000000000000 --- a/src/ui/nehubaContainer/mobileOverlay/mobileOverlay.component.ts +++ /dev/null @@ -1,332 +0,0 @@ -import { Component, ElementRef, EventEmitter, Input, OnDestroy, OnInit, Output, ViewChild, Inject, TemplateRef, ViewChildren, QueryList } from "@angular/core"; -import { combineLatest, concat, fromEvent, merge, Observable, of, Subject, BehaviorSubject } from "rxjs"; -import { filter, map, scan, switchMap, takeUntil, startWith, pairwise, tap, shareReplay, distinctUntilChanged, switchMapTo, reduce } from "rxjs/operators"; -import { clamp } from "src/util/generator"; -import { DOCUMENT } from "@angular/common"; - -const TOUCHMOVE_THRESHOLD = 50 -const SUBMENU_IXOBS_CONFIG = { - -} - -export interface ITunableProp{ - name: string - displayName?: string - values: string[] - selected?: any -} - -@Component({ - selector : 'mobile-overlay', - templateUrl : './mobileOverlay.template.html', - styleUrls : [ - './mobileOverlay.style.css', - ], - styles : [ - ` -div.active > span:before -{ - content: '\u2022'; - width: 1em; - display: inline-block; - background:none; -} -div:not(.active) > span:before -{ - content : ' '; - width : 1em; - display: inline-block; -} - `, - ], -}) - -export class MobileOverlay implements OnInit, OnDestroy { - - @Input('iav-mobile-overlay-guide-tmpl') - public guideTmpl: TemplateRef<any> - - @Input('iav-mobile-overlay-hide-ctrl-btn') - public hideCtrlBtn: boolean = false - - @Input('iav-mobile-overlay-ctrl-btn-pos') - public ctrlBtnPosition: { left: string, top: string } = { left: '50%', top: '50%' } - - @Input() public tunableProperties: ITunableProp[] = [] - - @Output() public tunablePropertySelected: EventEmitter<ITunableProp> = new EventEmitter() - @Output() public deltaValue: EventEmitter<{delta: number, selectedProp: ITunableProp}> = new EventEmitter() - @Output() public valueSelected: EventEmitter<{ value: string, selectedProp: ITunableProp }> = new EventEmitter() - - @ViewChild('initiator', {read: ElementRef, static: true}) public initiator: ElementRef - @ViewChild('mobileMenuContainer', {read: ElementRef, static: true}) public menuContainer: ElementRef - @ViewChild('intersector', {read: ElementRef, static: true}) public intersector: ElementRef - @ViewChild('subMenuObserver', {read: ElementRef, static: false}) public subMenuIx: ElementRef - @ViewChild('setValueContainer', { read: ElementRef, static: false }) public setValueContainer?: ElementRef - - private _onDestroySubject: Subject<boolean> = new Subject() - - private _focusedProperties: ITunableProp - get focusedProperty() { - return this._focusedProperties - ? this._focusedProperties - : this.tunableProperties[0] - } - get focusedIndex() { - return this._focusedProperties - ? this.tunableProperties.findIndex(p => p === this._focusedProperties) - : 0 - } - - private initiatorSingleTouchStart$: Observable<any> - - public showScreen$: Observable<boolean> - public showProperties$: Observable<boolean> - public showDelta$: Observable<boolean> - public showInitiator$: Observable<boolean> - private _drag$: Observable<any> - private _thresholdDrag$: Observable<any> - private intersectionObserver: IntersectionObserver - private subMenuIxObs: IntersectionObserver - - constructor( - @Inject(DOCUMENT) private document: Document - ){ - - } - - public ngOnDestroy() { - this._onDestroySubject.next(true) - this._onDestroySubject.complete() - this.intersectionObserver && this.intersectionObserver.disconnect() - this.subMenuIxObs && this.subMenuIxObs.disconnect() - } - - public ngOnInit() { - - this.initiatorSingleTouchStart$ = fromEvent(this.initiator.nativeElement, 'touchstart').pipe( - filter((ev: TouchEvent) => ev.touches.length === 1), - shareReplay(1) - ) - - const itemCount = this.tunableProperties.length - - const config = { - root: this.intersector.nativeElement, - threshold: [...[...Array(itemCount)].map((_, k) => k / itemCount), 1], - } - - this.intersectionObserver = new IntersectionObserver((arg) => { - if (arg[0].isIntersecting) { - const ratio = arg[0].intersectionRatio - (1 / this.tunableProperties.length / 2) - this.focusItemIndex = this.tunableProperties.length - Math.round(ratio * this.tunableProperties.length) - 1 - } - }, config) - - this.intersectionObserver.observe(this.menuContainer.nativeElement) - - this._drag$ = fromEvent(this.document, 'touchmove').pipe( - filter((ev: TouchEvent) => ev.touches.length === 1), - takeUntil(fromEvent(this.document, 'touchend').pipe( - filter((ev: TouchEvent) => ev.touches.length === 0), - )), - ) - - this._thresholdDrag$ = this._drag$.pipe( - distinctUntilChanged((o, n) => { - const deltaX = o.touches[0].screenX - n.touches[0].screenX - const deltaY = o.touches[0].screenY - n.touches[0].screenY - return (deltaX ** 2 + deltaY ** 2) < TOUCHMOVE_THRESHOLD - }), - ) - - this.showProperties$ = this.initiatorSingleTouchStart$.pipe( - switchMap(() => concat( - this._thresholdDrag$.pipe( - pairwise(), - map(double => ({ - deltaX : double[1].touches[0].screenX - double[0].touches[0].screenX, - deltaY : double[1].touches[0].screenY - double[0].touches[0].screenY, - })), - map(v => v.deltaY ** 2 > v.deltaX ** 2), - scan((acc, _curr) => acc), - ), - of(false) - )), - startWith(false), - distinctUntilChanged(), - shareReplay(1) - ) - - this.showDelta$ = this.initiatorSingleTouchStart$.pipe( - switchMap(() => concat( - this._thresholdDrag$.pipe( - pairwise(), - map(double => ({ - deltaX : double[1].touches[0].screenX - double[0].touches[0].screenX, - deltaY : double[1].touches[0].screenY - double[0].touches[0].screenY, - })), - scan((acc, _curr) => acc), - map(v => v.deltaX ** 2 > v.deltaY ** 2), - ), - of(false), - )), - startWith(false), - distinctUntilChanged(), - shareReplay(1) - ) - - this.showInitiator$ = combineLatest( - this.showProperties$, - this.showDelta$, - ).pipe( - map(([flag1, flag2]) => !flag1 && !flag2), - ) - - this.showScreen$ = combineLatest( - merge( - fromEvent(this.initiator.nativeElement, 'touchstart'), - fromEvent(this.initiator.nativeElement, 'touchend'), - ), - this.showInitiator$, - ).pipe( - map(([ev, showInitiator]: [TouchEvent, boolean]) => showInitiator && ev.touches.length === 1), - ) - - this.showDelta$.pipe( - filter(flag => flag), - switchMapTo(this._thresholdDrag$.pipe( - pairwise(), - map(double => double[1].touches[0].screenX - double[0].touches[0].screenX) - )), - takeUntil(this._onDestroySubject) - ).subscribe(ev => { - this.deltaValue.emit({ - delta: ev, - selectedProp: this.focusedProperty - }) - }) - - const offsetObs$ = this.initiatorSingleTouchStart$.pipe( - switchMap(() => this._drag$) - ) - - combineLatest( - this.showProperties$, - offsetObs$, - ).pipe( - filter(v => v[0]), - map(v => v[1]), - scan((acc, curr) => { - const { startY } = acc - const { screenY } = curr.touches[0] - return { - startY: startY || screenY, - totalDeltaY: screenY - (startY || 0) - } - }, { - startY: null, - totalDeltaY: 0 - }), - takeUntil(this._onDestroySubject), - ).subscribe(v => { - const deltaY = v.totalDeltaY - const cellHeight = this.menuContainer && this.tunableProperties && this.tunableProperties.length > 0 && this.menuContainer.nativeElement.offsetHeight / this.tunableProperties.length - const adjHeight = - this.focusedIndex * cellHeight - cellHeight * 0.5 - - const min = - cellHeight * 0.5 - const max = - this.tunableProperties.length * cellHeight + cellHeight * 0.5 - const finalYTranslate = clamp(adjHeight + deltaY, min, max ) - this.menuTransform = `translate(0px, ${finalYTranslate}px)` - }) - - this.showProperties$.pipe( - takeUntil(this._onDestroySubject), - filter(v => !v), - ).subscribe(() => { - if (this.focusItemIndex >= 0) { - this._focusedProperties = this.tunableProperties[this.focusItemIndex] - } - }) - - this.showDelta$.pipe( - tap(flag => { - this.highlightedSubmenu$.next(this.focusedProperty.values[0]) - if (!flag && !!this.subMenuIxObs) { - this.subMenuIxObs.disconnect() - this.subMenuIxObs = null - } - }), - filter(v => !!v), - // when options show again, options may have changed, so need to recalculate - tap(() => { - this.setValueContainerClampStart = null - this.setValueContainerWidth = null - this.setValueContainerClampEnd = null - this.setValueContainerOffset = null - }), - switchMapTo(this._drag$.pipe( - scan((acc, curr) => { - const { startX } = acc - const { screenX } = curr.touches[0] - return { - startX: startX || screenX, - totalDeltaX: screenX - (startX || 0) - } - }, { - startX: null, - totalDeltaX: 0 - }) - )), - takeUntil(this._onDestroySubject) - ).subscribe(({ totalDeltaX }) => { - if (!this.subMenuIxObs && this.subMenuIx) { - this.subMenuIxObs = new IntersectionObserver(ixs => { - const ix = ixs.find(({ intersectionRatio }) => intersectionRatio < 0.7) - if (!ix) return console.log(ixs) - const value = ix.target.getAttribute('data-submenu-value') - this.highlightedSubmenu$.next(value) - }, { - root: this.subMenuIx.nativeElement, - threshold: [ 0.1, 0.3, 0.5, 0.7, 0.9 ] - }) - - for (const btn of this.setValueContainer.nativeElement.children) { - this.subMenuIxObs.observe(btn) - } - } - if (!this.setValueContainerWidth) { - if (!this.setValueContainer) return - if (this.setValueContainer.nativeElement.children.length === 0) return - const { children, clientWidth } = this.setValueContainer.nativeElement - - this.setValueContainerWidth = clientWidth - const firstChildWidth = children[0].clientWidth - const lastChildWidth = children[children.length - 1].clientWidth - - this.setValueContainerOffset = firstChildWidth / -2 - this.setValueContainerClampStart = firstChildWidth / -2 - this.setValueContainerClampEnd = lastChildWidth / 2 - clientWidth - } - const actualDeltaX = clamp(totalDeltaX + this.setValueContainerOffset, this.setValueContainerClampStart, this.setValueContainerClampEnd) - this.subMenuTransform = `translate(${actualDeltaX}px , 0px)` - }) - - this.showDelta$.pipe( - takeUntil(this._onDestroySubject), - filter(v => !v) - ).subscribe(() => this.valueSelected.emit({ selectedProp: this.focusedProperty, value: this.highlightedSubmenu$.value })) - } - - public highlightedSubmenu$: BehaviorSubject<string> = new BehaviorSubject(null) - - private setValueContainerOffset = null - private setValueContainerClampEnd = null - private setValueContainerClampStart = null - private setValueContainerWidth = null - public subMenuTransform = `translate(0px, 0px)` - public menuTransform = `translate(0px, 0px)` - - public focusItemIndex: number = 0 - -} diff --git a/src/ui/nehubaContainer/mobileOverlay/mobileOverlay.style.css b/src/ui/nehubaContainer/mobileOverlay/mobileOverlay.style.css deleted file mode 100644 index 8d548523eaefd725bb5baa490dfc38e3e7abdc2e..0000000000000000000000000000000000000000 --- a/src/ui/nehubaContainer/mobileOverlay/mobileOverlay.style.css +++ /dev/null @@ -1,110 +0,0 @@ -:host -{ - width: 100%; - height: 100%; - top: 0; - left: 0; - position: absolute; - z-index: 9; - - pointer-events: none; -} - -:host [screen] -{ - width: 100%; - height: 100%; - top: 0; - left: 0; - position: absolute; - z-index: 99999; - - color : black; - background-color: rgba(255, 255, 255, 0.5); - - transition: all 200ms linear; -} - -:host-context([darktheme="true"]) [screen] -{ - color : white; - background-color: rgba(0, 0, 0, 0.5); -} - -[intersector] -{ - position: absolute; - top: 50%; - left: 0; - width: 100%; - height: 50%; - - display: flex; - flex-direction: column; - justify-content: flex-start; - align-items: center; -} - -[mobileMenuContainer] -{ - z-index: 1000; -} - -.scrollFocus:after -{ - content: ' '; - position:absolute; - width:100%; - height:100%; - top: 0; - left: 0; - - background-color: rgba(0, 0, 128, 0.2); -} - -:host-context([darktheme="true"]) .scrollFocus:after -{ - background-color: rgba(128, 128, 200, 0.2); -} - -.base-container -{ - position: relative; - width: 100%; - left: 0; - top: 50%; - height: 0; - z-index: 9999; -} - -div[delta] -{ - white-space: nowrap -} - -.popup -{ - transition: all 120ms linear; - transform-origin: 50% 100%; -} - -.scale-y-0 -{ - transform: scale(0.5, 0); - opacity: 0; -} - -.subMenu -{ - bottom: 15px; -} - -.w-50 -{ - width: 50%; -} - -.sliver -{ - width: 1px; -} \ No newline at end of file diff --git a/src/ui/nehubaContainer/mobileOverlay/mobileOverlay.template.html b/src/ui/nehubaContainer/mobileOverlay/mobileOverlay.template.html deleted file mode 100644 index 791edddf1b9dda7ae39043f7746afa8703f87bef..0000000000000000000000000000000000000000 --- a/src/ui/nehubaContainer/mobileOverlay/mobileOverlay.template.html +++ /dev/null @@ -1,82 +0,0 @@ -<div screen [hidden]="!(showProperties$ | async)"> - <div intersector #intersector> - <div [style.transform] = "menuTransform" class = "btn-group-vertical" role = "group" mobileMenuContainer #mobileMenuContainer> - <div - *ngFor = "let p of tunableProperties; let i = index" - [ngClass] = "{'active' : p === focusedProperty, 'scrollFocus': i === focusItemIndex}" - class = "btn btn-default theme-controlled property scrollFocus"> - <!-- scrollFocus class --> - <span> - {{ p.displayName || p.name }} - </span> - </div> - </div> - </div> -</div> - -<!-- container class --> -<div class="d-flex flex-column-reverse flex-nowrap align-items-center base-container position-relative"> - - <!-- ctrl nub --> - <div class="h-0 d-inline-flex align-items-center" [hidden]="!(showInitiator$ | async)" #initiator> - <div (contextmenu)="$event.stopPropagation(); $event.preventDefault();" - [ngStyle]="ctrlBtnPosition" - class="pe-all" - initiator> - <button mat-mini-fab color="primary"> - <i class="fas fa-globe"></i> - </button> - </div> - </div> - - <!-- guide text --> - <mat-card [ngClass]="{ 'scale-y-0': !(showScreen$ | async) }" - class="mb-4 popup muted position-absolute subMenu"> - <mat-card-content> - <ng-container *ngTemplateOutlet="guideTmpl"> - </ng-container> - </mat-card-content> - </mat-card> - - <!-- mobile set value --> - <div *ngIf="showDelta$ | async" class="position-absolute h-0 w-100 d-flex flex-row justify-content-end align-items-end"> - - <!-- intersection observer --> - <div class="w-50 d-flex flex-row flex-nowrap" #subMenuObserver> - - <!-- value selection container --> - <div class="position-relative mb-4" [style.transform]="subMenuTransform" #setValueContainer> - <!-- value selections --> - <ng-container *ngFor="let value of focusedProperty.values"> - <!-- selected button --> - <ng-template - [ngIf]="focusedProperty.selected === value" - [ngIfElse]="notSelectedTmpl"> - <button - [attr.data-submenu-value]="value" - mat-flat-button - [ngClass]="{ 'muted': (highlightedSubmenu$ | async) !== value }" - color="primary" - class="mr-2"> - {{ value }} - </button> - </ng-template> - - <!-- not selected button --> - <ng-template #notSelectedTmpl> - <button - [attr.data-submenu-value]="value" - mat-flat-button - [ngClass]="{ 'muted': (highlightedSubmenu$ | async) !== value }" - color="default" - class="mr-2"> - {{ value }} - </button> - </ng-template> - - </ng-container> - </div> - </div> - </div> - -</div> diff --git a/src/ui/nehubaContainer/reorderPanelIndex.pipe.ts b/src/ui/nehubaContainer/reorderPanelIndex.pipe.ts deleted file mode 100644 index 3e76f4d0234a6942743f9eeff983eb065b938298..0000000000000000000000000000000000000000 --- a/src/ui/nehubaContainer/reorderPanelIndex.pipe.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Pipe, PipeTransform } from "@angular/core"; - -@Pipe({ - name: 'reorderPanelIndexPipe', -}) - -export class ReorderPanelIndexPipe implements PipeTransform { - public transform(panelOrder: string, uncorrectedIndex: number) { - return uncorrectedIndex === null - ? null - : panelOrder.indexOf(uncorrectedIndex.toString()) - } -} diff --git a/src/ui/topMenu/topMenuCmp/topMenu.components.ts b/src/ui/topMenu/topMenuCmp/topMenu.components.ts index cf6922a353a041bd18b136ac7c1876dbc6e74c05..688da21160db6fef1fd93c1e95e17423cb627cbf 100644 --- a/src/ui/topMenu/topMenuCmp/topMenu.components.ts +++ b/src/ui/topMenu/topMenuCmp/topMenu.components.ts @@ -3,7 +3,6 @@ import { Component, Input, TemplateRef, - ViewChild, } from "@angular/core"; import { Observable } from "rxjs"; import { map } from "rxjs/operators"; diff --git a/src/ui/topMenu/topMenuCmp/topMenu.template.html b/src/ui/topMenu/topMenuCmp/topMenu.template.html index a33377a72f41a7126bd272754e3862651fd8b86a..c64c0dcee48ba00781195e882d4ffe17cd84e72d 100644 --- a/src/ui/topMenu/topMenuCmp/topMenu.template.html +++ b/src/ui/topMenu/topMenuCmp/topMenu.template.html @@ -22,7 +22,7 @@ <!-- pinned dataset --> <div iav-fab-speed-dial-child> - <ng-container *ngTemplateOutlet="pinnedDatasetBtnTmpl"> + <ng-container *ngTemplateOutlet="downloadAllTmpl"> </ng-container> </div> @@ -53,7 +53,7 @@ </ng-container> <!-- pinned dataset --> - <ng-container *ngTemplateOutlet="pinnedDatasetBtnTmpl"> + <ng-container *ngTemplateOutlet="downloadAllTmpl"> </ng-container> <!-- help one pager --> @@ -100,7 +100,7 @@ </ng-template> <!-- pinned dataset btn --> -<ng-template #pinnedDatasetBtnTmpl> +<ng-template #downloadAllTmpl> <div class="btnWrapper" [matTooltip]="(atlasDlDct.busy$| async) ? busyTxt : idleTxt" quick-tour diff --git a/src/ui/ui.module.ts b/src/ui/ui.module.ts index a657e32cc68916a0b63a485f226dbdd7a05e948e..2e083c87bbc17a268a43c2aba3cf1dd90acc5018 100644 --- a/src/ui/ui.module.ts +++ b/src/ui/ui.module.ts @@ -6,11 +6,6 @@ import { ScrollingModule } from "@angular/cdk/scrolling" import { HttpClientModule } from "@angular/common/http"; import { AngularMaterialModule } from 'src/sharedModules' import { UtilModule } from "src/util"; -import { DownloadDirective } from "../util/directives/download.directive"; -import { MobileOverlay } from "./nehubaContainer/mobileOverlay/mobileOverlay.component"; -import { HumanReadableFileSizePipe } from "src/util/pipes/humanReadableFileSize.pipe"; -import { ReorderPanelIndexPipe } from "./nehubaContainer/reorderPanelIndex.pipe"; -import { FixedMouseContextualContainerDirective } from "src/util/directives/FixedMouseContextualContainerDirective.directive"; import { ShareModule } from "src/share"; import { AuthModule } from "src/auth"; import { ActionDialog } from "./actionDialog/actionDialog.component"; @@ -34,14 +29,7 @@ import { HANDLE_SCREENSHOT_PROMISE, TypeHandleScrnShotPromise } from "../screens AuthModule, ], declarations: [ - MobileOverlay, ActionDialog, - /* pipes */ - HumanReadableFileSizePipe, - ReorderPanelIndexPipe, - /* directive */ - DownloadDirective, - FixedMouseContextualContainerDirective, ], providers: [ { @@ -53,8 +41,10 @@ import { HANDLE_SCREENSHOT_PROMISE, TypeHandleScrnShotPromise } from "../screens provide: HANDLE_SCREENSHOT_PROMISE, useValue: ((param) => { const canvas: HTMLCanvasElement = document.querySelector('#neuroglancer-container canvas') - if (!canvas) return Promise.reject(`element '#neuroglancer-container canvas' not found`) - const _ = (window as any).viewer.display.draw() + if (!canvas) { + return Promise.reject(`element '#neuroglancer-container canvas' not found`) + } + (window as any).viewer.display.draw() if (!param) { return new Promise(rs => { canvas.toBlob(blob => { @@ -101,10 +91,6 @@ import { HANDLE_SCREENSHOT_PROMISE, TypeHandleScrnShotPromise } from "../screens } ], exports: [ - // NehubaContainer, - MobileOverlay, - // StatusCardComponent, - FixedMouseContextualContainerDirective, ] }) diff --git a/src/util/constants.ts b/src/util/constants.ts index 001b1283945568cc84378cc55b4f06a41f02f866..763bc0695829eec89caa1c15c41d86be61d720ad 100644 --- a/src/util/constants.ts +++ b/src/util/constants.ts @@ -126,14 +126,21 @@ export const UNSUPPORTED_PREVIEW = [{ export const UNSUPPORTED_INTERVAL = 7000 +export const SPECIES_ENUM = { + HOMO_SAPIENS: "Homo sapiens", + MACACA_FASCICULARIS: "Macaca fascicularis", + RATTUS_NORVEGICUS: "Rattus norvegicus", + MUS_MUSCULUS: "Mus musculus", +} as const + /** * atlas should follow the following order */ export const speciesOrder = [ - "Homo sapiens", - "Macaca fascicularis", - "Rattus norvegicus", - "Mus musculus" + SPECIES_ENUM.HOMO_SAPIENS, + SPECIES_ENUM.MACACA_FASCICULARIS, + SPECIES_ENUM.RATTUS_NORVEGICUS, + SPECIES_ENUM.MUS_MUSCULUS, ] export const parcBanList: string[] = [ diff --git a/src/util/df-to-ds.pipe.ts b/src/util/df-to-ds.pipe.ts index 612b8b48220cb6463458d21bd63f5f5c2eb60e7b..f50be028d82a3522df9277be89ef75b59accd33d 100644 --- a/src/util/df-to-ds.pipe.ts +++ b/src/util/df-to-ds.pipe.ts @@ -1,5 +1,7 @@ import { CdkTableDataSourceInput } from '@angular/cdk/table'; import { Pipe, PipeTransform } from '@angular/core'; +import { MatSort } from '@angular/material/sort'; +import { MatTableDataSource } from '@angular/material/table'; import { components } from "src/atlasComponents/sapi/schemaV3" type DF = components["schemas"]["DataFrameModel"] @@ -19,11 +21,11 @@ function isDf(val: object): val is DF { }) export class DfToDsPipe implements PipeTransform { - transform(df: object): CdkTableDataSourceInput<unknown> { + transform(df: object, sort: MatSort): CdkTableDataSourceInput<unknown> { if (!isDf(df)) { return null } - return df.data.map((arr, idx) => { + const v = df.data.map((arr, idx) => { const val = df.index[idx] as any const returnVal: Record<string, string|number|number[]> = { index: val, @@ -37,6 +39,9 @@ export class DfToDsPipe implements PipeTransform { }) return returnVal }) + const ds = new MatTableDataSource(v) + ds.sort = sort + return ds } } diff --git a/src/util/directives/FixedMouseContextualContainerDirective.directive.spec.ts b/src/util/directives/FixedMouseContextualContainerDirective.directive.spec.ts deleted file mode 100644 index 0cf11aeb387d08a01a89f616e7095cc716fb63c5..0000000000000000000000000000000000000000 --- a/src/util/directives/FixedMouseContextualContainerDirective.directive.spec.ts +++ /dev/null @@ -1,131 +0,0 @@ -import { Component, ViewChild } from "@angular/core"; -import { TestBed } from "@angular/core/testing"; -import { FixedMouseContextualContainerDirective } from "./FixedMouseContextualContainerDirective.directive"; -import { By } from "@angular/platform-browser"; - -@Component({ - template: '' -}) - -class TestCmp{ - @ViewChild(FixedMouseContextualContainerDirective) directive: FixedMouseContextualContainerDirective -} - -describe('FixedMouseContextualContainerDirective', () => { - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [ - - ], - declarations: [ - TestCmp, - FixedMouseContextualContainerDirective - ] - }) - }) - - it('> can instantiate directive properly', () => { - TestBed.overrideComponent(TestCmp, { - set: { - template: ` - <div fixedMouseContextualContainerDirective> - </div> - ` - } - }).compileComponents() - - const fixture = TestBed.createComponent(TestCmp) - fixture.detectChanges() - const directive = fixture.debugElement.query( By.directive(FixedMouseContextualContainerDirective) ) - expect(directive).toBeTruthy() - - expect(fixture.componentInstance.directive).toBeTruthy() - }) - - describe('> hides if no content', () => { - it('> on #show, if content exists, isShown will be true', () => { - - TestBed.overrideComponent(TestCmp, { - set: { - template: ` - <div fixedMouseContextualContainerDirective> - <span>Hello World</span> - </div> - ` - } - }).compileComponents() - - const fixture = TestBed.createComponent(TestCmp) - fixture.detectChanges() - - const cmp = fixture.componentInstance - cmp.directive.show() - fixture.detectChanges() - expect(cmp.directive.isShown).toBeTrue() - }) - - it('> on #show, if only comment exists, isShown will be false', () => { - - TestBed.overrideComponent(TestCmp, { - set: { - template: ` - <div fixedMouseContextualContainerDirective> - <!-- hello world --> - </div> - ` - } - }).compileComponents() - - const fixture = TestBed.createComponent(TestCmp) - fixture.detectChanges() - - const cmp = fixture.componentInstance - cmp.directive.show() - fixture.detectChanges() - expect(cmp.directive.isShown).toBeFalse() - }) - - it('> on #show, if only text exists, isShown will be false', () => { - - TestBed.overrideComponent(TestCmp, { - set: { - template: ` - <div fixedMouseContextualContainerDirective> - hello world - </div> - ` - } - }).compileComponents() - - const fixture = TestBed.createComponent(TestCmp) - fixture.detectChanges() - - const cmp = fixture.componentInstance - cmp.directive.show() - fixture.detectChanges() - expect(cmp.directive.isShown).toBeFalse() - }) - - it('> on #show, if nothing exists, isShown will be false', () => { - - TestBed.overrideComponent(TestCmp, { - set: { - template: ` - <div fixedMouseContextualContainerDirective> - </div> - ` - } - }).compileComponents() - - const fixture = TestBed.createComponent(TestCmp) - fixture.detectChanges() - - const cmp = fixture.componentInstance - cmp.directive.show() - fixture.detectChanges() - expect(cmp.directive.isShown).toBeFalse() - }) - }) - - // TODO complete tests for FixedMouseContextualContainerDirective -}) \ No newline at end of file diff --git a/src/util/directives/FixedMouseContextualContainerDirective.directive.ts b/src/util/directives/FixedMouseContextualContainerDirective.directive.ts deleted file mode 100644 index d7fbf0905efa08166e8f864f45cf375a97950102..0000000000000000000000000000000000000000 --- a/src/util/directives/FixedMouseContextualContainerDirective.directive.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { Directive, ElementRef, EventEmitter, HostBinding, Input, Output, AfterContentChecked, ChangeDetectorRef, AfterViewInit } from "@angular/core"; - -@Directive({ - selector: '[fixedMouseContextualContainerDirective]', - exportAs: 'iavFixedMouseCtxContainer' -}) - -export class FixedMouseContextualContainerDirective implements AfterContentChecked { - - private defaultPos: [number, number] = [-1e3, -1e3] - public isShown: boolean = false - - @Input() - public mousePos: [number, number] = this.defaultPos - - @Output() - public onShow: EventEmitter<null> = new EventEmitter() - - @Output() - public onHide: EventEmitter<null> = new EventEmitter() - - constructor( - public el: ElementRef, - private cdr: ChangeDetectorRef, - ) { - } - - public recalculatePosition(){ - const clientWidth = this.el.nativeElement.clientWidth - const clientHeight = this.el.nativeElement.clientHeight - - const windowInnerWidth = window.innerWidth - const windowInnerHeight = window.innerHeight - if (windowInnerHeight - this.mousePos[1] < clientHeight) { - this.mousePos[1] = windowInnerHeight - clientHeight - } - - if ((windowInnerWidth - this.mousePos[0]) < clientWidth) { - this.mousePos[0] = windowInnerWidth - clientWidth - } - - this.transform = `translate(${this.mousePos.map(v => v.toString() + 'px').join(', ')})` - } - - ngAfterContentChecked(){ - if (this.el.nativeElement.childElementCount === 0) { - this.hide() - } - this.recalculatePosition() - this.cdr.markForCheck() - } - - public show() { - this.styleDisplay = 'inline-block' - this.isShown = true - this.onShow.emit() - } - - public hide() { - this.transform = `translate(${this.defaultPos.map(v => v.toString() + 'px').join(', ')})` - this.styleDisplay = 'none' - this.isShown = false - this.onHide.emit() - } - - @HostBinding('style.display') - public styleDisplay = `none` - - @HostBinding('style.transform') - public transform = `translate(${this.mousePos.map(v => v.toString() + 'px').join(', ')})` - -} diff --git a/src/util/directives/download.directive.ts b/src/util/directives/download.directive.ts deleted file mode 100644 index c0851002d0977cd7e534bc0bbf0b99cd446a7921..0000000000000000000000000000000000000000 --- a/src/util/directives/download.directive.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Directive, ElementRef, Renderer2 } from "@angular/core"; - -@Directive({ - selector : 'a[download]', -}) - -export class DownloadDirective { - - public downloadIcon: HTMLElement - - constructor(public el: ElementRef, public rd2: Renderer2) { - this.downloadIcon = rd2.createElement('i') - rd2.addClass(this.downloadIcon, 'fas') - rd2.addClass(this.downloadIcon, 'fa-download-alt') - } - - public ngAfterViewInit() { - this.rd2.appendChild(this.el.nativeElement, this.downloadIcon) - } -} diff --git a/src/util/fn.ts b/src/util/fn.ts index ff9b1c07fc1d93938e4f6e62a990aa34ce84422a..6457ccf78a80061da34734ae0c3b2e20b0ffe238 100644 --- a/src/util/fn.ts +++ b/src/util/fn.ts @@ -17,6 +17,9 @@ export function getDebug() { return (window as any).__DEBUG__ } +// eslint-disable-next-line @typescript-eslint/no-empty-function +export function noop(){} + export async function getExportNehuba() { // eslint-disable-next-line no-constant-condition while (true) { @@ -26,15 +29,6 @@ export async function getExportNehuba() { } } -const recursiveFlatten = (region, {ngId}) => { - return [{ - ngId, - ...region, - }].concat( - ...((region.children && region.children.map && region.children.map(c => recursiveFlatten(c, { ngId : region.ngId || ngId })) ) || []), - ) -} - export function getUuid(){ return crypto.getRandomValues(new Uint32Array(1))[0].toString(16) } @@ -140,7 +134,7 @@ export const CachedFunction = (config?: TCacheFunctionArg) => { } } -// A quick, non security hash function +// A quick, non secure hash function export class QuickHash { private length = 6 constructor(opts?: any){ @@ -423,7 +417,7 @@ export function bufferUntil<T>(opts: ISwitchMapWaitFor) { export function defaultdict<T>(fn: () => T): Record<string, T> { const obj = {} return new Proxy(obj, { - get(target, prop, rec) { + get(target, prop, _rec) { if (!(prop in target)){ target[prop] = fn() } diff --git a/src/util/interfaces.ts b/src/util/interfaces.ts index 6c057cb1ebe6acb7381f58864a0511cb3c1dc061..b9ba0df799dcd95099fdc94e718d20342bbfe62e 100644 --- a/src/util/interfaces.ts +++ b/src/util/interfaces.ts @@ -3,7 +3,6 @@ */ import { InjectionToken } from "@angular/core" -import { Observable } from "rxjs" export interface IHasId{ ['@id']: string diff --git a/src/util/pipes/humanReadableFileSize.pipe.spec.ts b/src/util/pipes/humanReadableFileSize.pipe.spec.ts deleted file mode 100644 index 74ae1113992802d8994023e8724acb4ee30a97ca..0000000000000000000000000000000000000000 --- a/src/util/pipes/humanReadableFileSize.pipe.spec.ts +++ /dev/null @@ -1,42 +0,0 @@ -import {} from 'jasmine' -import { HumanReadableFileSizePipe } from './humanReadableFileSize.pipe' - -describe('humanReadableFileSize.pipe.ts', () => { - describe('HumanReadableFileSizePipe', () => { - it('steps properly when nubmers ets large', () => { - const pipe = new HumanReadableFileSizePipe() - const num = 12 - - expect(pipe.transform(num, 0)).toBe(`12 byte(s)`) - expect(pipe.transform(num * 1e3, 0)).toBe(`12 KB`) - expect(pipe.transform(num * 1e6, 0)).toBe(`12 MB`) - expect(pipe.transform(num * 1e9, 0)).toBe(`12 GB`) - expect(pipe.transform(num * 1e12, 0)).toBe(`12 TB`) - - expect(pipe.transform(num * 1e15, 0)).toBe(`12000 TB`) - }) - - it('pads the correct zeros', () => { - const pipe = new HumanReadableFileSizePipe() - const num = 3.14159 - - expect(pipe.transform(num, 0)).toBe(`3 byte(s)`) - expect(pipe.transform(num, 1)).toBe(`3.1 byte(s)`) - expect(pipe.transform(num, 2)).toBe(`3.14 byte(s)`) - expect(pipe.transform(num, 3)).toBe(`3.142 byte(s)`) - expect(pipe.transform(num, 4)).toBe(`3.1416 byte(s)`) - expect(pipe.transform(num, 5)).toBe(`3.14159 byte(s)`) - expect(pipe.transform(num, 6)).toBe(`3.141590 byte(s)`) - expect(pipe.transform(num, 7)).toBe(`3.1415900 byte(s)`) - }) - - it('parses string as well as number', () => { - // TODO finish tests - }) - - it('throws when a non number is passed to either argument', () => { - // TODO finish tests - }) - - }) -}) diff --git a/src/util/pipes/humanReadableFileSize.pipe.ts b/src/util/pipes/humanReadableFileSize.pipe.ts deleted file mode 100644 index 1abb96e6bcb826dd64a4ebda3183a7e3b910df85..0000000000000000000000000000000000000000 --- a/src/util/pipes/humanReadableFileSize.pipe.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Pipe, PipeTransform } from "@angular/core"; - -export const steps = [ - 'byte(s)', - 'KB', - 'MB', - 'GB', - 'TB', -] - -@Pipe({ - name: 'humanReadableFileSizePipe', -}) - -export class HumanReadableFileSizePipe implements PipeTransform { - public transform(input: string | number, precision: number = 2) { - let _input = Number(input) - if (!_input) { throw new Error(`HumanReadableFileSizePipe needs a string or a number that can be parsed to number`) } - const _precision = Number(precision) - if (isNaN(_precision)) { throw new Error(`precision must be a number`) } - let counter = 0 - while (_input > 1000 && counter < 4) { - _input = _input / 1000 - counter += 1 - } - return `${_input.toFixed(precision)} ${steps[counter]}` - } -} diff --git a/src/util/pullable.ts b/src/util/pullable.ts index 24ae06f3b70e51ac84cef085807866dfea5a2ade..6a5d87e96701b3c9c41553ce66c0f611054c0afa 100644 --- a/src/util/pullable.ts +++ b/src/util/pullable.ts @@ -1,5 +1,5 @@ import { DataSource } from "@angular/cdk/collections" -import { BehaviorSubject, Observable, ReplaySubject, Subscription, combineLatest, concat, of, timer } from "rxjs" +import { BehaviorSubject, Observable, ReplaySubject, Subscription, concat, of, timer } from "rxjs" import { finalize, map, scan, shareReplay, startWith, tap } from "rxjs/operators" import { cachedPromise } from "./fn" diff --git a/src/viewerModule/nehuba/base.service/base.service.ts b/src/viewerModule/nehuba/base.service/base.service.ts index bb42ed31f608691ce4b91084927b9a44d0a8a558..a6bfe046ab51e81f2d84db4930e9747d22f0388d 100644 --- a/src/viewerModule/nehuba/base.service/base.service.ts +++ b/src/viewerModule/nehuba/base.service/base.service.ts @@ -44,7 +44,7 @@ export class BaseService { regionmap.set(r.name, r) } const returnVal: Record<string, Record<number, NgMapReturnType>> = {} - for (const [url, { layer, region }] of Object.entries(record)) { + for (const [ /* url */ , { layer, region }] of Object.entries(record)) { for (const { name, label } of region) { diff --git a/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.effects.ts b/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.effects.ts index 68f181a8654696b4c3c080bc8f079f694dce4591..b8e5e4b26a8f59e6f0dce71eaf8ee227d6e46e80 100644 --- a/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.effects.ts +++ b/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.effects.ts @@ -172,7 +172,7 @@ export class LayerCtrlEffects { parcNgLayers: from(this.sapi.getTranslatedLabelledNgMap(parcellation, template)).pipe( map(val => { const returnVal: Record<string, NgSegLayerSpec> = {} - for (const [ url, { layer, region } ] of Object.entries(val)) { + for (const [ /** url */, { layer, region } ] of Object.entries(val)) { const { name } = region[0] const ngId = getParcNgId(atlas, template, parcellation, { id: '', diff --git a/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.service.ts b/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.service.ts index f68170be4a59e2610000d997b852084512529dc4..be87e8eeec2a8fad91596feadac14a1dde29da3f 100644 --- a/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.service.ts +++ b/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.service.ts @@ -46,13 +46,16 @@ export class NehubaLayerControlService implements OnDestroy{ combineLatest([ this.completeNgIdLabelRegionMap$, this.customLayers$, + this.selectedRegion$, ]).pipe( - map(([record, layers]) => { + map(([record, layers, selectedRegions]) => { const returnVal: IColorMap = {} const cmCustomLayers = layers.filter(l => l.clType === "customlayer/colormap") as atlasAppearance.const.ColorMapCustomLayer[] const cmBaseLayers = layers.filter(l => l.clType === "baselayer/colormap") as atlasAppearance.const.ColorMapCustomLayer[] + const usingCustomCM = cmCustomLayers.length > 0 + const useCm = (() => { /** * if custom layer exist, use the last custom layer @@ -72,11 +75,20 @@ export class NehubaLayerControlService implements OnDestroy{ get: (r: SxplrRegion) => r.color } })() + + const selectedRegionNameSet = new Set(selectedRegions.map(v => v.name)) for (const [ngId, labelRecord] of Object.entries(record)) { for (const [label, region] of Object.entries(labelRecord)) { if (!region.color) continue - const [ red, green, blue ] = useCm.get(region) || [200, 200, 200] + /** + * if custom color map is used, do *not* selectively paint selected region + * custom color map can choose to subscribe to selected regions, and update the color map accordingly, + * if they wish to respect the selected regions + */ + const [ red, green, blue ] = usingCustomCM || selectedRegionNameSet.size === 0 || selectedRegionNameSet.has(region.name) + ? useCm.get(region) || [200, 200, 200] + : [255, 255, 255] if (!returnVal[ngId]) { returnVal[ngId] = {} } diff --git a/src/viewerModule/nehuba/layoutOverlay/nehuba.layoutOverlay/nehuba.layoutOverlay.component.ts b/src/viewerModule/nehuba/layoutOverlay/nehuba.layoutOverlay/nehuba.layoutOverlay.component.ts index a6e58b8462cb398d87f2f6b26b61607110950f2d..118031cc0806bb692dead3bff0f7811adae32759 100644 --- a/src/viewerModule/nehuba/layoutOverlay/nehuba.layoutOverlay/nehuba.layoutOverlay.component.ts +++ b/src/viewerModule/nehuba/layoutOverlay/nehuba.layoutOverlay/nehuba.layoutOverlay.component.ts @@ -4,7 +4,7 @@ import { combineLatest, fromEvent, interval, merge, Observable, of, Subject, Sub import { userInterface } from "src/state"; import { NehubaViewerUnit } from "../../nehubaViewer/nehubaViewer.component"; import { NEHUBA_INSTANCE_INJTKN, takeOnePipe, getFourPanel, getHorizontalOneThree, getSinglePanel, getPipPanel, getVerticalOneThree } from "../../util"; -import { QUICKTOUR_DESC, ARIA_LABELS, IDS } from 'common/constants' +import { QUICKTOUR_DESC, QUICKTOUR_DESC_MD, ARIA_LABELS, IDS } from 'common/constants' import { IQuickTourData } from "src/ui/quickTour/constrants"; import { debounce, debounceTime, distinctUntilChanged, filter, map, mapTo, switchMap, take } from "rxjs/operators"; import {panelOrder} from "src/state/userInterface/selectors"; @@ -28,6 +28,7 @@ export class NehubaLayoutOverlay implements OnDestroy{ public quickTourSliceViewSlide: IQuickTourData = { order: 1, description: QUICKTOUR_DESC.SLICE_VIEW, + descriptionMd: QUICKTOUR_DESC_MD.SLICE_VIEW } public quickTour3dViewSlide: IQuickTourData = { diff --git a/src/viewerModule/nehuba/layoutOverlay/nehuba.layoutOverlay/nehuba.layoutOverlay.template.html b/src/viewerModule/nehuba/layoutOverlay/nehuba.layoutOverlay/nehuba.layoutOverlay.template.html index 5ae334a2db080f80a983734c1004c3e3e46e231b..18968039f924b3625f13254ff2b2d6e0bf346f61 100644 --- a/src/viewerModule/nehuba/layoutOverlay/nehuba.layoutOverlay/nehuba.layoutOverlay.template.html +++ b/src/viewerModule/nehuba/layoutOverlay/nehuba.layoutOverlay/nehuba.layoutOverlay.template.html @@ -7,6 +7,7 @@ [iav-window-resize-time]="64" (iav-window-resize-event)="setQuickTourPos()" quick-tour + [quick-tour-description-md]="quickTourSliceViewSlide.descriptionMd" [quick-tour-description]="quickTourSliceViewSlide.description" [quick-tour-order]="quickTourSliceViewSlide.order" [quick-tour-overwrite-arrow]="sliceViewArrow" diff --git a/src/viewerModule/nehuba/mesh.service/mesh.service.spec.ts b/src/viewerModule/nehuba/mesh.service/mesh.service.spec.ts index 051d366a882a44f75f782e10fe58ccd25314869e..7db5392a46b74554df8c9e2db2ab8d5e221ab44b 100644 --- a/src/viewerModule/nehuba/mesh.service/mesh.service.spec.ts +++ b/src/viewerModule/nehuba/mesh.service/mesh.service.spec.ts @@ -196,6 +196,8 @@ describe('> mesh.service.ts', () => { beforeEach(() => { const mockStore = TestBed.inject(MockStore) mockStore.overrideSelector(atlasSelection.selectors.selectedRegions, []) + mockStore.overrideSelector(atlasSelection.selectors.selectedTemplate, {} as any) + mockStore.overrideSelector(atlasSelection.selectors.selectedParcellation, {} as any) }) it("> load all meshes", () => { @@ -226,6 +228,8 @@ describe('> mesh.service.ts', () => { beforeEach(() => { const mockStore = TestBed.inject(MockStore) mockStore.overrideSelector(atlasSelection.selectors.selectedRegions, [fits1]) + mockStore.overrideSelector(atlasSelection.selectors.selectedTemplate, {} as any) + mockStore.overrideSelector(atlasSelection.selectors.selectedParcellation, {} as any) }) it("> load only selected mesh", () => { diff --git a/src/viewerModule/nehuba/mesh.service/mesh.service.ts b/src/viewerModule/nehuba/mesh.service/mesh.service.ts index 29a40e94c6882b6022033b2d55103627d3d9b8cf..2af102701593d4918f8b4c45e3eef92f2382952b 100644 --- a/src/viewerModule/nehuba/mesh.service/mesh.service.ts +++ b/src/viewerModule/nehuba/mesh.service/mesh.service.ts @@ -7,6 +7,7 @@ import { selectorAuxMeshes } from "../store"; import { LayerCtrlEffects } from "../layerCtrl.service/layerCtrl.effects"; import { atlasSelection } from "src/state"; import { BaseService } from "../base.service/base.service"; +import { IDS } from "src/atlasComponents/sapi" /** * control mesh loading etc @@ -38,7 +39,7 @@ export class NehubaMeshService implements OnDestroy { const ngIdRecord: Record<string, number[]> = {} for (const [ngId, labelToRegion] of Object.entries(record)) { - for (const [label, _region] of Object.entries(labelToRegion)) { + for (const [label, ] of Object.entries(labelToRegion)) { if (!ngIdRecord[ngId]) { ngIdRecord[ngId] = [] } @@ -126,8 +127,21 @@ export class NehubaMeshService implements OnDestroy { this.#allSegmentMeshes$, this.#selectedSegmentMeshes$, this.#auxMesh$, + this.store$.pipe( + select(atlasSelection.selectors.selectedTemplate) + ), + this.store$.pipe( + select(atlasSelection.selectors.selectedParcellation) + ) ]).pipe( - switchMap(([ allSegMesh, selectedSegMesh, auxmesh ]) => { + switchMap(([ allSegMesh, selectedSegMesh, auxmesh, selectedTemplate, selectedParcellation ]) => { + /** + * TODO monkey patching jba29 in colin to show all meshes + * + */ + if (selectedParcellation.id === IDS.PARCELLATION.JBA29 && selectedTemplate.id === IDS.TEMPLATES.COLIN27) { + return of(...allSegMesh) + } const hasSegSelected = selectedSegMesh.some(v => v.labelIndicies.length !== 0) const hasAuxMesh = auxmesh.length > 0 const meshesToLoad: IMeshesToLoad[] = [] diff --git a/src/viewerModule/nehuba/module.ts b/src/viewerModule/nehuba/module.ts index 8243707dae27438a851e0ccd38cf39fa428519ff..682fae0bb9cf878ebd3ff18007a1ad26eb8cbfb0 100644 --- a/src/viewerModule/nehuba/module.ts +++ b/src/viewerModule/nehuba/module.ts @@ -1,4 +1,4 @@ -import { APP_INITIALIZER, CUSTOM_ELEMENTS_SCHEMA, NgModule } from "@angular/core"; +import { APP_INITIALIZER, 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"; diff --git a/src/viewerModule/nehuba/navigation.service/navigation.base.service.ts b/src/viewerModule/nehuba/navigation.service/navigation.base.service.ts index dd82454761ad41d4ccb18f8a37ea3abbabe3c6f5..66413f1ffe994dab36f2ecc0aa3eab5c6225b374 100644 --- a/src/viewerModule/nehuba/navigation.service/navigation.base.service.ts +++ b/src/viewerModule/nehuba/navigation.service/navigation.base.service.ts @@ -1,6 +1,6 @@ import { Inject, Injectable, Optional } from "@angular/core"; import { concat, EMPTY, NEVER, Observable, of } from "rxjs"; -import { delay, exhaustMap, shareReplay, switchMap, take, tap } from "rxjs/operators"; +import { delay, exhaustMap, shareReplay, switchMap, take } from "rxjs/operators"; import { TNehubaViewerUnit } from "../constants"; import { NEHUBA_INSTANCE_INJTKN } from "../util"; diff --git a/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.ts b/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.ts index e1806ae87856ee7dd6756f15e122ce54e3bc1409..e0e25cd158aedca533bbe1b9a336832ff2f82298 100644 --- a/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.ts +++ b/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.ts @@ -13,17 +13,6 @@ import { IColorMap, SET_COLORMAP_OBS, SET_LAYER_VISIBILITY } from "../layerCtrl. */ import { INgLayerCtrl, NG_LAYER_CONTROL, SET_SEGMENT_VISIBILITY, TNgLayerCtrl } from "../layerCtrl.service/layerCtrl.util"; -const NG_LANDMARK_LAYER_NAME = 'spatial landmark layer' -const NG_USER_LANDMARK_LAYER_NAME = 'user landmark layer' - -/** - * optimized for nehubaConfig.layout.useNehubaPerspective.fixedZoomPerspectiveSlices - * sliceZoom - * sliceViewportWidth - * sliceViewportHeight - */ -const NG_LANDMARK_CONSTANT = 1e-8 - export const IMPORT_NEHUBA_INJECT_TOKEN = `IMPORT_NEHUBA_INJECT_TOKEN` interface LayerLabelIndex { @@ -85,8 +74,7 @@ export class NehubaViewerUnit implements OnDestroy { url?: string } }> = new EventEmitter() - @Output() public mouseoverLandmarkEmitter: EventEmitter<string> = new EventEmitter() - @Output() public mouseoverUserlandmarkEmitter: EventEmitter<string> = new EventEmitter() + @Output() public regionSelectionEmitter: EventEmitter<{ segment: number layer: { @@ -95,6 +83,7 @@ export class NehubaViewerUnit implements OnDestroy { }}> = new EventEmitter() @Output() public errorEmitter: EventEmitter<any> = new EventEmitter() + @Output() public totalMeshesToLoad = new EventEmitter<number>() /* only used to set initial navigation state */ public initNav: any @@ -319,7 +308,7 @@ export class NehubaViewerUnit implements OnDestroy { totalMeshes += labelIndicies.length this.nehubaViewer.setMeshesToLoad(labelIndicies, layer) } - // TODO implement total mesh to be loaded and mesh loading UI + this.totalMeshesToLoad.emit(totalMeshes) }), ) } else { @@ -336,25 +325,6 @@ export class NehubaViewerUnit implements OnDestroy { } } - public spatialLandmarkSelectionChanged(labels: number[]) { - const getCondition = (label: number) => `if(label > ${label - 0.1} && label < ${label + 0.1} ){${FRAGMENT_EMIT_RED}}` - const newShader = `void main(){ ${labels.map(getCondition).join('else ')}else {${FRAGMENT_EMIT_WHITE}} }` - if (!this.nehubaViewer) { - this.log.warn('setting special landmark selection changed failed ... nehubaViewer is not yet defined') - return - } - const landmarkLayer = this.nehubaViewer.ngviewer.layerManager.getLayerByName(NG_LANDMARK_LAYER_NAME) - if (!landmarkLayer) { - this.log.warn('landmark layer could not be found ... will not update colour map') - return - } - if (labels.length === 0) { - landmarkLayer.layer.displayState.fragmentMain.restoreState(FRAGMENT_MAIN_WHITE) - } else { - landmarkLayer.layer.displayState.fragmentMain.restoreState(newShader) - } - } - public navPosReal: [number, number, number] = [0, 0, 0] public navPosVoxel: [number, number, number] = [0, 0, 0] @@ -483,20 +453,6 @@ export class NehubaViewerUnit implements OnDestroy { ) } - private userLandmarkShader: string = FRAGMENT_MAIN_WHITE - - public removeSpatialSearch3DLandmarks() { - this.removeLayer({ - name : NG_LANDMARK_LAYER_NAME, - }) - } - - public removeuserLandmarks() { - this.removeLayer({ - name : NG_USER_LANDMARK_LAYER_NAME, - }) - } - public setLayerVisibility(condition: {name: string|RegExp}, visible: boolean) { if (!this.nehubaViewer) { return false @@ -798,19 +754,6 @@ export class NehubaViewerUnit implements OnDestroy { }) }) - // TODO bug: mouseoverlandmarkemitter does not emit empty for VTK layer when user mouse click - this.ondestroySubscriptions.push( - this.nehubaViewer.mouseOver.layer - .filter(obj => obj.layer.name === NG_LANDMARK_LAYER_NAME) - .subscribe(obj => this.mouseoverLandmarkEmitter.emit(obj.value)), - ) - - this.ondestroySubscriptions.push( - this.nehubaViewer.mouseOver.layer - .filter(obj => obj.layer.name === NG_USER_LANDMARK_LAYER_NAME) - .subscribe(obj => this.mouseoverUserlandmarkEmitter.emit(obj.value)), - ) - this._s4$ = this.nehubaViewer.navigationState.position.inRealSpace .filter(v => typeof v !== 'undefined' && v !== null) .subscribe(v => { diff --git a/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts b/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts index 1ff596295312d22d8d87e775c3c12821f0f1cbcb..c3bc899bd568f5242f37f4a90f2cf1f57f78579b 100644 --- a/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts +++ b/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, EventEmitter, Inject, OnDestroy, Optional, Output, TemplateRef, ViewChild } from "@angular/core"; +import { ChangeDetectionStrategy, Component, EventEmitter, Inject, OnDestroy, Optional, Output } from "@angular/core"; import { select, Store } from "@ngrx/store"; import { ClickInterceptor, CLICK_INTERCEPTOR_INJECTOR } from "src/util"; import { distinctUntilChanged } from "rxjs/operators"; diff --git a/src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerInterface.directive.spec.ts b/src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerInterface.directive.spec.ts index 19314d6737ebfd0c3229b41f3fb62055d9b0bcb6..db6f3a030a26adaaf5748a2a0e1b4816f0080c85 100644 --- a/src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerInterface.directive.spec.ts +++ b/src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerInterface.directive.spec.ts @@ -94,8 +94,6 @@ describe('> nehubaViewerInterface.directive.ts', () => { layersChanged: new Subject(), nehubaReady: new Subject(), mouseoverSegmentEmitter: new Subject(), - mouseoverLandmarkEmitter: new Subject(), - mouseoverUserlandmarkEmitter: new Subject(), elementRef: { nativeElement: {} }, @@ -171,76 +169,7 @@ describe('> nehubaViewerInterface.directive.ts', () => { }) describe('> subscription of nehuba instance', () => { - describe('> mouseoverUserlandmarkEmitter', () => { - let spyNehubaViewerInstance: any - let dispatchSpy: jasmine.Spy - let directiveInstance: NehubaViewerContainerDirective - const template = {} - const lifecycle = {} - beforeEach(() => { - - spyNehubaViewerInstance = { - config: null, - lifecycle: null, - templateId: null, - errorEmitter: new Subject(), - viewerPositionChange: new Subject(), - layersChanged: new Subject(), - nehubaReady: new Subject(), - mouseoverSegmentEmitter: new Subject(), - mouseoverLandmarkEmitter: new Subject(), - mouseoverUserlandmarkEmitter: new Subject(), - elementRef: { - nativeElement: {} - } - } - const mockStore = TestBed.inject(MockStore) - dispatchSpy = spyOn(mockStore, 'dispatch') - - const fixture = TestBed.createComponent(DummyCmp) - const directive = fixture.debugElement.query( - By.directive(NehubaViewerContainerDirective) - ) - - const spyComRef = { - destroy: jasmine.createSpy('destroy') - } - directiveInstance = directive.injector.get(NehubaViewerContainerDirective) - spyOnProperty(directiveInstance, 'nehubaViewerInstance').and.returnValue(spyNehubaViewerInstance) - spyOn(directiveInstance['el'], 'clear').and.callFake(() => {}) - spyOn(directiveInstance, 'clear').and.callFake(() => {}) - // casting return value to any is not perfect, but since only 2 methods and 1 property is used, it's a quick way - // rather than allow component to be created - spyOn(directiveInstance['el'], 'createComponent').and.returnValue(spyComRef as any) - - }) - - afterEach(() => { - dispatchSpy.calls.reset() - }) - it('> single null emits null', fakeAsync(() => { - - })) - it('> single value emits value', fakeAsync(() => { - - })) - - describe('> double value in 140ms emits last value', () => { - - it('> null - 24 emits 24', fakeAsync(() => { - - })) - it('> 24 - null emits null', fakeAsync(() => { - - - })) - }) - - it('> single value outside 140 ms emits separately', fakeAsync(() => { - - })) - }) }) }) }) diff --git a/src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerInterface.directive.ts b/src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerInterface.directive.ts index aee39e6f48641ebdb9b5b8051f23c6c023a91545..45d9228cb40e036d94b3d78c644c722cf043e600 100644 --- a/src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerInterface.directive.ts +++ b/src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerInterface.directive.ts @@ -1,8 +1,8 @@ -import { Directive, ViewContainerRef, ComponentRef, OnDestroy, Output, EventEmitter, Optional, ChangeDetectorRef, ComponentFactoryResolver, ComponentFactory } from "@angular/core"; +import { Directive, ViewContainerRef, ComponentRef, OnDestroy, Output, EventEmitter, Optional, ChangeDetectorRef } from "@angular/core"; import { NehubaViewerUnit } from "../nehubaViewer/nehubaViewer.component"; import { Store, select } from "@ngrx/store"; -import { Subscription, Observable, asyncScheduler, combineLatest } from "rxjs"; -import { distinctUntilChanged, filter, debounceTime, scan, map, throttleTime, switchMap, take, tap } from "rxjs/operators"; +import { Subscription, Observable, combineLatest } from "rxjs"; +import { distinctUntilChanged, filter, debounceTime, scan, map, switchMap, take } from "rxjs/operators"; import { serializeSegment } from "../util"; import { LoggingService } from "src/logging"; import { arrayOfPrimitiveEqual } from 'src/util/fn' @@ -313,19 +313,6 @@ export class NehubaViewerContainerDirective implements OnDestroy{ map((map: Map<string, any>) => Array.from(map.entries()).filter(([_ngId, { segmentId }]) => segmentId)), ).subscribe(val => this.handleMouseoverSegments(val)), - this.nehubaViewerInstance.mouseoverLandmarkEmitter.pipe( - distinctUntilChanged() - ).subscribe(label => { - console.warn(`mouseover landmark`, label) - }), - - this.nehubaViewerInstance.mouseoverUserlandmarkEmitter.pipe( - throttleTime(160, asyncScheduler, {trailing: true}), - ).subscribe(label => { - const idx = Number(label.replace('label=', '')) - // TODO - // this is exclusive for vtk layer - }), combineLatest([ this.nehubaViewerInstance.mousePosInVoxel$, diff --git a/src/viewerModule/nehuba/userLayers/service.ts b/src/viewerModule/nehuba/userLayers/service.ts index 042cc54851be1e31cf457926d81768dfcbc2a5a8..3fe9d0a7db656354ed6217ebd13a0fd0354deae4 100644 --- a/src/viewerModule/nehuba/userLayers/service.ts +++ b/src/viewerModule/nehuba/userLayers/service.ts @@ -1,8 +1,8 @@ import { Injectable, OnDestroy } from "@angular/core" import { MatDialog } from "@angular/material/dialog" import { select, Store } from "@ngrx/store" -import { concat, of, Subscription } from "rxjs" -import { pairwise } from "rxjs/operators" +import { concat, from, of, Subscription } from "rxjs" +import { catchError, map, pairwise, switchMap } from "rxjs/operators" import { linearTransform, TVALID_LINEAR_XFORM_DST, @@ -198,29 +198,48 @@ export class UserLayerService implements OnDestroy { ) { this.#subscription.push( concat( - of(null), - this.routerSvc.customRoute$.pipe(select((v) => v[OVERLAY_LAYER_KEY])) - ) - .pipe(pairwise()) - .subscribe(([prev, curr]) => { - if (prev) { - this.removeUserLayer(prev) + of(null as string), + this.routerSvc.customRoute$.pipe( + select(v => v[OVERLAY_LAYER_KEY]) + ) + ).pipe( + pairwise(), + switchMap(([prev, curr]) => { + /** + * for precomputed sources, check if transform.json exists. + * if so, try to fetch it, and set it as transform + */ + if (!curr) { + return of([prev, curr, null]) } - if (curr) { - this.addUserLayer( - curr, - { - filename: curr, - message: `Overlay layer populated in URL`, - }, - { - shader: getShader({ - colormap: EnumColorMapName.MAGMA, - }), - } - ) + if (!curr.startsWith("precomputed://")) { + return of([prev, curr, null]) } + return from(fetch(`${curr.replace('precomputed://', '')}/transform.json`).then(res => res.json())).pipe( + catchError(() => of([prev, curr, null])), + map(transform => [prev, curr, transform]) + ) }) + ).subscribe(([prev, curr, transform]) => { + if (prev) { + this.removeUserLayer(prev) + } + if (curr) { + this.addUserLayer( + curr, + { + filename: curr, + message: `Overlay layer populated in URL`, + }, + { + shader: getShader({ + colormap: EnumColorMapName.MAGMA, + }), + transform + } + ) + } + }) ) } diff --git a/src/viewerModule/nehuba/util.ts b/src/viewerModule/nehuba/util.ts index 81aed3560abe6fe1f41b9faf13211d1002702591..c8d56c7e5ba037ab8366e4ca40d0066481146789 100644 --- a/src/viewerModule/nehuba/util.ts +++ b/src/viewerModule/nehuba/util.ts @@ -1,7 +1,7 @@ import { InjectionToken } from '@angular/core' import { Observable, pipe } from 'rxjs' import { filter, scan, take } from 'rxjs/operators' -import { getExportNehuba, getViewer } from 'src/util/fn' +import { getViewer } from 'src/util/fn' import { NehubaViewerUnit } from './nehubaViewer/nehubaViewer.component' import { userInterface } from 'src/state' diff --git a/src/viewerModule/nehuba/viewerCtrl/effects.ts b/src/viewerModule/nehuba/viewerCtrl/effects.ts index fd10b9db4dabeadb61bd8f75683b3492081d7a74..5eeaafed8eaf61c7d03a5ddee0cb55759b39a8e6 100644 --- a/src/viewerModule/nehuba/viewerCtrl/effects.ts +++ b/src/viewerModule/nehuba/viewerCtrl/effects.ts @@ -2,7 +2,7 @@ import { Injectable } from "@angular/core"; import { createEffect } from "@ngrx/effects"; import { Store } from "@ngrx/store"; import { of } from "rxjs"; -import { mapTo, switchMap } from "rxjs/operators"; +import { switchMap } from "rxjs/operators"; import { atlasSelection, userInterface } from "src/state"; @Injectable() diff --git a/src/viewerModule/nehuba/viewerCtrl/perspectiveViewSlider/perspectiveViewSlider.component.ts b/src/viewerModule/nehuba/viewerCtrl/perspectiveViewSlider/perspectiveViewSlider.component.ts index 67706f44a39f0b2600e75574dcfe8e25f41a18ac..6342411ebf03fabb2d1c816eac98cf15695a8451 100644 --- a/src/viewerModule/nehuba/viewerCtrl/perspectiveViewSlider/perspectiveViewSlider.component.ts +++ b/src/viewerModule/nehuba/viewerCtrl/perspectiveViewSlider/perspectiveViewSlider.component.ts @@ -1,7 +1,7 @@ import { Component, OnDestroy, Inject, ViewChild, ChangeDetectionStrategy } from "@angular/core"; import { FormControl } from "@angular/forms"; import { select, Store } from "@ngrx/store"; -import { combineLatest, concat, forkJoin, NEVER, Observable, of, Subject, Subscription, throwError } from "rxjs"; +import { combineLatest, concat, NEVER, Observable, of, Subject, Subscription } from "rxjs"; import { switchMap, distinctUntilChanged, map, debounceTime, shareReplay, take, withLatestFrom } from "rxjs/operators"; import { SAPI } from "src/atlasComponents/sapi"; import { SxplrTemplate } from "src/atlasComponents/sapi/sxplrTypes" @@ -292,7 +292,7 @@ export class PerspectiveViewSlider implements OnDestroy { this.currentTemplateSize$, this.rangeControlIsVertical$, ]).pipe( - map(([ navigation, viewportSize, ctrl, templateSize, isVertical ]) => { + map(([ navigation, viewportSize, ctrl, templateSize, ..._rest ]) => { if (!ctrl || !(templateSize?.real) || !navigation) return null const { zoom, position } = navigation diff --git a/src/viewerModule/threeSurfer/store/effects.ts b/src/viewerModule/threeSurfer/store/effects.ts index 501107552338fc19658b5757c13610b3fae15782..86992a1c3330ed1b3150467d04aa4f984cde169e 100644 --- a/src/viewerModule/threeSurfer/store/effects.ts +++ b/src/viewerModule/threeSurfer/store/effects.ts @@ -1,7 +1,7 @@ import { Injectable } from "@angular/core"; import { createEffect } from "@ngrx/effects"; import { select, Store } from "@ngrx/store"; -import { EMPTY, forkJoin, merge, Observable, of, pipe, throwError } from "rxjs"; +import { EMPTY, forkJoin, merge, Observable, of, pipe } from "rxjs"; import { debounceTime, map, switchMap, withLatestFrom, filter, shareReplay, distinctUntilChanged } from "rxjs/operators"; import { SAPI } from "src/atlasComponents/sapi"; import { SxplrAtlas, SxplrParcellation, SxplrTemplate } from "src/atlasComponents/sapi/sxplrTypes" @@ -118,7 +118,7 @@ export class ThreeSurferEffects { )) onATPDebounceAddBaseLayers$ = createEffect(() => this.onATPDebounceThreeSurferLayers$.pipe( - switchMap(({ labels, surfaces }) => { + switchMap(({ labels }) => { const labelMaps: ThreeSurferCustomLabelLayer[] = [] for (const key in labels) { const { laterality, url } = labels[key] diff --git a/src/viewerModule/viewerCmp/viewerCmp.component.ts b/src/viewerModule/viewerCmp/viewerCmp.component.ts index 136f0baf82166534a9e0d7480159e0dce58edc32..27d4c91ba0ef4cf7975cc89a45ac821b24b3a580 100644 --- a/src/viewerModule/viewerCmp/viewerCmp.component.ts +++ b/src/viewerModule/viewerCmp/viewerCmp.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, TemplateRef, ViewChild, ViewContainerRef } from "@angular/core"; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, TemplateRef, ViewChild } from "@angular/core"; import { select, Store } from "@ngrx/store"; import { combineLatest, Observable, Subscription } from "rxjs"; import { debounceTime, map, shareReplay } from "rxjs/operators"; @@ -14,6 +14,7 @@ import { atlasAppearance, atlasSelection, userInteraction } from "src/state"; import { SxplrTemplate } from "src/atlasComponents/sapi/sxplrTypes"; import { EntryComponent } from "src/features/entry/entry.component"; import { TFace, TSandsPoint, getCoord } from "src/util/types"; +import { wait } from "src/util/fn"; @Component({ selector: 'iav-cmp-viewer-container', @@ -438,4 +439,14 @@ export class ViewerCmp implements OnDestroy { }) ) } + + @ViewChild('voiFeatureEntryCmp') + voiFeatureEntryCmp: EntryComponent + + async pullAllVoi(){ + if (this.voiFeatureEntryCmp){ + await wait(320) + this.voiFeatureEntryCmp.pullAll() + } + } } diff --git a/src/viewerModule/viewerCmp/viewerCmp.template.html b/src/viewerModule/viewerCmp/viewerCmp.template.html index 0c80076b1593496a3f94cfb7892f312d192cad22..459fb19e841d22944af9cf5a8bca36512bcb6d0c 100644 --- a/src/viewerModule/viewerCmp/viewerCmp.template.html +++ b/src/viewerModule/viewerCmp/viewerCmp.template.html @@ -905,62 +905,69 @@ <ng-template #viewerStatusCtxMenu let-data> <ng-template [ngIf]="data.context" let-context> - <mat-list> + <!-- ref space & position --> + <ng-container [ngSwitch]="context.viewerType"> - <!-- ref space & position --> - <ng-container [ngSwitch]="context.viewerType"> - - <!-- volumetric i.e. nehuba --> - <ng-container *ngSwitchCase="'nehuba'"> - <mat-list-item mat-ripple - (click)="selectPoint({ point: context.payload.mouse.real }, data.metadata.template)"> - <div mat-list-icon> - <i class="fas fa-map"></i> - </div> - - <div mat-line> + <!-- volumetric i.e. nehuba --> + <ng-container *ngSwitchCase="'nehuba'"> + <button mat-button class="sxplr-list-like-button" + (click)="selectPoint({ point: context.payload.mouse.real }, data.metadata.template)"> + + <div class="sxplr-list-like-button-icon"> + <i class="fas fa-map"></i> + </div> + + <div class="sxplr-list-like-button-body"> + + <span class="sxplr-list-like-button-body-line"> {{ context.payload.mouse.real | nmToMm | numbers | addUnitAndJoin : '' }} (mm) - </div> - <div mat-line class="text-muted"> + </span> + <span class="sxplr-list-like-button-body-line text-muted"> Point - </div> - <div mat-line class="text-muted"> + </span> + <span class="sxplr-list-like-button-body-line text-muted"> {{ data.metadata.template.name }} - </div> - </mat-list-item> - </ng-container> - - <ng-container *ngSwitchCase="'threeSurfer'"> - - <ng-template [ngIf]="context.payload?.faceIndex" let-faceIndex> - <ng-template [ngIf]="context.payload?.vertexIndices" let-vertexIndices> - <mat-list-item mat-ripple - (click)="selectPoint({ face: faceIndex, vertices: vertexIndices }, data.metadata.template)"> - - <div mat-list-icon> - <i class="fas fa-map"></i> - </div> - - <div mat-line> + </span> + </div> + + </button> + </ng-container> + + <ng-container *ngSwitchCase="'threeSurfer'"> + + <ng-template [ngIf]="context.payload?.faceIndex" let-faceIndex> + <ng-template [ngIf]="context.payload?.vertexIndices" let-vertexIndices> + <button mat-button class="sxplr-list-like-button" + (click)="selectPoint({ face: faceIndex, vertices: vertexIndices }, data.metadata.template)"> + + <div class="sxplr-list-like-button-icon"> + <i class="fas fa-map"></i> + </div> + + <div class="sxplr-list-like-button-body"> + + <span class="sxplr-list-like-button-body-line"> Face Index: {{ faceIndex }}, Vertices Index: {{ vertexIndices | addUnitAndJoin : '' }} - </div> - <div mat-line class="text-muted"> + </span> + <span class="sxplr-list-like-button-body-line text-muted"> Mesh Face - </div> - <div mat-line class="text-muted"> + </span> + <span class="sxplr-list-like-button-body-line text-muted"> {{ data.metadata.template.name }} - </div> - </mat-list-item> - </ng-template> + </span> + </div> + + </button> + </ng-template> - - </ng-container> - - <ng-container *ngSwitchDefault> - DEFAULT - </ng-container> + </ng-template> + + </ng-container> + + <ng-container *ngSwitchDefault> + DEFAULT </ng-container> - </mat-list> + </ng-container> </ng-template> </ng-template> @@ -968,41 +975,32 @@ <!-- viewer state hover ctx menu --> <ng-template #viewerStatusRegionCtxMenu let-data> <!-- hovered ROIs --> - <mat-list> - <ng-template ngFor [ngForOf]="data.metadata.hoveredRegions" - let-region - let-first="first"> + <ng-template ngFor [ngForOf]="data.metadata.hoveredRegions" + let-region + let-first="first"> - <mat-divider class="top-0" *ngIf="!first"></mat-divider> + <mat-divider class="top-0" *ngIf="!first"></mat-divider> - <mat-list-item mat-ripple - class="cursor-default" - (click)="$event.ctrlKey ? toggleRoi(region) : selectRoi(region)"> + <button mat-button + (click)="$event.ctrlKey ? toggleRoi(region) : selectRoi(region)" + class="sxplr-list-like-button"> + + <div class="sxplr-list-like-button-icon"> + <i class="fas fa-brain"></i> + </div> - <div mat-list-icon> - <i class="fas fa-brain"></i> - </div> + <div class="sxplr-list-like-button-body"> - <span mat-line> + <span class="sxplr-list-like-button-body-line"> {{ region.name }} </span> - <span mat-line class="text-muted"> - <span> - Brain region - </span> + <span class="sxplr-list-like-button-body-line text-muted"> + Brain region </span> - - <!-- lookup region --> - <!-- <button mat-icon-button - (click)="selectRoi(region)" - ctx-menu-dismiss> - <i class="fas fa-search"></i> - </button> --> - </mat-list-item> - + </div> + </button> - </ng-template> - </mat-list> + </ng-template> </ng-template> <!-- feature tmpls --> @@ -1126,6 +1124,7 @@ class="sxplr-pe-all mat-elevation-z8" [template]="view.selectedTemplate" [bbox]="bbox.bbox$ | async | getProperty : 'bbox'" + [attr.data-feature-length]="((voiFeatureEntryCmp.features$ | async) || []).length" #voiFeatureEntryCmp="featureEntryCmp"> </sxplr-feature-entry> @@ -1143,7 +1142,7 @@ iav-switch [iav-switch-state]="false" #voiSwitch="iavSwitch" - (iav-switch-event)="$event && voiFeatureEntryCmp.pullAll()" + (iav-switch-event)="$event && pullAllVoi()" (click)="voiSwitch.toggle()"> <ng-template [ngIf]="voiSwitch.switchState$ | async" [ngIfElse]="chevronCollapseTmpl"> @@ -1175,6 +1174,7 @@ <div sxplr-sapiviews-core-space-boundingbox + (sxplr-sapiviews-core-space-boundingbox-changed)="pullAllVoi()" [sxplr-sapiviews-core-space-boundingbox-atlas]="selectedAtlas$ | async" [sxplr-sapiviews-core-space-boundingbox-space]="templateSelected$ | async" [sxplr-sapiviews-core-space-boundingbox-spec]="viewerCtx$ | async | nehubaVCtxToBbox" diff --git a/src/widget/service.ts b/src/widget/service.ts index 9430269418731ceb3b57810b54b26ef155ef7ef0..67856fb1f1c79ed1de0145394e3148f99c7bb633 100644 --- a/src/widget/service.ts +++ b/src/widget/service.ts @@ -1,5 +1,5 @@ import { ComponentPortal } from "@angular/cdk/portal"; -import { ComponentFactory, ComponentFactoryResolver, ComponentRef, Injectable, Injector, ViewContainerRef } from "@angular/core"; +import { ComponentRef, Injectable, Injector, ViewContainerRef } from "@angular/core"; import { RM_WIDGET } from "./constants"; import { WidgetPortal } from "./widgetPortal/widgetPortal.component"; @@ -12,11 +12,6 @@ export class WidgetService { public vcr: ViewContainerRef private viewRefMap = new Map<WidgetPortal<unknown>, ComponentRef<WidgetPortal<unknown>>>() - private cf: ComponentFactory<WidgetPortal<unknown>> - - constructor(cfr: ComponentFactoryResolver){ - this.cf = cfr.resolveComponentFactory(WidgetPortal) - } public addNewWidget<T>(Component: new (...arg: any) => T, injector: Injector): WidgetPortal<T> { const inj = Injector.create({ @@ -26,7 +21,8 @@ export class WidgetService { }], parent: injector }) - const widgetPortal = this.vcr.createComponent(this.cf, 0, inj) as ComponentRef<WidgetPortal<T>> + + const widgetPortal = this.vcr.createComponent(WidgetPortal, {index: 0, injector: inj}) as ComponentRef<WidgetPortal<T>> const cmpPortal = new ComponentPortal<T>(Component, this.vcr, inj) this.viewRefMap.set(widgetPortal.instance, widgetPortal)