Skip to content
Snippets Groups Projects
Commit 34053909 authored by Xiao Gui's avatar Xiao Gui
Browse files

feat: added no. ann badge

feat: delete all ann btn
feat: loading ann feedback
parent 2033d7aa
No related branches found
No related tags found
No related merge requests found
...@@ -70,7 +70,8 @@ ...@@ -70,7 +70,8 @@
USER_ANNOTATION_HIDE: 'user annotations hide', USER_ANNOTATION_HIDE: 'user annotations hide',
USER_ANNOTATION_DELETE: 'Delete annotation', 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' EXIT_ANNOTATION_MODE: 'Exit annotation mode',
BULK_DELETE_ANNOTATIONS: 'Delete all user annotations'
} }
exports.IDS = { exports.IDS = {
...@@ -79,6 +80,8 @@ ...@@ -79,6 +80,8 @@
} }
exports.CONST = { exports.CONST = {
LOADING_TXT: `Loading ...`,
CANNOT_DECIPHER_HEMISPHERE: 'Cannot decipher region hemisphere.', CANNOT_DECIPHER_HEMISPHERE: 'Cannot decipher region hemisphere.',
DOES_NOT_SUPPORT_MULTI_REGION_SELECTION: `Please only select a single region.`, DOES_NOT_SUPPORT_MULTI_REGION_SELECTION: `Please only select a single region.`,
MULTI_REGION_SELECTION: `Multi region selection`, MULTI_REGION_SELECTION: `Multi region selection`,
...@@ -103,6 +106,9 @@ ...@@ -103,6 +106,9 @@
QUICKTOUR_OK: `Start`, QUICKTOUR_OK: `Start`,
QUICKTOUR_NEXTTIME: `Not now`, QUICKTOUR_NEXTTIME: `Not now`,
QUICKTOUR_CANCEL: `Dismiss`, QUICKTOUR_CANCEL: `Dismiss`,
DELETE_ALL_ANNOTATION_CONFIRMATION_MSG: `Are you sure you want to delete all annotations?`,
LOADING_ANNOTATION_MSG: `Loading annotations... Please wait...`
} }
exports.QUICKTOUR_DESC ={ exports.QUICKTOUR_DESC ={
......
# v2.4.8 # v2.4.8
## Enhancements
- Added badges to annotation tab button to show number of annotations added (#1007)
- Added some feedbacks when annotations are being loaded
- Added delete all annotation button
## Bugfixes ## Bugfixes
## Under the hood stuff ## Under the hood stuff
......
import {Component, ViewChild} from "@angular/core"; import { Component, Optional, ViewChild } from "@angular/core";
import {ARIA_LABELS} from "common/constants"; import { ARIA_LABELS, CONST } from "common/constants";
import { ModularUserAnnotationToolService } from "../tools/service"; import { ModularUserAnnotationToolService } from "../tools/service";
import { IAnnotationGeometry, TExportFormats } from "../tools/type"; import { IAnnotationGeometry, TExportFormats } from "../tools/type";
import { ComponentStore } from "src/viewerModule/componentStore"; import { ComponentStore } from "src/viewerModule/componentStore";
import { map, shareReplay, startWith } from "rxjs/operators"; import { map, shareReplay, startWith } from "rxjs/operators";
import { Observable } from "rxjs"; import { Observable, Subscription } from "rxjs";
import { TZipFileConfig } from "src/zipFilesOutput/type"; import { TZipFileConfig } from "src/zipFilesOutput/type";
import { TFileInputEvent } from "src/getFileInput/type"; import { TFileInputEvent } from "src/getFileInput/type";
import { FileInputDirective } from "src/getFileInput/getFileInput.directive"; import { FileInputDirective } from "src/getFileInput/getFileInput.directive";
import { MatSnackBar } from "@angular/material/snack-bar"; import { MatSnackBar } from "@angular/material/snack-bar";
import { unzip } from "src/zipFilesOutput/zipFilesOutput.directive"; import { unzip } from "src/zipFilesOutput/zipFilesOutput.directive";
import { DialogService } from "src/services/dialogService.service";
const README = `{id}.sands.json file contains the data of annotations. {id}.desc.json contains the metadata of annotations.` const README = `{id}.sands.json file contains the data of annotations. {id}.desc.json contains the metadata of annotations.`
...@@ -26,12 +27,17 @@ export class AnnotationList { ...@@ -26,12 +27,17 @@ export class AnnotationList {
public ARIA_LABELS = ARIA_LABELS public ARIA_LABELS = ARIA_LABELS
@ViewChild(FileInputDirective) @ViewChild(FileInputDirective)
fileInput: FileInputDirective fileInput: FileInputDirective
private subs: Subscription[] = []
private managedAnnotations: IAnnotationGeometry[] = []
public managedAnnotations$ = this.annotSvc.spaceFilteredManagedAnnotations$ public managedAnnotations$ = this.annotSvc.spaceFilteredManagedAnnotations$
public badge$ = this.managedAnnotations$.pipe(
map(mann => mann.length > 0 ? mann.length : null)
)
public manAnnExists$ = this.managedAnnotations$.pipe( public manAnnExists$ = this.managedAnnotations$.pipe(
map(arr => !!arr && arr.length > 0), map(arr => !!arr && arr.length > 0),
startWith(false) startWith(false)
...@@ -64,10 +70,15 @@ export class AnnotationList { ...@@ -64,10 +70,15 @@ export class AnnotationList {
private annotSvc: ModularUserAnnotationToolService, private annotSvc: ModularUserAnnotationToolService,
private snackbar: MatSnackBar, private snackbar: MatSnackBar,
cStore: ComponentStore<{ useFormat: TExportFormats }>, cStore: ComponentStore<{ useFormat: TExportFormats }>,
@Optional() private dialogSvc: DialogService,
) { ) {
cStore.setState({ cStore.setState({
useFormat: 'sands' useFormat: 'sands'
}) })
this.subs.push(
this.managedAnnotations$.subscribe(anns => this.managedAnnotations = anns)
)
} }
public hiddenAnnotations$ = this.annotSvc.hiddenAnnotations$ public hiddenAnnotations$ = this.annotSvc.hiddenAnnotations$
...@@ -82,6 +93,11 @@ export class AnnotationList { ...@@ -82,6 +93,11 @@ export class AnnotationList {
} }
async handleImportEvent(ev: TFileInputEvent<'text' | 'file'>){ async handleImportEvent(ev: TFileInputEvent<'text' | 'file'>){
const { abort } = this.dialogSvc.blockUserInteraction({
title: CONST.LOADING_TXT,
markdown: CONST.LOADING_ANNOTATION_MSG,
})
try { try {
const clearFileInputAndInform = () => { const clearFileInputAndInform = () => {
if (this.fileInput) { if (this.fileInput) {
...@@ -135,6 +151,31 @@ export class AnnotationList { ...@@ -135,6 +151,31 @@ export class AnnotationList {
this.snackbar.open(`Error importing: ${e.toString()}`, 'Dismiss', { this.snackbar.open(`Error importing: ${e.toString()}`, 'Dismiss', {
duration: 3000 duration: 3000
}) })
} finally {
abort()
}
}
async deleteAllAnnotation(){
if (this.dialogSvc) {
try {
await this.dialogSvc.getUserConfirm({
markdown: CONST.DELETE_ALL_ANNOTATION_CONFIRMATION_MSG
})
for (const ann of this.managedAnnotations) {
ann.remove()
}
} catch (e) {
// aborted
}
} else {
if (window.confirm(CONST.DELETE_ALL_ANNOTATION_CONFIRMATION_MSG)) {
for (const ann of this.managedAnnotations) {
ann.remove()
}
}
} }
} }
} }
...@@ -33,6 +33,15 @@ ...@@ -33,6 +33,15 @@
[disabled]="!(manAnnExists$ | async)"> [disabled]="!(manAnnExists$ | async)">
<i class="fas fa-download"></i> <i class="fas fa-download"></i>
</button> </button>
<!-- delete all annotations -->
<button mat-icon-button
color="warn"
(click)="deleteAllAnnotation()"
[matTooltip]="ARIA_LABELS.BULK_DELETE_ANNOTATIONS"
[disabled]="!(manAnnExists$ | async)">
<i class="fas fa-trash"></i>
</button>
</mat-card-subtitle> </mat-card-subtitle>
</div> </div>
......
...@@ -25,12 +25,15 @@ export class ConfirmDialogComponent { ...@@ -25,12 +25,15 @@ export class ConfirmDialogComponent {
@Input() @Input()
public markdown: string public markdown: string
public hideActionBar = false
constructor(@Inject(MAT_DIALOG_DATA) data: any) { constructor(@Inject(MAT_DIALOG_DATA) data: any) {
const { title = null, message = null, markdown, okBtnText, cancelBtnText} = data || {} const { title = null, message = null, markdown, okBtnText, cancelBtnText, hideActionBar} = data || {}
if (title) this.title = title if (title) this.title = title
if (message) this.message = message if (message) this.message = message
if (markdown) this.markdown = markdown if (markdown) this.markdown = markdown
if (okBtnText) this.okBtnText = okBtnText if (okBtnText) this.okBtnText = okBtnText
if (cancelBtnText) this.cancelBtnText = cancelBtnText if (cancelBtnText) this.cancelBtnText = cancelBtnText
if (hideActionBar) this.hideActionBar = hideActionBar
} }
} }
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
<mat-divider></mat-divider> <mat-divider></mat-divider>
<mat-dialog-actions class="justify-content-start flex-row-reverse"> <mat-dialog-actions *ngIf="!hideActionBar" class="justify-content-start flex-row-reverse">
<button [mat-dialog-close]="true" mat-raised-button color="primary">{{ okBtnText }}</button> <button [mat-dialog-close]="true" mat-raised-button color="primary">{{ okBtnText }}</button>
<button [mat-dialog-close]="false" mat-button>{{ cancelBtnText }}</button> <button [mat-dialog-close]="false" mat-button>{{ cancelBtnText }}</button>
</mat-dialog-actions> </mat-dialog-actions>
...@@ -3,6 +3,10 @@ import { ConfirmDialogComponent } from "src/components/confirmDialog/confirmDial ...@@ -3,6 +3,10 @@ import { ConfirmDialogComponent } from "src/components/confirmDialog/confirmDial
import { DialogComponent } from "src/components/dialog/dialog.component"; import { DialogComponent } from "src/components/dialog/dialog.component";
import {MatDialog, MatDialogRef} from "@angular/material/dialog"; import {MatDialog, MatDialogRef} from "@angular/material/dialog";
type TCancellable = {
abort: () => void
}
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
}) })
...@@ -16,13 +20,26 @@ export class DialogService { ...@@ -16,13 +20,26 @@ export class DialogService {
} }
public blockUserInteraction(config: Partial<DialogConfig>): TCancellable {
const dialogRef = this.dialog.open(ConfirmDialogComponent, {
data: {
...config,
hideActionBar: true
},
hasBackdrop: true,
disableClose: true
})
const abort = () => dialogRef.close()
return { abort }
}
public getUserConfirm(config: Partial<DialogConfig> = {}): Promise<string> { public getUserConfirm(config: Partial<DialogConfig> = {}): Promise<string> {
this.confirmDialogRef = this.dialog.open(ConfirmDialogComponent, { this.confirmDialogRef = this.dialog.open(ConfirmDialogComponent, {
data: config, data: config,
}) })
return new Promise((resolve, reject) => this.confirmDialogRef.afterClosed() return new Promise((resolve, reject) => this.confirmDialogRef.afterClosed()
.subscribe(val => { .subscribe(val => {
if (val) { resolve() } else { reject('User cancelled') } if (val) { resolve('') } else { reject('User cancelled') }
}, },
reject, reject,
() => this.confirmDialogRef = null)) () => this.confirmDialogRef = null))
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
[autoFocus]="false" [autoFocus]="false"
[disableClose]="true" [disableClose]="true"
class="p-0 pe-all col-10 col-sm-10 col-md-5 col-lg-4 col-xl-3 col-xxl-2"> class="p-0 pe-all col-10 col-sm-10 col-md-5 col-lg-4 col-xl-3 col-xxl-2">
<annotation-list></annotation-list> <annotation-list #annListCmp="annotationListCmp"></annotation-list>
</mat-drawer> </mat-drawer>
<mat-drawer-content class="visible position-relative pe-none"> <mat-drawer-content class="visible position-relative pe-none">
...@@ -30,7 +30,8 @@ ...@@ -30,7 +30,8 @@
matColor: 'primary', matColor: 'primary',
fontIcon: 'fa-list', fontIcon: 'fa-list',
tooltip: 'Annotation list', tooltip: 'Annotation list',
click: annotationDrawer.toggle.bind(annotationDrawer) click: annotationDrawer.toggle.bind(annotationDrawer),
badge: annListCmp?.badge$ | async
}"> }">
</ng-container> </ng-container>
</div> </div>
...@@ -58,7 +59,6 @@ ...@@ -58,7 +59,6 @@
</mat-drawer-content> </mat-drawer-content>
</mat-drawer-container> </mat-drawer-container>
<!-- <annotation-message></annotation-message> -->
</div> </div>
<!-- top drawer --> <!-- top drawer -->
...@@ -390,6 +390,8 @@ ...@@ -390,6 +390,8 @@
let-customColor="customColor" let-customColor="customColor"
let-customColorDarkmode="customColorDarkmode" let-customColorDarkmode="customColorDarkmode"
let-tooltip="tooltip" let-tooltip="tooltip"
let-badge="badge"
let-badgeColor="badgeColor"
let-click="click"> let-click="click">
<!-- (click)="sideNavMasterSwitch.toggle()" --> <!-- (click)="sideNavMasterSwitch.toggle()" -->
<button mat-raised-button <button mat-raised-button
...@@ -402,7 +404,9 @@ ...@@ -402,7 +404,9 @@
}" }"
(click)="click && click()" (click)="click && click()"
[style.backgroundColor]="customColor" [style.backgroundColor]="customColor"
[color]="(!customColor && matColor) ? matColor : null"> [color]="(!customColor && matColor) ? matColor : null"
[matBadge]="badge"
[matBadgeColor]="badgeColor || 'warn'">
<span [ngClass]="{'iv-custom-comp text': !!customColor}"> <span [ngClass]="{'iv-custom-comp text': !!customColor}">
<i class="fas" [ngClass]="fontIcon || 'fa-question'"></i> <i class="fas" [ngClass]="fontIcon || 'fa-question'"></i>
......
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment