From 4584faf7fed556ce5bc31222239d8fbc4ecb0105 Mon Sep 17 00:00:00 2001
From: Xiao Gui <xgui3783@gmail.com>
Date: Sun, 20 Jun 2021 22:11:29 +0200
Subject: [PATCH] feat: reintrod exit panel refactor: individual annot export
 refactor: annotation geometry & edit cls bugfix: annots in other spaces feat:
 store annts in local storage

---
 common/constants.js                           |   1 +
 .../annotationList.component.ts               |   2 +-
 .../annotationList.template.html              |   2 +-
 .../annotationMode.component.ts               |   9 ++
 .../directives/annotationSwitch.directive.ts  | 113 ++++-----------
 .../userAnnotations/tools/delete.ts           |   2 +-
 .../userAnnotations/tools/line.ts             |  38 +----
 .../tools/line/line.component.ts              |   6 +-
 .../tools/line/line.template.html             |  25 +---
 .../userAnnotations/tools/module.ts           |  11 +-
 .../userAnnotations/tools/point.ts            |  30 +---
 .../tools/point/point.component.ts            |   6 +-
 .../tools/point/point.template.html           |  26 +---
 .../userAnnotations/tools/poly.ts             |  38 +----
 .../tools/poly/poly.component.ts              |   6 +-
 .../tools/poly/poly.template.html             |  25 +---
 .../userAnnotations/tools/select.ts           |   1 -
 .../userAnnotations/tools/service.ts          | 133 ++++++++++++++----
 .../textareaCopyExport.component.ts           |  52 +++++++
 .../textareaCopyExport.style.css              |   0
 .../textareaCopyExport.template.html          |  32 +++++
 .../userAnnotations/tools/toolCmp.base.ts     |  13 --
 .../userAnnotations/tools/type.ts             |   5 -
 .../topMenu/topMenuCmp/topMenu.template.html  |   1 +
 .../viewerCmp/viewerCmp.template.html         |  19 ++-
 .../downloadSingleFile.directive.ts           |  32 +++++
 src/zipFilesOutput/module.ts                  |   7 +-
 27 files changed, 334 insertions(+), 301 deletions(-)
 create mode 100644 src/atlasComponents/userAnnotations/tools/textareaCopyExport/textareaCopyExport.component.ts
 create mode 100644 src/atlasComponents/userAnnotations/tools/textareaCopyExport/textareaCopyExport.style.css
 create mode 100644 src/atlasComponents/userAnnotations/tools/textareaCopyExport/textareaCopyExport.template.html
 create mode 100644 src/zipFilesOutput/downloadSingleFile.directive.ts

diff --git a/common/constants.js b/common/constants.js
index b37adb9a6..3a63c22ab 100644
--- a/common/constants.js
+++ b/common/constants.js
@@ -6,6 +6,7 @@
     OPEN: 'Open',
     EXPAND: 'Expand',
     COLLAPSE: 'Collapse',
+    COPY_TO_CLIPBOARD: 'Copy to clipboard',
 
     // dataset specific
     EXPLORE_DATASET_IN_KG: `Explore dataset in Knowledge Graph`,
diff --git a/src/atlasComponents/userAnnotations/annotationList/annotationList.component.ts b/src/atlasComponents/userAnnotations/annotationList/annotationList.component.ts
index 59275a07d..2ffb0a698 100644
--- a/src/atlasComponents/userAnnotations/annotationList/annotationList.component.ts
+++ b/src/atlasComponents/userAnnotations/annotationList/annotationList.component.ts
@@ -29,7 +29,7 @@ export class AnnotationList {
   @ViewChild(FileInputDirective)
   fileInput: FileInputDirective
 
-  public managedAnnotations$ = this.annotSvc.managedAnnotations$
+  public managedAnnotations$ = this.annotSvc.spaceFilteredManagedAnnotations$
 
   public manAnnExists$ = this.managedAnnotations$.pipe(
     map(arr => !!arr && arr.length > 0),
diff --git a/src/atlasComponents/userAnnotations/annotationList/annotationList.template.html b/src/atlasComponents/userAnnotations/annotationList/annotationList.template.html
index 4cd227d10..17ac890ea 100644
--- a/src/atlasComponents/userAnnotations/annotationList/annotationList.template.html
+++ b/src/atlasComponents/userAnnotations/annotationList/annotationList.template.html
@@ -31,7 +31,7 @@
                 [attr.aria-label]="ARIA_LABELS.USER_ANNOTATION_EXPORT"
                 [matTooltip]="ARIA_LABELS.USER_ANNOTATION_EXPORT"
                 [disabled]="!(manAnnExists$ | async)">
-                <i class="fas fa-file-export"></i>
+                <i class="fas fa-download"></i>
             </button>
         </mat-card-subtitle>
     </div>
diff --git a/src/atlasComponents/userAnnotations/annotationMode/annotationMode.component.ts b/src/atlasComponents/userAnnotations/annotationMode/annotationMode.component.ts
index a2314a4ef..0bacfe6b2 100644
--- a/src/atlasComponents/userAnnotations/annotationMode/annotationMode.component.ts
+++ b/src/atlasComponents/userAnnotations/annotationMode/annotationMode.component.ts
@@ -6,6 +6,7 @@ import { ARIA_LABELS } from 'common/constants'
 import { ClickInterceptor, CLICK_INTERCEPTOR_INJECTOR, CONTEXT_MENU_ITEM_INJECTOR, TContextMenu } from "src/util";
 import { TContextArg } from "src/viewerModule/viewer.interface";
 import { TContextMenuReg } from "src/contextMenuModule";
+import { MatSnackBar } from "@angular/material/snack-bar";
 
 @Component({
   selector: 'annotating-tools-panel',
@@ -29,6 +30,7 @@ export class AnnotationMode implements OnDestroy{
   constructor(
     private store$: Store<any>,
     private modularToolSvc: ModularUserAnnotationToolService,
+    snackbar: MatSnackBar,
     @Optional() @Inject(CLICK_INTERCEPTOR_INJECTOR) clickInterceptor: ClickInterceptor,
     @Optional() @Inject(CONTEXT_MENU_ITEM_INJECTOR) ctxMenuInterceptor: TContextMenu<TContextMenuReg<TContextArg<'nehuba' | 'threeSurfer'>>>
   ) {
@@ -45,6 +47,13 @@ export class AnnotationMode implements OnDestroy{
       register(stopClickProp)
       this.onDestroyCb.push(() => deregister(stopClickProp))
     }
+
+    this.modularToolSvc.loadStoredAnnotations()
+      .catch(e => {
+        snackbar.open(`Loading annotations from storage failed: ${e.toString()}`, 'Dismiss', {
+          duration: 3000
+        })
+      })
   }
 
   exitAnnotationMode(){
diff --git a/src/atlasComponents/userAnnotations/directives/annotationSwitch.directive.ts b/src/atlasComponents/userAnnotations/directives/annotationSwitch.directive.ts
index 0aeb8ac79..1109fbdbc 100644
--- a/src/atlasComponents/userAnnotations/directives/annotationSwitch.directive.ts
+++ b/src/atlasComponents/userAnnotations/directives/annotationSwitch.directive.ts
@@ -1,110 +1,53 @@
-import { Directive, HostListener, Inject, OnDestroy, Optional } from "@angular/core";
+import { Directive, HostListener, Inject, Input, Optional } from "@angular/core";
 import { viewerStateSetViewerMode } from "src/services/state/viewerState/actions";
 import { ARIA_LABELS } from "common/constants";
-import { Store } from "@ngrx/store";
+import { select, Store } from "@ngrx/store";
 import { TContextArg } from "src/viewerModule/viewer.interface";
 import { TContextMenuReg } from "src/contextMenuModule";
 import { CONTEXT_MENU_ITEM_INJECTOR, TContextMenu } from "src/util";
 import { ModularUserAnnotationToolService } from "../tools/service";
-import { IAnnotationGeometry } from "../tools/type";
-import { retry } from 'common/util'
-import { MatSnackBar } from "@angular/material/snack-bar";
+import { Subscription } from "rxjs";
+import { viewerStateViewerModeSelector } from "src/services/state/viewerState/selectors";
 
 @Directive({
   selector: '[annotation-switch]'
 })
-export class AnnotationSwitch implements OnDestroy{
-  
-  private onDestroyCb: Function[] = []
+export class AnnotationSwitch {
 
+  @Input('annotation-switch-mode')
+  mode: 'toggle' | 'off' | 'on' = 'on'
+
+  private currMode = null
+  private subs: Subscription[] = []
   constructor(
     private store$: Store<any>,
     private svc: ModularUserAnnotationToolService,
-    private snackbar: MatSnackBar,
     @Optional() @Inject(CONTEXT_MENU_ITEM_INJECTOR) ctxMenuInterceptor: TContextMenu<TContextMenuReg<TContextArg<'nehuba' | 'threeSurfer'>>>
   ) {
-    
-    const sub = this.svc.managedAnnotations$.subscribe(manAnn => this.manangedAnnotations = manAnn)
-    this.onDestroyCb.push(
-      () => sub.unsubscribe()
+    this.subs.push(
+      this.store$.pipe(
+        select(viewerStateViewerModeSelector)
+      ).subscribe(mode => {
+        this.currMode = mode
+      })
     )
-
-
-    const loadAnn = async () => {
-      try {
-        const anns = await this.getAnnotation()
-        for (const ann of anns) {
-          this.svc.importAnnotation(ann)
-        }
-      } catch (e) {
-        this.snackbar.open(`Error loading annotation from storage: ${e.toString()}`, 'Dismiss', {
-          duration: 3000
-        })
-      }
-    }
-    loadAnn()
-  }
-
-  ngOnDestroy(){
-    while(this.onDestroyCb.length > 0) this.onDestroyCb.pop()()
-  }
-
-  /**
-   * TODO move annotation storage/retrival to more logical location
-   */
-  @HostListener('window:beforeunload')
-  onPageHide(){
-    this.storeAnnotation(this.manangedAnnotations)
   }
 
   @HostListener('click')
   onClick() {
-    this.store$.dispatch(
-      viewerStateSetViewerMode({
-        payload: ARIA_LABELS.VIEWER_MODE_ANNOTATING
-      })
-    )
-  }
-
-  private manangedAnnotations = []
-  private localstoragekey = 'userAnnotationKey'
-  private storeAnnotation(anns: IAnnotationGeometry[]){
-    const arr = []
-    for (const ann of anns) {
-      const json = ann.toJSON()
-      arr.push(json)
+    let payload = null
+    if (this.mode === 'on') payload = ARIA_LABELS.VIEWER_MODE_ANNOTATING
+    if (this.mode === 'off') {
+      if (this.currMode === ARIA_LABELS.VIEWER_MODE_ANNOTATING) payload = null
+      else return
     }
-    const stringifiedJSON = JSON.stringify(arr)
-    const { pako } = (window as any).export_nehuba
-    const compressed = pako.deflate(stringifiedJSON)
-    let out = ''
-    for (const num of compressed) {
-      out += String.fromCharCode(num)
+    if (this.mode === 'toggle') {
+      payload = this.currMode === ARIA_LABELS.VIEWER_MODE_ANNOTATING
+        ? null
+        : ARIA_LABELS.VIEWER_MODE_ANNOTATING
     }
-    const encoded = btoa(out)
-    window.localStorage.setItem(this.localstoragekey, encoded)
-  }
-  private async getAnnotation(): Promise<IAnnotationGeometry[]>{
-    const encoded = window.localStorage.getItem(this.localstoragekey)
-    if (!encoded) return []
-    const bin = atob(encoded)
-    
-    await retry(() => {
-      if (!!(window as any).export_nehuba) return true
-      else throw new Error(`export nehuba not yet ready`)
-    }, {
-      timeout: 1000,
-      retries: 10
-    })
-    
-    const { pako } = (window as any).export_nehuba
-    const decoded = pako.inflate(bin, { to: 'string' })
-    const arr = JSON.parse(decoded)
-    const out: IAnnotationGeometry[] = []
-    for (const obj of arr) {
-      const geometry = this.svc.parseAnnotationObject(obj)
-      out.push(geometry)
-    }
-    return out
+    this.store$.dispatch(
+      viewerStateSetViewerMode({ payload })
+    )
   }
 }
diff --git a/src/atlasComponents/userAnnotations/tools/delete.ts b/src/atlasComponents/userAnnotations/tools/delete.ts
index ecf04a10d..63a73c578 100644
--- a/src/atlasComponents/userAnnotations/tools/delete.ts
+++ b/src/atlasComponents/userAnnotations/tools/delete.ts
@@ -23,7 +23,7 @@ export class ToolDelete extends AbsToolClass<Point> implements IAnnotationTools,
 
   managedAnnotations$ = new Subject<Point[]>()
   private allManAnnotations: IAnnotationGeometry[] = []
-  allNgAnnotations$ = new Subject<TNgAnnotationPoint[]>()
+  
   constructor(
     annotationEv$: Observable<TAnnotationEvent<keyof IAnnotationEvents>>,
     callback: TCallbackFunction
diff --git a/src/atlasComponents/userAnnotations/tools/line.ts b/src/atlasComponents/userAnnotations/tools/line.ts
index c1ac7f8a4..f48247a65 100644
--- a/src/atlasComponents/userAnnotations/tools/line.ts
+++ b/src/atlasComponents/userAnnotations/tools/line.ts
@@ -13,7 +13,7 @@ import {
 } from "./type";
 import { Point, TPointJsonSpec } from './point'
 import { OnDestroy } from "@angular/core";
-import { merge, Observable, Subject, Subscription } from "rxjs";
+import { Observable, Subject, Subscription } from "rxjs";
 import { filter, switchMapTo, takeUntil } from "rxjs/operators";
 import { getUuid } from "src/util/fn";
 
@@ -192,11 +192,8 @@ export class ToolLine extends AbsToolClass<Line> implements IAnnotationTools, On
   
   subs: Subscription[] = []
 
-  private forceRefreshAnnotations$ = new Subject()
   private managedAnnotations: Line[] = []
   public managedAnnotations$ = new Subject<Line[]>()
-  public allNgAnnotations$ = new Subject<INgAnnotationTypes[keyof INgAnnotationTypes][]>()
-
 
   onMouseMoveRenderPreview(pos: [number, number, number]) {
     if (this.selectedLine && !!this.selectedLine.points[0]) {
@@ -259,16 +256,14 @@ export class ToolLine extends AbsToolClass<Line> implements IAnnotationTools, On
       ).subscribe(mouseev => {
         const crd = mouseev.detail.ngMouseEvent
         if (!this.selectedLine) {
-          this.selectedLine = new Line({
+          const newLine = new Line({
             space: this.space,
             "@type": 'siibra-ex/annotation/line',
             points: []
           })
-          const { id } = this.selectedLine
-          this.selectedLine.remove = () => this.removeAnnotation(id)
-          this.selectedLine.addLinePoints(crd)
-          this.managedAnnotations.push(this.selectedLine)
-          this.managedAnnotations$.next(this.managedAnnotations)
+          newLine.addLinePoints(crd)
+          this.addAnnotation(newLine)
+          this.selectedLine = newLine
         } else {
 
           this.selectedLine.addLinePoints(crd)
@@ -280,24 +275,6 @@ export class ToolLine extends AbsToolClass<Line> implements IAnnotationTools, On
         }
       }),
 
-      /**
-       * conditions by which ng annotations are refreshed
-       */
-      merge(
-        toolDeselect$,
-        toolSelThenClick$,
-        this.forceRefreshAnnotations$,
-      ).pipe(
-
-      ).subscribe(() => {
-        let out: INgAnnotationTypes['line'][] = []
-        for (const managedAnn of this.managedAnnotations) {
-          out = out.concat(...managedAnn.toNgAnnotation())
-        }
-
-        this.allNgAnnotations$.next(out)
-      }),
-
       /**
        * emit on init, and reset on mouseup$
        * otherwise, pairwise confuses last drag event and first drag event
@@ -319,7 +296,7 @@ export class ToolLine extends AbsToolClass<Line> implements IAnnotationTools, On
         } else {
           annotation.translate(deltaX, deltaY, deltaZ)
         }
-        this.forceRefreshAnnotations$.next(null)
+        this.managedAnnotations$.next(this.managedAnnotations)
       })
     )
   }
@@ -331,9 +308,9 @@ export class ToolLine extends AbsToolClass<Line> implements IAnnotationTools, On
   addAnnotation(line: Line) {
     const idx = this.managedAnnotations.findIndex(ann => ann.id === line.id)
     if (idx >= 0) throw new Error(`Line annotation has already been added`)
+    line.remove = () => this.removeAnnotation(line.id)
     this.managedAnnotations.push(line)
     this.managedAnnotations$.next(this.managedAnnotations)
-    this.forceRefreshAnnotations$.next(null)
   }
 
   removeAnnotation(id: string){
@@ -343,7 +320,6 @@ export class ToolLine extends AbsToolClass<Line> implements IAnnotationTools, On
     }
     this.managedAnnotations.splice(idx, 1)
     this.managedAnnotations$.next(this.managedAnnotations)
-    this.forceRefreshAnnotations$.next(null)
   }
 
 }
diff --git a/src/atlasComponents/userAnnotations/tools/line/line.component.ts b/src/atlasComponents/userAnnotations/tools/line/line.component.ts
index 82289d20a..bf3d32401 100644
--- a/src/atlasComponents/userAnnotations/tools/line/line.component.ts
+++ b/src/atlasComponents/userAnnotations/tools/line/line.component.ts
@@ -4,7 +4,6 @@ import { Store } from "@ngrx/store";
 import { Line, LINE_ICON_CLASS } from "../line";
 import { ToolCmpBase } from "../toolCmp.base";
 import { IAnnotationGeometry, TExportFormats, UDPATE_ANNOTATION_TOKEN } from "../type";
-import { Clipboard } from "@angular/cdk/clipboard";
 import { viewerStateChangeNavigation } from "src/services/state/viewerState/actions";
 import { Point } from "../point";
 import { ARIA_LABELS } from 'common/constants'
@@ -30,12 +29,11 @@ export class LineUpdateCmp extends ToolCmpBase implements OnDestroy{
 
   constructor(
     private store: Store<any>,
-    snackbar: MatSnackBar,
-    clipboard: Clipboard,
+    private snackbar: MatSnackBar,
     cStore: ComponentStore<{ useFormat: TExportFormats }>,
     @Optional() @Inject(UDPATE_ANNOTATION_TOKEN) updateAnnotation: IAnnotationGeometry,
   ){
-    super(clipboard, snackbar, cStore)
+    super(cStore)
 
     if (updateAnnotation) {
       if (updateAnnotation instanceof Line) {
diff --git a/src/atlasComponents/userAnnotations/tools/line/line.template.html b/src/atlasComponents/userAnnotations/tools/line/line.template.html
index 7b05ff79b..e0b3b06a7 100644
--- a/src/atlasComponents/userAnnotations/tools/line/line.template.html
+++ b/src/atlasComponents/userAnnotations/tools/line/line.template.html
@@ -40,25 +40,12 @@
     iav-stop="click">
 
     <div class="iv-custom-comp text">
-      <mat-form-field>
-        <mat-label>
-          {{ useFormat }}
-        </mat-label>
-        <textarea
-          disabled="true"
-          matInput
-          #exportTarget>{{ updateAnnotation.updateSignal$ | async | toFormattedStringPipe : updateAnnotation : useFormat }}</textarea>
-
-        <button mat-icon-button
-          matSuffix
-          iav-stop="click"
-          aria-label="Copy to clipboard"
-          matTooltip="Copy to clipboard."
-          (click)="copyToClipboard(exportTarget.value)"
-          color="basic">
-          <i class="fas fa-copy"></i>
-        </button>
-      </mat-form-field>
+      <textarea-copy-export
+        [textarea-copy-export-label]="useFormat"
+        [textarea-copy-export-text]="updateAnnotation.updateSignal$ | async | toFormattedStringPipe : updateAnnotation : useFormat"
+        [textarea-copy-export-download-filename]="updateAnnotation.id + '.sands.json'"
+        [textarea-copy-export-disable]="true">
+      </textarea-copy-export>
     </div>
   </div>
 </mat-menu>
diff --git a/src/atlasComponents/userAnnotations/tools/module.ts b/src/atlasComponents/userAnnotations/tools/module.ts
index e7a3a0402..869c30740 100644
--- a/src/atlasComponents/userAnnotations/tools/module.ts
+++ b/src/atlasComponents/userAnnotations/tools/module.ts
@@ -8,25 +8,28 @@ import { PointUpdateCmp } from "./point/point.component";
 import { PolyUpdateCmp } from "./poly/poly.component";
 import { ModularUserAnnotationToolService } from "./service";
 import { ToFormattedStringPipe } from "./toFormattedString.pipe";
-import { ANNOTATION_EVENT_INJ_TOKEN, } from "./type";
+import { ANNOTATION_EVENT_INJ_TOKEN } from "./type";
 import { Line, ToolLine } from "src/atlasComponents/userAnnotations/tools/line";
-
 import { Point, ToolPoint } from "./point";
 import { ToolSelect } from "./select";
 import { ToolDelete } from "./delete";
 import { Polygon, ToolPolygon } from "./poly";
+import { ZipFilesOutputModule } from "src/zipFilesOutput/module";
+import { TextareaCopyExportCmp } from "./textareaCopyExport/textareaCopyExport.component";
 
 @NgModule({
   imports: [
     CommonModule,
     AngularMaterialModule,
     UtilModule,
+    ZipFilesOutputModule,
   ],
   declarations: [
     LineUpdateCmp,
     PolyUpdateCmp,
     PointUpdateCmp,
     ToFormattedStringPipe,
+    TextareaCopyExportCmp,
   ],
   exports: [
     LineUpdateCmp,
@@ -44,7 +47,9 @@ import { Polygon, ToolPolygon } from "./poly";
 
 export class UserAnnotationToolModule {
 
-  constructor(svc: ModularUserAnnotationToolService){
+  constructor(
+    svc: ModularUserAnnotationToolService,
+  ){
     const selTool = svc.registerTool({
       toolCls: ToolSelect
     })
diff --git a/src/atlasComponents/userAnnotations/tools/point.ts b/src/atlasComponents/userAnnotations/tools/point.ts
index 233413921..7ea3b5f55 100644
--- a/src/atlasComponents/userAnnotations/tools/point.ts
+++ b/src/atlasComponents/userAnnotations/tools/point.ts
@@ -110,8 +110,6 @@ export class ToolPoint extends AbsToolClass<Point> implements IAnnotationTools,
   public subs: Subscription[] = []
   private managedAnnotations: Point[] = []
   public managedAnnotations$ = new Subject<Point[]>()
-  public allNgAnnotations$ = new Subject<INgAnnotationTypes[keyof INgAnnotationTypes][]>()
-  private forceRefresh$ = new Subject()
 
   constructor(
     annotationEv$: Observable<TAnnotationEvent<keyof IAnnotationEvents>>,
@@ -142,15 +140,8 @@ export class ToolPoint extends AbsToolClass<Point> implements IAnnotationTools,
           space,
           '@type': 'siibra-ex/annotation/point'
         })
-        const { id } = pt
-        pt.remove = () => this.removeAnnotation(id)
-        this.managedAnnotations.push(pt)
-        this.managedAnnotations$.next(this.managedAnnotations)
+        this.addAnnotation(pt)
         
-        /**
-         * force refresh of ng annotation
-         */
-        this.forceRefresh$.next(null)
         /**
          * deselect on selecting a point
          */
@@ -166,32 +157,18 @@ export class ToolPoint extends AbsToolClass<Point> implements IAnnotationTools,
         const foundAnn = this.managedAnnotations.find(ann => ann.id === pickedAnnotationId)
         if (foundAnn) {
           foundAnn.translate(deltaX, deltaY, deltaZ)
-          this.forceRefresh$.next(null)
+          this.managedAnnotations$.next(this.managedAnnotations)
         }
       }),
-      /**
-       * evts which forces redraw of ng annotations
-       */
-      merge(
-        this.forceRefresh$,
-      ).subscribe(() => {
-        let out: INgAnnotationTypes['point'][] = []
-        for (const managedAnn of this.managedAnnotations) {
-          if (managedAnn.space['@id'] === this.space['@id']) {
-            out = out.concat(...managedAnn.toNgAnnotation())
-          }
-        }
-        this.allNgAnnotations$.next(out)
-      })
     )
   }
 
   addAnnotation(point: Point){
     const found = this.managedAnnotations.find(p => p.id === point.id)
     if (found) throw new Error(`Point annotation already added`)
+    point.remove = () => this.removeAnnotation(point.id)
     this.managedAnnotations.push(point)
     this.managedAnnotations$.next(this.managedAnnotations)
-    this.forceRefresh$.next(null)
   }
 
   /**
@@ -206,7 +183,6 @@ export class ToolPoint extends AbsToolClass<Point> implements IAnnotationTools,
     }
     this.managedAnnotations.splice(idx, 1)
     this.managedAnnotations$.next(this.managedAnnotations)
-    this.forceRefresh$.next(null)
   }
 
   onMouseMoveRenderPreview(pos: [number, number, number]) {
diff --git a/src/atlasComponents/userAnnotations/tools/point/point.component.ts b/src/atlasComponents/userAnnotations/tools/point/point.component.ts
index 8dd44c0d7..c28152125 100644
--- a/src/atlasComponents/userAnnotations/tools/point/point.component.ts
+++ b/src/atlasComponents/userAnnotations/tools/point/point.component.ts
@@ -1,8 +1,6 @@
 import { Component, ElementRef, Inject, Input, OnDestroy, Optional, ViewChild } from "@angular/core";
-import { MatSnackBar } from "@angular/material/snack-bar";
 import { Point, POINT_ICON_CLASS } from "../point";
 import { IAnnotationGeometry, TExportFormats, UDPATE_ANNOTATION_TOKEN } from "../type";
-import { Clipboard } from "@angular/cdk/clipboard";
 import { ToolCmpBase } from "../toolCmp.base";
 import { Store } from "@ngrx/store";
 import { viewerStateChangeNavigation } from "src/services/state/viewerState/actions";
@@ -30,12 +28,10 @@ export class PointUpdateCmp extends ToolCmpBase implements OnDestroy{
 
   constructor(
     private store: Store<any>,
-    snackbar: MatSnackBar,
-    clipboard: Clipboard,
     cStore: ComponentStore<{ useFormat: TExportFormats }>,
     @Optional() @Inject(UDPATE_ANNOTATION_TOKEN) updateAnnotation: IAnnotationGeometry,
   ){
-    super(clipboard, snackbar, cStore)
+    super(cStore)
     
     if (updateAnnotation) {
       if (updateAnnotation instanceof Point) {
diff --git a/src/atlasComponents/userAnnotations/tools/point/point.template.html b/src/atlasComponents/userAnnotations/tools/point/point.template.html
index 2eeccd57c..b1b5ab285 100644
--- a/src/atlasComponents/userAnnotations/tools/point/point.template.html
+++ b/src/atlasComponents/userAnnotations/tools/point/point.template.html
@@ -39,25 +39,13 @@
     iav-stop="click">
 
     <div class="iv-custom-comp text">
-      <mat-form-field>
-        <mat-label>
-          {{ useFormat }}
-        </mat-label>
-        <textarea
-          disabled="true"
-          matInput
-          #exportTarget>{{ updateAnnotation.updateSignal$ | async | toFormattedStringPipe : updateAnnotation : useFormat }}</textarea>
-
-        <button mat-icon-button
-          matSuffix
-          iav-stop="click"
-          aria-label="Copy to clipboard"
-          matTooltip="Copy to clipboard."
-          (click)="copyToClipboard(exportTarget.value)"
-          color="basic">
-          <i class="fas fa-copy"></i>
-        </button>
-      </mat-form-field>
+
+      <textarea-copy-export
+        [textarea-copy-export-label]="useFormat"
+        [textarea-copy-export-text]="updateAnnotation.updateSignal$ | async | toFormattedStringPipe : updateAnnotation : useFormat"
+        [textarea-copy-export-download-filename]="updateAnnotation.id + '.sands.json'"
+        [textarea-copy-export-disable]="true">
+      </textarea-copy-export>
     </div>
   </div>
 </mat-menu>
diff --git a/src/atlasComponents/userAnnotations/tools/poly.ts b/src/atlasComponents/userAnnotations/tools/poly.ts
index 4dc49ee30..c0a65bca5 100644
--- a/src/atlasComponents/userAnnotations/tools/poly.ts
+++ b/src/atlasComponents/userAnnotations/tools/poly.ts
@@ -237,8 +237,6 @@ export class ToolPolygon extends AbsToolClass<Polygon> implements IAnnotationToo
   public managedAnnotations$ = new Subject<Polygon[]>()
 
   public subs: Subscription[] = []
-  private forceRefreshAnnotations$ = new Subject()
-  public allNgAnnotations$ = new Subject<INgAnnotationTypes[keyof INgAnnotationTypes][]>()
 
   onMouseMoveRenderPreview(pos: [number, number, number]) {
     if (this.lastAddedPoint) {
@@ -312,6 +310,8 @@ export class ToolPolygon extends AbsToolClass<Polygon> implements IAnnotationToo
           }
         }
 
+        this.managedAnnotations$.next(this.managedAnnotations)
+
         this.selectedPoly = null
         this.lastAddedPoint = null
       }),
@@ -324,15 +324,14 @@ export class ToolPolygon extends AbsToolClass<Polygon> implements IAnnotationToo
         withLatestFrom(this.hoverAnnotation$)
       ).subscribe(([mouseev, ann]) => {
         if (!this.selectedPoly) {
-          this.selectedPoly = new Polygon({
+          const newPoly =  new Polygon({
             edges: [],
             points: [],
             space: this.space,
             '@type': 'siibra-ex/annotation/polyline'
           })
-          const { id } = this.selectedPoly
-          this.selectedPoly.remove = () => this.removeAnnotation(id)
-          this.managedAnnotations.push(this.selectedPoly)
+          this.addAnnotation(newPoly)
+          this.selectedPoly = newPoly
         } else {
 
           if (ann.detail) {
@@ -365,28 +364,6 @@ export class ToolPolygon extends AbsToolClass<Polygon> implements IAnnotationToo
         this.managedAnnotations$.next(this.managedAnnotations)
       }),
 
-      /**
-       * conditions by which ng annotations are refreshed
-       */
-      merge(
-        toolDeselect$,
-        toolSelThenClick$,
-        this.forceRefreshAnnotations$,
-      ).pipe(
-        
-      ).subscribe(() => {
-        let out: INgAnnotationTypes['line'][] = []
-        for (const managedAnn of this.managedAnnotations) {
-          /**
-           * only emit annotations in matching space
-           */
-          if (managedAnn.space["@id"] === this.space["@id"]) {
-            out = out.concat(...managedAnn.toNgAnnotation())
-          }
-        }
-        this.allNgAnnotations$.next(out)
-      }),
-
       /**
        * translate point when on hover a point
        * translate entire annotation when hover edge
@@ -412,7 +389,7 @@ export class ToolPolygon extends AbsToolClass<Polygon> implements IAnnotationToo
           parsedAnnotation.point.translate(deltaX, deltaY, deltaZ)
         }
 
-        this.forceRefreshAnnotations$.next(null)
+        this.managedAnnotations$.next(this.managedAnnotations)
       }),
     )
   }
@@ -420,9 +397,9 @@ export class ToolPolygon extends AbsToolClass<Polygon> implements IAnnotationToo
   addAnnotation(poly: Polygon){
     const idx = this.managedAnnotations.findIndex(ann => ann.id === poly.id)
     if (idx >= 0) throw new Error(`Polygon already added.`)
+    poly.remove = () => this.removeAnnotation(poly.id)
     this.managedAnnotations.push(poly)
     this.managedAnnotations$.next(this.managedAnnotations)
-    this.forceRefreshAnnotations$.next(null)
   }
 
   removeAnnotation(id: string) {
@@ -432,7 +409,6 @@ export class ToolPolygon extends AbsToolClass<Polygon> implements IAnnotationToo
     }
     this.managedAnnotations.splice(idx, 1)
     this.managedAnnotations$.next(this.managedAnnotations)
-    this.forceRefreshAnnotations$.next(null)
   }
 
   ngOnDestroy(){
diff --git a/src/atlasComponents/userAnnotations/tools/poly/poly.component.ts b/src/atlasComponents/userAnnotations/tools/poly/poly.component.ts
index 885c8b41c..9d8371b40 100644
--- a/src/atlasComponents/userAnnotations/tools/poly/poly.component.ts
+++ b/src/atlasComponents/userAnnotations/tools/poly/poly.component.ts
@@ -3,7 +3,6 @@ import { MatSnackBar } from "@angular/material/snack-bar";
 import { Polygon, POLY_ICON_CLASS } from "../poly";
 import { ToolCmpBase } from "../toolCmp.base";
 import { IAnnotationGeometry, TExportFormats, UDPATE_ANNOTATION_TOKEN } from "../type";
-import { Clipboard } from "@angular/cdk/clipboard";
 import { viewerStateChangeNavigation } from "src/services/state/viewerState/actions";
 import { Store } from "@ngrx/store";
 import { Point } from "../point";
@@ -32,12 +31,11 @@ export class PolyUpdateCmp extends ToolCmpBase implements OnDestroy{
 
   constructor(
     private store: Store<any>,
-    snackbar: MatSnackBar,
-    clipboard: Clipboard,
+    private snackbar: MatSnackBar,
     cStore: ComponentStore<{ useFormat: TExportFormats }>,
     @Optional() @Inject(UDPATE_ANNOTATION_TOKEN) updateAnnotation: IAnnotationGeometry,
   ){
-    super(clipboard, snackbar, cStore)
+    super(cStore)
     if (this.cStore) {
       this.sub.push(
         this.cStore.select(store => store.useFormat).subscribe((val: TExportFormats) => {
diff --git a/src/atlasComponents/userAnnotations/tools/poly/poly.template.html b/src/atlasComponents/userAnnotations/tools/poly/poly.template.html
index 07fa27996..ea8665437 100644
--- a/src/atlasComponents/userAnnotations/tools/poly/poly.template.html
+++ b/src/atlasComponents/userAnnotations/tools/poly/poly.template.html
@@ -40,25 +40,12 @@
     iav-stop="click">
 
     <div class="iv-custom-comp text">
-      <mat-form-field>
-        <mat-label>
-          {{ useFormat }}
-        </mat-label>
-        <textarea
-          disabled="true"
-          matInput
-          #exportTarget>{{ updateAnnotation.updateSignal$ | async | toFormattedStringPipe : updateAnnotation : useFormat }}</textarea>
-
-        <button mat-icon-button
-          matSuffix
-          iav-stop="click"
-          aria-label="Copy to clipboard"
-          matTooltip="Copy to clipboard."
-          (click)="copyToClipboard(exportTarget.value)"
-          color="basic">
-          <i class="fas fa-copy"></i>
-        </button>
-      </mat-form-field>
+      <textarea-copy-export
+        [textarea-copy-export-label]="useFormat"
+        [textarea-copy-export-text]="updateAnnotation.updateSignal$ | async | toFormattedStringPipe : updateAnnotation : useFormat"
+        [textarea-copy-export-download-filename]="updateAnnotation.id + '.sands.json'"
+        [textarea-copy-export-disable]="true">
+      </textarea-copy-export>
     </div>
   </div>
 </mat-menu>
diff --git a/src/atlasComponents/userAnnotations/tools/select.ts b/src/atlasComponents/userAnnotations/tools/select.ts
index 2c10e4fe0..b5f614c39 100644
--- a/src/atlasComponents/userAnnotations/tools/select.ts
+++ b/src/atlasComponents/userAnnotations/tools/select.ts
@@ -22,7 +22,6 @@ export class ToolSelect extends AbsToolClass<Point> implements IAnnotationTools,
   removeAnnotation(){}
 
   managedAnnotations$ = new Subject<Point[]>()
-  allNgAnnotations$ = new Subject<TNgAnnotationPoint[]>()
   constructor(
     annotationEv$: Observable<TAnnotationEvent<keyof IAnnotationEvents>>,
     callback: TCallbackFunction
diff --git a/src/atlasComponents/userAnnotations/tools/service.ts b/src/atlasComponents/userAnnotations/tools/service.ts
index b6d4e8143..5af19480f 100644
--- a/src/atlasComponents/userAnnotations/tools/service.ts
+++ b/src/atlasComponents/userAnnotations/tools/service.ts
@@ -7,12 +7,15 @@ import { map, switchMap, filter, shareReplay, pairwise } from "rxjs/operators";
 import { viewerStateSelectedTemplatePureSelector, viewerStateViewerModeSelector } from "src/services/state/viewerState/selectors";
 import { NehubaViewerUnit } from "src/viewerModule/nehuba";
 import { NEHUBA_INSTANCE_INJTKN } from "src/viewerModule/nehuba/util";
-import { AbsToolClass, ANNOTATION_EVENT_INJ_TOKEN, IAnnotationEvents, IAnnotationGeometry, INgAnnotationTypes, INJ_ANNOT_TARGET, TAnnotationEvent, ClassInterface, TCallbackFunction, TSands, TGeometryJson } from "./type";
+import { AbsToolClass, ANNOTATION_EVENT_INJ_TOKEN, IAnnotationEvents, IAnnotationGeometry, INgAnnotationTypes, INJ_ANNOT_TARGET, TAnnotationEvent, ClassInterface, TCallbackFunction, TSands, TGeometryJson, TNgAnnotationLine } from "./type";
 import { switchMapWaitFor } from "src/util/fn";
 import { Polygon } from "./poly";
 import { Line } from "./line";
 import { Point } from "./point";
+import { FilterAnnotationsBySpace } from "../filterAnnotationBySpace.pipe";
+import { retry } from 'common/util'
 
+const LOCAL_STORAGE_KEY = 'userAnnotationKey'
 
 const IAV_VOXEL_SIZES_NM = {
   'minds/core/referencespace/v1.0.0/265d32a0-3d84-40a5-926f-bf89f68212b9': [25000, 25000, 25000],
@@ -71,13 +74,11 @@ export class ModularUserAnnotationToolService implements OnDestroy{
   private ngAnnotationLayer: any
   private activeToolName: string
   private forcedAnnotationRefresh$ = new BehaviorSubject(null)
-  private ngAnnotations$ = new Subject<{
-    tool: string
-    annotations: INgAnnotationTypes[keyof INgAnnotationTypes][]
-  }>()
-
 
   private selectedTmpl: any
+  private selectedTmpl$ = this.store.pipe(
+    select(viewerStateSelectedTemplatePureSelector),
+  )
   public moduleAnnotationTypes: {instance: {name: string, iconClass: string, toolSelected$: Observable<boolean>}, onClick: Function}[] = []
   private managedAnnotationsStream$ = new Subject<{
     tool: string
@@ -85,10 +86,22 @@ export class ModularUserAnnotationToolService implements OnDestroy{
   }>()
 
   private managedAnnotations: IAnnotationGeometry[] = []
+  private filterAnnotationBySpacePipe = new FilterAnnotationsBySpace()
   public managedAnnotations$ = this.managedAnnotationsStream$.pipe(
     scanCollapse(),
     shareReplay(1),
   )
+  public spaceFilteredManagedAnnotations$ = combineLatest([
+    this.selectedTmpl$,
+    this.managedAnnotations$
+  ]).pipe(
+    map(([tmpl, annts]) => {
+      return this.filterAnnotationBySpacePipe.transform(
+        annts,
+        tmpl
+      )
+    })
+  )
 
   private registeredTools: {
     name: string
@@ -147,18 +160,8 @@ export class ModularUserAnnotationToolService implements OnDestroy{
 
     const toolSubscriptions: Subscription[] = []
 
-    const { allNgAnnotations$, managedAnnotations$ } = newTool
+    const { managedAnnotations$ } = newTool
 
-    if ( allNgAnnotations$ ) {
-      toolSubscriptions.push(
-        newTool.allNgAnnotations$.subscribe(ann => {
-          this.ngAnnotations$.next({
-            tool: name,
-            annotations: ann
-          })
-        }),
-      )
-    }
     if ( managedAnnotations$ ){
       toolSubscriptions.push(
         managedAnnotations$.subscribe(ann => {
@@ -205,7 +208,7 @@ export class ModularUserAnnotationToolService implements OnDestroy{
   }
 
   constructor(
-    store: Store<any>,
+    private store: Store<any>,
     @Inject(INJ_ANNOT_TARGET) annotTarget$: Observable<HTMLElement>,
     @Inject(ANNOTATION_EVENT_INJ_TOKEN) private annotnEvSubj: Subject<TAnnotationEvent<keyof IAnnotationEvents>>,
     @Optional() @Inject(NEHUBA_INSTANCE_INJTKN) nehubaViewer$: Observable<NehubaViewerUnit>,
@@ -372,22 +375,27 @@ export class ModularUserAnnotationToolService implements OnDestroy{
     /**
      * on tool managed annotations update
      */
-    const managedAnnotationUpdate$ = combineLatest([
+    const spaceFilteredManagedAnnotationUpdate$ = combineLatest([
       this.forcedAnnotationRefresh$,
-      this.ngAnnotations$.pipe(
-        scanCollapse(),
+      this.spaceFilteredManagedAnnotations$.pipe(
         switchMap(switchMapWaitFor({
           condition: () => !!this.ngAnnotationLayer,
           leading: true
         })),
       )
     ]).pipe(
-      map(([_, ngAnnos]) => ngAnnos),
+      map(([_, annts]) => {
+        const out = []
+        for (const ann of annts) {
+          out.push(...ann.toNgAnnotation())
+        }
+        return out
+      }),
       shareReplay(1),
     )
     this.subscription.push(
       // delete removed annotations
-      managedAnnotationUpdate$.pipe(
+      spaceFilteredManagedAnnotationUpdate$.pipe(
         pairwise(),
         filter(([ oldAnn, newAnn ]) => newAnn.length < oldAnn.length),
       ).subscribe(([ oldAnn, newAnn ]) => {
@@ -398,7 +406,7 @@ export class ModularUserAnnotationToolService implements OnDestroy{
         }
       }), 
       //update annotations
-      managedAnnotationUpdate$.subscribe(arr => {
+      spaceFilteredManagedAnnotationUpdate$.subscribe(arr => {
         const ignoreNgAnnIdsSet = new Set<string>()
         for (const hiddenAnnot of this.hiddenAnnotations) {
           const ids = hiddenAnnot.getNgAnnotationIds()
@@ -454,6 +462,12 @@ export class ModularUserAnnotationToolService implements OnDestroy{
               }
             )
             this.ngAnnotationLayer = viewer.layerManager.addManagedLayer(layer)
+
+            /**
+             * on template changes, the layer gets lost
+             * force redraw annotations if layer needs to be recreated
+             */
+            this.forcedAnnotationRefresh$.next(null)
           }
         } else {
           if (this.ngAnnotationLayer) this.ngAnnotationLayer.setVisible(false)
@@ -466,9 +480,7 @@ export class ModularUserAnnotationToolService implements OnDestroy{
      * required for metadata in annotation geometry and voxel size
      */
     this.subscription.push(
-      store.pipe(
-        select(viewerStateSelectedTemplatePureSelector)
-      ).subscribe(tmpl => {
+      this.selectedTmpl$.subscribe(tmpl => {
         this.selectedTmpl = tmpl
         this.annotnEvSubj.next({
           type: 'metadataEv',
@@ -476,9 +488,74 @@ export class ModularUserAnnotationToolService implements OnDestroy{
             space: tmpl && { ['@id']: tmpl['@id'] }
           }
         })
+        this.forcedAnnotationRefresh$.next(null)
+      }),
+      this.managedAnnotations$.subscribe(ann => {
+        this.managedAnnotations = ann
       }),
-      this.managedAnnotations$.subscribe(ann => this.managedAnnotations = ann),
     )
+
+    /**
+     * on window unload, save annotation
+     */
+
+    /**
+     * before unload, save annotations
+     */
+     window.addEventListener('beforeunload', () => {
+      this.storeAnnotation(this.managedAnnotations)
+    })
+  }
+
+  /**
+   * ensure that loadStoredAnnotation only gets called once
+   */
+  private loadFlag = false
+  public async loadStoredAnnotations(){
+    if (this.loadFlag) return
+    this.loadFlag = true
+    
+    const encoded = window.localStorage.getItem(LOCAL_STORAGE_KEY)
+    if (!encoded) return []
+    const bin = atob(encoded)
+    
+    await retry(() => {
+      if (!!(window as any).export_nehuba) return true
+      else throw new Error(`export nehuba not yet ready`)
+    }, {
+      timeout: 1000,
+      retries: 10
+    })
+    
+    const { pako } = (window as any).export_nehuba
+    const decoded = pako.inflate(bin, { to: 'string' })
+    const arr = JSON.parse(decoded)
+    const anns: IAnnotationGeometry[] = []
+    for (const obj of arr) {
+      const geometry = this.parseAnnotationObject(obj)
+      anns.push(geometry)
+    }
+    
+    for (const ann of anns) {
+      this.importAnnotation(ann)
+    }
+  }
+
+  private storeAnnotation(anns: IAnnotationGeometry[]){
+    const arr = []
+    for (const ann of anns) {
+      const json = ann.toJSON()
+      arr.push(json)
+    }
+    const stringifiedJSON = JSON.stringify(arr)
+    const { pako } = (window as any).export_nehuba
+    const compressed = pako.deflate(stringifiedJSON)
+    let out = ''
+    for (const num of compressed) {
+      out += String.fromCharCode(num)
+    }
+    const encoded = btoa(out)
+    window.localStorage.setItem(LOCAL_STORAGE_KEY, encoded)
   }
 
   private hiddenAnnotationIds = new Set<string>()
diff --git a/src/atlasComponents/userAnnotations/tools/textareaCopyExport/textareaCopyExport.component.ts b/src/atlasComponents/userAnnotations/tools/textareaCopyExport/textareaCopyExport.component.ts
new file mode 100644
index 000000000..a3be425df
--- /dev/null
+++ b/src/atlasComponents/userAnnotations/tools/textareaCopyExport/textareaCopyExport.component.ts
@@ -0,0 +1,52 @@
+import { Component, Input } from "@angular/core";
+import { MatSnackBar } from "@angular/material/snack-bar";
+import { ARIA_LABELS } from 'common/constants'
+import { Clipboard } from "@angular/cdk/clipboard";
+
+@Component({
+  selector: 'textarea-copy-export',
+  templateUrl: './textareaCopyExport.template.html',
+  styleUrls: [
+    './textareaCopyExport.style.css'
+  ]
+})
+
+export class TextareaCopyExportCmp {
+
+  @Input('textarea-copy-export-label')
+  label: string
+
+  @Input('textarea-copy-export-text')
+  input: string
+
+  @Input('textarea-copy-export-rows')
+  rows: number = 20
+
+  @Input('textarea-copy-export-cols')
+  cols: number = 50
+
+
+  @Input('textarea-copy-export-download-filename')
+  filename: string = 'download.txt'
+
+  @Input('textarea-copy-export-disable')
+  disableFlag: boolean = false
+
+  public ARIA_LABELS = ARIA_LABELS
+
+  constructor(
+    private snackbar: MatSnackBar,
+    private clipboard: Clipboard,
+  ){
+
+  }
+
+  copyToClipboard(value: string){
+    const success = this.clipboard.copy(`${value}`)
+    this.snackbar.open(
+      success ? `Copied to clipboard!` : `Failed to copy URL to clipboard!`,
+      null,
+      { duration: 1000 }
+    )
+  }
+}
diff --git a/src/atlasComponents/userAnnotations/tools/textareaCopyExport/textareaCopyExport.style.css b/src/atlasComponents/userAnnotations/tools/textareaCopyExport/textareaCopyExport.style.css
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/atlasComponents/userAnnotations/tools/textareaCopyExport/textareaCopyExport.template.html b/src/atlasComponents/userAnnotations/tools/textareaCopyExport/textareaCopyExport.template.html
new file mode 100644
index 000000000..a82f19952
--- /dev/null
+++ b/src/atlasComponents/userAnnotations/tools/textareaCopyExport/textareaCopyExport.template.html
@@ -0,0 +1,32 @@
+<mat-form-field>
+  <mat-label *ngIf="label">
+    {{ label }}
+  </mat-label>
+  <textarea
+    [rows]="rows"
+    [cols]="cols"
+    [disabled]="disableFlag"
+    matInput
+    #exportTarget>{{ input }}</textarea>
+
+  <button mat-icon-button
+    matSuffix
+    iav-stop="click"
+    aria-label="Copy to clipboard"
+    matTooltip="Copy to clipboard."
+    (click)="copyToClipboard(exportTarget.value)"
+    color="basic">
+    <i class="fas fa-copy"></i>
+  </button>
+  <button mat-icon-button
+    matSuffix
+    iav-stop="click"
+    [matTooltip]="ARIA_LABELS.USER_ANNOTATION_EXPORT_SINGLE"
+    [attr.aria-label]="ARIA_LABELS.USER_ANNOTATION_EXPORT_SINGLE"
+    [single-file-output]="{
+      filename: filename,
+      filecontent: exportTarget.value
+    }">
+    <i class="fas fa-download"></i>
+  </button>
+</mat-form-field>
diff --git a/src/atlasComponents/userAnnotations/tools/toolCmp.base.ts b/src/atlasComponents/userAnnotations/tools/toolCmp.base.ts
index 586ef70c1..6eae6a303 100644
--- a/src/atlasComponents/userAnnotations/tools/toolCmp.base.ts
+++ b/src/atlasComponents/userAnnotations/tools/toolCmp.base.ts
@@ -1,5 +1,3 @@
-import { MatSnackBar } from "@angular/material/snack-bar"
-import { Clipboard } from "@angular/cdk/clipboard";
 import { ARIA_LABELS } from 'common/constants'
 import { ComponentStore } from "src/viewerModule/componentStore";
 import { TExportFormats } from "./type";
@@ -13,8 +11,6 @@ export abstract class ToolCmpBase {
 
   protected sub: Subscription[] = []
   constructor(
-    protected clipboard: Clipboard,
-    protected snackbar: MatSnackBar,
     protected cStore: ComponentStore<{ useFormat: TExportFormats }>,
   ){
 
@@ -35,15 +31,6 @@ export abstract class ToolCmpBase {
     }
   }
 
-  copyToClipboard(value: string){
-    const success = this.clipboard.copy(`${value}`)
-    this.snackbar.open(
-      success ? `Copied to clipboard!` : `Failed to copy URL to clipboard!`,
-      null,
-      { duration: 1000 }
-    )
-  }
-
   /**
    * Intention of navigating to ROI
    */
diff --git a/src/atlasComponents/userAnnotations/tools/type.ts b/src/atlasComponents/userAnnotations/tools/type.ts
index 2355942cc..ce9f776c1 100644
--- a/src/atlasComponents/userAnnotations/tools/type.ts
+++ b/src/atlasComponents/userAnnotations/tools/type.ts
@@ -23,11 +23,6 @@ export abstract class AbsToolClass<T extends IAnnotationGeometry> {
   abstract subs: Subscription[]
   protected space: TBaseAnnotationGeomtrySpec['space']
 
-  /**
-   * @description to be overwritten by subclass. Emit the latest representation of NgAnnotations from the tool.
-   */
-  public abstract allNgAnnotations$: Observable<INgAnnotationTypes[keyof INgAnnotationTypes][]>
-
   /**
    * @description to be overwritten by subclass. Called once every mousemove event, if the tool is active.
    * @param {[number, number, number]} mousepos
diff --git a/src/ui/topMenu/topMenuCmp/topMenu.template.html b/src/ui/topMenu/topMenuCmp/topMenu.template.html
index 89939c696..55182a62e 100644
--- a/src/ui/topMenu/topMenuCmp/topMenu.template.html
+++ b/src/ui/topMenu/topMenuCmp/topMenu.template.html
@@ -154,6 +154,7 @@
   <button mat-menu-item
     [disabled]="!viewerLoaded"
     annotation-switch
+    annotation-switch-mode="on"
     [matTooltip]="annotateTooltipText">
     <mat-icon fontSet="fas" fontIcon="fa-pencil-ruler">
     </mat-icon>
diff --git a/src/viewerModule/viewerCmp/viewerCmp.template.html b/src/viewerModule/viewerCmp/viewerCmp.template.html
index 4d51060ef..b64d1bcc3 100644
--- a/src/viewerModule/viewerCmp/viewerCmp.template.html
+++ b/src/viewerModule/viewerCmp/viewerCmp.template.html
@@ -10,7 +10,7 @@
     <mat-drawer-container class="mat-drawer-content-overflow-visible w-100 h-100 position-absolute invisible"
       [hasBackdrop]="false">
       <mat-drawer #annotationDrawer
-        [mode]="'push'"
+        mode="side"
         [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">
@@ -22,7 +22,7 @@
         <iav-layout-fourcorners>
 
           <!-- pullable tab top right corner -->
-          <div iavLayoutFourCornersTopLeft class="mt-5">
+          <div iavLayoutFourCornersTopLeft class="tab-toggle-container">
             <div>
               <ng-container *ngTemplateOutlet="tabTmpl_defaultTmpl; context: {
                 matColor: 'primary',
@@ -36,6 +36,21 @@
             <annotating-tools-panel class="z-index-10">
             </annotating-tools-panel>
           </div>
+
+          <div iavLayoutFourCornersTopRight>
+            <mat-card class="mat-card-sm pe-all m-4">
+              <span>
+                Annotating
+              </span>
+              <button mat-icon-button
+                [matTooltip]="ARIA_LABELS.EXIT_ANNOTATION_MODE"
+                color="warn"
+                annotation-switch
+                annotation-switch-mode="off">
+                <i class="fas fa-times"></i>
+              </button>
+            </mat-card>
+          </div>
         </iav-layout-fourcorners>
   
 
diff --git a/src/zipFilesOutput/downloadSingleFile.directive.ts b/src/zipFilesOutput/downloadSingleFile.directive.ts
new file mode 100644
index 000000000..1ad93e0fa
--- /dev/null
+++ b/src/zipFilesOutput/downloadSingleFile.directive.ts
@@ -0,0 +1,32 @@
+import { DOCUMENT } from "@angular/common";
+import { Directive, HostListener, Inject, Input } from "@angular/core";
+import { TZipFileConfig } from "./type";
+
+@Directive({
+  selector: '[single-file-output]',
+  exportAs: 'singleFileOutput'
+})
+
+export class SingleFileOutput {
+
+  @Input('single-file-output')
+  singleFile: TZipFileConfig
+
+  @HostListener('click')
+  onClick(){
+    const anchor = this.doc.createElement('a')
+    const blob = new Blob([this.singleFile.filecontent], { type: 'text/plain' })
+    anchor.href = URL.createObjectURL(blob)
+    anchor.download = this.singleFile.filename
+
+    this.doc.body.appendChild(anchor)
+    anchor.click()
+    this.doc.body.removeChild(anchor)
+    URL.revokeObjectURL(anchor.href)
+  }
+  constructor(
+    @Inject(DOCUMENT) private doc: Document
+  ){
+
+  }
+}
diff --git a/src/zipFilesOutput/module.ts b/src/zipFilesOutput/module.ts
index 59e58e224..7eeff21ca 100644
--- a/src/zipFilesOutput/module.ts
+++ b/src/zipFilesOutput/module.ts
@@ -1,5 +1,6 @@
 import { CommonModule } from "@angular/common";
 import { NgModule } from "@angular/core";
+import { SingleFileOutput } from "./downloadSingleFile.directive";
 import { ZipFilesOutput } from "./zipFilesOutput.directive";
 
 @NgModule({
@@ -7,10 +8,12 @@ import { ZipFilesOutput } from "./zipFilesOutput.directive";
     CommonModule,
   ],
   declarations: [
-    ZipFilesOutput
+    ZipFilesOutput,
+    SingleFileOutput,
   ],
   exports: [
-    ZipFilesOutput
+    ZipFilesOutput,
+    SingleFileOutput,
   ]
 })
 
-- 
GitLab