diff --git a/src/atlasComponents/userAnnotations/annotationList/annotationList.component.ts b/src/atlasComponents/userAnnotations/annotationList/annotationList.component.ts index 3184287efde360cce1add6008bbb7d9a5ad3811c..cf5132c2192da11cecb1ffaa1be11c2a40302fb5 100644 --- a/src/atlasComponents/userAnnotations/annotationList/annotationList.component.ts +++ b/src/atlasComponents/userAnnotations/annotationList/annotationList.component.ts @@ -1,5 +1,7 @@ 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"; @Component({ selector: 'annotation-list', @@ -8,16 +10,57 @@ import {AnnotationService} from "src/atlasComponents/userAnnotations/annotationS }) export class AnnotationList { - public annotationFilter: 'all' | 'current' = 'all' public editing = -1 get annotationsToShow() { return this.ans.annotations - .filter(a => (a.type !== 'polygon' || +a.id.split('_')[1] === 0) - && (this.annotationFilter === 'all' || a.templateName === this.ans.selectedTemplate)) + // .filter(a => this.annotationFilter === 'all' || a.templateName === this.ans.selectedTemplate) + // .filter(a => (a.type !== 'polygon' || +a.id.split('_')[1] === 0) + + // let transformed = [...this.ans.annotations] + // + // for (let i = 0; i<this.ans.annotations.length; i++) { + // if (this.ans.annotations[i].type === 'polygon') { + // const annotationId = this.ans.annotations[i].id.split('_') + // if (!transformed.find(t => t.id === annotationId[0])) { + // const polygonAnnotations = this.ans.annotations.filter(a => a.id.split('_')[0] === annotationId[0] + // && a.id.split('_')[1]) + // + // const polygonPositions = polygonAnnotations.map((a, i) => { + // return i-1 !== polygonAnnotations.length? { + // position: a.position1, + // lines: [ + // {id: a.id, point: 2}, + // {id: polygonAnnotations[i+1], point: 1} + // ] + // } : polygonAnnotations[i].position2 !== polygonAnnotations[0].position1? { + // position: a.position2, + // lines: [ + // {id: a.id, point: 2} + // ] + // } : null + // }) + // + // transformed = transformed.filter(a => a.id.split('_')[0] !== annotationId[0]) + // + // transformed.push({ + // id: annotationId[0], + // name: this.ans.annotations[i].name, + // type: 'polygon', + // annotations: polygonAnnotations, + // positions: polygonPositions, + // annotationVisible: this.ans.annotations[i].annotationVisible, + // templateName: this.ans.annotations[i].templateName + // }) + // } + // } + // } + // return transformed } - constructor(public ans: AnnotationService) {} + public identifyer = (index: number, item: any) => item.id + + constructor(private store$: Store<any>, public ans: AnnotationService) {} toggleAnnotationVisibility(annotation) { if (annotation.type === 'polygon') { @@ -50,4 +93,26 @@ export class AnnotationList { } } + navigate(position) { + //ToDo change for real position for all templates + position = position.split(',').map(p => +p * 1e6) + this.store$.dispatch( + viewerStateChangeNavigation({ + navigation: { + position, + positionReal: true + }, + }) + ) + } + + saveAnnotation(annotation) { + if (annotation.position1.split(',').length !== 3 || !annotation.position1.split(',').every(e => !!e) + || ((annotation.position2 + && annotation.position2.split(',').length !== 3) || !annotation.position1.split(',').every(e => !!e))) { + return + } + this.ans.saveAnnotation(annotation) + } + } diff --git a/src/atlasComponents/userAnnotations/annotationList/annotationList.style.css b/src/atlasComponents/userAnnotations/annotationList/annotationList.style.css index b405d8a9e5df75258355b2db083ec1f134b350d3..5d6cede953a44346bbb1a3c2aa13c1db1e6cdafc 100644 --- a/src/atlasComponents/userAnnotations/annotationList/annotationList.style.css +++ b/src/atlasComponents/userAnnotations/annotationList/annotationList.style.css @@ -1,5 +1,5 @@ .annotation-content { - max-width: 500px; + max-width: 300px; min-width: 300px; } @@ -10,3 +10,13 @@ .selecting-height { max-height: 100px; } + +input, textarea { + background: none; + border: none; +} + +input:focus, textarea:focus { + border-bottom: 2px solid; +} + diff --git a/src/atlasComponents/userAnnotations/annotationList/annotationList.template.html b/src/atlasComponents/userAnnotations/annotationList/annotationList.template.html index 39677b3ed260f965f6249ebd91e811d2f48fb4e1..b7a72d61f951e1cc81d7ee835c333c54fe333b19 100644 --- a/src/atlasComponents/userAnnotations/annotationList/annotationList.template.html +++ b/src/atlasComponents/userAnnotations/annotationList/annotationList.template.html @@ -17,12 +17,12 @@ </button> </div> <div class="d-flex flex-column"> - <small [ngClass]="[annotationFilter !== 'all'? 'inactive-filter' : '']" - class="cursor-pointer" (click)="annotationFilter = 'all'"> + <small [ngClass]="[ans.annotationFilter !== 'all'? 'inactive-filter' : '']" + class="cursor-pointer" (click)="ans.changeAnnotationFilter('all')"> All landmarks </small> - <small [ngClass]="[annotationFilter !== 'current'? 'inactive-filter' : '']" - class="cursor-pointer" (click)="annotationFilter = 'current'"> + <small [ngClass]="[ans.annotationFilter !== 'current'? 'inactive-filter' : '']" + class="cursor-pointer" (click)="ans.changeAnnotationFilter('current')"> Current template </small> </div> @@ -30,23 +30,29 @@ </div> <mat-divider class="mt-2 mb-2"></mat-divider> - <mat-accordion aria-label="user annotations list" class="h-100 d-flex flex-column overflow-auto"> <!-- <mat-expansion-panel hideToggle *ngFor="let annotation of ans.annotations | groupAnnotationPolygons; let i = index;">--> - <mat-expansion-panel hideToggle *ngFor="let annotation of annotationsToShow; let i = index;"> + <mat-expansion-panel #expansion hideToggle *ngFor="let annotation of ans.displayAnnotations; let i = index; trackBy: identifyer"> <mat-expansion-panel-header> <mat-panel-title> - <div class="d-flex flex-column align-items-center m-0"> - <small class="font-italic">{{annotation.position1}}</small> - <small class="font-italic" *ngIf="annotation.position2"> {{annotation.position2}}</small> - </div> + <small class="cursor-pointer mr-3 ml-1 d-flex align-items-center" + aria-label="Hide annotation" + [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> + <input class="font-italic outline-none color-inherit" + (click)="expansion.expanded? $event.stopPropagation() : null" + [(ngModel)]="annotation.name" + (keyup)="this.saveAnnotation(annotation)"/> +<!-- <span class="mr-2">{{annotation.name}}</span>--> </mat-panel-title> <mat-panel-description class="w-100 d-flex align-items-center justify-content-end" [matTooltip]="annotation.type"> - <span class="mr-2">{{annotation.name}}</span> <small><i [ngClass]="annotation.type === 'line'? 'fas fa-slash' : annotation.type === 'bounding box'? 'far fa-square' : annotation.type === 'ellipsoid'? 'fas fa-bullseye' @@ -60,28 +66,51 @@ <div> - <div *ngIf="annotation.description">{{annotation.description}}</div> + <small class="mt-2 mb-2">{{annotation.templateName}}</small> - <small>{{annotation.templateName}}</small> + <div *ngIf="annotation.type !== 'polygon'" class="w-100 d-flex align-items-center justify-content-between mt-2"> + <input class="w-100 font-italic outline-none color-inherit" + [(ngModel)]="annotation.position1" + (keyup)="this.saveAnnotation(annotation)"/> + <small class="d-flex align-items-center">mm <i class="fas fa-map-marked-alt mr-2 ml-2 cursor-pointer" (click)="navigate(annotation.position1)"></i></small> + </div> - <div class="d-flex align-items-center justify-content-center w-100"> - <button class="mr-1 ml-1" mat-icon-button - aria-label="Hide annotation" - [matTooltip]="annotation.annotationVisible? 'Hide' : 'Show'" - (click)="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> - </button> + <div *ngIf="annotation.type !== 'polygon' && annotation.position2" class="w-100 d-flex align-items-center justify-content-between mb-2"> + <input class="w-100 font-italic d-flex align-items-center outline-none color-inherit" + [(ngModel)]="annotation.position2" + (keyup)="this.saveAnnotation(annotation)"/> + <small class="d-flex align-items-center">mm <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" class="w-100 d-flex align-items-center justify-content-between mb-2"> + <input class="w-100 font-italic d-flex align-items-center outline-none color-inherit" + [value]="position?.position" + (keyup)="this.saveAnnotation(annotation)"/> + <small class="d-flex align-items-center">mm <i class="fas fa-map-marked-alt mr-2 ml-2 cursor-pointer" (click)="navigate(annotation?.position?.position)"></i></small> + </div> + </div> + + + <div class="w-100"> + <textarea class="w-100 outline-none color-inherit" + placeholder="Add description" + cdkTextareaAutosize + #autosize="cdkTextareaAutosize" + cdkAutosizeMinRows="1" + cdkAutosizeMaxRows="5" + [(ngModel)]="annotation.description" + (keyup)="this.saveAnnotation(annotation)"></textarea> + </div> + + + + <div class="d-flex align-items-center justify-content-end w-100"> <button class="mr-1 ml-1" mat-icon-button disabled aria-label="Export single annotation"> <i class="fas fa-file-export"></i> </button> - <button class="mr-1 ml-1" mat-icon-button - (click)="editing = i" - aria-label="Edit annotation"> - <i class="fas fa-edit"></i> - </button> <button class="mr-1 ml-1" mat-icon-button aria-label="Delete annotation" (click)="removeAnnotation(annotation)"> diff --git a/src/atlasComponents/userAnnotations/annotationMode/annotationMode.component.ts b/src/atlasComponents/userAnnotations/annotationMode/annotationMode.component.ts index c2368a8d4ab14fdd84552f6052a1d652cfa740bc..6605fb2c18ab27d93f78fa8ec3e877a6b5a7eeb4 100644 --- a/src/atlasComponents/userAnnotations/annotationMode/annotationMode.component.ts +++ b/src/atlasComponents/userAnnotations/annotationMode/annotationMode.component.ts @@ -1,11 +1,15 @@ import {Component, HostListener, Inject, OnDestroy, OnInit, Optional} from "@angular/core"; import {select, Store} from "@ngrx/store"; -import {CONST} from "common/constants"; +import {ARIA_LABELS, CONST} 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 {viewerStateNavigationStateSelector, viewerStateSelectedTemplateSelector} from "src/services/state/viewerState/selectors"; +import { + viewerStateNavigationStateSelector, + viewerStateSelectedTemplateSelector, + viewerStateViewerModeSelector +} from "src/services/state/viewerState/selectors"; import {AnnotationService} from "src/atlasComponents/userAnnotations/annotationService.service"; @Component({ @@ -30,9 +34,6 @@ export class AnnotationMode implements OnInit, OnDestroy { private onDestroyCb: Function[] = [] public subscriptions: Subscription[] = [] - //ToDo remove - public dark = true - private get viewer(){ return this.injectedViewer || (window as any).viewer } @@ -100,19 +101,27 @@ export class AnnotationMode implements OnInit, OnDestroy { ), ), ).subscribe(event => { - setTimeout(() => { + 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 - let hoveringType - let hoveringPosition1 - let hoveringPosition2 - let draggingStartPosition - let hoveringPolygonAnnotations + let hovering: any + let hoveringType: string + let hoveringName: string + let hoveringPosition1: [] + let hoveringPosition2: [] + let draggingStartPosition: [] + let hoveringPolygonAnnotations: any[] + let dragging = false this.subscriptions.push( mouseDown$.pipe( tap(() => { @@ -128,18 +137,26 @@ export class AnnotationMode implements OnInit, OnDestroy { hoveringPolygonAnnotations = this.ans.annotations.filter(a => a.id.split('_')[0] === hovering.id.split('_')[0]) } hoveringType = this.ans.annotations.find(a => a.id === hovering.id)?.type + hoveringName = this.ans.annotations.find(a => a.id === hovering.id)?.name } } }), switchMapTo( mouseMove$.pipe( - takeUntil(mouseUp$), + 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, false) + this.interactiveViewer.viewerHandle.setNavigationLoc(this.navState) // make changes to annotations by type // - when line is hovered move full annotation - // - when line point is hovered move only point @@ -147,35 +164,40 @@ export class AnnotationMode implements OnInit, OnDestroy { const dragRange = this.mousePos.map((mp, i) => mp - +draggingStartPosition[i]) if (hoveringType === 'point') { - this.ans.saveAnnotation({id: hovering.id, position1: this.mousePos.join(), type: hoveringType}) + this.ans.saveAnnotation({id: hovering.id, position1: this.mousePos.join(), 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]).join(), position2: hoveringPosition2.map((hp, i) => +hp + dragRange[i]).join(), - type: hoveringType}) + name: hoveringName, + type: hoveringType}, false, true) } else if (hovering.partIndex === 1) { this.ans.saveAnnotation({id: hovering.id, position1: this.mousePos.join(), position2: hoveringPosition2.join(), - type: hoveringType}) + name: hoveringName, + type: hoveringType}, false, true) } else if (hovering.partIndex === 2) { this.ans.saveAnnotation({id: hovering.id, position1: hoveringPosition1.join(), position2: this.mousePos.join(), - type: hoveringType}) + 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]).join(), position2: hoveringPosition2.map((hp, i) => +hp + dragRange[i]).join(), - type: hoveringType}) + name: hoveringName, + type: hoveringType}, false, true) } else if (hoveringType === 'ellipsoid') { this.ans.saveAnnotation({id: hovering.id, position1: hoveringPosition1.map((hp, i) => +hp + dragRange[i]).join(), position2: hoveringPosition2.join(), - type: hoveringType}) + name: hoveringName, + type: hoveringType}, false, true) } else if (hoveringType === 'polygon') { if (hovering.partIndex === 0) { hoveringPolygonAnnotations.forEach(pa => { @@ -183,38 +205,46 @@ export class AnnotationMode implements OnInit, OnDestroy { id: pa.id, position1: pa.position1.split(',').map((hp, i) => +hp + dragRange[i]).join(), position2: pa.position2.split(',').map((hp, i) => +hp + dragRange[i]).join(), + name: hoveringName, type: pa.type - }) + }, false, true) }) - } else if (hovering.partIndex === 2) { - this.ans.saveAnnotation({id: hovering.id, - position1: hoveringPosition1.join(), - position2: this.mousePos.join(), - type: hoveringType}) - - const hoveringPolygonNum = +hovering.id.split('_')[1] - const nextPolygon = hoveringPolygonAnnotations.find(hpa => +hpa.id.split('_')[1] === hoveringPolygonNum + 1) - if (nextPolygon) { - this.ans.saveAnnotation({id: nextPolygon.id, + } else { + let samePos1: any[] + let samePos2: any[] + if (hovering.partIndex === 2) { + samePos1 = hoveringPolygonAnnotations.filter(hp => hp.id !== hovering.id && hp.position1 === hoveringPosition2.join()) + samePos2 = hoveringPolygonAnnotations.filter(hp => hp.id !== hovering.id && hp.position2 === hoveringPosition2.join()) + this.ans.saveAnnotation({id: hovering.id, + position1: hoveringPosition1.join(), + position2: this.mousePos.join(), + name: hoveringName, + type: hoveringType}, false, true) + } else if (hovering.partIndex === 1) { + samePos1 = hoveringPolygonAnnotations.filter(hp => hp.id !== hovering.id && hp.position1 === hoveringPosition1.join()) + samePos2 = hoveringPolygonAnnotations.filter(hp => hp.id !== hovering.id && hp.position2 === hoveringPosition1.join()) + this.ans.saveAnnotation({id: hovering.id, position1: this.mousePos.join(), - position2: nextPolygon.position2, - type: nextPolygon.type}) - } + position2: hoveringPosition2.join(), + name: hoveringName, + type: hoveringType}, false, true) - } else if (hovering.partIndex === 1) { - this.ans.saveAnnotation({id: hovering.id, - position1: this.mousePos.join(), - position2: hoveringPosition2.join(), - type: hoveringType}) + } + samePos1.forEach(a => { + this.ans.saveAnnotation({id: a.id, + position1: this.mousePos.join(), + position2: a.position2, + name: hoveringName, + type: a.type}, false, true) + }) - const hoveringPolygonNum = +hovering.id.split('_')[1] - const prevPolygon = hoveringPolygonAnnotations.find(hpa => +hpa.id.split('_')[1] === hoveringPolygonNum - 1) || null - if (prevPolygon) { - this.ans.saveAnnotation({id: prevPolygon.id, - position1: prevPolygon.position1, + samePos2.forEach(a => { + this.ans.saveAnnotation({id: a.id, + position1: a.position1, position2: this.mousePos.join(), - type: prevPolygon.type}) - } + name: hoveringName, + type: a.type}, false, true) + }) } } } @@ -233,7 +263,7 @@ export class AnnotationMode implements OnInit, OnDestroy { } else if (this.selecting === 'position2' && this.mousePos) { if (this.ans.annotationTypes[this.selectedType].name === 'Ellipsoid') { this.position2 = [ - this.ans.getRadii(this.position1.split(',').map(n => +n), this.mousePos), + this.ans.getRadii(this.position1.split(','), this.mousePos), ].join() } else { this.position2 = this.mousePos.join() @@ -251,7 +281,7 @@ export class AnnotationMode implements OnInit, OnDestroy { } this.ans.saveAnnotation({id: this.editingAnnotationId, position1: this.position1, position2: this.position2, - type: this.ans.annotationTypes[this.selectedType].name}) + type: this.ans.annotationTypes[this.selectedType].name}, false) } } }), @@ -264,10 +294,19 @@ export class AnnotationMode implements OnInit, OnDestroy { ).subscribe(() => { if (this.ans.annotationTypes[this.selectedType].type === 'polygon') { - this.ans.removeAnnotation(this.editingAnnotationId) - const splitEditingAnnotationId = this.editingAnnotationId.split('_') - const prevAnnotation = this.ans.annotations.find(a => a.id === `${splitEditingAnnotationId[0]}_${+splitEditingAnnotationId[1] - 1}`) - if (prevAnnotation.id) this.ans.removeAnnotation(prevAnnotation.id) + // 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.editingAnnotationId = null this.selecting = 'position1' } @@ -276,20 +315,25 @@ export class AnnotationMode implements OnInit, OnDestroy { this.store$.pipe( select(viewerStateNavigationStateSelector), ).subscribe(nav => { - this.navState = nav.position.map(np => np/1e6) + this.navState = nav.position }), this.store$.pipe( select(viewerStateSelectedTemplateSelector), take(1) ).subscribe(tmpl => { this.ans.selectedTemplate = tmpl.name - this.dark = tmpl.useTheme === 'dark' + this.ans.darkTheme = tmpl.useTheme === 'dark' // Set get annotations from the local storage and add them to the viewer if (window.localStorage.getItem(CONST.USER_ANNOTATION_STORE_KEY) && window.localStorage.getItem(CONST.USER_ANNOTATION_STORE_KEY).length) { const annotationsString = window.localStorage.getItem(CONST.USER_ANNOTATION_STORE_KEY) this.ans.annotations = JSON.parse(annotationsString) - this.ans.annotations.filter(a => a.annotationVisible && a.templateName === this.ans.selectedTemplate).forEach(a => this.ans.addAnnotationOnViewer(a)) + this.ans.displayAnnotations = this.ans.annotations.filter(a => a.type !== 'polygon') + this.ans.addPolygonsToDisplayAnnotations(this.ans.annotations.filter(a => a.type === 'polygon')) + this.ans.annotations.filter(a => a.annotationVisible && a.templateName === this.ans.selectedTemplate) + .forEach(a => { + this.ans.addAnnotationOnViewer(a) + }) } }) ) @@ -307,11 +351,22 @@ export class AnnotationMode implements OnInit, OnDestroy { @HostListener('document:keydown.escape', ['$event']) onKeydownHandler(event: KeyboardEvent) { 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.hoverAnnotation) { @@ -335,15 +390,19 @@ export class AnnotationMode implements OnInit, OnDestroy { } } else if (this.selecting === 'position2' && this.position2 && this.mousePos) { - this.ans.saveAnnotation({id: this.editingAnnotationId, - position1: this.position1, - position2: this.position2, - type: this.ans.annotationTypes[this.selectedType].name}) 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, + position1: this.position1, + position2: this.position2, + type: this.ans.annotationTypes[this.selectedType].name}) this.editingAnnotationId = null this.selecting = 'position1' } diff --git a/src/atlasComponents/userAnnotations/annotationMode/annotationMode.style.css b/src/atlasComponents/userAnnotations/annotationMode/annotationMode.style.css index 2ff0c0d7115b449d37ebd3b5ad562393c1e9c6b8..02e6997f4b71864d4389ffb57e0b2b500fdecf3a 100644 --- a/src/atlasComponents/userAnnotations/annotationMode/annotationMode.style.css +++ b/src/atlasComponents/userAnnotations/annotationMode/annotationMode.style.css @@ -2,6 +2,12 @@ z-index: 100; width: 40px; } -.annotation-toolbar div { - +.annotation-toolbar-content { + margin-bottom: 20px; +} +.annotation-mode-message-panel { + height: 30px; + top: 20px; + right: 0; + background-color: rgba(66,65,65, 0.3); } diff --git a/src/atlasComponents/userAnnotations/annotationMode/annotationMode.template.html b/src/atlasComponents/userAnnotations/annotationMode/annotationMode.template.html index 022ae8cb0f8464d71dc61ed2a459b279fe9933ab..ed046f9891c62c6ce335cb22f21d8d8c4e6ea171 100644 --- a/src/atlasComponents/userAnnotations/annotationMode/annotationMode.template.html +++ b/src/atlasComponents/userAnnotations/annotationMode/annotationMode.template.html @@ -1,9 +1,8 @@ <div class="pe-all d-flex flex-column justify-content-content annotation-toolbar"> - <div class="d-flex flex-column flex-grow-0 panel-default" - [ngStyle]="{backgroundColor: dark? '#424242' : 'white', - color: dark? 'antiquewhite' : '#424242'}" - style="margin-bottom: 20px;"> + <div class="d-flex flex-column flex-grow-0 panel-default annotation-toolbar-content" + [ngStyle]="{backgroundColor: ans.darkTheme? '#424242' : 'white', + color: ans.darkTheme? 'antiquewhite' : '#424242'}"> <button *ngFor="let type of ans.annotationTypes; let i = index" mat-icon-button type="button" @@ -18,9 +17,20 @@ (click)="ans.disable()" type="button" class="mb-2 mt-2" - matTooltip="Disable annotating" + matTooltip="Exit annotation mode" color="warn"> <i class="fas fa-times"></i> </button> </div> </div> + +<div class="pe-all position-absolute d-flex align-items-center p-3 annotation-mode-message-panel"> + <span>Annotation mode</span> + <button mat-icon-button + (click)="ans.disable()" + type="button" + class="mb-2 mt-2" + matTooltip="Exit annotation mode"> + <i class="fas fa-times"></i> + </button> +</div> diff --git a/src/atlasComponents/userAnnotations/annotationService.service.ts b/src/atlasComponents/userAnnotations/annotationService.service.ts index 517f723b6873f8503e9c2db2fd5425157943f2d4..f72f2cea3c1b2933c03b090dff33e7ff17d66477 100644 --- a/src/atlasComponents/userAnnotations/annotationService.service.ts +++ b/src/atlasComponents/userAnnotations/annotationService.service.ts @@ -19,10 +19,13 @@ const USER_ANNOTATION_LAYER_SPEC = { export class AnnotationService implements OnDestroy { public annotations = [] + public displayAnnotations = [] public addedLayer: any public ellipsoidMinRadius = 0.5 + public annotationFilter: 'all' | 'current' = 'current' public selectedTemplate: string + public darkTheme = false public subscriptions: Subscription[] = [] public annotationTypes = [ @@ -30,8 +33,8 @@ export class AnnotationService implements OnDestroy { {name: 'Point', class: 'fas fa-circle', type: 'singleCoordinate'}, {name: 'Line', class: 'fas fa-slash', type: 'doubleCoordinate'}, {name: 'Polygon', class: 'fas fa-draw-polygon', type: 'polygon'}, - {name: 'Bounding box', class: 'far fa-square', type: 'doubleCoordinate'}, - {name: 'Ellipsoid', class: 'fas fa-bullseye', type: 'doubleCoordinate'}, + // {name: 'Bounding box', class: 'far fa-square', type: 'doubleCoordinate'}, + // {name: 'Ellipsoid', class: 'fas fa-bullseye', type: 'doubleCoordinate'}, {name: 'Remove', class: 'fas fa-trash', type: 'remove'}, ] @@ -74,7 +77,7 @@ export class AnnotationService implements OnDestroy { description = null, name = null, templateName = null, - } = {}) { + } = {}, store = true, backup = false) { let annotation = { id: id || getUuid(), annotationVisible: true, @@ -88,6 +91,53 @@ export class AnnotationService implements OnDestroy { const foundIndex = this.annotations.findIndex(x => x.id === annotation.id) + if (foundIndex >= 0) { + annotation = { + ...this.annotations[foundIndex], + ...annotation + } + } + + this.addAnnotationOnViewer(annotation) + + if (backup) { + const i = this.saveEditList.findIndex((e) => e.id === annotation.id) + if (i < 0) {this.saveEditList.push(annotation) + } else {this.saveEditList[i] = annotation} + } + + if (store) { + this.storeAnnotation(annotation) + } + } + public saveEditList = [] + + public storeBackup() { + if (this.saveEditList.length) { + if (this.saveEditList[0].type === 'polygon') { + this.addPolygonsToDisplayAnnotations(this.saveEditList) + } + this.saveEditList.forEach(a => this.storeAnnotation(a)) + this.saveEditList = [] + } + } + + storeAnnotation(annotation) { + // give names by type + number + if (!annotation.name) { + const pointAnnotationNumber = this.annotations + .filter(a => a.name && a.name.startsWith(annotation.type) && (+a.name.split(annotation.type)[1])) + .map(a => +a.name.split(annotation.type)[1]) + + if (pointAnnotationNumber && pointAnnotationNumber.length) { + annotation.name = `${annotation.type}${Math.max(...pointAnnotationNumber) + 1}` + } else { + annotation.name = `${annotation.type}1` + } + } + + const foundIndex = this.annotations.findIndex(x => x.id === annotation.id) + if (foundIndex >= 0) { annotation = { ...this.annotations[foundIndex], @@ -97,7 +147,17 @@ export class AnnotationService implements OnDestroy { } else { this.annotations.push(annotation) } - this.addAnnotationOnViewer(annotation) + + if(annotation.type !== 'polygon') { + const foundIndex = this.displayAnnotations.findIndex(x => x.id === annotation.id) + + if (foundIndex >= 0) { + this.displayAnnotations[foundIndex] = annotation + } else { + this.displayAnnotations.push(annotation) + } + } + this.storeToLocalStorage() } @@ -157,6 +217,64 @@ export class AnnotationService implements OnDestroy { } } + addPolygonsToDisplayAnnotations(annotations) { + let transformed = [...annotations] + + for (let i = 0; i<annotations.length; i++) { + + const annotationId = annotations[i].id.split('_') + if (!transformed.find(t => t.id === annotationId[0])) { + const polygonAnnotations = annotations.filter(a => a.id.split('_')[0] === annotationId[0] + && a.id.split('_')[1]) + + const polygonPositions = polygonAnnotations.map((a, i) => { + return i+1 !== polygonAnnotations.length? { + position: a.position2, + lines: [ + {id: a.id, point: 2}, + {id: polygonAnnotations[i+1], point: 1} + ] + } : polygonAnnotations[i].position2 !== polygonAnnotations[0].position1? { + position: a.position2, + lines: [ + {id: a.id, point: 2} + ] + } : { + position: a.position2, + lines: [ + {id: a.id, point: 2}, + {id: polygonAnnotations[0].id, point: 1} + ] + } + }) + + transformed = transformed.filter(a => a.id.split('_')[0] !== annotationId[0]) + + transformed.push({ + id: annotationId[0], + name: annotations[i].name, + type: 'polygon', + annotations: polygonAnnotations, + positions: polygonPositions, + annotationVisible: annotations[i].annotationVisible, + templateName: annotations[i].templateName + }) + } + + } + + this.displayAnnotations = [ + ...this.displayAnnotations, + ...transformed + ] + } + + changeAnnotationFilter(filter) { + this.annotationFilter = filter + this.displayAnnotations = this.displayAnnotations + .filter(a => this.annotationFilter === 'all' || a.templateName === this.selectedTemplate) + } + ngOnDestroy(){ this.subscriptions.forEach(s => s.unsubscribe()) } diff --git a/src/atlasComponents/userAnnotations/editAnnotation/editAnnotation.component.ts b/src/atlasComponents/userAnnotations/editAnnotation/editAnnotation.component.ts index 981b3ee9054d8c3e3f8d1b561fed9feb9b61ae08..e758630b360ff9b3bc3ebea8c77ea12f5ea490bb 100644 --- a/src/atlasComponents/userAnnotations/editAnnotation/editAnnotation.component.ts +++ b/src/atlasComponents/userAnnotations/editAnnotation/editAnnotation.component.ts @@ -10,6 +10,8 @@ import { FormBuilder, FormGroup, Validators } from "@angular/forms"; import { animate, style, transition, trigger } from "@angular/animations"; import { Subscription } from "rxjs"; import {AnnotationService} from "src/atlasComponents/userAnnotations/annotationService.service"; +import {viewerStateChangeNavigation} from "src/services/state/viewerState/actions"; +import {Store} from "@ngrx/store"; @Component({ selector: 'edit-annotation', @@ -37,7 +39,8 @@ export class EditAnnotationComponent implements OnInit, OnDestroy { constructor( private formBuilder: FormBuilder, - public ans: AnnotationService + public ans: AnnotationService, + private store$: Store<any>, ) { this.annotationForm = this.formBuilder.group({ id: [{value: 'null'}], @@ -69,6 +72,11 @@ export class EditAnnotationComponent implements OnInit, OnDestroy { submitForm() { if (this.annotationForm.valid) { + if (this.annotationForm.controls.position1.value.split(',').length !== 3 || + (this.annotationForm.controls.position2.value + && this.annotationForm.controls.position2.value.split(',').length !== 3)) { + return + } this.ans.saveAnnotation(this.annotationForm.value) this.cancelEditing() } @@ -94,4 +102,17 @@ export class EditAnnotationComponent implements OnInit, OnDestroy { this.subscriptions.forEach(s => s.unsubscribe()) } + navigate(position) { + //ToDo change for real position for all templates + position = position.split(',').map(p => +p * 1e6) + this.store$.dispatch( + viewerStateChangeNavigation({ + navigation: { + position, + positionReal: true + }, + }) + ) + } + } diff --git a/src/atlasComponents/userAnnotations/editAnnotation/editAnnotation.template.html b/src/atlasComponents/userAnnotations/editAnnotation/editAnnotation.template.html index a52351f571092806aab378126d34312f8c06d531..d423d541539d3ac8f1b4c0ab5a06edacab9a0cba 100644 --- a/src/atlasComponents/userAnnotations/editAnnotation/editAnnotation.template.html +++ b/src/atlasComponents/userAnnotations/editAnnotation/editAnnotation.template.html @@ -11,35 +11,37 @@ <mat-label>Name</mat-label> <input name="name" formControlName="name" + (keyup)="submitForm()" matInput> </mat-form-field> - <div class="d-flex w-100"> - <div class="d-flex flex-column align-items-center w-100"> + <div class="d-flex flex-column align-items-center position-relative w-100"> <mat-form-field class="w-100 annotation-editing-body"> - <mat-label *ngIf="annotation.type !== 'ellipsoid'">Position {{annotation.position2 && ' 1'}}</mat-label> + <mat-label *ngIf="annotation.type !== 'ellipsoid'">Position {{annotation.position2 && ' 1'}} (vox)</mat-label> <mat-label *ngIf="annotation.type === 'ellipsoid'">Center (vox)</mat-label> <div> <input type="text" name="position1" - placeholder="0,0,0" + placeholder="0,0,0mm" class="pr-4" formControlName="position1" + (keyup)="submitForm()" matInput> </div> </mat-form-field> + <small class="position-absolute" style="bottom: 20px; right: 0">mm <i class="fas fa-map-marked-alt mr-2 ml-2 cursor-pointer" (click)="navigate(annotation.position1)"></i></small> </div> - <div class="d-flex flex-column align-items-center w-100" *ngIf="annotation.position2"> + <div class="d-flex flex-column align-items-center w-100 position-relative" *ngIf="annotation.position2"> <mat-form-field class="w-100 annotation-editing-body"> <mat-label *ngIf="annotation.type !== 'ellipsoid'">Position 2 (vox)</mat-label> <mat-label *ngIf="annotation.type === 'ellipsoid'">Radii</mat-label> - <input type="text" name="position2" - class="pr-4" - placeholder="0,0,0" - formControlName="position2" matInput> + <input type="text" name="position2" class="pr-4" placeholder="0,0,0mm" + formControlName="position2" + (keyup)="submitForm()" + matInput> </mat-form-field> - </div> - </div> + <small class="position-absolute" style="bottom: 20px; right: 0">mm <i class="fas fa-map-marked-alt mr-2 ml-2 cursor-pointer" (click)="navigate(annotation.position2)"></i></small> + </div> <mat-form-field class="w-100 annotation-editing-body"> <mat-label>description</mat-label> @@ -47,6 +49,7 @@ [matAutosizeMinRows]="1" [matAutosizeMaxRows]="5" name="description" + (keyup)="submitForm()" formControlName="description" matInput></textarea> </mat-form-field> <div class="w-100 d-flex justify-content-end"> diff --git a/src/contextMenuModule/ctxMenuHost.directive.ts b/src/contextMenuModule/ctxMenuHost.directive.ts index b403b359bceece402b9be5594825f8e15a872371..3020e255df40754ebf66263f46c75d357e13836b 100644 --- a/src/contextMenuModule/ctxMenuHost.directive.ts +++ b/src/contextMenuModule/ctxMenuHost.directive.ts @@ -1,5 +1,9 @@ import { AfterViewInit, Directive, HostListener, Input, OnDestroy, TemplateRef, ViewContainerRef } from "@angular/core"; import { ContextMenuService } from "./service"; +import {select, Store} from "@ngrx/store"; +import {viewerStateViewerModeSelector} from "src/services/state/viewerState/selectors"; +import {take} from "rxjs/operators"; +import {ARIA_LABELS} from "common/constants"; @Directive({ selector: '[ctx-menu-host]' @@ -12,12 +16,16 @@ export class CtxMenuHost implements OnDestroy, AfterViewInit{ @HostListener('contextmenu', ['$event']) onClickListener(ev: MouseEvent){ + let viewerMode: string + this.store$.pipe(select(viewerStateViewerModeSelector), take(1)).subscribe(vm => viewerMode = vm) + if (viewerMode === ARIA_LABELS.VIEWER_MODE_ANNOTATING) return this.svc.showCtxMenu(ev, this.tmplRef) } constructor( private vcr: ViewContainerRef, - private svc: ContextMenuService + private svc: ContextMenuService, + private store$: Store<any>, ){ } diff --git a/src/ui/topMenu/topMenuCmp/topMenu.template.html b/src/ui/topMenu/topMenuCmp/topMenu.template.html index 773d7e82bd0f5a5bf6cb4c93b2c322a449fca834..0578434894262ce6752754e081e237186dee6ca8 100644 --- a/src/ui/topMenu/topMenuCmp/topMenu.template.html +++ b/src/ui/topMenu/topMenuCmp/topMenu.template.html @@ -158,7 +158,7 @@ <mat-icon fontSet="fas" fontIcon="fa-pencil-ruler"> </mat-icon> <span> - Annotate + Annotation mode </span> </button> <plugin-banner></plugin-banner>