diff --git a/docs/releases/v2.12.2.md b/docs/releases/v2.12.2.md new file mode 100644 index 0000000000000000000000000000000000000000..e232505af83b9698261e761af4e89fdccc42f7cc --- /dev/null +++ b/docs/releases/v2.12.2.md @@ -0,0 +1,8 @@ +# v2.12.2 + +## Bugfixes + +- fixes screenshot in fsaverage +- on hover region label in fsaverage now display properly +- fixes annotation mode (export annotations, annotations fail to render in viewer on startup (via shared link, local storage etc)) +- fixes an issue where region definition in full assignment table can also be clicked to select the region diff --git a/mkdocs.yml b/mkdocs.yml index 207b2580cd9132c87e0721534a3a192cbbfd4711..3b54fefc8324bbd99777bc67e61f5ec46d8865c2 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -33,6 +33,7 @@ nav: - Fetching datasets: 'advanced/datasets.md' - Display non-atlas volumes: 'advanced/otherVolumes.md' - Release notes: + - v2.12.2: 'releases/v2.12.2.md' - v2.12.1: 'releases/v2.12.1.md' - v2.12.0: 'releases/v2.12.0.md' - v2.11.4: 'releases/v2.11.4.md' diff --git a/package.json b/package.json index 5314a2daa6a1cc8d13088e24bd5568e42ab66b97..5746b8bb314805db1ef23fd255cbd2cfc2f07315 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "siibra-explorer", - "version": "2.12.1", + "version": "2.12.2", "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/atlasComponents/annotations/annotation.service.ts b/src/atlasComponents/annotations/annotation.service.ts index 543271a3a11ac766c9f194152d06b3c497a26a4f..d133671c3682ccf16c5af36271a12b90fbb4cf73 100644 --- a/src/atlasComponents/annotations/annotation.service.ts +++ b/src/atlasComponents/annotations/annotation.service.ts @@ -1,6 +1,6 @@ import { BehaviorSubject, Observable } from "rxjs"; import { distinctUntilChanged } from "rxjs/operators"; -import { getUuid } from "src/util/fn"; +import { getUuid, waitFor } from "src/util/fn"; import { PeriodicSvc } from "src/util/periodic.service"; export type TNgAnnotationEv = { @@ -144,8 +144,8 @@ export class AnnotationLayer { return false }) } - removeAnnotation(spec: { id: string }) { - if (!this.nglayer) return + async removeAnnotation(spec: { id: string }) { + await waitFor(() => !!this.nglayer?.layer?.localAnnotations) const { localAnnotations } = this.nglayer.layer this.idset.delete(spec.id) const ref = localAnnotations.references.get(spec.id) @@ -155,8 +155,8 @@ export class AnnotationLayer { } } async updateAnnotation(spec: AnnotationSpec) { - const localAnnotations = this.nglayer?.layer?.localAnnotations - if (!localAnnotations) return + await waitFor(() => !!this.nglayer?.layer?.localAnnotations) + const { localAnnotations } = this.nglayer.layer const ref = localAnnotations.references.get(spec.id) const _spec = this.parseNgSpecType(spec) if (ref) { 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 59694f74a9f84f067a93a56eb0f0d9988895d8e1..f031edbf5a99879f0041c02a545f234d0b4cd71a 100644 --- a/src/atlasComponents/sapiViews/volumes/point-assignment/point-assignment.component.html +++ b/src/atlasComponents/sapiViews/volumes/point-assignment/point-assignment.component.html @@ -58,15 +58,31 @@ #comphTableSort="matSort" matSortActive="map value" matSortDirection="desc"> - <ng-container *ngFor="let column of columns$ | async" - [matColumnDef]="column"> + + <ng-container matColumnDef="region"> <th mat-header-cell *matHeaderCellDef mat-sort-header> - {{ column }} + region </th> <td mat-cell *matCellDef="let element"> - {{ element[column] | prettyPresent }} + <button mat-button (click)="selectRegion(element['region'], $event)"> + {{ element['region'].name }} + </button> </td> </ng-container> + + <ng-template ngFor [ngForOf]="columns$ | async" let-column> + <ng-template [ngIf]="column !== 'region'"> + <ng-container [matColumnDef]="column"> + <th mat-header-cell *matHeaderCellDef mat-sort-header> + {{ column }} + </th> + <td mat-cell *matCellDef="let element"> + {{ element[column] | prettyPresent }} + </td> + </ng-container> + </ng-template> + </ng-template> + <tr mat-header-row *matHeaderRowDef="columns$ | async"></tr> <tr mat-row *matRowDef="let row; columns: columns$ | async;"></tr> 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 64b0ebdb1a71bdbf9baf0d0d4c687df6afa71cae..00ee69f552743fe8cd9a771dbfb44e6cd00ced89 100644 --- a/src/atlasComponents/sapiViews/volumes/point-assignment/point-assignment.component.ts +++ b/src/atlasComponents/sapiViews/volumes/point-assignment/point-assignment.component.ts @@ -1,5 +1,5 @@ import { Component, Input, OnDestroy, Output, TemplateRef, EventEmitter } from '@angular/core'; -import { MatDialog } from '@angular/material/dialog'; +import { MatDialog, MatDialogRef } from '@angular/material/dialog'; 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'; @@ -114,8 +114,12 @@ export class PointAssignmentComponent implements OnDestroy { constructor(private sapi: SAPI, private dialog: MatDialog) {} + #dialogRef: MatDialogRef<unknown> openDialog(tmpl: TemplateRef<unknown>){ - this.dialog.open(tmpl) + this.#dialogRef = this.dialog.open(tmpl) + this.#dialogRef.afterClosed().subscribe(() => { + this.#dialogRef = null + }) } #sub: Subscription[] = [] @@ -125,6 +129,9 @@ export class PointAssignmentComponent implements OnDestroy { async selectRegion(region: PathReturn<"/regions/{region_id}">, event: MouseEvent){ const sxplrReg = await translateV3Entities.translateRegion(region) this.clickOnRegion.emit({ target: sxplrReg, event }) + if (this.#dialogRef) { + this.#dialogRef.close() + } } zipfileConfig$: Observable<TZipFileConfig[]> = combineLatest([ diff --git a/src/atlasComponents/userAnnotations/annotationList/annotationList.component.spec.ts b/src/atlasComponents/userAnnotations/annotationList/annotationList.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..23662fcfdf9d305ec6daa00710944996af46b7fd --- /dev/null +++ b/src/atlasComponents/userAnnotations/annotationList/annotationList.component.spec.ts @@ -0,0 +1,117 @@ +import { ComponentFixture, TestBed } from "@angular/core/testing" +import { AnnotationList } from "./annotationList.component" +import { FileInputModule } from "src/getFileInput/module" +import { CommonModule } from "@angular/common" +import { ModularUserAnnotationToolService } from "../tools/service" +import { NoopAnimationsModule } from "@angular/platform-browser/animations" +import { MatDialogModule } from "@angular/material/dialog" +import { ComponentStore } from "@ngrx/component-store" +import { NEVER, of } from "rxjs" +import { MatSnackBarModule } from "@angular/material/snack-bar" +import { StateModule } from "src/state" +import { hot } from "jasmine-marbles" +import { IAnnotationGeometry } from "../tools/type" +import { MatTooltipModule } from "@angular/material/tooltip" +import { MatButtonModule } from "@angular/material/button" +import { MatCardModule } from "@angular/material/card" +import { ZipFilesOutputModule } from "src/zipFilesOutput/module" +import { AnnotationVisiblePipe } from "../annotationVisible.pipe" +import { SingleAnnotationClsIconPipe, SingleAnnotationNamePipe } from "../singleAnnotationUnit/singleAnnotationUnit.component" +import { MatExpansionModule } from "@angular/material/expansion" + +class MockModularUserAnnotationToolService { + hiddenAnnotations$ = of([]) + toggleAnnotationVisibilityById = jasmine.createSpy() + parseAnnotationObject = jasmine.createSpy() + importAnnotation = jasmine.createSpy() + spaceFilteredManagedAnnotations$ = NEVER + rSpaceManagedAnnotations$ = NEVER + otherSpaceManagedAnnotations$ = NEVER +} + +const readmeContent = `{id}.sands.json file contains the data of annotations. {id}.desc.json contains the metadata of annotations.` + +describe("annotationList.component.ts", () => { + let component: AnnotationList; + let fixture: ComponentFixture<AnnotationList>; + + describe("AnnotationList", () => { + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ + CommonModule, + FileInputModule, + NoopAnimationsModule, + MatDialogModule, + MatSnackBarModule, + StateModule, // needed for iavStateAggregator directive + MatTooltipModule, + MatButtonModule, + MatCardModule, + MatExpansionModule, + ZipFilesOutputModule, + ], + providers: [ + ComponentStore, + { + provide: ModularUserAnnotationToolService, + useClass: MockModularUserAnnotationToolService + } + ], + declarations: [ + AnnotationList, + AnnotationVisiblePipe, + SingleAnnotationNamePipe, + SingleAnnotationClsIconPipe, + ] + }).compileComponents() + }) + it("> can be init", () => { + + fixture = TestBed.createComponent(AnnotationList) + component = fixture.componentInstance + fixture.detectChanges() + expect(component).toBeTruthy() + }) + + describe("> filesExport$", () => { + beforeEach(() => { + const svc = TestBed.inject(ModularUserAnnotationToolService) + + const dummyGeom: Partial<IAnnotationGeometry> = { + id: 'foo', + toSands() { + return {} as any + }, + toMetadata() { + return {} as any + }, + toJSON() { + return {} + } + } + svc.spaceFilteredManagedAnnotations$ = of([dummyGeom] as IAnnotationGeometry[]) + }) + it("> do not emit duplicated values", () => { + + fixture = TestBed.createComponent(AnnotationList) + component = fixture.componentInstance + + expect(component.filesExport$).toBeObservable( + hot('(a|)', { + a: [{ + filename: 'README.md', + filecontent: readmeContent + }, { + filename: `foo.sands.json`, + filecontent: '{}' + }, { + filename: `foo.desc.json`, + filecontent: '{}' + }], + }) + ) + }) + }) + }) +}) diff --git a/src/atlasComponents/userAnnotations/annotationList/annotationList.component.ts b/src/atlasComponents/userAnnotations/annotationList/annotationList.component.ts index bad568f3822e640174da67c0162142b3a2188894..7463f9a105d179f96eb273d1880e2facda9a9155 100644 --- a/src/atlasComponents/userAnnotations/annotationList/annotationList.component.ts +++ b/src/atlasComponents/userAnnotations/annotationList/annotationList.component.ts @@ -3,8 +3,8 @@ import { ARIA_LABELS, CONST } from "common/constants"; import { ModularUserAnnotationToolService } from "../tools/service"; import { IAnnotationGeometry, TExportFormats } from "../tools/type"; import { ComponentStore } from "src/viewerModule/componentStore"; -import { map, shareReplay, startWith } from "rxjs/operators"; -import { combineLatest, Observable, Subscription } from "rxjs"; +import { debounceTime, map, shareReplay, startWith } from "rxjs/operators"; +import { combineLatest, concat, Observable, of, Subscription } from "rxjs"; import { TZipFileConfig } from "src/zipFilesOutput/type"; import { TFileInputEvent } from "src/getFileInput/type"; import { FileInputDirective } from "src/getFileInput/getFileInput.directive"; @@ -44,9 +44,11 @@ export class AnnotationList { startWith(false) ) - public filesExport$: Observable<TZipFileConfig[]> = this.managedAnnotations$.pipe( - startWith([] as IAnnotationGeometry[]), - shareReplay(1), + public filesExport$: Observable<TZipFileConfig[]> = concat( + of([] as IAnnotationGeometry[]), + this.managedAnnotations$ + ).pipe( + debounceTime(0), map(manAnns => { const readme = { filename: 'README.md', @@ -61,11 +63,12 @@ export class AnnotationList { const annotationDesc = manAnns.map(ann => { return { filename: `${ann.id}.desc.json`, - filecontent: JSON.stringify(this.annotSvc.exportAnnotationMetadata(ann), null, 2) + filecontent: JSON.stringify(ann.toMetadata(), null, 2) } }) return [ readme, ...annotationSands, ...annotationDesc ] - }) + }), + shareReplay(1), ) constructor( private annotSvc: ModularUserAnnotationToolService, @@ -82,10 +85,10 @@ export class AnnotationList { this.managedAnnotations$.subscribe(anns => this.managedAnnotations = anns), combineLatest([ this.managedAnnotations$.pipe( - startWith([]) + startWith([] as IAnnotationGeometry[]) ), this.annotationInOtherSpaces$.pipe( - startWith([]) + startWith([] as IAnnotationGeometry[]) ) ]).subscribe(([ann, annOther]) => { this.userAnnRoute = { diff --git a/src/atlasComponents/userAnnotations/tools/line.ts b/src/atlasComponents/userAnnotations/tools/line.ts index c156a9e09544fb3e82b50820f49d50668a4ea094..9223b1d989aacccab83e470f9145aca1b81674a9 100644 --- a/src/atlasComponents/userAnnotations/tools/line.ts +++ b/src/atlasComponents/userAnnotations/tools/line.ts @@ -78,11 +78,12 @@ export class Line extends IAnnotationGeometry{ x: x1, y: y1, z: z1 } = this.points[1] + const { id } = this.space return { '@id': this.id, '@type': "tmp/line", coordinateSpace: { - '@id': this.space["@id"] + '@id': id }, coordinatesFrom: [getCoord(x0/1e6), getCoord(y0/1e6), getCoord(z0/1e6)], coordinatesTo: [getCoord(x1/1e6), getCoord(y1/1e6), getCoord(z1/1e6)], diff --git a/src/atlasComponents/userAnnotations/tools/point.ts b/src/atlasComponents/userAnnotations/tools/point.ts index 58d8cdde8acd05863e682814e37f8d667124e36e..5086a30459665423439da1b8ee6cbb2fe51d35df 100644 --- a/src/atlasComponents/userAnnotations/tools/point.ts +++ b/src/atlasComponents/userAnnotations/tools/point.ts @@ -82,12 +82,13 @@ export class Point extends IAnnotationGeometry { } toSands(): TSandsPoint{ + const { id } = this.space const {x, y, z} = this return { '@id': this.id, '@type': 'https://openminds.ebrains.eu/sands/CoordinatePoint', coordinateSpace: { - '@id': this.space["@id"] + '@id': id }, coordinates:[ getCoord(x/1e6), getCoord(y/1e6), getCoord(z/1e6) ] } diff --git a/src/atlasComponents/userAnnotations/tools/poly.ts b/src/atlasComponents/userAnnotations/tools/poly.ts index 244c71a6a2d11163ba70af11727adcefd1ac399f..4bc86182aa6849161b384923cabd24d76534f502 100644 --- a/src/atlasComponents/userAnnotations/tools/poly.ts +++ b/src/atlasComponents/userAnnotations/tools/poly.ts @@ -94,11 +94,12 @@ export class Polygon extends IAnnotationGeometry{ } toSands(): TSandsPolyLine{ + const { id } = this.space return { "@id": this.id, "@type": 'tmp/poly', coordinateSpace: { - '@id': this.space["@id"], + '@id': id, }, coordinates: this.points.map(p => { const { x, y, z } = p diff --git a/src/atlasComponents/userAnnotations/tools/service.spec.ts b/src/atlasComponents/userAnnotations/tools/service.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..fdeacfde828aff2d09b31c81fd9b7e2de5baae63 --- /dev/null +++ b/src/atlasComponents/userAnnotations/tools/service.spec.ts @@ -0,0 +1,43 @@ +import { TestBed } from "@angular/core/testing" +import { ModularUserAnnotationToolService } from "./service" +import { MockStore, provideMockStore } from "@ngrx/store/testing" +import { NoopAnimationsModule } from "@angular/platform-browser/animations" +import { MatSnackBarModule } from "@angular/material/snack-bar" +import { ANNOTATION_EVENT_INJ_TOKEN, INJ_ANNOT_TARGET } from "./type" +import { NEVER, Subject } from "rxjs" +import { atlasSelection } from "src/state" + +describe("userAnnotations/service.ts", () => { + + describe("ModularUserAnnotationToolService", () => { + let service: ModularUserAnnotationToolService + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ + NoopAnimationsModule, + MatSnackBarModule, + ], + providers: [ + provideMockStore(), + { + provide: INJ_ANNOT_TARGET, + useValue: NEVER + }, + { + provide: ANNOTATION_EVENT_INJ_TOKEN, + useValue: new Subject() + }, + ModularUserAnnotationToolService + ] + }) + + const mStore = TestBed.inject(MockStore) + mStore.overrideSelector(atlasSelection.selectors.selectedTemplate, null) + mStore.overrideSelector(atlasSelection.selectors.viewerMode, null) + }) + it("> can be init", () => { + const svc = TestBed.inject(ModularUserAnnotationToolService) + expect(svc).toBeDefined() + }) + }) +}) diff --git a/src/atlasComponents/userAnnotations/tools/service.ts b/src/atlasComponents/userAnnotations/tools/service.ts index 03f0f060e620f05897e380044ef381bea50c5c71..9ef8d363ac2a1201e28854b1b940d44967f5def6 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, TCallback } from "./type"; +import { AbsToolClass, ANNOTATION_EVENT_INJ_TOKEN, IAnnotationEvents, IAnnotationGeometry, INgAnnotationTypes, INJ_ANNOT_TARGET, TAnnotationEvent, ClassInterface, TCallbackFunction, TSands, TGeometryJson, TCallback, DESC_TYPE } from "./type"; import { getExportNehuba, switchMapWaitFor } from "src/util/fn"; import { Polygon } from "./poly"; import { Line } from "./line"; @@ -27,7 +27,6 @@ type TAnnotationMetadata = { desc: string } -const descType = 'siibra-ex/meta/desc' as const type TTypedAnnMetadata = { '@type': 'siibra-ex/meta/desc' } & TAnnotationMetadata @@ -325,21 +324,22 @@ export class ModularUserAnnotationToolService implements OnDestroy{ /** * on new nehubaViewer, unset annotationLayer */ - this.subscription.push( - nehubaViewer$.subscribe(() => { - this.annotationLayer = null - }) - ) - - /** - * get mouse real position - */ - this.subscription.push( - nehubaViewer$.pipe( - switchMap(v => v?.mousePosInReal$ || of(null)) - ).subscribe(v => this.mousePosReal = v) - ) - + if (!!nehubaViewer$) { + this.subscription.push( + nehubaViewer$.subscribe(() => { + this.annotationLayer = null + }) + ) + + /** + * get mouse real position + */ + this.subscription.push( + nehubaViewer$.pipe( + switchMap(v => v?.mousePosInReal$ || of(null)) + ).subscribe(v => this.mousePosReal = v) + ) + } /** * on mouse move, render preview annotation */ @@ -410,13 +410,9 @@ export class ModularUserAnnotationToolService implements OnDestroy{ })), ) ]).pipe( - map(([_, annts]) => { - const out = [] - for (const ann of annts) { - out.push(...ann.toNgAnnotation()) - } - return out - }), + map(([_, annts]) => + annts.map(ann => ann.toNgAnnotation()).flatMap(v => v) + ), shareReplay(1), ) this.subscription.push( @@ -552,15 +548,6 @@ export class ModularUserAnnotationToolService implements OnDestroy{ } } - public exportAnnotationMetadata(ann: IAnnotationGeometry): TAnnotationMetadata & { '@type': 'siibra-ex/meta/desc' } { - return { - '@type': descType, - id: ann.id, - name: ann.name, - desc: ann.desc, - } - } - /** * stop gap measure when exporting/import annotations in sands format * metadata (name/desc) will be saved in a separate metadata file @@ -650,7 +637,7 @@ export class ModularUserAnnotationToolService implements OnDestroy{ if (json['@type'] === 'siibra-ex/annotation/polyline') { returnObj = Polygon.fromJSON(json) } - if (json['@type'] === descType) { + if (json['@type'] === DESC_TYPE) { const existingAnn = this.managedAnnotations.find(ann => json.id === ann.id) if (existingAnn) { diff --git a/src/atlasComponents/userAnnotations/tools/type.ts b/src/atlasComponents/userAnnotations/tools/type.ts index e2abb6f9cf4b745180c5d971ccd8a36178637388..d54b231afd55ca503430125855acdc6701c3fb72 100644 --- a/src/atlasComponents/userAnnotations/tools/type.ts +++ b/src/atlasComponents/userAnnotations/tools/type.ts @@ -9,6 +9,8 @@ import { TSandsCoord, TSandsPoint } from "src/util/types" export { getCoord, TSandsPoint } from "src/util/types" +export const DESC_TYPE = 'siibra-ex/meta/desc' as const + type TRecord = Record<string, unknown> /** @@ -311,6 +313,15 @@ export abstract class IAnnotationGeometry extends Highlightable { abstract toString(): string abstract toSands(): ISandsAnnotation[keyof ISandsAnnotation] + toMetadata(){ + return { + '@type': DESC_TYPE, + id: this.id, + name: this.name, + desc: this.desc, + } + } + public remove() { throw new Error(`The remove method needs to be overwritten by the tool manager`) } diff --git a/src/ui/ui.module.ts b/src/ui/ui.module.ts index 2e083c87bbc17a268a43c2aba3cf1dd90acc5018..98efd3854b801b7cffe5d1720ffc32fab5ea32d7 100644 --- a/src/ui/ui.module.ts +++ b/src/ui/ui.module.ts @@ -40,11 +40,21 @@ import { HANDLE_SCREENSHOT_PROMISE, TypeHandleScrnShotPromise } from "../screens { provide: HANDLE_SCREENSHOT_PROMISE, useValue: ((param) => { - const canvas: HTMLCanvasElement = document.querySelector('#neuroglancer-container canvas') + const ngCanvas: HTMLCanvasElement = document.querySelector('#neuroglancer-container canvas') + const threeSurferCanvas: HTMLCanvasElement = document.querySelector('three-surfer-glue-cmp canvas') + + if (threeSurferCanvas) { + const tsViewer = window['tsViewer'] + tsViewer.renderer.render(tsViewer.scene, tsViewer.camera) + } + if (ngCanvas) { + window['viewer'].display.draw() + } + const canvas = ngCanvas || threeSurferCanvas if (!canvas) { - return Promise.reject(`element '#neuroglancer-container canvas' not found`) + return Promise.reject(`element '#neuroglancer-container canvas' or 'three-surfer-glue-cmp canvas' not found`) } - (window as any).viewer.display.draw() + if (!param) { return new Promise(rs => { canvas.toBlob(blob => { diff --git a/src/util/fn.ts b/src/util/fn.ts index a859bad0898788e5c06c1e8292ddf2c66cafb452..c7edd341b1bad2170efa8a0f0cb51712885180d5 100644 --- a/src/util/fn.ts +++ b/src/util/fn.ts @@ -419,3 +419,15 @@ export function wait(ms: number){ rs(null) }, ms)) } + +/** + * @description Wait until predicate returns true. Tries once every 16 ms. + * @param predicate + */ +export async function waitFor(predicate: () => boolean) { + // eslint-disable-next-line no-constant-condition + while (true) { + if (predicate()) break + await wait(16) + } +} diff --git a/src/viewerModule/threeSurfer/threeSurferGlue/threeSurfer.component.ts b/src/viewerModule/threeSurfer/threeSurferGlue/threeSurfer.component.ts index 522d54c51836e83527b2742b529f3f39a946fa26..275b12b736715e33fa95f0854a0dd677b32baab7 100644 --- a/src/viewerModule/threeSurfer/threeSurferGlue/threeSurfer.component.ts +++ b/src/viewerModule/threeSurfer/threeSurferGlue/threeSurfer.component.ts @@ -824,11 +824,13 @@ export class ThreeSurferGlueCmp implements IViewer<'threeSurfer'>, AfterViewInit () => this.domEl.removeEventListener((window as any).ThreeSurfer.CUSTOM_EVENTNAME_UPDATED, customEvHandler) ) this.tsRef = new (window as any).ThreeSurfer(this.domEl, {highlightHovered: true}) + window['tsViewer'] = this.tsRef this.onDestroyCb.push( () => { this.tsRef.dispose() this.tsRef = null + window['tsViewer'] = null } ) this.tsRef.control.enablePan = false @@ -904,13 +906,9 @@ export class ThreeSurferGlueCmp implements IViewer<'threeSurfer'>, AfterViewInit } }) this.mouseoverText = '' - if (mouseover.length > 0) { - this.mouseoverText += mouseover.map(el => el.name).join(' / ') - } if (error) { this.mouseoverText += `::error: ${error}` } - if (this.mouseoverText === '') this.mouseoverText = null } public toggleMeshVis(label: string) { diff --git a/src/viewerModule/viewerCmp/viewerCmp.component.ts b/src/viewerModule/viewerCmp/viewerCmp.component.ts index b0d53ca1162ec2a39f94a90a091c32dbd5ea3663..75f18fd1e32b272b33b510222e3d11f9e71ffee6 100644 --- a/src/viewerModule/viewerCmp/viewerCmp.component.ts +++ b/src/viewerModule/viewerCmp/viewerCmp.component.ts @@ -405,6 +405,14 @@ export class ViewerCmp implements OnDestroy { ) } } + if (event.data.viewerType === "threeSurfer") { + const { regions=[] } = (event.data as TContextArg<"threeSurfer">).payload + this.store$.dispatch( + userInteraction.actions.mouseoverRegions({ + regions: regions as SxplrRegion[] + }) + ) + } break default: }