diff --git a/common/constants.js b/common/constants.js index b39325bc3a05a782e7178d68b976c1f933d86832..5054d2ede406c717c9c36669017e7fee0755433f 100644 --- a/common/constants.js +++ b/common/constants.js @@ -60,15 +60,14 @@ VIEWER_MODE_ANNOTATING: 'annotating', // Annotations - USER_ANNOTATION_VIEWER: 'user annotations viewer', USER_ANNOTATION_LIST: 'user annotations footer', USER_ANNOTATION_IMPORT: 'user annotations import', USER_ANNOTATION_EXPORT: 'user annotations export', USER_ANNOTATION_EXPORT_SINGLE: 'Export annotation', USER_ANNOTATION_HIDE: 'user annotations hide', USER_ANNOTATION_DELETE: 'Delete annotation', - GOTO_ANNOTATION_ROI: 'Navigate to annotation location of interest' - + GOTO_ANNOTATION_ROI: 'Navigate to annotation location of interest', + EXIT_ANNOTATION_MODE: 'Exit annotation mode' } exports.IDS = { diff --git a/src/atlasComponents/userAnnotations/annotationList/annotationList.component.ts b/src/atlasComponents/userAnnotations/annotationList/annotationList.component.ts index 0dccb54111b0b663098701fe5275fe305cb62e62..fd16cedee7b3320b858d9f92cf98a63f00faa6f4 100644 --- a/src/atlasComponents/userAnnotations/annotationList/annotationList.component.ts +++ b/src/atlasComponents/userAnnotations/annotationList/annotationList.component.ts @@ -1,7 +1,5 @@ import {Component} from "@angular/core"; import {AnnotationService} from "src/atlasComponents/userAnnotations/annotationService.service"; -import {viewerStateChangeNavigation} from "src/services/state/viewerState/actions"; -import {Store} from "@ngrx/store"; import {ARIA_LABELS} from "common/constants"; import { ModularUserAnnotationToolService } from "../tools/service"; import { TExportFormats } from "../tools/type"; @@ -19,12 +17,9 @@ import { ComponentStore } from "src/viewerModule/componentStore"; export class AnnotationList { public ARIA_LABELS = ARIA_LABELS - public identifier = (index: number, item: any) => item.id public managedAnnotations$ = this.annotSvc.managedAnnotations$ constructor( - private store$: Store<any>, - public ans: AnnotationService, private annotSvc: ModularUserAnnotationToolService, cStore: ComponentStore<{ useFormat: TExportFormats }>, ) { @@ -33,124 +28,6 @@ export class AnnotationList { }) } - toggleAnnotationVisibility(annotation) { - if (annotation.type === 'polygon') { - // ToDo Change when mainList will be groupedAnnotations - const annotationIndex = this.ans.groupedAnnotations.findIndex(a => a.id === annotation.id) - this.ans.groupedAnnotations[annotationIndex].annotationVisible = !this.ans.groupedAnnotations[annotationIndex].annotationVisible - this.ans.refreshFinalAnnotationList() - - this.ans.pureAnnotationsForViewer.filter(an => an.id.split('_')[0] === annotation.id.split('_')[0]) - .forEach(a => this.toggleVisibility(a)) - } else { - this.toggleVisibility(annotation) - } - } - - private toggleVisibility = (annotation) => { - const annotationIndex = this.ans.pureAnnotationsForViewer.findIndex(a => a.id === annotation.id) - - if (this.ans.pureAnnotationsForViewer[annotationIndex].annotationVisible) { - this.ans.removeAnnotationFromViewer(annotation.id) - this.ans.pureAnnotationsForViewer[annotationIndex].annotationVisible = false - } else { - this.ans.addAnnotationOnViewer(this.ans.pureAnnotationsForViewer[annotationIndex]) - this.ans.pureAnnotationsForViewer[annotationIndex].annotationVisible = true - } - this.ans.storeToLocalStorage() - } - - removeAnnotation(annotation) { - if (annotation.type === 'polygon') { - this.ans.pureAnnotationsForViewer.filter(an => an.id.split('_')[0] === annotation.id.split('_')[0]) - .forEach(a => this.ans.removeAnnotation(a.id)) - } else { - this.ans.removeAnnotation(annotation.id) - } - } - - navigate(position: number[]) { - // Convert to nm before navigate - position = position.map(p => +p * 1e6) - - if (position && position.length === 3) { - this.store$.dispatch( - viewerStateChangeNavigation({ - navigation: { - position, - positionReal: true - }, - }) - ) - } - } - - saveAnnotation(annotation) { - if (annotation.type !== 'polygon') { - - // Convert to Number Array - if (annotation.position1 && (typeof annotation.position1 === 'string')) { - annotation.position1 = this.positionToNumberArray(annotation.position1) - } - if (annotation.position2 && (typeof annotation.position2 === 'string')) { - annotation.position2 = this.positionToNumberArray(annotation.position2) - } - - // Return if positions are valid - if (annotation.position1.length !== 3 || !annotation.position1.every(e => !isNaN(e)) - || (annotation.position2 && (annotation.position2.length !== 3 || !annotation.position2.every(e => !isNaN(e))))) { - return - } else { - // Convert to Voxel - annotation.position1 = this.ans.mmToVoxel(annotation.position1) - annotation.position2 = annotation.position2 && this.ans.mmToVoxel(annotation.position2) - } - // Save annotation - this.ans.saveAnnotation(annotation) - } else { - - // if (!annotation.name) { - // annotation.name = this.ans.generateNameByType('polygon') - // } - - const toUpdateFirstAnnotation = this.ans.pureAnnotationsForViewer.find(a => a.id === `${annotation.id}_0`) - toUpdateFirstAnnotation.name = annotation.name - toUpdateFirstAnnotation.description = annotation.description - this.ans.saveAnnotation(toUpdateFirstAnnotation) - - //ToDo Change when main list will be groupedAnnotations - const toUpdate = this.ans.groupedAnnotations.findIndex(a => a.id === annotation.id) - this.ans.groupedAnnotations[toUpdate].name = annotation.name - this.ans.groupedAnnotations[toUpdate].description = annotation.description - this.ans.refreshFinalAnnotationList() - } - } - - positionToNumberArray(position) { - return position.split(',').map(n => parseFloat(n)) - } - - savePolygonPosition(id, position, inputVal) { - inputVal = this.positionToNumberArray(inputVal) - - if (inputVal.length !== 3 || !inputVal.every(e => !isNaN(e))) { - return - } else { - inputVal = this.ans.mmToVoxel(inputVal) - } - position.lines.forEach(l => { - if (l.point === 2) { - const annotation = this.ans.pureAnnotationsForViewer.find(a => a.id === l.id) - annotation.position2 = inputVal - this.ans.saveAnnotation(annotation, true) - } else { - const annotation = this.ans.pureAnnotationsForViewer.find(a => a.id === l.id) - annotation.position1 = inputVal - this.ans.saveAnnotation(annotation, true) - } - }) - } - public hiddenAnnotations$ = this.annotSvc.hiddenAnnotations$ toggleManagedAnnotationVisibility(id: string) { this.annotSvc.toggleAnnotationVisibilityById(id) diff --git a/src/atlasComponents/userAnnotations/annotationList/annotationList.template.html b/src/atlasComponents/userAnnotations/annotationList/annotationList.template.html index 2b06e3ade5695a9937016e92e724b2dc69929e23..fcfea4ae4a53a64ad1299f16c7ecf584ef461770 100644 --- a/src/atlasComponents/userAnnotations/annotationList/annotationList.template.html +++ b/src/atlasComponents/userAnnotations/annotationList/annotationList.template.html @@ -1,195 +1,26 @@ -<div [attr.aria-label]="ARIA_LABELS.USER_ANNOTATION_VIEWER" class="w-100"> +<!-- header --> - <!-- header section --> - <div class="overflow-hidden mt-3 mr-2 ml-2 d-flex justify-content-between"> - <div> - <button class="mr-1 ml-1" mat-icon-button - [attr.aria-label]="ARIA_LABELS.USER_ANNOTATION_IMPORT" - matTooltip="Import JSON" - [matMenuTriggerFor]="importMenu"> - <i class="fas fa-file-import"></i> - </button> - <input type="file" #importInput [import-annotations]="{sands: false}" hidden/> - <input type="file" #importInputSands [import-annotations]="{sands: true}" hidden/> - <mat-menu #importMenu="matMenu"> - <button mat-menu-item (click)="importInputSands.click()"> - SANDS format - </button> - <button mat-menu-item (click)="importInput.click()"> - Siibra explorer format - </button> - </mat-menu> - - <button class="mr-1 ml-1" mat-icon-button - [attr.aria-label]="ARIA_LABELS.USER_ANNOTATION_EXPORT" - matTooltip="Export" - [matMenuTriggerFor]="exportAllMenu"> - <i class="fas fa-file-export"></i> - </button> - <mat-menu #exportAllMenu="matMenu"> - <button mat-menu-item [export-annotations]="{annotations: ans.finalAnnotationList, sands: true}"> - SANDS format - </button> - <button mat-menu-item [export-annotations]="{annotations: ans.finalAnnotationList, sands: false}"> - Siibra explorer format - </button> - </mat-menu> - - </div> - - <div class="d-flex flex-column"> - <small [ngClass]="[ans.annotationFilter !== 'all'? 'text-muted' : '']" - class="cursor-pointer" (click)="ans.refreshFinalAnnotationList('all')"> - All landmarks - </small> - <small [ngClass]="[ans.annotationFilter !== 'current'? 'text-muted' : '']" - class="cursor-pointer" (click)="ans.refreshFinalAnnotationList('current')"> - Current template - </small> - </div> - </div> - - <mat-divider class="m-2"></mat-divider> - - <!-- list of annotations --> - <mat-accordion [attr.aria-label]="ARIA_LABELS.USER_ANNOTATION_LIST" - class="h-100 d-flex flex-column overflow-auto"> - <mat-expansion-panel hideToggle - *ngFor="let annotation of ans.finalAnnotationList; let i = index; trackBy: identifier"> - <mat-expansion-panel-header - [ngClass]="ans.hoverAnnotation?.id.includes(annotation.id) && 'highlight'"> - - <mat-panel-title> - <small class="cursor-pointer mr-3 ml-1 d-flex align-items-center" - [attr.aria-label]="ARIA_LABELS.USER_ANNOTATION_HIDE" - [matTooltip]="annotation.annotationVisible? 'Hide' : 'Show'" - (click)="$event.stopPropagation(); toggleAnnotationVisibility(annotation)"> - <i class="fas far fa-check-circle" *ngIf="annotation.annotationVisible; else notVisible"></i> - <ng-template #notVisible><i class="far fa-circle"></i></ng-template> - </small> - <div class="color-inherit">{{annotation.name}}</div> - </mat-panel-title> - - <mat-panel-description class="w-100 d-flex align-items-center justify-content-end" - [matTooltip]="annotation.type"> - <small><i [ngClass]="annotation.type === 'line'? 'fas fa-slash' - : annotation.type === 'bounding box'? 'far fa-square' - : annotation.type === 'ellipsoid'? 'fas fa-bullseye' - : annotation.type === 'polygonParent' || annotation.type === 'polygon'? 'fas fa-draw-polygon' - : 'fas fa-circle'"></i></small> - </mat-panel-description> - - - </mat-expansion-panel-header> - - - <div class="d-flex flex-column"> - <mat-form-field class="w-100"> - <input matInput class="font-italic color-inherit" - placeholder="Name" - [(ngModel)]="annotation.name" - (keydown.space)="$event.stopPropagation();" - annotation-list-key-listener - (keyup)="this.saveAnnotation(annotation)"/> - </mat-form-field> - - <p class="mt-2 mb-4">{{annotation.template.name}}</p> - - <div *ngIf="annotation.type !== 'polygon'" - class="w-100 d-flex align-items-center justify-content-between mt-2"> - <mat-form-field class="w-100"> - <input matInput class="w-100 font-italic color-inherit flex-grow-1" - placeholder="Position 1" - [ngModel]="(annotation.position1 | coordinateInputText)" - (ngModelChange)="annotation.position1 = positionToNumberArray($event)" - annotation-list-key-listener - (keyup)="this.saveAnnotation(annotation)"/> - </mat-form-field> - <small class="d-flex align-items-center flex-grow-0"> <i - class="fas fa-map-marked-alt mr-2 ml-2 cursor-pointer" - (click)="navigate(annotation.position1)"></i></small> - </div> - - <div *ngIf="annotation.type !== 'polygon' && annotation.position2" - class="w-100 d-flex align-items-center justify-content-between mb-2"> - <mat-form-field class="w-100"> - <input matInput - placeholder="Position 2" - class="w-100 font-italic d-flex align-items-center color-inherit" - [ngModel]="(annotation.position2 | coordinateInputText)" - (ngModelChange)="annotation.position2 = positionToNumberArray($event)" - annotation-list-key-listener - (keyup)="this.saveAnnotation(annotation)"/> - </mat-form-field> - <small class="d-flex align-items-center"> <i - class="fas fa-map-marked-alt mr-2 ml-2 cursor-pointer" - (click)="navigate(annotation.position2)"></i></small> - </div> - - <div *ngIf="annotation.type === 'polygon'"> - <div *ngFor="let position of annotation.positions; let positionIndex = index" - class="w-100 d-flex align-items-center justify-content-between mb-2"> - <mat-form-field class="w-100"> - <input matInput - [placeholder]="'Position' + (+positionIndex + 1)" - class="w-100 font-italic d-flex align-items-center color-inherit" - [value]="(position?.position | coordinateInputText)" - #polygonPositionInput - annotation-list-key-listener - (keyup)="this.savePolygonPosition(annotation.id, position, polygonPositionInput.value)"/> - </mat-form-field> - <small class="d-flex align-items-center"> <i - class="fas fa-map-marked-alt mr-2 ml-2 cursor-pointer" - (click)="navigate(position?.position)"></i></small> - </div> - </div> - - - <div class="w-100"> - <mat-form-field class="w-100"> - <textarea matInput class="w-100 color-inherit" - placeholder="Description" - cdkTextareaAutosize - #autosize="cdkTextareaAutosize" - cdkAutosizeMinRows="1" cdkAutosizeMaxRows="5" - [(ngModel)]="annotation.description" - annotation-list-key-listener - (keyup)="this.saveAnnotation(annotation)"> - </textarea></mat-form-field> - </div> +<div class="d-flex"> + <h2 class="mat-h2 mt-4 text-muted"> + Annotations + </h2> +</div> +<mat-divider></mat-divider> - <div class="d-flex align-items-center justify-content-end w-100"> - <button class="mr-1 ml-1" mat-icon-button - [attr.aria-label]="ARIA_LABELS.USER_ANNOTATION_EXPORT_SINGLE" - matTooltip="Export" - [matMenuTriggerFor]="exportSingleMenu"> - <i class="fas fa-file-export"></i> - </button> - <mat-menu #exportSingleMenu="matMenu"> - <button mat-menu-item [export-annotations]="{annotations: [annotation], sands: true}"> - SANDS format - </button> - <button mat-menu-item [export-annotations]="{annotations: [annotation], sands: false}"> - Siibra explorer format - </button> - </mat-menu> - <button class="mr-1 ml-1" mat-icon-button - [attr.aria-label]="ARIA_LABELS.USER_ANNOTATION_DELETE" - (click)="removeAnnotation(annotation)"> - <i class="fas fa-trash"></i> - </button> - </div> - </div> +<!-- list of annotations --> +<ng-template [ngIf]="managedAnnotations$ | async" [ngIfElse]="placeholderTmpl" let-managedAnnotations> - </mat-expansion-panel> + <mat-accordion *ngIf="managedAnnotations.length > 0; else placeholderTmpl" + [attr.aria-label]="ARIA_LABELS.USER_ANNOTATION_LIST" + class="h-100 d-flex flex-column overflow-auto"> <!-- expansion panel --> - <mat-expansion-panel *ngFor="let managedAnnotation of managedAnnotations$ | async" + <mat-expansion-panel *ngFor="let managedAnnotation of managedAnnotations" hideToggle> - <mat-expansion-panel-header> + <mat-expansion-panel-header [ngClass]="{'highlight': managedAnnotation.highlighted }"> <mat-panel-title class="d-flex align-items-center"> <!-- toggle visibility --> @@ -214,5 +45,16 @@ </ng-template> </mat-expansion-panel> </mat-accordion> - -</div> +</ng-template> + +<!-- place holder when no annotations exist --> +<ng-template #placeholderTmpl> + <mat-card> + <span> + No annotations visible yet. + </span> + <span> + Start by adding an annotion, or import an existing annotation. + </span> + </mat-card> +</ng-template> \ No newline at end of file diff --git a/src/atlasComponents/userAnnotations/annotationMessage/annotationMessage.component.ts b/src/atlasComponents/userAnnotations/annotationMessage/annotationMessage.component.ts deleted file mode 100644 index cf1c198e0209737d456fa297bbd30d189c8c24ee..0000000000000000000000000000000000000000 --- a/src/atlasComponents/userAnnotations/annotationMessage/annotationMessage.component.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {Component} from "@angular/core"; -import {AnnotationService} from "src/atlasComponents/userAnnotations/annotationService.service"; - -@Component({ - selector: 'annotation-message', - template: `<mat-card class="pe-all position-absolute d-flex align-items-center annotation-mode-message-panel"> - <mat-panel-title>Annotation mode</mat-panel-title> - <button mat-icon-button - color="warn" - (click)="ans.disable()" - type="button" - class="mb-2 mt-2" - matTooltip="Exit annotation mode"> - <i class="fas fa-times"></i> - </button> - </mat-card>`, - styles: [`.annotation-mode-message-panel {height: 30px;top: 20px;right: 0;}`] -}) -export class AnnotationMessage { - constructor(public ans: AnnotationService) {} -} diff --git a/src/atlasComponents/userAnnotations/annotationMode/annotationMode.component.ts b/src/atlasComponents/userAnnotations/annotationMode/annotationMode.component.ts index 7321b4de4dc86a52db6aebec572bd6735118c673..ee03a1d8d1a611f3857198ae4f6faee3cb7794f1 100644 --- a/src/atlasComponents/userAnnotations/annotationMode/annotationMode.component.ts +++ b/src/atlasComponents/userAnnotations/annotationMode/annotationMode.component.ts @@ -1,483 +1,42 @@ -import {Component, HostListener, Inject, OnDestroy, OnInit, Optional} from "@angular/core"; -import {select, Store} from "@ngrx/store"; -import { ARIA_LABELS } from "common/constants"; -import { Observable, Subscription} from "rxjs"; -import {getUuid} from "src/util/fn"; -import {VIEWER_INJECTION_TOKEN} from "src/ui/layerbrowser/layerDetail/layerDetail.component"; -import {buffer, debounceTime, distinctUntilChanged, filter, map, switchMapTo, take, takeUntil, tap} from "rxjs/operators"; -import { - viewerStateGetSelectedAtlas, - viewerStateNavigationStateSelector, - viewerStateSelectedTemplateSelector, viewerStateViewerModeSelector, -} from "src/services/state/viewerState/selectors"; -import {AnnotationService} from "src/atlasComponents/userAnnotations/annotationService.service"; -import {NehubaViewerUnit} from "src/viewerModule/nehuba"; -import {NEHUBA_INSTANCE_INJTKN} from "src/viewerModule/nehuba/util"; -import {CLICK_INTERCEPTOR_INJECTOR, ClickInterceptor} from "src/util"; +import { Component } from "@angular/core"; +import { Store } from "@ngrx/store"; import { ModularUserAnnotationToolService } from "../tools/service"; +import { viewerStateSetViewerMode } from "src/services/state/viewerState.store.helper"; +import { ARIA_LABELS } from 'common/constants' @Component({ - selector: 'annotating-mode', + selector: 'annotating-tools-panel', templateUrl: './annotationMode.template.html', styleUrls: ['./annotationMode.style.css'] }) -export class AnnotationMode implements OnInit, OnDestroy { - - public moduleAnnotationTypes: {instance: { name: string, iconClass: string }, onClick: Function} [] = [] - public selectedType = 0 - - public position1: number[] - public position2: number[] - public editingAnnotationId: string - - public selecting = 'position1' - public mousePos: number[] - public navState: any = {} - - private hoverAnnotation$: Observable<{id: string, partIndex: number}> - // public hoverAnnotation: {id: string, partIndex: number} - private onDestroyCb: Function[] = [] - public subscriptions: Subscription[] = [] - - private get viewer(){ - return this.injectedViewer || (window as any).viewer - } - - private _nehubaViewer: NehubaViewerUnit; - - get nehubaViewer(){ - return this._nehubaViewer - } - set nehubaViewer(v: NehubaViewerUnit) { - this._nehubaViewer = v - } - - get interactiveViewer() { - return (window as any).interactiveViewer - } - - constructor( - private store$: Store<any>, - public ans: AnnotationService, - private modularToolSvc: ModularUserAnnotationToolService, - @Optional() @Inject(VIEWER_INJECTION_TOKEN) private injectedViewer, - @Optional() @Inject(NEHUBA_INSTANCE_INJTKN) nehubaViewer$: Observable<NehubaViewerUnit>, - @Optional() @Inject(CLICK_INTERCEPTOR_INJECTOR) clickInterceptor: ClickInterceptor - ) { - this.moduleAnnotationTypes = this.modularToolSvc.moduleAnnotationTypes - if (clickInterceptor) { - const { register, deregister } = clickInterceptor - const onMouseClick = this.onMouseClick.bind(this) - register(onMouseClick) - this.onDestroyCb.push(() => deregister(onMouseClick)) - } - - if (nehubaViewer$) { - this.subscriptions.push( - nehubaViewer$.subscribe( - viewer => this.nehubaViewer = viewer - ) - ) - } - } - - onMouseClick(e: any) { - let viewerMode: string - this.store$.pipe( - select(viewerStateViewerModeSelector), - take(1) - ).subscribe(vm => viewerMode = vm) - if (viewerMode === ARIA_LABELS.VIEWER_MODE_ANNOTATING) return false - } - - ngOnInit(): void { - // Load annotation layer on init - this.ans.loadAnnotationLayer() - - this.hoverAnnotation$ = new Observable<{id: string, partIndex: number}>(obs => { - const mouseState = this.viewer.mouseState - const cb: () => void = mouseState.changed.add(() => { - if (mouseState.active && mouseState.pickedAnnotationLayer === this.ans.addedLayer.layer.annotationLayerState.value) { - obs.next({ - id: mouseState.pickedAnnotationId, - partIndex: mouseState.pickedOffset - }) - } else { - obs.next(null) - } - }) - this.onDestroyCb.push(() => { - cb() - obs.complete() - }) - }).pipe( - distinctUntilChanged((o, n) => { - if (o === n) return true - return `${o?.id || ''}${o?.partIndex || ''}` === `${n?.id || ''}${n?.partIndex || ''}` - }) - ) - - this.subscriptions.push(this.hoverAnnotation$.subscribe(ha => { - this.ans.hoverAnnotation = ha - })) - - const mouseDown$ = this.interactiveViewer.viewerHandle.mouseEvent.pipe( - filter((e: any) => e.eventName === 'mousedown') - ) - const mouseUp$ = this.interactiveViewer.viewerHandle.mouseEvent.pipe( - filter((e: any) => e.eventName === 'mouseup') - ) - const mouseMove$ = this.interactiveViewer.viewerHandle.mouseEvent.pipe( - filter((e: any) => e.eventName === 'mousemove') - ) - - this.subscriptions.push( - // Trigger mouse click on viewer (avoid dragging) - mouseDown$.pipe( - switchMapTo( - mouseUp$.pipe( - takeUntil(mouseMove$), - ), - ), - ).subscribe(event => { - if (event.event.button === 2) { - if (this.selecting === 'position2' && this.mousePos) { - this.ans.removeAnnotation(this.editingAnnotationId) - this.editingAnnotationId = null - this.selecting = 'position1' - } - } else { - this.mouseClick() - } - }) - ) - - // Dragging - edit hovering annotations while dragging - let hovering: any - let hoveringType: string - let hoveringName: string - let hoveringPosition1: number[] - let hoveringPosition2: number[] - let draggingStartPosition: any[] - let hoveringPolygonAnnotations: any[] - let dragging = false - this.subscriptions.push( - mouseDown$.pipe( - tap(() => { - hovering = this.ans.hoverAnnotation - if (hovering) { - draggingStartPosition = this.mousePos - const hoveringAnnotation = this.ans.pureAnnotationsForViewer.find(a => a.id === this.ans.hoverAnnotation.id) - if (hoveringAnnotation) { - hoveringPosition1 = hoveringAnnotation.position1 - hoveringPosition2 = hoveringAnnotation.position2 ? hoveringAnnotation.position2 : null - hoveringType = this.ans.pureAnnotationsForViewer.find(a => a.id === hovering.id)?.type - if (hoveringAnnotation.type === 'polygon') { - hoveringPolygonAnnotations = this.ans.pureAnnotationsForViewer.filter(a => a.id.split('_')[0] === hovering.id.split('_')[0]) - } - hoveringName = this.ans.pureAnnotationsForViewer.find(a => a.id === hovering.id)?.name - } - } - }), - switchMapTo( - mouseMove$.pipe( - takeUntil(mouseUp$.pipe(tap(() => { - if (dragging) { - this.ans.storeBackup() - dragging = false - } - }) - )), - ), - ), - ).subscribe(event => { - if (hovering && this.selecting !== 'position2') { - dragging = true - // keep navigation while dragging - // this.interactiveViewer.viewerHandle.setNavigationLoc(this.navState) - this.nehubaViewer.setNavigationState({ - position: this.navState.position, - perspectiveOrientation: this.navState.perspectiveOrientation, - positionReal: true - }) - // make changes to annotations by type - // - when line is hovered move full annotation - - // - when line point is hovered move only point - if (this.mousePos) { - const dragRange = this.mousePos.map((mp, i) => mp - +draggingStartPosition[i]) - - if (hoveringType === 'point') { - this.ans.saveAnnotation({id: hovering.id, position1: this.mousePos, name: hoveringName, type: hoveringType}, false, true) - } else if (hoveringType === 'line') { - if (hovering.partIndex === 0) { - this.ans.saveAnnotation({id: hovering.id, - position1: hoveringPosition1.map((hp, i) => +hp + dragRange[i]), - position2: hoveringPosition2.map((hp, i) => +hp + dragRange[i]), - name: hoveringName, - type: hoveringType}, false, true) - } else if (hovering.partIndex === 1) { - this.ans.saveAnnotation({id: hovering.id, - position1: this.mousePos, - position2: hoveringPosition2, - name: hoveringName, - type: hoveringType}, false, true) - } else if (hovering.partIndex === 2) { - this.ans.saveAnnotation({id: hovering.id, - position1: hoveringPosition1, - position2: this.mousePos, - name: hoveringName, - type: hoveringType}, false, true) - } - } else if (hoveringType === 'bounding box') { - this.ans.saveAnnotation({id: hovering.id, - position1: hoveringPosition1.map((hp, i) => +hp + dragRange[i]), - position2: hoveringPosition2.map((hp, i) => +hp + dragRange[i]), - name: hoveringName, - type: hoveringType}, false, true) - } else if (hoveringType === 'ellipsoid') { - this.ans.saveAnnotation({id: hovering.id, - position1: hoveringPosition1.map((hp, i) => +hp + dragRange[i]), - position2: hoveringPosition2, - name: hoveringName, - type: hoveringType}, false, true) - } else if (hoveringType === 'polygon') { - if (hovering.partIndex === 0) { - hoveringPolygonAnnotations.forEach(pa => { - this.ans.saveAnnotation({ - id: pa.id, - position1: pa.position1.map((hp, i) => +hp + dragRange[i]), - position2: pa.position2.map((hp, i) => +hp + dragRange[i]), - name: pa.name, - description: pa.description, - type: pa.type - }, false, true) - }) - } else { - let samePos1: any[] - let samePos2: any[] - const name = hoveringPolygonAnnotations[0].name - const description = hoveringPolygonAnnotations[0].description - if (hovering.partIndex === 2) { - samePos1 = hoveringPolygonAnnotations.filter(hp => hp.id !== hovering.id && hp.position1.join() === hoveringPosition2.join()) - samePos2 = hoveringPolygonAnnotations.filter(hp => hp.id !== hovering.id && hp.position2.join() === hoveringPosition2.join()) - this.ans.saveAnnotation({id: hovering.id, - position1: hoveringPosition1, - position2: this.mousePos, - name, - description, - type: hoveringType}, true, false) - } else if (hovering.partIndex === 1) { - samePos1 = hoveringPolygonAnnotations.filter(hp => hp.id !== hovering.id && hp.position1.join() === hoveringPosition1.join()) - samePos2 = hoveringPolygonAnnotations.filter(hp => hp.id !== hovering.id && hp.position2.join() === hoveringPosition1.join()) - this.ans.saveAnnotation({id: hovering.id, - position1: this.mousePos, - position2: hoveringPosition2, - name, - description, - type: hoveringType}, true, false) - - } - samePos1.forEach(a => { - this.ans.saveAnnotation({id: a.id, - position1: this.mousePos, - position2: a.position2, - name, - description, - type: a.type}, true, false) - }) - - samePos2.forEach(a => { - this.ans.saveAnnotation({id: a.id, - position1: a.position1, - position2: this.mousePos, - name, - description, - type: a.type}, true, false) - }) - - this.ans.addPolygonsToGroupedAnnotations( - this.ans.pureAnnotationsForViewer.filter(a => a.id.split('_')[0] === hovering.id.split('_')[0]) - ) - } - } - } - } - - }) - ) - - this.subscriptions.push( - this.nehubaViewer.mousePosInVoxel$ - .subscribe(floatArr => { - this.mousePos = floatArr && floatArr - if (this.selecting === 'position1' && this.mousePos) { - this.position1 = this.mousePos - } else if (this.selecting === 'position2' && this.mousePos) { - if (this.ans.annotationTypes[this.selectedType].name === 'Ellipsoid') { - this.position2 = this.ans.getRadii(this.position1, this.mousePos) - } else { - this.position2 = this.mousePos - } - - if (this.position1 - && (this.ans.annotationTypes[this.selectedType].type === 'doubleCoordinate' - || this.ans.annotationTypes[this.selectedType].type === 'polygon') - && this.position2) { - if (!this.editingAnnotationId) { - this.editingAnnotationId = getUuid() - if (this.ans.annotationTypes[this.selectedType].type === 'polygon') { - this.editingAnnotationId += '_0' - } - } - this.ans.saveAnnotation({id: this.editingAnnotationId, position1: this.position1, - position2: this.position2, - type: this.ans.annotationTypes[this.selectedType].name}, false) - } - } - }), - - // Double click - end creating polygon - mouseUp$.pipe( - buffer(mouseUp$.pipe(debounceTime(250))), - map((list: any) => list.length), - filter(x => x === 2) - ).subscribe(() => { - - if (this.ans.annotationTypes[this.selectedType].type === 'polygon') { - // this.ans.removeAnnotation(this.editingAnnotationId) - this.ans.saveEditList = this.ans.saveEditList.filter(se => se.id !== this.editingAnnotationId) - this.ans.removeAnnotationFromViewer(this.editingAnnotationId) - - const annIdObj = this.editingAnnotationId.split('_') - const prevAnnotation = this.ans.saveEditList.find(a => a.id === `${annIdObj[0]}_${+annIdObj[1] - 1}`) - if (prevAnnotation && prevAnnotation.id) { - this.ans.saveEditList = this.ans.saveEditList.filter(se => se.id !== prevAnnotation.id) - this.ans.removeAnnotationFromViewer(prevAnnotation.id) - } - - this.ans.storeBackup() - this.changeToDefaultTool() - - this.editingAnnotationId = null - this.selecting = 'position1' - } - }), - - this.store$.pipe( - select(viewerStateNavigationStateSelector), - ).subscribe(nav => { - this.navState.position = nav.position - this.navState.perspectiveOrientation = nav.perspectiveOrientation - }), - this.store$.pipe( - select(viewerStateGetSelectedAtlas) - ).subscribe(atlas => { - this.ans.selectedAtlas = {name: atlas.name, id: atlas['@id']} - }), - - this.store$.pipe( - select(viewerStateSelectedTemplateSelector), - take(1) - ).subscribe(tmpl => { - this.ans.selectedTemplate = { - name: tmpl.name, - id: tmpl['@id'] - } - this.ans.voxelSize = this.ans.getVoxelFromSpace(tmpl.fullId) - - this.ans.loadAnnotationsOnInit() - // Set get annotations from the local storage and add them to the viewer - - }) - ) - - } - - ngOnDestroy(): void { - while (this.onDestroyCb.length > 0) this.onDestroyCb.pop()() - this.subscriptions.forEach(s => s.unsubscribe()) - if (this.ans.addedLayer) { - this.viewer.layerManager.removeManagedLayer(this.ans.addedLayer) - } - } - - onKeydownHandler() { - this.modularToolSvc.deselectTools() - if (this.selecting === 'position2' && this.mousePos) { - this.ans.removeAnnotation(this.editingAnnotationId) - this.ans.storeBackup() - this.editingAnnotationId = null - this.selecting = 'position1' - } - } - - @HostListener('contextmenu', ['$event']) - onClickListener(ev: MouseEvent){ - if (this.selecting === 'position2' && this.mousePos) { - this.ans.removeAnnotation(this.editingAnnotationId) - this.editingAnnotationId = null - this.selecting = 'position1' - } - } - - - mouseClick() { - // Remove annotation - if (this.ans.annotationTypes[this.selectedType].type === 'remove' && this.ans.hoverAnnotation) { - const hoveringAnnotationObj = this.ans.pureAnnotationsForViewer.find(a => a.id === this.ans.hoverAnnotation.id) - if (hoveringAnnotationObj.type === 'polygon') { - const polygonAnnotations = this.ans.pureAnnotationsForViewer.filter(a => a.id.split('_')[0] === hoveringAnnotationObj.id.split('_')[0]) - polygonAnnotations.forEach(pa => this.ans.removeAnnotation(pa.id)) - } else { - this.ans.removeAnnotation(this.ans.hoverAnnotation.id) - } - this.changeToDefaultTool() - } - // save annotation by selected annotation type - if (this.selecting === 'position1' && this.position1) { - if (this.ans.annotationTypes[this.selectedType].type === 'singleCoordinate') { - this.ans.saveAnnotation({name: this.ans.generateNameByType(this.ans.annotationTypes[this.selectedType].name), - position1: this.position1, - type: this.ans.annotationTypes[this.selectedType].name}) - this.changeToDefaultTool() - } else if (this.ans.annotationTypes[this.selectedType].type === 'doubleCoordinate' - || this.ans.annotationTypes[this.selectedType].type === 'polygon') { - this.selecting = 'position2' - } - } else if (this.selecting === 'position2' && this.position2 && this.mousePos) { - if (this.ans.annotationTypes[this.selectedType].type === 'polygon') { - this.ans.saveAnnotation({id: this.editingAnnotationId, - position1: this.position1, - position2: this.position2, - type: this.ans.annotationTypes[this.selectedType].name}, false, true) - this.position1 = this.position2 - const splitEditingAnnotationId = this.editingAnnotationId.split('_') - this.editingAnnotationId = splitEditingAnnotationId[0] + '_' + (+splitEditingAnnotationId[1]+1) - } else { - this.ans.saveAnnotation({id: this.editingAnnotationId, - name: this.ans.generateNameByType(this.ans.annotationTypes[this.selectedType].name), - position1: this.position1, - position2: this.position2, - type: this.ans.annotationTypes[this.selectedType].name}) - this.changeToDefaultTool() - this.editingAnnotationId = null - this.selecting = 'position1' - } - - } - } - - changeToDefaultTool() { - this.selectedType = 0 - } - - public selectAnnotationType = (typeIndex) => { - this.selectedType = typeIndex - this.editingAnnotationId = null - this.mousePos = null - this.position2 = null - this.position1 = null - this.selecting = 'position1' - } - +export class AnnotationMode { + + public ARIA_LABELS = ARIA_LABELS + + public moduleAnnotationTypes: { + instance: { + name: string + iconClass: string + } + onClick: Function + }[] = [] + + constructor( + private store$: Store<any>, + private modularToolSvc: ModularUserAnnotationToolService, + ) { + this.moduleAnnotationTypes = this.modularToolSvc.moduleAnnotationTypes + } + + exitAnnotationMode(){ + this.store$.dispatch( + viewerStateSetViewerMode({ + payload: null + }) + ) + } + deselectTools(){ + console.log('deselect tools') + this.modularToolSvc.deselectTools() + } } diff --git a/src/atlasComponents/userAnnotations/annotationMode/annotationMode.style.css b/src/atlasComponents/userAnnotations/annotationMode/annotationMode.style.css index 89e110ea0d153b7a64e82c7a02deef854ead78d2..66ea5da7917740147c7d789a0630117a726ec805 100644 --- a/src/atlasComponents/userAnnotations/annotationMode/annotationMode.style.css +++ b/src/atlasComponents/userAnnotations/annotationMode/annotationMode.style.css @@ -1,7 +1,13 @@ -.annotation-toolbar { - z-index: 100; - width: 40px; -} -.annotation-toolbar-content { - margin-bottom: 20px; +.tab-toggle-container +{ + margin-left: -3rem; + padding-top: 0; + padding-bottom: 0; } + +.tab-toggle +{ + margin: 0.25rem -1rem 0.25rem 0rem; + padding-right: 1rem; + text-align: right; +} \ No newline at end of file diff --git a/src/atlasComponents/userAnnotations/annotationMode/annotationMode.template.html b/src/atlasComponents/userAnnotations/annotationMode/annotationMode.template.html index 3158ce368dcdad73c8b03f7f1a7052c41ce7ac1d..a21e682b08bc6b9e7adcab3737e1261fe63d9e5a 100644 --- a/src/atlasComponents/userAnnotations/annotationMode/annotationMode.template.html +++ b/src/atlasComponents/userAnnotations/annotationMode/annotationMode.template.html @@ -1,41 +1,25 @@ -<div class="pe-all d-flex flex-column justify-content-content z-index-10 w-3em" - [iav-key-listener]="[{ type: 'keydown', key: 'Escape', target: 'document' }]" - (iav-key-event)="onKeydownHandler()"> +<mat-card class="d-inline-flex flex-column pe-all tab-toggle-container" + [iav-key-listener]="[{ type: 'keydown', key: 'Escape', target: 'document', capture: true }]" + (iav-key-event)="deselectTools()"> - <mat-card class="d-flex flex-column flex-grow-0 panel-default p-0"> + <button + *ngFor="let moduleAnnotType of moduleAnnotationTypes" + mat-button + class="tab-toggle" + (click)="moduleAnnotType.onClick()" + [color]="(moduleAnnotType.instance.toolSelected$ | async) ? 'primary' : 'basic'" + type="button"> + <i [class]="moduleAnnotType.instance.iconClass"></i> + </button> - <div *ngFor="let type of ans.annotationTypes; let i = index; let last = last"> - <button - mat-icon-button - type="button" - (click)="selectAnnotationType(i)" - class="mb-2 mt-2" - [matTooltip]="type.name" - [color]="selectedType === i? 'primary' : 'secondary'"> - <i [ngClass]="type.class"></i> - </button> - <mat-divider *ngIf="last || type.action !== ans.annotationTypes[i+1].action"></mat-divider> - </div> - <button - mat-icon-button - (click)="ans.disable()" - type="button" - class="mb-2 mt-2" - matTooltip="Exit annotation mode" - color="warn"> - <i class="fas fa-times"></i> - </button> - <div> - <mat-divider></mat-divider> - <button - *ngFor="let moduleAnnotType of moduleAnnotationTypes" - mat-icon-button - (click)="moduleAnnotType.onClick()" - class="mt-2 mb-2" - [color]="(moduleAnnotType.instance.toolSelected$ | async) ? 'primary' : 'basic'" - type="button"> - <i [class]="moduleAnnotType.instance.iconClass"></i> - </button> - </div> - </mat-card> -</div> + <mat-divider class="d-block"></mat-divider> + + <button + mat-button + (click)="exitAnnotationMode()" + class="tab-toggle" + [matTooltip]="ARIA_LABELS.EXIT_ANNOTATION_MODE" + color="warn"> + <i class="fas fa-times"></i> + </button> +</mat-card> \ No newline at end of file diff --git a/src/atlasComponents/userAnnotations/directives/annotationSwitch.directive.ts b/src/atlasComponents/userAnnotations/directives/annotationSwitch.directive.ts index 3ff35b28af30ae4b60199457850ac572aad38020..88a2b9d47149ef12d627ee5c0bbf512139d17c38 100644 --- a/src/atlasComponents/userAnnotations/directives/annotationSwitch.directive.ts +++ b/src/atlasComponents/userAnnotations/directives/annotationSwitch.directive.ts @@ -1,24 +1,20 @@ -import {Directive, HostListener} from "@angular/core"; -import {viewerStateSetViewerMode} from "src/services/state/viewerState/actions"; -import {ARIA_LABELS} from "common/constants"; -import {Store} from "@ngrx/store"; +import { Directive, HostListener } from "@angular/core"; +import { viewerStateSetViewerMode } from "src/services/state/viewerState/actions"; +import { ARIA_LABELS } from "common/constants"; +import { Store } from "@ngrx/store"; @Directive({ selector: '[annotation-switch]' }) export class AnnotationSwitch { - - - constructor(private store$: Store<any>) { - - } + constructor(private store$: Store<any>) {} @HostListener('click') onClick() { - this.setAnnotatingMode() - } - - private setAnnotatingMode() { - this.store$.dispatch(viewerStateSetViewerMode({payload: ARIA_LABELS.VIEWER_MODE_ANNOTATING})) + this.store$.dispatch( + viewerStateSetViewerMode({ + payload: ARIA_LABELS.VIEWER_MODE_ANNOTATING + }) + ) } } diff --git a/src/atlasComponents/userAnnotations/index.ts b/src/atlasComponents/userAnnotations/index.ts index d5c0a00d05c9b537c5399dc66779a109a6035ef8..6da0cda7d75c0521617ec52270af1cb695ee7776 100644 --- a/src/atlasComponents/userAnnotations/index.ts +++ b/src/atlasComponents/userAnnotations/index.ts @@ -1,4 +1,3 @@ export { AnnotationMode } from "./annotationMode/annotationMode.component"; export { AnnotationList } from "./annotationList/annotationList.component"; -export { AnnotationMessage } from "./annotationMessage/annotationMessage.component"; export { UserAnnotationsModule } from "./module"; diff --git a/src/atlasComponents/userAnnotations/module.ts b/src/atlasComponents/userAnnotations/module.ts index f79e3e2f9c657dcc3859fffeda99219d930cff84..e3c764001977ef4907627a681d9e39342cbc722d 100644 --- a/src/atlasComponents/userAnnotations/module.ts +++ b/src/atlasComponents/userAnnotations/module.ts @@ -7,7 +7,6 @@ import {BrowserAnimationsModule} from "@angular/platform-browser/animations"; import {AnnotationMode} from "src/atlasComponents/userAnnotations/annotationMode/annotationMode.component"; import {AnnotationList} from "src/atlasComponents/userAnnotations/annotationList/annotationList.component"; import {AnnotationService} from "src/atlasComponents/userAnnotations/annotationService.service"; -import {AnnotationMessage} from "src/atlasComponents/userAnnotations/annotationMessage/annotationMessage.component"; import { UserAnnotationToolModule } from "./tools/module"; import {AnnotationSwitch} from "src/atlasComponents/userAnnotations/directives/annotationSwitch.directive"; import {ExportAnnotation} from "src/atlasComponents/userAnnotations/directives/exportAnnotation.directive"; @@ -32,7 +31,6 @@ import { AnnotationVisiblePipe } from "./annotationVisible.pipe"; declarations: [ AnnotationMode, AnnotationList, - AnnotationMessage, AnnotationSwitch, ImportAnnotation, ExportAnnotation, @@ -49,7 +47,6 @@ import { AnnotationVisiblePipe } from "./annotationVisible.pipe"; exports: [ AnnotationMode, AnnotationList, - AnnotationMessage, AnnotationSwitch ] }) diff --git a/src/atlasComponents/userAnnotations/tools/delete.ts b/src/atlasComponents/userAnnotations/tools/delete.ts new file mode 100644 index 0000000000000000000000000000000000000000..155651978b4e7d805272c5c3e9846f61dc7dc8ea --- /dev/null +++ b/src/atlasComponents/userAnnotations/tools/delete.ts @@ -0,0 +1,73 @@ +import { 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"; + +export class ToolDelete extends AbsToolClass implements IAnnotationTools, OnDestroy { + + public subs: Subscription[] = [] + toolType: TToolType = 'deletion' + iconClass = 'fas fa-trash' + name = 'Delete' + + onMouseMoveRenderPreview(){ + return [] + } + removeAnnotation(){ + + } + + managedAnnotations$ = new Subject<Point[]>() + private allManAnnotations: IAnnotationGeometry[] = [] + allNgAnnotations$ = new Subject<TNgAnnotationPoint[]>() + constructor( + annotationEv$: Observable<TAnnotationEvent<keyof IAnnotationEvents>>, + callback: TCallbackFunction + ){ + super(annotationEv$, callback) + this.init() + } + init(){ + if (this.callback) { + const obs$ = this.callback({ type: 'requestManAnnStreeam' }) + if (!obs$) throw new Error(`Error requestManAnnStreeam`) + + const toolDeselect$ = this.toolSelected$.pipe( + filter(flag => !flag) + ) + const toolSelThenClick$ = this.toolSelected$.pipe( + filter(flag => !!flag), + switchMapTo(this.mouseClick$.pipe( + takeUntil(toolDeselect$) + )) + ) + + this.subs.push( + /** + * Get stream of all managed annotations + */ + obs$.subscribe(manAnn => { + this.allManAnnotations = manAnn + }), + toolSelThenClick$.pipe( + withLatestFrom(this.hoverAnnotation$) + ).subscribe(([ _, ev ]) => { + const annId = ev?.detail?.pickedAnnotationId + if (!annId) return + for (const manan of this.allManAnnotations) { + if (manan.getNgAnnotationIds().indexOf(annId) >= 0) { + manan.remove() + return + } + } + }) + ) + } + } + + + ngOnDestroy(){ + while (this.subs.length > 0) this.subs.pop().unsubscribe() + } +} diff --git a/src/atlasComponents/userAnnotations/tools/point.ts b/src/atlasComponents/userAnnotations/tools/point.ts index 21cc0deb9f24d74e650bf86c71a095bba03f11c9..0b7d51d5e604dd3fbbf9a432436ea813bccd1884 100644 --- a/src/atlasComponents/userAnnotations/tools/point.ts +++ b/src/atlasComponents/userAnnotations/tools/point.ts @@ -119,7 +119,11 @@ export class ToolPoint extends AbsToolClass implements IAnnotationTools, OnDestr pt.remove = () => this.removeAnnotation(id) this.managedAnnotations.push(pt) this.managedAnnotations$.next(this.managedAnnotations) - + + /** + * force refresh of ng annotation + */ + this.forceRefresh$.next(null) /** * deselect on selecting a point */ @@ -142,9 +146,9 @@ export class ToolPoint extends AbsToolClass implements IAnnotationTools, OnDestr * evts which forces redraw of ng annotations */ merge( - toolSelThenClick$, this.forceRefresh$, ).subscribe(() => { + console.log('emit... here?') let out: INgAnnotationTypes['point'][] = [] for (const managedAnn of this.managedAnnotations) { if (managedAnn.space['@id'] === this.space['@id']) { diff --git a/src/atlasComponents/userAnnotations/tools/select.ts b/src/atlasComponents/userAnnotations/tools/select.ts new file mode 100644 index 0000000000000000000000000000000000000000..34ee2b2de535f6205b19e36804bb96e8b6be631e --- /dev/null +++ b/src/atlasComponents/userAnnotations/tools/select.ts @@ -0,0 +1,76 @@ +import { 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"; + +export class ToolSelect extends AbsToolClass implements IAnnotationTools, OnDestroy { + + public subs: Subscription[] = [] + toolType: TToolType = 'selecting' + iconClass = 'fas fa-mouse-pointer' + name = 'Select' + + onMouseMoveRenderPreview(){ + return [] + } + removeAnnotation(){ + + } + + managedAnnotations$ = new Subject<Point[]>() + allNgAnnotations$ = new Subject<TNgAnnotationPoint[]>() + constructor( + annotationEv$: Observable<TAnnotationEvent<keyof IAnnotationEvents>>, + callback: TCallbackFunction + ){ + super(annotationEv$, callback) + this.init() + } + + private highLightedAnnotation: IAnnotationGeometry + private allManAnnotations: IAnnotationGeometry[] = [] + init(){ + if (this.callback) { + const obs$ = this.callback({ type: 'requestManAnnStreeam' }) + if (!obs$) throw new Error(`Error requestManAnnStreeam`) + this.subs.push( + /** + * Get stream of all managed annotations + */ + obs$.subscribe(manAnn => { + this.allManAnnotations = manAnn + }), + + /** + * on hover ng annotatoin + */ + this.hoverAnnotation$.subscribe(ev => { + this.highLightedAnnotation?.setHighlighted(false) + const annId = ev?.detail?.pickedAnnotationId + if (!annId) return + for (const manan of this.allManAnnotations) { + if (manan.getNgAnnotationIds().indexOf(annId) >= 0) { + manan.setHighlighted(true) + this.highLightedAnnotation = manan + return + } + } + }), + + /** + * on deselect tool + */ + this.toolSelected$.pipe( + filter(flag => !flag) + ).subscribe(() => { + this.highLightedAnnotation?.setHighlighted(false) + }) + ) + } + } + + ngOnDestroy(){ + while (this.subs.length > 0) this.subs.pop().unsubscribe() + } +} \ No newline at end of file diff --git a/src/atlasComponents/userAnnotations/tools/service.ts b/src/atlasComponents/userAnnotations/tools/service.ts index 76d41728a1c1dd9479b17dc943aa501d64f5c46b..9cc72aa5ad507cda981c4a9ada6fda416ecebab4 100644 --- a/src/atlasComponents/userAnnotations/tools/service.ts +++ b/src/atlasComponents/userAnnotations/tools/service.ts @@ -15,6 +15,8 @@ import { PolyUpdateCmp } from './poly/poly.component' import { Point, ToolPoint } from "./point"; import { PointUpdateCmp } from "./point/point.component"; import { LineUpdateCmp } from "./line/line.component"; +import { ToolSelect } from "./select"; +import { ToolDelete } from "./delete"; const IAV_VOXEL_SIZES_NM = { 'minds/core/referencespace/v1.0.0/265d32a0-3d84-40a5-926f-bf89f68212b9': [25000, 25000, 25000], @@ -94,9 +96,9 @@ export class ModularUserAnnotationToolService implements OnDestroy{ private registeredTools: { name: string iconClass: string + toolInsance: AbsToolClass target?: ClassInterface<IAnnotationGeometry> editCmp?: ClassInterface<any> - onMouseMoveRenderPreview: (pos: [number, number, number]) => INgAnnotationTypes[keyof INgAnnotationTypes][] onDestoryCallBack: () => void }[] = [] private mousePosReal: [number, number, number] @@ -107,6 +109,9 @@ export class ModularUserAnnotationToolService implements OnDestroy{ this.deselectTools() return } + case 'requestManAnnStreeam': { + return this.managedAnnotations$ + } } } @@ -124,7 +129,7 @@ export class ModularUserAnnotationToolService implements OnDestroy{ toolCls: ClassInterface<T> target?: ClassInterface<IAnnotationGeometry> editCmp?: ClassInterface<any> - }){ + }): AbsToolClass{ const { toolCls: Cls, target, editCmp } = arg const newTool = new Cls(this.annotnEvSubj, arg => this.handleToolCallback(arg)) as AbsToolClass & { ngOnDestroy?: Function } const { name, iconClass, onMouseMoveRenderPreview } = newTool @@ -145,27 +150,35 @@ export class ModularUserAnnotationToolService implements OnDestroy{ const toolSubscriptions: Subscription[] = [] - toolSubscriptions.push( - newTool.allNgAnnotations$.subscribe(ann => { - this.ngAnnotations$.next({ - tool: name, - annotations: ann - }) - }), - newTool.managedAnnotations$.subscribe(ann => { - this.managedAnnotationsStream$.next({ - annotations: ann, - tool: name + const { allNgAnnotations$, managedAnnotations$ } = newTool + + if ( allNgAnnotations$ ) { + toolSubscriptions.push( + newTool.allNgAnnotations$.subscribe(ann => { + this.ngAnnotations$.next({ + tool: name, + annotations: ann + }) + }), + ) + } + if ( managedAnnotations$ ){ + toolSubscriptions.push( + managedAnnotations$.subscribe(ann => { + this.managedAnnotationsStream$.next({ + annotations: ann, + tool: name + }) }) - }) - ) + ) + } this.registeredTools.push({ name, iconClass, target, editCmp, - onMouseMoveRenderPreview: onMouseMoveRenderPreview.bind(newTool), + toolInsance: newTool, onDestoryCallBack: () => { newTool.ngOnDestroy && newTool.ngOnDestroy() this.managedAnnotationsStream$.next({ @@ -175,6 +188,8 @@ export class ModularUserAnnotationToolService implements OnDestroy{ while(toolSubscriptions.length > 0) toolSubscriptions.pop().unsubscribe() } }) + + return newTool } /** @@ -199,6 +214,11 @@ export class ModularUserAnnotationToolService implements OnDestroy{ @Optional() @Inject(NEHUBA_INSTANCE_INJTKN) nehubaViewer$: Observable<NehubaViewerUnit>, ){ + const selTool = this.registerTool({ + toolCls: ToolSelect + }) + this.defaultTool = selTool + this.registerTool({ toolCls: ToolPoint, target: Point, @@ -217,6 +237,10 @@ export class ModularUserAnnotationToolService implements OnDestroy{ editCmp: PolyUpdateCmp, }) + this.registerTool({ + toolCls: ToolDelete + }) + /** * listen to mouse event on nehubaViewer, and emit as TAnnotationEvent */ @@ -345,8 +369,10 @@ export class ModularUserAnnotationToolService implements OnDestroy{ console.warn(`cannot find tool ${selectedToolName}`) return } - const { onMouseMoveRenderPreview } = selectedTool - const previewNgAnnotation = onMouseMoveRenderPreview([ngMouseEvent.x, ngMouseEvent.y, ngMouseEvent.z]) + const { toolInsance } = selectedTool + const previewNgAnnotation = toolInsance.onMouseMoveRenderPreview + ? toolInsance.onMouseMoveRenderPreview([ngMouseEvent.x, ngMouseEvent.y, ngMouseEvent.z]) + : [] if (this.previewNgAnnIds.length !== previewNgAnnotation.length) { this.clearAllPreviewAnnotations() @@ -505,7 +531,7 @@ export class ModularUserAnnotationToolService implements OnDestroy{ } public getEditAnnotationCmp(annotation: IAnnotationGeometry): ClassInterface<any>{ - const foundTool = this.registeredTools.find(t => annotation instanceof t.target) + const foundTool = this.registeredTools.find(t => t.target && annotation instanceof t.target) return foundTool && foundTool.editCmp } @@ -522,14 +548,14 @@ export class ModularUserAnnotationToolService implements OnDestroy{ } } + private defaultTool: AbsToolClass public deselectTools(){ - // TODO refactor this.activeToolName = null this.annotnEvSubj.next({ type: 'toolSelect', detail: { - name: null + name: this.defaultTool.name || null } }) } diff --git a/src/atlasComponents/userAnnotations/tools/type.ts b/src/atlasComponents/userAnnotations/tools/type.ts index 65edbdd41c5d164c89a417459fa3341925e3d483..e8be6598144c0d30b90a4f883bd81f254d67d6d2 100644 --- a/src/atlasComponents/userAnnotations/tools/type.ts +++ b/src/atlasComponents/userAnnotations/tools/type.ts @@ -5,7 +5,9 @@ import { getUuid } from "src/util/fn" /** * base class to be extended by all annotation tools + * TODO perhaps split into drawing subclass/utility subclass */ + export abstract class AbsToolClass { public abstract name: string @@ -153,16 +155,20 @@ export abstract class AbsToolClass { ) } -export type TToolType = 'translation' | 'drawing' | 'deletion' +export type TToolType = 'selecting' | 'drawing' | 'deletion' export type TCallback = { paintingEnd: { callArg: {} returns: void } + requestManAnnStreeam: { + callArg: {} + returns: Observable<IAnnotationGeometry[]> + } } -export type TCallbackFunction = <T extends keyof TCallback>(arg: TCallback[T]['callArg'] & { type: T }) => TCallback[T] | void +export type TCallbackFunction = <T extends keyof TCallback>(arg: TCallback[T]['callArg'] & { type: T }) => TCallback[T]['returns'] | void export type TBaseAnnotationGeomtrySpec = { id?: string @@ -229,7 +235,20 @@ export interface ISandsAnnotation { polyline: TSandsPolyLine } -export abstract class IAnnotationGeometry { +export abstract class Highlightable { + + public highlighted = false + constructor(defaultFlag?: boolean){ + if (typeof defaultFlag !== 'undefined') { + this.highlighted = defaultFlag + } + } + setHighlighted(flag: boolean){ + this.highlighted = flag + } +} + +export abstract class IAnnotationGeometry extends Highlightable { public id: string public name: string @@ -249,6 +268,7 @@ export abstract class IAnnotationGeometry { public updateSignal$ = new Subject() constructor(spec?: TBaseAnnotationGeomtrySpec){ + super() this.id = spec && spec.id || getUuid() this.space = spec?.space this.name = spec?.name diff --git a/src/viewerModule/viewerCmp/viewerCmp.template.html b/src/viewerModule/viewerCmp/viewerCmp.template.html index 6e2d0bcdab4f197299c340b59f799a8a50feedaa..5ae2b7d4c0e22f7713fabe456b59f735c34ab882 100644 --- a/src/viewerModule/viewerCmp/viewerCmp.template.html +++ b/src/viewerModule/viewerCmp/viewerCmp.template.html @@ -5,35 +5,45 @@ <layout-floating-container [zIndex]="10"> - <!-- Annotation mode --> <div *ngIf="hideUi$ | async"> - <mat-drawer-container class="mat-drawer-content-overflow-visible w-100 h-100 position-absolute invisible" - [hasBackdrop]="false"> - <mat-drawer #drawer [mode]="'push'" [disableClose]="true" class="box-shadow-none border-0 pe-all col-10 col-sm-10 col-md-5 col-lg-4 col-xl-3 col-xxl-2"> + [hasBackdrop]="false"> + <mat-drawer #annotationDrawer + [mode]="'push'" + [disableClose]="true" + class="box-shadow-none border-0 pe-all col-10 col-sm-10 col-md-5 col-lg-4 col-xl-3 col-xxl-2"> <annotation-list></annotation-list> </mat-drawer> + + <mat-drawer-content class="visible position-relative pe-none"> - <!-- pullable tab top right corner --> - <div class="d-flex flex-column flex-nowrap w-100"> - <!-- top left --> - <div class="flex-grow-1 d-flex flex-nowrap mb-2"> - <div (click)="drawer.toggle()" class="mt-3"> - <ng-container *ngTemplateOutlet="tabTmpl_defaultTmpl; - context: {matColor: 'primary',fontIcon: 'fa-list',tooltip: 'Annotation list'}"> + <iav-layout-fourcorners> + + <!-- pullable tab top right corner --> + <div iavLayoutFourCornersTopLeft class="mt-5"> + <div> + <ng-container *ngTemplateOutlet="tabTmpl_defaultTmpl; context: { + matColor: 'primary', + fontIcon: 'fa-list', + tooltip: 'Annotation list', + click: annotationDrawer.toggle.bind(annotationDrawer) + }"> </ng-container> </div> + + <annotating-tools-panel class="z-index-10"> + </annotating-tools-panel> </div> - <annotating-mode #annotationMode> - </annotating-mode> - </div> + </iav-layout-fourcorners> + </mat-drawer-content> </mat-drawer-container> - <annotation-message></annotation-message> + <!-- <annotation-message></annotation-message> --> </div> + <!-- top drawer --> <mat-drawer-container [hidden]="hideUi$ | async" @@ -632,27 +642,28 @@ </ng-template> <ng-template #tabTmpl_defaultTmpl - let-matColor="matColor" - let-fontIcon="fontIcon" - let-customColor="customColor" - let-customColorDarkmode="customColorDarkmode" - let-tooltip="tooltip"> + let-matColor="matColor" + let-fontIcon="fontIcon" + let-customColor="customColor" + let-customColorDarkmode="customColorDarkmode" + let-tooltip="tooltip" + let-click="click"> <!-- (click)="sideNavMasterSwitch.toggle()" --> <button mat-raised-button - [attr.aria-label]="ARIA_LABELS.TOGGLE_SIDE_PANEL" - [matTooltip]="tooltip" - class="pe-all tab-toggle" - [ngClass]="{ - 'darktheme': customColorDarkmode === true, - 'lighttheme': customColorDarkmode === false - }" - [style.backgroundColor]="customColor" - - [color]="(!customColor && matColor) ? matColor : null"> - - <span [ngClass]="{'iv-custom-comp text': !!customColor}"> - <i class="fas" [ngClass]="fontIcon || 'fa-question'"></i> - </span> + [attr.aria-label]="ARIA_LABELS.TOGGLE_SIDE_PANEL" + [matTooltip]="tooltip" + class="pe-all tab-toggle" + [ngClass]="{ + 'darktheme': customColorDarkmode === true, + 'lighttheme': customColorDarkmode === false + }" + (click)="click && click()" + [style.backgroundColor]="customColor" + [color]="(!customColor && matColor) ? matColor : null"> + + <span [ngClass]="{'iv-custom-comp text': !!customColor}"> + <i class="fas" [ngClass]="fontIcon || 'fa-question'"></i> + </span> </button> </ng-template>