From 340539092390a78961c114064dd49cb8fbb40566 Mon Sep 17 00:00:00 2001
From: Xiao Gui <xgui3783@gmail.com>
Date: Mon, 4 Oct 2021 15:50:50 +0200
Subject: [PATCH] feat: added no. ann badge feat: delete all ann btn feat:
 loading ann feedback

---
 common/constants.js                           |  8 ++-
 docs/releases/v2.4.8.md                       |  6 +++
 .../annotationList.component.ts               | 49 +++++++++++++++++--
 .../annotationList.template.html              |  9 ++++
 .../confirmDialog/confirmDialog.component.ts  |  5 +-
 .../confirmDialog/confirmDialog.template.html |  2 +-
 src/services/dialogService.service.ts         | 19 ++++++-
 .../viewerCmp/viewerCmp.template.html         | 12 +++--
 8 files changed, 98 insertions(+), 12 deletions(-)

diff --git a/common/constants.js b/common/constants.js
index 64e7626ba..c882aaec1 100644
--- a/common/constants.js
+++ b/common/constants.js
@@ -70,7 +70,8 @@
     USER_ANNOTATION_HIDE: 'user annotations hide',
     USER_ANNOTATION_DELETE: 'Delete annotation',
     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 = {
@@ -79,6 +80,8 @@
   }
 
   exports.CONST = {
+    LOADING_TXT: `Loading ...`,
+
     CANNOT_DECIPHER_HEMISPHERE: 'Cannot decipher region hemisphere.',
     DOES_NOT_SUPPORT_MULTI_REGION_SELECTION: `Please only select a single region.`,
     MULTI_REGION_SELECTION: `Multi region selection`,
@@ -103,6 +106,9 @@
     QUICKTOUR_OK: `Start`,
     QUICKTOUR_NEXTTIME: `Not now`,
     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 ={
diff --git a/docs/releases/v2.4.8.md b/docs/releases/v2.4.8.md
index a448d53a7..b55bca636 100644
--- a/docs/releases/v2.4.8.md
+++ b/docs/releases/v2.4.8.md
@@ -1,5 +1,11 @@
 # 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
 
 ## Under the hood stuff
diff --git a/src/atlasComponents/userAnnotations/annotationList/annotationList.component.ts b/src/atlasComponents/userAnnotations/annotationList/annotationList.component.ts
index f3b0e82f7..91f2fd090 100644
--- a/src/atlasComponents/userAnnotations/annotationList/annotationList.component.ts
+++ b/src/atlasComponents/userAnnotations/annotationList/annotationList.component.ts
@@ -1,15 +1,16 @@
-import {Component, ViewChild} from "@angular/core";
-import {ARIA_LABELS} from "common/constants";
+import { Component, Optional, ViewChild } from "@angular/core";
+import { ARIA_LABELS, CONST } from "common/constants";
 import { ModularUserAnnotationToolService } from "../tools/service";
 import { IAnnotationGeometry, TExportFormats } from "../tools/type";
 import { ComponentStore } from "src/viewerModule/componentStore";
 import { map, shareReplay, startWith } from "rxjs/operators";
-import { Observable } from "rxjs";
+import { Observable, Subscription } from "rxjs";
 import { TZipFileConfig } from "src/zipFilesOutput/type";
 import { TFileInputEvent } from "src/getFileInput/type";
 import { FileInputDirective } from "src/getFileInput/getFileInput.directive";
 import { MatSnackBar } from "@angular/material/snack-bar";
 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.`
 
@@ -26,12 +27,17 @@ export class AnnotationList {
 
   public ARIA_LABELS = ARIA_LABELS
 
-
   @ViewChild(FileInputDirective)
   fileInput: FileInputDirective
 
+  private subs: Subscription[] = []
+  private managedAnnotations: IAnnotationGeometry[] = []
   public managedAnnotations$ = this.annotSvc.spaceFilteredManagedAnnotations$
 
+  public badge$ = this.managedAnnotations$.pipe(
+    map(mann => mann.length > 0 ? mann.length : null)
+  )
+
   public manAnnExists$ = this.managedAnnotations$.pipe(
     map(arr => !!arr && arr.length > 0),
     startWith(false)
@@ -64,10 +70,15 @@ export class AnnotationList {
     private annotSvc: ModularUserAnnotationToolService,
     private snackbar: MatSnackBar,
     cStore: ComponentStore<{ useFormat: TExportFormats }>,
+    @Optional() private dialogSvc: DialogService,
   ) {
     cStore.setState({
       useFormat: 'sands'
     })
+
+    this.subs.push(
+      this.managedAnnotations$.subscribe(anns => this.managedAnnotations = anns)
+    )
   }
 
   public hiddenAnnotations$ = this.annotSvc.hiddenAnnotations$
@@ -82,6 +93,11 @@ export class AnnotationList {
   }
 
   async handleImportEvent(ev: TFileInputEvent<'text' | 'file'>){
+
+    const { abort } = this.dialogSvc.blockUserInteraction({
+      title: CONST.LOADING_TXT,
+      markdown: CONST.LOADING_ANNOTATION_MSG,
+    })
     try {
       const clearFileInputAndInform = () => {
         if (this.fileInput) {
@@ -135,6 +151,31 @@ export class AnnotationList {
       this.snackbar.open(`Error importing: ${e.toString()}`, 'Dismiss', {
         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()
+        }
+      }
     }
   }
 }
diff --git a/src/atlasComponents/userAnnotations/annotationList/annotationList.template.html b/src/atlasComponents/userAnnotations/annotationList/annotationList.template.html
index 2bfd568c0..00bdaed21 100644
--- a/src/atlasComponents/userAnnotations/annotationList/annotationList.template.html
+++ b/src/atlasComponents/userAnnotations/annotationList/annotationList.template.html
@@ -33,6 +33,15 @@
                 [disabled]="!(manAnnExists$ | async)">
                 <i class="fas fa-download"></i>
             </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>
     </div>
 
diff --git a/src/components/confirmDialog/confirmDialog.component.ts b/src/components/confirmDialog/confirmDialog.component.ts
index 5c3d9eb02..ec2e20c9f 100644
--- a/src/components/confirmDialog/confirmDialog.component.ts
+++ b/src/components/confirmDialog/confirmDialog.component.ts
@@ -25,12 +25,15 @@ export class ConfirmDialogComponent {
   @Input()
   public markdown: string
 
+  public hideActionBar = false
+
   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 (message) this.message = message
     if (markdown) this.markdown = markdown
     if (okBtnText) this.okBtnText = okBtnText
     if (cancelBtnText) this.cancelBtnText = cancelBtnText
+    if (hideActionBar) this.hideActionBar = hideActionBar
   }
 }
diff --git a/src/components/confirmDialog/confirmDialog.template.html b/src/components/confirmDialog/confirmDialog.template.html
index 401261f1a..f5f054946 100644
--- a/src/components/confirmDialog/confirmDialog.template.html
+++ b/src/components/confirmDialog/confirmDialog.template.html
@@ -17,7 +17,7 @@
 
 <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]="false" mat-button>{{ cancelBtnText }}</button>
 </mat-dialog-actions>
diff --git a/src/services/dialogService.service.ts b/src/services/dialogService.service.ts
index c202eda58..4426ef8c7 100644
--- a/src/services/dialogService.service.ts
+++ b/src/services/dialogService.service.ts
@@ -3,6 +3,10 @@ import { ConfirmDialogComponent } from "src/components/confirmDialog/confirmDial
 import { DialogComponent } from "src/components/dialog/dialog.component";
 import {MatDialog, MatDialogRef} from "@angular/material/dialog";
 
+type TCancellable = {
+  abort: () => void
+}
+
 @Injectable({
   providedIn: 'root',
 })
@@ -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> {
     this.confirmDialogRef = this.dialog.open(ConfirmDialogComponent, {
       data: config,
     })
     return new Promise((resolve, reject) => this.confirmDialogRef.afterClosed()
       .subscribe(val => {
-        if (val) { resolve() } else { reject('User cancelled') }
+        if (val) { resolve('') } else { reject('User cancelled') }
       },
       reject,
       () => this.confirmDialogRef = null))
diff --git a/src/viewerModule/viewerCmp/viewerCmp.template.html b/src/viewerModule/viewerCmp/viewerCmp.template.html
index 20997801b..ca92a5a01 100644
--- a/src/viewerModule/viewerCmp/viewerCmp.template.html
+++ b/src/viewerModule/viewerCmp/viewerCmp.template.html
@@ -16,7 +16,7 @@
         [autoFocus]="false"
         [disableClose]="true"
         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-content class="visible position-relative pe-none">
@@ -30,7 +30,8 @@
                 matColor: 'primary',
                 fontIcon: 'fa-list',
                 tooltip: 'Annotation list',
-                click: annotationDrawer.toggle.bind(annotationDrawer)
+                click: annotationDrawer.toggle.bind(annotationDrawer),
+                badge: annListCmp?.badge$ | async
               }">
               </ng-container>
             </div>
@@ -58,7 +59,6 @@
 
       </mat-drawer-content>
     </mat-drawer-container>
-    <!-- <annotation-message></annotation-message> -->
   </div>
 
   <!-- top drawer -->
@@ -390,6 +390,8 @@
   let-customColor="customColor"
   let-customColorDarkmode="customColorDarkmode"
   let-tooltip="tooltip"
+  let-badge="badge"
+  let-badgeColor="badgeColor"
   let-click="click">
   <!-- (click)="sideNavMasterSwitch.toggle()" -->
   <button mat-raised-button
@@ -402,7 +404,9 @@
     }"
     (click)="click && click()"
     [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}">
       <i class="fas" [ngClass]="fontIcon || 'fa-question'"></i>
-- 
GitLab